@stacksjs/ts-cloud 0.7.32 → 0.7.34
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/bin/cli.js +709 -693
- package/dist/{chunk-dah449r1.js → chunk-e6xhm3qe.js} +47 -11
- package/dist/{chunk-jgenfdz6.js → chunk-jvp3s3na.js} +4 -2
- package/dist/deploy/index.js +2 -2
- package/dist/drivers/index.js +1 -1
- package/dist/drivers/shared/ubuntu-bootstrap.d.ts +10 -0
- package/dist/index.js +2 -2
- package/dist/ui/index.html +3 -3
- package/dist/ui/server/actions.html +23 -6
- package/dist/ui/server/backups.html +3 -3
- package/dist/ui/server/database.html +3 -3
- package/dist/ui/server/deployments.html +3 -3
- package/dist/ui/server/firewall.html +3 -3
- package/dist/ui/server/logs.html +3 -3
- package/dist/ui/server/services.html +3 -3
- package/dist/ui/server/sites.html +3 -3
- package/dist/ui/server/ssh-keys.html +26 -4
- package/dist/ui/server/team.html +26 -4
- package/dist/ui/server/terminal.html +3 -3
- package/dist/ui/server/workers.html +3 -3
- package/dist/ui/serverless/alarms.html +3 -3
- package/dist/ui/serverless/assets.html +3 -3
- package/dist/ui/serverless/data.html +3 -3
- package/dist/ui/serverless/deployments.html +3 -3
- package/dist/ui/serverless/functions.html +3 -3
- package/dist/ui/serverless/logs.html +3 -3
- package/dist/ui/serverless/queues.html +3 -3
- package/dist/ui/serverless/scheduler.html +3 -3
- package/dist/ui/serverless/secrets.html +3 -3
- package/dist/ui/serverless/traces.html +3 -3
- package/dist/ui/serverless.html +23 -6
- package/dist/ui-src/pages/server/actions.stx +27 -4
- package/dist/ui-src/pages/server/ssh-keys.stx +22 -1
- package/dist/ui-src/pages/server/team.stx +23 -1
- package/dist/ui-src/pages/serverless.stx +27 -4
- package/package.json +3 -3
|
@@ -401,7 +401,14 @@ function vhostBody(options) {
|
|
|
401
401
|
return lines;
|
|
402
402
|
}
|
|
403
403
|
function buildNginxVhost(options) {
|
|
404
|
-
const
|
|
404
|
+
const hosts = [options.domain, ...options.aliases || []].filter(Boolean);
|
|
405
|
+
for (const host of hosts) {
|
|
406
|
+
if (!/^(?=.{1,253}$)(?:\*\.)?[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?(?:\.[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)*$/i.test(host.trim()))
|
|
407
|
+
throw new Error(`Refusing to build a vhost: '${host}' is not a valid hostname.`);
|
|
408
|
+
}
|
|
409
|
+
if (!hosts.length)
|
|
410
|
+
throw new Error("Refusing to build a vhost: no server_name (domain) was given.");
|
|
411
|
+
const serverNames = hosts.join(" ");
|
|
405
412
|
const body = vhostBody(options);
|
|
406
413
|
if (options.ssl) {
|
|
407
414
|
const redirect = [
|
|
@@ -1004,6 +1011,8 @@ function buildRpxProvisionScript(options) {
|
|
|
1004
1011
|
const poolEnv = [`Environment=RPX_UPSTREAM_TIMEOUT=${upstreamTimeout}`];
|
|
1005
1012
|
if (typeof proxy.maxUpstreamConns === "number")
|
|
1006
1013
|
poolEnv.push(`Environment=RPX_MAX_UPSTREAM_CONNS=${proxy.maxUpstreamConns}`);
|
|
1014
|
+
const memoryHigh = proxy.memoryHigh ?? "512M";
|
|
1015
|
+
const memoryMax = proxy.memoryMax ?? "768M";
|
|
1007
1016
|
return [
|
|
1008
1017
|
"set -euo pipefail",
|
|
1009
1018
|
`mkdir -p ${RPX_DIR} ${RPX_SITES_DIR} ${certsDir} ${RPX_INSTALL_DIR}`,
|
|
@@ -1028,6 +1037,10 @@ function buildRpxProvisionScript(options) {
|
|
|
1028
1037
|
`WorkingDirectory=${RPX_INSTALL_DIR}`,
|
|
1029
1038
|
`Environment=BUN_INSTALL=/root/.bun`,
|
|
1030
1039
|
...poolEnv,
|
|
1040
|
+
"MemoryAccounting=true",
|
|
1041
|
+
`MemoryHigh=${memoryHigh}`,
|
|
1042
|
+
`MemoryMax=${memoryMax}`,
|
|
1043
|
+
"OOMPolicy=stop",
|
|
1031
1044
|
"Restart=always",
|
|
1032
1045
|
"RestartSec=5",
|
|
1033
1046
|
"LimitNOFILE=1048576",
|
|
@@ -1469,7 +1482,8 @@ function buildUbuntuBootstrapScript(options = {}) {
|
|
|
1469
1482
|
servicesProvision,
|
|
1470
1483
|
caddyfile,
|
|
1471
1484
|
rpxProvision,
|
|
1472
|
-
baked = false
|
|
1485
|
+
baked = false,
|
|
1486
|
+
swapGb = 2
|
|
1473
1487
|
} = options;
|
|
1474
1488
|
const packages = new Set(systemPackages);
|
|
1475
1489
|
if (database === "sqlite")
|
|
@@ -1481,6 +1495,25 @@ function buildUbuntuBootstrapScript(options = {}) {
|
|
|
1481
1495
|
let script = `#!/bin/bash
|
|
1482
1496
|
set -euo pipefail
|
|
1483
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
|
+
}
|
|
1484
1517
|
if (!baked) {
|
|
1485
1518
|
script += `
|
|
1486
1519
|
export DEBIAN_FRONTEND=noninteractive
|
|
@@ -2459,7 +2492,8 @@ class HetznerDriver {
|
|
|
2459
2492
|
phpProvision: provision.phpProvision,
|
|
2460
2493
|
servicesProvision: provision.servicesProvision,
|
|
2461
2494
|
rpxProvision,
|
|
2462
|
-
baked
|
|
2495
|
+
baked,
|
|
2496
|
+
swapGb: compute.swapGb
|
|
2463
2497
|
});
|
|
2464
2498
|
const userData = wrapCloudInitUserData(bootstrap);
|
|
2465
2499
|
const serverType = resolveHetznerServerType(compute.size);
|
|
@@ -2530,7 +2564,7 @@ class HetznerDriver {
|
|
|
2530
2564
|
{ direction: "in", protocol: "tcp", port: "7700", source_ips: [network.ip_range] }
|
|
2531
2565
|
]);
|
|
2532
2566
|
const servicesProvision = buildFleetServicesBoxProvision(config);
|
|
2533
|
-
const servicesUserData = wrapCloudInitUserData(buildUbuntuBootstrapScript({ runtime: "php", servicesProvision, baked }));
|
|
2567
|
+
const servicesUserData = wrapCloudInitUserData(buildUbuntuBootstrapScript({ runtime: "php", servicesProvision, baked, swapGb: compute.swapGb }));
|
|
2534
2568
|
const all = await this.client.listServers().catch(() => []);
|
|
2535
2569
|
const newServerIds = [];
|
|
2536
2570
|
let svcServer = all.find((s) => matchesTsCloudLabels(s.labels, slug, environment, "services"));
|
|
@@ -2566,7 +2600,7 @@ class HetznerDriver {
|
|
|
2566
2600
|
optimizeForProduction: compute.php?.optimizeForProduction,
|
|
2567
2601
|
ini: compute.php?.ini
|
|
2568
2602
|
});
|
|
2569
|
-
const appUserData = wrapCloudInitUserData(buildUbuntuBootstrapScript({ runtime: "php", phpProvision: appPhp, servicesProvision: appProvision, baked }));
|
|
2603
|
+
const appUserData = wrapCloudInitUserData(buildUbuntuBootstrapScript({ runtime: "php", phpProvision: appPhp, servicesProvision: appProvision, baked, swapGb: compute.swapGb }));
|
|
2570
2604
|
const existingApp = all.filter((s) => matchesTsCloudLabels(s.labels, slug, environment, "app"));
|
|
2571
2605
|
const appServerIds = existingApp.map((s) => s.id);
|
|
2572
2606
|
if (existingApp.length > topology.appServers) {
|
|
@@ -2700,7 +2734,7 @@ class HetznerDriver {
|
|
|
2700
2734
|
{ direction: "in", protocol: "tcp", port: "7700", source_ips: [network.ip_range] }
|
|
2701
2735
|
]);
|
|
2702
2736
|
const servicesProvision = buildFleetServicesBoxProvision(config);
|
|
2703
|
-
const servicesUserData = wrapCloudInitUserData(buildUbuntuBootstrapScript({ runtime: "bun", servicesProvision, baked }));
|
|
2737
|
+
const servicesUserData = wrapCloudInitUserData(buildUbuntuBootstrapScript({ runtime: "bun", servicesProvision, baked, swapGb: compute.swapGb }));
|
|
2704
2738
|
let svcServer = all.find((s) => matchesTsCloudLabels(s.labels, slug, environment, "services"));
|
|
2705
2739
|
if (!svcServer) {
|
|
2706
2740
|
const { server, action } = await this.client.createServer({
|
|
@@ -2732,7 +2766,8 @@ class HetznerDriver {
|
|
|
2732
2766
|
systemPackages: compute.systemPackages,
|
|
2733
2767
|
database: config.infrastructure?.database,
|
|
2734
2768
|
servicesProvision: appProvisionScripts.servicesProvision,
|
|
2735
|
-
baked
|
|
2769
|
+
baked,
|
|
2770
|
+
swapGb: compute.swapGb
|
|
2736
2771
|
}));
|
|
2737
2772
|
const existingApp = all.filter((s) => matchesTsCloudLabels(s.labels, slug, environment, "app"));
|
|
2738
2773
|
const appServerIds = existingApp.map((s) => s.id);
|
|
@@ -3712,7 +3747,7 @@ function buildRollbackScript(paths, options = {}) {
|
|
|
3712
3747
|
return [
|
|
3713
3748
|
...flip,
|
|
3714
3749
|
`TS_CLOUD_RB_ID=$(basename "$(readlink -f ${paths.current})")`,
|
|
3715
|
-
`if [ -f /etc/systemd/system/${unitBase}@.service ]; then ` + `systemctl start "${unitBase}@\${TS_CLOUD_RB_ID}.service"; sleep 2; ` + `systemctl is-active --quiet "${unitBase}@\${TS_CLOUD_RB_ID}.service" || { echo "rolled-back release failed to start" >&2; exit 1; }; ` + `systemctl enable "${unitBase}@\${TS_CLOUD_RB_ID}.service" 2>/dev/null || true; ` + `systemctl list-units --plain --no-legend --type=service "${unitBase}@*.service" 2>/dev/null | awk '{print $1}' | grep -v "^${unitBase}@\${TS_CLOUD_RB_ID}.service$" | while read -r TS_CLOUD_U; do systemctl stop "$TS_CLOUD_U" 2>/dev/null || true; systemctl disable "$TS_CLOUD_U" 2>/dev/null || true; done; ` + `elif [ -f /etc/systemd/system/${unitBase}.service ]; then systemctl restart ${unitBase}.service; fi`
|
|
3750
|
+
`if [ -f /etc/systemd/system/${unitBase}@.service ]; then ` + `systemctl start "${unitBase}@\${TS_CLOUD_RB_ID}.service"; sleep 2; ` + `systemctl is-active --quiet "${unitBase}@\${TS_CLOUD_RB_ID}.service" || { echo "rolled-back release failed to start" >&2; exit 1; }; ` + `systemctl enable "${unitBase}@\${TS_CLOUD_RB_ID}.service" 2>/dev/null || true; ` + `systemctl list-units --plain --no-legend --type=service "${unitBase}@*.service" 2>/dev/null | awk '{print $1}' | { grep -v "^${unitBase}@\${TS_CLOUD_RB_ID}.service$" || true; } | while read -r TS_CLOUD_U; do systemctl stop "$TS_CLOUD_U" 2>/dev/null || true; systemctl disable "$TS_CLOUD_U" 2>/dev/null || true; done; ` + `elif [ -f /etc/systemd/system/${unitBase}.service ]; then systemctl restart ${unitBase}.service; fi`
|
|
3716
3751
|
];
|
|
3717
3752
|
}
|
|
3718
3753
|
function buildPruneReleases(paths, keep = DEFAULT_KEEP_RELEASES) {
|
|
@@ -3842,7 +3877,7 @@ function buildSiteDeployScript(options) {
|
|
|
3842
3877
|
...buildActivateRelease(paths),
|
|
3843
3878
|
`systemctl enable ${instance} 2>/dev/null || true`,
|
|
3844
3879
|
`for TS_CLOUD_U in \${TS_CLOUD_OLD_UNITS}; do systemctl stop "$TS_CLOUD_U" 2>/dev/null || true; systemctl disable "$TS_CLOUD_U" 2>/dev/null || true; done`,
|
|
3845
|
-
`systemctl list-unit-files --plain --no-legend "${unitBase}@*.service" 2>/dev/null | awk '{print $1}' | grep -v -e "^${instance}$" -e "^${unitBase}@\\.service$" | while read -r TS_CLOUD_U; do systemctl disable "$TS_CLOUD_U" 2>/dev/null || true; done`,
|
|
3880
|
+
`systemctl list-unit-files --plain --no-legend "${unitBase}@*.service" 2>/dev/null | awk '{print $1}' | { grep -v -e "^${instance}$" -e "^${unitBase}@\\.service$" || true; } | while read -r TS_CLOUD_U; do systemctl disable "$TS_CLOUD_U" 2>/dev/null || true; done`,
|
|
3846
3881
|
`if [ -f /etc/systemd/system/${serviceName} ]; then systemctl disable ${serviceName} 2>/dev/null || true; rm -f /etc/systemd/system/${serviceName}; systemctl daemon-reload; fi`,
|
|
3847
3882
|
...buildPruneReleases(paths, keepReleases)
|
|
3848
3883
|
];
|
|
@@ -3987,9 +4022,10 @@ function resolveDashboardAuth(cwd, username, logger) {
|
|
|
3987
4022
|
writeFileSync(file, `${JSON.stringify({ username, password, generatedAt: new Date().toISOString() }, null, 2)}
|
|
3988
4023
|
`);
|
|
3989
4024
|
chmodSync(file, 384);
|
|
3990
|
-
logger.info(`Management dashboard: generated a password and saved it to ${DASHBOARD_CREDENTIALS_FILE} (
|
|
4025
|
+
logger.info(`Management dashboard: generated a password for '${username}' and saved it to ${DASHBOARD_CREDENTIALS_FILE} (read it there — it is not printed). Set TS_CLOUD_UI_PASSWORD to pin your own, or TS_CLOUD_UI_PUBLIC=1 to serve without auth.`);
|
|
3991
4026
|
} catch (error) {
|
|
3992
|
-
logger.warn(`Management dashboard: could not persist the generated password (${error?.message ?? error}). Using it for this deploy only — pass: ${password}
|
|
4027
|
+
logger.warn(`Management dashboard: could not persist the generated password (${error?.message ?? error}). Using it for this deploy only — pass: ${password}
|
|
4028
|
+
This password is now in your deploy log. Set TS_CLOUD_UI_PASSWORD to a value of your own and redeploy once ${DASHBOARD_CREDENTIALS_FILE} is writable.`);
|
|
3993
4029
|
}
|
|
3994
4030
|
return { password, source: "generated" };
|
|
3995
4031
|
}
|
|
@@ -29,7 +29,7 @@ import {
|
|
|
29
29
|
resolveSiteKind,
|
|
30
30
|
resolveUiSource,
|
|
31
31
|
siteInstallBase
|
|
32
|
-
} from "./chunk-
|
|
32
|
+
} from "./chunk-e6xhm3qe.js";
|
|
33
33
|
import {
|
|
34
34
|
artifactKey,
|
|
35
35
|
composeServerlessAppTemplate,
|
|
@@ -12638,7 +12638,7 @@ function renderSiteSnippet(input) {
|
|
|
12638
12638
|
`);
|
|
12639
12639
|
}
|
|
12640
12640
|
function escapeSingle(value) {
|
|
12641
|
-
return value.replace(/\\/g, "\\\\").replaceAll(String.fromCharCode(39), "\\'");
|
|
12641
|
+
return value.replace(/\\/g, "\\\\").replaceAll(String.fromCharCode(39), "\\'").replace(/\r/g, "\\r").replace(/\n/g, "\\n");
|
|
12642
12642
|
}
|
|
12643
12643
|
function normalizeSiteName(name) {
|
|
12644
12644
|
const normalized = name.trim();
|
|
@@ -13405,6 +13405,8 @@ async function startLocalDashboardServer(options = {}) {
|
|
|
13405
13405
|
}
|
|
13406
13406
|
if (body.port !== undefined && body.port !== null && body.port !== "" && (!Number.isInteger(Number(body.port)) || Number(body.port) < 1 || Number(body.port) > 65535))
|
|
13407
13407
|
return json({ ok: false, error: "Port must be a number between 1 and 65535." }, 422);
|
|
13408
|
+
if (typeof body.domain === "string" && body.domain.trim() && !isValidHostname(body.domain.trim()))
|
|
13409
|
+
return json({ ok: false, error: `Domain '${body.domain.trim()}' is not a valid hostname.` }, 422);
|
|
13408
13410
|
let text2 = await readFile(configPath, "utf8");
|
|
13409
13411
|
const set = (key, valueText) => {
|
|
13410
13412
|
text2 = setSitePropertyInCloudConfig({ configText: text2, siteName: name, key, valueText });
|
package/dist/deploy/index.js
CHANGED
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
sanitizeCloudConfig,
|
|
13
13
|
setMaintenance,
|
|
14
14
|
startLocalDashboardServer
|
|
15
|
-
} from "../chunk-
|
|
15
|
+
} from "../chunk-jvp3s3na.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-
|
|
33
|
+
} from "../chunk-e6xhm3qe.js";
|
|
34
34
|
import"../chunk-stt1z5cx.js";
|
|
35
35
|
import"../chunk-93hjhs78.js";
|
|
36
36
|
import {
|
package/dist/drivers/index.js
CHANGED
|
@@ -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-
|
|
58
|
+
} from "./chunk-jvp3s3na.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-
|
|
108
|
+
} from "./chunk-e6xhm3qe.js";
|
|
109
109
|
import {
|
|
110
110
|
ABTestManager,
|
|
111
111
|
AI,
|