package main import ( "context" "crypto/rand" "encoding/hex" "errors" "log/slog" "net/http" "os" "os/signal" "strconv" "strings" "syscall" "time" "git.hubris.network/dtoro/artifacto/internal/auth" "git.hubris.network/dtoro/artifacto/internal/server" "git.hubris.network/dtoro/artifacto/internal/store" ) func main() { logger := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{Level: logLevel()})) slog.SetDefault(logger) dataDir := envOr("DATA_DIR", "/data") bindAddr := envOr("BIND_ADDR", ":3000") baseURL := envOr("BASE_URL", "http://localhost:3000") adminPw := os.Getenv("ADMIN_PASSWORD") sessionSecret := os.Getenv("SESSION_SECRET") maxMB, _ := strconv.ParseInt(envOr("MAX_UPLOAD_MB", "5"), 10, 64) if sessionSecret == "" { // For dev convenience: generate an ephemeral secret if unset. buf := make([]byte, 32) _, _ = rand.Read(buf) sessionSecret = hex.EncodeToString(buf) logger.Warn("SESSION_SECRET not set; using ephemeral (sessions will not persist across restarts)") } s, err := store.Open(dataDir) if err != nil { logger.Error("open store", "err", err) os.Exit(1) } defer s.Close() secure := strings.HasPrefix(baseURL, "https://") admin, err := auth.NewAdmin(adminPw, sessionSecret, secure) if err != nil { logger.Error("init admin auth", "err", err) os.Exit(1) } artifactAuth := auth.NewArtifact(sessionSecret, secure) srv, err := server.New(server.Config{ BaseURL: strings.TrimRight(baseURL, "/"), MaxUploadMB: maxMB, Secure: secure, Logger: logger, }, s, admin, artifactAuth) if err != nil { logger.Error("init server", "err", err) os.Exit(1) } httpSrv := &http.Server{ Addr: bindAddr, Handler: srv.Handler(), ReadHeaderTimeout: 10 * time.Second, } stop := make(chan struct{}) go srv.RollupLoop(stop) // Graceful shutdown go func() { sig := make(chan os.Signal, 1) signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM) <-sig logger.Info("shutting down") close(stop) ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() _ = httpSrv.Shutdown(ctx) }() logger.Info("artifacto starting", "addr", bindAddr, "base_url", baseURL, "data_dir", dataDir) if err := httpSrv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) { logger.Error("http server", "err", err) os.Exit(1) } } func envOr(k, d string) string { if v := os.Getenv(k); v != "" { return v } return d } func logLevel() slog.Level { switch strings.ToLower(os.Getenv("LOG_LEVEL")) { case "debug": return slog.LevelDebug case "warn": return slog.LevelWarn case "error": return slog.LevelError default: return slog.LevelInfo } }