// metacrypt-web is the standalone web UI server for Metacrypt. // It communicates with the vault over gRPC and can be restarted independently // without requiring the vault to be re-unsealed. package main import ( "context" "fmt" "log/slog" "os" "os/signal" "syscall" "github.com/spf13/cobra" "git.wntrmute.dev/mc/metacrypt/internal/config" "git.wntrmute.dev/mc/metacrypt/internal/webserver" ) var cfgFile string var rootCmd = &cobra.Command{ Use: "metacrypt-web", Short: "Metacrypt web UI server", Long: "Standalone web UI server for Metacrypt. Communicates with the vault over gRPC.", RunE: run, } func init() { rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default /srv/metacrypt/metacrypt.toml)") } func run(cmd *cobra.Command, args []string) error { logger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelDebug})) configPath := cfgFile if configPath == "" { configPath = "/srv/metacrypt/metacrypt.toml" } cfg, err := config.Load(configPath) if err != nil { return err } if cfg.Web.VaultGRPC == "" { return fmt.Errorf("web.vault_grpc is required in config") } ws, err := webserver.New(cfg, logger) if err != nil { return err } ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM) defer stop() go func() { if err := ws.Start(); err != nil { logger.Error("web server error", "error", err) os.Exit(1) } }() <-ctx.Done() logger.Info("shutting down web server") return ws.Shutdown(context.Background()) } func main() { if err := rootCmd.Execute(); err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) } }