Push-based fleet Ansible: control = wherever Kai is #333

Closed
opened 2026-06-15 13:33:00 +00:00 by coilysiren · 1 comment
Owner

Decision

Move fleet Ansible off local-only execution. Today infrastructure/ansible runs ansible_connection: local against a bare localhost inventory, so converging the fleet means hand-running sync on every box — 6 physical hosts / 8 OS installs (Windows machines via WSL). That toll is the actual problem; the old "agents shouldn't SSH, so Ansible stays local" rule was conflating two separable things.

Decoupling: the agent-SSH-deny boundary already exists in the lockdown / cli-guard layer and needs no new work. Kai running a sync is an operator action, hands-on-keyboard. So Ansible's transport (push over SSH) and the agent's SSH denial are independent — nothing new has to enforce the split.

There is no ward component here. ward's only involvement is that the existing ward exec <verb> runs the sync Makefile target, exposed via a line in this repo's .ward/ward.yaml — the same path as build / test / lint. No new ward verb, no ward-side SSH policy.

Model

  • Control node = wherever Kai currently is. No designated always-on orchestrator. From whatever machine she's at, push to the targets.
  • Push targets = the always-on, headless boxes Kai is rarely physically at: kai-server, ser8, the desktop tower. They already run sshd, so push adds zero new inbound surface, and "on for long stretches" makes them reliably convergeable.
  • Daily-drivers stay local-run (or ansible-pull on login) — the macbook, the Windows laptop/WSL. Local-run is cheap there because Kai is already at the keyboard, and keeping them off the target list avoids enabling fresh inbound sshd surface.
  • Transport: tailnet-only, key-only, no WAN ingress. Uptime is not the security lever; transport scope is. On the tailnet, an unattended-while-away box is exposed to Kai's own tailnet, not the internet.
  • Local-run demotes to the bootstrap/fallback rung (new host with no keys yet; the control node converging itself), not the steady state.

Work

  • Build a real fleet inventory + a push-capable sync play (replaces bare localhost / local.yml).
  • Expose the sync target in .ward/ward.yaml so it runs via ward exec sync (config only, no ward code).
  • Handle Windows-native targets (push over SSH into WSL is fine; native Windows wants WinRM or SSH-into-WSL — likely keep those local-run / pull).
  • Bootstrap ordering vs the egress-deny in #332 (deny applied late enough not to brick provisioning; control node allowlisted).
  • #332 — deny outbound SSH on the autonomous-agent hosts. Compatible: push needs inbound SSH to targets; losing outbound on kai-server/ser8 doesn't block them being push targets.
  • Supersedes coilyco-flight-deck/ward#89 (closed — misframed as a ward feature).
## Decision Move fleet Ansible off local-only execution. Today `infrastructure/ansible` runs `ansible_connection: local` against a bare `localhost` inventory, so converging the fleet means hand-running sync on every box — 6 physical hosts / 8 OS installs (Windows machines via WSL). That toll is the actual problem; the old "agents shouldn't SSH, so Ansible stays local" rule was conflating two separable things. **Decoupling:** the agent-SSH-deny boundary already exists in the lockdown / cli-guard layer and needs no new work. Kai running a sync is an operator action, hands-on-keyboard. So Ansible's transport (push over SSH) and the agent's SSH denial are independent — nothing new has to enforce the split. There is **no ward component** here. ward's only involvement is that the existing `ward exec <verb>` runs the sync Makefile target, exposed via a line in this repo's `.ward/ward.yaml` — the same path as `build` / `test` / `lint`. No new ward verb, no ward-side SSH policy. ## Model - **Control node = wherever Kai currently is.** No designated always-on orchestrator. From whatever machine she's at, push to the targets. - **Push targets = the always-on, headless boxes** Kai is rarely physically at: `kai-server`, `ser8`, the desktop tower. They already run `sshd`, so push adds zero new inbound surface, and "on for long stretches" makes them reliably convergeable. - **Daily-drivers stay local-run** (or `ansible-pull` on login) — the macbook, the Windows laptop/WSL. Local-run is cheap there because Kai is already at the keyboard, and keeping them off the target list avoids enabling fresh inbound `sshd` surface. - **Transport: tailnet-only, key-only, no WAN ingress.** Uptime is not the security lever; transport scope is. On the tailnet, an unattended-while-away box is exposed to Kai's own tailnet, not the internet. - **Local-run demotes to the bootstrap/fallback rung** (new host with no keys yet; the control node converging itself), not the steady state. ## Work - [ ] Build a real fleet inventory + a push-capable sync play (replaces bare `localhost` / `local.yml`). - [ ] Expose the sync target in `.ward/ward.yaml` so it runs via `ward exec sync` (config only, no ward code). - [ ] Handle Windows-native targets (push over SSH into WSL is fine; native Windows wants WinRM or SSH-into-WSL — likely keep those local-run / pull). - [ ] Bootstrap ordering vs the egress-deny in #332 (deny applied late enough not to brick provisioning; control node allowlisted). ## Related - #332 — deny *outbound* SSH on the autonomous-agent hosts. Compatible: push needs *inbound* SSH to targets; losing outbound on kai-server/ser8 doesn't block them being push targets. - Supersedes coilyco-flight-deck/ward#89 (closed — misframed as a ward feature).
Author
Owner

Backlog burndown 2026-06-17: closing low-priority (P3/P4) to bring the open count to a manageable level. Nothing lost — reopen if this resurfaces. Batch tag: burndown-2026-06.

Backlog burndown 2026-06-17: closing low-priority (P3/P4) to bring the open count to a manageable level. Nothing lost — reopen if this resurfaces. Batch tag: `burndown-2026-06`.
coilysiren 2026-06-17 08:22:30 +00:00
Sign in to join this conversation.
No description provided.