- ARCHITECTURE.md: document nginx + direct gRPC topology, add grpc_plain_addr config, update cert filenames to Let's Encrypt convention, add passwd to CLI table - RUNBOOK.md: replace systemctl/journalctl with docker commands, fix cert path references, improve sync troubleshooting steps - Example config: update cert paths, document grpc_plain_addr option - grpcserver: add optional plaintext gRPC listener for reverse proxy - config: add GRPCPlainAddr field Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
119 lines
2.8 KiB
Go
119 lines
2.8 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"log/slog"
|
|
"os"
|
|
"os/signal"
|
|
"syscall"
|
|
"time"
|
|
|
|
"git.wntrmute.dev/kyle/eng-pad-server/internal/config"
|
|
"git.wntrmute.dev/kyle/eng-pad-server/internal/db"
|
|
"git.wntrmute.dev/kyle/eng-pad-server/internal/grpcserver"
|
|
applog "git.wntrmute.dev/kyle/eng-pad-server/internal/log"
|
|
"git.wntrmute.dev/kyle/eng-pad-server/internal/server"
|
|
"git.wntrmute.dev/kyle/eng-pad-server/internal/webserver"
|
|
"github.com/spf13/cobra"
|
|
)
|
|
|
|
var serverCmd = &cobra.Command{
|
|
Use: "server",
|
|
Short: "Start the eng-pad server",
|
|
RunE: runServer,
|
|
}
|
|
|
|
func init() {
|
|
rootCmd.AddCommand(serverCmd)
|
|
}
|
|
|
|
func runServer(cmd *cobra.Command, args []string) error {
|
|
cfg, err := config.Load(cfgFile)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
applog.Init(cfg.Log.Level)
|
|
|
|
database, err := db.Open(cfg.Database.Path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer func() { _ = database.Close() }()
|
|
|
|
if err := db.Migrate(database); err != nil {
|
|
return fmt.Errorf("migrate: %w", err)
|
|
}
|
|
|
|
slog.Info("eng-pad-server starting", "version", version)
|
|
|
|
// Start gRPC server
|
|
grpcSrv, err := grpcserver.Start(grpcserver.Config{
|
|
Addr: cfg.Server.GRPCAddr,
|
|
PlainAddr: cfg.Server.GRPCPlainAddr,
|
|
TLSCert: cfg.Server.TLSCert,
|
|
TLSKey: cfg.Server.TLSKey,
|
|
DB: database,
|
|
BaseURL: cfg.Web.BaseURL,
|
|
})
|
|
if err != nil {
|
|
return fmt.Errorf("start grpc: %w", err)
|
|
}
|
|
|
|
// Start REST API server
|
|
restSrv, err := server.Start(server.Config{
|
|
Addr: cfg.Server.ListenAddr,
|
|
TLSCert: cfg.Server.TLSCert,
|
|
TLSKey: cfg.Server.TLSKey,
|
|
DB: database,
|
|
BaseURL: cfg.Web.BaseURL,
|
|
})
|
|
if err != nil {
|
|
return fmt.Errorf("start rest: %w", err)
|
|
}
|
|
|
|
// Start Web UI server
|
|
webSrv, err := webserver.Start(webserver.Config{
|
|
Addr: cfg.Web.ListenAddr,
|
|
DB: database,
|
|
BaseURL: cfg.Web.BaseURL,
|
|
RPDisplayName: cfg.WebAuthn.RPDisplayName,
|
|
RPID: cfg.WebAuthn.RPID,
|
|
RPOrigins: cfg.WebAuthn.RPOrigins,
|
|
})
|
|
if err != nil {
|
|
return fmt.Errorf("start web: %w", err)
|
|
}
|
|
|
|
// Wait for shutdown signal
|
|
sigCh := make(chan os.Signal, 1)
|
|
signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
|
|
|
|
sig := <-sigCh
|
|
slog.Info("received shutdown signal", "signal", sig.String())
|
|
|
|
// Graceful shutdown with 30-second timeout
|
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
|
defer cancel()
|
|
|
|
// Stop gRPC server gracefully
|
|
grpcSrv.GracefulStop()
|
|
|
|
// Shutdown REST and web servers
|
|
if err := restSrv.Shutdown(ctx); err != nil {
|
|
slog.Error("REST server shutdown error", "error", err)
|
|
}
|
|
if err := webSrv.Shutdown(ctx); err != nil {
|
|
slog.Error("web server shutdown error", "error", err)
|
|
}
|
|
|
|
// Close the database
|
|
if err := database.Close(); err != nil {
|
|
slog.Error("database close error", "error", err)
|
|
}
|
|
|
|
slog.Info("shutdown complete")
|
|
return nil
|
|
}
|