Add wg-quick/systemctl service control and bulk-delete list views
Per-server Start/Stop/Restart via systemd wg-quick@<iface>.service units plus live status badge, and a table/list-view toggle with checkbox bulk-delete for both the server list and per-server client list. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PvrfUytqd74H6WcQkRzFM4
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
3a89a3cb5c
commit
c29edfdcc3
@@ -0,0 +1,88 @@
|
||||
package wireguard
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os/exec"
|
||||
|
||||
"github.com/ngoduykhanh/wireguard-ui/util"
|
||||
)
|
||||
|
||||
// UnitName returns the systemd unit name managing the given WireGuard
|
||||
// interface via wg-quick, e.g. "wg-quick@wg-home.service".
|
||||
func UnitName(iface string) string {
|
||||
return "wg-quick@" + iface + ".service"
|
||||
}
|
||||
|
||||
// Start brings up the given WireGuard interface via
|
||||
// `systemctl start wg-quick@<iface>.service`.
|
||||
func Start(ctx context.Context, iface string) error {
|
||||
return runSystemctl(ctx, "start", iface)
|
||||
}
|
||||
|
||||
// Stop brings down the given WireGuard interface via
|
||||
// `systemctl stop wg-quick@<iface>.service`.
|
||||
func Stop(ctx context.Context, iface string) error {
|
||||
return runSystemctl(ctx, "stop", iface)
|
||||
}
|
||||
|
||||
// Restart restarts the given WireGuard interface via
|
||||
// `systemctl restart wg-quick@<iface>.service`.
|
||||
func Restart(ctx context.Context, iface string) error {
|
||||
return runSystemctl(ctx, "restart", iface)
|
||||
}
|
||||
|
||||
func runSystemctl(ctx context.Context, action, iface string) error {
|
||||
if !util.ValidateInterfaceName(iface) {
|
||||
return fmt.Errorf("invalid interface name: %q", iface)
|
||||
}
|
||||
cmd := exec.CommandContext(ctx, "systemctl", action, UnitName(iface))
|
||||
out, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return fmt.Errorf("systemctl %s %s failed: %w: %s", action, UnitName(iface), err, string(out))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Status reports whether the given WireGuard interface's wg-quick unit is
|
||||
// currently active and whether it is enabled to start on boot. Only real
|
||||
// exec failures (e.g. systemctl binary missing) are returned as err - a
|
||||
// unit being inactive/disabled/failed is a normal, non-error result
|
||||
// reflected in the returned booleans.
|
||||
func Status(ctx context.Context, iface string) (active bool, enabled bool, err error) {
|
||||
if !util.ValidateInterfaceName(iface) {
|
||||
return false, false, fmt.Errorf("invalid interface name: %q", iface)
|
||||
}
|
||||
|
||||
unit := UnitName(iface)
|
||||
|
||||
active, err = systemctlBoolCheck(ctx, "is-active", unit)
|
||||
if err != nil {
|
||||
return false, false, err
|
||||
}
|
||||
|
||||
enabled, err = systemctlBoolCheck(ctx, "is-enabled", unit)
|
||||
if err != nil {
|
||||
return active, false, err
|
||||
}
|
||||
|
||||
return active, enabled, nil
|
||||
}
|
||||
|
||||
// systemctlBoolCheck runs `systemctl <verb> <unit>` and interprets a
|
||||
// non-zero exit code as a normal "false" result (inactive/disabled/failed),
|
||||
// not an error. Only a failure to execute systemctl at all (binary missing,
|
||||
// context cancelled, ...) is surfaced as err.
|
||||
func systemctlBoolCheck(ctx context.Context, verb, unit string) (bool, error) {
|
||||
cmd := exec.CommandContext(ctx, "systemctl", verb, unit)
|
||||
err := cmd.Run()
|
||||
if err == nil {
|
||||
return true, nil
|
||||
}
|
||||
if _, ok := err.(*exec.ExitError); ok {
|
||||
// systemctl ran fine and just reported a non-active/non-enabled
|
||||
// state via its exit code - not an execution error.
|
||||
return false, nil
|
||||
}
|
||||
return false, fmt.Errorf("systemctl %s %s failed to execute: %w", verb, unit, err)
|
||||
}
|
||||
Reference in New Issue
Block a user