Files
harmony/examples/iot_vm_setup/Cargo.toml
Jean-Gabriel Gill-Couture 1072aa2850 refactor(iot): address code review — ISP, VirtualMachineHost, cleanups
Structural changes (the biggest items from the review):

- `HostConfigurationProvider` split into five narrower capabilities:
  `HostReachable`, `PackageInstaller`, `FileDelivery`,
  `UnixUserManager`, `SystemdManager`. Each implementation now only
  implements what it can actually deliver — a future cloud-init /
  ignition / podman-agent backend can pick a subset without
  inheriting systemd assumptions it can't honour. Added an umbrella
  trait `LinuxHostConfiguration` blanket-impl'd for any type that
  has all five, so Scores keep a single bound.

- New `VirtualMachineHost` capability in domain/topology/: `list_vms`
  / `ensure_vm` / `delete_vm` / `get_vm_info`, with generic
  `VirtualMachineSpec` carrying a typed optional `VmFirstBootConfig`
  (hostname, admin user, authorized keys). `KvmHost` trait and
  `KvmHostTopology` deleted; `KvmVirtualMachineHost` is the
  concrete libvirt implementation. Cloud-init stays a KVM-impl
  detail — callers never see it.

- `KvmVmScore` + `CloudInitVmConfig` deleted; replaced by a generic
  `ProvisionVmScore` in `modules::iot::vm_score` bound to
  `T: VirtualMachineHost`. The Score itself has no knowledge of the
  hypervisor or its first-boot delivery mechanism.

- `IotDeviceSetupConfig.device_id` is now `harmony_types:🆔:Id`
  (timestamp-prefixed, sortable-by-creation, collision-safe).

- `ensure_ready` on `KvmVirtualMachineHost` is a Noop with a TODO
  pointing at ROADMAP/12-code-review-april-2026.md §12.1 (phased
  topology). Captures the concern about eagerly probing the
  hypervisor even when the current run doesn't need KVM.

Code quality fixes from the line-level comments:

- `render_toml` / `render_systemd_unit` / `render_user_data`
  rewritten as `format!` with raw-string templates (no more
  push_str chains).

- Every `Command::new(…).arg().arg().arg()` chain in the touched
  files converted to `.args([…])`.

- Ansible module args are now typed Rust structs (`AptArgs`,
  `AnsibleFileArgs`, `AnsibleUserArgs`, `AnsibleCopyArgs`,
  `AnsibleSystemdArgs`, `AnsibleCommandArgs`, `AnsibleStatArgs`)
  serialized via `serde_json::to_value`. No more `json!` macros
  with ad-hoc string keys.

- `ensure_linger`: no more shell sentinel. Uses
  `ansible.builtin.stat` on `/var/lib/systemd/linger/<user>` for
  the idempotent change-state check, then `ansible.builtin.command
  loginctl enable-linger` only on miss. `loginctl` is required
  (not just `file state=touch`) because systemd-logind needs the
  dbus signal to actually start the user manager; a plain file
  touch doesn't wake it up and every subsequent `systemctl --user
  …` fails with "Failed to connect to bus". Documented in-place.

- `ensure_user_unit_active`: picks up the user's UID first via
  `ansible.builtin.command id -u <user>` and wraps the
  `systemctl --user enable --now <unit>` invocation in `env
  XDG_RUNTIME_DIR=/run/user/<UID>`. The systemd module's
  task-level `environment:` keyword isn't available in ad-hoc
  mode; this is the cleanest equivalent. Documented the
  inline-playbook path as a future when we get more task-level-
  env callsites.

- `ensure_package` comment clarified: distro dispatch is this
  function's job; Debian-family is the first concrete target and
  extending to RHEL/Fedora/Alpine is an implementation detail,
  not a capability change.

- Kubespray line removed.

Verified: from a primed `$HARMONY_DATA_DIR/iot/`, smoke-a3.sh
still completes all 5 phases (bootstrap + provision + 9 setup
changes + initial NATS status + power-cycle recovery).
2026-04-20 17:58:42 -04:00

19 lines
399 B
TOML

[package]
name = "example_iot_vm_setup"
version.workspace = true
edition = "2024"
license.workspace = true
[[bin]]
name = "iot_vm_setup"
path = "src/main.rs"
[dependencies]
harmony = { path = "../../harmony", features = ["kvm"] }
harmony_types = { path = "../../harmony_types" }
tokio.workspace = true
log.workspace = true
env_logger.workspace = true
anyhow.workspace = true
clap.workspace = true