Some checks failed
CICD Start / Sanity and Base Decision (push) Successful in 18s
Runner Canary / Canary Heavy (ubuntu-act-8gb) (push) Has been skipped
Runner Canary / Canary Heavy (ubuntu-act-4gb) (push) Has been skipped
Runner Canary / Canary Burst (ubuntu-act (push) Failing after 11m10s
Runner Canary / Canary (ubuntu-latest) (push) Failing after 12m39s
Runner Canary / Canary (ubuntu-act) (push) Failing after 12m42s
Signed-off-by: Cliff Hill <xlorep@darkhelm.org> ## Summary Upgrades backend runtime baseline and dependency management for issue #10. ### Changes 1. **Python Baseline**: Updated from 3.13 to 3.14 - Updated `backend/pyproject.toml` requires-python constraint - Updated `backend/pyrightconfig.json` pythonVersion - Updated all Dockerfile and CI references 2. **Dependency Pinning**: Switched to exact version pins in `backend/pyproject.toml` - All dev and runtime dependencies now use `==` instead of `>=` - `fastapi==0.120.2`, `uvicorn==0.38.0` - ruff, pyright, pytest suite pinned to current resolved versions - Regenerated `backend/uv.lock` under Python 3.14 3. **Startup Compatibility Guard** (TDD via RED→GREEN) - New `compatibility_status()` function evaluates runtime and pinned deps - Startup raises `RuntimeError` if policy fails - Implemented via FastAPI lifespan (non-deprecated) handler 4. **Compatibility Status Endpoint** - New `GET /compatibility` returns policy status, runtime version, and package checks - Shares single source of truth with startup validation 5. **Integration Tests** - Added failing-then-passing tests for startup guard and endpoint behavior - 100% coverage maintained 6. **Direnv Configuration** - Added `UV_PYTHON="3.14"` pin to repo `.envrc` - Ensures direnv creates/recreates venv with correct Python version ### Validation - ✅ ruff format/check - ✅ pyright strict (0 errors) - ✅ pytest: 8 passed, 100% coverage (>=95 gate) - ✅ pydoclint: pass - ✅ xdoctest: pass ### Notes - SQLAlchemy/SQLModel introduction deferred to next pass per scope - Compatibility logic currently validates fastapi/uvicorn pins (runtime deps) - Ready for container build validation and Renovate bot testing Co-authored-by: copilotcoder <copilotcoder@darkhelm.org> Reviewed-on: #57 Co-authored-by: Cliff Hill <xlorep@darkhelm.org> Co-committed-by: Cliff Hill <xlorep@darkhelm.org>
133 lines
4.2 KiB
Plaintext
Executable File
133 lines
4.2 KiB
Plaintext
Executable File
#!/usr/bin/env xonsh
|
|
|
|
"""Deploy shared runner config.yaml to all hosts and wire compose to use CONFIG_FILE."""
|
|
|
|
import os
|
|
import subprocess
|
|
import tempfile
|
|
from datetime import datetime
|
|
|
|
TEMPLATE_HOST = "kankali.darkhelm.lan"
|
|
TEMPLATE_PATH = "/home/darkhelm/Projects/DarkHelm.org/gitea/runner-config.template.yaml"
|
|
|
|
HOSTS = {
|
|
"kankali.darkhelm.lan": "/home/darkhelm/Projects/DarkHelm.org/gitea",
|
|
"zhokq.darkhelm.lan": "/home/darkhelm/Projects/DarkHelm.org/gitea-runner",
|
|
"urtzul.darkhelm.lan": "/home/darkhelm/Projects/DarkHelm.org/gitea-runner",
|
|
"pi-desktop.darkhelm.lan": "/home/darkhelm/Projects/DarkHelm.org/gitea-runner",
|
|
}
|
|
|
|
def ssh(host, cmd):
|
|
proc = subprocess.run(
|
|
[
|
|
"ssh",
|
|
"-o",
|
|
"ConnectTimeout=30",
|
|
"-o",
|
|
"ServerAliveInterval=30",
|
|
"-o",
|
|
"ServerAliveCountMax=6",
|
|
host,
|
|
cmd,
|
|
],
|
|
capture_output=True,
|
|
text=True,
|
|
)
|
|
return proc.returncode, proc.stdout + proc.stderr
|
|
|
|
|
|
def scp(local_path, host, remote_path):
|
|
proc = subprocess.run(
|
|
[
|
|
"scp",
|
|
"-o",
|
|
"ConnectTimeout=30",
|
|
"-o",
|
|
"ServerAliveInterval=30",
|
|
"-o",
|
|
"ServerAliveCountMax=6",
|
|
local_path,
|
|
f"{host}:{remote_path}",
|
|
],
|
|
capture_output=True,
|
|
text=True,
|
|
)
|
|
return proc.returncode, proc.stdout + proc.stderr
|
|
|
|
|
|
def build_compose(compose_text):
|
|
updated = compose_text
|
|
|
|
volume_line = " - ./daemon.json:/etc/docker/daemon.json:ro"
|
|
add_volume = " - ./config.yaml:/config.yaml:ro"
|
|
if add_volume not in updated and volume_line in updated:
|
|
updated = updated.replace(volume_line, volume_line + "\n" + add_volume)
|
|
|
|
force_pull_line = " - GITEA_RUNNER_JOB_CONTAINER_FORCE_PULL=${GITEA_RUNNER_JOB_CONTAINER_FORCE_PULL:-false}"
|
|
add_cfg_env = " - CONFIG_FILE=/config.yaml"
|
|
if add_cfg_env not in updated and force_pull_line in updated:
|
|
updated = updated.replace(force_pull_line, force_pull_line + "\n" + add_cfg_env)
|
|
|
|
return updated
|
|
|
|
|
|
print("Fetching canonical runner config template")
|
|
rc, template = ssh(TEMPLATE_HOST, f"cat {TEMPLATE_PATH}")
|
|
if rc != 0 or not template.strip():
|
|
print("Failed to fetch template from kankali")
|
|
raise SystemExit(2)
|
|
|
|
now = datetime.now().strftime("%Y%m%d_%H%M%S")
|
|
|
|
with tempfile.TemporaryDirectory() as tmpdir:
|
|
local_cfg = os.path.join(tmpdir, "config.yaml")
|
|
with open(local_cfg, "w", encoding="utf-8") as f:
|
|
f.write(template)
|
|
|
|
for host, root in HOSTS.items():
|
|
print(f"\n===== {host} =====")
|
|
|
|
rc, compose = ssh(host, f"cat {root}/compose.yml")
|
|
if rc != 0:
|
|
print("Could not read compose.yml")
|
|
continue
|
|
|
|
new_compose = build_compose(compose)
|
|
|
|
rc, _ = ssh(host, f"mkdir -p {root}/backups")
|
|
rc, _ = ssh(host, f"cp -f {root}/compose.yml {root}/backups/compose.yml.{now}")
|
|
rc, _ = ssh(host, f"test -f {root}/config.yaml")
|
|
if rc == 0:
|
|
ssh(host, f"cp -f {root}/config.yaml {root}/backups/config.yaml.{now}")
|
|
|
|
local_compose = os.path.join(tmpdir, f"compose.{host}.yml")
|
|
with open(local_compose, "w", encoding="utf-8") as f:
|
|
f.write(new_compose)
|
|
|
|
rc, out = scp(local_compose, host, f"{root}/compose.yml")
|
|
if rc != 0:
|
|
print("Failed to upload compose.yml")
|
|
print(out)
|
|
continue
|
|
|
|
rc, out = scp(local_cfg, host, f"{root}/config.yaml")
|
|
if rc != 0:
|
|
print("Failed to upload config.yaml")
|
|
print(out)
|
|
continue
|
|
|
|
rc, out = ssh(host, f"grep -n 'config.yaml:/config.yaml:ro' {root}/compose.yml")
|
|
print(out.strip())
|
|
rc, out = ssh(host, f"grep -n 'CONFIG_FILE=/config.yaml' {root}/compose.yml")
|
|
print(out.strip())
|
|
|
|
rc, out = ssh(host, f"docker compose -f {root}/compose.yml up -d act_runner_1")
|
|
print(out.strip())
|
|
|
|
rc, out = ssh(host, "docker inspect gitea-act-runner-1 --format '{{range .Mounts}}{{.Destination}} {{end}}'")
|
|
print("mounts:", out.strip())
|
|
|
|
print("Done")
|
|
|
|
print("\nDeployment complete")
|