feat/prepare-rpi #280

Merged
stremblay merged 14 commits from feat/prepare-rpi into feat/iot-walking-skeleton 2026-05-04 17:28:45 +00:00

14 Commits

Author SHA1 Message Date
b86f8f11f9 feat: add little script to call the fleet_rpi_setup example
Some checks failed
Run Check Script / check (pull_request) Failing after -44h57m30s
2026-05-01 14:51:03 -04:00
34e2f832ec docs(linux): clarify sudo_password scope, TODO for SSH password auth
The new sudo_password field is strictly for privilege escalation on
the remote host (sudo -S, ansible become) — not for SSH login. SSH
auth is still key-only. Adds a TODO on SshCredentials pointing at
where SSH password support would land if/when we want it, and a
matching note on the SudoPassword Secret type.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 14:09:38 -04:00
be9073e461 feat(examples/fleet_rpi_setup): auto-fetch sudo password via SecretManager
Probe `sudo -n true` over SSH before constructing the topology. If
the probe succeeds (passwordless sudo, the typical rpi-imager
default), proceed silently. If it fails, fetch the password through
SecretManager::get_or_prompt::<SudoPassword>() — first run prompts
the operator, subsequent runs reuse the cached value (same flow
SshKeyPair etc. use).

Adds harmony_secret dep, env.sh with the standard
HARMONY_SECRET_NAMESPACE / HARMONY_SECRET_STORE / HARMONY_DATABASE_URL
/ RUST_LOG variables, and a doc snippet at the top of main.rs
pointing at it.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 13:57:00 -04:00
c2b8403f14 feat(linux): support optional sudo_password on SshCredentials
Lets callers populate creds.sudo_password when the bootstrap admin
doesn't have passwordless sudo. None = current behavior unchanged.

Wire-level injection:
- ansible runs: when Some, write to a tempfile::NamedTempFile and
  pass ANSIBLE_BECOME_PASSWORD_FILE=<path> via Command::env. Path
  in env, never value in argv. File deletes on drop.
- direct ssh_exec sudo paths (ensure_linger, ensure_user_unit_active,
  fetch_file): new sudo_exec helper that uses `sudo -S` with the
  password piped via the new ssh_exec stdin parameter, otherwise
  plain sudo. ensure_user_unit_active's && chain folded into one
  sudo+sh -c call since `sudo -S` only reads stdin once.

ssh_executor.rs: ssh_exec gains an optional stdin: Option<&str>; on
Some, writes via channel.data() then channel.eof() so the remote
reader doesn't hang. Existing 4 call sites pass None.

fleet_vm_setup updated to set sudo_password: None (behavior
identical).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 13:56:50 -04:00
414fe1cf7b feat(secret): add SudoPassword Secret type
Sudo password for a Linux bootstrap admin user. Stored under key
"SudoPassword" via SecretManager when a host doesn't have
passwordless sudo configured. Same shape as the other single-field
Secret types in this file.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 13:56:35 -04:00
413077bcd0 feat(fleet): pre-flight config diff + confirm in FleetDeviceSetupScore
New first step (1/7): read /etc/fleet-agent/config.toml off the
device and compare against the rendered desired config. Three
branches:

  - missing  → info, first install
  - matches  → warn, converge anyway
  - differs  → warn + unified diff (similar::TextDiff with 2-line
    context radius, '-/+' marker style) + inquire::Confirm prompt
    defaulting to N. Aborts with InterpretError if declined.

Existing 6 steps renumbered to 2/7-7/7. The diff replaces the
previous "dump both full configs" approach which was unreadable
even for one-line differences.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 13:11:10 -04:00
54d1f0733b feat(linux): add FileFetcher capability for reading remote files
Mirrors FileDelivery in the opposite direction: returns Some(content)
or None if the file doesn't exist. AnsibleHostConfigurator implements
it via two SSH calls (sudo test -e + sudo cat), routed through sudo
to handle root- or service-owned config files. Added to the
LinuxHostConfiguration umbrella so any score with that bound gets it.

Enables scores to pre-flight-compare desired state against current
state before committing to a destructive change.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 13:11:00 -04:00
12249a7f88 refactor(fleet): inline agent binary local->remote on one recap line
Folds the "-> /usr/local/bin/fleet-agent" continuation into the
"Agent binary:" line. Removes the hardcoded-indent fragility (bullet
prefix shifts in cli_reporter would have broken alignment).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 12:48:37 -04:00
3cbe62c807 fix(cli): surface Outcome.details on NOOP outcomes too
cli_reporter only accumulated details for SUCCESS, dropping the
recap on idempotent re-runs that legitimately return NOOP with
populated details. FleetDeviceSetupScore is the first score to
exercise this path; the filter was over-restrictive.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 12:48:32 -04:00
96f17f3ca0 refactor(examples/fleet_rpi_setup): adopt harmony_cli::run convention
Drop the bespoke framed renderer, failure hint catalog, and custom
env_logger setup. Score output now flows through harmony_cli's
standard reporter (bullet list under "🚀 All done!"), matching the
other examples. cli_logger::init() at the top of main so early
logs (ensure_ansible_venv) get the same formatting.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 12:38:39 -04:00
c3b25c8298 feat(fleet): structured progress + recap for FleetDeviceSetupScore
Replace the opaque change-log with tagged per-step info traces and
a human-readable Outcome.details recap (Device ID / NATS / Labels /
User / Agent binary -> remote / Service). User and Service lines
carry their own /🔄 state markers; final line is  for noop and
🎉 for runs that applied changes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 12:38:33 -04:00
bf1a438b90 fix(linux): drop redundant envelope from parseable ansible errors
When stdout already parses into UNREACHABLE!/FAILED! + msg, the
trailing (ansible-exit=..., stderr=..., stdout=...) envelope just
duplicated the same text. Strip it when stderr is empty and the
verb is recognized; keep it when it adds debug signal.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 12:38:27 -04:00
2cb884976a feat(examples): add fleet_rpi_setup for onboarding a physical Pi
Sibling of fleet_vm_setup with the libvirt provisioning step removed:
the operator has already booted Pi OS Lite themselves (rpi-imager,
preloaded SSH key, passwordless sudo on the admin user), so the
example goes straight to applying FleetDeviceSetupScore over SSH.

Defaults match the typical rpi-imager flow (--pi-user pi,
--ssh-key ~/.ssh/id_ed25519); --ssh-key supports tilde expansion.
The harmony dep is pulled in without the kvm feature since no VM is
created here. RUST_LOG defaults to info so the score's per-step
traces show up without the operator having to set the env var.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 11:41:05 -04:00
97ec4848fd fix(linux): make ensure_user_unit_active actually report NOOP
systemctl --user enable --now is systemd-level idempotent, but the
prior implementation always returned ChangeReport::CHANGED. This made
every re-run of any score that touches a user-scoped unit (notably
FleetDeviceSetupScore's podman.socket step) lie about its change
count, defeating the noop detection the rest of the score honors.

Probe is-enabled --quiet && is-active --quiet first; only call
enable --now (and report CHANGED) when the unit isn't already in the
desired state. Mirrors the existing ensure_linger pattern in the
same file.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 11:39:11 -04:00