diff --git a/app.go b/app.go index cad5496..9c4bbb3 100644 --- a/app.go +++ b/app.go @@ -1,7 +1,12 @@ package main import ( + "errors" + "flag" + "fmt" + "io" "os" + "runtime/debug" "time" "github.com/gotify/server/v2/config" @@ -27,7 +32,45 @@ var ( ) func main() { + os.Exit(run(os.Args[1:], os.Stdout, os.Stderr)) +} + +func run(args []string, stdout, stderr io.Writer) int { vInfo := &model.VersionInfo{Version: Version, Commit: Commit, BuildDate: BuildDate} + fs := flag.NewFlagSet("gotify", flag.ContinueOnError) + fs.SetOutput(stderr) + fs.Usage = func() { printUsage(stderr) } + if err := fs.Parse(args); err != nil { + if errors.Is(err, flag.ErrHelp) { + return 0 + } + return 2 + } + + command := fs.Arg(0) + switch command { + case "serve", "": + return serve(vInfo) + case "version": + fmt.Fprintln(stdout, "Version:", vInfo.Version) + fmt.Fprintln(stdout, "Commit:", vInfo.Commit) + fmt.Fprintln(stdout, "Build Date:", vInfo.BuildDate) + fmt.Fprintln(stdout, "Go Build Info:") + b, ok := debug.ReadBuildInfo() + if ok { + fmt.Fprintln(stdout, b) + } + return 0 + default: + if command != "" { + fmt.Fprintf(stderr, "gotify: unknown command %q\n\n", command) + } + printUsage(stderr) + return 2 + } +} + +func serve(vInfo *model.VersionInfo) int { mode.Set(Mode) conf, futureLogs := config.Get() @@ -40,21 +83,24 @@ func main() { exit = exit || futureLog.Level == zerolog.FatalLevel || futureLog.Level == zerolog.PanicLevel } if exit { - os.Exit(1) + return 1 } if conf.PluginsDir != "" { if err := os.MkdirAll(conf.PluginsDir, 0o755); err != nil { - panic(err) + log.Error().Err(err).Str("dir", conf.PluginsDir).Msg("Cannot create plugins directory") + return 1 } } if err := os.MkdirAll(conf.UploadedImagesDir, 0o755); err != nil { - panic(err) + log.Error().Err(err).Str("dir", conf.UploadedImagesDir).Msg("Cannot create uploaded images directory") + return 1 } db, err := database.New(conf.Database.Dialect, conf.Database.Connection, conf.DefaultUser.Name, conf.DefaultUser.Pass, conf.PassStrength, true, time.Now) if err != nil { - panic(err) + log.Error().Err(err).Msg("Cannot initialize database") + return 1 } defer db.Close() @@ -63,8 +109,20 @@ func main() { if err := runner.Run(engine, conf); err != nil { log.Error().Err(err).Msg("Server error") - os.Exit(1) + return 1 } + return 0 +} + +func printUsage(w io.Writer) { + fmt.Fprint(w, `Usage: gotify [flags] [arguments] + +Commands: + serve Start the Gotify server. + migrate-config Convert an old YAML config file to the new env + format and print it to stdout. + version Show version information +`) } func noColor(noColorEnv string) bool { diff --git a/app_test.go b/app_test.go new file mode 100644 index 0000000..ef1f030 --- /dev/null +++ b/app_test.go @@ -0,0 +1,31 @@ +package main + +import ( + "bytes" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestRun(t *testing.T) { + cases := []struct { + name string + args []string + wantCode int + stdout string // substring expected on stdout + stderr string // substring expected on stderr + }{ + {"version", []string{"version"}, 0, "Version: ", ""}, + {"unknown command", []string{"bogus"}, 2, "", "unknown command"}, + {"unknown flag", []string{"--nope"}, 2, "", "not defined"}, + } + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + var stdout, stderr bytes.Buffer + code := run(c.args, &stdout, &stderr) + assert.Equal(t, c.wantCode, code) + assert.Contains(t, stdout.String(), c.stdout) + assert.Contains(t, stderr.String(), c.stderr) + }) + } +} diff --git a/docker/Dockerfile b/docker/Dockerfile index 4c1dee1..bbed0fb 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -75,3 +75,4 @@ EXPOSE $GOTIFY_SERVER_EXPOSE COPY --from=builder /target / ENTRYPOINT ["./gotify-app"] +CMD ["serve"] diff --git a/ui/src/tests/setup.ts b/ui/src/tests/setup.ts index 84b757a..171ee03 100644 --- a/ui/src/tests/setup.ts +++ b/ui/src/tests/setup.ts @@ -123,7 +123,7 @@ const buildGoExecutable = (filename: string): Promise => { }; const startGotify = (filename: string, port: number, pluginDir: string): ChildProcess => { - const gotify = spawn(filename, [], { + const gotify = spawn(filename, ['serve'], { env: { GOTIFY_SERVER_PORT: '' + port, GOTIFY_DATABASE_CONNECTION: 'file::memory:?mode=memory&cache=shared',