feat(Production): Complete production deployment infrastructure

- Add comprehensive health check system with multiple endpoints
- Add Prometheus metrics endpoint
- Add production logging configurations (5 strategies)
- Add complete deployment documentation suite:
  * QUICKSTART.md - 30-minute deployment guide
  * DEPLOYMENT_CHECKLIST.md - Printable verification checklist
  * DEPLOYMENT_WORKFLOW.md - Complete deployment lifecycle
  * PRODUCTION_DEPLOYMENT.md - Comprehensive technical reference
  * production-logging.md - Logging configuration guide
  * ANSIBLE_DEPLOYMENT.md - Infrastructure as Code automation
  * README.md - Navigation hub
  * DEPLOYMENT_SUMMARY.md - Executive summary
- Add deployment scripts and automation
- Add DEPLOYMENT_PLAN.md - Concrete plan for immediate deployment
- Update README with production-ready features

All production infrastructure is now complete and ready for deployment.
This commit is contained in:
2025-10-25 19:18:37 +02:00
parent caa85db796
commit fc3d7e6357
83016 changed files with 378904 additions and 20919 deletions

View File

@@ -0,0 +1,294 @@
<?php
declare(strict_types=1);
namespace App\Framework\Logging\Commands;
use App\Framework\Console\ConsoleCommand;
use App\Framework\Console\ConsoleInput;
use App\Framework\Console\ExitCode;
use App\Framework\Core\ValueObjects\Byte;
/**
* Console Command zur Überwachung der Log-Infrastruktur
*
* Prüft:
* - Log-Verzeichnisse und Berechtigungen
* - Log-Dateien und deren Größen
* - Rotierte Log-Dateien
* - Disk-Space-Verfügbarkeit
* - Schreibrechte
*
* Usage:
* ```bash
* php console.php log:health-check
* php console.php log:health-check --detailed
* php console.php log:health-check --fix-permissions
* ```
*/
final readonly class LogHealthCheckCommand
{
#[ConsoleCommand(name: 'logs:health-check', description: 'Check log infrastructure health and identify issues')]
public function execute(ConsoleInput $input): ExitCode
{
$detailed = $input->hasOption('detailed');
$fixPermissions = $input->hasOption('fix-permissions');
$basePath = getcwd() ?: '/var/www/html';
$logsPath = $basePath . '/storage/logs';
echo "═══════════════════════════════════════════════════\n";
echo " Log Infrastructure Health Check \n";
echo "═══════════════════════════════════════════════════\n\n";
$checks = [
'directories' => $this->checkDirectories($logsPath, $basePath, $fixPermissions),
'permissions' => $this->checkPermissions($logsPath, $basePath, $fixPermissions),
'disk_space' => $this->checkDiskSpace($logsPath, $basePath),
'log_files' => $this->checkLogFiles($logsPath, $basePath, $detailed),
];
// Summary
echo "\n═══════════════════════════════════════════════════\n";
echo " Summary \n";
echo "═══════════════════════════════════════════════════\n";
$allPassed = true;
foreach ($checks as $checkName => $passed) {
$status = $passed ? '✓ PASS' : '✗ FAIL';
$color = $passed ? "\033[32m" : "\033[31m";
echo $color . $status . "\033[0m " . str_pad(ucfirst(str_replace('_', ' ', $checkName)), 30) . "\n";
if (!$passed) {
$allPassed = false;
}
}
echo "\n";
if ($allPassed) {
echo "\033[32m✓ All checks passed! Log infrastructure is healthy.\033[0m\n";
return ExitCode::SUCCESS;
}
echo "\033[31m✗ Some checks failed. Please review above output.\033[0m\n";
if (!$fixPermissions) {
echo "\033[33mTip: Use --fix-permissions to automatically fix permission issues.\033[0m\n";
}
return ExitCode::GENERAL_ERROR;
}
/**
* Prüft ob Log-Verzeichnisse existieren
*/
private function checkDirectories(string $logsPath, string $basePath, bool $fix): bool
{
echo "📁 Checking log directories...\n";
$directories = [
$logsPath,
$logsPath . '/app',
$logsPath . '/debug',
$logsPath . '/security',
];
$allExist = true;
foreach ($directories as $dir) {
$exists = is_dir($dir);
if ($exists) {
echo "" . $this->relativePath($dir, $basePath) . "\n";
} else {
echo "" . $this->relativePath($dir, $basePath) . " (missing)\n";
$allExist = false;
if ($fix) {
echo " → Creating directory...\n";
mkdir($dir, 0777, true);
echo " ✓ Directory created\n";
$allExist = true;
}
}
}
echo "\n";
return $allExist;
}
/**
* Prüft Schreibberechtigungen
*/
private function checkPermissions(string $logsPath, string $basePath, bool $fix): bool
{
echo "🔐 Checking write permissions...\n";
$testFile = $logsPath . '/.health-check-' . uniqid() . '.tmp';
try {
// Versuche Test-Datei zu schreiben
$result = @file_put_contents($testFile, 'health check test');
if ($result === false) {
echo " ✗ Cannot write to " . $this->relativePath($logsPath, $basePath) . "\n";
if ($fix) {
echo " → Attempting to fix permissions...\n";
@chmod($logsPath, 0777);
@chmod(dirname($logsPath), 0777);
// Retry
$result = @file_put_contents($testFile, 'health check test');
if ($result !== false) {
echo " ✓ Permissions fixed\n";
@unlink($testFile);
echo "\n";
return true;
}
}
echo "\n";
return false;
}
// Cleanup
@unlink($testFile);
$perms = substr(sprintf('%o', fileperms($logsPath)), -4);
echo " ✓ Write permissions OK (permissions: " . $perms . ")\n";
echo "\n";
return true;
} catch (\Throwable $e) {
echo " ✗ Permission check failed: " . $e->getMessage() . "\n";
echo "\n";
return false;
}
}
/**
* Prüft verfügbaren Disk-Space
*/
private function checkDiskSpace(string $logsPath, string $basePath): bool
{
echo "💾 Checking disk space...\n";
$freeSpace = disk_free_space($logsPath);
$totalSpace = disk_total_space($logsPath);
if ($freeSpace === false || $totalSpace === false) {
echo " ✗ Could not determine disk space\n";
echo "\n";
return false;
}
$freeBytes = Byte::fromBytes((int) $freeSpace);
$totalBytes = Byte::fromBytes((int) $totalSpace);
$usedBytes = Byte::fromBytes((int) ($totalSpace - $freeSpace));
$percentUsed = ($totalSpace > 0) ? round(($totalSpace - $freeSpace) / $totalSpace * 100, 2) : 0;
echo " Total: " . $totalBytes->toHumanReadable() . "\n";
echo " Used: " . $usedBytes->toHumanReadable() . " (" . $percentUsed . "%)\n";
echo " Free: " . $freeBytes->toHumanReadable() . "\n";
// Warn if less than 100MB free or more than 90% used
if ($freeSpace < 100 * 1024 * 1024 || $percentUsed > 90) {
echo " \033[33m⚠ Warning: Low disk space!\033[0m\n";
echo "\n";
return false;
}
echo " ✓ Disk space OK\n";
echo "\n";
return true;
}
/**
* Prüft Log-Dateien
*/
private function checkLogFiles(string $logsPath, string $basePath, bool $detailed): bool
{
echo "📄 Checking log files...\n";
$logFiles = $this->findLogFiles($logsPath);
if (empty($logFiles)) {
echo " No log files found (this is OK for a fresh install)\n";
echo "\n";
return true;
}
echo " Found " . count($logFiles) . " log file(s)\n\n";
$totalSize = 0;
$warnings = [];
foreach ($logFiles as $logFile) {
$size = filesize($logFile);
$totalSize += $size;
$sizeBytes = Byte::fromBytes($size);
$modified = date('Y-m-d H:i:s', filemtime($logFile));
if ($detailed) {
echo " " . $this->relativePath($logFile, $basePath) . "\n";
echo " Size: " . $sizeBytes->toHumanReadable() . "\n";
echo " Modified: " . $modified . "\n";
// Check if file is very large
if ($size > 50 * 1024 * 1024) { // 50MB
echo " \033[33m⚠ Large file (consider rotation)\033[0m\n";
$warnings[] = $logFile;
}
echo "\n";
}
}
$totalBytes = Byte::fromBytes($totalSize);
echo " Total log size: " . $totalBytes->toHumanReadable() . "\n";
if (!empty($warnings)) {
echo "\n \033[33m⚠ " . count($warnings) . " file(s) exceed 50MB\033[0m\n";
}
echo "\n";
return empty($warnings);
}
/**
* Findet alle Log-Dateien rekursiv
*/
private function findLogFiles(string $directory): array
{
$files = [];
$iterator = new \RecursiveIteratorIterator(
new \RecursiveDirectoryIterator($directory, \FilesystemIterator::SKIP_DOTS),
\RecursiveIteratorIterator::SELF_FIRST
);
foreach ($iterator as $file) {
if ($file->isFile() && preg_match('/\.(log|log\.\d+|log\.\d+\.gz)$/', $file->getFilename())) {
$files[] = $file->getPathname();
}
}
return $files;
}
/**
* Gibt relativen Pfad zurück
*/
private function relativePath(string $path, string $basePath): string
{
return str_replace($basePath . '/', '', $path);
}
}