{-# LANGUAGE OverloadedStrings #-} {- | Entry point for the Roux recipe server. Starts a Warp HTTP server and serves recipe content. -} module Main (main) where import Data.Maybe (fromMaybe) import Data.String (fromString) import qualified Network.Wai.Handler.Warp as Warp import qualified Options.Applicative as Opts import System.IO (BufferMode (NoBuffering), hSetBuffering, stderr, stdout) import qualified Roux import qualified Roux.Config as Config -- --------------------------------------------------------------------------- -- CLI options -- --------------------------------------------------------------------------- data Options = Options { optHost :: String , optPort :: Int , optRecipeDir :: FilePath , optDataDir :: Maybe FilePath , optConfigFile :: FilePath } optionsParser :: Opts.Parser Options optionsParser = Options <$> Opts.strOption ( Opts.long "host" <> Opts.metavar "HOST" <> Opts.help "Listen host" <> Opts.value "0.0.0.0" <> Opts.showDefault ) <*> Opts.option Opts.auto ( Opts.long "port" <> Opts.short 'p' <> Opts.metavar "PORT" <> Opts.help "Listen port" <> Opts.value 8080 <> Opts.showDefault ) <*> Opts.strOption ( Opts.long "recipe-dir" <> Opts.short 'r' <> Opts.metavar "DIR" <> Opts.help "Path to directory containing Cooklang recipe files" <> Opts.value "recipes" <> Opts.showDefault ) <*> Opts.optional ( Opts.strOption ( Opts.long "data-dir" <> Opts.metavar "DIR" <> Opts.help "Writable directory for app state (the cook log DB). \ \Created on startup if missing. Defaults to the recipe directory. \ \Set this when the recipe directory is read-only (e.g. the Docker deployment mounts recipes read-only and passes --data-dir /data)." ) ) <*> Opts.strOption ( Opts.long "config-file" <> Opts.metavar "FILE" <> Opts.help "Path to YAML configuration file" <> Opts.value "roux-config.yaml" <> Opts.showDefault ) main :: IO () main = do -- Disable buffering so Docker sees log output immediately. hSetBuffering stdout NoBuffering hSetBuffering stderr NoBuffering opts <- Opts.execParser $ Opts.info (optionsParser Opts.<**> Opts.helper) ( Opts.fullDesc <> Opts.progDesc "Roux recipe management server" <> Opts.header "roux-server — personal/family recipe manager" ) let settings = Warp.setPort (optPort opts) $ Warp.setHost (fromString (optHost opts)) Warp.defaultSettings -- Cook log DB (and any future state) lives here; falls back to the recipe -- directory when --data-dir is not given. let dataDir = fromMaybe (optRecipeDir opts) (optDataDir opts) putStrLn $ "[roux] scanning recipes from " <> optRecipeDir opts putStrLn $ "[roux] listening on " <> optHost opts <> ":" <> show (optPort opts) putStrLn $ "[roux] config file: " <> optConfigFile opts putStrLn $ "[roux] data dir: " <> dataDir -- Load config (file may be missing; LLM features disabled gracefully) configResult <- Config.loadConfig (optConfigFile opts) let config = case configResult of Right cfg -> cfg Left _ -> Config.RouxConfig Nothing case configResult of Left err -> putStrLn $ "[roux] warning: " <> err <> " — LLM features disabled" Right _ -> pure () waiApp <- Roux.app (optRecipeDir opts) dataDir config Warp.runSettings settings waiApp