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).
This commit is contained in:
Diego Rodrigues de Sa e Souza
2026-07-05 11:14:30 -03:00
committed by GitHub
parent c26984e9bd
commit bfd8a6533f
3 changed files with 80 additions and 3 deletions

View File

@@ -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

View File

@@ -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."

View File

@@ -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"