**ACLs (Access Control Lists):** - Ajout du type AclConfig dans sources/event.v (allowed_ips CIDR + allowed_tokens) - Chaque source webhook (Gitea, Uptime Kuma, Cron, Generic) supporte les ACLs - Validation IP via X-Forwarded-For / X-Real-IP avant HMAC - Validation Bearer token via Authorization header - Tests: 7 tests ACL (IP exact, CIDR, parse IPv4, ACL vide, IP+token combiné) **Plugin system:** - sources/plugin.v: runner exécutable externe, stdout JSON → Event - Exit 0 = publish, exit ≠ 0 = skip. Timeout configurable - Plugin loop dans server.v (goroutine, toutes les 60s) - Example: scripts/example-plugin-disk.sh (vérifie espace disque) **Docs:** - README.md: ajout source Plugin + section Features complète - ARCHITECTURE.md: flux Plugin, flux ACL, endpoints /metrics /api/silence - ROADMAP.md: Phase 5 → 8/8 complet, ajout v0.6.0 - ntfy-bridge.example.yaml: sections ACLs et Plugins commentées - Version bump: 0.5.0 → 0.6.0
This commit is contained in:
+335
-295
@@ -1,295 +1,335 @@
|
||||
# ntfy-bridge — Architecture
|
||||
|
||||
## Philosophie
|
||||
|
||||
ntfy-bridge est un **daemon HTTP léger** écrit en V (~1000 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, retry avec backoff
|
||||
- **Silencieux par défaut** — ne spamme pas, utilise les bons niveaux de priorité Ntfy (1-5)
|
||||
- **Extensible** — ajouter une source = ajouter un fichier dans `sources/` + une route webhook
|
||||
- **Zéro dépendance externe** — 100% stdlib V (`net.http`, `json`, `yaml`, `time`, `os`, `flag`)
|
||||
|
||||
## Architecture globale
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ ntfy-bridge │
|
||||
│ │
|
||||
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌────────────┐ │
|
||||
│ │ HTTP │ │ Docker │ │ HTTP │ │ Cron/ │ │
|
||||
│ │ Server │ │ Watcher │ │ Poller │ │ Generic │ │
|
||||
│ │ :9090 │ │ goroutine│ │ goroutine│ │ Receiver │ │
|
||||
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ └─────┬──────┘ │
|
||||
│ │ │ │ │ │
|
||||
│ └──────────────┼──────────────┼──────────────┘ │
|
||||
│ ▼ ▼ │
|
||||
│ ┌──────────────────────────┐ │
|
||||
│ │ Event Pipeline │ │
|
||||
│ │ ┌────────┐ ┌────────┐ │ │
|
||||
│ │ │Parse & │ │Dedup & │ │ │
|
||||
│ │ │Format │──│RateLim │ │ │
|
||||
│ │ └────────┘ └────────┘ │ │
|
||||
│ └────────────┬─────────────┘ │
|
||||
│ ▼ │
|
||||
│ ┌──────────────────────────┐ │
|
||||
│ │ Ntfy Publisher (retry) │ │
|
||||
│ │ POST /<topic> │ │
|
||||
│ │ 3 tentatives, backoff │ │
|
||||
│ └──────────────────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Flux de données
|
||||
|
||||
### 1. Webhook entrant (Gitea, Uptime Kuma, Cron, Generic)
|
||||
```
|
||||
POST /webhooks/gitea-flowdeck
|
||||
│
|
||||
▼
|
||||
1. O(1) lookup dans webhook_routes (map[string]WebhookRoute)
|
||||
2. Verify HMAC-SHA256 signature (si configuré, global)
|
||||
3. Dispatcher vers le transformer approprié (sources/transform_*.v)
|
||||
4. Parse JSON → Event struct
|
||||
5. Appliquer priority_map
|
||||
6. Appliquer template de message (via render_source_template)
|
||||
7. Pipeline publish: apply_defaults → dedup.allow → ntfy.publish
|
||||
8. POST vers Ntfy avec retry (3 tentatives, backoff 1s/2s/4s)
|
||||
```
|
||||
|
||||
### 2. Docker watcher (socket)
|
||||
```
|
||||
goroutine docker_watch_loop (via start_background_tasks)
|
||||
│
|
||||
▼
|
||||
1. Connexion au socket Docker Unix (/var/run/docker.sock)
|
||||
2. Stream events via GET /events?filters={"die":true,"oom":true,...}
|
||||
3. Filtrer par événements configurés (die, oom, health_status)
|
||||
4. Extraire container_name, image, exit_code via transform_docker_event
|
||||
5. Appliquer template
|
||||
6. Pipeline publish → Ntfy
|
||||
```
|
||||
|
||||
### 3. HTTP Poller
|
||||
```
|
||||
goroutine http_poll_loop (via start_background_tasks, toutes les N secondes)
|
||||
│
|
||||
▼
|
||||
1. GET <url> avec timeout configuré
|
||||
2. Si status ≠ expected → Event(priority=configurée, tags=["x"])
|
||||
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)
|
||||
5. State machine: poll_state[url] = is_up → notification uniquement au changement
|
||||
```
|
||||
|
||||
### 4. Cron receiver
|
||||
```
|
||||
POST /webhooks/cron-disk
|
||||
│
|
||||
▼
|
||||
1. Body = texte brut du script cron
|
||||
2. Pass-through vers Ntfy (transform_cron → Event)
|
||||
3. Le script cron contrôle son propre format
|
||||
```
|
||||
|
||||
## Modèle de données
|
||||
|
||||
```v
|
||||
// config.v
|
||||
struct ServerConfig {
|
||||
url string // URL du serveur Ntfy
|
||||
auth_token string // Token d'auth (optionnel, override via NTFY_TOKEN env)
|
||||
listen string // Adresse d'écoute HTTP (:9090)
|
||||
hmac_secret string // Secret partagé HMAC-SHA256 (override via NTFY_HMAC_SECRET env)
|
||||
}
|
||||
|
||||
struct DedupConfig {
|
||||
enabled bool
|
||||
ttl_seconds int // TTL du cache de déduplication
|
||||
rate_limit RateLimitConfig // max_per_minute, max_per_source
|
||||
}
|
||||
|
||||
// sources/event.v
|
||||
struct Event {
|
||||
source string // "gitea", "docker", "uptime_kuma", "http_poll", "cron", "generic"
|
||||
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)
|
||||
}
|
||||
```
|
||||
|
||||
Les types de sources (GiteaSource, UptimeKumaSource, DockerSource, HttpPollSource/HttpCheck, CronSource, GenericSource) sont définis dans `sources/event.v`.
|
||||
|
||||
## Pipeline de transformation
|
||||
|
||||
Chaque source a une **free function** dans `sources/` :
|
||||
|
||||
```v
|
||||
// Exemple : sources/gitea.v
|
||||
pub fn transform_gitea(raw string, source GiteaSource) ?Event { ... }
|
||||
```
|
||||
|
||||
Pas d'interface — les fonctions sont dispatchées via un `match route.kind` dans `server.v`. L'ajout d'une nouvelle source nécessite :
|
||||
1. Un fichier `sources/nouveau.v` avec la `transform_*` function
|
||||
2. Un type source dans `sources/event.v`
|
||||
3. Une entrée dans `SourcesConfig` (dans `config.v`)
|
||||
4. Une branche dans `build_webhook_routes` et `handle_webhook`
|
||||
|
||||
### Transformers inclus
|
||||
|
||||
| Transformer | Entrée | Sortie |
|
||||
|---|---|---|
|
||||
| `transform_gitea` | JSON webhook Gitea | `🔨 [FlowDeck] bruno pushed: "Fix bug" (a3f2c1d)` |
|
||||
| `transform_uptime_kuma` | JSON webhook Kuma | `🚨 Gitea is DOWN — 503 — since 14:32` |
|
||||
| `transform_docker_event` | Docker event JSON | `🐳 flowdeck exited on docker-prod-1` |
|
||||
| `transform_http_down/up` | HTTP response | `❌ og.dracodev.net/health → timeout` |
|
||||
| `transform_cron` | Raw body | Pass-through, pas de transformation |
|
||||
| `transform_generic` | Raw body | Pass-through, pas de transformation |
|
||||
|
||||
### Template engine
|
||||
|
||||
`render_source_template(template, vars)` dans `sources/event.v` — remplacement simple `{variable}` → valeur. Supporté par Gitea, Uptime Kuma, Docker, HTTP poll.
|
||||
|
||||
```
|
||||
template: "🐳 {container_name} → {status}\nImage: {image}\nHost: {host}"
|
||||
```
|
||||
|
||||
### 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 |
|
||||
|
||||
## Déduplication et rate limiting
|
||||
|
||||
- **Déduplication** : hash du message (FNV-1a-like) → cache avec TTL configurable (défaut: 5 min). Clé = `source:name:topic:hash(message)`. Un message identique dans la fenêtre TTL est ignoré.
|
||||
- **Rate limiting par topic** : `max_per_minute` notifications max par topic (défaut: 10). Fenêtre glissante de 60s.
|
||||
- **Rate limiting par source** : `max_per_source` notifications max par source configurée (défaut: 30). Fenêtre glissante de 60s.
|
||||
- **Nettoyage automatique** : les entrées expirées sont nettoyées à chaque appel `allow()`.
|
||||
|
||||
## Structure du projet
|
||||
|
||||
```
|
||||
ntfy-bridge/
|
||||
├── README.md # Vue d'ensemble, quickstart
|
||||
├── docs/
|
||||
│ ├── ARCHITECTURE.md # Ce document
|
||||
│ ├── ROADMAP.md # Phases de développement
|
||||
│ ├── CONTRIBUTING.md # Guide de contribution
|
||||
│ └── WEBHOOK_GITEA.md # Guide config webhook Gitea
|
||||
├── ntfy-bridge.example.yaml # Exemple de configuration complet
|
||||
├── main.v # Entrypoint, parsing CLI, header ASCII
|
||||
├── config.v # Chargement + validation YAML, env overrides
|
||||
├── server.v # HTTP server, routing webhooks O(1), dashboard, API
|
||||
├── ntfy.v # Client HTTP Ntfy (POST + retry backoff)
|
||||
├── dedup.v # Déduplication + rate limiting (topic + source)
|
||||
├── template.v # Application des defaults (priority, tags)
|
||||
├── log.v # Logging structuré (human + JSON)
|
||||
├── webhook.v # O(1) route dispatch (WebhookRoute map)
|
||||
├── docker_watcher.v # Watcher socket Docker (Unix seulement)
|
||||
├── dashboard.html # Interface web : stats, status, historique
|
||||
├── sources/ # Types Event + transformers
|
||||
│ ├── event.v # Types Event, Source, render_source_template
|
||||
│ ├── gitea.v # Transformer Gitea (push, PR, issue, release)
|
||||
│ ├── uptime_kuma.v # Transformer Uptime Kuma (state_map)
|
||||
│ ├── docker.v # Transformer Docker events
|
||||
│ ├── http_poll.v # Transformer HTTP poll (up/down)
|
||||
│ ├── cron.v # Transformer Cron (pass-through)
|
||||
│ └── generic.v # Transformer Generic (pass-through)
|
||||
├── *_test.v # Tests unitaires (config, gitea, uptime_kuma, dedup)
|
||||
├── scripts/
|
||||
│ └── example-cron-disk.sh # Exemple script cron → Ntfy
|
||||
└── v.mod # Dépendances V (aucune dépendance externe)
|
||||
```
|
||||
|
||||
## Dépendances
|
||||
|
||||
```v
|
||||
// v.mod
|
||||
Module {
|
||||
name: 'ntfy-bridge'
|
||||
version: '0.1.0'
|
||||
dependencies: [] // zéro dépendance externe
|
||||
}
|
||||
```
|
||||
|
||||
Le projet utilise **uniquement la stdlib V** :
|
||||
- `net.http` — serveur HTTP + client Ntfy
|
||||
- `x.json2` — parsing JSON
|
||||
- `yaml` — parsing YAML
|
||||
- `crypto.hmac`, `crypto.sha256`, `encoding.hex` — validation HMAC
|
||||
- `time`, `os`, `flag` — utilitaires
|
||||
- `net.unix` — socket Docker (Linux/macOS seulement, via `$if`)
|
||||
|
||||
## Endpoints HTTP
|
||||
|
||||
| Méthode | Path | Description |
|
||||
|---------|------|-------------|
|
||||
| GET | `/` ou `/dashboard` | Dashboard HTML |
|
||||
| GET | `/health` | Health check → `{"status":"ok","uptime_seconds":N,"total_notifications":N}` |
|
||||
| GET | `/stats` | Stats par source → `{"total":N,"errors":N,"by_source":{...}}` |
|
||||
| GET | `/api/config` | Config résumée (safe, sans secrets) |
|
||||
| GET | `/api/status` | État des health checks HTTP poll |
|
||||
| GET | `/api/history` | 100 derniers événements |
|
||||
| POST | `/webhooks/*` | Webhooks (Gitea, Uptime Kuma, Cron, Generic) |
|
||||
|
||||
## 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 --quiet --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 dashboard.html /etc/ntfy-bridge/
|
||||
WORKDIR /etc/ntfy-bridge
|
||||
EXPOSE 9090
|
||||
CMD ["/usr/local/bin/ntfy-bridge", "--quiet", "--config", "/etc/ntfy-bridge/ntfy-bridge.yaml"]
|
||||
```
|
||||
|
||||
### Option C : Compilation croisée
|
||||
```bash
|
||||
v -prod -os windows . -o ntfy-bridge.exe
|
||||
v -prod -os linux . -o ntfy-bridge
|
||||
```
|
||||
|
||||
## Sécurité
|
||||
|
||||
- **Signature webhooks** : vérification HMAC-SHA256 pour **tous** les webhooks (header `X-Hub-Signature-256: sha256=...`). Activé dès que `server.hmac_secret` est défini.
|
||||
- **Auth token** : token Bearer optionnel pour le serveur Ntfy (si auth activée sur le serveur Ntfy)
|
||||
- **Pas d'exposition externe** : le serveur HTTP écoute sur localhost par défaut (`127.0.0.1:9090`)
|
||||
- **Rate limiting interne** : par topic ET par source (configurable dans `dedup.rate_limit`)
|
||||
- **Input validation** : tout JSON entrant est validé avant processing, chemins webhook dupliqués détectés au démarrage
|
||||
- **No secrets in config** : `NTFY_TOKEN` et `NTFY_HMAC_SECRET` peuvent être passés via variables d'environnement
|
||||
|
||||
## Métriques et observabilité
|
||||
|
||||
- **Health endpoint** : `GET /health` → 200 OK + uptime + compteur de notifs
|
||||
- **Stats endpoint** : `GET /stats` → compteurs par source + erreurs
|
||||
- **API status** : `GET /api/status` → état des health checks HTTP poll
|
||||
- **API history** : `GET /api/history` → 100 derniers événements avec timestamps
|
||||
- **Dashboard** : interface HTML avec stats temps réel, statut des services, historique récent
|
||||
- **Logging** : stdout en mode human-readable (console) ou JSON structuré (`--quiet`, pour systemd)
|
||||
# ntfy-bridge — Architecture
|
||||
|
||||
## Philosophie
|
||||
|
||||
ntfy-bridge est un **daemon HTTP léger** écrit en V (~2000 lignes) qui compile en un seul binaire natif statique. Il écoute des webhooks, se connecte au socket Docker, exécute des plugins externes, 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, retry avec backoff
|
||||
- **Silencieux par défaut** — ne spamme pas, utilise les bons niveaux de priorité Ntfy (1-5)
|
||||
- **Extensible** — ajouter une source = ajouter un fichier dans `sources/` + une route webhook
|
||||
- **Zéro dépendance externe** — 100% stdlib V (`net.http`, `json`, `yaml`, `time`, `os`, `flag`)
|
||||
|
||||
## Architecture globale
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ ntfy-bridge │
|
||||
│ │
|
||||
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌────────────┐ ┌──────────┐ │
|
||||
│ │ HTTP │ │ Docker │ │ HTTP │ │ Cron/ │ │ Plugin │ │
|
||||
│ │ Server │ │ Watcher │ │ Poller │ │ Generic │ │ Runner │ │
|
||||
│ │ :9090 │ │ goroutine│ │ goroutine│ │ Receiver │ │ goroutine│ │
|
||||
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ └─────┬──────┘ └────┬─────┘ │
|
||||
│ │ │ │ │ │ │
|
||||
│ └──────────────┼──────────────┼──────────────┼──────────────┘ │
|
||||
│ ▼ ▼ │
|
||||
│ ┌──────────────────────────┐ │
|
||||
│ │ Event Pipeline │ │
|
||||
│ │ ┌────────┐ ┌────────┐ │ │
|
||||
│ │ │Parse & │ │Dedup & │ │ │
|
||||
│ │ │Format │──│RateLim │ │ │
|
||||
│ │ └────────┘ └────────┘ │ │
|
||||
│ └────────────┬─────────────┘ │
|
||||
│ ▼ │
|
||||
│ ┌──────────────────────────┐ │
|
||||
│ │ Ntfy Publisher (retry) │ │
|
||||
│ │ POST /<topic> │ │
|
||||
│ │ 3 tentatives, backoff │ │
|
||||
│ └──────────────────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Flux de données
|
||||
|
||||
### 1. Webhook entrant (Gitea, Uptime Kuma, Cron, Generic)
|
||||
```
|
||||
POST /webhooks/gitea-flowdeck
|
||||
│
|
||||
▼
|
||||
1. O(1) lookup dans webhook_routes (map[string]WebhookRoute)
|
||||
2. Verify HMAC-SHA256 signature (si configuré, global)
|
||||
3. Dispatcher vers le transformer approprié (sources/transform_*.v)
|
||||
4. Parse JSON → Event struct
|
||||
5. Appliquer priority_map
|
||||
6. Appliquer template de message (via render_source_template)
|
||||
7. Pipeline publish: apply_defaults → dedup.allow → ntfy.publish
|
||||
8. POST vers Ntfy avec retry (3 tentatives, backoff 1s/2s/4s)
|
||||
```
|
||||
|
||||
### 2. Docker watcher (socket)
|
||||
```
|
||||
goroutine docker_watch_loop (via start_background_tasks)
|
||||
│
|
||||
▼
|
||||
1. Connexion au socket Docker Unix (/var/run/docker.sock)
|
||||
2. Stream events via GET /events?filters={"die":true,"oom":true,...}
|
||||
3. Filtrer par événements configurés (die, oom, health_status)
|
||||
4. Extraire container_name, image, exit_code via transform_docker_event
|
||||
5. Appliquer template
|
||||
6. Pipeline publish → Ntfy
|
||||
```
|
||||
|
||||
### 3. HTTP Poller
|
||||
```
|
||||
goroutine http_poll_loop (via start_background_tasks, toutes les N secondes)
|
||||
│
|
||||
▼
|
||||
1. GET <url> avec timeout configuré
|
||||
2. Si status ≠ expected → Event(priority=configurée, tags=["x"])
|
||||
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)
|
||||
5. State machine: poll_state[url] = is_up → notification uniquement au changement
|
||||
```
|
||||
|
||||
### 4. Cron receiver
|
||||
```
|
||||
POST /webhooks/cron-disk
|
||||
│
|
||||
▼
|
||||
1. Body = texte brut du script cron
|
||||
2. Pass-through vers Ntfy (transform_cron → Event)
|
||||
3. Le script cron contrôle son propre format
|
||||
```
|
||||
|
||||
### 5. Plugin runner (v0.6)
|
||||
```
|
||||
goroutine plugin_loop (via start_background_tasks, toutes les 60s)
|
||||
│
|
||||
▼
|
||||
1. Exécute le binaire configuré (os.execute)
|
||||
2. Lit stdout → parse JSON → Event struct
|
||||
3. Exit 0 = publier, exit ≠ 0 = skip (pas de notif)
|
||||
4. Supporte priority, tags, message, click_url, actions
|
||||
5. Pipeline publish → Ntfy
|
||||
```
|
||||
|
||||
### 6. ACL pipeline (v0.6)
|
||||
```
|
||||
POST /webhooks/*
|
||||
│
|
||||
▼
|
||||
1. Extraire IP client (X-Forwarded-For > X-Real-IP)
|
||||
2. Extraire Bearer token (Authorization header)
|
||||
3. Si ACL configurée sur le webhook → valider IP et/ou token
|
||||
4. Si échec → 403 Forbidden
|
||||
5. Sinon → continuer le pipeline normal
|
||||
```
|
||||
|
||||
## Modèle de données
|
||||
|
||||
```v
|
||||
// config.v
|
||||
struct ServerConfig {
|
||||
url string // URL du serveur Ntfy
|
||||
auth_token string // Token d'auth (optionnel, override via NTFY_TOKEN env)
|
||||
listen string // Adresse d'écoute HTTP (:9090)
|
||||
hmac_secret string // Secret partagé HMAC-SHA256 (override via NTFY_HMAC_SECRET env)
|
||||
}
|
||||
|
||||
struct DedupConfig {
|
||||
enabled bool
|
||||
ttl_seconds int // TTL du cache de déduplication
|
||||
rate_limit RateLimitConfig // max_per_minute, max_per_source
|
||||
}
|
||||
|
||||
// sources/event.v
|
||||
struct Event {
|
||||
source string // "gitea", "docker", "uptime_kuma", "http_poll", "cron", "generic", "plugin"
|
||||
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)
|
||||
click_url string // URL ouverte au clic sur la notif (v0.5)
|
||||
actions []Action // boutons d'action Ntfy (v0.5)
|
||||
}
|
||||
```
|
||||
|
||||
Les types de sources (GiteaSource, UptimeKumaSource, DockerSource, HttpPollSource/HttpCheck, CronSource, GenericSource, PluginSource) et AclConfig sont définis dans `sources/event.v`.
|
||||
|
||||
## Pipeline de transformation
|
||||
|
||||
Chaque source a une **free function** dans `sources/` :
|
||||
|
||||
```v
|
||||
// Exemple : sources/gitea.v
|
||||
pub fn transform_gitea(raw string, source GiteaSource) ?Event { ... }
|
||||
```
|
||||
|
||||
Pas d'interface — les fonctions sont dispatchées via un `match route.kind` dans `server.v`. L'ajout d'une nouvelle source nécessite :
|
||||
1. Un fichier `sources/nouveau.v` avec la `transform_*` function
|
||||
2. Un type source dans `sources/event.v`
|
||||
3. Une entrée dans `SourcesConfig` (dans `config.v`)
|
||||
4. Une branche dans `build_webhook_routes` et `handle_webhook`
|
||||
|
||||
### Transformers inclus
|
||||
|
||||
| Transformer | Entrée | Sortie |
|
||||
|---|---|---|
|
||||
| `transform_gitea` | JSON webhook Gitea | `🔨 [FlowDeck] bruno pushed: "Fix bug" (a3f2c1d)` |
|
||||
| `transform_uptime_kuma` | JSON webhook Kuma | `🚨 Gitea is DOWN — 503 — since 14:32` |
|
||||
| `transform_docker_event` | Docker event JSON | `🐳 flowdeck exited on docker-prod-1` |
|
||||
| `transform_http_down/up` | HTTP response | `❌ og.dracodev.net/health → timeout` |
|
||||
| `transform_cron` | Raw body | Pass-through, pas de transformation |
|
||||
| `transform_generic` | Raw body | Pass-through, pas de transformation |
|
||||
| `transform_plugin` | stdout d'un exécutable externe | JSON → Event (exit 0 = publish, ≠ 0 = skip) |
|
||||
|
||||
### Template engine
|
||||
|
||||
`render_source_template(template, vars)` dans `sources/event.v` — remplacement simple `{variable}` → valeur. Supporté par Gitea, Uptime Kuma, Docker, HTTP poll.
|
||||
|
||||
```
|
||||
template: "🐳 {container_name} → {status}\nImage: {image}\nHost: {host}"
|
||||
```
|
||||
|
||||
### 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 |
|
||||
|
||||
## Déduplication et rate limiting
|
||||
|
||||
- **Déduplication** : hash du message (FNV-1a-like) → cache avec TTL configurable (défaut: 5 min). Clé = `source:name:topic:hash(message)`. Un message identique dans la fenêtre TTL est ignoré.
|
||||
- **Rate limiting par topic** : `max_per_minute` notifications max par topic (défaut: 10). Fenêtre glissante de 60s.
|
||||
- **Rate limiting par source** : `max_per_source` notifications max par source configurée (défaut: 30). Fenêtre glissante de 60s.
|
||||
- **Nettoyage automatique** : les entrées expirées sont nettoyées à chaque appel `allow()`.
|
||||
|
||||
## Structure du projet
|
||||
|
||||
```
|
||||
ntfy-bridge/
|
||||
├── README.md # Vue d'ensemble, quickstart
|
||||
├── docs/
|
||||
│ ├── ARCHITECTURE.md # Ce document
|
||||
│ ├── ROADMAP.md # Phases de développement
|
||||
│ ├── CONTRIBUTING.md # Guide de contribution
|
||||
│ └── WEBHOOK_GITEA.md # Guide config webhook Gitea
|
||||
├── ntfy-bridge.example.yaml # Exemple de configuration complet
|
||||
├── main.v # Entrypoint, parsing CLI, header ASCII
|
||||
├── config.v # Chargement + validation YAML, env overrides
|
||||
├── server.v # HTTP server, routing webhooks O(1), dashboard, API
|
||||
├── ntfy.v # Client HTTP Ntfy (POST + retry backoff)
|
||||
├── dedup.v # Déduplication + rate limiting (topic + source)
|
||||
├── template.v # Application des defaults (priority, tags)
|
||||
├── log.v # Logging structuré (human + JSON)
|
||||
├── webhook.v # O(1) route dispatch (WebhookRoute map)
|
||||
├── docker_watcher.v # Watcher socket Docker (Unix seulement)
|
||||
├── filter_outgoing.v # Filtres avancés + outgoing webhooks + state persistence
|
||||
├── metrics.v # Endpoint Prometheus /metrics
|
||||
├── service.v # Installation/désinstallation de service (systemd, openrc, nssm)
|
||||
├── docker_install.v # Génération de stack docker-compose
|
||||
├── dashboard.html # Interface web : stats, status, historique
|
||||
├── sources/ # Types Event + transformers
|
||||
│ ├── event.v # Types Event, Source, render_source_template
|
||||
│ ├── gitea.v # Transformer Gitea (push, PR, issue, release)
|
||||
│ ├── uptime_kuma.v # Transformer Uptime Kuma (state_map)
|
||||
│ ├── docker.v # Transformer Docker events
|
||||
│ ├── http_poll.v # Transformer HTTP poll (up/down)
|
||||
│ ├── cron.v # Transformer Cron (pass-through)
|
||||
│ ├── generic.v # Transformer Generic (pass-through)
|
||||
│ └── plugin.v # Transformer Plugin (exec stdout JSON)
|
||||
├── *_test.v # Tests unitaires (config, gitea, uptime_kuma, dedup, filter_outgoing, metrics, acl)
|
||||
├── scripts/
|
||||
│ ├── example-cron-disk.sh # Exemple script cron → Ntfy
|
||||
│ ├── example-plugin-disk.sh # Exemple plugin disk check
|
||||
│ ├── install.sh # Script d'installation Linux one-liner
|
||||
│ └── install.ps1 # Script d'installation Windows PowerShell
|
||||
└── v.mod # Dépendances V (aucune dépendance externe)
|
||||
```
|
||||
|
||||
## Dépendances
|
||||
|
||||
```v
|
||||
// v.mod
|
||||
Module {
|
||||
name: 'ntfy-bridge'
|
||||
version: '0.1.0'
|
||||
dependencies: [] // zéro dépendance externe
|
||||
}
|
||||
```
|
||||
|
||||
Le projet utilise **uniquement la stdlib V** :
|
||||
- `net.http` — serveur HTTP + client Ntfy
|
||||
- `x.json2` — parsing JSON
|
||||
- `yaml` — parsing YAML
|
||||
- `crypto.hmac`, `crypto.sha256`, `encoding.hex` — validation HMAC
|
||||
- `time`, `os`, `flag` — utilitaires
|
||||
- `net.unix` — socket Docker (Linux/macOS seulement, via `$if`)
|
||||
|
||||
## Endpoints HTTP
|
||||
|
||||
| Méthode | Path | Description |
|
||||
|---------|------|-------------|
|
||||
| GET | `/` ou `/dashboard` | Dashboard HTML |
|
||||
| GET | `/health` | Health check → `{"status":"ok","uptime_seconds":N,"total_notifications":N}` |
|
||||
| GET | `/stats` | Stats par source → `{"total":N,"errors":N,"by_source":{...}}` |
|
||||
| GET | `/api/config` | Config résumée (safe, sans secrets) |
|
||||
| GET | `/api/status` | État des health checks HTTP poll |
|
||||
| GET | `/api/history` | 100 derniers événements |
|
||||
| GET | `/api/silence` | État du silence (actif/restant) |
|
||||
| POST | `/api/silence?duration=30m` | Activer le silence |
|
||||
| DELETE | `/api/silence` | Désactiver le silence |
|
||||
| GET | `/metrics` | Métriques Prometheus / OpenMetrics |
|
||||
| POST | `/webhooks/*` | Webhooks (Gitea, Uptime Kuma, Cron, Generic) |
|
||||
|
||||
## 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 --quiet --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 dashboard.html /etc/ntfy-bridge/
|
||||
WORKDIR /etc/ntfy-bridge
|
||||
EXPOSE 9090
|
||||
CMD ["/usr/local/bin/ntfy-bridge", "--quiet", "--config", "/etc/ntfy-bridge/ntfy-bridge.yaml"]
|
||||
```
|
||||
|
||||
### Option C : Compilation croisée
|
||||
```bash
|
||||
v -prod -os windows . -o ntfy-bridge.exe
|
||||
v -prod -os linux . -o ntfy-bridge
|
||||
```
|
||||
|
||||
## Sécurité
|
||||
|
||||
- **ACLs par webhook** (v0.6) : restriction par IP (CIDR ou exacte) et/ou Bearer token. Validé avant HMAC.
|
||||
- **Signature webhooks** : vérification HMAC-SHA256 pour **tous** les webhooks (header `X-Hub-Signature-256: sha256=...`). Activé dès que `server.hmac_secret` est défini.
|
||||
- **Auth token** : token Bearer optionnel pour le serveur Ntfy (si auth activée sur le serveur Ntfy)
|
||||
- **Pas d'exposition externe** : le serveur HTTP écoute sur localhost par défaut (`127.0.0.1:9090`)
|
||||
- **Rate limiting interne** : par topic ET par source (configurable dans `dedup.rate_limit`)
|
||||
- **Input validation** : tout JSON entrant est validé avant processing, chemins webhook dupliqués détectés au démarrage
|
||||
- **No secrets in config** : `NTFY_TOKEN` et `NTFY_HMAC_SECRET` peuvent être passés via variables d'environnement
|
||||
|
||||
## Métriques et observabilité
|
||||
|
||||
- **Health endpoint** : `GET /health` → 200 OK + uptime + compteur de notifs
|
||||
- **Stats endpoint** : `GET /stats` → compteurs par source + erreurs
|
||||
- **API status** : `GET /api/status` → état des health checks HTTP poll
|
||||
- **API history** : `GET /api/history` → 100 derniers événements avec timestamps
|
||||
- **Dashboard** : interface HTML avec stats temps réel, statut des services, historique récent
|
||||
- **Logging** : stdout en mode human-readable (console) ou JSON structuré (`--quiet`, pour systemd)
|
||||
|
||||
+177
-169
@@ -1,169 +1,177 @@
|
||||
# ntfy-bridge — Roadmap
|
||||
|
||||
## Phase 1 : MVP (core engine) — v0.1.0 ✓ DONE
|
||||
|
||||
Objectif : daemon qui compile, lit une config, écoute des webhooks et envoie vers Ntfy.
|
||||
|
||||
- [x] `main.v` — CLI args (`--config`, `--version`, `--validate`, `--quiet`, `--help`)
|
||||
- [x] `config.v` — Chargement + validation du YAML (env overrides `NTFY_TOKEN`, `NTFY_HMAC_SECRET`, détection de chemins dupliqués)
|
||||
- [x] `server.v` — HTTP server sur `:9090` avec routing O(1), dashboard, health/stats endpoints
|
||||
- [x] `event.v` — Types de données : `Event`, `Config`, `Source` + template engine `render_source_template`
|
||||
- [x] `ntfy.v` — Client HTTP POST vers Ntfy (topic, message, priority, tags) + retry avec backoff exponentiel (3 tentatives)
|
||||
- [x] `sources/generic.v` — Webhook passe-partout (POST raw → Ntfy)
|
||||
- [x] `log.v` — Logging structuré (timestamp, level, message) — dual mode: human-readable + JSON
|
||||
- [x] `ntfy-bridge.example.yaml` — Config d'exemple complète et commentée
|
||||
|
||||
**Sortie** : `ntfy-bridge` compile, accepte un webhook générique et l'envoie vers Ntfy. ✅
|
||||
|
||||
## Phase 2 : Sources spécialisées — v0.2.0 ✓ DONE
|
||||
|
||||
- [x] `sources/gitea.v` — Transformer Gitea (push, PR, issues, releases)
|
||||
- Parse le JSON webhook Gitea
|
||||
- Template configurable avec variables `{repo}`, `{user}`, `{action}`, `{title}`, `{sha}`
|
||||
- Priority map par type d'événement
|
||||
- [x] `sources/uptime_kuma.v` — Transformer Uptime Kuma
|
||||
- Parse le JSON webhook Kuma (heartbeat)
|
||||
- `state_map` pour configurer priority/tags par état (up/down)
|
||||
- Template configurable avec variables `{monitor}`, `{status}`, `{msg}`, `{ping}`, `{time}`
|
||||
- [x] `sources/docker.v` — Watcher socket Docker
|
||||
- Connexion au socket Docker Unix (`docker_watcher.v`)
|
||||
- Filtrage par événements (die, health_status, oom)
|
||||
- Template de message configurable
|
||||
- Multi-hôtes (plusieurs sockets dans la config)
|
||||
- [x] `sources/http_poll.v` — Poller HTTP
|
||||
- Goroutine de polling périodique (intervalle configurable)
|
||||
- Vérification status code + timeout
|
||||
- State machine up/down avec notification uniquement au changement
|
||||
- Template configurable avec variables `{url}`, `{error}`, `{status}`
|
||||
- [x] `dedup.v` — Déduplication + rate limiting
|
||||
- Cache avec TTL (5 min par défaut) basé sur hash du message
|
||||
- Clé de déduplication : source + type + topic + hash(message)
|
||||
- Rate limiting par topic et par source
|
||||
|
||||
**Bonus (non planifié) :**
|
||||
- [x] `sources/cron.v` — Webhook pour scripts cron (pass-through)
|
||||
- [x] `webhook.v` — Route dispatch O(1) via `map[string]WebhookRoute`
|
||||
|
||||
**Sortie** : Toutes les sources majeures sont intégrées et fonctionnelles. ✅
|
||||
|
||||
## Phase 3 : Robustesse — v0.3.0 ✅ DONE
|
||||
|
||||
- [x] ~~`dedup.v` amélioré~~ — Rate limiting déjà intégré dans la phase 2
|
||||
- [x] Signature webhooks — Vérification HMAC-SHA256 (appliquée à **tous** les webhooks, pas seulement Gitea)
|
||||
- [x] Retry logic — Retry avec backoff exponentiel vers Ntfy (3 tentatives, 1s/2s/4s)
|
||||
- [x] Graceful shutdown — SIGTERM/SIGINT → drain des événements en cours (Linux/macOS)
|
||||
- [x] Health endpoint — `GET /health` → 200 + uptime + stats basiques
|
||||
- [x] Stats endpoint — `GET /stats` → compteurs par source
|
||||
- [x] Config reload — `SIGHUP` → reload config sans redémarrage
|
||||
- [x] Tests unitaires — `config_test.v`, `gitea_test.v`, `uptime_kuma_test.v`, `dedup_test.v`
|
||||
|
||||
**Sortie** : Le daemon est prêt pour la production homelab. ✅
|
||||
|
||||
## Phase 4 : Qualité de vie — v0.4.0 ✅ DONE
|
||||
|
||||
- [x] Template engine — Support des variables `{key}` dans les messages (toutes les sources)
|
||||
```
|
||||
"🐳 {container_name} → {status} (exit: {exit_code}) on {host}"
|
||||
```
|
||||
- [x] Silence rules — `POST /api/silence?duration=30m` + `GET /api/silence` + `DELETE /api/silence`
|
||||
- [x] Grouping — Regrouper N notifications similaires (buffer 10s, flush avec ×N)
|
||||
- [x] Webhook secret validation — HMAC global (tous les webhooks, pas seulement Gitea)
|
||||
- [x] CI/CD via Gitea Actions — `.gitea/workflows/ci.yml`
|
||||
|
||||
**Sortie** : Expérience utilisateur complète. ✅
|
||||
|
||||
## Phase 4.5 : Déploiement multi-plateforme — v0.4.5 ✅ DONE
|
||||
|
||||
Objectif : un seul binaire, une seule commande pour installer/désinstaller le service sur tous les OS supportés.
|
||||
|
||||
- [x] `service.v` — Module d'installation de service unifié
|
||||
- Commande `--install-service` : détecte l'OS et installe le service
|
||||
- Commande `--uninstall-service` : désinstalle le service
|
||||
- Commande `--service-status` : vérifie si le service est installé + running
|
||||
- [x] Windows Service — Intégration native Windows
|
||||
- Installation via `sc.exe` (Service Control Manager)
|
||||
- Support des événements start/stop/query
|
||||
- Options de recovery configurées (restart auto)
|
||||
- [x] Linux Debian/Ubuntu (systemd) — `ntfy-bridge.service`
|
||||
- Création + activation automatique du unit file
|
||||
- `--quiet` par défaut (logging JSON → journald)
|
||||
- Restart=always, démarrage après network.target
|
||||
- [x] Linux Alpine (openrc) — Script init.d
|
||||
- `/etc/init.d/ntfy-bridge` généré automatiquement
|
||||
- Commandes start/stop/restart/status
|
||||
- Ajout au runlevel default
|
||||
- [x] Raspberry Pi (Debian aarch64) — Support confirmé
|
||||
- Cross-compilation `v -os linux -arch arm64`
|
||||
- Même unit systemd que Debian x86_64
|
||||
- Testé sur Raspberry Pi OS (arm64)
|
||||
- [x] Docker image multi-arch — `Dockerfile` + `docker-compose.yml`
|
||||
- Build multi-stage (V from source → minimal Alpine runtime)
|
||||
- docker-compose.yml prêt à l'emploi avec healthcheck
|
||||
- [x] Scripts d'installation one-liner
|
||||
- `scripts/install.sh` : détecte l'OS/arch, build V, installe le service
|
||||
- `scripts/install.ps1` : équivalent Windows PowerShell
|
||||
- `curl -fsSL https://.../install.sh | bash`
|
||||
|
||||
**Sortie** : `ntfy-bridge --install-service` fonctionne sur Windows 10+, Debian 11+, Alpine 3.18+, Raspberry Pi OS (arm64). ✅
|
||||
|
||||
## Phase 5 : Futures idées — v0.5.0+ (6/8)
|
||||
|
||||
- [x] Web UI minimale — Dashboard avec statuts, historique, API endpoints
|
||||
- [x] Filtres avancés — Expressions conditionnelles par source
|
||||
```yaml
|
||||
filters:
|
||||
rules:
|
||||
- match_source: "docker"
|
||||
match_name: "test-*"
|
||||
action: drop
|
||||
```
|
||||
- [x] Notifications structurées — Support des actions Ntfy (boutons click)
|
||||
- Champ `click_url` pour ouvrir une URL au clic
|
||||
- Champ `actions` pour boutons d'action (view, broadcast, http)
|
||||
- [x] Intégration Prometheus — Métriques exposées au format OpenMetrics
|
||||
- Endpoint `GET /metrics` : gauges (uptime, poll_state, silence) + counters (notifications, errors, http_polls)
|
||||
- Format compatible Prometheus/Grafana
|
||||
- [x] Webhook sortant — Forwarder les événements vers Slack, Discord, JSON
|
||||
- [ ] Support multi-utilisateurs — ACLs par webhook_path
|
||||
- [x] Fichier d'état — Persistance de l'état up/down des health checks
|
||||
- [ ] Plugin system — Sources customisables via dll/.so
|
||||
- url: "https://hooks.slack.com/..."
|
||||
format: slack
|
||||
- url: "https://discord.com/api/webhooks/..."
|
||||
format: discord
|
||||
```
|
||||
- [ ] Support multi-utilisateurs — ACLs par webhook_path
|
||||
- [x] Fichier d'état — Persistance de l'état up/down des health checks
|
||||
```yaml
|
||||
state:
|
||||
file: "/var/lib/ntfy-bridge/state.json"
|
||||
```
|
||||
- Sauvegarde `poll_state` (up/down) + `silence_until`
|
||||
- Restauré au démarrage, persisté à chaque changement
|
||||
- [ ] Plugin system — Sources customisables via dll/.so
|
||||
|
||||
## Fonctionnalités non planifiées mais implémentées
|
||||
|
||||
Ces features ont été ajoutées en cours de route :
|
||||
|
||||
| Feature | Fichier | Description |
|
||||
|---------|---------|-------------|
|
||||
| Source Cron | `sources/cron.v` | Webhook pass-through pour scripts cron (curl → Ntfy) |
|
||||
| Dashboard web | `dashboard.html` | Interface avec stats, statut des health checks, historique récent |
|
||||
| Logging JSON | `log.v` | Mode JSON structuré activé avec `--quiet` (pour systemd) |
|
||||
| Env overrides | `config.v` | `NTFY_TOKEN` et `NTFY_HMAC_SECRET` depuis l'environnement |
|
||||
| Validation dupplicates | `config.v` | Détection de chemins webhook dupliqués dans la config |
|
||||
| Flag `--quiet` | `main.v` | Supprime la bannière ASCII au démarrage |
|
||||
| Flag `-V` (version) | `main.v` | Version courte en plus de `--version` |
|
||||
| API endpoints | `server.v` | `/api/config`, `/api/status`, `/api/history` pour le dashboard |
|
||||
|
||||
## Suivi des versions
|
||||
|
||||
| Version | Statut | Contenu |
|
||||
|---------|--------|---------|
|
||||
| v0.1.0 | ✅ Terminé | Moteur core + webhook générique + logging |
|
||||
| v0.2.0 | ✅ Terminé | Sources Gitea, Uptime Kuma, Docker, HTTP poll, Cron, déduplication |
|
||||
| v0.3.0 | ✅ Terminé | Robustesse, HMAC, retry, graceful shutdown, SIGHUP reload, tests |
|
||||
| v0.4.0 | ✅ Terminé | Templates, silence, grouping, HMAC global, CI/CD |
|
||||
| v0.4.5 | ✅ Terminé | Déploiement multi-plateforme : --install-service, Windows, systemd, openrc, Docker, install scripts |
|
||||
| v0.5.0+ | 🚧 6/8 | Dashboard, filtres, actions Ntfy, Prometheus, outgoing webhooks, state file — manque multi-user, plugins |
|
||||
# ntfy-bridge — Roadmap
|
||||
|
||||
## Phase 1 : MVP (core engine) — v0.1.0 ✓ DONE
|
||||
|
||||
Objectif : daemon qui compile, lit une config, écoute des webhooks et envoie vers Ntfy.
|
||||
|
||||
- [x] `main.v` — CLI args (`--config`, `--version`, `--validate`, `--quiet`, `--help`)
|
||||
- [x] `config.v` — Chargement + validation du YAML (env overrides `NTFY_TOKEN`, `NTFY_HMAC_SECRET`, détection de chemins dupliqués)
|
||||
- [x] `server.v` — HTTP server sur `:9090` avec routing O(1), dashboard, health/stats endpoints
|
||||
- [x] `event.v` — Types de données : `Event`, `Config`, `Source` + template engine `render_source_template`
|
||||
- [x] `ntfy.v` — Client HTTP POST vers Ntfy (topic, message, priority, tags) + retry avec backoff exponentiel (3 tentatives)
|
||||
- [x] `sources/generic.v` — Webhook passe-partout (POST raw → Ntfy)
|
||||
- [x] `log.v` — Logging structuré (timestamp, level, message) — dual mode: human-readable + JSON
|
||||
- [x] `ntfy-bridge.example.yaml` — Config d'exemple complète et commentée
|
||||
|
||||
**Sortie** : `ntfy-bridge` compile, accepte un webhook générique et l'envoie vers Ntfy. ✅
|
||||
|
||||
## Phase 2 : Sources spécialisées — v0.2.0 ✓ DONE
|
||||
|
||||
- [x] `sources/gitea.v` — Transformer Gitea (push, PR, issues, releases)
|
||||
- Parse le JSON webhook Gitea
|
||||
- Template configurable avec variables `{repo}`, `{user}`, `{action}`, `{title}`, `{sha}`
|
||||
- Priority map par type d'événement
|
||||
- [x] `sources/uptime_kuma.v` — Transformer Uptime Kuma
|
||||
- Parse le JSON webhook Kuma (heartbeat)
|
||||
- `state_map` pour configurer priority/tags par état (up/down)
|
||||
- Template configurable avec variables `{monitor}`, `{status}`, `{msg}`, `{ping}`, `{time}`
|
||||
- [x] `sources/docker.v` — Watcher socket Docker
|
||||
- Connexion au socket Docker Unix (`docker_watcher.v`)
|
||||
- Filtrage par événements (die, health_status, oom)
|
||||
- Template de message configurable
|
||||
- Multi-hôtes (plusieurs sockets dans la config)
|
||||
- [x] `sources/http_poll.v` — Poller HTTP
|
||||
- Goroutine de polling périodique (intervalle configurable)
|
||||
- Vérification status code + timeout
|
||||
- State machine up/down avec notification uniquement au changement
|
||||
- Template configurable avec variables `{url}`, `{error}`, `{status}`
|
||||
- [x] `dedup.v` — Déduplication + rate limiting
|
||||
- Cache avec TTL (5 min par défaut) basé sur hash du message
|
||||
- Clé de déduplication : source + type + topic + hash(message)
|
||||
- Rate limiting par topic et par source
|
||||
|
||||
**Bonus (non planifié) :**
|
||||
- [x] `sources/cron.v` — Webhook pour scripts cron (pass-through)
|
||||
- [x] `webhook.v` — Route dispatch O(1) via `map[string]WebhookRoute`
|
||||
|
||||
**Sortie** : Toutes les sources majeures sont intégrées et fonctionnelles. ✅
|
||||
|
||||
## Phase 3 : Robustesse — v0.3.0 ✅ DONE
|
||||
|
||||
- [x] ~~`dedup.v` amélioré~~ — Rate limiting déjà intégré dans la phase 2
|
||||
- [x] Signature webhooks — Vérification HMAC-SHA256 (appliquée à **tous** les webhooks, pas seulement Gitea)
|
||||
- [x] Retry logic — Retry avec backoff exponentiel vers Ntfy (3 tentatives, 1s/2s/4s)
|
||||
- [x] Graceful shutdown — SIGTERM/SIGINT → drain des événements en cours (Linux/macOS)
|
||||
- [x] Health endpoint — `GET /health` → 200 + uptime + stats basiques
|
||||
- [x] Stats endpoint — `GET /stats` → compteurs par source
|
||||
- [x] Config reload — `SIGHUP` → reload config sans redémarrage
|
||||
- [x] Tests unitaires — `config_test.v`, `gitea_test.v`, `uptime_kuma_test.v`, `dedup_test.v`
|
||||
|
||||
**Sortie** : Le daemon est prêt pour la production homelab. ✅
|
||||
|
||||
## Phase 4 : Qualité de vie — v0.4.0 ✅ DONE
|
||||
|
||||
- [x] Template engine — Support des variables `{key}` dans les messages (toutes les sources)
|
||||
```
|
||||
"🐳 {container_name} → {status} (exit: {exit_code}) on {host}"
|
||||
```
|
||||
- [x] Silence rules — `POST /api/silence?duration=30m` + `GET /api/silence` + `DELETE /api/silence`
|
||||
- [x] Grouping — Regrouper N notifications similaires (buffer 10s, flush avec ×N)
|
||||
- [x] Webhook secret validation — HMAC global (tous les webhooks, pas seulement Gitea)
|
||||
- [x] CI/CD via Gitea Actions — `.gitea/workflows/ci.yml`
|
||||
|
||||
**Sortie** : Expérience utilisateur complète. ✅
|
||||
|
||||
## Phase 4.5 : Déploiement multi-plateforme — v0.4.5 ✅ DONE
|
||||
|
||||
Objectif : un seul binaire, une seule commande pour installer/désinstaller le service sur tous les OS supportés.
|
||||
|
||||
- [x] `service.v` — Module d'installation de service unifié
|
||||
- Commande `--install-service` : détecte l'OS et installe le service
|
||||
- Commande `--uninstall-service` : désinstalle le service
|
||||
- Commande `--service-status` : vérifie si le service est installé + running
|
||||
- [x] Windows Service — Intégration native Windows
|
||||
- Installation via `sc.exe` (Service Control Manager)
|
||||
- Support des événements start/stop/query
|
||||
- Options de recovery configurées (restart auto)
|
||||
- [x] Linux Debian/Ubuntu (systemd) — `ntfy-bridge.service`
|
||||
- Création + activation automatique du unit file
|
||||
- `--quiet` par défaut (logging JSON → journald)
|
||||
- Restart=always, démarrage après network.target
|
||||
- [x] Linux Alpine (openrc) — Script init.d
|
||||
- `/etc/init.d/ntfy-bridge` généré automatiquement
|
||||
- Commandes start/stop/restart/status
|
||||
- Ajout au runlevel default
|
||||
- [x] Raspberry Pi (Debian aarch64) — Support confirmé
|
||||
- Cross-compilation `v -os linux -arch arm64`
|
||||
- Même unit systemd que Debian x86_64
|
||||
- Testé sur Raspberry Pi OS (arm64)
|
||||
- [x] Docker image multi-arch — `Dockerfile` + `docker-compose.yml`
|
||||
- Build multi-stage (V from source → minimal Alpine runtime)
|
||||
- docker-compose.yml prêt à l'emploi avec healthcheck
|
||||
- [x] Scripts d'installation one-liner
|
||||
- `scripts/install.sh` : détecte l'OS/arch, build V, installe le service
|
||||
- `scripts/install.ps1` : équivalent Windows PowerShell
|
||||
- `curl -fsSL https://.../install.sh | bash`
|
||||
|
||||
**Sortie** : `ntfy-bridge --install-service` fonctionne sur Windows 10+, Debian 11+, Alpine 3.18+, Raspberry Pi OS (arm64). ✅
|
||||
|
||||
## Phase 5 : Futures idées — v0.5.0+ (8/8) ✅ DONE
|
||||
|
||||
- [x] Web UI minimale — Dashboard avec statuts, historique, API endpoints
|
||||
- [x] Filtres avancés — Expressions conditionnelles par source
|
||||
```yaml
|
||||
filters:
|
||||
rules:
|
||||
- match_source: "docker"
|
||||
match_name: "test-*"
|
||||
action: drop
|
||||
```
|
||||
- [x] Notifications structurées — Support des actions Ntfy (boutons click)
|
||||
- Champ `click_url` pour ouvrir une URL au clic
|
||||
- Champ `actions` pour boutons d'action (view, broadcast, http)
|
||||
- [x] Intégration Prometheus — Métriques exposées au format OpenMetrics
|
||||
- Endpoint `GET /metrics` : gauges (uptime, poll_state, silence) + counters (notifications, errors, http_polls)
|
||||
- Format compatible Prometheus/Grafana
|
||||
- [x] Webhook sortant — Forwarder les événements vers Slack, Discord, JSON
|
||||
- [x] Support multi-utilisateurs — ACLs par webhook_path
|
||||
```yaml
|
||||
acl:
|
||||
allowed_ips: ["192.168.30.5", "10.0.0.0/8"]
|
||||
allowed_tokens: ["my-secret-token"]
|
||||
```
|
||||
- [x] Fichier d'état — Persistance de l'état up/down des health checks
|
||||
```yaml
|
||||
state:
|
||||
file: "/var/lib/ntfy-bridge/state.json"
|
||||
```
|
||||
- Sauvegarde `poll_state` (up/down) + `silence_until`
|
||||
- Restauré au démarrage, persisté à chaque changement
|
||||
- [x] Plugin system — Sources customisables via executables externes
|
||||
```yaml
|
||||
plugin:
|
||||
- name: "Disk space check"
|
||||
topic: daily
|
||||
command: /etc/ntfy-bridge/plugins/disk-check.sh
|
||||
timeout: 10
|
||||
```
|
||||
- Contrat simple : stdin (optionnel) → stdout JSON
|
||||
- Exit 0 = notif, exit ≠ 0 = skip
|
||||
- Compatible bash, Python, ou n'importe quel langage
|
||||
|
||||
## Fonctionnalités non planifiées mais implémentées
|
||||
|
||||
Ces features ont été ajoutées en cours de route :
|
||||
|
||||
| Feature | Fichier | Description |
|
||||
|---------|---------|-------------|
|
||||
| Source Cron | `sources/cron.v` | Webhook pass-through pour scripts cron (curl → Ntfy) |
|
||||
| Dashboard web | `dashboard.html` | Interface avec stats, statut des health checks, historique récent |
|
||||
| Logging JSON | `log.v` | Mode JSON structuré activé avec `--quiet` (pour systemd) |
|
||||
| Env overrides | `config.v` | `NTFY_TOKEN` et `NTFY_HMAC_SECRET` depuis l'environnement |
|
||||
| Validation dupplicates | `config.v` | Détection de chemins webhook dupliqués dans la config |
|
||||
| Flag `--quiet` | `main.v` | Supprime la bannière ASCII au démarrage |
|
||||
| Flag `-V` (version) | `main.v` | Version courte en plus de `--version` |
|
||||
| API endpoints | `server.v` | `/api/config`, `/api/status`, `/api/history` pour le dashboard |
|
||||
|
||||
## Suivi des versions
|
||||
|
||||
| Version | Statut | Contenu |
|
||||
|---------|--------|---------|
|
||||
| v0.1.0 | ✅ Terminé | Moteur core + webhook générique + logging |
|
||||
| v0.2.0 | ✅ Terminé | Sources Gitea, Uptime Kuma, Docker, HTTP poll, Cron, déduplication |
|
||||
| v0.3.0 | ✅ Terminé | Robustesse, HMAC, retry, graceful shutdown, SIGHUP reload, tests |
|
||||
| v0.4.0 | ✅ Terminé | Templates, silence, grouping, HMAC global, CI/CD |
|
||||
| v0.4.5 | ✅ Terminé | Déploiement multi-plateforme : --install-service, Windows, systemd, openrc, Docker, install scripts |
|
||||
| v0.5.0 | ✅ Terminé | Dashboard, filtres, actions Ntfy, Prometheus, outgoing webhooks, state file |
|
||||
| v0.6.0 | ✅ Terminé | ACLs multi-utilisateurs, plugin system (scripts externes) |
|
||||
|
||||
Reference in New Issue
Block a user