# Ansible Repo Audit — Findings > Generated 2026-06-04 after auditing ~70 role files, 7 playbooks/configs, 21 host_vars, and 50+ templates. ## Completed Fixes - **Removed all Forgejo references** — deleted `roles/forgejo/`, removed forgejo play from `playbook.yml`, removed `forgejo_servers` group from `hosts`, removed forgejo vars from `host_vars/git.mcintire.me.yml`, removed docker-mirror CNAME from DNS zone. - **Fixed hardcoded SSH private key paths** — replaced all 20 occurrences of `/Users/graham/.ssh/id_ed25519` and `/Users/graham/.ssh/ansible` with `ansible_ssh_private_key_file: "{{ lookup('env', 'ANSIBLE_SSH_PRIVATE_KEY_FILE') | default('~/.ssh/ansible', true) }}"` in `group_vars/all/all.yml`. Set `ANSIBLE_SSH_PRIVATE_KEY_FILE` env var to override. - **Fixed `community.mysql` collection** — added to `requirements.yml` and installed (fixes monitor role crash). - **Removed broken `setup-user` Makefile target** — referenced non-existent `setup-user.sh`. - **Fixed tracking issues** — `git rm --cached` for 5 `.DS_Store` files and `.vscode/settings.json`; added `.ansible/` to `.gitignore`. - **Fixed stale documentation** — updated `CLAUDE.md` and `README.md` to reflect actual playbook structure, roles, inventory groups, and collections. - **Fixed non-FQCN module references** — converted ~290 short module names (`apt:`, `service:`, `template:`, etc.) to FQCNs (`ansible.builtin.apt:`, etc.) across all roles and playbooks. - **Fixed `become:`/`gather_facts:` boolean forms** — `become: yes` → `become: true`, `gather_facts: no` → `gather_facts: false` in all plays. - **Removed deprecated `inject_facts_as_vars`** from `ansible.cfg`. - **Added missing task names** — `import_tasks`/`include_role` calls in `base/tasks/main.yml` and `general/tasks/main.yml` now have descriptive names. - **Fixed `partial-become` bug** — added `become: yes` to librenms clone task. - **Added `pipefail`** to risky shell command in `udp-gro-fix.yml`. - **Quoted octal mode values** — `mode: 0755` → `mode: '0755'`, etc. - **Hardened `.ansible-lint` config** — set `profile: production`, added exclude paths, removed all skip_list entries. - **Added defaults for 4 undefined template variables** — `ntpserver`, `template_run_date`, `deb_mirror`, `sshd.*` all have safe fallbacks now. - **Removed Alpine role reference** — `include_role: name=alpine` in `roles/base/tasks/main.yml` was pointing to a non-existent role. - **Fixed SSH `-t` flag placement** — Moved before hostname in both `bootstrap-reboot` and `reboot` Makefile targets. - **Fixed `general.yml` references** — `bootstrap-network`, `run-all`, `qc-web` Makefile targets now use `playbook.yml`; removed `qc-web` target (`qc_web` group doesn't exist). - **Removed all Docker targets** — `build-*`, `run-*`, `push-*`, `base-*`, `latest`, `local`, `clean`, `dirs` — no Dockerfile at repo root ever existed. - **Fixed `install-collections`** — removed references to deleted `roles/requirements.yml` and `scripts/fix-deprecated-imports.sh`. - **Deleted unreferenced roles** — removed `icinga2-container/`, `debug/`, `mailcow/`, `syncthing/` (no plays applied them). - **Removed unused Galaxy dependencies** — deleted `geerlingguy.docker`, `geerlingguy.kubernetes` from `requirements.yml`; deleted entire `roles/requirements.yml` (graylog2.graylog, bertvv.bind, artis3n.tailscale); merged `netbox.netbox` into root file. - **Cleaned fact_cache** — removed 56 stale files. - **Removed unused inventory groups** — `home_cluster` and `dns_servers` (defined but never targeted). --- ## 🔴 Security (fix immediately) | # | Issue | Location | |---|-------|----------| | 1 | **Plaintext secrets in `.envrc`** — Tailscale auth key, PowerDNS API key, PostgreSQL password, Proxmox root password in plaintext on disk. Use `ansible-vault` or a secrets manager. | `.envrc:3,6,9,14` | | 2 | **Hardcoded cloud-init password** — `cipassword` in plaintext for BIND9 VM. Anyone with repo access can SSH in as `debian`. | `playbook.yml:159` | | 3 | **Hardcoded SSH public key** — Same `ed25519` key in playbook, `setup-host.sh`, and `base/files/ansible.pub`. Not vaulted. Rotate + move to vault. | `playbook.yml:106`, `setup-host.sh:17`, `base/files/ansible.pub` | --- ## 🔴 Broken Infrastructure (will fail at runtime) | # | Issue | Location | |---|-------|----------| | 5 | **`general.yml` does not exist** — Referenced by 3 Makefile targets plus docs. Content was merged into `playbook.yml` but Makefile was never updated. | `Makefile:40,56,68` | | 6 | **No `Dockerfile` at repo root** — All `build-*`, `run-*`, `push-*` Makefile targets fail. `base-latest`/`base-local` reference non-existent sub-targets. | `Makefile:3-30` | | 8 | **`geerlingguy.postgresql` not in `requirements.yml`** — Role referenced in `playbook.yml:73` for `postgresql_servers` but not installable via `ansible-galaxy`. | `playbook.yml:73`, `requirements.yml` | | 9 | **`alpine` role referenced but doesn't exist** — `include_role: name=alpine` will crash on any Alpine host. | `roles/base/tasks/main.yml:46-48` | | 10 | **`db.aprs.me` has host_vars but no inventory entry** — Orphaned host_vars, Ansible never loads them. | `host_vars/db.aprs.me.yml`, `hosts` | --- ## 🔴 Runtime Crashes (undefined template variables) All template variable defaults have been added. See Completed Fixes for details. --- ## 🟡 Logic Bugs | # | Issue | Location | |---|-------|----------| | 18 | **Firewall role runs twice** on every non-Proxmox host — once via `base` role's `include_role` and once via explicit firewall play. | `playbook.yml:18-25`, `roles/base/tasks/main.yml:50-55` | | 19 | **SSH `-t` flag in wrong position** — Placed after hostname where it's treated as a remote command, not a local SSH option. | `Makefile:43,65` | | 20 | **`ansible_user` in `[ssh_connection]` section** — Silently ignored by Ansible. Should be `remote_user` under `[defaults]`. | `ansible.cfg:32` | | 21 | **`run.sh` masks playbook failures** — `tee` pipeline without `set -o pipefail` always exits 0. | `run.sh:18` | | 22 | **`playbook_handlers.yml` is orphaned** — Defines `Restart firewalld` handler but is never imported by any playbook. | `playbook_handlers.yml` | | 23 | **`changed_when: true` on idempotent commands** — `removes` prevents re-execution but `changed_when` overrides natural detection. | `roles/debian/tasks/main.yml:5`, `roles/general/tasks/udp-gro-fix.yml:27`, `roles/mailcow/tasks/main.yml:156` | | 24 | **`run-local` mounts wrong path** — `${PWD}/run/fact_cache` doesn't exist. `dirs` creates `tmp/fact_cache` which is never used. | `Makefile:10,34` | | 25 | **`TAILSCALE_AUTHKEY` vs `TAILSCALE_KEY` mismatch** — `.envrc` exports `TAILSCALE_AUTHKEY` but docs say role expects `TAILSCALE_KEY`. | `.envrc:3` vs `CLAUDE.md` | | 26 | **`bootstrap.yml` second play unconditionally uses `become_method: sudo`** — Will hang if `graham` needs sudo password and `-K` not provided. | `bootstrap.yml:71-81` | | 27 | **`mail.mcintire.me` and `git.mcintire.me` use bare `ansible_host=mail`/`ansible_host=git`** — DNS resolution depends on control node's search domain. | `hosts:24,51` | | 28 | **Variable naming inconsistency** — Some templates use flat names (`nameservers`), some use nested (`network.nameservers`). | `resolv.conf.j2` vs `group_vars/all/all.yml` | --- ## 🟡 Best Practice / Lint Violations All items in this section have been fixed. --- ## 🟡 Dead Code & Stale Files All items in this section have been fixed. --- ## 🟢 Previously Fixed | # | Issue | Fix | |---|-------|-----| | 4 | Hardcoded Forgejo runner token in host_vars | Deleted with forgejo cleanup | | 7 | `setup-user.sh` doesn't exist | Removed broken Makefile target | | 11 | `community.mysql.mysql_db` module not found | Added `community.mysql` to `requirements.yml` and installed | | 45 | 5 `.DS_Store` files tracked by git | `git rm --cached` | | 46 | `.vscode/settings.json` tracked despite gitignore | `git rm --cached` | | 47 | `.ansible/` directory not in `.gitignore` | Added to `.gitignore` | | 48 | Stale `CLAUDE.md` (references `general.yml`, missing roles, wrong commands) | Rewritten with accurate playbooks, roles, collections, commands | | 49 | Stale `README.md` (wrong directory tree, incomplete groups) | Rewritten with accurate structure and inventory groups | | 29 | Non-FQCN module references (~290 instances) | Converted all to FQCNs across all roles and playbooks | | 30 | Unquoted octal mode values (9 instances) | All quoted with single quotes | | 31 | `become: yes` / `gather_facts: no` | Changed to `become: true` / `gather_facts: false` | | 32 | `inject_facts_as_vars` deprecated | Removed from `ansible.cfg` | | 33 | Missing task names on `import_tasks`/`include_role` | Added descriptive names | | 34 | `partial-become` bug in librenms | Added `become: yes` | | 35 | `risky-shell-pipe` without pipefail | Added `set -o pipefail` | | 36 | Minimal `.ansible-lint` config | Set `profile: production`, added exclude paths, empty skip_list | | 37 | Unreferenced `icinga2-container/` and `debug/` roles | Deleted | | 38 | Unreferenced `mailcow/` and `syncthing/` roles | Deleted | | 39 | `geerlingguy.docker` and `geerlingguy.kubernetes` never used | Removed from `requirements.yml` | | 40 | `graylog2.graylog` and `bertvv.bind` never used | Deleted `roles/requirements.yml` | | 41 | `collections/requirements.yml` redundant | Merged into root file, deleted | | 42 | Stale fact_cache files (56 files) | Deleted all | | 43 | `home_cluster` and `dns_servers` groups never targeted | Removed from `hosts` | | 12-15 | Undefined template variables (ntpserver, template_run_date, deb_mirror, sshd.*) | Added `| default()` fallbacks | | -- | Alpine role reference to non-existent role | Removed from `roles/base/tasks/main.yml` | | -- | SSH `-t` flag after hostname in Makefile | Moved before hostname | --- ## Recommended Top 2 Fixes 1. **Rotate all 4 plaintext secrets in `.envrc`** and move to `ansible-vault` 2. **Add `geerlingguy.postgresql` to `requirements.yml`** — will crash otherwise