From 9fe65d5036bbb2b4d200f55ad3181a63b0dc75cc Mon Sep 17 00:00:00 2001 From: Hampus Date: Wed, 9 Sep 2026 11:26:30 +0200 Subject: [PATCH] fix(api): honour the configured S3 addressing on uploads (#2626) --- deploy/self-hosting/.env.example | 5 ++++ deploy/self-hosting/docker-compose.yml | 1 + .../api/infrastructure/StorageService.test.ts | 26 +++++++++++++++++++ .../src/api/infrastructure/StorageService.ts | 11 +++++++- 4 files changed, 42 insertions(+), 1 deletion(-) diff --git a/deploy/self-hosting/.env.example b/deploy/self-hosting/.env.example index 1adb9f849..37635bf74 100644 --- a/deploy/self-hosting/.env.example +++ b/deploy/self-hosting/.env.example @@ -166,6 +166,11 @@ MEILI_MASTER_KEY=CHANGE_ME #FLUXER_CLIENT_IP_HEADER_NAME=cf-connecting-ip #FLUXER_TRUST_CLIENT_IP_HEADER=true +# How much the services write. trace, debug, info, warn, error or fatal. Every +# service names the object storage endpoint and its addressing at info on start, +# so a bucket that answers 404 is visible without raising this. +#LOG_LEVEL=debug + FLUXER_S3_ACCESS_KEY=fluxer FLUXER_S3_SECRET_KEY=CHANGE_ME diff --git a/deploy/self-hosting/docker-compose.yml b/deploy/self-hosting/docker-compose.yml index 6b63a9edd..fd6f7197b 100644 --- a/deploy/self-hosting/docker-compose.yml +++ b/deploy/self-hosting/docker-compose.yml @@ -14,6 +14,7 @@ x-fluxer-env: &fluxer-env <<: *fluxer-postgres-env FLUXER_ENV: production NODE_ENV: production + LOG_LEVEL: ${LOG_LEVEL:-info} FLUXER_SELF_HOSTED: "true" FLUXER_BASE_DOMAIN: ${FLUXER_DOMAIN:?set FLUXER_DOMAIN in .env} FLUXER_PUBLIC_SCHEME: ${FLUXER_PUBLIC_SCHEME:-https} diff --git a/fluxer_api/src/api/infrastructure/StorageService.test.ts b/fluxer_api/src/api/infrastructure/StorageService.test.ts index 1c804ae62..4e7ac4dcb 100644 --- a/fluxer_api/src/api/infrastructure/StorageService.test.ts +++ b/fluxer_api/src/api/infrastructure/StorageService.test.ts @@ -143,6 +143,32 @@ describe('StorageService.getPresignedUploadURL', () => { }, ); }); + + it('gives both clients the configured addressing rather than pinning one to path style', async () => { + await withS3Config( + { + endpoint: 'https://s3.example.test', + presignedUrlBase: '', + forcePathStyle: false, + region: 'eu-central-1', + accessKeyId: 'fluxer', + secretAccessKey: 'fluxer-secret', + buckets: {uploads: 'fluxer-uploads'}, + }, + async () => { + const service = new StorageService(); + const probe = service as unknown as { + client: {config: {forcePathStyle?: unknown}}; + presignClient: {config: {forcePathStyle?: unknown}}; + }; + const resolve = async (value: unknown): Promise => + typeof value === 'function' ? await (value as () => Promise)() : value; + + expect(await resolve(probe.client.config.forcePathStyle)).toBe(false); + expect(await resolve(probe.presignClient.config.forcePathStyle)).toBe(false); + }, + ); + }); }); describe('StorageService.copyObjectWithMetadataStripping', () => { diff --git a/fluxer_api/src/api/infrastructure/StorageService.ts b/fluxer_api/src/api/infrastructure/StorageService.ts index 231f0863a..f165539f2 100644 --- a/fluxer_api/src/api/infrastructure/StorageService.ts +++ b/fluxer_api/src/api/infrastructure/StorageService.ts @@ -127,7 +127,7 @@ export class StorageService implements IStorageService { region: this.provider.region, accessKeyId: this.provider.accessKeyId, secretAccessKey: this.provider.secretAccessKey, - forcePathStyle: true, + forcePathStyle: this.provider.forcePathStyle, }); this.presignClient = buildPooledS3Client({ endpoint: this.resolvePresignEndpoint(), @@ -136,6 +136,15 @@ export class StorageService implements IStorageService { secretAccessKey: this.provider.secretAccessKey, forcePathStyle: this.provider.forcePathStyle, }); + Logger.info( + { + endpoint: this.provider.endpoint, + presignEndpoint: this.resolvePresignEndpoint(), + region: this.provider.region, + addressing: this.provider.forcePathStyle ? 'path' : 'virtual-host', + }, + 'Object storage client ready', + ); } private resolvePresignEndpoint(): string {