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,124 @@
|
||||
<?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()}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user