C2: Build authentik from source (Mikado chain) #274
21 changed files with 868 additions and 85 deletions
28
containers/authentik/api-go-vendor-hook.nix
Normal file
28
containers/authentik/api-go-vendor-hook.nix
Normal file
|
|
@ -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 <nixpkgs> { }, 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
|
||||
''
|
||||
)
|
||||
153
containers/authentik/authentik-django.nix
Normal file
153
containers/authentik/authentik-django.nix
Normal file
|
|
@ -0,0 +1,153 @@
|
|||
# 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.
|
||||
#
|
||||
# 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 <nixpkgs> { }
|
||||
, sources ? import ./sources.nix { inherit pkgs; }
|
||||
, webui ? null
|
||||
}:
|
||||
|
||||
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 = "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
|
||||
|
||||
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("${webuiPath}/icons/icon_left_brand.png")' \
|
||||
--replace-fail 'Path("web/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 \
|
||||
--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;
|
||||
}
|
||||
64
containers/authentik/authentik-server.nix
Normal file
64
containers/authentik/authentik-server.nix
Normal file
|
|
@ -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 <nixpkgs> { }
|
||||
, 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
|
||||
'';
|
||||
}
|
||||
47
containers/authentik/client-go.nix
Normal file
47
containers/authentik/client-go.nix
Normal file
|
|
@ -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 <nixpkgs> { }, 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
|
||||
'';
|
||||
}
|
||||
36
containers/authentik/client-ts.nix
Normal file
36
containers/authentik/client-ts.nix
Normal file
|
|
@ -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 <nixpkgs> { }, 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
|
||||
'';
|
||||
}
|
||||
|
|
@ -1,19 +1,41 @@
|
|||
# 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 <nixpkgs> { } }:
|
||||
|
||||
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; };
|
||||
# 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; };
|
||||
|
||||
# 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 +44,9 @@ pkgs.dockerTools.buildLayeredImage {
|
|||
tag = "latest";
|
||||
|
||||
contents = [
|
||||
pkgs.authentik
|
||||
ak
|
||||
authentik-django
|
||||
authentik-server
|
||||
pkgs.bashInteractive
|
||||
pkgs.coreutils
|
||||
pkgs.cacert
|
||||
|
|
@ -30,9 +54,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
|
||||
|
|
|
|||
125
containers/authentik/python-deps.nix
Normal file
125
containers/authentik/python-deps.nix
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
# 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 <nixpkgs> { }, sources ? import ./sources.nix { inherit pkgs; } }:
|
||||
|
||||
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 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 = "sha256-DtpcYQyI07m7v84D/UC28Tj35R9wye6IX+1D0gMZPgY=";
|
||||
|
||||
dontFixup = true;
|
||||
}
|
||||
30
containers/authentik/sources.nix
Normal file
30
containers/authentik/sources.nix
Normal file
|
|
@ -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 <nixpkgs> { } }:
|
||||
|
||||
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;
|
||||
};
|
||||
}
|
||||
44
containers/authentik/test-build.nix
Normal file
44
containers/authentik/test-build.nix
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
# Test harness for building authentik components on ringtail
|
||||
# Uses builtins.getFlake instead of <nixpkgs> (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'
|
||||
# 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 = _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; }
|
||||
];
|
||||
}
|
||||
51
containers/authentik/webui-deps.nix
Normal file
51
containers/authentik/webui-deps.nix
Normal file
|
|
@ -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 <nixpkgs> { }, 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;
|
||||
}
|
||||
80
containers/authentik/webui.nix
Normal file
80
containers/authentik/webui.nix
Normal file
|
|
@ -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 <nixpkgs> { }
|
||||
, 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";
|
||||
}
|
||||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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(<chain>): 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(<chain>): 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.
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
---
|
||||
title: Generate Authentik API Clients
|
||||
modified: 2026-02-28
|
||||
status: active
|
||||
requires:
|
||||
- mirror-authentik-build-deps
|
||||
tags:
|
||||
- how-to
|
||||
- authentik
|
||||
|
|
@ -32,6 +33,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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
---
|
||||
title: Build Authentik Python Backend
|
||||
modified: 2026-02-28
|
||||
status: active
|
||||
modified: 2026-03-01
|
||||
requires:
|
||||
- mirror-authentik-build-deps
|
||||
tags:
|
||||
- how-to
|
||||
- authentik
|
||||
|
|
@ -14,32 +15,60 @@ 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 sync FOD + autoPatchelfHook
|
||||
|
||||
Nix builds are sandboxed with no network access. The pattern is:
|
||||
|
||||
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.
|
||||
|
||||
**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 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 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
|
||||
|
||||
- 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)
|
||||
|
||||
## Lessons Learned
|
||||
|
||||
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 |
|
||||
| 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. 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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,6 @@
|
|||
---
|
||||
title: Build Authentik from Source
|
||||
modified: 2026-02-28
|
||||
status: active
|
||||
branch: mikado/authentik-source-build
|
||||
modified: 2026-03-01
|
||||
requires:
|
||||
- authentik-go-server-derivation
|
||||
- authentik-web-ui-derivation
|
||||
|
|
@ -15,45 +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 2025.12.4+. Building from source lets us target any release.
|
||||
|
||||
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, including 4 in-tree packages and a forked `djangorestframework`
|
||||
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 and aligns with upstream's build process.
|
||||
|
||||
## Source
|
||||
|
||||
Forge mirror: https://forge.ops.eblu.me/mirrors/authentik (upstream: `goauthentik/authentik`)
|
||||
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`)
|
||||
|
||||
Reference derivation: [nixpkgs `pkgs/by-name/au/authentik/package.nix`](https://github.com/NixOS/nixpkgs/tree/master/pkgs/by-name/au/authentik)
|
||||
Version and hashes are centralized in `containers/authentik/sources.nix`.
|
||||
|
||||
## What to Do
|
||||
## Updating to a New Version
|
||||
|
||||
Once all prerequisites are complete:
|
||||
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
|
||||
|
||||
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
|
||||
## Testing
|
||||
|
||||
Nix derivations target `x86_64-linux`. Test incrementally on ringtail:
|
||||
|
||||
```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 assembled --extra-experimental-features 'nix-command flakes'"
|
||||
ssh ringtail "rm -rf $tmpdir"
|
||||
```
|
||||
|
||||
`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
|
||||
|
|
|
|||
40
docs/how-to/authentik/mirror-authentik-build-deps.md
Normal file
40
docs/how-to/authentik/mirror-authentik-build-deps.md
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
---
|
||||
title: Mirror Authentik Build Dependencies
|
||||
modified: 2026-02-28
|
||||
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
|
||||
|
|
@ -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]]
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue