all repos — weimar @ 8dcf6e79e30fa40a7f7da17169314b250793210c

Unnamed repository; edit this file 'description' to name the repository.

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	"codeberg.org/maxwelljensen/weimar/internal/auth"
13	"codeberg.org/maxwelljensen/weimar/internal/config"
14	"codeberg.org/maxwelljensen/weimar/internal/db"
15	"codeberg.org/maxwelljensen/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}