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