@0xmaxma/claude-gateway 1.8.2 → 1.8.3
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/README.md +7 -2
- package/config.template.json +4 -0
- package/dist/agent/dreaming/config.d.ts +6 -0
- package/dist/agent/dreaming/config.d.ts.map +1 -1
- package/dist/agent/dreaming/config.js +17 -24
- package/dist/agent/dreaming/config.js.map +1 -1
- package/dist/agent/knowledge/config.d.ts.map +1 -1
- package/dist/agent/knowledge/config.js +7 -13
- package/dist/agent/knowledge/config.js.map +1 -1
- package/dist/agent/runner.d.ts +87 -13
- package/dist/agent/runner.d.ts.map +1 -1
- package/dist/agent/runner.js +342 -117
- package/dist/agent/runner.js.map +1 -1
- package/dist/agent/turn-stream.d.ts +202 -0
- package/dist/agent/turn-stream.d.ts.map +1 -0
- package/dist/agent/turn-stream.js +322 -0
- package/dist/agent/turn-stream.js.map +1 -0
- package/dist/api/apps-router.d.ts.map +1 -1
- package/dist/api/apps-router.js +14 -2
- package/dist/api/apps-router.js.map +1 -1
- package/dist/api/gateway-router.d.ts.map +1 -1
- package/dist/api/gateway-router.js +2 -3
- package/dist/api/gateway-router.js.map +1 -1
- package/dist/api/line-webhook-router.d.ts +43 -1
- package/dist/api/line-webhook-router.d.ts.map +1 -1
- package/dist/api/line-webhook-router.js +286 -38
- package/dist/api/line-webhook-router.js.map +1 -1
- package/dist/api/router.d.ts +7 -1
- package/dist/api/router.d.ts.map +1 -1
- package/dist/api/router.js +328 -114
- package/dist/api/router.js.map +1 -1
- package/dist/apps/agent-manager.d.ts.map +1 -1
- package/dist/apps/agent-manager.js +4 -1
- package/dist/apps/agent-manager.js.map +1 -1
- package/dist/apps/installer.d.ts +75 -2
- package/dist/apps/installer.d.ts.map +1 -1
- package/dist/apps/installer.js +198 -13
- package/dist/apps/installer.js.map +1 -1
- package/dist/config/agent-env.d.ts +41 -0
- package/dist/config/agent-env.d.ts.map +1 -0
- package/dist/config/agent-env.js +154 -0
- package/dist/config/agent-env.js.map +1 -0
- package/dist/config/loader.d.ts +33 -2
- package/dist/config/loader.d.ts.map +1 -1
- package/dist/config/loader.js +33 -9
- package/dist/config/loader.js.map +1 -1
- package/dist/config/watcher.d.ts +1 -0
- package/dist/config/watcher.d.ts.map +1 -1
- package/dist/config/watcher.js +17 -1
- package/dist/config/watcher.js.map +1 -1
- package/dist/index.js +24 -51
- package/dist/index.js.map +1 -1
- package/dist/load-dotenv.d.ts +17 -0
- package/dist/load-dotenv.d.ts.map +1 -1
- package/dist/load-dotenv.js +32 -9
- package/dist/load-dotenv.js.map +1 -1
- package/dist/session/store.d.ts +9 -0
- package/dist/session/store.d.ts.map +1 -1
- package/dist/session/store.js +12 -0
- package/dist/session/store.js.map +1 -1
- package/dist/shell/bypass-dialog.d.ts +142 -0
- package/dist/shell/bypass-dialog.d.ts.map +1 -0
- package/dist/shell/bypass-dialog.js +207 -0
- package/dist/shell/bypass-dialog.js.map +1 -0
- package/dist/shell/claude-pty-shell.js +42 -3
- package/dist/shell/claude-pty-shell.js.map +1 -1
- package/dist/shell/screen.d.ts +7 -0
- package/dist/shell/screen.d.ts.map +1 -1
- package/dist/shell/screen.js +10 -1
- package/dist/shell/screen.js.map +1 -1
- package/dist/types.d.ts +20 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/config-num.d.ts +22 -0
- package/dist/utils/config-num.d.ts.map +1 -0
- package/dist/utils/config-num.js +34 -0
- package/dist/utils/config-num.js.map +1 -0
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agent-manager.d.ts","sourceRoot":"","sources":["../../src/apps/agent-manager.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"agent-manager.d.ts","sourceRoot":"","sources":["../../src/apps/agent-manager.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAMpD,MAAM,WAAW,UAAU;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;CACjB;AAiBD,qBAAa,YAAY;IAKrB,OAAO,CAAC,QAAQ,CAAC,UAAU;IAC3B,OAAO,CAAC,QAAQ,CAAC,SAAS;IAL5B,qEAAqE;IACrE,OAAO,CAAC,UAAU,CAAoC;gBAGnC,UAAU,GAAE,MAA4B,EACxC,SAAS,GAAE,MAA2B;YAG3C,cAAc;IAc5B;;;;;;;;;;OAUG;IACH,gBAAgB,IAAI,UAAU;IAgB9B;;;;;;OAMG;IACH,kBAAkB,CAAC,KAAK,EAAE,QAAQ,GAAG,IAAI;IAqFzC;;;;;;;OAOG;IACG,WAAW,CAAC,KAAK,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC;IAqCjD;;;OAGG;IACG,WAAW,CAAC,KAAK,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC;IAKjD;;;;OAIG;IACG,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAYzD;;;;OAIG;IACG,eAAe,CAAC,QAAQ,EAAE,YAAY,GAAG,OAAO,CAAC,KAAK,CAAC;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAe7F;;;OAGG;IACH,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IAU9C;;OAEG;IACH,aAAa,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI;IAMvD,OAAO,CAAC,gBAAgB;IAMxB;;;OAGG;IACG,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAYhE,OAAO,CAAC,UAAU;IAWlB,OAAO,CAAC,WAAW;YAOL,iBAAiB;YAmBjB,iBAAiB;CAWhC"}
|
|
@@ -43,9 +43,12 @@ const os = __importStar(require("node:os"));
|
|
|
43
43
|
const node_child_process_1 = require("node:child_process");
|
|
44
44
|
const js_yaml_1 = __importDefault(require("js-yaml"));
|
|
45
45
|
const claude_bin_1 = require("../session/claude-bin");
|
|
46
|
+
const agent_env_1 = require("../config/agent-env");
|
|
46
47
|
// ─── Constants ────────────────────────────────────────────────────────────────
|
|
47
48
|
const DEFAULT_CONFIG_PATH = path.join(os.homedir(), '.claude-gateway', 'config.json');
|
|
48
|
-
|
|
49
|
+
// Derived, not declared independently: the agents dir is always config.json's
|
|
50
|
+
// sibling, and the two must not be able to drift apart.
|
|
51
|
+
const DEFAULT_AGENTS_DIR = (0, agent_env_1.agentsDirForConfig)(DEFAULT_CONFIG_PATH);
|
|
49
52
|
// ─── AgentManager ────────────────────────────────────────────────────────────
|
|
50
53
|
class AgentManager {
|
|
51
54
|
constructor(configPath = DEFAULT_CONFIG_PATH, agentsDir = DEFAULT_AGENTS_DIR) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agent-manager.js","sourceRoot":"","sources":["../../src/apps/agent-manager.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,4CAA8B;AAC9B,gDAAkC;AAClC,4CAA8B;AAC9B,2DAA8C;AAC9C,sDAA2B;AAE3B,sDAA0D;
|
|
1
|
+
{"version":3,"file":"agent-manager.js","sourceRoot":"","sources":["../../src/apps/agent-manager.ts"],"names":[],"mappings":"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"}
|
package/dist/apps/installer.d.ts
CHANGED
|
@@ -123,6 +123,26 @@ export interface AppHousekeepingConfig {
|
|
|
123
123
|
buildCacheMaxAgeHours?: number;
|
|
124
124
|
danglingImagePrune?: boolean;
|
|
125
125
|
}
|
|
126
|
+
/**
|
|
127
|
+
* Boot-restore budgets (issue #425). Mirrors `GatewayConfig['gateway']['appRestore']`;
|
|
128
|
+
* all fields optional so an absent config resolves to {@link RESTORE_BUILD_TIMEOUT_MS}
|
|
129
|
+
* / {@link RESTORE_COMPOSE_TIMEOUT_MS}. Split because the two phases have very
|
|
130
|
+
* different shapes: the healthcheck wait should stay short, while a cold build
|
|
131
|
+
* legitimately takes minutes and is *cancelled* if its budget expires.
|
|
132
|
+
*/
|
|
133
|
+
export interface AppRestoreConfig {
|
|
134
|
+
/** Ceiling for the restore's `docker compose build`. Default 1_800_000 (30 min). */
|
|
135
|
+
buildTimeoutMs?: number;
|
|
136
|
+
/** Ceiling for the restore's `docker compose up -d --wait`. Default 180_000 (3 min). */
|
|
137
|
+
waitTimeoutMs?: number;
|
|
138
|
+
}
|
|
139
|
+
/** Why an app's boot-time restore failed, and when. See {@link AppInstaller.getRestoreFailure}. */
|
|
140
|
+
export interface RestoreFailure {
|
|
141
|
+
/** The underlying error message (compose output tail, or the timeout). */
|
|
142
|
+
error: string;
|
|
143
|
+
/** ISO timestamp of the failure. */
|
|
144
|
+
at: string;
|
|
145
|
+
}
|
|
126
146
|
/** Read-only reclaim report (issue #302). Volumes are reported, never deleted. */
|
|
127
147
|
export interface HousekeepingReport {
|
|
128
148
|
/** Human-readable reclaimable build cache from `docker system df` (e.g. "1.457GB"); '' if unknown. */
|
|
@@ -212,7 +232,20 @@ export declare class AppInstaller {
|
|
|
212
232
|
private readonly appBackupConfig;
|
|
213
233
|
/** Tracks app names currently being installed to prevent concurrent installs of the same name. */
|
|
214
234
|
private readonly installingNames;
|
|
215
|
-
|
|
235
|
+
/**
|
|
236
|
+
* App names whose boot-time restore is in flight. Mirrors {@link installingNames}
|
|
237
|
+
* for {@link restoreRunningApps}: while a restore owns an app, its containers do
|
|
238
|
+
* not exist yet, so status reads must not reconcile it (issue #425).
|
|
239
|
+
*/
|
|
240
|
+
private readonly restoringNames;
|
|
241
|
+
/**
|
|
242
|
+
* App name → why this boot's restore of it failed. In-memory on purpose: it
|
|
243
|
+
* describes the *current* process's restore attempt, so a gateway restart
|
|
244
|
+
* (which retries the restore) correctly starts from a clean slate.
|
|
245
|
+
*/
|
|
246
|
+
private readonly restoreFailures;
|
|
247
|
+
private readonly restoreConfig;
|
|
248
|
+
constructor(registry: AppsRegistry, registryClient: RegistryClient, callbacks: InstallerCallbacks, spawn?: SpawnFn, appsDir?: string, agentManager?: AgentManager | undefined, spawnAsync?: AsyncSpawnFn, housekeepingConfig?: AppHousekeepingConfig, appBackupConfig?: AppBackupConfig, backupsDir?: string, appRestoreConfig?: AppRestoreConfig);
|
|
216
249
|
/** Start an async install job. Returns jobId immediately. */
|
|
217
250
|
install(options: InstallOptions): string;
|
|
218
251
|
getJob(jobId: string): JobState | undefined;
|
|
@@ -367,7 +400,8 @@ export declare class AppInstaller {
|
|
|
367
400
|
*
|
|
368
401
|
* When the live status differs from the stored one it is persisted back to
|
|
369
402
|
* `apps.json` before returning, so subsequent reads and the boot-time restore
|
|
370
|
-
* see the truth.
|
|
403
|
+
* see the truth. The one exception is an app whose boot restore failed: see
|
|
404
|
+
* {@link restoreRunningApps} for why that must not be written down.
|
|
371
405
|
*/
|
|
372
406
|
reconcileStatus(entry: AppEntry): Promise<AppEntry>;
|
|
373
407
|
/** Reconcile a list of entries against the Docker runtime, in parallel. See {@link reconcileStatus}. */
|
|
@@ -402,6 +436,12 @@ export declare class AppInstaller {
|
|
|
402
436
|
* Best-effort and non-fatal — a failure for one app is collected and the rest
|
|
403
437
|
* still proceed, so one broken app cannot block the others or gateway startup.
|
|
404
438
|
* Returns the apps that failed to start (and the count attempted, for logging).
|
|
439
|
+
*
|
|
440
|
+
* Each app is marked in-flight for the duration ({@link restoringNames}) so a
|
|
441
|
+
* concurrent status read cannot persist `stopped` underneath it, and a failure
|
|
442
|
+
* is recorded ({@link getRestoreFailure}) so it is visible through the apps API
|
|
443
|
+
* rather than only in the log, and so reconcileStatus() keeps the app eligible
|
|
444
|
+
* for the next boot's restore instead of latching it off (issue #425).
|
|
405
445
|
*/
|
|
406
446
|
restoreRunningApps(): Promise<{
|
|
407
447
|
attempted: number;
|
|
@@ -410,6 +450,13 @@ export declare class AppInstaller {
|
|
|
410
450
|
error: string;
|
|
411
451
|
}>;
|
|
412
452
|
}>;
|
|
453
|
+
/**
|
|
454
|
+
* Why this boot's restore of `appName` failed, or `undefined` when the last
|
|
455
|
+
* restore succeeded (or never ran). Lets `GET /api/v1/apps` tell an app the
|
|
456
|
+
* gateway tried and failed to start apart from one an operator stopped —
|
|
457
|
+
* previously the difference existed only in the boot log.
|
|
458
|
+
*/
|
|
459
|
+
getRestoreFailure(appName: string): RestoreFailure | undefined;
|
|
413
460
|
private runInstall;
|
|
414
461
|
private runUpdate;
|
|
415
462
|
/**
|
|
@@ -560,8 +607,34 @@ export declare class AppInstaller {
|
|
|
560
607
|
* dev-time port clashes during install/update, but after a host reboot nothing
|
|
561
608
|
* is running (the very reason this restore exists), so there is nothing to
|
|
562
609
|
* conflict with — and keeping it out avoids extra synchronous docker calls.
|
|
610
|
+
*
|
|
611
|
+
* Runs the cold-start steps and `up` under two budgets, but only pays for the
|
|
612
|
+
* cold path when the host actually lacks the images. `up` alone would pull and
|
|
613
|
+
* build whatever is missing, and then that work shares the healthcheck-wait
|
|
614
|
+
* budget — on a host with no image cache (data dir restored onto a fresh
|
|
615
|
+
* machine, migrated host, pruned Docker) that budget expires mid-build, at
|
|
616
|
+
* which point the SIGKILL in {@link defaultAsyncSpawn} *cancels* the build and
|
|
617
|
+
* leaves no image and no container (issue #425).
|
|
563
618
|
*/
|
|
564
619
|
private composeUpAsync;
|
|
620
|
+
/**
|
|
621
|
+
* Run one best-effort cold-start step (`pull` / `build`) under the build
|
|
622
|
+
* budget. Errors are swallowed on purpose so `up` stays the single authority
|
|
623
|
+
* on whether the app starts: it re-does whatever is still missing and reports
|
|
624
|
+
* the real failure together with the container logs.
|
|
625
|
+
*/
|
|
626
|
+
private tryColdStart;
|
|
627
|
+
/**
|
|
628
|
+
* True when at least one image the compose project needs is absent from the
|
|
629
|
+
* local daemon — the cold-host case that must build before it can start.
|
|
630
|
+
*
|
|
631
|
+
* Every failure path here answers `false`. "No signal" must mean "skip the
|
|
632
|
+
* pre-build", which leaves {@link composeUpAsync} behaving exactly as it did
|
|
633
|
+
* before the build step existed — `up` still builds whatever is missing. The
|
|
634
|
+
* opposite default would rebuild apps that only needed starting, which is the
|
|
635
|
+
* cost this probe exists to avoid.
|
|
636
|
+
*/
|
|
637
|
+
private needsImageBuild;
|
|
565
638
|
/** Async equivalent of {@link run}: throws on non-zero exit. */
|
|
566
639
|
private runAsync;
|
|
567
640
|
/** Evict terminal jobs older than 24 hours to bound memory growth. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"installer.d.ts","sourceRoot":"","sources":["../../src/apps/installer.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,kCAAkC,EAAoB,MAAM,oBAAoB,CAAC;AAC1F,OAAO,EAAE,YAAY,EAAE,QAAQ,EAAa,MAAM,YAAY,CAAC;AAC/D,OAAO,EAAE,cAAc,EAAmB,MAAM,mBAAmB,CAAC;AACpE,OAAO,EAIL,WAAW,EACX,aAAa,EACb,YAAY,EAEZ,gBAAgB,EACjB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"installer.d.ts","sourceRoot":"","sources":["../../src/apps/installer.ts"],"names":[],"mappings":"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"}
|
package/dist/apps/installer.js
CHANGED
|
@@ -45,6 +45,7 @@ const crypto = __importStar(require("node:crypto"));
|
|
|
45
45
|
const node_child_process_1 = require("node:child_process");
|
|
46
46
|
const compose_generator_1 = require("./compose-generator");
|
|
47
47
|
const cleanup_1 = require("../history/cleanup");
|
|
48
|
+
const config_num_1 = require("../utils/config-num");
|
|
48
49
|
// ─── Constants ────────────────────────────────────────────────────────────────
|
|
49
50
|
const DEFAULT_APPS_DIR = path.join(os.homedir(), '.claude-gateway', 'apps');
|
|
50
51
|
// Backups live under `<appsDir>/.backups/<app>/`. A dot-prefixed dir here is
|
|
@@ -73,9 +74,23 @@ const BACKUP_HELPER_IMAGE = 'alpine';
|
|
|
73
74
|
const BIND_MOVE_TIMEOUT_MS = 120000;
|
|
74
75
|
// Per-app ceiling for the boot-time `compose up --wait` during restore. Runs in
|
|
75
76
|
// the background (non-blocking), so this only bounds how long a hung container
|
|
76
|
-
// keeps its child process alive — not the gateway's responsiveness.
|
|
77
|
-
// the
|
|
77
|
+
// keeps its child process alive — not the gateway's responsiveness. Deliberately
|
|
78
|
+
// short: it bounds the *healthcheck wait* only. The build that may precede it on
|
|
79
|
+
// a cold host gets its own, far larger budget below — sizing this one to cover a
|
|
80
|
+
// from-source build would also make every hung container hold on for that long.
|
|
78
81
|
const RESTORE_COMPOSE_TIMEOUT_MS = 180000;
|
|
82
|
+
// Per-app ceiling for the boot-time `compose build` that precedes the wait.
|
|
83
|
+
// Applies when the host has no image cache for the app (data dir restored onto a
|
|
84
|
+
// fresh machine, migrated host, pruned Docker state), where the restore has to
|
|
85
|
+
// cover base-image pulls, a dependency install and an application build — for up
|
|
86
|
+
// to RESTORE_MAX_CONCURRENCY apps competing for the same CPU. A timeout here
|
|
87
|
+
// SIGKILLs the CLI, which *cancels* the build (see {@link defaultAsyncSpawn}),
|
|
88
|
+
// so this must be generous: an expired budget means the app is never built.
|
|
89
|
+
const RESTORE_BUILD_TIMEOUT_MS = 1800000;
|
|
90
|
+
// Ceiling for the read-only probe that decides whether a build is needed at all.
|
|
91
|
+
// Two local daemon queries, no network and no work — generous on purpose so a
|
|
92
|
+
// momentarily busy dockerd does not make the restore skip a build it needs.
|
|
93
|
+
const IMAGE_PROBE_TIMEOUT_MS = 30000;
|
|
79
94
|
// Max apps brought up concurrently during boot restore. Bounds the docker/CPU
|
|
80
95
|
// spike when many apps are marked running, while still parallelising the common
|
|
81
96
|
// case. Typical installs have 1-3 apps, so this rarely binds.
|
|
@@ -187,7 +202,7 @@ function parseJsonRows(stdout) {
|
|
|
187
202
|
/** Private tag the update holds on an image so a rollback can put it back. */
|
|
188
203
|
const ROLLBACK_TAG_PREFIX = 'cg-rollback-';
|
|
189
204
|
class AppInstaller {
|
|
190
|
-
constructor(registry, registryClient, callbacks, spawn = defaultSpawn, appsDir, agentManager, spawnAsync = defaultAsyncSpawn, housekeepingConfig = {}, appBackupConfig, backupsDir) {
|
|
205
|
+
constructor(registry, registryClient, callbacks, spawn = defaultSpawn, appsDir, agentManager, spawnAsync = defaultAsyncSpawn, housekeepingConfig = {}, appBackupConfig, backupsDir, appRestoreConfig) {
|
|
191
206
|
this.registry = registry;
|
|
192
207
|
this.registryClient = registryClient;
|
|
193
208
|
this.callbacks = callbacks;
|
|
@@ -198,8 +213,24 @@ class AppInstaller {
|
|
|
198
213
|
this.jobs = new Map();
|
|
199
214
|
/** Tracks app names currently being installed to prevent concurrent installs of the same name. */
|
|
200
215
|
this.installingNames = new Set();
|
|
216
|
+
/**
|
|
217
|
+
* App names whose boot-time restore is in flight. Mirrors {@link installingNames}
|
|
218
|
+
* for {@link restoreRunningApps}: while a restore owns an app, its containers do
|
|
219
|
+
* not exist yet, so status reads must not reconcile it (issue #425).
|
|
220
|
+
*/
|
|
221
|
+
this.restoringNames = new Set();
|
|
222
|
+
/**
|
|
223
|
+
* App name → why this boot's restore of it failed. In-memory on purpose: it
|
|
224
|
+
* describes the *current* process's restore attempt, so a gateway restart
|
|
225
|
+
* (which retries the restore) correctly starts from a clean slate.
|
|
226
|
+
*/
|
|
227
|
+
this.restoreFailures = new Map();
|
|
201
228
|
this.appsDir = appsDir ?? DEFAULT_APPS_DIR;
|
|
202
229
|
this.backupsDir = backupsDir ?? path.join(this.appsDir, APP_BACKUPS_DIRNAME);
|
|
230
|
+
this.restoreConfig = {
|
|
231
|
+
buildTimeoutMs: (0, config_num_1.msOr)(appRestoreConfig?.buildTimeoutMs, RESTORE_BUILD_TIMEOUT_MS),
|
|
232
|
+
waitTimeoutMs: (0, config_num_1.msOr)(appRestoreConfig?.waitTimeoutMs, RESTORE_COMPOSE_TIMEOUT_MS),
|
|
233
|
+
};
|
|
203
234
|
this.appBackupConfig = {
|
|
204
235
|
retention: appBackupConfig?.retention !== undefined && appBackupConfig.retention >= 0
|
|
205
236
|
? Math.floor(appBackupConfig.retention)
|
|
@@ -449,6 +480,9 @@ class AppInstaller {
|
|
|
449
480
|
}
|
|
450
481
|
}
|
|
451
482
|
await this.registry.remove(appName);
|
|
483
|
+
// The app is gone — drop its restore marker so a later install of the same
|
|
484
|
+
// name does not inherit a failure that belonged to the previous one.
|
|
485
|
+
this.restoreFailures.delete(appName);
|
|
452
486
|
}
|
|
453
487
|
// ─── Backup / Restore ───────────────────────────────────────────────────────
|
|
454
488
|
/**
|
|
@@ -1047,6 +1081,11 @@ class AppInstaller {
|
|
|
1047
1081
|
this.composeUp(appName, entry.installPath);
|
|
1048
1082
|
await this.registry.updateStatus(appName, 'running');
|
|
1049
1083
|
}
|
|
1084
|
+
// An explicit operator action supersedes this boot's restore attempt: a
|
|
1085
|
+
// stop is a deliberate `stopped`, a start replaced the failed one. Only
|
|
1086
|
+
// cleared on success — if the action threw, the app is still in the state
|
|
1087
|
+
// the failed restore left it in and stays eligible for the next retry.
|
|
1088
|
+
this.restoreFailures.delete(appName);
|
|
1050
1089
|
}
|
|
1051
1090
|
finally {
|
|
1052
1091
|
this.installingNames.delete(appName);
|
|
@@ -1068,12 +1107,30 @@ class AppInstaller {
|
|
|
1068
1107
|
*
|
|
1069
1108
|
* When the live status differs from the stored one it is persisted back to
|
|
1070
1109
|
* `apps.json` before returning, so subsequent reads and the boot-time restore
|
|
1071
|
-
* see the truth.
|
|
1110
|
+
* see the truth. The one exception is an app whose boot restore failed: see
|
|
1111
|
+
* {@link restoreRunningApps} for why that must not be written down.
|
|
1072
1112
|
*/
|
|
1073
1113
|
async reconcileStatus(entry) {
|
|
1074
1114
|
const live = await this.queryRuntimeStatus(entry);
|
|
1115
|
+
// Up and serving — whatever went wrong at boot has since been resolved
|
|
1116
|
+
// (dockerd finished, an operator started it), so drop the stale marker.
|
|
1117
|
+
if (live === 'running')
|
|
1118
|
+
this.restoreFailures.delete(entry.name);
|
|
1075
1119
|
if (live === entry.status)
|
|
1076
1120
|
return entry;
|
|
1121
|
+
// A failed boot restore must NOT rewrite `running` → `stopped`.
|
|
1122
|
+
// restoreRunningApps() only considers entries stored as `running`, so
|
|
1123
|
+
// persisting `stopped` here is exactly what excludes the app from every
|
|
1124
|
+
// future boot restore — one slow cold build would latch it off for good
|
|
1125
|
+
// (issue #425). Report `error` instead: honest (it is not serving), and
|
|
1126
|
+
// distinguishable from a deliberate stop — GET /api/v1/apps pairs it with
|
|
1127
|
+
// the reason from {@link getRestoreFailure} — while the stored `running`
|
|
1128
|
+
// intent survives for the next boot to retry.
|
|
1129
|
+
if (live === 'stopped' && entry.status === 'running' && this.restoreFailures.has(entry.name)) {
|
|
1130
|
+
// Matches the persisted path below: a corrected status carries a fresh
|
|
1131
|
+
// timestamp whether or not the write happened.
|
|
1132
|
+
return { ...entry, status: 'error', updatedAt: new Date().toISOString() };
|
|
1133
|
+
}
|
|
1077
1134
|
try {
|
|
1078
1135
|
await this.registry.updateStatus(entry.name, live);
|
|
1079
1136
|
}
|
|
@@ -1102,6 +1159,12 @@ class AppInstaller {
|
|
|
1102
1159
|
// An install in flight owns the status — don't race it.
|
|
1103
1160
|
if (entry.status === 'building')
|
|
1104
1161
|
return entry.status;
|
|
1162
|
+
// So does a boot-time restore: its `compose build`/`up` has not created the
|
|
1163
|
+
// containers yet, so a read landing mid-restore sees an empty list, maps it
|
|
1164
|
+
// to `stopped`, and reconcileStatus() persists that underneath the restore.
|
|
1165
|
+
// On a cold host the window is minutes, not seconds (issue #425).
|
|
1166
|
+
if (this.restoringNames.has(entry.name))
|
|
1167
|
+
return entry.status;
|
|
1105
1168
|
let stdout;
|
|
1106
1169
|
try {
|
|
1107
1170
|
const res = await this.spawnAsync('docker', ['compose', '-p', entry.name, 'ps', '-a', '--format', 'json'], { cwd: entry.installPath, timeoutMs: 10000 });
|
|
@@ -1133,11 +1196,27 @@ class AppInstaller {
|
|
|
1133
1196
|
* Best-effort and non-fatal — a failure for one app is collected and the rest
|
|
1134
1197
|
* still proceed, so one broken app cannot block the others or gateway startup.
|
|
1135
1198
|
* Returns the apps that failed to start (and the count attempted, for logging).
|
|
1199
|
+
*
|
|
1200
|
+
* Each app is marked in-flight for the duration ({@link restoringNames}) so a
|
|
1201
|
+
* concurrent status read cannot persist `stopped` underneath it, and a failure
|
|
1202
|
+
* is recorded ({@link getRestoreFailure}) so it is visible through the apps API
|
|
1203
|
+
* rather than only in the log, and so reconcileStatus() keeps the app eligible
|
|
1204
|
+
* for the next boot's restore instead of latching it off (issue #425).
|
|
1136
1205
|
*/
|
|
1137
1206
|
async restoreRunningApps() {
|
|
1138
1207
|
const apps = await this.registry.list();
|
|
1139
1208
|
const running = apps.filter((e) => e.status === 'running');
|
|
1140
1209
|
const failures = [];
|
|
1210
|
+
// Mark the WHOLE batch in flight before any worker starts, not per worker.
|
|
1211
|
+
// The pool runs at most RESTORE_MAX_CONCURRENCY at a time, so marking inside
|
|
1212
|
+
// the worker would leave every queued app unprotected for as long as the
|
|
1213
|
+
// apps ahead of it take — minutes on a cold host. A status read landing in
|
|
1214
|
+
// that window persists `running` → `stopped`, and once that is stored the
|
|
1215
|
+
// no-latch guard in reconcileStatus() no longer applies (it requires the
|
|
1216
|
+
// stored status to still be `running`), so a later failure would latch the
|
|
1217
|
+
// app off for good — the exact outcome this path exists to prevent (#425).
|
|
1218
|
+
for (const entry of running)
|
|
1219
|
+
this.restoringNames.add(entry.name);
|
|
1141
1220
|
// Bounded-concurrency worker pool: workers pull from a shared cursor until
|
|
1142
1221
|
// the list is drained, so at most RESTORE_MAX_CONCURRENCY compose-ups run at
|
|
1143
1222
|
// once. push() is safe across workers — JS is single-threaded between awaits.
|
|
@@ -1147,16 +1226,41 @@ class AppInstaller {
|
|
|
1147
1226
|
const entry = running[cursor++];
|
|
1148
1227
|
try {
|
|
1149
1228
|
await this.composeUpAsync(entry.name, entry.installPath);
|
|
1229
|
+
this.restoreFailures.delete(entry.name);
|
|
1150
1230
|
}
|
|
1151
1231
|
catch (err) {
|
|
1152
|
-
|
|
1232
|
+
const error = err.message;
|
|
1233
|
+
failures.push({ app: entry.name, error });
|
|
1234
|
+
this.restoreFailures.set(entry.name, { error, at: new Date().toISOString() });
|
|
1235
|
+
}
|
|
1236
|
+
finally {
|
|
1237
|
+
// Released per app, not per batch, so an app that is already up stops
|
|
1238
|
+
// being shielded the moment its own restore ends.
|
|
1239
|
+
this.restoringNames.delete(entry.name);
|
|
1153
1240
|
}
|
|
1154
1241
|
}
|
|
1155
1242
|
};
|
|
1156
1243
|
const poolSize = Math.min(RESTORE_MAX_CONCURRENCY, running.length);
|
|
1157
|
-
|
|
1244
|
+
try {
|
|
1245
|
+
await Promise.all(Array.from({ length: poolSize }, () => worker()));
|
|
1246
|
+
}
|
|
1247
|
+
finally {
|
|
1248
|
+
// Safety net for anything the per-app finally could not reach: a leaked
|
|
1249
|
+
// marker would freeze that app's status reporting for the whole process.
|
|
1250
|
+
for (const entry of running)
|
|
1251
|
+
this.restoringNames.delete(entry.name);
|
|
1252
|
+
}
|
|
1158
1253
|
return { attempted: running.length, failures };
|
|
1159
1254
|
}
|
|
1255
|
+
/**
|
|
1256
|
+
* Why this boot's restore of `appName` failed, or `undefined` when the last
|
|
1257
|
+
* restore succeeded (or never ran). Lets `GET /api/v1/apps` tell an app the
|
|
1258
|
+
* gateway tried and failed to start apart from one an operator stopped —
|
|
1259
|
+
* previously the difference existed only in the boot log.
|
|
1260
|
+
*/
|
|
1261
|
+
getRestoreFailure(appName) {
|
|
1262
|
+
return this.restoreFailures.get(appName);
|
|
1263
|
+
}
|
|
1160
1264
|
// ─── Internal install pipeline ────────────────────────────────────────────
|
|
1161
1265
|
async runInstall(job, options) {
|
|
1162
1266
|
job.status = 'running';
|
|
@@ -2516,9 +2620,84 @@ class AppInstaller {
|
|
|
2516
2620
|
* dev-time port clashes during install/update, but after a host reboot nothing
|
|
2517
2621
|
* is running (the very reason this restore exists), so there is nothing to
|
|
2518
2622
|
* conflict with — and keeping it out avoids extra synchronous docker calls.
|
|
2623
|
+
*
|
|
2624
|
+
* Runs the cold-start steps and `up` under two budgets, but only pays for the
|
|
2625
|
+
* cold path when the host actually lacks the images. `up` alone would pull and
|
|
2626
|
+
* build whatever is missing, and then that work shares the healthcheck-wait
|
|
2627
|
+
* budget — on a host with no image cache (data dir restored onto a fresh
|
|
2628
|
+
* machine, migrated host, pruned Docker) that budget expires mid-build, at
|
|
2629
|
+
* which point the SIGKILL in {@link defaultAsyncSpawn} *cancels* the build and
|
|
2630
|
+
* leaves no image and no container (issue #425).
|
|
2519
2631
|
*/
|
|
2520
2632
|
async composeUpAsync(appName, dir) {
|
|
2521
|
-
|
|
2633
|
+
// Only a cold host pays for pull/build. `up` fetches solely what is missing,
|
|
2634
|
+
// whereas `build` re-runs every time it is called — issuing it
|
|
2635
|
+
// unconditionally would re-execute every Dockerfile on every boot, and
|
|
2636
|
+
// `appHousekeeping` prunes build cache older than a week, so for an app
|
|
2637
|
+
// whose image was already there that is a from-scratch rebuild in front of
|
|
2638
|
+
// a start that needed none.
|
|
2639
|
+
if (await this.needsImageBuild(appName, dir)) {
|
|
2640
|
+
// Pull first: services declared with `image:` only are never built, so on
|
|
2641
|
+
// a cold host they have to be fetched — and `up` would otherwise fetch
|
|
2642
|
+
// them under the short wait budget. `--ignore-buildable` skips the
|
|
2643
|
+
// services that build, which the next step handles.
|
|
2644
|
+
await this.tryColdStart(appName, dir, ['pull', '--ignore-buildable']);
|
|
2645
|
+
await this.tryColdStart(appName, dir, ['build']);
|
|
2646
|
+
}
|
|
2647
|
+
await this.runAsync(['docker', 'compose', '-p', appName, 'up', '-d', '--wait'], dir, this.restoreConfig.waitTimeoutMs);
|
|
2648
|
+
}
|
|
2649
|
+
/**
|
|
2650
|
+
* Run one best-effort cold-start step (`pull` / `build`) under the build
|
|
2651
|
+
* budget. Errors are swallowed on purpose so `up` stays the single authority
|
|
2652
|
+
* on whether the app starts: it re-does whatever is still missing and reports
|
|
2653
|
+
* the real failure together with the container logs.
|
|
2654
|
+
*/
|
|
2655
|
+
async tryColdStart(appName, dir, step) {
|
|
2656
|
+
try {
|
|
2657
|
+
await this.runAsync(['docker', 'compose', '-p', appName, ...step], dir, this.restoreConfig.buildTimeoutMs);
|
|
2658
|
+
}
|
|
2659
|
+
catch {
|
|
2660
|
+
/* fall through — `up` redoes it and surfaces the actual failure */
|
|
2661
|
+
}
|
|
2662
|
+
}
|
|
2663
|
+
/**
|
|
2664
|
+
* True when at least one image the compose project needs is absent from the
|
|
2665
|
+
* local daemon — the cold-host case that must build before it can start.
|
|
2666
|
+
*
|
|
2667
|
+
* Every failure path here answers `false`. "No signal" must mean "skip the
|
|
2668
|
+
* pre-build", which leaves {@link composeUpAsync} behaving exactly as it did
|
|
2669
|
+
* before the build step existed — `up` still builds whatever is missing. The
|
|
2670
|
+
* opposite default would rebuild apps that only needed starting, which is the
|
|
2671
|
+
* cost this probe exists to avoid.
|
|
2672
|
+
*/
|
|
2673
|
+
async needsImageBuild(appName, dir) {
|
|
2674
|
+
let images;
|
|
2675
|
+
try {
|
|
2676
|
+
const { stdout } = await this.runAsync(['docker', 'compose', '-p', appName, 'config', '--images'], dir, IMAGE_PROBE_TIMEOUT_MS);
|
|
2677
|
+
// Image names come from the app's own compose file, so they are untrusted
|
|
2678
|
+
// here: one starting with '-' would be read as a flag by `image inspect`.
|
|
2679
|
+
// They are passed as argv (never a shell string), so dropping those is
|
|
2680
|
+
// the only guard needed.
|
|
2681
|
+
images = stdout
|
|
2682
|
+
.split('\n')
|
|
2683
|
+
.map((line) => line.trim())
|
|
2684
|
+
.filter((line) => line.length > 0 && !line.startsWith('-'));
|
|
2685
|
+
}
|
|
2686
|
+
catch {
|
|
2687
|
+
return false;
|
|
2688
|
+
}
|
|
2689
|
+
if (images.length === 0)
|
|
2690
|
+
return false;
|
|
2691
|
+
try {
|
|
2692
|
+
// `docker image inspect` exits non-zero when *any* argument is missing,
|
|
2693
|
+
// so one call covers the whole project. `--format` keeps the output to
|
|
2694
|
+
// one id per image instead of the full manifest JSON.
|
|
2695
|
+
const res = await this.spawnAsync('docker', ['image', 'inspect', '--format', '{{.Id}}', ...images], { cwd: dir, timeoutMs: IMAGE_PROBE_TIMEOUT_MS });
|
|
2696
|
+
return res.status !== 0;
|
|
2697
|
+
}
|
|
2698
|
+
catch {
|
|
2699
|
+
return false;
|
|
2700
|
+
}
|
|
2522
2701
|
}
|
|
2523
2702
|
/** Async equivalent of {@link run}: throws on non-zero exit. */
|
|
2524
2703
|
async runAsync(args, cwd, timeoutMs) {
|
|
@@ -2995,11 +3174,16 @@ function defaultSpawn(cmd, args, opts) {
|
|
|
2995
3174
|
* to a failure). On timeout the child is SIGKILLed and the promise rejects.
|
|
2996
3175
|
*
|
|
2997
3176
|
* NOTE on timeout semantics for `docker compose up --wait`: SIGKILL kills the
|
|
2998
|
-
* `docker compose` CLI process we spawned, NOT the containers.
|
|
2999
|
-
* bringing them up in the background, so a timeout means
|
|
3000
|
-
* the healthcheck", not "the start was cancelled" — the
|
|
3001
|
-
*
|
|
3002
|
-
*
|
|
3177
|
+
* `docker compose` CLI process we spawned, NOT the containers. **Once the images
|
|
3178
|
+
* exist**, dockerd keeps bringing them up in the background, so a timeout means
|
|
3179
|
+
* "we stopped waiting on the healthcheck", not "the start was cancelled" — the
|
|
3180
|
+
* app may well come up healthy moments later.
|
|
3181
|
+
*
|
|
3182
|
+
* That does NOT hold while an image is still being built. A build session is
|
|
3183
|
+
* driven by the client, so killing the CLI cancels it: the timeout leaves no
|
|
3184
|
+
* image and no container, and nothing arrives later. This is why the restore
|
|
3185
|
+
* builds under its own, far larger budget before it waits — see
|
|
3186
|
+
* {@link AppInstaller.composeUpAsync} and issue #425.
|
|
3003
3187
|
*/
|
|
3004
3188
|
function defaultAsyncSpawn(cmd, args, opts) {
|
|
3005
3189
|
return new Promise((resolve, reject) => {
|
|
@@ -3022,7 +3206,8 @@ function defaultAsyncSpawn(cmd, args, opts) {
|
|
|
3022
3206
|
? setTimeout(() => {
|
|
3023
3207
|
if (settled)
|
|
3024
3208
|
return;
|
|
3025
|
-
// SIGKILL the compose CLI
|
|
3209
|
+
// SIGKILL the compose CLI. Containers already handed to dockerd keep
|
|
3210
|
+
// starting; a build in progress does not survive. See the note above.
|
|
3026
3211
|
child.kill('SIGKILL');
|
|
3027
3212
|
fail(new Error(`Command timed out after ${opts.timeoutMs}ms: ${cmd} ${args[0] ?? ''}`));
|
|
3028
3213
|
}, opts.timeoutMs)
|