Haven Notify Logo
## Overview Haven Notify is an internal Go service that validates notification requests, renders Discord-compatible payloads, and delivers them to a configured Discord webhook. It runs as a standalone binary or container. Templates are embedded in the binary and validated at startup. ## Prerequisites - Go 1.25.10 - A Docker API compatible with Testcontainers for the end-to-end suite - A Discord webhook URL for normal service operation `WEBHOOK_URL` is required and must be an absolute HTTP or HTTPS URL. The process exits before becoming ready when the configuration or embedded templates are invalid. ## API All notification endpoints require `POST` and `Content-Type: application/json`. Request bodies are limited to 64 KiB, documented fields are required, and unknown fields are accepted for forward compatibility. ### Send Notification - **Endpoint**: `/notify` - **Request Body**: ```json { "title": "Notification Title", "message": "Notification Message" } ``` ### Send Backup Notification - **Endpoint**: `/template/notify/backup` - **Request Body**: ```json { "title": "Nightly backup", "asset": "Photos", "backupSizeInMB": 1536, "extra": [ { "name": "Host", "value": "nebula" } ] } ``` ### Send Update Notification - **Endpoint**: `/template/notify/update` - **Request Body**: ```json { "host": "nexus", "asset": "k3s", "time": 42 } ``` `time` is expressed in seconds. ### Send Error Notification - **Endpoint**: `/template/notify/error` - **Request Body**: ```json { "caller": "backup-job", "message": "Error while moving file", "critical": true, "extra": [ { "name": "Path", "value": "/mnt/backups" } ] } ``` ### Health Probes - `GET` or `HEAD /ready` returns `200 Ready` after configuration and templates initialize. - `GET` or `HEAD /live` returns `200 Alive` while the process can serve HTTP. ### Response Statuses | Status | Meaning | |---|---| | `200` | Notification delivered | | `400` | Invalid or missing request data | | `404` | Unknown route or template | | `405` | Unsupported method | | `415` | Content type is not JSON | | `502` | Discord rejected the request or delivery retries failed | | `504` | Discord delivery exceeded its timeout | Haven Notify suppresses Discord mention parsing and safely truncates content to Discord's message and embed limits. Transport errors, rate limits, and server errors receive at most two retries within a ten-second total delivery budget. ## Run ### Go ```sh WEBHOOK_URL=https://discord.com/api/webhooks/... go run . ``` ### Docker ```sh docker build -t haven-notify . docker run --rm -p 8080:8080 \ -e WEBHOOK_URL=https://discord.com/api/webhooks/... \ haven-notify ``` The runtime container uses a non-root `haven-notify` user and contains only the binary, Alpine runtime files, and CA certificates. ## Tests Run unit and HTTP contract tests: ```sh go test ./... ``` Run static analysis, the race detector, and coverage (the race detector requires CGO support): ```sh go vet ./... go test -race -shuffle=on -count=1 -covermode=atomic -coverprofile=coverage.out ./... go tool cover -func=coverage.out ``` Run the seeded fuzz target: ```sh go test -run=^$ -fuzz=FuzzErrorNotificationHandler -fuzztime=10s . ``` Run the black-box container suite: ```sh go test -tags=e2e -run '^TestContainerE2E$' -count=1 -timeout=5m -v . ``` The end-to-end suite builds the production Dockerfile, starts Haven Notify and a disposable MockServer on an isolated network, and verifies the complete HTTP-to-Discord payload flow. It never calls a real Discord webhook. Configure `DOCKER_HOST` or the Testcontainers host settings when the Docker daemon is not available through the platform's default socket. ## Deployment The Kubernetes Deployment, Service, and Ingress manifest lives in the `haven` homelab repository at `infra/haven-notify.yaml`. This repository holds the service source, Dockerfile, tests, and Gitea Actions pipeline. The production manifest maps the `HAVEN_WEBHOOK_URL` key from the `discord-webhook` secret into the container's `WEBHOOK_URL` environment variable.