GRM-148: chore: bump devx to v0.41.1, update deps and runner version
This commit was merged in pull request #218.
This commit is contained in:
@@ -178,13 +178,13 @@ jobs:
|
||||
- name: Discover assigned test pairs
|
||||
env:
|
||||
RUNNER_INDEX: ${{ matrix.runner-index }}
|
||||
MAX_RUNNERS: ${{ needs.validate.outputs.runner-count }}
|
||||
MAX_RUNNERS: 6
|
||||
run: |
|
||||
. .venv/bin/activate 2>/dev/null || true
|
||||
python3 -m devx.molecule.distribute_molecule \
|
||||
--runner-index "$RUNNER_INDEX" \
|
||||
--max-runners "$MAX_RUNNERS" \
|
||||
--github-env --skip-if-excess
|
||||
--github-env
|
||||
- name: Run molecule tests
|
||||
if: env.SKIP != 'true'
|
||||
env:
|
||||
|
||||
@@ -57,7 +57,7 @@ CI also runs a best-effort `make workflow-dryrun` step (skipped if act_runner is
|
||||
## Architecture
|
||||
|
||||
- **Python CLI** (`src/grm/`) — Click-based CLI that delegates to Ansible
|
||||
- **Ansible Role** (`ansible/roles/gitea-runner/`) — Idempotent role for rootless Docker runner setup with pasta networking (IPv6 support)
|
||||
- **Ansible Role** (`ansible/roles/gitea_runner/`) — Idempotent role for rootless Docker runner setup with pasta networking (IPv6 support)
|
||||
- **devx package** (installed from git) — Reusable CI/CD tools: auto-merge, post-merge, release, publishing, molecule distribution, PR reviews, failure notifications
|
||||
- **Versioning** (`cliff.toml`) — git-cliff configuration for automated semver versioning from conventional commits
|
||||
|
||||
|
||||
@@ -184,7 +184,7 @@ test-integration:
|
||||
$(BIN)/pytest tests/integration/ -v --no-cov
|
||||
|
||||
MOLECULE := $(realpath $(BIN))/molecule
|
||||
MOLECULE_BASE := cd $(CURDIR)/ansible/roles/gitea-runner && ANSIBLE_ALLOW_BROKEN_CONDITIONALS=true ANSIBLE_INJECT_INVOCATION=1 $(MOLECULE)
|
||||
MOLECULE_BASE := cd $(CURDIR)/ansible/roles/gitea_runner && ANSIBLE_ALLOW_BROKEN_CONDITIONALS=true ANSIBLE_INJECT_INVOCATION=1 $(MOLECULE)
|
||||
|
||||
# Quick local test: Ubuntu 22.04 only, all scenarios
|
||||
molecule:
|
||||
|
||||
@@ -266,8 +266,8 @@ One of GRM's core features is the ability to run multiple isolated runners on th
|
||||
|
||||
- **Dedicated system user**: `grm-<name>` with its own home directory at `/home/grm-<name>/`
|
||||
- **Rootless Docker daemon**: Isolated Docker socket at `/run/user/<UID>/docker.sock`
|
||||
- **Data directory**: `/var/lib/gitea-runner/<name>/`
|
||||
- **Config directory**: `/etc/gitea-runner/<name>/`
|
||||
- **Data directory**: `/var/lib/gitea_runner/<name>/`
|
||||
- **Config directory**: `/etc/gitea_runner/<name>/`
|
||||
- **Systemd user service**: `gitea-runner.service` (independent start/stop/enable)
|
||||
- **Docker prune timer**: Per-instance daily cleanup
|
||||
|
||||
@@ -341,13 +341,13 @@ GRM consists of two layers:
|
||||
|
||||
1. **Python CLI** (`src/grm/`) — Built with Click, handles argument parsing, environment loading, i18n translations, and delegates to Ansible via the `ansible-playbook` subprocess. Secrets are passed via temporary JSON files to avoid exposure in the process list.
|
||||
|
||||
2. **Ansible Role** (`ansible/roles/gitea-runner/`) — Idempotent role that creates a dedicated system user, sets up rootless Docker, installs the runner binary, creates a systemd user service, registers the runner with Gitea, and sets up a Docker prune timer.
|
||||
2. **Ansible Role** (`ansible/roles/gitea_runner/`) — Idempotent role that creates a dedicated system user, sets up rootless Docker, installs the runner binary, creates a systemd user service, registers the runner with Gitea, and sets up a Docker prune timer.
|
||||
|
||||
```text
|
||||
grm install <host>
|
||||
└── RunnerManager.install()
|
||||
└── ansible-playbook ansible/install-runner.yml
|
||||
└── role: gitea-runner
|
||||
└── role: gitea_runner
|
||||
├── user_setup.yml (create per-runner system user + lingering)
|
||||
├── rootless_docker.yml (rootless Docker setup under runner user)
|
||||
├── install_runner.yml (download binary, config, register, service)
|
||||
|
||||
@@ -6,13 +6,13 @@
|
||||
tasks:
|
||||
- name: Include systemd availability check
|
||||
ansible.builtin.include_role:
|
||||
name: gitea-runner
|
||||
name: gitea_runner
|
||||
tasks_from: systemd_check.yml
|
||||
|
||||
- name: Stop gitea-runner user service
|
||||
ansible.builtin.command: systemctl --user stop gitea-runner
|
||||
become: true
|
||||
become_user: "{{ gitea_runner_service_user | default('grm-' ~ runner_name) }}"
|
||||
become_user: "{{ gitea_runner_service_user | default('grm-' ~ gitea_runner_name) }}"
|
||||
environment:
|
||||
XDG_RUNTIME_DIR: "/run/user/{{ gitea_runner_uid | default('') }}"
|
||||
when: systemd_available.stat.exists
|
||||
@@ -21,7 +21,7 @@
|
||||
- name: Stop and disable healthcheck timer
|
||||
ansible.builtin.command: systemctl --user stop --disable runner-healthcheck.timer
|
||||
become: true
|
||||
become_user: "{{ gitea_runner_service_user | default('grm-' ~ runner_name) }}"
|
||||
become_user: "{{ gitea_runner_service_user | default('grm-' ~ gitea_runner_name) }}"
|
||||
environment:
|
||||
XDG_RUNTIME_DIR: "/run/user/{{ gitea_runner_uid | default('') }}"
|
||||
when: systemd_available.stat.exists
|
||||
@@ -30,14 +30,14 @@
|
||||
|
||||
- name: Include deregistration
|
||||
ansible.builtin.include_role:
|
||||
name: gitea-runner
|
||||
name: gitea_runner
|
||||
tasks_from: deregister.yml
|
||||
when: not skip_runner_registration | default(false)
|
||||
when: not gitea_runner_skip_registration | default(false)
|
||||
|
||||
- name: Disable gitea-runner user service
|
||||
ansible.builtin.command: systemctl --user disable gitea-runner
|
||||
become: true
|
||||
become_user: "{{ gitea_runner_service_user | default('grm-' ~ runner_name) }}"
|
||||
become_user: "{{ gitea_runner_service_user | default('grm-' ~ gitea_runner_name) }}"
|
||||
environment:
|
||||
XDG_RUNTIME_DIR: "/run/user/{{ gitea_runner_uid | default('') }}"
|
||||
when: systemd_available.stat.exists
|
||||
|
||||
@@ -6,13 +6,13 @@
|
||||
tasks:
|
||||
- name: Include systemd availability check
|
||||
ansible.builtin.include_role:
|
||||
name: gitea-runner
|
||||
name: gitea_runner
|
||||
tasks_from: systemd_check.yml
|
||||
|
||||
- name: Enable gitea-runner user service
|
||||
ansible.builtin.command: systemctl --user enable gitea-runner
|
||||
become: true
|
||||
become_user: "{{ gitea_runner_service_user | default('grm-' ~ runner_name) }}"
|
||||
become_user: "{{ gitea_runner_service_user | default('grm-' ~ gitea_runner_name) }}"
|
||||
environment:
|
||||
XDG_RUNTIME_DIR: "/run/user/{{ gitea_runner_uid | default('') }}"
|
||||
when: systemd_available.stat.exists
|
||||
@@ -21,7 +21,7 @@
|
||||
- name: Start gitea-runner user service
|
||||
ansible.builtin.command: systemctl --user start gitea-runner
|
||||
become: true
|
||||
become_user: "{{ gitea_runner_service_user | default('grm-' ~ runner_name) }}"
|
||||
become_user: "{{ gitea_runner_service_user | default('grm-' ~ gitea_runner_name) }}"
|
||||
environment:
|
||||
XDG_RUNTIME_DIR: "/run/user/{{ gitea_runner_uid | default('') }}"
|
||||
when: systemd_available.stat.exists
|
||||
|
||||
@@ -3,4 +3,4 @@
|
||||
hosts: all
|
||||
become: true
|
||||
roles:
|
||||
- role: gitea-runner
|
||||
- role: gitea_runner
|
||||
|
||||
+34
-34
@@ -6,11 +6,11 @@
|
||||
tasks:
|
||||
- name: Include systemd availability check
|
||||
ansible.builtin.include_role:
|
||||
name: gitea-runner
|
||||
name: gitea_runner
|
||||
tasks_from: systemd_check.yml
|
||||
|
||||
- name: Get runner user UID
|
||||
ansible.builtin.command: id -u "{{ gitea_runner_service_user | default('grm-' ~ runner_name) }}"
|
||||
ansible.builtin.command: id -u "{{ gitea_runner_service_user | default('grm-' ~ gitea_runner_name) }}"
|
||||
register: runner_uid_result
|
||||
changed_when: false
|
||||
failed_when: false
|
||||
@@ -23,20 +23,20 @@
|
||||
- name: Stop gitea-runner user service
|
||||
ansible.builtin.command: systemctl --user stop gitea-runner
|
||||
become: true
|
||||
become_user: "{{ gitea_runner_service_user | default('grm-' ~ runner_name) }}"
|
||||
become_user: "{{ gitea_runner_service_user | default('grm-' ~ gitea_runner_name) }}"
|
||||
environment:
|
||||
XDG_RUNTIME_DIR: "/run/user/{{ gitea_runner_uid | default('') }}"
|
||||
when: systemd_available.stat.exists
|
||||
when: gitea_runner_systemd_available.stat.exists
|
||||
changed_when: true
|
||||
failed_when: false
|
||||
|
||||
- name: Disable gitea-runner user service
|
||||
ansible.builtin.command: systemctl --user disable gitea-runner
|
||||
become: true
|
||||
become_user: "{{ gitea_runner_service_user | default('grm-' ~ runner_name) }}"
|
||||
become_user: "{{ gitea_runner_service_user | default('grm-' ~ gitea_runner_name) }}"
|
||||
environment:
|
||||
XDG_RUNTIME_DIR: "/run/user/{{ gitea_runner_uid | default('') }}"
|
||||
when: systemd_available.stat.exists
|
||||
when: gitea_runner_systemd_available.stat.exists
|
||||
changed_when: true
|
||||
failed_when: false
|
||||
|
||||
@@ -47,7 +47,7 @@
|
||||
args:
|
||||
executable: /bin/bash
|
||||
become: true
|
||||
become_user: "{{ gitea_runner_service_user | default('grm-' ~ runner_name) }}"
|
||||
become_user: "{{ gitea_runner_service_user | default('grm-' ~ gitea_runner_name) }}"
|
||||
environment:
|
||||
XDG_RUNTIME_DIR: "/run/user/{{ gitea_runner_uid | default('') }}"
|
||||
DOCKER_HOST: "unix:///run/user/{{ gitea_runner_uid | default('') }}/docker.sock"
|
||||
@@ -57,7 +57,7 @@
|
||||
- name: Prune all Docker images, volumes, and build cache (rootless)
|
||||
ansible.builtin.command: docker system prune -af --volumes
|
||||
become: true
|
||||
become_user: "{{ gitea_runner_service_user | default('grm-' ~ runner_name) }}"
|
||||
become_user: "{{ gitea_runner_service_user | default('grm-' ~ gitea_runner_name) }}"
|
||||
environment:
|
||||
XDG_RUNTIME_DIR: "/run/user/{{ gitea_runner_uid | default('') }}"
|
||||
DOCKER_HOST: "unix:///run/user/{{ gitea_runner_uid | default('') }}/docker.sock"
|
||||
@@ -67,7 +67,7 @@
|
||||
- name: Stop rootless Docker daemon
|
||||
ansible.builtin.command: systemctl --user stop docker
|
||||
become: true
|
||||
become_user: "{{ gitea_runner_service_user | default('grm-' ~ runner_name) }}"
|
||||
become_user: "{{ gitea_runner_service_user | default('grm-' ~ gitea_runner_name) }}"
|
||||
environment:
|
||||
XDG_RUNTIME_DIR: "/run/user/{{ gitea_runner_uid | default('') }}"
|
||||
changed_when: true
|
||||
@@ -75,120 +75,120 @@
|
||||
|
||||
- name: Include deregistration
|
||||
ansible.builtin.include_role:
|
||||
name: gitea-runner
|
||||
name: gitea_runner
|
||||
tasks_from: deregister.yml
|
||||
when: not skip_runner_registration | default(false)
|
||||
when: not gitea_runner_skip_registration | default(false)
|
||||
|
||||
- name: Stop and disable healthcheck timer
|
||||
ansible.builtin.command: systemctl --user stop --disable runner-healthcheck.timer
|
||||
become: true
|
||||
become_user: "{{ gitea_runner_service_user | default('grm-' ~ runner_name) }}"
|
||||
become_user: "{{ gitea_runner_service_user | default('grm-' ~ gitea_runner_name) }}"
|
||||
environment:
|
||||
XDG_RUNTIME_DIR: "/run/user/{{ gitea_runner_uid | default('') }}"
|
||||
when: systemd_available.stat.exists
|
||||
when: gitea_runner_systemd_available.stat.exists
|
||||
changed_when: true
|
||||
failed_when: false
|
||||
|
||||
- name: Remove docker-prune user service file
|
||||
ansible.builtin.file:
|
||||
path: "{{ gitea_runner_home | default('/home/grm-' ~ runner_name) }}/.config/systemd/user/docker-prune.service"
|
||||
path: "{{ gitea_runner_home | default('/home/grm-' ~ gitea_runner_name) }}/.config/systemd/user/docker-prune.service"
|
||||
state: absent
|
||||
failed_when: false
|
||||
|
||||
- name: Remove docker-prune user timer file
|
||||
ansible.builtin.file:
|
||||
path: "{{ gitea_runner_home | default('/home/grm-' ~ runner_name) }}/.config/systemd/user/docker-prune.timer"
|
||||
path: "{{ gitea_runner_home | default('/home/grm-' ~ gitea_runner_name) }}/.config/systemd/user/docker-prune.timer"
|
||||
state: absent
|
||||
failed_when: false
|
||||
|
||||
- name: Remove healthcheck user service file
|
||||
ansible.builtin.file:
|
||||
path: "{{ gitea_runner_home | default('/home/grm-' ~ runner_name) }}/.config/systemd/user/runner-healthcheck.service"
|
||||
path: "{{ gitea_runner_home | default('/home/grm-' ~ gitea_runner_name) }}/.config/systemd/user/runner-healthcheck.service"
|
||||
state: absent
|
||||
failed_when: false
|
||||
|
||||
- name: Remove healthcheck user timer file
|
||||
ansible.builtin.file:
|
||||
path: "{{ gitea_runner_home | default('/home/grm-' ~ runner_name) }}/.config/systemd/user/runner-healthcheck.timer"
|
||||
path: "{{ gitea_runner_home | default('/home/grm-' ~ gitea_runner_name) }}/.config/systemd/user/runner-healthcheck.timer"
|
||||
state: absent
|
||||
failed_when: false
|
||||
|
||||
- name: Remove healthcheck script
|
||||
ansible.builtin.file:
|
||||
path: "{{ gitea_runner_config_dir | default('/etc/gitea-runner/' ~ runner_name) }}/healthcheck.sh"
|
||||
path: "{{ gitea_runner_config_dir | default('/etc/gitea-runner/' ~ gitea_runner_name) }}/healthcheck.sh"
|
||||
state: absent
|
||||
failed_when: false
|
||||
|
||||
- name: Remove systemd user unit file
|
||||
ansible.builtin.file:
|
||||
path: "{{ gitea_runner_home | default('/home/grm-' ~ runner_name) }}/.config/systemd/user/gitea-runner.service"
|
||||
path: "{{ gitea_runner_home | default('/home/grm-' ~ gitea_runner_name) }}/.config/systemd/user/gitea-runner.service"
|
||||
state: absent
|
||||
when: remove_systemd_template | default(true)
|
||||
|
||||
- name: Kill remaining processes of runner user
|
||||
ansible.builtin.command: loginctl terminate-user "{{ gitea_runner_service_user | default('grm-' ~ runner_name) }}"
|
||||
ansible.builtin.command: loginctl terminate-user "{{ gitea_runner_service_user | default('grm-' ~ gitea_runner_name) }}"
|
||||
failed_when: false
|
||||
changed_when: true
|
||||
|
||||
- name: Wait for processes to terminate
|
||||
ansible.builtin.command: "pkill -u {{ gitea_runner_service_user | default('grm-' ~ runner_name) }}"
|
||||
ansible.builtin.command: "pkill -u {{ gitea_runner_service_user | default('grm-' ~ gitea_runner_name) }}"
|
||||
failed_when: false
|
||||
changed_when: false
|
||||
|
||||
- name: Disable lingering for runner user
|
||||
ansible.builtin.command: loginctl disable-linger "{{ gitea_runner_service_user | default('grm-' ~ runner_name) }}"
|
||||
ansible.builtin.command: loginctl disable-linger "{{ gitea_runner_service_user | default('grm-' ~ gitea_runner_name) }}"
|
||||
failed_when: false
|
||||
changed_when: true
|
||||
|
||||
- name: Remove runner user and home directory
|
||||
ansible.builtin.user:
|
||||
name: "{{ gitea_runner_service_user | default('grm-' ~ runner_name) }}"
|
||||
name: "{{ gitea_runner_service_user | default('grm-' ~ gitea_runner_name) }}"
|
||||
state: absent
|
||||
remove: true
|
||||
when: remove_runner_user | default(true)
|
||||
when: gitea_runner_remove_user | default(true)
|
||||
failed_when: false
|
||||
|
||||
- name: Remove Docker data root when user is kept
|
||||
ansible.builtin.file:
|
||||
path: "{{ gitea_runner_home | default('/home/grm-' ~ runner_name) }}/.local/share/docker"
|
||||
path: "{{ gitea_runner_home | default('/home/grm-' ~ gitea_runner_name) }}/.local/share/docker"
|
||||
state: absent
|
||||
when: not (remove_runner_user | default(true))
|
||||
when: not (gitea_runner_remove_user | default(true))
|
||||
failed_when: false
|
||||
|
||||
- name: Remove act cache when user is kept
|
||||
ansible.builtin.file:
|
||||
path: "{{ gitea_runner_home | default('/home/grm-' ~ runner_name) }}/.cache/act"
|
||||
path: "{{ gitea_runner_home | default('/home/grm-' ~ gitea_runner_name) }}/.cache/act"
|
||||
state: absent
|
||||
when: not (remove_runner_user | default(true))
|
||||
when: not (gitea_runner_remove_user | default(true))
|
||||
failed_when: false
|
||||
|
||||
- name: Remove systemd user config dir when user is kept
|
||||
ansible.builtin.file:
|
||||
path: "{{ gitea_runner_home | default('/home/grm-' ~ runner_name) }}/.config/systemd/user"
|
||||
path: "{{ gitea_runner_home | default('/home/grm-' ~ gitea_runner_name) }}/.config/systemd/user"
|
||||
state: absent
|
||||
when: not (remove_runner_user | default(true))
|
||||
when: not (gitea_runner_remove_user | default(true))
|
||||
failed_when: false
|
||||
|
||||
- name: Remove subuid entry for runner user
|
||||
ansible.builtin.lineinfile:
|
||||
path: /etc/subuid
|
||||
regexp: "^{{ gitea_runner_service_user | default('grm-' ~ runner_name) }}:"
|
||||
regexp: "^{{ gitea_runner_service_user | default('grm-' ~ gitea_runner_name) }}:"
|
||||
state: absent
|
||||
failed_when: false
|
||||
|
||||
- name: Remove subgid entry for runner user
|
||||
ansible.builtin.lineinfile:
|
||||
path: /etc/subgid
|
||||
regexp: "^{{ gitea_runner_service_user | default('grm-' ~ runner_name) }}:"
|
||||
regexp: "^{{ gitea_runner_service_user | default('grm-' ~ gitea_runner_name) }}:"
|
||||
state: absent
|
||||
failed_when: false
|
||||
|
||||
- name: Remove runner data directory
|
||||
ansible.builtin.file:
|
||||
path: "{{ gitea_runner_data_dir | default('/var/lib/gitea-runner/' ~ runner_name) }}"
|
||||
path: "{{ gitea_runner_data_dir | default('/var/lib/gitea-runner/' ~ gitea_runner_name) }}"
|
||||
state: absent
|
||||
|
||||
- name: Remove runner config directory
|
||||
ansible.builtin.file:
|
||||
path: "{{ gitea_runner_config_dir | default('/etc/gitea-runner/' ~ runner_name) }}"
|
||||
path: "{{ gitea_runner_config_dir | default('/etc/gitea-runner/' ~ gitea_runner_name) }}"
|
||||
state: absent
|
||||
|
||||
@@ -2,6 +2,6 @@ collections:
|
||||
- name: community.general
|
||||
version: "==13.1.0"
|
||||
- name: ansible.posix
|
||||
version: "==2.2.0"
|
||||
version: "==2.2.1"
|
||||
- name: community.docker
|
||||
version: "==5.2.1"
|
||||
|
||||
@@ -7,12 +7,12 @@
|
||||
tasks:
|
||||
- name: Include systemd availability check
|
||||
ansible.builtin.include_role:
|
||||
name: gitea-runner
|
||||
name: gitea_runner
|
||||
tasks_from: systemd_check.yml
|
||||
|
||||
- name: Resolve runner UID
|
||||
ansible.builtin.include_role:
|
||||
name: gitea-runner
|
||||
name: gitea_runner
|
||||
tasks_from: resolve_uid.yml
|
||||
|
||||
- name: Stop gitea-runner user service
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
---
|
||||
- name: Converge
|
||||
hosts: all
|
||||
become: true
|
||||
vars:
|
||||
gitea_url: "http://localhost:3000"
|
||||
registration_token: "fake-token-for-testing"
|
||||
runner_name: "molecule-test-runner"
|
||||
skip_runner_registration: true
|
||||
docker_rootless_setup: false
|
||||
roles:
|
||||
- role: gitea-runner
|
||||
@@ -1,12 +0,0 @@
|
||||
---
|
||||
- name: Converge
|
||||
hosts: all
|
||||
become: true
|
||||
vars:
|
||||
gitea_url: "http://localhost:3000"
|
||||
registration_token: "fake-token-for-testing"
|
||||
runner_name: "deregister-test-runner"
|
||||
skip_runner_registration: true
|
||||
docker_rootless_setup: false
|
||||
roles:
|
||||
- role: gitea-runner
|
||||
@@ -1,12 +0,0 @@
|
||||
---
|
||||
- name: Converge
|
||||
hosts: all
|
||||
become: true
|
||||
vars:
|
||||
gitea_url: "http://localhost:3000"
|
||||
registration_token: "fake-token-for-testing"
|
||||
runner_name: "lifecycle-test-runner"
|
||||
skip_runner_registration: true
|
||||
docker_rootless_setup: false
|
||||
roles:
|
||||
- role: gitea-runner
|
||||
@@ -1,12 +0,0 @@
|
||||
---
|
||||
- name: Converge
|
||||
hosts: all
|
||||
become: true
|
||||
vars:
|
||||
gitea_url: "http://localhost:3000"
|
||||
registration_token: "fake-token-for-testing"
|
||||
runner_name: "remove-test-runner"
|
||||
skip_runner_registration: true
|
||||
docker_rootless_setup: false
|
||||
roles:
|
||||
- role: gitea-runner
|
||||
@@ -1,12 +0,0 @@
|
||||
---
|
||||
- name: Converge
|
||||
hosts: all
|
||||
become: true
|
||||
vars:
|
||||
gitea_url: "http://localhost:3000"
|
||||
registration_token: "fake-token-for-testing"
|
||||
runner_name: "template-test-runner"
|
||||
skip_runner_registration: true
|
||||
docker_rootless_setup: false
|
||||
roles:
|
||||
- role: gitea-runner
|
||||
@@ -1,12 +0,0 @@
|
||||
---
|
||||
- name: Converge
|
||||
hosts: all
|
||||
become: true
|
||||
vars:
|
||||
gitea_url: "http://localhost:3000"
|
||||
registration_token: "fake-token-for-testing"
|
||||
runner_name: "update-test-runner"
|
||||
skip_runner_registration: true
|
||||
docker_rootless_setup: false
|
||||
roles:
|
||||
- role: gitea-runner
|
||||
@@ -1,58 +0,0 @@
|
||||
---
|
||||
- name: Check if runner registration file exists
|
||||
ansible.builtin.stat:
|
||||
path: "{{ gitea_runner_data_dir }}/.runner"
|
||||
register: runner_file_stat
|
||||
|
||||
- name: Read runner registration file
|
||||
ansible.builtin.slurp:
|
||||
src: "{{ gitea_runner_data_dir }}/.runner"
|
||||
register: runner_file_content
|
||||
when: runner_file_stat.stat.exists | default(false) | bool
|
||||
|
||||
- name: Parse runner registration data
|
||||
ansible.builtin.set_fact:
|
||||
runner_reg: >
|
||||
{{ (runner_file_content.content | b64decode | from_json)
|
||||
if (runner_file_content is defined and runner_file_content.content is defined)
|
||||
else {} }}
|
||||
when: runner_file_stat.stat.exists | default(false) | bool
|
||||
|
||||
- name: Deregister runner from Gitea via API
|
||||
ansible.builtin.command: >
|
||||
curl -sf --connect-timeout 5 --max-time 10 -X DELETE
|
||||
-H "Authorization: token {{ gitea_admin_token | default(registration_token) }}"
|
||||
"{{ gitea_url }}/api/v1/admin/actions/runners/{{ runner_reg.id }}"
|
||||
args:
|
||||
chdir: "{{ gitea_runner_data_dir }}"
|
||||
become: true
|
||||
become_user: "{{ gitea_runner_service_user }}"
|
||||
environment:
|
||||
XDG_RUNTIME_DIR: "/run/user/{{ gitea_runner_uid | default(0) }}"
|
||||
DOCKER_HOST: "unix:///run/user/{{ gitea_runner_uid | default(0) }}/docker.sock"
|
||||
when:
|
||||
- runner_file_stat.stat.exists | default(false) | bool
|
||||
- not skip_runner_registration
|
||||
- runner_reg.id is defined
|
||||
register: deregister_output
|
||||
changed_when: deregister_output.rc == 0
|
||||
failed_when: false
|
||||
|
||||
- name: Warn if deregistration failed
|
||||
ansible.builtin.debug:
|
||||
msg: >-
|
||||
WARNING: Runner deregistration from Gitea failed (rc={{ deregister_output.rc | default('N/A') }}).
|
||||
The runner entry may remain in Gitea's admin UI as offline.
|
||||
Use an admin token (gitea_admin_token var) to enable automatic cleanup,
|
||||
or remove it manually from {{ gitea_url }}/-/admin/actions/runners
|
||||
when:
|
||||
- runner_file_stat.stat.exists | default(false) | bool
|
||||
- not skip_runner_registration
|
||||
- deregister_output is defined
|
||||
- deregister_output.rc | default(1) != 0
|
||||
|
||||
- name: Remove runner registration file
|
||||
ansible.builtin.file:
|
||||
path: "{{ gitea_runner_data_dir }}/.runner"
|
||||
state: absent
|
||||
when: runner_file_stat.stat.exists | default(false) | bool
|
||||
@@ -1,102 +0,0 @@
|
||||
---
|
||||
- name: Check runner registration file exists
|
||||
ansible.builtin.stat:
|
||||
path: "{{ gitea_runner_data_dir }}/.runner"
|
||||
register: runner_file_stat
|
||||
|
||||
- name: Read runner registration file
|
||||
ansible.builtin.slurp:
|
||||
src: "{{ gitea_runner_data_dir }}/.runner"
|
||||
register: runner_file_content
|
||||
when: runner_file_stat.stat.exists | default(false) | bool
|
||||
|
||||
- name: Parse runner registration data
|
||||
ansible.builtin.set_fact:
|
||||
runner_reg: >
|
||||
{{ (runner_file_content.content | b64decode | from_json)
|
||||
if (runner_file_content is defined and runner_file_content.content is defined)
|
||||
else {} }}
|
||||
when: runner_file_stat.stat.exists | default(false) | bool
|
||||
|
||||
- name: Verify runner user service active
|
||||
ansible.builtin.command: systemctl --user is-active gitea-runner
|
||||
become: true
|
||||
become_user: "{{ gitea_runner_service_user }}"
|
||||
environment:
|
||||
XDG_RUNTIME_DIR: "/run/user/{{ gitea_runner_uid }}"
|
||||
register: service_check
|
||||
changed_when: false
|
||||
when:
|
||||
- systemd_available.stat.exists
|
||||
- docker_rootless_setup
|
||||
|
||||
- name: Validate runner installation
|
||||
ansible.builtin.fail:
|
||||
msg: >
|
||||
Runner '{{ runner_name }}' is not properly installed:
|
||||
{% if not (runner_file_stat.stat.exists | default(false)) %}
|
||||
- Registration file (.runner) is missing. Registration may have failed.
|
||||
{% endif %}
|
||||
{% if docker_rootless_setup and not (service_check.stdout | default('') | trim) == 'active' %}
|
||||
- Systemd user service is not active.
|
||||
{% endif %}
|
||||
when: >
|
||||
not (runner_file_stat.stat.exists | default(false))
|
||||
or (docker_rootless_setup and not (service_check.stdout | default('') | trim) == 'active')
|
||||
|
||||
- name: Report runner status
|
||||
ansible.builtin.debug:
|
||||
msg: >
|
||||
Runner '{{ runner_name }}' is installed and running.
|
||||
Registered: {{ runner_file_stat.stat.exists | default(false) }}
|
||||
{% if runner_reg.id is defined %}Runner ID: {{ runner_reg.id }}{% endif %}
|
||||
{% if runner_reg.uuid is defined %}UUID: {{ runner_reg.uuid }}{% endif %}
|
||||
{% if runner_reg.address is defined %}Gitea: {{ runner_reg.address }}{% endif %}
|
||||
Service: {{ service_check.stdout | default('unknown') | trim }}
|
||||
|
||||
- name: Optional Gitea API verification
|
||||
when:
|
||||
- gitea_url is defined
|
||||
- gitea_admin_token is defined
|
||||
- gitea_admin_token | length > 0
|
||||
block:
|
||||
- name: Check admin runners API
|
||||
ansible.builtin.uri:
|
||||
url: "{{ gitea_url }}/api/v1/admin/runners"
|
||||
headers:
|
||||
Authorization: "token {{ gitea_admin_token }}"
|
||||
method: GET
|
||||
status_code: [200, 401, 403, 404]
|
||||
return_content: true
|
||||
body_format: json
|
||||
register: admin_api_response
|
||||
ignore_errors: true
|
||||
|
||||
- name: Check repo runners API
|
||||
ansible.builtin.uri:
|
||||
url: "{{ gitea_url }}/api/v1/repos/{{ gitea_runner_test_repo | default('oblachno-oss/grm') }}/actions/runners"
|
||||
headers:
|
||||
Authorization: "token {{ gitea_admin_token }}"
|
||||
method: GET
|
||||
status_code: [200, 401, 403, 404]
|
||||
return_content: true
|
||||
body_format: json
|
||||
register: repo_api_response
|
||||
ignore_errors: true
|
||||
|
||||
- name: Report API status (informational only)
|
||||
ansible.builtin.debug:
|
||||
msg: >
|
||||
API checks (informational only — not used for pass/fail):
|
||||
Admin API: {{ admin_api_response.status | default('no response') }}.
|
||||
Repo API: {{ repo_api_response.status | default('no response') }}.
|
||||
{% if admin_api_response.json.runners | default([]) | selectattr('name', 'equalto', runner_name) | list | length > 0 %}
|
||||
Runner found in admin API.
|
||||
{% endif %}
|
||||
{% if repo_api_response.json.runners | default([]) | selectattr('name', 'equalto', runner_name) | list | length > 0 %}
|
||||
Runner found in repo API.
|
||||
{% endif %}
|
||||
rescue:
|
||||
- name: API check failed
|
||||
ansible.builtin.debug:
|
||||
msg: "API verification skipped due to connection or permission error."
|
||||
+17
-17
@@ -1,19 +1,19 @@
|
||||
---
|
||||
gitea_runner_version: "1.0.8"
|
||||
runner_labels: "docker,ubuntu-latest:docker://runner-images:ubuntu-26.04"
|
||||
skip_runner_registration: false
|
||||
gitea_runner_version: "2.0.1"
|
||||
gitea_runner_labels: "docker,ubuntu-latest:docker://runner-images:ubuntu-26.04"
|
||||
gitea_runner_skip_registration: false
|
||||
|
||||
# Per-runner user (rootless isolation)
|
||||
gitea_runner_user_prefix: "grm-"
|
||||
gitea_runner_base_home: "/home"
|
||||
gitea_runner_service_user: "{{ gitea_runner_user_prefix }}{{ runner_name }}"
|
||||
gitea_runner_service_user: "{{ gitea_runner_user_prefix }}{{ gitea_runner_name }}"
|
||||
gitea_runner_home: "{{ gitea_runner_base_home }}/{{ gitea_runner_service_user }}"
|
||||
|
||||
# Base paths (instance-scoped via runner_name)
|
||||
# Base paths (instance-scoped via gitea_runner_name)
|
||||
gitea_runner_base_data_dir: "/var/lib/gitea-runner"
|
||||
gitea_runner_base_config_dir: "/etc/gitea-runner"
|
||||
gitea_runner_data_dir: "{{ gitea_runner_base_data_dir }}/{{ runner_name }}"
|
||||
gitea_runner_config_dir: "{{ gitea_runner_base_config_dir }}/{{ runner_name }}"
|
||||
gitea_runner_data_dir: "{{ gitea_runner_base_data_dir }}/{{ gitea_runner_name }}"
|
||||
gitea_runner_config_dir: "{{ gitea_runner_base_config_dir }}/{{ gitea_runner_name }}"
|
||||
gitea_runner_binary_path: "/usr/local/bin/gitea_runner"
|
||||
|
||||
# Prune configuration
|
||||
@@ -33,11 +33,11 @@ gitea_runner_healthcheck_script_path: "{{ gitea_runner_config_dir }}/healthcheck
|
||||
# Admin token for runner deregistration via Gitea API.
|
||||
# If not set, falls back to registration_token (which likely lacks admin scope).
|
||||
# Set this to a token with admin scope to enable automatic runner cleanup on removal.
|
||||
gitea_admin_token: ""
|
||||
gitea_runner_admin_token: ""
|
||||
|
||||
# Removal defaults
|
||||
remove_systemd_template: true
|
||||
remove_runner_user: true
|
||||
gitea_runner_remove_systemd_template: true
|
||||
gitea_runner_remove_user: true
|
||||
|
||||
# Runner configuration
|
||||
gitea_runner_log_level: "info"
|
||||
@@ -45,18 +45,18 @@ gitea_runner_container_label: "gitea-runner=true"
|
||||
gitea_runner_file: ".runner"
|
||||
|
||||
# Docker installation (for rootless dependencies)
|
||||
docker_gpg_key_path: "/etc/apt/keyrings/docker.gpg"
|
||||
docker_apt_arch: "{{ 'amd64' if ansible_facts['architecture'] == 'x86_64' else ansible_facts['architecture'] }}"
|
||||
docker_apt_source_line: >-
|
||||
deb [arch={{ docker_apt_arch }} signed-by={{ docker_gpg_key_path }}]
|
||||
gitea_runner_docker_gpg_key_path: "/etc/apt/keyrings/docker.gpg"
|
||||
gitea_runner_docker_apt_arch: "{{ 'amd64' if ansible_facts['architecture'] == 'x86_64' else ansible_facts['architecture'] }}"
|
||||
gitea_runner_docker_apt_source_line: >-
|
||||
deb [arch={{ gitea_runner_docker_apt_arch }} signed-by={{ gitea_runner_docker_gpg_key_path }}]
|
||||
https://download.docker.com/linux/{{ ansible_facts['distribution'] | lower }}
|
||||
{{ ansible_facts['distribution_release'] }} stable
|
||||
# Set to false in CI/molecule to skip rootless daemon startup (needs kernel userns)
|
||||
docker_rootless_setup: true
|
||||
gitea_runner_docker_rootless_setup: true
|
||||
|
||||
# Rootless Docker network driver: "pasta" (IPv6 support) or "slirp4netns" (IPv4 only)
|
||||
# pasta has proper outgoing IPv6 support; slirp4netns does not (known limitation).
|
||||
docker_rootless_net_driver: "pasta"
|
||||
gitea_runner_docker_rootless_net_driver: "pasta"
|
||||
|
||||
# IPv6 subnet for rootless Docker containers (ULA range, not routable on internet)
|
||||
docker_ipv6_cidr: "fd00:dead:beef::/48"
|
||||
gitea_runner_docker_ipv6_cidr: "fd00:dead:beef::/48"
|
||||
+1
-1
@@ -9,4 +9,4 @@
|
||||
when:
|
||||
- ansible_facts is defined
|
||||
- ansible_facts['service_mgr'] | default('') == 'systemd'
|
||||
- docker_rootless_setup
|
||||
- gitea_runner_docker_rootless_setup
|
||||
@@ -0,0 +1,12 @@
|
||||
---
|
||||
- name: Converge
|
||||
hosts: all
|
||||
become: true
|
||||
vars:
|
||||
gitea_url: "http://localhost:3000"
|
||||
registration_token: "fake-token-for-testing"
|
||||
gitea_runner_name: "molecule-test-runner"
|
||||
gitea_runner_skip_registration: true
|
||||
gitea_runner_docker_rootless_setup: false
|
||||
roles:
|
||||
- role: gitea_runner
|
||||
+1
-1
@@ -3,7 +3,7 @@
|
||||
hosts: all
|
||||
become: true
|
||||
vars:
|
||||
runner_name: "molecule-test-runner"
|
||||
gitea_runner_name: "molecule-test-runner"
|
||||
pre_tasks:
|
||||
- name: Load role defaults
|
||||
ansible.builtin.include_vars:
|
||||
@@ -0,0 +1,12 @@
|
||||
---
|
||||
- name: Converge
|
||||
hosts: all
|
||||
become: true
|
||||
vars:
|
||||
gitea_url: "http://localhost:3000"
|
||||
registration_token: "fake-token-for-testing"
|
||||
gitea_runner_name: "deregister-test-runner"
|
||||
gitea_runner_skip_registration: true
|
||||
gitea_runner_docker_rootless_setup: false
|
||||
roles:
|
||||
- role: gitea_runner
|
||||
+5
-5
@@ -3,7 +3,7 @@
|
||||
hosts: all
|
||||
become: true
|
||||
vars:
|
||||
runner_name: "deregister-test-runner"
|
||||
gitea_runner_name: "deregister-test-runner"
|
||||
pre_tasks:
|
||||
- name: Load role defaults
|
||||
ansible.builtin.include_vars:
|
||||
@@ -13,7 +13,7 @@
|
||||
ansible.builtin.copy:
|
||||
dest: "{{ gitea_runner_data_dir }}/.runner"
|
||||
content: |
|
||||
{"id": 1, "uuid": "test-uuid-1234", "name": "{{ runner_name }}", "address": "http://localhost:3000"}
|
||||
{"id": 1, "uuid": "test-uuid-1234", "name": "{{ gitea_runner_name }}", "address": "http://localhost:3000"}
|
||||
owner: "{{ gitea_runner_service_user }}"
|
||||
group: "{{ gitea_runner_service_user }}"
|
||||
mode: "0644"
|
||||
@@ -22,12 +22,12 @@
|
||||
hosts: all
|
||||
become: true
|
||||
vars:
|
||||
runner_name: "deregister-test-runner"
|
||||
gitea_runner_name: "deregister-test-runner"
|
||||
registration_token: "fake-token-for-testing"
|
||||
gitea_url: "http://localhost:3000"
|
||||
skip_runner_registration: false
|
||||
gitea_runner_skip_registration: false
|
||||
tasks:
|
||||
- name: Include deregistration tasks
|
||||
ansible.builtin.include_role:
|
||||
name: gitea-runner
|
||||
name: gitea_runner
|
||||
tasks_from: deregister.yml
|
||||
+3
-3
@@ -3,7 +3,7 @@
|
||||
hosts: all
|
||||
become: true
|
||||
vars:
|
||||
runner_name: "deregister-test-runner"
|
||||
gitea_runner_name: "deregister-test-runner"
|
||||
pre_tasks:
|
||||
- name: Load role defaults
|
||||
ansible.builtin.include_vars:
|
||||
@@ -12,12 +12,12 @@
|
||||
- name: Check registration file was removed
|
||||
ansible.builtin.stat:
|
||||
path: "{{ gitea_runner_data_dir }}/.runner"
|
||||
register: runner_file_stat
|
||||
register: gitea_runner_file_stat
|
||||
|
||||
- name: Assert registration file no longer exists
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- not runner_file_stat.stat.exists
|
||||
- not gitea_runner_file_stat.stat.exists
|
||||
fail_msg: "Registration file (.runner) was not removed by deregistration"
|
||||
|
||||
- name: Check systemd user service still exists
|
||||
@@ -0,0 +1,12 @@
|
||||
---
|
||||
- name: Converge
|
||||
hosts: all
|
||||
become: true
|
||||
vars:
|
||||
gitea_url: "http://localhost:3000"
|
||||
registration_token: "fake-token-for-testing"
|
||||
gitea_runner_name: "lifecycle-test-runner"
|
||||
gitea_runner_skip_registration: true
|
||||
gitea_runner_docker_rootless_setup: false
|
||||
roles:
|
||||
- role: gitea_runner
|
||||
+2
-2
@@ -3,7 +3,7 @@
|
||||
hosts: all
|
||||
become: true
|
||||
vars:
|
||||
runner_name: "lifecycle-test-runner"
|
||||
gitea_runner_name: "lifecycle-test-runner"
|
||||
pre_tasks:
|
||||
- name: Load role defaults
|
||||
ansible.builtin.include_vars:
|
||||
@@ -23,7 +23,7 @@
|
||||
hosts: all
|
||||
become: true
|
||||
vars:
|
||||
runner_name: "lifecycle-test-runner"
|
||||
gitea_runner_name: "lifecycle-test-runner"
|
||||
pre_tasks:
|
||||
- name: Load role defaults
|
||||
ansible.builtin.include_vars:
|
||||
+1
-1
@@ -3,7 +3,7 @@
|
||||
hosts: all
|
||||
become: true
|
||||
vars:
|
||||
runner_name: "lifecycle-test-runner"
|
||||
gitea_runner_name: "lifecycle-test-runner"
|
||||
pre_tasks:
|
||||
- name: Load role defaults
|
||||
ansible.builtin.include_vars:
|
||||
+8
-8
@@ -5,11 +5,11 @@
|
||||
vars:
|
||||
gitea_url: "http://localhost:3000"
|
||||
registration_token: "fake-token-for-testing"
|
||||
runner_name: "molecule-runner-a"
|
||||
skip_runner_registration: true
|
||||
docker_rootless_setup: false
|
||||
gitea_runner_name: "molecule-runner-a"
|
||||
gitea_runner_skip_registration: true
|
||||
gitea_runner_docker_rootless_setup: false
|
||||
roles:
|
||||
- role: gitea-runner
|
||||
- role: gitea_runner
|
||||
|
||||
- name: Converge second runner instance
|
||||
hosts: all
|
||||
@@ -17,8 +17,8 @@
|
||||
vars:
|
||||
gitea_url: "http://localhost:3000"
|
||||
registration_token: "fake-token-for-testing"
|
||||
runner_name: "molecule-runner-b"
|
||||
skip_runner_registration: true
|
||||
docker_rootless_setup: false
|
||||
gitea_runner_name: "molecule-runner-b"
|
||||
gitea_runner_skip_registration: true
|
||||
gitea_runner_docker_rootless_setup: false
|
||||
roles:
|
||||
- role: gitea-runner
|
||||
- role: gitea_runner
|
||||
@@ -0,0 +1,12 @@
|
||||
---
|
||||
- name: Converge
|
||||
hosts: all
|
||||
become: true
|
||||
vars:
|
||||
gitea_url: "http://localhost:3000"
|
||||
registration_token: "fake-token-for-testing"
|
||||
gitea_runner_name: "remove-test-runner"
|
||||
gitea_runner_skip_registration: true
|
||||
gitea_runner_docker_rootless_setup: false
|
||||
roles:
|
||||
- role: gitea_runner
|
||||
+2
-2
@@ -2,7 +2,7 @@
|
||||
- name: Remove runner via remove-runner playbook
|
||||
ansible.builtin.import_playbook: "../../../../remove-runner.yml"
|
||||
vars:
|
||||
runner_name: "remove-test-runner"
|
||||
gitea_runner_name: "remove-test-runner"
|
||||
registration_token: "fake-token-for-testing"
|
||||
gitea_url: "http://localhost:3000"
|
||||
skip_runner_registration: true
|
||||
gitea_runner_skip_registration: true
|
||||
+1
-1
@@ -3,7 +3,7 @@
|
||||
hosts: all
|
||||
become: true
|
||||
vars:
|
||||
runner_name: "remove-test-runner"
|
||||
gitea_runner_name: "remove-test-runner"
|
||||
pre_tasks:
|
||||
- name: Load role defaults
|
||||
ansible.builtin.include_vars:
|
||||
@@ -0,0 +1,12 @@
|
||||
---
|
||||
- name: Converge
|
||||
hosts: all
|
||||
become: true
|
||||
vars:
|
||||
gitea_url: "http://localhost:3000"
|
||||
registration_token: "fake-token-for-testing"
|
||||
gitea_runner_name: "template-test-runner"
|
||||
gitea_runner_skip_registration: true
|
||||
gitea_runner_docker_rootless_setup: false
|
||||
roles:
|
||||
- role: gitea_runner
|
||||
+1
-1
@@ -3,7 +3,7 @@
|
||||
hosts: all
|
||||
become: true
|
||||
vars:
|
||||
runner_name: "template-test-runner"
|
||||
gitea_runner_name: "template-test-runner"
|
||||
pre_tasks:
|
||||
- name: Load role defaults
|
||||
ansible.builtin.include_vars:
|
||||
@@ -0,0 +1,12 @@
|
||||
---
|
||||
- name: Converge
|
||||
hosts: all
|
||||
become: true
|
||||
vars:
|
||||
gitea_url: "http://localhost:3000"
|
||||
registration_token: "fake-token-for-testing"
|
||||
gitea_runner_name: "update-test-runner"
|
||||
gitea_runner_skip_registration: true
|
||||
gitea_runner_docker_rootless_setup: false
|
||||
roles:
|
||||
- role: gitea_runner
|
||||
+2
-2
@@ -3,9 +3,9 @@
|
||||
hosts: all
|
||||
become: true
|
||||
vars:
|
||||
runner_name: "update-test-runner"
|
||||
gitea_runner_name: "update-test-runner"
|
||||
tasks:
|
||||
- name: Include update tasks
|
||||
ansible.builtin.include_role:
|
||||
name: gitea-runner
|
||||
name: gitea_runner
|
||||
tasks_from: update_runner.yml
|
||||
+1
-1
@@ -3,7 +3,7 @@
|
||||
hosts: all
|
||||
become: true
|
||||
vars:
|
||||
runner_name: "update-test-runner"
|
||||
gitea_runner_name: "update-test-runner"
|
||||
pre_tasks:
|
||||
- name: Load role defaults
|
||||
ansible.builtin.include_vars:
|
||||
@@ -0,0 +1,58 @@
|
||||
---
|
||||
- name: Check if runner registration file exists
|
||||
ansible.builtin.stat:
|
||||
path: "{{ gitea_runner_data_dir }}/.runner"
|
||||
register: gitea_runner_file_stat
|
||||
|
||||
- name: Read runner registration file
|
||||
ansible.builtin.slurp:
|
||||
src: "{{ gitea_runner_data_dir }}/.runner"
|
||||
register: gitea_runner_file_content
|
||||
when: gitea_runner_file_stat.stat.exists | default(false) | bool
|
||||
|
||||
- name: Parse runner registration data
|
||||
ansible.builtin.set_fact:
|
||||
gitea_runner_reg: >
|
||||
{{ (gitea_runner_file_content.content | b64decode | from_json)
|
||||
if (gitea_runner_file_content is defined and gitea_runner_file_content.content is defined)
|
||||
else {} }}
|
||||
when: gitea_runner_file_stat.stat.exists | default(false) | bool
|
||||
|
||||
- name: Deregister runner from Gitea via API
|
||||
ansible.builtin.command: >
|
||||
curl -sf --connect-timeout 5 --max-time 10 -X DELETE
|
||||
-H "Authorization: token {{ gitea_runner_admin_token | default(registration_token) }}"
|
||||
"{{ gitea_url }}/api/v1/admin/actions/runners/{{ gitea_runner_reg.id }}"
|
||||
args:
|
||||
chdir: "{{ gitea_runner_data_dir }}"
|
||||
become: true
|
||||
become_user: "{{ gitea_runner_service_user }}"
|
||||
environment:
|
||||
XDG_RUNTIME_DIR: "/run/user/{{ gitea_runner_uid | default(0) }}"
|
||||
DOCKER_HOST: "unix:///run/user/{{ gitea_runner_uid | default(0) }}/docker.sock"
|
||||
when:
|
||||
- gitea_runner_file_stat.stat.exists | default(false) | bool
|
||||
- not gitea_runner_skip_registration
|
||||
- gitea_runner_reg.id is defined
|
||||
register: gitea_runner_deregister_output
|
||||
changed_when: gitea_runner_deregister_output.rc == 0
|
||||
failed_when: false
|
||||
|
||||
- name: Warn if deregistration failed
|
||||
ansible.builtin.debug:
|
||||
msg: >-
|
||||
WARNING: Runner deregistration from Gitea failed (rc={{ gitea_runner_deregister_output.rc | default('N/A') }}).
|
||||
The runner entry may remain in Gitea's admin UI as offline.
|
||||
Use an admin token (gitea_runner_admin_token var) to enable automatic cleanup,
|
||||
or remove it manually from {{ gitea_url }}/-/admin/actions/runners
|
||||
when:
|
||||
- gitea_runner_file_stat.stat.exists | default(false) | bool
|
||||
- not gitea_runner_skip_registration
|
||||
- gitea_runner_deregister_output is defined
|
||||
- gitea_runner_deregister_output.rc | default(1) != 0
|
||||
|
||||
- name: Remove runner registration file
|
||||
ansible.builtin.file:
|
||||
path: "{{ gitea_runner_data_dir }}/.runner"
|
||||
state: absent
|
||||
when: gitea_runner_file_stat.stat.exists | default(false) | bool
|
||||
+4
-4
@@ -31,8 +31,8 @@
|
||||
XDG_RUNTIME_DIR: "/run/user/{{ gitea_runner_uid }}"
|
||||
changed_when: true
|
||||
when:
|
||||
- systemd_available.stat.exists
|
||||
- docker_rootless_setup
|
||||
- gitea_runner_systemd_available.stat.exists
|
||||
- gitea_runner_docker_rootless_setup
|
||||
|
||||
- name: Enable and start healthcheck user timer
|
||||
ansible.builtin.command: systemctl --user enable --now runner-healthcheck.timer
|
||||
@@ -42,5 +42,5 @@
|
||||
XDG_RUNTIME_DIR: "/run/user/{{ gitea_runner_uid }}"
|
||||
changed_when: true
|
||||
when:
|
||||
- systemd_available.stat.exists
|
||||
- docker_rootless_setup
|
||||
- gitea_runner_systemd_available.stat.exists
|
||||
- gitea_runner_docker_rootless_setup
|
||||
+1
-1
@@ -15,7 +15,7 @@
|
||||
|
||||
- name: Include registration
|
||||
ansible.builtin.include_tasks: register.yml
|
||||
when: not skip_runner_registration
|
||||
when: not gitea_runner_skip_registration
|
||||
|
||||
- name: Include service setup
|
||||
ansible.builtin.include_tasks: service.yml
|
||||
@@ -0,0 +1,102 @@
|
||||
---
|
||||
- name: Check runner registration file exists
|
||||
ansible.builtin.stat:
|
||||
path: "{{ gitea_runner_data_dir }}/.runner"
|
||||
register: gitea_runner_file_stat
|
||||
|
||||
- name: Read runner registration file
|
||||
ansible.builtin.slurp:
|
||||
src: "{{ gitea_runner_data_dir }}/.runner"
|
||||
register: gitea_runner_file_content
|
||||
when: gitea_runner_file_stat.stat.exists | default(false) | bool
|
||||
|
||||
- name: Parse runner registration data
|
||||
ansible.builtin.set_fact:
|
||||
gitea_runner_reg: >
|
||||
{{ (gitea_runner_file_content.content | b64decode | from_json)
|
||||
if (gitea_runner_file_content is defined and gitea_runner_file_content.content is defined)
|
||||
else {} }}
|
||||
when: gitea_runner_file_stat.stat.exists | default(false) | bool
|
||||
|
||||
- name: Verify runner user service active
|
||||
ansible.builtin.command: systemctl --user is-active gitea-runner
|
||||
become: true
|
||||
become_user: "{{ gitea_runner_service_user }}"
|
||||
environment:
|
||||
XDG_RUNTIME_DIR: "/run/user/{{ gitea_runner_uid }}"
|
||||
register: gitea_runner_service_check
|
||||
changed_when: false
|
||||
when:
|
||||
- gitea_runner_systemd_available.stat.exists
|
||||
- gitea_runner_docker_rootless_setup
|
||||
|
||||
- name: Validate runner installation
|
||||
ansible.builtin.fail:
|
||||
msg: >
|
||||
Runner '{{ gitea_runner_name }}' is not properly installed:
|
||||
{% if not (gitea_runner_file_stat.stat.exists | default(false)) %}
|
||||
- Registration file (.runner) is missing. Registration may have failed.
|
||||
{% endif %}
|
||||
{% if gitea_runner_docker_rootless_setup and not (gitea_runner_service_check.stdout | default('') | trim) == 'active' %}
|
||||
- Systemd user service is not active.
|
||||
{% endif %}
|
||||
when: >
|
||||
not (gitea_runner_file_stat.stat.exists | default(false))
|
||||
or (gitea_runner_docker_rootless_setup and not (gitea_runner_service_check.stdout | default('') | trim) == 'active')
|
||||
|
||||
- name: Report runner status
|
||||
ansible.builtin.debug:
|
||||
msg: >
|
||||
Runner '{{ gitea_runner_name }}' is installed and running.
|
||||
Registered: {{ gitea_runner_file_stat.stat.exists | default(false) }}
|
||||
{% if gitea_runner_reg.id is defined %}Runner ID: {{ gitea_runner_reg.id }}{% endif %}
|
||||
{% if gitea_runner_reg.uuid is defined %}UUID: {{ gitea_runner_reg.uuid }}{% endif %}
|
||||
{% if gitea_runner_reg.address is defined %}Gitea: {{ gitea_runner_reg.address }}{% endif %}
|
||||
Service: {{ gitea_runner_service_check.stdout | default('unknown') | trim }}
|
||||
|
||||
- name: Optional Gitea API verification
|
||||
when:
|
||||
- gitea_url is defined
|
||||
- gitea_runner_admin_token is defined
|
||||
- gitea_runner_admin_token | length > 0
|
||||
block:
|
||||
- name: Check admin runners API
|
||||
ansible.builtin.uri:
|
||||
url: "{{ gitea_url }}/api/v1/admin/runners"
|
||||
headers:
|
||||
Authorization: "token {{ gitea_runner_admin_token }}"
|
||||
method: GET
|
||||
status_code: [200, 401, 403, 404]
|
||||
return_content: true
|
||||
body_format: json
|
||||
register: gitea_runner_admin_api_response
|
||||
ignore_errors: true
|
||||
|
||||
- name: Check repo runners API
|
||||
ansible.builtin.uri:
|
||||
url: "{{ gitea_url }}/api/v1/repos/{{ gitea_runner_test_repo | default('oblachno-oss/grm') }}/actions/runners"
|
||||
headers:
|
||||
Authorization: "token {{ gitea_runner_admin_token }}"
|
||||
method: GET
|
||||
status_code: [200, 401, 403, 404]
|
||||
return_content: true
|
||||
body_format: json
|
||||
register: gitea_runner_repo_api_response
|
||||
ignore_errors: true
|
||||
|
||||
- name: Report API status (informational only)
|
||||
ansible.builtin.debug:
|
||||
msg: >
|
||||
API checks (informational only — not used for pass/fail):
|
||||
Admin API: {{ gitea_runner_admin_api_response.status | default('no response') }}.
|
||||
Repo API: {{ gitea_runner_repo_api_response.status | default('no response') }}.
|
||||
{% if gitea_runner_admin_api_response.json.runners | default([]) | selectattr('name', 'equalto', gitea_runner_name) | list | length > 0 %}
|
||||
Runner found in admin API.
|
||||
{% endif %}
|
||||
{% if gitea_runner_repo_api_response.json.runners | default([]) | selectattr('name', 'equalto', gitea_runner_name) | list | length > 0 %}
|
||||
Runner found in repo API.
|
||||
{% endif %}
|
||||
rescue:
|
||||
- name: API check failed
|
||||
ansible.builtin.debug:
|
||||
msg: "API verification skipped due to connection or permission error."
|
||||
+1
-1
@@ -19,4 +19,4 @@
|
||||
|
||||
- name: Include integration test
|
||||
ansible.builtin.include_tasks: integration_test.yml
|
||||
when: not skip_runner_registration
|
||||
when: not gitea_runner_skip_registration
|
||||
+4
-4
@@ -23,8 +23,8 @@
|
||||
XDG_RUNTIME_DIR: "/run/user/{{ gitea_runner_uid }}"
|
||||
changed_when: true
|
||||
when:
|
||||
- systemd_available.stat.exists
|
||||
- docker_rootless_setup
|
||||
- gitea_runner_systemd_available.stat.exists
|
||||
- gitea_runner_docker_rootless_setup
|
||||
|
||||
- name: Enable and start docker-prune user timer
|
||||
ansible.builtin.command: systemctl --user enable --now docker-prune.timer
|
||||
@@ -34,5 +34,5 @@
|
||||
XDG_RUNTIME_DIR: "/run/user/{{ gitea_runner_uid }}"
|
||||
changed_when: true
|
||||
when:
|
||||
- systemd_available.stat.exists
|
||||
- docker_rootless_setup
|
||||
- gitea_runner_systemd_available.stat.exists
|
||||
- gitea_runner_docker_rootless_setup
|
||||
+6
-6
@@ -10,15 +10,15 @@
|
||||
- name: Check if runner is already registered
|
||||
ansible.builtin.stat:
|
||||
path: "{{ gitea_runner_data_dir }}/.runner"
|
||||
register: runner_registered
|
||||
register: gitea_runner_registered
|
||||
|
||||
- name: Register runner with Gitea
|
||||
ansible.builtin.command: >
|
||||
{{ gitea_runner_binary_path }} register
|
||||
--token {{ registration_token }}
|
||||
--name {{ runner_name }}
|
||||
--name {{ gitea_runner_name }}
|
||||
--instance {{ gitea_url }}
|
||||
--labels {{ runner_labels }}
|
||||
--labels {{ gitea_runner_labels }}
|
||||
--no-interactive
|
||||
args:
|
||||
chdir: "{{ gitea_runner_data_dir }}"
|
||||
@@ -27,7 +27,7 @@
|
||||
environment:
|
||||
XDG_RUNTIME_DIR: "/run/user/{{ gitea_runner_uid | default(0) }}"
|
||||
DOCKER_HOST: "unix:///run/user/{{ gitea_runner_uid | default(0) }}/docker.sock"
|
||||
when: not runner_registered.stat.exists
|
||||
register: register_output
|
||||
changed_when: "'already exists' not in register_output.stdout | default('')"
|
||||
when: not gitea_runner_registered.stat.exists
|
||||
register: gitea_runner_register_output
|
||||
changed_when: "'already exists' not in gitea_runner_register_output.stdout | default('')"
|
||||
timeout: 60
|
||||
+3
-3
@@ -6,14 +6,14 @@
|
||||
|
||||
- name: Resolve runner service user
|
||||
ansible.builtin.set_fact:
|
||||
gitea_runner_service_user: "{{ gitea_runner_user_prefix | default('grm-') }}{{ runner_name }}"
|
||||
gitea_runner_service_user: "{{ gitea_runner_user_prefix | default('grm-') }}{{ gitea_runner_name }}"
|
||||
gitea_runner_base_data_dir: "/var/lib/gitea-runner"
|
||||
gitea_runner_base_config_dir: "/etc/gitea-runner"
|
||||
|
||||
- name: Resolve runner data and config dirs
|
||||
ansible.builtin.set_fact:
|
||||
gitea_runner_data_dir: "{{ gitea_runner_base_data_dir }}/{{ runner_name }}"
|
||||
gitea_runner_config_dir: "{{ gitea_runner_base_config_dir }}/{{ runner_name }}"
|
||||
gitea_runner_data_dir: "{{ gitea_runner_base_data_dir }}/{{ gitea_runner_name }}"
|
||||
gitea_runner_config_dir: "{{ gitea_runner_base_config_dir }}/{{ gitea_runner_name }}"
|
||||
|
||||
- name: Resolve runner service user UID
|
||||
ansible.builtin.getent:
|
||||
+26
-25
@@ -9,18 +9,19 @@
|
||||
- name: Download and dearmor Docker GPG key (Debian/Ubuntu)
|
||||
ansible.builtin.shell: |
|
||||
set -o pipefail
|
||||
curl -fsSL "https://download.docker.com/linux/{{ ansible_facts['distribution'] | lower }}/gpg" | gpg --dearmor --yes -o {{ docker_gpg_key_path }}
|
||||
curl -fsSL "https://download.docker.com/linux/{{ ansible_facts['distribution'] | lower }}/gpg" \
|
||||
| gpg --dearmor --yes -o {{ gitea_runner_docker_gpg_key_path }}
|
||||
args:
|
||||
creates: "{{ docker_gpg_key_path }}"
|
||||
creates: "{{ gitea_runner_docker_gpg_key_path }}"
|
||||
executable: /bin/bash
|
||||
when: ansible_facts['os_family'] == 'Debian'
|
||||
|
||||
- name: Add Docker APT repository (Debian/Ubuntu)
|
||||
ansible.builtin.copy:
|
||||
dest: /etc/apt/sources.list.d/docker.list
|
||||
content: "{{ docker_apt_source_line }}\n"
|
||||
content: "{{ gitea_runner_docker_apt_source_line }}\n"
|
||||
mode: "0644"
|
||||
register: docker_apt_repo
|
||||
register: gitea_runner_docker_apt_repo
|
||||
when: ansible_facts['os_family'] == 'Debian'
|
||||
|
||||
- name: Update apt cache after adding Docker repo (Debian/Ubuntu)
|
||||
@@ -28,7 +29,7 @@
|
||||
update_cache: true
|
||||
when:
|
||||
- ansible_facts['os_family'] == 'Debian'
|
||||
- docker_apt_repo is changed
|
||||
- gitea_runner_docker_apt_repo is changed
|
||||
|
||||
- name: Install rootless Docker dependencies (Debian/Ubuntu)
|
||||
ansible.builtin.apt:
|
||||
@@ -67,7 +68,7 @@
|
||||
- name: Check if rootless Docker is already set up
|
||||
ansible.builtin.stat:
|
||||
path: "{{ gitea_runner_home }}/.config/systemd/user/docker.service"
|
||||
register: rootless_docker_check
|
||||
register: gitea_runner_rootless_docker_check
|
||||
|
||||
- name: Set up rootless Docker for runner user
|
||||
ansible.builtin.command: dockerd-rootless-setuptool.sh install
|
||||
@@ -78,8 +79,8 @@
|
||||
environment:
|
||||
XDG_RUNTIME_DIR: "/run/user/{{ gitea_runner_uid }}"
|
||||
when:
|
||||
- docker_rootless_setup
|
||||
- not rootless_docker_check.stat.exists
|
||||
- gitea_runner_docker_rootless_setup
|
||||
- not gitea_runner_rootless_docker_check.stat.exists
|
||||
|
||||
- name: Start rootless Docker daemon (systemd user service)
|
||||
ansible.builtin.command: systemctl --user start docker
|
||||
@@ -88,7 +89,7 @@
|
||||
environment:
|
||||
XDG_RUNTIME_DIR: "/run/user/{{ gitea_runner_uid }}"
|
||||
changed_when: true
|
||||
when: docker_rootless_setup
|
||||
when: gitea_runner_docker_rootless_setup
|
||||
|
||||
- name: Enable rootless Docker daemon (systemd user service)
|
||||
ansible.builtin.command: systemctl --user enable docker
|
||||
@@ -97,7 +98,7 @@
|
||||
environment:
|
||||
XDG_RUNTIME_DIR: "/run/user/{{ gitea_runner_uid }}"
|
||||
changed_when: true
|
||||
when: docker_rootless_setup
|
||||
when: gitea_runner_docker_rootless_setup
|
||||
|
||||
- name: Ensure Docker config directory exists
|
||||
ansible.builtin.file:
|
||||
@@ -106,7 +107,7 @@
|
||||
mode: "0755"
|
||||
owner: "{{ gitea_runner_service_user }}"
|
||||
group: "{{ gitea_runner_service_user }}"
|
||||
when: docker_rootless_setup
|
||||
when: gitea_runner_docker_rootless_setup
|
||||
|
||||
- name: Ensure systemd user override directory exists
|
||||
ansible.builtin.file:
|
||||
@@ -115,21 +116,21 @@
|
||||
mode: "0755"
|
||||
owner: "{{ gitea_runner_service_user }}"
|
||||
group: "{{ gitea_runner_service_user }}"
|
||||
when: docker_rootless_setup
|
||||
when: gitea_runner_docker_rootless_setup
|
||||
|
||||
- name: Configure rootless Docker to use pasta with IPv6
|
||||
ansible.builtin.copy:
|
||||
dest: "{{ gitea_runner_home }}/.config/systemd/user/docker.service.d/override.conf"
|
||||
content: |
|
||||
[Service]
|
||||
Environment="DOCKERD_ROOTLESS_ROOTLESSKIT_NET={{ docker_rootless_net_driver }}"
|
||||
Environment="DOCKERD_ROOTLESS_ROOTLESSKIT_NET={{ gitea_runner_docker_rootless_net_driver }}"
|
||||
Environment="DOCKERD_ROOTLESS_ROOTLESSKIT_PORT_DRIVER=implicit"
|
||||
Environment="DOCKERD_ROOTLESS_ROOTLESSKIT_FLAGS=--ipv6"
|
||||
mode: "0644"
|
||||
owner: "{{ gitea_runner_service_user }}"
|
||||
group: "{{ gitea_runner_service_user }}"
|
||||
register: docker_network_override
|
||||
when: docker_rootless_setup
|
||||
register: gitea_runner_docker_network_override
|
||||
when: gitea_runner_docker_rootless_setup
|
||||
|
||||
- name: Reload systemd user daemon if network config changed
|
||||
ansible.builtin.command: systemctl --user daemon-reload
|
||||
@@ -139,8 +140,8 @@
|
||||
XDG_RUNTIME_DIR: "/run/user/{{ gitea_runner_uid }}"
|
||||
changed_when: true
|
||||
when:
|
||||
- docker_rootless_setup
|
||||
- docker_network_override is changed
|
||||
- gitea_runner_docker_rootless_setup
|
||||
- gitea_runner_docker_network_override is changed
|
||||
|
||||
- name: Configure rootless Docker daemon with IPv6 enabled
|
||||
ansible.builtin.copy:
|
||||
@@ -149,13 +150,13 @@
|
||||
{
|
||||
"ipv6": true,
|
||||
"ip6tables": true,
|
||||
"fixed-cidr-v6": "{{ docker_ipv6_cidr }}"
|
||||
"fixed-cidr-v6": "{{ gitea_runner_docker_ipv6_cidr }}"
|
||||
}
|
||||
mode: "0644"
|
||||
owner: "{{ gitea_runner_service_user }}"
|
||||
group: "{{ gitea_runner_service_user }}"
|
||||
register: docker_ipv6_config
|
||||
when: docker_rootless_setup
|
||||
register: gitea_runner_docker_ipv6_config
|
||||
when: gitea_runner_docker_rootless_setup
|
||||
|
||||
- name: Restart rootless Docker if config changed
|
||||
ansible.builtin.command: systemctl --user restart docker
|
||||
@@ -165,8 +166,8 @@
|
||||
XDG_RUNTIME_DIR: "/run/user/{{ gitea_runner_uid }}"
|
||||
changed_when: true
|
||||
when:
|
||||
- docker_rootless_setup
|
||||
- docker_ipv6_config is changed or docker_network_override is changed
|
||||
- gitea_runner_docker_rootless_setup
|
||||
- gitea_runner_docker_ipv6_config is changed or gitea_runner_docker_network_override is changed
|
||||
|
||||
- name: Wait for rootless Docker daemon to be ready
|
||||
ansible.builtin.command: docker version
|
||||
@@ -175,9 +176,9 @@
|
||||
environment:
|
||||
DOCKER_HOST: "unix:///run/user/{{ gitea_runner_uid }}/docker.sock"
|
||||
XDG_RUNTIME_DIR: "/run/user/{{ gitea_runner_uid }}"
|
||||
register: docker_ready
|
||||
until: docker_ready.rc == 0
|
||||
register: gitea_runner_docker_ready
|
||||
until: gitea_runner_docker_ready.rc == 0
|
||||
retries: 10
|
||||
delay: 2
|
||||
changed_when: false
|
||||
when: docker_rootless_setup
|
||||
when: gitea_runner_docker_rootless_setup
|
||||
+4
-4
@@ -15,8 +15,8 @@
|
||||
XDG_RUNTIME_DIR: "/run/user/{{ gitea_runner_uid }}"
|
||||
changed_when: true
|
||||
when:
|
||||
- systemd_available.stat.exists
|
||||
- docker_rootless_setup
|
||||
- gitea_runner_systemd_available.stat.exists
|
||||
- gitea_runner_docker_rootless_setup
|
||||
|
||||
- name: Enable and start gitea-runner user service
|
||||
ansible.builtin.command: systemctl --user enable --now gitea-runner
|
||||
@@ -26,5 +26,5 @@
|
||||
XDG_RUNTIME_DIR: "/run/user/{{ gitea_runner_uid }}"
|
||||
changed_when: true
|
||||
when:
|
||||
- systemd_available.stat.exists
|
||||
- docker_rootless_setup
|
||||
- gitea_runner_systemd_available.stat.exists
|
||||
- gitea_runner_docker_rootless_setup
|
||||
+1
-1
@@ -2,4 +2,4 @@
|
||||
- name: Check if systemd is available
|
||||
ansible.builtin.stat:
|
||||
path: /run/systemd/system
|
||||
register: systemd_available
|
||||
register: gitea_runner_systemd_available
|
||||
+2
-2
@@ -9,6 +9,6 @@
|
||||
environment:
|
||||
XDG_RUNTIME_DIR: "/run/user/{{ gitea_runner_uid }}"
|
||||
when:
|
||||
- systemd_available.stat.exists | default(false) | bool
|
||||
- docker_rootless_setup
|
||||
- gitea_runner_systemd_available.stat.exists | default(false) | bool
|
||||
- gitea_runner_docker_rootless_setup
|
||||
changed_when: true
|
||||
+5
-5
@@ -6,21 +6,21 @@
|
||||
shell: /bin/bash
|
||||
system: true
|
||||
create_home: true
|
||||
register: runner_user
|
||||
register: gitea_runner_user
|
||||
|
||||
- name: Set runner UID fact
|
||||
ansible.builtin.set_fact:
|
||||
gitea_runner_uid: "{{ runner_user.uid }}"
|
||||
gitea_runner_uid: "{{ gitea_runner_user.uid }}"
|
||||
|
||||
- name: Check if lingering is already enabled
|
||||
ansible.builtin.stat:
|
||||
path: "/var/lib/systemd/linger/{{ gitea_runner_service_user }}"
|
||||
register: linger_stat
|
||||
register: gitea_runner_linger_stat
|
||||
|
||||
- name: Enable lingering for runner user
|
||||
ansible.builtin.command: loginctl enable-linger {{ gitea_runner_service_user }}
|
||||
changed_when: not linger_stat.stat.exists
|
||||
when: systemd_available.stat.exists
|
||||
changed_when: not gitea_runner_linger_stat.stat.exists
|
||||
when: gitea_runner_systemd_available.stat.exists
|
||||
|
||||
- name: Ensure subuid entry for runner user
|
||||
ansible.builtin.lineinfile:
|
||||
+2
-2
@@ -21,6 +21,6 @@
|
||||
environment:
|
||||
DOCKER_HOST: "unix:///run/user/{{ gitea_runner_uid }}/docker.sock"
|
||||
XDG_RUNTIME_DIR: "/run/user/{{ gitea_runner_uid }}"
|
||||
register: docker_version_output
|
||||
register: gitea_runner_docker_version_output
|
||||
changed_when: false
|
||||
when: docker_rootless_setup
|
||||
when: gitea_runner_docker_rootless_setup
|
||||
@@ -6,12 +6,12 @@
|
||||
tasks:
|
||||
- name: Include systemd availability check
|
||||
ansible.builtin.include_role:
|
||||
name: gitea-runner
|
||||
name: gitea_runner
|
||||
tasks_from: systemd_check.yml
|
||||
|
||||
- name: Resolve runner UID
|
||||
ansible.builtin.include_role:
|
||||
name: gitea-runner
|
||||
name: gitea_runner
|
||||
tasks_from: resolve_uid.yml
|
||||
|
||||
- name: Check if runner is already registered
|
||||
@@ -21,11 +21,11 @@
|
||||
|
||||
- name: Include registration if not registered
|
||||
ansible.builtin.include_role:
|
||||
name: gitea-runner
|
||||
name: gitea_runner
|
||||
tasks_from: register.yml
|
||||
when:
|
||||
- not runner_registered.stat.exists
|
||||
- not skip_runner_registration | default(false)
|
||||
- not gitea_runner_skip_registration | default(false)
|
||||
|
||||
- name: Start gitea-runner user service
|
||||
ansible.builtin.command: systemctl --user start gitea-runner
|
||||
|
||||
@@ -6,12 +6,12 @@
|
||||
tasks:
|
||||
- name: Include systemd availability check
|
||||
ansible.builtin.include_role:
|
||||
name: gitea-runner
|
||||
name: gitea_runner
|
||||
tasks_from: systemd_check.yml
|
||||
|
||||
- name: Resolve runner UID
|
||||
ansible.builtin.include_role:
|
||||
name: gitea-runner
|
||||
name: gitea_runner
|
||||
tasks_from: resolve_uid.yml
|
||||
|
||||
- name: Check systemd user service status
|
||||
|
||||
@@ -6,12 +6,12 @@
|
||||
tasks:
|
||||
- name: Include systemd availability check
|
||||
ansible.builtin.include_role:
|
||||
name: gitea-runner
|
||||
name: gitea_runner
|
||||
tasks_from: systemd_check.yml
|
||||
|
||||
- name: Resolve runner UID
|
||||
ansible.builtin.include_role:
|
||||
name: gitea-runner
|
||||
name: gitea_runner
|
||||
tasks_from: resolve_uid.yml
|
||||
|
||||
- name: Stop gitea-runner user service
|
||||
|
||||
@@ -6,5 +6,5 @@
|
||||
tasks:
|
||||
- name: Update runner
|
||||
ansible.builtin.include_role:
|
||||
name: gitea-runner
|
||||
name: gitea_runner
|
||||
tasks_from: update_runner.yml
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
GRM consists of two layers:
|
||||
|
||||
1. **Python CLI** (`src/grm/`) — built with Click, handles argument parsing, environment loading, i18n translations, and delegates to Ansible via the `ansible-playbook` subprocess.
|
||||
2. **Ansible Role** (`ansible/roles/gitea-runner/`) — idempotent role that creates a dedicated system user, sets up rootless Docker, installs the runner binary, creates a systemd user service, and registers the runner with Gitea.
|
||||
2. **Ansible Role** (`ansible/roles/gitea_runner/`) — idempotent role that creates a dedicated system user, sets up rootless Docker, installs the runner binary, creates a systemd user service, and registers the runner with Gitea.
|
||||
|
||||
## High-Level Design
|
||||
|
||||
@@ -26,7 +26,7 @@ The Ansible role handles all remote state: user creation, package installation,
|
||||
grm install <host>
|
||||
└── RunnerManager.install()
|
||||
└── ansible-playbook ansible/install-runner.yml
|
||||
└── role: gitea-runner
|
||||
└── role: gitea_runner
|
||||
├── user_setup.yml (create per-runner system user + lingering)
|
||||
├── rootless_docker.yml (rootless Docker setup under runner user)
|
||||
├── install_runner.yml (download binary, config, register, service)
|
||||
@@ -83,8 +83,8 @@ Each runner runs as a systemd user service under a dedicated system user (`grm-<
|
||||
|
||||
- **User**: `grm-<name>` (dedicated system user with lingering enabled)
|
||||
- **Home**: `/home/grm-<name>/`
|
||||
- **Data**: `/var/lib/gitea-runner/<name>/`
|
||||
- **Config**: `/etc/gitea-runner/<name>/`
|
||||
- **Data**: `/var/lib/gitea_runner/<name>/`
|
||||
- **Config**: `/etc/gitea_runner/<name>/`
|
||||
- **Service**: `gitea-runner.service` (systemd user service)
|
||||
- **Docker socket**: `/run/user/<UID>/docker.sock` (rootless, per-runner)
|
||||
- **subuid/subgid**: `grm-<name>:100000:65536` (user namespace mapping)
|
||||
@@ -100,7 +100,7 @@ flowchart TD
|
||||
EXEC["Executor<br/>executor.py"]
|
||||
REG["Registry<br/>registry.py<br/>~/.local/share/grm/runners.json"]
|
||||
ANS["ansible-playbook subprocess"]
|
||||
ROLE["Ansible Role<br/>ansible/roles/gitea-runner/"]
|
||||
ROLE["Ansible Role<br/>ansible/roles/gitea_runner/"]
|
||||
USER["user_setup.yml<br/>create system user + lingering"]
|
||||
DOCKER["rootless_docker.yml<br/>rootless Docker setup"]
|
||||
INSTALL["install_runner.yml<br/>download, config, register, service"]
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
│ ├── ui.py # Colorised console output
|
||||
│ └── translations.json # Translation strings
|
||||
├── ansible/
|
||||
│ ├── roles/gitea-runner/ # Main Ansible role
|
||||
│ ├── roles/gitea_runner/ # Main Ansible role
|
||||
│ │ ├── defaults/main.yml # Default variables
|
||||
│ │ ├── tasks/ # Task files (13 files)
|
||||
│ │ ├── templates/ # Jinja2 templates (4 files)
|
||||
|
||||
@@ -43,11 +43,11 @@ make molecule # Quick: all 6 scenarios on Ubuntu 22.04
|
||||
make molecule-all # Full: all 6 scenarios on all 4 supported OSes
|
||||
```
|
||||
|
||||
Molecule tests validate the Ansible role (`ansible/roles/gitea-runner/`) by creating Docker containers as test hosts, applying the role, and verifying the results. Each scenario tests a specific aspect of the role.
|
||||
Molecule tests validate the Ansible role (`ansible/roles/gitea_runner/`) by creating Docker containers as test hosts, applying the role, and verifying the results. Each scenario tests a specific aspect of the role.
|
||||
|
||||
### Scenarios
|
||||
|
||||
Seven Molecule scenarios are defined under `ansible/roles/gitea-runner/molecule/`:
|
||||
Seven Molecule scenarios are defined under `ansible/roles/gitea_runner/molecule/`:
|
||||
|
||||
| Scenario | Description | What it verifies |
|
||||
|----------|-------------|------------------|
|
||||
|
||||
@@ -335,7 +335,7 @@ grm health prod-runner
|
||||
|
||||
Output shows NAME, HOST, HEALTHY (yes/no), and MESSAGE columns. The command exits with code 1 if any runner is unhealthy.
|
||||
|
||||
The health check is also run automatically via a systemd timer installed by the Ansible role. See `ansible/roles/gitea-runner/templates/runner-healthcheck.sh.j2` for the script and `runner-healthcheck.timer.j2` for the timer.
|
||||
The health check is also run automatically via a systemd timer installed by the Ansible role. See `ansible/roles/gitea_runner/templates/runner-healthcheck.sh.j2` for the script and `runner-healthcheck.timer.j2` for the timer.
|
||||
|
||||
## trigger-workflow
|
||||
|
||||
|
||||
+6
-6
@@ -32,19 +32,19 @@ version = {attr = "grm.__version__"}
|
||||
ci = [
|
||||
"pytest==9.1.1",
|
||||
"pytest-cov==7.1.0",
|
||||
"build==1.5.0",
|
||||
"build==1.5.1",
|
||||
"twine==6.2.0",
|
||||
# Reusable CI/CD and dev tools (auto-merge, pr-review, pre-push checks, etc.)
|
||||
"devx==0.40.0",
|
||||
"devx==0.42.0",
|
||||
]
|
||||
# Lint and type-checking tools (validate job)
|
||||
lint = [
|
||||
"ruff==0.15.20",
|
||||
"ruff==0.15.21",
|
||||
"pyright==1.1.411",
|
||||
"bandit==1.9.4",
|
||||
"pip-audit==2.10.1",
|
||||
"pre-commit==4.6.0",
|
||||
"ansible-lint==26.4.0",
|
||||
"ansible-lint==26.6.0",
|
||||
]
|
||||
# Molecule testing (molecule-tests job — stays as a separate CI job)
|
||||
molecule = [
|
||||
@@ -55,7 +55,7 @@ molecule = [
|
||||
dev = [
|
||||
"grm[ci,lint,molecule]",
|
||||
# Reusable CI/CD and dev tools (pre-push hooks, create-task, create-pr)
|
||||
"devx==0.40.0",
|
||||
"devx==0.42.0",
|
||||
# Non-Python dev dependency: checkmake (Makefile linter)
|
||||
# Install via: go install github.com/checkmake/checkmake/cmd/checkmake@latest
|
||||
]
|
||||
@@ -108,7 +108,7 @@ repo_name = "grm"
|
||||
ci_scripts_dir = ""
|
||||
|
||||
# Molecule test weights for LPT scheduling.
|
||||
# GRM has a single role (gitea-runner) with 7 scenarios.
|
||||
# GRM has a single role (gitea_runner) with 7 scenarios.
|
||||
# Weights are estimates — recalibrate from CI logs after next run.
|
||||
[tool.devx.molecule.weights]
|
||||
"multi-instance" = 8
|
||||
|
||||
+1
-1
@@ -157,7 +157,7 @@ class AnsibleExecutor:
|
||||
with open(log_file) as f:
|
||||
content = f.read()
|
||||
match = re.search(
|
||||
r'TASK \[gitea-runner : Report runner status\].*?"msg":\s*"([^"]+)"',
|
||||
r'TASK \[gitea_runner : Report runner status\].*?"msg":\s*"([^"]+)"',
|
||||
content,
|
||||
re.DOTALL,
|
||||
)
|
||||
|
||||
@@ -100,14 +100,14 @@ class RunnerManager:
|
||||
|
||||
extra_vars: dict[str, str | int] = {
|
||||
"registration_token": token,
|
||||
"runner_name": name,
|
||||
"gitea_runner_name": name,
|
||||
"gitea_url": gitea_url,
|
||||
"gitea_runner_integration_retries": integration_retries,
|
||||
}
|
||||
if admin_token:
|
||||
extra_vars["gitea_admin_token"] = admin_token
|
||||
if labels is not None:
|
||||
extra_vars["runner_labels"] = labels
|
||||
extra_vars["gitea_runner_labels"] = labels
|
||||
|
||||
with track_steps() as tracker:
|
||||
tracker.begin(_("Installing Gitea Runner on {host}", host=host))
|
||||
@@ -216,7 +216,7 @@ class RunnerManager:
|
||||
"start-runner.yml",
|
||||
actual_host,
|
||||
actual_user,
|
||||
{"runner_name": name},
|
||||
{"gitea_runner_name": name},
|
||||
actual_key,
|
||||
ask_become_pass,
|
||||
description=_("Starting Gitea Runner {name} on {host}", name=name, host=actual_host),
|
||||
@@ -243,7 +243,7 @@ class RunnerManager:
|
||||
"stop-runner.yml",
|
||||
actual_host,
|
||||
actual_user,
|
||||
{"runner_name": name},
|
||||
{"gitea_runner_name": name},
|
||||
actual_key,
|
||||
ask_become_pass,
|
||||
description=_("Stopping Gitea Runner {name} on {host}", name=name, host=actual_host),
|
||||
@@ -270,7 +270,7 @@ class RunnerManager:
|
||||
"restart-runner.yml",
|
||||
actual_host,
|
||||
actual_user,
|
||||
{"runner_name": name},
|
||||
{"gitea_runner_name": name},
|
||||
actual_key,
|
||||
ask_become_pass,
|
||||
description=_("Restarting Gitea Runner {name} on {host}", name=name, host=actual_host),
|
||||
@@ -297,7 +297,7 @@ class RunnerManager:
|
||||
"enable-runner.yml",
|
||||
actual_host,
|
||||
actual_user,
|
||||
{"runner_name": name},
|
||||
{"gitea_runner_name": name},
|
||||
actual_key,
|
||||
ask_become_pass,
|
||||
description=_("Enabling Gitea Runner {name} on {host}", name=name, host=actual_host),
|
||||
@@ -331,7 +331,7 @@ class RunnerManager:
|
||||
"disable-runner.yml",
|
||||
actual_host,
|
||||
actual_user,
|
||||
{"runner_name": name, "registration_token": token, "gitea_url": resolved_gitea_url},
|
||||
{"gitea_runner_name": name, "registration_token": token, "gitea_url": resolved_gitea_url},
|
||||
actual_key,
|
||||
ask_become_pass,
|
||||
description=_("Disabling Gitea Runner {name} on {host}", name=name, host=actual_host),
|
||||
@@ -358,7 +358,7 @@ class RunnerManager:
|
||||
"status-runner.yml",
|
||||
actual_host,
|
||||
actual_user,
|
||||
{"runner_name": name},
|
||||
{"gitea_runner_name": name},
|
||||
actual_key,
|
||||
ask_become_pass,
|
||||
description=_("Checking status of Gitea Runner {name} on {host}", name=name, host=actual_host),
|
||||
@@ -401,7 +401,7 @@ class RunnerManager:
|
||||
"remove-runner.yml",
|
||||
actual_host,
|
||||
actual_user,
|
||||
{"runner_name": name, "registration_token": resolved_token, "gitea_url": resolved_gitea_url},
|
||||
{"gitea_runner_name": name, "registration_token": resolved_token, "gitea_url": resolved_gitea_url},
|
||||
actual_key,
|
||||
ask_become_pass,
|
||||
description=_("Removing Gitea Runner {name} from {host}", name=name, host=actual_host),
|
||||
|
||||
@@ -70,7 +70,7 @@ class TestAnsibleExecutorRun:
|
||||
|
||||
def test_run_extracts_status(self, tmp_path: Path) -> None:
|
||||
log_content = (
|
||||
"TASK [gitea-runner : Report runner status]\n"
|
||||
"TASK [gitea_runner : Report runner status]\n"
|
||||
"ok: [127.0.0.1] => {\n"
|
||||
' "msg": "Runner \'127.0.0.1\' is installed and running."\n'
|
||||
"}\n"
|
||||
@@ -89,7 +89,7 @@ class TestAnsibleExecutorExtractStatus:
|
||||
def test_extract_status_found(self, tmp_path: Path) -> None:
|
||||
executor = AnsibleExecutor()
|
||||
log_content = (
|
||||
"TASK [gitea-runner : Report runner status]\n"
|
||||
"TASK [gitea_runner : Report runner status]\n"
|
||||
"ok: [127.0.0.1] => {\n"
|
||||
' "msg": "Runner \'127.0.0.1\' is installed and running."\n'
|
||||
"}\n"
|
||||
|
||||
@@ -49,7 +49,7 @@ class TestRunnerManager:
|
||||
assert "--extra-vars" in cmd_str
|
||||
assert "@/tmp/fake-vars.json" in cmd_str
|
||||
assert manager._captured_extra_vars["registration_token"] == "tok"
|
||||
assert manager._captured_extra_vars["runner_name"] == "192.168.1.10"
|
||||
assert manager._captured_extra_vars["gitea_runner_name"] == "192.168.1.10"
|
||||
assert manager._captured_extra_vars["gitea_url"] == "https://git.example.com"
|
||||
assert "Installing Gitea Runner on 192.168.1.10" in mock_executor.run.call_args.kwargs["description"]
|
||||
mock_registry.add.assert_called_once_with(
|
||||
@@ -75,7 +75,7 @@ class TestRunnerManager:
|
||||
assert "--private-key" in cmd_str
|
||||
assert "/key" in cmd_str
|
||||
assert manager._captured_extra_vars["registration_token"] == "preset"
|
||||
assert manager._captured_extra_vars["runner_name"] == "my-runner"
|
||||
assert manager._captured_extra_vars["gitea_runner_name"] == "my-runner"
|
||||
assert "--ask-become-pass" not in cmd_str
|
||||
mock_registry.add.assert_called_once_with(
|
||||
name="my-runner",
|
||||
@@ -126,7 +126,7 @@ class TestRunnerManager:
|
||||
gitea_url="https://git.example.com",
|
||||
labels="docker:docker://alpine:latest",
|
||||
)
|
||||
assert manager._captured_extra_vars["runner_labels"] == "docker:docker://alpine:latest"
|
||||
assert manager._captured_extra_vars["gitea_runner_labels"] == "docker:docker://alpine:latest"
|
||||
|
||||
def test_install_no_labels(self) -> None:
|
||||
mock_registry = MagicMock()
|
||||
@@ -145,7 +145,7 @@ class TestRunnerManager:
|
||||
manager._executor = mock_executor
|
||||
|
||||
manager.install("host1", "root", token="tok", gitea_url="https://git.example.com", labels="")
|
||||
assert manager._captured_extra_vars["runner_labels"] == ""
|
||||
assert manager._captured_extra_vars["gitea_runner_labels"] == ""
|
||||
|
||||
def test_install_with_admin_token(self) -> None:
|
||||
mock_registry = MagicMock()
|
||||
@@ -265,7 +265,7 @@ class TestRunnerManager:
|
||||
cmd = mock_executor.run.call_args.args[0]
|
||||
cmd_str = " ".join(cmd)
|
||||
assert "start-runner.yml" in cmd_str
|
||||
assert manager._captured_extra_vars["runner_name"] == "r1"
|
||||
assert manager._captured_extra_vars["gitea_runner_name"] == "r1"
|
||||
assert "Starting Gitea Runner r1 on host" in mock_executor.run.call_args.kwargs["description"]
|
||||
|
||||
def test_start_with_override(self) -> None:
|
||||
@@ -304,7 +304,7 @@ class TestRunnerManager:
|
||||
cmd = mock_executor.run.call_args.args[0]
|
||||
cmd_str = " ".join(cmd)
|
||||
assert "stop-runner.yml" in cmd_str
|
||||
assert manager._captured_extra_vars["runner_name"] == "r1"
|
||||
assert manager._captured_extra_vars["gitea_runner_name"] == "r1"
|
||||
assert "Stopping Gitea Runner r1 on host" in mock_executor.run.call_args.kwargs["description"]
|
||||
|
||||
def test_restart(self) -> None:
|
||||
@@ -318,7 +318,7 @@ class TestRunnerManager:
|
||||
cmd = mock_executor.run.call_args.args[0]
|
||||
cmd_str = " ".join(cmd)
|
||||
assert "restart-runner.yml" in cmd_str
|
||||
assert manager._captured_extra_vars["runner_name"] == "r1"
|
||||
assert manager._captured_extra_vars["gitea_runner_name"] == "r1"
|
||||
assert "Restarting Gitea Runner r1 on host" in mock_executor.run.call_args.kwargs["description"]
|
||||
|
||||
def test_enable(self) -> None:
|
||||
@@ -332,7 +332,7 @@ class TestRunnerManager:
|
||||
cmd = mock_executor.run.call_args.args[0]
|
||||
cmd_str = " ".join(cmd)
|
||||
assert "enable-runner.yml" in cmd_str
|
||||
assert manager._captured_extra_vars["runner_name"] == "r1"
|
||||
assert manager._captured_extra_vars["gitea_runner_name"] == "r1"
|
||||
assert "Enabling Gitea Runner r1 on host" in mock_executor.run.call_args.kwargs["description"]
|
||||
|
||||
def test_disable(self) -> None:
|
||||
@@ -351,7 +351,7 @@ class TestRunnerManager:
|
||||
cmd = mock_executor.run.call_args.args[0]
|
||||
cmd_str = " ".join(cmd)
|
||||
assert "disable-runner.yml" in cmd_str
|
||||
assert manager._captured_extra_vars["runner_name"] == "r1"
|
||||
assert manager._captured_extra_vars["gitea_runner_name"] == "r1"
|
||||
assert manager._captured_extra_vars["registration_token"] == "tok"
|
||||
assert manager._captured_extra_vars["gitea_url"] == "https://git.example.com"
|
||||
assert "Disabling Gitea Runner r1 on host" in mock_executor.run.call_args.kwargs["description"]
|
||||
@@ -396,7 +396,7 @@ class TestRunnerManager:
|
||||
cmd = mock_executor.run.call_args.args[0]
|
||||
cmd_str = " ".join(cmd)
|
||||
assert "status-runner.yml" in cmd_str
|
||||
assert manager._captured_extra_vars["runner_name"] == "r1"
|
||||
assert manager._captured_extra_vars["gitea_runner_name"] == "r1"
|
||||
assert "Checking status of Gitea Runner r1 on host" in mock_executor.run.call_args.kwargs["description"]
|
||||
|
||||
def test_remove(self) -> None:
|
||||
@@ -415,7 +415,7 @@ class TestRunnerManager:
|
||||
cmd = mock_executor.run.call_args.args[0]
|
||||
cmd_str = " ".join(cmd)
|
||||
assert "remove-runner.yml" in cmd_str
|
||||
assert manager._captured_extra_vars["runner_name"] == "r1"
|
||||
assert manager._captured_extra_vars["gitea_runner_name"] == "r1"
|
||||
assert manager._captured_extra_vars["registration_token"] == "tok"
|
||||
assert manager._captured_extra_vars["gitea_url"] == "https://git.example.com"
|
||||
assert "Removing Gitea Runner r1 from host" in mock_executor.run.call_args.kwargs["description"]
|
||||
|
||||
Reference in New Issue
Block a user