fix(connect): disable api plugin if unraid plugin is absent (#1773)

Mitigates an edge case where the connect api plugin does not uninstall
itself when Unraid version < 7.2.0, resulting in retention of undesired
connect functionality on stock unraid after upgrading to 7.2.0+.

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* App now detects Connect plugin availability at startup and falls back
gracefully with a no-op mode and a logged warning if the plugin is
absent.
* Added an environment option to skip the plugin availability check when
needed.
* Export behavior adjusted so the application uses the appropriate
module based on plugin presence.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
Pujit Mehrotra
2025-11-13 12:26:35 -05:00
committed by GitHub
parent 45cda4af80
commit c264a1843c
2 changed files with 69 additions and 1 deletions

View File

@@ -32,3 +32,4 @@ CHOKIDAR_USEPOLLING=true
LOG_TRANSPORT=console
LOG_LEVEL=trace
ENABLE_NEXT_DOCKER_RELEASE=true
SKIP_CONNECT_PLUGIN_CHECK=true

View File

@@ -1,5 +1,8 @@
import { Inject, Logger, Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { existsSync } from 'node:fs';
import { execa } from 'execa';
import { ConnectConfigPersister } from './config/config.persistence.js';
import { configFeature } from './config/connect.config.js';
@@ -8,6 +11,10 @@ import { ConnectModule } from './unraid-connect/connect.module.js';
export const adapter = 'nestjs';
/**
* When the plugin is installed we expose the full Nest module graph.
* Configuration and proxy submodules only bootstrap in this branch.
*/
@Module({
imports: [ConfigModule.forFeature(configFeature), ConnectModule, MothershipModule],
providers: [ConnectConfigPersister],
@@ -23,4 +30,64 @@ class ConnectPluginModule {
}
}
export const ApiModule = ConnectPluginModule;
/**
* Fallback module keeps the export shape intact but only warns operators.
* This makes `ApiModule` safe to import even when the plugin is absent.
*/
@Module({})
export class DisabledConnectPluginModule {
logger = new Logger(DisabledConnectPluginModule.name);
async onModuleInit() {
const removalCommand = 'unraid-api plugins remove -b unraid-api-plugin-connect';
this.logger.warn(
'Connect plugin is not installed, but is listed as an API plugin. Attempting `%s` automatically.',
removalCommand
);
try {
const { stdout, stderr } = await execa('unraid-api', [
'plugins',
'remove',
'-b',
'unraid-api-plugin-connect',
]);
if (stdout?.trim()) {
this.logger.debug(stdout.trim());
}
if (stderr?.trim()) {
this.logger.debug(stderr.trim());
}
this.logger.log(
'Successfully completed `%s` to prune the stale connect plugin entry.',
removalCommand
);
} catch (error) {
const message =
error instanceof Error
? error.message
: 'Unknown error while removing stale connect plugin entry.';
this.logger.error('Failed to run `%s`: %s', removalCommand, message);
}
}
}
/**
* Local filesystem and env checks stay synchronous so we can branch at module load.
*/
const isConnectPluginInstalled = () => {
if (process.env.SKIP_CONNECT_PLUGIN_CHECK === 'true') {
return true;
}
return existsSync('/boot/config/plugins/dynamix.unraid.net.plg');
};
/**
* Downstream code always imports `ApiModule`. We swap the implementation based on availability,
* avoiding dynamic module plumbing while keeping the DI graph predictable.
* Set `SKIP_CONNECT_PLUGIN_CHECK=true` in development to force the connected path.
*/
export const ApiModule = isConnectPluginInstalled() ? ConnectPluginModule : DisabledConnectPluginModule;