From c6f3b0f829fb2b12776ecaa7563269ec55efe4aa Mon Sep 17 00:00:00 2001 From: Alexandre Pires Date: Fri, 3 Jul 2026 19:12:48 +0200 Subject: [PATCH] Add Windows Gitea runner role with Podman and Docker support - Created templates for configuration files for both Podman and Docker-based Windows Gitea runners. - Added default variables for Windows Gitea runner role, including instance URL, registration token, and image settings. - Implemented tasks for validating settings, ensuring Docker service is running, and managing Docker users. - Developed logic for building and managing Windows runner images and containers, including checks for existing images and containers. - Updated inventory to include new groups for Windows Gitea runners. - Adjusted Terraform configurations for timeout settings in ingress resources. --- ansible/host_vars/ci-01.lab.alexpires.me.yml | 15 +- ansible/host_vars/gpu-01.lab.alexpires.me.yml | 6 + ansible/playbook_windows_gitea_runner.yml | 6 - .../playbook_windows_gitea_runner_podman.yml | 6 + .../playbook_windows_gitea_runner_windows.yml | 6 + .../windows_gitea_runner/defaults/main.yml | 44 -- .../roles/windows_gitea_runner/tasks/main.yml | 474 ------------------ .../windows_gitea_runner/tasks/runner.yml | 430 ---------------- .../templates/config.yaml.j2 | 11 - .../defaults/main.yml | 34 ++ .../meta/main.yml | 4 +- .../tasks/main.yml | 182 +++++++ .../tasks/runner.yml | 429 ++++++++++++++++ .../templates/config.yaml.j2 | 11 + .../defaults/main.yml | 30 ++ .../meta/main.yml | 14 + .../tasks/main.yml | 230 +++++++++ .../tasks/runner.yml} | 282 +++++------ .../templates/config.yaml.j2 | 11 + inventory/hosts | 5 +- terraform/apps/prod-01/config.tf | 4 +- terraform/modules/apps/open-webui/ingress.tf | 4 +- 22 files changed, 1117 insertions(+), 1121 deletions(-) delete mode 100644 ansible/playbook_windows_gitea_runner.yml create mode 100644 ansible/playbook_windows_gitea_runner_podman.yml create mode 100644 ansible/playbook_windows_gitea_runner_windows.yml delete mode 100644 ansible/roles/windows_gitea_runner/defaults/main.yml delete mode 100644 ansible/roles/windows_gitea_runner/tasks/main.yml delete mode 100644 ansible/roles/windows_gitea_runner/tasks/runner.yml delete mode 100644 ansible/roles/windows_gitea_runner/templates/config.yaml.j2 create mode 100644 ansible/roles/windows_gitea_runner_podman/defaults/main.yml rename ansible/roles/{windows_gitea_runner => windows_gitea_runner_podman}/meta/main.yml (63%) create mode 100644 ansible/roles/windows_gitea_runner_podman/tasks/main.yml create mode 100644 ansible/roles/windows_gitea_runner_podman/tasks/runner.yml create mode 100644 ansible/roles/windows_gitea_runner_podman/templates/config.yaml.j2 create mode 100644 ansible/roles/windows_gitea_runner_windows/defaults/main.yml create mode 100644 ansible/roles/windows_gitea_runner_windows/meta/main.yml create mode 100644 ansible/roles/windows_gitea_runner_windows/tasks/main.yml rename ansible/roles/{windows_gitea_runner/tasks/runner_windows.yml => windows_gitea_runner_windows/tasks/runner.yml} (54%) create mode 100644 ansible/roles/windows_gitea_runner_windows/templates/config.yaml.j2 diff --git a/ansible/host_vars/ci-01.lab.alexpires.me.yml b/ansible/host_vars/ci-01.lab.alexpires.me.yml index a806c40..3c1312d 100644 --- a/ansible/host_vars/ci-01.lab.alexpires.me.yml +++ b/ansible/host_vars/ci-01.lab.alexpires.me.yml @@ -106,11 +106,10 @@ llama_server_args_extra: ] 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: +windows_gitea_runner_podman_instance_url: "https://code.alexpires.me" +windows_gitea_runner_podman_registration_token: "{{ lookup('env', 'GITEA_RUNNER_REGISTRATION_TOKEN') }}" +windows_gitea_runner_podman_build_root: "D:\\GiteaRunner\\builds" +windows_gitea_runner_podman_runners: - profile: cicd-base name: "gitea-runner-cicd-base" container_name: "gitea-runner-cicd-base" @@ -168,8 +167,12 @@ windows_gitea_runner_runners: run_args: - "--privileged" enable_nested_build: true + +windows_gitea_runner_windows_instance_url: "https://code.alexpires.me" +windows_gitea_runner_windows_registration_token: "{{ lookup('env', 'GITEA_RUNNER_REGISTRATION_TOKEN') }}" +windows_gitea_runner_windows_build_root: "D:\\GiteaRunner\\windows-builds" +windows_gitea_runner_windows_runners: - profile: windows-msys - mode: windows_mcr name: "gitea-runner-windows-msys" container_name: "gitea-runner-windows-msys" image: "local/gitea-windows-runner:windows-msys" diff --git a/ansible/host_vars/gpu-01.lab.alexpires.me.yml b/ansible/host_vars/gpu-01.lab.alexpires.me.yml index 4a8ed8b..32b11f2 100644 --- a/ansible/host_vars/gpu-01.lab.alexpires.me.yml +++ b/ansible/host_vars/gpu-01.lab.alexpires.me.yml @@ -175,6 +175,12 @@ llama_server_models: revision: "main" preset: ctx-size: 98304 + - name: "qwen3-6-27b-iq4-xs" + model_id: "unsloth/Qwen3.6-27B-GGUF" + quant: "IQ4_XS" + revision: "main" + preset: + ctx-size: 98304 - name: "qwen3-6-27b-mtp-q4-0" model_id: "unsloth/Qwen3.6-27B-MTP-GGUF" quant: "Q4_0" diff --git a/ansible/playbook_windows_gitea_runner.yml b/ansible/playbook_windows_gitea_runner.yml deleted file mode 100644 index 767f658..0000000 --- a/ansible/playbook_windows_gitea_runner.yml +++ /dev/null @@ -1,6 +0,0 @@ ---- -- name: Setup Gitea Runner on Windows (Podman) - hosts: windows_gitea_runner - - roles: - - windows_gitea_runner diff --git a/ansible/playbook_windows_gitea_runner_podman.yml b/ansible/playbook_windows_gitea_runner_podman.yml new file mode 100644 index 0000000..37e149b --- /dev/null +++ b/ansible/playbook_windows_gitea_runner_podman.yml @@ -0,0 +1,6 @@ +--- +- name: Setup Gitea Runner on Windows (Podman Linux containers) + hosts: windows_gitea_runner_podman + + roles: + - windows_gitea_runner_podman diff --git a/ansible/playbook_windows_gitea_runner_windows.yml b/ansible/playbook_windows_gitea_runner_windows.yml new file mode 100644 index 0000000..199c7e3 --- /dev/null +++ b/ansible/playbook_windows_gitea_runner_windows.yml @@ -0,0 +1,6 @@ +--- +- name: Setup Gitea Runner on Windows (Windows MCR containers) + hosts: windows_gitea_runner_windows + + roles: + - windows_gitea_runner_windows diff --git a/ansible/roles/windows_gitea_runner/defaults/main.yml b/ansible/roles/windows_gitea_runner/defaults/main.yml deleted file mode 100644 index 882c8d6..0000000 --- a/ansible/roles/windows_gitea_runner/defaults/main.yml +++ /dev/null @@ -1,44 +0,0 @@ ---- -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" -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" -windows_gitea_runner_container_log_tail: 120 - -windows_gitea_runner_podman_machine_name: >- - {{ windows_containers_machine_name | default('podman-machine-default') }} -windows_gitea_runner_podman_install_dir: >- - {{ windows_containers_install_dir | default('C:\\Program Files\\RedHat\\Podman') }} -windows_gitea_runner_podman_service_user: "{{ windows_containers_service_user | default('podman') }}" -windows_gitea_runner_podman_service_name: "PodmanAutoStart" - -windows_gitea_runner_restart_policy: "always" -windows_gitea_runner_image_hash_label: "io.a13labs.gitea_runner.dockerfile_hash" -windows_gitea_runner_container_hash_label: "io.a13labs.gitea_runner.config_hash" diff --git a/ansible/roles/windows_gitea_runner/tasks/main.yml b/ansible/roles/windows_gitea_runner/tasks/main.yml deleted file mode 100644 index 3b44e23..0000000 --- a/ansible/roles/windows_gitea_runner/tasks/main.yml +++ /dev/null @@ -1,474 +0,0 @@ ---- -- name: Validate required runner settings - ansible.builtin.assert: - that: - - windows_gitea_runner_instance_url | length > 0 - - windows_gitea_runner_registration_token | length > 0 - - windows_gitea_runner_runners | length > 0 - 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: - name: "{{ windows_gitea_runner_podman_service_name }}" - start_mode: auto - state: started - 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: | - $ErrorActionPreference = "Stop" - $ready = $false - for ($i = 0; $i -lt 90; $i++) { - $null = & "{{ windows_gitea_runner_podman_install_dir }}\podman.exe" info --format json 2>$null - if ($LASTEXITCODE -eq 0) { - $ready = $true - break - } - Start-Sleep -Seconds 2 - } - if (-not $ready) { - Write-Error "Podman machine {{ windows_gitea_runner_podman_machine_name }} did not become ready" - exit 1 - } - become: true - 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: - path: "{{ windows_gitea_runner_base_build_dir }}" - user: ".\\{{ windows_gitea_runner_podman_service_user }}" - rights: FullControl - type: allow - state: present - inherit: ContainerInherit, ObjectInherit - propagation: None - 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: | - $ErrorActionPreference = "Stop" - $podman = "{{ windows_gitea_runner_podman_install_dir }}\podman.exe" - $image = "{{ windows_gitea_runner_base_image }}" - - $null = & $podman image exists $image 2>$null - if ($LASTEXITCODE -ne 0) { - Write-Output "__MISSING__" - exit 0 - } - - $raw = & $podman image inspect $image --format json 2>$null - if ($LASTEXITCODE -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_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_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: - __windows_gitea_runner_base_dockerfile_checksum: >- - {{ - lookup( - 'file', - 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: - __windows_gitea_runner_needs_base_image_rebuild: >- - {{ - (__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: | - $ErrorActionPreference = "Stop" - $podman = "{{ windows_gitea_runner_podman_install_dir }}\podman.exe" - & $podman build ` - --label "{{ windows_gitea_runner_base_image_hash_label }}={{ __windows_gitea_runner_base_dockerfile_checksum }}" ` - --tag "{{ windows_gitea_runner_base_image }}" ` - --file "{{ windows_gitea_runner_base_build_dir }}\\Dockerfile" ` - "{{ windows_gitea_runner_base_build_dir }}" - if ($LASTEXITCODE -ne 0) { - exit 1 - } - Write-Output "__BUILT__" - become: true - become_method: ansible.builtin.runas - 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_has_linux_runners - - __windows_gitea_runner_needs_base_image_rebuild - tags: - - roles::windows_gitea_runner::linux - -- 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 diff --git a/ansible/roles/windows_gitea_runner/tasks/runner.yml b/ansible/roles/windows_gitea_runner/tasks/runner.yml deleted file mode 100644 index 4ad56a4..0000000 --- a/ansible/roles/windows_gitea_runner/tasks/runner.yml +++ /dev/null @@ -1,430 +0,0 @@ ---- -- name: Resolve 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 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_image_prefix ~ ':' ~ __windows_gitea_runner_profile - ) - }} - __windows_gitea_runner_labels: >- - {{ - windows_gitea_runner_runner.labels | default( - __windows_gitea_runner_profile ~ ':host' - ) - }} - __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_config_name: >- - {{ - ( - windows_gitea_runner_runner.config_file | default( - windows_gitea_runner_config_root ~ '\\' ~ __windows_gitea_runner_profile ~ '\\config.yaml' - ) - ) | regex_search('[^\\\\]+$') - }} - __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_build_root ~ '\\' ~ __windows_gitea_runner_profile }} - __windows_gitea_runner_build_dockerfile_dest: >- - {{ windows_gitea_runner_build_root ~ '\\' ~ __windows_gitea_runner_profile ~ '\\Dockerfile' }} - __windows_gitea_runner_dockerfile_source_path: >- - {{ - windows_gitea_runner_dockerfile_source_root ~ '/' ~ ( - windows_gitea_runner_runner.dockerfile_src | default( - 'Dockerfile.' ~ __windows_gitea_runner_profile - ) - ) - }} - __windows_gitea_runner_enable_nested_build: >- - {{ - windows_gitea_runner_runner.enable_nested_build - | default(windows_gitea_runner_container_enable_nested_build) - }} - __windows_gitea_runner_storage_dir: >- - {{ - ( - windows_gitea_runner_runner.storage_dir | default( - windows_gitea_runner_container_storage_root ~ '\\' ~ __windows_gitea_runner_profile - ) - ) - if ( - windows_gitea_runner_runner.enable_nested_build - | default(windows_gitea_runner_container_enable_nested_build) - ) - else '' - }} - __windows_gitea_runner_run_args: >- - {{ windows_gitea_runner_runner.run_args | default(windows_gitea_runner_container_run_args) }} - -- name: Resolve runner runtime config path - ansible.builtin.set_fact: - __windows_gitea_runner_runtime_config_file: "{{ __windows_gitea_runner_data_dir }}\\config.yaml" - -- name: Read runner Dockerfile checksum - ansible.builtin.set_fact: - __windows_gitea_runner_base_dockerfile_checksum: >- - {{ - lookup( - 'file', - windows_gitea_runner_dockerfile_source_root ~ '/' ~ windows_gitea_runner_base_dockerfile_src - ) | hash('sha1') - }} - __windows_gitea_runner_profile_dockerfile_checksum: >- - {{ lookup('file', __windows_gitea_runner_dockerfile_source_path) | hash('sha1') }} - -- name: Compute desired runner image hash - ansible.builtin.set_fact: - __windows_gitea_runner_desired_image_hash: >- - {{ { - 'base': __windows_gitea_runner_base_dockerfile_checksum, - 'profile': __windows_gitea_runner_profile_dockerfile_checksum - } | to_json | hash('sha1') }} - -- name: Compute desired runner container hash - ansible.builtin.set_fact: - __windows_gitea_runner_desired_container_hash: >- - {{ { - '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, - 'config_dir': __windows_gitea_runner_data_dir, - 'config_name': ( - (__windows_gitea_runner_runtime_config_file | regex_search('[^\\\\]+$')) - | default('config.yaml', true) - ), - 'config_mount_mode': 'data', - 'data': __windows_gitea_runner_data_dir, - 'log': __windows_gitea_runner_log_dir, - 'nested_build': __windows_gitea_runner_enable_nested_build, - 'storage': __windows_gitea_runner_storage_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 runner directories exist - ansible.windows.win_file: - path: "{{ item }}" - state: directory - loop: - - "{{ windows_gitea_runner_build_root }}" - - "{{ __windows_gitea_runner_build_dir }}" - - "{{ __windows_gitea_runner_data_dir }}" - - "{{ __windows_gitea_runner_log_dir }}" - - "{{ __windows_gitea_runner_config_dir }}" - -- name: Ensure nested build storage directory exists - ansible.windows.win_file: - path: "{{ __windows_gitea_runner_storage_dir }}" - state: directory - when: __windows_gitea_runner_enable_nested_build - -- name: Grant podman service user access to runner directories - ansible.windows.win_acl: - path: "{{ item }}" - user: ".\\{{ windows_gitea_runner_podman_service_user }}" - rights: FullControl - type: allow - state: present - inherit: ContainerInherit, ObjectInherit - propagation: None - loop: - - "{{ windows_gitea_runner_build_root }}" - - "{{ __windows_gitea_runner_data_dir }}" - - "{{ __windows_gitea_runner_log_dir }}" - - "{{ __windows_gitea_runner_config_dir }}" - become: true - become_method: ansible.builtin.runas - become_user: SYSTEM - -- name: Grant podman service user access to nested build storage directory - ansible.windows.win_acl: - path: "{{ __windows_gitea_runner_storage_dir }}" - user: ".\\{{ windows_gitea_runner_podman_service_user }}" - rights: FullControl - type: allow - state: present - inherit: ContainerInherit, ObjectInherit - propagation: None - become: true - become_method: ansible.builtin.runas - become_user: SYSTEM - when: __windows_gitea_runner_enable_nested_build - -- name: Render runner config - ansible.windows.win_template: - src: config.yaml.j2 - dest: "{{ __windows_gitea_runner_runtime_config_file }}" - -- name: Copy 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 runner image hash label - ansible.windows.win_shell: | - $ErrorActionPreference = "Stop" - $podman = "{{ windows_gitea_runner_podman_install_dir }}\podman.exe" - $image = "{{ __windows_gitea_runner_image }}" - - $null = & $podman image exists $image 2>$null - if ($LASTEXITCODE -ne 0) { - Write-Output "__MISSING__" - exit 0 - } - - $raw = & $podman image inspect $image --format json 2>$null - if ($LASTEXITCODE -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_podman_service_user }}" - register: __windows_gitea_runner_current_image_hash - changed_when: false - -- name: Determine if 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 runner image when Dockerfile changed - ansible.windows.win_shell: | - $ErrorActionPreference = "Stop" - $podman = "{{ windows_gitea_runner_podman_install_dir }}\podman.exe" - & $podman 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_podman_service_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 runner container hash label - ansible.windows.win_shell: | - $ErrorActionPreference = "Stop" - $name = "{{ __windows_gitea_runner_container_name }}" - $podman = "{{ windows_gitea_runner_podman_install_dir }}\podman.exe" - - $null = & $podman container exists $name 2>$null - if ($LASTEXITCODE -ne 0) { - Write-Output "__MISSING__" - exit 0 - } - - $raw = & $podman inspect $name --format json 2>$null - if ($LASTEXITCODE -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_podman_service_user }}" - register: __windows_gitea_runner_current_hash - changed_when: false - -- name: Determine if 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 runner container before recreate - ansible.windows.win_shell: | - $podman = "{{ windows_gitea_runner_podman_install_dir }}\podman.exe" - & $podman rm -f "{{ __windows_gitea_runner_container_name }}" 2>$null - become: true - become_method: ansible.builtin.runas - become_user: "{{ windows_gitea_runner_podman_service_user }}" - when: __windows_gitea_runner_needs_recreate - changed_when: true - failed_when: false - -- name: Ensure runner container is running - ansible.windows.win_shell: | - $ErrorActionPreference = "Stop" - $podman = "{{ windows_gitea_runner_podman_install_dir }}\podman.exe" - $name = "{{ __windows_gitea_runner_container_name }}" - $exists = & $podman container exists $name - if ($LASTEXITCODE -eq 0) { - & $podman start $name 2>$null | Out-Null - Write-Output "__STARTED_EXISTING__" - exit 0 - } - - & $podman 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 }}" ` - -v "{{ __windows_gitea_runner_data_dir }}:/data" ` - -e "CONFIG_FILE=/data/config.yaml" ` - -e "GITEA_INSTANCE_URL={{ windows_gitea_runner_instance_url }}" ` - -e "GITEA_RUNNER_REGISTRATION_TOKEN={{ windows_gitea_runner_registration_token }}" ` - -e "GITEA_RUNNER_NAME={{ __windows_gitea_runner_name }}" ` - -e "GITEA_RUNNER_LABELS={{ __windows_gitea_runner_labels }}" ` - {% if __windows_gitea_runner_enable_nested_build %} - -v "{{ __windows_gitea_runner_storage_dir }}:/var/lib/containers" ` - -e "BUILDAH_ISOLATION=chroot" ` - -e "STORAGE_DRIVER=vfs" ` - {% endif %} - "{{ __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_podman_service_user }}" - register: __windows_gitea_runner_container - changed_when: "'__CREATED__' in __windows_gitea_runner_container.stdout" - no_log: true - -- name: Verify runner container state - ansible.windows.win_shell: | - $podman = "{{ windows_gitea_runner_podman_install_dir }}\podman.exe" - & $podman inspect "{{ __windows_gitea_runner_container_name }}" --format "{{ '{{' }}.State.Status{{ '}}' }}" - become: true - become_method: ansible.builtin.runas - become_user: "{{ windows_gitea_runner_podman_service_user }}" - register: __windows_gitea_runner_status - changed_when: false - failed_when: (__windows_gitea_runner_status.stdout | trim) != 'running' - -- name: Show tail of runner logs - ansible.windows.win_shell: | - $podman = "{{ windows_gitea_runner_podman_install_dir }}\podman.exe" - & $podman 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_podman_service_user }}" - register: __windows_gitea_runner_logs - changed_when: false - -- name: Print runner logs - ansible.builtin.debug: - msg: "{{ __windows_gitea_runner_logs.stdout | default('') }}" - -- name: Build normalized 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 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" - - "'is a directory' not in __windows_gitea_runner_logs_all" - fail_msg: >- - Gitea runner logs indicate a registration/startup failure. Check token, - instance URL, and config mount path. diff --git a/ansible/roles/windows_gitea_runner/templates/config.yaml.j2 b/ansible/roles/windows_gitea_runner/templates/config.yaml.j2 deleted file mode 100644 index 98917aa..0000000 --- a/ansible/roles/windows_gitea_runner/templates/config.yaml.j2 +++ /dev/null @@ -1,11 +0,0 @@ -log: - level: info - -runner: - file: {{ windows_gitea_runner_config_runner_file | default('/data/.runner') | to_json }} - capacity: 1 - envs: {} - -cache: - enabled: true - dir: {{ windows_gitea_runner_config_cache_dir | default('/data/cache') | to_json }} diff --git a/ansible/roles/windows_gitea_runner_podman/defaults/main.yml b/ansible/roles/windows_gitea_runner_podman/defaults/main.yml new file mode 100644 index 0000000..993db7a --- /dev/null +++ b/ansible/roles/windows_gitea_runner_podman/defaults/main.yml @@ -0,0 +1,34 @@ +--- +windows_gitea_runner_podman_instance_url: "" +windows_gitea_runner_podman_registration_token: "" + +windows_gitea_runner_podman_runners: [] + +windows_gitea_runner_podman_base_image: "local/gitea-runner-base:latest" +windows_gitea_runner_podman_base_dockerfile_src: "Dockerfile.gitea-runner-base" +windows_gitea_runner_podman_base_image_hash_label: "io.a13labs.gitea_runner.base_dockerfile_hash" +windows_gitea_runner_podman_image_prefix: "local/gitea-runner" +windows_gitea_runner_podman_image_hash_label: "io.a13labs.gitea_runner.dockerfile_hash" +windows_gitea_runner_podman_dockerfile_source_root: "{{ playbook_dir }}/files/dockerfiles" +windows_gitea_runner_podman_build_root: "C:\\ProgramData\\GiteaRunner\\builds" +windows_gitea_runner_podman_base_build_dir: "{{ windows_gitea_runner_podman_build_root }}\\base" + +windows_gitea_runner_podman_config_root: "C:\\ProgramData\\GiteaRunner\\config" +windows_gitea_runner_podman_data_root: "C:\\ProgramData\\GiteaRunner\\data" +windows_gitea_runner_podman_log_root: "C:\\Logs\\GiteaRunner" +windows_gitea_runner_podman_config_runner_file: "/data/.runner" +windows_gitea_runner_podman_config_cache_dir: "/data/cache" +windows_gitea_runner_podman_container_run_args: [] +windows_gitea_runner_podman_container_enable_nested_build: false +windows_gitea_runner_podman_container_storage_root: "{{ windows_gitea_runner_podman_data_root }}\\containers" +windows_gitea_runner_podman_container_log_tail: 120 +windows_gitea_runner_podman_container_hash_label: "io.a13labs.gitea_runner.config_hash" + +windows_gitea_runner_podman_machine_name: >- + {{ windows_containers_machine_name | default('podman-machine-default') }} +windows_gitea_runner_podman_install_dir: >- + {{ windows_containers_install_dir | default('C:\\Program Files\\RedHat\\Podman') }} +windows_gitea_runner_podman_service_user: "{{ windows_containers_service_user | default('podman') }}" +windows_gitea_runner_podman_service_name: "PodmanAutoStart" + +windows_gitea_runner_podman_restart_policy: "always" diff --git a/ansible/roles/windows_gitea_runner/meta/main.yml b/ansible/roles/windows_gitea_runner_podman/meta/main.yml similarity index 63% rename from ansible/roles/windows_gitea_runner/meta/main.yml rename to ansible/roles/windows_gitea_runner_podman/meta/main.yml index 07ab209..85fbfd6 100644 --- a/ansible/roles/windows_gitea_runner/meta/main.yml +++ b/ansible/roles/windows_gitea_runner_podman/meta/main.yml @@ -1,9 +1,9 @@ --- galaxy_info: author: Alexandre Pires - description: Run a persistent Gitea runner container on Podman on Windows + description: Run Linux Gitea runner containers on Podman (WSL2) on Windows company: A13Labs - role_name: windows_gitea_runner + role_name: windows_gitea_runner_podman namespace: a13labs license: MIT min_ansible_version: "2.1" diff --git a/ansible/roles/windows_gitea_runner_podman/tasks/main.yml b/ansible/roles/windows_gitea_runner_podman/tasks/main.yml new file mode 100644 index 0000000..6e15ef5 --- /dev/null +++ b/ansible/roles/windows_gitea_runner_podman/tasks/main.yml @@ -0,0 +1,182 @@ +--- +- name: Validate required runner settings + ansible.builtin.assert: + that: + - windows_gitea_runner_podman_instance_url | length > 0 + - windows_gitea_runner_podman_registration_token | length > 0 + - windows_gitea_runner_podman_runners | length > 0 + fail_msg: >- + windows_gitea_runner_podman_instance_url, windows_gitea_runner_podman_registration_token, + and at least one windows_gitea_runner_podman_runners entry are required. + tags: + - always + +- name: Ensure Podman auto-start service is running + ansible.windows.win_service: + name: "{{ windows_gitea_runner_podman_service_name }}" + start_mode: auto + state: started + become: true + become_method: ansible.builtin.runas + become_user: SYSTEM + tags: + - roles::windows_gitea_runner_podman::linux + +- name: Wait until Podman machine is ready + ansible.windows.win_shell: | + $ErrorActionPreference = "Stop" + $ready = $false + for ($i = 0; $i -lt 90; $i++) { + $null = & "{{ windows_gitea_runner_podman_install_dir }}\podman.exe" info --format json 2>$null + if ($LASTEXITCODE -eq 0) { + $ready = $true + break + } + Start-Sleep -Seconds 2 + } + if (-not $ready) { + Write-Error "Podman machine {{ windows_gitea_runner_podman_machine_name }} did not become ready" + exit 1 + } + become: true + become_method: ansible.builtin.runas + become_user: "{{ windows_gitea_runner_podman_service_user }}" + changed_when: false + tags: + - roles::windows_gitea_runner_podman::linux + +- name: Ensure base build directory exists + ansible.windows.win_file: + path: "{{ windows_gitea_runner_podman_base_build_dir }}" + state: directory + tags: + - roles::windows_gitea_runner_podman::linux + +- name: Copy base Dockerfile to build context + ansible.windows.win_copy: + src: >- + {{ windows_gitea_runner_podman_dockerfile_source_root }}/{{ + windows_gitea_runner_podman_base_dockerfile_src }} + dest: "{{ windows_gitea_runner_podman_base_build_dir }}\\Dockerfile" + tags: + - roles::windows_gitea_runner_podman::linux + +- name: Grant podman service user access to base build directory + ansible.windows.win_acl: + path: "{{ windows_gitea_runner_podman_base_build_dir }}" + user: ".\\{{ windows_gitea_runner_podman_service_user }}" + rights: FullControl + type: allow + state: present + inherit: ContainerInherit, ObjectInherit + propagation: None + become: true + become_method: ansible.builtin.runas + become_user: SYSTEM + tags: + - roles::windows_gitea_runner_podman::linux + +- name: Read current base image hash label + ansible.windows.win_shell: | + $ErrorActionPreference = "Stop" + $podman = "{{ windows_gitea_runner_podman_install_dir }}\podman.exe" + $image = "{{ windows_gitea_runner_podman_base_image }}" + + $null = & $podman image exists $image 2>$null + if ($LASTEXITCODE -ne 0) { + Write-Output "__MISSING__" + exit 0 + } + + $raw = & $podman image inspect $image --format json 2>$null + if ($LASTEXITCODE -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_podman_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_podman_service_user }}" + register: __windows_gitea_runner_podman_current_base_image_hash + changed_when: false + tags: + - roles::windows_gitea_runner_podman::linux + +- name: Read desired base Dockerfile checksum + ansible.builtin.set_fact: + __windows_gitea_runner_podman_base_dockerfile_checksum: >- + {{ + lookup( + 'file', + windows_gitea_runner_podman_dockerfile_source_root ~ '/' ~ windows_gitea_runner_podman_base_dockerfile_src + ) | hash('sha1') + }} + tags: + - roles::windows_gitea_runner_podman::linux + +- name: Determine if base image needs rebuild + ansible.builtin.set_fact: + __windows_gitea_runner_podman_needs_base_image_rebuild: >- + {{ + (__windows_gitea_runner_podman_current_base_image_hash.stdout | trim) + != (__windows_gitea_runner_podman_base_dockerfile_checksum | trim) + }} + tags: + - roles::windows_gitea_runner_podman::linux + +- name: Rebuild base image when Dockerfile changed + ansible.windows.win_shell: | + $ErrorActionPreference = "Stop" + $podman = "{{ windows_gitea_runner_podman_install_dir }}\podman.exe" + & $podman build ` + --label "{{ + windows_gitea_runner_podman_base_image_hash_label + }}={{ __windows_gitea_runner_podman_base_dockerfile_checksum }}" ` + --tag "{{ windows_gitea_runner_podman_base_image }}" ` + --file "{{ windows_gitea_runner_podman_base_build_dir }}\\Dockerfile" ` + "{{ windows_gitea_runner_podman_base_build_dir }}" + if ($LASTEXITCODE -ne 0) { + exit 1 + } + Write-Output "__BUILT__" + become: true + become_method: ansible.builtin.runas + become_user: "{{ windows_gitea_runner_podman_service_user }}" + register: __windows_gitea_runner_podman_base_build + changed_when: "'__BUILT__' in __windows_gitea_runner_podman_base_build.stdout" + when: __windows_gitea_runner_podman_needs_base_image_rebuild + tags: + - roles::windows_gitea_runner_podman::linux + +- name: Reconcile runners + ansible.builtin.include_tasks: runner.yml + args: + apply: + tags: + - roles::windows_gitea_runner_podman::linux + loop: "{{ windows_gitea_runner_podman_runners }}" + loop_control: + loop_var: windows_gitea_runner_podman_runner + label: >- + {{ windows_gitea_runner_podman_runner.name | default(windows_gitea_runner_podman_runner.profile) }} + tags: + - roles::windows_gitea_runner_podman::linux diff --git a/ansible/roles/windows_gitea_runner_podman/tasks/runner.yml b/ansible/roles/windows_gitea_runner_podman/tasks/runner.yml new file mode 100644 index 0000000..5385fe1 --- /dev/null +++ b/ansible/roles/windows_gitea_runner_podman/tasks/runner.yml @@ -0,0 +1,429 @@ +--- +- name: Resolve runner profile + ansible.builtin.set_fact: + __windows_gitea_runner_podman_profile: >- + {{ + windows_gitea_runner_podman_runner.profile + | default(windows_gitea_runner_podman_runner.name | default('runner')) + }} + +- name: Resolve runner-specific settings + ansible.builtin.set_fact: + __windows_gitea_runner_podman_name: >- + {{ + windows_gitea_runner_podman_runner.name | default( + 'gitea-runner-' ~ __windows_gitea_runner_podman_profile + ) + }} + __windows_gitea_runner_podman_container_name: >- + {{ + windows_gitea_runner_podman_runner.container_name | default( + 'gitea-runner-' ~ __windows_gitea_runner_podman_profile + ) + }} + __windows_gitea_runner_podman_image: >- + {{ + windows_gitea_runner_podman_runner.image | default( + windows_gitea_runner_podman_image_prefix ~ ':' ~ __windows_gitea_runner_podman_profile + ) + }} + __windows_gitea_runner_podman_labels: >- + {{ + windows_gitea_runner_podman_runner.labels | default( + __windows_gitea_runner_podman_profile ~ ':host' + ) + }} + __windows_gitea_runner_podman_dockerfile_src: >- + {{ + windows_gitea_runner_podman_runner.dockerfile_src | default( + 'Dockerfile.' ~ __windows_gitea_runner_podman_profile + ) + }} + __windows_gitea_runner_podman_config_file: >- + {{ + windows_gitea_runner_podman_runner.config_file | default( + windows_gitea_runner_podman_config_root ~ '\\' ~ __windows_gitea_runner_podman_profile ~ '\\config.yaml' + ) + }} + __windows_gitea_runner_podman_config_name: >- + {{ + ( + windows_gitea_runner_podman_runner.config_file | default( + windows_gitea_runner_podman_config_root ~ '\\' ~ __windows_gitea_runner_podman_profile ~ '\\config.yaml' + ) + ) | regex_search('[^\\\\]+$') + }} + __windows_gitea_runner_podman_data_dir: >- + {{ + windows_gitea_runner_podman_runner.data_dir | default( + windows_gitea_runner_podman_data_root ~ '\\' ~ __windows_gitea_runner_podman_profile ~ '\\data' + ) + }} + __windows_gitea_runner_podman_log_dir: >- + {{ + windows_gitea_runner_podman_runner.log_dir | default( + windows_gitea_runner_podman_log_root ~ '\\' ~ __windows_gitea_runner_podman_profile + ) + }} + __windows_gitea_runner_podman_build_dir: >- + {{ windows_gitea_runner_podman_build_root ~ '\\' ~ __windows_gitea_runner_podman_profile }} + __windows_gitea_runner_podman_build_dockerfile_dest: >- + {{ windows_gitea_runner_podman_build_root ~ '\\' ~ __windows_gitea_runner_podman_profile ~ '\\Dockerfile' }} + __windows_gitea_runner_podman_dockerfile_source_path: >- + {{ + windows_gitea_runner_podman_dockerfile_source_root ~ '/' ~ ( + windows_gitea_runner_podman_runner.dockerfile_src | default( + 'Dockerfile.' ~ __windows_gitea_runner_podman_profile + ) + ) + }} + __windows_gitea_runner_podman_enable_nested_build: >- + {{ + windows_gitea_runner_podman_runner.enable_nested_build + | default(windows_gitea_runner_podman_container_enable_nested_build) + }} + __windows_gitea_runner_podman_storage_dir: >- + {{ + ( + windows_gitea_runner_podman_runner.storage_dir | default( + windows_gitea_runner_podman_container_storage_root ~ '\\' ~ __windows_gitea_runner_podman_profile + ) + ) + if ( + windows_gitea_runner_podman_runner.enable_nested_build + | default(windows_gitea_runner_podman_container_enable_nested_build) + ) + else '' + }} + __windows_gitea_runner_podman_run_args: >- + {{ windows_gitea_runner_podman_runner.run_args | default(windows_gitea_runner_podman_container_run_args) }} + +- name: Resolve runner runtime config path + ansible.builtin.set_fact: + __windows_gitea_runner_podman_runtime_config_file: "{{ __windows_gitea_runner_podman_data_dir }}\\config.yaml" + +- name: Read runner Dockerfile checksum + ansible.builtin.set_fact: + __windows_gitea_runner_podman_base_dockerfile_checksum: >- + {{ + lookup( + 'file', + windows_gitea_runner_podman_dockerfile_source_root ~ '/' ~ windows_gitea_runner_podman_base_dockerfile_src + ) | hash('sha1') + }} + __windows_gitea_runner_podman_profile_dockerfile_checksum: >- + {{ lookup('file', __windows_gitea_runner_podman_dockerfile_source_path) | hash('sha1') }} + +- name: Compute desired runner image hash + ansible.builtin.set_fact: + __windows_gitea_runner_podman_desired_image_hash: >- + {{ { + 'base': __windows_gitea_runner_podman_base_dockerfile_checksum, + 'profile': __windows_gitea_runner_podman_profile_dockerfile_checksum + } | to_json | hash('sha1') }} + +- name: Compute desired runner container hash + ansible.builtin.set_fact: + __windows_gitea_runner_podman_desired_container_hash: >- + {{ { + 'image': __windows_gitea_runner_podman_image, + 'name': __windows_gitea_runner_podman_name, + 'labels': __windows_gitea_runner_podman_labels, + 'instance': windows_gitea_runner_podman_instance_url, + 'config': __windows_gitea_runner_podman_runtime_config_file, + 'config_dir': __windows_gitea_runner_podman_data_dir, + 'config_name': ( + (__windows_gitea_runner_podman_runtime_config_file | regex_search('[^\\\\]+$')) + | default('config.yaml', true) + ), + 'config_mount_mode': 'data', + 'data': __windows_gitea_runner_podman_data_dir, + 'log': __windows_gitea_runner_podman_log_dir, + 'nested_build': __windows_gitea_runner_podman_enable_nested_build, + 'storage': __windows_gitea_runner_podman_storage_dir, + 'run_args': __windows_gitea_runner_podman_run_args, + 'restart': windows_gitea_runner_podman_restart_policy, + 'image_hash': __windows_gitea_runner_podman_desired_image_hash + } | to_json | hash('sha1') }} + +- name: Ensure runner directories exist + ansible.windows.win_file: + path: "{{ item }}" + state: directory + loop: + - "{{ windows_gitea_runner_podman_build_root }}" + - "{{ __windows_gitea_runner_podman_build_dir }}" + - "{{ __windows_gitea_runner_podman_data_dir }}" + - "{{ __windows_gitea_runner_podman_log_dir }}" + +- name: Ensure nested build storage directory exists + ansible.windows.win_file: + path: "{{ __windows_gitea_runner_podman_storage_dir }}" + state: directory + when: __windows_gitea_runner_podman_enable_nested_build + +- name: Grant podman service user access to runner directories + ansible.windows.win_acl: + path: "{{ item }}" + user: ".\\{{ windows_gitea_runner_podman_service_user }}" + rights: FullControl + type: allow + state: present + inherit: ContainerInherit, ObjectInherit + propagation: None + loop: + - "{{ windows_gitea_runner_podman_build_root }}" + - "{{ __windows_gitea_runner_podman_data_dir }}" + - "{{ __windows_gitea_runner_podman_log_dir }}" + become: true + become_method: ansible.builtin.runas + become_user: SYSTEM + +- name: Grant podman service user access to nested build storage directory + ansible.windows.win_acl: + path: "{{ __windows_gitea_runner_podman_storage_dir }}" + user: ".\\{{ windows_gitea_runner_podman_service_user }}" + rights: FullControl + type: allow + state: present + inherit: ContainerInherit, ObjectInherit + propagation: None + become: true + become_method: ansible.builtin.runas + become_user: SYSTEM + when: __windows_gitea_runner_podman_enable_nested_build + +- name: Render runner config + ansible.windows.win_template: + src: config.yaml.j2 + dest: "{{ __windows_gitea_runner_podman_runtime_config_file }}" + +- name: Copy runner Dockerfile to build context + ansible.windows.win_copy: + src: "{{ __windows_gitea_runner_podman_dockerfile_source_path }}" + dest: "{{ __windows_gitea_runner_podman_build_dockerfile_dest }}" + +- name: Read current runner image hash label + ansible.windows.win_shell: | + $ErrorActionPreference = "Stop" + $podman = "{{ windows_gitea_runner_podman_install_dir }}\podman.exe" + $image = "{{ __windows_gitea_runner_podman_image }}" + + $null = & $podman image exists $image 2>$null + if ($LASTEXITCODE -ne 0) { + Write-Output "__MISSING__" + exit 0 + } + + $raw = & $podman image inspect $image --format json 2>$null + if ($LASTEXITCODE -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_podman_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_podman_service_user }}" + register: __windows_gitea_runner_podman_current_image_hash + changed_when: false + +- name: Determine if runner image needs rebuild + ansible.builtin.set_fact: + __windows_gitea_runner_podman_needs_image_rebuild: >- + {{ + (__windows_gitea_runner_podman_current_image_hash.stdout | trim) + != (__windows_gitea_runner_podman_desired_image_hash | trim) + }} + +- name: Rebuild runner image when Dockerfile changed + ansible.windows.win_shell: | + $ErrorActionPreference = "Stop" + $podman = "{{ windows_gitea_runner_podman_install_dir }}\podman.exe" + & $podman build ` + --label "{{ + windows_gitea_runner_podman_image_hash_label + }}={{ __windows_gitea_runner_podman_desired_image_hash }}" ` + --tag "{{ __windows_gitea_runner_podman_image }}" ` + --file "{{ __windows_gitea_runner_podman_build_dockerfile_dest }}" ` + "{{ __windows_gitea_runner_podman_build_dir }}" + if ($LASTEXITCODE -ne 0) { + exit 1 + } + Write-Output "__BUILT__" + become: true + become_method: ansible.builtin.runas + become_user: "{{ windows_gitea_runner_podman_service_user }}" + register: __windows_gitea_runner_podman_build + changed_when: "'__BUILT__' in __windows_gitea_runner_podman_build.stdout" + when: __windows_gitea_runner_podman_needs_image_rebuild + +- name: Read current runner container hash label + ansible.windows.win_shell: | + $ErrorActionPreference = "Stop" + $name = "{{ __windows_gitea_runner_podman_container_name }}" + $podman = "{{ windows_gitea_runner_podman_install_dir }}\podman.exe" + + $null = & $podman container exists $name 2>$null + if ($LASTEXITCODE -ne 0) { + Write-Output "__MISSING__" + exit 0 + } + + $raw = & $podman inspect $name --format json 2>$null + if ($LASTEXITCODE -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_podman_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_podman_service_user }}" + register: __windows_gitea_runner_podman_current_hash + changed_when: false + +- name: Determine if runner container needs recreation + ansible.builtin.set_fact: + __windows_gitea_runner_podman_needs_recreate: >- + {{ + (__windows_gitea_runner_podman_current_hash.stdout | trim) + != (__windows_gitea_runner_podman_desired_container_hash | trim) + }} + +- name: Remove stale runner container before recreate + ansible.windows.win_shell: | + $podman = "{{ windows_gitea_runner_podman_install_dir }}\podman.exe" + & $podman rm -f "{{ __windows_gitea_runner_podman_container_name }}" 2>$null + become: true + become_method: ansible.builtin.runas + become_user: "{{ windows_gitea_runner_podman_service_user }}" + when: __windows_gitea_runner_podman_needs_recreate + changed_when: true + failed_when: false + +- name: Ensure runner container is running + ansible.windows.win_shell: | + $ErrorActionPreference = "Stop" + $podman = "{{ windows_gitea_runner_podman_install_dir }}\podman.exe" + $name = "{{ __windows_gitea_runner_podman_container_name }}" + $exists = & $podman container exists $name + if ($LASTEXITCODE -eq 0) { + & $podman start $name 2>$null | Out-Null + Write-Output "__STARTED_EXISTING__" + exit 0 + } + + & $podman run -d --name $name --restart "{{ windows_gitea_runner_podman_restart_policy }}" ` + @({{ __windows_gitea_runner_podman_run_args | map('to_json') | join(', ') }}) ` + --label "{{ + windows_gitea_runner_podman_container_hash_label + }}={{ __windows_gitea_runner_podman_desired_container_hash }}" ` + -v "{{ __windows_gitea_runner_podman_data_dir }}:/data" ` + -e "CONFIG_FILE=/data/config.yaml" ` + -e "GITEA_INSTANCE_URL={{ windows_gitea_runner_podman_instance_url }}" ` + -e "GITEA_RUNNER_REGISTRATION_TOKEN={{ windows_gitea_runner_podman_registration_token }}" ` + -e "GITEA_RUNNER_NAME={{ __windows_gitea_runner_podman_name }}" ` + -e "GITEA_RUNNER_LABELS={{ __windows_gitea_runner_podman_labels }}" ` + {% if __windows_gitea_runner_podman_enable_nested_build %} + -v "{{ __windows_gitea_runner_podman_storage_dir }}:/var/lib/containers" ` + -e "BUILDAH_ISOLATION=chroot" ` + -e "STORAGE_DRIVER=vfs" ` + {% endif %} + "{{ __windows_gitea_runner_podman_image }}" | Out-Null + + if ($LASTEXITCODE -ne 0) { + exit 1 + } + + Write-Output "__CREATED__" + become: true + become_method: ansible.builtin.runas + become_user: "{{ windows_gitea_runner_podman_service_user }}" + register: __windows_gitea_runner_podman_container + changed_when: "'__CREATED__' in __windows_gitea_runner_podman_container.stdout" + no_log: true + +- name: Verify runner container state + ansible.windows.win_shell: | + $podman = "{{ windows_gitea_runner_podman_install_dir }}\podman.exe" + & $podman inspect "{{ __windows_gitea_runner_podman_container_name }}" --format "{{ '{{' }}.State.Status{{ '}}' }}" + become: true + become_method: ansible.builtin.runas + become_user: "{{ windows_gitea_runner_podman_service_user }}" + register: __windows_gitea_runner_podman_status + changed_when: false + failed_when: (__windows_gitea_runner_podman_status.stdout | trim) != 'running' + +- name: Show tail of runner logs + ansible.windows.win_shell: | + $podman = "{{ windows_gitea_runner_podman_install_dir }}\podman.exe" + & $podman logs --tail {{ + windows_gitea_runner_podman_container_log_tail }} "{{ + __windows_gitea_runner_podman_container_name }}" + become: true + become_method: ansible.builtin.runas + become_user: "{{ windows_gitea_runner_podman_service_user }}" + register: __windows_gitea_runner_podman_logs + changed_when: false + +- name: Print runner logs + ansible.builtin.debug: + msg: "{{ __windows_gitea_runner_podman_logs.stdout | default('') }}" + +- name: Build normalized runner log text + ansible.builtin.set_fact: + __windows_gitea_runner_podman_logs_all: >- + {{ + [ + __windows_gitea_runner_podman_logs.stdout | default(''), + __windows_gitea_runner_podman_logs.stderr | default('') + ] | join('\n') | lower + }} + +- name: Assert runner did not fail registration + ansible.builtin.assert: + that: + - "'invalid token' not in __windows_gitea_runner_podman_logs_all" + - "'unauthorized' not in __windows_gitea_runner_podman_logs_all" + - "'open config file' not in __windows_gitea_runner_podman_logs_all" + - "'is a directory' not in __windows_gitea_runner_podman_logs_all" + fail_msg: >- + Gitea runner logs indicate a registration/startup failure. Check token, + instance URL, and config mount path. diff --git a/ansible/roles/windows_gitea_runner_podman/templates/config.yaml.j2 b/ansible/roles/windows_gitea_runner_podman/templates/config.yaml.j2 new file mode 100644 index 0000000..54a79e5 --- /dev/null +++ b/ansible/roles/windows_gitea_runner_podman/templates/config.yaml.j2 @@ -0,0 +1,11 @@ +log: + level: info + +runner: + file: "{{ windows_gitea_runner_podman_config_runner_file | default('/data/.runner') }}" + capacity: 1 + envs: {} + +cache: + enabled: true + dir: "{{ windows_gitea_runner_podman_config_cache_dir | default('/data/cache') }}" diff --git a/ansible/roles/windows_gitea_runner_windows/defaults/main.yml b/ansible/roles/windows_gitea_runner_windows/defaults/main.yml new file mode 100644 index 0000000..8963b43 --- /dev/null +++ b/ansible/roles/windows_gitea_runner_windows/defaults/main.yml @@ -0,0 +1,30 @@ +--- +windows_gitea_runner_windows_instance_url: "" +windows_gitea_runner_windows_registration_token: "" + +windows_gitea_runner_windows_runners: [] + +windows_gitea_runner_windows_image_prefix: "local/gitea-windows-runner" +windows_gitea_runner_windows_image_hash_label: "io.a13labs.gitea_runner.dockerfile_hash" +windows_gitea_runner_windows_dockerfile_source_root: "{{ playbook_dir }}/files/dockerfiles/windows-runners" + +windows_gitea_runner_windows_runtime_bin: "docker" +windows_gitea_runner_windows_runtime_user: "" +windows_gitea_runner_windows_become_user: "SYSTEM" + +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_image_hash_label: "io.a13labs.gitea_runner.windows_base_dockerfile_hash" +windows_gitea_runner_windows_build_root: "C:\\ProgramData\\GiteaRunner\\windows-builds" +windows_gitea_runner_windows_base_build_dir: "{{ windows_gitea_runner_windows_build_root }}\\windows-base" + +windows_gitea_runner_windows_config_root: "C:\\ProgramData\\GiteaRunner\\config" +windows_gitea_runner_windows_data_root: "C:\\ProgramData\\GiteaRunner\\data" +windows_gitea_runner_windows_log_root: "C:\\Logs\\GiteaRunner" +windows_gitea_runner_windows_config_runner_file: "C:/runner-data/.runner" +windows_gitea_runner_windows_config_cache_dir: "C:/runner-data/cache" +windows_gitea_runner_windows_container_log_tail: 120 +windows_gitea_runner_windows_container_hash_label: "io.a13labs.gitea_runner.config_hash" + +windows_gitea_runner_windows_restart_policy: "always" diff --git a/ansible/roles/windows_gitea_runner_windows/meta/main.yml b/ansible/roles/windows_gitea_runner_windows/meta/main.yml new file mode 100644 index 0000000..431884e --- /dev/null +++ b/ansible/roles/windows_gitea_runner_windows/meta/main.yml @@ -0,0 +1,14 @@ +--- +galaxy_info: + author: Alexandre Pires + description: Run Windows Gitea runner containers via Mirantis Container Runtime on Windows + company: A13Labs + role_name: windows_gitea_runner_windows + namespace: a13labs + license: MIT + min_ansible_version: "2.1" + platforms: + - name: Windows + +dependencies: + - role: windows_containers diff --git a/ansible/roles/windows_gitea_runner_windows/tasks/main.yml b/ansible/roles/windows_gitea_runner_windows/tasks/main.yml new file mode 100644 index 0000000..af4fd73 --- /dev/null +++ b/ansible/roles/windows_gitea_runner_windows/tasks/main.yml @@ -0,0 +1,230 @@ +--- +- name: Validate required runner settings + ansible.builtin.assert: + that: + - windows_gitea_runner_windows_instance_url | length > 0 + - windows_gitea_runner_windows_registration_token | length > 0 + - windows_gitea_runner_windows_runners | length > 0 + fail_msg: >- + windows_gitea_runner_windows_instance_url, windows_gitea_runner_windows_registration_token, + and at least one windows_gitea_runner_windows_runners entry are required. + tags: + - always + +- name: Ensure Docker service is running + ansible.windows.win_service: + name: docker + start_mode: auto + state: started + become: true + become_method: ansible.builtin.runas + become_user: SYSTEM + tags: + - roles::windows_gitea_runner_windows::windows + +- name: Resolve runtime user + ansible.builtin.set_fact: + __windows_gitea_runner_windows_runtime_user: >- + {{ + windows_gitea_runner_windows_runtime_user + | 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) + }} + tags: + - roles::windows_gitea_runner_windows::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_windows_docker_users_group + changed_when: false + when: __windows_gitea_runner_windows_runtime_user | length > 0 + tags: + - roles::windows_gitea_runner_windows::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_windows_runtime_user | length > 0 + - "'__EXISTS__' in (__windows_gitea_runner_windows_docker_users_group.stdout | default(''))" + tags: + - roles::windows_gitea_runner_windows::windows + +- name: Ensure Windows base build directory exists + ansible.windows.win_file: + path: "{{ windows_gitea_runner_windows_base_build_dir }}" + state: directory + tags: + - roles::windows_gitea_runner_windows::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" + tags: + - roles::windows_gitea_runner_windows::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 + }} + tags: + - roles::windows_gitea_runner_windows::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_windows_current_base_image_hash + changed_when: false + tags: + - roles::windows_gitea_runner_windows::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') + }} + tags: + - roles::windows_gitea_runner_windows::windows + +- name: Determine if Windows base image needs rebuild + ansible.builtin.set_fact: + __windows_gitea_runner_windows_needs_base_image_rebuild: >- + {{ + (__windows_gitea_runner_windows_current_base_image_hash.stdout | trim) + != (__windows_gitea_runner_windows_base_dockerfile_checksum | trim) + }} + tags: + - roles::windows_gitea_runner_windows::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_windows_needs_base_image_rebuild + tags: + - roles::windows_gitea_runner_windows::windows + +- name: Reconcile runners + ansible.builtin.include_tasks: runner.yml + args: + apply: + tags: + - roles::windows_gitea_runner_windows::windows + loop: "{{ windows_gitea_runner_windows_runners }}" + loop_control: + loop_var: windows_gitea_runner_windows_runner + label: >- + {{ windows_gitea_runner_windows_runner.name | default(windows_gitea_runner_windows_runner.profile) }} + tags: + - roles::windows_gitea_runner_windows::windows diff --git a/ansible/roles/windows_gitea_runner/tasks/runner_windows.yml b/ansible/roles/windows_gitea_runner_windows/tasks/runner.yml similarity index 54% rename from ansible/roles/windows_gitea_runner/tasks/runner_windows.yml rename to ansible/roles/windows_gitea_runner_windows/tasks/runner.yml index dce7440..4c1a15c 100644 --- a/ansible/roles/windows_gitea_runner/tasks/runner_windows.yml +++ b/ansible/roles/windows_gitea_runner_windows/tasks/runner.yml @@ -1,94 +1,85 @@ --- - 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')) }} + __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_name: >- + __windows_gitea_runner_windows_name: >- {{ - windows_gitea_runner_runner.name | default( - 'gitea-runner-' ~ __windows_gitea_runner_profile + windows_gitea_runner_windows_runner.name | default( + 'gitea-runner-' ~ __windows_gitea_runner_windows_profile ) }} - __windows_gitea_runner_container_name: >- + __windows_gitea_runner_windows_container_name: >- {{ - windows_gitea_runner_runner.container_name | default( - 'gitea-runner-' ~ __windows_gitea_runner_profile + windows_gitea_runner_windows_runner.container_name | default( + 'gitea-runner-' ~ __windows_gitea_runner_windows_profile ) }} - __windows_gitea_runner_image: >- + __windows_gitea_runner_windows_image: >- {{ - windows_gitea_runner_runner.image | default( - windows_gitea_runner_windows_image_prefix ~ ':' ~ __windows_gitea_runner_profile + windows_gitea_runner_windows_runner.image | default( + windows_gitea_runner_windows_image_prefix ~ ':' ~ __windows_gitea_runner_windows_profile ) }} - __windows_gitea_runner_labels: >- + __windows_gitea_runner_windows_labels: >- {{ - windows_gitea_runner_runner.labels | default( - 'windows:host,' ~ __windows_gitea_runner_profile + windows_gitea_runner_windows_runner.labels | default( + 'windows:host,' ~ __windows_gitea_runner_windows_profile ) }} - __windows_gitea_runner_dockerfile_src: >- + __windows_gitea_runner_windows_dockerfile_src: >- {{ - windows_gitea_runner_runner.dockerfile_src | default( - 'Dockerfile.' ~ __windows_gitea_runner_profile + windows_gitea_runner_windows_runner.dockerfile_src | default( + 'Dockerfile.' ~ __windows_gitea_runner_windows_profile ) }} - __windows_gitea_runner_config_file: >- + __windows_gitea_runner_windows_config_file: >- {{ - windows_gitea_runner_runner.config_file | default( - windows_gitea_runner_config_root ~ '\\' ~ __windows_gitea_runner_profile ~ '\\config.yaml' + windows_gitea_runner_windows_runner.config_file | default( + windows_gitea_runner_windows_config_root ~ '\\' ~ __windows_gitea_runner_windows_profile ~ '\\config.yaml' ) }} - __windows_gitea_runner_config_dir: >- + __windows_gitea_runner_windows_data_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_windows_runner.data_dir | default( + windows_gitea_runner_windows_data_root ~ '\\' ~ __windows_gitea_runner_windows_profile ~ '\\data' ) }} - __windows_gitea_runner_log_dir: >- + __windows_gitea_runner_windows_log_dir: >- {{ - windows_gitea_runner_runner.log_dir | default( - windows_gitea_runner_log_root ~ '\\' ~ __windows_gitea_runner_profile + windows_gitea_runner_windows_runner.log_dir | default( + windows_gitea_runner_windows_log_root ~ '\\' ~ __windows_gitea_runner_windows_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_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_runner.dockerfile_src | default( - 'Dockerfile.' ~ __windows_gitea_runner_profile + windows_gitea_runner_windows_runner.dockerfile_src | default( + 'Dockerfile.' ~ __windows_gitea_runner_windows_profile ) ) }} - __windows_gitea_runner_run_args: >- - {{ windows_gitea_runner_runner.run_args | default([]) }} + __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_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') }} + __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_profile_dockerfile_checksum: >- - {{ lookup('file', __windows_gitea_runner_dockerfile_source_path) | hash('sha1') }} + __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: | @@ -131,32 +122,31 @@ become: true become_method: ansible.builtin.runas become_user: "{{ __windows_gitea_runner_windows_become_user }}" - register: __windows_gitea_runner_current_base_image_hash + 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_desired_image_hash: >- + __windows_gitea_runner_windows_desired_image_hash: >- {{ { - 'base': __windows_gitea_runner_current_base_image_hash.stdout | trim, - 'profile': __windows_gitea_runner_profile_dockerfile_checksum + '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_desired_container_hash: >- + __windows_gitea_runner_windows_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 + '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 @@ -165,26 +155,25 @@ 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 }}" + - "{{ __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_runtime_config_file }}" + dest: "{{ __windows_gitea_runner_windows_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 }}" + 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_image }}" + $image = "{{ __windows_gitea_runner_windows_image }}" if (Get-Variable -Name PSNativeCommandUseErrorActionPreference -ErrorAction SilentlyContinue) { $PSNativeCommandUseErrorActionPreference = $false @@ -210,7 +199,7 @@ exit 0 } - $key = "{{ windows_gitea_runner_image_hash_label }}" + $key = "{{ windows_gitea_runner_windows_image_hash_label }}" $current = $labels.$key if ([string]::IsNullOrWhiteSpace($current)) { Write-Output "__UNLABELED__" @@ -221,15 +210,15 @@ become: true become_method: ansible.builtin.runas become_user: "{{ __windows_gitea_runner_windows_become_user }}" - register: __windows_gitea_runner_current_image_hash + 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_needs_image_rebuild: >- + __windows_gitea_runner_windows_needs_image_rebuild: >- {{ - (__windows_gitea_runner_current_image_hash.stdout | trim) - != (__windows_gitea_runner_desired_image_hash | trim) + (__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 @@ -237,10 +226,12 @@ $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 }}" + --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 } @@ -248,14 +239,14 @@ 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 + 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_container_name }}" + $name = "{{ __windows_gitea_runner_windows_container_name }}" $docker = "{{ windows_gitea_runner_windows_runtime_bin }}" if (Get-Variable -Name PSNativeCommandUseErrorActionPreference -ErrorAction SilentlyContinue) { @@ -282,7 +273,7 @@ exit 0 } - $key = "{{ windows_gitea_runner_container_hash_label }}" + $key = "{{ windows_gitea_runner_windows_container_hash_label }}" $current = $labels.$key if ([string]::IsNullOrWhiteSpace($current)) { Write-Output "__UNLABELED__" @@ -293,25 +284,25 @@ become: true become_method: ansible.builtin.runas become_user: "{{ __windows_gitea_runner_windows_become_user }}" - register: __windows_gitea_runner_current_hash + 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_needs_recreate: >- + __windows_gitea_runner_windows_needs_recreate: >- {{ - (__windows_gitea_runner_current_hash.stdout | trim) - != (__windows_gitea_runner_desired_container_hash | trim) + (__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_container_name }}" 2>$null + & $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_needs_recreate + when: __windows_gitea_runner_windows_needs_recreate changed_when: true failed_when: false @@ -319,7 +310,7 @@ ansible.windows.win_shell: | $ErrorActionPreference = "Stop" $docker = "{{ windows_gitea_runner_windows_runtime_bin }}" - $name = "{{ __windows_gitea_runner_container_name }}" + $name = "{{ __windows_gitea_runner_windows_container_name }}" if (Get-Variable -Name PSNativeCommandUseErrorActionPreference -ErrorAction SilentlyContinue) { $PSNativeCommandUseErrorActionPreference = $false } @@ -333,24 +324,24 @@ 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 }} + $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_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" ` + & $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_image }}" | Out-Null + "{{ __windows_gitea_runner_windows_image }}" | Out-Null if ($LASTEXITCODE -ne 0) { exit 1 @@ -360,8 +351,8 @@ 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" + register: __windows_gitea_runner_windows_container + changed_when: "'__CREATED__' in __windows_gitea_runner_windows_container.stdout" failed_when: false no_log: true @@ -369,10 +360,10 @@ 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 }}" + $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 @@ -399,7 +390,7 @@ $inspect = "missing" } - $logs = & $docker logs --tail {{ windows_gitea_runner_container_log_tail }} $name 2>&1 + $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" @@ -408,14 +399,14 @@ $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" ` + @({{ __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_instance_url }}" ` + -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_labels }}" ` - "{{ __windows_gitea_runner_image }}" /S /C "echo DIAG_OK" 2>&1 + -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)" @@ -434,67 +425,72 @@ become: true become_method: ansible.builtin.runas become_user: "{{ __windows_gitea_runner_windows_become_user }}" - register: __windows_gitea_runner_start_failure_diag + register: __windows_gitea_runner_windows_start_failure_diag changed_when: false - when: (__windows_gitea_runner_container.rc | default(0)) != 0 + 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_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(''), '***') }} + 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_start_failure_diag.stdout | default(''), - __windows_gitea_runner_start_failure_diag.stderr | 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_container.rc | default(0)) != 0 + 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_container_name }}" --format "{{ '{{' }}.State.Status{{ '}}' }}" + & $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_status + register: __windows_gitea_runner_windows_status changed_when: false - failed_when: (__windows_gitea_runner_status.stdout | trim) != 'running' - when: (__windows_gitea_runner_container.rc | default(0)) == 0 + 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_container_log_tail }} "{{ __windows_gitea_runner_container_name }}" + & $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_logs + register: __windows_gitea_runner_windows_logs changed_when: false - when: (__windows_gitea_runner_container.rc | default(0)) == 0 + when: (__windows_gitea_runner_windows_container.rc | default(0)) == 0 - name: Print Windows runner logs ansible.builtin.debug: - msg: "{{ __windows_gitea_runner_logs.stdout | default('') }}" + msg: "{{ __windows_gitea_runner_windows_logs.stdout | default('') }}" - name: Build normalized Windows runner log text ansible.builtin.set_fact: - __windows_gitea_runner_logs_all: >- + __windows_gitea_runner_windows_logs_all: >- {{ [ - __windows_gitea_runner_logs.stdout | default(''), - __windows_gitea_runner_logs.stderr | default('') + __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_logs_all" - - "'unauthorized' not in __windows_gitea_runner_logs_all" - - "'open config file' not in __windows_gitea_runner_logs_all" + - "'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. diff --git a/ansible/roles/windows_gitea_runner_windows/templates/config.yaml.j2 b/ansible/roles/windows_gitea_runner_windows/templates/config.yaml.j2 new file mode 100644 index 0000000..243b9ca --- /dev/null +++ b/ansible/roles/windows_gitea_runner_windows/templates/config.yaml.j2 @@ -0,0 +1,11 @@ +log: + level: info + +runner: + file: "{{ windows_gitea_runner_windows_config_runner_file | default('C:/runner-data/.runner') }}" + capacity: 1 + envs: {} + +cache: + enabled: true + dir: "{{ windows_gitea_runner_windows_config_cache_dir | default('C:/runner-data/cache') }}" diff --git a/inventory/hosts b/inventory/hosts index 634041a..0f74d58 100644 --- a/inventory/hosts +++ b/inventory/hosts @@ -132,6 +132,9 @@ gpu-01.lab.alexpires.me [windows_containers] ci-01.lab.alexpires.me -[windows_gitea_runner] +[windows_gitea_runner_podman] +ci-01.lab.alexpires.me + +[windows_gitea_runner_windows] ci-01.lab.alexpires.me diff --git a/terraform/apps/prod-01/config.tf b/terraform/apps/prod-01/config.tf index 733e894..98b8e6b 100644 --- a/terraform/apps/prod-01/config.tf +++ b/terraform/apps/prod-01/config.tf @@ -153,8 +153,8 @@ locals { tls = true resolver = "10.19.4.136" fqdn = ["ai.alexpires.me"] - read_timeout = "300s" - send_timeout = "300s" + read_timeout = "1800" + send_timeout = "1800" upstream = "https://open-webui.lab.alexpires.me" locations = [ { diff --git a/terraform/modules/apps/open-webui/ingress.tf b/terraform/modules/apps/open-webui/ingress.tf index cad93ac..703bfc6 100644 --- a/terraform/modules/apps/open-webui/ingress.tf +++ b/terraform/modules/apps/open-webui/ingress.tf @@ -8,8 +8,8 @@ resource "kubernetes_ingress_v1" "openwebui" { "cert-manager.io/cluster-issuer" = "letsencrypt-prod" "kubernetes.io/tls-acme" = "true" "nginx.ingress.kubernetes.io/ssl-redirect" = "true" - "nginx.ingress.kubernetes.io/proxy-read-timeout" = "600s" - "nginx.ingress.kubernetes.io/proxy-send-timeout" = "600s" + "nginx.ingress.kubernetes.io/proxy-read-timeout" = "1800" + "nginx.ingress.kubernetes.io/proxy-send-timeout" = "1800" } } spec {