@stacksjs/ts-cloud 0.7.33 → 0.7.35

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.
@@ -29,7 +29,7 @@ import {
29
29
  resolveSiteKind,
30
30
  resolveUiSource,
31
31
  siteInstallBase
32
- } from "./chunk-m0zayf6n.js";
32
+ } from "./chunk-k9se7m8j.js";
33
33
  import {
34
34
  artifactKey,
35
35
  composeServerlessAppTemplate,
@@ -1011,6 +1011,8 @@ function buildRpxProvisionScript(options) {
1011
1011
  const poolEnv = [`Environment=RPX_UPSTREAM_TIMEOUT=${upstreamTimeout}`];
1012
1012
  if (typeof proxy.maxUpstreamConns === "number")
1013
1013
  poolEnv.push(`Environment=RPX_MAX_UPSTREAM_CONNS=${proxy.maxUpstreamConns}`);
1014
+ const memoryHigh = proxy.memoryHigh ?? "512M";
1015
+ const memoryMax = proxy.memoryMax ?? "768M";
1014
1016
  return [
1015
1017
  "set -euo pipefail",
1016
1018
  `mkdir -p ${RPX_DIR} ${RPX_SITES_DIR} ${certsDir} ${RPX_INSTALL_DIR}`,
@@ -1035,6 +1037,10 @@ function buildRpxProvisionScript(options) {
1035
1037
  `WorkingDirectory=${RPX_INSTALL_DIR}`,
1036
1038
  `Environment=BUN_INSTALL=/root/.bun`,
1037
1039
  ...poolEnv,
1040
+ "MemoryAccounting=true",
1041
+ `MemoryHigh=${memoryHigh}`,
1042
+ `MemoryMax=${memoryMax}`,
1043
+ "OOMPolicy=stop",
1038
1044
  "Restart=always",
1039
1045
  "RestartSec=5",
1040
1046
  "LimitNOFILE=1048576",
@@ -1476,7 +1482,8 @@ function buildUbuntuBootstrapScript(options = {}) {
1476
1482
  servicesProvision,
1477
1483
  caddyfile,
1478
1484
  rpxProvision,
1479
- baked = false
1485
+ baked = false,
1486
+ swapGb = 2
1480
1487
  } = options;
1481
1488
  const packages = new Set(systemPackages);
1482
1489
  if (database === "sqlite")
@@ -1488,6 +1495,25 @@ function buildUbuntuBootstrapScript(options = {}) {
1488
1495
  let script = `#!/bin/bash
1489
1496
  set -euo pipefail
1490
1497
  `;
1498
+ const swapSizeGb = Math.floor(swapGb);
1499
+ if (swapSizeGb > 0) {
1500
+ script += `
1501
+ # Swap: a swapless box under memory pressure lets the kernel OOM killer choose
1502
+ # victims box-wide (the gateway, a tenant app, postgres). A modest swapfile
1503
+ # with low swappiness gives the kernel headroom without hurting responsiveness.
1504
+ if ! swapon --show=NAME --noheadings 2>/dev/null | grep -qx '/swapfile'; then
1505
+ if [ ! -f /swapfile ]; then
1506
+ fallocate -l ${swapSizeGb}G /swapfile || dd if=/dev/zero of=/swapfile bs=1M count=${swapSizeGb * 1024} status=none
1507
+ chmod 600 /swapfile
1508
+ mkswap /swapfile >/dev/null
1509
+ fi
1510
+ swapon /swapfile
1511
+ fi
1512
+ grep -qE '^/swapfile\\s' /etc/fstab || echo '/swapfile none swap sw 0 0' >> /etc/fstab
1513
+ echo 'vm.swappiness=10' > /etc/sysctl.d/99-ts-cloud-swap.conf
1514
+ sysctl -w vm.swappiness=10 >/dev/null
1515
+ `;
1516
+ }
1491
1517
  if (!baked) {
1492
1518
  script += `
1493
1519
  export DEBIAN_FRONTEND=noninteractive
@@ -1633,6 +1659,12 @@ function awsComputeIngressRules(config) {
1633
1659
  function buildAwsUserData(config) {
1634
1660
  const compute = config.infrastructure?.compute ?? {};
1635
1661
  const provision = buildComputeProvisionScripts(config);
1662
+ const rpxProvision = compute.proxy?.engine === "rpx" ? buildRpxProvisionScript({
1663
+ proxy: compute.proxy,
1664
+ config: buildRpxConfig(config.sites ?? {}, { proxy: compute.proxy, slug: config.project.slug }),
1665
+ slug: config.project.slug,
1666
+ bunBin: compute.runtime === "node" || compute.runtime === "deno" ? undefined : "/usr/local/bin/bun"
1667
+ }) : undefined;
1636
1668
  return buildUbuntuBootstrapScript({
1637
1669
  runtime: provision.runtime,
1638
1670
  runtimeVersion: provision.runtimeVersion,
@@ -1640,7 +1672,9 @@ function buildAwsUserData(config) {
1640
1672
  database: config.infrastructure?.database,
1641
1673
  phpProvision: provision.phpProvision,
1642
1674
  servicesProvision: provision.servicesProvision,
1643
- baked: compute.bakedImage === true
1675
+ rpxProvision,
1676
+ baked: compute.bakedImage === true,
1677
+ swapGb: compute.swapGb
1644
1678
  });
1645
1679
  }
1646
1680
  function encodeUserData(userData) {
@@ -2466,7 +2500,8 @@ class HetznerDriver {
2466
2500
  phpProvision: provision.phpProvision,
2467
2501
  servicesProvision: provision.servicesProvision,
2468
2502
  rpxProvision,
2469
- baked
2503
+ baked,
2504
+ swapGb: compute.swapGb
2470
2505
  });
2471
2506
  const userData = wrapCloudInitUserData(bootstrap);
2472
2507
  const serverType = resolveHetznerServerType(compute.size);
@@ -2537,7 +2572,7 @@ class HetznerDriver {
2537
2572
  { direction: "in", protocol: "tcp", port: "7700", source_ips: [network.ip_range] }
2538
2573
  ]);
2539
2574
  const servicesProvision = buildFleetServicesBoxProvision(config);
2540
- const servicesUserData = wrapCloudInitUserData(buildUbuntuBootstrapScript({ runtime: "php", servicesProvision, baked }));
2575
+ const servicesUserData = wrapCloudInitUserData(buildUbuntuBootstrapScript({ runtime: "php", servicesProvision, baked, swapGb: compute.swapGb }));
2541
2576
  const all = await this.client.listServers().catch(() => []);
2542
2577
  const newServerIds = [];
2543
2578
  let svcServer = all.find((s) => matchesTsCloudLabels(s.labels, slug, environment, "services"));
@@ -2573,7 +2608,7 @@ class HetznerDriver {
2573
2608
  optimizeForProduction: compute.php?.optimizeForProduction,
2574
2609
  ini: compute.php?.ini
2575
2610
  });
2576
- const appUserData = wrapCloudInitUserData(buildUbuntuBootstrapScript({ runtime: "php", phpProvision: appPhp, servicesProvision: appProvision, baked }));
2611
+ const appUserData = wrapCloudInitUserData(buildUbuntuBootstrapScript({ runtime: "php", phpProvision: appPhp, servicesProvision: appProvision, baked, swapGb: compute.swapGb }));
2577
2612
  const existingApp = all.filter((s) => matchesTsCloudLabels(s.labels, slug, environment, "app"));
2578
2613
  const appServerIds = existingApp.map((s) => s.id);
2579
2614
  if (existingApp.length > topology.appServers) {
@@ -2707,7 +2742,7 @@ class HetznerDriver {
2707
2742
  { direction: "in", protocol: "tcp", port: "7700", source_ips: [network.ip_range] }
2708
2743
  ]);
2709
2744
  const servicesProvision = buildFleetServicesBoxProvision(config);
2710
- const servicesUserData = wrapCloudInitUserData(buildUbuntuBootstrapScript({ runtime: "bun", servicesProvision, baked }));
2745
+ const servicesUserData = wrapCloudInitUserData(buildUbuntuBootstrapScript({ runtime: "bun", servicesProvision, baked, swapGb: compute.swapGb }));
2711
2746
  let svcServer = all.find((s) => matchesTsCloudLabels(s.labels, slug, environment, "services"));
2712
2747
  if (!svcServer) {
2713
2748
  const { server, action } = await this.client.createServer({
@@ -2739,7 +2774,8 @@ class HetznerDriver {
2739
2774
  systemPackages: compute.systemPackages,
2740
2775
  database: config.infrastructure?.database,
2741
2776
  servicesProvision: appProvisionScripts.servicesProvision,
2742
- baked
2777
+ baked,
2778
+ swapGb: compute.swapGb
2743
2779
  }));
2744
2780
  const existingApp = all.filter((s) => matchesTsCloudLabels(s.labels, slug, environment, "app"));
2745
2781
  const appServerIds = existingApp.map((s) => s.id);
@@ -12,7 +12,7 @@ import {
12
12
  sanitizeCloudConfig,
13
13
  setMaintenance,
14
14
  startLocalDashboardServer
15
- } from "../chunk-1nfkggdw.js";
15
+ } from "../chunk-94myv1cx.js";
16
16
  import {
17
17
  buildAndPushServerlessImage
18
18
  } from "../chunk-tskj9fay.js";
@@ -30,7 +30,7 @@ import {
30
30
  resolveUiSource,
31
31
  siteInstallBase,
32
32
  validateDeploymentConfig
33
- } from "../chunk-m0zayf6n.js";
33
+ } from "../chunk-k9se7m8j.js";
34
34
  import"../chunk-stt1z5cx.js";
35
35
  import"../chunk-93hjhs78.js";
36
36
  import {
@@ -50,7 +50,7 @@ import {
50
50
  waitForCloudInit,
51
51
  waitForSsh,
52
52
  wrapCloudInitUserData
53
- } from "../chunk-m0zayf6n.js";
53
+ } from "../chunk-k9se7m8j.js";
54
54
  import"../chunk-stt1z5cx.js";
55
55
  import"../chunk-93hjhs78.js";
56
56
  import"../chunk-qpj3edwz.js";
@@ -47,6 +47,16 @@ export interface UbuntuBootstrapOptions {
47
47
  * boot near-instant. @default false
48
48
  */
49
49
  baked?: boolean;
50
+ /**
51
+ * Swapfile size in GB, from `compute.swapGb`. Small shared boxes run several
52
+ * tenants + services with no swap at all, so a memory spike (deploy, bun
53
+ * install, on-box builds) leaves the kernel OOM killer picking victims
54
+ * box-wide. Provisioned at the very top of the bootstrap — BEFORE the
55
+ * memory-hungry installs — on cold and baked boots alike (a golden image
56
+ * deliberately bakes no swap; see the image recipe). Idempotent: skipped
57
+ * when `/swapfile` is already active. `0` disables. @default 2
58
+ */
59
+ swapGb?: number;
50
60
  }
51
61
  /**
52
62
  * Build the Ubuntu provisioning bash script (with `#!/bin/bash` shebang).
package/dist/index.js CHANGED
@@ -55,7 +55,7 @@ import {
55
55
  sanitizeCloudConfig,
56
56
  setMaintenance,
57
57
  startLocalDashboardServer
58
- } from "./chunk-1nfkggdw.js";
58
+ } from "./chunk-94myv1cx.js";
59
59
  import {
60
60
  buildAndPushServerlessImage
61
61
  } from "./chunk-tskj9fay.js";
@@ -105,7 +105,7 @@ import {
105
105
  waitForCloudInit,
106
106
  waitForSsh,
107
107
  wrapCloudInitUserData
108
- } from "./chunk-m0zayf6n.js";
108
+ } from "./chunk-k9se7m8j.js";
109
109
  import {
110
110
  ABTestManager,
111
111
  AI,