Add YAML config loading with validation, env overrides for secrets, and a web dashboard exposing health, stats, and recent events.
267 lines
10 KiB
Markdown
267 lines
10 KiB
Markdown
# 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
|
|
|
|
```v
|
|
struct Config { // dans config.v
|
|
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 { // types spécifiques dans sources/event.v
|
|
name string
|
|
webhook_path string
|
|
topic string
|
|
template string // Go-like template
|
|
priority_map map[string]int
|
|
tags []string
|
|
}
|
|
|
|
struct Event { // dans sources/event.v
|
|
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 :
|
|
|
|
```v
|
|
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
|
|
├── main.v # Entrypoint, parsing CLI
|
|
├── config.v # Chargement + validation YAML
|
|
├── server.v # HTTP server + routing webhooks
|
|
├── ntfy.v # Client HTTP Ntfy (POST)
|
|
├── dedup.v # Déduplication + rate limiting
|
|
├── template.v # Mini moteur de template
|
|
├── log.v # Logging structuré
|
|
├── docker_watcher.v # Watcher socket Docker (Unix)
|
|
├── sources/ # Types Event + transformers
|
|
│ ├── event.v # Types Event, Source
|
|
│ ├── gitea.v # Transformer Gitea
|
|
│ ├── uptime_kuma.v # Transformer Uptime Kuma
|
|
│ ├── docker.v # Transformer Docker events
|
|
│ ├── http_poll.v # Transformer HTTP poll
|
|
│ ├── generic.v # Webhook passe-partout
|
|
│ └── cron.v # Webhook cron
|
|
├── *_test.v # Tests unitaires
|
|
├── scripts/
|
|
│ └── example-cron-disk.sh # Exemple script cron → Ntfy
|
|
└── v.mod # Dépendances V
|
|
```
|
|
|
|
## Dépendances V (v.mod)
|
|
|
|
```v
|
|
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 V `net`)
|
|
- `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
|
|
```ini
|
|
# /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
|
|
```dockerfile
|
|
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
|
|
```bash
|
|
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)
|