fix(wireguard): Tunnel reißt nie ab + Client-Endpoint auto-befüllt — v1.2.100
Zwei Bugs, die WireGuard-Verbindungen verhinderten/abrissen: 1) Client-Config-Endpoint war hartkodierter Platzhalter REPLACE_WITH_PUBLIC_HOST → neue Clients bauten nie einen Tunnel auf (Host löst nicht auf). Jetzt: WireguardHandler.PublicHost (aus setup.json FQDN, main.go) → Endpoint = <fqdn>:<port>. Platzhalter nur noch als Fallback wenn FQDN unbekannt. 2) Renderer machte bei JEDER Config-Änderung 'systemctl restart wg-quick@<iface>' → voller Link-Flap, alle Peers droppen (verstößt gegen 'wireguard darf nie abbrechen'). Jetzt: laufendes Interface → 'wg-quick strip | wg syncconf' (Peers/Listen-Port live, KEIN Abbruch); nur erstmaliges Hochfahren via systemctl start; restart nur noch als Fallback mit WARN. interfaceExists() via 'ip link show'. Neue sudoers: wg syncconf *, wg-quick strip *. Ein edgeguard-api-Restart (Deploy) fasst wg-quick@<iface> nicht an → Tunnel bleibt während Deploy bestehen. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,15 +1,42 @@
|
||||
package wireguard
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
// wg-quick is managed via systemd unit instances (wg-quick@<iface>).
|
||||
// Reload-via-syncconf would be cheaper (no link flap) but needs more
|
||||
// per-change diffing — for v1 we restart the unit, which takes ~1s
|
||||
// and re-establishes peers cleanly. The sudoers entry shipped in
|
||||
// postinst whitelists exactly these three commands.
|
||||
// Für ein BEREITS laufendes Interface werden Config-Änderungen per
|
||||
// `wg syncconf` LIVE angewendet (siehe syncWGQuick) — ohne Link-Flap,
|
||||
// damit bestehende Tunnel nie abreißen. Nur das erstmalige Hochfahren
|
||||
// (Interface noch nicht vorhanden) nutzt `systemctl start`. restart bleibt
|
||||
// als Fallback, falls syncconf nicht erlaubt/möglich ist. Die sudoers-
|
||||
// Einträge (postinst) whitelisten exakt diese Kommandos.
|
||||
|
||||
// interfaceExists meldet ob das wg-Interface aktuell existiert (also von
|
||||
// wg-quick bereits hochgefahren wurde). `ip link show` braucht kein root.
|
||||
func interfaceExists(iface string) bool {
|
||||
return exec.Command("/usr/bin/ip", "link", "show", iface).Run() == nil
|
||||
}
|
||||
|
||||
// syncWGQuick wendet Config-Änderungen LIVE auf ein laufendes Interface an
|
||||
// (`wg syncconf`) — Peers werden hinzugefügt/entfernt/aktualisiert und der
|
||||
// Listen-Port gesetzt, OHNE den Tunnel abzureißen. `wg-quick strip` liefert
|
||||
// die reine wg-Config (ohne Address/MTU/Routes-Direktiven). Beides braucht
|
||||
// root (Config ist root:root 700) → sudo.
|
||||
func syncWGQuick(iface string) error {
|
||||
stripped, err := exec.Command("sudo", "-n", "/usr/bin/wg-quick", "strip", iface).Output()
|
||||
if err != nil {
|
||||
return fmt.Errorf("wg-quick strip %s: %w", iface, err)
|
||||
}
|
||||
sync := exec.Command("sudo", "-n", "/usr/bin/wg", "syncconf", iface, "/dev/stdin")
|
||||
sync.Stdin = bytes.NewReader(stripped)
|
||||
if out, err := sync.CombinedOutput(); err != nil {
|
||||
return fmt.Errorf("wg syncconf %s: %w: %s", iface, err, string(out))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func startWGQuick(iface string) error {
|
||||
cmd := exec.Command("sudo", "-n", "/usr/bin/systemctl", "start", "wg-quick@"+iface+".service")
|
||||
|
||||
Reference in New Issue
Block a user