Files
ladill-hosting/app/Services/Mail/EmailServerPoolService.php
T
isaaccladandCursor e251a4cf60
Deploy Ladill Hosting / deploy (push) Failing after 17s
Initial Ladill Hosting app with Gitea deploy pipeline.
Shared web hosting extracted from the platform monolith, with CI deploy
to /var/www/ladill-hosting matching Bird/Domains/Email.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-06 16:24:20 +00:00

155 lines
4.7 KiB
PHP

<?php
namespace App\Services\Mail;
use App\Models\EmailDomain;
use App\Models\EmailServer;
use App\Models\Mailbox;
use Illuminate\Support\Collection;
class EmailServerPoolService
{
public function poolRoot(EmailServer $server): EmailServer
{
return $server->poolRoot();
}
/**
* @return Collection<int, EmailServer>
*/
public function poolMembers(EmailServer $server): Collection
{
$root = $this->poolRoot($server);
return EmailServer::query()
->where(function ($query) use ($root) {
$query->where('id', $root->id)
->orWhere('primary_email_server_id', $root->id);
})
->whereNotIn('status', [EmailServer::STATUS_DECOMMISSIONED])
->orderBy('role')
->get();
}
public function poolDiskGb(EmailServer $server): int
{
return (int) $this->poolMembers($server)->sum('disk_gb');
}
public function poolUsedStorageMb(EmailServer $server): int
{
return (int) $this->poolMembers($server)->sum('used_storage_mb');
}
public function poolAllocatedStorageMb(EmailServer $server): int
{
$memberIds = $this->poolMembers($server)->pluck('id');
$fromEmailDomains = EmailDomain::query()
->whereIn('email_server_id', $memberIds)
->withSum('mailboxes as quota_sum', 'quota_mb')
->get()
->sum(fn (EmailDomain $d) => (int) ($d->quota_sum ?? 0));
$fromWebsiteDomains = Mailbox::query()
->whereHas('domain', fn ($q) => $q->whereIn('email_server_id', $memberIds))
->sum('quota_mb');
return (int) ($fromEmailDomains + $fromWebsiteDomains);
}
public function poolDomainCount(EmailServer $server): int
{
$memberIds = $this->poolMembers($server)->pluck('id');
$standalone = EmailDomain::query()->whereIn('email_server_id', $memberIds)->count();
$website = \App\Models\Domain::query()->whereIn('email_server_id', $memberIds)->count();
return $standalone + $website;
}
public function poolMailboxCount(EmailServer $server): int
{
$memberIds = $this->poolMembers($server)->pluck('id');
$standalone = Mailbox::query()
->whereHas('emailDomain', fn ($q) => $q->whereIn('email_server_id', $memberIds))
->count();
$website = Mailbox::query()
->whereHas('domain', fn ($q) => $q->whereIn('email_server_id', $memberIds))
->count();
return $standalone + $website;
}
/**
* Pick the pool member with the most free physical disk for new mail storage.
*/
public function findStorageMember(EmailServer $poolRoot): ?EmailServer
{
$members = $this->poolMembers($poolRoot)
->filter(fn (EmailServer $m) => in_array($m->status, [EmailServer::STATUS_ACTIVE, EmailServer::STATUS_FULL], true));
if ($members->isEmpty()) {
return null;
}
return $members
->sortByDesc(function (EmailServer $member) {
$disk = (int) ($member->disk_gb ?? 0);
$usedGb = $this->memberUsedStorageGb($member);
return max($disk - $usedGb, 0);
})
->first();
}
public function memberUsedStorageGb(EmailServer $member): float
{
return ((int) $member->used_storage_mb) / 1024;
}
public function poolPressurePercent(EmailServer $server): float
{
$poolDisk = $this->poolDiskGb($server);
if ($poolDisk <= 0) {
return 0;
}
$usedGb = $this->poolUsedStorageMb($server) / 1024;
return round(($usedGb / $poolDisk) * 100, 2);
}
public function syncExtensionCredentials(EmailServer $primary): void
{
if (! $primary->isPrimary()) {
return;
}
$primary->extensions()->update([
'mail_host' => $primary->mail_host,
'db_host' => $primary->db_host,
'db_port' => $primary->db_port,
'db_database' => $primary->db_database,
'db_username' => $primary->db_username,
'db_password' => $primary->db_password,
]);
}
public function syncExtensionFromPrimary(EmailServer $extension, EmailServer $primary): void
{
$extension->update([
'mail_host' => $primary->mail_host,
'role' => EmailServer::ROLE_EXTENSION,
'primary_email_server_id' => $primary->id,
'db_host' => $primary->db_host,
'db_port' => $primary->db_port,
'db_database' => $primary->db_database,
'db_username' => $primary->db_username,
'db_password' => $primary->db_password,
]);
}
}