--- - name: Resolve Windows runner profile ansible.builtin.set_fact: __windows_gitea_runner_windows_profile: >- {{ windows_gitea_runner_windows_runner.profile | default(windows_gitea_runner_windows_runner.name | default('runner')) }} - name: Resolve Windows runner-specific settings ansible.builtin.set_fact: __windows_gitea_runner_windows_name: >- {{ windows_gitea_runner_windows_runner.name | default( 'gitea-runner-' ~ __windows_gitea_runner_windows_profile ) }} __windows_gitea_runner_windows_container_name: >- {{ windows_gitea_runner_windows_runner.container_name | default( 'gitea-runner-' ~ __windows_gitea_runner_windows_profile ) }} __windows_gitea_runner_windows_image: >- {{ windows_gitea_runner_windows_runner.image | default( windows_gitea_runner_windows_image_prefix ~ ':' ~ __windows_gitea_runner_windows_profile ) }} __windows_gitea_runner_windows_labels: >- {{ windows_gitea_runner_windows_runner.labels | default( 'windows:host,' ~ __windows_gitea_runner_windows_profile ) }} __windows_gitea_runner_windows_dockerfile_src: >- {{ windows_gitea_runner_windows_runner.dockerfile_src | default( 'Dockerfile.' ~ __windows_gitea_runner_windows_profile ) }} __windows_gitea_runner_windows_config_file: >- {{ windows_gitea_runner_windows_runner.config_file | default( windows_gitea_runner_windows_config_root ~ '\\' ~ __windows_gitea_runner_windows_profile ~ '\\config.yaml' ) }} __windows_gitea_runner_windows_data_dir: >- {{ windows_gitea_runner_windows_runner.data_dir | default( windows_gitea_runner_windows_data_root ~ '\\' ~ __windows_gitea_runner_windows_profile ~ '\\data' ) }} __windows_gitea_runner_windows_log_dir: >- {{ windows_gitea_runner_windows_runner.log_dir | default( windows_gitea_runner_windows_log_root ~ '\\' ~ __windows_gitea_runner_windows_profile ) }} __windows_gitea_runner_windows_build_dir: >- {{ windows_gitea_runner_windows_build_root ~ '\\' ~ __windows_gitea_runner_windows_profile }} __windows_gitea_runner_windows_build_dockerfile_dest: >- {{ windows_gitea_runner_windows_build_root ~ '\\' ~ __windows_gitea_runner_windows_profile ~ '\\Dockerfile' }} __windows_gitea_runner_windows_dockerfile_source_path: >- {{ windows_gitea_runner_windows_dockerfile_source_root ~ '/' ~ ( windows_gitea_runner_windows_runner.dockerfile_src | default( 'Dockerfile.' ~ __windows_gitea_runner_windows_profile ) ) }} __windows_gitea_runner_windows_run_args: >- {{ windows_gitea_runner_windows_runner.run_args | default([]) }} - name: Resolve Windows runner runtime config path ansible.builtin.set_fact: __windows_gitea_runner_windows_runtime_config_file: "{{ __windows_gitea_runner_windows_data_dir }}\\config.yaml" - name: Read Windows runner Dockerfile checksum ansible.builtin.set_fact: __windows_gitea_runner_windows_profile_dockerfile_checksum: >- {{ lookup('file', __windows_gitea_runner_windows_dockerfile_source_path) | hash('sha1') }} - name: Read current Windows base image hash label ansible.windows.win_shell: | $ErrorActionPreference = "Stop" $docker = "{{ windows_gitea_runner_windows_runtime_bin }}" $image = "{{ windows_gitea_runner_windows_base_image }}" if (Get-Variable -Name PSNativeCommandUseErrorActionPreference -ErrorAction SilentlyContinue) { $PSNativeCommandUseErrorActionPreference = $false } $ErrorActionPreference = "Continue" $raw = & $docker image inspect $image 2>$null $inspectExit = $LASTEXITCODE $ErrorActionPreference = "Stop" if ($inspectExit -ne 0 -or [string]::IsNullOrWhiteSpace($raw)) { Write-Output "__MISSING__" exit 0 } $obj = $raw | ConvertFrom-Json if ($obj -is [System.Array]) { $obj = $obj[0] } $labels = $obj.Config.Labels if ($null -eq $labels) { Write-Output "__UNLABELED__" exit 0 } $key = "{{ windows_gitea_runner_windows_base_image_hash_label }}" $current = $labels.$key if ([string]::IsNullOrWhiteSpace($current)) { Write-Output "__UNLABELED__" exit 0 } Write-Output $current.Trim() become: true become_method: ansible.builtin.runas become_user: "{{ __windows_gitea_runner_windows_become_user }}" register: __windows_gitea_runner_windows_current_base_image_hash changed_when: false - name: Compute desired Windows runner image hash ansible.builtin.set_fact: __windows_gitea_runner_windows_desired_image_hash: >- {{ { 'base': __windows_gitea_runner_windows_current_base_image_hash.stdout | trim, 'profile': __windows_gitea_runner_windows_profile_dockerfile_checksum } | to_json | hash('sha1') }} - name: Compute desired Windows runner container hash ansible.builtin.set_fact: __windows_gitea_runner_windows_desired_container_hash: >- {{ { 'image': __windows_gitea_runner_windows_image, 'name': __windows_gitea_runner_windows_name, 'labels': __windows_gitea_runner_windows_labels, 'instance': windows_gitea_runner_windows_instance_url, 'config': __windows_gitea_runner_windows_runtime_config_file, 'data': __windows_gitea_runner_windows_data_dir, 'log': __windows_gitea_runner_windows_log_dir, 'run_args': __windows_gitea_runner_windows_run_args, 'restart': windows_gitea_runner_windows_restart_policy, 'image_hash': __windows_gitea_runner_windows_desired_image_hash } | to_json | hash('sha1') }} - name: Ensure Windows runner directories exist ansible.windows.win_file: path: "{{ item }}" state: directory loop: - "{{ windows_gitea_runner_windows_build_root }}" - "{{ __windows_gitea_runner_windows_build_dir }}" - "{{ __windows_gitea_runner_windows_data_dir }}" - "{{ __windows_gitea_runner_windows_log_dir }}" - name: Render Windows runner config ansible.windows.win_template: src: config.yaml.j2 dest: "{{ __windows_gitea_runner_windows_runtime_config_file }}" - name: Copy Windows runner Dockerfile to build context ansible.windows.win_copy: src: "{{ __windows_gitea_runner_windows_dockerfile_source_path }}" dest: "{{ __windows_gitea_runner_windows_build_dockerfile_dest }}" - name: Read current Windows runner image hash label ansible.windows.win_shell: | $ErrorActionPreference = "Stop" $docker = "{{ windows_gitea_runner_windows_runtime_bin }}" $image = "{{ __windows_gitea_runner_windows_image }}" if (Get-Variable -Name PSNativeCommandUseErrorActionPreference -ErrorAction SilentlyContinue) { $PSNativeCommandUseErrorActionPreference = $false } $ErrorActionPreference = "Continue" $raw = & $docker image inspect $image 2>$null $inspectExit = $LASTEXITCODE $ErrorActionPreference = "Stop" if ($inspectExit -ne 0 -or [string]::IsNullOrWhiteSpace($raw)) { Write-Output "__MISSING__" exit 0 } $obj = $raw | ConvertFrom-Json if ($obj -is [System.Array]) { $obj = $obj[0] } $labels = $obj.Config.Labels if ($null -eq $labels) { Write-Output "__UNLABELED__" exit 0 } $key = "{{ windows_gitea_runner_windows_image_hash_label }}" $current = $labels.$key if ([string]::IsNullOrWhiteSpace($current)) { Write-Output "__UNLABELED__" exit 0 } Write-Output $current.Trim() become: true become_method: ansible.builtin.runas become_user: "{{ __windows_gitea_runner_windows_become_user }}" register: __windows_gitea_runner_windows_current_image_hash changed_when: false - name: Determine if Windows runner image needs rebuild ansible.builtin.set_fact: __windows_gitea_runner_windows_needs_image_rebuild: >- {{ (__windows_gitea_runner_windows_current_image_hash.stdout | trim) != (__windows_gitea_runner_windows_desired_image_hash | trim) }} - name: Rebuild Windows runner image when Dockerfile changed ansible.windows.win_shell: | $ErrorActionPreference = "Stop" $docker = "{{ windows_gitea_runner_windows_runtime_bin }}" & $docker build ` --label "{{ windows_gitea_runner_windows_image_hash_label }}={{ __windows_gitea_runner_windows_desired_image_hash }}" ` --tag "{{ __windows_gitea_runner_windows_image }}" ` --file "{{ __windows_gitea_runner_windows_build_dockerfile_dest }}" ` "{{ __windows_gitea_runner_windows_build_dir }}" if ($LASTEXITCODE -ne 0) { exit 1 } Write-Output "__BUILT__" become: true become_method: ansible.builtin.runas become_user: "{{ __windows_gitea_runner_windows_become_user }}" register: __windows_gitea_runner_windows_build changed_when: "'__BUILT__' in __windows_gitea_runner_windows_build.stdout" when: __windows_gitea_runner_windows_needs_image_rebuild - name: Read current Windows runner container hash label ansible.windows.win_shell: | $ErrorActionPreference = "Stop" $name = "{{ __windows_gitea_runner_windows_container_name }}" $docker = "{{ windows_gitea_runner_windows_runtime_bin }}" if (Get-Variable -Name PSNativeCommandUseErrorActionPreference -ErrorAction SilentlyContinue) { $PSNativeCommandUseErrorActionPreference = $false } $ErrorActionPreference = "Continue" $raw = & $docker inspect $name 2>$null $inspectExit = $LASTEXITCODE $ErrorActionPreference = "Stop" if ($inspectExit -ne 0 -or [string]::IsNullOrWhiteSpace($raw)) { Write-Output "__MISSING__" exit 0 } $obj = $raw | ConvertFrom-Json if ($obj -is [System.Array]) { $obj = $obj[0] } $labels = $obj.Config.Labels if ($null -eq $labels) { Write-Output "__UNLABELED__" exit 0 } $key = "{{ windows_gitea_runner_windows_container_hash_label }}" $current = $labels.$key if ([string]::IsNullOrWhiteSpace($current)) { Write-Output "__UNLABELED__" exit 0 } Write-Output $current.Trim() become: true become_method: ansible.builtin.runas become_user: "{{ __windows_gitea_runner_windows_become_user }}" register: __windows_gitea_runner_windows_current_hash changed_when: false - name: Determine if Windows runner container needs recreation ansible.builtin.set_fact: __windows_gitea_runner_windows_needs_recreate: >- {{ (__windows_gitea_runner_windows_current_hash.stdout | trim) != (__windows_gitea_runner_windows_desired_container_hash | trim) }} - name: Remove stale Windows runner container before recreate ansible.windows.win_shell: | $docker = "{{ windows_gitea_runner_windows_runtime_bin }}" & $docker rm -f "{{ __windows_gitea_runner_windows_container_name }}" 2>$null become: true become_method: ansible.builtin.runas become_user: "{{ __windows_gitea_runner_windows_become_user }}" when: __windows_gitea_runner_windows_needs_recreate changed_when: true failed_when: false - name: Ensure Windows runner container is running ansible.windows.win_shell: | $ErrorActionPreference = "Stop" $docker = "{{ windows_gitea_runner_windows_runtime_bin }}" $name = "{{ __windows_gitea_runner_windows_container_name }}" if (Get-Variable -Name PSNativeCommandUseErrorActionPreference -ErrorAction SilentlyContinue) { $PSNativeCommandUseErrorActionPreference = $false } $ErrorActionPreference = "Continue" $null = & $docker inspect $name 2>$null $inspectExit = $LASTEXITCODE $ErrorActionPreference = "Stop" if ($inspectExit -eq 0) { & $docker start $name 2>$null | Out-Null Write-Output "__STARTED_EXISTING__" exit 0 } $env:CONFIG_FILE = "C:\runner-data\config.yaml" $env:GITEA_INSTANCE_URL = {{ windows_gitea_runner_windows_instance_url | to_json }} $env:GITEA_RUNNER_REGISTRATION_TOKEN = {{ windows_gitea_runner_windows_registration_token | to_json }} $env:GITEA_RUNNER_NAME = {{ __windows_gitea_runner_windows_name | to_json }} $env:GITEA_RUNNER_LABELS = {{ __windows_gitea_runner_windows_labels | to_json }} & $docker run -d --name $name --restart "{{ windows_gitea_runner_windows_restart_policy }}" ` @({{ __windows_gitea_runner_windows_run_args | map('to_json') | join(', ') }}) ` --label "{{ windows_gitea_runner_windows_container_hash_label }}={{ __windows_gitea_runner_windows_desired_container_hash }}" ` --mount "type=bind,source={{ __windows_gitea_runner_windows_data_dir }},target=C:\runner-data" ` -e "CONFIG_FILE" ` -e "GITEA_INSTANCE_URL" ` -e "GITEA_RUNNER_REGISTRATION_TOKEN" ` -e "GITEA_RUNNER_NAME" ` -e "GITEA_RUNNER_LABELS" ` "{{ __windows_gitea_runner_windows_image }}" | Out-Null if ($LASTEXITCODE -ne 0) { exit 1 } Write-Output "__CREATED__" become: true become_method: ansible.builtin.runas become_user: "{{ __windows_gitea_runner_windows_become_user }}" register: __windows_gitea_runner_windows_container changed_when: "'__CREATED__' in __windows_gitea_runner_windows_container.stdout" failed_when: false no_log: true - name: Gather Windows runner container diagnostics when startup fails ansible.windows.win_shell: | $ErrorActionPreference = "Stop" $docker = "{{ windows_gitea_runner_windows_runtime_bin }}" $name = "{{ __windows_gitea_runner_windows_container_name }}" $image = "{{ __windows_gitea_runner_windows_image }}" $hostData = "{{ __windows_gitea_runner_windows_data_dir }}" $hostConfig = "{{ __windows_gitea_runner_windows_runtime_config_file }}" if (Get-Variable -Name PSNativeCommandUseErrorActionPreference -ErrorAction SilentlyContinue) { $PSNativeCommandUseErrorActionPreference = $false } $ErrorActionPreference = "Continue" $imageInspect = & $docker image inspect --format "{{ '{{' }}.Id{{ '}}' }}" $image 2>$null if ($LASTEXITCODE -ne 0 -or [string]::IsNullOrWhiteSpace($imageInspect)) { $imageInspect = "missing" } $hostDataExists = Test-Path -Path $hostData $hostConfigExists = Test-Path -Path $hostConfig $inspectFormat = ( "{{ '{{' }}.State.Status{{ '}}' }}|" + "{{ '{{' }}.State.ExitCode{{ '}}' }}|" + "{{ '{{' }}.State.Error{{ '}}' }}" ) $inspect = & $docker inspect --format $inspectFormat $name 2>$null $inspectExit = $LASTEXITCODE if ($inspectExit -ne 0) { $inspect = "missing" } $logs = & $docker logs --tail {{ windows_gitea_runner_windows_container_log_tail }} $name 2>&1 $logsExit = $LASTEXITCODE if ($logsExit -ne 0 -or [string]::IsNullOrWhiteSpace($logs)) { $logs = "no logs available" } $diagName = "$name-diag-$([guid]::NewGuid().ToString('N').Substring(0, 8))" $diagRun = & $docker run --rm --name $diagName ` --entrypoint "C:\Windows\System32\cmd.exe" ` @({{ __windows_gitea_runner_windows_run_args | map('to_json') | join(', ') }}) ` --mount "type=bind,source={{ __windows_gitea_runner_windows_data_dir }},target=C:\runner-data" ` -e "CONFIG_FILE=C:\runner-data\config.yaml" ` -e "GITEA_INSTANCE_URL={{ windows_gitea_runner_windows_instance_url }}" ` -e "GITEA_RUNNER_REGISTRATION_TOKEN=diag-token" ` -e "GITEA_RUNNER_NAME=diag-runner" ` -e "GITEA_RUNNER_LABELS={{ __windows_gitea_runner_windows_labels }}" ` "{{ __windows_gitea_runner_windows_image }}" /S /C "echo DIAG_OK" 2>&1 $diagRunExit = $LASTEXITCODE if ([string]::IsNullOrWhiteSpace($diagRun)) { $diagRun = "(empty)" } $ErrorActionPreference = "Stop" Write-Output "IMAGE: $imageInspect" Write-Output "HOST_DATA_EXISTS: $hostDataExists" Write-Output "HOST_CONFIG_EXISTS: $hostConfigExists" Write-Output "STATE: $inspect" Write-Output "---DIAG_RUN (rc=$diagRunExit)---" Write-Output $diagRun Write-Output "---LOGS---" Write-Output $logs become: true become_method: ansible.builtin.runas become_user: "{{ __windows_gitea_runner_windows_become_user }}" register: __windows_gitea_runner_windows_start_failure_diag changed_when: false when: (__windows_gitea_runner_windows_container.rc | default(0)) != 0 - name: Fail with Windows runner startup diagnostics ansible.builtin.fail: msg: | Windows runner container failed to start (rc={{ __windows_gitea_runner_windows_container.rc | default('unknown') }}). RUN_STDERR={{ (__windows_gitea_runner_windows_container.stderr | default('')) | replace(windows_gitea_runner_windows_registration_token | default(''), '***') }} RUN_STDOUT={{ (__windows_gitea_runner_windows_container.stdout | default('')) | replace(windows_gitea_runner_windows_registration_token | default(''), '***') }} {{ [ __windows_gitea_runner_windows_start_failure_diag.stdout | default(''), __windows_gitea_runner_windows_start_failure_diag.stderr | default('') ] | join('\n') | trim }} when: (__windows_gitea_runner_windows_container.rc | default(0)) != 0 - name: Verify Windows runner container state ansible.windows.win_shell: | $docker = "{{ windows_gitea_runner_windows_runtime_bin }}" & $docker inspect "{{ __windows_gitea_runner_windows_container_name }}" --format "{{ '{{' }}.State.Status{{ '}}' }}" become: true become_method: ansible.builtin.runas become_user: "{{ __windows_gitea_runner_windows_become_user }}" register: __windows_gitea_runner_windows_status changed_when: false failed_when: (__windows_gitea_runner_windows_status.stdout | trim) != 'running' when: (__windows_gitea_runner_windows_container.rc | default(0)) == 0 - name: Show tail of Windows runner logs ansible.windows.win_shell: | Start-Sleep -Seconds 5 $docker = "{{ windows_gitea_runner_windows_runtime_bin }}" & $docker logs --tail {{ windows_gitea_runner_windows_container_log_tail }} "{{ __windows_gitea_runner_windows_container_name }}" become: true become_method: ansible.builtin.runas become_user: "{{ __windows_gitea_runner_windows_become_user }}" register: __windows_gitea_runner_windows_logs changed_when: false when: (__windows_gitea_runner_windows_container.rc | default(0)) == 0 - name: Print Windows runner logs ansible.builtin.debug: msg: "{{ __windows_gitea_runner_windows_logs.stdout | default('') }}" - name: Build normalized Windows runner log text ansible.builtin.set_fact: __windows_gitea_runner_windows_logs_all: >- {{ [ __windows_gitea_runner_windows_logs.stdout | default(''), __windows_gitea_runner_windows_logs.stderr | default('') ] | join('\n') | lower }} - name: Assert Windows runner did not fail registration ansible.builtin.assert: that: - "'invalid token' not in __windows_gitea_runner_windows_logs_all" - "'unauthorized' not in __windows_gitea_runner_windows_logs_all" - "'open config file' not in __windows_gitea_runner_windows_logs_all" fail_msg: >- Windows Gitea runner logs indicate a registration/startup failure. Check token, instance URL, and runner image entrypoint behavior.