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}