Generate alertmanager.yml so no-webhook is a valid state, not a broken one
Deploying before the webhook exists is deliberate, so it must not depend on Alertmanager tolerating an empty webhook_url_file — which I could not verify. configure.py now generates the config and omits the Discord notifier entirely when ALERT_WEBHOOK_URL is unset: the receiver exists, drops notifications, and alerts still evaluate, group and show at /alerts. Setting the variable and re-applying turns delivery on. The URL stays in /etc/vanguard/alert-webhook rather than the config, because that file can be 0640 root:65534 for the container's user while the repo copy cannot. alertmanager.example.yml is the reference copy and is gitignored in generated form, like the other configs. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
57
configure.py
57
configure.py
@@ -17,7 +17,7 @@ Reads : Caddyfile (hostnames), mediamtx.yml (streamers), .env
|
||||
(editors/admins are Authentik groups now: editors, media-admins, admins — nothing local)
|
||||
(these are GENERATED on the server and gitignored; *.example.* in the repo are reference only —
|
||||
never scp them over the live ones: that is how streamers got wiped once)
|
||||
Writes : Caddyfile, mediamtx.yml, copyparty.conf, stream-token.secret -> /etc/vanguard, .env (generated secrets +
|
||||
Writes : Caddyfile, mediamtx.yml, copyparty.conf, observability/alertmanager.yml, stream-token.secret -> /etc/vanguard, .env (generated secrets +
|
||||
Discord bot token/guild), folders, restarts
|
||||
Set VG_DRY=1 to write files only (no sudo / docker / folders).
|
||||
"""
|
||||
@@ -500,6 +500,59 @@ paths:
|
||||
~^[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+$: {{}}
|
||||
""")
|
||||
|
||||
def write_alertmanager():
|
||||
"""Alertmanager config. Generated, because the Discord receiver must be ABSENT when no webhook is set:
|
||||
a receiver with no notifiers is valid and simply drops notifications, whereas pointing discord_configs at
|
||||
an empty webhook_url_file may stop Alertmanager from loading at all. The URL itself stays out of this file
|
||||
and in /etc/vanguard/alert-webhook (0640 root:65534), which is what webhook_url_file reads."""
|
||||
webhook = read_env().get("ALERT_WEBHOOK_URL", "").strip()
|
||||
discord = """ discord_configs:
|
||||
- webhook_url_file: /etc/vanguard/alert-webhook
|
||||
title: '{{ if eq .Status "firing" }}🔴{{ else }}✅{{ end }} {{ .CommonLabels.alertname }}'
|
||||
message: |
|
||||
{{ range .Alerts }}{{ .Annotations.summary }}
|
||||
{{ if .Annotations.detail }}{{ .Annotations.detail }}
|
||||
{{ end }}{{ end }}
|
||||
""" if webhook.startswith("https://") else """ # No ALERT_WEBHOOK_URL in .env, so this receiver sends nowhere: alerts still group and still show at
|
||||
# https://DIAG_HOST/alerts, they just are not delivered. Set the variable and re-apply to turn it on.
|
||||
"""
|
||||
Path("observability/alertmanager.yml").write_text(f"""# Alertmanager — GENERATED by configure.py from ALERT_WEBHOOK_URL. Reference copy: alertmanager.example.yml
|
||||
# The heartbeat route is half of the deadman: the Watchdog alert always fires, so Alertmanager posts to dumpd
|
||||
# every minute and bin/vanguard-deadman.sh treats a STALE heartbeat as the alarm.
|
||||
global:
|
||||
resolve_timeout: 5m
|
||||
|
||||
route:
|
||||
receiver: discord
|
||||
group_by: [alertname, service]
|
||||
group_wait: 30s
|
||||
group_interval: 5m
|
||||
repeat_interval: 12h
|
||||
routes:
|
||||
- matchers: [severity="none"]
|
||||
receiver: heartbeat
|
||||
group_wait: 0s
|
||||
group_interval: 1m
|
||||
repeat_interval: 1m
|
||||
- matchers: [severity="critical"]
|
||||
receiver: discord
|
||||
group_wait: 10s
|
||||
repeat_interval: 1h
|
||||
|
||||
inhibit_rules:
|
||||
- source_matchers: [alertname="MediaDiskCritical"]
|
||||
target_matchers: [alertname="MediaDiskFilling"]
|
||||
- source_matchers: [alertname="ContainerGone"]
|
||||
target_matchers: [alertname="ContainerRestartLoop"]
|
||||
|
||||
receivers:
|
||||
- name: discord
|
||||
{discord} - name: heartbeat
|
||||
webhook_configs:
|
||||
- url: http://host.docker.internal:8081/heartbeat
|
||||
send_resolved: false
|
||||
""")
|
||||
|
||||
def write_copyparty():
|
||||
"""copyparty behind Caddy + Authentik: identity and groups arrive as headers; permissions are per group."""
|
||||
key = read_env().get("COPYPARTY_IDP_KEY", "key")
|
||||
@@ -560,7 +613,7 @@ def apply(domain, admin_pw, streamers, editors, auth_host="", watch_host="", dis
|
||||
if not yes(" continue anyway?", False): print("aborted; add streamers first ([s] in the menu)"); sys.exit(1)
|
||||
print("\n== writing configs")
|
||||
write_env(watch_host, discord_token, discord_guild, git_host, bot_host, live_host, domain, live_webhook, files_host, sync_host, clips_host, diag_host)
|
||||
write_caddy(domain, auth_host, watch_host, git_host, bot_host, live_host, files_host, sync_host, clips_host, diag_host); write_mediamtx(domain); write_copyparty()
|
||||
write_caddy(domain, auth_host, watch_host, git_host, bot_host, live_host, files_host, sync_host, clips_host, diag_host); write_mediamtx(domain); write_copyparty(); write_alertmanager()
|
||||
Path("stream-token.secret").write_text(read_env()["STREAM_TOKEN_SECRET"] + "\n"); os.chmod("stream-token.secret", 0o600)
|
||||
run(["sudo", "install", "-d", "-o", os.environ.get("SUDO_USER") or os.environ.get("USER") or "debian", "/srv/live"])
|
||||
print("== app data folders")
|
||||
|
||||
Reference in New Issue
Block a user