Initial Ladill Hosting app with Gitea deploy pipeline.
Deploy Ladill Hosting / deploy (push) Failing after 17s
Deploy Ladill Hosting / deploy (push) Failing after 17s
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>
This commit is contained in:
@@ -0,0 +1,154 @@
|
||||
<?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,
|
||||
]);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user