Skip to content
14 changes: 14 additions & 0 deletions modules/storage/src/Storage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,14 @@ export default class Storage extends ManagedModule<Config> {
config.aws.usePathStyle = false;
}
}
if (config.provider === 'local') {
const secret = config.local?.signingSecret?.trim() ?? '';
if (secret && !/^[0-9a-fA-F]{64}$/.test(secret)) {
throw new Error(
"local.signingSecret must be a 64-character hex string (32 bytes). Generate one with: node -e \"console.log(require('crypto').randomBytes(32).toString('hex'))\"",
);
}
}
return config;
}

Expand All @@ -221,6 +229,9 @@ export default class Storage extends ManagedModule<Config> {
this._storageAuthzResourceDispose?.();
this._storageAuthzResourceDispose = null;
this.disposeDeclaredPeerWatches();
if (this.storageProvider?.dispose) {
await this.storageProvider.dispose();
}
Comment thread
ioanniskemerlis marked this conversation as resolved.
this.updateHealth(HealthCheckStatus.NOT_SERVING);
} else {
this.registerDeclaredPeerWatches();
Expand All @@ -239,6 +250,9 @@ export default class Storage extends ManagedModule<Config> {
this._storageAuthzResourceDispose?.();
this._storageAuthzResourceDispose = null;
}
if (this.storageProvider?.dispose) {
await this.storageProvider.dispose();
}
this.storageProvider = createStorageProvider(provider, {
local,
google,
Expand Down
15 changes: 15 additions & 0 deletions modules/storage/src/config/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,21 @@ export default {
format: 'String',
default: '/var/tmp',
},
httpPort: {
format: 'Number',
default: 3100,
doc: 'Port for the local storage HTTP file server (serves uploads/downloads)',
},
httpBaseUrl: {
Comment thread
ioanniskemerlis marked this conversation as resolved.
format: 'String',
default: '',
doc: 'External base URL for local storage file URLs. Leave empty to auto-derive from httpPort (http://localhost:{httpPort}). Override when the storage server is behind a reverse proxy or running in Docker.',
},
signingSecret: {
format: 'String',
default: '',
doc: 'Hex-encoded HMAC secret (64 hex chars / 32 bytes) for signing file URLs. Required for URLs to survive restarts — without it, a random secret is generated on startup and any public file URLs already stored in the database (sourceUrl/url) become invalid.',
},
},
suffixOnNameConflict: {
format: 'Boolean',
Expand Down
5 changes: 5 additions & 0 deletions modules/storage/src/interfaces/IStorageProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,4 +58,9 @@ export interface IStorageProvider {
getPublicUrl(fileName: string, containerIsPublic?: boolean): Promise<string | Error>;

getUploadUrl(fileName: string): Promise<string | Error>;

/**
* Releases provider resources (e.g. local HTTP file server).
*/
dispose?(): Promise<void>;
}
3 changes: 3 additions & 0 deletions modules/storage/src/interfaces/StorageConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,8 @@ export interface StorageConfig {
};
local: {
storagePath: string;
httpPort: number;
httpBaseUrl: string;
signingSecret: string;
};
}
Loading