argument('target') ?: '/'; $data = $this->probe($target); Setting::set('health.disk', $data); Setting::set('health.disk_updated_at', now()->toIso8601String()); $hb = function (int $bytes): string { $b = max(0, $bytes); if ($b >= 1024**3) return number_format($b / 1024**3, 1).' GB'; if ($b >= 1024**2) return number_format($b / 1024**2, 2).' MiB'; if ($b >= 1024) return number_format($b / 1024, 0).' KiB'; return $b.' B'; }; $this->info(sprintf( 'Storage %s → total:%dGB used:%dGB free_user:%dGB free+5%%:%dGB (%%used:%d) breakdown: system=%s mails=%s backups=%s', $data['mount'], $data['total_gb'], $data['used_gb'], $data['free_gb'], $data['free_plus_reserve_gb'], $data['percent_used_total'], $hb((int)$data['breakdown_bytes']['system']), $hb((int)$data['breakdown_bytes']['mails']), $hb((int)$data['breakdown_bytes']['backup']), )); // $this->info(sprintf( // 'Storage %s → total:%dGB used:%dGB free_user:%dGB free+5%%:%dGB (%%used:%d) breakdown: system=%.1fGB mails=%.1fGB backups=%.1fGB', // $data['mount'], // $data['total_gb'], // $data['used_gb'], // $data['free_gb'], // $data['free_plus_reserve_gb'], // $data['percent_used_total'], // $data['breakdown_bytes']['system'], // $data['breakdown_bytes']['mails'], // $data['breakdown_bytes']['backup'], // )); return self::SUCCESS; } private function detectMailRoot(): ?string { // Dovecot $ml = trim((string) @shell_exec('doveconf -n 2>/dev/null | awk -F= \'/^mail_location/ {print $2}\'')); if ($ml !== '') { if (preg_match('~^(?:maildir|mdbox|sdbox):([^%]+)~i', $ml, $m)) { $root = rtrim($m[1]); foreach (['/Maildir', '/mdbox', '/sdbox'] as $suffix) { if (str_ends_with($root, $suffix)) { $root = dirname($root); break; } } if (is_dir($root)) return $root; } } // Postfix $vmb = trim((string) @shell_exec('postconf -n 2>/dev/null | awk -F= \'/^virtual_mailbox_base/ {print $2}\'')); if ($vmb !== '' && is_dir($vmb)) return $vmb; // Fallbacks foreach (['/var/vmail', '/var/mail/vhosts', '/srv/mail/vhosts', '/home/vmail'] as $cand) { if (is_dir($cand)) return $cand; } return null; } private function duBytesDir(string $path): int { if (!is_dir($path)) return 0; $out = @shell_exec('LC_ALL=C du -sb --apparent-size ' . escapeshellarg($path) . ' 2>/dev/null | cut -f1'); return max(0, (int) trim((string) $out)); } // … oberhalb deiner probe()-Methode einfügen: // private function detectMailRoot(): ?string // { // // 1) Dovecot: mail_location = maildir:/var/vmail/%d/%n/Maildir // $ml = trim((string) @shell_exec('doveconf -n 2>/dev/null | awk -F= \'/^mail_location/ {print $2}\'')); // if ($ml !== '') { // $ml = trim($ml); // // maildir:/path/to/root/%d/%n/Maildir oder mdbox:/path/…/mdbox // if (preg_match('~^(?:maildir|mdbox|sdbox):([^%]+)~i', $ml, $m)) { // $root = rtrim($m[1]); // // häufig endet es auf /Maildir oder /mdbox → ein Level hoch als Root nehmen // foreach (['/Maildir', '/mdbox', '/sdbox'] as $suffix) { // if (str_ends_with($root, $suffix)) { // $root = dirname($root); // break; // } // } // if (is_dir($root)) return $root; // } // } // // // 2) Postfix: virtual_mailbox_base = /var/vmail // $vmb = trim((string) @shell_exec('postconf -n 2>/dev/null | awk -F= \'/^virtual_mailbox_base/ {print $2}\'')); // $vmb = $vmb !== '' ? trim($vmb) : ''; // if ($vmb !== '' && is_dir($vmb)) return $vmb; // // // 3) Fallbacks – nimm den ersten existierenden // foreach (['/var/vmail', '/var/mail/vhosts', '/srv/mail/vhosts', '/home/vmail'] as $cand) { // if (is_dir($cand)) return $cand; // } // return null; // } // // private function duBytesDir(string $path): int // { // if (!is_dir($path)) return 0; // // apparent-size zählt logisch (kleine Dateien werden nicht „weggerundet“) // $out = @shell_exec('LC_ALL=C du -sb --apparent-size ' . escapeshellarg($path) . ' 2>/dev/null | cut -f1'); // return max(0, (int) trim((string) $out)); // } protected function probe(string $target): array { // --- df: Gesamtdaten des Filesystems (inkl. Reserve) ----------------- $line = trim((string)@shell_exec('LC_ALL=C df -kP ' . escapeshellarg($target) . ' 2>/dev/null | tail -n1')); $device = $mount = ''; $totalKb = $usedKb = $availKb = 0; if ($line !== '') { $p = preg_split('/\s+/', $line); if (count($p) >= 6) { $device = $p[0]; $totalKb = (int)$p[1]; // TOTAL (inkl. Reserve) $usedKb = (int)$p[2]; // Used $availKb = (int)$p[3]; // Avail (User-sicht) $mount = $p[5]; } } $toGiB_i = static fn($kb) => (int)round(max(0, (int)$kb) / (1024 * 1024)); // ganzzahlig (UI: Gesamt/Genutzt/Frei) $toGiB_f = static fn($kb) => round(max(0, (int)$kb) / (1024 * 1024), 1); // eine Nachkommastelle (Breakdown/Legende) $totalGb = $toGiB_i($totalKb); $freeGb = $toGiB_i($availKb); // user-verfügbar $usedGb = max(0, $totalGb - $freeGb); // belegt inkl. Reserve $res5Gb = (int)round($totalGb * 0.05); // 5% von Gesamt $freePlusReserveGb = min($totalGb, $freeGb + $res5Gb); $percentUsed = $totalGb > 0 ? (int)round($usedGb * 100 / $totalGb) : 0; $duBytes = function (string $path): int { if (!is_dir($path)) return 0; $b = (int) trim((string) @shell_exec( 'LC_ALL=C du -sb --apparent-size ' . escapeshellarg($path) . ' 2>/dev/null | cut -f1' )); return max(0, $b); }; $mailRoot = $this->detectMailRoot(); $bytesMails = $mailRoot ? $this->duBytesDir($mailRoot) : 0; $bytesBackup = $duBytes('/var/backups/mailwolt'); $totalBytes = (int) $totalKb * 1024; $freeBytes = (int) $availKb * 1024; $usedBytes = max(0, $totalBytes - $freeBytes); $bytesSystem = max(0, $usedBytes - ($bytesMails + $bytesBackup)); return [ 'device' => $device ?: 'unknown', 'mount' => $mount ?: $target, 'total_gb' => $totalGb, 'used_gb' => $usedGb, // inkl. Reserve 'free_gb' => $freeGb, // User-sicht 'reserve5_gb' => $res5Gb, // Info 'free_plus_reserve_gb' => $freePlusReserveGb, // Anzeige „Frei“ 'percent_used_total' => $percentUsed, // Reale Breakdown-Werte 'breakdown_bytes' => [ 'system' => $bytesSystem, 'mails' => $bytesMails, 'backup' => $bytesBackup, ], ]; } } // //namespace App\Console\Commands; // //use Illuminate\Console\Command; //use App\Models\Setting; // //class StorageProbe extends Command //{ // protected $signature = 'health:probe-disk {target=/}'; // protected $description = 'Speichert Storage-Werte (inkl. Frei+5%) in settings:health.disk'; // // public function handle(): int // { // $target = $this->argument('target') ?: '/'; // $data = $this->probe($target); // // // Persistiert (DB + Redis) über dein Settings-Model // Setting::set('health.disk', $data); // Setting::set('health.disk_updated_at', now()->toIso8601String()); // // $this->info(sprintf( // 'Storage %s → total:%dGB used:%dGB free_user:%dGB free+5%%:%dGB (%%used:%d)', // $data['mount'], // $data['total_gb'], // $data['used_gb'], // $data['free_gb'], // $data['free_plus_reserve_gb'], // $data['percent_used_total'], // )); // // return self::SUCCESS; // } // // protected function probe(string $target): array // { // $line = trim((string) @shell_exec('df -kP ' . escapeshellarg($target) . ' 2>/dev/null | tail -n1')); // // $device = $mount = ''; // $totalKb = $usedKb = $availKb = 0; // // if ($line !== '') { // $p = preg_split('/\s+/', $line); // if (count($p) >= 6) { // $device = $p[0]; // $totalKb = (int) $p[1]; // TOTAL (inkl. Reserve) // $usedKb = (int) $p[2]; // Used // $availKb = (int) $p[3]; // Avail (User-sicht) // $mount = $p[5]; // } // } // // $toGiB = static fn($kb) => (int) round(max(0, (int)$kb) / (1024*1024)); // // $totalGb = $toGiB($totalKb); // $freeGb = $toGiB($availKb); // user-verfügbar // $usedGb = max(0, $totalGb - $freeGb); // belegt inkl. Reserve // $res5Gb = (int) round($totalGb * 0.05); // 5% von Gesamt // $freePlusReserveGb = min($totalGb, $freeGb + $res5Gb); // // $percentUsed = $totalGb > 0 ? (int) round($usedGb * 100 / $totalGb) : 0; // // return [ // 'device' => $device ?: 'unknown', // 'mount' => $mount ?: $target, // // 'total_gb' => $totalGb, // 'used_gb' => $usedGb, // inkl. Reserve // 'free_gb' => $freeGb, // User-sicht // 'reserve5_gb' => $res5Gb, // Info // 'free_plus_reserve_gb' => $freePlusReserveGb, // ← das willst du anzeigen // // 'percent_used_total' => $percentUsed, // fürs Donut (~15%) // 'breakdown' => [ // 'system_gb' => 5.2, // OS, App, Logs … // 'mails_gb' => 2.8, // /var/mail/vhosts // 'backup_gb' => 1.0, // /var/backups/mailwolt (oder wohin du sicherst) // ], // ]; // } //}