@stacksjs/ts-cloud 0.7.49 → 0.7.51

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 (35) hide show
  1. package/dist/bin/cli.js +444 -444
  2. package/dist/{chunk-kshwaj48.js → chunk-b17vf1g1.js} +1 -1
  3. package/dist/{chunk-9359j1an.js → chunk-d5vqwj8s.js} +78 -4
  4. package/dist/deploy/index.js +2 -2
  5. package/dist/drivers/hetzner/driver.d.ts +8 -0
  6. package/dist/drivers/index.d.ts +1 -1
  7. package/dist/drivers/index.js +3 -1
  8. package/dist/drivers/shared/compute-deploy.d.ts +7 -0
  9. package/dist/drivers/shared/rpx-gateway.d.ts +1 -0
  10. package/dist/index.js +2 -2
  11. package/dist/ui/index.html +3 -3
  12. package/dist/ui/server/actions.html +3 -3
  13. package/dist/ui/server/backups.html +3 -3
  14. package/dist/ui/server/database.html +3 -3
  15. package/dist/ui/server/deployments.html +3 -3
  16. package/dist/ui/server/firewall.html +3 -3
  17. package/dist/ui/server/logs.html +3 -3
  18. package/dist/ui/server/services.html +3 -3
  19. package/dist/ui/server/sites.html +3 -3
  20. package/dist/ui/server/ssh-keys.html +3 -3
  21. package/dist/ui/server/team.html +3 -3
  22. package/dist/ui/server/terminal.html +3 -3
  23. package/dist/ui/server/workers.html +3 -3
  24. package/dist/ui/serverless/alarms.html +3 -3
  25. package/dist/ui/serverless/assets.html +3 -3
  26. package/dist/ui/serverless/data.html +3 -3
  27. package/dist/ui/serverless/deployments.html +3 -3
  28. package/dist/ui/serverless/functions.html +3 -3
  29. package/dist/ui/serverless/logs.html +3 -3
  30. package/dist/ui/serverless/queues.html +3 -3
  31. package/dist/ui/serverless/scheduler.html +3 -3
  32. package/dist/ui/serverless/secrets.html +3 -3
  33. package/dist/ui/serverless/traces.html +3 -3
  34. package/dist/ui/serverless.html +3 -3
  35. package/package.json +3 -3
@@ -29,7 +29,7 @@ import {
29
29
  resolveSiteKind,
30
30
  resolveUiSource,
31
31
  siteInstallBase
32
- } from "./chunk-9359j1an.js";
32
+ } from "./chunk-d5vqwj8s.js";
33
33
  import {
34
34
  artifactKey,
35
35
  composeServerlessAppTemplate,
@@ -915,6 +915,10 @@ function writeFileHeredoc(path, content, delimiter, mode = "0644") {
915
915
  `chmod ${mode} ${path}`
916
916
  ];
917
917
  }
918
+ function rpxCertRenewServiceName(slug) {
919
+ const safeSlug = (slug || "app").replace(/[^a-z0-9._-]+/gi, "-");
920
+ return `rpx-cert-renew-${safeSlug}.service`;
921
+ }
918
922
  function certDomainsForConfig(config) {
919
923
  const seen = new Set;
920
924
  for (const r of config.proxies) {
@@ -940,7 +944,7 @@ function buildCertManagementCommands(options) {
940
944
  const spaced = domains.join(" ");
941
945
  const slug = (options.slug || "app").replace(/[^a-z0-9._-]+/gi, "-");
942
946
  const renewScriptPath = `${RPX_DIR}/renew-certs-${slug}.sh`;
943
- const renewServiceName = `rpx-cert-renew-${slug}.service`;
947
+ const renewServiceName = rpxCertRenewServiceName(slug);
944
948
  const renewTimerName = `rpx-cert-renew-${slug}.timer`;
945
949
  const renewScript = [
946
950
  "#!/bin/sh",
@@ -2447,6 +2451,9 @@ class HetznerDriver {
2447
2451
  if (!compute) {
2448
2452
  throw new Error("infrastructure.compute is required to provision Hetzner compute");
2449
2453
  }
2454
+ if (config.cloud?.attachTo) {
2455
+ return this.attachToComputeInfrastructure(config.cloud.attachTo, config, environment);
2456
+ }
2450
2457
  const stackName = resolveProjectStackName(config, environment);
2451
2458
  const serverName = `${slug}-${environment}-app`;
2452
2459
  const phpBox = compute.runtime === "php" || !!compute.php;
@@ -3022,9 +3029,9 @@ class HetznerDriver {
3022
3029
  const exact = servers.filter((server) => matchesTsCloudLabels(server.labels, options.slug, options.environment, role));
3023
3030
  if (exact.length > 0)
3024
3031
  return exact.map(toTarget);
3025
- if (role === "app") {
3032
+ if (role === "app" || role === "lb" || role === "services") {
3026
3033
  const state = await readDriverState(options.stackName ?? `${options.slug}-${options.environment}`);
3027
- const pinnedIds = [state?.serverId, ...state?.appServerIds ?? []].filter((id) => typeof id === "number");
3034
+ const pinnedIds = [...new Set(role === "app" ? [state?.serverId, ...state?.appServerIds ?? []] : role === "lb" ? [state?.lbServerId] : [state?.servicesServerId])].filter((id) => typeof id === "number");
3028
3035
  if (pinnedIds.length > 0) {
3029
3036
  const pinned = [];
3030
3037
  for (const id of pinnedIds) {
@@ -3043,6 +3050,34 @@ class HetznerDriver {
3043
3050
  }
3044
3051
  return [];
3045
3052
  }
3053
+ async attachToComputeInfrastructure(ownerSlug, config, environment) {
3054
+ const servers = await this.client.listServers();
3055
+ const running = (role) => servers.filter((server) => server.status !== "off" && matchesTsCloudLabels(server.labels, ownerSlug, environment, role));
3056
+ const appServers = running("app");
3057
+ if (appServers.length === 0) {
3058
+ throw new Error(`Cannot attach '${config.project.slug}' to '${ownerSlug}': no running Hetzner app server is labeled for ${ownerSlug}/${environment}. Deploy the owner project first.`);
3059
+ }
3060
+ const lbServer = running("lb")[0];
3061
+ const servicesServer = running("services")[0];
3062
+ const primaryApp = appServers[0];
3063
+ const publicGateway = lbServer ?? primaryApp;
3064
+ const stackName = resolveProjectStackName(config, environment);
3065
+ const state = {
3066
+ provider: "hetzner",
3067
+ stackName,
3068
+ serverId: primaryApp.id,
3069
+ serverName: primaryApp.name,
3070
+ appServerIds: appServers.map((server) => server.id),
3071
+ lbServerId: lbServer?.id,
3072
+ servicesServerId: servicesServer?.id,
3073
+ servicesPrivateIp: servicesServer?.private_net?.[0]?.ip,
3074
+ publicIp: publicGateway.public_net.ipv4?.ip,
3075
+ deployStoragePath: "/var/ts-cloud/staging",
3076
+ sshUser: this.sshUser
3077
+ };
3078
+ await writeDriverState(stackName, state);
3079
+ return this.outputsFromState(state);
3080
+ }
3046
3081
  async runRemoteDeploy(options) {
3047
3082
  if (options.targets.length === 0) {
3048
3083
  return { success: false, instanceCount: 0, perInstance: [], error: "No targets provided" };
@@ -5090,6 +5125,45 @@ async function reloadRpxGateway(options) {
5090
5125
  logger.success(`rpx gateway reloaded on ${result.instanceCount} target(s)`);
5091
5126
  return true;
5092
5127
  }
5128
+ async function renewRpxCertificates(options) {
5129
+ const { config, environment, driver, logger = noopLogger2 } = options;
5130
+ const routeSource = options.rpxConfig ?? config;
5131
+ const proxy = routeSource.infrastructure?.compute?.proxy ?? config.infrastructure?.compute?.proxy;
5132
+ if (proxy?.engine !== "rpx" || !proxy.onDemandTls)
5133
+ return true;
5134
+ const rpxConfig = buildRpxConfig(routeSource.sites || {}, { proxy, slug: config.project.slug });
5135
+ if (rpxConfig.proxies.length === 0)
5136
+ return true;
5137
+ const slug = config.project.slug;
5138
+ const stackName = resolveProjectStackName(config, environment);
5139
+ let role = "lb";
5140
+ let targets = await driver.findComputeTargets({ slug, environment, role, stackName });
5141
+ if (targets.length === 0) {
5142
+ role = "app";
5143
+ targets = await driver.findComputeTargets({ slug, environment, role, stackName });
5144
+ }
5145
+ if (targets.length === 0) {
5146
+ logger.warn("rpx TLS: no gateway targets found - skipping certificate issuance.");
5147
+ return true;
5148
+ }
5149
+ logger.step("Issuing rpx TLS certificates after DNS reconciliation...");
5150
+ const result = await driver.runRemoteDeploy({
5151
+ targets,
5152
+ commands: [`systemctl start ${rpxCertRenewServiceName(slug)}`],
5153
+ comment: `ts-cloud rpx TLS issuance ${slug}`,
5154
+ tags: {
5155
+ Project: slug,
5156
+ Environment: environment,
5157
+ Role: role
5158
+ }
5159
+ });
5160
+ if (!result.success) {
5161
+ logger.error(`rpx TLS issuance failed: ${result.error || "unknown error"}`);
5162
+ return false;
5163
+ }
5164
+ logger.success(`rpx TLS certificates reconciled on ${result.instanceCount} gateway target(s)`);
5165
+ return true;
5166
+ }
5093
5167
  // src/drivers/shared/cloudfront-origin.ts
5094
5168
  var MANAGED_CACHE_POLICY_OPTIMIZED = "658327ea-f89d-4fab-a63d-7e88639e58f6";
5095
5169
  var MANAGED_CACHE_POLICY_DISABLED = "4135ea2d-6df8-44a3-9df3-4b5a84be39ad";
@@ -5172,4 +5246,4 @@ function buildCloudFrontOriginConfig(options) {
5172
5246
  CustomErrorResponses: { Quantity: 0 }
5173
5247
  };
5174
5248
  }
5175
- export { siteInstallBase, isPhpSite, resolveSiteDeployTarget, resolveSiteKind, validateDeploymentConfig, PANTRY_PROJECT_DIR, pgAdminCommand, DEFAULT_RPX_CERTS_DIR, normalizeRoutePath, deriveRouteId, buildRpxConfig, buildRpxLbConfig, renderRpxLauncher, RPX_DIR, RPX_LAUNCHER_PATH, RPX_SERVICE_NAME, buildRpxProvisionScript, buildRpxFragmentRefreshScript, BACKUP_RUNNER_PATH, buildBackupRestoreScript, buildUbuntuBootstrapScript, AwsDriver, resolveHetznerLocation, HetznerClient, resolveHetznerApiToken2 as resolveHetznerApiToken, normalizeSshPublicKey, wrapCloudInitUserData, HetznerDriver, LocalBoxDriver, isBoxMode, createCloudDriver, CloudDriverFactory, cloudDrivers, ensureSshKey, ensureFirewall, ensureServer, serverPublicIpv4, buildSshArgs, sshExec, sshExecOrThrow, scpUpload, waitForSsh, waitForCloudInit, UBUNTU_2404_AMI_PARAM, buildBoxUserData, HetznerBoxProvisioner, AwsBoxProvisioner, createBoxProvisioner, releasePaths, buildRollbackScript, resolveExecStart, buildSiteDeployScript, buildStaticSiteDeployScript, releaseTarballTmpPath, buildAwsArtifactFetch, buildLocalArtifactFetch, buildHostCleanupScript, MANAGEMENT_DASHBOARD_SITE, DASHBOARD_CREDENTIALS_FILE, resolveDashboardAuth, resolveUiSource, ensureManagementDashboard, buildManagementDashboardArtifact, resolveSiteFramework, deploySiteRelease, deployAllComputeSites, reloadRpxGateway, MANAGED_CACHE_POLICY_OPTIMIZED, MANAGED_CACHE_POLICY_DISABLED, MANAGED_ORIGIN_REQUEST_POLICY_ALL_VIEWER, buildCloudFrontOriginConfig };
5249
+ export { siteInstallBase, isPhpSite, resolveSiteDeployTarget, resolveSiteKind, validateDeploymentConfig, PANTRY_PROJECT_DIR, pgAdminCommand, DEFAULT_RPX_CERTS_DIR, normalizeRoutePath, deriveRouteId, buildRpxConfig, buildRpxLbConfig, renderRpxLauncher, RPX_DIR, RPX_LAUNCHER_PATH, RPX_SERVICE_NAME, buildRpxProvisionScript, buildRpxFragmentRefreshScript, BACKUP_RUNNER_PATH, buildBackupRestoreScript, buildUbuntuBootstrapScript, AwsDriver, resolveHetznerLocation, HetznerClient, resolveHetznerApiToken2 as resolveHetznerApiToken, normalizeSshPublicKey, wrapCloudInitUserData, HetznerDriver, LocalBoxDriver, isBoxMode, createCloudDriver, CloudDriverFactory, cloudDrivers, ensureSshKey, ensureFirewall, ensureServer, serverPublicIpv4, buildSshArgs, sshExec, sshExecOrThrow, scpUpload, waitForSsh, waitForCloudInit, UBUNTU_2404_AMI_PARAM, buildBoxUserData, HetznerBoxProvisioner, AwsBoxProvisioner, createBoxProvisioner, releasePaths, buildRollbackScript, resolveExecStart, buildSiteDeployScript, buildStaticSiteDeployScript, releaseTarballTmpPath, buildAwsArtifactFetch, buildLocalArtifactFetch, buildHostCleanupScript, MANAGEMENT_DASHBOARD_SITE, DASHBOARD_CREDENTIALS_FILE, resolveDashboardAuth, resolveUiSource, ensureManagementDashboard, buildManagementDashboardArtifact, resolveSiteFramework, deploySiteRelease, deployAllComputeSites, reloadRpxGateway, renewRpxCertificates, MANAGED_CACHE_POLICY_OPTIMIZED, MANAGED_CACHE_POLICY_DISABLED, MANAGED_ORIGIN_REQUEST_POLICY_ALL_VIEWER, buildCloudFrontOriginConfig };
@@ -14,7 +14,7 @@ import {
14
14
  sanitizeCloudConfig,
15
15
  setMaintenance,
16
16
  startLocalDashboardServer
17
- } from "../chunk-kshwaj48.js";
17
+ } from "../chunk-b17vf1g1.js";
18
18
  import {
19
19
  buildAndPushServerlessImage
20
20
  } from "../chunk-f29w04w5.js";
@@ -32,7 +32,7 @@ import {
32
32
  resolveUiSource,
33
33
  siteInstallBase,
34
34
  validateDeploymentConfig
35
- } from "../chunk-9359j1an.js";
35
+ } from "../chunk-d5vqwj8s.js";
36
36
  import"../chunk-r16e02bc.js";
37
37
  import"../chunk-93hjhs78.js";
38
38
  import {
@@ -83,6 +83,14 @@ export declare class HetznerDriver implements CloudDriver {
83
83
  getComputeOutputs(options: ProvisionComputeOptions): Promise<ComputeStackOutputs>;
84
84
  uploadRelease(options: UploadReleaseOptions): Promise<UploadReleaseResult>;
85
85
  findComputeTargets(options: FindComputeTargetsOptions): Promise<ComputeTarget[]>;
86
+ /**
87
+ * Attach this project to compute owned by another ts-cloud project.
88
+ *
89
+ * The owner's Hetzner labels are the source of truth. We write a tenant-local
90
+ * state pin so all later deploy operations resolve the same app/LB targets,
91
+ * even when several unrelated managed boxes exist in the Hetzner project.
92
+ */
93
+ private attachToComputeInfrastructure;
86
94
  runRemoteDeploy(options: RunRemoteDeployOptions): Promise<RemoteDeployResult>;
87
95
  /**
88
96
  * Ensure the local SSH public key is registered in the Hetzner project and
@@ -11,7 +11,7 @@ export type { RemoteExecOptions, RemoteExecResult, WaitOptions } from './shared/
11
11
  export { AwsBoxProvisioner, buildBoxUserData, createBoxProvisioner, HetznerBoxProvisioner, UBUNTU_2404_AMI_PARAM, } from './shared/box-provision';
12
12
  export type { AwsBoxProvisionerOptions, BoxPort, BoxProviderName, BoxProvisioner, BoxSpec, CreateBoxProvisionerOptions, ProvisionedBox, } from './shared/box-provision';
13
13
  export { buildAwsArtifactFetch, buildHostCleanupScript, buildLocalArtifactFetch, buildSiteDeployScript, buildStaticSiteDeployScript, releaseTarballTmpPath, resolveExecStart, } from './shared/deploy-script';
14
- export { deployAllComputeSites, deploySiteRelease, reloadRpxGateway } from './shared/compute-deploy';
14
+ export { deployAllComputeSites, deploySiteRelease, reloadRpxGateway, renewRpxCertificates } from './shared/compute-deploy';
15
15
  export { buildRpxConfig, buildRpxFragmentRefreshScript, buildRpxLbConfig, buildRpxProvisionScript, deriveRouteId, normalizeRoutePath, renderRpxLauncher, DEFAULT_RPX_CERTS_DIR, RPX_DIR, RPX_LAUNCHER_PATH, RPX_SERVICE_NAME, } from './shared/rpx-gateway';
16
16
  export type { BuildRpxConfigOptions, BuildRpxFragmentRefreshOptions, BuildRpxProvisionOptions, RpxGatewayConfig, RpxLbAppBox, RpxRoute, } from './shared/rpx-gateway';
17
17
  export { buildCloudFrontOriginConfig, MANAGED_CACHE_POLICY_DISABLED, MANAGED_CACHE_POLICY_OPTIMIZED, MANAGED_ORIGIN_REQUEST_POLICY_ALL_VIEWER, } from './shared/cloudfront-origin';
@@ -42,6 +42,7 @@ import {
42
42
  releaseTarballTmpPath,
43
43
  reloadRpxGateway,
44
44
  renderRpxLauncher,
45
+ renewRpxCertificates,
45
46
  resolveExecStart,
46
47
  resolveHetznerApiToken,
47
48
  scpUpload,
@@ -51,7 +52,7 @@ import {
51
52
  waitForCloudInit,
52
53
  waitForSsh,
53
54
  wrapCloudInitUserData
54
- } from "../chunk-9359j1an.js";
55
+ } from "../chunk-d5vqwj8s.js";
55
56
  import"../chunk-r16e02bc.js";
56
57
  import"../chunk-93hjhs78.js";
57
58
  import"../chunk-qpj3edwz.js";
@@ -68,6 +69,7 @@ export {
68
69
  scpUpload,
69
70
  resolveHetznerApiToken,
70
71
  resolveExecStart,
72
+ renewRpxCertificates,
71
73
  renderRpxLauncher,
72
74
  reloadRpxGateway,
73
75
  releaseTarballTmpPath,
@@ -47,3 +47,10 @@ export declare function deployAllComputeSites(options: DeployAllSitesOptions): P
47
47
  * launcher + unit and `systemctl restart`s, reloading the new routes.
48
48
  */
49
49
  export declare function reloadRpxGateway(options: DeployAllSitesOptions): Promise<boolean>;
50
+ /**
51
+ * Request the first production certificates after DNS has been reconciled.
52
+ * Provisioning installs the per-project renewal unit, but ACME HTTP-01 cannot
53
+ * succeed until the domain points at the new box. This deliberately runs after
54
+ * DNS and targets the same gateway box selected by reloadRpxGateway.
55
+ */
56
+ export declare function renewRpxCertificates(options: Pick<DeployAllSitesOptions, 'config' | 'environment' | 'driver' | 'logger' | 'rpxConfig'>): Promise<boolean>;
@@ -273,6 +273,7 @@ export interface BuildRpxProvisionOptions {
273
273
  export declare const RPX_CERT_RENEW_SCRIPT = "/etc/rpx/renew-certs.sh";
274
274
  export declare const RPX_CERT_RENEW_SERVICE = "rpx-cert-renew.service";
275
275
  export declare const RPX_CERT_RENEW_TIMER = "rpx-cert-renew.timer";
276
+ export declare function rpxCertRenewServiceName(slug: string): string;
276
277
  /** The routable FQDNs in a gateway config — each terminates TLS so each needs a cert. */
277
278
  export declare function certDomainsForConfig(config: RpxGatewayConfig): string[];
278
279
  /**
package/dist/index.js CHANGED
@@ -57,7 +57,7 @@ import {
57
57
  sanitizeCloudConfig,
58
58
  setMaintenance,
59
59
  startLocalDashboardServer
60
- } from "./chunk-kshwaj48.js";
60
+ } from "./chunk-b17vf1g1.js";
61
61
  import {
62
62
  buildAndPushServerlessImage
63
63
  } from "./chunk-f29w04w5.js";
@@ -108,7 +108,7 @@ import {
108
108
  waitForCloudInit,
109
109
  waitForSsh,
110
110
  wrapCloudInitUserData
111
- } from "./chunk-9359j1an.js";
111
+ } from "./chunk-d5vqwj8s.js";
112
112
  import {
113
113
  ABTestManager,
114
114
  AI,