feat(vpn): unarr vpn command + report/arbitrate the WireGuard slot
Add `unarr vpn` (status/enable/disable, with `status --check`) to manage the managed WireGuard split-tunnel from the CLI. The daemon now reports its split-tunnel state (active, mode, exit server) to the web on register and on every sync, and sends its agent id when fetching the VPN config so the web can arbitrate the single WireGuard slot (1 VPNResellers account = 1 WG keypair = 1 concurrent connection): the first agent claims it; the rest are told to run OpenVPN on their own host (1 WireGuard + up to 9 OpenVPN = 10). `status --check` passes probe=1 so it validates provisioning without claiming the slot. VPNActive drops omitempty so a downed tunnel reaches the server and frees the slot. Bumps to 0.9.2 with CHANGELOG + README VPN section.
This commit is contained in:
parent
d0094e84bb
commit
5d44ee704c
11 changed files with 373 additions and 6 deletions
|
|
@ -18,6 +18,7 @@ import (
|
|||
"net"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
neturl "net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
|
@ -56,9 +57,22 @@ type fetchResponse struct {
|
|||
}
|
||||
|
||||
// FetchConfig retrieves the agent's WireGuard .conf from the web API. Auth is
|
||||
// `Authorization: Bearer <apiKey>` (the agent-auth scheme).
|
||||
func FetchConfig(ctx context.Context, apiURL, apiKey, userAgent string) (string, error) {
|
||||
// `Authorization: Bearer <apiKey>` (the agent-auth scheme). agentId lets the web
|
||||
// arbitrate the single WireGuard slot (first agent to ask claims it; others get
|
||||
// 409 → ErrSlotOnDevice and should use OpenVPN on their host instead).
|
||||
func FetchConfig(ctx context.Context, apiURL, apiKey, userAgent, agentID string, probe bool) (string, error) {
|
||||
q := neturl.Values{}
|
||||
if agentID != "" {
|
||||
q.Set("agentId", agentID)
|
||||
}
|
||||
if probe {
|
||||
// Validate provisioning without claiming the WireGuard slot (status --check).
|
||||
q.Set("probe", "1")
|
||||
}
|
||||
url := strings.TrimSuffix(apiURL, "/") + "/api/internal/agent/vpn-config"
|
||||
if len(q) > 0 {
|
||||
url += "?" + q.Encode()
|
||||
}
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
|
||||
if err != nil {
|
||||
return "", &FetchError{ErrUpstream, err.Error()}
|
||||
|
|
@ -103,6 +117,10 @@ func FetchConfig(ctx context.Context, apiURL, apiKey, userAgent string) (string,
|
|||
type Tunnel struct {
|
||||
dev *device.Device
|
||||
Net *netstack.Net
|
||||
// Endpoint is the resolved ip:port of the WireGuard server this tunnel
|
||||
// exits through — surfaced in `unarr vpn status` so the user can see which
|
||||
// VPN server their torrent traffic is routed out of.
|
||||
Endpoint string
|
||||
}
|
||||
|
||||
// Up parses a WireGuard .conf and brings up the tunnel in userspace.
|
||||
|
|
@ -132,7 +150,7 @@ func Up(confText string) (*Tunnel, error) {
|
|||
return nil, fmt.Errorf("wireguard up: %w", err)
|
||||
}
|
||||
|
||||
return &Tunnel{dev: dev, Net: tnet}, nil
|
||||
return &Tunnel{dev: dev, Net: tnet, Endpoint: wc.endpoint}, nil
|
||||
}
|
||||
|
||||
// Close tears the tunnel down.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue