diff --git a/DEPLOYMENT_HANDOFF.md b/DEPLOYMENT_HANDOFF.md index 704d67f..6cdc5e5 100644 --- a/DEPLOYMENT_HANDOFF.md +++ b/DEPLOYMENT_HANDOFF.md @@ -2,12 +2,12 @@ ## Where We Left Off -Phase 2 (AI stack) is fully deployed and verified on PlausibleDeniability (10.5.1.6). We were about to verify Gotify (Phase 1) and then deploy n8n (Phase 3). +Phase 2 (AI stack) is fully deployed and verified on PlausibleDeniability (10.5.30.6). We were about to verify Gotify (Phase 1) and then deploy n8n (Phase 3). ## What's Done ### Phase 2 — AI Stack (COMPLETE) -All 6 containers running and verified on PD (10.5.1.6): +All 6 containers running and verified on PD (10.5.30.6): - **Ollama:** port 11434 — qwen2.5:14b loaded - **OpenWebUI:** port 8282 (→8080) - **Qdrant:** port 6333 (HTTP), 6334 (gRPC) @@ -16,7 +16,7 @@ All 6 containers running and verified on PD (10.5.1.6): - **Whisper:** port 8786 (→8000) ### Key Fixes Applied (reference for future troubleshooting) -1. **TrueNAS Scale Docker quirk:** `curl localhost:PORT` returns 000. Always use `curl 10.5.1.6:PORT`. +1. **TrueNAS Scale Docker quirk:** `curl localhost:PORT` returns 000. Always use `curl 10.5.30.6:PORT`. 2. **Reranker image:** Use `cpu-latest` not `cpu-1.5` (hf-hub bug). 3. **LiteLLM config:** Remove `database_url` (newer versions try Prisma/PostgreSQL). Set `master_key: "os.environ/LITELLM_MASTER_KEY"` to read from env var. @@ -32,7 +32,7 @@ docker ps --filter name=gotify --format "table {{.Names}}\t{{.Image}}\t{{.Status docker logs gotify --tail 20 docker inspect gotify --format '{{json .Mounts}}' | python3 -m json.tool docker inspect gotify --format '{{json .NetworkSettings.Networks}}' | python3 -m json.tool -curl -sw "\n%{http_code}" http://10.5.1.6:8484/health +curl -sw "\n%{http_code}" http://10.5.30.6:8484/health ls -la /mnt/docker-ssd/docker/compose/automation/ cat /mnt/docker-ssd/docker/compose/automation/docker-compose.yaml ``` @@ -57,9 +57,9 @@ Per HOMELAB_BUILDOUT_PLAN.md Phase 3: | pangolin | Reverse proxy / external exposure | ## Machine IPs -- **PlausibleDeniability (PD):** 10.5.1.6 — TrueNAS Scale, RTX 2080 Ti +- **PlausibleDeniability (PD):** 10.5.30.6 — TrueNAS Scale, RTX 2080 Ti - **ROCINANTE:** 10.5.1.112 — RTX 4090 -- **N.O.M.A.D.:** 10.5.1.16 — Ubuntu, GTX 1080 +- **N.O.M.A.D.:** 10.5.30.7 — Ubuntu, GTX 1080 ## LiteLLM .env Secrets (on PD at /mnt/docker-ssd/docker/compose/ai/.env) Already configured — do not regenerate. Master key is in the .env file as LITELLM_MASTER_KEY. diff --git a/README.md b/README.md index e192d79..bf021c6 100644 --- a/README.md +++ b/README.md @@ -6,9 +6,9 @@ Docker Compose stacks for PlausibleDeniability (TrueNAS Scale), plus documentati | Server | Role | OS | IP | |--------|------|----|----| -| Serenity | NAS / Media ingestion | Unraid 7.2.4 | 10.5.1.5 | -| PlausibleDeniability | Primary Docker host | TrueNAS Scale 25.10.2.1 | 10.5.1.6 | -| N.O.M.A.D. | Offline knowledge + game servers | Ubuntu 25.10 | 10.5.1.16 | +| Serenity | NAS / Media ingestion | Unraid 7.2.4 | 10.5.30.5 | +| PlausibleDeniability | Primary Docker host | TrueNAS Scale 25.10.2.1 | 10.5.30.6 | +| N.O.M.A.D. | Offline knowledge + game servers | Ubuntu 25.10 | 10.5.30.7 | ## Repo Layout diff --git a/ai/litellm/config.yaml b/ai/litellm/config.yaml index 8a03d77..1d0c78a 100644 --- a/ai/litellm/config.yaml +++ b/ai/litellm/config.yaml @@ -1,9 +1,9 @@ model_list: - # ---- Reranker (Serenity, 10.5.1.5) ---- + # ---- Reranker (Serenity, 10.5.30.5) ---- - model_name: "reranker" litellm_params: model: "huggingface/BAAI/bge-reranker-v2-m3" - api_base: "http://10.5.1.5:9787" + api_base: "http://10.5.30.5:9787" # ---- ROCINANTE (RTX 4090, 24GB) — heavy reasoning ---- - model_name: "heavy" litellm_params: @@ -38,14 +38,14 @@ model_list: - model_name: "light" litellm_params: model: "ollama/deepseek-r1" - api_base: "http://10.5.1.16:11434" + api_base: "http://10.5.30.7:11434" timeout: 60 stream_timeout: 60 - model_name: "light" litellm_params: model: "ollama/qwen2.5:3b" - api_base: "http://10.5.1.16:11434" + api_base: "http://10.5.30.7:11434" timeout: 60 stream_timeout: 60 @@ -53,7 +53,7 @@ model_list: - model_name: "embed" litellm_params: model: "ollama/nomic-embed-text:v1.5" - api_base: "http://10.5.1.16:11434" + api_base: "http://10.5.30.7:11434" # ---- Direct model access (bypass tier routing) ---- - model_name: "ollama/qwen3.6:latest" @@ -79,17 +79,17 @@ model_list: - model_name: "ollama/deepseek-r1" litellm_params: model: "ollama/deepseek-r1" - api_base: "http://10.5.1.16:11434" + api_base: "http://10.5.30.7:11434" - model_name: "ollama/qwen2.5:3b" litellm_params: model: "ollama/qwen2.5:3b" - api_base: "http://10.5.1.16:11434" + api_base: "http://10.5.30.7:11434" - model_name: "ollama/nomic-embed-text:v1.5" litellm_params: model: "ollama/nomic-embed-text:v1.5" - api_base: "http://10.5.1.16:11434" + api_base: "http://10.5.30.7:11434" litellm_settings: drop_params: true diff --git a/automation/.env.example b/automation/.env.example index bb322c7..a345f34 100644 --- a/automation/.env.example +++ b/automation/.env.example @@ -29,7 +29,7 @@ UNIFI_SITE=default UNIFI_VERIFY_TLS=false # Backup automation scaffolding -SERENITY_BACKUP_HOST=root@10.5.1.5 +SERENITY_BACKUP_HOST=root@10.5.30.5 SERENITY_BACKUP_ROOT=/mnt/user/backups/plausible-deniability PD_APPDATA_ROOT=/mnt/docker-ssd/docker/appdata PD_DATABASES_ROOT=/mnt/docker-ssd/docker/databases diff --git a/automation/README.md b/automation/README.md index 85f41f1..8414a13 100644 --- a/automation/README.md +++ b/automation/README.md @@ -44,7 +44,7 @@ Repo-side deployment prerequisites remain: 1. real values in `.env` 2. a writable dump destination on PD -3. SSH trust / key path for Serenity (`root@10.5.1.5` by default) +3. SSH trust / key path for Serenity (`root@10.5.30.5` by default) - recommended known_hosts path on PD: `/home/truenas_admin/.ssh/known_hosts` 4. a real scheduler on the live host diff --git a/automation/bin/unifi_stage_low_risk_objects.py b/automation/bin/unifi_stage_low_risk_objects.py new file mode 100755 index 0000000..4e7477e --- /dev/null +++ b/automation/bin/unifi_stage_low_risk_objects.py @@ -0,0 +1,270 @@ +#!/usr/bin/env python3 +"""Stage low-risk UniFi objects for the network cutover. + +Default mode is read-only dry-run. Pass --apply to actually create missing objects. +Currently stages: +- Servers network object +- Servers port profile + +This is intentionally idempotent: if the objects already exist, it reports them and exits cleanly. +""" + +from __future__ import annotations + +import argparse +import json +import os +import ssl +import sys +from http.cookiejar import CookieJar +from pathlib import Path +from typing import Any +from urllib import error, request + +DEFAULT_TIMEOUT = 20 +SCRIPT_DIR = Path(__file__).resolve().parent +REPO_ROOT = SCRIPT_DIR.parent.parent +DEFAULT_ENV_PATH = REPO_ROOT / 'automation' / '.env' + + +def load_env_file(path: Path) -> None: + if not path.exists(): + return + for raw_line in path.read_text(encoding='utf-8').splitlines(): + line = raw_line.strip() + if not line or line.startswith('#') or '=' not in line: + continue + key, value = line.split('=', 1) + os.environ.setdefault(key.strip(), value.strip().strip('"').strip("'")) + + +def env_bool(name: str, default: bool) -> bool: + value = os.environ.get(name) + if value is None: + return default + return value.strip().lower() in {'1', 'true', 'yes', 'on'} + + +class UniFiClient: + def __init__(self, base_url: str, username: str, password: str, *, verify_tls: bool = True, timeout: int = DEFAULT_TIMEOUT) -> None: + self.base_url = base_url.rstrip('/') + self.username = username + self.password = password + self.timeout = timeout + self.cookie_jar = CookieJar() + self.csrf_token: str | None = None + handlers: list[Any] = [request.HTTPCookieProcessor(self.cookie_jar)] + if not verify_tls: + handlers.append(request.HTTPSHandler(context=ssl._create_unverified_context())) + self.opener = request.build_opener(*handlers) + + def _json_request(self, method: str, path: str, body: dict[str, Any] | None = None) -> tuple[int, Any]: + url = self.base_url + path + data = None + headers = { + 'Accept': 'application/json', + 'Content-Type': 'application/json', + 'User-Agent': 'doris-unifi-stage/1.0', + } + if method.upper() != 'GET' and self.csrf_token: + headers['X-Csrf-Token'] = self.csrf_token + if body is not None: + data = json.dumps(body).encode('utf-8') + req = request.Request(url, data=data, method=method.upper(), headers=headers) + try: + with self.opener.open(req, timeout=self.timeout) as resp: + if method.upper() == 'POST': + self.csrf_token = resp.headers.get('X-Csrf-Token', self.csrf_token) + raw = resp.read().decode('utf-8', 'replace') + ctype = resp.headers.get('Content-Type', '') + payload = json.loads(raw) if 'json' in ctype and raw else raw + return resp.status, payload + except error.HTTPError as exc: + raw = exc.read().decode('utf-8', 'replace') + try: + payload = json.loads(raw) if raw else {} + except Exception: + payload = raw + return exc.code, payload + + def login(self) -> None: + status, payload = self._json_request('POST', '/api/auth/login', { + 'username': self.username, + 'password': self.password, + 'rememberMe': True, + }) + if status >= 300: + raise SystemExit(f'UniFi login failed: HTTP {status} {payload!r}') + + def get(self, path: str) -> Any: + status, payload = self._json_request('GET', path) + if status >= 300: + raise SystemExit(f'UniFi GET {path} failed: HTTP {status} {payload!r}') + return payload.get('data', payload) if isinstance(payload, dict) else payload + + def post(self, path: str, body: dict[str, Any]) -> Any: + status, payload = self._json_request('POST', path, body) + if status >= 300: + raise SystemExit(f'UniFi POST {path} failed: HTTP {status} {payload!r}') + return payload.get('data', payload) if isinstance(payload, dict) else payload + + def site_path(self, site: str, suffix: str) -> str: + suffix = suffix.lstrip('/') + return f'/proxy/network/api/s/{site}/{suffix}' + + +def find_by_name(items: list[dict[str, Any]], name: str) -> dict[str, Any] | None: + return next((item for item in items if item.get('name') == name), None) + + +def servers_network_payload() -> dict[str, Any]: + return { + 'name': 'Servers', + 'purpose': 'corporate', + 'enabled': True, + 'networkgroup': 'LAN', + 'setting_preference': 'manual', + 'vlan_enabled': True, + 'vlan': 30, + 'ip_subnet': '10.5.30.1/24', + 'dhcpd_enabled': True, + 'dhcpd_start': '10.5.30.100', + 'dhcpd_stop': '10.5.30.199', + 'dhcpd_leasetime': 86400, + 'dhcpd_gateway_enabled': False, + 'dhcpd_dns_enabled': False, + 'dhcpd_ntp_enabled': False, + 'dhcpd_wins_enabled': False, + 'dhcpd_time_offset_enabled': False, + 'dhcpd_boot_enabled': False, + 'dhcpd_conflict_checking': True, + 'dhcp_relay_enabled': False, + 'dhcp_relay_servers': [], + 'domain_name': '', + 'ipv6_interface_type': 'none', + 'ipv6_setting_preference': 'manual', + 'ipv6_aliases': [], + 'ip_aliases': [], + 'internet_access_enabled': True, + 'network_isolation_enabled': False, + 'gateway_type': 'default', + 'nat_outbound_ip_addresses': [], + 'mdns_enabled': True, + 'lte_lan_enabled': True, + 'auto_scale_enabled': True, + } + + +def build_servers_port_profile_payload(template: dict[str, Any], servers_network_id: str) -> dict[str, Any]: + payload = { + k: v for k, v in template.items() + if k not in {'_id', 'site_id', 'name', 'native_networkconf_id'} + } + payload['name'] = 'Servers' + payload['native_networkconf_id'] = servers_network_id + return payload + + +def main() -> int: + parser = argparse.ArgumentParser(description='Stage low-risk UniFi objects for the cutover.') + parser.add_argument('--env-file', default=str(DEFAULT_ENV_PATH), help='Path to .env containing UNIFI_* vars') + parser.add_argument('--site', help='UniFi site name; defaults to UNIFI_SITE or default') + parser.add_argument('--apply', action='store_true', help='Actually create missing objects. Default is dry-run only.') + parser.add_argument('--timeout', type=int, default=DEFAULT_TIMEOUT) + args = parser.parse_args() + + load_env_file(Path(args.env_file)) + base_url = os.environ.get('UNIFI_BASE_URL', 'https://10.5.0.1') + username = os.environ.get('UNIFI_USERNAME') + password = os.environ.get('UNIFI_PASSWORD') + site = args.site or os.environ.get('UNIFI_SITE', 'default') + verify_tls = env_bool('UNIFI_VERIFY_TLS', False) + + if not username or not password: + raise SystemExit('Missing UNIFI_USERNAME or UNIFI_PASSWORD in env file/environment.') + + client = UniFiClient(base_url, username, password, verify_tls=verify_tls, timeout=args.timeout) + client.login() + + networks = client.get(client.site_path(site, 'rest/networkconf')) + port_profiles = client.get(client.site_path(site, 'rest/portconf')) + + result: dict[str, Any] = { + 'mode': 'apply' if args.apply else 'dry-run', + 'site': site, + 'changes': [], + } + + servers_network = find_by_name(networks, 'Servers') + if servers_network: + result['changes'].append({ + 'object': 'network', + 'name': 'Servers', + 'status': 'exists', + 'id': servers_network.get('_id'), + 'vlan': servers_network.get('vlan'), + 'ip_subnet': servers_network.get('ip_subnet'), + }) + else: + payload = servers_network_payload() + if args.apply: + created = client.post(client.site_path(site, 'rest/networkconf'), payload) + if isinstance(created, list): + created = created[0] if created else {} + servers_network = created + result['changes'].append({ + 'object': 'network', + 'name': 'Servers', + 'status': 'created', + 'id': created.get('_id'), + 'vlan': created.get('vlan'), + 'ip_subnet': created.get('ip_subnet'), + }) + else: + servers_network = {'_id': ''} + result['changes'].append({ + 'object': 'network', + 'name': 'Servers', + 'status': 'would_create', + 'payload': payload, + }) + + servers_port_profile = find_by_name(port_profiles, 'Servers') + if servers_port_profile: + result['changes'].append({ + 'object': 'port_profile', + 'name': 'Servers', + 'status': 'exists', + 'id': servers_port_profile.get('_id'), + 'native_networkconf_id': servers_port_profile.get('native_networkconf_id'), + }) + else: + trusted_template = find_by_name(port_profiles, 'Trusted') + if not trusted_template: + raise SystemExit('Could not find existing Trusted port profile to clone.') + payload = build_servers_port_profile_payload(trusted_template, servers_network['_id']) + if args.apply: + created = client.post(client.site_path(site, 'rest/portconf'), payload) + if isinstance(created, list): + created = created[0] if created else {} + result['changes'].append({ + 'object': 'port_profile', + 'name': 'Servers', + 'status': 'created', + 'id': created.get('_id'), + 'native_networkconf_id': created.get('native_networkconf_id'), + }) + else: + result['changes'].append({ + 'object': 'port_profile', + 'name': 'Servers', + 'status': 'would_create', + 'payload': payload, + }) + + print(json.dumps(result, indent=2, sort_keys=True)) + return 0 + + +if __name__ == '__main__': + raise SystemExit(main()) diff --git a/dev/rackpeek/config.yaml b/dev/rackpeek/config.yaml index e66d7d6..3d99b7c 100644 --- a/dev/rackpeek/config.yaml +++ b/dev/rackpeek/config.yaml @@ -27,7 +27,7 @@ resources: - docker-host - ai-light-tier labels: - lan-ip: 10.5.1.6 + lan-ip: 10.5.30.6 os: TrueNAS Scale 25.10.2.1 docker-root: /mnt/docker-ssd/docker/docker-data compose-root: /mnt/docker-ssd/docker/compose @@ -58,7 +58,7 @@ resources: - storage - reranker labels: - lan-ip: 10.5.1.5 + lan-ip: 10.5.30.5 tailscale-ip: 100.94.87.79 os: Unraid 7.2.4 notes: |- @@ -99,7 +99,7 @@ resources: - offline-knowledge - game-server labels: - lan-ip: 10.5.1.16 + lan-ip: 10.5.30.7 os: Ubuntu 25.10 notes: |- Offline knowledge, game server, and local services host. @@ -244,7 +244,7 @@ resources: os: truenas-scale-25.10.2.1 cores: 16 ram: 32 - ip: 10.5.1.6 + ip: 10.5.30.6 runsOn: - plausibledeniability @@ -254,7 +254,7 @@ resources: os: unraid-7.2.4 cores: 12 ram: 102 - ip: 10.5.1.5 + ip: 10.5.30.5 runsOn: - serenity @@ -264,7 +264,7 @@ resources: os: ubuntu-25.10 cores: 4 ram: 32 - ip: 10.5.1.16 + ip: 10.5.30.7 runsOn: - nomad @@ -319,37 +319,37 @@ resources: - kind: Service name: homepage network: - ip: 10.5.1.6 + ip: 10.5.30.6 port: 3300 protocol: TCP - url: http://10.5.1.6:3300 + url: http://10.5.30.6:3300 runsOn: - pd-truenas - kind: Service name: dockhand network: - ip: 10.5.1.6 + ip: 10.5.30.6 port: 3230 protocol: TCP - url: http://10.5.1.6:3230 + url: http://10.5.30.6:3230 runsOn: - pd-truenas - kind: Service name: uptime-kuma network: - ip: 10.5.1.6 + ip: 10.5.30.6 port: 3001 protocol: TCP - url: http://10.5.1.6:3001 + url: http://10.5.30.6:3001 runsOn: - pd-truenas - kind: Service name: gotify network: - ip: 10.5.1.6 + ip: 10.5.30.6 port: 8443 protocol: TCP url: https://gotify.paccoco.com @@ -359,50 +359,50 @@ resources: - kind: Service name: gitea network: - ip: 10.5.1.6 + ip: 10.5.30.6 port: 3002 protocol: TCP - url: http://10.5.1.6:3002 + url: http://10.5.30.6:3002 runsOn: - pd-truenas - kind: Service name: gitea-ssh network: - ip: 10.5.1.6 + ip: 10.5.30.6 port: 2222 protocol: TCP - url: ssh://git@10.5.1.6:2222/ + url: ssh://git@10.5.30.6:2222/ runsOn: - pd-truenas - kind: Service name: shlink network: - ip: 10.5.1.6 + ip: 10.5.30.6 port: 8087 protocol: TCP - url: http://10.5.1.6:8087 + url: http://10.5.30.6:8087 runsOn: - pd-truenas - kind: Service name: shlink-web-client network: - ip: 10.5.1.6 + ip: 10.5.30.6 port: 8088 protocol: TCP - url: http://10.5.1.6:8088 + url: http://10.5.30.6:8088 runsOn: - pd-truenas - kind: Service name: rackpeek network: - ip: 10.5.1.6 + ip: 10.5.30.6 port: 8283 protocol: TCP - url: http://10.5.1.6:8283 + url: http://10.5.30.6:8283 tags: - source-of-truth-target runsOn: @@ -425,7 +425,7 @@ resources: - kind: Service name: authelia network: - ip: 10.5.1.6 + ip: 10.5.30.6 port: 9091 protocol: TCP url: https://auth.paccoco.com @@ -435,60 +435,60 @@ resources: - kind: Service name: litellm network: - ip: 10.5.1.6 + ip: 10.5.30.6 port: 4000 protocol: TCP - url: http://10.5.1.6:4000 + url: http://10.5.30.6:4000 runsOn: - pd-truenas - kind: Service name: openwebui network: - ip: 10.5.1.6 + ip: 10.5.30.6 port: 8282 protocol: TCP - url: http://10.5.1.6:8282 + url: http://10.5.30.6:8282 runsOn: - pd-truenas - kind: Service name: qdrant network: - ip: 10.5.1.6 + ip: 10.5.30.6 port: 6333 protocol: TCP - url: http://10.5.1.6:6333 + url: http://10.5.30.6:6333 runsOn: - pd-truenas - kind: Service name: searxng network: - ip: 10.5.1.6 + ip: 10.5.30.6 port: 8888 protocol: TCP - url: http://10.5.1.6:8888 + url: http://10.5.30.6:8888 runsOn: - pd-truenas - kind: Service name: ollama-light-tier network: - ip: 10.5.1.6 + ip: 10.5.30.6 port: 11434 protocol: TCP - url: http://10.5.1.6:11434 + url: http://10.5.30.6:11434 runsOn: - pd-truenas - kind: Service name: whisper-cpu network: - ip: 10.5.1.16 + ip: 10.5.30.7 port: 8786 protocol: TCP - url: http://10.5.1.16:8786 + url: http://10.5.30.7:8786 runsOn: - nomad-ubuntu @@ -519,207 +519,207 @@ resources: - kind: Service name: reranker network: - ip: 10.5.1.5 + ip: 10.5.30.5 port: 9787 protocol: TCP - url: http://10.5.1.5:9787 + url: http://10.5.30.5:9787 runsOn: - serenity-unraid - kind: Service name: plex network: - ip: 10.5.1.6 + ip: 10.5.30.6 port: 32400 protocol: TCP - url: http://10.5.1.6:32400 + url: http://10.5.30.6:32400 runsOn: - pd-truenas - kind: Service name: tautulli network: - ip: 10.5.1.6 + ip: 10.5.30.6 port: 8181 protocol: TCP - url: http://10.5.1.6:8181 + url: http://10.5.30.6:8181 runsOn: - pd-truenas - kind: Service name: audiobookshelf network: - ip: 10.5.1.6 + ip: 10.5.30.6 port: 13358 protocol: TCP - url: http://10.5.1.6:13358 + url: http://10.5.30.6:13358 runsOn: - pd-truenas - kind: Service name: calibre-web network: - ip: 10.5.1.6 + ip: 10.5.30.6 port: 8183 protocol: TCP - url: http://10.5.1.6:8183 + url: http://10.5.30.6:8183 runsOn: - pd-truenas - kind: Service name: seerr network: - ip: 10.5.1.6 + ip: 10.5.30.6 port: 5055 protocol: TCP - url: http://10.5.1.6:5055 + url: http://10.5.30.6:5055 runsOn: - pd-truenas - kind: Service name: immich network: - ip: 10.5.1.6 + ip: 10.5.30.6 port: 2283 protocol: TCP - url: http://10.5.1.6:2283 + url: http://10.5.30.6:2283 runsOn: - pd-truenas - kind: Service name: paperless-ngx network: - ip: 10.5.1.6 + ip: 10.5.30.6 port: 8083 protocol: TCP - url: http://10.5.1.6:8083 + url: http://10.5.30.6:8083 runsOn: - pd-truenas - kind: Service name: karakeep network: - ip: 10.5.1.6 + ip: 10.5.30.6 port: 3100 protocol: TCP - url: http://10.5.1.6:3100 + url: http://10.5.30.6:3100 runsOn: - pd-truenas - kind: Service name: n8n network: - ip: 10.5.1.6 + ip: 10.5.30.6 port: 5678 protocol: TCP - url: http://10.5.1.6:5678 + url: http://10.5.30.6:5678 runsOn: - pd-truenas - kind: Service name: kitchenowl network: - ip: 10.5.1.6 + ip: 10.5.30.6 port: 8086 protocol: TCP - url: http://10.5.1.6:8086 + url: http://10.5.30.6:8086 runsOn: - pd-truenas - kind: Service name: donetick network: - ip: 10.5.1.6 + ip: 10.5.30.6 port: 2021 protocol: TCP - url: http://10.5.1.6:2021 + url: http://10.5.30.6:2021 runsOn: - pd-truenas - kind: Service name: dakboard-bridge network: - ip: 10.5.1.6 + ip: 10.5.30.6 port: 5087 protocol: TCP - url: http://10.5.1.6:5087 + url: http://10.5.30.6:5087 runsOn: - pd-truenas - kind: Service name: qui network: - ip: 10.5.1.6 + ip: 10.5.30.6 port: 7476 protocol: TCP - url: http://10.5.1.6:7476 + url: http://10.5.30.6:7476 runsOn: - pd-truenas - kind: Service name: home-assistant network: - ip: 10.5.1.6 + ip: 10.5.30.6 port: 8123 protocol: TCP - url: http://10.5.1.6:8123 + url: http://10.5.30.6:8123 runsOn: - pd-truenas - kind: Service name: kima-hub network: - ip: 10.5.1.6 + ip: 10.5.30.6 port: 3333 protocol: TCP - url: http://10.5.1.6:3333 + url: http://10.5.30.6:3333 runsOn: - pd-truenas - kind: Service name: grafana network: - ip: 10.5.1.6 + ip: 10.5.30.6 port: 3000 protocol: TCP - url: http://10.5.1.6:3000 + url: http://10.5.30.6:3000 runsOn: - pd-truenas - kind: Service name: prometheus network: - ip: 10.5.1.6 + ip: 10.5.30.6 port: 9090 protocol: TCP - url: http://10.5.1.6:9090 + url: http://10.5.30.6:9090 runsOn: - pd-truenas - kind: Service name: meshmonitor network: - ip: 10.5.1.6 + ip: 10.5.30.6 port: 8081 protocol: TCP - url: http://10.5.1.6:8081 + url: http://10.5.30.6:8081 runsOn: - pd-truenas - kind: Service name: tileserver-gl network: - ip: 10.5.1.6 + ip: 10.5.30.6 port: 8082 protocol: TCP - url: http://10.5.1.6:8082 + url: http://10.5.30.6:8082 runsOn: - pd-truenas - kind: Service name: pelican-panel network: - ip: 10.5.1.16 + ip: 10.5.30.7 port: 80 protocol: TCP url: https://panel.paccoco.com @@ -729,7 +729,7 @@ resources: - kind: Service name: wings-node-api network: - ip: 10.5.1.16 + ip: 10.5.30.7 port: 8443 protocol: TCP url: https://node1.paccoco.com diff --git a/docs/architecture/ARCHITECTURE_OVERVIEW.md b/docs/architecture/ARCHITECTURE_OVERVIEW.md index c101992..2569b44 100644 --- a/docs/architecture/ARCHITECTURE_OVERVIEW.md +++ b/docs/architecture/ARCHITECTURE_OVERVIEW.md @@ -6,9 +6,9 @@ Full homelab stack as of 2026-05-09. All 6 expansion phases are complete and dep | Server | OS | IP | Role | |--------|----|----|------| -| **PlausibleDeniability (PD)** | TrueNAS Scale 25.10.2.1 | 10.5.1.6 | Primary Docker host — all compose stacks | -| **Serenity** | Unraid 7.2.4 | 10.5.1.5 | NAS, ARR stack, CPU reranker | -| **N.O.M.A.D.** | Ubuntu 25.10 | 10.5.1.16 | Offline knowledge, game servers, and standalone local services | +| **PlausibleDeniability (PD)** | TrueNAS Scale 25.10.2.1 | 10.5.30.6 | Primary Docker host — all compose stacks | +| **Serenity** | Unraid 7.2.4 | 10.5.30.5 | NAS, ARR stack, CPU reranker | +| **N.O.M.A.D.** | Ubuntu 25.10 | 10.5.30.7 | Offline knowledge, game servers, and standalone local services | | **Rocinante** | (bare metal) | 10.5.1.112 | Heavy Ollama models (RTX 4090) | ## Network @@ -24,7 +24,7 @@ Full homelab stack as of 2026-05-09. All 6 expansion phases are complete and dep - **Homepage** (3300) — dashboard / service index - **Uptime Kuma** (3001) — monitoring and alerting - **Gotify** (8443) — push notifications -- **Gitea** (3002 web / 2222 SSH) — self-hosted git; internal SSH remote uses `ssh://git@10.5.1.6:2222//.git` +- **Gitea** (3002 web / 2222 SSH) — self-hosted git; internal SSH remote uses `ssh://git@10.5.30.6:2222//.git` - **Dockhand** (3230) — Docker management UI ### AI Layer @@ -61,4 +61,4 @@ Full homelab stack as of 2026-05-09. All 6 expansion phases are complete and dep 3. **External Docker networks** — `ai-services` bridges all AI containers across compose files; `ix-databases_shared-databases` provides shared DB access 4. **NFS for Immich** — photo library lives on Serenity's large array; PD mounts on demand (not at boot) 5. **TrueNAS manages Docker** — never use `systemctl restart docker` on PD; use `docker compose` or `docker restart` for individual containers -6. **Credential hygiene** — Gitea pushes from PD/NOMAD use internal SSH (`ssh://git@10.5.1.6:2222//.git`); GitHub remote uses SSH key (`/root/.ssh/id_gitea`) +6. **Credential hygiene** — Gitea pushes from PD/NOMAD use internal SSH (`ssh://git@10.5.30.6:2222//.git`); GitHub remote uses SSH key (`/root/.ssh/id_gitea`) diff --git a/docs/architecture/HARDWARE_INVENTORY.md b/docs/architecture/HARDWARE_INVENTORY.md index 448dc19..6ba0e9f 100644 --- a/docs/architecture/HARDWARE_INVENTORY.md +++ b/docs/architecture/HARDWARE_INVENTORY.md @@ -2,7 +2,7 @@ ## Serenity (NAS / Media Ingestion) - **OS:** Unraid 7.2.4 -- **IP:** 10.5.1.5 | Tailscale: 100.94.87.79 +- **IP:** 10.5.30.5 | Tailscale: 100.94.87.79 - **CPU:** Dual Xeon X5690 (6c/12t each — 24 threads total) - **RAM:** 102GB - **GPU:** None (Matrox IPMI only) @@ -51,7 +51,7 @@ ## N.O.M.A.D. (Offline Knowledge + Game Servers) - **OS:** Ubuntu 25.10 (bare metal) -- **IP:** 10.5.1.16 +- **IP:** 10.5.30.7 - **CPU:** Intel Core i7-4770K @ 3.50GHz (4c/8t, up to 3.9GHz) - **RAM:** 32GB DDR3 1600MHz (4×8GB) - **GPU:** NVIDIA GeForce GTX 1080 (8GB VRAM) diff --git a/docs/architecture/NETWORKING_MODEL.md b/docs/architecture/NETWORKING_MODEL.md index 7217f38..04eb5ce 100644 --- a/docs/architecture/NETWORKING_MODEL.md +++ b/docs/architecture/NETWORKING_MODEL.md @@ -12,9 +12,9 @@ - **Main LAN:** 10.5.1.x (Trusted VLAN) - **Management VLAN:** 10.5.0.x - **Other VLANs:** Cameras, Guest, IoT / Old IoT -- **Serenity IP:** 10.5.1.5 -- **N.O.M.A.D. IP:** 10.5.1.16 -- **PlausibleDeniability:** 10.5.1.6 (static on LAN) +- **Serenity IP:** 10.5.30.5 +- **N.O.M.A.D. IP:** 10.5.30.7 +- **PlausibleDeniability:** 10.5.30.6 (static on LAN) ## Remote Access - **Pangolin VPS + Newt:** Services exposed as subdomains via reverse proxy. Newt agents run on PD and N.O.M.A.D. diff --git a/docs/architecture/SERVICES_DIRECTORY.md b/docs/architecture/SERVICES_DIRECTORY.md index bd49f05..51bdb9e 100644 --- a/docs/architecture/SERVICES_DIRECTORY.md +++ b/docs/architecture/SERVICES_DIRECTORY.md @@ -20,7 +20,7 @@ All homelab services, their hosts, ports, and current status. | Dockhand | 3230 | http://pd:3230 | ✅ Active | | Uptime Kuma | 3001 | http://pd:3001 | ✅ Active | | Gotify | 8443 | https://gotify.paccoco.com | ✅ Active | -| Gitea | 3002 / 2222 | http://pd:3002 / http://10.5.1.6:3002 (SSH: `ssh://git@10.5.1.6:2222//.git`) | ✅ Active | +| Gitea | 3002 / 2222 | http://pd:3002 / http://10.5.30.6:3002 (SSH: `ssh://git@10.5.30.6:2222//.git`) | ✅ Active | | Shlink | 8087 | http://pd:8087 | ✅ Active | | Shlink Web Client | 8088 | http://pd:8088 | ✅ Active | | RackPeek | 8283 | http://pd:8283 | ✅ Active | @@ -33,12 +33,12 @@ All homelab services, their hosts, ports, and current status. | LiteLLM | PD | 4000 | ✅ Active | | OpenWebUI | PD | 8282 | ✅ Active | | Qdrant | PD | 6333 (HTTP) / 6334 (gRPC) | ✅ Active | -| Whisper (faster-whisper, CPU) | N.O.M.A.D. (10.5.1.16) | 8786 | ✅ Active | +| Whisper (faster-whisper, CPU) | N.O.M.A.D. (10.5.30.7) | 8786 | ✅ Active | | Whisper (faster-whisper, CUDA large-v3) | Rocinante (10.5.1.112) | 8787 | ⚠️ Unreachable from N.O.M.A.D. during 2026-05-21 Doris validation | | SearXNG | PD | 8888 | ✅ Active | -| Ollama — light tier | PD (10.5.1.6) | 11434 | ✅ Active | +| Ollama — light tier | PD (10.5.30.6) | 11434 | ✅ Active | | Ollama — heavy tier | Rocinante (10.5.1.112) | 11434 | ⚠️ Unreachable from N.O.M.A.D. during 2026-05-21 Doris validation | -| Reranker (TEI) | Serenity (10.5.1.5) | 9787 | ✅ Active | +| Reranker (TEI) | Serenity (10.5.30.5) | 9787 | ✅ Active | OpenClaw is no longer treated as an active PD service in the operator inventory. Only helper-artifact references remain under the school-intake planning docs. @@ -87,7 +87,7 @@ OpenClaw is no longer treated as an active PD service in the operator inventory. | Service | Host | Port | Status | |---------|------|------|--------| -| MeshCore to MQTT relay | N.O.M.A.D. (10.5.1.16) | outbound MQTT only | ✅ Running as Docker container `mctomqtt` (`mctomqtt:latest`); config bind-mounted from `/etc/mctomqtt` | +| MeshCore to MQTT relay | N.O.M.A.D. (10.5.30.7) | outbound MQTT only | ✅ Running as Docker container `mctomqtt` (`mctomqtt:latest`); config bind-mounted from `/etc/mctomqtt` | ## Mapping / Geo (PD) @@ -95,11 +95,11 @@ OpenClaw is no longer treated as an active PD service in the operator inventory. |---------|------|--------| | TileServer GL | 8082 | ✅ Active | -## Game Servers (N.O.M.A.D. — 10.5.1.16) +## Game Servers (N.O.M.A.D. — 10.5.30.7) | Service | Port | URL | Status | Notes | |---------|------|-----|--------|-------| -| Pelican Panel | 80 / 443 | https://panel.paccoco.com | ✅ Active | For local health/probe use `http://10.5.1.16:8080`; local TLS path is self-signed and `panel.paccoco.com` resolves to `127.0.0.1` on N.O.M.A.D. | +| Pelican Panel | 80 / 443 | https://panel.paccoco.com | ✅ Active | For local health/probe use `http://10.5.30.7:8080`; local TLS path is self-signed and `panel.paccoco.com` resolves to `127.0.0.1` on N.O.M.A.D. | | Wings Node API | 8443 / 443 | https://node1.paccoco.com | ✅ Active | `401` from the public endpoint is expected and means the daemon is reachable/auth-protected | | Minecraft Server 25565 | 25565/tcp+udp | managed via Pelican | ✅ Active | Wings-managed Java server | | Minecraft Server 25566 | 25566/tcp+udp | managed via Pelican | ✅ Active | Wings-managed Java/NeoForge server | diff --git a/docs/operations/PD_BACKUP_DEPLOYMENT.md b/docs/operations/PD_BACKUP_DEPLOYMENT.md index 0d27737..5020df1 100644 --- a/docs/operations/PD_BACKUP_DEPLOYMENT.md +++ b/docs/operations/PD_BACKUP_DEPLOYMENT.md @@ -28,7 +28,7 @@ Deploy the PD → Serenity backup runner from the real compose path on PD: ## Suggested `.env` values to review ```dotenv -SERENITY_BACKUP_HOST=root@10.5.1.5 +SERENITY_BACKUP_HOST=root@10.5.30.5 SERENITY_BACKUP_ROOT=/mnt/user/backups/plausible-deniability PD_APPDATA_ROOT=/mnt/docker-ssd/docker/appdata PD_DATABASES_ROOT=/mnt/docker-ssd/docker/databases @@ -79,7 +79,7 @@ Quarterly restore verification: ```bash tail -100 /mnt/tank/docker/backups/pd-backups.log ls -lh /mnt/tank/docker/backups/db-dumps -ssh root@10.5.1.5 'find /mnt/user/backups/plausible-deniability -maxdepth 2 -type d | sort | head -40' +ssh root@10.5.30.5 'find /mnt/user/backups/plausible-deniability -maxdepth 2 -type d | sort | head -40' ``` ## Restore verification diff --git a/docs/operations/SECRETS_MANAGEMENT.md b/docs/operations/SECRETS_MANAGEMENT.md index 28940a4..61c7a67 100644 --- a/docs/operations/SECRETS_MANAGEMENT.md +++ b/docs/operations/SECRETS_MANAGEMENT.md @@ -63,7 +63,7 @@ All `.env` files live alongside their `docker-compose.yaml`: | | | |---|---| | **Gitea repo** | `https://gitea.paccoco.com/fizzlepoof/homelab-secrets` (private) | -| **SSH remote** | `ssh://git@10.5.1.6:2222/fizzlepoof/homelab-secrets.git` | +| **SSH remote** | `ssh://git@10.5.30.6:2222/fizzlepoof/homelab-secrets.git` | | **Local path on PD** | `/mnt/docker-ssd/docker/secrets/` | | **Symmetric key location** | `/mnt/docker-ssd/docker/secrets/.git-crypt-secrets.key` (if copied there) or retrieve from password manager | | **Key backup** | Password manager + `C:\Users\Fizzlepoof\Downloads\.git-crypt-secrets.key` | @@ -150,7 +150,7 @@ sudo cp /root/bin/git-crypt /mnt/docker-ssd/bin/git-crypt ```bash # Install git-crypt first (see above), then: -git clone ssh://git@10.5.1.6:2222/fizzlepoof/homelab-secrets.git /mnt/docker-ssd/docker/secrets +git clone ssh://git@10.5.30.6:2222/fizzlepoof/homelab-secrets.git /mnt/docker-ssd/docker/secrets cd /mnt/docker-ssd/docker/secrets git-crypt unlock /path/to/.git-crypt-secrets.key ``` @@ -166,7 +166,7 @@ On a fresh PD after reinstalling TrueNAS: 1. **Install git-crypt** (Option 1 above — docker is available immediately after TrueNAS install) 2. **Restore secrets repo:** ```bash - git clone ssh://git@10.5.1.6:2222/fizzlepoof/homelab-secrets.git /mnt/docker-ssd/docker/secrets + git clone ssh://git@10.5.30.6:2222/fizzlepoof/homelab-secrets.git /mnt/docker-ssd/docker/secrets cd /mnt/docker-ssd/docker/secrets git-crypt unlock /path/to/.git-crypt-secrets.key ``` @@ -178,7 +178,7 @@ On a fresh PD after reinstalling TrueNAS: ``` 4. **Clone the main stacks repo:** ```bash - git clone ssh://git@10.5.1.6:2222/fizzlepoof/truenas-stacks.git /mnt/docker-ssd/docker/compose + git clone ssh://git@10.5.30.6:2222/fizzlepoof/truenas-stacks.git /mnt/docker-ssd/docker/compose ``` 5. **Redeploy stacks** per `DEPLOYMENT_CHECKLIST.md` 6. **After any future live `.env` edit**, immediately run: diff --git a/docs/operations/STACK_STANDARDS.md b/docs/operations/STACK_STANDARDS.md index 7ff38ae..a9028bb 100644 --- a/docs/operations/STACK_STANDARDS.md +++ b/docs/operations/STACK_STANDARDS.md @@ -7,8 +7,8 @@ NOMAD exception: standalone live deployments belong under `/opt/` rathe Remotes: - GitHub: `git@github.com:Paccoco/truenas-stacks.git` -- Gitea web: `http://10.5.1.6:3002/fizzlepoof/truenas-stacks` -- Gitea SSH: `ssh://git@10.5.1.6:2222/fizzlepoof/truenas-stacks.git` +- Gitea web: `http://10.5.30.6:3002/fizzlepoof/truenas-stacks` +- Gitea SSH: `ssh://git@10.5.30.6:2222/fizzlepoof/truenas-stacks.git` Use the internal Gitea SSH remote from PD/NOMAD. Port `2222` is SSH, not HTTP. @@ -144,7 +144,7 @@ After changing a healthcheck, must `down` then `up` (not just restart). cd /mnt/docker-ssd/docker/compose git add . git commit -m "description" -git push ssh://git@10.5.1.6:2222/fizzlepoof/truenas-stacks.git main +git push ssh://git@10.5.30.6:2222/fizzlepoof/truenas-stacks.git main ``` All `.env` files are gitignored. Only compose files, `.env.example`, and init scripts are committed. diff --git a/docs/planning/DEPLOY_KIMA_HUB.md b/docs/planning/DEPLOY_KIMA_HUB.md index ab8612f..5d57110 100644 --- a/docs/planning/DEPLOY_KIMA_HUB.md +++ b/docs/planning/DEPLOY_KIMA_HUB.md @@ -94,7 +94,7 @@ sudo docker logs --tail 60 kima ## Post-deploy checks -1. Open `http://10.5.1.6:3333` +1. Open `http://10.5.30.6:3333` 2. Confirm the app starts and initial setup loads 3. Confirm `/music` is visible to Kima 4. Confirm Kima creates and uses `/data` persistence correctly @@ -103,7 +103,7 @@ sudo docker logs --tail 60 kima Verified on 2026-05-15: - container `kima` running and healthy -- `http://10.5.1.6:3333/api/health` returned status OK +- `http://10.5.30.6:3333/api/health` returned status OK - live mount narrowed to `/mnt/unraid/data/media/music:/music` ## Optional integrations / secrets diff --git a/docs/planning/DEPLOY_PIHOLE.md b/docs/planning/DEPLOY_PIHOLE.md index a1ed955..1605573 100644 --- a/docs/planning/DEPLOY_PIHOLE.md +++ b/docs/planning/DEPLOY_PIHOLE.md @@ -1,23 +1,25 @@ # Pi-hole Deployment Plan -**Status:** PD primary and NOMAD backup are live and working; DNS clients are pointed at VIP `10.5.1.53`. RPi4 remains optional future BACKUP2 work. +**Status:** Historical Trusted-VLAN design. PD primary is still valid, but after NOMAD moved to Servers (`10.5.30.7`) its participation in the old Trusted-side VIP `10.5.30.53` was disabled to restore correct routing. Do not reuse this document for NOMAD as-is until a same-subnet HA redesign exists. **Architecture:** 3-node HA cluster with Keepalived VIP, Unbound recursive DNS, Nebula Sync +> **Warning:** The examples below describe the original Trusted-side plan where NOMAD was `10.5.30.7` and shared VIP `10.5.30.53/24`. They are retained as historical deployment notes, not the current NOMAD runtime truth. + --- ## Architecture Overview ``` [All LAN clients] - ↓ DNS: 10.5.1.53 (Virtual IP — floats to whichever node is MASTER) + ↓ DNS: 10.5.30.53 (Virtual IP — floats to whichever node is MASTER) ↓ ┌─────┴──────────────────────────────────┐ │ Keepalived VIP │ - │ 10.5.1.53/24 │ + │ 10.5.30.53/24 │ └──────┬──────────────┬──────────────────┘ │ │ │ PD (MASTER) N.O.M.A.D. (BACKUP1) RPi4 (BACKUP2) - 10.5.1.6 10.5.1.16 10.5.1.X + 10.5.30.6 10.5.30.7 10.5.1.X priority 100 priority 90 priority 80 Pi-hole :53 Pi-hole :53 Pi-hole :53 Unbound :5335 Unbound :5335 Unbound :5335 @@ -28,11 +30,11 @@ (recursive — no third-party upstream) ``` -- **Keepalived** floats the VIP `10.5.1.53` to whichever node is alive with the highest priority +- **Keepalived** floats the VIP `10.5.30.53` to whichever node is alive with the highest priority - **Unbound** on each node acts as the local recursive resolver (replaces Cloudflare/Quad9) - **Pi-hole** on each node forwards upstream queries to its local Unbound (`127.0.0.1#5335`) - **Nebula Sync** (running on PD) syncs all Pi-hole configs from primary → replicas daily -- Current live replica target is NOMAD at `10.5.1.16:8954` +- Current live replica target is NOMAD at `10.5.30.7:8954` All nodes use `network_mode: host` for Pi-hole, Unbound, and Keepalived — this is required for Keepalived to manipulate the VIP and for Pi-hole to bind cleanly to port 53. @@ -40,7 +42,7 @@ All nodes use `network_mode: host` for Pi-hole, Unbound, and Keepalived — this ## Pre-Deploy: Network Planning -1. **Pick a free static IP for the VIP** — `10.5.1.53` is recommended (memorable for DNS). Reserve it in UniFi so DHCP never assigns it. +1. **Pick a free static IP for the VIP** — `10.5.30.53` is recommended (memorable for DNS). Reserve it in UniFi so DHCP never assigns it. 2. **Find the LAN interface name** on each node: ```bash # PD / RPi4: @@ -171,7 +173,7 @@ services: volumes: - ./keepalived.conf:/container/environment/01-custom/keepalived.conf environment: - KEEPALIVED_VIRTUAL_IPS: "#PYTHON2BASH:['10.5.1.53/24']" + KEEPALIVED_VIRTUAL_IPS: "#PYTHON2BASH:['10.5.30.53/24']" ``` ### keepalived.conf (PD — MASTER) @@ -195,7 +197,7 @@ vrrp_instance VI_DNS { } virtual_ipaddress { - 10.5.1.53/24 + 10.5.30.53/24 } } ``` @@ -323,7 +325,7 @@ vrrp_instance VI_DNS { } virtual_ipaddress { - 10.5.1.53/24 + 10.5.30.53/24 } } ``` @@ -409,7 +411,7 @@ vrrp_instance VI_DNS { } virtual_ipaddress { - 10.5.1.53/24 + 10.5.30.53/24 } } ``` @@ -434,7 +436,7 @@ sudo systemctl start keepalived restart: unless-stopped environment: PRIMARY: "http://127.0.0.1:8953|${PIHOLE_PASSWORD}" - REPLICAS: "http://10.5.1.16:8954|${PIHOLE_PASSWORD_NOMAD},http://10.5.1.X:8955|${PIHOLE_PASSWORD_RPI}" + REPLICAS: "http://10.5.30.7:8954|${PIHOLE_PASSWORD_NOMAD},http://10.5.1.X:8955|${PIHOLE_PASSWORD_RPI}" RUN_GRAVITY: "true" FULL_SYNC: "false" CRON: "0 3 * * *" @@ -461,7 +463,7 @@ sudo systemctl start keepalived Point **only the VIP** as DNS: 1. UniFi Network → Settings → Networks → **LAN** → DHCP Name Server: **Manual** -2. **DNS Server 1:** `10.5.1.53` (VIP — floats between nodes) +2. **DNS Server 1:** `10.5.30.53` (VIP — floats between nodes) 3. **DNS Server 2:** *(leave blank or set a public fallback like `1.1.1.1` only if you want internet DNS as last resort)* 4. Save and renew DHCP leases @@ -472,9 +474,9 @@ Point **only the VIP** as DNS: ## Current live endpoints -- **PD admin UI:** `http://10.5.1.6:8953/admin/` -- **NOMAD admin UI:** `http://10.5.1.16:8954/admin/` -- **VIP for DNS clients:** `10.5.1.53` +- **PD admin UI:** `http://10.5.30.6:8953/admin/` +- **NOMAD admin UI:** `http://10.5.30.7:8954/admin/` +- **VIP for DNS clients:** `10.5.30.53` ## Post-Deploy @@ -482,11 +484,11 @@ Point **only the VIP** as DNS: ```bash # Check which node holds the VIP -ip addr show | grep 10.5.1.53 # Run on each node — only MASTER shows it +ip addr show | grep 10.5.30.53 # Run on each node — only MASTER shows it # Test DNS through VIP -nslookup google.com 10.5.1.53 -nslookup doubleclick.net 10.5.1.53 # Should return 0.0.0.0 (blocked) +nslookup google.com 10.5.30.53 +nslookup doubleclick.net 10.5.30.53 # Should return 0.0.0.0 (blocked) ``` ### Test failover @@ -495,7 +497,7 @@ nslookup doubleclick.net 10.5.1.53 # Should return 0.0.0.0 (blocked) # On PD, bring down keepalived sudo docker stop keepalived # Wait ~3 seconds, then check from another machine: -nslookup google.com 10.5.1.53 # Should still work — N.O.M.A.D. took over VIP +nslookup google.com 10.5.30.53 # Should still work — N.O.M.A.D. took over VIP # Restore sudo docker start keepalived ``` @@ -516,8 +518,8 @@ Add in Pi-hole (primary) → Local DNS → DNS Records: | Domain | IP | |--------|----| -| pd.lan | 10.5.1.6 | -| nomad.lan | 10.5.1.16 | +| pd.lan | 10.5.30.6 | +| nomad.lan | 10.5.30.7 | | rocinante.lan | 10.5.1.112 | | rpi4.lan | 10.5.1.X | @@ -527,8 +529,8 @@ Nebula Sync will propagate these to all replicas. | Subdomain | Target | |-----------|--------| -| `pihole.paccoco.com` | `http://10.5.1.6:8953` (primary) | -| `pihole2.paccoco.com` | `http://10.5.1.16:8954` (N.O.M.A.D.) | +| `pihole.paccoco.com` | `http://10.5.30.6:8953` (primary) | +| `pihole2.paccoco.com` | `http://10.5.30.7:8954` (N.O.M.A.D.) | Use `/admin/` on the public hostname for the Pi-hole admin UI path. diff --git a/docs/planning/N8N_WORKFLOW_IDEAS.md b/docs/planning/N8N_WORKFLOW_IDEAS.md index 076cf05..141f3ae 100644 --- a/docs/planning/N8N_WORKFLOW_IDEAS.md +++ b/docs/planning/N8N_WORKFLOW_IDEAS.md @@ -88,7 +88,7 @@ Generated: 2026-05-09 ### 12 — Pi-hole Stats Digest **Trigger:** Schedule (daily at 8 AM) **What it does:** -1. Query Pi-hole API on PD (`http://10.5.1.6:8953/api/stats/summary`) +1. Query Pi-hole API on PD (`http://10.5.30.6:8953/api/stats/summary`) 2. Query secondary Pi-hole on Serenity 3. LiteLLM summarizes notable blocked domains and compares primary vs secondary query load 4. Push digest to Gotify (Pi-hole app) with 24h block count, top blocked domains, and any unusual spikes @@ -136,7 +136,7 @@ Generated: 2026-05-09 ### 16 — NFS Mount Watchdog + Auto-Heal **Trigger:** Schedule (every 5 min) **What it does:** -1. SSH into PD (10.5.1.6) and probe each expected NFS mount path with `mountpoint -q`: +1. SSH into PD (10.5.30.6) and probe each expected NFS mount path with `mountpoint -q`: - `/mnt/unraid/data/media` (Plex, Audiobookshelf) - `/mnt/unraid/data/photos` (Immich) - Any other mounts defined in the mount script @@ -153,7 +153,7 @@ Generated: 2026-05-09 - Escalation: priority 9, list which paths are still unavailable **Implementation notes:** -- Uses n8n SSH node connecting to `10.5.1.6` with an SSH credential (key-based auth from n8n container) +- Uses n8n SSH node connecting to `10.5.30.6` with an SSH credential (key-based auth from n8n container) - SSH node runs a single compound command: `mountpoint -q /mnt/unraid/data/media && echo MEDIA_OK || echo MEDIA_MISSING` etc. - Parse output in a Code node to determine which mounts failed - Use separate SSH nodes for: (1) running the mount script, (2) restarting containers — keeps the flow readable diff --git a/docs/planning/TODO.md b/docs/planning/TODO.md index 4c57a37..42da049 100644 --- a/docs/planning/TODO.md +++ b/docs/planning/TODO.md @@ -5,7 +5,7 @@ - [x] Regenerate N.O.M.A.D. Newt secret (was exposed in chat) — completed 2026-05-15 ## Infrastructure -- [x] Deploy Pi-hole HA on PD + N.O.M.A.D. with VIP `10.5.1.53` — completed 2026-05-15; see docs/planning/DEPLOY_PIHOLE.md +- [x] Deploy Pi-hole HA on PD + N.O.M.A.D. with VIP `10.5.30.53` — completed 2026-05-15; historical Trusted-VLAN design, later superseded after NOMAD moved to Servers (`10.5.30.7`) and its Trusted-side Keepalived role was disabled pending a same-subnet HA redesign; see docs/planning/DEPLOY_PIHOLE.md - PD primary live at `/mnt/docker-ssd/docker/compose/pihole` - NOMAD backup live at `/opt/pihole-nomad` - RPi4 remains optional future BACKUP2 work if you want a third node later diff --git a/docs/reference/HOMELAB_AI_CONTEXT.md b/docs/reference/HOMELAB_AI_CONTEXT.md index eaeac8d..44554c9 100644 --- a/docs/reference/HOMELAB_AI_CONTEXT.md +++ b/docs/reference/HOMELAB_AI_CONTEXT.md @@ -34,9 +34,9 @@ See the individual docs in this repo for detailed information: ### Three Servers | Server | Role | OS | IP | |--------|------|----|----| -| Serenity | NAS / Media ingestion | Unraid 7.2.4 | 10.5.1.5 | -| PlausibleDeniability | Primary Docker host | TrueNAS Scale 25.10.2.1 | 10.5.1.6 | -| N.O.M.A.D. | Offline knowledge + game servers | Ubuntu 25.10 | 10.5.1.16 | +| Serenity | NAS / Media ingestion | Unraid 7.2.4 | 10.5.30.5 | +| PlausibleDeniability | Primary Docker host | TrueNAS Scale 25.10.2.1 | 10.5.30.6 | +| N.O.M.A.D. | Offline knowledge + game servers | Ubuntu 25.10 | 10.5.30.7 | ### Deployment Pattern (PD) ```bash diff --git a/docs/servers/NOMAD.md b/docs/servers/NOMAD.md index b045f08..a9ba8da 100644 --- a/docs/servers/NOMAD.md +++ b/docs/servers/NOMAD.md @@ -4,7 +4,7 @@ Offline survival/knowledge server and game server host. ## Specs - **OS:** Ubuntu 25.10 (bare metal) -- **IP:** 10.5.1.16 +- **IP:** 10.5.30.7 - **CPU:** Intel Core i7-4770K @ 3.50GHz (4c/8t) - **RAM:** 32GB DDR3 1600MHz - **GPU:** NVIDIA GeForce GTX 1080 (8GB VRAM) @@ -309,7 +309,7 @@ Removing them should not be treated as a Project N.O.M.A.D. change or a Wings/Pe - `panel.paccoco.com` → `127.0.0.1` in `/etc/hosts` for NAT loopback - nginx on 80 and 443 (self-signed cert) for local Wings → Panel - Wings uses `--ignore-certificate-errors` for self-signed cert -- For local operator-safe Pelican health checks, use `http://10.5.1.16:8080` instead of the self-signed TLS path; it redirects cleanly to `/home` without tripping certificate validation. +- For local operator-safe Pelican health checks, use `http://10.5.30.7:8080` instead of the self-signed TLS path; it redirects cleanly to `/home` without tripping certificate validation. - Pangolin resource `node1.paccoco.com` has auth disabled ## Known Quirks diff --git a/docs/servers/PLAUSIBLEDENABILITY.md b/docs/servers/PLAUSIBLEDENABILITY.md index 3a26a36..eddc2f9 100644 --- a/docs/servers/PLAUSIBLEDENABILITY.md +++ b/docs/servers/PLAUSIBLEDENABILITY.md @@ -33,7 +33,7 @@ RTX 2080 Ti used by: - immich-ml (CUDA face recognition) - Ollama light-tier inference for the main AI stack and local Honcho backend -> **Note:** Whisper moved to N.O.M.A.D. (10.5.1.16:8786) as CPU inference — avoids VRAM conflict with Ollama. See [NOMAD.md](NOMAD.md). +> **Note:** Whisper moved to N.O.M.A.D. (10.5.30.7:8786) as CPU inference — avoids VRAM conflict with Ollama. See [NOMAD.md](NOMAD.md). Recommended models for 11GB VRAM: | Model | VRAM | Best for | @@ -45,8 +45,8 @@ Recommended models for 11GB VRAM: | `phi4` | ~8GB | Analytical tasks | Current light-tier notes: -- PD Ollama is the active light-tier endpoint for LiteLLM on `http://10.5.1.6:11434` -- Local Honcho currently points at PD Ollama over LAN (`http://10.5.1.6:11434/v1`) instead of using a separate local model host +- PD Ollama is the active light-tier endpoint for LiteLLM on `http://10.5.30.6:11434` +- Local Honcho currently points at PD Ollama over LAN (`http://10.5.30.6:11434/v1`) instead of using a separate local model host - Current Honcho routing split keeps embeddings on `nomic-embed-text:latest`, cheap/default paths on `qwen2.5:7b`, and deeper high/max paths on `qwen2.5:14b` ## Remote Access diff --git a/docs/servers/SERENITY.md b/docs/servers/SERENITY.md index f379724..940dfb1 100644 --- a/docs/servers/SERENITY.md +++ b/docs/servers/SERENITY.md @@ -6,7 +6,7 @@ NAS, media ingestion, and CPU-based AI inference. | Component | Details | |-----------|---------| -| **IP (LAN)** | 10.5.1.5 | +| **IP (LAN)** | 10.5.30.5 | | **IP (Tailscale)** | 100.94.87.79 | | **OS** | Unraid 7.2.4 | | **CPU** | Dual Intel Xeon X5690 (6c/12t each — 24 threads total) | @@ -44,7 +44,7 @@ Text Embeddings Inference reranker for OpenWebUI RAG pipeline. | **Port** | `9787` | | **Volume** | `/mnt/user/appdata/reranker` | | **LiteLLM model name** | `reranker` | -| **API base** | `http://10.5.1.5:9787` | +| **API base** | `http://10.5.30.5:9787` | > Moved from PlausibleDeniability (port 8787) to Serenity (port 9787) on 2026-05-09. > CPU-only inference is acceptable for RAG reranking workloads. diff --git a/documents/.env.example b/documents/.env.example index 43edd2d..eb5afe7 100644 --- a/documents/.env.example +++ b/documents/.env.example @@ -18,7 +18,7 @@ KARAKEEP_DB_WAL_MODE=true KARAKEEP_LOG_LEVEL=notice # Optional local AI later: -# KARAKEEP_OLLAMA_BASE_URL=http://10.5.1.6:11434 +# KARAKEEP_OLLAMA_BASE_URL=http://10.5.30.6:11434 # KARAKEEP_INFERENCE_TEXT_MODEL=llama3.2 # KARAKEEP_INFERENCE_ENABLE_AUTO_TAGGING=true # KARAKEEP_INFERENCE_ENABLE_AUTO_SUMMARIZATION=false diff --git a/home/doris-dashboard/README.md b/home/doris-dashboard/README.md index cf0de8a..4e06c1d 100644 --- a/home/doris-dashboard/README.md +++ b/home/doris-dashboard/README.md @@ -28,7 +28,7 @@ The dashboard turns that into one opinionated, local-only page that Doris can tu - **Repo source:** `/home/fizzlepoof/repos/truenas-stacks/home/doris-dashboard` - **Live runtime:** `/opt/doris-dashboard` - **Generated page:** `/opt/doris-dashboard/public/index.html` -- **LAN URL:** `http://10.5.1.16:8787/` +- **LAN URL:** `http://10.5.30.7:8787/` - **Exposure rule:** local-only unless John explicitly asks otherwise ## What it shows @@ -143,7 +143,7 @@ This dashboard is local-only on NOMAD unless John explicitly asks to expose it. The dashboard is exposed on NOMAD's LAN IP via systemd: ```text -http://10.5.1.16:8787/ +http://10.5.30.7:8787/ ``` Service file: @@ -185,7 +185,7 @@ The generated page still lives at: And the LAN service remains: ```text -http://10.5.1.16:8787/ +http://10.5.30.7:8787/ ``` Verification commands: diff --git a/home/doris-dashboard/bin/generate_dashboard.py b/home/doris-dashboard/bin/generate_dashboard.py index d8ebc10..f279138 100755 --- a/home/doris-dashboard/bin/generate_dashboard.py +++ b/home/doris-dashboard/bin/generate_dashboard.py @@ -39,16 +39,16 @@ SERVICE_DIRECTORY = [ 'items': [ {'name': 'Doris Dashboard', 'url': 'index.html', 'health_url': 'http://127.0.0.1:8787/', 'purpose': 'Morning briefing, alerts, and operator homepage.', 'exposure': 'LAN', 'state': 'ok', 'host': 'NOMAD', 'port': '8787'}, {'name': 'Services Directory', 'url': 'services.html', 'health_url': 'http://127.0.0.1:8787/services.html', 'purpose': 'Front door into the rest of the stack.', 'exposure': 'LAN', 'state': 'ok', 'host': 'NOMAD', 'port': '8787'}, - {'name': 'Doris Kitchen Imports', 'url': 'http://10.5.1.16:8092/imports', 'health_url': 'http://10.5.1.16:8092/imports', 'purpose': 'Flag bad KitchenOwl recipe imports for Doris to repair later.', 'exposure': 'LAN', 'state': 'ok', 'host': 'NOMAD', 'port': '8092'}, - {'name': 'Homepage', 'url': 'http://10.5.1.6:3300', 'health_url': 'http://10.5.1.6:3300', 'purpose': 'Legacy PD portal and bookmarks page for the broader stack.', 'exposure': 'LAN', 'state': 'ok', 'host': 'PD', 'port': '3300'}, + {'name': 'Doris Kitchen Imports', 'url': 'http://10.5.30.7:8092/imports', 'health_url': 'http://10.5.30.7:8092/imports', 'purpose': 'Flag bad KitchenOwl recipe imports for Doris to repair later.', 'exposure': 'LAN', 'state': 'ok', 'host': 'NOMAD', 'port': '8092'}, + {'name': 'Homepage', 'url': 'http://10.5.30.6:3300', 'health_url': 'http://10.5.30.6:3300', 'purpose': 'Legacy PD portal and bookmarks page for the broader stack.', 'exposure': 'LAN', 'state': 'ok', 'host': 'PD', 'port': '3300'}, ], }, { 'group': 'Shared Datastores', 'items': [ - {'name': 'PostgreSQL', 'url': '', 'purpose': 'Primary shared Postgres backing Paperless, n8n, and other stateful stacks.', 'exposure': 'PRIVATE', 'state': 'ok', 'host': 'PD', 'port': '5432/tcp', 'tcp_host': '10.5.1.6', 'tcp_port': 5432, 'badge': 'Healthy'}, - {'name': 'MariaDB', 'url': '', 'purpose': 'Shared MariaDB service for stacks that still need MySQL-compatible storage.', 'exposure': 'PRIVATE', 'state': 'ok', 'host': 'PD', 'port': '3306/tcp', 'tcp_host': '10.5.1.6', 'tcp_port': 3306, 'badge': 'Healthy'}, - {'name': 'Redis', 'url': '', 'purpose': 'Shared cache and queue backend used across the homelab.', 'exposure': 'PRIVATE', 'state': 'ok', 'host': 'PD', 'port': '6379/tcp', 'tcp_host': '10.5.1.6', 'tcp_port': 6379, 'badge': 'Healthy'}, + {'name': 'PostgreSQL', 'url': '', 'purpose': 'Primary shared Postgres backing Paperless, n8n, and other stateful stacks.', 'exposure': 'PRIVATE', 'state': 'ok', 'host': 'PD', 'port': '5432/tcp', 'tcp_host': '10.5.30.6', 'tcp_port': 5432, 'badge': 'Healthy'}, + {'name': 'MariaDB', 'url': '', 'purpose': 'Shared MariaDB service for stacks that still need MySQL-compatible storage.', 'exposure': 'PRIVATE', 'state': 'ok', 'host': 'PD', 'port': '3306/tcp', 'tcp_host': '10.5.30.6', 'tcp_port': 3306, 'badge': 'Healthy'}, + {'name': 'Redis', 'url': '', 'purpose': 'Shared cache and queue backend used across the homelab.', 'exposure': 'PRIVATE', 'state': 'ok', 'host': 'PD', 'port': '6379/tcp', 'tcp_host': '10.5.30.6', 'tcp_port': 6379, 'badge': 'Healthy'}, ], }, { @@ -57,41 +57,41 @@ SERVICE_DIRECTORY = [ {'name': 'DoneTick', 'url': 'https://donetick.paccoco.com', 'health_url': 'https://donetick.paccoco.com', 'purpose': 'Chores, recurring work, and household task coordination.', 'exposure': 'PUBLIC', 'state': 'ok', 'host': 'PD', 'port': '2021'}, {'name': 'Paperless-NGX', 'url': 'https://paperless.paccoco.com', 'health_url': 'https://paperless.paccoco.com', 'purpose': 'Document archive, OCR intake, and household paperwork triage.', 'exposure': 'PUBLIC', 'state': 'ok', 'host': 'PD', 'port': '8083'}, {'name': 'n8n', 'url': 'https://n8n.paccoco.com', 'health_url': 'https://n8n.paccoco.com', 'purpose': 'Automation bus for webhooks, alerts, and cross-service glue.', 'exposure': 'PUBLIC', 'state': 'ok', 'host': 'PD', 'port': '5678'}, - {'name': 'Immich', 'url': 'http://10.5.1.6:2283', 'health_url': 'http://10.5.1.6:2283', 'purpose': 'Photo backup, search, and household media timeline.', 'exposure': 'LAN', 'state': 'ok', 'host': 'PD', 'port': '2283'}, - {'name': 'Karakeep', 'url': 'http://10.5.1.6:3100', 'health_url': 'http://10.5.1.6:3100/signin', 'purpose': 'Self-hosted bookmark capture and later-read archive.', 'exposure': 'LAN', 'state': 'ok', 'host': 'PD', 'port': '3100'}, - {'name': 'KitchenOwl', 'url': 'http://10.5.1.6:8086', 'health_url': 'http://10.5.1.6:8086', 'purpose': 'Shared shopping lists and meal planning workflows.', 'exposure': 'LAN', 'state': 'ok', 'host': 'PD', 'port': '8086'}, - {'name': 'Qui', 'url': 'http://10.5.1.6:7476', 'health_url': 'http://10.5.1.6:7476', 'purpose': 'Household utility app and quick operator tooling surface.', 'exposure': 'LAN', 'state': 'ok', 'host': 'PD', 'port': '7476'}, - {'name': 'Dakboard Bridge', 'url': '', 'health_url': 'http://10.5.1.6:5087', 'purpose': 'Authenticated bridge feeding Dakboard or display-side workflows.', 'exposure': 'PRIVATE', 'state': 'ok', 'host': 'PD', 'port': '5087'}, + {'name': 'Immich', 'url': 'http://10.5.30.6:2283', 'health_url': 'http://10.5.30.6:2283', 'purpose': 'Photo backup, search, and household media timeline.', 'exposure': 'LAN', 'state': 'ok', 'host': 'PD', 'port': '2283'}, + {'name': 'Karakeep', 'url': 'http://10.5.30.6:3100', 'health_url': 'http://10.5.30.6:3100/signin', 'purpose': 'Self-hosted bookmark capture and later-read archive.', 'exposure': 'LAN', 'state': 'ok', 'host': 'PD', 'port': '3100'}, + {'name': 'KitchenOwl', 'url': 'http://10.5.30.6:8086', 'health_url': 'http://10.5.30.6:8086', 'purpose': 'Shared shopping lists and meal planning workflows.', 'exposure': 'LAN', 'state': 'ok', 'host': 'PD', 'port': '8086'}, + {'name': 'Qui', 'url': 'http://10.5.30.6:7476', 'health_url': 'http://10.5.30.6:7476', 'purpose': 'Household utility app and quick operator tooling surface.', 'exposure': 'LAN', 'state': 'ok', 'host': 'PD', 'port': '7476'}, + {'name': 'Dakboard Bridge', 'url': '', 'health_url': 'http://10.5.30.6:5087', 'purpose': 'Authenticated bridge feeding Dakboard or display-side workflows.', 'exposure': 'PRIVATE', 'state': 'ok', 'host': 'PD', 'port': '5087'}, {'name': 'Doris Schoolhouse', 'url': 'https://schoolhouse.paccoco.com', 'health_url': 'https://schoolhouse.paccoco.com', 'purpose': 'Class workflows, uploads, recordings, and school intake.', 'exposure': 'PUBLIC', 'state': 'ok', 'host': 'NOMAD', 'port': '8091'}, - {'name': 'Doris Kitchen', 'url': 'http://10.5.1.16:8092', 'health_url': 'http://10.5.1.16:8092', 'purpose': 'Meal planning, grocery workflows, and kitchen-side operator tasks.', 'exposure': 'LAN', 'state': 'ok', 'host': 'NOMAD', 'port': '8092'}, + {'name': 'Doris Kitchen', 'url': 'http://10.5.30.7:8092', 'health_url': 'http://10.5.30.7:8092', 'purpose': 'Meal planning, grocery workflows, and kitchen-side operator tasks.', 'exposure': 'LAN', 'state': 'ok', 'host': 'NOMAD', 'port': '8092'}, ], }, { 'group': 'AI / Search', 'items': [ - {'name': 'LiteLLM', 'url': 'http://10.5.1.6:4000', 'health_url': 'http://10.5.1.6:4000/v1/models', 'purpose': 'Shared model gateway for automations, Schoolhouse, and local tools.', 'exposure': 'LAN', 'state': 'attention', 'host': 'PD', 'port': '4000'}, - {'name': 'OpenWebUI', 'url': 'https://openwebui.paccoco.com', 'health_url': 'http://10.5.1.6:8282', 'purpose': 'Chat UI for the lab models, docs, and retrieval workflows.', 'exposure': 'PUBLIC', 'state': 'ok', 'host': 'PD', 'port': '8282'}, - {'name': 'Qdrant', 'url': 'http://10.5.1.6:6333/dashboard', 'health_url': 'http://10.5.1.6:6333/healthz', 'purpose': 'Vector store for Project NOMAD and local retrieval systems.', 'exposure': 'LAN', 'state': 'ok', 'host': 'PD', 'port': '6333/6334'}, - {'name': 'Whisper (CPU)', 'url': 'http://10.5.1.16:8786/docs', 'health_url': 'http://10.5.1.16:8786/health', 'purpose': 'Fast local CPU transcription endpoint on NOMAD.', 'exposure': 'LAN', 'state': 'ok', 'host': 'NOMAD', 'port': '8786'}, + {'name': 'LiteLLM', 'url': 'http://10.5.30.6:4000', 'health_url': 'http://10.5.30.6:4000/v1/models', 'purpose': 'Shared model gateway for automations, Schoolhouse, and local tools.', 'exposure': 'LAN', 'state': 'attention', 'host': 'PD', 'port': '4000'}, + {'name': 'OpenWebUI', 'url': 'https://openwebui.paccoco.com', 'health_url': 'http://10.5.30.6:8282', 'purpose': 'Chat UI for the lab models, docs, and retrieval workflows.', 'exposure': 'PUBLIC', 'state': 'ok', 'host': 'PD', 'port': '8282'}, + {'name': 'Qdrant', 'url': 'http://10.5.30.6:6333/dashboard', 'health_url': 'http://10.5.30.6:6333/healthz', 'purpose': 'Vector store for Project NOMAD and local retrieval systems.', 'exposure': 'LAN', 'state': 'ok', 'host': 'PD', 'port': '6333/6334'}, + {'name': 'Whisper (CPU)', 'url': 'http://10.5.30.7:8786/docs', 'health_url': 'http://10.5.30.7:8786/health', 'purpose': 'Fast local CPU transcription endpoint on NOMAD.', 'exposure': 'LAN', 'state': 'ok', 'host': 'NOMAD', 'port': '8786'}, {'name': 'Whisper (CUDA)', 'url': 'http://10.5.1.112:8787/docs', 'health_url': 'http://10.5.1.112:8787/health', 'purpose': 'Heavy GPU transcription endpoint on Rocinante.', 'exposure': 'LAN', 'state': 'ok', 'host': 'ROCINANTE', 'port': '8787'}, - {'name': 'SearXNG', 'url': 'http://10.5.1.6:8888', 'health_url': 'http://10.5.1.6:8888', 'purpose': 'Private metasearch box for research and agent-side browsing.', 'exposure': 'LAN', 'state': 'ok', 'host': 'PD', 'port': '8888'}, - {'name': 'Ollama — light tier', 'url': 'http://10.5.1.6:11434/api/tags', 'health_url': 'http://10.5.1.6:11434/api/tags', 'purpose': 'PD light-tier Ollama runtime backing cheaper/default model paths.', 'exposure': 'LAN', 'state': 'ok', 'host': 'PD', 'port': '11434'}, + {'name': 'SearXNG', 'url': 'http://10.5.30.6:8888', 'health_url': 'http://10.5.30.6:8888', 'purpose': 'Private metasearch box for research and agent-side browsing.', 'exposure': 'LAN', 'state': 'ok', 'host': 'PD', 'port': '8888'}, + {'name': 'Ollama — light tier', 'url': 'http://10.5.30.6:11434/api/tags', 'health_url': 'http://10.5.30.6:11434/api/tags', 'purpose': 'PD light-tier Ollama runtime backing cheaper/default model paths.', 'exposure': 'LAN', 'state': 'ok', 'host': 'PD', 'port': '11434'}, {'name': 'Ollama — heavy tier', 'url': 'http://10.5.1.112:11434/api/tags', 'health_url': 'http://10.5.1.112:11434/api/tags', 'purpose': 'Rocinante heavy-tier Ollama runtime for larger model requests.', 'exposure': 'LAN', 'state': 'ok', 'host': 'ROCINANTE', 'port': '11434'}, - {'name': 'Reranker (TEI)', 'url': 'http://10.5.1.5:9787', 'health_url': 'http://10.5.1.5:9787', 'purpose': 'Serenity-hosted reranker used by retrieval flows.', 'exposure': 'LAN', 'state': 'ok', 'host': 'SERENITY', 'port': '9787'}, + {'name': 'Reranker (TEI)', 'url': 'http://10.5.30.5:9787', 'health_url': 'http://10.5.30.5:9787', 'purpose': 'Serenity-hosted reranker used by retrieval flows.', 'exposure': 'LAN', 'state': 'ok', 'host': 'SERENITY', 'port': '9787'}, ], }, { 'group': 'Homelab / Control Plane', 'items': [ {'name': 'Grafana', 'url': 'https://grafana.paccoco.com', 'health_url': 'https://grafana.paccoco.com', 'purpose': 'Metrics, dashboards, and alert drill-down.', 'exposure': 'PUBLIC', 'state': 'ok', 'host': 'PD', 'port': '3000'}, - {'name': 'Prometheus', 'url': 'http://10.5.1.6:9090', 'health_url': 'http://10.5.1.6:9090/-/healthy', 'purpose': 'Metrics scrape and alert evaluation backend.', 'exposure': 'LAN', 'state': 'ok', 'host': 'PD', 'port': '9090'}, + {'name': 'Prometheus', 'url': 'http://10.5.30.6:9090', 'health_url': 'http://10.5.30.6:9090/-/healthy', 'purpose': 'Metrics scrape and alert evaluation backend.', 'exposure': 'LAN', 'state': 'ok', 'host': 'PD', 'port': '9090'}, {'name': 'Gitea', 'url': 'https://gitea.paccoco.com', 'health_url': 'https://gitea.paccoco.com', 'purpose': 'Repos, issues, and source control.', 'exposure': 'PUBLIC', 'state': 'ok', 'host': 'PD', 'port': '3002 / 2222'}, - {'name': 'Dockhand', 'url': 'http://10.5.1.6:3230', 'health_url': 'http://10.5.1.6:3230', 'purpose': 'Container housekeeping and image/update operations.', 'exposure': 'LAN', 'state': 'ok', 'host': 'PD', 'port': '3230'}, - {'name': 'Uptime Kuma', 'url': 'http://10.5.1.6:3001', 'health_url': 'http://10.5.1.6:3001', 'purpose': 'Availability checks and service-watch status board.', 'exposure': 'LAN', 'state': 'ok', 'host': 'PD', 'port': '3001'}, + {'name': 'Dockhand', 'url': 'http://10.5.30.6:3230', 'health_url': 'http://10.5.30.6:3230', 'purpose': 'Container housekeeping and image/update operations.', 'exposure': 'LAN', 'state': 'ok', 'host': 'PD', 'port': '3230'}, + {'name': 'Uptime Kuma', 'url': 'http://10.5.30.6:3001', 'health_url': 'http://10.5.30.6:3001', 'purpose': 'Availability checks and service-watch status board.', 'exposure': 'LAN', 'state': 'ok', 'host': 'PD', 'port': '3001'}, {'name': 'Gotify', 'url': 'https://gotify.paccoco.com', 'health_url': 'https://gotify.paccoco.com', 'purpose': 'Push notification hub for alerts and workflow fan-out.', 'exposure': 'PUBLIC', 'state': 'ok', 'host': 'PD', 'port': '8443'}, - {'name': 'RackPeek', 'url': 'http://10.5.1.6:8283', 'health_url': 'http://10.5.1.6:8283', 'purpose': 'Rack/device inventory and operator reference panel.', 'exposure': 'LAN', 'state': 'ok', 'host': 'PD', 'port': '8283'}, - {'name': 'Shlink', 'url': 'http://10.5.1.6:8087', 'health_url': 'http://10.5.1.6:8087/rest/health', 'purpose': 'Short-link API and redirect backend.', 'exposure': 'LAN', 'state': 'ok', 'host': 'PD', 'port': '8087'}, - {'name': 'Shlink Web Client', 'url': 'http://10.5.1.6:8088', 'health_url': 'http://10.5.1.6:8088', 'purpose': 'Short-link management UI for the Shlink backend.', 'exposure': 'LAN', 'state': 'ok', 'host': 'PD', 'port': '8088'}, + {'name': 'RackPeek', 'url': 'http://10.5.30.6:8283', 'health_url': 'http://10.5.30.6:8283', 'purpose': 'Rack/device inventory and operator reference panel.', 'exposure': 'LAN', 'state': 'ok', 'host': 'PD', 'port': '8283'}, + {'name': 'Shlink', 'url': 'http://10.5.30.6:8087', 'health_url': 'http://10.5.30.6:8087/rest/health', 'purpose': 'Short-link API and redirect backend.', 'exposure': 'LAN', 'state': 'ok', 'host': 'PD', 'port': '8087'}, + {'name': 'Shlink Web Client', 'url': 'http://10.5.30.6:8088', 'health_url': 'http://10.5.30.6:8088', 'purpose': 'Short-link management UI for the Shlink backend.', 'exposure': 'LAN', 'state': 'ok', 'host': 'PD', 'port': '8088'}, {'name': 'Loki', 'url': '', 'purpose': 'Log aggregation backend for Grafana and the monitoring stack.', 'exposure': 'PRIVATE', 'state': 'ok', 'host': 'PD', 'port': 'internal', 'probe': False, 'badge': 'Manual', 'detail': 'documented internal monitoring component; verify from Grafana/compose when needed'}, {'name': 'Promtail', 'url': '', 'purpose': 'Log shipping agent feeding Loki from the homelab nodes.', 'exposure': 'PRIVATE', 'state': 'ok', 'host': 'PD', 'port': 'internal', 'probe': False, 'badge': 'Manual', 'detail': 'documented internal monitoring component; verify from Grafana/compose when needed'}, {'name': 'cAdvisor', 'url': '', 'purpose': 'Container metrics exporter feeding Prometheus.', 'exposure': 'PRIVATE', 'state': 'ok', 'host': 'PD', 'port': 'internal', 'probe': False, 'badge': 'Manual', 'detail': 'documented internal monitoring component; verify from Prometheus targets when needed'}, @@ -100,47 +100,47 @@ SERVICE_DIRECTORY = [ { 'group': 'Media / Library', 'items': [ - {'name': 'Plex', 'url': 'http://10.5.1.6:32400/web/index.html', 'health_url': 'http://10.5.1.6:32400/identity', 'purpose': 'Primary media server, playback hub, and GPU-backed transcoding surface.', 'exposure': 'LAN', 'state': 'ok', 'host': 'PD', 'port': '32400'}, - {'name': 'Tautulli', 'url': 'http://10.5.1.6:8181', 'health_url': 'http://10.5.1.6:8181', 'purpose': 'Plex activity analytics, user history, and watch-session drill-down.', 'exposure': 'LAN', 'state': 'ok', 'host': 'PD', 'port': '8181'}, - {'name': 'Audiobookshelf', 'url': 'http://10.5.1.6:13358', 'health_url': 'http://10.5.1.6:13358', 'purpose': 'Audiobook and podcast library with self-hosted playback and progress sync.', 'exposure': 'LAN', 'state': 'ok', 'host': 'PD', 'port': '13358'}, - {'name': 'Calibre-Web', 'url': 'http://10.5.1.6:8183', 'health_url': 'http://10.5.1.6:8183', 'purpose': 'Ebook library browser and metadata management front-end.', 'exposure': 'LAN', 'state': 'ok', 'host': 'PD', 'port': '8183'}, - {'name': 'Seerr (Overseerr)', 'url': 'http://10.5.1.6:5055', 'health_url': 'http://10.5.1.6:5055', 'purpose': 'Request intake for media additions and library demand tracking.', 'exposure': 'LAN', 'state': 'ok', 'host': 'PD', 'port': '5055'}, + {'name': 'Plex', 'url': 'http://10.5.30.6:32400/web/index.html', 'health_url': 'http://10.5.30.6:32400/identity', 'purpose': 'Primary media server, playback hub, and GPU-backed transcoding surface.', 'exposure': 'LAN', 'state': 'ok', 'host': 'PD', 'port': '32400'}, + {'name': 'Tautulli', 'url': 'http://10.5.30.6:8181', 'health_url': 'http://10.5.30.6:8181', 'purpose': 'Plex activity analytics, user history, and watch-session drill-down.', 'exposure': 'LAN', 'state': 'ok', 'host': 'PD', 'port': '8181'}, + {'name': 'Audiobookshelf', 'url': 'http://10.5.30.6:13358', 'health_url': 'http://10.5.30.6:13358', 'purpose': 'Audiobook and podcast library with self-hosted playback and progress sync.', 'exposure': 'LAN', 'state': 'ok', 'host': 'PD', 'port': '13358'}, + {'name': 'Calibre-Web', 'url': 'http://10.5.30.6:8183', 'health_url': 'http://10.5.30.6:8183', 'purpose': 'Ebook library browser and metadata management front-end.', 'exposure': 'LAN', 'state': 'ok', 'host': 'PD', 'port': '8183'}, + {'name': 'Seerr (Overseerr)', 'url': 'http://10.5.30.6:5055', 'health_url': 'http://10.5.30.6:5055', 'purpose': 'Request intake for media additions and library demand tracking.', 'exposure': 'LAN', 'state': 'ok', 'host': 'PD', 'port': '5055'}, ], }, { 'group': 'Smart Home / Access', 'items': [ {'name': 'Home Assistant', 'url': '', 'purpose': 'Smart-home control hub documented on PD, but Doris does not yet have a stable operator-safe route.', 'exposure': 'PRIVATE', 'state': 'attention', 'host': 'PD', 'port': '8123', 'probe': False, 'badge': 'Manual', 'detail': 'documented active on PD; local port currently refuses from NOMAD, so keep this manual until routed cleanly'}, - {'name': 'Kima Hub', 'url': 'http://10.5.1.6:3333', 'health_url': 'http://10.5.1.6:3333', 'purpose': 'Custom IoT gateway and smart-home integration surface.', 'exposure': 'LAN', 'state': 'ok', 'host': 'PD', 'port': '3333'}, - {'name': 'Pi-hole VIP', 'url': 'http://10.5.1.53/ui/', 'health_url': 'http://10.5.1.53/admin/', 'purpose': 'Floating DNS/admin surface for the HA Pi-hole pair your clients should target.', 'exposure': 'LAN', 'state': 'ok', 'host': 'VIP', 'port': '53 / 80'}, - {'name': 'Pi-hole PD', 'url': 'http://10.5.1.6/ui/', 'health_url': 'http://10.5.1.6/admin/', 'purpose': 'Primary PD Pi-hole node behind the floating DNS VIP.', 'exposure': 'LAN', 'state': 'ok', 'host': 'PD', 'port': '53 / 80'}, - {'name': 'Pi-hole NOMAD', 'url': 'https://10.5.1.16/login', 'purpose': 'NOMAD backup Pi-hole replica for the HA DNS pair.', 'exposure': 'LAN', 'state': 'attention', 'host': 'NOMAD', 'port': '53 / 80/443', 'probe': False, 'badge': 'Manual', 'detail': 'documented active; local admin currently presents a self-signed TLS flow from NOMAD so Doris leaves this manual'}, - {'name': 'Authelia', 'url': 'http://10.5.1.6:9091', 'health_url': 'http://10.5.1.6:9091', 'purpose': 'Identity and auth gate backing protected public routes.', 'exposure': 'LAN', 'state': 'ok', 'host': 'PD', 'port': '9091'}, + {'name': 'Kima Hub', 'url': 'http://10.5.30.6:3333', 'health_url': 'http://10.5.30.6:3333', 'purpose': 'Custom IoT gateway and smart-home integration surface.', 'exposure': 'LAN', 'state': 'ok', 'host': 'PD', 'port': '3333'}, + {'name': 'Pi-hole VIP', 'url': 'http://10.5.30.53/ui/', 'health_url': 'http://10.5.30.53/admin/', 'purpose': 'Floating DNS/admin surface for the HA Pi-hole pair your clients should target.', 'exposure': 'LAN', 'state': 'ok', 'host': 'VIP', 'port': '53 / 80'}, + {'name': 'Pi-hole PD', 'url': 'http://10.5.30.6/ui/', 'health_url': 'http://10.5.30.6/admin/', 'purpose': 'Primary PD Pi-hole node behind the floating DNS VIP.', 'exposure': 'LAN', 'state': 'ok', 'host': 'PD', 'port': '53 / 80'}, + {'name': 'Pi-hole NOMAD', 'url': 'https://10.5.30.7/login', 'purpose': 'NOMAD backup Pi-hole replica for the HA DNS pair.', 'exposure': 'LAN', 'state': 'attention', 'host': 'NOMAD', 'port': '53 / 80/443', 'probe': False, 'badge': 'Manual', 'detail': 'documented active; local admin currently presents a self-signed TLS flow from NOMAD so Doris leaves this manual'}, + {'name': 'Authelia', 'url': 'http://10.5.30.6:9091', 'health_url': 'http://10.5.30.6:9091', 'purpose': 'Identity and auth gate backing protected public routes.', 'exposure': 'LAN', 'state': 'ok', 'host': 'PD', 'port': '9091'}, ], }, { 'group': 'Mapping / Radio / Mesh', 'items': [ - {'name': 'MeshMonitor', 'url': 'https://meshmonitor.paccoco.com', 'health_url': 'http://10.5.1.6:8081', 'purpose': 'Meshtastic monitoring, node views, and map/radio telemetry.', 'exposure': 'PUBLIC', 'state': 'ok', 'host': 'PD', 'port': '8081'}, - {'name': 'TileServer GL', 'url': 'http://10.5.1.6:8082', 'health_url': 'http://10.5.1.6:8082', 'purpose': 'Map tile serving for local geo and radio/mapping workflows.', 'exposure': 'LAN', 'state': 'ok', 'host': 'PD', 'port': '8082'}, + {'name': 'MeshMonitor', 'url': 'https://meshmonitor.paccoco.com', 'health_url': 'http://10.5.30.6:8081', 'purpose': 'Meshtastic monitoring, node views, and map/radio telemetry.', 'exposure': 'PUBLIC', 'state': 'ok', 'host': 'PD', 'port': '8081'}, + {'name': 'TileServer GL', 'url': 'http://10.5.30.6:8082', 'health_url': 'http://10.5.30.6:8082', 'purpose': 'Map tile serving for local geo and radio/mapping workflows.', 'exposure': 'LAN', 'state': 'ok', 'host': 'PD', 'port': '8082'}, {'name': 'MeshCore to MQTT relay', 'url': '', 'purpose': 'Containerized relay moving MeshCore data into MQTT for the radio stack.', 'exposure': 'PRIVATE', 'state': 'ok', 'host': 'NOMAD', 'port': 'outbound MQTT only', 'probe': False, 'badge': 'Manual', 'detail': 'documented running as mctomqtt; verify via container/runtime logs when needed'}, ], }, { 'group': 'Game Servers', 'items': [ - {'name': 'Pelican Panel', 'url': 'https://panel.paccoco.com', 'health_url': 'http://10.5.1.16:8080', 'purpose': 'Game-server management panel for the NOMAD Wings stack.', 'exposure': 'PUBLIC', 'state': 'ok', 'host': 'NOMAD', 'port': '443'}, + {'name': 'Pelican Panel', 'url': 'https://panel.paccoco.com', 'health_url': 'http://10.5.30.7:8080', 'purpose': 'Game-server management panel for the NOMAD Wings stack.', 'exposure': 'PUBLIC', 'state': 'ok', 'host': 'NOMAD', 'port': '443'}, {'name': 'Wings Node API', 'url': 'https://node1.paccoco.com', 'health_url': 'https://node1.paccoco.com', 'purpose': 'Public-facing Wings node endpoint; 401 means the daemon is reachable and auth-protected.', 'exposure': 'PUBLIC', 'state': 'ok', 'host': 'NOMAD', 'port': '8443 / 443'}, - {'name': 'Minecraft Server 25565', 'url': '', 'purpose': 'First Wings-managed Java server on NOMAD.', 'exposure': 'PUBLIC', 'state': 'ok', 'host': 'NOMAD', 'port': '25565/tcp+udp', 'tcp_host': '10.5.1.16', 'tcp_port': 25565, 'badge': 'Healthy'}, - {'name': 'Minecraft Server 25566', 'url': '', 'purpose': 'Second Wings-managed Java/NeoForge server on NOMAD.', 'exposure': 'PUBLIC', 'state': 'ok', 'host': 'NOMAD', 'port': '25566/tcp+udp', 'tcp_host': '10.5.1.16', 'tcp_port': 25566, 'badge': 'Healthy'}, + {'name': 'Minecraft Server 25565', 'url': '', 'purpose': 'First Wings-managed Java server on NOMAD.', 'exposure': 'PUBLIC', 'state': 'ok', 'host': 'NOMAD', 'port': '25565/tcp+udp', 'tcp_host': '10.5.30.7', 'tcp_port': 25565, 'badge': 'Healthy'}, + {'name': 'Minecraft Server 25566', 'url': '', 'purpose': 'Second Wings-managed Java/NeoForge server on NOMAD.', 'exposure': 'PUBLIC', 'state': 'ok', 'host': 'NOMAD', 'port': '25566/tcp+udp', 'tcp_host': '10.5.30.7', 'tcp_port': 25566, 'badge': 'Healthy'}, ], }, { 'group': 'Nomad Local Apps', 'items': [ - {'name': 'Dozzle', 'url': 'http://10.5.1.16:9999', 'health_url': 'http://10.5.1.16:9999', 'purpose': 'Live Docker logs on NOMAD without opening a shell.', 'exposure': 'LAN', 'state': 'ok', 'host': 'NOMAD', 'port': '9999'}, - {'name': 'Flatnotes', 'url': 'http://10.5.1.16:8200', 'health_url': 'http://10.5.1.16:8200', 'purpose': 'Quick notes and lightweight knowledge capture.', 'exposure': 'LAN', 'state': 'ok', 'host': 'NOMAD', 'port': '8200'}, - {'name': 'Kiwix', 'url': 'http://10.5.1.16:8090', 'health_url': 'http://10.5.1.16:8090', 'purpose': 'Offline library / knowledge mirror for disconnected use.', 'exposure': 'LAN', 'state': 'ok', 'host': 'NOMAD', 'port': '8090'}, + {'name': 'Dozzle', 'url': 'http://10.5.30.7:9999', 'health_url': 'http://10.5.30.7:9999', 'purpose': 'Live Docker logs on NOMAD without opening a shell.', 'exposure': 'LAN', 'state': 'ok', 'host': 'NOMAD', 'port': '9999'}, + {'name': 'Flatnotes', 'url': 'http://10.5.30.7:8200', 'health_url': 'http://10.5.30.7:8200', 'purpose': 'Quick notes and lightweight knowledge capture.', 'exposure': 'LAN', 'state': 'ok', 'host': 'NOMAD', 'port': '8200'}, + {'name': 'Kiwix', 'url': 'http://10.5.30.7:8090', 'health_url': 'http://10.5.30.7:8090', 'purpose': 'Offline library / knowledge mirror for disconnected use.', 'exposure': 'LAN', 'state': 'ok', 'host': 'NOMAD', 'port': '8090'}, ], }, ] @@ -866,7 +866,7 @@ def inject_dashboard_todos(items:list[dict[str,Any]])->list[dict[str,Any]]: def load_paperless_review()->list[dict[str,Any]]: p=DATA/'paperless_review.json' - remote=os.environ.get('PAPERLESS_TRIAGE_REMOTE_QUEUE','truenas_admin@10.5.1.6:/mnt/docker-ssd/docker/appdata/n8n/paperless-triage/paperless_review.json') + remote=os.environ.get('PAPERLESS_TRIAGE_REMOTE_QUEUE','truenas_admin@10.5.30.6:/mnt/docker-ssd/docker/appdata/n8n/paperless-triage/paperless_review.json') ssh_key=os.environ.get('PAPERLESS_TRIAGE_SSH_KEY','/home/fizzlepoof/.ssh/id_ed25519_andys') if remote: tmp=DATA/'paperless_review.remote.tmp' diff --git a/home/doris-dashboard/docs/baselines/unifi-object-baseline-2026-05-22-024653.json b/home/doris-dashboard/docs/baselines/unifi-object-baseline-2026-05-22-024653.json new file mode 100644 index 0000000..13887b9 --- /dev/null +++ b/home/doris-dashboard/docs/baselines/unifi-object-baseline-2026-05-22-024653.json @@ -0,0 +1,608 @@ +{ + "captured_at": "2026-05-21T21:46:53-05:00", + "site": "default", + "networkconf": [ + { + "setting_preference": "auto", + "purpose": "wan", + "external_id": "d488cbbc-05dd-4bab-9efb-4b58340df685", + "wan_type_v6": "disabled", + "routing_table_id": 201, + "ipv6_wan_delegation_type": "none", + "wan_dhcpv6_pd_size_auto": true, + "firewall_zone_id": "6963d42a1131084f054618e0", + "igmp_proxy_upstream": false, + "mac_override_enabled": false, + "wan_load_balance_type": "failover-only", + "wan_failover_priority": 2, + "wan_ipv6_dns_preference": "auto", + "wan_networkgroup": "WAN", + "wan_dslite_remote_host_auto": false, + "wan_smartq_enabled": false, + "wan_dns_preference": "auto", + "wan_vlan_enabled": false, + "wan_load_balance_weight": 99, + "site_id": "6963c5321131084f05460911", + "name": "Internet 1", + "report_wan_event": false, + "_id": "6963c5831131084f05460929", + "attr_no_delete": true, + "wan_type": "dhcp", + "attr_hidden_id": "WAN" + }, + { + "setting_preference": "manual", + "purpose": "wan", + "wan_dhcp_cos": 0, + "external_id": "c10cd8c4-7f9c-4d12-a7ea-e1a34ab8ead7", + "wan_type_v6": "disabled", + "routing_table_id": 202, + "enabled": true, + "wan_dhcp_options": [], + "ipv6_wan_delegation_type": "none", + "wan_dhcpv6_pd_size_auto": true, + "firewall_zone_id": "6963d42a1131084f054618e0", + "igmp_proxy_upstream": false, + "mac_override_enabled": false, + "wan_load_balance_type": "weighted", + "wan_failover_priority": 1, + "ipv6_setting_preference": "manual", + "wan_ipv6_dns_preference": "auto", + "single_network_lan": "", + "wan_dns2": "9.9.9.9", + "wan_ipv6_dns1": "", + "wan_dns1": "10.5.1.53", + "wan_ipv6_dns2": "", + "wan_networkgroup": "WAN2", + "wan_dslite_remote_host_auto": false, + "wan_provider_capabilities": { + "upload_kilobits_per_second": 2409000, + "download_kilobits_per_second": 2120000 + }, + "wan_ip_aliases": [], + "wan_smartq_enabled": false, + "wan_dns_preference": "manual", + "wan_vlan_enabled": false, + "wan_load_balance_weight": 50, + "site_id": "6963c5321131084f05460911", + "name": "Internet 2", + "report_wan_event": false, + "_id": "6963c5831131084f0546092a", + "wan_type": "dhcp", + "attr_no_delete": true, + "igmp_proxy_for": "none" + }, + { + "setting_preference": "manual", + "dhcpdv6_dns_auto": true, + "ipv6_pd_stop": "::7d1", + "dhcpd_gateway_enabled": false, + "ipv6_client_address_assignment": "slaac", + "network_isolation_enabled": false, + "dhcp_relay_servers": [], + "dhcpd_start": "10.5.0.100", + "dhcpd_unifi_controller": "", + "ipv6_ra_enabled": true, + "domain_name": "localdomain", + "ip_subnet": "10.5.0.1/24", + "ipv6_interface_type": "none", + "dhcpd_wins_2": "", + "dhcpdv6_stop": "::7d1", + "is_nat": true, + "dhcpd_wins_1": "", + "dhcpd_dns_enabled": false, + "internet_access_enabled": true, + "nat_outbound_ip_addresses": [], + "dhcp_relay_enabled": false, + "dhcpd_conflict_checking": true, + "dhcpd_wins_enabled": false, + "dhcpdv6_leasetime": 86400, + "site_id": "6963c5321131084f05460911", + "name": "Management", + "_id": "6963c5831131084f0546092b", + "lte_lan_enabled": true, + "purpose": "corporate", + "dhcpd_leasetime": 86400, + "dhcpguard_enabled": false, + "dhcpd_time_offset_enabled": false, + "external_id": "652279eb-41e5-440d-aad5-231ca9683890", + "ipv6_ra_preferred_lifetime": 14400, + "dhcpd_stop": "10.5.0.199", + "is_smart_subnet_detected": false, + "enabled": true, + "dhcpd_enabled": true, + "dhcpd_wpad_url": "", + "networkgroup": "LAN", + "firewall_zone_id": "6963d42a1131084f054618df", + "dhcpdv6_start": "::2", + "vlan_enabled": false, + "ipv6_setting_preference": "auto", + "ipv6_aliases": [], + "gateway_type": "default", + "ipv6_ra_priority": "high", + "dhcpd_boot_enabled": false, + "ipv6_pd_start": "::2", + "upnp_lan_enabled": false, + "dhcpd_ntp_enabled": false, + "mdns_enabled": true, + "ip_aliases": [], + "attr_no_delete": true, + "attr_hidden_id": "LAN", + "dhcpd_tftp_server": "", + "auto_scale_enabled": false + }, + { + "setting_preference": "manual", + "dhcpd_gateway_enabled": false, + "network_isolation_enabled": false, + "dhcp_relay_servers": [], + "dhcpd_dns_1": "192.168.1.10", + "dhcpd_start": "10.5.1.100", + "dhcpd_unifi_controller": "", + "domain_name": "", + "ip_subnet": "10.5.1.1/24", + "dhcpd_dns_4": "", + "ipv6_interface_type": "none", + "dhcpd_dns_2": "", + "dhcpd_dns_3": "", + "dhcpd_wins_2": "", + "is_nat": true, + "dhcpd_wins_1": "", + "dhcpd_dns_enabled": false, + "internet_access_enabled": true, + "nat_outbound_ip_addresses": [], + "dhcp_relay_enabled": false, + "dhcpd_conflict_checking": true, + "dhcpd_wins_enabled": false, + "name": "Trusted", + "site_id": "6963c5321131084f05460911", + "_id": "6963cb201131084f05461635", + "lte_lan_enabled": true, + "dhcpd_leasetime": 86400, + "purpose": "corporate", + "dhcpd_time_offset_enabled": false, + "dhcpguard_enabled": false, + "external_id": "87b66e27-93a9-4fb7-93d2-3b7bca9e6414", + "enabled": true, + "dhcpd_stop": "10.5.1.199", + "dhcpd_enabled": true, + "vlan": 51, + "dhcpd_wpad_url": "", + "networkgroup": "LAN", + "firewall_zone_id": "6963d42a1131084f054618df", + "vlan_enabled": true, + "ipv6_setting_preference": "manual", + "ipv6_aliases": [], + "gateway_type": "default", + "dhcpd_boot_enabled": false, + "upnp_lan_enabled": false, + "dhcpd_ntp_enabled": false, + "mdns_enabled": true, + "ip_aliases": [], + "dhcpd_tftp_server": "", + "auto_scale_enabled": false + }, + { + "setting_preference": "manual", + "dhcpd_leasetime": 86400, + "purpose": "corporate", + "dhcpd_gateway_enabled": false, + "dhcpd_time_offset_enabled": false, + "dhcpguard_enabled": false, + "network_isolation_enabled": true, + "dhcp_relay_servers": [], + "dhcpd_start": "10.5.10.6", + "dhcpd_unifi_controller": "", + "external_id": "88180bc3-2c34-4f30-9b57-aed15da2a12a", + "enabled": true, + "dhcpd_stop": "10.5.10.254", + "domain_name": "", + "dhcpd_enabled": true, + "ip_subnet": "10.5.10.1/24", + "vlan": 510, + "dhcpd_wpad_url": "", + "ipv6_interface_type": "none", + "networkgroup": "LAN", + "firewall_zone_id": "6963d5a91131084f05461a0d", + "vlan_enabled": true, + "dhcpd_wins_2": "", + "ipv6_setting_preference": "manual", + "dhcpd_wins_1": "", + "dhcpd_dns_enabled": false, + "ipv6_aliases": [], + "internet_access_enabled": true, + "gateway_type": "default", + "nat_outbound_ip_addresses": [], + "dhcpd_boot_enabled": false, + "dhcp_relay_enabled": false, + "dhcpd_conflict_checking": true, + "dhcpd_wins_enabled": false, + "dhcpd_ntp_enabled": false, + "name": "IoT", + "site_id": "6963c5321131084f05460911", + "mdns_enabled": true, + "ip_aliases": [], + "_id": "6963cb461131084f05461642", + "lte_lan_enabled": true, + "dhcpd_tftp_server": "", + "auto_scale_enabled": true + }, + { + "setting_preference": "auto", + "dhcpd_leasetime": 86400, + "purpose": "guest", + "dhcpd_gateway_enabled": false, + "dhcpd_time_offset_enabled": false, + "dhcpguard_enabled": false, + "network_isolation_enabled": false, + "dhcp_relay_servers": [], + "dhcpd_start": "10.5.90.6", + "dhcpd_unifi_controller": "", + "external_id": "2986eea1-bf02-4879-a7b0-7d9f91e45c53", + "enabled": true, + "dhcpd_stop": "10.5.90.254", + "domain_name": "", + "dhcpd_enabled": true, + "ip_subnet": "10.5.90.1/24", + "vlan": 590, + "dhcpd_wpad_url": "", + "ipv6_interface_type": "none", + "networkgroup": "LAN", + "firewall_zone_id": "6963d42a1131084f054618e3", + "vlan_enabled": true, + "dhcpd_wins_2": "", + "ipv6_setting_preference": "manual", + "is_nat": true, + "dhcpd_wins_1": "", + "dhcpd_dns_enabled": false, + "ipv6_aliases": [], + "internet_access_enabled": true, + "gateway_type": "default", + "nat_outbound_ip_addresses": [], + "dhcpd_boot_enabled": false, + "dhcp_relay_enabled": false, + "dhcpd_conflict_checking": true, + "dhcpd_wins_enabled": false, + "upnp_lan_enabled": false, + "dhcpd_ntp_enabled": false, + "name": "Guest", + "site_id": "6963c5321131084f05460911", + "mdns_enabled": true, + "ip_aliases": [], + "_id": "6963cb941131084f054616af", + "lte_lan_enabled": true, + "dhcpd_tftp_server": "", + "auto_scale_enabled": true + }, + { + "setting_preference": "auto", + "dhcpd_leasetime": 86400, + "purpose": "corporate", + "dhcpd_gateway_enabled": false, + "dhcpd_time_offset_enabled": false, + "dhcpguard_enabled": false, + "network_isolation_enabled": false, + "dhcp_relay_servers": [], + "dhcpd_start": "10.5.20.6", + "dhcpd_unifi_controller": "", + "external_id": "98f2aff1-b394-493d-908f-ced4c83bde99", + "enabled": true, + "dhcpd_stop": "10.5.20.254", + "domain_name": "", + "dhcpd_enabled": true, + "ip_subnet": "10.5.20.1/24", + "vlan": 520, + "dhcpd_wpad_url": "", + "ipv6_interface_type": "none", + "networkgroup": "LAN", + "firewall_zone_id": "6963d5a91131084f05461a0d", + "vlan_enabled": true, + "dhcpd_wins_2": "", + "ipv6_setting_preference": "manual", + "is_nat": true, + "dhcpd_wins_1": "", + "dhcpd_dns_enabled": false, + "ipv6_aliases": [], + "internet_access_enabled": true, + "gateway_type": "default", + "nat_outbound_ip_addresses": [], + "dhcpd_boot_enabled": false, + "dhcp_relay_enabled": false, + "dhcpd_conflict_checking": true, + "dhcpd_wins_enabled": false, + "upnp_lan_enabled": false, + "dhcpd_ntp_enabled": false, + "name": "Camera", + "site_id": "6963c5321131084f05460911", + "mdns_enabled": true, + "ip_aliases": [], + "_id": "6963cbbf1131084f054616c1", + "lte_lan_enabled": true, + "dhcpd_tftp_server": "", + "auto_scale_enabled": true + }, + { + "setting_preference": "manual", + "dhcpd_leasetime": 86400, + "purpose": "corporate", + "dhcpd_gateway_enabled": false, + "dhcpd_time_offset_enabled": false, + "dhcpguard_enabled": false, + "network_isolation_enabled": false, + "dhcp_relay_servers": [], + "dhcpd_start": "192.168.1.100", + "dhcpd_unifi_controller": "", + "external_id": "9bd0ee41-3303-4c03-ad81-305248301921", + "enabled": true, + "dhcpd_stop": "192.168.1.199", + "domain_name": "", + "dhcpd_enabled": true, + "ip_subnet": "192.168.1.1/24", + "vlan": 2, + "dhcpd_wpad_url": "", + "ipv6_interface_type": "none", + "networkgroup": "LAN", + "firewall_zone_id": "6963d5a91131084f05461a0d", + "vlan_enabled": true, + "dhcpd_wins_2": "", + "ipv6_setting_preference": "manual", + "is_nat": true, + "dhcpd_wins_1": "", + "dhcpd_dns_enabled": false, + "ipv6_aliases": [], + "internet_access_enabled": true, + "gateway_type": "default", + "nat_outbound_ip_addresses": [], + "dhcpd_boot_enabled": false, + "dhcp_relay_enabled": false, + "dhcpd_conflict_checking": true, + "dhcpd_wins_enabled": false, + "upnp_lan_enabled": false, + "dhcpd_ntp_enabled": false, + "name": "Old IoT", + "site_id": "6963c5321131084f05460911", + "mdns_enabled": true, + "ip_aliases": [], + "_id": "6963e11c1131084f054622f1", + "lte_lan_enabled": true, + "dhcpd_tftp_server": "", + "auto_scale_enabled": false + }, + { + "wireguard_client_configuration_filename": "Unifi2-US-IL-267.conf", + "purpose": "vpn-client", + "wireguard_client_mode": "file", + "external_id": "8673a0e4-4f1b-42d6-9563-e6f6440065de", + "interface_mtu_enabled": false, + "routing_table_id": 178, + "enabled": true, + "vpn_type": "wireguard-client", + "mss_clamp": "auto", + "ip_subnet": "10.2.0.2/32", + "name": "Proton Chicago", + "site_id": "6963c5321131084f05460911", + "wireguard_id": 1, + "firewall_zone_id": "6963d42a1131084f054618e0", + "wireguard_client_configuration_file": "[REDACTED: exported WireGuard client config removed from tracked baseline]", + "_id": "696411c51131084f05465f8d" + }, + { + "setting_preference": "auto", + "wireguard_interface_binding_mode_ip_version": "v4", + "purpose": "remote-user-vpn", + "local_port": 51820, + "x_wireguard_private_key": "REDACTED", + "external_id": "c625964c-8663-4ef0-bf04-53a5466a168c", + "enabled": true, + "wireguard_local_wan_ip": "any", + "vpn_type": "wireguard-server", + "ip_subnet": "192.168.2.1/24", + "wireguard_interface": "wan2", + "name": "One-Click VPN", + "site_id": "6963c5321131084f05460911", + "wireguard_id": 1, + "firewall_zone_id": "6963d42a1131084f054618e2", + "_id": "69a31399822347a1daadae2e" + }, + { + "setting_preference": "auto", + "wireguard_interface_binding_mode_ip_version": "v4", + "purpose": "remote-user-vpn", + "x_wireguard_private_key": "REDACTED", + "dhcpd_start": "192.168.3.2", + "interface_mtu_enabled": false, + "external_id": "2431e2ed-2d1f-48ed-9b2f-3ed5b8021504", + "enabled": true, + "dhcpd_stop": "192.168.3.254", + "vpn_type": "wireguard-server", + "ip_subnet": "192.168.3.1/24", + "wireguard_interface": "wan2", + "wireguard_id": 2, + "firewall_zone_id": "6963d42a1131084f054618e2", + "vpn_client_configuration_remote_ip_override_enabled": false, + "dhcpd_dns_enabled": false, + "local_port": 51821, + "wireguard_local_wan_ip": "any", + "mss_clamp": "auto", + "dhcpd_wins_enabled": false, + "vpn_binding_mode": "any", + "name": "Slate 7", + "site_id": "6963c5321131084f05460911", + "_id": "69dd799da02014d2f4acc4e9", + "mss_clamp_ipv6": "auto" + } + ], + "portconf": [ + { + "setting_preference": "auto", + "port_security_enabled": false, + "stormctrl_ucast_rate": 100, + "egress_rate_limit_kbps_enabled": false, + "stormctrl_mcast_enabled": false, + "lldpmed_notify_enabled": false, + "tagged_vlan_mgmt": "auto", + "multicast_router_networkconf_ids": [], + "stormctrl_bcast_enabled": false, + "port_keepalive_enabled": false, + "stormctrl_mcast_rate": 100, + "native_networkconf_id": "6963c5831131084f0546092b", + "qos_profile": { + "qos_profile_mode": "custom", + "qos_policies": [] + }, + "port_security_mac_address": [], + "dot1x_idle_timeout": 300, + "op_mode": "switch", + "poe_mode": "auto", + "forward": "all", + "stormctrl_ucast_enabled": false, + "stormctrl_bcast_rate": 100, + "isolation": false, + "voice_networkconf_id": "", + "stp_port_mode": true, + "name": "Management", + "site_id": "6963c5321131084f05460911", + "_id": "6963cdbf1131084f0546177c", + "autoneg": true, + "lldpmed_enabled": true, + "dot1x_ctrl": "force_authorized" + }, + { + "setting_preference": "auto", + "port_security_enabled": false, + "stormctrl_ucast_rate": 100, + "egress_rate_limit_kbps_enabled": false, + "stormctrl_mcast_enabled": false, + "lldpmed_notify_enabled": false, + "tagged_vlan_mgmt": "auto", + "multicast_router_networkconf_ids": [], + "stormctrl_bcast_enabled": false, + "port_keepalive_enabled": false, + "excluded_networkconf_ids": [], + "stormctrl_mcast_rate": 100, + "native_networkconf_id": "6963cb201131084f05461635", + "qos_profile": { + "qos_profile_mode": "custom", + "qos_policies": [] + }, + "port_security_mac_address": [], + "dot1x_idle_timeout": 300, + "op_mode": "switch", + "poe_mode": "auto", + "forward": "customize", + "stormctrl_ucast_enabled": false, + "stormctrl_bcast_rate": 100, + "isolation": false, + "voice_networkconf_id": "", + "stp_port_mode": true, + "name": "Trusted", + "site_id": "6963c5321131084f05460911", + "_id": "6963cdfe1131084f0546178d", + "autoneg": true, + "lldpmed_enabled": true, + "dot1x_ctrl": "force_authorized" + }, + { + "setting_preference": "auto", + "port_security_enabled": false, + "stormctrl_ucast_rate": 100, + "egress_rate_limit_kbps_enabled": false, + "stormctrl_mcast_enabled": false, + "lldpmed_notify_enabled": false, + "tagged_vlan_mgmt": "block_all", + "multicast_router_networkconf_ids": [], + "stormctrl_bcast_enabled": false, + "port_keepalive_enabled": false, + "stormctrl_mcast_rate": 100, + "native_networkconf_id": "6963cb461131084f05461642", + "qos_profile": { + "qos_profile_mode": "custom", + "qos_policies": [] + }, + "port_security_mac_address": [], + "dot1x_idle_timeout": 300, + "op_mode": "switch", + "poe_mode": "auto", + "forward": "native", + "stormctrl_ucast_enabled": false, + "stormctrl_bcast_rate": 100, + "isolation": false, + "voice_networkconf_id": "", + "stp_port_mode": true, + "name": "IoT", + "site_id": "6963c5321131084f05460911", + "_id": "6963ce221131084f05461791", + "autoneg": true, + "lldpmed_enabled": true, + "dot1x_ctrl": "force_authorized" + }, + { + "setting_preference": "auto", + "port_security_enabled": false, + "stormctrl_ucast_rate": 100, + "egress_rate_limit_kbps_enabled": false, + "stormctrl_mcast_enabled": false, + "lldpmed_notify_enabled": false, + "tagged_vlan_mgmt": "block_all", + "multicast_router_networkconf_ids": [], + "stormctrl_bcast_enabled": false, + "port_keepalive_enabled": false, + "stormctrl_mcast_rate": 100, + "native_networkconf_id": "6963cbbf1131084f054616c1", + "qos_profile": { + "qos_profile_mode": "custom", + "qos_policies": [] + }, + "port_security_mac_address": [], + "dot1x_idle_timeout": 300, + "op_mode": "switch", + "poe_mode": "auto", + "forward": "native", + "stormctrl_ucast_enabled": false, + "stormctrl_bcast_rate": 100, + "isolation": false, + "voice_networkconf_id": "", + "stp_port_mode": true, + "name": "Camera", + "site_id": "6963c5321131084f05460911", + "_id": "6963ce3b1131084f05461798", + "autoneg": true, + "lldpmed_enabled": true, + "dot1x_ctrl": "force_authorized" + }, + { + "setting_preference": "auto", + "port_security_enabled": false, + "stormctrl_ucast_rate": 100, + "egress_rate_limit_kbps_enabled": false, + "stormctrl_mcast_enabled": false, + "lldpmed_notify_enabled": false, + "tagged_vlan_mgmt": "block_all", + "multicast_router_networkconf_ids": [], + "stormctrl_bcast_enabled": false, + "port_keepalive_enabled": false, + "stormctrl_mcast_rate": 100, + "native_networkconf_id": "6963e11c1131084f054622f1", + "qos_profile": { + "qos_profile_mode": "custom", + "qos_policies": [] + }, + "port_security_mac_address": [], + "dot1x_idle_timeout": 300, + "op_mode": "switch", + "poe_mode": "auto", + "forward": "native", + "stormctrl_ucast_enabled": false, + "stormctrl_bcast_rate": 100, + "isolation": false, + "voice_networkconf_id": "", + "stp_port_mode": true, + "name": "Old IoT", + "site_id": "6963c5321131084f05460911", + "_id": "6963e3511131084f054624cf", + "autoneg": true, + "lldpmed_enabled": true, + "dot1x_ctrl": "force_authorized" + } + ] +} diff --git a/home/doris-dashboard/docs/baselines/unifi-usw-pro-hd-24-port-overrides-pre-servers-apply-2026-05-22.json b/home/doris-dashboard/docs/baselines/unifi-usw-pro-hd-24-port-overrides-pre-servers-apply-2026-05-22.json new file mode 100644 index 0000000..24169f8 --- /dev/null +++ b/home/doris-dashboard/docs/baselines/unifi-usw-pro-hd-24-port-overrides-pre-servers-apply-2026-05-22.json @@ -0,0 +1,109 @@ +{ + "captured_at": "2026-05-22", + "device_id": "698776451131084f059d572b", + "device_mac": "8c:30:66:d0:9f:48", + "device_name": "USW Pro HD 24", + "port_overrides": [ + { + "name": "U7 Pro (Upstairs)", + "port_idx": 1, + "portconf_id": "6963cdbf1131084f0546177c", + "setting_preference": "manual" + }, + { + "autoneg": false, + "egress_rate_limit_kbps_enabled": false, + "excluded_networkconf_ids": [], + "forward": "customize", + "full_duplex": true, + "isolation": false, + "lldpmed_enabled": true, + "multicast_router_mode": "NONE", + "name": "Port 2", + "native_networkconf_id": "6963cb201131084f05461635", + "port_idx": 2, + "port_keepalive_enabled": false, + "port_security_enabled": false, + "port_security_mac_address": [], + "setting_preference": "manual", + "speed": 2500, + "stp_port_mode": true, + "tagged_vlan_mgmt": "auto", + "voice_networkconf_id": "" + }, + { + "name": "FlyingDutchman", + "port_idx": 3, + "portconf_id": "6963cdfe1131084f0546178d", + "setting_preference": "manual" + }, + { + "name": "Port 4", + "port_idx": 4, + "portconf_id": "6963cdbf1131084f0546177c", + "setting_preference": "manual" + }, + { + "autoneg": false, + "egress_rate_limit_kbps_enabled": false, + "excluded_networkconf_ids": [], + "forward": "customize", + "full_duplex": true, + "isolation": false, + "lldpmed_enabled": true, + "multicast_router_mode": "NONE", + "name": "Port 6", + "native_networkconf_id": "6963cb201131084f05461635", + "port_idx": 6, + "port_keepalive_enabled": false, + "port_security_enabled": false, + "port_security_mac_address": [], + "setting_preference": "manual", + "speed": 2500, + "stp_port_mode": true, + "tagged_vlan_mgmt": "auto", + "voice_networkconf_id": "" + }, + { + "autoneg": true, + "egress_rate_limit_kbps_enabled": false, + "forward": "disabled", + "isolation": false, + "lldpmed_enabled": true, + "name": "Port 14", + "native_networkconf_id": "", + "port_idx": 14, + "port_keepalive_enabled": false, + "port_security_enabled": true, + "port_security_mac_address": [], + "setting_preference": "auto", + "stp_port_mode": true, + "tagged_vlan_mgmt": "block_all", + "voice_networkconf_id": "" + }, + { + "name": "Port 22", + "port_idx": 22, + "portconf_id": "6963cdfe1131084f0546178d", + "setting_preference": "manual" + }, + { + "name": "Docker Server", + "port_idx": 23, + "portconf_id": "6963cdfe1131084f0546178d", + "setting_preference": "manual" + }, + { + "name": "Serenity 10G (1)", + "port_idx": 24, + "portconf_id": "6963cdfe1131084f0546178d", + "setting_preference": "manual" + }, + { + "name": "Port 27", + "port_idx": 27, + "portconf_id": "6963cdbf1131084f0546177c", + "setting_preference": "manual" + } + ] +} diff --git a/home/doris-dashboard/docs/legacy-cia-triage-worksheet.md b/home/doris-dashboard/docs/legacy-cia-triage-worksheet.md new file mode 100644 index 0000000..3741478 --- /dev/null +++ b/home/doris-dashboard/docs/legacy-cia-triage-worksheet.md @@ -0,0 +1,151 @@ +# Legacy CIA Via Device Triage Worksheet + +> For Doris: this worksheet exists so tomorrow does not devolve into vibes and guessing. Every device on Legacy CIA gets one label: migrate, quarantine, or kill. + +Goal: classify every remaining device on the legacy CIA Via VLAN/SSID so we can make deliberate decisions during and after the cutover. + +Rules: +- No new devices join Legacy CIA. +- If a device has a clean future home, move it. +- If a device is still useful but cannot be re-homed cleanly, quarantine it. +- If nobody knows what it is or nobody misses it, kill it. + +--- + +## 1. Decision Criteria + +### Migrate +Use `migrate` when all or most are true: +- device still matters +- app/admin control still exists +- owner is known +- function is understood +- a target VLAN/SSID is obvious +- there is a reasonable validation method after moving + +### Quarantine +Use `quarantine` when all or most are true: +- device still provides value +- current owner/function is known enough +- moving it is risky, annoying, or impossible right now +- app/pairing/discovery state is fragile +- acceptable to leave it internet-only or nearly so + +### Kill +Use `kill` when all or most are true: +- nobody knows what it is +- nobody can still administer it +- nobody notices when it is offline +- it duplicates something better +- it exists only because history happened + +## 2. Recommended First-Pass Classification + +### Migrate tomorrow if practical +- Main-Floor ecobee -> IoT VLAN 40 +- Upstairs ecobee -> IoT VLAN 40 +- MyQ -> IoT VLAN 40 +- Samsung FamilyHub -> IoT VLAN 40 +- LG dryer -> IoT VLAN 40 +- doorbell -> Cameras VLAN 60 +- Protect chimes -> Cameras VLAN 60 + +### Migrate later / careful handling +- Google Home Mini -> IoT VLAN 40 after discovery testing +- Chromecast-class devices -> IoT VLAN 40 after discovery testing +- any Google cast/speaker/display weirdness -> probably last batch + +### Quarantine on Legacy CIA +- old bulbs still doing useful work +- old plugs still doing useful work +- retained mystery devices with known household function but bad migration prospects +- any device that still works but has no sane re-pair workflow tonight + +### Likely kill candidates +- unknown stale MACs +- orphaned historical smart-home junk +- dead bulbs/plugs nobody notices +- duplicate or long-gone clients still mentally treated as active + +## 3. Live Worksheet Table + +Fill one row per client discovered on Legacy CIA. + +| Device name | MAC | IP | Vendor | Physical location | What it does | Owner | Current control path | Target lane | Disposition | Why | Test after move/block | Result | +|---|---|---|---|---|---|---|---|---|---|---|---|---| +| Main-Floor ecobee | | | ecobee | main floor | thermostat | household | ecobee app | IoT 40 | migrate | important + controllable | thermostat still controllable | | +| Upstairs ecobee | | | ecobee | upstairs | thermostat | household | ecobee app | IoT 40 | migrate | important + controllable | thermostat still controllable | | +| MyQ | | | Chamberlain/LiftMaster | garage | garage door | household | MyQ app | IoT 40 | migrate | meaningful device | app works after move | | +| Samsung FamilyHub | | | Samsung | kitchen | fridge/display | household | Samsung app/local | IoT 40 | migrate | meaningful appliance | app works after move | | +| LG dryer | | | LG | laundry | dryer telemetry/control | household | LG app | IoT 40 | migrate | low-risk appliance | app works after move | | +| Google Home Mini | | | Google | | voice/cast | household | Google Home app | IoT 40 | migrate later / quarantine | discovery pain | cast/discovery tested | | +| Chromecast / Cast device | | | Google | | casting | household | Google Home/app casting | IoT 40 | migrate later / quarantine | discovery pain | cast still works | | +| Legacy bulb A | | | | | light | | unknown/old app | Legacy CIA | quarantine or kill | depends if anyone misses it | light still works or nobody complains | | +| Legacy plug A | | | | | smart plug | | unknown/old app | Legacy CIA | quarantine or kill | depends if still useful | plug use validated or not | | +| Unknown client 1 | | | | | unknown | unknown | none | none | kill candidate | unidentified | nobody notices after block | | + +## 4. Fast Triage Questions During The Window + +Ask these for each questionable device: +1. What is it? +2. Who cares if it breaks? +3. Can we still control it? +4. Does it need local LAN access or just internet? +5. Is its correct long-term home obvious? +6. Can we test success in under 2 minutes? + +If answers are bad, it does not earn migration tomorrow. + +## 5. Specific Handling Guidance + +### ecobees +- Move if app/control confidence is decent. +- Validate both thermostats immediately after move. +- If one gets weird, revert that one only. + +### MyQ / fridge / dryer +- These are good early IoT wins. +- Minimal emotional attachment, clear owner, clear expected validation. + +### Google / Chromecast ecosystem +- Treat as suspicious until proven civilized. +- Do not write broad allow rules just because casting sulks. +- Prefer leaving these in quarantine over contaminating clean policy. + +### Protect chimes / doorbell +- These are not Legacy CIA end-state residents. +- Move to Cameras/Security as early cleanup. + +### old bulbs / old plugs +- If still useful but irrecoverable, quarantine. +- If their purpose is forgotten, block first, then kill if nobody notices. + +## 6. Quarantine Policy Reminder + +Legacy CIA devices that remain should get: +- DHCP +- DNS +- NTP +- internet outbound if needed +- no Management access +- no Trusted access +- no Servers access by default +- no Cameras access +- no new joins + +## 7. Post-Window Cleanup Queue + +After tomorrow, every leftover Legacy CIA row should get one next action: +- retry migration +- collect pairing/reset docs +- physically inspect location +- disable/block and observe +- remove permanently + +## 8. Success Condition + +The Legacy CIA sheet is successful when: +- every device has a named disposition +- no unknown active clients are left unclassified +- only genuinely hard leftovers remain in quarantine +- you can explain why each remaining device is still there diff --git a/home/doris-dashboard/docs/network-cutover-live-change-log-template.md b/home/doris-dashboard/docs/network-cutover-live-change-log-template.md new file mode 100644 index 0000000..3e17d22 --- /dev/null +++ b/home/doris-dashboard/docs/network-cutover-live-change-log-template.md @@ -0,0 +1,108 @@ +# Network Cutover Live Change Log Template + +Use this during the live window. + +Rule: +- every meaningful change gets a row +- if something breaks, the last row is your first suspect +- do not trust memory + +--- + +## Session header + +Date: +Operator: +Primary control box: Rocinante +Fallback admin path: +Start time: +Stop time: + +In scope tonight: +- Google: yes, but narrow/reversible only +- U6 LR: only if needed +- FlyingDutchman: stay Trusted unless explicitly reclassified +- Server batch: Nomad + Serenity + PD + +Out of scope / defer if ugly: +- +- +- + +--- + +## Live log + +| Time | Change ID | Area | Exact change made | Expected result | Validation performed | Result | Rollback needed? | Notes | +|---|---|---|---|---|---|---|---|---| +| | 001 | baseline | captured screenshots/exports | rollback baseline exists | exports saved | pass/fail | n | | +| | 002 | management | | | | | y/n | | +| | 003 | camera/security | | | | | y/n | | +| | 004 | server-batch | move Nomad to Servers | Nomad on correct subnet and reachable | IP + service check | pass/fail | y/n | | +| | 005 | server-batch | move Serenity to Servers | Serenity on correct subnet and reachable | IP + service check | pass/fail | y/n | | +| | 006 | server-batch | move PD to Servers | PD on correct subnet and reachable | IP + service check | pass/fail | y/n | | +| | 007 | iot | | | | | y/n | | +| | 008 | google-test | | | | | y/n | | +| | 009 | guest/ssid | | | | | y/n | | + +--- + +## Batch validation block: Nomad + Serenity + PD + +After the three-host move wave, record this before touching anything else: + +- Nomad subnet/gateway correct: yes/no +- Serenity subnet/gateway correct: yes/no +- PD subnet/gateway correct: yes/no +- Rocinante can still reach PD: yes/no +- NFS/share relationships recovered: yes/no +- Critical service checks passed: yes/no +- Need immediate rollback: yes/no + +If no to any critical line above: +- stop +- revert last moved host first +- unwind in reverse order + +--- + +## Temporary exceptions added + +| Time | Rule / exception | Why it was added | Narrow enough? | Remove before end? | +|---|---|---|---|---| +| | | | yes/no | yes/no | + +--- + +## Devices intentionally deferred + +| Device | Why deferred | Safe current lane | Follow-up needed | +|---|---|---|---| +| | | | | + +--- + +## End-of-window summary + +Minimum success criteria: +- Management materially cleaner: yes/no +- Camera/Security lane sane: yes/no +- Nomad/Serenity/PD batch completed or deliberately rolled back: yes/no +- Easy IoT wins done: yes/no +- Legacy CIA explicitly quarantine/sunset: yes/no +- No broad panic rules added: yes/no + +What changed successfully: +- +- +- + +What was reverted: +- +- +- + +What remains for later: +- +- +- diff --git a/home/doris-dashboard/docs/network-cutover-master-operator-sheet.md b/home/doris-dashboard/docs/network-cutover-master-operator-sheet.md new file mode 100644 index 0000000..a1ca920 --- /dev/null +++ b/home/doris-dashboard/docs/network-cutover-master-operator-sheet.md @@ -0,0 +1,358 @@ +# Network Cutover Master Operator Sheet + +> For Doris: this is the one-page live operator sheet for tomorrow. Use this instead of bouncing between five docs while tired. + +Goal: execute the UniFi network redesign with minimal improvisation, explicit stop/go gates, per-port actions, per-device dispositions, and fast rollback discipline. + +Architecture: +- Keep the target lanes clear: Management, Trusted, Servers, IoT, Guest, Cameras. +- Keep Legacy CIA alive only as a quarantine/sunset lane. +- Move true service hosts out of Trusted while keeping human endpoints where they belong. +- Move easy named IoT wins now; quarantine ambiguous junk; Google is in scope, but only through narrow, reversible tests. + +Primary references if deeper detail is needed: +- `network-redesign-implementation-runbook.md` +- `network-firewall-rule-order.md` +- `legacy-cia-triage-worksheet.md` +- `unifi-readonly-recon-2026-05-22.md` +- `usw-pro-hd-24-cutover-port-sheet.md` +- `old-iot-tomorrow-disposition-list.md` + +--- + +## 1. Live State Snapshot Already Confirmed + +Networks currently present: +- Management -> `10.5.0.1/24` +- Trusted -> VLAN 51 -> `10.5.1.1/24` +- Old IoT -> VLAN 2 -> `192.168.1.1/24` +- IoT -> VLAN 510 -> `10.5.10.1/24` +- Camera -> VLAN 520 -> `10.5.20.1/24` +- Guest -> VLAN 590 -> `10.5.90.1/24` + +SSIDs currently present: +- `CIA Via` -> Old IoT +- `UNEF's Playhouse` -> Camera +- `Whiskey Neat Fuck Ice` -> Trusted +- `Yer a Wifi Harry` -> Trusted + +Current client counts: +- Management: 2 +- Old IoT: 14 +- Camera: 1 +- Trusted: 14 + +Current hazards: +- two unnamed `espressif` devices are on Management via `UniFi Wireless` on the U7 Pro +- almost all Wi‑Fi clients are on the U7 Pro +- there are no custom firewall rules/groups yet, so tomorrow’s segmentation policy is basically greenfield + +## 2. Tomorrow’s Must-Win Outcomes + +By the time we stop, these should be true: +- Management is materially cleaned up +- Cameras lane exists and is not polluted by junk placement mistakes +- Servers lane exists and core hosts are moved or deliberately staged with validation +- IoT lane exists and easy-value devices are moved +- Legacy CIA is explicitly a quarantine/sunset lane +- no broad panic rules were added to compensate for fatigue + +In-scope if conditions support it: +- Google/cast migration work, but only through narrow, reversible tests +- U6 LR only if needed to stabilize client behavior or complete the design cleanly + +Nice-to-have only if smooth: +- SSID simplification + +## 3. Absolute Rules + +- Keep one stable admin path alive the whole time. +- Rocinante is the live operator station, so do not move it until the rest of the server batch is done or an alternate admin path is proven. +- Change one meaningful thing, then validate. +- Do not bulk-edit live server ports blindly. +- Nomad, Serenity, and PD can be moved as one controlled batch only because their NFS/shared-service coupling is real; pre-stage everything first, then validate the batch immediately. +- Do not try to empty Legacy CIA completely if the window gets noisy. +- Do not move unknown junk into Management, Trusted, or Servers. +- Do not let Google/cast problems bait you into broad allow rules. +- If management reachability degrades, rollback first, think second. + +## 4. Minute-by-Minute Execution Spine + +### T-30 to T-15: prep +- [ ] open this file +- [ ] open UniFi admin +- [ ] open Doris visual artifacts if helpful +- [ ] confirm Rocinante is the live control box +- [ ] confirm fallback route into UniFi in case Rocinante loses the lane mid-change +- [ ] decide whether U6 LR is in scope or explicitly out of scope + +### Minute 0-10: baseline capture +- [ ] screenshot/export networks +- [ ] screenshot/export SSIDs +- [ ] screenshot/export firewall/rule state +- [ ] screenshot/export port profiles +- [ ] screenshot/export switch port assignments +- [ ] screenshot/export AP mappings +- [ ] snapshot Management client list +- [ ] snapshot Old IoT client list + +Stop/go gate: +- [ ] do not proceed until rollback baseline exists + +### Minute 10-20: create/normalize objects only +- [ ] create/confirm Servers network object +- [ ] create/confirm any missing profiles +- [ ] create/confirm staged SSIDs if needed +- [ ] create/confirm staged firewall objects/rules + +Stop/go gate: +- [ ] no clients should have moved yet +- [ ] Trusted admin path still fine + +### Minute 20-35: management cleanup first +- [ ] identify/address the 2 Management `espressif` devices +- [ ] ensure infra devices remain on Management intent +- [ ] remove obvious non-infra junk from Management + +Stop/go gate: +- [ ] UniFi still sees gateway/switch/APs +- [ ] admin client still reaches controller + +### Minute 35-50: security cleanup +- [ ] validate Camera lane +- [ ] move Protect chimes if ready +- [ ] confirm doorbell remains healthy + +### Minute 50-85: core server move wave +- [ ] leave Rocinante in place as the operator station until the main server batch is done +- [ ] keep FlyingDutchman on Trusted unless a later decision explicitly reclassifies it +- [ ] pre-stage the exact three-port sequence: Nomad, Serenity, PD +- [ ] move Nomad, Serenity, and PD in one controlled wave because of NFS/shared-service coupling +- [ ] validate the batch immediately before touching anything else + +### Minute 85-110: easy IoT wins +- [ ] MyQ +- [ ] LG dryer +- [ ] Samsung FamilyHub +- [ ] Main-Floor ecobee +- [ ] Upstairs ecobee + +### Minute 110-125: Legacy CIA quarantine pass +- [ ] mark leftovers as quarantine/defer +- [ ] do not force migration of unknowns +- [ ] apply harsh quarantine posture + +### Minute 125-140: Google test if the window is still stable +- [ ] test one Google/cast device first +- [ ] if ugly, stop and defer + +### Minute 140-155: guest/SSID cleanup +- [ ] validate Guest +- [ ] disable only stale SSIDs that are truly no longer needed + +### Minute 155-180: final validation and stop +- [ ] Trusted ok +- [ ] server services ok +- [ ] moved IoT devices ok +- [ ] security ok +- [ ] Management materially cleaner +- [ ] Legacy CIA now explicitly quarantine + +## 5. Management Offender Sheet + +These two devices are currently on Management and need identification or removal from that lane: + +1. `espressif` +- IP: `10.5.0.123` +- AP: `U7 Pro` +- SSID: `UniFi Wireless` +- Default call: identify first; if it is smart-junk, move/quarantine off Management + +2. `espressif` +- IP: `10.5.0.189` +- AP: `U7 Pro` +- SSID: `UniFi Wireless` +- Default call: identify first; if it is smart-junk, move/quarantine off Management + +Operator note: +- these are almost certainly exactly the sort of device that should not live on your infrastructure lane + +## 6. Exact Core Port Move Sheet + +### Leave Alone Unless Positively Necessary +- USW Pro HD 24 port 1 -> `U7 Pro (Upstairs)` -> profile `Management` +- USW Pro HD 24 port 27 -> likely infra/uplink -> profile `Management` +- USW-24-PoE port 24 -> link up on `Management`, inspect before touching + +### Port Move Order + +1. USW Pro HD 24 port 2 +- Live host: `Rocinante` +- Current profile visibility from API: ambiguous +- Action: inspect in UI first, then move to `Servers` if confirmed +- Validate: + - correct subnet/gateway + - reachable from Trusted + - intended services still work + +2. USW Pro HD 24 port 3 +- Live host: `FlyingDutchman` +- Current profile: `Trusted` +- Action: keep on `Trusted` tomorrow unless a later decision explicitly reclassifies it; this is John and Manndra's gaming PC, not a homelab service host by default +- Validate: + - host still reachable + - services behave as expected +- Safe default: leave it in Trusted + +3. USW Pro HD 24 port 22 +- Live host: `Nomad` +- Current profile: `Trusted` +- Action: move as part of the controlled three-host server batch +- Validate: + - expected address + - services reachable from Trusted + +4. USW Pro HD 24 port 24 +- Live host: `Serenity` +- Current profile: `Trusted` +- Action: move as part of the controlled three-host server batch +- Validate: + - expected address + - intended services reachable + +5. USW Pro HD 24 port 23 +- Live host: `PlausibleDeniability` +- Current profile: `Trusted` +- Action: move as part of the controlled three-host server batch after Nomad and Serenity are ready +- Validate: + - expected address + - SSH/admin path still works + - critical homelab services still work +- Warning: + - because Nomad, Serenity, and PD are coupled by active shares/services, treat them as one planned wave rather than three unrelated experiments + - Rocinante should stay as the control box until that wave is complete + +## 7. Old IoT Device Disposition Sheet + +### Migrate Now +1. `MyQ-29B` -> `192.168.1.130` -> target `IoT` +2. `LG_Smart_Dryer2_open` -> `192.168.1.186` -> target `IoT` +3. `Samsung-FamilyHub` -> `192.168.1.149` -> target `IoT` +4. `Main-Floor` ecobee -> `192.168.1.102` -> target `IoT` +5. `Upstairs` ecobee -> `192.168.1.131` -> target `IoT` + +Validation for each: +- app still works +- device online +- no broad helper rule was needed + +### Migrate Later Only If Calm +6. `Google-Home-Mini` -> `192.168.1.185` +7. `3c:8d:20:f3:92:36` -> Google device -> `192.168.1.192` +8. `90:ca:fa:b6:7f:6e` -> Google device -> `192.168.1.129` + +Rule: +- Google is in scope tomorrow, but start with one Google-class device only unless everything is smooth +- if it demands broad discovery hacks, stop and defer + +### Quarantine Tomorrow +9. `5c:61:99:41:73:40` -> unknown Cloud Network device +10. `60:74:f4:54:fd:ec` -> Private/randomized +11. `60:74:f4:7b:6a:11` -> Private/randomized +12. `c0:f5:35:20:5d:94` -> AMPAK device +13. `d4:ad:fc:60:90:6a` -> Intellirocks +14. `d4:ad:fc:ea:7f:65` -> Intellirocks + +Rule: +- these do not earn clean-IoT membership by being merely online +- leave them on Legacy CIA quarantine until identified better + +## 8. Camera/Security Reality Check + +Already true: +- `front-doorbell` is already on Camera at `10.5.20.217` + +Tomorrow’s security work is therefore mainly: +- move/clean up chimes +- tighten policy +- preserve the doorbell’s healthy state + +## 9. Firewall Build Priorities + +Build these first: +- established/related allow +- invalid drop +- management shield +- Trusted admin -> Management allow +- Trusted -> Servers allow +- Guest -> internet only +- IoT -> DNS/NTP/internet + specific helpers only +- Camera -> DNS/NTP/internet/Protect only +- Legacy CIA -> DNS/NTP/internet only + explicit one-offs only +- broad internal denies for Guest/IoT/Camera/Legacy CIA + +Do not do tomorrow unless proven necessary: +- broad mDNS/cast trust exceptions +- loose `IoT -> Servers any` +- loose `Legacy CIA -> Trusted any` + +## 10. Validation Gates + +After each meaningful change, check the smallest thing that proves success. + +### After management changes +- [ ] UniFi admin still reachable +- [ ] gateway/switch/AP still visible +- [ ] no surprise loss of wireless control + +### After each server port move +- [ ] host gets correct subnet/gateway +- [ ] reachable from Trusted +- [ ] expected service path works + +### After the Nomad/Serenity/PD batch +- [ ] NFS/shared-service relationships recover cleanly +- [ ] PD remains reachable from Rocinante/admin path +- [ ] no cross-server dependency is hanging half-broken + +### After each IoT move +- [ ] device rejoins expected SSID/VLAN +- [ ] app/control works +- [ ] no broad workaround rule added + +### After security changes +- [ ] doorbell/chime online +- [ ] expected app/admin behavior works + +### After quarantine posture changes +- [ ] Legacy CIA devices still have minimum acceptable functionality +- [ ] they do not have new local trust + +## 11. Fast Rollback Strip + +If anything goes sideways: +1. revert the last moved device/port first +2. revert the last SSID/VLAN assignment second +3. revert the last firewall rule/order change third +4. restore management-plane reachability before doing anything clever +5. do not stack fresh changes on top of confusion + +Immediate rollback triggers: +- loss of UniFi management-plane access +- AP/switch disappears unexpectedly +- moved server loses reachability and root cause is not obvious quickly +- critical household function breaks and cannot be explained fast +- Google weirdness starts baiting sloppy panic rules + +## 12. Explicit Stop Condition + +Stop when: +- Management is materially cleaner +- Cameras lane is sane +- core server moves are done or deliberately deferred with reasons +- easy-value IoT moves are done +- Legacy CIA is explicitly a quarantine/sunset lane +- the next remaining work item smells like “heroics” instead of “mechanical completion” + +That is success. +Not every leftover wart has to die tomorrow. diff --git a/home/doris-dashboard/docs/network-cutover-minute-checklist.md b/home/doris-dashboard/docs/network-cutover-minute-checklist.md new file mode 100644 index 0000000..b4467ac --- /dev/null +++ b/home/doris-dashboard/docs/network-cutover-minute-checklist.md @@ -0,0 +1,261 @@ +# Home Network Redesign Minute-by-Minute Cutover Checklist + +> For Doris: this is the live change-window script. Use it like a pilot checklist, not a brainstorming prompt. + +Goal: execute the redesign tomorrow with deliberate pauses, validation after every block of changes, and clean rollback points. + +Assumptions: +- You have UniFi admin access. +- One stable Trusted admin client remains connected the entire time. +- You are not trying to empty Legacy CIA completely in one heroic swing. + +--- + +## T-30 to T-15 minutes: staging and sanity + +- [ ] Open the runbook: + - `/home/fizzlepoof/repos/truenas-stacks/home/doris-dashboard/docs/network-redesign-implementation-runbook.md` +- [ ] Open firewall rule order: + - `/home/fizzlepoof/repos/truenas-stacks/home/doris-dashboard/docs/network-firewall-rule-order.md` +- [ ] Open Legacy CIA worksheet: + - `/home/fizzlepoof/repos/truenas-stacks/home/doris-dashboard/docs/legacy-cia-triage-worksheet.md` +- [ ] Open visual artifacts: + - `http://10.5.30.7:8787/network-redesign.html` + - `http://10.5.30.7:8787/network-web.html` +- [ ] Confirm Rocinante is the live control box. +- [ ] Confirm fallback route to UniFi exists in case Rocinante loses the lane mid-change. +- [ ] Decide whether U6 LR is in or out for tomorrow. +- [ ] Do not start until you know where rollback begins. + +## Minute 0-10: baseline capture + +- [ ] Screenshot/export current networks. +- [ ] Screenshot/export current SSIDs. +- [ ] Screenshot/export firewall rules and order. +- [ ] Screenshot/export port profiles. +- [ ] Screenshot/export current switch port assignments. +- [ ] Screenshot/export AP config/broadcast mappings. +- [ ] Record current IP/MAC for: + - PD + - Serenity + - Nomad + - Rocinante + - doorbell + - Protect chimes + - ecobees + - MyQ + - Samsung FamilyHub + - LG dryer + - known Google/cast devices +- [ ] Snapshot which clients are on Management now. +- [ ] Snapshot which clients are on Legacy CIA now. + +Stop/go check: +- [ ] If baseline capture is incomplete, stop. + +## Minute 10-20: create/normalize objects only + +- [ ] Create or confirm target networks: + - Mgmt 10 + - Trusted 20 + - Servers 30 + - IoT 40 + - Guest 50 + - Cameras 60 + - Legacy CIA retained +- [ ] Create or confirm port profiles. +- [ ] Create or confirm SSIDs: + - Trusted + - Trusted-Compat if needed + - IoT + - Guest + - Security + - CIA Via only if still needed as temporary quarantine SSID +- [ ] Create/stage rule objects/groups. +- [ ] Enter staged firewall rules in proper order if not already present. + +Stop/go check: +- [ ] Nothing should have moved yet. +- [ ] Trusted admin client must still be fine. + +## Minute 20-35: management cleanup first + +- [ ] Confirm UDM, switches, and APs are mapped to Management intent. +- [ ] Remove obvious non-infrastructure devices from Management. +- [ ] Identify any stragglers remaining in Management and list them. +- [ ] If Security lane is ready, prepare to move chimes immediately. + +Validation: +- [ ] UniFi still sees gateway, switches, APs. +- [ ] Trusted admin client can still reach controller. +- [ ] No surprise disconnects. + +Rollback trigger: +- [ ] If management reachability degrades, revert the last infra change immediately. + +## Minute 35-50: stand up Cameras / Security lane + +- [ ] Activate/validate Cameras VLAN 60. +- [ ] Activate Security SSID if needed. +- [ ] Move Protect chimes. +- [ ] Move doorbell if ready. +- [ ] Confirm no security devices remain polluting Management. + +Validation: +- [ ] device rejoins expected lane +- [ ] app/admin/Protect behavior still works +- [ ] no broad emergency rules added yet + +Rollback trigger: +- [ ] If doorbell/chimes become unrecoverable fast, revert that device only. + +## Minute 50-75: move core servers to Servers VLAN 30 + +Order recommendation for this window: +1. keep Rocinante in place as the live operator station +2. move Nomad +3. move Serenity +4. move PD +5. move Rocinante only after the main batch is validated, or defer it +6. leave FlyingDutchman on Trusted unless you explicitly decide to reclassify it later + +For the Nomad/Serenity/PD batch: +- [ ] pre-stage the exact three-host move order before the first change +- [ ] move Nomad to Servers VLAN/profile +- [ ] move Serenity to Servers VLAN/profile +- [ ] move PD to Servers VLAN/profile +- [ ] verify IP/gateway/subnet for all three +- [ ] verify from Rocinante +- [ ] verify NFS/shared-service paths recover cleanly +- [ ] note any reservation/DNS fix needed before continuing + +Validation after the batch: +- [ ] each host responds as expected +- [ ] shared services still work +- [ ] PD is still reachable from Rocinante +- [ ] no accidental management loss + +Rollback trigger: +- [ ] If the batch breaks and root cause is not obvious quickly, revert the last moved host first and unwind in reverse order. + +## Minute 75-100: easy-value IoT migration + +Move in this general order: +1. MyQ +2. Samsung FamilyHub +3. LG dryer +4. ecobees if confidence remains high + +For each device/class: +- [ ] move to IoT VLAN 40 / IoT SSID +- [ ] wait for reassociation +- [ ] verify cloud/app control +- [ ] verify no stupid broad helper rule was needed + +Validation: +- [ ] app works +- [ ] device online +- [ ] no Management exposure + +Rollback trigger: +- [ ] If household-critical behavior breaks, revert only that device and continue with safer devices. + +## Minute 100-115: Legacy CIA triage pass + +- [ ] Open the triage worksheet. +- [ ] For every remaining Legacy CIA client, mark: + - migrate later + - quarantine + - kill candidate +- [ ] Keep useful-but-fragile leftovers on Legacy CIA. +- [ ] Apply quarantine firewall posture. +- [ ] Ensure no new devices are intentionally assigned there. + +Validation: +- [ ] Legacy CIA is now explicitly a quarantine lane, not fake production IoT. +- [ ] Remaining devices are explainable. + +## Minute 115-130: Google / discovery problem children only if time remains + +- [ ] Pick one Google/cast device only. +- [ ] Try moving it to IoT. +- [ ] Test casting/discovery from Trusted. +- [ ] If it needs exceptions, create only host-specific narrow ones. +- [ ] If it gets ugly, stop and revert that one device. + +Success condition: +- [ ] either one working pattern discovered +- [ ] or explicit deferral decided without poisoning policy + +## Minute 130-140: Guest and SSID cleanup + +- [ ] Verify Guest SSID -> VLAN 50. +- [ ] Test internet-only behavior. +- [ ] Disable stale SSIDs that are no longer needed. +- [ ] Keep Trusted-Compat only if it solved a real problem. +- [ ] Keep CIA Via only if remaining quarantined devices still require it. + +## Minute 140-150: final validation sweep + +From Trusted: +- [ ] internet works +- [ ] UniFi admin works +- [ ] PD/Serenity/Nomad/Rocinante reachable as intended + +From Guest: +- [ ] internet works +- [ ] local IPs blocked + +From IoT moved devices: +- [ ] app control works + +From Cameras/Security: +- [ ] doorbell/chimes online as expected + +From Management perspective: +- [ ] infra only, or any temporary exception is explicitly documented + +From Legacy CIA perspective: +- [ ] only leftovers remain +- [ ] quarantine posture is active + +## Minute 150-165: document what survived the window + +- [ ] Record exact devices moved successfully. +- [ ] Record exact devices reverted. +- [ ] Record temporary exceptions added. +- [ ] Record which devices remain on Legacy CIA and why. +- [ ] Record whether U6 LR remains out of scope or becomes next task. + +## Minute 165-180: stop, do not improvise + +If minimum success criteria are met, stop. + +Minimum success criteria: +- [ ] Management cleaned up materially +- [ ] Cameras lane exists +- [ ] Servers lane exists and core hosts are moved or clearly staged +- [ ] IoT lane exists and easy-value devices moved +- [ ] Legacy CIA converted into explicit quarantine/sunset lane +- [ ] no broad insecure panic rules were added + +If not met: +- [ ] roll back only the broken last-mile changes +- [ ] do not keep piling on work while tired + +## Emergency rollback strip + +If something goes sideways: +1. revert the last moved client/device +2. revert the last SSID/VLAN assignment change +3. revert the last firewall rule change/order change +4. restore management-plane reachability first +5. only continue after stability is back + +## Out of scope unless everything is shockingly smooth + +- full emptying of Legacy CIA +- perfect cast/mDNS handling for every Google thing +- RF fine-tuning +- U6 LR optimization work +- cleanup of every stale historical object in UniFi diff --git a/home/doris-dashboard/docs/network-cutover-red-team-card.md b/home/doris-dashboard/docs/network-cutover-red-team-card.md new file mode 100644 index 0000000..b12373d --- /dev/null +++ b/home/doris-dashboard/docs/network-cutover-red-team-card.md @@ -0,0 +1,103 @@ +# Network Cutover Red Team Card + +Use this during the live window when your brain gets dumb. + +Goal: +- make one clean change +- validate it +- either continue or revert + +If you feel rushed, stop. + +--- + +## DO + +- Keep one known-good Trusted admin client online the entire time. +- Keep Rocinante alive as the live control box until the main server batch is done or a fallback admin path is proven. +- Keep UniFi open. +- Keep the master operator sheet open. +- Change one thing at a time. +- Validate after every host, SSID, profile, or firewall change. +- Revert the last change fast if the break is not obvious. +- Write down temporary exceptions immediately. +- Treat Legacy CIA as quarantine, not a place to hide problems. + +## DO NOT + +- Do not move unrelated core hosts casually. +- Do not move Rocinante early if you are actively driving the migration from it. +- Do not touch port 27 casually. +- Do not touch port 1 unless intentionally normalizing infra. +- Do not "clean up" Google/cast weirdness early. +- Do not add broad allow-any-any panic rules. +- Do not keep pushing forward while tired or annoyed. +- Do not trust memory when the docs already exist. + +--- + +## FIRST IF BROKEN + +1. Ask: what was the very last change? +2. Revert that exact change first. +3. Restore management reachability before anything else. +4. Confirm UniFi gateway, switch, and AP visibility. +5. Confirm the Trusted admin client still reaches the controller. +6. Only then decide whether to retry. + +If management breaks: +- revert the last infra/profile/VLAN change immediately +- stop until control-plane access is stable + +If the Nomad/Serenity/PD batch breaks: +- revert the last moved host first and unwind in reverse order +- do not start debugging Google nonsense in the middle of a storage/service outage + +If a server breaks: +- revert only that server/port/profile +- do not move the next server yet + +If an IoT device breaks: +- revert only that device +- keep the rest of the window moving + +If Google/cast gets weird: +- defer it +- do not poison policy for the whole network + +--- + +## SAFE ORDER + +1. Baseline capture +2. Create/confirm objects only +3. Management cleanup +4. Security/chimes/camera lane +5. Server ports one by one +6. Easy IoT wins +7. Legacy CIA quarantine pass +8. Google weirdness only if time and confidence remain +9. Guest/SSID cleanup +10. Final validation +11. Stop + +--- + +## SUCCESS LOOKS LIKE + +- Management materially cleaner +- Camera/Security lane exists +- Servers lane exists and core hosts are moved or clearly staged +- Easy-value IoT devices are moved +- Legacy CIA is now an explicit quarantine/sunset lane +- No broad insecure emergency rules were added + +## HARD STOP + +Stop if: +- you lose clear management reachability +- you are stacking unresolved breakage +- you are guessing instead of validating +- you catch yourself saying "it’ll probably be fine" + +That sentence means stop. diff --git a/home/doris-dashboard/docs/network-firewall-rule-order.md b/home/doris-dashboard/docs/network-firewall-rule-order.md new file mode 100644 index 0000000..ef1682d --- /dev/null +++ b/home/doris-dashboard/docs/network-firewall-rule-order.md @@ -0,0 +1,411 @@ +# Home Network Redesign Firewall Rule Order + +> For Doris: this is the intended UniFi firewall ordering for the redesign. Use groups/comments so every rule is explainable at 2 AM. + +Goal: enforce clean segmentation between Management, Trusted, Servers, IoT, Guest, Cameras, and Legacy CIA quarantine without using broad lazy exceptions. + +Assumptions: +- Final networks: + - VLAN 10 Management -> `10.5.10.0/24` + - VLAN 20 Trusted -> `10.5.20.0/24` + - VLAN 30 Servers -> `10.5.30.0/24` + - VLAN 40 IoT -> `10.5.40.0/24` + - VLAN 50 Guest -> `10.5.50.0/24` + - VLAN 60 Cameras -> `10.5.60.0/24` + - Legacy CIA quarantine -> existing legacy subnet/VLAN retained temporarily +- Use address/object groups where UniFi allows them. +- Apply stateful best practice first: established/related before policy rules. +- Specific allows always go above broad denies. + +--- + +## 1. Object / Group Inventory To Create First + +Create these objects before writing rules: + +### Network groups +- `NET-MGMT` +- `NET-TRUSTED` +- `NET-SERVERS` +- `NET-IOT` +- `NET-GUEST` +- `NET-CAMERAS` +- `NET-LEGACY-CIA` +- `NET-RFC1918-ALL` = all internal subnets above plus any other local internal ranges + +### Device / host groups +- `HOST-ADMIN-TRUSTED` + - your main laptop + - your phone/tablet if you really need admin from it +- `HOST-CORE-SERVICES` + - PD + - Serenity + - Nomad + - Rocinante + - any controller/helper hosts +- `HOST-PROTECT-SERVICES` + - NVR / Protect endpoints if separate from above +- `HOST-DNS` + - whichever DNS resolvers clients should use +- `HOST-NTP` + - if you use local NTP, otherwise this can be omitted +- `HOST-IOT-HELPERS` + - only services IoT devices are explicitly allowed to hit +- `HOST-CAMERA-HELPERS` + - only services Cameras are explicitly allowed to hit +- `HOST-LEGACY-EXCEPTIONS` + - only for ugly one-off CIA quarantine exceptions + +### Port groups +- `PORT-DNS` = 53 TCP/UDP +- `PORT-DHCP` = 67-68 UDP +- `PORT-NTP` = 123 UDP +- `PORT-WEB-ADMIN` = 80,443,8443,9443 or whatever you actually use +- `PORT-SSH` = 22 +- `PORT-PROTECT` = exact ports only if needed +- `PORT-MDNS` = 5353 UDP +- `PORT-CAST` = exact discovery/control ports if later proven necessary + +## 2. Rule Philosophy + +- Trusted is the only lane that gets routine admin rights. +- Management is infrastructure-only and should accept traffic only from explicit admin initiators. +- Servers accept specific human/service traffic from Trusted and tightly scoped helper traffic from IoT/Cameras. +- IoT, Cameras, and Legacy CIA are default-deny internally. +- Guest is internet-only. +- Legacy CIA is hospice, not production. + +## 3. Recommended Rule Order + +This is ordered top to bottom. + +### Section A: Core state handling +1. `ALLOW Established/Related` + - Action: Allow + - States: Established, Related + - Source: Any + - Destination: Any + - Comment: `baseline stateful return traffic` + +2. `DROP Invalid` + - Action: Drop + - States: Invalid + - Source: Any + - Destination: Any + - Comment: `drop broken/invalid sessions early` + +### Section B: Management protection +3. `ALLOW Trusted Admin -> Management Admin Surfaces` + - Action: Allow + - Source: `HOST-ADMIN-TRUSTED` + - Destination: `NET-MGMT` + - Ports: `PORT-WEB-ADMIN`, `PORT-SSH` and anything truly required + - Comment: `explicit admin to management` + +4. `ALLOW Trusted Admin -> Gateway Infra Utilities` + - Action: Allow + - Source: `HOST-ADMIN-TRUSTED` + - Destination: `NET-MGMT` + - Ports: ICMP plus other explicitly needed management utilities + - Comment: `ping/test/manage infra` + +5. `DROP IoT -> Management` + - Action: Drop + - Source: `NET-IOT` + - Destination: `NET-MGMT` + - Comment: `iot never initiates to management` + +6. `DROP Cameras -> Management` + - Action: Drop + - Source: `NET-CAMERAS` + - Destination: `NET-MGMT` + - Comment: `camera lane never initiates to management` + +7. `DROP Guest -> Management` + - Action: Drop + - Source: `NET-GUEST` + - Destination: `NET-MGMT` + - Comment: `guest blocked from management` + +8. `DROP Legacy CIA -> Management` + - Action: Drop + - Source: `NET-LEGACY-CIA` + - Destination: `NET-MGMT` + - Comment: `legacy quarantine blocked from management` + +9. `DROP Any Internal -> Management` + - Action: Drop + - Source: `NET-RFC1918-ALL` + - Destination: `NET-MGMT` + - Comment: `default management shield after explicit allows` + +### Section C: Trusted human lane +10. `ALLOW Trusted -> Servers Approved Access` + - Action: Allow + - Source: `NET-TRUSTED` + - Destination: `NET-SERVERS` + - Ports: Any or explicit service groups depending on how tight you want day one + - Comment: `trusted human lane to servers` + +11. `ALLOW Trusted -> Cameras Admin/Viewer Access` + - Action: Allow + - Source: `NET-TRUSTED` + - Destination: `NET-CAMERAS` + - Ports: exact ports if known, otherwise temporary broader allow during migration + - Comment: `trusted operator to camera lane` + +12. `ALLOW Trusted -> IoT Control Exceptions` + - Action: Allow + - Source: `NET-TRUSTED` + - Destination: `NET-IOT` + - Ports: only what control/discovery really needs + - Comment: `trusted control to iot where required` + +### Section D: Server helper traffic +13. `ALLOW Servers -> IoT Approved Helpers` + - Action: Allow + - Source: `HOST-IOT-HELPERS` or `NET-SERVERS` narrowed to real helper hosts + - Destination: `NET-IOT` + - Ports: exact service ports only + - Comment: `ha/mqtt/etc only if actually used` + +14. `ALLOW Cameras -> Protect Services` + - Action: Allow + - Source: `NET-CAMERAS` + - Destination: `HOST-PROTECT-SERVICES` + - Ports: exact Protect/NVR ports + - Comment: `camera lane to protect only` + +15. `ALLOW IoT -> Approved Server Helpers` + - Action: Allow + - Source: `NET-IOT` + - Destination: `HOST-IOT-HELPERS` + - Ports: exact ports only + - Comment: `iot can only hit specific helper services` + +16. `ALLOW Legacy CIA -> Approved One-Off Exception` + - Action: Allow + - Source: specific host(s) in `NET-LEGACY-CIA` + - Destination: specific host(s) + - Ports: exact ports only + - Disabled by default unless proven needed + - Comment: `ugly but narrow quarantine exception` + +### Section E: DNS / NTP baseline for restricted lanes +17. `ALLOW IoT -> DNS` + - Action: Allow + - Source: `NET-IOT` + - Destination: `HOST-DNS` + - Ports: `PORT-DNS` + - Comment: `iot dns` + +18. `ALLOW Cameras -> DNS` + - Action: Allow + - Source: `NET-CAMERAS` + - Destination: `HOST-DNS` + - Ports: `PORT-DNS` + - Comment: `camera dns` + +19. `ALLOW Legacy CIA -> DNS` + - Action: Allow + - Source: `NET-LEGACY-CIA` + - Destination: `HOST-DNS` + - Ports: `PORT-DNS` + - Comment: `legacy dns` + +20. `ALLOW IoT -> NTP` + - Action: Allow + - Source: `NET-IOT` + - Destination: `HOST-NTP` or Any if using public NTP + - Ports: `PORT-NTP` + - Comment: `iot ntp` + +21. `ALLOW Cameras -> NTP` + - Action: Allow + - Source: `NET-CAMERAS` + - Destination: `HOST-NTP` or Any if using public NTP + - Ports: `PORT-NTP` + - Comment: `camera ntp` + +22. `ALLOW Legacy CIA -> NTP` + - Action: Allow + - Source: `NET-LEGACY-CIA` + - Destination: `HOST-NTP` or Any if using public NTP + - Ports: `PORT-NTP` + - Comment: `legacy ntp` + +### Section F: Internet access for constrained lanes +23. `ALLOW Guest -> Internet` + - Action: Allow + - Source: `NET-GUEST` + - Destination: Internet / Not RFC1918 + - Comment: `guest internet only` + +24. `ALLOW IoT -> Internet` + - Action: Allow + - Source: `NET-IOT` + - Destination: Internet / Not RFC1918 + - Comment: `iot outbound internet` + +25. `ALLOW Cameras -> Internet Updates` + - Action: Allow + - Source: `NET-CAMERAS` + - Destination: Internet / Not RFC1918 + - Comment: `camera updates/cloud only if needed` + +26. `ALLOW Legacy CIA -> Internet` + - Action: Allow + - Source: `NET-LEGACY-CIA` + - Destination: Internet / Not RFC1918 + - Comment: `legacy quarantine outbound only` + +### Section G: Broad internal denies for restricted lanes +27. `DROP Guest -> RFC1918/Internal` + - Action: Drop + - Source: `NET-GUEST` + - Destination: `NET-RFC1918-ALL` + - Comment: `guest blocked from local networks` + +28. `DROP IoT -> Trusted` + - Action: Drop + - Source: `NET-IOT` + - Destination: `NET-TRUSTED` + - Comment: `iot blocked from human lane` + +29. `DROP IoT -> Servers` + - Action: Drop + - Source: `NET-IOT` + - Destination: `NET-SERVERS` + - Comment: `iot blocked from servers after helper exceptions` + +30. `DROP IoT -> Cameras` + - Action: Drop + - Source: `NET-IOT` + - Destination: `NET-CAMERAS` + - Comment: `iot blocked from camera lane` + +31. `DROP Cameras -> Trusted` + - Action: Drop + - Source: `NET-CAMERAS` + - Destination: `NET-TRUSTED` + - Comment: `cameras blocked from human lane` + +32. `DROP Cameras -> Servers` + - Action: Drop + - Source: `NET-CAMERAS` + - Destination: `NET-SERVERS` + - Comment: `cameras blocked from servers except protect` + +33. `DROP Cameras -> IoT` + - Action: Drop + - Source: `NET-CAMERAS` + - Destination: `NET-IOT` + - Comment: `camera lane blocked from iot` + +34. `DROP Legacy CIA -> Trusted` + - Action: Drop + - Source: `NET-LEGACY-CIA` + - Destination: `NET-TRUSTED` + - Comment: `legacy blocked from human lane` + +35. `DROP Legacy CIA -> Servers` + - Action: Drop + - Source: `NET-LEGACY-CIA` + - Destination: `NET-SERVERS` + - Comment: `legacy blocked from servers after one-offs` + +36. `DROP Legacy CIA -> Cameras` + - Action: Drop + - Source: `NET-LEGACY-CIA` + - Destination: `NET-CAMERAS` + - Comment: `legacy blocked from security lane` + +37. `DROP Legacy CIA -> IoT` + - Action: Drop + - Source: `NET-LEGACY-CIA` + - Destination: `NET-IOT` + - Comment: `legacy does not mingle with clean iot` + +### Section H: Optional discovery exceptions +38. `ALLOW Trusted -> mDNS Reflector / Discovery Helper` + - Only if real testing proves needed + - Prefer reflector/helper architecture over broad subnet trust + +39. `ALLOW Specific Google/Cast Device -> Specific Helper` + - Host-specific only + - Ports exact only + - Comment with device name and why it exists + +40. `DROP Any Temporary Discovery Exception Cleanup Marker` + - Not a real rule type, just a process note + - Every temporary discovery rule gets a unique comment and review date + +## 4. Rule Notes By Lane + +Management: +- Most-protected lane. +- Admin access only from known trusted admin devices. + +Trusted: +- Primary operator lane. +- Allowed to talk where humans need to talk, but still not a dumping ground. + +Servers: +- Prefer exact service-based exposure later. +- For tomorrow, a broader Trusted -> Servers allow is acceptable if you document it and tighten later. + +IoT: +- Default deny inward and outward to internal LANs except helper carve-outs. + +Cameras: +- Same philosophy as IoT, but even stricter. +- If Protect is local, keep camera-to-protect flow exact. + +Legacy CIA: +- Outbound internet, DNS, NTP, nothing more unless explicitly justified. +- Every exception should feel embarrassing. + +## 5. What To Stage Tomorrow Versus Later + +Stage tomorrow: +- established/related +- invalid drop +- management shield +- guest internet-only pattern +- IoT/Cameras/Legacy broad internal denies +- Trusted admin to Management +- Trusted to Servers +- DNS/NTP for restricted lanes +- Legacy CIA quarantine posture + +Defer if not needed immediately: +- mDNS/cast helper rules +- fancy service-specific microsegmentation between Trusted and Servers +- any discovery exceptions not proven by real failure testing + +## 6. Validation Checklist After Rule Deployment + +- Trusted admin client can still reach UniFi/gateway/switch/AP admin surfaces +- Trusted can reach core server services +- Guest gets internet and cannot reach local IPs +- IoT device can resolve DNS, keep time, and reach vendor cloud if expected +- Camera/security device remains online and can reach Protect if required +- Legacy CIA device still works only at minimum acceptable level +- No restricted lane can initiate into Management + +## 7. Red Flags + +Do not do these: +- allow `IoT -> Servers any` +- allow `Legacy CIA -> Trusted any` +- allow `Guest -> RFC1918 any` +- put discovery/broadcast panic rules above security structure without device-specific justification +- leave unnamed temporary rules in place after testing + +## 8. First Tightening Pass After Tomorrow + +After the cutover stabilizes: +- narrow Trusted -> Servers from broad allow to explicit service sets if useful +- delete any temporary discovery exceptions that proved unnecessary +- audit whether Cameras even need outbound internet +- audit whether Legacy CIA can lose individual devices or the whole SSID diff --git a/home/doris-dashboard/docs/network-redesign-implementation-runbook.md b/home/doris-dashboard/docs/network-redesign-implementation-runbook.md new file mode 100644 index 0000000..27d8ae4 --- /dev/null +++ b/home/doris-dashboard/docs/network-redesign-implementation-runbook.md @@ -0,0 +1,419 @@ +# Home Network Redesign Implementation Runbook + +> For Doris: use this during the live UniFi change window. Goal is a controlled cutover with explicit validation and rollback at each boundary. + +Goal: deploy the target VLAN/SSID/firewall design with minimum drama, keep legacy CIA Via as a quarantine lane for devices that cannot yet be migrated, and finish the window with an understandable, supportable network. + +Architecture summary: +- Final target lanes remain VLAN 10 Management, VLAN 20 Trusted, VLAN 30 Servers, VLAN 40 IoT, VLAN 50 Guest, VLAN 60 Cameras/Security. +- Legacy CIA Via remains temporarily alive as a quarantine/sunset VLAN for unmanageable legacy devices. +- No non-infrastructure devices stay on Management when the window closes. +- No broad trust exceptions are added just to make migration feel easier. + +Tech stack / control surfaces: +- UniFi Network controller on UDM Pro +- UDM Pro gateway/firewall +- USW Pro HD 24 and other UniFi switching +- U7 Pro and optional U6 LR APs +- Doris artifacts for reference: + - `/home/fizzlepoof/repos/truenas-stacks/home/doris-dashboard/public/network-redesign.html` + - `/home/fizzlepoof/repos/truenas-stacks/home/doris-dashboard/public/network-web.html` + - `/home/fizzlepoof/repos/truenas-stacks/home/doris-dashboard/docs/network-redesign-plan.md` + +--- + +## 1. Required Access + +Required to implement directly: +- UniFi OS / Network write access on the UDM Pro site at `https://10.5.0.1` +- Ability to create/edit: + - VLANs / networks + - Wi-Fi SSIDs + - firewall rules + - port profiles + - switch port assignments + - device settings for APs/switches +- Ability to see client inventory with MAC/IP/vendor names + +Required for safe validation after each move: +- Existing SSH access to PD via `pd` (`truenas_admin@10.5.30.6`) +- Reachability to NOMAD-hosted Doris web artifacts at `10.5.30.7:8787` +- A Trusted client on-site on Wi‑Fi for live user validation +- At least one device on each of these categories available for test: + - Trusted client + - server service + - IoT device/app pair + - guest client + - camera/security device if moved tomorrow + +Optional but strongly preferred: +- Temporary full admin role in UniFi instead of pair-driving through read-only +- Access to export or screenshot current config before changes +- Ability to check AP/switch port LEDs/physical labels if anything is ambiguous + +Fallback if you do not want to grant write access: +- Pair-drive the entire window with you at the keyboard in UniFi while I call every exact change in order +- This is slower but workable + +## 2. What Can Be Finished Before Tomorrow + +Pre-work Doris can finish now without write access: +- Final runbook and sequence document +- Final object naming convention +- Final firewall intent matrix +- Validation checklist per VLAN +- Rollback checklist per phase +- Device triage sheet for Legacy CIA Via: + - migrate + - quarantine + - kill + +Pre-work requiring only read access / screenshots / exports from you: +- Confirm exact current UniFi object names +- Confirm whether VLAN IDs 10/20/30/40/50/60 already exist or must be created +- Confirm current SSID names and which APs broadcast each one +- Confirm current port-profile names and which switch ports use them +- Confirm which clients are currently on Management and CIA Via +- Confirm whether U6 LR is physically installed and link-ready or still shelved + +Pre-work requiring write access, if granted tonight: +- Create missing target networks/VLAN objects without moving clients yet +- Create disabled/staged SSIDs without enabling them yet +- Create new port profiles without assigning them yet +- Create firewall rules in disabled or low-priority staged form +- Label/comment rules and profiles for tomorrow’s change window + +## 3. Hard Rules For The Change Window + +- One operator console on Trusted stays connected the whole time. +- One fallback path into UniFi must remain alive before any management change. +- Do not move multiple unknown devices at once. +- Do not delete Legacy CIA Via tomorrow unless it is truly empty, which is unlikely. +- Do not move legacy/unmanageable junk into Management, Trusted, or Servers to “just get done.” +- After every change group, stop and validate before continuing. +- If management-plane reachability is degraded, roll back immediately before chasing anything else. + +## 4. Target Objects + +### Networks / VLANs +- `NET-MGMT` -> VLAN 10 -> `10.5.10.0/24` +- `NET-TRUSTED` -> VLAN 20 -> `10.5.20.0/24` +- `NET-SERVERS` -> VLAN 30 -> `10.5.30.0/24` +- `NET-IOT` -> VLAN 40 -> `10.5.40.0/24` +- `NET-GUEST` -> VLAN 50 -> `10.5.50.0/24` +- `NET-CAMERAS` -> VLAN 60 -> `10.5.60.0/24` +- `NET-LEGACY-CIA` -> existing legacy VLAN/subnet retained temporarily as quarantine lane + +### Wi‑Fi SSIDs +- `Trusted` -> VLAN 20 +- `Trusted-Compat` -> VLAN 20, optional temporary only +- `IoT` -> VLAN 40 +- `Guest` -> VLAN 50 +- `Security` -> VLAN 60 +- `CIA Via` -> legacy VLAN, temporary only, no new joins + +### Port profiles +- `PP-TRUNK-UPLINK` +- `PP-ACCESS-MGMT` +- `PP-ACCESS-TRUSTED` +- `PP-ACCESS-SERVERS` +- `PP-ACCESS-IOT` +- `PP-ACCESS-GUEST` +- `PP-ACCESS-CAMERAS` +- `PP-ACCESS-LEGACY-CIA` + +## 5. Firewall Policy Intent + +Trusted: +- allow to Management for admin devices only +- allow to Servers for normal human/service use +- allow to Cameras for operator/admin use +- allow limited control flows to IoT if required + +Servers: +- allow explicit services outward as needed +- no sloppy east-west broad allow +- allow NVR/Protect-related flows to Cameras only if required + +IoT: +- allow DHCP/DNS/NTP/internet +- deny Management +- deny Trusted by default +- deny Servers except specific helper/service destinations +- explicit discovery exceptions only when proven necessary + +Guest: +- internet only +- deny all local RFC1918/internal subnets +- client isolation on Wi‑Fi + +Cameras/Security: +- allow DHCP/DNS/NTP/internet/update paths as needed +- deny broad lateral movement +- allow only Protect/NVR/admin destinations + +Legacy CIA quarantine lane: +- allow DHCP/DNS/NTP/internet +- deny Management +- deny Trusted +- deny Cameras +- deny Servers by default +- add only device-specific exceptions if absolutely required +- no new devices assigned here intentionally + +## 6. Baseline Capture Before First Change + +Capture all of this before changing anything: +- Screenshot/export all existing networks +- Screenshot/export all SSIDs +- Screenshot/export firewall rules in current order +- Screenshot/export port profiles +- Screenshot/export AP settings +- Screenshot/export switch port assignments +- Record current IP/gateway/subnet for: + - PD + - Serenity + - Nomad + - Rocinante + - UDM Pro + - U7 Pro + - U6 LR if present +- Record current MAC/IP list for important Old IoT devices: + - Google Home Mini / cast devices + - both ecobees + - MyQ + - Samsung Family Hub + - LG dryer + - known legacy bulbs/plugs + - doorbell + - Protect chimes +- Confirm current DHCP reservations/static mappings that may break when VLANs move + +## 7. Validation Tests To Run Repeatedly + +Trusted validation: +- Trusted Wi‑Fi client gets correct subnet/gateway +- internet works +- can reach UniFi admin +- can reach PD/NOMAD/Serenity services expected from user lane + +Management validation: +- UDM, switches, APs remain visible/manageable in UniFi +- no unexpected client endpoints remain in Management + +Servers validation: +- PD/Serenity/Nomad/Rocinante get correct subnet/gateway after move +- critical services respond from Trusted +- outbound internet works if required + +IoT validation: +- device rejoins expected SSID/VLAN +- vendor app control still works +- local control still works only if intentionally allowed + +Guest validation: +- guest device gets internet +- cannot reach local subnets + +Security validation: +- doorbell/chimes/camera devices stay online +- admin/Protect access works if expected +- no reachability into unrelated lanes + +Legacy CIA validation: +- quarantined devices still work at the minimum acceptable level +- no new devices are added there + +## 8. Change Sequence For Tomorrow + +### Phase A: Safety setup +1. Confirm one wired or strongly stable Trusted admin client is connected. +2. Open UniFi in one session and keep Doris artifacts open in another. +3. Capture all baseline screenshots/exports. +4. Confirm rollback path into UniFi if Wi‑Fi changes go bad. +5. Confirm whether U6 LR participates tomorrow or stays out of scope. + +Exit criteria: +- baseline captured +- management reachability stable +- rollback path confirmed + +### Phase B: Create or normalize objects without moving clients +1. Create any missing target networks. +2. Create any missing port profiles. +3. Create/stage SSIDs: + - Trusted + - Trusted-Compat if needed + - IoT + - Guest + - Security +4. Create/stage Legacy CIA quarantine policy object naming. +5. Create staged firewall rules with comments. + +Exit criteria: +- all objects exist +- nothing has moved yet +- no current connectivity loss + +### Phase C: Management cleanup first +1. Ensure UDM, switches, and AP management surfaces are on VLAN 10 intent. +2. Remove obvious non-infrastructure devices from Management. +3. Move Protect chimes out of Management as top cleanup item if Security lane is ready. +4. Re-validate UniFi visibility of gateway, switches, APs. + +Rollback if: +- UniFi loses management of gateway/switch/APs +- admin client can no longer reach management plane + +Exit criteria: +- Management is infrastructure-only or very close to it +- no human endpoints or smart accessories remain there except known temporary exceptions tracked in writing + +### Phase D: Build/activate Cameras lane +1. Activate/validate `NET-CAMERAS`. +2. Activate `Security` SSID if used. +3. Move doorbell and Protect chimes to Cameras/Security. +4. Validate app/admin behavior. + +Rollback if: +- security devices fall offline and cannot be recovered quickly + +Exit criteria: +- doorbell/chimes are no longer polluting Management +- Camera lane exists for future growth + +### Phase E: Move core servers to Servers VLAN 30 +1. Move one least-scary server first. +2. Validate from Trusted. +3. Update any reservation/DNS/profile dependency if needed. +4. Move remaining core hosts one by one: + - PD + - Serenity + - Nomad + - Rocinante +5. After each move, validate service reachability and outbound needs. + +Rollback if: +- critical service path breaks and root cause is not obvious within a few minutes + +Exit criteria: +- core hosts reside in Servers lane +- Trusted still reaches intended services + +### Phase F: Move easy IoT first +1. Activate/validate `IoT` SSID and VLAN 40. +2. Move easiest/least-fragile devices first: + - MyQ + - Samsung Family Hub + - LG dryer + - ecobees if confidence is high +3. Validate each class before moving the next. +4. Leave Google/cast pain until the end of IoT work. + +Rollback if: +- essential household function breaks and app recovery is not immediate + +Exit criteria: +- easy-value devices are out of Legacy CIA +- no unnecessary broad allow rules were added + +### Phase G: Legacy CIA quarantine handling +1. Rename/reclassify CIA Via mentally and operationally as quarantine, not production IoT. +2. Leave orphaned bulbs/plugs/mystery devices there. +3. Apply strict quarantine firewall posture. +4. Build a written list of remaining MACs/devices with disposition: + - migrate later + - quarantine indefinitely for now + - kill/remove when safe + +Exit criteria: +- Legacy CIA contains only leftovers and quarantined junk +- its policy is harsh and explicit + +### Phase H: Google / discovery problem children +1. Move one Google/cast-class device only if time and energy remain. +2. Test discovery/casting behavior from Trusted. +3. Add only narrow exceptions if evidence proves a need. +4. If messy, stop. Leave the rest in Legacy CIA quarantine for a later targeted session. + +Exit criteria: +- either one known-good pattern exists, or the problem is intentionally deferred + +### Phase I: Guest and SSID cleanup +1. Validate Guest SSID maps to VLAN 50. +2. Confirm internet-only behavior. +3. Disable/retire any stale SSIDs not still needed for migration. +4. Keep `Trusted-Compat` only if it earns its keep. +5. Keep `CIA Via` only as temporary quarantine SSID if required for remaining devices. + +Exit criteria: +- SSIDs reflect policy, not history +- only necessary temporary migration SSIDs remain + +## 9. Explicit Device Triage + +Move tomorrow if practical: +- Protect chimes +- doorbell +- PD +- Serenity +- Nomad +- Rocinante +- MyQ +- Samsung Family Hub +- LG dryer +- both ecobees if low risk + +Probably defer or treat carefully: +- Google Home / cast ecosystem +- any device requiring odd mDNS/broadcast behavior + +Quarantine in Legacy CIA: +- legacy lights +- legacy plugs +- mystery old smart devices +- anything no longer controllable but still physically present + +Kill candidates after observation: +- unknown inactive MACs +- devices nobody misses after blocking +- duplicate/orphaned historical entries + +## 10. Rollback Rules + +Immediate rollback triggers: +- loss of UniFi management-plane access +- APs/switches vanish or go isolated unexpectedly +- Trusted admin client cannot reach controller/gateway +- critical household function breaks with no quick diagnosis + +Rollback scope: +- revert last moved device first +- revert last SSID mapping change second +- revert last firewall rule addition/position third +- revert network object only if the object itself is wrong +- never stack more changes onto a broken state + +## 11. Definition Of Done For Tomorrow + +Minimum successful tomorrow outcome: +- Management is cleaned up +- Cameras/Security lane exists +- Servers lane exists and at least core hosts are moved or staged with confidence +- IoT lane exists and at least easy-value devices are moved +- Legacy CIA is converted into explicit quarantine/sunset lane +- no broad insecure exceptions were added out of fatigue + +Nice-to-have, not mandatory tomorrow: +- U6 LR restored and tuned +- Google/cast fully migrated +- Legacy CIA emptied completely +- perfect final SSID simplification + +## 12. Follow-up Work After Tomorrow + +- Build exact firewall rule matrix page/artifact +- Commit/push dashboard docs and artifacts into repo properly +- Review remaining Legacy CIA devices one by one +- Decide whether U6 LR returns based on actual coverage evidence +- Remove stale SSIDs, port profiles, and rules once migration dust settles diff --git a/home/doris-dashboard/docs/network-redesign-plan.md b/home/doris-dashboard/docs/network-redesign-plan.md new file mode 100644 index 0000000..0d6e11b --- /dev/null +++ b/home/doris-dashboard/docs/network-redesign-plan.md @@ -0,0 +1,214 @@ +# Home Network Redesign Plan + +> For Doris: this is the target architecture and migration plan that the interactive site visualizes. + +Goal: redesign John's home network and homelab into a simpler, future-proof VLAN model that supports camera growth, cleaner firewall policy, better Wi-Fi placement, and easier operations. + +Architecture summary: +- Collapse historical clutter into six intentional VLANs. +- Separate user devices, servers, infrastructure, IoT, guests, and cameras/security. +- Move all non-infrastructure devices off Management. +- Retire Old IoT after staged migration. +- Add the downstairs U6 LR back into service if coverage or roaming needs it. + +## Target VLANs + +1. Infrastructure / Management +- VLAN 10 +- Subnet: 10.5.10.0/24 +- Contains: UDM, switches, APs, management IPs, controller-facing infra, OOB/admin surfaces +- Policy: only Trusted admin devices and designated server tooling may initiate into this VLAN + +2. Trusted Clients +- VLAN 20 +- Subnet: 10.5.20.0/24 +- Contains: phones, tablets, laptops, handhelds, Steam Deck, personal endpoints +- Policy: can reach Servers and selected admin surfaces; not a dumping ground for random smart devices + +3. Servers / Core Services +- VLAN 30 +- Subnet: 10.5.30.0/24 +- Contains: PlausibleDeniability, Serenity, Nomad, Rocinante, FlyingDutchman if it is a real service host +- Policy: tightly controlled east-west; explicit service exposure to IoT/Camera where needed + +4. IoT / Smart Home +- VLAN 40 +- Subnet: 10.5.40.0/24 +- Contains: ecobees, appliances, MyQ, Google Home-class devices, legacy smart junk worth keeping +- Policy: default deny to internal networks except DNS, NTP, specific server services, and carefully allowed discovery flows + +5. Guest +- VLAN 50 +- Subnet: 10.5.50.0/24 +- Contains: visitor and untrusted BYOD devices +- Policy: internet only, client isolation on Wi-Fi + +6. Cameras / Security +- VLAN 60 +- Subnet: 10.5.60.0/24 +- Contains: doorbell, Protect chimes, future cameras, NVR-adjacent accessories, security-specific Wi-Fi endpoints +- Policy: no broad access to LAN; only specific flows to Protect/NVR services and operator/admin clients + +## SSID Plan + +Preferred steady-state SSIDs: +- Trusted: one main trusted SSID +- Trusted-Compat: optional temporary fallback for stubborn devices only +- IoT: one dedicated smart-home SSID +- Guest: one guest SSID +- Security: one dedicated SSID for Wi-Fi doorbells/chimes/cameras if needed + +Mapping recommendation: +- Trusted SSID -> VLAN 20 +- Trusted-Compat SSID -> VLAN 20 (temporary or policy-reduced fallback) +- IoT SSID -> VLAN 40 +- Guest SSID -> VLAN 50 +- Security SSID -> VLAN 60 + +Current-name migration suggestion: +- Yer a Wifi Harry -> Trusted +- Whiskey Neat Fuck Ice -> temporary Trusted-Compat or retire later +- CIA Via -> repoint to IoT during migration, then decide whether to keep or rename +- UNEF's Playhouse -> Security / Cameras + +## Device Placement Recommendations + +Infrastructure / Management: +- UDM Pro +- USW Pro HD 24 +- USW-24-PoE +- U7 Pro +- U6 LR +- other management-only appliance interfaces + +Servers / Core Services: +- PlausibleDeniability +- Serenity +- Nomad +- Rocinante +- FlyingDutchman if used as a real service host + +Trusted Clients: +- phones +- tablets +- handhelds +- laptops/desktops +- Steam Deck + +IoT / Smart Home: +- Main-Floor ecobee +- Upstairs ecobee +- MyQ +- Samsung FamilyHub +- LG dryer +- Google Home Mini +- Google / Chromecast-class devices +- retained legacy smart devices + +Cameras / Security: +- front-doorbell +- Protect chimes +- future wired/PoE cameras +- future Wi-Fi security accessories + +## Wi-Fi and AP Placement + +Primary AP design: +- U7 Pro remains active as the main upstairs AP. +- Reinstall the U6 LR downstairs if downstairs coverage, roaming, or camera/IoT RSSI is weak. +- Prefer wired backhaul for both APs. + +Operational stance: +- Do not overcomplicate RF tuning on day one. +- First restore dual-AP coverage, then evaluate channels, transmit power, and minimum RSSI with real client behavior. +- For growing cameras/security footprint, bias toward wired PoE cameras on the Cameras VLAN when practical. + +## Firewall Intent + +Trusted -> Management +- allow only designated admin devices/services + +Trusted -> Servers +- allow + +Trusted -> IoT +- limited allow for control/discovery as needed + +Trusted -> Cameras +- allow operator/admin access + +Servers -> IoT +- allow only explicitly needed services + +Servers -> Cameras +- allow Protect/NVR-related flows only + +IoT -> internal networks +- default deny +- explicit allow to DNS, NTP, specific server services, and required discovery helpers only + +Cameras -> internal networks +- default deny +- explicit allow to Protect/NVR, time, DNS, and update paths only + +Guest -> internal networks +- deny + +Management -> internet +- only what infrastructure actually needs + +## Migration Order + +Phase 0: groundwork +- create target VLANs and port profiles +- stage firewall rules +- stage target SSIDs +- document rollback points + +Phase 1: infrastructure cleanup +- move all infra to Management VLAN 10 +- remove non-infra clients from management +- verify switch/AP/gateway management reachability + +Phase 2: servers +- move core hosts to Servers VLAN 30 +- confirm service reachability from Trusted +- update DNS/static mappings/firewall rules + +Phase 3: cameras/security +- move doorbell and chimes to VLAN 60 +- prepare room for future camera growth + +Phase 4: IoT migration +- move active smart-home devices from Old IoT into VLAN 40 in batches +- start with easiest cloud-managed devices, then thermostats, then Google ecosystem devices + +Phase 5: SSID simplification +- retire Old IoT SSID after validation +- collapse or retire temporary Trusted-Compat if not needed + +Phase 6: cleanup +- remove Old IoT VLAN 2 +- remove stale port profiles +- remove stale firewall rules and historical exceptions + +## Port Profile Set + +Maintain only intentional profiles: +- trunk-uplink +- access-management +- access-trusted +- access-servers +- access-iot +- access-guest +- access-cameras + +## Success Criteria + +- Management contains infrastructure only +- Old IoT is retired +- Cameras/Security has room to grow now, not later +- VLAN purpose is obvious at a glance +- Wi-Fi SSIDs reflect policy, not historical accidents +- Firewall rules match intent and are explainable +- A tired operator can still understand the network at 2 AM diff --git a/home/doris-dashboard/docs/old-iot-tomorrow-disposition-list.md b/home/doris-dashboard/docs/old-iot-tomorrow-disposition-list.md new file mode 100644 index 0000000..2cb66a7 --- /dev/null +++ b/home/doris-dashboard/docs/old-iot-tomorrow-disposition-list.md @@ -0,0 +1,226 @@ +# Old IoT Tomorrow Disposition List + +> For Doris: this is the concrete disposition call for every current live Old IoT client seen during read-only recon. This is the list to use tomorrow unless live validation proves otherwise. + +Source basis: +- live UniFi read-only recon on 2026-05-22 +- all devices listed were active on SSID `CIA Via` through the `U7 Pro` +- /home/fizzlepoof/repos/truenas-stacks/home/doris-dashboard/docs/unifi-readonly-recon-2026-05-22.md +- /home/fizzlepoof/repos/truenas-stacks/home/doris-dashboard/docs/legacy-cia-triage-worksheet.md + +Disposition labels: +- `migrate-now` +- `migrate-later` +- `quarantine` +- `kill-candidate` + +--- + +## 1. Move tomorrow if practical (`migrate-now`) + +### MyQ-29B +- IP: `192.168.1.130` +- Vendor: The Chamberlain Group, Inc +- Tomorrow disposition: `migrate-now` +- Target: `IoT` +- Why: + - known device + - clear owner/function + - easy-value migration win +- Validation: + - MyQ app still works + - garage status/control behaves normally + +### Main-Floor +- IP: `192.168.1.102` +- Vendor: ecobee inc +- Tomorrow disposition: `migrate-now` +- Target: `IoT` +- Why: + - known thermostat + - clear function + - deserves proper IoT placement +- Validation: + - thermostat visible in app + - control/update works + +### Upstairs +- IP: `192.168.1.131` +- Vendor: ecobee inc +- Tomorrow disposition: `migrate-now` +- Target: `IoT` +- Why: + - same reasoning as Main-Floor +- Validation: + - thermostat visible in app + - control/update works + +### LG_Smart_Dryer2_open +- IP: `192.168.1.186` +- Vendor: LG Innotek +- Tomorrow disposition: `migrate-now` +- Target: `IoT` +- Why: + - named appliance + - low emotional/operational complexity + - good early migration candidate +- Validation: + - LG app/device connectivity still works + +### Samsung-FamilyHub +- IP: `192.168.1.149` +- Vendor: Samsung Electronics Co., Ltd +- Tomorrow disposition: `migrate-now` +- Target: `IoT` +- Why: + - known appliance + - obvious long-term home +- Validation: + - Samsung app/features still work at the acceptable level + +## 2. Move only if time/energy remains, otherwise defer (`migrate-later`) + +### Google-Home-Mini +- IP: `192.168.1.185` +- Vendor: Google, Inc. +- Tomorrow disposition: `migrate-later` +- Target: `IoT` +- Why: + - discovery/cast pain risk + - likely to provoke panic-rule temptation +- Tomorrow handling: + - move only if the rest of the window is already stable + - if ugly, revert or leave on Legacy CIA quarantine +- Validation: + - app sees device + - casting/discovery from Trusted behaves acceptably + +### 3c:8d:20:f3:92:36 +- IP: `192.168.1.192` +- Vendor: Google, Inc. +- Tomorrow disposition: `migrate-later` +- Target: likely `IoT` +- Why: + - Google-class unknown-by-name device + - same discovery/cast risk profile +- Tomorrow handling: + - identify what it is before moving if possible + - otherwise defer +- Validation: + - whatever its expected Google behavior is still works + +### 90:ca:fa:b6:7f:6e +- IP: `192.168.1.129` +- Vendor: Google, Inc. +- Tomorrow disposition: `migrate-later` +- Target: likely `IoT` +- Why: + - likely another Google/cast/discovery problem child +- Tomorrow handling: + - identify first if possible + - otherwise defer +- Validation: + - expected Google behavior survives move + +## 3. Leave on Legacy CIA quarantine tomorrow unless identified better (`quarantine`) + +### 5c:61:99:41:73:40 +- IP: `192.168.1.172` +- Vendor: Cloud Network Technology Singapore Pte. Ltd. +- Tomorrow disposition: `quarantine` +- Why: + - unnamed + - unclear function + - not enough evidence to grant clean-IoT membership +- Tomorrow handling: + - leave on Legacy CIA with harsh restrictions + - identify later + +### 60:74:f4:54:fd:ec +- IP: `192.168.1.136` +- Vendor: Private +- Tomorrow disposition: `quarantine` +- Why: + - private/randomized identity + - no useful name + - function unclear +- Tomorrow handling: + - quarantine first, not migration first + +### 60:74:f4:7b:6a:11 +- IP: `192.168.1.117` +- Vendor: Private +- Tomorrow disposition: `quarantine` +- Why: + - same reasoning as above + +### c0:f5:35:20:5d:94 +- IP: `192.168.1.183` +- Vendor: AMPAK Technology,Inc. +- Tomorrow disposition: `quarantine` +- Why: + - could be all sorts of embedded junk + - no positive identification + +### d4:ad:fc:60:90:6a +- IP: `192.168.1.100` +- Vendor: Shenzhen Intellirocks Tech co., ltd +- Tomorrow disposition: `quarantine` +- Why: + - unclear function + - likely disposable/low-trust smart junk unless proven otherwise + +### d4:ad:fc:ea:7f:65 +- IP: `192.168.1.101` +- Vendor: Shenzhen Intellirocks Tech co., ltd +- Tomorrow disposition: `quarantine` +- Why: + - same reasoning as sister device above + +## 4. Kill-candidate logic + +No current Old IoT client is marked `kill-candidate` immediately from inventory alone because we still saw them as active. + +But these become `kill-candidate` fast if: +- nobody can identify them +- nobody can say what they do +- blocking/quarantining them produces no complaints +- they are duplicate Google/embedded junk with no actual value + +Most likely future kill-candidate pool: +- the unnamed `Private` devices +- the unknown Cloud Network / AMPAK devices +- the two unnamed Intellirocks devices +- any Google MAC-only device nobody can identify once mapped physically + +## 5. Suggested tomorrow move order inside Old IoT + +Use this order: +1. MyQ-29B +2. LG_Smart_Dryer2_open +3. Samsung-FamilyHub +4. Main-Floor ecobee +5. Upstairs ecobee +6. stop and assess +7. only then test one Google device if the window is still calm + +Everything else: +- stays on Legacy CIA quarantine +- gets documented +- gets revisited later with actual identification work + +## 6. Explicit anti-mistakes + +Do not do these tomorrow: +- do not migrate unknown MAC-only devices just because they are online +- do not broaden IoT trust to appease Google discovery quickly +- do not move quarantine-worthy junk into Management or Trusted +- do not try to fully empty Old IoT if the window starts getting noisy + +## 7. Success condition for tomorrow + +A successful tomorrow outcome is: +- easy-value named devices are moved into IoT +- Google/discovery weirdness is either handled narrowly or deferred +- all unknown junk remains contained on Legacy CIA +- every remaining Old IoT device has an explicit reason it is still there diff --git a/home/doris-dashboard/docs/server-batch-validation-checklist.md b/home/doris-dashboard/docs/server-batch-validation-checklist.md new file mode 100644 index 0000000..6dfabdf --- /dev/null +++ b/home/doris-dashboard/docs/server-batch-validation-checklist.md @@ -0,0 +1,135 @@ +# Server Batch Validation Checklist + +Use this immediately after moving Nomad, Serenity, and PD. +Do not start Google nonsense, SSID cleanup, or extra cleanup until this is green. + +Primary control path: +- operator box: Rocinante +- critical dependency group: Nomad + Serenity + PlausibleDeniability + +--- + +## Before the batch + +- [ ] Rocinante is stable and currently has UniFi/admin access. +- [ ] Fallback admin path exists if Rocinante drops off. +- [ ] Current port/profile screenshots taken for: + - [ ] Port 22 -> Nomad + - [ ] Port 24 -> Serenity + - [ ] Port 23 -> PD +- [ ] Expected target subnet/gateway for Servers VLAN is written down. +- [ ] You are ready to unwind in reverse order if needed. + +Move order: +1. Nomad +2. Serenity +3. PD + +Rollback order if ugly: +1. PD +2. Serenity +3. Nomad + +--- + +## Per-host quick checks during the wave + +### After Nomad move +- [ ] Link came back +- [ ] Nomad got expected Servers IP/subnet/gateway +- [ ] Rocinante can still reach Nomad +- [ ] Expected Nomad-hosted service(s) respond +- [ ] No obvious share/dependency explosion yet + +### After Serenity move +- [ ] Link came back +- [ ] Serenity got expected Servers IP/subnet/gateway +- [ ] Rocinante can still reach Serenity +- [ ] Expected Serenity-hosted service(s) respond +- [ ] No obvious share/dependency explosion yet + +### After PD move +- [ ] Link came back +- [ ] PD got expected Servers IP/subnet/gateway +- [ ] Rocinante can still reach PD +- [ ] SSH/admin path to PD still works +- [ ] Critical PD-hosted homelab services respond + +--- + +## Immediate batch validation + +Do all of these before touching anything else: + +### Reachability +- [ ] Rocinante -> Nomad reachable +- [ ] Rocinante -> Serenity reachable +- [ ] Rocinante -> PD reachable + +### IP sanity +- [ ] Nomad is on the intended Servers subnet +- [ ] Serenity is on the intended Servers subnet +- [ ] PD is on the intended Servers subnet +- [ ] Default gateway is correct for all three + +### Cross-host dependency sanity +- [ ] NFS mounts/shares recovered cleanly +- [ ] No host is hanging on stale routes or dead mount targets +- [ ] Any name-resolution dependency still works as expected +- [ ] No authentication/admin path you need for the rest of the window disappeared + +### Service sanity +- [ ] Doris/dashboard path(s) you need are still reachable +- [ ] Any storage-backed services needed for the live window still work +- [ ] Nothing critical is stuck waiting on a dead share + +### Control-plane sanity +- [ ] UniFi is still reachable from Rocinante +- [ ] Switch/AP visibility still looks normal +- [ ] No management-plane surprise coincided with the server batch + +--- + +## Stop conditions + +Stop and unwind if any of these are true: +- [ ] PD is not reachable from Rocinante +- [ ] NFS/share recovery is unclear after a short wait +- [ ] You cannot tell whether the problem is routing, DNS, or service binding +- [ ] UniFi/admin access is degraded at the same time +- [ ] You are guessing instead of validating + +--- + +## Reverse-order rollback strip + +If the batch is bad and the reason is not immediately obvious: + +1. Revert PD port/profile first. + - [ ] PD returns to prior lane/profile + - [ ] PD admin path restored +2. Revert Serenity port/profile second. + - [ ] Serenity returns to prior lane/profile +3. Revert Nomad port/profile third. + - [ ] Nomad returns to prior lane/profile +4. Re-check: + - [ ] Rocinante -> PD + - [ ] Rocinante -> Serenity + - [ ] Rocinante -> Nomad + - [ ] UniFi/admin access stable again + +Do not continue the broader migration until this set is stable. + +--- + +## When this batch counts as successful + +- [ ] Nomad moved successfully +- [ ] Serenity moved successfully +- [ ] PD moved successfully +- [ ] Rocinante still has the admin path you need +- [ ] Shares/services recovered cleanly enough to continue +- [ ] No emergency broad firewall workaround was needed + +If all six are true, move on. +If not, stop pretending and fix or roll back first. diff --git a/home/doris-dashboard/docs/unifi-live-preflight-snapshot-2026-05-22.md b/home/doris-dashboard/docs/unifi-live-preflight-snapshot-2026-05-22.md new file mode 100644 index 0000000..1c5f838 --- /dev/null +++ b/home/doris-dashboard/docs/unifi-live-preflight-snapshot-2026-05-22.md @@ -0,0 +1,117 @@ +# UniFi Live Preflight Snapshot + +Date: 2026-05-22 +Mode: read-only live pull from PD using the Doris UniFi operator account +Controller: `https://10.5.0.1` +Site: `default` + +## Why this exists + +This is a fresh, low-risk preflight snapshot taken after the earlier recon so tomorrow does not start from stale assumptions. + +## Networks + +- Internet 1 -> purpose=`wan` vlan=`untagged` subnet=`n/a` +- Internet 2 -> purpose=`wan` vlan=`untagged` subnet=`n/a` +- One-Click VPN -> purpose=`remote-user-vpn` vlan=`untagged` subnet=`192.168.2.1/24` +- Proton Chicago -> purpose=`vpn-client` vlan=`untagged` subnet=`10.2.0.2/32` +- Slate 7 -> purpose=`remote-user-vpn` vlan=`untagged` subnet=`192.168.3.1/24` +- Old IoT -> purpose=`corporate` vlan=`2` subnet=`192.168.1.1/24` +- Trusted -> purpose=`corporate` vlan=`51` subnet=`10.5.1.1/24` +- IoT -> purpose=`corporate` vlan=`510` subnet=`10.5.10.1/24` +- Camera -> purpose=`corporate` vlan=`520` subnet=`10.5.20.1/24` +- Guest -> purpose=`guest` vlan=`590` subnet=`10.5.90.1/24` +- Management -> purpose=`corporate` vlan=`untagged` subnet=`10.5.0.1/24` + +## SSIDs currently enabled + +- `CIA Via` +- `UNEF's Playhouse` +- `Whiskey Neat Fuck Ice` +- `Yer a Wifi Harry` + +## Port profiles currently present + +- `Camera` +- `IoT` +- `Management` +- `Old IoT` +- `Trusted` + +Operator implication: +- `Servers` still does not exist as a live port profile/object visible in this pull. +- Tomorrow must not begin server moves until `Servers` is created/confirmed. + +## UniFi device state + +- `U6 LR` -> type=`uap` ip=`10.5.0.20` state=`0` version=`6.7.41.15623` +- `U7 Pro` -> type=`uap` ip=`10.5.0.21` state=`1` version=`8.5.21.18681` +- `UDM Pro - Old` -> type=`udm` ip=`69.166.182.157` state=`1` version=`5.1.11.33023` +- `USW Pro HD 24` -> type=`usw` ip=`10.5.0.135` state=`1` version=`7.4.1.16850` +- `USW-24-PoE` -> type=`usw` ip=`10.5.0.10` state=`1` version=`7.4.1.16850` + +Interpretation: +- U6 LR still appears disconnected/offline in the current API view. +- The active wireless load is still effectively on the U7 Pro. + +## Current client counts by network + +- Camera: 1 +- Management: 2 +- Old IoT: 14 +- Trusted: 14 + +## Critical named clients for tomorrow + +- `PlausibleDeniability` -> Trusted -> `10.5.1.6` +- `Serenity` -> Trusted -> `10.5.1.5` +- `Nomad` -> Trusted -> `10.5.1.16` +- `Rocinante` -> Trusted -> `10.5.1.112` +- `FlyingDutchman` -> Trusted -> `10.5.1.111` +- `MyQ-29B` -> Old IoT -> `192.168.1.130` +- `Samsung-FamilyHub` -> Old IoT -> `192.168.1.149` +- `LG_Smart_Dryer2_open` -> Old IoT -> `192.168.1.186` +- `LG_Smart_Laundry2_open` -> Trusted -> `10.5.1.184` +- `Main-Floor` -> Old IoT -> `192.168.1.102` +- `Upstairs` -> Old IoT -> `192.168.1.131` +- `Google-Home-Mini` -> Old IoT -> `192.168.1.185` +- `front-doorbell` -> Camera -> `10.5.20.217` + +## Management offenders still present + +- `58:d6:1f:54:e5:6d` -> `10.5.0.123` +- `58:d6:1f:54:e5:af` -> `10.5.0.189` + +These are still the two management-lane ESP-class offenders to identify or evict. + +## Fresh anomalies vs the earlier planning assumptions + +1. `LG_Smart_Laundry2_open` is currently on `Trusted` at `10.5.1.184`. + - This was not part of the original Old IoT-first migration set. + - Decide tomorrow whether it belongs on IoT or whether it is a separate harmless laundry app endpoint you intentionally trust. + +2. `U6 LR` still shows state `0`. + - Treat it as optional only if needed, not as assumed active infrastructure. + +3. The controller still shows zero custom firewall groups/rules/routing objects from the earlier read-only recon. + - Firewall staging is still basically greenfield. + +## Safe prep work completed tonight + +- verified UniFi read access still works from PD +- refreshed live device/client counts +- confirmed named host placement for the server batch and Old IoT migration set +- confirmed management offenders remain present +- confirmed U6 LR still appears offline/disconnected +- captured a raw pre-write baseline of `networkconf` and `portconf` at `/home/fizzlepoof/repos/truenas-stacks/home/doris-dashboard/docs/baselines/unifi-object-baseline-2026-05-22-024653.json` +- authored an idempotent staging helper at `/home/fizzlepoof/repos/truenas-stacks/automation/bin/unifi_stage_low_risk_objects.py` +- copied that helper to PD at `/mnt/docker-ssd/docker/compose/automation/bin/unifi_stage_low_risk_objects.py` +- confirmed the current Doris UniFi account is still read-only for writes: `POST /rest/networkconf` returns `403 Forbidden` + +## Recommended no-surprise start conditions for tomorrow + +- create/confirm `Servers` object/profile before any server port move +- keep Rocinante on Trusted as the live control box until the Nomad/Serenity/PD wave is complete +- leave FlyingDutchman on Trusted +- treat Google as in-scope only through narrow reversible tests +- do not assume U6 LR is usable unless you explicitly verify it in the UI diff --git a/home/doris-dashboard/docs/unifi-readonly-recon-2026-05-22.md b/home/doris-dashboard/docs/unifi-readonly-recon-2026-05-22.md new file mode 100644 index 0000000..19d8ebb --- /dev/null +++ b/home/doris-dashboard/docs/unifi-readonly-recon-2026-05-22.md @@ -0,0 +1,138 @@ +# UniFi Read-Only Recon Notes + +Date: 2026-05-22 +Mode: read-only only +Auth status: verified working with upgraded Doris admin login + +## Scope completed +- authenticated login verified +- network object inventory read +- SSID inventory read +- port profile inventory read +- client inventory read +- AP/client mapping read +- wired switch-port/client mapping read +- firewall/policy endpoint probes read + +## High-value findings + +### 1. Firewall model / current policy state +- `firewallrule`: 200, count 0 +- `firewallgroup`: 200, count 0 +- `firewallzone`: 400 +- `trafficrule`: 400 +- `routing`: 200, count 0 + +Interpretation: +- there are currently no custom classic firewall rules in the Network app dataset returned by the API +- no custom firewall groups are defined there either +- zone/traffic-rule endpoints are not active in the way this API helper expected +- practical implication: tomorrow’s segmentation policy work is largely greenfield, not cleanup of a big custom ruleset + +### 2. Current network objects +- Management -> corporate -> untagged -> `10.5.0.1/24` +- Trusted -> corporate -> VLAN 51 -> `10.5.1.1/24` +- Old IoT -> corporate -> VLAN 2 -> `192.168.1.1/24` +- IoT -> corporate -> VLAN 510 -> `10.5.10.1/24` +- Camera -> corporate -> VLAN 520 -> `10.5.20.1/24` +- Guest -> guest -> VLAN 590 -> `10.5.90.1/24` + +### 3. Current SSIDs +- `CIA Via` -> Old IoT +- `UNEF's Playhouse` -> Camera +- `Whiskey Neat Fuck Ice` -> Trusted +- `Yer a Wifi Harry` -> Trusted + +### 4. Management offenders +Both Management-lane clients are Wi‑Fi clients on the U7 Pro and both are unnamed `espressif` devices on the default `UniFi Wireless` SSID. + +- `espressif` -> `10.5.0.123` -> U7 Pro -> SSID `UniFi Wireless` +- `espressif` -> `10.5.0.189` -> U7 Pro -> SSID `UniFi Wireless` + +Interpretation: +- these are almost certainly stray ESP-class smart devices and exactly the kind of junk that should not live on Management +- tomorrow we should either identify and move them, or quarantine them if they are brittle + +### 5. Old IoT live inventory +All are currently on SSID `CIA Via` via the U7 Pro. + +Likely migrate-first / easy-value: +- `MyQ-29B` -> `192.168.1.130` +- `Main-Floor` ecobee -> `192.168.1.102` +- `Upstairs` ecobee -> `192.168.1.131` +- `LG_Smart_Dryer2_open` -> `192.168.1.186` +- `Samsung-FamilyHub` -> `192.168.1.149` + +Likely migrate-later / discovery pain: +- `Google-Home-Mini` -> `192.168.1.185` +- `3c:8d:20:f3:92:36` -> Google -> `192.168.1.192` +- `90:ca:fa:b6:7f:6e` -> Google -> `192.168.1.129` + +Likely quarantine until identified better: +- `5c:61:99:41:73:40` -> Cloud Network Technology Singapore -> `192.168.1.172` +- `60:74:f4:54:fd:ec` -> Private -> `192.168.1.136` +- `60:74:f4:7b:6a:11` -> Private -> `192.168.1.117` +- `c0:f5:35:20:5d:94` -> AMPAK -> `192.168.1.183` +- `d4:ad:fc:60:90:6a` -> Shenzhen Intellirocks -> `192.168.1.100` +- `d4:ad:fc:ea:7f:65` -> Shenzhen Intellirocks -> `192.168.1.101` + +### 6. Camera lane live state +- `front-doorbell` is already on Camera network at `10.5.20.217` + +Interpretation: +- the doorbell is already where it belongs logically +- tomorrow’s security cleanup work is mainly about chimes and future policy tightening + +### 7. Trusted lane still carrying infrastructure-class hosts +Trusted currently includes: +- `PlausibleDeniability` -> `10.5.1.6` +- `Serenity` -> `10.5.1.5` +- `Nomad` -> `10.5.1.16` +- `Rocinante` -> `10.5.1.112` +- `FlyingDutchman` -> `10.5.1.111` + +Interpretation: +- tomorrow’s server-lane work is real and necessary +- these should not stay blended into the human client lane long-term + +### 8. AP/client mapping +- `U7 Pro`: 26 clients +- `wired/unknown`: 5 clients + +Interpretation: +- almost all current active wireless load is concentrated on the U7 Pro +- the U6 LR is presently not carrying client load + +### 9. Wired client to switch-port mapping +On `USW Pro HD 24`: +- port 2 -> `Rocinante` +- port 3 -> `FlyingDutchman` +- port 22 -> `Nomad` +- port 23 -> `PlausibleDeniability` +- port 24 -> `Serenity` + +### 10. Important switch-port assignments +On `USW Pro HD 24`: +- port 1 -> profile `Management` -> `U7 Pro (Upstairs)` +- port 22 -> profile `Trusted` -> `Nomad` +- port 23 -> profile `Trusted` -> `Docker Server` / PD +- port 24 -> profile `Trusted` -> `Serenity 10G (1)` +- port 27 -> profile `Management` -> up at 10G +- port 3 -> profile `Trusted` -> `FlyingDutchman` +- port 2 -> up at 2.5G with no explicit profile shown -> `Rocinante` + +On `USW-24-PoE`: +- port 24 -> profile `Management` -> up at 1G +- many inactive preassigned ports exist for `IoT`, `Old IoT`, and `Management` + +Interpretation: +- the core server moves tomorrow are mechanically straightforward because their live ports are visible +- PD / Serenity / Nomad / FlyingDutchman are explicitly sitting on Trusted access profiles today +- Rocinante needs a quick extra look because the client is live on port 2 but that port did not show an explicit profile name in the returned record + +## Recommended next actions for tomorrow +1. identify the two Management `espressif` devices before or during the window +2. move/changeprofile for server ports on the USW Pro HD 24 one at a time +3. treat Google devices as late-batch or defer +4. treat unknown Old IoT clients as quarantine by default, not migration by default +5. keep Camera cleanup focused on chimes and policy, because the doorbell is already in the correct lane diff --git a/home/doris-dashboard/docs/usw-pro-hd-24-cutover-port-sheet.md b/home/doris-dashboard/docs/usw-pro-hd-24-cutover-port-sheet.md new file mode 100644 index 0000000..b4c0838 --- /dev/null +++ b/home/doris-dashboard/docs/usw-pro-hd-24-cutover-port-sheet.md @@ -0,0 +1,209 @@ +# USW Pro HD 24 Exact Change Sheet + +> For Doris: this is the mechanical switch-port sheet for tomorrow. Do not freestyle. Change one thing, validate, then continue. + +Source basis: +- live UniFi read-only recon on 2026-05-22 +- /home/fizzlepoof/repos/truenas-stacks/home/doris-dashboard/docs/unifi-readonly-recon-2026-05-22.md +- /home/fizzlepoof/repos/truenas-stacks/home/doris-dashboard/docs/network-redesign-implementation-runbook.md + +## Core principle +- Uplink/AP/infra ports stay Management or trunked as appropriate. +- Server-class hosts leave Trusted and move to Servers. +- Unknown live ports get eyeballed before change. +- Do not bulk-edit profiles blindly. +- Rocinante is the live operator box, so keep it stable until the main server batch is complete or an alternate admin path is proven. +- Nomad, Serenity, and PD may need to move as one controlled wave because of NFS/shared-service coupling; validate the batch immediately. + +--- + +## 1. Desired profile set for tomorrow + +Current relevant profiles seen live: +- Management +- Trusted +- IoT +- Camera +- Old IoT + +Needed for tomorrow: +- Management +- Trusted +- Servers +- IoT +- Camera +- Guest +- Old IoT +- trunk/uplink profile if you choose to normalize uplinks now + +If `Servers` does not exist yet: +- create it before changing server ports +- use it as the target for the core host ports below + +## 2. USW Pro HD 24 live ports that matter + +### Port 1 +- Name: `U7 Pro (Upstairs)` +- Current profile: `Management` +- Link: up +- Speed: 2.5G +- Role: AP uplink / infrastructure +- Tomorrow action: leave on Management unless/until you replace with an explicit trunk/uplink profile +- Risk: high if changed incorrectly +- Validation after any change: + - U7 Pro remains online in UniFi + - wireless clients stay associated + - management plane remains reachable + +### Port 2 +- Name: `Port 2` +- Current observed live client: `Rocinante` +- Current explicit profile visibility: not clearly returned by API +- Link: up +- Speed: 2.5G +- Role: server/core host +- Tomorrow action: inspect in UniFi UI first, confirm current native network/profile, but keep Rocinante in place as the live operator station until the Nomad/Serenity/PD batch is done or a backup admin path is proven +- Risk: medium because profile visibility was ambiguous in API data +- Validation after move: + - Rocinante gets expected Servers subnet/gateway + - reachable from Trusted as intended + - any expected service ports work + +### Port 3 +- Name: `FlyingDutchman` +- Current profile: `Trusted` +- Link: up +- Speed: 2.5G +- Role: human endpoint / gaming PC unless proven otherwise +- Tomorrow action: leave on `Trusted` tomorrow unless you deliberately reclassify it later; default assumption is that it should stay with trusted human endpoints +- Risk: medium +- Validation after move: + - host gets expected subnet/gateway + - reachable from Trusted as intended + - only move if this box is actually supposed to live with the homelab service lane + +### Port 22 +- Name: `Port 22` +- Current live client: `Nomad` +- Current profile: `Trusted` +- Link: up +- Speed: 2.5G +- Role: core host +- Tomorrow action: move to `Servers` +- Risk: medium +- Validation after move: + - Nomad gets expected Servers address + - Doris pages / services expected on Nomad still reachable from Trusted + +### Port 23 +- Name: `Docker Server` +- Current live client: `PlausibleDeniability` +- Current profile: `Trusted` +- Link: up +- Speed: 10G +- Role: primary core server +- Tomorrow action: move to `Servers` +- Risk: high-ish because this is central infrastructure +- Validation after move: + - PD gets expected Servers address + - SSH to `pd` still works from Trusted/admin lane + - critical services still reachable from Trusted + - no surprise breakage to shared homelab functions + +### Port 24 +- Name: `Serenity 10G (1)` +- Current live client: `Serenity` +- Current profile: `Trusted` +- Link: up +- Speed: 10G +- Role: core host +- Tomorrow action: move to `Servers` +- Risk: medium-high +- Validation after move: + - Serenity gets expected Servers address + - intended services still reachable from Trusted + +### Port 27 +- Name: `Port 27` +- Current profile: `Management` +- Link: up +- Speed: 10G +- Role: likely infra/uplink +- Tomorrow action: do not touch unless you have positively identified what it is +- Risk: very high +- Validation if ever touched: + - upstream/downstream infra remains healthy + - no switch isolation or mgmt loss + +## 3. Recommended exact move order for server ports + +Use this order, not random order: + +1. Port 2 -> Rocinante +- inspect/profile-confirm first +- do not move it early if it is your live admin path + +2. Port 22 -> Nomad +- first member of the controlled three-host batch + +3. Port 24 -> Serenity +- second member of the controlled three-host batch + +4. Port 23 -> PlausibleDeniability +- third member of the controlled three-host batch + +5. Port 3 -> FlyingDutchman +- default action is no move; only revisit if you intentionally decide it belongs in Servers after all + +If you want the most conservative variant: +- leave Rocinante on Trusted during the live work +- move Nomad, Serenity, and PD as the only server-lane batch +- leave FlyingDutchman on Trusted + +## 4. Per-port execution script + +For each independent server port or server batch: +1. open switch port in UniFi UI +2. confirm live client identity matches expectation +3. screenshot current state +4. if touching Nomad/Serenity/PD, pre-stage the exact order before the first change +5. change profile from `Trusted` (or current) -> `Servers` +6. wait for link/client/IP reassignment +7. verify host gets expected subnet/gateway +8. verify reachability from Rocinante/admin path +9. verify expected service behavior +10. if it was the three-host batch, verify cross-host dependencies before anything else +11. only then proceed to next port + +## 5. Ports to explicitly leave alone tomorrow + +Leave alone unless a later issue forces action: +- Port 1 (U7 Pro upstairs) +- Port 27 (likely infra/uplink) +- all inactive ports +- any uplink/trunk-ish port not positively identified + +## 6. Potential follow-up ports outside the main switch + +USW-24-PoE notable live port: +- Port 24 -> profile `Management` -> link up at 1G +- Tomorrow action: read-only verify what it is before touching +- Do not change it casually during the same window unless you have confirmed its role + +## 7. Success criteria for this port sheet + +Successful tomorrow outcome: +- Nomad, Serenity, and PD are no longer sitting on Trusted access ports +- Rocinante is either still the live admin box by deliberate choice or moved cleanly after the main batch is complete +- FlyingDutchman stays on Trusted unless deliberately reclassified with reason +- AP/uplink/infra ports remain stable +- no server move proceeds without validation + +## 8. Red flags + +Stop immediately if: +- a moved host does not pull the expected subnet +- SSH/admin access path disappears unexpectedly +- a port identity in the UI does not match expected host +- changing one port breaks unrelated infra +- PD move causes unclear blast radius and you cannot explain it within a couple minutes diff --git a/home/doris-dashboard/public/network-redesign.html b/home/doris-dashboard/public/network-redesign.html new file mode 100644 index 0000000..a0db51c --- /dev/null +++ b/home/doris-dashboard/public/network-redesign.html @@ -0,0 +1,642 @@ + + + + + + Doris Network Redesign Plan + + + +
+
+
+
+

Operator evidence board

+

Home Network Redesign

+

This is the proposed future-state design for your house and homelab: six purposeful VLANs, a real camera/security lane now instead of later, cleaner SSIDs, stricter policy boundaries, and a staged migration path that avoids lighting the place on fire.

+ +
+
+ 6 target VLANs + Management, Trusted, Servers, IoT, Guest, Cameras/Security. No more historical junk lanes. +
+
+ 1 legacy VLAN to kill + Old IoT / VLAN 2 goes away after device migration and validation. +
+
+ 2 AP posture + Keep U7 Pro as main AP. Reinstall the U6 LR downstairs if coverage or camera RSSI needs it. +
+
+ Camera growth ready now + Doorbell, chimes, and future camera gear live on a security lane from day one. +
+
+
+
+

Interactive controls

+
+
+
Card filter
+
+ + + + + +
+
+
+
Detail level
+ +
+
+
Operator calls
+
+ Move chimes off Management + Retire VLAN 2 + Split Servers from Trusted + Pre-stage Camera VLAN now +
+
+
+
+
+ +
+
+
+
+

Current to target topology

+

What exists now versus the lane structure I would put in place before you hand me admin access.

+
+
+
+
+ Current Management + UDM, switches, APs, plus two Protect chimes that do not belong there. + 10.5.0.0/24 +
+
+
+ Mgmt VLAN 10 + Infra only: gateway, switches, APs, management IPs, admin surfaces. + 10.5.10.0/24 +
+
+ Trusted + Servers mixed + Main LAN currently holds user clients and service hosts together. + 10.5.1.0/24 +
+
+
+ Trusted VLAN 20 + Phones, desktops, laptops, Steam Deck, personal endpoints. + 10.5.20.0/24 +
+
+ Servers VLAN 30 + PD, Serenity, Nomad, Rocinante, and other service hosts. + 10.5.30.0/24 +
+
+ Old IoT still real + CIA Via on VLAN 2 is still carrying the meaningful smart-home traffic. + 192.168.1.0/24 +
+
+
+ IoT VLAN 40 + All retained smart-home gear lands here after phased migration. + 10.5.40.0/24 +
+
+ Camera lane underused + Doorbell is there, but security gear is not consistently placed yet. + 10.5.20.0/24 now / repurpose later +
+
+
+ Camera VLAN 60 + Doorbell, chimes, future cameras, Protect accessories. Built now, not “someday.” + 10.5.60.0/24 +
+
+ Guest VLAN 50 + Pure internet-only guest lane with client isolation. + 10.5.50.0/24 +
+
+
+ +
+
+
+

Target VLAN plan

+

These cards are the final lane definitions I would work from as your network engineer.

+
+
+
+
+ +
+
+
+

SSID plan

+

Fewer SSIDs, clearer meaning, and explicit temporary lanes only where justified.

+
+
+
+
+ +
+
+
+

Firewall intent matrix

+

Not every protocol spelled out yet, but the policy posture is explicit and sane.

+
+
+
+ +
+ +
+
+
+

Migration phases

+

Ordered to reduce blast radius: infrastructure first, then servers, then security, then IoT cleanup.

+
+
+
+
+ +
+
+
+

Wi-Fi coverage and AP posture

+

The downstairs U6 LR is part of the plan if coverage or camera RSSI says it should be.

+
+
+
+
+

Coverage sketch

+
+
+
+
Upstairs / main coverage zone / existing U7 Pro area
+
Downstairs / camera + IoT pressure zone / candidate U6 LR restore point
+
U7
+
U6
+
+ +
+
+

AP guidance

+
+ Preferred: wired backhaul + Delay RF micro-tuning +
+
    +
  • Keep the U7 Pro as the main upstairs AP.
  • +
  • Reinstall the U6 LR downstairs if validation shows weak coverage or poor roaming.
  • +
  • Bias cameras toward wired PoE whenever practical; do not solve camera growth with “more Wi-Fi” by default.
  • +
  • Put both AP management interfaces on Management VLAN 10.
  • +
+
+ Why not over-tune on day one? +

Because the real first win is correct segmentation and sane physical placement. Fancy channel math before the VLAN model is cleaned up is premature wizardry.

+
+
+
+
+ +
+
+
+

Decision summary

+

The crisp operator answer.

+
+
+
+
+

Best-fit final shape

+
    +
  • Management
  • +
  • Trusted
  • +
  • Servers
  • +
  • IoT
  • +
  • Guest
  • +
  • Cameras / Security
  • +
+
+
+

Things to delete

+
    +
  • Old IoT VLAN 2
  • +
  • Stale access-port archaeology
  • +
  • Non-infra devices on Management
  • +
  • Duplicate SSIDs without a real policy reason
  • +
+
+
+

Security posture

+
    +
  • IoT and Cameras default-deny toward the rest of the LAN
  • +
  • Trusted is the operator lane
  • +
  • Servers expose only explicit services
  • +
  • Guest is internet-only and isolated
  • +
+
+
+
+
+
+ + + + diff --git a/home/doris-dashboard/public/network-web.html b/home/doris-dashboard/public/network-web.html new file mode 100644 index 0000000..3f2dd9a --- /dev/null +++ b/home/doris-dashboard/public/network-web.html @@ -0,0 +1,641 @@ + + + + + + Doris Network Web + + + +
+
+
+

Operator map / graphical redesign artifact

+

Network Web v2

+

Now it is the actual operator map instead of just a conceptual graph. Same core redesign, but with three extra layers: physical/topology intent, firewall-flow reasoning, and staged migration sequencing. Click nodes or switch modes to inspect where the pain is and what the end state should look like.

+
+ 6 target VLANs + Topology overlay + Firewall flow layer + 4-stage migration path + Optional downstairs U6 LR +
+
+
+
Topology layerShows the cabling/placement story: operator, gateway, switch fabric, APs, and where endpoints really hang.
+
Firewall layerShows allow, deny, and exception paths so policy intent is visible instead of implied.
+
Migration layerShows what moves first, what is annoying, and what can wait until the end.
+
Operator mapUse this to reason about design, staging, and “what breaks if I move this” before touching UniFi.
+
+
+ +
+
+
+ + + + + + + +
+
+ + + + + + + + +
+ + + + + + + + + + + + + + + + Operator / routing core + Target VLANs and policy lanes + Endpoints, legacy scar tissue, migration pressure, and future growth + + + + + + +
+
+
Stage 1
+

Stop obvious sins

+

Move Protect chimes out of Management, stand up Camera VLAN 60, and make Management clean again.

+
+
+
Stage 2
+

Split infrastructure from life

+

Move PD, Serenity, Nomad, and Rocinante into Servers VLAN 30 so trusted clients stop sharing a lane with infrastructure.

+
+
+
Stage 3
+

Drain old IoT

+

Migrate ecobees, appliances, and low-drama devices first. Leave Google discovery weirdness for last inside IoT.

+
+
+
Stage 4
+

Refine radio and policy

+

Decide whether the downstairs U6 LR returns, clean up SSIDs, then tune firewall exceptions with evidence instead of superstition.

+
+
+ +
+
+

Topology interpretation

+

The UDM is routing brains, the USW is the practical center of gravity, and the APs are infrastructure nodes carrying policy-bound SSIDs. That means client traffic categories should be separated by VLAN intent, not by whatever happened to be easiest last year.

+
+
+

Firewall interpretation

+

Trusted gets explicit admin reach into Management and Servers. IoT and Cameras only get DNS/NTP/internet plus tightly scoped local-service exceptions. Guest gets nothing but internet. Old IoT gets a one-way ticket to deletion.

+
+
+ +
+
+

What this graph is saying now

+
    +
  • Management is not a junk drawer.
  • +
  • Servers are infrastructure and deserve their own lane.
  • +
  • Cameras/security is a first-class lane because future growth is already obvious.
  • +
  • Migration difficulty lives mostly in discovery-heavy IoT, not in the easy appliances.
  • +
+
+
+

What changes first

+
    +
  • Move chimes out of Management.
  • +
  • Create Camera VLAN 60 now.
  • +
  • Pull core servers out of Trusted.
  • +
  • Drain and kill Old IoT / VLAN 2.
  • +
+
+
+
+ + +
+
+ + + + diff --git a/monitoring/DEPLOY.md b/monitoring/DEPLOY.md index 77d66f6..ae82fe7 100644 --- a/monitoring/DEPLOY.md +++ b/monitoring/DEPLOY.md @@ -78,9 +78,9 @@ cd /mnt/docker-ssd/docker/compose/monitoring ```bash docker ps --filter name=prometheus --filter name=grafana --filter name=loki --filter name=promtail --filter name=node-exporter --filter name=cadvisor -curl -fsS http://10.5.1.6:9090/-/ready -curl -fsS http://10.5.1.6:9100/metrics | head -10 -curl -fsS http://10.5.1.6:9090/api/v1/targets | python3 -m json.tool | head -60 +curl -fsS http://10.5.30.6:9090/-/ready +curl -fsS http://10.5.30.6:9100/metrics | head -10 +curl -fsS http://10.5.30.6:9090/api/v1/targets | python3 -m json.tool | head -60 sudo docker inspect -f '{{.State.Status}}' loki ``` diff --git a/monitoring/prometheus.yml b/monitoring/prometheus.yml index 0e2c619..86a820c 100644 --- a/monitoring/prometheus.yml +++ b/monitoring/prometheus.yml @@ -25,13 +25,13 @@ scrape_configs: params: format: [prometheus] static_configs: - - targets: ['10.5.1.5:19999'] + - targets: ['10.5.30.5:19999'] labels: host: 'serenity' # ------- N.O.M.A.D. (via node-exporter) ------- - job_name: 'nomad-node' static_configs: - - targets: ['10.5.1.16:9100'] + - targets: ['10.5.30.7:9100'] labels: host: 'nomad' diff --git a/pihole/.env.example b/pihole/.env.example index ed15bfb..7f5e222 100644 --- a/pihole/.env.example +++ b/pihole/.env.example @@ -10,7 +10,7 @@ PIHOLE_PASSWORD=CHANGE_ME # Keepalived / VIP (VRRP PASS auth is max 8 chars) LAN_INTERFACE=eno1 -VIP_CIDR=10.5.1.53/24 +VIP_CIDR=10.5.30.53/24 KEEPALIVED_ROUTER_ID=53 KEEPALIVED_STATE=MASTER KEEPALIVED_PRIORITY=100 @@ -19,7 +19,7 @@ KEEPALIVED_NOPREEMPT=true # Nebula Sync (PD primary -> replicas) NEBULA_SYNC_PRIMARY=http://127.0.0.1:8953|CHANGE_ME -NEBULA_SYNC_REPLICAS=http://10.5.1.16:8954|CHANGE_ME +NEBULA_SYNC_REPLICAS=http://10.5.30.7:8954|CHANGE_ME NEBULA_SYNC_FULL_SYNC=false NEBULA_SYNC_RUN_GRAVITY=true NEBULA_SYNC_CRON=0 3 * * * diff --git a/pihole/README.md b/pihole/README.md index 3eb2a2b..02ee60e 100644 --- a/pihole/README.md +++ b/pihole/README.md @@ -5,7 +5,7 @@ Primary-node stack for the Pi-hole HA plan. - **Repo stack path:** `/home/fizzlepoof/repos/truenas-stacks/pihole` - **Live PD stack path:** `/mnt/docker-ssd/docker/compose/pihole` - **Node role:** PD primary / MASTER -- **VIP target:** `10.5.1.53/24` +- **VIP target:** `10.5.30.53/24` This stack intentionally covers the **PD primary** plus PD-hosted sync: - Pi-hole @@ -47,14 +47,14 @@ If that validates cleanly, bring it up: - Unbound container: `unbound-pihole-primary` - Keepalived container: `keepalived-pihole-primary` - Nebula Sync container: `nebula-sync` - - LAN admin URL: `http://10.5.1.6:8953/admin/` + - LAN admin URL: `http://10.5.30.6:8953/admin/` - **NOMAD backup** - service root: `/opt/pihole-nomad` - Pi-hole container: `pihole-nomad` - Unbound container: `unbound-pihole-nomad` - - Keepalived container: `keepalived-pihole-nomad` - - LAN admin URL: `http://10.5.1.16:8954/admin/` -- **VIP for client DNS:** `10.5.1.53` + - Keepalived container: `keepalived-pihole-nomad` (staged, disabled by default) + - LAN admin URL: `http://10.5.30.7:8954/admin/` +- **VIP for client DNS:** `10.5.30.53` - **Shared Pi-hole admin password:** stored in the local `.env` files; Nebula and both nodes use the same current value. ## Important notes @@ -64,6 +64,7 @@ If that validates cleanly, bring it up: - `FTLCONF_dns_listeningMode=LOCAL` is set because TrueNAS binds loopback on port 53. - Pi-hole NTP is explicitly disabled here because PD host `chronyd` already owns UDP 123. - Nebula Sync runs on PD and uses `NEBULA_SYNC_PRIMARY` / `NEBULA_SYNC_REPLICAS` from `.env`. -- Current replica target is NOMAD at `http://10.5.1.16:8954`. +- Current replica target is NOMAD at `http://10.5.30.7:8954`. +- NOMAD no longer participates in the old Trusted-side `10.5.30.53/24` Keepalived VIP after moving to Servers VLAN. Re-enable Keepalived there only after a same-subnet HA design exists. - Nebula Sync is intentionally set to selective mode with gravity sync toggles enabled for groups, adlists, and domain lists so replica-specific runtime/network settings are not clobbered. - `keepalived.conf` is generated and should stay local/untracked. diff --git a/pihole/bin/prepare_pd.sh b/pihole/bin/prepare_pd.sh index 195bf38..d1e6490 100755 --- a/pihole/bin/prepare_pd.sh +++ b/pihole/bin/prepare_pd.sh @@ -12,8 +12,21 @@ if [[ ! -f "$ENV_FILE" ]]; then fi set -a -# shellcheck disable=SC1090 -source "$ENV_FILE" +eval "$(python3 - "$ENV_FILE" <<'PY' +from pathlib import Path +import shlex +import sys + +env_file = Path(sys.argv[1]) +for raw_line in env_file.read_text(encoding='utf-8').splitlines(): + line = raw_line.strip() + if not line or line.startswith('#') or '=' not in line: + continue + key, value = line.split('=', 1) + value = value.strip().strip('"').strip("'") + print(f"{key.strip()}={shlex.quote(value)}") +PY +)" set +a required_vars=( diff --git a/pihole/nomad/.env.example b/pihole/nomad/.env.example index 139476b..a249d41 100644 --- a/pihole/nomad/.env.example +++ b/pihole/nomad/.env.example @@ -5,7 +5,7 @@ PIHOLE_WEB_PORT=8954 NOMAD_PIHOLE_WEB_PORT=8954 PIHOLE_PASSWORD=CHANGE_ME LAN_INTERFACE=enp5s0 -VIP_CIDR=10.5.1.53/24 +VIP_CIDR=10.5.30.53/24 KEEPALIVED_ROUTER_ID=53 KEEPALIVED_STATE=BACKUP KEEPALIVED_PRIORITY=90 diff --git a/pihole/nomad/README.md b/pihole/nomad/README.md index f731212..24fb309 100644 --- a/pihole/nomad/README.md +++ b/pihole/nomad/README.md @@ -1,11 +1,17 @@ -# NOMAD Pi-hole / Unbound / Keepalived Replica +# NOMAD Pi-hole / Unbound / staged Keepalived Replica - **Repo stack path:** `/home/fizzlepoof/repos/truenas-stacks/pihole/nomad` - **Suggested live stack path:** `/opt/pihole-nomad` -- **Node role:** NOMAD backup / BACKUP1 -- **VIP target:** `10.5.1.53/24` +- **Node role:** NOMAD Pi-hole replica on Servers VLAN +- **Staged future VIP target:** `10.5.30.53/24` - **LAN interface:** `enp5s0` -- **NOMAD LAN IP:** `10.5.1.16` +- **NOMAD LAN IP:** `10.5.30.7` + +Current state: + +- `pihole-nomad` and `unbound-pihole-nomad` stay up normally on NOMAD. +- `keepalived-pihole-nomad` is intentionally disabled by default after NOMAD moved from Trusted to Servers VLAN. +- Enable HA later with `COMPOSE_PROFILES=ha` only after a same-subnet peer and VIP design are ready. Bring-up: @@ -34,7 +40,9 @@ Pi-hole on NOMAD is pinned to `enp5s0` with `FTLCONF_dns_interface=enp5s0` and ` Current live containers: - `pihole-nomad` - `unbound-pihole-nomad` + +Staged but disabled-by-default HA container: - `keepalived-pihole-nomad` Current LAN admin URL: -- `http://10.5.1.16:8954/admin/` +- `http://10.5.30.7:8954/admin/` diff --git a/pihole/nomad/docker-compose.yaml b/pihole/nomad/docker-compose.yaml index f6b5fa0..0eeba02 100644 --- a/pihole/nomad/docker-compose.yaml +++ b/pihole/nomad/docker-compose.yaml @@ -51,6 +51,7 @@ services: image: osixia/keepalived:2.0.20 container_name: keepalived-pihole-nomad network_mode: host + profiles: ["ha"] restart: unless-stopped depends_on: pihole: diff --git a/pihole/nomad/keepalived.conf b/pihole/nomad/keepalived.conf index 2e6049f..c73f329 100644 --- a/pihole/nomad/keepalived.conf +++ b/pihole/nomad/keepalived.conf @@ -23,7 +23,7 @@ vrrp_instance VI_DNS { } virtual_ipaddress { - 10.5.1.53/24 + 10.5.30.53/24 } track_script { diff --git a/pihole/rpi4/README.md b/pihole/rpi4/README.md index 12fb75c..53a8548 100644 --- a/pihole/rpi4/README.md +++ b/pihole/rpi4/README.md @@ -3,7 +3,7 @@ - **Repo stack path:** `/home/fizzlepoof/repos/truenas-stacks/pihole/rpi4` - **Suggested live stack path:** `~/pihole/stack` - **Node role:** BACKUP2 -- **VIP target:** `10.5.1.53/24` +- **VIP target:** `10.5.30.53/24` This node still needs its actual LAN IP confirmed before live deployment. Native keepalived config example is in `keepalived.conf.example`. diff --git a/pihole/rpi4/keepalived.conf.example b/pihole/rpi4/keepalived.conf.example index f24c91c..e961d53 100644 --- a/pihole/rpi4/keepalived.conf.example +++ b/pihole/rpi4/keepalived.conf.example @@ -16,6 +16,6 @@ vrrp_instance VI_DNS { } virtual_ipaddress { - 10.5.1.53/24 + 10.5.30.53/24 } } diff --git a/pihole/serenity/.env.example b/pihole/serenity/.env.example index 6de1fc5..66d3718 100644 --- a/pihole/serenity/.env.example +++ b/pihole/serenity/.env.example @@ -4,7 +4,7 @@ APPDATA_UNBOUND_ROOT=/mnt/user/appdata/unbound-secondary SERENITY_PIHOLE_WEB_PORT=8954 PIHOLE_PASSWORD=CHANGE_ME LAN_INTERFACE=br0 -VIP_CIDR=10.5.1.53/24 +VIP_CIDR=10.5.30.53/24 KEEPALIVED_ROUTER_ID=53 KEEPALIVED_STATE=BACKUP KEEPALIVED_PRIORITY=90 diff --git a/pihole/serenity/README.md b/pihole/serenity/README.md index 3f4fe52..a801c6c 100644 --- a/pihole/serenity/README.md +++ b/pihole/serenity/README.md @@ -3,7 +3,7 @@ - **Repo stack path:** `/home/fizzlepoof/repos/truenas-stacks/pihole/serenity` - **Suggested live stack path:** `/boot/config/plugins/compose.manager/projects/pihole-ha` - **Node role:** Serenity backup / BACKUP1 -- **VIP target:** `10.5.1.53/24` +- **VIP target:** `10.5.30.53/24` - **LAN interface:** `br0` Bring-up: diff --git a/pihole/serenity/keepalived.conf b/pihole/serenity/keepalived.conf index 2e6049f..c73f329 100644 --- a/pihole/serenity/keepalived.conf +++ b/pihole/serenity/keepalived.conf @@ -23,7 +23,7 @@ vrrp_instance VI_DNS { } virtual_ipaddress { - 10.5.1.53/24 + 10.5.30.53/24 } track_script { diff --git a/scripts/setup-secrets-repo.sh b/scripts/setup-secrets-repo.sh index 59482b8..7ac3a51 100644 --- a/scripts/setup-secrets-repo.sh +++ b/scripts/setup-secrets-repo.sh @@ -5,7 +5,7 @@ set -euo pipefail # ── CONFIG ──────────────────────────────────────────────────────────────────── -GITEA_HTTP_URL="http://10.5.1.6:3000" # ← local Gitea HTTP port (check TrueNAS app config) +GITEA_HTTP_URL="http://10.5.30.6:3000" # ← local Gitea HTTP port (check TrueNAS app config) GITEA_SSH_PORT="2222" # ← Gitea SSH port GITEA_USER="fizzlepoof" GITEA_TOKEN="" # ← set this before use; regenerate with write:repository + write:user scopes @@ -61,7 +61,7 @@ else die "Gitea API returned HTTP ${HTTP_CODE}. Check token and URL." fi -REPO_URL="ssh://git@10.5.1.6:${GITEA_SSH_PORT}/${GITEA_USER}/${REPO_NAME}.git" +REPO_URL="ssh://git@10.5.30.6:${GITEA_SSH_PORT}/${GITEA_USER}/${REPO_NAME}.git" # ── 3. Init local repo ──────────────────────────────────────────────────────── info "Setting up local repo at ${LOCAL_PATH}..." @@ -89,7 +89,7 @@ info "Exporting symmetric key to ${KEY_EXPORT_PATH}..." git-crypt export-key "$KEY_EXPORT_PATH" chmod 600 "$KEY_EXPORT_PATH" warn "BACK THIS KEY UP. Without it you cannot decrypt the repo on another machine." -warn " scp root@10.5.1.6:${KEY_EXPORT_PATH} /safe/location/" +warn " scp root@10.5.30.6:${KEY_EXPORT_PATH} /safe/location/" # ── 6. .gitattributes — encrypt everything except meta files ───────────────── info "Writing .gitattributes..." diff --git a/search/.env.example b/search/.env.example index 2ea55e5..2982ec0 100644 --- a/search/.env.example +++ b/search/.env.example @@ -2,7 +2,7 @@ TZ=America/Chicago # Existing SearXNG public/base URL -SEARXNG_BASE_URL=http://10.5.1.6:8888/ +SEARXNG_BASE_URL=http://10.5.30.6:8888/ # Firecrawl API is exposed from the gluetun network namespace on this host port. # 3002 is already occupied by Gitea on PD, so Firecrawl uses 3302 externally. @@ -10,7 +10,7 @@ FIRECRAWL_PORT=3302 FIRECRAWL_INTERNAL_PORT=3002 # Gluetun control UI LAN bind. Keep it on the PD LAN IP, not 0.0.0.0. -GLUETUN_WEBUI_BIND_ADDRESS=10.5.1.6 +GLUETUN_WEBUI_BIND_ADDRESS=10.5.30.6 GLUETUN_WEBUI_PORT=8390 # Gluetun HTTP control API auth for the web UI. diff --git a/search/docker-compose.yaml b/search/docker-compose.yaml index 896bf43..6f2aea6 100644 --- a/search/docker-compose.yaml +++ b/search/docker-compose.yaml @@ -8,7 +8,7 @@ services: volumes: - /mnt/tank/docker/appdata/searxng:/etc/searxng environment: - - SEARXNG_BASE_URL=${SEARXNG_BASE_URL:-http://10.5.1.6:8888/} + - SEARXNG_BASE_URL=${SEARXNG_BASE_URL:-http://10.5.30.6:8888/} cap_drop: - ALL cap_add: @@ -53,7 +53,7 @@ services: depends_on: - search-gluetun ports: - - "${GLUETUN_WEBUI_BIND_ADDRESS:-10.5.1.6}:${GLUETUN_WEBUI_PORT:-8390}:3000" + - "${GLUETUN_WEBUI_BIND_ADDRESS:-10.5.30.6}:${GLUETUN_WEBUI_PORT:-8390}:3000" environment: - GLUETUN_CONTROL_URL=http://search-gluetun:8000 - GLUETUN_API_KEY=${GLUETUN_CONTROL_API_KEY}