Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,15 @@ FEISHU_REDIRECT_URI=http://localhost:3000/auth/feishu/callback
# S3_MAX_POOL_CONNECTIONS=50
# S3_WRITE_WORKERS=32

# Google Cloud Storage (S3-compatible API) — set these instead of MinIO values:
# STORAGE_BACKEND=s3
# S3_BUCKET=your-gcs-bucket-name
# S3_REGION=auto
# S3_ENDPOINT_URL=https://storage.googleapis.com
# S3_ACCESS_KEY_ID=your-hmac-access-key
# S3_SECRET_ACCESS_KEY=your-hmac-secret
# S3_PREFIX=agents

# Local MinIO settings used by docker-compose.multi-instance.yml.
# Change the password before exposing MinIO outside local development.
MINIO_ROOT_USER=clawith
Expand Down
9 changes: 7 additions & 2 deletions backend/app/services/storage_runtime/s3.py
Original file line number Diff line number Diff line change
Expand Up @@ -177,13 +177,16 @@ async def read_bytes(self, key: str) -> bytes:
return await asyncio.to_thread(body.read)

async def write_bytes(self, key: str, data: bytes, content_type: str | None = None) -> None:
# GCS S3-compatible API requires an explicit Content-Type; without it
# the V4 signature body-hash is calculated on an empty content-type,
# but GCS applies a different default — causing SignatureDoesNotMatch.
resolved_ct = content_type or "application/octet-stream"
kwargs: dict[str, Any] = {
"Bucket": self.bucket,
"Key": self._object_key(key),
"Body": data,
"ContentType": resolved_ct,
}
if content_type:
kwargs["ContentType"] = content_type
async with self._async_client() as client:
await client.put_object(**kwargs)

Expand Down Expand Up @@ -302,8 +305,10 @@ async def presign_download_url(self, key: str, filename: str | None = None, inli
parsed_url = urlparse(url)
parsed_endpoint = urlparse(self.endpoint_url)
if parsed_url.netloc == parsed_endpoint.netloc:
# MinIO-style endpoint: rewrite path with /minio prefix
new_path = "/minio" + parsed_url.path
url = urlunparse(("", "", new_path, parsed_url.params, parsed_url.query, parsed_url.fragment))
# GCS (storage.googleapis.com): presigned URLs are already correct, no rewrite needed
return url


Expand Down
9 changes: 9 additions & 0 deletions deploy/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,15 @@ FEISHU_REDIRECT_URI=http://localhost:3000/auth/feishu/callback
# S3_MAX_POOL_CONNECTIONS=50
# S3_WRITE_WORKERS=32

# Google Cloud Storage (S3-compatible API) — set these instead of MinIO values:
# STORAGE_BACKEND=s3
# S3_BUCKET=your-gcs-bucket-name
# S3_REGION=auto
# S3_ENDPOINT_URL=https://storage.googleapis.com
# S3_ACCESS_KEY_ID=your-hmac-access-key
# S3_SECRET_ACCESS_KEY=your-hmac-secret
# S3_PREFIX=agents


API_PORT=8000

Expand Down