Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
340222e041 | ||
|
|
179e47bbb2 | ||
|
|
68d16577b3 | ||
|
|
38607d9f29 | ||
|
|
90139b306b | ||
|
|
dd475bec0d | ||
|
|
0f0ada3576 | ||
|
|
185e41c49e | ||
|
|
103741b3ab |
@@ -2,6 +2,24 @@
|
|||||||
|
|
||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
|
## [0.18.6] - 2026-08-05
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
- Pre-configure daemon.json before rootless setuptool + add DBUS_SESSION_BUS_ADDRESS
|
||||||
|
|
||||||
|
## [0.18.5] - 2026-08-05
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
- Pin Docker 28.x + disable containerd snapshotter + tune prune/disk
|
||||||
|
|
||||||
|
## [0.18.4] - 2026-08-05
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
- Harden rootless Docker daemon resilience on CI runners
|
||||||
|
|
||||||
## [0.18.3] - 2026-08-04
|
## [0.18.3] - 2026-08-04
|
||||||
|
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
|
|||||||
@@ -8,12 +8,12 @@ Each runner runs in an isolated **rootless Docker** environment under a dedicate
|
|||||||
|
|
||||||
[](https://git.oblachno.oblachno.fyi/oblachno-oss/grm/actions)
|
[](https://git.oblachno.oblachno.fyi/oblachno-oss/grm/actions)
|
||||||
[](https://git.oblachno.oblachno.fyi/oblachno-oss/grm/src/branch/master/LICENSE)
|
[](https://git.oblachno.oblachno.fyi/oblachno-oss/grm/src/branch/master/LICENSE)
|
||||||
[](https://git.oblachno.oblachno.fyi/oblachno-oss/grm/actions)
|
[](https://git.oblachno.oblachno.fyi/oblachno-oss/grm/actions)
|
||||||
[](https://git.oblachno.oblachno.fyi/oblachno-oss/grm/actions)
|
[](https://git.oblachno.oblachno.fyi/oblachno-oss/grm/actions)
|
||||||
[](https://git.oblachno.oblachno.fyi/oblachno-oss/grm/wiki)
|
[](https://git.oblachno.oblachno.fyi/oblachno-oss/grm/wiki)
|
||||||
[](https://git.oblachno.oblachno.fyi/oblachno-oss/grm/actions)
|
[](https://git.oblachno.oblachno.fyi/oblachno-oss/grm/actions)
|
||||||
[](https://git.oblachno.oblachno.fyi/oblachno-oss/grm/releases)
|
[](https://git.oblachno.oblachno.fyi/oblachno-oss/grm/releases)
|
||||||
[](https://www.python.org/downloads/)
|
[](https://www.python.org/downloads/)
|
||||||
|
|
||||||
## Why GRM?
|
## Why GRM?
|
||||||
|
|
||||||
|
|||||||
@@ -18,18 +18,40 @@ gitea_runner_binary_path: "/usr/local/bin/gitea_runner"
|
|||||||
|
|
||||||
# Prune configuration
|
# Prune configuration
|
||||||
gitea_runner_prune_until: "24h"
|
gitea_runner_prune_until: "24h"
|
||||||
gitea_runner_prune_schedule: "daily"
|
# Every 6 hours — daily is insufficient for CI runners that build dozens
|
||||||
|
# of images per day. Accumulation between daily runs can trigger Docker
|
||||||
|
# daemon instability (containerd snapshotter GC holds locks, blocking
|
||||||
|
# container operations).
|
||||||
|
gitea_runner_prune_schedule: "*-*-* 00/6:00:00"
|
||||||
gitea_runner_prune_label: "gitea-runner=true"
|
gitea_runner_prune_label: "gitea-runner=true"
|
||||||
|
|
||||||
# Service configuration
|
# Service configuration
|
||||||
gitea_runner_service_restart_sec: "5"
|
gitea_runner_service_restart_sec: "5"
|
||||||
|
|
||||||
# Health check configuration
|
# Health check configuration
|
||||||
gitea_runner_healthcheck_interval: "5min"
|
# 2min interval — catches hung daemons before multiple CI jobs fail between checks.
|
||||||
|
# The previous 5min interval was too coarse: a stuck daemon could fail 3+ molecule
|
||||||
|
# jobs in the window between healthcheck runs.
|
||||||
|
gitea_runner_healthcheck_interval: "2min"
|
||||||
gitea_runner_healthcheck_boot_delay: "2min"
|
gitea_runner_healthcheck_boot_delay: "2min"
|
||||||
gitea_runner_healthcheck_disk_threshold: 85
|
gitea_runner_healthcheck_disk_threshold: 75
|
||||||
gitea_runner_healthcheck_script_path: "{{ gitea_runner_config_dir }}/healthcheck.sh"
|
gitea_runner_healthcheck_script_path: "{{ gitea_runner_config_dir }}/healthcheck.sh"
|
||||||
|
|
||||||
|
# Docker daemon resilience settings (applied to daemon.json).
|
||||||
|
# live-restore: containers survive daemon restarts — prevents stuck container
|
||||||
|
# states when the healthcheck restarts a hung daemon.
|
||||||
|
# shutdown-timeout: grace period (seconds) for containers to stop on daemon
|
||||||
|
# shutdown/restart. Default 15s is too short for DinD containers with nested
|
||||||
|
# processes (molecule tests). 30s gives SIGTERM time to propagate.
|
||||||
|
# max-concurrent-downloads/uploads: limits parallel transfers to reduce daemon
|
||||||
|
# memory pressure when multiple CI jobs pull images simultaneously.
|
||||||
|
# default-ulimits: prevents FD exhaustion in container processes.
|
||||||
|
gitea_runner_docker_live_restore: true
|
||||||
|
gitea_runner_docker_shutdown_timeout: 30
|
||||||
|
gitea_runner_docker_max_concurrent_downloads: 3
|
||||||
|
gitea_runner_docker_max_concurrent_uploads: 3
|
||||||
|
gitea_runner_docker_default_nofile: 65536
|
||||||
|
|
||||||
# Admin token for runner deregistration via Gitea API.
|
# Admin token for runner deregistration via Gitea API.
|
||||||
# If not set, falls back to registration_token (which likely lacks admin scope).
|
# 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.
|
# Set this to a token with admin scope to enable automatic runner cleanup on removal.
|
||||||
|
|||||||
@@ -49,6 +49,9 @@
|
|||||||
- "'Type=oneshot' in prune_service.content | b64decode"
|
- "'Type=oneshot' in prune_service.content | b64decode"
|
||||||
- "'docker system prune' in prune_service.content | b64decode"
|
- "'docker system prune' in prune_service.content | b64decode"
|
||||||
- "'docker volume prune' in prune_service.content | b64decode"
|
- "'docker volume prune' in prune_service.content | b64decode"
|
||||||
|
- "'docker container prune' in prune_service.content | b64decode"
|
||||||
|
- "'docker network prune' in prune_service.content | b64decode"
|
||||||
|
- "'docker builder prune' in prune_service.content | b64decode"
|
||||||
fail_msg: "Prune service template is missing expected directives"
|
fail_msg: "Prune service template is missing expected directives"
|
||||||
|
|
||||||
- name: Read rendered prune timer template
|
- name: Read rendered prune timer template
|
||||||
@@ -99,9 +102,15 @@
|
|||||||
ansible.builtin.assert:
|
ansible.builtin.assert:
|
||||||
that:
|
that:
|
||||||
- "'docker info' in healthcheck_script.content | b64decode"
|
- "'docker info' in healthcheck_script.content | b64decode"
|
||||||
|
- "'timeout 10 docker info' in healthcheck_script.content | b64decode"
|
||||||
- "'systemctl --user restart docker.service' in healthcheck_script.content | b64decode"
|
- "'systemctl --user restart docker.service' in healthcheck_script.content | b64decode"
|
||||||
- "'systemctl --user restart gitea-runner.service' in healthcheck_script.content | b64decode"
|
- "'systemctl --user restart gitea-runner.service' in healthcheck_script.content | b64decode"
|
||||||
- "'docker system prune' in healthcheck_script.content | b64decode"
|
- "'docker system prune' in healthcheck_script.content | b64decode"
|
||||||
|
- "'docker container prune' in healthcheck_script.content | b64decode"
|
||||||
|
- "'docker network prune' in healthcheck_script.content | b64decode"
|
||||||
|
- "'status=removing' in healthcheck_script.content | b64decode"
|
||||||
|
- "'status=stopping' in healthcheck_script.content | b64decode"
|
||||||
|
- "'docker rm -f' in healthcheck_script.content | b64decode"
|
||||||
- "gitea_runner_healthcheck_disk_threshold | string in healthcheck_script.content | b64decode"
|
- "gitea_runner_healthcheck_disk_threshold | string in healthcheck_script.content | b64decode"
|
||||||
fail_msg: "Healthcheck script template is missing expected content"
|
fail_msg: "Healthcheck script template is missing expected content"
|
||||||
|
|
||||||
|
|||||||
@@ -29,6 +29,7 @@
|
|||||||
become_user: "{{ gitea_runner_service_user }}"
|
become_user: "{{ gitea_runner_service_user }}"
|
||||||
environment:
|
environment:
|
||||||
XDG_RUNTIME_DIR: "/run/user/{{ gitea_runner_uid | default(0) }}"
|
XDG_RUNTIME_DIR: "/run/user/{{ gitea_runner_uid | default(0) }}"
|
||||||
|
DBUS_SESSION_BUS_ADDRESS: "unix:path=/run/user/{{ gitea_runner_uid | default(0) }}/bus"
|
||||||
DOCKER_HOST: "unix:///run/user/{{ gitea_runner_uid | default(0) }}/docker.sock"
|
DOCKER_HOST: "unix:///run/user/{{ gitea_runner_uid | default(0) }}/docker.sock"
|
||||||
when:
|
when:
|
||||||
- gitea_runner_file_stat.stat.exists | default(false) | bool
|
- gitea_runner_file_stat.stat.exists | default(false) | bool
|
||||||
|
|||||||
@@ -29,6 +29,7 @@
|
|||||||
become_user: "{{ gitea_runner_service_user }}"
|
become_user: "{{ gitea_runner_service_user }}"
|
||||||
environment:
|
environment:
|
||||||
XDG_RUNTIME_DIR: "/run/user/{{ gitea_runner_uid }}"
|
XDG_RUNTIME_DIR: "/run/user/{{ gitea_runner_uid }}"
|
||||||
|
DBUS_SESSION_BUS_ADDRESS: "unix:path=/run/user/{{ gitea_runner_uid | default(0) }}/bus"
|
||||||
changed_when: true
|
changed_when: true
|
||||||
when:
|
when:
|
||||||
- gitea_runner_systemd_available.stat.exists
|
- gitea_runner_systemd_available.stat.exists
|
||||||
@@ -40,6 +41,7 @@
|
|||||||
become_user: "{{ gitea_runner_service_user }}"
|
become_user: "{{ gitea_runner_service_user }}"
|
||||||
environment:
|
environment:
|
||||||
XDG_RUNTIME_DIR: "/run/user/{{ gitea_runner_uid }}"
|
XDG_RUNTIME_DIR: "/run/user/{{ gitea_runner_uid }}"
|
||||||
|
DBUS_SESSION_BUS_ADDRESS: "unix:path=/run/user/{{ gitea_runner_uid | default(0) }}/bus"
|
||||||
changed_when: true
|
changed_when: true
|
||||||
when:
|
when:
|
||||||
- gitea_runner_systemd_available.stat.exists
|
- gitea_runner_systemd_available.stat.exists
|
||||||
|
|||||||
@@ -24,6 +24,7 @@
|
|||||||
become_user: "{{ gitea_runner_service_user }}"
|
become_user: "{{ gitea_runner_service_user }}"
|
||||||
environment:
|
environment:
|
||||||
XDG_RUNTIME_DIR: "/run/user/{{ gitea_runner_uid }}"
|
XDG_RUNTIME_DIR: "/run/user/{{ gitea_runner_uid }}"
|
||||||
|
DBUS_SESSION_BUS_ADDRESS: "unix:path=/run/user/{{ gitea_runner_uid | default(0) }}/bus"
|
||||||
register: gitea_runner_service_check
|
register: gitea_runner_service_check
|
||||||
changed_when: false
|
changed_when: false
|
||||||
retries: 10
|
retries: 10
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
environment:
|
environment:
|
||||||
DOCKER_HOST: "unix:///run/user/{{ gitea_runner_uid }}/docker.sock"
|
DOCKER_HOST: "unix:///run/user/{{ gitea_runner_uid }}/docker.sock"
|
||||||
XDG_RUNTIME_DIR: "/run/user/{{ gitea_runner_uid }}"
|
XDG_RUNTIME_DIR: "/run/user/{{ gitea_runner_uid }}"
|
||||||
|
DBUS_SESSION_BUS_ADDRESS: "unix:path=/run/user/{{ gitea_runner_uid | default(0) }}/bus"
|
||||||
register: gitea_runner_pre_pull_result
|
register: gitea_runner_pre_pull_result
|
||||||
changed_when: "'Status: Downloaded' in gitea_runner_pre_pull_result.stdout or 'Status: Downloaded' in gitea_runner_pre_pull_result.stderr"
|
changed_when: "'Status: Downloaded' in gitea_runner_pre_pull_result.stdout or 'Status: Downloaded' in gitea_runner_pre_pull_result.stderr"
|
||||||
retries: 3
|
retries: 3
|
||||||
|
|||||||
@@ -21,6 +21,7 @@
|
|||||||
become_user: "{{ gitea_runner_service_user }}"
|
become_user: "{{ gitea_runner_service_user }}"
|
||||||
environment:
|
environment:
|
||||||
XDG_RUNTIME_DIR: "/run/user/{{ gitea_runner_uid }}"
|
XDG_RUNTIME_DIR: "/run/user/{{ gitea_runner_uid }}"
|
||||||
|
DBUS_SESSION_BUS_ADDRESS: "unix:path=/run/user/{{ gitea_runner_uid | default(0) }}/bus"
|
||||||
changed_when: true
|
changed_when: true
|
||||||
when:
|
when:
|
||||||
- gitea_runner_systemd_available.stat.exists
|
- gitea_runner_systemd_available.stat.exists
|
||||||
@@ -32,6 +33,7 @@
|
|||||||
become_user: "{{ gitea_runner_service_user }}"
|
become_user: "{{ gitea_runner_service_user }}"
|
||||||
environment:
|
environment:
|
||||||
XDG_RUNTIME_DIR: "/run/user/{{ gitea_runner_uid }}"
|
XDG_RUNTIME_DIR: "/run/user/{{ gitea_runner_uid }}"
|
||||||
|
DBUS_SESSION_BUS_ADDRESS: "unix:path=/run/user/{{ gitea_runner_uid | default(0) }}/bus"
|
||||||
changed_when: true
|
changed_when: true
|
||||||
when:
|
when:
|
||||||
- gitea_runner_systemd_available.stat.exists
|
- gitea_runner_systemd_available.stat.exists
|
||||||
|
|||||||
@@ -26,6 +26,7 @@
|
|||||||
become_user: "{{ gitea_runner_service_user }}"
|
become_user: "{{ gitea_runner_service_user }}"
|
||||||
environment:
|
environment:
|
||||||
XDG_RUNTIME_DIR: "/run/user/{{ gitea_runner_uid | default(0) }}"
|
XDG_RUNTIME_DIR: "/run/user/{{ gitea_runner_uid | default(0) }}"
|
||||||
|
DBUS_SESSION_BUS_ADDRESS: "unix:path=/run/user/{{ gitea_runner_uid | default(0) }}/bus"
|
||||||
DOCKER_HOST: "unix:///run/user/{{ gitea_runner_uid | default(0) }}/docker.sock"
|
DOCKER_HOST: "unix:///run/user/{{ gitea_runner_uid | default(0) }}/docker.sock"
|
||||||
when: not gitea_runner_registered.stat.exists
|
when: not gitea_runner_registered.stat.exists
|
||||||
register: gitea_runner_register_output
|
register: gitea_runner_register_output
|
||||||
|
|||||||
@@ -31,6 +31,11 @@
|
|||||||
- ansible_facts['os_family'] == 'Debian'
|
- ansible_facts['os_family'] == 'Debian'
|
||||||
- gitea_runner_docker_apt_repo is changed
|
- gitea_runner_docker_apt_repo is changed
|
||||||
|
|
||||||
|
# Install Docker packages from the upstream Docker APT repository.
|
||||||
|
# We do NOT pin to 28.x because recent Ubuntu releases (e.g. 26.04/plucky)
|
||||||
|
# may not have 28.x packages in the Docker repo, and Docker 29 is safe
|
||||||
|
# for rootless mode when the daemon.json disables the containerd snapshotter
|
||||||
|
# and sets a conservative default nofile ulimit (see daemon.json tasks below).
|
||||||
- name: Install rootless Docker dependencies (Debian/Ubuntu)
|
- name: Install rootless Docker dependencies (Debian/Ubuntu)
|
||||||
ansible.builtin.apt:
|
ansible.builtin.apt:
|
||||||
name:
|
name:
|
||||||
@@ -45,6 +50,7 @@
|
|||||||
- docker-compose-plugin
|
- docker-compose-plugin
|
||||||
- rsync
|
- rsync
|
||||||
state: present
|
state: present
|
||||||
|
register: gitea_runner_docker_install
|
||||||
when: ansible_facts['os_family'] == 'Debian'
|
when: ansible_facts['os_family'] == 'Debian'
|
||||||
|
|
||||||
- name: Update pacman cache (Arch Linux)
|
- name: Update pacman cache (Arch Linux)
|
||||||
@@ -133,8 +139,57 @@
|
|||||||
content: |
|
content: |
|
||||||
[Service]
|
[Service]
|
||||||
Environment="DOCKERD_ROOTLESS_ROOTLESSKIT_NET={{ gitea_runner_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_PORT_DRIVER={{ 'implicit' if gitea_runner_docker_rootless_net_driver == 'pasta' else 'builtin' }}"
|
||||||
|
{% if gitea_runner_docker_rootless_net_driver == 'pasta' %}
|
||||||
Environment="DOCKERD_ROOTLESS_ROOTLESSKIT_FLAGS=--ipv6"
|
Environment="DOCKERD_ROOTLESS_ROOTLESSKIT_FLAGS=--ipv6"
|
||||||
|
{% endif %}
|
||||||
|
mode: "0644"
|
||||||
|
owner: "{{ gitea_runner_service_user }}"
|
||||||
|
group: "{{ gitea_runner_service_user }}"
|
||||||
|
when:
|
||||||
|
- gitea_runner_docker_rootless_setup
|
||||||
|
- not gitea_runner_rootless_docker_check.stat.exists
|
||||||
|
|
||||||
|
# Write daemon.json BEFORE the setuptool starts dockerd, so Docker 29
|
||||||
|
# starts with containerd snapshotter disabled from the very first boot.
|
||||||
|
# Without this, Docker 29 uses containerd snapshots by default, which
|
||||||
|
# causes instability in rootless mode.
|
||||||
|
- name: Ensure Docker config directory exists (pre-setup)
|
||||||
|
ansible.builtin.file:
|
||||||
|
path: "{{ gitea_runner_home }}/.config/docker"
|
||||||
|
state: directory
|
||||||
|
mode: "0755"
|
||||||
|
owner: "{{ gitea_runner_service_user }}"
|
||||||
|
group: "{{ gitea_runner_service_user }}"
|
||||||
|
when:
|
||||||
|
- gitea_runner_docker_rootless_setup
|
||||||
|
- not gitea_runner_rootless_docker_check.stat.exists
|
||||||
|
|
||||||
|
- name: Pre-configure rootless Docker daemon.json (disable containerd snapshotter)
|
||||||
|
ansible.builtin.copy:
|
||||||
|
dest: "{{ gitea_runner_home }}/.config/docker/daemon.json"
|
||||||
|
content: |
|
||||||
|
{
|
||||||
|
"live-restore": {{ gitea_runner_docker_live_restore | to_json }},
|
||||||
|
"shutdown-timeout": {{ gitea_runner_docker_shutdown_timeout }},
|
||||||
|
"max-concurrent-downloads": {{ gitea_runner_docker_max_concurrent_downloads }},
|
||||||
|
"max-concurrent-uploads": {{ gitea_runner_docker_max_concurrent_uploads }},
|
||||||
|
"default-ulimits": {
|
||||||
|
"nofile": {"Name": "nofile", "Hard": {{ gitea_runner_docker_default_nofile }}, "Soft": {{ gitea_runner_docker_default_nofile }}}
|
||||||
|
},
|
||||||
|
"features": {
|
||||||
|
"containerd-snapshotter": false
|
||||||
|
},
|
||||||
|
{% if gitea_runner_docker_rootless_net_driver == 'pasta' %}
|
||||||
|
"ipv6": true,
|
||||||
|
"ip6tables": true,
|
||||||
|
"fixed-cidr-v6": "{{ gitea_runner_docker_ipv6_cidr }}",
|
||||||
|
"dns": ["10.0.2.3", "8.8.8.8"]
|
||||||
|
{% else %}
|
||||||
|
"ipv6": false,
|
||||||
|
"dns": ["8.8.8.8", "1.1.1.1"]
|
||||||
|
{% endif %}
|
||||||
|
}
|
||||||
mode: "0644"
|
mode: "0644"
|
||||||
owner: "{{ gitea_runner_service_user }}"
|
owner: "{{ gitea_runner_service_user }}"
|
||||||
group: "{{ gitea_runner_service_user }}"
|
group: "{{ gitea_runner_service_user }}"
|
||||||
@@ -150,6 +205,7 @@
|
|||||||
become_user: "{{ gitea_runner_service_user }}"
|
become_user: "{{ gitea_runner_service_user }}"
|
||||||
environment:
|
environment:
|
||||||
XDG_RUNTIME_DIR: "/run/user/{{ gitea_runner_uid }}"
|
XDG_RUNTIME_DIR: "/run/user/{{ gitea_runner_uid }}"
|
||||||
|
DBUS_SESSION_BUS_ADDRESS: "unix:path=/run/user/{{ gitea_runner_uid }}/bus"
|
||||||
DOCKERD_ROOTLESS_ROOTLESSKIT_NET: "{{ gitea_runner_docker_rootless_net_driver }}"
|
DOCKERD_ROOTLESS_ROOTLESSKIT_NET: "{{ gitea_runner_docker_rootless_net_driver }}"
|
||||||
when:
|
when:
|
||||||
- gitea_runner_docker_rootless_setup
|
- gitea_runner_docker_rootless_setup
|
||||||
@@ -161,6 +217,7 @@
|
|||||||
become_user: "{{ gitea_runner_service_user }}"
|
become_user: "{{ gitea_runner_service_user }}"
|
||||||
environment:
|
environment:
|
||||||
XDG_RUNTIME_DIR: "/run/user/{{ gitea_runner_uid }}"
|
XDG_RUNTIME_DIR: "/run/user/{{ gitea_runner_uid }}"
|
||||||
|
DBUS_SESSION_BUS_ADDRESS: "unix:path=/run/user/{{ gitea_runner_uid }}/bus"
|
||||||
changed_when: true
|
changed_when: true
|
||||||
when: gitea_runner_docker_rootless_setup
|
when: gitea_runner_docker_rootless_setup
|
||||||
|
|
||||||
@@ -170,6 +227,7 @@
|
|||||||
become_user: "{{ gitea_runner_service_user }}"
|
become_user: "{{ gitea_runner_service_user }}"
|
||||||
environment:
|
environment:
|
||||||
XDG_RUNTIME_DIR: "/run/user/{{ gitea_runner_uid }}"
|
XDG_RUNTIME_DIR: "/run/user/{{ gitea_runner_uid }}"
|
||||||
|
DBUS_SESSION_BUS_ADDRESS: "unix:path=/run/user/{{ gitea_runner_uid }}/bus"
|
||||||
changed_when: true
|
changed_when: true
|
||||||
when: gitea_runner_docker_rootless_setup
|
when: gitea_runner_docker_rootless_setup
|
||||||
|
|
||||||
@@ -204,6 +262,7 @@
|
|||||||
become_user: "{{ gitea_runner_service_user }}"
|
become_user: "{{ gitea_runner_service_user }}"
|
||||||
environment:
|
environment:
|
||||||
XDG_RUNTIME_DIR: "/run/user/{{ gitea_runner_uid }}"
|
XDG_RUNTIME_DIR: "/run/user/{{ gitea_runner_uid }}"
|
||||||
|
DBUS_SESSION_BUS_ADDRESS: "unix:path=/run/user/{{ gitea_runner_uid }}/bus"
|
||||||
changed_when: true
|
changed_when: true
|
||||||
when:
|
when:
|
||||||
- gitea_runner_docker_rootless_setup
|
- gitea_runner_docker_rootless_setup
|
||||||
@@ -214,6 +273,16 @@
|
|||||||
dest: "{{ gitea_runner_home }}/.config/docker/daemon.json"
|
dest: "{{ gitea_runner_home }}/.config/docker/daemon.json"
|
||||||
content: |
|
content: |
|
||||||
{
|
{
|
||||||
|
"live-restore": {{ gitea_runner_docker_live_restore | to_json }},
|
||||||
|
"shutdown-timeout": {{ gitea_runner_docker_shutdown_timeout }},
|
||||||
|
"max-concurrent-downloads": {{ gitea_runner_docker_max_concurrent_downloads }},
|
||||||
|
"max-concurrent-uploads": {{ gitea_runner_docker_max_concurrent_uploads }},
|
||||||
|
"default-ulimits": {
|
||||||
|
"nofile": {"Name": "nofile", "Hard": {{ gitea_runner_docker_default_nofile }}, "Soft": {{ gitea_runner_docker_default_nofile }}}
|
||||||
|
},
|
||||||
|
"features": {
|
||||||
|
"containerd-snapshotter": false
|
||||||
|
},
|
||||||
{% if gitea_runner_docker_rootless_net_driver == 'pasta' %}
|
{% if gitea_runner_docker_rootless_net_driver == 'pasta' %}
|
||||||
"ipv6": true,
|
"ipv6": true,
|
||||||
"ip6tables": true,
|
"ip6tables": true,
|
||||||
@@ -236,6 +305,7 @@
|
|||||||
become_user: "{{ gitea_runner_service_user }}"
|
become_user: "{{ gitea_runner_service_user }}"
|
||||||
environment:
|
environment:
|
||||||
XDG_RUNTIME_DIR: "/run/user/{{ gitea_runner_uid }}"
|
XDG_RUNTIME_DIR: "/run/user/{{ gitea_runner_uid }}"
|
||||||
|
DBUS_SESSION_BUS_ADDRESS: "unix:path=/run/user/{{ gitea_runner_uid }}/bus"
|
||||||
changed_when: true
|
changed_when: true
|
||||||
when:
|
when:
|
||||||
- gitea_runner_docker_rootless_setup
|
- gitea_runner_docker_rootless_setup
|
||||||
@@ -248,6 +318,7 @@
|
|||||||
environment:
|
environment:
|
||||||
DOCKER_HOST: "unix:///run/user/{{ gitea_runner_uid }}/docker.sock"
|
DOCKER_HOST: "unix:///run/user/{{ gitea_runner_uid }}/docker.sock"
|
||||||
XDG_RUNTIME_DIR: "/run/user/{{ gitea_runner_uid }}"
|
XDG_RUNTIME_DIR: "/run/user/{{ gitea_runner_uid }}"
|
||||||
|
DBUS_SESSION_BUS_ADDRESS: "unix:path=/run/user/{{ gitea_runner_uid }}/bus"
|
||||||
register: gitea_runner_docker_ready
|
register: gitea_runner_docker_ready
|
||||||
until: gitea_runner_docker_ready.rc == 0
|
until: gitea_runner_docker_ready.rc == 0
|
||||||
retries: 10
|
retries: 10
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
become_user: "{{ gitea_runner_service_user }}"
|
become_user: "{{ gitea_runner_service_user }}"
|
||||||
environment:
|
environment:
|
||||||
XDG_RUNTIME_DIR: "/run/user/{{ gitea_runner_uid }}"
|
XDG_RUNTIME_DIR: "/run/user/{{ gitea_runner_uid }}"
|
||||||
|
DBUS_SESSION_BUS_ADDRESS: "unix:path=/run/user/{{ gitea_runner_uid | default(0) }}/bus"
|
||||||
changed_when: true
|
changed_when: true
|
||||||
when:
|
when:
|
||||||
- gitea_runner_systemd_available.stat.exists
|
- gitea_runner_systemd_available.stat.exists
|
||||||
@@ -24,6 +25,7 @@
|
|||||||
become_user: "{{ gitea_runner_service_user }}"
|
become_user: "{{ gitea_runner_service_user }}"
|
||||||
environment:
|
environment:
|
||||||
XDG_RUNTIME_DIR: "/run/user/{{ gitea_runner_uid }}"
|
XDG_RUNTIME_DIR: "/run/user/{{ gitea_runner_uid }}"
|
||||||
|
DBUS_SESSION_BUS_ADDRESS: "unix:path=/run/user/{{ gitea_runner_uid | default(0) }}/bus"
|
||||||
changed_when: true
|
changed_when: true
|
||||||
when:
|
when:
|
||||||
- gitea_runner_systemd_available.stat.exists
|
- gitea_runner_systemd_available.stat.exists
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
become_user: "{{ gitea_runner_service_user }}"
|
become_user: "{{ gitea_runner_service_user }}"
|
||||||
environment:
|
environment:
|
||||||
XDG_RUNTIME_DIR: "/run/user/{{ gitea_runner_uid }}"
|
XDG_RUNTIME_DIR: "/run/user/{{ gitea_runner_uid }}"
|
||||||
|
DBUS_SESSION_BUS_ADDRESS: "unix:path=/run/user/{{ gitea_runner_uid | default(0) }}/bus"
|
||||||
when:
|
when:
|
||||||
- gitea_runner_systemd_available.stat.exists | default(false) | bool
|
- gitea_runner_systemd_available.stat.exists | default(false) | bool
|
||||||
- gitea_runner_docker_rootless_setup
|
- gitea_runner_docker_rootless_setup
|
||||||
|
|||||||
@@ -21,6 +21,7 @@
|
|||||||
environment:
|
environment:
|
||||||
DOCKER_HOST: "unix:///run/user/{{ gitea_runner_uid }}/docker.sock"
|
DOCKER_HOST: "unix:///run/user/{{ gitea_runner_uid }}/docker.sock"
|
||||||
XDG_RUNTIME_DIR: "/run/user/{{ gitea_runner_uid }}"
|
XDG_RUNTIME_DIR: "/run/user/{{ gitea_runner_uid }}"
|
||||||
|
DBUS_SESSION_BUS_ADDRESS: "unix:path=/run/user/{{ gitea_runner_uid | default(0) }}/bus"
|
||||||
register: gitea_runner_docker_version_output
|
register: gitea_runner_docker_version_output
|
||||||
changed_when: false
|
changed_when: false
|
||||||
when: gitea_runner_docker_rootless_setup
|
when: gitea_runner_docker_rootless_setup
|
||||||
|
|||||||
@@ -7,3 +7,6 @@ Environment=DOCKER_HOST=unix:///run/user/{{ gitea_runner_uid }}/docker.sock
|
|||||||
Environment=XDG_RUNTIME_DIR=/run/user/{{ gitea_runner_uid }}
|
Environment=XDG_RUNTIME_DIR=/run/user/{{ gitea_runner_uid }}
|
||||||
ExecStart=/usr/bin/docker system prune -f --filter "label={{ gitea_runner_prune_label }}" --filter "until={{ gitea_runner_prune_until }}"
|
ExecStart=/usr/bin/docker system prune -f --filter "label={{ gitea_runner_prune_label }}" --filter "until={{ gitea_runner_prune_until }}"
|
||||||
ExecStart=/usr/bin/docker volume prune -f --filter "label={{ gitea_runner_prune_label }}"
|
ExecStart=/usr/bin/docker volume prune -f --filter "label={{ gitea_runner_prune_label }}"
|
||||||
|
ExecStart=/usr/bin/docker container prune -f
|
||||||
|
ExecStart=/usr/bin/docker network prune -f
|
||||||
|
ExecStart=/usr/bin/docker builder prune -f
|
||||||
|
|||||||
@@ -7,18 +7,29 @@ DOCKER_HOST="unix:///run/user/{{ gitea_runner_uid }}/docker.sock"
|
|||||||
XDG_RUNTIME_DIR="/run/user/{{ gitea_runner_uid }}"
|
XDG_RUNTIME_DIR="/run/user/{{ gitea_runner_uid }}"
|
||||||
export DOCKER_HOST XDG_RUNTIME_DIR
|
export DOCKER_HOST XDG_RUNTIME_DIR
|
||||||
|
|
||||||
# 1. Check Docker daemon responsiveness
|
# 1. Check Docker daemon responsiveness (with timeout — a bare `docker info` can
|
||||||
if ! docker info >/dev/null 2>&1; then
|
# hang indefinitely on a stuck daemon, blocking the healthcheck itself).
|
||||||
echo "ERROR: Docker daemon not responding at ${DOCKER_HOST}"
|
if ! timeout 10 docker info >/dev/null 2>&1; then
|
||||||
|
echo "ERROR: Docker daemon not responding at ${DOCKER_HOST} (timed out after 10s)"
|
||||||
systemctl --user restart docker.service
|
systemctl --user restart docker.service
|
||||||
sleep 3
|
sleep 3
|
||||||
if ! docker info >/dev/null 2>&1; then
|
if ! timeout 10 docker info >/dev/null 2>&1; then
|
||||||
echo "CRITICAL: Docker daemon still down after restart"
|
echo "CRITICAL: Docker daemon still down after restart"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
echo "RECOVERED: Docker daemon restarted successfully"
|
echo "RECOVERED: Docker daemon restarted successfully"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# 1b. Clean up stuck containers — containers in "removing" or "stopping" state
|
||||||
|
# for too long cause "cannot kill container: did not receive an exit event"
|
||||||
|
# errors in molecule destroy phases. Force-remove them so subsequent CI jobs
|
||||||
|
# don't inherit the stuck state.
|
||||||
|
stuck_containers=$(timeout 10 docker ps -a --filter "status=removing" --filter "status=stopping" --format '{% raw %}{{.ID}}{% endraw %}' 2>/dev/null || true)
|
||||||
|
if [[ -n "$stuck_containers" ]]; then
|
||||||
|
echo "WARN: Found stuck containers (removing/stopping), force-cleaning"
|
||||||
|
echo "$stuck_containers" | xargs -r docker rm -f 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
|
||||||
# 2. Check gitea-runner service is active
|
# 2. Check gitea-runner service is active
|
||||||
runner_state=$(systemctl --user is-active gitea-runner.service 2>/dev/null || true)
|
runner_state=$(systemctl --user is-active gitea-runner.service 2>/dev/null || true)
|
||||||
if [[ "$runner_state" != "active" ]]; then
|
if [[ "$runner_state" != "active" ]]; then
|
||||||
@@ -41,6 +52,9 @@ if [[ "$disk_pct" -ge {{ gitea_runner_healthcheck_disk_threshold }} ]]; then
|
|||||||
docker volume prune -f --filter "label={{ gitea_runner_prune_label }}" || true
|
docker volume prune -f --filter "label={{ gitea_runner_prune_label }}" || true
|
||||||
# Only prune dangling (untagged) images — keep tagged runner images (ci-full, ci-quality)
|
# Only prune dangling (untagged) images — keep tagged runner images (ci-full, ci-quality)
|
||||||
docker image prune -f || true
|
docker image prune -f || true
|
||||||
|
# Clean up stopped containers and dangling networks that accumulate from failed jobs
|
||||||
|
docker container prune -f || true
|
||||||
|
docker network prune -f || true
|
||||||
disk_pct=$(df -P / | awk 'NR==2 {gsub(/%/, "", $5); print $5}')
|
disk_pct=$(df -P / | awk 'NR==2 {gsub(/%/, "", $5); print $5}')
|
||||||
echo "INFO: Disk usage after prune: ${disk_pct}%"
|
echo "INFO: Disk usage after prune: ${disk_pct}%"
|
||||||
fi
|
fi
|
||||||
|
|||||||
+6
-6
@@ -8,12 +8,12 @@ Each runner runs in an isolated **rootless Docker** environment under a dedicate
|
|||||||
|
|
||||||
[](https://git.oblachno.oblachno.fyi/oblachno-oss/grm/actions)
|
[](https://git.oblachno.oblachno.fyi/oblachno-oss/grm/actions)
|
||||||
[](https://git.oblachno.oblachno.fyi/oblachno-oss/grm/src/branch/master/LICENSE)
|
[](https://git.oblachno.oblachno.fyi/oblachno-oss/grm/src/branch/master/LICENSE)
|
||||||
[](https://git.oblachno.oblachno.fyi/oblachno-oss/grm/actions)
|
[](https://git.oblachno.oblachno.fyi/oblachno-oss/grm/actions)
|
||||||
[](https://git.oblachno.oblachno.fyi/oblachno-oss/grm/actions)
|
[](https://git.oblachno.oblachno.fyi/oblachno-oss/grm/actions)
|
||||||
[](https://git.oblachno.oblachno.fyi/oblachno-oss/grm/wiki)
|
[](https://git.oblachno.oblachno.fyi/oblachno-oss/grm/wiki)
|
||||||
[](https://git.oblachno.oblachno.fyi/oblachno-oss/grm/actions)
|
[](https://git.oblachno.oblachno.fyi/oblachno-oss/grm/actions)
|
||||||
[](https://git.oblachno.oblachno.fyi/oblachno-oss/grm/releases)
|
[](https://git.oblachno.oblachno.fyi/oblachno-oss/grm/releases)
|
||||||
[](https://www.python.org/downloads/)
|
[](https://www.python.org/downloads/)
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -1,3 +1,3 @@
|
|||||||
"""Gitea Runner Manager — lean CLI for managing Gitea Actions runners."""
|
"""Gitea Runner Manager — lean CLI for managing Gitea Actions runners."""
|
||||||
|
|
||||||
__version__ = "0.18.3"
|
__version__ = "0.18.6"
|
||||||
|
|||||||
Reference in New Issue
Block a user