Files
wg/cmd/server/main.go
T

141 lines
3.6 KiB
Go

package main
import (
"context"
"log"
"net/http"
"os"
"os/signal"
"path/filepath"
"syscall"
"time"
"github.com/evilfox/wg-panel/internal/auth"
"github.com/evilfox/wg-panel/internal/config"
"github.com/evilfox/wg-panel/internal/db"
"github.com/evilfox/wg-panel/internal/handlers"
"github.com/evilfox/wg-panel/internal/mysqlimport"
"github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware"
"github.com/jackc/pgx/v5/pgxpool"
)
func main() {
cfg := config.Load()
ctx := context.Background()
root := envOr("APP_ROOT", ".")
migrationsDir := envOr("MIGRATIONS_DIR", filepath.Join(root, "migrations"))
templatesDir := envOr("TEMPLATES_DIR", filepath.Join(root, "web", "templates"))
staticDir := envOr("STATIC_DIR", filepath.Join(root, "web", "static"))
pool, err := waitForDB(ctx, cfg.DatabaseURL, 60*time.Second)
if err != nil {
log.Fatalf("database: %v", err)
}
defer pool.Close()
if err := db.Migrate(ctx, pool, migrationsDir); err != nil {
log.Fatalf("migrate: %v", err)
}
if cfg.AutoImportDump {
if _, err := os.Stat(cfg.MySQLDumpPath); err == nil {
if err := mysqlimport.ImportFile(ctx, pool, cfg.MySQLDumpPath); err != nil {
log.Fatalf("mysql import: %v", err)
}
} else {
log.Printf("mysql dump not found at %s, skip import", cfg.MySQLDumpPath)
}
}
authStore := auth.NewStore(cfg, pool)
if err := authStore.EnsureAdmin(ctx); err != nil {
log.Fatalf("ensure admin: %v", err)
}
log.Printf("admin ready: username=%s email=%s", cfg.AdminUsername, cfg.AdminEmail)
app, err := handlers.New(pool, authStore, cfg, templatesDir)
if err != nil {
log.Fatalf("handlers: %v", err)
}
r := chi.NewRouter()
r.Use(middleware.RequestID)
r.Use(middleware.RealIP)
r.Use(middleware.Logger)
r.Use(middleware.Recoverer)
r.Use(app.OfflineMiddleware)
fileServer(r, "/static/", http.Dir(staticDir))
r.Get("/", app.Home)
r.Get("/healthz", func(w http.ResponseWriter, _ *http.Request) {
w.Header().Set("Content-Type", "text/plain")
w.WriteHeader(http.StatusOK)
_, _ = w.Write([]byte("ok"))
})
r.Get("/login", app.LoginPage)
r.Post("/login", app.LoginPost)
r.Get("/logout", app.Logout)
r.Get("/wg", app.PublicWG)
r.Get("/wg_config.php", app.PublicWG)
r.Route("/admin", func(r chi.Router) {
r.Use(authStore.RequireAdmin)
r.Get("/", app.AdminDashboard)
r.Post("/status", app.AdminStatusPost)
r.Get("/wg", app.AdminWG)
r.Post("/wg/upload", app.AdminWGUpload)
r.Post("/wg/delete", app.AdminWGDelete)
r.Post("/wg/settings", app.AdminWGSettings)
})
srv := &http.Server{
Addr: ":" + cfg.AppPort,
Handler: r,
ReadHeaderTimeout: 10 * time.Second,
}
go func() {
log.Printf("listening on :%s (%s)", cfg.AppPort, cfg.AppURL)
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatalf("server: %v", err)
}
}()
stop := make(chan os.Signal, 1)
signal.Notify(stop, syscall.SIGINT, syscall.SIGTERM)
<-stop
shutdownCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
_ = srv.Shutdown(shutdownCtx)
}
func waitForDB(ctx context.Context, databaseURL string, timeout time.Duration) (*pgxpool.Pool, error) {
deadline := time.Now().Add(timeout)
var last error
for time.Now().Before(deadline) {
pool, err := db.Connect(ctx, databaseURL)
if err == nil {
return pool, nil
}
last = err
log.Printf("waiting for postgres: %v", err)
time.Sleep(2 * time.Second)
}
return nil, last
}
func fileServer(r chi.Router, pathPrefix string, root http.FileSystem) {
r.Handle(pathPrefix+"*", http.StripPrefix(pathPrefix, http.FileServer(root)))
}
func envOr(k, def string) string {
if v := os.Getenv(k); v != "" {
return v
}
return def
}