@askexenow/exe-os 0.9.82 → 0.9.84
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/deploy/compose/.env +73 -0
- package/deploy/compose/.env.askexe-control-plane.example +18 -0
- package/deploy/compose/.env.customer.example +69 -0
- package/deploy/compose/.env.example +69 -0
- package/deploy/compose/README.md +164 -0
- package/deploy/compose/docker-compose.yml +392 -0
- package/deploy/compose/gateway.json +1 -0
- package/deploy/compose/generate-env.ts +252 -0
- package/deploy/stack-manifests/v0.9.json +137 -1
- package/dist/bin/cli.js +175 -16
- package/dist/bin/stack-update.js +169 -10
- package/package.json +3 -2
- package/stack.release.json +4 -4
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
|
-
"latest": "0.9.
|
|
3
|
+
"latest": "0.9.7",
|
|
4
4
|
"stacks": {
|
|
5
5
|
"0.9.0": {
|
|
6
6
|
"version": "0.9.0",
|
|
@@ -361,6 +361,142 @@
|
|
|
361
361
|
},
|
|
362
362
|
"date": "2026-05-12",
|
|
363
363
|
"sourceRef": "stack-v0.9.5"
|
|
364
|
+
},
|
|
365
|
+
"0.9.6": {
|
|
366
|
+
"version": "0.9.6",
|
|
367
|
+
"releasedAt": "2026-05-12T00:00:00Z",
|
|
368
|
+
"notes": "Customer stack lifecycle manager: exe-os stack-update now detects cold-start/partial/existing host state, creates compose/env templates, hydrates generated secrets/license/domain, checks/installs Docker on Ubuntu/Debian with --yes, and then plans/applies the pinned stack. Other services remain v0.9.3.",
|
|
369
|
+
"breakingChanges": [
|
|
370
|
+
{
|
|
371
|
+
"id": "whatsapp_relink_required",
|
|
372
|
+
"title": "WhatsApp QR re-link required for Baileys v7",
|
|
373
|
+
"description": "exe-gateway uses Baileys v7. Existing WhatsApp 6.x linked-device auth state must be backed up and re-linked once with a new QR code.",
|
|
374
|
+
"requiredAction": "Open https://<gateway-domain>/pair/default?token=<admin-token> and scan from WhatsApp → Linked Devices after the update.",
|
|
375
|
+
"expectedDowntimeMinutes": "2-5",
|
|
376
|
+
"requiresConfirmation": true
|
|
377
|
+
}
|
|
378
|
+
],
|
|
379
|
+
"services": {
|
|
380
|
+
"crm": {
|
|
381
|
+
"env": "CRM_IMAGE_TAG",
|
|
382
|
+
"image": "registry.askexe.com/askexe/exe-crm:v0.9.3",
|
|
383
|
+
"healthUrl": "http://127.0.0.1:3000/healthz",
|
|
384
|
+
"deploymentScope": "customer"
|
|
385
|
+
},
|
|
386
|
+
"wiki": {
|
|
387
|
+
"env": "WIKI_IMAGE_TAG",
|
|
388
|
+
"image": "registry.askexe.com/askexe/exe-wiki:v0.9.3",
|
|
389
|
+
"healthUrl": "http://127.0.0.1:3001/api/ping",
|
|
390
|
+
"deploymentScope": "customer"
|
|
391
|
+
},
|
|
392
|
+
"exed": {
|
|
393
|
+
"env": "EXED_IMAGE_TAG",
|
|
394
|
+
"image": "registry.askexe.com/askexe/exed:v0.9.6",
|
|
395
|
+
"healthUrl": "http://127.0.0.1:8765/health",
|
|
396
|
+
"deploymentScope": "customer"
|
|
397
|
+
},
|
|
398
|
+
"gateway": {
|
|
399
|
+
"env": "GATEWAY_IMAGE_TAG",
|
|
400
|
+
"image": "registry.askexe.com/askexe/exe-gateway:v0.9.3",
|
|
401
|
+
"healthUrl": "http://127.0.0.1:3100/health",
|
|
402
|
+
"deploymentScope": "customer"
|
|
403
|
+
},
|
|
404
|
+
"monitorAgent": {
|
|
405
|
+
"env": "MONITOR_AGENT_IMAGE_TAG",
|
|
406
|
+
"image": "registry.askexe.com/askexe/exe-monitor-agent:v0.9.3",
|
|
407
|
+
"deploymentScope": "customer"
|
|
408
|
+
}
|
|
409
|
+
},
|
|
410
|
+
"releaseDescriptors": {
|
|
411
|
+
"exed": "AskExe/exe-os@stack-v0.9.4:stack.release.json",
|
|
412
|
+
"crm": "AskExe/exe-crm@0.9.3:stack.release.json",
|
|
413
|
+
"wiki": "AskExe/exe-wiki@0.9.3:stack.release.json",
|
|
414
|
+
"gateway": "AskExe/exe-gateway@0.9.3:stack.release.json",
|
|
415
|
+
"db": "AskExe/exe-db@0.9.3:stack.release.json",
|
|
416
|
+
"monitorAgent": "AskExe/exe-monitor@0.9.3:stack.release.json"
|
|
417
|
+
},
|
|
418
|
+
"componentVersions": {
|
|
419
|
+
"exe-os": {
|
|
420
|
+
"repo": "AskExe/exe-os",
|
|
421
|
+
"npmPackage": "@askexenow/exe-os",
|
|
422
|
+
"npmVersion": "0.9.83",
|
|
423
|
+
"image": "registry.askexe.com/askexe/exed:v0.9.4",
|
|
424
|
+
"imageVersion": "0.9.6",
|
|
425
|
+
"note": "Customer stack image tag is 0.9.4; bundled npm package is @askexenow/exe-os@0.9.81. Hygo pulls through registry.askexe.com, which proxies the AskExe-owned GHCR artifact server-side.",
|
|
426
|
+
"sourceRef": "stack-v0.9.4",
|
|
427
|
+
"commit": "main@stack-v0.9.6"
|
|
428
|
+
}
|
|
429
|
+
},
|
|
430
|
+
"date": "2026-05-12",
|
|
431
|
+
"sourceRef": "stack-v0.9.6"
|
|
432
|
+
},
|
|
433
|
+
"0.9.7": {
|
|
434
|
+
"version": "0.9.7",
|
|
435
|
+
"releasedAt": "2026-05-12T00:00:00Z",
|
|
436
|
+
"notes": "Stack update auth unblock: default manifest now uses the packaged signed/customer manifest when available, so cold-start installs do not require update.askexe.com entitlement provisioning. Remote update service remains opt-in via EXE_STACK_MANIFEST.",
|
|
437
|
+
"breakingChanges": [
|
|
438
|
+
{
|
|
439
|
+
"id": "whatsapp_relink_required",
|
|
440
|
+
"title": "WhatsApp QR re-link required for Baileys v7",
|
|
441
|
+
"description": "exe-gateway uses Baileys v7. Existing WhatsApp 6.x linked-device auth state must be backed up and re-linked once with a new QR code.",
|
|
442
|
+
"requiredAction": "Open https://<gateway-domain>/pair/default?token=<admin-token> and scan from WhatsApp → Linked Devices after the update.",
|
|
443
|
+
"expectedDowntimeMinutes": "2-5",
|
|
444
|
+
"requiresConfirmation": true
|
|
445
|
+
}
|
|
446
|
+
],
|
|
447
|
+
"services": {
|
|
448
|
+
"crm": {
|
|
449
|
+
"env": "CRM_IMAGE_TAG",
|
|
450
|
+
"image": "registry.askexe.com/askexe/exe-crm:v0.9.3",
|
|
451
|
+
"healthUrl": "http://127.0.0.1:3000/healthz",
|
|
452
|
+
"deploymentScope": "customer"
|
|
453
|
+
},
|
|
454
|
+
"wiki": {
|
|
455
|
+
"env": "WIKI_IMAGE_TAG",
|
|
456
|
+
"image": "registry.askexe.com/askexe/exe-wiki:v0.9.3",
|
|
457
|
+
"healthUrl": "http://127.0.0.1:3001/api/ping",
|
|
458
|
+
"deploymentScope": "customer"
|
|
459
|
+
},
|
|
460
|
+
"exed": {
|
|
461
|
+
"env": "EXED_IMAGE_TAG",
|
|
462
|
+
"image": "registry.askexe.com/askexe/exed:v0.9.7",
|
|
463
|
+
"healthUrl": "http://127.0.0.1:8765/health",
|
|
464
|
+
"deploymentScope": "customer"
|
|
465
|
+
},
|
|
466
|
+
"gateway": {
|
|
467
|
+
"env": "GATEWAY_IMAGE_TAG",
|
|
468
|
+
"image": "registry.askexe.com/askexe/exe-gateway:v0.9.3",
|
|
469
|
+
"healthUrl": "http://127.0.0.1:3100/health",
|
|
470
|
+
"deploymentScope": "customer"
|
|
471
|
+
},
|
|
472
|
+
"monitorAgent": {
|
|
473
|
+
"env": "MONITOR_AGENT_IMAGE_TAG",
|
|
474
|
+
"image": "registry.askexe.com/askexe/exe-monitor-agent:v0.9.3",
|
|
475
|
+
"deploymentScope": "customer"
|
|
476
|
+
}
|
|
477
|
+
},
|
|
478
|
+
"releaseDescriptors": {
|
|
479
|
+
"exed": "AskExe/exe-os@stack-v0.9.4:stack.release.json",
|
|
480
|
+
"crm": "AskExe/exe-crm@0.9.3:stack.release.json",
|
|
481
|
+
"wiki": "AskExe/exe-wiki@0.9.3:stack.release.json",
|
|
482
|
+
"gateway": "AskExe/exe-gateway@0.9.3:stack.release.json",
|
|
483
|
+
"db": "AskExe/exe-db@0.9.3:stack.release.json",
|
|
484
|
+
"monitorAgent": "AskExe/exe-monitor@0.9.3:stack.release.json"
|
|
485
|
+
},
|
|
486
|
+
"componentVersions": {
|
|
487
|
+
"exe-os": {
|
|
488
|
+
"repo": "AskExe/exe-os",
|
|
489
|
+
"npmPackage": "@askexenow/exe-os",
|
|
490
|
+
"npmVersion": "0.9.84",
|
|
491
|
+
"image": "registry.askexe.com/askexe/exed:v0.9.4",
|
|
492
|
+
"imageVersion": "0.9.7",
|
|
493
|
+
"note": "Customer stack image tag is 0.9.4; bundled npm package is @askexenow/exe-os@0.9.81. Hygo pulls through registry.askexe.com, which proxies the AskExe-owned GHCR artifact server-side.",
|
|
494
|
+
"sourceRef": "stack-v0.9.4",
|
|
495
|
+
"commit": "main@stack-v0.9.7"
|
|
496
|
+
}
|
|
497
|
+
},
|
|
498
|
+
"date": "2026-05-12",
|
|
499
|
+
"sourceRef": "stack-v0.9.7"
|
|
364
500
|
}
|
|
365
501
|
}
|
|
366
502
|
}
|
package/dist/bin/cli.js
CHANGED
|
@@ -19745,12 +19745,13 @@ var init_update = __esm({
|
|
|
19745
19745
|
});
|
|
19746
19746
|
|
|
19747
19747
|
// src/lib/stack-update.ts
|
|
19748
|
-
import { execFileSync as execFileSync4 } from "child_process";
|
|
19749
|
-
import { createVerify, verify as verifySignature } from "crypto";
|
|
19750
|
-
import { existsSync as existsSync34, mkdirSync as mkdirSync24, readdirSync as readdirSync10, readFileSync as readFileSync29, renameSync as renameSync8, writeFileSync as writeFileSync25 } from "fs";
|
|
19748
|
+
import { execFileSync as execFileSync4, spawnSync } from "child_process";
|
|
19749
|
+
import { createVerify, randomBytes as randomBytes2, verify as verifySignature } from "crypto";
|
|
19750
|
+
import { copyFileSync as copyFileSync5, existsSync as existsSync34, mkdirSync as mkdirSync24, readdirSync as readdirSync10, readFileSync as readFileSync29, renameSync as renameSync8, writeFileSync as writeFileSync25 } from "fs";
|
|
19751
19751
|
import http from "http";
|
|
19752
19752
|
import https from "https";
|
|
19753
19753
|
import path42 from "path";
|
|
19754
|
+
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
19754
19755
|
function isSignedEnvelope(value) {
|
|
19755
19756
|
return !!value && typeof value === "object" && "manifest" in value && "signature" in value;
|
|
19756
19757
|
}
|
|
@@ -19985,10 +19986,139 @@ Re-run with --allow-breaking ${missing.map((c) => c.id).join(",")}`
|
|
|
19985
19986
|
);
|
|
19986
19987
|
}
|
|
19987
19988
|
}
|
|
19989
|
+
function commandSucceeds(cmd, args2 = []) {
|
|
19990
|
+
const res = spawnSync(cmd, args2, { stdio: "ignore" });
|
|
19991
|
+
return res.status === 0;
|
|
19992
|
+
}
|
|
19993
|
+
function shellSucceeds(command) {
|
|
19994
|
+
const res = spawnSync("sh", ["-lc", command], { stdio: "ignore" });
|
|
19995
|
+
return res.status === 0;
|
|
19996
|
+
}
|
|
19997
|
+
function resolvePackageRoot2() {
|
|
19998
|
+
const here = path42.dirname(fileURLToPath5(import.meta.url));
|
|
19999
|
+
const candidates = [
|
|
20000
|
+
path42.resolve(here, "..", ".."),
|
|
20001
|
+
path42.resolve(here, ".."),
|
|
20002
|
+
process.cwd()
|
|
20003
|
+
];
|
|
20004
|
+
for (const c of candidates) {
|
|
20005
|
+
if (existsSync34(path42.join(c, "package.json")) && existsSync34(path42.join(c, "deploy", "compose", "docker-compose.yml"))) return c;
|
|
20006
|
+
}
|
|
20007
|
+
return process.cwd();
|
|
20008
|
+
}
|
|
20009
|
+
function copyTemplateIfMissing(srcRel, dest, created) {
|
|
20010
|
+
if (existsSync34(dest)) return;
|
|
20011
|
+
const src = path42.join(resolvePackageRoot2(), srcRel);
|
|
20012
|
+
if (!existsSync34(src)) throw new Error(`Missing packaged stack template: ${srcRel}. Reinstall/update exe-os and retry.`);
|
|
20013
|
+
mkdirSync24(path42.dirname(dest), { recursive: true });
|
|
20014
|
+
copyFileSync5(src, dest);
|
|
20015
|
+
created.push(dest);
|
|
20016
|
+
}
|
|
20017
|
+
function installDockerUbuntu(exec2) {
|
|
20018
|
+
if (process.platform !== "linux") throw new Error("Docker auto-install is only supported on Linux. Install Docker manually, then retry.");
|
|
20019
|
+
if (!existsSync34("/etc/os-release")) throw new Error("Cannot detect Linux distro; install Docker manually, then retry.");
|
|
20020
|
+
const osRelease = readFileSync29("/etc/os-release", "utf8");
|
|
20021
|
+
if (!/ID=(ubuntu|debian)|ID_LIKE=.*debian/.test(osRelease)) {
|
|
20022
|
+
throw new Error("Docker auto-install currently supports Ubuntu/Debian only. Install Docker manually, then retry.");
|
|
20023
|
+
}
|
|
20024
|
+
const script = [
|
|
20025
|
+
"set -e",
|
|
20026
|
+
"sudo apt-get update",
|
|
20027
|
+
"sudo apt-get install -y ca-certificates curl gnupg",
|
|
20028
|
+
"sudo install -m 0755 -d /etc/apt/keyrings",
|
|
20029
|
+
"if [ ! -f /etc/apt/keyrings/docker.gpg ]; then curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg; sudo chmod a+r /etc/apt/keyrings/docker.gpg; fi",
|
|
20030
|
+
". /etc/os-release",
|
|
20031
|
+
'CODENAME="${VERSION_CODENAME:-bookworm}"',
|
|
20032
|
+
'if [ "${ID:-}" = "debian" ]; then DOCKER_DISTRO=debian; else DOCKER_DISTRO=ubuntu; fi',
|
|
20033
|
+
`printf 'deb [arch=%s signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/%s %s stable\\n' "$(dpkg --print-architecture)" "$DOCKER_DISTRO" "$CODENAME" | sudo tee /etc/apt/sources.list.d/docker.list >/dev/null`,
|
|
20034
|
+
"sudo apt-get update",
|
|
20035
|
+
"sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin",
|
|
20036
|
+
"sudo systemctl enable --now docker",
|
|
20037
|
+
"sudo docker version >/dev/null",
|
|
20038
|
+
"sudo docker compose version >/dev/null"
|
|
20039
|
+
].join("\n");
|
|
20040
|
+
exec2("sh", ["-lc", script]);
|
|
20041
|
+
}
|
|
20042
|
+
function randomSecret(bytes = 32) {
|
|
20043
|
+
return randomBytes2(bytes).toString("base64url");
|
|
20044
|
+
}
|
|
20045
|
+
function hydrateEnv(raw, opts) {
|
|
20046
|
+
let next = raw;
|
|
20047
|
+
const license = opts.licenseKey || process.env.EXE_LICENSE_KEY || loadLicense() || "";
|
|
20048
|
+
const domain = opts.domain || process.env.EXE_STACK_DOMAIN || process.env.CUSTOMER_DOMAIN || "";
|
|
20049
|
+
const replacements = {};
|
|
20050
|
+
const env = parseEnv(raw);
|
|
20051
|
+
for (const [key, value] of env.entries()) {
|
|
20052
|
+
if (!/CHANGEME/.test(value)) continue;
|
|
20053
|
+
if (key === "EXE_LICENSE_KEY" && license) replacements[key] = license;
|
|
20054
|
+
else if (key.endsWith("_PASSWORD")) replacements[key] = randomSecret(24);
|
|
20055
|
+
else if (key.endsWith("_SECRET") || key.endsWith("_TOKEN") || key.endsWith("_KEY") || key.endsWith("_SALT")) replacements[key] = value.replace(/CHANGEME[A-Z0-9_]*/g, randomSecret(32));
|
|
20056
|
+
else if (key === "EXED_MCP_TOKEN") replacements[key] = randomSecret(32);
|
|
20057
|
+
}
|
|
20058
|
+
if (domain) next = next.replaceAll("CHANGEME_DOMAIN", domain);
|
|
20059
|
+
next = patchEnv(next, replacements);
|
|
20060
|
+
const remaining = [...parseEnv(next).entries()].filter(([, value]) => /CHANGEME/.test(value)).map(([key, value]) => `${key}=${value}`);
|
|
20061
|
+
return { raw: next, hadPlaceholders: /CHANGEME/.test(raw), remaining: [...new Set(remaining)] };
|
|
20062
|
+
}
|
|
20063
|
+
function bootstrapStackHost(options) {
|
|
20064
|
+
const exec2 = options.exec ?? defaultExec;
|
|
20065
|
+
const createdFiles = [];
|
|
20066
|
+
const actions = [];
|
|
20067
|
+
let dockerInstalled = commandSucceeds("docker", ["version"]);
|
|
20068
|
+
let dockerComposeInstalled = shellSucceeds("docker compose version");
|
|
20069
|
+
if ((!dockerInstalled || !dockerComposeInstalled) && options.installDocker) {
|
|
20070
|
+
actions.push("install_docker");
|
|
20071
|
+
installDockerUbuntu(exec2);
|
|
20072
|
+
dockerInstalled = commandSucceeds("docker", ["version"]);
|
|
20073
|
+
dockerComposeInstalled = shellSucceeds("docker compose version");
|
|
20074
|
+
}
|
|
20075
|
+
copyTemplateIfMissing("deploy/compose/docker-compose.yml", options.composeFile, createdFiles);
|
|
20076
|
+
copyTemplateIfMissing("deploy/compose/.env.customer.example", options.envFile, createdFiles);
|
|
20077
|
+
const brandingDest = path42.join(path42.dirname(options.envFile), "branding.json");
|
|
20078
|
+
copyTemplateIfMissing("deploy/compose/gateway.json", path42.join(path42.dirname(options.envFile), "gateway.json"), createdFiles);
|
|
20079
|
+
if (!existsSync34(brandingDest)) writeFileSync25(brandingDest, JSON.stringify({ brandName: "Hygo", productName: "Hygo OS" }, null, 2) + "\n", { mode: 384 });
|
|
20080
|
+
let envHadPlaceholders = false;
|
|
20081
|
+
let envRemainingPlaceholders = [];
|
|
20082
|
+
if (existsSync34(options.envFile)) {
|
|
20083
|
+
const envRaw = readFileSync29(options.envFile, "utf8");
|
|
20084
|
+
const hydrated = hydrateEnv(envRaw, options);
|
|
20085
|
+
envHadPlaceholders = hydrated.hadPlaceholders;
|
|
20086
|
+
envRemainingPlaceholders = hydrated.remaining;
|
|
20087
|
+
if (hydrated.raw !== envRaw) {
|
|
20088
|
+
writeFileSync25(options.envFile, hydrated.raw, { mode: 384 });
|
|
20089
|
+
actions.push("hydrate_env_secrets");
|
|
20090
|
+
}
|
|
20091
|
+
}
|
|
20092
|
+
return {
|
|
20093
|
+
dockerInstalled,
|
|
20094
|
+
dockerComposeInstalled,
|
|
20095
|
+
composeFileExists: existsSync34(options.composeFile),
|
|
20096
|
+
envFileExists: existsSync34(options.envFile),
|
|
20097
|
+
envHadPlaceholders,
|
|
20098
|
+
envRemainingPlaceholders,
|
|
20099
|
+
licensePresent: !!(options.licenseKey || process.env.EXE_LICENSE_KEY || loadLicense()),
|
|
20100
|
+
createdFiles,
|
|
20101
|
+
actions
|
|
20102
|
+
};
|
|
20103
|
+
}
|
|
20104
|
+
function assertHostReadyForApply(report) {
|
|
20105
|
+
const blockers = [];
|
|
20106
|
+
if (!report.dockerInstalled) blockers.push("Docker is not installed/running. Re-run with --yes to auto-install on Ubuntu/Debian, or install Docker manually.");
|
|
20107
|
+
if (!report.dockerComposeInstalled) blockers.push("Docker Compose plugin is missing. Re-run with --yes to auto-install on Ubuntu/Debian, or install docker-compose-plugin manually.");
|
|
20108
|
+
if (!report.composeFileExists) blockers.push("docker-compose.yml is missing and could not be created.");
|
|
20109
|
+
if (!report.envFileExists) blockers.push(".env is missing and could not be created.");
|
|
20110
|
+
if (!report.licensePresent) blockers.push("Exe OS license key is missing. Run `exe-os setup`, `exe-os --activate <key>`, or pass --license-key.");
|
|
20111
|
+
const hardPlaceholders = report.envRemainingPlaceholders.filter((p) => !/WHATSAPP|API_ROUTER|MONITOR_AGENT/.test(p));
|
|
20112
|
+
if (hardPlaceholders.length > 0) blockers.push(`Required .env placeholders remain: ${hardPlaceholders.join(", ")}`);
|
|
20113
|
+
if (blockers.length > 0) throw new Error(`Stack host is not ready:
|
|
20114
|
+
- ${blockers.join("\n- ")}`);
|
|
20115
|
+
}
|
|
19988
20116
|
async function runStackUpdate(options) {
|
|
19989
20117
|
const exec2 = options.exec ?? defaultExec;
|
|
19990
20118
|
const now2 = options.now ?? (() => /* @__PURE__ */ new Date());
|
|
19991
20119
|
if (options.rollback) return rollbackStackUpdate(options);
|
|
20120
|
+
const report = bootstrapStackHost({ ...options, installDocker: options.installDocker ?? !!options.yes });
|
|
20121
|
+
if (!options.dryRun) assertHostReadyForApply(report);
|
|
19992
20122
|
const manifest = await loadStackManifest(options.manifestRef, options.fetchText, options.manifestPublicKey, options.manifestAuthToken);
|
|
19993
20123
|
const envRaw = readFileSync29(options.envFile, "utf8");
|
|
19994
20124
|
const plan = createStackUpdatePlan(manifest, envRaw, options.targetVersion);
|
|
@@ -20156,12 +20286,16 @@ async function defaultPostJson(url, body, authToken) {
|
|
|
20156
20286
|
function defaultStackPaths() {
|
|
20157
20287
|
const cwdCompose = path42.resolve("docker-compose.yml");
|
|
20158
20288
|
const cwdEnv = path42.resolve(".env");
|
|
20289
|
+
const packagedManifest = path42.join(resolvePackageRoot2(), "deploy", "stack-manifests", "v0.9.json");
|
|
20290
|
+
const manifestRef = process.env.EXE_STACK_MANIFEST || (existsSync34(packagedManifest) ? packagedManifest : "https://update.askexe.com/stack-manifest.json");
|
|
20159
20291
|
return {
|
|
20160
20292
|
composeFile: process.env.EXE_STACK_COMPOSE_FILE || (existsSync34(cwdCompose) ? cwdCompose : "/opt/exe-stack/docker-compose.yml"),
|
|
20161
20293
|
envFile: process.env.EXE_STACK_ENV_FILE || (existsSync34(cwdEnv) ? cwdEnv : "/opt/exe-stack/.env"),
|
|
20162
|
-
manifestRef
|
|
20163
|
-
|
|
20164
|
-
|
|
20294
|
+
manifestRef,
|
|
20295
|
+
// Only call update.askexe.com if explicitly configured or if a remote manifest was requested.
|
|
20296
|
+
// Packaged manifests keep cold-start installs unblocked even before update-service entitlements are provisioned.
|
|
20297
|
+
auditUrl: process.env.EXE_STACK_AUDIT_URL || (/^https?:\/\//.test(manifestRef) ? "https://update.askexe.com/v1/deploy-audits" : void 0),
|
|
20298
|
+
imageCredentialsUrl: process.env.EXE_STACK_IMAGE_CREDENTIALS_URL || (/^https?:\/\//.test(manifestRef) ? "https://update.askexe.com/v1/image-credentials" : void 0),
|
|
20165
20299
|
manifestAuthToken: process.env.EXE_STACK_UPDATE_TOKEN || process.env.EXE_LICENSE_KEY || loadLicense() || void 0,
|
|
20166
20300
|
manifestPublicKey: loadDefaultPublicKey()
|
|
20167
20301
|
};
|
|
@@ -20205,7 +20339,8 @@ function parseArgs4(args2) {
|
|
|
20205
20339
|
rollback: false,
|
|
20206
20340
|
deploymentPersona: process.env.EXE_STACK_DEPLOYMENT_PERSONA === "askexe-control-plane" ? "askexe-control-plane" : "customer",
|
|
20207
20341
|
yes: false,
|
|
20208
|
-
allowedBreakingChangeIds: []
|
|
20342
|
+
allowedBreakingChangeIds: [],
|
|
20343
|
+
noBootstrap: false
|
|
20209
20344
|
};
|
|
20210
20345
|
for (let i = 0; i < args2.length; i++) {
|
|
20211
20346
|
const arg = args2[i];
|
|
@@ -20216,8 +20351,8 @@ function parseArgs4(args2) {
|
|
|
20216
20351
|
else if (arg.startsWith("--target=")) opts.targetVersion = arg.split("=")[1];
|
|
20217
20352
|
else if (arg === "--compose-file") opts.composeFile = next();
|
|
20218
20353
|
else if (arg.startsWith("--compose-file=")) opts.composeFile = arg.split("=").slice(1).join("=");
|
|
20219
|
-
else if (arg === "--env-file") opts.envFile = next();
|
|
20220
|
-
else if (arg.startsWith("--env-file=")) opts.envFile = arg.split("=").slice(1).join("=");
|
|
20354
|
+
else if (arg === "--env-file" || arg === "--stack-env-file") opts.envFile = next();
|
|
20355
|
+
else if (arg.startsWith("--env-file=") || arg.startsWith("--stack-env-file=")) opts.envFile = arg.split("=").slice(1).join("=");
|
|
20221
20356
|
else if (arg === "--lock-file") opts.lockFile = next();
|
|
20222
20357
|
else if (arg === "--public-key") opts.manifestPublicKey = readFileSync30(next(), "utf8");
|
|
20223
20358
|
else if (arg.startsWith("--public-key=")) opts.manifestPublicKey = readFileSync30(arg.split("=").slice(1).join("="), "utf8");
|
|
@@ -20246,6 +20381,9 @@ function parseArgs4(args2) {
|
|
|
20246
20381
|
else if (arg.startsWith("--break-glass=")) opts.breakGlassReason = arg.split("=").slice(1).join("=");
|
|
20247
20382
|
else if (arg === "--break-glass-audit-file") opts.breakGlassAuditFile = next();
|
|
20248
20383
|
else if (arg.startsWith("--break-glass-audit-file=")) opts.breakGlassAuditFile = arg.split("=").slice(1).join("=");
|
|
20384
|
+
else if (arg === "--domain") opts.domain = next();
|
|
20385
|
+
else if (arg.startsWith("--domain=")) opts.domain = arg.split("=").slice(1).join("=");
|
|
20386
|
+
else if (arg === "--no-bootstrap") opts.noBootstrap = true;
|
|
20249
20387
|
else if (arg === "--dry-run") opts.dryRun = true;
|
|
20250
20388
|
else if (arg === "--check") opts.check = true;
|
|
20251
20389
|
else if (arg === "--yes" || arg === "-y") opts.yes = true;
|
|
@@ -20270,10 +20408,13 @@ Options:
|
|
|
20270
20408
|
--manifest <ref> Stack manifest JSON path or URL (default: update.askexe.com)
|
|
20271
20409
|
--target <version> Stack version to install (default: manifest.latest)
|
|
20272
20410
|
--compose-file <path> docker-compose.yml path (default: ./docker-compose.yml or /opt/exe-stack/docker-compose.yml)
|
|
20273
|
-
--env-file <path>
|
|
20411
|
+
--stack-env-file <path> .env path (default: ./.env or /opt/exe-stack/.env)
|
|
20412
|
+
--env-file <path> Alias; prefer --stack-env-file because Node 22 reserves --env-file
|
|
20274
20413
|
--lock-file <path> Lock file path (default: beside .env)
|
|
20275
20414
|
--check Print available changes only
|
|
20276
20415
|
--dry-run Plan only; do not run Docker
|
|
20416
|
+
--domain <domain> Fill CHANGEME_DOMAIN in new stack .env
|
|
20417
|
+
--no-bootstrap Do not create templates/hydrate .env/check host prereqs
|
|
20277
20418
|
--public-key <path> PEM public key for signed manifest verification
|
|
20278
20419
|
--auth-token <token> Bearer token for private update manifest/audit API
|
|
20279
20420
|
--auth-token-env <name> Read bearer token from an environment variable
|
|
@@ -20311,6 +20452,18 @@ function printBreaking(changes) {
|
|
|
20311
20452
|
if (c.expectedDowntimeMinutes) console.log(` Expected downtime: ${c.expectedDowntimeMinutes} minutes`);
|
|
20312
20453
|
}
|
|
20313
20454
|
}
|
|
20455
|
+
function printHostReport(report) {
|
|
20456
|
+
console.log("Host preflight:");
|
|
20457
|
+
console.log(` Docker: ${report.dockerInstalled ? "\u2705" : "\u274C"}`);
|
|
20458
|
+
console.log(` Docker Compose: ${report.dockerComposeInstalled ? "\u2705" : "\u274C"}`);
|
|
20459
|
+
console.log(` Compose file: ${report.composeFileExists ? "\u2705" : "\u274C"}`);
|
|
20460
|
+
console.log(` Env file: ${report.envFileExists ? "\u2705" : "\u274C"}`);
|
|
20461
|
+
console.log(` License: ${report.licensePresent ? "\u2705" : "\u274C"}`);
|
|
20462
|
+
if (report.createdFiles.length > 0) console.log(` Created: ${report.createdFiles.join(", ")}`);
|
|
20463
|
+
if (report.actions.length > 0) console.log(` Actions: ${report.actions.join(", ")}`);
|
|
20464
|
+
if (report.envRemainingPlaceholders.length > 0) console.log(` Remaining placeholders: ${report.envRemainingPlaceholders.join(", ")}`);
|
|
20465
|
+
console.log("");
|
|
20466
|
+
}
|
|
20314
20467
|
async function main7(args2 = process.argv.slice(2)) {
|
|
20315
20468
|
const opts = parseArgs4(args2);
|
|
20316
20469
|
if (opts.rollback) {
|
|
@@ -20322,6 +20475,12 @@ async function main7(args2 = process.argv.slice(2)) {
|
|
|
20322
20475
|
console.log(`\u2705 Stack rollback attempted using backup: ${result2.backupEnvFile ?? "latest backup"}`);
|
|
20323
20476
|
return;
|
|
20324
20477
|
}
|
|
20478
|
+
let hostReport;
|
|
20479
|
+
if (!opts.noBootstrap) {
|
|
20480
|
+
hostReport = bootstrapStackHost({ ...opts, installDocker: opts.yes, domain: opts.domain });
|
|
20481
|
+
printHostReport(hostReport);
|
|
20482
|
+
if (!opts.check && !opts.dryRun) assertHostReadyForApply(hostReport);
|
|
20483
|
+
}
|
|
20325
20484
|
const manifest = await loadStackManifest(opts.manifestRef, void 0, opts.manifestPublicKey, opts.manifestAuthToken);
|
|
20326
20485
|
const envRaw = readFileSync30(opts.envFile, "utf8");
|
|
20327
20486
|
const plan = createStackUpdatePlan(manifest, envRaw, opts.targetVersion);
|
|
@@ -34902,7 +35061,7 @@ __export(code_context_index_exports, {
|
|
|
34902
35061
|
import crypto14 from "crypto";
|
|
34903
35062
|
import path51 from "path";
|
|
34904
35063
|
import { existsSync as existsSync36, mkdirSync as mkdirSync25, readFileSync as readFileSync32, readdirSync as readdirSync11, statSync as statSync7, writeFileSync as writeFileSync26 } from "fs";
|
|
34905
|
-
import { spawnSync } from "child_process";
|
|
35064
|
+
import { spawnSync as spawnSync2 } from "child_process";
|
|
34906
35065
|
function normalizeProjectRoot(projectRoot) {
|
|
34907
35066
|
return path51.resolve(projectRoot || process.cwd());
|
|
34908
35067
|
}
|
|
@@ -34920,7 +35079,7 @@ function getCodeContextIndexPath(projectRoot) {
|
|
|
34920
35079
|
return path51.join(indexDir(), `${rootHash}.json`);
|
|
34921
35080
|
}
|
|
34922
35081
|
function currentBranch(projectRoot) {
|
|
34923
|
-
const result =
|
|
35082
|
+
const result = spawnSync2("git", ["branch", "--show-current"], { cwd: projectRoot, encoding: "utf8", timeout: 2e3 });
|
|
34924
35083
|
const branch = result.status === 0 ? result.stdout.trim() : "";
|
|
34925
35084
|
return branch || "detached-or-unknown";
|
|
34926
35085
|
}
|
|
@@ -34939,12 +35098,12 @@ function listRecursive(projectRoot, dir = projectRoot, out = []) {
|
|
|
34939
35098
|
return out;
|
|
34940
35099
|
}
|
|
34941
35100
|
function listCodeFiles(projectRoot, maxFiles) {
|
|
34942
|
-
const git =
|
|
35101
|
+
const git = spawnSync2("git", ["ls-files"], { cwd: projectRoot, encoding: "utf8", timeout: 5e3, maxBuffer: 1024 * 1024 * 16 });
|
|
34943
35102
|
let files = [];
|
|
34944
35103
|
if (git.status === 0 && git.stdout.trim()) {
|
|
34945
35104
|
files = git.stdout.split("\n").map((s) => s.trim()).filter(Boolean);
|
|
34946
35105
|
} else {
|
|
34947
|
-
const rg =
|
|
35106
|
+
const rg = spawnSync2("rg", ["--files"], { cwd: projectRoot, encoding: "utf8", timeout: 5e3, maxBuffer: 1024 * 1024 * 16 });
|
|
34948
35107
|
files = rg.status === 0 && rg.stdout.trim() ? rg.stdout.split("\n").map((s) => s.trim()).filter(Boolean) : listRecursive(projectRoot);
|
|
34949
35108
|
}
|
|
34950
35109
|
return files.map((file) => file.replaceAll(path51.sep, "/")).filter((file) => isChunkable(file) && !shouldIgnore(file)).slice(0, maxFiles).sort();
|
|
@@ -36146,9 +36305,9 @@ if (args.includes("--global")) {
|
|
|
36146
36305
|
);
|
|
36147
36306
|
await runClaudeInstall();
|
|
36148
36307
|
} else if (args.includes("--commands-only")) {
|
|
36149
|
-
const { copySlashCommands: copySlashCommands2, resolvePackageRoot:
|
|
36308
|
+
const { copySlashCommands: copySlashCommands2, resolvePackageRoot: resolvePackageRoot3 } = await Promise.resolve().then(() => (init_installer(), installer_exports));
|
|
36150
36309
|
try {
|
|
36151
|
-
const packageRoot =
|
|
36310
|
+
const packageRoot = resolvePackageRoot3();
|
|
36152
36311
|
const result = await copySlashCommands2(packageRoot);
|
|
36153
36312
|
if (result.copied > 0) {
|
|
36154
36313
|
process.stderr.write(
|