infra/ansible/findings.md
Graham McIntire ee72c64246
fix: resolve best practice and lint violations
- 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
2026-06-04 14:34:23 -05:00

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 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 issuesgit 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 formsbecome: yesbecome: true, gather_facts: nogather_facts: false in all plays.
  • Removed deprecated inject_facts_as_vars from ansible.cfg.
  • Added missing task namesimport_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 valuesmode: 0755mode: '0755', etc.
  • Hardened .ansible-lint config — set profile: 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 passwordcipassword 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 existinclude_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 failurestee 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 commandsremoves 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

  1. Rotate all 4 plaintext secrets in .envrc and move to ansible-vault
  2. Add geerlingguy.postgresql to requirements.yml — will crash otherwise
  3. Add defaults for 4 undefined template variables (ntpserver, template_run_date, deb_mirror, sshd.*) — will crash at runtime
  4. Remove alpine role reference in roles/base/tasks/main.yml or create the role
  5. Fix SSH -t flag placement in Makefile reboot targets