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}