refactor: split the TUI and refresh project docs

Move login state, authentication, discovery, input, rendering, and tests into focused TUI modules. Add detailed CLI error reports with recovery commands, align the build and host-check documentation, and keep the legacy Go tree outside normal builds.

Checks: make check; make integration-test; go test ./... (legacy/go).
This commit is contained in:
2026-10-05 21:37:15 +05:30
parent de8843471b
commit f2a885d621
33 changed files with 2866 additions and 2173 deletions

View File

@@ -2,127 +2,73 @@
## Unreleased
- Preview mode now loads `$XDG_CONFIG_HOME/latchd/config.toml` or
`~/.config/latchd/config.toml` when no `--config` path is set.
- Added `make install-user` to create a user configuration without replacing
an existing file.
- Added Docker PTY screenshots and a short UI guide.
- TUI module layout — `crates/latchd-tui/src/lib.rs` held state, input, authentication, discovery, rendering, and tests in one file; split those responsibilities into `app.rs`, `app/auth.rs`, `app/discovery.rs`, `app/input.rs`, `app/render.rs`, and `app/tests.rs`, which makes each change surface easier to locate
- error reports — `crates/latchd-core/src/error.rs::Error::user_report` now adds a detail line and recovery commands; `rust/main.rs` prints the report for CLI failures instead of one short error line
- TUI checks — keep the render and state tests beside the new modules; `cargo test --locked --workspace --all-targets` covers the split without changing the login flow
- service configuration — document `/etc/latchd/config.toml` as the service file and keep preview, build, and end-to-end instructions aligned with the Makefile
- Fixed Wayland compositors launched through user services, including Niri, by
providing the selected session type, desktop, seat, and VT to PAM before
`pam_open_session`.
- Session children now receive allowlisted runtime values produced by PAM,
including the real logind session ID, runtime directory, and user D-Bus
address. Latchd no longer fabricates `XDG_SESSION_ID=latchd`.
- Extended the disposable PAM fixture to verify both sides of the handoff:
selected session metadata entering PAM and PAM-generated values reaching the
session child.
## 0.5.0 — password and session handoff
## 0.5.0 — PAM password policy flow
- password-change state — before this change, an expired password had no separate screen state; `crates/latchd-tui/src/app/auth.rs` now switches to a new-password prompt for `PAM_NEW_AUTHTOK_REQD` and `PAM_AUTHTOK_EXPIRED`, then starts authentication again after the change
- password conversation — `crates/latchd-auth/src/lib.rs::converse` handles the current-password and new-password prompts in order; tests cover both prompt sequences in `crates/latchd-auth/src/lib.rs`
- audit events — `crates/latchd-core/src/audit.rs` records authentication, password-change, and session results with stable `PamReason` values, so the service log can group failures without copying module text into the event fields
- session preferences — `crates/latchd-core/src/preferences.rs` stores one bounded entry per account, writes through a private temporary file, and replaces the target atomically; invalid ownership or file mode stops the save
- Wayland handoff — `crates/latchd-core/src/session.rs::build_spec` carries the selected session type, desktop, seat, VT, runtime directory, session ID, and D-Bus address into `latchd-auth` before the session opens
- PAM fixture — `crates/latchd-auth/tests/pam_fixture.rs` covers denied authentication, password change, session metadata, and child environment values; `scripts/test-pam-fixture.sh` installs the temporary service files for the test run
- test coverage — `crates/latchd-core`, `crates/latchd-auth`, and `crates/latchd-tui` now test configuration, account state, session parsing, authentication conversations, preferences, rendering, and background animation
- Added PAM password-change handling for accounts that return
`PAM_NEW_AUTHTOK_REQD` or `PAM_AUTHTOK_EXPIRED` during login.
- Added an explicit TUI new-password state. PAM still enforces password
history, strength, and token policy; the login screen never implements its
own password rules.
- Added zeroizing old/new password buffers and conversation tests for the
current-password/new-password prompt sequence.
- Added redacted structured audit events for authentication, password changes,
and session handoff. Raw PAM messages and password material are excluded.
- Added TUI, core, and preference tests; the workspace now contains 37
Rust unit tests.
- Added bounded, atomic last-session preferences per account with owner-only
file permissions and a safe fallback when the state file is unavailable.
- Added disposable PAM deny-policy fault-injection tests. Docker runs them as
root; ordinary host runs skip them without touching the host PAM policy.
## 0.4.0 — account and session workflow
## 0.4.0 — Advanced login workflow
- account status — `crates/latchd-core/src/accounts.rs::inspect_account` reads lock markers, password age, account expiry, inactivity, login shell, and shadow availability; the TUI shows the matching state beside the selected account
- faillock parsing — `crates/latchd-core/src/accounts.rs::faillock_status_from_output` accepts legacy counts, username prefixes, and timestamp rows; malformed output or a timeout removes the extra count from the screen
- account switching — `crates/latchd-tui/src/app/input.rs::select_account_index` changes the account, clears the password, resets authentication state, and reapplies the saved session for the new account
- session refresh — `crates/latchd-tui/src/app/discovery.rs::start_refresh` moves account and session discovery to a worker; the screen keeps accepting input while the lists refresh
- filtering — `crates/latchd-tui/src/app/input.rs::matches_filter` adds smart-case filtering for account and session names; uppercase query characters select case-sensitive matching
- terminal display — `crates/latchd-tui/src/app/render.rs` adds high-contrast styles, footer hints, account status text, notifications, and a minimum terminal size of 60 columns by 16 rows
- authentication backoff — `crates/latchd-tui/src/app/auth.rs::auth_backoff` delays repeated failures from one to sixteen seconds and resets the delay when the account changes
- host checks — `scripts/test-cli.sh` and `scripts/test-system-e2e.sh` cover CLI preflight and the available PAM, faillock, VT, X11, Wayland, and systemd-logind capabilities
- VM gate — `docs/e2e-testing.md` lists the host requirements for the strict check and the commands for a disposable VM
- version — set the workspace package version to `0.4.0` in `Cargo.toml`
- Added account policy inspection for locked passwords, password expiry,
account expiry, inactivity, and missing shadow data without exposing hashes.
- Added active UTMPX session discovery and systemd-logind capability detection
with an explicit UTMPX fallback.
- Improved faillock parsing for legacy, username-prefixed, and timestamped
helper output. PAM remains authoritative for every login decision.
- Added smart-case filtering for users and sessions, non-blocking Ctrl-R discovery
refresh, contextual footer hints, high-contrast mode, and policy notices in
the TUI.
- Added bounded local authentication backoff after repeated failures and reset
it when switching accounts.
- Added CLI integration tests and host capability probes. Docker now runs the
release binary through the safe CLI and capability suites.
- Added documented strict VM validation for PAM policy, VT, X11, Wayland, and
systemd-logind; missing capabilities are reported as skips in ordinary CI.
- Bumped the workspace release version to 0.4.0.
## 0.3.0 — Rust workspace release
## 0.3.0 — Rust workspace release preparation
- Rust service — `rust/main.rs` became the supported login-manager entry point; the Go prototype stayed under `legacy/go/` and is excluded from normal builds
- workspace crates — split authentication, configuration, accounts, sessions, and the TUI into `latchd-core`, `latchd-auth`, and `latchd-tui`
- session commands — `crates/latchd-core/src/session.rs::parse_argv` stores a program and argument list instead of sending desktop entries through a shell, which rejects shell operators and desktop field codes
- terminal effects — `crates/latchd-tui/src/background.rs` adds wallpaper loading, a bounded cellular animation, reduced motion, and the background toggle
- release pipeline — `.gitlab-ci.yml` and `Makefile` add format, test, Clippy, rustdoc, Docker, version, package, checksum, and release targets
- deployment gate — `scripts/test-system-e2e.sh` and `docs/e2e-testing.md` separate container checks from live VT, display, and logind checks
- Rust is the supported login manager; the Go/cgo prototype is quarantined at
`legacy/go/` and excluded from normal builds and release archives.
- Added workspace crates for PAM authentication, session/config/account
management, and the TUI, including account switching and advisory faillock.
- Added optional terminal-cell wallpapers, cellular automata effects, and
accessible motion/background controls.
- Added GitLab CI verification, version-checked tag releases, optimized
single-version Cargo builds, deterministic installation archives, SHA-256
checksums, and embedded build metadata.
- Live VT, logind, PAM-policy, X11, and Wayland validation remains a deployment
requirement on an isolated VM or target host.
## Legacy Go history
## Legacy Go history (not the Rust release)
The entries below describe the first Go prototype. The Rust workspace does not build or ship this code.
## 0.1.0 — Unreleased
## 0.1.0 — first public prototype
first public release
- login screen — `legacy/go/login/login.go` added username and password fields, a session switcher, status messages, and preview mode
- session types — `legacy/go/session/scan.go` added X11, Wayland, script, and TTY entries
- authentication — `legacy/go/auth/pam.go` added cgo login support with one OS thread per PAM transaction
- faillock — `legacy/go/auth/faillock.go` added lockout counts, remaining attempts, and the administrator reset key
- terminal effects — `legacy/go/tui/bg/automata.go` added the cellular background, switcher motion, and failed-login shake
- terminal details — `legacy/go/login/login.go` added the caps-lock indicator, power keys, clock, and preview mode
- configuration — `legacy/go/config/config.go` added configuration parsing, variable replacement, and configurable power programs
- accounting — `legacy/go/auth/utmpx.go` added UTMPX records for `who`, `w`, and `last` on glibc
- X11 startup — `legacy/go/session/x11.go` added Xauthority cookies, the `SIGUSR1` readiness signal, and a server timeout
- privilege drop — `legacy/go/session/exec.go` started the selected child with the account UID, GID, and supplementary groups
- logs — `legacy/go/session/exec.go` capped the client log at 64 MiB
### What's In
### Fixes
- tui login screen with username/password fields, session switcher, and status messages
- supports x11, wayland, and tty sessions
- pam authentication via cgo (thread-locked for safety)
- faillock integration — detects lockouts, shows remaining attempts, admin bypass via ctrl+u
- conway's game of life background rendered at 30 fps with unicode half-block characters
- spring animation on switcher transitions
- shake animation on failed login
- caps lock indicator via ioctl
- configurable power controls (shutdown/reboot on f1/f2 by default)
- toml configuration with `$VARIABLE` substitution
- preview mode (`--preview`) to test the login screen without leaving your session
- utmpx user accounting (glibc only)
- x11 session setup — xauthority cookies, sigusr1 handshake, server timeout
- privilege dropping for child processes
- log piping with 64 mb cap
### Recent Fixes
- **DesktopNames parsing** — `.desktop` files now parse the `DesktopNames` field. the first semicolon-delimited entry becomes `XDG_SESSION_DESKTOP`, and the full list with `:` separators becomes `XDG_CURRENT_DESKTOP`. fixes GNOME/KDE sessions not getting proper environment variables
- **utmpx accounting wired** — `AddUtmpx` and `Close` are now called from `spawnSession` so user logins appear in `who`/`w`/`last`
- **fixed hardcoded shell** — `Credentials.Shell` now reads from `/etc/passwd` instead of always returning `/bin/sh`. removes duplicate `userShell` logic in exec.go
- **automata background wired** — `Automata.Frame()` is now rendered in `View()` as the base layer with the login form card overlaid using ANSI cursor positioning. the form has a dark card background for readability
- **clock widget** — added a datetime display (`Mon Jan 02 15:04:05`) to the top bar, updated every tick
- **session specifier label** — the environment switcher now shows the session type (`x11`, `wayland`) above the session name, like ly's specifier
- **form card background** — login content is rendered inside a padded card with a dark background so it reads cleanly over the animated automata
- **utmpx time field portability** — replaced hardcoded `C.__uint32_t` / `C.__int32_t` casts with a C helper function (`auth/utmpx_time.h`) that lets the compiler handle type conversion. fixes build failure on systems where `ut_tv` uses `struct timeval` instead of the 32/64 compat struct
- **cgo pointer safety for pam** — replaced `unsafe.Pointer(&h)` (Go stack pointer passed to C) with `storeHandle`/`loadHandle` helpers that keep the `cgo.Handle` in C-heap memory. fixes `cgo argument has Go pointer to unpinned Go pointer` panic on Go 1.22+
- **pam conversation callback** — fixed `unsafe.Slice` using `&r` (pointer-to-pointer) as the backing array instead of `r` (the calloc'd response array), which wrote password bytes into random stack memory. also now only responds to `PAM_PROMPT_ECHO_OFF`/`PAM_PROMPT_ECHO_ON` messages — info and error messages get nil. fixes `pam_unix: auth could not identify password`
- **cgo handle lifecycle** — moved `cgo.Handle` and its C-heap backing from `Validate` (where they were `defer`-freed on return) into the `Credentials` struct so they stay alive through `OpenSession` and until `CloseSession`. fixes `misuse of an invalid Handle` panic
- **session spawning** — replaced the broken `ForkExec` (which relaunched the same binary with made-up flags) with a direct `session.Spawn` call that drops privileges and runs the desktop in-process. fixes `flag provided but not defined: -user`
- **renamed extra/ files** — `lemurs.pam` → `latchd.pam`, `lemurs.service` → `latchd.service`
- **readme credits** — added a credits section acknowledging lemurs for the `extra/` directory structure and session scanning approach
- **makefile `enable` and `update` targets** — `make enable` stops any running DM, disables the aliased display-manager, and enables latchd. `make update` runs uninstall → git pull → reinstall in one command
- **missing `extra/` directory** — added `extra/config.toml`, `extra/xsetup.sh`, `extra/lemurs.pam`, and `extra/lemurs.service` so `make install` can find them
- **makefile `deps` target** — added `make deps` to automatically install `libpam0g-dev` and `build-essential`, and verify go is present
- **readme overhaul** — rewrote for clarity: added "what problem does this solve", "how it works", known issues, simpler install instructions, and proper dependency listing
- **view cleanup** — removed the non-functional automata background overlay from `View()` (the `Frame()` was never called, and the black-space placeholder caused `EEEE` garbage characters on some terminals). also removed a hardcoded 80-char width on the top bar so it adapts to the terminal
- **session environment setup** — `spawnSession` now calls `SetBasicVariables`, `SetDisplay`, `SetSessionParams`, `SetSeatVars`, `SetSessionVars`, and `SetXDGCommonPaths` before spawning the desktop. fixes `setupX` failing immediately with `missing DISPLAY/XDG_VTNR/HOME env vars` because none were set
- **pam session error made non-fatal** — `pam_open_session` failure (e.g. `pam_systemd` rejecting the DM process as session leader) is now logged as a warning instead of blocking the entire session spawn. also removed `session include login` from `latchd.pam` since the DM process can't register user sessions with systemd-logind
- **global session switching with `,` and `.`** — sessions can now be switched from any mode (not just when the switcher is focused). arrows still work when switcher is focused
### What's Missing
- no tests
- session leader is the latchd process, not the desktop — pam session tracking via systemd-logind may be imperfect
- not all config options affect the rendering yet
- occasional terminal state corruption on abrupt exit
- automata background visible at screen edges; form card covers the center
- DesktopNames parsing — `legacy/go/session/scan.go::splitDesktopNames` previously dropped desktop names from the session environment; it now maps the first semicolon-delimited value to `XDG_SESSION_DESKTOP` and the full list to `XDG_CURRENT_DESKTOP`, which fixes GNOME and KDE sessions that start without their desktop values
- UTMPX time fields — `legacy/go/auth/utmpx.go` used fixed C integer types for `ut_tv`; `legacy/go/auth/utmpx_time.h` now accepts the platform time fields, which fixes builds where `ut_tv` uses `struct timeval`
- cgo pointer storage — `legacy/go/auth/pam.go` passed a Go pointer through `pam_conv`; `storeHandle` and `loadHandle` keep the numeric handle in C memory, which fixes the Go 1.22 pointer check
- PAM conversation — `legacy/go/auth/pam.go::latchd_pam_conv` used a pointer-to-pointer as the response slice; it now writes to the allocated response array and answers only prompt messages, which fixes corrupted responses and the `pam_unix` password error
- cgo handle lifetime — `legacy/go/auth/pam.go::Credentials` now owns the handle until session close; the previous `Validate` cleanup released it before `OpenSession`
- session spawning — `legacy/go/session/exec.go` starts the selected desktop directly with dropped credentials; the previous path relaunched latchd with an undefined `-user` flag
- file names — rename the `lemurs` policy and unit files in `extra/` to `latchd`
- extra files — add `extra/config.toml`, `extra/xsetup.sh`, `extra/latchd.pam`, and `extra/latchd.service`, which lets `make install` find its source files
- Make targets — add `deps`, `enable`, and `update` targets for dependency checks, service enablement, and local updates
- terminal rendering — remove the unused background layer and fixed top-bar width from the Go renderer, which fixes garbage characters and narrow-terminal layout errors
- session environment — `legacy/go/session/env.go` now sets display, VT, desktop, seat, XDG, and home values before the desktop starts, which fixes missing `DISPLAY`, `XDG_VTNR`, and `HOME`
- session startup — handle `pam_open_session` errors as session log entries and remove the obsolete `login` include from the prototype service file
- global session switching — accept `,` and `.` from every screen state while keeping arrow keys for the focused switcher