1// Package main is the entry point for the soft-serve CLI.
2package main
3
4import (
5 "context"
6 "fmt"
7 "os"
8 "runtime/debug"
9 "strconv"
10
11 "github.com/charmbracelet/colorprofile"
12 "github.com/charmbracelet/log/v2"
13 "github.com/charmbracelet/soft-serve/cmd/soft/admin"
14 "github.com/charmbracelet/soft-serve/cmd/soft/browse"
15 "github.com/charmbracelet/soft-serve/cmd/soft/hook"
16 "github.com/charmbracelet/soft-serve/cmd/soft/serve"
17 "github.com/charmbracelet/soft-serve/pkg/config"
18 logr "github.com/charmbracelet/soft-serve/pkg/log"
19 "github.com/charmbracelet/soft-serve/pkg/ui/common"
20 "github.com/charmbracelet/soft-serve/pkg/version"
21 mcobra "github.com/muesli/mango-cobra"
22 "github.com/muesli/roff"
23 "github.com/spf13/cobra"
24 "go.uber.org/automaxprocs/maxprocs"
25)
26
27var (
28 // Version contains the application version number. It's set via ldflags
29 // when building.
30 Version = ""
31
32 // CommitSHA contains the SHA of the commit that this application was built
33 // against. It's set via ldflags when building.
34 CommitSHA = ""
35
36 // CommitDate contains the date of the commit that this application was
37 // built against. It's set via ldflags when building.
38 CommitDate = ""
39
40 rootCmd = &cobra.Command{
41 Use: "soft",
42 Short: "A self-hostable Git server for the command line",
43 Long: "Soft Serve is a self-hostable Git server for the command line.",
44 SilenceUsage: true,
45 RunE: func(cmd *cobra.Command, args []string) error {
46 return browse.Command.RunE(cmd, args)
47 },
48 }
49
50 manCmd = &cobra.Command{
51 Use: "man",
52 Short: "Generate man pages",
53 Args: cobra.NoArgs,
54 Hidden: true,
55 RunE: func(_ *cobra.Command, _ []string) error {
56 manPage, err := mcobra.NewManPage(1, rootCmd) //.
57 if err != nil {
58 return err //nolint:wrapcheck
59 }
60
61 manPage = manPage.WithSection("Copyright", "(C) 2021-2023 Charmbracelet, Inc.\n"+
62 "Released under MIT license.")
63 fmt.Println(manPage.Build(roff.NewDocument()))
64 return nil
65 },
66 }
67)
68
69func init() {
70 if noColor, _ := strconv.ParseBool(os.Getenv("SOFT_SERVE_NO_COLOR")); noColor {
71 common.DefaultColorProfile = colorprofile.NoTTY
72 }
73
74 rootCmd.AddCommand(
75 manCmd,
76 serve.Command,
77 hook.Command,
78 admin.Command,
79 browse.Command,
80 )
81 rootCmd.CompletionOptions.HiddenDefaultCmd = true
82
83 if len(CommitSHA) >= 7 {
84 vt := rootCmd.VersionTemplate()
85 rootCmd.SetVersionTemplate(vt[:len(vt)-1] + " (" + CommitSHA[0:7] + ")\n")
86 }
87 if Version == "" {
88 if info, ok := debug.ReadBuildInfo(); ok && info.Main.Sum != "" {
89 Version = info.Main.Version
90 } else {
91 Version = "unknown (built from source)"
92 }
93 }
94 rootCmd.Version = Version
95
96 version.Version = Version
97 version.CommitSHA = CommitSHA
98 version.CommitDate = CommitDate
99}
100
101func main() {
102 ctx := context.Background()
103 cfg := config.DefaultConfig()
104 if cfg.Exist() {
105 if err := cfg.Parse(); err != nil {
106 log.Fatal(err)
107 }
108 }
109
110 if err := cfg.ParseEnv(); err != nil {
111 log.Fatal(err)
112 }
113
114 ctx = config.WithContext(ctx, cfg)
115 logger, f, err := logr.NewLogger(cfg)
116 if err != nil {
117 log.Errorf("failed to create logger: %v", err)
118 }
119
120 ctx = log.WithContext(ctx, logger)
121 if f != nil {
122 defer f.Close() //nolint: errcheck
123 }
124
125 // Set global logger
126 log.SetDefault(logger)
127
128 var opts []maxprocs.Option
129 if config.IsVerbose() {
130 opts = append(opts, maxprocs.Logger(log.Debugf))
131 }
132
133 // Set the max number of processes to the number of CPUs
134 // This is useful when running soft serve in a container
135 if _, err := maxprocs.Set(opts...); err != nil {
136 log.Warn("couldn't set automaxprocs", "error", err)
137 }
138
139 if err := rootCmd.ExecuteContext(ctx); err != nil {
140 os.Exit(1)
141 }
142}