Add Windows runner profiles and configuration for Gitea CI

- Introduced Dockerfiles for various Windows runner profiles: C/C++, .NET, Go, and MSYS.
- Added a PowerShell script to start the Gitea runner with appropriate configurations.
- Updated Ansible host variables to include new Windows build root and runner configurations.
- Enhanced the Windows Gitea runner role to support multiple execution modes (linux_podman and windows_mcr).
- Implemented tasks to validate runner execution modes, ensure necessary services are running, and manage Docker containers for Windows runners.
- Created a new task file for managing Windows-specific runner operations.
- Updated the configuration template to dynamically set paths for runner files and cache directories.
This commit is contained in:
2026-07-03 00:18:20 +02:00
parent e59a832d5d
commit c4d1db3a91
12 changed files with 920 additions and 6 deletions
@@ -0,0 +1,22 @@
FROM mcr.microsoft.com/windows/servercore:ltsc2022
SHELL ["powershell", "-NoProfile", "-ExecutionPolicy", "Bypass", "-Command"]
# Base image for Windows Gitea runners with Git for Windows tools and gitea runner.
ARG ACT_RUNNER_VERSION=2.0.0
RUN Set-StrictMode -Version Latest; \
$ErrorActionPreference = 'Stop'; \
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; \
iex ((New-Object System.Net.WebClient).DownloadString('https://community.chocolatey.org/install.ps1')); \
choco config set commandExecutionTimeoutSeconds 14400; \
choco feature disable -n showDownloadProgress; \
choco feature enable -n allowGlobalConfirmation; \
choco install git --yes --no-progress --limit-output; \
New-Item -ItemType Directory -Path C:\gitea-runner -Force | Out-Null; \
$runnerUrl = 'https://gitea.com/gitea/runner/releases/download/v{0}/gitea-runner-{0}-windows-amd64.exe' -f $env:ACT_RUNNER_VERSION; \
Invoke-WebRequest -UseBasicParsing -Uri $runnerUrl -OutFile C:\gitea-runner\act_runner.exe
COPY ["start-runner.ps1", "C:/gitea-runner/start-runner.ps1"]
ENV PATH="C:\ProgramData\chocolatey\bin;C:\Program Files\Git\cmd;C:\Program Files\Git\bin;C:\Program Files\Git\usr\bin;C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerShell\v1.0\;C:\Windows\System32\OpenSSH\;C:\Users\ContainerAdministrator\AppData\Local\Microsoft\WindowsApps"
ENTRYPOINT ["C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe", "-NoProfile", "-ExecutionPolicy", "Bypass", "-File", "C:\\gitea-runner\\start-runner.ps1"]
@@ -0,0 +1,9 @@
FROM local/gitea-windows-runner:windows-base
SHELL ["powershell", "-NoProfile", "-ExecutionPolicy", "Bypass", "-Command"]
# C/C++ profile with cmake and ninja.
RUN choco feature enable -n allowGlobalConfirmation; \
choco install cmake --installargs 'ADD_CMAKE_TO_PATH=System'; \
choco install ninja; \
choco clean --yes
@@ -0,0 +1,8 @@
FROM local/gitea-windows-runner:windows-base
SHELL ["powershell", "-NoProfile", "-ExecutionPolicy", "Bypass", "-Command"]
# .NET profile for building and testing managed workloads.
RUN choco feature enable -n allowGlobalConfirmation; \
choco install dotnet-sdk --version=8.0.204; \
choco clean --yes
@@ -0,0 +1,8 @@
FROM local/gitea-windows-runner:windows-base
SHELL ["powershell", "-NoProfile", "-ExecutionPolicy", "Bypass", "-Command"]
# Go profile for Windows builds.
RUN choco feature enable -n allowGlobalConfirmation; \
choco install golang --version=1.22.4; \
choco clean --yes
@@ -0,0 +1,7 @@
FROM local/gitea-windows-runner:windows-base
SHELL ["powershell", "-NoProfile", "-ExecutionPolicy", "Bypass", "-Command"]
# MSYS-focused profile with common GNU toolchain extras.
RUN choco feature enable -n allowGlobalConfirmation; \
choco install make msys2 unzip --yes --no-progress --limit-output
@@ -0,0 +1,17 @@
$ErrorActionPreference = "Stop"
$runner = "C:\gitea-runner\act_runner.exe"
$config = if ($env:CONFIG_FILE) { $env:CONFIG_FILE } else { "C:\runner-data\config.yaml" }
$runnerFile = if ($env:GITEA_RUNNER_FILE) { $env:GITEA_RUNNER_FILE } else { "C:\runner-data\.runner" }
if (-not (Test-Path -Path $runnerFile) -and $env:GITEA_INSTANCE_URL -and $env:GITEA_RUNNER_REGISTRATION_TOKEN) {
$labels = if ($env:GITEA_RUNNER_LABELS) { $env:GITEA_RUNNER_LABELS } else { "windows:host" }
$name = if ($env:GITEA_RUNNER_NAME) { $env:GITEA_RUNNER_NAME } else { $env:COMPUTERNAME }
& $runner register --no-interactive --instance $env:GITEA_INSTANCE_URL --token $env:GITEA_RUNNER_REGISTRATION_TOKEN --name $name --labels $labels --config $config
if ($LASTEXITCODE -ne 0) {
exit $LASTEXITCODE
}
}
& $runner daemon --config $config
exit $LASTEXITCODE
@@ -109,6 +109,7 @@ windows_containers_service_password: "{{ lookup('env', 'WIN_CI_01_PASSWORD') }}"
windows_gitea_runner_instance_url: "https://code.alexpires.me"
windows_gitea_runner_registration_token: "{{ lookup('env', 'GITEA_RUNNER_REGISTRATION_TOKEN') }}"
windows_gitea_runner_build_root: "D:\\GiteaRunner\\builds"
windows_gitea_runner_windows_build_root: "D:\\GiteaRunner\\windows-builds"
windows_gitea_runner_runners:
- profile: cicd-base
name: "gitea-runner-cicd-base"
@@ -167,3 +168,13 @@ windows_gitea_runner_runners:
run_args:
- "--privileged"
enable_nested_build: true
- profile: windows-msys
mode: windows_mcr
name: "gitea-runner-windows-msys"
container_name: "gitea-runner-windows-msys"
image: "local/gitea-windows-runner:windows-msys"
dockerfile_src: "Dockerfile.windows-msys"
labels: "windows:host,windows-msys"
config_file: "D:\\GiteaRunner\\windows-msys\\config.yaml"
data_dir: "D:\\GiteaRunner\\windows-msys\\data"
log_dir: "D:\\Logs\\GiteaRunner\\windows-msys"
@@ -3,6 +3,7 @@ windows_gitea_runner_instance_url: ""
windows_gitea_runner_registration_token: ""
windows_gitea_runner_runners: []
windows_gitea_runner_default_mode: "linux_podman"
windows_gitea_runner_base_image: "local/gitea-runner-base:latest"
windows_gitea_runner_base_dockerfile_src: "Dockerfile.gitea-runner-base"
windows_gitea_runner_base_image_hash_label: "io.a13labs.gitea_runner.base_dockerfile_hash"
@@ -10,9 +11,22 @@ windows_gitea_runner_image_prefix: "local/gitea-runner"
windows_gitea_runner_dockerfile_source_root: "{{ playbook_dir }}/files/dockerfiles"
windows_gitea_runner_build_root: "C:\\ProgramData\\GiteaRunner\\builds"
windows_gitea_runner_base_build_dir: "{{ windows_gitea_runner_build_root }}\\base"
windows_gitea_runner_windows_image_prefix: "local/gitea-windows-runner"
windows_gitea_runner_windows_dockerfile_source_root: "{{ playbook_dir }}/files/dockerfiles/windows-runners"
windows_gitea_runner_windows_build_root: "C:\\ProgramData\\GiteaRunner\\windows-builds"
windows_gitea_runner_windows_runtime_bin: "docker"
windows_gitea_runner_windows_runtime_user: "{{ windows_gitea_runner_podman_service_user }}"
windows_gitea_runner_windows_become_user: "{{ windows_gitea_runner_podman_service_user }}"
windows_gitea_runner_windows_base_image: "{{ windows_gitea_runner_windows_image_prefix }}:windows-base"
windows_gitea_runner_windows_base_dockerfile_src: "Dockerfile.windows-base"
windows_gitea_runner_windows_base_entrypoint_script_src: "start-runner.ps1"
windows_gitea_runner_windows_base_build_dir: "{{ windows_gitea_runner_windows_build_root }}\\windows-base"
windows_gitea_runner_windows_base_image_hash_label: "io.a13labs.gitea_runner.windows_base_dockerfile_hash"
windows_gitea_runner_config_root: "C:\\ProgramData\\GiteaRunner\\config"
windows_gitea_runner_data_root: "C:\\ProgramData\\GiteaRunner\\data"
windows_gitea_runner_log_root: "C:\\Logs\\GiteaRunner"
windows_gitea_runner_config_runner_file: "/data/.runner"
windows_gitea_runner_config_cache_dir: "/data/cache"
windows_gitea_runner_container_run_args: []
windows_gitea_runner_container_enable_nested_build: false
windows_gitea_runner_container_storage_root: "{{ windows_gitea_runner_data_root }}\\containers"
@@ -10,5 +10,5 @@ galaxy_info:
platforms:
- name: Windows
# dependencies:
# - role: windows_containers
dependencies:
- role: windows_containers
@@ -8,10 +8,52 @@
fail_msg: >-
windows_gitea_runner_instance_url, windows_gitea_runner_registration_token,
and at least one windows_gitea_runner_runners entry are required.
tags:
- always
- name: Build effective runner matrix
ansible.builtin.set_fact:
__windows_gitea_runner_effective_runners: "{{ windows_gitea_runner_runners }}"
tags:
- always
- name: Validate runner execution modes
ansible.builtin.assert:
that:
- >-
(
windows_gitea_runner_runner.mode
| default(windows_gitea_runner_default_mode)
) in ['linux_podman', 'windows_mcr']
fail_msg: >-
Unsupported runner mode for
{{ windows_gitea_runner_runner.name | default(windows_gitea_runner_runner.profile | default('runner')) }}.
Supported modes: linux_podman, windows_mcr.
loop: "{{ __windows_gitea_runner_effective_runners }}"
loop_control:
loop_var: windows_gitea_runner_runner
tags:
- always
- name: Resolve runner mode flags
ansible.builtin.set_fact:
__windows_gitea_runner_modes: >-
{{
__windows_gitea_runner_effective_runners
| map(attribute='mode', default=windows_gitea_runner_default_mode)
| list
}}
tags:
- always
- name: Set runner mode booleans
ansible.builtin.set_fact:
__windows_gitea_runner_has_linux_runners: >-
{{ 'linux_podman' in __windows_gitea_runner_modes }}
__windows_gitea_runner_has_windows_runners: >-
{{ 'windows_mcr' in __windows_gitea_runner_modes }}
tags:
- always
- name: Ensure Podman auto-start service is running
ansible.windows.win_service:
@@ -21,6 +63,9 @@
become: true
become_method: ansible.builtin.runas
become_user: SYSTEM
when: __windows_gitea_runner_has_linux_runners
tags:
- roles::windows_gitea_runner::linux
- name: Wait until Podman machine is ready
ansible.windows.win_shell: |
@@ -42,16 +87,243 @@
become_method: ansible.builtin.runas
become_user: "{{ windows_gitea_runner_podman_service_user }}"
changed_when: false
when: __windows_gitea_runner_has_linux_runners
tags:
- roles::windows_gitea_runner::linux
- name: Ensure Docker service is running for Windows runners
ansible.windows.win_service:
name: docker
start_mode: auto
state: started
become: true
become_method: ansible.builtin.runas
become_user: SYSTEM
when: __windows_gitea_runner_has_windows_runners
tags:
- roles::windows_gitea_runner::windows
- name: Resolve Windows docker runtime user
ansible.builtin.set_fact:
__windows_gitea_runner_windows_runtime_user: >-
{{
windows_gitea_runner_windows_runtime_user
| default(windows_gitea_runner_podman_service_user, true)
| default(ansible_user | default(''), true)
}}
__windows_gitea_runner_windows_become_user: >-
{{
windows_gitea_runner_windows_become_user
| default(__windows_gitea_runner_windows_runtime_user, true)
| default('SYSTEM', true)
}}
when: __windows_gitea_runner_has_windows_runners
tags:
- roles::windows_gitea_runner::windows
- name: Check docker-users local group availability
ansible.windows.win_shell: |
$group = Get-LocalGroup -Name 'docker-users' -ErrorAction SilentlyContinue
if ($null -ne $group) {
Write-Output "__EXISTS__"
exit 0
}
Write-Output "__MISSING__"
exit 0
become: true
become_method: ansible.builtin.runas
become_user: SYSTEM
register: __windows_gitea_runner_docker_users_group
changed_when: false
when:
- __windows_gitea_runner_has_windows_runners
- __windows_gitea_runner_windows_runtime_user | length > 0
tags:
- roles::windows_gitea_runner::windows
- name: Add runtime user to docker-users group
ansible.windows.win_group_membership:
name: docker-users
members:
- >-
{{
__windows_gitea_runner_windows_runtime_user
if ('\\' in __windows_gitea_runner_windows_runtime_user)
else '.\\' ~ __windows_gitea_runner_windows_runtime_user
}}
state: present
become: true
become_method: ansible.builtin.runas
become_user: SYSTEM
when:
- __windows_gitea_runner_has_windows_runners
- __windows_gitea_runner_windows_runtime_user | length > 0
- "'__EXISTS__' in (__windows_gitea_runner_docker_users_group.stdout | default(''))"
tags:
- roles::windows_gitea_runner::windows
- name: Ensure Windows base build directory exists
ansible.windows.win_file:
path: "{{ windows_gitea_runner_windows_base_build_dir }}"
state: directory
when: __windows_gitea_runner_has_windows_runners
tags:
- roles::windows_gitea_runner::windows
- name: Copy Windows base Dockerfile to build context
ansible.windows.win_copy:
src: >-
{{
windows_gitea_runner_windows_dockerfile_source_root
~ '/'
~ windows_gitea_runner_windows_base_dockerfile_src
}}
dest: "{{ windows_gitea_runner_windows_base_build_dir }}\\Dockerfile"
when: __windows_gitea_runner_has_windows_runners
tags:
- roles::windows_gitea_runner::windows
- name: Copy Windows base entrypoint script to build context
ansible.windows.win_copy:
src: >-
{{
windows_gitea_runner_windows_dockerfile_source_root
~ '/'
~ windows_gitea_runner_windows_base_entrypoint_script_src
}}
dest: >-
{{
windows_gitea_runner_windows_base_build_dir
~ '\\'
~ windows_gitea_runner_windows_base_entrypoint_script_src
}}
when: __windows_gitea_runner_has_windows_runners
tags:
- roles::windows_gitea_runner::windows
- 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_current_windows_base_image_hash
changed_when: false
when: __windows_gitea_runner_has_windows_runners
tags:
- roles::windows_gitea_runner::windows
- name: Read desired Windows base Dockerfile checksum
ansible.builtin.set_fact:
__windows_gitea_runner_windows_base_dockerfile_checksum: >-
{{
(
lookup(
'file',
windows_gitea_runner_windows_dockerfile_source_root
~ '/'
~ windows_gitea_runner_windows_base_dockerfile_src
)
~ '\n'
~ lookup(
'file',
windows_gitea_runner_windows_dockerfile_source_root
~ '/'
~ windows_gitea_runner_windows_base_entrypoint_script_src
)
) | hash('sha1')
}}
when: __windows_gitea_runner_has_windows_runners
tags:
- roles::windows_gitea_runner::windows
- name: Determine if Windows base image needs rebuild
ansible.builtin.set_fact:
__windows_gitea_runner_needs_windows_base_image_rebuild: >-
{{
(__windows_gitea_runner_current_windows_base_image_hash.stdout | trim)
!= (__windows_gitea_runner_windows_base_dockerfile_checksum | trim)
}}
when: __windows_gitea_runner_has_windows_runners
tags:
- roles::windows_gitea_runner::windows
- name: Rebuild Windows base image when Dockerfile changed
ansible.windows.win_shell: |
$ErrorActionPreference = "Stop"
$docker = "{{ windows_gitea_runner_windows_runtime_bin }}"
$baseHash = "{{ __windows_gitea_runner_windows_base_dockerfile_checksum }}"
$baseLabel = "{{ windows_gitea_runner_windows_base_image_hash_label }}=$baseHash"
& $docker build `
--label "$baseLabel" `
--tag "{{ windows_gitea_runner_windows_base_image }}" `
--file "{{ windows_gitea_runner_windows_base_build_dir }}\Dockerfile" `
"{{ windows_gitea_runner_windows_base_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_base_build
changed_when: "'__BUILT__' in __windows_gitea_runner_windows_base_build.stdout"
when:
- __windows_gitea_runner_has_windows_runners
- __windows_gitea_runner_needs_windows_base_image_rebuild
tags:
- roles::windows_gitea_runner::windows
- name: Ensure base build directory exists
ansible.windows.win_file:
path: "{{ windows_gitea_runner_base_build_dir }}"
state: directory
when: __windows_gitea_runner_has_linux_runners
tags:
- roles::windows_gitea_runner::linux
- name: Copy base Dockerfile to build context
ansible.windows.win_copy:
src: "{{ windows_gitea_runner_dockerfile_source_root }}/{{ windows_gitea_runner_base_dockerfile_src }}"
dest: "{{ windows_gitea_runner_base_build_dir }}\\Dockerfile"
when: __windows_gitea_runner_has_linux_runners
tags:
- roles::windows_gitea_runner::linux
- name: Grant podman service user access to base build directory
ansible.windows.win_acl:
@@ -65,6 +337,9 @@
become: true
become_method: ansible.builtin.runas
become_user: SYSTEM
when: __windows_gitea_runner_has_linux_runners
tags:
- roles::windows_gitea_runner::linux
- name: Read current base image hash label
ansible.windows.win_shell: |
@@ -108,6 +383,9 @@
become_user: "{{ windows_gitea_runner_podman_service_user }}"
register: __windows_gitea_runner_current_base_image_hash
changed_when: false
when: __windows_gitea_runner_has_linux_runners
tags:
- roles::windows_gitea_runner::linux
- name: Read desired base Dockerfile checksum
ansible.builtin.set_fact:
@@ -118,6 +396,9 @@
windows_gitea_runner_dockerfile_source_root ~ '/' ~ windows_gitea_runner_base_dockerfile_src
) | hash('sha1')
}}
when: __windows_gitea_runner_has_linux_runners
tags:
- roles::windows_gitea_runner::linux
- name: Determine if base image needs rebuild
ansible.builtin.set_fact:
@@ -126,6 +407,9 @@
(__windows_gitea_runner_current_base_image_hash.stdout | trim)
!= (__windows_gitea_runner_base_dockerfile_checksum | trim)
}}
when: __windows_gitea_runner_has_linux_runners
tags:
- roles::windows_gitea_runner::linux
- name: Rebuild base image when Dockerfile changed
ansible.windows.win_shell: |
@@ -145,12 +429,46 @@
become_user: "{{ windows_gitea_runner_podman_service_user }}"
register: __windows_gitea_runner_base_build
changed_when: "'__BUILT__' in __windows_gitea_runner_base_build.stdout"
when: __windows_gitea_runner_needs_base_image_rebuild
when:
- __windows_gitea_runner_has_linux_runners
- __windows_gitea_runner_needs_base_image_rebuild
tags:
- roles::windows_gitea_runner::linux
- name: Reconcile all runners
- name: Reconcile Linux Podman runners
ansible.builtin.include_tasks: runner.yml
args:
apply:
tags:
- roles::windows_gitea_runner::linux
loop: "{{ __windows_gitea_runner_effective_runners }}"
loop_control:
loop_var: windows_gitea_runner_runner
label: >-
{{ windows_gitea_runner_runner.name | default(windows_gitea_runner_runner.profile) }}
when: >-
(
windows_gitea_runner_runner.mode
| default(windows_gitea_runner_default_mode)
) == 'linux_podman'
tags:
- roles::windows_gitea_runner::linux
- name: Reconcile Windows Mirantis runners
ansible.builtin.include_tasks: runner_windows.yml
args:
apply:
tags:
- roles::windows_gitea_runner::windows
loop: "{{ __windows_gitea_runner_effective_runners }}"
loop_control:
loop_var: windows_gitea_runner_runner
label: >-
{{ windows_gitea_runner_runner.name | default(windows_gitea_runner_runner.profile) }}
when: >-
(
windows_gitea_runner_runner.mode
| default(windows_gitea_runner_default_mode)
) == 'windows_mcr'
tags:
- roles::windows_gitea_runner::windows
@@ -0,0 +1,500 @@
---
- name: Resolve Windows runner profile
ansible.builtin.set_fact:
__windows_gitea_runner_profile: >-
{{ windows_gitea_runner_runner.profile | default(windows_gitea_runner_runner.name | default('runner')) }}
- name: Resolve Windows runner-specific settings
ansible.builtin.set_fact:
__windows_gitea_runner_name: >-
{{
windows_gitea_runner_runner.name | default(
'gitea-runner-' ~ __windows_gitea_runner_profile
)
}}
__windows_gitea_runner_container_name: >-
{{
windows_gitea_runner_runner.container_name | default(
'gitea-runner-' ~ __windows_gitea_runner_profile
)
}}
__windows_gitea_runner_image: >-
{{
windows_gitea_runner_runner.image | default(
windows_gitea_runner_windows_image_prefix ~ ':' ~ __windows_gitea_runner_profile
)
}}
__windows_gitea_runner_labels: >-
{{
windows_gitea_runner_runner.labels | default(
'windows:host,' ~ __windows_gitea_runner_profile
)
}}
__windows_gitea_runner_dockerfile_src: >-
{{
windows_gitea_runner_runner.dockerfile_src | default(
'Dockerfile.' ~ __windows_gitea_runner_profile
)
}}
__windows_gitea_runner_config_file: >-
{{
windows_gitea_runner_runner.config_file | default(
windows_gitea_runner_config_root ~ '\\' ~ __windows_gitea_runner_profile ~ '\\config.yaml'
)
}}
__windows_gitea_runner_config_dir: >-
{{
(
windows_gitea_runner_runner.config_file | default(
windows_gitea_runner_config_root ~ '\\' ~ __windows_gitea_runner_profile ~ '\\config.yaml'
)
) | regex_replace('\\\\[^\\\\]+$', '')
}}
__windows_gitea_runner_data_dir: >-
{{
windows_gitea_runner_runner.data_dir | default(
windows_gitea_runner_data_root ~ '\\' ~ __windows_gitea_runner_profile ~ '\\data'
)
}}
__windows_gitea_runner_log_dir: >-
{{
windows_gitea_runner_runner.log_dir | default(
windows_gitea_runner_log_root ~ '\\' ~ __windows_gitea_runner_profile
)
}}
__windows_gitea_runner_build_dir: >-
{{ windows_gitea_runner_windows_build_root ~ '\\' ~ __windows_gitea_runner_profile }}
__windows_gitea_runner_build_dockerfile_dest: >-
{{ windows_gitea_runner_windows_build_root ~ '\\' ~ __windows_gitea_runner_profile ~ '\\Dockerfile' }}
__windows_gitea_runner_dockerfile_source_path: >-
{{
windows_gitea_runner_windows_dockerfile_source_root ~ '/' ~ (
windows_gitea_runner_runner.dockerfile_src | default(
'Dockerfile.' ~ __windows_gitea_runner_profile
)
)
}}
__windows_gitea_runner_run_args: >-
{{ windows_gitea_runner_runner.run_args | default([]) }}
- name: Resolve Windows runner runtime config path
ansible.builtin.set_fact:
__windows_gitea_runner_runtime_config_file: "{{ __windows_gitea_runner_data_dir }}\\config.yaml"
windows_gitea_runner_config_runner_file: 'C:\runner-data\.runner'
windows_gitea_runner_config_cache_dir: 'C:\runner-data\cache'
__windows_gitea_runner_windows_become_user: >-
{{ windows_gitea_runner_windows_become_user | default('SYSTEM') }}
- name: Read Windows runner Dockerfile checksum
ansible.builtin.set_fact:
__windows_gitea_runner_profile_dockerfile_checksum: >-
{{ lookup('file', __windows_gitea_runner_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_current_base_image_hash
changed_when: false
- name: Compute desired Windows runner image hash
ansible.builtin.set_fact:
__windows_gitea_runner_desired_image_hash: >-
{{ {
'base': __windows_gitea_runner_current_base_image_hash.stdout | trim,
'profile': __windows_gitea_runner_profile_dockerfile_checksum
} | to_json | hash('sha1') }}
- name: Compute desired Windows runner container hash
ansible.builtin.set_fact:
__windows_gitea_runner_desired_container_hash: >-
{{ {
'mode': 'windows_mcr',
'image': __windows_gitea_runner_image,
'name': __windows_gitea_runner_name,
'labels': __windows_gitea_runner_labels,
'instance': windows_gitea_runner_instance_url,
'config': __windows_gitea_runner_runtime_config_file,
'data': __windows_gitea_runner_data_dir,
'log': __windows_gitea_runner_log_dir,
'run_args': __windows_gitea_runner_run_args,
'restart': windows_gitea_runner_restart_policy,
'image_hash': __windows_gitea_runner_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_build_dir }}"
- "{{ __windows_gitea_runner_data_dir }}"
- "{{ __windows_gitea_runner_log_dir }}"
- "{{ __windows_gitea_runner_config_dir }}"
- name: Render Windows runner config
ansible.windows.win_template:
src: config.yaml.j2
dest: "{{ __windows_gitea_runner_runtime_config_file }}"
- name: Copy Windows runner Dockerfile to build context
ansible.windows.win_copy:
src: "{{ __windows_gitea_runner_dockerfile_source_path }}"
dest: "{{ __windows_gitea_runner_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_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_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_current_image_hash
changed_when: false
- name: Determine if Windows runner image needs rebuild
ansible.builtin.set_fact:
__windows_gitea_runner_needs_image_rebuild: >-
{{
(__windows_gitea_runner_current_image_hash.stdout | trim)
!= (__windows_gitea_runner_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_image_hash_label }}={{ __windows_gitea_runner_desired_image_hash }}" `
--tag "{{ __windows_gitea_runner_image }}" `
--file "{{ __windows_gitea_runner_build_dockerfile_dest }}" `
"{{ __windows_gitea_runner_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_build
changed_when: "'__BUILT__' in __windows_gitea_runner_build.stdout"
when: __windows_gitea_runner_needs_image_rebuild
- name: Read current Windows runner container hash label
ansible.windows.win_shell: |
$ErrorActionPreference = "Stop"
$name = "{{ __windows_gitea_runner_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_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_current_hash
changed_when: false
- name: Determine if Windows runner container needs recreation
ansible.builtin.set_fact:
__windows_gitea_runner_needs_recreate: >-
{{
(__windows_gitea_runner_current_hash.stdout | trim)
!= (__windows_gitea_runner_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_container_name }}" 2>$null
become: true
become_method: ansible.builtin.runas
become_user: "{{ __windows_gitea_runner_windows_become_user }}"
when: __windows_gitea_runner_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_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
}
# Export values into process environment to avoid command-line parsing issues
# when secrets or labels contain special characters.
$env:CONFIG_FILE = "C:\runner-data\config.yaml"
$env:GITEA_INSTANCE_URL = {{ windows_gitea_runner_instance_url | to_json }}
$env:GITEA_RUNNER_REGISTRATION_TOKEN = {{ windows_gitea_runner_registration_token | to_json }}
$env:GITEA_RUNNER_NAME = {{ __windows_gitea_runner_name | to_json }}
$env:GITEA_RUNNER_LABELS = {{ __windows_gitea_runner_labels | to_json }}
& $docker run -d --name $name --restart "{{ windows_gitea_runner_restart_policy }}" `
@({{ __windows_gitea_runner_run_args | map('to_json') | join(', ') }}) `
--label "{{ windows_gitea_runner_container_hash_label }}={{ __windows_gitea_runner_desired_container_hash }}" `
--mount "type=bind,source={{ __windows_gitea_runner_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_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_container
changed_when: "'__CREATED__' in __windows_gitea_runner_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_container_name }}"
$image = "{{ __windows_gitea_runner_image }}"
$hostData = "{{ __windows_gitea_runner_data_dir }}"
$hostConfig = "{{ __windows_gitea_runner_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_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_run_args | map('to_json') | join(', ') }}) `
--mount "type=bind,source={{ __windows_gitea_runner_data_dir }},target=C:\runner-data" `
-e "CONFIG_FILE=C:\runner-data\config.yaml" `
-e "GITEA_INSTANCE_URL={{ windows_gitea_runner_instance_url }}" `
-e "GITEA_RUNNER_REGISTRATION_TOKEN=diag-token" `
-e "GITEA_RUNNER_NAME=diag-runner" `
-e "GITEA_RUNNER_LABELS={{ __windows_gitea_runner_labels }}" `
"{{ __windows_gitea_runner_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_start_failure_diag
changed_when: false
when: (__windows_gitea_runner_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_container.rc | default('unknown') }}).
RUN_STDERR={{ (__windows_gitea_runner_container.stderr | default(''))
| replace(windows_gitea_runner_registration_token | default(''), '***') }}
RUN_STDOUT={{ (__windows_gitea_runner_container.stdout | default(''))
| replace(windows_gitea_runner_registration_token | default(''), '***') }}
{{ [
__windows_gitea_runner_start_failure_diag.stdout | default(''),
__windows_gitea_runner_start_failure_diag.stderr | default('')
] | join('\n') | trim }}
when: (__windows_gitea_runner_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_container_name }}" --format "{{ '{{' }}.State.Status{{ '}}' }}"
become: true
become_method: ansible.builtin.runas
become_user: "{{ __windows_gitea_runner_windows_become_user }}"
register: __windows_gitea_runner_status
changed_when: false
failed_when: (__windows_gitea_runner_status.stdout | trim) != 'running'
when: (__windows_gitea_runner_container.rc | default(0)) == 0
- name: Show tail of Windows runner logs
ansible.windows.win_shell: |
$docker = "{{ windows_gitea_runner_windows_runtime_bin }}"
& $docker logs --tail {{ windows_gitea_runner_container_log_tail }} "{{ __windows_gitea_runner_container_name }}"
become: true
become_method: ansible.builtin.runas
become_user: "{{ __windows_gitea_runner_windows_become_user }}"
register: __windows_gitea_runner_logs
changed_when: false
when: (__windows_gitea_runner_container.rc | default(0)) == 0
- name: Print Windows runner logs
ansible.builtin.debug:
msg: "{{ __windows_gitea_runner_logs.stdout | default('') }}"
- name: Build normalized Windows runner log text
ansible.builtin.set_fact:
__windows_gitea_runner_logs_all: >-
{{
[
__windows_gitea_runner_logs.stdout | default(''),
__windows_gitea_runner_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_logs_all"
- "'unauthorized' not in __windows_gitea_runner_logs_all"
- "'open config file' not in __windows_gitea_runner_logs_all"
fail_msg: >-
Windows Gitea runner logs indicate a registration/startup failure.
Check token, instance URL, and runner image entrypoint behavior.
@@ -2,10 +2,10 @@ log:
level: info
runner:
file: /data/.runner
file: {{ windows_gitea_runner_config_runner_file | default('/data/.runner') | to_json }}
capacity: 1
envs: {}
cache:
enabled: true
dir: /data/cache
dir: {{ windows_gitea_runner_config_cache_dir | default('/data/cache') | to_json }}