info('=== mail:update-stats gestartet', [ 'user_opt' => $this->option('user'), 'php_user' => get_current_user(), 'uid' => getmyuid(), ]); $q = MailUser::query() ->where('is_active', true) ->with(['domain:id,domain']); if ($one = trim((string)$this->option('user'))) { if (str_contains($one, '@')) { [$local, $dom] = explode('@', $one, 2); $q->where('localpart', $local) ->whereHas('domain', fn($d) => $d->where('domain', $dom)); } else { $q->where('localpart', $one); } } $users = $q->get(); $log->info('Gefundene Mailboxen', ['count' => $users->count(), 'ids' => $users->pluck('id')]); if ($users->isEmpty()) { $this->warn('Keine passenden Mailboxen gefunden.'); $log->warning('Abbruch: keine Mailboxen gefunden'); return self::SUCCESS; } foreach ($users as $u) { $local = trim((string)$u->localpart); $domain = optional($u->domain)->domain; $email = trim((string)($u->email ?? '')); if ($email === '' && $local !== '' && $domain) { $email = "{$local}@{$domain}"; } if ($email === '' || !preg_match('/^[^@\s]+@[^@\s]+\.[^@\s]+$/', $email)) { $log->warning('Überspringe: ungültige Adresse', ['id' => $u->id, 'local' => $local, 'domain' => $domain, 'email' => $u->email]); continue; } $maildir = "/var/mail/vhosts/{$domain}/{$local}"; $log->info('Verarbeite Mailbox', ['id' => $u->id, 'email' => $email, 'maildir' => $maildir]); // 1) Größe berechnen $usedBytes = 0; if (is_dir($maildir)) { $it = new RecursiveIteratorIterator( new RecursiveDirectoryIterator($maildir, \FilesystemIterator::SKIP_DOTS) ); foreach ($it as $file) { if ($file->isFile()) { $usedBytes += $file->getSize(); } } } else { $log->warning('Maildir nicht gefunden', ['path' => $maildir]); } // 2) Nachrichtenzahl $messageCount = $this->countViaDoveadm($email, $log); if ($messageCount === null) { $messageCount = $this->countViaFilesystem($maildir, $log); } // Cache (Settings/Redis) Setting::set("mailbox.{$email}", [ 'used_bytes' => (int)$usedBytes, 'message_count' => (int)$messageCount, 'ts' => now()->toISOString(), ]); $this->line(sprintf("%-40s %7.1f MiB %5d msgs", $email, $usedBytes / 1024 / 1024, $messageCount)); $log->info('Aktualisiert', [ 'email' => $email, 'used_bytes' => $usedBytes, 'message_count' => $messageCount, ]); } $log->info('=== mail:update-stats fertig ==='); $this->info('Mailbox-Statistiken aktualisiert.'); return self::SUCCESS; } protected function countViaDoveadm(string $email, $log): ?int { $cmd = ['sudo', '-n', '-u', 'vmail', '/usr/bin/doveadm', '-f', 'tab', 'mailbox', 'status', '-u', $email, 'messages', 'INBOX']; $p = new Process($cmd, null, ['LANG' => 'C']); $p->setTimeout(10); try { $log->debug('Starte doveadm', ['cmd' => implode(' ', $cmd)]); $p->run(); $rc = $p->getExitCode(); $out = trim($p->getOutput()); $err = trim($p->getErrorOutput()); $log->debug('doveadm Ergebnis', ['rc' => $rc, 'out' => $out, 'err' => $err]); if ($rc !== 0) return null; $lines = preg_split('/\R+/', $out); $last = end($lines); if (preg_match('/\t(\d+)$/', (string)$last, $m)) { return (int)$m[1]; } return null; } catch (\Throwable $e) { $log->error('doveadm Exception', ['msg' => $e->getMessage()]); return null; } } protected function countViaFilesystem(string $maildir, $log): int { $count = 0; foreach (['cur', 'new'] as $sub) { $dir = "{$maildir}/{$sub}"; if (is_dir($dir) && ($dh = @opendir($dir))) { while (false !== ($fn = readdir($dh))) { if ($fn !== '.' && $fn !== '..' && $fn[0] !== '.') { $count++; } } closedir($dh); } else { $log->debug('Ordner fehlt/leer', ['path' => $dir]); } } return $count; } } // //namespace App\Console\Commands; // //use App\Models\MailUser; //use App\Models\Setting; //use Illuminate\Console\Command; //use RecursiveDirectoryIterator; //use RecursiveIteratorIterator; // //class UpdateMailboxStats extends Command //{ // protected $signature = 'mail:update-stats {--user=}'; // protected $description = 'Aktualisiert Mailquota und Nachrichtenzahl für alle Mailboxen (oder einen Benutzer)'; // // public function handle(): int // { // $q = MailUser::query() // ->where('is_active', true) // ->whereNotNull('email') // ->where('email', 'like', '%@%'); // // if ($email = trim((string)$this->option('user'))) { // $q->where('email', $email); // } // // $users = $q->get(); // if ($users->isEmpty()) { // $this->warn('Keine passenden Mailboxen gefunden.'); // return self::SUCCESS; // } // // foreach ($users as $u) { // $email = trim($u->email); // if (!preg_match('/^[^@\s]+@[^@\s]+\.[^@\s]+$/', $email)) { // // ungültig → überspringen // continue; // } // // [$local, $domain] = explode('@', $email, 2); // $maildir = "/var/mail/vhosts/{$domain}/{$local}"; // // // 1) Größe (rekursiv, ohne `du`) // $usedBytes = 0; // if (is_dir($maildir)) { // $it = new RecursiveIteratorIterator( // new RecursiveDirectoryIterator($maildir, \FilesystemIterator::SKIP_DOTS) // ); // foreach ($it as $file) { // if ($file->isFile()) { // $usedBytes += $file->getSize(); // } // } // } // // // 2) Nachrichtenzahl – bevorzugt via doveadm (sudo -u vmail), Fallback: Dateien zählen // $messageCount = $this->messageCountViaDoveadm($email); // if ($messageCount === null) { // $messageCount = $this->messageCountViaFilesystem($maildir); // } // // Setting::set("mailbox.{$email}", [ // 'used_bytes' => $usedBytes, // 'message_count' => $messageCount, // 'updated_at' => now()->toDateTimeString(), // ]); // // $this->line(sprintf( // "%-35s %7.1f MiB %5d Messages", // $email, // $usedBytes / 1024 / 1024, // $messageCount // )); // } // // $this->info('Mailbox-Statistiken aktualisiert.'); // return self::SUCCESS; // } // // /** // * Holt die Anzahl der Nachrichten in INBOX über doveadm als vmail (sudo). // * Gibt int bei Erfolg, oder null bei Fehlern zurück. // */ // private function messageCountViaDoveadm(string $email): ?int // { // // bevorzugtes Format: tabellarisch → "INBOX\t123" // $cmd = sprintf( // "sudo -n -u vmail /usr/bin/doveadm -f tab mailbox status -u %s messages INBOX 2>/dev/null", // escapeshellarg($email) // ); // $out = trim((string) shell_exec($cmd)); // // if ($out === '') { // // Fallback auf normales Format: "messages=123" // $cmd2 = sprintf( // "sudo -n -u vmail /usr/bin/doveadm mailbox status -u %s messages INBOX 2>/dev/null", // escapeshellarg($email) // ); // $out = trim((string) shell_exec($cmd2)); // } // // if ($out === '') { // return null; // } // // // Match "INBOX123" oder "messages=123" // if (preg_match('/\t(\d+)\s*$/', $out, $m) || preg_match('/messages=(\d+)/', $out, $m)) { // return (int) $m[1]; // } // // return null; // } // // /** // * Fallback: Zählt Dateien in cur/ + new/ (Maildir). // */ // private function messageCountViaFilesystem(string $maildir): int // { // $count = 0; // foreach (['cur', 'new'] as $sub) { // $dir = "{$maildir}/{$sub}"; // if (is_dir($dir) && is_readable($dir)) { // $dh = opendir($dir); // if ($dh) { // while (($fn = readdir($dh)) !== false) { // if ($fn !== '.' && $fn !== '..' && $fn[0] !== '.') { // $count++; // } // } // closedir($dh); // } // } // } // return $count; // } //}