From 06a1501fbe9f8205b3adfc5007823aee010dd014 Mon Sep 17 00:00:00 2001 From: Rob Watson Date: Wed, 30 Apr 2025 22:46:27 +0200 Subject: [PATCH] feat: headless mode --- internal/app/app.go | 43 +++++++++++++++++++++++++------------------ main.go | 4 +++- 2 files changed, 28 insertions(+), 19 deletions(-) diff --git a/internal/app/app.go b/internal/app/app.go index 7297e95..e5d4649 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -21,12 +21,15 @@ import ( // App is an instance of the app. type App struct { - cfg config.Config - configService *config.Service - eventBus *event.Bus - dispatchC chan event.Command - dockerClient container.DockerClient - screen *terminal.Screen // Screen may be nil. + cfg config.Config + configService *config.Service + eventBus *event.Bus + dispatchC chan event.Command + dockerClient container.DockerClient + screen *terminal.Screen // Screen may be nil. + // TODO: startup check + // TODO: handle SIGINT + headless bool clipboardAvailable bool configFilePath string buildInfo domain.BuildInfo @@ -39,6 +42,7 @@ type Params struct { DockerClient container.DockerClient ChanSize int Screen *terminal.Screen // Screen may be nil. + Headless bool ClipboardAvailable bool ConfigFilePath string BuildInfo domain.BuildInfo @@ -57,6 +61,7 @@ func New(params Params) *App { dispatchC: make(chan event.Command, cmp.Or(params.ChanSize, defaultChanSize)), dockerClient: params.DockerClient, screen: params.Screen, + headless: params.Headless, clipboardAvailable: params.ClipboardAvailable, configFilePath: params.ConfigFilePath, buildInfo: params.BuildInfo, @@ -75,19 +80,21 @@ func (a *App) Run(ctx context.Context) error { return errors.New("config: either sources.mediaServer.rtmp.enabled or sources.mediaServer.rtmps.enabled must be set") } - ui, err := terminal.StartUI(ctx, terminal.StartParams{ - EventBus: a.eventBus, - Dispatcher: func(cmd event.Command) { a.dispatchC <- cmd }, - Screen: a.screen, - ClipboardAvailable: a.clipboardAvailable, - ConfigFilePath: a.configFilePath, - BuildInfo: a.buildInfo, - Logger: a.logger.With("component", "ui"), - }) - if err != nil { - return fmt.Errorf("start terminal user interface: %w", err) + if !a.headless { + ui, err := terminal.StartUI(ctx, terminal.StartParams{ + EventBus: a.eventBus, + Dispatcher: func(cmd event.Command) { a.dispatchC <- cmd }, + Screen: a.screen, + ClipboardAvailable: a.clipboardAvailable, + ConfigFilePath: a.configFilePath, + BuildInfo: a.buildInfo, + Logger: a.logger.With("component", "ui"), + }) + if err != nil { + return fmt.Errorf("start terminal user interface: %w", err) + } + defer ui.Close() } - defer ui.Close() // emptyUI is a dummy function that sets the UI state to an empty state, and // re-renders the screen. diff --git a/main.go b/main.go index 23420a5..e2f9a4e 100644 --- a/main.go +++ b/main.go @@ -100,6 +100,7 @@ func run(ctx context.Context) error { app := app.New(app.Params{ ConfigService: configService, DockerClient: dockerClient, + Headless: os.Getenv("OCTO_HEADLESS") != "", ClipboardAvailable: clipboardAvailable, ConfigFilePath: configService.Path(), BuildInfo: domain.BuildInfo{ @@ -160,7 +161,8 @@ func printUsage() { os.Stderr.WriteString(" help Print this help message\n") os.Stderr.WriteString("\n") os.Stderr.WriteString("Additionally, Octoplex can be configured with the following environment variables:\n\n") - os.Stderr.WriteString(" OCTO_DEBUG Enables debug logging if set\n") + os.Stderr.WriteString(" OCTO_DEBUG Enables debug logging if set\n\n") + os.Stderr.WriteString(" OCTO_HEADLESS Enables headless mode if set (experimental)\n") } // buildLogger builds the logger, which may be a no-op logger.