ADR-0017 rev 3: single switch — PE replaces the SG105E, CCTV rides a VLAN-30 trunk on the LAN1 cable

Viktor prefers not running two switches, so the TL-SG105PE takes over
all rack duties (apartment uplink, 4G, UPS, camera PoE) and the CCTV
segment moves onto a managed tagged trunk over the existing LAN1 cable:
pfSense net3 re-pointed from vmbr2 to vmbr0 tag=30 (applied live; same
MAC so vtnet3/dCCTV survived untouched). This is safe where the original
802.1Q rejection was not, because the managed switch is the only device
on eno1 and polices VLAN-30 membership. eno2/vmbr2 kept dormant as the
documented fallback. Old SG105E retires to cold spare; PE inherits
192.168.1.6. Glossary Segment term updated (all three segments are now
bridge-tags feeding untagged pfSense vNICs).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Viktor Barzin 2026-07-03 09:15:52 +00:00
parent 4082934bc1
commit be80ef23bb
4 changed files with 116 additions and 119 deletions

View file

@ -119,8 +119,8 @@ _Avoid_: "external", "outside".
_Avoid_: bare "lan", "private", "intranet". _Avoid_: bare "lan", "private", "intranet".
**Segment**: **Segment**:
One isolated L2/L3 network with pfSense as its gateway — realised as one Proxmox bridge feeding one dedicated pfSense interface (dManagementsVms 10.0.10.0/24, dKubernetes 10.0.20.0/24, dCCTV 10.0.30.0/24). pfSense itself never terminates 802.1Q; any tagging happens on the bridge or a switch. One isolated L2/L3 network with pfSense as its gateway — realised as a Proxmox-bridge-level tag feeding one dedicated untagged pfSense interface (dManagementsVms 10.0.10.0/24 = vmbr1 tag 10, dKubernetes 10.0.20.0/24 = vmbr1 tag 20, dCCTV 10.0.30.0/24 = vmbr0 tag 30). pfSense itself never terminates 802.1Q.
_Avoid_: "VLAN" as the primary name (VLAN 10/20 are informal aliases; dCCTV has no tag on the wire at all). _Avoid_: "VLAN" as the primary name (the tags 10/20/30 are transport detail; the Segment is the concept).
**CCTV segment**: **CCTV segment**:
The untrusted camera **Segment** (`dCCTV`) — devices in it may be pulled from (RTSP/ISAPI) but may initiate nothing except NTP to their gateway. Deliberately outside every trusted source-IP allowlist (ADR-0017). The untrusted camera **Segment** (`dCCTV`) — devices in it may be pulled from (RTSP/ISAPI) but may initiate nothing except NTP to their gateway. Deliberately outside every trusted source-IP allowlist (ADR-0017).

View file

@ -1,6 +1,6 @@
# CCTV segment on a dedicated pfSense leg, not an 802.1Q trunk # CCTV segment: dedicated pfSense interface, VLAN-30 trunk on the LAN1 cable
Status: accepted (2026-07-02) Status: accepted (2026-07-02, rev 3 — single-switch)
![Network topology — dCCTV segment, flows, and camera-day steps](./0017-cctv-segment-topology.svg) ![Network topology — dCCTV segment, flows, and camera-day steps](./0017-cctv-segment-topology.svg)
@ -13,14 +13,22 @@ to pfSense" — but nothing in this network terminates dot1q on pfSense; the
site idiom is one vlan-aware Proxmox bridge → one tagged VM NIC → one clean site idiom is one vlan-aware Proxmox bridge → one tagged VM NIC → one clean
untagged pfSense interface per segment. untagged pfSense interface per segment.
**Decision:** the CCTV segment (`dCCTV`, 10.0.30.1/24) rides a dedicated **Decision (rev 3):** ONE switch — the new TL-SG105PE **replaces** the old
physical leg — R730 `eno2` (spare) → new bridge `vmbr2` → pfSense `net3` garage TL-SG105E (Viktor prefers not running two switches; retired unit
(vtnet3), untagged end-to-end. The new TL-SG105PE PoE switch is a **dedicated becomes a cold spare, its 192.168.1.6 mgmt IP passes to the PE). Five ports,
CCTV island**: camera in a PoE port, one port patched to eno2, no VLAN table all used: apartment uplink, 4G router 192.168.1.7, UPS mgmt (all untagged
at all, mgmt IP inside the segment (10.0.30.6 via Kea). The existing garage VLAN 1), the camera (untagged VLAN 30, PoE), and the **trunk to R730 `eno1`
TL-SG105E (192.168.1.6 — apartment uplink, R730 LAN1, 4G router 192.168.1.7, carrying home LAN untagged + CCTV tagged 30** over the existing LAN1 cable.
UPS mgmt; exactly one free port) is untouched — it has no PoE and no spare pfSense `net3` (vtnet3) sits on `vmbr0` with `tag=30` — exactly the site
port pair, which is also why the two roles cannot share one switch. idiom used for dManagementsVms/dKubernetes (bridge-level tag → clean untagged
vNIC; pfSense still terminates no dot1q itself). The earlier dedicated
`eno2`/`vmbr2` leg is kept **dormant as a fallback** (rev 2 wired it; moving
net3 back to vmbr2 restores pure physical isolation in one `qm set`).
This narrows the earlier 802.1Q objection rather than contradicting it: the
rejection assumed *unmanaged* switches, where any LAN device could inject
tagged frames; with the managed PE as the only device on eno1, VLAN-30
membership is {camera port, trunk port} only, so tag-30 ingress from every
other port — and from the exposed camera cable — is dropped or contained.
Cameras are untrusted: default-deny on dCCTV with a single Cameras are untrusted: default-deny on dCCTV with a single
NTP-to-gateway exception; Frigate (k8s) pulls RTSP in; ha-sofia (192.168.1.8) NTP-to-gateway exception; Frigate (k8s) pulls RTSP in; ha-sofia (192.168.1.8)
may reach ISAPI/RTSP directly; home-LAN clients route in via an AX6000 static may reach ISAPI/RTSP directly; home-LAN clients route in via an AX6000 static
@ -29,29 +37,35 @@ route (10.0.30.0/24 via 192.168.1.2). 10.0.30.0/24 is deliberately NOT in the
## Considered options ## Considered options
- **802.1Q tag over the existing LAN path (eno1/vmbr0)** — rejected: vmbr0 is - **802.1Q over the LAN path behind an UNMANAGED switch** (the original plan
vlan-aware with `bridge-vids 2-4094`, so ANY device on the home LAN could read this way) — rejected: any LAN device could inject tagged frames into
inject tagged frames straight into the camera segment (defeats the vmbr0 (`bridge-vids 2-4094`) and tag-passing through a dumb switch is
cable-tap threat model); tag-passing through the unmanaged SW1 is undefined. Rev 3 adopts the tagged path ONLY because the managed PE now
undefined; and it reconfigures the live bridge carrying the host IP and polices VLAN-30 membership at the single entry point to eno1; no bridge
pfSense WAN. reconfiguration was needed (vmbr0 was already vlan-aware).
- **Dedicated physical leg (eno2 → vmbr2 → net3), one switch per role**
(rev 1/2 as-built) — superseded by rev 3: it forced either a second switch
(6 connections vs 5 ports once the PE also replaced the old switch) or new
hardware. Strongest isolation of all options; kept dormant as the fallback.
- **AX6000 as the camera gateway** — rejected earlier in the design (consumer - **AX6000 as the camera gateway** — rejected earlier in the design (consumer
router, no inter-VLAN firewall). router, no inter-VLAN firewall).
## Consequences ## Consequences
- eno2 is consumed; eno3/eno4 remain the last spare NICs on the R730. - The switch is now single-point and load-bearing for everything in the rack
- Two Easy Smart switches live in the rack: the OLD TL-SG105E at 192.168.1.6 (apartment uplink, pfSense backup-WAN via 4G, UPS mgmt, CCTV) AND its VLAN
remains the load-bearing shared one (apartment uplink, R730 LAN1, pfSense's table + mgmt password are part of the isolation boundary — the Easy Smart
backup-WAN path via the 4G router, UPS mgmt — one port free); the NEW mgmt UI answers on every port, so the password is the gate between a
TL-SG105PE carries only CCTV. The Easy Smart mgmt-answers-on-every-port compromised camera and the switch config. All 5 ports are consumed: the
quirk is therefore contained: the PE's mgmt UI is only L2-adjacent to next camera forces an 8-port PoE upgrade (the wiring plan already fits it).
cameras, and pfSense still gates all L3. - `eno2`/`vmbr2` stay cabled-ready but dormant (fallback to rev 2's physical
- Adding a future camera = one free PoE port on the PE + a Kea leg); eno3/eno4 remain free.
reservation; no pfSense/PVE/VLAN work. - The old TL-SG105E is retired to cold spare; the PE inherits 192.168.1.6
- 2026-07-02 correction: an earlier revision of this ADR described ONE shared (Kea reservation by MAC).
PE switch with a port-based VLAN split — written before discovering the - Revision history (all 2026-07-02): rev 1 assumed one shared PE with a
live 192.168.1.6 device is a separate, older non-PoE TL-SG105E. No VLAN port-VLAN split (conflated the two devices); rev 2 split into two switches
table exists anywhere in the final design. after inspecting 192.168.1.6 (old non-PoE SG105E, 4/5 ports used); rev 3
consolidated back to one switch — the PE replacing the SG105E — per
Viktor's preference, moving CCTV onto a managed tagged trunk.
- Frigate's ADR-0016 VRAM budget was bumped 2000 → 2300 MiB for the extra - Frigate's ADR-0016 VRAM budget was bumped 2000 → 2300 MiB for the extra
NVDEC stream. NVDEC stream.

View file

@ -1,8 +1,8 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1600" height="880" viewBox="0 0 1600 880" font-family="system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif"> <svg xmlns="http://www.w3.org/2000/svg" width="1600" height="880" viewBox="0 0 1600 880" font-family="system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif">
<!-- ADR-0017 dCCTV topology (two-switch revision). Colors: reference dataviz <!-- ADR-0017 rev 3 dCCTV topology (single switch, VLAN-30 trunk on LAN1).
palette (light mode). blue #2a78d6 = home LAN · violet #4a3aa7 = dCCTV · Colors: reference dataviz palette (light mode). blue #2a78d6 = home LAN ·
aqua #1baf7a = dKubernetes · yellow #eda100 = dManagementsVms · violet #4a3aa7 = dCCTV · aqua #1baf7a = dKubernetes ·
green #008300 = allowed flow · red #e34948 = denied flow --> yellow #eda100 = dManagementsVms · green #008300 allow · red #e34948 deny -->
<defs> <defs>
<marker id="arrGreen" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse"> <marker id="arrGreen" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
<path d="M0,0 L10,5 L0,10 z" fill="#008300"/> <path d="M0,0 L10,5 L0,10 z" fill="#008300"/>
@ -17,11 +17,10 @@
<rect width="1600" height="880" fill="#fcfcfb"/> <rect width="1600" height="880" fill="#fcfcfb"/>
<!-- title --> <text x="40" y="42" font-size="26" font-weight="700" fill="#0b0b0b">ADR-0017 — CCTV segment behind pfSense, VLAN-30 trunk on the LAN1 cable</text>
<text x="40" y="42" font-size="26" font-weight="700" fill="#0b0b0b">ADR-0017 — CCTV segment on a dedicated pfSense leg</text> <text x="40" y="66" font-size="15" fill="#52514e">Sofia/Vermont · rev 3 (single switch) 2026-07-02 · dashed = camera-day · the ONLY 802.1Q is the trunk between the switch and eno1</text>
<text x="40" y="66" font-size="15" fill="#52514e">Sofia/Vermont · as-built 2026-07-02 · dashed = camera-day · no VLANs anywhere — isolation is physical</text>
<!-- camera -> everything else (denied): kept above the zones, below the subtitle --> <!-- camera -> everything else (denied) -->
<path d="M240,168 C520,104 900,104 1148,140" fill="none" stroke="#e34948" stroke-width="3" marker-end="url(#arrRed)"/> <path d="M240,168 C520,104 900,104 1148,140" fill="none" stroke="#e34948" stroke-width="3" marker-end="url(#arrRed)"/>
<g transform="translate(560,111)"> <g transform="translate(560,111)">
<circle r="11" fill="#fcfcfb" stroke="#e34948" stroke-width="2.5"/> <circle r="11" fill="#fcfcfb" stroke="#e34948" stroke-width="2.5"/>
@ -29,7 +28,7 @@
</g> </g>
<text x="588" y="100" font-size="13.5" font-weight="700" fill="#e34948">DENY · camera → LAN / other segments / internet (default deny on dCCTV)</text> <text x="588" y="100" font-size="13.5" font-weight="700" fill="#e34948">DENY · camera → LAN / other segments / internet (default deny on dCCTV)</text>
<!-- ═════════ GARAGE ENTRANCE zone ═════════ --> <!-- GARAGE ENTRANCE -->
<rect x="40" y="128" width="240" height="180" rx="10" fill="#4a3aa7" fill-opacity="0.06" stroke="#4a3aa7" stroke-opacity="0.35"/> <rect x="40" y="128" width="240" height="180" rx="10" fill="#4a3aa7" fill-opacity="0.06" stroke="#4a3aa7" stroke-opacity="0.35"/>
<text x="56" y="154" font-size="13" font-weight="700" fill="#52514e" letter-spacing="1">GARAGE ENTRANCE</text> <text x="56" y="154" font-size="13" font-weight="700" fill="#52514e" letter-spacing="1">GARAGE ENTRANCE</text>
<rect x="64" y="170" width="192" height="112" rx="8" fill="#ffffff" stroke="#4a3aa7" stroke-width="2"/> <rect x="64" y="170" width="192" height="112" rx="8" fill="#ffffff" stroke="#4a3aa7" stroke-width="2"/>
@ -39,67 +38,57 @@
<text x="80" y="252" font-size="12.5" fill="#52514e">DNS: garage-cam.viktorbarzin.lan</text> <text x="80" y="252" font-size="12.5" fill="#52514e">DNS: garage-cam.viktorbarzin.lan</text>
<text x="80" y="270" font-size="12.5" fill="#52514e">PoE from switch · cloud/P2P off</text> <text x="80" y="270" font-size="12.5" fill="#52514e">PoE from switch · cloud/P2P off</text>
<!-- camera cable to PE switch (camera day, dashed) --> <path d="M256,284 C330,330 412,368 417,430" fill="none" stroke="#52514e" stroke-width="2" stroke-dasharray="6,5" marker-end="url(#arrGray)"/>
<path d="M160,308 L160,390" fill="none" stroke="#52514e" stroke-width="2" stroke-dasharray="6,5" marker-end="url(#arrGray)"/> <text x="330" y="322" font-size="12" fill="#52514e">cat6 in conduit · PoE → P4</text>
<text x="172" y="344" font-size="12" fill="#52514e">cat6 in conduit · PoE</text>
<!-- ═════════ RACK zone ═════════ --> <!-- RACK zone: single switch -->
<rect x="40" y="360" width="560" height="265" rx="10" fill="#0b0b0b" fill-opacity="0.03" stroke="#b9b8b2"/> <rect x="40" y="360" width="560" height="265" rx="10" fill="#0b0b0b" fill-opacity="0.03" stroke="#b9b8b2"/>
<text x="56" y="384" font-size="13" font-weight="700" fill="#52514e" letter-spacing="1">RACK — GARAGE · TWO SWITCHES</text> <text x="56" y="384" font-size="13" font-weight="700" fill="#52514e" letter-spacing="1">RACK — GARAGE · ONE SWITCH</text>
<!-- TL-SG105PE: NEW, dedicated CCTV island --> <rect x="64" y="396" width="512" height="176" rx="8" fill="#4a3aa7" fill-opacity="0.04" stroke="#4a3aa7" stroke-width="2"/>
<rect x="64" y="396" width="512" height="88" rx="8" fill="#4a3aa7" fill-opacity="0.05" stroke="#4a3aa7" stroke-width="2"/> <text x="80" y="420" font-size="15" font-weight="700" fill="#0b0b0b">TL-SG105PE <tspan font-size="12.5" font-weight="400" fill="#52514e">replaces the SG105E · mgmt 192.168.1.6 (Kea) · all 5 ports used</tspan></text>
<text x="80" y="420" font-size="15" font-weight="700" fill="#0b0b0b">TL-SG105PE <tspan font-size="12.5" font-weight="400" fill="#52514e">NEW · dedicated CCTV island · mgmt 10.0.30.6 (Kea) · no VLAN table</tspan></text>
<g font-size="11.5" text-anchor="middle"> <g font-size="11.5" text-anchor="middle">
<rect x="80" y="432" width="120" height="40" rx="6" fill="#4a3aa7" fill-opacity="0.12" stroke="#4a3aa7"/> <rect x="80" y="436" width="88" height="56" rx="6" fill="#2a78d6" fill-opacity="0.12" stroke="#2a78d6"/>
<text x="140" y="449" font-weight="700" fill="#0b0b0b">camera · PoE</text> <text x="124" y="454" font-weight="700" fill="#0b0b0b">P1 · V1</text>
<text x="140" y="465" fill="#52514e">any of P1P4</text> <text x="124" y="470" fill="#52514e">apartment</text>
<rect x="212" y="432" width="120" height="40" rx="6" fill="#4a3aa7" fill-opacity="0.12" stroke="#4a3aa7"/> <text x="124" y="484" fill="#52514e">uplink</text>
<text x="272" y="449" font-weight="700" fill="#0b0b0b">→ R730 eno2</text> <rect x="178" y="436" width="88" height="56" rx="6" fill="#2a78d6" fill-opacity="0.12" stroke="#2a78d6"/>
<text x="272" y="465" fill="#52514e">uplink (P5)</text> <text x="222" y="454" font-weight="700" fill="#0b0b0b">P2 · V1</text>
<rect x="344" y="432" width="120" height="40" rx="6" fill="#ffffff" stroke="#8a8984" stroke-dasharray="4,3"/> <text x="222" y="470" fill="#52514e">4G router</text>
<text x="404" y="449" fill="#52514e">3 × spare PoE</text> <text x="222" y="484" fill="#52514e">192.168.1.7</text>
<text x="404" y="465" fill="#52514e">future cameras</text> <rect x="276" y="436" width="88" height="56" rx="6" fill="#2a78d6" fill-opacity="0.12" stroke="#2a78d6"/>
<text x="320" y="454" font-weight="700" fill="#0b0b0b">P3 · V1</text>
<text x="320" y="470" fill="#52514e">UPS mgmt</text>
<rect x="374" y="436" width="88" height="56" rx="6" fill="#4a3aa7" fill-opacity="0.12" stroke="#4a3aa7" stroke-width="2"/>
<text x="418" y="454" font-weight="700" fill="#0b0b0b">P4 · V30</text>
<text x="418" y="470" fill="#52514e">camera</text>
<text x="418" y="484" fill="#52514e">PoE ON</text>
<rect x="472" y="436" width="88" height="56" rx="6" fill="#2a78d6" fill-opacity="0.10" stroke="#4a3aa7" stroke-width="2" stroke-dasharray="0"/>
<text x="516" y="454" font-weight="700" fill="#0b0b0b">P5 · trunk</text>
<text x="516" y="470" fill="#52514e">V1 untagged</text>
<text x="516" y="484" fill="#4a3aa7">+ V30 tagged</text>
</g> </g>
<text x="80" y="516" font-size="12" fill="#52514e">802.1Q: VLAN 1 untagged {P1,P2,P3,P5} · VLAN 30 {P4 untagged/PVID 30, P5 tagged}</text>
<text x="80" y="534" font-size="12" fill="#52514e">tag-30 ingress on P1/P2/P3 is dropped (not members) — the trunk is the only tagged path</text>
<text x="80" y="558" font-size="12" fill="#8a8984">old TL-SG105E → retired, cold spare · backup-WAN (4G) + UPS keep their ports</text>
<!-- TL-SG105E: existing garage switch, untouched --> <!-- trunk: two parallel lines to eno1 -->
<rect x="64" y="496" width="512" height="116" rx="8" fill="#ffffff" stroke="#8a8984"/> <path d="M560,458 C630,458 640,428 692,420" fill="none" stroke="#2a78d6" stroke-width="2.5"/>
<text x="80" y="520" font-size="15" font-weight="700" fill="#0b0b0b">TL-SG105E · 192.168.1.6 <tspan font-size="12.5" font-weight="400" fill="#52514e">existing · no PoE · UNTOUCHED by this design</tspan></text> <path d="M560,466 C632,466 644,436 692,428" fill="none" stroke="#4a3aa7" stroke-width="2.5"/>
<g font-size="11.5" text-anchor="middle"> <text x="588" y="404" font-size="12" font-weight="700" fill="#0b0b0b">LAN1 cable</text>
<rect x="80" y="532" width="88" height="34" rx="6" fill="#2a78d6" fill-opacity="0.12" stroke="#2a78d6"/>
<text x="124" y="553" fill="#0b0b0b">P1 · 1G</text>
<rect x="178" y="532" width="88" height="34" rx="6" fill="#2a78d6" fill-opacity="0.12" stroke="#2a78d6"/>
<text x="222" y="553" fill="#0b0b0b">P2 · 100M</text>
<rect x="276" y="532" width="88" height="34" rx="6" fill="#2a78d6" fill-opacity="0.12" stroke="#2a78d6"/>
<text x="320" y="553" fill="#0b0b0b">P3 · 100M</text>
<rect x="374" y="532" width="88" height="34" rx="6" fill="#ffffff" stroke="#8a8984" stroke-dasharray="4,3"/>
<text x="418" y="553" fill="#52514e">P4 · free</text>
<rect x="472" y="532" width="88" height="34" rx="6" fill="#2a78d6" fill-opacity="0.12" stroke="#2a78d6"/>
<text x="516" y="553" fill="#0b0b0b">P5 · 1G</text>
</g>
<text x="80" y="590" font-size="12" fill="#52514e">1G ports: apartment uplink + R730 LAN1 · 100M ports: 4G router .7 (pfSense backup-WAN) + UPS mgmt</text>
<!-- PE -> eno2 patch (camera day, dashed) --> <!-- R730 / PVE zone -->
<path d="M576,452 C630,452 640,478 676,490" fill="none" stroke="#52514e" stroke-width="2" stroke-dasharray="6,5" marker-end="url(#arrGray)"/>
<text x="592" y="440" font-size="12" fill="#52514e">patch</text>
<!-- E -> eno1 (existing R730 LAN1) -->
<path d="M576,522 C630,522 650,470 696,432" fill="none" stroke="#2a78d6" stroke-width="2" opacity="0.6"/>
<text x="604" y="516" font-size="12" fill="#2a78d6">R730 LAN1</text>
<!-- ═════════ R730 / PVE zone ═════════ -->
<rect x="680" y="330" width="880" height="440" rx="10" fill="#0b0b0b" fill-opacity="0.03" stroke="#b9b8b2"/> <rect x="680" y="330" width="880" height="440" rx="10" fill="#0b0b0b" fill-opacity="0.03" stroke="#b9b8b2"/>
<text x="696" y="356" font-size="13" font-weight="700" fill="#52514e" letter-spacing="1">DELL R730 — PVE HOST 192.168.1.127 (IN THE RACK)</text> <text x="696" y="356" font-size="13" font-weight="700" fill="#52514e" letter-spacing="1">DELL R730 — PVE HOST 192.168.1.127 (IN THE RACK)</text>
<!-- NIC/bridge chips on left edge -->
<g font-size="12"> <g font-size="12">
<rect x="700" y="400" width="150" height="46" rx="6" fill="#2a78d6" fill-opacity="0.12" stroke="#2a78d6"/> <rect x="700" y="400" width="150" height="46" rx="6" fill="#2a78d6" fill-opacity="0.12" stroke="#4a3aa7" stroke-width="2"/>
<text x="712" y="419" font-weight="700" fill="#0b0b0b">eno1 → vmbr0</text> <text x="712" y="419" font-weight="700" fill="#0b0b0b">eno1 → vmbr0</text>
<text x="712" y="436" fill="#52514e">LAN1 · vlan-aware</text> <text x="712" y="436" fill="#52514e">untag V1 + tag 30</text>
<rect x="700" y="471" width="150" height="46" rx="6" fill="#4a3aa7" fill-opacity="0.12" stroke="#4a3aa7" stroke-width="2"/> <rect x="700" y="471" width="150" height="46" rx="6" fill="#ffffff" stroke="#8a8984" stroke-dasharray="4,3"/>
<text x="712" y="490" font-weight="700" fill="#0b0b0b">eno2 → vmbr2</text> <text x="712" y="490" font-weight="700" fill="#52514e">eno2 → vmbr2</text>
<text x="712" y="507" fill="#52514e">NEW · dedicated leg</text> <text x="712" y="507" fill="#8a8984">dormant fallback leg</text>
<rect x="700" y="542" width="150" height="46" rx="6" fill="#0b0b0b" fill-opacity="0.04" stroke="#8a8984"/> <rect x="700" y="542" width="150" height="46" rx="6" fill="#0b0b0b" fill-opacity="0.04" stroke="#8a8984"/>
<text x="712" y="561" font-weight="700" fill="#0b0b0b">vmbr1</text> <text x="712" y="561" font-weight="700" fill="#0b0b0b">vmbr1</text>
@ -112,17 +101,16 @@
<text x="906" y="432" font-size="12" fill="#52514e">gateway + firewall for every segment</text> <text x="906" y="432" font-size="12" fill="#52514e">gateway + firewall for every segment</text>
<g font-size="12"> <g font-size="12">
<rect x="906" y="444" width="268" height="34" rx="5" fill="#2a78d6" fill-opacity="0.12" stroke="#2a78d6"/> <rect x="906" y="444" width="268" height="34" rx="5" fill="#2a78d6" fill-opacity="0.12" stroke="#2a78d6"/>
<text x="916" y="465" fill="#0b0b0b">net0 · WAN <tspan fill="#52514e">192.168.1.2 (home LAN)</tspan></text> <text x="916" y="465" fill="#0b0b0b">net0 · WAN <tspan fill="#52514e">192.168.1.2 · vmbr0 untagged</tspan></text>
<rect x="906" y="484" width="268" height="34" rx="5" fill="#eda100" fill-opacity="0.14" stroke="#eda100"/> <rect x="906" y="484" width="268" height="34" rx="5" fill="#eda100" fill-opacity="0.14" stroke="#eda100"/>
<text x="916" y="505" fill="#0b0b0b">net1 · dManagementsVms <tspan fill="#52514e">10.0.10.1</tspan></text> <text x="916" y="505" fill="#0b0b0b">net1 · dManagementsVms <tspan fill="#52514e">10.0.10.1</tspan></text>
<rect x="906" y="524" width="268" height="34" rx="5" fill="#1baf7a" fill-opacity="0.12" stroke="#1baf7a"/> <rect x="906" y="524" width="268" height="34" rx="5" fill="#1baf7a" fill-opacity="0.12" stroke="#1baf7a"/>
<text x="916" y="545" fill="#0b0b0b">net2 · dKubernetes <tspan fill="#52514e">10.0.20.1</tspan></text> <text x="916" y="545" fill="#0b0b0b">net2 · dKubernetes <tspan fill="#52514e">10.0.20.1</tspan></text>
<rect x="906" y="564" width="268" height="34" rx="5" fill="#4a3aa7" fill-opacity="0.12" stroke="#4a3aa7" stroke-width="2"/> <rect x="906" y="564" width="268" height="34" rx="5" fill="#4a3aa7" fill-opacity="0.12" stroke="#4a3aa7" stroke-width="2"/>
<text x="916" y="585" fill="#0b0b0b">net3 · dCCTV <tspan fill="#52514e">10.0.30.1/24 · NEW</tspan></text> <text x="916" y="585" fill="#0b0b0b">net3 · dCCTV <tspan fill="#52514e">10.0.30.1/24 · vmbr0 tag 30</tspan></text>
</g> </g>
<!-- bridge attachments --> <path d="M850,415 L890,458" fill="none" stroke="#2a78d6" stroke-width="1.6" opacity="0.6"/>
<path d="M850,423 L890,458" fill="none" stroke="#2a78d6" stroke-width="1.6" opacity="0.6"/> <path d="M850,430 L890,581" fill="none" stroke="#4a3aa7" stroke-width="2"/>
<path d="M850,494 L890,581" fill="none" stroke="#4a3aa7" stroke-width="2"/>
<path d="M850,565 L890,501" fill="none" stroke="#8a8984" stroke-width="1.6" opacity="0.6"/> <path d="M850,565 L890,501" fill="none" stroke="#8a8984" stroke-width="1.6" opacity="0.6"/>
<path d="M850,565 L890,541" fill="none" stroke="#8a8984" stroke-width="1.6" opacity="0.6"/> <path d="M850,565 L890,541" fill="none" stroke="#8a8984" stroke-width="1.6" opacity="0.6"/>
@ -139,7 +127,7 @@
<text x="1268" y="562" font-size="13.5" font-weight="700" fill="#0b0b0b">go2rtc LB 10.0.20.204</text> <text x="1268" y="562" font-size="13.5" font-weight="700" fill="#0b0b0b">go2rtc LB 10.0.20.204</text>
<text x="1268" y="580" font-size="12" fill="#52514e">restream → HA live view (MSE/HLS)</text> <text x="1268" y="580" font-size="12" fill="#52514e">restream → HA live view (MSE/HLS)</text>
<!-- ═════════ HOME LAN zone ═════════ --> <!-- HOME LAN zone -->
<rect x="1148" y="128" width="412" height="180" rx="10" fill="#2a78d6" fill-opacity="0.06" stroke="#2a78d6" stroke-opacity="0.4"/> <rect x="1148" y="128" width="412" height="180" rx="10" fill="#2a78d6" fill-opacity="0.06" stroke="#2a78d6" stroke-opacity="0.4"/>
<text x="1164" y="154" font-size="13" font-weight="700" fill="#52514e" letter-spacing="1">HOME LAN 192.168.1.0/24</text> <text x="1164" y="154" font-size="13" font-weight="700" fill="#52514e" letter-spacing="1">HOME LAN 192.168.1.0/24</text>
<rect x="1164" y="168" width="180" height="56" rx="6" fill="#ffffff" stroke="#2a78d6"/> <rect x="1164" y="168" width="180" height="56" rx="6" fill="#ffffff" stroke="#2a78d6"/>
@ -151,45 +139,40 @@
<rect x="1360" y="168" width="184" height="56" rx="6" fill="#ffffff" stroke="#2a78d6"/> <rect x="1360" y="168" width="184" height="56" rx="6" fill="#ffffff" stroke="#2a78d6"/>
<text x="1372" y="190" font-size="13.5" font-weight="700" fill="#0b0b0b">apartment clients</text> <text x="1372" y="190" font-size="13.5" font-weight="700" fill="#0b0b0b">apartment clients</text>
<text x="1372" y="208" font-size="11.5" fill="#52514e">laptops, phones</text> <text x="1372" y="208" font-size="11.5" fill="#52514e">laptops, phones</text>
<!-- AX6000 route badge (camera day) -->
<rect x="1360" y="236" width="184" height="52" rx="6" fill="#ffffff" stroke="#52514e" stroke-dasharray="5,4"/> <rect x="1360" y="236" width="184" height="52" rx="6" fill="#ffffff" stroke="#52514e" stroke-dasharray="5,4"/>
<text x="1372" y="256" font-size="11.5" font-weight="700" fill="#52514e">CAMERA DAY: static route</text> <text x="1372" y="256" font-size="11.5" font-weight="700" fill="#52514e">CAMERA DAY: static route</text>
<text x="1372" y="272" font-size="11.5" fill="#52514e">10.0.30.0/24 via 192.168.1.2</text> <text x="1372" y="272" font-size="11.5" fill="#52514e">10.0.30.0/24 via 192.168.1.2</text>
<!-- home LAN -> pfSense WAN (via apartment uplink path) -->
<path d="M1254,308 C1150,352 950,372 790,400" fill="none" stroke="#2a78d6" stroke-width="2" opacity="0.6"/> <path d="M1254,308 C1150,352 950,372 790,400" fill="none" stroke="#2a78d6" stroke-width="2" opacity="0.6"/>
<text x="1010" y="374" font-size="12" fill="#2a78d6">apartment uplink · SG105E · eno1</text> <text x="1010" y="374" font-size="12" fill="#2a78d6">apartment uplink · switch P1 · trunk · eno1</text>
<!-- ═════════ FLOWS ═════════ --> <!-- FLOWS -->
<!-- Frigate -> camera RTSP (allowed): sweeps under the rack, terminates at the camera box -->
<path d="M1256,497 C1010,690 330,730 120,650 C40,618 40,380 96,286" fill="none" stroke="#008300" stroke-width="3" marker-end="url(#arrGreen)"/> <path d="M1256,497 C1010,690 330,730 120,650 C40,618 40,380 96,286" fill="none" stroke="#008300" stroke-width="3" marker-end="url(#arrGreen)"/>
<text x="620" y="700" font-size="13.5" font-weight="700" fill="#008300">ALLOW · Frigate → camera RTSP :554 (routed k8s → dCCTV; opt1 allow-all)</text> <text x="620" y="700" font-size="13.5" font-weight="700" fill="#008300">ALLOW · Frigate → camera RTSP :554 (routed k8s → dCCTV; opt1 allow-all)</text>
<!-- HA -> camera (allowed, via AX6000 route + WAN rules): labels above, arc dips below them -->
<path d="M1164,262 C820,282 470,268 302,176 C286,167 278,166 270,172" fill="none" stroke="#008300" stroke-width="3" marker-end="url(#arrGreen)"/> <path d="M1164,262 C820,282 470,268 302,176 C286,167 278,166 270,172" fill="none" stroke="#008300" stroke-width="3" marker-end="url(#arrGreen)"/>
<text x="484" y="216" font-size="13.5" font-weight="700" fill="#008300">ALLOW · ha-sofia → camera :80 ISAPI + :554</text> <text x="484" y="216" font-size="13.5" font-weight="700" fill="#008300">ALLOW · ha-sofia → camera :80 ISAPI + :554</text>
<text x="484" y="234" font-size="12" fill="#52514e">enters pfSense WAN · reply-to off · needs the AX6000 route</text> <text x="484" y="234" font-size="12" fill="#52514e">enters pfSense WAN · reply-to off · needs the AX6000 route</text>
<!-- camera -> NTP (allowed) -->
<path d="M280,232 C660,200 860,320 936,386" fill="none" stroke="#008300" stroke-width="2" opacity="0.85" marker-end="url(#arrGreen)"/> <path d="M280,232 C660,200 860,320 936,386" fill="none" stroke="#008300" stroke-width="2" opacity="0.85" marker-end="url(#arrGreen)"/>
<text x="740" y="322" font-size="12.5" font-weight="700" fill="#008300">ALLOW · camera → 10.0.30.1:123 (NTP)</text> <text x="740" y="322" font-size="12.5" font-weight="700" fill="#008300">ALLOW · camera → 10.0.30.1:123 (NTP)</text>
<!-- ═════════ LEGEND ═════════ --> <!-- LEGEND -->
<g transform="translate(40,800)" font-size="12.5"> <g transform="translate(40,800)" font-size="12.5">
<rect x="0" y="0" width="18" height="18" rx="4" fill="#2a78d6" fill-opacity="0.12" stroke="#2a78d6"/> <rect x="0" y="0" width="18" height="18" rx="4" fill="#2a78d6" fill-opacity="0.12" stroke="#2a78d6"/>
<text x="26" y="14" fill="#0b0b0b">home LAN 192.168.1.0/24</text> <text x="26" y="14" fill="#0b0b0b">home LAN / VLAN 1</text>
<rect x="230" y="0" width="18" height="18" rx="4" fill="#4a3aa7" fill-opacity="0.12" stroke="#4a3aa7" stroke-width="2"/> <rect x="200" y="0" width="18" height="18" rx="4" fill="#4a3aa7" fill-opacity="0.12" stroke="#4a3aa7" stroke-width="2"/>
<text x="256" y="14" fill="#0b0b0b">CCTV island / dCCTV 10.0.30.0/24</text> <text x="226" y="14" fill="#0b0b0b">CCTV / VLAN 30 / dCCTV 10.0.30.0/24</text>
<rect x="510" y="0" width="18" height="18" rx="4" fill="#1baf7a" fill-opacity="0.12" stroke="#1baf7a"/> <rect x="500" y="0" width="18" height="18" rx="4" fill="#1baf7a" fill-opacity="0.12" stroke="#1baf7a"/>
<text x="536" y="14" fill="#0b0b0b">dKubernetes</text> <text x="526" y="14" fill="#0b0b0b">dKubernetes</text>
<rect x="650" y="0" width="18" height="18" rx="4" fill="#eda100" fill-opacity="0.14" stroke="#eda100"/> <rect x="640" y="0" width="18" height="18" rx="4" fill="#eda100" fill-opacity="0.14" stroke="#eda100"/>
<text x="676" y="14" fill="#0b0b0b">dManagementsVms</text> <text x="666" y="14" fill="#0b0b0b">dManagementsVms</text>
<line x1="830" y1="9" x2="870" y2="9" stroke="#008300" stroke-width="3" marker-end="url(#arrGreen)"/> <line x1="820" y1="9" x2="860" y2="9" stroke="#008300" stroke-width="3" marker-end="url(#arrGreen)"/>
<text x="880" y="14" fill="#0b0b0b">allowed flow</text> <text x="870" y="14" fill="#0b0b0b">allowed flow</text>
<line x1="990" y1="9" x2="1030" y2="9" stroke="#e34948" stroke-width="3" marker-end="url(#arrRed)"/> <line x1="980" y1="9" x2="1020" y2="9" stroke="#e34948" stroke-width="3" marker-end="url(#arrRed)"/>
<text x="1040" y="14" fill="#0b0b0b">denied</text> <text x="1030" y="14" fill="#0b0b0b">denied</text>
<line x1="1110" y1="9" x2="1150" y2="9" stroke="#52514e" stroke-width="2" stroke-dasharray="6,5"/> <line x1="1100" y1="9" x2="1140" y2="9" stroke="#52514e" stroke-width="2" stroke-dasharray="6,5"/>
<text x="1160" y="14" fill="#0b0b0b">camera-day step</text> <text x="1150" y="14" fill="#0b0b0b">camera-day step</text>
<text x="1330" y="14" fill="#52514e">ADR-0017 · rev 2</text> <text x="1320" y="14" fill="#52514e">ADR-0017 · rev 3</text>
</g> </g>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Before After
Before After

View file

@ -89,7 +89,7 @@ graph TB
| phpIPAM | v1.7.0 | phpipam.viktorbarzin.me | IP address management, device inventory, DNS sync | | phpIPAM | v1.7.0 | phpipam.viktorbarzin.me | IP address management, device inventory, DNS sync |
| vmbr0 | Linux bridge | 192.168.1.127/24 | Physical bridge on eno1, uplink to LAN | | vmbr0 | Linux bridge | 192.168.1.127/24 | Physical bridge on eno1, uplink to LAN |
| vmbr1 | Linux bridge (VLAN-aware) | Internal | VLAN trunk for VM isolation | | vmbr1 | Linux bridge (VLAN-aware) | Internal | VLAN trunk for VM isolation |
| vmbr2 | Linux bridge | Physical (eno2) | dCCTV segment leg: eno2 → TL-SG105PE (dedicated CCTV switch in the rack) → cameras; pfSense net3 is the only L3 exit (ADR-0017) | | vmbr2 | Linux bridge | Physical (eno2) | DORMANT fallback leg for dCCTV (ADR-0017 rev 3) — live dCCTV rides vmbr0 tag 30 over the LAN1 trunk |
| Technitium DNS | Container | 10.0.20.201 (LB) / 10.96.0.53 (ClusterIP) | Internal DNS (viktorbarzin.lan) + full recursive resolver | | Technitium DNS | Container | 10.0.20.201 (LB) / 10.96.0.53 (ClusterIP) | Internal DNS (viktorbarzin.lan) + full recursive resolver |
| Cloudflare DNS | SaaS | External | ~50 public domains under viktorbarzin.me | | Cloudflare DNS | SaaS | External | ~50 public domains under viktorbarzin.me |
| Cloudflared | Container | K8s (3 replicas) | Tunnel ingress, replaces port forwarding | | Cloudflared | Container | K8s (3 replicas) | Tunnel ingress, replaces port forwarding |
@ -103,9 +103,9 @@ graph TB
Isolated camera segment for owned cameras at the Sofia site (first: `vermont-garage`, HiLook IPC-T241H-C at the garage entrance). Decision + rejected alternatives: `docs/adr/0017-cctv-segment-dedicated-pfsense-leg.md`. Isolated camera segment for owned cameras at the Sofia site (first: `vermont-garage`, HiLook IPC-T241H-C at the garage entrance). Decision + rejected alternatives: `docs/adr/0017-cctv-segment-dedicated-pfsense-leg.md`.
**Physical path**: camera → TL-SG105PE PoE port → R730 `eno2``vmbr2` (bridge-ports eno2, not vlan-aware) → pfSense `net3`/vtnet3 = interface **dCCTV `10.0.30.1/24`**. The TL-SG105PE is a dedicated CCTV island — camera + eno2 uplink + 3 spare PoE ports, **no VLAN table anywhere**, mgmt at `10.0.30.6` (Kea). It is a second switch: the pre-existing garage TL-SG105E (`192.168.1.6`; apartment uplink, R730 LAN1, 4G router `192.168.1.7`, UPS mgmt, one free port; no PoE) is not involved in the CCTV path at all. **Physical path (rev 3, single switch)**: camera → TL-SG105PE PoE port (untagged VLAN 30) → trunk port (home LAN untagged + CCTV **tagged 30**) → the existing LAN1 cable → R730 `eno1``vmbr0` (vlan-aware) → pfSense `net3`/vtnet3 = `vmbr0 tag=30` = interface **dCCTV `10.0.30.1/24`**. The TL-SG105PE **replaces** the old garage TL-SG105E (retired to cold spare) and carries everything: apartment uplink, 4G router `192.168.1.7`, UPS mgmt (VLAN 1), camera (VLAN 30), trunk — all 5 ports used. VLAN-30 membership is {camera port, trunk port} only, so tagged injection from other ports is dropped. `eno2`/`vmbr2` remain dormant as the fallback physical leg (rev 2).
**Addressing**: Kea DHCP pool `10.0.30.100-199`; devices get MAC reservations (camera `10.0.30.70`, PE switch mgmt `10.0.30.6`). Kea DDNS auto-registers names in Technitium; `phpipam-pfsense-import` picks up leases hourly. **Addressing**: Kea DHCP pool `10.0.30.100-199`; devices get MAC reservations (camera `10.0.30.70`; the PE switch mgmt inherits the retired switch's `192.168.1.6` on the home LAN). Kea DDNS auto-registers names in Technitium; `phpipam-pfsense-import` picks up leases hourly.
**Firewall** (all on pfSense): **Firewall** (all on pfSense):
- dCCTV in: pass `udp OPT4-net → 10.0.30.1:123` (NTP) — everything else hits the interface's default deny. Cameras cannot reach LAN, other segments, or the internet. - dCCTV in: pass `udp OPT4-net → 10.0.30.1:123` (NTP) — everything else hits the interface's default deny. Cameras cannot reach LAN, other segments, or the internet.