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:
148
nix/module.nix
Normal file
148
nix/module.nix
Normal file
@@ -0,0 +1,148 @@
|
||||
{ config, lib, pkgs, ... }:
|
||||
|
||||
let
|
||||
cfg = config.services.org-garden;
|
||||
in
|
||||
{
|
||||
options.services.org-garden = {
|
||||
enable = lib.mkEnableOption "org-garden publishing service";
|
||||
|
||||
package = lib.mkOption {
|
||||
type = lib.types.package;
|
||||
description = "The org-garden package to use.";
|
||||
};
|
||||
|
||||
notesDir = lib.mkOption {
|
||||
type = lib.types.path;
|
||||
description = "Path to org-roam notes directory.";
|
||||
};
|
||||
|
||||
outputDir = lib.mkOption {
|
||||
type = lib.types.path;
|
||||
default = "/var/lib/org-garden";
|
||||
description = "Output directory for generated content.";
|
||||
};
|
||||
|
||||
port = lib.mkOption {
|
||||
type = lib.types.port;
|
||||
default = 8080;
|
||||
description = "HTTP server port.";
|
||||
};
|
||||
|
||||
wsPort = lib.mkOption {
|
||||
type = lib.types.port;
|
||||
default = 3001;
|
||||
description = "WebSocket hot reload port.";
|
||||
};
|
||||
|
||||
healthPort = lib.mkOption {
|
||||
type = lib.types.port;
|
||||
default = 9090;
|
||||
description = "Health check endpoint port.";
|
||||
};
|
||||
|
||||
zoteroUrl = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "http://localhost:23119";
|
||||
description = "Zotero Better BibTeX URL.";
|
||||
};
|
||||
|
||||
bibtexFilePath = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.path;
|
||||
default = null;
|
||||
description = "Path to fallback BibTeX file.";
|
||||
};
|
||||
|
||||
citationMode = lib.mkOption {
|
||||
type = lib.types.enum [ "silent" "warn" "strict" ];
|
||||
default = "warn";
|
||||
description = "Citation resolution failure mode.";
|
||||
};
|
||||
|
||||
user = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "org-garden";
|
||||
description = "User to run the service as.";
|
||||
};
|
||||
|
||||
group = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "org-garden";
|
||||
description = "Group to run the service as.";
|
||||
};
|
||||
|
||||
openFirewall = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = false;
|
||||
description = "Whether to open the firewall for the HTTP port.";
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf cfg.enable {
|
||||
users.users.${cfg.user} = {
|
||||
isSystemUser = true;
|
||||
group = cfg.group;
|
||||
home = cfg.outputDir;
|
||||
createHome = true;
|
||||
};
|
||||
|
||||
users.groups.${cfg.group} = { };
|
||||
|
||||
systemd.services.org-garden = {
|
||||
description = "Org-Garden Publishing Service";
|
||||
documentation = [ "https://github.com/ignacio.ballesteros/org-garden" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
after = [ "network.target" ];
|
||||
|
||||
environment = {
|
||||
NOTES_DIR = toString cfg.notesDir;
|
||||
OUTPUT_DIR = cfg.outputDir;
|
||||
PORT = toString cfg.port;
|
||||
WS_PORT = toString cfg.wsPort;
|
||||
HEALTH_PORT = toString cfg.healthPort;
|
||||
ZOTERO_URL = cfg.zoteroUrl;
|
||||
CITATION_MODE = cfg.citationMode;
|
||||
} // lib.optionalAttrs (cfg.bibtexFile != null) {
|
||||
BIBTEX_FILE = toString cfg.bibtexFile;
|
||||
};
|
||||
|
||||
serviceConfig = {
|
||||
Type = "exec";
|
||||
ExecStart = "${cfg.package}/bin/org-garden serve";
|
||||
Restart = "on-failure";
|
||||
RestartSec = 5;
|
||||
|
||||
# Directories
|
||||
StateDirectory = "org-garden";
|
||||
WorkingDirectory = cfg.outputDir;
|
||||
|
||||
# User/Group
|
||||
User = cfg.user;
|
||||
Group = cfg.group;
|
||||
|
||||
# Hardening
|
||||
NoNewPrivileges = true;
|
||||
ProtectSystem = "strict";
|
||||
ProtectHome = "read-only";
|
||||
ReadWritePaths = [ cfg.outputDir ];
|
||||
ReadOnlyPaths = [ cfg.notesDir ];
|
||||
PrivateTmp = true;
|
||||
PrivateDevices = true;
|
||||
ProtectKernelTunables = true;
|
||||
ProtectKernelModules = true;
|
||||
ProtectControlGroups = true;
|
||||
RestrictAddressFamilies = [ "AF_INET" "AF_INET6" "AF_UNIX" ];
|
||||
RestrictNamespaces = true;
|
||||
LockPersonality = true;
|
||||
MemoryDenyWriteExecute = false; # Required for BEAM JIT
|
||||
RestrictRealtime = true;
|
||||
RestrictSUIDSGID = true;
|
||||
RemoveIPC = true;
|
||||
};
|
||||
};
|
||||
|
||||
networking.firewall = lib.mkIf cfg.openFirewall {
|
||||
allowedTCPPorts = [ cfg.port ];
|
||||
};
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user