From 3c87e0c74b9ebff1e22c99f941d5d9920e01588e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicolas=20Mass=C3=A9?= Date: Thu, 4 Jun 2026 10:00:08 +0000 Subject: [PATCH] add cookbook: ntfy --- cookbooks/ntfy/Makefile | 12 +++ cookbooks/ntfy/README.md | 62 +++++++++++++ cookbooks/ntfy/SPECS.md | 101 ++++++++++++++++++++++ cookbooks/ntfy/config/examples/server.yml | 29 +++++++ cookbooks/ntfy/ntfy.container | 49 +++++++++++ cookbooks/ntfy/ntfy.image | 9 ++ cookbooks/ntfy/ntfy.target | 13 +++ cookbooks/ntfy/other/postgresql/ntfy.sql | 5 ++ cookbooks/ntfy/other/traefik/ntfy.yaml | 12 +++ cookbooks/ntfy/overlay.bu | 9 ++ cookbooks/ntfy/tmpfiles.d/ntfy.conf | 2 + 11 files changed, 303 insertions(+) create mode 100644 cookbooks/ntfy/Makefile create mode 100644 cookbooks/ntfy/README.md create mode 100644 cookbooks/ntfy/SPECS.md create mode 100644 cookbooks/ntfy/config/examples/server.yml create mode 100644 cookbooks/ntfy/ntfy.container create mode 100644 cookbooks/ntfy/ntfy.image create mode 100644 cookbooks/ntfy/ntfy.target create mode 100644 cookbooks/ntfy/other/postgresql/ntfy.sql create mode 100644 cookbooks/ntfy/other/traefik/ntfy.yaml create mode 100644 cookbooks/ntfy/overlay.bu create mode 100644 cookbooks/ntfy/tmpfiles.d/ntfy.conf diff --git a/cookbooks/ntfy/Makefile b/cookbooks/ntfy/Makefile new file mode 100644 index 0000000..91ee07b --- /dev/null +++ b/cookbooks/ntfy/Makefile @@ -0,0 +1,12 @@ +## +## Makefile for ntfy quadlet +## + +DEPENDENCIES = postgresql traefik + +# ntfy quadlet is mapped to the 10027 user (ntfy) and 10000 group (itix-svc) +PROJECT_UID = 10027 +PROJECT_GID = 10000 + +# Include common Makefile +include ../../scripts/common.mk diff --git a/cookbooks/ntfy/README.md b/cookbooks/ntfy/README.md new file mode 100644 index 0000000..aee0a88 --- /dev/null +++ b/cookbooks/ntfy/README.md @@ -0,0 +1,62 @@ +# Podman Quadlet: ntfy + +## Overview + +ntfy is a simple HTTP-based pub-sub notification service started as a Podman Quadlet. It lets you send push notifications to your phone or desktop via scripts from any computer. + +This cookbook: + +- Runs ntfy as a rootless container with minimal privileges (UID 10027). +- Uses PostgreSQL as the database backend (requires the `postgresql` cookbook). +- Stores attachment cache on virtiofs (`/var/lib/virtiofs/data/ntfy`). +- Exposes ntfy through Traefik (requires the `traefik` cookbook). +- Includes health checks to monitor the service status. +- Supports automatic container image updates via Podman auto-update. + +## Prerequisites + +- The `postgresql` cookbook must be installed and running. +- The `traefik` cookbook must be installed and running. +- The `base` cookbook must be installed (provides the virtiofs mount). +- Configuration file `/etc/quadlets/ntfy/server.yml` must exist. + +## Usage + +Copy and customize the example configuration: + +```sh +sudo cp config/examples/server.yml /etc/quadlets/ntfy/server.yml +sudo vi /etc/quadlets/ntfy/server.yml +``` + +In a separate terminal, follow the logs: + +```sh +sudo make tail-logs +``` + +Install the Podman Quadlets and start ntfy: + +```sh +sudo make clean install +``` + +You should see the **ntfy.service** waiting for PostgreSQL to be available, then starting up. + +Verify ntfy is running: + +```sh +curl -sSf http://127.0.0.1:8080/v1/health +``` + +Restart the **ntfy.target** unit: + +```sh +sudo systemctl restart ntfy.target +``` + +Finally, remove the quadlets, their configuration and their data: + +```sh +sudo make uninstall clean +``` diff --git a/cookbooks/ntfy/SPECS.md b/cookbooks/ntfy/SPECS.md new file mode 100644 index 0000000..9b6c1e3 --- /dev/null +++ b/cookbooks/ntfy/SPECS.md @@ -0,0 +1,101 @@ +# Specification for ntfy Quadlet Cookbook + +You will have to develop a Quadlet cookbook for ntfy.sh, the self-hosted notification server. + +## Architecture + +Ntfy is a web application, deployed as a container image, available here: `docker.io/binwiederhier/ntfy:v2`. + +Ntfy relies on a PostgreSQL database to store its data. It also uses a cache directory for attachments (that you have to store on virtiofs). +You will also have to expose it through Traefik. + +## Common requirements + +- Each docker image MUST have its quadlet .image file. +- Each cookbook MUST have a dedicated unique UID. The GID is 10000. +- Persistent data MUST be stored on virtiofs (`/var/lib/virtiofs/data/ntfy`). + +## Sample commands for deployment + +You will have to convert the following command to a Quadlet recipe: + +```sh +docker run -v /etc/ntfy:/etc/ntfy -v /var/cache/ntfy:/var/cache/ntfy -e TZ=UTC -p 8080:8080 -u $UID:$GID -it binwiederhier/ntfy serve +``` + +Other example, using Docker Compose: + +```yaml +services: + ntfy: + image: binwiederhier/ntfy + container_name: ntfy + command: + - serve + environment: + - TZ=UTC # optional: set desired timezone + user: $UID:$GID # optional: replace with your own user/group or uid/gid + volumes: + - /var/cache/ntfy:/var/cache/ntfy + - /etc/ntfy:/etc/ntfy + ports: + - 8080:8080 + healthcheck: # optional: remember to adapt the host:port to your environment + test: ["CMD-SHELL", "wget -q --tries=1 http://localhost:8080/v1/health -O - | grep -Eo '\"healthy\"\\s*:\\s*true' || exit 1"] + interval: 60s + timeout: 10s + retries: 3 + start_period: 40s + restart: unless-stopped + init: true # needed, if healthcheck is used. Prevents zombie processes +``` + +## Security + +Directly set the UID and GID in the quadlet file (no mapping). +Use the host network, like other quadlet cookbooks. +Let's Encrypt certificates will be handled by Traefik, so no need to worry about that in the ntfy cookbook. + +## Configuration + +The configuration file for ntfy (`/etc/ntfy/server.yml` inside the container) is in YAML format. + +```yaml +# Server +base-url: "https://ntfy.itix.fr" +behind-proxy: true +listen-http: "127.0.0.1:8080" + +# Database +database-url: "postgres://user:pass@host:5432/ntfy" + +# Access control +auth-default-access: "deny-all" +auth-users: + # fields are: login:bcrypt-hashed-password:role (admin or user) + - "admin:$2b$REDACTED:admin" +enable-login: true +require-login: true + +# Attachments +attachment-cache-dir: "/var/cache/ntfy/attachments" +attachment-file-size-limit: "100M" +attachment-total-size-limit: "50G" +attachment-expiry-duration: "48h" + +# Message cache +cache-duration: "48h" + +# Upstream +upstream-base-url: "https://ntfy.sh" +``` + +## Useful examples + +You can copy the structure of the `miniflux` cookbook, which is also a web application relying on a database and exposed through Traefik. +For virtiofs persistent storage, have a look at the `redis` or `postgresql` cookbooks. + +## Useful links + +- [Installation guide](https://ntfy.sh/docs/install/) +- [Configuration reference](https://ntfy.sh/docs/config/) diff --git a/cookbooks/ntfy/config/examples/server.yml b/cookbooks/ntfy/config/examples/server.yml new file mode 100644 index 0000000..1868951 --- /dev/null +++ b/cookbooks/ntfy/config/examples/server.yml @@ -0,0 +1,29 @@ +# Server +base-url: "http://ntfy/" +behind-proxy: true +listen-http: "127.0.0.1:8080" + +# Database +database-url: "postgres://ntfy:ntfy@localhost/ntfy?sslmode=disable" + +# Access control +auth-default-access: "deny-all" +auth-users: + # fields are: login:bcrypt-hashed-password:role (admin or user) + # the following bcrypt hash has been generated with: + # echo -ne "admin\nadmin" | podman run -i --rm docker.io/binwiederhier/ntfy:v2 user hash + - "admin:$2a$10$9t74/X77vkvZJ.ZEBOd1aukjxwl5xk7FVtI99ywQ8rdqjPJiY9fHm:admin" +enable-login: true +require-login: true + +# Attachments (stored on virtiofs) +attachment-cache-dir: "/var/cache/ntfy/attachments" +attachment-file-size-limit: "100M" +attachment-total-size-limit: "50G" +attachment-expiry-duration: "48h" + +# Message cache +cache-duration: "48h" + +# Upstream (for iOS push notifications) +upstream-base-url: "https://ntfy.sh" diff --git a/cookbooks/ntfy/ntfy.container b/cookbooks/ntfy/ntfy.container new file mode 100644 index 0000000..3a8fa43 --- /dev/null +++ b/cookbooks/ntfy/ntfy.container @@ -0,0 +1,49 @@ +[Unit] +Description=ntfy - Simple HTTP-based pub-sub notification service +Documentation=https://docs.ntfy.sh/ +After=network.target +RequiresMountsFor=/var/lib/virtiofs/data + +# Only start if ntfy has been configured +ConditionPathExists=/etc/quadlets/ntfy/server.yml + +# Start/stop this unit when the target is started/stopped +PartOf=ntfy.target + +[Container] +ContainerName=ntfy +Image=ntfy.image +AutoUpdate=registry + +# Network configuration +Network=host + +# No need for root privileges +User=10027 +Group=10000 + +# Command +Exec=serve + +# Volume mounts +Volume=/etc/quadlets/ntfy/server.yml:/etc/ntfy/server.yml:ro,z +Volume=/var/lib/virtiofs/data/ntfy:/var/cache/ntfy:Z + +# Health check +HealthCmd=wget -q --tries=1 http://localhost:8080/v1/health -O - | grep -Eo '"healthy"\s*:\s*true' || exit 1 +HealthInterval=60s +HealthTimeout=10s +HealthStartPeriod=40s +HealthRetries=3 + +[Service] +Restart=always +RestartSec=10 +TimeoutStartSec=120 +TimeoutStopSec=30 + +# Wait for PostgreSQL to be ready on localhost +ExecStartPre=/bin/sh -c 'exec 2>/dev/null; for try in $(seq 0 12); do if ! /bin/true 5<> /dev/tcp/127.0.0.1/5432; then echo "Waiting for PostgreSQL to be available..."; sleep 5; else exit 0; fi; done; exit 1' + +[Install] +WantedBy=ntfy.target diff --git a/cookbooks/ntfy/ntfy.image b/cookbooks/ntfy/ntfy.image new file mode 100644 index 0000000..6234930 --- /dev/null +++ b/cookbooks/ntfy/ntfy.image @@ -0,0 +1,9 @@ +[Unit] +Description=podman pull docker.io/binwiederhier/ntfy +Documentation=https://docs.ntfy.sh/ + +# Only start if ntfy has been configured +ConditionPathExists=/etc/quadlets/ntfy/server.yml + +[Image] +Image=docker.io/binwiederhier/ntfy:v2 diff --git a/cookbooks/ntfy/ntfy.target b/cookbooks/ntfy/ntfy.target new file mode 100644 index 0000000..e3608ee --- /dev/null +++ b/cookbooks/ntfy/ntfy.target @@ -0,0 +1,13 @@ +[Unit] +Description=ntfy Service Target +Documentation=man:systemd.target(5) +Requires=postgresql.target ntfy.service +After=postgresql.target ntfy.service + +# Allow isolation - can stop/start this target independently +AllowIsolate=yes +# Only start if ntfy has been configured +ConditionPathExists=/etc/quadlets/ntfy/server.yml + +[Install] +WantedBy=multi-user.target diff --git a/cookbooks/ntfy/other/postgresql/ntfy.sql b/cookbooks/ntfy/other/postgresql/ntfy.sql new file mode 100644 index 0000000..e94bfd1 --- /dev/null +++ b/cookbooks/ntfy/other/postgresql/ntfy.sql @@ -0,0 +1,5 @@ +-- Initialization script for ntfy database and user +CREATE USER ntfy WITH PASSWORD 'ntfy'; +CREATE DATABASE ntfy OWNER ntfy; +GRANT ALL PRIVILEGES ON DATABASE ntfy TO ntfy; +ALTER ROLE ntfy SET client_encoding TO 'utf8'; diff --git a/cookbooks/ntfy/other/traefik/ntfy.yaml b/cookbooks/ntfy/other/traefik/ntfy.yaml new file mode 100644 index 0000000..c40c3f9 --- /dev/null +++ b/cookbooks/ntfy/other/traefik/ntfy.yaml @@ -0,0 +1,12 @@ +http: + routers: + ntfy: + rule: "Host(`ntfy`)" + entryPoints: + - http + service: "ntfy" + services: + ntfy: + loadBalancer: + servers: + - url: "http://127.0.0.1:8080" diff --git a/cookbooks/ntfy/overlay.bu b/cookbooks/ntfy/overlay.bu new file mode 100644 index 0000000..dab9e52 --- /dev/null +++ b/cookbooks/ntfy/overlay.bu @@ -0,0 +1,9 @@ +variant: fcos +version: 1.4.0 +passwd: + users: + - name: ntfy + uid: 10027 + gecos: ntfy + home_dir: /var/lib/quadlets/ntfy + primary_group: itix-svc diff --git a/cookbooks/ntfy/tmpfiles.d/ntfy.conf b/cookbooks/ntfy/tmpfiles.d/ntfy.conf new file mode 100644 index 0000000..3474d98 --- /dev/null +++ b/cookbooks/ntfy/tmpfiles.d/ntfy.conf @@ -0,0 +1,2 @@ +d$ /var/lib/virtiofs/data/ntfy 0700 10027 10000 - +d$ /var/lib/virtiofs/data/ntfy/attachments 0700 10027 10000 -