Files
admin/.tasks-imported/vds-ntfy-push.md
vitya 85b3ee847f import-stage: .tasks/ via split (temp prefix, await STATUS.md merge)
git-subtree-dir: .tasks-imported
git-subtree-mainline: c40418239e
git-subtree-split: cbdc2b39aa
2026-05-21 13:48:02 +03:00

6.6 KiB
Raw Blame History

vds-ntfy-push

Goal

Self-host ntfy.sh на VDS для push-нотификаций на телефон. Docker-image binwiederhier/ntfy. Использовать для:

  • vds-backup-rsync-kreknin backup status (вместо/параллельно email на vitya.kuznetsov@gmail.com)
  • Monitoring alerts (CMS down, traefik certs истекают, disk fill, registry GC fail, etc.)
  • Любые ad-hoc уведомления от ops-скриптов

Domain: ntfy.vds.kzntsv.site (под уже существующим wildcard *.vds.kzntsv.site).

Open questions

  • Auth model: per-topic ACL + admin user vitya (resolved 2026-05-20). Реализация: NTFY_AUTH_DEFAULT_ACCESS=deny-all + ntfy user add --role=admin vitya. Admin role → rw to all topics, per-topic ACL не нужен. Anon (no creds) → 403 на publish и subscribe .
  • Топики: начали с двух: vds-backup (для vds-backup-rsync-kreknin) + vds-ops (general alerts: cert expiry, disk fill, container crashes). Можно добавлять без миграции — admin role даёт доступ ко всему автоматом.
  • Phone app: Android (resolved 2026-05-20) → ntfy Android app, pure self-host без APNs middleware.
  • Persistence: sqlite в /opt/stacks/ntfy/data/ (cache.db + auth.db). Volume mount ./data:/var/cache/ntfy.
  • Phone-side smoke: confirmed 2026-05-20 22:54 MSK — оба push'а («Topic renamed», «Password updated») пришли в Android ntfy app, screenshot в conversation.

Implementation sketch

/opt/stacks/ntfy/docker-compose.yml:

services:
  ntfy:
    image: binwiederhier/ntfy
    container_name: ntfy
    restart: unless-stopped
    command:
      - serve
    networks:
      - proxy
    volumes:
      - ./data:/var/cache/ntfy
      - ./etc:/etc/ntfy
    environment:
      NTFY_BASE_URL: https://ntfy.vds.kzntsv.site
      NTFY_CACHE_FILE: /var/cache/ntfy/cache.db
      NTFY_AUTH_FILE: /var/cache/ntfy/auth.db
      NTFY_AUTH_DEFAULT_ACCESS: deny-all
      NTFY_BEHIND_PROXY: true
      NTFY_ATTACHMENT_CACHE_DIR: /var/cache/ntfy/attachments
    labels:
      - traefik.enable=true
      - traefik.http.routers.ntfy.rule=Host(`ntfy.vds.kzntsv.site`)
      - traefik.http.routers.ntfy.entrypoints=websecure
      - traefik.http.routers.ntfy.tls.certresolver=letsEncrypt
      - traefik.http.services.ntfy.loadbalancer.server.port=80

networks:
  proxy:
    external: true

После up — docker exec ntfy ntfy user add --role=admin vitya → задать пароль → грант access на топики.

Publish from any script:

curl -u "vitya:$NTFY_PASS" \
  -H "Title: VDS backup completed" \
  -H "Tags: white_check_mark" \
  -H "Priority: default" \
  -d "Size 12.5G, 18m12s, kreknin OK" \
  https://ntfy.vds.kzntsv.site/vds-backup

Phone subscribes to https://ntfy.vds.kzntsv.site/vds-backup (с basic auth) — получает уведомление.

Decisions log

  • 2026-05-20 — Image pinned binwiederhier/ntfy:v2.11.0 (conservative, matches pin policy остальных стэков: traefik v2.11, portainer 2.21.5).
  • 2026-05-20 — Auth: NTFY_AUTH_DEFAULT_ACCESS=deny-all + admin user vitya (random hex16 password). Admin role гранит rw ко всем топикам автоматом — per-topic ntfy access calls returned user vitya is an admin user, access control entries have no effect. Не нужны явные grants; разлогиниться от admin позже если будет потребность в ограниченных read-only users.
  • 2026-05-20 — Topics: vds-backup + vds-ops (минимум для текущих нужд). Расширение бесплатное (admin → rw to all).
  • 2026-05-20 — TLS через traefik LE http-01 (issuer R12, valid Aug 18 2026); ntfy listens HTTP :80 внутри proxy network, NTFY_BEHIND_PROXY=true чтобы trust X-Forwarded-* от traefik.
  • 2026-05-20 — Smoke green: anon publish/subscribe → 403, authed publish vds-backup+vds-ops → 200 с JSON id/ts.
  • 2026-05-20 — User feedback: random hex password неудобен для ввода в phone app. Меняю на Pryakhin9 (consistency с TRAEFIK_DASHBOARD_PASS). Trade-off: меньше entropy, но scope ограничен (admin role, single user, push-сервис без чувствительных данных, can rotate если threat model изменится). Lesson — для human-input creds (phone/web UI) использовать память user'а, не random hex; random hex оставить для script-only (DB, API keys).
  • 2026-05-20 — Topics renamed backupvds-backup, opsvds-ops (consistency с VDS scope, упрощает filtering если позже добавятся nas-* или kreknin-* топики).
  • 2026-05-20 — Phone-verified: Android ntfy app subscription → оба push'а пришли в течение секунд. End-to-end complete.

Completed steps

  • /opt/stacks/ntfy/{data} создано (owner vitya:vitya)
  • docker-compose.yml написан с traefik labels (Host, websecure, letsEncrypt, port 80)
  • docker compose up -d → container healthy, listening :80
  • Admin user vitya добавлен через printf "%s\n%s\n" "$PASS" "$PASS" | docker exec -i ntfy ntfy user add --role=admin vitya
  • LE cert issued via http-01, R12, valid 2026-05-20 → 2026-08-18
  • Smoke test (publish/subscribe/anon-deny) green
  • Creds saved: /opt/stacks/ntfy/.env (chmod 600) + Windows ~/projects/.common/secrets/vds-kzntsv.env
  • Password rotated random-hex → Pryakhin9 per user request (ntfy user change-pass)
  • Topics renamed backup/opsvds-backup/vds-ops
  • Phone-verified (Android ntfy app, screenshot 22:54 MSK)

Notes

  • Триггер: после [[vds-kzntsv-bootstrap]] Phase 3 и до или одновременно с [[vds-backup-rsync-kreknin]] (backup pipeline хочет ntfy для статус-уведомлений).
  • Интегрируется с [[vds-backup-rsync-kreknin]] — backup script публикует через ntfy + дублируется email'ом для надёжности.
  • Android setup hint: ntfy app → Add subscription → Topic vds-backup (или vds-ops) → Server https://ntfy.vds.kzntsv.site → enable «Use auth» → user vitya / password из vds-kzntsv.env.
  • Cert renewal — automatic via traefik (letsEncrypt resolver), нет дополнительных шагов.