jellyfin/install_plugin.sh
2025-06-05 16:12:21 +00:00

1026 lines
33 KiB
Bash
Executable file

#!/bin/bash
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
PLUGIN_DIR="./App/config/plugins"
CACHE_BASE_DIR="./plugin_manifests" # More persistent location
TEMP_DIR="/tmp/jellyfin_plugins"
MANIFEST_CACHE_DIR="${CACHE_BASE_DIR}/$(date +%Y.%m.%d)"
# Known repositories array
declare -A REPOSITORIES=(
["Official"]="https://sfo1.mirror.jellyfin.org/files/plugin/manifest.json"
["9p4's SSO"]="https://raw.githubusercontent.com/9p4/jellyfin-plugin-sso/manifest-release/manifest.json"
["Ani-Sync"]="https://raw.githubusercontent.com/vosmiic/jellyfin-ani-sync/master/manifest.json"
["danieladov"]="https://raw.githubusercontent.com/danieladov/JellyfinPluginManifest/master/manifest.json"
["dkanada"]="https://raw.githubusercontent.com/dkanada/jellyfin-plugin-intros/master/manifest.json"
["k-matti"]="https://raw.githubusercontent.com/k-matti/jellyfin-plugin-repository/master/manifest.json"
["Linfor"]="https://raw.githubusercontent.com/LinFor/jellyfin-plugin-kinopoisk/master/dist/manifest.json"
["LizardByte"]="https://app.lizardbyte.dev/jellyfin-plugin-repo/manifest.json"
["ShokoAnime"]="https://raw.githubusercontent.com/ShokoAnime/Shokofin/metadata/stable/manifest.json"
["TubeArchivist"]="https://raw.githubusercontent.com/tubearchivist/tubearchivist-jf-plugin/master/manifest.json"
["Streamyfin"]="https://raw.githubusercontent.com/streamyfin/jellyfin-plugin-streamyfin/main/manifest.json"
["jellyscrub"]="https://raw.githubusercontent.com/nicknsy/jellyscrub/main/manifest.json"
# ["Jellyfin Unstable"]="https://repo.jellyfin.org/files/plugin-unstable/manifest.json"
)
# Function to show usage
show_usage() {
echo -e "${BLUE}Jellyfin Plugin Installer${NC}"
echo "Usage:"
echo " $0 search <term> - Search known repositories for plugins"
echo " $0 install <name|guid|plugin_url> - Install a plugin by name, GUID, or URL"
echo " $0 info <name|guid> - Show detailed information about a specific plugin"
echo " $0 versions <name|guid> - Show latest 10 versions of a plugin"
echo " $0 repos - List known repositories"
echo " $0 update - Update plugin manifests"
echo " $0 upgrade - Check and upgrade installed plugins"
echo " $0 catalog <manifest_url> - List plugins from a manifest"
echo
echo "Example manifests:"
echo " https://raw.githubusercontent.com/nicknsy/jellyscrub/main/manifest.json"
echo " https://raw.githubusercontent.com/danieladov/JellyfinPluginManifest/master/manifest.json"
}
# Function to list repositories
list_repos() {
echo -e "${BLUE}Known Repositories:${NC}"
for repo in "${!REPOSITORIES[@]}"; do
echo -e "${GREEN}$repo${NC}"
echo " ${REPOSITORIES[$repo]}"
done
}
# Function to add new function to handle manifest caching
fetch_manifests() {
local force=$1
local today=$(date +%Y.%m.%d)
# Create base cache directory if it doesn't exist
mkdir -p "$CACHE_BASE_DIR"
# Check if we should use cached manifests
if [ "$force" != "true" ] && [ -d "$MANIFEST_CACHE_DIR" ]; then
echo -e "${BLUE}Using cached manifests from ${MANIFEST_CACHE_DIR}${NC}" >&2
return 0
fi
# Cleanup old manifest directories (keep last 7 days)
find "$CACHE_BASE_DIR" -maxdepth 1 -type d -mtime +7 -exec rm -rf {} \;
# Create fresh cache directory
mkdir -p "$MANIFEST_CACHE_DIR"
echo -e "${BLUE}Downloading manifests to ${MANIFEST_CACHE_DIR}${NC}" >&2
# Download all manifests with dynamic status line
local total=${#REPOSITORIES[@]}
local current=0
for repo in "${!REPOSITORIES[@]}"; do
((current++))
printf "\rFetching manifests: [%-50s] %d/%d - %s" \
"$(printf '#%.0s' $(seq 1 $((current * 50 / total))))" \
"$current" "$total" "$repo" >&2
# Download with error checking
if ! curl -s -f "${REPOSITORIES[$repo]}" > "${MANIFEST_CACHE_DIR}/${repo}.json.tmp"; then
continue
fi
# Validate JSON and move to final location if valid
if jq empty "${MANIFEST_CACHE_DIR}/${repo}.json.tmp" 2>/dev/null; then
mv "${MANIFEST_CACHE_DIR}/${repo}.json.tmp" "${MANIFEST_CACHE_DIR}/${repo}.json"
else
rm "${MANIFEST_CACHE_DIR}/${repo}.json.tmp"
fi
done
printf "\n" >&2
}
# Add spinner function at the top with other functions
spinner() {
local pid=$1
local delay=0.1
local spinstr='|/-\'
while ps -p $pid > /dev/null; do
local temp=${spinstr#?}
printf "\r%c" "$spinstr"
local spinstr=$temp${spinstr%"$temp"}
sleep $delay
done
printf "\r"
}
# Function to search repositories
search_repos() {
local search_term="$1"
local force=$2
local search_term_lower=$(echo "$search_term" | tr '[:upper:]' '[:lower:]')
local found=0
# Fetch/update manifests
fetch_manifests "$force"
# Print header
printf "${BLUE}%-30s %-20s %-15s %-20s %-36s${NC}\n" "Name" "Version" "ABI" "Source" "GUID"
printf "%.s-" {1..121} # Increased for GUID column
printf "\n"
# Create temp file for results
local results_file=$(mktemp)
# Run search in background and show spinner
(
# Search through cached manifests
for repo in "${!REPOSITORIES[@]}"; do
local manifest_file="${MANIFEST_CACHE_DIR}/${repo}.json"
[ ! -f "$manifest_file" ] && continue
while read -r line; do
if [ -n "$line" ]; then
echo "$line"
found=1
fi
done < <(jq -r --arg term "$search_term_lower" '
# Convert input to array if not already
if type == "array" then . else [.] end |
.[] |
# Get all searchable fields and ensure guid is included
{
name: (.name // ""),
description: (.description // ""),
overview: (.overview // ""),
category: (.category // ""),
owner: (.owner // ""),
guid: (.guid // "unknown"), # Ensure GUID has a default value
versions: (.versions // [])
} |
# Convert all fields to lowercase for searching
select(
(.name | ascii_downcase | contains($term)) or
(.description | ascii_downcase | contains($term)) or
(.overview | ascii_downcase | contains($term)) or
(.category | ascii_downcase | contains($term)) or
(.owner | ascii_downcase | contains($term))
) |
# Get latest version info
. as $plugin |
($plugin.versions | sort_by(.timestamp) | last) as $latest |
# Format output with guid
"\($plugin.name)|\($latest.version)|\($latest.targetAbi)|\("'"$repo"'")|\($plugin.guid)"
' "$manifest_file" 2>/dev/null | while IFS='|' read -r name version abi repo guid; do
printf "%-30s %-20s %-15s %-20s %-36s\n" "$name" "$version" "$abi" "$repo" "$guid" >> "$results_file"
done)
done
) & spinner $!
# Print results
if [ -s "$results_file" ]; then
cat "$results_file"
echo ""
else
echo -e "\n${RED}No plugins found matching: $search_term${NC}"
fi
# Cleanup
rm -f "$results_file"
}
# Function to list plugins from a manifest
list_catalog() {
local manifest_url="$1"
echo -e "${BLUE}Fetching plugin catalog from: ${manifest_url}${NC}"
# Create temp directory if it doesn't exist
mkdir -p "${TEMP_DIR}"
# Download and parse manifest
if ! curl -s "${manifest_url}" > "${TEMP_DIR}/manifest.json"; then
echo -e "${RED}Failed to download manifest${NC}"
exit 1
fi
# Use jq to parse the manifest if available, otherwise use grep/sed
if command -v jq >/dev/null 2>&1; then
echo -e "\n${GREEN}Available Plugins:${NC}"
jq -r '.[] | "Name: \(.name)\nDescription: \(.description)\nVersions:\n\(.versions[] | " Version: \(.version)\n Download URL: \(.sourceUrl)\n Checksum: \(.checksum)\n Target ABI: \(.targetAbi)\n")---"' "${TEMP_DIR}/manifest.json"
else
echo -e "${RED}jq not found, showing raw manifest:${NC}"
cat "${TEMP_DIR}/manifest.json"
fi
}
# Function to find plugin by name or GUID
find_plugin() {
local search_term="$1"
local results_file=$(mktemp)
local matches=0
for repo in "${!REPOSITORIES[@]}"; do
local manifest_file="${MANIFEST_CACHE_DIR}/${repo}.json"
[ ! -f "$manifest_file" ] && continue
jq -r --arg term "$search_term" '
if type == "array" then . else [.] end |
.[] |
select(
(.name == $term) or
(.guid == $term)
) |
. as $plugin |
($plugin.versions | sort_by(.timestamp) | last) as $latest |
{
name: $plugin.name,
guid: $plugin.guid,
version: $latest.version,
sourceUrl: $latest.sourceUrl,
checksum: $latest.checksum,
repo: "'"$repo"'"
} | @json
' "$manifest_file" 2>/dev/null >> "$results_file"
done
# Count unique matches
matches=$(jq -s 'length' "$results_file")
if [ "$matches" -eq 0 ]; then
echo -e "${RED}No plugin found matching: $search_term${NC}" >&2
rm -f "$results_file"
return 1
elif [ "$matches" -gt 1 ]; then
echo -e "${RED}Multiple plugins found matching: $search_term${NC}" >&2
echo "Please use the GUID to specify which plugin to install:" >&2
jq -r '.name + " (" + .guid + ") from " + .repo' "$results_file" >&2
rm -f "$results_file"
return 1
else
cat "$results_file"
rm -f "$results_file"
return 0
fi
}
# Function to find checksum for a URL
find_checksum_for_url() {
local search_url="$1"
local results_file=$(mktemp)
local found=false
for repo in "${!REPOSITORIES[@]}"; do
local manifest_file="${MANIFEST_CACHE_DIR}/${repo}.json"
[ ! -f "$manifest_file" ] && continue
if jq -e --arg url "$search_url" '
if type == "array" then . else [.] end |
.[] |
(.versions[] | select(.sourceUrl == $url)) |
.checksum
' "$manifest_file" 2>/dev/null > "$results_file"; then
found=true
break
fi
done
if [ "$found" = true ]; then
cat "$results_file"
rm -f "$results_file"
return 0
else
rm -f "$results_file"
return 1
fi
}
# Update verify_checksum function
verify_checksum() {
local file="$1"
local expected_checksum="$2"
# Get directory and filename separately
local dir=$(dirname "$file")
local filename=$(basename "$file")
# Determine hash length to choose verification method
local checksum_length=${#expected_checksum}
case $checksum_length in
32) # MD5
if ! (cd "$dir" && echo "$expected_checksum $filename" | md5sum -c --status); then
return 1
fi
;;
40) # SHA1
if ! (cd "$dir" && echo "$expected_checksum $filename" | sha1sum -c --status); then
return 1
fi
;;
64) # SHA256
if ! (cd "$dir" && echo "$expected_checksum $filename" | sha256sum -c --status); then
return 1
fi
;;
128) # SHA512
if ! (cd "$dir" && echo "$expected_checksum $filename" | sha512sum -c --status); then
return 1
fi
;;
*)
echo -e "${YELLOW}Warning: Unknown checksum format, skipping verification${NC}"
return 0
;;
esac
return 0
}
# Function to download plugin image
download_plugin_image() {
local image_url="$1"
local plugin_dir="$2"
local plugin_name="$3"
if [ -n "$image_url" ]; then
# Get file extension from URL
local ext=$(echo "$image_url" | grep -oP '\.[^.]+$')
if [ -z "$ext" ]; then
ext=".png" # Default to .png if no extension found
fi
# Download image
if curl -s -f -L "$image_url" -o "${plugin_dir}/${plugin_name}${ext}"; then
echo -e "${GREEN}Downloaded plugin image${NC}"
else
echo -e "${YELLOW}Failed to download plugin image${NC}"
fi
fi
}
# Function to generate meta.json
generate_meta_json() {
local plugin_name="$1"
local plugin_version="$2"
local plugin_guid="$3"
local plugin_description="$4"
local plugin_overview="$5"
local plugin_owner="$6"
local plugin_category="$7"
local plugin_timestamp="$8"
local plugin_abi="$9"
local plugin_changelog="${10}"
local plugin_dir="${11}"
local plugin_image_url="${12}"
# Start building JSON content
local json_content='{
"category": "'"${plugin_category}"'",
"changelog": "'"${plugin_changelog}"'",
"description": "'"${plugin_description}"'",
"guid": "'"${plugin_guid}"'",
"name": "'"${plugin_name}"'",
"overview": "'"${plugin_overview}"'",
"owner": "'"${plugin_owner}"'",
"targetAbi": "'"${plugin_abi}"'",
"timestamp": "'"${plugin_timestamp}"'",
"version": "'"${plugin_version}"'",
"status": "Active",
"autoUpdate": true'
# Add imagePath only if image URL exists
if [ -n "$plugin_image_url" ]; then
local ext=$(echo "$plugin_image_url" | grep -oP '\.[^.]+$')
if [ -z "$ext" ]; then
ext=".png"
fi
json_content+=',
"imagePath": "/config/plugins/'"${plugin_name}_${plugin_version}/${plugin_name}_${plugin_version}${ext}"'"'
fi
# Close the JSON
json_content+=',
"assemblies": []
}'
# Write to file
echo "$json_content" > "${plugin_dir}/meta.json"
}
# Function to find plugin info from URL in manifests
find_plugin_info_from_url() {
local url="$1"
local results_file=$(mktemp)
local found=false
# Search through cached manifests
for manifest_file in "${MANIFEST_CACHE_DIR}"/*.json; do
if [[ ! -f "$manifest_file" ]]; then
continue
fi
# Try to find plugin info by matching the URL
if jq -r --arg url "$url" '
if type == "array" then . else [.] end |
.[] |
. as $plugin |
(.versions[] | select(.sourceUrl == $url)) as $version |
if $version then
{
name: $plugin.name,
guid: $plugin.guid,
description: ($plugin.description // ""),
overview: ($plugin.overview // ""),
owner: ($plugin.owner // ""),
category: ($plugin.category // ""),
imageUrl: ($plugin.imageUrl // ""),
version: $version.version,
timestamp: $version.timestamp,
targetAbi: $version.targetAbi,
changelog: ($version.changelog // ""),
checksum: $version.checksum
}
else
empty
end
' "$manifest_file" > "$results_file" 2>/dev/null; then
if [ -s "$results_file" ]; then
found=true
break
fi
fi
done
if [ "$found" = true ]; then
cat "$results_file"
rm -f "$results_file"
return 0
fi
rm -f "$results_file"
return 1
}
# Update install_from_url function
install_from_url() {
local plugin_url="$1"
local checksum="$2"
local plugin_filename=$(basename "${plugin_url}")
local plugin_info
local plugin_name
local plugin_version
# Try to find plugin info in manifests
if plugin_info=$(find_plugin_info_from_url "$plugin_url"); then
# Use info from manifest
plugin_name=$(echo "$plugin_info" | jq -r '.name')
plugin_version=$(echo "$plugin_info" | jq -r '.version')
echo -e "${GREEN}Found plugin information in manifests${NC}"
# Create plugin directory with correct naming convention
local plugin_dir="${PLUGIN_DIR}/${plugin_name}_${plugin_version}"
mkdir -p "$plugin_dir"
# Download and install plugin
echo -e "${BLUE}Downloading plugin...${NC}"
if ! curl -L -o "${TEMP_DIR}/${plugin_filename}" "${plugin_url}"; then
echo -e "${RED}Failed to download plugin${NC}"
exit 1
fi
# Verify checksum
echo -e "${BLUE}Verifying checksum...${NC}"
if ! verify_checksum "${TEMP_DIR}/${plugin_filename}" "$(echo "$plugin_info" | jq -r '.checksum')"; then
echo -e "${RED}Checksum verification failed${NC}"
exit 1
fi
echo -e "${GREEN}Checksum verified${NC}"
# Extract plugin
echo -e "${BLUE}Installing plugin...${NC}"
if [[ "${plugin_filename}" == *.zip ]]; then
if ! unzip -o "${TEMP_DIR}/${plugin_filename}" -d "$plugin_dir"; then
echo -e "${RED}Failed to extract plugin${NC}"
exit 1
fi
else
cp "${TEMP_DIR}/${plugin_filename}" "$plugin_dir/"
fi
# Generate meta.json with manifest info
generate_meta_json \
"$(echo "$plugin_info" | jq -r '.name')" \
"$(echo "$plugin_info" | jq -r '.version')" \
"$(echo "$plugin_info" | jq -r '.guid')" \
"$(echo "$plugin_info" | jq -r '.description')" \
"$(echo "$plugin_info" | jq -r '.overview')" \
"$(echo "$plugin_info" | jq -r '.owner')" \
"$(echo "$plugin_info" | jq -r '.category')" \
"$(echo "$plugin_info" | jq -r '.timestamp')" \
"$(echo "$plugin_info" | jq -r '.targetAbi')" \
"$(echo "$plugin_info" | jq -r '.changelog')" \
"$plugin_dir" \
"$(echo "$plugin_info" | jq -r '.imageUrl')"
# Download image if available
local image_url=$(echo "$plugin_info" | jq -r '.imageUrl')
if [ -n "$image_url" ] && [ "$image_url" != "null" ]; then
download_plugin_image "$image_url" "$plugin_dir" "${plugin_name}_${plugin_version}"
fi
else
# No manifest info found - try to extract name and version from filename
echo -e "${YELLOW}No manifest information found for this plugin${NC}"
# Try to get name and version from filename
plugin_name=$(echo "${plugin_filename%.*}" | sed -E 's/[-_][0-9].*//' | sed 's/\.zip$//')
plugin_version=$(echo "${plugin_filename}" | grep -oP '[\d.]+(?=\.zip)' || echo "1.0.0.0")
if [ -z "$plugin_version" ]; then
plugin_version="1.0.0.0"
fi
local plugin_dir="${PLUGIN_DIR}/${plugin_name}_${plugin_version}"
mkdir -p "$plugin_dir"
# Download and install
echo -e "${BLUE}Downloading plugin...${NC}"
if ! curl -L -o "${TEMP_DIR}/${plugin_filename}" "${plugin_url}"; then
echo -e "${RED}Failed to download plugin${NC}"
exit 1
fi
# Extract plugin
echo -e "${BLUE}Installing plugin...${NC}"
if [[ "${plugin_filename}" == *.zip ]]; then
if ! unzip -o "${TEMP_DIR}/${plugin_filename}" -d "$plugin_dir"; then
echo -e "${RED}Failed to extract plugin${NC}"
exit 1
fi
else
cp "${TEMP_DIR}/${plugin_filename}" "$plugin_dir/"
fi
# Generate basic meta.json
generate_meta_json \
"$plugin_name" \
"$plugin_version" \
"" \
"" \
"" \
"Unknown" \
"General" \
"$(date -u +"%Y-%m-%dT%H:%M:%SZ")" \
"" \
"" \
"$plugin_dir" \
""
fi
# Set correct permissions
if [ -n "${PUID}" ] && [ -n "${PGID}" ]; then
chown -R ${PUID}:${PGID} "$plugin_dir"
fi
# Cleanup
rm -f "${TEMP_DIR}/${plugin_filename}"
echo -e "${GREEN}Plugin installed successfully${NC}"
echo "Restart Jellyfin to complete installation:"
echo "docker compose restart jellyfin"
}
# Update show_plugin_info function to include current version
show_plugin_info() {
local search_term="$1"
local found=0
local found_repo=""
local results_file=$(mktemp)
# Fetch manifests if needed
fetch_manifests "false"
# Search through cached manifests
for repo in "${!REPOSITORIES[@]}"; do
local manifest_file="${MANIFEST_CACHE_DIR}/${repo}.json"
[ ! -f "$manifest_file" ] && continue
# Search by name or GUID
jq -r --arg term "$search_term" '
# Convert input to array if not already
if type == "array" then . else [.] end |
.[] |
select(
(.name == $term) or
(.guid == $term)
) |
. as $plugin |
($plugin.versions | sort_by(.timestamp) | last) as $latest |
{
name: $plugin.name,
guid: $plugin.guid,
description: ($plugin.description // "No description available"),
overview: ($plugin.overview // "No overview available"),
owner: ($plugin.owner // "Unknown"),
category: ($plugin.category // "Unknown"),
versions: ($plugin.versions | length | tostring + " version(s) available"),
current: $latest.version
} |
"Name: \(.name)\n" +
"GUID: \(.guid)\n" +
"Owner: \(.owner)\n" +
"Category: \(.category)\n" +
"Description: \(.description)\n" +
"Overview: \(.overview)\n" +
"Versions: \(.versions)\n" +
"Current: \(.current)"
' "$manifest_file" 2>/dev/null > "$results_file"
if [ -s "$results_file" ]; then
found=1
found_repo="$repo"
break
fi
done
if [ $found -eq 1 ]; then
echo -e "${BLUE}Plugin Information:${NC}"
echo "----------------------------------------"
cat "$results_file"
echo -e "Repository: ${GREEN}${found_repo}${NC}"
echo "----------------------------------------"
else
echo -e "${RED}No plugin found matching: $search_term${NC}"
fi
rm -f "$results_file"
}
# Add show_plugin_versions function
show_plugin_versions() {
local search_term="$1"
local found=0
local results_file=$(mktemp)
# Fetch manifests if needed
fetch_manifests "false"
# Search through cached manifests
for repo in "${!REPOSITORIES[@]}"; do
local manifest_file="${MANIFEST_CACHE_DIR}/${repo}.json"
[ ! -f "$manifest_file" ] && continue
# Search by name or GUID and get version info
jq -r --arg term "$search_term" '
# Convert input to array if not already
if type == "array" then . else [.] end |
.[] |
select(
(.name == $term) or
(.guid == $term)
) |
{
name: .name,
versions: (.versions | sort_by(.timestamp) | reverse | .[0:10])
} |
"Plugin: \(.name)\n" +
"----------------------------------------\n" +
(.versions[] |
"Timestamp: \(.timestamp)\n" +
"Version: \(.version)\n" +
"Target ABI: \(.targetAbi)\n" +
"Changelog: \(.changelog // "No changelog available")\n" +
"Download URL: \(.sourceUrl)\n" +
"----------------------------------------\n"
)
' "$manifest_file" 2>/dev/null > "$results_file"
if [ -s "$results_file" ]; then
found=1
break
fi
done
if [ $found -eq 1 ]; then
echo -e "${BLUE}Latest 10 Versions:${NC}"
cat "$results_file"
else
echo -e "${RED}No plugin found matching: $search_term${NC}"
fi
rm -f "$results_file"
}
# Add update_manifests function
update_manifests() {
echo -e "${BLUE}Updating plugin manifests...${NC}"
fetch_manifests "true" # Force refresh
echo -e "${GREEN}Manifests updated successfully${NC}"
}
# Function to remove old plugin version
remove_old_plugin() {
local plugin_name="$1"
local new_version="$2"
local current_version="$3"
if [ -n "$current_version" ]; then
local old_dir="$PLUGIN_DIR/${plugin_name}_${current_version}"
if [ -d "$old_dir" ] && [ "$current_version" != "$new_version" ]; then
echo -e "${BLUE}Removing old version: $current_version${NC}"
rm -rf "$old_dir"
fi
fi
}
# Update install_plugin function to handle cleanup
install_plugin() {
local input="$1"
local force="$2"
local current_version="$3" # Add parameter for current version
local temp_dir="/tmp/jellyfin_plugins"
# Create temp directory
mkdir -p "$temp_dir"
# Check if input is a URL
if [[ "$input" =~ ^https?:// ]]; then
echo -e "${BLUE}Installing from URL: $input${NC}"
# Try to find checksum for this URL
local checksum
if checksum=$(find_checksum_for_url "$input"); then
echo -e "${GREEN}Found matching checksum in manifests${NC}"
else
echo -e "${YELLOW}Warning: No checksum found for this URL${NC}"
read -p "Continue without checksum verification? (y/N) " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
echo -e "${RED}Installation cancelled${NC}"
exit 1
fi
fi
install_from_url "$input" "$checksum"
return
fi
# If not a URL, treat as GUID or name
# Fetch/update manifests
fetch_manifests "$force"
# Find plugin in manifests
local found=false
local plugin_name=""
local plugin_version=""
local plugin_url=""
local plugin_checksum=""
local plugin_repo=""
local plugin_guid=""
local plugin_description=""
local plugin_overview=""
local plugin_owner=""
local plugin_category=""
local plugin_timestamp=""
local plugin_abi=""
local plugin_changelog=""
local plugin_image_url=""
for manifest_file in "${MANIFEST_CACHE_DIR}"/*.json; do
if [[ ! -f "$manifest_file" ]]; then
continue
fi
# Search for plugin by GUID or name
while IFS='|' read -r name version url checksum repo guid description overview owner category timestamp abi changelog; do
if [[ "$input" == "$guid" ]] || [[ "$input" == "$name" ]]; then
found=true
plugin_name="$name"
plugin_version="$version"
plugin_url="$url"
plugin_checksum="$checksum"
plugin_repo="$repo"
plugin_guid="$guid"
plugin_description="$description"
plugin_overview="$overview"
plugin_owner="$owner"
plugin_category="$category"
plugin_timestamp="$timestamp"
plugin_abi="$abi"
plugin_changelog="$changelog"
plugin_image_url="$(jq -r --arg guid "$guid" '.[] | select(.guid == $guid) | .imageUrl // ""' "$manifest_file")"
break 2
fi
done < <(jq -r --arg input "$input" '
.[] |
select(.guid == $input or .name == $input) |
. as $plugin |
($plugin.versions | sort_by(.timestamp) | last) as $latest |
[
$plugin.name,
$latest.version,
$latest.sourceUrl,
$latest.checksum,
.owner,
.guid,
(.description // ""),
(.overview // ""),
(.owner // ""),
(.category // ""),
$latest.timestamp,
$latest.targetAbi,
($latest.changelog // "")
] | join("|")
' "$manifest_file")
done
if [[ "$found" != "true" ]]; then
echo -e "${RED}Plugin not found with ID: $input${NC}"
exit 1
fi
echo -e "Found plugin: ${GREEN}$plugin_name${NC} (version $plugin_version) in $plugin_repo"
echo -e "Installing from: ${BLUE}$plugin_url${NC}"
# Create temp directory
mkdir -p "$temp_dir"
local download_file="$temp_dir/${plugin_name,,}-${plugin_version}.zip"
# Download plugin
echo "Downloading plugin..."
curl -L "$plugin_url" -o "$download_file"
# Verify checksum
echo "Verifying checksum..."
if ! verify_checksum "$download_file" "$plugin_checksum"; then
echo -e "${RED}Checksum verification failed${NC}"
rm -f "$download_file"
exit 1
fi
echo "Checksum verified"
# Install plugin
echo "Installing plugin..."
# Create plugin directory with correct naming convention
local plugin_dir="$PLUGIN_DIR/${plugin_name}_${plugin_version}"
mkdir -p "$plugin_dir"
# Extract to plugin directory
unzip -o "$download_file" -d "$plugin_dir"
# Generate meta.json
generate_meta_json \
"$plugin_name" \
"$plugin_version" \
"$plugin_guid" \
"$plugin_description" \
"$plugin_overview" \
"$plugin_owner" \
"$plugin_category" \
"$plugin_timestamp" \
"$plugin_abi" \
"$plugin_changelog" \
"$plugin_dir" \
"$plugin_image_url"
# Download plugin image if available
if [ -n "$plugin_image_url" ]; then
download_plugin_image "$plugin_image_url" "$plugin_dir" "${plugin_name}_${plugin_version}"
fi
# Set correct permissions
if [ -n "${PUID}" ] && [ -n "${PGID}" ]; then
chown -R ${PUID}:${PGID} "$plugin_dir"
fi
# Cleanup
rm -f "$download_file"
# After successful installation and before final message
if [ -n "$current_version" ]; then
remove_old_plugin "$plugin_name" "$plugin_version" "$current_version"
fi
echo -e "${GREEN}Plugin installed successfully${NC}"
echo "Restart Jellyfin to complete installation:"
echo "docker compose restart jellyfin"
}
# Update upgrade_plugins function to pass current version
upgrade_plugins() {
# Fetch latest manifests
fetch_manifests "true"
echo -e "${BLUE}Checking for plugin updates...${NC}"
local updates_found=false
# Scan plugins directory
for plugin_dir in "$PLUGIN_DIR"/*; do
if [ -f "$plugin_dir/meta.json" ]; then
# Read current plugin info
local current_guid=$(jq -r '.guid' "$plugin_dir/meta.json")
local current_version=$(jq -r '.version' "$plugin_dir/meta.json")
local plugin_name=$(jq -r '.name' "$plugin_dir/meta.json")
# Search manifests for newer version
for manifest_file in "${MANIFEST_CACHE_DIR}"/*.json; do
if [[ ! -f "$manifest_file" ]]; then
continue
fi
# Get latest version info
local latest_version=$(jq -r --arg guid "$current_guid" '
.[] |
select(.guid == $guid) |
.versions | sort_by(.timestamp) | last |
.version // empty
' "$manifest_file")
if [ -n "$latest_version" ] && [ "$latest_version" != "$current_version" ]; then
updates_found=true
echo -e "\nUpdate available for ${GREEN}$plugin_name${NC}"
echo "Current version: $current_version"
echo "Latest version: $latest_version"
read -p "Do you want to upgrade this plugin? (y/N) " -n 1 -r
echo
if [[ $REPLY =~ ^[Yy]$ ]]; then
if install_plugin "$current_guid" "true" "$current_version"; then
echo -e "${GREEN}Upgrade successful${NC}"
else
echo -e "${RED}Upgrade failed${NC}"
fi
fi
fi
done
fi
done
if [ "$updates_found" = false ]; then
echo -e "${GREEN}All plugins are up to date${NC}"
fi
}
# Update main script to handle info command
case "$1" in
"catalog")
if [ -z "$2" ]; then
show_usage
exit 1
fi
list_catalog "$2"
;;
"install")
if [ -z "$2" ]; then
show_usage
exit 1
fi
install_plugin "$2"
;;
"search")
if [ -z "$2" ]; then
show_usage
exit 1
fi
mkdir -p "${TEMP_DIR}"
search_repos "$2"
;;
"info")
if [ -z "$2" ]; then
show_usage
exit 1
fi
show_plugin_info "$2"
;;
"versions")
if [ -z "$2" ]; then
show_usage
exit 1
fi
show_plugin_versions "$2"
;;
"repos")
list_repos
;;
"update")
update_manifests
;;
"upgrade")
upgrade_plugins
;;
*)
show_usage
exit 1
;;
esac