From 1b8c862b10dd4cc1eb218c45d72948acf1fdd003 Mon Sep 17 00:00:00 2001 From: Snarf Date: Fri, 6 Nov 2026 09:02:19 -0500 Subject: [PATCH] feat: add various infrastructure and service diagnostic scripts --- homelab-gitops-repo | 2 +- scripts/check_aero_pihole_auth.py | 11 ++ scripts/check_api_status.py | 11 ++ scripts/check_apiurl.py | 11 ++ scripts/check_app_dll.py | 16 ++ scripts/check_cert1.py | 14 ++ scripts/check_divloginsso.py | 11 ++ scripts/check_dns_status.py | 50 ++++-- scripts/check_etc_dns.py | 11 ++ scripts/check_ftl_config.py | 16 ++ scripts/check_login_js_lines.py | 11 ++ scripts/check_login_js_lines2.py | 11 ++ scripts/check_mainjs.py | 11 ++ scripts/check_max_id.py | 21 +++ scripts/check_npm_nodes.py | 19 +- scripts/check_opt_pihole.py | 20 +++ scripts/check_pihole_ftl.py | 12 ++ scripts/check_pihole_login_js.py | 16 ++ scripts/check_pihole_password_details.py | 18 ++ scripts/check_pihole_pw_func.py | 11 ++ scripts/check_pihole_script.py | 16 ++ scripts/check_pihole_toml.py | 25 +++ scripts/check_ps.py | 17 +- scripts/check_setwebpassword.py | 11 ++ scripts/check_sso_err.py | 11 ++ scripts/check_sso_frontend.py | 17 ++ scripts/check_technitium_config.py | 16 ++ scripts/check_technitium_dotnet.py | 16 ++ scripts/check_technitium_logs.py | 16 ++ scripts/check_technitium_logs2.py | 15 ++ scripts/check_technitium_logs3.py | 16 ++ scripts/check_utils.py | 11 ++ scripts/configure_spaceport_technitium.py | 50 ++++++ scripts/create_technitium2_oidc.py | 88 +++++++++ scripts/inspect_authentik_dns_apps.py | 34 ++++ scripts/inspect_dns_details.py | 51 +++--- scripts/inspect_login_html.py | 17 ++ scripts/inspect_npm.py | 1 + scripts/read_npm_confs.py | 1 + scripts/read_today_log.py | 11 ++ scripts/remove_npm_unused.py | 51 ++++++ scripts/restart_and_test_technitium.py | 29 +++ scripts/restart_and_verify_sso.py | 27 +++ scripts/run_db_update.py | 98 ++++++++++ scripts/sync_authentik_apps.py | 126 +++++++++++++ scripts/test_authentik_reachability.py | 18 ++ scripts/test_curl_from_npm.py | 1 + scripts/test_npm_login.py | 1 + scripts/test_pihole_web_login.py | 21 +++ scripts/test_sso_after_restart.py | 17 ++ scripts/test_sso_and_ftl.py | 24 +++ scripts/test_sso_authority.py | 39 ++++ scripts/test_sso_forwarded.py | 16 ++ scripts/test_sso_get.py | 13 ++ scripts/test_technitium_auth_status.py | 18 ++ scripts/test_technitium_sso_set.py | 38 ++++ scripts/test_totp_null.py | 11 ++ scripts/update_npm_dns_entries.py | 208 ++++++++++++++++++++++ scripts/verify_npm_live.py | 58 ++++++ 59 files changed, 1499 insertions(+), 58 deletions(-) create mode 100644 scripts/check_aero_pihole_auth.py create mode 100644 scripts/check_api_status.py create mode 100644 scripts/check_apiurl.py create mode 100644 scripts/check_app_dll.py create mode 100644 scripts/check_cert1.py create mode 100644 scripts/check_divloginsso.py create mode 100644 scripts/check_etc_dns.py create mode 100644 scripts/check_ftl_config.py create mode 100644 scripts/check_login_js_lines.py create mode 100644 scripts/check_login_js_lines2.py create mode 100644 scripts/check_mainjs.py create mode 100644 scripts/check_max_id.py create mode 100644 scripts/check_opt_pihole.py create mode 100644 scripts/check_pihole_ftl.py create mode 100644 scripts/check_pihole_login_js.py create mode 100644 scripts/check_pihole_password_details.py create mode 100644 scripts/check_pihole_pw_func.py create mode 100644 scripts/check_pihole_script.py create mode 100644 scripts/check_pihole_toml.py create mode 100644 scripts/check_setwebpassword.py create mode 100644 scripts/check_sso_err.py create mode 100644 scripts/check_sso_frontend.py create mode 100644 scripts/check_technitium_config.py create mode 100644 scripts/check_technitium_dotnet.py create mode 100644 scripts/check_technitium_logs.py create mode 100644 scripts/check_technitium_logs2.py create mode 100644 scripts/check_technitium_logs3.py create mode 100644 scripts/check_utils.py create mode 100644 scripts/configure_spaceport_technitium.py create mode 100644 scripts/create_technitium2_oidc.py create mode 100644 scripts/inspect_authentik_dns_apps.py create mode 100644 scripts/inspect_login_html.py create mode 100644 scripts/read_today_log.py create mode 100644 scripts/remove_npm_unused.py create mode 100644 scripts/restart_and_test_technitium.py create mode 100644 scripts/restart_and_verify_sso.py create mode 100644 scripts/run_db_update.py create mode 100644 scripts/sync_authentik_apps.py create mode 100644 scripts/test_authentik_reachability.py create mode 100644 scripts/test_pihole_web_login.py create mode 100644 scripts/test_sso_after_restart.py create mode 100644 scripts/test_sso_and_ftl.py create mode 100644 scripts/test_sso_authority.py create mode 100644 scripts/test_sso_forwarded.py create mode 100644 scripts/test_sso_get.py create mode 100644 scripts/test_technitium_auth_status.py create mode 100644 scripts/test_technitium_sso_set.py create mode 100644 scripts/test_totp_null.py create mode 100644 scripts/update_npm_dns_entries.py create mode 100644 scripts/verify_npm_live.py diff --git a/homelab-gitops-repo b/homelab-gitops-repo index 9525cdb..d349834 160000 --- a/homelab-gitops-repo +++ b/homelab-gitops-repo @@ -1 +1 @@ -Subproject commit 9525cdbe95075e942f59224d46b80e183d6e389f +Subproject commit d3498345504d185c0a3d530b6bcc2e7664f5eeb2 diff --git a/scripts/check_aero_pihole_auth.py b/scripts/check_aero_pihole_auth.py new file mode 100644 index 0000000..929a3d9 --- /dev/null +++ b/scripts/check_aero_pihole_auth.py @@ -0,0 +1,11 @@ +import paramiko + +ssh = paramiko.SSHClient() +ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) +ssh.connect('140.44.4.11', username='root', key_filename=r'C:\Users\Snarf\.ssh\id_ed25519_snarfnet', timeout=10) + +stdin, stdout, stderr = ssh.exec_command('k3s kubectl exec -n dns deployment/pihole -- curl -s -X POST "http://localhost/api/auth" -H "Content-Type: application/json" -d \'{"password":"snarfnetadmin"}\'') +print("Aerodrome API auth test:") +print(stdout.read().decode('utf-8', errors='ignore')) + +ssh.close() diff --git a/scripts/check_api_status.py b/scripts/check_api_status.py new file mode 100644 index 0000000..93e73c0 --- /dev/null +++ b/scripts/check_api_status.py @@ -0,0 +1,11 @@ +import paramiko + +ssh = paramiko.SSHClient() +ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) +ssh.connect('140.44.4.11', username='root', key_filename=r'C:\Users\Snarf\.ssh\id_ed25519_snarfnet', timeout=10) + +stdin, stdout, stderr = ssh.exec_command('k3s kubectl exec -n dns deployment/technitium-dns -- curl -s "http://localhost:5380/api/status"') +print("api/status response:") +print(stdout.read().decode('utf-8', errors='ignore')) + +ssh.close() diff --git a/scripts/check_apiurl.py b/scripts/check_apiurl.py new file mode 100644 index 0000000..66fd135 --- /dev/null +++ b/scripts/check_apiurl.py @@ -0,0 +1,11 @@ +import paramiko + +ssh = paramiko.SSHClient() +ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) +ssh.connect('140.44.4.41', username='root', key_filename=r'C:\Users\Snarf\.ssh\id_ed25519_snarfnet', timeout=10) + +stdin, stdout, stderr = ssh.exec_command('k3s kubectl exec -n dns deployment/pihole -- curl -s "http://localhost/admin/login" | grep -i "data-apiurl"') +print("data-apiurl:") +print(stdout.read().decode()) + +ssh.close() diff --git a/scripts/check_app_dll.py b/scripts/check_app_dll.py new file mode 100644 index 0000000..ef76b4b --- /dev/null +++ b/scripts/check_app_dll.py @@ -0,0 +1,16 @@ +import paramiko + +ssh = paramiko.SSHClient() +ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) +ssh.connect('140.44.4.11', username='root', key_filename=r'C:\Users\Snarf\.ssh\id_ed25519_snarfnet', timeout=10) + +stdin, stdout, stderr = ssh.exec_command('k3s kubectl exec -n dns deployment/technitium-dns -- ls -la /app') +print("/app contents:") +print(stdout.read().decode('utf-8', errors='ignore')) + +# Search for AddAuthentication in dlls +stdin, stdout, stderr = ssh.exec_command('k3s kubectl exec -n dns deployment/technitium-dns -- strings /app/DnsServerApp.dll /app/DnsServerCore.dll 2>/dev/null | grep -i -C 3 "AddAuthentication"') +print("AddAuthentication strings:") +print(stdout.read().decode('utf-8', errors='ignore')) + +ssh.close() diff --git a/scripts/check_cert1.py b/scripts/check_cert1.py new file mode 100644 index 0000000..58e5761 --- /dev/null +++ b/scripts/check_cert1.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=5) + +stdin, stdout, stderr = c.exec_command("ls -la /var/lib/docker/volumes/nginx-proxy-manager_nginx-letsencrypt/_data/live/npm-1/") +print(stdout.read().decode('utf-8')) +c.close() + diff --git a/scripts/check_divloginsso.py b/scripts/check_divloginsso.py new file mode 100644 index 0000000..1b70d80 --- /dev/null +++ b/scripts/check_divloginsso.py @@ -0,0 +1,11 @@ +import paramiko + +ssh = paramiko.SSHClient() +ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) +ssh.connect('140.44.4.11', username='root', key_filename=r'C:\Users\Snarf\.ssh\id_ed25519_snarfnet', timeout=10) + +stdin, stdout, stderr = ssh.exec_command('k3s kubectl exec -n dns deployment/technitium-dns -- grep -rn "divLoginSso" /opt/technitium/ 2>/dev/null || true') +print("grep divLoginSso:") +print(stdout.read().decode('utf-8', errors='ignore')) + +ssh.close() diff --git a/scripts/check_dns_status.py b/scripts/check_dns_status.py index 382f9e1..07fe9ca 100644 --- a/scripts/check_dns_status.py +++ b/scripts/check_dns_status.py @@ -1,20 +1,44 @@ import paramiko -import sys -sys.stdout.reconfigure(encoding='utf-8') +def check_cluster(host, name): + print(f"=== Checking {name} ({host}) ===") + ssh = paramiko.SSHClient() + ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) + ssh.connect(host, username='root', key_filename=r'C:\Users\Snarf\.ssh\id_ed25519_snarfnet', timeout=10) -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.11", username="root", key_filename=key_path, timeout=5) + stdin, stdout, stderr = ssh.exec_command('k3s kubectl get pods -n dns -o wide') + print("Pods:") + print(stdout.read().decode()) -stdin, stdout, stderr = c.exec_command("k3s kubectl -n dns get pods,svc") -print("=== PODS & SVCS ===") -print(stdout.read().decode('utf-8')) + stdin, stdout, stderr = ssh.exec_command('k3s kubectl exec -n dns deployment/pihole -- pihole -v') + print("Pihole version:") + print(stdout.read().decode()) -stdin, stdout, stderr = c.exec_command("k3s kubectl -n dns describe svc pihole-dns-vip | grep -E 'Events:|Message|Failed'") -print("=== SERVICE EVENTS ===") -print(stdout.read().decode('utf-8')) + # Set password explicitly + stdin, stdout, stderr = ssh.exec_command('k3s kubectl exec -n dns deployment/pihole -- pihole setpassword snarfnetadmin') + out = stdout.read().decode() + err = stderr.read().decode() + print("Set password with pihole setpassword:") + print(out) + if err: + print("ERR:", err) -c.close() + if "Unknown" in out or "invalid" in out.lower() or err: + stdin, stdout, stderr = ssh.exec_command('k3s kubectl exec -n dns deployment/pihole -- pihole -a -p snarfnetadmin') + print("Fallback pihole -a -p:") + print(stdout.read().decode()) + # Check Technitium login via API + stdin, stdout, stderr = ssh.exec_command('k3s kubectl exec -n dns deployment/technitium-dns -- curl -s "http://localhost:5380/api/user/login?user=admin&pass=snarfnetadmin"') + login_res = stdout.read().decode() + print("Technitium API Login:", login_res) + + # Check Technitium SSO config + stdin, stdout, stderr = ssh.exec_command("k3s kubectl exec -n dns deployment/technitium-dns -- cat /etc/dns/auth.config 2>/dev/null || echo 'No auth.config'") + print("Technitium auth.config:") + print(stdout.read().decode()) + + ssh.close() + +check_cluster('140.44.4.11', 'Aerodrome') +check_cluster('140.44.4.41', 'Spaceport') diff --git a/scripts/check_etc_dns.py b/scripts/check_etc_dns.py new file mode 100644 index 0000000..1aa6d88 --- /dev/null +++ b/scripts/check_etc_dns.py @@ -0,0 +1,11 @@ +import paramiko + +ssh = paramiko.SSHClient() +ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) +ssh.connect('140.44.4.11', username='root', key_filename=r'C:\Users\Snarf\.ssh\id_ed25519_snarfnet', timeout=10) + +stdin, stdout, stderr = ssh.exec_command('k3s kubectl exec -n dns deployment/technitium-dns -- ls -la /etc/dns/') +print("/etc/dns listing:") +print(stdout.read().decode('utf-8', errors='ignore')) + +ssh.close() diff --git a/scripts/check_ftl_config.py b/scripts/check_ftl_config.py new file mode 100644 index 0000000..5a205a6 --- /dev/null +++ b/scripts/check_ftl_config.py @@ -0,0 +1,16 @@ +import paramiko + +ssh = paramiko.SSHClient() +ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) +ssh.connect('140.44.4.11', username='root', key_filename=r'C:\Users\Snarf\.ssh\id_ed25519_snarfnet', timeout=10) + +stdin, stdout, stderr = ssh.exec_command('k3s kubectl exec -n dns deployment/pihole -- pihole-FTL --config webserver.api.password') +print("Current config webserver.api.password:") +print(stdout.read().decode('utf-8', errors='ignore')) + +# Check pihole-FTL --config --help or options +stdin, stdout, stderr = ssh.exec_command('k3s kubectl exec -n dns deployment/pihole -- pihole-FTL --config 2>&1 || true') +print("pihole-FTL --config:") +print(stdout.read().decode('utf-8', errors='ignore')[:1500]) + +ssh.close() diff --git a/scripts/check_login_js_lines.py b/scripts/check_login_js_lines.py new file mode 100644 index 0000000..e6fb60c --- /dev/null +++ b/scripts/check_login_js_lines.py @@ -0,0 +1,11 @@ +import paramiko + +ssh = paramiko.SSHClient() +ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) +ssh.connect('140.44.4.41', username='root', key_filename=r'C:\Users\Snarf\.ssh\id_ed25519_snarfnet', timeout=10) + +stdin, stdout, stderr = ssh.exec_command('k3s kubectl exec -n dns deployment/pihole -- sed -n "20,60p" /var/www/html/admin/scripts/js/login.js') +print("login.js lines 20-60:") +print(stdout.read().decode()) + +ssh.close() diff --git a/scripts/check_login_js_lines2.py b/scripts/check_login_js_lines2.py new file mode 100644 index 0000000..9babcd3 --- /dev/null +++ b/scripts/check_login_js_lines2.py @@ -0,0 +1,11 @@ +import paramiko + +ssh = paramiko.SSHClient() +ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) +ssh.connect('140.44.4.41', username='root', key_filename=r'C:\Users\Snarf\.ssh\id_ed25519_snarfnet', timeout=10) + +stdin, stdout, stderr = ssh.exec_command('k3s kubectl exec -n dns deployment/pihole -- sed -n "60,110p" /var/www/html/admin/scripts/js/login.js') +print("login.js lines 60-110:") +print(stdout.read().decode()) + +ssh.close() diff --git a/scripts/check_mainjs.py b/scripts/check_mainjs.py new file mode 100644 index 0000000..a25ea32 --- /dev/null +++ b/scripts/check_mainjs.py @@ -0,0 +1,11 @@ +import paramiko + +ssh = paramiko.SSHClient() +ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) +ssh.connect('140.44.4.11', username='root', key_filename=r'C:\Users\Snarf\.ssh\id_ed25519_snarfnet', timeout=10) + +stdin, stdout, stderr = ssh.exec_command('k3s kubectl exec -n dns deployment/technitium-dns -- sed -n "40,65p" /opt/technitium/dns/www/js/main.js') +print("main.js lines 40-65:") +print(stdout.read().decode('utf-8', errors='ignore')) + +ssh.close() diff --git a/scripts/check_max_id.py b/scripts/check_max_id.py new file mode 100644 index 0000000..765dc26 --- /dev/null +++ b/scripts/check_max_id.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=5) + +py_cmd = """python3 -c " +import sqlite3 +conn = sqlite3.connect('/var/lib/docker/volumes/nginx-proxy-manager_nginx-data/_data/database.sqlite') +c = conn.cursor() +c.execute('SELECT MAX(id) FROM proxy_host') +print('MAX ID:', c.fetchone()[0]) +" """ +stdin, stdout, stderr = c.exec_command(py_cmd) +print(stdout.read().decode('utf-8')) +c.close() + diff --git a/scripts/check_npm_nodes.py b/scripts/check_npm_nodes.py index 2f8b617..56c1743 100644 --- a/scripts/check_npm_nodes.py +++ b/scripts/check_npm_nodes.py @@ -1,14 +1,13 @@ import paramiko +import sys -ssh = paramiko.SSHClient() -ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) -ssh.connect('140.44.4.71', username='snarf', key_filename=r'C:\Users\Snarf\.ssh\id_ed25519_snarfnet', timeout=10) +sys.stdout.reconfigure(encoding='utf-8') -def run(cmd): - print(f"=== {cmd} ===") - stdin, stdout, stderr = ssh.exec_command(cmd) - print(stdout.read().decode()) - -run("sudo docker service ps nginx-proxy-manager_app --filter 'desired-state=running'") -ssh.close() +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=5) +stdin, stdout, stderr = c.exec_command("docker service ps nginx-proxy-manager_app") +print(stdout.read().decode('utf-8')) +c.close() diff --git a/scripts/check_opt_pihole.py b/scripts/check_opt_pihole.py new file mode 100644 index 0000000..a846078 --- /dev/null +++ b/scripts/check_opt_pihole.py @@ -0,0 +1,20 @@ +import paramiko + +ssh = paramiko.SSHClient() +ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) +ssh.connect('140.44.4.11', username='root', key_filename=r'C:\Users\Snarf\.ssh\id_ed25519_snarfnet', timeout=10) + +stdin, stdout, stderr = ssh.exec_command('k3s kubectl exec -n dns deployment/pihole -- grep -rn "setFTLConfigValue" /opt/pihole/') +print("grep setFTLConfigValue in /opt/pihole/:") +print(stdout.read().decode('utf-8', errors='ignore')) + +stdin, stdout, stderr = ssh.exec_command('k3s kubectl exec -n dns deployment/pihole -- sed -n "1,50p" /opt/pihole/COL_TABLE') +print("head /opt/pihole/COL_TABLE or similar scripts:") +print(stdout.read().decode('utf-8', errors='ignore')) + +# Check files in /opt/pihole/ +stdin, stdout, stderr = ssh.exec_command('k3s kubectl exec -n dns deployment/pihole -- ls -la /opt/pihole/') +print("ls /opt/pihole/:") +print(stdout.read().decode('utf-8', errors='ignore')) + +ssh.close() diff --git a/scripts/check_pihole_ftl.py b/scripts/check_pihole_ftl.py new file mode 100644 index 0000000..d197c78 --- /dev/null +++ b/scripts/check_pihole_ftl.py @@ -0,0 +1,12 @@ +import paramiko + +ssh = paramiko.SSHClient() +ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) +ssh.connect('140.44.4.11', username='root', key_filename=r'C:\Users\Snarf\.ssh\id_ed25519_snarfnet', timeout=10) + +# Check pihole-FTL options for hashing password +stdin, stdout, stderr = ssh.exec_command('k3s kubectl exec -n dns deployment/pihole -- pihole-FTL --help') +print("pihole-FTL --help:") +print(stdout.read().decode('utf-8', errors='ignore')[:1000]) + +ssh.close() diff --git a/scripts/check_pihole_login_js.py b/scripts/check_pihole_login_js.py new file mode 100644 index 0000000..57c5595 --- /dev/null +++ b/scripts/check_pihole_login_js.py @@ -0,0 +1,16 @@ +import paramiko + +ssh = paramiko.SSHClient() +ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) +ssh.connect('140.44.4.41', username='root', key_filename=r'C:\Users\Snarf\.ssh\id_ed25519_snarfnet', timeout=10) + +stdin, stdout, stderr = ssh.exec_command('k3s kubectl exec -n dns deployment/pihole -- grep -rn "Wrong password" /var/www/ 2>/dev/null || true') +print("Wrong password grep in /var/www/:") +print(stdout.read().decode()) + +# Check html/login.js +stdin, stdout, stderr = ssh.exec_command('k3s kubectl exec -n dns deployment/pihole -- find / -name "*login*.js" 2>/dev/null || true') +print("login.js locations:") +print(stdout.read().decode()) + +ssh.close() diff --git a/scripts/check_pihole_password_details.py b/scripts/check_pihole_password_details.py new file mode 100644 index 0000000..7dfd92f --- /dev/null +++ b/scripts/check_pihole_password_details.py @@ -0,0 +1,18 @@ +import paramiko + +ssh = paramiko.SSHClient() +ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) +ssh.connect('140.44.4.11', username='root', key_filename=r'C:\Users\Snarf\.ssh\id_ed25519_snarfnet', timeout=10) + +# Check how pihole setpassword works +# Let's inspect /usr/local/bin/pihole around setFTLConfigValue +stdin, stdout, stderr = ssh.exec_command('k3s kubectl exec -n dns deployment/pihole -- grep -n -A 20 "setFTLConfigValue()" /usr/local/bin/pihole') +print("setFTLConfigValue function:") +print(stdout.read().decode('utf-8', errors='ignore')) + +# Check pihole-FTL CLI options for setting or hashing password +stdin, stdout, stderr = ssh.exec_command('k3s kubectl exec -n dns deployment/pihole -- pihole-FTL --help 2>&1') +print("pihole-FTL help:") +print(stdout.read().decode('utf-8', errors='ignore')[:1500]) + +ssh.close() diff --git a/scripts/check_pihole_pw_func.py b/scripts/check_pihole_pw_func.py new file mode 100644 index 0000000..cfca68c --- /dev/null +++ b/scripts/check_pihole_pw_func.py @@ -0,0 +1,11 @@ +import paramiko + +ssh = paramiko.SSHClient() +ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) +ssh.connect('140.44.4.11', username='root', key_filename=r'C:\Users\Snarf\.ssh\id_ed25519_snarfnet', timeout=10) + +stdin, stdout, stderr = ssh.exec_command('k3s kubectl exec -n dns deployment/pihole -- sed -n "200,280p" /usr/local/bin/pihole') +print("pihole script lines 200-280:") +print(stdout.read().decode('utf-8', errors='ignore')) + +ssh.close() diff --git a/scripts/check_pihole_script.py b/scripts/check_pihole_script.py new file mode 100644 index 0000000..5af57c8 --- /dev/null +++ b/scripts/check_pihole_script.py @@ -0,0 +1,16 @@ +import paramiko + +ssh = paramiko.SSHClient() +ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) +ssh.connect('140.44.4.11', username='root', key_filename=r'C:\Users\Snarf\.ssh\id_ed25519_snarfnet', timeout=10) + +stdin, stdout, stderr = ssh.exec_command('k3s kubectl exec -n dns deployment/pihole -- grep -rn "setpassword" /opt/pihole/ /usr/local/bin/ 2>/dev/null || true') +print("setpassword references:") +print(stdout.read().decode('utf-8', errors='ignore')) + +# Check how WEBPASSWORD or FTLCONF is handled during container start in entrypoint +stdin, stdout, stderr = ssh.exec_command('k3s kubectl exec -n dns deployment/pihole -- cat /entrypoint.sh 2>/dev/null || cat /start.sh 2>/dev/null || ls -l /') +print("Entrypoint check:") +print(stdout.read().decode('utf-8', errors='ignore')[:1500]) + +ssh.close() diff --git a/scripts/check_pihole_toml.py b/scripts/check_pihole_toml.py new file mode 100644 index 0000000..ae6e014 --- /dev/null +++ b/scripts/check_pihole_toml.py @@ -0,0 +1,25 @@ +import paramiko + +def check_pihole_toml(host, name): + print(f"=== {name} ({host}) ===") + ssh = paramiko.SSHClient() + ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) + ssh.connect(host, username='root', key_filename=r'C:\Users\Snarf\.ssh\id_ed25519_snarfnet', timeout=10) + + stdin, stdout, stderr = ssh.exec_command('k3s kubectl exec -n dns deployment/pihole -- grep -A 10 "\[webserver" /etc/pihole/pihole.toml 2>/dev/null || true') + print("webserver section in pihole.toml:") + print(stdout.read().decode('utf-8', errors='ignore')) + + stdin, stdout, stderr = ssh.exec_command('k3s kubectl exec -n dns deployment/pihole -- grep -rn "pwhash" /etc/pihole/ 2>/dev/null || true') + print("pwhash matches:") + print(stdout.read().decode('utf-8', errors='ignore')) + + # Let's test logging into pihole API with curl using password "snarfnetadmin" + stdin, stdout, stderr = ssh.exec_command('k3s kubectl exec -n dns deployment/pihole -- curl -s -X POST "http://localhost/api/auth" -H "Content-Type: application/json" -d \'{"password":"snarfnetadmin"}\'') + print("API auth test:") + print(stdout.read().decode('utf-8', errors='ignore')) + + ssh.close() + +check_pihole_toml('140.44.4.11', 'Aerodrome') +check_pihole_toml('140.44.4.41', 'Spaceport') diff --git a/scripts/check_ps.py b/scripts/check_ps.py index 10e67be..993365b 100644 --- a/scripts/check_ps.py +++ b/scripts/check_ps.py @@ -1,14 +1,11 @@ import paramiko -import sys -sys.stdout.reconfigure(encoding='utf-8') +ssh = paramiko.SSHClient() +ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) +ssh.connect('140.44.4.11', username='root', key_filename=r'C:\Users\Snarf\.ssh\id_ed25519_snarfnet', timeout=10) -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.11", username="root", key_filename=key_path, timeout=5) - -stdin, stdout, stderr = c.exec_command("ps aux | grep kubectl") -print(stdout.read().decode('utf-8')) -c.close() +stdin, stdout, stderr = ssh.exec_command('k3s kubectl exec -n dns deployment/technitium-dns -- ps aux') +print("ps aux:") +print(stdout.read().decode('utf-8', errors='ignore')) +ssh.close() diff --git a/scripts/check_setwebpassword.py b/scripts/check_setwebpassword.py new file mode 100644 index 0000000..5ef9a6f --- /dev/null +++ b/scripts/check_setwebpassword.py @@ -0,0 +1,11 @@ +import paramiko + +ssh = paramiko.SSHClient() +ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) +ssh.connect('140.44.4.11', username='root', key_filename=r'C:\Users\Snarf\.ssh\id_ed25519_snarfnet', timeout=10) + +stdin, stdout, stderr = ssh.exec_command('k3s kubectl exec -n dns deployment/pihole -- grep -n -A 30 "SetWebPassword()" /usr/local/bin/pihole') +print("SetWebPassword implementation:") +print(stdout.read().decode('utf-8', errors='ignore')) + +ssh.close() diff --git a/scripts/check_sso_err.py b/scripts/check_sso_err.py new file mode 100644 index 0000000..26dcde1 --- /dev/null +++ b/scripts/check_sso_err.py @@ -0,0 +1,11 @@ +import paramiko + +ssh = paramiko.SSHClient() +ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) +ssh.connect('140.44.4.11', username='root', key_filename=r'C:\Users\Snarf\.ssh\id_ed25519_snarfnet', timeout=10) + +stdin, stdout, stderr = ssh.exec_command('k3s kubectl exec -n dns deployment/technitium-dns -- tail -n 25 /var/log/technitium/dns/2026-11-06.log') +print("Recent logs:") +print(stdout.read().decode('utf-8', errors='ignore')) + +ssh.close() diff --git a/scripts/check_sso_frontend.py b/scripts/check_sso_frontend.py new file mode 100644 index 0000000..701b96a --- /dev/null +++ b/scripts/check_sso_frontend.py @@ -0,0 +1,17 @@ +import paramiko + +ssh = paramiko.SSHClient() +ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) +ssh.connect('140.44.4.11', username='root', key_filename=r'C:\Users\Snarf\.ssh\id_ed25519_snarfnet', timeout=10) + +# Test SSO login redirect +stdin, stdout, stderr = ssh.exec_command('k3s kubectl exec -n dns deployment/technitium-dns -- curl -s -I "http://localhost:5380/sso/login"') +print("SSO login redirect response:") +print(stdout.read().decode('utf-8', errors='ignore')) + +# Check index.html or what API the frontend calls +stdin, stdout, stderr = ssh.exec_command('k3s kubectl exec -n dns deployment/technitium-dns -- grep -n -C 5 "divLoginSso" /app/www/index.html 2>/dev/null || grep -n -C 5 "divLoginSso" /app/www/js/app.js 2>/dev/null || true') +print("divLoginSso in app:") +print(stdout.read().decode('utf-8', errors='ignore')[:1500]) + +ssh.close() diff --git a/scripts/check_technitium_config.py b/scripts/check_technitium_config.py new file mode 100644 index 0000000..bc69091 --- /dev/null +++ b/scripts/check_technitium_config.py @@ -0,0 +1,16 @@ +import paramiko + +ssh = paramiko.SSHClient() +ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) +ssh.connect('140.44.4.11', username='root', key_filename=r'C:\Users\Snarf\.ssh\id_ed25519_snarfnet', timeout=10) + +stdin, stdout, stderr = ssh.exec_command('k3s kubectl exec -n dns deployment/technitium-dns -- cat /etc/dns/log.config') +print("log.config:") +print(stdout.read().decode('utf-8', errors='ignore')) + +# Check where logs are saved or what log.config says +stdin, stdout, stderr = ssh.exec_command('k3s kubectl exec -n dns deployment/technitium-dns -- python3 -c "import json; print(open(\"/etc/dns/auth.config\", \"rb\").read())" 2>/dev/null || od -c /etc/dns/auth.config | head -n 30') +print("auth.config raw:") +print(stdout.read().decode('utf-8', errors='ignore')) + +ssh.close() diff --git a/scripts/check_technitium_dotnet.py b/scripts/check_technitium_dotnet.py new file mode 100644 index 0000000..08cf071 --- /dev/null +++ b/scripts/check_technitium_dotnet.py @@ -0,0 +1,16 @@ +import paramiko + +ssh = paramiko.SSHClient() +ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) +ssh.connect('140.44.4.11', username='root', key_filename=r'C:\Users\Snarf\.ssh\id_ed25519_snarfnet', timeout=10) + +stdin, stdout, stderr = ssh.exec_command('k3s kubectl exec -n dns deployment/technitium-dns -- strings /opt/technitium/dns/DnsServerApp.dll /opt/technitium/dns/DnsServerCore.dll | grep -i -C 3 "AddAuthentication"') +print("AddAuthentication:") +print(stdout.read().decode('utf-8', errors='ignore')) + +# Also search for "ssoEnabled" or how SSO config is checked during startup +stdin, stdout, stderr = ssh.exec_command('k3s kubectl exec -n dns deployment/technitium-dns -- strings /opt/technitium/dns/DnsServerCore.dll | grep -i -C 3 "AddOpenIdConnect"') +print("AddOpenIdConnect:") +print(stdout.read().decode('utf-8', errors='ignore')) + +ssh.close() diff --git a/scripts/check_technitium_logs.py b/scripts/check_technitium_logs.py new file mode 100644 index 0000000..6bc425d --- /dev/null +++ b/scripts/check_technitium_logs.py @@ -0,0 +1,16 @@ +import paramiko + +ssh = paramiko.SSHClient() +ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) +ssh.connect('140.44.4.11', username='root', key_filename=r'C:\Users\Snarf\.ssh\id_ed25519_snarfnet', timeout=10) + +stdin, stdout, stderr = ssh.exec_command('k3s kubectl logs -n dns deployment/technitium-dns --tail=100') +print("Technitium container logs:") +print(stdout.read().decode('utf-8', errors='ignore')) + +# Check /etc/dns/logs if any +stdin, stdout, stderr = ssh.exec_command('k3s kubectl exec -n dns deployment/technitium-dns -- ls -la /etc/dns/logs 2>/dev/null || true') +print("/etc/dns/logs:") +print(stdout.read().decode('utf-8', errors='ignore')) + +ssh.close() diff --git a/scripts/check_technitium_logs2.py b/scripts/check_technitium_logs2.py new file mode 100644 index 0000000..f6647c6 --- /dev/null +++ b/scripts/check_technitium_logs2.py @@ -0,0 +1,15 @@ +import paramiko + +ssh = paramiko.SSHClient() +ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) +ssh.connect('140.44.4.11', username='root', key_filename=r'C:\Users\Snarf\.ssh\id_ed25519_snarfnet', timeout=10) + +stdin, stdout, stderr = ssh.exec_command('k3s kubectl exec -n dns deployment/technitium-dns -- ls -la /var/log/technitium/ 2>/dev/null || true') +print("ls /var/log/technitium:") +print(stdout.read().decode('utf-8', errors='ignore')) + +stdin, stdout, stderr = ssh.exec_command('k3s kubectl exec -n dns deployment/technitium-dns -- tail -n 50 /var/log/technitium/dns.log 2>/dev/null || tail -n 50 /var/log/technitium/* 2>/dev/null || true') +print("logs:") +print(stdout.read().decode('utf-8', errors='ignore')[:2000]) + +ssh.close() diff --git a/scripts/check_technitium_logs3.py b/scripts/check_technitium_logs3.py new file mode 100644 index 0000000..412a4a7 --- /dev/null +++ b/scripts/check_technitium_logs3.py @@ -0,0 +1,16 @@ +import paramiko + +ssh = paramiko.SSHClient() +ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) +ssh.connect('140.44.4.11', username='root', key_filename=r'C:\Users\Snarf\.ssh\id_ed25519_snarfnet', timeout=10) + +stdin, stdout, stderr = ssh.exec_command('k3s kubectl exec -n dns deployment/technitium-dns -- ls -la /var/log/technitium/dns/ 2>/dev/null || true') +print("ls /var/log/technitium/dns:") +print(stdout.read().decode('utf-8', errors='ignore')) + +# Also let's check what SSO error occurred by querying the web service or error log +stdin, stdout, stderr = ssh.exec_command('k3s kubectl exec -n dns deployment/technitium-dns -- tail -n 50 /var/log/technitium/dns/* 2>/dev/null || true') +print("files:") +print(stdout.read().decode('utf-8', errors='ignore')[:2000]) + +ssh.close() diff --git a/scripts/check_utils.py b/scripts/check_utils.py new file mode 100644 index 0000000..430f249 --- /dev/null +++ b/scripts/check_utils.py @@ -0,0 +1,11 @@ +import paramiko + +ssh = paramiko.SSHClient() +ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) +ssh.connect('140.44.4.11', username='root', key_filename=r'C:\Users\Snarf\.ssh\id_ed25519_snarfnet', timeout=10) + +stdin, stdout, stderr = ssh.exec_command('k3s kubectl exec -n dns deployment/pihole -- sed -n "130,175p" /opt/pihole/utils.sh') +print("utils.sh lines 130-175:") +print(stdout.read().decode('utf-8', errors='ignore')) + +ssh.close() diff --git a/scripts/configure_spaceport_technitium.py b/scripts/configure_spaceport_technitium.py new file mode 100644 index 0000000..8b9c06b --- /dev/null +++ b/scripts/configure_spaceport_technitium.py @@ -0,0 +1,50 @@ +import paramiko +import json +import time + +def configure_spaceport_technitium(): + print("=== Configuring Spaceport Technitium SSO ===") + ssh = paramiko.SSHClient() + ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) + ssh.connect('140.44.4.41', username='root', key_filename=r'C:\Users\Snarf\.ssh\id_ed25519_snarfnet', timeout=10) + + # Login to get token + stdin, stdout, stderr = ssh.exec_command('k3s kubectl exec -n dns deployment/technitium-dns -- curl -s "http://localhost:5380/api/user/login?user=admin&pass=snarfnetadmin"') + data = json.loads(stdout.read().decode()) + token = data['token'] + print("Spaceport Technitium Token:", token) + + url = ( + f'"http://localhost:5380/api/admin/sso/set?token={token}' + '&ssoEnabled=true' + '&ssoAuthority=https://authentik.snarfnet.net/application/o/dns2/' + '&ssoClientId=technitium2-dns-oidc' + '&ssoClientSecret=SnarfNetTechnitiumOIDC2026!' + '&ssoMetadataAddress=https://authentik.snarfnet.net/application/o/dns2/.well-known/openid-configuration' + '&ssoScopes=openid,profile,email' + '&ssoAllowSignup=true' + '&ssoAllowSignupOnlyForMappedUsers=false' + '&ssoGroupMap=authentik%20Admins|Administrators"' + ) + stdin, stdout, stderr = ssh.exec_command(f'k3s kubectl exec -n dns deployment/technitium-dns -- curl -s {url}') + print("SSO set response:", stdout.read().decode()) + + print("Restarting deployment/technitium-dns on Spaceport...") + ssh.exec_command('k3s kubectl rollout restart deployment/technitium-dns -n dns') + time.sleep(12) + + stdin, stdout, stderr = ssh.exec_command('k3s kubectl wait --for=condition=Ready pod -l app=technitium-dns -n dns --timeout=30s') + print("Pod wait:", stdout.read().decode()) + + # Check api/status + stdin, stdout, stderr = ssh.exec_command('k3s kubectl exec -n dns deployment/technitium-dns -- curl -s "http://localhost:5380/api/status"') + print("api/status:", stdout.read().decode()) + + # Check SSO login redirect + stdin, stdout, stderr = ssh.exec_command('k3s kubectl exec -n dns deployment/technitium-dns -- curl -s -v -H "Host: dns2.snarfnet.net" -H "X-Forwarded-Proto: https" "http://localhost:5380/sso/login"') + print("SSO redirect test:") + print(stderr.read().decode('utf-8', errors='ignore')[:1000]) + + ssh.close() + +configure_spaceport_technitium() diff --git a/scripts/create_technitium2_oidc.py b/scripts/create_technitium2_oidc.py new file mode 100644 index 0000000..799b549 --- /dev/null +++ b/scripts/create_technitium2_oidc.py @@ -0,0 +1,88 @@ +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 + +token = "SnarfNetPermanentAutomationToken2026" +headers = { + "Authorization": f"Bearer {token}", + "Content-Type": "application/json" +} + +def api_call(url, method="GET", payload=None): + data = json.dumps(payload).encode('utf-8') if payload else None + req = urllib.request.Request(url, headers=headers, data=data, method=method) + try: + with urllib.request.urlopen(req, context=ctx, timeout=8) as resp: + return json.loads(resp.read().decode('utf-8')) + except urllib.error.HTTPError as e: + err = e.read().decode('utf-8') + print(f"API Error {e.code} on {method} {url}: {err}") + return None + +print("1. Creating OAuth2 Provider for Technitium DNS 2...") +p25_payload = { + "name": "Technitium DNS 2 SSO (Spaceport)", + "authorization_flow": "38a86ef3-f43a-4756-8f54-ca008a8eef6e", + "invalidation_flow": "7a8145b8-2476-4636-9901-825f826df805", + "client_type": "confidential", + "client_id": "technitium2-dns-oidc", + "client_secret": "SnarfNetTechnitiumOIDC2026!", + "redirect_uris": [ + {"url": "https://dns2.snarfnet.net/sso/callback", "matching_mode": "strict"}, + {"url": "https://dns2.snarfnet.net/", "matching_mode": "strict"} + ], + "signing_key": "6d48ba88-4b51-428f-903c-93334988a941", + "property_mappings": [ + "f521356c-080d-4bd6-b19a-68ab159a5aef", # openid + "8395b909-8e8d-4fc1-a620-5592e10df8fe", # email + "85b89b43-92d8-425c-b9cc-8e00038cad37", # profile + "d2248142-6be2-4833-94f8-e02f5d293b02" # groups + ], + "sub_mode": "hashed_user_id", + "issuer_mode": "per_provider", + "include_claims_in_id_token": True +} + +prov = api_call("https://authentik.snarfnet.net/api/v3/providers/oauth2/", method="POST", payload=p25_payload) +if not prov: + # Check if exists + res = api_call("https://authentik.snarfnet.net/api/v3/providers/oauth2/?search=Technitium+DNS+2") + prov_pk = res['results'][0]['pk'] +else: + prov_pk = prov['pk'] + +print(f"Provider created/found: PK {prov_pk}") + +print("\n2. Creating Application 'Technitium DNS 2 (Spaceport)'...") +app2_payload = { + "name": "Technitium DNS 2 (Spaceport)", + "slug": "dns2", + "provider": prov_pk, + "group": "Networking & Edge", + "meta_launch_url": "https://dns2.snarfnet.net", + "meta_description": "Authoritative & Recursive DNS Server with DoH & Local Resolution (Spaceport Secondary)", + "meta_icon": "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/technitium.png", + "open_in_new_tab": True +} + +app = api_call("https://authentik.snarfnet.net/api/v3/core/applications/", method="POST", payload=app2_payload) +if app: + print(f"Created Application: {app.get('name')} (slug: {app.get('slug')})") +else: + api_call("https://authentik.snarfnet.net/api/v3/core/applications/dns2/", method="PATCH", payload=app2_payload) + print("Updated Application dns2") + +print("\n3. Listing all DNS applications in Authentik App Library:") +final_apps = api_call("https://authentik.snarfnet.net/api/v3/core/applications/?page_size=100") +for a in sorted(final_apps.get('results', []), key=lambda x: x['name']): + if a['slug'] in ['pihole1', 'pihole2', 'dns1', 'dns2']: + print(f" [{a['group']}] {a['name']}") + print(f" Slug: {a['slug']} | URL: {a['meta_launch_url']} | Provider PK: {a['provider']}") + diff --git a/scripts/inspect_authentik_dns_apps.py b/scripts/inspect_authentik_dns_apps.py new file mode 100644 index 0000000..c5dfc9d --- /dev/null +++ b/scripts/inspect_authentik_dns_apps.py @@ -0,0 +1,34 @@ +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 + +token = "SnarfNetPermanentAutomationToken2026" +headers = { + "Authorization": f"Bearer {token}", + "Content-Type": "application/json" +} + +req = urllib.request.Request("https://authentik.snarfnet.net/api/v3/core/applications/", headers=headers) +with urllib.request.urlopen(req, context=ctx, timeout=8) as resp: + data = json.loads(resp.read().decode('utf-8')) + print("Applications matching pihole / technitium / dns:") + for app in data.get('results', []): + name = app.get('name', '').lower() + slug = app.get('slug', '').lower() + if 'pihole' in name or 'pihole' in slug or 'technitium' in name or 'technitium' in slug or 'dns' in name or 'dns' in slug: + print(f" Name: {app.get('name')}") + print(f" Slug: {app.get('slug')}") + print(f" PK: {app.get('pk')}") + print(f" Provider: {app.get('provider')}") + print(f" Launch URL: {app.get('meta_launch_url')}") + print(f" Group: {app.get('group')}") + print(f" Icon: {app.get('meta_icon')}") + print() + diff --git a/scripts/inspect_dns_details.py b/scripts/inspect_dns_details.py index 3c5021c..7224752 100644 --- a/scripts/inspect_dns_details.py +++ b/scripts/inspect_dns_details.py @@ -1,30 +1,33 @@ import paramiko -import sys -sys.stdout.reconfigure(encoding='utf-8') +ssh = paramiko.SSHClient() +ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) +ssh.connect('140.44.4.11', username='root', key_filename=r'C:\Users\Snarf\.ssh\id_ed25519_snarfnet', timeout=10) -key_path = r"C:\Users\Snarf\.ssh\id_ed25519_snarfnet" +# Check pihole config in /etc/pihole +stdin, stdout, stderr = ssh.exec_command('k3s kubectl exec -n dns deployment/pihole -- grep -i password /etc/pihole/pihole.toml 2>/dev/null || true') +print("pihole.toml password:") +print(stdout.read().decode('utf-8', errors='ignore')) -def check(host, ns, name): - print(f"\n=================== {name} ({host}) ===================") - c = paramiko.SSHClient() - c.set_missing_host_key_policy(paramiko.AutoAddPolicy()) - c.connect(host, username="root", key_filename=key_path, timeout=10) - - print("\n--- Pods in ns " + ns + " ---") - stdin, stdout, stderr = c.exec_command(f"k3s kubectl -n {ns} get pods -o wide") - print(stdout.read().decode('utf-8')) - - print("\n--- Pihole Pod describe/logs ---") - stdin, stdout, stderr = c.exec_command(f"k3s kubectl -n {ns} describe deploy/pihole | grep -E 'Image:|Environment|Mounts|Volumes'") - print(stdout.read().decode('utf-8')) - - print("\n--- Technitium Pod describe/logs ---") - stdin, stdout, stderr = c.exec_command(f"k3s kubectl -n {ns} describe deploy/technitium-dns | grep -E 'Image:|Environment|Mounts|Volumes'") - print(stdout.read().decode('utf-8')) - - c.close() +# Check pihole env +stdin, stdout, stderr = ssh.exec_command('k3s kubectl exec -n dns deployment/pihole -- env | grep -E "WEBPASSWORD|FTLCONF"') +print("pihole env:") +print(stdout.read().decode('utf-8', errors='ignore')) -check("140.44.4.11", "infra", "Aerodrome") -check("140.44.4.41", "infrastructure", "Spaceport") +# Let's test pihole web login via curl inside the pod or to localhost:80 +stdin, stdout, stderr = ssh.exec_command('k3s kubectl exec -n dns deployment/pihole -- curl -s -I http://localhost/admin/') +print("curl localhost/admin/ header:") +print(stdout.read().decode('utf-8', errors='ignore')) +# Check Technitium SSO settings via API using the valid token +token = "8d22b3ee9fe8ccbb317679afdbc86959d0ceea8261e0f0c7957d4325a7987d78" +stdin, stdout, stderr = ssh.exec_command(f'k3s kubectl exec -n dns deployment/technitium-dns -- curl -s "http://localhost:5380/api/admin/sso/get?token={token}"') +print("Technitium SSO get:") +print(stdout.read().decode('utf-8', errors='ignore')) + +# Check Technitium info or settings +stdin, stdout, stderr = ssh.exec_command(f'k3s kubectl exec -n dns deployment/technitium-dns -- curl -s "http://localhost:5380/api/settings/get?token={token}"') +print("Technitium settings get (sample):") +print(stdout.read().decode('utf-8', errors='ignore')[:300]) + +ssh.close() diff --git a/scripts/inspect_login_html.py b/scripts/inspect_login_html.py new file mode 100644 index 0000000..ce562e2 --- /dev/null +++ b/scripts/inspect_login_html.py @@ -0,0 +1,17 @@ +import urllib.request +import re + +url = "http://140.44.4.36:5380/" +with urllib.request.urlopen(url, timeout=5) as resp: + html = resp.read().decode('utf-8') + +# Search for login modal or sso in html +lines = html.splitlines() +for i, line in enumerate(lines): + if 'modalLogin' in line or 'btnSSO' in line or 'btnModalLogin' in line or 'Single Sign-On' in line or 'sso' in line.lower(): + start = max(0, i-5) + end = min(len(lines), i+15) + print(f"--- Around line {i} ---") + for j in range(start, end): + print(f"{j:4}: {lines[j]}") + print() diff --git a/scripts/inspect_npm.py b/scripts/inspect_npm.py index 5815bdd..37b82c2 100644 --- a/scripts/inspect_npm.py +++ b/scripts/inspect_npm.py @@ -24,3 +24,4 @@ if data: print("Mounts:", [m.get('Source') + " -> " + m.get('Destination') for m in c_info.get('Mounts', [])]) c.close() + diff --git a/scripts/read_npm_confs.py b/scripts/read_npm_confs.py index 3cc5d4f..460278b 100644 --- a/scripts/read_npm_confs.py +++ b/scripts/read_npm_confs.py @@ -14,3 +14,4 @@ for cid in [3, 43, 52, 53]: print(stdout.read().decode('utf-8')) c.close() + diff --git a/scripts/read_today_log.py b/scripts/read_today_log.py new file mode 100644 index 0000000..4cc95c4 --- /dev/null +++ b/scripts/read_today_log.py @@ -0,0 +1,11 @@ +import paramiko + +ssh = paramiko.SSHClient() +ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) +ssh.connect('140.44.4.11', username='root', key_filename=r'C:\Users\Snarf\.ssh\id_ed25519_snarfnet', timeout=10) + +stdin, stdout, stderr = ssh.exec_command('k3s kubectl exec -n dns deployment/technitium-dns -- tail -n 50 /var/log/technitium/dns/2026-11-06.log') +print("2026-11-06.log:") +print(stdout.read().decode('utf-8', errors='ignore')) + +ssh.close() diff --git a/scripts/remove_npm_unused.py b/scripts/remove_npm_unused.py new file mode 100644 index 0000000..a8ddcc9 --- /dev/null +++ b/scripts/remove_npm_unused.py @@ -0,0 +1,51 @@ +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) + +print("1. Updating database.sqlite: removing ID 3, 51, 54...") +py_script = """import sqlite3 + +conn = sqlite3.connect("/var/lib/docker/volumes/nginx-proxy-manager_nginx-data/_data/database.sqlite") +cur = conn.cursor() + +# Remove proxy hosts 3, 51, 54 +cur.execute("DELETE FROM proxy_host WHERE id IN (3, 51, 54)") +print(f"Deleted {cur.rowcount} rows from proxy_host (IDs 3, 51, 54)") + +conn.commit() +conn.close() +""" + +cmd_db = "cat << 'EOF' > /tmp/remove_npm.py\n" + py_script + "\nEOF\npython3 /tmp/remove_npm.py\n" +stdin, stdout, stderr = c.exec_command(cmd_db) +print(stdout.read().decode('utf-8')) +err = stderr.read().decode('utf-8') +if err: print("DB err:", err) + +print("2. Removing unused configuration files...") +remove_files_cmd = "rm -f /var/lib/docker/volumes/nginx-proxy-manager_nginx-data/_data/nginx/proxy_host/3.conf /var/lib/docker/volumes/nginx-proxy-manager_nginx-data/_data/nginx/proxy_host/51.conf /var/lib/docker/volumes/nginx-proxy-manager_nginx-data/_data/nginx/proxy_host/54.conf" +stdin, stdout, stderr = c.exec_command(remove_files_cmd) +stdout.channel.recv_exit_status() +print("Removed 3.conf, 51.conf, 54.conf") + +print("3. Testing Nginx configuration syntax inside container...") +test_cmd = "docker exec $(docker ps -q -f name=nginx-proxy-manager_app | head -n1) nginx -t" +stdin, stdout, stderr = c.exec_command(test_cmd) +test_out = stdout.read().decode('utf-8') +test_err = stderr.read().decode('utf-8') +print("nginx -t:\n", test_out, test_err) + +if "successful" in test_err or "successful" in test_out: + print("4. Reloading Nginx...") + stdin, stdout, stderr = c.exec_command("docker exec $(docker ps -q -f name=nginx-proxy-manager_app | head -n1) nginx -s reload") + stdout.channel.recv_exit_status() + print("Nginx reloaded successfully!") + +c.close() + diff --git a/scripts/restart_and_test_technitium.py b/scripts/restart_and_test_technitium.py new file mode 100644 index 0000000..c3029d1 --- /dev/null +++ b/scripts/restart_and_test_technitium.py @@ -0,0 +1,29 @@ +import paramiko +import time + +ssh = paramiko.SSHClient() +ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) +ssh.connect('140.44.4.11', username='root', key_filename=r'C:\Users\Snarf\.ssh\id_ed25519_snarfnet', timeout=10) + +# Delete the pod so deployment restarts it +stdin, stdout, stderr = ssh.exec_command('k3s kubectl rollout restart deployment/technitium-dns -n dns') +print("Restart technitium:", stdout.read().decode()) + +time.sleep(10) + +# Check pods +stdin, stdout, stderr = ssh.exec_command('k3s kubectl get pods -n dns -l app=technitium-dns') +print("Pods:", stdout.read().decode()) + +# Test GET /sso/login +stdin, stdout, stderr = ssh.exec_command('k3s kubectl exec -n dns deployment/technitium-dns -- curl -s -v -I "http://localhost:5380/sso/login"') +print("GET /sso/login response:") +print(stderr.read().decode('utf-8', errors='ignore')) +print(stdout.read().decode('utf-8', errors='ignore')) + +# Check logs for startup +stdin, stdout, stderr = ssh.exec_command('k3s kubectl exec -n dns deployment/technitium-dns -- tail -n 30 /var/log/technitium/dns/2026-11-06.log 2>/dev/null || true') +print("Recent logs:") +print(stdout.read().decode('utf-8', errors='ignore')) + +ssh.close() diff --git a/scripts/restart_and_verify_sso.py b/scripts/restart_and_verify_sso.py new file mode 100644 index 0000000..604c5aa --- /dev/null +++ b/scripts/restart_and_verify_sso.py @@ -0,0 +1,27 @@ +import paramiko +import time + +ssh = paramiko.SSHClient() +ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) +ssh.connect('140.44.4.11', username='root', key_filename=r'C:\Users\Snarf\.ssh\id_ed25519_snarfnet', timeout=10) + +print("Restarting deployment/technitium-dns...") +ssh.exec_command('k3s kubectl rollout restart deployment/technitium-dns -n dns') +time.sleep(12) + +# Wait for pod ready +stdin, stdout, stderr = ssh.exec_command('k3s kubectl wait --for=condition=Ready pod -l app=technitium-dns -n dns --timeout=30s') +print("Wait condition:", stdout.read().decode()) + +# Test GET /sso/login +stdin, stdout, stderr = ssh.exec_command('k3s kubectl exec -n dns deployment/technitium-dns -- curl -v "http://localhost:5380/sso/login"') +print("GET /sso/login result:") +print(stderr.read().decode('utf-8', errors='ignore')) +print(stdout.read().decode('utf-8', errors='ignore')) + +# Check recent logs +stdin, stdout, stderr = ssh.exec_command('k3s kubectl exec -n dns deployment/technitium-dns -- tail -n 25 /var/log/technitium/dns/2026-11-06.log 2>/dev/null || true') +print("Recent logs:") +print(stdout.read().decode('utf-8', errors='ignore')) + +ssh.close() diff --git a/scripts/run_db_update.py b/scripts/run_db_update.py new file mode 100644 index 0000000..9360b1d --- /dev/null +++ b/scripts/run_db_update.py @@ -0,0 +1,98 @@ +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) + +remote_script = """import sqlite3, json, datetime + +conn = sqlite3.connect("/var/lib/docker/volumes/nginx-proxy-manager_nginx-data/_data/database.sqlite") +cur = conn.cursor() +now = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") + +updates = [ + (3, json.dumps(["pihole.snarfnet.net"]), "140.44.4.34", 443, "https"), + (43, json.dumps(["pihole2.snarfnet.net"]), "140.44.4.62", 443, "https"), + (51, json.dumps(["pihole3.snarfnet.net"]), "140.44.4.62", 443, "https"), + (52, json.dumps(["dns1.snarfnet.net"]), "140.44.4.34", 443, "https"), + (53, json.dumps(["dns2.snarfnet.net"]), "140.44.4.62", 443, "https"), +] + +for pid, dnames, host, port, scheme in updates: + cur.execute('''UPDATE proxy_host + SET domain_names = ?, forward_host = ?, forward_port = ?, forward_scheme = ?, + certificate_id = 1, ssl_forced = 1, allow_websocket_upgrade = 1, http2_support = 1, + hsts_enabled = 1, hsts_subdomains = 1, advanced_config = "", modified_on = ? + WHERE id = ?''', (dnames, host, port, scheme, now, pid)) + print(f"Updated proxy host {pid} -> {host}:{port} ({scheme})") + +meta_json = json.dumps({"nginx_online": True, "nginx_err": None}) + +cur.execute("SELECT id FROM proxy_host WHERE id = 54") +if not cur.fetchone(): + cur.execute('''INSERT INTO proxy_host ( + id, created_on, modified_on, owner_user_id, is_deleted, + domain_names, forward_host, forward_port, access_list_id, + certificate_id, ssl_forced, caching_enabled, block_exploits, + advanced_config, meta, allow_websocket_upgrade, http2_support, + forward_scheme, enabled, locations, hsts_enabled, hsts_subdomains, + trust_forwarded_proto + ) VALUES ( + 54, ?, ?, 1, 0, + ?, "140.44.4.34", 443, 0, + 1, 1, 0, 1, + "", ?, 1, 1, + "https", 1, "[]", 1, 1, + 0 + )''', (now, now, json.dumps(["technitium.snarfnet.net"]), meta_json)) + print("Inserted proxy host 54: technitium.snarfnet.net -> 140.44.4.34:443") +else: + cur.execute('''UPDATE proxy_host + SET domain_names = ?, forward_host = "140.44.4.34", forward_port = 443, forward_scheme = "https", + certificate_id = 1, ssl_forced = 1, allow_websocket_upgrade = 1, http2_support = 1, + hsts_enabled = 1, hsts_subdomains = 1, modified_on = ? + WHERE id = 54''', (json.dumps(["technitium.snarfnet.net"]), now)) + print("Updated proxy host 54: technitium.snarfnet.net") + +cur.execute("SELECT id FROM proxy_host WHERE id = 55") +if not cur.fetchone(): + cur.execute('''INSERT INTO proxy_host ( + id, created_on, modified_on, owner_user_id, is_deleted, + domain_names, forward_host, forward_port, access_list_id, + certificate_id, ssl_forced, caching_enabled, block_exploits, + advanced_config, meta, allow_websocket_upgrade, http2_support, + forward_scheme, enabled, locations, hsts_enabled, hsts_subdomains, + trust_forwarded_proto + ) VALUES ( + 55, ?, ?, 1, 0, + ?, "140.44.4.34", 443, 0, + 1, 1, 0, 1, + "", ?, 1, 1, + "https", 1, "[]", 1, 1, + 0 + )''', (now, now, json.dumps(["pihole1.snarfnet.net"]), meta_json)) + print("Inserted proxy host 55: pihole1.snarfnet.net -> 140.44.4.34:443") +else: + cur.execute('''UPDATE proxy_host + SET domain_names = ?, forward_host = "140.44.4.34", forward_port = 443, forward_scheme = "https", + certificate_id = 1, ssl_forced = 1, allow_websocket_upgrade = 1, http2_support = 1, + hsts_enabled = 1, hsts_subdomains = 1, modified_on = ? + WHERE id = 55''', (json.dumps(["pihole1.snarfnet.net"]), now)) + print("Updated proxy host 55: pihole1.snarfnet.net") + +conn.commit() +conn.close() +""" + +# Write script to /tmp/update_npm_db.py +cmd = "cat << 'EOF' > /tmp/update_npm_db.py\n" + remote_script + "\nEOF\npython3 /tmp/update_npm_db.py\n" +stdin, stdout, stderr = c.exec_command(cmd) +print("Output:\n", stdout.read().decode('utf-8')) +err = stderr.read().decode('utf-8') +if err: print("Err:\n", err) + +c.close() diff --git a/scripts/sync_authentik_apps.py b/scripts/sync_authentik_apps.py new file mode 100644 index 0000000..6dcb1a8 --- /dev/null +++ b/scripts/sync_authentik_apps.py @@ -0,0 +1,126 @@ +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 + +token = "SnarfNetPermanentAutomationToken2026" +headers = { + "Authorization": f"Bearer {token}", + "Content-Type": "application/json" +} + +def api_call(url, method="GET", payload=None): + data = json.dumps(payload).encode('utf-8') if payload else None + req = urllib.request.Request(url, headers=headers, data=data, method=method) + try: + with urllib.request.urlopen(req, context=ctx, timeout=8) as resp: + return json.loads(resp.read().decode('utf-8')) + except urllib.error.HTTPError as e: + err = e.read().decode('utf-8') + print(f"API Error {e.code} on {method} {url}: {err}") + return None + +print("1. Updating OAuth2 Provider 23 (Technitium)...") +p23_payload = { + "name": "Technitium DNS SSO", + "redirect_uris": [ + {"url": "https://dns1.snarfnet.net/sso/callback", "matching_mode": "strict"}, + {"url": "https://dns2.snarfnet.net/sso/callback", "matching_mode": "strict"}, + {"url": "https://dns1.snarfnet.net/", "matching_mode": "strict"}, + {"url": "https://dns2.snarfnet.net/", "matching_mode": "strict"}, + {"url": "https://dns.*\\.snarfnet\\.net.*", "matching_mode": "regex"} + ] +} +api_call("https://authentik.snarfnet.net/api/v3/providers/oauth2/23/", method="PATCH", payload=p23_payload) +print("Updated Provider 23 redirect URIs.") + +print("\n2. Updating Applications in Authentik App Library...") + +# Target applications definition: +apps_target = [ + { + "name": "Pi-hole 1 (Aerodrome)", + "slug": "pihole1", + "provider": 24, + "group": "Networking & Edge", + "meta_launch_url": "https://pihole1.snarfnet.net", + "meta_description": "Network-wide Ad & Tracker Blocking (Aerodrome Primary)", + "meta_icon": "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/pi-hole.png", + "open_in_new_tab": True + }, + { + "name": "Pi-hole 2 (Spaceport)", + "slug": "pihole2", + "provider": 12, + "group": "Networking & Edge", + "meta_launch_url": "https://pihole2.snarfnet.net", + "meta_description": "Network-wide Ad & Tracker Blocking (Spaceport Secondary)", + "meta_icon": "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/pi-hole.png", + "open_in_new_tab": True + }, + { + "name": "Technitium DNS 1 (Aerodrome)", + "slug": "dns1", + "provider": 23, + "group": "Networking & Edge", + "meta_launch_url": "https://dns1.snarfnet.net", + "meta_description": "Authoritative & Recursive DNS Server with DoH & Local Resolution (Aerodrome Primary)", + "meta_icon": "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/technitium.png", + "open_in_new_tab": True + }, + { + "name": "Technitium DNS 2 (Spaceport)", + "slug": "dns2", + "provider": 23, + "group": "Networking & Edge", + "meta_launch_url": "https://dns2.snarfnet.net", + "meta_description": "Authoritative & Recursive DNS Server with DoH & Local Resolution (Spaceport Secondary)", + "meta_icon": "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/technitium.png", + "open_in_new_tab": True + } +] + +# Fetch current applications +current_apps = api_call("https://authentik.snarfnet.net/api/v3/core/applications/?page_size=100") +current_slugs = {a['slug']: a for a in current_apps.get('results', [])} + +# Delete obsolete slugs if they exist (e.g., 'technitium' or old 'pihole' if we recreate as 'pihole2') +if 'technitium' in current_slugs and 'dns1' not in current_slugs: + print("Renaming app 'technitium' to 'dns1'...") + api_call("https://authentik.snarfnet.net/api/v3/core/applications/technitium/", method="PATCH", payload=apps_target[2]) +elif 'technitium' in current_slugs: + print("Removing obsolete 'technitium' app...") + api_call("https://authentik.snarfnet.net/api/v3/core/applications/technitium/", method="DELETE") + +if 'pihole' in current_slugs: + print("Renaming app 'pihole' to 'pihole2'...") + api_call("https://authentik.snarfnet.net/api/v3/core/applications/pihole/", method="PATCH", payload=apps_target[1]) + +# Refresh apps list +current_apps = api_call("https://authentik.snarfnet.net/api/v3/core/applications/?page_size=100") +current_slugs = {a['slug']: a for a in current_apps.get('results', [])} + +for target in apps_target: + slug = target['slug'] + if slug in current_slugs: + print(f"Updating app '{target['name']}' ({slug})...") + res = api_call(f"https://authentik.snarfnet.net/api/v3/core/applications/{slug}/", method="PATCH", payload=target) + if res: print(f" ✓ Updated {slug}") + else: + print(f"Creating app '{target['name']}' ({slug})...") + res = api_call("https://authentik.snarfnet.net/api/v3/core/applications/", method="POST", payload=target) + if res: print(f" ✓ Created {slug}") + +print("\n3. Verifying final state in Authentik App Library...") +final_apps = api_call("https://authentik.snarfnet.net/api/v3/core/applications/?page_size=100") +for a in sorted(final_apps.get('results', []), key=lambda x: x['name']): + if a['slug'] in ['pihole1', 'pihole2', 'dns1', 'dns2', 'pihole', 'technitium']: + print(f" [{a['group']}] {a['name']}") + print(f" Slug: {a['slug']} | URL: {a['meta_launch_url']} | Provider PK: {a['provider']}") + diff --git a/scripts/test_authentik_reachability.py b/scripts/test_authentik_reachability.py new file mode 100644 index 0000000..1fc1b23 --- /dev/null +++ b/scripts/test_authentik_reachability.py @@ -0,0 +1,18 @@ +import paramiko + +ssh = paramiko.SSHClient() +ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) +ssh.connect('140.44.4.11', username='root', key_filename=r'C:\Users\Snarf\.ssh\id_ed25519_snarfnet', timeout=10) + +# Curl authentik from inside technitium pod +stdin, stdout, stderr = ssh.exec_command('k3s kubectl exec -n dns deployment/technitium-dns -- curl -v https://authentik.snarfnet.net/application/o/dns1/.well-known/openid-configuration') +print("Curl from technitium pod:") +print(stderr.read().decode('utf-8', errors='ignore')) +print(stdout.read().decode('utf-8', errors='ignore')) + +# Check DNS resolution inside technitium pod +stdin, stdout, stderr = ssh.exec_command('k3s kubectl exec -n dns deployment/technitium-dns -- cat /etc/resolv.conf') +print("/etc/resolv.conf inside technitium pod:") +print(stdout.read().decode('utf-8', errors='ignore')) + +ssh.close() diff --git a/scripts/test_curl_from_npm.py b/scripts/test_curl_from_npm.py index 841f3e3..d3cd193 100644 --- a/scripts/test_curl_from_npm.py +++ b/scripts/test_curl_from_npm.py @@ -22,3 +22,4 @@ for host, ip, path in [ print(f"{host} -> {ip}: {status_line} {loc}") c.close() + diff --git a/scripts/test_npm_login.py b/scripts/test_npm_login.py index befb36f..e7a2306 100644 --- a/scripts/test_npm_login.py +++ b/scripts/test_npm_login.py @@ -23,3 +23,4 @@ for host in ['140.44.4.70', '140.44.4.71']: except Exception as e: print(f" Conn error on {host}: {e}") break + diff --git a/scripts/test_pihole_web_login.py b/scripts/test_pihole_web_login.py new file mode 100644 index 0000000..bae7644 --- /dev/null +++ b/scripts/test_pihole_web_login.py @@ -0,0 +1,21 @@ +import paramiko + +ssh = paramiko.SSHClient() +ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) +ssh.connect('140.44.4.41', username='root', key_filename=r'C:\Users\Snarf\.ssh\id_ed25519_snarfnet', timeout=10) + +# Test login to pihole web interface via curl from the host +cmd = 'curl -s -c /tmp/cookies.txt -b /tmp/cookies.txt -X POST "http://140.44.4.65/api/auth" -H "Content-Type: application/json" -d \'{"password":"snarfnetadmin"}\'' +stdin, stdout, stderr = ssh.exec_command(cmd) +print("Spaceport /api/auth via 140.44.4.65:", stdout.read().decode()) + +cmd = 'curl -s -c /tmp/cookies.txt -b /tmp/cookies.txt "http://140.44.4.65/api/auth"' +stdin, stdout, stderr = ssh.exec_command(cmd) +print("Spaceport check session /api/auth:", stdout.read().decode()) + +# Check pihole FTL log for any error +stdin, stdout, stderr = ssh.exec_command('k3s kubectl exec -n dns deployment/pihole -- tail -n 25 /var/log/pihole/FTL.log 2>/dev/null || true') +print("FTL.log:") +print(stdout.read().decode()) + +ssh.close() diff --git a/scripts/test_sso_after_restart.py b/scripts/test_sso_after_restart.py new file mode 100644 index 0000000..573f0f7 --- /dev/null +++ b/scripts/test_sso_after_restart.py @@ -0,0 +1,17 @@ +import paramiko + +ssh = paramiko.SSHClient() +ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) +ssh.connect('140.44.4.11', username='root', key_filename=r'C:\Users\Snarf\.ssh\id_ed25519_snarfnet', timeout=10) + +stdin, stdout, stderr = ssh.exec_command('k3s kubectl exec -n dns deployment/technitium-dns -- curl -s -v "http://localhost:5380/sso/login"') +print("GET /sso/login header/body:") +print(stderr.read().decode('utf-8', errors='ignore')) +print(stdout.read().decode('utf-8', errors='ignore')) + +# Also check index.html from curl to see if sso button is shown +stdin, stdout, stderr = ssh.exec_command('k3s kubectl exec -n dns deployment/technitium-dns -- curl -s "http://localhost:5380/" | grep -C 5 "divLoginSso"') +print("divLoginSso in index.html:") +print(stdout.read().decode('utf-8', errors='ignore')) + +ssh.close() diff --git a/scripts/test_sso_and_ftl.py b/scripts/test_sso_and_ftl.py new file mode 100644 index 0000000..c137f53 --- /dev/null +++ b/scripts/test_sso_and_ftl.py @@ -0,0 +1,24 @@ +import paramiko + +ssh = paramiko.SSHClient() +ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) +ssh.connect('140.44.4.11', username='root', key_filename=r'C:\Users\Snarf\.ssh\id_ed25519_snarfnet', timeout=10) + +stdin, stdout, stderr = ssh.exec_command('k3s kubectl exec -n dns deployment/pihole -- grep -n -A 25 "setFTLConfigValue()" /usr/local/bin/pihole') +print("setFTLConfigValue:") +print(stdout.read().decode('utf-8', errors='ignore')) + +# Also let's check what Technitium API call sets SSO +# In Technitium: /api/admin/sso/set +# Let's inspect /api/admin/sso/set with token +token = "8d22b3ee9fe8ccbb317679afdbc86959d0ceea8261e0f0c7957d4325a7987d78" +stdin, stdout, stderr = ssh.exec_command(f'k3s kubectl exec -n dns deployment/technitium-dns -- curl -s "http://localhost:5380/api/admin/sso/set?token={token}&ssoEnabled=true&ssoClientId=technitium-dns-oidc&ssoClientSecret=SnarfNetTechnitiumOIDC2026!&ssoMetadataAddress=https://authentik.snarfnet.net/application/o/dns1/.well-known/openid-configuration&ssoAllowSignup=true&ssoAllowSignupOnlyForMappedUsers=false"') +print("Technitium SSO set test:") +print(stdout.read().decode('utf-8', errors='ignore')) + +# Check get after set +stdin, stdout, stderr = ssh.exec_command(f'k3s kubectl exec -n dns deployment/technitium-dns -- curl -s "http://localhost:5380/api/admin/sso/get?token={token}"') +print("Technitium SSO get after set:") +print(stdout.read().decode('utf-8', errors='ignore')) + +ssh.close() diff --git a/scripts/test_sso_authority.py b/scripts/test_sso_authority.py new file mode 100644 index 0000000..0c74bb0 --- /dev/null +++ b/scripts/test_sso_authority.py @@ -0,0 +1,39 @@ +import paramiko +import json + +ssh = paramiko.SSHClient() +ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) +ssh.connect('140.44.4.11', username='root', key_filename=r'C:\Users\Snarf\.ssh\id_ed25519_snarfnet', timeout=10) + +# Check Technitium local DNS resolution for authentik.snarfnet.net +stdin, stdout, stderr = ssh.exec_command('k3s kubectl exec -n dns deployment/technitium-dns -- dig @127.0.0.1 authentik.snarfnet.net +short') +print("Dig authentik.snarfnet.net from Technitium DNS:") +print(stdout.read().decode()) + +# Get fresh token +stdin, stdout, stderr = ssh.exec_command('k3s kubectl exec -n dns deployment/technitium-dns -- curl -s "http://localhost:5380/api/user/login?user=admin&pass=snarfnetadmin"') +data = json.loads(stdout.read().decode()) +token = data['token'] +print("Fresh token:", token) + +# Now call api/admin/sso/set with ssoAuthority set as well! +url = ( + f'"http://localhost:5380/api/admin/sso/set?token={token}' + '&ssoEnabled=true' + '&ssoAuthority=https://authentik.snarfnet.net/application/o/dns1/' + '&ssoClientId=technitium-dns-oidc' + '&ssoClientSecret=SnarfNetTechnitiumOIDC2026!' + '&ssoMetadataAddress=https://authentik.snarfnet.net/application/o/dns1/.well-known/openid-configuration' + '&ssoScopes=openid,profile,email' + '&ssoAllowSignup=true' + '&ssoAllowSignupOnlyForMappedUsers=false' + '&ssoGroupMap=authentik%20Admins|Administrators"' +) +stdin, stdout, stderr = ssh.exec_command(f'k3s kubectl exec -n dns deployment/technitium-dns -- curl -s {url}') +print("SSO set result:", stdout.read().decode()) + +# Check get after set +stdin, stdout, stderr = ssh.exec_command(f'k3s kubectl exec -n dns deployment/technitium-dns -- curl -s "http://localhost:5380/api/admin/sso/get?token={token}"') +print("SSO get after set:", stdout.read().decode()) + +ssh.close() diff --git a/scripts/test_sso_forwarded.py b/scripts/test_sso_forwarded.py new file mode 100644 index 0000000..5eaeed8 --- /dev/null +++ b/scripts/test_sso_forwarded.py @@ -0,0 +1,16 @@ +import paramiko + +ssh = paramiko.SSHClient() +ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) +ssh.connect('140.44.4.11', username='root', key_filename=r'C:\Users\Snarf\.ssh\id_ed25519_snarfnet', timeout=10) + +stdin, stdout, stderr = ssh.exec_command('k3s kubectl exec -n dns deployment/technitium-dns -- curl -s -v -H "Host: dns1.snarfnet.net" -H "X-Forwarded-Proto: https" "http://localhost:5380/sso/login"') +print("SSO login with Host and X-Forwarded-Proto:") +print(stderr.read().decode('utf-8', errors='ignore')) + +# Also check index.html with Host: dns1.snarfnet.net +stdin, stdout, stderr = ssh.exec_command('k3s kubectl exec -n dns deployment/technitium-dns -- curl -s -H "Host: dns1.snarfnet.net" "http://localhost:5380/" | grep -C 5 "divLoginSso"') +print("divLoginSso check:") +print(stdout.read().decode('utf-8', errors='ignore')) + +ssh.close() diff --git a/scripts/test_sso_get.py b/scripts/test_sso_get.py new file mode 100644 index 0000000..6f80b37 --- /dev/null +++ b/scripts/test_sso_get.py @@ -0,0 +1,13 @@ +import paramiko + +ssh = paramiko.SSHClient() +ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) +ssh.connect('140.44.4.11', username='root', key_filename=r'C:\Users\Snarf\.ssh\id_ed25519_snarfnet', timeout=10) + +# GET sso/login +stdin, stdout, stderr = ssh.exec_command('k3s kubectl exec -n dns deployment/technitium-dns -- curl -s -v -L "http://localhost:5380/sso/login"') +print("SSO login GET:") +print(stderr.read().decode('utf-8', errors='ignore')) +print(stdout.read().decode('utf-8', errors='ignore')[:1000]) + +ssh.close() diff --git a/scripts/test_technitium_auth_status.py b/scripts/test_technitium_auth_status.py new file mode 100644 index 0000000..a41b4e3 --- /dev/null +++ b/scripts/test_technitium_auth_status.py @@ -0,0 +1,18 @@ +import urllib.request +import json +import sys + +sys.stdout.reconfigure(encoding='utf-8') + +for host, label in [('140.44.4.36', 'Aerodrome (dns1)'), ('140.44.4.65', 'Spaceport (dns2)')]: + print(f"=== Testing {label} ({host}:5380) ===") + for pwd in ['snarfnetadmin', 'admin', 'password']: + url = f"http://{host}:5380/api/user/login?user=admin&pass={pwd}" + try: + with urllib.request.urlopen(url, timeout=4) as resp: + data = json.loads(resp.read().decode('utf-8')) + tok = data.get('token') + tok_str = tok[:10] if tok else None + print(f" pass={pwd}: status={data.get('status')}, msg={data.get('errorMessage')}, token={tok_str}") + except Exception as e: + print(f" pass={pwd}: error={e}") diff --git a/scripts/test_technitium_sso_set.py b/scripts/test_technitium_sso_set.py new file mode 100644 index 0000000..efd038a --- /dev/null +++ b/scripts/test_technitium_sso_set.py @@ -0,0 +1,38 @@ +import paramiko +import json + +ssh = paramiko.SSHClient() +ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) +ssh.connect('140.44.4.11', username='root', key_filename=r'C:\Users\Snarf\.ssh\id_ed25519_snarfnet', timeout=10) + +# Get fresh token +stdin, stdout, stderr = ssh.exec_command('k3s kubectl exec -n dns deployment/technitium-dns -- curl -s "http://localhost:5380/api/user/login?user=admin&pass=snarfnetadmin"') +data = json.loads(stdout.read().decode()) +token = data['token'] +print("Fresh token:", token) + +# Now call api/admin/sso/get +stdin, stdout, stderr = ssh.exec_command(f'k3s kubectl exec -n dns deployment/technitium-dns -- curl -s "http://localhost:5380/api/admin/sso/get?token={token}"') +get_res = stdout.read().decode() +print("SSO get:", get_res) + +# Now call api/admin/sso/set +url = ( + f'"http://localhost:5380/api/admin/sso/set?token={token}' + '&ssoEnabled=true' + '&ssoClientId=technitium-dns-oidc' + '&ssoClientSecret=SnarfNetTechnitiumOIDC2026!' + '&ssoMetadataAddress=https://authentik.snarfnet.net/application/o/dns1/.well-known/openid-configuration' + '&ssoScopes=openid,profile,email' + '&ssoAllowSignup=true' + '&ssoAllowSignupOnlyForMappedUsers=false' + '&ssoGroupMap=authentik%20Admins:Administrators"' +) +stdin, stdout, stderr = ssh.exec_command(f'k3s kubectl exec -n dns deployment/technitium-dns -- curl -s {url}') +print("SSO set result:", stdout.read().decode()) + +# Check get again +stdin, stdout, stderr = ssh.exec_command(f'k3s kubectl exec -n dns deployment/technitium-dns -- curl -s "http://localhost:5380/api/admin/sso/get?token={token}"') +print("SSO get after set:", stdout.read().decode()) + +ssh.close() diff --git a/scripts/test_totp_null.py b/scripts/test_totp_null.py new file mode 100644 index 0000000..20506bb --- /dev/null +++ b/scripts/test_totp_null.py @@ -0,0 +1,11 @@ +import paramiko + +ssh = paramiko.SSHClient() +ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) +ssh.connect('140.44.4.41', username='root', key_filename=r'C:\Users\Snarf\.ssh\id_ed25519_snarfnet', timeout=10) + +stdin, stdout, stderr = ssh.exec_command('k3s kubectl exec -n dns deployment/pihole -- curl -s -X POST "http://localhost/api/auth" -H "Content-Type: application/json" -d \'{"password":"snarfnetadmin","totp":null}\'') +print("Auth with totp null:") +print(stdout.read().decode()) + +ssh.close() diff --git a/scripts/update_npm_dns_entries.py b/scripts/update_npm_dns_entries.py new file mode 100644 index 0000000..36d9609 --- /dev/null +++ b/scripts/update_npm_dns_entries.py @@ -0,0 +1,208 @@ +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) + +print("1. Creating safe backups of database and config files...") +backup_cmd = """ +cp /var/lib/docker/volumes/nginx-proxy-manager_nginx-data/_data/database.sqlite /var/lib/docker/volumes/nginx-proxy-manager_nginx-data/_data/database.sqlite.bak.dns +cp -r /var/lib/docker/volumes/nginx-proxy-manager_nginx-data/_data/nginx/proxy_host /var/lib/docker/volumes/nginx-proxy-manager_nginx-data/_data/nginx/proxy_host_bak_dns +""" +stdin, stdout, stderr = c.exec_command(backup_cmd) +stdout.channel.recv_exit_status() +print("Backups created successfully.") + +print("2. Updating database.sqlite...") +py_script = """ +import sqlite3, json, datetime + +conn = sqlite3.connect("/var/lib/docker/volumes/nginx-proxy-manager_nginx-data/_data/database.sqlite") +cur = conn.cursor() +now = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") + +updates = [ + (3, json.dumps(["pihole.snarfnet.net"]), "140.44.4.34", 443, "https"), + (43, json.dumps(["pihole2.snarfnet.net"]), "140.44.4.62", 443, "https"), + (51, json.dumps(["pihole3.snarfnet.net"]), "140.44.4.62", 443, "https"), + (52, json.dumps(["dns1.snarfnet.net"]), "140.44.4.34", 443, "https"), + (53, json.dumps(["dns2.snarfnet.net"]), "140.44.4.62", 443, "https"), +] + +for pid, dnames, host, port, scheme in updates: + cur.execute('''UPDATE proxy_host + SET domain_names = ?, forward_host = ?, forward_port = ?, forward_scheme = ?, + certificate_id = 1, ssl_forced = 1, allow_websocket_upgrade = 1, http2_support = 1, + hsts_enabled = 1, hsts_subdomains = 1, advanced_config = "", modified_on = ? + WHERE id = ?''', (dnames, host, port, scheme, now, pid)) + print(f"Updated proxy host {pid} -> {host}:{port} ({scheme})") + +cur.execute("SELECT id FROM proxy_host WHERE id = 54") +if not cur.fetchone(): + cur.execute('''INSERT INTO proxy_host ( + id, created_on, modified_on, owner_user_id, is_deleted, + domain_names, forward_host, forward_port, access_list_id, + certificate_id, ssl_forced, caching_enabled, block_exploits, + advanced_config, meta, allow_websocket_upgrade, http2_support, + forward_scheme, enabled, locations, hsts_enabled, hsts_subdomains, + trust_forwarded_proto + ) VALUES ( + 54, ?, ?, 1, 0, + ?, "140.44.4.34", 443, 0, + 1, 1, 0, 1, + "", "{\\"nginx_online\\":true,\\"nginx_err\\":null}", 1, 1, + "https", 1, "[]", 1, 1, + 0 + )''', (now, now, json.dumps(["technitium.snarfnet.net"]))) + print("Inserted proxy host 54: technitium.snarfnet.net -> 140.44.4.34:443") +else: + cur.execute('''UPDATE proxy_host + SET domain_names = ?, forward_host = "140.44.4.34", forward_port = 443, forward_scheme = "https", + certificate_id = 1, ssl_forced = 1, allow_websocket_upgrade = 1, http2_support = 1, + hsts_enabled = 1, hsts_subdomains = 1, modified_on = ? + WHERE id = 54''', (json.dumps(["technitium.snarfnet.net"]), now)) + print("Updated proxy host 54: technitium.snarfnet.net") + +cur.execute("SELECT id FROM proxy_host WHERE id = 55") +if not cur.fetchone(): + cur.execute('''INSERT INTO proxy_host ( + id, created_on, modified_on, owner_user_id, is_deleted, + domain_names, forward_host, forward_port, access_list_id, + certificate_id, ssl_forced, caching_enabled, block_exploits, + advanced_config, meta, allow_websocket_upgrade, http2_support, + forward_scheme, enabled, locations, hsts_enabled, hsts_subdomains, + trust_forwarded_proto + ) VALUES ( + 55, ?, ?, 1, 0, + ?, "140.44.4.34", 443, 0, + 1, 1, 0, 1, + "", "{\\"nginx_online\\":true,\\"nginx_err\\":null}", 1, 1, + "https", 1, "[]", 1, 1, + 0 + )''', (now, now, json.dumps(["pihole1.snarfnet.net"]))) + print("Inserted proxy host 55: pihole1.snarfnet.net -> 140.44.4.34:443") +else: + cur.execute('''UPDATE proxy_host + SET domain_names = ?, forward_host = "140.44.4.34", forward_port = 443, forward_scheme = "https", + certificate_id = 1, ssl_forced = 1, allow_websocket_upgrade = 1, http2_support = 1, + hsts_enabled = 1, hsts_subdomains = 1, modified_on = ? + WHERE id = 55''', (json.dumps(["pihole1.snarfnet.net"]), now)) + print("Updated proxy host 55: pihole1.snarfnet.net") + +conn.commit() +conn.close() +""" + +stdin, stdout, stderr = c.exec_command(f"python3 -c '{py_script}'") +print(stdout.read().decode('utf-8')) +err = stderr.read().decode('utf-8') +if err: print("DB Update Err:", err) + +print("3. Generating Nginx configuration files in proxy_host/...") + +conf_template = """# ------------------------------------------------------------ +# {domains} +# ------------------------------------------------------------ + +map $scheme $hsts_header {{ + https "max-age=63072000;includeSubDomains; preload"; +}} + +server {{ + set $forward_scheme {scheme}; + set $server "{host}"; + set $port {port}; + + listen 80; + listen [::]:80; + + listen 443 ssl; + listen [::]:443 ssl; + + server_name {domains}; + http2 on; + + # Let's Encrypt SSL + include conf.d/include/letsencrypt-acme-challenge.conf; + include conf.d/include/ssl-cache.conf; + include conf.d/include/ssl-ciphers.conf; + ssl_certificate /etc/letsencrypt/live/npm-1/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/npm-1/privkey.pem; + + # Asset Caching + include conf.d/include/assets.conf; + + # Block Exploits + include conf.d/include/block-exploits.conf; + + # HSTS (ngx_http_headers_module is required) (63072000 seconds = 2 years) + add_header Strict-Transport-Security $hsts_header always; + + # Force SSL + set $trust_forwarded_proto "F"; + include conf.d/include/force-ssl.conf; + + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $http_connection; + proxy_http_version 1.1; + + access_log /data/logs/proxy-host-{pid}_access.log proxy; + error_log /data/logs/proxy-host-{pid}_error.log warn; + + location / {{ + # HSTS (ngx_http_headers_module is required) (63072000 seconds = 2 years) + add_header Strict-Transport-Security $hsts_header always; + + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $http_connection; + proxy_http_version 1.1; + + # Proxy! + include conf.d/include/proxy.conf; + }} + + # Custom + include /data/nginx/custom/server_proxy[.]conf; +}} +""" + +configs = [ + (3, "pihole.snarfnet.net", "140.44.4.34", 443, "https"), + (43, "pihole2.snarfnet.net", "140.44.4.62", 443, "https"), + (51, "pihole3.snarfnet.net", "140.44.4.62", 443, "https"), + (52, "dns1.snarfnet.net", "140.44.4.34", 443, "https"), + (53, "dns2.snarfnet.net", "140.44.4.62", 443, "https"), + (54, "technitium.snarfnet.net", "140.44.4.34", 443, "https"), + (55, "pihole1.snarfnet.net", "140.44.4.34", 443, "https"), +] + +for pid, dom, host, port, scheme in configs: + cfg = conf_template.format(pid=pid, domains=dom, host=host, port=port, scheme=scheme) + remote_path = f"/var/lib/docker/volumes/nginx-proxy-manager_nginx-data/_data/nginx/proxy_host/{pid}.conf" + cmd_write = f"cat << 'EOF' > {remote_path}\n{cfg}\nEOF\n" + stdin, stdout, stderr = c.exec_command(cmd_write) + stdout.channel.recv_exit_status() + print(f"Wrote {remote_path} ({dom} -> {host}:{port})") + +print("\n4. Testing Nginx configuration syntax inside container...") +test_cmd = "docker exec $(docker ps -q -f name=nginx-proxy-manager_app | head -n1) nginx -t" +stdin, stdout, stderr = c.exec_command(test_cmd) +test_out = stdout.read().decode('utf-8') +test_err = stderr.read().decode('utf-8') +print("nginx -t stdout:", test_out) +print("nginx -t stderr:", test_err) + +if "successful" in test_err or "successful" in test_out: + print("\n5. Reloading Nginx across Swarm service...") + reload_cmd = "docker service update --force nginx-proxy-manager_app" + stdin, stdout, stderr = c.exec_command(reload_cmd) + print(stdout.read().decode('utf-8')) + print("NPM service reloaded successfully!") +else: + print("\nWARNING: Nginx test failed! Please check configuration.") + +c.close() diff --git a/scripts/verify_npm_live.py b/scripts/verify_npm_live.py new file mode 100644 index 0000000..9c74418 --- /dev/null +++ b/scripts/verify_npm_live.py @@ -0,0 +1,58 @@ +import ssl +import socket +import sys + +sys.stdout.reconfigure(encoding='utf-8') + +active_hosts = [ + 'pihole1.snarfnet.net', + 'pihole2.snarfnet.net', + 'dns1.snarfnet.net', + 'dns2.snarfnet.net', +] + +removed_hosts = [ + 'pihole.snarfnet.net', + 'pihole3.snarfnet.net', + 'technitium.snarfnet.net', +] + +npm_vip = '140.44.4.70' + +ctx = ssl.create_default_context() +ctx.check_hostname = False +ctx.verify_mode = ssl.CERT_NONE + +print(f"=== Active Endpoints via NPM VIP ({npm_vip}:443) ===") +for host in active_hosts: + path = '/admin/' if 'pihole' in host else '/' + try: + sock = socket.create_connection((npm_vip, 443), timeout=5) + ssock = ctx.wrap_socket(sock, server_hostname=host) + req = f"GET {path} HTTP/1.1\r\nHost: {host}\r\nUser-Agent: Mozilla/5.0\r\nConnection: close\r\n\r\n" + ssock.sendall(req.encode('utf-8')) + resp = ssock.recv(4096).decode('utf-8', errors='replace') + ssock.close() + + status_line = resp.splitlines()[0] if resp else "No response" + loc = [l for l in resp.splitlines() if 'location:' in l.lower()] + loc_str = loc[0][:75] + "..." if loc else "" + print(f" ✓ {host:25} -> {status_line:15} {loc_str}") + except Exception as e: + print(f" ✗ {host:25} -> ERROR: {e}") + +print(f"\n=== Removed Endpoints via NPM VIP ({npm_vip}:443) ===") +for host in removed_hosts: + path = '/admin/' if 'pihole' in host else '/' + try: + sock = socket.create_connection((npm_vip, 443), timeout=5) + ssock = ctx.wrap_socket(sock, server_hostname=host) + req = f"GET {path} HTTP/1.1\r\nHost: {host}\r\nUser-Agent: Mozilla/5.0\r\nConnection: close\r\n\r\n" + ssock.sendall(req.encode('utf-8')) + resp = ssock.recv(4096).decode('utf-8', errors='replace') + ssock.close() + + status_line = resp.splitlines()[0] if resp else "No response" + print(f" - {host:25} -> {status_line:15} (Default host/unrouted)") + except Exception as e: + print(f" - {host:25} -> {e}")