Add config validation and dashboard

Add YAML config loading with validation, env overrides for secrets, and
a web dashboard exposing health, stats, and recent events.
This commit is contained in:
2026-08-03 12:12:12 -04:00
parent 3f48912ad7
commit 4e1d7cd94a
29 changed files with 2344 additions and 55 deletions
+266
View File
@@ -0,0 +1,266 @@
# 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)
+94
View File
@@ -0,0 +1,94 @@
# 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
```
.
├── main.v # Entrypoint, flag parsing, orchestration
├── config.v # Config loading + validation
├── server.v # HTTP server, webhook routing
├── ntfy.v # Ntfy HTTP client
├── dedup.v # Deduplication + rate limiting
├── template.v # Mini template engine
├── log.v # Structured logging
├── docker_watcher.v # Docker socket watcher (Unix)
├── sources/ # Event types + source-specific transformers
│ ├── event.v
│ ├── gitea.v
│ ├── uptime_kuma.v
│ ├── docker.v
│ ├── http_poll.v
│ ├── generic.v
│ └── cron.v
└── *_test.v # Unit tests
```
## Ajouter une nouvelle source
1. Créer `sources/ma_source.v`
2. Implémenter la fonction `transform(raw string, source MaSource) ?Event`
3. Déclarer le type source dans `sources/event.v`
4. Référencer le type dans `config.v` (`SourcesConfig`)
5. Router l'endpoint dans `server.v`
6. Ajouter un test dans `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 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
+95
View File
@@ -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 |
+255
View File
@@ -0,0 +1,255 @@
# Configurer les webhooks Gitea avec ntfy-bridge
Guide tape par tape pour connecter Gitea ntfy-bridge et recevoir des notifications Ntfy pour chaque push, PR, issue ou release.
---
## Prrequis
- **ntfy-bridge** install et configur (voir [README.md](../README.md))
- Un serveur **Ntfy** accessible (ex: `https://ntfy.dracodev.net`)
- Un compte **Gitea** avec accs admin au repo (ex: `https://git.dracodev.net`)
---
## Architecture
```
Gitea (git.dracodev.net) ntfy-bridge (ton serveur) Ntfy (ntfy.dracodev.net)
┌─────────────────────┐ ┌────────────────────────┐ ┌──────────────────────┐
│ bruno/flowdeck │ │ coute sur :9090 │ │ Topic: dev-notifs │
│ │ POST │ │ POST │ │
│ vnement (push) │──────────→│ /webhooks/gitea-... │───────→│ Ton tlphone │
│ JSON + HMAC sig │ │ │ │ │
│ │ │ Parse, format, forward │ │ │
└─────────────────────┘ └────────────────────────┘ └──────────────────────┘
```
**Le principe** : Gitea envoie un POST HTTP contenant un JSON avec tous les dtails de l'vnement. ntfy-bridge l'coute, le parse, le formate, et le forward vers Ntfy.
---
## tape 1 — Configurer ntfy-bridge
### 1a. Modifier `ntfy-bridge.yaml`
```yaml
server:
url: https://ntfy.dracodev.net # Ton serveur Ntfy
listen: "0.0.0.0:9090" # couter sur le rseau (pas juste localhost)
hmac_secret: "un-secret-que-tu-choisis" # Secret partag avec Gitea
sources:
gitea:
- name: "FlowDeck activity" # Nom affich dans les logs/dashboard
webhook_path: /webhooks/gitea-flowdeck # Chemin d'coute
repo: bruno/flowdeck # Repo concern
topic: dev-notifs # Topic Ntfy cible
priority_map: # Priorits par type d'vent
pull_request: 4 # 4 = high
issue: 3 # 3 = default
push: 2 # 2 = low
template: | # Format du message (optionnel)
{user} {action} sur **[{repo}]**: "{title}"
```
**Points cls** :
- `listen: "0.0.0.0:9090"` — indispensable si Gitea est sur un autre serveur. `127.0.0.1` = localhost seulement.
- `hmac_secret` — le mme secret que tu mettras dans Gitea. Optionnel mais recommand.
- `webhook_path` — le chemin dans l'URL. Doit tre unique par source.
- `template` — si absent, un format par dfaut est gnr automatiquement avec emoji.
### 1b. Lancer ntfy-bridge
```bash
cd /c/dev/git/V/ntfy-bridge
./ntfy-bridge.exe --config ntfy-bridge.yaml
```
Sortie attendue :
```
──────────────────────────────────────────────────────────────────────
ntfy-bridge v0.1.0 — Homelab Notification Hub
──────────────────────────────────────────────────────────────────────
Ntfy server: https://ntfy.dracodev.net
Listening on: http://0.0.0.0:9090
Dashboard: http://0.0.0.0:9090/
Sources loaded:
Gitea: 2 webhooks
Uptime Kuma: 1 webhooks
Docker: 1 watchers
HTTP Poll: 1 pollers (5 checks)
Cron: 2 webhooks
Generic: 1 webhooks
──────────────────────────────────────────────────────────────────────
```
### 1c. Vrifier que a marche
```bash
# Test health endpoint
curl http://localhost:9090/health
# → {"status":"ok","uptime_seconds":12,"total_notifications":0}
# Test dashboard
curl http://localhost:9090/
# → HTML du dashboard
```
---
## tape 2 — Configurer Gitea
### 2a. Aller dans les rglages du repo
1. Ouvre `https://git.dracodev.net/bruno/flowdeck`
2. Clic sur l'engrenage **Settings** (haut droite)
3. Menu gauche : **Webhooks**
4. Bouton **Add Webhook** → choisir **Gitea**
### 2b. Remplir le formulaire
| Champ | Valeur | Notes |
|---|---|---|
| **Target URL** | `http://192.168.30.X:9090/webhooks/gitea-flowdeck` | Remplacer X par l'IP relle du serveur |
| **HTTP Method** | POST | |
| **POST Content Type** | application/json | |
| **Secret** | `un-secret-que-tu-choisis` | Identique `hmac_secret` dans le YAML |
| **Trigger On** | Slectionner les vnements souhauts | Push, Pull Request, Issue, Release... |
**Trouver la bonne IP** : sur le serveur o tourne ntfy-bridge :
```bash
# Linux
ip addr show | grep "inet " | grep -v 127.0.0.1
# Windows
ipconfig | findstr "IPv4"
```
### 2c. Cliquer "Add Webhook"
Gitea envoie immdiatement un **test ping** pour vrifier la connectivit.
Rsultat russi :
```
✓ Delivery successful (HTTP 200)
```
En cas d'chec :
| Erreur | Cause probable | Solution |
|---|---|---|
| Connection refused | ntfy-bridge n'est pas lanc | Lancer `./ntfy-bridge.exe` |
| Connection refused | Firewall bloque le port 9090 | Ouvrir le port dans le firewall |
| HTTP 404 | Mauvais `webhook_path` | Vrifier que `/webhooks/gitea-flowdeck` est exact |
| HTTP 401 | Mauvais `hmac_secret` | Vrifier que le secret est identique des deux cts |
| Timeout | Mauvaise IP ou rseau inaccessible | Vrifier la connectivit rseau |
---
## tape 3 — Tester avec un vrai vnement
### Option A : Test simul (curl)
Depuis n'importe quel poste :
```bash
curl -X POST http://192.168.30.X:9090/webhooks/gitea-flowdeck \
-H "Content-Type: application/json" \
-H "X-Hub-Signature-256: sha256=..." \
-d '{
"commits": [{"id": "abc1234567890abcdef1234567890abcdef1234", "message": "test notification ntfy-bridge"}],
"pusher": {"username": "bruno"},
"repository": {"full_name": "bruno/flowdeck"}
}'
```
> Note : si `hmac_secret` est configur, gnre la signature correcte ou dsactive-le temporairement pour le test.
### Option B : Test rel
Fais un push, cre une PR, ou ouvre une issue dans le repo Gitea. Tu devrais recevoir une notification Ntfy dans les secondes qui suivent.
Exemple de notification reue :
```
bruno pushed sur [bruno/flowdeck]: "fix: corrige le bug du dashboard"
```
Ou avec le template par dfaut :
```
[bruno/flowdeck] bruno pushed: "fix: corrige le bug du dashboard" (abc1234)
```
---
## Ajouter d'autres repos
Rpte les mmes tapes pour chaque repo :
### Dans `ntfy-bridge.yaml`
```yaml
sources:
gitea:
- name: "FlowDeck activity"
webhook_path: /webhooks/gitea-flowdeck
repo: bruno/flowdeck
topic: dev-notifs
- name: "ObsiGate activity"
webhook_path: /webhooks/gitea-obsigate
repo: bruno/obsigate
topic: dev-notifs
- name: "Imago activity"
webhook_path: /webhooks/gitea-imago
repo: bruno/imago
topic: dev-notifs
```
### Dans Gitea
Pour chaque repo : Settings → Webhooks → Add Webhook avec l'URL correspondante.
```text
http://192.168.30.X:9090/webhooks/gitea-flowdeck → repo bruno/flowdeck
http://192.168.30.X:9090/webhooks/gitea-obsigate → repo bruno/obsigate
http://192.168.30.X:9090/webhooks/gitea-imago → repo bruno/imago
```
---
## Dpannage rapide
```bash
# Vrifier que ntfy-bridge tourne
curl http://localhost:9090/health
# Voir les stats
curl http://localhost:9090/stats
# Voir la config (sans les secrets)
curl http://localhost:9090/api/config
# Voir l'historique des derniers vnements
curl http://localhost:9090/api/history
# Voir le dashboard web
# Ouvre http://localhost:9090/ dans ton navigateur
```
---
## Rfrences
- [README.md](../README.md) — Vue d'ensemble du projet
- [ARCHITECTURE.md](../ARCHITECTURE.md) — Architecture dtaille
- [ntfy-bridge.example.yaml](../ntfy-bridge.example.yaml) — Exemple de config complet
- [Documentation webhooks Gitea](https://docs.gitea.com/usage/webhooks)
- [Documentation Ntfy](https://docs.ntfy.sh/)