Netzwerkeinstellungen: netplan/systemd-networkd als drittes Backend + DHCP-Interface-Default-Fix
Auf einer echten, frisch installierten Ubuntu-24.04-VM (192.168.80.114, Standard-Setup, Netz per DHCP) zeigten sich zwei reale Bugs: 1. Systemeinstellungen > Netzwerkeinstellungen: 'Kein unterstütztes Backend erkannt' trotz laufendem Netz -- _detect_network_backend() kannte nur NetworkManager und dhcpcd, nicht netplan/systemd-networkd, den mit Abstand haeufigsten Fall auf einem normalen Ubuntu-Host. Neues drittes Backend 'netplan' ergaenzt: Erkennung ueber systemd-networkd + vorhandenes netplan-Binary, Moduserkennung ueber das 'dynamic'-Flag des Kernels auf der aktuellen Adresse, Anwenden ueber eine eigene Override-Datei (/etc/netplan/90-poe-manager.yaml, gewinnt alphabetisch gegen z.B. cloud-inits 50-*.yaml) plus Aufruf von netplan apply, Backup/Auto-Rollback wie bei den anderen Backends. Live verifiziert: Anwenden loeste echten DHCP-Lease-Renew aus, nach Ablauf der 45s-Bestaetigungsfrist wurde die Override-Datei automatisch wieder entfernt (Rollback funktioniert). 2. DHCP-Konfiguration: 'Keine IPv4-Adresse auf eth0 gefunden', obwohl das Interface-Dropdown korrekt 'enp1s0' zeigte -- DHCP_DEFAULTS hatte 'eth0' hart kodiert (Annahme aus einer aelteren Testumgebung), was auf Systemen mit Predictable Network Interface Names schlicht nicht existiert. _load_dhcp_settings() korrigiert das jetzt: liegt der gespeicherte/Default-Wert nicht unter den tatsaechlich vorhandenen Schnittstellen, wird automatisch die erste echte vorgeschlagen. Neue Abhaengigkeit PyYAML fuer das Schreiben der netplan-Override-Datei.
This commit is contained in:
+98
-8
@@ -20,6 +20,7 @@ from cryptography.hazmat.primitives import hashes
|
||||
from datetime import datetime
|
||||
import base64, ipaddress, shutil, socket, sqlite3, glob, json, os, re, secrets, subprocess, threading, time, traceback
|
||||
import paramiko
|
||||
import yaml
|
||||
|
||||
|
||||
class _SendAllSocketProxy:
|
||||
@@ -1244,13 +1245,17 @@ def run_check_now():
|
||||
|
||||
NETWORK_REVERT_SECONDS = 45
|
||||
_pending_network_revert = {} # {"timer": Timer, "backend":..., "interface":..., "backup": {...}}
|
||||
# Eigene netplan-Override-Datei (siehe _apply_network_config) — bewusst nicht
|
||||
# die von cloud-init/dem Distributor mitgelieferte Datei, damit ein Rollback
|
||||
# nie versehentlich fremden Inhalt überschreibt.
|
||||
NETPLAN_CONFIG_PATH = "/etc/netplan/90-poe-manager.yaml"
|
||||
|
||||
|
||||
def _detect_network_backend():
|
||||
"""Rein lesend: welcher der beiden gängigen Netzwerk-Manager auf
|
||||
Debian/Raspberry Pi OS ist aktiv? Keine Annahme, falls keins von
|
||||
beiden läuft (z.B. in dieser WSL-Testumgebung, die ihr Netz über den
|
||||
Hyper-V-Adapter bezieht) — dann bleibt die Seite bewusst rein lesend."""
|
||||
"""Rein lesend: welcher der gängigen Netzwerk-Manager ist aktiv? Keine
|
||||
Annahme, falls keiner läuft (z.B. in dieser WSL-Testumgebung, die ihr
|
||||
Netz über den Hyper-V-Adapter bezieht) — dann bleibt die Seite bewusst
|
||||
rein lesend."""
|
||||
for service, name in (("NetworkManager", "networkmanager"), ("dhcpcd", "dhcpcd")):
|
||||
try:
|
||||
result = subprocess.run(["systemctl", "is-active", service], capture_output=True, text=True, timeout=5)
|
||||
@@ -1258,6 +1263,17 @@ def _detect_network_backend():
|
||||
return name
|
||||
except Exception:
|
||||
pass
|
||||
# Standard-Ubuntu (Server- wie Cloud-Images) verwaltet sein Netz
|
||||
# inzwischen über netplan + systemd-networkd statt NetworkManager oder
|
||||
# dhcpcd — mit Abstand der häufigste Fall auf einem "normalen" frisch
|
||||
# installierten Ubuntu-Host (live an einer echten Ubuntu-24.04-VM
|
||||
# verifiziert, die ihre IP per DHCP bezieht).
|
||||
try:
|
||||
result = subprocess.run(["systemctl", "is-active", "systemd-networkd"], capture_output=True, text=True, timeout=5)
|
||||
if result.stdout.strip() == "active" and shutil.which("netplan"):
|
||||
return "netplan"
|
||||
except Exception:
|
||||
pass
|
||||
return "unknown"
|
||||
|
||||
|
||||
@@ -1301,6 +1317,12 @@ def _read_network_state(interface, backend):
|
||||
mode = "static" if re.search(r"^\s*static\s+ip_address=", block, re.M) else "dhcp"
|
||||
except OSError:
|
||||
mode = "dhcp"
|
||||
elif backend == "netplan":
|
||||
# Netplan/systemd-networkd markiert eine per DHCP bezogene Adresse
|
||||
# im Kernel mit "dynamic" — zuverlässiger als die YAML-Dateien unter
|
||||
# /etc/netplan/ zu parsen (mehrere Dateien können sich überlagern).
|
||||
if net_info.get("ok"):
|
||||
mode = "dhcp" if net_info.get("dynamic") else "static"
|
||||
return {**net_info, "dns": _read_resolv_conf_dns(), "mode": mode, "backend": backend}
|
||||
|
||||
|
||||
@@ -1328,6 +1350,15 @@ def _backup_network_config(interface, backend):
|
||||
backup["nm_connection"] = dev_out.stdout.split(":", 1)[1].strip() if ":" in dev_out.stdout else None
|
||||
except Exception:
|
||||
backup["nm_connection"] = None
|
||||
elif backend == "netplan":
|
||||
# Eigene Override-Datei sichern statt der von cloud-init/dem
|
||||
# Distributor mitgelieferten Dateien — None heißt "existierte vorher
|
||||
# nicht", dann löscht der Revert sie wieder statt sie leer zu lassen.
|
||||
try:
|
||||
with open(NETPLAN_CONFIG_PATH, encoding="utf-8") as f:
|
||||
backup["netplan_conf"] = f.read()
|
||||
except OSError:
|
||||
backup["netplan_conf"] = None
|
||||
return backup
|
||||
|
||||
|
||||
@@ -1381,7 +1412,38 @@ def _apply_network_config(backend, interface, mode, ip, prefix, gateway, dns_lis
|
||||
except Exception as e:
|
||||
return False, str(e)
|
||||
|
||||
return False, "Kein unterstütztes Netzwerk-Backend erkannt (weder NetworkManager noch dhcpcd aktiv)."
|
||||
if backend == "netplan":
|
||||
try:
|
||||
eth_cfg = {}
|
||||
if mode == "static":
|
||||
eth_cfg["dhcp4"] = False
|
||||
eth_cfg["addresses"] = [f"{ip}/{prefix}"]
|
||||
if gateway:
|
||||
eth_cfg["routes"] = [{"to": "default", "via": gateway}]
|
||||
else:
|
||||
eth_cfg["dhcp4"] = True
|
||||
if dns_list:
|
||||
# Ohne diesen Override würde networkd die eigenen
|
||||
# DNS-Server mit den per DHCP gelieferten zusammen
|
||||
# verwenden, statt sie wie bei den anderen Backends
|
||||
# (ipv4.ignore-auto-dns / dhcpcd) vollständig zu ersetzen.
|
||||
eth_cfg["dhcp4-overrides"] = {"use-dns": False}
|
||||
if dns_list:
|
||||
eth_cfg["nameservers"] = {"addresses": dns_list}
|
||||
doc = {"network": {"version": 2, "ethernets": {interface: eth_cfg}}}
|
||||
# Eigene, niedrig priorisierte Datei statt der von cloud-init
|
||||
# mitgelieferten (z.B. 50-cloud-init.yaml) — netplan wendet
|
||||
# Dateien in alphabetischer Reihenfolge an und lässt spätere
|
||||
# Werte pro Schlüssel gewinnen, das ist hier der etablierte Weg
|
||||
# für eigene Overrides statt die Distributor-Datei zu editieren.
|
||||
with open(NETPLAN_CONFIG_PATH, "w", encoding="utf-8") as f:
|
||||
yaml.safe_dump(doc, f, default_flow_style=False)
|
||||
os.chmod(NETPLAN_CONFIG_PATH, 0o600)
|
||||
return _dhcp_run_privileged(["netplan", "apply"], timeout=20)
|
||||
except Exception as e:
|
||||
return False, str(e)
|
||||
|
||||
return False, "Kein unterstütztes Netzwerk-Backend erkannt (weder NetworkManager, dhcpcd noch netplan aktiv)."
|
||||
|
||||
|
||||
def _revert_network_config(token):
|
||||
@@ -1403,6 +1465,19 @@ def _revert_network_config(token):
|
||||
"networkmanager", backup["interface"],
|
||||
backup["mode"], backup.get("ip"), backup.get("prefix"), backup.get("gateway"), backup.get("dns", []),
|
||||
)
|
||||
elif backup["backend"] == "netplan":
|
||||
if backup.get("netplan_conf") is not None:
|
||||
with open(NETPLAN_CONFIG_PATH, "w", encoding="utf-8") as f:
|
||||
f.write(backup["netplan_conf"])
|
||||
else:
|
||||
# Datei existierte vor der Änderung nicht (erste Netplan-
|
||||
# Änderung über diese Seite) — dann muss sie beim Rollback
|
||||
# auch wieder verschwinden statt leer zurückzubleiben.
|
||||
try:
|
||||
os.remove(NETPLAN_CONFIG_PATH)
|
||||
except OSError:
|
||||
pass
|
||||
subprocess.run(["netplan", "apply"], timeout=20)
|
||||
log_action_system("settings.network_revert", backup["interface"], "automatisch nach Timeout zurückgerollt")
|
||||
except Exception as e:
|
||||
app.logger.error("Network auto-revert failed: %s", e)
|
||||
@@ -1571,7 +1646,17 @@ DHCP_OPTION_TYPE_KEYS = {t[0] for t in DHCP_OPTION_TYPES}
|
||||
|
||||
|
||||
def _load_dhcp_settings():
|
||||
return {key: get_setting(key, default) for key, default in DHCP_DEFAULTS.items()}
|
||||
cfg = {key: get_setting(key, default) for key, default in DHCP_DEFAULTS.items()}
|
||||
available = _list_network_interfaces()
|
||||
if available and cfg.get("dhcp_interface") not in available:
|
||||
# Der hart codierte Default ("eth0") bzw. ein früher gespeicherter Wert
|
||||
# existiert auf Systemen mit Predictable Network Interface Names
|
||||
# (z.B. "enp1s0") nicht — dann lieber die tatsächlich vorhandene erste
|
||||
# Schnittstelle vorschlagen, statt eine erkennbar falsche anzuzeigen
|
||||
# (führte sonst dazu, dass die Erkennung fälschlich "eth0" statt der
|
||||
# im Dropdown sichtbaren echten Schnittstelle prüfte).
|
||||
cfg["dhcp_interface"] = available[0]
|
||||
return cfg
|
||||
|
||||
|
||||
def _detect_dhcp_status():
|
||||
@@ -1625,7 +1710,8 @@ def _detect_interface_network(interface):
|
||||
manuell pflegen zu lassen) — dadurch koppelt sich die generierte
|
||||
DHCP-Konfiguration immer an das Netz, in dem der Host tatsächlich
|
||||
hängt, auch wenn sich dessen IP/Subnet mal ändert."""
|
||||
result = {"ok": False, "ip": None, "prefix": None, "netmask": None, "network": None, "gateway": None, "error": None}
|
||||
result = {"ok": False, "ip": None, "prefix": None, "netmask": None, "network": None, "gateway": None,
|
||||
"dynamic": None, "error": None}
|
||||
if not interface:
|
||||
result["error"] = "Kein Interface ausgewählt."
|
||||
return result
|
||||
@@ -1640,7 +1726,11 @@ def _detect_interface_network(interface):
|
||||
return result
|
||||
ip_str, prefix = match.group(1), int(match.group(2))
|
||||
network = ipaddress.IPv4Network(f"{ip_str}/{prefix}", strict=False)
|
||||
result.update(ok=True, ip=ip_str, prefix=prefix, network=network, netmask=str(network.netmask))
|
||||
# Der Kernel markiert eine per DHCP bezogene Adresse mit "dynamic" —
|
||||
# unabhängig vom Backend eine zuverlässige Quelle für den aktuellen
|
||||
# DHCP/Statisch-Modus (genutzt von der netplan-Erkennung unten).
|
||||
dynamic = bool(re.search(rf"inet {re.escape(ip_str)}/{prefix}\b[^\n]*\bdynamic\b", addr_out.stdout))
|
||||
result.update(ok=True, ip=ip_str, prefix=prefix, network=network, netmask=str(network.netmask), dynamic=dynamic)
|
||||
|
||||
route_out = subprocess.run(
|
||||
["ip", "-4", "route", "show", "default", "dev", interface],
|
||||
|
||||
@@ -13,6 +13,7 @@ MarkupSafe==3.0.2
|
||||
paramiko==3.5.1
|
||||
pycparser==2.23
|
||||
PyNaCl==1.5.0
|
||||
PyYAML==6.0.2
|
||||
simple-websocket==1.1.0
|
||||
Werkzeug==3.1.3
|
||||
|
||||
@@ -20,3 +21,7 @@ Werkzeug==3.1.3
|
||||
# (reines Python) statt eines lokalen pty + System-ssh-Client und läuft
|
||||
# damit plattformunabhängig — auch unter Windows. Die SSH-Verbindung wird
|
||||
# dabei immer serverseitig (vom PoE-Server) aufgebaut, nie vom Browser aus.
|
||||
#
|
||||
# PyYAML wird für das netplan-Backend der Host-Netzwerkeinstellungen
|
||||
# gebraucht (Systemeinstellungen → Netzwerkeinstellungen) — schreibt/liest
|
||||
# die eigene Override-Datei unter /etc/netplan/.
|
||||
|
||||
@@ -72,7 +72,7 @@
|
||||
{% if net_backend == 'unknown' %}
|
||||
<span class="pill unknown">Kein unterstütztes Backend erkannt</span>
|
||||
{% else %}
|
||||
<span class="pill online">{{ 'NetworkManager' if net_backend == 'networkmanager' else 'dhcpcd' }}</span>
|
||||
<span class="pill online">{{ {'networkmanager': 'NetworkManager', 'dhcpcd': 'dhcpcd', 'netplan': 'netplan'}[net_backend] }}</span>
|
||||
{% endif %}
|
||||
{% if net_state %}
|
||||
<span class="mono text-faint" style="font-size:12px;">
|
||||
@@ -143,7 +143,7 @@
|
||||
</form>
|
||||
{% elif net_backend == 'unknown' %}
|
||||
<p class="text-faint" style="font-size:12.5px;">
|
||||
Weder NetworkManager noch dhcpcd aktiv erkannt — Netzwerkänderungen über diese Seite sind deaktiviert.
|
||||
Weder NetworkManager, dhcpcd noch netplan/systemd-networkd aktiv erkannt — Netzwerkänderungen über diese Seite sind deaktiviert.
|
||||
Bitte die Netzwerkkonfiguration dieses Hosts manuell vornehmen.
|
||||
</p>
|
||||
{% else %}
|
||||
|
||||
Reference in New Issue
Block a user