where('is_active', true); if ($email = $this->option('user')) { $query->where('email', $email); } $users = $query->get(); $rows = []; foreach ($users as $u) { $email = (string)$u->email; // E-Mail validieren if (!filter_var($email, FILTER_VALIDATE_EMAIL)) { $this->warn("Überspringe ungültige Adresse: '{$email}'"); continue; } [$local, $domain] = explode('@', $email, 2); $base = "/var/mail/vhosts/{$domain}/{$local}"; // ---- Größe (Bytes) via `du -sb`, mit Fallback ---- $usedBytes = 0; if (is_dir($base)) { $usedBytes = $this->dirSizeBytes($base); } // ---- Nachrichten zählen: erst doveadm, sonst Maildir ---- $messageCount = $this->countMessages($email, $base); $u->update([ 'used_bytes' => $usedBytes, 'message_count' => $messageCount, 'stats_refreshed_at'=> now(), ]); $rows[] = sprintf( "%-35s %6.1f MiB %4d msgs", $email, $usedBytes / 1024 / 1024, $messageCount ); } foreach ($rows as $line) { $this->info($line); } return self::SUCCESS; } private function dirSizeBytes(string $path): int { // du -sb (schnell & korrekt, braucht nur r-x für Verzeichnisse) $p = Process::fromShellCommandline('du -sb '.escapeshellarg($path).' | cut -f1'); $p->run(); if ($p->isSuccessful()) { $val = trim($p->getOutput()); if ($val !== '' && ctype_digit($val)) { return (int)$val; } } // Fallback (langsamer, aber funktioniert ohne du) $size = 0; $it = new \RecursiveIteratorIterator( new \RecursiveDirectoryIterator($path, \FilesystemIterator::SKIP_DOTS) ); foreach ($it as $f) { if ($f->isFile()) { $size += $f->getSize(); } } return $size; } private function countMessages(string $email, string $base): int { // 1) Bevorzugt: doveadm $cmd = 'doveadm mailbox status -u '.escapeshellarg($email).' messages INBOX'; $p = Process::fromShellCommandline($cmd); $p->run(); if ($p->isSuccessful()) { $out = trim($p->getOutput()); if (preg_match('/messages=(\d+)/', $out, $m)) { return (int)$m[1]; } } // 2) Fallback: Maildir zählen (INBOX: root/cur + root/new) $count = 0; $paths = [ // Dovecot: INBOX ist i.d.R. das Root-Maildir $base.'/cur', $base.'/new', // manche Layouts haben expliziten INBOX-Ordner $base.'/INBOX/cur', $base.'/INBOX/new', ]; foreach ($paths as $p) { if (is_dir($p)) { $proc = Process::fromShellCommandline('find '.escapeshellarg($p).' -type f -printf . | wc -c'); $proc->run(); if ($proc->isSuccessful()) { $val = trim($proc->getOutput()); if ($val !== '' && ctype_digit($val)) { $count += (int)$val; } } } } return $count; } }