Files
bluejay-infra/apps/intranet/intranet.yaml

141 lines
189 KiB
YAML

apiVersion: v1
kind: Namespace
metadata:
labels:
app.kubernetes.io/part-of: bluejay-infra
name: intranet
---
apiVersion: v1
data:
default.conf: "server {\n listen 80;\n server_name _;\n root /usr/share/nginx/html;\n index index.html;\n\n location / {\n try_files $uri $uri/ =404;\n }\n\n location /healthz {\n access_log off;\n return 200 \"ok\";\n add_header Content-Type text/plain;\n }\n}\n"
kind: ConfigMap
metadata:
name: intranet-nginx-conf
namespace: intranet
---
apiVersion: v1
data:
index.html: "<!DOCTYPE html>\n<html lang=\"en\" data-theme=\"dark\">\n<head>\n<meta charset=\"UTF-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n<title>Blue Jay Lab Intranet - FlowerCore</title>\n<style>\n:root, [data-theme=\"dark\"] {\n --bg: #111b30; --bg-gradient: linear-gradient(180deg, #111b30 0%, #1e2d47 100%);\n --surface: #1e2d47; --surface2: #253751; --surface-hover: #2c4060;\n --border: #364b6b; --border-accent: #8ec8ff;\n --text: #e8edf4; --text-heading: #FFFFFF; --text-muted: #9ab0cc;\n --accent: #8ec8ff; --accent-light: #8ec8ff; --accent-bright: #b3daff;\n --gold: #FFB300; --gold-light: #FFCA40; --gold-dim: #CC8F00;\n --green: #3DB86A; --green-bg: rgba(61,184,106,0.12); --green-border: rgba(61,184,106,0.25);\n --yellow: #FFB300; --yellow-bg: rgba(255,179,0,0.12); --yellow-border: rgba(255,179,0,0.25);\n --red: #E84545; --red-bg: rgba(232,69,69,0.12); --red-border: rgba(232,69,69,0.25);\n --purple: #9B7ED8; --purple-bg: rgba(155,126,216,0.12); --purple-border: rgba(155,126,216,0.25);\n --orange: #E8883E; --cyan: #39D2C0; --pink: #F778BA;\n --navy: #111b30; --royal: #253751;\n --code-bg: #111b30; --code-text: #FFB300;\n --card-shadow: 0 2px 6px rgba(0,0,0,0.35);\n --header-gradient: linear-gradient(135deg, #111b30 0%, #253751 40%, #8ec8ff 100%);\n --header-text-shadow: 0 2px 16px rgba(142,200,255,0.35);\n --summary-border-top: 3px solid var(--gold);\n --note-bg: rgba(142,200,255,0.08); --note-warn-bg: rgba(255,179,0,0.08);\n --topo-bg: #111b30; --topo-border: #364b6b; --topo-text: #8ec8ff;\n --copy-bg: rgba(142,200,255,0.15); --copy-hover: rgba(142,200,255,0.3);\n --zebra: rgba(255,255,255,0.02);\n}\n[data-theme=\"light\"] {\n --bg: #F8FAFE; --bg-gradient: linear-gradient(180deg, #F8FAFE 0%, #EBF0F9 100%);\n --surface: #FFFFFF; --surface2: #EDF2FA; --surface-hover: #DCE5F4;\n --border: #C0D0E5; --border-accent: #2B7FE0;\n --text: #1A2744; --text-heading: #0D1B2E; --text-muted: #5A6F8A;\n --accent: #1565C0; --accent-light: #2B7FE0; --accent-bright: #1565C0;\n --gold: #E6A100; --gold-light: #FFB300; --gold-dim: #B38000;\n --green: #2A8A4A; --green-bg: rgba(42,138,74,0.1); --green-border: rgba(42,138,74,0.25);\n --yellow: #E6A100; --yellow-bg: rgba(230,161,0,0.1); --yellow-border: rgba(230,161,0,0.25);\n --red: #C03030; --red-bg: rgba(192,48,48,0.08); --red-border: rgba(192,48,48,0.2);\n --purple: #6B4FA0; --purple-bg: rgba(107,79,160,0.1); --purple-border: rgba(107,79,160,0.2);\n --orange: #C07020; --cyan: #288A7A; --pink: #C05090;\n --navy: #1A2744; --royal: #1565C0;\n --code-bg: #EDF2FA; --code-text: #C07020;\n --card-shadow: 0 1px 4px rgba(26,39,68,0.1);\n --header-gradient: linear-gradient(135deg, #0D1B2E 0%, #1565C0 40%, #2B7FE0 100%);\n --header-text-shadow: 0 1px 8px rgba(0,0,0,0.2);\n --summary-border-top: 3px solid var(--gold);\n --note-bg: rgba(21,101,192,0.05); --note-warn-bg: rgba(230,161,0,0.05);\n --topo-bg: #EDF2FA; --topo-border: #C0D0E5; --topo-text: #1565C0;\n --copy-bg: rgba(21,101,192,0.08); --copy-hover: rgba(21,101,192,0.15);\n --zebra: rgba(0,0,0,0.02);\n}\n* { box-sizing: border-box; margin: 0; padding: 0; }\nbody { font-family: 'Inter', 'Segoe UI', -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; background: var(--bg); color: var(--text); line-height: 1.65; padding: 2rem; max-width: 1500px; margin: 0 auto; transition: background 0.3s, color 0.3s; }\nh1 { font-size: 2.2rem; margin-bottom: 0.25rem; font-weight: 800; color: var(--text-heading); letter-spacing: 0.5px; }\nh2 { font-size: 1.35rem; color: var(--gold); margin: 2.5rem 0 1rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.5px; border-bottom: 2px solid var(--gold-dim); padding-bottom: 0.5rem; }\nh3 { font-size: 1.05rem; color: var(--purple); margin: 1.5rem 0 0.75rem; font-weight: 700; }\nh4 { font-size: 0.95rem; color: var(--accent-light); margin: 1rem 0 0.5rem; font-weight: 600; }\n.subtitle { color: var(--gold-light); margin-bottom: 0.5rem; font-size: 1.05rem; }\n.last-updated { color: rgba(255,255,255,0.5); font-size: 0.82rem; }\ncode { background: var(--code-bg); padding: 2px 7px; border-radius: 4px; font-size: 0.83rem; color: var(--code-text); border: 1px solid var(--border); font-family: 'Cascadia Code','Fira Code','Consolas',monospace; }\na { color: var(--accent-light); text-decoration: none; }\na:hover { text-decoration: underline; color: var(--accent-bright); }\n.brand-header { background: var(--header-gradient); border: none; border-radius: 10px; padding: 2.5rem 2.5rem 2rem; margin-bottom: 1.5rem; position: relative; overflow: hidden; box-shadow: 0 4px 20px rgba(0,0,0,0.2); }\n.brand-header::before { content: ''; position: absolute; top: -40%; right: -10%; width: 50%; height: 180%; background: radial-gradient(ellipse, rgba(255,255,255,0.04) 0%, transparent 70%); pointer-events: none; }\n.brand-header h1 { color: #fff; text-shadow: var(--header-text-shadow); font-size: 2.4rem; letter-spacing: 1px; text-transform: uppercase; }\n.status-badge { display: inline-block; background: rgba(255,179,0,0.2); border: 1px solid rgba(255,179,0,0.4); color: #FFCA40; padding: 4px 14px; border-radius: 14px; font-size: 0.78rem; font-weight: 700; letter-spacing: 0.5px; text-transform: uppercase; margin-top: 0.5rem; }\n.theme-toggle { position: absolute; top: 1.25rem; right: 1.5rem; background: rgba(255,255,255,0.1); border: 1px solid rgba(255,255,255,0.15); border-radius: 20px; padding: 6px 14px; color: rgba(255,255,255,0.8); cursor: pointer; font-size: 0.78rem; font-weight: 600; letter-spacing: 0.3px; transition: background 0.2s; backdrop-filter: blur(4px); display: flex; align-items: center; gap: 6px; }\n.theme-toggle:hover { background: rgba(255,255,255,0.18); border-color: rgba(255,255,255,0.25); }\n.theme-toggle svg { width: 14px; height: 14px; fill: currentColor; }\nnav { background: var(--surface); border: 1px solid var(--border); border-radius: 8px; padding: 0.75rem 1rem; margin-bottom: 1.5rem; position: sticky; top: 0; z-index: 100; box-shadow: var(--card-shadow); }\nnav ul { list-style: none; display: flex; flex-wrap: wrap; gap: 4px; justify-content: center; }\nnav li { display: inline; }\nnav a { color: var(--accent-light); text-decoration: none; padding: 6px 14px; border-radius: 6px; font-size: 0.8rem; font-weight: 500; transition: all 0.2s; border: 1px solid transparent; cursor: pointer; display: inline-block; }\nnav a:hover { background: var(--surface2); border-color: var(--border); text-decoration: none; color: var(--accent-bright); }\nnav a.active { background: var(--accent); color: #fff; border-color: var(--accent); font-weight: 700; }\n.summary-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: 0.85rem; margin: 1.25rem 0; }\n.summary-card { background: var(--surface); border: 1px solid var(--border); border-top: var(--summary-border-top); border-radius: 8px; padding: 1.25rem; text-align: center; box-shadow: var(--card-shadow); transition: border-color 0.2s, transform 0.15s; }\n.summary-card:hover { border-color: var(--border-accent); transform: translateY(-1px); }\n.summary-number { font-size: 2rem; font-weight: 800; color: var(--accent-light); }\n.summary-label { color: var(--text-muted); font-size: 0.78rem; text-transform: uppercase; letter-spacing: 0.3px; margin-top: 2px; }\n.b { display: inline-block; padding: 2px 10px; border-radius: 10px; font-size: 0.7rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.4px; }\n.b-online { background: var(--green-bg); color: var(--green); border: 1px solid var(--green-border); }\n.b-planned { background: var(--yellow-bg); color: var(--yellow); border: 1px solid var(--yellow-border); }\n.op-link { text-decoration: none; } .op-link:hover .b-planned { background: var(--yellow-border); color: var(--text-heading); cursor: pointer; }\n.b-offline { background: var(--red-bg); color: var(--red); border: 1px solid var(--red-border); }\n.b-mgmt { background: rgba(142,200,255,0.12); color: var(--accent-light); border: 1px solid rgba(142,200,255,0.25); }\n.b-prod { background: var(--green-bg); color: var(--green); border: 1px solid var(--green-border); }\n.b-home { background: var(--purple-bg); color: var(--purple); border: 1px solid var(--purple-border); }\n.b-tenant { background: rgba(232,136,62,0.12); color: var(--orange); border: 1px solid rgba(232,136,62,0.25); }\n.b-wifi { background: rgba(57,210,192,0.12); color: var(--cyan); border: 1px solid rgba(57,210,192,0.25); }\n.b-voip { background: var(--yellow-bg); color: var(--yellow); border: 1px solid var(--yellow-border); }\n.b-spare { background: var(--surface2); color: var(--text-muted); border: 1px solid var(--border); }\n.dot { display: inline-block; width: 8px; height: 8px; border-radius: 50%; margin-right: 6px; vertical-align: middle; }\n.dot-green { background: var(--green); box-shadow: 0 0 4px var(--green); }\n.dot-yellow { background: var(--yellow); box-shadow: 0 0 4px var(--yellow); }\n.dot-gray { background: var(--text-muted); }\n.dot-red { background: var(--red); box-shadow: 0 0 4px var(--red); }\ntable { width: 100%; border-collapse: collapse; margin: 1rem 0; font-size: 0.87rem; }\nth, td { padding: 0.55rem 0.75rem; border: 1px solid var(--border); text-align: left; }\nth { background: var(--surface2); color: var(--text-muted); font-weight: 600; font-size: 0.78rem; text-transform: uppercase; letter-spacing: 0.3px; }\ntr:nth-child(even) { background: var(--zebra); }\ntr:hover { background: rgba(142,200,255,0.05); }\n.ip { font-family: 'Cascadia Code','Fira Code','Consolas',monospace; color: var(--accent-light); font-weight: 600; font-size: 0.85rem; }\n.pw { font-family: 'Cascadia Code','Fira Code','Consolas',monospace; color: var(--orange); font-size: 0.82rem; }\n.card-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); gap: 12px; margin: 1rem 0; }\n.card { background: var(--surface); border: 1px solid var(--border); border-radius:\
\ 8px; padding: 16px; box-shadow: var(--card-shadow); transition: border-color 0.2s; }\n.card:hover { border-color: var(--border-accent); }\n.card-title { font-weight: 700; font-size: 1rem; color: var(--text-heading); margin-bottom: 8px; display: flex; align-items: center; gap: 8px; }\n.card p, .card li { color: var(--text-muted); font-size: 0.85rem; margin: 4px 0; }\n.card ul { list-style: none; padding: 0; }\n.card li { padding: 4px 0; border-bottom: 1px solid var(--border); }\n.card li:last-child { border-bottom: none; }\n.topology { background: var(--topo-bg); border: 2px solid var(--topo-border); border-radius: 8px; padding: 2rem; margin: 1rem 0; overflow-x: auto; }\n.topology pre { font-family: 'Cascadia Code','Fira Code','Consolas',monospace; font-size: 0.82rem; color: var(--topo-text); line-height: 1.5; white-space: pre; }\n.copy-btn { background: var(--copy-bg); border: 1px solid var(--border); border-radius: 4px; color: var(--accent-light); cursor: pointer; padding: 2px 8px; font-size: 0.72rem; font-family: inherit; transition: background 0.2s; margin-left: 6px; vertical-align: middle; }\n.copy-btn:hover { background: var(--copy-hover); }\n.copy-btn.copied { color: var(--green); border-color: var(--green-border); }\n.note { background: var(--note-bg); border-left: 3px solid var(--accent); padding: 0.75rem 1rem; margin: 1rem 0; border-radius: 0 6px 6px 0; font-size: 0.87rem; }\n.note-warn { background: var(--note-warn-bg); border-left-color: var(--yellow); }\n.quick-links { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 8px; margin: 1rem 0; }\n.quick-link { background: var(--surface); border: 1px solid var(--border); border-radius: 6px; padding: 10px 14px; display: flex; align-items: center; gap: 8px; transition: all 0.2s; text-decoration: none; color: var(--text); }\n.quick-link:hover { border-color: var(--accent); background: var(--surface-hover); text-decoration: none; }\n.quick-link .ql-name { font-weight: 600; font-size: 0.85rem; color: var(--text-heading); }\n.quick-link .ql-url { font-size: 0.72rem; color: var(--text-muted); font-family: 'Cascadia Code',monospace; }\n.tab-content { display: none; }\n.tab-content.active { display: block; }\n.wifi-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 16px; margin: 1.5rem 0; }\n.wifi-card { background: var(--surface); border: 1px solid var(--border); border-radius: 10px; overflow: hidden; box-shadow: var(--card-shadow); transition: border-color 0.2s, transform 0.15s; }\n.wifi-card:hover { border-color: var(--border-accent); transform: translateY(-2px); }\n.wifi-card-header { padding: 14px 16px 10px; border-bottom: 3px solid var(--border); }\n.wifi-ssid { font-size: 1.15rem; font-weight: 800; color: var(--text-heading); letter-spacing: 0.3px; }\n.wifi-vlan { margin-top: 4px; }\n.wifi-qr { display: flex; justify-content: center; align-items: center; padding: 16px; background: #ffffff; min-height: 180px; }\n.wifi-qr canvas { border-radius: 4px; }\n.wifi-qr-placeholder { background: var(--surface2) !important; border: 2px dashed var(--border); min-height: 180px; }\n.wifi-qr-placeholder .qr-placeholder-box { display: flex; flex-direction: column; align-items: center; gap: 12px; color: var(--text-muted); padding: 16px; text-align: center; }\n.wifi-qr-placeholder .qr-placeholder-box svg { color: var(--accent); opacity: 0.6; }\n.wifi-qr-placeholder .qr-placeholder-text { font-size: 0.82rem; font-weight: 600; letter-spacing: 0.3px; color: var(--accent-light); }\n.wifi-qr-open { border-color: var(--green-border); }\n.wifi-qr-open .qr-placeholder-box svg { color: var(--green); }\n.wifi-qr-open .qr-placeholder-text { color: var(--green); }\n.wifi-details { padding: 12px 16px 16px; }\n.wifi-field { display: flex; justify-content: space-between; align-items: center; padding: 6px 0; border-bottom: 1px solid var(--border); font-size: 0.85rem; }\n.wifi-field:last-child { border-bottom: none; }\n.wifi-label { color: var(--text-muted); font-weight: 600; font-size: 0.78rem; text-transform: uppercase; letter-spacing: 0.3px; min-width: 80px; }\n.wifi-value { color: var(--text); text-align: right; }\n.print-qr-btn { background: var(--accent); color: #fff; border: none; border-radius: 6px; padding: 8px 18px; font-size: 0.85rem; font-weight: 600; cursor: pointer; margin-left: 12px; transition: background 0.2s; }\n.print-qr-btn:hover { background: var(--accent-light); }\n@media print {\n body { background: #fff !important; color: #000 !important; padding: 0 !important; }\n nav, .brand-header, .theme-toggle, .copy-btn, .print-qr-btn, .note, .tab-content:not(.print-active) { display: none !important; }\n .tab-content.print-active { display: block !important; }\n .wifi-grid { grid-template-columns: repeat(2, 1fr) !important; gap: 20px !important; }\n .wifi-card { break-inside: avoid; border: 2px solid #333 !important; box-shadow: none !important; page-break-inside: avoid; }\n .wifi-card-header { border-bottom-color: #333 !important; }\n .wifi-ssid { color: #000 !important; }\n .wifi-qr { background: #fff !important; padding: 12px !important; }\n .wifi-qr-placeholder { background: #f5f5f5 !important; border-color: #999 !important; }\n .wifi-qr-placeholder .qr-placeholder-box svg { color: #333 !important; }\n .wifi-qr-placeholder .qr-placeholder-text { color: #333 !important; }\n .wifi-details { color: #000 !important; }\n .wifi-field { border-bottom-color: #ccc !important; }\n .wifi-label { color: #555 !important; }\n .wifi-value, .wifi-value code { color: #000 !important; background: #eee !important; }\n .b { border-color: #999 !important; color: #333 !important; background: #eee !important; }\n}\n@media (max-width: 768px) {\n body { padding: 1rem; }\n .brand-header { padding: 1.5rem; }\n .brand-header h1 { font-size: 1.5rem; }\n .summary-grid { grid-template-columns: repeat(3, 1fr); }\n .card-grid { grid-template-columns: 1fr; }\n nav a { font-size: 0.72rem; padding: 4px 8px; }\n .topology pre { font-size: 0.7rem; }\n table { font-size: 0.78rem; }\n th, td { padding: 0.4rem 0.5rem; }\n .quick-links { grid-template-columns: 1fr 1fr; }\n .wifi-grid { grid-template-columns: 1fr; }\n .wifi-qr canvas { width: 140px !important; height: 140px !important; }\n}\n</style>\n</head>\n<body>\n\n<div class=\"brand-header\">\n <button class=\"theme-toggle\" onclick=\"toggleTheme()\" title=\"Switch theme\">\n <svg viewBox=\"0 0 24 24\"><path d=\"M12 3a9 9 0 1 0 9 9c0-.46-.04-.92-.1-1.36a5.389 5.389 0 0 1-4.4 2.26 5.403 5.403 0 0 1-3.14-9.8c-.44-.06-.9-.1-1.36-.1z\"/></svg>\n <span id=\"themeLabel\">Light</span>\n </button>\n <h1>Blue Jay Lab Intranet</h1>\n <p class=\"subtitle\">BlueJay Network Infrastructure &mdash; 13 VLANs | 10 Physical Nodes | RKE2 Bare-Metal Cluster | 4 WiFi SSIDs | 18 Domains | 22 ArgoCD Apps | 41 Namespaces | NAS Storage | 4 Pi Devices | 21,437+ Tests</p>\n <p class=\"last-updated\">Last updated: 2026-03-31 &mdash; BLUEJAY-WS openSUSE Leap 16 workstation live (Agent Zero GPU + Traefik + 14 Ollama models). SSH keys rotated across 8 nodes, 1Password Connect rebuilt. Print.Web +33 tests (CUPS, red stripe). Barcode lookup timeouts (10s/15s). Post-install scripts updated (openSUSE + Windows).</p>\n <div class=\"status-badge\">Network Rebuild 100% Complete &mdash; /28 Fully Live &mdash; 22 ArgoCD Apps &mdash; 4 Pi Fleet Nodes &mdash; 21,437+ Tests Across 13 Services</div>\n</div>\n\n<nav>\n <ul>\n <li><a class=\"tab-btn active\" onclick=\"switchTab('overview',this)\">Overview</a></li>\n <li><a class=\"tab-btn\" onclick=\"switchTab('isp',this)\">ISP &amp; WAN</a></li>\n <li><a class=\"tab-btn\" onclick=\"switchTab('pfsense',this)\">pfSense</a></li>\n <li><a class=\"tab-btn\" onclick=\"switchTab('switching',this)\">Switch &amp; WiFi</a></li>\n <li><a class=\"tab-btn\" onclick=\"switchTab('dns',this)\">DNS Directory</a></li>\n <li><a class=\"tab-btn\" onclick=\"switchTab('k8s',this)\">Kubernetes</a></li>\n <li><a class=\"tab-btn\" onclick=\"switchTab('noc',this)\">NOC Services</a></li>\n <li><a class=\"tab-btn\" onclick=\"switchTab('vpn',this)\">VPN &amp; Security</a></li>\n <li><a class=\"tab-btn\" onclick=\"switchTab('remote',this)\">Remote Access</a></li>\n <li><a class=\"tab-btn\" onclick=\"switchTab('edge',this)\">Edge Nodes</a></li>\n <li><a class=\"tab-btn\" onclick=\"switchTab('storage',this)\">Storage</a></li>\n <li><a class=\"tab-btn\" onclick=\"switchTab('wifi',this)\">WiFi</a></li>\n <li><a class=\"tab-btn\" onclick=\"switchTab('planned',this)\">Planned</a></li>\n <li><a class=\"tab-btn\" onclick=\"switchTab('topology',this)\">Topology</a></li>\n <li><a class=\"tab-btn\" onclick=\"switchTab('domains',this)\">Domains</a></li>\n <li><a class=\"tab-btn\" onclick=\"switchTab('credentials',this)\">Credentials</a></li>\n </ul>\n</nav>\n\n<!-- ===== TAB: OVERVIEW ===== -->\n<div id=\"tab-overview\" class=\"tab-content active\">\n<h2>Overview</h2>\n<div class=\"summary-grid\">\n <div class=\"summary-card\"><div class=\"summary-number\">13</div><div class=\"summary-label\">VLANs</div></div>\n <div class=\"summary-card\"><div class=\"summary-number\">9</div><div class=\"summary-label\">Physical Nodes</div></div>\n <div class=\"summary-card\"><div class=\"summary-number\">3</div><div class=\"summary-label\">RKE2 Nodes</div></div>\n <div class=\"summary-card\"><div class=\"summary-number\">22</div><div class=\"summary-label\">ArgoCD Apps</div></div>\n <div class=\"summary-card\"><div class=\"summary-number\">13</div><div class=\"summary-label\">Zabbix Hosts</div></div>\n <div class=\"summary-card\"><div class=\"summary-number\">4</div><div class=\"summary-label\">Pi Devices</div></div>\n <div class=\"summary-card\"><div class=\"summary-number\">4</div><div class=\"summary-label\">WiFi SSIDs</div></div>\n <div class=\"summary-card\"><div class=\"summary-number\">9</div><div class=\"summary-label\">VPN Servers</div></div>\n\
\ <div class=\"summary-card\"><div class=\"summary-number\">13</div><div class=\"summary-label\">Public IPs</div></div>\n <div class=\"summary-card\"><div class=\"summary-number\">18</div><div class=\"summary-label\">Domains</div></div>\n <div class=\"summary-card\"><div class=\"summary-number\">14</div><div class=\"summary-label\">Guac Connections</div></div>\n <div class=\"summary-card\"><div class=\"summary-number\">9.1 TB</div><div class=\"summary-label\">NAS Storage</div></div>\n</div>\n\n<div class=\"note note-warn\"><strong>Network Status: ALL PHASES COMPLETE + FULLY OPERATIONAL.</strong> K3s on noc1, bare-metal RKE2 on 3 NUCs. /28 FULLY LIVE: 13 VIPs, 30 port forwards, Cloudflare DNS. ArgoCD: 22 apps (via bluejay-infra ApplicationSet). Asterisk PBX: MetalLB .207, 4 PJSIP extensions, Twilio SIP trunk. <strong>Telephony:</strong> 11,081 tests, 236 MCP tools, ~390 endpoints, 67 Blazor pages. <strong>OpenVPN:</strong> 9 servers (4 tun + 4 tap + FIT), Andrew/Matt tested from Mac. Split-tunnel DNS. <strong>Monitoring:</strong> 36 Prometheus scrape jobs, 25 alert rules (5 groups), 12 Grafana dashboards, thermal printer alerting LIVE, IRC alerts. Zabbix: 10+ hosts. <strong>Print.Web:</strong> 530 tests, 21 pages, 15 MCP, HTTPS via noc-proxy. <strong>WiFi Portal:</strong> wifi.flowercore.io (Cloudflare-proxied, K8s). <strong>New K8s workloads:</strong> FlowerCore.Signage (fc-signage), FlowerCore.RemoteDesktop (fc-desktop), WiFi Portal (wifi-portal). 1Password: 7 services wired, 45+ vault items. <strong>Pi Fleet:</strong> 4 devices, PiManager deployed to piez (:5000) + pirelay (:5100). Guacamole: Blue Jay branded, 23 connections (incl. 9 fcadmin), 4 extensions. <strong>Total tests:</strong> 21,437+ across 13 services.</div>\n\n<h3>Quick Links &mdash; Web UIs</h3>\n<div class=\"quick-links\">\n <a class=\"quick-link\" href=\"https://pfsense.iamworkin.lan\" target=\"_blank\"><div><div class=\"ql-name\"><span class=\"dot dot-green\"></span>pfSense</div><div class=\"ql-url\">https://pfsense.iamworkin.lan</div></div></a>\n <a class=\"quick-link\" href=\"https://unifi.iamworkin.lan\" target=\"_blank\"><div><div class=\"ql-name\"><span class=\"dot dot-green\"></span>UniFi Cloud Key</div><div class=\"ql-url\">https://unifi.iamworkin.lan</div></div></a>\n <a class=\"quick-link\" href=\"http://wifi.iamworkin.lan:8000\" target=\"_blank\"><div><div class=\"ql-name\"><span class=\"dot dot-green\"></span>Synology WiFi (SRM)</div><div class=\"ql-url\">http://wifi.iamworkin.lan:8000</div></div></a>\n <a class=\"quick-link\" href=\"https://element.iamworkin.lan\" target=\"_blank\"><div><div class=\"ql-name\"><span class=\"dot dot-green\"></span>Element Web (Matrix)</div><div class=\"ql-url\">https://element.iamworkin.lan</div></div></a>\n <a class=\"quick-link\" href=\"https://cockpit.iamworkin.lan\" target=\"_blank\"><div><div class=\"ql-name\"><span class=\"dot dot-green\"></span>Cockpit (noc1)</div><div class=\"ql-url\">https://cockpit.iamworkin.lan</div></div></a>\n <a class=\"quick-link\" href=\"https://grafana.iamworkin.lan\" target=\"_blank\"><div><div class=\"ql-name\"><span class=\"dot dot-green\"></span>Grafana</div><div class=\"ql-url\">https://grafana.iamworkin.lan</div></div></a>\n <a class=\"quick-link\" href=\"https://prometheus.iamworkin.lan\" target=\"_blank\"><div><div class=\"ql-name\"><span class=\"dot dot-green\"></span>Prometheus</div><div class=\"ql-url\">https://prometheus.iamworkin.lan</div></div></a>\n <a class=\"quick-link\" href=\"https://guac.iamworkin.lan\" target=\"_blank\"><div><div class=\"ql-name\"><span class=\"dot dot-green\"></span>Guacamole</div><div class=\"ql-url\">https://guac.iamworkin.lan</div></div></a>\n <a class=\"quick-link\" href=\"https://pki.iamworkin.lan\" target=\"_blank\"><div><div class=\"ql-name\"><span class=\"dot dot-green\"></span>PKI Web</div><div class=\"ql-url\">https://pki.iamworkin.lan</div></div></a>\n <a class=\"quick-link\" href=\"https://argocd.iamworkin.lan\" target=\"_blank\"><div><div class=\"ql-name\"><span class=\"dot dot-green\"></span>ArgoCD</div><div class=\"ql-url\">https://argocd.iamworkin.lan</div></div></a>\n <a class=\"quick-link\" href=\"https://zabbix.iamworkin.lan\" target=\"_blank\"><div><div class=\"ql-name\"><span class=\"dot dot-green\"></span>Zabbix</div><div class=\"ql-url\">https://zabbix.iamworkin.lan</div></div></a>\n <a class=\"quick-link\" href=\"https://gitea.iamworkin.lan\" target=\"_blank\"><div><div class=\"ql-name\"><span class=\"dot dot-green\"></span>Gitea</div><div class=\"ql-url\">https://gitea.iamworkin.lan</div></div></a>\n <a class=\"quick-link\" href=\"http://192.168.254.254\" target=\"_blank\"><div><div class=\"ql-name\"><span class=\"dot dot-green\"></span>Frontier Modem</div><div class=\"ql-url\">http://192.168.254.254</div></div></a>\n <a class=\"quick-link\" href=\"https://nas.iamworkin.lan:5001\" target=\"_blank\"><div><div class=\"ql-name\"><span class=\"dot dot-green\"></span>BlueJayNAS (DSM)</div><div class=\"ql-url\">https://nas.iamworkin.lan:5001</div></div></a>\n <a class=\"quick-link\" href=\"https://matrix.iamworkin.lan\" target=\"_blank\"><div><div class=\"ql-name\"><span class=\"dot dot-green\"></span>Matrix Synapse</div><div class=\"ql-url\">https://matrix.iamworkin.lan</div></div></a>\n <a class=\"quick-link\" href=\"https://telephony.iamworkin.lan\" target=\"_blank\"><div><div class=\"ql-name\"><span class=\"dot dot-green\"></span>Telephony</div><div class=\"ql-url\">https://telephony.iamworkin.lan</div></div></a>\n <a class=\"quick-link\" href=\"https://intranet.iamworkin.lan\" target=\"_blank\"><div><div class=\"ql-name\"><span class=\"dot dot-green\"></span>Intranet</div><div class=\"ql-url\">https://intranet.iamworkin.lan</div></div></a>\n <a class=\"quick-link\" href=\"http://piez.iamworkin.lan:5000\" target=\"_blank\"><div><div class=\"ql-name\"><span class=\"dot dot-green\"></span>PiManager (piez)</div><div class=\"ql-url\">http://piez.iamworkin.lan:5000</div></div></a>\n <a class=\"quick-link\" href=\"http://pirelay.iamworkin.lan:5100\" target=\"_blank\"><div><div class=\"ql-name\"><span class=\"dot dot-green\"></span>PiManager (pirelay)</div><div class=\"ql-url\">http://pirelay.iamworkin.lan:5100</div></div></a>\n <a class=\"quick-link\" href=\"https://print.iamworkin.lan\" target=\"_blank\"><div><div class=\"ql-name\"><span class=\"dot dot-green\"></span>Print Service</div><div class=\"ql-url\">https://print.iamworkin.lan</div></div></a>\n <a class=\"quick-link\" href=\"https://signage.iamworkin.lan\" target=\"_blank\"><div><div class=\"ql-name\"><span class=\"dot dot-green\"></span>Signage Web</div><div class=\"ql-url\">https://signage.iamworkin.lan</div></div></a>\n <a class=\"quick-link\" href=\"https://selenium.iamworkin.lan\" target=\"_blank\"><div><div class=\"ql-name\"><span class=\"dot dot-green\"></span>Selenium Grid</div><div class=\"ql-url\">https://selenium.iamworkin.lan</div></div></a>\n <a class=\"quick-link\" href=\"https://wifi.flowercore.io\" target=\"_blank\"><div><div class=\"ql-name\"><span class=\"dot dot-green\"></span>WiFi Portal</div><div class=\"ql-url\">https://wifi.flowercore.io</div></div></a>\n</div>\n\n<h3>Phase Progress</h3>\n<table>\n<thead><tr><th>Phase</th><th>Description</th><th>Status</th><th>Progress</th></tr></thead>\n<tbody>\n<tr><td>1</td><td>Frontier Modem Config</td><td><span class=\"b b-online\">Done</span></td><td>100%</td></tr>\n<tr><td>2</td><td>pfSense Base (WAN, LAN, VIPs)</td><td><span class=\"b b-online\">Done</span></td><td>100%</td></tr>\n<tr><td>3</td><td>VLAN Configuration (14 VLANs)</td><td><span class=\"b b-online\">Done</span></td><td>100%</td></tr>\n<tr><td>4</td><td>Firewall Rules &amp; Aliases</td><td><span class=\"b b-online\">Done</span></td><td>100%</td></tr>\n<tr><td>5</td><td>Bare-Metal RKE2 (Harvester Decommissioned)</td><td><span class=\"b b-online\">Done</span></td><td>100%</td></tr>\n<tr><td>6</td><td>OpenVPN (8 servers)</td><td><span class=\"b b-online\">Done</span></td><td>100%</td></tr>\n<tr><td>7</td><td>NAT Configuration</td><td><span class=\"b b-online\">Done</span></td><td>100%</td></tr>\n<tr><td>8</td><td>Traffic Shaper</td><td><span class=\"b b-online\">Done</span></td><td>100%</td></tr>\n<tr><td>9</td><td>DNS + NTP + SNMP</td><td><span class=\"b b-online\">Done</span></td><td>100%</td></tr>\n<tr><td>10</td><td>Switch + WiFi Config</td><td><span class=\"b b-online\">Done</span></td><td>100%</td></tr>\n<tr><td>11</td><td>NOC1 + Bare-Metal RKE2</td><td><span class=\"b b-online\">Done</span></td><td>100%</td></tr>\n<tr><td>12</td><td>GitOps + IaC (ArgoCD/Puppet)</td><td><span class=\"b b-online\">Done</span></td><td>100%</td></tr>\n<tr><td>13</td><td>Documentation Sync</td><td><span class=\"b b-online\">Done</span></td><td>100%</td></tr>\n</tbody>\n</table>\n</div>\n\n<!-- ===== TAB: ISP & WAN ===== -->\n<div id=\"tab-isp\" class=\"tab-content\">\n<h2>ISP &amp; WAN</h2>\n<div class=\"card-grid\">\n <div class=\"card\">\n <div class=\"card-title\"><span class=\"dot dot-green\"></span>ISP: Frontier Communications</div>\n <ul>\n <li><strong>Service:</strong> 1000/1000 Mbps fiber</li>\n <li><strong>Account:</strong> 952-431-5646-020421-7</li>\n <li><strong>Measured:</strong> 925 down / 677 up (MGMT VLAN)</li>\n </ul>\n </div>\n <div class=\"card\">\n <div class=\"card-title\"><span class=\"dot dot-green\"></span>Modem: NVG468MQ</div>\n <ul>\n <li><strong>Web:</strong> <a href=\"http://192.168.254.254\" target=\"_blank\">http://192.168.254.254 (modem LAN)</a> <button class=\"copy-btn\" onclick=\"copyText('192.168.254.254')\">copy</button></li>\n <li><strong>Credentials:</strong> <code>admin</code> / <a href=\"https://start.1password.com/open/i?v=qaphopopkryhbg353ukzhhuqoq&i=nasmtxdpd7moq6mbob2d5duc6a\" target=\"_blank\" class=\"op-link\" title=\"Open in 1Password\"><span class=\"b b-planned\">\U0001F510 Frontier Modem</span></a></li>\n <li><strong>Serial:</strong>\
\ 184795207512112</li>\n <li><strong>Firmware:</strong> 9.3.0h7d91</li>\n <li><strong>WAN IP:</strong> <span class=\"ip\">74.32.185.184</span> (DHCP) <button class=\"copy-btn\" onclick=\"copyText('74.32.185.184')\">copy</button></li>\n <li><strong>Config:</strong> DMZ to pfSense, WiFi OFF, firewall OFF</li>\n </ul>\n </div>\n</div>\n<h3>WAN Status</h3>\n<table>\n<thead><tr><th>Property</th><th>Value</th></tr></thead>\n<tbody>\n<tr><td>pfSense WAN Interface</td><td>igc3 (DHCP from modem)</td></tr>\n<tr><td>pfSense WAN IP</td><td><span class=\"ip\">192.168.254.122</span> <button class=\"copy-btn\" onclick=\"copyText('192.168.254.122')\">copy</button> (double NAT intentional)</td></tr>\n<tr><td>Public /28 Block</td><td><span class=\"ip\">74.40.140.16/28</span> <button class=\"copy-btn\" onclick=\"copyText('74.40.140.16/28')\">copy</button></td></tr>\n<tr><td>Gateway</td><td><span class=\"ip\">74.40.140.30</span> <button class=\"copy-btn\" onclick=\"copyText('74.40.140.30')\">copy</button></td></tr>\n<tr><td>Usable Range</td><td><span class=\"ip\">74.40.140.17 &ndash; 74.40.140.29</span> (13 IPs)</td></tr>\n</tbody>\n</table>\n<div class=\"note\"><strong>ISP /28 Routing: FULLY OPERATIONAL.</strong> Public subnet (74.40.140.16/28) is live. 13 VIPs on WAN (ix3), 12 hybrid outbound NAT rules, 28 port forwards. Cloudflare DNS with 28+ A records pointing to /28 IPs. DDNS: gateway.iamwork.in updates pfSense WAN DHCP IP via Cloudflare API.</div>\n<h3>Modem Static Routes</h3>\n<table>\n<thead><tr><th>Name</th><th>Destination</th><th>Gateway</th><th>Interface</th></tr></thead>\n<tbody>\n<tr><td>pfSense-Public-28</td><td><span class=\"ip\">74.40.140.16/28</span></td><td><span class=\"ip\">192.168.254.122</span></td><td>LAN</td></tr>\n<tr><td>pfSense-Private-Subnets</td><td><span class=\"ip\">10.0.0.0/8</span></td><td><span class=\"ip\">192.168.254.122</span></td><td>LAN</td></tr>\n</tbody>\n</table>\n<h3>Public IP Allocation (13 usable)</h3>\n<table>\n<thead><tr><th>IP</th><th>Full Address</th><th>Assignment</th><th>VLAN(s)</th><th>Services</th></tr></thead>\n<tbody>\n<tr><td>.16</td><td><span class=\"ip\">74.40.140.16</span></td><td><em>Network address</em></td><td>&mdash;</td><td>Unusable</td></tr>\n<tr><td>.17</td><td><span class=\"ip\">74.40.140.17</span> <button class=\"copy-btn\" onclick=\"copyText('74.40.140.17')\">copy</button></td><td>ANDREW + VPN</td><td>60</td><td>Andrew tenant primary + VPN :1194/:1195</td></tr>\n<tr><td>.18</td><td><span class=\"ip\">74.40.140.18</span> <button class=\"copy-btn\" onclick=\"copyText('74.40.140.18')\">copy</button></td><td>ANDREW #2</td><td>60</td><td>Andrew secondary</td></tr>\n<tr><td>.19</td><td><span class=\"ip\">74.40.140.19</span> <button class=\"copy-btn\" onclick=\"copyText('74.40.140.19')\">copy</button></td><td>MATT + VPN</td><td>61</td><td>Matt tenant primary + VPN :1194/:1195</td></tr>\n<tr><td>.20</td><td><span class=\"ip\">74.40.140.20</span> <button class=\"copy-btn\" onclick=\"copyText('74.40.140.20')\">copy</button></td><td>MATT #2</td><td>61</td><td>Matt secondary</td></tr>\n<tr><td>.21</td><td><span class=\"ip\">74.40.140.21</span> <button class=\"copy-btn\" onclick=\"copyText('74.40.140.21')\">copy</button></td><td>DUSTIN + VPN</td><td>62</td><td>Dustin tenant primary + VPN :1194/:1195</td></tr>\n<tr><td>.22</td><td><span class=\"ip\">74.40.140.22</span> <button class=\"copy-btn\" onclick=\"copyText('74.40.140.22')\">copy</button></td><td>SIERRA (Dustin #2)</td><td>62</td><td>Dustin secondary</td></tr>\n<tr><td>.23</td><td><span class=\"ip\">74.40.140.23</span> <button class=\"copy-btn\" onclick=\"copyText('74.40.140.23')\">copy</button></td><td>ERIK + VPN</td><td>63</td><td>Erik tenant primary + VPN :1194/:1195</td></tr>\n<tr><td>.24</td><td><span class=\"ip\">74.40.140.24</span> <button class=\"copy-btn\" onclick=\"copyText('74.40.140.24')\">copy</button></td><td>PROD</td><td>57</td><td>K8s web + mail (flowercore.io, SMTP)</td></tr>\n<tr><td>.25</td><td><span class=\"ip\">74.40.140.25</span> <button class=\"copy-btn\" onclick=\"copyText('74.40.140.25')\">copy</button></td><td>FIT + VPN</td><td>69</td><td>FIT tenant primary + VPN :1194/:1195</td></tr>\n<tr><td>.26</td><td><span class=\"ip\">74.40.140.26</span> <button class=\"copy-btn\" onclick=\"copyText('74.40.140.26')\">copy</button></td><td>FIT #2</td><td>69</td><td>FIT secondary</td></tr>\n<tr><td>.27</td><td><span class=\"ip\">74.40.140.27</span> <button class=\"copy-btn\" onclick=\"copyText('74.40.140.27')\">copy</button></td><td>COMMS</td><td>57</td><td>TeamSpeak, IRC, Matrix</td></tr>\n<tr><td>.28</td><td><span class=\"ip\">74.40.140.28</span> <button class=\"copy-btn\" onclick=\"copyText('74.40.140.28')\">copy</button></td><td>SHARED</td><td>59,64,65,66,67</td><td>WORK+SCHOOL+GUEST+VOIP+EMPLOYEE outbound</td></tr>\n<tr><td>.29</td><td><span class=\"ip\">74.40.140.29</span> <button class=\"copy-btn\" onclick=\"copyText('74.40.140.29')\">copy</button></td><td>HOME</td><td>58</td><td>Home traffic + Nintendo Switch static port NAT</td></tr>\n<tr><td>.30</td><td><span class=\"ip\">74.40.140.30</span></td><td><em>Gateway (Frontier)</em></td><td>&mdash;</td><td>ISP router</td></tr>\n<tr><td>.31</td><td><span class=\"ip\">74.40.140.31</span></td><td><em>Broadcast</em></td><td>&mdash;</td><td>Unusable</td></tr>\n</tbody>\n</table>\n</div>\n\n<!-- ===== TAB: PFSENSE ===== -->\n<div id=\"tab-pfsense\" class=\"tab-content\">\n<h2>pfSense Firewall</h2>\n<div class=\"card-grid\">\n <div class=\"card\">\n <div class=\"card-title\"><span class=\"dot dot-green\"></span>Netgate 4100</div>\n <ul>\n <li><strong>Web:</strong> <a href=\"https://pfsense.iamworkin.lan\" target=\"_blank\">https://pfsense.iamworkin.lan</a> <button class=\"copy-btn\" onclick=\"copyText('pfsense.iamworkin.lan')\">copy</button></li>\n <li><strong>Credentials:</strong> <code>admin</code> / <a href=\"https://start.1password.com/open/i?v=qaphopopkryhbg353ukzhhuqoq&i=kmmt7bckcm6hpvy5ajxgpodv5q\" target=\"_blank\" class=\"op-link\" title=\"Open in 1Password\"><span class=\"b b-planned\">\U0001F510 pfSense Firewall</span></a></li>\n <li><strong>SSH:</strong> <code>admin@pfsense.iamworkin.lan</code> <button class=\"copy-btn\" onclick=\"copyText('ssh admin@pfsense.iamworkin.lan')\">copy</button></li>\n <li><strong>Hardware:</strong> 2x SFP+, 4x 2.5GbE (igc0-3)</li>\n <li><strong>WAN:</strong> igc3 &mdash; LAN: igc0 (802.1Q trunk)</li>\n <li><strong>Domain:</strong> iamworkin.lan</li>\n </ul>\n </div>\n <div class=\"card\">\n <div class=\"card-title\">Firewall Stats</div>\n <ul>\n <li><strong>Aliases:</strong> 36 (16 port, 5 host, 15 network)</li>\n <li><strong>Rules:</strong> 90 active</li>\n <li><strong>Policy:</strong> Air-gapped default &mdash; deny all, explicit allow</li>\n <li><strong>SNMP:</strong> community <a href=\"https://start.1password.com/open/i?v=qaphopopkryhbg353ukzhhuqoq&i=7qias4wucncvleievorxda5pym\" target=\"_blank\" class=\"op-link\" title=\"Open in 1Password\"><span class=\"b b-planned\">\U0001F510 SNMP Community</span></a></li>\n <li><strong>SNMP Modules:</strong> mibII, netgraph, pf, hostres, bridge</li>\n </ul>\n </div>\n <div class=\"card\">\n <div class=\"card-title\">Services</div>\n <ul>\n <li><strong>DNS:</strong> Unbound (DNSSEC, WAN-only outgoing, prefetch)</li>\n <li><strong>DHCP:</strong> dhcpd on all 12 VLAN interfaces (.100-.199)</li>\n <li><strong>NTP:</strong> ntpd on all VLAN interfaces, DHCP option 42</li>\n <li><strong>Traffic Shaper:</strong> 24 dummynet pipes, fq_codel</li>\n </ul>\n </div>\n</div>\n<h3>VLAN Configuration (13 VLANs)</h3>\n<table>\n<thead><tr><th>VLAN</th><th>Name</th><th>Subnet</th><th>DHCP Range</th><th>Down/Up (Mbps)</th><th>Priority</th><th>Public IP</th></tr></thead>\n<tbody>\n<tr><td>56</td><td><span class=\"b b-mgmt\">MGMT</span></td><td><span class=\"ip\">10.0.56.0/24</span></td><td>.100-.199</td><td>500 / 500</td><td>5</td><td>WAN DHCP</td></tr>\n<tr><td>57</td><td><span class=\"b b-prod\">PROD</span></td><td><span class=\"ip\">10.0.57.0/24</span></td><td>.100-.199</td><td>500 / 500</td><td>5</td><td>.24</td></tr>\n<tr><td>58</td><td><span class=\"b b-home\">HOME</span></td><td><span class=\"ip\">10.0.58.0/24</span></td><td>.100-.199</td><td>800 / 800</td><td>3</td><td>.29</td></tr>\n<tr><td>59</td><td><span class=\"b b-wifi\">EMPLOYEE</span></td><td><span class=\"ip\">10.0.59.0/24</span></td><td>.100-.199</td><td>500 / 500</td><td>3</td><td>.28 (shared)</td></tr>\n<tr><td>60</td><td><span class=\"b b-tenant\">ANDREW</span></td><td><span class=\"ip\">10.0.60.0/24</span></td><td>.100-.199</td><td>300 / 300</td><td>3</td><td>.17</td></tr>\n<tr><td>61</td><td><span class=\"b b-tenant\">MATT</span></td><td><span class=\"ip\">10.0.61.0/24</span></td><td>.100-.199</td><td>300 / 300</td><td>3</td><td>.19</td></tr>\n<tr><td>62</td><td><span class=\"b b-tenant\">DUSTIN</span></td><td><span class=\"ip\">10.0.62.0/24</span></td><td>.100-.199</td><td>300 / 300</td><td>3</td><td>.21</td></tr>\n<tr><td>63</td><td><span class=\"b b-tenant\">ERIK</span></td><td><span class=\"ip\">10.0.63.0/24</span></td><td>.100-.199</td><td>300 / 300</td><td>3</td><td>.23</td></tr>\n<tr><td>64</td><td><span class=\"b b-wifi\">WORK</span></td><td><span class=\"ip\">10.0.64.0/24</span></td><td>.100-.199</td><td>500 / 500</td><td>3</td><td>.28 (shared)</td></tr>\n<tr><td>65</td><td><span class=\"b b-wifi\">SCHOOL</span></td><td><span class=\"ip\">10.0.65.0/24</span></td><td>.100-.199</td><td>200 / 200</td><td>1</td><td>.28 (shared)</td></tr>\n<tr><td>66</td><td><span class=\"b b-spare\">GUEST</span></td><td><span class=\"ip\">10.0.66.0/24</span></td><td>.100-.199</td><td>100 / 50</td><td>1</td><td>.28 (shared)</td></tr>\n<tr><td>67</td><td><span class=\"b b-voip\">VOIP</span></td><td><span class=\"ip\">10.0.67.0/24</span></td><td>.100-.199</td><td>100 / 100</td><td>7</td><td>.28 (shared)</td></tr>\n\
<tr><td>69</td><td><span class=\"b b-tenant\">FIT</span></td><td><span class=\"ip\">10.0.69.0/24</span></td><td>.100-.199</td><td>300 / 300</td><td>3</td><td>.25</td></tr>\n</tbody>\n</table>\n<div class=\"note\"><strong>Firewall Policy:</strong> MGMT has full access. HOME/WORK/SCHOOL get general internet. GUEST isolated except PROD web. Tenants fully isolated from each other &mdash; only PROD, DNS, NAS, and internet. VOIP is SIP-only outbound.</div>\n</div>\n\n<!-- ===== TAB: SWITCHING & WIFI ===== -->\n<div id=\"tab-switching\" class=\"tab-content\">\n<h2>Switching &amp; WiFi</h2>\n<div class=\"card-grid\">\n <div class=\"card\">\n <div class=\"card-title\"><span class=\"dot dot-green\"></span>UniFi Switch USW-Lite-16-PoE</div>\n <ul>\n <li><strong>IP:</strong> <span class=\"ip\">switch.iamworkin.lan</span> <button class=\"copy-btn\" onclick=\"copyText('switch.iamworkin.lan')\">copy</button></li>\n <li><strong>MAC:</strong> 74:ac:b9:e3:93:ba</li>\n <li><strong>SSH:</strong> <a href=\"https://start.1password.com/open/i?v=qaphopopkryhbg353ukzhhuqoq&i=sqozg7dicrzxzyj2p47hnxn7di\" target=\"_blank\" class=\"op-link\" title=\"Open in 1Password\"><span class=\"b b-planned\">\U0001F510 UniFi Switch</span></a></li>\n <li><strong>SSH Password:</strong> <a href=\"https://start.1password.com/open/i?v=qaphopopkryhbg353ukzhhuqoq&i=sqozg7dicrzxzyj2p47hnxn7di\" target=\"_blank\" class=\"op-link\" title=\"Open in 1Password\"><span class=\"b b-planned\">\U0001F510 UniFi Switch</span></a></li>\n <li><strong>Firmware:</strong> 7.2.123.16565</li>\n <li><strong>CLI:</strong> Type <code>cli</code> for Realtek switch CLI</li>\n </ul>\n </div>\n <div class=\"card\">\n <div class=\"card-title\"><span class=\"dot dot-green\"></span>UniFi Cloud Key G2</div>\n <ul>\n <li><strong>Web:</strong> <a href=\"https://unifi.iamworkin.lan\" target=\"_blank\">https://unifi.iamworkin.lan</a> <button class=\"copy-btn\" onclick=\"copyText('unifi.iamworkin.lan')\">copy</button></li>\n <li><strong>Credentials:</strong> <a href=\"https://start.1password.com/open/i?v=qaphopopkryhbg353ukzhhuqoq&i=sqozg7dicrzxzyj2p47hnxn7di\" target=\"_blank\" class=\"op-link\" title=\"Open in 1Password\"><span class=\"b b-planned\">\U0001F510 UniFi Cloud Key</span></a></li>\n <li><strong>SSH:</strong> <a href=\"https://start.1password.com/open/i?v=qaphopopkryhbg353ukzhhuqoq&i=sqozg7dicrzxzyj2p47hnxn7di\" target=\"_blank\" class=\"op-link\" title=\"Open in 1Password\"><span class=\"b b-planned\">\U0001F510 UniFi Cloud Key</span></a></li>\n <li><strong>Network Version:</strong> 7.1.61</li>\n <li><strong>MongoDB:</strong> port 27117 (database <code>ace</code>)</li>\n </ul>\n </div>\n <div class=\"card\">\n <div class=\"card-title\"><span class=\"dot dot-green\"></span>Synology RT6600AX (AP Mode)</div>\n <ul>\n <li><strong>Web:</strong> <a href=\"http://wifi.iamworkin.lan:8000\" target=\"_blank\">http://wifi.iamworkin.lan:8000</a> <button class=\"copy-btn\" onclick=\"copyText('wifi.iamworkin.lan')\">copy</button></li>\n <li><strong>Credentials:</strong> <a href=\"https://start.1password.com/open/i?v=qaphopopkryhbg353ukzhhuqoq&i=hlys5gwgnfa2a2nclrr52dr6gu\" target=\"_blank\" class=\"op-link\" title=\"Open in 1Password\"><span class=\"b b-planned\">\U0001F510 Synology WiFi Router</span></a></li>\n <li><strong>SSH:</strong> <code>bluejay@wifi.iamworkin.lan</code> <button class=\"copy-btn\" onclick=\"copyText('ssh bluejay@wifi.iamworkin.lan')\">copy</button></li>\n <li><strong>MAC:</strong> 90:09:d0:3d:64:ae</li>\n <li><strong>Mode:</strong> AP (bridge), all trunk ports enabled</li>\n </ul>\n </div>\n <div class=\"card\">\n <div class=\"card-title\"><span class=\"dot dot-green\"></span>BlueJayNAS (Synology DS1621+)</div>\n <ul>\n <li><strong>IP:</strong> <span class=\"ip\">nas.iamworkin.lan</span> <button class=\"copy-btn\" onclick=\"copyText('nas.iamworkin.lan')\">copy</button> (HOME VLAN 58)</li>\n <li><strong>DSM:</strong> <a href=\"https://nas.iamworkin.lan:5001\" target=\"_blank\">https://nas.iamworkin.lan:5001</a></li>\n <li><strong>Credentials:</strong> <a href=\"https://start.1password.com/open/i?v=qaphopopkryhbg353ukzhhuqoq&i=cbxfibct4j6jfm6ccsogpayjuy\" target=\"_blank\" class=\"op-link\" title=\"Open in 1Password\"><span class=\"b b-planned\">\U0001F510 BlueJayNAS</span></a></li>\n <li><strong>Storage:</strong> 9.1 TB Btrfs (RAID)</li>\n <li><strong>NFS Exports:</strong> Longhorn backup, shared media, ISO library</li>\n <li><strong>SNMP:</strong> Enabled (Zabbix monitored)</li>\n <li><strong>Zabbix:</strong> Host monitored via SNMP v2c</li>\n <li><strong>Switch Port:</strong> 14 (Access, VLAN 58)</li>\n </ul>\n </div>\n</div>\n<h3>Switch Port Assignments</h3>\n<table>\n<thead><tr><th>Port</th><th>Device</th><th>Mode</th><th>VLAN</th><th>Status</th></tr></thead>\n<tbody>\n<tr><td>1</td><td>pfSense Uplink</td><td>Trunk (All)</td><td>56-67</td><td><span class=\"dot dot-green\"></span>UP</td></tr>\n<tr><td>2</td><td>rke2-agent2</td><td>Trunk (All)</td><td>56-67</td><td><span class=\"dot dot-green\"></span>UP</td></tr>\n<tr><td>3</td><td>WiFi Uplink (Synology)</td><td>Trunk, native 58</td><td>57-67</td><td><span class=\"dot dot-green\"></span>UP</td></tr>\n<tr><td>4</td><td>rke2-agent1</td><td>Trunk (All)</td><td>56-67</td><td><span class=\"dot dot-green\"></span>UP</td></tr>\n<tr><td>5</td><td>Cloud Key G2</td><td>Access</td><td>56 (MGMT)</td><td><span class=\"dot dot-green\"></span>UP (PoE)</td></tr>\n<tr><td>6</td><td>rke2-server</td><td>Trunk (All)</td><td>56-67</td><td><span class=\"dot dot-green\"></span>UP</td></tr>\n<tr><td>7</td><td><em>Available</em></td><td>&mdash;</td><td>&mdash;</td><td><span class=\"dot dot-gray\"></span>Down</td></tr>\n<tr><td>8</td><td>noc1</td><td>Trunk (All)</td><td>56-67</td><td><span class=\"dot dot-green\"></span>UP</td></tr>\n<tr><td>9</td><td>BLUEJAY-WS (Workstation)</td><td>Access</td><td>56 (MGMT)</td><td><span class=\"dot dot-green\"></span>UP</td></tr>\n<tr><td>10</td><td><em>Available</em></td><td>&mdash;</td><td>&mdash;</td><td><span class=\"dot dot-gray\"></span>Down</td></tr>\n<tr><td>11</td><td>edge2 (Pi 4)</td><td>Access</td><td>57 (PROD)</td><td><span class=\"dot dot-green\"></span>UP</td></tr>\n<tr><td>12</td><td><em>Available</em></td><td>&mdash;</td><td>&mdash;</td><td><span class=\"dot dot-gray\"></span>Down</td></tr>\n<tr><td>13</td><td>edge1 (Pi 5)</td><td>Access</td><td>57 (PROD)</td><td><span class=\"dot dot-green\"></span>UP</td></tr>\n<tr><td>14</td><td>Synology NAS</td><td>Access</td><td>58 (HOME)</td><td><span class=\"dot dot-green\"></span>UP</td></tr>\n<tr><td>15</td><td><em>Available</em></td><td>&mdash;</td><td>&mdash;</td><td><span class=\"dot dot-gray\"></span>Down</td></tr>\n<tr><td>16</td><td>Synology 2</td><td>Access</td><td>58 (HOME)</td><td><span class=\"dot dot-green\"></span>UP</td></tr>\n</tbody>\n</table>\n<h3>WiFi SSIDs</h3>\n<table>\n<thead><tr><th>SSID</th><th>Bridge</th><th>VLAN</th><th>Type</th><th>Password</th></tr></thead>\n<tbody>\n<tr><td><strong>BlueJay-Home</strong></td><td>br0</td><td>untagged (58)</td><td>Primary</td><td><a href=\"https://start.1password.com/open/i?v=qaphopopkryhbg353ukzhhuqoq&i=vetpnvuxyfdcvkjr2cqhuaqekq\" target=\"_blank\" class=\"op-link\" title=\"Open in 1Password\"><span class=\"b b-planned\">\U0001F510 BlueJay-Home WiFi</span></a></td></tr>\n<tr><td><strong>BlueJay-Guest</strong></td><td>br2</td><td>66</td><td>Custom (captive portal)</td><td><a href=\"https://start.1password.com/open/i?v=qaphopopkryhbg353ukzhhuqoq&i=2nepku5kumcguwciiyhiiaifne\" target=\"_blank\" class=\"op-link\" title=\"Open in 1Password\"><span class=\"b b-planned\">\U0001F510 BlueJay-Guest WiFi</span></a></td></tr>\n<tr><td><strong>BlueJay-Work</strong></td><td>br3</td><td>64</td><td>Custom</td><td><a href=\"https://start.1password.com/open/i?v=qaphopopkryhbg353ukzhhuqoq&i=4omsv5ybqoyk4tvqrqww7kv3yu\" target=\"_blank\" class=\"op-link\" title=\"Open in 1Password\"><span class=\"b b-planned\">\U0001F510 BlueJay-Work WiFi</span></a></td></tr>\n<tr><td><strong>BlueJay-School</strong></td><td>br4</td><td>65</td><td>Custom</td><td><a href=\"https://start.1password.com/open/i?v=qaphopopkryhbg353ukzhhuqoq&i=jnl2dodsrbli2y5rhwirvcywzy\" target=\"_blank\" class=\"op-link\" title=\"Open in 1Password\"><span class=\"b b-planned\">\U0001F510 BlueJay-School WiFi</span></a></td></tr>\n</tbody>\n</table>\n</div>\n\n<!-- ===== TAB: DNS ===== -->\n<div id=\"tab-dns\" class=\"tab-content\">\n<h2>DNS Directory</h2>\n<div class=\"note\">All entries are pfSense Unbound host overrides under <code>iamworkin.lan</code>. 55+ host overrides configured, plus 4 tenant wildcard redirect zones (*.bluejay.lan, *.timefortaco.lan, *.erik.lan, *.flowerinsider.lan &rarr; 10.0.56.200 Traefik).</div>\n<h3>Management Devices</h3>\n<table>\n<thead><tr><th>Hostname</th><th>IP</th><th>Role</th></tr></thead>\n<tbody>\n<tr><td>pfsense.iamworkin.lan</td><td><span class=\"ip\">pfsense.iamworkin.lan</span> <button class=\"copy-btn\" onclick=\"copyText('pfsense.iamworkin.lan')\">copy</button></td><td>pfSense firewall</td></tr>\n<tr><td>switch.iamworkin.lan</td><td><span class=\"ip\">switch.iamworkin.lan</span> <button class=\"copy-btn\" onclick=\"copyText('switch.iamworkin.lan')\">copy</button></td><td>UniFi PoE Switch</td></tr>\n<tr><td>unifi.iamworkin.lan</td><td><span class=\"ip\">unifi.iamworkin.lan</span> <button class=\"copy-btn\" onclick=\"copyText('unifi.iamworkin.lan')\">copy</button></td><td>UniFi Cloud Key G2</td></tr>\n<tr><td>wifi.iamworkin.lan</td><td><span class=\"ip\">wifi.iamworkin.lan</span> <button class=\"copy-btn\" onclick=\"copyText('wifi.iamworkin.lan')\">copy</button></td><td>Synology WiFi Router (AP)</td></tr>\n<tr><td>nas.iamworkin.lan</td><td><span class=\"ip\">nas.iamworkin.lan</span> <button class=\"copy-btn\" onclick=\"copyText('nas.iamworkin.lan')\"\
>copy</button></td><td>Synology NAS</td></tr>\n</tbody>\n</table>\n<h3>RKE2 Bare-Metal Cluster</h3>\n<table>\n<thead><tr><th>Hostname</th><th>IP</th><th>Role</th></tr></thead>\n<tbody>\n<tr><td>rke2-server.iamworkin.lan</td><td><span class=\"ip\">rke2-server.iamworkin.lan</span> <button class=\"copy-btn\" onclick=\"copyText('rke2-server.iamworkin.lan')\">copy</button></td><td>RKE2 control plane (bare-metal, openSUSE Leap 16)</td></tr>\n<tr><td>rke2-agent1.iamworkin.lan</td><td><span class=\"ip\">rke2-agent1.iamworkin.lan</span> <button class=\"copy-btn\" onclick=\"copyText('rke2-agent1.iamworkin.lan')\">copy</button></td><td>RKE2 worker node 1 (bare-metal, openSUSE Leap 16)</td></tr>\n<tr><td>rke2-agent2.iamworkin.lan</td><td><span class=\"ip\">rke2-agent2.iamworkin.lan</span> <button class=\"copy-btn\" onclick=\"copyText('rke2-agent2.iamworkin.lan')\">copy</button></td><td>RKE2 worker node 2 (bare-metal, openSUSE Leap 16)</td></tr>\n</tbody>\n</table>\n<h3>NOC Services (noc1)</h3>\n<table>\n<thead><tr><th>Hostname</th><th>IP</th><th>Role</th></tr></thead>\n<tbody>\n<tr><td>noc1.iamworkin.lan</td><td><span class=\"ip\">noc1.iamworkin.lan</span> <button class=\"copy-btn\" onclick=\"copyText('noc1.iamworkin.lan')\">copy</button></td><td>NOC management node (K3s)</td></tr>\n<tr><td>acme.iamworkin.lan</td><td><span class=\"ip\">noc1.iamworkin.lan</span></td><td>step-ca ACME CA</td></tr>\n<tr><td>pki.iamworkin.lan</td><td><span class=\"ip\">noc1.iamworkin.lan</span></td><td>PKI cert/CRL distribution</td></tr>\n<tr><td>guac.iamworkin.lan</td><td><span class=\"ip\">traefik.iamworkin.lan</span></td><td>Apache Guacamole (Blue Jay branded, guacamole ns)</td></tr>\n<tr><td>grafana.iamworkin.lan</td><td><span class=\"ip\">traefik.iamworkin.lan</span></td><td>Grafana monitoring (monitoring ns, K8s primary)</td></tr>\n<tr><td>prometheus.iamworkin.lan</td><td><span class=\"ip\">traefik.iamworkin.lan</span></td><td>Prometheus metrics (monitoring ns, K8s primary)</td></tr>\n<tr><td>cockpit.iamworkin.lan</td><td><span class=\"ip\">traefik.iamworkin.lan</span></td><td>Cockpit web console (noc-proxy ns &rarr; noc1:9090)</td></tr>\n<tr><td>traefik.iamworkin.lan</td><td><span class=\"ip\">10.0.56.200</span></td><td>Traefik dashboard (RKE2, MetalLB VIP)</td></tr>\n<tr><td>op-connect.iamworkin.lan</td><td><span class=\"ip\">noc1.iamworkin.lan</span></td><td>1Password Connect API (:8180)</td></tr>\n</tbody>\n</table>\n<h3>RKE2 Services (via Traefik at traefik.iamworkin.lan)</h3>\n<table>\n<thead><tr><th>Hostname</th><th>IP</th><th>Role</th></tr></thead>\n<tbody>\n<tr><td>rke2.iamworkin.lan</td><td><span class=\"ip\">rke2-server.iamworkin.lan</span> <button class=\"copy-btn\" onclick=\"copyText('rke2-server.iamworkin.lan')\">copy</button></td><td>RKE2 API server (bare-metal control plane)</td></tr>\n<tr><td>rke2-traefik.iamworkin.lan</td><td><span class=\"ip\">traefik.iamworkin.lan</span> <button class=\"copy-btn\" onclick=\"copyText('traefik.iamworkin.lan')\">copy</button></td><td>Traefik LoadBalancer (MetalLB)</td></tr>\n<tr><td>argocd.iamworkin.lan</td><td><span class=\"ip\">traefik.iamworkin.lan</span></td><td>ArgoCD GitOps (22 apps, all Healthy)</td></tr>\n<tr><td>gitea.iamworkin.lan</td><td><span class=\"ip\">traefik.iamworkin.lan</span></td><td>Gitea Git hosting (SSH at MetalLB .201)</td></tr>\n<tr><td>zabbix.iamworkin.lan</td><td><span class=\"ip\">traefik.iamworkin.lan</span></td><td>Zabbix monitoring (10+ hosts, trapper at .203)</td></tr>\n<tr><td>guac.iamworkin.lan</td><td><span class=\"ip\">traefik.iamworkin.lan</span></td><td>Apache Guacamole (23 connections, Blue Jay branded)</td></tr>\n<tr><td>irc.iamworkin.lan</td><td><span class=\"ip\">traefik.iamworkin.lan</span></td><td>UnrealIRCd + Anope (ports 6667/6697/8067)</td></tr>\n<tr><td>matrix.iamworkin.lan</td><td><span class=\"ip\">traefik.iamworkin.lan</span></td><td>Matrix Synapse homeserver</td></tr>\n<tr><td>element.iamworkin.lan</td><td><span class=\"ip\">traefik.iamworkin.lan</span></td><td>Element Web (Matrix client)</td></tr>\n<tr><td>intranet.iamworkin.lan</td><td><span class=\"ip\">traefik.iamworkin.lan</span></td><td>Lab intranet dashboard</td></tr>\n<tr><td>pki.iamworkin.lan</td><td><span class=\"ip\">traefik.iamworkin.lan</span></td><td>PKI cert/CRL distribution</td></tr>\n<tr><td>mail.iamworkin.lan</td><td><span class=\"ip\">traefik.iamworkin.lan</span></td><td>docker-mailserver (SMTP at MetalLB .202)</td></tr>\n<tr><td>telephony.iamwork.in</td><td><span class=\"ip\">traefik.iamworkin.lan</span></td><td>FlowerCore.Telephony (:5100, Cloudflare origin cert)</td></tr>\n<tr><td>telephony.iamworkin.lan</td><td><span class=\"ip\">traefik.iamworkin.lan</span></td><td>FlowerCore.Telephony (internal, step-ca cert)</td></tr>\n<tr><td>signage.iamworkin.lan</td><td><span class=\"ip\">traefik.iamworkin.lan</span></td><td>FlowerCore.Signage.Web (fc-signage ns, :5190)</td></tr>\n<tr><td>remotedesktop.iamworkin.lan</td><td><span class=\"ip\">traefik.iamworkin.lan</span></td><td>FlowerCore.RemoteDesktop.Web (fc-desktop ns)</td></tr>\n<tr><td>print.iamworkin.lan</td><td><span class=\"ip\">traefik.iamworkin.lan</span></td><td>FlowerCore.Print.Web (noc-proxy ns &rarr; edge2:5200, HTTPS via step-ca)</td></tr>\n<tr><td>grafana.iamworkin.lan</td><td><span class=\"ip\">traefik.iamworkin.lan</span></td><td>Grafana (monitoring ns, K8s primary)</td></tr>\n<tr><td>prometheus.iamworkin.lan</td><td><span class=\"ip\">traefik.iamworkin.lan</span></td><td>Prometheus (monitoring ns, K8s primary)</td></tr>\n<tr><td>cockpit.iamworkin.lan</td><td><span class=\"ip\">traefik.iamworkin.lan</span></td><td>Cockpit (noc-proxy ns &rarr; noc1:9090)</td></tr>\n</tbody>\n</table>\n<h3>RKE2 MetalLB Service IPs</h3>\n<table>\n<thead><tr><th>IP</th><th>Service</th><th>Ports</th></tr></thead>\n<tbody>\n<tr><td><span class=\"ip\">traefik.iamworkin.lan</span></td><td>Traefik Ingress</td><td>80, 443, 8080, 6667, 6697</td></tr>\n<tr><td><span class=\"ip\">gitea-ssh.iamworkin.lan</span></td><td>Gitea SSH</td><td>22</td></tr>\n<tr><td><span class=\"ip\">mail.iamworkin.lan</span></td><td>Mail SMTP</td><td>25, 465, 587</td></tr>\n<tr><td><span class=\"ip\">zabbix-trapper.iamworkin.lan</span></td><td>Zabbix Trapper</td><td>10051</td></tr>\n<tr><td><span class=\"ip\">ts.iamworkin.lan</span></td><td>TeamSpeak</td><td>9987/UDP, 30033, 10011</td></tr>\n<tr><td><span class=\"ip\">asterisk.iamworkin.lan</span></td><td>Asterisk SIP</td><td>5060/UDP+TCP</td></tr>\n</tbody>\n</table>\n<h3>Production / Edge Nodes</h3>\n<table>\n<thead><tr><th>Hostname</th><th>IP</th><th>Role</th></tr></thead>\n<tbody>\n<tr><td>macmini.iamworkin.lan</td><td><span class=\"ip\">macmini.iamworkin.lan</span> <button class=\"copy-btn\" onclick=\"copyText('macmini.iamworkin.lan')\">copy</button></td><td>Mac Mini build node (Xcode)</td></tr>\n<tr><td>edge1.iamworkin.lan</td><td><span class=\"ip\">edge1.iamworkin.lan</span> <button class=\"copy-btn\" onclick=\"copyText('edge1.iamworkin.lan')\">copy</button></td><td>Pi 5 + Hailo AI HAT+ 2</td></tr>\n<tr><td>edge2.iamworkin.lan</td><td><span class=\"ip\">edge2.iamworkin.lan</span> <button class=\"copy-btn\" onclick=\"copyText('edge2.iamworkin.lan')\">copy</button></td><td>Pi 4 (Argon ONE, CI runner)</td></tr>\n<tr><td>piez.iamworkin.lan</td><td><span class=\"ip\">piez.iamworkin.lan</span> <button class=\"copy-btn\" onclick=\"copyText('piez.iamworkin.lan')\">copy</button></td><td>Pi 4 + EZ Connect (PiManager :5000, GPIO/I2C/SPI)</td></tr>\n<tr><td>pirelay.iamworkin.lan</td><td><span class=\"ip\">pirelay.iamworkin.lan</span> <button class=\"copy-btn\" onclick=\"copyText('pirelay.iamworkin.lan')\">copy</button></td><td>Pi 3 + 4-ch Relay (PiManager :5100, KS0212)</td></tr>\n</tbody>\n</table>\n<h3>BLUEJAY-WS (openSUSE Leap 16 Workstation)</h3>\n<table>\n<thead><tr><th>Hostname</th><th>IP</th><th>Role</th></tr></thead>\n<tbody>\n<tr><td>agent-zero-ws.iamworkin.lan</td><td><span class=\"ip\">10.0.56.20</span> <button class=\"copy-btn\" onclick=\"copyText('10.0.56.20')\">copy</button></td><td>Agent Zero (BLUEJAY-WS, R9700 GPU)</td></tr>\n<tr><td>ollama-ws.iamworkin.lan</td><td><span class=\"ip\">10.0.56.20</span> <button class=\"copy-btn\" onclick=\"copyText('10.0.56.20')\">copy</button></td><td>Ollama 0.19.0 (14 models, ROCm + Vulkan)</td></tr>\n<tr><td>bluejay-ws.iamworkin.lan</td><td><span class=\"ip\">10.0.56.20</span> <button class=\"copy-btn\" onclick=\"copyText('10.0.56.20')\">copy</button></td><td>Traefik v3.6.12 dashboard (BLUEJAY-WS)</td></tr>\n</tbody>\n</table>\n<h3>Planned / Windows (pre-registered)</h3>\n<table>\n<thead><tr><th>Hostname</th><th>IP</th><th>Role</th></tr></thead>\n<tbody>\n<tr><td>dc1.iamworkin.lan</td><td><span class=\"ip\">TBD</span> <button class=\"copy-btn\" onclick=\"copyText('TBD')\">copy</button></td><td>AD Domain Controller (planned &mdash; IP 10.0.56.20 now used by BLUEJAY-WS)</td></tr>\n<tr><td>wac1.iamworkin.lan</td><td><span class=\"ip\">10.0.56.21</span> <button class=\"copy-btn\" onclick=\"copyText('10.0.56.21')\">copy</button></td><td>Windows Admin Center (planned)</td></tr>\n<tr><td>rds1.iamworkin.lan</td><td><span class=\"ip\">10.0.57.20</span> <button class=\"copy-btn\" onclick=\"copyText('10.0.57.20')\">copy</button></td><td>Remote Desktop Services (planned)</td></tr>\n<tr><td>iis1.iamworkin.lan</td><td><span class=\"ip\">10.0.57.21</span> <button class=\"copy-btn\" onclick=\"copyText('10.0.57.21')\">copy</button></td><td>IIS Web Server (planned)</td></tr>\n<tr><td>proxy.iamworkin.lan</td><td><span class=\"ip\">10.0.56.22</span> <button class=\"copy-btn\" onclick=\"copyText('10.0.56.22')\">copy</button></td><td>Squid Authenticated Proxy (planned)</td></tr>\n</tbody>\n</table>\n</div>\n\n<!-- ===== TAB: KUBERNETES ===== -->\n<div id=\"tab-k8s\" class=\"tab-content\">\n<h2>Kubernetes Clusters</h2>\n<h3>K3s (noc1 &mdash; Emergency Fallback, Scaled to 0)</h3>\n<div class=\"card-grid\">\n <div class=\"card\" style=\"opacity:0.6;\">\n <div class=\"card-title\"><span\
\ class=\"dot dot-yellow\"></span>K3s on noc1 (Standby)</div>\n <ul>\n <li><strong>Node:</strong> <span class=\"ip\">noc1.iamworkin.lan</span> (single-node)</li>\n <li><strong>Version:</strong> K3s v1.34.5</li>\n <li><strong>Status:</strong> Scaled to 0 &mdash; emergency fallback only</li>\n <li><strong>Migration:</strong> All workloads moved to RKE2 (2026-03-09)</li>\n <li><strong>Tools:</strong> kubectl v1.35.2, helm v3.20.0</li>\n </ul>\n </div>\n</div>\n<h3>Harvester HCI (DECOMMISSIONED 2026-03-09)</h3>\n<div class=\"card-grid\">\n <div class=\"card\" style=\"opacity:0.5;border-color:var(--red-border);\">\n <div class=\"card-title\"><span class=\"dot dot-red\"></span>Harvester Cluster &mdash; Decommissioned</div>\n <ul>\n <li><strong>Status:</strong> <span class=\"b b-offline\">Decommissioned</span> &mdash; replaced by bare-metal RKE2</li>\n <li><strong>Reason:</strong> 6 K8s control planes caused 100&deg;C thermal throttling</li>\n <li><strong>Migration:</strong> All 3 NUCs reformatted to openSUSE Leap 16, bare-metal RKE2</li>\n <li><strong>Result:</strong> Temps 44-71&deg;C, 1 control plane instead of 6</li>\n </ul>\n </div>\n</div>\n<h3>RKE2 (Bare-Metal Cluster)</h3>\n<div class=\"card-grid\">\n <div class=\"card\">\n <div class=\"card-title\"><span class=\"dot dot-green\"></span>RKE2 Cluster</div>\n <ul>\n <li><strong>Version:</strong> RKE2 v1.34.5+rke2r1</li>\n <li><strong>OS:</strong> openSUSE Leap 16.0 (bare-metal)</li>\n <li><strong>CNI:</strong> Calico (VXLAN mode)</li>\n <li><strong>Pod CIDR:</strong> <span class=\"ip\">10.42.0.0/16</span></li>\n <li><strong>Service CIDR:</strong> <span class=\"ip\">10.43.0.0/16</span></li>\n <li><strong>Kubeconfig:</strong> <code>/root/.kube/rke2.yaml</code> on noc1 or WSL</li>\n <li><strong>SSH:</strong> ed25519 key auth (root)</li>\n </ul>\n </div>\n <div class=\"card\">\n <div class=\"card-title\">RKE2 Nodes (Bare-Metal)</div>\n <ul>\n <li><span class=\"dot dot-green\"></span><strong>rke2-server:</strong> <span class=\"ip\">rke2-server.iamworkin.lan</span> (i7-1260P / 64GB, control plane)</li>\n <li><span class=\"dot dot-green\"></span><strong>rke2-agent1:</strong> <span class=\"ip\">rke2-agent1.iamworkin.lan</span> (i7-1260P / 64GB, worker)</li>\n <li><span class=\"dot dot-green\"></span><strong>rke2-agent2:</strong> <span class=\"ip\">rke2-agent2.iamworkin.lan</span> (i5-1340P / 64GB, worker)</li>\n <li><strong>SSH:</strong> <code>root@10.0.56.{11,12,13}</code> (ed25519 key)</li>\n <li><strong>Puppet:</strong> profile::kubernetes::rke2 on all nodes</li>\n </ul>\n </div>\n <div class=\"card\">\n <div class=\"card-title\">RKE2 Infrastructure</div>\n <ul>\n <li><span class=\"dot dot-green\"></span><strong>MetalLB:</strong> L2 mode, pool <span class=\"ip\">10.0.56.200-220</span></li>\n <li><span class=\"dot dot-green\"></span><strong>Traefik:</strong> v3.6.10, 2 replicas, LB <span class=\"ip\">traefik.iamworkin.lan</span></li>\n <li><span class=\"dot dot-green\"></span><strong>Longhorn:</strong> Default StorageClass (iSCSI), NAS backup integration</li>\n <li><strong>Namespaces (41):</strong> fc-system, fc-signage, fc-desktop, fc-tenant-{andrew,matt,dustin,erik,fit}, tenant-{andrew,dustin,erik,fit,flowercore}, traefik-system, metallb-system, argocd, irc, mail, matrix, zabbix, guacamole, gitea, teamspeak, onepassword-system, cert-manager, telephony, monitoring, selenium, agent-zero, intranet, pki, noc-proxy, wifi-portal, voice, longhorn-system, kube-system, calico-system, tigera-operator</li>\n <li><strong>IngressRoutes:</strong> 49 Traefik routes (internal + Cloudflare public)</li>\n <li><strong>PVCs:</strong> 17 persistent volumes, ~69 Gi total (Longhorn iSCSI, NAS backup)</li>\n <li><strong>ArgoCD:</strong> 22 apps via bluejay-infra ApplicationSet (all Healthy &mdash; includes agent-zero, asterisk, monitoring, voice, 5 tenant landing pages, guacamole, mail, matrix, IRC, telephony, Gitea, Zabbix, PKI, intranet, noc-services)</li>\n <li><strong>1Password:</strong> Operator v1.11.0 in onepassword-system, 7 CRDs syncing</li>\n <li><strong>Cloudflare Origin Certs:</strong> *.flowercore.io + *.iamwork.in (15-year RSA) deployed across 8 namespaces</li>\n </ul>\n </div>\n</div>\n<h3>Cluster Resource Usage (2026-03-21)</h3>\n<table>\n<thead><tr><th>Node</th><th>CPU</th><th>Memory</th><th>Pods</th><th>Role</th></tr></thead>\n<tbody>\n<tr><td>rke2-server</td><td>866m (5%)</td><td>19,293 Mi (30%)</td><td>~40</td><td>Control plane + worker</td></tr>\n<tr><td>rke2-agent1</td><td>616m (3%)</td><td>20,905 Mi (32%)</td><td>~40</td><td>Worker</td></tr>\n<tr><td>rke2-agent2</td><td>1,430m (8%)</td><td>17,517 Mi (27%)</td><td>~40</td><td>Worker (Selenium + telephony)</td></tr>\n</tbody>\n</table>\n<div class=\"note\"><strong>Capacity:</strong> 192 GB total RAM (64 GB/node), ~30% utilized. Selenium Grid (4 pods) + ArgoCD (7 pods) + Longhorn (29 pods) are the biggest consumers. Prometheus at 10 Gi retention (90 days). All stateful workloads backed up to BlueJayNAS via Longhorn NFS.</div>\n\n<h3>pfSense Static Routes (K8s)</h3>\n<table>\n<thead><tr><th>Destination</th><th>Gateway</th><th>Purpose</th></tr></thead>\n<tbody>\n<tr><td><span class=\"ip\">10.42.0.0/16</span></td><td><span class=\"ip\">rke2-server.iamworkin.lan</span> (rke2-server)</td><td>Pod CIDR routing</td></tr>\n<tr><td><span class=\"ip\">10.43.0.0/16</span></td><td><span class=\"ip\">rke2-server.iamworkin.lan</span> (rke2-server)</td><td>Service CIDR routing</td></tr>\n</tbody>\n</table>\n</div>\n\n<!-- ===== TAB: NOC SERVICES ===== -->\n<div id=\"tab-noc\" class=\"tab-content\">\n<h2>NOC Services (noc1)</h2>\n<div class=\"card-grid\">\n <div class=\"card\">\n <div class=\"card-title\"><span class=\"dot dot-green\"></span>noc1 Host</div>\n <ul>\n <li><strong>IP:</strong> <span class=\"ip\">noc1.iamworkin.lan</span> <button class=\"copy-btn\" onclick=\"copyText('noc1.iamworkin.lan')\">copy</button></li>\n <li><strong>SSH:</strong> <code>root@noc1.iamworkin.lan</code> <button class=\"copy-btn\" onclick=\"copyText('ssh root@noc1.iamworkin.lan')\">copy</button></li>\n <li><strong>Password:</strong> <a href=\"https://start.1password.com/open/i?v=qaphopopkryhbg353ukzhhuqoq&i=gjb3da3jepm2xzr5mef3afzws4\" target=\"_blank\" class=\"op-link\" title=\"Open in 1Password\"><span class=\"b b-planned\">\U0001F510 noc1</span></a></li>\n <li><strong>Guacamole:</strong> <a href=\"https://guac.iamworkin.lan/#/client/MjcAYwBteXNxbA==\" target=\"_blank\"><span class=\"b b-online\">▶ fcadmin SSH</span></a></li>\n <li><strong>OS:</strong> openSUSE Leap Micro 6.2 (immutable)</li>\n <li><strong>CPU:</strong> Intel Celeron N5105 (4C/4T)</li>\n <li><strong>RAM:</strong> 32 GB</li>\n <li><strong>Disk:</strong> 1TB NVMe (929GB free)</li>\n <li><strong>Runtimes:</strong> Podman 5.4.2, K3s v1.34.5</li>\n </ul>\n </div>\n</div>\n<h3>Service Directory</h3>\n<table>\n<thead><tr><th>Service</th><th>URL</th><th>Port</th><th>Credentials</th><th>Status</th></tr></thead>\n<tbody>\n<tr><td>Cockpit</td><td><a href=\"https://cockpit.iamworkin.lan\" target=\"_blank\">https://cockpit.iamworkin.lan</a></td><td>443 (Traefik)</td><td><a href=\"https://start.1password.com/open/i?v=qaphopopkryhbg353ukzhhuqoq&i=gjb3da3jepm2xzr5mef3afzws4\" target=\"_blank\" class=\"op-link\" title=\"Open in 1Password\"><span class=\"b b-planned\">\U0001F510 noc1</span></a></td><td><span class=\"b b-online\">Online</span></td></tr>\n<tr><td>Prometheus</td><td><a href=\"https://prometheus.iamworkin.lan\" target=\"_blank\">https://prometheus.iamworkin.lan</a></td><td>443 (Traefik, monitoring ns)</td><td><em>No auth</em> (90-day retention, ~36 scrape jobs)</td><td><span class=\"b b-online\">Online</span></td></tr>\n<tr><td>Grafana</td><td><a href=\"https://grafana.iamworkin.lan\" target=\"_blank\">https://grafana.iamworkin.lan</a></td><td>443 (Traefik, monitoring ns)</td><td><a href=\"https://start.1password.com/open/i?v=qaphopopkryhbg353ukzhhuqoq&i=l2ld4i2m47keuyxrzmj5jm56nu\" target=\"_blank\" class=\"op-link\" title=\"Open in 1Password\"><span class=\"b b-planned\">\U0001F510 Grafana</span></a></td><td><span class=\"b b-online\">Online</span></td></tr>\n<tr><td>Node Exporter</td><td>http://noc1.iamworkin.lan:9100</td><td>9100</td><td><em>Metrics only</em></td><td><span class=\"b b-online\">Online</span></td></tr>\n<tr><td>SNMP Exporter</td><td>monitoring ns (K8s ClusterIP :9116)</td><td>9116</td><td><em>pfSense + NAS + Switch + Printer SNMP scraper</em></td><td><span class=\"b b-online\">Online</span></td></tr>\n<tr><td>step-ca ACME</td><td><a href=\"https://acme.iamworkin.lan:9443\" target=\"_blank\">https://acme.iamworkin.lan:9443</a></td><td>9443</td><td><a href=\"https://start.1password.com/open/i?v=qaphopopkryhbg353ukzhhuqoq&i=cg6ufz7vditglpqag2dyppi24q\" target=\"_blank\" class=\"op-link\" title=\"Open in 1Password\"><span class=\"b b-planned\">\U0001F510 step-ca</span></a></td><td><span class=\"b b-online\">Online</span></td></tr>\n<tr><td>1Password Connect</td><td>http://op-connect.iamworkin.lan:8180</td><td>8180/8181</td><td><em>API token auth</em></td><td><span class=\"b b-online\">Online</span></td></tr>\n<tr><td>Puppet Server</td><td>noc1:8140</td><td>8140</td><td><em>OpenVox Server 8.12 (Podman)</em></td><td><span class=\"b b-online\">Online</span></td></tr>\n</tbody>\n</table>\n<h3>RKE2 Services (22 ArgoCD Apps &mdash; All Healthy)</h3>\n<table>\n<thead><tr><th>Service</th><th>URL</th><th>MetalLB / Port</th><th>Credentials</th><th>Status</th></tr></thead>\n<tbody>\n<tr><td>ArgoCD</td><td><a href=\"https://argocd.iamworkin.lan\" target=\"_blank\">https://argocd.iamworkin.lan</a></td><td>443 (via Traefik)</td><td><a href=\"https://start.1password.com/open/i?v=qaphopopkryhbg353ukzhhuqoq&i=cmin4ocvddco3vshzozgjbupva\"\
\ target=\"_blank\" class=\"op-link\" title=\"Open in 1Password\"><span class=\"b b-planned\">\U0001F510 ArgoCD</span></a></td><td><span class=\"b b-online\">Online</span></td></tr>\n<tr><td>Traefik</td><td><a href=\"https://traefik.iamworkin.lan:8080/dashboard/\" target=\"_blank\">traefik.iamworkin.lan:8080</a></td><td>MetalLB .200 &mdash; 80/443/8080/6667/6697</td><td><a href=\"https://start.1password.com/open/i?v=qaphopopkryhbg353ukzhhuqoq&i=br3hedkopqujp7nzilxvtdpqnu\" target=\"_blank\" class=\"op-link\" title=\"Open in 1Password\"><span class=\"b b-planned\">\U0001F510 Traefik Dashboard</span></a></td><td><span class=\"b b-online\">Online</span></td></tr>\n<tr><td>Gitea</td><td><a href=\"https://gitea.iamworkin.lan\" target=\"_blank\">https://gitea.iamworkin.lan</a></td><td>.201 SSH:22 &mdash; HTTPS via Traefik</td><td><a href=\"https://start.1password.com/open/i?v=qaphopopkryhbg353ukzhhuqoq&i=h5q3v77rtaek4jifdqbjc7vqzi\" target=\"_blank\" class=\"op-link\" title=\"Open in 1Password\"><span class=\"b b-planned\">\U0001F510 Gitea</span></a></td><td><span class=\"b b-online\">Online</span></td></tr>\n<tr><td>Guacamole</td><td><a href=\"https://guac.iamworkin.lan\" target=\"_blank\">https://guac.iamworkin.lan</a></td><td>443 (via Traefik)</td><td><a href=\"https://start.1password.com/open/i?v=qaphopopkryhbg353ukzhhuqoq&i=j7p2jalf5pcltawrx3c6o54yga\" target=\"_blank\" class=\"op-link\" title=\"Open in 1Password\"><span class=\"b b-planned\">\U0001F510 Apache Guacamole</span></a></td><td><span class=\"b b-online\">Online</span></td></tr>\n<tr><td>UnrealIRCd</td><td>irc.iamworkin.lan:6697 (TLS)</td><td>.200 &mdash; 6667/6697</td><td><a href=\"https://start.1password.com/open/i?v=qaphopopkryhbg353ukzhhuqoq&i=3kxdh2gkmczv5thiestcmlkb3m\" target=\"_blank\" class=\"op-link\" title=\"Open in 1Password\"><span class=\"b b-planned\">\U0001F510 IRC Services</span></a></td><td><span class=\"b b-online\">Online</span></td></tr>\n<tr><td>Anope (IRC Services)</td><td>irc.iamworkin.lan:8067</td><td>ClusterIP only</td><td><em>NickServ, ChanServ</em></td><td><span class=\"b b-offline\">CrashLoop</span></td></tr>\n<tr><td>Zabbix</td><td><a href=\"https://zabbix.iamworkin.lan\" target=\"_blank\">https://zabbix.iamworkin.lan</a></td><td>.203 trapper &mdash; Web via Traefik</td><td><a href=\"https://start.1password.com/open/i?v=qaphopopkryhbg353ukzhhuqoq&i=mtazpqfrqltylsfzb25ejmmqdy\" target=\"_blank\" class=\"op-link\" title=\"Open in 1Password\"><span class=\"b b-planned\">\U0001F510 Zabbix Monitoring</span></a></td><td><span class=\"b b-online\">Online</span></td></tr>\n<tr><td>Mail (docker-mailserver)</td><td>mail.iamworkin.lan</td><td>.202 &mdash; SMTP 25/465/587</td><td><a href=\"https://start.1password.com/open/i?v=qaphopopkryhbg353ukzhhuqoq&i=f6bdif4rxzgh6fi6lfirrtriaq\" target=\"_blank\" class=\"op-link\" title=\"Open in 1Password\"><span class=\"b b-planned\">\U0001F510 Mail Server</span></a></td><td><span class=\"b b-online\">Online</span></td></tr>\n<tr><td>Matrix Synapse</td><td><a href=\"https://matrix.iamworkin.lan\" target=\"_blank\">https://matrix.iamworkin.lan</a></td><td>443 (via Traefik)</td><td><a href=\"https://start.1password.com/open/i?v=qaphopopkryhbg353ukzhhuqoq&i=hd75oppaq6dvf6hnmyntnth2ey\" target=\"_blank\" class=\"op-link\" title=\"Open in 1Password\"><span class=\"b b-planned\">\U0001F510 Matrix Synapse</span></a></td><td><span class=\"b b-online\">Online</span></td></tr>\n<tr><td>Element Web</td><td><a href=\"https://element.iamworkin.lan\" target=\"_blank\">https://element.iamworkin.lan</a></td><td>443 (via Traefik)</td><td><em>Uses Matrix account</em></td><td><span class=\"b b-online\">Online</span></td></tr>\n<tr><td>TeamSpeak</td><td>ts.iamworkin.lan</td><td>.205 &mdash; 9987/UDP, 30033, 10011</td><td><a href=\"https://start.1password.com/open/i?v=qaphopopkryhbg353ukzhhuqoq&i=736u546ufij75d36socv5lhop4\" target=\"_blank\" class=\"op-link\" title=\"Open in 1Password\"><span class=\"b b-planned\">\U0001F510 TeamSpeak</span></a></td><td><span class=\"b b-online\">Online</span></td></tr>\n<tr><td>FlowerCore Landing</td><td>flowercore.io</td><td>443 (via Traefik, Cloudflare)</td><td><em>Static page</em></td><td><span class=\"b b-online\">Online</span></td></tr>\n<tr><td>PKI Web</td><td><a href=\"https://pki.iamworkin.lan\" target=\"_blank\">https://pki.iamworkin.lan</a></td><td>443 (via Traefik)</td><td><em>Public (CRL/certs)</em></td><td><span class=\"b b-online\">Online</span></td></tr>\n<tr><td>Intranet</td><td><a href=\"https://intranet.iamworkin.lan\" target=\"_blank\">https://intranet.iamworkin.lan</a></td><td>443 (via Traefik)</td><td><em>Static page</em></td><td><span class=\"b b-online\">Online</span></td></tr>\n<tr><td>Snappymail</td><td><a href=\"https://mail-web.iamworkin.lan\" target=\"_blank\">https://mail-web.iamworkin.lan</a></td><td>443 (via Traefik)</td><td><a href=\"https://start.1password.com/open/i?v=qaphopopkryhbg353ukzhhuqoq&i=k5otwlgch366ozyc4yvrwohjya\" target=\"_blank\" class=\"op-link\" title=\"Open in 1Password\"><span class=\"b b-planned\">\U0001F510 Snappymail</span></a></td><td><span class=\"b b-online\">Online</span></td></tr>\n<tr><td>Telephony</td><td><a href=\"https://telephony.iamworkin.lan\" target=\"_blank\">https://telephony.iamworkin.lan</a></td><td>5100 (via Traefik + Cloudflare)</td><td><a href=\"https://start.1password.com/open/i?v=qaphopopkryhbg353ukzhhuqoq&i=uo5kuszy6pjijrxrkuugpdnsrq\" target=\"_blank\" class=\"op-link\" title=\"Open in 1Password\"><span class=\"b b-planned\">\U0001F510 Telephony</span></a></td><td><span class=\"b b-online\">Online</span></td></tr>\n<tr><td>Asterisk PBX</td><td>asterisk.iamworkin.lan:5060</td><td>.207 &mdash; SIP 5060/UDP, RTP 10000-20000</td><td><em>4 PJSIP ext, Twilio trunk</em></td><td><span class=\"b b-online\">Online</span></td></tr>\n<tr><td>Agent Zero</td><td><a href=\"https://agent-zero.iamworkin.lan\" target=\"_blank\">https://agent-zero.iamworkin.lan</a></td><td>443 (via Traefik)</td><td><a href=\"https://start.1password.com/open/i?v=qaphopopkryhbg353ukzhhuqoq&i=uo5kuszy6pjijrxrkuugpdnsrq\" target=\"_blank\" class=\"op-link\" title=\"Open in 1Password\"><span class=\"b b-planned\">\U0001F510 Agent Zero</span></a></td><td><span class=\"b b-online\">Online</span></td></tr>\n<tr><td>Agent Zero (WS)</td><td><a href=\"https://agent-zero-ws.iamworkin.lan\" target=\"_blank\">https://agent-zero-ws.iamworkin.lan</a></td><td>30050 (Traefik on BLUEJAY-WS)</td><td><em>Same credentials</em></td><td><span class=\"b b-online\">Online</span></td></tr>\n<tr><td>Ollama (WS)</td><td><a href=\"https://ollama-ws.iamworkin.lan\" target=\"_blank\">https://ollama-ws.iamworkin.lan</a></td><td>11434 (Traefik on BLUEJAY-WS)</td><td><em>No auth (LAN only)</em></td><td><span class=\"b b-online\">Online</span></td></tr>\n<tr><td>1Password Operator</td><td><em>In-cluster only</em></td><td>onepassword-system</td><td><em>Connect token</em></td><td><span class=\"b b-online\">Online</span></td></tr>\n<tr><td>Selenium Grid</td><td><a href=\"https://selenium.iamworkin.lan\" target=\"_blank\">https://selenium.iamworkin.lan</a></td><td>443 (via Traefik)</td><td><em>Hub + 2 Chrome + 1 Firefox</em></td><td><span class=\"b b-online\">Online</span></td></tr>\n<tr><td>FlowerCore.Signage</td><td><a href=\"https://signage.iamworkin.lan\" target=\"_blank\">https://signage.iamworkin.lan</a></td><td>443 (via Traefik, fc-signage ns)</td><td><em>Blazor + REST + gRPC, 4,611 tests</em></td><td><span class=\"b b-online\">Online</span></td></tr>\n<tr><td>FlowerCore.RemoteDesktop</td><td>remotedesktop.iamworkin.lan</td><td>443 (via Traefik, fc-desktop ns)</td><td><em>VDI platform, 34 tests, Phase 1</em></td><td><span class=\"b b-online\">Online</span></td></tr>\n<tr><td>WiFi Portal</td><td><a href=\"https://wifi.flowercore.io\" target=\"_blank\">wifi.flowercore.io</a></td><td>443 (Cloudflare-proxied, wifi-portal ns)</td><td><em>Captive portal for GUEST WiFi</em></td><td><span class=\"b b-online\">Online</span></td></tr>\n<tr><td>FlowerCore.Print.Web</td><td><a href=\"https://print.iamworkin.lan\" target=\"_blank\">https://print.iamworkin.lan</a></td><td>443 (noc-proxy &rarr; edge2:5200)</td><td><em>21 pages, 9 symbologies, 15 MCP, 530 tests</em></td><td><span class=\"b b-online\">Online</span></td></tr>\n<tr><td>FlowerCore.Kiosk</td><td>kiosk.iamworkin.lan (planned)</td><td>443 (Traefik, fc-kiosk ns, planned)</td><td><em>8 pages, 8 REST controllers, 13 MCP, 108 tests, Windows Service + WPF tray</em></td><td><span class=\"b b-planned\">Planned</span></td></tr>\n<tr><td>PiManager (piez)</td><td><a href=\"http://piez.iamworkin.lan:5000\" target=\"_blank\">http://piez.iamworkin.lan:5000</a></td><td>5000 (piez direct)</td><td><em>GPIO, I2C, SPI, 20 MCP</em></td><td><span class=\"b b-online\">Online</span></td></tr>\n<tr><td>PiManager (pirelay)</td><td><a href=\"http://pirelay.iamworkin.lan:5100\" target=\"_blank\">http://pirelay.iamworkin.lan:5100</a></td><td>5100 (pirelay direct)</td><td><em>4-ch relay, scheduling</em></td><td><span class=\"b b-online\">Online</span></td></tr>\n</tbody>\n</table>\n<h3>Monitoring</h3>\n<div class=\"card-grid\">\n <div class=\"card\">\n <div class=\"card-title\">Prometheus (RKE2 K8s &mdash; Primary)</div>\n <ul>\n <li><strong>Targets:</strong> ~36 scrape jobs (node-exporter: noc1 + 3 RKE2 + 4 Pi nodes, SNMP: pfSense + Switch + NAS + Printer, Print: CUPS + Print.Web OTEL + blackbox probes, PiManager: piez+pirelay, AI Stack: 4 blackbox probes, self + Grafana + blackbox)</li>\n <li><strong>Alert Rules:</strong> 25 rules in 5 groups (ai-stack: 3, print-services: 6, pi-fleet: 7, snmp-devices: 5, infrastructure: 4)</li>\n <li><strong>Thermal Alerting:</strong> <span class=\"b b-online\">LIVE</span> &mdash; alerts with <code>alert_channel=thermal_print</code> route to NuPrint 210 via irc-notify</li>\n <li><strong>IRC Alerts:</strong> <span class=\"b b-online\">LIVE</span> &mdash; Grafana &rarr; irc-notify &rarr; UnrealIRCd\
\ #alerts</li>\n <li><strong>Retention:</strong> 90 days, 10 Gi PVC</li>\n <li><strong>Config:</strong> <code>scripts/monitoring/prometheus.yml</code> (git) + K8s ConfigMap</li>\n </ul>\n </div>\n <div class=\"card\">\n <div class=\"card-title\">Grafana (RKE2 K8s)</div>\n <ul>\n <li><strong>Dashboards (12):</strong> ai-stack-health, print-services, pi-fleet, bluejay-edge-nodes, bluejay-network-overview, bluejay-operations, epson-et-3750-ecotank-printer, node-exporter-full &mdash; all backed up to <code>scripts/monitoring/grafana/</code></li>\n <li><strong>Datasource UID:</strong> <code>prometheus</code> (K8s monitoring ns)</li>\n <li><strong>Datasources:</strong> Prometheus (K8s ClusterIP), Zabbix (alexanderzobnin-zabbix-datasource)</li>\n <li><strong>Contact Points:</strong> IRC #alerts (default), Thermal Printer (critical alerts)</li>\n </ul>\n </div>\n <div class=\"card\">\n <div class=\"card-title\">Zabbix (RKE2) &mdash; 10+ Hosts</div>\n <ul>\n <li><strong>Agent Hosts (8):</strong> noc1, rke2-server, rke2-agent1, rke2-agent2, edge1, edge2, piez, pirelay</li>\n <li><strong>SNMP Hosts (4):</strong> pfSense, UniFi Switch, BlueJayNAS (DS1621+), Epson ET-3750 EcoTank</li>\n <li><strong>Local (1):</strong> Zabbix server self-check</li>\n <li><strong>Agent Version:</strong> Zabbix Agent 2 v7.0.22&ndash;7.2.15 on all 8 Linux nodes</li>\n <li><strong>Passive checks:</strong> Server= includes MetalLB VIP + RKE2 node IPs + pod CIDR</li>\n <li><strong>Counter Strategy:</strong> Simple change (delta), Change per second (rate), Calculated items (derived)</li>\n <li><strong>Note:</strong> Mac Mini (macOS) pending Zabbix agent setup</li>\n </ul>\n </div>\n</div>\n<h3>Pi Fleet Services (FlowerCore.PiManager)</h3>\n<table>\n<thead><tr><th>Device</th><th>URL</th><th>Port</th><th>Capabilities</th><th>Status</th></tr></thead>\n<tbody>\n<tr><td>piez (Pi 4)</td><td><a href=\"http://piez.iamworkin.lan:5000\" target=\"_blank\">http://piez.iamworkin.lan:5000</a></td><td>5000</td><td>GPIO, I2C, SPI, Expanders (MCP23017/PCF8574/74HC595) &mdash; 10 pages, 35 API, 20 MCP</td><td><span class=\"b b-online\">Online</span></td></tr>\n<tr><td>pirelay (Pi 3)</td><td><a href=\"http://pirelay.iamworkin.lan:5100\" target=\"_blank\">http://pirelay.iamworkin.lan:5100</a></td><td>5100</td><td>4-ch relay (KS0212, active-LOW), scheduling, usage tracking &mdash; 8 pages, relay API</td><td><span class=\"b b-online\">Online</span></td></tr>\n</tbody>\n</table>\n<div class=\"note\"><strong>PiManager:</strong> Unified .NET 10 service deployed to both Pi nodes with different <code>ASPNETCORE_ENVIRONMENT</code> overlays. Config-driven capabilities &mdash; same binary, different features per device. Supports relay presets: ks0212-4ch, walfront-16ch, sainsmart-8ch. API docs at <code>/scalar/v1</code> on each node.</div>\n\n<h3>Guacamole Connection Groups (23 connections)</h3>\n<table>\n<thead><tr><th>Group</th><th>Connections</th><th>Protocol</th></tr></thead>\n<tbody>\n<tr><td><strong>Kubernetes</strong> (3)</td><td>rke2-server, rke2-agent1, rke2-agent2</td><td>SSH</td></tr>\n<tr><td><strong>Network Devices</strong> (4)</td><td>pfSense, UniFi Cloud Key, Synology WiFi (SRM), BlueJayNAS</td><td>SSH</td></tr>\n<tr><td><strong>Servers</strong> (3)</td><td>noc1, Mac Mini (SSH), Mac Mini (VNC)</td><td>SSH/VNC</td></tr>\n<tr><td><strong>Edge Nodes</strong> (4)</td><td>edge1 (Pi 5 + AI), edge2 (Pi 4), piez (Pi 4 + EZ Connect), pirelay (Pi 3 + Relay)</td><td>SSH</td></tr>\n<tr><td><strong>AI Access (fcadmin)</strong> (9)</td><td>noc1, rke2-server, rke2-agent1, rke2-agent2, edge1, edge2, macmini, piez, pirelay</td><td>SSH</td></tr>\n</tbody>\n</table>\n<div class=\"note\"><strong>Guacamole credentials:</strong> All connection passwords are stored in the Guacamole MySQL database (synced from 1Password). The <strong>AI Access (fcadmin)</strong> group (ID 5) provides 9 SSH connections using the shared <a href=\"https://start.1password.com/open/i?v=qaphopopkryhbg353ukzhhuqoq&i=o4helobxaqrerkwd3oeag4dt6m\" target=\"_blank\" class=\"op-link\" title=\"Open in 1Password\"><span class=\"b b-planned\">\U0001F510 AI Shared SSH Key - fcadmin</span></a> for one-click AI agent access. Access at <a href=\"https://guac.iamworkin.lan\" target=\"_blank\">https://guac.iamworkin.lan</a> &mdash; <a href=\"https://start.1password.com/open/i?v=qaphopopkryhbg353ukzhhuqoq&i=j7p2jalf5pcltawrx3c6o54yga\" target=\"_blank\" class=\"op-link\" title=\"Open in 1Password\"><span class=\"b b-planned\">\U0001F510 Apache Guacamole</span></a></div>\n</div>\n\n<!-- ===== TAB: VPN & SECURITY ===== -->\n<div id=\"tab-vpn\" class=\"tab-content\">\n<h2>VPN &amp; Security</h2>\n<div class=\"note\"><strong>OpenVPN Status (2026-03-27):</strong> 9 servers (4 tun + 4 tap + FIT). Andrew + Matt TUN <span class=\"b b-online\">TESTED</span> from Mac (Home WiFi via NAT reflection + mobile hotspot). Split-tunnel DNS (DOMAIN-ROUTE) working. Cert-only auth (server_tls). Friendly DNS: vpn-andrew/matt.flowercore.io.</div>\n<h3>VPN Client Access</h3>\n<table>\n<thead><tr><th>Tenant</th><th>Hostname</th><th>Port</th><th>Auth</th><th>Status</th></tr></thead>\n<tbody>\n<tr><td><span class=\"b b-tenant\">ANDREW</span></td><td><code>vpn-andrew.flowercore.io</code></td><td>1194/UDP</td><td>Cert (TLS)</td><td><span class=\"b b-online\">Tested</span></td></tr>\n<tr><td><span class=\"b b-tenant\">MATT</span></td><td><code>vpn-matt.flowercore.io</code></td><td>1194/UDP</td><td>Cert (TLS)</td><td><span class=\"b b-online\">Tested</span></td></tr>\n<tr><td><span class=\"b b-tenant\">DUSTIN</span></td><td><code>vpn-dustin.flowercore.io</code></td><td>1194/UDP</td><td>Cert (TLS)</td><td><span class=\"b b-planned\">Ready</span></td></tr>\n<tr><td><span class=\"b b-tenant\">ERIK</span></td><td><code>vpn-erik.flowercore.io</code></td><td>1194/UDP</td><td>Cert (TLS)</td><td><span class=\"b b-planned\">Ready</span></td></tr>\n</tbody>\n</table>\n<div class=\"note\"><strong>Split-Tunnel DNS:</strong> Server pushes <code>dhcp-option DOMAIN-ROUTE</code> for iamworkin.lan, bluejay.lan, flowercore.io. macOS Viscosity/OpenVPN Connect automatically sets DNS mode to \"Split\" &mdash; only matching domains use VPN DNS (10.0.56.1).</div>\n<div class=\"note\"><strong>NAT Reflection:</strong> Pure NAT reflection enabled on all VPN port forwards. Internal clients (e.g., Home WiFi 10.0.58.x) can connect to public VIPs and pfSense hairpins the traffic. Works transparently with port translation (Matt: ext 1194 &rarr; int 1195).</div>\n<h3>OpenVPN Server Configuration</h3>\n<table>\n<thead><tr><th>Tenant</th><th>VIP</th><th>TUN Port</th><th>TAP Port</th><th>Tunnel (TUN)</th><th>Tunnel (TAP)</th><th>VLAN</th></tr></thead>\n<tbody>\n<tr><td><span class=\"b b-tenant\">ANDREW</span></td><td><span class=\"ip\">.17</span></td><td>1194/UDP</td><td>1195/UDP</td><td><span class=\"ip\">10.0.68.0/27</span></td><td><span class=\"ip\">10.0.68.128/27</span></td><td>60</td></tr>\n<tr><td><span class=\"b b-tenant\">MATT</span></td><td><span class=\"ip\">.19</span></td><td>1194/UDP</td><td>1195/UDP</td><td><span class=\"ip\">10.0.68.32/27</span></td><td><span class=\"ip\">10.0.68.160/27</span></td><td>61</td></tr>\n<tr><td><span class=\"b b-tenant\">DUSTIN</span></td><td><span class=\"ip\">.21</span></td><td>1194/UDP</td><td>1195/UDP</td><td><span class=\"ip\">10.0.68.64/27</span></td><td><span class=\"ip\">10.0.68.192/27</span></td><td>62</td></tr>\n<tr><td><span class=\"b b-tenant\">ERIK</span></td><td><span class=\"ip\">.23</span></td><td>1194/UDP</td><td>1195/UDP</td><td><span class=\"ip\">10.0.68.96/27</span></td><td><span class=\"ip\">10.0.68.224/27</span></td><td>63</td></tr>\n<tr><td><span class=\"b b-tenant\">FIT</span></td><td><span class=\"ip\">.25</span></td><td>1194/UDP</td><td>1195/UDP</td><td><span class=\"ip\">10.0.69.0/27</span></td><td><span class=\"ip\">10.0.69.128/27</span></td><td>69</td></tr>\n</tbody>\n</table>\n<h3>VPN Certificate Infrastructure</h3>\n<table>\n<thead><tr><th>Component</th><th>Details</th></tr></thead>\n<tbody>\n<tr><td>CA</td><td>BlueJay VPN CA (4096-bit RSA, SHA-256, 10-year)</td></tr>\n<tr><td>Server Certs</td><td>8 (one per VPN instance, 2048-bit RSA)</td></tr>\n<tr><td>Client Certs</td><td>4 (one per tenant, 2048-bit RSA)</td></tr>\n<tr><td>TLS Auth</td><td>Shared HMAC key across all servers</td></tr>\n<tr><td>Data Ciphers</td><td>AES-256-GCM, AES-128-GCM, CHACHA20-POLY1305</td></tr>\n</tbody>\n</table>\n<h3>IPsec Site-to-Site (Planned)</h3>\n<table>\n<thead><tr><th>Tunnel</th><th>Local</th><th>Remote</th><th>Phase 1</th><th>Phase 2 SAs</th></tr></thead>\n<tbody>\n<tr><td>Matt</td><td>.29 (pfSense WAN)</td><td>Matt's public IP</td><td>IKEv2, AES-256-GCM, DH 14+</td><td>MATT (10.0.61.0/24) + PROD (10.0.57.0/24)</td></tr>\n<tr><td>Dustin</td><td>.29 (pfSense WAN)</td><td>Dustin's public IP</td><td>IKEv2, AES-256-GCM, DH 14+</td><td>DUSTIN (10.0.62.0/24) + PROD (10.0.57.0/24)</td></tr>\n</tbody>\n</table>\n<h3>Security Policies</h3>\n<div class=\"card-grid\">\n <div class=\"card\">\n <div class=\"card-title\">Cloudflare Protection</div>\n <ul>\n <li><strong>SSL Mode:</strong> Full (strict) on all 6 zones</li>\n <li><strong>Origin Certs:</strong> *.flowercore.io + *.iamwork.in (15-year RSA), deployed across 8 K8s namespaces</li>\n <li><strong>HSTS:</strong> Enabled on all zones</li>\n <li><strong>Min TLS:</strong> 1.2</li>\n <li><strong>Anti-spoofing:</strong> null MX, SPF -all, DMARC reject on non-email domains</li>\n <li><strong>Cloudflare-only inbound:</strong> Port forwards for 80/443 restrict source to Cloudflare IP ranges</li>\n </ul>\n </div>\n <div class=\"card\">\n <div class=\"card-title\">SSH Key Policy</div>\n <ul>\n <li><strong>Key Type:</strong> ed25519 (deployed to all 10 physical nodes)</li>\n <li><strong>WSL Key:</strong> <code>stoltz@IAMWORKIN-WS</code> &mdash; deployed\
\ to noc1, rke2-server, rke2-agent1, rke2-agent2, edge1, edge2, piez, pirelay, Mac Mini, BLUEJAY-WS</li>\n <li><strong>noc1 Key:</strong> <code>noc1-root</code> + <code>rke2@bluejay</code> &mdash; management keys for remote nodes</li>\n <li><strong>Root Login:</strong> Key-only (PermitRootLogin without-password)</li>\n <li><strong>RKE2 Nodes:</strong> SELinux enforcing, <code>chcon -t ssh_home_t</code> on authorized_keys</li>\n <li><strong>Last verified:</strong> 2026-03-21 (all 9 nodes confirmed)</li>\n </ul>\n </div>\n <div class=\"card\">\n <div class=\"card-title\">Network Security Rules</div>\n <ul>\n <li><strong>Forced DNS:</strong> HOME/WORK/SCHOOL/GUEST block port 53 except to gateway</li>\n <li><strong>Blocked SMTP:</strong> Outbound 25/465/587 on HOME/WORK/SCHOOL/GUEST</li>\n <li><strong>Firewall Policy:</strong> Deny-all default, explicit allow per VLAN</li>\n <li><strong>Tenant Isolation:</strong> Tenants fully isolated from each other, only PROD + DNS + NAS + internet</li>\n </ul>\n </div>\n</div>\n<h3>PKI Hierarchy</h3>\n<table>\n<thead><tr><th>CA</th><th>Status</th><th>Purpose</th></tr></thead>\n<tbody>\n<tr><td>Root CA (IAmWorkin ACME CA)</td><td><span class=\"b b-online\">Operational</span></td><td>Trust anchor, ECDSA P-256, expires 2036. Install: <code>print.iamworkin.lan/ca.crt</code></td></tr>\n<tr><td>Intermediate CA</td><td><span class=\"b b-online\">Operational</span></td><td>IAmWorkin ACME CA Intermediate CA &mdash; signs all leaf certs</td></tr>\n<tr><td>ACME CA (step-ca on noc1:9443)</td><td><span class=\"b b-online\">Operational</span></td><td>Automated cert issuance. Consumers: cert-manager (K8s), pfSense ACME pkg, edge Traefik</td></tr>\n<tr><td>pfSense ACME</td><td><span class=\"b b-online\">Operational</span></td><td><code>pfsense.iamworkin.lan</code> cert via acme.sh + step-ca. Set as WebGUI cert (2026-03-27)</td></tr>\n<tr><td>Network CA</td><td><span class=\"b b-planned\">Planned</span></td><td>Switch, AP, pfSense device certs</td></tr>\n<tr><td>Windows AD CS CA</td><td><span class=\"b b-planned\">Planned</span></td><td>Domain-joined machine/user certs</td></tr>\n<tr><td>Internal Services CA</td><td><span class=\"b b-planned\">Planned</span></td><td>K8s service mesh, inter-service mTLS</td></tr>\n</tbody>\n</table>\n</div>\n\n<!-- ===== TAB: REMOTE ACCESS ===== -->\n<div id=\"tab-remote\" class=\"tab-content\">\n<h2>Remote Access &mdash; Blue Jay Gateway</h2>\n<div class=\"note\">Apache Guacamole with Blue Jay branding, 1Password vault integration, K8s exec, and embedded panels. All credentials resolved from 1Password at connection time &mdash; no passwords stored in Guacamole.</div>\n\n<div class=\"card-grid\">\n <div class=\"card\">\n <div class=\"card-title\"><span class=\"dot dot-green\"></span>Guacamole Web UI</div>\n <ul>\n <li><strong>URL:</strong> <a href=\"https://guac.iamworkin.lan/\" target=\"_blank\">guac.iamworkin.lan</a> <button class=\"copy-btn\" onclick=\"copyText('https://guac.iamworkin.lan/')\">copy</button></li>\n <li><strong>Version:</strong> 1.6.0 + Blue Jay branding</li>\n <li><strong>Admin:</strong> <a href=\"https://start.1password.com/open/i?v=qaphopopkryhbg353ukzhhuqoq\" target=\"_blank\" class=\"op-link\"><span class=\"b b-planned\">Guacamole</span></a></li>\n <li><strong>K8s:</strong> <code>guacamole</code> namespace</li>\n <li><strong>Ingress:</strong> Traefik &rarr; guacamole:8080 (WebSocket)</li>\n <li><strong>ArgoCD:</strong> <code>infra-guacamole</code></li>\n </ul>\n </div>\n <div class=\"card\">\n <div class=\"card-title\"><span class=\"dot dot-green\"></span>Extensions</div>\n <ul>\n <li><strong>Blue Jay Branding</strong> &mdash; Full dark theme, custom login, logo</li>\n <li><strong>1Password Vault</strong> &mdash; <code>${VAULT_PASSWORD}</code> token resolution</li>\n <li><strong>TOTP MFA</strong> &mdash; Required for all users</li>\n <li><strong>Auth Ban</strong> &mdash; 5 failures = 5min IP ban</li>\n <li><strong>JSON Auth</strong> &mdash; Signed tokens for embedded panels</li>\n <li><strong>Time Restrict</strong> &mdash; Per-connection time windows</li>\n <li><strong>Recording Storage</strong> &mdash; NFS (Synology) playback</li>\n <li><strong>Display Statistics</strong> &mdash; Performance metrics</li>\n </ul>\n </div>\n <div class=\"card\">\n <div class=\"card-title\"><span class=\"dot dot-green\"></span>1Password Integration</div>\n <ul>\n <li><strong>Connect URL:</strong> <code>onepassword-connect:8080</code> (K8s internal)</li>\n <li><strong>Vault:</strong> IAmWorkin (<code>qaphopopkryhbg353ukzhhuqoq</code>)</li>\n <li><strong>Token:</strong> Via <code>OnePasswordItem</code> CRD</li>\n <li><strong>Rotation:</strong> Automatic &mdash; change in 1Password, Guacamole picks up on next connect</li>\n <li><strong>Cache TTL:</strong> 5 minutes</li>\n </ul>\n </div>\n <div class=\"card\">\n <div class=\"card-title\"><span class=\"dot dot-green\"></span>Session Recording</div>\n <ul>\n <li><strong>Storage:</strong> NFS on Synology (<code>/volume1/guacamole/recordings</code>)</li>\n <li><strong>PVC:</strong> <code>guacamole-recordings-pvc</code> (50 Gi)</li>\n <li><strong>Format:</strong> Guacamole native (playable in browser)</li>\n <li><strong>Retention:</strong> Linked to connection history</li>\n </ul>\n </div>\n</div>\n\n<h3 style=\"margin-top:1.5rem;\">Connection Inventory (${VAULT_*} tokens &mdash; no hardcoded passwords)</h3>\n<table>\n<thead><tr><th>Connection</th><th>Protocol</th><th>Host</th><th>VLAN</th><th>1Password Item</th></tr></thead>\n<tbody>\n<tr style=\"background:var(--surface2);\"><td colspan=\"5\" style=\"font-weight:600;color:var(--gold);\">MGMT (VLAN 56) &mdash; Infrastructure</td></tr>\n<tr><td>pfSense &mdash; SSH</td><td><span class=\"b b-ok\" style=\"font-size:0.75em;\">SSH</span></td><td><span class=\"ip\">pfsense.iamworkin.lan</span></td><td>56</td><td>pfSense Admin</td></tr>\n<tr><td>Cloud Key &mdash; SSH</td><td><span class=\"b b-ok\" style=\"font-size:0.75em;\">SSH</span></td><td><span class=\"ip\">unifi.iamworkin.lan</span></td><td>56</td><td>UniFi CloudKey</td></tr>\n<tr><td>UniFi Switch &mdash; SSH</td><td><span class=\"b b-ok\" style=\"font-size:0.75em;\">SSH</span></td><td><span class=\"ip\">switch.iamworkin.lan</span></td><td>56</td><td>UniFi CloudKey</td></tr>\n<tr><td>noc1 &mdash; SSH</td><td><span class=\"b b-ok\" style=\"font-size:0.75em;\">SSH</span></td><td><span class=\"ip\">noc1.iamworkin.lan</span></td><td>56</td><td>noc1 Root SSH</td></tr>\n<tr><td>rke2-server &mdash; SSH</td><td><span class=\"b b-ok\" style=\"font-size:0.75em;\">SSH</span></td><td><span class=\"ip\">rke2-server.iamworkin.lan</span></td><td>56</td><td>RKE2 Server</td></tr>\n<tr><td>rke2-agent1 &mdash; SSH</td><td><span class=\"b b-ok\" style=\"font-size:0.75em;\">SSH</span></td><td><span class=\"ip\">rke2-agent1.iamworkin.lan</span></td><td>56</td><td>RKE2 Agent 1</td></tr>\n<tr><td>rke2-agent2 &mdash; SSH</td><td><span class=\"b b-ok\" style=\"font-size:0.75em;\">SSH</span></td><td><span class=\"ip\">rke2-agent2.iamworkin.lan</span></td><td>56</td><td>RKE2 Agent 2</td></tr>\n\n<tr style=\"background:var(--surface2);\"><td colspan=\"5\" style=\"font-weight:600;color:var(--accent);\">PROD (VLAN 57) &mdash; Production</td></tr>\n<tr><td>edge1 (Pi 5) &mdash; SSH</td><td><span class=\"b b-ok\" style=\"font-size:0.75em;\">SSH</span></td><td><span class=\"ip\">edge1.iamworkin.lan</span></td><td>57</td><td>Edge1 Pi5 SSH</td></tr>\n<tr><td>edge2 (Pi 4) &mdash; SSH</td><td><span class=\"b b-ok\" style=\"font-size:0.75em;\">SSH</span></td><td><span class=\"ip\">edge2.iamworkin.lan</span></td><td>57</td><td>Edge2 Pi4 SSH</td></tr>\n<tr><td>Mac Mini &mdash; SSH</td><td><span class=\"b b-ok\" style=\"font-size:0.75em;\">SSH</span></td><td><span class=\"ip\">macmini.iamworkin.lan</span></td><td>57</td><td>Mac Mini</td></tr>\n<tr><td>Mac Mini &mdash; VNC</td><td><span class=\"b b-warn\" style=\"font-size:0.75em;\">VNC</span></td><td><span class=\"ip\">macmini.iamworkin.lan</span></td><td>57</td><td>Mac Mini</td></tr>\n\n<tr style=\"background:var(--surface2);\"><td colspan=\"5\" style=\"font-weight:600;color:var(--green);\">HOME (VLAN 58) &mdash; Home Network</td></tr>\n<tr><td>Synology NAS &mdash; SSH</td><td><span class=\"b b-ok\" style=\"font-size:0.75em;\">SSH</span></td><td><span class=\"ip\">nas.iamworkin.lan</span></td><td>58</td><td>Synology NAS</td></tr>\n<tr><td>Synology WiFi &mdash; SSH</td><td><span class=\"b b-ok\" style=\"font-size:0.75em;\">SSH</span></td><td><span class=\"ip\">wifi.iamworkin.lan</span></td><td>58</td><td>Synology SRM</td></tr>\n<tr><td>piez (Pi 4) &mdash; SSH</td><td><span class=\"b b-ok\" style=\"font-size:0.75em;\">SSH</span></td><td><span class=\"ip\">piez.iamworkin.lan</span></td><td>58</td><td>PiEZ SSH</td></tr>\n<tr><td>pirelay (Pi 3) &mdash; SSH</td><td><span class=\"b b-ok\" style=\"font-size:0.75em;\">SSH</span></td><td><span class=\"ip\">pirelay.iamworkin.lan</span></td><td>58</td><td>PiRelay SSH</td></tr>\n\n<tr style=\"background:var(--surface2);\"><td colspan=\"5\" style=\"font-weight:600;color:var(--green);\">AI Access &mdash; fcadmin (Group 5, SSH key auth)</td></tr>\n<tr><td>noc1 &mdash; fcadmin</td><td><span class=\"b b-ok\" style=\"font-size:0.75em;\">SSH</span></td><td><span class=\"ip\">noc1.iamworkin.lan</span></td><td>56</td><td><a href=\"https://start.1password.com/open/i?v=qaphopopkryhbg353ukzhhuqoq&i=o4helobxaqrerkwd3oeag4dt6m\" target=\"_blank\" class=\"op-link\" title=\"Open in 1Password\"><span class=\"b b-planned\">\U0001F510 fcadmin</span></a> <a href=\"https://guac.iamworkin.lan/#/client/MjcAYwBteXNxbA==\" target=\"_blank\" class=\"op-link\" title=\"Open in Guacamole\"><span class=\"b b-online\">▶ Connect</span></a></td></tr>\n<tr><td>rke2-server &mdash; fcadmin</td><td><span\
\ class=\"b b-ok\" style=\"font-size:0.75em;\">SSH</span></td><td><span class=\"ip\">rke2-server.iamworkin.lan</span></td><td>56</td><td><a href=\"https://start.1password.com/open/i?v=qaphopopkryhbg353ukzhhuqoq&i=o4helobxaqrerkwd3oeag4dt6m\" target=\"_blank\" class=\"op-link\" title=\"Open in 1Password\"><span class=\"b b-planned\">\U0001F510 fcadmin</span></a> <a href=\"https://guac.iamworkin.lan/#/client/MjgAYwBteXNxbA==\" target=\"_blank\" class=\"op-link\" title=\"Open in Guacamole\"><span class=\"b b-online\">▶ Connect</span></a></td></tr>\n<tr><td>rke2-agent1 &mdash; fcadmin</td><td><span class=\"b b-ok\" style=\"font-size:0.75em;\">SSH</span></td><td><span class=\"ip\">rke2-agent1.iamworkin.lan</span></td><td>56</td><td><a href=\"https://start.1password.com/open/i?v=qaphopopkryhbg353ukzhhuqoq&i=o4helobxaqrerkwd3oeag4dt6m\" target=\"_blank\" class=\"op-link\" title=\"Open in 1Password\"><span class=\"b b-planned\">\U0001F510 fcadmin</span></a> <a href=\"https://guac.iamworkin.lan/#/client/MjkAYwBteXNxbA==\" target=\"_blank\" class=\"op-link\" title=\"Open in Guacamole\"><span class=\"b b-online\">▶ Connect</span></a></td></tr>\n<tr><td>rke2-agent2 &mdash; fcadmin</td><td><span class=\"b b-ok\" style=\"font-size:0.75em;\">SSH</span></td><td><span class=\"ip\">rke2-agent2.iamworkin.lan</span></td><td>56</td><td><a href=\"https://start.1password.com/open/i?v=qaphopopkryhbg353ukzhhuqoq&i=o4helobxaqrerkwd3oeag4dt6m\" target=\"_blank\" class=\"op-link\" title=\"Open in 1Password\"><span class=\"b b-planned\">\U0001F510 fcadmin</span></a> <a href=\"https://guac.iamworkin.lan/#/client/MzAAYwBteXNxbA==\" target=\"_blank\" class=\"op-link\" title=\"Open in Guacamole\"><span class=\"b b-online\">▶ Connect</span></a></td></tr>\n<tr><td>edge1 &mdash; fcadmin</td><td><span class=\"b b-ok\" style=\"font-size:0.75em;\">SSH</span></td><td><span class=\"ip\">edge1.iamworkin.lan</span></td><td>57</td><td><a href=\"https://start.1password.com/open/i?v=qaphopopkryhbg353ukzhhuqoq&i=o4helobxaqrerkwd3oeag4dt6m\" target=\"_blank\" class=\"op-link\" title=\"Open in 1Password\"><span class=\"b b-planned\">\U0001F510 fcadmin</span></a> <a href=\"https://guac.iamworkin.lan/#/client/MzEAYwBteXNxbA==\" target=\"_blank\" class=\"op-link\" title=\"Open in Guacamole\"><span class=\"b b-online\">▶ Connect</span></a></td></tr>\n<tr><td>edge2 &mdash; fcadmin</td><td><span class=\"b b-ok\" style=\"font-size:0.75em;\">SSH</span></td><td><span class=\"ip\">edge2.iamworkin.lan</span></td><td>57</td><td><a href=\"https://start.1password.com/open/i?v=qaphopopkryhbg353ukzhhuqoq&i=o4helobxaqrerkwd3oeag4dt6m\" target=\"_blank\" class=\"op-link\" title=\"Open in 1Password\"><span class=\"b b-planned\">\U0001F510 fcadmin</span></a> <a href=\"https://guac.iamworkin.lan/#/client/MzIAYwBteXNxbA==\" target=\"_blank\" class=\"op-link\" title=\"Open in Guacamole\"><span class=\"b b-online\">▶ Connect</span></a></td></tr>\n<tr><td>macmini &mdash; fcadmin</td><td><span class=\"b b-ok\" style=\"font-size:0.75em;\">SSH</span></td><td><span class=\"ip\">macmini.iamworkin.lan</span></td><td>57</td><td><a href=\"https://start.1password.com/open/i?v=qaphopopkryhbg353ukzhhuqoq&i=o4helobxaqrerkwd3oeag4dt6m\" target=\"_blank\" class=\"op-link\" title=\"Open in 1Password\"><span class=\"b b-planned\">\U0001F510 fcadmin</span></a> <a href=\"https://guac.iamworkin.lan/#/client/MzMAYwBteXNxbA==\" target=\"_blank\" class=\"op-link\" title=\"Open in Guacamole\"><span class=\"b b-online\">▶ Connect</span></a></td></tr>\n<tr><td>piez &mdash; fcadmin</td><td><span class=\"b b-ok\" style=\"font-size:0.75em;\">SSH</span></td><td><span class=\"ip\">piez.iamworkin.lan</span></td><td>58</td><td><a href=\"https://start.1password.com/open/i?v=qaphopopkryhbg353ukzhhuqoq&i=o4helobxaqrerkwd3oeag4dt6m\" target=\"_blank\" class=\"op-link\" title=\"Open in 1Password\"><span class=\"b b-planned\">\U0001F510 fcadmin</span></a> <a href=\"https://guac.iamworkin.lan/#/client/MzQAYwBteXNxbA==\" target=\"_blank\" class=\"op-link\" title=\"Open in Guacamole\"><span class=\"b b-online\">▶ Connect</span></a></td></tr>\n<tr><td>pirelay &mdash; fcadmin</td><td><span class=\"b b-ok\" style=\"font-size:0.75em;\">SSH</span></td><td><span class=\"ip\">pirelay.iamworkin.lan</span></td><td>58</td><td><a href=\"https://start.1password.com/open/i?v=qaphopopkryhbg353ukzhhuqoq&i=o4helobxaqrerkwd3oeag4dt6m\" target=\"_blank\" class=\"op-link\" title=\"Open in 1Password\"><span class=\"b b-planned\">\U0001F510 fcadmin</span></a> <a href=\"https://guac.iamworkin.lan/#/client/MzUAYwBteXNxbA==\" target=\"_blank\" class=\"op-link\" title=\"Open in Guacamole\"><span class=\"b b-online\">▶ Connect</span></a></td></tr>\n\n<tr style=\"background:var(--surface2);\"><td colspan=\"5\" style=\"font-weight:600;color:var(--cyan);\">Kubernetes &mdash; Pod Exec (auto-synced every 2min)</td></tr>\n<tr><td>argocd-server</td><td><span class=\"b b-planned\" style=\"font-size:0.75em;\">K8S</span></td><td>kubernetes.default.svc</td><td>&mdash;</td><td>(ServiceAccount)</td></tr>\n<tr><td>gitea-0</td><td><span class=\"b b-planned\" style=\"font-size:0.75em;\">K8S</span></td><td>kubernetes.default.svc</td><td>&mdash;</td><td>(ServiceAccount)</td></tr>\n<tr><td>asterisk</td><td><span class=\"b b-planned\" style=\"font-size:0.75em;\">K8S</span></td><td>kubernetes.default.svc</td><td>&mdash;</td><td>(ServiceAccount)</td></tr>\n<tr><td>zabbix-server</td><td><span class=\"b b-planned\" style=\"font-size:0.75em;\">K8S</span></td><td>kubernetes.default.svc</td><td>&mdash;</td><td>(ServiceAccount)</td></tr>\n<tr><td>synapse</td><td><span class=\"b b-planned\" style=\"font-size:0.75em;\">K8S</span></td><td>kubernetes.default.svc</td><td>&mdash;</td><td>(ServiceAccount)</td></tr>\n<tr><td>unrealircd</td><td><span class=\"b b-planned\" style=\"font-size:0.75em;\">K8S</span></td><td>kubernetes.default.svc</td><td>&mdash;</td><td>(ServiceAccount)</td></tr>\n</tbody>\n</table>\n\n<h3 style=\"margin-top:1.5rem;\">Embedded Panel (Quick SSH)</h3>\n<div class=\"note\">The embedded panel below uses <code>guacamole-common-js</code> to connect directly to Guacamole's tunnel servlet. Requires authentication to <a href=\"https://guac.iamworkin.lan/\" target=\"_blank\">guac.iamworkin.lan</a> first.</div>\n<div class=\"card\" style=\"padding:0;overflow:hidden;\">\n <div class=\"guac-embed\" id=\"panel-noc1\" data-connection=\"noc1 &mdash; SSH\" data-width=\"100%\" data-height=\"400\" style=\"width:100%;\">\n <div style=\"display:flex;align-items:center;gap:8px;padding:8px 12px;background:var(--surface2);border-bottom:1px solid var(--border);\">\n <span style=\"width:8px;height:8px;border-radius:50%;background:var(--text-muted);\" id=\"guac-status-dot\"></span>\n <span style=\"color:var(--text-heading);font-weight:600;flex:1;\">noc1 &mdash; SSH Terminal</span>\n <a href=\"https://guac.iamworkin.lan/#/client/bm9jMS1zc2g\" target=\"_blank\" style=\"color:var(--accent);text-decoration:none;font-size:0.85em;\">Open in Guacamole &rarr;</a>\n </div>\n <div style=\"height:400px;background:#000;display:flex;align-items:center;justify-content:center;color:var(--text-muted);font-family:var(--font-mono, monospace);\">\n <p>Connect via <a href=\"https://guac.iamworkin.lan/\" target=\"_blank\" style=\"color:var(--accent);\">Blue Jay Remote Access</a> to use the embedded terminal.<br>\n <span style=\"font-size:0.85em;opacity:0.7;\">Requires <code>bluejay-guac-embed.js</code> and <code>guacamole-common-js</code></span></p>\n </div>\n </div>\n</div>\n\n<h3 style=\"margin-top:1.5rem;\">Deployment Details</h3>\n<table>\n<thead><tr><th>Component</th><th>Image</th><th>Replicas</th><th>Resources</th></tr></thead>\n<tbody>\n<tr><td>guacamole (Tomcat)</td><td><code>fc-guacamole:1.6.0-bluejay</code></td><td>1</td><td>200m-1 CPU, 512Mi-1Gi</td></tr>\n<tr><td>guacd (C proxy)</td><td><code>guacamole/guacd:1.6.0</code></td><td>1</td><td>200m-2 CPU, 256Mi-1Gi</td></tr>\n<tr><td>MySQL 8</td><td><code>mysql:8.0</code></td><td>1 (StatefulSet)</td><td>100m-500m CPU, 256-512Mi</td></tr>\n<tr><td>K8s Sync CronJob</td><td><code>bitnami/kubectl:1.34</code></td><td>every 2min</td><td>minimal</td></tr>\n</tbody>\n</table>\n\n<h3 style=\"margin-top:1.5rem;\">Files Reference</h3>\n<table>\n<thead><tr><th>Artifact</th><th>Path</th></tr></thead>\n<tbody>\n<tr><td>Design plan</td><td><code>docs/infrastructure/guacamole-customization-plan.md</code></td></tr>\n<tr><td>K8s manifests</td><td><code>k8s/guacamole/*.yaml</code></td></tr>\n<tr><td>Branding extension</td><td><code>k8s/guacamole/extensions/bluejay-branding/</code></td></tr>\n<tr><td>1Password vault extension</td><td><code>k8s/guacamole/extensions/1password-vault/</code></td></tr>\n<tr><td>Embed library</td><td><code>k8s/guacamole/scripts/bluejay-guac-embed.js</code></td></tr>\n<tr><td>Dockerfile</td><td><code>k8s/guacamole/Dockerfile</code></td></tr>\n<tr><td>Bootstrap script</td><td><code>k8s/guacamole/scripts/bootstrap-connections.sh</code></td></tr>\n<tr><td>Build/deploy script</td><td><code>k8s/guacamole/scripts/build-image.sh</code></td></tr>\n</tbody>\n</table>\n</div>\n\n<!-- ===== TAB: EDGE NODES ===== -->\n<div id=\"tab-edge\" class=\"tab-content\">\n<h2>Edge Nodes</h2>\n<div class=\"card-grid\">\n <div class=\"card\">\n <div class=\"card-title\"><span class=\"dot dot-green\"></span>edge1 &mdash; Raspberry Pi 5 + Hailo AI</div>\n <ul>\n <li><strong>IP:</strong> <span class=\"ip\">edge1.iamworkin.lan</span> <button class=\"copy-btn\" onclick=\"copyText('edge1.iamworkin.lan')\">copy</button> (PROD VLAN 57)</li>\n <li><strong>SSH:</strong> <code>stoltz@edge1.iamworkin.lan</code> <button class=\"copy-btn\" onclick=\"copyText('ssh stoltz@edge1.iamworkin.lan')\">copy</button></li>\n <li><strong>Password:</strong> <a href=\"https://start.1password.com/open/i?v=qaphopopkryhbg353ukzhhuqoq&i=62jgyxh6emltmharocvzxq2oue\" target=\"_blank\" class=\"op-link\" title=\"Open in 1Password\"><span\
\ class=\"b b-planned\">\U0001F510 Edge1 Pi5 SSH</span></a></li>\n <li><strong>Hardware:</strong> Pi 5 16GB + Hailo-10H 40 TOPS (AI HAT+ 2)</li>\n <li><strong>OS:</strong> Debian 13 (trixie) aarch64</li>\n <li><strong>PCIe:</strong> Gen 3 x1 (8.0 GT/s)</li>\n <li><strong>Power:</strong> 27W USB-C</li>\n <li><strong>.NET SDK:</strong> 10.0.103</li>\n <li><strong>GitHub Runner:</strong> v2.332.0 (labels: pi5, hailo)</li>\n <li><strong>Node Exporter:</strong> :9100</li>\n <li><strong>Puppet:</strong> profile::edge_ai</li>\n <li><strong>Zabbix Agent:</strong> v7.2.15 (passive, port 10050)</li>\n <li><strong>Switch Port:</strong> 13</li>\n <li><strong>Disk:</strong> 93% (2.0GB free)</li>\n <li><strong>Guacamole:</strong> <a href=\"https://guac.iamworkin.lan/#/client/MzEAYwBteXNxbA==\" target=\"_blank\"><span class=\"b b-online\">▶ fcadmin SSH</span></a></li>\n </ul>\n </div>\n <div class=\"card\">\n <div class=\"card-title\"><span class=\"dot dot-green\"></span>edge2 &mdash; Raspberry Pi 4 (Argon ONE)</div>\n <ul>\n <li><strong>IP:</strong> <span class=\"ip\">edge2.iamworkin.lan</span> <button class=\"copy-btn\" onclick=\"copyText('edge2.iamworkin.lan')\">copy</button> (PROD VLAN 57)</li>\n <li><strong>SSH:</strong> <code>stoltz@edge2.iamworkin.lan</code> <button class=\"copy-btn\" onclick=\"copyText('ssh stoltz@edge2.iamworkin.lan')\">copy</button></li>\n <li><strong>Password:</strong> <a href=\"https://start.1password.com/open/i?v=qaphopopkryhbg353ukzhhuqoq&i=kyeqfwy76msqb4cfdjueaq5uta\" target=\"_blank\" class=\"op-link\" title=\"Open in 1Password\"><span class=\"b b-planned\">\U0001F510 Edge2 Pi4 SSH</span></a></li>\n <li><strong>Hardware:</strong> Pi 4 Model B 4GB, Argon ONE case</li>\n <li><strong>OS:</strong> Debian 13 (trixie) aarch64</li>\n <li><strong>Fan Control:</strong> argononed.service (55&deg;C=10%, 60&deg;C=55%, 65&deg;C=100%)</li>\n <li><strong>.NET SDK:</strong> 10.0.103</li>\n <li><strong>Print Service:</strong> <span class=\"b b-online\">LIVE</span> <a href=\"https://print.iamworkin.lan\" target=\"_blank\">FlowerCore.Print.Web</a> (21 pages, 15 MCP tools, 530 tests, HTTPS via noc-proxy, CUPS+AirPrint)</li>\n <li><strong>GitHub Runners:</strong> v2.332.0 &mdash; MySQL (edge2-mysql), PHP (edge2-php)</li>\n <li><strong>Node Exporter:</strong> :9100</li>\n <li><strong>Puppet:</strong> profile::edge_runner</li>\n <li><strong>Zabbix Agent:</strong> v7.2.15 (passive, port 10050)</li>\n <li><strong>Switch Port:</strong> 11</li>\n <li><strong>Guacamole:</strong> SSH connection in Edge Nodes group | <a href=\"https://guac.iamworkin.lan/#/client/MzIAYwBteXNxbA==\" target=\"_blank\"><span class=\"b b-online\">▶ fcadmin SSH</span></a></li>\n </ul>\n </div>\n <div class=\"card\">\n <div class=\"card-title\"><span class=\"dot dot-green\"></span>piez &mdash; Raspberry Pi 4 + EZ Connect</div>\n <ul>\n <li><strong>IP:</strong> <span class=\"ip\">piez.iamworkin.lan</span> <button class=\"copy-btn\" onclick=\"copyText('piez.iamworkin.lan')\">copy</button> (HOME VLAN 58, WiFi)</li>\n <li><strong>SSH:</strong> <code>stoltz@piez.iamworkin.lan</code> <button class=\"copy-btn\" onclick=\"copyText('ssh stoltz@piez.iamworkin.lan')\">copy</button></li>\n <li><strong>Password:</strong> <a href=\"https://start.1password.com/open/i?v=qaphopopkryhbg353ukzhhuqoq&i=62jgyxh6emltmharocvzxq2oue\" target=\"_blank\" class=\"op-link\" title=\"Open in 1Password\"><span class=\"b b-planned\">\U0001F510 piez SSH</span></a></li>\n <li><strong>Hardware:</strong> Pi 4 Model B 4GB + Pi EZ Connect board</li>\n <li><strong>OS:</strong> Debian 13 (trixie) aarch64</li>\n <li><strong>Role:</strong> GPIO prototyping, breadboard dev, I2C/SPI sensors</li>\n <li><strong>.NET SDK:</strong> 10.0.201</li>\n <li><strong>Web:</strong> <span class=\"b b-online\">LIVE</span> <a href=\"http://piez.iamworkin.lan:5000\" target=\"_blank\">FlowerCore.PiManager :5000</a> (10 pages, 35 API endpoints, 20 MCP tools)</li>\n <li><strong>API Docs:</strong> <a href=\"http://piez.iamworkin.lan:5000/scalar/v1\" target=\"_blank\">Scalar :5000/scalar/v1</a></li>\n <li><strong>Capabilities:</strong> GPIO, I2C, SPI, Expanders (MCP23017/PCF8574/74HC595)</li>\n <li><strong>Node Exporter:</strong> :9100</li>\n <li><strong>Zabbix Agent:</strong> v7.0.22 (passive, port 10050)</li>\n <li><strong>Guacamole:</strong> SSH connection in Edge Nodes group | <a href=\"https://guac.iamworkin.lan/#/client/MzQAYwBteXNxbA==\" target=\"_blank\"><span class=\"b b-online\">▶ fcadmin SSH</span></a></li>\n <li><strong>Dashboard:</strong> <a href=\"piez-prototyping.html\">piez-prototyping.html</a></li>\n </ul>\n </div>\n <div class=\"card\">\n <div class=\"card-title\"><span class=\"dot dot-green\"></span>pirelay &mdash; Raspberry Pi 3 + 4-Ch Relay</div>\n <ul>\n <li><strong>IP:</strong> <span class=\"ip\">pirelay.iamworkin.lan</span> <button class=\"copy-btn\" onclick=\"copyText('pirelay.iamworkin.lan')\">copy</button> (HOME VLAN 58)</li>\n <li><strong>SSH:</strong> <code>stoltz@pirelay.iamworkin.lan</code> <button class=\"copy-btn\" onclick=\"copyText('ssh stoltz@pirelay.iamworkin.lan')\">copy</button></li>\n <li><strong>Password:</strong> <a href=\"https://start.1password.com/open/i?v=qaphopopkryhbg353ukzhhuqoq&i=62jgyxh6emltmharocvzxq2oue\" target=\"_blank\" class=\"op-link\" title=\"Open in 1Password\"><span class=\"b b-planned\">\U0001F510 pirelay SSH</span></a></li>\n <li><strong>Hardware:</strong> Pi 3 Model B v1.2, 906 MB RAM + Keyestudio KS0212 4-channel relay shield</li>\n <li><strong>OS:</strong> Debian 13 (trixie) aarch64</li>\n <li><strong>Role:</strong> Relay controller, home automation prototyping</li>\n <li><strong>Web:</strong> <span class=\"b b-online\">LIVE</span> <a href=\"http://pirelay.iamworkin.lan:5100\" target=\"_blank\">FlowerCore.PiManager :5100</a> (relay preset: ks0212-4ch)</li>\n <li><strong>API Docs:</strong> <a href=\"http://pirelay.iamworkin.lan:5100/scalar/v1\" target=\"_blank\">Scalar :5100/scalar/v1</a></li>\n <li><strong>GPIO (BCM, active-LOW):</strong> CH1=GPIO4, CH2=GPIO22, CH3=GPIO6, CH4=GPIO26</li>\n <li><strong>Relay Ratings:</strong> 10A @ 250VAC / 30VDC per channel</li>\n <li><strong>Node Exporter:</strong> :9100</li>\n <li><strong>Zabbix Agent:</strong> v7.0.22 (passive, port 10050)</li>\n <li><strong>Guacamole:</strong> SSH connection in Edge Nodes group | <a href=\"https://guac.iamworkin.lan/#/client/MzUAYwBteXNxbA==\" target=\"_blank\"><span class=\"b b-online\">▶ fcadmin SSH</span></a></li>\n <li><strong>Dashboard:</strong> <a href=\"relay-controller.html\">relay-controller.html</a></li>\n </ul>\n </div>\n <div class=\"card\">\n <div class=\"card-title\"><span class=\"dot dot-green\"></span>Mac Mini (Build/Test Node)</div>\n <ul>\n <li><strong>IP:</strong> <span class=\"ip\">macmini.iamworkin.lan</span> <button class=\"copy-btn\" onclick=\"copyText('macmini.iamworkin.lan')\">copy</button> (PROD VLAN 57)</li>\n <li><strong>SSH:</strong> <code>bluejay@macmini.iamworkin.lan</code> <button class=\"copy-btn\" onclick=\"copyText('ssh bluejay@macmini.iamworkin.lan')\">copy</button></li>\n <li><strong>Credentials:</strong> <a href=\"https://start.1password.com/open/i?v=qaphopopkryhbg353ukzhhuqoq&i=npkseg2zvbmxmtg7qrseredvym\" target=\"_blank\" class=\"op-link\" title=\"Open in 1Password\"><span class=\"b b-planned\">\U0001F510 Mac Mini</span></a></li>\n <li><strong>VNC:</strong> <code>vnc://macmini.iamworkin.lan:5900</code> &mdash; <a href=\"https://start.1password.com/open/i?v=qaphopopkryhbg353ukzhhuqoq&i=npkseg2zvbmxmtg7qrseredvym\" target=\"_blank\" class=\"op-link\" title=\"Open in 1Password\"><span class=\"b b-planned\">\U0001F510 Mac Mini</span></a></li>\n <li><strong>Hardware:</strong> Apple M1, 16GB RAM, 926GB SSD</li>\n <li><strong>OS:</strong> macOS 26.3.1 (Darwin 25.3.0)</li>\n <li><strong>Role:</strong> Xcode builds, Selenium Grid node, automated browser/app testing</li>\n <li><strong>Guacamole:</strong> SSH + VNC connections in Servers group | <a href=\"https://guac.iamworkin.lan/#/client/MzMAYwBteXNxbA==\" target=\"_blank\"><span class=\"b b-online\">▶ fcadmin SSH</span></a></li>\n </ul>\n </div>\n</div>\n\n<h3>Edge2 &mdash; Print Service</h3>\n<div class=\"card-grid\">\n <div class=\"card\">\n <div class=\"card-title\"><span class=\"dot dot-green\"></span>FlowerCore.Print.Web</div>\n <ul>\n <li><strong>URL:</strong> <a href=\"https://print.iamworkin.lan\" target=\"_blank\">https://print.iamworkin.lan</a> (HTTPS via K8s noc-proxy + step-ca ACME)</li>\n <li><strong>Direct:</strong> <a href=\"https://print.iamworkin.lan\" target=\"_blank\">https://print.iamworkin.lan</a> (HTTP, edge2 direct)</li>\n <li><strong>Pages:</strong> 21 Blazor pages (Dashboard, Queue, Config, QR Generator, Barcode Generator, Product Lookup, AI Assistant, Test Print, Print Log, Webhooks, Batch Print, Web Print, SharePrint, QuickPrint, Paper Management, Admin, About, Error, NotFound + more)</li>\n <li><strong>Tests:</strong> 530 (ALL PASS)</li>\n <li><strong>Symbologies:</strong> 9 (Code128, EAN-13, QR, DataMatrix, ITF-14, UPC-A/E, Code39, Codabar)</li>\n <li><strong>Features:</strong> Product cache DB, AI label generation, batch/combo labels, smart paper roll tracking, waste analysis, source policies, receipt photo analysis, recipe scraper+bookmarklet, SignalR job broadcasts, role-based ACL (Admin/Operator/PrintClient), ISBN auto-format, scanner JS interop</li>\n <li><strong>MCP Tools:</strong> 15 tools for barcode/label generation, queue control, paper management, and waste summary</li>\n <li><strong>Thermal Printer:</strong> NuPrint 210 (58mm ESC/POS, Linux USB, <code>/dev/usb/lp0</code>)</li>\n\
\ <li><strong>CUPS:</strong> ZJ-58 raster driver, AirPrint/mDNS, cups_exporter (:9628)</li>\n <li><strong>Monitoring:</strong> OTEL metrics &rarr; Prometheus, thermal printer alerting pipeline</li>\n <li><strong>systemd:</strong> <code>flowercore-print.service</code> (auto-start)</li>\n </ul>\n </div>\n</div>\n<div class=\"note note-warn\"><strong>2026-03-25 session note:</strong> The print service stayed reachable and small endpoints remained fast, but larger IPv4 responses from <code>edge2</code> on <code>:5200</code> and <code>:9100</code> stalled after roughly <code>15-16 KB</code> from this Mac. That looked like a host-side transport problem on <code>edge2</code>, not a VLAN block or missing stylesheet issue. Detailed write-up: <a href=\"../infrastructure/print-service-session-learnings-2026-03-25.md\">print-service-session-learnings-2026-03-25.md</a></div>\n\n<h3>BLUEJAY-WS AI Services (10.0.56.20)</h3>\n<div class=\"card-grid\">\n <div class=\"card\">\n <div class=\"card-title\"><span class=\"dot dot-green\"></span>Agent Zero (WS)</div>\n <ul>\n <li><strong>URL:</strong> <a href=\"https://agent-zero-ws.iamworkin.lan\" target=\"_blank\">https://agent-zero-ws.iamworkin.lan</a> <button class=\"copy-btn\" onclick=\"copyText('https://agent-zero-ws.iamworkin.lan')\">copy</button></li>\n <li><strong>Port:</strong> 30050 (behind Traefik v3.6.12)</li>\n <li><strong>Runtime:</strong> Podman pod (systemd user service with linger)</li>\n <li><strong>Models:</strong> Chat: qwen3:32b, Utility: qwen2.5:3b, Embed: nomic-embed-text</li>\n <li><strong>Config:</strong> <code>/a0/usr/plugins/_model_config/config.json</code></li>\n <li><strong>Ollama Access:</strong> <code>host.containers.internal:11434</code> (NOT host IP)</li>\n </ul>\n </div>\n <div class=\"card\">\n <div class=\"card-title\"><span class=\"dot dot-green\"></span>Ollama 0.19.0 (WS)</div>\n <ul>\n <li><strong>URL:</strong> <a href=\"https://ollama-ws.iamworkin.lan\" target=\"_blank\">https://ollama-ws.iamworkin.lan</a> <button class=\"copy-btn\" onclick=\"copyText('https://ollama-ws.iamworkin.lan')\">copy</button></li>\n <li><strong>Port:</strong> 11434 (binds 0.0.0.0, LAN accessible)</li>\n <li><strong>GPU:</strong> AMD Radeon AI PRO R9700 32GB GDDR6 (ROCm + Vulkan)</li>\n <li><strong>Models:</strong> 14 installed (~155GB) on /home/ollama/.ollama/models (XFS)</li>\n <li><strong>Key Models:</strong> qwen3:32b, qwen3-coder:30b, devstral:24b, deepseek-r1:32b, gemma3:27b, phi4:14b</li>\n </ul>\n </div>\n <div class=\"card\">\n <div class=\"card-title\"><span class=\"dot dot-green\"></span>Traefik v3.6.12 (WS)</div>\n <ul>\n <li><strong>Dashboard:</strong> <a href=\"https://bluejay-ws.iamworkin.lan\" target=\"_blank\">https://bluejay-ws.iamworkin.lan</a></li>\n <li><strong>Runtime:</strong> Podman (root systemd service)</li>\n <li><strong>TLS:</strong> step-ca ACME auto-provisioned certs from IAmWorkin ACME CA</li>\n <li><strong>Routes:</strong> agent-zero-ws, ollama-ws, bluejay-ws</li>\n </ul>\n </div>\n <div class=\"card\">\n <div class=\"card-title\"><span class=\"dot dot-green\"></span>Piper TTS (WS)</div>\n <ul>\n <li><strong>Port:</strong> tcp://localhost:30052</li>\n <li><strong>Runtime:</strong> Podman pod</li>\n </ul>\n </div>\n</div>\n<div class=\"note\"><strong>Two Agent Zeros coexist:</strong> <code>agent-zero.iamworkin.lan</code> &rarr; 10.0.56.200 (RKE2 Traefik, Pi AI models) | <code>agent-zero-ws.iamworkin.lan</code> &rarr; 10.0.56.20 (BLUEJAY-WS R9700 GPU). BLUEJAY-WS hardware: Intel Xeon w3-2425, 32GB DDR5, AMD R9700 32GB GDDR6. openSUSE Leap 16 (dual boot Windows 11). Sleep/suspend disabled.</div>\n\n<h3>Edge1 AI &amp; Speech Services</h3>\n<div class=\"card-grid\">\n <div class=\"card\">\n <div class=\"card-title\"><span class=\"dot dot-green\"></span>Ollama (LLM Inference)</div>\n <ul>\n <li><strong>API:</strong> <span class=\"ip\">http://edge1.iamworkin.lan:11434</span> <button class=\"copy-btn\" onclick=\"copyText('http://edge1.iamworkin.lan:11434')\">copy</button></li>\n <li><strong>Model:</strong> qwen2.5-coder:7b (4.7GB Q4_K_M)</li>\n <li><strong>Managed by:</strong> profile::edge::ollama (Puppet)</li>\n <li><strong>Firewall:</strong> nftables port 11434 from MGMT+PROD</li>\n <li><strong>Note:</strong> SD card 95% full &mdash; one model max</li>\n </ul>\n </div>\n <div class=\"card\">\n <div class=\"card-title\"><span class=\"dot dot-green\"></span>Piper TTS (Text-to-Speech)</div>\n <ul>\n <li><strong>Version:</strong> piper-tts 1.4.1 in ~/piper-env venv</li>\n <li><strong>Voices:</strong> en_US-amy-low (16kHz) + en_US-amy-medium (22kHz)</li>\n <li><strong>Performance:</strong> RTF 0.10 (10x real-time), 222ms latency (short)</li>\n <li><strong>CPU Usage:</strong> 3/4 cores (271%)</li>\n <li><strong>Note:</strong> 16kHz matches G.711 natively for telephony</li>\n </ul>\n </div>\n <div class=\"card\">\n <div class=\"card-title\"><span class=\"dot dot-green\"></span>Hailo Whisper STT (Speech-to-Text)</div>\n <ul>\n <li><strong>Model:</strong> Whisper-Base HEF (131MB, v5.1.1)</li>\n <li><strong>Path:</strong> /opt/hailo-models/Whisper-Base.hef</li>\n <li><strong>Performance:</strong> RTF 0.05-0.11 (10-18x real-time)</li>\n <li><strong>Model Load:</strong> 1.2s cold start</li>\n <li><strong>Multi-process:</strong> VDevice for coexistence with Frigate</li>\n </ul>\n </div>\n <div class=\"card\">\n <div class=\"card-title\"><span class=\"dot dot-green\"></span>Speech Pipeline Service</div>\n <ul>\n <li><strong>API:</strong> <span class=\"ip\">http://edge1.iamworkin.lan:8500</span> <button class=\"copy-btn\" onclick=\"copyText('http://edge1.iamworkin.lan:8500')\">copy</button></li>\n <li><strong>Endpoints:</strong> POST /tts, POST /stt, GET /health</li>\n <li><strong>User:</strong> <code>speech</code> in <code>hailo</code> group</li>\n <li><strong>Managed by:</strong> profile::edge::speech_pipeline (Puppet)</li>\n <li><strong>Firewall:</strong> nftables port 8500 from MGMT+PROD</li>\n </ul>\n </div>\n <div class=\"card\">\n <div class=\"card-title\"><span class=\"dot dot-yellow\"></span>Twilio Voice Bridge (PoC)</div>\n <ul>\n <li><strong>Location:</strong> /opt/twilio-bridge/ on edge1</li>\n <li><strong>WebSocket:</strong> :8765 &bull; <strong>TwiML:</strong> :8766</li>\n <li><strong>Cloudflare Tunnel:</strong> bluejay-voice (3ddfa567-b0a7-40cb-9c57-7f20f3ec3637)</li>\n <li><strong>URLs:</strong> voice.bluejay.dev (TwiML), voice-ws.bluejay.dev (WS)</li>\n <li><strong>Services:</strong> cloudflared-tunnel on noc1, twilio-bridge + twilio-twiml on edge1</li>\n <li><strong>Status:</strong> <span class=\"b b-planned\">PoC</span> &mdash; STT fixed, TTS stream API mismatch</li>\n </ul>\n </div>\n</div>\n</div>\n\n<!-- ===== TAB: STORAGE ===== -->\n<div id=\"tab-storage\" class=\"tab-content\">\n<h2>Storage</h2>\n<div class=\"card-grid\">\n <div class=\"card\" style=\"border-top: 3px solid var(--accent);\">\n <div class=\"card-title\"><span class=\"dot dot-green\"></span>BlueJayNAS &mdash; Synology DS1621+</div>\n <ul>\n <li><strong>IP:</strong> <span class=\"ip\">nas.iamworkin.lan</span> (HOME VLAN 58, switch port 14)</li>\n <li><strong>DNS:</strong> <code>nas.iamworkin.lan</code>, <code>synology.iamworkin.lan</code></li>\n <li><strong>DSM:</strong> <a href=\"https://nas.iamworkin.lan:5001\" target=\"_blank\">https://nas.iamworkin.lan:5001</a> (v7.3.2-86009 Update 1)</li>\n <li><strong>SSH:</strong> <code>bluejay@nas.iamworkin.lan</code> <button class=\"copy-btn\" onclick=\"copyText('ssh bluejay@nas.iamworkin.lan')\">copy</button></li>\n <li><strong>Credentials:</strong> <a href=\"https://start.1password.com/open/i?v=qaphopopkryhbg353ukzhhuqoq&i=cbxfibct4j6jfm6ccsogpayjuy\" target=\"_blank\" class=\"op-link\" title=\"Open in 1Password\"><span class=\"b b-planned\">\U0001F510 BlueJayNAS</span></a></li>\n <li><strong>Model:</strong> DS1621+ (6-bay, AMD Ryzen V1500B)</li>\n <li><strong>Storage:</strong> 9.1 TB Btrfs (RAID), ~7.8 TB free</li>\n <li><strong>MAC:</strong> 00:11:32:f2:43:6b</li>\n <li><strong>TLS Cert:</strong> ca.iamworkin.lan (step-ca ACME, expires 2026-06-03)</li>\n <li><strong>NFS Domain:</strong> private.iamwork.in</li>\n <li><strong>2FA:</strong> TOTP enabled on DSM</li>\n </ul>\n </div>\n <div class=\"card\" style=\"border-top: 3px solid var(--green);\">\n <div class=\"card-title\">NFS Exports &amp; Services</div>\n <ul>\n <li><strong>Longhorn Backup:</strong> <code>nfs://nas.iamworkin.lan:/volume1/NetBackup/longhorn-backups</code></li>\n <li><strong>Kubernetes Shared:</strong> <code>/volume1/kubernetes</code> (NFS mount for PVCs)</li>\n <li><strong>Selenium Screenshots:</strong> <code>/volume1/selenium/screenshots</code> (AAT visual tests via PVC)</li>\n <li><strong>Selenium Videos:</strong> <code>/volume1/selenium/videos</code> (test recordings)</li>\n <li><strong>NFS Permissions:</strong> RKE2 nodes rke2-server/agent1/agent2 (MGMT VLAN cross-VLAN rule)</li>\n <li><strong>Ports:</strong> NFS (2049), iSCSI (3260, no targets yet), DSM API (5001), SSH (22), SNMP (161)</li>\n <li><strong>pfSense Rule:</strong> RKE2 &rarr; NAS on 2049/3260/5001</li>\n </ul>\n </div>\n <div class=\"card\" style=\"border-top: 3px solid var(--gold);\">\n <div class=\"card-title\">Monitoring &amp; Security</div>\n <ul>\n <li><strong>SNMP:</strong> v2c community <a href=\"https://start.1password.com/open/i?v=qaphopopkryhbg353ukzhhuqoq&i=7qias4wucncvleievorxda5pym\" target=\"_blank\" class=\"op-link\" title=\"Open in 1Password\"><span class=\"b b-planned\">\U0001F510 SNMP</span></a></li>\n <li><strong>Zabbix\
\ Host:</strong> BlueJayNAS (ID 10678) &mdash; Linux by SNMP template</li>\n <li><strong>Prometheus:</strong> SNMP scrape via snmp-exporter (synology module)</li>\n <li><strong>Auto Block:</strong> Enabled (brute-force protection)</li>\n <li><strong>DSM Firewall:</strong> <span class=\"b b-offline\">DO NOT ENABLE</span> &mdash; synofirewall segfaults on 7.3.2, causes lockout</li>\n <li><strong>admin account:</strong> <span class=\"b b-online\">ENABLED</span> &mdash; never disable (breaks all admin-group privileges)</li>\n <li><strong>SSH:</strong> Keep <code>PasswordAuthentication yes</code> (disabling breaks sudo/PAM)</li>\n <li><strong>Guacamole:</strong> SSH connection in Network Devices group</li>\n </ul>\n </div>\n <div class=\"card\" style=\"border-top: 3px solid var(--red);\">\n <div class=\"card-title\">Recovery Notes</div>\n <ul>\n <li><strong>admin disabled recovery:</strong> Physical RESET button (4s hold, 1 beep) + power cycle</li>\n <li><strong>Firewall lockout:</strong> Physical RESET (same procedure)</li>\n <li><strong>Security hardening:</strong> Use pfSense cross-VLAN rules, NOT DSM-level firewall/SSH hardening</li>\n <li><strong>CLI tools:</strong> <code>/usr/syno/bin/synopkg</code>, <code>/usr/syno/sbin/synouser</code>, <code>/usr/syno/sbin/synogroup</code></li>\n <li><strong>DSM API:</strong> <code>https://nas.iamworkin.lan:5001/webapi/entry.cgi</code> &mdash; SYNO.API.Auth + otp_code for 2FA</li>\n </ul>\n </div>\n</div>\n\n<h3>Longhorn Persistent Volume Claims (17 PVCs, ~69 Gi)</h3>\n<div class=\"note\"><strong>Longhorn &rarr; NAS Backup:</strong> Longhorn is the default StorageClass on the RKE2 cluster (iSCSI). All 17 PVCs backed up to BlueJayNAS via NFS. Daily backups at 02:00 UTC (retain 14 days), hourly snapshots (retain 24).</div>\n<table>\n<thead><tr><th>Namespace</th><th>PVC</th><th>Size</th><th>Purpose</th></tr></thead>\n<tbody>\n<tr><td>monitoring</td><td>prometheus-data + grafana-data</td><td>12 Gi</td><td>Prometheus TSDB (90-day retention) + Grafana dashboards/DB</td></tr>\n<tr><td>zabbix</td><td>zabbix-postgres-data</td><td>10 Gi</td><td>Zabbix PostgreSQL (13 hosts, history/trends)</td></tr>\n<tr><td>gitea</td><td>gitea-shared-storage</td><td>10 Gi</td><td>Git repositories, LFS objects, attachments</td></tr>\n<tr><td>telephony</td><td>asterisk-data + telephony-data</td><td>10 Gi</td><td>Asterisk PBX config + FlowerCore.Telephony DB</td></tr>\n<tr><td>matrix</td><td>matrix-postgres-data + synapse-data</td><td>7 Gi</td><td>Matrix Synapse PostgreSQL + media store</td></tr>\n<tr><td>mail</td><td>mail-data + mail-state</td><td>6 Gi</td><td>docker-mailserver (Postfix queues, Dovecot mail)</td></tr>\n<tr><td>agent-zero</td><td>agent-zero-data + knowledge</td><td>6 Gi</td><td>Agent Zero persistent data + FAISS knowledge base</td></tr>\n<tr><td>guacamole</td><td>guac-mysql-data</td><td>5 Gi</td><td>Guacamole MySQL (23 connections, session history)</td></tr>\n<tr><td>irc</td><td>anope-data + unrealircd-data</td><td>2 Gi</td><td>IRC services DB (channels, nicks) + UnrealIRCd config</td></tr>\n<tr><td>teamspeak</td><td>teamspeak-data</td><td>1 Gi</td><td>TeamSpeak virtual server config + file transfers</td></tr>\n</tbody>\n</table>\n<table>\n<thead><tr><th>Component</th><th>Detail</th></tr></thead>\n<tbody>\n<tr><td>Storage Backend</td><td>Longhorn (iSCSI, default StorageClass, 3 replicas per volume)</td></tr>\n<tr><td>Backup Target</td><td><code>nfs://nas.iamworkin.lan:/volume1/NetBackup/longhorn-backups</code></td></tr>\n<tr><td>Backup Schedule</td><td>Daily at 02:00 UTC (retain 14 days), hourly snapshots (retain 24)</td></tr>\n<tr><td>RKE2 Requirement</td><td>iscsid enabled on all nodes (<code>systemctl enable --now iscsid</code>)</td></tr>\n<tr><td>Managed by</td><td>Puppet profile::kubernetes::rke2 (prerequisites, kernel modules, sysctl)</td></tr>\n</tbody>\n</table>\n\n<h3>Synology CSI Driver (Pending)</h3>\n<div class=\"note note-warn\"><strong>Status:</strong> Helm repo added, deployment pending. Will enable dynamic PVC provisioning directly from Synology NFS/iSCSI.</div>\n<table>\n<thead><tr><th>Component</th><th>Detail</th></tr></thead>\n<tbody>\n<tr><td>Driver</td><td>SynologyOpenSource/synology-csi v1.2.1</td></tr>\n<tr><td>Helm Chart</td><td>christian-schlichtherle, v0.11.0</td></tr>\n<tr><td>Protocols</td><td>NFS, iSCSI, SMB</td></tr>\n<tr><td>Service Account</td><td><code>k8s-csi</code> (UID 1032) on BlueJayNAS</td></tr>\n<tr><td>1Password</td><td><a href=\"https://start.1password.com/open/i?v=qaphopopkryhbg353ukzhhuqoq&i=cbxfibct4j6jfm6ccsogpayjuy\" target=\"_blank\" class=\"op-link\" title=\"Open in 1Password\"><span class=\"b b-planned\">\U0001F510 Synology CSI creds</span></a></td></tr>\n</tbody>\n</table>\n</div>\n\n<!-- ===== TAB: WIFI ===== -->\n<div id=\"tab-wifi\" class=\"tab-content\">\n<h2>WiFi Networks</h2>\n<div class=\"note\"><strong>Credentials:</strong> All WiFi passwords are stored in the <strong>IAmWorkin</strong> vault on 1Password. To connect a device, open the 1Password app, find the WiFi entry, and scan the QR code from there. Passwords are not stored in this page for security.</div>\n<div class=\"note note-warn\"><strong>QR Code Connection:</strong> Open 1Password &rarr; search for the SSID name &rarr; tap &ldquo;Show QR Code&rdquo; &rarr; scan with your device camera. The QR code encodes the full <code>WIFI:T:WPA;S:{SSID};P:{PASSWORD};;;</code> connection string.</div>\n\n<div class=\"wifi-grid\" id=\"wifiGrid\">\n <!-- BlueJay-Home -->\n <div class=\"wifi-card\" data-ssid=\"BlueJay-Home\" data-vlan=\"58\">\n <div class=\"wifi-card-header\" style=\"border-color: var(--purple);\">\n <div class=\"wifi-ssid\">BlueJay-Home</div>\n <div class=\"wifi-vlan\"><span class=\"b b-home\">HOME (VLAN 58)</span></div>\n </div>\n <div class=\"wifi-qr wifi-qr-placeholder\">\n <div class=\"qr-placeholder-box\">\n <svg viewBox=\"0 0 24 24\" width=\"36\" height=\"36\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <rect x=\"3\" y=\"3\" width=\"7\" height=\"7\" rx=\"1\"/><rect x=\"14\" y=\"3\" width=\"7\" height=\"7\" rx=\"1\"/><rect x=\"3\" y=\"14\" width=\"7\" height=\"7\" rx=\"1\"/>\n <rect x=\"14\" y=\"14\" width=\"3\" height=\"3\"/><rect x=\"18\" y=\"14\" width=\"3\" height=\"3\"/><rect x=\"14\" y=\"18\" width=\"3\" height=\"3\"/><rect x=\"18\" y=\"18\" width=\"3\" height=\"3\"/>\n <rect x=\"5\" y=\"5\" width=\"3\" height=\"3\"/><rect x=\"16\" y=\"5\" width=\"3\" height=\"3\"/><rect x=\"5\" y=\"16\" width=\"3\" height=\"3\"/>\n </svg>\n <span class=\"qr-placeholder-text\">Scan from 1Password app</span>\n </div>\n </div>\n <div class=\"wifi-details\">\n <div class=\"wifi-field\">\n <span class=\"wifi-label\">SSID</span>\n <span class=\"wifi-value\"><code>BlueJay-Home</code></span>\n </div>\n <div class=\"wifi-field\">\n <span class=\"wifi-label\">VLAN</span>\n <span class=\"wifi-value\">58 (untagged on AP)</span>\n </div>\n <div class=\"wifi-field\">\n <span class=\"wifi-label\">Security</span>\n <span class=\"wifi-value\">WPA2/WPA3</span>\n </div>\n <div class=\"wifi-field\">\n <span class=\"wifi-label\">Password</span>\n <span class=\"wifi-value\"><a href=\"https://start.1password.com/open/i?v=qaphopopkryhbg353ukzhhuqoq&i=vetpnvuxyfdcvkjr2cqhuaqekq\" target=\"_blank\" class=\"op-link\"><span class=\"b b-planned\">\U0001F510 Open in 1Password</span></a></span>\n </div>\n <div class=\"wifi-field\">\n <span class=\"wifi-label\">Purpose</span>\n <span class=\"wifi-value\">Home network &mdash; personal / family use</span>\n </div>\n <div class=\"wifi-field\">\n <span class=\"wifi-label\">Bandwidth</span>\n <span class=\"wifi-value\">800 / 800 Mbps</span>\n </div>\n <div class=\"wifi-field\">\n <span class=\"wifi-label\">Public IP</span>\n <span class=\"wifi-value\"><span class=\"ip\">74.40.140.29</span></span>\n </div>\n </div>\n </div>\n\n <!-- BlueJay-Work -->\n <div class=\"wifi-card\" data-ssid=\"BlueJay-Work\" data-vlan=\"64\">\n <div class=\"wifi-card-header\" style=\"border-color: var(--accent);\">\n <div class=\"wifi-ssid\">BlueJay-Work</div>\n <div class=\"wifi-vlan\"><span class=\"b b-wifi\">WORK (VLAN 64)</span></div>\n </div>\n <div class=\"wifi-qr wifi-qr-placeholder\">\n <div class=\"qr-placeholder-box\">\n <svg viewBox=\"0 0 24 24\" width=\"36\" height=\"36\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <rect x=\"3\" y=\"3\" width=\"7\" height=\"7\" rx=\"1\"/><rect x=\"14\" y=\"3\" width=\"7\" height=\"7\" rx=\"1\"/><rect x=\"3\" y=\"14\" width=\"7\" height=\"7\" rx=\"1\"/>\n <rect x=\"14\" y=\"14\" width=\"3\" height=\"3\"/><rect x=\"18\" y=\"14\" width=\"3\" height=\"3\"/><rect x=\"14\" y=\"18\" width=\"3\" height=\"3\"/><rect x=\"18\" y=\"18\" width=\"3\" height=\"3\"/>\n <rect x=\"5\" y=\"5\" width=\"3\" height=\"3\"/><rect x=\"16\" y=\"5\" width=\"3\" height=\"3\"/><rect x=\"5\" y=\"16\" width=\"3\" height=\"3\"/>\n </svg>\n <span class=\"qr-placeholder-text\">Scan from 1Password app</span>\n </div>\n </div>\n <div class=\"wifi-details\">\n <div class=\"wifi-field\">\n <span class=\"wifi-label\">SSID</span>\n <span class=\"wifi-value\"><code>BlueJay-Work</code></span>\n </div>\n <div class=\"wifi-field\">\n <span class=\"wifi-label\">VLAN</span>\n <span class=\"wifi-value\">64</span>\n </div>\n <div class=\"wifi-field\">\n <span class=\"wifi-label\">Security</span>\n <span class=\"wifi-value\">WPA2/WPA3</span>\n </div>\n <div class=\"wifi-field\"\
>\n <span class=\"wifi-label\">Password</span>\n <span class=\"wifi-value\"><a href=\"https://start.1password.com/open/i?v=qaphopopkryhbg353ukzhhuqoq&i=4omsv5ybqoyk4tvqrqww7kv3yu\" target=\"_blank\" class=\"op-link\"><span class=\"b b-planned\">\U0001F510 Open in 1Password</span></a></span>\n </div>\n <div class=\"wifi-field\">\n <span class=\"wifi-label\">Purpose</span>\n <span class=\"wifi-value\">Work network &mdash; business devices</span>\n </div>\n <div class=\"wifi-field\">\n <span class=\"wifi-label\">Bandwidth</span>\n <span class=\"wifi-value\">500 / 500 Mbps</span>\n </div>\n <div class=\"wifi-field\">\n <span class=\"wifi-label\">Public IP</span>\n <span class=\"wifi-value\"><span class=\"ip\">74.40.140.28</span> (shared)</span>\n </div>\n </div>\n </div>\n\n <!-- BlueJay-School -->\n <div class=\"wifi-card\" data-ssid=\"BlueJay-School\" data-vlan=\"65\">\n <div class=\"wifi-card-header\" style=\"border-color: var(--green);\">\n <div class=\"wifi-ssid\">BlueJay-School</div>\n <div class=\"wifi-vlan\"><span class=\"b b-wifi\">SCHOOL (VLAN 65)</span></div>\n </div>\n <div class=\"wifi-qr wifi-qr-placeholder\">\n <div class=\"qr-placeholder-box\">\n <svg viewBox=\"0 0 24 24\" width=\"36\" height=\"36\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <rect x=\"3\" y=\"3\" width=\"7\" height=\"7\" rx=\"1\"/><rect x=\"14\" y=\"3\" width=\"7\" height=\"7\" rx=\"1\"/><rect x=\"3\" y=\"14\" width=\"7\" height=\"7\" rx=\"1\"/>\n <rect x=\"14\" y=\"14\" width=\"3\" height=\"3\"/><rect x=\"18\" y=\"14\" width=\"3\" height=\"3\"/><rect x=\"14\" y=\"18\" width=\"3\" height=\"3\"/><rect x=\"18\" y=\"18\" width=\"3\" height=\"3\"/>\n <rect x=\"5\" y=\"5\" width=\"3\" height=\"3\"/><rect x=\"16\" y=\"5\" width=\"3\" height=\"3\"/><rect x=\"5\" y=\"16\" width=\"3\" height=\"3\"/>\n </svg>\n <span class=\"qr-placeholder-text\">Scan from 1Password app</span>\n </div>\n </div>\n <div class=\"wifi-details\">\n <div class=\"wifi-field\">\n <span class=\"wifi-label\">SSID</span>\n <span class=\"wifi-value\"><code>BlueJay-School</code></span>\n </div>\n <div class=\"wifi-field\">\n <span class=\"wifi-label\">VLAN</span>\n <span class=\"wifi-value\">65</span>\n </div>\n <div class=\"wifi-field\">\n <span class=\"wifi-label\">Security</span>\n <span class=\"wifi-value\">WPA2/WPA3</span>\n </div>\n <div class=\"wifi-field\">\n <span class=\"wifi-label\">Password</span>\n <span class=\"wifi-value\"><a href=\"https://start.1password.com/open/i?v=qaphopopkryhbg353ukzhhuqoq&i=jnl2dodsrbli2y5rhwirvcywzy\" target=\"_blank\" class=\"op-link\"><span class=\"b b-planned\">\U0001F510 Open in 1Password</span></a></span>\n </div>\n <div class=\"wifi-field\">\n <span class=\"wifi-label\">Purpose</span>\n <span class=\"wifi-value\">School network &mdash; student devices</span>\n </div>\n <div class=\"wifi-field\">\n <span class=\"wifi-label\">Bandwidth</span>\n <span class=\"wifi-value\">200 / 200 Mbps</span>\n </div>\n <div class=\"wifi-field\">\n <span class=\"wifi-label\">Public IP</span>\n <span class=\"wifi-value\"><span class=\"ip\">74.40.140.28</span> (shared)</span>\n </div>\n </div>\n </div>\n\n <!-- BlueJay-Guest -->\n <div class=\"wifi-card\" data-ssid=\"BlueJay-Guest\" data-vlan=\"66\">\n <div class=\"wifi-card-header\" style=\"border-color: var(--gold);\">\n <div class=\"wifi-ssid\">BlueJay-Guest</div>\n <div class=\"wifi-vlan\"><span class=\"b b-spare\">GUEST (VLAN 66)</span></div>\n </div>\n <div class=\"wifi-qr wifi-qr-placeholder wifi-qr-open\">\n <div class=\"qr-placeholder-box\">\n <svg viewBox=\"0 0 24 24\" width=\"36\" height=\"36\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <rect x=\"3\" y=\"3\" width=\"7\" height=\"7\" rx=\"1\"/><rect x=\"14\" y=\"3\" width=\"7\" height=\"7\" rx=\"1\"/><rect x=\"3\" y=\"14\" width=\"7\" height=\"7\" rx=\"1\"/>\n <rect x=\"14\" y=\"14\" width=\"3\" height=\"3\"/><rect x=\"18\" y=\"14\" width=\"3\" height=\"3\"/><rect x=\"14\" y=\"18\" width=\"3\" height=\"3\"/><rect x=\"18\" y=\"18\" width=\"3\" height=\"3\"/>\n <rect x=\"5\" y=\"5\" width=\"3\" height=\"3\"/><rect x=\"16\" y=\"5\" width=\"3\" height=\"3\"/><rect x=\"5\" y=\"16\" width=\"3\" height=\"3\"/>\n </svg>\n <span class=\"qr-placeholder-text\">Open network &mdash; no password required</span>\n </div>\n </div>\n <div class=\"wifi-details\">\n <div class=\"wifi-field\">\n <span class=\"wifi-label\">SSID</span>\n <span class=\"wifi-value\"><code>BlueJay-Guest</code></span>\n </div>\n <div class=\"wifi-field\">\n <span class=\"wifi-label\">VLAN</span>\n <span class=\"wifi-value\">66</span>\n </div>\n <div class=\"wifi-field\">\n <span class=\"wifi-label\">Security</span>\n <span class=\"wifi-value\">Open / Captive Portal (<a href=\"https://wifi.flowercore.io\" target=\"_blank\">wifi.flowercore.io</a>)</span>\n </div>\n <div class=\"wifi-field\">\n <span class=\"wifi-label\">Password</span>\n <span class=\"wifi-value\"><span class=\"b b-online\">None (open)</span></span>\n </div>\n <div class=\"wifi-field\">\n <span class=\"wifi-label\">Captive Portal</span>\n <span class=\"wifi-value\"><code>wifi.flowercore.io</code> &mdash; Cloudflare-proxied, public Let's Encrypt cert, K8s hosted</span>\n </div>\n <div class=\"wifi-field\">\n <span class=\"wifi-label\">Purpose</span>\n <span class=\"wifi-value\">Guest WiFi &mdash; fully isolated, NAT only, terms acceptance required</span>\n </div>\n <div class=\"wifi-field\">\n <span class=\"wifi-label\">Bandwidth</span>\n <span class=\"wifi-value\">100 / 50 Mbps</span>\n </div>\n <div class=\"wifi-field\">\n <span class=\"wifi-label\">Public IP</span>\n <span class=\"wifi-value\"><span class=\"ip\">74.40.140.28</span> (shared)</span>\n </div>\n </div>\n </div>\n</div>\n\n<h3>WiFi Access Point</h3>\n<div class=\"card-grid\">\n <div class=\"card\">\n <div class=\"card-title\"><span class=\"dot dot-green\"></span>Synology RT6600AX (AP Mode)</div>\n <ul>\n <li><strong>Management:</strong> <a href=\"http://wifi.iamworkin.lan:8000\" target=\"_blank\">http://wifi.iamworkin.lan:8000</a></li>\n <li><strong>Credentials:</strong> <a href=\"https://start.1password.com/open/i?v=qaphopopkryhbg353ukzhhuqoq&i=hlys5gwgnfa2a2nclrr52dr6gu\" target=\"_blank\" class=\"op-link\" title=\"Open in 1Password\"><span class=\"b b-planned\">\U0001F510 Synology WiFi Router</span></a></li>\n <li><strong>Mode:</strong> Access Point (bridge mode), all trunk ports enabled</li>\n <li><strong>Bands:</strong> Wi-Fi 6E (2.4 GHz + 5 GHz + 6 GHz)</li>\n <li><strong>Switch Port:</strong> 3 (trunk, native VLAN 58)</li>\n </ul>\n </div>\n</div>\n\n<div class=\"note\"><strong>Network Isolation:</strong> Each SSID maps to a separate VLAN with independent firewall rules and bandwidth limits. GUEST is fully isolated with NAT &mdash; no access to internal resources. EMPLOYEE, WORK, and SCHOOL share public IP .28 with traffic shaping.</div>\n</div>\n\n<!-- ===== TAB: CREDENTIALS ===== -->\n<div id=\"tab-credentials\" class=\"tab-content\">\n<h2>Credentials &amp; 1Password</h2>\n<div class=\"card-grid\">\n <div class=\"card\" style=\"border-top: 3px solid var(--accent);\">\n <div class=\"card-title\"><span class=\"dot dot-green\"></span>1Password Connect Server</div>\n <ul>\n <li><strong>API:</strong> <span class=\"ip\">http://op-connect.iamworkin.lan:8180</span> <button class=\"copy-btn\" onclick=\"copyText('http://op-connect.iamworkin.lan:8180')\">copy</button></li>\n <li><strong>Sync:</strong> <span class=\"ip\">http://op-connect.iamworkin.lan:8181</span></li>\n <li><strong>Host:</strong> noc1 (Podman containers)</li>\n <li><strong>Status:</strong> <span class=\"b b-online\">Online</span></li>\n </ul>\n </div>\n <div class=\"card\" style=\"border-top: 3px solid var(--accent);\">\n <div class=\"card-title\"><span class=\"dot dot-green\"></span>1Password K8s Operator</div>\n <ul>\n <li><strong>Namespace:</strong> <code>onepassword-system</code></li>\n <li><strong>Chart:</strong> 1password/connect v2.3.0</li>\n <li><strong>Operator:</strong> v1.11.0</li>\n <li><strong>Poll Interval:</strong> 600s</li>\n <li><strong>Status:</strong> <span class=\"b b-online\">Online</span></li>\n </ul>\n </div>\n <div class=\"card\" style=\"border-top: 3px solid var(--gold);\">\n <div class=\"card-title\">IAmWorkin Vault</div>\n <ul>\n <li><strong>Vault Name:</strong> IAmWorkin</li>\n <li><strong>Items:</strong> 45+ items (infra credentials + WiFi QR codes + Pi device passwords)</li>\n <li><strong>AI SSH Key:</strong> <a href=\"https://start.1password.com/open/i?v=qaphopopkryhbg353ukzhhuqoq&i=o4helobxaqrerkwd3oeag4dt6m\" target=\"_blank\" class=\"op-link\" title=\"Open in 1Password\"><span class=\"b b-planned\">\U0001F510 AI Shared SSH Key - fcadmin</span></a></li>\n <li><strong>Rotation:</strong> Quarterly (Jan/Apr/Jul/Oct)</li>\n <li><strong>Script:</strong> <code>/opt/scripts/rotate-credentials.sh</code></li>\n <li><strong>Timer:</strong> <code>credential-rotation.timer</code></li>\n </ul>\n </div>\n</div>\n\n<div class=\"note\"><strong>All infrastructure credentials are managed in 1Password.</strong> The IAmWorkin vault contains credentials for every service\
\ listed on this intranet. K8s workloads (Zabbix, Matrix, Guacamole, Mail, IRC, Gitea, ArgoCD) sync secrets automatically via OnePasswordItem CRDs. Credential rotation runs quarterly via systemd timer.</div>\n\n<h3>K8s Secret Sync (OnePasswordItem CRDs)</h3>\n<table>\n<thead><tr><th>Namespace</th><th>Secret Name</th><th>Source (1Password Item)</th><th>Status</th></tr></thead>\n<tbody>\n<tr><td>zabbix</td><td>zabbix-credentials</td><td>Zabbix Monitoring</td><td><span class=\"b b-online\">Synced</span></td></tr>\n<tr><td>matrix</td><td>matrix-credentials</td><td>Matrix Synapse</td><td><span class=\"b b-online\">Synced</span></td></tr>\n<tr><td>guacamole</td><td>guacamole-credentials</td><td>Apache Guacamole</td><td><span class=\"b b-online\">Synced</span></td></tr>\n<tr><td>mail</td><td>mail-credentials</td><td>Mail Server</td><td><span class=\"b b-online\">Synced</span></td></tr>\n<tr><td>irc</td><td>irc-credentials</td><td>IRC Services</td><td><span class=\"b b-online\">Synced</span></td></tr>\n<tr><td>gitea</td><td>gitea-credentials</td><td>Gitea</td><td><span class=\"b b-online\">Synced</span></td></tr>\n<tr><td>argocd</td><td>argocd-credentials</td><td>ArgoCD</td><td><span class=\"b b-online\">Synced</span></td></tr>\n</tbody>\n</table>\n\n<h3>Pi Fleet &amp; Edge Node Credentials</h3>\n<table>\n<thead><tr><th>Device</th><th>IP</th><th>User</th><th>1Password Item</th><th>Services</th></tr></thead>\n<tbody>\n<tr><td>edge1 (Pi 5)</td><td><span class=\"ip\">edge1.iamworkin.lan</span></td><td><code>stoltz</code></td><td><a href=\"https://start.1password.com/open/i?v=qaphopopkryhbg353ukzhhuqoq&i=62jgyxh6emltmharocvzxq2oue\" target=\"_blank\" class=\"op-link\" title=\"Open in 1Password\"><span class=\"b b-planned\">\U0001F510 Edge1 Pi5 SSH</span></a></td><td>Ollama, Piper TTS, Hailo STT, Frigate</td></tr>\n<tr><td>edge2 (Pi 4)</td><td><span class=\"ip\">edge2.iamworkin.lan</span></td><td><code>stoltz</code></td><td><a href=\"https://start.1password.com/open/i?v=qaphopopkryhbg353ukzhhuqoq&i=kyeqfwy76msqb4cfdjueaq5uta\" target=\"_blank\" class=\"op-link\" title=\"Open in 1Password\"><span class=\"b b-planned\">\U0001F510 Edge2 Pi4 SSH</span></a></td><td>Print.Web, CUPS, GitHub Actions runners</td></tr>\n<tr><td>piez (Pi 4)</td><td><span class=\"ip\">piez.iamworkin.lan</span></td><td><code>stoltz</code></td><td><a href=\"https://start.1password.com/open/i?v=qaphopopkryhbg353ukzhhuqoq&i=62jgyxh6emltmharocvzxq2oue\" target=\"_blank\" class=\"op-link\" title=\"Open in 1Password\"><span class=\"b b-planned\">\U0001F510 piez</span></a></td><td>PiManager :5000 (GPIO, I2C, SPI)</td></tr>\n<tr><td>pirelay (Pi 3)</td><td><span class=\"ip\">pirelay.iamworkin.lan</span></td><td><code>stoltz</code></td><td><a href=\"https://start.1password.com/open/i?v=qaphopopkryhbg353ukzhhuqoq&i=62jgyxh6emltmharocvzxq2oue\" target=\"_blank\" class=\"op-link\" title=\"Open in 1Password\"><span class=\"b b-planned\">\U0001F510 pirelay</span></a></td><td>PiManager :5100 (4-ch relay)</td></tr>\n<tr><td>Mac Mini</td><td><span class=\"ip\">macmini.iamworkin.lan</span></td><td><code>bluejay</code></td><td><a href=\"https://start.1password.com/open/i?v=qaphopopkryhbg353ukzhhuqoq&i=npkseg2zvbmxmtg7qrseredvym\" target=\"_blank\" class=\"op-link\" title=\"Open in 1Password\"><span class=\"b b-planned\">\U0001F510 Mac Mini</span></a></td><td>SSH + VNC :5900, Xcode builds</td></tr>\n</tbody>\n</table>\n\n<h3>Credential Rotation</h3>\n<div class=\"card-grid\">\n <div class=\"card\">\n <div class=\"card-title\">Rotation Script</div>\n <ul>\n <li><strong>Path:</strong> <code>/opt/scripts/rotate-credentials.sh</code></li>\n <li><strong>Usage:</strong> <code>rotate-credentials.sh {service|all} [--dry-run]</code></li>\n <li><strong>Services:</strong> grafana, guacamole, zabbix, argocd, gitea, snappymail, traefik, matrix, harvester (17/17 complete, all XKCD-style)</li>\n <li><strong>Schedule:</strong> Quarterly (1st of Jan/Apr/Jul/Oct at 03:00 UTC)</li>\n <li><strong>Log:</strong> <code>/var/log/credential-rotation.log</code></li>\n </ul>\n </div>\n</div>\n</div>\n\n<!-- ===== TAB: PLANNED ===== -->\n<div id=\"tab-planned\" class=\"tab-content\">\n<h2>Planned Services</h2>\n<div class=\"note\">All previously planned services (Gitea, IRC, Zabbix, ArgoCD, 1Password, Mail, Matrix, TeamSpeak, Guacamole, Signage, Telephony, RemoteDesktop, WiFi Portal, Monitoring, Selenium Grid) are now <strong>live on RKE2</strong>. Remaining planned items are Windows Server VMs and authenticated proxy.</div>\n<table>\n<thead><tr><th>Service</th><th>IP</th><th>Host</th><th>Role</th><th>Status</th></tr></thead>\n<tbody>\n<tr><td>Windows DC1</td><td><span class=\"ip\">TBD</span></td><td>VM (hypervisor TBD)</td><td>AD Domain Controller (iamworkin.lan) &mdash; IP 10.0.56.20 now used by BLUEJAY-WS</td><td><span class=\"b b-planned\">Planned</span></td></tr>\n<tr><td>Windows WAC1</td><td><span class=\"ip\">10.0.56.21</span></td><td>VM (hypervisor TBD)</td><td>Windows Admin Center</td><td><span class=\"b b-planned\">Planned</span></td></tr>\n<tr><td>Windows RDS1</td><td><span class=\"ip\">10.0.57.20</span></td><td>VM (hypervisor TBD)</td><td>Remote Desktop Services</td><td><span class=\"b b-planned\">Planned</span></td></tr>\n<tr><td>Windows IIS1</td><td><span class=\"ip\">10.0.57.21</span></td><td>VM (hypervisor TBD)</td><td>IIS Web Server</td><td><span class=\"b b-planned\">Planned</span></td></tr>\n<tr><td>Squid Proxy</td><td><span class=\"ip\">10.0.56.22</span></td><td>VM (hypervisor TBD)</td><td>Authenticated web proxy (Kerberos/LDAP)</td><td><span class=\"b b-planned\">Planned</span></td></tr>\n</tbody>\n</table>\n</div>\n\n<!-- ===== TAB: TOPOLOGY ===== -->\n<div id=\"tab-topology\" class=\"tab-content\">\n<h2>Network Topology</h2>\n<div style=\"display:flex;flex-direction:column;align-items:center;gap:8px;padding:1.5rem;\">\n\n<div class=\"card\" style=\"text-align:center;max-width:280px;border-color:var(--text-muted);\"><div class=\"card-title\" style=\"justify-content:center;\">Internet</div></div>\n<div style=\"width:2px;height:16px;background:var(--border-accent);\"></div>\n<div class=\"card\" style=\"text-align:center;max-width:320px;\"><div class=\"card-title\" style=\"justify-content:center;\"><span class=\"dot dot-green\"></span>Frontier ONT + NVG468MQ Modem</div><p><span class=\"ip\">WAN: 74.32.185.184</span> &bull; <span class=\"ip\">/28: .17-.29</span></p><p style=\"font-size:0.78rem;\">192.168.254.254 &bull; DMZ to pfSense</p></div>\n<div style=\"width:2px;height:16px;background:var(--border-accent);\"></div>\n<div class=\"card\" style=\"text-align:center;max-width:360px;border-color:var(--accent);\"><div class=\"card-title\" style=\"justify-content:center;\"><span class=\"dot dot-green\"></span>pfSense Netgate 4100</div><p><span class=\"ip\">WAN: igc3 (.122)</span> &bull; <span class=\"ip\">LAN: igc0</span> (802.1Q trunk)</p><p style=\"font-size:0.78rem;\">13 VLANs &bull; 13 VIPs &bull; 28 port forwards &bull; DNS/DHCP/NTP/SNMP</p></div>\n<div style=\"width:2px;height:16px;background:var(--border-accent);\"></div>\n<div class=\"card\" style=\"text-align:center;max-width:340px;\"><div class=\"card-title\" style=\"justify-content:center;\"><span class=\"dot dot-green\"></span>UniFi USW-Lite-16-PoE Switch</div><p><span class=\"ip\">switch.iamworkin.lan</span> &bull; 16 ports &bull; VLANs 56-67</p></div>\n<div style=\"width:2px;height:8px;background:var(--border-accent);\"></div>\n\n<div style=\"display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:10px;width:100%;max-width:1200px;\">\n <div class=\"card\" style=\"border-left:3px solid var(--accent);\">\n <div class=\"card-title\"><span class=\"dot dot-green\"></span>noc1</div>\n <p><span class=\"ip\">noc1.iamworkin.lan</span> <span class=\"b b-mgmt\">MGMT</span></p>\n <p style=\"font-size:0.78rem;\">Celeron N5105 &bull; 32GB &bull; K3s + Podman</p>\n <ul style=\"font-size:0.78rem;\">\n <li>step-ca :9443</li><li>Cockpit :9090</li>\n <li>Puppet :8140</li><li>1Password Connect :8180</li>\n <li>node-exporter :9100</li>\n </ul>\n </div>\n <div class=\"card\" style=\"border-left:3px solid var(--green);\">\n <div class=\"card-title\"><span class=\"dot dot-green\"></span>RKE2 Bare-Metal Cluster</div>\n <p><span class=\"ip\">Traefik: traefik.iamworkin.lan</span> <span class=\"b b-mgmt\">MGMT</span></p>\n <ul style=\"font-size:0.78rem;\">\n <li>rke2-server: <span class=\"ip\">.11</span> (i7-1260P/64GB, control plane)</li>\n <li>rke2-agent1: <span class=\"ip\">.12</span> (i7-1260P/64GB, worker)</li>\n <li>rke2-agent2: <span class=\"ip\">.13</span> (i5-1340P/64GB, worker)</li>\n </ul>\n <p style=\"font-size:0.78rem;\">RKE2 v1.34.5 &bull; Calico &bull; MetalLB &bull; Longhorn &bull; Traefik v3.6.10 &bull; ArgoCD &bull; 22 apps &bull; 41 namespaces &bull; Asterisk PBX</p>\n </div>\n <div class=\"card\" style=\"border-left:3px solid var(--cyan);\">\n <div class=\"card-title\"><span class=\"dot dot-green\"></span>WiFi (Synology RT6600AX)</div>\n <p><span class=\"ip\">wifi.iamworkin.lan</span> <span class=\"b b-home\">HOME</span></p>\n <ul style=\"font-size:0.78rem;\">\n <li>BlueJay-Home (untagged)</li>\n <li>BlueJay-Guest (VLAN 66, captive portal)</li>\n <li>BlueJay-Work (VLAN 64)</li><li>BlueJay-School (VLAN 65)</li>\n </ul>\n </div>\n <div class=\"card\" style=\"border-left:3px solid var(--orange);\">\n <div class=\"card-title\"><span class=\"dot dot-green\"></span>PROD Nodes</div>\n <p><span class=\"b b-prod\">PROD VLAN 57</span></p>\n <ul style=\"font-size:0.78rem;\">\n <li>Mac Mini: <span class=\"ip\">macmini.iamworkin.lan</span> (Xcode)</li>\n <li>edge1 Pi5: <span class=\"ip\">edge1.iamworkin.lan</span> (Hailo AI)</li>\n <li>edge2 Pi4: <span class=\"ip\">edge2.iamworkin.lan</span> (CI runner)</li>\n </ul>\n </div>\n <div class=\"card\" style=\"border-left:3px\
\ solid var(--purple);\">\n <div class=\"card-title\"><span class=\"dot dot-green\"></span>HOME Pi Fleet</div>\n <p><span class=\"b b-home\">HOME VLAN 58</span> &bull; FlowerCore.PiManager</p>\n <ul style=\"font-size:0.78rem;\">\n <li>piez Pi4: <span class=\"ip\">piez.iamworkin.lan</span> (EZ Connect, GPIO/I2C/SPI) — <a href=\"http://piez.iamworkin.lan:5000\">:5000</a></li>\n <li>pirelay Pi3: <span class=\"ip\">pirelay.iamworkin.lan</span> (KS0212 4-ch relay) — <a href=\"http://pirelay.iamworkin.lan:5100\">:5100</a></li>\n <li style=\"font-size:0.72rem;color:var(--text-muted);\">Unified PiManager binary &bull; config-driven &bull; node-exporter &bull; Zabbix</li>\n </ul>\n </div>\n <div class=\"card\" style=\"border-left:3px solid var(--pink);\">\n <div class=\"card-title\"><span class=\"dot dot-green\"></span>BLUEJAY-WS (Workstation)</div>\n <p><span class=\"ip\">10.0.56.20</span> <span class=\"b b-mgmt\">MGMT</span></p>\n <p style=\"font-size:0.78rem;\">Xeon w3-2425 &bull; 32GB DDR5 &bull; R9700 32GB &bull; openSUSE Leap 16</p>\n <ul style=\"font-size:0.78rem;\">\n <li>Agent Zero: <a href=\"https://agent-zero-ws.iamworkin.lan\">agent-zero-ws</a></li>\n <li>Ollama 0.19.0: <a href=\"https://ollama-ws.iamworkin.lan\">ollama-ws</a> (14 models)</li>\n <li>Traefik v3.6.12: <a href=\"https://bluejay-ws.iamworkin.lan\">bluejay-ws</a></li>\n <li>Piper TTS :30052</li>\n </ul>\n </div>\n <div class=\"card\" style=\"border-left:3px solid var(--gold);\">\n <div class=\"card-title\"><span class=\"dot dot-green\"></span>Network Devices &amp; Storage</div>\n <ul style=\"font-size:0.78rem;\">\n <li>Cloud Key: <span class=\"ip\">unifi.iamworkin.lan</span></li>\n <li>BlueJayNAS (DS1621+): <span class=\"ip\">nas.iamworkin.lan</span></li>\n <li style=\"font-size:0.72rem;color:var(--text-muted);\">9.1TB Btrfs &bull; NFS &bull; Longhorn backup &bull; SNMP</li>\n <li>Modem: <span class=\"ip\">192.168.254.254</span></li>\n </ul>\n </div>\n</div>\n</div>\n</div>\n\n<!-- ===== TAB: DOMAINS ===== -->\n<div id=\"tab-domains\" class=\"tab-content\">\n<h2>Domains</h2>\n<div class=\"summary-grid\">\n <div class=\"summary-card\"><div class=\"summary-number\">17</div><div class=\"summary-label\">Registered Domains</div></div>\n <div class=\"summary-card\"><div class=\"summary-number\">1</div><div class=\"summary-label\">Internal Domain</div></div>\n <div class=\"summary-card\"><div class=\"summary-number\">1</div><div class=\"summary-label\">Blog Hosting (DreamHost)</div></div>\n <div class=\"summary-card\"><div class=\"summary-number\">18</div><div class=\"summary-label\">Total Domains</div></div>\n</div>\n\n<h3>FlowerCore Domains</h3>\n<table>\n<thead><tr><th>Domain</th><th>Category</th><th>Owner</th><th>Purpose</th><th>DNS Provider</th><th>Registrar</th></tr></thead>\n<tbody>\n<tr><td><strong>flowercore.io</strong></td><td><span class=\"b b-prod\">FlowerCore</span></td><td>Andrew</td><td>Production API</td><td>Cloudflare</td><td>Namecheap</td></tr>\n<tr><td><strong>flowerinsider.xyz</strong></td><td><span class=\"b b-prod\">FlowerCore</span></td><td>Andrew</td><td>Dev/staging</td><td>Cloudflare</td><td>Namecheap</td></tr>\n<tr><td><strong>flowerinsider.com</strong></td><td><span class=\"b b-prod\">FlowerCore Co</span></td><td>Andrew</td><td>Company site</td><td>Namecheap</td><td>Namecheap</td></tr>\n<tr><td><strong>flowerinsider.nl</strong></td><td><span class=\"b b-prod\">FlowerCore Co</span></td><td>Andrew</td><td>Dutch site</td><td>Namecheap</td><td>Namecheap</td></tr>\n</tbody>\n</table>\n\n<h3>Work Domains</h3>\n<table>\n<thead><tr><th>Domain</th><th>Category</th><th>Owner</th><th>Purpose</th><th>DNS Provider</th><th>Registrar</th></tr></thead>\n<tbody>\n<tr><td><strong>iamwork.in</strong></td><td><span class=\"b b-wifi\">Work</span></td><td>Andrew</td><td>Employee portal, IVR, Telephony</td><td>Cloudflare</td><td>Namecheap</td></tr>\n<tr><td><strong>iamworkin.com</strong></td><td><span class=\"b b-wifi\">Work</span></td><td>Andrew</td><td>Redirect</td><td>Namecheap</td><td>Namecheap</td></tr>\n</tbody>\n</table>\n\n<h3>Personal &amp; Tenant Domains</h3>\n<table>\n<thead><tr><th>Domain</th><th>Category</th><th>Owner</th><th>Purpose</th><th>DNS Provider</th><th>Registrar</th></tr></thead>\n<tbody>\n<tr><td><strong>ackeroni.com</strong></td><td><span class=\"b b-tenant\">Erik</span></td><td>Erik</td><td>Personal</td><td>Namecheap</td><td>Namecheap</td></tr>\n<tr><td><strong>erckak.com</strong></td><td><span class=\"b b-tenant\">Erik</span></td><td>Erik</td><td>Personal</td><td>Namecheap</td><td>Namecheap</td></tr>\n<tr><td><strong>erckak.dev</strong></td><td><span class=\"b b-tenant\">Erik</span></td><td>Erik</td><td>Developer portfolio</td><td>Cloudflare</td><td>Namecheap</td></tr>\n<tr><td><strong>digirido.com</strong></td><td><span class=\"b b-spare\">Random</span></td><td>Andrew</td><td>DigiKey testing</td><td>Namecheap</td><td>Namecheap</td></tr>\n<tr><td><strong>timeforta.co</strong></td><td><span class=\"b b-tenant\">Dustin</span></td><td>Dustin</td><td>Personal</td><td>Cloudflare</td><td>Namecheap</td></tr>\n<tr><td><strong>shenanjia.com</strong></td><td><span class=\"b b-home\">Wife</span></td><td>Wife</td><td>Personal site</td><td>Namecheap</td><td>Namecheap</td></tr>\n<tr><td><strong>bluejay.api</strong></td><td><span class=\"b b-mgmt\">Personal Fun</span></td><td>Andrew</td><td>API experiments</td><td>Namecheap</td><td>Namecheap</td></tr>\n<tr><td><strong>bluejay.dev</strong></td><td><span class=\"b b-mgmt\">Personal Fun</span></td><td>Andrew</td><td>Dev projects, voice bridge</td><td>Cloudflare</td><td>Namecheap</td></tr>\n<tr><td><strong>jayblue.dev</strong></td><td><span class=\"b b-mgmt\">Personal Fun</span></td><td>Andrew</td><td>Dev projects</td><td>Namecheap</td><td>Namecheap</td></tr>\n<tr><td><strong>z.orb</strong></td><td><span class=\"b b-spare\">Random</span></td><td>Andrew</td><td>Short URL</td><td>Namecheap</td><td>Namecheap</td></tr>\n</tbody>\n</table>\n\n<h3>Blog &amp; Content Domains</h3>\n<table>\n<thead><tr><th>Domain</th><th>Category</th><th>Owner</th><th>Purpose</th><th>DNS Provider</th><th>Registrar</th></tr></thead>\n<tbody>\n<tr><td><strong>pebbleandpeanut.com</strong></td><td><span class=\"b b-home\">Blog</span></td><td>Andrew</td><td>Personal blog</td><td>DreamHost</td><td>Namecheap</td></tr>\n<tr><td><strong>pebblesandpeanuts.com</strong></td><td><span class=\"b b-home\">Blog</span></td><td>Andrew</td><td>Alt redirect</td><td>Namecheap</td><td>Namecheap</td></tr>\n</tbody>\n</table>\n\n<h3>Internal Domain</h3>\n<table>\n<thead><tr><th>Domain</th><th>Category</th><th>Owner</th><th>Purpose</th><th>DNS Provider</th><th>Notes</th></tr></thead>\n<tbody>\n<tr><td><strong>iamworkin.lan</strong></td><td><span class=\"b b-mgmt\">Internal</span></td><td>Andrew</td><td>Internal infrastructure, future AD DS</td><td>pfSense Unbound</td><td>52+ host overrides + 4 wildcard redirect zones, not publicly registered</td></tr>\n</tbody>\n</table>\n\n<h3>Cloudflare (6 Zones)</h3>\n<div class=\"card-grid\">\n <div class=\"card\">\n <div class=\"card-title\"><span class=\"dot dot-green\"></span>Cloudflare Account</div>\n <ul>\n <li><strong>Account:</strong> Astoltz@iamwork.in</li>\n <li><strong>Plan:</strong> Pro (planned)</li>\n <li><strong>NS:</strong> dan.ns.cloudflare.com, frida.ns.cloudflare.com</li>\n <li><strong>All Zones:</strong> SSL Full(strict), HSTS, min TLS 1.2</li>\n <li><strong>API Tokens:</strong> <a href=\"https://start.1password.com/open/i?v=qaphopopkryhbg353ukzhhuqoq&i=mp5u2gxtq4xaz2wfsggxzm2a3e\" target=\"_blank\" class=\"op-link\" title=\"Open in 1Password\"><span class=\"b b-planned\">\U0001F510 Cloudflare API Tokens</span></a></li>\n </ul>\n </div>\n <div class=\"card\">\n <div class=\"card-title\">Active Zones</div>\n <ul>\n <li><strong>flowercore.io</strong> &mdash; Production API, landing page</li>\n <li><strong>iamwork.in</strong> &mdash; Employee portal, telephony, DDNS</li>\n <li><strong>bluejay.dev</strong> &mdash; Dev projects, voice bridge</li>\n <li><strong>erckak.dev</strong> &mdash; Erik developer portfolio</li>\n <li><strong>timeforta.co</strong> &mdash; Dustin personal</li>\n <li><strong>flowerinsider.xyz</strong> &mdash; Dev/staging</li>\n </ul>\n </div>\n</div>\n<h3>Namecheap API</h3>\n<div class=\"card-grid\">\n <div class=\"card\">\n <div class=\"card-title\">API Configuration</div>\n <ul>\n <li><strong>Base URL:</strong> <code>https://api.namecheap.com/xml.response</code> <button class=\"copy-btn\" onclick=\"copyText('https://api.namecheap.com/xml.response')\">copy</button></li>\n <li><strong>API User:</strong> <code>astoltz</code> <button class=\"copy-btn\" onclick=\"copyText('astoltz')\">copy</button></li>\n <li><strong>API Key:</strong> <a href=\"https://start.1password.com/open/i?v=qaphopopkryhbg353ukzhhuqoq&i=wghrgst2tjer36istiwej43fcy\" target=\"_blank\" class=\"op-link\" title=\"Open in 1Password\"><span class=\"b b-planned\">\U0001F510 Namecheap API</span></a></li>\n <li><strong>Sandbox URL:</strong> <code>https://api.sandbox.namecheap.com/xml.response</code> <button class=\"copy-btn\" onclick=\"copyText('https://api.sandbox.namecheap.com/xml.response')\">copy</button></li>\n </ul>\n </div>\n <div class=\"card\">\n <div class=\"card-title\">Dynamic DNS</div>\n <ul>\n <li><strong>Hostname:</strong> <code>gateway.iamwork.in</code> <button class=\"copy-btn\" onclick=\"copyText('gateway.iamwork.in')\">copy</button></li>\n <li><strong>Points to:</strong> pfSense WAN IP (auto-updated)</li>\n <li><strong>DDNS:</strong> gateway.iamwork.in &rarr; pfSense WAN DHCP IP (via Cloudflare API)</li>\n <li><strong>Update Method:</strong> pfSense Dynamic DNS client (Cloudflare API token)</li>\n <li><strong>Token:</strong> <a href=\"https://start.1password.com/open/i?v=qaphopopkryhbg353ukzhhuqoq&i=mp5u2gxtq4xaz2wfsggxzm2a3e\"\
\ target=\"_blank\" class=\"op-link\" title=\"Open in 1Password\"><span class=\"b b-planned\">\U0001F510 Cloudflare pfSense Token</span></a></li>\n </ul>\n </div>\n</div>\n\n<h3>Internal DNS Architecture</h3>\n<div class=\"note\"><strong>Split-Horizon DNS (LIVE):</strong> External requests to flowercore.io resolve via Cloudflare to public IP .24 (PROD). Internal requests resolve via pfSense Unbound to K8s MetalLB VIP (10.0.56.200), avoiding NAT hairpin. All internal infrastructure uses iamworkin.lan zone. 4 tenant .lan wildcard redirect zones configured in Unbound (base64-encoded custom_options).</div>\n\n<h3>Planned IPv6 (ULA)</h3>\n<table>\n<thead><tr><th>Prefix</th><th>Scheme</th><th>Method</th></tr></thead>\n<tbody>\n<tr><td><code>fdbc:56:XX::/64</code></td><td>XX = VLAN ID (e.g., fdbc:56:56::/64 for MGMT)</td><td>SLAAC + DHCPv6 (servers), SLAAC-only (clients)</td></tr>\n</tbody>\n</table>\n</div>\n\n<script>\n/* ===== QR Code Generator (inline, air-gap safe) ===== */\n/* Minimal QR code encoder — generates canvas-based QR codes. Based on public domain QR algorithms. */\nvar QRCode=(function(){\nvar PAD0=0xEC,PAD1=0x11;\nfunction QRCode(typeNumber,errorCorrectionLevel){this.typeNumber=typeNumber;this.errorCorrectionLevel=errorCorrectionLevel;this.modules=null;this.moduleCount=0;this.dataCache=null;this.dataList=[];}\nQRCode.prototype={addData:function(data){this.dataList.push(new QR8bitByte(data));this.dataCache=null;},make:function(){if(this.typeNumber<1){var tn=1;for(tn=1;tn<40;tn++){var rs=QRRSBlock.getRSBlocks(tn,this.errorCorrectionLevel);var buf=new QRBitBuffer();for(var i=0;i<this.dataList.length;i++){var d=this.dataList[i];buf.put(d.mode,4);buf.put(d.getLength(),QRUtil.getLengthInBits(d.mode,tn));d.write(buf);}var totalDataCount=0;for(i=0;i<rs.length;i++){totalDataCount+=rs[i].dataCount;}if(buf.getLengthInBits()<=totalDataCount*8)break;}this.typeNumber=tn;}this.makeImpl(false,this.getBestMaskPattern());},makeImpl:function(test,maskPattern){this.moduleCount=this.typeNumber*4+17;this.modules=new Array(this.moduleCount);for(var row=0;row<this.moduleCount;row++){this.modules[row]=new Array(this.moduleCount);for(var col=0;col<this.moduleCount;col++){this.modules[row][col]=null;}}this.setupPositionProbePattern(0,0);this.setupPositionProbePattern(this.moduleCount-7,0);this.setupPositionProbePattern(0,this.moduleCount-7);this.setupPositionAdjustPattern();this.setupTimingPattern();this.setupTypeInfo(test,maskPattern);if(this.typeNumber>=7){this.setupTypeNumber(test);}if(this.dataCache==null){this.dataCache=QRCode.createData(this.typeNumber,this.errorCorrectionLevel,this.dataList);}this.mapData(this.dataCache,maskPattern);},setupPositionProbePattern:function(row,col){for(var r=-1;r<=7;r++){if(row+r<=-1||this.moduleCount<=row+r)continue;for(var c=-1;c<=7;c++){if(col+c<=-1||this.moduleCount<=col+c)continue;if((0<=r&&r<=6&&(c==0||c==6))||(0<=c&&c<=6&&(r==0||r==6))||(2<=r&&r<=4&&2<=c&&c<=4)){this.modules[row+r][col+c]=true;}else{this.modules[row+r][col+c]=false;}}}},getBestMaskPattern:function(){var minLostPoint=0;var pattern=0;for(var i=0;i<8;i++){this.makeImpl(true,i);var lostPoint=QRUtil.getLostPoint(this);if(i==0||minLostPoint>lostPoint){minLostPoint=lostPoint;pattern=i;}}return pattern;},setupTimingPattern:function(){for(var r=8;r<this.moduleCount-8;r++){if(this.modules[r][6]!=null)continue;this.modules[r][6]=(r%2==0);}for(var c=8;c<this.moduleCount-8;c++){if(this.modules[6][c]!=null)continue;this.modules[6][c]=(c%2==0);}},setupPositionAdjustPattern:function(){var pos=QRUtil.getPatternPosition(this.typeNumber);for(var i=0;i<pos.length;i++){for(var j=0;j<pos.length;j++){var row=pos[i];var col=pos[j];if(this.modules[row][col]!=null)continue;for(var r=-2;r<=2;r++){for(var c=-2;c<=2;c++){if(r==-2||r==2||c==-2||c==2||(r==0&&c==0)){this.modules[row+r][col+c]=true;}else{this.modules[row+r][col+c]=false;}}}}}},setupTypeNumber:function(test){var bits=QRUtil.getBCHTypeNumber(this.typeNumber);for(var i=0;i<18;i++){var mod=(!test&&((bits>>i)&1)==1);this.modules[Math.floor(i/3)][i%3+this.moduleCount-8-3]=mod;}for(i=0;i<18;i++){mod=(!test&&((bits>>i)&1)==1);this.modules[i%3+this.moduleCount-8-3][Math.floor(i/3)]=mod;}},setupTypeInfo:function(test,maskPattern){var data=(this.errorCorrectionLevel<<3)|maskPattern;var bits=QRUtil.getBCHTypeInfo(data);for(var i=0;i<15;i++){var mod=(!test&&((bits>>i)&1)==1);if(i<6){this.modules[i][8]=mod;}else if(i<8){this.modules[i+1][8]=mod;}else{this.modules[this.moduleCount-15+i][8]=mod;}}for(i=0;i<15;i++){mod=(!test&&((bits>>i)&1)==1);if(i<8){this.modules[8][this.moduleCount-i-1]=mod;}else if(i<9){this.modules[8][15-i-1+1]=mod;}else{this.modules[8][15-i-1]=mod;}}this.modules[this.moduleCount-8][8]=(!test);},mapData:function(data,maskPattern){var inc=-1;var row=this.moduleCount-1;var bitIndex=7;var byteIndex=0;for(var col=this.moduleCount-1;col>0;col-=2){if(col==6)col--;while(true){for(var c=0;c<2;c++){if(this.modules[row][col-c]==null){var dark=false;if(byteIndex<data.length){dark=(((data[byteIndex]>>>bitIndex)&1)==1);}var mask=QRUtil.getMask(maskPattern,row,col-c);if(mask){dark=!dark;}this.modules[row][col-c]=dark;bitIndex--;if(bitIndex==-1){byteIndex++;bitIndex=7;}}}row+=inc;if(row<0||this.moduleCount<=row){row-=inc;inc=-inc;break;}}}}};\nQRCode.createData=function(typeNumber,errorCorrectionLevel,dataList){var rsBlocks=QRRSBlock.getRSBlocks(typeNumber,errorCorrectionLevel);var buffer=new QRBitBuffer();for(var i=0;i<dataList.length;i++){var data=dataList[i];buffer.put(data.mode,4);buffer.put(data.getLength(),QRUtil.getLengthInBits(data.mode,typeNumber));data.write(buffer);}var totalDataCount=0;for(i=0;i<rsBlocks.length;i++){totalDataCount+=rsBlocks[i].dataCount;}if(buffer.getLengthInBits()>totalDataCount*8){throw new Error('code length overflow. ('+buffer.getLengthInBits()+'>'+totalDataCount*8+')');}if(buffer.getLengthInBits()+4<=totalDataCount*8){buffer.put(0,4);}while(buffer.getLengthInBits()%8!=0){buffer.putBit(false);}while(true){if(buffer.getLengthInBits()>=totalDataCount*8)break;buffer.put(PAD0,8);if(buffer.getLengthInBits()>=totalDataCount*8)break;buffer.put(PAD1,8);}return QRCode.createBytes(buffer,rsBlocks);};\nQRCode.createBytes=function(buffer,rsBlocks){var offset=0;var maxDcCount=0;var maxEcCount=0;var dcdata=new Array(rsBlocks.length);var ecdata=new Array(rsBlocks.length);for(var r=0;r<rsBlocks.length;r++){var dcCount=rsBlocks[r].dataCount;var ecCount=rsBlocks[r].totalCount-dcCount;maxDcCount=Math.max(maxDcCount,dcCount);maxEcCount=Math.max(maxEcCount,ecCount);dcdata[r]=new Array(dcCount);for(var i=0;i<dcdata[r].length;i++){dcdata[r][i]=0xff&buffer.buffer[i+offset];}offset+=dcCount;var rsPoly=QRUtil.getErrorCorrectPolynomial(ecCount);var rawPoly=new QRPolynomial(dcdata[r],rsPoly.getLength()-1);var modPoly=rawPoly.mod(rsPoly);ecdata[r]=new Array(rsPoly.getLength()-1);for(i=0;i<ecdata[r].length;i++){var modIndex=i+modPoly.getLength()-ecdata[r].length;ecdata[r][i]=(modIndex>=0)?modPoly.get(modIndex):0;}}var totalCodeCount=0;for(i=0;i<rsBlocks.length;i++){totalCodeCount+=rsBlocks[i].totalCount;}var data=new Array(totalCodeCount);var index=0;for(i=0;i<maxDcCount;i++){for(r=0;r<rsBlocks.length;r++){if(i<dcdata[r].length){data[index++]=dcdata[r][i];}}}for(i=0;i<maxEcCount;i++){for(r=0;r<rsBlocks.length;r++){if(i<ecdata[r].length){data[index++]=ecdata[r][i];}}}return data;};\nfunction QR8bitByte(data){this.mode=4;this.data=data;}QR8bitByte.prototype={getLength:function(){return this.data.length;},write:function(buffer){for(var i=0;i<this.data.length;i++){buffer.put(this.data.charCodeAt(i),8);}}};\nvar QRUtil={PATTERN_POSITION_TABLE:[[],[6,18],[6,22],[6,26],[6,30],[6,34],[6,22,38],[6,24,42],[6,26,46],[6,28,50],[6,30,54],[6,32,58],[6,34,62],[6,26,46,66],[6,26,48,70],[6,26,50,74],[6,30,54,78],[6,30,56,82],[6,30,58,86],[6,34,62,90],[6,28,50,72,94],[6,26,50,74,98],[6,30,54,78,102],[6,28,54,80,106],[6,32,58,84,110],[6,30,58,86,114],[6,34,62,90,118],[6,26,50,74,98,122],[6,30,54,78,102,126],[6,26,52,78,104,130],[6,30,56,82,108,134],[6,34,60,86,112,138],[6,30,58,86,114,142],[6,34,62,90,118,146],[6,30,54,78,102,126,150],[6,24,50,76,102,128,154],[6,28,54,80,106,132,158],[6,32,58,84,110,136,162],[6,26,54,82,110,138,166],[6,30,58,86,114,142,170]],G15:(1<<10)|(1<<8)|(1<<5)|(1<<4)|(1<<2)|(1<<1)|(1<<0),G18:(1<<12)|(1<<11)|(1<<10)|(1<<9)|(1<<8)|(1<<5)|(1<<2)|(1<<0),G15_MASK:(1<<14)|(1<<12)|(1<<10)|(1<<4)|(1<<1),getBCHTypeInfo:function(data){var d=data<<10;while(QRUtil.getBCHDigit(d)-QRUtil.getBCHDigit(QRUtil.G15)>=0){d^=(QRUtil.G15<<(QRUtil.getBCHDigit(d)-QRUtil.getBCHDigit(QRUtil.G15)));}return((data<<10)|d)^QRUtil.G15_MASK;},getBCHTypeNumber:function(data){var d=data<<12;while(QRUtil.getBCHDigit(d)-QRUtil.getBCHDigit(QRUtil.G18)>=0){d^=(QRUtil.G18<<(QRUtil.getBCHDigit(d)-QRUtil.getBCHDigit(QRUtil.G18)));}return(data<<12)|d;},getBCHDigit:function(data){var digit=0;while(data!=0){digit++;data>>>=1;}return digit;},getPatternPosition:function(typeNumber){return QRUtil.PATTERN_POSITION_TABLE[typeNumber-1];},getMask:function(maskPattern,i,j){switch(maskPattern){case 0:return(i+j)%2==0;case 1:return i%2==0;case 2:return j%3==0;case 3:return(i+j)%3==0;case 4:return(Math.floor(i/2)+Math.floor(j/3))%2==0;case 5:return(i*j)%2+(i*j)%3==0;case 6:return((i*j)%2+(i*j)%3)%2==0;case 7:return((i*j)%3+(i+j)%2)%2==0;default:throw new Error('bad maskPattern:'+maskPattern);}},getErrorCorrectPolynomial:function(errorCorrectLength){var a=new QRPolynomial([1],0);for(var i=0;i<errorCorrectLength;i++){a=a.multiply(new QRPolynomial([1,QRMath.gexp(i)],0));}return a;},getLengthInBits:function(mode,type){if(1<=type&&type<10){return 8;}else if(type<27){return 16;}else if(type<41){return 16;}throw new Error('type:'+type);},getLostPoint:function(qrCode){var moduleCount=qrCode.moduleCount;var lostPoint=0;for(var row=0;row<moduleCount;row++){for(var col=0;col<moduleCount;col++){var sameCount=0;var dark=qrCode.modules[row][col];for(var r=-1;r<=1;r++){if(row+r<0||moduleCount<=row+r)continue;for(var c=-1;c<=1;c++){if(col+c<0||moduleCount<=col+c)continue;if(r==0&&c==0)continue;if(dark==qrCode.modules[row+r][col+c]){sameCount++;}}}if(sameCount>5){lostPoint+=(3+sameCount-5);}}}for(row=0;row<moduleCount-1;row++){for(col=0;col<moduleCount-1;col++){var\
\ count=0;if(qrCode.modules[row][col])count++;if(qrCode.modules[row+1][col])count++;if(qrCode.modules[row][col+1])count++;if(qrCode.modules[row+1][col+1])count++;if(count==0||count==4){lostPoint+=3;}}}for(row=0;row<moduleCount;row++){for(col=0;col<moduleCount-6;col++){if(qrCode.modules[row][col]&&!qrCode.modules[row][col+1]&&qrCode.modules[row][col+2]&&qrCode.modules[row][col+3]&&qrCode.modules[row][col+4]&&!qrCode.modules[row][col+5]&&qrCode.modules[row][col+6]){lostPoint+=40;}}}for(col=0;col<moduleCount;col++){for(row=0;row<moduleCount-6;row++){if(qrCode.modules[row][col]&&!qrCode.modules[row+1][col]&&qrCode.modules[row+2][col]&&qrCode.modules[row+3][col]&&qrCode.modules[row+4][col]&&!qrCode.modules[row+5][col]&&qrCode.modules[row+6][col]){lostPoint+=40;}}}var darkCount=0;for(col=0;col<moduleCount;col++){for(row=0;row<moduleCount;row++){if(qrCode.modules[row][col]){darkCount++;}}}var ratio=Math.abs(100*darkCount/moduleCount/moduleCount-50)/5;lostPoint+=ratio*10;return lostPoint;}};\nvar QRMath={glog:function(n){if(n<1)throw new Error('glog('+n+')');return QRMath.LOG_TABLE[n];},gexp:function(n){while(n<0){n+=255;}while(n>=256){n-=255;}return QRMath.EXP_TABLE[n];},EXP_TABLE:new Array(256),LOG_TABLE:new Array(256)};for(var i=0;i<8;i++){QRMath.EXP_TABLE[i]=1<<i;}for(i=8;i<256;i++){QRMath.EXP_TABLE[i]=QRMath.EXP_TABLE[i-4]^QRMath.EXP_TABLE[i-5]^QRMath.EXP_TABLE[i-6]^QRMath.EXP_TABLE[i-8];}for(i=0;i<255;i++){QRMath.LOG_TABLE[QRMath.EXP_TABLE[i]]=i;}\nfunction QRPolynomial(num,shift){if(num.length==undefined)throw new Error(num.length+'/'+shift);var offset=0;while(offset<num.length&&num[offset]==0){offset++;}this.num=new Array(num.length-offset+shift);for(var i=0;i<num.length-offset;i++){this.num[i]=num[i+offset];}}QRPolynomial.prototype={get:function(index){return this.num[index];},getLength:function(){return this.num.length;},multiply:function(e){var num=new Array(this.getLength()+e.getLength()-1);for(var i=0;i<this.getLength();i++){for(var j=0;j<e.getLength();j++){num[i+j]^=QRMath.gexp(QRMath.glog(this.get(i))+QRMath.glog(e.get(j)));}}return new QRPolynomial(num,0);},mod:function(e){if(this.getLength()-e.getLength()<0)return this;var ratio=QRMath.glog(this.get(0))-QRMath.glog(e.get(0));var num=new Array(this.getLength());for(var i=0;i<this.getLength();i++){num[i]=this.get(i);}for(i=0;i<e.getLength();i++){num[i]^=QRMath.gexp(QRMath.glog(e.get(i))+ratio);}return new QRPolynomial(num,0).mod(e);}};\nfunction QRRSBlock(totalCount,dataCount){this.totalCount=totalCount;this.dataCount=dataCount;}\nQRRSBlock.RS_BLOCK_TABLE=[[1,26,19],[1,26,16],[1,26,13],[1,26,9],[1,44,34],[1,44,28],[1,44,22],[1,44,16],[1,70,55],[1,70,44],[2,35,17],[2,35,13],[1,100,80],[2,50,32],[2,50,24],[4,25,9],[1,134,108],[2,67,43],[2,33,15,2,34,16],[2,33,11,2,34,12],[2,86,68],[4,43,27],[4,43,19],[4,43,15],[2,98,78],[4,49,31],[2,32,14,4,33,15],[4,39,13,1,40,14],[2,121,97],[2,60,38,2,61,39],[4,40,18,2,41,19],[4,40,14,2,41,15],[2,146,116],[3,58,36,2,59,37],[4,36,16,4,37,17],[4,36,12,4,37,13],[2,86,68,2,87,69],[4,69,43,1,70,44],[6,43,19,2,44,20],[6,43,15,2,44,16],[4,101,81],[1,80,50,4,81,51],[4,50,22,4,51,23],[3,36,12,8,37,13],[2,116,92,2,117,93],[6,58,36,2,59,37],[4,46,20,6,47,21],[7,42,14,4,43,15],[4,133,107],[8,59,37,1,60,38],[8,44,20,4,45,21],[12,33,11,4,34,12],[3,145,115,1,146,116],[4,64,40,5,65,41],[11,36,16,5,37,17],[11,36,12,5,37,13],[5,109,87,1,110,88],[5,65,41,5,66,42],[5,54,24,7,55,25],[11,36,12,7,37,13],[5,122,98,1,123,99],[7,73,45,3,74,46],[15,43,19,2,44,20],[3,45,15,13,46,16],[1,135,107,5,136,108],[10,74,46,1,75,47],[1,50,22,15,51,23],[2,42,14,17,43,15],[5,150,120,1,151,121],[9,69,43,4,70,44],[17,50,22,1,51,23],[2,42,14,19,43,15],[3,141,113,4,142,114],[3,70,44,11,71,45],[17,47,21,4,48,22],[9,39,13,16,40,14],[3,135,107,5,136,108],[3,67,41,13,68,42],[15,54,24,5,55,25],[15,43,15,10,44,16],[4,144,116,4,145,117],[17,68,42],[17,50,22,6,51,23],[19,46,16,6,47,17],[2,139,111,7,140,112],[17,74,46],[7,54,24,16,55,25],[34,37,13],[4,151,121,5,152,122],[4,75,47,14,76,48],[11,54,24,14,55,25],[16,45,15,14,46,16],[6,147,117,4,148,118],[6,73,45,14,74,46],[11,54,24,16,55,25],[30,46,16,2,47,17],[8,132,106,4,133,107],[8,75,47,13,76,48],[7,54,24,22,55,25],[22,45,15,13,46,16],[10,142,114,2,143,115],[19,74,46,4,75,47],[28,50,22,6,51,23],[33,46,16,4,47,17],[8,152,122,4,153,123],[22,73,45,3,74,46],[8,53,23,26,54,24],[12,45,15,28,46,16],[3,147,117,10,148,118],[3,73,45,23,74,46],[4,54,24,31,55,25],[11,45,15,31,46,16],[7,146,116,7,147,117],[21,73,45,7,74,46],[1,53,23,37,54,24],[19,45,15,26,46,16],[5,145,115,10,146,116],[19,75,47,10,76,48],[15,54,24,25,55,25],[23,45,15,25,46,16],[13,145,115,3,146,116],[2,74,46,29,75,47],[42,54,24,1,55,25],[23,45,15,28,46,16],[17,145,115],[10,74,46,23,75,47],[10,54,24,35,55,25],[19,45,15,35,46,16],[17,145,115,1,146,116],[14,74,46,21,75,47],[29,54,24,19,55,25],[11,45,15,46,46,16],[13,145,115,6,146,116],[14,74,46,23,75,47],[44,54,24,7,55,25],[59,46,16,1,47,17],[12,151,121,7,152,122],[12,75,47,26,76,48],[39,54,24,14,55,25],[22,45,15,41,46,16],[6,151,121,14,152,122],[6,75,47,34,76,48],[46,54,24,10,55,25],[2,45,15,64,46,16],[17,152,122,4,153,123],[29,74,46,14,75,47],[49,54,24,10,55,25],[24,45,15,46,46,16],[4,152,122,18,153,123],[13,74,46,32,75,47],[48,54,24,14,55,25],[42,45,15,32,46,16],[20,147,117,4,148,118],[40,75,47,7,76,48],[43,54,24,22,55,25],[10,45,15,67,46,16],[19,148,118,6,149,119],[18,75,47,31,76,48],[34,54,24,34,55,25],[20,45,15,61,46,16]];\nQRRSBlock.getRSBlocks=function(typeNumber,errorCorrectionLevel){var rsBlock=QRRSBlock.getRsBlockTable(typeNumber,errorCorrectionLevel);if(rsBlock==undefined){throw new Error('bad rs block @ typeNumber:'+typeNumber+'/errorCorrectionLevel:'+errorCorrectionLevel);}var length=rsBlock.length/3;var list=[];for(var i=0;i<length;i++){var count=rsBlock[i*3+0];var totalCount=rsBlock[i*3+1];var dataCount=rsBlock[i*3+2];for(var j=0;j<count;j++){list.push(new QRRSBlock(totalCount,dataCount));}}return list;};\nQRRSBlock.getRsBlockTable=function(typeNumber,errorCorrectionLevel){switch(errorCorrectionLevel){case 1:return QRRSBlock.RS_BLOCK_TABLE[(typeNumber-1)*4+0];case 0:return QRRSBlock.RS_BLOCK_TABLE[(typeNumber-1)*4+1];case 3:return QRRSBlock.RS_BLOCK_TABLE[(typeNumber-1)*4+2];case 2:return QRRSBlock.RS_BLOCK_TABLE[(typeNumber-1)*4+3];default:return undefined;}};\nfunction QRBitBuffer(){this.buffer=[];this.length=0;}QRBitBuffer.prototype={get:function(index){var bufIndex=Math.floor(index/8);return((this.buffer[bufIndex]>>>(7-index%8))&1)==1;},put:function(num,length){for(var i=0;i<length;i++){this.putBit(((num>>>(length-i-1))&1)==1);}},getLengthInBits:function(){return this.length;},putBit:function(bit){var bufIndex=Math.floor(this.length/8);if(this.buffer.length<=bufIndex){this.buffer.push(0);}if(bit){this.buffer[bufIndex]|=(0x80>>>(this.length%8));}this.length++;}};\nreturn QRCode;})();\n\n/* ===== WiFi QR Code Rendering ===== */\n/* QR codes with passwords are generated via 1Password app, not stored in this page.\n The inline QR library above is retained for potential future use (e.g., guest open network QR). */\nfunction initWifiQRCodes() {\n /* Placeholder — QR codes are now served from 1Password app */\n}\n\nfunction printQRCodes() {\n /* Switch to WiFi tab, mark it for print, print, then restore */\n var allTabs = document.querySelectorAll('.tab-content');\n var wifiTab = document.getElementById('tab-wifi');\n allTabs.forEach(function(t) { t.classList.remove('print-active'); });\n if (wifiTab) { wifiTab.classList.add('print-active'); }\n window.print();\n if (wifiTab) { wifiTab.classList.remove('print-active'); }\n}\n\n/* ===== Existing Functions ===== */\nfunction toggleTheme(){var h=document.documentElement,c=h.getAttribute('data-theme'),n=c==='dark'?'light':'dark';h.setAttribute('data-theme',n);document.getElementById('themeLabel').textContent=n==='dark'?'Light':'Dark';try{localStorage.setItem('bjTheme',n)}catch(e){}}\n(function(){try{var s=localStorage.getItem('bjTheme');if(s){document.documentElement.setAttribute('data-theme',s);var l=document.getElementById('themeLabel');if(l)l.textContent=s==='dark'?'Light':'Dark'}}catch(e){}})();\nvar ALL_TABS=['overview','isp','pfsense','switching','dns','k8s','noc','vpn','edge','storage','wifi','planned','topology','domains','credentials'];\nfunction switchTab(id,el){document.querySelectorAll('.tab-content').forEach(function(t){t.classList.remove('active')});document.querySelectorAll('.tab-btn').forEach(function(b){b.classList.remove('active')});var tgt=document.getElementById('tab-'+id);if(tgt)tgt.classList.add('active');if(el)el.classList.add('active');history.replaceState(null,null,'#'+id);window.scrollTo({top:0,behavior:'smooth'});if(id==='wifi'){setTimeout(initWifiQRCodes,50);}}\n(function(){var h=location.hash.replace('#','');if(h){var tgt=document.getElementById('tab-'+h);if(tgt){document.querySelectorAll('.tab-content').forEach(function(t){t.classList.remove('active')});document.querySelectorAll('.tab-btn').forEach(function(b){b.classList.remove('active')});tgt.classList.add('active');var idx=ALL_TABS.indexOf(h);var btns=document.querySelectorAll('.tab-btn');if(idx>=0&&btns[idx])btns[idx].classList.add('active');if(h==='wifi'){setTimeout(initWifiQRCodes,100);}}}})();\nfunction copyText(t){var btn=event.target;if(navigator.clipboard){navigator.clipboard.writeText(t).then(function(){btn.textContent='copied!';btn.classList.add('copied');setTimeout(function(){btn.textContent='copy';btn.classList.remove('copied')},1500)})}else{var ta=document.createElement('textarea');ta.value=t;ta.style.position='fixed';ta.style.opacity='0';document.body.appendChild(ta);ta.select();try{document.execCommand('copy')}catch(e){}document.body.removeChild(ta);btn.textContent='copied!';btn.classList.add('copied');setTimeout(function(){btn.textContent='copy';btn.classList.remove('copied')},1500)}}\ndocument.addEventListener('keydown',function(e){if(e.altKey){var k=parseInt(e.key);if(k>=1&&k<=9){e.preventDefault();var idx=k-1;if(idx<ALL_TABS.length){var btns=document.querySelectorAll('.tab-btn');btns.forEach(function(b){b.classList.remove('active')});if(btns[idx])btns[idx].classList.add('active');document.querySelectorAll('.tab-content').forEach(function(t){t.classList.remove('active')});var\
\ tgt=document.getElementById('tab-'+ALL_TABS[idx]);if(tgt)tgt.classList.add('active');if(ALL_TABS[idx]==='wifi'){setTimeout(initWifiQRCodes,50);}}}if(e.key==='0'){e.preventDefault();var btns=document.querySelectorAll('.tab-btn');btns.forEach(function(b){b.classList.remove('active')});if(btns[9])btns[9].classList.add('active');document.querySelectorAll('.tab-content').forEach(function(t){t.classList.remove('active')});var tgt=document.getElementById('tab-'+ALL_TABS[9]);if(tgt)tgt.classList.add('active');if(ALL_TABS[9]==='wifi'){setTimeout(initWifiQRCodes,50);}}}});\n</script>\n</body>\n</html>\n"
kind: ConfigMap
metadata:
name: intranet-html
namespace: intranet
---
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: intranet
name: intranet
namespace: intranet
spec:
replicas: 1
selector:
matchLabels:
app: intranet
template:
metadata:
labels:
app: intranet
spec:
containers:
- image: nginx:alpine
livenessProbe:
httpGet:
path: /healthz
port: 80
initialDelaySeconds: 5
periodSeconds: 10
name: nginx
ports:
- containerPort: 80
name: http
readinessProbe:
httpGet:
path: /healthz
port: 80
initialDelaySeconds: 3
periodSeconds: 5
resources:
limits:
cpu: 50m
memory: 64Mi
requests:
cpu: 5m
memory: 16Mi
volumeMounts:
- mountPath: /etc/nginx/conf.d/default.conf
name: nginx-conf
subPath: default.conf
- mountPath: /usr/share/nginx/html
name: html
volumes:
- configMap:
name: intranet-nginx-conf
name: nginx-conf
- configMap:
name: intranet-html
name: html
---
apiVersion: v1
kind: Service
metadata:
name: intranet
namespace: intranet
spec:
ports:
- name: http
port: 80
targetPort: 80
selector:
app: intranet
---
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: intranet-tls
namespace: intranet
spec:
dnsNames:
- intranet.iamworkin.lan
issuerRef:
kind: ClusterIssuer
name: step-ca-acme
secretName: intranet-tls
---
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: intranet
namespace: intranet
spec:
entryPoints:
- websecure
routes:
- kind: Rule
match: Host(`intranet.iamworkin.lan`)
services:
- name: intranet
port: 80
tls:
secretName: intranet-tls