Skip to content

Release Monitor

Release Monitor #14142

name: Release Monitor
on:
schedule:
# Every 5 minutes (for testing)
- cron: "*/5 * * * *"
workflow_dispatch: {}
permissions:
contents: read
jobs:
# ============================================================
# 1) Check latest GitHub Release has all required assets
# ============================================================
check_latest_release:
runs-on: ubuntu-24.04
outputs:
tag: ${{ steps.check.outputs.tag }}
html_url: ${{ steps.check.outputs.html_url }}
steps:
- name: Check latest GitHub Release assets
id: check
uses: actions/github-script@v7
with:
script: |
const { owner, repo } = context.repo;
// === REQUIRED ASSETS ===
// These must exist on every production release.
const requiredAssets = [
// Linux x86_64
"mirrord_linux_x86_64.zip",
"mirrord_linux_x86_64.shasum256",
"mirrord_linux_x86_64",
"libmirrord_layer_linux_x86_64.so",
// Linux aarch64
"mirrord_linux_aarch64.zip",
"mirrord_linux_aarch64.shasum256",
"mirrord_linux_aarch64",
"libmirrord_layer_linux_aarch64.so",
// macOS universal
"mirrord_mac_universal.zip",
"mirrord_mac_universal.shasum256",
"mirrord_mac_universal",
"libmirrord_layer_mac_universal.dylib"
];
// === OPTIONAL ASSETS ===
// Windows artifacts: warn if missing, but don't fail the job.
const optionalAssets = [
"mirrord.exe",
"mirrord.exe.sha256",
"mirrord_layer_win.dll",
"mirrord_layer_win.dll.sha256"
];
core.info(`Checking latest release for ${owner}/${repo}...`);
let release;
try {
const res = await github.rest.repos.getLatestRelease({ owner, repo });
release = res.data;
} catch (error) {
const msg = `Failed to get latest release: ${error.message}`;
core.warning(msg);
core.setOutput("ok", "false");
core.setOutput("error", msg);
core.setOutput("tag", "");
core.setOutput("html_url", "");
core.setOutput("missing_required", "");
core.setOutput("missing_optional", "");
return;
}
const tag = release.tag_name;
const htmlUrl = release.html_url;
const assetNames = release.assets.map(a => a.name);
core.info(`Latest release tag: ${tag}`);
core.info(`Assets on release: ${assetNames.join(", ") || "(none)"}`);
const missingRequired = requiredAssets.filter(name => !assetNames.includes(name));
const missingOptional = optionalAssets.filter(name => !assetNames.includes(name));
core.setOutput("tag", tag);
core.setOutput("html_url", htmlUrl);
core.setOutput("missing_required", missingRequired.join(", "));
core.setOutput("missing_optional", missingOptional.join(", "));
if (missingRequired.length > 0) {
const msg = `Missing REQUIRED assets on latest release ${tag}: ${missingRequired.join(", ")}`;
core.warning(msg);
core.setOutput("ok", "false");
core.setOutput("error", msg);
} else {
// All required are present. Optional may still be missing.
core.info("All required assets are present on latest release.");
if (missingOptional.length > 0) {
core.warning(
`Missing OPTIONAL (Windows) assets on latest release ${tag}: ${missingOptional.join(", ")}`
);
}
core.setOutput("ok", "true");
core.setOutput("error", "");
}
# Hard failures: required assets missing or API failure
- name: Notify Slack on REQUIRED asset failure
if: steps.check.outputs.ok != 'true'
env:
SLACK_PROD_ALERTS_WEBHOOK_URL: ${{ secrets.SLACK_PROD_ALERTS_WEBHOOK_URL }}
TAG: ${{ steps.check.outputs.tag }}
HTML_URL: ${{ steps.check.outputs.html_url }}
ERROR: ${{ steps.check.outputs.error }}
MISSING_REQUIRED: ${{ steps.check.outputs.missing_required }}
shell: bash
run: |
if [ -z "$SLACK_PROD_ALERTS_WEBHOOK_URL" ]; then
echo "SLACK_PROD_ALERTS_WEBHOOK_URL is not set. Skipping Slack notification."
exit 0
fi
repo="${GITHUB_REPOSITORY}"
tag="${TAG:-unknown}"
html_url="${HTML_URL:-https://github.com/$repo/releases}"
error="${ERROR:-Unknown error}"
missing_required="${MISSING_REQUIRED:-(none listed)}"
text=":rotating_light: *Release monitor FAILURE for \`$repo\`*\n\
• *Tag:* \`$tag\`\n\
• *Problem:* $error\n\
• *Missing REQUIRED assets:* $missing_required\n\
• *Release page:* $html_url"
payload=$(jq -Rn --arg text "$text" '{text: $text}')
curl -X POST -H 'Content-type: application/json' \
--data "$payload" \
"$SLACK_PROD_ALERTS_WEBHOOK_URL"
# Mark the job as failed for required-asset issues
exit 1
# Soft warnings: only optional Windows assets missing
- name: Notify Slack on OPTIONAL Windows asset warning
if: steps.check.outputs.ok == 'true' && steps.check.outputs.missing_optional != ''
env:
SLACK_PROD_ALERTS_WEBHOOK_URL: ${{ secrets.SLACK_PROD_ALERTS_WEBHOOK_URL }}
TAG: ${{ steps.check.outputs.tag }}
HTML_URL: ${{ steps.check.outputs.html_url }}
MISSING_OPTIONAL: ${{ steps.check.outputs.missing_optional }}
shell: bash
run: |
if [ -z "$SLACK_PROD_ALERTS_WEBHOOK_URL" ]; then
echo "SLACK_PROD_ALERTS_WEBHOOK_URL is not set. Skipping Slack notification."
exit 0
fi
repo="${GITHUB_REPOSITORY}"
tag="${TAG:-unknown}"
html_url="${HTML_URL:-https://github.com/$repo/releases}"
missing_optional="${MISSING_OPTIONAL:-(none listed)}"
text=":warning: *Release monitor WARNING for \`$repo\`*\n\
• *Tag:* \`$tag\`\n\
• *Missing OPTIONAL (Windows) assets:* $missing_optional\n\
• *Note:* Linux/macOS assets are OK. This is a warning only.\n\
• *Release page:* $html_url"
payload=$(jq -Rn --arg text "$text" '{text: $text}')
curl -X POST -H 'Content-type: application/json' \
--data "$payload" \
"$SLACK_PROD_ALERTS_WEBHOOK_URL"
- name: Log success
if: steps.check.outputs.ok == 'true' && steps.check.outputs.missing_optional == ''
shell: bash
run: |
echo "✅ Release monitor: all required and optional assets are present on latest release."
# ============================================================
# 2) Install-path tests (curl|bash + Homebrew) on Linux & macOS
# ============================================================
test_install_paths:
needs: check_latest_release
strategy:
fail-fast: false
matrix:
os: [ubuntu-24.04, macos-latest]
runs-on: ${{ matrix.os }}
steps:
- name: Show env + release info
shell: bash
run: |
echo "Running on OS: ${{ runner.os }}"
echo "Latest tag from monitor: '${{ needs.check_latest_release.outputs.tag }}'"
echo "Release URL from monitor: '${{ needs.check_latest_release.outputs.html_url }}'"
- name: Test curl|bash installer
id: curl
shell: bash
run: |
set -euo pipefail
url="https://raw.githubusercontent.com/metalbear-co/mirrord/main/scripts/install.sh"
echo "Installing mirrord via curl|bash from: $url"
if ! curl -fsSL "$url" | bash >/tmp/install_sh.log 2>&1; then
echo "ok=false" >> "$GITHUB_OUTPUT"
echo "error=curl|bash installer failed from $url. See logs in this step." >> "$GITHUB_OUTPUT"
echo "----- installer output -----"
cat /tmp/install_sh.log || true
exit 0
fi
# Verify mirrord is on PATH and prints a version
if ! command -v mirrord >/dev/null 2>&1; then
echo "ok=false" >> "$GITHUB_OUTPUT"
echo "error=mirrord is not on PATH after curl|bash install." >> "$GITHUB_OUTPUT"
exit 0
fi
if ! mirrord --version >/tmp/mirrord_version_curl.log 2>&1; then
echo "ok=false" >> "$GITHUB_OUTPUT"
echo "error=mirrord --version failed after curl|bash install." >> "$GITHUB_OUTPUT"
echo "----- mirrord --version output (curl) -----"
cat /tmp/mirrord_version_curl.log || true
exit 0
fi
echo "curl|bash installer succeeded. Version:"
cat /tmp/mirrord_version_curl.log || true
echo "ok=true" >> "$GITHUB_OUTPUT"
echo "error=" >> "$GITHUB_OUTPUT"
- name: Remove mirrord binary between installers
shell: bash
run: |
set -euo pipefail
BIN="$(command -v mirrord || true)"
if [ -n "$BIN" ]; then
echo "Removing mirrord binary at: $BIN"
rm -f "$BIN" || true
else
echo "No mirrord binary found to remove."
fi
hash -r || true
- name: Install Homebrew on Linux if missing
if: runner.os == 'Linux'
shell: bash
run: |
set -euo pipefail
if command -v brew >/dev/null 2>&1; then
echo "Homebrew already installed on this Linux runner."
else
echo "Installing Homebrew on Linux (non-interactive)..."
NONINTERACTIVE=1 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
fi
# Ensure Linuxbrew is on PATH for subsequent steps
if [ -d "/home/linuxbrew/.linuxbrew/bin" ]; then
echo "Adding Linuxbrew to PATH"
{
echo "HOMEBREW_PREFIX=/home/linuxbrew/.linuxbrew"
echo "HOMEBREW_NO_AUTO_UPDATE=1"
echo "PATH=/home/linuxbrew/.linuxbrew/bin:/home/linuxbrew/.linuxbrew/sbin:$PATH"
} >> "$GITHUB_ENV"
fi
- name: Test Homebrew install (Linux + macOS)
id: brew
shell: bash
run: |
set -euo pipefail
if ! command -v brew >/dev/null 2>&1; then
echo "ok=false" >> "$GITHUB_OUTPUT"
echo "error=Homebrew (brew) is not installed on this runner." >> "$GITHUB_OUTPUT"
exit 0
fi
echo "Installing mirrord via Homebrew: metalbear-co/mirrord/mirrord"
if ! brew install metalbear-co/mirrord/mirrord >/tmp/brew_install.log 2>&1; then
echo "ok=false" >> "$GITHUB_OUTPUT"
echo "error=brew install metalbear-co/mirrord/mirrord failed. See logs in this step." >> "$GITHUB_OUTPUT"
echo "----- brew output -----"
cat /tmp/brew_install.log || true
exit 0
fi
if ! command -v mirrord >/dev/null 2>&1; then
echo "ok=false" >> "$GITHUB_OUTPUT"
echo "error=mirrord not on PATH after brew install." >> "$GITHUB_OUTPUT"
exit 0
fi
if ! mirrord --version >/tmp/mirrord_version_brew.log 2>&1; then
echo "ok=false" >> "$GITHUB_OUTPUT"
echo "error=mirrord --version failed after brew install." >> "$GITHUB_OUTPUT"
echo "----- mirrord --version output (brew) -----"
cat /tmp/mirrord_version_brew.log || true
exit 0
fi
echo "brew install succeeded. Version:"
cat /tmp/mirrord_version_brew.log || true
echo "ok=true" >> "$GITHUB_OUTPUT"
echo "error=" >> "$GITHUB_OUTPUT"
- name: Notify Slack on install failures
# Fail if curl fails or brew fails (on any OS in the matrix).
if: steps.curl.outputs.ok != 'true' || steps.brew.outputs.ok != 'true'
env:
SLACK_PROD_ALERTS_WEBHOOK_URL: ${{ secrets.SLACK_PROD_ALERTS_WEBHOOK_URL }}
BREW_OK: ${{ steps.brew.outputs.ok }}
BREW_ERROR: ${{ steps.brew.outputs.error }}
CURL_OK: ${{ steps.curl.outputs.ok }}
CURL_ERROR: ${{ steps.curl.outputs.error }}
TAG: ${{ needs.check_latest_release.outputs.tag }}
HTML_URL: ${{ needs.check_latest_release.outputs.html_url }}
RUNNER_OS: ${{ runner.os }}
shell: bash
run: |
if [ -z "$SLACK_PROD_ALERTS_WEBHOOK_URL" ]; then
echo "SLACK_PROD_ALERTS_WEBHOOK_URL is not set. Skipping Slack notification."
exit 0
fi
repo="${GITHUB_REPOSITORY}"
tag="${TAG:-unknown}"
html_url="${HTML_URL:-https://github.com/$repo/releases}"
brew_status="${BREW_OK:-not-run}"
curl_status="${CURL_OK:-unknown}"
brew_error="${BREW_ERROR:-none}"
curl_error="${CURL_ERROR:-none}"
os="${RUNNER_OS:-unknown}"
# Pretty Block Kit payload
payload=$(jq -n \
--arg repo "$repo" \
--arg os "$os" \
--arg tag "$tag" \
--arg curl_status "$curl_status" \
--arg curl_error "$curl_error" \
--arg brew_status "$brew_status" \
--arg brew_error "$brew_error" \
--arg html_url "$html_url" \
'{
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": ":warning: *Release install-path check FAILED for `\($repo)` on `\($os)`*"
}
},
{
"type": "section",
"fields": [
{
"type": "mrkdwn",
"text": "*Tag (from monitor):*\n`\($tag)`"
},
{
"type": "mrkdwn",
"text": "*OS:*\n`\($os)`"
},
{
"type": "mrkdwn",
"text": "*curl|bash installer ok?:*\n\($curl_status)"
},
{
"type": "mrkdwn",
"text": "*curl error:*\n\($curl_error)"
},
{
"type": "mrkdwn",
"text": "*brew install ok?:*\n\($brew_status)"
},
{
"type": "mrkdwn",
"text": "*brew error:*\n\($brew_error)"
}
]
},
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "*Release page:*\n<\($html_url)|Open latest release>"
}
}
]
}')
curl -X POST -H 'Content-type: application/json' \
--data "$payload" \
"$SLACK_PROD_ALERTS_WEBHOOK_URL"
# Mark job as failed if any required path failed.
exit 1
- name: Log install success
if: steps.curl.outputs.ok == 'true' && steps.brew.outputs.ok == 'true'
shell: bash
run: |
echo "✅ Install monitor: curl and Homebrew installers succeeded on ${{ runner.os }}."
# ============================================================
# 3) Version endpoint vs GitHub release download URLs
# ============================================================
check_version_endpoint:
needs: check_latest_release
runs-on: ubuntu-24.04
steps:
- name: Check version endpoint vs release download URLs
id: version
shell: bash
run: |
set -euo pipefail
api_url="https://version.mirrord.dev/v1/version"
echo "Fetching version from: $api_url"
# The endpoint returns plain text like: 3.174.0%
if ! curl -fsSL "$api_url" -o /tmp/version.txt; then
echo "ok=false" >> "$GITHUB_OUTPUT"
echo "error=Failed to fetch $api_url" >> "$GITHUB_OUTPUT"
echo "version=" >> "$GITHUB_OUTPUT"
echo "missing_urls=" >> "$GITHUB_OUTPUT"
exit 0
fi
echo "Raw version.mirrord.dev response:"
cat /tmp/version.txt || true
# Strip whitespace and trailing '%' if present
ver="$(tr -d '\r' < /tmp/version.txt | sed 's/[[:space:]]*$//' | sed 's/%$//')"
if [ -z "$ver" ]; then
echo "ok=false" >> "$GITHUB_OUTPUT"
echo "error=Empty version string from version.mirrord.dev" >> "$GITHUB_OUTPUT"
echo "version=" >> "$GITHUB_OUTPUT"
echo "missing_urls=" >> "$GITHUB_OUTPUT"
exit 0
fi
echo "Normalized version from API: '$ver'"
base="https://github.com/metalbear-co/mirrord/releases/download"
urls=(
"$base/$ver/mirrord_linux_x86_64"
"$base/$ver/mirrord_linux_aarch64"
"$base/$ver/mirrord_mac_universal"
)
echo "Checking URLs:"
printf ' - %s\n' "${urls[@]}"
missing=()
for u in "${urls[@]}"; do
# HEAD to avoid downloading full binaries
if ! curl -fsSIL "$u" >/dev/null 2>&1; then
echo "URL appears unreachable (HEAD failed): $u"
missing+=("$u")
fi
done
if [ "${#missing[@]}" -gt 0 ]; then
echo "Some URLs are missing or unreachable for version $ver:"
printf ' - %s\n' "${missing[@]}"
echo "ok=false" >> "$GITHUB_OUTPUT"
echo "error=Some download URLs are missing or not reachable for version $ver" >> "$GITHUB_OUTPUT"
printf 'missing_urls=%s\n' "$(IFS=','; echo "${missing[*]}")" >> "$GITHUB_OUTPUT"
else
echo "All download URLs for version $ver are reachable."
echo "ok=true" >> "$GITHUB_OUTPUT"
echo "error=" >> "$GITHUB_OUTPUT"
echo "missing_urls=" >> "$GITHUB_OUTPUT"
fi
printf 'version=%s\n' "$ver" >> "$GITHUB_OUTPUT"
- name: Notify Slack on version API / URL mismatch
if: steps.version.outputs.ok != 'true'
env:
SLACK_PROD_ALERTS_WEBHOOK_URL: ${{ secrets.SLACK_PROD_ALERTS_WEBHOOK_URL }}
TAG: ${{ needs.check_latest_release.outputs.tag }}
HTML_URL: ${{ needs.check_latest_release.outputs.html_url }}
VER: ${{ steps.version.outputs.version }}
ERROR: ${{ steps.version.outputs.error }}
MISSING_URLS: ${{ steps.version.outputs.missing_urls }}
shell: bash
run: |
if [ -z "$SLACK_PROD_ALERTS_WEBHOOK_URL" ]; then
echo "SLACK_PROD_ALERTS_WEBHOOK_URL is not set. Skipping Slack notification."
exit 0
fi
repo="${GITHUB_REPOSITORY}"
tag="${TAG:-unknown}"
ver="${VER:-unknown}"
html_url="${HTML_URL:-https://github.com/$repo/releases}"
error="${ERROR:-Unknown error}"
missing_urls="${MISSING_URLS:-(none listed)}"
text=":rotating_light: *Version endpoint check FAILED for \`$repo\`*\n\
• *Version from API:* \`$ver\`\n\
• *Latest release tag (from monitor):* \`$tag\`\n\
• *Problem:* $error\n\
• *Missing/broken URLs:* $missing_urls\n\
• *Release page:* $html_url\n\
• *API endpoint:* https://version.mirrord.dev/v1/version"
payload=$(jq -Rn --arg text "$text" '{text: $text}')
curl -X POST -H 'Content-type: application/json' \
--data "$payload" \
"$SLACK_PROD_ALERTS_WEBHOOK_URL"
exit 1
- name: Log version endpoint success
if: steps.version.outputs.ok == 'true'
shell: bash
run: |
echo "✅ Version endpoint check passed. version.mirrord.dev -> ${{ steps.version.outputs.version }}"