11 changed files with 303 additions and 0 deletions
@ -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 |
||||
@ -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 |
||||
|
``` |
||||
@ -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/) |
||||
@ -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" |
||||
@ -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 |
||||
@ -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 |
||||
@ -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 |
||||
@ -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'; |
||||
@ -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" |
||||
@ -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 |
||||
@ -0,0 +1,2 @@ |
|||||
|
d$ /var/lib/virtiofs/data/ntfy 0700 10027 10000 - |
||||
|
d$ /var/lib/virtiofs/data/ntfy/attachments 0700 10027 10000 - |
||||
Loading…
Reference in new issue