Nik Afiq 4cb1290daa
All checks were successful
CI / changes (push) Successful in 18s
CI / test (push) Successful in 25s
CI / build-ai-gateway (push) Has been skipped
CI / build-ha-gateway (push) Has been skipped
CI / build-discord-bot (push) Has been skipped
CI / build-alexa-bridge (push) Has been skipped
CI / build-alert-bridge (push) Successful in 30s
CI / build-tts-gateway (push) Successful in 29s
CI / build-tts-sidecar (push) Has been skipped
CI / build-tts-model (push) Has been skipped
feat: add usage instructions for alert-bridge from another LAN host
2026-08-01 23:05:55 +09:00

6.6 KiB

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, 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 POSTs 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>.

    {"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

Local Run

cd alert-bridge
cp .env.example .env
# edit .env: set API_KEY and DISCORD_WEBHOOK_URL
go run ./cmd/bridge
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"}'

Usage From Another LAN Host

Deployed at http://alert-bridge.home.arpa (plain HTTP, not HTTPS — see "Deployment" below for why). Any host on the home network can reach it directly, no VPN/tunnel/cluster access needed:

curl -X POST http://alert-bridge.home.arpa/alerts \
  -H "Authorization: Bearer $ALERT_BRIDGE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"source":"ba-cronjob","message":"Started","level":"info"}'

# ... job runs ...

curl -X POST http://alert-bridge.home.arpa/alerts \
  -H "Authorization: Bearer $ALERT_BRIDGE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"source":"ba-cronjob","message":"Finished with 0 errors","level":"info"}'

A level=error call additionally pings the configured MENTION_USER_ID in Discord:

curl -X POST http://alert-bridge.home.arpa/alerts \
  -H "Authorization: Bearer $ALERT_BRIDGE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"source":"ba-cronjob","message":"Finished with 3 errors","level":"error"}'

Reusable shape for a cronjob script — export ALERT_BRIDGE_API_KEY once at the top (never hardcode the token in the script itself) and call alert at each step:

#!/usr/bin/env bash
set -euo pipefail

ALERT_URL="http://alert-bridge.home.arpa/alerts"
SOURCE="ba-cronjob"

alert() {
  local level="$1" message="$2"
  curl -sf -X POST "$ALERT_URL" \
    -H "Authorization: Bearer $ALERT_BRIDGE_API_KEY" \
    -H "Content-Type: application/json" \
    -d "{\"source\":\"$SOURCE\",\"message\":\"$message\",\"level\":\"$level\"}" \
    > /dev/null
}

alert info "Started"

if ! run_the_actual_job; then
  alert error "Finished with errors"
  exit 1
fi

alert info "Finished with 0 errors"

Since this is plain HTTP, the bearer token travels in cleartext on the LAN — acceptable for a home network, but don't reuse ALERT_BRIDGE_API_KEY for anything more sensitive.

Test And Build

go test ./...
go build ./...

Build the container image from the workspace root:

docker build -f alert-bridge/Dockerfile -t alert-bridge:dev .

Package Map

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, plus a plain-HTTP Traefik IngressRoute at alert-bridge.home.arpa so LAN callers outside the cluster can reach it) live in the separate homelab repo, not here. Deployed and confirmed working as of 2026-08-01.

The ingress is plain HTTP rather than the internal-CA HTTPS every other *.home.arpa service uses — LAN callers (e.g. nik-gpu) don't have this cluster's internal CA trusted, and installing it on every caller was judged not worth it for an endpoint that's already bearer-token-authenticated and LAN-only. See homelab/manifests/home-services/alert-bridge-ingress.yaml's header comment for the full reasoning.

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.