From 22772a1d3d7d30bd13ac15898ca5b13840226550 Mon Sep 17 00:00:00 2001 From: Kris Date: Thu, 19 Feb 2026 21:04:10 +0200 Subject: [PATCH] fixxxxxxxxxxxxxxxx --- README.md | 4 +- modules/wafrn.nix | 155 ++++++++++++++++++++++++++++++++++++---------- 2 files changed, 124 insertions(+), 35 deletions(-) diff --git a/README.md b/README.md index 6dc7186..3180a66 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,5 @@ # wafrn-nix -By default this module uses a flake-pinned Wafrn source, so you do not need to clone Wafrn manually. You can still override `services.wafrn.source` if you want a local checkout. - Example: ```nix @@ -18,6 +16,8 @@ Example: services.wafrn = { enable = true; + # package = my-wafrn-src-package; + # optional: override source checkout path # source = "/srv/wafrn"; stateDir = "/var/lib/wafrn"; diff --git a/modules/wafrn.nix b/modules/wafrn.nix index 952c776..bb73ad0 100644 --- a/modules/wafrn.nix +++ b/modules/wafrn.nix @@ -12,6 +12,23 @@ let cfg = config.services.wafrn; + defaultWafrnPackage = + if wafrnSrc != null then + pkgs.stdenvNoCC.mkDerivation { + pname = "wafrn-source"; + version = "unstable"; + src = wafrnSrc; + dontConfigure = true; + dontBuild = true; + installPhase = '' + mkdir -p "$out" + cp -a ./. "$out/" + chmod -R u+w "$out" + ''; + } + else + null; + toEnvString = value: if builtins.isBool value then (if value then "true" else "false") @@ -105,14 +122,17 @@ let serviceEnvFile = "${cfg.stateDir}/.env"; composeFile = "${cfg.stateDir}/docker-compose.yml"; - sourcePath = - if cfg.source != null then - cfg.source - else if wafrnSrc != null then - toString wafrnSrc - else - ""; + packageSourcePath = toString cfg.package; + sourcePath = if cfg.source != null then cfg.source else ""; preparedSourcePath = "${cfg.stateDir}/source"; + usingPrebuiltImages = cfg.images.backend != null && cfg.images.frontend != null; + effectiveCaddyConfigDir = + if cfg.caddyConfigDir != null then + cfg.caddyConfigDir + else if usingPrebuiltImages then + null + else + "${preparedSourcePath}/packages/caddy"; publishedPorts = lib.optionals (cfg.httpPort != null) [ "${toString cfg.httpPort}:80" ] @@ -132,8 +152,24 @@ let dockerfile = "packages/backend/Dockerfile"; }; + backendContainerSpec = + if usingPrebuiltImages then + { image = cfg.images.backend; } + else + { build = backendBuild; }; + + frontendContainerSpec = + if usingPrebuiltImages then + { image = cfg.images.frontend; } + else + { + build = { + context = preparedSourcePath; + dockerfile = "packages/frontend/Dockerfile"; + }; + }; + backendService = { - build = backendBuild; depends_on = { db = { condition = "service_started"; }; redis = { condition = "service_started"; }; @@ -153,9 +189,9 @@ let composeConfig = { services = { - backend = serviceCommon // backendService; + backend = serviceCommon // backendService // backendContainerSpec; - migration = serviceCommon // backendService // { + migration = serviceCommon // backendService // backendContainerSpec // { restart = "no"; depends_on = { db = { condition = "service_started"; }; @@ -165,11 +201,7 @@ let command = "npm exec tsx migrate.ts init-container"; }; - frontend = serviceCommon // { - build = { - context = preparedSourcePath; - dockerfile = "packages/frontend/Dockerfile"; - }; + frontend = serviceCommon // frontendContainerSpec // { restart = "unless-stopped"; ports = publishedPorts; extra_hosts = [ "host.docker.internal:host-gateway" ]; @@ -177,7 +209,8 @@ let "${cfg.stateDir}/caddy:/data" "${cfg.stateDir}/frontend:/var/www/html/frontend" "${cfg.stateDir}/uploads:/var/www/html/uploads" - "${preparedSourcePath}/packages/caddy:/etc/caddy/config" + ] ++ lib.optionals (effectiveCaddyConfigDir != null) [ + "${effectiveCaddyConfigDir}:/etc/caddy/config" ]; }; @@ -205,8 +238,7 @@ let }; } // optionalAttrs cfg.bluesky.enable { - pds_worker = serviceCommon // { - build = backendBuild; + pds_worker = serviceCommon // backendContainerSpec // { command = "npm exec tsx atproto.ts"; depends_on = { db = { condition = "service_started"; }; @@ -266,7 +298,41 @@ in type = types.nullOr types.str; default = null; example = "/srv/wafrn"; - description = "Optional path to a Wafrn source checkout. If null, the module uses the pinned source from this flake input."; + description = "Optional path to a Wafrn source checkout. If null, the service uses services.wafrn.package as the source."; + }; + + package = mkOption { + type = types.package; + default = + if defaultWafrnPackage != null then + defaultWafrnPackage + else + throw "services.wafrn.package must be set when no flake-pinned wafrn source is available"; + defaultText = lib.literalExpression ""; + description = "Package containing Wafrn source tree used to build backend/frontend Docker images."; + }; + + caddyConfigDir = mkOption { + type = types.nullOr types.str; + default = null; + example = "/srv/wafrn/packages/caddy"; + description = "Optional host path mounted into frontend container as /etc/caddy/config for Caddy hooks/overrides."; + }; + + images = { + backend = mkOption { + type = types.nullOr types.str; + default = null; + example = "ghcr.io/your-org/wafrn-backend:2026.02.01"; + description = "Prebuilt backend image. Set together with images.frontend to skip local source/Docker builds."; + }; + + frontend = mkOption { + type = types.nullOr types.str; + default = null; + example = "ghcr.io/your-org/wafrn-frontend:2026.02.01"; + description = "Prebuilt frontend image. Set together with images.backend to skip local source/Docker builds."; + }; }; stateDir = mkOption { @@ -352,8 +418,12 @@ in message = "services.wafrn requires virtualisation.docker.enable = true;"; } { - assertion = cfg.source != null || wafrnSrc != null; - message = "services.wafrn.source is null and no flake-pinned wafrn source is available."; + assertion = usingPrebuiltImages || cfg.source != null || cfg.package != null; + message = "Provide services.wafrn.images.backend+frontend, set services.wafrn.source, or set services.wafrn.package."; + } + { + assertion = (cfg.images.backend == null) == (cfg.images.frontend == null); + message = "Set both services.wafrn.images.backend and services.wafrn.images.frontend, or neither."; } { assertion = cfg.httpPort != null || cfg.httpsPort != null; @@ -369,6 +439,7 @@ in "d ${cfg.stateDir}/cache 0750 root root -" "d ${cfg.stateDir}/caddy 0750 root root -" "d ${cfg.stateDir}/frontend 0750 root root -" + ] ++ lib.optionals (!usingPrebuiltImages) [ "d ${cfg.stateDir}/source 0750 root root -" ] ++ lib.optionals (cfg.bluesky.enable && cfg.bluesky.useBundledPds) [ "d ${cfg.stateDir}/pds 0750 root root -" @@ -389,20 +460,38 @@ in script = '' set -euo pipefail - if [ ! -d "${sourcePath}" ]; then - echo "wafrn-nix: source directory does not exist: ${sourcePath}" >&2 - exit 1 - fi + ${optionalString (!usingPrebuiltImages) '' + selected_source="" - rm -rf "${preparedSourcePath}" - mkdir -p "${preparedSourcePath}" - cp -a "${sourcePath}/." "${preparedSourcePath}/" - chmod -R u+w "${preparedSourcePath}" + if [ -n "${sourcePath}" ] && [ -d "${sourcePath}" ]; then + selected_source="${sourcePath}" + elif [ -n "${sourcePath}" ]; then + echo "wafrn-nix: configured source does not exist (${sourcePath}), falling back to services.wafrn.package" >&2 + fi - if [ ! -f "${preparedSourcePath}/package-lock.json" ]; then - echo "wafrn-nix: package-lock.json missing, generating with npm" >&2 - (cd "${preparedSourcePath}" && npm install --package-lock-only --ignore-scripts) - fi + if [ -z "$selected_source" ] && [ -d "${packageSourcePath}" ]; then + selected_source="${packageSourcePath}" + fi + + if [ -z "$selected_source" ]; then + echo "wafrn-nix: no usable source directory found" >&2 + exit 1 + fi + + rm -rf "${preparedSourcePath}" + mkdir -p "${preparedSourcePath}" + cp -a "$selected_source/." "${preparedSourcePath}/" + chmod -R u+w "${preparedSourcePath}" + + if [ ! -e "${preparedSourcePath}/.git" ]; then + mkdir -p "${preparedSourcePath}/.git" + fi + + if [ ! -f "${preparedSourcePath}/package-lock.json" ]; then + echo "wafrn-nix: package-lock.json missing, generating with npm" >&2 + (cd "${preparedSourcePath}" && npm install --package-lock-only --ignore-scripts) + fi + ''} install -m 0600 ${envTemplate} ${serviceEnvFile} ${optionalString (cfg.secretsFile != null) ''