parent
45d0071302
commit
45ec38452c
|
|
@ -1,6 +1,5 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
|
||||||
namespace App\Console\Commands;
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
use App\Models\MailUser;
|
use App\Models\MailUser;
|
||||||
|
|
@ -10,7 +9,7 @@ use Symfony\Component\Process\Process;
|
||||||
class UpdateMailboxStats extends Command
|
class UpdateMailboxStats extends Command
|
||||||
{
|
{
|
||||||
protected $signature = 'mail:update-stats {--user=}';
|
protected $signature = 'mail:update-stats {--user=}';
|
||||||
protected $description = 'Aktualisiert Mailquota und Nachrichtenzahl (Dovecot doveadm)';
|
protected $description = 'Aktualisiert Mailquota und Nachrichtenzahl für alle Mailboxen (oder einen spezifischen Benutzer)';
|
||||||
|
|
||||||
public function handle(): int
|
public function handle(): int
|
||||||
{
|
{
|
||||||
|
|
@ -20,121 +19,113 @@ class UpdateMailboxStats extends Command
|
||||||
}
|
}
|
||||||
|
|
||||||
$users = $query->get();
|
$users = $query->get();
|
||||||
|
$rows = [];
|
||||||
|
|
||||||
foreach ($users as $u) {
|
foreach ($users as $u) {
|
||||||
$email = $u->email;
|
$email = (string)$u->email;
|
||||||
|
|
||||||
[$messages, $vsize] = $this->fetchViaDoveadm($email);
|
// E-Mail validieren
|
||||||
|
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
|
||||||
|
$this->warn("Überspringe ungültige Adresse: '{$email}'");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if ($messages === null || $vsize === null) {
|
[$local, $domain] = explode('@', $email, 2);
|
||||||
if (config('mailpool.fallback_du')) {
|
$base = "/var/mail/vhosts/{$domain}/{$local}";
|
||||||
[$messages, $vsize] = $this->fallbackDu($email);
|
|
||||||
}
|
// ---- 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([
|
$u->update([
|
||||||
'message_count' => (int)($messages ?? 0),
|
'used_bytes' => $usedBytes,
|
||||||
'used_bytes' => (int)($vsize ?? 0),
|
'message_count' => $messageCount,
|
||||||
'stats_refreshed_at' => now(),
|
'stats_refreshed_at'=> now(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$this->line(sprintf(
|
$rows[] = sprintf(
|
||||||
'%-35s %6.1f MiB %5d msgs',
|
"%-35s %6.1f MiB %4d msgs",
|
||||||
$email,
|
$email,
|
||||||
($vsize ?? 0) / 1024 / 1024,
|
$usedBytes / 1024 / 1024,
|
||||||
$messages ?? 0
|
$messageCount
|
||||||
));
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->info('Mailbox-Statistiken aktualisiert.');
|
foreach ($rows as $line) {
|
||||||
return Command::SUCCESS;
|
$this->info($line);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @return array{0:int|null,1:int|null} [messages, vsize] */
|
return self::SUCCESS;
|
||||||
private function fetchViaDoveadm(string $email): array
|
}
|
||||||
|
|
||||||
|
private function dirSizeBytes(string $path): int
|
||||||
{
|
{
|
||||||
$bin = config('mailpool.doveadm');
|
// du -sb (schnell & korrekt, braucht nur r-x für Verzeichnisse)
|
||||||
$sudo = config('mailpool.doveadm_sudo');
|
$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;
|
||||||
|
}
|
||||||
|
|
||||||
$cmd = [$bin, 'mailbox', 'status', '-u', $email, 'messages', 'vsize', 'INBOX'];
|
private function countMessages(string $email, string $base): int
|
||||||
if ($sudo) array_unshift($cmd, 'sudo', '-n');
|
{
|
||||||
|
// 1) Bevorzugt: doveadm
|
||||||
$p = new Process($cmd);
|
$cmd = 'doveadm mailbox status -u '.escapeshellarg($email).' messages INBOX';
|
||||||
$p->setTimeout(10);
|
$p = Process::fromShellCommandline($cmd);
|
||||||
$p->run();
|
$p->run();
|
||||||
|
|
||||||
if (!$p->isSuccessful()) {
|
if ($p->isSuccessful()) {
|
||||||
return [null, null];
|
|
||||||
}
|
|
||||||
|
|
||||||
$out = trim($p->getOutput());
|
$out = trim($p->getOutput());
|
||||||
$messages = preg_match('/\bmessages=(\d+)/', $out, $m) ? (int)$m[1] : null;
|
if (preg_match('/messages=(\d+)/', $out, $m)) {
|
||||||
$vsize = preg_match('/\bvsize=(\d+)/', $out, $m) ? (int)$m[1] : null;
|
return (int)$m[1];
|
||||||
|
}
|
||||||
return [$messages, $vsize];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Fallback mit du (nur Größe, keine Message-Zählung) */
|
// 2) Fallback: Maildir zählen (INBOX: root/cur + root/new)
|
||||||
private function fallbackDu(string $email): array
|
$count = 0;
|
||||||
{
|
|
||||||
[$local, $domain] = explode('@', $email, 2);
|
|
||||||
$path = rtrim(config('mailpool.mailroot'), '/') . "/{$domain}/{$local}";
|
|
||||||
|
|
||||||
$p = new Process(['bash', '-lc', 'du -sb ' . escapeshellarg($path) . ' 2>/dev/null | cut -f1']);
|
$paths = [
|
||||||
$p->setTimeout(10)->run();
|
// 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',
|
||||||
|
];
|
||||||
|
|
||||||
$bytes = $p->isSuccessful() ? (int)trim($p->getOutput()) : 0;
|
foreach ($paths as $p) {
|
||||||
return [null, $bytes];
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//
|
|
||||||
//namespace App\Console\Commands;
|
|
||||||
//
|
|
||||||
//use App\Models\MailUser;
|
|
||||||
//use Illuminate\Console\Command;
|
|
||||||
//
|
|
||||||
//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();
|
|
||||||
//
|
|
||||||
// foreach ($users as $u) {
|
|
||||||
// $email = $u->email;
|
|
||||||
// $domain = explode('@', $email)[1];
|
|
||||||
// $local = explode('@', $email)[0];
|
|
||||||
// $path = "/var/mail/vhosts/{$domain}/{$local}";
|
|
||||||
//
|
|
||||||
// $usedBytes = 0;
|
|
||||||
// $messageCount = 0;
|
|
||||||
//
|
|
||||||
// if (is_dir($path)) {
|
|
||||||
// $usedBytes = (int) trim(shell_exec('du -sb '.escapeshellarg($path).' 2>/dev/null | cut -f1'));
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// $out = trim(shell_exec('doveadm mailbox status -u '.escapeshellarg($email).' messages INBOX 2>/dev/null'));
|
|
||||||
// if (preg_match('/messages=(\d+)/', $out, $m)) {
|
|
||||||
// $messageCount = (int) $m[1];
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// $u->update([
|
|
||||||
// 'used_bytes' => $usedBytes,
|
|
||||||
// 'message_count' => $messageCount,
|
|
||||||
// 'stats_refreshed_at' => now(),
|
|
||||||
// ]);
|
|
||||||
//
|
|
||||||
// $this->info(sprintf("%-35s %6.1f MiB %4d Nachrichten", $email, $usedBytes / 1024 / 1024, $messageCount));
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// return Command::SUCCESS;
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue