mirror of
https://github.com/unraid/api.git
synced 2026-04-28 11:09:21 -05:00
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:
@@ -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';
|
||||
|
||||
@@ -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 });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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,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';
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
@@ -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 +0,0 @@
|
||||
webComps
|
||||
@@ -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:
|
||||
|
||||
-74
@@ -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
|
||||
-59
@@ -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"
|
||||
|
||||
+1
-4
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user