mirror of
https://github.com/fscotto/infra.git
synced 2026-09-27 19:03:47 +00:00
* feat(aegis): add ARM image bootstrap workflow * feat(aegis): manage firewall and SSH hardening * feat(aegis): harden Fedora IoT services * chore(aegis): remove local TLS configuration * fix(aegis): restore supported Fedora IoT bootstrap
172 lines
14 KiB
Markdown
172 lines
14 KiB
Markdown
# AGENTS.md
|
|
|
|
Ansible-driven personal infrastructure repo for Fedora and Void desktops, Fedora IoT, WSL, and servers.
|
|
|
|
## Source Of Truth
|
|
- Main orchestration: `ansible/site.yml`
|
|
- Inventory and layering inputs: `ansible/inventory/hosts.yml`, `ansible/inventory/group_vars/*.yml`, `ansible/inventory/host_vars/*.yml`
|
|
- Dotfiles live under `dotfiles/`
|
|
- AI agent instructions (bootstrap, rules, knowledge) are centralized in `dotfiles/common/.config/ai/` and shared between OpenCode, Codex, and Gemini CLI.
|
|
- OpenCode loads its entrypoint configuration from `dotfiles/common/.config/opencode/opencode.json`.
|
|
- Codex config is rendered from `dotfiles/common/.codex/config.toml.j2` so `model_instructions_file` points to the deployed `~/.config/ai/bootstrap.md`.
|
|
|
|
## Topology
|
|
- Current personal desktop: `ikaros = platform_fedora + role_personal_workstation + graphical_desktop + desktop_gnome`
|
|
- Current laptop: `nymph = platform_fedora + graphical_desktop + desktop_gnome`
|
|
- Void desktop profile is also the base for other future/reference hosts via `platform_void + graphical_desktop`
|
|
- Workstation: `deadalus` is Windows + Fedora WSL.
|
|
- Ubuntu server: `prometheus`
|
|
- NAS: `atlas` (Rocky Linux 9, reached through SSH)
|
|
- Always-on LAN node: `aegis` (Fedora IoT on Raspberry Pi 4, reached through SSH)
|
|
- Hosts intentionally belong to multiple groups; trust `ansible/site.yml` over hostname assumptions.
|
|
- Inventory axes are independent: `platform_*`, `role_*`, and `desktop_*`. Legacy `void` and `desktop` remain compatibility parents.
|
|
|
|
## Working Rules
|
|
- Preserve layering `all -> platform -> role -> desktop -> host`.
|
|
- Keep `ansible/site.yml` small; orchestration belongs there, implementation belongs in roles.
|
|
- Prefer minimal, targeted edits. Preserve idempotency and existing ordering.
|
|
- Use Git Flow branch prefixes: `feature/` for new functionality, `bugfix/` for non-urgent fixes,
|
|
`hotfix/` for urgent production fixes, `release/` for release preparation, and `support/` for
|
|
maintained release lines. Do not use abbreviated prefixes such as `feat/`.
|
|
- Desktop and WSL hosts use `ansible_connection: local`; remote infrastructure hosts use SSH.
|
|
- Treat `secrets/` as sensitive. Never print secret values.
|
|
- Tmux plugins are bootstrapped by TPM on the host; the repo only keeps tmux config and custom helper scripts.
|
|
- Read the relevant role tasks, templates, vars, and deployed dotfiles before editing.
|
|
|
|
## Validation
|
|
- Default minimum:
|
|
- `ansible-playbook ansible/site.yml --syntax-check`
|
|
- Repo-wide checks:
|
|
- `ansible-lint ansible/site.yml`
|
|
- `ansible-lint ansible/roles`
|
|
- `yamllint ansible/`
|
|
- Host-focused dry runs:
|
|
- Fedora desktop work: `ansible-playbook ansible/site.yml --limit ikaros --check --diff`
|
|
- Fedora laptop work: `ansible-playbook ansible/site.yml --limit nymph --check --diff`
|
|
- WSL workstation dev: `ansible-playbook ansible/site.yml --limit deadalus --check --diff`
|
|
- Server: `ansible-playbook ansible/site.yml --limit prometheus --check --diff`
|
|
- Atlas NAS: `ansible-playbook ansible/site.yml --limit atlas --check --diff`
|
|
- Aegis IoT: `ansible-playbook ansible/site.yml --limit aegis --check --diff`
|
|
- Focused checks:
|
|
- Emacs is disabled by default; temporary Emacs check: `ansible-playbook ansible/site.yml --limit <host> --tags emacs --check --diff -e emacs_enabled=true`
|
|
- AI coding agents: `ansible-playbook ansible/site.yml --limit <host> --tags ai_agents --check --diff`
|
|
- Mail bootstrap: `sh -n scripts/bootstrap_mail.sh` and `shellcheck scripts/bootstrap_mail.sh`
|
|
- Server compose render: `docker compose -f /opt/docker/server/docker-compose.yml config`
|
|
|
|
## Conventions
|
|
- Use FQCN Ansible modules.
|
|
- Prefer declarative modules over `command`/`shell`; when `shell` is required, make idempotency and failure behavior explicit.
|
|
- Start YAML files with `---`, use 2-space indentation, and keep file modes quoted like `"0644"`.
|
|
- Keep booleans as booleans and structured vars as YAML lists/maps.
|
|
- Put host-specific overrides in `host_vars`, not shared `group_vars`.
|
|
- Use `no_log: true` for secret-bearing task inputs or outputs.
|
|
|
|
## Desktop Notes
|
|
- `desktop_profile` names independently selectable desktop groups such as `desktop_gnome`, `desktop_sway`, and `desktop_niri`. Keep platform-specific session bootstrap in platform-specific roles.
|
|
- `desktop_environment` is fixed to `minimal` for Void desktops. `profile_desktop_common` owns shared Void bootstrap; `profile_desktop_sway` and `profile_desktop_niri` manage the enabled sessions, while `profile_desktop_gnome` copies shared desktop dotfiles for Fedora/GNOME without managing GNOME settings. `desktop_sessions_enabled` and `desktop_default_session` apply to the minimal mode.
|
|
- Emacs has one authoring-oriented `.emacs.d`, deployed by `dotfiles_common` when `emacs_enabled` is true. Fedora/GNOME desktops and workstation profiles enable it; keep platform dependencies in package group vars rather than branching in Emacs Lisp.
|
|
- NTFS filesystem support is provided by `ntfs-3g` in `ansible/inventory/group_vars/void.yml`.
|
|
- Void user services are managed by `turnstile` and live under `dotfiles/desktop/.config/service/`.
|
|
- `ssh-agent` keeps the stable socket `~/.local/state/ssh-agent/socket`.
|
|
- Critical session entrypoints:
|
|
- `dotfiles/desktop/.config/sway/config` plus `host.conf` and `session-env` deployed via `host_sway_dotfiles` (sway / Wayland)
|
|
- `dotfiles/desktop/.config/niri/config.kdl` and `session-env` deployed via `desktop_niri_dotfiles` (Niri / Wayland)
|
|
- Void Niri lives in `profile_desktop_niri`, gated on `'niri' in desktop_sessions_enabled`; it installs the `emptty` `niri.desktop` session, the `/usr/local/bin/start-niri` launcher, and the xdg-desktop-portal config, mirroring `profile_desktop_sway`.
|
|
- Fedora GNOME (`desktop_gnome`) assumes GNOME comes from the Fedora Workstation base install; Ansible deploys shared desktop dotfiles and git/GPG config for `ikaros` and `nymph`, not GNOME settings.
|
|
- Do not switch or restart the display manager during a playbook run from an active graphical session.
|
|
- `nymph` is the Fedora/GNOME laptop target; keep GNOME settings unmanaged for now and add host-specific tuning only after real use.
|
|
|
|
## Void Package And Dotfile Bucket Rules
|
|
`platform_void` is the reusable Void platform selection. The legacy `void` group remains a compatibility parent so existing `group_vars/void.yml` and `when: "'void' in group_names"` checks keep working during the transition.
|
|
The Void desktop package lists in `ansible/inventory/group_vars/void.yml` are kept disjoint by role:
|
|
- `void_packages_base` — system runtime only (init/services, kernel, audio core, networking, filesystem, firewall, hardware daemons, runit logging).
|
|
- `desktop_common_packages` — GUI infrastructure shared by the minimal desktop mode.
|
|
- `desktop_minimal_packages` — applications, integration components, and the `emptty` display manager.
|
|
- `desktop_sway_packages` — binaries specific to the Sway session.
|
|
`profile_packages` in the same file is cross-distro and is overridden by `group_vars/server.yml` and the workstation group vars; do not move desktop-specific Void entries through it.
|
|
The dotfile vars follow the same split: `desktop_common_dotfiles` carries mode-independent content and `desktop_minimal_dotfiles` carries Thunar, Udiskie, and MIME defaults. `desktop_void_dotfiles` remains reserved for files that need the Void runtime.
|
|
|
|
## Workstation Notes
|
|
- `deadalus` is modeled as Windows + Fedora WSL and is the sole workstation target.
|
|
- Fedora WSL belongs to `platform_fedora`, `workstation_dev_fedora`, and the shared WSL layer. It must not receive Flatpak or Snap runtimes.
|
|
- Fedora WSL installs Mise from the official `jdxcode/mise` COPR and uses its pinned Temurin Java 11 JDK; update the declared Mise version deliberately.
|
|
- Windows applications are installed manually and are not managed from the WSL profile.
|
|
|
|
## Atlas NAS Notes
|
|
- `atlas` is a remote Rocky Linux 9 NAS. Keep its connection, LAN, pool and mountpoint values in
|
|
`host_vars/atlas.yml`. Bootstrap it once with `-e atlas_connection_username=<existing-admin>`;
|
|
subsequent runs use the dedicated Atlas account.
|
|
- The pool is pre-existing: never add pool creation, disk partitioning, RAIDZ creation, rollback,
|
|
or destruction to the Atlas profile.
|
|
- `atlas_manage_storage` and `atlas_manage_firewall` remain false until their placeholders are
|
|
replaced; only then may the profile manage datasets, shares and LAN-restricted firewall rules.
|
|
- Atlas requires `vault_atlas_authorized_ssh_keys`, `vault_atlas_admin_password_hash` for Cockpit
|
|
and, when storage is enabled, `vault_atlas_samba_password`. Never print these values.
|
|
- Atlas uses NFSv4 for Linux and SMB for Windows/WSL, restricted to the configured LAN. Snapshot,
|
|
Borg/Hetzner offsite backup, Prometheus pull and USB backup automation are intentionally deferred.
|
|
|
|
## Atlas NAS TODO
|
|
- Replace every Atlas `CHANGEME` value, provide the required Vault variables and validate the first
|
|
remote bootstrap on the real Rocky Linux 9 host. Enable `atlas_manage_storage` first and
|
|
`atlas_manage_firewall` only after confirming the pool, mountpoints, LAN subnet and firewalld zone.
|
|
- Validate the complete baseline on the target: OpenZFS kmod loading, existing pool import, dataset
|
|
mounts, SSH reconnect, Cockpit and all selected 45Drives plugins, NFSv4, SMB and Syncthing.
|
|
- Finalize dataset properties and the shared UID/GID, group and POSIX ACL model; test the same files
|
|
through both NFS and SMB before considering multiprotocol access complete.
|
|
- Add Ansible-managed ZFS snapshot retention and scrub timers. Use Cockpit Scheduler for visibility
|
|
or manual operations, not as the only source of configuration, and never automate snapshot rollback.
|
|
- Manage the Syncthing star topology, device IDs, folders, folder modes, ignore rules and protected GUI
|
|
or API access for the selected clients.
|
|
- Add Tailscale or WireGuard and corresponding LAN/VPN-only firewalld rules before enabling remote
|
|
services; never expose SSH, Cockpit, NFS, SMB or Syncthing through public port forwarding.
|
|
- Add the least-privilege Prometheus backup flow: remote dump generation, dedicated SSH identity,
|
|
pinned host key, atomic pull, verification, retention and an Atlas systemd service/timer.
|
|
- Add the encrypted offsite backup with Borg to a Hetzner Storage Box: use a dedicated SSH identity,
|
|
pin the host key, keep Borg repository credentials and encryption material in Vault, use
|
|
snapshot-consistent sources, and manage retries, logging, pruning, repository checks and restores.
|
|
- Add the UUID-bound offline USB backup with versioned rsync, locking, capacity checks, verification,
|
|
safe unmounting and a tested restore procedure; never trigger it for an arbitrary USB disk.
|
|
- Add monitoring and alerting for pool health, scrub/resilver, SMART data, temperatures, free space and
|
|
failed backup timers, plus a controlled Rocky kernel/OpenZFS update and reboot procedure.
|
|
- Document and test disaster recovery: rebuild Atlas with Ansible, import the existing pool, restore
|
|
from snapshot/USB/Hetzner, preserve Vault and Borg recovery material offline, and define RPO/RTO.
|
|
- Optionally design iCloud photo ingestion as a separate workflow after the storage and backup layers
|
|
are validated; do not make it a dependency of the Atlas baseline.
|
|
|
|
## Coding Agent Notes
|
|
- Shared agent definitions and lifecycle flags live in `ai_agents` in `ansible/inventory/group_vars/all.yml`.
|
|
- Shared agent dotfiles live in `ai_agents_dotfiles`; rendered configs live in `ai_agents_templates`.
|
|
- Every `ai_agents.<agent>` entry has independent `install_enabled`, `deploy_enabled`, and `uninstall_enabled` flags. Installation and removal must not both be true for the same agent; the common pre-task fails before changes when they conflict.
|
|
- Fedora, Void desktop, and WSL workstation profiles consume the shared agent definitions; do not duplicate package entries in profile-specific vars. IBM Bob on the workstation follows its own flags.
|
|
- `dotfiles_common` deploys `ai_agents_dotfiles` and renders `ai_agents_templates` only when deployment is enabled.
|
|
- Removal is limited to the managed npm packages and `/usr/local/bin/bob`; never remove agent dotfiles, instructions, credentials, or user data.
|
|
- Keep `.config/ai/` as the common instruction source; update agent-specific entrypoints to reference it rather than duplicating instruction text.
|
|
|
|
## Tooling Notes
|
|
- Install local tooling with:
|
|
- `python3 -m pip install ansible ansible-lint yamllint shellcheck-py`
|
|
- `ansible-galaxy collection install -r ansible/collections/requirements.yml`
|
|
- Required collections currently include `ansible.posix` and `community.general`.
|
|
- `.yamllint` treats `line-length` as a warning at 120 chars and disables `document-start` and `comments-indentation`.
|
|
|
|
## When Updating Docs
|
|
- Keep `README.md` and `AGENTS.md` aligned when workflows materially change.
|
|
- If you add a new operational area, also add the narrowest validation command for it.
|
|
- Call out checks you could not run and any follow-up verification needed.
|
|
|
|
## Aegis Fedora IoT Notes
|
|
- `aegis` is a remote Fedora IoT Raspberry Pi 4 node. Bootstrap it once with
|
|
`ansible/bootstrap/aegis.bu`; the remaining configuration is applied by `profile_aegis` over SSH.
|
|
- Fedora IoT is immutable. Do not add it to mutable Fedora package or shared dotfile roles.
|
|
- `profile_aegis` owns rootful Podman Quadlets, persistent container state under `/var/lib`, the
|
|
Podman auto-update timer, LAN-restricted firewalld rules, and SSH hardening. Keep
|
|
`aegis_lan_subnet` and `aegis_adguard_web_port` host-specific; SSH permits only the declared
|
|
key-authenticated users, never root or password authentication. Keep Apple IDs and other
|
|
credentials in Vault and use `no_log` for their rendering.
|
|
- `aegis_adguard_web_port` defaults to `80`. The initial AdGuard Home wizard port `3000` is intentionally unmanaged: open and close it manually only while
|
|
completing initial setup. Disable the local systemd-resolved stub through `profile_aegis` before
|
|
AdGuard binds port 53; keep
|
|
`/etc/resolv.conf` linked to `/run/systemd/resolve/resolv.conf` so Aegis retains router-provided DNS.
|
|
- iCloudPD requires post-deployment interactive MFA initialization; its cookie/configuration state is
|
|
persisted in `/var/lib/icloudpd/config`.
|