{ 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.bibtexFilePath != null) { BIBTEX_FILE = toString cfg.bibtexFilePath; }; 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 ]; }; }; }