Operate MyOS¶
This section serves two readers at once, by depth:
- The human superadmin reads the framing — what a change does, the trade-offs, and what to tell the agent. Stay at the top level of each topic.
- The AI agent sysadmin (or a hands-on operator) opens the Commands blocks for the exact, deterministic steps.
That's deliberate: the superadmin directs; the agent executes. The decisions live here once;
the commands are one click deeper, not duplicated. For a machine-readable map of this site, see
/draft/llms.txt. For the authoritative deep reference, see
Reference and the runbooks.
Authority & guardrails
The agent has broad reach but the human holds the master keys and authorizes anything hard to reverse or outward-facing: pushes to the default branch (they publish), DNS changes, exposing a service to the internet, and anything touching the identity/CA core. Work on a branch; prefer reversible changes. See Direct & Decide.
The fleet & IP map¶
Every host, its zone, and the ports that matter. Convention: the gateway is ${ip.gateway_suffix}
in every zone; servers are static, clients are DHCP. Internal names are split-horizon (resolve
only on the LAN or VPN); public names resolve via the edge.
| Host | Zone | Address | Role |
|---|---|---|---|
| Firewall (OPNsense) | mgmt | ${host.opnsense} |
per-zone gateway, VPN egress, DNS |
| Hypervisor (Proxmox) | mgmt | ${host.proxmox} |
runs the VMs/containers |
| Control node | mgmt | ${host.ctrl} |
runs Ansible; reaches the fleet via a signed cert |
| Identity (FreeIPA) | servers | ${host.freeipa} |
directory, Kerberos, CA, DNS |
| Fileserver (ZFS/NFS) | servers | ${host.fileserver} |
home + media datasets |
| Registry (zot) | servers | ${host.registry_ip} |
internal images + pull-through cache |
| SSO (Authelia) | servers | ${host.sso} |
identity provider / forward-auth |
| Observability | servers | ${host.obs} |
Grafana + metrics + logs |
| Notifications (ntfy) | servers | ${host.ntfy} |
alert + push bus |
| Origin Caddy | dmz | ${host.origin_caddy} |
TLS terminator + reverse proxy |
| Git (Forgejo) | dmz | ${host.forgejo} |
git + CI |
| Hidden DNS primary | dmz | ${host.bind_primary} |
authoritative source, AXFR to edges |
| Jump host | dmz | ${host.jump} |
SSH pivot to the fleet |
Network zones: management ${ip.mgmt}, servers ${ip.servers}, DMZ ${ip.dmz}, workstations
${ip.workstations}, IoT ${ip.iot}, guest ${ip.guest}, road-warrior VPN ${ip.wg_rw}, all
inside ${ip.space}.
The authoritative source for this table is the host/IP allocation in Design & Decisions plus the provisioning manifest — keep them in sync.
The Ansible workflow¶
The fleet is provisioned by a clone-and-cloud-init script and configured by Ansible (built-in modules only). Almost every change is: edit code → run the relevant playbook → verify → commit.
Commands — apply config to a host
Run from the control node, as the unprivileged automation user. Limit to one host while testing:
ansible-playbook playbooks/site.yml -l <host>
A coordinated, one-at-a-time OS update across the fleet:
ansible-playbook playbooks/update.yml # serial: 1
Day-2 tasks¶
Add or remove a VM¶
Decision: new service → does it need its own kernel/appliance (a VM) or is it a light single app (a container)? Either way it keeps its own IP, zone, and firewall — consolidation onto shared hosts is out, because it would collapse the trust model.
Commands — add a host
- Add a row to the provisioning manifest (
provision/vms.conf): name, ID, cores, RAM, disk, zone VLAN, IP, gateway. - Clone it:
provision/lab-provision.sh. - Add it to
ansible/inventory.iniunder a group that's part ofall_linux:children. ansible-playbook playbooks/site.yml -l <host>— the baseline + monitoring agent apply automatically; the host appears in dashboards with no edits.
Add a web service¶
Decision: first pick its exposure tier (below). Then run it as a container (quadlet) or point at an existing backend, add a vhost to the right Caddy plane, and add DNS.
Commands — expose an app
- Run the app (the
backendsrole; setbackend_appin the host's vars), or use an existing backend. - Add a vhost to the right plane:
origin_caddy(public, Tier 0/1), or the internal proxy planes for private user apps / admin UIs (Tier 2). - DNS: a public name is added on the hidden primary (
${host.bind_primary}) and AXFRs to the edges; an internal-only name is a split-horizon override on the firewall's resolver. - To gate it with SSO, set
forward_authon the vhost and add an Authelia access-control rule.
Exposure tiers (the key decision per service)¶
| Tier | Meaning | Reachable from | Trust anchor |
|---|---|---|---|
| 0 | Public, no auth | internet (via edge) | n/a (intentionally open) |
| 1 | Public + SSO | internet (via edge) | identity (Authelia) |
| 2 | Private | LAN / VPN only | the network |
Default to Tier 2. Always-on VPN on roaming devices makes private nearly free. Promote to Tier 1 only when a service must work without the VPN or is shared with non-VPN people. High-value targets (smart-home, admin UIs) stay Tier 2 regardless.
Egress control¶
Decision: everything egresses via the VPN by default. Only when a VPN exit IP gets blocked by some destination do you send that destination out the real WAN — exposing your real IP for it alone. The bypass list is empty by default; treat adding to it as a deliberate privacy trade-off.
Secrets¶
Decision: automation secrets live in the Ansible vault; human break-glass logins live in KeePass. Never paste secrets into chat or commits. Some admin passwords live in both.
Commands — read a secret
scripts/secrets.sh ls # list KeePass entries + vault vars
scripts/secrets.sh kp <entry> # reveal a login
ansible-vault view group_vars/all/vault.yml
Updates, backups, certificates, DNS, observability¶
Decisions, briefly:
- Updates: OS security patches are automatic per-host; the coordinated cross-fleet update is one box at a time. App versions (SSO, identity major versions) are upgraded deliberately.
- Backups: daily hypervisor snapshots cover the VMs; app-consistent database dumps ride along; the fileserver pool is protected by ZFS snapshots + replication to a second box.
- Certificates & SSH-CA: host/service certs come from the identity CA; the SSH-CA is hardware (YubiKey) backed and split — one CA grants access, another grants only host identity.
- DNS: the hidden primary (
${host.bind_primary}) is the source of truth; edits re-sign and AXFR to the edge secondaries automatically. Internal-only names are split-horizon on the resolver. - Observability: agents push to the observability host; a new host gets the agent automatically and shows up in dashboards. Any failed service/container pushes an alert to ntfy.
Each of these has a full runbook under Reference › Runbooks.
Direct & Decide (superadmin oversight)¶
The parts a superadmin owns rather than delegates:
- Credential custody (four tiers): automation's machine credential, the AI agent's own short-lived hardware-signed certificate, human web logins (KeePass + YubiKey), and WebAuthn. The agent never borrows automation's credential, and its standing certificate cannot root its own workstation or touch the identity core — elevation for those is just-in-time and operator-signed.
- The kill switch: the agent's access is revocable at any time — certificate expiry, a revocation list, or a live kill script. Its access is deliberate, granted, and auditable, never permanent.
- Outward-facing approvals: publishing (a push to the default branch deploys public docs), DNS, and any new internet exposure are human-authorized.
The how-to behind these lives in the SSH-CA, control-node, and agent-operating-model runbooks under Reference.