diff --git a/doc/self-hosting.md b/doc/self-hosting.md index 1578d1fee..7c8b897f1 100644 --- a/doc/self-hosting.md +++ b/doc/self-hosting.md @@ -144,6 +144,16 @@ Why these knobs: - `providers.ollama.enabled: false` — Puter auto-probes a local Ollama at `127.0.0.1:11434` by default; without one running you'd see `ECONNREFUSED` on every boot. To run a bundled Ollama, see [Optional: local LLM (Ollama)](#optional-local-llm-ollama) below. - `s3.s3Config.forcePathStyle: true` — RustFS / MinIO / fauxqs need path-style URLs (`/`). Real AWS S3 wants virtual-hosted (`.`) — drop this flag (or set `false`) when you swap to real S3. - `s3.s3Config.publicEndpoint` — `endpoint` (`http://s3:9000`) only resolves inside the docker network; presigned upload/download URLs handed to the browser need a host-reachable URL. Caddy routes the `s3.` subdomain to RustFS internally and preserves the Host header end-to-end (required for S3 signature validation), so the browser hits the same port/protocol as the rest of the app — no separate published port, no mixed-content surprises when you turn on TLS. Switch to `https://s3.` once you enable TLS in Step 3. Real AWS S3 doesn't need this — its endpoint is already public; drop the field entirely. +- **Bucket CORS** — the browser uploads file bytes straight to RustFS with a cross-origin `PUT` to the presigned URL (Dev Center deploy, `puter.fs.upload`). That only works if the bucket answers the `OPTIONS` preflight with `Access-Control-Allow-*` headers. The `s3-init` container applies those rules on every boot (`put-bucket-cors`, origins `*` — the signed URL is the credential, and the SDK sends no cookies). If deploys fail with `No 'Access-Control-Allow-Origin' header is present`, re-run it: `docker compose run --rm s3-init`. Verify with: + + ```bash + curl -sk -X OPTIONS "https://s3.//test" \ + -H "Origin: https://" \ + -H "Access-Control-Request-Method: PUT" \ + -H "Access-Control-Request-Headers: content-type" -D - -o /dev/null | grep -i access-control + ``` + + You should see `access-control-allow-origin`, `allow-methods: GET, HEAD, PUT, POST, DELETE`, `allow-headers: *`, and `max-age: 3600`. - `trust_proxy: 1` — Caddy terminates TLS and forwards `X-Forwarded-For`. Without this, `req.ip` is the docker-network address of the Caddy container instead of the real client IP, which breaks rate limiting and IP-based audit logs. `1` = one trusted hop (Caddy). Bump to `2` if you put Cloudflare in front of Caddy; never set `true` (it trusts every hop and makes XFF forgeable). > If you ever change `MARIADB_PASSWORD` after first boot, `.env` alone won't update MariaDB — its credentials are baked into `./puter/data/mariadb/` on first init. Either rotate the password inside MariaDB by hand or `docker compose down && rm -rf ./puter/data/mariadb` to start fresh. diff --git a/docker-compose.yml b/docker-compose.yml index 6ba505f5c..ffba46bee 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -139,8 +139,10 @@ services: start_period: 5s s3-init: - # One-shot container that creates the `puter-local` bucket on first - # boot. Exits 0 once the bucket exists; stays exited 0 thereafter. + # One-shot container that creates the `puter-local` bucket and + # applies its CORS rules on every boot (put-bucket-cors is + # idempotent). Without CORS, browser uploads to presigned URLs + # fail the preflight check. image: amazon/aws-cli:latest container_name: puter-s3-init depends_on: @@ -163,6 +165,26 @@ services: echo "creating bucket $$bucket" aws --endpoint-url "$$endpoint" s3 mb "s3://$$bucket" fi + # Browser uploads (Dev Center deploy, puter.fs.upload) PUT to + # presigned URLs cross-origin, so the bucket must answer the + # OPTIONS preflight. Origins are open because the presigned URL + # itself is the credential and the SDK sends no cookies. + cat > /tmp/cors.json <<'EOF' + { + "CORSRules": [ + { + "AllowedOrigins": ["*"], + "AllowedMethods": ["GET", "HEAD", "PUT", "POST", "DELETE"], + "AllowedHeaders": ["*"], + "ExposeHeaders": ["ETag", "x-amz-request-id"], + "MaxAgeSeconds": 3600 + } + ] + } + EOF + aws --endpoint-url "$$endpoint" s3api put-bucket-cors \ + --bucket "$$bucket" --cors-configuration file:///tmp/cors.json + echo "bucket CORS configured" restart: "no" # ── Optional: local LLM ───────────────────────────────────────────