El servidor Almacenamiento: S3, GCS, Azure y disco
Almacenamiento: S3, GCS, Azure y disco
En esta página
La clave
En Cloudflare R2: R2 → Create bucket, luego Manage API tokens → Create API token con “Object Read & Write” solo para ese bucket.
# The S3 API of the account, not the public address of the bucket (r2.dev)
S3_ENDPOINT=https://<account-id>.r2.cloudflarestorage.com
S3_BUCKET=acme-files
S3_REGION=auto
S3_ACCESS_KEY_ID=…
S3_SECRET_ACCESS_KEY=…
# Optional: before every key, to share the bucket with something else
S3_PREFIX=files/
# What tokens are signed with: long, random, the same in every process
REBOFILES_SECRET=…s3Storage(s3OptionsFrom(process.env)) las lee. En AWS, omite S3_ENDPOINT y da la región; MinIO y Backblaze usan las mismas variables.
La regla de orígenes
En Settings → CORS policy, con las direcciones desde las que se sirve tu aplicación. ETag tiene que estar expuesto.
[
{
"AllowedOrigins": ["https://your-app.com", "http://localhost:3000"],
"AllowedMethods": ["GET", "PUT"],
"AllowedHeaders": ["content-type"],
"ExposeHeaders": ["ETag"],
"MaxAgeSeconds": 3600
}
]Una regla de ciclo de vida que aborta las subidas multiparte sin terminar tras un día evita que las partes de una subida nunca terminada se queden en el bucket.
Google Cloud Storage
Por su API XML, que habla S3: las mismas direcciones, firmadas con una clave HMAC del bucket.
import { gcsOptionsFrom, gcsStorage } from '@rebofiles/server';
// An HMAC key of the bucket: Cloud Storage → Settings → Interoperability.
// GCS_BUCKET, GCS_ACCESS_KEY_ID (GOOG1…), GCS_SECRET_ACCESS_KEY, GCS_PREFIX
createDriveHandler({ ...options, storage: gcsStorage(gcsOptionsFrom(process.env)) });Lo que esa API no hace se hace de otra forma: cada clave se borra por separado, y se envían todas las partes de una versión nueva, porque no copia ninguna. El CORS del bucket, puesto con gcloud storage buckets update --cors-file, tiene que exponer ETag.
Azure Blob Storage
Un contenedor de una cuenta de almacenamiento: direcciones firmadas con la clave de la cuenta (una SAS de cada blob, por minutos), cada parte un bloque, y el archivo hecho de su lista de bloques.
import { azureOptionsFrom, azureStorage } from '@rebofiles/server';
// AZURE_STORAGE_ACCOUNT, AZURE_STORAGE_KEY, AZURE_STORAGE_CONTAINER, AZURE_STORAGE_PREFIX
createDriveHandler({ ...options, storage: azureStorage(azureOptionsFrom(process.env)) });- Subidas retomadas a partir de los bloques que lista Azure, y una versión nueva enviada solo con las partes que cambiaron (Put Block From URL).
- Los bloques de una subida que nunca terminó se van solos a la semana: no hay nada que barrer.
- El CORS de la cuenta (Uso compartido de recursos) deja entrar los orígenes de tus páginas, con
PUT,GETyHEAD; no hace falta exponer ninguna cabecera, porque los bloques se juntan por su número. - Probado contra Azurite, el emulador de Microsoft, en los tests del servidor.
En el disco
diskStorage({ folder, secret }) guarda los bytes en una carpeta, y el propio servidor los transporta, a direcciones suyas: para una sola máquina, una demo o un test.
Cuando algo falla
S3_ENDPOINT is the public address of the bucket (r2.dev)
La API S3 de R2 está en https://<account-id>.r2.cloudflarestorage.com, en la página de ajustes del bucket.
The part could not be sent. If the bytes go to a bucket, check that it lets this origin in.
La regla de orígenes: falta la dirección de la página en AllowedOrigins, o content-type en AllowedHeaders.
The bucket did not let the page read the ETag of a part.
Los archivos pequeños suben y los grandes no: ExposeHeaders necesita ETag.
The bucket answered 403 to …
La clave: no puede escribir en ese bucket, o el endpoint, la región o el nombre del bucket no son para los que se hizo.
There is no room for it: … are used, and it is ….
La cuota de la persona (413 over-quota): más espacio en los ajustes del drive, o menos en sus archivos y su papelera.
En el repositorio, npm run storage:check hace todo el recorrido de un archivo contra el bucket de apps/storage/.env y dice qué respondió el bucket en el paso que falló.