Files
ladill-hosting/app/Console/Commands/ProcessExpiredHostingAccountsCommand.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

125 lines
4.8 KiB
PHP

<?php
namespace App\Console\Commands;
use App\Models\HostingAccount;
use App\Notifications\HostingSuspendedNotification;
use App\Services\Hosting\HostingProvisioningService;
use App\Services\Hosting\Providers\SharedNodeProvider;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Log;
class ProcessExpiredHostingAccountsCommand extends Command
{
private const EXPIRY_SUSPENSION_GRACE_DAYS = 7;
protected $signature = 'hosting:process-expired-accounts';
protected $description = 'Suspend expired hosting accounts (serve expired page) and terminate those past the grace period';
public function handle(SharedNodeProvider $nodeProvider, HostingProvisioningService $provisioning): int
{
$this->suspendNewlyExpired($nodeProvider);
$this->terminatePastGracePeriod($provisioning);
return self::SUCCESS;
}
private function suspendNewlyExpired(SharedNodeProvider $nodeProvider): void
{
$accounts = HostingAccount::query()
->where('status', HostingAccount::STATUS_ACTIVE)
->whereNotNull('expires_at')
->where('expires_at', '<', now()->subDays(self::EXPIRY_SUSPENSION_GRACE_DAYS))
->with(['node', 'sites', 'user'])
->get();
if ($accounts->isEmpty()) {
$this->info('No newly expired accounts to suspend.');
return;
}
$this->info("Found {$accounts->count()} expired account(s) to suspend.");
foreach ($accounts as $account) {
try {
$nodeProvider->serveExpiredPage($account);
$reason = 'Hosting account expired on '.$account->expires_at->format('M d, Y').'. Renew to restore service.';
$account->update([
'status' => HostingAccount::STATUS_SUSPENDED,
'suspension_reason' => $reason,
'suspended_at' => now(),
'metadata' => array_merge((array) ($account->metadata ?? []), [
'suspension_origin' => 'expiry',
'expired_at' => $account->expires_at->toIso8601String(),
'expiry_suspension_grace_days' => self::EXPIRY_SUSPENSION_GRACE_DAYS,
'grace_period_ends_at' => $account->gracePeriodEndsAt()->toIso8601String(),
]),
]);
if ($account->user) {
$account->user->notify(new HostingSuspendedNotification($account->fresh(), $reason));
}
Log::info('ProcessExpiredHostingAccounts: Suspended expired account', [
'account_id' => $account->id,
'username' => $account->username,
'expired_at' => $account->expires_at,
]);
$this->line(" Suspended: {$account->username} (expired {$account->expires_at->format('M d, Y')})");
} catch (\Throwable $e) {
Log::error('ProcessExpiredHostingAccounts: Failed to suspend expired account', [
'account_id' => $account->id,
'error' => $e->getMessage(),
]);
$this->error(" Failed to suspend {$account->username}: {$e->getMessage()}");
}
}
}
private function terminatePastGracePeriod(HostingProvisioningService $provisioning): void
{
$graceDeadline = now()->subMonths(HostingAccount::GRACE_PERIOD_MONTHS);
$accounts = HostingAccount::query()
->where('status', HostingAccount::STATUS_SUSPENDED)
->whereNotNull('expires_at')
->where('expires_at', '<', $graceDeadline)
->whereJsonContains('metadata->suspension_origin', 'expiry')
->with(['node', 'sites'])
->get();
if ($accounts->isEmpty()) {
$this->info('No accounts past grace period to terminate.');
return;
}
$this->info("Found {$accounts->count()} account(s) past grace period to terminate.");
foreach ($accounts as $account) {
try {
$provisioning->terminateAccount($account);
Log::info('ProcessExpiredHostingAccounts: Terminated account past grace period', [
'account_id' => $account->id,
'username' => $account->username,
'expired_at' => $account->expires_at,
]);
$this->line(" Terminated: {$account->username} (expired {$account->expires_at->format('M d, Y')})");
} catch (\Throwable $e) {
Log::error('ProcessExpiredHostingAccounts: Failed to terminate account', [
'account_id' => $account->id,
'error' => $e->getMessage(),
]);
$this->error(" Failed to terminate {$account->username}: {$e->getMessage()}");
}
}
}
}