From 88593947b03ba4ae5d9158ba2a947db261b8088a Mon Sep 17 00:00:00 2001 From: Nomadcxx Date: Thu, 18 Jun 2026 01:14:19 +1000 Subject: [PATCH 1/9] feat: add Cage greeter path and Hyprland deprecation warnings Add cage as the recommended greetd backend (lite mode: Kitty-only, no gSlapper wallpapers at startup). Hyprland greeter support remains functional but is marked deprecated with ~3 month removal timeline. Includes design spec, implementation plan, installer/Nix/postinstall integration, docs, launcher script, and verify script. Addresses #69. --- README.md | 17 +- cmd/installer/main.go | 51 +++- docs-src/compositors/cage.md | 157 ++++++++++++ docs-src/compositors/hyprland.md | 7 + docs-src/getting-started/installation.md | 18 +- .../plans/2026-06-18-cage-compositor-plan.md | 159 ++++++++++++ .../2026-06-18-cage-compositor-design.md | 242 ++++++++++++++++++ flake.nix | 22 +- mkdocs.yml | 1 + nfpm.yaml | 8 +- scripts/cage-greeter-session.sh | 20 ++ scripts/postinstall.sh | 29 ++- scripts/verify-cage-greeter.sh | 60 +++++ 13 files changed, 757 insertions(+), 34 deletions(-) create mode 100644 docs-src/compositors/cage.md create mode 100644 docs/superpowers/plans/2026-06-18-cage-compositor-plan.md create mode 100644 docs/superpowers/specs/2026-06-18-cage-compositor-design.md create mode 100755 scripts/cage-greeter-session.sh create mode 100755 scripts/verify-cage-greeter.sh 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..7ba726a 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", "hyprland", "sway", or "cage" 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,7 +373,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 +381,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 +564,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 +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 is being phased out — cage is the recommended path")) + 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 { @@ -953,6 +959,7 @@ func installCompositor(m *model) error { "niri": {"niri"}, "hyprland": {"Hyprland", "hyprland"}, "sway": {"sway"}, + "cage": {"cage"}, } // Check if compositor already installed @@ -996,6 +1003,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 +1016,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 +1029,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": @@ -1612,12 +1625,31 @@ 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 + } + if err := os.WriteFile(configPath, []byte(compositorConfig), configMode); err != nil { return fmt.Errorf("compositor config write failed") } @@ -1702,6 +1734,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/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..1d11ab6 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 (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). + ## 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 +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 (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/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/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..d942e3b 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -60,6 +60,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 From 25e043cdc96f573c4c3c66b9c9fc936ef988e7da Mon Sep 17 00:00:00 2001 From: Nomadcxx Date: Mon, 6 Jul 2026 21:22:49 +1000 Subject: [PATCH 2/9] Add cagebreak compositor design spec --- .../2026-07-06-cagebreak-compositor-design.md | 201 ++++++++++++++++++ 1 file changed, 201 insertions(+) create mode 100644 docs/superpowers/specs/2026-07-06-cagebreak-compositor-design.md 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..7dcc568 --- /dev/null +++ b/docs/superpowers/specs/2026-07-06-cagebreak-compositor-design.md @@ -0,0 +1,201 @@ +# Cagebreak Compositor — Design Spec + +> **Status:** Approved design (pending spike gate) +> **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 + +``` +cagebreak (recommended) — Minimal tiling kiosk; full gSlapper wallpapers +niri — Tiling compositor with scrollable workspaces +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 From 59979afd992052d0c032db5e91a4f985507ce9dc Mon Sep 17 00:00:00 2001 From: Nomadcxx Date: Mon, 6 Jul 2026 21:30:12 +1000 Subject: [PATCH 3/9] Add cagebreak implementation plan --- .../2026-07-06-cagebreak-compositor-plan.md | 813 ++++++++++++++++++ 1 file changed, 813 insertions(+) create mode 100644 docs/superpowers/plans/2026-07-06-cagebreak-compositor-plan.md 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. From 31437a8b4eff0a772fb76daf9d269b8b647d1865 Mon Sep 17 00:00:00 2001 From: Nomadcxx Date: Mon, 6 Jul 2026 21:43:10 +1000 Subject: [PATCH 4/9] Add cagebreak greeter config, drop cage launcher --- config/cagebreak-greeter-config | 14 ++++++++++++++ scripts/cage-greeter-session.sh | 20 -------------------- 2 files changed, 14 insertions(+), 20 deletions(-) create mode 100644 config/cagebreak-greeter-config delete mode 100755 scripts/cage-greeter-session.sh 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/scripts/cage-greeter-session.sh b/scripts/cage-greeter-session.sh deleted file mode 100755 index 0b9bc08..0000000 --- a/scripts/cage-greeter-session.sh +++ /dev/null @@ -1,20 +0,0 @@ -#!/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}" From a03acd33af512453f809ddc878635b3beae44384 Mon Sep 17 00:00:00 2001 From: Nomadcxx Date: Mon, 6 Jul 2026 21:45:37 +1000 Subject: [PATCH 5/9] Switch installer greeter backend from cage to cagebreak --- cmd/installer/main.go | 96 ++++++++++++++++++++++++++----------------- 1 file changed, 59 insertions(+), 37 deletions(-) diff --git a/cmd/installer/main.go b/cmd/installer/main.go index 7ba726a..413609a 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", "sway", or "cage" + selectedCompositor string // "cagebreak", "niri", "sway", or "hyprland" compositorIndex int // Current selection in compositor menu debugMode bool // Show verbose output logFile *os.File // Installer log file @@ -373,15 +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{"cage", "niri", "sway", "hyprland"} + compositors := []string{"cagebreak", "niri", "sway", "hyprland"} m.selectedCompositor = compositors[m.compositorIndex] // Validate compositor is installed compositorBinaries := map[string][]string{ - "niri": {"niri"}, - "hyprland": {"Hyprland", "hyprland"}, - "sway": {"sway"}, - "cage": {"cage"}, + "niri": {"niri"}, + "hyprland": {"Hyprland", "hyprland"}, + "sway": {"sway"}, + "cagebreak": {"cagebreak"}, } compositorInstalled := false @@ -564,10 +564,10 @@ func (m model) renderCompositorSelect() string { name string desc string }{ - {"cage", "Recommended — minimal kiosk; fast boot; TUI backgrounds only"}, + {"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 cage"}, + {"hyprland", "Deprecated — greeter support ending in ~3 months; migrate to cagebreak"}, } for i, comp := range compositors { @@ -579,10 +579,10 @@ func (m model) renderCompositorSelect() string { b.WriteString(" " + comp.desc + "\n\n") } - b.WriteString(lipgloss.NewStyle().Foreground(FgMuted).Render("Hyprland greeter support is being phased out — cage is the recommended path")) + b.WriteString(lipgloss.NewStyle().Foreground(FgMuted).Render("Hyprland greeter support is being phased out — cagebreak is the recommended path")) 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.")) + 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 @@ -953,13 +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"}, - "cage": {"cage"}, + "niri": {"niri"}, + "hyprland": {"Hyprland", "hyprland"}, + "sway": {"sway"}, + "cagebreak": {"cagebreak"}, } // Check if compositor already installed @@ -1003,8 +1025,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 "cagebreak": + return installCagebreakArch() } case "apt": @@ -1016,8 +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 "cage": - return fmt.Errorf("cage not in standard apt repos — build from https://github.com/cage-kiosk/cage") + case "cagebreak": + return fmt.Errorf("cagebreak not in standard apt repos — build from https://github.com/project-repo/cagebreak") } case "dnf": @@ -1029,8 +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 "cage": - return fmt.Errorf("cage not in standard dnf repos — build from https://github.com/cage-kiosk/cage") + case "cagebreak": + return fmt.Errorf("cagebreak not in standard dnf repos — build from https://github.com/project-repo/cagebreak") } case "yum": @@ -1625,31 +1647,31 @@ 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 + 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 -set -eu +# gSlapper wallpaper daemon (wlr-layer-shell client, same as niri/sway setup) +exec HOME=/var/lib/greeter /usr/local/bin/sysc-greet --wallpaper-daemon -export XDG_CACHE_HOME=/tmp/greeter-cache -export HOME=/var/lib/greeter +# 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" -exec kitty --start-as=fullscreen --config=/etc/greetd/kitty.conf /usr/local/bin/sysc-greet +# No bind/definekey lines on purpose: no compositor keybindings reachable from the greeter ` - configPath = "/etc/greetd/cage-greeter-session.sh" - greetdCommand = "cage -s -m extend -- /etc/greetd/cage-greeter-session.sh" + configPath = "/etc/greetd/cagebreak-greeter-config" + greetdCommand = "cagebreak -e -c /etc/greetd/cagebreak-greeter-config" default: return fmt.Errorf("unknown compositor: %s", m.selectedCompositor) } - // Write compositor config (or cage launcher script) - configMode := os.FileMode(0644) - if m.selectedCompositor == "cage" { - configMode = 0755 - } - if err := os.WriteFile(configPath, []byte(compositorConfig), configMode); err != nil { + // Write compositor config + if err := os.WriteFile(configPath, []byte(compositorConfig), 0644); err != nil { return fmt.Errorf("compositor config write failed") } @@ -1734,7 +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/cage-greeter-session.sh", + "/etc/greetd/cagebreak-greeter-config", } for _, path := range paths { From 9b9ec5b11cfd91b4c01696e96e61b62dafc9d7fe Mon Sep 17 00:00:00 2001 From: Nomadcxx Date: Mon, 6 Jul 2026 21:47:38 +1000 Subject: [PATCH 6/9] Use cagebreak in Nix module --- flake.nix | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/flake.nix b/flake.nix index bafb3b2..3704966 100644 --- a/flake.nix +++ b/flake.nix @@ -87,12 +87,12 @@ --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 \ + # 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 " - chmod +x $out/etc/greetd/cage-greeter-session.sh + --replace 'kitty ' "${pkgs.kitty}/bin/kitty " \ + --replace 'socat ' "${pkgs.socat}/bin/socat " # Install polkit rule mkdir -p $out/etc/polkit-1/rules.d @@ -148,7 +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 if cfg.compositor == "cagebreak" then cfg.cagebreakPackage else cfg.swayPackage; compositorExecutable = pkg: executable: if pkg == null then executable else "${pkg}/bin/${executable}"; @@ -157,8 +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 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 @@ -167,9 +167,9 @@ EOF enable = mkEnableOption "sysc-greet greeter for greetd"; compositor = mkOption { - type = types.enum [ "niri" "hyprland" "sway" "cage" ]; + type = types.enum [ "niri" "hyprland" "sway" "cagebreak" ]; default = "niri"; - description = "Wayland compositor to use with sysc-greet. Use cage for a minimal kiosk greeter (experimental, no boot wallpapers)."; + description = "Wayland compositor to use with sysc-greet. Use cagebreak for a minimal tiling kiosk greeter with full gSlapper wallpaper support (recommended)."; }; compositorCommand = mkOption { @@ -208,11 +208,11 @@ EOF description = "sway package to use and install for the greeter compositor. When null, the sway command is resolved from PATH."; }; - cagePackage = mkOption { + cagebreakPackage = 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."; + description = "cagebreak package to use and install for the greeter compositor. When null, the cagebreak command is resolved from PATH."; }; settings = mkOption { @@ -269,7 +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"; + "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"; }; From f1ebf83fbd962276bd18011866ec7ece0b8a32d4 Mon Sep 17 00:00:00 2001 From: Nomadcxx Date: Mon, 6 Jul 2026 21:48:28 +1000 Subject: [PATCH 7/9] Package cagebreak greeter config and prefer it in postinstall --- nfpm.yaml | 10 +++++----- scripts/postinstall.sh | 14 +++++++------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/nfpm.yaml b/nfpm.yaml index 6daca7a..7ce1cff 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, Sway, and experimental Cage compositors. + Supports Cagebreak, Niri, and Sway compositors (Hyprland deprecated). vendor: Nomadcxx homepage: https://github.com/Nomadcxx/sysc-greet license: GPL-3.0 @@ -58,11 +58,11 @@ contents: file_info: mode: 0644 - # Cage lite launcher (experimental) - - src: ./scripts/cage-greeter-session.sh - dst: /etc/greetd/cage-greeter-session.sh + # Cagebreak greeter config + - src: ./config/cagebreak-greeter-config + dst: /etc/greetd/cagebreak-greeter-config file_info: - mode: 0755 + mode: 0644 # Polkit rule for greeter shutdown/reboot - src: ./config/85-greeter.rules diff --git a/scripts/postinstall.sh b/scripts/postinstall.sh index 7ef69f5..a420f63 100755 --- a/scripts/postinstall.sh +++ b/scripts/postinstall.sh @@ -24,9 +24,9 @@ chmod 755 /var/lib/greeter echo "==> Detecting greeter backend..." COMPOSITOR="" -if command -v cage &>/dev/null; then - COMPOSITOR="cage" - GREETD_COMMAND="cage -s -m extend -- /etc/greetd/cage-greeter-session.sh" +if command -v cagebreak &>/dev/null; then + COMPOSITOR="cagebreak" + GREETD_COMMAND="cagebreak -e -c /etc/greetd/cagebreak-greeter-config" elif command -v niri &>/dev/null; then COMPOSITOR="niri" GREETD_COMMAND="niri -c /etc/greetd/niri-greeter-config.kdl" @@ -40,16 +40,16 @@ elif command -v Hyprland &>/dev/null || command -v hyprland &>/dev/null; then fi if [ -z "$COMPOSITOR" ]; then - echo "WARNING: No supported greeter backend detected (cage, niri, sway, hyprland)" - echo "Please install cage (recommended) and manually configure /etc/greetd/config.toml" + echo "WARNING: No supported greeter backend detected (cagebreak, niri, sway, hyprland)" + echo "Please install cagebreak (recommended) and manually configure /etc/greetd/config.toml" else 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 " Migrate to cagebreak: SYSC_COMPOSITOR=cagebreak sudo ./install.sh" + echo " See https://nomadcxx.github.io/sysc-greet/compositors/cagebreak/" echo "" fi From 4a12497b99eb583ee4d428c396cfc8aa38d15162 Mon Sep 17 00:00:00 2001 From: Nomadcxx Date: Mon, 6 Jul 2026 22:27:35 +1000 Subject: [PATCH 8/9] Add cagebreak verify script --- scripts/verify-cage-greeter.sh | 60 ----------------------------- scripts/verify-cagebreak-greeter.sh | 58 ++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+), 60 deletions(-) delete mode 100755 scripts/verify-cage-greeter.sh create mode 100755 scripts/verify-cagebreak-greeter.sh diff --git a/scripts/verify-cage-greeter.sh b/scripts/verify-cage-greeter.sh deleted file mode 100755 index f2c9e8d..0000000 --- a/scripts/verify-cage-greeter.sh +++ /dev/null @@ -1,60 +0,0 @@ -#!/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-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 From 04314edacf1138fc38578a65a67930975b518ba6 Mon Sep 17 00:00:00 2001 From: Nomadcxx Date: Mon, 6 Jul 2026 22:35:26 +1000 Subject: [PATCH 9/9] Document cagebreak as hyprland replacement, keep niri default --- README.md | 15 +- cmd/installer/main.go | 12 +- docs-src/compositors/cage.md | 157 ------------------ docs-src/compositors/cagebreak.md | 79 +++++++++ docs-src/compositors/hyprland.md | 8 +- docs-src/getting-started/installation.md | 16 +- .../plans/2026-06-18-cage-compositor-plan.md | 2 + .../2026-06-18-cage-compositor-design.md | 2 + .../2026-07-06-cagebreak-compositor-design.md | 14 +- flake.nix | 2 +- mkdocs.yml | 4 +- nfpm.yaml | 2 +- scripts/postinstall.sh | 12 +- 13 files changed, 128 insertions(+), 197 deletions(-) delete mode 100644 docs-src/compositors/cage.md create mode 100644 docs-src/compositors/cagebreak.md diff --git a/README.md b/README.md index e2cd63d..2f8ed09 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ curl -fsSL https://raw.githubusercontent.com/Nomadcxx/sysc-greet/master/install. 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**. +> Hyprland greeter support ends in ~3 months. New installs should choose niri (default) or cagebreak, its replacement. ### Build from Source @@ -39,17 +39,18 @@ The installer walks you through compositor selection and configuration. Three AUR packages available depending on which greeter backend you're using: ```bash -# Recommended (cage — install script, or: pacman -S cage && SYSC_COMPOSITOR=cage ./install.sh) -# niri (full wallpapers) +# niri (default) yay -S sysc-greet -# Hyprland variant (deprecated — migrate to cage) +# 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: @@ -82,7 +83,7 @@ If you're on NixOS, add sysc-greet to your flake: { services.sysc-greet = { enable = true; - compositor = "cage"; # recommended — or "niri", "sway", "hyprland" (deprecated) + compositor = "niri"; # or "cagebreak", "sway", "hyprland" (deprecated) }; # Optional: Set initial session for auto-login @@ -93,8 +94,8 @@ If you're on NixOS, add sysc-greet to your flake: } ``` -By default, the NixOS module does not install `cage`, `niri`, `hyprland`, or `sway`. -Install your chosen backend yourself, or set `cagePackage`, `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 413609a..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 // "cagebreak", "niri", "sway", or "hyprland" + 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 @@ -373,7 +373,7 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { } } else if m.step == stepCompositorSelect { // Set compositor based on selection - compositors := []string{"cagebreak", "niri", "sway", "hyprland"} + compositors := []string{"niri", "cagebreak", "sway", "hyprland"} m.selectedCompositor = compositors[m.compositorIndex] // Validate compositor is installed @@ -564,9 +564,9 @@ func (m model) renderCompositorSelect() string { 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"}, + {"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"}, } @@ -579,7 +579,7 @@ func (m model) renderCompositorSelect() string { b.WriteString(" " + comp.desc + "\n\n") } - b.WriteString(lipgloss.NewStyle().Foreground(FgMuted).Render("Hyprland greeter support is being phased out — cagebreak is the recommended path")) + 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.")) diff --git a/docs-src/compositors/cage.md b/docs-src/compositors/cage.md deleted file mode 100644 index ef83fae..0000000 --- a/docs-src/compositors/cage.md +++ /dev/null @@ -1,157 +0,0 @@ -# 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/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 eb18e28..db68537 100644 --- a/docs-src/compositors/hyprland.md +++ b/docs-src/compositors/hyprland.md @@ -1,11 +1,11 @@ # 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**. +!!! warning "Deprecated" + sysc-greet ends Hyprland greeter support in ~3 months. It remains functional until then. - **Migrate to [Cage](cage.md)** (recommended — faster, simpler) or [niri](niri.md) (full wallpaper support). + Migrate to [cagebreak](cagebreak.md), the Hyprland replacement, or [niri](niri.md), the default. - Your daily Hyprland desktop session is unaffected — only the boot greeter changes. + Your Hyprland desktop session is unaffected. Only the boot greeter changes. Configuration for running sysc-greet with the Hyprland Wayland compositor. diff --git a/docs-src/getting-started/installation.md b/docs-src/getting-started/installation.md index 1d11ab6..d608d3b 100644 --- a/docs-src/getting-started/installation.md +++ b/docs-src/getting-started/installation.md @@ -11,11 +11,11 @@ curl -fsSL https://raw.githubusercontent.com/Nomadcxx/sysc-greet/master/install. ``` The interactive installer will prompt you to: -1. Choose your greeter backend (cage recommended, niri, sway, or hyprland deprecated) +1. Choose your greeter backend (niri default, cagebreak, 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). +> Hyprland greeter support ends in ~3 months. Migrate to [cagebreak](../compositors/cagebreak.md) or [niri](../compositors/niri.md). ## Manual Build @@ -23,7 +23,7 @@ The interactive installer will prompt you to: - Go 1.25+ - greetd -- Wayland backend: cage (recommended), niri, sway, or hyprland (deprecated) +- Wayland backend: niri (default), cagebreak, sway, or hyprland (deprecated) - kitty (terminal emulator) - gSlapper (wallpaper daemon) - swww (legacy wallpaper daemon, optional fallback) @@ -50,18 +50,18 @@ go run ./cmd/installer/ sysc-greet provides AUR packages for different greeter backends: ```bash -# niri (full wallpapers) +# niri (default) yay -S sysc-greet -# Hyprland variant (deprecated — use cage instead) +# Hyprland variant (deprecated, replaced by cagebreak) 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 +# 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 index 2899e4f..f911584 100644 --- a/docs/superpowers/plans/2026-06-18-cage-compositor-plan.md +++ b/docs/superpowers/plans/2026-06-18-cage-compositor-plan.md @@ -1,5 +1,7 @@ # 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 diff --git a/docs/superpowers/specs/2026-06-18-cage-compositor-design.md b/docs/superpowers/specs/2026-06-18-cage-compositor-design.md index e2f0237..b80de65 100644 --- a/docs/superpowers/specs/2026-06-18-cage-compositor-design.md +++ b/docs/superpowers/specs/2026-06-18-cage-compositor-design.md @@ -1,5 +1,7 @@ # 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` diff --git a/docs/superpowers/specs/2026-07-06-cagebreak-compositor-design.md b/docs/superpowers/specs/2026-07-06-cagebreak-compositor-design.md index 7dcc568..849427f 100644 --- a/docs/superpowers/specs/2026-07-06-cagebreak-compositor-design.md +++ b/docs/superpowers/specs/2026-07-06-cagebreak-compositor-design.md @@ -1,6 +1,10 @@ # Cagebreak Compositor — Design Spec -> **Status:** Approved design (pending spike gate) +> **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) @@ -142,10 +146,10 @@ user = "greeter" ### Installer UX ``` -cagebreak (recommended) — Minimal tiling kiosk; full gSlapper wallpapers -niri — Tiling compositor with scrollable workspaces -sway — Stable i3-compatible tiling compositor -hyprland (deprecated) — Greeter support ending in ~3 months; migrate to cagebreak +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 diff --git a/flake.nix b/flake.nix index 3704966..73d1aef 100644 --- a/flake.nix +++ b/flake.nix @@ -169,7 +169,7 @@ EOF compositor = mkOption { type = types.enum [ "niri" "hyprland" "sway" "cagebreak" ]; default = "niri"; - description = "Wayland compositor to use with sysc-greet. Use cagebreak for a minimal tiling kiosk greeter with full gSlapper wallpaper support (recommended)."; + description = "Wayland compositor to use with sysc-greet. cagebreak replaces hyprland for the greeter session."; }; compositorCommand = mkOption { diff --git a/mkdocs.yml b/mkdocs.yml index d942e3b..9cce644 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -58,9 +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 - - Cage (experimental): compositors/cage.md + - Hyprland (deprecated): compositors/hyprland.md - Development: - Architecture: development/architecture.md - Building: development/building.md diff --git a/nfpm.yaml b/nfpm.yaml index 7ce1cff..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 Cagebreak, Niri, and Sway compositors (Hyprland deprecated). + Supports Niri, Cagebreak, and Sway compositors (Hyprland deprecated). vendor: Nomadcxx homepage: https://github.com/Nomadcxx/sysc-greet license: GPL-3.0 diff --git a/scripts/postinstall.sh b/scripts/postinstall.sh index a420f63..ccad5e0 100755 --- a/scripts/postinstall.sh +++ b/scripts/postinstall.sh @@ -24,12 +24,12 @@ chmod 755 /var/lib/greeter echo "==> Detecting greeter backend..." COMPOSITOR="" -if command -v cagebreak &>/dev/null; then - COMPOSITOR="cagebreak" - GREETD_COMMAND="cagebreak -e -c /etc/greetd/cagebreak-greeter-config" -elif command -v niri &>/dev/null; then +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" @@ -40,8 +40,8 @@ elif command -v Hyprland &>/dev/null || command -v hyprland &>/dev/null; then fi if [ -z "$COMPOSITOR" ]; then - echo "WARNING: No supported greeter backend detected (cagebreak, niri, sway, hyprland)" - echo "Please install cagebreak (recommended) 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 backend: $COMPOSITOR"