mailwolt/app/Console/Commands/UpdateMailboxStats.php

132 lines
3.9 KiB
PHP

<?php
namespace App\Console\Commands;
use App\Models\MailUser;
use Illuminate\Console\Command;
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 spezifischen Benutzer)';
public function handle(): int
{
$query = MailUser::query()->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;
}
}