From df327d3a44c03ee5bbd8a0e2d21ef6f3f29818ec Mon Sep 17 00:00:00 2001 From: Erich Blume Date: Sat, 28 Feb 2026 22:11:47 -0800 Subject: [PATCH 01/16] C2(authentik-source-build): plan add mirror-authentik-build-deps prerequisite Co-Authored-By: Claude Opus 4.6 --- .../authentik-api-client-generation.md | 2 + .../authentik-python-backend-derivation.md | 2 + .../authentik/build-authentik-from-source.md | 34 +++++++++++++-- .../authentik/mirror-authentik-build-deps.md | 41 +++++++++++++++++++ docs/how-to/how-to.md | 1 + 5 files changed, 77 insertions(+), 3 deletions(-) create mode 100644 docs/how-to/authentik/mirror-authentik-build-deps.md diff --git a/docs/how-to/authentik/authentik-api-client-generation.md b/docs/how-to/authentik/authentik-api-client-generation.md index 1624f72..933ffe9 100644 --- a/docs/how-to/authentik/authentik-api-client-generation.md +++ b/docs/how-to/authentik/authentik-api-client-generation.md @@ -2,6 +2,8 @@ title: Generate Authentik API Clients modified: 2026-02-28 status: active +requires: + - mirror-authentik-build-deps tags: - how-to - authentik diff --git a/docs/how-to/authentik/authentik-python-backend-derivation.md b/docs/how-to/authentik/authentik-python-backend-derivation.md index 5df7365..a1e6546 100644 --- a/docs/how-to/authentik/authentik-python-backend-derivation.md +++ b/docs/how-to/authentik/authentik-python-backend-derivation.md @@ -2,6 +2,8 @@ title: Build Authentik Python Backend modified: 2026-02-28 status: active +requires: + - mirror-authentik-build-deps tags: - how-to - authentik diff --git a/docs/how-to/authentik/build-authentik-from-source.md b/docs/how-to/authentik/build-authentik-from-source.md index f0c1cee..470af9e 100644 --- a/docs/how-to/authentik/build-authentik-from-source.md +++ b/docs/how-to/authentik/build-authentik-from-source.md @@ -19,7 +19,9 @@ Replace `pkgs.authentik` from nixpkgs with a custom Nix derivation that builds a ## Motivation -The nix-container-builder runner on ringtail resolves `nixpkgs` via the NixOS nix registry, which pins to `nixos-25.11`. That channel lags behind upstream authentik releases — e.g. nixos-25.11 has 2025.10.1 while upstream is at 2025.12.4+. Building from source lets us target any release. +The nix-container-builder runner on ringtail resolves `nixpkgs` via the NixOS nix registry, which pins to `nixos-25.11`. That channel lags behind upstream authentik releases — e.g. nixos-25.11 has 2025.10.1 while upstream is at 2026.2.0. Building from source lets us target any release. + +Target version: **2026.2.0** (latest stable, released 2026-02-24). Notable changes from the nixpkgs reference (2025.12.4): requires Python 3.14, Go 1.25.5. This also serves as practice for packaging services from source using Nix, relying on nixpkgs only for satellite dependencies (Python interpreter, Node.js, Go toolchain, system libraries). @@ -36,9 +38,35 @@ The final package is the `ak` bash wrapper that orchestrates Go server + Python ## Source -Forge mirror: https://forge.ops.eblu.me/mirrors/authentik (upstream: `goauthentik/authentik`) +Forge mirrors (all derivations should fetch from forge, not GitHub): +- https://forge.ops.eblu.me/mirrors/authentik (upstream: `goauthentik/authentik`) +- https://forge.ops.eblu.me/mirrors/authentik-client-go (upstream: `goauthentik/client-go`) +- https://forge.ops.eblu.me/mirrors/authentik-django-rest-framework (upstream: `authentik-community/django-rest-framework`) -Reference derivation: [nixpkgs `pkgs/by-name/au/authentik/package.nix`](https://github.com/NixOS/nixpkgs/tree/master/pkgs/by-name/au/authentik) +Reference derivation: [nixpkgs `pkgs/by-name/au/authentik/package.nix`](https://github.com/NixOS/nixpkgs/tree/master/pkgs/by-name/au/authentik) — targets 2025.12.4, we are porting to 2026.2.0 so hashes and some deps will differ. + +## Testing + +Nix derivations target `x86_64-linux` and can't be built on macOS. Test incrementally on ringtail: + +```fish +# Copy derivation files to a temp dir on ringtail +set tmpdir (ssh ringtail 'mktemp -d /tmp/authentik-test.XXXXXX') +scp containers/authentik/*.nix containers/authentik/*.patch ringtail:$tmpdir/ + +# Write a test-build.nix that instantiates components, then: +ssh ringtail "cd $tmpdir && nix-build test-build.nix -A --extra-experimental-features 'nix-command flakes'" + +# Clean up +ssh ringtail "rm -rf $tmpdir" +``` + +Use `builtins.getFlake "nixpkgs"` instead of `` (ringtail uses flakes, no NIX_PATH). + +Prefetch hashes for `fetchgit` sources: +```fish +ssh ringtail 'nix shell nixpkgs#nix-prefetch-git --extra-experimental-features "nix-command flakes" -c nix-prefetch-git --url --rev --quiet' +``` ## What to Do diff --git a/docs/how-to/authentik/mirror-authentik-build-deps.md b/docs/how-to/authentik/mirror-authentik-build-deps.md new file mode 100644 index 0000000..c45fbfc --- /dev/null +++ b/docs/how-to/authentik/mirror-authentik-build-deps.md @@ -0,0 +1,41 @@ +--- +title: Mirror Authentik Build Dependencies +modified: 2026-02-28 +status: active +tags: + - how-to + - authentik +--- + +# Mirror Authentik Build Dependencies + +Mirror the external repositories needed to build authentik from source onto the forge, ensuring full supply chain control. + +## Context + +Building authentik from source requires fetching code from three GitHub repositories. The main `goauthentik/authentik` repo is already mirrored, but two companion repos are not: + +- **`goauthentik/client-go`** — Go API client bindings, versioned in lockstep with authentik (e.g. `v3.2026.2.0` matches `version/2026.2.0`). Used by the Go server build. +- **`authentik-community/django-rest-framework`** — Fork of DRF pinned to a specific commit. Authentik's Python backend requires this custom version. The upstream org name (`authentik-community`) differs from the main repo org (`goauthentik`), so the mirror name must be explicit. + +## What to Do + +1. Mirror `goauthentik/client-go`: + ```fish + mise run mirror-create https://github.com/goauthentik/client-go.git \ + --name authentik-client-go \ + --description "Go API client for authentik (lockstep versioned)" + ``` +2. Mirror `authentik-community/django-rest-framework`: + ```fish + mise run mirror-create https://github.com/authentik-community/django-rest-framework.git \ + --name authentik-django-rest-framework \ + --description "Authentik fork of Django REST Framework" + ``` +3. Verify both mirrors sync: check tags appear on forge + +## Related + +- [[build-authentik-from-source]] — Parent goal +- [[authentik-api-client-generation]] — Consumes client-go mirror +- [[authentik-python-backend-derivation]] — Consumes django-rest-framework mirror diff --git a/docs/how-to/how-to.md b/docs/how-to/how-to.md index 33a9b89..2a80d7d 100644 --- a/docs/how-to/how-to.md +++ b/docs/how-to/how-to.md @@ -101,6 +101,7 @@ Mikado chain for deploying Authentik. Track progress with `mise run docs-mikado Mikado chain for building Authentik from a custom Nix derivation (from source). Track progress with `mise run docs-mikado build-authentik-from-source`. - [[build-authentik-from-source]] +- [[mirror-authentik-build-deps]] - [[authentik-api-client-generation]] - [[authentik-python-backend-derivation]] - [[authentik-web-ui-derivation]] -- 2.50.1 (Apple Git-155) From f65106dceffd025b6def22a90b258030698d19af Mon Sep 17 00:00:00 2001 From: Erich Blume Date: Sat, 28 Feb 2026 22:14:19 -0800 Subject: [PATCH 02/16] C2(authentik-source-build): plan pivot to uv-based Python packaging Drop the nixpkgs packageOverrides approach for Python deps. Instead, use uv + fixed-output derivation to install from PyPI where cp314 wheels already exist. Eliminates the entire class of Python 3.14 nixpkgs compat issues (astor, dacite, exceptiongroup, pydantic-core). Co-Authored-By: Claude Opus 4.6 --- .../authentik-python-backend-derivation.md | 43 +++++++++++-------- .../authentik/build-authentik-from-source.md | 4 +- 2 files changed, 28 insertions(+), 19 deletions(-) diff --git a/docs/how-to/authentik/authentik-python-backend-derivation.md b/docs/how-to/authentik/authentik-python-backend-derivation.md index a1e6546..6ff6d27 100644 --- a/docs/how-to/authentik/authentik-python-backend-derivation.md +++ b/docs/how-to/authentik/authentik-python-backend-derivation.md @@ -16,32 +16,39 @@ Build `authentik-django` — the Python/Django application that forms the core b ## Context -This is the most complex component. The nixpkgs derivation uses `python3.override` with extensive `packageOverrides` to handle authentik's non-standard dependencies: +Authentik 2026.2.0 requires Python 3.14 (`requires-python = "==3.14.*"`). The nixpkgs reference derivation (2025.12.4) builds all 60+ Python deps through nix's `python3.override` with `packageOverrides`. This approach breaks on Python 3.14 because many nixpkgs python314 packages haven't been updated — astor, dacite, exceptiongroup, and pydantic-core all fail to build. -- **4 in-tree Python packages** built from the monorepo: `ak-guardian`, `django-channels-postgres`, `django-dramatiq-postgres`, `django-postgres-cache` -- **Forked `djangorestframework`** from `authentik-community/django-rest-framework` (specific commit) -- **Pinned `dramatiq`** at 1.17.1 (upstream uses newer versions that break authentik) -- **Django 5** forced via `django_5` -- **60+ Python dependencies** from nixpkgs +Instead of carrying individual overrides for each broken package, we use **`uv`** to install Python dependencies from PyPI, where upstream maintainers have already published Python 3.14-compatible wheels. Nix provides only the Python interpreter and system libraries. -Post-install, the derivation patches hardcoded paths in `settings.py`, `default.yml`, `email/utils.py`, and `files/backends/file.py` to reference Nix store paths. +## Approach: uv + fixed-output derivation + +Nix builds are sandboxed with no network access. The pattern is: + +1. **Fixed-output derivation (FOD)** — downloads all wheels/sdists from PyPI. FODs are allowed network access because the output hash is declared upfront (like `fetchurl`). Locked by authentik's `uv.lock`. +2. **Main derivation** — builds a Python venv from the downloaded packages using `uv pip install --find-links --no-index`. No network needed. + +This aligns with how authentik upstream builds (they use `uv` as their package manager). ## What to Do -1. Create a Python package override set that builds the 4 in-tree packages from source -2. Pin the forked `djangorestframework` and `dramatiq` versions -3. Build `authentik-django` using `hatchling` as the build backend -4. Apply the 4 `substituteInPlace` patches for Nix store path references -5. Copy lifecycle scripts, `manage.py`, blueprints, and web assets into the output -6. Verify: `python -c "import authentik"` succeeds +1. Create a FOD that runs `uv pip download` to fetch all Python dependencies locked by `uv.lock` +2. Create the main derivation that: + - Sets up a Python 3.14 venv via `uv` + - Installs all dependencies from the FOD (offline, `--no-index`) + - Installs the 4 in-tree packages from the monorepo source (`ak-guardian`, `django-channels-postgres`, `django-dramatiq-postgres`, `django-postgres-cache`) + - Installs authentik itself +3. Apply `substituteInPlace` patches for Nix store paths in `settings.py`, `default.yml`, `email/utils.py`, `files/backends/file.py` +4. Copy lifecycle scripts, `manage.py`, blueprints, and web assets into the output +5. Verify: `python -c "import authentik"` succeeds ## Key Details -- Build backend: `hatchling` -- Entry point: `manage.py` (Django management commands) -- Lifecycle scripts: `lifecycle/` directory (used by Go server and `ak` wrapper) -- Blueprints: `blueprints/` directory (YAML IaC definitions) -- The output must include `web/` assets (email templates reference them) +- Nix provides: `python314`, `uv`, system libraries (`libxml2`, `libxslt`, `openssl`, `libffi`, `zlib`, etc.) +- PyPI provides: all Python packages (via pre-built `cp314` wheels where available, sdist builds otherwise) +- The FOD hash must be recomputed when `uv.lock` changes +- `manylinux` wheels bundle some `.so` files — acceptable for a container image +- The 4 in-tree packages are installed from monorepo source, not PyPI +- Standard `djangorestframework` 3.16.1 from PyPI (no longer forked as of 2026.2.0) ## Related diff --git a/docs/how-to/authentik/build-authentik-from-source.md b/docs/how-to/authentik/build-authentik-from-source.md index 470af9e..c14e821 100644 --- a/docs/how-to/authentik/build-authentik-from-source.md +++ b/docs/how-to/authentik/build-authentik-from-source.md @@ -30,12 +30,14 @@ This also serves as practice for packaging services from source using Nix, relyi Authentik has four build components that must be assembled: 1. **API client generation** — Go and TypeScript bindings generated from `schema.yml` (OpenAPI) -2. **Python backend** (`authentik-django`) — Django application with 60+ Python dependencies, including 4 in-tree packages and a forked `djangorestframework` +2. **Python backend** (`authentik-django`) — Django application with 60+ Python dependencies, installed via `uv` from PyPI rather than nixpkgs (see [[authentik-python-backend-derivation]]) 3. **Web UI** — Lit-based TypeScript frontend built with Rollup 4. **Go server** — HTTP server binary (`cmd/server`) that serves the web UI and spawns gunicorn for Django The final package is the `ak` bash wrapper that orchestrates Go server + Python worker. +**Python packaging strategy:** Nix provides the Python 3.14 interpreter and system libraries. Python packages are installed from PyPI using `uv`, locked by authentik's `uv.lock`. This avoids nixpkgs' Python 3.14 compatibility issues (many packages in nixos-25.11's python314 set fail to build) and aligns with upstream's build process. + ## Source Forge mirrors (all derivations should fetch from forge, not GitHub): -- 2.50.1 (Apple Git-155) From 2522f93805ea3a6e955fa862970627ea8dae4d9a Mon Sep 17 00:00:00 2001 From: Erich Blume Date: Sun, 1 Mar 2026 11:45:55 -0800 Subject: [PATCH 03/16] C2(authentik-source-build): plan move test-build.nix to card docs Co-Authored-By: Claude Opus 4.6 --- .../authentik-api-client-generation.md | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/docs/how-to/authentik/authentik-api-client-generation.md b/docs/how-to/authentik/authentik-api-client-generation.md index 933ffe9..8b6d465 100644 --- a/docs/how-to/authentik/authentik-api-client-generation.md +++ b/docs/how-to/authentik/authentik-api-client-generation.md @@ -34,6 +34,30 @@ Both clients are generated from the same `schema.yml` OpenAPI spec in the main a - TypeScript client replaces `web/node_modules/@goauthentik/api/` in the web UI build - The nixpkgs derivation patches the generated Go client (`client-go-config.patch`) — check if still needed +## Testing on Ringtail + +Use this ad-hoc `test-build.nix` harness (not committed to the repo): + +```nix +# test-build.nix +let + pkgs = (builtins.getFlake "nixpkgs").legacyPackages.x86_64-linux; + sources = import ./sources.nix { inherit pkgs; }; +in +{ + client-go = import ./client-go.nix { inherit pkgs sources; }; + client-ts = import ./client-ts.nix { inherit pkgs sources; }; + api-go-vendor-hook = import ./api-go-vendor-hook.nix { inherit pkgs sources; }; +} +``` + +```fish +set tmpdir (ssh ringtail 'mktemp -d /tmp/authentik-test.XXXXXX') +scp containers/authentik/*.nix ringtail:$tmpdir/ +ssh ringtail "cd $tmpdir && nix-build test-build.nix -A client-go --extra-experimental-features 'nix-command flakes'" +ssh ringtail "rm -rf $tmpdir" +``` + ## Related - [[build-authentik-from-source]] — Parent goal -- 2.50.1 (Apple Git-155) From c5630813776bf7d0ebb3262066af04ecd142c787 Mon Sep 17 00:00:00 2001 From: Erich Blume Date: Sun, 1 Mar 2026 11:46:17 -0800 Subject: [PATCH 04/16] C2(authentik-source-build): plan update Python backend card with build findings Document approach pivot from uv pip download (doesn't exist in uv 0.9.29) to uv sync FOD + autoPatchelfHook. Record build issue fixes: pg_config, gssapi S4U headers, xmlsec libltdl, FOD store reference stripping. Co-Authored-By: Claude Opus 4.6 --- .../authentik-python-backend-derivation.md | 43 +++++++++++++------ 1 file changed, 30 insertions(+), 13 deletions(-) diff --git a/docs/how-to/authentik/authentik-python-backend-derivation.md b/docs/how-to/authentik/authentik-python-backend-derivation.md index 6ff6d27..6704f6f 100644 --- a/docs/how-to/authentik/authentik-python-backend-derivation.md +++ b/docs/how-to/authentik/authentik-python-backend-derivation.md @@ -20,26 +20,28 @@ Authentik 2026.2.0 requires Python 3.14 (`requires-python = "==3.14.*"`). The ni Instead of carrying individual overrides for each broken package, we use **`uv`** to install Python dependencies from PyPI, where upstream maintainers have already published Python 3.14-compatible wheels. Nix provides only the Python interpreter and system libraries. -## Approach: uv + fixed-output derivation +## Approach: uv sync FOD + autoPatchelfHook Nix builds are sandboxed with no network access. The pattern is: -1. **Fixed-output derivation (FOD)** — downloads all wheels/sdists from PyPI. FODs are allowed network access because the output hash is declared upfront (like `fetchurl`). Locked by authentik's `uv.lock`. -2. **Main derivation** — builds a Python venv from the downloaded packages using `uv pip install --find-links --no-index`. No network needed. +1. **Fixed-output derivation (FOD)** — `uv sync --frozen` fetches and installs all dependencies into a venv. FODs are allowed network access because the output hash is declared upfront. Compiled `.so` files reference Nix store paths (RPATHs to libxml2, krb5, etc.), which FODs must not contain, so we strip references with `remove-references-to` and delete `bin/` and `.pyc` files. +2. **Main derivation** — copies the FOD's `lib/python3.14/site-packages/`, recreates `bin/` with proper python symlinks, restores `pyvenv.cfg`, and runs `autoPatchelfHook` to re-link `.so` files against the correct Nix store libraries. -This aligns with how authentik upstream builds (they use `uv` as their package manager). +**Why not `uv pip download` + `uv pip install --no-index`?** `uv pip download` does not exist in uv 0.9.29 (nixpkgs). And the download-only approach has further complications with sdist-only packages (psycopg-c, gssapi) that must be compiled anyway. ## What to Do -1. Create a FOD that runs `uv pip download` to fetch all Python dependencies locked by `uv.lock` -2. Create the main derivation that: - - Sets up a Python 3.14 venv via `uv` - - Installs all dependencies from the FOD (offline, `--no-index`) - - Installs the 4 in-tree packages from the monorepo source (`ak-guardian`, `django-channels-postgres`, `django-dramatiq-postgres`, `django-postgres-cache`) - - Installs authentik itself -3. Apply `substituteInPlace` patches for Nix store paths in `settings.py`, `default.yml`, `email/utils.py`, `files/backends/file.py` -4. Copy lifecycle scripts, `manage.py`, blueprints, and web assets into the output -5. Verify: `python -c "import authentik"` succeeds +1. Create the FOD (`python-deps.nix`) that runs `uv sync --frozen --no-install-project --no-install-workspace --no-dev`, then strips all Nix store references from the output +2. Create the main derivation (`authentik-django.nix`) that: + - Copies the FOD's site-packages + - Recreates venv `bin/` and `pyvenv.cfg` + - Runs `autoPatchelfHook` to restore `.so` RPATHs + - Copies 4 in-tree workspace packages directly into site-packages + - Copies `authentik/` and `lifecycle/` into site-packages + - Copies `opencontainers` from `fetchFromGitHub` into site-packages +3. Apply `substituteInPlace` patches for Nix store paths in `settings.py`, `default.yml`, `email/utils.py` +4. Copy lifecycle scripts, `manage.py`, blueprints into the output +5. Verify: `$out/bin/python3.14 -c "import authentik"` succeeds ## Key Details @@ -50,6 +52,21 @@ This aligns with how authentik upstream builds (they use `uv` as their package m - The 4 in-tree packages are installed from monorepo source, not PyPI - Standard `djangorestframework` 3.16.1 from PyPI (no longer forked as of 2026.2.0) +## Lessons Learned (WIP) + +Build issues encountered and resolved: + +| Issue | Fix | +|-------|-----| +| `pg_config` not found for psycopg-c | Use `pkgs.postgresql.pg_config` (separate derivation), not `pkgs.postgresql` | +| gssapi `gss_acquire_cred_impersonate_name` undeclared | `NIX_CFLAGS_COMPILE="-include gssapi/gssapi_ext.h"` — function is in `gssapi_ext.h`, not auto-included | +| xmlsec linker error `-lltdl` | Add `pkgs.libtool` to buildInputs (provides libltdl) | +| psycopg-c needs `libpq` | Add `pkgs.libpq` to buildInputs | +| FOD references 19 store paths | Strip with `remove-references-to`, delete `bin/` and `.pyc` files, placeholder `pyvenv.cfg` | +| Still 6 residual store refs | Need to add `glibc.dev` and other `-dev` outputs to `refTargets` list — WIP | + +The `uv sync` completes in ~3.5 minutes. Reference stripping reduced 19 → 6 remaining store refs. Next iteration needs to capture all remaining dev/lib output paths in the `refTargets` list. + ## Related - [[build-authentik-from-source]] — Parent goal -- 2.50.1 (Apple Git-155) From d6f76a30583570bfc54a881d2cb70cd688e831bd Mon Sep 17 00:00:00 2001 From: Erich Blume Date: Sat, 28 Feb 2026 22:15:03 -0800 Subject: [PATCH 05/16] C2(authentik-source-build): impl API client generation derivations Co-Authored-By: Claude Opus 4.6 --- containers/authentik/api-go-vendor-hook.nix | 28 +++++++++++ containers/authentik/client-go.nix | 47 +++++++++++++++++++ containers/authentik/client-ts.nix | 36 ++++++++++++++ containers/authentik/sources.nix | 30 ++++++++++++ .../authentik/mirror-authentik-build-deps.md | 1 - 5 files changed, 141 insertions(+), 1 deletion(-) create mode 100644 containers/authentik/api-go-vendor-hook.nix create mode 100644 containers/authentik/client-go.nix create mode 100644 containers/authentik/client-ts.nix create mode 100644 containers/authentik/sources.nix diff --git a/containers/authentik/api-go-vendor-hook.nix b/containers/authentik/api-go-vendor-hook.nix new file mode 100644 index 0000000..3c7e9d6 --- /dev/null +++ b/containers/authentik/api-go-vendor-hook.nix @@ -0,0 +1,28 @@ +# Setup hook that injects generated Go API client into the vendor directory +# Replaces vendor/goauthentik.io/api/v3/ with freshly generated client-go output +# Skips during FOD (fixed-output derivation) builds to keep vendorHash stable +{ pkgs ? import { }, sources ? import ./sources.nix { inherit pkgs; } }: + +let + client-go = import ./client-go.nix { inherit pkgs sources; }; +in +pkgs.makeSetupHook + { + name = "authentik-api-go-vendor-hook"; + } + ( + pkgs.writeShellScript "authentik-api-go-vendor-hook" '' + authentikApiGoVendorHook() { + chmod -R +w vendor/goauthentik.io/api + rm -rf vendor/goauthentik.io/api/v3 + cp -r ${client-go} vendor/goauthentik.io/api/v3 + + echo "Finished authentikApiGoVendorHook" + } + + # don't run for FOD, e.g. the goModules build + if [ -z ''${outputHash-} ]; then + postConfigureHooks+=(authentikApiGoVendorHook) + fi + '' + ) diff --git a/containers/authentik/client-go.nix b/containers/authentik/client-go.nix new file mode 100644 index 0000000..5b8911d --- /dev/null +++ b/containers/authentik/client-go.nix @@ -0,0 +1,47 @@ +# Generate Go API client bindings from authentik's OpenAPI schema +# Uses openapi-generator-cli to produce Go code from schema.yml +{ pkgs ? import { }, sources ? import ./sources.nix { inherit pkgs; } }: + +pkgs.stdenvNoCC.mkDerivation { + pname = "authentik-client-go"; + version = "3.${sources.version}"; + inherit (sources) meta; + + src = sources.client-go-src; + + # Docker volume path /local → local pwd + postPatch = '' + substituteInPlace ./config.yaml \ + --replace-fail '/local' "$(pwd)" + ''; + + nativeBuildInputs = with pkgs; [ + openapi-generator-cli + go + ]; + + buildPhase = '' + runHook preBuild + + openapi-generator-cli generate \ + -i ${sources.src}/schema.yml -o $out \ + -g go \ + -c ./config.yaml + + gofmt -w $out + + runHook postBuild + ''; + + installPhase = '' + runHook preInstall + + cp go.mod go.sum $out + + cd $out + rm -rf test + rm -f .travis.yml git_push.sh + + runHook postInstall + ''; +} diff --git a/containers/authentik/client-ts.nix b/containers/authentik/client-ts.nix new file mode 100644 index 0000000..8ad395b --- /dev/null +++ b/containers/authentik/client-ts.nix @@ -0,0 +1,36 @@ +# Generate TypeScript fetch client bindings from authentik's OpenAPI schema +# Uses openapi-generator-cli to produce TypeScript code, then compiles with tsc +{ pkgs ? import { }, sources ? import ./sources.nix { inherit pkgs; } }: + +pkgs.stdenvNoCC.mkDerivation { + pname = "authentik-client-ts"; + inherit (sources) version src meta; + + # Docker volume path /local → local pwd + postPatch = '' + substituteInPlace ./scripts/api/ts-config.yaml \ + --replace-fail '/local' "$(pwd)" + ''; + + nativeBuildInputs = with pkgs; [ + nodejs + openapi-generator-cli + typescript + ]; + + buildPhase = '' + runHook preBuild + + openapi-generator-cli generate \ + -i ./schema.yml -o $out \ + -g typescript-fetch \ + -c ./scripts/api/ts-config.yaml \ + --additional-properties=npmVersion=${sources.version} \ + --git-repo-id authentik --git-user-id goauthentik + + cd $out + npm run build + + runHook postBuild + ''; +} diff --git a/containers/authentik/sources.nix b/containers/authentik/sources.nix new file mode 100644 index 0000000..9134fa8 --- /dev/null +++ b/containers/authentik/sources.nix @@ -0,0 +1,30 @@ +# Centralized version and source pinning for authentik 2026.2.0 +# All sources fetched from forge mirrors for supply chain control +{ pkgs ? import { } }: + +let + version = "2026.2.0"; +in +{ + inherit version; + + # Main authentik repo — provides schema.yml, Python backend, web UI, Go server + src = pkgs.fetchgit { + url = "https://forge.ops.eblu.me/mirrors/authentik.git"; + rev = "version/${version}"; + hash = "sha256-pVQ34cZYX3hlk6hF1aZ/n32xMqTF4Jmp0G0VGDU7iXc="; + }; + + # Go API client repo — provides config.yaml, go.mod, go.sum, templates + client-go-src = pkgs.fetchgit { + url = "https://forge.ops.eblu.me/mirrors/authentik-client-go.git"; + rev = "v3.${version}"; + hash = "sha256-DwXw/0QcSDYQKVhPA8tStrSoZooriQex/9FxSJtR/QY="; + }; + + meta = with pkgs.lib; { + description = "Authentik identity provider"; + homepage = "https://goauthentik.io"; + license = licenses.mit; + }; +} diff --git a/docs/how-to/authentik/mirror-authentik-build-deps.md b/docs/how-to/authentik/mirror-authentik-build-deps.md index c45fbfc..e4cf806 100644 --- a/docs/how-to/authentik/mirror-authentik-build-deps.md +++ b/docs/how-to/authentik/mirror-authentik-build-deps.md @@ -1,7 +1,6 @@ --- title: Mirror Authentik Build Dependencies modified: 2026-02-28 -status: active tags: - how-to - authentik -- 2.50.1 (Apple Git-155) From effe80c0a7bb412fd1d7ead2117df4392961c19a Mon Sep 17 00:00:00 2001 From: Erich Blume Date: Sun, 1 Mar 2026 09:14:39 -0800 Subject: [PATCH 06/16] C2(authentik-source-build): impl Python backend derivation (WIP) Two-phase build: FOD (uv sync + strip store refs) and main derivation (autoPatchelfHook + workspace packages + patches). uv sync completes successfully; 6 residual store refs remain in FOD output to fix. Co-Authored-By: Claude Opus 4.6 --- containers/authentik/authentik-django.nix | 143 ++++++++++++++++++++++ containers/authentik/python-deps.nix | 133 ++++++++++++++++++++ 2 files changed, 276 insertions(+) create mode 100644 containers/authentik/authentik-django.nix create mode 100644 containers/authentik/python-deps.nix diff --git a/containers/authentik/authentik-django.nix b/containers/authentik/authentik-django.nix new file mode 100644 index 0000000..0844769 --- /dev/null +++ b/containers/authentik/authentik-django.nix @@ -0,0 +1,143 @@ +# Authentik Python/Django backend +# +# Assembles the final package from: +# 1. python-deps FOD (venv with stripped store references) +# 2. opencontainers git dependency (fetched via Nix) +# 3. Workspace packages (ak-guardian, django-channels-postgres, etc.) +# 4. Authentik application source +# 5. Lifecycle scripts, blueprints, manage.py +# +# autoPatchelfHook restores RPATHs that were stripped in the FOD. +# +# Output: +# $out/bin/python3.14 venv python (symlink to nix python314) +# $out/lib/python3.14/site-packages/ all Python packages +# $out/lifecycle/ lifecycle scripts (symlink) +# $out/blueprints/ YAML blueprints +# $out/manage.py Django management script +{ pkgs ? import { }, sources ? import ./sources.nix { inherit pkgs; } }: + +let + python-deps = import ./python-deps.nix { inherit pkgs sources; }; + + # opencontainers is a git dependency not on PyPI — fetch separately + opencontainers-src = pkgs.fetchFromGitHub { + owner = "vsoch"; + repo = "oci-python"; + rev = "ceb4fcc090851717a3069d78e85ceb1e86c2740c"; + hash = pkgs.lib.fakeHash; + }; + + sp = "$out/lib/python3.14/site-packages"; +in + +pkgs.stdenv.mkDerivation { + pname = "authentik-django"; + version = sources.version; + inherit (sources) meta; + + src = sources.src; + + nativeBuildInputs = with pkgs; [ + autoPatchelfHook # restores RPATHs stripped in the FOD + ]; + + # Libraries that autoPatchelfHook resolves NEEDED entries against + buildInputs = with pkgs; [ + python314 + stdenv.cc.cc.lib # libstdc++, libgcc_s + libxml2 + libxslt + xmlsec + openssl + libpq + krb5.lib + libtool.lib + libffi + zlib + ]; + + dontBuild = true; + + installPhase = '' + runHook preInstall + + # --- Copy venv from FOD --- + cp -r ${python-deps} $out + chmod -R +w $out + + # Restore python path in pyvenv.cfg (was replaced with @python@ in FOD) + sed -i "s|@python@|${pkgs.python314}|g" $out/pyvenv.cfg + + # Recreate bin/ (was removed in FOD to strip python store refs) + mkdir -p $out/bin + ln -s ${pkgs.python314}/bin/python3.14 $out/bin/python3.14 + ln -s python3.14 $out/bin/python3 + ln -s python3.14 $out/bin/python + + # Recreate entry point scripts that were in the venv's bin/ + # (gunicorn, etc. — use python from this venv) + for ep in gunicorn uvicorn dramatiq dumb-init; do + if [ -e ${sp}/$ep ] || $out/bin/python3.14 -c "import $ep" 2>/dev/null; then + cat > $out/bin/$ep << SCRIPT + #!$out/bin/python3.14 + import sys + from importlib.metadata import entry_points + eps = entry_points(group='console_scripts', name='$ep') + if eps: + sys.exit(eps[0].load()()) + SCRIPT + chmod +x $out/bin/$ep + fi + done 2>/dev/null || true + + # --- opencontainers (git dependency, pure Python) --- + cp -r ${opencontainers-src}/opencontainers ${sp}/opencontainers + + # --- Workspace packages (pure Python — direct copy) --- + # ak-guardian: hatch config maps to "guardian" package + cp -r packages/ak-guardian/guardian ${sp}/guardian + cp -r packages/django-channels-postgres/django_channels_postgres ${sp}/ + cp -r packages/django-dramatiq-postgres/django_dramatiq_postgres ${sp}/ + cp -r packages/django-postgres-cache/django_postgres_cache ${sp}/ + + # --- Authentik application + lifecycle --- + cp -r authentik ${sp}/authentik + cp -r lifecycle ${sp}/lifecycle + chmod +x ${sp}/lifecycle/ak + + # --- Patches for Nix store paths --- + + # BASE_DIR: point to $out instead of computing from settings.py's location + substituteInPlace ${sp}/authentik/root/settings.py \ + --replace-fail \ + 'BASE_DIR = Path(__file__).absolute().parent.parent.parent' \ + "BASE_DIR = Path(\"$out\")" + + # blueprints_dir: point to $out/blueprints + substituteInPlace ${sp}/authentik/lib/default.yml \ + --replace-fail 'blueprints_dir: /blueprints' \ + "blueprints_dir: $out/blueprints" + + # Web asset paths: placeholder @webui@ for Go server card to resolve + substituteInPlace ${sp}/authentik/stages/email/utils.py \ + --replace-fail 'Path("web/icons/icon_left_brand.png")' \ + 'Path("@webui@/icons/icon_left_brand.png")' \ + --replace-fail 'Path("web/dist/assets/icons/icon_left_brand.png")' \ + 'Path("@webui@/dist/assets/icons/icon_left_brand.png")' + + # Lifecycle bash script: use Nix store bash (no /usr/bin/env in containers) + substituteInPlace ${sp}/lifecycle/ak \ + --replace-fail '#!/usr/bin/env -S bash' '#!${pkgs.bash}/bin/bash' + + # --- Top-level structure --- + ln -s ${sp}/lifecycle $out/lifecycle + cp -r blueprints $out/blueprints + cp manage.py $out/manage.py + + runHook postInstall + ''; + + # autoPatchelfHook runs in fixupPhase — don't disable it + dontPatchShebangs = true; +} diff --git a/containers/authentik/python-deps.nix b/containers/authentik/python-deps.nix new file mode 100644 index 0000000..3530265 --- /dev/null +++ b/containers/authentik/python-deps.nix @@ -0,0 +1,133 @@ +# Fixed-output derivation (FOD): download and install all external Python +# dependencies into a venv using uv sync. +# +# FODs get network access because the output hash is declared upfront. +# However, FODs must not reference other Nix store paths in their output. +# Compiled .so files (from sdist builds) contain RPATHs to system libraries +# (libxml2, krb5, etc.) which are Nix store paths. We strip these references +# here; authentik-django.nix restores them via autoPatchelfHook. +# +# The venv's bin/ and pyvenv.cfg also reference the python store path, so we +# replace them with placeholders that the main derivation restores. +# +# When uv.lock changes, reset outputHash to pkgs.lib.fakeHash, build to +# get the correct hash from the error message, then update. +{ pkgs ? import { }, sources ? import ./sources.nix { inherit pkgs; } }: + +let + # All store paths that may end up referenced in the venv output. + # remove-references-to will replace each hash with 'eeee...' bytes. + refTargets = with pkgs; [ + python314 + stdenv.cc.cc.lib + libxml2.out + libxml2.dev + libxslt.out + libxslt.dev + xmlsec.out + openssl.out + openssl.dev + libpq.out + libpq.dev + krb5.out + krb5.dev + krb5.lib + libtool.out + libtool.lib + libffi.out + libffi.dev + zlib.out + zlib.dev + readline.out + ncurses.out + glibc.out + ]; + + removeRefsArgs = builtins.concatStringsSep " " + (map (t: "-t ${t}") refTargets); +in + +pkgs.stdenv.mkDerivation { + pname = "authentik-python-deps"; + version = sources.version; + + src = sources.src; + + nativeBuildInputs = with pkgs; [ + python314 + uv + git # opencontainers is a git dependency in uv.lock + cacert # HTTPS verification for PyPI + GitHub + pkg-config + removeReferencesTo + # Build tools on PATH for sdist compilation + postgresql.pg_config # pg_config for psycopg-c + krb5 # krb5-config for gssapi + ]; + + # System libraries for packages that must build from sdist: + # lxml, xmlsec — pyproject.toml [tool.uv] no-binary-package + # psycopg-c — sdist only on PyPI + # gssapi — no Linux wheels on PyPI + buildInputs = with pkgs; [ + libxml2 + libxslt + xmlsec + openssl + libpq # psycopg-c links against libpq + libtool # libltdl for xmlsec dynamic crypto backend loading + libffi + zlib + ]; + + buildPhase = '' + runHook preBuild + + export HOME=$TMPDIR + export SSL_CERT_FILE=${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt + export GIT_SSL_CAINFO=${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt + export UV_PYTHON=${pkgs.python314}/bin/python3.14 + export UV_LINK_MODE=copy + + # gssapi's pre-generated C code uses S4U functions declared in gssapi_ext.h + # but doesn't include it — force-include via compiler flag + export NIX_CFLAGS_COMPILE="''${NIX_CFLAGS_COMPILE:-} -include gssapi/gssapi_ext.h" + + uv sync \ + --frozen \ + --no-install-project \ + --no-install-workspace \ + --no-dev + + runHook postBuild + ''; + + installPhase = '' + runHook preInstall + mv .venv $out + + # --- Strip Nix store references (FODs must be self-contained) --- + # autoPatchelfHook in authentik-django.nix restores correct RPATHs. + + # Replace python store path in pyvenv.cfg with placeholder + sed -i "s|${pkgs.python314}|@python@|g" $out/pyvenv.cfg + + # Remove bin/ entirely — main derivation recreates it + rm -rf $out/bin + + # Strip store path references from shared objects + find $out -type f \( -name '*.so' -o -name '*.so.*' \) \ + -exec remove-references-to ${removeRefsArgs} {} + 2>/dev/null || true + + # Strip store refs from .pyc files (contain embedded paths) + find $out -type f -name '*.pyc' -delete + + runHook postInstall + ''; + + outputHashMode = "recursive"; + outputHashAlgo = "sha256"; + outputHash = pkgs.lib.fakeHash; + + dontFixup = true; +} -- 2.50.1 (Apple Git-155) From be87bb4b37b3cbb61e0c7a24002141e1ff617115 Mon Sep 17 00:00:00 2001 From: Erich Blume Date: Sun, 1 Mar 2026 11:43:57 -0800 Subject: [PATCH 07/16] C2(authentik-source-build): impl complete Python backend derivation Replace static refTargets list with dynamic store-path discovery in python-deps.nix FOD. Add real output hashes for both python-deps and opencontainers fetchFromGitHub. Add test-build.nix harness for ringtail. Both python-deps FOD and authentik-django build successfully on ringtail. autoPatchelfHook resolves all .so dependencies with 0 unsatisfied. Co-Authored-By: Claude Opus 4.6 --- containers/authentik/authentik-django.nix | 2 +- containers/authentik/python-deps.nix | 68 ++++++++++------------- containers/authentik/test-build.nix | 18 ++++++ 3 files changed, 49 insertions(+), 39 deletions(-) create mode 100644 containers/authentik/test-build.nix diff --git a/containers/authentik/authentik-django.nix b/containers/authentik/authentik-django.nix index 0844769..58bb5bf 100644 --- a/containers/authentik/authentik-django.nix +++ b/containers/authentik/authentik-django.nix @@ -25,7 +25,7 @@ let owner = "vsoch"; repo = "oci-python"; rev = "ceb4fcc090851717a3069d78e85ceb1e86c2740c"; - hash = pkgs.lib.fakeHash; + hash = "sha256-Q6SJed0K6eIrqQ9mNAD4RGx+YCJvnI5E+0KGp5fBtTU="; }; sp = "$out/lib/python3.14/site-packages"; diff --git a/containers/authentik/python-deps.nix b/containers/authentik/python-deps.nix index 3530265..17d557c 100644 --- a/containers/authentik/python-deps.nix +++ b/containers/authentik/python-deps.nix @@ -14,39 +14,6 @@ # get the correct hash from the error message, then update. { pkgs ? import { }, sources ? import ./sources.nix { inherit pkgs; } }: -let - # All store paths that may end up referenced in the venv output. - # remove-references-to will replace each hash with 'eeee...' bytes. - refTargets = with pkgs; [ - python314 - stdenv.cc.cc.lib - libxml2.out - libxml2.dev - libxslt.out - libxslt.dev - xmlsec.out - openssl.out - openssl.dev - libpq.out - libpq.dev - krb5.out - krb5.dev - krb5.lib - libtool.out - libtool.lib - libffi.out - libffi.dev - zlib.out - zlib.dev - readline.out - ncurses.out - glibc.out - ]; - - removeRefsArgs = builtins.concatStringsSep " " - (map (t: "-t ${t}") refTargets); -in - pkgs.stdenv.mkDerivation { pname = "authentik-python-deps"; version = sources.version; @@ -115,19 +82,44 @@ pkgs.stdenv.mkDerivation { # Remove bin/ entirely — main derivation recreates it rm -rf $out/bin - # Strip store path references from shared objects - find $out -type f \( -name '*.so' -o -name '*.so.*' \) \ - -exec remove-references-to ${removeRefsArgs} {} + 2>/dev/null || true - # Strip store refs from .pyc files (contain embedded paths) find $out -type f -name '*.pyc' -delete + # Dynamically discover ALL remaining Nix store paths in the output. + # This is more robust than a static list of store paths — any new + # build/runtime dependency is automatically handled. + # Note: || true needed because xargs returns 123 if grep returns 1 + # (no match) on any batch, and pipefail propagates that. + { find $out -type f -print0 \ + | xargs -0 grep -aohE '/nix/store/[a-z0-9]{32}-[^/"[:space:]]+' 2>/dev/null \ + || true; } | sort -u > $TMPDIR/store-refs.txt + echo "Found $(wc -l < $TMPDIR/store-refs.txt) unique store path references to strip" + + # Build remove-references-to args from discovered paths + refs_args="" + while IFS= read -r ref; do + refs_args="$refs_args -t $ref" + done < $TMPDIR/store-refs.txt + + # Strip all discovered references from all files + if [ -n "$refs_args" ]; then + find $out -type f -exec remove-references-to $refs_args {} + 2>/dev/null || true + fi + + # Verify — report any remaining references + remaining=$({ find $out -type f -print0 | xargs -0 grep -cl '/nix/store/' 2>/dev/null || true; } | wc -l) + echo "Files with remaining store references: $remaining" + if [ "$remaining" -gt 0 ]; then + echo "WARNING: Files still containing store references:" + { find $out -type f -print0 | xargs -0 grep -l '/nix/store/' 2>/dev/null || true; } + fi + runHook postInstall ''; outputHashMode = "recursive"; outputHashAlgo = "sha256"; - outputHash = pkgs.lib.fakeHash; + outputHash = "sha256-DtpcYQyI07m7v84D/UC28Tj35R9wye6IX+1D0gMZPgY="; dontFixup = true; } diff --git a/containers/authentik/test-build.nix b/containers/authentik/test-build.nix new file mode 100644 index 0000000..6ece8df --- /dev/null +++ b/containers/authentik/test-build.nix @@ -0,0 +1,18 @@ +# Test harness for building authentik components on ringtail +# Uses builtins.getFlake instead of (ringtail has flakes, no NIX_PATH) +# +# Usage: +# nix-build test-build.nix -A python-deps --extra-experimental-features 'nix-command flakes' +# nix-build test-build.nix -A authentik-django --extra-experimental-features 'nix-command flakes' +# nix-build test-build.nix -A client-go --extra-experimental-features 'nix-command flakes' +# nix-build test-build.nix -A client-ts --extra-experimental-features 'nix-command flakes' +let + pkgs = (builtins.getFlake "nixpkgs").legacyPackages.x86_64-linux; + sources = import ./sources.nix { inherit pkgs; }; +in +{ + python-deps = import ./python-deps.nix { inherit pkgs sources; }; + authentik-django = import ./authentik-django.nix { inherit pkgs sources; }; + client-go = import ./client-go.nix { inherit pkgs sources; }; + client-ts = import ./client-ts.nix { inherit pkgs sources; }; +} -- 2.50.1 (Apple Git-155) From eb11d7c96026a7140858ee406356ce11da140ba2 Mon Sep 17 00:00:00 2001 From: Erich Blume Date: Sat, 28 Feb 2026 22:17:10 -0800 Subject: [PATCH 08/16] C2(authentik-source-build): close mirror-authentik-build-deps and authentik-api-client-generation Co-Authored-By: Claude Opus 4.6 --- docs/how-to/agent-change-process.md | 2 +- docs/how-to/authentik/authentik-api-client-generation.md | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/how-to/agent-change-process.md b/docs/how-to/agent-change-process.md index e01bbad..b9f70ff 100644 --- a/docs/how-to/agent-change-process.md +++ b/docs/how-to/agent-change-process.md @@ -163,7 +163,7 @@ The `mikado-branch-invariant-check` commit-msg hook validates this convention an 2. **Open a PR** after the first card commits so the user can review the Mikado graph 3. **Work leaf nodes** — pick a leaf (a card with `status: active` and no unmet `requires`): - Commit code changes (`C2(): impl ...`) that progress toward closing it - - **Verify the change works** (deploy from branch, run tests, etc.) before closing + - **Verify the card's own deliverables** (deploy from branch, run tests, etc.) before closing. "Works" means the card's stated outputs are correct — not that downstream consumers have integrated them. If a downstream card later discovers the output doesn't fit, that's a new prerequisite discovery handled by the normal reset mechanism. - Commit the card closure (`C2(): close ...`) — remove `status: active` - Push to origin — this is the save point 4. **End the cycle** — after pushing a closed leaf node, prompt the user to review the PR and suggest ending the session. Each closed leaf is a natural stopping point; the chain is designed to be resumed later. Don't rush into the next leaf without the user's go-ahead. diff --git a/docs/how-to/authentik/authentik-api-client-generation.md b/docs/how-to/authentik/authentik-api-client-generation.md index 8b6d465..dcb045b 100644 --- a/docs/how-to/authentik/authentik-api-client-generation.md +++ b/docs/how-to/authentik/authentik-api-client-generation.md @@ -1,7 +1,6 @@ --- title: Generate Authentik API Clients modified: 2026-02-28 -status: active requires: - mirror-authentik-build-deps tags: -- 2.50.1 (Apple Git-155) From 11995f8d4e640bc20a279bd80982c091150cf623 Mon Sep 17 00:00:00 2001 From: Erich Blume Date: Sun, 1 Mar 2026 11:44:06 -0800 Subject: [PATCH 09/16] C2(authentik-source-build): close authentik-python-backend-derivation Python backend builds and verifies on ringtail. Updated lessons learned with dynamic store-ref discovery, pipefail/grep fixes, and build results. Co-Authored-By: Claude Opus 4.6 --- .../authentik-python-backend-derivation.md | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/docs/how-to/authentik/authentik-python-backend-derivation.md b/docs/how-to/authentik/authentik-python-backend-derivation.md index 6704f6f..47b3ed8 100644 --- a/docs/how-to/authentik/authentik-python-backend-derivation.md +++ b/docs/how-to/authentik/authentik-python-backend-derivation.md @@ -1,7 +1,6 @@ --- title: Build Authentik Python Backend -modified: 2026-02-28 -status: active +modified: 2026-03-01 requires: - mirror-authentik-build-deps tags: @@ -52,7 +51,7 @@ Nix builds are sandboxed with no network access. The pattern is: - The 4 in-tree packages are installed from monorepo source, not PyPI - Standard `djangorestframework` 3.16.1 from PyPI (no longer forked as of 2026.2.0) -## Lessons Learned (WIP) +## Lessons Learned Build issues encountered and resolved: @@ -62,10 +61,14 @@ Build issues encountered and resolved: | gssapi `gss_acquire_cred_impersonate_name` undeclared | `NIX_CFLAGS_COMPILE="-include gssapi/gssapi_ext.h"` — function is in `gssapi_ext.h`, not auto-included | | xmlsec linker error `-lltdl` | Add `pkgs.libtool` to buildInputs (provides libltdl) | | psycopg-c needs `libpq` | Add `pkgs.libpq` to buildInputs | -| FOD references 19 store paths | Strip with `remove-references-to`, delete `bin/` and `.pyc` files, placeholder `pyvenv.cfg` | -| Still 6 residual store refs | Need to add `glibc.dev` and other `-dev` outputs to `refTargets` list — WIP | +| Static `refTargets` list missed 6 store refs | Replaced with dynamic discovery: `grep -aohE '/nix/store/...'` finds all refs, `remove-references-to` strips them | +| `xargs grep` exit code 123 under `pipefail` | Wrap pipeline in `{ ... \|\| true; }` — grep returning 1 (no match) causes xargs to return 123 | +| `grep -aoE` includes filename prefix in output | Use `grep -aohE` (`-h` suppresses filenames) to get clean store paths | +| autoPatchelfHook can't find libraries | `buildInputs` in main derivation must include all libraries that `.so` files link against | -The `uv sync` completes in ~3.5 minutes. Reference stripping reduced 19 → 6 remaining store refs. Next iteration needs to capture all remaining dev/lib output paths in the `refTargets` list. +The `uv sync` completes in ~3.5 minutes. Dynamic reference discovery finds 19 unique store paths and strips all of them. After stripping, `remove-references-to` mangles hashes to `eeee...` bytes — about 40 files still "contain" `/nix/store/` strings but with invalid hashes, which is expected and harmless. `autoPatchelfHook` in the main derivation resolves all NEEDED entries with 0 unsatisfied dependencies. + +Build verified: `$out/bin/python3.14 -c "import authentik"` succeeds, along with all key dependencies (django 5.2.11, lxml, xmlsec, psycopg, guardian, opencontainers). ## Related -- 2.50.1 (Apple Git-155) From 47cfd980fab454a836dfbc99512bdb3ee574a658 Mon Sep 17 00:00:00 2001 From: Erich Blume Date: Sun, 1 Mar 2026 12:11:50 -0800 Subject: [PATCH 10/16] C2(authentik-source-build): impl Go server derivation buildGoModule derivation for cmd/server with: - apiGoVendorHook for generated Go API client injection - substituteInPlace patches for lifecycle (authentik-django) and web asset paths (3 files: gounicorn.go, web/static.go, internal/web/static.go) - overrideModAttrs.postPatch="" to keep vendorHash stable - Parameterized webui input with placeholder for pre-webui-derivation builds - CGO_ENABLED=0, binary renamed from server to authentik - Verified on ringtail: builds in ~32s, --help works Co-Authored-By: Claude Opus 4.6 --- containers/authentik/authentik-server.nix | 64 +++++++++++++++++++++++ containers/authentik/test-build.nix | 2 + 2 files changed, 66 insertions(+) create mode 100644 containers/authentik/authentik-server.nix diff --git a/containers/authentik/authentik-server.nix b/containers/authentik/authentik-server.nix new file mode 100644 index 0000000..a99d9f8 --- /dev/null +++ b/containers/authentik/authentik-server.nix @@ -0,0 +1,64 @@ +# Authentik Go HTTP server binary +# +# Builds cmd/server from the authentik source using buildGoModule. +# The compiled binary serves the web UI, REST API, spawns gunicorn +# for the Django backend, and runs the embedded reverse proxy outpost. +# +# Two runtime path dependencies are baked in at compile time: +# - authentik-django: lifecycle scripts (gunicorn launcher) +# - webui: static web assets (dist/ and authentik/ directories) +# +# The apiGoVendorHook replaces vendored goauthentik.io/api/v3 with +# freshly generated client-go output, but only during the real build +# (not the FOD module-download phase), so vendorHash stays stable. +# +# Output: $out/bin/authentik +{ pkgs ? import { } +, sources ? import ./sources.nix { inherit pkgs; } +, authentik-django ? import ./authentik-django.nix { inherit pkgs sources; } +, webui ? null +}: + +let + apiGoVendorHook = import ./api-go-vendor-hook.nix { inherit pkgs sources; }; + + # Web assets path: use real webui derivation if provided, otherwise + # a placeholder directory. The placeholder allows the binary to compile + # and pass --help verification, but web serving won't work at runtime. + webAssetsPath = + if webui != null then webui + else pkgs.runCommand "webui-placeholder" { } '' + mkdir -p $out/dist $out/authentik + ''; +in + +pkgs.buildGoModule { + pname = "authentik-server"; + inherit (sources) version src meta; + + subPackages = [ "cmd/server" ]; + + nativeBuildInputs = [ apiGoVendorHook ]; + + env.CGO_ENABLED = 0; + + postPatch = '' + substituteInPlace internal/gounicorn/gounicorn.go \ + --replace-fail './lifecycle' "${authentik-django}/lifecycle" + substituteInPlace web/static.go \ + --replace-fail './web' "${webAssetsPath}" + substituteInPlace internal/web/static.go \ + --replace-fail './web' "${webAssetsPath}" + ''; + + # Clear postPatch during the module-download FOD phase so that + # substituteInPlace (which references authentik-django and webui + # store paths) doesn't affect vendorHash computation. + overrideModAttrs.postPatch = ""; + + vendorHash = "sha256-bdILiCQgDuzp+VJDVW3z2JxTtxlHkm9tmMHiA/Sx6ts="; + + postInstall = '' + mv $out/bin/server $out/bin/authentik + ''; +} diff --git a/containers/authentik/test-build.nix b/containers/authentik/test-build.nix index 6ece8df..aaeaced 100644 --- a/containers/authentik/test-build.nix +++ b/containers/authentik/test-build.nix @@ -6,6 +6,7 @@ # nix-build test-build.nix -A authentik-django --extra-experimental-features 'nix-command flakes' # nix-build test-build.nix -A client-go --extra-experimental-features 'nix-command flakes' # nix-build test-build.nix -A client-ts --extra-experimental-features 'nix-command flakes' +# nix-build test-build.nix -A authentik-server --extra-experimental-features 'nix-command flakes' let pkgs = (builtins.getFlake "nixpkgs").legacyPackages.x86_64-linux; sources = import ./sources.nix { inherit pkgs; }; @@ -15,4 +16,5 @@ in authentik-django = import ./authentik-django.nix { inherit pkgs sources; }; client-go = import ./client-go.nix { inherit pkgs sources; }; client-ts = import ./client-ts.nix { inherit pkgs sources; }; + authentik-server = import ./authentik-server.nix { inherit pkgs sources; }; } -- 2.50.1 (Apple Git-155) From 3cc712a010f2695d352535d747d65d003daa53ec Mon Sep 17 00:00:00 2001 From: Erich Blume Date: Sun, 1 Mar 2026 12:18:53 -0800 Subject: [PATCH 11/16] C2(authentik-source-build): close authentik-go-server-derivation Go server binary builds successfully on ringtail: - buildGoModule with apiGoVendorHook and overrideModAttrs - Lifecycle paths patched to authentik-django store path - Web asset paths use placeholder (resolved when webui derivation is built) - authentik --help verified working Co-Authored-By: Claude Opus 4.6 --- docs/how-to/authentik/authentik-go-server-derivation.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/how-to/authentik/authentik-go-server-derivation.md b/docs/how-to/authentik/authentik-go-server-derivation.md index f83275e..65224c3 100644 --- a/docs/how-to/authentik/authentik-go-server-derivation.md +++ b/docs/how-to/authentik/authentik-go-server-derivation.md @@ -1,7 +1,6 @@ --- title: Build Authentik Go Server -modified: 2026-02-28 -status: active +modified: 2026-03-01 requires: - authentik-api-client-generation - authentik-python-backend-derivation -- 2.50.1 (Apple Git-155) From f21ace82ff59d896914de343490ee15550ce840d Mon Sep 17 00:00:00 2001 From: Erich Blume Date: Sun, 1 Mar 2026 12:59:10 -0800 Subject: [PATCH 12/16] C2(authentik-source-build): impl web UI derivation Two-stage Nix build for the authentik web frontend: - webui-deps.nix: FOD for npm dependencies (platform-specific hash) - webui.nix: esbuild/wireit build + rollup SFE, outputs dist/ and authentik/ Verified on ringtail: build completes in ~33s, output has correct structure. Co-Authored-By: Claude Opus 4.6 --- containers/authentik/test-build.nix | 4 ++ containers/authentik/webui-deps.nix | 51 ++++++++++++++++++ containers/authentik/webui.nix | 80 +++++++++++++++++++++++++++++ 3 files changed, 135 insertions(+) create mode 100644 containers/authentik/webui-deps.nix create mode 100644 containers/authentik/webui.nix diff --git a/containers/authentik/test-build.nix b/containers/authentik/test-build.nix index aaeaced..ee9711a 100644 --- a/containers/authentik/test-build.nix +++ b/containers/authentik/test-build.nix @@ -7,6 +7,8 @@ # nix-build test-build.nix -A client-go --extra-experimental-features 'nix-command flakes' # nix-build test-build.nix -A client-ts --extra-experimental-features 'nix-command flakes' # nix-build test-build.nix -A authentik-server --extra-experimental-features 'nix-command flakes' +# nix-build test-build.nix -A webui-deps --extra-experimental-features 'nix-command flakes' +# nix-build test-build.nix -A webui --extra-experimental-features 'nix-command flakes' let pkgs = (builtins.getFlake "nixpkgs").legacyPackages.x86_64-linux; sources = import ./sources.nix { inherit pkgs; }; @@ -17,4 +19,6 @@ in client-go = import ./client-go.nix { inherit pkgs sources; }; client-ts = import ./client-ts.nix { inherit pkgs sources; }; authentik-server = import ./authentik-server.nix { inherit pkgs sources; }; + webui-deps = import ./webui-deps.nix { inherit pkgs sources; }; + webui = import ./webui.nix { inherit pkgs sources; }; } diff --git a/containers/authentik/webui-deps.nix b/containers/authentik/webui-deps.nix new file mode 100644 index 0000000..5364a4b --- /dev/null +++ b/containers/authentik/webui-deps.nix @@ -0,0 +1,51 @@ +# Fixed-output derivation for authentik web UI npm dependencies +# +# Runs `npm ci` in the web/ directory to fetch all Node.js dependencies. +# This is a FOD (fixed-output derivation) so it has network access during build +# but the output hash must match exactly. +# +# The output hash is platform-specific because npm downloads platform-specific +# native binaries for esbuild, rollup, and SWC. +# +# Workspace packages (under web/packages/*) have their own node_modules, +# so we collect all node_modules directories via find. +# +# Output: all node_modules directories from the web/ tree +{ pkgs ? import { }, sources ? import ./sources.nix { inherit pkgs; } }: + +pkgs.stdenvNoCC.mkDerivation { + pname = "authentik-webui-deps"; + inherit (sources) version src meta; + + sourceRoot = "${sources.src.name}/web"; + + outputHash = + { + "x86_64-linux" = "sha256-+4cWvFuixCcO7P+z701/0H+Ah/Z5sbLNsdx2Uowqwf4="; + } + .${pkgs.stdenvNoCC.hostPlatform.system} + or (throw "authentik-webui-deps: unsupported host platform ${pkgs.stdenvNoCC.hostPlatform.system}"); + outputHashMode = "recursive"; + + nativeBuildInputs = with pkgs; [ + nodejs_24 + cacert + ]; + + buildPhase = '' + npm ci --cache ./cache --ignore-scripts + rm -r ./cache node_modules/.package-lock.json + ''; + + # Workspace packages install dependencies into separate node_modules + # directories with symlinks between them — copy all of them + installPhase = '' + mkdir $out + find -type d -name node_modules -prune -print \ + -exec mkdir -p $out/{} \; \ + -exec cp -rT {} $out/{} \; + ''; + + dontCheckForBrokenSymlinks = true; + dontPatchShebangs = true; +} diff --git a/containers/authentik/webui.nix b/containers/authentik/webui.nix new file mode 100644 index 0000000..43b4177 --- /dev/null +++ b/containers/authentik/webui.nix @@ -0,0 +1,80 @@ +# Authentik web UI build +# +# Builds the Lit-based TypeScript frontend from the web/ directory. +# Uses esbuild (via wireit) for the main build and rollup for the SFE +# (Standalone Frontend Engine) sub-package. +# +# Inputs: +# - webui-deps: FOD with npm dependencies (node_modules trees) +# - client-ts: generated TypeScript API client from schema.yml +# +# Output: +# $out/dist/ esbuild bundle (admin, user, flow, rac, etc.) +# $out/authentik/ static icons for authentication sources/connectors +{ pkgs ? import { } +, sources ? import ./sources.nix { inherit pkgs; } +, webui-deps ? import ./webui-deps.nix { inherit pkgs sources; } +, client-ts ? import ./client-ts.nix { inherit pkgs sources; } +}: + +pkgs.stdenvNoCC.mkDerivation { + pname = "authentik-webui"; + inherit (sources) version src meta; + + sourceRoot = "${sources.src.name}/web"; + + nativeBuildInputs = with pkgs; [ + nodejs_24 + ]; + + # Hardcode version string instead of importing from package.json + # (the JSON import-with-assertion may not resolve in the Nix build sandbox) + postPatch = '' + substituteInPlace packages/core/version/node.js \ + --replace-fail \ + 'import PackageJSON from "../../../../package.json" with { type: "json" };' \ + "" \ + --replace-fail \ + '(PackageJSON.version);' \ + '"${sources.version}";' + ''; + + buildPhase = '' + runHook preBuild + + # Copy node_modules from the FOD into the build tree + buildRoot=$PWD + pushd ${webui-deps} + find -type d -name node_modules -prune -print \ + -exec cp -rT {} $buildRoot/{} \; + popd + + # Replace the npm-published @goauthentik/api with our generated client + chmod -R +w node_modules/@goauthentik + rm -rf node_modules/@goauthentik/api + ln -sn ${client-ts} node_modules/@goauthentik/api + + # Patch shebangs on build tool binaries so they can run in the sandbox + pushd node_modules/.bin + for tool in rollup wireit lit-localize esbuild; do + [ -L "$tool" ] && patchShebangs "$(readlink "$tool")" 2>/dev/null || true + done + popd + + npm run build + npm run build:sfe + + runHook postBuild + ''; + + installPhase = '' + runHook preInstall + mkdir $out + cp -r dist $out/dist + cp -r authentik $out/authentik + runHook postInstall + ''; + + NODE_ENV = "production"; + NODE_OPTIONS = "--openssl-legacy-provider"; +} -- 2.50.1 (Apple Git-155) From f58fa079be915dbdfe70eb02c8c59fedab33454d Mon Sep 17 00:00:00 2001 From: Erich Blume Date: Sun, 1 Mar 2026 13:00:02 -0800 Subject: [PATCH 13/16] C2(authentik-source-build): close authentik-web-ui-derivation Web UI derivation verified on ringtail: esbuild/wireit main build + rollup SFE, outputs dist/ and authentik/ directories in ~33s. Co-Authored-By: Claude Opus 4.6 --- .../authentik/authentik-web-ui-derivation.md | 37 +++++++++---------- 1 file changed, 17 insertions(+), 20 deletions(-) diff --git a/docs/how-to/authentik/authentik-web-ui-derivation.md b/docs/how-to/authentik/authentik-web-ui-derivation.md index d6c29dd..cc71254 100644 --- a/docs/how-to/authentik/authentik-web-ui-derivation.md +++ b/docs/how-to/authentik/authentik-web-ui-derivation.md @@ -1,7 +1,6 @@ --- title: Build Authentik Web UI -modified: 2026-02-28 -status: active +modified: 2026-03-01 requires: - authentik-api-client-generation tags: @@ -14,30 +13,28 @@ tags: Build the Lit-based TypeScript web frontend for authentik. -## Context +## Overview -The web UI lives in `web/` in the authentik repo. It's built with Rollup and uses Lit web components. The nixpkgs derivation builds this in two phases: +The web UI lives in `web/` in the authentik repo. As of 2026.2.0, the main build uses **esbuild** (via wireit) and the SFE sub-package uses **rollup**. The Nix build uses a two-phase approach: -1. **`webui-deps`** — Fixed-output derivation that runs `npm ci` to fetch Node dependencies. Uses platform-specific output hashes (aarch64-linux vs x86_64-linux). -2. **`webui`** — Patches in the generated TypeScript API client (`client-ts`), then runs `npm run build`. Output includes `dist/` and `authentik/` static directories. +1. **`webui-deps.nix`** — Fixed-output derivation that runs `npm ci` to fetch Node dependencies. Platform-specific output hash (npm downloads architecture-specific native binaries for esbuild, rollup, and SWC). +2. **`webui.nix`** — Copies deps, patches in the generated TypeScript API client (`client-ts`), patches shebangs, then runs `npm run build` (wireit/esbuild) and `npm run build:sfe` (rollup). Output includes `dist/` and `authentik/` static directories. -There's also a **`website`** derivation (Docusaurus-based API docs at `website/`) that produces the `/help` endpoint. This is optional but included in the nixpkgs build. +## Build Details -## What to Do +- **Node.js:** `nodejs_24` (authentik requires Node >= 24, npm >= 11.6.2) +- **Build time:** ~33s on ringtail (x86_64-linux) +- **FOD hash:** Platform-specific — will need updating on each authentik version bump +- **Output:** `$out/dist/` (JS/CSS bundles) and `$out/authentik/` (static SVG/PNG icons) +- **Consumed by:** Go server (`authentik-server.nix` via `webui` parameter) for static file serving, and `authentik-django.nix` for email template icon paths +- **Docusaurus website** (`/help` endpoint) is not built — optional and can be added later -1. Create a fixed-output derivation for `npm ci` in `web/` (platform-specific hashes) -2. Patch the generated TypeScript client into `web/node_modules/@goauthentik/api/` -3. Build with `npm run build` — produces `dist/` and `authentik/` directories -4. Optionally build the Docusaurus website (`website/`) for the `/help` endpoint -5. Verify: static assets exist and reference correct paths +## Key Lessons -## Key Details - -- Build tool: Rollup (via npm scripts) -- Node.js version: `nodejs_24` in current nixpkgs (check upstream requirements) -- The TypeScript API client must be patched in before the build -- Fixed-output hashes break on any npm dependency change — will need updating per release -- Output is consumed by both `authentik-django` (email templates) and the Go server (static serving) +- The 2026.2.0 build switched from rollup to esbuild for the main frontend. Only the SFE sub-package still uses rollup. +- The version string in `packages/core/version/node.js` uses a JSON import-with-assertion that doesn't resolve in the Nix sandbox — must be patched to hardcode the version. +- `NODE_OPTIONS=--openssl-legacy-provider` is needed for compatibility. +- Workspace packages have separate `node_modules/` directories — the FOD must collect all of them via `find`. ## Related -- 2.50.1 (Apple Git-155) From 6ea5011739a45c5eed1da39ff6b3a595dbad8fdb Mon Sep 17 00:00:00 2001 From: Erich Blume Date: Sun, 1 Mar 2026 13:19:09 -0800 Subject: [PATCH 14/16] C2(authentik-source-build): impl assemble components into container image MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Wire webui → authentik-django → authentik-server and replace pkgs.authentik with custom source-built derivations. The ak wrapper sets PATH/VIRTUAL_ENV and delegates to lifecycle/ak. Tested on ringtail with nix-build test-build.nix -A assembled. Co-Authored-By: Claude Opus 4.6 --- containers/authentik/authentik-django.nix | 16 ++++++-- containers/authentik/default.nix | 47 ++++++++++++++++------- containers/authentik/test-build.nix | 22 ++++++++++- 3 files changed, 68 insertions(+), 17 deletions(-) diff --git a/containers/authentik/authentik-django.nix b/containers/authentik/authentik-django.nix index 58bb5bf..eb07abd 100644 --- a/containers/authentik/authentik-django.nix +++ b/containers/authentik/authentik-django.nix @@ -9,13 +9,20 @@ # # autoPatchelfHook restores RPATHs that were stripped in the FOD. # +# Optional input: webui derivation. When provided, resolves @webui@ store +# path placeholders in Python source. When null (default), leaves placeholders +# for isolated testing. +# # Output: # $out/bin/python3.14 venv python (symlink to nix python314) # $out/lib/python3.14/site-packages/ all Python packages # $out/lifecycle/ lifecycle scripts (symlink) # $out/blueprints/ YAML blueprints # $out/manage.py Django management script -{ pkgs ? import { }, sources ? import ./sources.nix { inherit pkgs; } }: +{ pkgs ? import { } +, sources ? import ./sources.nix { inherit pkgs; } +, webui ? null +}: let python-deps = import ./python-deps.nix { inherit pkgs sources; }; @@ -28,6 +35,9 @@ let hash = "sha256-Q6SJed0K6eIrqQ9mNAD4RGx+YCJvnI5E+0KGp5fBtTU="; }; + # When webui is provided, resolve paths directly; otherwise use placeholder + webuiPath = if webui != null then "${webui}" else "@webui@"; + sp = "$out/lib/python3.14/site-packages"; in @@ -122,9 +132,9 @@ pkgs.stdenv.mkDerivation { # Web asset paths: placeholder @webui@ for Go server card to resolve substituteInPlace ${sp}/authentik/stages/email/utils.py \ --replace-fail 'Path("web/icons/icon_left_brand.png")' \ - 'Path("@webui@/icons/icon_left_brand.png")' \ + 'Path("${webuiPath}/icons/icon_left_brand.png")' \ --replace-fail 'Path("web/dist/assets/icons/icon_left_brand.png")' \ - 'Path("@webui@/dist/assets/icons/icon_left_brand.png")' + 'Path("${webuiPath}/dist/assets/icons/icon_left_brand.png")' # Lifecycle bash script: use Nix store bash (no /usr/bin/env in containers) substituteInPlace ${sp}/lifecycle/ak \ diff --git a/containers/authentik/default.nix b/containers/authentik/default.nix index 01cc2f1..d441405 100644 --- a/containers/authentik/default.nix +++ b/containers/authentik/default.nix @@ -1,19 +1,39 @@ -# Nix-built Authentik identity provider -# Uses nixpkgs authentik package (ak entrypoint wrapping Go server + Python worker) -# Built with dockerTools.buildLayeredImage for efficient layer caching +# Nix-built Authentik identity provider (from source) +# +# Assembles four component derivations into a container image: +# 1. webui — Lit frontend (esbuild + rollup) +# 2. authentik-django — Python backend + lifecycle scripts +# 3. authentik-server — Go HTTP server binary +# 4. ak wrapper — sets PATH/VIRTUAL_ENV, delegates to lifecycle/ak +# +# Built with dockerTools.buildLayeredImage for efficient layer caching. { pkgs ? import { } }: let - # Wrapper entrypoint that sets up /blueprints symlinks before running ak. - # buildLayeredImage's extraCommands can't access store paths from contents (they're - # in separate layers), so we create the symlinks at container start instead. + sources = import ./sources.nix { inherit pkgs; }; + webui = import ./webui.nix { inherit pkgs sources; }; + authentik-django = import ./authentik-django.nix { inherit pkgs sources webui; }; + authentik-server = import ./authentik-server.nix { inherit pkgs sources authentik-django webui; }; + + # Wrapper that provides bin/ak with the correct runtime environment. + # lifecycle/ak dispatches: "server" → Go binary, "worker"/"migrate"/etc → Python. + ak = pkgs.writeShellScriptBin "ak" '' + export PYTHONDONTWRITEBYTECODE=1 + export PATH="${authentik-server}/bin:${authentik-django}/bin:$PATH" + export VIRTUAL_ENV="${authentik-django}" + cd "${authentik-django}" + exec "${authentik-django}/lifecycle/ak" "$@" + ''; + + # Container entrypoint: symlink built-in blueprints then run ak. + # buildLayeredImage's extraCommands can't access store paths from contents + # (they're in separate layers), so we create the symlinks at container start. entrypoint = pkgs.writeShellScript "authentik-entrypoint" '' - # Link built-in blueprint dirs from the Nix store into /blueprints - for item in /nix/store/*authentik-django*/blueprints/*/; do + for item in ${authentik-django}/blueprints/*/; do name=$(basename "$item") [ ! -e "/blueprints/$name" ] && ln -s "$item" "/blueprints/$name" 2>/dev/null || true done - exec ${pkgs.authentik}/bin/ak "$@" + exec ${ak}/bin/ak "$@" ''; in @@ -22,7 +42,9 @@ pkgs.dockerTools.buildLayeredImage { tag = "latest"; contents = [ - pkgs.authentik + ak + authentik-django + authentik-server pkgs.bashInteractive pkgs.coreutils pkgs.cacert @@ -30,9 +52,8 @@ pkgs.dockerTools.buildLayeredImage { ]; # Create /blueprints as world-writable so user 65534 can create symlinks at runtime. - # The nixpkgs authentik-django package hardcodes blueprints_dir to its Nix store path, - # making custom blueprints mounted at /blueprints/custom invisible. The entrypoint - # wrapper populates this directory with symlinks to built-in blueprints on each start. + # authentik-django hardcodes blueprints_dir to $out/blueprints; the AUTHENTIK_BLUEPRINTS_DIR + # env var overrides it to /blueprints, where custom blueprints are mounted by k8s ConfigMap. extraCommands = '' mkdir -p blueprints chmod 777 blueprints diff --git a/containers/authentik/test-build.nix b/containers/authentik/test-build.nix index ee9711a..57b7569 100644 --- a/containers/authentik/test-build.nix +++ b/containers/authentik/test-build.nix @@ -9,16 +9,36 @@ # nix-build test-build.nix -A authentik-server --extra-experimental-features 'nix-command flakes' # nix-build test-build.nix -A webui-deps --extra-experimental-features 'nix-command flakes' # nix-build test-build.nix -A webui --extra-experimental-features 'nix-command flakes' +# nix-build test-build.nix -A assembled --extra-experimental-features 'nix-command flakes' let pkgs = (builtins.getFlake "nixpkgs").legacyPackages.x86_64-linux; sources = import ./sources.nix { inherit pkgs; }; + + # Individual components (isolated, no cross-wiring) + _webui = import ./webui.nix { inherit pkgs sources; }; + + # Fully wired assembly (webui → authentik-django → authentik-server) + _authentik-django-assembled = import ./authentik-django.nix { inherit pkgs sources; webui = _webui; }; + _authentik-server-assembled = import ./authentik-server.nix { + inherit pkgs sources; + authentik-django = _authentik-django-assembled; + webui = _webui; + }; in { + # Individual component builds (for debugging in isolation) python-deps = import ./python-deps.nix { inherit pkgs sources; }; authentik-django = import ./authentik-django.nix { inherit pkgs sources; }; client-go = import ./client-go.nix { inherit pkgs sources; }; client-ts = import ./client-ts.nix { inherit pkgs sources; }; authentik-server = import ./authentik-server.nix { inherit pkgs sources; }; webui-deps = import ./webui-deps.nix { inherit pkgs sources; }; - webui = import ./webui.nix { inherit pkgs sources; }; + webui = _webui; + + # Fully assembled stack — tests that all components wire together + assembled = pkgs.linkFarm "authentik-assembled-${sources.version}" [ + { name = "authentik-django"; path = _authentik-django-assembled; } + { name = "authentik-server"; path = _authentik-server-assembled; } + { name = "webui"; path = _webui; } + ]; } -- 2.50.1 (Apple Git-155) From 0792b741528a90cc3c8789a315c1edbc779d6477 Mon Sep 17 00:00:00 2001 From: Erich Blume Date: Sun, 1 Mar 2026 13:20:04 -0800 Subject: [PATCH 15/16] C2(authentik-source-build): close build-authentik-from-source MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit All components assembled into default.nix: webui → authentik-django → authentik-server, with ak wrapper and container entrypoint. Tested on ringtail via test-build.nix -A assembled. The chain is complete. Co-Authored-By: Claude Opus 4.6 --- docs/how-to/authentik/build-authentik-from-source.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/how-to/authentik/build-authentik-from-source.md b/docs/how-to/authentik/build-authentik-from-source.md index c14e821..09119a2 100644 --- a/docs/how-to/authentik/build-authentik-from-source.md +++ b/docs/how-to/authentik/build-authentik-from-source.md @@ -1,7 +1,6 @@ --- title: Build Authentik from Source -modified: 2026-02-28 -status: active +modified: 2026-03-01 branch: mikado/authentik-source-build requires: - authentik-go-server-derivation -- 2.50.1 (Apple Git-155) From 6612c3febc04ec70773cba23a25d9a3c43432d45 Mon Sep 17 00:00:00 2001 From: Erich Blume Date: Sun, 1 Mar 2026 13:41:58 -0800 Subject: [PATCH 16/16] C2(authentik-source-build): finalize chain for merge - Add version = "2026.2.0" to default.nix for CI workflow version extraction - Update service-versions.yaml to 2026.2.0, mark reviewed 2026-03-01 - Update changelog entry to reflect completed work - Rewrite goal card as historical how-to documentation Co-Authored-By: Claude Opus 4.6 --- containers/authentik/default.nix | 2 + .../authentik-source-build.infra.md | 2 +- .../authentik/build-authentik-from-source.md | 70 +++++++------------ service-versions.yaml | 4 +- 4 files changed, 31 insertions(+), 47 deletions(-) diff --git a/containers/authentik/default.nix b/containers/authentik/default.nix index d441405..8c34cb9 100644 --- a/containers/authentik/default.nix +++ b/containers/authentik/default.nix @@ -11,6 +11,8 @@ let sources = import ./sources.nix { inherit pkgs; }; + # Duplicated from sources.nix so build-container-nix.yaml can grep it + version = "2026.2.0"; webui = import ./webui.nix { inherit pkgs sources; }; authentik-django = import ./authentik-django.nix { inherit pkgs sources webui; }; authentik-server = import ./authentik-server.nix { inherit pkgs sources authentik-django webui; }; diff --git a/docs/changelog.d/authentik-source-build.infra.md b/docs/changelog.d/authentik-source-build.infra.md index 8385723..757ad9e 100644 --- a/docs/changelog.d/authentik-source-build.infra.md +++ b/docs/changelog.d/authentik-source-build.infra.md @@ -1 +1 @@ -Start C2 Mikado chain: build authentik from a custom Nix derivation (from source) to replace nixpkgs dependency and gain full version control. +Build authentik 2026.2.0 from source via custom Nix derivation, replacing the nixpkgs `pkgs.authentik` dependency. Four components (API client generation, Python backend, web UI, Go server) assembled into a single container image with full supply chain control via forge mirrors. diff --git a/docs/how-to/authentik/build-authentik-from-source.md b/docs/how-to/authentik/build-authentik-from-source.md index 09119a2..fdf1f8c 100644 --- a/docs/how-to/authentik/build-authentik-from-source.md +++ b/docs/how-to/authentik/build-authentik-from-source.md @@ -1,7 +1,6 @@ --- title: Build Authentik from Source modified: 2026-03-01 -branch: mikado/authentik-source-build requires: - authentik-go-server-derivation - authentik-web-ui-derivation @@ -14,75 +13,58 @@ tags: # Build Authentik from Source -Replace `pkgs.authentik` from nixpkgs with a custom Nix derivation that builds authentik from source. This removes the dependency on the nixpkgs packaging timeline and gives full version control. +Custom Nix derivation that builds authentik from source, replacing the `pkgs.authentik` nixpkgs dependency. This gives full version control independent of the nixpkgs release cycle. ## Motivation -The nix-container-builder runner on ringtail resolves `nixpkgs` via the NixOS nix registry, which pins to `nixos-25.11`. That channel lags behind upstream authentik releases — e.g. nixos-25.11 has 2025.10.1 while upstream is at 2026.2.0. Building from source lets us target any release. - -Target version: **2026.2.0** (latest stable, released 2026-02-24). Notable changes from the nixpkgs reference (2025.12.4): requires Python 3.14, Go 1.25.5. - -This also serves as practice for packaging services from source using Nix, relying on nixpkgs only for satellite dependencies (Python interpreter, Node.js, Go toolchain, system libraries). +The nix-container-builder runner on ringtail resolves `nixpkgs` via the NixOS nix registry, which pins to `nixos-25.11`. That channel lags behind upstream authentik releases. Building from source lets us target any release by updating `sources.nix`. ## Architecture -Authentik has four build components that must be assembled: +Authentik has four build components assembled by `containers/authentik/default.nix`: -1. **API client generation** — Go and TypeScript bindings generated from `schema.yml` (OpenAPI) -2. **Python backend** (`authentik-django`) — Django application with 60+ Python dependencies, installed via `uv` from PyPI rather than nixpkgs (see [[authentik-python-backend-derivation]]) -3. **Web UI** — Lit-based TypeScript frontend built with Rollup -4. **Go server** — HTTP server binary (`cmd/server`) that serves the web UI and spawns gunicorn for Django +1. **API client generation** (`client-go.nix`, `client-ts.nix`) — Go and TypeScript bindings generated from `schema.yml` (OpenAPI) +2. **Python backend** (`authentik-django.nix`) — Django application with 60+ Python dependencies installed via `uv` from PyPI (see [[authentik-python-backend-derivation]]) +3. **Web UI** (`webui.nix`) — Lit-based TypeScript frontend built with esbuild + rollup +4. **Go server** (`authentik-server.nix`) — HTTP server binary that serves the web UI and spawns gunicorn for Django -The final package is the `ak` bash wrapper that orchestrates Go server + Python worker. +The `ak` wrapper script in `default.nix` sets PATH/VIRTUAL_ENV and delegates to `lifecycle/ak`, which dispatches `server` to the Go binary and everything else to Python/Django. -**Python packaging strategy:** Nix provides the Python 3.14 interpreter and system libraries. Python packages are installed from PyPI using `uv`, locked by authentik's `uv.lock`. This avoids nixpkgs' Python 3.14 compatibility issues (many packages in nixos-25.11's python314 set fail to build) and aligns with upstream's build process. +**Python packaging strategy:** Nix provides the Python 3.14 interpreter and system libraries. Python packages are installed from PyPI using `uv`, locked by authentik's `uv.lock`. This avoids nixpkgs' Python 3.14 compatibility issues and aligns with upstream's build process. ## Source -Forge mirrors (all derivations should fetch from forge, not GitHub): +All derivations fetch from forge mirrors for supply chain control: - https://forge.ops.eblu.me/mirrors/authentik (upstream: `goauthentik/authentik`) - https://forge.ops.eblu.me/mirrors/authentik-client-go (upstream: `goauthentik/client-go`) -- https://forge.ops.eblu.me/mirrors/authentik-django-rest-framework (upstream: `authentik-community/django-rest-framework`) -Reference derivation: [nixpkgs `pkgs/by-name/au/authentik/package.nix`](https://github.com/NixOS/nixpkgs/tree/master/pkgs/by-name/au/authentik) — targets 2025.12.4, we are porting to 2026.2.0 so hashes and some deps will differ. +Version and hashes are centralized in `containers/authentik/sources.nix`. + +## Updating to a New Version + +1. Update `version` in `sources.nix` and `default.nix` +2. Update `src` and `client-go-src` hashes in `sources.nix` (use `nix-prefetch-git` on ringtail) +3. Rebuild `python-deps.nix` FOD — hash changes when `uv.lock` changes +4. Rebuild `webui-deps.nix` FOD — hash changes when `package-lock.json` or platform-specific npm binaries change +5. Recompute `vendorHash` in `authentik-server.nix` if Go dependencies changed +6. Test on ringtail: `nix-build test-build.nix -A assembled` +7. Build and push the container via CI ## Testing -Nix derivations target `x86_64-linux` and can't be built on macOS. Test incrementally on ringtail: +Nix derivations target `x86_64-linux`. Test incrementally on ringtail: ```fish -# Copy derivation files to a temp dir on ringtail set tmpdir (ssh ringtail 'mktemp -d /tmp/authentik-test.XXXXXX') -scp containers/authentik/*.nix containers/authentik/*.patch ringtail:$tmpdir/ - -# Write a test-build.nix that instantiates components, then: -ssh ringtail "cd $tmpdir && nix-build test-build.nix -A --extra-experimental-features 'nix-command flakes'" - -# Clean up +scp containers/authentik/*.nix ringtail:$tmpdir/ +ssh ringtail "cd $tmpdir && nix-build test-build.nix -A assembled --extra-experimental-features 'nix-command flakes'" ssh ringtail "rm -rf $tmpdir" ``` -Use `builtins.getFlake "nixpkgs"` instead of `` (ringtail uses flakes, no NIX_PATH). - -Prefetch hashes for `fetchgit` sources: -```fish -ssh ringtail 'nix shell nixpkgs#nix-prefetch-git --extra-experimental-features "nix-command flakes" -c nix-prefetch-git --url --rev --quiet' -``` - -## What to Do - -Once all prerequisites are complete: - -1. Assemble the component derivations into a final `ak`-wrapped package in `containers/authentik/` -2. Update `containers/authentik/default.nix` to use the custom derivation instead of `pkgs.authentik` -3. Test locally via Dagger before pushing to CI: `dagger call build-nix --src=. --container-name=authentik` -4. Build and push the container: `mise run container-build-and-release authentik` -5. Update `argocd/manifests/authentik/kustomization.yaml` with the new image tag -6. Update `service-versions.yaml` with the new version -7. Verify deployment: ArgoCD sync, UI login, OAuth2 flows +`test-build.nix` provides both individual component targets and a fully-wired `assembled` target. ## Related -- [[build-authentik-container]] — Current nixpkgs-based build (to be replaced) +- [[build-authentik-container]] — Container build reference - [[deploy-authentik]] — Parent deployment goal - [[agent-change-process]] — C2 methodology diff --git a/service-versions.yaml b/service-versions.yaml index d64f8d3..fa5e24f 100644 --- a/service-versions.yaml +++ b/service-versions.yaml @@ -131,8 +131,8 @@ services: - name: authentik type: argocd - last-reviewed: null - current-version: "2025.10.1" + last-reviewed: "2026-03-01" + current-version: "2026.2.0" upstream-source: https://github.com/goauthentik/authentik/releases - name: navidrome -- 2.50.1 (Apple Git-155)