@stacksjs/buddy 0.70.205 → 0.70.206
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/deploy.js +32 -6
- package/package.json +2 -2
package/dist/commands/deploy.js
CHANGED
|
@@ -690,7 +690,7 @@ async function runHetznerDeploy(args) {
|
|
|
690
690
|
process.exit(ExitCode.FatalError);
|
|
691
691
|
}
|
|
692
692
|
const attachTo = tsCloudConfig.cloud?.attachTo;
|
|
693
|
-
let ip;
|
|
693
|
+
let ip, ipv6;
|
|
694
694
|
if (attachTo) {
|
|
695
695
|
const box = await resolveAttachTargetBox(attachTo, environment);
|
|
696
696
|
if (!box?.publicIp) {
|
|
@@ -698,6 +698,7 @@ async function runHetznerDeploy(args) {
|
|
|
698
698
|
process.exit(ExitCode.FatalError);
|
|
699
699
|
}
|
|
700
700
|
ip = box.publicIp;
|
|
701
|
+
ipv6 = box.publicIpv6;
|
|
701
702
|
log.info(`Attaching to '${attachTo}' box '${box.serverName}' (${ip}) \u2014 skipping provisioning`);
|
|
702
703
|
const compute = (tsCloudConfig.infrastructure ??= {}).compute ??= {};
|
|
703
704
|
compute.webServer = "rpx";
|
|
@@ -717,6 +718,7 @@ async function runHetznerDeploy(args) {
|
|
|
717
718
|
log.info("Provisioning Hetzner compute infrastructure...");
|
|
718
719
|
const outputs = await driver.provisionComputeInfrastructure({ config: scrubLoopbackSitePortsForFirewall(tsCloudConfig), environment });
|
|
719
720
|
ip = outputs.appPublicIp;
|
|
721
|
+
ipv6 = outputs.appPublicIpv6;
|
|
720
722
|
log.success("Hetzner compute infrastructure ready");
|
|
721
723
|
if (outputs.appInstanceId)
|
|
722
724
|
log.info(`Server ID: ${outputs.appInstanceId}`);
|
|
@@ -815,7 +817,7 @@ async function runHetznerDeploy(args) {
|
|
|
815
817
|
}
|
|
816
818
|
});
|
|
817
819
|
if (ok)
|
|
818
|
-
await reconcileHetznerDns(onlySite ? { [onlySite]: sites[onlySite] } : sites, ip, log);
|
|
820
|
+
await reconcileHetznerDns(onlySite ? { [onlySite]: sites[onlySite] } : sites, ip, log, ipv6);
|
|
819
821
|
if (ok)
|
|
820
822
|
await reconcileConfigDns(onlySite ? { [onlySite]: sites[onlySite] } : sites, log);
|
|
821
823
|
if (ok) {
|
|
@@ -921,7 +923,13 @@ async function resolveAttachTargetBox(owner, environment) {
|
|
|
921
923
|
}, byLabel = await req(`label_selector=${encodeURIComponent(`ts-cloud/project=${owner},ts-cloud/environment=${environment},ts-cloud/role=app`)}`), chosen = pick(byLabel) || pick(await req(`name=${encodeURIComponent(`${owner}-${environment}-app`)}`));
|
|
922
924
|
if (!chosen)
|
|
923
925
|
return null;
|
|
924
|
-
|
|
926
|
+
const { hetznerBoxIpv6 } = await import("@stacksjs/ts-cloud");
|
|
927
|
+
return {
|
|
928
|
+
serverId: chosen.id,
|
|
929
|
+
serverName: chosen.name,
|
|
930
|
+
publicIp: chosen.public_net?.ipv4?.ip,
|
|
931
|
+
publicIpv6: hetznerBoxIpv6?.(chosen.public_net?.ipv6?.ip)
|
|
932
|
+
};
|
|
925
933
|
}
|
|
926
934
|
export async function provisionMailTenant(ip, logger) {
|
|
927
935
|
if (!hasExplicitEmailConfig())
|
|
@@ -1291,7 +1299,7 @@ async function reconcileConfigDns(sites, logger) {
|
|
|
1291
1299
|
logger.warn(`DNS (config/dns.ts) reconcile for ${domain} failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
1292
1300
|
}
|
|
1293
1301
|
}
|
|
1294
|
-
async function verifyDnsRecord(provider, domain, fqdn, ip) {
|
|
1302
|
+
async function verifyDnsRecord(provider, domain, fqdn, ip, recordType = "A") {
|
|
1295
1303
|
try {
|
|
1296
1304
|
if (typeof provider?.listRecords !== "function")
|
|
1297
1305
|
return !0;
|
|
@@ -1300,13 +1308,17 @@ async function verifyDnsRecord(provider, domain, fqdn, ip) {
|
|
|
1300
1308
|
return !0;
|
|
1301
1309
|
return res.records.some((r) => {
|
|
1302
1310
|
const name = typeof r?.name === "string" ? r.name.replace(/\.$/, "") : "";
|
|
1303
|
-
return r?.type ===
|
|
1311
|
+
return r?.type === recordType && name === fqdn && r?.content === ip;
|
|
1304
1312
|
});
|
|
1305
1313
|
} catch {
|
|
1306
1314
|
return !0;
|
|
1307
1315
|
}
|
|
1308
1316
|
}
|
|
1309
|
-
|
|
1317
|
+
const IPV6_EXCLUDED_HOSTS = new Set(["mail", "smtp", "imap", "mx"]);
|
|
1318
|
+
function skipIpv6ForHost(fqdn) {
|
|
1319
|
+
return IPV6_EXCLUDED_HOSTS.has(fqdn.split(".")[0] ?? "");
|
|
1320
|
+
}
|
|
1321
|
+
async function reconcileHetznerDns(sites, ip, logger, ipv6) {
|
|
1310
1322
|
const domains = new Set;
|
|
1311
1323
|
for (const site of Object.values(sites))
|
|
1312
1324
|
if (site?.domain && typeof site.domain === "string")
|
|
@@ -1382,6 +1394,20 @@ async function reconcileHetznerDns(sites, ip, logger) {
|
|
|
1382
1394
|
logger.success(` DNS: ${fqdn} \u2192 ${ip} (${provider.name})`);
|
|
1383
1395
|
else
|
|
1384
1396
|
logger.warn(` DNS: ${fqdn} \u2192 ${ip} reported success at ${provider.name} but no matching A record exists \u2014 create it manually: A ${fqdn} \u2192 ${ip}`);
|
|
1397
|
+
if (!ipv6 || skipIpv6ForHost(fqdn))
|
|
1398
|
+
continue;
|
|
1399
|
+
const v6res = await provider.upsertRecord(domain, { name: fqdn, type: "AAAA", content: ipv6, ttl: 600 });
|
|
1400
|
+
if (v6res?.success === !1) {
|
|
1401
|
+
logger.warn(` DNS: ${fqdn} \u2192 ${ipv6} failed: ${v6res.error || v6res.message || "unknown error"}`);
|
|
1402
|
+
continue;
|
|
1403
|
+
}
|
|
1404
|
+
const v6Cleanup = await removeStaleServerAddressRecords(provider, domain, fqdn, ipv6, "AAAA");
|
|
1405
|
+
for (const warning of v6Cleanup)
|
|
1406
|
+
logger.warn(` DNS: ${fqdn} cleanup: ${warning}`);
|
|
1407
|
+
if (await verifyDnsRecord(provider, domain, fqdn, ipv6, "AAAA"))
|
|
1408
|
+
logger.success(` DNS: ${fqdn} \u2192 ${ipv6} (${provider.name})`);
|
|
1409
|
+
else
|
|
1410
|
+
logger.warn(` DNS: ${fqdn} \u2192 ${ipv6} reported success at ${provider.name} but no matching AAAA record exists \u2014 create it manually: AAAA ${fqdn} \u2192 ${ipv6}`);
|
|
1385
1411
|
}
|
|
1386
1412
|
} catch (err) {
|
|
1387
1413
|
logger.warn(` DNS: ${domain} reconciliation failed: ${err?.message || err}`);
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@stacksjs/buddy",
|
|
3
3
|
"type": "module",
|
|
4
4
|
"sideEffects": false,
|
|
5
|
-
"version": "0.70.
|
|
5
|
+
"version": "0.70.206",
|
|
6
6
|
"description": "Meet Buddy. The Stacks runtime.",
|
|
7
7
|
"author": "Chris Breuer",
|
|
8
8
|
"contributors": [
|
|
@@ -137,7 +137,7 @@
|
|
|
137
137
|
"@stacksjs/ui": "^0.70.180",
|
|
138
138
|
"@stacksjs/utils": "^0.70.180",
|
|
139
139
|
"@stacksjs/validation": "^0.70.180",
|
|
140
|
-
"@stacksjs/ts-cloud": "^0.7.
|
|
140
|
+
"@stacksjs/ts-cloud": "^0.7.85",
|
|
141
141
|
"ajv": "^8.20.0",
|
|
142
142
|
"ajv-formats": "^3.0.1"
|
|
143
143
|
},
|