137 lines
4.3 KiB
PHP
137 lines
4.3 KiB
PHP
<?php
|
||
|
||
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 msgs",
|
||
$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 "INBOX<TAB>123" 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;
|
||
}
|
||
}
|