package main import ( "context" "flag" "log" "net" "net/http" "os" "os/signal" "path/filepath" "syscall" "time" "clashweb/internal/app" "clashweb/web" ) func env(key, fallback string) string { if value := os.Getenv(key); value != "" { return value } return fallback } func main() { listen := flag.String("listen", env("CLASHWEB_LISTEN", "127.0.0.1:8080"), "Web 管理监听地址") data := flag.String("data", env("CLASHWEB_DATA_DIR", "data"), "SQLite 数据目录") coreDir := flag.String("core-dir", os.Getenv("CLASHWEB_CORE_DIR"), "Mihomo 工作目录") coreBin := flag.String("mihomo", os.Getenv("CLASHWEB_MIHOMO_BIN"), "Mihomo 可执行文件") initOnly := flag.Bool("init", false, "校验并生成初始配置,然后退出") flag.Parse() a, err := app.New(app.Options{DataDir: *data, CoreDir: *coreDir, CoreBinary: *coreBin, Helper: os.Getenv("CLASHWEB_CONTROL_HELPER"), SecureCookie: os.Getenv("CLASHWEB_SECURE_COOKIE") == "true", Web: web.Handler()}) if err != nil { log.Fatal(err) } defer a.Close() ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM) defer cancel() if *initOnly { if err := a.Initialize(ctx); err != nil { log.Fatal(err) } log.Print("初始配置生成完成") return } go a.Run(ctx) server := &http.Server{Addr: *listen, Handler: a.Handler(), ReadHeaderTimeout: 10 * time.Second, IdleTimeout: 90 * time.Second, MaxHeaderBytes: 1 << 20, BaseContext: func(net.Listener) context.Context { return ctx }} go func() { <-ctx.Done() shutdown, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() _ = server.Shutdown(shutdown) }() abs, _ := filepath.Abs(*data) log.Printf("Clash Web: http://%s (数据: %s)", *listen, abs) if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed { log.Fatal(err) } }