liab-deployments/liab_deployments/deploy/ntfy.py
2026-06-22 11:44:51 +02:00

187 lines
4.4 KiB
Python

#
# Deploy ntfy.sh server (https://ntfy.sh) pub-sub notification service
#
# An example inventory may look like this:
#
# group = [
# (
# 'edu.datalad.org',
# {
# 'ntfy': [
# {
# 'serve_address': 'ntfy.example.org',
# 'container_tag': 'binwiederhier/ntfy',
# # user name and numerical ID
# 'user': ('ntfy', '4000'),
# 'host_port': 30030,
# 'config_file_asset': 'assets/status-example-config.yaml',
# #'data_dir': 'data',
# },
# ...
#
from pyinfra.api import deploy
from pyinfra import (
host,
)
from pyinfra.operations import (
files,
server,
)
from liab_deployments.operations import (
caddy,
user,
user_systemd,
)
service_unit_tmpl = """\
[Unit]
Description=Podman-managed ntfy service
Wants=network-online.target
After=network-online.target
RequiresMountsFor=%t/containers
[Service]
Environment=PODMAN_SYSTEMD_UNIT=%n
Restart=always
TimeoutStopSec=300
ExecStartPre=/bin/rm \\
-f %t/%n.ctr-id
ExecStart=/usr/bin/podman container run \\
--cidfile=%t/%n.ctr-id \\
--cgroups=no-conmon \\
--rm \\
--sdnotify=conmon \\
-d \\
--replace \\
--pull newer \\
--name ntfy \\
-e "NTFY_BASE_URL=https://{serve_address}" \\
-e "NTFY_CACHE_FILE=/data/cache.db" \\
-e "NTFY_AUTH_FILE=/data/auth.db" \\
-e "NTFY_AUTH_DEFAULT_ACCESS=deny-all" \\
-e "NTFY_BEHIND_PROXY=true" \\
-e "NTFY_ATTACHMENT_CACHE_DIR=/data/attachments" \\
-p {host_port}:80 \\
-v {user_home}/data:/data:rw \\
-v {user_home}/config:/etc/ntfy:ro \\
{container_tag} \\
serve
ExecStop=/usr/bin/podman stop \\
--ignore -t 10 \\
--cidfile=%t/%n.ctr-id
ExecStopPost=/usr/bin/podman rm \\
-f \\
--ignore -t 10 \\
--cidfile=%t/%n.ctr-id
Type=notify
NotifyAccess=all
[Install]
WantedBy=default.target
"""
caddyfile_block_tmpl = """\
{serve_address} {{
reverse_proxy localhost:{host_port}
# Redirect HTTP to HTTPS, but only for GET topic addresses, since we want
# it to work with curl without the annoying https:// prefix
@httpget {{
protocol http
method GET
path_regexp ^/([-_a-z0-9]{{0,64}}$|docs/|static/)
}}
redir @httpget https://{{host}}{{uri}}
}}
"""
@deploy("Deploy ntfy")
def deploy_sites():
if not hasattr(host.data, 'ntfy'):
return
for spec in host.data.ntfy.get('sites', []):
_deploy_site(
spec['serve_address'],
spec['container_tag'],
spec['user'],
spec['host_port'],
name=spec.get('name', 'ntfy'),
caddyfile_block_tmpl=spec.get(
'caddyfile_block_tmpl', caddyfile_block_tmpl
),
config_file_asset=spec.get('config_file_asset'),
data_dir=spec.get('data_dir'),
)
def _deploy_site(
serve_address,
container_tag,
user_spec: tuple[str, int],
host_port,
*,
name,
caddyfile_block_tmpl,
config_file_asset=None,
data_dir=None,
):
user_name, uid = user_spec
user_home = f'/home/{user_name}'
user.systemd_service(
user_name,
uid,
user_home,
)
if data_dir is None:
data_dir = f'{user_home}/data'
for i in [f'{user_home}/config', data_dir]:
files.directory(
path=i,
present=True,
_sudo_user=user_name,
)
user_systemd.service_unit(
user_name,
user_home,
name,
service_unit_tmpl.format(
serve_address=serve_address,
user_home=user_home,
container_tag=container_tag,
host_port=host_port,
),
)
if config_file_asset:
files.put(
name='ntfy config',
src=config_file_asset,
dest=f'{user_home}/config/server.yml',
_sudo_user=user_name,
)
user_systemd.run_service(
user_name,
uid,
'ntfy',
)
server.wait(
name=f"Wait for ntfy {serve_address!r} to start",
port=host_port,
)
caddy.caddyfile_block(
marker=f'NTFY {serve_address}',
content=caddyfile_block_tmpl.format(
serve_address=serve_address,
host_port=host_port,
),
)