Establish clean homelab infrastructure baseline
lint / yaml (push) Has been cancelled
lint / ansible (push) Has been cancelled
lint / terraform (push) Has been cancelled

Reorganize the brownfield repository, remove retired and generated artifacts, harden ignore rules, and record the GitOps/IaC redesign.
This commit is contained in:
2026-09-09 16:47:20 +00:00
commit 88a02ababa
418 changed files with 50579 additions and 0 deletions
@@ -0,0 +1,29 @@
---
# PVE SDN — VLAN zone on vmbr0.
#
# WHY VLAN AND NOT VXLAN: the unmanaged switch forwards 802.1Q-tagged frames
# untouched (dumb switches forward on MAC; the tag is opaque payload). VERIFIED
# on this LAN 2026-07-25 — a VLAN-100 ping between nodes passed at both 1472B and
# 8972B. So VLAN gives cross-node L2 with NO encapsulation, no overhead, and no
# MTU arithmetic. VXLAN would work too (jumbo leaves room for its ~50 bytes) but
# buys nothing here and costs CPU.
#
# ⚠️ A dumb switch enforces nothing, so these VLANs are SEGMENTATION, not
# security: anything on the LAN could inject tagged frames. Do not treat a VNet
# here as an isolation boundary for anything that matters.
#
# NOTE: a VLAN zone is pure L2 — no gateway, no SNAT, no internet. That is the
# point for lab/retro-OS networks. If a VNet later needs routing or internet
# egress, that is an EVPN zone (frr is already installed) or a router VM.
pve_sdn_zone: lab
pve_sdn_bridge: vmbr0
# Guests keep a standard 1500 MTU; the 9000 underlay carries it comfortably.
pve_sdn_mtu: 1500
pve_sdn_vnets:
- name: labnet # general isolated lab L2
tag: 100
alias: "Isolated lab network (no gateway)"
- name: retronet # retro OSes: keep ancient stacks off the real LAN
tag: 110
alias: "Retro OS network (no gateway, no internet)"
@@ -0,0 +1,60 @@
---
# SDN config is cluster-wide (/etc/pve/sdn/), so everything runs once.
- name: Read existing zones
ansible.builtin.command:
cmd: "pvesh get /cluster/sdn/zones --output-format json"
register: _zones
changed_when: false
check_mode: false
run_once: true
- name: Create the VLAN zone
ansible.builtin.command:
cmd: >-
pvesh create /cluster/sdn/zones --zone {{ pve_sdn_zone }} --type vlan
--bridge {{ pve_sdn_bridge }} --mtu {{ pve_sdn_mtu }}
--nodes {{ groups['pve'] | join(',') }}
when: pve_sdn_zone not in (_zones.stdout | from_json | map(attribute='zone') | list)
run_once: true
- name: Read existing vnets
ansible.builtin.command:
cmd: "pvesh get /cluster/sdn/vnets --output-format json"
register: _vnets
changed_when: false
check_mode: false
run_once: true
- name: Create the VNets
ansible.builtin.command:
cmd: >-
pvesh create /cluster/sdn/vnets --vnet {{ item.name }}
--zone {{ pve_sdn_zone }} --tag {{ item.tag }}
--alias '{{ item.alias }}'
loop: "{{ pve_sdn_vnets }}"
loop_control:
label: "{{ item.name }} (vlan {{ item.tag }})"
when: item.name not in (_vnets.stdout | from_json | map(attribute='vnet') | list)
run_once: true
- name: Apply the SDN configuration
# SDN changes stay PENDING until applied; without this the VNet bridges are
# never actually created on the nodes.
ansible.builtin.command:
cmd: "pvesh set /cluster/sdn"
register: _apply
changed_when: true
run_once: true
- name: Report
ansible.builtin.shell:
cmd: "pvesh get /cluster/sdn/vnets --output-format json | python3 -c \"import json,sys;[print(' ',v['vnet'],'vlan',v.get('tag'),'zone',v.get('zone')) for v in json.load(sys.stdin)]\""
register: _rep
changed_when: false
run_once: true
- name: Show it
ansible.builtin.debug:
msg: "{{ _rep.stdout_lines }}"
run_once: true