Files
rvbox/cmd/rvbox-server/main.go
T

125 lines
4.3 KiB
Go

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