460b7d0fdc
Build and Deploy / build-and-deploy (push) Successful in 2m43s
The cook log SQLite DB was created at recipeDir/cook-log.db. In the Docker deployment /recipes is mounted read-only, so SQLite could never create the file there — the cook log has never worked in the container. The writable /data volume was mounted but unused. - initDb now creates the DB's parent directory if missing, so startup initialization is self-contained and idempotent. - app takes a dataDir and places cook-log.db there. - Add optional --data-dir flag (defaults to the recipe dir, preserving local-dev behavior); Docker CMD passes --data-dir /data. Verified end-to-end as root against a read-only recipe dir and a non-existent data dir: the dir and DB are created and cook-log POST/GET round-trips. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
112 lines
4.0 KiB
Haskell
112 lines
4.0 KiB
Haskell
{-# 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
|