From 56f09b50010aa0b01b7a1a1d900437cbefee7059 Mon Sep 17 00:00:00 2001 From: Michael Schiemer Date: Mon, 3 Nov 2025 23:54:27 +0100 Subject: [PATCH] docs(cache): add comprehensive cache configuration and permission handling guides - Introduce `cache-configuration.md` for detailed instructions on cache setup, permission troubleshooting, and best practices. - Add `cache-permissions-quick-fix.md` for concise resolutions to common permission errors. - Include a detailed `FILECACHE_PERMISSION_FIX_PLAN.md` outlining solutions for permission-related issues. - Enhance `docker-entrypoint.sh` with permission fixes for multi-user caches. - Update `Makefile` with cache clear commands for local and staging environments. - Improve `FileCache` for graceful degradation on permission errors, ensuring reliability under multi-user scenarios. --- Makefile | 12 +- docker/php/docker-entrypoint.sh | 2 + docs/FILECACHE_PERMISSION_FIX_PLAN.md | 217 ++++++++++++++++ docs/deployment/README.md | 1 + docs/deployment/cache-configuration.md | 244 ++++++++++++++++++ .../deployment/cache-permissions-quick-fix.md | 74 ++++++ src/Framework/Cache/Driver/FileCache.php | 60 ++++- src/Framework/Filesystem/FileStorage.php | 11 +- 8 files changed, 610 insertions(+), 11 deletions(-) create mode 100644 docs/FILECACHE_PERMISSION_FIX_PLAN.md create mode 100644 docs/deployment/cache-configuration.md create mode 100644 docs/deployment/cache-permissions-quick-fix.md diff --git a/Makefile b/Makefile index 8d179c01..f6805769 100644 --- a/Makefile +++ b/Makefile @@ -276,6 +276,16 @@ logs-staging-php: ## Show PHP application logs from staging-app (log files) @echo "📋 Showing PHP application logs from staging-app..." @ssh -i ~/.ssh/production deploy@94.16.110.151 "docker exec -i staging-app tail -f /var/www/html/storage/logs/*.log 2>/dev/null || docker exec -i staging-app ls -la /var/www/html/storage/logs/ 2>/dev/null || echo 'Log directory /var/www/html/storage/logs/ not accessible'" +cache-clear-staging: ## Clear cache on staging server + @echo "🗑️ Clearing cache on staging server..." + @ssh -i ~/.ssh/production deploy@94.16.110.151 "cd ~/deployment/stacks/staging && docker compose exec staging-app php console.php cache:clear" + @echo "✅ Cache cleared on staging server" + +cache-clear: ## Clear cache locally + @echo "🗑️ Clearing cache locally..." + docker exec php php console.php cache:clear + @echo "✅ Cache cleared locally" + # SSL Certificate Management (PHP Framework Integration) ssl-init: ## Initialize Let's Encrypt certificates @echo "🔒 Initializing SSL certificates..." @@ -393,4 +403,4 @@ env-validate: ## Validiert ENV-Files (Base+Override Pattern) fi @echo "💡 Framework lädt: .env.base → .env.local → System ENV" -.PHONY: up down build restart logs ps phpinfo deploy setup clean clean-coverage status fix-ssh-perms setup-ssh setup-autossh ssh ssh-production ssh-git ssh-status ssh-logs test test-coverage test-coverage-html test-unit test-framework test-domain test-watch test-parallel test-profile test-filter security-check security-audit-json security-check-prod update-production restart-production deploy-production-quick status-production logs-production logs-staging logs-staging-php ssl-init ssl-init-staging ssl-test ssl-renew ssl-status ssl-backup push-staging env-base env-local env-check env-validate +.PHONY: up down build restart logs ps phpinfo deploy setup clean clean-coverage status fix-ssh-perms setup-ssh setup-autossh ssh ssh-production ssh-git ssh-status ssh-logs test test-coverage test-coverage-html test-unit test-framework test-domain test-watch test-parallel test-profile test-filter security-check security-audit-json security-check-prod update-production restart-production deploy-production-quick status-production logs-production logs-staging logs-staging-php cache-clear-staging cache-clear ssl-init ssl-init-staging ssl-test ssl-renew ssl-status ssl-backup push-staging env-base env-local env-check env-validate diff --git a/docker/php/docker-entrypoint.sh b/docker/php/docker-entrypoint.sh index e9928c16..1c317f42 100644 --- a/docker/php/docker-entrypoint.sh +++ b/docker/php/docker-entrypoint.sh @@ -27,6 +27,8 @@ mkdir -p /var/www/html/storage/analytics 2>/dev/null || true mkdir -p /var/www/html/storage/sessions 2>/dev/null || true # Fix ownership for all storage directories (including mounted volumes) +# WICHTIG: Cache-Verzeichnis ben?tigt 775 (Group-writable) f?r Multi-User/Process-Umgebungen +# F?r das L?schen von Cache-Dateien werden nur Verzeichnis-Rechte ben?tigt, nicht Datei-Rechte if [ -d /var/www/html/storage ]; then chown -R appuser:appuser /var/www/html/storage 2>/dev/null || true chmod -R 775 /var/www/html/storage 2>/dev/null || true diff --git a/docs/FILECACHE_PERMISSION_FIX_PLAN.md b/docs/FILECACHE_PERMISSION_FIX_PLAN.md new file mode 100644 index 00000000..d7889d20 --- /dev/null +++ b/docs/FILECACHE_PERMISSION_FIX_PLAN.md @@ -0,0 +1,217 @@ +# FileCache Permission Problem - L?sungsplan + +## Problem-Analyse + +### Fehler +``` +PHP Fatal error: Uncaught App\Framework\Filesystem\Exceptions\FilePermissionException [FS007]: +Permission denied for delete on file: var/www/html/storage/cache/8993618a07a0f5ee371a4d3b029abf09_1762296020.cache.php +(File is not writable) +``` + +### Root Cause + +1. **Bug in `FileStorage::delete()`** (Zeile 222): + - Die Methode pr?ft `is_writable($resolvedPath)` auf die Datei selbst + - **FALSCH**: F?r das L?schen einer Datei braucht man nur Schreibrechte im **Verzeichnis**, nicht auf der Datei selbst! + - Unix-Regel: `unlink()` ben?tigt nur Schreibrechte im Parent-Verzeichnis + +2. **Cache-Dateien werden mit 0644 erstellt**: + - Owner: `rw-` (read-write) + - Group: `r--` (read-only) + - Others: `r--` (read-only) + - Problem: Wenn verschiedene Prozesse/User Dateien erstellen, k?nnen sie Dateien des anderen nicht l?schen (Owner-Mismatch) + +3. **Staging-Server Infrastruktur**: + - M?glicherweise werden Cache-Dateien von verschiedenen Usern/Prozessen erstellt + - PHP-Prozess l?uft als anderer User als der, der die Dateien erstellt hat + +## L?sungsplan + +### L?sung 1: Bug-Fix in `FileStorage::delete()` (H?CHSTE PRIORIT?T) + +**Problem**: Falsche Pr?fung - Datei muss nicht beschreibbar sein zum L?schen + +**Fix**: Entferne `is_writable()` Pr?fung auf die Datei selbst + +```php +public function delete(string $path): void +{ + $resolvedPath = $this->resolvePath($path); + + // ... existing validation ... + + if (! is_file($resolvedPath)) { + throw new FileNotFoundException($path); + } + + // Pr?fe Directory-Permissions (DAS IST WICHTIG!) + $dir = dirname($resolvedPath); + if (! is_writable($dir)) { + throw FilePermissionException::delete($path, 'Directory is not writable: ' . $dir); + } + + // ENTFERNT: Pr?fe File-Permissions + // F?r unlink() braucht man nur Verzeichnis-Rechte! + // if (! is_writable($resolvedPath)) { + // throw FilePermissionException::delete($path, 'File is not writable'); + // } + + if (! @unlink($resolvedPath)) { + $error = error_get_last(); + if ($error && str_contains($error['message'], 'Permission denied')) { + throw FilePermissionException::delete($path, $error['message']); + } + + throw new FileDeleteException($path); + } + + // ... logging ... +} +``` + +**Warum**: Unix `unlink()` ben?tigt nur Schreibrechte im Parent-Verzeichnis, nicht auf der Datei selbst. + +--- + +### L?sung 2: Graceful Degradation in FileCache (HOHE PRIORIT?T) + +**Problem**: FileCache wirft Fatal Error bei Permission-Exceptions + +**Fix**: Catch FilePermissionException in FileCache-Methoden + +**Betroffene Stellen**: +1. `getSingleKey()` - beim L?schen abgelaufener Dateien +2. `set()` - beim L?schen alter Cache-Dateien +3. `forget()` - beim expliziten L?schen +4. `clear()` - beim L?schen aller Cache-Dateien + +**Implementierung**: +```php +// In getSingleKey() +if ($expiresAt > 0 && $expiresAt < time()) { + try { + $this->fileSystem->delete($fullPath); + } catch (\App\Framework\Filesystem\Exceptions\FilePermissionException $e) { + // Permission denied - continue (graceful degradation) + // Datei wird beim n?chsten Cleanup gel?scht + continue; + } catch (\Throwable $e) { + // Other errors - continue + continue; + } +} + +// In set(), forget(), clear() - ?hnlich +``` + +**Vorteil**: Cache funktioniert weiter, auch wenn einzelne Dateien nicht gel?scht werden k?nnen. + +--- + +### L?sung 3: Optionale Auto-Fix vor L?schen (NIEDRIGE PRIORIT?T) + +**Option**: Versuche Berechtigungen zu fixen, bevor gel?scht wird + +**Implementierung**: +```php +private function tryDeleteWithFix(string $path): bool +{ + try { + $this->fileSystem->delete($path); + return true; + } catch (\App\Framework\Filesystem\Exceptions\FilePermissionException $e) { + // Versuche Berechtigungen zu fixen + try { + // Versuche chmod auf 0644 (falls m?glich) + @chmod($path, 0644); + // Versuche erneut zu l?schen + $this->fileSystem->delete($path); + return true; + } catch (\Throwable $e2) { + // Auto-fix fehlgeschlagen + return false; + } + } +} +``` + +**Warnung**: Funktioniert nur, wenn der Prozess Owner oder root ist. Sonst wird `chmod()` fehlschlagen. + +--- + +### L?sung 4: Infrastruktur-Fix f?r Staging (MITTEL PRIORIT?T) + +**Problem**: Cache-Dateien werden von verschiedenen Usern/Prozessen erstellt + +**L?sungen**: + +#### Option A: Fix Berechtigungen im Entrypoint-Script +```bash +# In docker/php/docker-entrypoint.sh +# Nach Volume-Mounting: +chown -R appuser:appuser /var/www/html/storage/cache 2>/dev/null || true +chmod -R 775 /var/www/html/storage/cache 2>/dev/null || true +``` + +#### Option B: Fix Berechtigungen bei Cache-Erstellung +```php +// In FileStorage::put() - nach dem Schreiben +// Stelle sicher, dass Owner/Group korrekt ist +if (function_exists('posix_geteuid')) { + $currentUid = posix_geteuid(); + @chown($resolvedPath, $currentUid); +} +``` + +#### Option C: Verwendung von Group-Writable Permissions +```php +// In FileStorage::put() - statt 0644 +$this->permissions->setPermissions( + $path, + ValueObjects\FilePermissions::readWriteOwnerReadGroupWritable() // 0664 +); +``` + +**Empfehlung**: Option A + Option C kombiniert + +--- + +## Implementierungsreihenfolge + +1. ? **L?sung 1** (Bug-Fix) - SOFORT + - Behebt das Hauptproblem + - Semantisch korrekt (Unix-Verhalten) + +2. ? **L?sung 2** (Graceful Degradation) - SOFORT + - Verhindert Fatal Errors + - Cache bleibt funktionsf?hig + +3. ?? **L?sung 4** (Infrastruktur) - NACH Code-Fix + - Verhindert zuk?nftige Probleme + - Dokumentiere in `docs/deployment/` + +4. ?? **L?sung 3** (Auto-Fix) - NUR WENN N?TIG + - Kann Probleme verschlimmern, wenn nicht richtig implementiert + - Nur verwenden, wenn L?sungen 1+2+4 nicht ausreichen + +## Testing + +Nach Implementierung testen: + +1. **Unit-Test**: `delete()` sollte funktionieren, auch wenn Datei nicht beschreibbar ist +2. **Integration-Test**: FileCache sollte funktionieren, auch wenn einige Dateien nicht gel?scht werden k?nnen +3. **Staging-Test**: Cache-Operationen sollten keine Fatal Errors mehr werfen + +## Dokumentation + +? **Dokumentation erstellt**: +- ? `docs/deployment/cache-configuration.md` - Vollst?ndige Cache-Konfigurations-Dokumentation +- `docs/claude/error-handling.md` - Cache-specific error handling (optional) + +## Status + +? **L?sung 1**: Bug-Fix in `delete()` - IMPLEMENTIERT +? **L?sung 2**: Graceful Degradation in FileCache - IMPLEMENTIERT +? **L?sung 4**: Infrastruktur-Dokumentation - IMPLEMENTIERT +?? **L?sung 3**: Auto-Fix mit chmod() - NUR WENN N?TIG diff --git a/docs/deployment/README.md b/docs/deployment/README.md index ea941026..701cd526 100644 --- a/docs/deployment/README.md +++ b/docs/deployment/README.md @@ -26,6 +26,7 @@ Die folgenden Dokumente behandeln spezifische Deployment-Themen: ### Configuration & Setup - **[database-migration-strategy.md](database-migration-strategy.md)** - Database Migration Strategy +- **[cache-configuration.md](cache-configuration.md)** - Cache Configuration & Permissions - **[logging-configuration.md](logging-configuration.md)** - Logging Configuration - **[production-logging.md](production-logging.md)** - Production Logging Best Practices - **[secrets-management.md](secrets-management.md)** - Secrets Management mit Vault diff --git a/docs/deployment/cache-configuration.md b/docs/deployment/cache-configuration.md new file mode 100644 index 00000000..6b31ed0e --- /dev/null +++ b/docs/deployment/cache-configuration.md @@ -0,0 +1,244 @@ +# Cache Configuration & Permissions + +## ?bersicht + +Diese Dokumentation beschreibt die Konfiguration und Berechtigungsverwaltung f?r das FileCache-System auf Staging und Production-Servern. + +## Problem-Background + +Das FileCache-System speichert Cache-Dateien in `/var/www/html/storage/cache`. In Multi-User/Process-Umgebungen k?nnen Permission-Probleme auftreten, wenn: + +- Cache-Dateien von verschiedenen Prozessen/Usern erstellt werden +- Owner/Group-Mismatch zwischen erstellenden und l?schenden Prozessen +- Falsche Berechtigungen auf Cache-Dateien oder Verzeichnissen + +### Gel?ste Probleme + +? **Bug-Fix**: `FileStorage::delete()` pr?ft jetzt nur noch Verzeichnis-Rechte (nicht Datei-Rechte) +? **Graceful Degradation**: FileCache f?ngt Permission-Exceptions und arbeitet weiter +? **Robuste Fehlerbehandlung**: Cache funktioniert auch bei Permission-Problemen + +## Berechtigungen + +### Verzeichnis-Berechtigungen + +Das Cache-Verzeichnis sollte folgende Berechtigungen haben: + +```bash +# Verzeichnis: 775 (rwxrwxr-x) +# - Owner: rwx (read, write, execute) +# - Group: rwx (read, write, execute) +# - Others: r-x (read, execute) +chmod 775 /var/www/html/storage/cache +``` + +### Datei-Berechtigungen + +Cache-Dateien werden mit folgenden Berechtigungen erstellt: + +```php +// 0644 (rw-r--r--) +// - Owner: rw- (read, write) +// - Group: r-- (read) +// - Others: r-- (read) +``` + +**Wichtig**: F?r das L?schen von Dateien werden nur **Verzeichnis-Rechte** ben?tigt, nicht Datei-Rechte! + +## Docker Entrypoint-Script + +Das Entrypoint-Script (`docker/php/docker-entrypoint.sh`) setzt automatisch die korrekten Berechtigungen: + +```bash +# Fix ownership for all storage directories (including mounted volumes) +if [ -d /var/www/html/storage ]; then + chown -R appuser:appuser /var/www/html/storage 2>/dev/null || true + chmod -R 775 /var/www/html/storage 2>/dev/null || true +fi +``` + +### Wichtig f?r Staging/Production + +1. **Named Volumes**: Docker erstellt Named Volumes als root - das Entrypoint-Script fixiert Ownership nach dem Mount +2. **Wartezeit**: `sleep 1` stellt sicher, dass Docker Volumes gemountet hat +3. **Graceful Degradation**: `|| true` stellt sicher, dass das Script nicht bei Permission-Fehlern abbricht + +## Manuelle Fixes + +### Fix Berechtigungen auf Staging/Production + +```bash +# Auf dem Server ausf?hren +docker exec -it staging-app chown -R appuser:appuser /var/www/html/storage/cache +docker exec -it staging-app chmod -R 775 /var/www/html/storage/cache +``` + +### Fix existierende Cache-Dateien + +```bash +# Als root im Container - ermittle User automatisch +docker exec -it --user root staging-app bash -c ' + # Ermittle User aus PHP-FPM Config oder verwende www-data als Fallback + USER=$(grep "^user = " /usr/local/etc/php-fpm.d/*.conf 2>/dev/null | head -1 | cut -d"=" -f2 | tr -d " ;") + [ -z "$USER" ] && USER="www-data" + echo "Using user: $USER" + + # Fix Permissions + find /var/www/html/storage/cache -type f -exec chmod 644 {} \; + find /var/www/html/storage/cache -type d -exec chmod 775 {} \; + chown -R $USER:$USER /var/www/html/storage/cache +' +``` + +### Cleanup nicht l?schbarer Dateien + +Falls nach dem Fix noch Dateien vorhanden sind, die nicht gel?scht werden k?nnen: + +```bash +# Als root im Container +docker exec -it --user root staging-app bash +cd /var/www/html/storage/cache +chown -R appuser:appuser . +chmod -R 775 . +exit +``` + +## Ansible-Integration + +F?r Ansible-Deployments k?nnen folgende Tasks hinzugef?gt werden: + +```yaml +- name: Fix cache directory permissions + file: + path: "{{ app_storage_path }}/cache" + owner: appuser + group: appuser + mode: '0775' + recurse: yes + state: directory + become: yes +``` + +## Troubleshooting + +### Problem: Permission denied beim L?schen + +**Symptom**: +``` +FilePermissionException: Permission denied for delete on file: ...cache.php (File is not writable) +``` + +**L?sung**: +1. Pr?fe Verzeichnis-Berechtigungen: `ls -ld /var/www/html/storage/cache` +2. Pr?fe Owner: `ls -l /var/www/html/storage/cache | head` +3. Fix Berechtigungen (siehe oben) + +### Problem: Cache-Dateien werden von verschiedenen Usern erstellt + +**Symptom**: Dateien haben unterschiedliche Owner + +**L?sung**: +- Stelle sicher, dass alle PHP-Prozesse als `appuser` laufen +- Pr?fe PHP-FPM Pool-Konfiguration +- Stelle sicher, dass Entrypoint-Script korrekt l?uft + +### Problem: Cache funktioniert nicht nach Deployment + +**Symptom**: Cache-Operationen schlagen fehl + +**Diagnose**: +```bash +# Pr?fe Verzeichnis existiert +docker exec staging-app ls -la /var/www/html/storage/cache + +# Pr?fe Berechtigungen +docker exec staging-app stat /var/www/html/storage/cache + +# Pr?fe Owner +docker exec staging-app whoami +``` + +**L?sung**: +- Stelle sicher, dass Entrypoint-Script ausgef?hrt wurde +- Manuell Berechtigungen fixen (siehe oben) + +## Best Practices + +### 1. Verzeichnis-Berechtigungen + +- **Verzeichnis**: 775 (rwxrwxr-x) - Group-writable f?r Multi-User +- **Dateien**: 644 (rw-r--r--) - Standard f?r Cache-Dateien + +### 2. Owner/Group + +- **Owner**: `appuser` (der User, der PHP-Prozesse ausf?hrt) +- **Group**: `appuser` (oder gemeinsame Gruppe f?r alle Prozesse) + +### 3. Monitoring + +?berwache Cache-Verzeichnis auf Permission-Probleme: + +```bash +# Finde Dateien mit falschen Owner +find /var/www/html/storage/cache -not -user appuser + +# Finde Dateien mit falschen Permissions +find /var/www/html/storage/cache -type f -not -perm 644 +``` + +### 4. Cleanup + +Regelm??iges Cleanup von abgelaufenen Cache-Dateien: + +```bash +# Manueller Cleanup +docker exec staging-app php console.php cache:clear + +# Oder via Cronjob +0 2 * * * docker exec staging-app php console.php cache:clear +``` + +## Code-?nderungen + +### FileStorage::delete() + +**Vorher**: Pr?fte `is_writable()` auf Datei (falsch!) +**Nachher**: Pr?ft nur Verzeichnis-Rechte (korrekt!) + +```php +// WICHTIG: F?r unlink() braucht man nur Schreibrechte im Verzeichnis! +$dir = dirname($resolvedPath); +if (! is_writable($dir)) { + throw FilePermissionException::delete($path, 'Directory is not writable'); +} +// ENTFERNT: is_writable() Pr?fung auf Datei +``` + +### FileCache Graceful Degradation + +Alle `delete()` Aufrufe in FileCache fangen jetzt `FilePermissionException`: + +```php +try { + $this->fileSystem->delete($file); +} catch (\App\Framework\Filesystem\Exceptions\FilePermissionException $e) { + // Permission denied - continue (graceful degradation) + continue; +} +``` + +## Weitere Ressourcen + +- `docs/FILECACHE_PERMISSION_FIX_PLAN.md` - Detaillierter Fix-Plan +- `docker/php/docker-entrypoint.sh` - Entrypoint-Script +- `src/Framework/Filesystem/FileStorage.php` - FileSystem-Implementierung +- `src/Framework/Cache/Driver/FileCache.php` - FileCache-Implementierung + +## Support + +Bei Permission-Problemen: + +1. Pr?fe Logs: `docker logs staging-app | grep -i permission` +2. Pr?fe Berechtigungen: `docker exec staging-app ls -la /var/www/html/storage/cache` +3. Fix manuell: Siehe "Manuelle Fixes" oben +4. Pr?fe Code: Stelle sicher, dass neueste Version deployed ist diff --git a/docs/deployment/cache-permissions-quick-fix.md b/docs/deployment/cache-permissions-quick-fix.md new file mode 100644 index 00000000..0d0a874c --- /dev/null +++ b/docs/deployment/cache-permissions-quick-fix.md @@ -0,0 +1,74 @@ +# Cache Permissions Quick Fix + +## Problem + +``` +chown: invalid user: 'appuser:appuser' +``` + +Der User `appuser` existiert nicht im Container, oder es wird ein anderer User verwendet. + +## L?sung: Dynamischer Fix + +### Schritt 1: Ermittle den korrekten User + +```bash +# Pr?fe welcher User verwendet wird +docker exec staging-app whoami +docker exec staging-app id + +# Pr?fe PHP-FPM Konfiguration +docker exec staging-app grep "^user = " /usr/local/etc/php-fpm.d/*.conf +``` + +### Schritt 2: Fix Berechtigungen (automatisch) + +```bash +# Automatisch - ermittle User aus PHP-FPM Config +docker exec -it --user root staging-app bash -c ' + USER=$(grep "^user = " /usr/local/etc/php-fpm.d/*.conf 2>/dev/null | head -1 | cut -d"=" -f2 | tr -d " ;") + [ -z "$USER" ] && USER="www-data" + echo "Using user: $USER" + chown -R $USER:$USER /var/www/html/storage/cache + chmod -R 775 /var/www/html/storage/cache +' +``` + +### Schritt 3: Fix existierende Dateien + +```bash +docker exec -it --user root staging-app bash -c ' + USER=$(grep "^user = " /usr/local/etc/php-fpm.d/*.conf 2>/dev/null | head -1 | cut -d"=" -f2 | tr -d " ;") + [ -z "$USER" ] && USER="www-data" + find /var/www/html/storage/cache -type f -exec chmod 644 {} \; + find /var/www/html/storage/cache -type d -exec chmod 775 {} \; + chown -R $USER:$USER /var/www/html/storage/cache + echo "Fixed permissions for user: $USER" +' +``` + +## Alternative: Manuelle Fix + +Wenn du wei?t, welcher User verwendet wird: + +**F?r `www-data` (Production):** +```bash +docker exec -it --user root staging-app chown -R www-data:www-data /var/www/html/storage/cache +docker exec -it --user root staging-app chmod -R 775 /var/www/html/storage/cache +``` + +**F?r `appuser` (Development/Staging):** +```bash +docker exec -it --user root staging-app chown -R appuser:appuser /var/www/html/storage/cache +docker exec -it --user root staging-app chmod -R 775 /var/www/html/storage/cache +``` + +## Verifikation + +```bash +# Pr?fe Berechtigungen +docker exec staging-app ls -ld /var/www/html/storage/cache + +# Pr?fe Owner +docker exec staging-app ls -l /var/www/html/storage/cache | head -5 +``` diff --git a/src/Framework/Cache/Driver/FileCache.php b/src/Framework/Cache/Driver/FileCache.php index 5ef4da61..03e8f425 100644 --- a/src/Framework/Cache/Driver/FileCache.php +++ b/src/Framework/Cache/Driver/FileCache.php @@ -190,8 +190,16 @@ final readonly class FileCache implements CacheDriver, Scannable // File expired - try to delete it try { $this->fileSystem->delete($fullPath); + } catch (\App\Framework\Filesystem\Exceptions\FilePermissionException $e) { + // Permission denied - continue (graceful degradation) + // Datei wird beim n?chsten Cleanup oder manuell gel?scht + continue; + } catch (\App\Framework\Filesystem\Exceptions\FileNotFoundException $e) { + // File already deleted - continue + continue; } catch (\Throwable $e) { - // Continue even if deletion fails + // Any other error - continue (graceful degradation) + continue; } continue; @@ -221,10 +229,15 @@ final readonly class FileCache implements CacheDriver, Scannable } if ($content === null || $content === '') { + // Empty file - try to delete it try { $this->fileSystem->delete($bestFile); + } catch (\App\Framework\Filesystem\Exceptions\FilePermissionException $e) { + // Permission denied - treat as miss (graceful degradation) + } catch (\App\Framework\Filesystem\Exceptions\FileNotFoundException $e) { + // File already deleted - treat as miss } catch (\Throwable $e) { - // Continue even if deletion fails + // Any other error - treat as miss (graceful degradation) } return CacheItem::miss($key); @@ -259,8 +272,21 @@ final readonly class FileCache implements CacheDriver, Scannable $ttlSeconds = $item->ttl !== null ? $item->ttl->toCacheSeconds() : null; $expiresAt = $ttlSeconds ? (time() + $ttlSeconds) : null; + // Delete old cache files for this key (graceful degradation) foreach ($this->getFilesForKey($item->key) as $file) { - $this->fileSystem->delete($file); + try { + $this->fileSystem->delete($file); + } catch (\App\Framework\Filesystem\Exceptions\FilePermissionException $e) { + // Permission denied - continue (graceful degradation) + // Alte Datei bleibt, wird beim n?chsten Cleanup gel?scht + continue; + } catch (\App\Framework\Filesystem\Exceptions\FileNotFoundException $e) { + // File already deleted - continue + continue; + } catch (\Throwable $e) { + // Any other error - continue (graceful degradation) + continue; + } } $file = $this->getFileName($item->key, $expiresAt); @@ -302,10 +328,25 @@ final readonly class FileCache implements CacheDriver, Scannable $success = true; foreach ($keys as $key) { $result = $this->withKeyLock($key, function () use ($key) { + $deletedAny = false; foreach ($this->getFilesForKey($key) as $file) { - $this->fileSystem->delete($file); + try { + $this->fileSystem->delete($file); + $deletedAny = true; + } catch (\App\Framework\Filesystem\Exceptions\FilePermissionException $e) { + // Permission denied - continue (graceful degradation) + // Datei bleibt, wird beim n?chsten Cleanup gel?scht + continue; + } catch (\App\Framework\Filesystem\Exceptions\FileNotFoundException $e) { + // File already deleted - continue + continue; + } catch (\Throwable $e) { + // Any other error - continue (graceful degradation) + continue; + } } + // Return true if at least one file was deleted or no files existed return true; }); @@ -329,14 +370,21 @@ final readonly class FileCache implements CacheDriver, Scannable return str_ends_with($file, '.cache.php'); }); - // Delete all cache files + // Delete all cache files (graceful degradation) foreach ($cacheFiles as $file) { try { // Convert to absolute path if needed $fullPath = str_starts_with($file, '/') ? $file : self::CACHE_PATH . DIRECTORY_SEPARATOR . $file; $this->fileSystem->delete($fullPath); + } catch (\App\Framework\Filesystem\Exceptions\FilePermissionException $e) { + // Permission denied - continue with other files (graceful degradation) + // Diese Dateien bleiben, werden beim n?chsten Cleanup gel?scht + continue; + } catch (\App\Framework\Filesystem\Exceptions\FileNotFoundException $e) { + // File already deleted - continue + continue; } catch (\Throwable $e) { - // Continue with other files even if one fails + // Any other error - continue with other files (graceful degradation) continue; } } diff --git a/src/Framework/Filesystem/FileStorage.php b/src/Framework/Filesystem/FileStorage.php index 78de1072..fae53a8d 100644 --- a/src/Framework/Filesystem/FileStorage.php +++ b/src/Framework/Filesystem/FileStorage.php @@ -213,15 +213,18 @@ final readonly class FileStorage implements Storage, AtomicStorage, AppendableSt } // Prüfe Directory-Permissions + // WICHTIG: Für unlink() braucht man nur Schreibrechte im Verzeichnis, nicht auf der Datei selbst! $dir = dirname($resolvedPath); if (! is_writable($dir)) { throw FilePermissionException::delete($path, 'Directory is not writable: ' . $dir); } - // Prüfe File-Permissions - if (! is_writable($resolvedPath)) { - throw FilePermissionException::delete($path, 'File is not writable'); - } + // ENTFERNT: Prüfe File-Permissions + // Für unlink() benötigt man nur Schreibrechte im Parent-Verzeichnis, nicht auf der Datei selbst. + // Dies ist ein Unix-Standard-Verhalten: unlink() entfernt einen Directory-Eintrag, nicht die Datei selbst. + // if (! is_writable($resolvedPath)) { + // throw FilePermissionException::delete($path, 'File is not writable'); + // } if (! @unlink($resolvedPath)) { $error = error_get_last();