From 8c0b1c3a78e6b1c97875e8a3c927a5f304a05578 Mon Sep 17 00:00:00 2001 From: Pujit Mehrotra Date: Fri, 27 Jun 2025 13:37:37 -0400 Subject: [PATCH 01/21] build: update doinst during build-plugin step --- plugin/builder/build-txz.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin/builder/build-txz.ts b/plugin/builder/build-txz.ts index e0c808097c..39b6766bd7 100644 --- a/plugin/builder/build-txz.ts +++ b/plugin/builder/build-txz.ts @@ -187,7 +187,7 @@ const buildTxz = async (validatedEnv: TxzEnv) => { // Create the package using the default package name await $`${join(startingDir, "scripts/makepkg")} --chown y --compress -${ validatedEnv.compress - } --linkadd n ${txzPath}`; + } --linkadd y ${txzPath}`; $.verbose = false; await cd(startingDir); }; From 27b31f2c8d6124ce6ca834d5f583812594e71222 Mon Sep 17 00:00:00 2001 From: Pujit Mehrotra Date: Fri, 27 Jun 2025 13:40:57 -0400 Subject: [PATCH 02/21] rm log debugging from doinst --- plugin/source/dynamix.unraid.net/install/doinst.sh | 7 ------- 1 file changed, 7 deletions(-) diff --git a/plugin/source/dynamix.unraid.net/install/doinst.sh b/plugin/source/dynamix.unraid.net/install/doinst.sh index 2ee92da52e..d2b61e4533 100644 --- a/plugin/source/dynamix.unraid.net/install/doinst.sh +++ b/plugin/source/dynamix.unraid.net/install/doinst.sh @@ -8,13 +8,6 @@ INSTALL_MODE="${1:-install}" # Use absolute paths for script directory to avoid path resolution issues SCRIPTS_DIR="/usr/local/share/dynamix.unraid.net/install/scripts" -# Log file for debugging -LOGFILE="/var/log/unraid-api/dynamix-unraid-install.log" -mkdir -p "$(dirname "$LOGFILE")" -date > "$LOGFILE" -echo "Starting installation with mode: $INSTALL_MODE" >> "$LOGFILE" -echo "Script directory: $SCRIPTS_DIR" >> "$LOGFILE" - # Make sure scripts are executable if [ -d "$SCRIPTS_DIR" ]; then chmod +x "$SCRIPTS_DIR"/*.sh From c1e4e77e82f05b8ac16a27652b9e5847c1e78528 Mon Sep 17 00:00:00 2001 From: Pujit Mehrotra Date: Fri, 27 Jun 2025 13:41:35 -0400 Subject: [PATCH 03/21] rm redudant `chmod +x` in doinst --- plugin/source/dynamix.unraid.net/install/doinst.sh | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/plugin/source/dynamix.unraid.net/install/doinst.sh b/plugin/source/dynamix.unraid.net/install/doinst.sh index d2b61e4533..79cdfd50e4 100644 --- a/plugin/source/dynamix.unraid.net/install/doinst.sh +++ b/plugin/source/dynamix.unraid.net/install/doinst.sh @@ -8,16 +8,6 @@ INSTALL_MODE="${1:-install}" # Use absolute paths for script directory to avoid path resolution issues SCRIPTS_DIR="/usr/local/share/dynamix.unraid.net/install/scripts" -# Make sure scripts are executable -if [ -d "$SCRIPTS_DIR" ]; then - chmod +x "$SCRIPTS_DIR"/*.sh - echo "Made scripts executable" >> "$LOGFILE" -else - echo "ERROR: Scripts directory not found: $SCRIPTS_DIR" >> "$LOGFILE" - # Create directory structure if it doesn't exist yet - mkdir -p "$SCRIPTS_DIR" -fi - # Process based on installation mode if [ "$INSTALL_MODE" = "install" ] || [ "$INSTALL_MODE" = "upgrade" ]; then echo "Starting Unraid Connect installation..." From ca5a9599c02eeff9ff071c6ebf5fc3e78ba1c9b7 Mon Sep 17 00:00:00 2001 From: Pujit Mehrotra Date: Fri, 27 Jun 2025 13:44:34 -0400 Subject: [PATCH 04/21] rm no-op remove hook?? --- plugin/source/dynamix.unraid.net/install/doinst.sh | 7 ------- 1 file changed, 7 deletions(-) diff --git a/plugin/source/dynamix.unraid.net/install/doinst.sh b/plugin/source/dynamix.unraid.net/install/doinst.sh index 79cdfd50e4..bee53b38f0 100644 --- a/plugin/source/dynamix.unraid.net/install/doinst.sh +++ b/plugin/source/dynamix.unraid.net/install/doinst.sh @@ -71,11 +71,4 @@ if [ "$INSTALL_MODE" = "install" ] || [ "$INSTALL_MODE" = "upgrade" ]; then echo "Installation completed successfully." echo "Installation completed at $(date)" >> "$LOGFILE" - -elif [ "$INSTALL_MODE" = "remove" ]; then - echo "Starting Unraid Connect removal..." - echo "Starting removal" >> "$LOGFILE" - - echo "Removal completed successfully." - echo "Removal completed at $(date)" >> "$LOGFILE" fi From a9c3f3dc0a3dc763ad41902697178ee68aa3f9ad Mon Sep 17 00:00:00 2001 From: Pujit Mehrotra Date: Fri, 27 Jun 2025 14:06:07 -0400 Subject: [PATCH 05/21] port doinst.sh effects to plg file --- plugin/plugins/dynamix.unraid.net.plg | 90 +++++++++++-- .../dynamix.unraid.net/install/doinst.sh | 75 +---------- .../install/scripts/file_patches.sh | 127 +++++++++--------- 3 files changed, 147 insertions(+), 145 deletions(-) diff --git a/plugin/plugins/dynamix.unraid.net.plg b/plugin/plugins/dynamix.unraid.net.plg index 9420991949..21f7d1f829 100755 --- a/plugin/plugins/dynamix.unraid.net.plg +++ b/plugin/plugins/dynamix.unraid.net.plg @@ -210,7 +210,7 @@ exit 0 - + TAG="&tag;" @@ -242,17 +242,90 @@ if [ $? -ne 0 ]; then exit 1 fi -# Clean up any old node_modules archives (on the boot drive) that don't match our current version -# -# Must run after package installation because it provides an update api config file, -# which determines the current API version and vendor archive to keep. -/etc/rc.d/rc.unraid-api cleanup-dependencies - if [[ -n "$TAG" && "$TAG" != "" ]]; then printf -v sedcmd 's@^\*\*Unraid Connect\*\*@**Unraid Connect (%s)**@' "$TAG" sed -i "${sedcmd}" "/usr/local/emhttp/plugins/dynamix.unraid.net/README.md" fi +exit 0 +]]> + + + + + + + > "$LOGFILE" + # Capture output and add to log file + patches_output=$("$SCRIPTS_DIR/file_patches.sh") + echo "$patches_output" >> "$LOGFILE" +else + echo "ERROR: file_patches.sh not found or not executable" >> "$LOGFILE" +fi + +# Setup the API (but don't start it yet) +if [ -x "$SCRIPTS_DIR/setup_api.sh" ]; then + echo "Setting up Unraid API..." + echo "Running setup_api.sh" >> "$LOGFILE" + # Capture output and add to log file + setup_output=$("$SCRIPTS_DIR/setup_api.sh") + echo "$setup_output" >> "$LOGFILE" + + # Verify symlinks were created + if [ -L "/usr/local/bin/unraid-api" ]; then + echo "Symlink created successfully" >> "$LOGFILE" + else + echo "ERROR: Symlink not created, attempting to create manually" >> "$LOGFILE" + # Create the symlink manually as fallback + if [ -f "/usr/local/unraid-api/dist/cli.js" ]; then + ln -sf "/usr/local/unraid-api/dist/cli.js" "/usr/local/bin/unraid-api" + ln -sf "/usr/local/bin/unraid-api" "/usr/local/sbin/unraid-api" + ln -sf "/usr/local/bin/unraid-api" "/usr/bin/unraid-api" + echo "Manually created symlinks" >> "$LOGFILE" + else + echo "ERROR: Source file for symlink not found" >> "$LOGFILE" + fi + fi +else + echo "ERROR: setup_api.sh not found or not executable" >> "$LOGFILE" +fi + +# Run post-installation verification +if [ -x "$SCRIPTS_DIR/verify_install.sh" ]; then + echo "Running post-installation verification..." + echo "Running verify_install.sh" >> "$LOGFILE" + # Capture output and add to log file + verify_output=$("$SCRIPTS_DIR/verify_install.sh") + echo "$verify_output" >> "$LOGFILE" +else + echo "ERROR: verify_install.sh not found or not executable" >> "$LOGFILE" +fi + +echo "Installation completed at $(date)" >> "$LOGFILE" + ]]> + + + + + + + + ]]> diff --git a/plugin/source/dynamix.unraid.net/install/doinst.sh b/plugin/source/dynamix.unraid.net/install/doinst.sh index bee53b38f0..96b4b06ad4 100644 --- a/plugin/source/dynamix.unraid.net/install/doinst.sh +++ b/plugin/source/dynamix.unraid.net/install/doinst.sh @@ -1,74 +1 @@ -#!/bin/sh -set -eu -# Main installation script for dynamix.unraid.net package -# This script calls specialized external scripts to handle different aspects of installation - -# Get the install mode (passed as the first argument by the installpkg script) -INSTALL_MODE="${1:-install}" -# Use absolute paths for script directory to avoid path resolution issues -SCRIPTS_DIR="/usr/local/share/dynamix.unraid.net/install/scripts" - -# Process based on installation mode -if [ "$INSTALL_MODE" = "install" ] || [ "$INSTALL_MODE" = "upgrade" ]; then - echo "Starting Unraid Connect installation..." - - # Apply file patches and system configurations - if [ -x "$SCRIPTS_DIR/file_patches.sh" ]; then - echo "Applying system patches and configurations..." - echo "Running file_patches.sh" >> "$LOGFILE" - # Capture output and add to log file - patches_output=$("$SCRIPTS_DIR/file_patches.sh") - echo "$patches_output" >> "$LOGFILE" - else - echo "ERROR: file_patches.sh not found or not executable" >> "$LOGFILE" - fi - - # Setup the API (but don't start it yet) - if [ -x "$SCRIPTS_DIR/setup_api.sh" ]; then - echo "Setting up Unraid API..." - echo "Running setup_api.sh" >> "$LOGFILE" - # Capture output and add to log file - setup_output=$("$SCRIPTS_DIR/setup_api.sh") - echo "$setup_output" >> "$LOGFILE" - - # Verify symlinks were created - if [ -L "/usr/local/bin/unraid-api" ]; then - echo "Symlink created successfully" >> "$LOGFILE" - else - echo "ERROR: Symlink not created, attempting to create manually" >> "$LOGFILE" - # Create the symlink manually as fallback - if [ -f "/usr/local/unraid-api/dist/cli.js" ]; then - ln -sf "/usr/local/unraid-api/dist/cli.js" "/usr/local/bin/unraid-api" - ln -sf "/usr/local/bin/unraid-api" "/usr/local/sbin/unraid-api" - ln -sf "/usr/local/bin/unraid-api" "/usr/bin/unraid-api" - echo "Manually created symlinks" >> "$LOGFILE" - else - echo "ERROR: Source file for symlink not found" >> "$LOGFILE" - fi - fi - else - echo "ERROR: setup_api.sh not found or not executable" >> "$LOGFILE" - fi - - # Make the rc script executable - if [ -f /etc/rc.d/rc.unraid-api ]; then - chmod 755 /etc/rc.d/rc.unraid-api - echo "Made rc.unraid-api executable" >> "$LOGFILE" - else - echo "ERROR: rc.unraid-api not found" >> "$LOGFILE" - fi - - # Run post-installation verification - if [ -x "$SCRIPTS_DIR/verify_install.sh" ]; then - echo "Running post-installation verification..." - echo "Running verify_install.sh" >> "$LOGFILE" - # Capture output and add to log file - verify_output=$("$SCRIPTS_DIR/verify_install.sh") - echo "$verify_output" >> "$LOGFILE" - else - echo "ERROR: verify_install.sh not found or not executable" >> "$LOGFILE" - fi - - echo "Installation completed successfully." - echo "Installation completed at $(date)" >> "$LOGFILE" -fi +#!/bin/sh \ No newline at end of file diff --git a/plugin/source/dynamix.unraid.net/usr/local/share/dynamix.unraid.net/install/scripts/file_patches.sh b/plugin/source/dynamix.unraid.net/usr/local/share/dynamix.unraid.net/install/scripts/file_patches.sh index 154b90e482..8824aebca0 100755 --- a/plugin/source/dynamix.unraid.net/usr/local/share/dynamix.unraid.net/install/scripts/file_patches.sh +++ b/plugin/source/dynamix.unraid.net/usr/local/share/dynamix.unraid.net/install/scripts/file_patches.sh @@ -1,74 +1,77 @@ #!/bin/sh # Script to handle file patches +# DEPRECATED +# TODO: move into file modifications as needed + # Patch nginx config if needed -NGINX_CHANGED=0 -FILE=/etc/nginx/nginx.conf -if grep -q "SAMEORIGIN" "${FILE}" >/dev/null 2>&1; then - cp -p "$FILE" "$FILE-" - OLD="add_header X-Frame-Options 'SAMEORIGIN';" - NEW="add_header Content-Security-Policy \"frame-ancestors 'self' https://connect.myunraid.net/\";" - sed -i "s|${OLD}|${NEW}|" "${FILE}" - NGINX_CHANGED=1 -fi +# NGINX_CHANGED=0 +# FILE=/etc/nginx/nginx.conf +# if grep -q "SAMEORIGIN" "${FILE}" >/dev/null 2>&1; then +# cp -p "$FILE" "$FILE-" +# OLD="add_header X-Frame-Options 'SAMEORIGIN';" +# NEW="add_header Content-Security-Policy \"frame-ancestors 'self' https://connect.myunraid.net/\";" +# sed -i "s|${OLD}|${NEW}|" "${FILE}" +# NGINX_CHANGED=1 +# fi -# Patch robots.txt handling -FILE=/etc/rc.d/rc.nginx -if ! grep -q "#robots.txt any origin" "${FILE}" >/dev/null 2>&1; then - cp -p "$FILE" "$FILE-" - FIND="location = \/robots.txt {" - # escape tabs and spaces - ADD="\t add_header Access-Control-Allow-Origin *; #robots.txt any origin" - # shell-safe: pass ADD via printf to preserve escapes - sed -i "/${FIND}/a $(printf '%s\n' "${ADD}")" "${FILE}" - NGINX_CHANGED=1 -fi +# # Patch robots.txt handling +# FILE=/etc/rc.d/rc.nginx +# if ! grep -q "#robots.txt any origin" "${FILE}" >/dev/null 2>&1; then +# cp -p "$FILE" "$FILE-" +# FIND="location = \/robots.txt {" +# # escape tabs and spaces +# ADD="\t add_header Access-Control-Allow-Origin *; #robots.txt any origin" +# # shell-safe: pass ADD via printf to preserve escapes +# sed -i "/${FIND}/a $(printf '%s\n' "${ADD}")" "${FILE}" +# NGINX_CHANGED=1 +# fi -# Remove keys.limetechnology.com from hosts file -if grep -q "keys.lime-technology.com" /etc/hosts >/dev/null 2>&1; then - sed -i "/keys.lime-technology.com/d" /etc/hosts >/dev/null 2>&1 -fi +# # Remove keys.limetechnology.com from hosts file +# if grep -q "keys.lime-technology.com" /etc/hosts >/dev/null 2>&1; then +# sed -i "/keys.lime-technology.com/d" /etc/hosts >/dev/null 2>&1 +# fi -# Fix update.htm to work in an iframe -FILE=/usr/local/emhttp/update.htm -if [ -f "${FILE}" ] && grep -q "top.document" "${FILE}" >/dev/null 2>&1; then - cp -p "$FILE" "$FILE-" - sed -i 's|top.document|parent.document|gm' "${FILE}" -fi +# # Fix update.htm to work in an iframe +# FILE=/usr/local/emhttp/update.htm +# if [ -f "${FILE}" ] && grep -q "top.document" "${FILE}" >/dev/null 2>&1; then +# cp -p "$FILE" "$FILE-" +# sed -i 's|top.document|parent.document|gm' "${FILE}" +# fi -# Fix logging.htm to work in an iframe -FILE=/usr/local/emhttp/logging.htm -if [ -f "${FILE}" ] && grep -q "top.Shadowbox" "${FILE}" >/dev/null 2>&1; then - cp -p "$FILE" "$FILE-" - sed -i 's|top.Shadowbox|parent.Shadowbox|gm' "${FILE}" -fi +# # Fix logging.htm to work in an iframe +# FILE=/usr/local/emhttp/logging.htm +# if [ -f "${FILE}" ] && grep -q "top.Shadowbox" "${FILE}" >/dev/null 2>&1; then +# cp -p "$FILE" "$FILE-" +# sed -i 's|top.Shadowbox|parent.Shadowbox|gm' "${FILE}" +# fi -# Relax restrictions on built-in Firefox -FIREFOX_DIR=/usr/share/mozilla/firefox -# Find the default profile directory (may change in future versions) -PROFILE_DIR=$(find "$FIREFOX_DIR" -name "*.default" -type d 2>/dev/null | head -n 1) +# # Relax restrictions on built-in Firefox +# FIREFOX_DIR=/usr/share/mozilla/firefox +# # Find the default profile directory (may change in future versions) +# PROFILE_DIR=$(find "$FIREFOX_DIR" -name "*.default" -type d 2>/dev/null | head -n 1) -if [ -z "$PROFILE_DIR" ]; then - echo "Firefox default profile directory not found, skipping Firefox configuration" -else - FILE="$PROFILE_DIR/user.js" - if [ -f "$FILE" ]; then - cp -p "$FILE" "$FILE-" - # Append settings if they don't exist - grep -q "privacy.firstparty.isolate" "$FILE" || echo 'user_pref("privacy.firstparty.isolate", false);' >> "$FILE" - grep -q "javascript.options.asmjs" "$FILE" || echo 'user_pref("javascript.options.asmjs", true);' >> "$FILE" - grep -q "javascript.options.wasm" "$FILE" || echo 'user_pref("javascript.options.wasm", true);' >> "$FILE" - echo "Updated Firefox preferences in $FILE" - fi -fi +# if [ -z "$PROFILE_DIR" ]; then +# echo "Firefox default profile directory not found, skipping Firefox configuration" +# else +# FILE="$PROFILE_DIR/user.js" +# if [ -f "$FILE" ]; then +# cp -p "$FILE" "$FILE-" +# # Append settings if they don't exist +# grep -q "privacy.firstparty.isolate" "$FILE" || echo 'user_pref("privacy.firstparty.isolate", false);' >> "$FILE" +# grep -q "javascript.options.asmjs" "$FILE" || echo 'user_pref("javascript.options.asmjs", true);' >> "$FILE" +# grep -q "javascript.options.wasm" "$FILE" || echo 'user_pref("javascript.options.wasm", true);' >> "$FILE" +# echo "Updated Firefox preferences in $FILE" +# fi +# fi -# Move settings on flash drive -CFG_OLD=/boot/config/plugins/Unraid.net -CFG_NEW=/boot/config/plugins/dynamix.my.servers -[ -d "$CFG_OLD" ] && [ ! -d "$CFG_NEW" ] && mv "$CFG_OLD" "$CFG_NEW" +# # Move settings on flash drive +# CFG_OLD=/boot/config/plugins/Unraid.net +# CFG_NEW=/boot/config/plugins/dynamix.my.servers +# [ -d "$CFG_OLD" ] && [ ! -d "$CFG_NEW" ] && mv "$CFG_OLD" "$CFG_NEW" -# Reload nginx if needed -if [ "$NGINX_CHANGED" = "1" ] && /etc/rc.d/rc.nginx status >/dev/null 2>&1; then - echo "Reloading web server to apply changes" - /etc/rc.d/rc.nginx reload >/dev/null 2>&1 -fi \ No newline at end of file +# # Reload nginx if needed +# if [ "$NGINX_CHANGED" = "1" ] && /etc/rc.d/rc.nginx status >/dev/null 2>&1; then +# echo "Reloading web server to apply changes" +# /etc/rc.d/rc.nginx reload >/dev/null 2>&1 +# fi \ No newline at end of file From f9952ef57dac9dc0f69ca34db063fbaada3265d3 Mon Sep 17 00:00:00 2001 From: Pujit Mehrotra Date: Fri, 27 Jun 2025 14:13:07 -0400 Subject: [PATCH 06/21] rm `/boot` bc it gets overwritten anyway --- .../boot/config/plugins/dynamix.my.servers/.gitignore | 1 - 1 file changed, 1 deletion(-) delete mode 100644 plugin/source/dynamix.unraid.net/boot/config/plugins/dynamix.my.servers/.gitignore diff --git a/plugin/source/dynamix.unraid.net/boot/config/plugins/dynamix.my.servers/.gitignore b/plugin/source/dynamix.unraid.net/boot/config/plugins/dynamix.my.servers/.gitignore deleted file mode 100644 index 69e8c03b73..0000000000 --- a/plugin/source/dynamix.unraid.net/boot/config/plugins/dynamix.my.servers/.gitignore +++ /dev/null @@ -1 +0,0 @@ -webComps From 02e3f3ea844f61d96739523a8c82bb095a1f12ac Mon Sep 17 00:00:00 2001 From: Pujit Mehrotra Date: Fri, 27 Jun 2025 14:26:47 -0400 Subject: [PATCH 07/21] move unraid-api symlink assurance to doinst --- plugin/plugins/dynamix.unraid.net.plg | 16 ---------------- .../source/dynamix.unraid.net/install/doinst.sh | 7 ++++++- 2 files changed, 6 insertions(+), 17 deletions(-) diff --git a/plugin/plugins/dynamix.unraid.net.plg b/plugin/plugins/dynamix.unraid.net.plg index 21f7d1f829..4a4bbf9c04 100755 --- a/plugin/plugins/dynamix.unraid.net.plg +++ b/plugin/plugins/dynamix.unraid.net.plg @@ -280,22 +280,6 @@ if [ -x "$SCRIPTS_DIR/setup_api.sh" ]; then # Capture output and add to log file setup_output=$("$SCRIPTS_DIR/setup_api.sh") echo "$setup_output" >> "$LOGFILE" - - # Verify symlinks were created - if [ -L "/usr/local/bin/unraid-api" ]; then - echo "Symlink created successfully" >> "$LOGFILE" - else - echo "ERROR: Symlink not created, attempting to create manually" >> "$LOGFILE" - # Create the symlink manually as fallback - if [ -f "/usr/local/unraid-api/dist/cli.js" ]; then - ln -sf "/usr/local/unraid-api/dist/cli.js" "/usr/local/bin/unraid-api" - ln -sf "/usr/local/bin/unraid-api" "/usr/local/sbin/unraid-api" - ln -sf "/usr/local/bin/unraid-api" "/usr/bin/unraid-api" - echo "Manually created symlinks" >> "$LOGFILE" - else - echo "ERROR: Source file for symlink not found" >> "$LOGFILE" - fi - fi else echo "ERROR: setup_api.sh not found or not executable" >> "$LOGFILE" fi diff --git a/plugin/source/dynamix.unraid.net/install/doinst.sh b/plugin/source/dynamix.unraid.net/install/doinst.sh index 96b4b06ad4..362c176847 100644 --- a/plugin/source/dynamix.unraid.net/install/doinst.sh +++ b/plugin/source/dynamix.unraid.net/install/doinst.sh @@ -1 +1,6 @@ -#!/bin/sh \ No newline at end of file +#!/bin/sh + +rm -rf usr/local/bin/unraid-api +ln -sf usr/local/unraid-api/dist/cli.js usr/local/bin/unraid-api + +# auto-generated actions from makepkg: From 641801bc00bbf62dcae5da25dbd68f2e1e0011f0 Mon Sep 17 00:00:00 2001 From: Pujit Mehrotra Date: Fri, 27 Jun 2025 15:05:15 -0400 Subject: [PATCH 08/21] port setup_api.sh --- api/src/environment.ts | 1 + api/src/unraid-api/cli/pm2.service.ts | 11 +++- api/src/unraid-api/cli/start.command.ts | 1 + .../dynamix.unraid.net/install/doinst.sh | 20 +++++++ .../install/scripts/setup_api.sh | 59 ------------------- 5 files changed, 31 insertions(+), 61 deletions(-) diff --git a/api/src/environment.ts b/api/src/environment.ts index eb4e81eaa1..f418bd8a61 100644 --- a/api/src/environment.ts +++ b/api/src/environment.ts @@ -99,6 +99,7 @@ export const MOTHERSHIP_GRAPHQL_LINK = process.env.MOTHERSHIP_GRAPHQL_LINK export const PM2_HOME = process.env.PM2_HOME ?? join(homedir(), '.pm2'); export const PM2_PATH = join(import.meta.dirname, '../../', 'node_modules', 'pm2', 'bin', 'pm2'); export const ECOSYSTEM_PATH = join(import.meta.dirname, '../../', 'ecosystem.config.json'); +export const LOGS_DIR = process.env.LOGS_DIR ?? '/var/log/unraid-api'; export const PATHS_CONFIG_MODULES = process.env.PATHS_CONFIG_MODULES ?? '/boot/config/plugins/dynamix.my.servers/configs'; diff --git a/api/src/unraid-api/cli/pm2.service.ts b/api/src/unraid-api/cli/pm2.service.ts index cf0fbc7714..79c59a9097 100644 --- a/api/src/unraid-api/cli/pm2.service.ts +++ b/api/src/unraid-api/cli/pm2.service.ts @@ -1,12 +1,12 @@ import { Injectable } from '@nestjs/common'; import { existsSync } from 'node:fs'; -import { rm } from 'node:fs/promises'; +import { mkdir, rm } from 'node:fs/promises'; import { join } from 'node:path'; import type { Options, Result, ResultPromise } from 'execa'; import { execa, ExecaError } from 'execa'; -import { PM2_HOME, PM2_PATH } from '@app/environment.js'; +import { LOGS_DIR, PM2_HOME, PM2_PATH } from '@app/environment.js'; import { LogService } from '@app/unraid-api/cli/log.service.js'; type CmdContext = Options & { @@ -97,4 +97,11 @@ export class PM2Service { this.logger.trace('PM2 home directory does not exist.'); } } + + /** + * Ensures that the dependencies necessary for PM2 to start and operate are present. + */ + async ensurePm2Dependencies() { + await mkdir(LOGS_DIR, { recursive: true }); + } } diff --git a/api/src/unraid-api/cli/start.command.ts b/api/src/unraid-api/cli/start.command.ts index e9b8939575..33e2c4845d 100644 --- a/api/src/unraid-api/cli/start.command.ts +++ b/api/src/unraid-api/cli/start.command.ts @@ -20,6 +20,7 @@ export class StartCommand extends CommandRunner { } async cleanupPM2State() { + await this.pm2.ensurePm2Dependencies(); await this.pm2.run({ tag: 'PM2 Stop' }, 'stop', ECOSYSTEM_PATH); await this.pm2.run({ tag: 'PM2 Update' }, 'update'); await this.pm2.deleteDump(); diff --git a/plugin/source/dynamix.unraid.net/install/doinst.sh b/plugin/source/dynamix.unraid.net/install/doinst.sh index 362c176847..a28781d8f0 100644 --- a/plugin/source/dynamix.unraid.net/install/doinst.sh +++ b/plugin/source/dynamix.unraid.net/install/doinst.sh @@ -1,6 +1,26 @@ #!/bin/sh +backup_file_if_exists() { + if [ -f "$1" ]; then + mv "$1" "$1.old" + fi +} + +chmod 755 etc/rc.d/rc6.d/K*unraid-api +chmod 755 etc/rc.d/rc6.d/K*flash-backup + +chmod +x usr/local/unraid-api/dist/cli.js +chmod +x usr/local/unraid-api/dist/main.js + rm -rf usr/local/bin/unraid-api ln -sf usr/local/unraid-api/dist/cli.js usr/local/bin/unraid-api +# deprecated +ln -sf usr/local/bin/unraid-api usr/local/sbin/unraid-api +ln -sf usr/local/bin/unraid-api usr/bin/unraid-api + +# By default, we want to overwrite the active api-specific .env configuration on every install. +# We keep a backup in case a user needs to revert to their prior configuration. +backup_file_if_exists usr/local/unraid-api/.env +cp usr/local/unraid-api/.env.production usr/local/unraid-api/.env # auto-generated actions from makepkg: diff --git a/plugin/source/dynamix.unraid.net/usr/local/share/dynamix.unraid.net/install/scripts/setup_api.sh b/plugin/source/dynamix.unraid.net/usr/local/share/dynamix.unraid.net/install/scripts/setup_api.sh index e58783fa8c..69ab6d0030 100755 --- a/plugin/source/dynamix.unraid.net/usr/local/share/dynamix.unraid.net/install/scripts/setup_api.sh +++ b/plugin/source/dynamix.unraid.net/usr/local/share/dynamix.unraid.net/install/scripts/setup_api.sh @@ -19,65 +19,6 @@ else echo "Env file already exists" fi -# Create log directory (PM2 will not start without it) -mkdir -p /var/log/unraid-api -echo "Created log directory at /var/log/unraid-api" - -# Create Symlinks for the Unraid API -if [ -f "${API_BASE_DIR}/dist/cli.js" ]; then - echo "Creating symlinks for unraid-api" - ln -sf "${API_BASE_DIR}/dist/cli.js" "/usr/local/bin/unraid-api" - ln -sf "/usr/local/bin/unraid-api" "/usr/local/sbin/unraid-api" - ln -sf "/usr/local/bin/unraid-api" "/usr/bin/unraid-api" - - # Verify symlinks were created - if [ -L "/usr/local/bin/unraid-api" ]; then - echo "Symlinks created successfully" - else - echo "ERROR: Failed to create symlinks" - fi - - # Make API scripts executable - echo "Making API scripts executable" - chmod +x "${API_BASE_DIR}/dist/cli.js" - chmod +x "${API_BASE_DIR}/dist/main.js" - echo "API scripts are now executable" -else - echo "ERROR: Source file ${API_BASE_DIR}/dist/cli.js does not exist" - - # Check if the directory exists - if [ -d "${API_BASE_DIR}" ]; then - echo "API base directory exists" - ls -la "${API_BASE_DIR}" - - if [ -d "${API_BASE_DIR}/dist" ]; then - echo "Dist directory exists" - ls -la "${API_BASE_DIR}/dist" - else - echo "Dist directory does not exist" - fi - else - echo "API base directory does not exist" - fi -fi - -# Copy env file -if [ -f "${API_BASE_DIR}/.env.production" ]; then - echo "Copying .env.production to .env" - cp "${API_BASE_DIR}/.env.production" "${API_BASE_DIR}/.env" -else - echo "ERROR: .env.production file not found" -fi - -# Ensure rc directories exist and scripts are executable -echo "Ensuring shutdown scripts are executable" -if [ -d "/etc/rc.d/rc6.d" ]; then - chmod 755 /etc/rc.d/rc6.d/K*unraid-api 2>/dev/null - chmod 755 /etc/rc.d/rc6.d/K*flash-backup 2>/dev/null -else - echo "Warning: rc6.d directory does not exist" -fi - # Create symlink for rc0.d to rc6.d if needed if [ ! -L /etc/rc.d/rc0.d ] && [ ! -d /etc/rc.d/rc0.d ]; then echo "Creating symlink from /etc/rc.d/rc0.d to /etc/rc.d/rc6.d" From f6408c63bd22dd114cccd0a963f971420a5f0814 Mon Sep 17 00:00:00 2001 From: Pujit Mehrotra Date: Fri, 27 Jun 2025 15:10:14 -0400 Subject: [PATCH 09/21] rm file_patches.sh --- plugin/plugins/dynamix.unraid.net.plg | 10 --- .../install/scripts/file_patches.sh | 77 ------------------- 2 files changed, 87 deletions(-) delete mode 100755 plugin/source/dynamix.unraid.net/usr/local/share/dynamix.unraid.net/install/scripts/file_patches.sh diff --git a/plugin/plugins/dynamix.unraid.net.plg b/plugin/plugins/dynamix.unraid.net.plg index 4a4bbf9c04..ad7a5c6eb1 100755 --- a/plugin/plugins/dynamix.unraid.net.plg +++ b/plugin/plugins/dynamix.unraid.net.plg @@ -262,16 +262,6 @@ LOGFILE="/var/log/unraid-api/dynamix-unraid-install.log" mkdir -p "$(dirname "$LOGFILE")" echo "Starting Unraid Connect installation..." -# Apply file patches and system configurations -if [ -x "$SCRIPTS_DIR/file_patches.sh" ]; then - echo "Applying system patches and configurations..." - echo "Running file_patches.sh" >> "$LOGFILE" - # Capture output and add to log file - patches_output=$("$SCRIPTS_DIR/file_patches.sh") - echo "$patches_output" >> "$LOGFILE" -else - echo "ERROR: file_patches.sh not found or not executable" >> "$LOGFILE" -fi # Setup the API (but don't start it yet) if [ -x "$SCRIPTS_DIR/setup_api.sh" ]; then diff --git a/plugin/source/dynamix.unraid.net/usr/local/share/dynamix.unraid.net/install/scripts/file_patches.sh b/plugin/source/dynamix.unraid.net/usr/local/share/dynamix.unraid.net/install/scripts/file_patches.sh deleted file mode 100755 index 8824aebca0..0000000000 --- a/plugin/source/dynamix.unraid.net/usr/local/share/dynamix.unraid.net/install/scripts/file_patches.sh +++ /dev/null @@ -1,77 +0,0 @@ -#!/bin/sh -# Script to handle file patches - -# DEPRECATED -# TODO: move into file modifications as needed - -# Patch nginx config if needed -# NGINX_CHANGED=0 -# FILE=/etc/nginx/nginx.conf -# if grep -q "SAMEORIGIN" "${FILE}" >/dev/null 2>&1; then -# cp -p "$FILE" "$FILE-" -# OLD="add_header X-Frame-Options 'SAMEORIGIN';" -# NEW="add_header Content-Security-Policy \"frame-ancestors 'self' https://connect.myunraid.net/\";" -# sed -i "s|${OLD}|${NEW}|" "${FILE}" -# NGINX_CHANGED=1 -# fi - -# # Patch robots.txt handling -# FILE=/etc/rc.d/rc.nginx -# if ! grep -q "#robots.txt any origin" "${FILE}" >/dev/null 2>&1; then -# cp -p "$FILE" "$FILE-" -# FIND="location = \/robots.txt {" -# # escape tabs and spaces -# ADD="\t add_header Access-Control-Allow-Origin *; #robots.txt any origin" -# # shell-safe: pass ADD via printf to preserve escapes -# sed -i "/${FIND}/a $(printf '%s\n' "${ADD}")" "${FILE}" -# NGINX_CHANGED=1 -# fi - -# # Remove keys.limetechnology.com from hosts file -# if grep -q "keys.lime-technology.com" /etc/hosts >/dev/null 2>&1; then -# sed -i "/keys.lime-technology.com/d" /etc/hosts >/dev/null 2>&1 -# fi - -# # Fix update.htm to work in an iframe -# FILE=/usr/local/emhttp/update.htm -# if [ -f "${FILE}" ] && grep -q "top.document" "${FILE}" >/dev/null 2>&1; then -# cp -p "$FILE" "$FILE-" -# sed -i 's|top.document|parent.document|gm' "${FILE}" -# fi - -# # Fix logging.htm to work in an iframe -# FILE=/usr/local/emhttp/logging.htm -# if [ -f "${FILE}" ] && grep -q "top.Shadowbox" "${FILE}" >/dev/null 2>&1; then -# cp -p "$FILE" "$FILE-" -# sed -i 's|top.Shadowbox|parent.Shadowbox|gm' "${FILE}" -# fi - -# # Relax restrictions on built-in Firefox -# FIREFOX_DIR=/usr/share/mozilla/firefox -# # Find the default profile directory (may change in future versions) -# PROFILE_DIR=$(find "$FIREFOX_DIR" -name "*.default" -type d 2>/dev/null | head -n 1) - -# if [ -z "$PROFILE_DIR" ]; then -# echo "Firefox default profile directory not found, skipping Firefox configuration" -# else -# FILE="$PROFILE_DIR/user.js" -# if [ -f "$FILE" ]; then -# cp -p "$FILE" "$FILE-" -# # Append settings if they don't exist -# grep -q "privacy.firstparty.isolate" "$FILE" || echo 'user_pref("privacy.firstparty.isolate", false);' >> "$FILE" -# grep -q "javascript.options.asmjs" "$FILE" || echo 'user_pref("javascript.options.asmjs", true);' >> "$FILE" -# grep -q "javascript.options.wasm" "$FILE" || echo 'user_pref("javascript.options.wasm", true);' >> "$FILE" -# echo "Updated Firefox preferences in $FILE" -# fi -# fi - -# # Move settings on flash drive -# CFG_OLD=/boot/config/plugins/Unraid.net -# CFG_NEW=/boot/config/plugins/dynamix.my.servers -# [ -d "$CFG_OLD" ] && [ ! -d "$CFG_NEW" ] && mv "$CFG_OLD" "$CFG_NEW" - -# # Reload nginx if needed -# if [ "$NGINX_CHANGED" = "1" ] && /etc/rc.d/rc.nginx status >/dev/null 2>&1; then -# echo "Reloading web server to apply changes" -# /etc/rc.d/rc.nginx reload >/dev/null 2>&1 -# fi \ No newline at end of file From 220272a057bccd57a96661c271d4363519716b78 Mon Sep 17 00:00:00 2001 From: Pujit Mehrotra Date: Fri, 27 Jun 2025 15:47:08 -0400 Subject: [PATCH 10/21] fix symlink paths in doinst --- plugin/source/dynamix.unraid.net/install/doinst.sh | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/plugin/source/dynamix.unraid.net/install/doinst.sh b/plugin/source/dynamix.unraid.net/install/doinst.sh index a28781d8f0..24f62da54d 100644 --- a/plugin/source/dynamix.unraid.net/install/doinst.sh +++ b/plugin/source/dynamix.unraid.net/install/doinst.sh @@ -6,17 +6,18 @@ backup_file_if_exists() { fi } -chmod 755 etc/rc.d/rc6.d/K*unraid-api -chmod 755 etc/rc.d/rc6.d/K*flash-backup +for f in etc/rc.d/rc6.d/K*unraid-api etc/rc.d/rc6.d/K*flash-backup; do + [ -e "$f" ] && chmod 755 "$f" +done chmod +x usr/local/unraid-api/dist/cli.js chmod +x usr/local/unraid-api/dist/main.js rm -rf usr/local/bin/unraid-api -ln -sf usr/local/unraid-api/dist/cli.js usr/local/bin/unraid-api +ln -sf ../unraid-api/dist/cli.js usr/local/bin/unraid-api # deprecated -ln -sf usr/local/bin/unraid-api usr/local/sbin/unraid-api -ln -sf usr/local/bin/unraid-api usr/bin/unraid-api +ln -sf ../bin/unraid-api usr/local/sbin/unraid-api +ln -sf ../local/bin/unraid-api usr/bin/unraid-api # By default, we want to overwrite the active api-specific .env configuration on every install. # We keep a backup in case a user needs to revert to their prior configuration. From 894db4bb804855365f6060ca3359994f3cf74daf Mon Sep 17 00:00:00 2001 From: Pujit Mehrotra Date: Fri, 27 Jun 2025 16:51:19 -0400 Subject: [PATCH 11/21] move `NginxService` into api as global module dependency to make plumbing easier :( --- api/src/unraid-api/nginx/nginx.module.ts | 12 ++++++++++++ .../src/unraid-api/nginx}/nginx.service.ts | 0 api/src/unraid-api/plugin/global-deps.module.ts | 10 +++++++++- .../src/module/system.module.ts | 3 --- .../src/service/network.service.ts | 7 +++++-- packages/unraid-shared/src/services/nginx.ts | 7 +++++++ packages/unraid-shared/src/tokens.ts | 1 + 7 files changed, 34 insertions(+), 6 deletions(-) create mode 100644 api/src/unraid-api/nginx/nginx.module.ts rename {packages/unraid-api-plugin-connect/src/service => api/src/unraid-api/nginx}/nginx.service.ts (100%) create mode 100644 packages/unraid-shared/src/services/nginx.ts diff --git a/api/src/unraid-api/nginx/nginx.module.ts b/api/src/unraid-api/nginx/nginx.module.ts new file mode 100644 index 0000000000..fa17d0fe5f --- /dev/null +++ b/api/src/unraid-api/nginx/nginx.module.ts @@ -0,0 +1,12 @@ +import { Module } from '@nestjs/common'; + +import { NginxService } from '@app/unraid-api/nginx/nginx.service.js'; + +/** + * + */ +@Module({ + providers: [NginxService], + exports: [NginxService], +}) +export class NginxModule {} diff --git a/packages/unraid-api-plugin-connect/src/service/nginx.service.ts b/api/src/unraid-api/nginx/nginx.service.ts similarity index 100% rename from packages/unraid-api-plugin-connect/src/service/nginx.service.ts rename to api/src/unraid-api/nginx/nginx.service.ts diff --git a/api/src/unraid-api/plugin/global-deps.module.ts b/api/src/unraid-api/plugin/global-deps.module.ts index 844b12b245..31bf427277 100644 --- a/api/src/unraid-api/plugin/global-deps.module.ts +++ b/api/src/unraid-api/plugin/global-deps.module.ts @@ -5,6 +5,7 @@ import { GRAPHQL_PUBSUB_TOKEN } from '@unraid/shared/pubsub/graphql.pubsub.js'; import { API_KEY_SERVICE_TOKEN, LIFECYCLE_SERVICE_TOKEN, + NGINX_SERVICE_TOKEN, UPNP_CLIENT_TOKEN, } from '@unraid/shared/tokens.js'; @@ -12,12 +13,14 @@ import { pubsub } from '@app/core/pubsub.js'; import { LifecycleService } from '@app/unraid-api/app/lifecycle.service.js'; import { ApiKeyService } from '@app/unraid-api/auth/api-key.service.js'; import { ApiKeyModule } from '@app/unraid-api/graph/resolvers/api-key/api-key.module.js'; +import { NginxModule } from '@app/unraid-api/nginx/nginx.module.js'; +import { NginxService } from '@app/unraid-api/nginx/nginx.service.js'; import { upnpClient } from '@app/upnp/helpers.js'; // This is the actual module that provides the global dependencies @Global() @Module({ - imports: [ApiKeyModule], + imports: [ApiKeyModule, NginxModule], providers: [ { provide: UPNP_CLIENT_TOKEN, @@ -31,6 +34,10 @@ import { upnpClient } from '@app/upnp/helpers.js'; provide: API_KEY_SERVICE_TOKEN, useClass: ApiKeyService, }, + { + provide: NGINX_SERVICE_TOKEN, + useClass: NginxService, + }, PrefixedID, LifecycleService, { @@ -42,6 +49,7 @@ import { upnpClient } from '@app/upnp/helpers.js'; UPNP_CLIENT_TOKEN, GRAPHQL_PUBSUB_TOKEN, API_KEY_SERVICE_TOKEN, + NGINX_SERVICE_TOKEN, PrefixedID, LIFECYCLE_SERVICE_TOKEN, LifecycleService, diff --git a/packages/unraid-api-plugin-connect/src/module/system.module.ts b/packages/unraid-api-plugin-connect/src/module/system.module.ts index 38a7cb5cd4..823eb08fe4 100644 --- a/packages/unraid-api-plugin-connect/src/module/system.module.ts +++ b/packages/unraid-api-plugin-connect/src/module/system.module.ts @@ -5,7 +5,6 @@ import { NetworkResolver } from '../resolver/network.resolver.js'; import { ConnectConfigService } from '../service/connect-config.service.js'; import { DnsService } from '../service/dns.service.js'; import { NetworkService } from '../service/network.service.js'; -import { NginxService } from '../service/nginx.service.js'; import { UpnpService } from '../service/upnp.service.js'; import { UrlResolverService } from '../service/url-resolver.service.js'; @@ -17,7 +16,6 @@ import { UrlResolverService } from '../service/url-resolver.service.js'; UpnpService, UrlResolverService, DnsService, - NginxService, ConnectConfigService, ], exports: [ @@ -26,7 +24,6 @@ import { UrlResolverService } from '../service/url-resolver.service.js'; UpnpService, UrlResolverService, DnsService, - NginxService, ConnectConfigService, ], }) diff --git a/packages/unraid-api-plugin-connect/src/service/network.service.ts b/packages/unraid-api-plugin-connect/src/service/network.service.ts index bdad892865..4c57045a08 100644 --- a/packages/unraid-api-plugin-connect/src/service/network.service.ts +++ b/packages/unraid-api-plugin-connect/src/service/network.service.ts @@ -1,13 +1,16 @@ -import { Injectable } from '@nestjs/common'; +import { Inject, Injectable } from '@nestjs/common'; + +import { NginxService } from '@unraid/shared/services/nginx.js'; +import { NGINX_SERVICE_TOKEN } from '@unraid/shared/tokens.js'; import { ConnectConfigService } from './connect-config.service.js'; import { DnsService } from './dns.service.js'; -import { NginxService } from './nginx.service.js'; import { UrlResolverService } from './url-resolver.service.js'; @Injectable() export class NetworkService { constructor( + @Inject(NGINX_SERVICE_TOKEN) private readonly nginxService: NginxService, private readonly dnsService: DnsService, private readonly urlResolverService: UrlResolverService, diff --git a/packages/unraid-shared/src/services/nginx.ts b/packages/unraid-shared/src/services/nginx.ts new file mode 100644 index 0000000000..b9c1261cdb --- /dev/null +++ b/packages/unraid-shared/src/services/nginx.ts @@ -0,0 +1,7 @@ +export interface NginxService { + /** + * Reloads nginx via its rc script + * @returns true if the reload was successful, false otherwise + */ + reload(): Promise; +} \ No newline at end of file diff --git a/packages/unraid-shared/src/tokens.ts b/packages/unraid-shared/src/tokens.ts index f2be0b998a..d1998fd186 100644 --- a/packages/unraid-shared/src/tokens.ts +++ b/packages/unraid-shared/src/tokens.ts @@ -1,3 +1,4 @@ export const UPNP_CLIENT_TOKEN = 'UPNP_CLIENT'; export const API_KEY_SERVICE_TOKEN = 'ApiKeyService'; export const LIFECYCLE_SERVICE_TOKEN = 'LifecycleService'; +export const NGINX_SERVICE_TOKEN = 'NginxService'; From 544af5c61ef64b8f0f7ad6f406d113e470259f41 Mon Sep 17 00:00:00 2001 From: Pujit Mehrotra Date: Fri, 27 Jun 2025 16:52:27 -0400 Subject: [PATCH 12/21] init effect system in file modification module with nginx:reload --- .../file-modification-effect.service.ts | 16 +++++++++++++ .../unraid-file-modifier/file-modification.ts | 8 +++++++ .../unraid-file-modifier.module.ts | 5 +++- .../unraid-file-modifier.service.ts | 24 +++++++++++++++++-- 4 files changed, 50 insertions(+), 3 deletions(-) create mode 100644 api/src/unraid-api/unraid-file-modifier/file-modification-effect.service.ts diff --git a/api/src/unraid-api/unraid-file-modifier/file-modification-effect.service.ts b/api/src/unraid-api/unraid-file-modifier/file-modification-effect.service.ts new file mode 100644 index 0000000000..629317e6d2 --- /dev/null +++ b/api/src/unraid-api/unraid-file-modifier/file-modification-effect.service.ts @@ -0,0 +1,16 @@ +import { Injectable } from '@nestjs/common'; + +import { NginxService } from '@app/unraid-api/nginx/nginx.service.js'; +import { ModificationEffect } from '@app/unraid-api/unraid-file-modifier/file-modification.js'; + +@Injectable() +export class FileModificationEffectService { + constructor(private readonly nginxService: NginxService) {} + async runEffect(effect: ModificationEffect): Promise { + switch (effect) { + case 'nginx:reload': + await this.nginxService.reload(); + break; + } + } +} diff --git a/api/src/unraid-api/unraid-file-modifier/file-modification.ts b/api/src/unraid-api/unraid-file-modifier/file-modification.ts index 8804a825ca..f04b70256d 100644 --- a/api/src/unraid-api/unraid-file-modifier/file-modification.ts +++ b/api/src/unraid-api/unraid-file-modifier/file-modification.ts @@ -8,9 +8,17 @@ import { coerce, compare, gte } from 'semver'; import { getUnraidVersion } from '@app/common/dashboard/get-unraid-version.js'; +export type ModificationEffect = 'nginx:reload'; + export interface ShouldApplyWithReason { shouldApply: boolean; reason: string; + /** + * Effectful actions that should be performed after the modification is applied. + * + * This field helps ensure that an effect requested by multiple modifications is only performed once. + */ + effects?: ModificationEffect[]; } // Convert interface to abstract class with default implementations diff --git a/api/src/unraid-api/unraid-file-modifier/unraid-file-modifier.module.ts b/api/src/unraid-api/unraid-file-modifier/unraid-file-modifier.module.ts index 3b6329a289..21f5ae5af4 100644 --- a/api/src/unraid-api/unraid-file-modifier/unraid-file-modifier.module.ts +++ b/api/src/unraid-api/unraid-file-modifier/unraid-file-modifier.module.ts @@ -1,8 +1,11 @@ import { Module } from '@nestjs/common'; +import { NginxModule } from '@app/unraid-api/nginx/nginx.module.js'; +import { FileModificationEffectService } from '@app/unraid-api/unraid-file-modifier/file-modification-effect.service.js'; import { UnraidFileModificationService } from '@app/unraid-api/unraid-file-modifier/unraid-file-modifier.service.js'; @Module({ - providers: [UnraidFileModificationService], + imports: [NginxModule], + providers: [UnraidFileModificationService, FileModificationEffectService], }) export class UnraidFileModifierModule {} diff --git a/api/src/unraid-api/unraid-file-modifier/unraid-file-modifier.service.ts b/api/src/unraid-api/unraid-file-modifier/unraid-file-modifier.service.ts index 9196186584..a5353cd811 100644 --- a/api/src/unraid-api/unraid-file-modifier/unraid-file-modifier.service.ts +++ b/api/src/unraid-api/unraid-file-modifier/unraid-file-modifier.service.ts @@ -1,11 +1,24 @@ -import { Injectable, Logger, OnModuleDestroy, OnModuleInit } from '@nestjs/common'; +import { + Injectable, + Logger, + OnApplicationBootstrap, + OnModuleDestroy, + OnModuleInit, +} from '@nestjs/common'; +import type { ModificationEffect } from '@app/unraid-api/unraid-file-modifier/file-modification.js'; +import { FileModificationEffectService } from '@app/unraid-api/unraid-file-modifier/file-modification-effect.service.js'; import { FileModification } from '@app/unraid-api/unraid-file-modifier/file-modification.js'; @Injectable() -export class UnraidFileModificationService implements OnModuleInit, OnModuleDestroy { +export class UnraidFileModificationService + implements OnModuleInit, OnModuleDestroy, OnApplicationBootstrap +{ private readonly logger = new Logger(UnraidFileModificationService.name); private appliedModifications: FileModification[] = []; + private effects: Set = new Set(); + + constructor(private readonly effectService: FileModificationEffectService) {} /** * Load and apply all modifications on module init @@ -22,6 +35,12 @@ export class UnraidFileModificationService implements OnModuleInit, OnModuleDest } } + async onApplicationBootstrap() { + for (const effect of this.effects) { + await this.effectService.runEffect(effect); + } + } + /** * Rollback all applied modifications on module destroy */ @@ -93,6 +112,7 @@ export class UnraidFileModificationService implements OnModuleInit, OnModuleDest ); await modification.apply(); this.appliedModifications.push(modification); + shouldApplyWithReason.effects?.forEach((effect) => this.effects.add(effect)); this.logger.log(`Modification applied successfully: ${modification.id}`); } else { this.logger.log( From 9152e9c8c4e82f80374b28268b3a2b1581bd9e61 Mon Sep 17 00:00:00 2001 From: Pujit Mehrotra Date: Fri, 27 Jun 2025 17:00:53 -0400 Subject: [PATCH 13/21] add robots.txt modification --- .../modifications/rc-nginx.modification.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/api/src/unraid-api/unraid-file-modifier/modifications/rc-nginx.modification.ts b/api/src/unraid-api/unraid-file-modifier/modifications/rc-nginx.modification.ts index 615b42cbe7..2021188183 100644 --- a/api/src/unraid-api/unraid-file-modifier/modifications/rc-nginx.modification.ts +++ b/api/src/unraid-api/unraid-file-modifier/modifications/rc-nginx.modification.ts @@ -77,6 +77,16 @@ check_remote_access(){ 'for NET in "${!NET_FQDN[@]}"; do' ); + // Add robots.txt Access-Control-Allow-Origin header if not already present + if (!newContent.includes('#robots.txt any origin')) { + newContent = newContent.replace( + 'location = /robots.txt {', + // prettier-ignore + `location = /robots.txt { +\t add_header Access-Control-Allow-Origin *; #robots.txt any origin` + ); + } + return this.createPatchWithDiff(overridePath ?? this.filePath, fileContent, newContent); } @@ -91,6 +101,7 @@ check_remote_access(){ return { shouldApply: shouldApply, reason: shouldApply ? 'Unraid version is less than 7.2.0, applying the patch.' : reason, + effects: ['nginx:reload'], }; } } From 2b2cb7187201a1a00d5eb36f8d708e19e726abd0 Mon Sep 17 00:00:00 2001 From: Pujit Mehrotra Date: Fri, 27 Jun 2025 17:26:56 -0400 Subject: [PATCH 14/21] fix file modification test suite --- .../unraid-file-modifier.spec.ts | 33 +++++++++++-------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/api/src/unraid-api/unraid-file-modifier/unraid-file-modifier.spec.ts b/api/src/unraid-api/unraid-file-modifier/unraid-file-modifier.spec.ts index b8b285616e..f2041fe727 100644 --- a/api/src/unraid-api/unraid-file-modifier/unraid-file-modifier.spec.ts +++ b/api/src/unraid-api/unraid-file-modifier/unraid-file-modifier.spec.ts @@ -9,6 +9,8 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { fileExistsSync } from '@app/core/utils/files/file-exists.js'; import { FileLoadStatus } from '@app/store/types.js'; +import { NginxModule } from '@app/unraid-api/nginx/nginx.module.js'; +import { FileModificationEffectService } from '@app/unraid-api/unraid-file-modifier/file-modification-effect.service.js'; import { FileModification, ShouldApplyWithReason, @@ -69,7 +71,8 @@ describe.sequential('FileModificationService', () => { logger = new Logger('test'); const module: TestingModule = await Test.createTestingModule({ - providers: [UnraidFileModificationService], + imports: [NginxModule], + providers: [UnraidFileModificationService, FileModificationEffectService], }).compile(); service = module.get(UnraidFileModificationService); @@ -128,13 +131,15 @@ describe.sequential('FileModificationService', () => { expect(rolledBackContent).toBe(ORIGINAL_CONTENT); expect(mockLogger.warn).toHaveBeenCalledWith('Could not load pregenerated patch for: test'); - expect(mockLogger.log.mock.calls).toEqual([ - ['RootTestModule dependencies initialized'], - ['Applying modification: test - Always Apply this mod'], - ['Modification applied successfully: test'], - ['Rolling back modification: test'], - ['Successfully rolled back modification: test'], - ]); + expect(mockLogger.log.mock.calls).toEqual( + expect.arrayContaining([ + ['RootTestModule dependencies initialized'], + ['Applying modification: test - Always Apply this mod'], + ['Modification applied successfully: test'], + ['Rolling back modification: test'], + ['Successfully rolled back modification: test'], + ]) + ); }); it('should handle errors during dual application', async () => { @@ -146,11 +151,13 @@ describe.sequential('FileModificationService', () => { await service.applyModification(mod); - expect(mockLogger.log.mock.calls).toEqual([ - ['RootTestModule dependencies initialized'], - ['Applying modification: test - Always Apply this mod'], - ['Modification applied successfully: test'], - ]); + expect(mockLogger.log.mock.calls).toEqual( + expect.arrayContaining([ + ['RootTestModule dependencies initialized'], + ['Applying modification: test - Always Apply this mod'], + ['Modification applied successfully: test'], + ]) + ); // Now apply again and ensure the contents don't change await service.applyModification(mod); From 4cb902ce69708432354607f45fe438c63bf712c4 Mon Sep 17 00:00:00 2001 From: Pujit Mehrotra Date: Fri, 27 Jun 2025 17:48:54 -0400 Subject: [PATCH 15/21] allow no-diff patches to succeed --- .../unraid-file-modifier/file-modification.ts | 3 ++- .../modifications/hosts.modification.ts | 24 +++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) create mode 100644 api/src/unraid-api/unraid-file-modifier/modifications/hosts.modification.ts diff --git a/api/src/unraid-api/unraid-file-modifier/file-modification.ts b/api/src/unraid-api/unraid-file-modifier/file-modification.ts index f04b70256d..0bd0c9396a 100644 --- a/api/src/unraid-api/unraid-file-modifier/file-modification.ts +++ b/api/src/unraid-api/unraid-file-modifier/file-modification.ts @@ -106,7 +106,8 @@ export abstract class FileModification { const currentContent = await readFile(this.filePath, 'utf8').catch(() => ''); const parsedPatch = parsePatch(patchContents)[0]; if (!parsedPatch?.hunks.length) { - throw new Error('Invalid Patch Format: No hunks found'); + this.logger.warn(`Invalid Patch Format: No hunks found for ${this.filePath}`); + return currentContent; } const results = applyPatch(currentContent, parsedPatch); diff --git a/api/src/unraid-api/unraid-file-modifier/modifications/hosts.modification.ts b/api/src/unraid-api/unraid-file-modifier/modifications/hosts.modification.ts new file mode 100644 index 0000000000..bcfd588f14 --- /dev/null +++ b/api/src/unraid-api/unraid-file-modifier/modifications/hosts.modification.ts @@ -0,0 +1,24 @@ +import { constants } from 'fs'; +import { access } from 'fs/promises'; +import { readFile } from 'node:fs/promises'; + +import { + FileModification, + ShouldApplyWithReason, +} from '@app/unraid-api/unraid-file-modifier/file-modification.js'; + +export default class HostsModification extends FileModification { + id: string = 'hosts'; + public readonly filePath: string = '/etc/hosts' as const; + + protected async generatePatch(overridePath?: string): Promise { + const originalContent = await readFile(this.filePath, 'utf8'); + + const newContent = originalContent + .split('\n') + .filter((line) => !line.includes('keys.lime-technology.com')) + .join('\n'); + + return this.createPatchWithDiff(overridePath ?? this.filePath, originalContent, newContent); + } +} From bdba118ece111dd0a99ab3d7b01f4951905250f6 Mon Sep 17 00:00:00 2001 From: Pujit Mehrotra Date: Fri, 27 Jun 2025 18:01:00 -0400 Subject: [PATCH 16/21] move legacy config dir migration to plg file --- plugin/plugins/dynamix.unraid.net.plg | 5 +++++ .../dynamix.unraid.net/install/scripts/verify_install.sh | 5 +---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/plugin/plugins/dynamix.unraid.net.plg b/plugin/plugins/dynamix.unraid.net.plg index ad7a5c6eb1..89634846b4 100755 --- a/plugin/plugins/dynamix.unraid.net.plg +++ b/plugin/plugins/dynamix.unraid.net.plg @@ -263,6 +263,11 @@ mkdir -p "$(dirname "$LOGFILE")" echo "Starting Unraid Connect installation..." +# Move settings on flash drive +CFG_OLD=/boot/config/plugins/Unraid.net +CFG_NEW=/boot/config/plugins/dynamix.my.servers +[ -d "$CFG_OLD" ] && [ ! -d "$CFG_NEW" ] && mv "$CFG_OLD" "$CFG_NEW" + # Setup the API (but don't start it yet) if [ -x "$SCRIPTS_DIR/setup_api.sh" ]; then echo "Setting up Unraid API..." diff --git a/plugin/source/dynamix.unraid.net/usr/local/share/dynamix.unraid.net/install/scripts/verify_install.sh b/plugin/source/dynamix.unraid.net/usr/local/share/dynamix.unraid.net/install/scripts/verify_install.sh index 2b9285cbf0..de578df03d 100755 --- a/plugin/source/dynamix.unraid.net/usr/local/share/dynamix.unraid.net/install/scripts/verify_install.sh +++ b/plugin/source/dynamix.unraid.net/usr/local/share/dynamix.unraid.net/install/scripts/verify_install.sh @@ -10,10 +10,7 @@ echo "Performing comprehensive installation verification..." # Define critical files to check (POSIX-compliant, no arrays) CRITICAL_FILES="/usr/local/bin/unraid-api /etc/rc.d/rc.unraid-api -/usr/local/emhttp/plugins/dynamix.my.servers/scripts/gitflash_log -/usr/local/share/dynamix.unraid.net/install/scripts/cleanup.sh -/usr/local/share/dynamix.unraid.net/install/scripts/file_patches.sh -/usr/local/share/dynamix.unraid.net/install/scripts/setup_api.sh" +/usr/local/emhttp/plugins/dynamix.my.servers/scripts/gitflash_log" # Define critical directories to check (POSIX-compliant, no arrays) CRITICAL_DIRS="/usr/local/unraid-api From ec9dfd8da6fcb927a8d198421727abb57784db17 Mon Sep 17 00:00:00 2001 From: Pujit Mehrotra Date: Fri, 27 Jun 2025 18:01:24 -0400 Subject: [PATCH 17/21] add an nginx conf modification just in case --- .../modifications/nginx-conf.modification.ts | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 api/src/unraid-api/unraid-file-modifier/modifications/nginx-conf.modification.ts diff --git a/api/src/unraid-api/unraid-file-modifier/modifications/nginx-conf.modification.ts b/api/src/unraid-api/unraid-file-modifier/modifications/nginx-conf.modification.ts new file mode 100644 index 0000000000..f48ff65871 --- /dev/null +++ b/api/src/unraid-api/unraid-file-modifier/modifications/nginx-conf.modification.ts @@ -0,0 +1,40 @@ +import { readFile } from 'node:fs/promises'; + +import { + FileModification, + ShouldApplyWithReason, +} from '@app/unraid-api/unraid-file-modifier/file-modification.js'; + +export default class NginxConfModification extends FileModification { + id: string = 'nginx-conf'; + public readonly filePath: string = '/etc/nginx/nginx.conf' as const; + + protected async generatePatch(overridePath?: string): Promise { + const originalContent = await readFile(this.filePath, 'utf8'); + const newContent = originalContent.replace( + "add_header X-Frame-Options 'SAMEORIGIN';", + 'add_header Content-Security-Policy "frame-ancestors \'self\' https://connect.myunraid.net/";' + ); + return this.createPatchWithDiff(overridePath ?? this.filePath, originalContent, newContent); + } + + async shouldApply(): Promise { + const superShouldApply = await super.shouldApply(); + if (!superShouldApply.shouldApply) { + return superShouldApply; + } + const content = await readFile(this.filePath, 'utf8'); + const hasSameOrigin = content.includes("add_header X-Frame-Options 'SAMEORIGIN';"); + if (!hasSameOrigin) { + return { + shouldApply: false, + reason: 'X-Frame-Options SAMEORIGIN header not found in nginx.conf', + }; + } + return { + shouldApply: true, + reason: 'X-Frame-Options SAMEORIGIN found and needs to be replaced with Content-Security-Policy', + effects: ['nginx:reload'], + }; + } +} From 1c7e528aff71ec1fc7f2f5e780e39beb9544e93f Mon Sep 17 00:00:00 2001 From: Pujit Mehrotra Date: Fri, 27 Jun 2025 18:22:43 -0400 Subject: [PATCH 18/21] revert acceptance of 0 hunk patches?? --- api/src/unraid-api/unraid-file-modifier/file-modification.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/api/src/unraid-api/unraid-file-modifier/file-modification.ts b/api/src/unraid-api/unraid-file-modifier/file-modification.ts index 0bd0c9396a..5be2ea217f 100644 --- a/api/src/unraid-api/unraid-file-modifier/file-modification.ts +++ b/api/src/unraid-api/unraid-file-modifier/file-modification.ts @@ -106,8 +106,7 @@ export abstract class FileModification { const currentContent = await readFile(this.filePath, 'utf8').catch(() => ''); const parsedPatch = parsePatch(patchContents)[0]; if (!parsedPatch?.hunks.length) { - this.logger.warn(`Invalid Patch Format: No hunks found for ${this.filePath}`); - return currentContent; + throw new Error(`Invalid Patch Format: No hunks found for ${this.filePath}`); } const results = applyPatch(currentContent, parsedPatch); From a79f583e9517345a27feec933cbbf390b46c499f Mon Sep 17 00:00:00 2001 From: Pujit Mehrotra Date: Mon, 30 Jun 2025 10:35:51 -0400 Subject: [PATCH 19/21] fix security scanner issue --- .../modifications/hosts.modification.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/api/src/unraid-api/unraid-file-modifier/modifications/hosts.modification.ts b/api/src/unraid-api/unraid-file-modifier/modifications/hosts.modification.ts index bcfd588f14..5dea1e03aa 100644 --- a/api/src/unraid-api/unraid-file-modifier/modifications/hosts.modification.ts +++ b/api/src/unraid-api/unraid-file-modifier/modifications/hosts.modification.ts @@ -14,9 +14,13 @@ export default class HostsModification extends FileModification { protected async generatePatch(overridePath?: string): Promise { const originalContent = await readFile(this.filePath, 'utf8'); + // Use a case-insensitive word-boundary regex so the hostname must appear as an independent token + // prevents partial string & look-alike conflicts such as "keys.lime-technology.com.example.com" + const hostPattern = /\bkeys\.lime-technology\.com\b/i; + const newContent = originalContent .split('\n') - .filter((line) => !line.includes('keys.lime-technology.com')) + .filter((line) => !hostPattern.test(line)) .join('\n'); return this.createPatchWithDiff(overridePath ?? this.filePath, originalContent, newContent); From 224f27f5e28f51fe289fbb6c8e0a6d2641627441 Mon Sep 17 00:00:00 2001 From: Pujit Mehrotra Date: Mon, 30 Jun 2025 10:39:26 -0400 Subject: [PATCH 20/21] add logging & error handling to file mod effects --- .../unraid-file-modifier/unraid-file-modifier.service.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/api/src/unraid-api/unraid-file-modifier/unraid-file-modifier.service.ts b/api/src/unraid-api/unraid-file-modifier/unraid-file-modifier.service.ts index a5353cd811..d05c2ae27d 100644 --- a/api/src/unraid-api/unraid-file-modifier/unraid-file-modifier.service.ts +++ b/api/src/unraid-api/unraid-file-modifier/unraid-file-modifier.service.ts @@ -37,7 +37,12 @@ export class UnraidFileModificationService async onApplicationBootstrap() { for (const effect of this.effects) { - await this.effectService.runEffect(effect); + try { + await this.effectService.runEffect(effect); + this.logger.log(`Applied effect: ${effect}`); + } catch (err) { + this.logger.error(err, `Failed to apply effect: ${effect}`); + } } } From 7f0180224bd4c2782b387b02468548d68f688350 Mon Sep 17 00:00:00 2001 From: Pujit Mehrotra Date: Mon, 30 Jun 2025 10:42:29 -0400 Subject: [PATCH 21/21] drop `-r` flag from unraid-api deletion --- plugin/source/dynamix.unraid.net/install/doinst.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin/source/dynamix.unraid.net/install/doinst.sh b/plugin/source/dynamix.unraid.net/install/doinst.sh index 24f62da54d..3f8ee6e413 100644 --- a/plugin/source/dynamix.unraid.net/install/doinst.sh +++ b/plugin/source/dynamix.unraid.net/install/doinst.sh @@ -13,7 +13,7 @@ done chmod +x usr/local/unraid-api/dist/cli.js chmod +x usr/local/unraid-api/dist/main.js -rm -rf usr/local/bin/unraid-api +rm -f usr/local/bin/unraid-api ln -sf ../unraid-api/dist/cli.js usr/local/bin/unraid-api # deprecated ln -sf ../bin/unraid-api usr/local/sbin/unraid-api