Files
jellyfin-plugin-recently-ad…/.gitea/workflows/release.yaml
T
Jonas MenzelandClaude Opus 5.5 333ac6f6ce
build / build (push) Successful in 44s
release / release (push) Successful in 1m26s
fix(ci): absolute manifest path for jprm and stable release download URL
jprm rejects a bare file name as repository path (it checks the directory
"" for existence). The asset upload answers with an /attachments/<uuid>
link; the manifest now uses /releases/download/<tag>/<file> instead.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 23:23:52 +02:00

199 lines
8.6 KiB
YAML

# Release on tag vA.B.C.D (Jellyfin plugins use four-part versions):
# 1. test, 2. build the plugin ZIP with jprm (DLL + meta.json only), 3. publish a Gitea release
# with the ZIP, 4. add the version to manifest.json on the `manifest` branch (the URL Jellyfin reads),
# 5. check that manifest and ZIP are reachable anonymously and the MD5 matches.
# The automatic GITEA_TOKEN does all of this; Gitea never starts workflows for pushes made with it,
# so the manifest commit triggers no further run.
name: release
on:
push:
tags: ["v*"]
concurrency:
group: recently-added-guard-release
cancel-in-progress: false
# contents: write = Code (manifest branch) + Releases
permissions:
contents: write
env:
MANIFEST_BRANCH: manifest
JPRM_VERSION: "1.1.0"
PLUGIN_GUID: 6fdfedef-adde-4ac3-a873-62a057c95574
jobs:
release:
runs-on: ubuntu-latest
container:
image: mcr.microsoft.com/dotnet/sdk:10.0
timeout-minutes: 30
defaults:
run:
shell: bash
env:
DOTNET_CLI_TELEMETRY_OPTOUT: "1"
DOTNET_NOLOGO: "1"
DEBIAN_FRONTEND: noninteractive
PYTHONUTF8: "1"
TOKEN: ${{ secrets.GITEA_TOKEN }}
steps:
# The SDK image has no Node.js, so actions/checkout cannot run; plain git instead.
- name: Checkout tag
run: |
git config --global --add safe.directory '*'
git init -q .
git remote add origin "$GITHUB_SERVER_URL/$GITHUB_REPOSITORY.git"
git config http.extraHeader "Authorization: basic $(printf 'x-access-token:%s' "$TOKEN" | base64 -w0)"
git fetch -q --tags origin "+refs/heads/*:refs/remotes/origin/*"
git checkout -q "$GITHUB_SHA"
git log -1 --oneline
- name: Version and changelog
id: meta
run: |
tag="$GITHUB_REF_NAME"
version="${tag#v}"
if ! [[ "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo "Tag $tag hat nicht das Format vA.B.C.D" >&2
exit 1
fi
# Changelog: message of an annotated tag, otherwise the commit subjects since the previous tag.
: > changelog.md
if [ "$(git cat-file -t "refs/tags/$tag")" = tag ]; then
git tag -l --format='%(contents)' "$tag" | sed '/-----BEGIN PGP SIGNATURE-----/,$d' > changelog.md
fi
if ! grep -q '[^[:space:]]' changelog.md; then
previous=$(git describe --tags --abbrev=0 --match 'v*' "$tag^" 2>/dev/null || true)
git log --no-merges --pretty='- %s' "${previous:+$previous..}$tag" > changelog.md
fi
cat changelog.md
echo "version=$version" >> "$GITHUB_OUTPUT"
- name: Test
run: dotnet test -c Release
- name: Install jprm
run: |
apt-get update -qq
apt-get install -y -qq --no-install-recommends python3-venv > /dev/null
python3 -m venv /opt/jprm
/opt/jprm/bin/pip install -q "jprm==$JPRM_VERSION"
- name: Build plugin ZIP
id: build
env:
VERSION: ${{ steps.meta.outputs.version }}
run: |
# Version and changelog go through build.yaml into meta.json (only this CI checkout changes).
/opt/jprm/bin/python - "$VERSION" <<'EOF'
import sys
import yaml
with open("build.yaml", encoding="utf-8") as fh:
config = yaml.safe_load(fh)
with open("changelog.md", encoding="utf-8") as fh:
config["changelog"] = fh.read().strip() or "Version " + sys.argv[1]
config["version"] = sys.argv[1]
with open("build.yaml", "w", encoding="utf-8") as fh:
yaml.safe_dump(config, fh, allow_unicode=True, sort_keys=False)
EOF
mkdir -p artifacts
zip=$(/opt/jprm/bin/jprm plugin build . --output artifacts --version "$VERSION")
contents=$(/opt/jprm/bin/python -c 'import sys, zipfile; print(" ".join(sorted(zipfile.ZipFile(sys.argv[1]).namelist())))' "$zip")
if [ "$contents" != "Jellyfin.Plugin.RecentlyAddedGuard.dll meta.json" ]; then
echo "Unerwarteter ZIP-Inhalt: $contents" >&2
exit 1
fi
md5sum "$zip"
echo "zip=$(realpath "$zip")" >> "$GITHUB_OUTPUT"
echo "name=$(basename "$zip")" >> "$GITHUB_OUTPUT"
- name: Publish Gitea release
id: release
env:
VERSION: ${{ steps.meta.outputs.version }}
ZIP: ${{ steps.build.outputs.zip }}
ASSET: ${{ steps.build.outputs.name }}
run: |
api="$GITHUB_SERVER_URL/api/v1/repos/$GITHUB_REPOSITORY"
auth=(-H "Authorization: token $TOKEN")
json() { /opt/jprm/bin/python -c "import json, sys; data = json.load(sys.stdin); $1" "${@:2}"; }
# A re-run reuses the release of the tag and replaces the asset.
if ! release=$(curl -fsS "${auth[@]}" "$api/releases/tags/$GITHUB_REF_NAME"); then
body=$(/opt/jprm/bin/python -c 'import json, sys; print(json.dumps({"tag_name": sys.argv[1], "name": "Recently Added Guard " + sys.argv[2], "body": open("changelog.md", encoding="utf-8").read()}))' "$GITHUB_REF_NAME" "$VERSION")
release=$(curl -fsS -X POST "${auth[@]}" -H "Content-Type: application/json" -d "$body" "$api/releases")
fi
release_id=$(printf '%s' "$release" | json 'print(data["id"])')
for asset_id in $(printf '%s' "$release" | json '[print(a["id"]) for a in data["assets"] if a["name"] == sys.argv[1]]' "$ASSET"); do
curl -fsS -X DELETE "${auth[@]}" "$api/releases/$release_id/assets/$asset_id"
done
curl -fsS -X POST "${auth[@]}" -F "attachment=@$ZIP" "$api/releases/$release_id/assets?name=$ASSET" > /dev/null
# The upload answers with an /attachments/<uuid> link; the release download URL is stable and readable.
url="$GITHUB_SERVER_URL/$GITHUB_REPOSITORY/releases/download/$GITHUB_REF_NAME/$ASSET"
echo "Release-Asset: $url"
echo "url=$url" >> "$GITHUB_OUTPUT"
- name: Update manifest.json
env:
VERSION: ${{ steps.meta.outputs.version }}
ZIP: ${{ steps.build.outputs.zip }}
URL: ${{ steps.release.outputs.url }}
run: |
workdir=$(mktemp -d)
if git show-ref --verify --quiet "refs/remotes/origin/$MANIFEST_BRANCH"; then
git worktree add -q --detach "$workdir" "origin/$MANIFEST_BRANCH"
else
git worktree add -q --orphan -b "$MANIFEST_BRANCH" "$workdir"
fi
cd "$workdir"
[ -f manifest.json ] || echo '[]' > manifest.json
# Adds the version in front (sorted descending) and keeps older versions; MD5 of the ZIP as checksum.
# jprm needs an absolute path: for a bare file name it checks the directory "" and fails.
/opt/jprm/bin/jprm repo add "$workdir/manifest.json" "$ZIP" --plugin-url "$URL"
git add manifest.json
if git diff --cached --quiet; then
echo "manifest.json unverändert"
exit 0
fi
git -c user.name="Gitea Actions" -c user.email="actions@noreply.git.menzel.center" \
commit -q -m "manifest: Recently Added Guard $VERSION"
git push -q origin "HEAD:refs/heads/$MANIFEST_BRANCH"
- name: Verify anonymous download and checksum
env:
VERSION: ${{ steps.meta.outputs.version }}
URL: ${{ steps.release.outputs.url }}
run: |
manifest_url="$GITHUB_SERVER_URL/$GITHUB_REPOSITORY/raw/branch/$MANIFEST_BRANCH/manifest.json"
# No credentials: Jellyfin fetches both anonymously.
curl -fsSL "$manifest_url" -o /tmp/manifest.json
curl -fsSL "$URL" -o /tmp/plugin.zip
/opt/jprm/bin/python - "$VERSION" "$URL" <<'EOF'
import hashlib
import json
import os
import sys
version, url = sys.argv[1], sys.argv[2]
with open("/tmp/manifest.json", encoding="utf-8") as fh:
manifest = json.load(fh)
plugin = next(p for p in manifest if p["guid"] == os.environ["PLUGIN_GUID"])
entry = next(v for v in plugin["versions"] if v["version"] == version)
with open("/tmp/plugin.zip", "rb") as fh:
md5 = hashlib.md5(fh.read()).hexdigest()
assert plugin["versions"][0]["version"] == version, "neue Version steht nicht vorne"
assert entry["sourceUrl"] == url, (entry["sourceUrl"], url)
assert entry["checksum"] == md5, (entry["checksum"], md5)
print(f"OK: {version} -> {url}, MD5 {md5}, targetAbi {entry['targetAbi']}")
EOF
echo "Repository-URL für Jellyfin: $manifest_url"