Initial Navis client with NaiveProxy, profiles, ping and git updates
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,13 @@
|
||||
//go:build !windows
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
func main() {
|
||||
fmt.Fprintln(os.Stderr, "Navis GUI is Windows-only (WebView2). Use vpnclient CLI on this platform.")
|
||||
os.Exit(1)
|
||||
}
|
||||
@@ -0,0 +1,385 @@
|
||||
//go:build windows
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"github.com/jchv/go-webview2"
|
||||
"golang.org/x/sys/windows"
|
||||
|
||||
"vpnclient/internal/appui"
|
||||
"vpnclient/internal/config"
|
||||
"vpnclient/internal/core"
|
||||
"vpnclient/internal/netcheck"
|
||||
"vpnclient/internal/protocols/naive"
|
||||
"vpnclient/internal/update"
|
||||
)
|
||||
|
||||
type app struct {
|
||||
mu sync.Mutex
|
||||
mgr *core.Manager
|
||||
cfgPath string
|
||||
logBuf *bytes.Buffer
|
||||
updateStatus update.Status
|
||||
pings []netcheck.Result
|
||||
webview webview2.WebView
|
||||
}
|
||||
|
||||
type uiState struct {
|
||||
Connected bool `json:"connected"`
|
||||
Profile string `json:"profile,omitempty"`
|
||||
ActiveProfile string `json:"active_profile,omitempty"`
|
||||
Protocol string `json:"protocol,omitempty"`
|
||||
HTTPProxy string `json:"http_proxy,omitempty"`
|
||||
SOCKSProxy string `json:"socks_proxy,omitempty"`
|
||||
SystemProxy bool `json:"system_proxy"`
|
||||
Proxy string `json:"proxy"`
|
||||
CoreReady bool `json:"core_ready"`
|
||||
CorePath string `json:"core_path,omitempty"`
|
||||
ConfigPath string `json:"config_path"`
|
||||
Profiles []config.ProfileInfo `json:"profiles"`
|
||||
Version string `json:"version"`
|
||||
Update update.Status `json:"update"`
|
||||
Pings []netcheck.Result `json:"pings"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
log.SetFlags(log.LstdFlags | log.Lshortfile)
|
||||
|
||||
cfgPath, err := config.LocateConfig()
|
||||
if err != nil {
|
||||
fatalDialog("Не удалось найти путь конфига: %v", err)
|
||||
}
|
||||
if _, err := os.Stat(cfgPath); os.IsNotExist(err) {
|
||||
if err := config.WriteExample(cfgPath); err != nil {
|
||||
fatalDialog("Не удалось создать конфиг: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
cfg, err := config.Load(cfgPath)
|
||||
if err != nil {
|
||||
fatalDialog("Ошибка конфига %s: %v", cfgPath, err)
|
||||
}
|
||||
|
||||
logBuf := &bytes.Buffer{}
|
||||
mgr, err := core.NewManager(cfgPath, cfg, logBuf)
|
||||
if err != nil {
|
||||
fatalDialog("%v", err)
|
||||
}
|
||||
|
||||
a := &app{
|
||||
mgr: mgr,
|
||||
cfgPath: cfgPath,
|
||||
logBuf: logBuf,
|
||||
updateStatus: update.Status{
|
||||
Current: update.CurrentVersion,
|
||||
ManifestURL: update.DefaultManifestURL,
|
||||
},
|
||||
}
|
||||
|
||||
dataPath := filepath.Join(filepath.Dir(cfgPath), ".navis-webview")
|
||||
_ = os.MkdirAll(dataPath, 0o755)
|
||||
|
||||
go func() {
|
||||
if _, err := naive.ResolveBinary(mgr.BinDir()); err == nil {
|
||||
return
|
||||
}
|
||||
_, _ = naive.EnsureBinary(mgr.BinDir())
|
||||
}()
|
||||
|
||||
w := webview2.NewWithOptions(webview2.WebViewOptions{
|
||||
Debug: false,
|
||||
AutoFocus: true,
|
||||
DataPath: dataPath,
|
||||
WindowOptions: webview2.WindowOptions{
|
||||
Title: "Navis",
|
||||
Width: 520,
|
||||
Height: 860,
|
||||
Center: true,
|
||||
IconId: 1,
|
||||
},
|
||||
})
|
||||
if w == nil {
|
||||
fatalDialog("Не удалось создать окно WebView2.\nУстановите Microsoft Edge WebView2 Runtime.")
|
||||
}
|
||||
a.webview = w
|
||||
defer func() {
|
||||
_ = a.mgr.Disconnect()
|
||||
w.Destroy()
|
||||
}()
|
||||
|
||||
applyWindowIcon(w.Window())
|
||||
w.SetSize(520, 860, webview2.HintNone)
|
||||
|
||||
mustBind(w, "getState", a.getState)
|
||||
mustBind(w, "connect", a.connect)
|
||||
mustBind(w, "disconnect", a.disconnect)
|
||||
mustBind(w, "saveProfile", a.saveProfile)
|
||||
mustBind(w, "createProfile", a.createProfile)
|
||||
mustBind(w, "selectProfile", a.selectProfile)
|
||||
mustBind(w, "deleteProfile", a.deleteProfile)
|
||||
mustBind(w, "installCore", a.installCore)
|
||||
mustBind(w, "openURL", openURL)
|
||||
mustBind(w, "pingServers", a.pingServers)
|
||||
mustBind(w, "checkUpdate", a.checkUpdate)
|
||||
mustBind(w, "applyUpdate", a.applyUpdate)
|
||||
|
||||
go a.autoCheckUpdate()
|
||||
|
||||
w.SetHtml(appui.IndexHTML)
|
||||
w.Run()
|
||||
}
|
||||
|
||||
func mustBind(w webview2.WebView, name string, fn interface{}) {
|
||||
if err := w.Bind(name, fn); err != nil {
|
||||
fatalDialog("bind %s: %v", name, err)
|
||||
}
|
||||
}
|
||||
|
||||
func (a *app) getState() (uiState, error) {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
|
||||
st := a.mgr.Status()
|
||||
cfg := a.mgr.Config()
|
||||
proxy := ""
|
||||
active := cfg.Active
|
||||
if p, err := cfg.ActiveProfile(); err == nil {
|
||||
proxy = p.Proxy
|
||||
active = p.Name
|
||||
}
|
||||
corePath, coreErr := naive.ResolveBinary(a.mgr.BinDir())
|
||||
out := uiState{
|
||||
Connected: st.Connected,
|
||||
Profile: st.Profile,
|
||||
ActiveProfile: active,
|
||||
Protocol: string(st.Protocol),
|
||||
HTTPProxy: st.HTTPProxy,
|
||||
SOCKSProxy: st.SOCKSProxy,
|
||||
SystemProxy: cfg.SystemProxy,
|
||||
Proxy: proxy,
|
||||
CoreReady: coreErr == nil,
|
||||
CorePath: corePath,
|
||||
ConfigPath: a.cfgPath,
|
||||
Profiles: cfg.ListProfiles(),
|
||||
Version: update.CurrentVersion,
|
||||
Update: a.updateStatus,
|
||||
Pings: append([]netcheck.Result(nil), a.pings...),
|
||||
}
|
||||
if st.Connected {
|
||||
out.SystemProxy = st.SystemProxy
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (a *app) saveProfile(name, proxy string, systemProxy bool) error {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
name = strings.TrimSpace(name)
|
||||
if name == "" {
|
||||
return fmt.Errorf("укажите название профиля")
|
||||
}
|
||||
a.mgr.SetSystemProxy(systemProxy)
|
||||
return a.mgr.SaveActiveProfile(name, proxy)
|
||||
}
|
||||
|
||||
func (a *app) createProfile(name, proxy string, systemProxy bool) error {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
name = strings.TrimSpace(name)
|
||||
if name == "" {
|
||||
return fmt.Errorf("укажите название профиля")
|
||||
}
|
||||
a.mgr.SetSystemProxy(systemProxy)
|
||||
return a.mgr.SaveProfile(name, proxy)
|
||||
}
|
||||
|
||||
func (a *app) selectProfile(name string) error {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
return a.mgr.SetActiveProfile(name)
|
||||
}
|
||||
|
||||
func (a *app) deleteProfile(name string) error {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
return a.mgr.DeleteProfile(name)
|
||||
}
|
||||
|
||||
func (a *app) connect() error {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
|
||||
if p, err := a.mgr.Config().ActiveProfile(); err != nil {
|
||||
return err
|
||||
} else if strings.TrimSpace(p.Proxy) == "" {
|
||||
return fmt.Errorf("сначала вставьте ссылку сервера")
|
||||
}
|
||||
|
||||
if _, err := naive.EnsureBinary(a.mgr.BinDir()); err != nil {
|
||||
return err
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 45*time.Second)
|
||||
defer cancel()
|
||||
return a.mgr.Connect(ctx, "")
|
||||
}
|
||||
|
||||
func (a *app) disconnect() error {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
return a.mgr.Disconnect()
|
||||
}
|
||||
|
||||
func (a *app) installCore() (string, error) {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
return naive.EnsureBinary(a.mgr.BinDir())
|
||||
}
|
||||
|
||||
func (a *app) pingServers() ([]netcheck.Result, error) {
|
||||
a.mu.Lock()
|
||||
list := a.mgr.Profiles()
|
||||
a.mu.Unlock()
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
|
||||
defer cancel()
|
||||
|
||||
out := make([]netcheck.Result, 0, len(list))
|
||||
for _, p := range list {
|
||||
proxy := p.Proxy
|
||||
if proxy == "" {
|
||||
out = append(out, netcheck.Result{Name: p.Name, Error: "нет ссылки сервера"})
|
||||
continue
|
||||
}
|
||||
out = append(out, netcheck.PingProxyURI(ctx, p.Name, proxy))
|
||||
}
|
||||
|
||||
a.mu.Lock()
|
||||
a.pings = out
|
||||
a.mu.Unlock()
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (a *app) checkUpdate() (update.Status, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 25*time.Second)
|
||||
defer cancel()
|
||||
st, err := update.Check(ctx, update.DefaultManifestURL)
|
||||
a.mu.Lock()
|
||||
if err != nil {
|
||||
st.Current = update.CurrentVersion
|
||||
st.ManifestURL = update.DefaultManifestURL
|
||||
st.Error = err.Error()
|
||||
}
|
||||
a.updateStatus = st
|
||||
a.mu.Unlock()
|
||||
return st, nil
|
||||
}
|
||||
|
||||
func (a *app) applyUpdate() error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Minute)
|
||||
defer cancel()
|
||||
latest, err := update.Apply(ctx, update.DefaultManifestURL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_ = a.mgr.Disconnect()
|
||||
go func() {
|
||||
time.Sleep(400 * time.Millisecond)
|
||||
if a.webview != nil {
|
||||
a.webview.Terminate()
|
||||
}
|
||||
os.Exit(0)
|
||||
}()
|
||||
_ = latest
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *app) autoCheckUpdate() {
|
||||
time.Sleep(2 * time.Second)
|
||||
_, _ = a.checkUpdate()
|
||||
ticker := time.NewTicker(6 * time.Hour)
|
||||
defer ticker.Stop()
|
||||
for range ticker.C {
|
||||
_, _ = a.checkUpdate()
|
||||
}
|
||||
}
|
||||
|
||||
func openURL(raw string) error {
|
||||
raw = strings.TrimSpace(raw)
|
||||
switch raw {
|
||||
case "https://evilfox.win/", "https://evilfox.win", "http://evilfox.win/", "http://evilfox.win":
|
||||
raw = "https://evilfox.win/"
|
||||
default:
|
||||
return fmt.Errorf("разрешена только ссылка evilfox.win")
|
||||
}
|
||||
cmd := exec.Command("rundll32", "url.dll,FileProtocolHandler", raw)
|
||||
cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true}
|
||||
return cmd.Start()
|
||||
}
|
||||
|
||||
func applyWindowIcon(hwnd unsafe.Pointer) {
|
||||
ico := findIconPath()
|
||||
if ico == "" || hwnd == nil {
|
||||
return
|
||||
}
|
||||
pathPtr, err := windows.UTF16PtrFromString(ico)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
user32 := windows.NewLazySystemDLL("user32.dll")
|
||||
loadImage := user32.NewProc("LoadImageW")
|
||||
sendMessage := user32.NewProc("SendMessageW")
|
||||
const (
|
||||
imageIcon = 1
|
||||
lrLoadFromFile = 0x0010
|
||||
lrDefaultSize = 0x0040
|
||||
wmSetIcon = 0x0080
|
||||
iconSmall = 0
|
||||
iconBig = 1
|
||||
)
|
||||
h, _, _ := loadImage.Call(0, uintptr(unsafe.Pointer(pathPtr)), imageIcon, 0, 0, lrLoadFromFile|lrDefaultSize)
|
||||
if h == 0 {
|
||||
return
|
||||
}
|
||||
sendMessage.Call(uintptr(hwnd), wmSetIcon, iconSmall, h)
|
||||
sendMessage.Call(uintptr(hwnd), wmSetIcon, iconBig, h)
|
||||
}
|
||||
|
||||
func findIconPath() string {
|
||||
candidates := []string{}
|
||||
if exe, err := os.Executable(); err == nil {
|
||||
dir := filepath.Dir(exe)
|
||||
candidates = append(candidates,
|
||||
filepath.Join(dir, "assets", "navis.ico"),
|
||||
filepath.Join(dir, "navis.ico"),
|
||||
)
|
||||
}
|
||||
if cwd, err := os.Getwd(); err == nil {
|
||||
candidates = append(candidates, filepath.Join(cwd, "assets", "navis.ico"))
|
||||
}
|
||||
for _, c := range candidates {
|
||||
if st, err := os.Stat(c); err == nil && !st.IsDir() {
|
||||
return c
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func fatalDialog(format string, args ...interface{}) {
|
||||
msg := fmt.Sprintf(format, args...)
|
||||
log.Println(msg)
|
||||
messageBox(msg)
|
||||
os.Exit(1)
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
//go:build windows
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
func messageBox(text string) {
|
||||
user32 := syscall.NewLazyDLL("user32.dll")
|
||||
proc := user32.NewProc("MessageBoxW")
|
||||
title, _ := syscall.UTF16PtrFromString("Navis")
|
||||
body, _ := syscall.UTF16PtrFromString(text)
|
||||
proc.Call(0, uintptr(unsafe.Pointer(body)), uintptr(unsafe.Pointer(title)), 0x10) // MB_ICONERROR
|
||||
}
|
||||
@@ -0,0 +1,250 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"vpnclient/internal/config"
|
||||
"vpnclient/internal/core"
|
||||
"vpnclient/internal/protocols/naive"
|
||||
)
|
||||
|
||||
func main() {
|
||||
if len(os.Args) < 2 {
|
||||
printUsage()
|
||||
os.Exit(2)
|
||||
}
|
||||
|
||||
cmd := os.Args[1]
|
||||
args := os.Args[2:]
|
||||
|
||||
switch cmd {
|
||||
case "connect":
|
||||
os.Exit(runConnect(args))
|
||||
case "disconnect":
|
||||
fmt.Fprintln(os.Stderr, "disconnect: use Ctrl+C on a running connect session")
|
||||
os.Exit(2)
|
||||
case "status":
|
||||
fmt.Fprintln(os.Stderr, "status: only available while connect is running in this process")
|
||||
os.Exit(2)
|
||||
case "install-core":
|
||||
os.Exit(runInstallCore(args))
|
||||
case "init":
|
||||
os.Exit(runInit(args))
|
||||
case "probe":
|
||||
os.Exit(runProbe(args))
|
||||
case "set-proxy":
|
||||
os.Exit(runSetProxy(args))
|
||||
case "help", "-h", "--help":
|
||||
printUsage()
|
||||
default:
|
||||
fmt.Fprintf(os.Stderr, "unknown command: %s\n", cmd)
|
||||
printUsage()
|
||||
os.Exit(2)
|
||||
}
|
||||
}
|
||||
|
||||
func printUsage() {
|
||||
fmt.Fprintf(os.Stderr, `VPN client (NaiveProxy) for Windows
|
||||
|
||||
Usage:
|
||||
vpnclient init [-config configs/config.json]
|
||||
vpnclient install-core [-config configs/config.json]
|
||||
vpnclient set-proxy [-config configs/config.json] <share-or-proxy-uri>
|
||||
vpnclient connect [-config configs/config.json] [-profile name] [-no-sysproxy] [-probe]
|
||||
vpnclient probe [-config configs/config.json] [-profile name] [-url URL]
|
||||
|
||||
Share links like naive+https://user:pass@host:443#name are accepted.
|
||||
|
||||
Architecture:
|
||||
This client runs the official naive.exe from klzgrad/naiveproxy (Chromium
|
||||
network stack) and optionally sets the Windows system HTTP proxy so apps
|
||||
use the tunnel. That is a real NaiveProxy session, not a simulation.
|
||||
|
||||
`)
|
||||
}
|
||||
|
||||
func defaultConfigPath() string {
|
||||
return filepath.Join("configs", "config.json")
|
||||
}
|
||||
|
||||
func runInit(args []string) int {
|
||||
fs := flag.NewFlagSet("init", flag.ExitOnError)
|
||||
cfgPath := fs.String("config", defaultConfigPath(), "path to write example config")
|
||||
_ = fs.Parse(args)
|
||||
if _, err := os.Stat(*cfgPath); err == nil {
|
||||
fmt.Fprintf(os.Stderr, "refusing to overwrite existing %s\n", *cfgPath)
|
||||
return 1
|
||||
}
|
||||
if err := config.WriteExample(*cfgPath); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "init: %v\n", err)
|
||||
return 1
|
||||
}
|
||||
fmt.Printf("wrote %s — edit proxy user/pass/host, then: vpnclient install-core && vpnclient connect\n", *cfgPath)
|
||||
return 0
|
||||
}
|
||||
|
||||
func runInstallCore(args []string) int {
|
||||
fs := flag.NewFlagSet("install-core", flag.ExitOnError)
|
||||
cfgPath := fs.String("config", defaultConfigPath(), "config path (for bin_dir)")
|
||||
binDirFlag := fs.String("bin-dir", "", "override bin directory")
|
||||
_ = fs.Parse(args)
|
||||
|
||||
binDir := *binDirFlag
|
||||
if binDir == "" {
|
||||
if cfg, err := config.Load(*cfgPath); err == nil {
|
||||
resolved, err := config.ResolveBinDir(*cfgPath, cfg.BinDir)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "install-core: %v\n", err)
|
||||
return 1
|
||||
}
|
||||
binDir = resolved
|
||||
} else {
|
||||
binDir = "bin"
|
||||
}
|
||||
}
|
||||
path, err := naive.EnsureBinary(binDir)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "install-core: %v\n", err)
|
||||
return 1
|
||||
}
|
||||
fmt.Printf("naive core ready: %s\n", path)
|
||||
return 0
|
||||
}
|
||||
|
||||
func loadManager(cfgPath string) (*core.Manager, *config.Config, error) {
|
||||
cfg, err := config.Load(cfgPath)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
mgr, err := core.NewManager(cfgPath, cfg, os.Stderr)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return mgr, cfg, nil
|
||||
}
|
||||
|
||||
func runConnect(args []string) int {
|
||||
fs := flag.NewFlagSet("connect", flag.ExitOnError)
|
||||
cfgPath := fs.String("config", defaultConfigPath(), "config path")
|
||||
profile := fs.String("profile", "", "profile name (default: config.active)")
|
||||
noSys := fs.Bool("no-sysproxy", false, "do not change Windows system proxy")
|
||||
doProbe := fs.Bool("probe", false, "verify tunnel with an HTTP request after connect")
|
||||
_ = fs.Parse(args)
|
||||
|
||||
mgr, cfg, err := loadManager(*cfgPath)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "connect: %v\n", err)
|
||||
return 1
|
||||
}
|
||||
if *noSys {
|
||||
cfg.SystemProxy = false
|
||||
}
|
||||
|
||||
// Ensure official core is present.
|
||||
if _, err := naive.EnsureBinary(mgr.BinDir()); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "connect: %v\n", err)
|
||||
return 1
|
||||
}
|
||||
|
||||
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
|
||||
defer stop()
|
||||
|
||||
if err := mgr.Connect(ctx, *profile); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "connect: %v\n", err)
|
||||
return 1
|
||||
}
|
||||
defer func() {
|
||||
if err := mgr.Disconnect(); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "disconnect: %v\n", err)
|
||||
}
|
||||
}()
|
||||
|
||||
st := mgr.Status()
|
||||
enc := json.NewEncoder(os.Stdout)
|
||||
enc.SetIndent("", " ")
|
||||
_ = enc.Encode(st)
|
||||
fmt.Fprintln(os.Stderr, "connected — press Ctrl+C to disconnect and restore system proxy")
|
||||
|
||||
if *doProbe {
|
||||
pctx, cancel := context.WithTimeout(ctx, 45*time.Second)
|
||||
err := mgr.Probe(pctx, "")
|
||||
cancel()
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "probe failed: %v\n", err)
|
||||
} else {
|
||||
fmt.Fprintln(os.Stderr, "probe ok")
|
||||
}
|
||||
}
|
||||
|
||||
<-ctx.Done()
|
||||
fmt.Fprintln(os.Stderr, "shutting down...")
|
||||
return 0
|
||||
}
|
||||
|
||||
func runProbe(args []string) int {
|
||||
fs := flag.NewFlagSet("probe", flag.ExitOnError)
|
||||
cfgPath := fs.String("config", defaultConfigPath(), "config path")
|
||||
profile := fs.String("profile", "", "profile name")
|
||||
testURL := fs.String("url", "https://www.google.com/generate_204", "URL to fetch via tunnel")
|
||||
_ = fs.Parse(args)
|
||||
|
||||
mgr, cfg, err := loadManager(*cfgPath)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "probe: %v\n", err)
|
||||
return 1
|
||||
}
|
||||
cfg.SystemProxy = false
|
||||
|
||||
if _, err := naive.EnsureBinary(mgr.BinDir()); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "probe: %v\n", err)
|
||||
return 1
|
||||
}
|
||||
|
||||
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
|
||||
defer stop()
|
||||
|
||||
if err := mgr.Connect(ctx, *profile); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "probe: connect: %v\n", err)
|
||||
return 1
|
||||
}
|
||||
defer mgr.Disconnect()
|
||||
|
||||
pctx, cancel := context.WithTimeout(ctx, 45*time.Second)
|
||||
defer cancel()
|
||||
if err := mgr.Probe(pctx, *testURL); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "probe failed: %v\n", err)
|
||||
return 1
|
||||
}
|
||||
fmt.Println("probe ok")
|
||||
return 0
|
||||
}
|
||||
|
||||
func runSetProxy(args []string) int {
|
||||
fs := flag.NewFlagSet("set-proxy", flag.ExitOnError)
|
||||
cfgPath := fs.String("config", defaultConfigPath(), "config path")
|
||||
_ = fs.Parse(args)
|
||||
rest := fs.Args()
|
||||
if len(rest) < 1 {
|
||||
fmt.Fprintln(os.Stderr, "usage: vpnclient set-proxy <uri>")
|
||||
return 2
|
||||
}
|
||||
mgr, _, err := loadManager(*cfgPath)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "set-proxy: %v\n", err)
|
||||
return 1
|
||||
}
|
||||
if err := mgr.UpdateProxyURI(rest[0]); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "set-proxy: %v\n", err)
|
||||
return 1
|
||||
}
|
||||
fmt.Println("proxy saved")
|
||||
return 0
|
||||
}
|
||||
Reference in New Issue
Block a user