@askexenow/exe-os 0.9.62 → 0.9.64
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 +126 -7
- package/dist/bin/stack-update.js +126 -7
- package/package.json +1 -1
package/dist/bin/cli.js
CHANGED
|
@@ -17181,10 +17181,14 @@ function parseStackManifest(raw, publicKey) {
|
|
|
17181
17181
|
}
|
|
17182
17182
|
return parsed;
|
|
17183
17183
|
}
|
|
17184
|
-
async function loadStackManifest(ref, fetchText = defaultFetchText, publicKey) {
|
|
17185
|
-
if (/^https?:\/\//.test(ref)) return parseStackManifest(await
|
|
17184
|
+
async function loadStackManifest(ref, fetchText = defaultFetchText, publicKey, authToken) {
|
|
17185
|
+
if (/^https?:\/\//.test(ref)) return parseStackManifest(await fetchTextWithAuth(ref, fetchText, authToken), publicKey);
|
|
17186
17186
|
return parseStackManifest(readFileSync25(ref, "utf8"), publicKey);
|
|
17187
17187
|
}
|
|
17188
|
+
async function fetchTextWithAuth(ref, fetchText, authToken) {
|
|
17189
|
+
if (!authToken || fetchText !== defaultFetchText) return fetchText(ref);
|
|
17190
|
+
return defaultFetchText(ref, authToken);
|
|
17191
|
+
}
|
|
17188
17192
|
function parseEnv(raw) {
|
|
17189
17193
|
const env = /* @__PURE__ */ new Map();
|
|
17190
17194
|
for (const line of raw.split(/\r?\n/)) {
|
|
@@ -17250,14 +17254,16 @@ async function runStackUpdate(options) {
|
|
|
17250
17254
|
const exec2 = options.exec ?? defaultExec;
|
|
17251
17255
|
const now = options.now ?? (() => /* @__PURE__ */ new Date());
|
|
17252
17256
|
if (options.rollback) return rollbackStackUpdate(options);
|
|
17253
|
-
const manifest = await loadStackManifest(options.manifestRef, options.fetchText, options.manifestPublicKey);
|
|
17257
|
+
const manifest = await loadStackManifest(options.manifestRef, options.fetchText, options.manifestPublicKey, options.manifestAuthToken);
|
|
17254
17258
|
const envRaw = readFileSync25(options.envFile, "utf8");
|
|
17255
17259
|
const plan = createStackUpdatePlan(manifest, envRaw, options.targetVersion);
|
|
17256
17260
|
assertBreakingChangesAllowed(plan, options.allowedBreakingChangeIds ?? []);
|
|
17257
17261
|
const lockFile = options.lockFile ?? path37.join(path37.dirname(options.envFile), ".exe-stack-lock.json");
|
|
17262
|
+
const previousVersion = readCurrentStackVersion(lockFile);
|
|
17258
17263
|
if (options.dryRun || plan.changes.length === 0) {
|
|
17259
17264
|
return { status: "planned", targetVersion: plan.targetVersion, changes: plan.changes, lockFile };
|
|
17260
17265
|
}
|
|
17266
|
+
await postDeployAudit(options, "started", plan.targetVersion, previousVersion);
|
|
17261
17267
|
const backupDir = path37.join(path37.dirname(options.envFile), ".exe-stack-backups");
|
|
17262
17268
|
mkdirSync19(backupDir, { recursive: true });
|
|
17263
17269
|
const stamp = now().toISOString().replace(/[:.]/g, "-");
|
|
@@ -17269,11 +17275,18 @@ async function runStackUpdate(options) {
|
|
|
17269
17275
|
writeFileSync20(tmp, patched, { mode: 384 });
|
|
17270
17276
|
renameSync7(tmp, options.envFile);
|
|
17271
17277
|
const composeArgs = ["compose", "--file", options.composeFile, "--env-file", options.envFile];
|
|
17278
|
+
let registryForLogout;
|
|
17272
17279
|
try {
|
|
17280
|
+
const creds = await fetchImageCredentials(options);
|
|
17281
|
+
if (creds) {
|
|
17282
|
+
(options.dockerLogin ?? defaultDockerLogin)(creds);
|
|
17283
|
+
registryForLogout = creds.registry;
|
|
17284
|
+
}
|
|
17273
17285
|
exec2("docker", [...composeArgs, "pull"]);
|
|
17274
17286
|
exec2("docker", [...composeArgs, "up", "-d"]);
|
|
17275
17287
|
await verifyReleaseHealth(plan.release, options.healthRetries ?? 12, options.healthDelayMs ?? 5e3);
|
|
17276
17288
|
writeFileSync20(lockFile, JSON.stringify({ stackVersion: plan.targetVersion, updatedAt: now().toISOString(), backupEnvFile, services: plan.release.services }, null, 2) + "\n");
|
|
17289
|
+
await postDeployAudit(options, "success", plan.targetVersion, previousVersion, void 0, { changes: plan.changes.length });
|
|
17277
17290
|
return { status: "updated", targetVersion: plan.targetVersion, changes: plan.changes, backupEnvFile, lockFile };
|
|
17278
17291
|
} catch (err) {
|
|
17279
17292
|
writeFileSync20(options.envFile, envRaw, { mode: 384 });
|
|
@@ -17282,7 +17295,55 @@ async function runStackUpdate(options) {
|
|
|
17282
17295
|
} catch {
|
|
17283
17296
|
}
|
|
17284
17297
|
const reason = err instanceof Error ? err.message : String(err);
|
|
17298
|
+
await postDeployAudit(options, "failed", plan.targetVersion, previousVersion, reason, { rollbackAttempted: true });
|
|
17285
17299
|
throw new Error(`Stack update failed and rollback was attempted: ${reason}`);
|
|
17300
|
+
} finally {
|
|
17301
|
+
if (registryForLogout) {
|
|
17302
|
+
try {
|
|
17303
|
+
(options.dockerLogout ?? defaultDockerLogout)(registryForLogout);
|
|
17304
|
+
} catch {
|
|
17305
|
+
}
|
|
17306
|
+
}
|
|
17307
|
+
}
|
|
17308
|
+
}
|
|
17309
|
+
async function fetchImageCredentials(options) {
|
|
17310
|
+
if (!options.imageCredentialsUrl) return null;
|
|
17311
|
+
const res = await fetch(options.imageCredentialsUrl, {
|
|
17312
|
+
method: "POST",
|
|
17313
|
+
headers: {
|
|
17314
|
+
"content-type": "application/json",
|
|
17315
|
+
...options.manifestAuthToken ? { authorization: `Bearer ${options.manifestAuthToken}` } : {}
|
|
17316
|
+
},
|
|
17317
|
+
body: JSON.stringify({ deviceId: options.deviceId, licenseKey: options.licenseKey })
|
|
17318
|
+
});
|
|
17319
|
+
if (!res.ok) throw new Error(`Failed to fetch image credentials: HTTP ${res.status}`);
|
|
17320
|
+
return await res.json();
|
|
17321
|
+
}
|
|
17322
|
+
function readCurrentStackVersion(lockFile) {
|
|
17323
|
+
if (!existsSync30(lockFile)) return void 0;
|
|
17324
|
+
try {
|
|
17325
|
+
const parsed = JSON.parse(readFileSync25(lockFile, "utf8"));
|
|
17326
|
+
return parsed.stackVersion;
|
|
17327
|
+
} catch {
|
|
17328
|
+
return void 0;
|
|
17329
|
+
}
|
|
17330
|
+
}
|
|
17331
|
+
async function postDeployAudit(options, status, stackVersion, previousVersion, error, metadata) {
|
|
17332
|
+
if (!options.auditUrl) return;
|
|
17333
|
+
const postJson = options.postJson ?? defaultPostJson;
|
|
17334
|
+
try {
|
|
17335
|
+
await postJson(options.auditUrl, {
|
|
17336
|
+
stackVersion,
|
|
17337
|
+
previousVersion,
|
|
17338
|
+
status,
|
|
17339
|
+
error,
|
|
17340
|
+
metadata,
|
|
17341
|
+
deviceId: options.deviceId,
|
|
17342
|
+
licenseKey: options.licenseKey
|
|
17343
|
+
}, options.manifestAuthToken);
|
|
17344
|
+
} catch (err) {
|
|
17345
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
17346
|
+
console.warn(`[stack-update] deploy audit failed: ${reason}`);
|
|
17286
17347
|
}
|
|
17287
17348
|
}
|
|
17288
17349
|
async function verifyReleaseHealth(release, retries, delayMs) {
|
|
@@ -17321,20 +17382,53 @@ function httpStatus(urlString) {
|
|
|
17321
17382
|
function defaultExec(cmd, args2, opts) {
|
|
17322
17383
|
execFileSync3(cmd, args2, { stdio: "inherit", cwd: opts?.cwd });
|
|
17323
17384
|
}
|
|
17324
|
-
|
|
17325
|
-
|
|
17385
|
+
function defaultDockerLogin(creds) {
|
|
17386
|
+
execFileSync3("docker", ["login", creds.registry, "-u", creds.username, "--password-stdin"], {
|
|
17387
|
+
input: creds.password,
|
|
17388
|
+
stdio: ["pipe", "inherit", "inherit"]
|
|
17389
|
+
});
|
|
17390
|
+
}
|
|
17391
|
+
function defaultDockerLogout(registry) {
|
|
17392
|
+
execFileSync3("docker", ["logout", registry], { stdio: "ignore" });
|
|
17393
|
+
}
|
|
17394
|
+
async function defaultFetchText(ref, authToken) {
|
|
17395
|
+
const res = await fetch(ref, {
|
|
17396
|
+
headers: authToken ? { authorization: `Bearer ${authToken}` } : void 0
|
|
17397
|
+
});
|
|
17326
17398
|
if (!res.ok) throw new Error(`Failed to fetch ${ref}: HTTP ${res.status}`);
|
|
17327
17399
|
return res.text();
|
|
17328
17400
|
}
|
|
17401
|
+
async function defaultPostJson(url, body, authToken) {
|
|
17402
|
+
const res = await fetch(url, {
|
|
17403
|
+
method: "POST",
|
|
17404
|
+
headers: {
|
|
17405
|
+
"content-type": "application/json",
|
|
17406
|
+
...authToken ? { authorization: `Bearer ${authToken}` } : {}
|
|
17407
|
+
},
|
|
17408
|
+
body: JSON.stringify(body)
|
|
17409
|
+
});
|
|
17410
|
+
if (!res.ok) throw new Error(`Failed to POST ${url}: HTTP ${res.status}`);
|
|
17411
|
+
}
|
|
17329
17412
|
function defaultStackPaths() {
|
|
17330
17413
|
const cwdCompose = path37.resolve("docker-compose.yml");
|
|
17331
17414
|
const cwdEnv = path37.resolve(".env");
|
|
17332
17415
|
return {
|
|
17333
17416
|
composeFile: process.env.EXE_STACK_COMPOSE_FILE || (existsSync30(cwdCompose) ? cwdCompose : "/opt/exe-stack/docker-compose.yml"),
|
|
17334
17417
|
envFile: process.env.EXE_STACK_ENV_FILE || (existsSync30(cwdEnv) ? cwdEnv : "/opt/exe-stack/.env"),
|
|
17335
|
-
manifestRef: process.env.EXE_STACK_MANIFEST || "https://update.askexe.com/stack-manifest.json"
|
|
17418
|
+
manifestRef: process.env.EXE_STACK_MANIFEST || "https://update.askexe.com/stack-manifest.json",
|
|
17419
|
+
auditUrl: process.env.EXE_STACK_AUDIT_URL || "https://update.askexe.com/v1/deploy-audits",
|
|
17420
|
+
imageCredentialsUrl: process.env.EXE_STACK_IMAGE_CREDENTIALS_URL || "https://update.askexe.com/v1/image-credentials",
|
|
17421
|
+
manifestAuthToken: process.env.EXE_STACK_UPDATE_TOKEN,
|
|
17422
|
+
manifestPublicKey: loadDefaultPublicKey()
|
|
17336
17423
|
};
|
|
17337
17424
|
}
|
|
17425
|
+
function loadDefaultPublicKey() {
|
|
17426
|
+
if (process.env.EXE_STACK_PUBLIC_KEY) return process.env.EXE_STACK_PUBLIC_KEY;
|
|
17427
|
+
if (process.env.EXE_STACK_PUBLIC_KEY_FILE && existsSync30(process.env.EXE_STACK_PUBLIC_KEY_FILE)) {
|
|
17428
|
+
return readFileSync25(process.env.EXE_STACK_PUBLIC_KEY_FILE, "utf8");
|
|
17429
|
+
}
|
|
17430
|
+
return void 0;
|
|
17431
|
+
}
|
|
17338
17432
|
var init_stack_update = __esm({
|
|
17339
17433
|
"src/lib/stack-update.ts"() {
|
|
17340
17434
|
"use strict";
|
|
@@ -17353,6 +17447,12 @@ function parseArgs4(args2) {
|
|
|
17353
17447
|
manifestRef: defaults.manifestRef,
|
|
17354
17448
|
composeFile: defaults.composeFile,
|
|
17355
17449
|
envFile: defaults.envFile,
|
|
17450
|
+
auditUrl: defaults.auditUrl,
|
|
17451
|
+
imageCredentialsUrl: defaults.imageCredentialsUrl,
|
|
17452
|
+
manifestAuthToken: defaults.manifestAuthToken,
|
|
17453
|
+
manifestPublicKey: defaults.manifestPublicKey,
|
|
17454
|
+
deviceId: process.env.EXE_DEVICE_ID,
|
|
17455
|
+
licenseKey: process.env.EXE_LICENSE_KEY,
|
|
17356
17456
|
dryRun: false,
|
|
17357
17457
|
check: false,
|
|
17358
17458
|
rollback: false,
|
|
@@ -17373,6 +17473,18 @@ function parseArgs4(args2) {
|
|
|
17373
17473
|
else if (arg === "--lock-file") opts.lockFile = next();
|
|
17374
17474
|
else if (arg === "--public-key") opts.manifestPublicKey = readFileSync26(next(), "utf8");
|
|
17375
17475
|
else if (arg.startsWith("--public-key=")) opts.manifestPublicKey = readFileSync26(arg.split("=").slice(1).join("="), "utf8");
|
|
17476
|
+
else if (arg === "--auth-token") opts.manifestAuthToken = next();
|
|
17477
|
+
else if (arg.startsWith("--auth-token=")) opts.manifestAuthToken = arg.split("=").slice(1).join("=");
|
|
17478
|
+
else if (arg === "--auth-token-env") opts.manifestAuthToken = process.env[next()] ?? "";
|
|
17479
|
+
else if (arg === "--audit-url") opts.auditUrl = next();
|
|
17480
|
+
else if (arg.startsWith("--audit-url=")) opts.auditUrl = arg.split("=").slice(1).join("=");
|
|
17481
|
+
else if (arg === "--image-credentials-url") opts.imageCredentialsUrl = next();
|
|
17482
|
+
else if (arg.startsWith("--image-credentials-url=")) opts.imageCredentialsUrl = arg.split("=").slice(1).join("=");
|
|
17483
|
+
else if (arg === "--no-image-credentials") opts.imageCredentialsUrl = void 0;
|
|
17484
|
+
else if (arg === "--device-id") opts.deviceId = next();
|
|
17485
|
+
else if (arg.startsWith("--device-id=")) opts.deviceId = arg.split("=").slice(1).join("=");
|
|
17486
|
+
else if (arg === "--license-key") opts.licenseKey = next();
|
|
17487
|
+
else if (arg.startsWith("--license-key=")) opts.licenseKey = arg.split("=").slice(1).join("=");
|
|
17376
17488
|
else if (arg === "--rollback") opts.rollback = true;
|
|
17377
17489
|
else if (arg === "--dry-run") opts.dryRun = true;
|
|
17378
17490
|
else if (arg === "--check") opts.check = true;
|
|
@@ -17403,6 +17515,13 @@ Options:
|
|
|
17403
17515
|
--check Print available changes only
|
|
17404
17516
|
--dry-run Plan only; do not run Docker
|
|
17405
17517
|
--public-key <path> PEM public key for signed manifest verification
|
|
17518
|
+
--auth-token <token> Bearer token for private update manifest/audit API
|
|
17519
|
+
--auth-token-env <name> Read bearer token from an environment variable
|
|
17520
|
+
--audit-url <url> Deploy-audit endpoint (default: EXE_STACK_AUDIT_URL/update.askexe.com)
|
|
17521
|
+
--image-credentials-url <url> Registry credential broker endpoint
|
|
17522
|
+
--no-image-credentials Skip registry credential broker / Docker login
|
|
17523
|
+
--device-id <id> Device ID to include in deploy audit
|
|
17524
|
+
--license-key <key> License key to include in deploy audit
|
|
17406
17525
|
--rollback Restore latest backed-up .env and restart stack
|
|
17407
17526
|
--allow-breaking <ids> Confirm breaking changes, comma-separated
|
|
17408
17527
|
-y, --yes Non-interactive confirmation
|
|
@@ -17440,7 +17559,7 @@ async function main3() {
|
|
|
17440
17559
|
console.log(`\u2705 Stack rollback attempted using backup: ${result2.backupEnvFile ?? "latest backup"}`);
|
|
17441
17560
|
return;
|
|
17442
17561
|
}
|
|
17443
|
-
const manifest = await loadStackManifest(opts.manifestRef, void 0, opts.manifestPublicKey);
|
|
17562
|
+
const manifest = await loadStackManifest(opts.manifestRef, void 0, opts.manifestPublicKey, opts.manifestAuthToken);
|
|
17444
17563
|
const envRaw = readFileSync26(opts.envFile, "utf8");
|
|
17445
17564
|
const plan = createStackUpdatePlan(manifest, envRaw, opts.targetVersion);
|
|
17446
17565
|
console.log(`Exe OS stack target: ${plan.targetVersion}`);
|
package/dist/bin/stack-update.js
CHANGED
|
@@ -95,10 +95,14 @@ function parseStackManifest(raw, publicKey) {
|
|
|
95
95
|
}
|
|
96
96
|
return parsed;
|
|
97
97
|
}
|
|
98
|
-
async function loadStackManifest(ref, fetchText = defaultFetchText, publicKey) {
|
|
99
|
-
if (/^https?:\/\//.test(ref)) return parseStackManifest(await
|
|
98
|
+
async function loadStackManifest(ref, fetchText = defaultFetchText, publicKey, authToken) {
|
|
99
|
+
if (/^https?:\/\//.test(ref)) return parseStackManifest(await fetchTextWithAuth(ref, fetchText, authToken), publicKey);
|
|
100
100
|
return parseStackManifest(readFileSync(ref, "utf8"), publicKey);
|
|
101
101
|
}
|
|
102
|
+
async function fetchTextWithAuth(ref, fetchText, authToken) {
|
|
103
|
+
if (!authToken || fetchText !== defaultFetchText) return fetchText(ref);
|
|
104
|
+
return defaultFetchText(ref, authToken);
|
|
105
|
+
}
|
|
102
106
|
function parseEnv(raw) {
|
|
103
107
|
const env = /* @__PURE__ */ new Map();
|
|
104
108
|
for (const line of raw.split(/\r?\n/)) {
|
|
@@ -164,14 +168,16 @@ async function runStackUpdate(options) {
|
|
|
164
168
|
const exec = options.exec ?? defaultExec;
|
|
165
169
|
const now = options.now ?? (() => /* @__PURE__ */ new Date());
|
|
166
170
|
if (options.rollback) return rollbackStackUpdate(options);
|
|
167
|
-
const manifest = await loadStackManifest(options.manifestRef, options.fetchText, options.manifestPublicKey);
|
|
171
|
+
const manifest = await loadStackManifest(options.manifestRef, options.fetchText, options.manifestPublicKey, options.manifestAuthToken);
|
|
168
172
|
const envRaw = readFileSync(options.envFile, "utf8");
|
|
169
173
|
const plan = createStackUpdatePlan(manifest, envRaw, options.targetVersion);
|
|
170
174
|
assertBreakingChangesAllowed(plan, options.allowedBreakingChangeIds ?? []);
|
|
171
175
|
const lockFile = options.lockFile ?? path.join(path.dirname(options.envFile), ".exe-stack-lock.json");
|
|
176
|
+
const previousVersion = readCurrentStackVersion(lockFile);
|
|
172
177
|
if (options.dryRun || plan.changes.length === 0) {
|
|
173
178
|
return { status: "planned", targetVersion: plan.targetVersion, changes: plan.changes, lockFile };
|
|
174
179
|
}
|
|
180
|
+
await postDeployAudit(options, "started", plan.targetVersion, previousVersion);
|
|
175
181
|
const backupDir = path.join(path.dirname(options.envFile), ".exe-stack-backups");
|
|
176
182
|
mkdirSync(backupDir, { recursive: true });
|
|
177
183
|
const stamp = now().toISOString().replace(/[:.]/g, "-");
|
|
@@ -183,11 +189,18 @@ async function runStackUpdate(options) {
|
|
|
183
189
|
writeFileSync(tmp, patched, { mode: 384 });
|
|
184
190
|
renameSync(tmp, options.envFile);
|
|
185
191
|
const composeArgs = ["compose", "--file", options.composeFile, "--env-file", options.envFile];
|
|
192
|
+
let registryForLogout;
|
|
186
193
|
try {
|
|
194
|
+
const creds = await fetchImageCredentials(options);
|
|
195
|
+
if (creds) {
|
|
196
|
+
(options.dockerLogin ?? defaultDockerLogin)(creds);
|
|
197
|
+
registryForLogout = creds.registry;
|
|
198
|
+
}
|
|
187
199
|
exec("docker", [...composeArgs, "pull"]);
|
|
188
200
|
exec("docker", [...composeArgs, "up", "-d"]);
|
|
189
201
|
await verifyReleaseHealth(plan.release, options.healthRetries ?? 12, options.healthDelayMs ?? 5e3);
|
|
190
202
|
writeFileSync(lockFile, JSON.stringify({ stackVersion: plan.targetVersion, updatedAt: now().toISOString(), backupEnvFile, services: plan.release.services }, null, 2) + "\n");
|
|
203
|
+
await postDeployAudit(options, "success", plan.targetVersion, previousVersion, void 0, { changes: plan.changes.length });
|
|
191
204
|
return { status: "updated", targetVersion: plan.targetVersion, changes: plan.changes, backupEnvFile, lockFile };
|
|
192
205
|
} catch (err) {
|
|
193
206
|
writeFileSync(options.envFile, envRaw, { mode: 384 });
|
|
@@ -196,7 +209,55 @@ async function runStackUpdate(options) {
|
|
|
196
209
|
} catch {
|
|
197
210
|
}
|
|
198
211
|
const reason = err instanceof Error ? err.message : String(err);
|
|
212
|
+
await postDeployAudit(options, "failed", plan.targetVersion, previousVersion, reason, { rollbackAttempted: true });
|
|
199
213
|
throw new Error(`Stack update failed and rollback was attempted: ${reason}`);
|
|
214
|
+
} finally {
|
|
215
|
+
if (registryForLogout) {
|
|
216
|
+
try {
|
|
217
|
+
(options.dockerLogout ?? defaultDockerLogout)(registryForLogout);
|
|
218
|
+
} catch {
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
async function fetchImageCredentials(options) {
|
|
224
|
+
if (!options.imageCredentialsUrl) return null;
|
|
225
|
+
const res = await fetch(options.imageCredentialsUrl, {
|
|
226
|
+
method: "POST",
|
|
227
|
+
headers: {
|
|
228
|
+
"content-type": "application/json",
|
|
229
|
+
...options.manifestAuthToken ? { authorization: `Bearer ${options.manifestAuthToken}` } : {}
|
|
230
|
+
},
|
|
231
|
+
body: JSON.stringify({ deviceId: options.deviceId, licenseKey: options.licenseKey })
|
|
232
|
+
});
|
|
233
|
+
if (!res.ok) throw new Error(`Failed to fetch image credentials: HTTP ${res.status}`);
|
|
234
|
+
return await res.json();
|
|
235
|
+
}
|
|
236
|
+
function readCurrentStackVersion(lockFile) {
|
|
237
|
+
if (!existsSync(lockFile)) return void 0;
|
|
238
|
+
try {
|
|
239
|
+
const parsed = JSON.parse(readFileSync(lockFile, "utf8"));
|
|
240
|
+
return parsed.stackVersion;
|
|
241
|
+
} catch {
|
|
242
|
+
return void 0;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
async function postDeployAudit(options, status, stackVersion, previousVersion, error, metadata) {
|
|
246
|
+
if (!options.auditUrl) return;
|
|
247
|
+
const postJson = options.postJson ?? defaultPostJson;
|
|
248
|
+
try {
|
|
249
|
+
await postJson(options.auditUrl, {
|
|
250
|
+
stackVersion,
|
|
251
|
+
previousVersion,
|
|
252
|
+
status,
|
|
253
|
+
error,
|
|
254
|
+
metadata,
|
|
255
|
+
deviceId: options.deviceId,
|
|
256
|
+
licenseKey: options.licenseKey
|
|
257
|
+
}, options.manifestAuthToken);
|
|
258
|
+
} catch (err) {
|
|
259
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
260
|
+
console.warn(`[stack-update] deploy audit failed: ${reason}`);
|
|
200
261
|
}
|
|
201
262
|
}
|
|
202
263
|
async function verifyReleaseHealth(release, retries, delayMs) {
|
|
@@ -235,20 +296,53 @@ function httpStatus(urlString) {
|
|
|
235
296
|
function defaultExec(cmd, args, opts) {
|
|
236
297
|
execFileSync(cmd, args, { stdio: "inherit", cwd: opts?.cwd });
|
|
237
298
|
}
|
|
238
|
-
|
|
239
|
-
|
|
299
|
+
function defaultDockerLogin(creds) {
|
|
300
|
+
execFileSync("docker", ["login", creds.registry, "-u", creds.username, "--password-stdin"], {
|
|
301
|
+
input: creds.password,
|
|
302
|
+
stdio: ["pipe", "inherit", "inherit"]
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
function defaultDockerLogout(registry) {
|
|
306
|
+
execFileSync("docker", ["logout", registry], { stdio: "ignore" });
|
|
307
|
+
}
|
|
308
|
+
async function defaultFetchText(ref, authToken) {
|
|
309
|
+
const res = await fetch(ref, {
|
|
310
|
+
headers: authToken ? { authorization: `Bearer ${authToken}` } : void 0
|
|
311
|
+
});
|
|
240
312
|
if (!res.ok) throw new Error(`Failed to fetch ${ref}: HTTP ${res.status}`);
|
|
241
313
|
return res.text();
|
|
242
314
|
}
|
|
315
|
+
async function defaultPostJson(url, body, authToken) {
|
|
316
|
+
const res = await fetch(url, {
|
|
317
|
+
method: "POST",
|
|
318
|
+
headers: {
|
|
319
|
+
"content-type": "application/json",
|
|
320
|
+
...authToken ? { authorization: `Bearer ${authToken}` } : {}
|
|
321
|
+
},
|
|
322
|
+
body: JSON.stringify(body)
|
|
323
|
+
});
|
|
324
|
+
if (!res.ok) throw new Error(`Failed to POST ${url}: HTTP ${res.status}`);
|
|
325
|
+
}
|
|
243
326
|
function defaultStackPaths() {
|
|
244
327
|
const cwdCompose = path.resolve("docker-compose.yml");
|
|
245
328
|
const cwdEnv = path.resolve(".env");
|
|
246
329
|
return {
|
|
247
330
|
composeFile: process.env.EXE_STACK_COMPOSE_FILE || (existsSync(cwdCompose) ? cwdCompose : "/opt/exe-stack/docker-compose.yml"),
|
|
248
331
|
envFile: process.env.EXE_STACK_ENV_FILE || (existsSync(cwdEnv) ? cwdEnv : "/opt/exe-stack/.env"),
|
|
249
|
-
manifestRef: process.env.EXE_STACK_MANIFEST || "https://update.askexe.com/stack-manifest.json"
|
|
332
|
+
manifestRef: process.env.EXE_STACK_MANIFEST || "https://update.askexe.com/stack-manifest.json",
|
|
333
|
+
auditUrl: process.env.EXE_STACK_AUDIT_URL || "https://update.askexe.com/v1/deploy-audits",
|
|
334
|
+
imageCredentialsUrl: process.env.EXE_STACK_IMAGE_CREDENTIALS_URL || "https://update.askexe.com/v1/image-credentials",
|
|
335
|
+
manifestAuthToken: process.env.EXE_STACK_UPDATE_TOKEN,
|
|
336
|
+
manifestPublicKey: loadDefaultPublicKey()
|
|
250
337
|
};
|
|
251
338
|
}
|
|
339
|
+
function loadDefaultPublicKey() {
|
|
340
|
+
if (process.env.EXE_STACK_PUBLIC_KEY) return process.env.EXE_STACK_PUBLIC_KEY;
|
|
341
|
+
if (process.env.EXE_STACK_PUBLIC_KEY_FILE && existsSync(process.env.EXE_STACK_PUBLIC_KEY_FILE)) {
|
|
342
|
+
return readFileSync(process.env.EXE_STACK_PUBLIC_KEY_FILE, "utf8");
|
|
343
|
+
}
|
|
344
|
+
return void 0;
|
|
345
|
+
}
|
|
252
346
|
|
|
253
347
|
// src/bin/stack-update.ts
|
|
254
348
|
function parseArgs(args) {
|
|
@@ -257,6 +351,12 @@ function parseArgs(args) {
|
|
|
257
351
|
manifestRef: defaults.manifestRef,
|
|
258
352
|
composeFile: defaults.composeFile,
|
|
259
353
|
envFile: defaults.envFile,
|
|
354
|
+
auditUrl: defaults.auditUrl,
|
|
355
|
+
imageCredentialsUrl: defaults.imageCredentialsUrl,
|
|
356
|
+
manifestAuthToken: defaults.manifestAuthToken,
|
|
357
|
+
manifestPublicKey: defaults.manifestPublicKey,
|
|
358
|
+
deviceId: process.env.EXE_DEVICE_ID,
|
|
359
|
+
licenseKey: process.env.EXE_LICENSE_KEY,
|
|
260
360
|
dryRun: false,
|
|
261
361
|
check: false,
|
|
262
362
|
rollback: false,
|
|
@@ -277,6 +377,18 @@ function parseArgs(args) {
|
|
|
277
377
|
else if (arg === "--lock-file") opts.lockFile = next();
|
|
278
378
|
else if (arg === "--public-key") opts.manifestPublicKey = readFileSync2(next(), "utf8");
|
|
279
379
|
else if (arg.startsWith("--public-key=")) opts.manifestPublicKey = readFileSync2(arg.split("=").slice(1).join("="), "utf8");
|
|
380
|
+
else if (arg === "--auth-token") opts.manifestAuthToken = next();
|
|
381
|
+
else if (arg.startsWith("--auth-token=")) opts.manifestAuthToken = arg.split("=").slice(1).join("=");
|
|
382
|
+
else if (arg === "--auth-token-env") opts.manifestAuthToken = process.env[next()] ?? "";
|
|
383
|
+
else if (arg === "--audit-url") opts.auditUrl = next();
|
|
384
|
+
else if (arg.startsWith("--audit-url=")) opts.auditUrl = arg.split("=").slice(1).join("=");
|
|
385
|
+
else if (arg === "--image-credentials-url") opts.imageCredentialsUrl = next();
|
|
386
|
+
else if (arg.startsWith("--image-credentials-url=")) opts.imageCredentialsUrl = arg.split("=").slice(1).join("=");
|
|
387
|
+
else if (arg === "--no-image-credentials") opts.imageCredentialsUrl = void 0;
|
|
388
|
+
else if (arg === "--device-id") opts.deviceId = next();
|
|
389
|
+
else if (arg.startsWith("--device-id=")) opts.deviceId = arg.split("=").slice(1).join("=");
|
|
390
|
+
else if (arg === "--license-key") opts.licenseKey = next();
|
|
391
|
+
else if (arg.startsWith("--license-key=")) opts.licenseKey = arg.split("=").slice(1).join("=");
|
|
280
392
|
else if (arg === "--rollback") opts.rollback = true;
|
|
281
393
|
else if (arg === "--dry-run") opts.dryRun = true;
|
|
282
394
|
else if (arg === "--check") opts.check = true;
|
|
@@ -307,6 +419,13 @@ Options:
|
|
|
307
419
|
--check Print available changes only
|
|
308
420
|
--dry-run Plan only; do not run Docker
|
|
309
421
|
--public-key <path> PEM public key for signed manifest verification
|
|
422
|
+
--auth-token <token> Bearer token for private update manifest/audit API
|
|
423
|
+
--auth-token-env <name> Read bearer token from an environment variable
|
|
424
|
+
--audit-url <url> Deploy-audit endpoint (default: EXE_STACK_AUDIT_URL/update.askexe.com)
|
|
425
|
+
--image-credentials-url <url> Registry credential broker endpoint
|
|
426
|
+
--no-image-credentials Skip registry credential broker / Docker login
|
|
427
|
+
--device-id <id> Device ID to include in deploy audit
|
|
428
|
+
--license-key <key> License key to include in deploy audit
|
|
310
429
|
--rollback Restore latest backed-up .env and restart stack
|
|
311
430
|
--allow-breaking <ids> Confirm breaking changes, comma-separated
|
|
312
431
|
-y, --yes Non-interactive confirmation
|
|
@@ -344,7 +463,7 @@ async function main() {
|
|
|
344
463
|
console.log(`\u2705 Stack rollback attempted using backup: ${result2.backupEnvFile ?? "latest backup"}`);
|
|
345
464
|
return;
|
|
346
465
|
}
|
|
347
|
-
const manifest = await loadStackManifest(opts.manifestRef, void 0, opts.manifestPublicKey);
|
|
466
|
+
const manifest = await loadStackManifest(opts.manifestRef, void 0, opts.manifestPublicKey, opts.manifestAuthToken);
|
|
348
467
|
const envRaw = readFileSync2(opts.envFile, "utf8");
|
|
349
468
|
const plan = createStackUpdatePlan(manifest, envRaw, opts.targetVersion);
|
|
350
469
|
console.log(`Exe OS stack target: ${plan.targetVersion}`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@askexenow/exe-os",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.64",
|
|
4
4
|
"description": "AI employee operating system — persistent memory, task management, and multi-agent coordination for Claude Code.",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE",
|
|
6
6
|
"type": "module",
|