Development
This commit is contained in:
parent
205d6889df
commit
58ab569e42
27 changed files with 2894 additions and 2605 deletions
149
routlin/mod_networkd.py
Normal file
149
routlin/mod_networkd.py
Normal 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.")
|
||||
Loading…
Add table
Add a link
Reference in a new issue