diff --git a/ts/classes/docker.ts b/ts/classes/docker.ts index 64d355a..6569264 100644 --- a/ts/classes/docker.ts +++ b/ts/classes/docker.ts @@ -939,4 +939,49 @@ export class OneboxDockerManager { } return this.dockerClient.listContainers(); } + + /** + * Stream container logs continuously + * @param containerID The container ID + * @param callback Callback for each log line (line, isError) + */ + async streamContainerLogs( + containerID: string, + callback: (line: string, isError: boolean) => void + ): Promise { + try { + const container = await this.dockerClient!.getContainerById(containerID); + + if (!container) { + throw new Error(`Container not found: ${containerID}`); + } + + const logStream = await container.streamLogs({ + stdout: true, + stderr: true, + timestamps: true, + tail: 100, + }); + + logStream.on('data', (chunk: Uint8Array) => { + // Docker multiplexes stdout/stderr with 8-byte header + // Byte 0: stream type (1=stdout, 2=stderr) + // Bytes 4-7: frame size (big-endian) + // Rest: actual log data + const streamType = chunk[0]; + const isError = streamType === 2; + const content = new TextDecoder().decode(chunk.slice(8)); + if (content.trim()) { + callback(content.trim(), isError); + } + }); + + logStream.on('error', (err: Error) => { + logger.error(`Log stream error for ${containerID}: ${err.message}`); + }); + } catch (error) { + logger.error(`Failed to stream logs for ${containerID}: ${getErrorMessage(error)}`); + throw error; + } + } } diff --git a/ts/classes/httpserver.ts b/ts/classes/httpserver.ts index 06a38b6..dc6cb5b 100644 --- a/ts/classes/httpserver.ts +++ b/ts/classes/httpserver.ts @@ -275,13 +275,13 @@ export class OneboxHttpServer { } else if (path === '/api/platform-services' && method === 'GET') { return await this.handleListPlatformServicesRequest(); } else if (path.match(/^\/api\/platform-services\/(mongodb|minio|redis|postgresql|rabbitmq)$/) && method === 'GET') { - const type = path.split('/').pop()!; + const type = path.split('/').pop()! as TPlatformServiceType; return await this.handleGetPlatformServiceRequest(type); } else if (path.match(/^\/api\/platform-services\/(mongodb|minio|redis|postgresql|rabbitmq)\/start$/) && method === 'POST') { - const type = path.split('/')[3]; + const type = path.split('/')[3] as TPlatformServiceType; return await this.handleStartPlatformServiceRequest(type); } else if (path.match(/^\/api\/platform-services\/(mongodb|minio|redis|postgresql|rabbitmq)\/stop$/) && method === 'POST') { - const type = path.split('/')[3]; + const type = path.split('/')[3] as TPlatformServiceType; return await this.handleStopPlatformServiceRequest(type); } else if (path.match(/^\/api\/services\/[^/]+\/platform-resources$/) && method === 'GET') { const serviceName = path.split('/')[3]; diff --git a/ts/classes/registry.ts b/ts/classes/registry.ts index b18ece1..bcf9a0d 100644 --- a/ts/classes/registry.ts +++ b/ts/classes/registry.ts @@ -45,10 +45,10 @@ export class RegistryManager { this.s3Server = await plugins.smarts3.Smarts3.createAndStart({ server: { port: port, - host: '0.0.0.0', + address: '0.0.0.0', }, storage: { - bucketsDir: dataDir, + directory: dataDir, cleanSlate: false, // Preserve data across restarts }, }); @@ -70,9 +70,13 @@ export class RegistryManager { }, auth: { jwtSecret: this.jwtSecret, + tokenStore: 'memory', + npmTokens: { + enabled: false, + }, ociTokens: { enabled: true, - issuer: 'onebox-registry', + realm: 'onebox-registry', service: 'onebox-registry', }, },