Merge pull request #563 from jcastro/feature/oci-authentik

Add Authentik to OCI catalog
This commit is contained in:
MacRimi
2026-10-31 09:53:15 +01:00
committed by GitHub
8 changed files with 1500 additions and 7 deletions
+5 -1
View File
@@ -249,7 +249,7 @@ jobs:
registry-accounts: registry-accounts:
runs-on: ubuntu-latest runs-on: ubuntu-latest
timeout-minutes: 5 timeout-minutes: 10
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
with: with:
@@ -330,6 +330,10 @@ jobs:
env: env:
PYTHONDONTWRITEBYTECODE: '1' PYTHONDONTWRITEBYTECODE: '1'
run: python3 -m unittest discover -s oci/tests -p test_catalog_web_links.py -v run: python3 -m unittest discover -s oci/tests -p test_catalog_web_links.py -v
- name: Run full OCI engine test suite
env:
PYTHONDONTWRITEBYTECODE: '1'
run: python3 -m unittest discover -s oci/tests -v
pbs: pbs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
+4
View File
@@ -3174,6 +3174,7 @@
"Identical:": "Zhodné:", "Identical:": "Zhodné:",
"Identify candidate disk (never use system disk):": "Nájsť vhodný disk (nikdy nepoužívajte systémový disk):", "Identify candidate disk (never use system disk):": "Nájsť vhodný disk (nikdy nepoužívajte systémový disk):",
"Identify persistent disk paths": "Zistiť trvalé cesty k diskom", "Identify persistent disk paths": "Zistiť trvalé cesty k diskom",
"Identity provider for single sign-on and access control": "Poskytovateľ identity pre jednotné prihlasovanie a riadenie prístupu",
"If 'proxmox-ve' removal warning:": "Ak sa zobrazí varovanie o odstránení 'proxmox-ve':", "If 'proxmox-ve' removal warning:": "Ak sa zobrazí varovanie o odstránení 'proxmox-ve':",
"If Code 43 error appears, KVM hiding is already configured.": "Ak sa objaví chyba Code 43, KVM skrytie už je nastavené.", "If Code 43 error appears, KVM hiding is already configured.": "Ak sa objaví chyba Code 43, KVM skrytie už je nastavené.",
"If GUI does not load:": "Ak sa GUI nenačíta:", "If GUI does not load:": "Ak sa GUI nenačíta:",
@@ -3336,6 +3337,7 @@
"Initial Stirling-PDF account": "Počiatočný účet Stirling-PDF", "Initial Stirling-PDF account": "Počiatočný účet Stirling-PDF",
"Initial Tandoor administrator": "Počiatočný správca Tandoor", "Initial Tandoor administrator": "Počiatočný správca Tandoor",
"Initial Umami administrator": "Počiatočný správca Umami", "Initial Umami administrator": "Počiatočný správca Umami",
"Initial administrator account": "Úvodný účet správcu",
"Initial administrator created:": "Pôvodný správca vytvorený:", "Initial administrator created:": "Pôvodný správca vytvorený:",
"Initial administrator user": "Počiatočné používateľské meno správcu", "Initial administrator user": "Počiatočné používateľské meno správcu",
"Initializing Borg repository if needed...": "Pripravujem Borg repozitár, ak je to potrebné...", "Initializing Borg repository if needed...": "Pripravujem Borg repozitár, ak je to potrebné...",
@@ -4889,6 +4891,7 @@
"Open the dashboard from this host on port 8008 to create a new admin account.": "Otvorte dashboard z tohto servera na porte 8008 a vytvorte nový admin účet.", "Open the dashboard from this host on port 8008 to create a new admin account.": "Otvorte dashboard z tohto servera na porte 8008 a vytvorte nový admin účet.",
"Open the dashboard to create a new admin account:": "Otvorte dashboard a vytvorte nový admin účet:", "Open the dashboard to create a new admin account:": "Otvorte dashboard a vytvorte nový admin účet:",
"Open the page of an application in the menu": "Otvoriť stránku aplikácie v ponuke", "Open the page of an application in the menu": "Otvoriť stránku aplikácie v ponuke",
"Open the web address and create the password of the akadmin user in the first-run setup.": "Otvorte webovú adresu a v prvotnom nastavení vytvorte heslo používateľa akadmin.",
"Open-source AI-powered coding assistant": "Programátorský asistent využívajúci AI s otvoreným zdrojovým kódom", "Open-source AI-powered coding assistant": "Programátorský asistent využívajúci AI s otvoreným zdrojovým kódom",
"Open-source UI for building and debugging multi-agent and RAG applications": "Rozhranie s otvoreným zdrojovým kódom na tvorbu a ladenie aplikácií s viacerými agentmi a RAG", "Open-source UI for building and debugging multi-agent and RAG applications": "Rozhranie s otvoreným zdrojovým kódom na tvorbu a ladenie aplikácií s viacerými agentmi a RAG",
"Open-source in-memory data store.": "Úložisko údajov v pamäti s otvoreným zdrojovým kódom.", "Open-source in-memory data store.": "Úložisko údajov v pamäti s otvoreným zdrojovým kódom.",
@@ -8181,6 +8184,7 @@
"Web access": "Adresa webového rozhrania", "Web access": "Adresa webového rozhrania",
"Web address of the AdGuard Home that receives the settings (e.g. http://192.168.1.3)": "Webová adresa AdGuard Home, ktorá prijíma nastavenia (napr. http://192.168.1.3)", "Web address of the AdGuard Home that receives the settings (e.g. http://192.168.1.3)": "Webová adresa AdGuard Home, ktorá prijíma nastavenia (napr. http://192.168.1.3)",
"Web address of the main AdGuard Home, whose settings are copied (e.g. http://192.168.1.2)": "Webová adresa hlavného AdGuard Home, ktorého nastavenia sú kopírované (napr. http://192.168.1.2)", "Web address of the main AdGuard Home, whose settings are copied (e.g. http://192.168.1.2)": "Webová adresa hlavného AdGuard Home, ktorého nastavenia sú kopírované (napr. http://192.168.1.2)",
"Web interface": "Webové rozhranie",
"Web interface for Borg Backup repositories and jobs.": "Webové rozhranie na správu úložísk a úloh Borg Backup.", "Web interface for Borg Backup repositories and jobs.": "Webové rozhranie na správu úložísk a úloh Borg Backup.",
"Web interface for managing 3D printers.": "Webové rozhranie na správu 3D tlačiarní.", "Web interface for managing 3D printers.": "Webové rozhranie na správu 3D tlačiarní.",
"Web interface to manage devices running Tasmota firmware.": "Webové rozhranie pre správu zariadení bežiacich na Tasmota firmware.", "Web interface to manage devices running Tasmota firmware.": "Webové rozhranie pre správu zariadení bežiacich na Tasmota firmware.",
+1 -1
View File
@@ -2,7 +2,7 @@
<!-- Generated from oci/catalog/verification.json by .github/scripts/oci_validation.py. Do not edit by hand. --> <!-- Generated from oci/catalog/verification.json by .github/scripts/oci_validation.py. Do not edit by hand. -->
44 of 530 applications in the OCI catalog have been tested for real: 41 in the ProxMenux lab and 3 by the community. The OCI installer shows them as verified, with a ✓ in the lists. 44 of 531 applications in the OCI catalog have been tested for real: 41 in the ProxMenux lab and 3 by the community. The OCI installer shows them as verified, with a ✓ in the lists.
Each test describes the scenario that was run and names the image it ran on. It does not cover every possible configuration of the application, and a newer image has not been tested until someone reports it. Each test describes the scenario that was run and names the image it ran on. It does not cover every possible configuration of the application, and a newer image has not been tested until someone reports it.
+640
View File
@@ -0,0 +1,640 @@
{
"schema_version": "0.5.0",
"kind": "proxmenux.oci-template",
"id": "image-authentik",
"status": "generated-unvalidated",
"catalog_ui": {
"title": {
"en_US": "Authentik"
},
"tagline": {
"en_US": "Identity provider for single sign-on and access control"
},
"description": {
"en_US": "Authentik is an open-source identity provider and access platform. This recipe follows the official three-service deployment with PostgreSQL, the server and a worker, each in its own Proxmox OCI LXC."
},
"category": "security",
"category_label": "Authentication & Security",
"author": "Authentik",
"developer": "Authentik",
"icon": "https://cdn.jsdelivr.net/gh/selfhst/icons@main/webp/authentik.webp",
"thumbnail": null,
"screenshots": [],
"architectures": [
"amd64",
"arm64"
],
"launch": {
"scheme": "http",
"port": 9000,
"path": "/"
},
"website": "https://goauthentik.io/",
"documentation": "https://docs.goauthentik.io/install-config/install/docker-compose/",
"repository": "https://github.com/goauthentik/authentik",
"tips": [
"The server and worker share /data and /templates through host directories; those directories are not included in Proxmox CT backups.",
"The worker Docker-socket mount is omitted. Use the embedded outpost or deploy and manage a separate outpost manually.",
"The worker gets an interface on the selected Proxmox bridge for outbound access; PostgreSQL stays on the private stack network."
],
"mini_changelog": [],
"display_version": "2026.8.5",
"updated_at": "2026-10-21"
},
"source": {
"provider": "goauthentik",
"repository": "https://github.com/goauthentik/authentik",
"default_branch": "main",
"revision": "4ec0d19b98da3c329781894cd550da95d3033954",
"readme_raw_url": "https://goauthentik.io/version/2026.8/lifecycle/container/compose.yml",
"image_repository_url": "https://github.com/goauthentik/authentik/pkgs/container/server",
"readme_pushed_at": "2026-10-21T12:57:59Z",
"compose_sha256": "a3e138b5a85844d15c4a7ba6339997e43c0479d9d198582c67dd19523aadc694",
"generated_at": "2026-10-30T18:09:01+00:00"
},
"container_contract": {
"service_name": "server",
"container_name": "authentik-server",
"image": {
"reference": "ghcr.io/goauthentik/server:2026.8.5",
"registry": "ghcr.io",
"repository": "ghcr.io/goauthentik/server",
"tag": "2026.8.5",
"digest": null,
"pull_policy": "resolve-written-reference-to-architecture-digest-at-install"
},
"environment": [
{
"name": "AUTHENTIK_POSTGRESQL__HOST",
"example": "postgresql",
"required": true,
"sensitive": false,
"source": "docker-compose"
},
{
"name": "AUTHENTIK_POSTGRESQL__NAME",
"example": "authentik",
"required": true,
"sensitive": false,
"source": "docker-compose"
},
{
"name": "AUTHENTIK_POSTGRESQL__PASSWORD",
"example": "${GENERATED_GENERATED_PG_PASS}",
"required": true,
"sensitive": true,
"source": "docker-compose"
},
{
"name": "AUTHENTIK_POSTGRESQL__USER",
"example": "authentik",
"required": true,
"sensitive": false,
"source": "docker-compose"
},
{
"name": "AUTHENTIK_SECRET_KEY",
"example": "${GENERATED_GENERATED_AUTHENTIK_SECRET_KEY}",
"required": true,
"sensitive": true,
"source": "docker-compose"
}
],
"volumes": [
{
"id": "volume-0",
"container_path": "/data",
"compose_source_example": "./data",
"read_only": false,
"required": true,
"installation_choice": [
"host-bind"
],
"default": "host-bind",
"managed_volume": {
"backup": false,
"default_size_gb": 8
}
},
{
"id": "volume-1",
"container_path": "/templates",
"compose_source_example": "./custom-templates",
"read_only": false,
"required": true,
"installation_choice": [
"host-bind"
],
"default": "host-bind",
"managed_volume": {
"backup": false,
"default_size_gb": 8
}
}
],
"ports": [
{
"container_port": 9000,
"published_example": 9000,
"protocol": "tcp",
"required": true,
"proxmox_behavior": "listener-on-dedicated-lxc-address-no-nat"
},
{
"container_port": 9443,
"published_example": 9443,
"protocol": "tcp",
"required": true,
"proxmox_behavior": "listener-on-dedicated-lxc-address-no-nat"
}
],
"related_services": [
{
"name": "postgresql",
"image": "docker.io/library/postgres:16-alpine"
},
{
"name": "worker",
"image": "ghcr.io/goauthentik/server:2026.8.5"
}
],
"restart": "unless-stopped",
"stop_grace_period": null,
"original_compose": "name: authentik\nservices:\n postgresql:\n environment:\n POSTGRES_DB: authentik\n POSTGRES_PASSWORD: ${GENERATED_PG_PASS}\n POSTGRES_USER: authentik\n healthcheck:\n interval: 30s\n retries: 5\n start_period: 20s\n test:\n - CMD-SHELL\n - pg_isready -h 127.0.0.1 -d authentik -U authentik\n timeout: 5s\n image: docker.io/library/postgres:16-alpine\n restart: unless-stopped\n volumes:\n - read_only: false\n source: database\n target: /var/lib/postgresql/data\n server:\n command: server\n depends_on:\n postgresql:\n condition: service_healthy\n environment:\n AUTHENTIK_POSTGRESQL__HOST: postgresql\n AUTHENTIK_POSTGRESQL__NAME: authentik\n AUTHENTIK_POSTGRESQL__PASSWORD: ${GENERATED_PG_PASS}\n AUTHENTIK_POSTGRESQL__USER: authentik\n AUTHENTIK_SECRET_KEY: ${GENERATED_AUTHENTIK_SECRET_KEY}\n image: ghcr.io/goauthentik/server:2026.8.5\n ports:\n - 9000:9000\n - 9443:9443\n restart: unless-stopped\n shm_size: 512mb\n volumes:\n - read_only: false\n source: ./data\n target: /data\n - read_only: false\n source: ./custom-templates\n target: /templates\n worker:\n command: worker\n depends_on:\n postgresql:\n condition: service_healthy\n environment:\n AUTHENTIK_POSTGRESQL__HOST: postgresql\n AUTHENTIK_POSTGRESQL__NAME: authentik\n AUTHENTIK_POSTGRESQL__PASSWORD: ${GENERATED_PG_PASS}\n AUTHENTIK_POSTGRESQL__USER: authentik\n AUTHENTIK_SECRET_KEY: ${GENERATED_AUTHENTIK_SECRET_KEY}\n image: ghcr.io/goauthentik/server:2026.8.5\n restart: unless-stopped\n shm_size: 512mb\n user: root\n volumes:\n - read_only: false\n source: ./data\n target: /data\n - read_only: false\n source: ./certs\n target: /certs\n - read_only: false\n source: ./custom-templates\n target: /templates\nvolumes:\n database: {}\n"
},
"compose_stack": {
"project_name": "authentik",
"deployment_model": "one-native-oci-lxc-per-compose-service",
"user_experience": "single-application-install",
"main_service": "server",
"service_count": 3,
"services": [
{
"name": "postgresql",
"image": "docker.io/library/postgres:16-alpine",
"is_main": false,
"role": "dependency",
"vmid_offset": 1,
"depends_on": [],
"frontend_network": false,
"private_network": true,
"compose": {
"environment": {
"POSTGRES_DB": "authentik",
"POSTGRES_PASSWORD": "${GENERATED_PG_PASS}",
"POSTGRES_USER": "authentik"
},
"healthcheck": {
"interval": "30s",
"retries": 5,
"start_period": "20s",
"test": [
"CMD-SHELL",
"pg_isready -h 127.0.0.1 -d authentik -U authentik"
],
"timeout": "5s"
},
"image": "docker.io/library/postgres:16-alpine",
"restart": "unless-stopped",
"volumes": [
{
"source": "database",
"target": "/var/lib/postgresql/data",
"read_only": false
}
]
}
},
{
"name": "worker",
"image": "ghcr.io/goauthentik/server:2026.8.5",
"is_main": false,
"role": "dependency",
"vmid_offset": 2,
"depends_on": [
"postgresql"
],
"frontend_network": true,
"private_network": true,
"compose": {
"command": "worker",
"depends_on": {
"postgresql": {
"condition": "service_healthy"
}
},
"environment": {
"AUTHENTIK_POSTGRESQL__HOST": "postgresql",
"AUTHENTIK_POSTGRESQL__NAME": "authentik",
"AUTHENTIK_POSTGRESQL__PASSWORD": "${GENERATED_PG_PASS}",
"AUTHENTIK_POSTGRESQL__USER": "authentik",
"AUTHENTIK_SECRET_KEY": "${GENERATED_AUTHENTIK_SECRET_KEY}"
},
"image": "ghcr.io/goauthentik/server:2026.8.5",
"restart": "unless-stopped",
"shm_size": "512mb",
"user": "root",
"volumes": [
{
"source": "./data",
"target": "/data",
"read_only": false
},
{
"source": "./certs",
"target": "/certs",
"read_only": false
},
{
"source": "./custom-templates",
"target": "/templates",
"read_only": false
}
]
}
},
{
"name": "server",
"image": "ghcr.io/goauthentik/server:2026.8.5",
"is_main": true,
"role": "frontend",
"vmid_offset": 0,
"depends_on": [
"postgresql"
],
"frontend_network": true,
"private_network": true,
"compose": {
"command": "server",
"depends_on": {
"postgresql": {
"condition": "service_healthy"
}
},
"environment": {
"AUTHENTIK_POSTGRESQL__HOST": "postgresql",
"AUTHENTIK_POSTGRESQL__NAME": "authentik",
"AUTHENTIK_POSTGRESQL__PASSWORD": "${GENERATED_PG_PASS}",
"AUTHENTIK_POSTGRESQL__USER": "authentik",
"AUTHENTIK_SECRET_KEY": "${GENERATED_AUTHENTIK_SECRET_KEY}"
},
"image": "ghcr.io/goauthentik/server:2026.8.5",
"ports": [
"9000:9000",
"9443:9443"
],
"restart": "unless-stopped",
"shm_size": "512mb",
"volumes": [
{
"source": "./data",
"target": "/data",
"read_only": false
},
{
"source": "./custom-templates",
"target": "/templates",
"read_only": false
}
]
}
}
],
"top_level": {
"name": "authentik",
"volumes": {
"database": {}
}
},
"networking": {
"frontend": "selected-proxmox-bridge",
"private_required": true,
"private_creation": "automatic-create-if-missing",
"private_address_allocation": "automatic-static-address-per-service",
"service_discovery": "private-addresses-with-compose-service-host-aliases",
"dependency_external_access": "worker-through-selected-proxmox-bridge; postgresql-private-only",
"prompt_user_for_private_network": false
},
"storage": [
{
"id": "postgresql-volume-0",
"service": "postgresql",
"container_path": "/var/lib/postgresql/data",
"mode": "managed-volume",
"user_selectable": false,
"backup": true,
"shared_with_other_lxc": false,
"source_path": null,
"source_path_prompt": null
},
{
"id": "server-volume-0",
"service": "server",
"container_path": "/data",
"mode": "host-bind",
"user_selectable": true,
"backup": false,
"shared_with_other_lxc": true,
"source_path": null,
"source_path_prompt": "Host directory for authentik:/data"
},
{
"id": "server-volume-1",
"service": "server",
"container_path": "/templates",
"mode": "host-bind",
"user_selectable": true,
"backup": false,
"shared_with_other_lxc": true,
"source_path": null,
"source_path_prompt": "Host directory for authentik:/templates"
},
{
"id": "worker-volume-0",
"service": "worker",
"container_path": "/data",
"mode": "host-bind",
"user_selectable": true,
"backup": false,
"shared_with_other_lxc": true,
"source_path": null,
"source_path_prompt": "Host directory for authentik:/data"
},
{
"id": "worker-volume-1",
"service": "worker",
"container_path": "/certs",
"mode": "managed-volume",
"user_selectable": false,
"backup": true,
"shared_with_other_lxc": false,
"source_path": null,
"source_path_prompt": null
},
{
"id": "worker-volume-2",
"service": "worker",
"container_path": "/templates",
"mode": "host-bind",
"user_selectable": true,
"backup": false,
"shared_with_other_lxc": true,
"source_path": null,
"source_path_prompt": "Host directory for authentik:/templates"
}
],
"orchestration": {
"reserve_vmids_atomically": 3,
"start_order": [
"postgresql",
"worker",
"server"
],
"stop_order": [
"server",
"worker",
"postgresql"
],
"dependency_readiness": "compose-healthcheck-then-port-or-process-fallback",
"rollback_on_failure": "remove-new-rootfs-preserve-created-persistent-volumes"
},
"installer_inputs": {
"prompted": [
"stack_name",
"base_vmid",
"rootfs_storage",
"persistent_data_destinations",
"frontend_bridge",
"frontend_ipv4_mode"
],
"automatic": [
"dependent_vmids",
"private_bridge",
"private_subnet",
"private_service_addresses",
"compose_service_aliases",
"generated_secrets",
"dependency_start_and_stop_order"
],
"generated_secrets": [
{
"id": "pg-pass",
"strategy": "generate-cryptographically-random-at-install",
"bindings": [
{
"service": "postgresql",
"environment_variable": "POSTGRES_PASSWORD"
},
{
"service": "server",
"environment_variable": "AUTHENTIK_POSTGRESQL__PASSWORD"
},
{
"service": "worker",
"environment_variable": "AUTHENTIK_POSTGRESQL__PASSWORD"
}
]
},
{
"id": "authentik-secret-key",
"strategy": "generate-cryptographically-random-at-install",
"bindings": [
{
"service": "server",
"environment_variable": "AUTHENTIK_SECRET_KEY"
},
{
"service": "worker",
"environment_variable": "AUTHENTIK_SECRET_KEY"
}
]
}
]
}
},
"first_run": {
"endpoints": [
{
"label": "Web interface",
"scheme": "http",
"port": 9000,
"path": "/",
"source": "official-compose"
}
],
"credentials": []
},
"proxmox": {
"runtime": "native-oci-lxc",
"technology_status": "proxmox-technology-preview",
"defaults": {
"unprivileged": true,
"ostype": "auto-from-image",
"cores": 2,
"memory_mb": 1024,
"swap_mb": 512,
"rootfs_size_gb": 8,
"rootfs_storage": "local-lvm",
"volume_storage": "local-lvm",
"template_storage": "local",
"bridge": "vmbr0",
"ipv4": "dhcp",
"firewall": true,
"host_managed_network": true,
"onboot": true,
"features": [
"nesting=1"
],
"shutdown_timeout_seconds": 30
},
"image_metadata_policy": {
"entrypoint": "import-from-oci-image",
"cmd": "import-from-oci-image",
"environment": "import-image-env-then-apply-compose-overrides",
"user": "import-from-oci-image",
"working_dir": "import-from-oci-image",
"stop_signal": "import-from-oci-image"
},
"installer_profile": {
"stack_driver": "generic-multi-lxc-stack",
"volume_preparations": [
{
"container_path": "/data",
"remove_lost_found": true,
"owner_strategy": "mapped-application-user",
"only_when_mount_type": "managed-volume"
},
{
"container_path": "/templates",
"remove_lost_found": true,
"owner_strategy": "mapped-application-user",
"only_when_mount_type": "managed-volume"
},
{
"container_path": "/certs",
"remove_lost_found": true,
"owner_strategy": "mapped-application-user",
"only_when_mount_type": "managed-volume"
},
{
"container_path": "/var/lib/postgresql/data",
"remove_lost_found": true,
"owner_strategy": "mapped-application-user",
"only_when_mount_type": "managed-volume"
}
]
},
"adaptations": [
{
"id": "worker-docker-socket-removed",
"upstream_behavior": "The official Compose mounts the Docker socket into the worker for automatic deployment and management of separate outposts.",
"native_lxc_behavior": "The Docker socket mount is omitted. Authentik itself runs as server, PostgreSQL and worker in native OCI LXCs.",
"reason": "The ProxMenux native OCI stack does not run Docker Engine or provide a Docker daemon socket.",
"behavioral_impact": "Use the embedded outpost for a simple proxy-provider deployment, or deploy and manage an outpost separately.",
"validation": "passed-ms03-2026-10-30"
},
{
"id": "postgres-healthcheck-normalized",
"upstream_behavior": "The official PostgreSQL healthcheck expands Compose environment variables in its command.",
"native_lxc_behavior": "The check uses the recipe’s fixed database and username with pg_isready against localhost.",
"reason": "The native stack healthcheck runs in the database LXC and does not evaluate Compose variable interpolation.",
"behavioral_impact": "It checks the same local database readiness before dependent services start.",
"validation": "passed-ms03-postgresql-readiness-2026-10-30"
},
{
"id": "server-worker-shared-data",
"upstream_behavior": "Authentik server and worker both mount /data and /templates from the same Compose source directories.",
"native_lxc_behavior": "ProxMenux uses shared host directories for these two paths across the server and worker LXCs.",
"reason": "The same data and custom templates must be visible to both services; the stack driver shares named Compose sources through host paths.",
"behavioral_impact": "These shared host directories are not included in native LXC backups and need a separate backup.",
"validation": "passed-ms03-2026-10-30"
}
],
"custom_compose_stack": true,
"custom_compose_interpolated": false,
"stack_image_entrypoints": {},
"stack_completion_notes": [
"Open the web address and create the password of the akadmin user in the first-run setup."
]
},
"compatibility": {
"automatic_install_candidate": true,
"validated": false,
"supported_compose_keys": [
"cap_add",
"command",
"container_name",
"cpu_shares",
"deploy",
"devices",
"entrypoint",
"environment",
"extra_hosts",
"group_add",
"healthcheck",
"hostname",
"image",
"init",
"ipc",
"labels",
"logging",
"mac_address",
"mem_limit",
"network_mode",
"networks",
"ports",
"privileged",
"restart",
"runtime",
"security_opt",
"shm_size",
"stdin_open",
"stop_grace_period",
"stop_signal",
"sysctls",
"tty",
"ulimits",
"user",
"volumes",
"working_dir"
],
"untranslated_blockers": [],
"policy": "Official Authentik Compose translated to three native OCI LXCs. The Docker socket mount is deliberately removed; the embedded outpost remains available."
},
"validation": {
"schema": "passed-local-2026-10-30",
"image_pull": "passed-ms03-skopeo-and-OCI-integrity-server-worker-postgresql-2026-10-30",
"default_install": "passed-ms03-three-LXCs-started-PostgreSQL-healthy-2026-10-30",
"advanced_install": "pending",
"credentials": "pending-user-first-run-and-login",
"web_ui": "pending-user-first-run-and-login",
"storage_modes": "pending",
"restart_persistence": "pending",
"backup_restore": "pending",
"update_preserves_data": "pending",
"rollback": "pending",
"modification": "pending",
"removal": "pending",
"oci_engine_tests": "pending",
"offline_i18n_tests": "pending",
"runtime_architecture": "amd64-ms03; Skopeo manifests include amd64-and-arm64",
"database_health": "passed-pg_isready-2026-10-30"
},
"lifecycle": {
"update_strategy": "resolve-selected-tag-and-architecture-digest-then-apply-reviewed-native-lxc-update",
"registry_state": {
"resolved_architecture": null,
"resolved_digest": null,
"image_version_label": "2026.8.5",
"image_created": null
},
"change_detection": "compare-compose-sha256-and-resolved-image-digest",
"automatic_unattended_updates": false,
"dependency_lifecycle": {
"implementation": "proxmox-hookscript",
"trigger": "main-lxc-pre-start",
"waits_for_dependency_healthchecks": true,
"stops_dependencies_with_main": false
}
}
}
+640
View File
@@ -0,0 +1,640 @@
{
"schema_version": "0.5.0",
"kind": "proxmenux.oci-template",
"id": "image-authentik",
"status": "generated-unvalidated",
"catalog_ui": {
"title": {
"en_US": "Authentik"
},
"tagline": {
"en_US": "Identity provider for single sign-on and access control"
},
"description": {
"en_US": "Authentik is an open-source identity provider and access platform. This recipe follows the official three-service deployment with PostgreSQL, the server and a worker, each in its own Proxmox OCI LXC."
},
"category": "security",
"category_label": "Authentication & Security",
"author": "Authentik",
"developer": "Authentik",
"icon": "https://cdn.jsdelivr.net/gh/selfhst/icons@main/webp/authentik.webp",
"thumbnail": null,
"screenshots": [],
"architectures": [
"amd64",
"arm64"
],
"launch": {
"scheme": "http",
"port": 9000,
"path": "/"
},
"website": "https://goauthentik.io/",
"documentation": "https://docs.goauthentik.io/install-config/install/docker-compose/",
"repository": "https://github.com/goauthentik/authentik",
"tips": [
"The server and worker share /data and /templates through host directories; those directories are not included in Proxmox CT backups.",
"The worker Docker-socket mount is omitted. Use the embedded outpost or deploy and manage a separate outpost manually.",
"The worker gets an interface on the selected Proxmox bridge for outbound access; PostgreSQL stays on the private stack network."
],
"mini_changelog": [],
"display_version": "2026.8.5",
"updated_at": "2026-10-21"
},
"source": {
"provider": "goauthentik",
"repository": "https://github.com/goauthentik/authentik",
"default_branch": "main",
"revision": "4ec0d19b98da3c329781894cd550da95d3033954",
"readme_raw_url": "https://goauthentik.io/version/2026.8/lifecycle/container/compose.yml",
"image_repository_url": "https://github.com/goauthentik/authentik/pkgs/container/server",
"readme_pushed_at": "2026-10-21T12:57:59Z",
"compose_sha256": "a3e138b5a85844d15c4a7ba6339997e43c0479d9d198582c67dd19523aadc694",
"generated_at": "2026-10-30T18:09:01+00:00"
},
"container_contract": {
"service_name": "server",
"container_name": "authentik-server",
"image": {
"reference": "ghcr.io/goauthentik/server:2026.8.5",
"registry": "ghcr.io",
"repository": "ghcr.io/goauthentik/server",
"tag": "2026.8.5",
"digest": null,
"pull_policy": "resolve-written-reference-to-architecture-digest-at-install"
},
"environment": [
{
"name": "AUTHENTIK_POSTGRESQL__HOST",
"example": "postgresql",
"required": true,
"sensitive": false,
"source": "docker-compose"
},
{
"name": "AUTHENTIK_POSTGRESQL__NAME",
"example": "authentik",
"required": true,
"sensitive": false,
"source": "docker-compose"
},
{
"name": "AUTHENTIK_POSTGRESQL__PASSWORD",
"example": "${GENERATED_GENERATED_PG_PASS}",
"required": true,
"sensitive": true,
"source": "docker-compose"
},
{
"name": "AUTHENTIK_POSTGRESQL__USER",
"example": "authentik",
"required": true,
"sensitive": false,
"source": "docker-compose"
},
{
"name": "AUTHENTIK_SECRET_KEY",
"example": "${GENERATED_GENERATED_AUTHENTIK_SECRET_KEY}",
"required": true,
"sensitive": true,
"source": "docker-compose"
}
],
"volumes": [
{
"id": "volume-0",
"container_path": "/data",
"compose_source_example": "./data",
"read_only": false,
"required": true,
"installation_choice": [
"host-bind"
],
"default": "host-bind",
"managed_volume": {
"backup": false,
"default_size_gb": 8
}
},
{
"id": "volume-1",
"container_path": "/templates",
"compose_source_example": "./custom-templates",
"read_only": false,
"required": true,
"installation_choice": [
"host-bind"
],
"default": "host-bind",
"managed_volume": {
"backup": false,
"default_size_gb": 8
}
}
],
"ports": [
{
"container_port": 9000,
"published_example": 9000,
"protocol": "tcp",
"required": true,
"proxmox_behavior": "listener-on-dedicated-lxc-address-no-nat"
},
{
"container_port": 9443,
"published_example": 9443,
"protocol": "tcp",
"required": true,
"proxmox_behavior": "listener-on-dedicated-lxc-address-no-nat"
}
],
"related_services": [
{
"name": "postgresql",
"image": "docker.io/library/postgres:16-alpine"
},
{
"name": "worker",
"image": "ghcr.io/goauthentik/server:2026.8.5"
}
],
"restart": "unless-stopped",
"stop_grace_period": null,
"original_compose": "name: authentik\nservices:\n postgresql:\n environment:\n POSTGRES_DB: authentik\n POSTGRES_PASSWORD: ${GENERATED_PG_PASS}\n POSTGRES_USER: authentik\n healthcheck:\n interval: 30s\n retries: 5\n start_period: 20s\n test:\n - CMD-SHELL\n - pg_isready -h 127.0.0.1 -d authentik -U authentik\n timeout: 5s\n image: docker.io/library/postgres:16-alpine\n restart: unless-stopped\n volumes:\n - read_only: false\n source: database\n target: /var/lib/postgresql/data\n server:\n command: server\n depends_on:\n postgresql:\n condition: service_healthy\n environment:\n AUTHENTIK_POSTGRESQL__HOST: postgresql\n AUTHENTIK_POSTGRESQL__NAME: authentik\n AUTHENTIK_POSTGRESQL__PASSWORD: ${GENERATED_PG_PASS}\n AUTHENTIK_POSTGRESQL__USER: authentik\n AUTHENTIK_SECRET_KEY: ${GENERATED_AUTHENTIK_SECRET_KEY}\n image: ghcr.io/goauthentik/server:2026.8.5\n ports:\n - 9000:9000\n - 9443:9443\n restart: unless-stopped\n shm_size: 512mb\n volumes:\n - read_only: false\n source: ./data\n target: /data\n - read_only: false\n source: ./custom-templates\n target: /templates\n worker:\n command: worker\n depends_on:\n postgresql:\n condition: service_healthy\n environment:\n AUTHENTIK_POSTGRESQL__HOST: postgresql\n AUTHENTIK_POSTGRESQL__NAME: authentik\n AUTHENTIK_POSTGRESQL__PASSWORD: ${GENERATED_PG_PASS}\n AUTHENTIK_POSTGRESQL__USER: authentik\n AUTHENTIK_SECRET_KEY: ${GENERATED_AUTHENTIK_SECRET_KEY}\n image: ghcr.io/goauthentik/server:2026.8.5\n restart: unless-stopped\n shm_size: 512mb\n user: root\n volumes:\n - read_only: false\n source: ./data\n target: /data\n - read_only: false\n source: ./certs\n target: /certs\n - read_only: false\n source: ./custom-templates\n target: /templates\nvolumes:\n database: {}\n"
},
"compose_stack": {
"project_name": "authentik",
"deployment_model": "one-native-oci-lxc-per-compose-service",
"user_experience": "single-application-install",
"main_service": "server",
"service_count": 3,
"services": [
{
"name": "postgresql",
"image": "docker.io/library/postgres:16-alpine",
"is_main": false,
"role": "dependency",
"vmid_offset": 1,
"depends_on": [],
"frontend_network": false,
"private_network": true,
"compose": {
"environment": {
"POSTGRES_DB": "authentik",
"POSTGRES_PASSWORD": "${GENERATED_PG_PASS}",
"POSTGRES_USER": "authentik"
},
"healthcheck": {
"interval": "30s",
"retries": 5,
"start_period": "20s",
"test": [
"CMD-SHELL",
"pg_isready -h 127.0.0.1 -d authentik -U authentik"
],
"timeout": "5s"
},
"image": "docker.io/library/postgres:16-alpine",
"restart": "unless-stopped",
"volumes": [
{
"source": "database",
"target": "/var/lib/postgresql/data",
"read_only": false
}
]
}
},
{
"name": "worker",
"image": "ghcr.io/goauthentik/server:2026.8.5",
"is_main": false,
"role": "dependency",
"vmid_offset": 2,
"depends_on": [
"postgresql"
],
"frontend_network": true,
"private_network": true,
"compose": {
"command": "worker",
"depends_on": {
"postgresql": {
"condition": "service_healthy"
}
},
"environment": {
"AUTHENTIK_POSTGRESQL__HOST": "postgresql",
"AUTHENTIK_POSTGRESQL__NAME": "authentik",
"AUTHENTIK_POSTGRESQL__PASSWORD": "${GENERATED_PG_PASS}",
"AUTHENTIK_POSTGRESQL__USER": "authentik",
"AUTHENTIK_SECRET_KEY": "${GENERATED_AUTHENTIK_SECRET_KEY}"
},
"image": "ghcr.io/goauthentik/server:2026.8.5",
"restart": "unless-stopped",
"shm_size": "512mb",
"user": "root",
"volumes": [
{
"source": "./data",
"target": "/data",
"read_only": false
},
{
"source": "./certs",
"target": "/certs",
"read_only": false
},
{
"source": "./custom-templates",
"target": "/templates",
"read_only": false
}
]
}
},
{
"name": "server",
"image": "ghcr.io/goauthentik/server:2026.8.5",
"is_main": true,
"role": "frontend",
"vmid_offset": 0,
"depends_on": [
"postgresql"
],
"frontend_network": true,
"private_network": true,
"compose": {
"command": "server",
"depends_on": {
"postgresql": {
"condition": "service_healthy"
}
},
"environment": {
"AUTHENTIK_POSTGRESQL__HOST": "postgresql",
"AUTHENTIK_POSTGRESQL__NAME": "authentik",
"AUTHENTIK_POSTGRESQL__PASSWORD": "${GENERATED_PG_PASS}",
"AUTHENTIK_POSTGRESQL__USER": "authentik",
"AUTHENTIK_SECRET_KEY": "${GENERATED_AUTHENTIK_SECRET_KEY}"
},
"image": "ghcr.io/goauthentik/server:2026.8.5",
"ports": [
"9000:9000",
"9443:9443"
],
"restart": "unless-stopped",
"shm_size": "512mb",
"volumes": [
{
"source": "./data",
"target": "/data",
"read_only": false
},
{
"source": "./custom-templates",
"target": "/templates",
"read_only": false
}
]
}
}
],
"top_level": {
"name": "authentik",
"volumes": {
"database": {}
}
},
"networking": {
"frontend": "selected-proxmox-bridge",
"private_required": true,
"private_creation": "automatic-create-if-missing",
"private_address_allocation": "automatic-static-address-per-service",
"service_discovery": "private-addresses-with-compose-service-host-aliases",
"dependency_external_access": "worker-through-selected-proxmox-bridge; postgresql-private-only",
"prompt_user_for_private_network": false
},
"storage": [
{
"id": "postgresql-volume-0",
"service": "postgresql",
"container_path": "/var/lib/postgresql/data",
"mode": "managed-volume",
"user_selectable": false,
"backup": true,
"shared_with_other_lxc": false,
"source_path": null,
"source_path_prompt": null
},
{
"id": "server-volume-0",
"service": "server",
"container_path": "/data",
"mode": "host-bind",
"user_selectable": true,
"backup": false,
"shared_with_other_lxc": true,
"source_path": null,
"source_path_prompt": "Host directory for authentik:/data"
},
{
"id": "server-volume-1",
"service": "server",
"container_path": "/templates",
"mode": "host-bind",
"user_selectable": true,
"backup": false,
"shared_with_other_lxc": true,
"source_path": null,
"source_path_prompt": "Host directory for authentik:/templates"
},
{
"id": "worker-volume-0",
"service": "worker",
"container_path": "/data",
"mode": "host-bind",
"user_selectable": true,
"backup": false,
"shared_with_other_lxc": true,
"source_path": null,
"source_path_prompt": "Host directory for authentik:/data"
},
{
"id": "worker-volume-1",
"service": "worker",
"container_path": "/certs",
"mode": "managed-volume",
"user_selectable": false,
"backup": true,
"shared_with_other_lxc": false,
"source_path": null,
"source_path_prompt": null
},
{
"id": "worker-volume-2",
"service": "worker",
"container_path": "/templates",
"mode": "host-bind",
"user_selectable": true,
"backup": false,
"shared_with_other_lxc": true,
"source_path": null,
"source_path_prompt": "Host directory for authentik:/templates"
}
],
"orchestration": {
"reserve_vmids_atomically": 3,
"start_order": [
"postgresql",
"worker",
"server"
],
"stop_order": [
"server",
"worker",
"postgresql"
],
"dependency_readiness": "compose-healthcheck-then-port-or-process-fallback",
"rollback_on_failure": "remove-new-rootfs-preserve-created-persistent-volumes"
},
"installer_inputs": {
"prompted": [
"stack_name",
"base_vmid",
"rootfs_storage",
"persistent_data_destinations",
"frontend_bridge",
"frontend_ipv4_mode"
],
"automatic": [
"dependent_vmids",
"private_bridge",
"private_subnet",
"private_service_addresses",
"compose_service_aliases",
"generated_secrets",
"dependency_start_and_stop_order"
],
"generated_secrets": [
{
"id": "pg-pass",
"strategy": "generate-cryptographically-random-at-install",
"bindings": [
{
"service": "postgresql",
"environment_variable": "POSTGRES_PASSWORD"
},
{
"service": "server",
"environment_variable": "AUTHENTIK_POSTGRESQL__PASSWORD"
},
{
"service": "worker",
"environment_variable": "AUTHENTIK_POSTGRESQL__PASSWORD"
}
]
},
{
"id": "authentik-secret-key",
"strategy": "generate-cryptographically-random-at-install",
"bindings": [
{
"service": "server",
"environment_variable": "AUTHENTIK_SECRET_KEY"
},
{
"service": "worker",
"environment_variable": "AUTHENTIK_SECRET_KEY"
}
]
}
]
}
},
"first_run": {
"endpoints": [
{
"label": "Web interface",
"scheme": "http",
"port": 9000,
"path": "/",
"source": "official-compose"
}
],
"credentials": []
},
"proxmox": {
"runtime": "native-oci-lxc",
"technology_status": "proxmox-technology-preview",
"defaults": {
"unprivileged": true,
"ostype": "auto-from-image",
"cores": 2,
"memory_mb": 1024,
"swap_mb": 512,
"rootfs_size_gb": 8,
"rootfs_storage": "local-lvm",
"volume_storage": "local-lvm",
"template_storage": "local",
"bridge": "vmbr0",
"ipv4": "dhcp",
"firewall": true,
"host_managed_network": true,
"onboot": true,
"features": [
"nesting=1"
],
"shutdown_timeout_seconds": 30
},
"image_metadata_policy": {
"entrypoint": "import-from-oci-image",
"cmd": "import-from-oci-image",
"environment": "import-image-env-then-apply-compose-overrides",
"user": "import-from-oci-image",
"working_dir": "import-from-oci-image",
"stop_signal": "import-from-oci-image"
},
"installer_profile": {
"stack_driver": "generic-multi-lxc-stack",
"volume_preparations": [
{
"container_path": "/data",
"remove_lost_found": true,
"owner_strategy": "mapped-application-user",
"only_when_mount_type": "managed-volume"
},
{
"container_path": "/templates",
"remove_lost_found": true,
"owner_strategy": "mapped-application-user",
"only_when_mount_type": "managed-volume"
},
{
"container_path": "/certs",
"remove_lost_found": true,
"owner_strategy": "mapped-application-user",
"only_when_mount_type": "managed-volume"
},
{
"container_path": "/var/lib/postgresql/data",
"remove_lost_found": true,
"owner_strategy": "mapped-application-user",
"only_when_mount_type": "managed-volume"
}
]
},
"adaptations": [
{
"id": "worker-docker-socket-removed",
"upstream_behavior": "The official Compose mounts the Docker socket into the worker for automatic deployment and management of separate outposts.",
"native_lxc_behavior": "The Docker socket mount is omitted. Authentik itself runs as server, PostgreSQL and worker in native OCI LXCs.",
"reason": "The ProxMenux native OCI stack does not run Docker Engine or provide a Docker daemon socket.",
"behavioral_impact": "Use the embedded outpost for a simple proxy-provider deployment, or deploy and manage an outpost separately.",
"validation": "passed-ms03-2026-10-30"
},
{
"id": "postgres-healthcheck-normalized",
"upstream_behavior": "The official PostgreSQL healthcheck expands Compose environment variables in its command.",
"native_lxc_behavior": "The check uses the recipe’s fixed database and username with pg_isready against localhost.",
"reason": "The native stack healthcheck runs in the database LXC and does not evaluate Compose variable interpolation.",
"behavioral_impact": "It checks the same local database readiness before dependent services start.",
"validation": "passed-ms03-postgresql-readiness-2026-10-30"
},
{
"id": "server-worker-shared-data",
"upstream_behavior": "Authentik server and worker both mount /data and /templates from the same Compose source directories.",
"native_lxc_behavior": "ProxMenux uses shared host directories for these two paths across the server and worker LXCs.",
"reason": "The same data and custom templates must be visible to both services; the stack driver shares named Compose sources through host paths.",
"behavioral_impact": "These shared host directories are not included in native LXC backups and need a separate backup.",
"validation": "passed-ms03-2026-10-30"
}
],
"custom_compose_stack": true,
"custom_compose_interpolated": false,
"stack_image_entrypoints": {},
"stack_completion_notes": [
"Open the web address and create the password of the akadmin user in the first-run setup."
]
},
"compatibility": {
"automatic_install_candidate": true,
"validated": false,
"supported_compose_keys": [
"cap_add",
"command",
"container_name",
"cpu_shares",
"deploy",
"devices",
"entrypoint",
"environment",
"extra_hosts",
"group_add",
"healthcheck",
"hostname",
"image",
"init",
"ipc",
"labels",
"logging",
"mac_address",
"mem_limit",
"network_mode",
"networks",
"ports",
"privileged",
"restart",
"runtime",
"security_opt",
"shm_size",
"stdin_open",
"stop_grace_period",
"stop_signal",
"sysctls",
"tty",
"ulimits",
"user",
"volumes",
"working_dir"
],
"untranslated_blockers": [],
"policy": "Official Authentik Compose translated to three native OCI LXCs. The Docker socket mount is deliberately removed; the embedded outpost remains available."
},
"validation": {
"schema": "passed-local-2026-10-30",
"image_pull": "passed-ms03-skopeo-and-OCI-integrity-server-worker-postgresql-2026-10-30",
"default_install": "passed-ms03-three-LXCs-started-PostgreSQL-healthy-2026-10-30",
"advanced_install": "pending",
"credentials": "pending-user-first-run-and-login",
"web_ui": "pending-user-first-run-and-login",
"storage_modes": "pending",
"restart_persistence": "pending",
"backup_restore": "pending",
"update_preserves_data": "pending",
"rollback": "pending",
"modification": "pending",
"removal": "pending",
"oci_engine_tests": "pending",
"offline_i18n_tests": "pending",
"runtime_architecture": "amd64-ms03; Skopeo manifests include amd64-and-arm64",
"database_health": "passed-pg_isready-2026-10-30"
},
"lifecycle": {
"update_strategy": "resolve-selected-tag-and-architecture-digest-then-apply-reviewed-native-lxc-update",
"registry_state": {
"resolved_architecture": null,
"resolved_digest": null,
"image_version_label": "2026.8.5",
"image_created": null
},
"change_detection": "compare-compose-sha256-and-resolved-image-digest",
"automatic_unattended_updates": false,
"dependency_lifecycle": {
"implementation": "proxmox-hookscript",
"trigger": "main-lxc-pre-start",
"waits_for_dependency_healthchecks": true,
"stops_dependencies_with_main": false
}
}
}
+35 -2
View File
@@ -2,7 +2,7 @@
"schema_version": "0.2.0", "schema_version": "0.2.0",
"kind": "proxmenux.oci-catalog-index", "kind": "proxmenux.oci-catalog-index",
"provider": "multiple-container-images", "provider": "multiple-container-images",
"generated_at": "2026-10-26T08:05:54+00:00", "generated_at": "2026-10-30T18:13:36+00:00",
"applications": [ "applications": [
{ {
"id": "2fauth", "id": "2fauth",
@@ -752,6 +752,39 @@
"hidden": false, "hidden": false,
"template_id": "image-audiobookshelf" "template_id": "image-audiobookshelf"
}, },
{
"id": "authentik",
"provider": "goauthentik",
"template_family": "curated-profile",
"title": "Authentik",
"repository": "https://github.com/goauthentik/authentik",
"description": "Authentik is an open-source identity provider and access platform. This recipe follows the official three-service deployment with PostgreSQL, the server and a worker, each in its own Proxmox OCI LXC.",
"website": "https://goauthentik.io/",
"icon": "https://cdn.jsdelivr.net/gh/selfhst/icons@main/webp/authentik.webp",
"architectures": [
"amd64",
"arm64"
],
"updated_at": "2026-10-21",
"main_image": "ghcr.io/goauthentik/server:2026.8.5",
"category": "security",
"category_label": "Authentication & Security",
"replaces_discovered_ids": [],
"curated_path": "catalog/curated/authentik.json",
"template_id": "image-authentik",
"template": "apps/authentik.json",
"template_status": "generated-unvalidated",
"hidden": false,
"automatic_install_candidate": true,
"untranslated_blockers": [],
"requires_privileged_lxc": false,
"optional_privileged_lxc": false,
"requires_host_pid_namespace": false,
"requires_relaxed_confinement": false,
"optional_relaxed_confinement": false,
"requires_security_confirmation": false,
"multi_container": true
},
{ {
"id": "autobrr", "id": "autobrr",
"source_app_id": "autobrr", "source_app_id": "autobrr",
@@ -18976,7 +19009,7 @@
"skipped": [] "skipped": []
}, },
"curated_profiles": { "curated_profiles": {
"included_count": 17 "included_count": 18
} }
} }
} }
+9 -3
View File
@@ -655,8 +655,14 @@ def build_stack(template, ui, mode='advanced'):
from .custom_mounts import ask_stack_custom_mounts from .custom_mounts import ask_stack_custom_mounts
ask_stack_custom_mounts(ui, drafts, volumes) ask_stack_custom_mounts(ui, drafts, volumes)
bridge = ask_bridge(ui, translate('Access bridge'), defaults['bridge'], mode) bridge = ask_bridge(ui, translate('Access bridge'), defaults['bridge'], mode)
frontend_names = [s['name'] for s in services if s['name'] == template['compose_stack']['main_service'] or (custom and service_endpoint(s))] frontend_names = [
address_labels = frontend_names if custom else [''] s['name'] for s in services
if (s['name'] == template['compose_stack']['main_service']
or s.get('frontend_network') is True
or (custom and service_endpoint(s)))
]
labelled_frontends = custom or len(frontend_names) > 1
address_labels = frontend_names if labelled_frontends else ['']
addresses, gateway = access.ask_addresses(ui, bridge, address_labels) addresses, gateway = access.ask_addresses(ui, bridge, address_labels)
timezone = quiet.ask(translate('Timezone'), host.timezone()) timezone = quiet.ask(translate('Timezone'), host.timezone())
envs = resolve_environments(services, timezone, generators, literal=literal) envs = resolve_environments(services, timezone, generators, literal=literal)
@@ -749,7 +755,7 @@ def build_stack(template, ui, mode='advanced'):
plans[-1]['public_environment_bindings'] = public_bindings[s['name']] plans[-1]['public_environment_bindings'] = public_bindings[s['name']]
if s['name'] in frontend_names and s['name'] not in jobs: if s['name'] in frontend_names and s['name'] not in jobs:
plans[-1]['frontend'] = True plans[-1]['frontend'] = True
plans[-1]['frontend_ipv4'] = addresses[s['name']] if custom else addresses[''] plans[-1]['frontend_ipv4'] = addresses[s['name']] if labelled_frontends else addresses['']
if mode != DEFAULT_MODE: if mode != DEFAULT_MODE:
from .extra_devices import ask_stack_extra_devices from .extra_devices import ask_stack_extra_devices
ask_stack_extra_devices(ui, plans) ask_stack_extra_devices(ui, plans)
+166
View File
@@ -0,0 +1,166 @@
"""Authentik keeps the vendor's three-service deployment as native OCI LXCs."""
import json
from pathlib import Path
import sys
import unittest
from unittest.mock import patch
ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT / "src"))
from proxmenux_oci.installer import DEFAULT_MODE
from proxmenux_oci.stack import DefaultsUI, build_stack
class AuthentikRecipeTests(unittest.TestCase):
def setUp(self):
self.curated = json.loads(
(ROOT / "catalog/curated/authentik.json").read_text(encoding="utf-8")
)
self.generated = json.loads(
(ROOT / "catalog/apps/authentik.json").read_text(encoding="utf-8")
)
self.services = {
service["name"]: service
for service in self.curated["compose_stack"]["services"]
}
def test_generated_catalog_copy_matches_curated_profile(self):
self.assertEqual(self.curated, self.generated)
def test_official_three_service_stack_uses_pinned_images_and_order(self):
self.assertEqual(set(self.services), {"postgresql", "server", "worker"})
self.assertEqual(self.curated["compose_stack"]["main_service"], "server")
self.assertEqual(
self.services["server"]["image"], "ghcr.io/goauthentik/server:2026.8.5"
)
self.assertEqual(
self.services["worker"]["image"], "ghcr.io/goauthentik/server:2026.8.5"
)
self.assertEqual(
self.services["postgresql"]["image"],
"docker.io/library/postgres:16-alpine",
)
self.assertEqual(self.services["server"]["depends_on"], ["postgresql"])
self.assertEqual(self.services["worker"]["depends_on"], ["postgresql"])
def test_generated_database_and_application_secrets_are_shared_correctly(self):
services = {
name: service["compose"].get("environment", {})
for name, service in self.services.items()
}
self.assertEqual(
services["postgresql"]["POSTGRES_PASSWORD"], "${GENERATED_PG_PASS}"
)
for name in ("server", "worker"):
self.assertEqual(
services[name]["AUTHENTIK_POSTGRESQL__PASSWORD"],
"${GENERATED_PG_PASS}",
)
self.assertEqual(
services[name]["AUTHENTIK_SECRET_KEY"],
"${GENERATED_AUTHENTIK_SECRET_KEY}",
)
generated = self.curated["compose_stack"]["installer_inputs"][
"generated_secrets"
]
self.assertEqual({secret["id"] for secret in generated}, {
"pg-pass",
"authentik-secret-key",
})
def test_persistent_mounts_and_database_healthcheck_are_preserved(self):
mounts = {
name: service["compose"].get("volumes", [])
for name, service in self.services.items()
}
self.assertIn(
{"source": "database", "target": "/var/lib/postgresql/data", "read_only": False},
mounts["postgresql"],
)
self.assertIn(
{"source": "./data", "target": "/data", "read_only": False},
mounts["server"],
)
self.assertIn(
{"source": "./data", "target": "/data", "read_only": False},
mounts["worker"],
)
self.assertIn(
{"source": "./custom-templates", "target": "/templates", "read_only": False},
mounts["server"],
)
self.assertIn(
{"source": "./custom-templates", "target": "/templates", "read_only": False},
mounts["worker"],
)
self.assertIn(
{"source": "./certs", "target": "/certs", "read_only": False},
mounts["worker"],
)
check = self.services["postgresql"]["compose"]["healthcheck"]["test"]
self.assertEqual(
check,
["CMD-SHELL", "pg_isready -h 127.0.0.1 -d authentik -U authentik"],
)
def test_cross_lxc_data_and_templates_are_host_shared_and_backed_up_separately(self):
shared = [
item
for item in self.curated["compose_stack"]["storage"]
if item["container_path"] in ("/data", "/templates")
]
self.assertEqual(len(shared), 4)
self.assertTrue(all(item["mode"] == "host-bind" for item in shared))
self.assertTrue(all(item["shared_with_other_lxc"] for item in shared))
self.assertTrue(all(item["backup"] is False for item in shared))
self.assertTrue(
all(
volume["default"] == "host-bind"
and volume["managed_volume"]["backup"] is False
for volume in self.curated["container_contract"]["volumes"]
)
)
def test_docker_socket_is_not_mounted_and_both_web_ports_are_exposed(self):
all_mounts = [
mount
for service in self.services.values()
for mount in service["compose"].get("volumes", [])
]
self.assertFalse(any("docker.sock" in mount for mount in all_mounts))
self.assertEqual(
self.services["server"]["compose"]["ports"],
["9000:9000", "9443:9443"],
)
def test_worker_gets_lan_interface_while_postgresql_stays_private(self):
selected = {"worker": "192.0.2.21/24", "server": "192.0.2.22/24"}
with patch("proxmenux_oci.installer.ask_storage", return_value="local-zfs"), \
patch("proxmenux_oci.installer.ask_bridge", return_value="vmbr0"), \
patch("proxmenux_oci.host.timezone", return_value="UTC"), \
patch("proxmenux_oci.network.ask_addresses", return_value=(selected, "192.0.2.1")) as ask_addresses:
plan = build_stack(self.curated, DefaultsUI(), DEFAULT_MODE)
services = {service["name"]: service for service in plan["services"]}
self.assertEqual(ask_addresses.call_args.args[2], ["worker", "server"])
self.assertTrue(services["worker"]["frontend"])
self.assertEqual(services["worker"]["frontend_ipv4"], selected["worker"])
self.assertTrue(services["server"]["frontend"])
self.assertEqual(services["server"]["frontend_ipv4"], selected["server"])
self.assertNotIn("frontend", services["postgresql"])
def test_first_run_discloses_the_initial_admin_setup_without_faking_a_password(self):
# akadmin has no password until it is created in the setup wizard,
# which opens from the root address.
first_run = self.curated["first_run"]
self.assertEqual(first_run["endpoints"][0]["port"], 9000)
self.assertEqual(first_run["endpoints"][0]["path"], "/")
self.assertEqual(first_run["credentials"], [])
self.assertIn("akadmin", self.curated["proxmox"]["stack_completion_notes"][0])
if __name__ == "__main__":
unittest.main()