diff --git a/README.md b/README.md index f049fd8..e2cd63d 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,9 @@ One-line installer that works on most Linux distributions: curl -fsSL https://raw.githubusercontent.com/Nomadcxx/sysc-greet/master/install.sh | sudo bash ``` -The installer automatically detects your package manager and works on Arch Linux, Debian/Ubuntu, Fedora, and openSUSE. It'll handle compositor selection, install dependencies, and set everything up for you. +The installer automatically detects your package manager and works on Arch Linux, Debian/Ubuntu, Fedora, and openSUSE. It'll handle greeter backend selection, install dependencies, and set everything up for you. + +> **Note:** Hyprland greeter support is being phased out over the next ~3 months. New installs should choose **cage** (recommended) or **niri**. ### Build from Source @@ -34,13 +36,14 @@ The installer walks you through compositor selection and configuration. ### Arch Linux (AUR) -Three AUR packages available depending on which compositor you're using: +Three AUR packages available depending on which greeter backend you're using: ```bash -# Recommended (niri) +# Recommended (cage — install script, or: pacman -S cage && SYSC_COMPOSITOR=cage ./install.sh) +# niri (full wallpapers) yay -S sysc-greet -# Hyprland variant +# Hyprland variant (deprecated — migrate to cage) yay -S sysc-greet-hyprland # Sway variant @@ -79,7 +82,7 @@ If you're on NixOS, add sysc-greet to your flake: { services.sysc-greet = { enable = true; - compositor = "niri"; # or "hyprland" or "sway" + compositor = "cage"; # recommended — or "niri", "sway", "hyprland" (deprecated) }; # Optional: Set initial session for auto-login @@ -90,8 +93,8 @@ If you're on NixOS, add sysc-greet to your flake: } ``` -By default, the NixOS module does not install `niri`, `hyprland`, or `sway`. -Install your chosen compositor yourself, or set `niriPackage`, `hyprlandPackage`, +By default, the NixOS module does not install `cage`, `niri`, `hyprland`, or `sway`. +Install your chosen backend yourself, or set `cagePackage`, `niriPackage`, `hyprlandPackage`, or `swayPackage` if you want the module to install and use a specific package. If your compositor is managed elsewhere, set `compositorCommand` to the exact command greetd should run. diff --git a/cmd/installer/main.go b/cmd/installer/main.go index 463e8c6..be022c3 100644 --- a/cmd/installer/main.go +++ b/cmd/installer/main.go @@ -69,6 +69,9 @@ var binaryToPackage = map[string]map[string]string{ "yum": { "ninja": "ninja-build", }, + "xbps": { + "ninja": "ninja", + }, } // getBinaryPackageName returns the package name for a binary, accounting for distro differences @@ -96,6 +99,8 @@ func checkPackageInstalled(pkg, packageManager string) bool { return strings.Contains(string(output), "install ok installed") case "dnf", "yum", "zypper": cmd = exec.Command("rpm", "-q", pkg) + case "xbps": + cmd = exec.Command("xbps-query", pkg) default: return false } @@ -116,6 +121,8 @@ func checkPackageExists(pkg, packageManager string) bool { cmd = exec.Command("yum", "info", pkg) case "zypper": cmd = exec.Command("zypper", "info", pkg) + case "xbps": + cmd = exec.Command("xbps-query", "-Rs", pkg) default: return false } @@ -219,11 +226,15 @@ type model struct { spinner spinner.Model errors []string packageManager string - greetdInstalled bool + initSystem string + distroID string + greeterUser string + greeterHome string + greetdInstalled bool needsGreetd bool uninstallMode bool selectedOption int // 0 = Install, 1 = Uninstall - selectedCompositor string // "niri", "hyprland", or "sway" + selectedCompositor string // "niri", "hyprland", "sway", or "cage" compositorIndex int // Current selection in compositor menu debugMode bool // Show verbose output logFile *os.File // Installer log file @@ -291,8 +302,9 @@ func newModel(debugMode bool, logFile *os.File) model { beams: beams, } - // Detect package manager during initialization (not in async task) + // Detect package manager and platform during initialization (not in async task) detectPackageManager(&m) + detectPlatform(&m) // Check for pre-selected compositor from environment variable if comp := os.Getenv("SYSC_COMPOSITOR"); comp != "" { @@ -340,7 +352,7 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case "down", "j": if m.step == stepWelcome && m.selectedOption < 1 { m.selectedOption++ - } else if m.step == stepCompositorSelect && m.compositorIndex < 2 { + } else if m.step == stepCompositorSelect && m.compositorIndex < 3 { m.compositorIndex++ } case "enter": @@ -373,7 +385,7 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { } } else if m.step == stepCompositorSelect { // Set compositor based on selection - compositors := []string{"niri", "hyprland", "sway"} + compositors := []string{"cage", "niri", "sway", "hyprland"} m.selectedCompositor = compositors[m.compositorIndex] // Validate compositor is installed @@ -381,6 +393,7 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { "niri": {"niri"}, "hyprland": {"Hyprland", "hyprland"}, "sway": {"sway"}, + "cage": {"cage"}, } compositorInstalled := false @@ -563,9 +576,10 @@ func (m model) renderCompositorSelect() string { name string desc string }{ - {"niri", "Tiling compositor with scrollable workspaces"}, - {"hyprland", "Dynamic tiling compositor with extensive features"}, - {"sway", "Stable i3-compatible tiling compositor"}, + {"cage", "Recommended — minimal kiosk; fast boot; TUI backgrounds only"}, + {"niri", "Tiling compositor with scrollable workspaces + gSlapper wallpapers"}, + {"sway", "Stable i3-compatible tiling compositor + gSlapper wallpapers"}, + {"hyprland", "Deprecated — greeter support ending in ~3 months; migrate to cage"}, } for i, comp := range compositors { @@ -577,7 +591,15 @@ func (m model) renderCompositorSelect() string { b.WriteString(" " + comp.desc + "\n\n") } - b.WriteString(lipgloss.NewStyle().Foreground(FgMuted).Render("The greeter will work identically on all compositors")) + b.WriteString(lipgloss.NewStyle().Foreground(FgMuted).Render("Hyprland greeter support is being phased out — cage is the recommended path")) + if m.distroID == "void" { + b.WriteString("\n") + b.WriteString(lipgloss.NewStyle().Foreground(FgMuted).Render("Void Linux: uses xbps + runit (_greeter user). cage skips gSlapper build.")) + } + if m.compositorIndex == 3 { + b.WriteString("\n") + b.WriteString(lipgloss.NewStyle().Foreground(ErrorColor).Render("⚠ Hyprland will be removed from the greeter in ~3 months. Use cage or niri instead.")) + } // Show errors if any if len(m.errors) > 0 { @@ -802,6 +824,7 @@ func detectPackageManager(m *model) { {"dnf", "/usr/bin/dnf"}, {"yum", "/usr/bin/yum"}, // Older Fedora/RHEL {"zypper", "/usr/bin/zypper"}, // openSUSE + {"xbps", "/usr/bin/xbps-install"}, // Void Linux } for _, pm := range packageManagers { @@ -827,12 +850,15 @@ func detectPackageManager(m *model) { func checkDependencies(m *model) error { missing := []string{} - // Check critical deps if _, err := exec.LookPath("go"); err != nil { missing = append(missing, "go") } - if _, err := exec.LookPath("systemctl"); err != nil { - missing = append(missing, "systemd") + + switch m.initSystem { + case initSystemd, initRunit: + // supported + case "": + missing = append(missing, "init (systemd or runit)") } if len(missing) > 0 { @@ -897,6 +923,9 @@ func installGreetd(m *model) error { case "zypper": cmd = exec.Command("zypper", "install", "-y", "greetd") + case "xbps": + cmd = exec.Command("xbps-install", xbpsInstallArgs("greetd")...) + default: return fmt.Errorf("unsupported package manager '%s' - install greetd manually", m.packageManager) } @@ -905,6 +934,7 @@ func installGreetd(m *model) error { return fmt.Errorf("failed to install greetd (try: manual installation)") } + resolveGreeterAccount(m) return nil } @@ -936,6 +966,9 @@ func installKitty(m *model) error { case "zypper": cmd = exec.Command("zypper", "install", "-y", "kitty") + case "xbps": + cmd = exec.Command("xbps-install", xbpsInstallArgs("kitty")...) + default: return fmt.Errorf("unsupported package manager '%s' - install kitty manually", m.packageManager) } @@ -953,6 +986,7 @@ func installCompositor(m *model) error { "niri": {"niri"}, "hyprland": {"Hyprland", "hyprland"}, "sway": {"sway"}, + "cage": {"cage"}, } // Check if compositor already installed @@ -996,6 +1030,8 @@ func installCompositor(m *model) error { cmd = exec.Command("pacman", "-S", "--noconfirm", "hyprland") case "sway": cmd = exec.Command("pacman", "-S", "--noconfirm", "sway") + case "cage": + cmd = exec.Command("pacman", "-S", "--noconfirm", "cage") } case "apt": @@ -1007,6 +1043,8 @@ func installCompositor(m *model) error { return fmt.Errorf("hyprland not in standard apt repos - see https://hyprland.org for installation") case "sway": cmd = exec.Command("apt-get", "install", "-y", "sway") + case "cage": + return fmt.Errorf("cage not in standard apt repos — build from https://github.com/cage-kiosk/cage") } case "dnf": @@ -1018,6 +1056,8 @@ func installCompositor(m *model) error { return fmt.Errorf("hyprland not in standard dnf repos - see https://hyprland.org for installation") case "sway": cmd = exec.Command("dnf", "install", "-y", "sway") + case "cage": + return fmt.Errorf("cage not in standard dnf repos — build from https://github.com/cage-kiosk/cage") } case "yum": @@ -1040,6 +1080,18 @@ func installCompositor(m *model) error { return fmt.Errorf("%s may not be in zypper repos - install manually", m.selectedCompositor) } + case "xbps": + switch m.selectedCompositor { + case "niri": + cmd = exec.Command("xbps-install", xbpsInstallArgs("niri")...) + case "sway": + cmd = exec.Command("xbps-install", xbpsInstallArgs("sway")...) + case "cage": + cmd = exec.Command("xbps-install", xbpsInstallArgs("cage")...) + case "hyprland": + return fmt.Errorf("hyprland not in Void repos — use cage or niri") + } + default: return fmt.Errorf("unsupported package manager '%s' - install %s manually", m.packageManager, m.selectedCompositor) } @@ -1056,6 +1108,14 @@ func installCompositor(m *model) error { } func installGslapper(m *model) error { + // Cage lite mode has no wallpaper daemon. + if m.selectedCompositor == "cage" { + for i := 0; i < 6; i++ { + updateSubTaskStatus(m, i, statusSkipped) + } + return nil + } + // Sub-task 0: Check if already installed updateSubTaskStatus(m, 0, statusRunning) @@ -1140,6 +1200,17 @@ func getGStreamerDeps(packageManager string) []string { "wayland-devel", "wayland-protocols-devel", } + case "xbps": + return []string{ + "gstreamer1-devel", + "gst-plugins-base1-devel", + "gst-plugins-good1", + "gst-plugins-bad1", + "wayland-devel", + "wayland-protocols", + "mesa", + "pkg-config", + } default: return []string{} } @@ -1210,6 +1281,8 @@ func installGStreamerDeps(m *model) error { case "zypper": args := append([]string{"install", "-y"}, result.toInstall...) cmd = exec.Command("zypper", args...) + case "xbps": + cmd = exec.Command("xbps-install", xbpsInstallArgs(result.toInstall...)...) default: return fmt.Errorf("unsupported package manager for GStreamer deps") } @@ -1270,6 +1343,8 @@ func buildGslapperFromSource(m *model) error { case "zypper": args := append([]string{"install", "-y"}, missingPackages...) cmd = exec.Command("zypper", args...) + case "xbps": + cmd = exec.Command("xbps-install", xbpsInstallArgs(missingPackages...)...) default: updateSubTaskStatus(m, 3, statusFailed) return fmt.Errorf("missing build tools: %s", strings.Join(missingBinaries, ", ")) @@ -1403,43 +1478,42 @@ func installConfigs(m *model) error { } func setupCache(m *model) error { + resolveGreeterAccount(m) + greeterHome := m.greeterHome + greeterUser := m.greeterUser + wallpaperDir := greeterHome + "/Pictures/wallpapers" + // Create cache directory if err := exec.Command("mkdir", "-p", "/var/cache/sysc-greet").Run(); err != nil { return fmt.Errorf("cache dir creation failed") } - // Create greeter home - if err := exec.Command("mkdir", "-p", "/var/lib/greeter/Pictures/wallpapers").Run(); err != nil { + // Create greeter home wallpaper dir + if err := exec.Command("mkdir", "-p", wallpaperDir).Run(); err != nil { return fmt.Errorf("greeter home creation failed") } - // Create greeter user if needed - // FIXED 2025-10-15 - Add render group for modern Intel/AMD iGPU support - // Modern Linux uses 'render' group for /dev/dri/renderD* (non-privileged GPU access) - // Both 'video' and 'render' groups needed for laptop iGPU compatibility - cmd := exec.Command("id", "greeter") - if err := cmd.Run(); err != nil { - // User doesn't exist - create with video,render,input groups - cmd = exec.Command("useradd", "-M", "-G", "video,render,input", "-s", "/usr/bin/nologin", "greeter") + groups := greeterGroups() + + // Create greeter user if needed (non-Void path) + if !userExists(greeterUser) { + cmd := exec.Command("useradd", "-M", "-G", groups, "-s", "/usr/bin/nologin", "-d", greeterHome, greeterUser) if err := cmd.Run(); err != nil { return fmt.Errorf("greeter user creation failed") } } else { - // User exists - ensure they have required groups - // CRITICAL: This fixes laptops where greeter user exists but lacks render group - exec.Command("usermod", "-aG", "video,render,input", "greeter").Run() + exec.Command("usermod", "-aG", groups, greeterUser).Run() } // Set ownership - paths := []string{"/var/cache/sysc-greet", "/var/lib/greeter"} + paths := []string{"/var/cache/sysc-greet", greeterHome} for _, path := range paths { - if err := exec.Command("chown", "-R", "greeter:greeter", path).Run(); err != nil { + if err := exec.Command("chown", "-R", greeterUser+":"+greeterUser, path).Run(); err != nil { return fmt.Errorf("ownership change failed for %s", path) } } - // Set permissions - if err := exec.Command("chmod", "755", "/var/lib/greeter").Run(); err != nil { + if err := exec.Command("chmod", "755", greeterHome).Run(); err != nil { return fmt.Errorf("permissions change failed") } @@ -1612,12 +1686,32 @@ exec "XDG_CACHE_HOME=/tmp/greeter-cache HOME=/var/lib/greeter kitty --start-as=f configPath = "/etc/greetd/sway-greeter-config" greetdCommand = "sway --unsupported-gpu -c /etc/greetd/sway-greeter-config" + case "cage": + compositorConfig = `#!/bin/sh +# SYSC-Greet Cage launcher (lite mode — no gSlapper wallpaper daemon) +# See docs-src/compositors/cage.md + +set -eu + +export XDG_CACHE_HOME=/tmp/greeter-cache +export HOME=/var/lib/greeter + +exec kitty --start-as=fullscreen --config=/etc/greetd/kitty.conf /usr/local/bin/sysc-greet +` + configPath = "/etc/greetd/cage-greeter-session.sh" + greetdCommand = "cage -s -m extend -- /etc/greetd/cage-greeter-session.sh" + default: return fmt.Errorf("unknown compositor: %s", m.selectedCompositor) } - // Write compositor config - if err := os.WriteFile(configPath, []byte(compositorConfig), 0644); err != nil { + // Write compositor config (or cage launcher script) + configMode := os.FileMode(0644) + if m.selectedCompositor == "cage" { + configMode = 0755 + } + compositorConfig = substituteGreeterPaths(compositorConfig, m) + if err := os.WriteFile(configPath, []byte(compositorConfig), configMode); err != nil { return fmt.Errorf("compositor config write failed") } @@ -1627,12 +1721,12 @@ vt = 1 [default_session] command = "%s" -user = "greeter" +user = "%s" [initial_session] command = "%s" -user = "greeter" -`, greetdCommand, greetdCommand) +user = "%s" +`, greetdCommand, m.greeterUser, greetdCommand, m.greeterUser) if err := os.WriteFile("/etc/greetd/config.toml", []byte(greetdConfig), 0644); err != nil { return fmt.Errorf("greetd config write failed") @@ -1652,7 +1746,7 @@ user = "greeter" if err := os.MkdirAll("/etc/polkit-1/rules.d", 0755); err != nil { return fmt.Errorf("polkit rules directory creation failed") } - if err := os.WriteFile("/etc/polkit-1/rules.d/85-greeter.rules", []byte(polkitRule), 0644); err != nil { + if err := os.WriteFile("/etc/polkit-1/rules.d/85-greeter.rules", []byte(substituteGreeterPaths(polkitRule, m)), 0644); err != nil { return fmt.Errorf("polkit rule write failed") } @@ -1660,30 +1754,13 @@ user = "greeter" } func enableService(m *model) error { - // Remove existing display-manager.service symlink - symlinkPath := "/etc/systemd/system/display-manager.service" - if _, err := os.Lstat(symlinkPath); err == nil { - os.Remove(symlinkPath) - } - - // Enable greetd - cmd := exec.Command("systemctl", "enable", "greetd.service") - if err := cmd.Run(); err != nil { - return fmt.Errorf("service enable failed") - } - - return nil + return enableGreeterService(m) } // Uninstall functions func disableService(m *model) error { - // Disable greetd service - if err := exec.Command("systemctl", "disable", "greetd.service").Run(); err != nil { - // Not a critical error if it's already disabled - return nil - } - return nil + return disableGreeterService(m) } func removeBinary(m *model) error { @@ -1702,6 +1779,7 @@ func removeConfigs(m *model) error { "/etc/greetd/niri-greeter-config.kdl", "/etc/greetd/hyprland-greeter-config.conf", "/etc/greetd/sway-greeter-config", + "/etc/greetd/cage-greeter-session.sh", } for _, path := range paths { diff --git a/cmd/installer/platform.go b/cmd/installer/platform.go new file mode 100644 index 0000000..4afc859 --- /dev/null +++ b/cmd/installer/platform.go @@ -0,0 +1,170 @@ +package main + +import ( + "bufio" + "fmt" + "os" + "os/exec" + "strings" +) + +const ( + initSystemd = "systemd" + initRunit = "runit" +) + +// detectPlatform fills init system, distro id, and default greeter account hints. +func detectPlatform(m *model) { + m.distroID = readOSRelease("ID") + m.initSystem = detectInitSystem() + resolveGreeterAccount(m) + + if m.debugMode { + fmt.Fprintf(os.Stderr, "[DEBUG] Platform: distro=%s init=%s greeter=%s home=%s\n", + m.distroID, m.initSystem, m.greeterUser, m.greeterHome) + } +} + +func readOSRelease(key string) string { + file, err := os.Open("/etc/os-release") + if err != nil { + return "" + } + defer file.Close() + + prefix := key + "=" + scanner := bufio.NewScanner(file) + for scanner.Scan() { + line := scanner.Text() + if strings.HasPrefix(line, prefix) { + value := strings.TrimPrefix(line, prefix) + return strings.Trim(value, `"`) + } + } + return "" +} + +func detectInitSystem() string { + if _, err := exec.LookPath("systemctl"); err == nil { + if _, err := os.Stat("/run/systemd/system"); err == nil { + return initSystemd + } + } + if _, err := os.Stat("/etc/sv"); err == nil { + return initRunit + } + if _, err := os.Stat("/var/service"); err == nil { + return initRunit + } + return "" +} + +func resolveGreeterAccount(m *model) { + // Void's greetd package provisions _greeter — use it when present. + if userExists("_greeter") || m.distroID == "void" { + m.greeterUser = "_greeter" + m.greeterHome = "/var/lib/_greeter" + return + } + m.greeterUser = "greeter" + m.greeterHome = "/var/lib/greeter" +} + +func userExists(username string) bool { + return exec.Command("id", username).Run() == nil +} + +func substituteGreeterPaths(content string, m *model) string { + if m.greeterUser == "" || m.greeterHome == "" { + resolveGreeterAccount(m) + } + out := strings.ReplaceAll(content, "/var/lib/greeter", m.greeterHome) + out = strings.ReplaceAll(out, "user = \"greeter\"", fmt.Sprintf("user = \"%s\"", m.greeterUser)) + out = strings.ReplaceAll(out, "subject.user == \"greeter\"", fmt.Sprintf("subject.user == \"%s\"", m.greeterUser)) + return out +} + +func xbpsInstallArgs(packages ...string) []string { + args := []string{"install", "-Sy"} + return append(args, packages...) +} + +func enableGreeterService(m *model) error { + switch m.initSystem { + case initRunit: + return enableRunitGreetd(m) + case initSystemd: + return enableSystemdGreetd() + case "": + return fmt.Errorf("no supported init system detected (need systemd or runit)") + default: + return fmt.Errorf("unsupported init system: %s", m.initSystem) + } +} + +func disableGreeterService(m *model) error { + switch m.initSystem { + case initRunit: + _ = os.Remove("/var/service/greetd") + return nil + case initSystemd: + _ = exec.Command("systemctl", "disable", "greetd.service").Run() + return nil + default: + return nil + } +} + +func enableSystemdGreetd() error { + symlinkPath := "/etc/systemd/system/display-manager.service" + if _, err := os.Lstat(symlinkPath); err == nil { + os.Remove(symlinkPath) + } + cmd := exec.Command("systemctl", "enable", "greetd.service") + if err := cmd.Run(); err != nil { + return fmt.Errorf("systemd enable failed") + } + return nil +} + +func enableRunitGreetd(m *model) error { + if _, err := os.Stat("/etc/sv/greetd"); err != nil { + return fmt.Errorf("runit service missing at /etc/sv/greetd — reinstall greetd") + } + + serviceLink := "/var/service/greetd" + if _, err := os.Lstat(serviceLink); err == nil { + if m.debugMode { + fmt.Fprintf(os.Stderr, "[DEBUG] greetd runit service already enabled\n") + } + } else { + if err := os.Symlink("/etc/sv/greetd", serviceLink); err != nil { + return fmt.Errorf("failed to symlink /var/service/greetd: %w", err) + } + } + + // Void greetd README: disable agetty on the greeter VT (we use vt=1). + agettyLinks := []string{ + "/var/service/agetty-tty1", + "/etc/service/agetty-tty1", + } + for _, link := range agettyLinks { + if _, err := os.Lstat(link); err == nil { + if err := os.Remove(link); err != nil && m.debugMode { + fmt.Fprintf(os.Stderr, "[DEBUG] could not remove %s: %v\n", link, err) + } else if m.debugMode { + fmt.Fprintf(os.Stderr, "[DEBUG] disabled conflicting service %s\n", link) + } + } + } + + return nil +} + +func greeterGroups() string { + // Void greetd template only adds video; render may not exist. + if _, err := exec.Command("getent", "group", "render").Output(); err == nil { + return "video,render,input" + } + return "video,input" +} diff --git a/docs-src/compositors/cage.md b/docs-src/compositors/cage.md new file mode 100644 index 0000000..ef83fae --- /dev/null +++ b/docs-src/compositors/cage.md @@ -0,0 +1,157 @@ +# Cage Setup + +!!! tip "Recommended greeter backend" + Cage is the **recommended** Wayland backend for the sysc-greet greeter session. It replaces Hyprland for greetd, which is being [phased out over the next ~3 months](hyprland.md). + +!!! warning "Cage Lite — no boot wallpapers" + Cage is a **single-client kiosk** backend. Unlike niri, Hyprland, and sway, it cannot run gSlapper as a second Wayland client. + + **What you lose:** boot static/video wallpapers, in-greeter wallpaper changes via gSlapper + **What you keep:** login, themes, TUI background effects, ASCII art, session selection + +See the [design spec](https://github.com/Nomadcxx/sysc-greet/blob/master/docs/superpowers/specs/2026-06-18-cage-compositor-design.md) for the full investigation. + +## Why Cage? + +- **Faster, simpler greeter session** — no Hyprland config, window rules, layer rules, or `hyprctl` exit chain +- **Fewer moving parts** — one compositor binary, one launcher script +- **Good fit for kiosk-style greeters** — fullscreen Kitty is exactly what Cage is designed for + +Tracked in [issue #69](https://github.com/Nomadcxx/sysc-greet/issues/69). + +## Install Cage + +=== "Arch Linux" + +```bash +sudo pacman -S cage +``` + +=== "NixOS" + +```nix +services.sysc-greet = { + enable = true; + compositor = "cage"; +}; +``` + +=== "Other distros" + +Build from [cage-kiosk/cage](https://github.com/cage-kiosk/cage) or check your package manager. + +## greetd Config + +=== "Installer" + +```bash +SYSC_COMPOSITOR=cage sudo ./install.sh +``` + +=== "Manual" + +Edit `/etc/greetd/config.toml`: + +```toml +[terminal] +vt = 1 + +[default_session] +command = "cage -s -m extend -- /etc/greetd/cage-greeter-session.sh" +user = "greeter" + +[initial_session] +command = "cage -s -m extend -- /etc/greetd/cage-greeter-session.sh" +user = "greeter" +``` + +Install the launcher script from `scripts/cage-greeter-session.sh` to `/etc/greetd/cage-greeter-session.sh` (mode `755`). + +### Flags + +| Flag | Purpose | +|------|---------| +| `-s` | Allow VT switching | +| `-m extend` | Span the greeter across all connected outputs | + +Cage exits automatically when Kitty (its sole client) exits after a successful login. + +## Launcher Script + +`/etc/greetd/cage-greeter-session.sh`: + +```sh +#!/bin/sh +export XDG_CACHE_HOME=/tmp/greeter-cache +export HOME=/var/lib/greeter +exec kitty --start-as=fullscreen --config=/etc/greetd/kitty.conf /usr/local/bin/sysc-greet +``` + +## Keyboard Layout + +Cage has **no compositor keyboard configuration**. Set XKB environment variables on the Kitty line (in the launcher script): + +```sh +export XKB_DEFAULT_LAYOUT=fr +export XKB_DEFAULT_VARIANT=oss # if needed +exec kitty ... +``` + +See [Keyboard Layout](../configuration/keyboard-layout.md) for layout codes. + +## Feature Comparison + +| Feature | niri / Hyprland / sway | Cage Lite | +|---------|------------------------|-----------| +| Login via greetd | ✅ | ✅ | +| TUI background effects | ✅ | ✅ | +| Theme colors / ASCII | ✅ | ✅ | +| Boot static wallpaper | ✅ | ❌ | +| Video wallpaper | ✅ | ❌ | +| gSlapper layer-shell | ✅ | ❌ | +| Compositor config file | Required | None | +| Hyprland maintenance | Ongoing | N/A | + +## Verification + +```bash +# Static checks (from repo root) +./scripts/verify-cage-greeter.sh + +# Restart greetd (from a TTY) +sudo systemctl restart greetd +journalctl -u greetd -b --no-pager | tail -30 +``` + +## Cagebreak (future) + +[Cagebreak](https://github.com/project-repo/cagebreak) is a tiling compositor forked from Cage. It may support multiple clients and wlr-layer-shell, which would restore wallpaper parity. This is **not yet implemented** — see the implementation plan for the Phase 2 spike. + +## Migrating from Hyprland + +If you currently use Hyprland for the greetd greeter session: + +```bash +sudo pacman -S cage # or your distro's cage package +SYSC_COMPOSITOR=cage sudo ./install.sh +sudo systemctl restart greetd +``` + +Your Hyprland **desktop session** is unchanged — only the boot greeter moves to Cage. + +**Trade-off:** Cage Lite has no gSlapper boot wallpapers. Use TUI background effects (F3) instead, or switch to niri if you need video/static wallpapers at the greeter. + +## Troubleshooting + +**Greeter does not appear** + +- Confirm `cage` is in PATH for the greeter user +- Check `journalctl -u greetd` for cage startup errors + +**Password rejected with non-US keyboard** + +- Add `XKB_DEFAULT_LAYOUT` (and variant if needed) to the launcher script + +**Expected wallpapers missing** + +- This is by design in Cage Lite mode. Use TUI background effects (F3) or switch to niri/Hyprland/sway for gSlapper wallpapers. diff --git a/docs-src/compositors/hyprland.md b/docs-src/compositors/hyprland.md index cb1c43e..eb18e28 100644 --- a/docs-src/compositors/hyprland.md +++ b/docs-src/compositors/hyprland.md @@ -1,5 +1,12 @@ # Hyprland Setup +!!! warning "Deprecated — greeter support ending in ~3 months" + Hyprland remains functional today, but **sysc-greet is phasing out Hyprland for the greetd greeter session**. + + **Migrate to [Cage](cage.md)** (recommended — faster, simpler) or [niri](niri.md) (full wallpaper support). + + Your daily Hyprland desktop session is unaffected — only the boot greeter changes. + Configuration for running sysc-greet with the Hyprland Wayland compositor. ## greetd Config diff --git a/docs-src/getting-started/installation.md b/docs-src/getting-started/installation.md index bab2757..3ae1aa9 100644 --- a/docs-src/getting-started/installation.md +++ b/docs-src/getting-started/installation.md @@ -11,17 +11,27 @@ curl -fsSL https://raw.githubusercontent.com/Nomadcxx/sysc-greet/master/install. ``` The interactive installer will prompt you to: -1. Choose your compositor (niri, hyprland, or sway) -2. Configure compositor settings +1. Choose your greeter backend (cage recommended, niri, sway, or hyprland deprecated) +2. Configure backend settings 3. Install dependencies automatically +> **Hyprland deprecation:** Hyprland greeter support will be removed in ~3 months. Migrate to [Cage](../compositors/cage.md) or [niri](../compositors/niri.md). + +### Void Linux + +Void is supported via the Go installer (xbps + runit). See [Void Linux guide](void-linux.md). + +```bash +SYSC_COMPOSITOR=cage sudo ./install.sh +``` + ## Manual Build ### Prerequisites - Go 1.25+ - greetd -- Wayland compositor (niri, hyprland, or sway) +- Wayland backend: cage (recommended), niri, sway, or hyprland (deprecated) - kitty (terminal emulator) - gSlapper (wallpaper daemon) - swww (legacy wallpaper daemon, optional fallback) @@ -45,17 +55,21 @@ go run ./cmd/installer/ ## Arch Linux (AUR) -sysc-greet provides three AUR packages for different compositors: +sysc-greet provides AUR packages for different greeter backends: ```bash -# Recommended (niri) +# niri (full wallpapers) yay -S sysc-greet -# Hyprland variant +# Hyprland variant (deprecated — use cage instead) yay -S sysc-greet-hyprland # Sway variant yay -S sysc-greet-sway + +# Cage (recommended): install cage from repos, then: +sudo pacman -S cage +SYSC_COMPOSITOR=cage curl -fsSL .../install.sh | sudo bash ``` ## Pre-built Packages diff --git a/docs-src/getting-started/void-linux.md b/docs-src/getting-started/void-linux.md new file mode 100644 index 0000000..469dd41 --- /dev/null +++ b/docs-src/getting-started/void-linux.md @@ -0,0 +1,81 @@ +# Void Linux Installation + +sysc-greet is installable on **Void Linux** through the **Go installer** (`cmd/installer`), with **runit** service management and **xbps** packages. + +## Quick install + +```bash +git clone https://github.com/Nomadcxx/sysc-greet.git +cd sysc-greet +go run ./cmd/installer/ +``` + +Or via the one-line wrapper (requires Go): + +```bash +curl -fsSL https://raw.githubusercontent.com/Nomadcxx/sysc-greet/master/install.sh | sudo bash +``` + +Pre-select a Wayland backend: + +```bash +SYSC_COMPOSITOR=cage sudo ./install.sh # recommended on Void +SYSC_COMPOSITOR=niri sudo ./install.sh # builds gSlapper from source +``` + +## What the installer does on Void + +| Step | Void behaviour | +|------|----------------| +| Package manager | `xbps-install -Sy` | +| greetd user | **`_greeter`** (from Void's greetd package) | +| Greeter home | `/var/lib/_greeter` | +| Service enable | `ln -s /etc/sv/greetd /var/service/` | +| VT conflict | Disables `agetty-tty1` if present (greetd uses vt=1) | +| cage backend | Skips gSlapper (TUI backgrounds only) | +| niri/sway | Builds gSlapper from source (not in Void repos) | + +## Recommended backend on Void + +**cage** is the simplest path — greetd, cage, and kitty are all in official Void repos with no source builds. + +**niri** works and supports full wallpapers, but the installer must compile gSlapper and GStreamer build deps. + +**hyprland** is not available in Void repos via xbps; the installer will refuse it. + +## niri on Void — important note + +Do **not** add `niri --session` to greeter or desktop configs on Void ([niri#2448](https://github.com/niri-wm/niri/issues/2448)). sysc-greet's niri greeter config uses `niri -c /etc/greetd/niri-greeter-config.kdl` only. + +## Manual verification after install + +```bash +ls -la /var/service/greetd # should symlink to /etc/sv/greetd +cat /etc/greetd/config.toml +sv status greetd # if runit-sv is installed +``` + +Reboot from a TTY to test the greeter (not over SSH). + +## Troubleshooting + +**Installer says missing init** + +Void must use runit (`/etc/sv` present). Install `runit` if missing. + +**Black screen / no greeter** + +- Check `agetty-tty1` is not still enabled on vt 1 +- `journalctl` is unavailable on Void — use `sv log greetd` or `/var/log/sv/greetd/current` + +**gSlapper build fails on niri** + +Install build deps manually: `xbps-install -Sy meson ninja git pkg-config gstreamer1-devel gst-plugins-base1-devel wayland-devel` + +Or switch to cage: re-run installer with `SYSC_COMPOSITOR=cage`. + +## See also + +- [Cage backend](../compositors/cage.md) +- [Niri backend](../compositors/niri.md) +- [Investigation spec](../../superpowers/specs/2026-06-18-issue-53-void-chimera-bsd-design.md) diff --git a/docs/superpowers/plans/2026-06-18-cage-compositor-plan.md b/docs/superpowers/plans/2026-06-18-cage-compositor-plan.md new file mode 100644 index 0000000..2899e4f --- /dev/null +++ b/docs/superpowers/plans/2026-06-18-cage-compositor-plan.md @@ -0,0 +1,159 @@ +# Cage Compositor — Implementation Plan + +> **Design:** [2026-06-18-cage-compositor-design.md](../specs/2026-06-18-cage-compositor-design.md) +> **Branch:** `feat/cage-compositor-investigation` +> **Type:** Draft PR / investigation spike + +## Summary + +Add **Cage Lite** greeter support and begin **phasing out Hyprland** for the greetd session. Hyprland remains functional in this PR — deprecation is communicated via installer UI, docs, and postinstall warnings. Cagebreak spike follows separately. + +--- + +## Phase 1 — Draft PR (this branch) + +### Task 1: Launcher script + +- [x] Create `scripts/cage-greeter-session.sh` +- [x] Installer writes to `/etc/greetd/cage-greeter-session.sh` (mode 755) +- [x] nfpm.yaml ships script + +### Task 2: Installer integration + +- [x] Add `cage` to compositor menu (first position — recommended) +- [x] Mark `hyprland` as deprecated in menu description +- [x] Update arrow-key bounds (`compositorIndex < 3`) +- [x] `configureGreetd()` case `"cage"` +- [x] `installCompositor()` cage package mapping (pacman) +- [x] `SYSC_COMPOSITOR=cage` env pre-select +- [x] Uninstall cleanup: remove cage launcher script +- [ ] Installer Hyprland deprecation banner when hyprland selected +- [ ] Skip gslapper install when cage selected (optional — defer if complex) + +### Task 3: Nix flake + +- [x] Add `"cage"` to enum +- [x] Add `cagePackage` option +- [x] `defaultCompositorCommand` for cage +- [x] Install launcher via `environment.etc` +- [x] `environment.systemPackages` includes cage when selected + +### Task 4: Documentation + +- [x] `docs-src/compositors/cage.md` +- [x] `mkdocs.yml` nav entry +- [ ] `docs-src/getting-started/installation.md` — cage + Hyprland deprecation +- [ ] `docs-src/compositors/hyprland.md` — deprecation banner +- [ ] `README.md` — cage variant + deprecation notice +- [ ] Link from issue #69 + +### Task 5: postinstall + packaging + +- [ ] `scripts/postinstall.sh` — prefer cage in auto-detect; warn on hyprland +- [x] `scripts/verify-cage-greeter.sh` + +### Task 6: Hyprland deprecation comms + +- [ ] Installer footer: "~3 months until Hyprland greeter removal" +- [ ] postinstall warning when hyprland auto-detected +- [ ] docs migration guide: Hyprland → Cage + +### Task 7: Draft PR + +- [ ] Push branch +- [ ] Open draft PR with deprecation timeline + manual test checklist + +--- + +## Phase 2 — Cagebreak spike (follow-up, not this PR) + +### Investigation tasks + +- [ ] Install cagebreak on test machine +- [ ] Test: `gslapper` + kitty as two clients +- [ ] Test: wlr-layer-shell namespace `wallpaper` +- [ ] Test: greetd session exit (`cagebreak` IPC socket / config quit command) +- [ ] Test: XKB layout via cagebreak config +- [ ] Document results in `docs/superpowers/specs/2026-06-18-cagebreak-spike-results.md` +- [ ] Decision: promote cagebreak to supported compositor OR stay cage-lite only + +### If cagebreak passes layer-shell test + +- [ ] Add `config/cagebreak-greeter-config` +- [ ] Mirror niri startup pattern (wallpaper daemon + kitty chain) +- [ ] Installer 5th option or replace cage with cagebreak + +--- + +## Phase 3 — Hyprland removal (after Cage stable) + +- [ ] Stop shipping `sysc-greet-hyprland` AUR variant +- [ ] Remove Hyprland from installer menu +- [ ] Remove `config/hyprland-greeter-config*.conf` from packages +- [ ] Archive `docs-src/compositors/hyprland.md` with migration link +- [ ] Close #69 if Cagebreak not needed + +--- + +## Phase 4 — Polish (post-approval) + +- [ ] AUR package `sysc-greet-cage` variant +- [ ] postinstall: **do not** auto-select cage (keep explicit opt-in) +- [ ] Optional: `SYSC_GREETER_WALLPAPER_MODE=tui-only` env for logging clarity +- [ ] Close #69 with link to cage docs (or keep open for cagebreak) + +--- + +## Manual Test Checklist + +```bash +# 1. Install cage +sudo pacman -S cage # or nix profile install nixpkgs#cage + +# 2. Build & install sysc-greet with cage compositor +SYSC_COMPOSITOR=cage sudo ./install.sh + +# 3. Verify greetd config +grep -A2 default_session /etc/greetd/config.toml +cat /etc/greetd/cage-greeter-session.sh + +# 4. Restart greetd (from TTY, not SSH) +sudo systemctl restart greetd + +# 5. Verify +# - Greeter appears on boot +# - TUI backgrounds/effects work (F3 menu) +# - Login succeeds → user session starts +# - No Hyprland splash / config errors in journal +journalctl -u greetd -b --no-pager | tail -50 + +# 6. Non-US keyboard (if applicable) +# Edit launcher script or installer output to set XKB_DEFAULT_LAYOUT=fr +# Confirm password entry works + +# 7. Multi-monitor (if available) +# Confirm kitty spans outputs with cage -m extend +``` + +--- + +## Files Touched (Phase 1) + +| File | Change | +|------|--------| +| `scripts/cage-greeter-session.sh` | **NEW** | +| `scripts/verify-cage-greeter.sh` | **NEW** | +| `docs-src/compositors/cage.md` | **NEW** | +| `docs/superpowers/specs/2026-06-18-cage-compositor-design.md` | **NEW** | +| `docs/superpowers/plans/2026-06-18-cage-compositor-plan.md` | **NEW** | +| `cmd/installer/main.go` | compositor menu + configureGreetd | +| `flake.nix` | cage enum + package option | +| `mkdocs.yml` | nav entry | +| `nfpm.yaml` | ship launcher script (if applicable) | + +| `scripts/postinstall.sh` | cage-first auto-detect + hyprland deprecation warning | +| `README.md` | cage variant + deprecation notice | +| `docs-src/getting-started/installation.md` | cage + migration | +| `docs-src/compositors/hyprland.md` | deprecation banner | + +**Unchanged:** `cmd/sysc-greet/*` (backend-neutral) diff --git a/docs/superpowers/specs/2026-06-18-cage-compositor-design.md b/docs/superpowers/specs/2026-06-18-cage-compositor-design.md new file mode 100644 index 0000000..e2f0237 --- /dev/null +++ b/docs/superpowers/specs/2026-06-18-cage-compositor-design.md @@ -0,0 +1,242 @@ +# Cage / Cagebreak Compositor — Design Spec + +> **Status:** Draft / investigation +> **Issue:** [#69 Feature request: cage](https://github.com/Nomadcxx/sysc-greet/issues/69) +> **Branch:** `feat/cage-compositor-investigation` + +## Problem + +Hyprland is the default greeter compositor for many installs (AUR `sysc-greet-hyprland`, postinstall auto-detection). It works but carries ongoing cost: + +- Config churn (window rules, layer rules, `start-hyprland` vs `Hyprland`, watchdog fd, ecosystem popups) +- Heavier cold-start than a kiosk compositor +- ~99% of greeter-specific issues trace to compositor integration, not sysc-greet Go code + +[Cage](https://github.com/cage-kiosk/cage) is a minimal wlroots kiosk compositor designed to run **one maximized application**. That matches 80% of what the greeter needs: a fullscreen Kitty running sysc-greet. + +## Goals + +1. Add **Cage** as a supported greeter backend alongside niri, sway, and Hyprland +2. **Phase out Hyprland** for the greeter session over the next few months — not removed in this PR, but deprecated with clear user-facing notices +3. Preserve **login reliability** (greetd IPC, session start, keyboard input) +4. Document **feature trade-offs** honestly (no silent regressions) +5. Leave a clean extension point for **Cagebreak** investigation in Phase 2 + +## Hyprland Deprecation Policy + +Hyprland greeter support remains **fully functional** in this release. We are not removing it yet. + +| Timeline | Action | +|----------|--------| +| **Now (this PR)** | Cage available in installer, Nix module, docs. Hyprland labeled **deprecated** in UI and docs. | +| **Next few months** | Encourage migration to Cage (or niri/sway). Collect feedback on Cage Lite trade-offs. | +| **~3 months after Cage ships stable** | Remove Hyprland from installer default menu position; postinstall stops auto-selecting Hyprland. | +| **Later** | Remove Hyprland greeter configs, AUR `sysc-greet-hyprland` variant, and related docs — only after Cage (or Cagebreak) is proven stable. | + +**Rationale:** Hyprland is excellent as a *user session* compositor but expensive as a *greeter* compositor. Most greeter bugs are Hyprland config churn, not sysc-greet logic. Users who run Hyprland daily can keep it for their desktop session — only the greetd greeter session moves to Cage. + +## Non-goals (Phase 1) + +- Removing Hyprland support in this PR +- Making Cage the silent default without user awareness +- Full feature parity with Hyprland/niri (video wallpapers, gSlapper layer-shell, multi-monitor wallpaper daemon) +- Dropping niri or sway support +- Cagebreak implementation (investigation only) + +## Current Architecture (reference) + +``` +greetd → compositor → [gslapper daemon] + kitty → sysc-greet + ↑ second Wayland client (wlr-layer-shell) +``` + +All three supported compositors (niri, Hyprland, sway) are **multi-client** and provide: + +| Capability | Used by sysc-greet | +|------------|-------------------| +| Multiple Wayland clients | gSlapper + Kitty | +| wlr-layer-shell | gSlapper `wallpaper` namespace | +| Window rules | Kitty fullscreen | +| Compositor exit command | `hyprctl` / `niri msg` / `swaymsg` | +| Compositor keyboard config | `kb_layout` / `xkb` (+ Kitty `XKB_*` env) | + +## Cage Constraint (critical) + +**Cage runs exactly one application.** It does not host a second Wayland client. + +Implications: + +| Feature | Cage support | +|---------|-------------| +| Kitty fullscreen TUI | ✅ Primary client | +| `sysc-greet --wallpaper-daemon` → gSlapper | ❌ Second client rejected / invisible | +| Video wallpapers at boot | ❌ Requires gSlapper | +| Theme PNG wallpapers at boot | ❌ Requires gSlapper | +| TUI background effects (matrix, sonar, etc.) | ✅ Rendered inside Kitty | +| Theme colors / ASCII art | ✅ Unchanged | +| greetd IPC login | ✅ Unchanged | +| XKB via `XKB_DEFAULT_*` on Kitty | ✅ Only path (no compositor xkb config) | +| Multi-monitor | ⚠️ Cage `-m extend` spans single client across outputs | +| Compositor exit | ✅ Automatic when Kitty exits | + +**Conclusion:** Cage "Lite" mode = Kitty-only session. Wallpapers fall back to TUI effects + theme colors already implemented in sysc-greet. + +## Cagebreak (Phase 2 — not in Phase 1) + +[Cagebreak](https://github.com/project-repo/cagebreak) is a Ratpoison-inspired **tiling** compositor forked from Cage. It supports multiple windows/workspaces and may support wlr-layer-shell. + +| Question | Investigation needed | +|----------|---------------------| +| Layer-shell for gSlapper? | Test `gslapper` + kitty under cagebreak | +| Config format | `~/.config/cagebreak/config` vs greetd inline command | +| Packaging | Arch `cagebreak` AUR; not in most distro repos | +| Maintenance | Smaller community than cage-kiosk/cage | +| Greeter fit | Better wallpaper parity if layer-shell works | + +**Recommendation:** Ship Cage Lite in Phase 1. Run cagebreak spike as gated Phase 2 before promising wallpaper parity. + +## Proposed Approaches + +### Approach A — Cage Lite (recommended Phase 1) + +Inline greetd command via launcher script: + +```bash +cage -s -m extend -- /etc/greetd/cage-greeter-session.sh +``` + +`cage-greeter-session.sh` sets greeter env vars and `exec`s Kitty → sysc-greet. + +**Pros:** Minimal code, fast boot, no Hyprland config, easy to reason about +**Cons:** No gSlapper/video/static boot wallpapers + +### Approach B — Shell wrapper spawning gSlapper inside Cage + +```bash +cage -- sh -c 'sysc-greet --wallpaper-daemon & exec kitty ...' +``` + +**Pros:** Would restore wallpapers if it worked +**Cons:** gSlapper is a **second Wayland client** — Cage kiosk model does not support this. Issue #69 experiments without gSlapper succeeded; with gSlapper likely fails silently. + +**Verdict:** Reject for Phase 1 unless empirical testing on real hardware proves otherwise. + +### Approach C — Cagebreak with compositor config + +Add `config/cagebreak-greeter-config` mirroring niri structure. + +**Pros:** Potential full wallpaper + multi-client parity +**Cons:** Unknown layer-shell support, more packaging work, smaller user base + +**Verdict:** Phase 2 spike, not Phase 1 deliverable. + +## Selected Design: Approach A (Cage Lite) + +### Components + +| File | Purpose | +|------|---------| +| `scripts/cage-greeter-session.sh` | Greeter env + Kitty exec (installed to `/etc/greetd/`) | +| `docs-src/compositors/cage.md` | Experimental setup + trade-offs | +| `cmd/installer/main.go` | 4th compositor option `(experimental)` | +| `flake.nix` | `compositor = "cage"` enum + `cagePackage` option | +| `scripts/verify-cage-greeter.sh` | Smoke test (nested Wayland if available) | + +### greetd command + +```toml +[default_session] +command = "cage -s -m extend -- /etc/greetd/cage-greeter-session.sh" +user = "greeter" +``` + +Flags: + +- `-s` — allow VT switching (match issue #69 / greetd expectations) +- `-m extend` — span greeter across all connected outputs + +### Launcher script + +```sh +#!/bin/sh +export XDG_CACHE_HOME=/tmp/greeter-cache +export HOME=/var/lib/greeter +# XKB_DEFAULT_LAYOUT / VARIANT set by installer or user override +exec kitty --start-as=fullscreen --config=/etc/greetd/kitty.conf /usr/local/bin/sysc-greet +``` + +No `pkill cage` — Cage exits when its sole client (Kitty) exits. + +### Installer UX + +Menu order (Cage first — recommended path): + +``` +cage (recommended) — Minimal kiosk; fast boot; TUI backgrounds only +niri — Full wallpapers; tiling compositor +sway — Full wallpapers; i3-compatible +hyprland (deprecated) — Full wallpapers; greeter support ending in ~3 months +``` + +- `SYSC_COMPOSITOR=cage` env pre-select supported +- **postinstall.sh:** prefer `cage` → `niri` → `sway` → `hyprland` (with deprecation warning on hyprland) +- Show Hyprland deprecation notice in installer footer and when hyprland is selected + +### NixOS module + +```nix +compositor = "cage"; +# optional +cagePackage = pkgs.cage; +``` + +`defaultCompositorCommand` becomes: + +```nix +"${cage}/bin/cage -s -m extend -- /etc/greetd/cage-greeter-session.sh" +``` + +### Go application changes + +**None required for Phase 1.** sysc-greet already: + +- Falls back to TUI backgrounds when gSlapper is absent +- Uses greetd IPC regardless of compositor + +Optional Phase 1.5: log once at startup when `SYSC_GREETER_MODE=cage` to clarify wallpaper fallback (not in initial draft). + +### Feature matrix (documented for users) + +| Feature | Hyprland/niri/sway | Cage Lite | +|---------|-------------------|-----------| +| Login | ✅ | ✅ | +| TUI effects | ✅ | ✅ | +| Theme colors | ✅ | ✅ | +| Boot static/video wallpaper | ✅ | ❌ | +| In-greeter wallpaper change | ✅ | ⚠️ TUI effects only | +| Non-US keyboard | compositor + Kitty XKB | Kitty XKB only | +| Multi-monitor wallpaper | ✅ | ⚠️ Single client span | +| Hyprland config maintenance | ❌ ongoing | ✅ none | + +## Risks & Mitigations + +| Risk | Mitigation | +|------|------------| +| Users expect wallpapers | Bold EXPERIMENTAL label + docs trade-off table | +| Old Intel GPU + Kitty EGL | Issue #77 (foot) is separate; cage doesn't solve EGL | +| Cage not in repos | Document manual install; Nix has `pkgs.cage` | +| greetd user can't run cage | Polkit/capabilities unchanged from other compositors | +| Regression in login | `verify-cage-greeter.sh` + manual greetd test checklist | + +## Success Criteria (Phase 1) + +1. `SYSC_COMPOSITOR=cage` install produces working greetd config +2. Greeter login succeeds on at least one test machine (Arch + cage from repos) +3. Docs clearly state wallpaper limitation +4. Draft PR open for community feedback before marking stable + +## Open Questions (for user / Phase 2) + +1. Should Cage Lite become the **recommended** compositor for new installs (over niri)? +2. Is TUI-only wallpaper acceptable as permanent trade-off, or is Cagebreak spike a blocker? +3. Should we add `foot` terminal path alongside cage for old Intel GPUs (#77)? diff --git a/docs/superpowers/specs/2026-06-18-issue-53-void-chimera-bsd-design.md b/docs/superpowers/specs/2026-06-18-issue-53-void-chimera-bsd-design.md new file mode 100644 index 0000000..df63d9a --- /dev/null +++ b/docs/superpowers/specs/2026-06-18-issue-53-void-chimera-bsd-design.md @@ -0,0 +1,269 @@ +# Issue #53 — Void / Chimera / BSD Support — Investigation + +> **Issue:** [#53 add void and chimera support](https://github.com/Nomadcxx/sysc-greet/issues/53) +> **Status:** Investigation complete — Void is viable first target; BSD is harder +> **Date:** 2026-06-18 + +## Request + +Original reporter uses **Void Linux** and plans to move to **Chimera**. They want the install script to support these distros. Owner previously noted interest in Void and BSD/GhostBSD support. + +## Executive summary + +| Platform | Verdict | First usable path | Effort | +|----------|---------|-------------------|--------| +| **Void Linux** | ✅ **Yes — recommended Phase 1** | Manual install today; installer Phase 1 | Medium | +| **Chimera Linux** | ✅ Yes — Phase 2 | Manual install; shares apk-like tooling | Medium | +| **GhostBSD / FreeBSD** | ⚠️ Partial — Phase 3 | Manual install; greetd port very new | High | + +**Bottom line:** We can get Void working without huge rewrites. The blockers are **init system** (not package names) and **gSlapper absence** (Cage Lite from PR #78 mitigates this). Chimera is structurally similar. GhostBSD is viable for sway/cage but greetd packaging is immature and paths differ. + +--- + +## Current installer assumptions (blockers) + +The Go installer (`cmd/installer/main.go`) is built for **systemd Linux**: + +| Assumption | Void | Chimera | FreeBSD/GhostBSD | +|------------|------|---------|------------------| +| Package manager | ❌ `xbps` not detected | ❌ `apk` not detected | ❌ `pkg` not detected | +| Init / service | ❌ requires `systemctl` | ❌ uses **dinit** | ❌ uses **rc.d** / `service` | +| greetd user | `greeter` | `greeter` (manual) | `greeter` (manual) | +| greetd package user | **`_greeter`** (Void official) | TBD | N/A (new port) | +| Greeter home | `/var/lib/greeter` | `/var/lib/greeter` | `/var/lib/greeter` | +| Binary path | `/usr/local/bin/sysc-greet` | `/usr/bin` typical | `/usr/local/bin` | +| polkit shutdown rules | `/etc/polkit-1/rules.d/` | likely similar | different stack | +| gSlapper build | ❌ no Void template | unknown | unlikely packaged | + +**Hardest gap:** `checkDependencies()` fails without `systemd` — Void/Chimera/BSD installs die before compositor selection. + +--- + +## Dependency availability matrix + +### Void Linux (xbps — all in official repos unless noted) + +| Package | Void repo | Notes | +|---------|-----------|-------| +| greetd 0.10.3 | ✅ | runit service (`/etc/sv/greetd`); user **`_greeter`** | +| kitty 0.47.1 | ✅ | | +| cage 0.3.0 | ✅ | pulls `xorg-server-xwayland` | +| niri 25.11 | ✅ | **Avoid `niri --session` on Void** (known lockup) | +| sway | ✅ | | +| hyprland | ❓ | not verified in void-packages search | +| gSlapper | ❌ | **not in void-packages** — build from source or skip | +| go | ✅ | | +| polkit | ✅ | | +| seatd | ✅ | | + +### Chimera Linux (apk — main + user repos) + +| Package | Chimera | Notes | +|---------|---------|-------| +| greetd 0.10.3 | ✅ user repo | `apk add greetd` | +| kitty 0.46.2 | ✅ user repo | | +| sway 1.12 | ✅ main | | +| cage | ❓ | verify in cports | +| niri | ❓ | verify | +| gSlapper | ❓ | likely absent | +| init | **dinit** | `dinitctl enable greetd` | + +### GhostBSD / FreeBSD (pkg) + +| Package | FreeBSD ports | Notes | +|---------|---------------|-------| +| greetd | ⚠️ **new port Jan 2026** (bug 292588) | may not be in GhostBSD stable yet | +| kitty 0.47 | ✅ x11/kitty | quarterly repo may lag — use `latest` | +| cage 0.3.0 | ✅ x11-wm/cage | | +| sway | ✅ | handbook documents greetd + tuigreet pattern | +| hyprland | ✅ | community installs exist | +| seatd | ✅ required | `sysrc seatd_enable=YES` | +| gSlapper | ❌ | not found in ports | +| init | **rc.d** | no systemd | + +--- + +## Void-specific gotchas + +### 1. greetd system account naming + +Void's greetd package creates: + +- User: **`_greeter`** (not `greeter`) +- Home: **`/var/lib/_greeter`** +- Service: runit via `ln -s /etc/sv/greetd /var/service/` + +sysc-greet installer creates **`greeter`** at `/var/lib/greeter`. Both can coexist if we own the greetd config, but we must pick one consistently in `config.toml` and launcher scripts. + +**Recommendation:** On Void, detect `_greeter` from greetd package and use it OR document using sysc-greet's `greeter` user and skip void's default config. + +### 2. runit, not systemd + +```bash +# Enable greetd on Void +ln -s /etc/sv/greetd /var/service/ + +# Disable conflicting agetty on greeter VT (from void greetd README) +rm /var/service/agetty-tty1 # if tty1 used in config.toml +``` + +Installer needs `enableService()` branch for runit. + +### 3. niri `--session` flag + +Void users report niri 25.08+ lockups when launched with `--session`. sysc-greet niri greeter config correctly uses `niri -c ...` without `--session`. ✅ No change needed, but document for Void niri desktop sessions separately. + +### 4. No gSlapper + +Void has no gslapper package. **Cage Lite** (PR #78) is the natural Void default: + +```bash +xbps-install -S greetd cage kitty go git meson ninja +# build sysc-greet, SYSC_COMPOSITOR=cage manual install +``` + +For niri/sway on Void, gslapper must be built from source (optional task) or wallpapers limited to TUI effects. + +### 5. musl vs glibc + +Void supports both. Go binary is generally portable. Kitty/cage/niri have musl-specific build notes in void templates — use distro packages, don't bundle. + +--- + +## Chimera-specific notes + +- Package manager: `apk` (v3 — Chimera fork, Alpine-compatible CLI) +- greetd in **`user`** repo — enable `[user]` in `/etc/apk/repositories` +- Service: **dinit** (`dinitctl enable greetd` per Chimera docs) +- Reporter's planned migration path: Void → Chimera means **apk support covers both** long-term +- Chimera uses elogind (sway depends on `libelogind` — visible in package deps) + +--- + +## GhostBSD / FreeBSD notes + +- **seatd** must be running before Wayland greeter +- Paths: `/usr/local/bin` vs `/usr/bin` — configs need substitution (like Nix flake already does) +- **greetd port is weeks old** — GhostBSD may need ports build or wait for package +- **quarterly vs latest** pkg repos — kitty/cage may be missing on quarterly (document `pkg meta -l latest`) +- Polkit / shutdown: may need different approach than Linux polkit rules +- No `render` group — FreeBSD uses different DRM permission model + +**Realistic GhostBSD Phase 3 deliverable:** documented manual install with sway or cage, not full installer automation initially. + +--- + +## Recommended approach + +### Phase 1 — Void Linux (highest ROI, satisfies #53 reporter) + +**Goal:** `xbps-install` path + runit service + Cage Lite default + +1. Add `xbps` to `detectPackageManager()` (check `/usr/bin/xbps-install`) +2. Add `initSystem` detection: `systemd` | `runit` | `dinit` | `rc` +3. Relax `checkDependencies()` — require greetd-capable init, not systemd specifically +4. `installGreetd/kitty/cage/niri/sway` cases for `xbps-install -Sy` +5. `enableService()` runit branch: symlink `/etc/sv/greetd` → `/var/service/` +6. Void greeter user detection: `_greeter` vs `greeter` +7. Skip gslapper install on Void (or optional source build task) +8. Docs: `docs-src/getting-started/void-linux.md` +9. `scripts/verify-void-greeter.sh` + +**Manual install recipe (works today without code changes):** + +```bash +# Void — Cage Lite manual path +sudo xbps-install -Sy greetd cage kitty git go +git clone https://github.com/Nomadcxx/sysc-greet.git +cd sysc-greet +git checkout feat/cage-compositor-investigation # or master once merged +go build -o sysc-greet ./cmd/sysc-greet/ +go build -o install-sysc-greet ./cmd/installer/ + +# Installer will fail on systemd check — workaround: manual config +sudo install -Dm755 sysc-greet /usr/local/bin/sysc-greet +sudo install -Dm755 scripts/cage-greeter-session.sh /etc/greetd/ +sudo install -Dm644 config/kitty-greeter.conf /etc/greetd/kitty.conf +sudo cp -r ascii_configs fonts wallpapers /usr/share/sysc-greet/ + +# greetd config — use _greeter if void package installed, else create greeter +sudo tee /etc/greetd/config.toml <<'EOF' +[terminal] +vt = 1 + +[default_session] +command = "cage -s -m extend -- /etc/greetd/cage-greeter-session.sh" +user = "_greeter" +EOF + +sudo ln -sf /etc/sv/greetd /var/service/ +sudo rm -f /var/service/agetty-tty1 # if using vt=1 +``` + +### Phase 2 — Chimera Linux + +- Add `apk` detection (careful: distinguish Chimera apk from Alpine in `/etc/os-release`) +- dinit service enable +- user repo for greetd/kitty +- Reuse most Void logic (non-systemd, no gslapper default) + +### Phase 3 — GhostBSD / FreeBSD + +- Add `pkg` detection +- seatd + rc.d enable +- Path substitution in configs (`/usr/local/bin`) +- greetd from ports when available in GhostBSD repos +- Document quarterly vs latest repo requirement + +--- + +## Relationship to Cage Lite (PR #78) + +Cage Lite is a **strong enabler** for Void/BSD: + +| Feature | niri/sway on Void | cage on Void | +|---------|-------------------|--------------| +| gSlapper wallpapers | needs source build | not needed | +| Compositor config file | required | launcher script only | +| Package count | higher | greetd + cage + kitty | + +**Recommend Void/BSD default to cage** in new platform docs, same as Linux deprecation direction. + +--- + +## What NOT to do + +- Don't claim full Chimera support in Phase 1 — reporter is on Void now +- Don't bundle gslapper build into installer initially — high failure rate across distros +- Don't assume GhostBSD has greetd in pkg yet — verify per release +- Don't use `niri --session` anywhere in Void docs + +--- + +## Success criteria + +### Phase 1 (Void) +- [ ] `SYSC_COMPOSITOR=cage ./install.sh` works on Void without systemd +- [ ] greetd starts via runit after reboot +- [ ] Login succeeds; TUI backgrounds work +- [ ] Issue #53 reporter can install without manual config hacking + +### Phase 2 (Chimera) +- [ ] Same flow with `apk` + dinit + +### Phase 3 (GhostBSD) +- [ ] Documented manual path verified on GhostBSD 24.x+ +- [ ] Installer automation optional follow-up + +--- + +## Open questions + +1. Use Void's `_greeter` or sysc-greet's `greeter` on Void? (Recommend: detect and adapt) +2. Submit gslapper template to void-packages? (Upstream contribution — separate effort) +3. Chimera in Phase 1 or Phase 2? (Recommend Phase 2 — same apk code, less test surface) +4. AUR-style `sysc-greet-void` XBPS template? (Phase 1.5 packaging) + +## Suggested issue #53 triage comment + +> Investigation complete. **Void is viable** as Phase 1 (xbps + runit + Cage Lite). Chimera Phase 2 (apk + dinit). GhostBSD/FreeBSD Phase 3 (pkg + rc.d, greetd port very new). Main blockers are systemd-only installer and missing gslapper on Void — Cage path from #78 mitigates the latter. Design spec: `docs/superpowers/specs/2026-06-18-issue-53-void-chimera-bsd-design.md` diff --git a/flake.nix b/flake.nix index 2773765..bafb3b2 100644 --- a/flake.nix +++ b/flake.nix @@ -87,6 +87,13 @@ --replace 'kitty ' "${pkgs.kitty}/bin/kitty " \ --replace 'swaymsg ' "${pkgs.sway}/bin/swaymsg " + # Cage lite launcher (single-client kiosk — no gSlapper) + cp scripts/cage-greeter-session.sh $out/etc/greetd/ + substituteInPlace $out/etc/greetd/cage-greeter-session.sh \ + --replace '/usr/local/bin/sysc-greet' "$out/bin/sysc-greet" \ + --replace 'kitty ' "${pkgs.kitty}/bin/kitty " + chmod +x $out/etc/greetd/cage-greeter-session.sh + # Install polkit rule mkdir -p $out/etc/polkit-1/rules.d cat > $out/etc/polkit-1/rules.d/85-greeter.rules <<'EOF' @@ -141,6 +148,7 @@ EOF compositorPackage = if cfg.compositor == "niri" then cfg.niriPackage else if cfg.compositor == "hyprland" then cfg.hyprlandPackage + else if cfg.compositor == "cage" then cfg.cagePackage else cfg.swayPackage; compositorExecutable = pkg: executable: if pkg == null then executable else "${pkg}/bin/${executable}"; @@ -149,6 +157,8 @@ EOF "${compositorExecutable cfg.niriPackage "niri"} -c /etc/greetd/niri-greeter-config.kdl" else if cfg.compositor == "hyprland" then "${compositorExecutable cfg.hyprlandPackage "start-hyprland"} -- -c /etc/greetd/hyprland-greeter-config.conf" + else if cfg.compositor == "cage" then + "${compositorExecutable cfg.cagePackage "cage"} -s -m extend -- /etc/greetd/cage-greeter-session.sh" else "${compositorExecutable cfg.swayPackage "sway"} -c /etc/greetd/sway-greeter-config"; in @@ -157,9 +167,9 @@ EOF enable = mkEnableOption "sysc-greet greeter for greetd"; compositor = mkOption { - type = types.enum [ "niri" "hyprland" "sway" ]; + type = types.enum [ "niri" "hyprland" "sway" "cage" ]; default = "niri"; - description = "Wayland compositor to use with sysc-greet"; + description = "Wayland compositor to use with sysc-greet. Use cage for a minimal kiosk greeter (experimental, no boot wallpapers)."; }; compositorCommand = mkOption { @@ -198,6 +208,13 @@ EOF description = "sway package to use and install for the greeter compositor. When null, the sway command is resolved from PATH."; }; + cagePackage = mkOption { + type = types.nullOr types.package; + default = null; + defaultText = literalExpression "null"; + description = "cage package to use and install for the greeter compositor (lite mode). When null, the cage command is resolved from PATH."; + }; + settings = mkOption { type = types.attrs; default = { }; @@ -252,6 +269,7 @@ EOF "greetd/niri-greeter-config.kdl".source = "${package}/etc/greetd/niri-greeter-config.kdl"; "greetd/hyprland-greeter-config.conf".source = "${package}/etc/greetd/hyprland-greeter-config.conf"; "greetd/sway-greeter-config".source = "${package}/etc/greetd/sway-greeter-config"; + "greetd/cage-greeter-session.sh".source = "${package}/etc/greetd/cage-greeter-session.sh"; "polkit-1/rules.d/85-greeter.rules".source = "${package}/etc/polkit-1/rules.d/85-greeter.rules"; }; diff --git a/mkdocs.yml b/mkdocs.yml index cdf75af..cc023c1 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -46,6 +46,7 @@ nav: - Getting Started: - Installation: getting-started/installation.md - Quick Start: getting-started/quick-start.md + - Void Linux: getting-started/void-linux.md - Troubleshooting: getting-started/troubleshooting.md - Features: - Backgrounds & Effects: features/backgrounds-effects.md @@ -60,6 +61,7 @@ nav: - Niri: compositors/niri.md - Hyprland: compositors/hyprland.md - Sway: compositors/sway.md + - Cage (experimental): compositors/cage.md - Development: - Architecture: development/architecture.md - Building: development/building.md diff --git a/nfpm.yaml b/nfpm.yaml index 7e9a4bf..6daca7a 100644 --- a/nfpm.yaml +++ b/nfpm.yaml @@ -7,7 +7,7 @@ priority: optional maintainer: Nomadcxx description: | Graphical console greeter for greetd with ASCII art and themes. - Supports Niri, Hyprland, and Sway compositors. + Supports Niri, Hyprland, Sway, and experimental Cage compositors. vendor: Nomadcxx homepage: https://github.com/Nomadcxx/sysc-greet license: GPL-3.0 @@ -58,6 +58,12 @@ contents: file_info: mode: 0644 + # Cage lite launcher (experimental) + - src: ./scripts/cage-greeter-session.sh + dst: /etc/greetd/cage-greeter-session.sh + file_info: + mode: 0755 + # Polkit rule for greeter shutdown/reboot - src: ./config/85-greeter.rules dst: /etc/polkit-1/rules.d/85-greeter.rules diff --git a/scripts/cage-greeter-session.sh b/scripts/cage-greeter-session.sh new file mode 100755 index 0000000..0b9bc08 --- /dev/null +++ b/scripts/cage-greeter-session.sh @@ -0,0 +1,20 @@ +#!/bin/sh +# cage-greeter-session.sh — greetd session launcher for Cage (lite mode) +# +# Cage is a single-client kiosk compositor. This script is the ONE application +# cage runs. It execs kitty → sysc-greet directly (no gSlapper wallpaper daemon). +# +# See docs-src/compositors/cage.md and docs/superpowers/specs/2026-06-18-cage-compositor-design.md + +set -eu + +export XDG_CACHE_HOME="${XDG_CACHE_HOME:-/tmp/greeter-cache}" +export HOME="${HOME:-/var/lib/greeter}" + +KITTY_CONFIG="${KITTY_CONFIG:-/etc/greetd/kitty.conf}" +SYSC_GREET_BIN="${SYSC_GREET_BIN:-/usr/local/bin/sysc-greet}" + +# XKB_DEFAULT_LAYOUT / XKB_DEFAULT_VARIANT may be set by installer or user. +# Cage has no compositor keyboard config — Kitty env vars are the only path. + +exec kitty --start-as=fullscreen --config="${KITTY_CONFIG}" "${SYSC_GREET_BIN}" diff --git a/scripts/postinstall.sh b/scripts/postinstall.sh index 0596f47..7ef69f5 100755 --- a/scripts/postinstall.sh +++ b/scripts/postinstall.sh @@ -20,27 +20,38 @@ chown -R greeter:greeter /var/cache/sysc-greet 2>/dev/null || true chown -R greeter:greeter /var/lib/greeter 2>/dev/null || true chmod 755 /var/lib/greeter -# Detect installed compositor and configure greetd -echo "==> Detecting compositor..." +# Detect installed Wayland backend and configure greetd +echo "==> Detecting greeter backend..." COMPOSITOR="" -if command -v niri &>/dev/null; then +if command -v cage &>/dev/null; then + COMPOSITOR="cage" + GREETD_COMMAND="cage -s -m extend -- /etc/greetd/cage-greeter-session.sh" +elif command -v niri &>/dev/null; then COMPOSITOR="niri" GREETD_COMMAND="niri -c /etc/greetd/niri-greeter-config.kdl" +elif command -v sway &>/dev/null; then + COMPOSITOR="sway" + GREETD_COMMAND="sway -c /etc/greetd/sway-greeter-config" elif command -v Hyprland &>/dev/null || command -v hyprland &>/dev/null; then COMPOSITOR="hyprland" # Use legacy hyprland command (not start-hyprland) for compatibility GREETD_COMMAND="Hyprland -c /etc/greetd/hyprland-greeter-config.conf" -elif command -v sway &>/dev/null; then - COMPOSITOR="sway" - GREETD_COMMAND="sway -c /etc/greetd/sway-greeter-config" fi if [ -z "$COMPOSITOR" ]; then - echo "WARNING: No supported compositor detected (niri, hyprland, sway)" - echo "Please install a compositor and manually configure /etc/greetd/config.toml" + echo "WARNING: No supported greeter backend detected (cage, niri, sway, hyprland)" + echo "Please install cage (recommended) and manually configure /etc/greetd/config.toml" else - echo "Detected compositor: $COMPOSITOR" + echo "Detected backend: $COMPOSITOR" + + if [ "$COMPOSITOR" = "hyprland" ]; then + echo "" + echo "WARNING: Hyprland greeter support is deprecated and will be removed in ~3 months." + echo " Migrate to cage: SYSC_COMPOSITOR=cage sudo ./install.sh" + echo " See https://nomadcxx.github.io/sysc-greet/compositors/cage/" + echo "" + fi # Only create greetd config if it doesn't exist or is empty if [ ! -s /etc/greetd/config.toml ]; then diff --git a/scripts/verify-cage-greeter.sh b/scripts/verify-cage-greeter.sh new file mode 100755 index 0000000..f2c9e8d --- /dev/null +++ b/scripts/verify-cage-greeter.sh @@ -0,0 +1,60 @@ +#!/usr/bin/env bash +# verify-cage-greeter.sh — smoke checks for Cage lite greeter integration +set -euo pipefail + +ROOT="$(cd "$(dirname "$0")/.." && pwd)" +LAUNCHER="${ROOT}/scripts/cage-greeter-session.sh" +FAIL=0 + +pass() { echo "✓ $*"; } +fail() { echo "✗ $*"; FAIL=1; } + +echo "=== Cage greeter verification ===" + +if [[ -x "${LAUNCHER}" ]] || [[ -f "${LAUNCHER}" ]]; then + pass "launcher script exists: ${LAUNCHER}" +else + fail "launcher script missing: ${LAUNCHER}" +fi + +if bash -n "${LAUNCHER}" 2>/dev/null; then + pass "launcher script syntax (bash -n)" +else + fail "launcher script syntax check failed" +fi + +if command -v cage >/dev/null 2>&1; then + pass "cage binary found: $(command -v cage)" + cage -v 2>/dev/null | head -1 || true +else + echo "⚠ cage not installed — skip runtime test (install: pacman -S cage / nix profile install nixpkgs#cage)" +fi + +if [[ -f "${ROOT}/docs-src/compositors/cage.md" ]]; then + pass "cage compositor docs present" +else + fail "docs-src/compositors/cage.md missing" +fi + +if [[ -f "${ROOT}/docs/superpowers/specs/2026-06-18-cage-compositor-design.md" ]]; then + pass "design spec present" +else + fail "design spec missing" +fi + +# Grep installer for cage integration (draft PR scaffold) +if rg -q '"cage"' "${ROOT}/cmd/installer/main.go" 2>/dev/null; then + pass "installer references cage compositor" +else + fail "installer does not yet reference cage (expected after integration)" +fi + +echo +if [[ "${FAIL}" -eq 0 ]]; then + echo "All static checks passed." + echo "Manual: SYSC_COMPOSITOR=cage ./install.sh → systemctl restart greetd → test login" + exit 0 +else + echo "Some checks failed." + exit 1 +fi diff --git a/scripts/verify-void-installer.sh b/scripts/verify-void-installer.sh new file mode 100755 index 0000000..8c9baf8 --- /dev/null +++ b/scripts/verify-void-installer.sh @@ -0,0 +1,59 @@ +#!/usr/bin/env bash +# verify-void-installer.sh — static checks for Void Linux installer support +set -euo pipefail + +ROOT="$(cd "$(dirname "$0")/.." && pwd)" +FAIL=0 + +pass() { echo "✓ $*"; } +fail() { echo "✗ $*"; FAIL=1; } + +echo "=== Void installer verification ===" + +for f in cmd/installer/platform.go cmd/installer/main.go; do + if [[ -f "${ROOT}/${f}" ]]; then + pass "found ${f}" + else + fail "missing ${f}" + fi +done + +if rg -q 'xbps' "${ROOT}/cmd/installer/main.go" && rg -q 'initRunit' "${ROOT}/cmd/installer/platform.go"; then + pass "xbps + runit references present" +else + fail "xbps or runit support missing in installer" +fi + +if rg -q '_greeter' "${ROOT}/cmd/installer/platform.go"; then + pass "Void _greeter account handling" +else + fail "_greeter handling missing" +fi + +if rg -q 'selectedCompositor == "cage"' "${ROOT}/cmd/installer/main.go"; then + pass "cage gslapper skip logic" +else + fail "cage gslapper skip missing" +fi + +if go build -o /dev/null "${ROOT}/cmd/installer/" 2>/dev/null; then + pass "installer compiles" +else + fail "installer compile failed" + go build -o /dev/null "${ROOT}/cmd/installer/" || true +fi + +if [[ -f "${ROOT}/docs-src/getting-started/void-linux.md" ]]; then + pass "Void docs present" +else + fail "Void docs missing" +fi + +echo +if [[ "${FAIL}" -eq 0 ]]; then + echo "All static checks passed." + echo "Runtime: run on Void with SYSC_COMPOSITOR=cage sudo ./install.sh" + exit 0 +fi +echo "Some checks failed." +exit 1