From 85cc8494fa07b008d765cc7a40a063e3c8f0175c Mon Sep 17 00:00:00 2001 From: Nik Afiq Date: Sat, 1 Aug 2026 22:51:59 +0900 Subject: [PATCH] feat: add alert bridge service and related configurations --- .env.example | 6 ++ README.md | 3 +- .../home-services/alert-bridge-ingress.yaml | 39 +++++++++ .../home-services/alert-bridge-sealed.yaml | 15 ++++ .../home-services/alert-bridge-secret.sh | 25 ++++++ manifests/home-services/alert-bridge.yaml | 80 +++++++++++++++++++ values/pihole-debian.yaml | 1 + values/pihole.yaml | 3 +- 8 files changed, 170 insertions(+), 2 deletions(-) create mode 100644 manifests/home-services/alert-bridge-ingress.yaml create mode 100644 manifests/home-services/alert-bridge-sealed.yaml create mode 100755 manifests/home-services/alert-bridge-secret.sh create mode 100644 manifests/home-services/alert-bridge.yaml diff --git a/.env.example b/.env.example index d33351b..61c8abe 100644 --- a/.env.example +++ b/.env.example @@ -38,5 +38,11 @@ SWITCHBOT_SECRET=your_switchbot_secret_here # Dashy weather widget API key (rotated off the plaintext value formerly in config/dashy/conf.yaml) DASHY_WEATHER_API_KEY=your_api_key_here +# alert-bridge (Discord alert webhook relay) - prefixed since "API_KEY" alone +# is too generic a name for this shared flat .env namespace +ALERT_BRIDGE_API_KEY=your_api_key_here +ALERT_BRIDGE_DISCORD_WEBHOOK_URL=your_discord_webhook_url_here +ALERT_BRIDGE_MENTION_USER_ID=your_discord_user_id_here + PIA_USER=your_pia_username_here PIA_PASSWORD=your_pia_password_here \ No newline at end of file diff --git a/README.md b/README.md index 2fa900b..7f912da 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ Public services under `nik4nao.com` use Let's Encrypt. | Observability | kube-prometheus-stack, Grafana, Loki, Tempo, OpenTelemetry Collector, Glances | | Git and CI | Gitea, Gitea Actions runner, registry pull secrets, Gitea backup CronJob | | Media | Jellyfin, qBittorrent, JDownloader | -| Home services | Home Assistant ingress, HA gateway, AI gateway, Discord bot | +| Home services | Home Assistant ingress, HA gateway, AI gateway, Discord bot, Alexa bridge, Alert bridge | | Public apps | Portfolio, Watch Party ingress to the Mac Mini | | Dashboard | Dashy | @@ -61,6 +61,7 @@ Public services under `nik4nao.com` use Let's Encrypt. | `https://ha.home.arpa` | Home Assistant | Internal CA | | `https://glances.home.arpa` | Glances on K3s | Internal CA | | `https://glances-debian.home.arpa` | Glances on Debian | Internal CA | +| `https://alert-bridge.home.arpa` | Alert bridge (Discord webhook relay) | Internal CA | | `https://watch-party.nik4nao.com` | Watch Party on Mac Mini | Let's Encrypt | | `https://nik4nao.com` | Portfolio | Let's Encrypt | | `http://ca.home.arpa` | Internal CA installer | Plain HTTP | diff --git a/manifests/home-services/alert-bridge-ingress.yaml b/manifests/home-services/alert-bridge-ingress.yaml new file mode 100644 index 0000000..6fb5a4c --- /dev/null +++ b/manifests/home-services/alert-bridge-ingress.yaml @@ -0,0 +1,39 @@ +# Apply: kubectl apply -f manifests/home-services/alert-bridge-ingress.yaml +# Delete: kubectl delete -f manifests/home-services/alert-bridge-ingress.yaml +# Description: Internal-CA TLS certificate and Traefik IngressRoute for +# alert-bridge at alert-bridge.home.arpa, so a caller elsewhere on the home +# network (not inside the k8s cluster, e.g. a cronjob on another LAN host) can +# reach POST /alerts. Modeled on manifests/authentik/authentik-ingress.yaml's +# shape (internal-ca-issuer + websecure entrypoint) rather than +# alexa-bridge-public-ingress.yaml's letsencrypt-prod pattern, since this is +# not exposed to the public internet - LAN-only, same trust tier as Argo CD, +# Traefik's own dashboard, etc. +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: alert-bridge-tls + namespace: home-services +spec: + secretName: alert-bridge-tls + issuerRef: + name: internal-ca-issuer + kind: ClusterIssuer + dnsNames: + - alert-bridge.home.arpa +--- +apiVersion: traefik.io/v1alpha1 +kind: IngressRoute +metadata: + name: alert-bridge + namespace: home-services +spec: + entryPoints: + - websecure + routes: + - match: Host(`alert-bridge.home.arpa`) + kind: Rule + services: + - name: alert-bridge + port: 8080 + tls: + secretName: alert-bridge-tls diff --git a/manifests/home-services/alert-bridge-sealed.yaml b/manifests/home-services/alert-bridge-sealed.yaml new file mode 100644 index 0000000..24613c5 --- /dev/null +++ b/manifests/home-services/alert-bridge-sealed.yaml @@ -0,0 +1,15 @@ +--- +apiVersion: bitnami.com/v1alpha1 +kind: SealedSecret +metadata: + name: alert-bridge-secret + namespace: home-services +spec: + encryptedData: + API_KEY: AgBrR36fXjr9dIns0ygP/oa+/AowMElESRGiB88Y//v4aq+9Xhv4MzZJOLaLr9E3mPioNtvnh6fbgtswqPX+gldGUbU6FJEt5dSGwlRZpDR7B3QX+Dn7HBFLoiwGISqwFFxr1xD+vn0mrmV1KggriT26ofz4JW2IDMXrPArkyaeJ0F0XfbA4Ddv1BQ3iZ6ujoFaYRIOHYg8V1aRTr/cQZCyRXH3IGxIkkkyJNQ4SUWX1XoQ8aCjSxoawQKgVQUYbh9yaoy17+Qhs9dKlA4SS/8Ez5fBv2WfXb8jzX4qcm8J/jZoZjhtjJFpZeuAg7Gwj3f6pmVrtkUcWmYQRHO7ftHLpjgED/9CmVAOrqWpHmnRWQheSf2aeXPWyHUqSH17m6Z7uZd51Apkkge1+ZdGIEpgJPgzsjAC/ACLgOoAqV+LlD1ZezyesK19VvvtYsXbjKhCcN1eAqSGm/oTo6LVWkgthedfUnRzT3srZLtwcbL6/YONGcL+Ah+RlDXXfa0SGMyRSr8Wi8bpNMIwx3NN5b6WSoDWAZ6Hlny+UvqfAcCAR/pZyzbD3PC3pE90ewpvt4gOzy/LjRxcLt1D3E5uE82WvdyLGzLoP2MSVY6W3VeYV2PzZOF2nuHu19eNVmipe8j4kuBetdFuNEMXUe2nKgGKed+w8t+59Kq2fkEJEpPppWzDPMbmTSiZF4rygBjEfQlslzv/mm3qTTbsFx1vw2ziQpaxs75WM0tZwlsWlromx8txpOx+klWlQzUu10h2qXcHf2boAgjBGknUmoCXmlEo/ + DISCORD_WEBHOOK_URL: AgAV8De/Nv2tF8Qp+lshiPYF6C0HNPRhotriB/aUwCTMhZ481L6fFet91yLMPtQQNupAETAqsy/3Xl5AYyJeR8zGF1qeG1AbjFVtURQpha1kkaGdl6c01sZAxX2ASCIO8RmqUPbRrxs7RvQh0tSNYc5UVoCl9N7dUrNXsS/wKigdvRydSCuJ1q39I2mL/NX0ZHcH81nCkTmNSZ2Z7MJ+VAe/WAydRv7qCaj13c6okKP5fWiSnrjw5/uLGlhcOQNObu6I9JbsA16fDQd5ez1Uu+EQfmHXtvIuZVMuOR/IEESG0Zvm+jUBA/0MHIovMI6TMIOQI8DqO/r7jNXB9pP6sJW9qRT54S9rtHH0+5VPe/QL6IqWQzEeoHMLEEikL0heks7Ww2HEKRVOnmmEomdVX/MzPZatWJpV7oshlWaTAjJC/gH0yvxj393cuKubOWT0J/POZfrnMfAZd3sgE4eoX9xWrp1gNsEatExBcnaLKdipvGNAirMhieQZC0+VvevO0VEuDfV5/B07OlC6Zbg1BVLm2btvODXp+eKHDeG4cqd74eWzJoEuSwZbkTH3EAyJy7j7scElFz1ZlTOJmYypxmRFhxeqYRPSXsHCHrBrCmwsj5LzE6ih3QnyVdi8fZWslBKDoz4u7K2Dy3bjP/zmWmygIEMZ5aCR42AcH+mwTgNDmU5x93h4jkl7atWAmz35oHKjDazLhNLM09gSwnwkp2zGeFUGSQb3G3oZF92+cy2Fj1Iqkraz2Ische+m+q91eG8coJgzygDb7y2RvAkMwMSTNMp3BySXlUw6dqj8mEjArAhRK2Pn03BlGYTvbiHJJea4GtjjxqpU9lk81SGyMM7Vcf2lnJ3rPf+6 + MENTION_USER_ID: AgCqqxjnt/xiDfBj7+3i3p46+/yqeDvtLJPLdRwiGWFFJZ0gbm7tDH2q04GatSzxLM/NI5yapIbizmBmBqswEfGyraaTtwP9uBoQZsc/vZBncSoxAkHQvDzJr67KL0yOpIyYm+JeY8KPyIra6INozE0Ir6sLo8diyKxLvPlxvg9UJMfEp5eRdk01PxgBkj9kWLWA3shRNZlH+WfnzPILvc9TUPaSI2xq8QJaKOg93Wqf6grqK5x/6C4vV7nMzPo/NOSSfZ76Da8iUig4mZJ0aSzdZqBQSOhu4EBspG8iA9q/Ptn0Oao5nNTF53ozDdADAS0hJ3ODHaLi+iyTg3N0tWzK0ivsGs7yRe37NuGwdYEx2H2GMxSZTYlMX+v/FPsGw2tktkokUkjwzEcw7iq5XaHUW6j2XwB8WXpde3mYMx3ZhysJRwALkI9wNEK3npGl9pNrGkyTVXwQkcatAO2G2n7sxc0moCa74h3RHiSy0MtiIa79A/JhPlFVg3W4DXTaKRrNb8RhFjhvB+HVvJ9MWxcAicJMUiZftnsYGEJiTg2nDdmJM8Hof0+AJRXDCy3txcWnzOx2hTjGvBzI4qpSiU4+LU5FKPYrZ0ZtkHXYRil4M/BKS1pCPFoMQf10qsBDOHJ3jKhq3zG33qaVaF/vYljMr+q1TutIETzSJNzKLBmCjj9+PKslpiNm6493a9zuTwvm8VAAY0WXoxeKLj3ov7SLbP8= + template: + metadata: + name: alert-bridge-secret + namespace: home-services diff --git a/manifests/home-services/alert-bridge-secret.sh b/manifests/home-services/alert-bridge-secret.sh new file mode 100755 index 0000000..883baff --- /dev/null +++ b/manifests/home-services/alert-bridge-secret.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env bash +# Usage: bash manifests/home-services/alert-bridge-secret.sh +# Description: Regenerates the alert-bridge SealedSecret from .env +# +# Deliberately reads prefixed ALERT_BRIDGE_* variable names from the shared +# .env, unlike ha-gateway-secret.sh/discord-bot-secret.sh's unprefixed +# HA_TOKEN/DISCORD_TOKEN - "API_KEY" alone is too generic a name to trust to +# this repo's single flat .env namespace shared across every service's +# secret.sh script. +set -euo pipefail + +source "$(dirname "$0")/../../.env" + +kubectl create secret generic alert-bridge-secret \ + --namespace=home-services \ + --from-literal=API_KEY="${ALERT_BRIDGE_API_KEY}" \ + --from-literal=DISCORD_WEBHOOK_URL="${ALERT_BRIDGE_DISCORD_WEBHOOK_URL}" \ + --from-literal=MENTION_USER_ID="${ALERT_BRIDGE_MENTION_USER_ID}" \ + --dry-run=client -o yaml \ + | kubeseal --controller-namespace=kube-system \ + --controller-name=sealed-secrets-controller \ + --format=yaml \ + > "$(dirname "$0")/alert-bridge-sealed.yaml" + +echo "alert-bridge-sealed.yaml regenerated — commit to repo" diff --git a/manifests/home-services/alert-bridge.yaml b/manifests/home-services/alert-bridge.yaml new file mode 100644 index 0000000..3a7329e --- /dev/null +++ b/manifests/home-services/alert-bridge.yaml @@ -0,0 +1,80 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: alert-bridge + namespace: home-services + labels: + app: alert-bridge +spec: + replicas: 1 + selector: + matchLabels: + app: alert-bridge + template: + metadata: + labels: + app: alert-bridge + spec: + imagePullSecrets: + - name: gitea-registry-secret + containers: + - name: alert-bridge + image: gitea.nik4nao.com/nik/alert-bridge:latest + ports: + - containerPort: 8080 + name: http + env: + - name: HTTP_PORT + value: "8080" + - name: API_KEY + valueFrom: + secretKeyRef: + name: alert-bridge-secret + key: API_KEY + - name: DISCORD_WEBHOOK_URL + valueFrom: + secretKeyRef: + name: alert-bridge-secret + key: DISCORD_WEBHOOK_URL + - name: MENTION_USER_ID + valueFrom: + secretKeyRef: + name: alert-bridge-secret + key: MENTION_USER_ID + - name: OTEL_ENDPOINT + value: "otel-collector-opentelemetry-collector.monitoring.svc.cluster.local:4317" + readinessProbe: + httpGet: + path: /healthz + port: 8080 + initialDelaySeconds: 5 + periodSeconds: 10 + livenessProbe: + httpGet: + path: /healthz + port: 8080 + initialDelaySeconds: 10 + periodSeconds: 30 + resources: + requests: + cpu: 50m + memory: 64Mi + limits: + cpu: 200m + memory: 128Mi +--- +apiVersion: v1 +kind: Service +metadata: + name: alert-bridge + namespace: home-services + labels: + app: alert-bridge +spec: + selector: + app: alert-bridge + ports: + - name: http + port: 8080 + targetPort: 8080 + type: ClusterIP diff --git a/values/pihole-debian.yaml b/values/pihole-debian.yaml index e53b34a..43e1a73 100644 --- a/values/pihole-debian.yaml +++ b/values/pihole-debian.yaml @@ -51,6 +51,7 @@ dnsmasq: - address=/ha.home.arpa/192.168.7.77 - address=/argocd.home.arpa/192.168.7.77 - address=/gluetun.home.arpa/192.168.7.77 + - address=/alert-bridge.home.arpa/192.168.7.77 persistentVolumeClaim: enabled: true diff --git a/values/pihole.yaml b/values/pihole.yaml index cd3ca12..12129a3 100644 --- a/values/pihole.yaml +++ b/values/pihole.yaml @@ -81,4 +81,5 @@ dnsmasq: - address=/gitea.nik4nao.com/192.168.7.77 - address=/ha.home.arpa/192.168.7.77 - address=/argocd.home.arpa/192.168.7.77 - - address=/gluetun.home.arpa/192.168.7.77 \ No newline at end of file + - address=/gluetun.home.arpa/192.168.7.77 + - address=/alert-bridge.home.arpa/192.168.7.77 \ No newline at end of file