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() if err := ensureAppMarksNft(); err != nil { log.Printf("traffic appmarks nft init warning: %v", err) } if err := restoreAppMarksFromState(); err != nil { log.Printf("traffic appmarks restore warning: %v", err) } 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/advanced/reset", handleTrafficAdvancedReset) 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) // traffic audit (sanity checks / duplicates / nft consistency) mux.HandleFunc("/api/v1/traffic/audit", handleTrafficAudit) // 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/upstream-pool", handleDNSUpstreamPool) mux.HandleFunc("/api/v1/dns/status", handleDNSStatus) mux.HandleFunc("/api/v1/dns/mode", handleDNSModeSet) mux.HandleFunc("/api/v1/dns/benchmark", handleDNSBenchmark) 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) } }