From 39b8f453da23793ef51f8e7f7196370aada8c5aa Mon Sep 17 00:00:00 2001 From: Eli Bosley Date: Wed, 9 Jul 2025 09:21:43 -0400 Subject: [PATCH] fix: invalid state for unraid plugin (#1492) ## Summary by CodeRabbit * **New Features** * Improved connection status handling by introducing a new service that writes connection status to a JSON file for enhanced integration. * Updated system components to read connection status and allowed origins from the new JSON file, ensuring more reliable and up-to-date information. * **Chores** * Expanded allowed Bash command permissions to include commands starting with "mv:". --- .claude/settings.local.json | 3 +- .../config/connect-status-writer.service.ts | 69 +++++++++++++++++++ .../unraid-api-plugin-connect/src/index.ts | 3 +- .../include/UpdateFlashBackup.php | 13 +++- .../dynamix.my.servers/include/state.php | 21 ++++-- 5 files changed, 97 insertions(+), 12 deletions(-) create mode 100644 packages/unraid-api-plugin-connect/src/config/connect-status-writer.service.ts diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 0fd183f27..3022f1b0b 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -10,7 +10,8 @@ "Bash(grep:*)", "Bash(pnpm type-check:*)", "Bash(pnpm lint:*)", - "Bash(pnpm --filter ./api lint)" + "Bash(pnpm --filter ./api lint)", + "Bash(mv:*)" ] }, "enableAllProjectMcpServers": false diff --git a/packages/unraid-api-plugin-connect/src/config/connect-status-writer.service.ts b/packages/unraid-api-plugin-connect/src/config/connect-status-writer.service.ts new file mode 100644 index 000000000..cd5e48f38 --- /dev/null +++ b/packages/unraid-api-plugin-connect/src/config/connect-status-writer.service.ts @@ -0,0 +1,69 @@ +import { Injectable, Logger, OnModuleInit } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; +import { writeFile } from 'fs/promises'; + +import { ConnectionMetadata, ConfigType } from './connect.config.js'; + +@Injectable() +export class ConnectStatusWriterService implements OnModuleInit { + constructor(private readonly configService: ConfigService) {} + + private logger = new Logger(ConnectStatusWriterService.name); + + get statusFilePath() { + // Write to /var/local/emhttp/connectStatus.json so PHP can read it + return '/var/local/emhttp/connectStatus.json'; + } + + async onModuleInit() { + this.logger.verbose(`Status file path: ${this.statusFilePath}`); + + // Write initial status + await this.writeStatus(); + + // Listen for changes to connection status + this.configService.changes$.subscribe({ + next: async (change) => { + const connectionChanged = change.path && change.path.startsWith('connect.mothership'); + if (connectionChanged) { + await this.writeStatus(); + } + }, + error: (err) => { + this.logger.error('Error receiving config changes:', err); + }, + }); + } + + private async writeStatus() { + try { + const connectionMetadata = this.configService.get('connect.mothership'); + + // Try to get allowed origins from the store + let allowedOrigins = ''; + try { + // We can't import from @app here, so we'll skip allowed origins for now + // This can be added later if needed + allowedOrigins = ''; + } catch (error) { + this.logger.debug('Could not get allowed origins:', error); + } + + const statusData = { + connectionStatus: connectionMetadata?.status || 'PRE_INIT', + error: connectionMetadata?.error || null, + lastPing: connectionMetadata?.lastPing || null, + allowedOrigins: allowedOrigins, + timestamp: Date.now() + }; + + const data = JSON.stringify(statusData, null, 2); + this.logger.verbose(`Writing connection status: ${data}`); + + await writeFile(this.statusFilePath, data); + this.logger.verbose(`Status written to ${this.statusFilePath}`); + } catch (error) { + this.logger.error(error, `Error writing status to '${this.statusFilePath}'`); + } + } +} \ No newline at end of file diff --git a/packages/unraid-api-plugin-connect/src/index.ts b/packages/unraid-api-plugin-connect/src/index.ts index 21748673f..1181a7646 100644 --- a/packages/unraid-api-plugin-connect/src/index.ts +++ b/packages/unraid-api-plugin-connect/src/index.ts @@ -3,6 +3,7 @@ import { ConfigModule, ConfigService } from '@nestjs/config'; import { ConnectConfigPersister } from './config/config.persistence.js'; import { configFeature } from './config/connect.config.js'; +import { ConnectStatusWriterService } from './config/connect-status-writer.service.js'; import { MothershipModule } from './mothership-proxy/mothership.module.js'; import { ConnectModule } from './unraid-connect/connect.module.js'; @@ -10,7 +11,7 @@ export const adapter = 'nestjs'; @Module({ imports: [ConfigModule.forFeature(configFeature), ConnectModule, MothershipModule], - providers: [ConnectConfigPersister], + providers: [ConnectConfigPersister, ConnectStatusWriterService], exports: [], }) class ConnectPluginModule { diff --git a/plugin/source/dynamix.unraid.net/usr/local/emhttp/plugins/dynamix.my.servers/include/UpdateFlashBackup.php b/plugin/source/dynamix.unraid.net/usr/local/emhttp/plugins/dynamix.my.servers/include/UpdateFlashBackup.php index 5362ddec6..c61efd225 100644 --- a/plugin/source/dynamix.unraid.net/usr/local/emhttp/plugins/dynamix.my.servers/include/UpdateFlashBackup.php +++ b/plugin/source/dynamix.unraid.net/usr/local/emhttp/plugins/dynamix.my.servers/include/UpdateFlashBackup.php @@ -23,9 +23,16 @@ $myservers_flash_cfg_path='/boot/config/plugins/dynamix.my.servers/myservers.cfg $myservers = file_exists($myservers_flash_cfg_path) ? @parse_ini_file($myservers_flash_cfg_path,true) : []; $isRegistered = !empty($myservers['remote']['username']); -$myservers_memory_cfg_path ='/var/local/emhttp/myservers.cfg'; -$mystatus = (file_exists($myservers_memory_cfg_path)) ? @parse_ini_file($myservers_memory_cfg_path) : []; -$isConnected = (($mystatus['minigraph']??'')==='CONNECTED') ? true : false; +// Read connection status from the new API status file +$statusFilePath = '/var/local/emhttp/connectStatus.json'; +$connectionStatus = ''; + +if (file_exists($statusFilePath)) { + $statusData = @json_decode(file_get_contents($statusFilePath), true); + $connectionStatus = $statusData['connectionStatus'] ?? ''; +} + +$isConnected = ($connectionStatus === 'CONNECTED') ? true : false; $flashbackup_ini = '/var/local/emhttp/flashbackup.ini'; diff --git a/plugin/source/dynamix.unraid.net/usr/local/emhttp/plugins/dynamix.my.servers/include/state.php b/plugin/source/dynamix.unraid.net/usr/local/emhttp/plugins/dynamix.my.servers/include/state.php index 65abcc3c4..6b13f1dbf 100644 --- a/plugin/source/dynamix.unraid.net/usr/local/emhttp/plugins/dynamix.my.servers/include/state.php +++ b/plugin/source/dynamix.unraid.net/usr/local/emhttp/plugins/dynamix.my.servers/include/state.php @@ -168,9 +168,8 @@ class ServerState private function getMyServersCfgValues() { /** - * @todo can we read this from somewhere other than the flash? Connect page uses this path and /boot/config/plugins/dynamix.my.servers/myservers.cfg… - * - $myservers_memory_cfg_path ='/var/local/emhttp/myservers.cfg'; - * - $mystatus = (file_exists($myservers_memory_cfg_path)) ? @parse_ini_file($myservers_memory_cfg_path) : []; + * Memory config is now written by the new API to /usr/local/emhttp/state/myservers.cfg + * This contains runtime state including connection status. */ $flashCfgPath = '/boot/config/plugins/dynamix.my.servers/myservers.cfg'; $this->myServersFlashCfg = file_exists($flashCfgPath) ? @parse_ini_file($flashCfgPath, true) : []; @@ -212,11 +211,19 @@ class ServerState * Include localhost in the test, but only display HTTP(S) URLs that do not include localhost. */ $this->host = $_SERVER['HTTP_HOST'] ?? "unknown"; - $memoryCfgPath = '/var/local/emhttp/myservers.cfg'; - $this->myServersMemoryCfg = (file_exists($memoryCfgPath)) ? @parse_ini_file($memoryCfgPath) : []; - $this->myServersMiniGraphConnected = (($this->myServersMemoryCfg['minigraph'] ?? '') === 'CONNECTED'); + // Read connection status and allowed origins from the new API status file + $statusFilePath = '/var/local/emhttp/connectStatus.json'; + $connectionStatus = ''; + $allowedOrigins = ''; + + if (file_exists($statusFilePath)) { + $statusData = @json_decode(file_get_contents($statusFilePath), true); + $connectionStatus = $statusData['connectionStatus'] ?? ''; + $allowedOrigins = $statusData['allowedOrigins'] ?? ''; + } + + $this->myServersMiniGraphConnected = ($connectionStatus === 'CONNECTED'); - $allowedOrigins = $this->myServersMemoryCfg['allowedOrigins'] ?? ""; $extraOrigins = $this->myServersFlashCfg['api']['extraOrigins'] ?? ""; $combinedOrigins = $allowedOrigins . "," . $extraOrigins; // combine the two strings for easier searching $combinedOrigins = str_replace(" ", "", $combinedOrigins); // replace any spaces with nothing