info('=== mail:update-stats START ===', [ 'userOpt' => $this->option('user') ]); $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()) { Log::channel('mailstats')->warning('Keine passenden Mailboxen gefunden.'); $this->warn('Keine passenden Mailboxen gefunden.'); return self::SUCCESS; } foreach ($users as $u) { $email = trim((string)$u->email); if (!preg_match('/^[^@\s]+@[^@\s]+\.[^@\s]+$/', $email)) { Log::channel('mailstats')->warning('Überspringe ungültige Adresse', ['email' => $email]); continue; } [$local, $domain] = explode('@', $email, 2); $maildir = "/var/mail/vhosts/{$domain}/{$local}"; // 1) Größe berechnen (Dateisystem) $usedBytes = 0; if (is_dir($maildir)) { $it = new RecursiveIteratorIterator( new RecursiveDirectoryIterator($maildir, \FilesystemIterator::SKIP_DOTS) ); foreach ($it as $f) { if ($f->isFile()) $usedBytes += $f->getSize(); } } // 2) Nachrichten zählen – bevorzugt via doveadm als vmail $messageCount = $this->countViaDoveadm($email); if ($messageCount === null) { $messageCount = $this->countViaFilesystem($maildir); } // optional: in Settings/Redis cachen (für UI) Setting::set("mailbox.{$email}", [ 'used_bytes' => (int)$usedBytes, 'message_count' => (int)$messageCount, 'updated_at' => now()->toDateTimeString(), ]); $line = sprintf("%-35s %7.1f MiB %5d msgs", $email, $usedBytes / 1024 / 1024, $messageCount); $this->line($line); Log::channel('mailstats')->info('OK', [ 'email' => $email, 'maildir' => $maildir, 'used_bytes' => $usedBytes, 'message_count' => $messageCount, ]); } Log::channel('mailstats')->info('=== mail:update-stats DONE ==='); $this->info('Mailbox-Statistiken aktualisiert.'); return self::SUCCESS; } protected function countViaDoveadm(string $email): ?int { $cmd = "sudo -n -u vmail /usr/bin/doveadm -f tab mailbox status -u " . escapeshellarg($email) . " messages INBOX 2>&1"; $out = []; $rc = 0; exec($cmd, $out, $rc); Log::channel('mailstats')->debug('doveadm exec', ['cmd' => $cmd, 'rc' => $rc, 'out' => $out]); if ($rc !== 0) return null; // Erwartet: "mailbox\tmessages\nINBOX\t" foreach ($out as $line) { if (preg_match('/^\s*INBOX\s+(\d+)\s*$/i', trim($line), $m)) { return (int)$m[1]; } } return null; } protected function countViaFilesystem(string $maildir): int { $n = 0; foreach (['cur', 'new'] as $sub) { $dir = "{$maildir}/{$sub}"; if (!is_dir($dir)) continue; $dh = opendir($dir); if (!$dh) continue; while (($fn = readdir($dh)) !== false) { if ($fn === '.' || $fn === '..' || $fn[0] === '.') continue; $n++; } closedir($dh); } Log::channel('mailstats')->debug('fs count', ['maildir' => $maildir, 'count' => $n]); return $n; } } //namespace App\Console\Commands; // //use App\Models\MailUser; //use App\Models\Setting; //use Illuminate\Console\Command; //use Illuminate\Support\Facades\Log; //use RecursiveDirectoryIterator; //use RecursiveIteratorIterator; //use Symfony\Component\Process\Process; // //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 // { // $log = Log::channel('mailstats'); // $log->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; // } //}