name: Build and Release Jellyfin plugins on: push: paths: - "Jellyfin/**" jobs: release: runs-on: ubuntu-latest # Avoid rebuild loops from catalog commits if: ${{ !contains(github.event.head_commit.message, '[skip ci]') }} steps: - name: Checkout uses: actions/checkout@v4 with: token: ${{ secrets.GITHUB_TOKEN }} fetch-depth: 2 - name: Detect changed plugins id: detect run: | set -euo pipefail changed=$(git diff --name-only HEAD~1 HEAD -- 'Jellyfin/' || true) if [[ -z "$changed" ]]; then echo "No Jellyfin changes" echo "plugins=" >> "$GITHUB_OUTPUT" exit 0 fi # Ignore catalog-only pushes non_manifest=$(echo "$changed" | grep -v '^Jellyfin/manifest.json$' || true) if [[ -z "$non_manifest" ]]; then echo "Only Jellyfin/manifest.json changed — skipping" echo "plugins=" >> "$GITHUB_OUTPUT" exit 0 fi plugins=$(echo "$non_manifest" \ | awk -F/ '$1=="Jellyfin" && NF>=2 {print $2}' \ | grep -v '^_template$' \ | grep -v '^README.md$' \ | sort -u) to_release="" for slug in $plugins; do dir="Jellyfin/${slug}" if [[ -f "${dir}/build.yaml" && -f "${dir}/version" ]]; then to_release="${to_release}${slug}"$'\n' elif [[ -d "$dir" ]]; then echo "Skipping ${slug}: need build.yaml + version file" fi done to_release=$(printf '%s' "$to_release" | sed '/^$/d') echo "Changed plugins to release:" echo "$to_release" { echo "plugins<> "$GITHUB_OUTPUT" - name: Setup .NET if: steps.detect.outputs.plugins != '' uses: actions/setup-dotnet@v4 with: dotnet-version: "9.0.x" - name: Install jprm if: steps.detect.outputs.plugins != '' run: | python3 -m venv /tmp/jprm-venv /tmp/jprm-venv/bin/pip install --upgrade pip jprm - name: Build and release plugins if: steps.detect.outputs.plugins != '' env: GITEA_TOKEN: ${{ secrets.GITHUB_TOKEN }} PLUGINS: ${{ steps.detect.outputs.plugins }} run: | set -euo pipefail GITEA_URL="${{ github.server_url }}" REPO_OWNER="${{ github.repository_owner }}" REPO_NAME="${{ github.event.repository.name }}" git config user.name "gitea-actions" git config user.email "actions@local" manifest_dirty=0 while IFS= read -r SLUG; do [[ -z "$SLUG" ]] && continue PLUGIN_DIR="Jellyfin/${SLUG}" VERSION=$(tr -d '[:space:]' < "${PLUGIN_DIR}/version") if [[ ! "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then echo "Invalid version in ${PLUGIN_DIR}/version: '${VERSION}' (expected X.Y.Z)" exit 1 fi VERSION4="${VERSION}.0" TAG="jellyfin/${SLUG}/v${VERSION}" echo "::group::Release ${SLUG} ${VERSION}" # Skip if this version was already released HTTP=$(curl -sS -o /tmp/rel.json -w "%{http_code}" \ -H "Authorization: token ${GITEA_TOKEN}" \ "${GITEA_URL}/api/v1/repos/${REPO_OWNER}/${REPO_NAME}/releases/tags/${TAG}") if [[ "$HTTP" == "200" ]]; then echo "Release ${TAG} already exists — skipping build" echo "::endgroup::" continue fi sed -i -E "s/^version:.*/version: \"${VERSION4}\"/" "${PLUGIN_DIR}/build.yaml" mkdir -p "${PLUGIN_DIR}/artifacts" rm -f "${PLUGIN_DIR}/artifacts"/*.zip /tmp/jprm-venv/bin/jprm --verbosity=info plugin build "${PLUGIN_DIR}" --output "${PLUGIN_DIR}/artifacts" ARTIFACT=$(find "${PLUGIN_DIR}/artifacts" -type f -name '*.zip' | head -n1) if [[ -z "$ARTIFACT" ]]; then echo "No zip produced for ${SLUG}" ls -laR "${PLUGIN_DIR}/artifacts" || true exit 1 fi ARTIFACT_NAME=$(basename "$ARTIFACT") CHECKSUM=$(sha256sum "$ARTIFACT" | awk '{print $1}') BODY=$(printf 'Jellyfin plugin **%s** %s\n\nBuilt automatically from Jellyfin/%s changes.' "$SLUG" "$VERSION" "$SLUG") BODY_JSON=$(printf '%s' "$BODY" | jq -Rs .) RESPONSE=$(curl -sS -w "\n%{http_code}" -X POST \ -H "Authorization: token ${GITEA_TOKEN}" \ -H "Content-Type: application/json" \ "${GITEA_URL}/api/v1/repos/${REPO_OWNER}/${REPO_NAME}/releases" \ -d "{ \"tag_name\": \"${TAG}\", \"name\": \"${SLUG} ${VERSION}\", \"body\": ${BODY_JSON}, \"draft\": false, \"prerelease\": false }") HTTP_CODE=$(echo "$RESPONSE" | tail -n1) RESP_BODY=$(echo "$RESPONSE" | sed '$d') if [[ "$HTTP_CODE" != "201" && "$HTTP_CODE" != "200" ]]; then echo "Failed to create release ${TAG}: HTTP $HTTP_CODE" echo "$RESP_BODY" exit 1 fi RELEASE_ID=$(echo "$RESP_BODY" | jq -r '.id') curl -sS -f -X POST \ -H "Authorization: token ${GITEA_TOKEN}" \ -H "Content-Type: application/zip" \ --data-binary "@${ARTIFACT}" \ "${GITEA_URL}/api/v1/repos/${REPO_OWNER}/${REPO_NAME}/releases/${RELEASE_ID}/assets?name=${ARTIFACT_NAME}" DOWNLOAD_URL="${GITEA_URL}/${REPO_OWNER}/${REPO_NAME}/releases/download/${TAG}/${ARTIFACT_NAME}" TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ") GUID=$(grep -E '^guid:' "${PLUGIN_DIR}/build.yaml" | head -1 | sed -E 's/guid:[[:space:]]*"?([^"]*)"?/\1/') TARGET_ABI=$(grep -E '^targetAbi:' "${PLUGIN_DIR}/build.yaml" | head -1 | sed -E 's/targetAbi:[[:space:]]*"?([^"]*)"?/\1/') NEW_VERSION=$(jq -n \ --arg version "$VERSION4" \ --arg changelog "Release ${VERSION}" \ --arg targetAbi "$TARGET_ABI" \ --arg sourceUrl "$DOWNLOAD_URL" \ --arg checksum "$CHECKSUM" \ --arg timestamp "$TIMESTAMP" \ '{ version: $version, changelog: $changelog, targetAbi: $targetAbi, sourceUrl: $sourceUrl, checksum: $checksum, timestamp: $timestamp }') jq --arg guid "$GUID" --argjson newver "$NEW_VERSION" ' map(if .guid == $guid then .versions = ([$newver] + (.versions // [])) else . end) ' Jellyfin/manifest.json > Jellyfin/manifest.json.tmp mv Jellyfin/manifest.json.tmp Jellyfin/manifest.json manifest_dirty=1 echo "Released ${TAG} → ${DOWNLOAD_URL}" echo "::endgroup::" done <<< "$PLUGINS" if [[ "$manifest_dirty" -eq 1 ]]; then git add Jellyfin/manifest.json # Also commit synced build.yaml version lines if changed git add Jellyfin/*/build.yaml 2>/dev/null || true if ! git diff --staged --quiet; then git commit -m "chore(jellyfin): update plugin catalog [skip ci]" git push fi fi