--- # Run only in an approved outage with -e atlas_gitea_owner_migration=true. - name: Move live Gitea from the legacy host account to admin tags: [atlas, gitea_owner_migration] when: atlas_gitea_owner_migration | bool block: - name: Refuse a check-mode owner migration ansible.builtin.assert: that: not ansible_check_mode fail_msg: The owner migration requires an explicit live outage. - name: Inspect the Gitea dataset owner ansible.builtin.stat: path: "{{ atlas_gitea_mountpoint }}" register: atlas_gitea_migration_owner - name: Require either the legacy owner or an already migrated dataset ansible.builtin.assert: that: - atlas_gitea_migration_owner.stat.isdir | default(false) - atlas_gitea_migration_owner.stat.uid | int in [atlas_gitea_legacy_uid | int, atlas_admin_uid | int] fail_msg: Refusing to modify a Gitea dataset with an unexpected owner. - name: Migrate only a legacy-owned Gitea dataset when: atlas_gitea_migration_owner.stat.uid | int == atlas_gitea_legacy_uid | int block: - name: Require the final cutover marker and configuration ansible.builtin.stat: path: "{{ item }}" loop: - "{{ atlas_gitea_mountpoint }}/.final-sha256" - "{{ atlas_gitea_mountpoint }}/config/app.ini" register: atlas_gitea_migration_files - name: Refuse migration without both final data and configuration ansible.builtin.assert: that: atlas_gitea_migration_files.results | map(attribute='stat.isreg') | min - name: Check that admin has no existing Gitea Quadlet ansible.builtin.stat: path: "{{ atlas_gitea_quadlet_dir }}/atlas-gitea.container" register: atlas_gitea_admin_quadlet - name: Refuse to overwrite an existing admin Quadlet ansible.builtin.assert: that: not atlas_gitea_admin_quadlet.stat.exists - name: Check pool health before the outage ansible.builtin.command: argv: [zpool, status, -x, "{{ atlas_zfs_pool }}"] register: atlas_gitea_pool_before changed_when: false failed_when: "'is healthy' not in atlas_gitea_pool_before.stdout" - name: Ensure the admin Gitea image is available before stopping the source ansible.builtin.import_tasks: gitea_image.yml - name: Stop, snapshot and test the admin-owned staging service block: - name: Stop and disable the legacy Gitea user service become_user: "{{ atlas_gitea_legacy_username }}" ansible.builtin.systemd: name: atlas-gitea.service scope: user state: stopped enabled: false environment: XDG_RUNTIME_DIR: "/run/user/{{ atlas_gitea_legacy_uid }}" DBUS_SESSION_BUS_ADDRESS: "unix:path=/run/user/{{ atlas_gitea_legacy_uid }}/bus" - name: Record the migration snapshot name ansible.builtin.set_fact: atlas_gitea_migration_snapshot: >- {{ atlas_gitea_dataset }}@gitea-owner-migration-{{ ansible_facts.date_time.iso8601_basic_short }} - name: Snapshot the stopped Gitea dataset for manual recovery ansible.builtin.command: argv: [zfs, snapshot, "{{ atlas_gitea_migration_snapshot }}"] - name: Transfer only the Gitea dataset to admin ansible.builtin.file: path: "{{ atlas_gitea_mountpoint }}" state: directory owner: "{{ atlas_admin_username }}" group: "{{ atlas_admin_group }}" recurse: true - name: Set the actual internal Unix process user ansible.builtin.lineinfile: path: "{{ atlas_gitea_mountpoint }}/config/app.ini" regexp: '^RUN_USER\s*=' line: RUN_USER = gitea mode: "0600" no_log: true diff: false - name: Preserve public git clone URLs independently of the Unix user community.general.ini_file: path: "{{ atlas_gitea_mountpoint }}/config/app.ini" section: server option: "{{ item }}" value: git mode: "0600" no_extra_spaces: false loop: [BUILTIN_SSH_SERVER_USER, SSH_USER] no_log: true diff: false - name: Render admin's loopback-only staging Quadlet ansible.builtin.template: src: atlas-gitea.container.j2 dest: "{{ atlas_gitea_quadlet_dir }}/atlas-gitea.container" owner: "{{ atlas_admin_username }}" group: "{{ atlas_admin_group }}" mode: "0644" vars: atlas_gitea_production_enabled: false - name: Reload the admin user manager for staging become_user: "{{ atlas_admin_username }}" ansible.builtin.systemd: scope: user daemon_reload: true environment: XDG_RUNTIME_DIR: "/run/user/{{ atlas_admin_uid }}" DBUS_SESSION_BUS_ADDRESS: "unix:path=/run/user/{{ atlas_admin_uid }}/bus" - name: Start admin's loopback-only staging service become_user: "{{ atlas_admin_username }}" ansible.builtin.systemd: name: atlas-gitea.service scope: user state: started environment: XDG_RUNTIME_DIR: "/run/user/{{ atlas_admin_uid }}" DBUS_SESSION_BUS_ADDRESS: "unix:path=/run/user/{{ atlas_admin_uid }}/bus" - name: Verify staging HTTP before promotion ansible.builtin.uri: url: "http://127.0.0.1:{{ atlas_gitea_staging_http_port }}/" status_code: 200 register: atlas_gitea_staging_http retries: 30 delay: 2 until: atlas_gitea_staging_http is succeeded - name: Verify the container really runs as internal gitea become_user: "{{ atlas_admin_username }}" ansible.builtin.command: argv: [podman, exec, atlas-gitea, id, -un] environment: HOME: "{{ atlas_admin_home }}" XDG_RUNTIME_DIR: "/run/user/{{ atlas_admin_uid }}" register: atlas_gitea_internal_user changed_when: false failed_when: atlas_gitea_internal_user.stdout != 'gitea' - name: Verify the migrated SQLite database ansible.builtin.command: argv: - sqlite3 - "{{ atlas_gitea_mountpoint }}/data/gitea/gitea.db" - PRAGMA quick_check; register: atlas_gitea_migration_sqlite changed_when: false failed_when: atlas_gitea_migration_sqlite.stdout != 'ok' rescue: - name: Stop admin's failed staging service become_user: "{{ atlas_admin_username }}" ansible.builtin.systemd: name: atlas-gitea.service scope: user state: stopped environment: XDG_RUNTIME_DIR: "/run/user/{{ atlas_admin_uid }}" DBUS_SESSION_BUS_ADDRESS: "unix:path=/run/user/{{ atlas_admin_uid }}/bus" failed_when: false - name: Restore the original Gitea configuration from the safety snapshot ansible.builtin.command: argv: - cp - -a - "{{ atlas_gitea_mountpoint }}/.zfs/snapshot/{{ atlas_gitea_migration_snapshot.split('@')[1] }}/config/app.ini" - "{{ atlas_gitea_mountpoint }}/config/app.ini" when: atlas_gitea_migration_snapshot is defined - name: Return the Gitea dataset to the legacy account ansible.builtin.file: path: "{{ atlas_gitea_mountpoint }}" state: directory owner: "{{ atlas_gitea_legacy_username }}" group: "{{ atlas_gitea_legacy_username }}" recurse: true - name: Restart the legacy Gitea service become_user: "{{ atlas_gitea_legacy_username }}" ansible.builtin.systemd: name: atlas-gitea.service scope: user state: started enabled: true environment: XDG_RUNTIME_DIR: "/run/user/{{ atlas_gitea_legacy_uid }}" DBUS_SESSION_BUS_ADDRESS: "unix:path=/run/user/{{ atlas_gitea_legacy_uid }}/bus" - name: Report the failed migration and preserved snapshot ansible.builtin.fail: msg: >- Admin staging failed; legacy Gitea was restarted. Inspect {{ atlas_gitea_migration_snapshot | default('the host journal') }}. - name: Stop admin's validated staging service become_user: "{{ atlas_admin_username }}" ansible.builtin.systemd: name: atlas-gitea.service scope: user state: stopped environment: XDG_RUNTIME_DIR: "/run/user/{{ atlas_admin_uid }}" DBUS_SESSION_BUS_ADDRESS: "unix:path=/run/user/{{ atlas_admin_uid }}/bus" - name: Render admin's production Gitea Quadlet ansible.builtin.template: src: atlas-gitea.container.j2 dest: "{{ atlas_gitea_quadlet_dir }}/atlas-gitea.container" owner: "{{ atlas_admin_username }}" group: "{{ atlas_admin_group }}" mode: "0644" vars: atlas_gitea_production_enabled: true - name: Reload admin's production user manager become_user: "{{ atlas_admin_username }}" ansible.builtin.systemd: scope: user daemon_reload: true environment: XDG_RUNTIME_DIR: "/run/user/{{ atlas_admin_uid }}" DBUS_SESSION_BUS_ADDRESS: "unix:path=/run/user/{{ atlas_admin_uid }}/bus" - name: Enable and start admin's production Gitea become_user: "{{ atlas_admin_username }}" ansible.builtin.systemd: name: atlas-gitea.service scope: user state: started enabled: true environment: XDG_RUNTIME_DIR: "/run/user/{{ atlas_admin_uid }}" DBUS_SESSION_BUS_ADDRESS: "unix:path=/run/user/{{ atlas_admin_uid }}/bus" - name: Verify production HTTP before retiring the old Quadlet ansible.builtin.uri: url: "http://{{ atlas_gitea_bind_address }}:{{ atlas_gitea_http_port }}/" status_code: 200 register: atlas_gitea_production_http retries: 30 delay: 2 until: atlas_gitea_production_http is succeeded - name: Remove only the disabled legacy Quadlet ansible.builtin.file: path: "{{ atlas_gitea_legacy_home }}/.config/containers/systemd/atlas-gitea.container" state: absent - name: Reload the legacy user manager after Quadlet removal become_user: "{{ atlas_gitea_legacy_username }}" ansible.builtin.systemd: scope: user daemon_reload: true environment: XDG_RUNTIME_DIR: "/run/user/{{ atlas_gitea_legacy_uid }}" DBUS_SESSION_BUS_ADDRESS: "unix:path=/run/user/{{ atlas_gitea_legacy_uid }}/bus"