PodmanService gains secret_env (env name + secret name under <kv>/<prefix>/<deployment>/); the agent resolves references at apply time through harmony_secret's OpenbaoSecretStore, reusing the NATS path's ZitadelJwtBearer — one machine identity for both consumers. Resolution failure surfaces as Phase::Failed naming the denied fetch. - OpenbaoSecretStore::with_zitadel_bearer: no token-file cache; login is lazy and get_raw retries once through refresh_auth on permission denied (batch tokens expire without renewal; re-login IS the sync) - agent [openbao] config section (url + secret_prefix; mounts/role default to the staging shape) - FleetDeviceSetupScore renders the [openbao] block when configured - resolution binds at apply, never on the periodic tick, keeping the secret store off the 30s hot path (fleet-scale login load is the ADR-025 constraint)
example_iot_vm_setup
End-to-end driver for the IoT walking-skeleton VM-as-device flow. Runs two Harmony Scores in sequence:
KvmVmScore— provision a libvirt VM from an Ubuntu 24.04 cloud image with a cloud-init seed ISO that authorizes one SSH key. Returns the booted VM's IP.FleetDeviceSetupScore— SSH into the VM (via the Ansible-backedHostConfigurationProvider) and install podman + thefleet-agentbinary, drop the TOML config, bring up the systemd unit.
After a successful run, the VM is a fleet member reporting to NATS under
the --device-id you chose, carrying the --group label you passed.
One-time setup
WORK=/var/tmp/harmony-iot-smoke
mkdir -p "$WORK/ssh"
# 1. Ubuntu 24.04 cloud image (~700 MB) — cached between runs.
curl -o "$WORK/ubuntu-24.04-server-cloudimg-amd64.img" \
https://cloud-images.ubuntu.com/releases/24.04/release/ubuntu-24.04-server-cloudimg-amd64.img
# 2. SSH keypair the VM will trust.
ssh-keygen -t ed25519 -N '' -f "$WORK/ssh/id_ed25519"
# 3. Runtime deps — Harmony self-installs Ansible into a managed venv
# under $HARMONY_DATA_DIR/ansible-venv on first run, so you only need
# python3 + venv on the runner. No system-wide `ansible` needed.
# On Arch:
# sudo pacman -S libvirt qemu-full xorriso python
# On Debian/Ubuntu:
# sudo apt install libvirt-daemon-system qemu-kvm xorriso python3 python3-venv
# 4. libvirt default network.
sudo virsh net-start default
sudo virsh net-autostart default
Run
cargo build -p fleet-agent-v0
cargo run -p example_iot_vm_setup -- \
--base-image /var/tmp/harmony-iot-smoke/ubuntu-24.04-server-cloudimg-amd64.img \
--ssh-pubkey /var/tmp/harmony-iot-smoke/ssh/id_ed25519.pub \
--ssh-privkey /var/tmp/harmony-iot-smoke/ssh/id_ed25519 \
--work-dir /var/tmp/harmony-iot-smoke \
--agent-binary target/debug/fleet-agent-v0 \
--nats-url nats://192.168.122.1:4222
Changing groups
Re-running with a different --group rewrites
/etc/fleet-agent/config.toml on the VM and restarts the agent. The VM
itself is untouched.
cargo run -p example_iot_vm_setup -- ... --group group-b
Full end-to-end via smoke test
See fleet/scripts/smoke-a3.sh — stands up NATS in a podman container,
runs this example, asserts the agent's status lands in NATS.