// 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" "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) } }