diff --git a/backend/src/config/dependency-injector.config.ts b/backend/src/config/dependency-injector.config.ts index b6f8920..29dc8da 100644 --- a/backend/src/config/dependency-injector.config.ts +++ b/backend/src/config/dependency-injector.config.ts @@ -90,5 +90,11 @@ const configureStorage = (storageMode: string) => { export const initializeEagerServices = async () => { // Force the creation of services that need to be initialized at startup container.get(RecordingService); - await container.get(MeetStorageService).initializeGlobalPreferences(); + + // Perform comprehensive health checks before initializing other services + const storageService = container.get(MeetStorageService); + await storageService.checkStartupHealth(); + + // Initialize global preferences after health checks pass + await storageService.initializeGlobalPreferences(); }; diff --git a/backend/src/services/storage/providers/abs/abs-storage.provider.ts b/backend/src/services/storage/providers/abs/abs-storage.provider.ts index ce5673a..03808ec 100644 --- a/backend/src/services/storage/providers/abs/abs-storage.provider.ts +++ b/backend/src/services/storage/providers/abs/abs-storage.provider.ts @@ -148,4 +148,24 @@ export class ABSStorageProvider implements StorageProvider { throw error; } } + + /** + * Performs a health check on the Azure Blob Storage provider. + */ + async checkHealth(): Promise<{ accessible: boolean; bucketExists?: boolean; containerExists?: boolean }> { + try { + this.logger.debug('Performing ABS storage health check'); + const healthResult = await this.azureBlobService.checkHealth(); + return { + accessible: healthResult.accessible, + containerExists: healthResult.containerExists + }; + } catch (error) { + this.logger.error(`ABS storage health check failed: ${error}`); + return { + accessible: false, + containerExists: false + }; + } + } } diff --git a/backend/src/services/storage/providers/abs/abs.service.ts b/backend/src/services/storage/providers/abs/abs.service.ts index a826072..eda8b0e 100644 --- a/backend/src/services/storage/providers/abs/abs.service.ts +++ b/backend/src/services/storage/providers/abs/abs.service.ts @@ -287,4 +287,26 @@ export class ABSService { return `${prefix}/${name}`; } + + /** + * Health check for Azure Blob Storage service and container accessibility. + * Verifies both service connectivity and container existence. + */ + async checkHealth(): Promise<{ accessible: boolean; containerExists: boolean }> { + try { + // Check if we can access the container by checking if it exists + const exists = await this.containerClient.exists(); + + if (exists) { + this.logger.verbose(`ABS health check: service accessible and container '${MEET_AZURE_CONTAINER_NAME}' exists`); + return { accessible: true, containerExists: true }; + } else { + this.logger.error(`ABS container '${MEET_AZURE_CONTAINER_NAME}' does not exist`); + return { accessible: true, containerExists: false }; + } + } catch (error: any) { + this.logger.error(`ABS health check failed: ${error.message}`); + return { accessible: false, containerExists: false }; + } + } } diff --git a/backend/src/services/storage/providers/s3/s3-storage.provider.ts b/backend/src/services/storage/providers/s3/s3-storage.provider.ts index 8cb8b87..2c32c51 100644 --- a/backend/src/services/storage/providers/s3/s3-storage.provider.ts +++ b/backend/src/services/storage/providers/s3/s3-storage.provider.ts @@ -137,4 +137,24 @@ export class S3StorageProvider implements StorageProvider { throw error; } } + + /** + * Performs a health check on the S3 storage provider. + */ + async checkHealth(): Promise<{ accessible: boolean; bucketExists?: boolean; containerExists?: boolean }> { + try { + this.logger.debug('Performing S3 storage health check'); + const healthResult = await this.s3Service.checkHealth(); + return { + accessible: healthResult.accessible, + bucketExists: healthResult.bucketExists + }; + } catch (error) { + this.logger.error(`S3 storage health check failed: ${error}`); + return { + accessible: false, + bucketExists: false + }; + } + } } diff --git a/backend/src/services/storage/providers/s3/s3.service.ts b/backend/src/services/storage/providers/s3/s3.service.ts index 0b52571..e8aa140 100644 --- a/backend/src/services/storage/providers/s3/s3.service.ts +++ b/backend/src/services/storage/providers/s3/s3.service.ts @@ -233,6 +233,37 @@ export class S3Service { this.logger.info('S3 client destroyed'); } + /** + * Health check for S3 service and bucket accessibility. + * Verifies both service connectivity and bucket existence. + */ + async checkHealth(): Promise<{ accessible: boolean; bucketExists: boolean }> { + try { + // Check if we can access the S3 service by listing objects with a small limit + await this.run( + new ListObjectsV2Command({ + Bucket: MEET_S3_BUCKET, + MaxKeys: 1 + }) + ); + + // If we reach here, both service and bucket are accessible + this.logger.verbose(`S3 health check: service accessible and bucket '${MEET_S3_BUCKET}' exists`); + return { accessible: true, bucketExists: true }; + } catch (error: any) { + this.logger.error(`S3 health check failed: ${error.message}`); + + // Check if it's a bucket-specific error + if (error.name === 'NoSuchBucket') { + this.logger.error(`S3 bucket '${MEET_S3_BUCKET}' does not exist`); + return { accessible: true, bucketExists: false }; + } + + // Service is not accessible + return { accessible: false, bucketExists: false }; + } + } + /** * Constructs the full key for an S3 object by ensuring it includes the specified sub-bucket prefix. * If the provided name already starts with the prefix, it is returned as-is. diff --git a/backend/src/services/storage/storage.interface.ts b/backend/src/services/storage/storage.interface.ts index aad754d..405ca10 100644 --- a/backend/src/services/storage/storage.interface.ts +++ b/backend/src/services/storage/storage.interface.ts @@ -93,6 +93,14 @@ export interface StorageProvider { * @returns A promise that resolves to a readable stream of the object content */ getObjectAsStream(key: string, range?: { start: number; end: number }): Promise; + + /** + * Performs a health check on the storage provider. + * Verifies both service connectivity and container/bucket existence. + * + * @returns A promise that resolves to an object indicating accessibility and container/bucket existence + */ + checkHealth(): Promise<{ accessible: boolean; bucketExists?: boolean; containerExists?: boolean }>; } /** diff --git a/backend/src/services/storage/storage.service.ts b/backend/src/services/storage/storage.service.ts index 21b6ebb..49d0899 100644 --- a/backend/src/services/storage/storage.service.ts +++ b/backend/src/services/storage/storage.service.ts @@ -67,6 +67,36 @@ export class MeetStorageService< this.keyBuilder = keyBuilder; } + /** + * Performs a health check on the storage system. + * Verifies both service connectivity and container/bucket existence. + * Terminates the process if storage is not accessible. + */ + async checkStartupHealth(): Promise { + try { + this.logger.verbose('Performing storage health check...'); + + // Get the underlying storage service to perform health check + const isHealthy = await this.storageProvider.checkHealth(); + + if (!isHealthy.accessible) { + this.logger.error('Storage service is not accessible. Terminating process...'); + process.exit(1); + } + + if (!isHealthy.bucketExists && !isHealthy.containerExists) { + this.logger.error('Storage bucket/container does not exist. Terminating process...'); + process.exit(1); + } + + this.logger.verbose('Storage health check passed successfully'); + } catch (error) { + this.logger.error('Storage health check failed:', error); + this.logger.error('Terminating process due to storage health check failure...'); + process.exit(1); + } + } + // ========================================== // GLOBAL PREFERENCES DOMAIN LOGIC // ==========================================