1package main
  2
  3import (
  4	"context"
  5	"fmt"
  6	"os"
  7	"runtime/debug"
  8	"strings"
  9	"time"
 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	_ "github.com/lib/pq" // postgres driver
 16	"github.com/spf13/cobra"
 17	"go.uber.org/automaxprocs/maxprocs"
 18
 19	_ "modernc.org/sqlite" // sqlite driver
 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 := newDefaultLogger(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
106// newDefaultLogger returns a new logger with default settings.
107func newDefaultLogger(cfg *config.Config) (*log.Logger, *os.File, error) {
108	logger := log.NewWithOptions(os.Stderr, log.Options{
109		ReportTimestamp: true,
110		TimeFormat:      time.DateOnly,
111	})
112
113	switch {
114	case config.IsVerbose():
115		logger.SetReportCaller(true)
116		fallthrough
117	case config.IsDebug():
118		logger.SetLevel(log.DebugLevel)
119	}
120
121	logger.SetTimeFormat(cfg.Log.TimeFormat)
122
123	switch strings.ToLower(cfg.Log.Format) {
124	case "json":
125		logger.SetFormatter(log.JSONFormatter)
126	case "logfmt":
127		logger.SetFormatter(log.LogfmtFormatter)
128	case "text":
129		logger.SetFormatter(log.TextFormatter)
130	}
131
132	var f *os.File
133	if cfg.Log.Path != "" {
134		f, err := os.OpenFile(cfg.Log.Path, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o644)
135		if err != nil {
136			return nil, nil, err
137		}
138		logger.SetOutput(f)
139	}
140
141	return logger, f, nil
142}
143
144func initBackendContext(cmd *cobra.Command, _ []string) error {
145	ctx := cmd.Context()
146	cfg := config.FromContext(ctx)
147	dbx, err := db.Open(ctx, cfg.DB.Driver, cfg.DB.DataSource)
148	if err != nil {
149		return fmt.Errorf("open database: %w", err)
150	}
151
152	ctx = db.WithContext(ctx, dbx)
153	be := backend.New(ctx, cfg, dbx)
154	ctx = backend.WithContext(ctx, be)
155
156	cmd.SetContext(ctx)
157
158	return nil
159}
160
161func closeDBContext(cmd *cobra.Command, _ []string) error {
162	ctx := cmd.Context()
163	dbx := db.FromContext(ctx)
164	if dbx != nil {
165		if err := dbx.Close(); err != nil {
166			return fmt.Errorf("close database: %w", err)
167		}
168	}
169
170	return nil
171}