18 KiB
AGENTS.md
Guidance for coding agents working in this repository. Project type: Ansible-driven infrastructure, workstation/server provisioning, and user dotfiles.
Repository Map
- Entry playbook:
ansible/site.yml - Inventory:
ansible/inventory/hosts.yml - Group vars:
ansible/inventory/group_vars/*.yml - Host vars:
ansible/inventory/host_vars/*.yml - Shared templates:
ansible/templates/**/*.j2 - Roles:
ansible/roles/* - Role assets:
ansible/roles/*/{tasks,templates,files,handlers}/ - Dotfiles:
dotfiles/ - Scripts:
scripts/ - Secrets:
secrets/
Topology And Orchestration
- Void desktops:
ikaros,nymph - Ubuntu workstation:
deadalus-ubuntu - Fedora workstation:
deadalus-fedora - Ubuntu server:
prometheus - Workstation topology now supports explicit native Linux workstation targets for Ubuntu and Fedora, plus Windows 11 host + Ubuntu WSL dev as separate layers
- A single inventory host can intentionally participate in multiple plays by belonging to multiple groups; host identity and play layering are not 1:1
- The WSL dev environment is intended to be managed by running Ansible locally from inside the distro, while the Windows host is managed remotely via PSRP and Windows package installs default to
winget_psrp - Most hosts use
ansible_connection: local - Current playbook layering:
all:!workstation_host_windows -> dotfiles_common,void -> packages_void + services_runit + profile_desktop_common + profile_desktop_i3 + profile_desktop_sway + profile_desktop_hyprland + profile_desktop_host,workstation_dev_ubuntu -> packages_ubuntu + services_systemd + profile_workstation_dev_common,workstation_dev_fedora -> packages_fedora + services_systemd + profile_workstation_dev_common,workstation_host_linux -> profile_workstation_gnome,workstation_dev_wsl -> packages_ubuntu + services_systemd + profile_workstation_dev_common + profile_workstation_dev_wsl,workstation_host_windows -> profile_workstation_host_windows,ubuntu_server -> packages_ubuntu + services_systemd + profile_server - Present but currently unwired roles:
base,dotfiles
Local Instruction Files
Checked when this file was updated:
.cursorrules: not present.cursor/rules/: not present.github/copilot-instructions.md: not present If any of these files appear later, treat them as higher-priority repo-local instructions.
Working Principles
- Preserve the layering
all -> OS -> profile -> host - Prefer minimal, targeted edits over cleanup or refactors
- Preserve idempotency and reproducibility
- Validate on one limited host before broad rollout
- Treat
secrets/as sensitive; never print secret values - Avoid editing vendored code under
dotfiles/desktop/.tmux/plugins/unless explicitly asked - Keep
ansible/site.ymlsmall; orchestration belongs there, implementation belongs in roles - Read the relevant inventory, vars, role tasks, templates, files, handlers, and dotfiles before editing
Build, Lint, And Test Commands
There is no compile/build pipeline. Confidence comes from syntax checks, dry runs, linting, and targeted shell validation.
Install tooling if needed:
python3 -m pip install ansible ansible-lint yamllint shellcheck-py
ansible-galaxy collection install -r ansible/collections/requirements.yml
Vault handling:
secrets/vault.ymlis the shared encrypted vars filesecrets/vault.local.ymlis an optional machine-local encrypted override file and should stay untrackedsecrets/.vault_pass.gpgis the preferred optional local vault password file;scripts/vault_password_client.shdecrypts it withgpgsecrets/.vault_passremains supported as a legacy local fallback if.vault_pass.gpgis absent- if neither local file exists, Ansible falls back to an interactive prompt via
scripts/vault_password_client.sh
Core validation from the repo root:
ansible-playbook ansible/site.yml --syntax-check
ansible-playbook ansible/site.yml --limit ikaros --check --diff
ansible-playbook ansible/site.yml --limit nymph --check --diff
ansible-playbook ansible/site.yml --limit deadalus-ubuntu --check --diff
ansible-playbook ansible/site.yml --limit deadalus-fedora --check --diff
ansible-playbook ansible/site.yml --limit deadalus-wsl --check --diff
ansible-playbook ansible/site.yml --limit prometheus --check --diff
ansible-lint ansible/site.yml
ansible-lint ansible/roles
yamllint ansible/
Useful execution commands:
ansible-playbook ansible/site.yml
ansible-playbook ansible/site.yml --limit ikaros
ansible-playbook ansible/site.yml --limit nymph
ansible-playbook ansible/site.yml --limit deadalus-ubuntu
ansible-playbook ansible/site.yml --limit deadalus-fedora
ansible-playbook ansible/site.yml --limit deadalus-wsl
ansible-playbook ansible/site.yml --limit prometheus
scripts/bootstrap_mail.sh
pwsh -File scripts/bootstrap_windows_workstation.ps1
Single-Test Equivalents
Use the narrowest command matching the changed area.
- Playbook syntax only:
ansible-playbook ansible/site.yml --syntax-check - Single host dry run:
ansible-playbook ansible/site.yml --limit <host> --check --diff - Single host, selected tags:
ansible-playbook ansible/site.yml --limit <host> --tags <tag1>,<tag2> --check --diff - Single task restart point:
ansible-playbook ansible/site.yml --limit <host> --start-at-task "<task name>" --check --diff - Single role lint:
ansible-lint ansible/roles/<role> - Single YAML file lint:
yamllint ansible/path/to/file.yml - Waybar config validation:
python3 -m json.tool dotfiles/desktop/.config/waybar/config-sway.jsonc >/dev/nullandpython3 -m json.tool dotfiles/desktop/.config/waybar/config-hyprland.jsonc >/dev/null - Script syntax/lint:
sh -n scripts/bootstrap_mail.shandshellcheck scripts/bootstrap_mail.sh - Windows bootstrap script parse check:
pwsh -NoProfile -Command "[void][System.Management.Automation.Language.Parser]::ParseFile('scripts/bootstrap_windows_workstation.ps1', [ref]$null, [ref]$null)"
Code Style Guidelines
General
- Keep orchestration in playbooks and implementation in roles
- Prefer declarative modules over imperative shell commands
- Make state transitions explicit
- Avoid unrelated refactors in the same change
- Keep comments sparse and only for non-obvious behavior
YAML And Formatting
- Start YAML files with
--- - Use 2-space indentation; never use tabs
- Keep existing ordering stable when editing lists and maps
- Quote file modes as strings such as
"0644","0755","0600","0700" - Avoid formatting-only churn in untouched sections
Modules, Imports, And Task Structure
- Use FQCN module names such as
ansible.builtin.copy,ansible.builtin.template,ansible.windows.win_powershell, andcommunity.general.xbps - Prefer dedicated modules over
ansible.builtin.commandoransible.builtin.shell - Use
commandonly when no module fits; useshellonly when shell features are required - When using
commandorshell, setchanged_whenandfailed_whenwhen defaults are misleading - Prefer
ansible.builtin.import_tasksfor static task composition andinclude_tasksonly for dynamic or conditional includes - Keep task names imperative, descriptive, and stable enough for
--start-at-task - Tag tasks consistently with existing families such as
packages,services,dotfiles,gnome,wsl,vscode,nvidia, anddotfiles:* - Prefer
loopwithloop_control.labelfor multi-item tasks - Use handlers for service restarts when configuration changes should trigger them
Variables, Types, And Naming
- Use
snake_casefor vars, facts, registered values, and custom facts - Follow existing families such as
common_packages,profile_packages,host_packages,desktop_common_packages,enabled_services,host_enabled_services,windows_winget_packages, andwindows_vscode_extensions - Keep booleans as booleans, not quoted strings
- Keep structured values as YAML lists/maps, not comma-separated strings
- Guard optional lists with
default([]), mappings withdefault({}), and strings withdefault('') - Build managed-user paths from
{{ user_home }}where applicable - Put host-specific overrides in
host_vars, not sharedgroup_vars
Templates, Dotfiles, And Shell
- Keep secrets parameterized through vars; never hardcode them in templates or dotfiles
- Prefer role
templates/for variable-driven config and rolefiles/for static payloads - Preserve destination paths and permissions unless the task requires a change
- Dotfiles should stay deployable on real machines; avoid repo-only hacks in
dotfiles/ - Keep
dotfiles/desktop/.config/waybar/config-*.jsonceffectively valid JSON - Prefer POSIX
shfor simple scripts; use Bash only when needed - Use
set -euin POSIX shell scripts unless there is a clear reason not to - Quote variable expansions unless intentional splitting is required
Error Handling, Safety, And Idempotency
- Fail early with
ansible.builtin.failwhen prerequisites are missing - Guard OS-specific, DE-specific, version-specific, and host-specific behavior with
when - Use
no_log: truefor passwords, tokens, private keys, and secret-bearing command results - Use
failed_when: falseonly for intentional probes - Keep tasks non-interactive unless explicitly user-driven
- Avoid destructive changes in user homes unless clearly required
- For firewall changes, allow required access before enabling the firewall
- When running commands as the desktop user, set
become_userand the requiredHOMEor session environment explicitly
Area-Specific Notes
profile_desktop_commonmanages shared Void desktop bootstrap,emptty, PAM hooks, dotfiles, mail bootstrap, and shared desktop tooling- Void desktop user services now use
turnstilewith runit-backed definitions underdotfiles/desktop/.config/service/; session launchers refresh the shared turnstile env for GUI-aware services such asemacs ssh-agenton Void desktops now runs under a separate always-on per-user runsvdir rooted at~/.local/runit/current, with a stable socket under~/.local/state/ssh-agent/socketollamaon Void desktops is installed from the upstream Linux tarball into/usr/localand runs as a separate always-on per-user runit service under~/.local/runit/current;Codex CLIis installed globally via npm and can target that local Ollama instanceprofile_desktop_i3contains the X11/i3 session piecesprofile_desktop_swaycontains the wlroots/Sway session pieces and deploys shared Sway + Waybar dotfilesprofile_desktop_hyprlandcontains the optional Hyprland/Wayland session piecesprofile_desktop_hostcarries host-specific desktop overrides such as NVIDIA, PRIME wrappers, and host-only WirePlumber configprofile_workstation_dev_commoncarries the shared dev layer for native Linux workstation profiles plus Ubuntu WSLpackages_fedoramanages the Fedora workstation package catalog, including Docker and Google Chrome repos, VS Code via the Microsoft RPM repo, IntelliJ IDEA Ultimate via COPR, and the remaining workstation GUI apps via Flatpakprofile_workstation_gnomecarries Linux host-only GNOME setup, extensions, firewall enablement, and host-managedgsettings- Native Linux workstation plays can be combined on the same inventory host when that host is placed in both the relevant OS/dev group and
workstation_host_linux deadalus-fedorakeeps Fedora-specificworkstation_gnome_managed_settingsinansible/inventory/host_vars/deadalus-fedora.yml, derived from the real host state and intentionally separate from Ubuntuprofile_workstation_dev_wslcarries WSL-specific Ubuntu tweaks such assystemdand PSRP Python dependenciesprofile_workstation_host_windowsmanages the Windows 11 host via PSRP over HTTPS usingnegotiateby default, installs host applications viawingetwith a configurablewindows_package_backenddefaulting towinget_psrp, applies Windows shell tweaks, manages taskbar pins through a local Start layout policy withPinListPlacement="Replace", and sets Windows Terminal's default profile to Ubuntudeadalus-wslis modeled as a local inventory target intended to be run from inside the Ubuntu WSL distro- Windows taskbar pinning is driven by the ordered
windows_taskbar_pinslist inansible/inventory/group_vars/workstation_host_windows.yml; validate identifiers from a real Windows session before changing that list - Do not auto-restart
empttyduring playbook runs on active desktop hosts; prefer a manual restart from SSH or another TTY after the run dotfiles/desktop/.xinitrcaffects X11 login behaviordotfiles/desktop/.local/bin/start-sway-sessionandstart-hyprland-sessionare critical session bootstrap pathsnymphhas special NVIDIA/PRIME handling, host-specific WirePlumber camera config, host-specific Sway overrides, and deterministic workspace placement viakanshi
Planned Windows Backend Expansion
This section captures agreed future design decisions for adding a third explicit Windows package backend using Chocolatey. It is planning context only and does not describe current repository behavior beyond what is already implemented.
Agreed Design Decisions
- Add
chocolateyas a third explicitwindows_package_backendalongsidewinget_psrpandwinget_wsl_local - Keep backend selection explicit per host; do not implement Chocolatey as an automatic fallback from winget
- Keep
winget_psrpas the default backend unless overridden through inventory, extra vars, or vault - Use an internal or proxy Chocolatey feed, not the public community feed directly
- Allow automatic Chocolatey bootstrap if
choco.exeis missing - Fail fast when the selected backend does not have a package mapping for a requested Windows package
Planned Data Model
- Replace the current
windows_winget_packages-only model with a backend-neutral catalog such aswindows_packages - Recommended shape:
windows_packages:
- key: sevenzip
name: 7-Zip
winget:
id: 7zip.7zip
chocolatey:
name: 7zip
- key: whatsapp
name: WhatsApp
winget:
id: 9NKSQGP7F2NH
source: msstore
- The selected backend should consume only its own nested mapping
- If
windows_package_backend == 'chocolatey'and a package lacks achocolateymapping, fail before installation starts - Keep backend-specific fields nested under backend keys rather than mixing them at top level
Planned Variables
- Extend
windows_package_backendto acceptchocolatey - Add
windows_chocolatey_source_name - Add
windows_chocolatey_source_url - Add optional vault-backed Chocolatey source credentials if the internal or proxy feed requires authentication
Planned Implementation Areas
ansible/site.yml: extend backend validation to supportchocolateyansible/roles/profile_workstation_host_windows/tasks/main.yml: add an explicit Chocolatey code path, bootstrap Chocolatey when missing, configure the internal or proxy source, validate package mappings, and install packages withchocolatey.chocolatey.win_chocolateyansible/inventory/group_vars/workstation_host_windows.yml: migrate package data fromwindows_winget_packagesto a backend-neutral catalog and add non-secret Chocolatey source settings if appropriateansible/collections/requirements.yml: add thechocolatey.chocolateycollectionREADME.md,AGENTS.md, andsecrets/vault.yml.example: document backend selection and Chocolatey source variables
Package Coverage Notes For First Iteration
- Likely good first Chocolatey candidates:
7zip,adobereader,dbeaver,googlechrome,intellijidea-ultimate,logioptionsplus,vscode,postman, andtelegram - Treat
WhatsAppas not covered until a valid non-deprecated Chocolatey package is confirmed - Treat
Windows Terminalas not covered until a stable Chocolatey package mapping is confirmed - Because the agreed behavior is fail-fast on missing mappings,
windows_package_backend=chocolateyshould fail if unsupported packages remain requested without a valid Chocolatey mapping
Source Strategy
- First implementation should target an internal or proxy Chocolatey feed rather than fully internalized packages
- This is acceptable as a faster first phase, but it is less deterministic long term than full internalization
Validation Expectations For This Future Work
Minimum:
ansible-playbook ansible/site.yml --syntax-check
Backend-specific dry runs:
ansible-playbook ansible/site.yml --limit deadalus-win --check --diff -e windows_package_backend=winget_psrp
ansible-playbook ansible/site.yml --limit deadalus-win --check --diff -e windows_package_backend=winget_wsl_local
ansible-playbook ansible/site.yml --limit deadalus-win --check --diff -e windows_package_backend=chocolatey
Targeted non-check validation once feed details exist:
ansible-playbook ansible/site.yml --limit deadalus-win --tags packages -e windows_package_backend=chocolatey
Remaining Open Detail
- The concrete internal or proxy repository product and source details still need to be supplied when this work is implemented, for example Nexus, Artifactory, ProGet, or another NuGet-compatible proxy
Validation Expectations Before Finishing
Default minimum:
ansible-playbook ansible/site.yml --syntax-check
Run a host-limited dry run whenever the change affects a real host profile, package set, service set, session, PAM stack, templates, or dotfiles.
For workstation changes, prefer:
ansible-playbook ansible/site.yml --limit deadalus-ubuntu --check --diff
ansible-playbook ansible/site.yml --limit deadalus-fedora --check --diff
ansible-playbook ansible/site.yml --limit deadalus-wsl --check --diff
If you touched scripts/bootstrap_mail.sh, also run:
sh -n scripts/bootstrap_mail.sh
shellcheck scripts/bootstrap_mail.sh
If you touched scripts/bootstrap_windows_workstation.ps1, also run:
pwsh -NoProfile -Command "[void][System.Management.Automation.Language.Parser]::ParseFile('scripts/bootstrap_windows_workstation.ps1', [ref]$null, [ref]$null)"
Agent Workflow Expectations
- Do not revert unrelated worktree changes made by the user
- Keep
README.mdandAGENTS.mdaligned when workflows materially change - If you add a new operational area, also add the validation command agents should run
- Prefer host-limited validation first:
ikarosornymphfor Void desktop work,deadalus-ubuntufor Ubuntu workstation work,deadalus-fedorafor Fedora workstation work, andprometheusfor server work - Call out checks you could not run and any follow-up verification needed