Files
esh-pfi-infrastructure/playbooks/gx10-rack-network.yaml
T
vh a95717e810 feat(gx10): rack networking — VLAN 50 via DHCP reservation, wired only
pfi-gx10 moved from desk Wi-Fi (10.100.10.226, VLAN 10) to the rack on
10.100.50.60 (nh3-servers, VLAN 50), reachable as pfi-gx10.nh3.internal.

The address is a DHCP RESERVATION on the UDM, not a host static. Operator
ruling during the move, and the better design: a host-side static works
until the box moves and is then a stale netplan file on a machine whose
address you no longer know. The pre-written playbook wrote a host static;
it is kept for its safety ordering and annotated as retired.

The port arrived on the native VLAN, not the server VLAN, so switch port 22
was repointed first. port_overrides is a whole-array PUT — two unrelated
overrides on ports 21 and 23 were read, preserved and written back, with the
original array backed up to a file before the change.

Wi-Fi stayed up as the escape hatch until the wired path was proven from
outside, and was downed last as its own step. The step worth keeping: while
Wi-Fi was up, traffic to nh3-dev still preferred wlP9s9 because that
interface sits directly on the userland subnet, so reachability proved
nothing about the wired path. `ping -I enP7s7` across the VLAN boundary is
what actually settled it before the hatch came down.
2026-09-03 15:59:09 -07:00

139 lines
6.6 KiB
YAML

# pfi-gx10 — RETIRED 2026-09-03. Kept for its reasoning, not for running.
#
# ⚠ THE STATIC-IP APPROACH IN THIS FILE WAS NOT USED. Operator ruling on the
# day of the rack move: put the address on the SWITCH/FIREWALL side as a DHCP
# RESERVATION and leave the host on DHCP, so moving the box later does not mean
# unpicking a stale netplan static on a machine you may not be able to reach.
# The host-static this playbook writes is exactly the "interesting adventure"
# that ruling avoids.
#
# What was actually done instead (see docs/runbooks/gx10-rack-network.md):
# 1. UniFi switch port 22 native network -> nh3-servers (VLAN 50)
# 2. UniFi client reservation: 30:c5:99:3d:a7:45 -> 10.100.50.60
# 3. host DHCP renew; Wi-Fi left UP until the wired path was proven
# 4. dns/internal.yaml + dns-sync
# 5. nmcli radio wifi off, last, as its own step
#
# The safety ORDER below is still right and was followed: verify carrier,
# verify MAC not interface name, keep Wi-Fi up as the escape hatch, prove the
# new address from OUTSIDE, tear the hatch down separately and last.
#
# pfi-gx10 — move from desk (Wi-Fi + DHCP, VLAN 10) to rack (wired static,
# VLAN 50 nh3-servers). Run AFTER the box is racked and the switch port has
# been placed on nh3-servers.
#
# scripts/elway infra-ops@<current-ip> --playbook playbooks/gx10-rack-network.yaml
#
# Find <current-ip> without asking anyone — the wired NIC has its own MAC:
# curl -sk "https://10.100.0.1/proxy/network/api/s/default/stat/sta" \
# -H "X-API-KEY: $(secret get unifi/pfi-udmse-api-key)" \
# | python3 -c "import json,sys;[print(c['ip'],c.get('sw_port')) for c in json.load(sys.stdin)['data'] if c['mac']=='30:c5:99:3d:a7:45']"
#
# ─────────────────────────────────────────────────────────────────────────────
# WHY THIS IS SAFE TO RUN AGAINST A BOX IN A RACK
#
# The lockout risk in remote network reconfiguration is applying a static
# address that does not work and losing the only path back. This playbook never
# has one path: Wi-Fi (wlP9s9) STAYS UP THROUGHOUT. The wired interface is
# configured alongside it, verified from outside on the new address, and only
# then is Wi-Fi torn down — by a SEPARATE playbook run, after a human has seen
# the new address answer.
#
# So a botched netplan costs a retry over Wi-Fi, not a trip to the rack.
# ─────────────────────────────────────────────────────────────────────────────
vars:
wired_if: enP7s7
wired_mac: "30:c5:99:3d:a7:45"
static_ip: 10.100.50.60
prefix: "24"
gateway: 10.100.50.1
dns_servers: "10.100.50.40" # nh3-docker / AdGuard, per dns/internal.yaml
search_domain: nh3.internal
steps:
- name: Refuse to proceed if the cable is not actually in
# carrier=0 means no link. Writing a static config for a dead interface and
# calling it done is the kind of green that costs a rack visit to discover.
shell: test "$(cat /sys/class/net/{{ wired_if }}/carrier 2>/dev/null)" = "1"
- name: Confirm the interface is the NIC we think it is
# Interface names can renumber across kernels. The MAC cannot.
shell: test "$(cat /sys/class/net/{{ wired_if }}/address)" = "{{ wired_mac }}"
- name: Back up the existing netplan
shell: sudo cp -an /etc/netplan /etc/netplan.bak-preRack 2>/dev/null || true
creates: /etc/netplan.bak-preRack
- name: Write the wired static config
# A SEPARATE file from whatever manages Wi-Fi. Wi-Fi keeps working while
# this lands; that is the escape hatch and it is deliberate.
shell: |
sudo tee /etc/netplan/60-wired-static.yaml >/dev/null <<'EOF'
network:
version: 2
ethernets:
{{ wired_if }}:
dhcp4: false
dhcp6: false
addresses: [{{ static_ip }}/{{ prefix }}]
routes:
- to: default
via: {{ gateway }}
metric: 100
nameservers:
addresses: [{{ dns_servers }}]
search: [{{ search_domain }}]
EOF
sudo chmod 600 /etc/netplan/60-wired-static.yaml
creates: /etc/netplan/60-wired-static.yaml
- name: Apply
# `netplan try` would be better but needs a TTY to accept or roll back;
# under elway there is nobody to press a key. Wi-Fi staying up is what
# substitutes for the auto-rollback.
shell: sudo netplan apply
- name: Wait for the address to come up locally
shell: |
for i in $(seq 1 15); do
ip -4 addr show {{ wired_if }} | grep -q "{{ static_ip }}/{{ prefix }}" && exit 0
sleep 2
done
exit 1
verify:
- name: Wired interface holds the static address
shell: ip -4 addr show {{ wired_if }} | grep -q "{{ static_ip }}/{{ prefix }}"
changed_when: "false"
- name: Default route is via the servers-VLAN gateway
shell: ip route get 1.1.1.1 | grep -q "via {{ gateway }}"
changed_when: "false"
- name: Gateway answers
shell: ping -c 2 -W 2 {{ gateway }} >/dev/null
changed_when: "false"
- name: DNS resolves through the fleet resolver
shell: getent hosts nh3-docker.nh3.internal >/dev/null
changed_when: "false"
- name: Wi-Fi is STILL UP — the escape hatch must survive this run
# If this fails, the box may be reachable only on an address that was just
# proven or just broken. Loudly not optional.
shell: ip link show wlP9s9 | grep -q "state UP"
changed_when: "false"
# ─────────────────────────────────────────────────────────────────────────────
# AFTERWARDS, and only once the new address has been confirmed from OUTSIDE:
#
# 1. ssh infra-ops@10.100.50.60 # prove it from another host first
# 2. update servers/pfi-gx10/ssh-target
# 3. add to dns/internal.yaml, then scripts/dns-sync.py --dry-run && ...sync
# 4. tear down Wi-Fi — a server with two default routes picks one at random
# and you find out later, in a way that looks like packet loss:
# sudo nmcli radio wifi off # or delete the wlP9s9 netplan stanza
# Do that as its own change, not bundled in here.
# ─────────────────────────────────────────────────────────────────────────────