build: fix doinst.sh compatibility with installpkg --root (#1446)

- Isolate plugin concerns into `.plg` plugin & api file modifiers instead of the api's
slackware package.

## Summary by CodeRabbit

* **New Features**
* Installation process modularized into package installation,
post-install setup with verification, and service startup with cleanup.
* Added logging and error detection during installation, including
symlink verification.
  * Created required log directory to support service dependencies.
* Integrated nginx service with reload capability triggered by
configuration changes.
* Added automatic patching of nginx configuration and hosts file to
improve security and iframe compatibility.
* Enhanced file modification system to handle side effects and trigger
nginx reloads as needed.

* **Refactor**
* Restructured installation scripts for clarity; setup scripts now
separate steps.
  * Removed legacy installation logic and deprecated related scripts.
  * Enabled hard link addition during package creation.
* Simplified installation scripts by removing conditional branching and
detailed logging.
* Streamlined API environment setup by removing redundant post-install
steps.
* Updated dependency injection to explicitly provide nginx service
token.
  * Improved patch application error reporting with file path details.

* **Chores**
  * Disabled legacy scripts to streamline installation.
  * Removed `.gitignore` to track previously ignored files.
* Updated tests to include new dependencies and relaxed logger
assertions.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1210661184127051
This commit is contained in:
Pujit Mehrotra
2025-06-30 10:49:46 -04:00
committed by GitHub
parent f73e5e0058
commit a8566e9e5a
25 changed files with 284 additions and 266 deletions
+1
View File
@@ -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';
+9 -2
View File
@@ -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 });
}
}
+1
View File
@@ -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();
+12
View File
@@ -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 {}
@@ -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,
@@ -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<void> {
switch (effect) {
case 'nginx:reload':
await this.nginxService.reload();
break;
}
}
}
@@ -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);
@@ -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<string> {
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);
}
}
@@ -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<string> {
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<ShouldApplyWithReason> {
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'],
};
}
}
@@ -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'],
};
}
}
@@ -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 {}
@@ -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<ModificationEffect> = 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(
@@ -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>(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);
@@ -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,
],
})
@@ -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,
@@ -0,0 +1,7 @@
export interface NginxService {
/**
* Reloads nginx via its rc script
* @returns true if the reload was successful, false otherwise
*/
reload(): Promise<boolean>;
}
+1
View File
@@ -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';
+1 -1
View File
@@ -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);
};
+60 -9
View File
@@ -210,7 +210,7 @@ exit 0
</INLINE>
</FILE>
<!-- install all the things -->
<!-- install api package and all necessary files -->
<FILE Run="/bin/bash" Method="install">
<INLINE>
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
]]>
</INLINE>
</FILE>
<!-- port of the old doinst.sh script -->
<FILE Run="/bin/bash" Method="install">
<INLINE>
<![CDATA[
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")"
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..."
echo "Running setup_api.sh" >> "$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"
]]>
</INLINE>
</FILE>
<!-- start the unraid api service -->
<FILE Run="/bin/bash" Method="install">
<INLINE>
<![CDATA[
# Clean up any old node_modules archives (on the boot drive) that don't match our current version
#
# Must run after package installation because the package 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
echo "Starting Unraid API service"
/etc/rc.d/rc.unraid-api plugins add unraid-api-plugin-connect -b --no-restart
/etc/rc.d/rc.unraid-api start
@@ -260,9 +312,8 @@ echo "Starting Unraid API service"
echo "Unraid API service started"
echo "✅ Installation is complete, it is safe to close this window"
echo
exit 0
]]>
]]>
</INLINE>
</FILE>
@@ -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:
@@ -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
@@ -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"
@@ -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