Floatplane channel plugin for Jellyfin 10.10.x: browse, HLS proxy, cookie session, CI release
Some checks failed
release / build-release (push) Has been cancelled
Some checks failed
release / build-release (push) Has been cancelled
- IChannel: creators -> sub-channels -> video posts (public listing API, search mapped) - IRequiresMediaInfoCallback -> signed proxy URLs; stream controller rewrites HLS so the AES-128 key is fetched server-side with the session cookie, segments stay on the CDN - delivery/info cached per token lifetime and throttled (~2/min) - config page: paste sails.sid + validate, extra creators, quality, search test - scripts/package.sh -> plugin folder + zip + md5 + manifest.json; Gitea release workflow - research docs, HAR analysis (HAR itself gitignored), install-pi5.sh Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
127
install-pi5.sh
Executable file
127
install-pi5.sh
Executable file
@@ -0,0 +1,127 @@
|
||||
#!/bin/sh
|
||||
# Manual install/upgrade of the Floatplane plugin on a host that runs the
|
||||
# linuxserver/jellyfin container (plugins live in /config/data/plugins/ inside it).
|
||||
#
|
||||
# curl -fsSL https://gitea.brasse-pc.eu/brasse/jellyfin-plugin-floatplane/releases/download/latest/install-pi5.sh | sh
|
||||
# sh install-pi5.sh --dry-run # only print what would happen
|
||||
#
|
||||
# Environment (all optional):
|
||||
# TAG release to install: latest (default) or vX.Y.Z
|
||||
# JELLYFIN_CONFIG_DIR host path mounted as the container's /config (default /srv/docker/jellyfin,
|
||||
# i.e. ${DOCKER_PATH}/jellyfin on the Pi5)
|
||||
# JELLYFIN_CONTAINER container name to restart (default jellyfin); empty = don't restart
|
||||
# PUID / PGID owner for the plugin files (default: read from the container, else 1000:1000)
|
||||
#
|
||||
# NOTE (Pi5): this restarts the jellyfin container and writes under /srv/docker, so run it
|
||||
# as a user that may do that (Bjorn, typically with sudo). Nothing is changed before the
|
||||
# summary below is printed; pass --dry-run to stop there.
|
||||
set -eu
|
||||
|
||||
REPO_URL="https://gitea.brasse-pc.eu/brasse/jellyfin-plugin-floatplane"
|
||||
TAG="${TAG:-latest}"
|
||||
JELLYFIN_CONFIG_DIR="${JELLYFIN_CONFIG_DIR:-/srv/docker/jellyfin}"
|
||||
JELLYFIN_CONTAINER="${JELLYFIN_CONTAINER-jellyfin}"
|
||||
DRY_RUN=0
|
||||
for a in "$@"; do
|
||||
case "$a" in
|
||||
--dry-run|-n) DRY_RUN=1 ;;
|
||||
-h|--help) sed -n '2,20p' "$0"; exit 0 ;;
|
||||
*) echo "unknown argument: $a" >&2; exit 2 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
need() { command -v "$1" >/dev/null 2>&1 || { echo "error: '$1' is required" >&2; exit 1; }; }
|
||||
need curl; need unzip; need md5sum
|
||||
|
||||
TMP=$(mktemp -d)
|
||||
trap 'rm -rf "$TMP"' EXIT
|
||||
|
||||
MANIFEST_URL="$REPO_URL/releases/download/$TAG/manifest.json"
|
||||
echo "fetching $MANIFEST_URL"
|
||||
curl -fsSL "$MANIFEST_URL" -o "$TMP/manifest.json"
|
||||
|
||||
if command -v jq >/dev/null 2>&1; then
|
||||
VERSION=$(jq -r '.[0].versions[0].version' "$TMP/manifest.json")
|
||||
SOURCE_URL=$(jq -r '.[0].versions[0].sourceUrl' "$TMP/manifest.json")
|
||||
CHECKSUM=$(jq -r '.[0].versions[0].checksum' "$TMP/manifest.json")
|
||||
TARGET_ABI=$(jq -r '.[0].versions[0].targetAbi' "$TMP/manifest.json")
|
||||
else
|
||||
# fallback without jq: the manifest has exactly one version entry
|
||||
VERSION=$(sed -n 's/.*"version": *"\([^"]*\)".*/\1/p' "$TMP/manifest.json" | head -1)
|
||||
SOURCE_URL=$(sed -n 's/.*"sourceUrl": *"\([^"]*\)".*/\1/p' "$TMP/manifest.json" | head -1)
|
||||
CHECKSUM=$(sed -n 's/.*"checksum": *"\([^"]*\)".*/\1/p' "$TMP/manifest.json" | head -1)
|
||||
TARGET_ABI=$(sed -n 's/.*"targetAbi": *"\([^"]*\)".*/\1/p' "$TMP/manifest.json" | head -1)
|
||||
fi
|
||||
[ -n "$VERSION" ] && [ -n "$SOURCE_URL" ] || { echo "error: could not read version/sourceUrl from manifest" >&2; exit 1; }
|
||||
|
||||
PLUGINS_DIR="$JELLYFIN_CONFIG_DIR/data/plugins"
|
||||
DEST="$PLUGINS_DIR/Floatplane_$VERSION"
|
||||
|
||||
# owner: the container's PUID/PGID if we can read them, else 1000:1000
|
||||
OWNER_UID="${PUID:-}"; OWNER_GID="${PGID:-}"
|
||||
if { [ -z "$OWNER_UID" ] || [ -z "$OWNER_GID" ]; } && [ -n "$JELLYFIN_CONTAINER" ] && command -v docker >/dev/null 2>&1; then
|
||||
ENVS=$(docker inspect -f '{{range .Config.Env}}{{println .}}{{end}}' "$JELLYFIN_CONTAINER" 2>/dev/null || true)
|
||||
[ -z "$OWNER_UID" ] && OWNER_UID=$(printf '%s\n' "$ENVS" | sed -n 's/^PUID=//p' | head -1)
|
||||
[ -z "$OWNER_GID" ] && OWNER_GID=$(printf '%s\n' "$ENVS" | sed -n 's/^PGID=//p' | head -1)
|
||||
fi
|
||||
OWNER_UID="${OWNER_UID:-1000}"; OWNER_GID="${OWNER_GID:-1000}"
|
||||
|
||||
OLD=$(ls -d "$PLUGINS_DIR"/Floatplane_* 2>/dev/null | grep -v "/Floatplane_$VERSION\$" || true)
|
||||
|
||||
echo
|
||||
echo "Floatplane plugin $VERSION (release $TAG, targetAbi $TARGET_ABI)"
|
||||
echo " package: $SOURCE_URL"
|
||||
echo " md5: $CHECKSUM"
|
||||
echo " install: $DEST/"
|
||||
[ -n "$OLD" ] && echo " remove: $(echo "$OLD" | tr '\n' ' ')"
|
||||
echo " owner: $OWNER_UID:$OWNER_GID"
|
||||
if [ -n "$JELLYFIN_CONTAINER" ]; then echo " restart: docker restart $JELLYFIN_CONTAINER"; else echo " restart: (skipped, JELLYFIN_CONTAINER empty)"; fi
|
||||
echo
|
||||
|
||||
if [ "$DRY_RUN" = 1 ]; then
|
||||
echo "dry run - nothing changed."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [ ! -d "$PLUGINS_DIR" ]; then
|
||||
echo "error: $PLUGINS_DIR does not exist - is JELLYFIN_CONFIG_DIR right? (linuxserver image keeps data in <config>/data)" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "downloading $SOURCE_URL"
|
||||
curl -fsSL "$SOURCE_URL" -o "$TMP/plugin.zip"
|
||||
ACTUAL=$(md5sum "$TMP/plugin.zip" | cut -d' ' -f1)
|
||||
if [ -n "$CHECKSUM" ] && [ "$CHECKSUM" != "null" ] && [ "$ACTUAL" != "$CHECKSUM" ]; then
|
||||
echo "error: md5 mismatch (expected $CHECKSUM, got $ACTUAL)" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "checksum ok"
|
||||
|
||||
mkdir -p "$TMP/unpack"
|
||||
unzip -q -o "$TMP/plugin.zip" -d "$TMP/unpack"
|
||||
[ -f "$TMP/unpack/Jellyfin.Plugin.Floatplane.dll" ] && [ -f "$TMP/unpack/meta.json" ] || { echo "error: zip does not contain the plugin" >&2; exit 1; }
|
||||
|
||||
rm -rf "$DEST"
|
||||
mkdir -p "$DEST"
|
||||
cp "$TMP/unpack"/* "$DEST/"
|
||||
chown -R "$OWNER_UID:$OWNER_GID" "$DEST" 2>/dev/null || echo "warning: could not chown $DEST (run with sudo?)"
|
||||
chmod -R u+rwX,go+rX "$DEST"
|
||||
echo "installed into $DEST"
|
||||
|
||||
if [ -n "$OLD" ]; then
|
||||
echo "$OLD" | while IFS= read -r d; do
|
||||
[ -n "$d" ] && rm -rf "$d" && echo "removed old $d"
|
||||
done
|
||||
fi
|
||||
|
||||
if [ -n "$JELLYFIN_CONTAINER" ]; then
|
||||
if command -v docker >/dev/null 2>&1; then
|
||||
echo "restarting container $JELLYFIN_CONTAINER ..."
|
||||
docker restart "$JELLYFIN_CONTAINER" >/dev/null && echo "restarted"
|
||||
else
|
||||
echo "docker not found - restart the Jellyfin container yourself so the plugin loads"
|
||||
fi
|
||||
fi
|
||||
|
||||
echo
|
||||
echo "done. Next: Jellyfin Dashboard -> Plugins -> Floatplane -> paste your sails.sid cookie -> Save."
|
||||
Reference in New Issue
Block a user