Files
jbrechtel 460b7d0fdc
Build and Deploy / build-and-deploy (push) Successful in 2m43s
Store cook log DB in writable data dir, not read-only recipes mount
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>
2026-07-12 21:53:35 -04:00

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