Periodic cleanup goroutine, started alongside the worker when DATABASE_URL is set. Three concerns : - DELETE rows with status='done' older than QUEUE_DONE_RETENTION (default 168h / 7 days). Past success rows have no value beyond debug runway. - UPDATE rows stuck in status='running' for more than QUEUE_STUCK_TIMEOUT (default 30m) back to 'pending' so a worker can retry. Handles the case of a pod crashing mid-job (without this, jobs stay orphaned forever). - 'dead' rows are NEVER auto-purged (volume negligible, kept for forensics). Configurable via env : - QUEUE_DONE_RETENTION (default 168h) - QUEUE_STUCK_TIMEOUT (default 30m) - QUEUE_JANITOR_INTERVAL (default 1h) The janitor runs once immediately at startup (recovers anything orphaned by the previous pod before opening for new traffic), then ticks on the interval. Queue interface gains PurgeDone + RecoverStuck — both use Postgres' make_interval(secs) for safe parameterization. 4 new unit tests via fakeQueue mock (47 total, race clean).
telegram-gateway
Telegram webhook gateway for the Arcodange home lab. Replaces polling-based bots (e.g. those scheduled in Cowork) with direct webhook delivery from Telegram, routed to per-bot handlers running on the k3s cluster.
Phase 1 (MVP): single sync
echohandler, end-to-end flow validated. Phase 2 (planned):httpforward handler + Postgres-backed durable queue. Phase 3 (planned): asyncshell/script/ollamahandlers.
See the design doc at ~/.claude/plans/pour-les-notifications-on-inherited-seal.md.
Architecture (current)
Telegram → Cloudflare Tunnel (tg.arcodange.fr) → Service telegram-gateway:8080
→ /bot/<slug> → secret_token check → handler dispatch → Bot API sendMessage
Routes
| Method | Path | Description |
|---|---|---|
| GET | /healthz |
Liveness probe |
| GET | /readyz |
Readiness probe |
| POST | /bot/{slug} |
Telegram webhook entry (validates secret) |
Local dev
Pour le dev local complet (Redis pour l'auth + Postgres pour la queue) :
make compose-up # docker compose up -d --wait : redis + postgres
export REDIS_URL=redis://localhost:6379/0
export DATABASE_URL=postgres://gateway:gateway@localhost:5432/gateway?sslmode=disable
export AUTH_SECRET=$(openssl rand -hex 16)
export ALLOWED_USERS=<your-tg-user-id>
export BOT_FACTORY_TOKEN='8737289837:…' # from @BotFather
export BOT_FACTORY_SECRET=$(openssl rand -hex 32)
make run # uses bots.example.yaml
Smoke d'un webhook (sans Telegram cloud) :
curl -X POST -H "X-Telegram-Bot-Api-Secret-Token: $BOT_FACTORY_SECRET" \
-H 'Content-Type: application/json' \
-d '{"update_id":1,"message":{"message_id":1,"from":{"id":<tg-id>},"chat":{"id":<tg-id>},"text":"hi"}}' \
http://localhost:8080/bot/factory
Tests
make test # unit + integration (in-process miniredis + httptest mock Telegram)
make test-race # avec race detector
make vet
43 tests couvrent : allowlist parsing, secret comparison, auth flow (login/logout/whoami/replay-defense), echo handler (plain/slash/empty), http handler (forward/timeout/non-2xx/empty), webhook dispatch (bad secret 401, unknown bot 404, allowlist drop, gated bot prompt, full /auth → echo flow).
Pour des smokes locaux contre une vraie API Telegram, voir la section "Local dev" ci-dessus.
Set / delete webhook
# Once the gateway is reachable at https://tg.arcodange.fr:
export BOT_FACTORY_TOKEN=…
export BOT_FACTORY_SECRET=…
make setwebhook SLUG=factory BASE_URL=https://tg.arcodange.fr
make deletewebhook SLUG=factory
Configuration
- Routing (non-secret): YAML at
$CONFIG_PATH(default/etc/telegram-gateway/bots.yaml, mounted from a ConfigMap in cluster). - Secrets: per-bot env vars
BOT_<UPPER_SLUG>_TOKEN,BOT_<UPPER_SLUG>_SECRET. Sourced from Vault pathkvv2/telegram-gateway/configvia Vault Secrets Operator.
Cluster deploy
- Image:
gitea.arcodange.lab/arcodange/telegram-gateway:<tag> - Helm chart:
chart/ - ArgoCD app:
telegram-gateway(infactory/argocd/values.yaml) - Public URL:
https://tg.arcodange.fr(Cloudflare déjà configuré pour router*.arcodange.frvers le home lab → Traefik route par Host) - Secrets Phase 1 :
kubectl create secret generic telegram-gateway-bots …(sans Vault). Migration vers Vault Secrets Operator en Phase 2+ viavault.enabled: truedanschart/values.yaml.
Voir DEPLOY.md pour la procédure end-to-end.
Layout
.
├── main.go # bootstrap, subcommand dispatch
├── server.go # HTTP routes
├── middleware.go # secret validation, recover, access log
├── handlers.go # Handler interface + Registry
├── handler_echo.go # echo handler
├── telegram.go # Telegram Bot API client
├── telegram_types.go # Update / Message structs
├── config.go # YAML routing config + per-bot env merge
├── setwebhook.go # CLI subcommands (setwebhook / deletewebhook)
├── chart/ # Helm chart
└── .gitea/workflows/ # CI: docker build → gitea registry