@proagentstore/cli 0.4.149 → 0.4.151
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/bootstrap.js +74 -9
- package/dist/chunk-PKMXQJEJ.js +281 -0
- package/dist/index.js +491 -105
- package/package.json +1 -1
- package/dist/chunk-NNA7VZ6W.js +0 -138
package/dist/index.js
CHANGED
|
@@ -2,12 +2,19 @@
|
|
|
2
2
|
import {
|
|
3
3
|
BOOTSTRAP_ENV,
|
|
4
4
|
CLI_PACKAGE,
|
|
5
|
+
badVersions,
|
|
6
|
+
beginTrial,
|
|
5
7
|
installPayload,
|
|
6
8
|
isVersion,
|
|
7
9
|
latestPublishedRelease,
|
|
10
|
+
logUpdateStep,
|
|
11
|
+
markPayloadHealthy,
|
|
8
12
|
olderThan,
|
|
9
|
-
|
|
10
|
-
|
|
13
|
+
payloadRoot,
|
|
14
|
+
runNpm,
|
|
15
|
+
takeRollbackNotice,
|
|
16
|
+
updateLogUpload
|
|
17
|
+
} from "./chunk-PKMXQJEJ.js";
|
|
11
18
|
|
|
12
19
|
// src/index.ts
|
|
13
20
|
import { createRequire as createRequire3 } from "module";
|
|
@@ -1361,6 +1368,7 @@ function deferredUpdateLeaseActive(lease, now = Date.now()) {
|
|
|
1361
1368
|
return now < lease.expiresAt;
|
|
1362
1369
|
}
|
|
1363
1370
|
function leaveForRestart(restarter, deps = { spawn: spawn3, exit: (code) => process.exit(code) }) {
|
|
1371
|
+
logUpdateStep(`restart: leaving for the ${restarter} restarter (${restarter === "command" ? "restart command started, exit 0" : `exit ${RUNNER_RESTART_EXIT_CODE}`})`);
|
|
1364
1372
|
if (restarter === "command") {
|
|
1365
1373
|
deps.spawn(process.env[RESTART_COMMAND_ENV], { shell: true, detached: true, stdio: "ignore", env: process.env }).unref();
|
|
1366
1374
|
deps.exit(0);
|
|
@@ -1379,6 +1387,8 @@ function planRunnerUpdate(f) {
|
|
|
1379
1387
|
if (f.fromSource) return { action: "refused", current: f.current, reason: "This runner runs from a source checkout \u2014 update it with `git pull` there, not npm." };
|
|
1380
1388
|
if (!f.latest) return { action: "refused", current: f.current, reason: `npm could not be asked for the latest ${CLI_PACKAGE} from this machine${f.latestError ? `: ${f.latestError}` : "."}`, retryable: true };
|
|
1381
1389
|
if (!olderThan(f.current, f.latest)) return { action: "up-to-date", current: f.current };
|
|
1390
|
+
const rolledBack = f.rolledBack?.[f.latest];
|
|
1391
|
+
if (rolledBack) return { action: "refused", current: f.current, reason: `${f.latest} was installed here and rolled back: ${rolledBack}. This machine stays on ${f.current} until a newer release is published.`, retryable: true };
|
|
1382
1392
|
if (!f.restarter) {
|
|
1383
1393
|
return {
|
|
1384
1394
|
action: "refused",
|
|
@@ -1411,17 +1421,24 @@ function planRunnerRestart(f) {
|
|
|
1411
1421
|
}
|
|
1412
1422
|
return { action: "restart", current: f.current, restarter: f.restarter };
|
|
1413
1423
|
}
|
|
1414
|
-
async function installVersion(version2) {
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
return;
|
|
1418
|
-
}
|
|
1424
|
+
async function installVersion(version2, current = null) {
|
|
1425
|
+
const bootstrapped = !!process.env[BOOTSTRAP_ENV];
|
|
1426
|
+
logUpdateStep(`install: ${current ?? "?"} \u2192 ${version2} starting (${bootstrapped ? "payload cache" : "npm i -g"})`);
|
|
1419
1427
|
try {
|
|
1420
|
-
|
|
1428
|
+
if (bootstrapped) {
|
|
1429
|
+
await installPayload(version2);
|
|
1430
|
+
beginTrial(payloadRoot(), version2, current);
|
|
1431
|
+
} else {
|
|
1432
|
+
await runNpm(["i", "-g", `${CLI_PACKAGE}@${version2}`], { timeout: 5 * 6e4 }).catch((e) => {
|
|
1433
|
+
const stderr = String(e.stderr ?? "").trim();
|
|
1434
|
+
throw new Error((stderr || (e instanceof Error ? e.message : String(e))).slice(-400));
|
|
1435
|
+
});
|
|
1436
|
+
}
|
|
1421
1437
|
} catch (e) {
|
|
1422
|
-
|
|
1423
|
-
throw
|
|
1438
|
+
logUpdateStep(`install: ${version2} FAILED \u2014 ${e instanceof Error ? e.message : String(e)}`);
|
|
1439
|
+
throw e;
|
|
1424
1440
|
}
|
|
1441
|
+
logUpdateStep(`install: ${version2} installed`);
|
|
1425
1442
|
}
|
|
1426
1443
|
|
|
1427
1444
|
// src/commands/runner/membership.ts
|
|
@@ -1776,13 +1793,15 @@ function parseAutoUpdatePolicy(value) {
|
|
|
1776
1793
|
const lastAttemptAt = raw.lastAttemptAt ?? raw.last_attempt_at;
|
|
1777
1794
|
const reason = raw.reason;
|
|
1778
1795
|
const recovery = parseRecovery(raw.recovery);
|
|
1796
|
+
const fleet = raw.fleetUpdating ?? raw.fleet_updating;
|
|
1779
1797
|
return {
|
|
1780
1798
|
autoUpdate,
|
|
1781
1799
|
...status ? { status } : {},
|
|
1782
1800
|
...typeof latestVersion === "string" ? { latestVersion } : {},
|
|
1783
1801
|
...typeof lastAttemptAt === "string" ? { lastAttemptAt } : {},
|
|
1784
1802
|
...typeof reason === "string" ? { reason } : {},
|
|
1785
|
-
...recovery ? { recovery } : {}
|
|
1803
|
+
...recovery ? { recovery } : {},
|
|
1804
|
+
...typeof fleet === "number" && Number.isInteger(fleet) && fleet >= 0 ? { fleetUpdating: fleet } : {}
|
|
1786
1805
|
};
|
|
1787
1806
|
}
|
|
1788
1807
|
function policyFromResponse(value) {
|
|
@@ -1843,7 +1862,9 @@ function acceptPolicyResponse(local, wasAuthoritative, response) {
|
|
|
1843
1862
|
const { recovery: _fromService, ...remote } = parsed;
|
|
1844
1863
|
const action = policyScheduleAction(local, remote, wasAuthoritative);
|
|
1845
1864
|
const ownerTransition = action === "cancel" || !local.autoUpdate;
|
|
1846
|
-
|
|
1865
|
+
const { fleetUpdating: _stale, ...base } = local;
|
|
1866
|
+
const fleet = remote.fleetUpdating === void 0 ? {} : { fleetUpdating: remote.fleetUpdating };
|
|
1867
|
+
return { policy: ownerTransition ? { ...base, ...remote, ...fleet } : { ...base, autoUpdate: remote.autoUpdate, ...fleet }, action };
|
|
1847
1868
|
}
|
|
1848
1869
|
var ResponseFence = class {
|
|
1849
1870
|
sent = 0;
|
|
@@ -2068,6 +2089,12 @@ var AutomaticUpdateController = class {
|
|
|
2068
2089
|
this.defer();
|
|
2069
2090
|
return;
|
|
2070
2091
|
}
|
|
2092
|
+
if (policy.fleetUpdating) {
|
|
2093
|
+
this.blockerClass = "fleet_stagger";
|
|
2094
|
+
this.deps.status("waiting-for-idle", { latestVersion: plan.latest, reason: `${policy.fleetUpdating} other machine(s) on this account are mid-update; this one updates after them.` });
|
|
2095
|
+
this.defer(plan.latest);
|
|
2096
|
+
return;
|
|
2097
|
+
}
|
|
2071
2098
|
const target = open && isVersion(open.target) ? open.target : plan.latest;
|
|
2072
2099
|
const attempt = { ...plan, latest: target };
|
|
2073
2100
|
const recovery = open ?? { target, attachments: [...this.deps.attachments?.() ?? []], at: new Date(this.now()).toISOString() };
|
|
@@ -2077,7 +2104,7 @@ var AutomaticUpdateController = class {
|
|
|
2077
2104
|
this.deps.status("restarting", { latestVersion: target, reason: void 0 });
|
|
2078
2105
|
return;
|
|
2079
2106
|
}
|
|
2080
|
-
this.blockerClass = "idle_wait";
|
|
2107
|
+
this.blockerClass = this.deps.policy().value.fleetUpdating ? "fleet_stagger" : "idle_wait";
|
|
2081
2108
|
this.defer(target);
|
|
2082
2109
|
}
|
|
2083
2110
|
defer(latest) {
|
|
@@ -2250,7 +2277,10 @@ function createPolicyChannel(deps) {
|
|
|
2250
2277
|
if (action === "cancel") deps.onAction("cancel");
|
|
2251
2278
|
else if (action === "start" && !deps.cell.refreshing) deps.onAction("start");
|
|
2252
2279
|
};
|
|
2253
|
-
const accept = (response, sentAs) =>
|
|
2280
|
+
const accept = (response, sentAs) => {
|
|
2281
|
+
act(deps.cell.accept(response, sentAs));
|
|
2282
|
+
deps.onAnswer?.();
|
|
2283
|
+
};
|
|
2254
2284
|
const register = async (id, force = deps.force, signal) => {
|
|
2255
2285
|
const sent = deps.cell.next();
|
|
2256
2286
|
try {
|
|
@@ -2300,7 +2330,7 @@ function createInstallBridge(deps) {
|
|
|
2300
2330
|
if (automatic) {
|
|
2301
2331
|
const fresh = await within((signal) => deps.refresh(signal));
|
|
2302
2332
|
if (fresh === null) expired(false, "The owner policy refresh before installing");
|
|
2303
|
-
if (!fresh || !deps.cell.policy.autoUpdate) {
|
|
2333
|
+
if (!fresh || !deps.cell.policy.autoUpdate || deps.cell.policy.fleetUpdating) {
|
|
2304
2334
|
release(false);
|
|
2305
2335
|
return false;
|
|
2306
2336
|
}
|
|
@@ -2344,6 +2374,122 @@ function createInstallBridge(deps) {
|
|
|
2344
2374
|
return { run, inFlight: () => inFlight, installedPendingRestart: () => installedPendingRestart };
|
|
2345
2375
|
}
|
|
2346
2376
|
|
|
2377
|
+
// src/commands/runner/release-trial.ts
|
|
2378
|
+
function releaseTrial(version2, cell, log, env = process.env, root = payloadRoot) {
|
|
2379
|
+
if (!env[BOOTSTRAP_ENV]) return { rollbackReason: null, proven: () => void 0, rolledBack: () => ({}) };
|
|
2380
|
+
const notice = takeRollbackNotice(root());
|
|
2381
|
+
const rollbackReason = notice ? `Rolled back from ${notice.from} to ${version2}: ${notice.reason}.` : null;
|
|
2382
|
+
if (rollbackReason) {
|
|
2383
|
+
log(`auto-update: ${rollbackReason}`);
|
|
2384
|
+
cell.record("failure", { reason: rollbackReason, recovery: void 0 });
|
|
2385
|
+
}
|
|
2386
|
+
let done = false;
|
|
2387
|
+
return {
|
|
2388
|
+
rollbackReason,
|
|
2389
|
+
proven: () => {
|
|
2390
|
+
if (done) return;
|
|
2391
|
+
done = true;
|
|
2392
|
+
if (markPayloadHealthy(root(), version2)) log(`auto-update: ${version2} registered \u2014 it is kept.`);
|
|
2393
|
+
},
|
|
2394
|
+
rolledBack: () => badVersions(root())
|
|
2395
|
+
};
|
|
2396
|
+
}
|
|
2397
|
+
|
|
2398
|
+
// src/commands/up-restart.ts
|
|
2399
|
+
import { spawn as nodeSpawn, spawnSync as nodeSpawnSync } from "child_process";
|
|
2400
|
+
var UP_LAUNCHER_ENV = "PAGS_UP_LAUNCHER";
|
|
2401
|
+
var UP_LAUNCHER_PID_ENV = "PAGS_UP_LAUNCHER_PID";
|
|
2402
|
+
var RUNNER_DIED_EXIT_CODE = 76;
|
|
2403
|
+
function upRestartMode(env, parent = { pid: process.ppid, alive: pidAlive }) {
|
|
2404
|
+
if (env[SERVICE_ENV] === "1") return "service";
|
|
2405
|
+
if (env[RESTART_COMMAND_ENV]?.trim()) return "command";
|
|
2406
|
+
const launcher = Number(env[UP_LAUNCHER_PID_ENV]);
|
|
2407
|
+
if (env[UP_LAUNCHER_ENV] === "1" && launcher === parent.pid && parent.alive(launcher)) return "launcher-child";
|
|
2408
|
+
return "launcher";
|
|
2409
|
+
}
|
|
2410
|
+
function runnerExitAction(code, stopping) {
|
|
2411
|
+
if (code === RUNNER_RESTART_EXIT_CODE) return "restart";
|
|
2412
|
+
if (stopping) return "ignore";
|
|
2413
|
+
return code === 0 ? "stop" : "died";
|
|
2414
|
+
}
|
|
2415
|
+
function restartOwner(env = process.env, alive = pidAlive) {
|
|
2416
|
+
if (env[SERVICE_ENV] === "1") return "service";
|
|
2417
|
+
return env[UP_LAUNCHER_ENV] === "1" && alive(Number(env[UP_LAUNCHER_PID_ENV])) ? "launcher" : "none";
|
|
2418
|
+
}
|
|
2419
|
+
function relaunchDelayMs(failures) {
|
|
2420
|
+
return Math.min(5 * 6e4, 1e4 * 2 ** Math.max(0, failures - 1));
|
|
2421
|
+
}
|
|
2422
|
+
var RAN_MS = 6e4;
|
|
2423
|
+
var RESET_MS = 10 * 6e4;
|
|
2424
|
+
function processRestartDeps() {
|
|
2425
|
+
return {
|
|
2426
|
+
env: process.env,
|
|
2427
|
+
execPath: process.execPath,
|
|
2428
|
+
execArgv: process.execArgv,
|
|
2429
|
+
script: process.argv[1],
|
|
2430
|
+
spawn: nodeSpawn,
|
|
2431
|
+
spawnSync: nodeSpawnSync,
|
|
2432
|
+
exit: (code) => process.exit(code),
|
|
2433
|
+
log: () => {
|
|
2434
|
+
},
|
|
2435
|
+
sleep: (ms) => Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms),
|
|
2436
|
+
step: (line) => logUpdateStep(line),
|
|
2437
|
+
parent: { pid: process.ppid, alive: pidAlive },
|
|
2438
|
+
pid: process.pid,
|
|
2439
|
+
now: Date.now
|
|
2440
|
+
};
|
|
2441
|
+
}
|
|
2442
|
+
function restartPagsUp(args, extraEnv, deps, opts = {}) {
|
|
2443
|
+
const env = { ...deps.env, ...extraEnv };
|
|
2444
|
+
const argv = [...deps.execArgv, deps.script, ...args];
|
|
2445
|
+
const exitCode = opts.exitCode ?? RUNNER_RESTART_EXIT_CODE;
|
|
2446
|
+
const mode = upRestartMode(env, deps.parent);
|
|
2447
|
+
deps.step(`${opts.initial ? "start" : "restart"}: pags ${args.join(" ")} \u2014 ${opts.initial ? "as" : "leaving via"} ${mode}${exitCode === RUNNER_DIED_EXIT_CODE ? " (the runner died)" : ""}`);
|
|
2448
|
+
if (mode === "service") {
|
|
2449
|
+
deps.exit(exitCode);
|
|
2450
|
+
return;
|
|
2451
|
+
}
|
|
2452
|
+
if (mode === "command") {
|
|
2453
|
+
leaveForRestart(mode, { spawn: deps.spawn, exit: deps.exit });
|
|
2454
|
+
return;
|
|
2455
|
+
}
|
|
2456
|
+
if (mode === "launcher-child") {
|
|
2457
|
+
deps.exit(exitCode);
|
|
2458
|
+
return;
|
|
2459
|
+
}
|
|
2460
|
+
const childEnv = { ...env, [UP_LAUNCHER_ENV]: "1", [UP_LAUNCHER_PID_ENV]: String(deps.pid) };
|
|
2461
|
+
let failures = 0;
|
|
2462
|
+
for (let first = true; ; first = false) {
|
|
2463
|
+
const unattended = !first || env.PAGS_NO_PROMPT === "1";
|
|
2464
|
+
deps.step(`launcher: starting pags up${unattended ? " (unattended)" : ""}`);
|
|
2465
|
+
const startedAt = deps.now();
|
|
2466
|
+
const r = deps.spawnSync(deps.execPath, argv, { stdio: "inherit", env: first ? childEnv : { ...childEnv, PAGS_NO_PROMPT: "1" } });
|
|
2467
|
+
const ran = deps.now() - startedAt;
|
|
2468
|
+
const how = r.error ? `could not start (${r.error.message})` : r.signal ? `was killed by ${r.signal}` : `exited ${r.status}`;
|
|
2469
|
+
deps.step(`launcher: pags up ${how} after ${Math.round(ran / 1e3)}s`);
|
|
2470
|
+
if (ran >= RESET_MS) failures = 0;
|
|
2471
|
+
if (r.status === RUNNER_RESTART_EXIT_CODE) {
|
|
2472
|
+
failures = 0;
|
|
2473
|
+
continue;
|
|
2474
|
+
}
|
|
2475
|
+
if (!r.error && (r.status === 0 || r.signal === "SIGINT")) {
|
|
2476
|
+
deps.exit(0);
|
|
2477
|
+
return;
|
|
2478
|
+
}
|
|
2479
|
+
if (unattended || r.status === RUNNER_DIED_EXIT_CODE || ran >= RAN_MS) {
|
|
2480
|
+
failures++;
|
|
2481
|
+
const wait = relaunchDelayMs(failures);
|
|
2482
|
+
deps.log(`pags up ${how} \u2014 starting it again in ${Math.round(wait / 1e3)}s (attempt ${failures + 1}).`);
|
|
2483
|
+
deps.step(`launcher: retrying in ${Math.round(wait / 1e3)}s (failure ${failures})`);
|
|
2484
|
+
deps.sleep(wait);
|
|
2485
|
+
continue;
|
|
2486
|
+
}
|
|
2487
|
+
if (!opts.initial) deps.log(`Restart failed${typeof r.status === "number" ? ` (exit ${r.status})` : ""} \u2014 the runner is NOT running. Run 'pags up' again.`);
|
|
2488
|
+
deps.exit(typeof r.status === "number" && r.status !== 0 ? r.status : 1);
|
|
2489
|
+
return;
|
|
2490
|
+
}
|
|
2491
|
+
}
|
|
2492
|
+
|
|
2347
2493
|
// src/commands/runner/relay.ts
|
|
2348
2494
|
var MEMBERSHIP_SYNC_PATH = "/pags/membership/sync";
|
|
2349
2495
|
var CLI_CONTROL_PATHS = /* @__PURE__ */ new Set([MEMBERSHIP_SYNC_PATH, RUNNER_UPDATE_PATH, RUNNER_RESTART_PATH]);
|
|
@@ -2355,12 +2501,15 @@ async function connectViaRelay(instanceIds, localUrl, runnerToken, opts, force =
|
|
|
2355
2501
|
const machine = loadMachineIdentity(runnerNode);
|
|
2356
2502
|
const autoUpdateKey = machine.id ? autoUpdatePolicyKey(machine.id) : "";
|
|
2357
2503
|
const cell = new PolicyCell(autoUpdateKey ? loadAutoUpdatePolicy(autoUpdateKey) ?? { autoUpdate: false } : { autoUpdate: false }, (p) => saveAutoUpdatePolicy(autoUpdateKey, p), !!autoUpdateKey);
|
|
2504
|
+
const trial = releaseTrial(CLI_VERSION, cell, writeLine);
|
|
2358
2505
|
let scheduleAutomaticUpdate = () => void 0;
|
|
2359
2506
|
let autoUpdateDiagnostics = () => ({});
|
|
2507
|
+
const updateLog = updateLogUpload(CLI_VERSION);
|
|
2360
2508
|
const autoUpdateStatusBody = () => ({
|
|
2361
2509
|
status: autoUpdateStatusWire(cell.policy.status),
|
|
2362
2510
|
latestVersion: cell.policy.latestVersion,
|
|
2363
2511
|
error: cell.policy.reason,
|
|
2512
|
+
...updateLog.body(),
|
|
2364
2513
|
...autoUpdateDiagnostics()
|
|
2365
2514
|
});
|
|
2366
2515
|
const capabilities = await requestRunner("GET", "/capabilities", { url: localUrl, token: runnerToken, instanceId: instanceIds[0] });
|
|
@@ -2387,7 +2536,11 @@ async function connectViaRelay(instanceIds, localUrl, runnerToken, opts, force =
|
|
|
2387
2536
|
lastRegisterError = msg;
|
|
2388
2537
|
writeError(`register ${id.slice(0, 8)}\u2026 failed: ${msg}`);
|
|
2389
2538
|
},
|
|
2390
|
-
onAction: (action) => scheduleAutomaticUpdate(action === "start", action === "cancel")
|
|
2539
|
+
onAction: (action) => scheduleAutomaticUpdate(action === "start", action === "cancel"),
|
|
2540
|
+
onAnswer: () => {
|
|
2541
|
+
trial.proven();
|
|
2542
|
+
updateLog.answered();
|
|
2543
|
+
}
|
|
2391
2544
|
});
|
|
2392
2545
|
const registered = channel.registered;
|
|
2393
2546
|
const registerRuntime = (id, forceClaim = force) => channel.register(id, forceClaim);
|
|
@@ -2424,6 +2577,7 @@ async function connectViaRelay(instanceIds, localUrl, runnerToken, opts, force =
|
|
|
2424
2577
|
..."error" in release ? { latestError: release.error } : {},
|
|
2425
2578
|
fromSource: runsFromSource(),
|
|
2426
2579
|
restarter: restarterFrom(process.env),
|
|
2580
|
+
rolledBack: trial.rolledBack(),
|
|
2427
2581
|
// A restart ends browser/application work too. Give these durable names to the same
|
|
2428
2582
|
// planner so neither manual nor automatic update can mistake them for an idle machine.
|
|
2429
2583
|
busy
|
|
@@ -2434,7 +2588,7 @@ async function connectViaRelay(instanceIds, localUrl, runnerToken, opts, force =
|
|
|
2434
2588
|
cell,
|
|
2435
2589
|
refresh: channel.refresh,
|
|
2436
2590
|
facts: updateFacts,
|
|
2437
|
-
install: installVersion,
|
|
2591
|
+
install: (version2) => installVersion(version2, CLI_VERSION),
|
|
2438
2592
|
admission: relayMutationAdmission,
|
|
2439
2593
|
leaseActive: deferredUpdateLeaseActive,
|
|
2440
2594
|
detachAll: () => {
|
|
@@ -2631,7 +2785,9 @@ async function connectViaRelay(instanceIds, localUrl, runnerToken, opts, force =
|
|
|
2631
2785
|
// #896: who is heartbeating. Set by `runner connect` from the lock it took.
|
|
2632
2786
|
...process.env.PAGS_RUNNER_RSID ? { rsid: process.env.PAGS_RUNNER_RSID } : {},
|
|
2633
2787
|
pid: process.pid,
|
|
2634
|
-
...process.env.PAGS_RUNNER_LAUNCH ? { launch: process.env.PAGS_RUNNER_LAUNCH } : {}
|
|
2788
|
+
...process.env.PAGS_RUNNER_LAUNCH ? { launch: process.env.PAGS_RUNNER_LAUNCH } : {},
|
|
2789
|
+
supervisor: restartOwner()
|
|
2790
|
+
// #1154: what starts it again if it dies
|
|
2635
2791
|
},
|
|
2636
2792
|
relayRttMs,
|
|
2637
2793
|
sessions: sessions ?? void 0
|
|
@@ -3198,6 +3354,8 @@ function createRunnerCommand() {
|
|
|
3198
3354
|
process.exit(1);
|
|
3199
3355
|
}
|
|
3200
3356
|
lockHeld = { path: taken.path, rsid: taken.lock.rsid, nonce: taken.lock.nonce, launch: taken.lock.launch };
|
|
3357
|
+
process.env.PAGS_RUNNER_RSID = taken.lock.rsid;
|
|
3358
|
+
process.env.PAGS_RUNNER_LAUNCH = taken.lock.launch;
|
|
3201
3359
|
writeLine(`STATUS runner-session=${taken.lock.rsid}`);
|
|
3202
3360
|
}
|
|
3203
3361
|
const releaseHeldLock = () => {
|
|
@@ -3394,7 +3552,7 @@ import { readFileSync as readFileSync11, statSync as statSync2, watchFile } from
|
|
|
3394
3552
|
import { Command as Command9 } from "commander";
|
|
3395
3553
|
|
|
3396
3554
|
// src/background.ts
|
|
3397
|
-
import { spawn as
|
|
3555
|
+
import { spawn as nodeSpawn2 } from "child_process";
|
|
3398
3556
|
import { closeSync as closeSync3, existsSync as existsSync10, mkdirSync as mkdirSync8, openSync as openSync3, readFileSync as readFileSync10, renameSync as renameSync4, statSync, writeFileSync as writeFileSync6 } from "fs";
|
|
3399
3557
|
import { dirname as dirname5, join as join11 } from "path";
|
|
3400
3558
|
var BACKGROUND_ENV = "PAGS_BACKGROUND";
|
|
@@ -3433,7 +3591,7 @@ function backgroundRestartCommand(execPath, script, upArgs, platform = process.p
|
|
|
3433
3591
|
const argv = [execPath, script, ...upArgs, "--background"].map((a) => shellQuote(a, platform)).join(" ");
|
|
3434
3592
|
return platform === "win32" ? `set ${BACKGROUND_RESTART_ENV}=1&& ${argv}` : `${BACKGROUND_RESTART_ENV}=1 ${argv}`;
|
|
3435
3593
|
}
|
|
3436
|
-
function spawnDetached(command, args, env, logPath, spawn5 =
|
|
3594
|
+
function spawnDetached(command, args, env, logPath, spawn5 = nodeSpawn2) {
|
|
3437
3595
|
mkdirSync8(dirname5(logPath), { recursive: true });
|
|
3438
3596
|
rotateLog(logPath);
|
|
3439
3597
|
const fd = openSync3(logPath, "a", 384);
|
|
@@ -3531,7 +3689,7 @@ var logsCommand = new Command9("logs").description("Show the background runner's
|
|
|
3531
3689
|
// src/commands/up.ts
|
|
3532
3690
|
import { createRequire as createRequire2 } from "module";
|
|
3533
3691
|
import { Command as Command10 } from "commander";
|
|
3534
|
-
import { hostname as hostname7 } from "os";
|
|
3692
|
+
import { homedir as homedir5, hostname as hostname7, userInfo } from "os";
|
|
3535
3693
|
|
|
3536
3694
|
// src/tui.ts
|
|
3537
3695
|
import chalk from "chalk";
|
|
@@ -3650,65 +3808,6 @@ async function waitForKey(keys, onInterrupt) {
|
|
|
3650
3808
|
});
|
|
3651
3809
|
}
|
|
3652
3810
|
|
|
3653
|
-
// src/commands/up-restart.ts
|
|
3654
|
-
import { spawn as nodeSpawn2, spawnSync as nodeSpawnSync } from "child_process";
|
|
3655
|
-
var UP_LAUNCHER_ENV = "PAGS_UP_LAUNCHER";
|
|
3656
|
-
function upRestartMode(env, platform, canExecve) {
|
|
3657
|
-
if (env[SERVICE_ENV] === "1") return "service";
|
|
3658
|
-
if (env[RESTART_COMMAND_ENV]?.trim()) return "command";
|
|
3659
|
-
if (env[UP_LAUNCHER_ENV] === "1") return "launcher-child";
|
|
3660
|
-
return platform !== "win32" && canExecve ? "exec" : "launcher";
|
|
3661
|
-
}
|
|
3662
|
-
function processRestartDeps() {
|
|
3663
|
-
const execve = process.execve;
|
|
3664
|
-
return {
|
|
3665
|
-
env: process.env,
|
|
3666
|
-
platform: process.platform,
|
|
3667
|
-
execPath: process.execPath,
|
|
3668
|
-
execArgv: process.execArgv,
|
|
3669
|
-
script: process.argv[1],
|
|
3670
|
-
execve: typeof execve === "function" ? execve.bind(process) : void 0,
|
|
3671
|
-
spawn: nodeSpawn2,
|
|
3672
|
-
spawnSync: nodeSpawnSync,
|
|
3673
|
-
exit: (code) => process.exit(code),
|
|
3674
|
-
log: () => {
|
|
3675
|
-
}
|
|
3676
|
-
};
|
|
3677
|
-
}
|
|
3678
|
-
function restartPagsUp(args, extraEnv, deps) {
|
|
3679
|
-
const env = { ...deps.env, ...extraEnv };
|
|
3680
|
-
const argv = [...deps.execArgv, deps.script, ...args];
|
|
3681
|
-
const mode = upRestartMode(env, deps.platform, !!deps.execve);
|
|
3682
|
-
if (mode === "service" || mode === "command") {
|
|
3683
|
-
leaveForRestart(mode, { spawn: deps.spawn, exit: deps.exit });
|
|
3684
|
-
return;
|
|
3685
|
-
}
|
|
3686
|
-
if (mode === "launcher-child") {
|
|
3687
|
-
deps.exit(RUNNER_RESTART_EXIT_CODE);
|
|
3688
|
-
return;
|
|
3689
|
-
}
|
|
3690
|
-
if (mode === "exec") {
|
|
3691
|
-
try {
|
|
3692
|
-
deps.execve(deps.execPath, [deps.execPath, ...argv], env);
|
|
3693
|
-
} catch (e) {
|
|
3694
|
-
deps.log(`Restart failed (${e instanceof Error ? e.message : String(e)}) \u2014 the runner is NOT running. Run 'pags up' again.`);
|
|
3695
|
-
deps.exit(1);
|
|
3696
|
-
}
|
|
3697
|
-
return;
|
|
3698
|
-
}
|
|
3699
|
-
for (let first = true; ; first = false) {
|
|
3700
|
-
const r = deps.spawnSync(deps.execPath, argv, { stdio: "inherit", env: { ...env, [UP_LAUNCHER_ENV]: "1", ...first ? {} : { PAGS_NO_PROMPT: "1" } } });
|
|
3701
|
-
if (r.status === RUNNER_RESTART_EXIT_CODE) continue;
|
|
3702
|
-
if (r.error || r.status !== 0) {
|
|
3703
|
-
deps.log(`Restart failed${typeof r.status === "number" ? ` (exit ${r.status})` : ""} \u2014 the runner is NOT running. Run 'pags up' again.`);
|
|
3704
|
-
deps.exit(typeof r.status === "number" && r.status !== 0 ? r.status : 1);
|
|
3705
|
-
return;
|
|
3706
|
-
}
|
|
3707
|
-
deps.exit(0);
|
|
3708
|
-
return;
|
|
3709
|
-
}
|
|
3710
|
-
}
|
|
3711
|
-
|
|
3712
3811
|
// src/runner-replace.ts
|
|
3713
3812
|
var REPLACE_EXIT_TIMEOUT_MS = 3e4;
|
|
3714
3813
|
function describeWork(work) {
|
|
@@ -3850,11 +3949,263 @@ async function startInBackground(start, deps = processDeps()) {
|
|
|
3850
3949
|
return 1;
|
|
3851
3950
|
}
|
|
3852
3951
|
|
|
3952
|
+
// src/service-install.ts
|
|
3953
|
+
import { execFileSync as execFileSync3 } from "child_process";
|
|
3954
|
+
import { mkdirSync as mkdirSync9, rmSync, writeFileSync as writeFileSync7 } from "fs";
|
|
3955
|
+
import { posix, win32 as win322 } from "path";
|
|
3956
|
+
var SERVICE_LABEL = "online.proagentstore.pags-up";
|
|
3957
|
+
var SYSTEMD_UNIT = "pags-up.service";
|
|
3958
|
+
var WINDOWS_TASK = "pags up";
|
|
3959
|
+
var pathFor = (ctx) => ctx.platform === "win32" ? win322 : posix;
|
|
3960
|
+
var supervisorPath = (ctx) => pathFor(ctx).join(ctx.configDir, "pags-up-supervisor.mjs");
|
|
3961
|
+
var serviceLogPath = (ctx) => pathFor(ctx).join(ctx.configDir, "pags-up.log");
|
|
3962
|
+
function supervisorScript(ctx) {
|
|
3963
|
+
const config = JSON.stringify({ node: ctx.nodePath, script: ctx.script, log: serviceLogPath(ctx), path: ctx.pathEnv });
|
|
3964
|
+
return `// Written by \`pags up --install-service\` (#1141). Keeps \`pags up\` running; see packages/cli/src/service-install.ts.
|
|
3965
|
+
import { spawn } from "node:child_process";
|
|
3966
|
+
import { appendFileSync, closeSync, openSync } from "node:fs";
|
|
3967
|
+
|
|
3968
|
+
const config = ${config};
|
|
3969
|
+
const num = (name, fallback) => { const n = Number(process.env[name]); return Number.isFinite(n) && n > 0 ? n : fallback; };
|
|
3970
|
+
const MIN = num("PAGS_SUPERVISOR_MIN_MS", 10_000);
|
|
3971
|
+
const MAX = num("PAGS_SUPERVISOR_MAX_MS", 300_000);
|
|
3972
|
+
const RESET = num("PAGS_SUPERVISOR_RESET_MS", 600_000);
|
|
3973
|
+
const RESTART_CODE = 75; // \`pags up\` asking to be started again: an update, or runner_restart
|
|
3974
|
+
|
|
3975
|
+
const say = (line) => { try { appendFileSync(config.log, \`[\${new Date().toISOString()}] supervisor: \${line}\\n\`); } catch { /* a log that cannot be written must not stop the runner */ } };
|
|
3976
|
+
let delay = MIN;
|
|
3977
|
+
let child = null;
|
|
3978
|
+
let stopping = false;
|
|
3979
|
+
|
|
3980
|
+
function start() {
|
|
3981
|
+
if (stopping) return;
|
|
3982
|
+
const startedAt = Date.now();
|
|
3983
|
+
let out = "ignore";
|
|
3984
|
+
try { out = openSync(config.log, "a"); } catch { /* run without a log rather than not at all */ }
|
|
3985
|
+
say("starting pags up");
|
|
3986
|
+
child = spawn(config.node, [config.script, "up", "--headless"], {
|
|
3987
|
+
stdio: ["ignore", out, out],
|
|
3988
|
+
env: { ...process.env, PATH: config.path || process.env.PATH, PAGS_SERVICE: "1", PAGS_SERVICE_NAME: "${SERVICE_LABEL}", PAGS_NO_PROMPT: "1" },
|
|
3989
|
+
windowsHide: true,
|
|
3990
|
+
});
|
|
3991
|
+
if (typeof out === "number") closeSync(out);
|
|
3992
|
+
child.on("error", (e) => say(\`could not start pags up: \${e.message}\`));
|
|
3993
|
+
child.on("exit", (code, signal) => {
|
|
3994
|
+
child = null;
|
|
3995
|
+
if (stopping) return;
|
|
3996
|
+
if (Date.now() - startedAt >= RESET) delay = MIN;
|
|
3997
|
+
const wait = code === RESTART_CODE ? 1_000 : delay;
|
|
3998
|
+
say(\`pags up exited (\${signal ?? code}); starting it again in \${Math.round(wait / 1000)}s\`);
|
|
3999
|
+
if (code !== RESTART_CODE) delay = Math.min(delay * 2, MAX);
|
|
4000
|
+
setTimeout(start, wait);
|
|
4001
|
+
});
|
|
4002
|
+
}
|
|
4003
|
+
|
|
4004
|
+
for (const sig of ["SIGTERM", "SIGINT"]) {
|
|
4005
|
+
process.on(sig, () => {
|
|
4006
|
+
stopping = true;
|
|
4007
|
+
say(\`stopping (\${sig})\`);
|
|
4008
|
+
if (child) child.kill("SIGTERM");
|
|
4009
|
+
setTimeout(() => process.exit(0), 5_000).unref();
|
|
4010
|
+
if (!child) process.exit(0);
|
|
4011
|
+
else child.once("exit", () => process.exit(0));
|
|
4012
|
+
});
|
|
4013
|
+
}
|
|
4014
|
+
start();
|
|
4015
|
+
`;
|
|
4016
|
+
}
|
|
4017
|
+
var xml = (s) => s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
4018
|
+
var unitWord = (s) => `"${s.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
|
|
4019
|
+
var ps = (s) => `'${s.replace(/'/g, "''")}'`;
|
|
4020
|
+
function versionManagedNode(nodePath) {
|
|
4021
|
+
if (/[\\/]n[\\/]versions[\\/]/.test(nodePath)) return "n";
|
|
4022
|
+
const m = /[\\/]\.?(nvm|fnm|volta|asdf|nodenv)[\\/]/i.exec(nodePath);
|
|
4023
|
+
return m ? m[1].toLowerCase() : null;
|
|
4024
|
+
}
|
|
4025
|
+
var pinnedNodeNote = (ctx) => {
|
|
4026
|
+
const manager = versionManagedNode(ctx.nodePath);
|
|
4027
|
+
return manager ? [`It runs Node ${ctx.nodePath}, installed by ${manager}. Removing or switching away from that Node version stops the service; after changing Node, run \`pags up --install-service\` again.`] : [];
|
|
4028
|
+
};
|
|
4029
|
+
function servicePlan(ctx) {
|
|
4030
|
+
const supervisor = supervisorPath(ctx);
|
|
4031
|
+
const log = serviceLogPath(ctx);
|
|
4032
|
+
const files = [{ path: supervisor, content: supervisorScript(ctx) }];
|
|
4033
|
+
if (ctx.platform === "darwin") {
|
|
4034
|
+
const plist = posix.join(ctx.home, "Library", "LaunchAgents", `${SERVICE_LABEL}.plist`);
|
|
4035
|
+
const domain = `gui/${ctx.uid ?? 501}`;
|
|
4036
|
+
files.push({
|
|
4037
|
+
path: plist,
|
|
4038
|
+
content: `<?xml version="1.0" encoding="UTF-8"?>
|
|
4039
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
4040
|
+
<plist version="1.0">
|
|
4041
|
+
<dict>
|
|
4042
|
+
<key>Label</key><string>${SERVICE_LABEL}</string>
|
|
4043
|
+
<key>ProgramArguments</key>
|
|
4044
|
+
<array>
|
|
4045
|
+
<string>${xml(ctx.nodePath)}</string>
|
|
4046
|
+
<string>${xml(supervisor)}</string>
|
|
4047
|
+
</array>
|
|
4048
|
+
<key>RunAtLoad</key><true/>
|
|
4049
|
+
<key>KeepAlive</key><true/>
|
|
4050
|
+
<key>ThrottleInterval</key><integer>30</integer>
|
|
4051
|
+
<key>ProcessType</key><string>Background</string>
|
|
4052
|
+
<key>WorkingDirectory</key><string>${xml(ctx.home)}</string>
|
|
4053
|
+
<key>StandardOutPath</key><string>${xml(log)}</string>
|
|
4054
|
+
<key>StandardErrorPath</key><string>${xml(log)}</string>
|
|
4055
|
+
</dict>
|
|
4056
|
+
</plist>
|
|
4057
|
+
`
|
|
4058
|
+
});
|
|
4059
|
+
return {
|
|
4060
|
+
kind: "launchd",
|
|
4061
|
+
files,
|
|
4062
|
+
commands: [
|
|
4063
|
+
{ file: "launchctl", args: ["bootout", `${domain}/${SERVICE_LABEL}`], optional: true, why: "unload an earlier copy" },
|
|
4064
|
+
{ file: "launchctl", args: ["bootstrap", domain, plist], why: "load the LaunchAgent" },
|
|
4065
|
+
{ file: "launchctl", args: ["kickstart", "-k", `${domain}/${SERVICE_LABEL}`], why: "start it now" }
|
|
4066
|
+
],
|
|
4067
|
+
notes: [
|
|
4068
|
+
...pinnedNodeNote(ctx),
|
|
4069
|
+
`LaunchAgent ${plist} \u2014 runs while this user is logged in. On a machine that must survive a reboot unattended, turn on automatic login for this user.`,
|
|
4070
|
+
`Remove it with: pags up --uninstall-service`
|
|
4071
|
+
]
|
|
4072
|
+
};
|
|
4073
|
+
}
|
|
4074
|
+
if (ctx.platform === "win32") {
|
|
4075
|
+
const task = win322.join(ctx.configDir, "pags-up-task.ps1");
|
|
4076
|
+
const principal = `${ctx.domain ? `${ctx.domain}\\` : ""}${ctx.user ?? ""}`;
|
|
4077
|
+
files.push({
|
|
4078
|
+
path: task,
|
|
4079
|
+
content: `# Written by \`pags up --install-service\` (#1141). Registers the "${WINDOWS_TASK}" Scheduled Task; needs an elevated PowerShell.
|
|
4080
|
+
$ErrorActionPreference = 'Stop'
|
|
4081
|
+
$action = New-ScheduledTaskAction -Execute ${ps(ctx.nodePath)} -Argument ${ps(`"${supervisor}"`)}
|
|
4082
|
+
$trigger = New-ScheduledTaskTrigger -AtStartup
|
|
4083
|
+
# Task Scheduler restarts a task that failed to START; a supervisor killed later just ends. Re-trigger it every five
|
|
4084
|
+
# minutes, forever \u2014 MultipleInstances IgnoreNew makes a trigger that finds it running do nothing (#1154).
|
|
4085
|
+
$trigger.Repetition = (New-ScheduledTaskTrigger -Once -At (Get-Date) -RepetitionInterval (New-TimeSpan -Minutes 5)).Repetition
|
|
4086
|
+
$principal = New-ScheduledTaskPrincipal -UserId ${ps(principal)} -LogonType S4U -RunLevel Limited
|
|
4087
|
+
$settings = New-ScheduledTaskSettingsSet -ExecutionTimeLimit ([TimeSpan]::Zero) -RestartCount 999 -RestartInterval (New-TimeSpan -Minutes 1) -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -StartWhenAvailable -MultipleInstances IgnoreNew
|
|
4088
|
+
Unregister-ScheduledTask -TaskName ${ps(WINDOWS_TASK)} -Confirm:$false -ErrorAction SilentlyContinue
|
|
4089
|
+
Register-ScheduledTask -TaskName ${ps(WINDOWS_TASK)} -Action $action -Trigger $trigger -Principal $principal -Settings $settings | Out-Null
|
|
4090
|
+
Start-ScheduledTask -TaskName ${ps(WINDOWS_TASK)}
|
|
4091
|
+
`
|
|
4092
|
+
});
|
|
4093
|
+
return {
|
|
4094
|
+
kind: "scheduled-task",
|
|
4095
|
+
files,
|
|
4096
|
+
commands: [{ file: "powershell.exe", args: ["-NoProfile", "-ExecutionPolicy", "Bypass", "-File", task], why: "register and start the Scheduled Task" }],
|
|
4097
|
+
notes: [
|
|
4098
|
+
...pinnedNodeNote(ctx),
|
|
4099
|
+
`Scheduled Task "${WINDOWS_TASK}" \u2014 starts at boot as ${principal || "this user"}, with nobody logged in. Registering it needs an elevated PowerShell: if it was refused, run ${task} from one.`,
|
|
4100
|
+
`Remove it with: pags up --uninstall-service`
|
|
4101
|
+
]
|
|
4102
|
+
};
|
|
4103
|
+
}
|
|
4104
|
+
const unit = posix.join(ctx.home, ".config", "systemd", "user", SYSTEMD_UNIT);
|
|
4105
|
+
files.push({
|
|
4106
|
+
path: unit,
|
|
4107
|
+
content: `# Written by \`pags up --install-service\` (#1141).
|
|
4108
|
+
[Unit]
|
|
4109
|
+
Description=ProAgentStore runner (pags up)
|
|
4110
|
+
After=network-online.target
|
|
4111
|
+
Wants=network-online.target
|
|
4112
|
+
StartLimitIntervalSec=0
|
|
4113
|
+
|
|
4114
|
+
[Service]
|
|
4115
|
+
ExecStart=${unitWord(ctx.nodePath)} ${unitWord(supervisor)}
|
|
4116
|
+
Environment=${unitWord(`PATH=${ctx.pathEnv}`)}
|
|
4117
|
+
Restart=always
|
|
4118
|
+
RestartSec=10
|
|
4119
|
+
KillMode=mixed
|
|
4120
|
+
|
|
4121
|
+
[Install]
|
|
4122
|
+
WantedBy=default.target
|
|
4123
|
+
`
|
|
4124
|
+
});
|
|
4125
|
+
return {
|
|
4126
|
+
kind: "systemd",
|
|
4127
|
+
files,
|
|
4128
|
+
commands: [
|
|
4129
|
+
{ file: "systemctl", args: ["--user", "daemon-reload"], why: "read the unit" },
|
|
4130
|
+
{ file: "systemctl", args: ["--user", "enable", "--now", SYSTEMD_UNIT], why: "enable and start it" },
|
|
4131
|
+
{ file: "loginctl", args: ["enable-linger", ...ctx.user ? [ctx.user] : []], optional: true, why: "keep it running with nobody logged in" }
|
|
4132
|
+
],
|
|
4133
|
+
notes: [...pinnedNodeNote(ctx), `systemd user unit ${unit}. Logs: ${log} (and journalctl --user -u ${SYSTEMD_UNIT}).`, `Remove it with: pags up --uninstall-service`]
|
|
4134
|
+
};
|
|
4135
|
+
}
|
|
4136
|
+
function serviceRemovalPlan(ctx) {
|
|
4137
|
+
const plan = servicePlan(ctx);
|
|
4138
|
+
const files = plan.files.map((f) => f.path);
|
|
4139
|
+
if (plan.kind === "launchd") return { files, commands: [{ file: "launchctl", args: ["bootout", `gui/${ctx.uid ?? 501}/${SERVICE_LABEL}`], optional: true, why: "unload the LaunchAgent" }] };
|
|
4140
|
+
if (plan.kind === "systemd") return { files, commands: [{ file: "systemctl", args: ["--user", "disable", "--now", SYSTEMD_UNIT], optional: true, why: "stop and disable the unit" }] };
|
|
4141
|
+
return {
|
|
4142
|
+
files,
|
|
4143
|
+
commands: [{ file: "powershell.exe", args: ["-NoProfile", "-Command", `Stop-ScheduledTask -TaskName ${ps(WINDOWS_TASK)} -ErrorAction SilentlyContinue; Unregister-ScheduledTask -TaskName ${ps(WINDOWS_TASK)} -Confirm:$false`], optional: true, why: "remove the Scheduled Task" }]
|
|
4144
|
+
};
|
|
4145
|
+
}
|
|
4146
|
+
var defaultWrite = (path, content) => {
|
|
4147
|
+
mkdirSync9(path.replace(/[\\/][^\\/]*$/, ""), { recursive: true });
|
|
4148
|
+
writeFileSync7(path, content);
|
|
4149
|
+
};
|
|
4150
|
+
var defaultRun = (file, args) => void execFileSync3(file, args, { stdio: "pipe", timeout: 6e4 });
|
|
4151
|
+
function installService(ctx, deps = {}) {
|
|
4152
|
+
const plan = servicePlan(ctx);
|
|
4153
|
+
const lines = [];
|
|
4154
|
+
for (const f of plan.files) {
|
|
4155
|
+
(deps.write ?? defaultWrite)(f.path, f.content);
|
|
4156
|
+
lines.push(`wrote ${f.path}`);
|
|
4157
|
+
}
|
|
4158
|
+
let ok = true;
|
|
4159
|
+
for (const c2 of plan.commands) {
|
|
4160
|
+
try {
|
|
4161
|
+
(deps.run ?? defaultRun)(c2.file, c2.args);
|
|
4162
|
+
lines.push(`${c2.why}: ok`);
|
|
4163
|
+
} catch (e) {
|
|
4164
|
+
const why = String(e.stderr ?? "").trim() || (e instanceof Error ? e.message : String(e));
|
|
4165
|
+
lines.push(`${c2.why}: ${c2.optional ? "skipped" : "FAILED"} \u2014 ${why.split(/\r?\n/)[0].slice(0, 200)}`);
|
|
4166
|
+
if (!c2.optional) ok = false;
|
|
4167
|
+
}
|
|
4168
|
+
}
|
|
4169
|
+
return { ok, lines: [...lines, ...plan.notes] };
|
|
4170
|
+
}
|
|
4171
|
+
function uninstallService(ctx, deps = {}) {
|
|
4172
|
+
const plan = serviceRemovalPlan(ctx);
|
|
4173
|
+
const lines = [];
|
|
4174
|
+
for (const c2 of plan.commands) {
|
|
4175
|
+
try {
|
|
4176
|
+
(deps.run ?? defaultRun)(c2.file, c2.args);
|
|
4177
|
+
lines.push(`${c2.why}: ok`);
|
|
4178
|
+
} catch (e) {
|
|
4179
|
+
lines.push(`${c2.why}: skipped \u2014 ${(e instanceof Error ? e.message : String(e)).split(/\r?\n/)[0].slice(0, 200)}`);
|
|
4180
|
+
}
|
|
4181
|
+
}
|
|
4182
|
+
for (const f of plan.files) {
|
|
4183
|
+
(deps.remove ?? ((p) => rmSync(p, { force: true })))(f);
|
|
4184
|
+
lines.push(`removed ${f}`);
|
|
4185
|
+
}
|
|
4186
|
+
return { lines };
|
|
4187
|
+
}
|
|
4188
|
+
|
|
3853
4189
|
// src/commands/up.ts
|
|
3854
4190
|
var API_BASE3 = "https://api.proagentstore.online";
|
|
3855
4191
|
var CLI_VERSION2 = createRequire2(import.meta.url)("../package.json").version;
|
|
3856
|
-
var upCommand = new Command10("up").description("Start the browser runner for all your agent instances").option("--headless", "Run browser in headless mode").option("--instance <id>", "Connect to a specific instance only").option("--force", "Take over from another connected machine").option("--replace", "Stop the `pags up` already running here for this account, then take over").option("--now", "With --replace: stop it even though work is running on it (that work is lost)").option("--background", "Run the runner in the background, with no terminal open (stop it with `pags down`)").action(async (opts) => {
|
|
4192
|
+
var upCommand = new Command10("up").description("Start the browser runner for all your agent instances").option("--headless", "Run browser in headless mode").option("--instance <id>", "Connect to a specific instance only").option("--force", "Take over from another connected machine").option("--replace", "Stop the `pags up` already running here for this account, then take over").option("--now", "With --replace: stop it even though work is running on it (that work is lost)").option("--background", "Run the runner in the background, with no terminal open (stop it with `pags down`)").option("--install-service", "Install an OS service that keeps `pags up` running (launchd, systemd or a Scheduled Task), then exit").option("--uninstall-service", "Remove the service --install-service installed, then exit").action(async (opts) => {
|
|
4193
|
+
if (opts.uninstallService) {
|
|
4194
|
+
for (const line of uninstallService(serviceContext()).lines) writeLine(line);
|
|
4195
|
+
return;
|
|
4196
|
+
}
|
|
4197
|
+
if (!opts.background && !opts.installService && upRestartMode(process.env) === "launcher") {
|
|
4198
|
+
restartPagsUp(process.argv.slice(2), {}, { ...processRestartDeps(), log: (line) => writeLine(` ${line}`) }, { initial: true });
|
|
4199
|
+
return;
|
|
4200
|
+
}
|
|
3857
4201
|
const session = requireSession();
|
|
4202
|
+
if (opts.installService) {
|
|
4203
|
+
const result = installService(serviceContext());
|
|
4204
|
+
for (const line of result.lines) writeLine(line);
|
|
4205
|
+
writeLine(result.ok ? "The service starts `pags up` now and whenever it exits. A `pags up` already running here keeps the lock; the service takes over once it stops." : "The service was not fully installed \u2014 see the FAILED line above.");
|
|
4206
|
+
if (!result.ok) process.exitCode = 1;
|
|
4207
|
+
return;
|
|
4208
|
+
}
|
|
3858
4209
|
const state = {
|
|
3859
4210
|
user: session.user.login,
|
|
3860
4211
|
instances: [],
|
|
@@ -3871,18 +4222,34 @@ var upCommand = new Command10("up").description("Start the browser runner for al
|
|
|
3871
4222
|
// as taking a few seconds, indefinitely, because nothing counted (#497).
|
|
3872
4223
|
startedAt: Date.now()
|
|
3873
4224
|
};
|
|
4225
|
+
const unattended = process.env.PAGS_NO_PROMPT === "1";
|
|
4226
|
+
logUpdateStep(`start: pags up ${CLI_VERSION2}${unattended ? " (unattended)" : ""}`);
|
|
3874
4227
|
clearScreen();
|
|
3875
4228
|
printLogo(CLI_VERSION2);
|
|
3876
4229
|
printStep("Signed in as " + session.user.login, "ok");
|
|
3877
4230
|
printStep("Fetching instances...", "wait");
|
|
3878
|
-
|
|
3879
|
-
|
|
3880
|
-
|
|
3881
|
-
|
|
3882
|
-
|
|
3883
|
-
|
|
4231
|
+
let data;
|
|
4232
|
+
for (let failures = 1; ; failures++) {
|
|
4233
|
+
let failure;
|
|
4234
|
+
try {
|
|
4235
|
+
const res = await fetch(`${API_BASE3}/v1/instances/my/instances`, { headers: { Authorization: `Bearer ${session.token}` } });
|
|
4236
|
+
if (res.ok) {
|
|
4237
|
+
data = await res.json();
|
|
4238
|
+
break;
|
|
4239
|
+
}
|
|
4240
|
+
failure = `HTTP ${res.status}`;
|
|
4241
|
+
} catch (e) {
|
|
4242
|
+
failure = e instanceof Error ? e.message : String(e);
|
|
4243
|
+
}
|
|
4244
|
+
logUpdateStep(`start: fetching instances failed (${failure})${unattended ? "" : " \u2014 exiting"}`);
|
|
4245
|
+
if (!unattended) {
|
|
4246
|
+
printStep("Failed to fetch instances: " + failure, "fail");
|
|
4247
|
+
process.exit(1);
|
|
4248
|
+
}
|
|
4249
|
+
const wait = Math.min(5 * 6e4, 5e3 * 2 ** (failures - 1));
|
|
4250
|
+
printStep(`Failed to fetch instances (${failure}) \u2014 retrying in ${Math.round(wait / 1e3)}s`, "wait");
|
|
4251
|
+
await new Promise((resolve5) => setTimeout(resolve5, wait));
|
|
3884
4252
|
}
|
|
3885
|
-
const data = await res.json();
|
|
3886
4253
|
const active = (data.instances || []).filter((i) => i.status === "active");
|
|
3887
4254
|
let instances = active;
|
|
3888
4255
|
let elsewhere = [];
|
|
@@ -3930,9 +4297,10 @@ var upCommand = new Command10("up").description("Start the browser runner for al
|
|
|
3930
4297
|
state.activeInstance = instances.length === 1 ? instances[0].name || instances[0].slug || instances[0].id.slice(0, 8) : `${instances.length} agents`;
|
|
3931
4298
|
printStep(`Connecting ${state.activeInstance}\u2026`, "wait");
|
|
3932
4299
|
const lockFile = lockPath(session.user.login);
|
|
3933
|
-
if (opts.background && process.env[BACKGROUND_RESTART_ENV] === "1") await waitUntilTakeable(() => inspectHolder(lockFile));
|
|
4300
|
+
if (unattended || opts.background && process.env[BACKGROUND_RESTART_ENV] === "1") await waitUntilTakeable(() => inspectHolder(lockFile), { timeoutMs: 6e4 });
|
|
3934
4301
|
const held = await inspectHolder(lockFile);
|
|
3935
4302
|
if (held.lock && !isTakeable(held.verdict)) {
|
|
4303
|
+
logUpdateStep(`start: the lock is held by pid ${held.lock.pid} (${held.verdict})${opts.replace ? " \u2014 replacing it" : " \u2014 exiting"}`);
|
|
3936
4304
|
if (!opts.replace) {
|
|
3937
4305
|
clearScreen();
|
|
3938
4306
|
printLogo(CLI_VERSION2);
|
|
@@ -4036,31 +4404,29 @@ var upCommand = new Command10("up").description("Start the browser runner for al
|
|
|
4036
4404
|
void deliverCrashReports({ apiBase: API_BASE3, token: session.token, account: session.user.login, paths: crashes }).catch(() => void 0);
|
|
4037
4405
|
};
|
|
4038
4406
|
child.on("exit", (code, signal) => {
|
|
4039
|
-
|
|
4407
|
+
const action = runnerExitAction(code, stopping);
|
|
4408
|
+
if (action === "restart") {
|
|
4409
|
+
logUpdateStep("restart: runner connect asked for a restart (exit 75)");
|
|
4040
4410
|
restartUp("Runner updated remotely \u2014 restarting pags up on the new version\u2026", { PAGS_NO_PROMPT: "1" });
|
|
4041
4411
|
return;
|
|
4042
4412
|
}
|
|
4043
4413
|
childDead = true;
|
|
4044
|
-
|
|
4045
|
-
if (
|
|
4046
|
-
|
|
4047
|
-
|
|
4048
|
-
|
|
4049
|
-
|
|
4050
|
-
}
|
|
4051
|
-
if (code !== 0) {
|
|
4052
|
-
state.runner = "error";
|
|
4053
|
-
state.lastEvent = `Runner exited (code ${code})`;
|
|
4054
|
-
const recent = logs.slice(-5);
|
|
4055
|
-
if (recent.length) state.lastEvent += ": " + recent[recent.length - 1].slice(0, 60);
|
|
4056
|
-
printStatus(state);
|
|
4414
|
+
logUpdateStep(`runner connect ${signal ? `was killed by ${signal}` : `exited ${code}`}${stopping ? " (stopping)" : ""}`);
|
|
4415
|
+
if (action === "ignore") return;
|
|
4416
|
+
if (action === "stop") {
|
|
4417
|
+
clearScreen();
|
|
4418
|
+
writeLine(" Runner stopped (pags down, or replaced by another pags up).");
|
|
4419
|
+
process.exit(0);
|
|
4057
4420
|
}
|
|
4421
|
+
recordConnectExit(code, signal ?? null, logs.slice(-20));
|
|
4422
|
+
const recent = logs.slice(-5).at(-1);
|
|
4423
|
+
restartUp(`Runner ${signal ? `was killed by ${signal}` : `exited (code ${code})`}${recent ? `: ${recent.slice(0, 60)}` : ""} \u2014 starting it again\u2026`, { PAGS_NO_PROMPT: "1" }, RUNNER_DIED_EXIT_CODE);
|
|
4058
4424
|
});
|
|
4059
|
-
function restartUp(why, env = {}) {
|
|
4425
|
+
function restartUp(why, env = {}, exitCode) {
|
|
4060
4426
|
writeLine(` ${why}`);
|
|
4061
4427
|
if (process.stdin.isTTY) process.stdin.setRawMode(false);
|
|
4062
4428
|
process.stdin.pause();
|
|
4063
|
-
restartPagsUp(restartUpArgs(opts), env, { ...processRestartDeps(), log: (line) => writeLine(` ${line}`) });
|
|
4429
|
+
restartPagsUp(restartUpArgs(opts), env, { ...processRestartDeps(), log: (line) => writeLine(` ${line}`) }, { exitCode });
|
|
4064
4430
|
process.exit(1);
|
|
4065
4431
|
}
|
|
4066
4432
|
const shutdown = () => {
|
|
@@ -4125,6 +4491,26 @@ var downCommand = new Command10("down").description("Stop the browser runner and
|
|
|
4125
4491
|
}
|
|
4126
4492
|
writeLine("");
|
|
4127
4493
|
});
|
|
4494
|
+
function serviceContext() {
|
|
4495
|
+
const user = (() => {
|
|
4496
|
+
try {
|
|
4497
|
+
return userInfo().username;
|
|
4498
|
+
} catch {
|
|
4499
|
+
return process.env.USER || process.env.USERNAME || void 0;
|
|
4500
|
+
}
|
|
4501
|
+
})();
|
|
4502
|
+
return {
|
|
4503
|
+
platform: process.platform,
|
|
4504
|
+
home: homedir5(),
|
|
4505
|
+
nodePath: process.execPath,
|
|
4506
|
+
script: process.argv[1],
|
|
4507
|
+
configDir: configDir(),
|
|
4508
|
+
pathEnv: process.env.PATH ?? "",
|
|
4509
|
+
uid: process.getuid?.(),
|
|
4510
|
+
user,
|
|
4511
|
+
domain: process.env.USERDOMAIN
|
|
4512
|
+
};
|
|
4513
|
+
}
|
|
4128
4514
|
|
|
4129
4515
|
// src/index.ts
|
|
4130
4516
|
var require2 = createRequire3(import.meta.url);
|