- README.md: project overview, quickstart, sources summary - ARCHITECTURE.md: complete architecture, data flow, project structure - ROADMAP.md: 5-phase development plan (v0.1.0 → v0.5.0+) - CONTRIBUTING.md: dev setup, conventions, how to add a source - ntfy-bridge.example.yaml: fully documented configuration template - scripts/example-cron-disk.sh: example cron script → ntfy-bridge
10 KiB
10 KiB
ntfy-bridge — Architecture
Philosophie
ntfy-bridge est un daemon HTTP léger écrit en V (~500 lignes) qui compile en un seul binaire natif statique. Il écoute des webhooks, se connecte au socket Docker, et poll des endpoints HTTP — puis transforme chaque événement en notification Ntfy formatée.
Principes :
- Un binaire, zéro runtime — pas de Node, Python, JVM ou conteneur obligatoire
- Config-driven — tout passe par le fichier YAML, pas de recompilation
- Fail-safe — un échec réseau vers Ntfy ne crashe pas le daemon
- Silencieux par défaut — ne spamme pas, utilise les bons niveaux de priorité Ntfy (1-5)
- Extensible — ajouter une source = implémenter un module dans
sources/
Architecture globale
┌─────────────────────────────────────────────────────────────┐
│ ntfy-bridge │
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌────────────┐ │
│ │ HTTP │ │ Docker │ │ HTTP │ │ Cron │ │
│ │ Server │ │ Watcher │ │ Poller │ │ Receiver │ │
│ │ :9090 │ │ goroutine│ │ goroutine│ │ /webhooks │ │
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ └─────┬──────┘ │
│ │ │ │ │ │
│ └──────────────┼──────────────┼──────────────┘ │
│ ▼ ▼ │
│ ┌──────────────────────────┐ │
│ │ Event Pipeline │ │
│ │ ┌────────┐ ┌────────┐ │ │
│ │ │Parse & │ │Dedup & │ │ │
│ │ │Format │──│RateLim │ │ │
│ │ └────────┘ └────────┘ │ │
│ └────────────┬─────────────┘ │
│ ▼ │
│ ┌──────────────────────────┐ │
│ │ Ntfy Publisher │ │
│ │ POST /<topic> │ │
│ └──────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
Flux de données
1. Webhook entrant (Gitea, Uptime Kuma, generic)
POST /webhooks/gitea-flowdeck
│
▼
1. Verify signature (optionnel)
2. Parse JSON → Event struct
3. Route vers le transformer approprié
4. Appliquer priority_map
5. Appliquer template de message
6. POST vers Ntfy
2. Docker watcher (socket)
goroutine DockerWatcher
│
▼
1. Connexion au socket Docker (/var/run/docker.sock)
2. Stream events via GET /events?filters=...
3. Filtrer par événements configurés (die, oom, health_status)
4. Extraire container_name, image, exit_code
5. Appliquer template
6. POST vers Ntfy
3. HTTP Poller
goroutine HttpPoller (toutes les N secondes)
│
▼
1. GET <url> avec timeout configuré
2. Si status ≠ expected → Event(priority=5)
3. Si OK et précédemment DOWN → Event(priority=1, tags="white_check_mark")
4. Sinon → rien (pas de notification si tout va bien)
4. Cron receiver
POST /webhooks/cron-disk
│
▼
1. Body = texte brut du script cron
2. Pass-through vers Ntfy
3. Le script cron contrôle son propre format
Modèle de données
struct Config {
server ServerConfig
defaults DefaultConfig
sources SourcesConfig
}
struct ServerConfig {
url string // URL du serveur Ntfy
auth_token string // Token d'auth (optionnel)
listen string // Adresse d'écoute HTTP (:9090)
}
struct DefaultConfig {
priority int // 1-5
tags []string // ["loudspeaker"]
}
struct Source {
name string
webhook_path string
topic string
template string // Go-like template
priority_map map[string]int
tags []string
}
struct Event {
source string // "gitea", "docker", "uptime_kuma", "http_poll", "cron"
name string // nom de la source configurée
topic string // topic Ntfy cible
priority int // 1-5
tags []string
message string // message formaté final
raw string // payload brut (pour debug)
}
Pipeline de transformation
Chaque source implémente l'interface :
interface Transformer {
transform(raw string, source &Source) ?Event
}
Transformers inclus
| Transformer | Entrée | Sortie |
|---|---|---|
gitea_transformer |
JSON webhook Gitea | 🔨 [FlowDeck] bruno pushed to main: "Fix bug" (a3f2c1d) |
uptime_kuma_transformer |
JSON webhook Kuma | 🚨 Gitea is DOWN — 503 — since 14:32 |
docker_transformer |
Docker event JSON | 🐳 flowdeck exited OOMKilled on docker-prod-1 |
http_poll_transformer |
HTTP response | ❌ obsigate.dracodev.net/health → timeout 30s |
generic_transformer |
Raw body | Pass-through, pas de transformation |
Priorités Ntfy
| Niveau | Usage | Exemple |
|---|---|---|
| 5 (urgent) | Service critique down, container OOM | Uptime Kuma DOWN |
| 4 (high) | PR ouverte, container crash | Gitea pull_request, Docker die |
| 3 (default) | Push, issue, activité normale | Gitea push |
| 2 (low) | Info, succès | Service back UP |
| 1 (min) | Debug, heartbeat | Health check OK (si configuré) |
Déduplication et rate limiting
- Déduplication : même source + même type d'événement + même cible → cooldown 5 minutes
- Rate limiting : max 10 notifs/minute/topic (configurable)
- Uptime Kuma spécifique : les "down" répétés dans un intervalle de 2 min sont dédupliqués
Structure du projet
ntfy-bridge/
├── README.md # Vue d'ensemble, quickstart
├── ARCHITECTURE.md # Ce document
├── ROADMAP.md # Phases de développement
├── CONTRIBUTING.md # Guide de contribution
├── ntfy-bridge.example.yaml # Exemple de configuration complet
├── src/
│ ├── main.v # Entrypoint, parsing CLI
│ ├── config.v # Chargement + validation YAML
│ ├── server.v # HTTP server + routing webhooks
│ ├── event.v # Types Event, pipeline
│ ├── ntfy.v # Client HTTP Ntfy (POST)
│ ├── dedup.v # Déduplication + rate limiting
│ ├── template.v # Mini moteur de template
│ ├── sources/
│ │ ├── gitea.v # Transformer Gitea
│ │ ├── uptime_kuma.v # Transformer Uptime Kuma
│ │ ├── docker.v # Watcher Docker socket
│ │ ├── http_poll.v # Poller HTTP périodique
│ │ └── generic.v # Webhook passe-partout
│ └── log.v # Logging structuré
├── tests/
│ ├── config_test.v # Tests validation config
│ ├── gitea_test.v # Tests transformer Gitea
│ ├── uptime_kuma_test.v # Tests transformer Kuma
│ └── dedup_test.v # Tests déduplication
├── scripts/
│ └── example-cron-disk.sh # Exemple script cron → Ntfy
└── v.mod # Dépendances V
Dépendances V (v.mod)
Module {
name: 'ntfy-bridge'
description: 'Homelab notification hub — aggregates Gitea, Docker, Uptime Kuma → Ntfy'
version: '0.1.0'
deps: [
'ui-driver.cua'
]
}
Dépendances externes minimales :
vsl— parsing YAML (sinon implémenter un parseur minimal)x.net— client HTTP (ou lib standard Vnet)json— stdlib V pour le parsing JSON
En réalité, V a tout dans sa stdlib : net.http, json, time, os, flag. Le projet vise zéro dépendance externe.
Déploiement
Option A : Binaire nu + systemd
# /etc/systemd/system/ntfy-bridge.service
[Unit]
Description=ntfy-bridge notification hub
After=network.target
[Service]
ExecStart=/usr/local/bin/ntfy-bridge --config /etc/ntfy-bridge.yaml
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.target
Option B : Conteneur Docker
FROM alpine:latest
COPY ntfy-bridge /usr/local/bin/
COPY ntfy-bridge.yaml /etc/
EXPOSE 9090
CMD ["/usr/local/bin/ntfy-bridge", "--config", "/etc/ntfy-bridge.yaml"]
Option C : Compilation croisée
v -os windows . -o ntfy-bridge.exe # Pour Windows
v -os linux . -o ntfy-bridge # Pour Linux
v -os macos . -o ntfy-bridge-mac # Pour macOS
Sécurité
- Signature webhooks : vérification HMAC-SHA256 optionnelle pour Gitea
- Auth token : token optionnel pour le serveur Ntfy (si auth activée)
- Pas d'exposition externe : le serveur HTTP écoute sur localhost par défaut (:9090)
- Rate limiting interne : max 10 notifs/minute/source pour éviter le spam
- Input validation : tout JSON entrant est validé avant processing
- No secrets in config : le token Ntfy peut être passé via variable d'environnement
NTFY_TOKEN
Métriques et observabilité
- Health endpoint :
GET /health→ 200 OK + uptime + compteur de notifs - Stats endpoint :
GET /stats→ compteurs par source, erreurs, latence Ntfy - Logging : stdout en JSON structuré (timestamp, level, source, event, error)