feat: add homelab topology documentation, infrastructure improvement plans, and management scripts
This commit is contained in:
245
scripts/setup_homeassistant_notifications.py
Normal file
245
scripts/setup_homeassistant_notifications.py
Normal file
@@ -0,0 +1,245 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Configure Home Assistant (14.4.4.20:8123) Notification Forwarding & Infrastructure Automations:
|
||||
1. Updates gotify-discord-bridge on 140.44.4.71 with Home Assistant logbook.log watcher.
|
||||
2. Creates 'script.snarfnet_broadcast' in Home Assistant for unified Gotify + Discord + Mobile App alerts.
|
||||
3. Creates 'Snarfnet UPS Power & Exhaust Protocol' and 'Snarfnet Server Room Thermal Guard' automations.
|
||||
4. Enhances the 4 Bambu H2D 3D Printer automations to also broadcast to Gotify & Discord #home-assistant.
|
||||
5. Triggers a live end-to-end verification notification with real-time Server Room & UPS telemetry.
|
||||
"""
|
||||
import json
|
||||
import sys
|
||||
import time
|
||||
import urllib.request
|
||||
import paramiko
|
||||
|
||||
sys.stdout.reconfigure(encoding="utf-8")
|
||||
|
||||
HA_URL = "http://14.4.4.20:8123"
|
||||
HA_TOKEN = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJkZWNlMWI5ZjJhZDQ0MTRmODU4NGYzN2I1YjgwZDQxMiIsImlhdCI6MTc5MzQ1MjQ2MywiZXhwIjoyMTA4ODEyNDYzfQ.JznpOqsCSnHz6aTVa8K9pQCz1816DC8SrmS1xn1Sm2k"
|
||||
|
||||
|
||||
def ha_req(path, method="GET", data=None):
|
||||
req = urllib.request.Request(
|
||||
f"{HA_URL}{path}",
|
||||
data=json.dumps(data).encode("utf-8") if data is not None else None,
|
||||
headers={
|
||||
"Authorization": f"Bearer {HA_TOKEN}",
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
method=method,
|
||||
)
|
||||
with urllib.request.urlopen(req, timeout=12) as r:
|
||||
body = r.read().decode("utf-8")
|
||||
return json.loads(body) if body else {}
|
||||
|
||||
|
||||
def main():
|
||||
# 1. Update discord_bridge.py on 140.44.4.71 and restart bridge container
|
||||
with open(r"c:\Users\Snarf\OneDrive\Desktop\code\SnarfCode\scripts\gotify_discord_bridge.py", "r", encoding="utf-8") as f:
|
||||
bridge_code = f.read()
|
||||
|
||||
ssh = paramiko.SSHClient()
|
||||
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
ssh.connect("140.44.4.71", username="snarf", password="Katzir476!", timeout=10)
|
||||
sftp = ssh.open_sftp()
|
||||
with sftp.file("/tmp/discord_bridge.py", "w") as rf:
|
||||
rf.write(bridge_code)
|
||||
sftp.close()
|
||||
|
||||
cmd = r"""echo 'Katzir476!' | sudo -S bash -c '
|
||||
rm -f /var/lib/docker/volumes/gotify_gotify_data/_data/ha_last_logbook_when.txt /mnt/swarm_shared/legacy_volumes/gotify/ha_last_logbook_when.txt
|
||||
cp /tmp/discord_bridge.py /var/lib/docker/volumes/gotify_gotify_data/_data/discord_bridge.py
|
||||
cp /tmp/discord_bridge.py /mnt/swarm_shared/legacy_volumes/gotify/discord_bridge.py
|
||||
chmod 755 /var/lib/docker/volumes/gotify_gotify_data/_data/discord_bridge.py /mnt/swarm_shared/legacy_volumes/gotify/discord_bridge.py
|
||||
docker service scale gotify_gotify-discord-bridge=0
|
||||
docker rm -f $(docker ps -aq -f name=gotify_gotify-discord-bridge) 2>/dev/null || true
|
||||
docker service scale gotify_gotify-discord-bridge=1
|
||||
'"""
|
||||
stdin, stdout, stderr = ssh.exec_command(cmd)
|
||||
stdout.read()
|
||||
print("Updated and restarted gotify_gotify-discord-bridge on 140.44.4.71.")
|
||||
|
||||
# 2. Provision script.snarfnet_broadcast in Home Assistant
|
||||
broadcast_script = {
|
||||
"alias": "Snarfnet Broadcast Notification",
|
||||
"description": "Broadcasts alerts to Gotify, Discord #home-assistant webhook, and Pixel Companion Apps",
|
||||
"mode": "parallel",
|
||||
"fields": {
|
||||
"title": {"description": "Notification title", "example": "Smart Home Alert"},
|
||||
"message": {"description": "Notification body", "example": "Event details"},
|
||||
"notification_id": {"description": "Unique notification key", "example": "snarfnet_alert"},
|
||||
},
|
||||
"sequence": [
|
||||
{
|
||||
"action": "logbook.log",
|
||||
"data": {
|
||||
"name": "{{ title | default('Home Assistant Alert') }}",
|
||||
"message": "{{ message }}",
|
||||
"entity_id": "script.snarfnet_broadcast",
|
||||
},
|
||||
},
|
||||
{
|
||||
"action": "persistent_notification.create",
|
||||
"data": {
|
||||
"title": "{{ title | default('Home Assistant Alert') }}",
|
||||
"message": "{{ message }}",
|
||||
"notification_id": "{{ notification_id | default('snarfnet_' ~ (now().timestamp() | int)) }}",
|
||||
},
|
||||
},
|
||||
{
|
||||
"action": "notify.mobile_app_pixel_10_pro_fold",
|
||||
"data": {
|
||||
"title": "{{ title | default('Home Assistant Alert') }}",
|
||||
"message": "{{ message }}",
|
||||
},
|
||||
},
|
||||
{
|
||||
"action": "notify.mobile_app_pixel_9_pro_fold",
|
||||
"data": {
|
||||
"title": "{{ title | default('Home Assistant Alert') }}",
|
||||
"message": "{{ message }}",
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
ha_req("/api/config/script/config/snarfnet_broadcast", method="POST", data=broadcast_script)
|
||||
print("Configured script.snarfnet_broadcast in Home Assistant.")
|
||||
|
||||
# 3. Provision UPS Power State & Exhaust Fan Automation
|
||||
ups_automation = {
|
||||
"id": "1793453000001",
|
||||
"alias": "Snarfnet UPS Power & Exhaust Protocol",
|
||||
"description": "Broadcasts UPS state transitions to Gotify/Discord/Mobile and engages Server Closet exhaust fan on battery",
|
||||
"triggers": [
|
||||
{
|
||||
"trigger": "state",
|
||||
"entity_id": ["sensor.server_closet_status_data"],
|
||||
}
|
||||
],
|
||||
"conditions": [
|
||||
{
|
||||
"condition": "template",
|
||||
"value_template": "{{ trigger.from_state.state != trigger.to_state.state and trigger.to_state.state not in ['unknown', 'unavailable'] }}",
|
||||
}
|
||||
],
|
||||
"actions": [
|
||||
{
|
||||
"action": "script.snarfnet_broadcast",
|
||||
"data": {
|
||||
"title": "⚡ UPS Status Changed: {{ states('sensor.server_closet_status') }} ({{ states('sensor.server_closet_status_data') }})",
|
||||
"message": "Server Closet UPS transitioned from {{ trigger.from_state.state }} to {{ trigger.to_state.state }}. Battery: {{ states('sensor.server_closet_battery_charge') }}% | Load: {{ states('sensor.server_closet_load') }}% | Input: {{ states('sensor.server_closet_input_voltage') }}V | Closet Temp: {{ states('sensor.server_room_temperature_temperature') }}°F.",
|
||||
"notification_id": "snarfnet_ups_state",
|
||||
},
|
||||
},
|
||||
{
|
||||
"choose": [
|
||||
{
|
||||
"conditions": [
|
||||
{
|
||||
"condition": "template",
|
||||
"value_template": "{{ 'OB' in states('sensor.server_closet_status_data') or 'LB' in states('sensor.server_closet_status_data') }}",
|
||||
}
|
||||
],
|
||||
"sequence": [
|
||||
{
|
||||
"action": "fan.set_percentage",
|
||||
"target": {"entity_id": "fan.server_closet_switch"},
|
||||
"data": {"percentage": 100},
|
||||
}
|
||||
],
|
||||
}
|
||||
]
|
||||
},
|
||||
],
|
||||
"mode": "queued",
|
||||
}
|
||||
ha_req("/api/config/automation/config/1793453000001", method="POST", data=ups_automation)
|
||||
print("Configured automation 'Snarfnet UPS Power & Exhaust Protocol' (1793453000001).")
|
||||
|
||||
# 4. Provision Server Room Thermal Guard Automation
|
||||
thermal_automation = {
|
||||
"id": "1793453000002",
|
||||
"alias": "Snarfnet Server Room Thermal Guard",
|
||||
"description": "Automatically engages Server Closet exhaust fan and broadcasts alert if Server Room exceeds 82°F",
|
||||
"triggers": [
|
||||
{
|
||||
"trigger": "numeric_state",
|
||||
"entity_id": ["sensor.server_room_temperature_temperature"],
|
||||
"above": 82,
|
||||
"for": {"hours": 0, "minutes": 2, "seconds": 0},
|
||||
}
|
||||
],
|
||||
"conditions": [],
|
||||
"actions": [
|
||||
{
|
||||
"action": "fan.set_percentage",
|
||||
"target": {"entity_id": "fan.server_closet_switch"},
|
||||
"data": {"percentage": 100},
|
||||
},
|
||||
{
|
||||
"action": "script.snarfnet_broadcast",
|
||||
"data": {
|
||||
"title": "🌡️ High Server Room Temperature Warning",
|
||||
"message": "Server Room temperature has reached {{ states('sensor.server_room_temperature_temperature') }}°F (Humidity: {{ states('sensor.server_room_temperature_humidity') }}%). Server Closet exhaust fan engaged at 100%.",
|
||||
"notification_id": "snarfnet_thermal_warning",
|
||||
},
|
||||
},
|
||||
],
|
||||
"mode": "single",
|
||||
}
|
||||
ha_req("/api/config/automation/config/1793453000002", method="POST", data=thermal_automation)
|
||||
print("Configured automation 'Snarfnet Server Room Thermal Guard' (1793453000002).")
|
||||
|
||||
# 5. Enhance the 4 existing 3D Printer automations to call script.snarfnet_broadcast
|
||||
printer_autos = [
|
||||
("1788367985433", "🖨️ 3D Print Started", "A 3D print has started on Bambu H2D.", "h2d_print_start"),
|
||||
("1788368709567", "✅ 3D Print Completed", "A 3D print has finished on Bambu H2D.", "h2d_print_complete"),
|
||||
("1788370272217", "⚠️ 3D Print Stalled", "A 3D print has stalled/paused on Bambu H2D!", "h2d_print_stalled"),
|
||||
("1788370321997", "🚨 3D Print Failed", "A 3D print has failed on Bambu H2D!", "h2d_print_failed"),
|
||||
]
|
||||
for aid, title, msg, nid in printer_autos:
|
||||
cfg = ha_req(f"/api/config/automation/config/{aid}")
|
||||
# Filter out old persistent_notification/snarfnet_broadcast actions and append clean script.snarfnet_broadcast
|
||||
actions = [a for a in cfg.get("actions", []) if a.get("action") not in ("persistent_notification.create", "script.snarfnet_broadcast")]
|
||||
actions.append(
|
||||
{
|
||||
"action": "script.snarfnet_broadcast",
|
||||
"metadata": {},
|
||||
"data": {
|
||||
"title": title,
|
||||
"message": msg,
|
||||
"notification_id": nid,
|
||||
},
|
||||
}
|
||||
)
|
||||
cfg["actions"] = actions
|
||||
ha_req(f"/api/config/automation/config/{aid}", method="POST", data=cfg)
|
||||
print(f"Enhanced 3D Printer automation [{aid}] '{cfg.get('alias')}' with script.snarfnet_broadcast.")
|
||||
|
||||
ha_req("/api/services/script/reload", method="POST", data={})
|
||||
ha_req("/api/services/automation/reload", method="POST", data={})
|
||||
|
||||
# Wait 5s for restarted bridge container to finish [INIT-HA]
|
||||
time.sleep(5)
|
||||
|
||||
# 6. Trigger live test via script.snarfnet_broadcast
|
||||
ha_req(
|
||||
"/api/services/script/snarfnet_broadcast",
|
||||
method="POST",
|
||||
data={
|
||||
"title": "Home Assistant <-> Gotify <-> Discord Linked",
|
||||
"message": "Live Telemetry: Server Room {{ states('sensor.server_room_temperature_temperature') }}°F ({{ states('sensor.server_room_temperature_humidity') }}% RH) | UPS: {{ states('sensor.server_closet_status') }} ({{ states('sensor.server_closet_battery_charge') }}% Battery, {{ states('sensor.server_closet_load') }}% Load, {{ states('sensor.server_closet_input_voltage') }}V).",
|
||||
"notification_id": "snarfnet_ha_bridge_verified",
|
||||
},
|
||||
)
|
||||
print("Triggered live script.snarfnet_broadcast test in Home Assistant.")
|
||||
|
||||
time.sleep(5)
|
||||
stdin, stdout, stderr = ssh.exec_command("echo 'Katzir476!' | sudo -S docker logs $(docker ps -q -f name=gotify_gotify-discord-bridge) --tail 25 2>&1")
|
||||
print("Bridge logs:\n", stdout.read().decode("utf-8", errors="ignore"))
|
||||
ssh.close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user