@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.
@@ -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
- return { serverId: chosen.id, serverName: chosen.name, publicIp: chosen.public_net?.ipv4?.ip };
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 === "A" && name === fqdn && r?.content === ip;
1311
+ return r?.type === recordType && name === fqdn && r?.content === ip;
1304
1312
  });
1305
1313
  } catch {
1306
1314
  return !0;
1307
1315
  }
1308
1316
  }
1309
- async function reconcileHetznerDns(sites, ip, logger) {
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.205",
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.84",
140
+ "@stacksjs/ts-cloud": "^0.7.85",
141
141
  "ajv": "^8.20.0",
142
142
  "ajv-formats": "^3.0.1"
143
143
  },