Arch repo metadata and R2 uploads need to be reproducible from CI, with retry hardening for transient Cloudflare failures.
197 lines
7.9 KiB
YAML
197 lines
7.9 KiB
YAML
name: Publish Package Repositories
|
|
|
|
on:
|
|
workflow_dispatch:
|
|
inputs:
|
|
run_id:
|
|
description: "Release run ID to publish (leave empty for latest successful stable)"
|
|
required: false
|
|
default: ""
|
|
|
|
permissions:
|
|
contents: read
|
|
actions: read
|
|
|
|
jobs:
|
|
publish:
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 25
|
|
steps:
|
|
- name: Checkout (shallow)
|
|
uses: actions/checkout@v4
|
|
with:
|
|
fetch-depth: 1
|
|
submodules: false # Publishing does not require submodule contents
|
|
|
|
- name: Validate and resolve artifact source run
|
|
id: resolve-run
|
|
shell: bash
|
|
env:
|
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
run: |
|
|
set -euo pipefail
|
|
if [ -n "${{ inputs.run_id }}" ]; then
|
|
RUN_ID="${{ inputs.run_id }}"
|
|
echo "Validating provided run_id: $RUN_ID"
|
|
RUN_INFO=$(gh api "repos/${{ github.repository }}/actions/runs/${RUN_ID}" \
|
|
--jq '{conclusion: .conclusion, head_branch: .head_branch, event: .event}')
|
|
CONCLUSION=$(echo "$RUN_INFO" | jq -r '.conclusion')
|
|
BRANCH=$(echo "$RUN_INFO" | jq -r '.head_branch')
|
|
EVENT=$(echo "$RUN_INFO" | jq -r '.event')
|
|
if [[ "$CONCLUSION" != "success" ]]; then
|
|
echo "::warning::Run $RUN_ID did not succeed (conclusion=$CONCLUSION); using explicit run_id for repo artifact republish"
|
|
fi
|
|
if [[ "$BRANCH" != "main" ]]; then
|
|
echo "::warning::Run $RUN_ID is from branch '$BRANCH', not main"
|
|
fi
|
|
if [[ "$EVENT" == "pull_request" ]]; then
|
|
echo "::error::Run $RUN_ID is a pull_request run; refusing artifact from untrusted source"
|
|
exit 1
|
|
fi
|
|
echo "run_id=$RUN_ID" >> "$GITHUB_OUTPUT"
|
|
else
|
|
# Find latest successful release.yml run on main (stable tag push)
|
|
RUN_ID=$(gh api "repos/${{ github.repository }}/actions/workflows/release.yml/runs?per_page=20&branch=main" \
|
|
--jq '.workflow_runs[] | select(.conclusion=="success" and (.event=="push" or .event=="workflow_dispatch")) | .id' | head -1)
|
|
if [ -z "$RUN_ID" ]; then
|
|
echo "::error::No successful Release workflow run found on main"
|
|
exit 1
|
|
fi
|
|
echo "run_id=$RUN_ID" >> "$GITHUB_OUTPUT"
|
|
echo "Resolved latest successful Release run on main: $RUN_ID"
|
|
fi
|
|
|
|
- name: Download repo artifacts (apt-repo)
|
|
uses: actions/download-artifact@v4
|
|
with:
|
|
run-id: ${{ steps.resolve-run.outputs.run_id }}
|
|
name: apt-repo
|
|
path: repo-artifacts/apt-repo
|
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
|
|
- name: Download repo artifacts (yum-repo)
|
|
uses: actions/download-artifact@v4
|
|
with:
|
|
run-id: ${{ steps.resolve-run.outputs.run_id }}
|
|
name: yum-repo
|
|
path: repo-artifacts/yum-repo
|
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
|
|
- name: Download repo artifacts (arch-repo)
|
|
uses: actions/download-artifact@v4
|
|
with:
|
|
run-id: ${{ steps.resolve-run.outputs.run_id }}
|
|
name: arch-repo
|
|
path: repo-artifacts/arch-repo
|
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
|
|
- name: Prepare upload set
|
|
id: prep
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
ls -1 repo-artifacts || true
|
|
# Expect artifacts named: apt-repo, yum-repo, homebrew-formula, etc.
|
|
if [ -d repo-artifacts/apt-repo ]; then
|
|
mv repo-artifacts/apt-repo aptrepo
|
|
fi
|
|
if [ -d repo-artifacts/yum-repo ]; then
|
|
mv repo-artifacts/yum-repo yumrepo
|
|
fi
|
|
if [ -d repo-artifacts/arch-repo ]; then
|
|
mv repo-artifacts/arch-repo archrepo
|
|
fi
|
|
# latest.json is inside assets attached to release; we attempt fetch from GitHub release API.
|
|
TAG=$(gh release list --limit 1 --json tagName --jq '.[0].tagName') || TAG=""
|
|
if [ -z "$TAG" ]; then
|
|
echo "Could not determine latest tag; skipping manifest fetch" >&2
|
|
else
|
|
echo "Latest tag: $TAG" >&2
|
|
gh release download "$TAG" --pattern latest.json --dir . || echo "No latest.json asset yet"
|
|
fi
|
|
# Summaries
|
|
{
|
|
echo "HAS_APT=$([ -d aptrepo ] && echo 1 || echo 0)"
|
|
echo "HAS_YUM=$([ -d yumrepo ] && echo 1 || echo 0)"
|
|
echo "HAS_ARCH=$([ -d archrepo ] && echo 1 || echo 0)"
|
|
} >> "$GITHUB_OUTPUT"
|
|
env:
|
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
|
|
- name: Publish to R2
|
|
if: ${{ steps.prep.outputs.HAS_APT == '1' || steps.prep.outputs.HAS_YUM == '1' || steps.prep.outputs.HAS_ARCH == '1' }}
|
|
env:
|
|
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN || secrets.R2_SECRET_ACCESS_KEY }}
|
|
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
|
GPG_PUBLIC_KEY_B64: ${{ secrets.GPG_PUBLIC_KEY_B64 || '' }}
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
if [ -z "${CLOUDFLARE_API_TOKEN:-}" ] || [ -z "${CLOUDFLARE_ACCOUNT_ID:-}" ]; then
|
|
echo "Cloudflare API token/account missing; aborting publish." >&2
|
|
exit 1
|
|
fi
|
|
WRANGLER_ROOT="${RUNNER_TEMP:-/tmp}/wrangler-cli"
|
|
WRANGLER_BIN="$WRANGLER_ROOT/node_modules/.bin/wrangler"
|
|
if [ ! -x "$WRANGLER_BIN" ]; then
|
|
npm install --prefix "$WRANGLER_ROOT" wrangler@4.69.0 >/dev/null
|
|
fi
|
|
"$WRANGLER_BIN" --version >&2
|
|
upload_file() {
|
|
local src="$1"; shift
|
|
local key="$1"; shift
|
|
local ct="application/octet-stream"
|
|
case "$key" in
|
|
*.json) ct="application/json" ;;
|
|
*.key) ct="text/plain" ;;
|
|
*.txt) ct="text/plain" ;;
|
|
*.xml) ct="application/xml" ;;
|
|
*.gz) ct="application/gzip" ;;
|
|
*.deb) ct="application/vnd.debian.binary-package" ;;
|
|
*.rpm) ct="application/x-rpm" ;;
|
|
*.pkg.tar.zst) ct="application/zstd" ;;
|
|
*.db|*.files) ct="application/gzip" ;;
|
|
*.asc) ct="application/pgp-signature" ;;
|
|
esac
|
|
echo "-- Upload $key" >&2
|
|
for attempt in 1 2 3 4 5; do
|
|
if "$WRANGLER_BIN" r2 object put "yams-repository/$key" \
|
|
--remote \
|
|
--file "$src" \
|
|
--content-type "$ct"; then
|
|
return 0
|
|
fi
|
|
if [ "$attempt" -eq 5 ]; then
|
|
echo "R2 upload failed after ${attempt} attempts: $key" >&2
|
|
return 1
|
|
fi
|
|
sleep $((attempt * 5))
|
|
done
|
|
}
|
|
if [ -n "${GPG_PUBLIC_KEY_B64:-}" ]; then
|
|
echo "$GPG_PUBLIC_KEY_B64" | base64 -d > gpg.key
|
|
upload_file gpg.key gpg.key
|
|
fi
|
|
if [ -d aptrepo ]; then
|
|
find aptrepo -type f -print0 | while IFS= read -r -d '' f; do
|
|
rel="${f#aptrepo/}"; upload_file "$f" "aptrepo/$rel";
|
|
done
|
|
fi
|
|
if [ -d yumrepo ]; then
|
|
find yumrepo -type f -print0 | while IFS= read -r -d '' f; do
|
|
rel="${f#yumrepo/}"; upload_file "$f" "yumrepo/$rel";
|
|
done
|
|
fi
|
|
if [ -d archrepo ]; then
|
|
find archrepo -type f -print0 | while IFS= read -r -d '' f; do
|
|
rel="${f#archrepo/}"; upload_file "$f" "archrepo/$rel";
|
|
done
|
|
fi
|
|
if [ -f latest.json ]; then
|
|
upload_file latest.json latest.json
|
|
fi
|
|
echo "Publish complete." >&2
|
|
|
|
- name: Warn if nothing to publish
|
|
if: ${{ steps.prep.outputs.HAS_APT != '1' && steps.prep.outputs.HAS_YUM != '1' && steps.prep.outputs.HAS_ARCH != '1' }}
|
|
run: echo "No repo artifacts found to publish." >&2
|