diff --git a/README.md b/README.md index f049fd8..2f8ed09 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. + +> Hyprland greeter support ends in ~3 months. New installs should choose niri (default) or cagebreak, its replacement. ### Build from Source @@ -34,19 +36,21 @@ 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) +# niri (default) yay -S sysc-greet -# Hyprland variant +# Hyprland variant (deprecated, replaced by cagebreak) yay -S sysc-greet-hyprland # Sway variant yay -S sysc-greet-sway ``` +For cagebreak, install it from the AUR (`paru -S cagebreak`, plus `socat`) and run the installer with `SYSC_COMPOSITOR=cagebreak`. + ### NixOS (Flake) If you're on NixOS, add sysc-greet to your flake: @@ -79,7 +83,7 @@ If you're on NixOS, add sysc-greet to your flake: { services.sysc-greet = { enable = true; - compositor = "niri"; # or "hyprland" or "sway" + compositor = "niri"; # or "cagebreak", "sway", "hyprland" (deprecated) }; # Optional: Set initial session for auto-login @@ -90,8 +94,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 `niri`, `cagebreak`, `hyprland`, or `sway`. +Install your chosen backend yourself, or set `niriPackage`, `cagebreakPackage`, `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..d7d4889 100644 --- a/cmd/installer/main.go +++ b/cmd/installer/main.go @@ -223,7 +223,7 @@ type model struct { needsGreetd bool uninstallMode bool selectedOption int // 0 = Install, 1 = Uninstall - selectedCompositor string // "niri", "hyprland", or "sway" + selectedCompositor string // "niri", "cagebreak", "sway", or "hyprland" compositorIndex int // Current selection in compositor menu debugMode bool // Show verbose output logFile *os.File // Installer log file @@ -340,7 +340,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,14 +373,15 @@ 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{"niri", "cagebreak", "sway", "hyprland"} m.selectedCompositor = compositors[m.compositorIndex] // Validate compositor is installed compositorBinaries := map[string][]string{ - "niri": {"niri"}, - "hyprland": {"Hyprland", "hyprland"}, - "sway": {"sway"}, + "niri": {"niri"}, + "hyprland": {"Hyprland", "hyprland"}, + "sway": {"sway"}, + "cagebreak": {"cagebreak"}, } compositorInstalled := false @@ -563,9 +564,10 @@ func (m model) renderCompositorSelect() string { name string desc string }{ - {"niri", "Tiling compositor with scrollable workspaces"}, - {"hyprland", "Dynamic tiling compositor with extensive features"}, + {"niri", "Tiling compositor with scrollable workspaces (default)"}, + {"cagebreak", "Minimal tiling kiosk; replaces hyprland for the greeter"}, {"sway", "Stable i3-compatible tiling compositor"}, + {"hyprland", "Deprecated — greeter support ending in ~3 months; migrate to cagebreak"}, } for i, comp := range compositors { @@ -577,7 +579,11 @@ 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 ends in ~3 months; cagebreak replaces it")) + 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 cagebreak or niri instead.")) + } // Show errors if any if len(m.errors) > 0 { @@ -947,12 +953,35 @@ func installKitty(m *model) error { return nil } +// installCagebreakArch installs cagebreak on Arch: official repos first, then +// an AUR helper run as the invoking user (AUR helpers refuse to run as root). +func installCagebreakArch() error { + if err := exec.Command("pacman", "-S", "--noconfirm", "cagebreak").Run(); err == nil { + return nil + } + helperPath := detectAURHelper() + if helperPath == "" { + return fmt.Errorf("cagebreak is AUR-only and no AUR helper found — install it first: paru -S cagebreak (or yay -S cagebreak)") + } + var cmd *exec.Cmd + if sudoUser := os.Getenv("SUDO_USER"); sudoUser != "" { + cmd = exec.Command("sudo", "-u", sudoUser, helperPath, "-S", "--noconfirm", "cagebreak") + } else { + cmd = exec.Command(helperPath, "-S", "--noconfirm", "cagebreak") + } + if err := cmd.Run(); err != nil { + return fmt.Errorf("cagebreak AUR install failed — install it manually with %s -S cagebreak", helperPath) + } + return nil +} + func installCompositor(m *model) error { // Map compositor selection to binary names compositorBinaries := map[string][]string{ - "niri": {"niri"}, - "hyprland": {"Hyprland", "hyprland"}, - "sway": {"sway"}, + "niri": {"niri"}, + "hyprland": {"Hyprland", "hyprland"}, + "sway": {"sway"}, + "cagebreak": {"cagebreak"}, } // Check if compositor already installed @@ -996,6 +1025,8 @@ func installCompositor(m *model) error { cmd = exec.Command("pacman", "-S", "--noconfirm", "hyprland") case "sway": cmd = exec.Command("pacman", "-S", "--noconfirm", "sway") + case "cagebreak": + return installCagebreakArch() } case "apt": @@ -1007,6 +1038,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 "cagebreak": + return fmt.Errorf("cagebreak not in standard apt repos — build from https://github.com/project-repo/cagebreak") } case "dnf": @@ -1018,6 +1051,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 "cagebreak": + return fmt.Errorf("cagebreak not in standard dnf repos — build from https://github.com/project-repo/cagebreak") } case "yum": @@ -1612,6 +1647,25 @@ 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 "cagebreak": + compositorConfig = `# sysc-greet cagebreak greeter config — used ONLY by the greetd greeter session +# greetd runs: cagebreak -e -c /etc/greetd/cagebreak-greeter-config +# -e enables the IPC socket used to quit cagebreak after login (requires socat) +# Keyboard layout: set XKB_DEFAULT_LAYOUT/XKB_DEFAULT_VARIANT in greetd's command env + +background 0.0 0.0 0.0 + +# gSlapper wallpaper daemon (wlr-layer-shell client, same as niri/sway setup) +exec HOME=/var/lib/greeter /usr/local/bin/sysc-greet --wallpaper-daemon + +# Greeter UI; when it exits (login or shutdown), quit cagebreak so greetd can start the session +exec XDG_CACHE_HOME=/tmp/greeter-cache HOME=/var/lib/greeter kitty --start-as=fullscreen --config=/etc/greetd/kitty.conf /usr/local/bin/sysc-greet; echo quit | socat - UNIX-CONNECT:"$CAGEBREAK_SOCKET" + +# No bind/definekey lines on purpose: no compositor keybindings reachable from the greeter +` + configPath = "/etc/greetd/cagebreak-greeter-config" + greetdCommand = "cagebreak -e -c /etc/greetd/cagebreak-greeter-config" + default: return fmt.Errorf("unknown compositor: %s", m.selectedCompositor) } @@ -1702,6 +1756,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/cagebreak-greeter-config", } for _, path := range paths { diff --git a/config/cagebreak-greeter-config b/config/cagebreak-greeter-config new file mode 100644 index 0000000..d0ce71f --- /dev/null +++ b/config/cagebreak-greeter-config @@ -0,0 +1,14 @@ +# sysc-greet cagebreak greeter config — used ONLY by the greetd greeter session +# greetd runs: cagebreak -e -c /etc/greetd/cagebreak-greeter-config +# -e enables the IPC socket used to quit cagebreak after login (requires socat) +# Keyboard layout: set XKB_DEFAULT_LAYOUT/XKB_DEFAULT_VARIANT in greetd's command env + +background 0.0 0.0 0.0 + +# gSlapper wallpaper daemon (wlr-layer-shell client, same as niri/sway setup) +exec HOME=/var/lib/greeter /usr/local/bin/sysc-greet --wallpaper-daemon + +# Greeter UI; when it exits (login or shutdown), quit cagebreak so greetd can start the session +exec XDG_CACHE_HOME=/tmp/greeter-cache HOME=/var/lib/greeter kitty --start-as=fullscreen --config=/etc/greetd/kitty.conf /usr/local/bin/sysc-greet; echo quit | socat - UNIX-CONNECT:"$CAGEBREAK_SOCKET" + +# No bind/definekey lines on purpose: no compositor keybindings reachable from the greeter diff --git a/docs-src/compositors/cagebreak.md b/docs-src/compositors/cagebreak.md new file mode 100644 index 0000000..1d3c146 --- /dev/null +++ b/docs-src/compositors/cagebreak.md @@ -0,0 +1,79 @@ +# Cagebreak Setup + +Cagebreak is a tiling kiosk compositor forked from Cage. It replaces Hyprland for the greetd greeter session, which is [deprecated](hyprland.md). Niri remains the default greeter compositor. + +Cagebreak supports wlr-layer-shell, so gSlapper wallpapers work the same as under niri and sway. Wallpapers appear on secondary monitors; the primary output is covered by the greeter TUI, which draws its own background effects. + +The greeter config is ~10 lines, defines no keybindings, and quits the compositor through cagebreak's IPC socket after login. Tracked in [issue #69](https://github.com/Nomadcxx/sysc-greet/issues/69). + +## Install Cagebreak + +=== "Arch Linux" + + Cagebreak is in the AUR, not the official repos. socat is required to quit the compositor after login. + + ```bash + paru -S cagebreak # or: yay -S cagebreak + sudo pacman -S socat + ``` + +=== "NixOS" + + ```nix + services.sysc-greet = { + enable = true; + compositor = "cagebreak"; + # optional: cagebreakPackage = pkgs.cagebreak; + }; + ``` + +=== "Other distros" + + Check your package manager or build from [project-repo/cagebreak](https://github.com/project-repo/cagebreak). + +## greetd Config + +=== "Installer" + + ```bash + SYSC_COMPOSITOR=cagebreak sudo ./install.sh + ``` + +=== "Manual" + + Edit `/etc/greetd/config.toml`: + + ```toml + [terminal] + vt = 1 + + [default_session] + command = "cagebreak -e -c /etc/greetd/cagebreak-greeter-config" + user = "greeter" + ``` + + `-e` enables the IPC socket. The greeter config uses it to quit cagebreak after login. + +The installer and packages place the greeter config at `/etc/greetd/cagebreak-greeter-config`. + +## Keyboard Layout + +Cagebreak reads the standard XKB environment variables. For a non-US layout, wrap the greetd command: + +```toml +command = "env XKB_DEFAULT_LAYOUT=de XKB_DEFAULT_VARIANT=nodeadkeys cagebreak -e -c /etc/greetd/cagebreak-greeter-config" +``` + +## Migrating from Hyprland + +1. Install cagebreak and socat (see above) +2. Re-run the installer: `SYSC_COMPOSITOR=cagebreak sudo ./install.sh` +3. Reboot, or `sudo systemctl restart greetd` from a TTY + +Your Hyprland desktop session is unaffected. Only the boot greeter changes. + +## Troubleshooting + +- **No wallpaper on a secondary monitor:** check gSlapper is installed and `/var/cache/sysc-greet/` is readable by the greeter user. Single-monitor setups never show a wallpaper; the TUI covers the only output. +- **Greeter restarts in a loop:** run `cagebreak -e -c /etc/greetd/cagebreak-greeter-config` from a TTY to see the error. A config parse error makes cagebreak exit immediately and greetd relaunches it. +- **Stuck after login:** verify socat is installed. Without it the compositor never quits and greetd waits forever. diff --git a/docs-src/compositors/hyprland.md b/docs-src/compositors/hyprland.md index cb1c43e..db68537 100644 --- a/docs-src/compositors/hyprland.md +++ b/docs-src/compositors/hyprland.md @@ -1,5 +1,12 @@ # Hyprland Setup +!!! warning "Deprecated" + sysc-greet ends Hyprland greeter support in ~3 months. It remains functional until then. + + Migrate to [cagebreak](cagebreak.md), the Hyprland replacement, or [niri](niri.md), the default. + + Your 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..d608d3b 100644 --- a/docs-src/getting-started/installation.md +++ b/docs-src/getting-started/installation.md @@ -11,17 +11,19 @@ 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 (niri default, cagebreak, sway, or hyprland deprecated) +2. Configure backend settings 3. Install dependencies automatically +> Hyprland greeter support ends in ~3 months. Migrate to [cagebreak](../compositors/cagebreak.md) or [niri](../compositors/niri.md). + ## Manual Build ### Prerequisites - Go 1.25+ - greetd -- Wayland compositor (niri, hyprland, or sway) +- Wayland backend: niri (default), cagebreak, sway, or hyprland (deprecated) - kitty (terminal emulator) - gSlapper (wallpaper daemon) - swww (legacy wallpaper daemon, optional fallback) @@ -45,17 +47,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 (default) yay -S sysc-greet -# Hyprland variant +# Hyprland variant (deprecated, replaced by cagebreak) yay -S sysc-greet-hyprland # Sway variant yay -S sysc-greet-sway + +# Cagebreak: install from AUR, then run the installer +paru -S cagebreak && sudo pacman -S socat +SYSC_COMPOSITOR=cagebreak curl -fsSL .../install.sh | sudo bash ``` ## Pre-built Packages 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..f911584 --- /dev/null +++ b/docs/superpowers/plans/2026-06-18-cage-compositor-plan.md @@ -0,0 +1,161 @@ +# Cage Compositor — Implementation Plan + +> **SUPERSEDED (2026-07-06):** See [2026-07-06-cagebreak-compositor-plan.md](2026-07-06-cagebreak-compositor-plan.md). + +> **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/plans/2026-07-06-cagebreak-compositor-plan.md b/docs/superpowers/plans/2026-07-06-cagebreak-compositor-plan.md new file mode 100644 index 0000000..2fe8f8d --- /dev/null +++ b/docs/superpowers/plans/2026-07-06-cagebreak-compositor-plan.md @@ -0,0 +1,813 @@ +# Cagebreak Compositor Pivot Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Replace the Cage Lite greeter path in PR #78 with Cagebreak, giving full gSlapper wallpaper parity, so Hyprland greeter support can be deprecated. + +**Architecture:** Config-driven, mirroring the niri pattern: greetd runs `cagebreak -e -c /etc/greetd/cagebreak-greeter-config`; the config `exec`s the gSlapper wallpaper daemon (layer-shell) and the kitty→sysc-greet chain, which sends `quit` to cagebreak's IPC socket after login. No Go application changes to sysc-greet itself; installer/flake/packaging/docs swap cage→cagebreak. + +**Tech Stack:** Go (installer), POSIX sh, cagebreak 3.2.1, greetd, gSlapper, Nix flake, nfpm. + +**Spec:** `docs/superpowers/specs/2026-07-06-cagebreak-compositor-design.md` + +## Global Constraints + +- Branch: `feat/cage-compositor-investigation` in worktree `.claude/worktrees/cagebreak-pivot`. Merge target is `development`, NOT `master`. Never merge or push without explicit user approval. +- Commit style per CLAUDE.md: brief, action-focused, NO Co-Authored-By / AI attribution, explicit file paths in `git add` (never `git add .`). +- greetd command everywhere: `cagebreak -e -c /etc/greetd/cagebreak-greeter-config` +- Greeter config path everywhere: `/etc/greetd/cagebreak-greeter-config` (mode 0644) +- Cage (the compositor) is fully removed from the PR: `scripts/cage-greeter-session.sh` and `docs-src/compositors/cage.md` deleted, all `"cage"` cases replaced by `"cagebreak"`. +- Tasks 3+ are GATED on Task 2 (VT spike) passing its P0 checks. If the wallpaper check fails, STOP and consult the user. +- Deprecation copy (use verbatim where a menu/warning mentions it): Hyprland greeter support ending in ~3 months; migrate to cagebreak. + +--- + +### Task 1: Headless spike (SSH-safe, automated) + +Verifies over SSH, without a display: cagebreak parses our config, `$CAGEBREAK_SOCKET` propagates to `exec` children, gSlapper (layer-shell client) starts and stays alive, and `quit` over the socket shuts cagebreak down cleanly. + +**Files:** +- Create: `/tmp/claude-1000/-home-nomadx-Documents-sysc-greet-dev/1cb7821e-6425-4a7f-a87c-74180e296830/scratchpad/spike/config` (throwaway, not committed) +- Create: `/tmp/claude-1000/-home-nomadx-Documents-sysc-greet-dev/1cb7821e-6425-4a7f-a87c-74180e296830/scratchpad/spike/run.sh` (throwaway, not committed) + +**Interfaces:** +- Produces: a pass/fail result for `SOCKET_PROPAGATES` (drives the quit mechanism in Task 3) and `GSLAPPER_ALIVE` (early layer-shell signal before the VT test). + +- [ ] **Step 1: Write the spike config** + +`$SCRATCHPAD/spike/config` (use the scratchpad path above; `$SCRATCHPAD` below means that directory): + +``` +background 0.1 0.1 0.1 +exec env > /tmp/cagebreak-spike-env.txt +exec gslapper -f '*' /home/nomadx/Documents/sysc-greet-dev/.claude/worktrees/cagebreak-pivot/wallpapers/sysc-greet-example.png 2> /tmp/cagebreak-spike-gslapper.log +``` + +Note: if `wallpapers/sysc-greet-example.png` does not exist in the worktree, use any PNG on disk (e.g. one from `/usr/share/sysc-greet/wallpapers/`); adjust the path in the config. + +- [ ] **Step 2: Write the runner script** + +`$SCRATCHPAD/spike/run.sh`: + +```bash +#!/usr/bin/env bash +set -u +SPIKE_DIR="$(cd "$(dirname "$0")" && pwd)" +rm -f /tmp/cagebreak-spike-env.txt /tmp/cagebreak-spike-gslapper.log + +WLR_BACKENDS=headless WLR_LIBINPUT_NO_DEVICES=1 WLR_RENDERER=pixman \ + cagebreak -e -c "${SPIKE_DIR}/config" & +CB_PID=$! + +sleep 3 + +FAIL=0 + +# 1. cagebreak still running? +if kill -0 "$CB_PID" 2>/dev/null; then echo "PASS cagebreak running"; else echo "FAIL cagebreak exited early"; FAIL=1; fi + +# 2. CAGEBREAK_SOCKET propagated to exec children? +if grep -q '^CAGEBREAK_SOCKET=' /tmp/cagebreak-spike-env.txt 2>/dev/null; then + echo "PASS SOCKET_PROPAGATES" + SOCK=$(grep '^CAGEBREAK_SOCKET=' /tmp/cagebreak-spike-env.txt | cut -d= -f2) +else + echo "FAIL SOCKET_PROPAGATES (env dump: $(cat /tmp/cagebreak-spike-env.txt 2>/dev/null | head -c 200))" + SOCK="" + FAIL=1 +fi + +# 3. gslapper (layer-shell client) still alive? +if pgrep -x gslapper >/dev/null; then echo "PASS GSLAPPER_ALIVE"; else echo "FAIL GSLAPPER_ALIVE"; cat /tmp/cagebreak-spike-gslapper.log 2>/dev/null; FAIL=1; fi + +# 4. quit via socket +if [ -n "$SOCK" ] && [ -S "$SOCK" ]; then + echo quit | socat - UNIX-CONNECT:"$SOCK" + sleep 2 + if kill -0 "$CB_PID" 2>/dev/null; then echo "FAIL quit-via-socket (still running)"; FAIL=1; else echo "PASS quit-via-socket"; fi +fi + +# Cleanup whatever is left +kill "$CB_PID" 2>/dev/null; pkill -x gslapper 2>/dev/null +exit "$FAIL" +``` + +- [ ] **Step 3: Run it** + +Run: `bash $SCRATCHPAD/spike/run.sh` +Expected: all four `PASS` lines, exit 0. + +- If `FAIL cagebreak exited early`: read cagebreak's stderr (rerun without `&`, capture output). Common causes: `XDG_RUNTIME_DIR` unset, config parse error (cagebreak is strict — one command per line). +- If `WLR_RENDERER=pixman` fails, retry without it (allow GLES on the server GPU). +- If `FAIL SOCKET_PROPAGATES`: record it — Task 3 then uses the `pkill` quit variant. +- If `FAIL GSLAPPER_ALIVE`: read `/tmp/cagebreak-spike-gslapper.log`. If it shows a missing `zwlr_layer_shell_v1` global, the pivot's premise is broken — STOP, report to user (fall back to Cage Lite per spec). + +- [ ] **Step 4: Record results** + +Note the outcomes of `SOCKET_PROPAGATES` and `GSLAPPER_ALIVE` in the task report. Nothing to commit (spike artifacts are scratchpad-only). + +--- + +### Task 2: VT spike on real hardware (USER-ASSISTED GATE) + +Visual confirmation on a real DRM session: wallpaper renders *behind* fullscreen kitty running sysc-greet, keyboard input works, chain-quit works. + +**Files:** +- Create: `/tmp/cagebreak-vt-spike/config` (throwaway, on-disk where a TTY shell can reach it) + +**Interfaces:** +- Consumes: Task 1's `SOCKET_PROPAGATES` result (pick the quit line accordingly). +- Produces: the go/no-go decision for Tasks 3–10. + +- [ ] **Step 1: Build sysc-greet and stage the VT spike config** + +```bash +cd /home/nomadx/Documents/sysc-greet-dev/.claude/worktrees/cagebreak-pivot +mkdir -p /tmp/cagebreak-vt-spike +go build -o /tmp/cagebreak-vt-spike/sysc-greet ./cmd/sysc-greet/ +``` + +Write `/tmp/cagebreak-vt-spike/config` (socket variant — swap the trailing `; echo quit | socat ...` for `; pkill cagebreak` if Task 1 failed SOCKET_PROPAGATES): + +``` +background 0.0 0.0 0.0 +exec gslapper -f '*' /usr/share/sysc-greet/wallpapers/sysc-greet-example.png +exec kitty --start-as=fullscreen -o background_opacity=0.6 /tmp/cagebreak-vt-spike/sysc-greet --test; echo quit | socat - UNIX-CONNECT:"$CAGEBREAK_SOCKET" +``` + +(Adjust the PNG path to any wallpaper that exists on the machine. `background_opacity=0.6` makes the layer-shell wallpaper visibly show through — that's the P0 check.) + +- [ ] **Step 2: Hand off to the user** + +Ask the user to run, from a physical TTY (not SSH): + +```bash +cagebreak -e -c /tmp/cagebreak-vt-spike/config +``` + +And confirm: +1. **P0:** wallpaper image visible behind the translucent kitty/sysc-greet UI +2. **P0:** keyboard input works in sysc-greet (arrow keys, typing) +3. **P0:** quitting sysc-greet (Ctrl+C in test mode) exits kitty AND cagebreak, returning to the TTY — no hang, no loop +4. **P1:** `XKB_DEFAULT_LAYOUT=de cagebreak -e -c ...` gives a German layout (optional) + +- [ ] **Step 3: GATE — wait for user confirmation** + +Do not proceed to Task 3 until the user reports the P0 results. If wallpaper fails: STOP, report, fall back to Cage Lite per spec. + +--- + +### Task 3: Greeter config file; delete cage launcher + +**Files:** +- Create: `config/cagebreak-greeter-config` +- Delete: `scripts/cage-greeter-session.sh` + +**Interfaces:** +- Produces: `/etc/greetd/cagebreak-greeter-config` content consumed verbatim by Task 4 (installer embeds the same content) and shipped by Tasks 5–6. + +- [ ] **Step 1: Write `config/cagebreak-greeter-config`** + +Socket-quit variant (default; if Task 1 failed SOCKET_PROPAGATES, replace everything after `/usr/local/bin/sysc-greet` on the kitty line with `; pkill cagebreak` and drop the socat mention in the comment): + +``` +# sysc-greet cagebreak greeter config — used ONLY by the greetd greeter session +# greetd runs: cagebreak -e -c /etc/greetd/cagebreak-greeter-config +# -e enables the IPC socket used to quit cagebreak after login (requires socat) +# Keyboard layout: set XKB_DEFAULT_LAYOUT/XKB_DEFAULT_VARIANT in greetd's command env + +background 0.0 0.0 0.0 + +# gSlapper wallpaper daemon (wlr-layer-shell client, same as niri/sway setup) +exec HOME=/var/lib/greeter /usr/local/bin/sysc-greet --wallpaper-daemon + +# Greeter UI; when it exits (login or shutdown), quit cagebreak so greetd can start the session +exec XDG_CACHE_HOME=/tmp/greeter-cache HOME=/var/lib/greeter kitty --start-as=fullscreen --config=/etc/greetd/kitty.conf /usr/local/bin/sysc-greet; echo quit | socat - UNIX-CONNECT:"$CAGEBREAK_SOCKET" + +# No bind/definekey lines on purpose: no compositor keybindings reachable from the greeter +``` + +- [ ] **Step 2: Validate the config parses** + +Run: `WLR_BACKENDS=headless WLR_LIBINPUT_NO_DEVICES=1 timeout 5 cagebreak -e -c config/cagebreak-greeter-config; echo "exit=$?"` +Expected: cagebreak starts (timeout kills it, exit=124) with no config parse errors on stderr. The `exec` lines will log failures for `/usr/local/bin/sysc-greet` if not installed — that's fine; only parse errors matter. + +- [ ] **Step 3: Delete the cage launcher** + +```bash +git rm scripts/cage-greeter-session.sh +``` + +- [ ] **Step 4: Commit** + +```bash +git add config/cagebreak-greeter-config +git commit -m "Add cagebreak greeter config, drop cage launcher" +``` + +--- + +### Task 4: Installer swap cage → cagebreak + +**Files:** +- Modify: `cmd/installer/main.go` (comment at ~line 226, compositor list ~line 376, validation map ~line 381 and ~line 959, menu render ~line 564, pacman/apt/dnf install cases ~lines 1003–1035, `configureGreetd` cage case ~line 1628, config write mode ~line 1648, `removeConfigs` ~line 1737) + +**Interfaces:** +- Consumes: config content from Task 3 (embedded verbatim, socket or pkill variant to match). +- Produces: installer option string `"cagebreak"`, greetd command `cagebreak -e -c /etc/greetd/cagebreak-greeter-config`. + +- [ ] **Step 1: Replace identifiers and menu entries** + +In `cmd/installer/main.go`: + +1. Struct comment: `// "niri", "hyprland", "sway", or "cage"` → `// "cagebreak", "niri", "sway", or "hyprland"` +2. Selection list: `compositors := []string{"cage", "niri", "sway", "hyprland"}` → `compositors := []string{"cagebreak", "niri", "sway", "hyprland"}` +3. Both validation maps (`Update` and `installCompositor`): `"cage": {"cage"},` → `"cagebreak": {"cagebreak"},` +4. Menu render block: + +```go + compositors := []struct { + name string + desc string + }{ + {"cagebreak", "Recommended — minimal tiling kiosk; full gSlapper wallpapers"}, + {"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 cagebreak"}, + } +``` + +5. Footer line: `"Hyprland greeter support is being phased out — cage is the recommended path"` → `"Hyprland greeter support is being phased out — cagebreak is the recommended path"` and the index-3 warning text `Use cage or niri instead.` → `Use cagebreak or niri instead.` + +- [ ] **Step 2: Package-manager install cases** + +pacman case — replace: + +```go + case "cage": + cmd = exec.Command("pacman", "-S", "--noconfirm", "cage") +``` + +with: + +```go + case "cagebreak": + return installCagebreakArch(m) +``` + +apt and dnf cases — replace the two `case "cage":` returns with: + +```go + case "cagebreak": + return fmt.Errorf("cagebreak not in standard repos — build from https://github.com/project-repo/cagebreak") +``` + +Add this function near `installCompositor`: + +```go +// installCagebreakArch installs cagebreak on Arch: official repos first, then +// an AUR helper run as the invoking user (AUR helpers refuse to run as root). +func installCagebreakArch(m *model) error { + if err := exec.Command("pacman", "-S", "--noconfirm", "cagebreak").Run(); err == nil { + return nil + } + sudoUser := os.Getenv("SUDO_USER") + for _, helper := range []string{"paru", "yay"} { + helperPath, err := exec.LookPath(helper) + if err != nil { + continue + } + var cmd *exec.Cmd + if sudoUser != "" { + cmd = exec.Command("sudo", "-u", sudoUser, helperPath, "-S", "--noconfirm", "cagebreak") + } else { + cmd = exec.Command(helperPath, "-S", "--noconfirm", "cagebreak") + } + if err := cmd.Run(); err == nil { + return nil + } + } + return fmt.Errorf("cagebreak is AUR-only — install it first: paru -S cagebreak (or yay -S cagebreak)") +} +``` + +- [ ] **Step 3: `configureGreetd` — replace the cage case** + +Replace the whole `case "cage":` block with (config string must byte-match Task 3's file): + +```go + case "cagebreak": + compositorConfig = `# sysc-greet cagebreak greeter config — used ONLY by the greetd greeter session +# greetd runs: cagebreak -e -c /etc/greetd/cagebreak-greeter-config +# -e enables the IPC socket used to quit cagebreak after login (requires socat) +# Keyboard layout: set XKB_DEFAULT_LAYOUT/XKB_DEFAULT_VARIANT in greetd's command env + +background 0.0 0.0 0.0 + +# gSlapper wallpaper daemon (wlr-layer-shell client, same as niri/sway setup) +exec HOME=/var/lib/greeter /usr/local/bin/sysc-greet --wallpaper-daemon + +# Greeter UI; when it exits (login or shutdown), quit cagebreak so greetd can start the session +exec XDG_CACHE_HOME=/tmp/greeter-cache HOME=/var/lib/greeter kitty --start-as=fullscreen --config=/etc/greetd/kitty.conf /usr/local/bin/sysc-greet; echo quit | socat - UNIX-CONNECT:"$CAGEBREAK_SOCKET" + +# No bind/definekey lines on purpose: no compositor keybindings reachable from the greeter +` + configPath = "/etc/greetd/cagebreak-greeter-config" + greetdCommand = "cagebreak -e -c /etc/greetd/cagebreak-greeter-config" +``` + +Then remove the cage 0755 special case — restore: + +```go + // Write compositor config + if err := os.WriteFile(configPath, []byte(compositorConfig), 0644); err != nil { + return fmt.Errorf("compositor config write failed") + } +``` + +- [ ] **Step 4: Uninstall cleanup** + +In `removeConfigs`, replace `"/etc/greetd/cage-greeter-session.sh",` with `"/etc/greetd/cagebreak-greeter-config",`. + +- [ ] **Step 5: Build and vet** + +Run: `go build ./... && go vet ./cmd/installer/` +Expected: clean build, no vet errors. Also run `grep -rn '"cage"' cmd/installer/main.go` — expected: no matches. + +- [ ] **Step 6: Commit** + +```bash +git add cmd/installer/main.go +git commit -m "Switch installer greeter backend from cage to cagebreak" +``` + +--- + +### Task 5: Nix flake swap cage → cagebreak + +**Files:** +- Modify: `flake.nix` (package build script cage block ~line 90, `compositorPackage` ~line 148, `defaultCompositorCommand` ~line 157, enum ~line 170, `cagePackage` option ~line 211, `environment.etc` ~line 272) + +**Interfaces:** +- Consumes: `config/cagebreak-greeter-config` from Task 3. +- Produces: NixOS options `compositor = "cagebreak"` and `cagebreakPackage`. + +- [ ] **Step 1: Package build — replace the cage launcher block** + +Replace: + +```nix + # 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 +``` + +with: + +```nix + # Cagebreak greeter config (gSlapper layer-shell wallpapers supported) + cp config/cagebreak-greeter-config $out/etc/greetd/ + substituteInPlace $out/etc/greetd/cagebreak-greeter-config \ + --replace '/usr/local/bin/sysc-greet' "$out/bin/sysc-greet" \ + --replace 'kitty ' "${pkgs.kitty}/bin/kitty " \ + --replace 'socat ' "${pkgs.socat}/bin/socat " +``` + +- [ ] **Step 2: Module options** + +1. `compositorPackage`: `else if cfg.compositor == "cage" then cfg.cagePackage` → `else if cfg.compositor == "cagebreak" then cfg.cagebreakPackage` +2. `defaultCompositorCommand`: + +```nix + else if cfg.compositor == "cagebreak" then + "${compositorExecutable cfg.cagebreakPackage "cagebreak"} -e -c /etc/greetd/cagebreak-greeter-config" +``` + +3. Enum: `types.enum [ "niri" "hyprland" "sway" "cage" ]` → `types.enum [ "niri" "hyprland" "sway" "cagebreak" ]`, description: `Use cagebreak for a minimal tiling kiosk greeter with full gSlapper wallpaper support (recommended).` +4. Rename option `cagePackage` → `cagebreakPackage`: + +```nix + cagebreakPackage = mkOption { + type = types.nullOr types.package; + default = null; + defaultText = literalExpression "null"; + description = "cagebreak package to use and install for the greeter compositor. When null, the cagebreak command is resolved from PATH."; + }; +``` + +5. `environment.etc`: `"greetd/cage-greeter-session.sh".source = ...cage-greeter-session.sh";` → `"greetd/cagebreak-greeter-config".source = "${package}/etc/greetd/cagebreak-greeter-config";` + +- [ ] **Step 3: Validate** + +Run: `command -v nix >/dev/null && nix flake check --no-build 2>&1 | tail -5 || nix-instantiate --parse flake.nix >/dev/null 2>&1 || echo "nix unavailable — grep fallback"` +If nix is unavailable, verify by inspection: `grep -n 'cage' flake.nix` — expected: only `cagebreak` matches, no bare-cage references. + +- [ ] **Step 4: Commit** + +```bash +git add flake.nix +git commit -m "Use cagebreak in Nix module" +``` + +--- + +### Task 6: Packaging (nfpm) + postinstall auto-detect + +**Files:** +- Modify: `nfpm.yaml` (description ~line 10, cage launcher entry ~line 61) +- Modify: `scripts/postinstall.sh` (auto-detect block ~lines 26–54) + +**Interfaces:** +- Consumes: greetd command string from Global Constraints. + +- [ ] **Step 1: nfpm.yaml** + +Description: `Supports Niri, Hyprland, Sway, and experimental Cage compositors.` → `Supports Cagebreak, Niri, and Sway compositors (Hyprland deprecated).` + +Replace: + +```yaml + # Cage lite launcher (experimental) + - src: ./scripts/cage-greeter-session.sh + dst: /etc/greetd/cage-greeter-session.sh + file_info: + mode: 0755 +``` + +with: + +```yaml + # Cagebreak greeter config + - src: ./config/cagebreak-greeter-config + dst: /etc/greetd/cagebreak-greeter-config + file_info: + mode: 0644 +``` + +- [ ] **Step 2: postinstall.sh auto-detect** + +Replace the cage branch: + +```sh +if command -v cage &>/dev/null; then + COMPOSITOR="cage" + GREETD_COMMAND="cage -s -m extend -- /etc/greetd/cage-greeter-session.sh" +``` + +with: + +```sh +if command -v cagebreak &>/dev/null; then + COMPOSITOR="cagebreak" + GREETD_COMMAND="cagebreak -e -c /etc/greetd/cagebreak-greeter-config" +``` + +Update the two user-facing strings: `(cage, niri, sway, hyprland)` → `(cagebreak, niri, sway, hyprland)`, `Please install cage (recommended)` → `Please install cagebreak (recommended)`, and in the hyprland warning `Migrate to cage: SYSC_COMPOSITOR=cage sudo ./install.sh` → `Migrate to cagebreak: SYSC_COMPOSITOR=cagebreak sudo ./install.sh` plus doc URL `compositors/cage/` → `compositors/cagebreak/`. + +- [ ] **Step 3: Validate** + +Run: `bash -n scripts/postinstall.sh && grep -c cagebreak scripts/postinstall.sh nfpm.yaml` +Expected: syntax OK; cagebreak referenced in both files; `grep -n '\bcage\b' scripts/postinstall.sh nfpm.yaml` returns nothing. + +- [ ] **Step 4: Commit** + +```bash +git add nfpm.yaml scripts/postinstall.sh +git commit -m "Package cagebreak greeter config and prefer it in postinstall" +``` + +--- + +### Task 7: Verify script + +**Files:** +- Create: `scripts/verify-cagebreak-greeter.sh` +- Delete: `scripts/verify-cage-greeter.sh` + +- [ ] **Step 1: Write `scripts/verify-cagebreak-greeter.sh`** + +```bash +#!/usr/bin/env bash +# verify-cagebreak-greeter.sh — smoke checks for the cagebreak greeter integration +set -euo pipefail + +ROOT="$(cd "$(dirname "$0")/.." && pwd)" +CONFIG="${ROOT}/config/cagebreak-greeter-config" +FAIL=0 + +pass() { echo "✓ $*"; } +fail() { echo "✗ $*"; FAIL=1; } + +echo "=== Cagebreak greeter verification ===" + +if [[ -f "${CONFIG}" ]]; then + pass "greeter config exists: ${CONFIG}" +else + fail "greeter config missing: ${CONFIG}" +fi + +if command -v cagebreak >/dev/null 2>&1; then + pass "cagebreak binary found: $(command -v cagebreak) ($(cagebreak -v 2>&1))" + if WLR_BACKENDS=headless WLR_LIBINPUT_NO_DEVICES=1 timeout 5 cagebreak -e -c "${CONFIG}" >/dev/null 2>&1 || [[ $? -eq 124 ]]; then + pass "config parses under headless cagebreak" + else + fail "cagebreak rejected the greeter config" + fi +else + echo "⚠ cagebreak not installed — skip runtime test (Arch: paru -S cagebreak)" +fi + +if command -v socat >/dev/null 2>&1; then + pass "socat available (needed for quit-via-socket)" +else + fail "socat missing — greeter cannot quit cagebreak after login" +fi + +if grep -q '"cagebreak"' "${ROOT}/cmd/installer/main.go"; then + pass "installer references cagebreak" +else + fail "installer does not reference cagebreak" +fi + +if [[ -f "${ROOT}/docs-src/compositors/cagebreak.md" ]]; then + pass "cagebreak docs present" +else + fail "docs-src/compositors/cagebreak.md missing" +fi + +echo +if [[ "${FAIL}" -eq 0 ]]; then + echo "All static checks passed." + echo "Manual: SYSC_COMPOSITOR=cagebreak sudo ./install.sh → systemctl restart greetd → test login" +else + echo "Some checks failed." + exit 1 +fi +``` + +Then: `chmod +x scripts/verify-cagebreak-greeter.sh && git rm scripts/verify-cage-greeter.sh` + +- [ ] **Step 2: Run it** + +Run: `bash scripts/verify-cagebreak-greeter.sh` +Expected: all checks pass except the docs check (✗ until Task 8) — acceptable interim state; re-run in Task 9. + +- [ ] **Step 3: Commit** + +```bash +git add scripts/verify-cagebreak-greeter.sh +git commit -m "Add cagebreak verify script" +``` + +--- + +### Task 8: Documentation swap + +**Files:** +- Create: `docs-src/compositors/cagebreak.md` +- Delete: `docs-src/compositors/cage.md` +- Modify: `mkdocs.yml:63`, `docs-src/compositors/hyprland.md:1-12`, `docs-src/getting-started/installation.md`, `README.md` +- Modify: `docs/superpowers/specs/2026-06-18-cage-compositor-design.md:1-5`, `docs/superpowers/plans/2026-06-18-cage-compositor-plan.md:1-5` (superseded banners) + +- [ ] **Step 1: Write `docs-src/compositors/cagebreak.md`** + +```markdown +# Cagebreak Setup + +!!! tip "Recommended greeter backend" + Cagebreak 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). + +Unlike the earlier Cage experiment, Cagebreak supports **wlr-layer-shell**, so gSlapper boot wallpapers (static and video) work exactly as they do under niri and sway — full feature parity with the Hyprland greeter. + +Tracked in [issue #69](https://github.com/Nomadcxx/sysc-greet/issues/69). + +## Why Cagebreak? + +- **Minimal tiling kiosk** — a Cage fork built for exactly this: a fullscreen app plus support clients +- **Full wallpapers** — gSlapper runs as a layer-shell client, same as niri/sway +- **No config churn** — a ~10 line static config vs Hyprland's window rules, layer rules, and exit chains +- **Locked down** — the greeter config defines zero keybindings + +## Install Cagebreak + +=== "Arch Linux" + + Cagebreak is in the AUR (not the official repos): + + ```bash + paru -S cagebreak # or: yay -S cagebreak + ``` + + Also install socat (used to quit the compositor after login): + + ```bash + sudo pacman -S socat + ``` + +=== "NixOS" + + ```nix + services.sysc-greet = { + enable = true; + compositor = "cagebreak"; + # optional: cagebreakPackage = pkgs.cagebreak; + }; + ``` + +=== "Other distros" + + Check your package manager or build from [project-repo/cagebreak](https://github.com/project-repo/cagebreak). + +## greetd Config + +=== "Installer" + + ```bash + SYSC_COMPOSITOR=cagebreak sudo ./install.sh + ``` + +=== "Manual" + + Edit `/etc/greetd/config.toml`: + + ```toml + [terminal] + vt = 1 + + [default_session] + command = "cagebreak -e -c /etc/greetd/cagebreak-greeter-config" + user = "greeter" + ``` + + `-e` enables cagebreak's IPC socket; the greeter config uses it to quit the compositor after login. + +The greeter config lives at `/etc/greetd/cagebreak-greeter-config` and is installed by the installer or package. + +## Keyboard Layout + +Cagebreak uses the standard XKB environment variables. For a non-US layout, wrap the greetd command: + +```toml +command = "env XKB_DEFAULT_LAYOUT=de XKB_DEFAULT_VARIANT=nodeadkeys cagebreak -e -c /etc/greetd/cagebreak-greeter-config" +``` + +## Feature Matrix + +| Feature | Cagebreak | niri / sway | Hyprland (deprecated) | +|---------|-----------|-------------|-----------------------| +| Login via greetd IPC | ✅ | ✅ | ✅ | +| TUI background effects | ✅ | ✅ | ✅ | +| Static/video boot wallpapers (gSlapper) | ✅ | ✅ | ✅ | +| Non-US keyboard | ✅ XKB env | ✅ compositor config | ✅ compositor config | +| Greeter config size | ~10 lines | ~80 lines | ~100+ lines | + +## Migrating from Hyprland + +1. Install cagebreak (see above) +2. Re-run the installer: `SYSC_COMPOSITOR=cagebreak sudo ./install.sh` +3. Reboot (or `sudo systemctl restart greetd` from a TTY) + +Your daily Hyprland desktop session is unaffected — only the boot greeter changes. + +## Troubleshooting + +- **Black screen, no wallpaper:** check gSlapper is installed and `/var/cache/sysc-greet/` is readable by the greeter user +- **Greeter restarts in a loop:** run `cagebreak -e -c /etc/greetd/cagebreak-greeter-config` from a TTY as your user to see the error; a config parse error makes cagebreak exit immediately and greetd relaunches it +- **Stuck after login:** verify `socat` is installed — without it the compositor never quits and greetd waits forever +``` + +Then: `git rm docs-src/compositors/cage.md` + +- [ ] **Step 2: mkdocs.yml nav** + +`- Cage (experimental): compositors/cage.md` → `- Cagebreak: compositors/cagebreak.md` + +- [ ] **Step 3: Cross-references in hyprland.md / installation.md / README.md** + +All in the deprecation copy added by the cage draft — swap cage → cagebreak: + +- `docs-src/compositors/hyprland.md`: `**Migrate to [Cage](cage.md)** (recommended — faster, simpler)` → `**Migrate to [Cagebreak](cagebreak.md)** (recommended — full wallpaper parity)` +- `docs-src/getting-started/installation.md`: `(cage recommended, niri, sway, or hyprland deprecated)` → `(cagebreak recommended, niri, sway, or hyprland deprecated)`; `Migrate to [Cage](../compositors/cage.md)` → `Migrate to [Cagebreak](../compositors/cagebreak.md)`; prerequisites line `cage (recommended), niri, sway` → `cagebreak (recommended), niri, sway`; the AUR block + + ``` + # Cage (recommended): install cage from repos, then: + sudo pacman -S cage + SYSC_COMPOSITOR=cage curl -fsSL .../install.sh | sudo bash + ``` + + becomes + + ``` + # Cagebreak (recommended): install from AUR, then: + paru -S cagebreak && sudo pacman -S socat + SYSC_COMPOSITOR=cagebreak curl -fsSL .../install.sh | sudo bash + ``` + +- `README.md`: `New installs should choose **cage** (recommended) or **niri**.` → `New installs should choose **cagebreak** (recommended) or **niri**.`; the AUR comment `# Recommended (cage — install script, or: pacman -S cage && SYSC_COMPOSITOR=cage ./install.sh)` → `# Recommended (cagebreak — AUR: paru -S cagebreak, then SYSC_COMPOSITOR=cagebreak ./install.sh)`; `(deprecated — migrate to cage)` → `(deprecated — migrate to cagebreak)`; Nix example `compositor = "cage"; # recommended` → `compositor = "cagebreak"; # recommended` and `set \`cagePackage\`, \`niriPackage\`` → `set \`cagebreakPackage\`, \`niriPackage\``; `does not install \`cage\`, \`niri\`` → `does not install \`cagebreak\`, \`niri\`` + +- [ ] **Step 4: Superseded banners on the June 18 docs** + +Insert directly under the title of `docs/superpowers/specs/2026-06-18-cage-compositor-design.md`: + +```markdown +> **SUPERSEDED (2026-07-06):** This investigation pivoted from Cage to Cagebreak (layer-shell → full gSlapper wallpaper parity). See [2026-07-06-cagebreak-compositor-design.md](2026-07-06-cagebreak-compositor-design.md). +``` + +And under the title of `docs/superpowers/plans/2026-06-18-cage-compositor-plan.md`: + +```markdown +> **SUPERSEDED (2026-07-06):** See [2026-07-06-cagebreak-compositor-plan.md](2026-07-06-cagebreak-compositor-plan.md). +``` + +- [ ] **Step 5: Verify no stale references** + +Run: `grep -rn --include='*.md' -i '\bcage\b' README.md docs-src/ mkdocs.yml | grep -v -i cagebreak` +Expected: no hits outside the two superseded June 18 docs (docs/superpowers is excluded from the grep paths on purpose). + +- [ ] **Step 6: Commit** + +```bash +git add docs-src/compositors/cagebreak.md mkdocs.yml docs-src/compositors/hyprland.md docs-src/getting-started/installation.md README.md docs/superpowers/specs/2026-06-18-cage-compositor-design.md docs/superpowers/plans/2026-06-18-cage-compositor-plan.md +git commit -m "Document cagebreak greeter backend" +``` + +--- + +### Task 9: Full verification pass + +**Files:** none new. + +- [ ] **Step 1: Build + tests + vet** + +Run: `go build ./... && go test ./... && go vet ./...` +Expected: build OK, `ok github.com/Nomadcxx/sysc-greet/cmd/sysc-greet`, no vet errors. + +- [ ] **Step 2: Verify script clean** + +Run: `bash scripts/verify-cagebreak-greeter.sh` +Expected: ALL checks ✓ (docs now exist), exit 0. + +- [ ] **Step 3: Whole-repo stale-cage sweep** + +Run: `grep -rn '\bcage\b' --include='*.go' --include='*.sh' --include='*.yaml' --include='*.yml' --include='*.nix' cmd/ scripts/ config/ flake.nix nfpm.yaml mkdocs.yml` +Expected: no matches (cagebreak only). + +- [ ] **Step 4: Commit any stragglers** + +Only if steps 1–3 forced fixes; use explicit paths, message `Fix cagebreak review nits`. + +--- + +### Task 10: Update PR #78 (user approval required to push) + +**Files:** none (GitHub metadata + push). + +- [ ] **Step 1: ASK THE USER before pushing** + +CLAUDE.md: never push automatically. Present the commit list (`git log --oneline origin/feat/cage-compositor-investigation..HEAD`) and ask for approval to push. + +- [ ] **Step 2: Push and retarget (after approval)** + +```bash +git push origin feat/cage-compositor-investigation +gh pr edit 78 --repo Nomadcxx/sysc-greet --base development --title "feat: Cagebreak greeter path + Hyprland deprecation (draft)" +``` + +- [ ] **Step 3: Update the PR body (after approval)** + +```bash +gh pr edit 78 --repo Nomadcxx/sysc-greet --body "$(cat <<'EOF' +## Summary + +Implements [#69](https://github.com/Nomadcxx/sysc-greet/issues/69): add **Cagebreak** as the recommended greetd backend and begin **phasing out Hyprland** for the greeter session. + +Pivoted from the earlier Cage Lite draft: Cagebreak supports **wlr-layer-shell**, so gSlapper boot wallpapers (static + video) work — **full feature parity with the Hyprland greeter**, none of Cage Lite's trade-offs. + +**Hyprland is NOT removed in this PR** — it remains functional but is marked deprecated with a ~3 month removal timeline. + +## What changed + +- **Cagebreak path:** `cagebreak -e -c /etc/greetd/cagebreak-greeter-config` → gSlapper (layer-shell wallpapers) + Kitty → sysc-greet; compositor quits via IPC socket after login +- **Installer:** cagebreak first in menu (recommended); AUR-helper fallback on Arch; hyprland last (deprecated warning) +- **Nix:** `compositor = "cagebreak"` + `cagebreakPackage` option +- **postinstall:** prefer cagebreak → niri → sway → hyprland; warn on hyprland auto-detect +- **Docs:** `docs-src/compositors/cagebreak.md` (replaces cage.md), migration guide, deprecation banners +- **Design:** `docs/superpowers/specs/2026-07-06-cagebreak-compositor-design.md` +- **Verified on hardware:** wallpaper renders behind the greeter, login works, compositor exits cleanly (spike results in plan doc) + +## Manual test checklist + +- [ ] `paru -S cagebreak && sudo pacman -S socat` +- [ ] `SYSC_COMPOSITOR=cagebreak sudo ./install.sh` +- [ ] `sudo systemctl restart greetd` (from TTY) +- [ ] Greeter appears **with wallpaper**, login works +- [ ] Non-US keyboard: `XKB_DEFAULT_LAYOUT` in greetd command env +EOF +)" +``` + +- [ ] **Step 4: Report** + +Link the PR, summarize spike results, and remind: merge target is `development`, merge only on explicit user sign-off. 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..b80de65 --- /dev/null +++ b/docs/superpowers/specs/2026-06-18-cage-compositor-design.md @@ -0,0 +1,244 @@ +# Cage / Cagebreak Compositor — Design Spec + +> **SUPERSEDED (2026-07-06):** This investigation pivoted from Cage to Cagebreak (layer-shell restores gSlapper wallpapers). See [2026-07-06-cagebreak-compositor-design.md](2026-07-06-cagebreak-compositor-design.md). + +> **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-07-06-cagebreak-compositor-design.md b/docs/superpowers/specs/2026-07-06-cagebreak-compositor-design.md new file mode 100644 index 0000000..849427f --- /dev/null +++ b/docs/superpowers/specs/2026-07-06-cagebreak-compositor-design.md @@ -0,0 +1,205 @@ +# Cagebreak Compositor — Design Spec + +> **Status:** Approved design (spike passed; hardware-validated 2026-07-06) +> **Amended 2026-07-06:** niri remains the default compositor. Cagebreak is the +> Hyprland replacement only — never labeled "recommended". Menu/detect order: +> niri, cagebreak, sway, hyprland. Wallpapers serve secondary monitors; the +> greeter TUI stays fully opaque. +> **Issue:** [#69 Feature request: cage](https://github.com/Nomadcxx/sysc-greet/issues/69) +> **PR:** [#78](https://github.com/Nomadcxx/sysc-greet/pull/78) — pivoted from Cage Lite to Cagebreak +> **Supersedes:** [2026-06-18-cage-compositor-design.md](2026-06-18-cage-compositor-design.md) +> **Branch:** `feat/cage-compositor-investigation` (worktree-isolated; merge target is `development`, NOT `master`) + +## Problem + +Hyprland is expensive as a *greeter* compositor: config churn, heavy cold-start, and +most greeter-specific bug reports trace to compositor integration rather than +sysc-greet Go code. We want a minimal, stable replacement so Hyprland greeter +support can be deprecated and eventually removed. + +The previous draft of PR #78 chose **Cage** ("Cage Lite"). Cage works with greetd +(it is the standard `cage -s -- gtkgreet` pattern on the Arch wiki), but it has **no +wlr-layer-shell**, so gSlapper wallpapers can never work — users migrating from +Hyprland would silently lose boot wallpapers. That makes Cage Lite a weak +deprecation story. + +## Decision + +Replace Cage with **[Cagebreak](https://github.com/project-repo/cagebreak)** +(v3.2.1, released 2026-06-13, actively maintained). Cagebreak is a Cage fork with +tiling, a config file, an IPC socket, and — critically — **wlr-layer-shell scene +trees** (`layer_shell_background/bottom/top` in `output.h`), which should let +gSlapper render wallpapers exactly as it does under niri/sway/Hyprland. + +Why this beats Cage Lite: + +| Capability | Cage 0.3.x | Cagebreak 3.2.x | +|---|---|---| +| greetd session | ✅ | ✅ (needs empirical spike) | +| gSlapper wallpapers (layer-shell) | ❌ never | ✅ expected — spike gate | +| Config file (`-c `) | ❌ flags only | ✅ | +| Startup exec of multiple clients | ❌ single app | ✅ `exec ` | +| Programmatic quit | exits with client | ✅ `quit` via IPC socket (`-e`) | +| XKB layout | `XKB_DEFAULT_*` env | `XKB_DEFAULT_*` env | +| Keybinding lockdown | n/a | ✅ define no binds | +| Arch packaging | extra repo | ⚠️ AUR only (`cagebreak`, `cagebreak-bin`) | + +The single downside is packaging: cagebreak is AUR-only on Arch. The installer +handles this (see below). Nixpkgs ships `cagebreak`. + +## Goals + +1. Add **cagebreak** as a supported greeter backend with **full feature parity** + (gSlapper wallpapers, videos, multi-monitor) alongside niri and sway +2. **Phase out Hyprland** for the greeter session — deprecated in this PR with + user-facing notices, removed ~3 months after cagebreak ships stable +3. Preserve login reliability (greetd IPC, session start, keyboard input) +4. No Go application changes — sysc-greet is compositor-agnostic + +## Non-goals + +- Keeping Cage Lite as a parallel option (removed from this PR; can return later + if someone asks for a no-wallpaper kiosk path) +- Removing Hyprland support in this PR +- Dropping niri or sway support + +## Spike Gate (must pass before the PR rewrite ships) + +The design hinges on one empirical unknown: **does gSlapper's layer-shell +wallpaper actually render under cagebreak?** Nobody has tested it. The spike +runs on real hardware (a VT, not SSH — wlroots needs DRM/seat): + +1. Install `cagebreak` (AUR: `yay -S cagebreak` or `cagebreak-bin`) on the test + machine. *Note: as of 2026-07-06 cagebreak is NOT installed on the dev + server — only cage 0.3.0.* +2. Minimal test config exercising the exact greeter pattern: + - `background 0.0 0.0 0.0` + - `exec` gSlapper (via `sysc-greet --wallpaper-daemon`) + - `exec` kitty → `sysc-greet --test`, then quit via socket + - no `bind`/`definekey` lines +3. Run `cagebreak -e -c /tmp/cagebreak-spike-config` from a VT. +4. Verify, in order of importance: + - **P0:** wallpaper renders behind kitty (layer-shell background works) + - **P0:** login-shaped flow works: kitty fullscreen, keyboard input reaches + sysc-greet + - **P0:** compositor exits when the kitty chain sends `quit` to + `$CAGEBREAK_SOCKET` (confirm the env var propagates to `exec` children; + fallback if not: `pkill cagebreak` as last resort) + - **P1:** `XKB_DEFAULT_LAYOUT` honored + - **P1:** single view occupies the full output (no visible tiling chrome) + - **P1:** multi-monitor behavior sane (if second output available) + +**Gate outcome:** If the P0 wallpaper test fails, STOP — the pivot loses its +rationale; fall back to the existing Cage Lite implementation already on the +branch and re-evaluate. + +## Architecture + +Mirrors the **niri pattern** (config-driven), not the Cage launcher-script +pattern: + +``` +greetd → cagebreak -e -c /etc/greetd/cagebreak-greeter-config + ├─ exec sysc-greet --wallpaper-daemon (gSlapper, layer-shell) + └─ exec kitty → sysc-greet ; quit-via-socket +``` + +### Components + +| File | Change | +|---|---| +| `config/cagebreak-greeter-config` | **New.** Greeter config (see below). Replaces `scripts/cage-greeter-session.sh` (deleted). | +| `cmd/installer/main.go` | `cage` → `cagebreak` in menu/validation/install/greetd config/uninstall. AUR-aware install on Arch. | +| `flake.nix` | `compositor = "cagebreak"` enum value + `cagebreakPackage` option; ship the config via `environment.etc`; drop cage branches. | +| `nfpm.yaml` | Ship `/etc/greetd/cagebreak-greeter-config` (0644); drop cage launcher. | +| `scripts/postinstall.sh` | Auto-detect order: `cagebreak → niri → sway → hyprland` (deprecation warning on hyprland). | +| `scripts/verify-cagebreak-greeter.sh` | Rewritten smoke checks + spike helper. Replaces `verify-cage-greeter.sh`. | +| `docs-src/compositors/cagebreak.md` | Replaces `cage.md`: setup, AUR note, full-parity feature table, Hyprland migration guide. | +| `docs-src/compositors/hyprland.md`, `installation.md`, `README.md`, `mkdocs.yml` | Deprecation notices retained, cage references → cagebreak. | + +### Greeter config (`config/cagebreak-greeter-config`) + +``` +# sysc-greet cagebreak greeter config — used ONLY by greetd +background 0.0 0.0 0.0 +exec HOME=/var/lib/greeter /usr/local/bin/sysc-greet --wallpaper-daemon +exec XDG_CACHE_HOME=/tmp/greeter-cache HOME=/var/lib/greeter kitty --start-as=fullscreen --config=/etc/greetd/kitty.conf /usr/local/bin/sysc-greet; echo quit | socat - UNIX-CONNECT:"$CAGEBREAK_SOCKET" +# No bind/definekey lines: no compositor keybindings reachable from the greeter +``` + +Exact quit mechanism (socat vs alternative) is confirmed during the spike; if +`$CAGEBREAK_SOCKET` does not propagate to `exec` children, fall back to +`pkill cagebreak`. `socat` becomes a package dependency only if the socket path +is used (Arch: `socat` in core/extra; document in nfpm depends + docs). + +### greetd command + +```toml +[default_session] +command = "cagebreak -e -c /etc/greetd/cagebreak-greeter-config" +user = "greeter" +``` + +`-e` enables the IPC socket needed for `quit`. XKB overrides documented as: +`command = "env XKB_DEFAULT_LAYOUT=de cagebreak -e -c ..."`. + +### Installer UX + +``` +niri — Tiling compositor with scrollable workspaces (default) +cagebreak — Minimal tiling kiosk; replaces hyprland for the greeter +sway — Stable i3-compatible tiling compositor +hyprland (deprecated) — Greeter support ending in ~3 months; migrate to cagebreak +``` + +Arch install path for cagebreak: try `pacman -S cagebreak` (in case it enters +official repos), else detect `paru`/`yay` and install from AUR **as the invoking +user** (AUR helpers refuse root; use `SUDO_USER`), else print manual AUR +instructions and continue (compositor presence is validated before this step +anyway). + +### NixOS module + +```nix +services.sysc-greet = { + compositor = "cagebreak"; + cagebreakPackage = pkgs.cagebreak; # optional; PATH resolution when null +}; +``` + +`defaultCompositorCommand`: +`"${cagebreak}/bin/cagebreak -e -c /etc/greetd/cagebreak-greeter-config"`. + +### Go application changes + +**None.** sysc-greet already launches gSlapper generically and uses greetd IPC +regardless of compositor. + +## PR / branch mechanics + +- Work continues on `feat/cage-compositor-investigation` in an isolated worktree + (`.claude/worktrees/cagebreak-pivot`). +- PR #78 base is retargeted `master` → `development`. +- PR title/body rewritten for the cagebreak pivot. +- The old cage spec/plan docs remain in-tree with a "superseded" banner for + the investigation record. +- **Never merge without explicit user sign-off; merge target is `development`.** + +## Risks & Mitigations + +| Risk | Mitigation | +|---|---| +| Layer-shell doesn't actually work with gSlapper | Spike gate before rewrite; fall back to Cage Lite already on branch | +| `$CAGEBREAK_SOCKET` not visible to exec children | Spike verifies; `pkill cagebreak` fallback | +| AUR-only packaging on Arch | Installer AUR-helper fallback + manual instructions; document clearly | +| Cagebreak default keybindings reachable pre-login | Greeter config defines zero binds; spike confirms no built-in defaults remain | +| greetd restart loop on compositor crash | Config validated by `cagebreak -c ` parse; verify script checks config syntax | +| Smaller community than cage-kiosk/cage | Accepted: 3.2.1 June 2026 shows active maintenance; revisit at Hyprland-removal decision | + +## Success Criteria + +1. Spike passes all P0 checks on real hardware +2. `SYSC_COMPOSITOR=cagebreak sudo ./install.sh` produces a working greetd config +3. Boot → greeter with gSlapper wallpaper → login → session start, no loop +4. Hyprland deprecation notices present in installer, postinstall, docs +5. PR #78 updated (base `development`), out of draft only after criteria 1–4 diff --git a/flake.nix b/flake.nix index 2773765..73d1aef 100644 --- a/flake.nix +++ b/flake.nix @@ -87,6 +87,13 @@ --replace 'kitty ' "${pkgs.kitty}/bin/kitty " \ --replace 'swaymsg ' "${pkgs.sway}/bin/swaymsg " + # Cagebreak greeter config (gSlapper layer-shell wallpapers supported) + cp config/cagebreak-greeter-config $out/etc/greetd/ + substituteInPlace $out/etc/greetd/cagebreak-greeter-config \ + --replace '/usr/local/bin/sysc-greet' "$out/bin/sysc-greet" \ + --replace 'kitty ' "${pkgs.kitty}/bin/kitty " \ + --replace 'socat ' "${pkgs.socat}/bin/socat " + # 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 == "cagebreak" then cfg.cagebreakPackage 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 == "cagebreak" then + "${compositorExecutable cfg.cagebreakPackage "cagebreak"} -e -c /etc/greetd/cagebreak-greeter-config" 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" "cagebreak" ]; default = "niri"; - description = "Wayland compositor to use with sysc-greet"; + description = "Wayland compositor to use with sysc-greet. cagebreak replaces hyprland for the greeter session."; }; 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."; }; + cagebreakPackage = mkOption { + type = types.nullOr types.package; + default = null; + defaultText = literalExpression "null"; + description = "cagebreak package to use and install for the greeter compositor. When null, the cagebreak 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/cagebreak-greeter-config".source = "${package}/etc/greetd/cagebreak-greeter-config"; "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..9cce644 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -58,8 +58,9 @@ nav: - Keyboard Layout: configuration/keyboard-layout.md - Compositors: - Niri: compositors/niri.md - - Hyprland: compositors/hyprland.md + - Cagebreak: compositors/cagebreak.md - Sway: compositors/sway.md + - Hyprland (deprecated): compositors/hyprland.md - Development: - Architecture: development/architecture.md - Building: development/building.md diff --git a/nfpm.yaml b/nfpm.yaml index 7e9a4bf..89e985c 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, Cagebreak, and Sway compositors (Hyprland deprecated). vendor: Nomadcxx homepage: https://github.com/Nomadcxx/sysc-greet license: GPL-3.0 @@ -58,6 +58,12 @@ contents: file_info: mode: 0644 + # Cagebreak greeter config + - src: ./config/cagebreak-greeter-config + dst: /etc/greetd/cagebreak-greeter-config + file_info: + mode: 0644 + # Polkit rule for greeter shutdown/reboot - src: ./config/85-greeter.rules dst: /etc/polkit-1/rules.d/85-greeter.rules diff --git a/scripts/postinstall.sh b/scripts/postinstall.sh index 0596f47..ccad5e0 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 COMPOSITOR="niri" GREETD_COMMAND="niri -c /etc/greetd/niri-greeter-config.kdl" +elif command -v cagebreak &>/dev/null; then + COMPOSITOR="cagebreak" + GREETD_COMMAND="cagebreak -e -c /etc/greetd/cagebreak-greeter-config" +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 (niri, cagebreak, sway, hyprland)" + echo "Please install niri or cagebreak 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 cagebreak: SYSC_COMPOSITOR=cagebreak sudo ./install.sh" + echo " See https://nomadcxx.github.io/sysc-greet/compositors/cagebreak/" + 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-cagebreak-greeter.sh b/scripts/verify-cagebreak-greeter.sh new file mode 100755 index 0000000..1c7684b --- /dev/null +++ b/scripts/verify-cagebreak-greeter.sh @@ -0,0 +1,58 @@ +#!/usr/bin/env bash +# verify-cagebreak-greeter.sh — smoke checks for the cagebreak greeter integration +set -euo pipefail + +ROOT="$(cd "$(dirname "$0")/.." && pwd)" +CONFIG="${ROOT}/config/cagebreak-greeter-config" +FAIL=0 + +pass() { echo "✓ $*"; } +fail() { echo "✗ $*"; FAIL=1; } + +echo "=== Cagebreak greeter verification ===" + +if [[ -f "${CONFIG}" ]]; then + pass "greeter config exists: ${CONFIG}" +else + fail "greeter config missing: ${CONFIG}" +fi + +if command -v cagebreak >/dev/null 2>&1; then + pass "cagebreak binary found: $(command -v cagebreak) ($(cagebreak -v 2>&1))" + rc=0 + WLR_BACKENDS=headless WLR_LIBINPUT_NO_DEVICES=1 timeout 5 cagebreak -e -c "${CONFIG}" >/dev/null 2>&1 || rc=$? + if [[ "${rc}" -eq 0 || "${rc}" -eq 124 ]]; then + pass "config parses under headless cagebreak" + else + fail "cagebreak rejected the greeter config (exit ${rc})" + fi +else + echo "⚠ cagebreak not installed — skip runtime test (Arch: paru -S cagebreak)" +fi + +if command -v socat >/dev/null 2>&1; then + pass "socat available (needed for quit-via-socket)" +else + fail "socat missing — greeter cannot quit cagebreak after login" +fi + +if grep -q '"cagebreak"' "${ROOT}/cmd/installer/main.go"; then + pass "installer references cagebreak" +else + fail "installer does not reference cagebreak" +fi + +if [[ -f "${ROOT}/docs-src/compositors/cagebreak.md" ]]; then + pass "cagebreak docs present" +else + fail "docs-src/compositors/cagebreak.md missing" +fi + +echo +if [[ "${FAIL}" -eq 0 ]]; then + echo "All static checks passed." + echo "Manual: SYSC_COMPOSITOR=cagebreak sudo ./install.sh → systemctl restart greetd → test login" +else + echo "Some checks failed." + exit 1 +fi