Skip to content

Conversation

@pujitm
Copy link
Member

@pujitm pujitm commented Nov 18, 2025

Summary by CodeRabbit

  • Refactor

    • Plugin configuration now lives in a single API configuration object for consistent handling.
    • Connection plugin wiring simplified so the connect plugin is always provided without runtime fallbacks.
  • Chores

    • Startup now automatically removes stale connect-plugin entries from saved config when the plugin is absent, improving startup reliability.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 18, 2025

Walkthrough

Centralizes writing the nested api.plugins config, removes the conditional Connect plugin module fallback in favor of a static export, adds utilities to detect/prune stale Connect plugin entries, and invokes pruning during API startup and API config loading when needed.

Changes

Cohort / File(s) Summary
Plugin configuration changes
api/src/unraid-api/plugin/plugin-management.service.ts
Adds private updatePluginsConfig(plugins: string[]) and replaces direct configService.set calls so api.plugins is merged into the api object; removes prior @ts-expect-error usages.
Plugin tests updated
api/src/unraid-api/plugin/__test__/plugin-management.service.spec.ts
Mock configService now supports nested api object: get('api') returns { plugins: [...] }, and set('api', obj) accepts an object with plugins and updates the mock store accordingly.
Connect plugin package API simplified
packages/unraid-api-plugin-connect/src/index.ts
Removes DisabledConnectPluginModule, its onModuleInit/execa pruning logic, and the isConnectPluginInstalled decision helper; ApiModule now statically exports ConnectPluginModule. Unused fs/execa imports removed.
Connect plugin cleanup utilities
api/src/connect-plugin-cleanup.ts
Adds isConnectPluginInstalled(): boolean (honors SKIP_CONNECT_PLUGIN_CHECK) and related pruning helper(s) to remove stale unraid-api-plugin-connect entries when appropriate.
Startup & config integration
api/src/index.ts, api/src/unraid-api/config/api-config.module.ts
index.ts imports fs/promises readFile and invokes pruning during startup (try/catch). api-config.module.ts uses isConnectPluginInstalled() to filter/prune unraid-api-plugin-connect from diskConfig.plugins and persists the updated api config when necessary.

Sequence Diagram(s)

sequenceDiagram
    participant Startup as API Startup
    participant ConfigModule as API Config Loader
    participant Cleanup as Connect Cleanup
    participant FS as Filesystem
    participant CLI as CLI (unraid-api)
    participant Config as Config Service

    Startup->>Cleanup: pruneStaleConnectPluginEntryIfNecessary()
    Cleanup->>FS: isConnectPluginInstalled() (check SKIP env or plugin file)
    alt plugin installed
        FS-->>Cleanup: true
        Cleanup-->>Startup: complete (no-op)
    else plugin not installed
        FS-->>Cleanup: false
        Cleanup->>CLI: run "unraid-api plugins remove -b unraid-api-plugin-connect [--no-restart]"
        CLI->>Config: mutate/persist config (remove plugin entry)
        CLI-->>Cleanup: ExecaReturnValue
        Cleanup-->>Startup: complete
    end

    note right of ConfigModule `#DDEBF7`: During config load
    ConfigModule->>FS: isConnectPluginInstalled()
    alt plugin not installed
        ConfigModule->>Config: filter out 'unraid-api-plugin-connect' from diskConfig.plugins
        ConfigModule->>Config: persist updated api config
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~40 minutes

  • Inspect updatePluginsConfig() merging semantics and all call sites in plugin-management service.
  • Verify removal of DisabledConnectPluginModule and static ApiModule export across consumers/packages.
  • Review pruning helpers for correct sync/async behavior, environment handling (SKIP_CONNECT_PLUGIN_CHECK), and CLI command construction.
  • Confirm api-config.module.ts write-back flow is safe (race conditions, ordering) and tests reflect the runtime shape.

Poem

🐇 I nudged the nested plugins into place,
I hopped off the fallback and set a steady face.
I sniffed for stale bits beneath the boot,
Pruned with a twitch and left configs astute.
Startup hums — small paws, tidy root. 🥕

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'fix: auto-uninstallation of connect api plugin' directly describes the main objective: automating the removal of the connect plugin when not installed. The changes span multiple files but collectively implement this auto-uninstallation feature through conditional module removal, cleanup logic, and configuration management.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/connect-install

Comment @coderabbitai help to get the list of available commands and usage tips.

@codecov
Copy link

codecov bot commented Nov 18, 2025

Codecov Report

❌ Patch coverage is 82.60870% with 4 lines in your changes missing coverage. Please review.
✅ Project coverage is 52.13%. Comparing base (3d4f193) to head (76d4f5a).
⚠️ Report is 5 commits behind head on main.

Files with missing lines Patch % Lines
api/src/connect-plugin-cleanup.ts 77.77% 2 Missing ⚠️
api/src/index.ts 0.00% 1 Missing ⚠️
...src/unraid-api/plugin/plugin-management.service.ts 83.33% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1791      +/-   ##
==========================================
- Coverage   52.27%   52.13%   -0.14%     
==========================================
  Files         874      874              
  Lines       50561    50431     -130     
  Branches     5063     5027      -36     
==========================================
- Hits        26430    26293     -137     
- Misses      24056    24063       +7     
  Partials       75       75              

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
api/src/unraid-api/plugin/__test__/plugin-management.service.spec.ts (1)

30-36: Remove @ts-expect-error with explicit typing.

The multiple @ts-expect-error suppressions can be avoided by asserting the type after runtime checks.

Apply this diff to improve type safety:

-            set: vi.fn((key: string, value: unknown) => {
-                if (key === 'api' && typeof value === 'object' && value !== null) {
-                    // @ts-expect-error - value is an object
-                    if (Array.isArray(value.plugins)) {
-                        // @ts-expect-error - value is an object
-                        configStore = [...value.plugins];
-                    }
-                }
+            set: vi.fn((key: string, value: unknown) => {
+                if (key === 'api' && typeof value === 'object' && value !== null) {
+                    const apiValue = value as Record<string, unknown>;
+                    if (Array.isArray(apiValue.plugins)) {
+                        configStore = [...apiValue.plugins];
+                    }
+                }
                 if (key === 'api.plugins' && Array.isArray(value)) {
                     configStore = [...value];
                 }
             }),
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 170d55d and 2e95be6.

📒 Files selected for processing (1)
  • api/src/unraid-api/plugin/__test__/plugin-management.service.spec.ts (1 hunks)
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: pujitm
Repo: unraid/api PR: 1415
File: plugin/plugins/dynamix.unraid.net.plg:234-236
Timestamp: 2025-06-11T14:14:30.348Z
Learning: For the Unraid Connect plugin, the script `/etc/rc.d/rc.unraid-api` is bundled with the plugin package itself, so its presence on the target system is guaranteed during installation.
📚 Learning: 2025-04-23T20:19:42.542Z
Learnt from: pujitm
Repo: unraid/api PR: 1367
File: packages/unraid-api-plugin-connect/src/pubsub/user.service.ts:44-52
Timestamp: 2025-04-23T20:19:42.542Z
Learning: The project uses a custom or extended implementation of NestJS ConfigService that includes a `set()` method for runtime configuration mutation, unlike the standard nestjs/config package which only provides getter methods.

Applied to files:

  • api/src/unraid-api/plugin/__test__/plugin-management.service.spec.ts
🔇 Additional comments (1)
api/src/unraid-api/plugin/__test__/plugin-management.service.spec.ts (1)

24-26: LGTM: Test supports nested api object reads.

The mock correctly returns the api object structure with the plugins array, aligning with the production change to nested configuration storage.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2e95be6 and f294678.

📒 Files selected for processing (1)
  • packages/unraid-api-plugin-connect/src/index.ts (1 hunks)
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: pujitm
Repo: unraid/api PR: 1415
File: plugin/plugins/dynamix.unraid.net.plg:234-236
Timestamp: 2025-06-11T14:14:30.348Z
Learning: For the Unraid Connect plugin, the script `/etc/rc.d/rc.unraid-api` is bundled with the plugin package itself, so its presence on the target system is guaranteed during installation.
📚 Learning: 2025-06-11T14:14:30.348Z
Learnt from: pujitm
Repo: unraid/api PR: 1415
File: plugin/plugins/dynamix.unraid.net.plg:234-236
Timestamp: 2025-06-11T14:14:30.348Z
Learning: For the Unraid Connect plugin, the script `/etc/rc.d/rc.unraid-api` is bundled with the plugin package itself, so its presence on the target system is guaranteed during installation.

Applied to files:

  • packages/unraid-api-plugin-connect/src/index.ts
🧬 Code graph analysis (1)
packages/unraid-api-plugin-connect/src/index.ts (2)
api/src/unraid-api/plugin/plugin-management.service.ts (1)
  • plugins (17-19)
api/src/unraid-api/config/api-config.module.ts (1)
  • apiConfig (45-45)
🔇 Additional comments (1)
packages/unraid-api-plugin-connect/src/index.ts (1)

41-41: LGTM: Proper dependency injection.

The constructor correctly injects ConfigService following NestJS patterns.

Comment on lines 69 to 76
} catch (error) {
const message =
error instanceof Error ? error.message : 'Unknown error while restarting API.';
this.logger.error('Failed to restart API: %s', message);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Major: Failed restart leaves system in inconsistent state.

If the restart fails, the configuration has been updated to remove the plugin, but the current running instance still has the plugin loaded in memory. This creates an inconsistency between the persisted configuration and the running system state.

Consider adding recovery logic:

  • Restore the original config if restart fails
  • Set a flag or state indicating manual intervention is needed
  • Provide clear guidance to operators on how to complete the removal manually

@pujitm pujitm force-pushed the fix/connect-install branch from f294678 to 4d3c810 Compare November 18, 2025 20:00
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (2)
packages/unraid-api-plugin-connect/src/index.ts (2)

74-78: Self-restart during module initialization remains problematic.

As flagged in the previous review, triggering an API restart from within onModuleInit() creates race conditions and lifecycle issues:

  1. The module may be mid-initialization when restart occurs
  2. The restart command lacks proper oversight of the current process lifecycle
  3. No mechanism to verify restart completed successfully

The concern raised previously about using a signal file, controlled shutdown mechanism, or supervisor-based restart remains valid.


79-85: Failed restart leaves system in inconsistent state.

As noted in the previous review, if the restart fails after the configuration has been updated (by the CLI command), the running instance will still have the plugin loaded in memory while the persisted configuration has it removed. This creates an inconsistency between the runtime state and the persisted configuration.

The previous suggestion to add recovery logic remains valid:

  • Restore the original config if restart fails, or
  • Set a flag indicating manual intervention is needed, or
  • Provide clear guidance to operators on how to complete the removal manually
🧹 Nitpick comments (2)
packages/unraid-api-plugin-connect/src/index.ts (2)

40-41: Remove unnecessary empty constructor.

TypeScript and NestJS do not require an explicit empty constructor. You can safely remove these lines.

-
-    constructor() {}

52-56: Consider removing redundant execa options.

The extendEnv: true option is the default behavior in execa 9.6.0, making it redundant. Additionally, shell: 'bash' is likely unnecessary for these simple command executions—execa can execute them directly without spawning a shell, which is more secure and efficient.

 const { stdout, stderr } = await execa(
     'unraid-api',
     ['plugins', 'remove', '-b', 'unraid-api-plugin-connect', '--no-restart'],
-    { shell: 'bash', extendEnv: true }
 );
 await execa('unraid-api', ['restart'], {
-    shell: 'bash',
-    extendEnv: true,
 });

Also applies to: 75-77

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f294678 and 4feb452.

📒 Files selected for processing (1)
  • packages/unraid-api-plugin-connect/src/index.ts (2 hunks)
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: pujitm
Repo: unraid/api PR: 1415
File: plugin/plugins/dynamix.unraid.net.plg:234-236
Timestamp: 2025-06-11T14:14:30.348Z
Learning: For the Unraid Connect plugin, the script `/etc/rc.d/rc.unraid-api` is bundled with the plugin package itself, so its presence on the target system is guaranteed during installation.
📚 Learning: 2025-06-11T14:14:30.348Z
Learnt from: pujitm
Repo: unraid/api PR: 1415
File: plugin/plugins/dynamix.unraid.net.plg:234-236
Timestamp: 2025-06-11T14:14:30.348Z
Learning: For the Unraid Connect plugin, the script `/etc/rc.d/rc.unraid-api` is bundled with the plugin package itself, so its presence on the target system is guaranteed during installation.

Applied to files:

  • packages/unraid-api-plugin-connect/src/index.ts
🔇 Additional comments (1)
packages/unraid-api-plugin-connect/src/index.ts (1)

43-69: Config persistence is properly implemented via apiConfigPersistence.persist() — no changes needed.

The CLI command at line 135 of api/src/unraid-api/cli/plugins/plugin.command.ts explicitly calls await this.apiConfigPersistence.persist() after removing the plugin. This method writes the updated configuration to disk via ConfigFileHandler.writeConfigFile() before the CLI process returns. Since your code awaits the execa() call, the configuration is persisted to disk before execution continues. The --no-restart flag prevents any restart loop. The implementation is correct.

Comment on lines 71 to 72
// Wait a bit for any potential file system flushes or other ops to settle
await new Promise((resolve) => setTimeout(resolve, 1000));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Replace arbitrary delay with proper synchronization.

The 1-second delay is an unreliable synchronization mechanism. If you're waiting for configuration persistence or filesystem operations to complete, you should instead:

  1. Ensure the CLI command's promise resolution indicates persistence is complete, or
  2. Explicitly verify the configuration file has been updated before proceeding, or
  3. Use proper filesystem sync operations (e.g., fsync) if needed

Arbitrary time-based delays can fail under load, on slower systems, or due to timing variations, leading to race conditions.

Example of explicit verification:

// After CLI removal completes, verify the config file reflects the change
const configPath = '/path/to/api/config.json'; // adjust path as needed
const configContent = await fs.promises.readFile(configPath, 'utf-8');
const config = JSON.parse(configContent);
if (config.api?.plugins?.includes('unraid-api-plugin-connect')) {
    throw new Error('Plugin removal was not persisted to config file');
}
this.logger.log('Verified plugin removal persisted to disk');
🤖 Prompt for AI Agents
In packages/unraid-api-plugin-connect/src/index.ts around lines 71-72, replace
the arbitrary 1s setTimeout with deterministic synchronization: after the CLI
removal promise resolves, either rely on the CLI to guarantee persistence
(ensure you await its returned promise) or explicitly verify the config file has
been updated by reading and parsing the config file and checking the plugin
entry is gone, retrying with short backoff until success or a sensible timeout;
alternatively, open the file descriptor and call fsync to ensure data is flushed
before proceeding. Ensure the code throws a clear error on timeout/failure and
logs successful verification instead of using a fixed delay.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (3)
packages/unraid-api-plugin-connect/src/index.ts (3)

40-41: Remove unnecessary empty constructor.

The empty constructor adds no value and should be removed. TypeScript automatically provides a default constructor when none is defined.

Apply this diff:

-
-    constructor() {}
-
     async onModuleInit() {

52-56: Remove unnecessary shell: 'bash' option.

The shell: 'bash' option is likely unnecessary for this straightforward command execution. Execa can execute the command directly without spawning a shell, which is more secure and efficient. The extendEnv: true option is also the default and can be omitted.

Apply this diff:

             const { stdout, stderr } = await execa(
                 'unraid-api',
-                ['plugins', 'remove', '-b', 'unraid-api-plugin-connect', '--no-restart'],
-                { shell: 'bash', extendEnv: true }
+                ['plugins', 'remove', '-b', 'unraid-api-plugin-connect', '--no-restart']
             );

43-86: Clarify the intended behavior and document required operator action.

Since the restart logic is commented out, operators need clear guidance on what happens after this automatic removal:

  1. Update the warning message (line 46-49) to inform operators that a manual restart is required to fully unload the plugin
  2. Consider adding a success message that explicitly tells operators to run unraid-api restart manually
  3. Document the decision: Add a comment explaining why automatic restart was removed and manual intervention is preferred

Example enhanced logging:

this.logger.warn(
    'Connect plugin is not installed, but is listed as an API plugin. ' +
    'Will attempt to remove it from configuration. ' +
    'A manual API restart will be required to fully unload the plugin.'
);

// ... after successful removal ...

this.logger.log(
    'Successfully removed stale connect plugin entry from configuration. ' +
    'Please run `unraid-api restart` to fully unload the plugin from memory.'
);
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4feb452 and ca2493c.

📒 Files selected for processing (1)
  • packages/unraid-api-plugin-connect/src/index.ts (2 hunks)
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: pujitm
Repo: unraid/api PR: 1415
File: plugin/plugins/dynamix.unraid.net.plg:234-236
Timestamp: 2025-06-11T14:14:30.348Z
Learning: For the Unraid Connect plugin, the script `/etc/rc.d/rc.unraid-api` is bundled with the plugin package itself, so its presence on the target system is guaranteed during installation.
📚 Learning: 2025-06-11T14:14:30.348Z
Learnt from: pujitm
Repo: unraid/api PR: 1415
File: plugin/plugins/dynamix.unraid.net.plg:234-236
Timestamp: 2025-06-11T14:14:30.348Z
Learning: For the Unraid Connect plugin, the script `/etc/rc.d/rc.unraid-api` is bundled with the plugin package itself, so its presence on the target system is guaranteed during installation.

Applied to files:

  • packages/unraid-api-plugin-connect/src/index.ts

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

♻️ Duplicate comments (1)
packages/unraid-api-plugin-connect/src/index.ts (1)

55-58: Duplicate: Arbitrary delay should be removed.

This 1-second delay is unreliable synchronization. The comment claims it allows "config subscription to flush to disk," but configService.set() does not trigger asynchronous persistence. Since the in-memory config update should be removed (as noted in the previous comment), this delay is unnecessary.

🧹 Nitpick comments (1)
packages/unraid-api-plugin-connect/src/index.ts (1)

43-84: Consider the implications of plugin removal without restart.

The module attempts to remove itself from the configuration during initialization, but without restarting, the plugin remains loaded in memory for the current session. This creates a temporary inconsistency where:

  1. The configuration no longer lists the plugin (after CLI succeeds)
  2. The plugin module is still active in the current process
  3. Future startups will correctly exclude the plugin

If this behavior is intentional (automatic cleanup without service disruption), consider adding a log message to clarify that the plugin will be fully removed on next restart. If immediate removal is required, you'll need to implement a graceful restart mechanism.

Example clarifying log message:

this.logger.log(
    'Successfully completed `%s` to prune the stale connect plugin entry. Plugin will be fully unloaded on next API restart.',
    removalCommand
);
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ca2493c and e54d889.

📒 Files selected for processing (1)
  • packages/unraid-api-plugin-connect/src/index.ts (2 hunks)
🧰 Additional context used
🧠 Learnings (3)
📓 Common learnings
Learnt from: pujitm
Repo: unraid/api PR: 1415
File: plugin/plugins/dynamix.unraid.net.plg:234-236
Timestamp: 2025-06-11T14:14:30.348Z
Learning: For the Unraid Connect plugin, the script `/etc/rc.d/rc.unraid-api` is bundled with the plugin package itself, so its presence on the target system is guaranteed during installation.
📚 Learning: 2025-02-05T21:10:48.136Z
Learnt from: elibosley
Repo: unraid/api PR: 1120
File: plugin/plugins/dynamix.unraid.net.plg:35-38
Timestamp: 2025-02-05T21:10:48.136Z
Learning: When providing error handling guidance for Unraid plugins, direct users to use the web GUI (Plugins > Installed Plugins) rather than suggesting command-line actions.

Applied to files:

  • packages/unraid-api-plugin-connect/src/index.ts
📚 Learning: 2025-06-11T14:14:30.348Z
Learnt from: pujitm
Repo: unraid/api PR: 1415
File: plugin/plugins/dynamix.unraid.net.plg:234-236
Timestamp: 2025-06-11T14:14:30.348Z
Learning: For the Unraid Connect plugin, the script `/etc/rc.d/rc.unraid-api` is bundled with the plugin package itself, so its presence on the target system is guaranteed during installation.

Applied to files:

  • packages/unraid-api-plugin-connect/src/index.ts
🧬 Code graph analysis (1)
packages/unraid-api-plugin-connect/src/index.ts (2)
api/src/unraid-api/config/api-config.module.ts (1)
  • apiConfig (45-45)
api/src/unraid-api/plugin/plugin-management.service.ts (1)
  • plugins (17-19)
🔇 Additional comments (1)
packages/unraid-api-plugin-connect/src/index.ts (1)

41-41: LGTM: ConfigService injection.

The constructor properly injects ConfigService, which is necessary for reading and updating the API plugin configuration.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (3)
packages/unraid-api-plugin-connect/src/index.ts (3)

63-67: Remove unnecessary shell: 'bash' option.

The shell: 'bash' option is unnecessary for this straightforward command execution. The command doesn't use shell-specific features (like &&, ||, or pipes), so direct execution is preferred for better performance and security.

Apply this diff:

             const { stdout, stderr } = await execa(
                 'unraid-api',
                 ['plugins', 'remove', '-b', 'unraid-api-plugin-connect', '--no-restart'],
-                { shell: 'bash', extendEnv: true }
+                { extendEnv: true }
             );

52-60: Critical: In-memory config update is not persisted and may be unnecessary.

The code updates in-memory configuration via configService.set(), but NestJS ConfigService does not persist changes to disk. The subsequent CLI command with --no-restart should handle both the plugin removal and configuration persistence. This in-memory update creates potential inconsistencies:

  1. If the CLI succeeds, it will persist the correct configuration, making this update redundant
  2. If the CLI fails, the in-memory state shows the plugin removed while disk config still contains it
  3. The 1-second delay is arbitrary and unreliable for synchronization

Consider removing this in-memory update entirely and relying on the CLI command. If you need to update in-memory state to reflect the CLI's changes, do so after the CLI succeeds.

Based on learnings from past reviews about config persistence issues.


88-92: Fix inconsistent error messages.

The error messages reference "restarting API" but the command uses --no-restart, so it only removes the plugin without restarting. The error handling should accurately describe what failed.

Apply this diff:

         } catch (error) {
             const message =
-                error instanceof Error ? error.message : 'Unknown error while restarting API.';
-            this.logger.error('Failed to restart API: %s', message);
+                error instanceof Error ? error.message : 'Unknown error during plugin removal.';
+            this.logger.error('Failed to run `%s`: %s', removalCommand, message);
         }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e54d889 and 2e98e82.

📒 Files selected for processing (1)
  • packages/unraid-api-plugin-connect/src/index.ts (3 hunks)
🧰 Additional context used
🧠 Learnings (4)
📓 Common learnings
Learnt from: pujitm
Repo: unraid/api PR: 1415
File: plugin/plugins/dynamix.unraid.net.plg:234-236
Timestamp: 2025-06-11T14:14:30.348Z
Learning: For the Unraid Connect plugin, the script `/etc/rc.d/rc.unraid-api` is bundled with the plugin package itself, so its presence on the target system is guaranteed during installation.
📚 Learning: 2025-02-05T21:10:48.136Z
Learnt from: elibosley
Repo: unraid/api PR: 1120
File: plugin/plugins/dynamix.unraid.net.plg:35-38
Timestamp: 2025-02-05T21:10:48.136Z
Learning: When providing error handling guidance for Unraid plugins, direct users to use the web GUI (Plugins > Installed Plugins) rather than suggesting command-line actions.

Applied to files:

  • packages/unraid-api-plugin-connect/src/index.ts
📚 Learning: 2025-04-23T20:19:42.542Z
Learnt from: pujitm
Repo: unraid/api PR: 1367
File: packages/unraid-api-plugin-connect/src/pubsub/user.service.ts:44-52
Timestamp: 2025-04-23T20:19:42.542Z
Learning: The project uses a custom or extended implementation of NestJS ConfigService that includes a `set()` method for runtime configuration mutation, unlike the standard nestjs/config package which only provides getter methods.

Applied to files:

  • packages/unraid-api-plugin-connect/src/index.ts
📚 Learning: 2025-02-18T21:39:10.313Z
Learnt from: pujitm
Repo: unraid/api PR: 1145
File: api/src/unraid-api/unraid-file-modifier/modifications/log-rotate.modification.ts:57-60
Timestamp: 2025-02-18T21:39:10.313Z
Learning: When using Node.js `rm` with `{ force: true }` option or Unix/Linux `rm -f`, existence checks are redundant as the command continues silently if the file doesn't exist.

Applied to files:

  • packages/unraid-api-plugin-connect/src/index.ts
🧬 Code graph analysis (1)
packages/unraid-api-plugin-connect/src/index.ts (2)
api/src/unraid-api/config/api-config.module.ts (1)
  • apiConfig (45-45)
api/src/unraid-api/plugin/plugin-management.service.ts (1)
  • plugins (17-19)
🔇 Additional comments (3)
packages/unraid-api-plugin-connect/src/index.ts (3)

11-11: LGTM: Import needed for scheduled method.

The import of Timeout from @nestjs/schedule is required for the @Timeout(1_000) decorator used on line 44.


42-42: LGTM: ConfigService injection.

The constructor injection of ConfigService is necessary for the plugin removal logic and follows NestJS best practices.


44-50: LGTM: Method initialization and warning.

The @Timeout(1_000) decorator appropriately delays the removal attempt until after module initialization completes. The warning message clearly communicates the automatic removal attempt.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (3)
packages/unraid-api-plugin-connect/src/index.ts (3)

3-61: Avoid in‑memory api mutation + fixed delay; rely on CLI for persistence

This block updates api.plugins in memory, waits 1s “to flush to disk,” then runs the CLI which is what actually persists config. With the custom ConfigService.set() here being in‑memory only, this has a few issues:

  • If the CLI fails, runtime state now shows the plugin removed while disk config still contains it.
  • If the CLI succeeds, the pre‑CLI set() and 1s sleep are redundant.
  • The fixed 1s delay is a brittle, non-deterministic way to synchronize with any background persistence.

Given the CLI is the source of truth for plugin removal and config persistence, consider removing this in‑memory mutation and delay (or, if you really need to mirror the CLI result in memory, do it only after a successful CLI run).

A minimal fix is to drop this whole block:

-        const apiConfig = this.configService.get('api') || {};
-        const plugins = apiConfig.plugins || [];
-        const updatedPlugins = plugins.filter((p: string) => p !== 'unraid-api-plugin-connect');
-        this.configService.set('api', { ...apiConfig, plugins: updatedPlugins });
-
-        // Allow config subscription to flush to disk (25ms buffer + margin)
-        await new Promise((resolve) => setTimeout(resolve, 1000));
-        const debugPlugins = this.configService.get<string[]>('api.plugins') || [];
-        this.logger.debug('Plugins before running removal command: %o', debugPlugins);

and rely on the CLI (and whatever persists api config) instead. If you still want pre‑removal diagnostics, log api.plugins without mutating it first.

Based on learnings that this ConfigService.set() is in‑memory only.


64-68: Consider dropping shell: 'bash' for the execa call

The execa invocation passes a simple command plus argument list and doesn’t appear to use any shell features. Running via shell: 'bash' adds overhead and broadens the attack surface unnecessarily.

Unless unraid-api specifically requires a shell, you can simplify to:

-            const { stdout, stderr } = await execa(
-                'unraid-api',
-                ['plugins', 'remove', '-b', 'unraid-api-plugin-connect', '--no-restart'],
-                { shell: 'bash', extendEnv: true }
-            );
+            const { stdout, stderr } = await execa(
+                'unraid-api',
+                ['plugins', 'remove', '-b', 'unraid-api-plugin-connect', '--no-restart'],
+                { extendEnv: true }
+            );

Please confirm in your environment/docs that unraid-api doesn’t depend on being launched via a shell.


98-99: Align error message with actual operation (plugin removal, not restart)

The catch block still talks about “restarting API,” but this code only runs the plugin removal CLI with --no-restart. The log message is misleading.

Consider updating it to reflect the actual failure:

-            const message =
-                error instanceof Error ? error.message : 'Unknown error while restarting API.';
-            this.logger.error('Failed to restart API: %s', message);
+            const message =
+                error instanceof Error ? error.message : 'Unknown error during plugin removal.';
+            this.logger.error('Failed to run `%s`: %s', removalCommand, message);
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2e98e82 and 84504dd.

📒 Files selected for processing (1)
  • packages/unraid-api-plugin-connect/src/index.ts (3 hunks)
🧰 Additional context used
🧠 Learnings (6)
📓 Common learnings
Learnt from: pujitm
Repo: unraid/api PR: 1415
File: plugin/plugins/dynamix.unraid.net.plg:234-236
Timestamp: 2025-06-11T14:14:30.348Z
Learning: For the Unraid Connect plugin, the script `/etc/rc.d/rc.unraid-api` is bundled with the plugin package itself, so its presence on the target system is guaranteed during installation.
📚 Learning: 2025-02-05T21:10:48.136Z
Learnt from: elibosley
Repo: unraid/api PR: 1120
File: plugin/plugins/dynamix.unraid.net.plg:35-38
Timestamp: 2025-02-05T21:10:48.136Z
Learning: When providing error handling guidance for Unraid plugins, direct users to use the web GUI (Plugins > Installed Plugins) rather than suggesting command-line actions.

Applied to files:

  • packages/unraid-api-plugin-connect/src/index.ts
📚 Learning: 2025-04-23T20:19:42.542Z
Learnt from: pujitm
Repo: unraid/api PR: 1367
File: packages/unraid-api-plugin-connect/src/pubsub/user.service.ts:44-52
Timestamp: 2025-04-23T20:19:42.542Z
Learning: The project uses a custom or extended implementation of NestJS ConfigService that includes a `set()` method for runtime configuration mutation, unlike the standard nestjs/config package which only provides getter methods.

Applied to files:

  • packages/unraid-api-plugin-connect/src/index.ts
📚 Learning: 2025-02-18T21:39:10.313Z
Learnt from: pujitm
Repo: unraid/api PR: 1145
File: api/src/unraid-api/unraid-file-modifier/modifications/log-rotate.modification.ts:57-60
Timestamp: 2025-02-18T21:39:10.313Z
Learning: When using Node.js `rm` with `{ force: true }` option or Unix/Linux `rm -f`, existence checks are redundant as the command continues silently if the file doesn't exist.

Applied to files:

  • packages/unraid-api-plugin-connect/src/index.ts
📚 Learning: 2025-04-23T20:19:42.542Z
Learnt from: pujitm
Repo: unraid/api PR: 1367
File: packages/unraid-api-plugin-connect/src/pubsub/user.service.ts:44-52
Timestamp: 2025-04-23T20:19:42.542Z
Learning: The project uses a custom ConfigService implementation that includes a `set()` method for runtime configuration mutation, unlike the standard nestjs/config package which only provides getter methods.

Applied to files:

  • packages/unraid-api-plugin-connect/src/index.ts
📚 Learning: 2025-03-13T16:17:55.820Z
Learnt from: elibosley
Repo: unraid/api PR: 1211
File: api/src/unraid-api/graph/connect/connect-settings.service.ts:48-53
Timestamp: 2025-03-13T16:17:55.820Z
Learning: In NestJS services, dynamic imports should be wrapped in try/catch blocks with proper error logging and fallback handling to prevent unhandled rejections from crashing the application.

Applied to files:

  • packages/unraid-api-plugin-connect/src/index.ts
🧬 Code graph analysis (1)
packages/unraid-api-plugin-connect/src/index.ts (2)
api/src/unraid-api/config/api-config.module.ts (1)
  • apiConfig (45-45)
api/src/unraid-api/plugin/plugin-management.service.ts (1)
  • plugins (17-19)

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (2)
packages/unraid-api-plugin-connect/src/unraid-connect/connect-plugin.service.ts (2)

35-39: Consider replacing the arbitrary startup delay with proper synchronization.

The 1-second delay uses an arbitrary timeout to work around parallel startup tasks. While the comment explains the reasoning, this approach could fail if startup takes longer or creates unnecessary delays when startup completes faster.

Consider using NestJS module initialization ordering (e.g., importing dependent modules first) or implementing a condition-based wait that checks for specific initialization markers rather than relying on a fixed delay.


80-94: Remove unnecessary shell: 'bash' option.

The shell: 'bash' option on line 92 is unnecessary for this straightforward command execution. execa can execute unraid-api directly without spawning a shell, which is more efficient and cross-platform.

Apply this diff:

         const { stdout, stderr } = await execa('unraid-api', removalCommand.split(' ').slice(1), {
-            shell: 'bash',
             extendEnv: true,
         });
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 84504dd and a2d7f62.

📒 Files selected for processing (5)
  • packages/unraid-api-plugin-connect/src/__test__/connect-plugin.service.test.ts (1 hunks)
  • packages/unraid-api-plugin-connect/src/index.ts (2 hunks)
  • packages/unraid-api-plugin-connect/src/unraid-connect/connect-plugin.service.ts (1 hunks)
  • packages/unraid-api-plugin-connect/src/unraid-connect/connect-plugin.utils.ts (1 hunks)
  • packages/unraid-api-plugin-connect/src/unraid-connect/connect.module.ts (2 hunks)
🧰 Additional context used
🧠 Learnings (12)
📓 Common learnings
Learnt from: pujitm
Repo: unraid/api PR: 1415
File: plugin/plugins/dynamix.unraid.net.plg:234-236
Timestamp: 2025-06-11T14:14:30.348Z
Learning: For the Unraid Connect plugin, the script `/etc/rc.d/rc.unraid-api` is bundled with the plugin package itself, so its presence on the target system is guaranteed during installation.
📚 Learning: 2025-06-11T14:14:30.348Z
Learnt from: pujitm
Repo: unraid/api PR: 1415
File: plugin/plugins/dynamix.unraid.net.plg:234-236
Timestamp: 2025-06-11T14:14:30.348Z
Learning: For the Unraid Connect plugin, the script `/etc/rc.d/rc.unraid-api` is bundled with the plugin package itself, so its presence on the target system is guaranteed during installation.

Applied to files:

  • packages/unraid-api-plugin-connect/src/unraid-connect/connect-plugin.utils.ts
  • packages/unraid-api-plugin-connect/src/unraid-connect/connect-plugin.service.ts
  • packages/unraid-api-plugin-connect/src/index.ts
📚 Learning: 2025-05-08T19:28:54.365Z
Learnt from: elibosley
Repo: unraid/api PR: 1381
File: plugin/source/dynamix.unraid.net/usr/local/share/dynamix.unraid.net/install/scripts/verify_install.sh:19-24
Timestamp: 2025-05-08T19:28:54.365Z
Learning: The directory `/usr/local/emhttp/plugins/dynamix.my.servers` is a valid directory that exists as part of the Unraid API plugin installation and should be included in verification checks.

Applied to files:

  • packages/unraid-api-plugin-connect/src/unraid-connect/connect-plugin.utils.ts
📚 Learning: 2025-08-09T01:03:29.676Z
Learnt from: elibosley
Repo: unraid/api PR: 1575
File: packages/unraid-shared/src/services/socket-config.service.spec.ts:10-13
Timestamp: 2025-08-09T01:03:29.676Z
Learning: Vitest is used for all testing across all repositories in the unraid organization, not Jest. Always use `vi` for mocking utilities, not `jest`.

Applied to files:

  • packages/unraid-api-plugin-connect/src/__test__/connect-plugin.service.test.ts
📚 Learning: 2025-02-05T21:10:48.136Z
Learnt from: elibosley
Repo: unraid/api PR: 1120
File: plugin/plugins/dynamix.unraid.net.plg:35-38
Timestamp: 2025-02-05T21:10:48.136Z
Learning: When providing error handling guidance for Unraid plugins, direct users to use the web GUI (Plugins > Installed Plugins) rather than suggesting command-line actions.

Applied to files:

  • packages/unraid-api-plugin-connect/src/index.ts
📚 Learning: 2025-02-18T21:39:10.313Z
Learnt from: pujitm
Repo: unraid/api PR: 1145
File: api/src/unraid-api/unraid-file-modifier/modifications/log-rotate.modification.ts:57-60
Timestamp: 2025-02-18T21:39:10.313Z
Learning: When using Node.js `rm` with `{ force: true }` option or Unix/Linux `rm -f`, existence checks are redundant as the command continues silently if the file doesn't exist.

Applied to files:

  • packages/unraid-api-plugin-connect/src/index.ts
📚 Learning: 2025-09-04T15:26:34.416Z
Learnt from: elibosley
Repo: unraid/api PR: 1657
File: web/scripts/deploy-dev.sh:37-41
Timestamp: 2025-09-04T15:26:34.416Z
Learning: In web/scripts/deploy-dev.sh, the command `rm -rf /usr/local/emhttp/plugins/dynamix.my.servers/unraid-components/*` intentionally removes all contents of the unraid-components directory before deploying standalone components. This broader cleanup is desired behavior according to the maintainer elibosley.

Applied to files:

  • packages/unraid-api-plugin-connect/src/index.ts
📚 Learning: 2025-03-27T13:34:53.438Z
Learnt from: pujitm
Repo: unraid/api PR: 1252
File: api/src/environment.ts:56-56
Timestamp: 2025-03-27T13:34:53.438Z
Learning: For critical components in the Unraid API, such as retrieving version information from package.json, failing fast (allowing crashes) is preferred over graceful degradation with fallback values.

Applied to files:

  • packages/unraid-api-plugin-connect/src/index.ts
📚 Learning: 2025-01-30T19:38:02.478Z
Learnt from: pujitm
Repo: unraid/api PR: 1075
File: unraid-ui/src/register.ts:15-34
Timestamp: 2025-01-30T19:38:02.478Z
Learning: In the web components registration process for unraid-ui, use a soft-fail approach (logging warnings/errors) instead of throwing errors, to ensure other components can still register successfully even if one component fails.

Applied to files:

  • packages/unraid-api-plugin-connect/src/index.ts
📚 Learning: 2024-11-05T14:49:07.308Z
Learnt from: elibosley
Repo: unraid/api PR: 942
File: api/src/unraid-api/auth/api-key.service.ts:62-70
Timestamp: 2024-11-05T14:49:07.308Z
Learning: In `api/src/unraid-api/auth/api-key.service.ts`, when handling read errors in the `findById` method, throw a `GraphQLError` instead of an `InternalServerErrorException`.

Applied to files:

  • packages/unraid-api-plugin-connect/src/index.ts
📚 Learning: 2025-03-13T16:17:55.820Z
Learnt from: elibosley
Repo: unraid/api PR: 1211
File: api/src/unraid-api/graph/connect/connect-settings.service.ts:48-53
Timestamp: 2025-03-13T16:17:55.820Z
Learning: In NestJS services, dynamic imports should be wrapped in try/catch blocks with proper error logging and fallback handling to prevent unhandled rejections from crashing the application.

Applied to files:

  • packages/unraid-api-plugin-connect/src/index.ts
📚 Learning: 2025-04-23T20:19:42.542Z
Learnt from: pujitm
Repo: unraid/api PR: 1367
File: packages/unraid-api-plugin-connect/src/pubsub/user.service.ts:44-52
Timestamp: 2025-04-23T20:19:42.542Z
Learning: The project uses a custom or extended implementation of NestJS ConfigService that includes a `set()` method for runtime configuration mutation, unlike the standard nestjs/config package which only provides getter methods.

Applied to files:

  • packages/unraid-api-plugin-connect/src/index.ts
🧬 Code graph analysis (2)
packages/unraid-api-plugin-connect/src/unraid-connect/connect-plugin.service.ts (2)
packages/unraid-api-plugin-connect/src/config/connect.config.ts (1)
  • ConfigType (187-190)
packages/unraid-api-plugin-connect/src/unraid-connect/connect-plugin.utils.ts (1)
  • isConnectPluginInstalled (7-12)
packages/unraid-api-plugin-connect/src/index.ts (1)
packages/unraid-api-plugin-connect/src/unraid-connect/connect.module.ts (1)
  • Module (15-34)
🔇 Additional comments (5)
packages/unraid-api-plugin-connect/src/unraid-connect/connect-plugin.utils.ts (1)

7-12: LGTM!

The utility function correctly checks plugin installation status synchronously, which is appropriate for module load-time decisions. The environment variable escape hatch is useful for development.

packages/unraid-api-plugin-connect/src/unraid-connect/connect.module.ts (1)

13-13: LGTM!

ConnectPluginService is correctly wired as a provider in the module. Keeping it internal (not exported) is appropriate for a lifecycle management service.

Also applies to: 23-23

packages/unraid-api-plugin-connect/src/__test__/connect-plugin.service.test.ts (1)

1-100: LGTM!

The test suite correctly uses Vitest (with vi mocking per project standards) and provides good coverage of the service's lifecycle and pruning behaviors. The tests appropriately verify behavior based on plugin installation status and the shouldRestart parameter.

packages/unraid-api-plugin-connect/src/index.ts (1)

7-8: LGTM!

The refactoring successfully moves complex plugin management logic from module initialization to the dedicated ConnectPluginService, resulting in cleaner module structure and better separation of concerns. The simplified DisabledConnectPluginModule appropriately delegates lifecycle management to the service.

Also applies to: 36-39, 46-46

packages/unraid-api-plugin-connect/src/unraid-connect/connect-plugin.service.ts (1)

24-27: LGTM!

The onModuleDestroy hook appropriately handles cleanup when the module is destroyed, avoiding a restart since the process is already shutting down.

Comment on lines 49 to 65
async pruneStaleConnectPluginEntry({ shouldRestart = true }: { shouldRestart?: boolean } = {}) {
if (isConnectPluginInstalled()) {
this.logger.warn(
'Connect plugin is installed, skipping pruning. This should not have been invoked.'
);
return;
}

// First, correct the in-memory config, so we don't persist an incorrect config when this api instance shuts down.
this.removeConnectPluginFromNestConfig();

// Then, allow config subscription to flush to disk (25ms buffer + margin)
await new Promise((resolve) => setTimeout(resolve, 1000));

// Finally, remove the stale plugin entry.
await this.removeConnectPluginFromBootConfig(shouldRestart);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Replace arbitrary config flush delay with explicit persistence.

The 1-second setTimeout at line 61 assumes configuration changes will be persisted within that timeframe, but there's no explicit persistence call or verification. The comment mentions "25ms buffer + margin," yet uses 1000ms—a massive margin that still provides no guarantee.

Issues:

  1. No explicit persistence: configService.set() on line 58 (via removeConnectPluginFromNestConfig) updates in-memory state only. Per project learnings, NestJS ConfigService doesn't automatically persist changes to disk.
  2. Race condition: If removeConnectPluginFromBootConfig executes before persistence completes, and if a restart occurs, the API will reload the old configuration containing the plugin entry, potentially causing a restart loop.
  3. Arbitrary timeout: 1 second provides no deterministic guarantee, especially under system load.

Solution: Explicitly invoke the persistence mechanism (e.g., ConnectConfigPersister or equivalent) and await its completion before proceeding to line 64.

Based on learnings about NestJS ConfigService.set() and past review feedback on config persistence.

🤖 Prompt for AI Agents
In
packages/unraid-api-plugin-connect/src/unraid-connect/connect-plugin.service.ts
around lines 49 to 65, the method uses an arbitrary 1s setTimeout to wait for an
in-memory config change to "flush" but never explicitly persists the change,
risking a race and restart loop; replace the timeout by calling and awaiting the
project's config persistence API (e.g., ConnectConfigPersister.persist/save or
the equivalent persistence method) after removeConnectPluginFromNestConfig
returns, or make removeConnectPluginFromNestConfig return a Promise that
performs the persistence, then await that Promise before calling
removeConnectPluginFromBootConfig(shouldRestart) so that disk persistence is
guaranteed before proceeding.

Comment on lines 121 to 125
} catch (error) {
const message =
error instanceof Error ? error.message : 'Unknown error while restarting API.';
this.logger.error('Failed to restart API: %s', message);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Fix misleading error messages.

The error messages reference "restarting API" and "while restarting API," but when shouldRestart is false, the command uses --no-restart and doesn't restart at all. The messages should accurately reflect that the plugin removal failed, not specifically the restart.

Apply this diff:

         } catch (error) {
             const message =
-                error instanceof Error ? error.message : 'Unknown error while restarting API.';
-            this.logger.error('Failed to restart API: %s', message);
+                error instanceof Error ? error.message : 'Unknown error during plugin removal.';
+            this.logger.error('Failed to remove plugin via `%s`: %s', removalCommand, message);
         }

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
packages/unraid-api-plugin-connect/src/unraid-connect/connect-plugin.service.ts
around lines 121 to 125: the catch message incorrectly says "restarting API"
even when shouldRestart is false (command uses --no-restart), so change the
error text to reflect plugin removal failure; update the const message and
logger.error call to use a neutral description like "Failed to remove plugin"
(include the actual error.message when available) so logs correctly describe the
failure regardless of restart behavior.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (2)
api/src/connect-plugin-cleanup.ts (1)

24-38: Remove unnecessary shell: 'bash' from execa and simplify command construction

For this straightforward unraid-api invocation, spawning an extra Bash shell is unnecessary and reintroduces a pattern that was previously removed elsewhere. You can invoke the CLI directly with explicit args, which is simpler, faster, and has less surface area:

-    let removalCommand = 'unraid-api plugins remove -b unraid-api-plugin-connect';
-    if (!shouldRestart) {
-        removalCommand += ' --no-restart';
-    }
-    return await execa('unraid-api', removalCommand.split(' ').slice(1), {
-        shell: 'bash',
-        extendEnv: true,
-    });
+    const args = ['plugins', 'remove', '-b', 'unraid-api-plugin-connect'];
+    if (!shouldRestart) {
+        args.push('--no-restart');
+    }
+    return await execa('unraid-api', args, {
+        extendEnv: true,
+    });

This preserves behavior while avoiding the shell dependency.

api/src/index.ts (1)

16-16: Avoid restarting the API from within its own startup path

pruneStaleConnectPluginEntryIfNecessary() defaults shouldRestart = true, so calling it here can trigger a self‑initiated unraid-api restart during viteNodeApp, recreating the lifecycle/race risks you previously had in onModuleInit. For a startup‑time cleanup/migration, you can safely prune the stale plugin entry without restarting and let this process continue booting.

Consider explicitly disabling restart at this call site:

-        await pruneStaleConnectPluginEntryIfNecessary();
+        await pruneStaleConnectPluginEntryIfNecessary({ shouldRestart: false });

This keeps cleanup behavior while avoiding self‑managed restarts from inside the API process.

Also applies to: 64-65

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a2d7f62 and 21f110d.

📒 Files selected for processing (3)
  • api/src/connect-plugin-cleanup.ts (1 hunks)
  • api/src/index.ts (2 hunks)
  • packages/unraid-api-plugin-connect/src/index.ts (1 hunks)
🧰 Additional context used
🧠 Learnings (10)
📓 Common learnings
Learnt from: pujitm
Repo: unraid/api PR: 1415
File: plugin/plugins/dynamix.unraid.net.plg:234-236
Timestamp: 2025-06-11T14:14:30.348Z
Learning: For the Unraid Connect plugin, the script `/etc/rc.d/rc.unraid-api` is bundled with the plugin package itself, so its presence on the target system is guaranteed during installation.
📚 Learning: 2025-06-11T14:14:30.348Z
Learnt from: pujitm
Repo: unraid/api PR: 1415
File: plugin/plugins/dynamix.unraid.net.plg:234-236
Timestamp: 2025-06-11T14:14:30.348Z
Learning: For the Unraid Connect plugin, the script `/etc/rc.d/rc.unraid-api` is bundled with the plugin package itself, so its presence on the target system is guaranteed during installation.

Applied to files:

  • api/src/connect-plugin-cleanup.ts
  • packages/unraid-api-plugin-connect/src/index.ts
📚 Learning: 2025-02-05T21:10:48.136Z
Learnt from: elibosley
Repo: unraid/api PR: 1120
File: plugin/plugins/dynamix.unraid.net.plg:35-38
Timestamp: 2025-02-05T21:10:48.136Z
Learning: When providing error handling guidance for Unraid plugins, direct users to use the web GUI (Plugins > Installed Plugins) rather than suggesting command-line actions.

Applied to files:

  • packages/unraid-api-plugin-connect/src/index.ts
📚 Learning: 2025-02-18T21:39:10.313Z
Learnt from: pujitm
Repo: unraid/api PR: 1145
File: api/src/unraid-api/unraid-file-modifier/modifications/log-rotate.modification.ts:57-60
Timestamp: 2025-02-18T21:39:10.313Z
Learning: When using Node.js `rm` with `{ force: true }` option or Unix/Linux `rm -f`, existence checks are redundant as the command continues silently if the file doesn't exist.

Applied to files:

  • packages/unraid-api-plugin-connect/src/index.ts
📚 Learning: 2025-09-04T15:26:34.416Z
Learnt from: elibosley
Repo: unraid/api PR: 1657
File: web/scripts/deploy-dev.sh:37-41
Timestamp: 2025-09-04T15:26:34.416Z
Learning: In web/scripts/deploy-dev.sh, the command `rm -rf /usr/local/emhttp/plugins/dynamix.my.servers/unraid-components/*` intentionally removes all contents of the unraid-components directory before deploying standalone components. This broader cleanup is desired behavior according to the maintainer elibosley.

Applied to files:

  • packages/unraid-api-plugin-connect/src/index.ts
📚 Learning: 2025-03-27T13:34:53.438Z
Learnt from: pujitm
Repo: unraid/api PR: 1252
File: api/src/environment.ts:56-56
Timestamp: 2025-03-27T13:34:53.438Z
Learning: For critical components in the Unraid API, such as retrieving version information from package.json, failing fast (allowing crashes) is preferred over graceful degradation with fallback values.

Applied to files:

  • packages/unraid-api-plugin-connect/src/index.ts
📚 Learning: 2025-01-30T19:38:02.478Z
Learnt from: pujitm
Repo: unraid/api PR: 1075
File: unraid-ui/src/register.ts:15-34
Timestamp: 2025-01-30T19:38:02.478Z
Learning: In the web components registration process for unraid-ui, use a soft-fail approach (logging warnings/errors) instead of throwing errors, to ensure other components can still register successfully even if one component fails.

Applied to files:

  • packages/unraid-api-plugin-connect/src/index.ts
📚 Learning: 2024-11-05T14:49:07.308Z
Learnt from: elibosley
Repo: unraid/api PR: 942
File: api/src/unraid-api/auth/api-key.service.ts:62-70
Timestamp: 2024-11-05T14:49:07.308Z
Learning: In `api/src/unraid-api/auth/api-key.service.ts`, when handling read errors in the `findById` method, throw a `GraphQLError` instead of an `InternalServerErrorException`.

Applied to files:

  • packages/unraid-api-plugin-connect/src/index.ts
📚 Learning: 2025-04-23T20:19:42.542Z
Learnt from: pujitm
Repo: unraid/api PR: 1367
File: packages/unraid-api-plugin-connect/src/pubsub/user.service.ts:44-52
Timestamp: 2025-04-23T20:19:42.542Z
Learning: The project uses a custom or extended implementation of NestJS ConfigService that includes a `set()` method for runtime configuration mutation, unlike the standard nestjs/config package which only provides getter methods.

Applied to files:

  • packages/unraid-api-plugin-connect/src/index.ts
📚 Learning: 2025-04-23T20:19:42.542Z
Learnt from: pujitm
Repo: unraid/api PR: 1367
File: packages/unraid-api-plugin-connect/src/pubsub/user.service.ts:44-52
Timestamp: 2025-04-23T20:19:42.542Z
Learning: The project uses a custom ConfigService implementation that includes a `set()` method for runtime configuration mutation, unlike the standard nestjs/config package which only provides getter methods.

Applied to files:

  • packages/unraid-api-plugin-connect/src/index.ts
🧬 Code graph analysis (2)
api/src/index.ts (1)
api/src/connect-plugin-cleanup.ts (1)
  • pruneStaleConnectPluginEntryIfNecessary (24-39)
packages/unraid-api-plugin-connect/src/index.ts (1)
packages/unraid-api-plugin-generator/src/templates/index.ts (1)
  • ApiModule (28-28)
🔇 Additional comments (1)
packages/unraid-api-plugin-connect/src/index.ts (1)

30-30: Static ApiModule export looks good

Exporting ApiModule as ConnectPluginModule aligns with the generator template and removes the dynamic/disabled fallback complexity. Just ensure there are no remaining imports of the old DisabledConnectPluginModule or isConnectPluginInstalled elsewhere in the repo or tests.

@pujitm pujitm requested a review from elibosley November 19, 2025 18:25
@pujitm pujitm marked this pull request as ready for review November 19, 2025 18:25
import { type NestFastifyApplication } from '@nestjs/platform-fastify';
import { unlinkSync } from 'fs';
import { mkdir } from 'fs/promises';
import { mkdir, readFile } from 'fs/promises';

Check notice

Code scanning / CodeQL

Unused variable, import, function or class Note

Unused import readFile.

Copilot Autofix

AI about 2 hours ago

The best way to fix this problem is to remove the unused readFile import from line 7 in api/src/index.ts. This should be done by editing the import statement so that only the actually used mkdir named import remains. No other changes need to be made elsewhere in the code since readFile is never referenced.


Suggested changeset 1
api/src/index.ts

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/api/src/index.ts b/api/src/index.ts
--- a/api/src/index.ts
+++ b/api/src/index.ts
@@ -4,7 +4,7 @@
 
 import { type NestFastifyApplication } from '@nestjs/platform-fastify';
 import { unlinkSync } from 'fs';
-import { mkdir, readFile } from 'fs/promises';
+import { mkdir } from 'fs/promises';
 import http from 'http';
 import https from 'https';
 
EOF
@@ -4,7 +4,7 @@

import { type NestFastifyApplication } from '@nestjs/platform-fastify';
import { unlinkSync } from 'fs';
import { mkdir, readFile } from 'fs/promises';
import { mkdir } from 'fs/promises';
import http from 'http';
import https from 'https';

Copilot is powered by AI and may make mistakes. Always verify output.
Unable to commit as this autofix suggestion is now outdated
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (2)
api/src/unraid-api/config/api-config.module.ts (2)

33-39: Consider refactoring: Side effect in a "load" function violates single responsibility.

The loadApiConfig function name suggests it only reads configuration, but it now also modifies the config file on disk. While the comment acknowledges this as a "Hack," this approach:

  • Violates the principle of least surprise
  • Makes the function harder to test and reason about
  • Could cause issues if called in unexpected contexts

Consider one of these alternatives:

  1. Move the cleanup logic to onApplicationBootstrap in ApiConfigPersistence class
  2. Create a separate cleanup function that's explicitly called during startup
  3. Add a separate cleanup pass in the main bootstrap sequence

This would maintain clearer separation between loading and cleanup operations.


33-33: Improve documentation: Explain the "Hack" comment more thoroughly.

The comment acknowledges this is a "Hack" but doesn't explain why it's necessary, what the ideal solution would be, or what problem it solves. More context would help future maintainers understand the tradeoffs.

Consider expanding the comment to something like:

// HACK: Auto-cleanup stale connect plugin entry during config load.
// This runs here (outside NestJS DI) to ensure the plugin list is clean
// before module initialization. Ideally, this would be handled by a dedicated
// cleanup service that runs during bootstrap, but that would require refactoring
// the plugin loading order.
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1e1a04f and 76d4f5a.

📒 Files selected for processing (3)
  • api/src/connect-plugin-cleanup.ts (1 hunks)
  • api/src/index.ts (1 hunks)
  • api/src/unraid-api/config/api-config.module.ts (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • api/src/index.ts
  • api/src/connect-plugin-cleanup.ts
🧰 Additional context used
🧠 Learnings (3)
📓 Common learnings
Learnt from: pujitm
Repo: unraid/api PR: 1415
File: plugin/plugins/dynamix.unraid.net.plg:234-236
Timestamp: 2025-06-11T14:14:30.348Z
Learning: For the Unraid Connect plugin, the script `/etc/rc.d/rc.unraid-api` is bundled with the plugin package itself, so its presence on the target system is guaranteed during installation.
📚 Learning: 2025-03-14T16:10:38.562Z
Learnt from: pujitm
Repo: unraid/api PR: 1211
File: api/src/graphql/schema/types/connect/connect.graphql:142-146
Timestamp: 2025-03-14T16:10:38.562Z
Learning: The updateApiSettings mutation in api/src/unraid-api/graph/connect/connect.resolver.ts is protected with the UsePermissions decorator that requires UPDATE permission on the CONFIG resource.

Applied to files:

  • api/src/unraid-api/config/api-config.module.ts
📚 Learning: 2025-06-11T14:14:30.348Z
Learnt from: pujitm
Repo: unraid/api PR: 1415
File: plugin/plugins/dynamix.unraid.net.plg:234-236
Timestamp: 2025-06-11T14:14:30.348Z
Learning: For the Unraid Connect plugin, the script `/etc/rc.d/rc.unraid-api` is bundled with the plugin package itself, so its presence on the target system is guaranteed during installation.

Applied to files:

  • api/src/unraid-api/config/api-config.module.ts
🧬 Code graph analysis (1)
api/src/unraid-api/config/api-config.module.ts (1)
api/src/connect-plugin-cleanup.ts (1)
  • isConnectPluginInstalled (7-12)
🔇 Additional comments (2)
api/src/unraid-api/config/api-config.module.ts (2)

9-9: LGTM: Import statement is correct.

The imported function isConnectPluginInstalled is properly defined in the cleanup module and the import path is correct.


38-38: Type assertion justifiably raises concern about missing required fields.

The type assertion as ApiConfig on line 38 does bypass type safety. While loadConfig() theoretically returns a full ApiConfig with defaults merged, diskConfig is explicitly typed as Partial<ApiConfig>, and after the optional-chaining filter on plugins, there's no guarantee all required fields are present.

The codebase demonstrates the correct pattern elsewhere: merge with defaults before writing (lines 50-53). The problematic code should either:

  1. Merge with defaults before writing: await apiHandler.writeConfigFile({...createDefaultConfig(), ...diskConfig})
  2. Update writeConfigFile signature to accept Partial<ApiConfig> with proper merging logic, as shown in updateConfig

The assertion hides the risk that required fields could be missing when persisted to disk.

Comment on lines +33 to +39
// Hack: cleanup stale connect plugin entry if necessary
if (!isConnectPluginInstalled()) {
diskConfig.plugins = diskConfig.plugins?.filter(
(plugin) => plugin !== 'unraid-api-plugin-connect'
);
await apiHandler.writeConfigFile(diskConfig as ApiConfig);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Add error handling for the config write operation.

The writeConfigFile operation can fail (e.g., due to disk I/O errors or permission issues), but there's no error handling in place. Since this runs during application startup outside the NestJS DI container, an unhandled error could crash the application before proper error handling is initialized.

Consider adding error handling:

 // Hack: cleanup stale connect plugin entry if necessary
 if (!isConnectPluginInstalled()) {
-    diskConfig.plugins = diskConfig.plugins?.filter(
-        (plugin) => plugin !== 'unraid-api-plugin-connect'
-    );
-    await apiHandler.writeConfigFile(diskConfig as ApiConfig);
+    const originalPlugins = diskConfig.plugins ?? [];
+    const cleanedPlugins = originalPlugins.filter(
+        (plugin) => plugin !== 'unraid-api-plugin-connect'
+    );
+    if (cleanedPlugins.length !== originalPlugins.length) {
+        diskConfig.plugins = cleanedPlugins;
+        try {
+            await apiHandler.writeConfigFile(diskConfig as ApiConfig);
+        } catch (error) {
+            console.error('Failed to cleanup stale connect plugin entry:', error);
+            // Continue with startup even if cleanup fails
+        }
+    }
 }
🤖 Prompt for AI Agents
In api/src/unraid-api/config/api-config.module.ts around lines 33 to 39, the
call to await apiHandler.writeConfigFile(diskConfig as ApiConfig) is unprotected
and can throw during startup; wrap this call in a try/catch, catch any error,
log the error details (e.g., console.error with a clear message and the error
object) and avoid rethrowing so startup doesn't crash; if you need to treat this
as fatal, log the error then call process.exit(1) explicitly—otherwise just log
and continue.

⚠️ Potential issue | 🟠 Major

Optimize: Only write config if the plugin was actually removed.

The current implementation writes to disk even if the 'unraid-api-plugin-connect' entry was not present in the plugins array. This causes unnecessary I/O operations on every startup when the plugin is not installed but also not in the config.

Apply this diff to optimize the write operation:

 // Hack: cleanup stale connect plugin entry if necessary
 if (!isConnectPluginInstalled()) {
-    diskConfig.plugins = diskConfig.plugins?.filter(
-        (plugin) => plugin !== 'unraid-api-plugin-connect'
-    );
-    await apiHandler.writeConfigFile(diskConfig as ApiConfig);
+    const originalPlugins = diskConfig.plugins ?? [];
+    const cleanedPlugins = originalPlugins.filter(
+        (plugin) => plugin !== 'unraid-api-plugin-connect'
+    );
+    if (cleanedPlugins.length !== originalPlugins.length) {
+        diskConfig.plugins = cleanedPlugins;
+        await apiHandler.writeConfigFile(diskConfig as ApiConfig);
+    }
 }
🤖 Prompt for AI Agents
In api/src/unraid-api/config/api-config.module.ts around lines 33 to 39, the
code always writes diskConfig back to disk even when 'unraid-api-plugin-connect'
wasn't present; change the logic to check whether the plugin was actually
removed before calling writeConfigFile: compute the filtered plugins array,
compare its length (or use includes to detect presence) against the original
plugins, and only assign diskConfig.plugins and await
apiHandler.writeConfigFile(diskConfig as ApiConfig) when the filtered array
differs from the original (i.e., the plugin existed and was removed).

@github-actions
Copy link
Contributor

This plugin has been deployed to Cloudflare R2 and is available for testing.
Download it at this URL:

https://preview.dl.unraid.net/unraid-api/tag/PR1791/dynamix.unraid.net.plg

@elibosley elibosley merged commit e734043 into main Nov 19, 2025
12 of 13 checks passed
@elibosley elibosley deleted the fix/connect-install branch November 19, 2025 19:22
elibosley pushed a commit that referenced this pull request Nov 19, 2025
🤖 I have created a release *beep* *boop*
---


## [4.27.0](v4.26.2...v4.27.0)
(2025-11-19)


### Features

* remove Unraid API log download functionality
([#1793](#1793))
([e4a9b82](e4a9b82))


### Bug Fixes

* auto-uninstallation of connect api plugin
([#1791](#1791))
([e734043](e734043))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants