1048 lines
37 KiB
PHP
1048 lines
37 KiB
PHP
<?php
|
||
|
||
|
||
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;
|
||
|
||
class UpdateMailboxStats extends Command
|
||
{
|
||
protected $signature = 'mail:update-stats {--user=}';
|
||
protected $description = 'Aktualisiert Quota & Nachrichtenzahl (Settings/Redis; ohne DB-Spalten).';
|
||
|
||
// public function handle(): int
|
||
// {
|
||
// $log = Log::channel('mailstats');
|
||
// $onlyUser = trim((string)$this->option('user')) ?: null;
|
||
// $t0 = microtime(true);
|
||
//
|
||
// // Basis-Query: nur aktive, keine System-Mailboxen und keine System-Domains
|
||
// $base = MailUser::query()
|
||
// ->select(['id', 'domain_id', 'localpart', 'email', 'is_active', 'is_system'])
|
||
// ->with(['domain:id,domain,is_system'])
|
||
// ->where('is_active', true)
|
||
// ->where('is_system', false)
|
||
// ->whereHas('domain', fn($d) => $d->where('is_system', false));
|
||
//
|
||
// if ($onlyUser) {
|
||
// $base->where('email', $onlyUser);
|
||
// }
|
||
//
|
||
// $checked = 0;
|
||
// $changed = 0;
|
||
//
|
||
// $log->info('mail:update-stats START', ['only' => $onlyUser]);
|
||
//
|
||
// $base->orderBy('id')->chunkById(200, function ($users) use (&$checked, &$changed, $log) {
|
||
// foreach ($users as $u) {
|
||
// $checked++;
|
||
//
|
||
// // Email robust bestimmen (raw -> accessor -> zusammengesetzt)
|
||
// $raw = (string)($u->getRawOriginal('email') ?? '');
|
||
// $email = $raw !== '' ? $raw : ($u->email ?? $u->address ?? null);
|
||
//
|
||
// if (!is_string($email) || !preg_match('/^[^@\s]+@[^@\s]+\.[^@\s]+$/', $email)) {
|
||
// // still – kein Log-Spam
|
||
// continue;
|
||
// }
|
||
//
|
||
// [$local, $domain] = explode('@', $email, 2);
|
||
// $maildir = "/var/mail/vhosts/{$domain}/{$local}";
|
||
//
|
||
// // Größe in Bytes (rekursiv)
|
||
// $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();
|
||
// }
|
||
// }
|
||
//
|
||
// // Message-Count
|
||
// $messageCount = $this->countViaDoveadm($email);
|
||
// if ($messageCount === null) {
|
||
// $messageCount = $this->countViaFilesystem($maildir);
|
||
// }
|
||
//
|
||
// $key = "mailbox.{$email}";
|
||
// $prev = (array)(Setting::get($key, []) ?: []);
|
||
// $new = [
|
||
// 'used_bytes' => (int)$usedBytes,
|
||
// 'message_count' => (int)$messageCount,
|
||
// 'updated_at' => now()->toDateTimeString(),
|
||
// ];
|
||
//
|
||
// if (($prev['used_bytes'] ?? null) !== $new['used_bytes']
|
||
// || ($prev['message_count'] ?? null) !== $new['message_count']) {
|
||
// Setting::set($key, $new);
|
||
// $changed++;
|
||
//
|
||
// // kurze Ausgabe & Info-Log NUR bei Änderung
|
||
// $this->line(sprintf("%-35s %7.1f MiB %5d msgs",
|
||
// $email, $usedBytes / 1048576, $messageCount));
|
||
// $log->info('updated', ['email' => $email, 'used_bytes' => $new['used_bytes'], 'message_count' => $new['message_count']]);
|
||
// }
|
||
// }
|
||
// });
|
||
//
|
||
// $ms = (int)((microtime(true) - $t0) * 1000);
|
||
// $log->info('mail:update-stats DONE', compact('checked', 'changed', 'ms'));
|
||
// $this->info('Mailbox-Statistiken aktualisiert.');
|
||
// return self::SUCCESS;
|
||
// }
|
||
|
||
public function handle(): int
|
||
{
|
||
$log = Log::channel('mailstats');
|
||
$onlyUser = trim((string)$this->option('user')) ?: null;
|
||
$t0 = microtime(true);
|
||
|
||
// Summen
|
||
$sumUserBytes = 0;
|
||
$sumSystemBytes = 0;
|
||
|
||
// aktiver Benutzerbestand (inkl. Domains, um system/non-system zu unterscheiden)
|
||
$base = MailUser::query()
|
||
->select(['id','domain_id','localpart','email','is_active','is_system'])
|
||
->with(['domain:id,domain,is_system'])
|
||
->where('is_active', true);
|
||
|
||
if ($onlyUser) {
|
||
$base->where('email', $onlyUser);
|
||
}
|
||
|
||
$checked = 0; $changed = 0;
|
||
$log->info('mail:update-stats START', ['only' => $onlyUser]);
|
||
|
||
$base->orderBy('id')->chunkById(200, function ($users) use (&$checked,&$changed,&$sumUserBytes,&$sumSystemBytes,$log) {
|
||
foreach ($users as $u) {
|
||
$checked++;
|
||
|
||
$raw = (string)($u->getRawOriginal('email') ?? '');
|
||
$email = $raw !== '' ? $raw : ($u->email ?? $u->address ?? null);
|
||
if (!is_string($email) || !preg_match('/^[^@\s]+@[^@\s]+\.[^@\s]+$/', $email)) {
|
||
continue;
|
||
}
|
||
|
||
[$local, $domain] = explode('@', $email, 2);
|
||
$maildir = "/var/mail/vhosts/{$domain}/{$local}";
|
||
|
||
// Größe in Bytes (rekursiv)
|
||
$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();
|
||
}
|
||
|
||
$isSystemDomain = (bool)($u->domain->is_system ?? false);
|
||
|
||
if ($isSystemDomain || $u->is_system) {
|
||
// systemische Mailboxen fließen nur in die System-Summe ein
|
||
$sumSystemBytes += $usedBytes;
|
||
continue; // KEIN per-user Setting
|
||
}
|
||
|
||
// Message-Count
|
||
$messageCount = $this->countViaDoveadm($email);
|
||
if ($messageCount === null) $messageCount = $this->countViaFilesystem($maildir);
|
||
|
||
$sumUserBytes += $usedBytes;
|
||
|
||
$key = "mailbox.{$email}";
|
||
$prev = (array)(Setting::get($key, []) ?: []);
|
||
$new = [
|
||
'used_bytes' => (int)$usedBytes,
|
||
'message_count' => (int)$messageCount,
|
||
'updated_at' => now()->toDateTimeString(),
|
||
];
|
||
|
||
if (($prev['used_bytes'] ?? null) !== $new['used_bytes']
|
||
|| ($prev['message_count'] ?? null) !== $new['message_count']) {
|
||
Setting::set($key, $new);
|
||
$changed++;
|
||
$this->line(sprintf("%-35s %7.2f MiB %5d msgs",
|
||
$email, $usedBytes / 1048576, $messageCount));
|
||
$log->info('updated', ['email'=>$email]+$new);
|
||
}
|
||
}
|
||
});
|
||
|
||
// Totals persistieren (Nutzen wir später im StorageProbe)
|
||
Setting::set('mailbox.totals', [
|
||
'users_bytes' => (int)$sumUserBytes, // alle nicht-systemischen Mailboxen
|
||
'system_bytes' => (int)$sumSystemBytes, // systemische Mailboxen
|
||
'updated_at' => now()->toIso8601String(),
|
||
]);
|
||
|
||
$ms = (int)((microtime(true)-$t0)*1000);
|
||
$log->info('mail:update-stats DONE', compact('checked','changed','ms','sumUserBytes','sumSystemBytes'));
|
||
$this->info('Mailbox-Statistiken aktualisiert.');
|
||
return self::SUCCESS;
|
||
}
|
||
|
||
|
||
private 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);
|
||
if ($rc !== 0) return null;
|
||
|
||
foreach ($out as $line) {
|
||
if (preg_match('/^\s*INBOX\s+(\d+)\s*$/i', trim($line), $m)) {
|
||
return (int)$m[1];
|
||
}
|
||
}
|
||
return null;
|
||
}
|
||
|
||
private function countViaFilesystem(string $maildir): int
|
||
{
|
||
$n = 0;
|
||
foreach (['cur', 'new'] as $sub) {
|
||
$dir = "{$maildir}/{$sub}";
|
||
if (!is_dir($dir)) continue;
|
||
$h = opendir($dir);
|
||
if (!$h) continue;
|
||
while (($fn = readdir($h)) !== false) {
|
||
if ($fn === '.' || $fn === '..' || $fn[0] === '.') continue;
|
||
$n++;
|
||
}
|
||
closedir($h);
|
||
}
|
||
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;
|
||
//
|
||
//class UpdateMailboxStats extends Command
|
||
//{
|
||
// protected $signature = 'mail:update-stats {--user=}';
|
||
// protected $description = 'Aktualisiert Quota & Nachrichtenzahl (Settings/Redis; ohne MailUser-DB-Felder).';
|
||
//
|
||
// public function handle(): int
|
||
// {
|
||
// $log = Log::channel('mailstats');
|
||
//
|
||
// $onlyUser = trim((string)$this->option('user')) ?: null;
|
||
// $started = microtime(true);
|
||
//
|
||
// $q = MailUser::query()
|
||
// ->with('domain:id,domain,is_system')
|
||
// ->where('is_active', true)
|
||
// ->where('is_system', false) // keine System-Mailboxen
|
||
// ->whereHas('domain', fn($d) => $d->where('is_system', false)); // keine Systemdomains
|
||
//
|
||
// if ($onlyUser) {
|
||
// $q->where('email', $onlyUser);
|
||
// }
|
||
//
|
||
// $users = $q->get();
|
||
//
|
||
// $log->info('mail:update-stats START', [
|
||
// 'only' => $onlyUser,
|
||
// 'users' => $users->count(),
|
||
// ]);
|
||
//
|
||
// if ($users->isEmpty()) {
|
||
// $this->warn('Keine passenden Mailboxen gefunden.');
|
||
// $log->info('mail:update-stats DONE (no users)', ['ms' => (int)((microtime(true) - $started) * 1000)]);
|
||
// return self::SUCCESS;
|
||
// }
|
||
//
|
||
// $changed = 0;
|
||
// $checked = 0;
|
||
//
|
||
// foreach ($users as $u) {
|
||
// $checked++;
|
||
//
|
||
// // Email robust ermitteln
|
||
// $raw = (string)($u->getRawOriginal('email') ?? '');
|
||
// $email = $raw !== '' ? $raw : ($u->email ?? $u->address ?? null);
|
||
//
|
||
// if (!is_string($email) || !preg_match('/^[^@\s]+@[^@\s]+\.[^@\s]+$/', $email)) {
|
||
// // nur debug – vermeidet Spam
|
||
// $log->debug('skip invalid email', ['user_id' => $u->id, 'raw' => $raw, 'computed' => $email]);
|
||
// continue;
|
||
// }
|
||
//
|
||
// [$local, $domain] = explode('@', $email, 2);
|
||
// $maildir = "/var/mail/vhosts/{$domain}/{$local}";
|
||
//
|
||
// // Größe (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();
|
||
// }
|
||
// }
|
||
// }
|
||
//
|
||
// // Nachrichten zählen
|
||
// $messageCount = $this->countViaDoveadm($email);
|
||
// if ($messageCount === null) {
|
||
// $messageCount = $this->countViaFilesystem($maildir);
|
||
// }
|
||
//
|
||
// // Nur bei Änderungen persistieren + loggen
|
||
// $key = "mailbox.{$email}";
|
||
// $prev = Setting::get($key, null) ?: [];
|
||
//
|
||
// $new = [
|
||
// 'used_bytes' => (int)$usedBytes,
|
||
// 'message_count' => (int)$messageCount,
|
||
// 'updated_at' => now()->toDateTimeString(),
|
||
// ];
|
||
//
|
||
// if (
|
||
// !is_array($prev) ||
|
||
// ($prev['used_bytes'] ?? null) !== $new['used_bytes'] ||
|
||
// ($prev['message_count'] ?? null) !== $new['message_count']
|
||
// ) {
|
||
// Setting::set($key, $new);
|
||
// $changed++;
|
||
//
|
||
// // kurze, nützliche Info – nur bei Änderung
|
||
// $this->line(sprintf("%-35s %7.1f MiB %5d msgs",
|
||
// $email, $usedBytes / 1024 / 1024, $messageCount));
|
||
//
|
||
// $log->info('updated', [
|
||
// 'email' => $email,
|
||
// 'used_bytes' => $new['used_bytes'],
|
||
// 'message_count' => $new['message_count'],
|
||
// ]);
|
||
// } else {
|
||
// // keine Änderung → kein Info-Log
|
||
// $log->debug('unchanged', ['email' => $email]);
|
||
// }
|
||
// }
|
||
//
|
||
// $ms = (int)((microtime(true) - $started) * 1000);
|
||
// $log->info('mail:update-stats DONE', [
|
||
// 'checked' => $checked,
|
||
// 'changed' => $changed,
|
||
// 'ms' => $ms,
|
||
// ]);
|
||
//
|
||
// $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);
|
||
//
|
||
// if ($rc !== 0) return null;
|
||
//
|
||
// 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;
|
||
// $h = opendir($dir);
|
||
// if (!$h) continue;
|
||
// while (($fn = readdir($h)) !== false) {
|
||
// if ($fn === '.' || $fn === '..' || $fn[0] === '.') continue;
|
||
// $n++;
|
||
// }
|
||
// closedir($h);
|
||
// }
|
||
// 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;
|
||
//
|
||
//class UpdateMailboxStats extends Command
|
||
//{
|
||
// protected $signature = 'mail:update-stats {--user=}';
|
||
// protected $description = 'Aktualisiert Quota & Nachrichtenzahl (Settings/Redis; DB-frei)';
|
||
//
|
||
// public function handle(): int
|
||
// {
|
||
// Log::info('=== mail:update-stats START ===', ['userOpt' => $this->option('user')]);
|
||
//
|
||
// $q = MailUser::query()
|
||
// ->with('domain:id,domain')
|
||
// ->active();
|
||
//
|
||
// if ($only = trim((string)$this->option('user'))) {
|
||
// $q->byEmail($only);
|
||
// }
|
||
//
|
||
// $users = $q->get();
|
||
// if ($users->isEmpty()) {
|
||
// Log::warning('Keine passenden Mailboxen gefunden.');
|
||
// $this->warn('Keine passenden Mailboxen gefunden.');
|
||
// return self::SUCCESS;
|
||
// }
|
||
//
|
||
// foreach ($users as $u) {
|
||
// // 1) sichere E-Mail ermitteln: roher DB-Wert → Accessor → zusammengesetzt
|
||
// $raw = (string)($u->getRawOriginal('email') ?? '');
|
||
// $email = $raw !== '' ? $raw : ($u->email ?? $u->address ?? '');
|
||
//
|
||
// if (!preg_match('/^[^@\s]+@[^@\s]+\.[^@\s]+$/', (string)$email)) {
|
||
// Log::warning('Ungültige effektive Adresse – skip', [
|
||
// 'user_id' => $u->id, 'raw' => $raw, 'computed' => $email
|
||
// ]);
|
||
// continue;
|
||
// }
|
||
//
|
||
// [$local, $domain] = explode('@', $email, 2);
|
||
// $maildir = "/var/mail/vhosts/{$domain}/{$local}";
|
||
//
|
||
// // 2) Größe (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();
|
||
// }
|
||
// }
|
||
//
|
||
// // 3) Message-Count – prefer doveadm als vmail; Fallback: Files
|
||
// $messageCount = $this->countViaDoveadm($email);
|
||
// if ($messageCount === null) {
|
||
// $messageCount = $this->countViaFilesystem($maildir);
|
||
// }
|
||
//
|
||
// // 4) In Settings (→ Redis + DB-Backup) persistieren
|
||
// Setting::set("mailbox.{$email}", [
|
||
// 'used_bytes' => (int)$usedBytes,
|
||
// 'message_count' => (int)$messageCount,
|
||
// 'updated_at' => now()->toDateTimeString(),
|
||
// ]);
|
||
//
|
||
// $this->line(sprintf("%-35s %7.1f MiB %5d msgs",
|
||
// $email, $usedBytes / 1024 / 1024, $messageCount));
|
||
//
|
||
// Log::info('Mailbox-Stat OK', [
|
||
// 'email' => $email,
|
||
// 'maildir' => $maildir,
|
||
// 'used_bytes' => $usedBytes,
|
||
// 'message_count' => $messageCount,
|
||
// ]);
|
||
// }
|
||
//
|
||
// Log::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);
|
||
// if ($rc !== 0) return null;
|
||
//
|
||
// 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;
|
||
// $h = opendir($dir);
|
||
// if (!$h) continue;
|
||
// while (($fn = readdir($h)) !== false) {
|
||
// if ($fn === '.' || $fn === '..' || $fn[0] === '.') continue;
|
||
// $n++;
|
||
// }
|
||
// closedir($h);
|
||
// }
|
||
// return $n;
|
||
// }
|
||
//}
|
||
|
||
//namespace App\Console\Commands;
|
||
//
|
||
//use Illuminate\Console\Command;
|
||
//use Illuminate\Support\Facades\Log;
|
||
//use App\Models\MailUser;
|
||
//use App\Models\Setting;
|
||
//use RecursiveDirectoryIterator;
|
||
//use RecursiveIteratorIterator;
|
||
|
||
|
||
//class UpdateMailboxStats extends Command
|
||
//{
|
||
// protected $signature = 'mail:update-stats {--user=}';
|
||
// protected $description = 'Aktualisiert Mailquota und Nachrichtenzahl';
|
||
//
|
||
// public function handle(): int
|
||
// {
|
||
// Log::info('=== mail:update-stats START ===', ['userOpt' => $this->option('user')]);
|
||
//
|
||
// $q = MailUser::query()
|
||
// ->with('domain:id,domain') // <— Domain fürs Fallback laden
|
||
// ->where('is_active', true);
|
||
//
|
||
// if ($only = trim((string)$this->option('user'))) {
|
||
// $q->where('email', $only);
|
||
// } else {
|
||
// $q->whereNotNull('email')->where('email', 'like', '%@%');
|
||
// }
|
||
//
|
||
// $users = $q->get();
|
||
// if ($users->isEmpty()) {
|
||
// Log::warning('Keine passenden Mailboxen gefunden.');
|
||
// $this->warn('Keine passenden Mailboxen gefunden.');
|
||
// return self::SUCCESS;
|
||
// }
|
||
//
|
||
// foreach ($users as $u) {
|
||
// // 1) rohen DB-Wert nehmen (ohne Accessor/Casts)
|
||
// $rawEmail = (string) $u->getRawOriginal('email');
|
||
//
|
||
// // 2) Fallback aus localpart + domain, falls roher Wert leer
|
||
// $fallback = ($u->localpart ?? '') . '@' . ($u->domain->domain ?? '');
|
||
// $email = trim($rawEmail !== '' ? $rawEmail : $fallback);
|
||
//
|
||
// if (!preg_match('/^[^@\s]+@[^@\s]+\.[^@\s]+$/', $email)) {
|
||
// Log::warning('Ungültige effektive Adresse – skip', [
|
||
// 'raw' => $rawEmail,
|
||
// 'fallback' => $fallback,
|
||
// 'got' => $email,
|
||
// 'user_id' => $u->id,
|
||
// ]);
|
||
// continue;
|
||
// }
|
||
//
|
||
// [$local, $domain] = explode('@', $email, 2);
|
||
// $maildir = "/var/mail/vhosts/{$domain}/{$local}";
|
||
//
|
||
// // Größe (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();
|
||
// }
|
||
// }
|
||
//
|
||
// // Zähler via doveadm (als vmail), Fallback: Files zählen
|
||
// $messageCount = $this->countViaDoveadm($email);
|
||
// if ($messageCount === null) {
|
||
// $messageCount = $this->countViaFilesystem($maildir);
|
||
// }
|
||
//
|
||
// // Optional zusätzlich cachen (UI liest das bereits)
|
||
// Setting::set("mailbox.{$email}", [
|
||
// 'used_bytes' => (int)$usedBytes,
|
||
// 'message_count' => (int)$messageCount,
|
||
// 'updated_at' => now()->toDateTimeString(),
|
||
// ]);
|
||
//
|
||
// $this->line(sprintf("%-35s %7.1f MiB %5d msgs", $email, $usedBytes/1024/1024, $messageCount));
|
||
// Log::info('Mailbox-Stat OK', compact('email','maildir','usedBytes','messageCount'));
|
||
// }
|
||
//
|
||
// Log::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);
|
||
// if ($rc !== 0) return null;
|
||
// 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;
|
||
// $h = opendir($dir);
|
||
// if (!$h) continue;
|
||
// while (($fn = readdir($h)) !== false) {
|
||
// if ($fn === '.' || $fn === '..' || $fn[0] === '.') continue;
|
||
// $n++;
|
||
// }
|
||
// closedir($h);
|
||
// }
|
||
// 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;
|
||
//
|
||
//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::channel('mailstats')->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<number>"
|
||
// 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 "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;
|
||
// }
|
||
//}
|