Reconcile Pi providers on first boot, where no login session exists #50

Open
opened 2026-08-22 13:58:46 +01:00 by vnprc-agent · 0 comments

A freshly provisioned dev VM configures Pi on its first boot, with nobody logged in.

Primary goals:

  • First boot configures Pi — the Home Manager activation that runs at boot reconciles the machine's declared providers instead of refusing, so a provisioned machine has working Pi credentials before anyone logs in.
  • Staging stays memory-backed — the reconciler's rollback snapshots still never touch disk, and the directory it falls back to is measured to be tmpfs rather than assumed.
  • The boot condition gets a witness — the lifecycle check exercises reconciliation with no session runtime directory, which is the state first boot actually presents.

Current state and specifics

modules/pi-provider-reconcile.sh:45 resolves its staging directory as ${XDG_RUNTIME_DIR:-/run/user/$(id -u)} and exits 1 when that path is not a directory:

runtime_dir=${XDG_RUNTIME_DIR:-/run/user/$(id -u)}
if [[ ! -d "$runtime_dir" || -L "$runtime_dir" ]]; then
  echo "ERROR: volatile user runtime directory is unavailable; refusing Pi provider reconciliation" >&2
  exit 1
fi

The generated home-manager-<user>.service is WantedBy=multi-user.target, runs as the account with User=, and is ordered Before=systemd-user-sessions.service. It therefore has no PAM session, and /run/user/<uid> — which systemd-logind creates for a session — does not exist yet. Every first boot logs the refusal above followed by the module's own Pi provider configuration was not reconciled; see ERROR above, then rebuild. and leaves auth.json and models.json untouched.

Nothing else on the path is at fault. The Age credential is already deployed and owned by the account when activation runs, and the joined provider catalog and credential map are correct in the generated store paths. Running the same reconciler by hand against the same inputs, differing only in that a login session exists, configures both providers.

The condition is invisible to the existing check because checks/pi-provider-lifecycle.nix:151 exports XDG_RUNTIME_DIR before every fixture, so no fixture ever runs without one.

Scope

In: the staging-directory selection in modules/pi-provider-reconcile.sh, a boot-path fixture in checks/pi-provider-lifecycle.nix, and the README paragraph stating when reconciliation runs.

Out: credential delivery, the ownership manifest format, and the locking protocol — all measured correct here. The refusal when no tmpfs exists at all stays a loud failure with no fixture: pinning it would need an override that lets a caller aim staging elsewhere, which is exactly the bypass this script should not ship.

A freshly provisioned dev VM configures Pi on its first boot, with nobody logged in. Primary goals: - **First boot configures Pi** — the Home Manager activation that runs at boot reconciles the machine's declared providers instead of refusing, so a provisioned machine has working Pi credentials before anyone logs in. - **Staging stays memory-backed** — the reconciler's rollback snapshots still never touch disk, and the directory it falls back to is measured to be tmpfs rather than assumed. - **The boot condition gets a witness** — the lifecycle check exercises reconciliation with no session runtime directory, which is the state first boot actually presents. ### Current state and specifics `modules/pi-provider-reconcile.sh:45` resolves its staging directory as `${XDG_RUNTIME_DIR:-/run/user/$(id -u)}` and exits 1 when that path is not a directory: ``` runtime_dir=${XDG_RUNTIME_DIR:-/run/user/$(id -u)} if [[ ! -d "$runtime_dir" || -L "$runtime_dir" ]]; then echo "ERROR: volatile user runtime directory is unavailable; refusing Pi provider reconciliation" >&2 exit 1 fi ``` The generated `home-manager-<user>.service` is `WantedBy=multi-user.target`, runs as the account with `User=`, and is ordered `Before=systemd-user-sessions.service`. It therefore has no PAM session, and `/run/user/<uid>` — which systemd-logind creates for a session — does not exist yet. Every first boot logs the refusal above followed by the module's own `Pi provider configuration was not reconciled; see ERROR above, then rebuild.` and leaves `auth.json` and `models.json` untouched. Nothing else on the path is at fault. The Age credential is already deployed and owned by the account when activation runs, and the joined provider catalog and credential map are correct in the generated store paths. Running the same reconciler by hand against the same inputs, differing only in that a login session exists, configures both providers. The condition is invisible to the existing check because `checks/pi-provider-lifecycle.nix:151` exports `XDG_RUNTIME_DIR` before every fixture, so no fixture ever runs without one. ### Scope In: the staging-directory selection in `modules/pi-provider-reconcile.sh`, a boot-path fixture in `checks/pi-provider-lifecycle.nix`, and the README paragraph stating when reconciliation runs. Out: credential delivery, the ownership manifest format, and the locking protocol — all measured correct here. The refusal when no tmpfs exists at all stays a loud failure with no fixture: pinning it would need an override that lets a caller aim staging elsewhere, which is exactly the bypass this script should not ship.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
allod/archetypes#50
No description provided.