From 8d61a21a1b5ba512d936a3220a8d0e806f015797 Mon Sep 17 00:00:00 2001 From: jdelpilar Date: Thu, 16 Jul 2026 13:14:06 -0700 Subject: [PATCH] feat(ntfy):add ntfy server role (#20) ### Description This PR adds a new `ntfy_server` role to configure, start, and test a ntfy container. This container is designed to be secure by default by requiring auth by default ### Related Issue Closes #15 ### Testing Done - [x] verified new container file was created correctly\ - [x] verified ntfy server webui is working and requires auth - [x] verified with automated test that ntfy server is working and accepting post requests ### Checklist - [x] My code follows the project's style guidelines. - [x] I have performed a self-review of my own code. - [x] I have updated the documentation (if necessary). --------- Co-authored-by: Jordan Del Pilar Reviewed-on: https://git.delpilar.net/jdelpilar/johto-infra/pulls/20 --- group_vars/all.yaml | 1 + host_vars/new-bark.yaml | 33 ++++++ inventory.yaml | 3 + roles/ntfy_server/README.md | 106 ++++++++++++++++++ roles/ntfy_server/defaults/main.yaml | 9 ++ roles/ntfy_server/handlers/main.yaml | 6 + roles/ntfy_server/tasks/main.yml | 40 +++++++ roles/ntfy_server/templates/ntfy.container.j2 | 57 ++++++++++ site.yaml | 8 ++ 9 files changed, 263 insertions(+) create mode 100644 roles/ntfy_server/README.md create mode 100644 roles/ntfy_server/defaults/main.yaml create mode 100644 roles/ntfy_server/handlers/main.yaml create mode 100644 roles/ntfy_server/tasks/main.yml create mode 100644 roles/ntfy_server/templates/ntfy.container.j2 diff --git a/group_vars/all.yaml b/group_vars/all.yaml index 2b6e02f..cc359a3 100644 --- a/group_vars/all.yaml +++ b/group_vars/all.yaml @@ -1,2 +1,3 @@ --- github_key_url: https://github.com/redjordan2539.keys +base_domain: delpilar.net diff --git a/host_vars/new-bark.yaml b/host_vars/new-bark.yaml index 9658bc2..1c9f9fc 100644 --- a/host_vars/new-bark.yaml +++ b/host_vars/new-bark.yaml @@ -250,3 +250,36 @@ minecraft_servers: TYPE: PAPER ENABLE_AUTOPAUSE: "true" OPS: redjordan1 + +ntfy_web_user: jdelpilar +ntfy_web_pass: !vault | + $ANSIBLE_VAULT;1.1;AES256 + 39363231343664626436343130316662336663303939343334353138353864383334363362343362 + 6366633162323162393263336137313534303563353337340a316362653735306538363733333461 + 38626362386263613932366664316630306361313036353839373562663534613962346339626538 + 3866356430663962660a613962396662333538333836666334613064343266376431633463326164 + 39333735343634316336613933303765373934393834663933626439643138663561 + +ntfy_users: + - username: jdelpilar + pass: !vault | + $ANSIBLE_VAULT;1.1;AES256 + 63613562396339323236636436613162633933306338333662633933613832613038313230333165 + 3365636562366165363861353638306264653739396532320a366564346331396266653762306165 + 33333639373532653762363563653534353661366239306265303538376237393438636437393632 + 3138386130306430640a383932383634313764373565636463636233623063613961353961643531 + 35613864303162393362616634626238316334343533646261643063303963383932653534316466 + 31313231643239653865363163623136316234386433393263366537323866326633313261656533 + 313965396432613533653934323464626334 + level: admin + - username: hass + pass: !vault | + $ANSIBLE_VAULT;1.1;AES256 + 65656330333535306239343935306234633730376361393234376266393837636436313262353939 + 3635653139383864396533373534306236386662643535650a373765303531633563326563663736 + 39666339343439333730373834353962343962383335633466656162356465366537303532623438 + 6361633163623061660a323565346663313238396431373932333562313237326238313235373330 + 33663632333266623162373033376432336538393334386663393866623836663562343936396234 + 38633431623838653035623066346138386234623861316461653430623638656563633261383238 + 353132376664303838316637663035633862 + level: user diff --git a/inventory.yaml b/inventory.yaml index cf8dd9a..0c53b87 100644 --- a/inventory.yaml +++ b/inventory.yaml @@ -32,3 +32,6 @@ all: minecraft_servers: hosts: new-bark: + ntfy_servers: + hosts: + new-bark: \ No newline at end of file diff --git a/roles/ntfy_server/README.md b/roles/ntfy_server/README.md new file mode 100644 index 0000000..520bef3 --- /dev/null +++ b/roles/ntfy_server/README.md @@ -0,0 +1,106 @@ +# Role: ntfy Server +This role sets up, starts and test the ntfy container. +This role is designed to only create one ntfy container per host. + +## Tags +- ntfy +- comms + +## Required Variables +Below is an annotated breakdown of the required variables and their structure for this role + +> [!WARNING] +> Any variable marked `# !SENSITIVE` **should not** be stored in plain text under any circumstance + +### ntfy User Info +Defines users to be set up and used in ntfy. +These settings should be defined on each server you plan on setting up ntfy on as they are unique to each server + +```yaml +# (required) username of the primary web user of the ntfy webui +# this account is used during the role to send a test post request to verify the server is running +# this username must also be defined in the below ntfy_users list +# format: username +ntfy_web_user: + +# (required) password of the above web user +# this is used during basic auth to send the test request to the server +# !SENSITIVE +# format: string +ntfy_web_pass: + +# (required) list of users to be created in the ntfy instance +# you must defined the above ntfy_web_user in this list +ntfy_users: + # (required) username of the user to be created + # format: username + - username: + # (required) password hash for the user + # for more info on the requirements of this hash please read below + # !SENSITIVE + # format: string + pass: + + # (required) user level of the generated user + # you must define at least one admin user per instance + # format: choice("user", "admin") + level: +``` +### ntfy container Info +Defines settings for the ntfy container + +```yaml +# (optional) name of the ntfy container +# default: ntfy +# format: snake_case +ntfy_name: + +# (optional) path to ntfy container image +# can be set to any container register +# default: docker.io/binwiederhier/ntfy +# format: URL +ntfy_image: + +# (optional) name of the container owner +# typically set to the same user as the ansible user +# default: {{ ansible_user }} +# format: username +container_owner: + +# (optional) flag to enable traefik labels in the container +# default: true +# format: bool +ntfy_enable_traefik: + +# (optional) subdomain of the ntfy instance +# only used if `ntfy_enable_traefik` == true +# default: ntfy +# format: string +ntfy_subdomain: + +# (optional) port to ntfy webui +# only used if `ntfy_enable_traefik` == true +# default: 80 +# format: int +ntfy_port: + +# (optional) podman network to bind ntfy container to +# default: management-net +# format: string +ntfy_network: + +# (optional) URL of the ntfy webui +# default: ntfy.delpilar.net +# format: url +ntfy_url: +``` +## Templates + +### ntfy.container.j2 +This template is used to generate .container files for ntfy services. This template includes a section for traefik labels. + +## Execution +To run this role without running all other roles use the following command +```bash +ansible-playbook site.yaml --tags "ntfy" +``` diff --git a/roles/ntfy_server/defaults/main.yaml b/roles/ntfy_server/defaults/main.yaml new file mode 100644 index 0000000..36ceb29 --- /dev/null +++ b/roles/ntfy_server/defaults/main.yaml @@ -0,0 +1,9 @@ +--- +ntfy_name: ntfy +ntfy_image: docker.io/binwiederhier/ntfy +container_owner: "{{ ansible_user }}" +ntfy_enable_traefik: true +ntfy_subdomain: ntfy +ntfy_port: 80 +ntfy_network: management-net +ntfy_url: ntfy.delpilar.net diff --git a/roles/ntfy_server/handlers/main.yaml b/roles/ntfy_server/handlers/main.yaml new file mode 100644 index 0000000..81cfa15 --- /dev/null +++ b/roles/ntfy_server/handlers/main.yaml @@ -0,0 +1,6 @@ +- name: Restart ntfy on Change + ansible.builtin.systemd: + name: "{{ ntfy_name }}" + state: restarted + scope: user + daemon_reload: true diff --git a/roles/ntfy_server/tasks/main.yml b/roles/ntfy_server/tasks/main.yml new file mode 100644 index 0000000..a805f41 --- /dev/null +++ b/roles/ntfy_server/tasks/main.yml @@ -0,0 +1,40 @@ +--- +- name: Create ntfy Directories + ansible.builtin.file: + path: "{{ podman_config_base_dir }}/{{ ntfy_name }}/{{ item }}" + state: directory + mode: "0755" + loop: + - "/cache" + - "/auth" + +- name: Create ntfy Quadlet + ansible.builtin.template: + src: ntfy.container.j2 + dest: "{{ podman_quadlet_base_dir }}/management/{{ ntfy_name }}.container" + owner: "{{ ansible_user }}" + mode: "644" + notify: Restart ntfy on Change + +- name: Flush Handlers + ansible.builtin.meta: flush_handlers + +- name: Start ntfy Servers + ansible.builtin.systemd: + name: "{{ ntfy_name }}" + state: started + scope: user + +- name: Verify server is running + ansible.builtin.uri: + url: "https://{{ ntfy_url }}/ansible_test" + method: POST + body: "Deployment Successful! ntfy is online" + url_username: "{{ ntfy_web_user }}" + url_password: "{{ ntfy_web_pass }}" + force_basic_auth: true + status_code: 200 + register: ntfy_health_check + until: ntfy_health_check.status == 200 + retries: 10 + delay: 3 diff --git a/roles/ntfy_server/templates/ntfy.container.j2 b/roles/ntfy_server/templates/ntfy.container.j2 new file mode 100644 index 0000000..a289ec1 --- /dev/null +++ b/roles/ntfy_server/templates/ntfy.container.j2 @@ -0,0 +1,57 @@ +# {{ ansible_managed }} +[Unit] +After=network-online.target + +StartLimitBurst=10 +StartLimitIntervalSec=120 + +[Container] +ContainerName={{ ntfy_name }} +Image={{ ntfy_image }} + +Network={{ ntfy_network | default('management-net', true) }} + +AutoUpdate=registry + +Label=category=management +Label=owner={{ container_owner | default('jdelpilar', true) }} + +{% if ntfy_enable_traefik | default(true) %} +Label=traefik.enable=true +Label=traefik.http.routers.{{ ntfy_name }}.rule=Host(`{{ ntfy_url | default(ntfy_name + base_domain, true) }}`) +Label=traefik.http.routers.{{ ntfy_name }}.entrypoints={{ traefik_entrypoint | default('websecure', true) }} +Label=traefik.http.routers.{{ ntfy_name }}.tls.certresolver={{ traefik_resolver | default('cloudflare', true) }} +Label=traefik.http.services.{{ ntfy_name }}.loadbalancer.server.port={{ ntfy_port | default(80, true) }} +Label=traefik.docker.network={{ ntfy_network | default('management-net', true) }} +Label=traefik.http.routers.{{ ntfy_name }}.tls=true +{% endif %} + +Volume={{ podman_config_base_dir }}/{{ ntfy_name }}/cache:/var/cache/ntfy +Volume={{ podman_config_base_dir }}/{{ ntfy_name }}/auth:/var/lib/ntfy +{% if item.volumes is defined %} +{% for volume in item.volumes %} +Volume={{ volume }} +{% endfor %} +{% endif %} + +Environment=TZ={{ timezone | default('America/Los_Angeles') }} +Environment=NTFY_BASE_URL=https://{{ ntfy_url | default(ntfy_name + base_domain, true) }} +Environment=NTFY_AUTH_FILE=/var/lib/ntfy/user.db +Environment=NTFY_AUTH_DEFAULT_ACCESS=deny-all +Environment=NTFY_ENABLE_LOGIN=true +Environment=NTFY_REQUIRE_LOGIN=true +Environment=NTFY_AUTH_USERS="{% for user in ntfy_users %}{{ user['username'] }}:{{ user['pass'] }}:{{ user['level'] }}{{ ',' if not loop.last }}{% endfor %}" +{% if ntfy_env is defined %} +{% for key, value in ntfy_env.items() | sort %} +Environment={{ key }}={{ value }} +{% endfor %} +{% endif %} + +Exec=serve + +[Service] +Restart=on-failure +RestartSec=5 + +[Install] +WantedBy=default.target diff --git a/site.yaml b/site.yaml index 6e15623..2385154 100644 --- a/site.yaml +++ b/site.yaml @@ -36,3 +36,11 @@ - minecraft_server tags: - minecraft + +- name: Ntfy Setup + hosts: ntfy_servers + roles: + - ntfy_server + tags: + - ntfy + - comms