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:
@@ -48,6 +48,8 @@ defmodule OrgGarden.CLI do
|
||||
|
||||
require Logger
|
||||
|
||||
alias OrgGarden.Config
|
||||
|
||||
@transforms [OrgGarden.Transforms.Citations]
|
||||
|
||||
def main(argv) do
|
||||
@@ -70,31 +72,36 @@ defmodule OrgGarden.CLI do
|
||||
def handle_serve(argv) do
|
||||
require_quartz_env()
|
||||
{notes_dir, output_dir, content_dir, opts} = parse_serve_args(argv)
|
||||
pipeline_opts = build_pipeline_opts()
|
||||
|
||||
# Initial batch export
|
||||
wipe(content_dir)
|
||||
export_all(notes_dir, output_dir)
|
||||
run_pipeline(content_dir, pipeline_opts)
|
||||
generate_index(content_dir)
|
||||
|
||||
IO.puts("==> Starting development server...")
|
||||
|
||||
{:ok, _pid} =
|
||||
OrgGarden.Supervisor.start_link(
|
||||
notes_dir: notes_dir,
|
||||
output_dir: output_dir,
|
||||
content_dir: content_dir,
|
||||
pipeline_opts: pipeline_opts,
|
||||
transforms: @transforms,
|
||||
port: opts[:port] || 8080,
|
||||
ws_port: opts[:ws_port] || 3001
|
||||
)
|
||||
case OrgGarden.Server.start_link(
|
||||
notes_dir: notes_dir,
|
||||
output_dir: output_dir,
|
||||
content_dir: content_dir,
|
||||
port: opts[:port],
|
||||
ws_port: opts[:ws_port]
|
||||
) do
|
||||
{:ok, pid} ->
|
||||
IO.puts("==> Server running at http://localhost:#{opts[:port] || Config.get(:http_port, 8080)}")
|
||||
IO.puts("==> Watching #{notes_dir} for changes (Ctrl+C to stop)")
|
||||
|
||||
IO.puts("==> Server running at http://localhost:#{opts[:port] || 8080}")
|
||||
IO.puts("==> Watching #{notes_dir} for changes (Ctrl+C to stop)")
|
||||
# Wait for server to exit
|
||||
ref = Process.monitor(pid)
|
||||
|
||||
Process.sleep(:infinity)
|
||||
receive do
|
||||
{:DOWN, ^ref, :process, ^pid, reason} ->
|
||||
case reason do
|
||||
:normal -> :ok
|
||||
:shutdown -> :ok
|
||||
{:shutdown, _} -> :ok
|
||||
_ -> abort("Server crashed: #{inspect(reason)}")
|
||||
end
|
||||
end
|
||||
|
||||
{:error, reason} ->
|
||||
abort("Failed to start server: #{inspect(reason)}")
|
||||
end
|
||||
end
|
||||
|
||||
defp parse_serve_args(argv) do
|
||||
@@ -122,7 +129,7 @@ defmodule OrgGarden.CLI do
|
||||
def handle_build(argv) do
|
||||
quartz_path = require_quartz_env()
|
||||
{notes_dir, output_dir, content_dir, _opts} = parse_build_args(argv)
|
||||
pipeline_opts = build_pipeline_opts()
|
||||
pipeline_opts = Config.pipeline_opts()
|
||||
|
||||
# Full batch export
|
||||
wipe(content_dir)
|
||||
@@ -130,7 +137,7 @@ defmodule OrgGarden.CLI do
|
||||
run_pipeline(content_dir, pipeline_opts)
|
||||
generate_index(content_dir)
|
||||
|
||||
node_path = System.get_env("NODE_PATH", "node")
|
||||
node_path = Config.get(:node_path, "node")
|
||||
|
||||
IO.puts("==> Building static site with Quartz...")
|
||||
|
||||
@@ -177,7 +184,7 @@ defmodule OrgGarden.CLI do
|
||||
|
||||
def handle_export(argv) do
|
||||
{notes_dir, output_dir, content_dir, watch?} = parse_export_args(argv)
|
||||
pipeline_opts = build_pipeline_opts()
|
||||
pipeline_opts = Config.pipeline_opts()
|
||||
|
||||
# Phase 1-4: full batch export
|
||||
wipe(content_dir)
|
||||
@@ -197,16 +204,29 @@ defmodule OrgGarden.CLI do
|
||||
if watch? do
|
||||
IO.puts("==> Watching #{notes_dir} for .org changes... (Ctrl+C to stop)")
|
||||
|
||||
{:ok, _pid} =
|
||||
OrgGarden.Watcher.start_link(
|
||||
notes_dir: notes_dir,
|
||||
output_dir: output_dir,
|
||||
content_dir: content_dir,
|
||||
pipeline_opts: pipeline_opts,
|
||||
transforms: @transforms
|
||||
{:ok, pid} =
|
||||
DynamicSupervisor.start_child(
|
||||
OrgGarden.DynamicSupervisor,
|
||||
{OrgGarden.Watcher,
|
||||
notes_dir: notes_dir,
|
||||
output_dir: output_dir,
|
||||
content_dir: content_dir,
|
||||
pipeline_opts: pipeline_opts,
|
||||
transforms: @transforms}
|
||||
)
|
||||
|
||||
Process.sleep(:infinity)
|
||||
# Wait for watcher to exit
|
||||
ref = Process.monitor(pid)
|
||||
|
||||
receive do
|
||||
{:DOWN, ^ref, :process, ^pid, reason} ->
|
||||
case reason do
|
||||
:normal -> :ok
|
||||
:shutdown -> :ok
|
||||
{:shutdown, _} -> :ok
|
||||
_ -> abort("Watcher crashed: #{inspect(reason)}")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -235,7 +255,7 @@ defmodule OrgGarden.CLI do
|
||||
dir
|
||||
|
||||
[] ->
|
||||
System.get_env("NOTES_DIR") ||
|
||||
Config.get(:notes_dir) ||
|
||||
abort("Usage: org-garden #{command} <notes-dir> [options]")
|
||||
end
|
||||
|
||||
@@ -249,7 +269,7 @@ defmodule OrgGarden.CLI do
|
||||
end
|
||||
|
||||
defp extract_output_dir(opts) do
|
||||
(opts[:output] || System.get_env("OUTPUT_DIR") || File.cwd!())
|
||||
(opts[:output] || Config.get(:output_dir) || File.cwd!())
|
||||
|> Path.expand()
|
||||
end
|
||||
|
||||
@@ -328,7 +348,7 @@ defmodule OrgGarden.CLI do
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
defp require_quartz_env do
|
||||
case System.get_env("QUARTZ_PATH") do
|
||||
case Config.get(:quartz_path) do
|
||||
nil ->
|
||||
abort("""
|
||||
Error: QUARTZ_PATH environment variable not set.
|
||||
@@ -355,19 +375,6 @@ defmodule OrgGarden.CLI do
|
||||
end
|
||||
end
|
||||
|
||||
defp build_pipeline_opts do
|
||||
%{
|
||||
zotero_url: System.get_env("ZOTERO_URL", "http://localhost:23119"),
|
||||
bibtex_file: System.get_env("BIBTEX_FILE"),
|
||||
citation_mode:
|
||||
case System.get_env("CITATION_MODE", "warn") do
|
||||
"silent" -> :silent
|
||||
"strict" -> :strict
|
||||
_ -> :warn
|
||||
end
|
||||
}
|
||||
end
|
||||
|
||||
defp abort(message) do
|
||||
IO.puts(:stderr, message)
|
||||
System.halt(1)
|
||||
|
||||
Reference in New Issue
Block a user