Make Start/Stop enable/disable the wg-quick unit, not just start/stop it

A server brought up via the UI stayed active but not enabled, so a reboot
silently dropped it (and its PostUp cross-tunnel routes) with no error to
point at. Start now runs enable --now, Stop runs disable --now, so
"running now" and "survives a reboot" are the same action.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ATVUwTa4Pqwq26orW5BcDW
This commit is contained in:
sysops
2026-07-29 13:37:44 +02:00
co-authored by Claude Sonnet 5
parent 83b1da291f
commit 3e57de1b44
2 changed files with 153 additions and 6 deletions
+17 -6
View File
@@ -17,16 +17,22 @@ func UnitName(iface string) string {
}
// Start brings up the given WireGuard interface via
// `systemctl start wg-quick@<iface>.service`.
// `systemctl enable --now wg-quick@<iface>.service`. Enabling (not just
// starting) is deliberate: a server that's up now but not enabled silently
// vanishes on the next reboot, taking its PostUp-installed cross-tunnel
// routes with it, with no error anywhere to point at - this makes "started
// from the UI" and "survives a reboot" the same action instead of two.
func Start(ctx context.Context, iface string) error {
EnsureIPForwarding()
return runSystemctl(ctx, "start", iface)
return runSystemctlArgs(ctx, iface, "enable", "--now")
}
// Stop brings down the given WireGuard interface via
// `systemctl stop wg-quick@<iface>.service`.
// `systemctl disable --now wg-quick@<iface>.service`. Disabling mirrors
// Start's enable: an admin-initiated stop should stay stopped after a
// reboot too, not silently come back.
func Stop(ctx context.Context, iface string) error {
return runSystemctl(ctx, "stop", iface)
return runSystemctlArgs(ctx, iface, "disable", "--now")
}
// Restart restarts the given WireGuard interface via
@@ -57,13 +63,18 @@ func setSysctl(path string) {
}
func runSystemctl(ctx context.Context, action, iface string) error {
return runSystemctlArgs(ctx, iface, action)
}
func runSystemctlArgs(ctx context.Context, iface string, action ...string) error {
if !util.ValidateInterfaceName(iface) {
return fmt.Errorf("invalid interface name: %q", iface)
}
cmd := exec.CommandContext(ctx, "systemctl", action, UnitName(iface))
args := append(append([]string{}, action...), UnitName(iface))
cmd := exec.CommandContext(ctx, "systemctl", args...)
out, err := cmd.CombinedOutput()
if err != nil {
return fmt.Errorf("systemctl %s %s failed: %w: %s", action, UnitName(iface), err, string(out))
return fmt.Errorf("systemctl %s %s failed: %w: %s", strings.Join(action, " "), UnitName(iface), err, string(out))
}
return nil
}