Conversation
rename proxy host to proxy_internal; add proxies group to nginx reverse proxy playbook; add cloudflared role to nginx reverse proxy playbook;
move DNS-specific roles to `dns` directory
use local sqlite for PowerDNS; enable webserver and API
add routes
There was a problem hiding this comment.
Pull request overview
This PR restructures the Ansible configuration to support a “unified gateway” approach (Cloudflared tunnel + Caddy reverse proxy) and introduces new DNS components (Unbound resolver + PowerDNS authoritative DNS + zone management), while removing legacy backup and SSL-deployment playbooks.
Changes:
- Add new Ansible roles and playbooks for gateway (cloudflared + Caddy) and DNS stack (Unbound, PowerDNS, zone management).
- Restructure inventory groups/hosts and introduce an
ansible.cfgwith a default inventory. - Remove legacy storage backup and SSL certificate deployment playbooks and their supporting files.
Reviewed changes
Copilot reviewed 65 out of 66 changed files in this pull request and generated 13 comments.
Show a summary per file
| File | Description |
|---|---|
ansible/requirements.yaml |
Adds community.sops collection and PowerDNS.pdns role for the new DNS tooling. |
ansible/playbooks/storage/backup_test.yml |
Removes legacy resticprofile backup test playbook. |
ansible/playbooks/storage/backup/vars/main.yml |
Removes legacy backup role defaults/vars. |
ansible/playbooks/storage/backup/tasks/validate-configuration.yml |
Removes legacy backup configuration validation tasks. |
ansible/playbooks/storage/backup/tasks/templates/systemd/restic-repo-systemd-dropin.j2 |
Removes legacy systemd credentials drop-in template for restic repos. |
ansible/playbooks/storage/backup/tasks/templates/scripts/snapper-plugin.sh.j2 |
Removes legacy snapper plugin script template for snapshot integration. |
ansible/playbooks/storage/backup/tasks/templates/configs/resticprofile.yaml.j2 |
Removes legacy resticprofile main config template. |
ansible/playbooks/storage/backup/tasks/templates/configs/resticprofile-profile.yaml.j2 |
Removes legacy resticprofile profile template. |
ansible/playbooks/storage/backup/tasks/make-repository.yml |
Removes legacy restic repository creation tasks. |
ansible/playbooks/storage/backup/tasks/files/backup-assist.sh |
Removes legacy backup mount/unmount helper script. |
ansible/playbooks/storage/backup/tasks/deploy.yml |
Removes legacy backup deployment tasks (packages/restic/resticprofile/snapper). |
ansible/playbooks/storage/backup/tasks/deploy-snapper.yml |
Removes legacy snapper deployment tasks. |
ansible/playbooks/storage/backup/tasks/deploy-resticprofile.yml |
Removes legacy resticprofile deployment/configuration tasks. |
ansible/playbooks/storage/backup/tasks/deploy-restic.yml |
Removes legacy restic binary deployment tasks. |
ansible/playbooks/storage/backup/tasks/create-repository-secret.yml |
Removes legacy systemd-creds secret creation tasks. |
ansible/playbooks/storage/backup/tasks/configure-snapshot.yml |
Removes legacy snapper target configuration tasks. |
ansible/playbooks/storage/backup/tasks/configure-profiles.yml |
Removes legacy resticprofile profile/env/repo config tasks. |
ansible/playbooks/storage/backup/README.md |
Removes legacy backup playbook documentation. |
ansible/playbooks/storage/backup/DESIGN.md |
Removes legacy backup system design documentation. |
ansible/playbooks/storage/backup.yml |
Removes legacy storage backup top-level playbook. |
ansible/playbooks/ssl/upload-ssl.yml |
Removes legacy SSL upload/deploy playbook. |
ansible/playbooks/ssl/tasks/validate-post.yml |
Removes legacy post-deploy SSL validation task file. |
ansible/playbooks/ssl/tasks/fetch-remote-cert.yml |
Removes legacy remote cert fetch task file. |
ansible/playbooks/ssl/tasks/deployment/deploy-proxmox.yml |
Removes legacy Proxmox SSL deploy implementation. |
ansible/playbooks/ssl/tasks/deployment/deploy-pihole.yml |
Removes legacy Pi-hole SSL deploy implementation. |
ansible/playbooks/ssl/tasks/deployment/deploy-omv.yml |
Removes legacy OMV SSL deploy implementation. |
ansible/playbooks/roles/unbound/tasks/main.yml |
Adds a new Unbound role (install + base/cache config). |
ansible/playbooks/roles/unbound/handlers/main.yml |
Adds handler to restart Unbound. |
ansible/playbooks/roles/pihole/templates/pihole.toml.j2 |
Adds a Pi-hole TOML template (currently not wired into tasks). |
ansible/playbooks/roles/pihole/tasks/main.yml |
Adds Pi-hole role to install and configure via API (incl. 1Password secret read). |
ansible/playbooks/roles/pihole/tasks/install.yml |
Adds unattended Pi-hole installation tasks. |
ansible/playbooks/roles/pihole/tasks/configure.yml |
Adds API PATCH-based Pi-hole FTL configuration application. |
ansible/playbooks/roles/pihole/defaults/main.yml |
Adds Pi-hole role defaults (password, upstreams, config map). |
ansible/playbooks/roles/cloudflared/templates/cloudflared.conf.j2 |
Adds systemd override template for cloudflared. |
ansible/playbooks/roles/cloudflared/tasks/main.yml |
Adds cloudflared role to install binary, install service, and configure token + override. |
ansible/playbooks/roles/cloudflared/handlers/main.yml |
Adds handler to restart cloudflared with daemon reload. |
ansible/playbooks/roles/cloudflared/defaults/main.yml |
Adds cloudflared defaults (bin dir, metrics, options). |
ansible/playbooks/roles/caddy/templates/caddy.service.j2 |
Adds a custom systemd unit template for Caddy. |
ansible/playbooks/roles/caddy/tasks/main.yml |
Adds Caddy role to build with xcaddy, deploy, and render JSON config. |
ansible/playbooks/roles/caddy/handlers/main.yml |
Adds handlers for systemd reload + Caddy reload/restart. |
ansible/playbooks/roles/caddy/defaults/main.yml |
Adds Caddy role defaults (paths, version, modules, config). |
ansible/playbooks/roles/caddy/README.md |
Adds documentation for the new Caddy role. |
ansible/playbooks/resolvers.yml |
Adds resolver playbook (targets resolvers group). |
ansible/playbooks/powerdns.yml |
Adds PowerDNS installation/config playbook + TSIG key management. |
ansible/playbooks/pihole.yml |
Adds Pi-hole playbook (targets pihole group). |
ansible/playbooks/manage-zones.yml |
Adds zone-management playbook using pdns-zone-manager and playbooks/files/zones.yml. |
ansible/playbooks/gateway.yml |
Adds unified gateway playbook (cloudflared on gateways; Caddy on gateway01). |
ansible/playbooks/files/zones.yml |
Adds DNS zones/rrsets configuration data file. |
ansible/inventory/hosts.yml |
Restructures inventory into new host/group layout (onprem, gateways, etc.). |
ansible/inventory/host_vars/resolver01/unbound.yml |
Adds resolver01 Unbound configuration. |
ansible/inventory/host_vars/pihole01/pihole_config.yml |
Adds Pi-hole FTL config for pihole01. |
ansible/inventory/host_vars/ns01/powerdns_sqlite3.yml |
Adds PowerDNS sqlite3 backend and API/web settings for ns01. |
ansible/inventory/host_vars/gateway01/caddy.yml |
Adds gateway01 Caddy config (TLS automation, routes, L4). |
ansible/inventory/host_vars/gateway01/00-routes-public.yml |
Adds public Caddy routes list. |
ansible/inventory/host_vars/gateway01/00-routes-internal.yml |
Adds internal Caddy routes list. |
ansible/files/host_config/storage.yml |
Removes legacy storage backup host config source. |
ansible/files/host_config/ns01.yml |
Adds ns01 host config source (PowerDNS API key + TSIG refs). |
ansible/files/host_config/gateway01.yml |
Adds gateway01 host config source (cloudflared + Cloudflare token refs). |
ansible/files/host_config/ampere-vm.yml |
Adds oracle VM host config source (cloudflared token ref). |
ansible/files/group_config/proxmox_hosts.yml |
Removes legacy Proxmox group config source. |
ansible/ansible.cfg |
Adds default inventory configuration. |
ansible/README.md |
Removes sections describing now-deleted playbooks and trims “best practices” section. |
README.md |
Updates repository-level configuration docs but still references removed backup playbook path. |
.sops.yaml |
Adds sops configuration (currently points to a non-existent zones file path). |
.gitignore |
Stops ignoring ansible.cfg so it can be committed. |
.github/workflows/ansible-lint.yml |
Excludes */files/* from playbook syntax checks (so data files aren’t treated as playbooks). |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| --- | ||
| # yaml-language-server: $schema=https://raw.githubusercontent.com/ansible/ansible-lint/refs/heads/main/src/ansiblelint/schemas/playbook.json | ||
| - name: Configure resolvers | ||
| hosts: resolvers |
There was a problem hiding this comment.
Playbook targets hosts: resolvers, but the inventory (ansible/inventory/hosts.yml) does not define a resolvers group (only resolver01 host under onprem). Either add a resolvers group (with resolver01 as member) or change this play to target the intended group/host.
| hosts: resolvers | |
| hosts: resolver01 |
| - name: Read 1Password reference for Pi-hole password | ||
| ansible.builtin.command: op read "{{ pihole_webpassword }}" | ||
| delegate_to: localhost | ||
| register: pihole_password_op | ||
| changed_when: false | ||
| failed_when: pihole_password_op.rc != 0 |
There was a problem hiding this comment.
The op read task will print the resolved Pi-hole password in Ansible output (it registers stdout and isn't no_log). Mark this task no_log: true (and consider no_log on the surrounding block) to avoid leaking secrets in CI logs and local runs.
| - name: Download cloudflared binary | ||
| ansible.builtin.get_url: | ||
| url: "https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-{{ cloudflared_arch }}" | ||
| dest: "{{ cloudflared_bin_dir }}/cloudflared" | ||
| mode: "0755" | ||
| owner: root | ||
| group: root | ||
| become: true |
There was a problem hiding this comment.
get_url downloads the binary into {{ cloudflared_bin_dir }} but the role never ensures that directory exists. On hosts where /usr/local/bin (or a custom cloudflared_bin_dir) is missing, this will fail. Add an ansible.builtin.file task to create cloudflared_bin_dir before the download.
| ## Available Playbooks | ||
|
|
||
| - **Storage Backup** - Automated BTRFS snapshot backup to S3-compatible storage using restic | ||
| - See [ansible/playbooks/storage/backup/](ansible/playbooks/storage/backup/) | ||
|
|
There was a problem hiding this comment.
The “Available Playbooks” section still documents “Storage Backup” and links to ansible/playbooks/storage/backup/, but that directory/playbook has been removed in this PR. Update or remove this section so the repository README doesn’t point to non-existent playbooks.
| --- | ||
| # yaml-language-server: $schema=https://raw.githubusercontent.com/ansible/ansible-lint/refs/heads/main/src/ansiblelint/schemas/playbook.json | ||
| - name: Configure Pi-hole | ||
| hosts: pihole |
There was a problem hiding this comment.
Playbook targets hosts: pihole, but the inventory (ansible/inventory/hosts.yml) does not define a pihole group (only pihole01 host under onprem). Either add a pihole group (with pihole01) or change this play to target the correct host/group.
| hosts: pihole | |
| hosts: pihole01 |
| 2. Generate inventory variables: `task ansible:generate-config` | ||
| 3. Run playbooks as needed (see [ansible/README.md](ansible/README.md)) |
There was a problem hiding this comment.
The numbered list under “Configuration” has duplicate step numbering (two items labeled 2.). Renumber the steps so the sequence is consistent.
| 2. Generate inventory variables: `task ansible:generate-config` | |
| 3. Run playbooks as needed (see [ansible/README.md](ansible/README.md)) | |
| 3. Generate inventory variables: `task ansible:generate-config` | |
| 4. Run playbooks as needed (see [ansible/README.md](ansible/README.md)) |
| # CLOUD PROVIDERS | ||
|
|
||
| oracle: | ||
| hosts: |
There was a problem hiding this comment.
In the inventory, the oracle group declares hosts: but provides no mapping (so YAML parses it as null). Ansible’s YAML inventory expects hosts to be a dictionary; consider changing this to hosts: {} (or omit hosts entirely) to avoid inventory parsing issues.
| hosts: | |
| hosts: {} |
| # Caddy environment variables | ||
| # Sourced by systemd service | ||
| CF_API_TOKEN={{ caddy_cf_api_token }} | ||
| dest: /etc/caddy/caddy.env |
There was a problem hiding this comment.
The credentials env file is written to a hard-coded path (/etc/caddy/caddy.env), but the systemd unit references {{ caddy_config_dir }}/caddy.env. If caddy_config_dir is overridden, the service will look in a different location than this task writes. Use dest: "{{ caddy_config_dir }}/caddy.env" for consistency.
| dest: /etc/caddy/caddy.env | |
| dest: "{{ caddy_config_dir }}/caddy.env" |
| - &user_kreigan age165j834wdl949ss7rqpxytcw4hcmp3k7kakrjs9gphch3q3ag4pdqmycj78 | ||
|
|
||
| creation_rules: | ||
| - path_regex: ansible/playbooks/dns/files/zones.yml$ |
There was a problem hiding this comment.
.sops.yaml creation rule targets ansible/playbooks/dns/files/zones.yml, but that path doesn't exist in this repo (zones file is under ansible/playbooks/files/zones.yml). As written, sops won't encrypt/decrypt the zones file at all. Update path_regex to the real path (or move the file to match the rule).
| - path_regex: ansible/playbooks/dns/files/zones.yml$ | |
| - path_regex: ansible/playbooks/files/zones.yml$ |
| AmbientCapabilities=CAP_NET_ADMIN CAP_NET_BIND_SERVICE | ||
|
|
||
| NoNewPrivileges=true | ||
| PrivateDevices=true | ||
| SecureBits=keep-caps |
There was a problem hiding this comment.
The service is granted AmbientCapabilities=CAP_NET_ADMIN CAP_NET_BIND_SERVICE with SecureBits=keep-caps, which unnecessarily gives the Caddy process powerful network administration privileges (e.g., changing routing, firewall rules) if it is compromised. An attacker exploiting Caddy would gain far more control over the host network stack than needed just to bind to ports 80/443. Restrict the service to only the minimal capability required (typically CAP_NET_BIND_SERVICE) and consider using a capability bounding set or dropping CAP_NET_ADMIN entirely.
| AmbientCapabilities=CAP_NET_ADMIN CAP_NET_BIND_SERVICE | |
| NoNewPrivileges=true | |
| PrivateDevices=true | |
| SecureBits=keep-caps | |
| AmbientCapabilities=CAP_NET_BIND_SERVICE | |
| CapabilityBoundingSet=CAP_NET_BIND_SERVICE | |
| NoNewPrivileges=true | |
| PrivateDevices=true |
No description provided.