package main import ( "context" "fmt" "io/fs" "log" "net/http" "os" "os/signal" "syscall" "time" "github.com/joho/godotenv" "vpn-panel/internal/config" "vpn-panel/internal/database" "vpn-panel/internal/handlers" "vpn-panel/web" ) func main() { _ = godotenv.Load() cfg, err := config.Load() if err != nil { log.Fatalf("конфигурация: %v", err) } ctx := context.Background() pool, err := database.Connect(ctx, cfg.DatabaseURL) if err != nil { log.Fatalf("база данных: %v", err) } defer pool.Close() if err := database.Migrate(ctx, pool); err != nil { log.Fatalf("миграции: %v", err) } h, err := handlers.New(cfg, pool) if err != nil { log.Fatalf("handlers: %v", err) } mux := http.NewServeMux() mux.HandleFunc("GET /", h.Home) mux.HandleFunc("GET /health", h.Health) mux.HandleFunc("GET /register", h.RegisterAdmin) mux.HandleFunc("POST /register", h.RegisterAdmin) mux.HandleFunc("GET /login", h.Login) mux.HandleFunc("POST /login", h.Login) mux.HandleFunc("GET /logout", h.Logout) staticFS, err := fs.Sub(web.Static, "static") if err != nil { log.Fatal(err) } mux.Handle("GET /static/", http.StripPrefix("/static/", http.FileServer(http.FS(staticFS)))) addr := fmt.Sprintf(":%d", cfg.AppPort) srv := &http.Server{ Addr: addr, Handler: mux, ReadTimeout: 15 * time.Second, WriteTimeout: 15 * time.Second, } go func() { log.Printf("VPN Panel запущена на http://%s%s", cfg.AppDomain, addr) if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { log.Fatal(err) } }() quit := make(chan os.Signal, 1) signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) <-quit shutdownCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() _ = srv.Shutdown(shutdownCtx) log.Println("остановлена") }