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}