debtik-ifnames 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101
  1. #!/usr/bin/env python3
  2. """
  3. DebTik Network Interface Name Reconciler
  4. Runs before networking.service / network-pre.target on boot.
  5. Reads /etc/debtik/metadata.json (or legacy /etc/proxmox-router/metadata.json).
  6. Matches physical/veth devices by hardware MAC address and renames them to match the target interface name.
  7. Also brings UP interfaces that have configured IP addresses or auto_up set.
  8. """
  9. import json
  10. import os
  11. import subprocess
  12. import sys
  13. import time
  14. METADATA_PATHS = [
  15. "/etc/debtik/metadata.json",
  16. "/etc/proxmox-router/metadata.json"
  17. ]
  18. def log(msg):
  19. print(f"[debtik-ifnames] {msg}", file=sys.stderr)
  20. def get_current_interfaces():
  21. res = {}
  22. net_dir = "/sys/class/net"
  23. if not os.path.exists(net_dir):
  24. return res
  25. for dev in os.listdir(net_dir):
  26. if dev == "lo":
  27. continue
  28. addr_file = os.path.join(net_dir, dev, "address")
  29. if os.path.exists(addr_file):
  30. try:
  31. with open(addr_file, "r") as f:
  32. mac = f.read().strip().lower()
  33. if mac and mac != "00:00:00:00:00:00":
  34. res[dev] = mac
  35. except Exception:
  36. pass
  37. return res
  38. def rename_device(old_name, new_name):
  39. try:
  40. if os.path.exists(f"/sys/class/net/{new_name}"):
  41. tmp_name = f"tmp{int(time.time() * 1000) % 100000}"
  42. subprocess.run(["ip", "link", "set", "dev", new_name, "down"], check=False)
  43. subprocess.run(["ip", "link", "set", "dev", new_name, "name", tmp_name], check=False)
  44. log(f"Moved conflicting {new_name} -> {tmp_name}")
  45. subprocess.run(["ip", "link", "set", "dev", old_name, "down"], check=False)
  46. r = subprocess.run(["ip", "link", "set", "dev", old_name, "name", new_name], capture_output=True, text=True)
  47. if r.returncode == 0:
  48. log(f"Successfully renamed {old_name} -> {new_name}")
  49. return True
  50. else:
  51. log(f"Failed to rename {old_name} -> {new_name}: {r.stderr.strip()}")
  52. return False
  53. except Exception as e:
  54. log(f"Exception renaming {old_name} -> {new_name}: {e}")
  55. return False
  56. def main():
  57. metadata = None
  58. for p in METADATA_PATHS:
  59. if os.path.exists(p):
  60. try:
  61. with open(p, "r") as f:
  62. metadata = json.load(f)
  63. break
  64. except Exception as e:
  65. log(f"Error reading {p}: {e}")
  66. if not metadata or "interfaces" not in metadata:
  67. sys.exit(0)
  68. target_map = {}
  69. auto_up_names = set()
  70. for name, item in metadata["interfaces"].items():
  71. mac = item.get("mac", "").strip().lower()
  72. if mac:
  73. target_map[mac] = name
  74. if item.get("auto_up") or item.get("ip_addresses"):
  75. auto_up_names.add(name)
  76. if not target_map:
  77. sys.exit(0)
  78. current_devs = get_current_interfaces()
  79. for dev_name, mac in list(current_devs.items()):
  80. if mac in target_map:
  81. target_name = target_map[mac]
  82. if dev_name != target_name:
  83. log(f"Device {dev_name} has MAC {mac}, reconciling to {target_name}")
  84. if rename_device(dev_name, target_name):
  85. current_devs[target_name] = mac
  86. del current_devs[dev_name]
  87. if target_name in auto_up_names:
  88. subprocess.run(["ip", "link", "set", "dev", target_name, "up"], check=False)
  89. if __name__ == "__main__":
  90. main()