feat: add secret settings manager and migration for legacy settings

- Implemented SecretSettingsManager to handle secret settings with encryption.
- Added functionality to migrate legacy plaintext settings into encrypted storage.
- Introduced methods for setting, getting, and clearing secret settings.
- Created tests for verifying the migration and canonicalization of secret settings.
- Updated app state to handle service updates via socket communication.
- Added interface for push service updates to manage service state changes.
This commit is contained in:
2026-04-19 01:47:06 +00:00
parent 618d4d674f
commit 061ce7c3f2
17 changed files with 413 additions and 73 deletions
+23 -13
View File
@@ -23,6 +23,17 @@ export class OneboxServicesManager {
this.docker = oneboxRef.docker;
}
private async broadcastServiceUpdate(
serviceName: string,
action: 'created' | 'updated' | 'deleted' | 'started' | 'stopped',
): Promise<void> {
await this.oneboxRef.opsServer.broadcastServiceUpdate(
serviceName,
action,
this.database.getServiceByName(serviceName),
);
}
/**
* Deploy a new service (full workflow)
*/
@@ -197,7 +208,9 @@ export class OneboxServicesManager {
logger.success(`Service deployed successfully: ${options.name}`);
return this.database.getServiceByName(options.name)!;
const deployedService = this.database.getServiceByName(options.name)!;
await this.broadcastServiceUpdate(options.name, 'created');
return deployedService;
} catch (error) {
logger.error(`Failed to deploy service ${options.name}: ${getErrorMessage(error)}`);
throw error;
@@ -236,12 +249,14 @@ export class OneboxServicesManager {
}
logger.success(`Service started: ${name}`);
await this.broadcastServiceUpdate(name, 'started');
} catch (error) {
logger.error(`Failed to start service ${name}: ${getErrorMessage(error)}`);
this.database.updateService(
this.database.getServiceByName(name)?.id!,
{ status: 'failed' }
);
await this.broadcastServiceUpdate(name, 'updated');
throw error;
}
}
@@ -274,6 +289,7 @@ export class OneboxServicesManager {
}
logger.success(`Service stopped: ${name}`);
await this.broadcastServiceUpdate(name, 'stopped');
} catch (error) {
logger.error(`Failed to stop service ${name}: ${getErrorMessage(error)}`);
throw error;
@@ -301,6 +317,7 @@ export class OneboxServicesManager {
this.database.updateService(service.id!, { status: 'running' });
logger.success(`Service restarted: ${name}`);
await this.broadcastServiceUpdate(name, 'updated');
} catch (error) {
logger.error(`Failed to restart service ${name}: ${getErrorMessage(error)}`);
throw error;
@@ -357,6 +374,7 @@ export class OneboxServicesManager {
this.database.deleteService(service.id!);
logger.success(`Service removed: ${name}`);
await this.oneboxRef.opsServer.broadcastServiceUpdate(name, 'deleted');
} catch (error) {
logger.error(`Failed to remove service ${name}: ${getErrorMessage(error)}`);
throw error;
@@ -625,7 +643,9 @@ export class OneboxServicesManager {
logger.success(`Service ${name} updated (not started)`);
}
return this.database.getServiceByName(name)!;
const refreshedService = this.database.getServiceByName(name)!;
await this.broadcastServiceUpdate(name, 'updated');
return refreshedService;
} catch (error) {
logger.error(`Failed to update service ${name}: ${getErrorMessage(error)}`);
throw error;
@@ -659,11 +679,7 @@ export class OneboxServicesManager {
// Only update and broadcast if status changed
if (service.status !== ourStatus) {
this.database.updateService(service.id!, { status: ourStatus });
// Broadcast status change via WebSocket
if (this.oneboxRef.httpServer) {
this.oneboxRef.httpServer.broadcastServiceStatus(name, ourStatus);
}
await this.broadcastServiceUpdate(name, 'updated');
}
} catch (error) {
logger.debug(`Failed to sync status for service ${name}: ${getErrorMessage(error)}`);
@@ -756,12 +772,6 @@ export class OneboxServicesManager {
// Restart service
logger.info(`Auto-restarting service: ${service.name}`);
await this.restartService(service.name);
// Broadcast update via WebSocket
this.oneboxRef.httpServer.broadcastServiceUpdate({
action: 'updated',
service: this.database.getServiceByName(service.name)!,
});
} else if (!service.imageDigest) {
// First time - just store the digest
this.database.updateService(service.id!, {