diff --git a/.env.example b/.env.example index 46202b553..e7cb93e20 100644 --- a/.env.example +++ b/.env.example @@ -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 diff --git a/backend/app/services/storage_runtime/s3.py b/backend/app/services/storage_runtime/s3.py index 6b569447a..1b6b105d2 100644 --- a/backend/app/services/storage_runtime/s3.py +++ b/backend/app/services/storage_runtime/s3.py @@ -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) @@ -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 diff --git a/deploy/.env.example b/deploy/.env.example index efd6ecb2e..20b1591a5 100644 --- a/deploy/.env.example +++ b/deploy/.env.example @@ -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