docs: initial architecture, roadmap, readme for ntfy-bridge
- 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
This commit is contained in:
+28
@@ -0,0 +1,28 @@
|
|||||||
|
# ntfy-bridge
|
||||||
|
|
||||||
|
# Binary
|
||||||
|
ntfy-bridge
|
||||||
|
ntfy-bridge.exe
|
||||||
|
ntfy-bridge-mac
|
||||||
|
|
||||||
|
# V build artifacts
|
||||||
|
.vmodules/
|
||||||
|
*.o
|
||||||
|
*.obj
|
||||||
|
*.exe
|
||||||
|
*.so
|
||||||
|
*.dylib
|
||||||
|
|
||||||
|
# Config (contains secrets — use example as template)
|
||||||
|
ntfy-bridge.yaml
|
||||||
|
*.local.yaml
|
||||||
|
|
||||||
|
# IDE
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
|
||||||
|
# OS
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
+269
@@ -0,0 +1,269 @@
|
|||||||
|
# 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 {
|
||||||
|
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 :
|
||||||
|
|
||||||
|
```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
|
||||||
|
├── 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)
|
||||||
|
|
||||||
|
```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)
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
# Contributing to ntfy-bridge
|
||||||
|
|
||||||
|
## Stack
|
||||||
|
|
||||||
|
- **Langage** : [V](https://vlang.io/) — compilé, rapide, syntaxe proche de Go
|
||||||
|
- **Dépendances** : Zéro dépendance externe (stdlib V uniquement)
|
||||||
|
- **Build** : `v .` → binaire natif statique
|
||||||
|
|
||||||
|
## Setup développement
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Installer V
|
||||||
|
curl -s https://github.com/vlang/v/releases/latest/download/v_linux.zip -o /tmp/v.zip
|
||||||
|
unzip /tmp/v.zip -d /tmp && sudo /tmp/v/v symlink
|
||||||
|
|
||||||
|
# Cloner
|
||||||
|
git clone https://git.dracodev.net/bruno/ntfy-bridge.git
|
||||||
|
cd ntfy-bridge
|
||||||
|
|
||||||
|
# Compiler (mode dev avec hot reload)
|
||||||
|
v watch run .
|
||||||
|
|
||||||
|
# Tests
|
||||||
|
v test .
|
||||||
|
|
||||||
|
# Build production
|
||||||
|
v -prod .
|
||||||
|
```
|
||||||
|
|
||||||
|
## Structure du code
|
||||||
|
|
||||||
|
```
|
||||||
|
src/
|
||||||
|
├── main.v # Entrypoint, flag parsing, orchestration
|
||||||
|
├── config.v # Config loading + validation
|
||||||
|
├── server.v # HTTP server, webhook routing
|
||||||
|
├── event.v # Event types, pipeline
|
||||||
|
├── ntfy.v # Ntfy HTTP client
|
||||||
|
├── dedup.v # Deduplication + rate limiting
|
||||||
|
├── template.v # Mini template engine
|
||||||
|
├── log.v # Structured logging
|
||||||
|
└── sources/ # Source-specific transformers
|
||||||
|
├── gitea.v
|
||||||
|
├── uptime_kuma.v
|
||||||
|
├── docker.v
|
||||||
|
├── http_poll.v
|
||||||
|
└── generic.v
|
||||||
|
```
|
||||||
|
|
||||||
|
## Ajouter une nouvelle source
|
||||||
|
|
||||||
|
1. Créer `src/sources/ma_source.v`
|
||||||
|
2. Implémenter la fonction `transform(raw string, source &Source) ?Event`
|
||||||
|
3. Déclarer le type dans `sources` du `config.v`
|
||||||
|
4. Router l'endpoint dans `server.v`
|
||||||
|
5. Ajouter un test dans `tests/ma_source_test.v`
|
||||||
|
|
||||||
|
## Conventions de code
|
||||||
|
|
||||||
|
- **Nommage** : snake_case pour les fonctions/variables, PascalCase pour les structs
|
||||||
|
- **Gestion d'erreurs** : utiliser le type `?` (Option/Result) de V
|
||||||
|
- **Logging** : `log.info()`, `log.warn()`, `log.error()` avec contexte
|
||||||
|
- **Messages de commit** : [Conventional Commits](https://www.conventionalcommits.org/)
|
||||||
|
- `feat: add Gitea webhook transformer`
|
||||||
|
- `fix: handle Docker socket disconnect`
|
||||||
|
- `docs: update ARCHITECTURE.md`
|
||||||
|
|
||||||
|
## Tests
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Tous les tests
|
||||||
|
v test .
|
||||||
|
|
||||||
|
# Test spécifique
|
||||||
|
v test tests/gitea_test.v
|
||||||
|
|
||||||
|
# Avec verbose
|
||||||
|
v test . -stats
|
||||||
|
```
|
||||||
|
|
||||||
|
## CI/CD
|
||||||
|
|
||||||
|
Le repo utilise Gitea Actions. Le workflow dans `.gitea/workflows/ci.yml` :
|
||||||
|
- Compile en mode `-prod`
|
||||||
|
- Lance `v test .`
|
||||||
|
- Vérifie le formatting (`v fmt -verify`)
|
||||||
|
|
||||||
|
## Questions ?
|
||||||
|
|
||||||
|
Ouvrir une issue sur https://git.dracodev.net/bruno/ntfy-bridge/issues
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
# ntfy-bridge
|
||||||
|
|
||||||
|
Hub central de notifications pour homelab — agrège des sources multiples (Gitea, Docker, Uptime Kuma, health checks HTTP, scripts cron) et les transforme en notifications [Ntfy](https://ntfy.sh/) intelligentes, formatées, avec priorités et contexte.
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────┐
|
||||||
|
│ Gitea webhook │──┐
|
||||||
|
├─────────────────┤ │
|
||||||
|
│ Uptime Kuma │──┤
|
||||||
|
├─────────────────┤ │ ┌──────────────┐ ┌───────────┐ ┌──────────────┐
|
||||||
|
│ Docker events │──┤────→│ ntfy-bridge │─────→│ Ntfy Srv │─────→│ Ton phone │
|
||||||
|
├─────────────────┤ │ └──────────────┘ └───────────┘ └──────────────┘
|
||||||
|
│ HTTP health │──┤
|
||||||
|
├─────────────────┤ │
|
||||||
|
│ Scripts cron │──┘
|
||||||
|
└─────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
## Pourquoi ntfy-bridge ?
|
||||||
|
|
||||||
|
Dans un homelab avec 8+ hôtes Docker, Gitea, Uptime Kuma et des dizaines de services, les alertes arrivent de partout. `ntfy-bridge` centralise tout dans une seule file de notifications intelligentes — avec le bon niveau de priorité, le bon format, et le bon topic Ntfy.
|
||||||
|
|
||||||
|
| Sans ntfy-bridge | Avec ntfy-bridge |
|
||||||
|
|---|---|
|
||||||
|
| Webhook Gitea → email (noyé) | Push = notif Ntfy formatée avec auteur, commit, message |
|
||||||
|
| Uptime Kuma → alerte brute | "🚨 Gitea DOWN — 503 — since 14:32" avec priority=5 |
|
||||||
|
| Container qui crashe → logs Docker | "🐳 flowdeck exited OOMKilled on docker-prod-1" |
|
||||||
|
| Script cron → email perdu | Résumé quotidien disque à 9h dans ta poche |
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Installer V (si pas déjà fait)
|
||||||
|
curl -s https://github.com/vlang/v/releases/latest/download/v_linux.zip -o /tmp/v.zip
|
||||||
|
unzip /tmp/v.zip -d /tmp && sudo /tmp/v/v symlink
|
||||||
|
|
||||||
|
# Compiler
|
||||||
|
git clone https://git.dracodev.net/bruno/ntfy-bridge.git
|
||||||
|
cd ntfy-bridge
|
||||||
|
v .
|
||||||
|
|
||||||
|
# Configurer
|
||||||
|
cp ntfy-bridge.example.yaml ntfy-bridge.yaml
|
||||||
|
vim ntfy-bridge.yaml
|
||||||
|
|
||||||
|
# Lancer
|
||||||
|
./ntfy-bridge --config ntfy-bridge.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
## Sources supportées
|
||||||
|
|
||||||
|
| Source | Type | Description |
|
||||||
|
|--------|------|-------------|
|
||||||
|
| **Gitea** | Webhook | Push, PR, issues, releases → notifs formatées |
|
||||||
|
| **Uptime Kuma** | Webhook | Statut up/down avec priorité critique |
|
||||||
|
| **Docker** | Socket | Container start/die/oom/health_status |
|
||||||
|
| **HTTP Poll** | Polling | Health checks périodiques |
|
||||||
|
| **Cron** | Webhook | Reçoit des notifs depuis des scripts shell |
|
||||||
|
| **Generic** | Webhook | Endpoint passe-partout pour tout script custom |
|
||||||
|
|
||||||
|
## Exemple de config minimale
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
server:
|
||||||
|
url: https://ntfy.dracodev.net
|
||||||
|
listen: ":9090"
|
||||||
|
|
||||||
|
sources:
|
||||||
|
gitea:
|
||||||
|
- webhook_path: /webhooks/gitea
|
||||||
|
topic: dev-notifs
|
||||||
|
```
|
||||||
|
|
||||||
|
## Licence
|
||||||
|
|
||||||
|
MIT
|
||||||
+95
@@ -0,0 +1,95 @@
|
|||||||
|
# ntfy-bridge — Roadmap
|
||||||
|
|
||||||
|
## Phase 1 : MVP (core engine) — v0.1.0
|
||||||
|
|
||||||
|
Objectif : daemon qui compile, lit une config, écoute des webhooks et envoie vers Ntfy.
|
||||||
|
|
||||||
|
- [ ] `main.v` — CLI args (`--config`, `--version`, `--validate`)
|
||||||
|
- [ ] `config.v` — Chargement + validation du YAML
|
||||||
|
- [ ] `server.v` — HTTP server sur `:9090` avec routing basique
|
||||||
|
- [ ] `event.v` — Types de données : `Event`, `Config`, `Source`
|
||||||
|
- [ ] `ntfy.v` — Client HTTP POST vers Ntfy (topic, message, priority, tags)
|
||||||
|
- [ ] `sources/generic.v` — Webhook passe-partout (POST raw → Ntfy)
|
||||||
|
- [ ] `log.v` — Logging structuré (timestamp, level, message)
|
||||||
|
- [ ] `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
|
||||||
|
|
||||||
|
- [ ] `sources/gitea.v` — Transformer Gitea (push, PR, issues, releases)
|
||||||
|
- Parse le JSON webhook Gitea
|
||||||
|
- Format : `🔨 [repo] auteur action: "message" (sha)`
|
||||||
|
- Priority map par type d'événement
|
||||||
|
- [ ] `sources/uptime_kuma.v` — Transformer Uptime Kuma
|
||||||
|
- Parse le JSON webhook Kuma (heartbeat)
|
||||||
|
- Format : `🚨 monitor is DOWN — status — since time`
|
||||||
|
- Priority 5 pour DOWN, 1 pour UP
|
||||||
|
- [ ] `sources/docker.v` — Watcher socket Docker
|
||||||
|
- Connexion au socket Docker (local + distant TCP)
|
||||||
|
- Filtrage par événements (die, health_status, oom)
|
||||||
|
- Template de message configurable
|
||||||
|
- Multi-hôtes (plusieurs sockets dans la config)
|
||||||
|
- [ ] `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
|
||||||
|
- [ ] `dedup.v` — Déduplication basique
|
||||||
|
- Cache LRU avec TTL (5 min par défaut)
|
||||||
|
- Clé de déduplication : source + type + cible
|
||||||
|
|
||||||
|
**Sortie** : Toutes les sources majeures sont intégrées et fonctionnelles.
|
||||||
|
|
||||||
|
## Phase 3 : Robustesse — v0.3.0
|
||||||
|
|
||||||
|
- [ ] `dedup.v` amélioré — Rate limiting global par topic
|
||||||
|
- [ ] Signature webhooks — Vérification HMAC-SHA256 optionnelle (Gitea)
|
||||||
|
- [ ] Retry logic — Retry avec backoff exponentiel vers Ntfy (3 tentatives)
|
||||||
|
- [ ] Graceful shutdown — SIGTERM → drain des événements en cours
|
||||||
|
- [ ] Health endpoint — `GET /health` → 200 + uptime + stats basiques
|
||||||
|
- [ ] Stats endpoint — `GET /stats` → compteurs par source
|
||||||
|
- [ ] Config reload — `SIGHUP` → reload config sans redémarrage
|
||||||
|
- [ ] Tests unitaires — `config_test.v`, `gitea_test.v`, `dedup_test.v`
|
||||||
|
|
||||||
|
**Sortie** : Le daemon est prêt pour la production homelab.
|
||||||
|
|
||||||
|
## Phase 4 : Qualité de vie — v0.4.0
|
||||||
|
|
||||||
|
- [ ] Template engine avancé — Support des variables dans les messages
|
||||||
|
```
|
||||||
|
"🐳 {container_name} → {status} (exit: {exit_code}) on {host}"
|
||||||
|
```
|
||||||
|
- [ ] Silence rules — `POST /api/silence?duration=30m` mute temporaire
|
||||||
|
- [ ] Grouping — Regrouper N notifications similaires en une seule
|
||||||
|
- [ ] Webhook secret validation — HMAC pour tous les webhooks
|
||||||
|
- [ ] Docker Compose example — `docker-compose.yml` prêt à l'emploi
|
||||||
|
- [ ] systemd unit file — `ntfy-bridge.service`
|
||||||
|
- [ ] CI/CD via Gitea Actions — build + test automatique
|
||||||
|
|
||||||
|
**Sortie** : Expérience utilisateur complète et agréable.
|
||||||
|
|
||||||
|
## Phase 5 : Futures idées — v0.5.0+
|
||||||
|
|
||||||
|
- [ ] Web UI minimale — Page de statut des sources + historique récent
|
||||||
|
- [ ] Filtres avancés — Expressions conditionnelles par source
|
||||||
|
```yaml
|
||||||
|
filters:
|
||||||
|
- match: { container_name: "test-*" }
|
||||||
|
action: drop
|
||||||
|
```
|
||||||
|
- [ ] Notifications structurées — Support des actions Ntfy (boutons click)
|
||||||
|
- [ ] Intégration Prometheus — Métriques exposées au format OpenMetrics
|
||||||
|
- [ ] Webhook sortant — Forwarder les événements vers un autre webhook (Slack, Discord)
|
||||||
|
- [ ] Support multi-utilisateurs — ACLs par webhook_path
|
||||||
|
- [ ] Fichier d'état — Persistance de l'état up/down des health checks
|
||||||
|
- [ ] Plugin system — Sources customisables via dll/.so (futur lointain)
|
||||||
|
|
||||||
|
## Suivi des versions
|
||||||
|
|
||||||
|
| Version | Date cible | Contenu |
|
||||||
|
|---------|-----------|---------|
|
||||||
|
| v0.1.0 | — | Moteur core + webhook générique |
|
||||||
|
| v0.2.0 | — | Sources Gitea, Uptime Kuma, Docker, HTTP poll |
|
||||||
|
| v0.3.0 | — | Robustesse, tests, health check, rate limiting |
|
||||||
|
| v0.4.0 | — | Templates, silence rules, grouping, CI/CD |
|
||||||
|
| v0.5.0+ | — | Web UI, Prometheus, plugins |
|
||||||
@@ -0,0 +1,128 @@
|
|||||||
|
# ntfy-bridge configuration
|
||||||
|
# ============================
|
||||||
|
|
||||||
|
# ── Server ────────────────────────────────────────────
|
||||||
|
server:
|
||||||
|
# URL de ton serveur Ntfy
|
||||||
|
url: https://ntfy.dracodev.net
|
||||||
|
|
||||||
|
# Token d'authentification (optionnel, si ton serveur Ntfy a auth activée)
|
||||||
|
# Peut aussi être passé via variable d'environnement NTFY_TOKEN
|
||||||
|
# auth_token: tk_xxxxxxxxxxxx
|
||||||
|
|
||||||
|
# Adresse d'écoute du serveur HTTP interne
|
||||||
|
# Mettre "0.0.0.0:9090" pour exposer réseau, "127.0.0.1:9090" pour localhost
|
||||||
|
listen: "127.0.0.1:9090"
|
||||||
|
|
||||||
|
# ── Defaults ──────────────────────────────────────────
|
||||||
|
# Appliqués à toutes les sources sauf override explicite
|
||||||
|
defaults:
|
||||||
|
priority: 3 # 1=min, 2=low, 3=default, 4=high, 5=urgent
|
||||||
|
tags: ["loudspeaker"] # Tags/emojis Ntfy
|
||||||
|
|
||||||
|
# ── Sources ───────────────────────────────────────────
|
||||||
|
sources:
|
||||||
|
|
||||||
|
# ═══════════════════════════════════════════════════
|
||||||
|
# GITEA — webhooks depuis git.dracodev.net
|
||||||
|
# ═══════════════════════════════════════════════════
|
||||||
|
gitea:
|
||||||
|
- name: "FlowDeck activity"
|
||||||
|
webhook_path: /webhooks/gitea-flowdeck
|
||||||
|
repo: bruno/flowdeck
|
||||||
|
topic: dev-notifs
|
||||||
|
priority_map:
|
||||||
|
pull_request: 4
|
||||||
|
issue: 3
|
||||||
|
push: 2
|
||||||
|
# Template de message (variables dispo: {repo}, {user}, {action}, {title}, {sha})
|
||||||
|
template: |
|
||||||
|
🔨 **[{repo}]** {user} {action}: "{title}"
|
||||||
|
`{sha}`
|
||||||
|
|
||||||
|
- name: "ObsiGate activity"
|
||||||
|
webhook_path: /webhooks/gitea-obsigate
|
||||||
|
repo: bruno/obsigate
|
||||||
|
topic: dev-notifs
|
||||||
|
|
||||||
|
# ═══════════════════════════════════════════════════
|
||||||
|
# UPTIME KUMA — alertes de monitoring
|
||||||
|
# ═══════════════════════════════════════════════════
|
||||||
|
uptime_kuma:
|
||||||
|
- name: "Services critiques"
|
||||||
|
webhook_path: /webhooks/kuma-critical
|
||||||
|
topic: alerts
|
||||||
|
state_map:
|
||||||
|
down: { priority: 5, tags: ["rotating_light", "x"] }
|
||||||
|
up: { priority: 1, tags: ["white_check_mark"] }
|
||||||
|
|
||||||
|
# ═══════════════════════════════════════════════════
|
||||||
|
# DOCKER — surveillance des conteneurs
|
||||||
|
# ═══════════════════════════════════════════════════
|
||||||
|
docker:
|
||||||
|
- name: "Production containers"
|
||||||
|
hosts:
|
||||||
|
- unix:///var/run/docker.sock
|
||||||
|
# Hôtes distants:
|
||||||
|
# - tcp://192.168.30.101:2375
|
||||||
|
# - tcp://192.168.30.20:2375
|
||||||
|
events: [die, health_status, oom]
|
||||||
|
topic: infra
|
||||||
|
template: |
|
||||||
|
🐳 **{container_name}** → {status}
|
||||||
|
Image: `{image}`
|
||||||
|
Exit code: {exit_code}
|
||||||
|
Host: {host}
|
||||||
|
|
||||||
|
# ═══════════════════════════════════════════════════
|
||||||
|
# HTTP POLL — health checks périodiques
|
||||||
|
# ═══════════════════════════════════════════════════
|
||||||
|
http_poll:
|
||||||
|
- name: "Health endpoints"
|
||||||
|
interval: 60 # secondes entre chaque check
|
||||||
|
checks:
|
||||||
|
- url: https://obsigate.dracodev.net/health
|
||||||
|
topic: alerts
|
||||||
|
priority: 5 # priorité si DOWN
|
||||||
|
expect_status: 200
|
||||||
|
timeout: 10 # secondes
|
||||||
|
|
||||||
|
- url: https://flowdeck.dracodev.net/health
|
||||||
|
topic: alerts
|
||||||
|
priority: 5
|
||||||
|
expect_status: 200
|
||||||
|
timeout: 10
|
||||||
|
|
||||||
|
- url: https://git.dracodev.net/api/v1/version
|
||||||
|
topic: alerts
|
||||||
|
priority: 5
|
||||||
|
expect_status: 200
|
||||||
|
timeout: 10
|
||||||
|
|
||||||
|
# ═══════════════════════════════════════════════════
|
||||||
|
# CRON — reçoit des notifs depuis des scripts shell
|
||||||
|
# ═══════════════════════════════════════════════════
|
||||||
|
cron:
|
||||||
|
- name: "Disk usage quotidien"
|
||||||
|
webhook_path: /webhooks/cron-disk
|
||||||
|
topic: daily
|
||||||
|
|
||||||
|
- name: "Backup report"
|
||||||
|
webhook_path: /webhooks/cron-backup
|
||||||
|
topic: daily
|
||||||
|
|
||||||
|
# ═══════════════════════════════════════════════════
|
||||||
|
# GENERIC — webhook passe-partout
|
||||||
|
# ═══════════════════════════════════════════════════
|
||||||
|
generic:
|
||||||
|
- name: "Custom alerts"
|
||||||
|
webhook_path: /webhooks/generic
|
||||||
|
topic: custom
|
||||||
|
|
||||||
|
# ── Déduplication ──────────────────────────────────────
|
||||||
|
dedup:
|
||||||
|
enabled: true
|
||||||
|
ttl_seconds: 300 # 5 minutes — durée du cache de déduplication
|
||||||
|
rate_limit:
|
||||||
|
max_per_minute: 10 # max notifications/minute par topic
|
||||||
|
max_per_source: 30 # max notifications/minute toute source confondue
|
||||||
Executable
+25
@@ -0,0 +1,25 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# example-cron-disk.sh
|
||||||
|
# Exemple de script cron qui envoie un résumé d'espace disque vers ntfy-bridge.
|
||||||
|
# À appeler depuis cron : 0 9 * * * /path/to/example-cron-disk.sh
|
||||||
|
|
||||||
|
NTFY_BRIDGE_URL="${NTFY_BRIDGE_URL:-http://127.0.0.1:9090}"
|
||||||
|
WEBHOOK_PATH="${WEBHOOK_PATH:-/webhooks/cron-disk}"
|
||||||
|
|
||||||
|
# Collecter l'info disque
|
||||||
|
DISK_INFO=$(df -h / /mnt/nfs /home 2>/dev/null | tail -n +2 | awk '{print " " $6 ": " $3 "/" $2 " (" $5 " used)"}')
|
||||||
|
|
||||||
|
# Construire le message
|
||||||
|
MESSAGE="💾 **Disk Usage Report** — $(date '+%Y-%m-%d %H:%M')
|
||||||
|
${DISK_INFO}
|
||||||
|
|
||||||
|
Warnings (>80%):
|
||||||
|
$(df -h / /mnt/nfs /home 2>/dev/null | awk 'NR>1 {gsub(/%/,"",$5); if ($5+0 > 80) print " ⚠️ " $6 " is at " $5 "%"}' || echo " ✅ All partitions under 80%")"
|
||||||
|
|
||||||
|
# Envoyer vers ntfy-bridge
|
||||||
|
curl -s -X POST \
|
||||||
|
-H "Content-Type: text/plain" \
|
||||||
|
-d "$MESSAGE" \
|
||||||
|
"${NTFY_BRIDGE_URL}${WEBHOOK_PATH}" > /dev/null
|
||||||
|
|
||||||
|
echo "Disk report sent to ntfy-bridge"
|
||||||
Reference in New Issue
Block a user