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

alexa-bridge

alexa-bridge is a self-hosted Alexa Custom Skill backend. It verifies that incoming HTTPS requests actually came from Alexa, resolves the spoken device name against entities discovered from ha-gateway, and executes the resulting action over gRPC. Unlike the other three services in this repo, it is internet-facing — Alexa's servers call directly into it — so it is the one service whose own inbound edge is untrusted by default and needs its own request verification, on top of the mTLS it uses (like ai-gateway and discord-bot) to call ha-gateway.

See plan.md for the full design, the action-table mapping from Alexa intents to ha-gateway RPCs, and the resolved "Decisions on open questions" section documenting scope choices (single-setpoint-only climate control, exact-match-only entity name resolution, no remote/SwitchBot support, etc.).

Runtime Flow

  1. The service loads .env, configures logging and telemetry, blocking-fetches the initial entity list from ha-gateway's EntityService (failing startup if that fails — nothing can resolve a device name without it), then starts serving HTTP on HTTP_PORT.
  2. A background goroutine refreshes the entity list every ENTITY_REFRESH_INTERVAL; a failed periodic refresh only logs and keeps serving the last-known-good list.
  3. Each incoming Alexa request is verified: SignatureCertChainUrl/ Signature headers (cert chain fetched from Amazon's S3 host and cached, RSA-SHA1 signature over the raw body), request timestamp (150s replay tolerance), and the request envelope's application ID against ALEXA_SKILL_ID.
  4. Verified IntentRequests are routed by intent name; the Device slot is resolved (exact match, normalized) against the cached entity list, and the resulting (entity_id, action, params) triple is executed via ha-gateway's Light/Switch/Climate services over mTLS.
  5. Resolution and execution failures produce a spoken failure response rather than an HTTP error — a Dispatch-layer error would surface as Alexa's own generic failure speech instead of a message this skill controls.

Configuration

Environment variables:

Variable Default Description
HTTP_PORT 8080 HTTP listen port
ALEXA_SKILL_ID — (required) Verified against the request envelope's application ID
HA_GATEWAY_ADDR ha-gateway.home-services.svc.cluster.local:50051 gRPC address for ha-gateway
HA_GATEWAY_SERVER_NAME ha-gateway.home-services.svc.cluster.local Expected server name for mTLS
TLS_DIR /tls mTLS client cert dir for the ha-gateway call. Unlike ai-gateway/discord-bot, this defaults on rather than empty — alexa-bridge is internet-facing, so mTLS to ha-gateway should be on by default rather than opt-in. Set empty to disable for local plaintext dev.
ENTITY_REFRESH_INTERVAL 5m Periodic entity list refresh cadence
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

When TLS_DIR is set, the directory must contain tls.crt, tls.key, and ca.crt.

Local Run

Start ha-gateway first, then run:

cd alexa-bridge
cp .env.example .env
# edit .env: set ALEXA_SKILL_ID, leave TLS_DIR empty for plaintext local dev
go run ./cmd/bridge

alexa-bridge doesn't register with Alexa on its own — during development, point Alexa's request signature verification at a tunneled local endpoint (e.g. ngrok) and set that HTTPS URL as the skill's endpoint in the Alexa developer console. /healthz returns 200 OK for liveness/readiness probes.

HTTP API

There is no gRPC surface here (unlike the other three services) — this is a plain HTTP server:

  • POST /: the Alexa Custom Skill endpoint. Expects the standard Alexa request envelope as JSON, with SignatureCertChainUrl and Signature headers.
  • GET /healthz: unauthenticated liveness/readiness check, always 200 OK.

Deployment

Kubernetes manifests (Deployment/Service, the alexa-bridge-tls mTLS client cert, and the public HTTPS ingress) live in the separate homelab repo, not here — see plan.md's mTLS section and "Decisions on open questions" #2 for what's drafted there.