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:
91
docs/architecture.md
Normal file
91
docs/architecture.md
Normal file
@@ -0,0 +1,91 @@
|
||||
# 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.
|
||||
159
docs/configuration.md
Normal file
159
docs/configuration.md
Normal file
@@ -0,0 +1,159 @@
|
||||
# Configuration reference
|
||||
|
||||
Latchd uses `/etc/latchd/config.toml` as its service configuration.
|
||||
The complete example is [`../extra/config.toml`](../extra/config.toml). Copy it to the main file, then change the required values.
|
||||
|
||||
## Command-line overrides
|
||||
|
||||
These command-line options replace selected values for one run:
|
||||
|
||||
| Option | Field changed |
|
||||
| --- | --- |
|
||||
| `--tty N` | `tty` |
|
||||
| `--xsessions PATH` | `x11.sessions_path` |
|
||||
| `--wlsessions PATH` | `wayland.sessions_path` |
|
||||
| `--initial-path PATHS` | `initial_path` |
|
||||
|
||||
Use these options for a one-time test. Put permanent values in the main file.
|
||||
|
||||
## Top-level fields
|
||||
|
||||
| Field | Default | Description |
|
||||
| --- | --- | --- |
|
||||
| `tty` | `2` | Virtual terminal number. Valid values are `1` through `63`. |
|
||||
| `main_log_path` | `/var/log/latchd.log` | Main service log. |
|
||||
| `client_log_path` | `/var/log/latchd.client.log` | Output log for graphical session clients. |
|
||||
| `pam_service` | `latchd` | Name of the PAM service file in `/etc/pam.d/`. |
|
||||
| `system_shell` | `/bin/sh` | Shell used by the TTY session and the preflight check. |
|
||||
| `initial_path` | `/usr/local/sbin:/usr/local/bin:/usr/bin` | Absolute directories used to resolve session programs. |
|
||||
|
||||
The path fields in this table must be absolute. `initial_path` must contain only non-empty absolute directories separated by colons.
|
||||
|
||||
## `[x11]`
|
||||
|
||||
The X server starts only when the selected session has kind `X11`.
|
||||
|
||||
| Field | Default | Description |
|
||||
| --- | --- | --- |
|
||||
| `display` | `:1` | X display name passed to Xorg and the session. |
|
||||
| `server_path` | `/usr/bin/X` | X server executable. |
|
||||
| `xauth_path` | `/usr/bin/xauth` | Program that writes the temporary Xauthority file. |
|
||||
| `server_timeout_secs` | `60` | Maximum wait for the X server readiness signal. |
|
||||
| `server_log_path` | `/var/log/latchd.xorg.log` | X server log. |
|
||||
| `sessions_path` | `/usr/share/xsessions` | Directory with X11 `.desktop` files. |
|
||||
| `scripts_path` | `/etc/latchd/wms` | Directory with administrator X11 session scripts. |
|
||||
|
||||
Set absolute paths for the X server and `xauth`. Latchd passes `display`, `vtN`, and the authority file to Xorg.
|
||||
|
||||
## `[wayland]`
|
||||
|
||||
| Field | Default | Description |
|
||||
| --- | --- | --- |
|
||||
| `sessions_path` | `/usr/share/wayland-sessions` | Directory with Wayland `.desktop` files. |
|
||||
| `scripts_path` | `/etc/latchd/wayland` | Directory with administrator Wayland session scripts. |
|
||||
|
||||
The selected Wayland program starts with these values; latchd does not start a compositor or create a Wayland socket.
|
||||
|
||||
## `[environment_switcher]`
|
||||
|
||||
| Field | Default | Description |
|
||||
| --- | --- | --- |
|
||||
| `include_tty_shell` | `false` | Add the TTY shell when graphical sessions exist. |
|
||||
| `max_display_length` | `18` | Maximum display width used for session titles. Must be greater than zero. |
|
||||
| `remember_last_session` | `true` | Remember the last successful session for each account. |
|
||||
| `last_session_path` | `/var/lib/latchd/last-session.toml` | File for the saved session preference. |
|
||||
|
||||
The preference file stores a session kind and title for each account; it holds at most 128 entries, writes a synced temporary file, and renames it into place. The file and its parent directory must belong to the account that runs latchd and must not be writable by group or other users.
|
||||
|
||||
Set `remember_last_session = false` to disable reads and writes; latchd ignores the path in that case.
|
||||
|
||||
## `[accounts]`
|
||||
|
||||
| Field | Default | Description |
|
||||
| --- | --- | --- |
|
||||
| `minimum_uid` | `1000` | Lowest UID that account enumeration includes. |
|
||||
| `excluded_users` | `[]` | Usernames that account enumeration skips. |
|
||||
| `enumerate` | `true` | Discover accounts through NSS for the account list. |
|
||||
| `faillock_path` | `/usr/sbin/faillock` | Faillock helper executable. |
|
||||
| `faillock_config` | `/etc/security/faillock.conf` | File from which Latchd reads the `deny` value. |
|
||||
| `shadow_path` | `/etc/shadow` | Shadow file used for account status display. |
|
||||
|
||||
Account enumeration also requires an absolute login shell and a safe home directory; `/bin/false`, `/usr/sbin/nologin`, and `/sbin/nologin` are excluded.
|
||||
|
||||
Latchd reads the shadow file only when it is a root-owned, non-writable regular file. The screen can show these states:
|
||||
|
||||
- `Locked` when the password hash starts with `!` or `*`;
|
||||
- `PasswordExpired` when the last-change day is zero or the maximum age passed;
|
||||
- `AccountExpired` when the account expiry day passed;
|
||||
- `AccountInactive` when the inactive period passed;
|
||||
- `NoLoginShell` when the shell does not permit login;
|
||||
- `Unknown` when shadow data is not available.
|
||||
|
||||
The faillock check only adds status text. The helper runs:
|
||||
|
||||
```text
|
||||
faillock --user USERNAME --legacy-output
|
||||
```
|
||||
|
||||
The query stops after 750 milliseconds. Bad output, a missing helper, or a timeout removes the faillock status from the screen.
|
||||
|
||||
## `[visual]`
|
||||
|
||||
| Field | Default | Description |
|
||||
| --- | --- | --- |
|
||||
| `wallpaper_path` | not set | Optional PNG or JPEG file for the TUI background. |
|
||||
| `background_enabled` | `true` | Draw the wallpaper and ambient background. |
|
||||
| `animations` | `true` | Update the ambient cellular animation. |
|
||||
| `notifications` | `true` | Show short status notices in the TUI. |
|
||||
| `reduced_motion` | `false` | Start with the ambient animation disabled. |
|
||||
| `max_wallpaper_bytes` | `8388608` | Maximum wallpaper file size. Valid range is 1 to 67108864 bytes. |
|
||||
|
||||
The wallpaper path must be absolute, and the file must be root-owned, regular, and not group-writable or other-writable. Source images are limited to 8192 by 8192 pixels and 16,777,216 pixels total, then scaled for the terminal.
|
||||
|
||||
The TUI also caps the animation state at 160 columns by 64 rows. Press `Ctrl-B` to toggle the background. Press `Ctrl-M` to toggle reduced motion.
|
||||
|
||||
## `[power_controls]`
|
||||
|
||||
Power actions are disabled in preview mode. Each action needs an absolute program path and a function-key label:
|
||||
|
||||
```toml
|
||||
[[power_controls.entries]]
|
||||
key = "F1"
|
||||
hint = "Power off"
|
||||
program = "/usr/bin/systemctl"
|
||||
args = ["poweroff"]
|
||||
```
|
||||
|
||||
`key` must be `F1` through `F12`, and `hint` must not be empty; `program` starts directly with `args`, without a shell, after the TUI asks for confirmation.
|
||||
|
||||
## Desktop entry rules
|
||||
|
||||
Only the `[Desktop Entry]` section is read, with these keys:
|
||||
|
||||
| Key | Use |
|
||||
| --- | --- |
|
||||
| `Name` | Session title. |
|
||||
| `Type` | Must be `Application` when present. |
|
||||
| `Hidden` | `true` hides the entry. |
|
||||
| `NoDisplay` | `true` hides the entry. |
|
||||
| `TryExec` | Optional executable check. |
|
||||
| `Exec` | Program and arguments. |
|
||||
| `DesktopNames` | Values for `XDG_CURRENT_DESKTOP` and `XDG_SESSION_DESKTOP`. |
|
||||
|
||||
Plain and quoted arguments are accepted; desktop field codes such as `%u` and `%f`, plus shell syntax, are rejected.
|
||||
|
||||
## Validation failures
|
||||
|
||||
The parser rejects unknown fields. The validator rejects these common errors:
|
||||
|
||||
- a virtual terminal outside `1` through `63`;
|
||||
- an empty PAM service name;
|
||||
- an empty path or a relative directory in `initial_path`;
|
||||
- a relative path in a required absolute-path field;
|
||||
- a zero session title width;
|
||||
- a relative last-session path when preferences are enabled;
|
||||
- a zero or oversized wallpaper limit;
|
||||
- an empty X display or zero X server timeout;
|
||||
- an invalid power-action key, label, or program path.
|
||||
|
||||
Use `--preview --no-tui` to report these errors without starting the screen.
|
||||
114
docs/development.md
Normal file
114
docs/development.md
Normal file
@@ -0,0 +1,114 @@
|
||||
# Development and release
|
||||
|
||||
This page lists the commands to build, test, and package latchd. The service configuration is `/etc/latchd/config.toml`.
|
||||
|
||||
## Toolchain
|
||||
|
||||
Install Rustup with Rust 1.88, then install the native packages used by the PAM link step:
|
||||
|
||||
```sh
|
||||
sudo apt-get install build-essential pkg-config libpam0g-dev libpam-modules
|
||||
```
|
||||
|
||||
Keep `Cargo.lock` with dependency changes.
|
||||
|
||||
## Build commands
|
||||
|
||||
| Command | Result |
|
||||
| --- | --- |
|
||||
| `make deps` | Checks Cargo, rustfmt, pkg-config, and the PAM development files |
|
||||
| `make build` | Builds the optimized binary and copies it to `build/latchd` |
|
||||
| `make run-preview` | Builds the binary and opens preview mode |
|
||||
| `make version` | Prints version, commit, epoch, and target metadata |
|
||||
| `make package` | Creates a deterministic archive and SHA-256 file in `dist/` |
|
||||
| `make clean` | Removes `build/`, `dist/`, and Cargo build output |
|
||||
|
||||
`make build` reads the version from `Cargo.toml`, embeds the current commit, and uses `SOURCE_DATE_EPOCH` for repeatable package metadata. Set `VERSION`, `TARGET`, or `SOURCE_DATE_EPOCH` when you need a different release value.
|
||||
|
||||
Build for another Rust target with:
|
||||
|
||||
```sh
|
||||
make build TARGET=x86_64-unknown-linux-gnu
|
||||
```
|
||||
|
||||
The target toolchain and system libraries must already be installed.
|
||||
|
||||
## Test commands
|
||||
|
||||
Run the fast checks before the host checks:
|
||||
|
||||
```sh
|
||||
make fmt
|
||||
make test
|
||||
make lint
|
||||
make doc
|
||||
make integration-test
|
||||
```
|
||||
|
||||
`make check` runs the first four commands together. The integration script uses preview mode and checks metadata, configuration validation, preflight, and safe CLI paths.
|
||||
|
||||
Run the optional security audit after you install the tool:
|
||||
|
||||
```sh
|
||||
cargo install cargo-audit --locked
|
||||
make audit
|
||||
```
|
||||
|
||||
The Go prototype has its own opt-in checks:
|
||||
|
||||
```sh
|
||||
make legacy-check
|
||||
make legacy-build
|
||||
```
|
||||
|
||||
The release build never calls these targets.
|
||||
|
||||
## Docker check
|
||||
|
||||
`make docker-test` builds `Dockerfile.test` as `latchd-test:local`. The image installs the PAM development files, Xvfb, and `xauth`, then runs formatting, workspace tests, Clippy, rustdoc, a release build, the CLI script, the capability probe, and the disposable PAM fixture.
|
||||
|
||||
The container checks build, test, preview, and fixture paths. Run the host checks in a disposable VM when you need a real VT, user session, compositor, or systemd-logind seat.
|
||||
|
||||
Remove the local image after a test run if you need the space:
|
||||
|
||||
```sh
|
||||
docker image rm latchd-test:local
|
||||
```
|
||||
|
||||
## PAM fixture
|
||||
|
||||
`make pam-fixture-test` must run as root because it writes temporary files below `/etc/pam.d/`. The script uses unique service names, runs the ignored Rust fixture tests, and removes the files in its exit trap.
|
||||
|
||||
The fixture checks authentication failure, password-change failure, selected session metadata, and environment values returned by the session module.
|
||||
|
||||
## Host checks
|
||||
|
||||
Use `make system-e2e` to inspect the binary, PAM library, faillock helper, VT device, Xvfb, Wayland socket, and systemd-logind.
|
||||
|
||||
Use the strict form only in a disposable VM:
|
||||
|
||||
```sh
|
||||
make system-e2e-strict
|
||||
```
|
||||
|
||||
The VM needs a test policy, a real VT, a working logind seat, and the X11 or Wayland stack under test. See [End-to-end testing](e2e-testing.md) for the setup and limits.
|
||||
|
||||
## Release archive
|
||||
|
||||
Run the release gates before packaging:
|
||||
|
||||
```sh
|
||||
make check
|
||||
make audit
|
||||
make integration-test
|
||||
make docker-test
|
||||
make package
|
||||
```
|
||||
|
||||
`make package` stages the binary, configuration, login policy, systemd unit, README, license, changelog, and build metadata. It sorts the archive, sets a fixed owner and timestamp, then writes the archive checksum and `dist/release.env`.
|
||||
|
||||
GitLab CI runs the verification and Docker jobs on branch and merge-request pipelines. A tag that matches the SemVer rule runs the package and release jobs. The package job checks that the tag version matches `Cargo.toml` before it creates the artifact.
|
||||
|
||||
## Repository changes
|
||||
|
||||
Keep Rust source under the workspace crates, keep host scripts in `scripts/`, and put sample service files in `extra/`. Add tests beside the behavior they cover, update the changelog with the implementation detail and the user-visible result, then run `git diff --check` before you commit.
|
||||
@@ -1,43 +1,85 @@
|
||||
# End-to-end testing
|
||||
# End-to-end checks
|
||||
|
||||
Latchd has two test layers:
|
||||
Latchd has checks for a development host, the Docker image, and a disposable VM.
|
||||
|
||||
* `make integration-test` builds the binary and exercises metadata, TOML
|
||||
validation, preflight, and the non-authenticating preview path. It does not
|
||||
need PAM credentials, root, a VT, X11, or Wayland.
|
||||
* `make pam-fixture-test` installs unique temporary PAM services only when run
|
||||
as root, executes authentication/password-change fault tests and the session
|
||||
metadata/environment handoff tests, then removes the services on exit.
|
||||
Non-root runs report `SKIP`.
|
||||
* `make system-e2e` probes the host for PAM, faillock, VT, X11, Wayland, and
|
||||
systemd-logind capabilities. Missing capabilities are reported as `SKIP`,
|
||||
never as a successful test. Use `make system-e2e-strict` only in a prepared
|
||||
VM or privileged runner; strict mode fails when a capability is unavailable.
|
||||
## Check matrix
|
||||
|
||||
The Docker test image runs the first layer and starts an isolated Xvfb server
|
||||
when available. It does not use host display sockets, host PAM files, or a
|
||||
host VT. The password-change path is covered by FFI/conversation tests in the
|
||||
container, but a real PAM password rotation and login handoff require a
|
||||
disposable VM with a dedicated test account and an explicitly provisioned PAM
|
||||
policy. The container probe intentionally reports those checks as capability
|
||||
information only.
|
||||
| Command | What it covers | Requirement |
|
||||
| --- | --- | --- |
|
||||
| `make check` | formatting, Rust tests, Clippy, and rustdoc | Rust toolchain and PAM development files |
|
||||
| `make integration-test` | release CLI, configuration validation, preflight, and preview paths | local build |
|
||||
| `make docker-test` | release build, CLI checks, Xvfb, capability probe, and PAM fixture | Docker |
|
||||
| `make pam-fixture-test` | denied login, password change, session metadata, and returned environment | root for temporary `/etc/pam.d/` files |
|
||||
| `make system-e2e` | PAM library, faillock, VT device, X11, Wayland socket, and logind seat | host capabilities |
|
||||
| `make system-e2e-strict` | all host capability checks | prepared VM |
|
||||
|
||||
For a local release-style run:
|
||||
Run the local set with:
|
||||
|
||||
```sh
|
||||
make check
|
||||
make integration-test
|
||||
make pam-fixture-test
|
||||
make docker-test
|
||||
make system-e2e
|
||||
```
|
||||
|
||||
The preview interface is documented in [`ui.md`](ui.md). Its screenshots come from a Docker PTY and do not represent a host VT or compositor.
|
||||
## Docker
|
||||
|
||||
For a VM with a real seat, compositor, and logind:
|
||||
`Dockerfile.test` installs Rust 1.88, the PAM development files, Xvfb, and `xauth`, then runs the release checks inside the image.
|
||||
|
||||
Use a disposable VM for VT, compositor, and systemd-logind checks.
|
||||
|
||||
The Docker test keeps the image as `latchd-test:local`. Remove it after the run when required:
|
||||
|
||||
```sh
|
||||
docker image rm latchd-test:local
|
||||
```
|
||||
|
||||
The UI screenshots use a Docker PTY and preview mode.
|
||||
|
||||
## PAM fixture
|
||||
|
||||
Run the fixture as root:
|
||||
|
||||
```sh
|
||||
sudo make pam-fixture-test
|
||||
```
|
||||
|
||||
The script creates temporary service names below `/etc/pam.d/`, builds a small session module, runs the Rust fixture tests, and removes the temporary files after the run.
|
||||
|
||||
The fixture checks these handoffs:
|
||||
|
||||
- a denied authentication returns an error;
|
||||
- an expired password enters the password-change path;
|
||||
- the selected TTY, session type, desktop, seat, and VT reach the session stack;
|
||||
- session values returned by the module reach the child environment.
|
||||
|
||||
## Host capability probe
|
||||
|
||||
Use `make system-e2e` to inspect host capabilities.
|
||||
|
||||
The probe checks `/dev/tty0`, Xvfb, a Wayland socket, the PAM library, faillock, `/etc/pam.d/latchd`, and systemd-logind.
|
||||
|
||||
## Strict VM check
|
||||
|
||||
Prepare a disposable VM with:
|
||||
|
||||
- the latchd binary and `/etc/latchd/config.toml`;
|
||||
- a test account and `/etc/pam.d/latchd` configured for it;
|
||||
- a real virtual terminal and an available `seat0`;
|
||||
- systemd-logind;
|
||||
- the X11 or Wayland stack under test;
|
||||
- the desktop files and executables named by the config.
|
||||
|
||||
Build the binary, then run:
|
||||
|
||||
```sh
|
||||
make build
|
||||
make system-e2e-strict
|
||||
```
|
||||
|
||||
Do not run strict mode on a normal developer workstation unless the VM's
|
||||
display-manager handoff and test PAM policy are isolated from the host.
|
||||
## Reading failures
|
||||
|
||||
- `configuration` errors point to a value or ownership check; run `--preview --show-config` to isolate it
|
||||
- `session` errors point to a desktop entry, executable, home directory, X11 server, or child process; run `--preview --no-tui` and read the client log
|
||||
- `PAM` errors point to account or service setup; check `/etc/pam.d/latchd` and the service journal
|
||||
- `terminal` errors mean the process lacks a controlling terminal; use preview mode for non-interactive checks
|
||||
|
||||
208
docs/guide.md
Normal file
208
docs/guide.md
Normal file
@@ -0,0 +1,208 @@
|
||||
# Latchd guide
|
||||
|
||||
Latchd is a terminal login manager for Linux. It starts on a virtual terminal, gives you a keyboard-first screen, and starts the shell or desktop you select.
|
||||
|
||||
The service is small on purpose: systemd gives it the terminal, the login stack checks the account, and latchd handles the screen plus the process handoff. The [configuration reference](configuration.md) lists every field, while the [architecture notes](architecture.md) explain the Rust workspace.
|
||||
|
||||
## What starts when
|
||||
|
||||
The service unit starts `/usr/bin/latchd` as root on `/dev/tty2`. The default configuration file is `/etc/latchd/config.toml`.
|
||||
|
||||
The screen contains these parts:
|
||||
|
||||
- A list of desktop and shell sessions.
|
||||
- An account field.
|
||||
- A password field.
|
||||
- A status line.
|
||||
|
||||
Use the screen in this order:
|
||||
|
||||
- Select an account.
|
||||
- Select a desktop or shell.
|
||||
- Enter the password.
|
||||
- Press Enter.
|
||||
- Enter a new password if the login stack requests one.
|
||||
|
||||
After login, latchd performs these actions:
|
||||
|
||||
- Build an environment for the selected account and session.
|
||||
- Open the session.
|
||||
- Start the selected child with the account credentials.
|
||||
- Wait for the child to exit.
|
||||
- Close the session.
|
||||
|
||||
Session-specific setup works as follows:
|
||||
|
||||
- X11 uses a temporary authority file.
|
||||
- Wayland receives the session values that it needs.
|
||||
- A TTY session starts the account shell.
|
||||
|
||||
```text
|
||||
Start latchd
|
||||
|
|
||||
v
|
||||
Show accounts and sessions
|
||||
|
|
||||
v
|
||||
Select account, session, and password
|
||||
|
|
||||
v
|
||||
Press Enter
|
||||
|
|
||||
v
|
||||
Check the password and account state
|
||||
|
|
||||
+------------------------------+
|
||||
| |
|
||||
v v
|
||||
Login accepted New password required
|
||||
| |
|
||||
| v
|
||||
| Show new password field
|
||||
| |
|
||||
| v
|
||||
| Submit the password change
|
||||
| |
|
||||
+--------------+---------------+
|
||||
|
|
||||
v
|
||||
Build the session environment
|
||||
|
|
||||
v
|
||||
Open the session
|
||||
|
|
||||
v
|
||||
Start the child
|
||||
|
|
||||
v
|
||||
Wait for exit
|
||||
|
|
||||
v
|
||||
Close the session
|
||||
```
|
||||
|
||||
## Service mode and preview mode
|
||||
|
||||
Service mode needs root, a controlling terminal, and a system configuration file with safe ownership. It can authenticate users and start sessions.
|
||||
|
||||
Preview mode is for local work; it draws the screen, discovers the configured entries, and reads the user configuration, but it does not authenticate or start a child process.
|
||||
|
||||
```sh
|
||||
make build
|
||||
./build/latchd --preview --no-log
|
||||
```
|
||||
|
||||
Use the non-interactive checks when you only need a result:
|
||||
|
||||
```sh
|
||||
./build/latchd --preview --show-config --no-log
|
||||
./build/latchd --preview --no-tui --no-log
|
||||
```
|
||||
|
||||
`--show-config` prints the build information plus the selected terminal, service name, and shell. `--no-tui` validates the configuration and runs session preflight without opening the screen.
|
||||
|
||||
## Build from source
|
||||
|
||||
Install Rust 1.88 and the PAM development files. On Debian or Ubuntu, use:
|
||||
|
||||
```sh
|
||||
sudo apt-get install build-essential pkg-config libpam0g-dev libpam-modules
|
||||
```
|
||||
|
||||
The toolchain file selects Rust 1.88 and the required `rustfmt` and `clippy` components. Build the optimized binary with:
|
||||
|
||||
```sh
|
||||
make build --release
|
||||
```
|
||||
|
||||
The output is at `build/latchd`, `make build` also embeds the version, commit, and build time.
|
||||
|
||||
Run `make check` for verification and `make package` to create a tarball with the binary, the system configuration, and the systemd unit. The `make install` target writes the files to the system.
|
||||
|
||||
The commands and the container workflow are in [Development and release](development.md).
|
||||
|
||||
## Install and start the service
|
||||
|
||||
Use a disposable machine first and keep another way to log in while you change the display manager:
|
||||
|
||||
```sh
|
||||
sudo make install
|
||||
sudo systemctl enable latchd.service
|
||||
sudo systemctl start latchd.service
|
||||
```
|
||||
|
||||
The install target writes the binary, the system TOML file, the login policy, the session-script directories, and the systemd unit. It does not disable another display manager, so stop the service that already owns the target VT before you start latchd there.
|
||||
|
||||
The supplied unit owns `tty2`. If you move it, change `tty` in the TOML file and `TTYPath` in the unit together; otherwise the values passed to the login stack and the terminal will disagree.
|
||||
|
||||
Use `sudo systemctl status latchd.service` and `sudo journalctl -u latchd.service -b --no-pager` when the unit fails before the screen appears.
|
||||
|
||||
## Configuration files
|
||||
|
||||
The service default is `/etc/latchd/config.toml`. Preview mode first checks `$XDG_CONFIG_HOME/latchd/config.toml`, then `~/.config/latchd/config.toml`, and finally the system file; `--config PATH` overrides all of them.
|
||||
|
||||
Create the user file with:
|
||||
|
||||
```sh
|
||||
make install-user
|
||||
```
|
||||
|
||||
The target creates the directory with mode `700` and the file with mode `600`, and it leaves an existing file unchanged. Read [Configuration reference](configuration.md) before you add a variable file or change a path used by the service.
|
||||
|
||||
The main sections are:
|
||||
|
||||
| Section | What it controls |
|
||||
| --- | --- |
|
||||
| `[accounts]` | NSS account list, shadow status, and faillock display |
|
||||
| `[x11]` | X server, authority tool, desktop entries, and scripts |
|
||||
| `[wayland]` | Wayland desktop entries and scripts |
|
||||
| `[environment_switcher]` | TTY entry and last-session file |
|
||||
| `[visual]` | Wallpaper, animation, notices, and motion setting |
|
||||
| `[power_controls]` | Confirmed programs bound to F1 through F12 |
|
||||
|
||||
## Accounts and sessions
|
||||
|
||||
Account discovery starts with NSS and then removes entries below `minimum_uid`, entries in `excluded_users`, accounts with a no-login shell, and homes with unsafe ownership or write permission. Set `enumerate = false` when you want a blank list and manual username entry.
|
||||
|
||||
The screen can show password expiry, account expiry, lock state, and inactivity from the configured shadow file. It can also call `faillock` for the selected name; the query has a 750 millisecond limit and a failed query only removes the extra status text.
|
||||
|
||||
Session discovery reads `.desktop` files from the X11 and Wayland directories, then reads executable files from the matching script directories. Files must belong to root and must not be writable by group or other users. A desktop entry with `Hidden=true`, `NoDisplay=true`, an invalid `Exec`, or an unavailable `TryExec` is skipped.
|
||||
|
||||
Latchd stores desktop commands as argument lists, so shell operators and desktop field codes do not run. A script entry uses the file path as its command. The TTY shell appears when `include_tty_shell = true`, and it also appears when no other session exists.
|
||||
|
||||
## The session environment
|
||||
|
||||
The child starts with an empty environment, then receives terminal locale values, account paths, XDG directories, session type, seat, VT number, and the selected desktop values. The account UID, primary GID, and supplementary groups are set before the child runs.
|
||||
|
||||
X11 starts the configured server on the selected VT, waits for its readiness signal, creates a private authority file, and removes that file after the client exits. Wayland starts the selected program but does not start a compositor. TTY mode attaches the shell to the controlling terminal.
|
||||
|
||||
The session records a UTMPX user process when the platform supports it. A last-session file can remember the successful choice for each account; the file is bounded and replaced atomically.
|
||||
|
||||
## Logs and useful paths
|
||||
|
||||
The example file uses these paths:
|
||||
|
||||
| Path | Use |
|
||||
| --- | --- |
|
||||
| `/var/log/latchd.log` | Main service and audit output |
|
||||
| `/var/log/latchd.client.log` | Graphical session output |
|
||||
| `/var/log/latchd.xorg.log` | X server output |
|
||||
| `/var/lib/latchd/last-session.toml` | Last session per account |
|
||||
| `/etc/latchd/wms/` | X11 script entries |
|
||||
| `/etc/latchd/wayland/` | Wayland script entries |
|
||||
|
||||
The service creates log files with mode `600` and rejects a main log larger than 64 MiB. Audit lines include the action, result, account, and a short reason; passwords and command arguments stay out of the log.
|
||||
|
||||
## When a session does not start
|
||||
|
||||
Check the config and the paths first:
|
||||
|
||||
```sh
|
||||
./build/latchd --preview --show-config --no-log
|
||||
./build/latchd --preview --no-tui --no-log
|
||||
make system-e2e
|
||||
```
|
||||
|
||||
Then check the desktop file, the executable named by `Exec` or `TryExec`, the account home permissions, and the service log. For X11, check the X server and `xauth` paths; for Wayland, check the compositor and its user service outside latchd.
|
||||
|
||||
Use [End-to-end testing](e2e-testing.md) when the host is a container or when you need a disposable VM for the terminal and display stack.
|
||||
82
docs/ui.md
82
docs/ui.md
@@ -1,39 +1,75 @@
|
||||
# Latchd user interface
|
||||
# User interface
|
||||
|
||||
Latchd starts on a virtual terminal. The interface keeps the session list and the login fields visible at the same time.
|
||||
The screen is designed for a virtual terminal. It keeps the session list beside the account and password fields, and adapts down to 60 columns by 16 rows.
|
||||
|
||||

|
||||

|
||||
|
||||
The screenshot comes from the Docker test image. It uses preview mode. Preview mode does not contact PAM or start a user session.
|
||||
Preview mode screenshot of the login layout.
|
||||
|
||||
## Fields and status
|
||||
|
||||
- session list — choose an X11, Wayland, administrator-script, or TTY entry
|
||||
- username — type an account name or select one discovered through NSS
|
||||
- password — enter the password; the screen clears it after an attempt
|
||||
- status line — shows focus prompts, account state, faillock counts, notices, and authentication progress
|
||||
- footer — shows the keys that apply to the current screen state
|
||||
|
||||
When the selected account needs a new password, the password field changes to the new-password prompt. The old password stays in the pending authentication request until the password-change call finishes, then both values are cleared.
|
||||
|
||||
## Keys
|
||||
|
||||
| Key | Action |
|
||||
| --- | --- |
|
||||
| `Tab` / `Shift-Tab` | Move between session, username, and password |
|
||||
| `Up` / `Down` | Move focus, or move through the filtered list |
|
||||
| `Left` / `Right` | Change the session when the session list has focus |
|
||||
| `/` | Start a session or account filter |
|
||||
| `Ctrl-R` | Refresh accounts and sessions in the background |
|
||||
| `Ctrl-N` / `Ctrl-P` | Select the next or previous account |
|
||||
| `Ctrl-B` | Toggle the wallpaper and ambient animation |
|
||||
| `Ctrl-M` | Toggle reduced motion |
|
||||
| `Enter` | Start login or submit a new password |
|
||||
| `Esc` | Clear the password, close a popup, or cancel power confirmation |
|
||||
| `?` | Open the key reference |
|
||||
| `Ctrl-C` | Restore the terminal and exit |
|
||||
| `F1`–`F12` | Confirm the matching configured power action |
|
||||
|
||||
Lowercase filters ignore case. A filter with an uppercase character uses case-sensitive matching. Press `Esc` while filtering to leave the filter field.
|
||||
|
||||
## Visual settings
|
||||
|
||||
The `[visual]` section controls the background:
|
||||
|
||||
```toml
|
||||
[visual]
|
||||
background_enabled = true
|
||||
animations = true
|
||||
notifications = true
|
||||
reduced_motion = false
|
||||
max_wallpaper_bytes = 8388608
|
||||
# wallpaper_path = "/etc/latchd/wallpaper.png"
|
||||
```
|
||||
|
||||
`wallpaper_path` accepts a root-owned PNG or JPEG file. The loader checks the file size and source dimensions, then scales the image before it draws it in the terminal. `animations = false` stops frame updates at startup, while `reduced_motion = true` does the same and can also be changed with `Ctrl-M`.
|
||||
|
||||
Set `LATCHD_ASCII=1` for terminals without reliable Unicode cells, `NO_COLOR=1` for plain output, and `LATCHD_HIGH_CONTRAST=1` for a stronger focus style.
|
||||
|
||||
## Help panel
|
||||
|
||||
Press `?` to show the key reference.
|
||||
Press `?` to open the key reference:
|
||||
|
||||

|
||||

|
||||
|
||||
Use the following keys:
|
||||
Press `Esc` or `?` again to close it. The help panel does not change the selected account or session.
|
||||
|
||||
- Press `Tab` or `Shift-Tab` to move focus.
|
||||
- Press the arrow keys to choose a session.
|
||||
- Press `/` to filter sessions or accounts.
|
||||
- Press `Ctrl-N` or `Ctrl-P` to change the account.
|
||||
- Press `Ctrl-R` to refresh account and session discovery.
|
||||
- Press `Ctrl-B` to toggle the background.
|
||||
- Press `Ctrl-M` to toggle reduced motion.
|
||||
- Press `Enter` to authenticate.
|
||||
- Press `Esc` to clear the password or close the help panel.
|
||||
- Press `Ctrl-C` to restore the terminal and quit.
|
||||
## Capture the screen
|
||||
|
||||
These messages show account state before login. PAM accepts or rejects the login.
|
||||
|
||||
## Capture method
|
||||
|
||||
The capture uses a Docker PTY and the release binary from `latchd-test:local`:
|
||||
Build the Docker test image, then start a PTY with a fixed size:
|
||||
|
||||
```sh
|
||||
docker build -f Dockerfile.test -t latchd-test:local .
|
||||
tmux new-session -d -x 120 -y 36 \
|
||||
'docker run --rm -it latchd-test:local /workspace/target/release/latchd --preview --no-log'
|
||||
```
|
||||
|
||||
The image in this directory contains the plain terminal capture. It does not show a host VT, a host compositor, or a real PAM login.
|
||||
The capture shows the preview binary and terminal renderer, preview mode is simply for testing and doesn't perform any real login.
|
||||
Reference in New Issue
Block a user