1package main
  2
  3import (
  4	"context"
  5	"errors"
  6	"fmt"
  7	"io/fs"
  8	"os"
  9	"runtime/debug"
 10
 11	"github.com/charmbracelet/log"
 12	"github.com/charmbracelet/soft-serve/server/backend"
 13	"github.com/charmbracelet/soft-serve/server/config"
 14	"github.com/charmbracelet/soft-serve/server/db"
 15	logr "github.com/charmbracelet/soft-serve/server/log"
 16	"github.com/charmbracelet/soft-serve/server/store"
 17	"github.com/charmbracelet/soft-serve/server/store/database"
 18	"github.com/spf13/cobra"
 19	"go.uber.org/automaxprocs/maxprocs"
 20)
 21
 22var (
 23	// Version contains the application version number. It's set via ldflags
 24	// when building.
 25	Version = ""
 26
 27	// CommitSHA contains the SHA of the commit that this application was built
 28	// against. It's set via ldflags when building.
 29	CommitSHA = ""
 30
 31	rootCmd = &cobra.Command{
 32		Use:          "soft",
 33		Short:        "A self-hostable Git server for the command line",
 34		Long:         "Soft Serve is a self-hostable Git server for the command line.",
 35		SilenceUsage: true,
 36	}
 37)
 38
 39func init() {
 40	rootCmd.AddCommand(
 41		serveCmd,
 42		manCmd,
 43		hookCmd,
 44		migrateConfig,
 45		adminCmd,
 46	)
 47	rootCmd.CompletionOptions.HiddenDefaultCmd = true
 48
 49	if len(CommitSHA) >= 7 {
 50		vt := rootCmd.VersionTemplate()
 51		rootCmd.SetVersionTemplate(vt[:len(vt)-1] + " (" + CommitSHA[0:7] + ")\n")
 52	}
 53	if Version == "" {
 54		if info, ok := debug.ReadBuildInfo(); ok && info.Main.Sum != "" {
 55			Version = info.Main.Version
 56		} else {
 57			Version = "unknown (built from source)"
 58		}
 59	}
 60	rootCmd.Version = Version
 61}
 62
 63func main() {
 64	ctx := context.Background()
 65	cfg := config.DefaultConfig()
 66	if cfg.Exist() {
 67		if err := cfg.Parse(); err != nil {
 68			log.Fatal(err)
 69		}
 70	}
 71
 72	if err := cfg.ParseEnv(); err != nil {
 73		log.Fatal(err)
 74	}
 75
 76	ctx = config.WithContext(ctx, cfg)
 77	logger, f, err := logr.NewLogger(cfg)
 78	if err != nil {
 79		log.Errorf("failed to create logger: %v", err)
 80	}
 81
 82	ctx = log.WithContext(ctx, logger)
 83	if f != nil {
 84		defer f.Close() // nolint: errcheck
 85	}
 86
 87	// Set global logger
 88	log.SetDefault(logger)
 89
 90	var opts []maxprocs.Option
 91	if config.IsVerbose() {
 92		opts = append(opts, maxprocs.Logger(log.Debugf))
 93	}
 94
 95	// Set the max number of processes to the number of CPUs
 96	// This is useful when running soft serve in a container
 97	if _, err := maxprocs.Set(opts...); err != nil {
 98		log.Warn("couldn't set automaxprocs", "error", err)
 99	}
100
101	if err := rootCmd.ExecuteContext(ctx); err != nil {
102		os.Exit(1)
103	}
104}
105
106func initBackendContext(cmd *cobra.Command, _ []string) error {
107	ctx := cmd.Context()
108	cfg := config.FromContext(ctx)
109	if _, err := os.Stat(cfg.DataPath); errors.Is(err, fs.ErrNotExist) {
110		if err := os.MkdirAll(cfg.DataPath, os.ModePerm); err != nil {
111			return fmt.Errorf("create data directory: %w", err)
112		}
113	}
114	dbx, err := db.Open(ctx, cfg.DB.Driver, cfg.DB.DataSource)
115	if err != nil {
116		return fmt.Errorf("open database: %w", err)
117	}
118
119	ctx = db.WithContext(ctx, dbx)
120	dbstore := database.New(ctx, dbx)
121	ctx = store.WithContext(ctx, dbstore)
122	be := backend.New(ctx, cfg, dbx)
123	ctx = backend.WithContext(ctx, be)
124
125	cmd.SetContext(ctx)
126
127	return nil
128}
129
130func closeDBContext(cmd *cobra.Command, _ []string) error {
131	ctx := cmd.Context()
132	dbx := db.FromContext(ctx)
133	if dbx != nil {
134		if err := dbx.Close(); err != nil {
135			return fmt.Errorf("close database: %w", err)
136		}
137	}
138
139	return nil
140}