@stacksjs/ts-cloud 0.5.32 → 0.5.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 +326 -326
- package/dist/drivers/shared/app-frameworks.d.ts +21 -8
- package/dist/index.js +29 -14
- 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/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/workers.html +3 -3
- package/dist/ui/serverless/logs.html +3 -3
- package/package.json +3 -3
|
@@ -25,23 +25,36 @@ export interface FrameworkExecContext {
|
|
|
25
25
|
export interface AppFrameworkDriver {
|
|
26
26
|
readonly id: FrameworkId;
|
|
27
27
|
/**
|
|
28
|
-
* Wrap a
|
|
29
|
-
*
|
|
28
|
+
* Wrap a command for a systemd unit / cron entry. Prefer a BARE command +
|
|
29
|
+
* {@link AppFrameworkDriver.execEnv} (like the app's own service) so starting
|
|
30
|
+
* a long-lived unit over SSH doesn't hold the deploy channel open; only wrap
|
|
31
|
+
* in a shell when the env must be computed at runtime (e.g. Laravel/pantry).
|
|
30
32
|
*/
|
|
31
33
|
wrapExec: (command: string) => string;
|
|
34
|
+
/** Static `Environment=` vars for a unit (systemd inherits no shell env). */
|
|
35
|
+
readonly execEnv?: Record<string, string>;
|
|
32
36
|
/**
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
37
|
+
* How the app scheduler runs:
|
|
38
|
+
* - `'cron'` → `schedule:run` is one-shot; run it every minute via cron (Laravel).
|
|
39
|
+
* - `'daemon'` → `schedule:run` is long-lived (holds in-process timers); run it
|
|
40
|
+
* as a single always-on systemd unit (Stacks).
|
|
41
|
+
*/
|
|
42
|
+
readonly schedulerMode: 'cron' | 'daemon';
|
|
43
|
+
/**
|
|
44
|
+
* The scheduler command WITHOUT output redirection. For `'cron'` it is run
|
|
45
|
+
* from cron and sets up its own cwd/env; for `'daemon'` it becomes a unit's
|
|
46
|
+
* ExecStart (systemd supplies WorkingDirectory), so no `cd` is needed.
|
|
36
47
|
*/
|
|
37
48
|
schedulerCommand: (ctx: FrameworkExecContext) => string;
|
|
38
49
|
/** `ExecStart` for a single queue-worker process. */
|
|
39
50
|
queueWorkerCommand: (worker: QueueWorkerConfig, ctx: FrameworkExecContext) => string;
|
|
40
51
|
}
|
|
41
52
|
/**
|
|
42
|
-
* Stacks (Bun) — the default.
|
|
43
|
-
*
|
|
44
|
-
*
|
|
53
|
+
* Stacks (Bun) — the default. bun lives at an absolute path installed by the box
|
|
54
|
+
* bootstrap, so units use a BARE ExecStart (no shell wrapper) + `Environment=`
|
|
55
|
+
* for PATH/BUN_INSTALL — matching the app's own service. This matters: wrapping
|
|
56
|
+
* a long-lived unit in `/bin/sh -lc` makes `systemctl restart` over SSH hold the
|
|
57
|
+
* deploy channel open and hang the deploy.
|
|
45
58
|
*/
|
|
46
59
|
export declare const stacksDriver: AppFrameworkDriver;
|
|
47
60
|
/** Laravel (PHP / Artisan) — the original Forge-style behavior, now a driver. */
|
package/dist/index.js
CHANGED
|
@@ -87741,13 +87741,12 @@ function resolveSiteFramework(site) {
|
|
|
87741
87741
|
var PANTRY_ENV_EVAL = `eval "$(cd ${PANTRY_PROJECT_DIR} && pantry env 2>/dev/null)"`;
|
|
87742
87742
|
var BUN_BIN = "/usr/local/bin/bun";
|
|
87743
87743
|
var STACKS_CLI = "storage/framework/core/buddy/src/cli.ts";
|
|
87744
|
-
function bunEnvWrap(command) {
|
|
87745
|
-
return `/bin/sh -lc 'export PATH="/usr/local/bin:$PATH"; export BUN_INSTALL="/root/.bun"; exec ${command}'`;
|
|
87746
|
-
}
|
|
87747
87744
|
var stacksDriver = {
|
|
87748
87745
|
id: "stacks",
|
|
87749
|
-
wrapExec:
|
|
87750
|
-
|
|
87746
|
+
wrapExec: (command) => command,
|
|
87747
|
+
execEnv: { PATH: "/usr/local/bin:/usr/bin:/bin", BUN_INSTALL: "/root/.bun" },
|
|
87748
|
+
schedulerMode: "daemon",
|
|
87749
|
+
schedulerCommand: () => `${BUN_BIN} ${STACKS_CLI} schedule:run`,
|
|
87751
87750
|
queueWorkerCommand: (worker, { current }) => {
|
|
87752
87751
|
const flags = [
|
|
87753
87752
|
`--queue=${worker.queue || "default"}`,
|
|
@@ -87761,7 +87760,8 @@ var stacksDriver = {
|
|
|
87761
87760
|
var laravelDriver = {
|
|
87762
87761
|
id: "laravel",
|
|
87763
87762
|
wrapExec: (command) => `/bin/sh -lc '${PANTRY_ENV_EVAL}; exec ${command}'`,
|
|
87764
|
-
|
|
87763
|
+
schedulerMode: "cron",
|
|
87764
|
+
schedulerCommand: ({ current }) => `cd ${current} && ${PANTRY_ENV_EVAL} && php artisan schedule:run`,
|
|
87765
87765
|
queueWorkerCommand: (worker, { current }) => {
|
|
87766
87766
|
const artisan = `${current}/artisan`;
|
|
87767
87767
|
if (worker.horizon)
|
|
@@ -87814,6 +87814,7 @@ function systemdUnit(opts) {
|
|
|
87814
87814
|
"[Service]",
|
|
87815
87815
|
"Type=simple",
|
|
87816
87816
|
`WorkingDirectory=${opts.workingDir}`,
|
|
87817
|
+
...Object.entries(opts.environment ?? {}).map(([k, v]) => `Environment="${k}=${v}"`),
|
|
87817
87818
|
`ExecStart=${opts.execStart}`,
|
|
87818
87819
|
`Restart=${opts.restart || "always"}`,
|
|
87819
87820
|
"RestartSec=5"
|
|
@@ -87855,6 +87856,7 @@ function buildSiteServicesScript(options) {
|
|
|
87855
87856
|
description: `${siteName} queue worker ${qIndex}.${p} (managed by ts-cloud)`,
|
|
87856
87857
|
workingDir: current,
|
|
87857
87858
|
execStart: driver.wrapExec(driver.queueWorkerCommand(worker, ctx)),
|
|
87859
|
+
environment: driver.execEnv,
|
|
87858
87860
|
stopWaitSecs: worker.stopWaitSecs ?? 90
|
|
87859
87861
|
})));
|
|
87860
87862
|
}
|
|
@@ -87869,22 +87871,33 @@ function buildSiteServicesScript(options) {
|
|
|
87869
87871
|
description: `${siteName} daemon ${daemon.name || daemon.command} (managed by ts-cloud)`,
|
|
87870
87872
|
workingDir: daemon.directory || current,
|
|
87871
87873
|
execStart: driver.wrapExec(daemon.command),
|
|
87874
|
+
environment: driver.execEnv,
|
|
87872
87875
|
restart: daemon.restart,
|
|
87873
87876
|
user: daemon.user
|
|
87874
87877
|
})));
|
|
87875
87878
|
}
|
|
87876
87879
|
});
|
|
87880
|
+
const scheduler2 = site.scheduler;
|
|
87881
|
+
const schedulerEnabled = scheduler2 === true || typeof scheduler2 === "object" && scheduler2 !== null;
|
|
87882
|
+
const schedulerUnit = `${slug}-${siteName}-scheduler`;
|
|
87883
|
+
if (schedulerEnabled && driver.schedulerMode === "daemon") {
|
|
87884
|
+
desiredUnits.push(schedulerUnit);
|
|
87885
|
+
out.push(...writeUnitScript(schedulerUnit, systemdUnit({
|
|
87886
|
+
description: `${siteName} scheduler (managed by ts-cloud)`,
|
|
87887
|
+
workingDir: current,
|
|
87888
|
+
execStart: driver.wrapExec(driver.schedulerCommand(ctx)),
|
|
87889
|
+
environment: driver.execEnv
|
|
87890
|
+
})));
|
|
87891
|
+
}
|
|
87877
87892
|
const desiredList = desiredUnits.map((n) => `${n}.service`).join(" ");
|
|
87878
|
-
out.push("systemctl daemon-reload", `TS_CLOUD_DESIRED="${desiredList}"`, `for unit in $(ls /etc/systemd/system/ 2>/dev/null | grep -E '^${reEscape(slug)}-${reEscape(siteName)}-(queue|daemon)
|
|
87893
|
+
out.push("systemctl daemon-reload", `TS_CLOUD_DESIRED="${desiredList}"`, `for unit in $(ls /etc/systemd/system/ 2>/dev/null | grep -E '^${reEscape(slug)}-${reEscape(siteName)}-((queue|daemon)-.*|scheduler)\\.service$' || true); do`, ' case " $TS_CLOUD_DESIRED " in', ' *" $unit "*) ;;', ' *) systemctl stop "$unit" 2>/dev/null || true; systemctl disable "$unit" 2>/dev/null || true; rm -f "/etc/systemd/system/$unit" ;;', " esac", "done", "systemctl daemon-reload");
|
|
87879
87894
|
for (const name of desiredUnits) {
|
|
87880
87895
|
out.push(`systemctl enable ${name}.service`, `systemctl restart ${name}.service`);
|
|
87881
87896
|
}
|
|
87882
87897
|
const cronPath = schedulerCronPath(slug, siteName);
|
|
87883
|
-
|
|
87884
|
-
const schedulerEnabled = scheduler2 === true || typeof scheduler2 === "object" && scheduler2 !== null;
|
|
87885
|
-
if (schedulerEnabled) {
|
|
87898
|
+
if (schedulerEnabled && driver.schedulerMode === "cron") {
|
|
87886
87899
|
const heartbeat = typeof scheduler2 === "object" && scheduler2 !== null ? scheduler2 : undefined;
|
|
87887
|
-
let command = driver.schedulerCommand(ctx)
|
|
87900
|
+
let command = `${driver.schedulerCommand(ctx)} >> /dev/null 2>&1`;
|
|
87888
87901
|
if (heartbeat?.heartbeatUrl) {
|
|
87889
87902
|
const method = heartbeat.heartbeatMethod || "GET";
|
|
87890
87903
|
const methodFlag = method === "GET" ? "" : `-X ${method} `;
|
|
@@ -88031,12 +88044,12 @@ async function deploySiteRelease(driver, options, logger4 = noopLogger2) {
|
|
|
88031
88044
|
security: site.security
|
|
88032
88045
|
}) : [];
|
|
88033
88046
|
const sslScript = useNginx ? buildSslScript(site) : [];
|
|
88034
|
-
const
|
|
88047
|
+
const servicesScript2 = siteHasServices(site) ? buildSiteServicesScript({ slug, siteName, site, phpVersion, appBase }) : [];
|
|
88035
88048
|
const healthCheckScript = useNginx ? buildHealthCheckScript(site) : [];
|
|
88036
88049
|
logger4.step(`Deploying PHP site '${siteName}' to ${targets.length} target(s)...`);
|
|
88037
88050
|
const phpResult = await driver.runRemoteDeploy({
|
|
88038
88051
|
targets,
|
|
88039
|
-
commands: [...deployScript, ...poolScript, ...vhostScript, ...sslScript, ...
|
|
88052
|
+
commands: [...deployScript, ...poolScript, ...vhostScript, ...sslScript, ...servicesScript2, ...healthCheckScript],
|
|
88040
88053
|
comment: `ts-cloud deploy ${slug}/${siteName}@${sha}`,
|
|
88041
88054
|
tags: { Project: slug, Environment: environment, Role: "app" }
|
|
88042
88055
|
});
|
|
@@ -88102,6 +88115,7 @@ async function deploySiteRelease(driver, options, logger4 = noopLogger2) {
|
|
|
88102
88115
|
security: site.security
|
|
88103
88116
|
}) : [];
|
|
88104
88117
|
const staticSsl = wantsNginxStatic ? buildSslScript(site) : [];
|
|
88118
|
+
const servicesScript = siteHasServices(site) ? buildSiteServicesScript({ slug, siteName, site, appBase: `/var/www/${siteName}` }) : [];
|
|
88105
88119
|
const remoteScript = [
|
|
88106
88120
|
...buildDeployHistoryHeader(`/var/www/${siteName}`, {
|
|
88107
88121
|
releaseId: sha,
|
|
@@ -88110,7 +88124,8 @@ async function deploySiteRelease(driver, options, logger4 = noopLogger2) {
|
|
|
88110
88124
|
}),
|
|
88111
88125
|
...baseScript,
|
|
88112
88126
|
...staticVhost,
|
|
88113
|
-
...staticSsl
|
|
88127
|
+
...staticSsl,
|
|
88128
|
+
...servicesScript
|
|
88114
88129
|
];
|
|
88115
88130
|
logger4.step(`Deploying to ${targets.length} target(s)...`);
|
|
88116
88131
|
const result = await driver.runRemoteDeploy({
|