diff options
Diffstat (limited to 'cmd/mirum-server/main.go')
| -rw-r--r-- | cmd/mirum-server/main.go | 253 | +0 −253 |
1 files changed, 0 insertions, 253 deletions
diff --git a/cmd/mirum-server/main.go b/cmd/mirum-server/main.go deleted file mode 100644 --- a/cmd/mirum-server/main.go +++ /dev/null @@ -1,253 +0,0 @@ -// SPDX-FileCopyrightText: 2026 Nikolay Govorov -// SPDX-License-Identifier: AGPL-3.0-or-later - -package main - -import ( - "context" - "errors" - "fmt" - "log/slog" - "net" - "net/http" - "os" - - "dimidiumlabs/mirum/cmd/mirum-server/apipb/apipbconnect" - "dimidiumlabs/mirum/internal/config" - "dimidiumlabs/mirum/internal/forges" - "dimidiumlabs/mirum/internal/protocol/wirepb" - "dimidiumlabs/mirum/internal/supervisor" - - "github.com/spf13/cobra" -) - -func hardenServer(s *http.Server) *http.Server { - s.IdleTimeout = config.HTTPIdleTimeout - s.MaxHeaderBytes = config.HTTPMaxHeaderBytes - s.ReadHeaderTimeout = config.HTTPReadHeaderTimeout - return s -} - -func main() { - var socketPath string - - root := &cobra.Command{Use: "mirum-server", Short: "Mirum CI server"} - root.PersistentFlags().StringVar(&socketPath, "socket", "", "admin socket path (default from config or /run/mirum-server/admin.sock)") - root.AddGroup(&cobra.Group{ID: "main", Title: "Commands:"}) - - daemonCmd := &cobra.Command{ - Use: "daemon", - Short: "Start the server", - GroupID: "main", - SilenceUsage: true, - SilenceErrors: true, - RunE: func(cmd *cobra.Command, args []string) error { - configFile, _ := cmd.Flags().GetString("config") - return daemon(configFile, socketPath) - }, - } - daemonCmd.Flags().String("config", "", "path to config file") - _ = daemonCmd.MarkFlagRequired("config") - root.AddCommand(daemonCmd) - - // Console subcommands are generated from api.proto via reflection. - buildConsoleCLI(root, func() apipbconnect.ConsoleClient { return consoleClient(socketPath) }) - for _, c := range root.Commands() { - if c.GroupID == "" { - c.GroupID = "main" - } - } - - if err := root.Execute(); err != nil { - os.Exit(1) - } -} - -func daemon(configFile, socketFlag string) error { - cfg, err := getConfig(configFile) - if err != nil { - slog.Error("config parsing failed", "err", err) - return err - } - - if socketFlag != "" { - cfg.AdminSocket = socketFlag - } - - slog.Info("config loaded", "configfile", configFile) - - sup := supervisor.Detect() - ctx, cancel := context.WithCancel(sup.WaitForStop(context.Background())) - defer cancel() - - db, err := DatabaseOpen(ctx, cfg.DatabaseUri) - if err != nil { - slog.Error("couldn't open database", "err", err) - return err - } - - srv := &server{ - db: db, - cfg: cfg, - forge: &forges.GitHub{Secret: cfg.WebhookSecret, Token: cfg.GitHubToken}, - queue: make(chan *wirepb.Task, config.TaskQueueCapacity), - } - defer srv.Close() - - if err := db.Migrate(ctx); err != nil { - slog.Error("migration failed", "err", err) - return err - } - - slog.Info("database ready") - - go srv.PurgeSessions(ctx) - - consolePath, consoleHandler := NewConsoleHandler(srv) - - webSrv := hardenServer(NewWebServer(ctx, srv, consolePath, consoleHandler)) - grpcSrv := hardenServer(NewGrpcServer(ctx, srv)) - - adminMux := http.NewServeMux() - adminMux.Handle(consolePath, consoleHandler) - adminSrv := hardenServer(&http.Server{ - Handler: adminMux, - ConnContext: func(ctx context.Context, _ net.Conn) context.Context { - return context.WithValue(ctx, actorKey{}, OperatorActor()) - }, - BaseContext: func(_ net.Listener) context.Context { - return ctx - }, - }) - - grpcLn, webLn, adminLn, err := listeners(cfg, sup) - if err != nil { - slog.Error("listeners failed", "err", err) - return err - } - - slog.Info("listening", "grpc", grpcLn.Addr(), "web", webLn.Addr(), "admin", cfg.AdminSocket) - - errs := make(chan error, 3) - serve := func(name string, fn func() error) { - go func() { - err := fn() - if errors.Is(err, http.ErrServerClosed) { - err = nil - } - if err != nil { - err = fmt.Errorf("%s server: %w", name, err) - } - errs <- err - }() - } - serve("web", func() error { - if webSrv.TLSConfig != nil { - return webSrv.ServeTLS(webLn, "", "") - } - return webSrv.Serve(webLn) - }) - serve("grpc", func() error { return grpcSrv.ServeTLS(grpcLn, "", "") }) - serve("admin", func() error { return adminSrv.Serve(adminLn) }) - - sup.Ready() - go sup.StartWatchdog(ctx) - - var runErr error - select { - case <-ctx.Done(): - slog.Info("shutting down") - case err := <-errs: - runErr = err - // Propagate the crash to all handler contexts so Poll and - // other long-lived RPCs exit via ctx.Done(); Shutdown below - // then completes without waiting on them. - cancel() - if err != nil { - slog.Error("server exited, shutting down peers", "err", err) - } else { - slog.Warn("server exited unexpectedly, shutting down peers") - } - } - - sup.Stopping() - - shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), config.HTTPShutdownTimeout) - defer shutdownCancel() - - shutdown := func(name string, s *http.Server) { - if err := s.Shutdown(shutdownCtx); err != nil { - slog.Error("server shutdown", "name", name, "err", err) - } - } - - shutdown("web", webSrv) - shutdown("grpc", grpcSrv) - shutdown("admin", adminSrv) - - return runErr -} - -// listeners returns gRPC, web, and admin listeners. -// With systemd socket activation it expects two named fds: "grpc" and "web". -// Without socket activation it falls back to configured addresses. -func listeners(cfg *appConfig, sup supervisor.Supervisor) (grpcLn, webLn, adminLn net.Listener, err error) { - named, err := sup.ActivationListeners() - if err != nil { - return nil, nil, nil, fmt.Errorf("socket activation: %w", err) - } - - if lns := named["grpc"]; len(lns) > 0 { - grpcLn = lns[0] - } else if grpcLn, err = net.Listen("tcp", cfg.GrpcAddr); err != nil { - return nil, nil, nil, err - } - defer func() { - if err != nil && grpcLn != nil { - _ = grpcLn.Close() - } - }() - - if lns := named["web"]; len(lns) > 0 { - webLn = lns[0] - } else if webLn, err = net.Listen("tcp", cfg.WebAddr); err != nil { - return nil, nil, nil, err - } - defer func() { - if err != nil && webLn != nil { - _ = webLn.Close() - } - }() - - _ = os.Remove(cfg.AdminSocket) - if adminLn, err = net.Listen("unix", cfg.AdminSocket); err != nil { - return nil, nil, nil, err - } - defer func() { - if err != nil && adminLn != nil { - _ = adminLn.Close() - } - }() - - if err = os.Chmod(cfg.AdminSocket, 0o660); err != nil { - return nil, nil, nil, fmt.Errorf("chmod admin socket: %w", err) - } - - return grpcLn, webLn, adminLn, nil -} - -func consoleClient(socketPath string) apipbconnect.ConsoleClient { - if socketPath == "" { - socketPath = "/run/mirum-server/admin.sock" - } - return apipbconnect.NewConsoleClient( - &http.Client{ - Transport: &http.Transport{ - DialContext: func(ctx context.Context, _, _ string) (net.Conn, error) { - return net.Dial("unix", socketPath) - }, - }, - }, - "http://localhost.unix", - ) -} |
