@stacksjs/ts-cloud 0.7.7 → 0.7.10
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 +98 -98
- package/dist/{chunk-x07dz3sd.js → chunk-6va72rhh.js} +2 -2
- package/dist/{chunk-xzn0ntr0.js → chunk-7pym4v5b.js} +64 -14
- package/dist/deploy/index.js +2 -2
- package/dist/drivers/index.js +1 -1
- package/dist/drivers/shared/deploy-script.d.ts +38 -4
- package/dist/drivers/shared/releases.d.ts +10 -2
- package/dist/index.js +2 -2
- package/dist/ui/index.html +3 -3
- package/dist/ui/server/actions.html +3 -3
- 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 +3 -3
- 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 +3 -3
- package/package.json +3 -3
|
@@ -26,7 +26,7 @@ import {
|
|
|
26
26
|
resolveSiteFramework,
|
|
27
27
|
resolveSiteKind,
|
|
28
28
|
resolveUiSource
|
|
29
|
-
} from "./chunk-
|
|
29
|
+
} from "./chunk-7pym4v5b.js";
|
|
30
30
|
import {
|
|
31
31
|
artifactKey,
|
|
32
32
|
composeServerlessAppTemplate,
|
|
@@ -11016,7 +11016,7 @@ async function rollbackComputeSite(ctx, options) {
|
|
|
11016
11016
|
const paths = releasePaths(appBase, options.to || "unused");
|
|
11017
11017
|
const commands = [
|
|
11018
11018
|
"set -uo pipefail",
|
|
11019
|
-
...buildRollbackScript(paths, options.to ? { to: options.to } : {}),
|
|
11019
|
+
...buildRollbackScript(paths, { ...options.to ? { to: options.to } : {}, unitBase: `${ctx.slug}-${options.siteName}` }),
|
|
11020
11020
|
`(cd ${PANTRY_PROJECT_DIR} && pantry restart php-fpm) 2>/dev/null || true`,
|
|
11021
11021
|
`(cd ${paths.current} && eval "$(cd ${PANTRY_PROJECT_DIR} && pantry env 2>/dev/null)" && php artisan queue:restart) 2>/dev/null || true`
|
|
11022
11022
|
];
|
|
@@ -3496,15 +3496,11 @@ function buildActivateRelease(paths) {
|
|
|
3496
3496
|
];
|
|
3497
3497
|
}
|
|
3498
3498
|
function buildRollbackScript(paths, options = {}) {
|
|
3499
|
-
|
|
3500
|
-
|
|
3501
|
-
|
|
3502
|
-
|
|
3503
|
-
|
|
3504
|
-
`mv -Tf ${paths.current}.tmp ${paths.current}`
|
|
3505
|
-
];
|
|
3506
|
-
}
|
|
3507
|
-
return [
|
|
3499
|
+
const flip = options.to ? [
|
|
3500
|
+
`[ -d ${paths.releases}/${options.to} ] || { echo "rollback target ${paths.releases}/${options.to} not found" >&2; exit 1; }`,
|
|
3501
|
+
`ln -sfn ${paths.releases}/${options.to} ${paths.current}.tmp`,
|
|
3502
|
+
`mv -Tf ${paths.current}.tmp ${paths.current}`
|
|
3503
|
+
] : [
|
|
3508
3504
|
`TS_CLOUD_CURRENT=$(readlink -f ${paths.current} 2>/dev/null || true)`,
|
|
3509
3505
|
`TS_CLOUD_PREV=$(ls -1dt ${paths.releases}/*/ 2>/dev/null | sed 's#/$##' | while read -r r; do ` + '[ "$(readlink -f "$r")" != "$TS_CLOUD_CURRENT" ] && { echo "$r"; break; }; done)',
|
|
3510
3506
|
'[ -n "$TS_CLOUD_PREV" ] || { echo "no previous release to roll back to" >&2; exit 1; }',
|
|
@@ -3512,6 +3508,14 @@ function buildRollbackScript(paths, options = {}) {
|
|
|
3512
3508
|
`mv -Tf ${paths.current}.tmp ${paths.current}`,
|
|
3513
3509
|
'echo "rolled back to $TS_CLOUD_PREV"'
|
|
3514
3510
|
];
|
|
3511
|
+
if (!options.unitBase)
|
|
3512
|
+
return flip;
|
|
3513
|
+
const unitBase = options.unitBase;
|
|
3514
|
+
return [
|
|
3515
|
+
...flip,
|
|
3516
|
+
`TS_CLOUD_RB_ID=$(basename "$(readlink -f ${paths.current})")`,
|
|
3517
|
+
`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`
|
|
3518
|
+
];
|
|
3515
3519
|
}
|
|
3516
3520
|
function buildPruneReleases(paths, keep = DEFAULT_KEEP_RELEASES) {
|
|
3517
3521
|
const n = Math.max(1, keep);
|
|
@@ -3559,15 +3563,19 @@ function buildSiteDeployScript(options) {
|
|
|
3559
3563
|
envEntries,
|
|
3560
3564
|
port,
|
|
3561
3565
|
keepReleases = DEFAULT_KEEP_RELEASES,
|
|
3562
|
-
preStartCommands = []
|
|
3566
|
+
preStartCommands = [],
|
|
3567
|
+
healthCheckPath,
|
|
3568
|
+
healthGateSeconds = 5
|
|
3563
3569
|
} = options;
|
|
3570
|
+
const zeroDowntime = options.zeroDowntime ?? port != null;
|
|
3564
3571
|
const base = options.appDir ?? `/var/www/${siteName}`;
|
|
3565
3572
|
const paths = releasePaths(base, releaseId);
|
|
3566
|
-
const
|
|
3573
|
+
const unitBase = `${slug}-${siteName}`;
|
|
3574
|
+
const serviceName = `${unitBase}.service`;
|
|
3567
3575
|
const sharedPaths = [".env"];
|
|
3568
3576
|
const envFile = formatEnvFile(envEntries);
|
|
3569
3577
|
const preStart = preStartCommands.length > 0 ? [`cd ${paths.release}`, ...preStartCommands] : [];
|
|
3570
|
-
|
|
3578
|
+
const stageRelease = [
|
|
3571
3579
|
"set -euo pipefail",
|
|
3572
3580
|
...artifactFetch,
|
|
3573
3581
|
...buildEnsureReleaseLayout(paths, sharedPaths),
|
|
@@ -3579,7 +3587,47 @@ function buildSiteDeployScript(options) {
|
|
|
3579
3587
|
"TS_CLOUD_ENV_EOF",
|
|
3580
3588
|
`chmod 600 ${paths.shared}/.env`,
|
|
3581
3589
|
...buildLinkSharedPaths(paths, sharedPaths),
|
|
3582
|
-
...preStart
|
|
3590
|
+
...preStart
|
|
3591
|
+
];
|
|
3592
|
+
if (zeroDowntime && port != null) {
|
|
3593
|
+
const instance = `${unitBase}@${releaseId}.service`;
|
|
3594
|
+
const gatePath = healthCheckPath ? healthCheckPath.startsWith("/") ? healthCheckPath : `/${healthCheckPath}` : null;
|
|
3595
|
+
const failGate = `{ echo "[ts-cloud] release ${releaseId} failed its health gate — previous release keeps serving" >&2; journalctl -u ${instance} -n 50 --no-pager >&2 || true; systemctl stop ${instance} 2>/dev/null || true; exit 1; }`;
|
|
3596
|
+
return [
|
|
3597
|
+
...stageRelease,
|
|
3598
|
+
`cat > /etc/systemd/system/${unitBase}@.service <<'TS_CLOUD_UNIT_EOF'`,
|
|
3599
|
+
"[Unit]",
|
|
3600
|
+
`Description=${siteName} release %i (managed by ts-cloud)`,
|
|
3601
|
+
"After=network.target",
|
|
3602
|
+
"",
|
|
3603
|
+
"[Service]",
|
|
3604
|
+
"Type=simple",
|
|
3605
|
+
`WorkingDirectory=${paths.releases}/%i`,
|
|
3606
|
+
`ExecStart=${execStart}`,
|
|
3607
|
+
"Restart=always",
|
|
3608
|
+
"RestartSec=5",
|
|
3609
|
+
`EnvironmentFile=${paths.releases}/%i/.env`,
|
|
3610
|
+
`Environment=PORT=${port}`,
|
|
3611
|
+
"",
|
|
3612
|
+
"[Install]",
|
|
3613
|
+
"WantedBy=multi-user.target",
|
|
3614
|
+
"TS_CLOUD_UNIT_EOF",
|
|
3615
|
+
"systemctl daemon-reload",
|
|
3616
|
+
`TS_CLOUD_OLD_UNITS=$(systemctl list-units --plain --no-legend --type=service "${unitBase}@*.service" 2>/dev/null | awk '{print $1}' | grep -v "^${instance}$" || true)`,
|
|
3617
|
+
`if [ -f /etc/systemd/system/${serviceName} ] && systemctl is-active --quiet ${serviceName}; then echo "[ts-cloud] retiring pre-zero-downtime unit ${serviceName} (one-time restart cutover)"; systemctl stop ${serviceName}; fi`,
|
|
3618
|
+
`systemctl start ${instance}`,
|
|
3619
|
+
`for TS_CLOUD_I in $(seq 1 ${Math.max(1, healthGateSeconds)}); do sleep 1; systemctl is-active --quiet ${instance} || ${failGate}; done`,
|
|
3620
|
+
...gatePath ? [`curl -sf -o /dev/null --max-time 10 "http://127.0.0.1:${port}${gatePath}" || ${failGate}`] : [],
|
|
3621
|
+
...buildActivateRelease(paths),
|
|
3622
|
+
`systemctl enable ${instance} 2>/dev/null || true`,
|
|
3623
|
+
`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`,
|
|
3624
|
+
`systemctl list-unit-files --plain --no-legend "${unitBase}@*.service" 2>/dev/null | awk '{print $1}' | grep -v "^${instance}$" | while read -r TS_CLOUD_U; do systemctl disable "$TS_CLOUD_U" 2>/dev/null || true; done`,
|
|
3625
|
+
`if [ -f /etc/systemd/system/${serviceName} ]; then systemctl disable ${serviceName} 2>/dev/null || true; rm -f /etc/systemd/system/${serviceName}; systemctl daemon-reload; fi`,
|
|
3626
|
+
...buildPruneReleases(paths, keepReleases)
|
|
3627
|
+
];
|
|
3628
|
+
}
|
|
3629
|
+
return [
|
|
3630
|
+
...stageRelease,
|
|
3583
3631
|
`cat > /etc/systemd/system/${serviceName} <<'TS_CLOUD_UNIT_EOF'`,
|
|
3584
3632
|
"[Unit]",
|
|
3585
3633
|
`Description=${siteName} (managed by ts-cloud)`,
|
|
@@ -4367,7 +4415,9 @@ async function deploySiteRelease(driver, options, logger = noopLogger2) {
|
|
|
4367
4415
|
execStart: resolveExecStart(site.start, runtime),
|
|
4368
4416
|
envEntries: site.env || {},
|
|
4369
4417
|
port: site.port,
|
|
4370
|
-
preStartCommands: site.preStart
|
|
4418
|
+
preStartCommands: site.preStart,
|
|
4419
|
+
zeroDowntime: site.zeroDowntime !== false,
|
|
4420
|
+
healthCheckPath: site.healthCheck?.path
|
|
4371
4421
|
});
|
|
4372
4422
|
const compute = config.infrastructure?.compute;
|
|
4373
4423
|
const wantsNginxStatic = kind === "server-static" && compute?.webServer !== "rpx" && !!site.domain;
|
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-6va72rhh.js";
|
|
16
16
|
import {
|
|
17
17
|
buildAndPushServerlessImage
|
|
18
18
|
} from "../chunk-0wxyppza.js";
|
|
@@ -29,7 +29,7 @@ import {
|
|
|
29
29
|
resolveSiteKind,
|
|
30
30
|
resolveUiSource,
|
|
31
31
|
validateDeploymentConfig
|
|
32
|
-
} from "../chunk-
|
|
32
|
+
} from "../chunk-7pym4v5b.js";
|
|
33
33
|
import"../chunk-c6rgvg1j.js";
|
|
34
34
|
import"../chunk-93hjhs78.js";
|
|
35
35
|
import {
|
package/dist/drivers/index.js
CHANGED
|
@@ -25,13 +25,47 @@ export interface BuildSiteDeployScriptOptions {
|
|
|
25
25
|
* `node_modules`.
|
|
26
26
|
*/
|
|
27
27
|
preStartCommands?: string[];
|
|
28
|
+
/**
|
|
29
|
+
* True zero-downtime cutover for ported sites: the new release runs as its
|
|
30
|
+
* own systemd instance (`<slug>-<site>@<releaseId>`) that binds the same
|
|
31
|
+
* port via SO_REUSEPORT while the old instance still serves, must pass a
|
|
32
|
+
* health gate, and only then is the old instance stopped. A release that
|
|
33
|
+
* crashes on boot fails the deploy with the old release still serving.
|
|
34
|
+
*
|
|
35
|
+
* Requires the app to bind with `reusePort` (Stacks' server does in
|
|
36
|
+
* production). Defaults to true when `port` is set; portless sites
|
|
37
|
+
* (queue workers, schedulers) always use the stop/start flow because two
|
|
38
|
+
* overlapping instances would double-process their work.
|
|
39
|
+
*/
|
|
40
|
+
zeroDowntime?: boolean;
|
|
41
|
+
/**
|
|
42
|
+
* HTTP path polled on `127.0.0.1:<port>` as part of the health gate (e.g.
|
|
43
|
+
* `/health`). Optional — without it the gate is "the instance stays
|
|
44
|
+
* active for {@link BuildSiteDeployScriptOptions.healthGateSeconds}".
|
|
45
|
+
*/
|
|
46
|
+
healthCheckPath?: string;
|
|
47
|
+
/**
|
|
48
|
+
* Seconds the new instance must stay active (and, with
|
|
49
|
+
* {@link BuildSiteDeployScriptOptions.healthCheckPath}, respond 2xx/3xx)
|
|
50
|
+
* before the old instance is stopped.
|
|
51
|
+
* @default 5
|
|
52
|
+
*/
|
|
53
|
+
healthGateSeconds?: number;
|
|
28
54
|
}
|
|
29
55
|
/**
|
|
30
56
|
* Build the remote shell commands that install/refresh a server-app site on a
|
|
31
|
-
* compute target with
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
57
|
+
* compute target with an atomic release (Envoyer-style): unpack into
|
|
58
|
+
* `releases/<id>`, link the shared `.env`, build, then cut over.
|
|
59
|
+
*
|
|
60
|
+
* The cutover has two modes:
|
|
61
|
+
* - **zero-downtime** (default for ported sites): the new release starts as a
|
|
62
|
+
* templated systemd instance that shares the port via SO_REUSEPORT with the
|
|
63
|
+
* still-running old instance, must pass a health gate, and only then does
|
|
64
|
+
* the old instance stop — no dropped connections, and a crash-on-boot
|
|
65
|
+
* release fails the deploy with the old one still serving.
|
|
66
|
+
* - **restart** (portless sites, or `zeroDowntime: false`): the classic flip
|
|
67
|
+
* `current` + `systemctl restart` — correct for workers/schedulers where two
|
|
68
|
+
* overlapping instances would double-process work.
|
|
35
69
|
*/
|
|
36
70
|
export declare function buildSiteDeployScript(options: BuildSiteDeployScriptOptions): string[];
|
|
37
71
|
export interface BuildStaticSiteDeployScriptOptions {
|
|
@@ -58,11 +58,19 @@ export declare function buildActivateRelease(paths: ReleasePaths): string[];
|
|
|
58
58
|
* `to` set, points `current` at `releases/<to>`; otherwise picks the most recent
|
|
59
59
|
* release that isn't the one `current` resolves to. Atomic (temp symlink + `mv
|
|
60
60
|
* -T`), and a no-op-safe guard fails loudly if the target is missing rather than
|
|
61
|
-
* leaving `current` dangling.
|
|
62
|
-
*
|
|
61
|
+
* leaving `current` dangling.
|
|
62
|
+
*
|
|
63
|
+
* With `unitBase` set (e.g. `myapp-api`), the script also swaps the running
|
|
64
|
+
* systemd release instance for sites deployed zero-downtime style (templated
|
|
65
|
+
* `<unitBase>@<releaseId>` units pinned to their release dirs): it starts the
|
|
66
|
+
* instance for the rolled-back release — overlapping on the SO_REUSEPORT port —
|
|
67
|
+
* then stops the newer one, so even the rollback itself is zero-downtime. Sites
|
|
68
|
+
* on the legacy single unit just get a restart. The caller appends any engine
|
|
69
|
+
* reload (php-fpm/queues) — see {@link import('./laravel-deploy')}.
|
|
63
70
|
*/
|
|
64
71
|
export declare function buildRollbackScript(paths: ReleasePaths, options?: {
|
|
65
72
|
to?: string;
|
|
73
|
+
unitBase?: string;
|
|
66
74
|
}): string[];
|
|
67
75
|
/**
|
|
68
76
|
* Remove all but the newest `keep` releases (by mtime). `current` always points
|
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-6va72rhh.js";
|
|
59
59
|
import {
|
|
60
60
|
buildAndPushServerlessImage
|
|
61
61
|
} from "./chunk-0wxyppza.js";
|
|
@@ -105,7 +105,7 @@ import {
|
|
|
105
105
|
waitForCloudInit,
|
|
106
106
|
waitForSsh,
|
|
107
107
|
wrapCloudInitUserData
|
|
108
|
-
} from "./chunk-
|
|
108
|
+
} from "./chunk-7pym4v5b.js";
|
|
109
109
|
import {
|
|
110
110
|
ABTestManager,
|
|
111
111
|
AI,
|