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/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/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..5be2ea217f 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 @@ -98,7 +106,7 @@ 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'); + throw new Error(`Invalid Patch Format: No hunks found for ${this.filePath}`); } 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..5dea1e03aa --- /dev/null +++ b/api/src/unraid-api/unraid-file-modifier/modifications/hosts.modification.ts @@ -0,0 +1,28 @@ +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'); + + // 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) => !hostPattern.test(line)) + .join('\n'); + + return this.createPatchWithDiff(overridePath ?? this.filePath, originalContent, newContent); + } +} 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'], + }; + } +} 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'], }; } } 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..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 @@ -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,17 @@ export class UnraidFileModificationService implements OnModuleInit, OnModuleDest } } + async onApplicationBootstrap() { + for (const effect of this.effects) { + try { + await this.effectService.runEffect(effect); + this.logger.log(`Applied effect: ${effect}`); + } catch (err) { + this.logger.error(err, `Failed to apply effect: ${effect}`); + } + } + } + /** * Rollback all applied modifications on module destroy */ @@ -93,6 +117,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( 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); 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'; 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); }; diff --git a/plugin/plugins/dynamix.unraid.net.plg b/plugin/plugins/dynamix.unraid.net.plg index 9420991949..89634846b4 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,69 @@ 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 + setup_output=$("$SCRIPTS_DIR/setup_api.sh") + echo "$setup_output" >> "$LOGFILE" +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/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 diff --git a/plugin/source/dynamix.unraid.net/install/doinst.sh b/plugin/source/dynamix.unraid.net/install/doinst.sh index 2ee92da52e..3f8ee6e413 100644 --- a/plugin/source/dynamix.unraid.net/install/doinst.sh +++ b/plugin/source/dynamix.unraid.net/install/doinst.sh @@ -1,98 +1,27 @@ #!/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" +backup_file_if_exists() { + if [ -f "$1" ]; then + mv "$1" "$1.old" + fi +} -# 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" +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 -# 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 +chmod +x usr/local/unraid-api/dist/cli.js +chmod +x usr/local/unraid-api/dist/main.js -# 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" - -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 +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 +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. +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/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 154b90e482..0000000000 --- a/plugin/source/dynamix.unraid.net/usr/local/share/dynamix.unraid.net/install/scripts/file_patches.sh +++ /dev/null @@ -1,74 +0,0 @@ -#!/bin/sh -# Script to handle file patches - -# 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 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" 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