root: Better version bump (#14905)

Co-authored-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
This commit is contained in:
Jens L.
2025-08-12 14:50:12 +01:00
committed by GitHub
parent 34bab28985
commit a38239509b
58 changed files with 465 additions and 432 deletions

View File

@@ -1,36 +0,0 @@
[bumpversion]
current_version = 2025.6.4
tag = True
commit = True
parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)(?:-(?P<rc_t>[a-zA-Z-]+)(?P<rc_n>[1-9]\\d*))?
serialize =
{major}.{minor}.{patch}-{rc_t}{rc_n}
{major}.{minor}.{patch}
message = release: {new_version}
tag_name = version/{new_version}
[bumpversion:part:rc_t]
values =
rc
final
optional_value = final
[bumpversion:file:pyproject.toml]
[bumpversion:file:uv.lock]
[bumpversion:file:package.json]
[bumpversion:file:package-lock.json]
[bumpversion:file:docker-compose.yml]
[bumpversion:file:schema.yml]
[bumpversion:file:blueprints/schema.json]
[bumpversion:file:authentik/__init__.py]
[bumpversion:file:internal/constants/constants.go]
[bumpversion:file:lifecycle/aws/template.yaml]

View File

@@ -1,5 +1,6 @@
htmlcov htmlcov
*.env.yml *.env.yml
node_modules
**/node_modules **/node_modules
dist/** dist/**
build/** build/**

View File

@@ -54,6 +54,10 @@ outputs:
runs: runs:
using: "composite" using: "composite"
steps: steps:
- name: Setup authentik env
uses: ./.github/actions/setup
with:
dependencies: "python"
- name: Generate config - name: Generate config
id: ev id: ev
shell: bash shell: bash
@@ -64,4 +68,4 @@ runs:
PR_HEAD_SHA: ${{ github.event.pull_request.head.sha }} PR_HEAD_SHA: ${{ github.event.pull_request.head.sha }}
REF: ${{ github.ref }} REF: ${{ github.ref }}
run: | run: |
python3 ${{ github.action_path }}/push_vars.py uv run python3 ${{ github.action_path }}/push_vars.py

View File

@@ -1,13 +1,10 @@
"""Helper script to get the actual branch name, docker safe""" """Helper script to get the actual branch name, docker safe"""
import configparser
import os import os
from importlib.metadata import version as package_version
from json import dumps from json import dumps
from time import time from time import time
parser = configparser.ConfigParser()
parser.read(".bumpversion.cfg")
# Decide if we should push the image or not # Decide if we should push the image or not
should_push = True should_push = True
if len(os.environ.get("DOCKER_USERNAME", "")) < 1: if len(os.environ.get("DOCKER_USERNAME", "")) < 1:
@@ -31,7 +28,7 @@ is_release = "dev" not in image_names[0]
sha = os.environ["GITHUB_SHA"] if not is_pull_request else os.getenv("PR_HEAD_SHA") sha = os.environ["GITHUB_SHA"] if not is_pull_request else os.getenv("PR_HEAD_SHA")
# 2042.1.0 or 2042.1.0-rc1 # 2042.1.0 or 2042.1.0-rc1
version = parser.get("bumpversion", "current_version") version = package_version("authentik")
# 2042.1 # 2042.1
version_family = ".".join(version.split("-", 1)[0].split(".")[:-1]) version_family = ".".join(version.split("-", 1)[0].split(".")[:-1])
prerelease = "-" in version prerelease = "-" in version

View File

@@ -2,6 +2,9 @@ name: "Setup authentik testing environment"
description: "Setup authentik testing environment" description: "Setup authentik testing environment"
inputs: inputs:
dependencies:
description: "List of dependencies to setup"
default: "system,python,node,go,runtime"
postgresql_version: postgresql_version:
description: "Optional postgresql image tag" description: "Optional postgresql image tag"
default: "16" default: "16"
@@ -10,42 +13,51 @@ runs:
using: "composite" using: "composite"
steps: steps:
- name: Install apt deps - name: Install apt deps
if: ${{ contains(inputs.dependencies, 'system') || contains(inputs.dependencies, 'python') }}
shell: bash shell: bash
run: | run: |
sudo apt-get update sudo apt-get update
sudo apt-get install --no-install-recommends -y libpq-dev openssl libxmlsec1-dev pkg-config gettext libkrb5-dev krb5-kdc krb5-user krb5-admin-server sudo apt-get install --no-install-recommends -y libpq-dev openssl libxmlsec1-dev pkg-config gettext libkrb5-dev krb5-kdc krb5-user krb5-admin-server
- name: Install uv - name: Install uv
if: ${{ contains(inputs.dependencies, 'python') }}
uses: astral-sh/setup-uv@v5 uses: astral-sh/setup-uv@v5
with: with:
enable-cache: true enable-cache: true
- name: Setup python - name: Setup python
if: ${{ contains(inputs.dependencies, 'python') }}
uses: actions/setup-python@v5 uses: actions/setup-python@v5
with: with:
python-version-file: "pyproject.toml" python-version-file: "pyproject.toml"
- name: Install Python deps - name: Install Python deps
if: ${{ contains(inputs.dependencies, 'python') }}
shell: bash shell: bash
run: uv sync --all-extras --dev --frozen run: uv sync --all-extras --dev --frozen
- name: Setup node - name: Setup node
if: ${{ contains(inputs.dependencies, 'node') }}
uses: actions/setup-node@v4 uses: actions/setup-node@v4
with: with:
node-version-file: web/package.json node-version-file: web/package.json
cache: "npm" cache: "npm"
cache-dependency-path: web/package-lock.json cache-dependency-path: web/package-lock.json
- name: Setup go - name: Setup go
if: ${{ contains(inputs.dependencies, 'go') }}
uses: actions/setup-go@v5 uses: actions/setup-go@v5
with: with:
go-version-file: "go.mod" go-version-file: "go.mod"
- name: Setup docker cache - name: Setup docker cache
if: ${{ contains(inputs.dependencies, 'runtime') }}
uses: AndreKurait/docker-cache@0fe76702a40db986d9663c24954fc14c6a6031b7 uses: AndreKurait/docker-cache@0fe76702a40db986d9663c24954fc14c6a6031b7
with: with:
key: docker-images-${{ runner.os }}-${{ hashFiles('.github/actions/setup/docker-compose.yml', 'Makefile') }}-${{ inputs.postgresql_version }} key: docker-images-${{ runner.os }}-${{ hashFiles('.github/actions/setup/docker-compose.yml', 'Makefile') }}-${{ inputs.postgresql_version }}
- name: Setup dependencies - name: Setup dependencies
if: ${{ contains(inputs.dependencies, 'runtime') }}
shell: bash shell: bash
run: | run: |
export PSQL_TAG=${{ inputs.postgresql_version }} export PSQL_TAG=${{ inputs.postgresql_version }}
docker compose -f .github/actions/setup/docker-compose.yml up -d docker compose -f .github/actions/setup/docker-compose.yml up -d
cd web && npm ci cd web && npm ci
- name: Generate config - name: Generate config
if: ${{ contains(inputs.dependencies, 'python') }}
shell: uv run python {0} shell: uv run python {0}
run: | run: |
from authentik.lib.generators import generate_id from authentik.lib.generators import generate_id

View File

@@ -16,6 +16,7 @@ GEN_API_GO = gen-go-api
pg_user := $(shell uv run python -m authentik.lib.config postgresql.user 2>/dev/null) pg_user := $(shell uv run python -m authentik.lib.config postgresql.user 2>/dev/null)
pg_host := $(shell uv run python -m authentik.lib.config postgresql.host 2>/dev/null) pg_host := $(shell uv run python -m authentik.lib.config postgresql.host 2>/dev/null)
pg_name := $(shell uv run python -m authentik.lib.config postgresql.name 2>/dev/null) pg_name := $(shell uv run python -m authentik.lib.config postgresql.name 2>/dev/null)
redis_db := $(shell uv run python -m authentik.lib.config redis.db 2>/dev/null)
all: lint-fix lint test gen web ## Lint, build, and test everything all: lint-fix lint test gen web ## Lint, build, and test everything
@@ -57,7 +58,7 @@ migrate: ## Run the Authentik Django server's migrations
i18n-extract: core-i18n-extract web-i18n-extract ## Extract strings that require translation into files to send to a translation service i18n-extract: core-i18n-extract web-i18n-extract ## Extract strings that require translation into files to send to a translation service
aws-cfn: aws-cfn:
cd lifecycle/aws && npm run aws-cfn cd lifecycle/aws && npm i && npm run aws-cfn
run-server: ## Run the main authentik server process run-server: ## Run the main authentik server process
uv run ak server uv run ak server
@@ -79,10 +80,10 @@ core-i18n-extract:
install: node-install docs-install core-install ## Install all requires dependencies for `node`, `docs` and `core` install: node-install docs-install core-install ## Install all requires dependencies for `node`, `docs` and `core`
dev-drop-db: dev-drop-db:
dropdb -U ${pg_user} -h ${pg_host} ${pg_name} dropdb -U ${pg_user} -h ${pg_host} ${pg_name} || true
# Also remove the test-db if it exists # Also remove the test-db if it exists
dropdb -U ${pg_user} -h ${pg_host} test_${pg_name} || true dropdb -U ${pg_user} -h ${pg_host} test_${pg_name} || true
redis-cli -n 0 flushall redis-cli -n ${redis_db} flushall
dev-create-db: dev-create-db:
createdb -U ${pg_user} -h ${pg_host} ${pg_name} createdb -U ${pg_user} -h ${pg_host} ${pg_name}
@@ -93,6 +94,16 @@ update-test-mmdb: ## Update test GeoIP and ASN Databases
curl -L https://raw.githubusercontent.com/maxmind/MaxMind-DB/refs/heads/main/test-data/GeoLite2-ASN-Test.mmdb -o ${PWD}/tests/GeoLite2-ASN-Test.mmdb curl -L https://raw.githubusercontent.com/maxmind/MaxMind-DB/refs/heads/main/test-data/GeoLite2-ASN-Test.mmdb -o ${PWD}/tests/GeoLite2-ASN-Test.mmdb
curl -L https://raw.githubusercontent.com/maxmind/MaxMind-DB/refs/heads/main/test-data/GeoLite2-City-Test.mmdb -o ${PWD}/tests/GeoLite2-City-Test.mmdb curl -L https://raw.githubusercontent.com/maxmind/MaxMind-DB/refs/heads/main/test-data/GeoLite2-City-Test.mmdb -o ${PWD}/tests/GeoLite2-City-Test.mmdb
bump: ## Bump authentik version. Usage: make bump version=20xx.xx.xx
ifndef version
$(error Usage: make bump version=20xx.xx.xx )
endif
uv version $(version)
$(MAKE) gen-build gen-compose aws-cfn
npm version --no-git-tag-version --allow-same-version $(version)
cd ${PWD}/web && npm version --no-git-tag-version --allow-same-version $(version)
echo -n $(version) > ${PWD}/internal/constants/VERSION
######################### #########################
## API Schema ## API Schema
######################### #########################
@@ -107,6 +118,9 @@ gen-build: ## Extract the schema from the database
AUTHENTIK_OUTPOSTS__DISABLE_EMBEDDED_OUTPOST=true \ AUTHENTIK_OUTPOSTS__DISABLE_EMBEDDED_OUTPOST=true \
uv run ak spectacular --file schema.yml uv run ak spectacular --file schema.yml
gen-compose:
uv run scripts/generate_docker_compose.py
gen-changelog: ## (Release) generate the changelog based from the commits since the last tag gen-changelog: ## (Release) generate the changelog based from the commits since the last tag
git log --pretty=format:" - %s" $(shell git describe --tags $(shell git rev-list --tags --max-count=1))...$(shell git branch --show-current) | sort > changelog.md git log --pretty=format:" - %s" $(shell git describe --tags $(shell git rev-list --tags --max-count=1))...$(shell git branch --show-current) | sort > changelog.md
npx prettier --write changelog.md npx prettier --write changelog.md

View File

@@ -1,20 +1,28 @@
"""authentik root module""" """authentik root module"""
from functools import lru_cache
from importlib.metadata import version
from os import environ from os import environ
__version__ = "2025.6.4"
ENV_GIT_HASH_KEY = "GIT_BUILD_HASH" ENV_GIT_HASH_KEY = "GIT_BUILD_HASH"
def get_build_hash(fallback: str | None = None) -> str: @lru_cache
def authentik_version() -> str:
return version("authentik")
@lru_cache
def authentik_build_hash(fallback: str | None = None) -> str:
"""Get build hash""" """Get build hash"""
build_hash = environ.get(ENV_GIT_HASH_KEY, fallback if fallback else "") build_hash = environ.get(ENV_GIT_HASH_KEY, fallback if fallback else "")
return fallback if build_hash == "" and fallback else build_hash return fallback if build_hash == "" and fallback else build_hash
def get_full_version() -> str: @lru_cache
def authentik_full_version() -> str:
"""Get full version, with build hash appended""" """Get full version, with build hash appended"""
version = __version__ version = authentik_version()
if (build_hash := get_build_hash()) != "": if (build_hash := authentik_build_hash()) != "":
return f"{version}+{build_hash}" return f"{version}+{build_hash}"
return version return version

View File

@@ -16,7 +16,7 @@ from rest_framework.request import Request
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework.views import APIView from rest_framework.views import APIView
from authentik import get_full_version from authentik import authentik_full_version
from authentik.core.api.utils import PassiveSerializer from authentik.core.api.utils import PassiveSerializer
from authentik.enterprise.license import LicenseKey from authentik.enterprise.license import LicenseKey
from authentik.lib.config import CONFIG from authentik.lib.config import CONFIG
@@ -78,7 +78,7 @@ class SystemInfoSerializer(PassiveSerializer):
"""Get versions""" """Get versions"""
return { return {
"architecture": platform.machine(), "architecture": platform.machine(),
"authentik_version": get_full_version(), "authentik_version": authentik_full_version(),
"environment": get_env(), "environment": get_env(),
"openssl_fips_enabled": ( "openssl_fips_enabled": (
backend._fips_enabled if LicenseKey.get_total().status().is_valid else None backend._fips_enabled if LicenseKey.get_total().status().is_valid else None

View File

@@ -10,7 +10,7 @@ from rest_framework.request import Request
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework.views import APIView from rest_framework.views import APIView
from authentik import __version__, get_build_hash from authentik import authentik_build_hash, authentik_version
from authentik.admin.tasks import VERSION_CACHE_KEY, VERSION_NULL, update_latest_version from authentik.admin.tasks import VERSION_CACHE_KEY, VERSION_NULL, update_latest_version
from authentik.core.api.utils import PassiveSerializer from authentik.core.api.utils import PassiveSerializer
from authentik.outposts.models import Outpost from authentik.outposts.models import Outpost
@@ -29,20 +29,20 @@ class VersionSerializer(PassiveSerializer):
def get_build_hash(self, _) -> str: def get_build_hash(self, _) -> str:
"""Get build hash, if version is not latest or released""" """Get build hash, if version is not latest or released"""
return get_build_hash() return authentik_build_hash()
def get_version_current(self, _) -> str: def get_version_current(self, _) -> str:
"""Get current version""" """Get current version"""
return __version__ return authentik_version()
def get_version_latest(self, _) -> str: def get_version_latest(self, _) -> str:
"""Get latest version from cache""" """Get latest version from cache"""
if get_current_tenant().schema_name == get_public_schema_name(): if get_current_tenant().schema_name == get_public_schema_name():
return __version__ return authentik_version()
version_in_cache = cache.get(VERSION_CACHE_KEY) version_in_cache = cache.get(VERSION_CACHE_KEY)
if not version_in_cache: # pragma: no cover if not version_in_cache: # pragma: no cover
update_latest_version.send() update_latest_version.send()
return __version__ return authentik_version()
return version_in_cache return version_in_cache
def get_version_latest_valid(self, _) -> bool: def get_version_latest_valid(self, _) -> bool:

View File

@@ -8,7 +8,7 @@ from packaging.version import parse
from requests import RequestException from requests import RequestException
from structlog.stdlib import get_logger from structlog.stdlib import get_logger
from authentik import __version__, get_build_hash from authentik import authentik_build_hash, authentik_version
from authentik.admin.apps import PROM_INFO from authentik.admin.apps import PROM_INFO
from authentik.events.models import Event, EventAction from authentik.events.models import Event, EventAction
from authentik.lib.config import CONFIG from authentik.lib.config import CONFIG
@@ -19,16 +19,16 @@ LOGGER = get_logger()
VERSION_NULL = "0.0.0" VERSION_NULL = "0.0.0"
VERSION_CACHE_KEY = "authentik_latest_version" VERSION_CACHE_KEY = "authentik_latest_version"
VERSION_CACHE_TIMEOUT = 8 * 60 * 60 # 8 hours VERSION_CACHE_TIMEOUT = 8 * 60 * 60 # 8 hours
LOCAL_VERSION = parse(__version__) LOCAL_VERSION = parse(authentik_version())
def _set_prom_info(): def _set_prom_info():
"""Set prometheus info for version""" """Set prometheus info for version"""
PROM_INFO.info( PROM_INFO.info(
{ {
"version": __version__, "version": authentik_version(),
"latest": cache.get(VERSION_CACHE_KEY, ""), "latest": cache.get(VERSION_CACHE_KEY, ""),
"build_hash": get_build_hash(), "build_hash": authentik_build_hash(),
} }
) )

View File

@@ -5,7 +5,7 @@ from json import loads
from django.test import TestCase from django.test import TestCase
from django.urls import reverse from django.urls import reverse
from authentik import __version__ from authentik import authentik_version
from authentik.blueprints.tests import reconcile_app from authentik.blueprints.tests import reconcile_app
from authentik.core.models import Group, User from authentik.core.models import Group, User
from authentik.lib.generators import generate_id from authentik.lib.generators import generate_id
@@ -27,7 +27,7 @@ class TestAdminAPI(TestCase):
response = self.client.get(reverse("authentik_api:admin_version")) response = self.client.get(reverse("authentik_api:admin_version"))
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
body = loads(response.content) body = loads(response.content)
self.assertEqual(body["version_current"], __version__) self.assertEqual(body["version_current"], authentik_version())
def test_apps(self): def test_apps(self):
"""Test apps API""" """Test apps API"""

View File

@@ -11,7 +11,7 @@ from rest_framework.relations import PrimaryKeyRelatedField
from rest_framework.serializers import Serializer from rest_framework.serializers import Serializer
from structlog.stdlib import get_logger from structlog.stdlib import get_logger
from authentik import __version__ from authentik import authentik_version
from authentik.blueprints.v1.common import BlueprintEntryDesiredState from authentik.blueprints.v1.common import BlueprintEntryDesiredState
from authentik.blueprints.v1.importer import SERIALIZER_CONTEXT_BLUEPRINT, is_model_allowed from authentik.blueprints.v1.importer import SERIALIZER_CONTEXT_BLUEPRINT, is_model_allowed
from authentik.blueprints.v1.meta.registry import BaseMetaModel, registry from authentik.blueprints.v1.meta.registry import BaseMetaModel, registry
@@ -48,7 +48,7 @@ class Command(BaseCommand):
"$schema": "http://json-schema.org/draft-07/schema", "$schema": "http://json-schema.org/draft-07/schema",
"$id": "https://goauthentik.io/blueprints/schema.json", "$id": "https://goauthentik.io/blueprints/schema.json",
"type": "object", "type": "object",
"title": f"authentik {__version__} Blueprint schema", "title": f"authentik {authentik_version()} Blueprint schema",
"required": ["version", "entries"], "required": ["version", "entries"],
"properties": { "properties": {
"version": { "version": {

View File

@@ -9,7 +9,7 @@ from django.http.request import HttpRequest
from django.utils.html import _json_script_escapes from django.utils.html import _json_script_escapes
from django.utils.safestring import mark_safe from django.utils.safestring import mark_safe
from authentik import get_full_version from authentik import authentik_full_version
from authentik.brands.models import Brand from authentik.brands.models import Brand
from authentik.lib.sentry import get_http_meta from authentik.lib.sentry import get_http_meta
from authentik.tenants.models import Tenant from authentik.tenants.models import Tenant
@@ -44,5 +44,5 @@ def context_processor(request: HttpRequest) -> dict[str, Any]:
"brand_css": brand_css, "brand_css": brand_css,
"footer_links": tenant.footer_links, "footer_links": tenant.footer_links,
"html_meta": {**get_http_meta()}, "html_meta": {**get_http_meta()},
"version": get_full_version(), "version": authentik_full_version(),
} }

View File

@@ -11,7 +11,7 @@ from django.core.management.base import BaseCommand
from django.db.models import Model from django.db.models import Model
from django.db.models.signals import post_save, pre_delete from django.db.models.signals import post_save, pre_delete
from authentik import get_full_version from authentik import authentik_full_version
from authentik.core.models import User from authentik.core.models import User
from authentik.events.middleware import should_log_model from authentik.events.middleware import should_log_model
from authentik.events.models import Event, EventAction from authentik.events.models import Event, EventAction
@@ -19,7 +19,7 @@ from authentik.events.utils import model_to_dict
def get_banner_text(shell_type="shell") -> str: def get_banner_text(shell_type="shell") -> str:
return f"""### authentik {shell_type} ({get_full_version()}) return f"""### authentik {shell_type} ({authentik_full_version()})
### Node {platform.node()} | Arch {platform.machine()} | Python {platform.python_version()} """ ### Node {platform.node()} | Arch {platform.machine()} | Python {platform.python_version()} """

View File

@@ -3,7 +3,7 @@
from django import template from django import template
from django.templatetags.static import static as static_loader from django.templatetags.static import static as static_loader
from authentik import get_full_version from authentik import authentik_full_version
register = template.Library() register = template.Library()
@@ -11,4 +11,4 @@ register = template.Library()
@register.simple_tag() @register.simple_tag()
def versioned_script(path: str) -> str: def versioned_script(path: str) -> str:
"""Wrapper around {% static %} tag that supports setting the version""" """Wrapper around {% static %} tag that supports setting the version"""
return static_loader(path.replace("%v", get_full_version())) return static_loader(path.replace("%v", authentik_full_version()))

View File

@@ -10,7 +10,7 @@ from django.utils.translation import gettext as _
from django.views.generic.base import RedirectView, TemplateView from django.views.generic.base import RedirectView, TemplateView
from rest_framework.request import Request from rest_framework.request import Request
from authentik import get_build_hash from authentik import authentik_build_hash
from authentik.admin.tasks import LOCAL_VERSION from authentik.admin.tasks import LOCAL_VERSION
from authentik.api.v3.config import ConfigView from authentik.api.v3.config import ConfigView
from authentik.brands.api import CurrentBrandSerializer from authentik.brands.api import CurrentBrandSerializer
@@ -52,7 +52,7 @@ class InterfaceView(TemplateView):
kwargs["brand_json"] = dumps(brand.data) kwargs["brand_json"] = dumps(brand.data)
kwargs["version_family"] = f"{LOCAL_VERSION.major}.{LOCAL_VERSION.minor}" kwargs["version_family"] = f"{LOCAL_VERSION.major}.{LOCAL_VERSION.minor}"
kwargs["version_subdomain"] = f"version-{LOCAL_VERSION.major}-{LOCAL_VERSION.minor}" kwargs["version_subdomain"] = f"version-{LOCAL_VERSION.major}-{LOCAL_VERSION.minor}"
kwargs["build"] = get_build_hash() kwargs["build"] = authentik_build_hash()
kwargs["url_kwargs"] = self.kwargs kwargs["url_kwargs"] = self.kwargs
kwargs["base_url"] = self.request.build_absolute_uri(CONFIG.get("web.path", "/")) kwargs["base_url"] = self.request.build_absolute_uri(CONFIG.get("web.path", "/"))
kwargs["base_url_rel"] = CONFIG.get("web.path", "/") kwargs["base_url_rel"] = CONFIG.get("web.path", "/")

View File

@@ -12,7 +12,7 @@ from cryptography.x509.oid import NameOID
from django.db import models from django.db import models
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from authentik import __version__ from authentik import authentik_version
from authentik.crypto.models import CertificateKeyPair from authentik.crypto.models import CertificateKeyPair
@@ -85,7 +85,7 @@ class CertificateBuilder:
.issuer_name( .issuer_name(
x509.Name( x509.Name(
[ [
x509.NameAttribute(NameOID.COMMON_NAME, f"authentik {__version__}"), x509.NameAttribute(NameOID.COMMON_NAME, f"authentik {authentik_version()}"),
] ]
) )
) )

View File

@@ -18,7 +18,7 @@ from requests import RequestException
from rest_framework.serializers import Serializer from rest_framework.serializers import Serializer
from structlog.stdlib import get_logger from structlog.stdlib import get_logger
from authentik import get_full_version from authentik import authentik_full_version
from authentik.brands.models import Brand from authentik.brands.models import Brand
from authentik.brands.utils import DEFAULT_BRAND from authentik.brands.utils import DEFAULT_BRAND
from authentik.core.middleware import ( from authentik.core.middleware import (
@@ -434,7 +434,7 @@ class NotificationTransport(TasksModel, SerializerModel):
"title": notification.body, "title": notification.body,
"color": "#fd4b2d", "color": "#fd4b2d",
"fields": fields, "fields": fields,
"footer": f"authentik {get_full_version()}", "footer": f"authentik {authentik_full_version()}",
} }
], ],
} }

View File

@@ -7,7 +7,7 @@ from django.core.mail.backends.locmem import EmailBackend
from django.test import TestCase from django.test import TestCase
from requests_mock import Mocker from requests_mock import Mocker
from authentik import get_full_version from authentik import authentik_full_version
from authentik.core.tests.utils import create_test_admin_user from authentik.core.tests.utils import create_test_admin_user
from authentik.events.models import ( from authentik.events.models import (
Event, Event,
@@ -118,7 +118,7 @@ class TestEventTransports(TestCase):
{"short": True, "title": "Event user", "value": self.user.username}, {"short": True, "title": "Event user", "value": self.user.username},
{"title": "foo", "value": "bar,"}, {"title": "foo", "value": "bar,"},
], ],
"footer": f"authentik {get_full_version()}", "footer": f"authentik {authentik_full_version()}",
} }
], ],
}, },

View File

@@ -10,7 +10,7 @@ from django.core.management.base import BaseCommand
from django.test import RequestFactory from django.test import RequestFactory
from structlog.stdlib import get_logger from structlog.stdlib import get_logger
from authentik import __version__ from authentik import authentik_version
from authentik.core.tests.utils import create_test_admin_user from authentik.core.tests.utils import create_test_admin_user
from authentik.flows.models import Flow from authentik.flows.models import Flow
from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER, FlowPlanner from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER, FlowPlanner
@@ -99,7 +99,7 @@ class Command(BaseCommand):
total_min: int = min(min(inner) for inner in values) total_min: int = min(min(inner) for inner in values)
total_avg = sum(sum(inner) for inner in values) / sum(len(inner) for inner in values) total_avg = sum(sum(inner) for inner in values) / sum(len(inner) for inner in values)
print(f"Version: {__version__}") print(f"Version: {authentik_version()}")
print(f"Processes: {len(values)}") print(f"Processes: {len(values)}")
print(f"\tMax: {total_max * 100}ms") print(f"\tMax: {total_max * 100}ms")
print(f"\tMin: {total_min * 100}ms") print(f"\tMin: {total_min * 100}ms")

View File

@@ -30,7 +30,7 @@ from sentry_sdk.tracing import BAGGAGE_HEADER_NAME, SENTRY_TRACE_HEADER_NAME
from structlog.stdlib import get_logger from structlog.stdlib import get_logger
from websockets.exceptions import WebSocketException from websockets.exceptions import WebSocketException
from authentik import __version__, get_build_hash from authentik import authentik_build_hash, authentik_version
from authentik.lib.config import CONFIG from authentik.lib.config import CONFIG
from authentik.lib.utils.http import authentik_user_agent from authentik.lib.utils.http import authentik_user_agent
from authentik.lib.utils.reflection import get_env from authentik.lib.utils.reflection import get_env
@@ -117,11 +117,11 @@ def sentry_init(**sentry_init_kwargs):
], ],
before_send=before_send, before_send=before_send,
traces_sampler=traces_sampler, traces_sampler=traces_sampler,
release=f"authentik@{__version__}", release=f"authentik@{authentik_version()}",
transport=SentryTransport, transport=SentryTransport,
**kwargs, **kwargs,
) )
set_tag("authentik.build_hash", get_build_hash("tagged")) set_tag("authentik.build_hash", authentik_build_hash("tagged"))
set_tag("authentik.env", get_env()) set_tag("authentik.env", get_env())
set_tag("authentik.component", "backend") set_tag("authentik.component", "backend")

View File

@@ -5,7 +5,7 @@ from uuid import uuid4
from requests.sessions import PreparedRequest, Session from requests.sessions import PreparedRequest, Session
from structlog.stdlib import get_logger from structlog.stdlib import get_logger
from authentik import get_full_version from authentik import authentik_full_version
from authentik.lib.config import CONFIG from authentik.lib.config import CONFIG
LOGGER = get_logger() LOGGER = get_logger()
@@ -13,7 +13,7 @@ LOGGER = get_logger()
def authentik_user_agent() -> str: def authentik_user_agent() -> str:
"""Get a common user agent""" """Get a common user agent"""
return f"authentik@{get_full_version()}" return f"authentik@{authentik_full_version()}"
class TimeoutSession(Session): class TimeoutSession(Session):

View File

@@ -13,7 +13,7 @@ from rest_framework.request import Request
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework.viewsets import ModelViewSet from rest_framework.viewsets import ModelViewSet
from authentik import get_build_hash from authentik import authentik_build_hash
from authentik.core.api.providers import ProviderSerializer from authentik.core.api.providers import ProviderSerializer
from authentik.core.api.used_by import UsedByMixin from authentik.core.api.used_by import UsedByMixin
from authentik.core.api.utils import JSONDictField, ModelSerializer, PassiveSerializer from authentik.core.api.utils import JSONDictField, ModelSerializer, PassiveSerializer
@@ -194,7 +194,7 @@ class OutpostViewSet(UsedByMixin, ModelViewSet):
"openssl_version": state.openssl_version, "openssl_version": state.openssl_version,
"fips_enabled": state.fips_enabled, "fips_enabled": state.fips_enabled,
"hostname": state.hostname, "hostname": state.hostname,
"build_hash_should": get_build_hash(), "build_hash_should": authentik_build_hash(),
} }
) )
return Response(OutpostHealthSerializer(states, many=True).data) return Response(OutpostHealthSerializer(states, many=True).data)

View File

@@ -4,7 +4,7 @@ from dataclasses import dataclass
from structlog.stdlib import get_logger from structlog.stdlib import get_logger
from authentik import __version__, get_build_hash from authentik import authentik_build_hash, authentik_version
from authentik.events.logs import LogEvent, capture_logs from authentik.events.logs import LogEvent, capture_logs
from authentik.lib.config import CONFIG from authentik.lib.config import CONFIG
from authentik.lib.sentry import SentryIgnoredException from authentik.lib.sentry import SentryIgnoredException
@@ -99,6 +99,6 @@ class BaseController:
image_name_template: str = CONFIG.get("outposts.container_image_base") image_name_template: str = CONFIG.get("outposts.container_image_base")
return image_name_template % { return image_name_template % {
"type": self.outpost.type, "type": self.outpost.type,
"version": __version__, "version": authentik_version(),
"build_hash": get_build_hash(), "build_hash": authentik_build_hash(),
} }

View File

@@ -13,7 +13,7 @@ from paramiko.ssh_exception import SSHException
from structlog.stdlib import get_logger from structlog.stdlib import get_logger
from yaml import safe_dump from yaml import safe_dump
from authentik import __version__ from authentik import authentik_version
from authentik.outposts.apps import MANAGED_OUTPOST from authentik.outposts.apps import MANAGED_OUTPOST
from authentik.outposts.controllers.base import BaseClient, BaseController, ControllerException from authentik.outposts.controllers.base import BaseClient, BaseController, ControllerException
from authentik.outposts.docker_ssh import DockerInlineSSH, SSHManagedExternallyException from authentik.outposts.docker_ssh import DockerInlineSSH, SSHManagedExternallyException
@@ -185,7 +185,7 @@ class DockerController(BaseController):
try: try:
self.client.images.pull(image) self.client.images.pull(image)
except DockerException: # pragma: no cover except DockerException: # pragma: no cover
image = f"ghcr.io/goauthentik/{self.outpost.type}:{__version__}" image = f"ghcr.io/goauthentik/{self.outpost.type}:{authentik_version()}"
self.client.images.pull(image) self.client.images.pull(image)
return image return image

View File

@@ -17,7 +17,7 @@ from requests import Response
from structlog.stdlib import get_logger from structlog.stdlib import get_logger
from urllib3.exceptions import HTTPError from urllib3.exceptions import HTTPError
from authentik import __version__ from authentik import authentik_version
from authentik.outposts.apps import MANAGED_OUTPOST from authentik.outposts.apps import MANAGED_OUTPOST
from authentik.outposts.controllers.base import ControllerException from authentik.outposts.controllers.base import ControllerException
from authentik.outposts.controllers.k8s.triggers import NeedsRecreate, NeedsUpdate from authentik.outposts.controllers.k8s.triggers import NeedsRecreate, NeedsUpdate
@@ -29,8 +29,8 @@ T = TypeVar("T", V1Pod, V1Deployment)
def get_version() -> str: def get_version() -> str:
"""Wrapper for __version__ to make testing easier""" """Wrapper for authentik_version() to make testing easier"""
return __version__ return authentik_version()
class KubernetesObjectReconciler(Generic[T]): class KubernetesObjectReconciler(Generic[T]):

View File

@@ -23,7 +23,7 @@ from kubernetes.client import (
V1SecurityContext, V1SecurityContext,
) )
from authentik import get_full_version from authentik import authentik_full_version
from authentik.outposts.controllers.base import FIELD_MANAGER from authentik.outposts.controllers.base import FIELD_MANAGER
from authentik.outposts.controllers.k8s.base import KubernetesObjectReconciler from authentik.outposts.controllers.k8s.base import KubernetesObjectReconciler
from authentik.outposts.controllers.k8s.triggers import NeedsUpdate from authentik.outposts.controllers.k8s.triggers import NeedsUpdate
@@ -94,7 +94,7 @@ class DeploymentReconciler(KubernetesObjectReconciler[V1Deployment]):
meta = self.get_object_meta(name=self.name) meta = self.get_object_meta(name=self.name)
image_name = self.controller.get_container_image() image_name = self.controller.get_container_image()
image_pull_secrets = self.outpost.config.kubernetes_image_pull_secrets image_pull_secrets = self.outpost.config.kubernetes_image_pull_secrets
version = get_full_version().replace("+", "-") version = authentik_full_version().replace("+", "-")
return V1Deployment( return V1Deployment(
metadata=meta, metadata=meta,
spec=V1DeploymentSpec( spec=V1DeploymentSpec(

View File

@@ -19,7 +19,7 @@ from packaging.version import Version, parse
from rest_framework.serializers import Serializer from rest_framework.serializers import Serializer
from structlog.stdlib import get_logger from structlog.stdlib import get_logger
from authentik import __version__, get_build_hash from authentik import authentik_build_hash, authentik_version
from authentik.blueprints.models import ManagedModel from authentik.blueprints.models import ManagedModel
from authentik.brands.models import Brand from authentik.brands.models import Brand
from authentik.core.models import ( from authentik.core.models import (
@@ -40,7 +40,7 @@ from authentik.outposts.controllers.k8s.utils import get_namespace
from authentik.tasks.schedules.common import ScheduleSpec from authentik.tasks.schedules.common import ScheduleSpec
from authentik.tasks.schedules.models import ScheduledModel from authentik.tasks.schedules.models import ScheduledModel
OUR_VERSION = parse(__version__) OUR_VERSION = parse(authentik_version())
OUTPOST_HELLO_INTERVAL = 10 OUTPOST_HELLO_INTERVAL = 10
LOGGER = get_logger() LOGGER = get_logger()
@@ -481,7 +481,7 @@ class OutpostState:
"""Check if outpost version matches our version""" """Check if outpost version matches our version"""
if not self.version: if not self.version:
return False return False
if self.build_hash != get_build_hash(): if self.build_hash != authentik_build_hash():
return False return False
return parse(self.version) != OUR_VERSION return parse(self.version) != OUR_VERSION

View File

@@ -6,7 +6,7 @@ from channels.routing import URLRouter
from channels.testing import WebsocketCommunicator from channels.testing import WebsocketCommunicator
from django.test import TransactionTestCase from django.test import TransactionTestCase
from authentik import __version__ from authentik import authentik_version
from authentik.core.tests.utils import create_test_flow from authentik.core.tests.utils import create_test_flow
from authentik.outposts.consumer import WebsocketMessage, WebsocketMessageInstruction from authentik.outposts.consumer import WebsocketMessage, WebsocketMessageInstruction
from authentik.outposts.models import Outpost, OutpostType from authentik.outposts.models import Outpost, OutpostType
@@ -65,7 +65,7 @@ class TestOutpostWS(TransactionTestCase):
WebsocketMessage( WebsocketMessage(
instruction=WebsocketMessageInstruction.HELLO, instruction=WebsocketMessageInstruction.HELLO,
args={ args={
"version": __version__, "version": authentik_version(),
"buildHash": "foo", "buildHash": "foo",
"uuid": "123", "uuid": "123",
}, },

View File

@@ -10,7 +10,7 @@ import orjson
from sentry_sdk import set_tag from sentry_sdk import set_tag
from xmlsec import enable_debug_trace from xmlsec import enable_debug_trace
from authentik import __version__ from authentik import authentik_version
from authentik.lib.config import CONFIG, django_db_config, redis_url from authentik.lib.config import CONFIG, django_db_config, redis_url
from authentik.lib.logging import get_logger_config, structlog_configure from authentik.lib.logging import get_logger_config, structlog_configure
from authentik.lib.sentry import sentry_init from authentik.lib.sentry import sentry_init
@@ -144,7 +144,7 @@ GUARDIAN_MONKEY_PATCH_USER = False
SPECTACULAR_SETTINGS = { SPECTACULAR_SETTINGS = {
"TITLE": "authentik", "TITLE": "authentik",
"DESCRIPTION": "Making authentication simple.", "DESCRIPTION": "Making authentication simple.",
"VERSION": __version__, "VERSION": authentik_version(),
"COMPONENT_SPLIT_REQUEST": True, "COMPONENT_SPLIT_REQUEST": True,
"SCHEMA_PATH_PREFIX": "/api/v([0-9]+(beta)?)", "SCHEMA_PATH_PREFIX": "/api/v([0-9]+(beta)?)",
"SCHEMA_PATH_PREFIX_TRIM": True, "SCHEMA_PATH_PREFIX_TRIM": True,
@@ -567,7 +567,7 @@ if DEBUG:
enable_debug_trace(True) enable_debug_trace(True)
CONFIG.log("info", "Booting authentik", version=__version__) CONFIG.log("info", "Booting authentik", version=authentik_version())
# Load subapps's settings # Load subapps's settings
_filter_and_update(SHARED_APPS + TENANT_APPS) _filter_and_update(SHARED_APPS + TENANT_APPS)

View File

@@ -5,7 +5,7 @@ from ssl import OPENSSL_VERSION
import pytest import pytest
from cryptography.hazmat.backends.openssl.backend import backend from cryptography.hazmat.backends.openssl.backend import backend
from authentik import get_full_version from authentik import authentik_full_version
IS_CI = "CI" in environ IS_CI = "CI" in environ
@@ -22,7 +22,7 @@ def pytest_sessionstart(*_, **__):
def pytest_report_header(*_, **__): def pytest_report_header(*_, **__):
"""Add authentik version to pytest output""" """Add authentik version to pytest output"""
return [ return [
f"authentik version: {get_full_version()}", f"authentik version: {authentik_full_version()}",
f"OpenSSL version: {OPENSSL_VERSION}, FIPS: {backend._fips_enabled}", f"OpenSSL version: {OPENSSL_VERSION}, FIPS: {backend._fips_enabled}",
] ]

View File

@@ -6,7 +6,7 @@ from django.http.response import Http404
from requests.exceptions import RequestException from requests.exceptions import RequestException
from structlog.stdlib import get_logger from structlog.stdlib import get_logger
from authentik import __version__ from authentik import authentik_version
from authentik.core.sources.flow_manager import SourceFlowManager from authentik.core.sources.flow_manager import SourceFlowManager
from authentik.lib.utils.http import get_http_session from authentik.lib.utils.http import get_http_session
from authentik.sources.plex.models import ( from authentik.sources.plex.models import (
@@ -38,7 +38,7 @@ class PlexAuth:
"""Get common headers""" """Get common headers"""
return { return {
"X-Plex-Product": "authentik", "X-Plex-Product": "authentik",
"X-Plex-Version": __version__, "X-Plex-Version": authentik_version(),
"X-Plex-Device-Vendor": "goauthentik.io", "X-Plex-Device-Vendor": "goauthentik.io",
} }

View File

@@ -7,7 +7,7 @@ from rest_framework.request import Request
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework.views import APIView from rest_framework.views import APIView
from authentik import get_full_version from authentik import authentik_full_version
from authentik.rbac.permissions import HasPermission from authentik.rbac.permissions import HasPermission
from authentik.tasks.models import WorkerStatus from authentik.tasks.models import WorkerStatus
@@ -30,7 +30,7 @@ class WorkerView(APIView):
) )
def get(self, request: Request) -> Response: def get(self, request: Request) -> Response:
response = [] response = []
our_version = parse(get_full_version()) our_version = parse(authentik_full_version())
for status in WorkerStatus.objects.filter(last_seen__gt=now() - timedelta(minutes=2)): for status in WorkerStatus.objects.filter(last_seen__gt=now() - timedelta(minutes=2)):
lock_id = f"goauthentik.io/worker/status/{status.pk}" lock_id = f"goauthentik.io/worker/status/{status.pk}"
with pglock.advisory(lock_id, timeout=0, side_effect=pglock.Return) as acquired: with pglock.advisory(lock_id, timeout=0, side_effect=pglock.Return) as acquired:

View File

@@ -7,7 +7,9 @@ import pglock
from django.db import OperationalError, connections from django.db import OperationalError, connections
from django.utils.timezone import now from django.utils.timezone import now
from django_dramatiq_postgres.middleware import HTTPServer from django_dramatiq_postgres.middleware import HTTPServer
from django_dramatiq_postgres.middleware import MetricsMiddleware as BaseMetricsMiddleware from django_dramatiq_postgres.middleware import (
MetricsMiddleware as BaseMetricsMiddleware,
)
from django_redis import get_redis_connection from django_redis import get_redis_connection
from dramatiq.broker import Broker from dramatiq.broker import Broker
from dramatiq.message import Message from dramatiq.message import Message
@@ -15,7 +17,7 @@ from dramatiq.middleware import Middleware
from redis.exceptions import RedisError from redis.exceptions import RedisError
from structlog.stdlib import get_logger from structlog.stdlib import get_logger
from authentik import get_full_version from authentik import authentik_full_version
from authentik.events.models import Event, EventAction from authentik.events.models import Event, EventAction
from authentik.lib.sentry import should_ignore_exception from authentik.lib.sentry import should_ignore_exception
from authentik.tasks.models import Task, TaskStatus, WorkerStatus from authentik.tasks.models import Task, TaskStatus, WorkerStatus
@@ -208,7 +210,7 @@ class WorkerStatusMiddleware(Middleware):
def run(): def run():
status = WorkerStatus.objects.create( status = WorkerStatus.objects.create(
hostname=socket.gethostname(), hostname=socket.gethostname(),
version=get_full_version(), version=authentik_full_version(),
) )
lock_id = f"goauthentik.io/worker/status/{status.pk}" lock_id = f"goauthentik.io/worker/status/{status.pk}"
with pglock.advisory(lock_id, side_effect=pglock.Raise): with pglock.advisory(lock_id, side_effect=pglock.Raise):

View File

@@ -6,7 +6,7 @@ from django.utils.timezone import now, timedelta
from packaging.version import parse from packaging.version import parse
from prometheus_client import Gauge from prometheus_client import Gauge
from authentik import get_full_version from authentik import authentik_full_version
from authentik.root.monitoring import monitoring_set from authentik.root.monitoring import monitoring_set
from authentik.tasks.models import WorkerStatus from authentik.tasks.models import WorkerStatus
@@ -22,7 +22,7 @@ GAUGE_WORKERS = Gauge(
) )
_version = parse(get_full_version()) _version = parse(authentik_full_version())
@receiver(monitoring_set) @receiver(monitoring_set)

View File

@@ -36,7 +36,7 @@ var rootCmd = &cobra.Command{
AttachStacktrace: true, AttachStacktrace: true,
EnableTracing: true, EnableTracing: true,
TracesSampler: sentryutils.SamplerFunc(config.Get().ErrorReporting.SampleRate), TracesSampler: sentryutils.SamplerFunc(config.Get().ErrorReporting.SampleRate),
Release: fmt.Sprintf("authentik@%s", constants.VERSION), Release: fmt.Sprintf("authentik@%s", constants.VERSION()),
Environment: config.Get().ErrorReporting.Environment, Environment: config.Get().ErrorReporting.Environment,
HTTPTransport: webutils.NewUserAgentTransport(constants.UserAgent(), http.DefaultTransport), HTTPTransport: webutils.NewUserAgentTransport(constants.UserAgent(), http.DefaultTransport),
IgnoreErrors: []string{ IgnoreErrors: []string{

View File

@@ -1,90 +1,85 @@
---
services: services:
postgresql: postgresql:
image: docker.io/library/postgres:16-alpine env_file:
restart: unless-stopped - .env
healthcheck:
test: ["CMD-SHELL", "pg_isready -d $${POSTGRES_DB} -U $${POSTGRES_USER}"]
start_period: 20s
interval: 30s
retries: 5
timeout: 5s
volumes:
- database:/var/lib/postgresql/data
environment: environment:
POSTGRES_DB: ${PG_DB:-authentik}
POSTGRES_PASSWORD: ${PG_PASS:?database password required} POSTGRES_PASSWORD: ${PG_PASS:?database password required}
POSTGRES_USER: ${PG_USER:-authentik} POSTGRES_USER: ${PG_USER:-authentik}
POSTGRES_DB: ${PG_DB:-authentik}
env_file:
- .env
redis:
image: docker.io/library/redis:alpine
command: --save 60 1 --loglevel warning
restart: unless-stopped
healthcheck: healthcheck:
test: ["CMD-SHELL", "redis-cli ping | grep PONG"]
start_period: 20s
interval: 30s interval: 30s
retries: 5 retries: 5
timeout: 3s start_period: 20s
volumes: test:
- redis:/data - CMD-SHELL
server: - pg_isready -d $${POSTGRES_DB} -U $${POSTGRES_USER}
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2025.6.4} timeout: 5s
image: docker.io/library/postgres:16-alpine
restart: unless-stopped restart: unless-stopped
command: server
environment:
AUTHENTIK_SECRET_KEY: ${AUTHENTIK_SECRET_KEY:?secret key required}
AUTHENTIK_REDIS__HOST: redis
AUTHENTIK_POSTGRESQL__HOST: postgresql
AUTHENTIK_POSTGRESQL__USER: ${PG_USER:-authentik}
AUTHENTIK_POSTGRESQL__NAME: ${PG_DB:-authentik}
AUTHENTIK_POSTGRESQL__PASSWORD: ${PG_PASS}
volumes: volumes:
- ./media:/media - database:/var/lib/postgresql/data
- ./custom-templates:/templates redis:
env_file: command: --save 60 1 --loglevel warning
- .env healthcheck:
ports: interval: 30s
- "${COMPOSE_PORT_HTTP:-9000}:9000" retries: 5
- "${COMPOSE_PORT_HTTPS:-9443}:9443" start_period: 20s
test:
- CMD-SHELL
- redis-cli ping | grep PONG
timeout: 3s
image: docker.io/library/redis:alpine
restart: unless-stopped
volumes:
- redis:/data
server:
command: server
depends_on: depends_on:
postgresql: postgresql:
condition: service_healthy condition: service_healthy
redis: redis:
condition: service_healthy condition: service_healthy
worker: env_file:
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2025.6.4} - .env
restart: unless-stopped
command: worker
environment: environment:
AUTHENTIK_SECRET_KEY: ${AUTHENTIK_SECRET_KEY:?secret key required}
AUTHENTIK_REDIS__HOST: redis
AUTHENTIK_POSTGRESQL__HOST: postgresql AUTHENTIK_POSTGRESQL__HOST: postgresql
AUTHENTIK_POSTGRESQL__USER: ${PG_USER:-authentik}
AUTHENTIK_POSTGRESQL__NAME: ${PG_DB:-authentik} AUTHENTIK_POSTGRESQL__NAME: ${PG_DB:-authentik}
AUTHENTIK_POSTGRESQL__PASSWORD: ${PG_PASS} AUTHENTIK_POSTGRESQL__PASSWORD: ${PG_PASS}
# `user: root` and the docker socket volume are optional. AUTHENTIK_POSTGRESQL__USER: ${PG_USER:-authentik}
# See more for the docker socket integration here: AUTHENTIK_REDIS__HOST: redis
# https://goauthentik.io/docs/outposts/integrations/docker AUTHENTIK_SECRET_KEY: ${AUTHENTIK_SECRET_KEY:?secret key required}
# Removing `user: root` also prevents the worker from fixing the permissions image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2025.6.4}
# on the mounted folders, so when removing this make sure the folders have the correct UID/GID ports:
# (1000:1000 by default) - ${COMPOSE_PORT_HTTP:-9000}:9000
- ${COMPOSE_PORT_HTTPS:-9443}:9443
restart: unless-stopped
volumes:
- ./media:/media
- ./custom-templates:/templates
worker:
command: worker
depends_on:
postgresql:
condition: service_healthy
redis:
condition: service_healthy
env_file:
- .env
environment:
AUTHENTIK_POSTGRESQL__HOST: postgresql
AUTHENTIK_POSTGRESQL__NAME: ${PG_DB:-authentik}
AUTHENTIK_POSTGRESQL__PASSWORD: ${PG_PASS}
AUTHENTIK_POSTGRESQL__USER: ${PG_USER:-authentik}
AUTHENTIK_REDIS__HOST: redis
AUTHENTIK_SECRET_KEY: ${AUTHENTIK_SECRET_KEY:?secret key required}
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2025.6.4}
restart: unless-stopped
user: root user: root
volumes: volumes:
- /var/run/docker.sock:/var/run/docker.sock - /var/run/docker.sock:/var/run/docker.sock
- ./media:/media - ./media:/media
- ./certs:/certs - ./certs:/certs
- ./custom-templates:/templates - ./custom-templates:/templates
env_file:
- .env
depends_on:
postgresql:
condition: service_healthy
redis:
condition: service_healthy
volumes: volumes:
database: database:
driver: local driver: local

View File

@@ -0,0 +1 @@
2025.6.4

View File

@@ -1,10 +1,14 @@
package constants package constants
import ( import (
_ "embed"
"fmt" "fmt"
"os" "os"
) )
//go:embed VERSION
var version string
func BUILD(def string) string { func BUILD(def string) string {
build := os.Getenv("GIT_BUILD_HASH") build := os.Getenv("GIT_BUILD_HASH")
if build == "" { if build == "" {
@@ -13,12 +17,15 @@ func BUILD(def string) string {
return build return build
} }
func VERSION() string {
return version
}
func FullVersion() string { func FullVersion() string {
ver := VERSION
if b := BUILD(""); b != "" { if b := BUILD(""); b != "" {
return fmt.Sprintf("%s+%s", ver, b) return fmt.Sprintf("%s+%s", version, b)
} }
return ver return version
} }
func UserAgentOutpost() string { func UserAgentOutpost() string {
@@ -32,5 +39,3 @@ func UserAgentIPC() string {
func UserAgent() string { func UserAgent() string {
return fmt.Sprintf("authentik@%s", FullVersion()) return fmt.Sprintf("authentik@%s", FullVersion())
} }
const VERSION = "2025.6.4"

View File

@@ -198,7 +198,7 @@ func (a *APIController) OnRefresh() error {
func (a *APIController) getEventPingArgs() map[string]interface{} { func (a *APIController) getEventPingArgs() map[string]interface{} {
args := map[string]interface{}{ args := map[string]interface{}{
"version": constants.VERSION, "version": constants.VERSION(),
"buildHash": constants.BUILD(""), "buildHash": constants.BUILD(""),
"uuid": a.instanceUUID.String(), "uuid": a.instanceUUID.String(),
"golangVersion": runtime.Version(), "golangVersion": runtime.Version(),
@@ -218,7 +218,7 @@ func (a *APIController) StartBackgroundTasks() error {
"outpost_name": a.Outpost.Name, "outpost_name": a.Outpost.Name,
"outpost_type": a.Server.Type(), "outpost_type": a.Server.Type(),
"uuid": a.instanceUUID.String(), "uuid": a.instanceUUID.String(),
"version": constants.VERSION, "version": constants.VERSION(),
"build": constants.BUILD(""), "build": constants.BUILD(""),
}).Set(1) }).Set(1)
go func() { go func() {

View File

@@ -163,7 +163,7 @@ func (ac *APIController) startEventHandler() {
"outpost_name": ac.Outpost.Name, "outpost_name": ac.Outpost.Name,
"outpost_type": ac.Server.Type(), "outpost_type": ac.Server.Type(),
"uuid": ac.instanceUUID.String(), "uuid": ac.instanceUUID.String(),
"version": constants.VERSION, "version": constants.VERSION(),
"build": constants.BUILD(""), "build": constants.BUILD(""),
}).SetToCurrentTime() }).SetToCurrentTime()
} }
@@ -228,7 +228,7 @@ func (ac *APIController) startIntervalUpdater() {
"outpost_name": ac.Outpost.Name, "outpost_name": ac.Outpost.Name,
"outpost_type": ac.Server.Type(), "outpost_type": ac.Server.Type(),
"uuid": ac.instanceUUID.String(), "uuid": ac.instanceUUID.String(),
"version": constants.VERSION, "version": constants.VERSION(),
"build": constants.BUILD(""), "build": constants.BUILD(""),
}).SetToCurrentTime() }).SetToCurrentTime()
} }

View File

@@ -53,7 +53,7 @@ func doGlobalSetup(outpost api.Outpost, globalConfig *api.Config) {
Environment: globalConfig.ErrorReporting.Environment, Environment: globalConfig.ErrorReporting.Environment,
EnableTracing: true, EnableTracing: true,
TracesSampler: sentryutils.SamplerFunc(float64(globalConfig.ErrorReporting.TracesSampleRate)), TracesSampler: sentryutils.SamplerFunc(float64(globalConfig.ErrorReporting.TracesSampleRate)),
Release: fmt.Sprintf("authentik@%s", constants.VERSION), Release: fmt.Sprintf("authentik@%s", constants.VERSION()),
HTTPTransport: webutils.NewUserAgentTransport(constants.UserAgentOutpost(), http.DefaultTransport), HTTPTransport: webutils.NewUserAgentTransport(constants.UserAgentOutpost(), http.DefaultTransport),
IgnoreErrors: []string{ IgnoreErrors: []string{
http.ErrAbortHandler.Error(), http.ErrAbortHandler.Error(),
@@ -66,7 +66,7 @@ func doGlobalSetup(outpost api.Outpost, globalConfig *api.Config) {
} }
if !initialSetup { if !initialSetup {
l.WithField("hash", constants.BUILD("tagged")).WithField("version", constants.VERSION).Info("Starting authentik outpost") l.WithField("hash", constants.BUILD("tagged")).WithField("version", constants.VERSION()).Info("Starting authentik outpost")
initialSetup = true initialSetup = true
} }
} }

View File

@@ -98,7 +98,7 @@ func (ws *WebServer) staticHeaderMiddleware(h http.Handler) http.Handler {
etagHandler := etag.Handler(h, false) etagHandler := etag.Handler(h, false)
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Cache-Control", "public, no-transform") w.Header().Set("Cache-Control", "public, no-transform")
w.Header().Set("X-authentik-version", constants.VERSION) w.Header().Set("X-authentik-version", constants.VERSION())
w.Header().Set("Vary", "X-authentik-version, Etag") w.Header().Set("Vary", "X-authentik-version, Etag")
etagHandler.ServeHTTP(w, r) etagHandler.ServeHTTP(w, r)
}) })

View File

@@ -34,7 +34,7 @@ from aws_cdk import (
) )
from constructs import Construct from constructs import Construct
from authentik import __version__ from authentik import authentik_version as ak_version
class AuthentikStack(Stack): class AuthentikStack(Stack):
@@ -88,7 +88,7 @@ class AuthentikStack(Stack):
self, self,
"AuthentikVersion", "AuthentikVersion",
type="String", type="String",
default=__version__, default=ak_version(),
description="authentik Docker image tag", description="authentik Docker image tag",
) )

View File

@@ -8,7 +8,7 @@ from typing import TYPE_CHECKING
from prometheus_client.values import MultiProcessValue from prometheus_client.values import MultiProcessValue
from authentik import get_full_version from authentik import authentik_full_version
from authentik.lib.config import CONFIG from authentik.lib.config import CONFIG
from authentik.lib.debug import start_debug_server from authentik.lib.debug import start_debug_server
from authentik.lib.logging import get_logger_config from authentik.lib.logging import get_logger_config
@@ -127,9 +127,9 @@ if not CONFIG.get_bool("disable_startup_analytics", False):
json={ json={
"domain": "authentik", "domain": "authentik",
"name": "pageview", "name": "pageview",
"referrer": get_full_version(), "referrer": authentik_full_version(),
"url": ( "url": (
f"http://localhost/{env}?utm_source={get_full_version()}&utm_medium={env}" f"http://localhost/{env}?utm_source={authentik_full_version()}&utm_medium={env}"
), ),
}, },
headers={ headers={

View File

@@ -2,7 +2,7 @@
from lifecycle.migrate import BaseMigration from lifecycle.migrate import BaseMigration
from datetime import datetime from datetime import datetime
from authentik import __version__, get_build_hash from authentik import authentik_version, authentik_build_hash
class Migration(BaseMigration): class Migration(BaseMigration):
@@ -14,7 +14,7 @@ class Migration(BaseMigration):
ORDER BY "timestamp" DESC ORDER BY "timestamp" DESC
LIMIT 1 LIMIT 1
""", """,
(__version__, get_build_hash()), (authentik_version(), authentik_build_hash()),
) )
return not bool(self.cur.rowcount) return not bool(self.cur.rowcount)
@@ -24,7 +24,7 @@ class Migration(BaseMigration):
INSERT INTO authentik_version_history ("timestamp", version, build) INSERT INTO authentik_version_history ("timestamp", version, build)
VALUES (%s, %s, %s) VALUES (%s, %s, %s)
""", """,
(datetime.now(), __version__, get_build_hash()), (datetime.now(), authentik_version(), authentik_build_hash()),
) )
self.cur.execute( self.cur.execute(
""" """

View File

@@ -5,112 +5,111 @@ description = ""
authors = [{ name = "authentik Team", email = "hello@goauthentik.io" }] authors = [{ name = "authentik Team", email = "hello@goauthentik.io" }]
requires-python = "==3.13.*" requires-python = "==3.13.*"
dependencies = [ dependencies = [
"argon2-cffi==25.1.0", "argon2-cffi==25.1.0",
"channels==4.3.0", "channels==4.3.0",
"channels-redis==4.3.0", "channels-redis==4.3.0",
"cryptography==45.0.5", "cryptography==45.0.5",
"dacite==1.9.2", "dacite==1.9.2",
"deepmerge==2.0", "deepmerge==2.0",
"defusedxml==0.7.1", "defusedxml==0.7.1",
"django==5.1.11", "django==5.1.11",
"django-countries==7.6.1", "django-countries==7.6.1",
"django-cte==2.0.0", "django-cte==2.0.0",
"django-dramatiq-postgres", "django-dramatiq-postgres",
"django-filter==25.1", "django-filter==25.1",
"django-guardian==3.0.3", "django-guardian==3.0.3",
"django-model-utils==5.0.0", "django-model-utils==5.0.0",
"django-pglock==1.7.2", "django-pglock==1.7.2",
"django-pgtrigger==4.15.2", "django-pgtrigger==4.15.2",
"django-prometheus==2.4.1", "django-prometheus==2.4.1",
"django-redis==6.0.0", "django-redis==6.0.0",
"django-storages[s3]==1.14.6", "django-storages[s3]==1.14.6",
"django-tenants==3.8.0", "django-tenants==3.8.0",
"djangoql==0.18.1", "djangoql==0.18.1",
"djangorestframework-guardian==0.4.0", "djangorestframework-guardian==0.4.0",
"djangorestframework==3.16.0", "djangorestframework==3.16.0",
"docker==7.1.0", "docker==7.1.0",
"drf-orjson-renderer==1.7.3", "drf-orjson-renderer==1.7.3",
"drf-spectacular==0.28.0", "drf-spectacular==0.28.0",
"dumb-init==1.2.5.post1", "dumb-init==1.2.5.post1",
"duo-client==5.5.0", "duo-client==5.5.0",
"fido2==2.0.0", "fido2==2.0.0",
"geoip2==5.1.0", "geoip2==5.1.0",
"geopy==2.4.1", "geopy==2.4.1",
"google-api-python-client==2.177.0", "google-api-python-client==2.177.0",
"gssapi==1.9.0", "gssapi==1.9.0",
"gunicorn==23.0.0", "gunicorn==23.0.0",
"jsonpatch==1.33", "jsonpatch==1.33",
"jwcrypto==1.5.6", "jwcrypto==1.5.6",
"kubernetes==33.1.0", "kubernetes==33.1.0",
"ldap3==2.9.1", "ldap3==2.9.1",
"lxml==6.0.0", "lxml==6.0.0",
"msgraph-sdk==1.39.0", "msgraph-sdk==1.39.0",
"opencontainers==0.0.15", "opencontainers==0.0.15",
"packaging==25.0", "packaging==25.0",
"paramiko==3.5.1", "paramiko==3.5.1",
"psycopg[c,pool]==3.2.9", "psycopg[c,pool]==3.2.9",
"pydantic==2.11.7", "pydantic==2.11.7",
"pydantic-scim==0.0.8", "pydantic-scim==0.0.8",
"pyjwt==2.10.1", "pyjwt==2.10.1",
"pyrad==2.4", "pyrad==2.4",
"python-kadmin-rs==0.6.1", "python-kadmin-rs==0.6.1",
"pyyaml==6.0.2", "pyyaml==6.0.2",
"requests-oauthlib==2.0.0", "requests-oauthlib==2.0.0",
"scim2-filter-parser==0.7.0", "scim2-filter-parser==0.7.0",
"sentry-sdk==2.33.2", "sentry-sdk==2.33.2",
"service-identity==24.2.0", "service-identity==24.2.0",
"setproctitle==1.3.6", "setproctitle==1.3.6",
"structlog==25.4.0", "structlog==25.4.0",
"swagger-spec-validator==3.0.4", "swagger-spec-validator==3.0.4",
"twilio==9.7.0", "twilio==9.7.0",
"ua-parser==1.0.1", "ua-parser==1.0.1",
"unidecode==1.4.0", "unidecode==1.4.0",
"urllib3<3", "urllib3<3",
"uvicorn[standard]==0.35.0", "uvicorn[standard]==0.35.0",
"watchdog==6.0.0", "watchdog==6.0.0",
"webauthn==2.6.0", "webauthn==2.6.0",
"wsproto==1.2.0", "wsproto==1.2.0",
"xmlsec==1.3.16", "xmlsec==1.3.16",
"zxcvbn==4.5.0", "zxcvbn==4.5.0",
] ]
[dependency-groups] [dependency-groups]
dev = [ dev = [
"aws-cdk-lib==2.188.0", "aws-cdk-lib==2.188.0",
"bandit==1.8.3", "bandit==1.8.3",
"black==25.1.0", "black==25.1.0",
"bump2version==1.0.1", "channels[daphne]==4.3.0",
"channels[daphne]==4.3.0", "codespell==2.4.1",
"codespell==2.4.1", "colorama==0.4.6",
"colorama==0.4.6", "constructs==10.4.2",
"constructs==10.4.2", "coverage[toml]==7.8.0",
"coverage[toml]==7.8.0", "debugpy==1.8.14",
"debugpy==1.8.14", "drf-jsonschema-serializer==3.0.0",
"drf-jsonschema-serializer==3.0.0", "freezegun==1.5.1",
"freezegun==1.5.1", "importlib-metadata==8.6.1",
"importlib-metadata==8.6.1", "k5test==0.10.4",
"k5test==0.10.4", "pdoc==15.0.3",
"pdoc==15.0.3", "pytest==8.3.5",
"pytest==8.3.5", "pytest-django==4.11.1",
"pytest-django==4.11.1", "pytest-github-actions-annotate-failures==0.3.0",
"pytest-github-actions-annotate-failures==0.3.0", "pytest-randomly==3.16.0",
"pytest-randomly==3.16.0", "pytest-timeout==2.4.0",
"pytest-timeout==2.4.0", "requests-mock==1.12.1",
"requests-mock==1.12.1", "ruff==0.11.9",
"ruff==0.11.9", "selenium==4.32.0",
"selenium==4.32.0",
] ]
[tool.uv] [tool.uv]
no-binary-package = [ no-binary-package = [
# This differs from the no-binary packages in the Dockerfile. This is due to the fact # This differs from the no-binary packages in the Dockerfile. This is due to the fact
# that these packages are built from source for different reasons than cryptography and kadmin. # that these packages are built from source for different reasons than cryptography and kadmin.
# These packages are built from source to link against the libxml2 on the system which is # These packages are built from source to link against the libxml2 on the system which is
# required for functionality and to stay up-to-date on both libraries. # required for functionality and to stay up-to-date on both libraries.
# The other packages specified in the dockerfile are compiled from source to link against the # The other packages specified in the dockerfile are compiled from source to link against the
# correct FIPS OpenSSL libraries # correct FIPS OpenSSL libraries
"lxml", "lxml",
"xmlsec", "xmlsec",
] ]
[tool.uv.sources] [tool.uv.sources]
@@ -133,29 +132,29 @@ exclude_dirs = ["**/node_modules/**"]
[tool.codespell] [tool.codespell]
skip = [ skip = [
"**/node_modules", "**/node_modules",
"**/package-lock.json", "**/package-lock.json",
"schema.yml", "schema.yml",
"unittest.xml", "unittest.xml",
"./blueprints/schema.json", "./blueprints/schema.json",
"go.sum", "go.sum",
"locale", "locale",
"**/web/src/locales", "**/web/src/locales",
"**/dist", # Distributed build output "**/dist", # Distributed build output
"**/storybook-static", "**/storybook-static",
"**/web/xliff", "**/web/xliff",
"**/out", # TypeScript type-checking output "**/out", # TypeScript type-checking output
"./web/custom-elements.json", # TypeScript custom element definitions "./web/custom-elements.json", # TypeScript custom element definitions
"./website/build", # TODO: Remove this after moving website to docs "./website/build", # TODO: Remove this after moving website to docs
"./website/**/build", # TODO: Remove this after moving website to docs "./website/**/build", # TODO: Remove this after moving website to docs
"./docs/build", # Docusaurus Topic docs build output "./docs/build", # Docusaurus Topic docs build output
"./docs/**/build", # Docusaurus workspaces output "./docs/**/build", # Docusaurus workspaces output
"*.api.mdx", # Generated API docs "*.api.mdx", # Generated API docs
"./gen-ts-api", "./gen-ts-api",
"./gen-py-api", "./gen-py-api",
"./gen-go-api", "./gen-go-api",
"./htmlcov", "./htmlcov",
"./media", "./media",
] ]
dictionary = ".github/codespell-dictionary.txt,-" dictionary = ".github/codespell-dictionary.txt,-"
ignore-words = ".github/codespell-words.txt" ignore-words = ".github/codespell-words.txt"
@@ -172,23 +171,23 @@ exclude = ["**/migrations/**", "**/node_modules/**"]
[tool.ruff.lint] [tool.ruff.lint]
select = [ select = [
# pycodestyle # pycodestyle
"E", "E",
# Pyflakes # Pyflakes
"F", "F",
# isort # isort
"I", "I",
# pyupgrade # pyupgrade
"UP", "UP",
# flake8-bugbear # flake8-bugbear
"B", "B",
# django # django
"DJ", "DJ",
# pylint # pylint
"PL", "PL",
] ]
ignore = [ ignore = [
"DJ001", # Avoid using `null=True` on string-based fields, "DJ001", # Avoid using `null=True` on string-based fields,
] ]
[tool.ruff.lint.pylint] [tool.ruff.lint.pylint]
@@ -200,14 +199,14 @@ max-returns = 10
source = ["authentik"] source = ["authentik"]
relative_files = true relative_files = true
omit = [ omit = [
"*/asgi.py", "*/asgi.py",
"manage.py", "manage.py",
"*/migrations/*", "*/migrations/*",
"*/management/commands/*", "*/management/commands/*",
"*/apps.py", "*/apps.py",
# TODO: Remove this after moving website to docs # TODO: Remove this after moving website to docs
"website/", "website/",
"docs/", "docs/",
] ]
[tool.coverage.report] [tool.coverage.report]
@@ -215,19 +214,19 @@ sort = "Cover"
skip_covered = true skip_covered = true
precision = 2 precision = 2
exclude_lines = [ exclude_lines = [
"pragma: no cover", "pragma: no cover",
# Don't complain about missing debug-only code: # Don't complain about missing debug-only code:
"def __unicode__", "def __unicode__",
"def __str__", "def __str__",
"def __repr__", "def __repr__",
"if self.debug", "if self.debug",
"if TYPE_CHECKING", "if TYPE_CHECKING",
# Don't complain if tests don't hit defensive assertion code: # Don't complain if tests don't hit defensive assertion code:
"raise AssertionError", "raise AssertionError",
"raise NotImplementedError", "raise NotImplementedError",
# Don't complain if non-runnable code isn't run: # Don't complain if non-runnable code isn't run:
"if 0:", "if 0:",
"if __name__ == .__main__.:", "if __name__ == .__main__.:",
] ]
show_missing = true show_missing = true
@@ -237,6 +236,6 @@ python_files = ["tests.py", "test_*.py", "*_tests.py"]
junit_family = "xunit2" junit_family = "xunit2"
addopts = "-p authentik.root.test_plugin --junitxml=unittest.xml -vv --full-trace --doctest-modules --import-mode=importlib --ignore=authentik/tasks/setup.py" addopts = "-p authentik.root.test_plugin --junitxml=unittest.xml -vv --full-trace --doctest-modules --import-mode=importlib --ignore=authentik/tasks/setup.py"
filterwarnings = [ filterwarnings = [
"ignore:defusedxml.lxml is no longer supported and will be removed in a future release.:DeprecationWarning", "ignore:defusedxml.lxml is no longer supported and will be removed in a future release.:DeprecationWarning",
"ignore:SelectableGroups dict interface is deprecated. Use select.:DeprecationWarning", "ignore:SelectableGroups dict interface is deprecated. Use select.:DeprecationWarning",
] ]

View File

@@ -0,0 +1,92 @@
from yaml import safe_dump
from authentik import authentik_version
authentik_image = (
f"${{AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}}:${{AUTHENTIK_TAG:-{authentik_version()}}}"
)
base = {
"services": {
"postgresql": {
"env_file": [".env"],
"environment": {
"POSTGRES_DB": "${PG_DB:-authentik}",
"POSTGRES_PASSWORD": "${PG_PASS:?database " "password " "required}",
"POSTGRES_USER": "${PG_USER:-authentik}",
},
"healthcheck": {
"interval": "30s",
"retries": 5,
"start_period": "20s",
"test": ["CMD-SHELL", "pg_isready -d " "$${POSTGRES_DB} -U " "$${POSTGRES_USER}"],
"timeout": "5s",
},
"image": "docker.io/library/postgres:16-alpine",
"restart": "unless-stopped",
"volumes": ["database:/var/lib/postgresql/data"],
},
"redis": {
"command": "--save 60 1 --loglevel warning",
"healthcheck": {
"interval": "30s",
"retries": 5,
"start_period": "20s",
"test": ["CMD-SHELL", "redis-cli ping | grep PONG"],
"timeout": "3s",
},
"image": "docker.io/library/redis:alpine",
"restart": "unless-stopped",
"volumes": ["redis:/data"],
},
"server": {
"command": "server",
"depends_on": {
"postgresql": {"condition": "service_healthy"},
"redis": {"condition": "service_healthy"},
},
"env_file": [".env"],
"environment": {
"AUTHENTIK_POSTGRESQL__HOST": "postgresql",
"AUTHENTIK_POSTGRESQL__NAME": "${PG_DB:-authentik}",
"AUTHENTIK_POSTGRESQL__PASSWORD": "${PG_PASS}",
"AUTHENTIK_POSTGRESQL__USER": "${PG_USER:-authentik}",
"AUTHENTIK_REDIS__HOST": "redis",
"AUTHENTIK_SECRET_KEY": "${AUTHENTIK_SECRET_KEY:?secret " "key " "required}",
},
"image": authentik_image,
"ports": ["${COMPOSE_PORT_HTTP:-9000}:9000", "${COMPOSE_PORT_HTTPS:-9443}:9443"],
"restart": "unless-stopped",
"volumes": ["./media:/media", "./custom-templates:/templates"],
},
"worker": {
"command": "worker",
"depends_on": {
"postgresql": {"condition": "service_healthy"},
"redis": {"condition": "service_healthy"},
},
"env_file": [".env"],
"environment": {
"AUTHENTIK_POSTGRESQL__HOST": "postgresql",
"AUTHENTIK_POSTGRESQL__NAME": "${PG_DB:-authentik}",
"AUTHENTIK_POSTGRESQL__PASSWORD": "${PG_PASS}",
"AUTHENTIK_POSTGRESQL__USER": "${PG_USER:-authentik}",
"AUTHENTIK_REDIS__HOST": "redis",
"AUTHENTIK_SECRET_KEY": "${AUTHENTIK_SECRET_KEY:?secret " "key " "required}",
},
"image": authentik_image,
"restart": "unless-stopped",
"user": "root",
"volumes": [
"/var/run/docker.sock:/var/run/docker.sock",
"./media:/media",
"./certs:/certs",
"./custom-templates:/templates",
],
},
},
"volumes": {"database": {"driver": "local"}, "redis": {"driver": "local"}},
}
with open("docker-compose.yml", "w") as _compose:
safe_dump(base, _compose)

View File

@@ -5,11 +5,11 @@ Generates a Semantic Versioning identifier, suffixed with a timestamp.
from time import time from time import time
from authentik import __version__ as package_version from authentik import authentik_version
""" """
See: https://semver.org/#spec-item-9 (Pre-release spec) See: https://semver.org/#spec-item-9 (Pre-release spec)
""" """
pre_release_timestamp = int(time()) pre_release_timestamp = int(time())
print(f"{package_version}-{pre_release_timestamp}") print(f"{authentik_version()}-{pre_release_timestamp}")

View File

@@ -1,7 +1,6 @@
"""LDAP and Outpost e2e tests""" """LDAP and Outpost e2e tests"""
from dataclasses import asdict from dataclasses import asdict
from time import sleep
from guardian.shortcuts import assign_perm from guardian.shortcuts import assign_perm
from ldap3 import ALL, ALL_ATTRIBUTES, ALL_OPERATIONAL_ATTRIBUTES, SUBTREE, Connection, Server from ldap3 import ALL, ALL_ATTRIBUTES, ALL_OPERATIONAL_ATTRIBUTES, SUBTREE, Connection, Server
@@ -56,17 +55,6 @@ class TestProviderLDAP(SeleniumTestCase):
outpost.providers.add(ldap) outpost.providers.add(ldap)
self.start_ldap(outpost) self.start_ldap(outpost)
# Wait until outpost healthcheck succeeds
healthcheck_retries = 0
while healthcheck_retries < 50: # noqa: PLR2004
if len(outpost.state) > 0:
state = outpost.state[0]
if state.last_seen:
break
healthcheck_retries += 1
sleep(0.5)
sleep(5)
return outpost return outpost
@retry() @retry()

View File

@@ -88,15 +88,6 @@ class TestProviderProxy(SeleniumTestCase):
self.start_proxy(outpost) self.start_proxy(outpost)
# Wait until outpost healthcheck succeeds
healthcheck_retries = 0
while healthcheck_retries < 50: # noqa: PLR2004
if len(outpost.state) > 0:
state = outpost.state[0]
if state.last_seen:
break
healthcheck_retries += 1
sleep(0.5)
sleep(5) sleep(5)
self.driver.get("http://localhost:9000/api") self.driver.get("http://localhost:9000/api")
@@ -170,15 +161,6 @@ class TestProviderProxy(SeleniumTestCase):
self.start_proxy(outpost) self.start_proxy(outpost)
# Wait until outpost healthcheck succeeds
healthcheck_retries = 0
while healthcheck_retries < 50: # noqa: PLR2004
if len(outpost.state) > 0:
state = outpost.state[0]
if state.last_seen:
break
healthcheck_retries += 1
sleep(0.5)
sleep(5) sleep(5)
self.driver.get("http://localhost:9000/api") self.driver.get("http://localhost:9000/api")

View File

@@ -77,17 +77,6 @@ class TestProviderProxyForward(SeleniumTestCase):
self.start_outpost(outpost) self.start_outpost(outpost)
# Wait until outpost healthcheck succeeds
healthcheck_retries = 0
while healthcheck_retries < 50: # noqa: PLR2004
if len(outpost.state) > 0:
state = outpost.state[0]
if state.last_seen:
break
healthcheck_retries += 1
sleep(0.5)
sleep(5)
@retry() @retry()
def test_traefik(self): def test_traefik(self):
"""Test traefik""" """Test traefik"""

View File

@@ -51,15 +51,6 @@ class TestProviderRadius(SeleniumTestCase):
self.start_radius(outpost) self.start_radius(outpost)
# Wait until outpost healthcheck succeeds
healthcheck_retries = 0
while healthcheck_retries < 50: # noqa: PLR2004
if len(outpost.state) > 0:
state = outpost.state[0]
if state.last_seen:
break
healthcheck_retries += 1
sleep(0.5)
sleep(5) sleep(5)
return outpost return outpost

View File

@@ -54,7 +54,7 @@ def get_local_ip() -> str:
class DockerTestCase(TestCase): class DockerTestCase(TestCase):
"""Mixin for dealing with containers""" """Mixin for dealing with containers"""
max_healthcheck_attempts = 30 max_healthcheck_attempts = 45
__client: DockerClient __client: DockerClient
__network: Network __network: Network
@@ -88,7 +88,7 @@ class DockerTestCase(TestCase):
sleep(1) sleep(1)
attempt += 1 attempt += 1
if attempt >= self.max_healthcheck_attempts: if attempt >= self.max_healthcheck_attempts:
self.failureException("Container failed to start") raise self.failureException("Container failed to start")
def get_container_image(self, base: str) -> str: def get_container_image(self, base: str) -> str:
"""Try to pull docker image based on git branch, fallback to main if not found.""" """Try to pull docker image based on git branch, fallback to main if not found."""

11
uv.lock generated
View File

@@ -236,7 +236,6 @@ dev = [
{ name = "aws-cdk-lib" }, { name = "aws-cdk-lib" },
{ name = "bandit" }, { name = "bandit" },
{ name = "black" }, { name = "black" },
{ name = "bump2version" },
{ name = "channels", extra = ["daphne"] }, { name = "channels", extra = ["daphne"] },
{ name = "codespell" }, { name = "codespell" },
{ name = "colorama" }, { name = "colorama" },
@@ -334,7 +333,6 @@ dev = [
{ name = "aws-cdk-lib", specifier = "==2.188.0" }, { name = "aws-cdk-lib", specifier = "==2.188.0" },
{ name = "bandit", specifier = "==1.8.3" }, { name = "bandit", specifier = "==1.8.3" },
{ name = "black", specifier = "==25.1.0" }, { name = "black", specifier = "==25.1.0" },
{ name = "bump2version", specifier = "==1.0.1" },
{ name = "channels", extras = ["daphne"], specifier = "==4.3.0" }, { name = "channels", extras = ["daphne"], specifier = "==4.3.0" },
{ name = "codespell", specifier = "==2.4.1" }, { name = "codespell", specifier = "==2.4.1" },
{ name = "colorama", specifier = "==0.4.6" }, { name = "colorama", specifier = "==0.4.6" },
@@ -583,15 +581,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/16/56/dd25fb9e47060e8f7e353208678fefb65d1b06704ea30983cad8bdd81370/botocore-1.40.2-py3-none-any.whl", hash = "sha256:a31e6269af05498f8dc1c7f2b3f34448a0f16c79a8601c0389ecddab51b2c2ab", size = 13944886, upload-time = "2025-08-04T19:31:37.027Z" }, { url = "https://files.pythonhosted.org/packages/16/56/dd25fb9e47060e8f7e353208678fefb65d1b06704ea30983cad8bdd81370/botocore-1.40.2-py3-none-any.whl", hash = "sha256:a31e6269af05498f8dc1c7f2b3f34448a0f16c79a8601c0389ecddab51b2c2ab", size = 13944886, upload-time = "2025-08-04T19:31:37.027Z" },
] ]
[[package]]
name = "bump2version"
version = "1.0.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/29/2a/688aca6eeebfe8941235be53f4da780c6edee05dbbea5d7abaa3aab6fad2/bump2version-1.0.1.tar.gz", hash = "sha256:762cb2bfad61f4ec8e2bdf452c7c267416f8c70dd9ecb1653fd0bbb01fa936e6", size = 36236, upload-time = "2020-10-07T18:38:40.119Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/1d/e3/fa60c47d7c344533142eb3af0b73234ef8ea3fb2da742ab976b947e717df/bump2version-1.0.1-py2.py3-none-any.whl", hash = "sha256:37f927ea17cde7ae2d7baf832f8e80ce3777624554a653006c9144f8017fe410", size = 22030, upload-time = "2020-10-07T18:38:38.148Z" },
]
[[package]] [[package]]
name = "cachetools" name = "cachetools"
version = "5.5.2" version = "5.5.2"

4
web/package-lock.json generated
View File

@@ -1,12 +1,12 @@
{ {
"name": "@goauthentik/web", "name": "@goauthentik/web",
"version": "0.0.0", "version": "2025.6.4",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "@goauthentik/web", "name": "@goauthentik/web",
"version": "0.0.0", "version": "2025.6.4",
"license": "MIT", "license": "MIT",
"workspaces": [ "workspaces": [
"./packages/*" "./packages/*"

View File

@@ -1,6 +1,6 @@
{ {
"name": "@goauthentik/web", "name": "@goauthentik/web",
"version": "0.0.0", "version": "2025.6.4",
"license": "MIT", "license": "MIT",
"private": true, "private": true,
"scripts": { "scripts": {

View File

@@ -41,7 +41,7 @@ export function readGitBuildHash() {
/** /**
* Reads the build identifier for the current environment. * Reads the build identifier for the current environment.
* *
* This must match the behavior defined in authentik's server-side `get_full_version` function. * This must match the behavior defined in authentik's server-side `authentik_full_version` function.
* *
* @runtime node * @runtime node
* @see {@link "authentik\_\_init\_\_.py"} * @see {@link "authentik\_\_init\_\_.py"}