Compare commits
1 Commits
main
...
ci/bundle-
| Author | SHA1 | Date | |
|---|---|---|---|
| 7db58031c7 |
@@ -13,15 +13,23 @@
|
||||
# byte-identical.
|
||||
#
|
||||
# ── Where it is published, and why not as a release ──────────────────────────
|
||||
# Bundles are COMMITTED to this repo under bundles/:
|
||||
# Bundles are COMMITTED to this repo, on their own `bundles` branch, at its root:
|
||||
#
|
||||
# bundles/current.json the bundle the installer uses by default
|
||||
# bundles/bundle-<tag>.json every bundle ever published, kept for --bundle
|
||||
# current.json the bundle the installer uses by default
|
||||
# bundle-<tag>.json every bundle ever published, kept for --bundle
|
||||
#
|
||||
# so the installer's two fetches are plain anonymous raw URLs on a public repo:
|
||||
#
|
||||
# https://gitea.whitlocktech.com/RunicGateway/installer/raw/branch/main/bundles/current.json
|
||||
# https://gitea.whitlocktech.com/RunicGateway/installer/raw/branch/main/bundles/bundle-2026.08.04.json
|
||||
# https://gitea.whitlocktech.com/RunicGateway/installer/raw/branch/bundles/current.json
|
||||
# https://gitea.whitlocktech.com/RunicGateway/installer/raw/branch/bundles/bundle-2026.08.04.json
|
||||
#
|
||||
# A BRANCH, not `main`, because `main` is protected and this job is unattended:
|
||||
# the pre-receive hook declines a push from CI, which is not a thing a nightly
|
||||
# cron can resolve. Publishing to a branch of its own keeps everything the
|
||||
# original choice was for — a reviewable diff, a git history of the compat
|
||||
# matrix, plain raw URLs, no auth on the shard host — and needs no protection
|
||||
# exception. The alternative, whitelisting a scheduled job for pushes to the
|
||||
# default branch, buys nothing this does not.
|
||||
#
|
||||
# The obvious alternative — one Gitea release per bundle — was rejected because
|
||||
# it collides with this repo's own product. release.yml publishes the installer
|
||||
@@ -30,8 +38,8 @@
|
||||
# intermittently resolve to a release containing no installer binary. Committing
|
||||
# also gets a reviewable diff and a git history of the compat matrix for free.
|
||||
#
|
||||
# The push to `main` needs no new branch-protection exception: release.yml's
|
||||
# version-bump commit already requires REGISTRY_USER to be able to push here.
|
||||
# `main` is never pushed to by this workflow. (release.yml does not push to it
|
||||
# either — it tags and lets the release API do the rest.)
|
||||
#
|
||||
# ── Triggers (PLAN.md §7.2) ──────────────────────────────────────────────────
|
||||
# workflow_dispatch — POSTed by link's and servuo-plugins' release workflows
|
||||
@@ -86,14 +94,44 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
# Full history: the push step rebases onto main if release.yml's version
|
||||
# bump landed while this job was composing, and a depth-1 clone has no
|
||||
# base to rebase onto.
|
||||
- name: Check out the bundles directory
|
||||
# Full history: the publish step rebases onto the bundles branch if another
|
||||
# run landed while this one was composing, and a depth-1 clone has no base
|
||||
# to rebase onto.
|
||||
- name: Check out the repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
# The published bundles live on their own branch (see the header), so they
|
||||
# are materialized into a worktree rather than being part of the checkout.
|
||||
# Everything downstream reads and writes `published/`, which means the
|
||||
# ".2 suffix" scan and the idempotence check both see what is actually
|
||||
# published rather than a stale copy on main.
|
||||
- name: Materialize the bundles branch
|
||||
run: |
|
||||
set -euo pipefail
|
||||
git config user.name "installer-ci"
|
||||
git config user.email "ci@whitlocktech.com"
|
||||
# `prune` matters on a re-run in an existing checkout: removing the
|
||||
# directory leaves the worktree registered, and `worktree add` then
|
||||
# refuses the path. CI checks out fresh every time, so this only shows
|
||||
# up when driving the job by hand — which is how it is tested.
|
||||
rm -rf published
|
||||
git worktree prune
|
||||
if git ls-remote --exit-code --heads origin bundles >/dev/null 2>&1; then
|
||||
git fetch origin bundles
|
||||
git worktree add -B bundles published origin/bundles
|
||||
echo "==> bundles branch: $(ls published/*.json 2>/dev/null | wc -l) published bundle(s)"
|
||||
else
|
||||
# First run. A root commit with an empty tree gives the worktree a
|
||||
# branch to sit on without inheriting main's history, which has
|
||||
# nothing to do with the compat matrix.
|
||||
EMPTY_TREE="$(git hash-object -t tree /dev/null)"
|
||||
ROOT="$(git commit-tree "$EMPTY_TREE" -m 'chore(bundle): start the bundles branch')"
|
||||
git worktree add -B bundles published "$ROOT"
|
||||
echo "==> bundles branch does not exist yet; it will be created by the first publish"
|
||||
fi
|
||||
|
||||
- name: Install jq and curl
|
||||
run: |
|
||||
set -euo pipefail
|
||||
@@ -176,14 +214,15 @@ jobs:
|
||||
# a target to link's release.yml (macOS, a Windows arm64) surfaces here
|
||||
# as a red run, rather than being silently dropped from every bundle.
|
||||
#
|
||||
# linux-aarch64 is recognized here BEFORE link publishes one
|
||||
# (PLAN.md §5.2, step 1 of 4). That order is forced by the two rules
|
||||
# linux-aarch64 was recognized here one merge BEFORE link published one
|
||||
# (PLAN.md §5.2, steps 1 and 3). That order was forced by the two rules
|
||||
# below being strict in opposite directions: an unknown name fails the
|
||||
# run, and a missing REQUIRED key fails it too. So the name has to be
|
||||
# taught before the release that carries it, and the key can only be
|
||||
# required after — requiring it first would fail every bundle for as
|
||||
# long as the gap lasts. Step 3 promotes it into REQUIRED once a link
|
||||
# release actually ships the binary.
|
||||
# run, and a missing REQUIRED key fails it too. So the name had to be
|
||||
# taught before the release that carried it, and the key could only be
|
||||
# required after — requiring it first would have failed every bundle
|
||||
# for as long as the gap lasted. link v1.1.1 ships the binary, so the
|
||||
# key is now required: a dropped target reddens this job instead of
|
||||
# vanishing from every bundle.
|
||||
: > work/link-platforms.tsv
|
||||
while IFS="$(printf '\t')" read -r NAME URL; do
|
||||
[ -n "$NAME" ] || continue
|
||||
@@ -196,9 +235,9 @@ jobs:
|
||||
printf '%s\t%s\t%s\t%s\n' "$PLAT" "$NAME" "$URL" \
|
||||
"$(sha256sum "work/link/${NAME}" | cut -d' ' -f1)" >> work/link-platforms.tsv
|
||||
done < work/link/asset-list.tsv
|
||||
for REQUIRED in linux-x86_64 windows-x86_64; do
|
||||
for REQUIRED in linux-x86_64 linux-aarch64 windows-x86_64; do
|
||||
grep -q "^${REQUIRED}$(printf '\t')" work/link-platforms.tsv \
|
||||
|| fail "link release is missing a ${REQUIRED} binary; the installer ships for both"
|
||||
|| fail "link release is missing a ${REQUIRED} binary; the installer ships for all three"
|
||||
done
|
||||
|
||||
# The overlay release carries exactly one artifact: the tarball.
|
||||
@@ -340,8 +379,8 @@ jobs:
|
||||
# commit a dated duplicate of the same matrix forever. Compare only
|
||||
# what the installer would actually act on.
|
||||
CHANGED=true
|
||||
if [ -f bundles/current.json ]; then
|
||||
if jq -S 'del(.bundle, .generated)' bundles/current.json > work/old-content.json \
|
||||
if [ -f published/current.json ]; then
|
||||
if jq -S 'del(.bundle, .generated)' published/current.json > work/old-content.json \
|
||||
&& jq -S '.' work/content.json > work/new-content.json \
|
||||
&& cmp -s work/old-content.json work/new-content.json; then
|
||||
CHANGED=false
|
||||
@@ -350,7 +389,7 @@ jobs:
|
||||
echo "changed=${CHANGED}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
if [ "$CHANGED" = false ]; then
|
||||
echo "==> identical to bundles/current.json — nothing to publish."
|
||||
echo "==> identical to the published current.json — nothing to publish."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
@@ -360,15 +399,14 @@ jobs:
|
||||
# always names exactly one matrix and `--bundle` stays reproducible.
|
||||
BASE="$(date -u +%Y.%m.%d)"
|
||||
TAG="$BASE"; N=1
|
||||
while [ -f "bundles/bundle-${TAG}.json" ]; do
|
||||
while [ -f "published/bundle-${TAG}.json" ]; do
|
||||
N=$((N+1)); TAG="${BASE}.${N}"
|
||||
done
|
||||
|
||||
mkdir -p bundles
|
||||
jq --arg bundle "$TAG" --arg generated "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
|
||||
'{ schema: .schema, bundle: $bundle, generated: $generated } + del(.schema)' \
|
||||
work/content.json > "bundles/bundle-${TAG}.json"
|
||||
cp "bundles/bundle-${TAG}.json" bundles/current.json
|
||||
work/content.json > "published/bundle-${TAG}.json"
|
||||
cp "published/bundle-${TAG}.json" published/current.json
|
||||
|
||||
echo "bundle_tag=${TAG}" >> "$GITHUB_OUTPUT"
|
||||
echo "==> composed bundle ${TAG}"
|
||||
@@ -397,9 +435,11 @@ jobs:
|
||||
set -euo pipefail
|
||||
CI_TOKEN="$(printf '%s' "${REGISTRY_TOKEN:-}" | tr -d '\r\n')"
|
||||
# Warnings go to a FILE, not a step output. The job summary below
|
||||
# reads it with `cat`; interpolating a multi-line `${{ }}` value into
|
||||
# reads it with `cat`; interpolating a multi-line template value into
|
||||
# a shell string there would let any character in a commit-derived
|
||||
# message change what that script does.
|
||||
# message change what that script does. (Do not write that token
|
||||
# literally in a comment: the runner parses it, fails, and silently
|
||||
# skips the whole step.)
|
||||
: > work/stale-warnings.md
|
||||
|
||||
for pair in "${LINK_REPO}:${{ steps.resolve.outputs.link_tag }}" \
|
||||
@@ -480,27 +520,25 @@ jobs:
|
||||
# cannot be parsed").
|
||||
CI_USER="$(printf '%s' "${REGISTRY_USER}" | tr -d '\r\n')"
|
||||
CI_TOKEN="$(printf '%s' "${REGISTRY_TOKEN}" | tr -d '\r\n')"
|
||||
git config user.name "installer-ci"
|
||||
git config user.email "ci@whitlocktech.com"
|
||||
git remote set-url origin "https://${CI_USER}:${CI_TOKEN}@${GITEA_HOST}/${REPO}.git"
|
||||
|
||||
git add bundles
|
||||
cd published
|
||||
git add -A
|
||||
git commit -m "chore(bundle): publish ${TAG} (link ${{ steps.resolve.outputs.link_tag }}, overlay ${{ steps.resolve.outputs.overlay_tag }}, protocol ${{ steps.protocol.outputs.protocol }}) [skip ci]"
|
||||
|
||||
# The checkout is a detached snapshot of main; push the commit at HEAD
|
||||
# to the branch the installer reads its raw URLs from. release.yml
|
||||
# pushes its version-bump commit to the same branch, so losing the
|
||||
# race is normal rather than exceptional — rebase and retry once
|
||||
# instead of failing and leaving the bundle unpublished until the
|
||||
# next cron. Only bundles/ is touched here, so a rebase over a bump
|
||||
# commit cannot conflict.
|
||||
if ! git push origin "HEAD:main"; then
|
||||
echo "::warning::push rejected (main moved during compose) — rebasing and retrying once"
|
||||
git fetch origin main
|
||||
git rebase origin/main
|
||||
git push origin "HEAD:main"
|
||||
# Two runs can compose at once — a component release dispatches this
|
||||
# while the nightly cron is mid-flight — so losing the race is normal
|
||||
# rather than exceptional. Rebase and retry once instead of failing and
|
||||
# leaving the bundle unpublished until tomorrow. Every file here is a
|
||||
# bundle nobody else edits, and a bundle tag names exactly one matrix,
|
||||
# so a rebase cannot conflict.
|
||||
if ! git push origin bundles; then
|
||||
echo "::warning::push rejected (the bundles branch moved during compose) — rebasing and retrying once"
|
||||
git fetch origin bundles
|
||||
git rebase origin/bundles
|
||||
git push origin bundles
|
||||
fi
|
||||
echo "==> published bundles/bundle-${TAG}.json and bundles/current.json"
|
||||
echo "==> published bundle-${TAG}.json and current.json on the bundles branch"
|
||||
|
||||
- name: Job summary
|
||||
if: always()
|
||||
|
||||
@@ -13,7 +13,10 @@ nightly, so a missed dispatch self-heals. A run that finds nothing changed write
|
||||
|
||||
See `docs/installer/PLAN.md` §7 for the design.
|
||||
|
||||
## Layout
|
||||
## Where they live: the `bundles` branch
|
||||
|
||||
**The JSON documents are not in this directory.** They are published to a branch of their own,
|
||||
[`bundles`](https://gitea.whitlocktech.com/RunicGateway/installer/src/branch/bundles), at its root:
|
||||
|
||||
| File | What it is |
|
||||
|---|---|
|
||||
@@ -24,14 +27,24 @@ Tags are UTC dates — `2026.08.04`. A second bundle on the same day (a sidecar
|
||||
morning, an overlay release in the afternoon) becomes `2026.08.04.2`, so one tag always names
|
||||
exactly one matrix.
|
||||
|
||||
**Why a branch rather than `main`.** `main` is protected and this job is unattended: the pre-receive
|
||||
hook declines a push from CI, which is not something a nightly cron can resolve. A branch of its own
|
||||
keeps everything the original choice was for — a reviewable diff, a git history of the compat
|
||||
matrix, plain anonymous raw URLs, no credentials on the shard host — and needs no protection
|
||||
exception. Whitelisting a scheduled job for pushes to the default branch would buy nothing this does
|
||||
not.
|
||||
|
||||
This directory keeps the documentation, because that is what belongs on `main`: the branch carries
|
||||
data, and only data.
|
||||
|
||||
## How the installer fetches these
|
||||
|
||||
Plain anonymous `GET`s against a public repo. The shard host gets no git and no Gitea credentials
|
||||
(`PLAN.md` §1), so nothing here may require auth:
|
||||
|
||||
```
|
||||
https://gitea.whitlocktech.com/RunicGateway/installer/raw/branch/main/bundles/current.json
|
||||
https://gitea.whitlocktech.com/RunicGateway/installer/raw/branch/main/bundles/bundle-2026.08.04.json
|
||||
https://gitea.whitlocktech.com/RunicGateway/installer/raw/branch/bundles/current.json
|
||||
https://gitea.whitlocktech.com/RunicGateway/installer/raw/branch/bundles/bundle-2026.08.04.json
|
||||
```
|
||||
|
||||
Bundles are committed rather than published as Gitea releases because this repo's *own* releases are
|
||||
@@ -58,9 +71,8 @@ protocol) or to either component's release version. All three move independently
|
||||
"protocol": 3,
|
||||
"assets": { // per-platform: the installer runs on each
|
||||
"linux-x86_64": { "name": "…", "url": "…", "sha256": "…" },
|
||||
"linux-aarch64": { "name": "…", "url": "…", "sha256": "…" },
|
||||
"windows-x86_64": { "name": "…", "url": "…", "sha256": "…" }
|
||||
// "linux-aarch64" joins these from link's first release built for it;
|
||||
// bundle.yml already knows the name (PLAN.md §5.2)
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
{
|
||||
"schema": 1,
|
||||
"bundle": "2026.08.04",
|
||||
"generated": "2026-08-04T16:07:13Z",
|
||||
"protocol": 3,
|
||||
"link": {
|
||||
"repo": "RunicGateway/link",
|
||||
"tag": "v1.1.0",
|
||||
"version": "1.1.0",
|
||||
"protocol": 3,
|
||||
"assets": {
|
||||
"linux-x86_64": {
|
||||
"name": "uo-link-sidecar-linux-x86_64",
|
||||
"url": "https://gitea.whitlocktech.com/RunicGateway/link/releases/download/v1.1.0/uo-link-sidecar-linux-x86_64",
|
||||
"sha256": "27d491efda3fc6859dd38da9b2aa3b97b5fdf1dc5fc488a8916bb88b03443ad9"
|
||||
},
|
||||
"windows-x86_64": {
|
||||
"name": "uo-link-sidecar-windows-x86_64.exe",
|
||||
"url": "https://gitea.whitlocktech.com/RunicGateway/link/releases/download/v1.1.0/uo-link-sidecar-windows-x86_64.exe",
|
||||
"sha256": "fbefd886af0355bf128f1f4c65657b772a58b128d32438061adb0069978e0b8f"
|
||||
}
|
||||
}
|
||||
},
|
||||
"overlay": {
|
||||
"repo": "RunicGateway/servuo-plugins",
|
||||
"tag": "v0.1.1",
|
||||
"version": "0.1.1",
|
||||
"commit": "3a52abbd77047e7c94883934533edcfef3ede555",
|
||||
"protocol": 3,
|
||||
"servuo": {
|
||||
"min_version": "57.4",
|
||||
"patches_verified_against": "57.4"
|
||||
},
|
||||
"asset": {
|
||||
"name": "runicgateway-overlay-0.1.1.tar.gz",
|
||||
"url": "https://gitea.whitlocktech.com/RunicGateway/servuo-plugins/releases/download/v0.1.1/runicgateway-overlay-0.1.1.tar.gz",
|
||||
"sha256": "75dc6d6ce08322b753a30303b3b2df6f15cf1e84658507d97430af44ec4d34d7"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
{
|
||||
"schema": 1,
|
||||
"bundle": "2026.08.04",
|
||||
"generated": "2026-08-04T16:07:13Z",
|
||||
"protocol": 3,
|
||||
"link": {
|
||||
"repo": "RunicGateway/link",
|
||||
"tag": "v1.1.0",
|
||||
"version": "1.1.0",
|
||||
"protocol": 3,
|
||||
"assets": {
|
||||
"linux-x86_64": {
|
||||
"name": "uo-link-sidecar-linux-x86_64",
|
||||
"url": "https://gitea.whitlocktech.com/RunicGateway/link/releases/download/v1.1.0/uo-link-sidecar-linux-x86_64",
|
||||
"sha256": "27d491efda3fc6859dd38da9b2aa3b97b5fdf1dc5fc488a8916bb88b03443ad9"
|
||||
},
|
||||
"windows-x86_64": {
|
||||
"name": "uo-link-sidecar-windows-x86_64.exe",
|
||||
"url": "https://gitea.whitlocktech.com/RunicGateway/link/releases/download/v1.1.0/uo-link-sidecar-windows-x86_64.exe",
|
||||
"sha256": "fbefd886af0355bf128f1f4c65657b772a58b128d32438061adb0069978e0b8f"
|
||||
}
|
||||
}
|
||||
},
|
||||
"overlay": {
|
||||
"repo": "RunicGateway/servuo-plugins",
|
||||
"tag": "v0.1.1",
|
||||
"version": "0.1.1",
|
||||
"commit": "3a52abbd77047e7c94883934533edcfef3ede555",
|
||||
"protocol": 3,
|
||||
"servuo": {
|
||||
"min_version": "57.4",
|
||||
"patches_verified_against": "57.4"
|
||||
},
|
||||
"asset": {
|
||||
"name": "runicgateway-overlay-0.1.1.tar.gz",
|
||||
"url": "https://gitea.whitlocktech.com/RunicGateway/servuo-plugins/releases/download/v0.1.1/runicgateway-overlay-0.1.1.tar.gz",
|
||||
"sha256": "75dc6d6ce08322b753a30303b3b2df6f15cf1e84658507d97430af44ec4d34d7"
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user