Files
jellyfin-srfPlay/.gitea/workflows/latest.yaml
T
dtourolleandClaude Opus 5.5 582c94f5f9
🏗️ Build Plugin / build (push) Successful in 2m27s
Nightly Build / nightly-build (push) Failing after 1m18s
🧪 Test Plugin / test (push) Successful in 43s
🚀 Release Plugin / build-and-release (push) Failing after 1m12s
Build one plugin per Jellyfin generation (10.9, 10.11, 12)
The single net8.0 build compiled against Jellyfin 10.9.11 still loads on
10.11 and 12, so channels and browsing keep working, but 10.11 changed
APIs in ways that only fail at runtime. A binary audit of the DLL against
the real server assemblies found 9 broken references on both 10.11 and
12 (none on 10.9/10.10):

- TaskTriggerInfo.Type became an enum. GetDefaultTriggers throws, so
  Jellyfin silently gives every SRF task a 24h fallback trigger. The
  recording scheduler therefore ran about once a day and scheduled
  recordings never started.
- ILibraryManager.GetItemList changed its return type: the expiration
  check crashed on every run.
- Jellyfin.Data.Entities.User moved (IUserManager.Users, GetUserData,
  SaveUserData, PlaybackProgressEventArgs.Users): resume cleanup returned
  500 and the playback-stop guard threw on every stop.

Jellyfin 12 also rejects the legacy X-Emby-Token header with an empty
401, which the recordings UI surfaced as "Unexpected end of JSON input".
The pages now send Authorization: MediaBrowser Token="...", verified on
10.9, 10.11 and 12.

The plugin now multi-targets net8.0/net9.0/net10.0 against Jellyfin
10.9.11/10.11.0/12.0.0 (the oldest package of each line, so each DLL
loads on every patch release of it), with JELLYFIN_10_11_OR_GREATER and
JELLYFIN_12_OR_GREATER for the differences. Trigger construction moves
into Utilities/TaskTriggers so the version switch lives in one place.

.gitea/scripts/build-plugins.sh builds one zip per generation. jprm has
no targetAbi flag, so it rewrites build.yaml per flavour and restores
it. Jellyfin installs the highest version whose targetAbi it satisfies,
so the flavour code goes into the last version segment:

  release v1.2.0 -> 1.2.0.9 / 1.2.0.11 / 1.2.0.12
  nightly        -> 1.0.<date>.<run>09 / 11 / 12

The PR, nightly and release workflows use the script and publish one
manifest entry per zip. Verified end to end against a three-entry
manifest: 10.11 installs the .11 build, 12.1 installs the .12 build.
The existing .NET 10 builder image builds all three unchanged.

Supporting changes: the test project multi-targets too, because jprm
publishes the whole solution for one framework at a time, and it drops
its Microsoft.Extensions.Logging 8.0.1 pin, which is a NU1605 downgrade
under the newer Jellyfin packages. CA1873 (new in the .NET 10 analyzers)
is silenced beside CA1848. The release manifest step checks out
origin/master like the nightly does since 7e1973a.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-24 01:42:38 +02:00

185 lines
8.0 KiB
YAML

name: 'Nightly Build'
on:
push:
branches:
- master
paths-ignore:
- '**/*.md'
- 'manifest.json'
- 'manifest-nightly.json'
jobs:
nightly-build:
runs-on: linux/amd64
container:
image: gitea.tourolle.paris/dtourolle/srfplay-builder:latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
path: build-${{ github.run_id }}
- name: Compute nightly version
id: version
run: |
# Date-based nightly version: 1.0.<YYYYMMDD>.<run_number>
# The run_number suffix keeps multiple builds on the same day
# monotonically increasing so Jellyfin always offers the newest.
# Each Jellyfin generation gets its own build, so the last segment
# is <run_number><2-digit flavour code>, e.g. run 204 -> 20409/20411/20412
# (see .gitea/scripts/build-plugins.sh).
DATE=$(date -u +"%Y%m%d")
VERSION="1.0.${DATE}.${{ github.run_number }}"
echo "version=${VERSION}" >> $GITHUB_OUTPUT
echo "format=${VERSION}%02d" >> $GITHUB_OUTPUT
echo "Nightly version: ${VERSION}"
- name: Restore dependencies
working-directory: build-${{ github.run_id }}
run: dotnet restore Jellyfin.Plugin.SRFPlay.sln
- name: Build solution
working-directory: build-${{ github.run_id }}
run: dotnet build Jellyfin.Plugin.SRFPlay.sln --configuration Release --no-restore --no-self-contained /m:1
- name: Run tests
working-directory: build-${{ github.run_id }}
run: dotnet test Jellyfin.Plugin.SRFPlay.sln --no-build --configuration Release --verbosity normal
- name: Build Jellyfin Plugins
working-directory: build-${{ github.run_id }}
run: .gitea/scripts/build-plugins.sh "${{ steps.version.outputs.format }}"
- name: Delete existing nightly release
working-directory: build-${{ github.run_id }}
env:
GITEA_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
REPO_OWNER="${{ github.repository_owner }}"
REPO_NAME="${{ github.event.repository.name }}"
GITEA_URL="${{ github.server_url }}"
TAG="latest"
EXISTING=$(curl -s -o /dev/null -w "%{http_code}" \
-H "Authorization: token ${GITEA_TOKEN}" \
"${GITEA_URL}/api/v1/repos/${REPO_OWNER}/${REPO_NAME}/releases/tags/${TAG}")
if [ "$EXISTING" = "200" ]; then
RELEASE_ID=$(curl -s \
-H "Authorization: token ${GITEA_TOKEN}" \
"${GITEA_URL}/api/v1/repos/${REPO_OWNER}/${REPO_NAME}/releases/tags/${TAG}" | jq -r '.id')
echo "Deleting existing nightly release (ID: ${RELEASE_ID})..."
curl -s -X DELETE \
-H "Authorization: token ${GITEA_TOKEN}" \
"${GITEA_URL}/api/v1/repos/${REPO_OWNER}/${REPO_NAME}/releases/${RELEASE_ID}"
fi
curl -s -X DELETE \
-H "Authorization: token ${GITEA_TOKEN}" \
"${GITEA_URL}/api/v1/repos/${REPO_OWNER}/${REPO_NAME}/tags/${TAG}" || true
- name: Create nightly release
working-directory: build-${{ github.run_id }}
env:
GITEA_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
REPO_OWNER="${{ github.repository_owner }}"
REPO_NAME="${{ github.event.repository.name }}"
GITEA_URL="${{ github.server_url }}"
VERSION="${{ steps.version.outputs.version }}"
SHORT_SHA=$(echo "${{ github.sha }}" | cut -c1-7)
RESPONSE=$(curl -s -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 "$(jq -n --arg tag "latest" --arg name "Nightly Build ${VERSION}" --arg body "SRFPlay Jellyfin Plugin nightly build ${VERSION} from master (${SHORT_SHA})." '{tag_name: $tag, name: $name, body: $body, target_commitish: "master", draft: false, prerelease: true}')")
HTTP_CODE=$(echo "$RESPONSE" | tail -n1)
BODY=$(echo "$RESPONSE" | sed '$d')
if [ "$HTTP_CODE" -ge 200 ] && [ "$HTTP_CODE" -lt 300 ]; then
RELEASE_ID=$(echo "$BODY" | jq -r '.id')
echo "Created release with ID: ${RELEASE_ID}"
else
echo "Failed to create release. HTTP ${HTTP_CODE}"
echo "$BODY"
exit 1
fi
# Upload one plugin zip per Jellyfin generation
while IFS=$'\t' read -r _ _ ZIP _; do
echo "Uploading ${ZIP}..."
curl -f -X POST \
-H "Authorization: token ${GITEA_TOKEN}" \
-H "Content-Type: application/zip" \
--data-binary "@${ZIP}" \
"${GITEA_URL}/api/v1/repos/${REPO_OWNER}/${REPO_NAME}/releases/${RELEASE_ID}/assets?name=$(basename "${ZIP}")"
done < artifacts/builds.tsv
# Upload build.yaml
echo "Uploading build.yaml..."
curl -f -X POST \
-H "Authorization: token ${GITEA_TOKEN}" \
-H "Content-Type: application/x-yaml" \
--data-binary "@build.yaml" \
"${GITEA_URL}/api/v1/repos/${REPO_OWNER}/${REPO_NAME}/releases/${RELEASE_ID}/assets?name=build.yaml"
echo "Nightly release updated successfully!"
- name: Update manifest-nightly.json
working-directory: build-${{ github.run_id }}
env:
GITEA_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
REPO_OWNER="${{ github.repository_owner }}"
REPO_NAME="${{ github.event.repository.name }}"
GITEA_URL="${{ github.server_url }}"
TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
SHORT_SHA=$(echo "${{ github.sha }}" | cut -c1-7)
# builds.tsv is read before the checkout below replaces the tree.
BUILDS=$(cat artifacts/builds.tsv)
git config user.name "Gitea Actions"
git config user.email "actions@gitea.tourolle.paris"
git fetch origin master
# -B against origin/master, not a bare `checkout master`. The bare
# form switches to the LOCAL master that actions/checkout left at the
# run's own SHA, so if master moved while the build ran -- and this is
# a nightly, so it usually has -- the manifest is edited on a stale
# base and the push is rejected non-fast-forward. Run 1045 failed
# exactly this way, having already printed "Your branch is behind
# 'origin/master' by 1 commit" one line before committing anyway.
#
# Resetting is right rather than merely convenient: this step appends
# one entry to the manifest as it stands on master, so the base it
# edits must be master, not whatever this build started from.
git checkout -B master origin/master
# One entry per Jellyfin generation; each server installs the newest
# entry whose targetAbi it satisfies.
NEW_VERSIONS=$(echo "${BUILDS}" | jq -R -s \
--arg url "${GITEA_URL}/${REPO_OWNER}/${REPO_NAME}/releases/download/latest" \
--arg sha "${SHORT_SHA}" --arg ts "${TIMESTAMP}" \
'split("\n") | map(select(length > 0) | split("\t")
| {version: .[0], changelog: "Nightly build \(.[0]) (\($sha))", targetAbi: .[1],
sourceUrl: "\($url)/\(.[2] | split("/") | last)", checksum: .[3], timestamp: $ts})
| reverse')
# Prepend the new builds and keep only the most recent 5 nightlies
# (3 entries each, one per Jellyfin generation).
jq --argjson newvers "${NEW_VERSIONS}" \
'.[0].versions = ($newvers + .[0].versions)[0:15]' \
manifest-nightly.json > manifest.tmp && mv manifest.tmp manifest-nightly.json
git add manifest-nightly.json
git commit -m "Nightly: ${{ steps.version.outputs.version }} (${SHORT_SHA})" || echo "No changes to commit"
git push origin master
- name: Cleanup
if: always()
run: rm -rf build-${{ github.run_id }}