diff --git a/containers/ntfy/default.nix b/containers/ntfy/default.nix index 47a43ab..b3435da 100644 --- a/containers/ntfy/default.nix +++ b/containers/ntfy/default.nix @@ -1,20 +1,80 @@ # Nix-built ntfy push notification server -# Replaces the multi-stage Dockerfile (Node + Go + Alpine) with nixpkgs ntfy-sh +# Builds v2.17.0 from forge mirror (nixpkgs has 2.15.0) # Built with dockerTools.buildLayeredImage for efficient layer caching { pkgs ? import { } }: +let + version = "2.17.0"; + + src = pkgs.fetchgit { + url = "https://forge.ops.eblu.me/eblume/ntfy.git"; + rev = "v${version}"; + hash = "sha256-/dxILAkye1HwYcybnx1WrMRK2jXZMrxal2ZKm6y2bWc="; + }; + + ui = pkgs.buildNpmPackage { + inherit src version; + pname = "ntfy-sh-ui"; + npmDepsHash = "sha256-d73rymqCKalsjAwHSJshEovmUHJStfGt8wcZYN49sHY="; + + prePatch = '' + cd web/ + ''; + + installPhase = '' + runHook preInstall + mv build/index.html build/app.html + rm build/config.js + mkdir -p $out + mv build/ $out/site + runHook postInstall + ''; + }; + + ntfy = pkgs.buildGoModule { + inherit src version; + pname = "ntfy-sh"; + vendorHash = "sha256-/mQ+UwBYz78mPVVwYgsSYatE00ce2AKXJdx+nl6oT8E="; + + doCheck = false; + + ldflags = [ + "-s" + "-w" + "-X main.version=${version}" + ]; + + postPatch = '' + sed -i 's# /bin/echo# echo#' Makefile + ''; + + # Copy pre-built web UI; skip docs (create placeholder for go:embed) + preBuild = '' + cp -r ${ui}/site/ server/ + mkdir -p server/docs && touch server/docs/placeholder + ''; + + meta = with pkgs.lib; { + description = "Send push notifications to your phone or desktop via PUT/POST"; + homepage = "https://ntfy.sh"; + license = licenses.asl20; + mainProgram = "ntfy"; + }; + }; +in + pkgs.dockerTools.buildLayeredImage { name = "blumeops/ntfy"; tag = "latest"; contents = [ - pkgs.ntfy-sh + ntfy pkgs.cacert pkgs.tzdata ]; config = { - Entrypoint = [ "${pkgs.ntfy-sh}/bin/ntfy" ]; + Entrypoint = [ "${ntfy}/bin/ntfy" ]; Env = [ "SSL_CERT_FILE=${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt" "TZDIR=${pkgs.tzdata}/share/zoneinfo" diff --git a/docs/how-to/zot/fix-ntfy-nix-version.md b/docs/how-to/zot/fix-ntfy-nix-version.md index 7f1afd1..4d444aa 100644 --- a/docs/how-to/zot/fix-ntfy-nix-version.md +++ b/docs/how-to/zot/fix-ntfy-nix-version.md @@ -1,7 +1,6 @@ --- title: Fix ntfy Nix Version modified: 2026-02-20 -status: active tags: - how-to - containers @@ -17,40 +16,24 @@ Override the nixpkgs ntfy-sh derivation to build v2.17.0 from the forge mirror, Discovered during [[add-container-version-sync-check]]: the ntfy container has both a Dockerfile and a `default.nix`. The Dockerfile builds v2.17.0 from `forge.ops.eblu.me/eblume/ntfy.git`, but the nix derivation uses `pkgs.ntfy-sh` from nixpkgs which is pinned at 2.15.0. The version sync check currently excludes ntfy from nix version validation as a workaround. -## What to Do +## What Was Done -Override the nixpkgs `ntfy-sh` derivation in `containers/ntfy/default.nix` to build from the forge mirror at the v2.17.0 tag. The nixpkgs derivation uses `buildGoModule` with a nested `buildNpmPackage` for the web UI. +Replaced the nixpkgs `pkgs.ntfy-sh` reference in `containers/ntfy/default.nix` with a custom derivation that builds v2.17.0 from the forge mirror using `fetchgit`, `buildNpmPackage` (web UI), and `buildGoModule` (server). Docs are skipped (placeholder for `go:embed`, matching the Dockerfile approach). -### Hashes to Update - -| Hash | What it covers | When to update | -|------|---------------|----------------| -| `src.hash` | Source tarball integrity | Always (new source) | -| `vendorHash` | Go module dependencies | If `go.mod`/`go.sum` changed between 2.15.0 and 2.17.0 | -| `npmDepsHash` | npm dependencies | If `web/package-lock.json` changed | - -Use `lib.fakeHash` for each, attempt a build, and nix will report the expected hash. - -### Steps - -1. In `containers/ntfy/default.nix`, override `pkgs.ntfy-sh` with `fetchgit` pointing to `https://forge.ops.eblu.me/eblume/ntfy.git` at the v2.17.0 tag -2. Update all three hashes via iterative builds -3. Build and test with `dagger call build-nix --src=. --container-name=ntfy` -4. Re-enable ntfy in `NIX_PACKAGE_MAP` in `mise-tasks/container-version-check` -5. Verify `mise run container-version-check --all-files` passes +The `container-version-check` script was updated to extract versions from local nix files via regex (`version = "X.Y.Z"`) before falling back to the Dagger `nix-version` function for unmodified nixpkgs packages. This avoids the issue where `nix eval nixpkgs#ntfy-sh.version` returns the upstream 2.15.0 instead of our overridden 2.17.0. ## Key Files | File | Change | |------|--------| -| `containers/ntfy/default.nix` | Override ntfy-sh derivation to build from forge | -| `mise-tasks/container-version-check` | Re-add ntfy to `NIX_PACKAGE_MAP` | +| `containers/ntfy/default.nix` | Custom derivation building v2.17.0 from forge | +| `mise-tasks/container-version-check` | Regex-based local nix version extraction | ## Verification -- [ ] `dagger call build-nix --src=. --container-name=ntfy` produces a working image -- [ ] `dagger call nix-version --package=ntfy-sh` returns 2.17.0 (or the overridden version is extractable) -- [ ] `mise run container-version-check --all-files` passes with ntfy included +- [x] `dagger call build-nix --src=. --container-name=ntfy` produces a working image +- [x] Version extractable from local `default.nix` via regex (2.17.0) +- [x] `mise run container-version-check --all-files` passes with ntfy included ## Related diff --git a/mise-tasks/container-version-check b/mise-tasks/container-version-check index 4d46469..29be3ab 100755 --- a/mise-tasks/container-version-check +++ b/mise-tasks/container-version-check @@ -45,14 +45,16 @@ CONTAINER_TO_SERVICE = { "kiwix-serve": "kiwix", } -# Container dir name → nixpkgs package name for dagger nix-version -# ntfy excluded until nix derivation is updated to build v2.17.0 from forge -# See: docs/how-to/zot/fix-ntfy-nix-version.md +# Container dir name → nixpkgs package name for dagger nix-version. +# Used for containers that use an unmodified nixpkgs package (version matches upstream). +# Containers with local overrides (e.g. ntfy) declare version in default.nix +# and are detected automatically via NIX_VERSION_PATTERN. NIX_PACKAGE_MAP = { "authentik": "authentik", } VERSION_ARG_PATTERN = re.compile(r"^ARG\s+CONTAINER_APP_VERSION=(\S+)", re.MULTILINE) +NIX_VERSION_PATTERN = re.compile(r'^\s*version\s*=\s*"([^"]+)"\s*;', re.MULTILINE) app = typer.Typer() console = Console() @@ -91,8 +93,14 @@ def changed_containers() -> set[str] | None: return names -def get_nix_version(container_name: str) -> str | None: - """Extract nix package version via dagger. Returns None if unavailable.""" +def get_nix_version(container_name: str, nix_file: Path) -> str | None: + """Extract nix package version. Tries local nix file first, then dagger.""" + # Try extracting version declared directly in the nix file (local overrides) + match = NIX_VERSION_PATTERN.search(nix_file.read_text()) + if match: + return match.group(1) + + # Fall back to dagger for unmodified nixpkgs packages pkg = NIX_PACKAGE_MAP.get(container_name) if pkg is None: return None @@ -169,7 +177,7 @@ def main( # Rule 2: nix derivation must produce a version if has_nix: - nix_ver = get_nix_version(name) + nix_ver = get_nix_version(name, nix_file) if nix_ver is not None: versions["nix"] = nix_ver elif name in NIX_PACKAGE_MAP: