diff --git a/oci/catalog/apps/authentik.json b/oci/catalog/apps/authentik.json index d466d36f..57f2dcca 100644 --- a/oci/catalog/apps/authentik.json +++ b/oci/catalog/apps/authentik.json @@ -34,7 +34,8 @@ "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 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", @@ -213,7 +214,7 @@ "depends_on": [ "postgresql" ], - "frontend_network": false, + "frontend_network": true, "private_network": true, "compose": { "command": "worker", @@ -311,7 +312,7 @@ "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": "disabled-unless-service-publishes-ports", + "dependency_external_access": "worker-through-selected-proxmox-bridge; postgresql-private-only", "prompt_user_for_private_network": false }, "storage": [ diff --git a/oci/catalog/curated/authentik.json b/oci/catalog/curated/authentik.json index d466d36f..57f2dcca 100644 --- a/oci/catalog/curated/authentik.json +++ b/oci/catalog/curated/authentik.json @@ -34,7 +34,8 @@ "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 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", @@ -213,7 +214,7 @@ "depends_on": [ "postgresql" ], - "frontend_network": false, + "frontend_network": true, "private_network": true, "compose": { "command": "worker", @@ -311,7 +312,7 @@ "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": "disabled-unless-service-publishes-ports", + "dependency_external_access": "worker-through-selected-proxmox-bridge; postgresql-private-only", "prompt_user_for_private_network": false }, "storage": [ diff --git a/oci/src/proxmenux_oci/stack.py b/oci/src/proxmenux_oci/stack.py index 1c383b44..11e196f2 100644 --- a/oci/src/proxmenux_oci/stack.py +++ b/oci/src/proxmenux_oci/stack.py @@ -655,8 +655,14 @@ def build_stack(template, ui, mode='advanced'): from .custom_mounts import ask_stack_custom_mounts ask_stack_custom_mounts(ui, drafts, volumes) 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))] - address_labels = frontend_names if custom else [''] + frontend_names = [ + 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) timezone = quiet.ask(translate('Timezone'), host.timezone()) 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']] if s['name'] in frontend_names and s['name'] not in jobs: 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: from .extra_devices import ask_stack_extra_devices ask_stack_extra_devices(ui, plans) diff --git a/oci/tests/test_authentik_recipe.py b/oci/tests/test_authentik_recipe.py index 15102b1b..c829b708 100644 --- a/oci/tests/test_authentik_recipe.py +++ b/oci/tests/test_authentik_recipe.py @@ -2,10 +2,16 @@ 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): @@ -130,6 +136,22 @@ class AuthentikRecipeTests(unittest.TestCase): ["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): first_run = self.curated["first_run"] self.assertEqual(first_run["endpoints"][0]["port"], 9000)