Add service infrastructure for long-running deployment
- Add configuration system (config/*.exs, OrgGarden.Config) - Refactor supervision tree with DynamicSupervisor and Registry - Add OrgGarden.Server for serve mode lifecycle management - Add health check HTTP endpoints (Bandit/Plug on :9090) - Add telemetry events for export and watcher operations - Implement graceful shutdown with SIGTERM handling - Add Mix Release support with overlay scripts - Add NixOS module for systemd service deployment - Update documentation with service usage
This commit is contained in:
98
lib/org_garden/config.ex
Normal file
98
lib/org_garden/config.ex
Normal file
@@ -0,0 +1,98 @@
|
||||
defmodule OrgGarden.Config do
|
||||
@moduledoc """
|
||||
Centralized configuration access with validation.
|
||||
|
||||
Provides a unified interface for accessing configuration values,
|
||||
with support for defaults and required value validation.
|
||||
|
||||
## Usage
|
||||
|
||||
OrgGarden.Config.get(:http_port)
|
||||
#=> 8080
|
||||
|
||||
OrgGarden.Config.get!(:quartz_path)
|
||||
#=> "/path/to/quartz" or raises if not set
|
||||
|
||||
OrgGarden.Config.pipeline_opts()
|
||||
#=> %{zotero_url: "...", bibtex_file: nil, citation_mode: :warn}
|
||||
"""
|
||||
|
||||
@doc """
|
||||
Get a configuration value with an optional default.
|
||||
"""
|
||||
def get(key, default \\ nil) do
|
||||
Application.get_env(:org_garden, key, default)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Get a required configuration value. Raises if not set.
|
||||
"""
|
||||
def get!(key) do
|
||||
case Application.get_env(:org_garden, key) do
|
||||
nil -> raise ArgumentError, "Missing required configuration: #{key}"
|
||||
value -> value
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Build pipeline options map for transforms.
|
||||
"""
|
||||
def pipeline_opts do
|
||||
%{
|
||||
zotero_url: get(:zotero_url, "http://localhost:23119"),
|
||||
bibtex_file: get(:bibtex_file),
|
||||
citation_mode: get(:citation_mode, :warn)
|
||||
}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Validate that all required configuration is present.
|
||||
Returns :ok or {:error, reasons}.
|
||||
"""
|
||||
def validate do
|
||||
errors =
|
||||
[]
|
||||
|> validate_quartz_path()
|
||||
|> validate_citation_mode()
|
||||
|
||||
case errors do
|
||||
[] -> :ok
|
||||
errors -> {:error, errors}
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Validate configuration and raise on errors.
|
||||
"""
|
||||
def validate! do
|
||||
case validate() do
|
||||
:ok -> :ok
|
||||
{:error, errors} -> raise "Configuration errors: #{inspect(errors)}"
|
||||
end
|
||||
end
|
||||
|
||||
# Private validation helpers
|
||||
|
||||
defp validate_quartz_path(errors) do
|
||||
case get(:quartz_path) do
|
||||
nil ->
|
||||
errors
|
||||
|
||||
path ->
|
||||
cli_path = Path.join(path, "quartz/bootstrap-cli.mjs")
|
||||
|
||||
if File.exists?(cli_path) do
|
||||
errors
|
||||
else
|
||||
[{:quartz_path, "bootstrap-cli.mjs not found at #{cli_path}"} | errors]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp validate_citation_mode(errors) do
|
||||
case get(:citation_mode) do
|
||||
mode when mode in [:silent, :warn, :strict] -> errors
|
||||
other -> [{:citation_mode, "Invalid citation mode: #{inspect(other)}"} | errors]
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user