Switch to terraform

This commit is contained in:
Prox
2026-02-15 18:37:15 +02:00
commit a7062b43ab
70 changed files with 6063 additions and 0 deletions

View File

@@ -0,0 +1,89 @@
---
# =============================================================================
# Gitea - Full Cleanup
# =============================================================================
# Removes containers and optionally all data
#
# Usage (containers only):
# ansible-playbook -i inventory.yml cleanup-full.yml
#
# Usage (including data - DESTRUCTIVE):
# ansible-playbook -i inventory.yml cleanup-full.yml -e "remove_data=true"
#
# Usage (including backups - VERY DESTRUCTIVE):
# ansible-playbook -i inventory.yml cleanup-full.yml -e "remove_data=true" -e "remove_backups=true"
# =============================================================================
- name: Gitea - Full Cleanup
hosts: gitea_servers
become: true
vars_files:
- group_vars/gitea_servers.yml
vars:
# Safety flags - must be explicitly enabled
remove_data: false
remove_backups: false
tasks:
- name: Display warning
ansible.builtin.debug:
msg: |
============================================
WARNING: Full Cleanup
============================================
remove_data: {{ remove_data }}
remove_backups: {{ remove_backups }}
Data directory: {{ gitea_data_dir }}
Backup directory: {{ gitea_backup_dir }}
============================================
- name: Check if docker-compose.yml exists
ansible.builtin.stat:
path: "{{ gitea_base_dir }}/docker-compose.yml"
register: compose_file
- name: Stop and remove containers with volumes
ansible.builtin.command:
cmd: docker compose down -v --remove-orphans
chdir: "{{ gitea_base_dir }}"
when: compose_file.stat.exists
ignore_errors: true
changed_when: true
- name: Remove OAuth setup script
ansible.builtin.file:
path: "{{ gitea_base_dir }}/setup-gitea-oauth.sh"
state: absent
- name: Remove Gitea data directory
ansible.builtin.file:
path: "{{ gitea_data_dir }}"
state: absent
when: remove_data | bool
- name: Remove backup directory
ansible.builtin.file:
path: "{{ gitea_backup_dir }}"
state: absent
when: remove_backups | bool
- name: Display cleanup status
ansible.builtin.debug:
msg: |
============================================
Gitea - Full Cleanup Complete
============================================
Containers: Removed
Data: {{ 'REMOVED' if remove_data else 'Preserved at ' + gitea_data_dir }}
Backups: {{ 'REMOVED' if remove_backups else 'Preserved at ' + gitea_backup_dir }}
Note: Authentik OAuth application still exists.
To remove, go to Authentik admin panel:
https://{{ authentik_domain }}/if/admin/#/core/applications
To redeploy:
ansible-playbook -i inventory.yml playbook.yml --ask-vault-pass
============================================

View File

@@ -0,0 +1,50 @@
---
# =============================================================================
# Gitea - Soft Cleanup
# =============================================================================
# Stops containers but preserves configuration and data
#
# Usage:
# ansible-playbook -i inventory.yml cleanup-soft.yml
# =============================================================================
- name: Gitea - Soft Cleanup
hosts: gitea_servers
become: true
vars_files:
- group_vars/gitea_servers.yml
tasks:
- name: Check if docker-compose.yml exists
ansible.builtin.stat:
path: "{{ gitea_base_dir }}/docker-compose.yml"
register: compose_file
- name: Stop containers (preserve data)
ansible.builtin.command:
cmd: docker compose down
chdir: "{{ gitea_base_dir }}"
when: compose_file.stat.exists
ignore_errors: true
changed_when: true
- name: Display cleanup status
ansible.builtin.debug:
msg: |
============================================
Gitea - Soft Cleanup Complete
============================================
Containers stopped. Data preserved at:
{{ gitea_data_dir }}
Backups at:
{{ gitea_backup_dir }}
To restart:
ansible-playbook -i inventory.yml playbook.yml --ask-vault-pass
To fully remove:
ansible-playbook -i inventory.yml cleanup-full.yml
============================================

View File

@@ -0,0 +1,69 @@
---
# =============================================================================
# Gitea Migration Configuration
# =============================================================================
# Migrating from stuslab.cc to code.stuslab.cc with Authentik OAuth
#
# Before running:
# 1. Ensure Authentik is deployed at auth.stuslab.cc
# 2. Create group_vars/vault.yml from vault.yml.example
# 3. Add DNS record: code.stuslab.cc -> 94.130.181.201
# 4. Run: ansible-playbook -i inventory.yml playbook.yml --ask-vault-pass
# =============================================================================
# =============================================================================
# Domain Configuration
# =============================================================================
# Old domain (will redirect to new)
gitea_old_domain: "stuslab.cc"
# New domain for Gitea
gitea_domain: "code.stuslab.cc"
# SSH domain (for git clone URLs)
gitea_ssh_domain: "code.stuslab.cc"
# =============================================================================
# Let's Encrypt Configuration
# =============================================================================
letsencrypt_email: "vlad.stus@gmail.com"
# =============================================================================
# Paths
# =============================================================================
# Existing Gitea installation path on VPS
gitea_base_dir: "/root/gitea"
# Data directory (contains repos, database, config)
gitea_data_dir: "{{ gitea_base_dir }}/gitea_data"
# Backup directory on VPS
gitea_backup_dir: "/root/gitea-backups"
# =============================================================================
# Authentik Configuration
# =============================================================================
# Domain where Authentik is deployed
authentik_domain: "auth.stuslab.cc"
# OAuth provider name (must match exactly in Gitea UI)
gitea_oauth_provider_name: "Authentik"
# OAuth client ID (used in Authentik and Gitea)
gitea_oauth_client_id: "gitea"
# =============================================================================
# Docker Configuration
# =============================================================================
gitea_image: "gitea/gitea:latest"
gitea_http_port: 3000
gitea_ssh_port: 2222
# =============================================================================
# Migration Flags
# =============================================================================
# Create backup before migration (recommended)
gitea_create_backup: true
# Upload backup to GDrive via rclone (requires rclone configured on VPS)
gitea_backup_to_gdrive: false

View File

@@ -0,0 +1,51 @@
$ANSIBLE_VAULT;1.1;AES256
66313066626635366538383531303838363335366332373763343030373535343935343463363037
3661653331333337613763316135653338636265656238300a343233383237316565306161326435
62616533386336333932393230383332383839363366373566306165383936366361663864393231
3536343039663639650a643539323937623334616230363337306661616463313239306438326238
31663535333137323831303266336161353232626564613436613732626461343733623963376565
61303663326633616263613461383263353734303462363634393562663064663332363738303832
66636663653762343636323936656362646236383539666464373862336461363864373963313039
31313166656665663035353130643761616161353837313839636631373236343666343838653837
36366266636339323931383362646634343164666138633364623538383466363662656635636366
33326637303363353961633434376330623836666434383237346430373739333333396539636366
32396339663930353131323032343433656332373635643638623862363164636661313735626639
36613838366231636636623439393137353138613562646664336366663864306664316130656237
33643235646334306336613662303532653033343034643737326230653161326136313132666231
64323734623231623933353763383564353438343236323333613461363031363530356431393461
38636532636532633532613862636635353532666330373034353164326662656638356233306633
37653532306530633135393232316635333863626564666231623961366237366161656437623665
39643134623835316139623236633166636364313866343636326466393035653365626130363533
31633137653463333561653132636234633230373030376633623166383364646536646261633731
37626538623831613431353766656661346565643633353034343533316134616166316136306339
35323666306439393865626465396336623662353161396366653532326633346436336566646336
38373539353334386134646237653534343430343439366533383738653938336530666266636563
66313130313438363830386538306662393264643838656136623136386565303366636362306564
62343030616361616661393063313938663433323662373531333435333032353831663537636461
62666665646566656562303666333830363337663436633435653934656137626664616163303461
32376363353534366235383635333538316431313736663237623966363431343434386263376132
37353764313136323335633133343466343830343366363536303237333835303165333337636230
37643132643866616633376566623264633534343334306537316461616132336265626537333666
61353933366532363363613465313861333362383531306230343238313633633934626264366530
64316335623637363537336162303933393935613734326535613738333262323033373935313632
63393332346132353735356161393438643264343264326634353562613536303566623464646363
61663639336466666364353838323931323134333461303831383265626139303135303566376433
64383339373961303137616530616632366562326662646131363534613065623363633731313639
36353363633836316436666564396438353161623765356230333166346436346662373032336263
34613135623138306331626264316132363838376363373462616338613432343737646231333563
33633062613030643832663263376231316431616239373639646532623639646362393234656364
61393462346631633365613463323361626664316563656461646137386332366565366135623364
36343664333039343538353663346532623733386464306265396565363966363535353837366238
36643635623131313636393237643737343565656166653337656666636231343066383962306539
64303666613437353039353630353633353630336336636539333166373561626634353363623765
62626464386130646536323933653464656332373632366535633436346336306337313063356466
66663233616434383230316564343132663132373431396137623334333636363231336334333535
63336464623736306531653039333833316631393636363861613938386563613136636561626663
66323638653337333732326335376630633065623437386330323136623766313334306663613866
38383636353934386662633232303239656134633162396432393363336138366239323330643161
39666333393032373363633435316136366663643931366561643735633262323236373465323363
34323163353461616433613464646435326335336464333962646361666662656566636339646335
31633266663761666432656464323135343534346663383862306461323762306461626161356265
64653965643563643263386430653933613566303537636563636536366133383838336335316363
31653666323965346535646439316163346166343261656432343465386634313037323736376464
3562623165376161663466356130613064366433323662346430

View File

@@ -0,0 +1,20 @@
---
# =============================================================================
# Gitea Migration Vault Secrets
# =============================================================================
# Copy to vault.yml and encrypt:
# cp vault.yml.example vault.yml
# # Edit vault.yml with your values
# ansible-vault encrypt vault.yml
#
# Run playbook with:
# ansible-playbook -i inventory.yml playbook.yml --ask-vault-pass
# =============================================================================
# =============================================================================
# Authentik API Access
# =============================================================================
# Bootstrap token from Authentik deployment
# Get from VPS:
# ssh root@auth.stuslab.cc "grep AUTHENTIK_BOOTSTRAP_TOKEN /opt/authentik/authentik.env"
vault_authentik_bootstrap_token: "PASTE_AUTHENTIK_BOOTSTRAP_TOKEN_HERE"

View File

@@ -0,0 +1,9 @@
---
all:
children:
gitea_servers:
hosts:
gitea-homelab:
ansible_host: 94.130.181.201
ansible_user: root
ansible_python_interpreter: /usr/bin/python3

View File

@@ -0,0 +1,308 @@
---
# =============================================================================
# Gitea Migration Playbook
# =============================================================================
# Migrates Gitea from stuslab.cc to code.stuslab.cc with Authentik OAuth
#
# Prerequisites:
# 1. Authentik deployed at auth.stuslab.cc
# 2. DNS record: code.stuslab.cc -> 94.130.181.201
# 3. group_vars/vault.yml with authentik bootstrap token
#
# Usage:
# ansible-playbook -i inventory.yml playbook.yml --ask-vault-pass
#
# What this playbook does:
# 1. Validates existing Gitea installation
# 2. Creates backup of gitea_data/
# 3. Updates app.ini with new domain settings
# 4. Deploys updated Caddyfile with redirect
# 5. Creates OAuth application in Authentik
# 6. Verifies migration success
# =============================================================================
- name: Migrate Gitea to code.stuslab.cc
hosts: gitea_servers
become: true
vars_files:
- group_vars/gitea_servers.yml
- group_vars/vault.yml
# ===========================================================================
# Pre-flight Validation
# ===========================================================================
pre_tasks:
- name: Validate required variables
ansible.builtin.assert:
that:
- gitea_domain is defined
- gitea_old_domain is defined
- authentik_domain is defined
- vault_authentik_bootstrap_token is defined
- vault_authentik_bootstrap_token != "PASTE_AUTHENTIK_BOOTSTRAP_TOKEN_HERE"
fail_msg: |
Required variables not configured!
1. Copy vault.yml.example to vault.yml
2. Add your Authentik bootstrap token
3. Encrypt with: ansible-vault encrypt group_vars/vault.yml
- name: Check existing Gitea installation
ansible.builtin.stat:
path: "{{ gitea_base_dir }}/gitea_data/gitea/conf/app.ini"
register: existing_gitea
- name: Fail if no existing Gitea found
ansible.builtin.fail:
msg: |
No existing Gitea installation found at {{ gitea_base_dir }}
Expected app.ini at: {{ gitea_base_dir }}/gitea_data/gitea/conf/app.ini
when: not existing_gitea.stat.exists
- name: Display pre-flight status
ansible.builtin.debug:
msg: |
============================================
Gitea Migration Pre-flight Check
============================================
Current domain: {{ gitea_old_domain }}
Target domain: {{ gitea_domain }}
Authentik: {{ authentik_domain }}
Base dir: {{ gitea_base_dir }}
============================================
- name: Verify Authentik is reachable
ansible.builtin.uri:
url: "https://{{ authentik_domain }}/api/v3/core/brands/"
method: GET
headers:
Authorization: "Bearer {{ vault_authentik_bootstrap_token }}"
status_code: 200
timeout: 30
register: authentik_check
ignore_errors: true
- name: Warn if Authentik not reachable
ansible.builtin.debug:
msg: |
WARNING: Authentik not reachable at https://{{ authentik_domain }}
OAuth setup will be skipped. You can run it manually later.
when: authentik_check.failed
tasks:
# =========================================================================
# Stage 1: Backup (skip if recent backup exists)
# =========================================================================
- name: Create backup directory
ansible.builtin.file:
path: "{{ gitea_backup_dir }}"
state: directory
mode: "0755"
when: gitea_create_backup
- name: Check for existing backup from today
ansible.builtin.find:
paths: "{{ gitea_backup_dir }}"
patterns: "gitea-backup-{{ ansible_date_time.date | replace('-', '') }}*.tar.gz"
register: existing_backups
when: gitea_create_backup
- name: Set backup needed flag
ansible.builtin.set_fact:
backup_needed: "{{ gitea_create_backup and (existing_backups.files | length == 0) }}"
- name: Skip backup message
ansible.builtin.debug:
msg: "Backup already exists from today: {{ existing_backups.files[0].path | basename }}. Skipping backup."
when:
- gitea_create_backup
- existing_backups.files | length > 0
- name: Stop Gitea container for consistent backup
ansible.builtin.command:
cmd: docker compose stop gitea
chdir: "{{ gitea_base_dir }}"
when: backup_needed
changed_when: true
- name: Generate backup timestamp
ansible.builtin.set_fact:
backup_timestamp: "{{ ansible_date_time.iso8601_basic_short }}"
when: backup_needed
- name: Create backup archive
ansible.builtin.archive:
path: "{{ gitea_data_dir }}"
dest: "{{ gitea_backup_dir }}/gitea-backup-{{ backup_timestamp }}.tar.gz"
format: gz
when: backup_needed
- name: Display backup status
ansible.builtin.debug:
msg: "Backup created: {{ gitea_backup_dir }}/gitea-backup-{{ backup_timestamp }}.tar.gz"
when: backup_needed
- name: Upload backup to GDrive (if configured)
ansible.builtin.command:
cmd: "rclone copy {{ gitea_backup_dir }}/gitea-backup-{{ backup_timestamp }}.tar.gz GDrive:backups/gitea/"
when:
- backup_needed
- gitea_backup_to_gdrive
ignore_errors: true
register: gdrive_upload
changed_when: gdrive_upload.rc == 0
# =========================================================================
# Stage 2: Domain Migration (app.ini)
# =========================================================================
- name: Update app.ini ROOT_URL
ansible.builtin.lineinfile:
path: "{{ gitea_data_dir }}/gitea/conf/app.ini"
regexp: '^ROOT_URL\s*='
line: "ROOT_URL = https://{{ gitea_domain }}/"
backup: true
- name: Update app.ini SSH_DOMAIN
ansible.builtin.lineinfile:
path: "{{ gitea_data_dir }}/gitea/conf/app.ini"
regexp: '^SSH_DOMAIN\s*='
line: "SSH_DOMAIN = {{ gitea_ssh_domain }}"
- name: Update app.ini DOMAIN
ansible.builtin.lineinfile:
path: "{{ gitea_data_dir }}/gitea/conf/app.ini"
regexp: '^DOMAIN\s*='
line: "DOMAIN = {{ gitea_domain }}"
# =========================================================================
# Stage 3: Deploy Updated Configuration
# =========================================================================
- name: Deploy docker-compose.yml
ansible.builtin.template:
src: templates/docker-compose.yml.j2
dest: "{{ gitea_base_dir }}/docker-compose.yml"
mode: "0644"
backup: true
- name: Deploy Caddyfile with domain redirect
ansible.builtin.template:
src: templates/Caddyfile.j2
dest: "{{ gitea_base_dir }}/Caddyfile"
mode: "0644"
backup: true
- name: Start services
ansible.builtin.command:
cmd: docker compose up -d
chdir: "{{ gitea_base_dir }}"
changed_when: true
- name: Wait for Caddy to start (port 443)
ansible.builtin.wait_for:
port: 443
host: 127.0.0.1
delay: 5
timeout: 60
- name: Wait for Gitea container to be healthy
ansible.builtin.command:
cmd: docker compose ps gitea --format json
chdir: "{{ gitea_base_dir }}"
register: gitea_container
until: "'running' in gitea_container.stdout"
retries: 12
delay: 5
changed_when: false
# =========================================================================
# Stage 4: Authentik OAuth Setup
# =========================================================================
- name: Install jq for OAuth setup script
ansible.builtin.apt:
name: jq
state: present
update_cache: true
when: not authentik_check.failed
- name: Deploy Gitea OAuth setup script
ansible.builtin.template:
src: templates/setup-gitea-oauth.sh.j2
dest: "{{ gitea_base_dir }}/setup-gitea-oauth.sh"
mode: "0755"
when: not authentik_check.failed
- name: Run Gitea OAuth setup on Authentik
ansible.builtin.command:
cmd: "{{ gitea_base_dir }}/setup-gitea-oauth.sh"
register: oauth_setup
when: not authentik_check.failed
changed_when: true
- name: Display OAuth setup output
ansible.builtin.debug:
var: oauth_setup.stdout_lines
when:
- not authentik_check.failed
- oauth_setup is defined
# =========================================================================
# Stage 5: Verification
# =========================================================================
- name: Wait for Gitea to be healthy on new domain
ansible.builtin.uri:
url: "https://{{ gitea_domain }}/api/v1/version"
method: GET
status_code: 200
timeout: 30
validate_certs: true
register: gitea_health
until: gitea_health.status == 200
retries: 12
delay: 10
ignore_errors: true
- name: Check old domain redirect
ansible.builtin.uri:
url: "https://{{ gitea_old_domain }}/"
method: GET
follow_redirects: none
status_code: [301, 302, 308]
validate_certs: true
register: redirect_check
ignore_errors: true
- name: Display migration status
ansible.builtin.debug:
msg: |
============================================
Gitea Migration Complete!
============================================
New URL: https://{{ gitea_domain }}
Old URL: https://{{ gitea_old_domain }} (redirects)
Health check: {{ 'PASSED' if gitea_health.status == 200 else 'PENDING - may need DNS propagation' }}
Redirect check: {{ 'PASSED' if redirect_check.status in [301, 302, 308] else 'PENDING' }}
Backup: {{ gitea_backup_dir }}/gitea-backup-{{ backup_timestamp | default('N/A') }}.tar.gz
============================================
MANUAL STEPS REQUIRED:
============================================
1. DNS (if not done):
Add A record: code.stuslab.cc -> 94.130.181.201
2. OAuth Configuration in Gitea UI:
- Go to: https://{{ gitea_domain }}/admin/auths/new
- See credentials: cat /tmp/gitea-oauth-credentials.json
3. Test git operations:
ssh -T git@{{ gitea_ssh_domain }} -p {{ gitea_ssh_port }}
git clone git@{{ gitea_ssh_domain }}:user/repo.git
============================================
View logs:
ssh root@{{ ansible_host }} "cd {{ gitea_base_dir }} && docker compose logs -f"
============================================

View File

@@ -0,0 +1,17 @@
{
email {{ letsencrypt_email }}
}
# =============================================================================
# Primary Domain - Gitea
# =============================================================================
{{ gitea_domain }} {
reverse_proxy gitea:{{ gitea_http_port }}
}
# =============================================================================
# Old Domain - Permanent Redirect
# =============================================================================
{{ gitea_old_domain }} {
redir https://{{ gitea_domain }}{uri} permanent
}

View File

@@ -0,0 +1,49 @@
networks:
gitea:
external: false
services:
gitea:
image: {{ gitea_image }}
container_name: gitea
restart: unless-stopped
networks:
- gitea
environment:
- USER_UID=1000
- USER_GID=1000
volumes:
- {{ gitea_data_dir }}:/data
- /etc/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro
ports:
- "{{ gitea_ssh_port }}:22"
logging:
driver: "json-file"
options:
max-size: "100m"
max-file: "2"
caddy:
image: caddy:alpine
container_name: caddy
restart: unless-stopped
networks:
- gitea
ports:
- "80:80"
- "443:443"
- "443:443/udp"
volumes:
- {{ gitea_base_dir }}/Caddyfile:/etc/caddy/Caddyfile
- caddy_data:/data
- caddy_config:/config
logging:
driver: "json-file"
options:
max-size: "100m"
max-file: "2"
volumes:
caddy_data:
caddy_config:

View File

@@ -0,0 +1,252 @@
#!/bin/bash
# =============================================================================
# Gitea OAuth Application Setup for Authentik
# =============================================================================
# Creates OAuth2 provider and application in Authentik for Gitea
# Outputs credentials for manual Gitea UI configuration
#
# Generated by ansible - do not edit manually
# =============================================================================
set -e
AUTHENTIK_DOMAIN="{{ authentik_domain }}"
GITEA_DOMAIN="{{ gitea_domain }}"
CLIENT_ID="{{ gitea_oauth_client_id }}"
PROVIDER_NAME="{{ gitea_oauth_provider_name }}"
OUTPUT_FILE="/tmp/gitea-oauth-credentials.json"
# Bootstrap token from Authentik
API_TOKEN="{{ vault_authentik_bootstrap_token }}"
echo "============================================"
echo "Gitea OAuth Application Setup"
echo "============================================"
echo ""
echo "Authentik: https://${AUTHENTIK_DOMAIN}"
echo "Gitea: https://${GITEA_DOMAIN}"
echo ""
# -----------------------------------------------------------------------------
# Test API access
# -----------------------------------------------------------------------------
echo "Testing Authentik API access..."
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" \
"https://${AUTHENTIK_DOMAIN}/api/v3/core/brands/" \
-H "Authorization: Bearer ${API_TOKEN}" \
-H "Accept: application/json")
if [ "$HTTP_CODE" != "200" ]; then
echo "ERROR: API authentication failed (HTTP $HTTP_CODE)"
echo "Check that vault_authentik_bootstrap_token is correct"
exit 1
fi
echo "Authentik API ready!"
echo ""
# -----------------------------------------------------------------------------
# Get authorization flow PK
# -----------------------------------------------------------------------------
echo "Finding authorization flow..."
AUTH_FLOW_RESPONSE=$(curl -s \
"https://${AUTHENTIK_DOMAIN}/api/v3/flows/instances/?designation=authorization" \
-H "Authorization: Bearer ${API_TOKEN}" \
-H "Accept: application/json")
AUTH_FLOW_PK=$(echo "$AUTH_FLOW_RESPONSE" | jq -r '.results[0].pk')
echo "Authorization flow: $AUTH_FLOW_PK"
# -----------------------------------------------------------------------------
# Get invalidation flow PK
# -----------------------------------------------------------------------------
echo "Finding invalidation flow..."
INVALID_FLOW_RESPONSE=$(curl -s \
"https://${AUTHENTIK_DOMAIN}/api/v3/flows/instances/?designation=invalidation" \
-H "Authorization: Bearer ${API_TOKEN}" \
-H "Accept: application/json")
INVALID_FLOW_PK=$(echo "$INVALID_FLOW_RESPONSE" | jq -r '.results[0].pk')
echo "Invalidation flow: $INVALID_FLOW_PK"
# -----------------------------------------------------------------------------
# Get signing certificate
# -----------------------------------------------------------------------------
echo "Finding signing certificate..."
CERT_RESPONSE=$(curl -s \
"https://${AUTHENTIK_DOMAIN}/api/v3/crypto/certificatekeypairs/" \
-H "Authorization: Bearer ${API_TOKEN}" \
-H "Accept: application/json")
SIGNING_KEY_PK=$(echo "$CERT_RESPONSE" | jq -r '.results[0].pk')
echo "Signing key: $SIGNING_KEY_PK"
# -----------------------------------------------------------------------------
# Get scope mappings
# -----------------------------------------------------------------------------
echo "Getting scope mappings..."
SCOPE_MAPPINGS=$(curl -s \
"https://${AUTHENTIK_DOMAIN}/api/v3/propertymappings/provider/scope/" \
-H "Authorization: Bearer ${API_TOKEN}" \
-H "Accept: application/json")
OPENID_PK=$(echo "$SCOPE_MAPPINGS" | jq -r '.results[] | select(.scope_name=="openid") | .pk')
PROFILE_PK=$(echo "$SCOPE_MAPPINGS" | jq -r '.results[] | select(.scope_name=="profile") | .pk')
EMAIL_PK=$(echo "$SCOPE_MAPPINGS" | jq -r '.results[] | select(.scope_name=="email") | .pk')
echo "Scopes: openid=$OPENID_PK, profile=$PROFILE_PK, email=$EMAIL_PK"
echo ""
# -----------------------------------------------------------------------------
# Check if provider already exists
# -----------------------------------------------------------------------------
echo "Checking for existing Gitea provider..."
EXISTING_PROVIDER=$(curl -s \
"https://${AUTHENTIK_DOMAIN}/api/v3/providers/oauth2/?name=Gitea" \
-H "Authorization: Bearer ${API_TOKEN}" \
-H "Accept: application/json")
EXISTING_PK=$(echo "$EXISTING_PROVIDER" | jq -r '.results[0].pk // empty')
if [ -n "$EXISTING_PK" ] && [ "$EXISTING_PK" != "null" ]; then
echo "Provider already exists (PK: $EXISTING_PK), updating..."
# Update existing provider
PROVIDER_RESPONSE=$(curl -s -X PATCH \
"https://${AUTHENTIK_DOMAIN}/api/v3/providers/oauth2/${EXISTING_PK}/" \
-H "Authorization: Bearer ${API_TOKEN}" \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-d "{
\"redirect_uris\": [
{\"matching_mode\": \"strict\", \"url\": \"https://${GITEA_DOMAIN}/user/oauth2/${PROVIDER_NAME}/callback\"}
]
}")
PROVIDER_PK="$EXISTING_PK"
CLIENT_SECRET=$(echo "$EXISTING_PROVIDER" | jq -r '.results[0].client_secret // empty')
else
# -----------------------------------------------------------------------------
# Create OAuth2 Provider (confidential client for Gitea)
# -----------------------------------------------------------------------------
echo "Creating Gitea OAuth2 Provider..."
PROVIDER_RESPONSE=$(curl -s -X POST \
"https://${AUTHENTIK_DOMAIN}/api/v3/providers/oauth2/" \
-H "Authorization: Bearer ${API_TOKEN}" \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-d "{
\"name\": \"Gitea\",
\"authorization_flow\": \"${AUTH_FLOW_PK}\",
\"invalidation_flow\": \"${INVALID_FLOW_PK}\",
\"signing_key\": \"${SIGNING_KEY_PK}\",
\"client_type\": \"confidential\",
\"client_id\": \"${CLIENT_ID}\",
\"redirect_uris\": [
{\"matching_mode\": \"strict\", \"url\": \"https://${GITEA_DOMAIN}/user/oauth2/${PROVIDER_NAME}/callback\"}
],
\"access_code_validity\": \"minutes=10\",
\"access_token_validity\": \"hours=1\",
\"refresh_token_validity\": \"days=30\",
\"property_mappings\": [\"${OPENID_PK}\", \"${PROFILE_PK}\", \"${EMAIL_PK}\"],
\"sub_mode\": \"user_email\",
\"include_claims_in_id_token\": true,
\"issuer_mode\": \"per_provider\"
}")
PROVIDER_PK=$(echo "$PROVIDER_RESPONSE" | jq -r '.pk // empty')
CLIENT_SECRET=$(echo "$PROVIDER_RESPONSE" | jq -r '.client_secret // empty')
if [ -z "$PROVIDER_PK" ] || [ "$PROVIDER_PK" = "null" ]; then
echo "ERROR: Failed to create provider"
echo "$PROVIDER_RESPONSE" | jq .
exit 1
fi
fi
echo "Provider PK: $PROVIDER_PK"
echo ""
# -----------------------------------------------------------------------------
# Check if application already exists
# -----------------------------------------------------------------------------
echo "Checking for existing Gitea application..."
EXISTING_APP=$(curl -s \
"https://${AUTHENTIK_DOMAIN}/api/v3/core/applications/?slug=gitea" \
-H "Authorization: Bearer ${API_TOKEN}" \
-H "Accept: application/json")
EXISTING_APP_SLUG=$(echo "$EXISTING_APP" | jq -r '.results[0].slug // empty')
if [ -z "$EXISTING_APP_SLUG" ] || [ "$EXISTING_APP_SLUG" = "null" ]; then
# -----------------------------------------------------------------------------
# Create Application
# -----------------------------------------------------------------------------
echo "Creating Gitea Application..."
APP_RESPONSE=$(curl -s -X POST \
"https://${AUTHENTIK_DOMAIN}/api/v3/core/applications/" \
-H "Authorization: Bearer ${API_TOKEN}" \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-d "{
\"name\": \"Gitea\",
\"slug\": \"gitea\",
\"provider\": ${PROVIDER_PK},
\"meta_launch_url\": \"https://${GITEA_DOMAIN}\",
\"open_in_new_tab\": false
}")
APP_SLUG=$(echo "$APP_RESPONSE" | jq -r '.slug // empty')
if [ -z "$APP_SLUG" ] || [ "$APP_SLUG" = "null" ]; then
echo "WARNING: Failed to create application (may already exist)"
else
echo "Application created: $APP_SLUG"
fi
else
echo "Application already exists: $EXISTING_APP_SLUG"
fi
echo ""
# -----------------------------------------------------------------------------
# Output credentials
# -----------------------------------------------------------------------------
cat > "$OUTPUT_FILE" << EOF
{
"client_id": "${CLIENT_ID}",
"client_secret": "${CLIENT_SECRET}",
"auto_discover_url": "https://${AUTHENTIK_DOMAIN}/application/o/gitea/.well-known/openid-configuration",
"scopes": "email profile",
"provider_name": "${PROVIDER_NAME}"
}
EOF
echo "============================================"
echo "OAuth Setup Complete!"
echo "============================================"
echo ""
echo "Credentials saved to: ${OUTPUT_FILE}"
echo ""
echo "========================================"
echo "MANUAL CONFIGURATION REQUIRED IN GITEA"
echo "========================================"
echo ""
echo "1. Log into Gitea as admin:"
echo " https://${GITEA_DOMAIN}/user/login"
echo ""
echo "2. Navigate to:"
echo " Site Administration -> Authentication Sources -> Add"
echo ""
echo "3. Fill in the form:"
echo " Authentication Type: OAuth2"
echo " Authentication Name: ${PROVIDER_NAME}"
echo " OAuth2 Provider: OpenID Connect"
echo " Client ID: ${CLIENT_ID}"
echo " Client Secret: ${CLIENT_SECRET}"
echo " OpenID Connect Auto Discovery URL:"
echo " https://${AUTHENTIK_DOMAIN}/application/o/gitea/.well-known/openid-configuration"
echo " Additional Scopes: email profile"
echo ""
echo "4. Click 'Add Authentication Source'"
echo ""
echo "5. Test by logging out and clicking 'Sign in with ${PROVIDER_NAME}'"
echo ""
echo "========================================"
echo ""
echo "Credentials JSON:"
cat "$OUTPUT_FILE"
echo ""