# Runtime configuration. Built once at startup, then treated as immutable. # Every knob has a default so the service runs with zero configuration, and # every knob can be overridden by an environment variable (see `config_from_env`). Base.@kwdef struct Config host::String = "127.0.0.1" port::Int = 8080 worker_count::Int = Threads.nthreads() queue_capacity::Int = 1000 # Stage 2 (enrichment) has its own pool + queue: exiftool work is process-spawn # and I/O bound, a different cost profile than the CPU-bound Lux classify, so # the two pools are tuned independently. known_worker_count::Int = Threads.nthreads() known_queue_capacity::Int = 1000 # Stage 3 (content triage) also gets its own pool + queue: sorting an # unrecognized file into binary/ vs text/ is cheap I/O, tuned independently # of the classify and enrich pools. unknown_worker_count::Int = Threads.nthreads() unknown_queue_capacity::Int = 1000 spool_dir::String = "data/spool" # files land here on intake (pending classification) known_dir::String = "data/known" # classified-known, awaiting enrichment (stage 2) unknown_dir::String = "data/unknown" # classified-unknown, awaiting content triage (stage 3) binary_dir::String = "data/binary" # stage-3 sink: unknown files that look like binary data text_dir::String = "data/text" # stage-3 sink: unknown files that look like text done_dir::String = "data/done" # fully enriched known files (+ .meta.json sidecars) failed_dir::String = "data/failed" # files move here if a worker throws model_path::String = "model/classifier.jld2" # committed classifier artifact, loaded at startup exiftool_timeout::Int = 30 # seconds before a stuck exiftool is killed → degraded sidecar end """ config_from_env(; overrides...) Build a `Config` from environment variables, falling back to the struct defaults. Any keyword `overrides` win over the environment (useful for tests and for `FileServer.run(; port=...)`). Recognised variables: FS_HOST, FS_PORT, FS_WORKERS, FS_QUEUE_CAPACITY, FS_KNOWN_WORKERS, FS_KNOWN_QUEUE_CAPACITY, FS_UNKNOWN_WORKERS, FS_UNKNOWN_QUEUE_CAPACITY, FS_SPOOL_DIR, FS_KNOWN_DIR, FS_UNKNOWN_DIR, FS_BINARY_DIR, FS_TEXT_DIR, FS_DONE_DIR, FS_FAILED_DIR, FS_MODEL_PATH, FS_EXIFTOOL_TIMEOUT """ function config_from_env(; host=nothing, port=nothing, worker_count=nothing, queue_capacity=nothing, known_worker_count=nothing, known_queue_capacity=nothing, unknown_worker_count=nothing, unknown_queue_capacity=nothing, spool_dir=nothing, known_dir=nothing, unknown_dir=nothing, binary_dir=nothing, text_dir=nothing, done_dir=nothing, failed_dir=nothing, model_path=nothing, exiftool_timeout=nothing) Config( host = something(host, get(ENV, "FS_HOST", "127.0.0.1")), port = something(port, parse(Int, get(ENV, "FS_PORT", "8080"))), worker_count = something(worker_count, parse(Int, get(ENV, "FS_WORKERS", string(Threads.nthreads())))), queue_capacity = something(queue_capacity, parse(Int, get(ENV, "FS_QUEUE_CAPACITY", "1000"))), known_worker_count = something(known_worker_count, parse(Int, get(ENV, "FS_KNOWN_WORKERS", string(Threads.nthreads())))), known_queue_capacity = something(known_queue_capacity, parse(Int, get(ENV, "FS_KNOWN_QUEUE_CAPACITY", "1000"))), unknown_worker_count = something(unknown_worker_count, parse(Int, get(ENV, "FS_UNKNOWN_WORKERS", string(Threads.nthreads())))), unknown_queue_capacity = something(unknown_queue_capacity, parse(Int, get(ENV, "FS_UNKNOWN_QUEUE_CAPACITY", "1000"))), spool_dir = something(spool_dir, get(ENV, "FS_SPOOL_DIR", "data/spool")), known_dir = something(known_dir, get(ENV, "FS_KNOWN_DIR", "data/known")), unknown_dir = something(unknown_dir, get(ENV, "FS_UNKNOWN_DIR", "data/unknown")), binary_dir = something(binary_dir, get(ENV, "FS_BINARY_DIR", "data/binary")), text_dir = something(text_dir, get(ENV, "FS_TEXT_DIR", "data/text")), done_dir = something(done_dir, get(ENV, "FS_DONE_DIR", "data/done")), failed_dir = something(failed_dir, get(ENV, "FS_FAILED_DIR", "data/failed")), model_path = something(model_path, get(ENV, "FS_MODEL_PATH", "model/classifier.jld2")), exiftool_timeout = something(exiftool_timeout, parse(Int, get(ENV, "FS_EXIFTOOL_TIMEOUT", "30"))), ) end "Create all the pipeline-stage directories if they don't already exist." function ensure_dirs(cfg::Config) for d in (cfg.spool_dir, cfg.known_dir, cfg.unknown_dir, cfg.binary_dir, cfg.text_dir, cfg.done_dir, cfg.failed_dir) mkpath(d) end return nothing end