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']}")