Ports the v0.1 Gitea release flow to the v0.2 single-binary + 4-package
layout, so a tag publishes a signed opkg feed the routers install from.
- .gitea/workflows/release.yml: on tag v* (+ dispatch), matrix over
{x86_64, aarch64_cortex-a53}. Per arch: setup Go 1.24/Node 20/UPX ->
scripts/build-shaterd.sh (SPA-embedded shaterd, stages the .upx) ->
ci/build-feed.sh (OpenWrt SDK container builds all 4 packages ->
usign-signed Packages index). A release job merges both arches into one
signed feed + publishes the rolling 'latest'/tag release via the Gitea API.
- ci/sdk-build.sh: in-SDK build — add openwrt/ as the 'shater' feed, feeds
update/install, make package/{shaterd,shater-core,byedpi,luci-app-shater}/
compile (shaterd validates+installs the staged prebuilt; byedpi cross-
compiles from source). ci/make-index.sh: opkg Packages(.gz) + usign sign
with KEY_BUILD (keyfile umask 077, no secret hardcoded), verifiable by
dist/shater-feed.pub. ci/install-usign.sh + ci/gitea-release.sh ported.
- INSTALL.md: add the signed feed src/gz line + import dist/shater-feed.pub
to /etc/opkg/keys; apk (25.12) path noted.
Key kept: usign feed key 5ac4b177689cb8e0 (public dist/shater-feed.pub,
secret Gitea repo secret KEY_BUILD). Decision: opkg (24.10 uses opkg; apk
is 25.12) — matches the existing usign trust anchor.
Verified structurally (no live runner here): release.yml is valid YAML, all
ci/*.sh are bash -n clean, no hardcoded secrets, and every package name/
path/arch/artifact/secret reference cross-checks against openwrt/, scripts/
build-shaterd.sh, and dist/shater-feed.pub. Live-runner unknowns (full SDK
compile of the 4 packages, router-side signature verify) flagged in-agent.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LLthkP2S8WAfxu7fcYbPfE
111 lines
5.0 KiB
Bash
111 lines
5.0 KiB
Bash
#!/bin/bash
|
|
# Create (or refresh) a Gitea release and upload assets via the Gitea API.
|
|
#
|
|
# Self-contained: uses only curl (present in the act_runner image), so it needs
|
|
# NO external marketplace action — the safest option on a self-hosted Gitea
|
|
# act_runner where github.com/gitea.com action fetches may be unavailable.
|
|
#
|
|
# Idempotent: safe to re-run for the same tag. A pre-existing release (rolling
|
|
# `latest`, or a re-run of a tag build) is reused — its clashing assets are
|
|
# deleted and replaced — instead of aborting on a 409.
|
|
#
|
|
# Env:
|
|
# SERVER Gitea base URL, e.g. https://git.qomar.pw (default: $GITHUB_SERVER_URL)
|
|
# REPO owner/repo (default: $GITHUB_REPOSITORY)
|
|
# TOKEN API token with contents:write (default: $GITHUB_TOKEN)
|
|
# TAG release tag, e.g. v0.2.0 or "latest"
|
|
# NAME release title (default: TAG)
|
|
# BODY release notes markdown (default: "")
|
|
# PRERELEASE true|false (default: false)
|
|
# TARGET commit sha the tag should point at (default: $GITHUB_SHA)
|
|
# ROLLING true => move the tag to $TARGET (delete+recreate); for `latest`
|
|
# Args: asset files to upload.
|
|
set -eu
|
|
|
|
SERVER="${SERVER:-${GITHUB_SERVER_URL:?}}"
|
|
REPO="${REPO:-${GITHUB_REPOSITORY:?}}"
|
|
TOKEN="${TOKEN:-${GITHUB_TOKEN:?token required (GITHUB_TOKEN or RELEASE_TOKEN)}}"
|
|
TAG="${TAG:?tag required}"
|
|
NAME="${NAME:-$TAG}"
|
|
BODY="${BODY:-}"
|
|
PRERELEASE="${PRERELEASE:-false}"
|
|
TARGET="${TARGET:-${GITHUB_SHA:-}}"
|
|
ROLLING="${ROLLING:-false}"
|
|
|
|
API="$SERVER/api/v1/repos/$REPO"
|
|
AUTH=(-H "Authorization: token $TOKEN")
|
|
BODYF="$(mktemp)" # last response body
|
|
# FIRST "<key>": <number> in the body. grep -o preserves order, so for a release
|
|
# object the top-level "id" (the release id) comes before nested author/asset ids
|
|
# — a greedy `.*"id":` would wrongly grab the LAST id on a one-line JSON response.
|
|
int() { grep -o "\"$1\"[[:space:]]*:[[:space:]]*[0-9]\{1,\}" "$BODYF" | head -n1 | grep -o '[0-9]\{1,\}'; }
|
|
|
|
# api METHOD PATH [curl-args...] -> echoes HTTP code, body in $BODYF
|
|
api() {
|
|
local m="$1" p="$2"; shift 2
|
|
curl -sS -o "$BODYF" -w '%{http_code}' -X "$m" "${AUTH[@]}" "$@" "$API$p"
|
|
}
|
|
|
|
echo "[release] repo=$REPO tag=$TAG prerelease=$PRERELEASE rolling=$ROLLING target=${TARGET:0:8}"
|
|
|
|
# --- find any existing release for this tag (by tag lookup, then by listing) --
|
|
find_release() {
|
|
local code
|
|
code=$(api GET "/releases/tags/$TAG")
|
|
if [ "$code" = 200 ]; then int id; return; fi
|
|
# fall back to scanning the releases list (tag lookup 404s on some versions).
|
|
# Split the array into per-release chunks and read the id of the chunk whose
|
|
# tag_name matches — avoids a greedy match spanning objects.
|
|
api GET "/releases?limit=50" >/dev/null || true
|
|
tr '{' '\n' < "$BODYF" | grep -F "\"tag_name\":\"$TAG\"" \
|
|
| grep -o '"id"[[:space:]]*:[[:space:]]*[0-9]\{1,\}' | head -n1 | grep -o '[0-9]\{1,\}'
|
|
}
|
|
|
|
rid="$(find_release || true)"
|
|
if [ -n "${rid:-}" ]; then
|
|
echo "[release] deleting existing release id=$rid"
|
|
api DELETE "/releases/$rid" >/dev/null || true
|
|
fi
|
|
|
|
# For a rolling tag, drop the git tag so it re-points at $TARGET on recreate.
|
|
if [ "$ROLLING" = true ]; then
|
|
api DELETE "/tags/$TAG" >/dev/null 2>&1 || true
|
|
fi
|
|
|
|
# --- create the release (idempotent: reuse on 409) ---------------------------
|
|
esc_body=$(printf '%s' "$BODY" | sed 's/\\/\\\\/g; s/"/\\"/g' | awk 'BEGIN{ORS="\\n"}{print}')
|
|
payload=$(printf '{"tag_name":"%s","target_commitish":"%s","name":"%s","body":"%s","draft":false,"prerelease":%s}' \
|
|
"$TAG" "$TARGET" "$NAME" "$esc_body" "$PRERELEASE")
|
|
|
|
code=$(api POST "/releases" -H 'Content-Type: application/json' -d "$payload")
|
|
if [ "$code" = 201 ] || [ "$code" = 200 ]; then
|
|
rid=$(int id)
|
|
elif [ "$code" = 409 ]; then
|
|
echo "[release] 409 on create — reusing existing release for tag $TAG"
|
|
rid="$(find_release || true)"
|
|
else
|
|
echo "[release] ERROR: create returned HTTP $code: $(cat "$BODYF")" >&2; exit 1
|
|
fi
|
|
[ -n "${rid:-}" ] || { echo "[release] ERROR: no release id after create (HTTP $code)" >&2; cat "$BODYF" >&2; exit 1; }
|
|
echo "[release] release id=$rid"
|
|
|
|
# --- upload assets, replacing any of the same name ---------------------------
|
|
api GET "/releases/$rid/assets" >/dev/null || true
|
|
assets_body="$(cat "$BODYF")"
|
|
for f in "$@"; do
|
|
[ -f "$f" ] || { echo "[release] skip missing $f"; continue; }
|
|
base=$(basename "$f")
|
|
# delete a pre-existing asset with this name (idempotent re-run)
|
|
aid=$(printf '%s' "$assets_body" | sed -n 's/.*"id":[[:space:]]*\([0-9]*\)[^}]*"name":[[:space:]]*"'"$base"'".*/\1/p' | head -n1)
|
|
[ -n "$aid" ] && api DELETE "/releases/$rid/assets/$aid" >/dev/null 2>&1 || true
|
|
echo "[release] uploading $base"
|
|
code=$(api POST "/releases/$rid/assets?name=$base" -F "attachment=@$f;filename=$base")
|
|
case "$code" in
|
|
201|200) ;;
|
|
*) echo "[release] ERROR uploading $base: HTTP $code: $(cat "$BODYF")" >&2; exit 1 ;;
|
|
esac
|
|
done
|
|
|
|
rm -f "$BODYF"
|
|
echo "[release] done: $SERVER/$REPO/releases/tag/$TAG"
|