fix: invalid state for unraid plugin (#1492)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## 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:".
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
Eli Bosley
2025-07-09 09:21:43 -04:00
committed by GitHub
parent 6bf3f77638
commit 39b8f453da
5 changed files with 97 additions and 12 deletions

View File

@@ -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

View File

@@ -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<ConfigType, true>) {}
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<ConnectionMetadata>('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}'`);
}
}
}

View File

@@ -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 {

View File

@@ -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';

View File

@@ -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