Fix subdomain DNS messaging for Ladill nameservers and retire pending-setup primaries.
Deploy Ladill Hosting / deploy (push) Successful in 47s
Deploy Ladill Hosting / deploy (push) Successful in 47s
Treat ns_auto domains as managed DNS, publish subdomain A records via PowerDNS without failing the UX when the local DNS registry is missing, and promote real linked domains over pending-setup.local placeholders. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1162,16 +1162,23 @@ class HostingPanelController extends Controller
|
||||
|
||||
// Restore the soft-deleted site and recreate nginx config
|
||||
$existingSite->restore();
|
||||
$placeholders = app(\App\Services\Hosting\PlaceholderPrimaryDomainService::class);
|
||||
$promoteToPrimary = $placeholders->shouldBecomePrimary($account, $domainHost);
|
||||
|
||||
$existingSite->fill([
|
||||
'document_root' => $docRoot,
|
||||
'php_version' => $account->php_version ?? $existingSite->php_version ?? '8.2',
|
||||
'status' => 'active',
|
||||
'type' => $existingSite->type ?: 'addon',
|
||||
'type' => $promoteToPrimary ? 'primary' : ($existingSite->type ?: 'addon'),
|
||||
])->save();
|
||||
try {
|
||||
$existingSite->loadMissing(['account.node']);
|
||||
$this->runtimeFor($account)->addSite($existingSite);
|
||||
|
||||
if ($promoteToPrimary) {
|
||||
$placeholders->promoteSite($account, $existingSite);
|
||||
}
|
||||
|
||||
// Queue SSL provisioning for restored domain
|
||||
ProvisionHostingSslJob::dispatch($existingSite->id)->delay(now()->addMinutes(2));
|
||||
|
||||
@@ -1256,12 +1263,15 @@ class HostingPanelController extends Controller
|
||||
]);
|
||||
}
|
||||
|
||||
$placeholders = app(\App\Services\Hosting\PlaceholderPrimaryDomainService::class);
|
||||
$promoteToPrimary = $placeholders->shouldBecomePrimary($account, $domainHost);
|
||||
|
||||
$site = HostedSite::create([
|
||||
'hosting_account_id' => $account->id,
|
||||
'domain_id' => $domain->id,
|
||||
'domain' => $domainHost,
|
||||
'document_root' => $docRoot,
|
||||
'type' => 'addon',
|
||||
'type' => $promoteToPrimary ? 'primary' : 'addon',
|
||||
'php_version' => $account->php_version ?? '8.2',
|
||||
'ssl_enabled' => false,
|
||||
'status' => 'active',
|
||||
@@ -1270,6 +1280,10 @@ class HostingPanelController extends Controller
|
||||
try {
|
||||
$this->runtimeFor($account)->addSite($site);
|
||||
|
||||
if ($promoteToPrimary) {
|
||||
$placeholders->promoteSite($account, $site);
|
||||
}
|
||||
|
||||
// Queue DNS zone provisioning (manual pack is local-only).
|
||||
if ($onboardingMode === Domain::MODE_MANUAL_DNS) {
|
||||
$verification->prepareManualDnsPack($domain);
|
||||
@@ -1368,14 +1382,23 @@ class HostingPanelController extends Controller
|
||||
try {
|
||||
$existingSite->loadMissing(['account.node']);
|
||||
$this->runtimeFor($account)->addSite($existingSite);
|
||||
$this->provisionSubdomainDns($account, $parent, $label, $fqdn, $serverIp, $pdns);
|
||||
$dnsResult = $this->provisionSubdomainDns($account, $parent, $label, $fqdn, $serverIp, $pdns);
|
||||
if (! empty($dnsResult['domain_id']) && (int) $existingSite->domain_id !== (int) $dnsResult['domain_id']) {
|
||||
$existingSite->update(['domain_id' => $dnsResult['domain_id']]);
|
||||
}
|
||||
ProvisionHostingSslJob::dispatch($existingSite->id)->delay(now()->addMinutes(2));
|
||||
|
||||
$message = match ($dnsResult['status']) {
|
||||
'managed' => "Subdomain {$fqdn} has been restored. DNS was updated on Ladill nameservers; SSL will provision automatically.",
|
||||
'managed_failed' => "Subdomain {$fqdn} has been restored. Ladill nameservers are in use, but we could not publish the A record automatically. Check DNS for {$parent->domain} in Ladill Domains.",
|
||||
default => "Subdomain {$fqdn} has been restored. Because this domain uses external DNS, add an A record for {$fqdn} → {$serverIp}. SSL will provision automatically.",
|
||||
};
|
||||
|
||||
if ($request->wantsJson()) {
|
||||
return response()->json(['success' => true, 'message' => "Subdomain {$fqdn} has been restored."]);
|
||||
return response()->json(['success' => true, 'message' => $message]);
|
||||
}
|
||||
|
||||
return back()->with('success', "Subdomain {$fqdn} has been restored. SSL will be provisioned automatically.");
|
||||
return back()->with('success', $message);
|
||||
} catch (\Exception $e) {
|
||||
$existingSite->delete();
|
||||
Log::error('Failed to restore subdomain nginx config: '.$e->getMessage());
|
||||
@@ -1416,12 +1439,17 @@ class HostingPanelController extends Controller
|
||||
|
||||
try {
|
||||
$this->runtimeFor($account)->addSite($site);
|
||||
$dnsManaged = $this->provisionSubdomainDns($account, $parent, $label, $fqdn, $serverIp, $pdns);
|
||||
$dnsResult = $this->provisionSubdomainDns($account, $parent, $label, $fqdn, $serverIp, $pdns);
|
||||
if (! empty($dnsResult['domain_id']) && (int) $site->domain_id !== (int) $dnsResult['domain_id']) {
|
||||
$site->update(['domain_id' => $dnsResult['domain_id']]);
|
||||
}
|
||||
ProvisionHostingSslJob::dispatch($site->id)->delay(now()->addMinutes(2));
|
||||
|
||||
$message = $dnsManaged
|
||||
? "Subdomain {$fqdn} created. DNS A record was added for the parent zone; SSL will provision automatically."
|
||||
: "Subdomain {$fqdn} created. Point an A record for {$fqdn} to {$serverIp}, then SSL will provision automatically.";
|
||||
$message = match ($dnsResult['status']) {
|
||||
'managed' => "Subdomain {$fqdn} created. DNS was updated on Ladill nameservers; SSL will provision automatically.",
|
||||
'managed_failed' => "Subdomain {$fqdn} created. Ladill nameservers are in use, but we could not publish the A record automatically. Check DNS for {$parent->domain} in Ladill Domains (expect {$fqdn} → {$serverIp}). SSL will retry once DNS resolves.",
|
||||
default => "Subdomain {$fqdn} created. Because this domain uses external DNS, add an A record for {$fqdn} → {$serverIp}, then SSL will provision automatically.",
|
||||
};
|
||||
|
||||
if ($request->wantsJson()) {
|
||||
return response()->json(['success' => true, 'message' => $message]);
|
||||
@@ -1461,7 +1489,7 @@ class HostingPanelController extends Controller
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool True when DNS was managed/updated automatically
|
||||
* @return array{status: 'managed'|'managed_failed'|'manual', domain_id: ?int}
|
||||
*/
|
||||
private function provisionSubdomainDns(
|
||||
HostingAccount $account,
|
||||
@@ -1470,14 +1498,20 @@ class HostingPanelController extends Controller
|
||||
string $fqdn,
|
||||
string $serverIp,
|
||||
PowerDnsClient $pdns,
|
||||
): bool {
|
||||
// HostedSite has both a `domain` column and a domain() relation — load via domain_id.
|
||||
$parentDomain = $parent->domain_id
|
||||
? Domain::query()->find($parent->domain_id)
|
||||
: null;
|
||||
): array {
|
||||
$parentDomain = $this->resolveParentDomainRecord($parent);
|
||||
|
||||
if (! $parentDomain || $parentDomain->dns_mode !== 'managed') {
|
||||
return false;
|
||||
if (! $parentDomain || ! $parentDomain->usesManagedDns()) {
|
||||
return ['status' => 'manual', 'domain_id' => $parentDomain?->id];
|
||||
}
|
||||
|
||||
// Heal legacy rows that were on ns_auto but never stamped dns_mode=managed.
|
||||
if ((string) $parentDomain->dns_mode !== 'managed') {
|
||||
$parentDomain->update(['dns_mode' => 'managed']);
|
||||
}
|
||||
|
||||
if (! $parent->domain_id) {
|
||||
$parent->update(['domain_id' => $parentDomain->id]);
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -1487,7 +1521,18 @@ class HostingPanelController extends Controller
|
||||
'ttl' => 3600,
|
||||
'contents' => [$serverIp],
|
||||
]]);
|
||||
} catch (\Throwable $e) {
|
||||
Log::warning('Failed to provision subdomain DNS record', [
|
||||
'fqdn' => $fqdn,
|
||||
'parent_domain_id' => $parentDomain->id,
|
||||
'error' => $e->getMessage(),
|
||||
]);
|
||||
|
||||
return ['status' => 'managed_failed', 'domain_id' => $parentDomain->id];
|
||||
}
|
||||
|
||||
// Local registry is best-effort; PowerDNS publish is what matters for Ladill NS.
|
||||
try {
|
||||
DomainDnsRecord::query()->updateOrCreate(
|
||||
[
|
||||
'domain_id' => $parentDomain->id,
|
||||
@@ -1503,17 +1548,32 @@ class HostingPanelController extends Controller
|
||||
'last_checked_at' => now(),
|
||||
],
|
||||
);
|
||||
|
||||
return true;
|
||||
} catch (\Throwable $e) {
|
||||
Log::warning('Failed to provision subdomain DNS record', [
|
||||
Log::info('Subdomain DNS published but local domain_dns_records sync failed', [
|
||||
'fqdn' => $fqdn,
|
||||
'parent_domain_id' => $parentDomain->id,
|
||||
'error' => $e->getMessage(),
|
||||
]);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return ['status' => 'managed', 'domain_id' => $parentDomain->id];
|
||||
}
|
||||
|
||||
private function resolveParentDomainRecord(HostedSite $parent): ?Domain
|
||||
{
|
||||
if ($parent->domain_id) {
|
||||
$byId = Domain::query()->find($parent->domain_id);
|
||||
if ($byId) {
|
||||
return $byId;
|
||||
}
|
||||
}
|
||||
|
||||
$host = strtolower(trim((string) $parent->getAttribute('domain')));
|
||||
if ($host === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
return Domain::query()->where('host', $host)->first();
|
||||
}
|
||||
|
||||
private function cleanupSubdomainDns(HostedSite $site): void
|
||||
@@ -1523,7 +1583,7 @@ class HostingPanelController extends Controller
|
||||
}
|
||||
|
||||
$parentDomain = Domain::query()->find($site->domain_id);
|
||||
if (! $parentDomain || $parentDomain->dns_mode !== 'managed') {
|
||||
if (! $parentDomain || ! $parentDomain->usesManagedDns()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1732,17 +1792,33 @@ class HostingPanelController extends Controller
|
||||
|
||||
try {
|
||||
$this->runtimeFor($account)->requestLetsEncryptCertificate($site);
|
||||
$expiresAt = now()->addDays(90);
|
||||
$site->update([
|
||||
'ssl_enabled' => true,
|
||||
'ssl_status' => 'issued',
|
||||
'ssl_provisioned_at' => now(),
|
||||
'ssl_expires_at' => $expiresAt,
|
||||
'ssl_error' => null,
|
||||
]);
|
||||
|
||||
if ($site->domain_id) {
|
||||
Domain::query()->whereKey($site->domain_id)->update([
|
||||
'ssl_status' => 'active',
|
||||
'ssl_expires_at' => $expiresAt,
|
||||
]);
|
||||
}
|
||||
|
||||
app(\App\Services\Ssl\CentralSslRegistry::class)->register($site->domain, $expiresAt);
|
||||
|
||||
return back()->with('success', "SSL certificate issued for {$site->domain} successfully.");
|
||||
} catch (\Exception $e) {
|
||||
Log::error("Failed to request SSL: " . $e->getMessage());
|
||||
|
||||
$site->update([
|
||||
'ssl_status' => 'failed',
|
||||
'ssl_error' => $e->getMessage(),
|
||||
]);
|
||||
|
||||
$message = trim($e->getMessage());
|
||||
|
||||
return back()->with(
|
||||
|
||||
Reference in New Issue
Block a user