baseline: api+gui traffic mode + candidates picker
Snapshot before app-launcher (cgroup/mark) work; ignore binaries/backups.
This commit is contained in:
204
selective-vpn-api/app/server.go
Normal file
204
selective-vpn-api/app/server.go
Normal file
@@ -0,0 +1,204 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// main + общие хелперы
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
// EN: Application entrypoint and process bootstrap.
|
||||
// EN: This file wires CLI modes, registers all HTTP routes, and starts background
|
||||
// EN: watchers plus the localhost API server.
|
||||
// RU: Точка входа приложения и bootstrap процесса.
|
||||
// RU: Этот файл связывает CLI-режимы, регистрирует все HTTP-маршруты и запускает
|
||||
// RU: фоновые вотчеры вместе с локальным API-сервером.
|
||||
func Run() {
|
||||
// ---------------------------------------------------------------------
|
||||
// CLI modes
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
// CLI mode: routes-update
|
||||
if len(os.Args) > 1 && (os.Args[1] == "routes-update" || os.Args[1] == "-routes-update") {
|
||||
fs := flag.NewFlagSet("routes-update", flag.ExitOnError)
|
||||
iface := fs.String("iface", "", "VPN interface (empty/auto = detect active)")
|
||||
_ = fs.Parse(os.Args[2:])
|
||||
lock, err := os.OpenFile(lockFile, os.O_CREATE|os.O_RDWR, 0o644)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "lock open error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
if err := syscall.Flock(int(lock.Fd()), syscall.LOCK_EX|syscall.LOCK_NB); err != nil {
|
||||
fmt.Println("routes update already running")
|
||||
lock.Close()
|
||||
return
|
||||
}
|
||||
res := routesUpdate(*iface)
|
||||
_ = syscall.Flock(int(lock.Fd()), syscall.LOCK_UN)
|
||||
_ = lock.Close()
|
||||
if res.OK {
|
||||
fmt.Println(res.Message)
|
||||
return
|
||||
}
|
||||
fmt.Fprintln(os.Stderr, res.Message)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// CLI mode: routes-clear
|
||||
if len(os.Args) > 1 && os.Args[1] == "routes-clear" {
|
||||
res := routesClear()
|
||||
if res.OK {
|
||||
fmt.Println(res.Message)
|
||||
return
|
||||
}
|
||||
fmt.Fprintln(os.Stderr, res.Message)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// CLI mode: autoloop
|
||||
if len(os.Args) > 1 && os.Args[1] == "autoloop" {
|
||||
fs := flag.NewFlagSet("autoloop", flag.ExitOnError)
|
||||
iface := fs.String("iface", "", "VPN interface (empty/auto = detect active)")
|
||||
table := fs.String("table", "agvpn", "routing table name")
|
||||
mtu := fs.Int("mtu", 1380, "MTU for default route")
|
||||
stateDirFlag := fs.String("state-dir", stateDir, "state directory")
|
||||
defaultLoc := fs.String("default-location", "Austria", "default location")
|
||||
_ = fs.Parse(os.Args[2:])
|
||||
resolvedIface := normalizePreferredIface(*iface)
|
||||
if resolvedIface == "" {
|
||||
resolvedIface, _ = resolveTrafficIface(loadTrafficModeState().PreferredIface)
|
||||
}
|
||||
if resolvedIface == "" {
|
||||
fmt.Fprintln(os.Stderr, "autoloop: cannot resolve VPN interface (set --iface or preferred iface)")
|
||||
os.Exit(1)
|
||||
}
|
||||
runAutoloop(resolvedIface, *table, *mtu, *stateDirFlag, *defaultLoc)
|
||||
return
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// API server bootstrap
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
ensureSeeds()
|
||||
|
||||
mux := http.NewServeMux()
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// route registration
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
// health
|
||||
mux.HandleFunc("/healthz", handleHealthz)
|
||||
// event stream (SSE)
|
||||
mux.HandleFunc("/api/v1/events/stream", handleEventsStream)
|
||||
|
||||
// статус selective-routes
|
||||
mux.HandleFunc("/api/v1/status", handleGetStatus)
|
||||
mux.HandleFunc("/api/v1/routes/status", handleGetStatus)
|
||||
|
||||
// login state
|
||||
mux.HandleFunc("/api/v1/vpn/login-state", handleVPNLoginState)
|
||||
|
||||
// systemd state
|
||||
mux.HandleFunc("/api/v1/systemd/state", handleSystemdState)
|
||||
|
||||
// сервис selective-routes
|
||||
mux.HandleFunc("/api/v1/routes/service/start",
|
||||
makeRoutesServiceActionHandler("start"))
|
||||
mux.HandleFunc("/api/v1/routes/service/stop",
|
||||
makeRoutesServiceActionHandler("stop"))
|
||||
mux.HandleFunc("/api/v1/routes/service/restart",
|
||||
makeRoutesServiceActionHandler("restart"))
|
||||
// универсальный: {"action":"start|stop|restart"}
|
||||
mux.HandleFunc("/api/v1/routes/service", handleRoutesService)
|
||||
// ручной апдейт маршрутов (Go-реализация вместо bash)
|
||||
mux.HandleFunc("/api/v1/routes/update", handleRoutesUpdate)
|
||||
|
||||
// таймер маршрутов (новый API)
|
||||
mux.HandleFunc("/api/v1/routes/timer", handleRoutesTimer)
|
||||
// старый toggle для совместимости
|
||||
mux.HandleFunc("/api/v1/routes/timer/toggle", handleRoutesTimerToggle)
|
||||
|
||||
// rollback / clear (Go implementation)
|
||||
mux.HandleFunc("/api/v1/routes/rollback", handleRoutesClear)
|
||||
// alias: /routes/clear
|
||||
mux.HandleFunc("/api/v1/routes/clear", handleRoutesClear)
|
||||
// fast restore from clear-cache
|
||||
mux.HandleFunc("/api/v1/routes/cache/restore", handleRoutesCacheRestore)
|
||||
|
||||
// фиксим policy route
|
||||
mux.HandleFunc("/api/v1/routes/fix-policy-route", handleFixPolicyRoute)
|
||||
mux.HandleFunc("/api/v1/routes/fix-policy", handleFixPolicyRoute)
|
||||
mux.HandleFunc("/api/v1/traffic/mode", handleTrafficMode)
|
||||
mux.HandleFunc("/api/v1/traffic/mode/test", handleTrafficModeTest)
|
||||
mux.HandleFunc("/api/v1/traffic/interfaces", handleTrafficInterfaces)
|
||||
mux.HandleFunc("/api/v1/traffic/candidates", handleTrafficCandidates)
|
||||
|
||||
// trace: хвост + JSON + append для GUI
|
||||
mux.HandleFunc("/api/v1/trace", handleTraceTailPlain)
|
||||
mux.HandleFunc("/api/v1/trace-json", handleTraceJSON)
|
||||
mux.HandleFunc("/api/v1/trace/append", handleTraceAppend)
|
||||
|
||||
// DNS upstreams
|
||||
mux.HandleFunc("/api/v1/dns-upstreams", handleDNSUpstreams)
|
||||
mux.HandleFunc("/api/v1/dns/status", handleDNSStatus)
|
||||
mux.HandleFunc("/api/v1/dns/mode", handleDNSModeSet)
|
||||
mux.HandleFunc("/api/v1/dns/smartdns-service", handleDNSSmartdnsService)
|
||||
|
||||
// SmartDNS service
|
||||
mux.HandleFunc("/api/v1/smartdns/service", handleSmartdnsService)
|
||||
mux.HandleFunc("/api/v1/smartdns/runtime", handleSmartdnsRuntime)
|
||||
mux.HandleFunc("/api/v1/smartdns/prewarm", handleSmartdnsPrewarm)
|
||||
|
||||
// domains editor
|
||||
mux.HandleFunc("/api/v1/domains/table", handleDomainsTable)
|
||||
mux.HandleFunc("/api/v1/domains/file", handleDomainsFile)
|
||||
|
||||
// SmartDNS wildcards
|
||||
mux.HandleFunc("/api/v1/smartdns/wildcards", handleSmartdnsWildcards)
|
||||
|
||||
// AdGuard VPN: status + autoloop + autoconnect + locations
|
||||
mux.HandleFunc("/api/v1/vpn/autoloop-status", handleVPNAutoloopStatus)
|
||||
mux.HandleFunc("/api/v1/vpn/status", handleVPNStatus)
|
||||
mux.HandleFunc("/api/v1/vpn/autoconnect", handleVPNAutoconnect)
|
||||
mux.HandleFunc("/api/v1/vpn/locations", handleVPNListLocations)
|
||||
mux.HandleFunc("/api/v1/vpn/location", handleVPNSetLocation)
|
||||
|
||||
// AdGuard VPN: interactive login session (PTY)
|
||||
mux.HandleFunc("/api/v1/vpn/login/session/start", handleVPNLoginSessionStart)
|
||||
mux.HandleFunc("/api/v1/vpn/login/session/state", handleVPNLoginSessionState)
|
||||
mux.HandleFunc("/api/v1/vpn/login/session/action", handleVPNLoginSessionAction)
|
||||
mux.HandleFunc("/api/v1/vpn/login/session/stop", handleVPNLoginSessionStop)
|
||||
// logout
|
||||
mux.HandleFunc("/api/v1/vpn/logout", handleVPNLogout)
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// HTTP server
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
srv := &http.Server{
|
||||
Addr: "127.0.0.1:8080",
|
||||
Handler: logRequests(mux),
|
||||
ReadHeaderTimeout: 5 * time.Second,
|
||||
}
|
||||
|
||||
go startWatchers(ctx)
|
||||
|
||||
log.Printf("selective-vpn API listening on %s", srv.Addr)
|
||||
if err := srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
|
||||
log.Fatalf("server error: %v", err)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user