Revert "Split release publishing into a separate public distribution repo"
This reverts commit 1d2246c5e2.
This commit is contained in:
@@ -1,14 +1,5 @@
|
||||
name: Release Plugin
|
||||
|
||||
# Releases are published to a *separate public* distribution repo, not this one.
|
||||
# Gitea has no way to make individual release assets public while the source repo
|
||||
# stays private -- repo visibility is all-or-nothing, confirmed against Gitea's own
|
||||
# docs/issue tracker -- so the actual plugin zip + manifest.json live in
|
||||
# PUBLIC_REPO_NAME below, kept in sync by this workflow. This (private source) repo
|
||||
# never needs to be public for Jellyfin installs to work.
|
||||
env:
|
||||
PUBLIC_REPO_NAME: JellyfinSyncPlus-repo
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
@@ -32,6 +23,7 @@ jobs:
|
||||
id: meta
|
||||
run: |
|
||||
echo REPO_OWNER=$(echo ${GITHUB_REPOSITORY} | awk -F"/" '{print $1}') >> $GITHUB_OUTPUT
|
||||
echo REPO_NAME=$(echo ${GITHUB_REPOSITORY} | awk -F"/" '{print $2}') >> $GITHUB_OUTPUT
|
||||
echo VERSION=$(echo ${GITHUB_REF_NAME} | sed 's/^v//') >> $GITHUB_OUTPUT
|
||||
cat $GITHUB_OUTPUT
|
||||
|
||||
@@ -46,40 +38,53 @@ jobs:
|
||||
docker cp jf-extract:/usr/lib/jellyfin/bin/Emby.Server.Implementations.dll lib/jellyfin-10.11.6/
|
||||
docker rm jf-extract
|
||||
|
||||
- name: Build and package
|
||||
id: package
|
||||
- name: Build, package, update manifest
|
||||
env:
|
||||
VERSION: ${{ steps.meta.outputs.VERSION }}
|
||||
SERVER_URL: ${{ github.server_url }}
|
||||
PUBLIC_REPO_OWNER: ${{ steps.meta.outputs.REPO_OWNER }}
|
||||
PUBLIC_REPO_NAME: ${{ env.PUBLIC_REPO_NAME }}
|
||||
REPO_OWNER: ${{ steps.meta.outputs.REPO_OWNER }}
|
||||
REPO_NAME: ${{ steps.meta.outputs.REPO_NAME }}
|
||||
run: dev/package-release.sh
|
||||
|
||||
- name: Create release and upload zip on the public repo
|
||||
- name: Create Gitea release and upload zip
|
||||
env:
|
||||
GITEA_TOKEN: ${{ secrets.RELEASE_TOKEN }}
|
||||
SERVER_URL: ${{ github.server_url }}
|
||||
PUBLIC_REPO_OWNER: ${{ steps.meta.outputs.REPO_OWNER }}
|
||||
REPO_OWNER: ${{ steps.meta.outputs.REPO_OWNER }}
|
||||
REPO_NAME: ${{ steps.meta.outputs.REPO_NAME }}
|
||||
VERSION: ${{ steps.meta.outputs.VERSION }}
|
||||
TAG: ${{ github.ref_name }}
|
||||
ZIP_PATH: ${{ steps.package.outputs.ZIP_PATH }}
|
||||
run: |
|
||||
RELEASE_ID=$(curl -sf -X POST \
|
||||
-H "Authorization: token ${GITEA_TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"tag_name\":\"${TAG}\",\"name\":\"${TAG}\",\"body\":\"See build.yaml changelog / manifest.json for this version's notes.\"}" \
|
||||
"${SERVER_URL}/api/v1/repos/${PUBLIC_REPO_OWNER}/${PUBLIC_REPO_NAME}/releases" | python3 -c 'import json,sys; print(json.load(sys.stdin)["id"])')
|
||||
"${SERVER_URL}/api/v1/repos/${REPO_OWNER}/${REPO_NAME}/releases" | python3 -c 'import json,sys; print(json.load(sys.stdin)["id"])')
|
||||
curl -sf -X POST \
|
||||
-H "Authorization: token ${GITEA_TOKEN}" \
|
||||
-F "attachment=@${ZIP_PATH}" \
|
||||
"${SERVER_URL}/api/v1/repos/${PUBLIC_REPO_OWNER}/${PUBLIC_REPO_NAME}/releases/${RELEASE_ID}/assets?name=JellyfinSyncPlus_${VERSION}.zip"
|
||||
-F "attachment=@release/JellyfinSyncPlus_${VERSION}.zip" \
|
||||
"${SERVER_URL}/api/v1/repos/${REPO_OWNER}/${REPO_NAME}/releases/${RELEASE_ID}/assets?name=JellyfinSyncPlus_${VERSION}.zip"
|
||||
|
||||
- name: Publish manifest.json to the public repo
|
||||
# manifest.json has to live at a stable URL on a normal branch (master) --
|
||||
# that's the one URL admins add to Jellyfin once, and every future release
|
||||
# just appends to it. The tag checkout above is detached HEAD, so switch to
|
||||
# master to commit, carrying the just-updated manifest.json across.
|
||||
- name: Commit updated manifest to master
|
||||
env:
|
||||
GITEA_TOKEN: ${{ secrets.RELEASE_TOKEN }}
|
||||
SERVER_URL: ${{ github.server_url }}
|
||||
PUBLIC_REPO_OWNER: ${{ steps.meta.outputs.REPO_OWNER }}
|
||||
REPO_OWNER: ${{ steps.meta.outputs.REPO_OWNER }}
|
||||
REPO_NAME: ${{ steps.meta.outputs.REPO_NAME }}
|
||||
VERSION: ${{ steps.meta.outputs.VERSION }}
|
||||
CHECKSUM: ${{ steps.package.outputs.CHECKSUM }}
|
||||
SOURCE_URL: ${{ steps.package.outputs.SOURCE_URL }}
|
||||
run: dev/publish-manifest.sh
|
||||
run: |
|
||||
cp manifest.json /tmp/manifest.json
|
||||
git fetch origin master
|
||||
git checkout master
|
||||
cp /tmp/manifest.json manifest.json
|
||||
git config user.name "gitea-actions"
|
||||
git config user.email "actions@gitea.local"
|
||||
git add manifest.json
|
||||
git diff --cached --quiet && echo "No manifest changes to commit" && exit 0
|
||||
git commit -m "Release ${VERSION}"
|
||||
HOST=$(echo "${SERVER_URL}" | sed 's#https\?://##')
|
||||
git push "https://gitea-actions:${GITEA_TOKEN}@${HOST}/${REPO_OWNER}/${REPO_NAME}.git" HEAD:master
|
||||
|
||||
@@ -149,55 +149,13 @@ Two different loops -- do not conflate them:
|
||||
briefly interrupts the live, shared Jellyfin instance -- **do not do this
|
||||
routinely; only for actual multi-friend sync test sessions, and confirm
|
||||
with the user first.**
|
||||
3. **Release distribution -- DONE (Phase 5, built 2026-07-09).** Package as a
|
||||
versioned zip + `manifest.json`, host via Gitea, add the repo URL in
|
||||
Jellyfin's dashboard. Mirrors MR-Discord's tag -> Gitea Actions -> build
|
||||
pipeline, just producing a plugin zip instead of a Docker image.
|
||||
- **Source repo (`JellyfinSyncPlus`) stays private; a separate public repo
|
||||
(`JellyfinSyncPlus-repo`) holds only `manifest.json` and release zips.**
|
||||
Confirmed via Gitea's own docs/issue tracker: there's no way to make
|
||||
individual release assets public while the repo itself stays private --
|
||||
repo visibility is all-or-nothing, covering raw files, releases, and API
|
||||
access alike. So the two are fully decoupled: CI builds from the private
|
||||
source repo (has the checkout + build context) but publishes the
|
||||
release and updates the manifest on the separate public repo, using the
|
||||
same `RELEASE_TOKEN` (Gitea personal access tokens are user-scoped, not
|
||||
repo-scoped, so one token works against both repos as long as the
|
||||
owning account has write access to each).
|
||||
- `dev/package-release.sh` -- builds Release config, zips the DLL,
|
||||
computes its MD5 checksum, derives the release download URL (pointing
|
||||
at the *public* repo). Live-tested locally twice (once before, once
|
||||
after the public/private split) -- produces a real, valid zip + a
|
||||
`manifest.json` schema-verified against a real published Jellyfin
|
||||
plugin repo's actual file, not just assumed from memory.
|
||||
- `dev/publish-manifest.sh` -- fetches the public repo's current
|
||||
`manifest.json` via Gitea's Contents API (or starts fresh on the very
|
||||
first release), merges in this version via `dev/update_manifest.py`,
|
||||
and pushes it back via the same API. No git clone of the public repo
|
||||
needed.
|
||||
- `.github/workflows/release.yml` -- on a `v*` tag push: builds
|
||||
(including `dev/extract-private-refs.sh`'s CI equivalent, pulling the
|
||||
two private SDK assemblies straight from the pinned
|
||||
`linuxserver/jellyfin:10.11.6` image via `docker cp`), packages,
|
||||
creates a Gitea release + uploads the zip on the *public* repo, then
|
||||
publishes the manifest there too.
|
||||
- `.github/workflows/build.yml` -- plain build-check on every push/PR to
|
||||
`master`, mirroring MR-Discord's `build.yml` pattern.
|
||||
- **Live-verified 2026-07-09, partially**: tagged and pushed `v0.2.0.0`
|
||||
against the *first* version of this pipeline (before the public/private
|
||||
split existed) -- confirmed the workflow actually ran, created a real
|
||||
Gitea release with the zip attached, and committed a real
|
||||
`manifest.json`. That run published to the private source repo itself,
|
||||
which is exactly the mistake the public/private split above exists to
|
||||
fix; the updated, split version hasn't had a live tag-triggered CI run
|
||||
yet as of this note -- the local packaging half is tested, the
|
||||
`publish-manifest.sh` Contents API calls are syntax/logic-checked but
|
||||
not yet exercised against a real Gitea instance.
|
||||
- Once a plugin is actually installed from this repo, Jellyfin has no
|
||||
hot-reload -- same as local dev, a server restart is required. On the
|
||||
real k8s deployment that's the "confirm with the user first" milestone
|
||||
action from the "Dev / test / iterate workflow" section above, not a
|
||||
routine step.
|
||||
3. **Release distribution (later, once stable):** package as a versioned zip
|
||||
+ `meta.json`, host a plugin-repository manifest via Gitea (raw file URLs
|
||||
or release attachments both work, Jellyfin just needs HTTP(S) access to
|
||||
the manifest + zip), add the repo URL in Jellyfin's dashboard. Mirrors
|
||||
MR-Discord's tag -> Gitea Actions -> build pipeline, just producing a
|
||||
plugin zip instead of a Docker image. Slow iteration loop -- not for
|
||||
development, only for shipping tested versions.
|
||||
|
||||
## Known friction / risks (flagged going in, not discovered later)
|
||||
|
||||
|
||||
+16
-21
@@ -1,21 +1,18 @@
|
||||
#!/usr/bin/env bash
|
||||
# Builds and packages a tagged release. Used by .github/workflows/release.yml; safe to
|
||||
# run locally too for a dry run. Does NOT touch manifest.json -- that's published
|
||||
# separately to the public distribution repo by dev/publish-manifest.sh, since the zip
|
||||
# this produces gets uploaded there too (release assets on a private repo can't be made
|
||||
# public on their own in Gitea; see PLAN.md "Release distribution").
|
||||
# Builds, packages, and updates manifest.json for a tagged release. Used by
|
||||
# .github/workflows/release.yml; safe to run locally too for a dry run.
|
||||
#
|
||||
# Required env vars:
|
||||
# VERSION -- e.g. 0.2.0.0 (must match build.yaml's version)
|
||||
# SERVER_URL -- e.g. https://gitea.mrcynic.site
|
||||
# PUBLIC_REPO_OWNER -- Gitea org/user the *public* distribution repo lives under
|
||||
# PUBLIC_REPO_NAME -- the public distribution repo's name (holds releases + manifest.json)
|
||||
# VERSION -- e.g. 0.2.0.0 (must match build.yaml's version)
|
||||
# SERVER_URL -- e.g. https://gitea.mrcynic.site
|
||||
# REPO_OWNER -- Gitea org/user the repo lives under
|
||||
# REPO_NAME -- Gitea repo name
|
||||
set -euo pipefail
|
||||
|
||||
: "${VERSION:?VERSION env var required, e.g. 0.2.0.0}"
|
||||
: "${SERVER_URL:?SERVER_URL env var required, e.g. https://gitea.mrcynic.site}"
|
||||
: "${PUBLIC_REPO_OWNER:?PUBLIC_REPO_OWNER env var required}"
|
||||
: "${PUBLIC_REPO_NAME:?PUBLIC_REPO_NAME env var required}"
|
||||
: "${REPO_OWNER:?REPO_OWNER env var required}"
|
||||
: "${REPO_NAME:?REPO_NAME env var required}"
|
||||
|
||||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
cd "$ROOT"
|
||||
@@ -37,17 +34,15 @@ rm -f "$ZIP_PATH"
|
||||
(cd src/JellyfinSyncPlus/bin/Release/net9.0 && zip -j "$ROOT/$ZIP_PATH" JellyfinSyncPlus.dll)
|
||||
|
||||
CHECKSUM=$(md5sum "$ZIP_PATH" | awk '{print $1}')
|
||||
SOURCE_URL="${SERVER_URL}/${PUBLIC_REPO_OWNER}/${PUBLIC_REPO_NAME}/releases/download/v${VERSION}/${ZIP_NAME}"
|
||||
SOURCE_URL="${SERVER_URL}/${REPO_OWNER}/${REPO_NAME}/releases/download/v${VERSION}/${ZIP_NAME}"
|
||||
|
||||
python3 "$ROOT/dev/update_manifest.py" \
|
||||
--build-yaml "$ROOT/build.yaml" \
|
||||
--manifest "$ROOT/manifest.json" \
|
||||
--version "$VERSION" \
|
||||
--checksum "$CHECKSUM" \
|
||||
--source-url "$SOURCE_URL"
|
||||
|
||||
echo "Packaged $ZIP_PATH"
|
||||
echo "Checksum: $CHECKSUM"
|
||||
echo "Source URL: $SOURCE_URL"
|
||||
|
||||
# For the next workflow step to pick up without re-deriving them (no-op outside CI).
|
||||
if [ -n "${GITHUB_OUTPUT:-}" ]; then
|
||||
{
|
||||
echo "CHECKSUM=${CHECKSUM}"
|
||||
echo "SOURCE_URL=${SOURCE_URL}"
|
||||
echo "ZIP_PATH=${ZIP_PATH}"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
@@ -1,65 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
# Fetches the current manifest.json from the public distribution repo (or starts fresh
|
||||
# if this is the first release), merges in this build's version entry, and pushes it
|
||||
# back -- all via Gitea's Contents API, so this never needs a git clone of the public
|
||||
# repo. Keeps the private source repo and the public distribution repo fully decoupled.
|
||||
#
|
||||
# Required env vars:
|
||||
# GITEA_TOKEN, SERVER_URL, PUBLIC_REPO_OWNER, PUBLIC_REPO_NAME, VERSION, CHECKSUM, SOURCE_URL
|
||||
set -euo pipefail
|
||||
|
||||
: "${GITEA_TOKEN:?}"
|
||||
: "${SERVER_URL:?}"
|
||||
: "${PUBLIC_REPO_OWNER:?}"
|
||||
: "${PUBLIC_REPO_NAME:?}"
|
||||
: "${VERSION:?}"
|
||||
: "${CHECKSUM:?}"
|
||||
: "${SOURCE_URL:?}"
|
||||
|
||||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
API="${SERVER_URL}/api/v1/repos/${PUBLIC_REPO_OWNER}/${PUBLIC_REPO_NAME}/contents/manifest.json"
|
||||
WORKDIR=$(mktemp -d)
|
||||
trap 'rm -rf "$WORKDIR"' EXIT
|
||||
|
||||
HTTP_CODE=$(curl -s -o "$WORKDIR/existing.json" -w "%{http_code}" \
|
||||
-H "Authorization: token ${GITEA_TOKEN}" "${API}?ref=master")
|
||||
|
||||
if [ "$HTTP_CODE" = "200" ]; then
|
||||
SHA=$(python3 -c 'import json; print(json.load(open("'"$WORKDIR"'/existing.json"))["sha"])')
|
||||
python3 -c 'import json,base64
|
||||
d = json.load(open("'"$WORKDIR"'/existing.json"))
|
||||
open("'"$WORKDIR"'/manifest.json", "wb").write(base64.b64decode(d["content"]))'
|
||||
echo "Fetched existing manifest.json (sha ${SHA})"
|
||||
else
|
||||
SHA=""
|
||||
echo "[]" > "$WORKDIR/manifest.json"
|
||||
echo "No existing manifest.json in ${PUBLIC_REPO_OWNER}/${PUBLIC_REPO_NAME} -- starting fresh (HTTP $HTTP_CODE)"
|
||||
fi
|
||||
|
||||
python3 "$ROOT/dev/update_manifest.py" \
|
||||
--build-yaml "$ROOT/build.yaml" \
|
||||
--manifest "$WORKDIR/manifest.json" \
|
||||
--version "$VERSION" \
|
||||
--checksum "$CHECKSUM" \
|
||||
--source-url "$SOURCE_URL"
|
||||
|
||||
CONTENT_B64=$(base64 -w0 "$WORKDIR/manifest.json")
|
||||
|
||||
python3 -c 'import json,sys
|
||||
body = {"content": sys.argv[1], "message": "Release " + sys.argv[2], "branch": "master"}
|
||||
if sys.argv[3]:
|
||||
body["sha"] = sys.argv[3]
|
||||
json.dump(body, open(sys.argv[4], "w"))' "$CONTENT_B64" "$VERSION" "$SHA" "$WORKDIR/body.json"
|
||||
|
||||
METHOD="POST"
|
||||
if [ -n "$SHA" ]; then
|
||||
METHOD="PUT"
|
||||
fi
|
||||
|
||||
curl -sf -X "$METHOD" \
|
||||
-H "Authorization: token ${GITEA_TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "@$WORKDIR/body.json" \
|
||||
"$API" > /dev/null
|
||||
|
||||
echo "Published manifest.json version ${VERSION} to ${PUBLIC_REPO_OWNER}/${PUBLIC_REPO_NAME}"
|
||||
@@ -0,0 +1,20 @@
|
||||
[
|
||||
{
|
||||
"guid": "267dbfe9-bb9c-4eeb-97aa-f0449283cfe6",
|
||||
"name": "JellyfinSyncPlus",
|
||||
"description": "Read-only live view of every active SyncPlay group's members -- drift, position, play method, bitrate, and codec info -- plus a manual \"sync me to group\" button for one-shot self-correction. Does not touch playback automatically: earlier attempts at automatic quality-forcing and automatic drift correction both caused real problems in live use and were reverted (see PLAN.md). Reachable via Dashboard -> Plugins -> JellyfinSyncPlus.",
|
||||
"overview": "SyncPlay stats for nerds: live drift/playback view plus a manual one-shot sync button.",
|
||||
"owner": "cynic",
|
||||
"category": "General",
|
||||
"versions": [
|
||||
{
|
||||
"version": "0.2.0.0",
|
||||
"changelog": "0.2.0.0: SyncPlay stats-for-nerds page and manual one-shot \"sync me to group\" button. No automatic playback intervention. 0.1.0.0: Phase 0 scaffold. Trivial plugin, no functionality yet.",
|
||||
"targetAbi": "10.11.6.0",
|
||||
"sourceUrl": "https://gitea.mrcynic.site/seer/JellyfinSyncPlus/releases/download/v0.2.0.0/JellyfinSyncPlus_0.2.0.0.zip",
|
||||
"checksum": "34e384fc5914e2c459f52a9a7bfd91a5",
|
||||
"timestamp": "2026-07-09T15:11:18Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
Reference in New Issue
Block a user