Pular para conteúdo

Provedor Garage — arquivos (bucket + key + presign)

Status: Aprovado · Responsável: Gustavo Madruga · Atualizado em: 2026-07-03

O provider de arquivos da Central de Apps: por app, provisiona 1 bucket (globalAlias = app_id) + 1 access key read/write escopada, no cluster Garage (S3-compatível, Admin API v2). O app acessa os arquivos por URLs presignadas que o auth assina (broker-signed) — a write-key nunca sai do servidor. Porquê e alternativas: decisão 0012.

Envs no recurso auth (Coolify)

Lidas em runtime pelo auth (não build). Marque o token como Secret.

Variável Valor Secret?
GARAGE_ADMIN_URL Admin API interna do Garage (ex. http://garage:3903) ou https://arquivo-admin.xadm.biz não
GARAGE_ADMIN_TOKEN admin token (o GARAGE_ADMIN_TOKEN/SERVICE_PASSWORD_GARAGE do cluster) sim ✅
GARAGE_S3_ENDPOINT endpoint S3 (ex. https://arquivo-api.xadm.biz) — usado no presign não
GARAGE_S3_REGION região SigV4 (s3_region do garage.toml, ex. garage) não

Ausentes → o provider recusa provisionar com erro claro (não quebra o boot). O cluster precisa de GARAGE_ALLOW_WORLD_READABLE_SECRETS=true para o auth re-obter o secret na re-provisão (GetKeyInfo?showSecretKey=true).

Provisão (idempotente)

Disparada pelo /xadm-setup (feature garage) → POST /api/setup/provision. O provider:

  1. GET /v2/GetBucketInfo?globalAlias=<app_id> — acha o bucket, ou POST /v2/CreateBucket.
  2. Reusa a key associada ao bucket (GetKeyInfo?showSecretKey=true) — ou POST /v2/CreateKey + POST /v2/AllowBucketKey (read+write, owner:false).
  3. Grava client_config = {endpoint, bucket, region} (→ app.json) e o secretAccessKey cifrado em secret_ref. Re-rodar reusa bucket+key (sem duplicar).

Acesso a arquivos (presign broker-signed)

O app (com JWT de usuário do auth, claim oauth_app_id) chama:

GET /api/apps/garage/presign?app_id=<app>&op=GET|PUT&key=<objeto>
Authorization: Bearer <JWT do usuário>
→ 200 { "url": "<presigned SigV4, 10 min>", "expires_in": 600 }
  • op=GET → download; op=PUT → upload. A URL vale ~10 min.
  • Autz: app_id tem que casar com o oauth_app_id do token → 403 cross-app. Token sem oauth_app_id → 403. Sem token → 401. App sem Garage provisionado → 404.

Diagnóstico

  • Provisão falha → a resposta do /provision traz providers[].last_error (ex. Garage GetBucketInfo HTTP 401 = token inválido; Garage inacessível = URL/rede).
  • Presign 404 → o par (app, garage) não está ligada no catálogo (rodar o setup).
  • Presign dá URL mas o objeto não abre → conferir GARAGE_S3_ENDPOINT/GARAGE_S3_REGION (região SigV4 tem que casar com o garage.toml) e se o cluster aceita path-style.

Acesso S3 direto (app server/web)

Para app server/web (tem production_url → recurso Coolify), o broker injeta a write-key no recurso do app, além do client-grade — o app fala S3 direto (sem o round-trip de presign). Envs que o app recebe:

Env injetada Origem Secret?
GARAGE_ENDPOINT client_config.endpoint não
GARAGE_BUCKET client_config.bucket (= app_id) não
GARAGE_REGION client_config.region (SigV4) não
GARAGE_ACCESS_KEY_ID resource_ref.access_key_id não
GARAGE_SECRET_ACCESS_KEY secret_ref (decifrada no ato) sim ✅ (mascarada por is_shown_once)

O app configura um cliente S3 (path-style, endpoint override) com essas envs. A key é per-app, escopada só ao bucket do app (read+write, sem owner). Rotação: re-rodar /xadm-setup (re-injeta) + reiniciar o recurso. O secret entra só no Coolify, nunca no app.json.

Mobile/local (sem production_url) não recebe a write-key → usa o presign broker-signed (acima), com o segredo só no auth. Presign para device, S3 direto para server — coexistem. Ver decisão 0012.