Update flask_notification_routes.py

This commit is contained in:
MacRimi
2026-02-21 20:49:59 +01:00
parent 0d854ae42b
commit 91da8db589

View File

@@ -231,9 +231,10 @@ def _pve_remove_our_blocks(text, headers_to_remove):
def _build_webhook_fallback(): def _build_webhook_fallback():
"""Build fallback manual commands for webhook setup.""" """Build fallback manual commands for webhook setup."""
body_tpl = '{"title":"{{ escape title }}","message":"{{ escape message }}","severity":"{{ severity }}","timestamp":"{{ timestamp }}","type":"{{#if fields.type}}{{ fields.type }}{{else}}test{{/if}}","hostname":"{{#if fields.hostname}}{{ fields.hostname }}{{else}}unknown{{/if}}"}'
return [ return [
"# Append to END of /etc/pve/notifications.cfg", "# 1. Append to END of /etc/pve/notifications.cfg",
"# (do NOT delete existing content):", "# (do NOT delete existing content):",
"", "",
f"webhook: {_PVE_ENDPOINT_ID}", f"webhook: {_PVE_ENDPOINT_ID}",
f"\tmethod post", f"\tmethod post",
@@ -243,8 +244,11 @@ def _build_webhook_fallback():
f"\ttarget {_PVE_ENDPOINT_ID}", f"\ttarget {_PVE_ENDPOINT_ID}",
"\tmode all", "\tmode all",
"", "",
"# ALSO append to /etc/pve/priv/notifications.cfg :", "# 2. Append to /etc/pve/priv/notifications.cfg :",
f"webhook: {_PVE_ENDPOINT_ID}", f"webhook: {_PVE_ENDPOINT_ID}",
"",
"# 3. Set body via pvesh (NOT in the config file -- PVE stores it base64):",
f"pvesh set /cluster/notifications/endpoints/webhook/{_PVE_ENDPOINT_ID} --body '{body_tpl}'",
] ]
@@ -306,15 +310,10 @@ def setup_pve_webhook_core() -> dict:
# PVE secret format is: secret name=key,value=<base64> # PVE secret format is: secret name=key,value=<base64>
# Neither is needed for localhost calls. # Neither is needed for localhost calls.
# PVE webhook format requires: # Write ONLY basic properties (method, url) to the config file.
# - body: Handlebars template for the HTTP body # body and header MUST be set via pvesh API (Step 10) because PVE
# - header: Content-Type header for JSON # stores them base64-encoded internally. Writing them as plain text
# - A matching entry in priv/notifications.cfg (even if empty) # to the config file corrupts PVE's config parser.
# The body template uses PVE's Handlebars syntax to pass notification
# metadata to our webhook handler as structured JSON.
# PVE sends JSON by default (title, message, severity, timestamp, fields).
# Do NOT set header or body -- PVE's config parser rejects custom header
# formats and a missing/malformed header line corrupts the ENTIRE config.
endpoint_block = ( endpoint_block = (
f"webhook: {_PVE_ENDPOINT_ID}\n" f"webhook: {_PVE_ENDPOINT_ID}\n"
f"\tmethod post\n" f"\tmethod post\n"
@@ -382,32 +381,43 @@ def setup_pve_webhook_core() -> dict:
pass pass
# ── Step 10: Configure body and header via pvesh API ── # ── Step 10: Configure body and header via pvesh API ──
# Writing header/body directly to the config file uses a different # body and header are stored base64-encoded in the config file.
# internal format that PVE's parser rejects. Using pvesh set handles # Writing them as plain text corrupts PVE's parser. pvesh handles
# escaping and the priv-config wire format correctly. # the encoding correctly.
import subprocess import subprocess
# Body template: PVE Handlebars that sends JSON to our webhook pvesh_path = f'/cluster/notifications/endpoints/webhook/{_PVE_ENDPOINT_ID}'
body_template = '{"title":"{{ escape title }}","message":"{{ escape message }}","severity":"{{ severity }}","timestamp":"{{ timestamp }}"}'
# Body template using PVE Handlebars syntax.
# - {{ escape title }} and {{ escape message }} are PVE built-in helpers
# - {{ severity }} gives: info, notice, warning, error, unknown
# - {{ fields.X }} is populated for real events (backup, replication, etc.)
# but NOT for test notifications, so we use {{#if}} fallbacks.
# - {{ timestamp }} gives epoch seconds
body_template = (
'{'
'"title":"{{ escape title }}",'
'"message":"{{ escape message }}",'
'"severity":"{{ severity }}",'
'"timestamp":"{{ timestamp }}",'
'"type":"{{#if fields.type}}{{ fields.type }}{{else}}test{{/if}}",'
'"hostname":"{{#if fields.hostname}}{{ fields.hostname }}{{else}}unknown{{/if}}"'
'}'
)
try: try:
# Set body template # Set body template via pvesh (NOT in the config file -- PVE
subprocess.run( # stores body base64-encoded internally, pvesh handles that).
['pvesh', 'set', # No header needed: our webhook handler parses JSON from raw
f'/cluster/notifications/endpoints/webhook/{_PVE_ENDPOINT_ID}', # body regardless of Content-Type.
'--body', body_template], r1 = subprocess.run(
capture_output=True, text=True, timeout=10 ['pvesh', 'set', pvesh_path, '--body', body_template],
)
# Set Content-Type header
subprocess.run(
['pvesh', 'set',
f'/cluster/notifications/endpoints/webhook/{_PVE_ENDPOINT_ID}',
'--header', 'Content-Type:application/json'],
capture_output=True, text=True, timeout=10 capture_output=True, text=True, timeout=10
) )
if r1.returncode != 0:
result['body_config_warning'] = f'pvesh set --body failed: {r1.stderr.strip()}'
except Exception as e: except Exception as e:
# Non-fatal: webhook still works, just sends raw format # Non-fatal: if pvesh fails, webhook still receives POSTs (just empty body)
result['body_config_warning'] = str(e) result['body_config_warning'] = str(e)
result['configured'] = True result['configured'] = True