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.
This commit is contained in:
12
Makefile
12
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..."
|
@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'"
|
@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 Certificate Management (PHP Framework Integration)
|
||||||
ssl-init: ## Initialize Let's Encrypt certificates
|
ssl-init: ## Initialize Let's Encrypt certificates
|
||||||
@echo "🔒 Initializing SSL certificates..."
|
@echo "🔒 Initializing SSL certificates..."
|
||||||
@@ -393,4 +403,4 @@ env-validate: ## Validiert ENV-Files (Base+Override Pattern)
|
|||||||
fi
|
fi
|
||||||
@echo "💡 Framework lädt: .env.base → .env.local → System ENV"
|
@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
|
||||||
|
|||||||
@@ -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
|
mkdir -p /var/www/html/storage/sessions 2>/dev/null || true
|
||||||
|
|
||||||
# Fix ownership for all storage directories (including mounted volumes)
|
# 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
|
if [ -d /var/www/html/storage ]; then
|
||||||
chown -R appuser:appuser /var/www/html/storage 2>/dev/null || true
|
chown -R appuser:appuser /var/www/html/storage 2>/dev/null || true
|
||||||
chmod -R 775 /var/www/html/storage 2>/dev/null || true
|
chmod -R 775 /var/www/html/storage 2>/dev/null || true
|
||||||
|
|||||||
217
docs/FILECACHE_PERMISSION_FIX_PLAN.md
Normal file
217
docs/FILECACHE_PERMISSION_FIX_PLAN.md
Normal file
@@ -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
|
||||||
@@ -26,6 +26,7 @@ Die folgenden Dokumente behandeln spezifische Deployment-Themen:
|
|||||||
|
|
||||||
### Configuration & Setup
|
### Configuration & Setup
|
||||||
- **[database-migration-strategy.md](database-migration-strategy.md)** - Database Migration Strategy
|
- **[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
|
- **[logging-configuration.md](logging-configuration.md)** - Logging Configuration
|
||||||
- **[production-logging.md](production-logging.md)** - Production Logging Best Practices
|
- **[production-logging.md](production-logging.md)** - Production Logging Best Practices
|
||||||
- **[secrets-management.md](secrets-management.md)** - Secrets Management mit Vault
|
- **[secrets-management.md](secrets-management.md)** - Secrets Management mit Vault
|
||||||
|
|||||||
244
docs/deployment/cache-configuration.md
Normal file
244
docs/deployment/cache-configuration.md
Normal file
@@ -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
|
||||||
74
docs/deployment/cache-permissions-quick-fix.md
Normal file
74
docs/deployment/cache-permissions-quick-fix.md
Normal file
@@ -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
|
||||||
|
```
|
||||||
@@ -190,8 +190,16 @@ final readonly class FileCache implements CacheDriver, Scannable
|
|||||||
// File expired - try to delete it
|
// File expired - try to delete it
|
||||||
try {
|
try {
|
||||||
$this->fileSystem->delete($fullPath);
|
$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) {
|
} catch (\Throwable $e) {
|
||||||
// Continue even if deletion fails
|
// Any other error - continue (graceful degradation)
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
@@ -221,10 +229,15 @@ final readonly class FileCache implements CacheDriver, Scannable
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ($content === null || $content === '') {
|
if ($content === null || $content === '') {
|
||||||
|
// Empty file - try to delete it
|
||||||
try {
|
try {
|
||||||
$this->fileSystem->delete($bestFile);
|
$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) {
|
} catch (\Throwable $e) {
|
||||||
// Continue even if deletion fails
|
// Any other error - treat as miss (graceful degradation)
|
||||||
}
|
}
|
||||||
|
|
||||||
return CacheItem::miss($key);
|
return CacheItem::miss($key);
|
||||||
@@ -259,8 +272,21 @@ final readonly class FileCache implements CacheDriver, Scannable
|
|||||||
$ttlSeconds = $item->ttl !== null ? $item->ttl->toCacheSeconds() : null;
|
$ttlSeconds = $item->ttl !== null ? $item->ttl->toCacheSeconds() : null;
|
||||||
$expiresAt = $ttlSeconds ? (time() + $ttlSeconds) : null;
|
$expiresAt = $ttlSeconds ? (time() + $ttlSeconds) : null;
|
||||||
|
|
||||||
|
// Delete old cache files for this key (graceful degradation)
|
||||||
foreach ($this->getFilesForKey($item->key) as $file) {
|
foreach ($this->getFilesForKey($item->key) as $file) {
|
||||||
|
try {
|
||||||
$this->fileSystem->delete($file);
|
$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);
|
$file = $this->getFileName($item->key, $expiresAt);
|
||||||
@@ -302,10 +328,25 @@ final readonly class FileCache implements CacheDriver, Scannable
|
|||||||
$success = true;
|
$success = true;
|
||||||
foreach ($keys as $key) {
|
foreach ($keys as $key) {
|
||||||
$result = $this->withKeyLock($key, function () use ($key) {
|
$result = $this->withKeyLock($key, function () use ($key) {
|
||||||
|
$deletedAny = false;
|
||||||
foreach ($this->getFilesForKey($key) as $file) {
|
foreach ($this->getFilesForKey($key) as $file) {
|
||||||
|
try {
|
||||||
$this->fileSystem->delete($file);
|
$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;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -329,14 +370,21 @@ final readonly class FileCache implements CacheDriver, Scannable
|
|||||||
return str_ends_with($file, '.cache.php');
|
return str_ends_with($file, '.cache.php');
|
||||||
});
|
});
|
||||||
|
|
||||||
// Delete all cache files
|
// Delete all cache files (graceful degradation)
|
||||||
foreach ($cacheFiles as $file) {
|
foreach ($cacheFiles as $file) {
|
||||||
try {
|
try {
|
||||||
// Convert to absolute path if needed
|
// Convert to absolute path if needed
|
||||||
$fullPath = str_starts_with($file, '/') ? $file : self::CACHE_PATH . DIRECTORY_SEPARATOR . $file;
|
$fullPath = str_starts_with($file, '/') ? $file : self::CACHE_PATH . DIRECTORY_SEPARATOR . $file;
|
||||||
$this->fileSystem->delete($fullPath);
|
$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) {
|
} catch (\Throwable $e) {
|
||||||
// Continue with other files even if one fails
|
// Any other error - continue with other files (graceful degradation)
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -213,15 +213,18 @@ final readonly class FileStorage implements Storage, AtomicStorage, AppendableSt
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Prüfe Directory-Permissions
|
// Prüfe Directory-Permissions
|
||||||
|
// WICHTIG: Für unlink() braucht man nur Schreibrechte im Verzeichnis, nicht auf der Datei selbst!
|
||||||
$dir = dirname($resolvedPath);
|
$dir = dirname($resolvedPath);
|
||||||
if (! is_writable($dir)) {
|
if (! is_writable($dir)) {
|
||||||
throw FilePermissionException::delete($path, 'Directory is not writable: ' . $dir);
|
throw FilePermissionException::delete($path, 'Directory is not writable: ' . $dir);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prüfe File-Permissions
|
// ENTFERNT: Prüfe File-Permissions
|
||||||
if (! is_writable($resolvedPath)) {
|
// Für unlink() benötigt man nur Schreibrechte im Parent-Verzeichnis, nicht auf der Datei selbst.
|
||||||
throw FilePermissionException::delete($path, 'File is not writable');
|
// 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)) {
|
if (! @unlink($resolvedPath)) {
|
||||||
$error = error_get_last();
|
$error = error_get_last();
|
||||||
|
|||||||
Reference in New Issue
Block a user