diff --git a/cmd/rvbox-server/main.go b/cmd/rvbox-server/main.go index 178f493..36e03cb 100644 --- a/cmd/rvbox-server/main.go +++ b/cmd/rvbox-server/main.go @@ -1,4 +1,93 @@ // Command rvbox-server is the RVBox control and agent server. package main -func main() {} +import ( + "context" + "errors" + "flag" + "fmt" + "log" + "net" + "net/http" + "os" + "os/signal" + "syscall" + + "github.com/rvbox/rvbox/internal/agentproto" + "github.com/rvbox/rvbox/internal/config" + "github.com/rvbox/rvbox/internal/server/session" + "github.com/rvbox/rvbox/internal/server/store" +) + +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) + } + agent := &session.AgentServer{ + Store: persistence, Registry: session.NewRegistry(), 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, 1) + go func() { serveError <- httpServer.Serve(listener) }() + + 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) { + return nil + } + return err + case <-signals: + shutdownContext, cancel := context.WithTimeout(context.Background(), configured.Server.ShutdownGrace) + defer cancel() + return httpServer.Shutdown(shutdownContext) + } +} diff --git a/internal/server/session/agent_server.go b/internal/server/session/agent_server.go index d8af37a..31136a9 100644 --- a/internal/server/session/agent_server.go +++ b/internal/server/session/agent_server.go @@ -220,10 +220,7 @@ func (server *AgentServer) writeLoop(ctx context.Context, connection *websocket. } func (server *AgentServer) close(connection *websocket.Conn, status websocket.StatusCode, reason string) { - ctx, cancel := context.WithTimeout(context.Background(), server.writeDeadline()) - defer cancel() _ = connection.Close(status, reason) - _ = ctx } func (server *AgentServer) agentPath() string {