// Command server runs the DriverVault API Server. It is the single gateway // between clients (web app, phone app, Home Assistant plugin, ESP32 device) and // the PocketBase database kept behind it — clients never talk to PocketBase // directly. It also serves the superadmin web panel at the server root. package main import ( "context" "errors" "log" "net/http" "os" "os/signal" "syscall" "time" "drivervault/apiserver/internal/api" "drivervault/apiserver/internal/config" "drivervault/apiserver/internal/pb" ) func main() { log.SetFlags(log.LstdFlags | log.Lmsgprefix) log.SetPrefix("[api] ") cfg := config.Load() client := pb.New(cfg.PocketBaseURL, cfg.PocketBaseAdminEmail, cfg.PocketBaseAdminPassword) // Authenticate the service account up front so the first request doesn't pay // for it. A failure is NOT fatal: the PocketBase connection is editable at // runtime from the panel, so a superadmin must be able to log in and fix a // bad address or bad credentials. if cfg.AdminConfigured() { authCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second) if err := client.Authenticate(authCtx); err != nil { log.Printf("WARNING: PocketBase service account auth failed (%s): %v", cfg.PocketBaseURL, err) log.Printf("fix it under Settings → PocketBase in the panel at %s", cfg.Addr) } else { log.Printf("authenticated to PocketBase at %s", cfg.PocketBaseURL) } cancel() } else { log.Println("WARNING: POCKETBASE_ADMIN_EMAIL/PASSWORD unset — management endpoints return 503 until configured") } srv := api.New(cfg, client) if err := srv.StartPlugins(); err != nil { log.Printf("plugins: load failed: %v", err) } httpServer := &http.Server{ Addr: cfg.Addr, Handler: srv.Handler(), ReadHeaderTimeout: 10 * time.Second, IdleTimeout: 60 * time.Second, } go func() { log.Printf("listening on %s (PocketBase: %s, panel at /)", cfg.Addr, cfg.PocketBaseURL) if err := httpServer.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) { log.Fatalf("server error: %v", err) } }() // Graceful shutdown on SIGINT/SIGTERM. stop := make(chan os.Signal, 1) signal.Notify(stop, os.Interrupt, syscall.SIGTERM) <-stop log.Println("shutting down...") ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() srv.Stop(ctx) if err := httpServer.Shutdown(ctx); err != nil { log.Printf("shutdown error: %v", err) } log.Println("stopped") }