flysystem-offload/README.md

226 lines
7.8 KiB
Markdown

### Flysystem Offload
Flysystem Offload es un plugin minimalista de WordPress que sustituye el almacenamiento local por un backend remoto usando [Flysystem v3](https://flysystem.thephpleague.com/v3/docs/). Inspirado por el estilo ligero de “S3 Uploads”, se concentra en proporcionar una integración transparente, sin panel de administración, y con compatibilidad total con el flujo estándar de medios de WordPress.
### Características
- Registro temprano del stream wrapper para redirigir `wp-content/uploads` a almacenamiento remoto.
- Generación automática de URLs públicas coherentes con el prefijo de bucket.
- Cabeceras `Cache-Control` y `Expires` aplicadas en las subidas.
- Limpieza remota de adjuntos al eliminarlos desde WordPress.
- Configuración declarativa vía `config/flysystem-offload.php`.
### Requisitos
- WordPress 6.5 o superior.
- PHP 8.2 o superior (probado en PHP 8.4).
- Extensiones PHP: `json`, `curl`, `mbstring`.
- Credenciales válidas para un servicio S3 compatible.
- Composer para instalar dependencias (AWS SDK).
### Instalación
1. Clona el repositorio en `wp-content/plugins/flysystem-offload`.
2. Ejecuta `composer install` dentro del plugin si el SDK no está en el proyecto.
3. Copia `config/flysystem-offload.example.php` a `config/flysystem-offload.php`.
4. Ajusta las claves del archivo de configuración.
5. Activa el plugin desde el dashboard de WordPress o vía `wp plugin activate flysystem-offload`.
### Configuración básica (`config/flysystem-offload.php`)
```php
<?php
return [
'driver' => 's3',
'visibility' => 'public',
'stream' => [
'protocol' => 'flysystem',
'root_prefix' => '',
'host' => 'uploads',
],
'uploads' => [
'base_url' => getenv('FLYSYSTEM_OFFLOAD_BASE_URL') ?: 'https://offload.brasdrive.com.br',
'delete_remote' => true,
'prefer_local_for_missing' => false,
'cache_control' => 'public, max-age=31536000, immutable',
'expires_ttl' => 31536000,
'expires' => null,
],
'admin' => [
'enabled' => false,
],
's3' => [
'key' => getenv('AWS_ACCESS_KEY_ID') ?: null,
'secret' => getenv('AWS_SECRET_ACCESS_KEY') ?: null,
'session_token' => getenv('AWS_SESSION_TOKEN') ?: null,
'region' => getenv('AWS_DEFAULT_REGION') ?: 'us-east-1',
'bucket' => getenv('FLYSYSTEM_OFFLOAD_BUCKET') ?: 'your-bucket-name',
'prefix' => getenv('FLYSYSTEM_OFFLOAD_PREFIX') ?: 'wordpress',
'endpoint' => getenv('FLYSYSTEM_OFFLOAD_ENDPOINT') ?: null,
'use_path_style_endpoint' => (bool) (getenv('AWS_USE_PATH_STYLE_ENDPOINT') ?: false),
'version' => 'latest',
'options' => [],
'default_options' => [],
'acl_public' => 'public-read',
'acl_private' => 'private',
],
];
```
### Normalización de configuración
`Plugin::normaliseConfig()` establece valores por defecto, garantiza que `uploads.base_url` termine sin `/`, y fusiona `s3.prefix`, `stream.root_prefix` y `stream.host` para producir el prefijo remoto final.
### Funcionamiento del stream wrapper
- El filtro `upload_dir` devuelve rutas como `flysystem://uploads/2025/01`.
- `MediaHooks` reescribe automáticamente `wp_get_attachment_url` para apuntar a la ruta remota (`base_url + prefijo + archivo`).
- El prefijo remoto resultante combina `s3.prefix`, `stream.root_prefix` y `stream.host` para reflejar exactamente la clave almacenada en el bucket (ej.: `wordpress/uploads/2025/01/file.jpg`).
### Cabeceras de caché
El adaptador S3 añade `Cache-Control` y `Expires` a través de las opciones por defecto de Flysystem:
```php
$defaultOptions['CacheControl'] = 'public, max-age=31536000, immutable';
$defaultOptions['Expires'] = gmdate('D, d M Y H:i:s \G\M\T', time() + 31536000);
```
Puede personalizarse mediante el filtro `flysystem_offload_s3_default_write_options`.
### Escenario bucket público (probado con IDrive e2)
1. Marcar bucket como público en la consola de IDrive e2.
2. Aplicar policy simple (la UI sólo acepta una versión sin `Condition`).
```json
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowPublicReadUploads",
"Effect": "Allow",
"Principal": { "AWS": "*" },
"Action": [
"s3:GetObject"
],
"Resource": [
"arn:aws:s3:::flysystem-offload/wordpress/uploads/*"
]
}
]
}
```
3. Configurar CORS si el panel o el frontend cargan las imágenes desde otro origen.
```json
[
{
"AllowedOrigins": [
"https://offload.brasdrive.com.br"
],
"AllowedMethods": [
"GET",
"HEAD"
],
"AllowedHeaders": [
"*"
],
"ExposeHeaders": [
"ETag",
"Last-Modified",
"x-amz-request-id",
"x-amz-id-2"
],
"AllowCredentials": false,
"MaxAgeSeconds": 86400
}
]
```
### Escenario recomendado: bucket privado + Cloudflare
1. **Bucket privado**
- Mantenerlo cerrado; permitir `s3:GetObject` sólo a IPs de Cloudflare.
- Aplicar policy vía CLI (`aws s3api put-bucket-policy`). Ejemplo con `Condition` por IP (mantén la lista actualizada con la API `GET /client/v4/ips`):
```json
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowCloudflareAccess",
"Effect": "Allow",
"Principal": { "AWS": "*" },
"Action": [
"s3:GetObject"
],
"Resource": [
"arn:aws:s3:::flysystem-offload/wordpress/uploads/*"
],
"Condition": {
"IpAddress": {
"aws:SourceIp": [
"173.245.48.0/20",
"103.21.244.0/22"
// ...
]
}
}
}
]
}
```
2. **Cloudflare**
- CNAME `offload.brasdrive.com.br` → endpoint IDrive (ej.`u8k6.va11.idrivee2-9.com`).
- Proxy naranja activado; SSL universal.
- Reglas WAF o Rate Limiting opcionales.
3. **WordPress**
- `uploads.base_url = 'https://offload.brasdrive.com.br'`.
- Mantener `prefer_local_for_missing = false`.
4. **Automatización**
- Script/cron que consulte la API de Cloudflare, regenere la policy con los nuevos rangos IP y la aplique (controlando con `etag`).
5. **Caché y purgas**
- Cloudflare respeta las cabeceras `Cache-Control` y `Expires`.
- Purgar desde la consola de Cloudflare cuando sea necesario (por URL o caché completa).
### Compatibilidad de backends
Actualmente Flysystem Offload proporciona el adaptador **S3**. El código está preparado para admitir otros backends soportados por Flysystem (WebDAV, SFTP, etc.), pero aún deben agregarse adaptadores específicos (`WebdavAdapter`, `SftpAdapter`, etc.) y sus respectivas configuraciones. Esto forma parte del plan inmediato de ampliación: se crearán clases adicionales en `src/Filesystem/Adapters/`, se extenderá la factoría de adaptadores y se actualizará la documentación para cada backend.
### Notas sobre IDrive e2
- Para habilitar un bucket público reciente, es necesario contactar soporte.
- El editor web de políticas no soporta `Condition`; aplicar policies complejas via CLI/API.
- CORS sí puede configurarse desde la UI mientras se respete el formato simple.
- Usa el endpoint regional del bucket como `endpoint` en configuración y CLI.
### Solución de problemas
- URLs sin prefijo: actualizar a la versión reciente de `MediaHooks.php`.
- Confirmar `s3.prefix`, `stream.root_prefix`, `stream.host`.
- Falta de `Cache-Control`/`Expires`: revisar `config` y `s3.default_options`.
- Error `_.filter` en la UI: bug del formulario; usar CLI.
- Archivos inaccesibles con bucket privado: verificar policy, rangos Cloudflare, CNAME activo.
### Contribuciones
Se aceptan PRs. Mantén el enfoque minimalista, sin panel de administración, y prioriza compatibilidad con el flujo nativo de WordPress. Roadmap cercano: añadir adaptadores para otros drivers Flysystem.
### Licencia
MIT. Revisa el archivo `LICENSE`.