@stacksjs/ts-cloud 0.7.85 → 0.7.87

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.
Files changed (59) hide show
  1. package/dist/aws/ec2.d.ts +13 -0
  2. package/dist/aws/index.js +1 -1
  3. package/dist/bin/cli.js +533 -533
  4. package/dist/{chunk-1qjyqrc5.js → chunk-24kwnpe5.js} +2 -0
  5. package/dist/{chunk-703nkybg.js → chunk-7m60qnc8.js} +10 -0
  6. package/dist/{chunk-d2aw5vyv.js → chunk-aqmdexc3.js} +1 -1
  7. package/dist/{chunk-qbj74thz.js → chunk-cj3qrktr.js} +5 -5
  8. package/dist/{chunk-gx4288t5.js → chunk-tcr4xba6.js} +123 -62
  9. package/dist/{chunk-tq4v58zp.js → chunk-w61hh7n3.js} +2 -2
  10. package/dist/deploy/index.js +17 -9
  11. package/dist/deploy/server-dns.d.ts +66 -7
  12. package/dist/drivers/index.js +3 -3
  13. package/dist/drivers/shared/rpx-gateway.d.ts +1 -0
  14. package/dist/index.d.ts +2 -1
  15. package/dist/index.js +15 -7
  16. package/dist/ui/account/automation.html +3 -3
  17. package/dist/ui/account/security.html +2 -2
  18. package/dist/ui/applications/compose.html +4 -4
  19. package/dist/ui/applications/new.html +2 -2
  20. package/dist/ui/data/backups.html +4 -4
  21. package/dist/ui/data/services.html +4 -4
  22. package/dist/ui/data/volumes.html +4 -4
  23. package/dist/ui/index.html +4 -4
  24. package/dist/ui/operations/alerts.html +3 -3
  25. package/dist/ui/operations/configuration.html +4 -4
  26. package/dist/ui/operations/jobs.html +4 -4
  27. package/dist/ui/operations/maintenance.html +4 -4
  28. package/dist/ui/operations/observability.html +4 -4
  29. package/dist/ui/operations/previews.html +4 -4
  30. package/dist/ui/operations/queue.html +4 -4
  31. package/dist/ui/operations/regions.html +4 -4
  32. package/dist/ui/operations/releases.html +4 -4
  33. package/dist/ui/operations/workloads.html +4 -4
  34. package/dist/ui/server/actions.html +4 -4
  35. package/dist/ui/server/capacity.html +4 -4
  36. package/dist/ui/server/database.html +4 -4
  37. package/dist/ui/server/deployments.html +4 -4
  38. package/dist/ui/server/diagnostics.html +2 -2
  39. package/dist/ui/server/firewall.html +4 -4
  40. package/dist/ui/server/fleet.html +4 -4
  41. package/dist/ui/server/logs.html +4 -4
  42. package/dist/ui/server/metrics.html +4 -4
  43. package/dist/ui/server/services.html +2 -2
  44. package/dist/ui/server/sites.html +4 -4
  45. package/dist/ui/server/ssh-keys.html +4 -4
  46. package/dist/ui/server/team.html +4 -4
  47. package/dist/ui/server/terminal.html +2 -2
  48. package/dist/ui/serverless/alarms.html +3 -3
  49. package/dist/ui/serverless/cost.html +2 -2
  50. package/dist/ui/serverless/data.html +4 -4
  51. package/dist/ui/serverless/firewall.html +2 -2
  52. package/dist/ui/serverless/functions.html +4 -4
  53. package/dist/ui/serverless/logs.html +4 -4
  54. package/dist/ui/serverless/metrics.html +2 -2
  55. package/dist/ui/serverless/queues.html +4 -4
  56. package/dist/ui/serverless/secrets.html +4 -4
  57. package/dist/ui/serverless/traces.html +4 -4
  58. package/dist/ui/serverless.html +3 -3
  59. package/package.json +3 -3
@@ -22000,6 +22000,8 @@ function resolveManagementDashboardSites(config, environment, opts) {
22000
22000
  start: `bun ${DASHBOARD_ENTRY} dashboard:serve --box --host 127.0.0.1 --port ${port}`,
22001
22001
  port,
22002
22002
  env: {
22003
+ APP_ENV: "production",
22004
+ NODE_ENV: "production",
22003
22005
  TS_CLOUD_DASHBOARD_TELEMETRY: "1"
22004
22006
  },
22005
22007
  zeroDowntime: false,
@@ -1087,6 +1087,15 @@ class EC2Client {
1087
1087
  return [];
1088
1088
  return Array.isArray(item) ? item : [item];
1089
1089
  }
1090
+ parseFirstIpv6Address(item) {
1091
+ for (const eni of this.parseArray(item)) {
1092
+ for (const entry of this.parseArray(eni?.ipv6AddressesSet?.item)) {
1093
+ if (entry?.ipv6Address)
1094
+ return entry.ipv6Address;
1095
+ }
1096
+ }
1097
+ return;
1098
+ }
1090
1099
  parseTags(item) {
1091
1100
  return this.parseArray(item).map((t) => ({
1092
1101
  Key: t.key,
@@ -1110,6 +1119,7 @@ class EC2Client {
1110
1119
  } : undefined,
1111
1120
  PrivateIpAddress: i.privateIpAddress,
1112
1121
  PublicIpAddress: i.ipAddress,
1122
+ Ipv6Address: this.parseFirstIpv6Address(i.networkInterfaceSet?.item),
1113
1123
  SubnetId: i.subnetId,
1114
1124
  VpcId: i.vpcId,
1115
1125
  SecurityGroups: this.parseArray(i.groupSet?.item).map((g) => ({
@@ -8,7 +8,7 @@ import {
8
8
  phpRuntimeLayerAssets,
9
9
  runPhpBuildHooks,
10
10
  sha256
11
- } from "./chunk-1qjyqrc5.js";
11
+ } from "./chunk-24kwnpe5.js";
12
12
 
13
13
  // src/deploy/serverless-image.ts
14
14
  import { execFileSync } from "node:child_process";
@@ -9,7 +9,7 @@ import {
9
9
  step,
10
10
  success,
11
11
  warn
12
- } from "./chunk-d2aw5vyv.js";
12
+ } from "./chunk-aqmdexc3.js";
13
13
  import {
14
14
  AwsBackupClient,
15
15
  CloudWatchLogsClient,
@@ -45,7 +45,7 @@ import {
45
45
  resolveSiteKind,
46
46
  resolveUiSource,
47
47
  siteInstallBase
48
- } from "./chunk-gx4288t5.js";
48
+ } from "./chunk-tcr4xba6.js";
49
49
  import {
50
50
  artifactKey,
51
51
  buildCloudFormationTemplate,
@@ -67,10 +67,10 @@ import {
67
67
  resolveStatePath,
68
68
  setStateDir,
69
69
  statePath
70
- } from "./chunk-1qjyqrc5.js";
70
+ } from "./chunk-24kwnpe5.js";
71
71
  import {
72
72
  SSMClient
73
- } from "./chunk-703nkybg.js";
73
+ } from "./chunk-7m60qnc8.js";
74
74
  import {
75
75
  CloudFormationClient
76
76
  } from "./chunk-4cjrg98a.js";
@@ -8324,7 +8324,7 @@ async function deployServerlessApp(config, environment2, opts = {}) {
8324
8324
  }
8325
8325
  if (imageMode) {
8326
8326
  step("Building + pushing container image");
8327
- const { buildAndPushServerlessImage } = await import("./chunk-tq4v58zp.js");
8327
+ const { buildAndPushServerlessImage } = await import("./chunk-w61hh7n3.js");
8328
8328
  const built = await buildAndPushServerlessImage({
8329
8329
  app,
8330
8330
  projectRoot,
@@ -8,11 +8,11 @@ import {
8
8
  resolveProjectStackName,
9
9
  resolveStatePath,
10
10
  statePath
11
- } from "./chunk-1qjyqrc5.js";
11
+ } from "./chunk-24kwnpe5.js";
12
12
  import {
13
13
  EC2Client,
14
14
  SSMClient
15
- } from "./chunk-703nkybg.js";
15
+ } from "./chunk-7m60qnc8.js";
16
16
  import {
17
17
  CloudFormationClient
18
18
  } from "./chunk-4cjrg98a.js";
@@ -27,6 +27,98 @@ import {
27
27
  import { readFileSync } from "node:fs";
28
28
  import { join } from "node:path";
29
29
 
30
+ // src/deploy/server-dns.ts
31
+ function collectServerDnsDomains(sites = {}) {
32
+ const domains = new Set;
33
+ for (const site of Object.values(sites)) {
34
+ if (!site.domain)
35
+ continue;
36
+ if (site.redirect || site.deploy === "server" || site.start)
37
+ domains.add(site.domain);
38
+ }
39
+ return domains;
40
+ }
41
+ function normalizeName(name) {
42
+ return name.replace(/\.$/, "").toLowerCase();
43
+ }
44
+ function matchesHostname(record, zone, hostname) {
45
+ const recordName = normalizeName(record.name);
46
+ const normalizedZone = normalizeName(zone);
47
+ const normalizedHostname = normalizeName(hostname);
48
+ const relativeName = normalizedHostname === normalizedZone ? "@" : normalizedHostname.endsWith(`.${normalizedZone}`) ? normalizedHostname.slice(0, -(normalizedZone.length + 1)) : normalizedHostname;
49
+ return recordName === normalizedHostname || recordName === relativeName || relativeName === "@" && (recordName === "" || recordName === normalizedZone);
50
+ }
51
+ async function removeStaleServerAddressRecords(provider, zone, hostname, desiredAddress, recordType = "A") {
52
+ const listed = await provider.listRecords(zone);
53
+ if (!listed.success)
54
+ return [`could not list ${recordType} records: ${listed.message || "unknown provider error"}`];
55
+ const matching = listed.records.filter((record) => record.type === recordType && matchesHostname(record, zone, hostname));
56
+ const desiredIndex = matching.findIndex((record) => record.content === desiredAddress);
57
+ if (matching.length <= 1 || desiredIndex === -1)
58
+ return [];
59
+ const warnings = [];
60
+ for (const [index, record] of matching.entries()) {
61
+ if (index === desiredIndex)
62
+ continue;
63
+ const result = await provider.deleteRecord(zone, record);
64
+ if (!result.success)
65
+ warnings.push(`could not remove stale ${record.name} ${recordType} ${record.content}: ${result.message || "unknown provider error"}`);
66
+ }
67
+ return warnings;
68
+ }
69
+ function normalizePublicIpv6(reported) {
70
+ if (!reported)
71
+ return;
72
+ const trimmed = reported.trim();
73
+ if (!trimmed)
74
+ return;
75
+ if (!trimmed.includes("/"))
76
+ return trimmed;
77
+ const [block] = trimmed.split("/");
78
+ if (!block.endsWith("::"))
79
+ return block || undefined;
80
+ return `${block}1`;
81
+ }
82
+ var IPV6_EXCLUDED_HOST_LABELS = new Set(["mail", "smtp", "imap", "mx"]);
83
+ function hostAcceptsIpv6(fqdn) {
84
+ return !IPV6_EXCLUDED_HOST_LABELS.has(fqdn.split(".")[0] ?? "");
85
+ }
86
+ async function reconcileAddressRecords(options) {
87
+ const { provider, zone, fqdn, ipv4, ipv6, ttl = 600 } = options;
88
+ const report = { published: [], warnings: [] };
89
+ const families = [{ type: "A", content: ipv4 }];
90
+ if (ipv6 && hostAcceptsIpv6(fqdn))
91
+ families.push({ type: "AAAA", content: ipv6 });
92
+ for (const { type, content } of families) {
93
+ const result = await provider.upsertRecord(zone, { name: fqdn, type, content, ttl });
94
+ if (result?.success === false) {
95
+ report.warnings.push(`${fqdn} → ${content} failed: ${result.error || result.message || "unknown error"}`);
96
+ continue;
97
+ }
98
+ report.warnings.push(...(await removeStaleServerAddressRecords(provider, zone, fqdn, content, type)).map((warning) => `${fqdn} cleanup: ${warning}`));
99
+ if (await verifyAddressRecord(provider, zone, fqdn, content, type))
100
+ report.published.push({ fqdn, type, content });
101
+ else
102
+ report.warnings.push(`${fqdn} → ${content} reported success at ${provider.name} but no matching ${type} record exists — create it manually: ${type} ${fqdn} → ${content}`);
103
+ }
104
+ return report;
105
+ }
106
+ async function verifyAddressRecord(provider, zone, fqdn, content, recordType) {
107
+ try {
108
+ if (typeof provider?.listRecords !== "function")
109
+ return true;
110
+ const listed = await provider.listRecords(zone);
111
+ if (!listed?.success || !Array.isArray(listed.records))
112
+ return true;
113
+ return listed.records.some((record) => {
114
+ const name = typeof record?.name === "string" ? record.name.replace(/\.$/, "") : "";
115
+ return record?.type === recordType && name === fqdn && record?.content === content;
116
+ });
117
+ } catch {
118
+ return true;
119
+ }
120
+ }
121
+
30
122
  // src/drivers/shared/package-manager.ts
31
123
  var PANTRY_PACKAGES = {
32
124
  php: "php.net",
@@ -1163,6 +1255,7 @@ function usesRpxProxy(compute) {
1163
1255
  var RPX_DIR = "/etc/rpx";
1164
1256
  var RPX_INSTALL_DIR = "/opt/rpx-gateway";
1165
1257
  var RPX_LAUNCHER_PATH = "/etc/rpx/gateway.ts";
1258
+ var RPX_BINARY_PATH = "/etc/rpx/gateway";
1166
1259
  var RPX_SERVICE_NAME = "rpx-gateway.service";
1167
1260
  var RPX_SITES_DIR = "/etc/rpx/sites.d";
1168
1261
  function renderRpxAssembler(sitesDir = RPX_SITES_DIR, defaultCertsDir = DEFAULT_RPX_CERTS_DIR) {
@@ -1368,6 +1461,9 @@ function buildRpxProvisionScript(options) {
1368
1461
  `ln -sfn ${RPX_INSTALL_DIR}/node_modules ${RPX_DIR}/node_modules`,
1369
1462
  ...writeFileHeredoc(`${RPX_SITES_DIR}/${slug}.json`, fragment, "TS_CLOUD_RPX_FRAGMENT_EOF", "0600"),
1370
1463
  ...writeFileHeredoc(RPX_LAUNCHER_PATH, assembler, "TS_CLOUD_RPX_EOF"),
1464
+ `${bunBin} build --production --compile --outfile ${RPX_BINARY_PATH}.next ${RPX_LAUNCHER_PATH}`,
1465
+ `chmod 0755 ${RPX_BINARY_PATH}.next`,
1466
+ `mv -f ${RPX_BINARY_PATH}.next ${RPX_BINARY_PATH}`,
1371
1467
  ...writeFileHeredoc(`/etc/systemd/system/${RPX_SERVICE_NAME}`, [
1372
1468
  "[Unit]",
1373
1469
  "Description=rpx reverse-proxy gateway (managed by ts-cloud)",
@@ -1376,9 +1472,11 @@ function buildRpxProvisionScript(options) {
1376
1472
  "",
1377
1473
  "[Service]",
1378
1474
  "Type=simple",
1379
- `ExecStart=${bunBin} ${RPX_LAUNCHER_PATH}`,
1475
+ `ExecStart=${RPX_BINARY_PATH}`,
1380
1476
  `WorkingDirectory=${RPX_INSTALL_DIR}`,
1381
1477
  `Environment=BUN_INSTALL=/root/.bun`,
1478
+ `Environment=APP_ENV=production`,
1479
+ `Environment=NODE_ENV=production`,
1382
1480
  ...poolEnv,
1383
1481
  "MemoryAccounting=true",
1384
1482
  `MemoryHigh=${memoryHigh}`,
@@ -1752,6 +1850,7 @@ class AwsDriver {
1752
1850
  return {
1753
1851
  appInstanceId: first.id,
1754
1852
  appPublicIp: first.publicIp,
1853
+ appPublicIpv6: first.publicIpv6,
1755
1854
  sshUser: "ubuntu",
1756
1855
  deployStoragePath: "/var/ts-cloud/staging"
1757
1856
  };
@@ -1834,6 +1933,7 @@ class AwsDriver {
1834
1933
  return {
1835
1934
  appInstanceId: instanceId,
1836
1935
  appPublicIp: running.PublicIpAddress,
1936
+ appPublicIpv6: normalizePublicIpv6(running.Ipv6Address),
1837
1937
  sshUser: "ubuntu",
1838
1938
  deployStoragePath: "/var/ts-cloud/staging"
1839
1939
  };
@@ -1892,6 +1992,7 @@ class AwsDriver {
1892
1992
  return {
1893
1993
  appInstanceId: first?.id,
1894
1994
  appPublicIp: first?.publicIp,
1995
+ appPublicIpv6: first?.publicIpv6,
1895
1996
  sshUser: "ubuntu",
1896
1997
  deployStoragePath: "/var/ts-cloud/staging"
1897
1998
  };
@@ -1961,6 +2062,7 @@ class AwsDriver {
1961
2062
  id: instance.InstanceId,
1962
2063
  name: nameTag,
1963
2064
  publicIp: instance.PublicIpAddress,
2065
+ publicIpv6: normalizePublicIpv6(instance.Ipv6Address),
1964
2066
  privateIp: instance.PrivateIpAddress,
1965
2067
  status: instance.State?.Name
1966
2068
  });
@@ -2045,59 +2147,6 @@ import { execSync } from "node:child_process";
2045
2147
  import { randomUUID } from "node:crypto";
2046
2148
  import { existsSync, readFileSync as readFileSync2 } from "node:fs";
2047
2149
 
2048
- // src/deploy/server-dns.ts
2049
- function collectServerDnsDomains(sites = {}) {
2050
- const domains = new Set;
2051
- for (const site of Object.values(sites)) {
2052
- if (!site.domain)
2053
- continue;
2054
- if (site.redirect || site.deploy === "server" || site.start)
2055
- domains.add(site.domain);
2056
- }
2057
- return domains;
2058
- }
2059
- function normalizeName(name) {
2060
- return name.replace(/\.$/, "").toLowerCase();
2061
- }
2062
- function matchesHostname(record, zone, hostname) {
2063
- const recordName = normalizeName(record.name);
2064
- const normalizedZone = normalizeName(zone);
2065
- const normalizedHostname = normalizeName(hostname);
2066
- const relativeName = normalizedHostname === normalizedZone ? "@" : normalizedHostname.endsWith(`.${normalizedZone}`) ? normalizedHostname.slice(0, -(normalizedZone.length + 1)) : normalizedHostname;
2067
- return recordName === normalizedHostname || recordName === relativeName || relativeName === "@" && (recordName === "" || recordName === normalizedZone);
2068
- }
2069
- async function removeStaleServerAddressRecords(provider, zone, hostname, desiredAddress, recordType = "A") {
2070
- const listed = await provider.listRecords(zone);
2071
- if (!listed.success)
2072
- return [`could not list ${recordType} records: ${listed.message || "unknown provider error"}`];
2073
- const matching = listed.records.filter((record) => record.type === recordType && matchesHostname(record, zone, hostname));
2074
- const desiredIndex = matching.findIndex((record) => record.content === desiredAddress);
2075
- if (matching.length <= 1 || desiredIndex === -1)
2076
- return [];
2077
- const warnings = [];
2078
- for (const [index, record] of matching.entries()) {
2079
- if (index === desiredIndex)
2080
- continue;
2081
- const result = await provider.deleteRecord(zone, record);
2082
- if (!result.success)
2083
- warnings.push(`could not remove stale ${record.name} ${recordType} ${record.content}: ${result.message || "unknown provider error"}`);
2084
- }
2085
- return warnings;
2086
- }
2087
- function hetznerBoxIpv6(reported) {
2088
- if (!reported)
2089
- return;
2090
- const trimmed = reported.trim();
2091
- if (!trimmed)
2092
- return;
2093
- if (!trimmed.includes("/"))
2094
- return trimmed;
2095
- const [block] = trimmed.split("/");
2096
- if (!block.endsWith("::"))
2097
- return block || undefined;
2098
- return `${block}1`;
2099
- }
2100
-
2101
2150
  // src/drivers/shared/fleet.ts
2102
2151
  function resolveFleetTopology(compute = {}) {
2103
2152
  const appServers = Math.max(1, compute.appServers ?? compute.instances ?? 1);
@@ -2666,7 +2715,7 @@ class HetznerDriver {
2666
2715
  serverId: alreadyRunning.id,
2667
2716
  serverName: alreadyRunning.name,
2668
2717
  publicIp: alreadyRunning.public_net.ipv4?.ip,
2669
- publicIpv6: hetznerBoxIpv6(alreadyRunning.public_net.ipv6?.ip),
2718
+ publicIpv6: normalizePublicIpv6(alreadyRunning.public_net.ipv6?.ip),
2670
2719
  deployStoragePath: "/var/ts-cloud/staging",
2671
2720
  sshUser: this.sshUser
2672
2721
  };
@@ -2714,7 +2763,7 @@ class HetznerDriver {
2714
2763
  serverName: running.name,
2715
2764
  firewallId: firewall.id,
2716
2765
  publicIp: running.public_net.ipv4?.ip,
2717
- publicIpv6: hetznerBoxIpv6(running.public_net.ipv6?.ip),
2766
+ publicIpv6: normalizePublicIpv6(running.public_net.ipv6?.ip),
2718
2767
  deployStoragePath: "/var/ts-cloud/staging",
2719
2768
  sshUser: this.sshUser
2720
2769
  };
@@ -3216,6 +3265,7 @@ class HetznerDriver {
3216
3265
  id: String(server.id),
3217
3266
  name: server.name,
3218
3267
  publicIp: server.public_net.ipv4?.ip,
3268
+ publicIpv6: normalizePublicIpv6(server.public_net.ipv6?.ip),
3219
3269
  privateIp: server.private_net?.[0]?.ip,
3220
3270
  status: server.status
3221
3271
  });
@@ -3428,7 +3478,7 @@ ${out}`);
3428
3478
  deployStoragePath: state.deployStoragePath || "/var/ts-cloud/staging",
3429
3479
  appInstanceId: state.serverId ? String(state.serverId) : undefined,
3430
3480
  appPublicIp: server?.public_net.ipv4?.ip || state.publicIp,
3431
- appPublicIpv6: hetznerBoxIpv6(server?.public_net.ipv6?.ip) || state.publicIpv6,
3481
+ appPublicIpv6: normalizePublicIpv6(server?.public_net.ipv6?.ip) || state.publicIpv6,
3432
3482
  sshUser: state.sshUser || this.sshUser,
3433
3483
  servicesPrivateIp: state.servicesPrivateIp
3434
3484
  };
@@ -5172,7 +5222,12 @@ var STACKS_CLI = "storage/framework/core/buddy/src/cli.ts";
5172
5222
  var stacksDriver = {
5173
5223
  id: "stacks",
5174
5224
  wrapExec: (command) => command,
5175
- execEnv: { PATH: "/usr/local/bin:/usr/bin:/bin", BUN_INSTALL: "/root/.bun" },
5225
+ execEnv: {
5226
+ PATH: "/usr/local/bin:/usr/bin:/bin",
5227
+ BUN_INSTALL: "/root/.bun",
5228
+ APP_ENV: "production",
5229
+ NODE_ENV: "production"
5230
+ },
5176
5231
  schedulerMode: "daemon",
5177
5232
  schedulerCommand: () => `${BUN_BIN} ${STACKS_CLI} schedule:run`,
5178
5233
  queueWorkerCommand: (worker, { current }) => {
@@ -5658,7 +5713,13 @@ async function deploySiteRelease(driver, options, logger = noopLogger2) {
5658
5713
  const dbEnv = outputs.servicesPrivateIp ? buildFleetServicesEnv(outputs.servicesPrivateIp, resolveAppDatabase(config)) : buildManagedDbEnv(resolveAppDatabase(config));
5659
5714
  const compute = config.infrastructure?.compute;
5660
5715
  const proxyEnv = usesRpxProxy(compute) ? { HOST: "127.0.0.1" } : {};
5661
- const envWithServices = { ...dbEnv, ...proxyEnv, ...site.env || {} };
5716
+ const productionEnv = environment === "production" ? { APP_ENV: "production", NODE_ENV: "production" } : {};
5717
+ const envWithServices = {
5718
+ ...dbEnv,
5719
+ ...proxyEnv,
5720
+ ...site.env || {},
5721
+ ...productionEnv
5722
+ };
5662
5723
  if (isPhpSite(site)) {
5663
5724
  const phpVersion = site.phpVersion ?? compute?.php?.default ?? compute?.php?.versions?.[0];
5664
5725
  const appBase2 = siteInstallBase(slug, siteName);
@@ -6254,4 +6315,4 @@ function buildCloudFrontOriginConfig(options) {
6254
6315
  CustomErrorResponses: { Quantity: 0 }
6255
6316
  };
6256
6317
  }
6257
- export { PANTRY_PROJECT_DIR, pgAdminCommand, BACKUP_RUNNER_PATH, buildBackupRestoreScript, siteInstallBase, isPhpSite, resolveSiteDeployTarget, resolveSiteKind, validateDeploymentConfig, shipsARelease, DEFAULT_RPX_CERTS_DIR, normalizeRoutePath, deriveRouteId, buildRpxConfig, buildRpxLbConfig, renderRpxLauncher, RPX_DIR, RPX_LAUNCHER_PATH, RPX_SERVICE_NAME, buildRpxProvisionScript, buildRpxFragmentRefreshScript, buildUbuntuBootstrapScript, AwsDriver, collectServerDnsDomains, removeStaleServerAddressRecords, hetznerBoxIpv6, resolveHetznerLocation, HetznerClient, resolveHetznerApiToken2 as resolveHetznerApiToken, normalizeSshPublicKey, wrapCloudInitUserData, HetznerDriver, LocalBoxDriver, isBoxMode, createCloudDriver, CloudDriverFactory, cloudDrivers, planHetznerServerResize, isHetznerCapacityError, executeHetznerServerResize, buildSshArgs, sshExec, sshExecOrThrow, scpUpload, waitForSsh, waitForCloudInit, collectHetznerResizeManifest, prepareHetznerResize, verifyHetznerResize, resolveHetznerHostOptimizationPlan, buildHetznerHostOptimizationScript, applyHetznerHostOptimization, collectHetznerHostOptimizationReport, verifyHetznerHostOptimization, resizeCheckpointPath, resizeLockPath, readResizeCheckpoint, writeResizeCheckpoint, acquireResizeLock, ensureSshKey, ensureFirewall, ensureServer, serverPublicIpv4, UBUNTU_2404_AMI_PARAM, buildBoxUserData, HetznerBoxProvisioner, AwsBoxProvisioner, createBoxProvisioner, releasePaths, buildRollbackScript, resolveExecStart, buildSiteDeployScript, buildStaticSiteDeployScript, releaseTarballTmpPath, buildAwsArtifactFetch, buildLocalArtifactFetch, buildHostCleanupScript, MANAGEMENT_DASHBOARD_SITE, dashboardCredentialsFile, resolveDashboardAuth, resolveUiSource, ensureManagementDashboard, buildManagementDashboardArtifact, resolveSiteFramework, deploySiteRelease, deployAllComputeSites, reloadRpxGateway, renewRpxCertificates, MANAGED_CACHE_POLICY_OPTIMIZED, MANAGED_CACHE_POLICY_DISABLED, MANAGED_ORIGIN_REQUEST_POLICY_ALL_VIEWER, buildCloudFrontOriginConfig };
6318
+ export { collectServerDnsDomains, removeStaleServerAddressRecords, normalizePublicIpv6, IPV6_EXCLUDED_HOST_LABELS, hostAcceptsIpv6, reconcileAddressRecords, verifyAddressRecord, PANTRY_PROJECT_DIR, pgAdminCommand, BACKUP_RUNNER_PATH, buildBackupRestoreScript, siteInstallBase, isPhpSite, resolveSiteDeployTarget, resolveSiteKind, validateDeploymentConfig, shipsARelease, DEFAULT_RPX_CERTS_DIR, normalizeRoutePath, deriveRouteId, buildRpxConfig, buildRpxLbConfig, renderRpxLauncher, RPX_DIR, RPX_LAUNCHER_PATH, RPX_SERVICE_NAME, buildRpxProvisionScript, buildRpxFragmentRefreshScript, buildUbuntuBootstrapScript, AwsDriver, resolveHetznerLocation, HetznerClient, resolveHetznerApiToken2 as resolveHetznerApiToken, normalizeSshPublicKey, wrapCloudInitUserData, HetznerDriver, LocalBoxDriver, isBoxMode, createCloudDriver, CloudDriverFactory, cloudDrivers, planHetznerServerResize, isHetznerCapacityError, executeHetznerServerResize, buildSshArgs, sshExec, sshExecOrThrow, scpUpload, waitForSsh, waitForCloudInit, collectHetznerResizeManifest, prepareHetznerResize, verifyHetznerResize, resolveHetznerHostOptimizationPlan, buildHetznerHostOptimizationScript, applyHetznerHostOptimization, collectHetznerHostOptimizationReport, verifyHetznerHostOptimization, resizeCheckpointPath, resizeLockPath, readResizeCheckpoint, writeResizeCheckpoint, acquireResizeLock, ensureSshKey, ensureFirewall, ensureServer, serverPublicIpv4, UBUNTU_2404_AMI_PARAM, buildBoxUserData, HetznerBoxProvisioner, AwsBoxProvisioner, createBoxProvisioner, releasePaths, buildRollbackScript, resolveExecStart, buildSiteDeployScript, buildStaticSiteDeployScript, releaseTarballTmpPath, buildAwsArtifactFetch, buildLocalArtifactFetch, buildHostCleanupScript, MANAGEMENT_DASHBOARD_SITE, dashboardCredentialsFile, resolveDashboardAuth, resolveUiSource, ensureManagementDashboard, buildManagementDashboardArtifact, resolveSiteFramework, deploySiteRelease, deployAllComputeSites, reloadRpxGateway, renewRpxCertificates, MANAGED_CACHE_POLICY_OPTIMIZED, MANAGED_CACHE_POLICY_DISABLED, MANAGED_ORIGIN_REQUEST_POLICY_ALL_VIEWER, buildCloudFrontOriginConfig };
@@ -1,8 +1,8 @@
1
1
  import {
2
2
  buildAndPushServerlessImage
3
- } from "./chunk-d2aw5vyv.js";
3
+ } from "./chunk-aqmdexc3.js";
4
4
  import"./chunk-50jpda9q.js";
5
- import"./chunk-1qjyqrc5.js";
5
+ import"./chunk-24kwnpe5.js";
6
6
  import"./chunk-zqtpg06c.js";
7
7
  import"./chunk-v0bahtg2.js";
8
8
  export {
@@ -30,7 +30,7 @@ import {
30
30
  synchronizeDashboardUsers,
31
31
  trackDashboardOperation,
32
32
  verifyStaticApiOrigin
33
- } from "../chunk-qbj74thz.js";
33
+ } from "../chunk-cj3qrktr.js";
34
34
  import {
35
35
  deleteStaticSite,
36
36
  deployStaticSite,
@@ -44,20 +44,23 @@ import {
44
44
  } from "../chunk-ef2dwfrg.js";
45
45
  import {
46
46
  buildAndPushServerlessImage
47
- } from "../chunk-d2aw5vyv.js";
47
+ } from "../chunk-aqmdexc3.js";
48
48
  import"../chunk-ybcz6sxc.js";
49
49
  import"../chunk-01d86gt1.js";
50
50
  import"../chunk-50jpda9q.js";
51
51
  import"../chunk-he9a874b.js";
52
52
  import"../chunk-gttvakpv.js";
53
53
  import {
54
+ IPV6_EXCLUDED_HOST_LABELS,
54
55
  MANAGEMENT_DASHBOARD_SITE,
55
56
  buildManagementDashboardArtifact,
56
57
  collectServerDnsDomains,
57
58
  dashboardCredentialsFile,
58
59
  ensureManagementDashboard,
59
- hetznerBoxIpv6,
60
+ hostAcceptsIpv6,
60
61
  isPhpSite,
62
+ normalizePublicIpv6,
63
+ reconcileAddressRecords,
61
64
  removeStaleServerAddressRecords,
62
65
  resolveDashboardAuth,
63
66
  resolveSiteDeployTarget,
@@ -65,10 +68,11 @@ import {
65
68
  resolveUiSource,
66
69
  shipsARelease,
67
70
  siteInstallBase,
68
- validateDeploymentConfig
69
- } from "../chunk-gx4288t5.js";
70
- import"../chunk-1qjyqrc5.js";
71
- import"../chunk-703nkybg.js";
71
+ validateDeploymentConfig,
72
+ verifyAddressRecord
73
+ } from "../chunk-tcr4xba6.js";
74
+ import"../chunk-24kwnpe5.js";
75
+ import"../chunk-7m60qnc8.js";
72
76
  import"../chunk-4cjrg98a.js";
73
77
  import"../chunk-hpv68b4a.js";
74
78
  import"../chunk-32e7ya18.js";
@@ -77,6 +81,7 @@ import"../chunk-zqtpg06c.js";
77
81
  import"../chunk-v0bahtg2.js";
78
82
  export {
79
83
  verifyStaticApiOrigin,
84
+ verifyAddressRecord,
80
85
  validateDeploymentConfig,
81
86
  uploadStaticFiles,
82
87
  trackDashboardOperation,
@@ -98,11 +103,13 @@ export {
98
103
  resolveDashboardAction,
99
104
  removeStaleServerAddressRecords,
100
105
  redeployServerlessApp,
106
+ reconcileAddressRecords,
107
+ normalizePublicIpv6,
101
108
  isPhpSite,
102
109
  invalidateCache,
103
110
  initializeDashboardControlPlane,
104
111
  infraEnvFromOutputs,
105
- hetznerBoxIpv6,
112
+ hostAcceptsIpv6,
106
113
  hashContainerContext,
107
114
  generateStaticSiteTemplate,
108
115
  generateExternalDnsStaticSiteTemplate,
@@ -131,5 +138,6 @@ export {
131
138
  buildFunctionEnv,
132
139
  buildAndPushServerlessImage,
133
140
  buildAndPushContainerImage,
134
- MANAGEMENT_DASHBOARD_SITE
141
+ MANAGEMENT_DASHBOARD_SITE,
142
+ IPV6_EXCLUDED_HOST_LABELS
135
143
  };
@@ -19,13 +19,72 @@ export declare function collectServerDnsDomains(sites?: Record<string, ServerSit
19
19
  */
20
20
  export declare function removeStaleServerAddressRecords(provider: DnsProvider, zone: string, hostname: string, desiredAddress: string, recordType?: 'A' | 'AAAA'): Promise<string[]>;
21
21
  /**
22
- * Hetzner hands a cloud server a routed /64 and configures `::1` inside it on
23
- * the interface, but the API reports the block (`2a01:4f8:c014:6186::/64`), not
24
- * the address. Publishing the block verbatim as an AAAA record yields a host
25
- * nothing answers on, so turn it into the address the box actually holds.
22
+ * Turn whatever a provider reports as a box's IPv6 into an address an AAAA
23
+ * record can point at.
26
24
  *
27
- * A plain address (no prefix) is returned unchanged, so this is safe to apply
28
- * to whatever the driver surfaced.
25
+ * Providers disagree on what "the server's IPv6" means. Hetzner hands out a
26
+ * routed /64 and reports the block (`2a01:4f8:c014:6186::/64`) while the
27
+ * interface actually holds `::1` inside it; AWS reports a plain address per
28
+ * network interface. Publishing a block verbatim gives an AAAA record nothing
29
+ * answers on, which is the kind of failure that only shows up for the fraction
30
+ * of visitors whose network prefers IPv6 — so every driver runs its value
31
+ * through here rather than reinventing the narrowing.
32
+ *
33
+ * A plain address passes through unchanged.
34
+ */
35
+ export declare function normalizePublicIpv6(reported: string | undefined | null): string | undefined;
36
+ /**
37
+ * Hostname labels that must stay IPv4-only.
38
+ *
39
+ * A box typically runs its mail server on IPv4 alone, and its IPv6 address
40
+ * usually has no PTR — both of which make an AAAA on a mail host actively
41
+ * harmful: senders try the v6 address first, find nothing listening (or get
42
+ * rejected for the missing reverse record) and defer the message. Web traffic
43
+ * has no such constraint, so the exclusion is per-host rather than per-zone.
44
+ */
45
+ export declare const IPV6_EXCLUDED_HOST_LABELS: ReadonlySet<string>;
46
+ /** Whether `fqdn` should get an AAAA record pointing at the box. */
47
+ export declare function hostAcceptsIpv6(fqdn: string): boolean;
48
+ export interface AddressRecordReport {
49
+ /** Records confirmed present at the provider after the write. */
50
+ published: Array<{
51
+ fqdn: string;
52
+ type: 'A' | 'AAAA';
53
+ content: string;
54
+ }>;
55
+ /** Anything the caller should surface: failed writes, unverified writes, stale-record cleanup problems. */
56
+ warnings: string[];
57
+ }
58
+ interface ReconcileAddressRecordsOptions {
59
+ provider: DnsProvider;
60
+ zone: string;
61
+ fqdn: string;
62
+ ipv4: string;
63
+ /** Omit when the box has no public IPv6; the AAAA pass is then skipped entirely. */
64
+ ipv6?: string;
65
+ ttl?: number;
66
+ }
67
+ /**
68
+ * Point one hostname at a box on every address family the box actually has.
69
+ *
70
+ * Shared by every driver and by the framework's deploy command so the rules
71
+ * live in one place: upsert, then remove the *other* records of that family for
72
+ * the same hostname (a leftover address round-robins traffic to a dead host —
73
+ * and a stale AAAA is the worse of the two, because dual-stack clients prefer
74
+ * IPv6), then verify against the provider rather than trusting the write.
75
+ */
76
+ export declare function reconcileAddressRecords(options: ReconcileAddressRecordsOptions): Promise<AddressRecordReport>;
77
+ /**
78
+ * Best-effort post-write check that the record exists at the provider.
79
+ *
80
+ * Returns true when the provider offers no list API or the listing itself
81
+ * fails: verification must never turn a possibly-good write into a false
82
+ * alarm. It exists to catch phantom successes — an upsert that reported OK
83
+ * while editing the wrong record — at providers that can list their zone.
84
+ *
85
+ * The listing is deliberately untyped. Typed listings map to endpoints like
86
+ * Porkbun's retrieveByNameType, whose subdomain-less form returns apex records
87
+ * only, which made verification blind to every non-apex record.
29
88
  */
30
- export declare function hetznerBoxIpv6(reported: string | undefined | null): string | undefined;
89
+ export declare function verifyAddressRecord(provider: DnsProvider, zone: string, fqdn: string, content: string, recordType: 'A' | 'AAAA'): Promise<boolean>;
31
90
  export {};
@@ -68,9 +68,9 @@ import {
68
68
  waitForSsh,
69
69
  wrapCloudInitUserData,
70
70
  writeResizeCheckpoint
71
- } from "../chunk-gx4288t5.js";
72
- import"../chunk-1qjyqrc5.js";
73
- import"../chunk-703nkybg.js";
71
+ } from "../chunk-tcr4xba6.js";
72
+ import"../chunk-24kwnpe5.js";
73
+ import"../chunk-7m60qnc8.js";
74
74
  import"../chunk-4cjrg98a.js";
75
75
  import"../chunk-wj3s95p9.js";
76
76
  import"../chunk-zqtpg06c.js";
@@ -230,6 +230,7 @@ export declare function usesRpxProxy(compute?: {
230
230
  export declare const RPX_DIR = "/etc/rpx";
231
231
  export declare const RPX_INSTALL_DIR = "/opt/rpx-gateway";
232
232
  export declare const RPX_LAUNCHER_PATH = "/etc/rpx/gateway.ts";
233
+ export declare const RPX_BINARY_PATH = "/etc/rpx/gateway";
233
234
  export declare const RPX_SERVICE_NAME = "rpx-gateway.service";
234
235
  /**
235
236
  * Per-app gateway registry. Each project's deploy writes ONLY its own fragment
package/dist/index.d.ts CHANGED
@@ -30,7 +30,8 @@ export { keyMatchesFilters, migrateObjectStorage, remapKey } from './object-stor
30
30
  export type { MigrateEndpoint, MigrateError, MigrateOptions, MigratePlanItem, MigrateProgress, MigrateResult, MigrateVerification, } from './object-storage/migrate';
31
31
  export * from './ssl';
32
32
  export { deployStaticSite, deployStaticSiteFull, uploadStaticFiles, invalidateCache, deleteStaticSite, generateStaticSiteTemplate, deployStaticSiteWithExternalDns, deployStaticSiteWithExternalDnsFull, generateExternalDnsStaticSiteTemplate, deploySite, resolveSiteDeployTarget, resolveSiteKind, validateDeploymentConfig, buildAndPushServerlessImage, buildFunctionEnv, deployServerlessApp, infraEnvFromOutputs, redeployServerlessApp, rollbackServerlessApp, runRemoteCommand, setMaintenance, buildManagementDashboardArtifact, dashboardCredentialsFile, ensureManagementDashboard, MANAGEMENT_DASHBOARD_SITE, resolveDashboardAuth, resolveUiSource, } from './deploy';
33
- export { collectServerDnsDomains, hetznerBoxIpv6, removeStaleServerAddressRecords } from './deploy/server-dns';
33
+ export { collectServerDnsDomains, hostAcceptsIpv6, IPV6_EXCLUDED_HOST_LABELS, normalizePublicIpv6, reconcileAddressRecords, removeStaleServerAddressRecords, verifyAddressRecord, } from './deploy/server-dns';
34
+ export type { AddressRecordReport } from './deploy/server-dns';
34
35
  export type { EnsureDashboardLogger, ResolvedDashboardAuth, StaticSiteConfig, DeployResult, UploadOptions, ExternalDnsStaticSiteConfig, ExternalDnsDeployResult, DeploySiteConfig, DeploySiteResult, StaticSiteDnsProvider, SiteDeployKind, DeploymentValidationResult, BuildImageOptions, BuiltImage, CodeSource, DeployServerlessOptions, ResolvedContext, } from './deploy';
35
36
  export { createCloudDriver, CloudDriverFactory, cloudDrivers, AwsDriver, HetznerDriver, HetznerClient, resolveHetznerApiToken, normalizeSshPublicKey, ensureFirewall, ensureServer, ensureSshKey, serverPublicIpv4, sshExec, sshExecOrThrow, scpUpload, waitForSsh, waitForCloudInit, buildSshArgs, generateUbuntuAppCloudInit, wrapCloudInitUserData, buildHostCleanupScript, buildSiteDeployScript, buildStaticSiteDeployScript, resolveExecStart, deployAllComputeSites, deploySiteRelease, } from './drivers';
36
37
  export type { CreateCloudDriverOptions } from './drivers/factory';