cmd/weimar/serve.go (view raw)
1package main
2
3import (
4 "embed"
5 "fmt"
6 "io/fs"
7 "log/slog"
8 "os"
9 "os/signal"
10 "syscall"
11
12 "github.com/mjensen/weimar/internal/auth"
13 "github.com/mjensen/weimar/internal/config"
14 "github.com/mjensen/weimar/internal/db"
15 "github.com/mjensen/weimar/internal/server"
16 "github.com/spf13/cobra"
17 "github.com/spf13/viper"
18)
19
20//go:embed web/build/*
21var webFS embed.FS
22
23var adminEmail string
24var adminPassword string
25
26var serveCmd = &cobra.Command{
27 Use: "serve",
28 Short: "Start the HTTP server",
29 RunE: func(cmd *cobra.Command, args []string) error {
30 cfg := config.FromViper()
31
32 webSub, err := fs.Sub(webFS, "web/build")
33 if err != nil {
34 return fmt.Errorf("reading embedded web build: %w", err)
35 }
36
37 database, err := db.Open(cfg.Database.Path)
38 if err != nil {
39 return fmt.Errorf("opening database: %w", err)
40 }
41 defer database.Close()
42
43 if err := database.Migrate(cmd.Context()); err != nil {
44 return fmt.Errorf("running migrations: %w", err)
45 }
46
47 // Create admin user on first run if no users exist and credentials are provided.
48 if adminEmail != "" && adminPassword != "" {
49 users, err := database.ListUsers(cmd.Context())
50 if err != nil {
51 return fmt.Errorf("checking for existing users: %w", err)
52 }
53 if len(users) == 0 {
54 if _, err := auth.Register(cmd.Context(), database, adminEmail, adminPassword, true); err != nil {
55 return fmt.Errorf("creating admin user: %w", err)
56 }
57 slog.Info("created initial admin user", "username", adminEmail)
58 }
59 }
60
61 srv, err := server.New(&cfg, webSub, database)
62 if err != nil {
63 return fmt.Errorf("creating server: %w", err)
64 }
65
66 // Handle SIGHUP for config reload.
67 sig := make(chan os.Signal, 1)
68 signal.Notify(sig, syscall.SIGHUP)
69 go func() {
70 for range sig {
71 slog.Info("received SIGHUP, reloading config")
72 if err := initConfig(); err != nil {
73 slog.Warn("config reload failed", "error", err)
74 continue
75 }
76 srv.ReloadConfig(config.FromViper())
77 slog.Info("config reloaded")
78 }
79 }()
80
81 ctx, stop := signal.NotifyContext(cmd.Context(), syscall.SIGINT, syscall.SIGTERM)
82 defer stop()
83
84 slog.Info("starting weimar", "host", cfg.Server.Host, "port", cfg.Server.Port)
85 return srv.Start(ctx)
86 },
87}
88
89func init() {
90 rootCmd.AddCommand(serveCmd)
91 serveCmd.Flags().StringVar(&adminEmail, "admin-email", "", "admin email for first-run setup")
92 serveCmd.Flags().StringVar(&adminPassword, "admin-password", "", "admin password for first-run setup")
93 serveCmd.Flags().Bool("behind-proxy", false, "set Secure flag on cookies and trust X-Forwarded headers")
94 viper.BindPFlag("server.behind_proxy", serveCmd.Flags().Lookup("behind-proxy"))
95 viper.BindPFlag("server.host", serveCmd.Flags().Lookup("host"))
96 viper.BindPFlag("server.port", serveCmd.Flags().Lookup("port"))
97}