#!/usr/bin/env python3 """ DebTik Network Interface Name Reconciler Runs before networking.service / network-pre.target on boot. Reads /etc/debtik/metadata.json (or legacy /etc/proxmox-router/metadata.json). Matches physical/veth devices by hardware MAC address and renames them to match the target interface name. Also brings UP interfaces that have configured IP addresses or auto_up set. """ import json import os import subprocess import sys import time METADATA_PATHS = [ "/etc/debtik/metadata.json", "/etc/proxmox-router/metadata.json" ] def log(msg): print(f"[debtik-ifnames] {msg}", file=sys.stderr) def get_current_interfaces(): res = {} net_dir = "/sys/class/net" if not os.path.exists(net_dir): return res for dev in os.listdir(net_dir): if dev == "lo": continue addr_file = os.path.join(net_dir, dev, "address") if os.path.exists(addr_file): try: with open(addr_file, "r") as f: mac = f.read().strip().lower() if mac and mac != "00:00:00:00:00:00": res[dev] = mac except Exception: pass return res def rename_device(old_name, new_name): try: if os.path.exists(f"/sys/class/net/{new_name}"): tmp_name = f"tmp{int(time.time() * 1000) % 100000}" subprocess.run(["ip", "link", "set", "dev", new_name, "down"], check=False) subprocess.run(["ip", "link", "set", "dev", new_name, "name", tmp_name], check=False) log(f"Moved conflicting {new_name} -> {tmp_name}") subprocess.run(["ip", "link", "set", "dev", old_name, "down"], check=False) r = subprocess.run(["ip", "link", "set", "dev", old_name, "name", new_name], capture_output=True, text=True) if r.returncode == 0: log(f"Successfully renamed {old_name} -> {new_name}") return True else: log(f"Failed to rename {old_name} -> {new_name}: {r.stderr.strip()}") return False except Exception as e: log(f"Exception renaming {old_name} -> {new_name}: {e}") return False def main(): metadata = None for p in METADATA_PATHS: if os.path.exists(p): try: with open(p, "r") as f: metadata = json.load(f) break except Exception as e: log(f"Error reading {p}: {e}") if not metadata or "interfaces" not in metadata: sys.exit(0) target_map = {} auto_up_names = set() for name, item in metadata["interfaces"].items(): mac = item.get("mac", "").strip().lower() if mac: target_map[mac] = name if item.get("auto_up") or item.get("ip_addresses"): auto_up_names.add(name) if not target_map: sys.exit(0) current_devs = get_current_interfaces() for dev_name, mac in list(current_devs.items()): if mac in target_map: target_name = target_map[mac] if dev_name != target_name: log(f"Device {dev_name} has MAC {mac}, reconciling to {target_name}") if rename_device(dev_name, target_name): current_devs[target_name] = mac del current_devs[dev_name] if target_name in auto_up_names: subprocess.run(["ip", "link", "set", "dev", target_name, "up"], check=False) if __name__ == "__main__": main()