Development

This commit is contained in:
Matthew Grotke 2026-06-05 01:48:27 -04:00
parent 205d6889df
commit 58ab569e42
27 changed files with 2894 additions and 2605 deletions

149
routlin/mod_networkd.py Normal file
View file

@ -0,0 +1,149 @@
"""
mod_networkd.py -- systemd-networkd configuration for VLAN interfaces.
Generates .netdev and .network files for each VLAN, removes legacy
conflicting files, and triggers a networkd reload when content changes.
"""
import subprocess
from pathlib import Path
import mod_shared as shared
import mod_validation as validation
NETWORKD_DIR = Path("/etc/systemd/network")
# ===================================================================
# Build
# ===================================================================
def build_netdev(vlan, vid, iface):
return "\n".join([
"# Generated by core.py -- do not edit manually.",
"# Edit config.json and re-run: sudo python3 core.py --apply",
"",
"[NetDev]",
f"Name={iface}",
"Kind=vlan",
"",
"[VLAN]",
f"Id={vid}",
"",
])
def build_network(vlan, vid, iface, all_vlan_ids):
network = shared.network_for(vlan)
prefix = network.prefixlen
lines = [
"# Generated by core.py -- do not edit manually.",
"# Edit config.json and re-run: sudo python3 core.py --apply",
"",
"[Match]",
f"Name={iface}",
"",
"[Network]",
"DHCP=no",
"LinkLocalAddressing=no",
]
for ident in vlan["server_identities"]:
lines.append(f"# {ident['description']}")
lines.append(f"Address={ident['ip']}/{prefix}")
if shared.is_physical(vlan):
lines.append("")
for v in all_vlan_ids:
if v != 1:
lines.append(f"VLAN={iface}.{v}")
lines.append("")
return "\n".join(lines)
# ===================================================================
# Apply
# ===================================================================
def find_legacy_files(managed_interfaces):
to_remove = []
for pattern in ("*.network", "*.netdev"):
for f in NETWORKD_DIR.glob(pattern):
if f.name.startswith(f"10-{shared.PRODUCT_NAME}-"):
continue
try:
content = f.read_text()
except OSError:
continue
for iface in managed_interfaces:
if f"Name={iface}" in content:
to_remove.append(f)
break
return to_remove
def apply_networkd(data, dry_run=False, only_if_changed=False):
"""Write systemd-networkd files and reload.
If only_if_changed=True, write files only when content differs from disk
and skip the networkd reload if nothing changed. Used by --apply mode.
"""
all_vlan_ids = [v.get('vlan_id') for v in data["vlans"] if not validation.is_wg(v)]
managed_ifaces = [validation.derive_interface(v, data) for v in data["vlans"]]
changed = False
legacy = find_legacy_files(managed_ifaces)
if legacy:
print("Removing legacy networkd files:")
for f in legacy:
if not dry_run:
f.unlink()
changed = True
print(f" {'[dry-run] would remove' if dry_run else 'Removed'}: {f}")
print()
for vlan in data["vlans"]:
if validation.is_wg(vlan):
continue
iface = validation.derive_interface(vlan, data)
vid = vlan.get('vlan_id')
stem = shared.networkd_stem(vlan)
if not shared.is_physical(vlan):
netdev_path = NETWORKD_DIR / f"{stem}.netdev"
netdev_content = build_netdev(vlan, vid, iface)
if dry_run:
print(f"# -- {netdev_path} (dry-run) --")
print(netdev_content)
else:
existing = netdev_path.read_text() if netdev_path.exists() else None
if existing != netdev_content:
netdev_path.write_text(netdev_content)
print(f"Written: {netdev_path}")
changed = True
elif not only_if_changed:
print(f"Unchanged: {netdev_path}")
network_path = NETWORKD_DIR / f"{stem}.network"
network_content = build_network(vlan, vid, iface, all_vlan_ids)
if dry_run:
print(f"# -- {network_path} (dry-run) --")
print(network_content)
else:
existing = network_path.read_text() if network_path.exists() else None
if existing != network_content:
network_path.write_text(network_content)
print(f"Written: {network_path}")
changed = True
elif not only_if_changed:
print(f"Unchanged: {network_path}")
if not dry_run:
if changed:
print("Reloading systemd-networkd...")
result = subprocess.run(
["networkctl", "reload"], capture_output=True, text=True
)
if result.returncode != 0:
print(f"WARNING: networkctl reload returned non-zero:\n{result.stderr.strip()}")
else:
print("systemd-networkd reloaded.")
elif only_if_changed:
print("systemd-networkd: no changes. Good.")