From 3595d86d5cd87dc5bc82befc54ef7c837523bea9 Mon Sep 17 00:00:00 2001 From: sh Date: Fri, 10 Jul 2026 13:44:00 +0000 Subject: [PATCH] cli: add --headless option for chat relay Skips interactive prompts (relay address creation, display name) so the chat relay can run non-interactively as a service. Requires --relay; creating a new profile also requires --user-display-name. --- src/Simplex/Chat/Core.hs | 16 +++++++++------- src/Simplex/Chat/Mobile.hs | 1 + src/Simplex/Chat/Options.hs | 9 +++++++++ tests/ChatClient.hs | 1 + 4 files changed, 20 insertions(+), 7 deletions(-) diff --git a/src/Simplex/Chat/Core.hs b/src/Simplex/Chat/Core.hs index a18c2f3c93..c11fe285fe 100644 --- a/src/Simplex/Chat/Core.hs +++ b/src/Simplex/Chat/Core.hs @@ -92,11 +92,11 @@ simplexChatCore cfg@ChatConfig {confirmMigrations, testView, chatHooks} opts@Cha exitFailure runSimplexChat :: ChatConfig -> ChatOpts -> User -> ChatController -> (User -> ChatController -> IO ()) -> IO () -runSimplexChat ChatConfig {testView} ChatOpts {coreOptions = CoreChatOpts {chatRelay, chatRelayServer, maintenance}} u cc@ChatController {config = ChatConfig {chatHooks}} chat +runSimplexChat ChatConfig {testView} ChatOpts {coreOptions = CoreChatOpts {chatRelay, chatRelayServer, headless, maintenance}} u cc@ChatController {config = ChatConfig {chatHooks}} chat | maintenance = wait =<< async (chat u cc) | otherwise = do a1 <- runReaderT (startChatController True True) cc - when (chatRelay && not testView) $ askCreateRelayAddress cc u chatRelayServer + when (chatRelay && not testView) $ askCreateRelayAddress cc u chatRelayServer headless forM_ (postStartHook chatHooks) ($ cc) a2 <- async $ chat u cc waitEither_ a1 a2 @@ -140,13 +140,15 @@ selectActiveUser CoreChatOpts {chatRelay} st users in Just <$> withTransaction st (`setActiveUser` user) createActiveUser :: ChatController -> CoreChatOpts -> Maybe CreateBotOpts -> Maybe Text -> Maybe ImageData -> IO User -createActiveUser cc CoreChatOpts {chatRelay} createBot_ userDisplayName_ img_ = case createBot_ of +createActiveUser cc CoreChatOpts {chatRelay, headless} createBot_ userDisplayName_ img_ = case createBot_ of Just CreateBotOpts {botDisplayName, allowFiles, clientService} -> do let preferences = if allowFiles then Nothing else Just emptyChatPrefs {files = Just FilesPreference {allow = FANo}} createUser exitFailure clientService $ (mkProfile botDisplayName) {peerType = Just CPTBot, preferences} Nothing -> case userDisplayName_ of Just displayName -> createUser exitFailure False $ (mkProfile displayName :: Profile) {image = img_} - Nothing -> putStrLn prompt >> loop + Nothing + | headless -> putStrLn "No user profile found and no --user-display-name provided (required with --headless)" >> exitFailure + | otherwise -> putStrLn prompt >> loop where prompt | chatRelay = @@ -167,8 +169,8 @@ createActiveUser cc CoreChatOpts {chatRelay} createBot_ userDisplayName_ img_ = Right (CRActiveUser user) -> pure user r -> printResponseEvent (Nothing, Nothing) (config cc) r >> onError -askCreateRelayAddress :: ChatController -> User -> Maybe SMPServerWithAuth -> IO () -askCreateRelayAddress cc@ChatController {chatStore} user@User {userId} server_ = +askCreateRelayAddress :: ChatController -> User -> Maybe SMPServerWithAuth -> Bool -> IO () +askCreateRelayAddress cc@ChatController {chatStore} user@User {userId} server_ headless = withTransaction chatStore (\db -> runExceptT $ getUserAddress db user) >>= \case Right _ -> pure () Left SEUserContactLinkNotFound -> promptCreate @@ -176,7 +178,7 @@ askCreateRelayAddress cc@ChatController {chatStore} user@User {userId} server_ = where promptCreate :: IO () promptCreate = do - ok <- onOffPrompt "Create relay address" True + ok <- if headless then pure True else onOffPrompt "Create relay address" True when ok $ execChatCommand' (APICreateMyAddress userId server_) 0 `runReaderT` cc >>= \case Right (CRUserContactLinkCreated _ address) -> do diff --git a/src/Simplex/Chat/Mobile.hs b/src/Simplex/Chat/Mobile.hs index 6ff17a4ba7..4e3dc3ab34 100644 --- a/src/Simplex/Chat/Mobile.hs +++ b/src/Simplex/Chat/Mobile.hs @@ -263,6 +263,7 @@ mobileChatOpts dbOptions = chatRelay = False, webPreviewConfig = Nothing, chatRelayServer = Nothing, + headless = False, highlyAvailable = False, yesToUpMigrations = False, migrationBackupPath = Just "", diff --git a/src/Simplex/Chat/Options.hs b/src/Simplex/Chat/Options.hs index 864202a189..bfdf29befd 100644 --- a/src/Simplex/Chat/Options.hs +++ b/src/Simplex/Chat/Options.hs @@ -70,6 +70,7 @@ data CoreChatOpts = CoreChatOpts chatRelay :: Bool, webPreviewConfig :: Maybe WebPreviewConfig, chatRelayServer :: Maybe SMPServerWithAuth, + headless :: Bool, highlyAvailable :: Bool, yesToUpMigrations :: Bool, migrationBackupPath :: Maybe FilePath, @@ -292,6 +293,11 @@ coreChatOptsP appDir defaultDbName = do <> metavar "SERVER" <> help "SMP server to use for chat relay address link (requires --relay)" ) + headless <- + switch + ( long "headless" + <> help "Run chat relay without interactive prompts, e.g. as a service (requires --relay; on first run also --user-display-name to create the profile)" + ) highlyAvailable <- switch ( long "ha" @@ -339,6 +345,9 @@ coreChatOptsP appDir defaultDbName = do chatRelayServer = case chatRelayServer of Just _ | not chatRelay -> error "--relay-address-server option requires --relay option" _ -> chatRelayServer, + headless = case headless of + True | not chatRelay -> error "--headless option requires --relay option" + _ -> headless, highlyAvailable, yesToUpMigrations, migrationBackupPath, diff --git a/tests/ChatClient.hs b/tests/ChatClient.hs index 654d98bb05..9ce0defd1a 100644 --- a/tests/ChatClient.hs +++ b/tests/ChatClient.hs @@ -158,6 +158,7 @@ testCoreOpts = chatRelay = False, webPreviewConfig = Nothing, chatRelayServer = Nothing, + headless = False, highlyAvailable = False, yesToUpMigrations = False, migrationBackupPath = Nothing,