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
|
|
@ -48,6 +48,12 @@ type Daemon struct {
|
|||
State DaemonState
|
||||
lastNotifiedVersion string
|
||||
|
||||
// Managed-VPN split-tunnel state, set by cmd/daemon.go before Run and folded
|
||||
// into DaemonState on every write so external tools (`unarr vpn status`) see it.
|
||||
vpnActive bool
|
||||
vpnMode string
|
||||
vpnServer string
|
||||
|
||||
// Watching tracks whether a user is viewing download progress in the web UI.
|
||||
Watching atomic.Bool
|
||||
|
||||
|
|
@ -70,6 +76,14 @@ func NewDaemon(cfg DaemonConfig, client *Client) *Daemon {
|
|||
// SyncClient returns the sync client for external wiring.
|
||||
func (d *Daemon) SyncClient() *SyncClient { return d.sync }
|
||||
|
||||
// SetVPNState records the managed-VPN split-tunnel state so it's reflected in the
|
||||
// daemon state file (read by `unarr vpn status`). Call before Run.
|
||||
func (d *Daemon) SetVPNState(active bool, mode, server string) {
|
||||
d.vpnActive = active
|
||||
d.vpnMode = mode
|
||||
d.vpnServer = server
|
||||
}
|
||||
|
||||
// UpdateStreamPort updates the stream port reported in sync requests.
|
||||
func (d *Daemon) UpdateStreamPort(port int) {
|
||||
d.cfg.StreamPort = port
|
||||
|
|
@ -91,6 +105,9 @@ func (d *Daemon) Register(ctx context.Context) error {
|
|||
TailscaleIP: d.cfg.TailscaleIP,
|
||||
HWAccel: d.cfg.HWAccel,
|
||||
MaxTranscodeHeight: d.cfg.MaxTranscodeHeight,
|
||||
VPNActive: d.vpnActive,
|
||||
VPNMode: d.vpnMode,
|
||||
VPNServer: d.vpnServer,
|
||||
}
|
||||
if free, total, err := DiskInfo(d.cfg.DownloadDir); err == nil {
|
||||
req.DiskFreeBytes = free
|
||||
|
|
@ -141,6 +158,9 @@ func (d *Daemon) Register(ctx context.Context) error {
|
|||
PID: os.Getpid(),
|
||||
StartedAt: now,
|
||||
MethodStats: make(map[string]int),
|
||||
VPNActive: d.vpnActive,
|
||||
VPNMode: d.vpnMode,
|
||||
VPNServer: d.vpnServer,
|
||||
}
|
||||
WriteState(&d.State)
|
||||
|
||||
|
|
@ -195,6 +215,9 @@ func (d *Daemon) Run(ctx context.Context) error {
|
|||
d.sync.OnWatchingChange = func(watching bool) {
|
||||
d.Watching.Store(watching)
|
||||
}
|
||||
d.sync.GetVPNState = func() (bool, string, string) {
|
||||
return d.vpnActive, d.vpnMode, d.vpnServer
|
||||
}
|
||||
d.sync.OnSyncSuccess = func() {
|
||||
d.State.LastHeartbeat = time.Now()
|
||||
if d.GetActiveCount != nil {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue