442 lines
17 KiB
Python
442 lines
17 KiB
Python
r"""Customize Authentik application dashboard with high-resolution icons, groups, and launch URLs.
|
|
|
|
Full homelab catalog covering all active services across:
|
|
- CI/CD & Automation
|
|
- Developer Tools
|
|
- Infrastructure & Hypervisors
|
|
- Storage & Media
|
|
- Smart Home & IoT
|
|
- Observability & Operations
|
|
- Networking & Edge
|
|
- Hardware & Server BMC
|
|
- Portals & Dashboards
|
|
"""
|
|
|
|
import json
|
|
import ssl
|
|
import sys
|
|
import urllib.error
|
|
import urllib.request
|
|
|
|
ctx = ssl.create_default_context()
|
|
ctx.check_hostname = False
|
|
ctx.verify_mode = ssl.CERT_NONE
|
|
|
|
AK_BASE = "https://authentik.snarfnet.net"
|
|
AK_TOKEN = "SnarfNetPermanentAutomationToken2026"
|
|
|
|
APPLICATIONS = [
|
|
# CI/CD & Automation
|
|
{
|
|
"slug": "argo-cd",
|
|
"name": "Argo CD",
|
|
"group": "CI/CD & Automation",
|
|
"meta_icon": "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/argo-cd.png",
|
|
"meta_description": "Declarative GitOps Continuous Delivery",
|
|
"meta_launch_url": "https://argo.snarfnet.net",
|
|
"open_in_new_tab": True,
|
|
},
|
|
{
|
|
"slug": "awx",
|
|
"name": "Ansible AWX",
|
|
"group": "CI/CD & Automation",
|
|
"meta_icon": "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/ansible.png",
|
|
"meta_description": "Ansible Automation Controller & Playbook Execution",
|
|
"meta_launch_url": "https://awx.snarfnet.net",
|
|
"open_in_new_tab": True,
|
|
},
|
|
{
|
|
"slug": "jenkins",
|
|
"name": "Jenkins",
|
|
"group": "CI/CD & Automation",
|
|
"meta_icon": "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/jenkins.png",
|
|
"meta_description": "Continuous Integration & Build Server",
|
|
"meta_launch_url": "https://jenkins.snarfnet.net",
|
|
"open_in_new_tab": True,
|
|
},
|
|
{
|
|
"slug": "semaphore",
|
|
"name": "Semaphore UI",
|
|
"group": "CI/CD & Automation",
|
|
"meta_icon": "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/semaphore.png",
|
|
"meta_description": "Modern Web UI for Ansible & Task Automation",
|
|
"meta_launch_url": "https://semaphore.snarfnet.net",
|
|
"open_in_new_tab": True,
|
|
},
|
|
|
|
# Developer Tools
|
|
{
|
|
"slug": "coder",
|
|
"name": "Coder",
|
|
"group": "Developer Tools",
|
|
"meta_icon": "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/coder.png",
|
|
"meta_description": "Self-Hosted Cloud Development Environments",
|
|
"meta_launch_url": "https://coder.snarfnet.net",
|
|
"open_in_new_tab": True,
|
|
},
|
|
{
|
|
"slug": "gitea",
|
|
"name": "Gitea",
|
|
"group": "Developer Tools",
|
|
"meta_icon": "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/gitea.png",
|
|
"meta_description": "Git Version Control & Repository Hosting",
|
|
"meta_launch_url": "https://git.snarfnet.net",
|
|
"open_in_new_tab": True,
|
|
},
|
|
{
|
|
"slug": "vscode",
|
|
"name": "VS Code Server",
|
|
"group": "Developer Tools",
|
|
"meta_icon": "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/vscode.png",
|
|
"meta_description": "Browser-Based Code Editor & Remote Terminal",
|
|
"meta_launch_url": "http://code.snarfnet.net",
|
|
"open_in_new_tab": True,
|
|
},
|
|
|
|
# Infrastructure & Hypervisors
|
|
{
|
|
"slug": "rancher",
|
|
"name": "Rancher",
|
|
"group": "Infrastructure & Hypervisors",
|
|
"meta_icon": "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/rancher.png",
|
|
"meta_description": "Enterprise Multi-Cluster Kubernetes Management",
|
|
"meta_launch_url": "https://aerodrome.snarfnet.net",
|
|
"open_in_new_tab": True,
|
|
},
|
|
{
|
|
"slug": "harvester",
|
|
"name": "Harvester HCI",
|
|
"group": "Infrastructure & Hypervisors",
|
|
"meta_icon": "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/harvester.png",
|
|
"meta_description": "Hyperconverged Infrastructure & Virtualization",
|
|
"meta_launch_url": "https://harvester.spacedock.snarfnet.net",
|
|
"open_in_new_tab": True,
|
|
},
|
|
{
|
|
"slug": "portainer",
|
|
"name": "Portainer EE",
|
|
"group": "Infrastructure & Hypervisors",
|
|
"meta_icon": "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/portainer.png",
|
|
"meta_description": "Docker Swarm & Container Management (OAuth Active)",
|
|
"meta_launch_url": "https://shipyard.snarfnet.net",
|
|
"open_in_new_tab": True,
|
|
},
|
|
{
|
|
"slug": "meshcentral",
|
|
"name": "MeshCentral",
|
|
"group": "Infrastructure & Hypervisors",
|
|
"meta_icon": "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/meshcentral.png",
|
|
"meta_description": "Remote Device Management & Web KVM",
|
|
"meta_launch_url": "https://mesh.snarfnet.net",
|
|
"open_in_new_tab": True,
|
|
},
|
|
{
|
|
"slug": "longhorn-aerodrome",
|
|
"name": "Longhorn (Aerodrome)",
|
|
"group": "Infrastructure & Hypervisors",
|
|
"meta_icon": "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/longhorn.png",
|
|
"meta_description": "Distributed Block Storage (Aerodrome Cluster)",
|
|
"meta_launch_url": "https://longhorn.aerodrome.snarfnet.net",
|
|
"open_in_new_tab": True,
|
|
},
|
|
{
|
|
"slug": "longhorn-spaceport",
|
|
"name": "Longhorn (Spaceport)",
|
|
"group": "Infrastructure & Hypervisors",
|
|
"meta_icon": "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/longhorn.png",
|
|
"meta_description": "Distributed Block Storage (Spaceport Cluster)",
|
|
"meta_launch_url": "https://longhorn.spaceport.snarfnet.net",
|
|
"open_in_new_tab": True,
|
|
},
|
|
|
|
# Storage & Media
|
|
{
|
|
"slug": "snarfnas",
|
|
"name": "Synology GA (SnarfNAS)",
|
|
"group": "Storage & Media",
|
|
"meta_icon": "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/synology.png",
|
|
"meta_description": "Primary Network Attached Storage (DSM 7)",
|
|
"meta_launch_url": "https://snarfnas.snarfnet.net",
|
|
"open_in_new_tab": True,
|
|
},
|
|
{
|
|
"slug": "sn-nas",
|
|
"name": "Synology SC (SN-NAS)",
|
|
"group": "Storage & Media",
|
|
"meta_icon": "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/synology.png",
|
|
"meta_description": "Secondary Storage & Backup NAS (DSM 7)",
|
|
"meta_launch_url": "https://sn-nas.snarfnet.net",
|
|
"open_in_new_tab": True,
|
|
},
|
|
{
|
|
"slug": "plex",
|
|
"name": "Plex Media Server",
|
|
"group": "Storage & Media",
|
|
"meta_icon": "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/plex.png",
|
|
"meta_description": "Home Streaming Media, Movies & TV Shows",
|
|
"meta_launch_url": "https://plex.snarfnet.net",
|
|
"open_in_new_tab": True,
|
|
},
|
|
{
|
|
"slug": "kavita",
|
|
"name": "Kavita",
|
|
"group": "Storage & Media",
|
|
"meta_icon": "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/kavita.png",
|
|
"meta_description": "Digital Comic, Manga & Book Library",
|
|
"meta_launch_url": "https://kavita.snarfnet.net",
|
|
"open_in_new_tab": True,
|
|
},
|
|
|
|
# Smart Home & IoT
|
|
{
|
|
"slug": "home-assistant",
|
|
"name": "Home Assistant",
|
|
"group": "Smart Home & IoT",
|
|
"meta_icon": "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/home-assistant.png",
|
|
"meta_description": "Smart Home Automation Hub & Sensors",
|
|
"meta_launch_url": "https://snarfhome.snarfnet.net",
|
|
"open_in_new_tab": True,
|
|
},
|
|
{
|
|
"slug": "wled",
|
|
"name": "WLED Lights",
|
|
"group": "Smart Home & IoT",
|
|
"meta_icon": "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/wled.png",
|
|
"meta_description": "Office Addressable LED Lighting Controller",
|
|
"meta_launch_url": "https://lights.snarfnet.net",
|
|
"open_in_new_tab": True,
|
|
},
|
|
|
|
# Observability & Operations
|
|
{
|
|
"slug": "grafana",
|
|
"name": "Grafana",
|
|
"group": "Observability & Operations",
|
|
"meta_icon": "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/grafana.png",
|
|
"meta_description": "Dashboards, Metrics & Visualizations",
|
|
"meta_launch_url": "https://grafana.snarfnet.net",
|
|
"open_in_new_tab": True,
|
|
},
|
|
{
|
|
"slug": "uptime-kuma",
|
|
"name": "Uptime Kuma",
|
|
"group": "Observability & Operations",
|
|
"meta_icon": "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/uptime-kuma.png",
|
|
"meta_description": "Service Uptime & Status Monitoring",
|
|
"meta_launch_url": "https://uptime.snarfnet.net",
|
|
"open_in_new_tab": True,
|
|
},
|
|
{
|
|
"slug": "zabbix",
|
|
"name": "Zabbix",
|
|
"group": "Observability & Operations",
|
|
"meta_icon": "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/zabbix.png",
|
|
"meta_description": "Enterprise Network & Infrastructure Monitoring",
|
|
"meta_launch_url": "https://zabbix.snarfnet.net",
|
|
"open_in_new_tab": True,
|
|
},
|
|
{
|
|
"slug": "peanut",
|
|
"name": "PeaNUT",
|
|
"group": "Observability & Operations",
|
|
"meta_icon": "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/nut.png",
|
|
"meta_description": "UPS Power & Server Closet Battery Monitoring",
|
|
"meta_launch_url": "https://peanut.snarfnet.net/device/server-closet",
|
|
"open_in_new_tab": True,
|
|
},
|
|
|
|
# Networking & Edge
|
|
{
|
|
"slug": "pihole",
|
|
"name": "Pi-hole Web",
|
|
"group": "Networking & Edge",
|
|
"meta_icon": "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/pi-hole.png",
|
|
"meta_description": "Network DNS Sinkhole & Ad Blocker",
|
|
"meta_launch_url": "https://pihole3.snarfnet.net",
|
|
"open_in_new_tab": True,
|
|
},
|
|
{
|
|
"slug": "nginx-proxy",
|
|
"name": "Nginx Proxy Manager",
|
|
"group": "Networking & Edge",
|
|
"meta_icon": "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/nginx-proxy-manager.png",
|
|
"meta_description": "Edge Reverse Proxy & SSL Certificate Admin",
|
|
"meta_launch_url": "https://nginx.snarfnet.net",
|
|
"open_in_new_tab": True,
|
|
},
|
|
{
|
|
"slug": "gotify",
|
|
"name": "Gotify",
|
|
"group": "Networking & Edge",
|
|
"meta_icon": "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/gotify.png",
|
|
"meta_description": "Self-Hosted Push Notification Server",
|
|
"meta_launch_url": "https://gotify.snarfnet.net",
|
|
"open_in_new_tab": True,
|
|
},
|
|
{
|
|
"slug": "unifi",
|
|
"name": "UniFi Cloud",
|
|
"group": "Networking & Edge",
|
|
"meta_icon": "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/unifi.png",
|
|
"meta_description": "Ubiquiti UniFi Network & Gateway Controller",
|
|
"meta_launch_url": "https://unifi.ui.com",
|
|
"open_in_new_tab": True,
|
|
},
|
|
|
|
# Hardware & Server BMC
|
|
{
|
|
"slug": "idrac-1",
|
|
"name": "iDRAC 1 (Spacedock 1)",
|
|
"group": "Hardware & Server BMC",
|
|
"meta_icon": "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/dell.png",
|
|
"meta_description": "Dell PowerEdge Server 1 Out-of-Band BMC",
|
|
"meta_launch_url": "https://spacedock1.snarfnet.net",
|
|
"open_in_new_tab": True,
|
|
},
|
|
{
|
|
"slug": "idrac-2",
|
|
"name": "iDRAC 2 (Spacedock 2)",
|
|
"group": "Hardware & Server BMC",
|
|
"meta_icon": "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/dell.png",
|
|
"meta_description": "Dell PowerEdge Server 2 Out-of-Band BMC",
|
|
"meta_launch_url": "https://spacedock2.snarfnet.net",
|
|
"open_in_new_tab": True,
|
|
},
|
|
{
|
|
"slug": "idrac-3",
|
|
"name": "iDRAC 3 (Spacedock 3)",
|
|
"group": "Hardware & Server BMC",
|
|
"meta_icon": "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/dell.png",
|
|
"meta_description": "Dell PowerEdge Server 3 Out-of-Band BMC",
|
|
"meta_launch_url": "https://spacedock3.snarfnet.net",
|
|
"open_in_new_tab": True,
|
|
},
|
|
{
|
|
"slug": "idrac-4",
|
|
"name": "iDRAC 4 (Spacedock 4)",
|
|
"group": "Hardware & Server BMC",
|
|
"meta_icon": "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/dell.png",
|
|
"meta_description": "Dell PowerEdge Server 4 Out-of-Band BMC",
|
|
"meta_launch_url": "https://spacedock4.snarfnet.net",
|
|
"open_in_new_tab": True,
|
|
},
|
|
|
|
# Portals & Dashboards
|
|
{
|
|
"slug": "snarfnet-homelab",
|
|
"name": "Homepage",
|
|
"group": "Portals & Dashboards",
|
|
"meta_icon": "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/homepage.png",
|
|
"meta_description": "SnarfNet Homelab Central Portal",
|
|
"meta_launch_url": "https://homepage.snarfnet.net",
|
|
"open_in_new_tab": True,
|
|
},
|
|
{
|
|
"slug": "homebox",
|
|
"name": "Homebox",
|
|
"group": "Portals & Dashboards",
|
|
"meta_icon": "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/homebox.png",
|
|
"meta_description": "Physical Asset & Inventory Management",
|
|
"meta_launch_url": "https://homebox.snarfnet.net",
|
|
"open_in_new_tab": True,
|
|
},
|
|
{
|
|
"slug": "dakboard",
|
|
"name": "Dakboard Display",
|
|
"group": "Portals & Dashboards",
|
|
"meta_icon": "https://dakboard.com/favicon.ico",
|
|
"meta_description": "Custom Digital Wall Calendar & Display",
|
|
"meta_launch_url": "https://dakboard.com/site",
|
|
"open_in_new_tab": True,
|
|
},
|
|
]
|
|
|
|
|
|
def ak_request(endpoint, method="GET", data=None):
|
|
url = f"{AK_BASE.rstrip('/')}/{endpoint.lstrip('/')}"
|
|
body = json.dumps(data).encode("utf-8") if data else None
|
|
headers = {
|
|
"Authorization": f"Bearer {AK_TOKEN}",
|
|
"Content-Type": "application/json",
|
|
"Accept": "application/json",
|
|
}
|
|
req = urllib.request.Request(url, data=body, headers=headers, method=method)
|
|
try:
|
|
with urllib.request.urlopen(req, timeout=10, context=ctx) as r:
|
|
res_body = r.read().decode("utf-8")
|
|
return r.getcode(), json.loads(res_body) if res_body else {}
|
|
except urllib.error.HTTPError as e:
|
|
err_msg = e.read().decode("utf-8")
|
|
return e.code, err_msg
|
|
|
|
|
|
def fetch_all_apps():
|
|
apps = []
|
|
page = 1
|
|
while True:
|
|
code, res = ak_request(f"/api/v3/core/applications/?page={page}")
|
|
if code != 200 or not res.get("results"):
|
|
break
|
|
apps.extend(res["results"])
|
|
if not res.get("pagination", {}).get("next"):
|
|
break
|
|
page += 1
|
|
return {a["slug"]: a for a in apps}
|
|
|
|
|
|
def main():
|
|
print("=" * 60, flush=True)
|
|
print("SnarfNet Authentik Application Customizer (Full Catalog)", flush=True)
|
|
print("=" * 60, flush=True)
|
|
|
|
# 1. Fetch current applications
|
|
existing_apps = fetch_all_apps()
|
|
print(f"Found {len(existing_apps)} existing applications in Authentik.\n", flush=True)
|
|
|
|
for app in APPLICATIONS:
|
|
slug = app["slug"]
|
|
if slug in existing_apps:
|
|
# Preserve existing provider if assigned
|
|
patch_data = {
|
|
"name": app["name"],
|
|
"group": app["group"],
|
|
"meta_icon": app["meta_icon"],
|
|
"meta_description": app["meta_description"],
|
|
"meta_launch_url": app["meta_launch_url"],
|
|
"open_in_new_tab": app["open_in_new_tab"],
|
|
}
|
|
code, r = ak_request(f"/api/v3/core/applications/{slug}/", method="PATCH", data=patch_data)
|
|
if code == 200:
|
|
print(f"[UPDATED] {slug:20} -> {app['name']:28} ({app['group']})", flush=True)
|
|
else:
|
|
print(f"[FAIL] {slug:20} -> Error {code}: {r}", flush=True)
|
|
else:
|
|
# POST new application without provider (standalone launcher)
|
|
post_data = {
|
|
"name": app["name"],
|
|
"slug": app["slug"],
|
|
"group": app["group"],
|
|
"meta_icon": app["meta_icon"],
|
|
"meta_description": app["meta_description"],
|
|
"meta_launch_url": app["meta_launch_url"],
|
|
"open_in_new_tab": app["open_in_new_tab"],
|
|
}
|
|
code, r = ak_request("/api/v3/core/applications/", method="POST", data=post_data)
|
|
if code == 201:
|
|
print(f"[CREATED] {slug:20} -> {app['name']:28} ({app['group']})", flush=True)
|
|
else:
|
|
print(f"[FAIL] {slug:20} -> Error {code}: {r}", flush=True)
|
|
|
|
print("\n" + "=" * 60, flush=True)
|
|
print("Verifying updated applications catalog:", flush=True)
|
|
print("=" * 60, flush=True)
|
|
all_apps = fetch_all_apps()
|
|
for slug, a in sorted(all_apps.items(), key=lambda x: (x[1].get("group") or "", x[1].get("name") or "")):
|
|
prov_name = a.get("provider_obj", {}).get("name") if a.get("provider_obj") else "Direct"
|
|
print(f"[{a.get('group', 'Default'):28}] {a['name']:28} (Auth: {prov_name:18}) -> {a.get('launch_url')}", flush=True)
|
|
print("\nFull catalog update complete!", flush=True)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|