From bfd8a6533f6457b6a2ddb24a30808302cb171434 Mon Sep 17 00:00:00 2001 From: Diego Rodrigues de Sa e Souza <8016841+diegosouzapw@users.noreply.github.com> Date: Sun, 5 Jul 2026 11:14:30 -0300 Subject: [PATCH] ci: opt-in self-hosted VPS runners for the release window (anti-queue) (#6284) Adds the on-demand self-hosted runner plumbing for /generate-release: - scripts/vps/release-runner-up.sh: starts the runner VM on Proxmox, waits for >=1 'omni-release' runner to report online via the GitHub API, then flips the USE_VPS_RUNNER repo variable to true. Any failure/timeout sets it back to false and exits 1 so the caller falls back to hosted runners. - scripts/vps/release-runner-down.sh: flips USE_VPS_RUNNER=false FIRST (so no job gets scheduled onto a dying runner), then gracefully shuts the VM down. Idempotent. - ci.yml: build, test-unit x8 and test-vitest pick their runner dynamically. Self-hosted is used ONLY when USE_VPS_RUNNER == 'true' AND the event is own-origin (push/dispatch, or a PR whose head repo is this repository). Fork PRs and the var's default/absent state always fall back to ubuntu-latest. Why: the Free plan caps hosted concurrency at 20 jobs; a release run saturates it and queues. The benchmarked VPS (32-core, 4 runners) matches hosted per-job times (unit ~8.5min/shard, build 9min turbo) but eliminates the queue, which is the real release bottleneck (~30-50min on a busy day). Scope is conservative: only the three job families benchmarked on the VPS; e2e/electron/integration stay hosted (playwright/xvfb provisioning not validated on the runner workspace yet). --- .github/workflows/ci.yml | 16 +++++++++-- scripts/vps/release-runner-down.sh | 22 +++++++++++++++ scripts/vps/release-runner-up.sh | 45 ++++++++++++++++++++++++++++++ 3 files changed, 80 insertions(+), 3 deletions(-) create mode 100755 scripts/vps/release-runner-down.sh create mode 100755 scripts/vps/release-runner-up.sh diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 21db33deae..be1d3e4465 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -506,7 +506,15 @@ jobs: build: name: Build - runs-on: ubuntu-latest + # Dynamic runner: when the release captain flips the USE_VPS_RUNNER repo var to + # 'true' (scripts/vps/release-runner-up.sh does it after the self-hosted VM is + # online), the heavy jobs run on the dedicated 32-core VPS runners (label + # omni-release) instead of queueing on the 20-concurrent-job hosted pool. + # Safety: fork PRs NEVER reach the self-hosted runner — the expression falls + # back to ubuntu-latest unless the PR head repo is this repository (push / + # dispatch events are own-origin by definition). Any failure path (VM down, + # var unset/false) also falls back to ubuntu-latest. + runs-on: ${{ (vars.USE_VPS_RUNNER == 'true' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository)) && fromJSON('["self-hosted","omni-release"]') || 'ubuntu-latest' }} needs: changes if: ${{ github.event_name != 'pull_request' || (needs.changes.outputs.code == 'true' && github.event.pull_request.draft == false) }} steps: @@ -621,7 +629,8 @@ jobs: test-unit: name: Unit Tests (${{ matrix.shard }}/8) - runs-on: ubuntu-latest + # Same dynamic-runner rule as Build (own-origin only; fallback ubuntu-latest). + runs-on: ${{ (vars.USE_VPS_RUNNER == 'true' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository)) && fromJSON('["self-hosted","omni-release"]') || 'ubuntu-latest' }} timeout-minutes: 25 # needs: changes (not build) — this job never downloads the next-build artifact; # gating it on Build only serialized ~20min of wall-clock for nothing. Jobs that @@ -675,7 +684,8 @@ jobs: test-vitest: name: Vitest (MCP / autoCombo / UI components) - runs-on: ubuntu-latest + # Same dynamic-runner rule as Build (own-origin only; fallback ubuntu-latest). + runs-on: ${{ (vars.USE_VPS_RUNNER == 'true' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository)) && fromJSON('["self-hosted","omni-release"]') || 'ubuntu-latest' }} timeout-minutes: 15 # needs: changes (not build) — no artifact consumed; see test-unit note. needs: changes diff --git a/scripts/vps/release-runner-down.sh b/scripts/vps/release-runner-down.sh new file mode 100755 index 0000000000..dd27087cad --- /dev/null +++ b/scripts/vps/release-runner-down.sh @@ -0,0 +1,22 @@ +#!/usr/bin/env bash +# Desliga a VM self-hosted (VPS 113) ao fim do release e volta o CI para o GitHub-hosted. +# Idempotente: seguro chamar mesmo que a VM já esteja desligada. +# +# Uso: scripts/vps/release-runner-down.sh +set -uo pipefail + +PVE_HOST="${PVE_HOST:-192.168.0.100}" +VM_ID="${VM_ID:-113}" +REPO="${REPO:-diegosouzapw/OmniRoute}" +SSH="ssh -o BatchMode=yes -o ConnectTimeout=8" + +# 1) Volta o CI para ubuntu-latest ANTES de derrubar a VM (evita jobs presos). +echo "[release-runner] USE_VPS_RUNNER=false (CI volta ao GitHub-hosted)." +gh variable set USE_VPS_RUNNER --repo "$REPO" --body "false" >/dev/null 2>&1 || true + +# 2) Shutdown graceful da VM (libera os 32 cores / 24GB de volta ao host). +echo "[release-runner] desligando VM $VM_ID (graceful)..." +$SSH "root@$PVE_HOST" "qm shutdown $VM_ID --timeout 120" 2>/dev/null \ + || $SSH "root@$PVE_HOST" "qm stop $VM_ID" 2>/dev/null \ + || echo "[release-runner] ⚠️ não consegui desligar a VM $VM_ID — verifique manualmente." +echo "[release-runner] pronto." diff --git a/scripts/vps/release-runner-up.sh b/scripts/vps/release-runner-up.sh new file mode 100755 index 0000000000..8f35066d33 --- /dev/null +++ b/scripts/vps/release-runner-up.sh @@ -0,0 +1,45 @@ +#!/usr/bin/env bash +# Liga a VM self-hosted (VPS 113) e aguarda seus runners ficarem online, para a fase +# de release usar runners dedicados (anti-fila). Falha => o caller cai no GitHub-hosted. +# +# Uso: scripts/vps/release-runner-up.sh [timeout_seg] +# Saída: exit 0 + seta a repo-var USE_VPS_RUNNER=true quando >=1 runner omni-release online +# exit 1 + seta USE_VPS_RUNNER=false em qualquer falha/timeout (fallback) +# +# Pré-requisitos no host que roda o /generate-release: +# - chave SSH autorizada em root@$PVE_HOST (Proxmox) e root@$VPS_HOST (a VM) +# - gh autenticado com admin no repo (para ler runners + setar a variable) +set -uo pipefail + +PVE_HOST="${PVE_HOST:-192.168.0.100}" # Proxmox host +VPS_HOST="${VPS_HOST:-192.168.0.113}" # a VM dos runners +VM_ID="${VM_ID:-113}" +REPO="${REPO:-diegosouzapw/OmniRoute}" +LABEL="${RUNNER_LABEL:-omni-release}" +TIMEOUT="${1:-120}" +SSH="ssh -o BatchMode=yes -o ConnectTimeout=8" + +fallback() { + echo "[release-runner] ⚠️ $1 — usando GitHub-hosted (fallback)." + gh variable set USE_VPS_RUNNER --repo "$REPO" --body "false" >/dev/null 2>&1 || true + exit 1 +} + +echo "[release-runner] ligando VM $VM_ID no Proxmox $PVE_HOST..." +$SSH "root@$PVE_HOST" "qm start $VM_ID" 2>/dev/null || true # ok se já estiver rodando + +echo "[release-runner] aguardando runners '$LABEL' ficarem online (timeout ${TIMEOUT}s)..." +deadline=$(( $(date +%s) + TIMEOUT )) +while [ "$(date +%s)" -lt "$deadline" ]; do + online=$(gh api "repos/$REPO/actions/runners" \ + --jq "[.runners[] | select(.status==\"online\") | select(.labels[].name==\"$LABEL\")] | length" \ + 2>/dev/null || echo 0) + if [ "${online:-0}" -ge 1 ]; then + echo "[release-runner] ✅ $online runner(s) '$LABEL' online — usando a VPS." + gh variable set USE_VPS_RUNNER --repo "$REPO" --body "true" >/dev/null 2>&1 \ + || fallback "não consegui setar USE_VPS_RUNNER" + exit 0 + fi + sleep 6 +done +fallback "runners não ficaram online a tempo"