diff --git a/README.md b/README.md index 26f2171..9b0caa9 100644 --- a/README.md +++ b/README.md @@ -1,116 +1,225 @@ -# Flysystem Offload — Almacenamiento universal para WordPress +### Flysystem Offload -Flysystem Offload sustituye el sistema de archivos local de WordPress por un backend remoto operado con Flysystem v3. Los medios se suben, sirven y eliminan directamente desde el proveedor seleccionado (S3 y compatibles en la primera versión) sin modificar el flujo editorial. +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 +### Características -- **Proveedor seleccionable:** Amazon S3 y endpoints compatibles (MinIO, DigitalOcean Spaces, Wasabi, etc.). -- **Integración transparente:** hooks de `upload_dir`, `wp_get_attachment_url`, stream wrapper `fly://` y borrado sincronizado. -- **Arquitectura modular:** preparada para añadir SFTP, WebDAV y otros adaptadores en iteraciones futuras. -- **Panel de ajustes:** selector de proveedor y credenciales gestionadas desde la administración. - - ## Requisitos - -- PHP 8.0+ -- WordPress 6.0+ -- Extensiones PHP: `curl`, `mbstring`, `xml` -- Acceso a Composer durante la construcción del paquete (o usar la imagen Docker proporcionada) -- Credenciales válidas de S3 o servicio compatible - - ## Instalación - - ### Opción A · Proyecto existente de WordPress - +- 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`. -1. Clona este repositorio dentro del árbol de tu sitio: - `git clone https://git.brasdrive.com.br/Brasdrive/flysystem-offload.git` -2. Entra en la carpeta del plugin y ejecuta Composer para traer las dependencias: - - ```bash - cd flysystem-offload - composer install --no-dev --optimize-autoloader - ``` - -3. Empaqueta el plugin con la carpeta `vendor/` incluida y súbelo a `/wp-content/plugins/` del sitio que corresponda (vía SCP, rsync o panel de hosting). -4. Activa **Flysystem Offload** desde **Plugins > Plugins instalados** en el escritorio de WordPress. - - ### Opción B · Imagen Docker (multi-stage) - - El repositorio incluye un Dockerfile que construye una imagen basada en `wordpress:6.8.3-php8.4-apache` y prepara el plugin en tiempo de build: - - ```Dockerfile - # Etapa 1: composer:2.8.12 instala las dependencias en /app/wp-content/plugins/flysystem-offload - # Etapa 2: copia el plugin con vendor/ dentro de /usr/src/wordpress y /var/www/html - # Instala redis, WP-CLI y algunas utilidades - # Habilita módulos de Apache y carga configuraciones personalizadas de PHP/Apache - ``` - - Pasos para usarla: - - ```bash - # Desde la raíz del repositorio - docker build -t flysystem-offload-wp . - # Arranca el contenedor exponiendo el puerto 80 (puedes convertirlo en un stack Compose si lo prefieres) - docker run --rm -p 8080:80 flysystem-offload-wp - ``` - - La imagen resultante ya contiene: +### Requisitos -- El plugin con todas sus dependencias PHP en `/usr/src/wordpress/wp-content/plugins/flysystem-offload`. -- Copia pre-sincronizada en `/var/www/html/wp-content/plugins` para que esté disponible desde el primer arranque. -- Extensión Redis habilitada, WP-CLI disponible y módulos `rewrite`, `headers`, `expires`, `deflate` activos. - - ### Nota sobre Composer - - En entornos que no usan Docker, asegúrate de ejecutar `/composer install` antes de empaquetar o desplegar. WordPress no ejecuta Composer automáticamente durante la activación de un plugin. - - ## Configuración inicial (S3 / compatible) - +- 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). -1. En el escritorio de WordPress abre **Ajustes > Flysystem Offload**. -2. Selecciona **Amazon S3 / Compatible**. -3. Completa los campos: - - Access Key - - Secret Key (permanece oculta tras guardarla) - - Región (`us-east-1`, `eu-west-1`, etc.) - - Bucket - - Prefijo opcional (subcarpeta dentro del bucket) - - Endpoint personalizado (solo para servicios compatibles con S3) - - URL CDN opcional (sustituye la URL pública del bucket por tu dominio CDN) -4. Guarda los cambios. El plugin reconstruye automáticamente el filesystem y el stream wrapper. - **Prefijo base:** Puedes definir un prefijo global (`wordpress/uploads/`) que se añadirá a todas las rutas remotas antes de delegar en el adaptador. - - ## Flujo de funcionamiento - +### Instalación -- WordPress sigue usando `wp_handle_upload()`. -- Los filtros de `upload_dir` cambian `basedir` a `fly://...`. -- El stream wrapper reenvía lecturas/escrituras a Flysystem y este al cliente S3. -- `wp_get_attachment_url` reescribe la URL base con el dominio del bucket o el CDN configurado. -- Al eliminar un adjunto, se borran el archivo principal y sus derivadas desde el almacenamiento remoto. - - ## Roadmap inmediato - -- Campos y validaciones para SFTP y WebDAV. -- Health check vía WP-CLI. -- Herramientas de migración para copiar la biblioteca existente al proveedor remoto. -- Adaptadores adicionales (GCS, Azure Blob) y conectores OAuth (Drive, OneDrive, Dropbox). - - ## Contribuir - +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`. -1. Haz fork y crea una rama (`feature/tu-feature`). -2. Sigue los [WordPress Coding Standards](https://developer.wordpress.org/coding-standards/). -3. Ejecuta tests si están disponibles y actualiza la documentación si corresponde. -4. Abre un Pull Request describiendo el cambio. +### Configuración básica (`config/flysystem-offload.php`) -- Documentación: Este archivo. -- Issues/Contacto: jdavidcamejo@gmail.com +```php + 's3', + 'visibility' => 'public', - ## Licencia - - GPL v2. Consulta [LICENSE](LICENSE) para más detalles. + '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, + ], -Desarrollado por [Brasdrive](https://brasdrive.com.br). \ No newline at end of file + '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`.