website/DEMO_MODE.md §3.2, §3.4, §3.5, §7.3 and §7.5 (step 2 of §10). Nothing changes off the demo; everything below sits behind ctx.demoMode. - demoLocks in module.json: PUT and DELETE /admin/rust/servers/:id. - The console-command deny list (DM11): quit, restart, oxide.unload, oxide.reload, rcon.*, fps.limit and server.tickrate, by first word, with global. stripped and o. read as oxide. Refused at save through core's demoRefuses (MODULE_API 1.19.0, DM18) and again at run, with retry: false, before the rig is asked. Teardown commands too. - The config editor (DM14): the bridge's files are left out of the list and refused for reading, writing and in the write history, by name (canonicalised) and by the catalogue's self. Other plugins' credential fields are redacted in the text and the form and made read-only; raw-JSON saves are refused; a file that does not parse is withheld. The admin server list leaves out the sidecar URL. - onDemoSeed (DM15): upserts the rig's server row with an encrypted token and demo-player's stand-in Steam link. - The release's install manifest carries protocol, read from PROTOCOL_VERSION, for the wipe's bundle match (DM12). coreApi is ^1.19.0; ci/core-ref.json pins website#224's head. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
463 lines
24 KiB
YAML
463 lines
24 KiB
YAML
# Build and publish the installable bundle: `module-rust-<version>.tar.gz` plus
|
|
# the manifest carrying its sha256 (docs/website/MODULE_SYSTEM.md §2.3, §2.5).
|
|
#
|
|
# ── What a release IS here ──────────────────────────────────────────────────
|
|
#
|
|
# **An operator never builds anything** (MODULE_SYSTEM.md §1.14 — the constraint
|
|
# the whole module system is shaped around). So a release is not source: it is the
|
|
# directory core's loader expects to find at `modules/rust/`, already assembled —
|
|
# the prebuilt client chunk, the schema fragment and the OpenAPI fragment — packed
|
|
# as it will be unpacked. Core's admin install downloads the tarball, verifies it
|
|
# against the `sha256` in the manifest, and unpacks it onto the volume. Nothing
|
|
# runs `npm` on the way.
|
|
#
|
|
# **And nothing is installed into the bundle either**, which is where this repo
|
|
# differs from Module-uo: the shipped half declares no runtime dependencies, so
|
|
# there is no `npm ci --omit=dev` and no `server/node_modules` in the tarball (org
|
|
# lead, phase 2). That is a decision worth being loud about rather than a detail —
|
|
# `server/scripts/checkBundle.js` fails the PR that adds a dependency without also
|
|
# teaching this file to install and pack it, because a bundle that declares an
|
|
# import it does not carry fails the same way a missing directory does.
|
|
#
|
|
# ── The version is DERIVED, and the declaration is a floor ──────────────────
|
|
#
|
|
# The engine `link`, `installer` and `Module-uo` already run (MODULE_SYSTEM
|
|
# §2.7.1, decision 19 as amended):
|
|
#
|
|
# feat!: / BREAKING CHANGE -> major feat: -> minor fix|perf: -> patch
|
|
# nothing releasable -> no release is cut
|
|
# (first ever run, no tag) -> releases what module.json declares
|
|
#
|
|
# Module-uo learned this the expensive way: it released only when a merge left
|
|
# `module.json` at a version with no release yet — the version DECLARED, never
|
|
# computed — and between 2026-08-12 and 2026-08-19 that cost it *every* bundle,
|
|
# because nine phases of work landed without anyone touching that line.
|
|
#
|
|
# **The declared version is kept as a floor, not deleted.** If `module.json` names
|
|
# a version above the newest tag, that version releases. Raising it by hand is how
|
|
# you say "this one is a minor, whatever the subjects imply", and it is the natural
|
|
# place to move when a `coreApi` bump forces the question.
|
|
#
|
|
# The number that ships is therefore the TAG, and CI writes it into the
|
|
# `module.json` inside the bundle at assembly time. The committed `module.json` is
|
|
# a floor and a starting point, not a record of the last release — a release engine
|
|
# that has to commit a bump back to `main` stops working the day someone protects
|
|
# the branch, and this one is protected.
|
|
#
|
|
# ── The backdoor ────────────────────────────────────────────────────────────
|
|
#
|
|
# `workflow_dispatch` publishes on demand, for the case the rules above cannot
|
|
# reach: `module.json` changed in a way worth shipping — a widened `coreApi`, a
|
|
# new mount, a capability — with no releasable code behind it. Leave `version`
|
|
# blank to bump the newest tag by `bump` (default `patch`), or name an exact
|
|
# version to publish that. A dispatch releases even when nothing in the log is
|
|
# releasable; that is the entire point of pressing the button.
|
|
#
|
|
# Re-running on a version that is already released is a no-op, so a rerun after an
|
|
# unrelated failure is safe. A tag that exists with no release behind it is NOT a
|
|
# no-op — see the recovery branch in the plan step. That state is not theoretical:
|
|
# `servuo-plugins`' first release pushed its tag and then 401'd on the release API
|
|
# because the secret was absent, and without the recovery branch the repo would
|
|
# have been stuck there permanently.
|
|
#
|
|
# This workflow never writes to a branch. It tags and publishes, so `main` needs
|
|
# no push exception.
|
|
#
|
|
# Prerequisites (Settings → Actions → Secrets on RunicGateway/Module-Rust):
|
|
# REGISTRY_TOKEN — Gitea access token with `write:repository`, to push the tag
|
|
# and create the release.
|
|
|
|
name: Release
|
|
|
|
on:
|
|
push:
|
|
branches: [main]
|
|
workflow_dispatch:
|
|
inputs:
|
|
version:
|
|
description: 'Exact version to publish (e.g. 0.1.1). Blank = bump the newest tag by the level below.'
|
|
required: false
|
|
default: ''
|
|
bump:
|
|
description: 'Bump level when version is blank: patch | minor | major'
|
|
required: false
|
|
default: 'patch'
|
|
|
|
concurrency:
|
|
group: release-module-rust
|
|
cancel-in-progress: false
|
|
|
|
env:
|
|
GITEA_HOST: gitea.whitlocktech.com
|
|
REPO: RunicGateway/Module-Rust
|
|
|
|
jobs:
|
|
release:
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 30
|
|
steps:
|
|
# Full history: the plan step reads every tag and every subject since the
|
|
# newest one, and a shallow clone has neither.
|
|
- uses: actions/checkout@v4
|
|
with:
|
|
fetch-depth: 0
|
|
|
|
- uses: actions/setup-node@v4
|
|
with:
|
|
node-version: 20
|
|
|
|
- name: Plan the release (version + changelog)
|
|
id: plan
|
|
env:
|
|
REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }}
|
|
EVENT: ${{ github.event_name }}
|
|
IN_VERSION: ${{ github.event.inputs.version }}
|
|
IN_BUMP: ${{ github.event.inputs.bump }}
|
|
run: |
|
|
set -euo pipefail
|
|
mkdir -p dist
|
|
git fetch --tags --force >/dev/null 2>&1 || true
|
|
|
|
DECLARED="$(node -p "require('./module.json').version")"
|
|
LAST_TAG="$(git describe --tags --match 'v*' --abbrev=0 2>/dev/null || true)"
|
|
CURRENT="${LAST_TAG#v}"
|
|
RANGE="${LAST_TAG:+${LAST_TAG}..}HEAD"
|
|
echo "module.json declares ${DECLARED}; newest tag is ${LAST_TAG:-<none>}"
|
|
|
|
SUBJECTS="$(git log --no-merges --format='%s' $RANGE || true)"
|
|
BODIES="$(git log --no-merges --format='%B' $RANGE || true)"
|
|
|
|
BUMP=none
|
|
if echo "$BODIES" | grep -qE 'BREAKING[ -]CHANGE' ; then BUMP=major; fi
|
|
if echo "$SUBJECTS" | grep -qE '^[a-z]+(\([^)]+\))?!:' ; then BUMP=major; fi
|
|
if [ "$BUMP" = none ] && echo "$SUBJECTS" | grep -qE '^feat(\([^)]+\))?:' ; then BUMP=minor; fi
|
|
if [ "$BUMP" = none ] && echo "$SUBJECTS" | grep -qE '^(fix|perf)(\([^)]+\))?:' ; then BUMP=patch; fi
|
|
|
|
bump() { # <x.y.z> <major|minor|patch> -> bumped
|
|
IFS=. read -r MA MI PA <<< "$1"
|
|
case "$2" in
|
|
major) echo "$((MA+1)).0.0" ;;
|
|
minor) echo "${MA}.$((MI+1)).0" ;;
|
|
patch) echo "${MA}.${MI}.$((PA+1))" ;;
|
|
esac
|
|
}
|
|
|
|
# `sort -V` orders version strings, so the higher of two is its last
|
|
# line. Used rather than a hand-rolled field compare because 0.10.0 vs
|
|
# 0.9.0 is exactly the comparison a string sort gets wrong.
|
|
higher() { printf '%s\n%s\n' "$1" "$2" | sort -V | tail -1; }
|
|
|
|
rank() { case "$1" in major) echo 3 ;; minor) echo 2 ;; patch) echo 1 ;; *) echo 0 ;; esac; }
|
|
bigger_bump() { if [ "$(rank "$1")" -ge "$(rank "$2")" ]; then echo "$1"; else echo "$2"; fi; }
|
|
|
|
VERSION=""
|
|
if [ -n "${IN_VERSION:-}" ]; then
|
|
# The backdoor's exact form. Deliberately unvalidated against the log:
|
|
# a human typed it, and the already-released check below is the only
|
|
# guard that matters.
|
|
VERSION="${IN_VERSION}"
|
|
echo "dispatch: publishing the requested version ${VERSION}"
|
|
else
|
|
LEVEL="$BUMP"
|
|
# A dispatch with nothing releasable in the log still releases — that
|
|
# is what the button is for. Where the log DOES say something, the
|
|
# larger of the two wins rather than the input: pressing the button on
|
|
# a log full of `feat:` without touching the dropdown would otherwise
|
|
# publish its `patch` default over a minor's worth of work, and a
|
|
# version that undersells its own contents cannot be taken back.
|
|
if [ "${EVENT:-}" = workflow_dispatch ]; then
|
|
LEVEL="$(bigger_bump "$LEVEL" "${IN_BUMP:-patch}")"
|
|
if [ "$BUMP" = none ]; then
|
|
echo "dispatch: nothing releasable in the log, bumping ${LEVEL} anyway"
|
|
elif [ "$LEVEL" != "$BUMP" ]; then
|
|
echo "dispatch: the log says ${BUMP}, the run asked for ${LEVEL} — taking ${LEVEL}"
|
|
fi
|
|
fi
|
|
|
|
if [ -z "$CURRENT" ]; then
|
|
VERSION="$DECLARED" # first ever release: ship what is declared
|
|
elif [ "$LEVEL" != none ]; then
|
|
VERSION="$(bump "$CURRENT" "$LEVEL")"
|
|
fi
|
|
|
|
# The floor. A `module.json` above the newest tag releases at that
|
|
# version even when the log says nothing and even when the log says
|
|
# patch.
|
|
if [ -n "$CURRENT" ] && [ "$DECLARED" != "$CURRENT" ] \
|
|
&& [ "$(higher "$DECLARED" "$CURRENT")" = "$DECLARED" ]; then
|
|
if [ -z "$VERSION" ] || [ "$(higher "$DECLARED" "$VERSION")" = "$DECLARED" ]; then
|
|
echo "module.json declares ${DECLARED}, above both ${CURRENT} and the derived version — releasing that."
|
|
VERSION="$DECLARED"
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
RELEASE=true
|
|
if [ -z "$VERSION" ]; then
|
|
RELEASE=false
|
|
VERSION="$CURRENT"
|
|
echo "Nothing releasable since ${LAST_TAG} (no feat/fix/perf/breaking subject) — standing down."
|
|
fi
|
|
|
|
# An existing tag is NOT automatically "nothing to do". A tag with no
|
|
# release behind it means a previous run tagged and then died before
|
|
# publishing — which is what happened on servuo-plugins' first release,
|
|
# where absent secrets took the release API call to 401 after the tag had
|
|
# already been pushed. Standing down on the tag alone makes that state
|
|
# permanent. Note this deliberately OVERRIDES the RELEASE=false above:
|
|
# with the tag in place there is nothing releasable after it, so the
|
|
# normal path would stand down, which is why it could never self-heal.
|
|
# Anything other than 200/404 — a network failure, a bad token — is not
|
|
# evidence of absence, and guessing "no" would publish over a good
|
|
# release, so refuse instead.
|
|
REUSE_TAG=false
|
|
if [ -n "$VERSION" ] && git rev-parse -q --verify "refs/tags/v${VERSION}" >/dev/null; then
|
|
CI_TOKEN="$(printf '%s' "${REGISTRY_TOKEN:-}" | tr -d '\r\n')"
|
|
REL_HTTP="$(curl -s -o /dev/null -w '%{http_code}' \
|
|
-H "Authorization: token ${CI_TOKEN}" \
|
|
"https://${GITEA_HOST}/api/v1/repos/${REPO}/releases/tags/v${VERSION}" || echo 000)"
|
|
case "$REL_HTTP" in
|
|
200) echo "v${VERSION} is already released — nothing to do."; RELEASE=false ;;
|
|
404) echo "::warning::Tag v${VERSION} exists but has no release — a previous run failed after tagging. Reusing the tag and publishing the release it is missing."
|
|
REUSE_TAG=true; RELEASE=true ;;
|
|
*) echo "::error::Could not determine whether v${VERSION} is released (HTTP ${REL_HTTP}). Refusing to guess."; exit 1 ;;
|
|
esac
|
|
fi
|
|
|
|
# Changelog range. A recovery run has nothing after the tag, so
|
|
# summarize what the tag itself contains rather than emitting an empty
|
|
# list: the range that produced it, i.e. previous-tag..this-tag.
|
|
if [ "$REUSE_TAG" = true ]; then
|
|
PREV_TAG="$(git describe --tags --match 'v*' --abbrev=0 "v${VERSION}^" 2>/dev/null || true)"
|
|
CL_RANGE="${PREV_TAG:+${PREV_TAG}..}v${VERSION}"
|
|
SINCE="$PREV_TAG"
|
|
else
|
|
CL_RANGE="$RANGE"
|
|
SINCE="$LAST_TAG"
|
|
fi
|
|
CL_SUBJECTS="$(git log --no-merges --format='%s' $CL_RANGE || true)"
|
|
|
|
{
|
|
echo "## module-rust v${VERSION}"
|
|
echo
|
|
echo "Install from the website's Admin → Modules screen by pasting the URL of"
|
|
echo "\`module-rust-${VERSION}.json\`, or unpack the tarball onto the modules volume"
|
|
echo "as \`modules/rust/\`. Requires a core whose \`MODULE_API_VERSION\` satisfies"
|
|
echo "\`$(node -p "require('./module.json').coreApi")\`."
|
|
echo
|
|
echo "A Rust server also needs the other two halves of the bridge:"
|
|
echo "[Rust-Link](https://${GITEA_HOST}/RunicGateway/Rust-Link) (the sidecar) and"
|
|
echo "[Rust-Plugins](https://${GITEA_HOST}/RunicGateway/Rust-Plugins) (the Oxide/Carbon plugin)."
|
|
echo
|
|
FEATS="$(echo "$CL_SUBJECTS" | grep -E '^feat' || true)"
|
|
FIXES="$(echo "$CL_SUBJECTS" | grep -E '^(fix|perf)' || true)"
|
|
[ -n "$FEATS" ] && { echo "### Features"; echo "$FEATS" | sed 's/^/- /'; echo; }
|
|
[ -n "$FIXES" ] && { echo "### Fixes"; echo "$FIXES" | sed 's/^/- /'; echo; }
|
|
echo "### All changes"
|
|
if [ -n "$SINCE" ]; then echo "Since ${SINCE}:"; fi
|
|
echo "$CL_SUBJECTS" | sed 's/^/- /'
|
|
echo
|
|
echo "### Verifying this download"
|
|
echo
|
|
echo "Releases are **unsigned** — the \`sha256\` in \`module-rust-${VERSION}.json\` is the"
|
|
echo "trust anchor, and the website verifies it before unpacking."
|
|
echo
|
|
echo '```bash'
|
|
echo "sha256sum -c SHA256SUMS --ignore-missing"
|
|
echo '```'
|
|
} > dist/CHANGELOG.md
|
|
|
|
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
|
|
echo "tag=v${VERSION}" >> "$GITHUB_OUTPUT"
|
|
echo "release=${RELEASE}" >> "$GITHUB_OUTPUT"
|
|
echo "reuse_tag=${REUSE_TAG}" >> "$GITHUB_OUTPUT"
|
|
echo "bump=${BUMP}" >> "$GITHUB_OUTPUT"
|
|
echo "==> release=${RELEASE} version=${VERSION} bump=${BUMP} declared=${DECLARED} last_tag=${LAST_TAG:-<none>}"
|
|
|
|
# Before anything is built or tagged, so a repo without secrets fails
|
|
# legibly rather than half-publishing: the tag push can succeed on the
|
|
# credential actions/checkout left in the local git config while the release
|
|
# API call 401s, leaving the repo tagged and unreleased.
|
|
- name: Verify release credentials are configured
|
|
if: ${{ steps.plan.outputs.release == 'true' }}
|
|
env:
|
|
REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }}
|
|
run: |
|
|
set -euo pipefail
|
|
if [ -z "$(printf '%s' "${REGISTRY_TOKEN:-}" | tr -d '\r\n')" ]; then
|
|
echo "::error::Missing Actions secret REGISTRY_TOKEN (needs write:repository) on ${REPO}."
|
|
exit 1
|
|
fi
|
|
echo "Release credentials present."
|
|
|
|
- name: Build the client chunk
|
|
if: ${{ steps.plan.outputs.release == 'true' }}
|
|
run: |
|
|
npm ci --prefix client
|
|
npm run build --prefix client
|
|
|
|
# ── Assemble exactly what an operator's volume gets ──────────────────
|
|
#
|
|
# Stated as an INCLUDE list, not an exclude list. An exclude list ships
|
|
# whatever it forgot: the day someone adds `server/tools/` with a scratch
|
|
# credential in it, an exclude list packs it and nobody finds out.
|
|
#
|
|
# The list itself lives in `ci/bundle.json`, not here, because it has a
|
|
# second reader: `server/scripts/checkBundle.js` runs in PR checks and asks
|
|
# whether the list still covers everything `server/index.js` reaches. One
|
|
# declaration, two readers, so a new directory cannot go missing quietly.
|
|
- name: Assemble the bundle
|
|
if: ${{ steps.plan.outputs.release == 'true' }}
|
|
run: |
|
|
set -euo pipefail
|
|
VERSION="${{ steps.plan.outputs.version }}"
|
|
OUT="dist/module-rust-${VERSION}"
|
|
rm -rf "$OUT" && mkdir -p "$OUT"
|
|
|
|
# The manifest core reads — with the RELEASED version written into it.
|
|
# The committed `module.json` is a floor, not a record of the last
|
|
# release (see the header), so copying it verbatim would ship a bundle
|
|
# whose `installed_modules` row and admin screen disagree with the tag it
|
|
# came from. This is the one place the derived number becomes the
|
|
# module's own.
|
|
jq --arg v "$VERSION" '.version = $v' module.json > "$OUT/module.json"
|
|
|
|
# The two fragments, and the licence the code is under — a bundle that
|
|
# ships GPL code without its licence is not distributable.
|
|
for f in $(jq -r '.root[]' ci/bundle.json); do
|
|
cp "$f" "$OUT/"
|
|
done
|
|
|
|
# The server half, minus what never runs inside core's process. No
|
|
# node_modules: the shipped half declares no runtime dependencies, and
|
|
# check:bundle is what keeps that true.
|
|
mkdir -p "$OUT/server"
|
|
for d in $(jq -r '.server[]' ci/bundle.json); do
|
|
cp -r "server/$d" "$OUT/server/"
|
|
done
|
|
|
|
# The client half is the BUILT chunks only. `client/src` is source an
|
|
# operator has no use for and core will never read. Every `.js`, not
|
|
# `entry.js` by name: since D120 the Map tab imports Leaflet's split
|
|
# chunk from beside it, and a release that shipped the entry alone
|
|
# would load everywhere and spin for ever on that tab.
|
|
mkdir -p "$OUT/client/dist"
|
|
cp client/dist/*.js "$OUT/client/dist/"
|
|
|
|
# Prove the bundle is loadable before it is published: these are the
|
|
# paths core's loader resolves out of module.json, and a release whose
|
|
# entry point is missing fails on an operator's box with a
|
|
# `startup_failed` row instead of here. The version assertion guards the
|
|
# rewrite above — a bundle that still carried the declared version would
|
|
# install under a number that is not the one it was released as.
|
|
node -e '
|
|
const fs = require("fs"), path = require("path");
|
|
const [root, want] = process.argv.slice(1);
|
|
const m = JSON.parse(fs.readFileSync(path.join(root, "module.json"), "utf8"));
|
|
if (m.version !== want) {
|
|
console.error(`bundle declares ${m.version}, but this is release ${want}`);
|
|
process.exit(1);
|
|
}
|
|
for (const p of [m.server, m.schema, m.purge, m.client.entry, "swagger-fragment.json"]) {
|
|
if (!fs.existsSync(path.join(root, p))) { console.error("bundle is missing " + p); process.exit(1); }
|
|
}
|
|
console.log("bundle contents check: ok");
|
|
' "$OUT" "$VERSION"
|
|
|
|
# ── And that it can actually LOAD ─────────────────────────────────
|
|
#
|
|
# The check above stats the paths `module.json` declares, which is a real
|
|
# question but a shallow one: Module-uo's v1.0.0 passed exactly that and
|
|
# was still missing `server/commands/`, because a file reached only by a
|
|
# require inside `register()` is named nowhere in `module.json`. This
|
|
# resolves every relative require in the assembled tree and asserts the
|
|
# target is in it — asked of the artifact, so it also catches a copy that
|
|
# half failed or a list naming a path that has since moved.
|
|
#
|
|
# Run from the SOURCE tree (`server/scripts/` never ships) against the
|
|
# assembled bundle.
|
|
node server/scripts/checkBundle.js --bundle "$OUT"
|
|
|
|
tar -C dist -czf "dist/module-rust-${VERSION}.tar.gz" "module-rust-${VERSION}"
|
|
rm -rf "$OUT"
|
|
|
|
SHA="$(sha256sum "dist/module-rust-${VERSION}.tar.gz" | cut -d' ' -f1)"
|
|
SIZE="$(stat -c%s "dist/module-rust-${VERSION}.tar.gz")"
|
|
|
|
# The wire protocol this release speaks, for the public demo's wipe
|
|
# (website/DEMO_MODE.md §7.5, DM12). The wipe picks the newest release
|
|
# whose `protocol` EQUALS the Rust bundle's. The Rust link has no 409,
|
|
# so a plugin on another protocol would mis-parse rather than be
|
|
# refused. Read from the one declaration (`server/sidecarClient.js`);
|
|
# a release that cannot find it fails here rather than ship a manifest
|
|
# the wipe would skip. Core's installer reads only the fields it names
|
|
# (modules/install.js), so the extra key changes nothing there.
|
|
PROTOCOL="$(node -e '
|
|
const src = require("fs").readFileSync("server/sidecarClient.js", "utf8");
|
|
const m = /^const PROTOCOL_VERSION = (\d+)$/m.exec(src);
|
|
if (!m) { console.error("no PROTOCOL_VERSION in server/sidecarClient.js"); process.exit(1); }
|
|
console.log(m[1]);
|
|
')"
|
|
echo "protocol ${PROTOCOL}"
|
|
|
|
# The install manifest. Same shape as the installer's bundle JSON — a
|
|
# per-asset sha256 fetched over HTTPS, no signatures — because that is
|
|
# the model this project already has and a second one would be a second
|
|
# thing to get right (MODULE_SYSTEM.md §1.11).
|
|
jq -n \
|
|
--arg id "$(node -p "require('./module.json').id")" \
|
|
--arg name "$(node -p "require('./module.json').name")" \
|
|
--arg version "$VERSION" \
|
|
--arg coreApi "$(node -p "require('./module.json').coreApi")" \
|
|
--arg artifact "module-rust-${VERSION}.tar.gz" \
|
|
--arg sha256 "$SHA" \
|
|
--argjson size "$SIZE" \
|
|
--argjson protocol "$PROTOCOL" \
|
|
--arg url "https://${GITEA_HOST}/${REPO}/releases/download/v${VERSION}/module-rust-${VERSION}.tar.gz" \
|
|
'{schema:1, id:$id, name:$name, version:$version, coreApi:$coreApi,
|
|
artifact:$artifact, url:$url, sha256:$sha256, size:$size, protocol:$protocol}' \
|
|
> "dist/module-rust-${VERSION}.json"
|
|
|
|
echo "${SHA} module-rust-${VERSION}.tar.gz" > dist/SHA256SUMS
|
|
cat "dist/module-rust-${VERSION}.json"
|
|
|
|
# Skipped on a recovery run: the tag is already there and is the thing being
|
|
# published against.
|
|
- name: Tag the release
|
|
if: ${{ steps.plan.outputs.release == 'true' && steps.plan.outputs.reuse_tag != 'true' }}
|
|
env:
|
|
REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }}
|
|
run: |
|
|
set -euo pipefail
|
|
TAG="${{ steps.plan.outputs.tag }}"
|
|
git config user.name 'Runic Gateway CI'
|
|
git config user.email 'ci@whitlocktech.net'
|
|
git tag -a "$TAG" -m "module-rust ${TAG}"
|
|
git push origin "$TAG"
|
|
|
|
- name: Create the Gitea release and upload the bundle
|
|
if: ${{ steps.plan.outputs.release == 'true' }}
|
|
env:
|
|
REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }}
|
|
run: |
|
|
set -euo pipefail
|
|
TAG="${{ steps.plan.outputs.tag }}"
|
|
VERSION="${{ steps.plan.outputs.version }}"
|
|
API="https://${GITEA_HOST}/api/v1/repos/${REPO}"
|
|
CI_TOKEN="$(printf '%s' "${REGISTRY_TOKEN}" | tr -d '\r\n')"
|
|
|
|
REL_ID="$(curl -sSf -X POST "${API}/releases" \
|
|
-H "Authorization: token ${CI_TOKEN}" \
|
|
-H "Content-Type: application/json" \
|
|
-d "$(jq -n --arg tag "$TAG" --arg body "$(cat dist/CHANGELOG.md)" \
|
|
'{tag_name:$tag, name:$tag, body:$body, draft:false, prerelease:false}')" \
|
|
| jq -r '.id')"
|
|
echo "Created release ${TAG} (id=${REL_ID})"
|
|
|
|
for f in "module-rust-${VERSION}.tar.gz" "module-rust-${VERSION}.json" SHA256SUMS; do
|
|
curl -sSf -X POST "${API}/releases/${REL_ID}/assets?name=${f}" \
|
|
-H "Authorization: token ${CI_TOKEN}" \
|
|
-F "attachment=@dist/${f}" >/dev/null
|
|
echo " uploaded ${f}"
|
|
done
|