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\Jobs;
|
||||
|
||||
use App\Models\HostingAccount;
|
||||
use App\Models\HostingNode;
|
||||
use App\Services\Hosting\NodeCapacityService;
|
||||
use App\Services\Hosting\Providers\SharedNodeProvider;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class ProvisionHostingAccountJob implements ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
public int $tries = 3;
|
||||
public int $timeout = 300; // 5 minutes for SSH operations
|
||||
public array $backoff = [30, 60, 120]; // Retry after 30s, 60s, 120s
|
||||
|
||||
public function __construct(
|
||||
private int $accountId,
|
||||
) {
|
||||
}
|
||||
|
||||
public function handle(SharedNodeProvider $provider, NodeCapacityService $capacityService): void
|
||||
{
|
||||
$account = HostingAccount::with(['node', 'product'])->find($this->accountId);
|
||||
|
||||
if (! $account) {
|
||||
Log::warning('ProvisionHostingAccountJob: Account not found', ['account_id' => $this->accountId]);
|
||||
return;
|
||||
}
|
||||
|
||||
// Skip if already provisioned or not in pending status
|
||||
if ($account->status !== 'pending' && $account->provisioned_at !== null) {
|
||||
Log::info('ProvisionHostingAccountJob: Already provisioned', ['account_id' => $this->accountId, 'status' => $account->status]);
|
||||
return;
|
||||
}
|
||||
|
||||
$node = $account->node;
|
||||
if (! $node) {
|
||||
Log::error('ProvisionHostingAccountJob: No node assigned', ['account_id' => $this->accountId]);
|
||||
$account->update(['status' => 'failed', 'notes' => 'No hosting node assigned']);
|
||||
return;
|
||||
}
|
||||
|
||||
$product = $account->product;
|
||||
if (! $product) {
|
||||
Log::error('ProvisionHostingAccountJob: No product found', ['account_id' => $this->accountId]);
|
||||
$account->update(['status' => 'failed', 'notes' => 'No hosting product found']);
|
||||
return;
|
||||
}
|
||||
|
||||
// Mark as provisioning
|
||||
$account->update(['status' => 'provisioning']);
|
||||
|
||||
try {
|
||||
$password = \Illuminate\Support\Str::password(16, true, true, false, false);
|
||||
$domain = $account->primary_domain ?? $account->username . '.ladill.com';
|
||||
|
||||
$result = $provider->createAccountOnNode($node, [
|
||||
'username' => $account->username,
|
||||
'password' => $password,
|
||||
'domain' => $domain,
|
||||
'php_version' => '8.4',
|
||||
'disk_quota_mb' => max((int) ($product->disk_gb ?? 0), 1) * 1024,
|
||||
]);
|
||||
|
||||
$docRoot = $result['document_root'] ?? "/home/{$account->username}/public_html";
|
||||
|
||||
$account->update([
|
||||
'status' => 'active',
|
||||
'home_directory' => $result['home_directory'] ?? "/home/{$account->username}",
|
||||
'provisioned_at' => now(),
|
||||
'metadata' => array_merge((array) $account->metadata, [
|
||||
'provisioned_password' => $password, // Store temporarily for admin reference
|
||||
'provisioned_at' => now()->toISOString(),
|
||||
]),
|
||||
]);
|
||||
|
||||
// Apply resource limits
|
||||
$provider->applyAccountResourceProfile($account->fresh(), false);
|
||||
|
||||
// Install WordPress if it's a WordPress hosting product
|
||||
if ($product->type === 'wordpress') {
|
||||
$wpResult = $provider->installWordPress($node, $account->username, $domain, [
|
||||
'document_root' => $docRoot,
|
||||
'admin_email' => $account->user?->email ?? 'admin@' . $domain,
|
||||
'site_title' => $domain,
|
||||
]);
|
||||
|
||||
// Store WordPress credentials in account metadata
|
||||
$account->update([
|
||||
'metadata' => array_merge((array) $account->metadata, [
|
||||
'wp_admin_user' => $wpResult['admin_user'] ?? 'admin',
|
||||
'wp_admin_password' => $wpResult['admin_password'] ?? null,
|
||||
'wp_db_name' => $wpResult['db_name'] ?? null,
|
||||
'wp_db_user' => $wpResult['db_user'] ?? null,
|
||||
'wp_db_password' => $wpResult['db_password'] ?? null,
|
||||
]),
|
||||
]);
|
||||
|
||||
Log::info('ProvisionHostingAccountJob: WordPress installed', [
|
||||
'account_id' => $this->accountId,
|
||||
'domain' => $domain,
|
||||
]);
|
||||
}
|
||||
|
||||
// Refresh node capacity
|
||||
$capacityService->refreshNodeResourceTotals($node);
|
||||
|
||||
Log::info('ProvisionHostingAccountJob: Successfully provisioned', [
|
||||
'account_id' => $this->accountId,
|
||||
'username' => $account->username,
|
||||
'node_id' => $node->id,
|
||||
]);
|
||||
} catch (\Throwable $exception) {
|
||||
Log::error('ProvisionHostingAccountJob: Failed to provision', [
|
||||
'account_id' => $this->accountId,
|
||||
'username' => $account->username,
|
||||
'error' => $exception->getMessage(),
|
||||
'trace' => $exception->getTraceAsString(),
|
||||
]);
|
||||
|
||||
$account->update([
|
||||
'status' => 'failed',
|
||||
'notes' => 'Provisioning failed: ' . $exception->getMessage(),
|
||||
]);
|
||||
|
||||
// Re-throw to trigger retry
|
||||
throw $exception;
|
||||
}
|
||||
}
|
||||
|
||||
public function failed(\Throwable $exception): void
|
||||
{
|
||||
Log::error('ProvisionHostingAccountJob: Job failed after all retries', [
|
||||
'account_id' => $this->accountId,
|
||||
'error' => $exception->getMessage(),
|
||||
]);
|
||||
|
||||
$account = HostingAccount::find($this->accountId);
|
||||
if ($account) {
|
||||
$account->update([
|
||||
'status' => 'failed',
|
||||
'notes' => 'Provisioning failed after retries: ' . $exception->getMessage(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user