From 3fdff3bf670ef85d26dd2d99b842631dee4a4e4e Mon Sep 17 00:00:00 2001 From: DTTerastar Date: Sat, 25 Apr 2026 11:15:13 -0400 Subject: [PATCH] ci: add weekly probe for Liftoff API host rotation Liftoff retires version-pinned API subdomains over time, breaking the CLI's hardcoded default. This weekly job probes the current defaultAPIBase, and if the backend returns "The server is deprecated.", scans a candidate window (patch+1..3, minor+1, minor+2, major+1) for a live host and opens a PR bumping the constant. No-op when the host is still live. Manual trigger via workflow_dispatch. --- .github/workflows/api-host-probe.yml | 103 +++++++++++++++++++++++++++ 1 file changed, 103 insertions(+) create mode 100644 .github/workflows/api-host-probe.yml diff --git a/.github/workflows/api-host-probe.yml b/.github/workflows/api-host-probe.yml new file mode 100644 index 0000000..fe367c4 --- /dev/null +++ b/.github/workflows/api-host-probe.yml @@ -0,0 +1,103 @@ +name: Probe Liftoff API host rotation + +on: + schedule: + - cron: '0 14 * * 1' # Mondays 14:00 UTC (10am EDT / 9am EST) + workflow_dispatch: + +permissions: + contents: write + pull-requests: write + +jobs: + probe: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: + go-version-file: go.mod + + - name: Probe and bump + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + set -euo pipefail + file=internal/auth/auth.go + current=$(grep -oE 'https://v[0-9]+-[0-9]+-[0-9]+\.api\.getgymbros\.com' "$file" | head -1) + [ -n "$current" ] || { echo "::error::could not extract defaultAPIBase"; exit 1; } + echo "current=$current" + + # 0 = live, 1 = deprecated, 2 = no/failed response + probe() { + local body + body=$(curl -sS -m 10 "${1}/api/trpc/user.refreshToken?batch=1&input=%7B%220%22%3A%7B%22json%22%3A%22invalid%22%7D%7D" 2>/dev/null) || return 2 + [ -n "$body" ] || return 2 + echo "$body" | grep -qi "server is deprecated" && return 1 + return 0 + } + + set +e + probe "$current" + rc=$? + set -e + if [ $rc -eq 0 ]; then + echo "no-op: $current still live" + exit 0 + fi + if [ $rc -eq 2 ]; then + echo "::warning::current host $current did not respond — leaving as-is" + exit 0 + fi + + echo "current host is deprecated, scanning candidates" + ver=$(echo "$current" | grep -oE 'v[0-9]+-[0-9]+-[0-9]+') + maj=${ver#v}; maj=${maj%%-*} + rest=${ver#v*-}; min=${rest%%-*}; patch=${rest#*-} + + live="" + lver="" + for spec in \ + "$maj $min $((patch+1))" "$maj $min $((patch+2))" "$maj $min $((patch+3))" \ + "$maj $((min+1)) 0" "$maj $((min+1)) 1" "$maj $((min+1)) 2" "$maj $((min+1)) 3" \ + "$maj $((min+2)) 0" "$maj $((min+2)) 1" "$((maj+1)) 0 0"; do + read M m p <<< "$spec" + host="https://v${M}-${m}-${p}.api.getgymbros.com" + set +e + probe "$host" + prc=$? + set -e + if [ $prc -eq 0 ]; then + live="$host" + lver="v${M}-${m}-${p}" + break + fi + done + + if [ -z "$live" ]; then + echo "::error::no live host found in candidate window (looked at patch+1..3, minor+1, minor+2, major+1)" + exit 1 + fi + + echo "found live host: $live" + + # Skip if a PR for this version already exists + if gh pr list --search "auto/bump-api-host-${lver} in:head state:open" --json number --jq 'length' | grep -qv '^0$'; then + echo "PR already open for ${lver}; skipping" + exit 0 + fi + + branch="auto/bump-api-host-${lver}" + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git checkout -b "$branch" + sed -i "s|$current|$live|" "$file" + go build ./... + go vet ./... + git add "$file" + git commit -m "fix(auth): bump default API host to ${lver} (auto)" + git push -u origin "$branch" + + gh pr create --base main \ + --title "fix(auth): bump default API host to ${lver}" \ + --body "Automated probe found \`$current\` returns \"server is deprecated\". \`$live\` is currently live. Triggered by \`.github/workflows/api-host-probe.yml\`. Verify with \`LIFTOFF_API_BASE=$live liftoff-export auth refresh\` before merging."