export CUSTOM_CSS_VERSION=v3 export FLOW_AUTHENTICATION_VERSION=v4 export FLOW_INVITATION_VERSION=v3 export FLOW_INVALIDATION_VERSION=v2 export FLOW_RECOVERY_VERSION=v2 export FLOW_TRANSLATION_VERSION=v3 export SYSTEM_BRAND_VERSION=v4 export NEXTCLOUD_CONFIG_VERSION=v4 export WORDPRESS_CONFIG_VERSION=v8 export MATRIX_CONFIG_VERSION=v4 export WEKAN_CONFIG_VERSION=v6 export VIKUNJA_CONFIG_VERSION=v4 export OUTLINE_CONFIG_VERSION=v5 export KIMAI_CONFIG_VERSION=v3 export ZAMMAD_CONFIG_VERSION=v4 export RALLLY_CONFIG_VERSION=v5 export HEDGEDOC_CONFIG_VERSION=v4 export MONITORING_CONFIG_VERSION=v6 export MILA_CONFIG_VERSION=v2 export DB_ENTRYPOINT_VERSION=v1 export PG_BACKUP_VERSION=v2 export ENTRYPOINT_CSS_VERSION=v1 customize() { if [ -z "$1" ]; then echo "Usage: ... customize " exit 1 fi asset_dir=$1 for asset in $COPY_ASSETS; do source=$(echo $asset | cut -d "|" -f1) target=$(echo $asset | cut -d "|" -f2) echo copy $source to $target abra app cp $APP_NAME $asset_dir/$source $target done } shell() { if [ -z "$1" ]; then echo "Usage: ... shell " exit 1 fi ak shell -c "$1" 2>&1 | quieten } import_user() { if [ -z "$1" ]; then echo "Usage: ... import_user " exit 1 fi source_file=$1 filename=$(basename $source_file) abra app cp -C $APP_NAME $source_file worker:/tmp/ abra app cmd -C -T $APP_NAME worker _import_user $filename } init_admin_groups() { /manage.py shell -c """ from authentik.core.models import Group groups = [ os.environ.get('WORDPRESS_GROUP', ''), os.environ.get('MILA_GROUP', ''), os.environ.get('KIMAI_GROUP', '') ] groups = [g.strip() for g in groups if g.strip()] if not groups: print('Keine Gruppen definiert.') admin = User.objects.get(username='akadmin') for group_name in groups: group_name = group_name.strip() if Group.objects.filter(name=group_name): group = Group.objects.get(name=group_name) else: group = Group.objects.create(name=group_name) print(f'{group_name} created') group.users.add(admin) print(f'add akadmin to group {group_name}') """ 2>&1 | quieten return ${PIPESTATUS[0]} } _import_user() { /manage.py shell -c """ from authentik.core.models import Group import csv new_user = User() with open('/tmp/$1', newline='') as file: reader = csv.reader(file) for row in reader: name = row[0].strip() username = row[1].strip() email = row[2].strip() groups = row[3].split(';') if User.objects.filter(username=username): print(f'{username} already exists') continue new_user = User.objects.create(name=name, username=username, email=email) print(f'{username} created') for group_name in groups: group_name = group_name.strip() if Group.objects.filter(name=group_name): group = Group.objects.get(name=group_name) else: group = Group.objects.create(name=group_name) print(f'{group_name} created') group.users.add(new_user) print(f'add {username} to group {group_name}') """ 2>&1 | quieten return ${PIPESTATUS[0]} } set_user_pass() { username="$1" password="$2" /manage.py shell -c """ user = User.objects.get(username='$username') user.set_password('$password') user.save() print('Changed $username password') """ 2>&1 | quieten return ${PIPESTATUS[0]} } set_admin_pass() { password=$(cat /run/secrets/admin_pass) token=$(cat /run/secrets/admin_token) /manage.py shell -c """ import time i = 0 while (not User.objects.filter(username='akadmin')): print('Waiting for akadmin to be created...') time.sleep(10) i += 1 if i > 6: print('Failed to find admin user!') exit() akadmin = User.objects.get(username='akadmin') akadmin.set_password('$password') akadmin.save() print('Changed akadmin password') from authentik.core.models import TokenIntents key='$token' if (token:= Token.objects.filter(identifier='authentik-bootstrap-token').first()): token.key=key token.save() print('Changed authentik-bootstrap-token') else: Token.objects.create( identifier='authentik-bootstrap-token', user=akadmin, intent=TokenIntents.INTENT_API, expiring=False, key=key, ) print('Created authentik-bootstrap-token') """ 2>&1 | quieten return ${PIPESTATUS[0]} } rotate_db_pass() { db_password=$(cat /run/secrets/db_password) psql -U authentik -c """ALTER USER authentik WITH PASSWORD '$db_password';""" } # This function is for blueprints that are overwriting custom blueprints # It deactivates the affected custom blueprints to avoid changes to be reverted apply_blueprints() { update_and_disable_blueprint default/flow-password-change.yaml update_and_disable_blueprint default/flow-default-authentication-flow.yaml update_and_disable_blueprint default/flow-default-user-settings-flow.yaml update_and_disable_blueprint default/flow-default-source-enrollment.yaml apply_blueprint 3_flow_translation.yaml apply_blueprint 2_flow_authentication.yaml } update_and_disable_blueprint() { enable_blueprint $@ 2>&1 | quieten sleep 1 apply_blueprint $@ sleep 1 disable_blueprint $@ 2>&1 | quieten return ${PIPESTATUS[0]} } disable_blueprint() { blueprint_state False $@ } enable_blueprint() { blueprint_state True $@ } apply_blueprint() { echo apply blueprint $@ ak apply_blueprint $@ 2>&1 | quieten return ${PIPESTATUS[0]} } blueprint_state() { /manage.py shell -c """ import time blueprint_state=$1 blueprint_path='$2' blueprint = BlueprintInstance.objects.filter(path=blueprint_path).first() blueprint.enabled = blueprint_state # Hacky workaround to reduce chance of a race condition blueprint.save() time.sleep(1) blueprint.save() time.sleep(1) blueprint.save() print(f'{blueprint.name} enabled: {blueprint.enabled}') """ 2>&1 | quieten return ${PIPESTATUS[0]} } # This function adds each application with its name, slug and group if passed add_applications() { export APPLICATIONS /manage.py shell -c """ import json import os if os.environ['APPLICATIONS'] == '': exit() applications = json.loads(os.environ['APPLICATIONS']) for name, details in applications.items(): url = details['url'] app = Application.objects.filter(name=name).first() if not app: app = Application() app.name = name app.slug = name.replace(' ', '-') app.meta_launch_url = url group = details['group'] if group: app.group = group print(f'Add {name}: {url} in group: {group}') else: app.group = '' print(f'Add {name}: {url}') app.open_in_new_tab = True app.save() """ 2>&1 | quieten return ${PIPESTATUS[0]} } # This function adds one application with its name, slug and group if passed add_single_application() { if [ -z "$2" ]; then echo "Usage: ... add_single_application " exit 1 fi /manage.py shell -c """ import json import os name = '$1' url = '$2' app = Application.objects.filter(name=name).first() if not app: app = Application() app.name = name app.slug = name.replace(' ', '-') app.meta_launch_url = url group = '$3' if group: app.group = group print(f'Add {name}: {url} in group: {group}') else: app.group = '' print(f'Add {name}: {url}') app.open_in_new_tab = True app.save() """ 2>&1 | quieten return ${PIPESTATUS[0]} } ## This function is for renaming apps - usage: rename "old name" "new name" rename() { /manage.py shell -c """ old_name = '$1' new_name = '$2' if '$2' else old_name app = Application.objects.filter(name=old_name).first() if app: app.name = new_name app.save() print(f'Renamed application from {old_name} to {new_name}') else: print(f'No application found with name: {old_name}') """ 2>&1 | quieten return ${PIPESTATUS[0]} } quieten() { # 'SyntaxWarning|version_regex|"http\[' # is a workaround to get rid of some verbose syntax warnings, this might be fixed with another version grep -Pv '"level": "(info|debug)"|SyntaxWarning|version_regex|"http\[|RuntimeWarning:|### authentik shell|### Node| objects imported automatically|^$' || true } add_email_templates() { for file_path in "$@"; do echo copy template $file_path abra app cp $APP_NAME $file_path app:/templates/ done } set_icons() { if [ -n "$1" ]; then APP_ICONS="$1" fi for icon in $APP_ICONS; do app=$(echo $icon | cut -d ":" -f1) file_path=$(eval echo $(echo $icon | cut -d ":" -f2)) file=$(basename $file_path) echo copy icon $file_path for $app abra app cp -C $APP_NAME $file_path app:/media/ abra app cmd -C -T $APP_NAME app set_app_icon $app /media/$file done } set_extra_icons() { if [ -z "$EXTRA_ICONS" ]; then echo "Variable EXTRA_ICONS is not set" exit 1 fi export EXTRA_ICONS icon_key_values=$(python3 -c " import json import os for key, value in json.loads(os.environ['EXTRA_ICONS']).items(): slug = key.replace(' ','-') print(f'{slug}:{value}') ") set_icons "$icon_key_values" } set_app_icon() { TOKEN=$(cat /run/secrets/admin_token) python -c """ import requests import os my_token = '$TOKEN' application = '$1' icon_path = '$2' base_url = f'https://$DOMAIN/api/v3' headers = {'Authorization': f'Bearer {my_token}'} name_img = os.path.basename(icon_path) # Upload file via the file management API with open(icon_path, 'rb') as img: r = requests.post( f'{base_url}/admin/file/', files={'file': (name_img, img, 'image/png')}, data={'name': name_img}, headers=headers, ) if r.status_code == 400 and 'already exists' in r.text: print(f'{name_img} already uploaded') elif r.status_code != 200: print(f'Upload failed: {r.status_code} {r.text}') exit(1) else: print(f'Uploaded {name_img}') # Set the icon on the application r = requests.patch( f'{base_url}/core/applications/{application}/', json={'meta_icon': name_img}, headers=headers, ) if r.status_code == 200: print(f'Set icon for {application}') else: print(f'Failed to set icon: {r.status_code} {r.text}') """ } blueprint_cleanup() { /manage.py shell -c """ delete_flows = ['default-recovery-flow' , 'custom-authentication-flow' , 'invitation-enrollment-flow' , 'initial-setup'] Flow.objects.filter(slug__in=delete_flows).delete() Stage.objects.filter(flow=None).delete() Prompt.objects.filter(promptstage=None).delete() Brand.objects.filter(default=True).delete() """ 2>&1 | quieten apply_blueprints } check_blueprints() { failed_blueprints=$(/manage.py shell -c """ print(','.join(b.path for b in BlueprintInstance.objects.exclude(status='successful').exclude(name='Default - Out-of-box-experience flow'))) """ 2>&1 | quieten | tail -n1) if [ -z "$failed_blueprints" ]; then echo "All Blueprints Successful" return 0 fi echo "These Blueprints failed:" echo "$failed_blueprints" | tr ',' '\n' echo "execute failed Blueprints" export AUTHENTIK_LOG_LEVEL=warning for bp in $(echo "$failed_blueprints" | tr ',' '\n'); do echo "Applying: $bp" ak apply_blueprint "/blueprints/$bp" 2>&1 | quieten done return 1 } get_certificate() { /manage.py shell -c """ provider_name='$1' if not provider_name: print('no Provider Name given') exit(1) provider = Provider.objects.filter(name=provider_name).first() saml = provider.samlprovider cert = saml.signing_kp print(''.join(cert.certificate_data.splitlines()[1:-1])) """ 2>&1 | quieten return ${PIPESTATUS[0]} } get_user_uid() { /manage.py shell -c """ print(User.objects.filter(username='$1').first().uid) """ 2>&1 | quieten return ${PIPESTATUS[0]} } get_secrets() { grep "" -r /var/run/secrets } fix_collation_mismatch() { psql -U ${POSTGRES_USER} -d authentik -c "ALTER DATABASE authentik REFRESH COLLATION VERSION;" psql -U ${POSTGRES_USER} -d authentik -c "REINDEX DATABASE authentik;" psql -U ${POSTGRES_USER} -d postgres -c "ALTER DATABASE postgres REFRESH COLLATION VERSION;" psql -U ${POSTGRES_USER} -d postgres -c "REINDEX DATABASE postgres;" }