// Command rvbox-server is the RVBox control and agent server. package main import ( "context" "errors" "flag" "fmt" "log" "net" "net/http" "os" "os/signal" "syscall" rvboxv1 "github.com/rvbox/rvbox/gen/go/rvbox/v1" "github.com/rvbox/rvbox/internal/agentproto" "github.com/rvbox/rvbox/internal/config" "github.com/rvbox/rvbox/internal/server/control" "github.com/rvbox/rvbox/internal/server/session" "github.com/rvbox/rvbox/internal/server/store" "google.golang.org/grpc" ) func main() { var configPath string flag.StringVar(&configPath, "config", "", "absolute server TOML configuration path") flag.Parse() if configPath == "" { log.Print("rvbox-server: --config is required") os.Exit(2) } if err := run(configPath); err != nil { log.Printf("rvbox-server: %v", err) os.Exit(1) } } func run(configPath string) error { configured, err := config.LoadServer(configPath) if err != nil { return err } persistence, err := store.Open(context.Background(), store.Options{ DataDir: configured.Server.DataDir, BusyTimeout: configured.Storage.SQLiteBusyTimeout, SegmentTargetSize: configured.Storage.SegmentTargetBytes, QuotaLimits: store.QuotaLimits{ HardAllocationBytes: store.DefaultSegmentLimit, CommandOutputBytes: configured.Storage.CommandOutputLimitBytes, CommandTotalBytes: configured.Storage.CommandTotalLimitBytes, ClientTotalBytes: configured.Storage.ClientTotalLimitBytes, ServerTotalBytes: configured.Storage.ServerTotalLimitBytes, CloseoutReserveBytes: configured.Storage.CommandCloseoutReserveBytes, FilesystemFloorBytes: configured.Storage.FreeSpaceFloorBytes, }, }) if err != nil { return err } defer persistence.Close() listener, err := net.Listen("tcp", configured.Server.AgentListen) if err != nil { return fmt.Errorf("listen for agents: %w", err) } defer listener.Close() controlListener, cleanupControl, err := control.ListenUnix(configured.Server.ControlSocket) if err != nil { return err } defer func() { _ = cleanupControl() }() registry := session.NewRegistry() controlService, err := control.NewService(control.Options{ Store: persistence, DefaultQueueTTL: configured.Queue.DefaultTTL, DefaultQueueTTLSet: true, WakeClient: registry.Wake, Limits: agentproto.Limits{MaxEnvelopeBytes: configured.Protocol.MaxAgentEnvelopeBytes, MaxExecutionSpecBytes: configured.Protocol.MaxExecutionSpecBytes, MaxRawChunkBytes: configured.Protocol.MaxRawChunkBytes, MaxScriptBytes: configured.Protocol.MaxScriptBytes, MaxDetailBytes: configured.Storage.ProtocolDetailMaxBytes}, }) if err != nil { return err } grpcServer := grpc.NewServer(grpc.MaxRecvMsgSize(int(configured.Protocol.MaxControlRequestBytes)), grpc.MaxSendMsgSize(int(configured.Protocol.MaxControlRequestBytes))) rvboxv1.RegisterControlServer(grpcServer, controlService) agent := &session.AgentServer{ Store: persistence, Registry: registry, Path: configured.Server.AgentPath, Limits: agentproto.Limits{ MaxEnvelopeBytes: configured.Protocol.MaxAgentEnvelopeBytes, MaxExecutionSpecBytes: configured.Protocol.MaxExecutionSpecBytes, MaxRawChunkBytes: configured.Protocol.MaxRawChunkBytes, MaxScriptBytes: configured.Protocol.MaxScriptBytes, MaxDetailBytes: configured.Storage.ProtocolDetailMaxBytes, }, WriteDeadline: configured.Flow.WriteDeadline, HeartbeatIdle: configured.Protocol.HeartbeatIdle, LivenessTimeout: configured.Protocol.LivenessTimeout, } httpServer := &http.Server{Handler: agent, ReadHeaderTimeout: configured.Flow.WriteDeadline} serveError := make(chan error, 2) go func() { serveError <- httpServer.Serve(listener) }() go func() { serveError <- grpcServer.Serve(controlListener) }() signals := make(chan os.Signal, 1) signal.Notify(signals, os.Interrupt, syscall.SIGTERM) defer signal.Stop(signals) select { case err := <-serveError: if errors.Is(err, http.ErrServerClosed) { grpcServer.Stop() return nil } grpcServer.Stop() return err case <-signals: shutdownContext, cancel := context.WithTimeout(context.Background(), configured.Server.ShutdownGrace) defer cancel() httpErr := httpServer.Shutdown(shutdownContext) grpcDone := make(chan struct{}) go func() { grpcServer.GracefulStop(); close(grpcDone) }() select { case <-grpcDone: case <-shutdownContext.Done(): grpcServer.Stop() } return httpErr } }