Browse Source

add cookbook: ntfy

main
Nicolas Massé 3 weeks ago
parent
commit
3c87e0c74b
  1. 12
      cookbooks/ntfy/Makefile
  2. 62
      cookbooks/ntfy/README.md
  3. 101
      cookbooks/ntfy/SPECS.md
  4. 29
      cookbooks/ntfy/config/examples/server.yml
  5. 49
      cookbooks/ntfy/ntfy.container
  6. 9
      cookbooks/ntfy/ntfy.image
  7. 13
      cookbooks/ntfy/ntfy.target
  8. 5
      cookbooks/ntfy/other/postgresql/ntfy.sql
  9. 12
      cookbooks/ntfy/other/traefik/ntfy.yaml
  10. 9
      cookbooks/ntfy/overlay.bu
  11. 2
      cookbooks/ntfy/tmpfiles.d/ntfy.conf

12
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

62
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
```

101
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/)

29
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"

49
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

9
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

13
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

5
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';

12
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"

9
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

2
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 -
Loading…
Cancel
Save