]> git.r.bdr.sh - rbdr/mobius/blame - cmd/mobius-hotline-server/main.go
Add cmdline arg to initialize a default config
[rbdr/mobius] / cmd / mobius-hotline-server / main.go
CommitLineData
6988a057
JH
1package main
2
3import (
4 "context"
8796b449 5 "embed"
23411fc2 6 "encoding/json"
6988a057
JH
7 "flag"
8 "fmt"
22c599ab 9 "github.com/jhalter/mobius/hotline"
6988a057
JH
10 "go.uber.org/zap"
11 "go.uber.org/zap/zapcore"
23411fc2
JH
12 "io"
13 "log"
93df4153 14 "math/rand"
23411fc2 15 "net/http"
6988a057 16 "os"
18a8614d 17 "runtime"
93df4153 18 "time"
6988a057
JH
19)
20
8796b449
JH
21//go:embed mobius/config
22var cfgTemplate embed.FS
23
6988a057 24const (
18a8614d 25 defaultPort = 5500
6988a057
JH
26)
27
28func main() {
93df4153
JH
29 rand.Seed(time.Now().UnixNano())
30
6988a057
JH
31 ctx, cancelRoot := context.WithCancel(context.Background())
32
33 basePort := flag.Int("bind", defaultPort, "Bind address and port")
23411fc2 34 statsPort := flag.String("stats-port", "", "Enable stats HTTP endpoint on address and port")
18a8614d 35 configDir := flag.String("config", defaultConfigPath(), "Path to config root")
6988a057
JH
36 version := flag.Bool("version", false, "print version and exit")
37 logLevel := flag.String("log-level", "info", "Log level")
8796b449
JH
38 init := flag.Bool("init", false, "Populate the config dir with default configuration")
39
6988a057
JH
40 flag.Parse()
41
42 if *version {
43 fmt.Printf("v%s\n", hotline.VERSION)
44 os.Exit(0)
45 }
46
47 zapLvl, ok := zapLogLevel[*logLevel]
48 if !ok {
49 fmt.Printf("Invalid log level %s. Must be debug, info, warn, or error.\n", *logLevel)
50 os.Exit(0)
51 }
52
53 cores := []zapcore.Core{newStdoutCore(zapLvl)}
54 l := zap.New(zapcore.NewTee(cores...))
55 defer func() { _ = l.Sync() }()
56 logger := l.Sugar()
57
8796b449
JH
58 if *init {
59 if _, err := os.Stat(*configDir + "/config.yaml"); !os.IsNotExist(err) {
60 logger.Fatalw("Init failed. Existing config directory found: " + *configDir)
61 }
62
63 if err := os.MkdirAll(*configDir, 0750); err != nil {
64 logger.Fatal(err)
65 }
66
67 if err := copyDir("mobius/config", *configDir); err != nil {
68 logger.Fatal(err)
69 }
70 }
71
6988a057
JH
72 if _, err := os.Stat(*configDir); os.IsNotExist(err) {
73 logger.Fatalw("Configuration directory not found", "path", configDir)
74 }
75
b196a50a 76 srv, err := hotline.NewServer(*configDir, "", *basePort, logger, &hotline.OSFileStore{})
6988a057
JH
77 if err != nil {
78 logger.Fatal(err)
79 }
80
23411fc2
JH
81 sh := statHandler{hlServer: srv}
82 if *statsPort != "" {
83 http.HandleFunc("/", sh.RenderStats)
84
85 go func(srv *hotline.Server) {
86 // Use the default DefaultServeMux.
87 err = http.ListenAndServe(":"+*statsPort, nil)
88 if err != nil {
89 log.Fatal(err)
90 }
91 }(srv)
92 }
93
6988a057
JH
94 // Serve Hotline requests until program exit
95 logger.Fatal(srv.ListenAndServe(ctx, cancelRoot))
96}
97
23411fc2
JH
98type statHandler struct {
99 hlServer *hotline.Server
100}
101
102func (sh *statHandler) RenderStats(w http.ResponseWriter, _ *http.Request) {
103 u, err := json.Marshal(sh.hlServer.Stats)
104 if err != nil {
105 panic(err)
106 }
107
108 _, _ = io.WriteString(w, string(u))
109}
110
6988a057
JH
111func newStdoutCore(level zapcore.Level) zapcore.Core {
112 encoderCfg := zap.NewProductionEncoderConfig()
113 encoderCfg.TimeKey = "timestamp"
114 encoderCfg.EncodeTime = zapcore.ISO8601TimeEncoder
115
116 return zapcore.NewCore(
117 zapcore.NewConsoleEncoder(encoderCfg),
118 zapcore.Lock(os.Stdout),
119 level,
120 )
121}
122
123var zapLogLevel = map[string]zapcore.Level{
124 "debug": zap.DebugLevel,
125 "info": zap.InfoLevel,
126 "warn": zap.WarnLevel,
127 "error": zap.ErrorLevel,
128}
18a8614d
JH
129
130func defaultConfigPath() (cfgPath string) {
131 switch runtime.GOOS {
132 case "windows":
133 cfgPath = "config"
134 case "darwin":
135 if _, err := os.Stat("/usr/local/var/mobius/config/"); err == nil {
136 cfgPath = "/usr/local/var/mobius/config/"
137 } else if _, err := os.Stat("/opt/homebrew/var/mobius/config"); err == nil {
138 cfgPath = "/opt/homebrew/var/mobius/config/"
139 }
140 case "linux":
141 cfgPath = "/usr/local/var/mobius/config/"
142 default:
143 fmt.Printf("unsupported OS")
144 }
145
146 return cfgPath
147}
8796b449
JH
148
149// TODO: Simplify this mess. Why is it so difficult to recursively copy a directory?
150func copyDir(src, dst string) error {
151 entries, err := cfgTemplate.ReadDir(src)
152 if err != nil {
153 return err
154 }
155 for _, dirEntry := range entries {
156 if dirEntry.IsDir() {
157 if err := os.MkdirAll(dst+"/"+dirEntry.Name(), 0777); err != nil {
158 panic(err)
159 }
160 subdirEntries, _ := cfgTemplate.ReadDir(src + "/" + dirEntry.Name())
161 for _, subDirEntry := range subdirEntries {
162 f, err := os.Create(dst + "/" + dirEntry.Name() + "/" + subDirEntry.Name())
163 if err != nil {
164 return err
165 }
166
167 srcFile, err := cfgTemplate.Open(src + "/" + dirEntry.Name() + "/" + subDirEntry.Name())
168 if err != nil {
169 return err
170 }
171 _, err = io.Copy(f, srcFile)
172 if err != nil {
173 return err
174 }
175 f.Close()
176 }
177 } else {
178 f, err := os.Create(dst + "/" + dirEntry.Name())
179 if err != nil {
180 return err
181 }
182
183 srcFile, err := cfgTemplate.Open(src + "/" + dirEntry.Name())
184 if err != nil {
185 return err
186 }
187 _, err = io.Copy(f, srcFile)
188 if err != nil {
189 return err
190 }
191 f.Close()
192 }
193
194 }
195
196 return nil
197}