fix: force HTTPS S3 endpoint to avoid Coolify 307 on uploads

http://s3 hosts redirect to https; AWS SDK cannot follow PUT redirects, which caused XML parse errors.
This commit is contained in:
2026-08-15 03:04:53 +03:30
parent aa2132f02c
commit e1772a2559
3 changed files with 42 additions and 11 deletions
+6 -2
View File
@@ -27,14 +27,18 @@ S3_ACCESS_KEY=minioadmin
S3_SECRET_KEY=minioadmin
# SeaweedFS live example (preferred aliases also work without S3_*):
# Use the public HTTPS URL (SERVICE_URL_S3). Port 8333 is often internal-only.
# Use the public HTTPS URL (SERVICE_URL_S3). Never use plain http:// for the public host —
# Coolify/Traefik returns HTTP 307 and AWS SDK PutObject cannot follow it.
# Do NOT use :8333 from outside the SeaweedFS network (often refused).
# S3_ENDPOINT=https://s3.game-no.ir
# SERVICE_URL_S3=https://s3.game-no.ir
# SERVICE_URL_S3_8333=https://s3.game-no.ir:8333
# SERVICE_USER_S3=...
# SERVICE_PASSWORD_S3=...
# AWS_ACCESS_KEY_ID=${SERVICE_USER_S3}
# AWS_SECRET_ACCESS_KEY=${SERVICE_PASSWORD_S3}
#
# File uploads: multer uses MEMORY storage and streams the buffer straight to the
# S3 temp bucket. No Coolify persistent volume / uploads folder is required for this.
S3_TEMP_BUCKET=temp
S3_CERTIFICATES_BUCKET=certificates
+25 -4
View File
@@ -31,11 +31,25 @@ const config = {
// S3-compatible storage (MinIO locally, SeaweedFS in production)
// Accepts S3_* directly, or SeaweedFS/AWS service env aliases from the host platform.
S3_ENDPOINT:
// IMPORTANT: use https:// for public SeaweedFS — http:// gets a 307 that AWS SDK cannot follow on PUT.
S3_ENDPOINT: (() => {
const raw = (
process.env.S3_ENDPOINT
|| process.env.SERVICE_URL_S3
|| process.env.SERVICE_URL_S3_8333
|| 'http://localhost:9000',
|| 'http://localhost:9000'
).trim().replace(/\/$/, '');
// Upgrade plain http → https for non-local endpoints (Coolify/proxy 307 breaks PutObject)
const isLocal = /^(http:\/\/)?(localhost|127\.0\.0\.1|minio)(:|\/|$)/i.test(raw)
|| raw.startsWith('http://localhost')
|| raw.startsWith('http://127.0.0.1')
|| raw.startsWith('http://minio');
if (raw.startsWith('http://') && !isLocal) {
return `https://${raw.slice('http://'.length)}`;
}
return raw;
})(),
S3_REGION: process.env.S3_REGION || process.env.AWS_REGION || 'us-east-1',
S3_ACCESS_KEY:
process.env.S3_ACCESS_KEY
@@ -59,10 +73,17 @@ const config = {
),
SIGNED_URL_EXPIRES_IN: parseInt(process.env.SIGNED_URL_EXPIRES_IN, 10) || 900, // 15 minutes default
// Public base URL for public buckets only (private buckets leave fileUrl empty)
S3_PUBLIC_BASE_URL:
S3_PUBLIC_BASE_URL: (() => {
const raw = (
process.env.S3_PUBLIC_BASE_URL
|| process.env.SERVICE_URL_S3
|| '',
|| ''
).trim().replace(/\/$/, '');
if (raw.startsWith('http://') && !/localhost|127\.0\.0\.1/i.test(raw)) {
return `https://${raw.slice('http://'.length)}`;
}
return raw;
})(),
S3_CERTIFICATES_PUBLIC: parseBool(process.env.S3_CERTIFICATES_PUBLIC, false),
S3_DOCUMENTS_PUBLIC: parseBool(process.env.S3_DOCUMENTS_PUBLIC, false),
// SeaweedFS admin console (optional; not used by the S3 SDK client)
+6
View File
@@ -80,6 +80,12 @@ const logS3Error = (label, error) => {
+ (status ? ` (HTTP ${status})` : '')
+ (bodyPreview ? ` body=${bodyPreview.replace(/\s+/g, ' ')}` : '')
);
if (status === 307 || /Temporary Redirect/i.test(String(error.message) + bodyPreview)) {
logger.error(
'[S3 Storage ERROR] Hint: HTTP 307 means S3_ENDPOINT is redirecting (often http→https). '
+ `Set S3_ENDPOINT=https://… (current: ${config.S3_ENDPOINT}). AWS SDK does not follow PUT redirects.`
);
}
};
const uploadToTempBucket = async (fileBuffer, filename, contentType = 'application/octet-stream') => {