personal_infra/ansible/services/ntfy/deploy_ntfy_playbook.yml

100 lines
2.8 KiB
YAML
Raw Normal View History

2025-07-27 12:54:30 +02:00
- name: Deploy ntfy and configure Caddy reverse proxy
hosts: monitoring
2025-07-27 12:54:30 +02:00
become: yes
vars_files:
- ../../infra_vars.yml
2025-12-01 11:16:47 +01:00
- ../../infra_secrets.yml
2025-11-06 23:09:44 +01:00
- ../../services_config.yml
2025-07-27 12:54:30 +02:00
- ./ntfy_vars.yml
vars:
2025-11-06 23:09:44 +01:00
ntfy_subdomain: "{{ subdomains.ntfy }}"
2025-07-27 12:54:30 +02:00
ntfy_domain: "{{ ntfy_subdomain }}.{{ root_domain }}"
tasks:
- name: Ensure /etc/apt/keyrings exists
file:
path: /etc/apt/keyrings
state: directory
mode: '0755'
- name: Download and dearmor ntfy GPG key
shell: curl -fsSL https://archive.heckel.io/apt/pubkey.txt | gpg --dearmor -o /etc/apt/keyrings/archive.heckel.io.gpg
args:
creates: /etc/apt/keyrings/archive.heckel.io.gpg
- name: Add ntfy APT repository
copy:
dest: /etc/apt/sources.list.d/archive.heckel.io.list
content: |
deb [arch=amd64 signed-by=/etc/apt/keyrings/archive.heckel.io.gpg] https://archive.heckel.io/apt debian main
mode: '0644'
- name: Update APT cache
apt:
update_cache: yes
- name: Install ntfy
apt:
name: ntfy
state: present
- name: Ensure ntfy cache directories exist
file:
path: "{{ item }}"
state: directory
owner: ntfy
group: ntfy
mode: '0755'
loop:
- /var/cache/ntfy
- /var/cache/ntfy/attachments
- name: Deploy ntfy configuration file
copy:
dest: /etc/ntfy/server.yml
content: |
base-url: "http://{{ ntfy_domain }}"
listen-http: ":{{ ntfy_port }}"
cache-file: "/var/cache/ntfy/cache.db"
attachment-cache-dir: "/var/cache/ntfy/attachments"
behind-proxy: true
auth-file: "/var/lib/ntfy/user.db"
auth-default-access: "deny-all"
owner: root
group: root
mode: '0644'
notify: Restart ntfy
- name: Enable and start ntfy service
systemd:
name: ntfy
enabled: yes
state: started
- name: Create ntfy admin user
shell: |
2025-12-01 11:16:47 +01:00
(echo "{{ ntfy_password }}"; echo "{{ ntfy_password }}") | ntfy user add --role=admin "{{ ntfy_username }}"
2025-07-27 12:54:30 +02:00
ntfy, datum-gateway, headscale: use the caddy_site role Completes Stage 3. No hand-rolled Caddy plumbing remains anywhere: `grep sites-enabled` outside roles/ returns nothing, and so does `grep "systemctl reload caddy"`. ntfy uses caddy_site_body for its plain-HTTP listener and @httpget redirect. Verified ok/unchanged against watchtower; the one other changed task is a pre-existing "Update APT cache". datum-gateway keeps a whole-Caddyfile validate after the role call. The role validates its own fragment, but only a whole-file validate catches a conflict between two sites, and this playbook was the only one that ever had it. Its two debug tasks that echoed command output are gone with the commands. headscale is the one that mattered. Its playbook wrote `reverse_proxy localhost:8080`, but spacey is actually running a /admin* route in front of Headplane behind Caddy basic auth. Running that playbook would have deleted the admin route and its auth - a hazard that predates this work. It now renders the config that is really there, verified ok/unchanged via --start-at-task (the play cannot reach Caddy in check mode: "Install headscale package" fails because the .deb is not really downloaded, before and after this edit alike). Supporting changes for headscale: - headscale_ui_password_hash added to infra_secrets.yml and the identical group_vars/all/vault.yml, read from the live config on spacey. The vault already had headscale_ui_username (= counterweight, confirmed) and headscale_ui_password; I did not verify the password is the plaintext of this hash. - headplane_port added to headscale_vars.yml. - The role's handler now sets become: true. Handlers do not inherit become from the task that notified them, and this play runs become: no. - The include uses `apply: become: yes`; `become:` on an include_role is rejected outright. All 14 site files on all 3 hosts still byte-identical. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-11 23:37:38 +02:00
- name: Publish ntfy through Caddy
ansible.builtin.include_role:
name: caddy_site
vars:
caddy_site_name: ntfy
caddy_site_domain: "{{ ntfy_domain }}, http://{{ ntfy_domain }}"
# Raw body: ntfy needs a plain-HTTP listener for its CLI/app clients,
# with only GETs to the docs and topic paths redirected to HTTPS.
caddy_site_body: |
reverse_proxy 127.0.0.1:{{ ntfy_port }}
2025-07-27 12:54:30 +02:00
ntfy, datum-gateway, headscale: use the caddy_site role Completes Stage 3. No hand-rolled Caddy plumbing remains anywhere: `grep sites-enabled` outside roles/ returns nothing, and so does `grep "systemctl reload caddy"`. ntfy uses caddy_site_body for its plain-HTTP listener and @httpget redirect. Verified ok/unchanged against watchtower; the one other changed task is a pre-existing "Update APT cache". datum-gateway keeps a whole-Caddyfile validate after the role call. The role validates its own fragment, but only a whole-file validate catches a conflict between two sites, and this playbook was the only one that ever had it. Its two debug tasks that echoed command output are gone with the commands. headscale is the one that mattered. Its playbook wrote `reverse_proxy localhost:8080`, but spacey is actually running a /admin* route in front of Headplane behind Caddy basic auth. Running that playbook would have deleted the admin route and its auth - a hazard that predates this work. It now renders the config that is really there, verified ok/unchanged via --start-at-task (the play cannot reach Caddy in check mode: "Install headscale package" fails because the .deb is not really downloaded, before and after this edit alike). Supporting changes for headscale: - headscale_ui_password_hash added to infra_secrets.yml and the identical group_vars/all/vault.yml, read from the live config on spacey. The vault already had headscale_ui_username (= counterweight, confirmed) and headscale_ui_password; I did not verify the password is the plaintext of this hash. - headplane_port added to headscale_vars.yml. - The role's handler now sets become: true. Handlers do not inherit become from the task that notified them, and this play runs become: no. - The include uses `apply: become: yes`; `become:` on an include_role is rejected outright. All 14 site files on all 3 hosts still byte-identical. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-11 23:37:38 +02:00
@httpget {
protocol http
method GET
path_regexp ^/([-_a-z0-9]{0,64}$|docs/|static/)
2025-07-27 12:54:30 +02:00
}
ntfy, datum-gateway, headscale: use the caddy_site role Completes Stage 3. No hand-rolled Caddy plumbing remains anywhere: `grep sites-enabled` outside roles/ returns nothing, and so does `grep "systemctl reload caddy"`. ntfy uses caddy_site_body for its plain-HTTP listener and @httpget redirect. Verified ok/unchanged against watchtower; the one other changed task is a pre-existing "Update APT cache". datum-gateway keeps a whole-Caddyfile validate after the role call. The role validates its own fragment, but only a whole-file validate catches a conflict between two sites, and this playbook was the only one that ever had it. Its two debug tasks that echoed command output are gone with the commands. headscale is the one that mattered. Its playbook wrote `reverse_proxy localhost:8080`, but spacey is actually running a /admin* route in front of Headplane behind Caddy basic auth. Running that playbook would have deleted the admin route and its auth - a hazard that predates this work. It now renders the config that is really there, verified ok/unchanged via --start-at-task (the play cannot reach Caddy in check mode: "Install headscale package" fails because the .deb is not really downloaded, before and after this edit alike). Supporting changes for headscale: - headscale_ui_password_hash added to infra_secrets.yml and the identical group_vars/all/vault.yml, read from the live config on spacey. The vault already had headscale_ui_username (= counterweight, confirmed) and headscale_ui_password; I did not verify the password is the plaintext of this hash. - headplane_port added to headscale_vars.yml. - The role's handler now sets become: true. Handlers do not inherit become from the task that notified them, and this play runs become: no. - The include uses `apply: become: yes`; `become:` on an include_role is rejected outright. All 14 site files on all 3 hosts still byte-identical. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-11 23:37:38 +02:00
redir @httpget https://{host}{uri}
2025-07-27 12:54:30 +02:00
handlers:
- name: Restart ntfy
systemd:
name: ntfy
state: restarted