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).
92 lines
5.7 KiB
Markdown
92 lines
5.7 KiB
Markdown
# Architecture
|
|
|
|
Latchd is one Rust binary split into three workspace crates; `rust/main.rs` joins them and owns the service loop.
|
|
|
|
## Crates
|
|
|
|
| Path | Role |
|
|
| --- | --- |
|
|
| `crates/latchd-core` | Configuration, account discovery, session discovery, environment construction, UTMPX, preferences, and audit records |
|
|
| `crates/latchd-auth` | PAM FFI, the conversation callback, authentication tickets, password changes, and session cleanup |
|
|
| `crates/latchd-tui` | Ratatui state, terminal input, rendering, filters, notices, wallpaper, and animation |
|
|
| `rust/main.rs` | CLI options, configuration, logging, preflight, and the service loop |
|
|
|
|
## Startup
|
|
|
|
The systemd unit starts latchd on the configured VT, then the process follows this path:
|
|
|
|
1. Parse the CLI options.
|
|
2. Read `/etc/latchd/config.toml`.
|
|
3. Apply command-line overrides.
|
|
4. Validate values, paths, and power actions.
|
|
5. Check the shell, desktop programs, X11 tools, and session scripts.
|
|
6. Start the auth worker and the TUI.
|
|
|
|
The service path also checks the terminal and the absence of an existing user session. Preview skips these checks and stops before authentication.
|
|
|
|
## Authentication worker
|
|
|
|
The auth worker owns each PAM handle and receives requests through a bounded channel. The TUI receives an opaque ticket after authentication.
|
|
|
|
For a login, the worker starts a transaction, answers prompts, checks the account, resolves it through NSS, and establishes credentials. The password-change call accepts the current and new passwords when required.
|
|
|
|
The worker drops the saved transaction when the user cancels a ticket.
|
|
|
|
## Session handoff
|
|
|
|
After the user selects a session, the main loop creates a `SessionSpec` with the account, command, environment, terminal, and optional X11 runtime. The worker then:
|
|
|
|
1. sets the terminal name to `ttyN`;
|
|
2. supplies the selected session type, desktop, seat, and VT;
|
|
3. opens the login session;
|
|
4. copies a fixed list of runtime values into the child environment;
|
|
5. starts the user process;
|
|
6. closes the session and removes the credentials.
|
|
|
|
The child starts with a cleared environment, receives locale values plus the account and XDG paths, creates a new session ID, initializes supplementary groups, and switches to the account UID and GID before it runs.
|
|
|
|
The imported runtime list contains `XDG_SESSION_CLASS`, `XDG_SESSION_TYPE`, `XDG_SESSION_DESKTOP`, `XDG_SEAT`, `XDG_VTNR`, `XDG_RUNTIME_DIR`, `XDG_SESSION_ID`, and `DBUS_SESSION_BUS_ADDRESS`.
|
|
|
|
## X11, Wayland, and TTY
|
|
|
|
- X11 — starts the configured server on `vtN`, waits for its readiness signal, writes a private authority file with `xauth`, starts the desktop client, then removes the file and stops the server
|
|
- Wayland — starts the selected desktop program with the session values; the compositor and its socket come from the user service or desktop setup
|
|
- TTY — attaches the selected shell to the controlling terminal and keeps the terminal as the child standard input, output, and error
|
|
|
|
The session runner writes a UTMPX user-process record when the platform supports it. It closes that record when the child exits. The runtime can report systemd-logind markers, but it does not call a logind D-Bus API.
|
|
|
|
## Session discovery
|
|
|
|
The core crate scans the configured X11 and Wayland directories in sorted file-name order. It keeps a `.desktop` file only when the file is root-owned, regular, and not writable by group or other users.
|
|
|
|
The parser reads `Name`, `Type`, `Hidden`, `NoDisplay`, `TryExec`, `Exec`, and `DesktopNames`; it rejects field codes and shell operators, so each session remains a program plus an argument list. Administrator scripts use the same ownership checks, and the file name becomes the display title.
|
|
|
|
The TTY shell appears when the configuration requests it or when no graphical entry survives the scan.
|
|
|
|
## Account discovery
|
|
|
|
The account scanner reads NSS records, sorts them by username, and filters by UID, excluded name, login shell, home ownership, and home permissions. Shadow aging data adds the status shown beside the selected account, while the faillock helper adds a short failure count with a 750 millisecond limit.
|
|
|
|
A missing shadow file or a failed faillock query produces an unknown status. The account remains in the list.
|
|
|
|
## Preferences and visual state
|
|
|
|
The last-session store keeps one bounded entry per account, writes through a private temporary file, syncs it, and renames it into place. A missing or invalid store falls back to the first discovered session.
|
|
|
|
The visual module loads an optional PNG or JPEG, scales it to a terminal-sized RGB image, and draws a small cellular animation over it. The animation state is capped at 160 columns by 64 rows; `Ctrl-B` toggles the background and `Ctrl-M` toggles motion.
|
|
|
|
## File and process rules
|
|
|
|
- The service configuration must belong to root and must not be writable by group or other users.
|
|
- Desktop files, X11 tools, scripts, shells, and wallpapers must pass ownership and mode checks.
|
|
- Session logs and Xauthority files use private permissions.
|
|
- Power actions use absolute program paths and require Enter confirmation.
|
|
|
|
## Changing the code
|
|
|
|
Add a config field in `crates/latchd-core/src/config.rs`, add its validation, and add one valid and one invalid test. Add a new session source as a `SessionDescriptor`; keep command parsing separate from shell execution.
|
|
|
|
Keep handle-based authentication work inside `crates/latchd-auth`, add a fault test when the operation crosses the C boundary, and keep password storage zeroizing. Add TUI behavior in the state machine, then add a state test and a render test at the minimum terminal size.
|
|
|
|
Run `cargo fmt --all -- --check`, `cargo test --locked --workspace --all-targets`, `cargo clippy --locked --workspace --all-targets --all-features -- -D warnings`, and rustdoc before you send the change.
|