package main import ( "context" "errors" "fmt" "log/slog" "os" "os/signal" "syscall" "time" "drive.local/drivev2/internal/adapters/authn" "drive.local/drivev2/internal/adapters/postgres" "drive.local/drivev2/internal/adapters/webassets" "drive.local/drivev2/internal/application" "drive.local/drivev2/internal/config" "drive.local/drivev2/internal/transport/httpapi" ) var version = "dev" func main() { if err := run(); err != nil { slog.Error("drive stopped", "error", err) os.Exit(1) } } func run() error { command := "serve" if len(os.Args) > 1 { command = os.Args[1] } switch command { case "serve": return serve() case "version": fmt.Println(version) return nil default: return fmt.Errorf("unknown command %q", command) } } func serve() error { cfg, err := config.FromEnvironment() if err != nil { return err } if cfg.DatabaseURL == "" { return errors.New("DRIVE_DATABASE_URL is required for serve") } logger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{Level: cfg.LogLevel})) slog.SetDefault(logger) startupCtx, startupCancel := context.WithTimeout(context.Background(), 30*time.Second) defer startupCancel() if err := postgres.Migrate(cfg.DatabaseURL); err != nil { return err } store, err := postgres.Open(startupCtx, cfg.DatabaseURL) if err != nil { return err } defer store.Close() systemService := application.NewSystemService(store, version) authenticationService := application.NewAuthenticationService( store, authn.NewPasswordHasher(), authn.CredentialGenerator{}, authn.SystemClock{}, ) server := httpapi.NewServer(cfg, logger, systemService, authenticationService, webassets.New(cfg.WebRoot)) ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM) defer stop() errCh := make(chan error, 1) go func() { logger.Info("starting drive", "address", cfg.HTTPAddress, "version", version) errCh <- server.ListenAndServe() }() select { case err := <-errCh: if errors.Is(err, httpapi.ErrServerClosed) { return nil } return err case <-ctx.Done(): shutdownCtx, cancel := context.WithTimeout(context.Background(), 20*time.Second) defer cancel() return server.Shutdown(shutdownCtx) } }