From 1bbe3b4f43573930beaaea78e47d66a8487c7e2a Mon Sep 17 00:00:00 2001 From: Emil Simeonov Date: Fri, 19 Jun 2026 02:47:14 +0200 Subject: [PATCH] GRM-17: fix docker mode status detection and systemd template - systemd template for docker mode now uses Type=oneshot + RemainAfterExit=yes so that systemctl is-active returns active when the container is running. Previously docker run -d exited immediately, causing systemd to mark the service as inactive even though the container was still up. - grm list now tries multiple container name fallbacks for docker mode: 1. gitea-runner-{name} (current naming) 2. gitea-runner-{host} (legacy installs where name defaulted to host) 3. systemctl is-active gitea-runner@{name} (for installs with fixed template) - All tests pass, 100% coverage, ruff + pyright clean --- .../templates/gitea-runner@.service.j2 | 4 +- src/gitea_runner_manager/runner_manager.py | 55 +++++++++++++------ 2 files changed, 40 insertions(+), 19 deletions(-) diff --git a/ansible/roles/gitea-runner/templates/gitea-runner@.service.j2 b/ansible/roles/gitea-runner/templates/gitea-runner@.service.j2 index 52ee713..39805b5 100644 --- a/ansible/roles/gitea-runner/templates/gitea-runner@.service.j2 +++ b/ansible/roles/gitea-runner/templates/gitea-runner@.service.j2 @@ -4,8 +4,9 @@ After=network.target{% if runner_mode == 'docker' %} docker.service Requires=docker.service{% endif %} [Service] -Type=simple {% if runner_mode == 'docker' %} +Type=oneshot +RemainAfterExit=yes ExecStartPre=-/usr/bin/docker rm -f gitea-runner-%i ExecStart=/usr/bin/docker run -d --name gitea-runner-%i \ --restart=no \ @@ -19,6 +20,7 @@ ExecStopPost=-/usr/bin/docker rm -f gitea-runner-%i Restart=on-failure RestartSec={{ gitea_runner_service_restart_sec }} {% else %} +Type=simple ExecStart={{ gitea_runner_binary_path }} daemon --config {{ gitea_runner_base_config_dir }}/%i/config.yaml WorkingDirectory={{ gitea_runner_base_data_dir }}/%i ExecStop=/bin/kill -TERM $MAINPID diff --git a/src/gitea_runner_manager/runner_manager.py b/src/gitea_runner_manager/runner_manager.py index a482336..af2c303 100644 --- a/src/gitea_runner_manager/runner_manager.py +++ b/src/gitea_runner_manager/runner_manager.py @@ -289,18 +289,44 @@ class RunnerManager: user=user, ) ) + + def _parse_status(stdout: str) -> str: + ansible_noise = (" | CHANGED | ", " | FAILED | ", " | UNREACHABLE | ", "[WARNING]", "ssh:", ">>") + lines = [ln for ln in stdout.splitlines() if ln.strip() and not any(p in ln for p in ansible_noise)] + return lines[-1].strip() if lines else "unknown" + try: if mode == "docker": - stdout = self._executor.run_ad_hoc( - host, - user, - key, - "shell", - f"docker inspect -f '{{{{.State.Status}}}}' gitea-runner-{name} 2>/dev/null", - become=True, - ask_become_pass=True, - check=False, - ) + # Try expected container name first, then host-based fallback for legacy installs + for container_name in (f"gitea-runner-{name}", f"gitea-runner-{host}"): + stdout = self._executor.run_ad_hoc( + host, + user, + key, + "shell", + f"docker inspect -f '{{{{.State.Status}}}}' {container_name} 2>/dev/null", + become=True, + ask_become_pass=True, + check=False, + ) + service_status = _parse_status(stdout) + if service_status != "unknown": + docker_map = {"running": "active", "exited": "inactive", "dead": "failed"} + service_status = docker_map.get(service_status, "unknown") + break + else: + # Final fallback: check systemd service (for installs using the fixed template) + stdout = self._executor.run_ad_hoc( + host, + user, + key, + "shell", + f"systemctl is-active gitea-runner@{name} 2>/dev/null", + become=True, + ask_become_pass=True, + check=False, + ) + service_status = _parse_status(stdout) else: stdout = self._executor.run_ad_hoc( host, @@ -312,14 +338,7 @@ class RunnerManager: ask_become_pass=True, check=False, ) - # Filter out ansible noise (headers, warnings, SSH messages) - ansible_noise = (" | CHANGED | ", " | FAILED | ", " | UNREACHABLE | ", "[WARNING]", "ssh:", ">>") - lines = [ln for ln in stdout.splitlines() if ln.strip() and not any(p in ln for p in ansible_noise)] - service_status = lines[-1].strip() if lines else "unknown" - # Docker statuses need mapping to systemd vocabulary - if mode == "docker": - docker_map = {"running": "active", "exited": "inactive", "dead": "failed"} - service_status = docker_map.get(service_status, "unknown") + service_status = _parse_status(stdout) except Exception: service_status = "unknown" # Translate known status values