diff --git a/.github/workflows/build-plugin.yml b/.github/workflows/build-plugin.yml index fcac511dd..f84357e3e 100644 --- a/.github/workflows/build-plugin.yml +++ b/.github/workflows/build-plugin.yml @@ -103,7 +103,7 @@ jobs: - name: Download PNPM Store uses: actions/download-artifact@v4 with: - name: packed-pnpm-store + name: packed-node-modules path: ${{ github.workspace }}/plugin/ - name: Extract Unraid API run: | @@ -130,6 +130,11 @@ jobs: exit 1 fi + if [ ! -f ./deploy/*.tar.xz ]; then + echo "Error: .tar.xz file not found in plugin/deploy/" + exit 1 + fi + - name: Upload to GHA uses: actions/upload-artifact@v4 with: diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 8795fb899..58bfa197b 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -191,11 +191,11 @@ jobs: with: name: unraid-api path: ${{ github.workspace }}/api/deploy/unraid-api.tgz - - name: Upload PNPM Store to Github artifacts + - name: Upload Node Modules to Github artifacts uses: actions/upload-artifact@v4 with: - name: packed-pnpm-store - path: ${{ github.workspace }}/api/deploy/packed-pnpm-store.txz + name: packed-node-modules + path: ${{ github.workspace }}/api/deploy/packed-node-modules.tar.xz build-unraid-ui-webcomponents: name: Build Unraid UI Library (Webcomponent Version) diff --git a/api/package.json b/api/package.json index 1f5959544..b8921b058 100644 --- a/api/package.json +++ b/api/package.json @@ -201,6 +201,13 @@ "overrides": { "eslint": { "jiti": "2" + }, + "@as-integrations/fastify": { + "fastify": "$fastify" + }, + "nest-authz": { + "@nestjs/common": "$@nestjs/common", + "@nestjs/core": "$@nestjs/core" } }, "private": true, diff --git a/api/scripts/build.ts b/api/scripts/build.ts index 40986a36c..d19c13692 100755 --- a/api/scripts/build.ts +++ b/api/scripts/build.ts @@ -20,8 +20,6 @@ try { // Get package details const packageJson = await readFile('./package.json', 'utf-8'); const parsedPackageJson = JSON.parse(packageJson); - const rootPackageJson = await readFile('./../package.json', 'utf-8'); - const parsedRootPackageJson = JSON.parse(rootPackageJson); const deploymentVersion = await getDeploymentVersion(process.env, parsedPackageJson.version); @@ -30,9 +28,6 @@ try { // omit dev dependencies from release build parsedPackageJson.devDependencies = {}; - // add all PNPM settings for pnpm install from root package.json - parsedPackageJson.pnpm = parsedRootPackageJson.pnpm; - // Create a temporary directory for packaging await mkdir('./deploy/pack/', { recursive: true }); @@ -43,23 +38,20 @@ try { // Change to the pack directory and install dependencies cd('./deploy/pack'); - console.log('Building production pnpm store...'); + console.log('Building production node_modules...'); $.verbose = true; - await $`pnpm install --prod --ignore-workspace --store-dir=../.pnpm-store`; + await $`npm install --omit=dev`; - // Now remove the onlybuilddependencies from the package json - delete parsedPackageJson.pnpm; - // Now write the package.json back to the pack directoryaw + // Now write the package.json back to the pack directory await writeFile('package.json', JSON.stringify(parsedPackageJson, null, 4)); - await $`rm -rf node_modules`; // Don't include node_modules in final package - const sudoCheck = await $`command -v sudo`.nothrow(); const SUDO = sudoCheck.exitCode === 0 ? 'sudo' : ''; - await $`${SUDO} chown -R 0:0 ../.pnpm-store`; + await $`${SUDO} chown -R 0:0 node_modules`; - await $`XZ_OPT=-5 tar -cJf ../packed-pnpm-store.txz ../.pnpm-store`; - await $`${SUDO} rm -rf ../.pnpm-store`; + await $`XZ_OPT=-5 tar -cJf packed-node-modules.tar.xz node_modules`; + await $`mv packed-node-modules.tar.xz ../`; + await $`${SUDO} rm -rf node_modules`; // chmod the cli await $`chmod +x ./dist/cli.js`; diff --git a/package.json b/package.json index fe74f9237..a555ea96d 100644 --- a/package.json +++ b/package.json @@ -22,13 +22,11 @@ }, "onlyBuiltDependencies": [ "@apollo/protobufjs", - "@nestjs/core", + "protobufjs", "@parcel/watcher", "@swc/core", "@unraid/libvirt", - "cpu-features", "esbuild", - "nestjs-pino", "ssh2", "vue-demi" ] diff --git a/plugin/builder/build-plugin.ts b/plugin/builder/build-plugin.ts index e0b478cce..8f55bc212 100644 --- a/plugin/builder/build-plugin.ts +++ b/plugin/builder/build-plugin.ts @@ -12,7 +12,7 @@ import { } from "./utils/paths"; import { PluginEnv, setupPluginEnv } from "./cli/setup-plugin-environment"; import { cleanupPluginFiles } from "./utils/cleanup"; -import { bundlePnpmStore, getPnpmBundleName } from "./build-pnpm-store"; +import { bundleVendorStore, getVendorBundleName } from "./build-vendor-store"; /** * Check if git is available @@ -61,8 +61,8 @@ const buildPlugin = async ({ pluginURL: getPluginUrl({ baseUrl, tag }), MAIN_TXZ: getMainTxzUrl({ baseUrl, pluginVersion, tag }), TXZ_SHA256: txzSha256, - VENDOR_STORE_URL: getAssetUrl({ baseUrl, tag }, getPnpmBundleName()), - VENDOR_STORE_FILENAME: getPnpmBundleName(), + VENDOR_STORE_URL: getAssetUrl({ baseUrl, tag }, getVendorBundleName()), + VENDOR_STORE_FILENAME: getVendorBundleName(), ...(tag ? { TAG: tag } : {}), }; @@ -108,7 +108,7 @@ const main = async () => { await buildPlugin(validatedEnv); await moveTxzFile(validatedEnv.txzPath, validatedEnv.pluginVersion); - await bundlePnpmStore(); + await bundleVendorStore(); } catch (error) { console.error(error); process.exit(1); diff --git a/plugin/builder/build-pnpm-store.ts b/plugin/builder/build-pnpm-store.ts deleted file mode 100644 index f80e0b667..000000000 --- a/plugin/builder/build-pnpm-store.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { apiDir, deployDir } from "./utils/paths"; -import { join } from "path"; -import { readFileSync } from "node:fs"; -import { startingDir } from "./utils/consts"; -import { copyFile } from "node:fs/promises"; - -/** - * Get the version of the API from the package.json file - * - * Throws if package.json is not found or is invalid JSON. - * @returns The version of the API - */ -function getVersion(): string { - const packageJsonPath = join(apiDir, "package.json"); - const packageJsonString = readFileSync(packageJsonPath, "utf8"); - const packageJson = JSON.parse(packageJsonString); - return packageJson.version; -} - -/** - * The name of the pnpm store archive that will be vendored with the plugin. - * @returns The name of the pnpm store bundle file - */ -export function getPnpmBundleName(): string { - const version = getVersion(); - return `pnpm-store-for-v${version}.txz`; -} - -/** - * Prepare a versioned bundle of the API's pnpm store to vendor dependencies. - * - * It expects a generic `packed-pnpm-store.txz` archive to be available in the `startingDir`. - * It copies this archive to the `deployDir` directory and adds a version to the filename. - * It does not actually create the packed pnpm store archive; that is done inside the API's build script. - * - * After this operation, the vendored store will be available inside the `deployDir`. - */ -export async function bundlePnpmStore(): Promise { - const storeArchive = join(startingDir, "packed-pnpm-store.txz"); - const pnpmStoreTarPath = join(deployDir, getPnpmBundleName()); - await copyFile(storeArchive, pnpmStoreTarPath); -} diff --git a/plugin/builder/build-vendor-store.ts b/plugin/builder/build-vendor-store.ts new file mode 100644 index 000000000..a8a25f15b --- /dev/null +++ b/plugin/builder/build-vendor-store.ts @@ -0,0 +1,42 @@ +import { apiDir, deployDir } from "./utils/paths"; +import { join } from "path"; +import { readFileSync } from "node:fs"; +import { startingDir } from "./utils/consts"; +import { copyFile } from "node:fs/promises"; + +/** + * Get the version of the API from the package.json file + * + * Throws if package.json is not found or is invalid JSON. + * @returns The version of the API + */ +function getVersion(): string { + const packageJsonPath = join(apiDir, "package.json"); + const packageJsonString = readFileSync(packageJsonPath, "utf8"); + const packageJson = JSON.parse(packageJsonString); + return packageJson.version; +} + +/** + * The name of the node_modules archive that will be vendored with the plugin. + * @returns The name of the node_modules bundle file + */ +export function getVendorBundleName(): string { + const version = getVersion(); + return `node_modules-for-v${version}.tar.xz`; +} + +/** + * Prepare a versioned bundle of the API's node_modules to vendor dependencies. + * + * It expects a generic `packed-node-modules.tar.xz` archive to be available in the `startingDir`. + * It copies this archive to the `deployDir` directory and adds a version to the filename. + * It does not actually create the packed node_modules archive; that is done inside the API's build script. + * + * After this operation, the vendored node_modules will be available inside the `deployDir`. + */ +export async function bundleVendorStore(): Promise { + const storeArchive = join(startingDir, "packed-node-modules.tar.xz"); + const vendorStoreTarPath = join(deployDir, getVendorBundleName()); + await copyFile(storeArchive, vendorStoreTarPath); +} diff --git a/plugin/docker-compose.yml b/plugin/docker-compose.yml index cbc85af00..ab90db777 100644 --- a/plugin/docker-compose.yml +++ b/plugin/docker-compose.yml @@ -12,7 +12,7 @@ services: - ../unraid-ui/dist-wc:/app/source/dynamix.unraid.net/usr/local/emhttp/plugins/dynamix.my.servers/unraid-components/uui - ../web/.nuxt/nuxt-custom-elements/dist/unraid-components:/app/source/dynamix.unraid.net/usr/local/emhttp/plugins/dynamix.my.servers/unraid-components/nuxt - ../api/deploy/pack/:/app/source/dynamix.unraid.net/usr/local/unraid-api - - ../api/deploy/packed-pnpm-store.txz:/app/packed-pnpm-store.txz + - ../api/deploy/packed-node-modules.tar.xz:/app/packed-node-modules.tar.xz stdin_open: true # equivalent to -i tty: true # equivalent to -t environment: diff --git a/plugin/plugins/dynamix.unraid.net.plg b/plugin/plugins/dynamix.unraid.net.plg index 373da2f34..5823b8a5b 100755 --- a/plugin/plugins/dynamix.unraid.net.plg +++ b/plugin/plugins/dynamix.unraid.net.plg @@ -152,8 +152,9 @@ exit 0 # deprecated Apr 2025. kept to remove unused archives for users upgrading from versioned node downloads. find /boot/config/plugins/dynamix.my.servers/ -name "node-v*-linux-x64.tar.xz" ! -name "${NODE_FILE}" -delete - # Remove stale pnpm store archives from the boot drive + # Remove stale pnpm store and node_modules archives from the boot drive find /boot/config/plugins/dynamix.my.servers/ -name "pnpm-store-for-v*.txz" ! -name "${VENDOR_ARCHIVE}" -delete + find /boot/config/plugins/dynamix.my.servers/ -name "node_modules-for-v*.tar.xz" ! -name "${VENDOR_ARCHIVE}" -delete # Remove the legacy node directory rm -rf /usr/local/node @@ -811,11 +812,8 @@ cp -f "${PNPM_BINARY_FILE}" /usr/local/bin/pnpm chmod +x /usr/local/bin/pnpm /etc/rc.d/rc.unraid-api restore-dependencies "$VENDOR_ARCHIVE" -/etc/rc.d/rc.unraid-api pnpm-install -echo -echo "About to start the Unraid API" - +echo "Starting flash backup (if enabled)" logger "Starting flash backup (if enabled)" echo "/etc/rc.d/rc.flash_backup start" | at -M now &>/dev/null . /root/.bashrc diff --git a/plugin/source/dynamix.unraid.net/etc/rc.d/rc.unraid-api b/plugin/source/dynamix.unraid.net/etc/rc.d/rc.unraid-api index 1471396cd..42dfb499b 100755 --- a/plugin/source/dynamix.unraid.net/etc/rc.d/rc.unraid-api +++ b/plugin/source/dynamix.unraid.net/etc/rc.d/rc.unraid-api @@ -8,7 +8,7 @@ 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" +dependencies_dir="/usr/local/unraid-api/node_modules" # Placeholder functions for plugin installation/uninstallation install() { @@ -18,53 +18,13 @@ 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 +# Restores the node_modules directory 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 - +restore_dependencies() { local backup_file="$1" # Check if backup file exists if [ ! -f "$backup_file" ]; then @@ -76,7 +36,7 @@ restore_pnpm_store() { 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=$(df --output=avail "$(dirname "$dependencies_dir")" | tail -n1) dest_space=$((dest_space * 1024)) # Convert KB to bytes # Require 1.5x the backup size for safe extraction @@ -87,53 +47,48 @@ restore_pnpm_store() { return 1 fi - echo "Restoring pnpm store from '$backup_file' to '$pnpm_store_dir'" + echo "Restoring node_modules from '$backup_file' to '$dependencies_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")" + rm -rf "$dependencies_dir" + mkdir -p "$(dirname "$dependencies_dir")" # Extract directly to final location - if ! tar -xJf "$backup_file" -C "$(dirname "$pnpm_store_dir")" --preserve-permissions; then + if ! tar -xJf "$backup_file" -C "$(dirname "$dependencies_dir")" --preserve-permissions; then echo "Error: Failed to extract backup to final location." - rm -rf "$pnpm_store_dir" + rm -rf "$dependencies_dir" return 1 fi - echo "pnpm store restored successfully." + echo "node_modules restored successfully." } -# Executes pnpm install with production dependencies and offline preference -# Captures and logs build script warnings to a dedicated log file at /var/log/unraid-api/build-scripts.log +# Archives the node_modules directory to a specified location # Args: none -# Output: Streams install progress and logs build script warnings -run_pnpm_install() { - local log_file="/var/log/unraid-api/build-scripts.log" - stdbuf -oL pnpm install --prod --prefer-offline --reporter=append-only 2>&1 | sed -e "/^╭ Warning/,/^╰/w $log_file" -e "/^╭ Warning/,/^╰/c\Build scripts completed. See $log_file for details." - echo "Note: This warning is expected. Build scripts are intentionally ignored for security and performance reasons." >> "$log_file" -} - -# 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." +archive_dependencies() { + local source_dir="/usr/local/unraid-api/node_modules" + local dest_dir="/boot/config/plugins/dynamix.my.servers" + local archive_file="${dest_dir}/node_modules.tar.xz" + + # Check if source directory exists + if [ ! -d "$source_dir" ]; then + echo "Error: Source node_modules directory '$source_dir' does not exist." 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." + # Create destination directory if it doesn't exist + mkdir -p "$dest_dir" + + echo "Archiving node_modules from '$source_dir' to '$archive_file'" + + # Create archive with XZ compression level 5, preserving symlinks + if XZ_OPT=-5 tar -cJf "$archive_file" -C "$(dirname "$source_dir")" "$(basename "$source_dir")"; then + echo "node_modules archive created successfully." + else + echo "Error: Failed to create node_modules archive." 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" && run_pnpm_install) } case "$1" in @@ -146,14 +101,11 @@ case "$1" in 'uninstall') uninstall ;; -'pnpm-install') - pnpm_install_unraid_api - ;; -'backup-dependencies') - backup_pnpm_store "$2" - ;; 'restore-dependencies') - restore_pnpm_store "$2" + restore_dependencies "$2" + ;; +'archive-dependencies') + archive_dependencies ;; *) # Pass all other commands to unraid-api