Replace the legacy Go implementation as the production path, quarantine the reference tree, add deterministic release packaging and version metadata, and run verification through GitLab CI.
149 lines
8.5 KiB
Markdown
149 lines
8.5 KiB
Markdown
# latchd
|
||
|
||
`latchd` is a Linux/PAM terminal login manager written in Rust. It presents a keyboard-first Ratatui login screen on a virtual terminal, authenticates through PAM, and starts an isolated TTY, X11, or Wayland session with an explicit per-user environment.
|
||
|
||
The Rust binary is the supported implementation. The original Go/cgo tree under `legacy/go/` is retained as a quarantined comparison artifact and is not built by the default targets.
|
||
|
||
## Security model
|
||
|
||
- The service requires root for real sessions and refuses to start from an existing desktop session.
|
||
- PAM work is serialized on one dedicated worker thread. PAM handles never cross that thread boundary; authentication tickets are opaque RAII-owned transactions.
|
||
- Password buffers are zeroized and are never placed in argv, logs, or the rendered UI.
|
||
- Session environments are rebuilt from an allowlist for every login. Shell commands are argv vectors; no configured command is passed through a shell.
|
||
- Desktop files and session scripts must be root-owned, regular, non-group-writable files. Account shells such as `false` and `nologin` are rejected.
|
||
- Xauthority cookies are generated in a `0600` file owned by the account, supplied to `xauth` through stdin, and removed by an RAII cleanup guard.
|
||
- Main, graphical-session, and Xorg logs are private (`0600`) and opened without following symlinks.
|
||
- Power actions are an explicit absolute-path allowlist and require confirmation. They are disabled in `--preview` mode.
|
||
|
||
The implementation intentionally does not provide an administrator unlock shortcut: account lockout and authorization remain the responsibility of the configured PAM policy.
|
||
|
||
## Build and test
|
||
|
||
Requirements: Rust 1.88 or newer, a Linux PAM development package, and a C toolchain. Debian/Ubuntu packages are `libpam0g-dev`, `libpam-modules`, and `build-essential`.
|
||
|
||
```bash
|
||
make deps
|
||
make build
|
||
make test # unit tests and rustdoc warnings
|
||
make lint # Clippy with warnings denied
|
||
make audit # RustSec dependency advisory scan
|
||
make docker-test # the same checks in a pinned Debian container
|
||
make package # deterministic install archive plus SHA-256
|
||
make release # all local gates, Docker, and package output
|
||
```
|
||
|
||
`make build` installs the release binary at `build/latchd`. The reproducible test image is defined by [Dockerfile.test](Dockerfile.test) and does not require `--privileged`.
|
||
|
||
## Releases
|
||
|
||
The supported release pipeline is GitLab CI in [`.gitlab-ci.yml`](.gitlab-ci.yml).
|
||
Every branch and merge request runs formatting, tests, Clippy, rustdoc, a
|
||
RustSec dependency audit, and the Docker suite. Docker-in-Docker requires a trusted privileged GitLab Runner with
|
||
the runner client-certificate volume configured; untrusted fork pipelines
|
||
should not be assigned to that runner. A SemVer tag
|
||
such as `v0.3.0` additionally builds a deterministic
|
||
Linux archive containing the binary, systemd unit, PAM policy, configuration,
|
||
license, changelog, build metadata, and a SHA-256 checksum, then publishes it
|
||
as a GitLab Release. The tag version must match the workspace version. CI
|
||
creates the GitLab Release only after both Rust and Docker verification pass.
|
||
|
||
Release builds use `SOURCE_DATE_EPOCH`, commit metadata, stripped symbols,
|
||
`opt-level = 3`, thin LTO, one codegen unit, no incremental state, and
|
||
`panic = "abort"`. Set `TARGET` or `DESTDIR` when invoking `make package` for a
|
||
controlled package build; `VERSION` must match the workspace version. The
|
||
legacy Go tree is physically isolated under `legacy/go/` and is never included
|
||
in release artifacts.
|
||
|
||
## Installation
|
||
|
||
```bash
|
||
sudo make install
|
||
sudo systemctl enable latchd.service
|
||
```
|
||
|
||
Installation places the binary at `/usr/bin/latchd`, the configuration at `/etc/latchd/config.toml`, the PAM policy at `/etc/pam.d/latchd`, and the systemd unit at `/usr/lib/systemd/system/latchd.service`. The unit owns `/dev/tty2`; adjust `TTYPath`, `After`, and the `tty` configuration together if another VT is required.
|
||
|
||
Keep the previous package/binary available while validating a deployment. `--preview` is a safe UI exercise that does not authenticate, create a user session, or execute power actions. `--no-tui` (also `--preflight`) validates configuration and exits without entering the login screen.
|
||
|
||
## Command line
|
||
|
||
```text
|
||
latchd [OPTIONS]
|
||
|
||
-c, --config <PATH> Configuration TOML (default /etc/latchd/config.toml)
|
||
-v, --variables <PATH> Optional TOML variable file
|
||
--tty <N> Override the virtual terminal number
|
||
--xsessions <DIR> Override the X11 desktop-entry directory
|
||
--wlsessions <DIR> Override the Wayland desktop-entry directory
|
||
--initial-path <PATH> Override the session PATH
|
||
--preview Draw the UI without authentication or session startup
|
||
--no-tui, --preflight Validate configuration and exit
|
||
--no-log Disable the main log
|
||
--show-config Print the key validated settings and exit
|
||
--build-info Print version, commit, and build epoch and exit
|
||
-V, --version Print the version
|
||
```
|
||
|
||
An explicit configuration or variables file used by a real (non-preview) service must be root-owned, regular, and not group/other writable. The default configuration is optional: when `/etc/latchd/config.toml` is absent, built-in defaults are used.
|
||
|
||
## Configuration
|
||
|
||
See [extra/config.toml](extra/config.toml) for the complete schema. Values may be substituted from a TOML variables file with `$NAME` syntax. Power entries contain an executable path and an argv array, never a shell command string.
|
||
|
||
Session discovery reads root-owned `.desktop` files from the configured X11 and Wayland directories and executable root-owned scripts from `/etc/latchd/wms` and `/etc/latchd/wayland`. Unsupported desktop-entry field codes are rejected rather than interpreted by a shell. A TTY shell is offered when no graphical sessions are available, or when `include_tty_shell = true`.
|
||
|
||
When enabled, the account chooser discovers eligible NSS users (by default,
|
||
UID 1000 and above with a real home directory and login shell). `Ctrl-N` and
|
||
`Ctrl-P` cycle accounts, clear the password, and start a bounded advisory
|
||
`faillock` query. The displayed lockout state never overrides PAM: a PAM
|
||
conversation remains the only authorization decision. Set
|
||
`accounts.enumerate = false` when listing local accounts is not appropriate.
|
||
|
||
The `[visual]` section is TUI-only. It can load a root-owned PNG/JPEG as a
|
||
downsampled terminal-cell wallpaper, run a low-rate cellular-automata ambient
|
||
layer, and show short-lived notifications. `reduced_motion = true` disables
|
||
motion while preserving the static layout; `NO_COLOR=1` disables the image
|
||
layer and all color effects.
|
||
|
||
## TUI controls
|
||
|
||
The screen is designed for 80×24 and remains usable down to 60 columns. Below the supported minimum it reports the required size without drawing a partial frame.
|
||
|
||
| Key | Action |
|
||
| --- | --- |
|
||
| Tab / Shift-Tab | Move focus |
|
||
| Up / Down | Move focus |
|
||
| Left / Right | Change session when the chooser is focused |
|
||
| Ctrl-N / Ctrl-P | Select the next / previous discovered account |
|
||
| Ctrl-B | Toggle the TUI-only wallpaper/automata background |
|
||
| Ctrl-M | Toggle reduced motion |
|
||
| Enter | Authenticate from the password field |
|
||
| Esc | Clear the password / cancel power confirmation |
|
||
| `?` | Show keyboard help |
|
||
| Ctrl-C | Restore the terminal and quit |
|
||
| F1–F12 | Confirm and run a configured power action |
|
||
|
||
`NO_COLOR=1` requests a monochrome semantic style, and `LATCHD_ASCII=1` selects ASCII session markers for terminals without reliable Unicode support.
|
||
|
||
## Project layout
|
||
|
||
```text
|
||
crates/
|
||
├── latchd-core/ validated config, accounts, sessions, and UTMPX accounting
|
||
├── latchd-auth/ PAM FFI, conversation callback, and transaction worker
|
||
└── latchd-tui/ Ratatui state machine, wallpaper, automata, and effects
|
||
rust/main.rs CLI, privilege checks, logging, and orchestration
|
||
extra/ example config, PAM policy, systemd unit, session directories
|
||
scripts/ Docker test entry point
|
||
legacy/
|
||
└── go/ non-production Go reference (explicit `make legacy-build` only)
|
||
```
|
||
|
||
## Platform support
|
||
|
||
The first release targets Linux with PAM and systemd. BSD and non-PAM platforms are not claimed or tested. Real PAM/VT/X11 integration should be exercised on an isolated test host; the ordinary Docker suite covers compilation, unit tests, rendering tests, formatting, Clippy, and rustdoc without privileged container access.
|
||
|
||
## License
|
||
|
||
Apache-2.0; see [LICENSE](LICENSE).
|