Files
elmprodvpn/selective-vpn-api/app/server.go

211 lines
7.8 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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)
// per-app runtime marks (systemd scope / cgroup -> fwmark)
mux.HandleFunc("/api/v1/traffic/appmarks", handleTrafficAppMarks)
// list runtime marks items (for UI)
mux.HandleFunc("/api/v1/traffic/appmarks/items", handleTrafficAppMarksItems)
// persistent app profiles (saved launch configs)
mux.HandleFunc("/api/v1/traffic/app-profiles", handleTrafficAppProfiles)
// 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)
}
}