- Convert ~290 short module names to FQCNs across all roles - Change become: yes/no to become: true/false, gather_facts: no to false - Remove deprecated inject_facts_as_vars from ansible.cfg - Add missing task names to import_tasks/include_role calls - Add become: yes to librenms clone task (partial-become fix) - Add pipefail to risky shell command in udp-gro-fix.yml - Quote all octal mode values - Harden .ansible-lint with production profile and empty skip_list - Update findings.md with all fixes
9.9 KiB
9.9 KiB
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 fromplaybook.yml, removedforgejo_serversgroup fromhosts, removed forgejo vars fromhost_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_ed25519and/Users/graham/.ssh/ansiblewithansible_ssh_private_key_file: "{{ lookup('env', 'ANSIBLE_SSH_PRIVATE_KEY_FILE') | default('~/.ssh/ansible', true) }}"ingroup_vars/all/all.yml. SetANSIBLE_SSH_PRIVATE_KEY_FILEenv var to override. - Fixed
community.mysqlcollection — added torequirements.ymland installed (fixes monitor role crash). - Removed broken
setup-userMakefile target — referenced non-existentsetup-user.sh. - Fixed tracking issues —
git rm --cachedfor 5.DS_Storefiles and.vscode/settings.json; added.ansible/to.gitignore. - Fixed stale documentation — updated
CLAUDE.mdandREADME.mdto 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: falsein all plays. - Removed deprecated
inject_facts_as_varsfromansible.cfg. - Added missing task names —
import_tasks/include_rolecalls inbase/tasks/main.ymlandgeneral/tasks/main.ymlnow have descriptive names. - Fixed
partial-becomebug — addedbecome: yesto librenms clone task. - Added
pipefailto risky shell command inudp-gro-fix.yml. - Quoted octal mode values —
mode: 0755→mode: '0755', etc. - Hardened
.ansible-lintconfig — setprofile: production, added exclude paths, removed all skip_list entries.
🔴 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)
| # | Issue | Location |
|---|---|---|
| 12 | ntpserver undefined — No default. Inconsistent with base.ntp.server used elsewhere. |
roles/base/templates/ntp.conf.j2:45 |
| 13 | template_run_date undefined — No default on .strftime() call. MOTD template crashes. |
roles/general/templates/etc/motd.j2:2 |
| 14 | deb_mirror undefined — No default, 4 references across the template. |
roles/general/templates/etc/apt/sources.list.j2 |
| 15 | sshd.* dict entirely undefined — 5 nested references (permit_root_login, forwarding.*) with no defaults. |
roles/general/templates/etc/ssh/sshd_config.j2:25,61-64 |
| 16 | interfaces undefined — No default guard on `{% if interfaces |
length > 0 %}`. |
| 17 | nfs_exports undefined — No default. Only defined in skippy.w5isp.com.yml. Crashes on other hosts. |
roles/general/templates/etc/exports.j2:3 |
🟡 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
| # | Issue | Location |
|---|---|---|
| 37 | icinga2-container/ and debug/ roles exist but never referenced in any playbook. |
roles/icinga2-container/, roles/debug/ |
| 38 | mailcow and syncthing roles exist but no play applies them. |
roles/mailcow/, roles/syncthing/ |
| 39 | geerlingguy.docker and geerlingguy.kubernetes never used in any playbook. |
requirements.yml:15-19 |
| 40 | graylog2.graylog and bertvv.bind never used — Entire roles/requirements.yml appears legacy. |
roles/requirements.yml |
| 41 | collections/requirements.yml is redundant — Lists ansible.posix and netbox.netbox without version pins. |
collections/requirements.yml |
| 42 | 49/56 fact_cache files are stale — 40 use old s1_ naming convention, 9 are decommissioned hosts. |
fact_cache/ |
| 43 | home_cluster and dns_servers groups — Defined but never targeted by any play. |
hosts |
| 44 | node_exporter_servers is a pure alias for prometheus_servers (1 host). |
hosts |
🟢 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 |
Recommended Top 5 Fixes
Recommended Top 5 Fixes
- Rotate all 4 plaintext secrets in
.envrcand move toansible-vault - Add
geerlingguy.postgresqltorequirements.yml— will crash otherwise - Add defaults for 4 undefined template variables (
ntpserver,template_run_date,deb_mirror,sshd.*) — will crash at runtime - Remove
alpinerole reference inroles/base/tasks/main.ymlor create the role - Fix SSH
-tflag placement in Makefile reboot targets