Nik Afiq 39b7d6d4b4
All checks were successful
CI / changes (push) Successful in 19s
CI / test (push) Successful in 31s
CI / build-ai-gateway (push) Successful in 1m8s
CI / build-ha-gateway (push) Successful in 1m19s
CI / build-discord-bot (push) Successful in 59s
CI / build-alexa-bridge (push) Successful in 1m8s
CI / build-alert-bridge (push) Successful in 1m17s
CI / build-tts-gateway (push) Successful in 1m10s
CI / build-tts-sidecar (push) Has been skipped
CI / build-tts-model (push) Has been skipped
feat: add alert-bridge service for external cronjob -> Discord alerts
New standalone service that receives authenticated HTTP POSTs (e.g. from a
cronjob elsewhere on the home network) and posts them to a Discord channel
via an Incoming Webhook. Fire-and-forget, no dependency on discord-bot's bot
session at all - mirrors alexa-bridge's precedent of a small bridge service
with its own HTTP listener and auth rather than adding an inbound edge to an
existing internal service.

Wires alert-bridge into go.work, CI (changes filter, test job, new
build-alert-bridge job), and the other five Dockerfiles' COPY lines.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-01 22:45:18 +09:00

115 lines
4.4 KiB
Markdown

# alert-bridge
`alert-bridge` turns an authenticated HTTP POST from an external caller (e.g.
a cronjob running elsewhere on the home network) into a message posted to a
Discord channel. It is fire-and-forget: each request becomes one new Discord
message, there is no editing or reacting to prior messages.
Unlike `ha-gateway`/`ai-gateway`/`discord-bot`/`tts-gateway`, it has no
dependency on any other service in this repo — it posts directly to a Discord
[Incoming Webhook](https://discord.com/developers/docs/resources/webhook), not
through `discord-bot`'s bot session. That keeps the whole flow to one hop and
means `discord-bot` needed zero changes to support this feature.
## Runtime Flow
1. The process loads `.env`, configures logging/telemetry, and starts serving
HTTP on `HTTP_PORT`.
2. A caller `POST`s to `/alerts` with a bearer token and a JSON body.
3. The handler checks the bearer token (constant-time compare against
`API_KEY`), validates the body, and defaults `level` to `info` if omitted.
4. The alert is formatted as a Discord embed (color-coded by level) and POSTed
to `DISCORD_WEBHOOK_URL`. `level=error` alerts additionally prefix the
message with a mention of `MENTION_USER_ID`, since embeds alone never
trigger a Discord ping.
## HTTP API
- `POST /alerts` — requires `Authorization: Bearer <API_KEY>`.
```json
{"source": "ba-cronjob", "message": "Finished with 0 errors", "level": "info"}
```
- `source`, `message`: required.
- `level`: optional, one of `info` | `warn` | `error`, defaults to `info`.
- Responses: `202` delivered, `400` bad request, `401` bad/missing token,
`502` the Discord webhook call itself failed.
- `GET /healthz`: unauthenticated liveness/readiness check, always `200 OK`.
## Configuration
Environment variables:
| Variable | Default | Description |
| --- | --- | --- |
| `HTTP_PORT` | `8080` | HTTP listen port |
| `API_KEY` | required | Shared secret callers must present as `Authorization: Bearer <API_KEY>` |
| `DISCORD_WEBHOOK_URL` | required | Target channel's Discord Incoming Webhook URL |
| `MENTION_USER_ID` | empty | Discord user ID mentioned on `level=error` alerts; leave empty to never mention |
| `OTEL_ENDPOINT` | empty | OTLP gRPC collector endpoint; empty disables telemetry |
| `LOG_LEVEL` | `info` | `debug`, `info`, `warn`, or `error` |
| `LOG_FORMAT` | `json` | `json` or `text` |
Example env file: [.env.example](.env.example)
## Local Run
```bash
cd alert-bridge
cp .env.example .env
# edit .env: set API_KEY and DISCORD_WEBHOOK_URL
go run ./cmd/bridge
```
```bash
curl -X POST http://localhost:8080/alerts \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{"source":"ba-cronjob","message":"Started","level":"info"}'
```
## Test And Build
```bash
go test ./...
go build ./...
```
Build the container image from the workspace root:
```bash
docker build -f alert-bridge/Dockerfile -t alert-bridge:dev .
```
## Package Map
```text
cmd/bridge/ # process entrypoint and wiring
internal/adapters/primary/http/ # POST /alerts handler: auth, decode, validate
internal/adapters/secondary/discordwebhook/ # builds the embed payload, posts to the Discord webhook
internal/app/ # thin orchestration between the HTTP handler and the Notifier
internal/core/domain/ # Alert{Source, Message, Level}
internal/core/ports/driven/ # Notifier interface
internal/config/ # environment loading
internal/logger/ # slog setup
internal/telemetry/ # OpenTelemetry setup
```
## Deployment
Kubernetes manifests (Deployment/Service, and an internal-CA `IngressRoute` at
a `*.home.arpa` hostname so LAN callers outside the cluster can reach it) live
in the separate `homelab` repo, not here — not yet added as of this writing.
## Limitations
- Single channel/webhook only — no per-source routing to multiple channels.
- No rate limiting of its own; relies on Discord's own webhook rate limits
(5 requests/2s per webhook), which is more than enough for cronjob-scale
notification volume.
- Fire-and-forget only: no message editing, threading, or reaction handling.
That would require routing through `discord-bot`'s live bot session instead
of a plain webhook — not needed for the current use case.