feat(shoukei): pin home Wi-Fi to a static IPv4 & adjust networking.nix (#407)

This commit is contained in:
ryan4yin | 二花 authored and GitHub committed 2026-10-07 21:27:34 +08:00
1 parent e9d706aa96
commit 835b7f6df1
5 files changed
+237 -70

No files matched your search

+6
View File
@@ -157,6 +157,12 @@ niri mode="switch" verbosity="normal":
use {{utils_nu}} *; use {{utils_nu}} *;
nixos-switch $"(hostname)-niri" {{mode}} {{verbosity}} nixos-switch $"(hostname)-niri" {{mode}} {{verbosity}}
# Pin shoukei's home Wi-Fi to a static IPv4 (run once; the profile persists).
[linux]
[group('desktop')]
shoukei-home-wifi:
sudo nu {{absolute_path("scripts/shoukei-home-wifi-static.nu")}}
############################################################################ ############################################################################
# #
# Darwin related commands # Darwin related commands
+4 -2
View File
@@ -2,8 +2,10 @@
This is NixOS's configuration for my Macbook Pro 2022 M2, 16G RAM. This is NixOS's configuration for my Macbook Pro 2022 M2, 16G RAM.
This laptop joins untrusted networks and uses DHCP instead of a static IP, so it has no entry in This laptop joins untrusted networks and uses DHCP by default. On the home Wi-Fi
`vars/networking.nix`. (`shadow_light_ryan`) run `just shoukei-home-wifi` to pin `192.168.5.108`; the recipe calls
[`scripts/shoukei-home-wifi-static.nu`](../../scripts/shoukei-home-wifi-static.nu), and
`vars/networking.nix` records the address with `homeOnly = true`.
## x86/x86_64 applications ## x86/x86_64 applications
+5 -3
View File
@@ -7,9 +7,11 @@
let let
# Exporters bind to this host's static LAN IPv4 (the same address # Exporters bind to this host's static LAN IPv4 (the same address
# VictoriaMetrics scrapes), so the socket exists only on the LAN interface — # VictoriaMetrics scrapes), so the socket exists only on the LAN interface —
# defence in depth behind the firewall allowlist. Hosts without a static LAN # defence in depth behind the firewall allowlist. Hosts without an always-on
# address (laptops like shoukei, DHCP VMs like akane) run no exporters. # static LAN address (the `homeOnly` laptop, DHCP VMs like akane) run no
lanAddr = myvars.networking.hostsAddr.${config.networking.hostName}.ipv4 or null; # exporters.
hostAddr = myvars.networking.hostsAddr.${config.networking.hostName} or { };
lanAddr = if hostAddr.homeOnly or false then null else hostAddr.ipv4 or null;
in in
{ {
# enable the node exporter on all nixos hosts # enable the node exporter on all nixos hosts
+149
View File
@@ -0,0 +1,149 @@
#!/usr/bin/env nu
#
# Pin shoukei's home Wi-Fi profile to a static IPv4.
#
# NetworkManager matches connections by SSID. Run this ONCE, while connected to
# HOME_SSID: the script refuses to run unless the current Wi-Fi network is that
# exact SSID, so it can never pin the wrong network by accident. Every other
# network keeps using DHCP. The change is stored in the profile under
# /etc/NetworkManager/system-connections (preserved by impermanence), so it
# survives reboots and rebuilds -- no need to run it again.
#
# just shoukei-home-wifi
#
# See hosts/12kingdoms-shoukei/README.md and WORKAROUNDS.md WA-024/025.
# ---------------------------------------------------------------------------
# Configuration
# ---------------------------------------------------------------------------
# The home network: 2.4 and 5 GHz are merged into this single SSID, so it is
# the only network this script may touch.
const HOME_SSID = "shadow_light_ryan"
# The address to pin on that SSID.
const STATIC = {
address: "192.168.5.108/24"
gateway: "192.168.5.1"
dns: "192.168.5.1"
}
# ---------------------------------------------------------------------------
# nmcli helpers
# ---------------------------------------------------------------------------
# Run nmcli; abort the script if it exits non-zero.
def nmcli [args: list<string>] {
let result = (^nmcli ...$args | complete)
if $result.exit_code != 0 {
error make { msg: $"nmcli ($args | str join ' ') failed: ($result.stderr | str trim)" }
}
$result.stdout | str trim
}
# Names of the connections that are active right now.
def active-connections [] {
nmcli ["-t" "-f" "NAME,ACTIVE" "connection" "show"]
| lines
| where { |line| ($line | split row ":" | last) == "yes" }
| each { |line| $line | split row ":" | first }
}
# Every Wi-Fi connection as a { name, ssid } record.
def wifi-profiles [] {
nmcli ["-t" "-f" "NAME" "connection" "show"]
| lines
| where { |name| ($name | str trim) != "" }
| each { |name|
if (nmcli ["-g" "connection.type" "connection" "show" $name]) == "802-11-wireless" {
{ name: $name, ssid: (nmcli ["-g" "802-11-wireless.ssid" "connection" "show" $name]) }
}
}
| compact
}
# ---------------------------------------------------------------------------
# Guards
# ---------------------------------------------------------------------------
def assert-root [] {
if (^id -u | str trim | into int) != 0 {
error make { msg: "must run as root -- nmcli edits system connections; try: just shoukei-home-wifi" }
}
}
# Abort unless Wi-Fi is connected to HOME_SSID right now.
def assert-on-home [active: list<string>, wifi: list] {
let connected = ($wifi | where { |p| $p.name in $active })
let current = (if ($connected | is-empty) { [] } else { $connected | get ssid })
if ($current | is-empty) {
error make { msg: $"not connected to Wi-Fi -- connect to '($HOME_SSID)' first" }
}
if (not ($HOME_SSID in $current)) {
error make { msg: $"not connected to '($HOME_SSID)'; currently on ($current | str join ', ')" }
}
}
# ---------------------------------------------------------------------------
# Profile operations
# ---------------------------------------------------------------------------
# The profile names that carry HOME_SSID.
def home-profiles [wifi: list] {
$wifi | where { |p| $p.ssid == $HOME_SSID } | get name
}
def set-static-ip [profile: string] {
nmcli [
"connection" "modify" $profile
"ipv4.method" "manual"
"ipv4.addresses" $STATIC.address
"ipv4.gateway" $STATIC.gateway
"ipv4.dns" $STATIC.dns
] | ignore
}
# Apply the new settings without dropping the link (avoids the brcmfmac
# re-auth/password re-prompt, WA-024/025); reconnect only as a fallback.
def apply-live [profile: string] {
let device = (nmcli ["-g" "GENERAL.DEVICES" "connection" "show" $profile])
if ($device | is-empty) { return }
let result = (^nmcli device reapply $device | complete)
if $result.exit_code == 0 {
print $" applied on ($device) via reapply, no reconnect"
} else {
print $" reapply failed, reconnecting '($profile)'"
nmcli ["connection" "up" $profile] | ignore
}
}
# ---------------------------------------------------------------------------
# Entry point
# ---------------------------------------------------------------------------
def main [] {
assert-root
let active = (active-connections)
let wifi = (wifi-profiles)
assert-on-home $active $wifi
let profiles = (home-profiles $wifi)
if ($profiles | is-empty) {
error make { msg: $"no Wi-Fi profile for SSID '($HOME_SSID)'" }
}
print $"pinning '($HOME_SSID)': ($STATIC.address) via ($STATIC.gateway), dns ($STATIC.dns)"
for p in $profiles {
print $" set '($p)'"
set-static-ip $p
}
for p in $profiles {
if $p in $active { apply-live $p }
}
print ""
print "done. check: nmcli -f NAME,ipv4.method,ipv4.addresses connection show; ip -4 addr show wld0"
}
+73 -65
View File
@@ -36,39 +36,19 @@ rec {
}; };
}; };
# Static LAN addresses, sorted by IPv4 ascending -- keep it sorted when adding
# a host so the next free address is obvious. `homeOnly = true` marks an
# address that only exists at home (the laptop's home Wi-Fi static); ssh and
# monitoring skip those entries.
hostsAddr = { hostsAddr = {
# ============================================
# Homelab's Physical Machines (VM hosts)
# ============================================
shoryu = {
iface = "eno1";
ipv4 = "192.168.5.181";
};
shushou = {
iface = "eno1";
ipv4 = "192.168.5.182";
};
youko = {
iface = "eno1";
ipv4 = "192.168.5.183";
};
# ============================================
# Other VMs and Physical Machines
# ============================================
ai = { ai = {
# Desktop PC # Desktop PC
iface = "enp130s0"; iface = "enp130s0";
ipv4 = "192.168.5.100"; ipv4 = "192.168.5.100";
ipv6 = "fe80::10"; # Link-local Address ipv6 = "fe80::10"; # Link-local Address
}; };
# akane = {
# # VM (running in macOS's UTM App), using DHCP instead of static ip.
# iface = "enp0s1";
# ipv4 = "192.168.64.2";
# };
aquamarine = { aquamarine = {
# VM # VM (retired)
iface = "enp2s0"; iface = "enp2s0";
ipv4 = "192.168.5.101"; ipv4 = "192.168.5.101";
}; };
@@ -102,40 +82,12 @@ rec {
iface = "end1"; iface = "end1";
ipv4 = "192.168.5.107"; ipv4 = "192.168.5.107";
}; };
rakushun = { shoukei = {
# Orange Pi 5 - ARM # Laptop: pinned on the home Wi-Fi only, by `just shoukei-home-wifi`; off
# RJ45 port 1 - enP4p65s0 # the home network it uses DHCP.
# RJ45 port 2 - enP3p49s0 iface = "wld0";
iface = "enP4p65s0"; ipv4 = "192.168.5.108";
ipv4 = "192.168.5.179"; homeOnly = true;
};
suzi = {
iface = "enp2s0"; # fake iface, it's not used by the host
ipv4 = "192.168.5.178";
ipv6 = "fe80::8"; # Link-local Address, can be used as default gateway
};
mitsuha = {
iface = "enp2s0"; # fake iface, it's not used by the host
ipv4 = "192.168.5.177";
};
# ============================================
# Kubernetes Clusters
# ============================================
k3s-test-1-master-1 = {
# VM
iface = "enp2s0";
ipv4 = "192.168.5.114";
};
k3s-test-1-master-2 = {
# VM
iface = "enp2s0";
ipv4 = "192.168.5.115";
};
k3s-test-1-master-3 = {
# VM
iface = "enp2s0";
ipv4 = "192.168.5.116";
}; };
k3s-test-1-worker-1 = { k3s-test-1-worker-1 = {
# VM # VM
@@ -152,6 +104,57 @@ rec {
iface = "enp2s0"; iface = "enp2s0";
ipv4 = "192.168.5.113"; ipv4 = "192.168.5.113";
}; };
k3s-test-1-master-1 = {
# VM
iface = "enp2s0";
ipv4 = "192.168.5.114";
};
k3s-test-1-master-2 = {
# VM
iface = "enp2s0";
ipv4 = "192.168.5.115";
};
k3s-test-1-master-3 = {
# VM
iface = "enp2s0";
ipv4 = "192.168.5.116";
};
mitsuha = {
iface = "enp2s0"; # fake iface, it's not used by the host
ipv4 = "192.168.5.177";
};
suzi = {
iface = "enp2s0"; # fake iface, it's not used by the host
ipv4 = "192.168.5.178";
ipv6 = "fe80::8"; # Link-local Address, can be used as default gateway
};
rakushun = {
# Orange Pi 5 - ARM, now running Armbian (not NixOS)
# RJ45 port 1 - enP4p65s0
# RJ45 port 2 - enP3p49s0
iface = "enP4p65s0";
ipv4 = "192.168.5.179";
};
shoryu = {
# Homelab's physical machine (VM host)
iface = "eno1";
ipv4 = "192.168.5.181";
};
shushou = {
# Homelab's physical machine (VM host)
iface = "eno1";
ipv4 = "192.168.5.182";
};
youko = {
# Homelab's physical machine (VM host)
iface = "eno1";
ipv4 = "192.168.5.183";
};
# akane = {
# # VM (running in macOS's UTM App), using DHCP instead of static ip.
# iface = "enp0s1";
# ipv4 = "192.168.64.2";
# };
}; };
ssh = { ssh = {
@@ -164,15 +167,20 @@ rec {
# IdentityFile — the location of your SSH key authentication file for the account. # IdentityFile — the location of your SSH key authentication file for the account.
# Format in details: # Format in details:
# https://www.ssh.com/academy/ssh/config # https://www.ssh.com/academy/ssh/config
# `homeOnly` entries get no alias: their address is not reachable off the
# home LAN, so an alias would break e.g. `ssh shoukei` over Tailscale.
extraConfig = ( extraConfig = (
lib.attrsets.foldlAttrs ( lib.attrsets.foldlAttrs (
acc: host: val: acc: host: val:
acc if val.homeOnly or false then
+ '' acc
Host ${host} else
HostName ${val.ipv4} acc
Port 22 + ''
'' Host ${host}
HostName ${val.ipv4}
Port 22
''
) "" hostsAddr ) "" hostsAddr
); );