mirror of
https://github.com/Vateron-Media/XC_VM.git
synced 2026-10-31 11:03:22 +01:00
feat(lb): sync the cert to the front at the serve-only cutover (Sourcery #8)
A node converted to serve-only while it already holds a valid cert would serve the installer's self-signed server.crt on the Go front until the cert is next due, because syncFrontCert only runs on a certbot_generate and the daily cron sends none while the cert is valid. Queue one certbot_generate at the cutover (MAIN has the domain; a serve-only node has no DB to resolve it itself) so the node's existing-cert path runs syncFrontCert within the minute; a fresh node issues via the webroot. Skipped when the node has only an IP (no http-01 name).
This commit is contained in:
@@ -4,6 +4,7 @@ namespace XcVm\Cli\Commands;
|
||||
|
||||
use XcVm\Cli\CommandInterface;
|
||||
use XcVm\Core\Backup\BackupService;
|
||||
use XcVm\Core\Cluster\NodeActions;
|
||||
use XcVm\Core\Cluster\StreamRecords;
|
||||
use XcVm\Core\Cluster\StreamVersions;
|
||||
use XcVm\Core\Config\SettingsManager;
|
||||
@@ -371,6 +372,22 @@ class ServerInstallCommand implements CommandInterface {
|
||||
// http-nginx + php-fpm and the Go front is the sole listener; without it
|
||||
// the node stays dual-stacked until the next restart. Serve-only only.
|
||||
call_user_func($rRunSSH, $rConn, 'sudo systemctl restart xc_vm');
|
||||
// Sync the node's real cert to the Go front now: a node converted to
|
||||
// serve-only while it already holds a valid cert would otherwise serve the
|
||||
// installer's self-signed server.crt until the cert is next due (the daily
|
||||
// cron sends no certbot_generate while it is valid). Queue one — the node's
|
||||
// existing-cert path runs syncFrontCert (a fresh node issues via webroot).
|
||||
// Skipped when the node has only an IP (no http-01 name).
|
||||
$rCertDomains = [];
|
||||
foreach (explode(',', (string) ($rServers[$rServerID]['domain_name'] ?? '')) as $rCertDomain) {
|
||||
$rCertDomain = trim($rCertDomain);
|
||||
if ($rCertDomain !== '' && !filter_var($rCertDomain, FILTER_VALIDATE_IP)) {
|
||||
$rCertDomains[] = $rCertDomain;
|
||||
}
|
||||
}
|
||||
if ($rCertDomains !== []) {
|
||||
NodeActions::certbot($rServerID, $rCertDomains, $db);
|
||||
}
|
||||
} else {
|
||||
// Downgrade out of serve-only: the service already (re)started above
|
||||
// (finalizeHostAfterRuntime) while the marker was still present, so it booted
|
||||
|
||||
@@ -35,6 +35,25 @@ final class CertbotServeOnlyTest extends TestCase {
|
||||
$this->assertMatchesRegularExpression("/\\} else \\{[^}]*--standalone/", $rSrc, 'the other branch keeps --standalone');
|
||||
}
|
||||
|
||||
public function testServeOnlyCutoverQueuesACertbotSyncForTheFront(): void {
|
||||
// A node converted to serve-only while it already holds a valid cert would serve the
|
||||
// installer's self-signed server.crt until the cert is next due (the daily cron sends
|
||||
// no certbot_generate while it is valid). The cutover queues one certbot_generate so
|
||||
// the node's existing-cert path runs syncFrontCert within the minute; skipped when the
|
||||
// node has only an IP (no http-01 name).
|
||||
$rCmd = (string) file_get_contents(MAIN_HOME . 'Cli/Commands/ServerInstallCommand.php');
|
||||
$this->assertMatchesRegularExpression(
|
||||
"/if \\(\\\$rServeOnly\\) \\{.*NodeActions::certbot\\(\\\$rServerID, \\\$rCertDomains, \\\$db\\)/s",
|
||||
$rCmd,
|
||||
'the serve-only cutover queues a certbot_generate so an existing cert reaches the front'
|
||||
);
|
||||
$this->assertMatchesRegularExpression(
|
||||
"/if \\(\\\$rCertDomains !== \\[\\]\\) \\{\\s*NodeActions::certbot/",
|
||||
$rCmd,
|
||||
'the certbot dispatch is skipped when the node has no domain name (IP-only)'
|
||||
);
|
||||
}
|
||||
|
||||
public function testSyncFrontCertCopiesForServeOnlyOnly(): void {
|
||||
// On a serve-only node the Go front reads server.crt/server.key, so a renewed
|
||||
// certbot cert must be copied there (its CertReloader hot-reloads on mtime).
|
||||
|
||||
Reference in New Issue
Block a user