refactor: server CLI interface

- add start/stop subcommands
- handle startup check when no clients attached
This commit is contained in:
Rob Watson 2025-05-15 07:28:58 +02:00
parent 8db2caf6a1
commit d48aeed636
2 changed files with 74 additions and 28 deletions

View File

@ -45,6 +45,10 @@ type Params struct {
// defaultChanSize is the default size of the dispatch channel.
const defaultChanSize = 64
// ErrOtherInstanceDetected is returned when another instance of the app is
// detected on startup.
var ErrOtherInstanceDetected = errors.New("another instance is currently running")
// New creates a new application instance.
func New(params Params) *App {
return &App{
@ -180,6 +184,10 @@ func (a *App) Run(ctx context.Context) error {
return startupErr
} else if ok {
startMediaServerC <- struct{}{}
} else if internalAPI.GetClientCount() == 0 {
// startup check failed, and there are no clients connected to the API so
// probably in server-only mode. In this case, we just bail out.
return ErrOtherInstanceDetected
}
for {
@ -447,3 +455,14 @@ func buildNetAddr(src config.RTMPSource) mediaserver.OptionalNetAddr {
return mediaserver.OptionalNetAddr{Enabled: true, NetAddr: domain.NetAddr(src.NetAddr)}
}
// Stop stops all containers and networks created by any instance of the app.
func (a *App) Stop(ctx context.Context) error {
containerClient, err := container.NewClient(ctx, a.dockerClient, a.logger.With("component", "container_client"))
if err != nil {
return fmt.Errorf("create container client: %w", err)
}
defer containerClient.Close()
return closeOtherInstances(ctx, containerClient)
}

79
main.go
View File

@ -48,7 +48,48 @@ func (e errInterrupt) ExitCode() int {
}
func main() {
serverSubcommands := []*cli.Command{
app := &cli.App{
Name: "Octoplex",
Usage: "Octoplex is a live video restreamer for Docker.",
Commands: []*cli.Command{
{
Name: "client",
Usage: "Run the client",
Action: func(c *cli.Context) error {
return runClient(c.Context, c)
},
},
{
Name: "server",
Usage: "Manage the standalone server.",
Action: func(c *cli.Context) error {
return c.App.Command("server").Subcommands[0].Action(c)
},
Subcommands: []*cli.Command{
{
Name: "start",
Usage: "Start the server",
Description: "Start the standalone server, without a CLI client attached.",
Action: func(c *cli.Context) error {
return runServer(c.Context, c, serverConfig{
stderrAvailable: true,
handleSigInt: true,
waitForClient: false,
})
},
},
{
Name: "stop",
Usage: "Stop the server",
Description: "Stop all containers and networks created by Octoplex, and exit.",
Action: func(c *cli.Context) error {
return runServer(c.Context, c, serverConfig{
stderrAvailable: true,
handleSigInt: false,
waitForClient: false,
})
},
},
{
Name: "print-config",
Usage: "Print the config file path",
@ -63,38 +104,15 @@ func main() {
return editConfig()
},
},
}
app := &cli.App{
Name: "Octoplex",
Usage: "Octoplex is a live video restreamer for Docker.",
Commands: []*cli.Command{
{
Name: "client",
Usage: "Run the client",
Action: func(c *cli.Context) error {
return runClient(c.Context, c)
},
},
{
Name: "server",
Usage: "Run the server",
Action: func(c *cli.Context) error {
return runServer(c.Context, c, serverConfig{
stderrAvailable: true,
handleSigInt: true,
waitForClient: false,
})
},
Subcommands: serverSubcommands,
},
{
Name: "run",
Usage: "Run server and client in the same process",
Description: "Run the server and client in the same process. This is useful for testing, debugging or running for a single user.",
Action: func(c *cli.Context) error {
return runClientAndServer(c)
},
Subcommands: serverSubcommands,
},
{
Name: "version",
@ -159,7 +177,7 @@ type serverConfig struct {
waitForClient bool
}
func runServer(ctx context.Context, _ *cli.Context, serverCfg serverConfig) error {
func runServer(ctx context.Context, c *cli.Context, serverCfg serverConfig) error {
ctx, cancel := context.WithCancelCause(ctx)
defer cancel(nil)
@ -224,6 +242,10 @@ func runServer(ctx context.Context, _ *cli.Context, serverCfg serverConfig) erro
Logger: logger,
})
if c.Command.Name == "stop" {
return app.Stop(ctx)
}
logger.Info(
"Starting server",
"version",
@ -240,6 +262,11 @@ func runServer(ctx context.Context, _ *cli.Context, serverCfg serverConfig) erro
if errors.Is(err, context.Canceled) && errors.Is(context.Cause(ctx), errInterrupt{}) {
return context.Cause(ctx)
}
if errors.Is(err, server.ErrOtherInstanceDetected) {
msg := "Another instance of the server may be running.\n" +
"To stop the server, run `octoplex server stop`."
return cli.Exit(msg, 1)
}
return err
}