root.go

  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		RunE: func(cmd *cobra.Command, args []string) error {
 37			return browseCmd.RunE(cmd, args)
 38		},
 39	}
 40)
 41
 42func init() {
 43	rootCmd.AddCommand(
 44		serveCmd,
 45		manCmd,
 46		hookCmd,
 47		migrateConfig,
 48		adminCmd,
 49	)
 50	rootCmd.CompletionOptions.HiddenDefaultCmd = true
 51
 52	if len(CommitSHA) >= 7 {
 53		vt := rootCmd.VersionTemplate()
 54		rootCmd.SetVersionTemplate(vt[:len(vt)-1] + " (" + CommitSHA[0:7] + ")\n")
 55	}
 56	if Version == "" {
 57		if info, ok := debug.ReadBuildInfo(); ok && info.Main.Sum != "" {
 58			Version = info.Main.Version
 59		} else {
 60			Version = "unknown (built from source)"
 61		}
 62	}
 63	rootCmd.Version = Version
 64}
 65
 66func main() {
 67	ctx := context.Background()
 68	cfg := config.DefaultConfig()
 69	if cfg.Exist() {
 70		if err := cfg.Parse(); err != nil {
 71			log.Fatal(err)
 72		}
 73	}
 74
 75	if err := cfg.ParseEnv(); err != nil {
 76		log.Fatal(err)
 77	}
 78
 79	ctx = config.WithContext(ctx, cfg)
 80	logger, f, err := logr.NewLogger(cfg)
 81	if err != nil {
 82		log.Errorf("failed to create logger: %v", err)
 83	}
 84
 85	ctx = log.WithContext(ctx, logger)
 86	if f != nil {
 87		defer f.Close() // nolint: errcheck
 88	}
 89
 90	// Set global logger
 91	log.SetDefault(logger)
 92
 93	var opts []maxprocs.Option
 94	if config.IsVerbose() {
 95		opts = append(opts, maxprocs.Logger(log.Debugf))
 96	}
 97
 98	// Set the max number of processes to the number of CPUs
 99	// This is useful when running soft serve in a container
100	if _, err := maxprocs.Set(opts...); err != nil {
101		log.Warn("couldn't set automaxprocs", "error", err)
102	}
103
104	if err := rootCmd.ExecuteContext(ctx); err != nil {
105		os.Exit(1)
106	}
107}
108
109func initBackendContext(cmd *cobra.Command, _ []string) error {
110	ctx := cmd.Context()
111	cfg := config.FromContext(ctx)
112	if _, err := os.Stat(cfg.DataPath); errors.Is(err, fs.ErrNotExist) {
113		if err := os.MkdirAll(cfg.DataPath, os.ModePerm); err != nil {
114			return fmt.Errorf("create data directory: %w", err)
115		}
116	}
117	dbx, err := db.Open(ctx, cfg.DB.Driver, cfg.DB.DataSource)
118	if err != nil {
119		return fmt.Errorf("open database: %w", err)
120	}
121
122	ctx = db.WithContext(ctx, dbx)
123	dbstore := database.New(ctx, dbx)
124	ctx = store.WithContext(ctx, dbstore)
125	be := backend.New(ctx, cfg, dbx)
126	ctx = backend.WithContext(ctx, be)
127
128	cmd.SetContext(ctx)
129
130	return nil
131}
132
133func closeDBContext(cmd *cobra.Command, _ []string) error {
134	ctx := cmd.Context()
135	dbx := db.FromContext(ctx)
136	if dbx != nil {
137		if err := dbx.Close(); err != nil {
138			return fmt.Errorf("close database: %w", err)
139		}
140	}
141
142	return nil
143}