diff --git a/docs/opencanary_honeypot.md b/docs/opencanary_honeypot.md
new file mode 100644
index 0000000..0b68516
--- /dev/null
+++ b/docs/opencanary_honeypot.md
@@ -0,0 +1,58 @@
+# OpenCanary Decoy Honeypot Architecture & Configuration
+
+## 1. Overview
+As part of the **Snarfnet Network Security Hardening Blueprint (Phase 3)**, an **OpenCanary Honeypot** has been deployed to detect lateral movement, unauthorized reconnaissance, and compromised devices inside the **Smart Home VLAN (VLAN 50, `14.4.4.0/25`)**.
+
+* **Decoy IP:** `14.4.4.99` (Subnet: `14.4.4.0/25`, Gateway: `14.4.4.1`)
+* **Host Machine:** `snshpd4lnxswm01` (`140.44.4.71`)
+* **Physical Uplink:** Direct 802.1Q trunk port with tagged VLAN 50 support.
+* **Network Mode:** Native Docker `macvlan` attached to subinterface `eth0.50`.
+
+---
+
+## 2. Emulated Services & Ports
+
+The honeypot presents realistic service banners designed to trap automated scanners and unauthorized intruders without exposing real systems:
+
+| Port | Protocol | Emulated Service | Decoy Profile |
+|:---:|:---:|:---|:---|
+| **21** | TCP | **FTP** | Linux FTP server (`FTP server ready`) |
+| **22** | TCP | **SSH** | Ubuntu OpenSSH banner (`SSH-2.0-OpenSSH_8.9p1 Ubuntu-3ubuntu0.10`) |
+| **23** | TCP | **Telnet** | Ubuntu login prompt (`Ubuntu 22.04 LTS login: `) |
+| **80** | TCP | **HTTP Web** | Synology/NAS Web Admin Login skin |
+| **1433** | TCP | **MSSQL** | Microsoft SQL Server 2012 |
+| **3306** | TCP | **MySQL** | MySQL 5.7 database server |
+| **3389** | TCP | **RDP** | Windows Remote Desktop Protocol listener |
+| **5000** | TCP | **VNC** | Remote framebuffer / VNC listener |
+| **8080** | TCP | **HTTP Proxy** | Squid HTTP proxy decoy |
+| **9418** | TCP | **Git** | Git daemon repository service |
+
+---
+
+## 3. Real-Time Alerting Pipeline
+
+```mermaid
+flowchart LR
+ Attacker["Suspicious Host / IoT Device
(e.g. 14.4.4.15)"] -->|TCP / Port Probe| Decoy["OpenCanary (14.4.4.99:22/80/445/3389)"]
+ Decoy -->|Structured JSON Log| LogFile["/opt/opencanary/logs/opencanary.log"]
+ LogFile -->|Real-Time Tail| Watcher["opencanary-watcher.service
(Python Daemon)"]
+ Watcher -->|HTTP POST (Priority 8)| Gotify["Gotify Server (App 6)
(gotify.snarfnet.net)"]
+ Gotify -->|Instant Push Notification| Phone["Admin Mobile App"]
+ Gotify -->|Discord Webhook Bridge| Discord["Discord #infra-alerts"]
+```
+
+1. Any TCP connection, banner grab, authentication attempt, or probe arriving on `14.4.4.99` generates a structured event in `/opt/opencanary/logs/opencanary.log`.
+2. The `opencanary-watcher.service` daemon ingests the event in real-time, extracts the attacker's source IP, source port, targeted port, service type, and credentials (if entered).
+3. The event is posted to **Gotify Application #6** (`A9oPeNcAnArYhOnEyPoT`) with high priority (`8`).
+4. `gotify-discord-bridge` formats a rich embed and posts the alert directly into the Discord `#infra-alerts` channel.
+
+---
+
+## 4. Systemd Services & Maintenance
+
+* **VLAN Subinterface Service:**
+ `/etc/systemd/system/opencanary-vlan.service` (Ensures `eth0.50` is brought up on boot).
+* **Alert Watcher Service:**
+ `/etc/systemd/system/opencanary-watcher.service` (Tails log file and delivers push alerts).
+* **Docker Container:**
+ `opencanary` (Managed via Docker with `restart: unless-stopped` on network `opencanary_net`).
diff --git a/scripts/check_all_npm_hosts.py b/scripts/check_all_npm_hosts.py
index a5406ea..376f915 100644
--- a/scripts/check_all_npm_hosts.py
+++ b/scripts/check_all_npm_hosts.py
@@ -22,3 +22,4 @@ stdin, stdout, stderr = c.exec_command(f'python3 -c "{py_query}"')
print("All NPM hosts:\n", stdout.read().decode())
c.close()
+
diff --git a/scripts/check_authentik_events.py b/scripts/check_authentik_events.py
index d39d30a..3c7984b 100644
--- a/scripts/check_authentik_events.py
+++ b/scripts/check_authentik_events.py
@@ -33,3 +33,4 @@ try:
print(f" Context: {json.dumps(context, indent=2)}")
except Exception as e:
print(f"Error querying Authentik: {e}")
+
diff --git a/scripts/check_bridge_logs.py b/scripts/check_bridge_logs.py
new file mode 100644
index 0000000..5c00ee7
--- /dev/null
+++ b/scripts/check_bridge_logs.py
@@ -0,0 +1,14 @@
+import paramiko
+import sys
+
+sys.stdout.reconfigure(encoding='utf-8')
+key_path = r'C:\Users\Snarf\.ssh\id_ed25519_snarfnet'
+
+c = paramiko.SSHClient()
+c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
+c.connect("140.44.4.71", username="root", key_filename=key_path, timeout=10)
+
+stdin, stdout, stderr = c.exec_command("docker service logs --tail 10 gotify_gotify-discord-bridge")
+print(stdout.read().decode())
+
+c.close()
diff --git a/scripts/check_bridge_map.py b/scripts/check_bridge_map.py
new file mode 100644
index 0000000..19a6ac8
--- /dev/null
+++ b/scripts/check_bridge_map.py
@@ -0,0 +1,15 @@
+import paramiko
+import sys
+
+sys.stdout.reconfigure(encoding='utf-8')
+key_path = r'C:\Users\Snarf\.ssh\id_ed25519_snarfnet'
+
+c = paramiko.SSHClient()
+c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
+c.connect("140.44.4.71", username="root", key_filename=key_path, timeout=10)
+
+stdin, stdout, stderr = c.exec_command("grep -n -A 10 'APP_WEBHOOK_MAP' /var/lib/docker/volumes/gotify_gotify_data/_data/discord_bridge.py")
+print(stdout.read().decode())
+
+c.close()
+
diff --git a/scripts/check_docker_tail.py b/scripts/check_docker_tail.py
new file mode 100644
index 0000000..927a9f2
--- /dev/null
+++ b/scripts/check_docker_tail.py
@@ -0,0 +1,15 @@
+import paramiko
+import sys
+
+sys.stdout.reconfigure(encoding='utf-8')
+key_path = r'C:\Users\Snarf\.ssh\id_ed25519_snarfnet'
+
+c = paramiko.SSHClient()
+c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
+c.connect("140.44.4.71", username="root", key_filename=key_path, timeout=10)
+
+stdin, stdout, stderr = c.exec_command("docker logs --tail 20 opencanary")
+print("Docker logs:\n", stdout.read().decode())
+
+c.close()
+
diff --git a/scripts/check_gotify_apps.py b/scripts/check_gotify_apps.py
new file mode 100644
index 0000000..54b0cea
--- /dev/null
+++ b/scripts/check_gotify_apps.py
@@ -0,0 +1,21 @@
+import paramiko
+import sys
+
+sys.stdout.reconfigure(encoding='utf-8')
+key_path = r'C:\Users\Snarf\.ssh\id_ed25519_snarfnet'
+
+c = paramiko.SSHClient()
+c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
+c.connect("140.44.4.71", username="root", key_filename=key_path, timeout=10)
+
+def run(cmd):
+ stdin, stdout, stderr = c.exec_command(cmd)
+ return stdout.read().decode('utf-8', errors='ignore').strip()
+
+print("Gotify database check:")
+print(run("find /var/lib/docker/volumes/ -name 'gotify.db'"))
+print("\nGotify apps in SQLite:")
+print(run("sqlite3 $(find /var/lib/docker/volumes/ -name 'gotify.db') 'SELECT id, name, token FROM application;'"))
+
+c.close()
+
diff --git a/scripts/check_gotify_apps_py.py b/scripts/check_gotify_apps_py.py
new file mode 100644
index 0000000..8a0c952
--- /dev/null
+++ b/scripts/check_gotify_apps_py.py
@@ -0,0 +1,28 @@
+import paramiko
+import sys
+
+sys.stdout.reconfigure(encoding='utf-8')
+key_path = r'C:\Users\Snarf\.ssh\id_ed25519_snarfnet'
+
+c = paramiko.SSHClient()
+c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
+c.connect("140.44.4.71", username="root", key_filename=key_path, timeout=10)
+
+py_script = """
+import sqlite3
+conn = sqlite3.connect('/var/lib/docker/volumes/gotify_gotify_data/_data/gotify.db')
+cur = conn.cursor()
+cur.execute("SELECT name FROM sqlite_master WHERE type='table'")
+print('Tables:', cur.fetchall())
+for tbl in cur.fetchall():
+ pass
+cur.execute("SELECT * FROM application")
+print('Applications:', cur.fetchall())
+"""
+stdin, stdout, stderr = c.exec_command(f'python3 -c "{py_script}"')
+print(stdout.read().decode())
+err = stderr.read().decode()
+if err: print("Stderr:", err)
+
+c.close()
+
diff --git a/scripts/check_gotify_data.py b/scripts/check_gotify_data.py
new file mode 100644
index 0000000..9907bef
--- /dev/null
+++ b/scripts/check_gotify_data.py
@@ -0,0 +1,18 @@
+import paramiko
+import sys
+
+sys.stdout.reconfigure(encoding='utf-8')
+key_path = r'C:\Users\Snarf\.ssh\id_ed25519_snarfnet'
+
+c = paramiko.SSHClient()
+c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
+c.connect("140.44.4.71", username="root", key_filename=key_path, timeout=10)
+
+def run(cmd):
+ stdin, stdout, stderr = c.exec_command(cmd)
+ return stdout.read().decode('utf-8', errors='ignore').strip()
+
+print(run("ls -la /var/lib/docker/volumes/gotify_gotify_data/_data/; file /var/lib/docker/volumes/gotify_gotify_data/_data/*"))
+
+c.close()
+
diff --git a/scripts/check_gotify_msgs.py b/scripts/check_gotify_msgs.py
new file mode 100644
index 0000000..9ca4300
--- /dev/null
+++ b/scripts/check_gotify_msgs.py
@@ -0,0 +1,14 @@
+import paramiko
+import sys
+
+sys.stdout.reconfigure(encoding='utf-8')
+key_path = r'C:\Users\Snarf\.ssh\id_ed25519_snarfnet'
+
+c = paramiko.SSHClient()
+c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
+c.connect("140.44.4.71", username="root", key_filename=key_path, timeout=10)
+
+stdin, stdout, stderr = c.exec_command('sqlite3 /var/lib/docker/volumes/gotify_gotify_data/_data/gotify.db "SELECT id, appid, title, message FROM messages ORDER BY id DESC LIMIT 5;"')
+print("Recent messages in Gotify:\n", stdout.read().decode())
+
+c.close()
diff --git a/scripts/check_gotify_tables.py b/scripts/check_gotify_tables.py
new file mode 100644
index 0000000..15538b4
--- /dev/null
+++ b/scripts/check_gotify_tables.py
@@ -0,0 +1,21 @@
+import paramiko
+import sys
+
+sys.stdout.reconfigure(encoding='utf-8')
+key_path = r'C:\Users\Snarf\.ssh\id_ed25519_snarfnet'
+
+c = paramiko.SSHClient()
+c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
+c.connect("140.44.4.71", username="root", key_filename=key_path, timeout=10)
+
+def run(cmd):
+ stdin, stdout, stderr = c.exec_command(cmd)
+ return stdout.read().decode('utf-8', errors='ignore').strip()
+
+print("Gotify tables:")
+print(run("sqlite3 /var/lib/docker/volumes/gotify_gotify_data/_data/gotify.db '.tables'"))
+print("\nApplications:")
+print(run("sqlite3 /var/lib/docker/volumes/gotify_gotify_data/_data/gotify.db 'SELECT * FROM applications;'"))
+
+c.close()
+
diff --git a/scripts/check_ha_dns.py b/scripts/check_ha_dns.py
new file mode 100644
index 0000000..4cddd37
--- /dev/null
+++ b/scripts/check_ha_dns.py
@@ -0,0 +1,25 @@
+import paramiko
+import sys
+
+sys.stdout.reconfigure(encoding='utf-8')
+key_path = r'C:\Users\Snarf\.ssh\id_ed25519_snarfnet'
+
+c = paramiko.SSHClient()
+c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
+c.connect("140.44.4.71", username="root", key_filename=key_path, timeout=10)
+
+def run(cmd):
+ stdin, stdout, stderr = c.exec_command(cmd)
+ return stdout.read().decode('utf-8', errors='ignore')
+
+print("--- dig snarfhome ---")
+print(run("dig @140.44.4.36 +short snarfhome.snarfnet.net"))
+
+print("--- dig homeassistant ---")
+print(run("dig @140.44.4.36 +short homeassistant.snarfnet.net"))
+
+print("--- dig ha ---")
+print(run("dig @140.44.4.36 +short ha.snarfnet.net"))
+
+c.close()
+
diff --git a/scripts/check_ha_events.py b/scripts/check_ha_events.py
index 8030d38..eac0b81 100644
--- a/scripts/check_ha_events.py
+++ b/scripts/check_ha_events.py
@@ -1,21 +1,43 @@
-import json
-import ssl
import urllib.request
+import ssl
+import json
+import sys
+
+sys.stdout.reconfigure(encoding='utf-8')
ctx = ssl.create_default_context()
ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE
-req = urllib.request.Request(
- 'https://authentik.snarfnet.net/api/v3/events/events/?page_size=25',
- headers={'Authorization': 'Bearer SnarfNetPermanentAutomationToken2026'}
-)
-res = json.loads(urllib.request.urlopen(req, context=ctx).read())
-print(f"Total events found: {len(res['results'])}")
-for e in res['results']:
- action = e.get('action')
- app = e.get('app')
- user = e.get('user', {}).get('username')
- created = e.get('created')
- context = json.dumps(e.get('context', {}))
- print(f"{created} | {action:25} | app: {app:20} | user: {user} | {context[:120]}")
+AK_BASE = "https://authentik.snarfnet.net"
+AK_TOKEN = "SnarfNetPermanentAutomationToken2026"
+
+headers = {
+ "Authorization": f"Bearer {AK_TOKEN}",
+ "Content-Type": "application/json",
+ "Accept": "application/json"
+}
+
+# Fetch events matching homeassistant or redirect_uri
+req = urllib.request.Request(f"{AK_BASE}/api/v3/events/events/?search=homeassistant", headers=headers)
+with urllib.request.urlopen(req, timeout=10, context=ctx) as r:
+ data = json.loads(r.read().decode())
+ print(f"Total matching 'homeassistant': {data.get('pagination', {}).get('count')}")
+ for ev in data.get("results", []):
+ created = ev.get("created")
+ action = ev.get("action")
+ ctx_data = ev.get("context", {})
+ print(f"[{created}] Action: {action}")
+ print(json.dumps(ctx_data, indent=2))
+
+print("\n--- Also search for 'redirect_uri' ---")
+req2 = urllib.request.Request(f"{AK_BASE}/api/v3/events/events/?search=redirect_uri", headers=headers)
+with urllib.request.urlopen(req2, timeout=10, context=ctx) as r:
+ data2 = json.loads(r.read().decode())
+ print(f"Total matching 'redirect_uri': {data2.get('pagination', {}).get('count')}")
+ for ev in data2.get("results", [])[:5]:
+ created = ev.get("created")
+ action = ev.get("action")
+ ctx_data = ev.get("context", {})
+ print(f"[{created}] Action: {action}")
+ print(json.dumps(ctx_data, indent=2))
diff --git a/scripts/check_ha_homepage.py b/scripts/check_ha_homepage.py
new file mode 100644
index 0000000..b790046
--- /dev/null
+++ b/scripts/check_ha_homepage.py
@@ -0,0 +1,18 @@
+import paramiko
+import sys
+
+sys.stdout.reconfigure(encoding='utf-8')
+key_path = r'C:\Users\Snarf\.ssh\id_ed25519_snarfnet'
+
+c = paramiko.SSHClient()
+c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
+c.connect("140.44.4.41", username="root", key_filename=key_path, timeout=10)
+
+stdin, stdout, stderr = c.exec_command("k3s kubectl get cm -A | grep -i home")
+print("CMs:\n", stdout.read().decode())
+
+stdin, stdout, stderr = c.exec_command("k3s kubectl get cm -n infrastructure -o yaml | grep -A 20 -B 5 -i homeassistant")
+print("Live HA config in Homepage:\n", stdout.read().decode())
+
+c.close()
+
diff --git a/scripts/check_ha_npm.py b/scripts/check_ha_npm.py
index 8406018..19236f1 100644
--- a/scripts/check_ha_npm.py
+++ b/scripts/check_ha_npm.py
@@ -23,3 +23,4 @@ stdin, stdout, stderr = c.exec_command(f'python3 -c "{py_query}"')
print("Home Assistant in NPM:\n", stdout.read().decode())
c.close()
+
diff --git a/scripts/check_ha_ssh.py b/scripts/check_ha_ssh.py
new file mode 100644
index 0000000..0a66020
--- /dev/null
+++ b/scripts/check_ha_ssh.py
@@ -0,0 +1,19 @@
+import paramiko
+import sys
+
+sys.stdout.reconfigure(encoding='utf-8')
+key_path = r'C:\Users\Snarf\.ssh\id_ed25519_snarfnet'
+
+c = paramiko.SSHClient()
+c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
+try:
+ c.connect("14.4.4.20", username="root", key_filename=key_path, timeout=5)
+ print("SSH root ok")
+except Exception as e:
+ print("SSH root failed:", e)
+ try:
+ c.connect("14.4.4.20", username="snarf", key_filename=key_path, timeout=5)
+ print("SSH snarf ok")
+ except Exception as e2:
+ print("SSH snarf failed:", e2)
+
diff --git a/scripts/check_heimdall_db.py b/scripts/check_heimdall_db.py
new file mode 100644
index 0000000..331f551
--- /dev/null
+++ b/scripts/check_heimdall_db.py
@@ -0,0 +1,19 @@
+import paramiko
+import sys
+
+sys.stdout.reconfigure(encoding='utf-8')
+key_path = r'C:\Users\Snarf\.ssh\id_ed25519_snarfnet'
+
+c = paramiko.SSHClient()
+c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
+c.connect("140.44.4.71", username="root", key_filename=key_path, timeout=10)
+
+def run(cmd):
+ stdin, stdout, stderr = c.exec_command(cmd)
+ return stdout.read().decode('utf-8', errors='ignore')
+
+print("Heimdall volumes/data:")
+print(run("find /var/lib/docker/volumes/ -name '*heimdall*' -o -name '*app.sqlite*'"))
+
+c.close()
+
diff --git a/scripts/check_heimdall_ha_item.py b/scripts/check_heimdall_ha_item.py
new file mode 100644
index 0000000..26eb48e
--- /dev/null
+++ b/scripts/check_heimdall_ha_item.py
@@ -0,0 +1,15 @@
+import paramiko
+import sys
+
+sys.stdout.reconfigure(encoding='utf-8')
+key_path = r'C:\Users\Snarf\.ssh\id_ed25519_snarfnet'
+
+c = paramiko.SSHClient()
+c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
+c.connect("140.44.4.75", username="root", key_filename=key_path, timeout=10)
+
+stdin, stdout, stderr = c.exec_command('docker exec $(docker ps -q -f name=heimdall) sqlite3 /config/www/app.sqlite "SELECT id, title, url, config FROM items WHERE title LIKE \'%home%\' OR title LIKE \'%assistant%\';"')
+print("Heimdall HA item:\n", stdout.read().decode())
+
+c.close()
+
diff --git a/scripts/check_heimdall_node.py b/scripts/check_heimdall_node.py
new file mode 100644
index 0000000..4e073b6
--- /dev/null
+++ b/scripts/check_heimdall_node.py
@@ -0,0 +1,15 @@
+import paramiko
+import sys
+
+sys.stdout.reconfigure(encoding='utf-8')
+key_path = r'C:\Users\Snarf\.ssh\id_ed25519_snarfnet'
+
+c = paramiko.SSHClient()
+c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
+c.connect("140.44.4.71", username="root", key_filename=key_path, timeout=10)
+
+stdin, stdout, stderr = c.exec_command("docker service ps heimdall_heimdall --no-trunc")
+print(stdout.read().decode())
+
+c.close()
+
diff --git a/scripts/check_npm_20.py b/scripts/check_npm_20.py
new file mode 100644
index 0000000..634e27e
--- /dev/null
+++ b/scripts/check_npm_20.py
@@ -0,0 +1,15 @@
+import paramiko
+import sys
+
+sys.stdout.reconfigure(encoding='utf-8')
+key_path = r'C:\Users\Snarf\.ssh\id_ed25519_snarfnet'
+
+c = paramiko.SSHClient()
+c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
+c.connect("140.44.4.71", username="root", key_filename=key_path, timeout=10)
+
+stdin, stdout, stderr = c.exec_command("cat /var/lib/docker/volumes/nginx-proxy-manager_nginx-data/_data/nginx/proxy_host/20.conf 2>/dev/null || echo '20.conf does not exist'")
+print(stdout.read().decode())
+
+c.close()
+
diff --git a/scripts/check_oc_live.py b/scripts/check_oc_live.py
new file mode 100644
index 0000000..078d64f
--- /dev/null
+++ b/scripts/check_oc_live.py
@@ -0,0 +1,19 @@
+import paramiko
+import sys
+
+sys.stdout.reconfigure(encoding='utf-8')
+key_path = r'C:\Users\Snarf\.ssh\id_ed25519_snarfnet'
+
+c = paramiko.SSHClient()
+c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
+c.connect("140.44.4.71", username="root", key_filename=key_path, timeout=10)
+
+def run(cmd):
+ stdin, stdout, stderr = c.exec_command(cmd)
+ return stdout.read().decode('utf-8', errors='ignore').strip()
+
+print("Container status:\n", run("docker ps -f name=opencanary"))
+print("\nRecent logs:\n", run("docker logs --tail 30 opencanary"))
+
+c.close()
+
diff --git a/scripts/check_opencanary_image.py b/scripts/check_opencanary_image.py
new file mode 100644
index 0000000..5f9fa22
--- /dev/null
+++ b/scripts/check_opencanary_image.py
@@ -0,0 +1,19 @@
+import paramiko
+import sys
+
+sys.stdout.reconfigure(encoding='utf-8')
+key_path = r'C:\Users\Snarf\.ssh\id_ed25519_snarfnet'
+
+c = paramiko.SSHClient()
+c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
+c.connect("140.44.4.71", username="root", key_filename=key_path, timeout=10)
+
+def run(cmd):
+ stdin, stdout, stderr = c.exec_command(cmd)
+ return stdout.read().decode('utf-8', errors='ignore').strip()
+
+print("Docker image search/pull check:")
+print(run("docker pull thinkst/opencanary:latest"))
+
+c.close()
+
diff --git a/scripts/check_opencanary_status.py b/scripts/check_opencanary_status.py
new file mode 100644
index 0000000..b7a17fa
--- /dev/null
+++ b/scripts/check_opencanary_status.py
@@ -0,0 +1,22 @@
+import paramiko
+import sys
+
+sys.stdout.reconfigure(encoding='utf-8')
+key_path = r'C:\Users\Snarf\.ssh\id_ed25519_snarfnet'
+
+c = paramiko.SSHClient()
+c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
+c.connect("140.44.4.71", username="root", key_filename=key_path, timeout=10)
+
+def run(cmd):
+ stdin, stdout, stderr = c.exec_command(cmd)
+ return stdout.read().decode('utf-8', errors='ignore').strip()
+
+print("OpenCanary container logs:")
+print(run("docker logs --tail 30 opencanary"))
+
+print("\nContainer status:")
+print(run("docker ps -f name=opencanary"))
+
+c.close()
+
diff --git a/scripts/check_recent_ak_events.py b/scripts/check_recent_ak_events.py
new file mode 100644
index 0000000..e161a36
--- /dev/null
+++ b/scripts/check_recent_ak_events.py
@@ -0,0 +1,38 @@
+import urllib.request
+import ssl
+import json
+import sys
+
+sys.stdout.reconfigure(encoding='utf-8')
+
+ctx = ssl.create_default_context()
+ctx.check_hostname = False
+ctx.verify_mode = ssl.CERT_NONE
+
+AK_BASE = "https://authentik.snarfnet.net"
+AK_TOKEN = "SnarfNetPermanentAutomationToken2026"
+
+headers = {
+ "Authorization": f"Bearer {AK_TOKEN}",
+ "Content-Type": "application/json",
+ "Accept": "application/json"
+}
+
+req = urllib.request.Request(f"{AK_BASE}/api/v3/events/events/?page_size=40", headers=headers)
+with urllib.request.urlopen(req, timeout=10, context=ctx) as r:
+ data = json.loads(r.read().decode())
+ for ev in data.get("results", []):
+ created = ev.get("created")
+ action = ev.get("action")
+ user = ev.get("user", {}).get("username")
+ ctx_data = ev.get("context", {})
+ if action != "secret_rotate":
+ print(f"[{created}] Action: {action} | User: {user}")
+ # print request details
+ http_req = ctx_data.get("http_request", {})
+ if http_req:
+ print(f" Path: {http_req.get('path')}")
+ print(f" Args: {http_req.get('args')}")
+ if "message" in ctx_data:
+ print(f" Message: {ctx_data.get('message')}")
+
diff --git a/scripts/check_snarfhome_response.py b/scripts/check_snarfhome_response.py
new file mode 100644
index 0000000..78bd422
--- /dev/null
+++ b/scripts/check_snarfhome_response.py
@@ -0,0 +1,24 @@
+import urllib.request
+import ssl
+import sys
+
+sys.stdout.reconfigure(encoding='utf-8')
+
+ctx = ssl.create_default_context()
+ctx.check_hostname = False
+ctx.verify_mode = ssl.CERT_NONE
+
+req = urllib.request.Request("https://snarfhome.snarfnet.net/")
+try:
+ with urllib.request.urlopen(req, timeout=5, context=ctx) as r:
+ print("Status:", r.getcode())
+ print("Headers:", dict(r.headers))
+ content = r.read().decode('utf-8', errors='ignore')
+ print("Content preview:\n", content[:500])
+except urllib.error.HTTPError as e:
+ print("HTTPError:", e.code)
+ print("Headers:", dict(e.headers))
+ print("Body:\n", e.read().decode('utf-8', errors='ignore')[:500])
+except Exception as e:
+ print("Exception:", e)
+
diff --git a/scripts/check_watcher_logs.py b/scripts/check_watcher_logs.py
new file mode 100644
index 0000000..2f97252
--- /dev/null
+++ b/scripts/check_watcher_logs.py
@@ -0,0 +1,21 @@
+import paramiko
+import sys
+
+sys.stdout.reconfigure(encoding='utf-8')
+key_path = r'C:\Users\Snarf\.ssh\id_ed25519_snarfnet'
+
+c = paramiko.SSHClient()
+c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
+c.connect("140.44.4.71", username="root", key_filename=key_path, timeout=10)
+
+def run(cmd):
+ stdin, stdout, stderr = c.exec_command(cmd)
+ return stdout.read().decode('utf-8', errors='ignore').strip()
+
+print("Watcher status & logs:")
+print(run("systemctl status opencanary-watcher.service --no-pager"))
+print("\nJournalctl:")
+print(run("journalctl -u opencanary-watcher.service -n 30 --no-pager"))
+
+c.close()
+
diff --git a/scripts/deploy_opencanary.py b/scripts/deploy_opencanary.py
new file mode 100644
index 0000000..f108f00
--- /dev/null
+++ b/scripts/deploy_opencanary.py
@@ -0,0 +1,251 @@
+import paramiko
+import sys
+import json
+
+sys.stdout.reconfigure(encoding='utf-8')
+key_path = r'C:\Users\Snarf\.ssh\id_ed25519_snarfnet'
+
+c = paramiko.SSHClient()
+c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
+c.connect("140.44.4.71", username="root", key_filename=key_path, timeout=10)
+
+def run(cmd):
+ stdin, stdout, stderr = c.exec_command(cmd)
+ out = stdout.read().decode('utf-8', errors='ignore').strip()
+ err = stderr.read().decode('utf-8', errors='ignore').strip()
+ if err:
+ return f"{out}\nSTDERR: {err}"
+ return out
+
+print("--- Step 1: Setting up opencanary-vlan.service ---")
+vlan_svc = """[Unit]
+Description=OpenCanary VLAN 50 Subinterface
+After=network.target
+
+[Service]
+Type=oneshot
+RemainAfterExit=yes
+ExecStart=/sbin/ip link add link eth0 name eth0.50 type vlan id 50
+ExecStart=/sbin/ip link set eth0.50 up
+ExecStop=/sbin/ip link del eth0.50
+
+[Install]
+WantedBy=multi-user.target
+"""
+c.exec_command("cat << 'EOF' > /etc/systemd/system/opencanary-vlan.service\n" + vlan_svc + "\nEOF\n")
+run("systemctl daemon-reload && systemctl enable --now opencanary-vlan.service")
+print("VLAN interface status:\n", run("ip -br link show eth0.50"))
+
+print("\n--- Step 2: Creating Docker macvlan network ---")
+run("docker network rm opencanary_net 2>/dev/null")
+net_create = run("docker network create -d macvlan --subnet=14.4.4.0/25 --gateway=14.4.4.1 -o parent=eth0.50 opencanary_net")
+print("Network create result:", net_create)
+
+print("\n--- Step 3: Creating /opt/opencanary directories & config ---")
+run("mkdir -p /opt/opencanary/logs")
+
+oc_config = {
+ "device.node_id": "snarfnet-honeypot-vlan50",
+ "ip.ignorelist": [],
+ "logtype.ignorelist": [],
+ "git.enabled": True,
+ "git.port": 9418,
+ "ftp.enabled": True,
+ "ftp.port": 21,
+ "ftp.banner": "FTP server ready",
+ "http.banner": "Apache/2.4.52 (Ubuntu)",
+ "http.enabled": True,
+ "http.port": 80,
+ "http.skin": "nasLogin",
+ "httpproxy.enabled": True,
+ "httpproxy.port": 8080,
+ "httpproxy.skin": "squid",
+ "logger": {
+ "class": "PyLogger",
+ "kwargs": {
+ "formatters": {
+ "plain": {
+ "format": "%(message)s"
+ }
+ },
+ "handlers": {
+ "console": {
+ "class": "logging.StreamHandler",
+ "stream": "ext://sys.stdout"
+ },
+ "file": {
+ "class": "logging.FileHandler",
+ "filename": "/var/tmp/opencanary.log"
+ }
+ }
+ }
+ },
+ "mysql.enabled": True,
+ "mysql.port": 3306,
+ "mysql.banner": "5.7.42-0ubuntu0.18.04.1",
+ "ssh.enabled": True,
+ "ssh.port": 22,
+ "ssh.version": "SSH-2.0-OpenSSH_8.9p1 Ubuntu-3ubuntu0.10",
+ "rdp.enabled": True,
+ "rdp.port": 3389,
+ "telnet.enabled": True,
+ "telnet.port": 23,
+ "telnet.banner": "\\r\\nUbuntu 22.04 LTS\\r\\nlogin: ",
+ "mssql.enabled": True,
+ "mssql.version": "2016",
+ "mssql.port": 1433,
+ "vnc.enabled": True,
+ "vnc.port": 5000
+}
+
+config_json = json.dumps(oc_config, indent=4)
+c.exec_command("cat << 'EOF' > /opt/opencanary/opencanary.conf\n" + config_json + "\nEOF\n")
+print("opencanary.conf written.")
+
+print("\n--- Step 4: Creating Gotify Alert Watcher daemon ---")
+watcher_script = """#!/usr/bin/env python3
+import json
+import os
+import time
+import urllib.request
+import urllib.error
+
+LOG_FILE = "/opt/opencanary/logs/opencanary.log"
+GOTIFY_URL = "http://127.0.0.1:8090/message?token=A9oPeNcAnArYhOnEyPoT"
+
+LOGTYPE_NAMES = {
+ 1000: "Portscan Syn Probe",
+ 1001: "Portscan Nmap OS Detection",
+ 1002: "Portscan Null Scan",
+ 1003: "Portscan Xmas Scan",
+ 1004: "Portscan Fin Scan",
+ 2000: "FTP Connection",
+ 2001: "FTP Auth Attempt",
+ 3000: "HTTP Connection",
+ 3001: "HTTP Web Login Attempt",
+ 4000: "SSH Connection",
+ 4001: "SSH Auth Attempt",
+ 5000: "SMB Session Request",
+ 6001: "Telnet Login Attempt",
+ 7001: "HTTP Proxy Request",
+ 8001: "MySQL Login Attempt",
+ 9001: "MSSQL Login Attempt",
+ 10001: "VNC Connection",
+ 11001: "Git Clone / Pull Attempt",
+ 12001: "RDP Connection"
+}
+
+def send_alert(event):
+ logtype = event.get("logtype", 0)
+ service_name = LOGTYPE_NAMES.get(logtype, f"Canary Event {logtype}")
+ src_host = event.get("src_host", "Unknown IP")
+ src_port = event.get("src_port", "Unknown Port")
+ dst_port = event.get("dst_port", "")
+ logdata = event.get("logdata", {})
+
+ title = f"🚨 [Honeypot Alert] {service_name} from {src_host}"
+
+ details = []
+ details.append(f"• Attacker IP: {src_host}:{src_port}")
+ if dst_port:
+ details.append(f"• Targeted Port: {dst_port}")
+ details.append(f"• Decoy IP: 14.4.4.99 (VLAN 50 Smart Home)")
+
+ if isinstance(logdata, dict):
+ if "USERNAME" in logdata:
+ details.append(f"• Username attempted: {logdata.get('USERNAME')}")
+ if "PASSWORD" in logdata:
+ details.append(f"• Password attempted: {logdata.get('PASSWORD')}")
+ if "PATH" in logdata:
+ details.append(f"• Path: {logdata.get('PATH')}")
+ if "USERAGENT" in logdata:
+ details.append(f"• User-Agent: {logdata.get('USERAGENT')}")
+ elif logdata:
+ details.append(f"• Payload: {str(logdata)[:200]}")
+
+ body = "\\n".join(details)
+
+ payload = {
+ "title": title,
+ "message": body,
+ "priority": 8
+ }
+
+ try:
+ req = urllib.request.Request(
+ GOTIFY_URL,
+ data=json.dumps(payload).encode('utf-8'),
+ headers={"Content-Type": "application/json"}
+ )
+ with urllib.request.urlopen(req, timeout=5) as r:
+ pass
+ print(f"[Alert Sent] {title}")
+ except Exception as e:
+ print(f"[Alert Error] {e}")
+
+def main():
+ print("OpenCanary Gotify Watcher started...")
+ # Ensure log file exists
+ if not os.path.exists(LOG_FILE):
+ open(LOG_FILE, 'a').close()
+
+ with open(LOG_FILE, 'r') as f:
+ # Seek to end on startup
+ f.seek(0, os.SEEK_END)
+ while True:
+ line = f.readline()
+ if not line:
+ time.sleep(0.5)
+ continue
+ line = line.strip()
+ if not line:
+ continue
+ try:
+ event = json.loads(line)
+ send_alert(event)
+ except Exception as e:
+ # Non-JSON log line
+ pass
+
+if __name__ == '__main__':
+ main()
+"""
+
+c.exec_command("cat << 'EOF' > /opt/opencanary/alert_watcher.py\n" + watcher_script + "\nEOF\n")
+run("chmod +x /opt/opencanary/alert_watcher.py")
+
+watcher_svc = """[Unit]
+Description=OpenCanary Gotify Alert Watcher
+After=docker.service
+
+[Service]
+Type=simple
+ExecStart=/usr/bin/python3 /opt/opencanary/alert_watcher.py
+Restart=always
+RestartSec=3
+
+[Install]
+WantedBy=multi-user.target
+"""
+c.exec_command("cat << 'EOF' > /etc/systemd/system/opencanary-watcher.service\n" + watcher_svc + "\nEOF\n")
+run("systemctl daemon-reload && systemctl enable --now opencanary-watcher.service")
+print("Watcher service status:\n", run("systemctl is-active opencanary-watcher.service"))
+
+print("\n--- Step 5: Launching OpenCanary Docker Container ---")
+run("docker rm -f opencanary 2>/dev/null")
+docker_run_cmd = """
+docker run -d \
+ --name opencanary \
+ --restart unless-stopped \
+ --net opencanary_net \
+ --ip 14.4.4.99 \
+ -v /opt/opencanary/opencanary.conf:/root/.opencanary.conf:ro \
+ -v /opt/opencanary/logs:/var/tmp \
+ thinkst/opencanary:latest \
+ opencanaryd --dev
+"""
+container_id = run(docker_run_cmd)
+print("Container ID:", container_id)
+
+c.close()
+
diff --git a/scripts/find_ha_php.py b/scripts/find_ha_php.py
new file mode 100644
index 0000000..e64a451
--- /dev/null
+++ b/scripts/find_ha_php.py
@@ -0,0 +1,15 @@
+import paramiko
+import sys
+
+sys.stdout.reconfigure(encoding='utf-8')
+key_path = r'C:\Users\Snarf\.ssh\id_ed25519_snarfnet'
+
+c = paramiko.SSHClient()
+c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
+c.connect("140.44.4.75", username="root", key_filename=key_path, timeout=10)
+
+stdin, stdout, stderr = c.exec_command("docker exec $(docker ps -q -f name=heimdall) find / -name 'HomeAssistant.php' 2>/dev/null")
+print(stdout.read().decode())
+
+c.close()
+
diff --git a/scripts/find_nabu_casa.py b/scripts/find_nabu_casa.py
new file mode 100644
index 0000000..6d69b55
--- /dev/null
+++ b/scripts/find_nabu_casa.py
@@ -0,0 +1,26 @@
+import paramiko
+import sys
+
+sys.stdout.reconfigure(encoding='utf-8')
+key_path = r'C:\Users\Snarf\.ssh\id_ed25519_snarfnet'
+
+c = paramiko.SSHClient()
+c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
+c.connect("140.44.4.71", username="root", key_filename=key_path, timeout=10)
+
+stdin, stdout, stderr = c.exec_command("grep -rn 'nabu.casa' /var/lib/docker/volumes/nginx-proxy-manager* 2>/dev/null")
+print("Grep nabu.casa in NPM data:\n", stdout.read().decode())
+
+py_query = """
+import sqlite3
+conn = sqlite3.connect('/var/lib/docker/volumes/nginx-proxy-manager_nginx-data/_data/database.sqlite')
+cur = conn.cursor()
+cur.execute('SELECT * FROM redirection_host')
+print('Redirection hosts:', cur.fetchall())
+conn.close()
+"""
+stdin, stdout, stderr = c.exec_command(f'python3 -c "{py_query}"')
+print("SQLite redirection hosts:\n", stdout.read().decode())
+
+c.close()
+
diff --git a/scripts/find_opencanary_settings.py b/scripts/find_opencanary_settings.py
new file mode 100644
index 0000000..69b1c52
--- /dev/null
+++ b/scripts/find_opencanary_settings.py
@@ -0,0 +1,18 @@
+import paramiko
+import sys
+
+sys.stdout.reconfigure(encoding='utf-8')
+key_path = r'C:\Users\Snarf\.ssh\id_ed25519_snarfnet'
+
+c = paramiko.SSHClient()
+c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
+c.connect("140.44.4.71", username="root", key_filename=key_path, timeout=10)
+
+def run(cmd):
+ stdin, stdout, stderr = c.exec_command(cmd)
+ return stdout.read().decode('utf-8', errors='ignore').strip()
+
+print(run("docker run --rm --entrypoint python3 thinkst/opencanary:latest -c 'import opencanary.config; print(opencanary.config.SETTINGS)' 2>/dev/null || docker run --rm --entrypoint find thinkst/opencanary:latest / -name 'settings.json' 2>/dev/null"))
+
+c.close()
+
diff --git a/scripts/fix_mssql_version.py b/scripts/fix_mssql_version.py
new file mode 100644
index 0000000..818fb76
--- /dev/null
+++ b/scripts/fix_mssql_version.py
@@ -0,0 +1,40 @@
+import paramiko
+import sys
+import json
+
+sys.stdout.reconfigure(encoding='utf-8')
+key_path = r'C:\Users\Snarf\.ssh\id_ed25519_snarfnet'
+
+c = paramiko.SSHClient()
+c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
+c.connect("140.44.4.71", username="root", key_filename=key_path, timeout=10)
+
+def run(cmd):
+ stdin, stdout, stderr = c.exec_command(cmd)
+ return stdout.read().decode('utf-8', errors='ignore').strip()
+
+# Fix MSSQL version in config
+with open("c:/Users/Snarf/OneDrive/Desktop/code/SnarfCode/scripts/deploy_opencanary.py", "r") as f:
+ text = f.read()
+
+# Read config from host, update mssql.version to "2012"
+py_fix_mssql = """
+import json
+path = '/opt/opencanary/opencanary.conf'
+with open(path, 'r') as f:
+ cfg = json.load(f)
+cfg['mssql.version'] = '2012'
+with open(path, 'w') as f:
+ json.dump(cfg, f, indent=4)
+print('Updated mssql.version to 2012')
+"""
+c.exec_command(f'python3 -c "{py_fix_mssql}"')
+run("docker restart opencanary")
+
+import time
+time.sleep(3)
+print("Logs after restart:")
+print(run("docker logs --tail 25 opencanary"))
+
+c.close()
+
diff --git a/scripts/fix_opencanary_cmd.py b/scripts/fix_opencanary_cmd.py
new file mode 100644
index 0000000..d8b2c26
--- /dev/null
+++ b/scripts/fix_opencanary_cmd.py
@@ -0,0 +1,36 @@
+import paramiko
+import sys
+
+sys.stdout.reconfigure(encoding='utf-8')
+key_path = r'C:\Users\Snarf\.ssh\id_ed25519_snarfnet'
+
+c = paramiko.SSHClient()
+c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
+c.connect("140.44.4.71", username="root", key_filename=key_path, timeout=10)
+
+def run(cmd):
+ stdin, stdout, stderr = c.exec_command(cmd)
+ return stdout.read().decode('utf-8', errors='ignore').strip()
+
+print("Recreating container with correct arguments...")
+run("docker rm -f opencanary 2>/dev/null")
+run("""docker run -d \
+ --name opencanary \
+ --restart unless-stopped \
+ --net opencanary_net \
+ --ip 14.4.4.99 \
+ -v /opt/opencanary/opencanary.conf:/root/.opencanary.conf:ro \
+ -v /opt/opencanary/logs:/var/tmp \
+ thinkst/opencanary:latest \
+ --dev --allow-run-as-root
+""")
+
+import time
+time.sleep(3)
+print("Logs after start:")
+print(run("docker logs --tail 30 opencanary"))
+print("\nStatus:")
+print(run("docker ps -f name=opencanary"))
+
+c.close()
+
diff --git a/scripts/get_live_homepage_services.py b/scripts/get_live_homepage_services.py
new file mode 100644
index 0000000..4fb9da0
--- /dev/null
+++ b/scripts/get_live_homepage_services.py
@@ -0,0 +1,15 @@
+import paramiko
+import sys
+
+sys.stdout.reconfigure(encoding='utf-8')
+key_path = r'C:\Users\Snarf\.ssh\id_ed25519_snarfnet'
+
+c = paramiko.SSHClient()
+c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
+c.connect("140.44.4.41", username="root", key_filename=key_path, timeout=10)
+
+stdin, stdout, stderr = c.exec_command("k3s kubectl exec -n infrastructure $(k3s kubectl get pods -n infrastructure -l app.kubernetes.io/name=homepage-2-1744068158 -o jsonpath='{.items[0].metadata.name}') -- cat /app/config/services.yaml")
+print(stdout.read().decode())
+
+c.close()
+
diff --git a/scripts/get_opencanary_default_conf.py b/scripts/get_opencanary_default_conf.py
new file mode 100644
index 0000000..c6d6c55
--- /dev/null
+++ b/scripts/get_opencanary_default_conf.py
@@ -0,0 +1,18 @@
+import paramiko
+import sys
+
+sys.stdout.reconfigure(encoding='utf-8')
+key_path = r'C:\Users\Snarf\.ssh\id_ed25519_snarfnet'
+
+c = paramiko.SSHClient()
+c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
+c.connect("140.44.4.71", username="root", key_filename=key_path, timeout=10)
+
+def run(cmd):
+ stdin, stdout, stderr = c.exec_command(cmd)
+ return stdout.read().decode('utf-8', errors='ignore').strip()
+
+print(run("docker run --rm thinkst/opencanary:latest sh -c 'opencanaryd --copyconfig && cat /etc/opencanaryd/opencanary.conf'"))
+
+c.close()
+
diff --git a/scripts/inspect_ha_authentik.py b/scripts/inspect_ha_authentik.py
index 0c93eb8..114bcb9 100644
--- a/scripts/inspect_ha_authentik.py
+++ b/scripts/inspect_ha_authentik.py
@@ -33,3 +33,4 @@ with urllib.request.urlopen(req2, timeout=10, context=ctx) as r:
if "home" in p.get("name", "").lower() or "snarfhome" in str(p).lower():
print("Proxy Provider:")
print(json.dumps(p, indent=2))
+
diff --git a/scripts/inspect_opencanary_conf.py b/scripts/inspect_opencanary_conf.py
new file mode 100644
index 0000000..e19e041
--- /dev/null
+++ b/scripts/inspect_opencanary_conf.py
@@ -0,0 +1,21 @@
+import paramiko
+import sys
+
+sys.stdout.reconfigure(encoding='utf-8')
+key_path = r'C:\Users\Snarf\.ssh\id_ed25519_snarfnet'
+
+c = paramiko.SSHClient()
+c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
+c.connect("140.44.4.71", username="root", key_filename=key_path, timeout=10)
+
+def run(cmd):
+ stdin, stdout, stderr = c.exec_command(cmd)
+ return stdout.read().decode('utf-8', errors='ignore').strip()
+
+print("Inspect opencanary command line help & sample config:")
+print(run("docker run --rm thinkst/opencanary:latest opencanaryd --help"))
+print("\nSample config template:")
+print(run("docker run --rm thinkst/opencanary:latest opencanaryd --copyconfig /tmp/oc.conf && docker run --rm thinkst/opencanary:latest cat /tmp/oc.conf 2>/dev/null || docker run --rm --entrypoint cat thinkst/opencanary:latest /root/.opencanary.conf 2>/dev/null || docker run --rm thinkst/opencanary:latest python3 -c 'import opencanary; import pkg_resources; print(pkg_resources.resource_string(\"opencanary\", \"data/settings.json\").decode())'"))
+
+c.close()
+
diff --git a/scripts/print_settings_json.py b/scripts/print_settings_json.py
new file mode 100644
index 0000000..1aff057
--- /dev/null
+++ b/scripts/print_settings_json.py
@@ -0,0 +1,18 @@
+import paramiko
+import sys
+
+sys.stdout.reconfigure(encoding='utf-8')
+key_path = r'C:\Users\Snarf\.ssh\id_ed25519_snarfnet'
+
+c = paramiko.SSHClient()
+c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
+c.connect("140.44.4.71", username="root", key_filename=key_path, timeout=10)
+
+def run(cmd):
+ stdin, stdout, stderr = c.exec_command(cmd)
+ return stdout.read().decode('utf-8', errors='ignore').strip()
+
+print(run("docker run --rm --entrypoint cat thinkst/opencanary:latest /opencanary/opencanary/data/settings.json"))
+
+c.close()
+
diff --git a/scripts/read_gotify_apps.py b/scripts/read_gotify_apps.py
new file mode 100644
index 0000000..b84b177
--- /dev/null
+++ b/scripts/read_gotify_apps.py
@@ -0,0 +1,26 @@
+import paramiko
+import sys
+
+sys.stdout.reconfigure(encoding='utf-8')
+key_path = r'C:\Users\Snarf\.ssh\id_ed25519_snarfnet'
+
+c = paramiko.SSHClient()
+c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
+c.connect("140.44.4.71", username="root", key_filename=key_path, timeout=10)
+
+py_script = """
+import sqlite3
+conn = sqlite3.connect('/var/lib/docker/volumes/gotify_gotify_data/_data/gotify.db')
+cur = conn.cursor()
+cur.execute("PRAGMA table_info(applications)")
+print('Columns:', cur.fetchall())
+cur.execute("SELECT id, token, name, description FROM applications")
+print('Applications:', cur.fetchall())
+conn.close()
+"""
+c.exec_command("cat << 'EOF' > /tmp/read_gotify_apps.py\n" + py_script + "\nEOF\n")
+stdin, stdout, stderr = c.exec_command("python3 /tmp/read_gotify_apps.py")
+print(stdout.read().decode())
+
+c.close()
+
diff --git a/scripts/read_gotify_direct.py b/scripts/read_gotify_direct.py
new file mode 100644
index 0000000..7a923f5
--- /dev/null
+++ b/scripts/read_gotify_direct.py
@@ -0,0 +1,28 @@
+import paramiko
+import sys
+
+sys.stdout.reconfigure(encoding='utf-8')
+key_path = r'C:\Users\Snarf\.ssh\id_ed25519_snarfnet'
+
+c = paramiko.SSHClient()
+c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
+c.connect("140.44.4.71", username="root", key_filename=key_path, timeout=10)
+
+py_script = """
+import sqlite3
+conn = sqlite3.connect('/var/lib/docker/volumes/gotify_gotify_data/_data/gotify.db')
+cur = conn.cursor()
+cur.execute("SELECT name FROM sqlite_master WHERE type='table'")
+print('Tables:', cur.fetchall())
+cur.execute("SELECT id, token, name, description FROM application")
+print('Applications:', cur.fetchall())
+conn.close()
+"""
+c.exec_command("cat << 'EOF' > /tmp/read_gotify.py\n" + py_script + "\nEOF\n")
+stdin, stdout, stderr = c.exec_command("python3 /tmp/read_gotify.py")
+print(stdout.read().decode())
+err = stderr.read().decode()
+if err: print("Stderr:", err)
+
+c.close()
+
diff --git a/scripts/read_gotify_msgs_table.py b/scripts/read_gotify_msgs_table.py
new file mode 100644
index 0000000..3c1bfe8
--- /dev/null
+++ b/scripts/read_gotify_msgs_table.py
@@ -0,0 +1,29 @@
+import paramiko
+import sys
+
+sys.stdout.reconfigure(encoding='utf-8')
+key_path = r'C:\Users\Snarf\.ssh\id_ed25519_snarfnet'
+
+c = paramiko.SSHClient()
+c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
+c.connect("140.44.4.71", username="root", key_filename=key_path, timeout=10)
+
+py_query = """
+import sqlite3
+conn = sqlite3.connect('/var/lib/docker/volumes/gotify_gotify_data/_data/gotify.db')
+cur = conn.cursor()
+cur.execute("PRAGMA table_info(messages)")
+print('Columns:', cur.fetchall())
+cur.execute("SELECT count(*) FROM messages")
+print('Count:', cur.fetchall())
+cur.execute("SELECT * FROM messages ORDER BY id DESC LIMIT 3")
+print('Last rows:', cur.fetchall())
+conn.close()
+"""
+c.exec_command("cat << 'EOF' > /tmp/read_m.py\n" + py_query + "\nEOF\n")
+stdin, stdout, stderr = c.exec_command("python3 /tmp/read_m.py")
+print(stdout.read().decode())
+err = stderr.read().decode()
+if err: print("Stderr:", err)
+
+c.close()
diff --git a/scripts/read_ha_php.py b/scripts/read_ha_php.py
new file mode 100644
index 0000000..ff4d4a1
--- /dev/null
+++ b/scripts/read_ha_php.py
@@ -0,0 +1,15 @@
+import paramiko
+import sys
+
+sys.stdout.reconfigure(encoding='utf-8')
+key_path = r'C:\Users\Snarf\.ssh\id_ed25519_snarfnet'
+
+c = paramiko.SSHClient()
+c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
+c.connect("140.44.4.75", username="root", key_filename=key_path, timeout=10)
+
+stdin, stdout, stderr = c.exec_command("docker exec $(docker ps -q -f name=heimdall) cat /config/www/SupportedApps/HomeAssistant/HomeAssistant.php")
+print(stdout.read().decode())
+
+c.close()
+
diff --git a/scripts/read_ha_supported_app.py b/scripts/read_ha_supported_app.py
new file mode 100644
index 0000000..7840d5d
--- /dev/null
+++ b/scripts/read_ha_supported_app.py
@@ -0,0 +1,15 @@
+import paramiko
+import sys
+
+sys.stdout.reconfigure(encoding='utf-8')
+key_path = r'C:\Users\Snarf\.ssh\id_ed25519_snarfnet'
+
+c = paramiko.SSHClient()
+c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
+c.connect("140.44.4.75", username="root", key_filename=key_path, timeout=10)
+
+stdin, stdout, stderr = c.exec_command("docker exec $(docker ps -q -f name=heimdall) cat /var/www/localhost/heimdall/app/SupportedApps/HomeAssistant/HomeAssistant.php")
+print(stdout.read().decode())
+
+c.close()
+
diff --git a/scripts/read_heimdall_44.py b/scripts/read_heimdall_44.py
new file mode 100644
index 0000000..d5f9ee8
--- /dev/null
+++ b/scripts/read_heimdall_44.py
@@ -0,0 +1,23 @@
+import paramiko
+import sys
+
+sys.stdout.reconfigure(encoding='utf-8')
+key_path = r'C:\Users\Snarf\.ssh\id_ed25519_snarfnet'
+
+c = paramiko.SSHClient()
+c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
+c.connect("140.44.4.75", username="root", key_filename=key_path, timeout=10)
+
+py_script = """
+import sqlite3
+conn = sqlite3.connect('/var/lib/docker/volumes/heimdall_heimdall_config/_data/www/app.sqlite')
+cur = conn.cursor()
+cur.execute('SELECT id, title, url, description FROM items WHERE id = 44')
+print(cur.fetchall())
+"""
+c.exec_command("cat << 'EOF' > /tmp/read_h_44.py\n" + py_script + "\nEOF\n")
+stdin, stdout, stderr = c.exec_command("python3 /tmp/read_h_44.py")
+print(stdout.read().decode())
+
+c.close()
+
diff --git a/scripts/read_heimdall_all.py b/scripts/read_heimdall_all.py
new file mode 100644
index 0000000..d7f33d1
--- /dev/null
+++ b/scripts/read_heimdall_all.py
@@ -0,0 +1,27 @@
+import paramiko
+import sys
+
+sys.stdout.reconfigure(encoding='utf-8')
+key_path = r'C:\Users\Snarf\.ssh\id_ed25519_snarfnet'
+
+c = paramiko.SSHClient()
+c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
+c.connect("140.44.4.75", username="root", key_filename=key_path, timeout=10)
+
+py_code = """
+import sqlite3
+conn = sqlite3.connect('/var/lib/docker/volumes/heimdall_heimdall_config/_data/www/app.sqlite')
+cur = conn.cursor()
+cur.execute("SELECT id, title, url, appdescription, appid FROM items")
+for r in cur.fetchall():
+ print(r)
+cur.execute("SELECT name FROM sqlite_master WHERE type='table'")
+print('Tables:', cur.fetchall())
+"""
+stdin, stdout, stderr = c.exec_command(f'python3 -c "{py_code}"')
+print(stdout.read().decode())
+err = stderr.read().decode()
+if err: print("Stderr:", err)
+
+c.close()
+
diff --git a/scripts/read_heimdall_items.py b/scripts/read_heimdall_items.py
new file mode 100644
index 0000000..625223e
--- /dev/null
+++ b/scripts/read_heimdall_items.py
@@ -0,0 +1,32 @@
+import paramiko
+import sys
+
+sys.stdout.reconfigure(encoding='utf-8')
+key_path = r'C:\Users\Snarf\.ssh\id_ed25519_snarfnet'
+
+c = paramiko.SSHClient()
+c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
+c.connect("140.44.4.75", username="root", key_filename=key_path, timeout=10)
+
+py_code = """
+import sqlite3, glob
+db_files = glob.glob('/var/lib/docker/volumes/*heimdall*/_data/www/app.sqlite')
+if not db_files:
+ print('No db found on host, searching...')
+ db_files = glob.glob('/var/lib/docker/volumes/**/app.sqlite', recursive=True)
+print('Found:', db_files)
+if db_files:
+ conn = sqlite3.connect(db_files[0])
+ cur = conn.cursor()
+ cur.execute('SELECT id, title, url, config FROM items')
+ for row in cur.fetchall():
+ if any(w in row[1].lower() for w in ['home', 'assist', 'snarf']):
+ print(row)
+"""
+stdin, stdout, stderr = c.exec_command(f'python3 -c "{py_code}"')
+print(stdout.read().decode())
+err = stderr.read().decode()
+if err: print("Stderr:", err)
+
+c.close()
+
diff --git a/scripts/read_heimdall_keys.py b/scripts/read_heimdall_keys.py
new file mode 100644
index 0000000..3d6769a
--- /dev/null
+++ b/scripts/read_heimdall_keys.py
@@ -0,0 +1,32 @@
+import paramiko
+import sys
+
+sys.stdout.reconfigure(encoding='utf-8')
+key_path = r'C:\Users\Snarf\.ssh\id_ed25519_snarfnet'
+
+c = paramiko.SSHClient()
+c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
+c.connect("140.44.4.75", username="root", key_filename=key_path, timeout=10)
+
+py_script = """
+import sqlite3
+conn = sqlite3.connect('/var/lib/docker/volumes/heimdall_heimdall_config/_data/www/app.sqlite')
+cur = conn.cursor()
+for tbl in ['items', 'settings', 'setting_user', 'applications']:
+ print('=== Table:', tbl, '===')
+ cur.execute(f'PRAGMA table_info({tbl})')
+ print([c[1] for c in cur.fetchall()])
+ cur.execute(f'SELECT * FROM {tbl} WHERE 1')
+ for r in cur.fetchall():
+ row_str = str(r)
+ if any(w in row_str.lower() for w in ['home', 'assist', 'snarf', 'token', 'key']):
+ print(r)
+"""
+c.exec_command("cat << 'EOF' > /tmp/read_h_settings.py\n" + py_script + "\nEOF\n")
+stdin, stdout, stderr = c.exec_command("python3 /tmp/read_h_settings.py")
+print(stdout.read().decode())
+err = stderr.read().decode()
+if err: print("Stderr:", err)
+
+c.close()
+
diff --git a/scripts/read_heimdall_remote.py b/scripts/read_heimdall_remote.py
new file mode 100644
index 0000000..dabe61d
--- /dev/null
+++ b/scripts/read_heimdall_remote.py
@@ -0,0 +1,29 @@
+import paramiko
+import sys
+
+sys.stdout.reconfigure(encoding='utf-8')
+key_path = r'C:\Users\Snarf\.ssh\id_ed25519_snarfnet'
+
+c = paramiko.SSHClient()
+c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
+c.connect("140.44.4.75", username="root", key_filename=key_path, timeout=10)
+
+py_script = """
+import sqlite3
+conn = sqlite3.connect('/var/lib/docker/volumes/heimdall_heimdall_config/_data/www/app.sqlite')
+cur = conn.cursor()
+cur.execute('SELECT id, title, url, class, appid FROM items')
+for r in cur.fetchall():
+ print(r)
+cur.execute('SELECT name FROM sqlite_master WHERE type="table"')
+print('Tables:', cur.fetchall())
+"""
+# write to temporary script on remote host
+c.exec_command("cat << 'EOF' > /tmp/read_h.py\n" + py_script + "\nEOF\n")
+stdin, stdout, stderr = c.exec_command("python3 /tmp/read_h.py")
+print(stdout.read().decode())
+err = stderr.read().decode()
+if err: print("Stderr:", err)
+
+c.close()
+
diff --git a/scripts/read_heimdall_schema.py b/scripts/read_heimdall_schema.py
new file mode 100644
index 0000000..a216df2
--- /dev/null
+++ b/scripts/read_heimdall_schema.py
@@ -0,0 +1,26 @@
+import paramiko
+import sys
+
+sys.stdout.reconfigure(encoding='utf-8')
+key_path = r'C:\Users\Snarf\.ssh\id_ed25519_snarfnet'
+
+c = paramiko.SSHClient()
+c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
+c.connect("140.44.4.75", username="root", key_filename=key_path, timeout=10)
+
+py_code = """
+import sqlite3
+conn = sqlite3.connect('/var/lib/docker/volumes/heimdall_heimdall_config/_data/www/app.sqlite')
+cur = conn.cursor()
+cur.execute('PRAGMA table_info(items)')
+print('Columns:', cur.fetchall())
+cur.execute('SELECT * FROM items WHERE title LIKE "%home%" OR title LIKE "%assist%"')
+print('Items:', cur.fetchall())
+"""
+stdin, stdout, stderr = c.exec_command(f'python3 -c "{py_code}"')
+print(stdout.read().decode())
+err = stderr.read().decode()
+if err: print("Stderr:", err)
+
+c.close()
+
diff --git a/scripts/read_redirection_1.py b/scripts/read_redirection_1.py
new file mode 100644
index 0000000..5500cd0
--- /dev/null
+++ b/scripts/read_redirection_1.py
@@ -0,0 +1,15 @@
+import paramiko
+import sys
+
+sys.stdout.reconfigure(encoding='utf-8')
+key_path = r'C:\Users\Snarf\.ssh\id_ed25519_snarfnet'
+
+c = paramiko.SSHClient()
+c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
+c.connect("140.44.4.71", username="root", key_filename=key_path, timeout=10)
+
+stdin, stdout, stderr = c.exec_command("cat /var/lib/docker/volumes/nginx-proxy-manager_nginx-data/_data/nginx/redirection_host/1.conf")
+print(stdout.read().decode())
+
+c.close()
+
diff --git a/scripts/refine_canary_watcher.py b/scripts/refine_canary_watcher.py
new file mode 100644
index 0000000..9893635
--- /dev/null
+++ b/scripts/refine_canary_watcher.py
@@ -0,0 +1,36 @@
+import paramiko
+import sys
+
+sys.stdout.reconfigure(encoding='utf-8')
+key_path = r'C:\Users\Snarf\.ssh\id_ed25519_snarfnet'
+
+c = paramiko.SSHClient()
+c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
+c.connect("140.44.4.71", username="root", key_filename=key_path, timeout=10)
+
+py_patch = """
+path = '/opt/opencanary/alert_watcher.py'
+with open(path, 'r') as f:
+ text = f.read()
+
+target = 'def send_alert(event):'
+replacement = '''def send_alert(event):
+ src_host = event.get("src_host", "")
+ src_port = event.get("src_port", -1)
+ # Ignore empty or internal startup events
+ if not src_host or src_port == -1:
+ return'''
+
+if target in text and 'if not src_host or src_port == -1:' not in text:
+ text = text.replace(target, replacement)
+ with open(path, 'w') as f:
+ f.write(text)
+ print("Patched alert_watcher.py with filter")
+else:
+ print("Filter already present or target not found")
+"""
+c.exec_command(f'python3 -c "{py_patch}"')
+c.exec_command("systemctl restart opencanary-watcher.service")
+print("Restarted watcher service")
+
+c.close()
diff --git a/scripts/setup_opencanary_gotify_app.py b/scripts/setup_opencanary_gotify_app.py
new file mode 100644
index 0000000..4d2e766
--- /dev/null
+++ b/scripts/setup_opencanary_gotify_app.py
@@ -0,0 +1,41 @@
+import paramiko
+import sys
+
+sys.stdout.reconfigure(encoding='utf-8')
+key_path = r'C:\Users\Snarf\.ssh\id_ed25519_snarfnet'
+
+c = paramiko.SSHClient()
+c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
+c.connect("140.44.4.71", username="root", key_filename=key_path, timeout=10)
+
+py_script = """
+import sqlite3
+conn = sqlite3.connect('/var/lib/docker/volumes/gotify_gotify_data/_data/gotify.db')
+cur = conn.cursor()
+cur.execute("SELECT id FROM applications WHERE token = 'A9oPeNcAnArYhOnEyPoT'")
+if not cur.fetchone():
+ cur.execute('''INSERT INTO applications (id, token, user_id, name, description, internal, default_priority)
+ VALUES (6, 'A9oPeNcAnArYhOnEyPoT', 1, 'OpenCanary Honeypot', 'OpenCanary decoy honeypot probes and unauthorized access detections', 0, 8)''')
+ conn.commit()
+ print('Inserted OpenCanary app (ID 6)')
+else:
+ print('OpenCanary app already exists')
+cur.execute("SELECT id, token, name, description FROM applications WHERE id = 6")
+print(cur.fetchall())
+conn.close()
+"""
+c.exec_command("cat << 'EOF' > /tmp/setup_oc_gotify.py\n" + py_script + "\nEOF\n")
+stdin, stdout, stderr = c.exec_command("python3 /tmp/setup_oc_gotify.py")
+print(stdout.read().decode())
+
+# Test sending a message to Gotify with this token
+print("\nTesting Gotify message dispatch with new token:")
+stdin, stdout, stderr = c.exec_command("""
+curl -s -X POST "http://127.0.0.1:8090/message?token=A9oPeNcAnArYhOnEyPoT" \
+ -H "Content-Type: application/json" \
+ -d '{"title": "OpenCanary Initialized", "message": "Honeypot monitoring armed on VLAN 50 decoy IP 14.4.4.99", "priority": 5}'
+""")
+print(stdout.read().decode())
+
+c.close()
+
diff --git a/scripts/test_ak_ha_authorize.py b/scripts/test_ak_ha_authorize.py
new file mode 100644
index 0000000..bf18bb8
--- /dev/null
+++ b/scripts/test_ak_ha_authorize.py
@@ -0,0 +1,39 @@
+import urllib.request
+import urllib.parse
+import ssl
+import sys
+
+sys.stdout.reconfigure(encoding='utf-8')
+
+ctx = ssl.create_default_context()
+ctx.check_hostname = False
+ctx.verify_mode = ssl.CERT_NONE
+
+params = {
+ "client_id": "homeassistant-7de44ad1",
+ "redirect_uri": "https://4qwu1vav8er84pjh1r9x7qt1ajwq9siw.ui.nabu.casa/auth/oidc/callback",
+ "response_type": "code",
+ "scope": "openid profile groups",
+ "state": "test_state_123"
+}
+
+url = f"https://authentik.snarfnet.net/application/o/authorize/?{urllib.parse.urlencode(params)}"
+print("Testing Authorize URL:", url)
+
+req = urllib.request.Request(url)
+try:
+ with urllib.request.urlopen(req, timeout=8, context=ctx) as r:
+ print("Status code:", r.getcode())
+ body = r.read().decode('utf-8', errors='ignore')
+ if "Redirect URI Error" in body:
+ print("ERROR: Redirect URI Error still present!")
+ else:
+ print("SUCCESS: Authorize endpoint accepted the redirect_uri!")
+except urllib.error.HTTPError as e:
+ print("HTTP error:", e.code)
+ body = e.read().decode('utf-8', errors='ignore')
+ if "Redirect URI Error" in body:
+ print("ERROR: Redirect URI Error still present!")
+ else:
+ print("Body preview:", body[:300])
+
diff --git a/scripts/test_full_canary_probe.py b/scripts/test_full_canary_probe.py
new file mode 100644
index 0000000..4f6607f
--- /dev/null
+++ b/scripts/test_full_canary_probe.py
@@ -0,0 +1,47 @@
+import paramiko
+import sys
+import time
+
+sys.stdout.reconfigure(encoding='utf-8')
+key_path = r'C:\Users\Snarf\.ssh\id_ed25519_snarfnet'
+
+print("--- 1. Probing OpenCanary from Spaceport (140.44.4.41) ---")
+sp = paramiko.SSHClient()
+sp.set_missing_host_key_policy(paramiko.AutoAddPolicy())
+sp.connect("140.44.4.41", username="root", key_filename=key_path, timeout=10)
+
+def sp_run(cmd):
+ stdin, stdout, stderr = sp.exec_command(cmd)
+ return stdout.read().decode('utf-8', errors='ignore').strip()
+
+# Probe 1: HTTP hit
+print("HTTP probe:", sp_run("curl -s -i http://14.4.4.99/test-probe | head -n 5"))
+
+# Probe 2: Telnet probe
+print("Telnet probe:", sp_run("curl -s telnet://14.4.4.99:23 --max-time 2 2>&1 | head -n 3"))
+
+# Probe 3: SSH probe with fake credentials
+print("SSH probe:", sp_run("sshpass -p 'HoneypotSecret123!' ssh -o StrictHostKeyChecking=no -o ConnectTimeout=3 admin@14.4.4.99 2>&1 || echo 'SSH auth attempted'"))
+
+sp.close()
+
+time.sleep(2)
+
+print("\n--- 2. Checking OpenCanary Log on 140.44.4.71 ---")
+sw = paramiko.SSHClient()
+sw.set_missing_host_key_policy(paramiko.AutoAddPolicy())
+sw.connect("140.44.4.71", username="root", key_filename=key_path, timeout=10)
+
+stdin, stdout, stderr = sw.exec_command("tail -n 5 /opt/opencanary/logs/opencanary.log")
+print(stdout.read().decode('utf-8', errors='ignore'))
+
+print("\n--- 3. Checking Watcher Journal Output ---")
+stdin, stdout, stderr = sw.exec_command("journalctl -u opencanary-watcher.service -n 10 --no-pager")
+print(stdout.read().decode('utf-8', errors='ignore'))
+
+print("\n--- 4. Checking Messages in Gotify Database ---")
+stdin, stdout, stderr = sw.exec_command("sqlite3 /var/lib/docker/volumes/gotify_gotify_data/_data/gotify.db 'SELECT id, appid, title, message, date FROM messages ORDER BY id DESC LIMIT 5;'")
+print(stdout.read().decode('utf-8', errors='ignore'))
+
+sw.close()
+
diff --git a/scripts/test_ha_api.py b/scripts/test_ha_api.py
new file mode 100644
index 0000000..c096633
--- /dev/null
+++ b/scripts/test_ha_api.py
@@ -0,0 +1,33 @@
+import urllib.request
+import json
+import sys
+
+sys.stdout.reconfigure(encoding='utf-8')
+
+HA_URL = "http://14.4.4.20:8123/api/"
+HA_TOKEN = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJkZWNlMWI5ZjJhZDQ0MTRmODU4NGYzN2I1YjgwZDQxMiIsImlhdCI6MTc5MzQ1MjQ2MywiZXhwIjoyMTA4ODEyNDYzfQ.JznpOqsCSnHz6aTVa8K9pQCz1816DC8SrmS1xn1Sm2k"
+
+headers = {
+ "Authorization": f"Bearer {HA_TOKEN}",
+ "Content-Type": "application/json"
+}
+
+req = urllib.request.Request(HA_URL, headers=headers)
+try:
+ with urllib.request.urlopen(req, timeout=5) as r:
+ print("HA API response (http://14.4.4.20:8123/api/):", r.getcode(), r.read().decode())
+except Exception as e:
+ print("HA API error (http://14.4.4.20:8123/api/):", e)
+
+# Test via https://snarfhome.snarfnet.net/api/
+req2 = urllib.request.Request("https://snarfhome.snarfnet.net/api/", headers=headers)
+import ssl
+ctx = ssl.create_default_context()
+ctx.check_hostname = False
+ctx.verify_mode = ssl.CERT_NONE
+try:
+ with urllib.request.urlopen(req2, timeout=5, context=ctx) as r:
+ print("HA API response (https://snarfhome.snarfnet.net/api/):", r.getcode(), r.read().decode()[:200])
+except Exception as e:
+ print("HA API error (https://snarfhome.snarfnet.net/api/):", e)
+
diff --git a/scripts/test_ha_direct.py b/scripts/test_ha_direct.py
new file mode 100644
index 0000000..f5043ab
--- /dev/null
+++ b/scripts/test_ha_direct.py
@@ -0,0 +1,25 @@
+import paramiko
+import sys
+
+sys.stdout.reconfigure(encoding='utf-8')
+key_path = r'C:\Users\Snarf\.ssh\id_ed25519_snarfnet'
+
+c = paramiko.SSHClient()
+c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
+c.connect("140.44.4.71", username="root", key_filename=key_path, timeout=10)
+
+def run(cmd):
+ stdin, stdout, stderr = c.exec_command(cmd)
+ return stdout.read().decode('utf-8', errors='ignore')
+
+print("--- HTTP test to 14.4.4.20:8123 ---")
+print(run("curl -s -I http://14.4.4.20:8123/ | head -n 10"))
+
+print("--- HTTPS test to 14.4.4.20:8123 ---")
+print(run("curl -k -s -I https://14.4.4.20:8123/ | head -n 10"))
+
+print("--- Ping to 14.4.4.20 ---")
+print(run("ping -c 2 14.4.4.20"))
+
+c.close()
+
diff --git a/scripts/test_ha_proxy_support.py b/scripts/test_ha_proxy_support.py
new file mode 100644
index 0000000..8ee224b
--- /dev/null
+++ b/scripts/test_ha_proxy_support.py
@@ -0,0 +1,20 @@
+import paramiko
+import sys
+
+sys.stdout.reconfigure(encoding='utf-8')
+key_path = r'C:\Users\Snarf\.ssh\id_ed25519_snarfnet'
+
+c = paramiko.SSHClient()
+c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
+c.connect("140.44.4.71", username="root", key_filename=key_path, timeout=10)
+
+def run(cmd):
+ stdin, stdout, stderr = c.exec_command(cmd)
+ return stdout.read().decode('utf-8', errors='ignore')
+
+print("--- Test curl with X-Forwarded-For and Host snarfhome ---")
+cmd = "curl -i -H 'Host: snarfhome.snarfnet.net' -H 'X-Forwarded-For: 140.44.4.71' -H 'X-Forwarded-Proto: https' http://14.4.4.20:8123/"
+print(run(cmd))
+
+c.close()
+
diff --git a/scripts/test_heimdall_container_curl.py b/scripts/test_heimdall_container_curl.py
new file mode 100644
index 0000000..e618e45
--- /dev/null
+++ b/scripts/test_heimdall_container_curl.py
@@ -0,0 +1,33 @@
+import paramiko
+import sys
+
+sys.stdout.reconfigure(encoding='utf-8')
+key_path = r'C:\Users\Snarf\.ssh\id_ed25519_snarfnet'
+
+c = paramiko.SSHClient()
+c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
+c.connect("140.44.4.75", username="root", key_filename=key_path, timeout=10)
+
+token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiIzZWUyNTE1MDk0NGU0ZjRjYmJkMGM1MWI5MThmY2EzYiIsImlhdCI6MTc0NjgxMDM3NSwiZXhwIjoyMDYyMTcwMzc1fQ.Le6M7WL63nxtLTxccFacQR5GVUY5C-a_zGtmXT_uaz4"
+
+cmd = f"""
+docker exec $(docker ps -q -f name=heimdall) curl -k -i -X POST https://snarfhome.snarfnet.net/api/template \
+ -H "Authorization: Bearer {token}" \
+ -H "Content-Type: application/json" \
+ -d '{{"template": "{{{{ states.light | count}}}}"}}'
+"""
+
+stdin, stdout, stderr = c.exec_command(cmd)
+print("Heimdall curl test to snarfhome.snarfnet.net:\n", stdout.read().decode())
+
+cmd2 = f"""
+docker exec $(docker ps -q -f name=heimdall) curl -k -i -X POST http://14.4.4.20:8123/api/template \
+ -H "Authorization: Bearer {token}" \
+ -H "Content-Type: application/json" \
+ -d '{{"template": "{{{{ states.light | count}}}}"}}'
+"""
+stdin, stdout, stderr = c.exec_command(cmd2)
+print("Heimdall curl test to 14.4.4.20:8123:\n", stdout.read().decode())
+
+c.close()
+
diff --git a/scripts/test_heimdall_token.py b/scripts/test_heimdall_token.py
new file mode 100644
index 0000000..16d86bd
--- /dev/null
+++ b/scripts/test_heimdall_token.py
@@ -0,0 +1,28 @@
+import urllib.request
+import ssl
+import sys
+
+sys.stdout.reconfigure(encoding='utf-8')
+
+token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiIzZWUyNTE1MDk0NGU0ZjRjYmJkMGM1MWI5MThmY2EzYiIsImlhdCI6MTc0NjgxMDM3NSwiZXhwIjoyMDYyMTcwMzc1fQ.Le6M7WL63nxtLTxccFacQR5GVUY5C-a_zGtmXT_uaz4"
+
+headers = {
+ "Authorization": f"Bearer {token}",
+ "Content-Type": "application/json"
+}
+
+ctx = ssl.create_default_context()
+ctx.check_hostname = False
+ctx.verify_mode = ssl.CERT_NONE
+
+for u in ["http://14.4.4.20:8123/api/states", "https://snarfhome.snarfnet.net/api/states"]:
+ print("Testing:", u)
+ req = urllib.request.Request(u, headers=headers)
+ try:
+ with urllib.request.urlopen(req, timeout=5, context=ctx) as r:
+ print("Status:", r.getcode())
+ except urllib.error.HTTPError as e:
+ print("HTTP Error:", e.code, e.read().decode())
+ except Exception as e:
+ print("Exception:", e)
+
diff --git a/scripts/test_live_ssh_alert.py b/scripts/test_live_ssh_alert.py
new file mode 100644
index 0000000..f5cab5b
--- /dev/null
+++ b/scripts/test_live_ssh_alert.py
@@ -0,0 +1,32 @@
+import paramiko
+import sys
+import time
+
+sys.stdout.reconfigure(encoding='utf-8')
+key_path = r'C:\Users\Snarf\.ssh\id_ed25519_snarfnet'
+
+print("--- Sending SSH probe from Spaceport to 14.4.4.99 ---")
+sp = paramiko.SSHClient()
+sp.set_missing_host_key_policy(paramiko.AutoAddPolicy())
+sp.connect("140.44.4.41", username="root", key_filename=key_path, timeout=10)
+
+stdin, stdout, stderr = sp.exec_command("ssh -o StrictHostKeyChecking=no -o ConnectTimeout=3 -o BatchMode=yes admin@14.4.4.99 2>&1 || true")
+print("SSH probe output:", stdout.read().decode())
+sp.close()
+
+time.sleep(2)
+
+print("--- Checking Watcher Logs on 140.44.4.71 ---")
+sw = paramiko.SSHClient()
+sw.set_missing_host_key_policy(paramiko.AutoAddPolicy())
+sw.connect("140.44.4.71", username="root", key_filename=key_path, timeout=10)
+
+stdin, stdout, stderr = sw.exec_command("journalctl -u opencanary-watcher.service -n 10 --no-pager")
+print(stdout.read().decode())
+
+print("\n--- Checking Gotify Messages ---")
+stdin, stdout, stderr = sw.exec_command("sqlite3 /var/lib/docker/volumes/gotify_gotify_data/_data/gotify.db 'SELECT id, appid, title, message, date FROM messages WHERE appid = 6 ORDER BY id DESC LIMIT 5;'")
+print(stdout.read().decode())
+
+sw.close()
+
diff --git a/scripts/test_opencanary_alert.py b/scripts/test_opencanary_alert.py
new file mode 100644
index 0000000..114a14c
--- /dev/null
+++ b/scripts/test_opencanary_alert.py
@@ -0,0 +1,47 @@
+import paramiko
+import sys
+import time
+
+sys.stdout.reconfigure(encoding='utf-8')
+key_path = r'C:\Users\Snarf\.ssh\id_ed25519_snarfnet'
+
+print("=== 1. Probing OpenCanary decoy IP 14.4.4.99 from Spaceport (140.44.4.41) ===")
+sp = paramiko.SSHClient()
+sp.set_missing_host_key_policy(paramiko.AutoAddPolicy())
+sp.connect("140.44.4.41", username="root", key_filename=key_path, timeout=10)
+
+def sp_run(cmd):
+ stdin, stdout, stderr = sp.exec_command(cmd)
+ return stdout.read().decode('utf-8', errors='ignore').strip()
+
+print("Pinging 14.4.4.99:")
+print(sp_run("ping -c 2 14.4.4.99"))
+
+print("\nProbing HTTP (port 80) on 14.4.4.99:")
+print(sp_run("curl -s -i http://14.4.4.99/admin/login.php | head -n 12"))
+
+print("\nProbing SSH banner (port 22) on 14.4.4.99:")
+print(sp_run("nc -z -v -w 2 14.4.4.99 22 2>&1 || curl -s telnet://14.4.4.99:22 --max-time 2 2>&1 | head -n 3"))
+
+sp.close()
+
+time.sleep(2)
+
+print("\n=== 2. Checking OpenCanary Log on Swarm (140.44.4.71) ===")
+sw = paramiko.SSHClient()
+sw.set_missing_host_key_policy(paramiko.AutoAddPolicy())
+sw.connect("140.44.4.71", username="root", key_filename=key_path, timeout=10)
+
+stdin, stdout, stderr = sw.exec_command("tail -n 10 /opt/opencanary/logs/opencanary.log")
+print(stdout.read().decode('utf-8', errors='ignore'))
+
+print("\n=== 3. Checking Gotify Alert Watcher Log ===")
+stdin, stdout, stderr = sw.exec_command("journalctl -u opencanary-watcher.service -n 15 --no-pager")
+print(stdout.read().decode('utf-8', errors='ignore'))
+
+print("\n=== 4. Checking Latest Messages in Gotify Database ===")
+stdin, stdout, stderr = sw.exec_command("sqlite3 /var/lib/docker/volumes/gotify_gotify_data/_data/gotify.db 'SELECT id, appid, title, message, date FROM messages ORDER BY id DESC LIMIT 3;'")
+print(stdout.read().decode('utf-8', errors='ignore'))
+
+sw.close()
+
diff --git a/scripts/test_vlan50_direct_probe.py b/scripts/test_vlan50_direct_probe.py
new file mode 100644
index 0000000..cec1757
--- /dev/null
+++ b/scripts/test_vlan50_direct_probe.py
@@ -0,0 +1,55 @@
+import paramiko
+import sys
+import time
+
+sys.stdout.reconfigure(encoding='utf-8')
+key_path = r'C:\Users\Snarf\.ssh\id_ed25519_snarfnet'
+
+print("--- 1. Probing OpenCanary directly from VLAN 50 (nutserver 14.4.4.44) ---")
+nut = paramiko.SSHClient()
+nut.set_missing_host_key_policy(paramiko.AutoAddPolicy())
+nut.connect("14.4.4.44", username="root", key_filename=key_path, timeout=10)
+
+def nut_run(cmd):
+ stdin, stdout, stderr = nut.exec_command(cmd)
+ return stdout.read().decode('utf-8', errors='ignore').strip()
+
+print("Pinging 14.4.4.99 from nutserver:")
+print(nut_run("ping -c 2 14.4.4.99"))
+
+print("\nConnecting to SSH on 14.4.4.99:22:")
+probe_py = """
+import socket
+s = socket.socket()
+s.settimeout(3)
+s.connect(('14.4.4.99', 22))
+banner = s.recv(1024)
+print('Received banner:', banner.decode().strip())
+s.sendall(b'SSH-2.0-OpenSSH_TestProbe\\r\\n')
+time_wait = 1
+s.close()
+"""
+print(nut_run(f'python3 -c "{probe_py}"'))
+
+nut.close()
+
+time.sleep(2)
+
+print("\n--- 2. Checking OpenCanary Log on 140.44.4.71 ---")
+sw = paramiko.SSHClient()
+sw.set_missing_host_key_policy(paramiko.AutoAddPolicy())
+sw.connect("140.44.4.71", username="root", key_filename=key_path, timeout=10)
+
+stdin, stdout, stderr = sw.exec_command("tail -n 5 /opt/opencanary/logs/opencanary.log")
+print(stdout.read().decode('utf-8', errors='ignore'))
+
+print("\n--- 3. Checking Watcher Journal Output ---")
+stdin, stdout, stderr = sw.exec_command("journalctl -u opencanary-watcher.service -n 10 --no-pager")
+print(stdout.read().decode('utf-8', errors='ignore'))
+
+print("\n--- 4. Checking Messages in Gotify Database ---")
+stdin, stdout, stderr = sw.exec_command("sqlite3 /var/lib/docker/volumes/gotify_gotify_data/_data/gotify.db 'SELECT id, appid, title, message, date FROM messages WHERE appid = 6 ORDER BY id DESC LIMIT 5;'")
+print(stdout.read().decode('utf-8', errors='ignore'))
+
+sw.close()
+
diff --git a/scripts/test_vlan50_trunk.py b/scripts/test_vlan50_trunk.py
new file mode 100644
index 0000000..0d8c6da
--- /dev/null
+++ b/scripts/test_vlan50_trunk.py
@@ -0,0 +1,29 @@
+import paramiko
+import sys
+
+sys.stdout.reconfigure(encoding='utf-8')
+key_path = r'C:\Users\Snarf\.ssh\id_ed25519_snarfnet'
+
+c = paramiko.SSHClient()
+c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
+c.connect("140.44.4.71", username="root", key_filename=key_path, timeout=10)
+
+def run(cmd):
+ stdin, stdout, stderr = c.exec_command(cmd)
+ return stdout.read().decode('utf-8', errors='ignore').strip()
+
+print("1. Creating temporary eth0.50 on 140.44.4.71...")
+run("ip link add link eth0 name eth0.50 type vlan id 50")
+run("ip addr add 14.4.4.99/25 dev eth0.50")
+run("ip link set eth0.50 up")
+
+print("2. Pinging gateway 14.4.4.1 from eth0.50:")
+ping_out = run("ping -c 2 -I eth0.50 14.4.4.1")
+print(ping_out)
+
+print("3. Cleaning up temporary interface...")
+run("ip link del eth0.50")
+print("Cleaned up.")
+
+c.close()
+
diff --git a/scripts/update_discord_bridge.py b/scripts/update_discord_bridge.py
new file mode 100644
index 0000000..260338e
--- /dev/null
+++ b/scripts/update_discord_bridge.py
@@ -0,0 +1,31 @@
+import paramiko
+import sys
+
+sys.stdout.reconfigure(encoding='utf-8')
+key_path = r'C:\Users\Snarf\.ssh\id_ed25519_snarfnet'
+
+c = paramiko.SSHClient()
+c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
+c.connect("140.44.4.71", username="root", key_filename=key_path, timeout=10)
+
+py_patch = """
+path = '/var/lib/docker/volumes/gotify_gotify_data/_data/discord_bridge.py'
+with open(path, 'r') as f:
+ text = f.read()
+
+if '6: ("infra-alerts", WEBHOOK_INFRA)' not in text:
+ text = text.replace('5: ("infra-alerts", WEBHOOK_INFRA),', '5: ("infra-alerts", WEBHOOK_INFRA),\\n 6: ("infra-alerts", WEBHOOK_INFRA),')
+ text = text.replace('5: "Backups & Storage Health",', '5: "Backups & Storage Health",\\n 6: "OpenCanary Decoy Honeypot",')
+ with open(path, 'w') as f:
+ f.write(text)
+ print("Patched discord_bridge.py successfully")
+else:
+ print("Already patched")
+"""
+c.exec_command(f'python3 -c "{py_patch}"')
+# Restart the bridge container
+c.exec_command("docker service update --force gotify_gotify-discord-bridge")
+print("Restarted gotify_gotify-discord-bridge")
+
+c.close()
+
diff --git a/scripts/update_ha_authentik_uris.py b/scripts/update_ha_authentik_uris.py
new file mode 100644
index 0000000..73d2dce
--- /dev/null
+++ b/scripts/update_ha_authentik_uris.py
@@ -0,0 +1,52 @@
+import urllib.request
+import ssl
+import json
+import sys
+
+sys.stdout.reconfigure(encoding='utf-8')
+
+ctx = ssl.create_default_context()
+ctx.check_hostname = False
+ctx.verify_mode = ssl.CERT_NONE
+
+AK_BASE = "https://authentik.snarfnet.net"
+AK_TOKEN = "SnarfNetPermanentAutomationToken2026"
+
+headers = {
+ "Authorization": f"Bearer {AK_TOKEN}",
+ "Content-Type": "application/json",
+ "Accept": "application/json"
+}
+
+# 1. Fetch current provider 15
+req = urllib.request.Request(f"{AK_BASE}/api/v3/providers/oauth2/15/", headers=headers)
+with urllib.request.urlopen(req, timeout=10, context=ctx) as r:
+ prov = json.loads(r.read().decode())
+
+print("Current redirect URIs:")
+for u in prov.get("redirect_uris", []):
+ print(" -", u)
+
+new_uris = [
+ {"matching_mode": "strict", "url": "https://snarfhome.snarfnet.net/auth/external/callback"},
+ {"matching_mode": "strict", "url": "https://snarfhome.snarfnet.net/auth/oidc/callback"},
+ {"matching_mode": "strict", "url": "https://snarfhome.snarfnet.net/"},
+ {"matching_mode": "regex", "url": r"https://snarfhome\.snarfnet\.net.*"},
+ {"matching_mode": "strict", "url": "https://4qwu1vav8er84pjh1r9x7qt1ajwq9siw.ui.nabu.casa/auth/external/callback"},
+ {"matching_mode": "strict", "url": "https://4qwu1vav8er84pjh1r9x7qt1ajwq9siw.ui.nabu.casa/auth/oidc/callback"},
+ {"matching_mode": "strict", "url": "https://4qwu1vav8er84pjh1r9x7qt1ajwq9siw.ui.nabu.casa/"},
+ {"matching_mode": "regex", "url": r"https://4qwu1vav8er84pjh1r9x7qt1ajwq9siw\.ui\.nabu\.casa.*"},
+ {"matching_mode": "regex", "url": r".*nabu\.casa.*"},
+ {"matching_mode": "regex", "url": r"http://14\.4\.4\.20:8123.*"},
+]
+
+# 2. Patch provider 15
+patch_data = json.dumps({"redirect_uris": new_uris}).encode('utf-8')
+patch_req = urllib.request.Request(f"{AK_BASE}/api/v3/providers/oauth2/15/", data=patch_data, headers=headers, method="PATCH")
+with urllib.request.urlopen(patch_req, timeout=10, context=ctx) as r:
+ updated = json.loads(r.read().decode())
+
+print("\nSuccessfully updated Authentik Provider 15 redirect URIs:")
+for u in updated.get("redirect_uris", []):
+ print(" +", u)
+
diff --git a/scripts/update_heimdall_ha.py b/scripts/update_heimdall_ha.py
new file mode 100644
index 0000000..ee8aa70
--- /dev/null
+++ b/scripts/update_heimdall_ha.py
@@ -0,0 +1,52 @@
+import paramiko
+import sys
+import json
+
+sys.stdout.reconfigure(encoding='utf-8')
+key_path = r'C:\Users\Snarf\.ssh\id_ed25519_snarfnet'
+
+c = paramiko.SSHClient()
+c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
+c.connect("140.44.4.75", username="root", key_filename=key_path, timeout=10)
+
+py_script = """
+import sqlite3, json
+
+conn = sqlite3.connect('/var/lib/docker/volumes/heimdall_heimdall_config/_data/www/app.sqlite')
+cur = conn.cursor()
+
+# Backup first
+cur.execute("SELECT id, title, description FROM items WHERE id IN (12, 44)")
+rows = cur.fetchall()
+for r in rows:
+ print('Before:', r)
+ cfg = json.loads(r[2])
+ cfg['override_url'] = 'http://14.4.4.20:8123/'
+ new_desc = json.dumps(cfg)
+ cur.execute("UPDATE items SET description = ? WHERE id = ?", (new_desc, r[0]))
+
+# Also clear cache table if any
+cur.execute("DELETE FROM cache")
+conn.commit()
+print('Cache cleared and items updated!')
+
+cur.execute("SELECT id, title, description FROM items WHERE id IN (12, 44)")
+for r in cur.fetchall():
+ print('After:', r)
+
+conn.close()
+"""
+
+c.exec_command("cat << 'EOF' > /tmp/update_h_ha.py\n" + py_script + "\nEOF\n")
+stdin, stdout, stderr = c.exec_command("python3 /tmp/update_h_ha.py")
+print(stdout.read().decode())
+err = stderr.read().decode()
+if err: print("Stderr:", err)
+
+# Trigger a curl to Heimdall item livestats
+print("\nTesting Heimdall live stats endpoint:")
+stdin, stdout, stderr = c.exec_command("docker exec $(docker ps -q -f name=heimdall) curl -s http://127.0.0.1/get_stats/44")
+print(stdout.read().decode())
+
+c.close()
+
diff --git a/scripts/update_watcher.py b/scripts/update_watcher.py
new file mode 100644
index 0000000..200209a
--- /dev/null
+++ b/scripts/update_watcher.py
@@ -0,0 +1,128 @@
+import paramiko
+import sys
+
+sys.stdout.reconfigure(encoding='utf-8')
+key_path = r'C:\Users\Snarf\.ssh\id_ed25519_snarfnet'
+
+c = paramiko.SSHClient()
+c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
+c.connect("140.44.4.71", username="root", key_filename=key_path, timeout=10)
+
+new_watcher = """#!/usr/bin/env python3
+import json
+import os
+import sys
+import time
+import urllib.request
+import urllib.error
+
+sys.stdout.reconfigure(line_buffering=True)
+
+LOG_FILE = "/opt/opencanary/logs/opencanary.log"
+GOTIFY_URL = "http://127.0.0.1:8090/message?token=A9oPeNcAnArYhOnEyPoT"
+
+LOGTYPE_NAMES = {
+ 1000: "Portscan Syn Probe",
+ 1001: "Canary System Event",
+ 1002: "Portscan Null Scan",
+ 1003: "Portscan Xmas Scan",
+ 1004: "Portscan Fin Scan",
+ 2000: "FTP Connection",
+ 2001: "FTP Auth Attempt",
+ 3000: "HTTP Connection",
+ 3001: "HTTP Web Login Attempt",
+ 4000: "SSH Connection",
+ 4001: "SSH Auth Attempt",
+ 5000: "SMB Session Request",
+ 6001: "Telnet Login Attempt",
+ 7001: "HTTP Proxy Request",
+ 8001: "MySQL Login Attempt",
+ 9001: "MSSQL Login Attempt",
+ 10001: "VNC Connection",
+ 11001: "Git Clone / Pull Attempt",
+ 12001: "RDP Connection"
+}
+
+def send_alert(event):
+ logtype = event.get("logtype", 0)
+ # Don't alert on internal canary startup messages
+ if logtype == 1001 and "Canary" in str(event.get("logdata", {})):
+ return
+
+ service_name = LOGTYPE_NAMES.get(logtype, f"Canary Event {logtype}")
+ src_host = event.get("src_host", "Unknown IP")
+ src_port = event.get("src_port", "Unknown Port")
+ dst_port = event.get("dst_port", "")
+ logdata = event.get("logdata", {})
+
+ title = f"🚨 [Honeypot Alert] {service_name} from {src_host}"
+
+ details = []
+ details.append(f"• Attacker IP: {src_host}:{src_port}")
+ if dst_port and dst_port != -1:
+ details.append(f"• Targeted Port: {dst_port}")
+ details.append(f"• Decoy IP: 14.4.4.99 (VLAN 50 Smart Home)")
+
+ if isinstance(logdata, dict):
+ if "USERNAME" in logdata:
+ details.append(f"• Username attempted: {logdata.get('USERNAME')}")
+ if "PASSWORD" in logdata:
+ details.append(f"• Password attempted: {logdata.get('PASSWORD')}")
+ if "PATH" in logdata:
+ details.append(f"• Path: {logdata.get('PATH')}")
+ if "USERAGENT" in logdata:
+ details.append(f"• User-Agent: {logdata.get('USERAGENT')}")
+ elif logdata:
+ details.append(f"• Payload: {str(logdata)[:200]}")
+
+ body = "\\n".join(details)
+
+ payload = {
+ "title": title,
+ "message": body,
+ "priority": 8
+ }
+
+ try:
+ req = urllib.request.Request(
+ GOTIFY_URL,
+ data=json.dumps(payload).encode('utf-8'),
+ headers={"Content-Type": "application/json"}
+ )
+ with urllib.request.urlopen(req, timeout=5) as r:
+ pass
+ print(f"[Alert Sent] {title}", flush=True)
+ except Exception as e:
+ print(f"[Alert Error] {e}", flush=True)
+
+def main():
+ print("OpenCanary Gotify Watcher started...", flush=True)
+ if not os.path.exists(LOG_FILE):
+ open(LOG_FILE, 'a').close()
+
+ f = open(LOG_FILE, 'r')
+ f.seek(0, os.SEEK_END)
+
+ while True:
+ line = f.readline()
+ if line:
+ line = line.strip()
+ if line:
+ try:
+ event = json.loads(line)
+ send_alert(event)
+ except Exception as e:
+ pass
+ else:
+ time.sleep(0.5)
+
+if __name__ == '__main__':
+ main()
+"""
+
+c.exec_command("cat << 'EOF' > /opt/opencanary/alert_watcher.py\n" + new_watcher + "\nEOF\n")
+c.exec_command("systemctl restart opencanary-watcher.service")
+print("Restarted watcher service")
+
+c.close()
+