Compare commits
5
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
97e7687e25 | ||
|
|
f672da1753 | ||
|
|
d6549de2e0 | ||
|
|
dc4bb0936d | ||
|
|
52477e558a |
@@ -295,6 +295,49 @@ jobs:
|
||||
--checklist-confirmed \
|
||||
--checklist-categories 1,2,3,4,5,6,7,8,9,10,11,12,13 \
|
||||
--body "Auto-approved: all CI checks passed (validate, molecule-tests)."
|
||||
- name: Wait for molecule tests to complete
|
||||
env:
|
||||
CI_GITEA_API_TOKEN: ${{ secrets.CI_GITEA_API_TOKEN }}
|
||||
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
|
||||
run: |
|
||||
. .venv/bin/activate 2>/dev/null || true
|
||||
# Poll commit status until all required checks pass or fail
|
||||
MAX_WAIT=600 # 10 minutes
|
||||
ELAPSED=0
|
||||
while [ $ELAPSED -lt $MAX_WAIT ]; do
|
||||
STATUS=$(curl -s -H "Authorization: token $CI_GITEA_API_TOKEN" \
|
||||
"https://git.oblachno.oblachno.fyi/api/v1/repos/${{ github.repository }}/commits/$HEAD_SHA/status" \
|
||||
| python3 -c "
|
||||
import sys,json
|
||||
d=json.load(sys.stdin)
|
||||
statuses={s['context']:s['status'] for s in d.get('statuses',[])}
|
||||
# Check if all molecule-tests contexts are terminal (success/failure)
|
||||
mol_contexts=[k for k in statuses if 'molecule-tests' in k]
|
||||
if not mol_contexts:
|
||||
print('pending')
|
||||
elif all(statuses[k] in ('success','failure') for k in mol_contexts):
|
||||
if any(statuses[k]=='failure' for k in mol_contexts):
|
||||
print('failure')
|
||||
else:
|
||||
print('success')
|
||||
else:
|
||||
print('pending')
|
||||
")
|
||||
echo "Molecule tests status: $STATUS (elapsed: ${ELAPSED}s)"
|
||||
if [ "$STATUS" = "success" ]; then
|
||||
echo "All molecule tests passed."
|
||||
break
|
||||
elif [ "$STATUS" = "failure" ]; then
|
||||
echo "ERROR: Molecule tests failed. Aborting auto-merge."
|
||||
exit 1
|
||||
fi
|
||||
sleep 30
|
||||
ELAPSED=$((ELAPSED + 30))
|
||||
done
|
||||
if [ $ELAPSED -ge $MAX_WAIT ]; then
|
||||
echo "ERROR: Timed out waiting for molecule tests."
|
||||
exit 1
|
||||
fi
|
||||
- name: Squash merge with task ID
|
||||
env:
|
||||
CI_GITEA_API_TOKEN: ${{ secrets.CI_GITEA_API_TOKEN }}
|
||||
|
||||
@@ -2,6 +2,18 @@
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
## [0.21.1] - 2026-08-24
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Use runuser for systemctl --user tasks in gitea_runner role
|
||||
|
||||
## [0.21.0] - 2026-08-09
|
||||
|
||||
### Features
|
||||
|
||||
- *(healthcheck)* Add two-tier disk prune with critical threshold
|
||||
|
||||
## [0.20.0] - 2026-08-09
|
||||
|
||||
### Features
|
||||
|
||||
@@ -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/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/wiki)
|
||||
[](https://git.oblachno.oblachno.fyi/oblachno-oss/grm/actions)
|
||||
[](https://git.oblachno.oblachno.fyi/oblachno-oss/grm/releases)
|
||||
[](https://www.python.org/downloads/)
|
||||
[](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/actions)
|
||||
[](https://git.oblachno.oblachno.fyi/oblachno-oss/grm/releases)
|
||||
[](https://www.python.org/downloads/)
|
||||
|
||||
## Why GRM?
|
||||
|
||||
|
||||
@@ -36,10 +36,9 @@ gitea_runner_prune_label: "gitea-runner=true"
|
||||
gitea_runner_service_restart_sec: "5"
|
||||
|
||||
# Health check configuration
|
||||
# 2min interval — catches hung daemons before multiple CI jobs fail between
|
||||
# checks. The 1min interval caused excessive pruning which removed cached
|
||||
# images, forcing all 6 parallel slots to re-pull simultaneously and
|
||||
# actually increasing disk pressure.
|
||||
# 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_disk_threshold: 70
|
||||
@@ -48,10 +47,6 @@ gitea_runner_healthcheck_disk_threshold: 70
|
||||
# This removes all stopped containers and unused images regardless of age.
|
||||
# At 75%+, molecule containers fail with "container is not running" because
|
||||
# overlay2 runs out of space under parallel DinD load.
|
||||
# IMPORTANT: keep at 75 (not lower) — the host disk normally sits at ~74%.
|
||||
# Lowering to 70 triggers full prune every cycle, wiping cached images and
|
||||
# forcing all parallel slots to re-pull simultaneously, which increases
|
||||
# disk pressure rather than reducing it.
|
||||
gitea_runner_healthcheck_disk_critical: 75
|
||||
gitea_runner_healthcheck_script_path: "{{ gitea_runner_config_dir }}/healthcheck.sh"
|
||||
|
||||
@@ -81,10 +76,6 @@ 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
|
||||
# Log file size limits — under parallel DinD load, container logs can fill
|
||||
# disk and cause the daemon to become unresponsive. Limit log size per container.
|
||||
gitea_runner_docker_max_log_size: "10m"
|
||||
gitea_runner_docker_max_log_files: 3
|
||||
|
||||
# Admin token for runner deregistration via Gitea API.
|
||||
# If not set, falls back to registration_token (which likely lacks admin scope).
|
||||
|
||||
@@ -10,7 +10,3 @@
|
||||
- ansible_facts is defined
|
||||
- ansible_facts['service_mgr'] | default('') == 'systemd'
|
||||
- gitea_runner_docker_rootless_setup
|
||||
|
||||
- name: Reload systemd user daemon
|
||||
ansible.builtin.systemd:
|
||||
daemon_reload: true
|
||||
|
||||
@@ -40,24 +40,22 @@
|
||||
mode: "0644"
|
||||
|
||||
- name: Reload systemd user daemon for healthcheck timer
|
||||
ansible.builtin.command: systemctl --user daemon-reload
|
||||
become: true
|
||||
become_user: "{{ gitea_runner_service_user }}"
|
||||
environment:
|
||||
XDG_RUNTIME_DIR: "/run/user/{{ gitea_runner_uid }}"
|
||||
DBUS_SESSION_BUS_ADDRESS: "unix:path=/run/user/{{ gitea_runner_uid | default(0) }}/bus"
|
||||
ansible.builtin.command: >
|
||||
runuser -u {{ gitea_runner_service_user }} --
|
||||
env XDG_RUNTIME_DIR=/run/user/{{ gitea_runner_uid }}
|
||||
DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/{{ gitea_runner_uid | default(0) }}/bus
|
||||
systemctl --user daemon-reload
|
||||
changed_when: true
|
||||
when:
|
||||
- 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
|
||||
become: true
|
||||
become_user: "{{ gitea_runner_service_user }}"
|
||||
environment:
|
||||
XDG_RUNTIME_DIR: "/run/user/{{ gitea_runner_uid }}"
|
||||
DBUS_SESSION_BUS_ADDRESS: "unix:path=/run/user/{{ gitea_runner_uid | default(0) }}/bus"
|
||||
ansible.builtin.command: >
|
||||
runuser -u {{ gitea_runner_service_user }} --
|
||||
env XDG_RUNTIME_DIR=/run/user/{{ gitea_runner_uid }}
|
||||
DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/{{ gitea_runner_uid | default(0) }}/bus
|
||||
systemctl --user enable --now runner-healthcheck.timer
|
||||
changed_when: true
|
||||
when:
|
||||
- gitea_runner_systemd_available.stat.exists
|
||||
|
||||
@@ -13,9 +13,9 @@
|
||||
- name: Include validation
|
||||
ansible.builtin.include_tasks: validate.yml
|
||||
|
||||
- name: Include service setup
|
||||
ansible.builtin.include_tasks: service.yml
|
||||
|
||||
- name: Include registration
|
||||
ansible.builtin.include_tasks: register.yml
|
||||
when: not gitea_runner_skip_registration
|
||||
|
||||
- name: Include service setup
|
||||
ansible.builtin.include_tasks: service.yml
|
||||
|
||||
@@ -18,12 +18,11 @@
|
||||
register: gitea_runner_prune_timer
|
||||
|
||||
- name: Reload systemd user daemon for prune timer
|
||||
ansible.builtin.command: systemctl --user daemon-reload
|
||||
become: true
|
||||
become_user: "{{ gitea_runner_service_user }}"
|
||||
environment:
|
||||
XDG_RUNTIME_DIR: "/run/user/{{ gitea_runner_uid }}"
|
||||
DBUS_SESSION_BUS_ADDRESS: "unix:path=/run/user/{{ gitea_runner_uid | default(0) }}/bus"
|
||||
ansible.builtin.command: >
|
||||
runuser -u {{ gitea_runner_service_user }} --
|
||||
env XDG_RUNTIME_DIR=/run/user/{{ gitea_runner_uid }}
|
||||
DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/{{ gitea_runner_uid | default(0) }}/bus
|
||||
systemctl --user daemon-reload
|
||||
changed_when: true
|
||||
when:
|
||||
- gitea_runner_systemd_available.stat.exists
|
||||
@@ -31,12 +30,11 @@
|
||||
- gitea_runner_prune_service is changed or gitea_runner_prune_timer is changed
|
||||
|
||||
- name: Enable and start docker-prune user timer
|
||||
ansible.builtin.command: systemctl --user enable --now docker-prune.timer
|
||||
become: true
|
||||
become_user: "{{ gitea_runner_service_user }}"
|
||||
environment:
|
||||
XDG_RUNTIME_DIR: "/run/user/{{ gitea_runner_uid }}"
|
||||
DBUS_SESSION_BUS_ADDRESS: "unix:path=/run/user/{{ gitea_runner_uid | default(0) }}/bus"
|
||||
ansible.builtin.command: >
|
||||
runuser -u {{ gitea_runner_service_user }} --
|
||||
env XDG_RUNTIME_DIR=/run/user/{{ gitea_runner_uid }}
|
||||
DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/{{ gitea_runner_uid | default(0) }}/bus
|
||||
systemctl --user enable --now docker-prune.timer
|
||||
changed_when: true
|
||||
when:
|
||||
- gitea_runner_systemd_available.stat.exists
|
||||
|
||||
@@ -44,13 +44,16 @@
|
||||
('already exists' not in gitea_runner_register_output.stdout | default(''))
|
||||
timeout: 60
|
||||
|
||||
- name: Ensure runner service is running after registration
|
||||
ansible.builtin.command: systemctl --user start gitea-runner
|
||||
become: true
|
||||
become_user: "{{ gitea_runner_service_user }}"
|
||||
environment:
|
||||
XDG_RUNTIME_DIR: "/run/user/{{ gitea_runner_uid }}"
|
||||
DBUS_SESSION_BUS_ADDRESS: "unix:path=/run/user/{{ gitea_runner_uid | default(0) }}/bus"
|
||||
# Note: service start is handled by service.yml (included after register.yml
|
||||
# in install_runner.yml). Starting here fails because the systemd unit file
|
||||
# has not been created yet.
|
||||
|
||||
- name: Restart runner service after (re-)registration
|
||||
ansible.builtin.command: >
|
||||
runuser -u {{ gitea_runner_service_user }} --
|
||||
env XDG_RUNTIME_DIR=/run/user/{{ gitea_runner_uid }}
|
||||
DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/{{ gitea_runner_uid | default(0) }}/bus
|
||||
systemctl --user restart gitea-runner
|
||||
changed_when: true
|
||||
when:
|
||||
- gitea_runner_systemd_available.stat.exists
|
||||
|
||||
@@ -143,6 +143,10 @@
|
||||
{% if gitea_runner_docker_rootless_net_driver == 'pasta' %}
|
||||
Environment="DOCKERD_ROOTLESS_ROOTLESSKIT_FLAGS=--ipv6"
|
||||
{% endif %}
|
||||
Restart=always
|
||||
RestartSec=5
|
||||
StartLimitIntervalSec=300
|
||||
StartLimitBurst=10
|
||||
mode: "0644"
|
||||
owner: "{{ gitea_runner_service_user }}"
|
||||
group: "{{ gitea_runner_service_user }}"
|
||||
@@ -180,10 +184,6 @@
|
||||
"features": {
|
||||
"containerd-snapshotter": false
|
||||
},
|
||||
"log-opts": {
|
||||
"max-size": "{{ gitea_runner_docker_max_log_size }}",
|
||||
"max-file": "{{ gitea_runner_docker_max_log_files }}"
|
||||
},
|
||||
{% if gitea_runner_docker_rootless_net_driver == 'pasta' %}
|
||||
"ipv6": true,
|
||||
"ip6tables": true,
|
||||
@@ -216,22 +216,20 @@
|
||||
- not gitea_runner_rootless_docker_check.stat.exists
|
||||
|
||||
- name: Start rootless Docker daemon (systemd user service)
|
||||
ansible.builtin.command: systemctl --user start docker
|
||||
become: true
|
||||
become_user: "{{ gitea_runner_service_user }}"
|
||||
environment:
|
||||
XDG_RUNTIME_DIR: "/run/user/{{ gitea_runner_uid }}"
|
||||
DBUS_SESSION_BUS_ADDRESS: "unix:path=/run/user/{{ gitea_runner_uid }}/bus"
|
||||
ansible.builtin.command: >
|
||||
runuser -u {{ gitea_runner_service_user }} --
|
||||
env XDG_RUNTIME_DIR=/run/user/{{ gitea_runner_uid }}
|
||||
DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/{{ gitea_runner_uid | default(0) }}/bus
|
||||
systemctl --user start docker
|
||||
changed_when: true
|
||||
when: gitea_runner_docker_rootless_setup
|
||||
|
||||
- name: Enable rootless Docker daemon (systemd user service)
|
||||
ansible.builtin.command: systemctl --user enable docker
|
||||
become: true
|
||||
become_user: "{{ gitea_runner_service_user }}"
|
||||
environment:
|
||||
XDG_RUNTIME_DIR: "/run/user/{{ gitea_runner_uid }}"
|
||||
DBUS_SESSION_BUS_ADDRESS: "unix:path=/run/user/{{ gitea_runner_uid }}/bus"
|
||||
ansible.builtin.command: >
|
||||
runuser -u {{ gitea_runner_service_user }} --
|
||||
env XDG_RUNTIME_DIR=/run/user/{{ gitea_runner_uid }}
|
||||
DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/{{ gitea_runner_uid | default(0) }}/bus
|
||||
systemctl --user enable docker
|
||||
changed_when: true
|
||||
when: gitea_runner_docker_rootless_setup
|
||||
|
||||
@@ -254,6 +252,10 @@
|
||||
{% if gitea_runner_docker_rootless_net_driver == 'pasta' %}
|
||||
Environment="DOCKERD_ROOTLESS_ROOTLESSKIT_FLAGS=--ipv6"
|
||||
{% endif %}
|
||||
Restart=always
|
||||
RestartSec=5
|
||||
StartLimitIntervalSec=300
|
||||
StartLimitBurst=10
|
||||
mode: "0644"
|
||||
owner: "{{ gitea_runner_service_user }}"
|
||||
group: "{{ gitea_runner_service_user }}"
|
||||
@@ -261,12 +263,11 @@
|
||||
when: gitea_runner_docker_rootless_setup
|
||||
|
||||
- name: Reload systemd user daemon if network config changed
|
||||
ansible.builtin.command: systemctl --user daemon-reload
|
||||
become: true
|
||||
become_user: "{{ gitea_runner_service_user }}"
|
||||
environment:
|
||||
XDG_RUNTIME_DIR: "/run/user/{{ gitea_runner_uid }}"
|
||||
DBUS_SESSION_BUS_ADDRESS: "unix:path=/run/user/{{ gitea_runner_uid }}/bus"
|
||||
ansible.builtin.command: >
|
||||
runuser -u {{ gitea_runner_service_user }} --
|
||||
env XDG_RUNTIME_DIR=/run/user/{{ gitea_runner_uid }}
|
||||
DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/{{ gitea_runner_uid | default(0) }}/bus
|
||||
systemctl --user daemon-reload
|
||||
changed_when: true
|
||||
when:
|
||||
- gitea_runner_docker_rootless_setup
|
||||
@@ -287,10 +288,6 @@
|
||||
"features": {
|
||||
"containerd-snapshotter": false
|
||||
},
|
||||
"log-opts": {
|
||||
"max-size": "{{ gitea_runner_docker_max_log_size }}",
|
||||
"max-file": "{{ gitea_runner_docker_max_log_files }}"
|
||||
},
|
||||
{% if gitea_runner_docker_rootless_net_driver == 'pasta' %}
|
||||
"ipv6": true,
|
||||
"ip6tables": true,
|
||||
@@ -308,12 +305,11 @@
|
||||
when: gitea_runner_docker_rootless_setup
|
||||
|
||||
- name: Restart rootless Docker if config changed
|
||||
ansible.builtin.command: systemctl --user restart docker
|
||||
become: true
|
||||
become_user: "{{ gitea_runner_service_user }}"
|
||||
environment:
|
||||
XDG_RUNTIME_DIR: "/run/user/{{ gitea_runner_uid }}"
|
||||
DBUS_SESSION_BUS_ADDRESS: "unix:path=/run/user/{{ gitea_runner_uid }}/bus"
|
||||
ansible.builtin.command: >
|
||||
runuser -u {{ gitea_runner_service_user }} --
|
||||
env XDG_RUNTIME_DIR=/run/user/{{ gitea_runner_uid }}
|
||||
DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/{{ gitea_runner_uid | default(0) }}/bus
|
||||
systemctl --user restart docker
|
||||
changed_when: true
|
||||
when:
|
||||
- gitea_runner_docker_rootless_setup
|
||||
|
||||
@@ -9,12 +9,11 @@
|
||||
register: gitea_runner_service_file
|
||||
|
||||
- name: Reload systemd user daemon
|
||||
ansible.builtin.command: systemctl --user daemon-reload
|
||||
become: true
|
||||
become_user: "{{ gitea_runner_service_user }}"
|
||||
environment:
|
||||
XDG_RUNTIME_DIR: "/run/user/{{ gitea_runner_uid }}"
|
||||
DBUS_SESSION_BUS_ADDRESS: "unix:path=/run/user/{{ gitea_runner_uid | default(0) }}/bus"
|
||||
ansible.builtin.command: >
|
||||
runuser -u {{ gitea_runner_service_user }} --
|
||||
env XDG_RUNTIME_DIR=/run/user/{{ gitea_runner_uid }}
|
||||
DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/{{ gitea_runner_uid | default(0) }}/bus
|
||||
systemctl --user daemon-reload
|
||||
changed_when: true
|
||||
when:
|
||||
- gitea_runner_systemd_available.stat.exists
|
||||
@@ -22,12 +21,11 @@
|
||||
- gitea_runner_service_file is changed
|
||||
|
||||
- name: Restart gitea-runner if service file changed
|
||||
ansible.builtin.command: systemctl --user restart gitea-runner
|
||||
become: true
|
||||
become_user: "{{ gitea_runner_service_user }}"
|
||||
environment:
|
||||
XDG_RUNTIME_DIR: "/run/user/{{ gitea_runner_uid }}"
|
||||
DBUS_SESSION_BUS_ADDRESS: "unix:path=/run/user/{{ gitea_runner_uid | default(0) }}/bus"
|
||||
ansible.builtin.command: >
|
||||
runuser -u {{ gitea_runner_service_user }} --
|
||||
env XDG_RUNTIME_DIR=/run/user/{{ gitea_runner_uid }}
|
||||
DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/{{ gitea_runner_uid | default(0) }}/bus
|
||||
systemctl --user restart gitea-runner
|
||||
changed_when: true
|
||||
when:
|
||||
- gitea_runner_systemd_available.stat.exists
|
||||
@@ -35,12 +33,11 @@
|
||||
- gitea_runner_service_file is changed
|
||||
|
||||
- name: Enable and start gitea-runner user service
|
||||
ansible.builtin.command: systemctl --user enable --now gitea-runner
|
||||
become: true
|
||||
become_user: "{{ gitea_runner_service_user }}"
|
||||
environment:
|
||||
XDG_RUNTIME_DIR: "/run/user/{{ gitea_runner_uid }}"
|
||||
DBUS_SESSION_BUS_ADDRESS: "unix:path=/run/user/{{ gitea_runner_uid | default(0) }}/bus"
|
||||
ansible.builtin.command: >
|
||||
runuser -u {{ gitea_runner_service_user }} --
|
||||
env XDG_RUNTIME_DIR=/run/user/{{ gitea_runner_uid }}
|
||||
DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/{{ gitea_runner_uid | default(0) }}/bus
|
||||
systemctl --user enable --now gitea-runner
|
||||
changed_when: true
|
||||
when:
|
||||
- gitea_runner_systemd_available.stat.exists
|
||||
|
||||
@@ -69,31 +69,3 @@
|
||||
owner: "{{ gitea_runner_service_user }}"
|
||||
group: "{{ gitea_runner_service_user }}"
|
||||
mode: "0755"
|
||||
|
||||
- name: Disable systemd-oomd memory pressure kill for runner user
|
||||
when: gitea_runner_systemd_available.stat.exists
|
||||
block:
|
||||
- name: Ensure user service override directory exists
|
||||
ansible.builtin.file:
|
||||
path: "/etc/systemd/system/user@{{ gitea_runner_uid }}.service.d"
|
||||
state: directory
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0755"
|
||||
|
||||
- name: Disable ManagedOOMMemoryPressure for runner user
|
||||
ansible.builtin.copy:
|
||||
content: |
|
||||
[Service]
|
||||
ManagedOOMMemoryPressure=auto
|
||||
ManagedOOMMemoryPressureLimit=100%
|
||||
OOMScoreAdjust=-500
|
||||
dest: "/etc/systemd/system/user@{{ gitea_runner_uid }}.service.d/oomd-override.conf"
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
notify: Reload systemd user daemon
|
||||
|
||||
- name: Reload systemd daemon for oomd override
|
||||
ansible.builtin.systemd:
|
||||
daemon_reload: true
|
||||
|
||||
@@ -223,10 +223,6 @@ fi
|
||||
|
||||
# 3. Check disk space — prune aggressively if below threshold
|
||||
disk_pct=$(df -P / | awk 'NR==2 {gsub(/%/, "", $5); print $5}')
|
||||
# Debug: log image count and sizes before pruning decisions
|
||||
img_count=$(timeout 10 docker images -q 2>/dev/null | wc -l || echo "?")
|
||||
img_size=$(timeout 10 docker system df --format '{% raw %}{{.Type}} {{.Size}} {{.Reclaimable}}{% endraw %}' 2>/dev/null | grep "Images" || echo "unknown")
|
||||
echo "INFO: disk=${disk_pct}% images=${img_count} | ${img_size}"
|
||||
if [[ "$disk_pct" -ge {{ gitea_runner_healthcheck_disk_critical }} ]]; then
|
||||
echo "CRITICAL: Disk usage at ${disk_pct}% (>= {{ gitea_runner_healthcheck_disk_critical }}%), full prune"
|
||||
# Critical level: remove ALL stopped containers (no age filter) and ALL
|
||||
@@ -238,10 +234,7 @@ if [[ "$disk_pct" -ge {{ gitea_runner_healthcheck_disk_critical }} ]]; then
|
||||
| awk '{print $1}' \
|
||||
| xargs -r docker rm -f 2>/dev/null || true
|
||||
docker system prune -af --volumes || true
|
||||
# Prune networks older than 1 hour only — removing ALL networks can
|
||||
# race with Gitea Actions jobs that are mid-startup (network created
|
||||
# but container not yet attached), causing "network not found" errors.
|
||||
docker network prune -f --filter "until=1h" || true
|
||||
docker network prune -f || true
|
||||
docker builder prune -af || true
|
||||
disk_pct=$(df -P / | awk 'NR==2 {gsub(/%/, "", $5); print $5}')
|
||||
echo "INFO: Disk usage after full prune: ${disk_pct}%"
|
||||
@@ -258,12 +251,9 @@ elif [[ "$disk_pct" -ge {{ gitea_runner_healthcheck_disk_threshold }} ]]; then
|
||||
| grep -E '(hour|day|week|month|year)s? ago' \
|
||||
| awk '{print $1}' \
|
||||
| xargs -r docker rm -f 2>/dev/null || true
|
||||
# Prune containers and volumes older than 1h. Do NOT prune images here —
|
||||
# the CI runner image (ci-full:latest, ~2.3GB) is unused between jobs and
|
||||
# would be removed by image prune, forcing a re-pull on every CI job
|
||||
# (84+ seconds per pull, and the pull can fail silently causing
|
||||
# "No such image" errors). Images are only pruned at the critical
|
||||
# threshold (75%) when disk is genuinely full.
|
||||
# Prune images and containers older than 1h (until filter is NOT
|
||||
# supported with --volumes, so prune volumes separately without a filter).
|
||||
docker image prune -af --filter "until=1h" 2>/dev/null || true
|
||||
docker container prune -f --filter "until=1h" 2>/dev/null || true
|
||||
docker volume prune -f 2>/dev/null || true
|
||||
# Prune networks older than 1 hour to avoid removing networks that
|
||||
|
||||
+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/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/wiki)
|
||||
[](https://git.oblachno.oblachno.fyi/oblachno-oss/grm/actions)
|
||||
[](https://git.oblachno.oblachno.fyi/oblachno-oss/grm/releases)
|
||||
[](https://www.python.org/downloads/)
|
||||
[](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/actions)
|
||||
[](https://git.oblachno.oblachno.fyi/oblachno-oss/grm/releases)
|
||||
[](https://www.python.org/downloads/)
|
||||
|
||||
## Overview
|
||||
|
||||
|
||||
+2
-2
@@ -36,7 +36,7 @@ ci = [
|
||||
"build==1.5.1",
|
||||
"twine==6.2.0",
|
||||
# Reusable CI/CD and dev tools (auto-merge, pr-review, pre-push checks, etc.)
|
||||
"devx @ git+https://git.oblachno.oblachno.fyi/oblachno-oss/devx.git@v0.50.0",
|
||||
"devx @ git+https://git.oblachno.oblachno.fyi/oblachno-oss/devx.git@v0.50.1",
|
||||
]
|
||||
# Lint and type-checking tools (validate job)
|
||||
lint = [
|
||||
@@ -56,7 +56,7 @@ molecule = [
|
||||
dev = [
|
||||
"grm[ci,lint,molecule]",
|
||||
# Reusable CI/CD and dev tools (pre-push hooks, create-task, create-pr)
|
||||
"devx @ git+https://git.oblachno.oblachno.fyi/oblachno-oss/devx.git@v0.50.0",
|
||||
"devx @ git+https://git.oblachno.oblachno.fyi/oblachno-oss/devx.git@v0.50.1",
|
||||
# Non-Python dev dependency: checkmake (Makefile linter)
|
||||
# Install via: go install github.com/checkmake/checkmake/cmd/checkmake@latest
|
||||
]
|
||||
|
||||
+1
-1
@@ -1,3 +1,3 @@
|
||||
"""Gitea Runner Manager — lean CLI for managing Gitea Actions runners."""
|
||||
|
||||
__version__ = "0.20.0"
|
||||
__version__ = "0.21.1"
|
||||
|
||||
Reference in New Issue
Block a user