For Agents

Living index of themes for this project. Each H2 is a topic; bullets are wikilinks to related notes. Updated by obsidian-documenter when documenting work. Read by historian at bootstrap. Topics kept alphabetical.

Access & Remote Management

  • telep-router — SSH via Tailscale MagicDNS; on macOS a shell wrapper breaks bare ssh, use /usr/bin/ssh explicitly
  • Access — Frigate UI over Tailscale Serve (--https=443127.0.0.1:8971), real LE cert, tailnet-only; iOS Private Relay breaks MagicDNS in Safari
  • 2026-07-24-global-dashboardhome.taild4189d.ts.net, the tailnet landing dashboard linking Kamerák / Tudásbázis / Top képek / Router
  • filestash — Windows drive browser over Tailscale Serve (--https=8445127.0.0.1:8334), tailnet-only
  • Prerequisite router access — mainframe→router SSH added via LAN-only key-only dropbear + /home/levander/.ssh/router_alarm (router was Tailscale-SSH-only, ACL-blocked)

Alerting & Notifications

  • Alerting — Telegram bot “Dezsi az őr” → supergroup; frigate-notify polls the Frigate HTTP API every 15s (no MQTT); dedup is zone-aware
  • 2026-07-17-intruder-alarm — same “Dezsi az őr” bot; auto-arm/disarm + 🚨 BETŐRŐ! intruder escalation (1×day/2×night) + snapshot; sole getUpdates consumer for enrollment/commands
  • 2026-07-23-frigate-viewer-alert — same frigate-notify bot+chat: Telegrams 👁 <device> opened Frigate when someone opens the camera UI; identity from tailscale serve’s X-Forwarded-For → tailscale device name; per-source 5-min session cooldown

Bandwidth & Traffic Metering

  • 2026-07-22-wifi-usage-strip — live per-device WiFi bandwidth on the camwall; wifi-usage.service polls iw dev <ap> station dump every 5s and diffs byte counters into Mbps
  • [[2026-07-22-wifi-usage-strip#2-bytes-come-from-iw-not-iwinfo|2. Bytes come from iw, not iwinfo]] — iwinfo assoclist has no byte counters, iw station dump does; per-device metering needs no nftables accounting, conntrack parsing or nlbwmon, and iw ships on the router
  • SESSION-HANDOVER — the tc HTB 15 Mbit cap on 192.168.1.135, persistent via /etc/hotplug.d/iface/99-throttle-fm; bmon for live per-interface rates

Cameras & Surveillance

  • telep-mainframe — Frigate 0.17.2, ONNX YOLOv9-s on an RTX 3080, events-only recording, birdseye HDMI wall
  • RTSP stream mapTapo TC47 dual-lens: /cam1/ and /cam2/ are the same lens. Fixed = /stream1+/stream2, PTZ = /stream6+/stream7
  • Camera VLAN (telep-cc) — isolated 192.168.30.0/24, hidden SSID, no internet, NTP DNAT’d back
  • 2026-07-17-intruder-alarm — watches Frigate person/car events to escalate intruders and siren the TV while armed
  • 2026-07-18-birdseye-nvenc-blue-glare — the camwall’s live panes play Frigate’s birdseye restream; NVENC encoder corruption there = “blue glare” on the TV wall (isolation method for the whole RTSP→mpv→TV pipeline)
  • 2026-07-22-wifi-usage-strip — the camwall’s bottom strip (y 1880–2140, below TOP KÉPEK): live per-device WiFi rates drawn as an mpv osd-overlay, never over the camera image; uses osd-overlay rather than camwall-reload so playback is untouched
  • 2026-07-23-frigate-viewer-alert — “who is looking at my cameras”: follows Frigate’s nginx access log via docker logs -f, alerts on every UI session by tailnet device name; internal automation (XFF -) excluded by construction

Containers & Docker

  • nvr)frigate (stable-tensorrt image, but onnx detector) + frigate-notify; mount the whole /config dir or the event DB is wiped on recreate
  • filestashmachines/filestash bound to 127.0.0.1:8334, state in filestash_data volume, Windows drive passed in at /mnt/data:ro
  • The Windows drivea bind-mount captures the host dir at container start: mounting /mnt/win after filebrowser started leaves /srv empty inside the container → mount first, then docker restart filebrowser

Documentation & Knowledge Tooling

  • 2026-07-24-knowledgebase — tailnet-only manual library + OCR ingest portal; mkdocs-material static site of OCR’d PDF manuals (folder-tree browse, full-text search, dark mode, lazy images) + a Flask /upload portal that OCRs a PDF into the library
  • How it works (the reusable parts)chunked loading = split each manual by its # h1 headings into many small per-section pages (a 795 KB markdown would be one huge page) + a mkdocs on_post_page hook injecting loading="lazy" into every <img>; folder tree → mkdocs auto-nav; mkdocs builds to a temp dir then os.rename-swaps site/ (atomic, a broken source never takes down the live site)
  • Library expansion (2026-07-24) — the expanded manual library (suzuki-vitara / chevy-tracker / suzuki-sidekick); marker → seed_import.py → build, manuals queued as CHAINED nohup scripts (.done sentinel) to serialize the single GPU; md5-dedup downloads BEFORE OCR
  • 2026-07-24-kickfix-ingest — ingesting a 112 MB offline archive of the dead kick-fix.com repair site (HTML→markdown, 25 topics / 127 articles / 741 images) into docs/kick-fix/; reusable lessons for old table-layout HTML
  • 2026-07-24-kb-vectorize-complete — semantic search over the whole library: 2839 chunks embedded (bge-large-en-v1.5, CPU torch) into Qdrant (kb-qdrant, collection manuals); cross-source search blends official manuals + kick-fix; idempotent per-manual (delete-by-filter + uuid5 ids)
  • 2026-07-24-kb-semantics-phase2-completephase 2 (semantic dedup): fully-local SECTION clustering over the phase-1 vectors + a read-only “Szemantika” explorer, NO LLM. Mean-pool 3252 chunk vectors → ~1248 section vectors → HDBSCAN → 194 clusters / 24 cross-SOURCE / 631 noise; clusters.json regenerated on demand (kbclust.py build), explorer /semantics folded into the existing knowledgebase app (no new service); ranked by cross-source span; labels pluggable behind make_label for a future claude -p relabel; clusters.json = phase 3’s retrieval bundle
  • scanned-pdf-to-markdown-marker — the reusable ~/ocr/venv marker OCR pipeline the knowledgebase ingest calls as a subprocess (no duplicate torch/model install)

File Storage & Sharing

  • filestash — in-browser file browser for the box’s Windows C: drive; NTFS mounted read-only at /mnt/win via ntfs3, served over tailnet at :8445
  • mp4-missing-moov-forensics — a 1.6 GB SplitCam recording served off /mnt/win via Filestash was dead on arrival; how to trace a download’s origin (kMDItemWhereFroms) and chunk-md5 it against the source
  • Drive inventory (2026-07-21) — mainframe drives: nvme0n1 1.8T Windows, nvme1n1 3.6T LUKS+LVM OS, sda 447G unused, sdb = Hiren’s BootCD PE USB stick

Firewall & Security

  • telep-router — zones: lan ACCEPT, wan REJECT + masq + mtu_fix, plus an isolated cams zone with no cams → wan
  • Security Gaps — 8-char WPA3 PSK, dropbear password auth, LuCI on plain HTTP, plaintext DNS, unpatched 24.10.0
  • telep-mainframe — all ports bound to 127.0.0.1; passwordless sudo is an accepted tradeoff; Secure Boot disabled to load the NVIDIA module
  • Prerequisite router access — new LAN-only key-only dropbear on the router (PasswordAuth off) partially closes the “dropbear password auth” gap; bot token read from ~/nvr/frigate-notify/config.yml, never hard-coded
  • 2026-07-24-luks-tpm-autounlock — root LUKS2 now TPM2 auto-unlocks via clevis (keyslot 1 = TPM with empty PCRs, keyslot 0 = passphrase fallback); with empty PCRs + Secure Boot off the tradeoff protects against bare-drive theft, not whole-box theft; header backed up before touching keyslots. Reboot test PASSED in production — self-recovered root through the 3-reboot PSU-load storm (2026-07-24-psu-load-resets)

GPU & Hardware Acceleration

  • telep-mainframe — RTX 3080: ONNX inference ~12ms / ~6% GPU, NVDEC decode (preset-nvidia), nvdec-copy in mpv so the drawtext filter can run
  • TV wall (HDMI)nvidia-drm modeset=1 is mandatory or the 3080 exposes zero display connectors
  • 2026-07-18-birdseye-nvenc-blue-glare — the 3080’s NVENC encoder (not just NVDEC) can wedge and corrupt Frigate’s birdseye restream (h264_nvenc) blue/purple even at GPU idle; docker compose restart frigate clears it, blank hwaccel_args → libx264 for a durable fix (YOLO inference stays on CUDA)
  • 2026-07-24-knowledgebase — the knowledgebase ingest reuses the ~/ocr/venv marker pipeline as a subprocess (marker_single, TORCH_DEVICE=cuda); a single background worker GPU-serialises OCR (one at a time, shared with Frigate) so occasional ingests coexist with detection on the 12 GB card
  • 2026-07-24-kb-vectorize-complete — embeddings run on CPU torch deliberately (sidesteps the CUDA-13-vs-driver-550 trap and avoids GPU contention with OCR + Frigate); but maxing all 16 cores browned out the PSU → cap to 4 threads (see 2026-07-24-psu-load-resets)
  • Running it on a CUDA GPU box (telep-mainframe)driver 550.163.01 caps you at CUDA 12.4: pip’s default cu130 torch can’t init CUDA and PyTorch silently runs on CPU; fix is a cu126 build (minor-version compat), not a driver upgrade — driver 550 is load-bearing for Frigate. Reusable OCR venv at ~/ocr/venv; marker ~3.5 GB VRAM coexists fine with Frigate’s ~2.5 GB on the 12 GB card

Gotchas & Learnings

  • 2026-07-24-kb-semantics-phase2-complete — ⭐ HDBSCAN’s min_samples defaults to min_cluster_size → with min_cluster_size=2 unset, everything becomes noise (0 clusters); set min_samples=1 explicitly. ⭐ Cross-source “span” inflates when your id is <folder>/<manual> and one archive is split into many subfolders (kick-fix = 1 source but ~25 subfolders) → rank span by distinct TOP-LEVEL folder (manual_id.split("/",1)[0]), stoplist boilerplate labels, and treat 51% noise singletons as EXPECTED for a dedup goal; whole pipeline is READ-ONLY scroll → local clusters.json
  • Gotchas — wifi-ifaces declare network='lan wan' (inert, but one step from bridging APs onto WAN); 8-char PSK; double-NAT; /rom at 100% is normal for squashfs
  • filestash — Windows NTFS mount is manual (not fstab, won’t survive reboot); RW would corrupt the live/hibernating Windows install; iOS Private Relay breaks MagicDNS in Safari
  • The Windows driveNVMe device nodes flip across reboots (the Windows NTFS partition moved nvme1n1p2nvme0n1p2): lsblk -f every time, UUID= in fstab; and docker restart filebrowser after mounting or /srv looks empty in the container
  • 2026-07-17-intruder-alarmsleeping iOS phones stay WiFi-associated but stop answering ARP (arp-scan alone false-arms; assoc table saves it); failed scans skipped so blips can’t false-arm; camera SSID telep-cc (phy1-ap1) leaked the camera as an enrollable device; enabling router dropbear rotated its SSH host key
  • 2026-07-18-birdseye-nvenc-blue-glare — camwall “blue glare” is NVENC-encode corruption of Frigate’s birdseye (distinct from the NVDEC decode magenta bug); wedges even at GPU idle; restart Frigate to clear, blank hwaccel_args for a durable libx264 fix; isolate by grabbing a still per pipeline layer
  • mp4-missing-moov-forensicsmoov atom not found does not always mean “untrunc can fix it”: a crashed recorder + NTFS preallocation leaves an mdat full of stale deleted-file bytes (max entropy, zero valid NAL chains) = unrecoverable. Verify the payload parses before investing in recovery tooling; Homebrew has no untrunc formula (2026-07)
  • 2026-07-22-facekom-vpn-exit-node — a wrong peer IP looks exactly like a bad CA bundle when two OpenVPN servers share a subnet+port; a labelled/tab-delimited vendor auth.txt (jelszó:<TAB>pw) silently becomes part of the password → AUTH_FAILED; openvpn-client@.service’s process limit makes up/route-up scripts fatal (fork errno=11); Tailscale exit nodes exclude RFC1918; pin curl --resolve when testing route changes or anycast fools you; diff cert fingerprints before believing a “new” vendor bundle
  • 2026-07-22-wifi-usage-stripmpv destroys osd-overlay entries when the IPC client disconnects (and still replies {"error":"success"}) → the connection must be persistent and the event stream drained; station byte counters are AP-perspective so tx bytes is the device’s download while the WAN isn’t inverted; camwall-reload restarts playback so it’s unusable for live data; SSH ControlMaster masks a deleted key (drop the control socket to test); subprocess.run(text=True) can raise UnicodeDecodeError — a ValueError, not caught by except (SubprocessError, OSError)
  • 2026-07-23-mains-power-shutdownsa journal that stops dead with no Stopping/panic/thermal trace = power was physically removed, not a software or OS shutdown; to separate a PC PSU fault from a mains event, cross-check an independent always-on device’s boot time (date +%s minus /proc/uptime) — same-timestamp death = mains; a desktop that “stays off” while other gear silently recovers is the BIOS Restore on AC Power Loss = stay off policy, not a new failure
  • 2026-07-23-frigate-viewer-alertX-Forwarded-For is the LAST quoted nginx field before request_time=, not the first — leftmost-match is spoofable via a " injected into the User-Agent/Referer, so take findall(...)[-1]; never spawn tailscale status (or any ~10s subprocess) per log line — under an active viewer that’s a self-DoS, rate-limit miss-refreshes to ≤ once/60s; reset a docker logs -f reconnect backoff only after a line is read, and --since 0m so a daemon restart doesn’t replay stale alerts
  • 2026-07-24-luks-tpm-autounlockDebian 13 stock initramfs (initramfs-tools) ignores systemd-cryptenroll TPM2 tokens (update-initramfs warns ignoring unknown option 'tpm2-device' and still prompts at boot) → use clevis (clevis-initramfs+clevis-tpm2, clevis luks bind … tpm2 '{}') for TPM LUKS auto-unlock on Debian; adding ANY keyslot needs the existing passphrase (an agent can’t) and is slow, so an impatient Ctrl-C orphans a systemd-crypt* process that holds the header lock AND /dev/tpmrm0 and blocks the next bind (SIGKILL it, verify fuser/lsof clear); back up the LUKS header before touching keyslots
  • 2026-07-24-knowledgebasetailscale serve --https fails “no TailscaleVarRoot” (TLS handshake 000 even with -k, though TCP/ping succeed) when the second tailscaled has only --state=<file> — it has no var root for the LE cert; fix is --statedir=<dir>. Also: re-ingesting the same folder+name must rmtree the destination first or stale orphan section pages/images linger as ghost pages in nav/search; a bare mkdocs folder URL 404s by design (section pages have no folder index); the # split is on the ORIGINAL doc’s h1 count (few h1s → few coarse pages)
  • 2026-07-24-kickfix-ingestmarkdownify silently drops <img> inside <table> cells (old sites use tables for layout) → .unwrap() the layout containers first + rewrite src to the copied basename in place; “see photo” <a href="X.jpg"> text links are separate from inline <img> and were 365/664 broken → also walk <a href>, copy + rewrite existing images; URL-decode + resolve ../ cross-topic for both src and href; clean-regenerate each run
  • 2026-07-24-psu-load-resetsTWO distinct power problems: mains outage (router dies too → UPS) vs PSU-can’t-sustain-load resets (router stays up → serialize/bigger PSU, a UPS does nothing); the router-uptime cross-check is the discriminator (same dead-stop journal signature either way); the default 16-thread CPU embedding reset the box in 2–6 min, OMP_NUM_THREADS=4 + serialize fixed it
  • shell-gotchas-pgrep-self-matchpgrep -f/pkill -f match your own command line (worst over SSH, where the whole remote string is one argv): a completion monitor reported RUNNING 2.5 h after the job ended, and a pkill -f killed its own shell → exit 143 with the rest of the script silently skipped. Use PIDs, the [m]atch bracket trick, or a real artifact/sentinel
  • The CUDA driver mismatch — torch does not error on a too-new CUDA build, it falls back to CPU; and pip install --index-url .../cu126 is a silent no-op unless you pip uninstall -y torch first (the version constraint is already satisfied). Debian 13 PEP 668 also blocks bare pip install → plain python3 -m venv
  • Since last handover (2026-07-24)on OpenWrt/busybox use setsid, not nohup (absent), to detach a self-reverting service swap (keep a persisted /overlay binary backup + auto-revert so a bad swap can’t lock you out of the router); a hand-installed package (router tailscale 1.98.9) is out-of-band so opkg still records the old version and a future opkg upgrade can clobber it
  • Gotchas — the nine that cost real time: Secure Boot vs the DKMS-signed NVIDIA module; TensorRT detector dead on amd64 in Frigate 0.17; YOLOv9 export broken by torch ≥2.6 (pin 2.5.1); /cam1/ and /cam2/ are the same lens; Tapo silently truncates long passwords (and is Basic-auth only); Frigate’s DB lives at /config/frigate.db; birdseye draws no camera names; birdseye grid geometry follows the canvas aspect; iOS Private Relay vs MagicDNS

Linux Host Ops

  • Host — Debian 13 bare metal; the installer ticks the desktop task by default (systemctl set-default multi-user.target); all sleep/suspend/hibernate targets masked; getty@tty1 disabled so mpv can own the TTY
  • The Windows drive — NTFS Windows C: mounted read-only via in-kernel ntfs3 at /mnt/win; RO because live Windows hibernation would corrupt RW; manual mount, not in fstab (won’t survive reboot); device node unstable → lsblk -f, UUID=
  • Mount procedure (verified 2026-07-21)modprobe ntfs3mount -t ntfs3 -o ro <1.8T ntfs part> /mnt/windocker restart filebrowser; proper fix is a systemd mount unit on UUID= ordered Before=docker.service
  • 2026-07-23-mains-power-shutdowns — proving an overnight shutdown was external mains loss, not the host: the dead-stop journal signature (no Stopping/panic/thermal), ruling out software poweroff (crontabs, poweroff/suspend timers, atq, rtc wakealarm, masked sleep targets) and thermal (grep -iE "thermal|mce|Xid"), and cross-checking the router’s boot epoch

Network Topology

  • telep-router — br-lan 192.168.1.1/24, only 10g-copper has link; WAN is a DHCP client at 192.168.254.2 behind upstream 192.168.254.1double-NAT
  • Camera VLAN (telep-cc) — third segment: 192.168.30.0/24, reachable from lan, reaches nothing
  • 2026-07-22-facekom-vpn-exit-node — a fourth segment reachable on demand: the FaceKom office LAN 192.168.105.0/24 via the exit-vpn container’s OpenVPN tunnel (gateway 92.119.122.32), re-exported to the tailnet
  • “VPN-only hosts” are a routing problem, not DNS — “VPN-only” FaceKom hosts (npm.facekom.net) are public IPs behind an nginx source-IP ACL, identical DNS answers everywhere: 403 from home WAN, 200 from the VPN

OpenWrt Configuration

  • telep-router — UCI-based config; hostname drift fixed 2026-07-13 (uci set system.@system[0].hostname); 174 packages, essentially stock

Power & Reliability

  • 2026-07-23-mains-power-shutdownstelep-mainframe’s recurring overnight shutdowns root-caused to brief mains power interruptions, confirmed by the telep-router (separate PSU) dying at the same instant; the PC only “stays off” because BIOS Restore on AC Power Loss = stay-off. Fix: ~600–900 VA UPS + NUT (logs sags, graceful shutdown); stopgap: flip the BIOS AC-loss policy (physical access only); also rule out an overloaded shared circuit
  • Host — all sleep/suspend/hibernate targets masked (relevant when ruling out a software-initiated poweroff)
  • 2026-07-24-psu-load-resets — a SECOND, distinct power fault: the PSU can’t sustain peak combined i9-12900K + RTX 3080 draw and hard-resets the box under full load (~3× in 30 min); NOT thermal, NOT mains (the router stayed up 14h+); the default 16-thread CPU embedding was the reliable trigger; mitigate with a 4-thread cap + serialized jobs, real fix is a bigger PSU. A UPS does NOT fix this (it fixes mains, 2026-07-23-mains-power-shutdowns)
  • 2026-07-24-luks-tpm-autounlock — the reboot-recovery follow-up: root was LUKS2 passphrase-only so an unattended reboot stalled at the unlock prompt (no dropbear-initramfs); clevis TPM2 auto-unlock now unseals the disk at boot. But auto-unlock only helps once the box BOOTS — the BIOS AC-loss flip is still the prerequisite; Debian stock initramfs ignores systemd-cryptenroll TPM tokens (use clevis); reboot test PASSED in production (self-recovered through the 2026-07-24-psu-load-resets reboot storm)
  • 2026-07-24-psu-load-resets — the PSU-under-load resets, a SECOND power problem distinct from the mains outages; the router-uptime cross-check discriminates the two; a UPS fixes mains, NOT this — serialize heavy jobs / cap CPU threads / bigger PSU
  • Since last handover (2026-07-24) — a daily ~06:00 mains die-off pattern (NEW, UNCONFIRMED): the box + router die together ~06:00 most mornings (Jul 19/23/24) = mains-level, suspect a scheduled utility relay (vezérelt/GEO tariff) or a timed high-draw appliance; confirm via journalctl --list-boots vs router reboots. Also: remote-wake (WOL) is NOT possible as configured (BIOS+NIC WOL off, no router wake tool, MAC unknown after lease expiry) → the real fix is BIOS Restore on AC Power Loss → Power On

Presence & Automation

  • 2026-07-17-intruder-alarm — auto-arming alarm; presence = union of router WiFi association table (iwinfo assoclist) + arp-scan on enp5s0; dormant until ≥1 trusted device, arms after all absent 10 min, disarms instantly on return; TV siren via aplay -D plughw:1,3 (NVIDIA HDMI = ALSA card 1 dev 3)
  • Device naming & enrollment — device names via mDNS avahi-resolve → dnsmasq reverse DNS → eszköz-<last4>; confirm-each enrollment over Telegram inline buttons
  • Untrusted input: LAN device names — reuses the alarm’s ~/alarm/trusted.json names rather than inventing a second naming system; LAN-supplied names are an untrusted input path — ASS-escape and cap at 22 chars, and decode with errors="replace"

Tailscale / Tailnet

  • telep-router — Tailscale hand-upgraded 1.80.3 → 1.98.9 (static arm64 build; opkg still records 1.80.3-r1 → a future opkg upgrade may clobber it, backup /overlay/ts-backup/), nftables mode, 100.115.194.51; the workaround for having no public IP
  • Since last handover (2026-07-24) — router tailscale upgrade done out-of-band via a setsid-detached self-reverting swap (busybox has no nohup); persist a binary backup on /overlay + auto-revert so a bad swap can’t lock you out of the router
  • Access — Tailscale Serve as the TLS front door (needs HTTPS certs enabled in the tailnet admin console); persists across reboots
  • Exposure — one tailscale serve port per app on telep-mainframe: :443 Frigate, :8443 top4 picker, :8445 Filestash
  • homelab — tailnet peer list and cross-project role
  • 2026-07-23-frigate-viewer-alerttailscale serve writes the tailnet client IP into the proxied request’s X-Forwarded-For → a 100.x XFF in an nginx access line is a real human viewer, - is internal automation; map the IP → device name with tailscale status. The basis for identifying who opened the Frigate UI
  • 2026-07-24-knowledgebasea separate tailnet identity on one host = a second tailscaled (--tun=userspace-networking, own --socket/--statedir) then up --hostname= + serve; gives knowledgebase.<tailnet>.ts.net as its own device without a container or GPU passthrough. serve --https needs --statedir not --state=<file> or it fails “no TailscaleVarRoot” (no var root for the LE cert; TLS 000 while TCP/ping succeed)
  • 2026-07-24-global-dashboardhome.taild4189d.ts.net, a second tag:telep node using the same separate-tailscaled-identity pattern; the knowledgebase became its own tag:telep node too
  • Tailscale exit nodeexit-vpn / 100.98.208.98 advertises an exit node + 192.168.105.0/24 subnet route (two separate admin-console checkboxes); off by default, tailscale set --exit-node=exit-vpn to use; exit nodes do not carry RFC1918 traffic; full-tunnel route changes strand tailscaled offline → oneshot unit restarts it after the tunnel

VPN

  • 2026-07-22-facekom-vpn-exit-node — FaceKom “VirtualOffice” OpenVPN on the exit-vpn Incus container: tcp-client to 92.119.122.32:1194, AES-256-CBC/SHA1, full tunnel via redirect-gateway def1, persistent through openvpn-client@farm (symlink into /etc/openvpn/client/ — the tt/ subdir isn’t scanned and openvpn.service is a /bin/true stub)
  • Three root causes (all previously misdiagnosed) — the “bad CA” that stalled this for weeks was really a wrong remote IP; plus wrong ciphers and a label-corrupted password file
  • SESSION-HANDOVER — one-screen current state in the exit-vpn container section

WiFi

  • telep-router — SSID telep1 on both radios; radio0 5 GHz ch36 HE80, radio1 2.4 GHz ch1 HE20
  • Camera VLAN (telep-cc) — hidden SSID, WPA2-PSK forced (Tapo has no WPA3-SAE), client isolation on; hidden-SSID rejoin tested and works
  • Presence detection — AP map used for presence: telep1 = phy0-ap0 (5 GHz) + phy1-ap0 (2.4 GHz); telep-cc camera SSID = phy1-ap1 (excluded)
  • 2026-07-22-wifi-usage-strip — same telep1 AP map for per-station byte counters (iw dev <ap> station dump); telep-cc excluded by construction, never referenced at all