feat: api plugin system & offline versioned dependency vendoring (#1252)

- **New Features**
- Created a dynamic plugin system for the API to enable community
augmentation of GraphQL, CLI, and Cron functionalities capabilities.
- Included an example plugin under `packages/unraid-api-plugin-health`
that adds a new graphql query for API health checks.
- Added `rc.unraid-api` commands for backing up, restoring, and
installing production dependencies, streamlining maintenance and
deployment.
- Improved dependency vendoring by bundling a versioned pnpm store
(instead of `node_modules`). Versioning will allow users to add plugins
to a specific api release without requiring an internet connection on
subsequent reboots.

- **Chores**
- Upgraded build workflows and versioning processes to ensure more
reliable artifact handling and production packaging.
This commit is contained in:
Pujit Mehrotra
2025-03-27 13:23:55 -04:00
committed by GitHub
parent c4b4d26af0
commit 9f492bf217
28 changed files with 826 additions and 2245 deletions
@@ -8,7 +8,9 @@ flash="/boot/config/plugins/dynamix.my.servers"
[[ ! -d "${flash}" ]] && echo "Please reinstall the Unraid Connect plugin" && exit 1
[[ ! -f "${flash}/env" ]] && echo 'env=production' >"${flash}/env"
unraid_binary_path="/usr/local/bin/unraid-api"
pnpm_store_dir="/usr/.pnpm-store"
# Placeholder functions for plugin installation/uninstallation
install() {
true;
}
@@ -16,6 +18,115 @@ uninstall() {
true;
}
# Creates a backup of the global pnpm store directory
# Args:
# $1 - Path to the backup file (tar.xz format)
# Returns:
# 0 on success, 1 on failure
backup_pnpm_store() {
# Check if backup file path is provided
if [ -z "$1" ]; then
echo "Error: Backup file path is required"
return 1
fi
local backup_file="$1"
# Check if pnpm command exists
if ! command -v pnpm >/dev/null 2>&1; then
echo "pnpm is not installed. Skipping backup."
return 1
fi
# Determine the global pnpm store directory
mkdir -p "$pnpm_store_dir"
echo "Backing up pnpm store from '$pnpm_store_dir' to '$backup_file'"
# Create a tar.gz archive of the global pnpm store
if tar -cJf "$backup_file" -C "$(dirname "$pnpm_store_dir")" "$(basename "$pnpm_store_dir")"; then
echo "pnpm store backup completed successfully."
else
echo "Error: Failed to create pnpm store backup."
return 1
fi
}
# Restores the pnpm store from a backup file
# Args:
# $1 - Path to the backup file (tar.xz format)
# Returns:
# 0 on success, 1 on failure
# Note: Requires 1.5x the backup size in free space for safe extraction
restore_pnpm_store() {
# Check if pnpm command exists
if ! command -v pnpm >/dev/null 2>&1; then
echo "pnpm is not installed. Cannot restore store."
return 1
fi
local backup_file="$1"
# Check if backup file exists
if [ ! -f "$backup_file" ]; then
echo "Backup file not found at '$backup_file'. Skipping restore."
return 0
fi
# Check available disk space in destination
local backup_size
backup_size=$(stat -c%s "$backup_file")
local dest_space
dest_space=$(df --output=avail "$(dirname "$pnpm_store_dir")" | tail -n1)
dest_space=$((dest_space * 1024)) # Convert KB to bytes
# Require 1.5x the backup size for safe extraction
local required_space=$((backup_size + (backup_size / 2)))
if [ "$dest_space" -lt "$required_space" ]; then
echo "Error: Insufficient disk space in destination. Need at least $((required_space / 1024 / 1024))MB, have $((dest_space / 1024 / 1024))MB"
return 1
fi
echo "Restoring pnpm store from '$backup_file' to '$pnpm_store_dir'"
# Remove existing store directory if it exists and ensure its parent directory exists
rm -rf "$pnpm_store_dir"
mkdir -p "$(dirname "$pnpm_store_dir")"
# Extract directly to final location
if ! tar -xJf "$backup_file" -C "$(dirname "$pnpm_store_dir")" --preserve-permissions; then
echo "Error: Failed to extract backup to final location."
rm -rf "$pnpm_store_dir"
return 1
fi
echo "pnpm store restored successfully."
}
# Installs production dependencies for the unraid-api using pnpm. Prefers offline mode.
# Uses the api_base_directory variable or defaults to /usr/local/unraid-api
# Returns:
# 0 on success, 1 on failure
pnpm_install_unraid_api() {
# Check if pnpm command exists
if ! command -v pnpm >/dev/null 2>&1; then
echo "Error: pnpm command not found. Cannot install dependencies."
return 1
fi
# Use the api_base_directory variable if set, otherwise default to /usr/local/unraid-api
local unraid_api_dir="${api_base_directory:-/usr/local/unraid-api}"
if [ ! -d "$unraid_api_dir" ]; then
echo "Error: unraid API directory '$unraid_api_dir' does not exist."
return 1
fi
echo "Executing 'pnpm install' in $unraid_api_dir"
rm -rf /usr/local/unraid-api/node_modules
# Run pnpm install in a subshell to prevent changing the current working directory of the script
( cd "$unraid_api_dir" && pnpm install --prod --prefer-offline )
}
case "$1" in
'install')
install "$2"
@@ -26,6 +137,15 @@ case "$1" in
'uninstall')
uninstall
;;
'pnpm-install')
pnpm_install_unraid_api
;;
'backup-dependencies')
backup_pnpm_store "$2"
;;
'restore-dependencies')
restore_pnpm_store "$2"
;;
*)
# Pass all other commands to unraid-api
"${unraid_binary_path}" "$@"
File diff suppressed because it is too large Load Diff