--- # ============================================================================= # 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" ============================================