diff --git a/pmoves/integrations/github-runners/compose/lane_hosts.json b/pmoves/integrations/github-runners/compose/lane_hosts.json index 6036f16189..11fb1afd1d 100644 --- a/pmoves/integrations/github-runners/compose/lane_hosts.json +++ b/pmoves/integrations/github-runners/compose/lane_hosts.json @@ -1,3 +1,29 @@ { - "lane_hosts": {} + "lane_hosts": { + "self-hosted,Linux,X64": { + "host": "vps / ai-lab", + "runner_name": "pmoves-vps-runner", + "registration_script": "integrations/github-runners/compose/register-runner.sh" + }, + "self-hosted,ai-lab,gpu": { + "host": "ai-lab", + "runner_name": "pmoves-ai-lab-runner", + "registration_script": "integrations/github-runners/compose/register-runner.sh" + }, + "self-hosted,ai-lab": { + "host": "ai-lab", + "runner_name": "pmoves-ai-lab-runner", + "registration_script": "integrations/github-runners/compose/register-runner.sh" + }, + "self-hosted,vps,Linux,X64": { + "host": "vps", + "runner_name": "pmoves-vps-runner", + "registration_script": "integrations/github-runners/compose/register-runner.sh" + }, + "self-hosted,gpu": { + "host": "ai-lab", + "runner_name": "pmoves-ai-lab-runner", + "registration_script": "integrations/github-runners/compose/register-runner.sh" + } + } } diff --git a/pmoves/integrations/github-runners/compose/runner_phase_policy.json b/pmoves/integrations/github-runners/compose/runner_phase_policy.json index 54c7de3c05..55a6854545 100644 --- a/pmoves/integrations/github-runners/compose/runner_phase_policy.json +++ b/pmoves/integrations/github-runners/compose/runner_phase_policy.json @@ -1,4 +1,11 @@ { - "default_phase": "", - "phases": {} + "default_phase": "local-certification", + "phases": { + "local-certification": { + "description": "Both runners on local Docker containers for development/CI", + "required_online": ["self-hosted,Linux,X64", "self-hosted,ai-lab,gpu"], + "required_offline": [], + "min_runners": 2 + } + } } diff --git a/pmoves/tools/local_cert_runners.py b/pmoves/tools/local_cert_runners.py index 0963b53b7d..0f061344fc 100644 --- a/pmoves/tools/local_cert_runners.py +++ b/pmoves/tools/local_cert_runners.py @@ -83,31 +83,66 @@ def docker_rm(container_name: str) -> None: run_cmd(["docker", "rm", "-f", container_name], check=False) +LOKI_URL = os.getenv("LOKI_URL", "http://localhost:3100/loki/api/v1/push") + +# Default resource limits per lane (overridable via env) +LANE_RESOURCES: dict[str, dict[str, str]] = { + "ai-lab": { + "cpus": os.getenv("RUNNER_AI_LAB_CPUS", "4"), + "memory": os.getenv("RUNNER_AI_LAB_MEMORY", "8g"), + "gpus": "all", + }, + "vps": { + "cpus": os.getenv("RUNNER_VPS_CPUS", "2"), + "memory": os.getenv("RUNNER_VPS_MEMORY", "4g"), + "gpus": "", + }, +} + + def docker_run(repo: str, image: str, lane: RunnerLane, token: str) -> None: - run_cmd( - [ - "docker", - "run", - "-d", - "--name", - lane.container_name, - "--restart", - "unless-stopped", - "-e", - f"REPO_URL=https://github.com/{repo}", - "-e", - f"RUNNER_NAME={lane.runner_name}", - "-e", - f"RUNNER_TOKEN={token}", - "-e", - f"LABELS={lane.labels}", - "-e", - "RUNNER_WORKDIR=/tmp/runner/_work", - "-v", - "/var/run/docker.sock:/var/run/docker.sock", - image, - ] - ) + resources = LANE_RESOURCES.get(lane.lane, {"cpus": "4", "memory": "8g"}) + cmd = [ + "docker", + "run", + "-d", + "--name", + lane.container_name, + "--restart", + "unless-stopped", + "--cpus", + resources["cpus"], + "--memory", + resources["memory"], + "--log-driver", + "loki", + "--log-opt", + f"loki-url={LOKI_URL}", + "--log-opt", + f"loki-external-labels=job=gha-runner,lane={lane.lane},runner={lane.runner_name}", + ] + # GPU passthrough for ai-lab lane only + gpus = resources.get("gpus", "") + if gpus: + cmd.extend(["--gpus", gpus]) + cmd.extend(["-e", "NVIDIA_VISIBLE_DEVICES=all"]) + cmd.extend(["-e", "NVIDIA_DRIVER_CAPABILITIES=compute,utility"]) + cmd.extend([ + "-e", + f"REPO_URL=https://github.com/{repo}", + "-e", + f"RUNNER_NAME={lane.runner_name}", + "-e", + f"RUNNER_TOKEN={token}", + "-e", + f"LABELS={lane.labels}", + "-e", + "RUNNER_WORKDIR=/tmp/runner/_work", + "-v", + "/var/run/docker.sock:/var/run/docker.sock", + image, + ]) + run_cmd(cmd) def cmd_up(repo: str, image: str) -> int: diff --git a/pmoves/tools/runner_lane_map.py b/pmoves/tools/runner_lane_map.py index 08aad6fe29..1222aa7758 100644 --- a/pmoves/tools/runner_lane_map.py +++ b/pmoves/tools/runner_lane_map.py @@ -208,6 +208,15 @@ def evaluate_phase( else: notes.append(f"[{phase_name}] cannot validate offline state without --check-gh: {lane}") + min_runners = int(phase_cfg.get("min_runners", 0)) + if min_runners > 0 and check_gh: + distinct_names = {r.name for r in runners if r.online} + if len(distinct_names) < min_runners: + failures.append( + f"[{phase_name}] min_runners={min_runners} requires {min_runners} distinct " + f"online runners, found {len(distinct_names)}: {sorted(distinct_names)}" + ) + return failures, notes