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 <noreply@anthropic.com>
This commit is contained in:
parent
d6f76a3058
commit
effe80c0a7
2 changed files with 276 additions and 0 deletions
143
containers/authentik/authentik-django.nix
Normal file
143
containers/authentik/authentik-django.nix
Normal file
|
|
@ -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 <nixpkgs> { }, 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;
|
||||
}
|
||||
133
containers/authentik/python-deps.nix
Normal file
133
containers/authentik/python-deps.nix
Normal file
|
|
@ -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 <nixpkgs> { }, 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;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue