@0xmaxma/claude-gateway 1.7.10 → 1.8.0
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 +167 -41
- package/dist/agent/builtin-commands.js +2 -2
- package/dist/agent/builtin-commands.js.map +1 -1
- package/dist/agent/knowledge/config.d.ts.map +1 -1
- package/dist/agent/knowledge/config.js +2 -9
- package/dist/agent/knowledge/config.js.map +1 -1
- package/dist/agent/model-catalog.d.ts +62 -0
- package/dist/agent/model-catalog.d.ts.map +1 -0
- package/dist/agent/model-catalog.js +264 -0
- package/dist/agent/model-catalog.js.map +1 -0
- package/dist/agent/runner.d.ts +76 -0
- package/dist/agent/runner.d.ts.map +1 -1
- package/dist/agent/runner.js +189 -36
- package/dist/agent/runner.js.map +1 -1
- package/dist/api/cron-router.d.ts +12 -8
- package/dist/api/cron-router.d.ts.map +1 -1
- package/dist/api/cron-router.js +82 -16
- package/dist/api/cron-router.js.map +1 -1
- package/dist/api/gateway-router.d.ts +4 -0
- package/dist/api/gateway-router.d.ts.map +1 -1
- package/dist/api/gateway-router.js +14 -0
- package/dist/api/gateway-router.js.map +1 -1
- package/dist/api/manifest-sources.d.ts +15 -0
- package/dist/api/manifest-sources.d.ts.map +1 -0
- package/dist/api/manifest-sources.js +27 -0
- package/dist/api/manifest-sources.js.map +1 -0
- package/dist/api/meta-router.d.ts +13 -0
- package/dist/api/meta-router.d.ts.map +1 -0
- package/dist/api/meta-router.js +26 -0
- package/dist/api/meta-router.js.map +1 -0
- package/dist/api/packages.d.ts.map +1 -1
- package/dist/api/packages.js +18 -198
- package/dist/api/packages.js.map +1 -1
- package/dist/api/route-registry.d.ts +81 -0
- package/dist/api/route-registry.d.ts.map +1 -0
- package/dist/api/route-registry.js +44 -0
- package/dist/api/route-registry.js.map +1 -0
- package/dist/api/router.d.ts.map +1 -1
- package/dist/api/router.js +21 -3
- package/dist/api/router.js.map +1 -1
- package/dist/apps/installer.d.ts +73 -5
- package/dist/apps/installer.d.ts.map +1 -1
- package/dist/apps/installer.js +237 -22
- package/dist/apps/installer.js.map +1 -1
- package/dist/cli/args.d.ts +25 -0
- package/dist/cli/args.d.ts.map +1 -0
- package/dist/cli/args.js +55 -0
- package/dist/cli/args.js.map +1 -0
- package/dist/cli/colors.d.ts +37 -0
- package/dist/cli/colors.d.ts.map +1 -0
- package/dist/cli/colors.js +63 -0
- package/dist/cli/colors.js.map +1 -0
- package/dist/cli/command-names.d.ts +98 -0
- package/dist/cli/command-names.d.ts.map +1 -0
- package/dist/cli/command-names.js +109 -0
- package/dist/cli/command-names.js.map +1 -0
- package/dist/cli/commands/agents.d.ts +28 -0
- package/dist/cli/commands/agents.d.ts.map +1 -0
- package/dist/cli/commands/agents.js +321 -0
- package/dist/cli/commands/agents.js.map +1 -0
- package/dist/cli/commands/channels.d.ts +17 -0
- package/dist/cli/commands/channels.d.ts.map +1 -0
- package/dist/cli/commands/channels.js +101 -0
- package/dist/cli/commands/channels.js.map +1 -0
- package/dist/cli/commands/debug-bundle.d.ts +6 -0
- package/dist/cli/commands/debug-bundle.d.ts.map +1 -0
- package/dist/cli/commands/debug-bundle.js +220 -0
- package/dist/cli/commands/debug-bundle.js.map +1 -0
- package/dist/cli/commands/doctor.d.ts +3 -0
- package/dist/cli/commands/doctor.d.ts.map +1 -0
- package/dist/cli/commands/doctor.js +104 -0
- package/dist/cli/commands/doctor.js.map +1 -0
- package/dist/cli/commands/gateway.d.ts +12 -0
- package/dist/cli/commands/gateway.d.ts.map +1 -0
- package/dist/cli/commands/gateway.js +145 -0
- package/dist/cli/commands/gateway.js.map +1 -0
- package/dist/cli/commands/service.d.ts +33 -0
- package/dist/cli/commands/service.d.ts.map +1 -0
- package/dist/cli/commands/service.js +513 -0
- package/dist/cli/commands/service.js.map +1 -0
- package/dist/cli/commands/update.d.ts +16 -0
- package/dist/cli/commands/update.d.ts.map +1 -0
- package/dist/cli/commands/update.js +149 -0
- package/dist/cli/commands/update.js.map +1 -0
- package/dist/cli/commands.generated.d.ts +4 -0
- package/dist/cli/commands.generated.d.ts.map +1 -0
- package/dist/cli/commands.generated.js +220 -0
- package/dist/cli/commands.generated.js.map +1 -0
- package/dist/cli/health.d.ts +31 -0
- package/dist/cli/health.d.ts.map +1 -0
- package/dist/cli/health.js +49 -0
- package/dist/cli/health.js.map +1 -0
- package/dist/cli/http-client.d.ts +140 -0
- package/dist/cli/http-client.d.ts.map +1 -0
- package/dist/cli/http-client.js +332 -0
- package/dist/cli/http-client.js.map +1 -0
- package/dist/cli/index.d.ts +27 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +372 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/manager.d.ts +64 -0
- package/dist/cli/manager.d.ts.map +1 -0
- package/dist/cli/manager.js +181 -0
- package/dist/cli/manager.js.map +1 -0
- package/dist/cli/output.d.ts +49 -0
- package/dist/cli/output.d.ts.map +1 -0
- package/dist/cli/output.js +101 -0
- package/dist/cli/output.js.map +1 -0
- package/dist/cli/prompt.d.ts +29 -0
- package/dist/cli/prompt.d.ts.map +1 -0
- package/dist/cli/prompt.js +154 -0
- package/dist/cli/prompt.js.map +1 -0
- package/dist/cli/redact.d.ts +3 -0
- package/dist/cli/redact.d.ts.map +1 -0
- package/dist/cli/redact.js +33 -0
- package/dist/cli/redact.js.map +1 -0
- package/dist/cli/types.d.ts +22 -0
- package/dist/cli/types.d.ts.map +1 -0
- package/dist/cli/types.js +4 -0
- package/dist/cli/types.js.map +1 -0
- package/dist/config/bootstrap.d.ts +33 -0
- package/dist/config/bootstrap.d.ts.map +1 -0
- package/dist/config/bootstrap.js +117 -0
- package/dist/config/bootstrap.js.map +1 -0
- package/dist/config/loader.d.ts.map +1 -1
- package/dist/config/loader.js +5 -1
- package/dist/config/loader.js.map +1 -1
- package/dist/config/watcher.d.ts.map +1 -1
- package/dist/config/watcher.js +2 -35
- package/dist/config/watcher.js.map +1 -1
- package/dist/discord/receiver.d.ts +6 -1
- package/dist/discord/receiver.d.ts.map +1 -1
- package/dist/discord/receiver.js +9 -1
- package/dist/discord/receiver.js.map +1 -1
- package/dist/entry.d.ts +3 -0
- package/dist/entry.d.ts.map +1 -0
- package/dist/entry.js +83 -0
- package/dist/entry.js.map +1 -0
- package/dist/index.js +160 -46
- package/dist/index.js.map +1 -1
- package/dist/load-dotenv.d.ts +2 -0
- package/dist/load-dotenv.d.ts.map +1 -0
- package/dist/load-dotenv.js +68 -0
- package/dist/load-dotenv.js.map +1 -0
- package/dist/packages/registry.d.ts +50 -0
- package/dist/packages/registry.d.ts.map +1 -0
- package/dist/packages/registry.js +209 -0
- package/dist/packages/registry.js.map +1 -0
- package/dist/shutdown-signals.d.ts +47 -0
- package/dist/shutdown-signals.d.ts.map +1 -0
- package/dist/shutdown-signals.js +62 -0
- package/dist/shutdown-signals.js.map +1 -0
- package/dist/telegram/receiver.d.ts +6 -1
- package/dist/telegram/receiver.d.ts.map +1 -1
- package/dist/telegram/receiver.js +9 -1
- package/dist/telegram/receiver.js.map +1 -1
- package/dist/utils/orphan-receivers.d.ts +90 -0
- package/dist/utils/orphan-receivers.d.ts.map +1 -0
- package/dist/utils/orphan-receivers.js +218 -0
- package/dist/utils/orphan-receivers.js.map +1 -0
- package/dist/utils/paths.d.ts +16 -0
- package/dist/utils/paths.d.ts.map +1 -0
- package/dist/utils/paths.js +60 -0
- package/dist/utils/paths.js.map +1 -0
- package/dist/utils/stop-child.d.ts +21 -0
- package/dist/utils/stop-child.d.ts.map +1 -0
- package/dist/utils/stop-child.js +64 -0
- package/dist/utils/stop-child.js.map +1 -0
- package/lib/pairing.ts +1 -1
- package/mcp/tools/agent/handlers.ts +1 -1
- package/mcp/tools/discord/receiver-server.ts +4 -0
- package/mcp/tools/telegram/receiver-server.ts +6 -4
- package/package.json +6 -4
package/dist/apps/installer.d.ts
CHANGED
|
@@ -177,6 +177,27 @@ type AsyncSpawnFn = (cmd: string, args: string[], opts?: {
|
|
|
177
177
|
stderr: string;
|
|
178
178
|
status: number | null;
|
|
179
179
|
}>;
|
|
180
|
+
/**
|
|
181
|
+
* A filesystem call that failed because the gateway user lacks the rights, not
|
|
182
|
+
* because the path is wrong. Containers running as their image's uid leave
|
|
183
|
+
* files and directories the gateway user can neither delete nor rename, and
|
|
184
|
+
* those are the two codes the kernel reports for it.
|
|
185
|
+
*/
|
|
186
|
+
export declare function isPermissionError(err: unknown): boolean;
|
|
187
|
+
/**
|
|
188
|
+
* Deepest directory containing both paths, never either path itself.
|
|
189
|
+
*
|
|
190
|
+
* Used to pick a **single** mount for the root helper that moves a bind path:
|
|
191
|
+
* mounting each parent separately would put the two paths on different mount
|
|
192
|
+
* points, where `rename(2)` fails EXDEV and `mv` degrades to a copy.
|
|
193
|
+
*
|
|
194
|
+
* The comparison stops one component short of the shorter path, so the result
|
|
195
|
+
* is always a strict ancestor of both — for siblings (what the callers here
|
|
196
|
+
* pass) that is exact. When one path contains the other it is the *shallower*
|
|
197
|
+
* path's parent, i.e. broader than strictly needed; never narrower, which would
|
|
198
|
+
* put a path outside the mount.
|
|
199
|
+
*/
|
|
200
|
+
export declare function commonAncestorDir(a: string, b: string): string;
|
|
180
201
|
export declare class AppInstaller {
|
|
181
202
|
private readonly registry;
|
|
182
203
|
private readonly registryClient;
|
|
@@ -311,13 +332,16 @@ export declare class AppInstaller {
|
|
|
311
332
|
* are disabled. The timer is `unref`'d so it never holds the event loop open.
|
|
312
333
|
*/
|
|
313
334
|
/**
|
|
314
|
-
* Reclaim update
|
|
315
|
-
*
|
|
316
|
-
* snapshots. Staging moved next to the install path so the swap is a
|
|
335
|
+
* Reclaim the `.cg-update-*` staging checkout a crashed or killed update left
|
|
336
|
+
* behind. Staging moved next to the install path so the swap is a
|
|
317
337
|
* same-filesystem rename, which also means `/tmp` cleanup no longer collects
|
|
318
338
|
* it — without this sweep a mid-update crash leaks a full app checkout
|
|
319
339
|
* forever.
|
|
320
340
|
*
|
|
341
|
+
* Release snapshots (`-old-`/`-failed-`) are **reported, never removed**: see
|
|
342
|
+
* {@link RELEASE_SNAPSHOT_DIR_RE}. They can hold the only copy of a live bind
|
|
343
|
+
* mount, and this sweep deletes with root.
|
|
344
|
+
*
|
|
321
345
|
* **Boot only.** An update in flight owns directories matching these names,
|
|
322
346
|
* so this must run before any update can start. Never throws; a directory it
|
|
323
347
|
* cannot remove is reported and skipped.
|
|
@@ -475,10 +499,54 @@ export declare class AppInstaller {
|
|
|
475
499
|
private moveBindMounts;
|
|
476
500
|
private moveBindEntry;
|
|
477
501
|
/**
|
|
478
|
-
*
|
|
479
|
-
*
|
|
502
|
+
* Name what a preserve-the-live-copy collision is about to delete.
|
|
503
|
+
*
|
|
504
|
+
* The discarded side is the release's own checkout, so it is always readable
|
|
505
|
+
* by the gateway user even when the live side is not. Without this the log
|
|
506
|
+
* says only that "the release's copy was discarded" — which reads as a
|
|
507
|
+
* `.gitkeep` and hides the case that matters: a release shipping a new
|
|
508
|
+
* `init.sql` or entrypoint script *inside* a path whose live copy wins, where
|
|
509
|
+
* the file is dropped on every update and nothing ever says which.
|
|
510
|
+
*/
|
|
511
|
+
private describeDiscarded;
|
|
512
|
+
/**
|
|
513
|
+
* Undo {@link moveBindMounts}. Returns the paths it could **not** move back:
|
|
514
|
+
* those still live only under `fromDir`, so the caller must keep that
|
|
515
|
+
* directory rather than clean it up.
|
|
516
|
+
*
|
|
517
|
+
* A path that is missing at the source, or already present at the
|
|
518
|
+
* destination, is not a failure — it needs no move and is not reported.
|
|
480
519
|
*/
|
|
481
520
|
private restoreBindMounts;
|
|
521
|
+
/**
|
|
522
|
+
* Move one bind-mount path between app directories.
|
|
523
|
+
*
|
|
524
|
+
* `rename(2)` that moves a **directory** to a different parent needs write
|
|
525
|
+
* permission on the directory itself — the kernel has to update its `..`
|
|
526
|
+
* entry — not just on the two parents, which is exactly what the swap does. A bind mount the app's own container created is owned by that
|
|
527
|
+
* image's uid (postgres initdb leaves its data directory mode 0700), so the
|
|
528
|
+
* gateway user cannot rename it and the update swap failed with EACCES on
|
|
529
|
+
* every attempt.
|
|
530
|
+
*
|
|
531
|
+
* The same host-vs-container ownership split is already handled elsewhere in
|
|
532
|
+
* this file — backups tar through a root helper container, {@link rmrf} falls
|
|
533
|
+
* back to `sudo rm -rf`. The swap was the one path still doing it bare, so
|
|
534
|
+
* the fallback here matches the backup mechanism: a throwaway root container.
|
|
535
|
+
*
|
|
536
|
+
* The helper mounts the **nearest common ancestor** of the two paths rather
|
|
537
|
+
* than each parent separately, and that is load-bearing: two bind mounts are
|
|
538
|
+
* two mount points, so `rename(2)` across them fails EXDEV and busybox `mv`
|
|
539
|
+
* silently degrades to copy-then-unlink. Measured by inode, two mounts give a
|
|
540
|
+
* new inode (a copy) where one mount preserves it (a real rename). For a
|
|
541
|
+
* database directory the degraded path means duplicating it on disk and
|
|
542
|
+
* losing the atomicity the swap depends on.
|
|
543
|
+
*
|
|
544
|
+
* The single mount is therefore the apps root, not one app's directory — the
|
|
545
|
+
* two app directories are siblings under it. The helper is a throwaway
|
|
546
|
+
* container whose only command is the `mv`, and it is reached only after the
|
|
547
|
+
* gateway user's own rename was refused.
|
|
548
|
+
*/
|
|
549
|
+
private moveBindPath;
|
|
482
550
|
/**
|
|
483
551
|
* Stop conflicting containers then run `docker compose up -d --wait`.
|
|
484
552
|
* Captures container logs into the job on failure before rethrowing.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"installer.d.ts","sourceRoot":"","sources":["../../src/apps/installer.ts"],"names":[],"mappings":"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
|
|
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
|
@@ -34,6 +34,8 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.AppInstaller = void 0;
|
|
37
|
+
exports.isPermissionError = isPermissionError;
|
|
38
|
+
exports.commonAncestorDir = commonAncestorDir;
|
|
37
39
|
exports.parseComposePs = parseComposePs;
|
|
38
40
|
exports.mapContainerStatesToAppStatus = mapContainerStatesToAppStatus;
|
|
39
41
|
const fs = __importStar(require("node:fs"));
|
|
@@ -65,6 +67,10 @@ const VOLUME_TAR_TIMEOUT_MS = 300000;
|
|
|
65
67
|
// OCI image used for the throwaway tar helper. Small, ubiquitous, already a
|
|
66
68
|
// transitive dependency of most stacks, so it is almost always cache-warm.
|
|
67
69
|
const BACKUP_HELPER_IMAGE = 'alpine';
|
|
70
|
+
// Ceiling for the root helper that moves a bind path the gateway user cannot
|
|
71
|
+
// rename itself. The move is a single rename(2) — instant — so this only bounds
|
|
72
|
+
// a stuck container (or a cold pull of the helper image) during an update swap.
|
|
73
|
+
const BIND_MOVE_TIMEOUT_MS = 120000;
|
|
68
74
|
// Per-app ceiling for the boot-time `compose up --wait` during restore. Runs in
|
|
69
75
|
// the background (non-blocking), so this only bounds how long a hung container
|
|
70
76
|
// keeps its child process alive — not the gateway's responsiveness. Shorter than
|
|
@@ -98,12 +104,60 @@ const GITHUB_URL_RE = /^https:\/\/github\.com\/(?!.*\.\.)[A-Za-z0-9][A-Za-z0-9_.
|
|
|
98
104
|
// ─── Installer ────────────────────────────────────────────────────────────────
|
|
99
105
|
const UUID_RE_SRC = '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}';
|
|
100
106
|
/**
|
|
101
|
-
*
|
|
102
|
-
* `.cg-update-<app>-<uuid
|
|
103
|
-
*
|
|
104
|
-
*
|
|
107
|
+
* The staging checkout `runUpdate` creates beside an app's install path,
|
|
108
|
+
* `.cg-update-<app>-<uuid>`. It holds a fresh clone and nothing else, so a
|
|
109
|
+
* crash leftover is pure garbage and safe to reclaim. The UUID means the
|
|
110
|
+
* pattern cannot match an app directory named by a user.
|
|
105
111
|
*/
|
|
106
|
-
const STALE_UPDATE_DIR_RE = new RegExp(`^\\.cg-update-.+-${UUID_RE_SRC}
|
|
112
|
+
const STALE_UPDATE_DIR_RE = new RegExp(`^\\.cg-update-.+-${UUID_RE_SRC}$`);
|
|
113
|
+
/**
|
|
114
|
+
* The release snapshots `runUpdate` renames an install path to during the swap:
|
|
115
|
+
* `<appDir>-old-<uuid>` (previous release) and `<appDir>-failed-<uuid>` (the
|
|
116
|
+
* release that failed). Deliberately **not** swept.
|
|
117
|
+
*
|
|
118
|
+
* Both can hold the only copy of live bind-mount data. `-failed-` is kept on
|
|
119
|
+
* purpose when a rollback cannot move a bind path back, and a crash between the
|
|
120
|
+
* swap's two renames leaves an `-old-` still holding the paths that had not
|
|
121
|
+
* moved yet. Sweeping them would delete a database — through {@link rmrf}'s
|
|
122
|
+
* `sudo rm -rf` fallback, which exists precisely to defeat the container
|
|
123
|
+
* ownership that made the data unmovable in the first place. They are reported
|
|
124
|
+
* at boot instead, so a leak is visible rather than silent.
|
|
125
|
+
*/
|
|
126
|
+
const RELEASE_SNAPSHOT_DIR_RE = new RegExp(`-(?:old|failed)-${UUID_RE_SRC}$`);
|
|
127
|
+
/**
|
|
128
|
+
* A filesystem call that failed because the gateway user lacks the rights, not
|
|
129
|
+
* because the path is wrong. Containers running as their image's uid leave
|
|
130
|
+
* files and directories the gateway user can neither delete nor rename, and
|
|
131
|
+
* those are the two codes the kernel reports for it.
|
|
132
|
+
*/
|
|
133
|
+
function isPermissionError(err) {
|
|
134
|
+
const code = err?.code;
|
|
135
|
+
return code === 'EACCES' || code === 'EPERM';
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Deepest directory containing both paths, never either path itself.
|
|
139
|
+
*
|
|
140
|
+
* Used to pick a **single** mount for the root helper that moves a bind path:
|
|
141
|
+
* mounting each parent separately would put the two paths on different mount
|
|
142
|
+
* points, where `rename(2)` fails EXDEV and `mv` degrades to a copy.
|
|
143
|
+
*
|
|
144
|
+
* The comparison stops one component short of the shorter path, so the result
|
|
145
|
+
* is always a strict ancestor of both — for siblings (what the callers here
|
|
146
|
+
* pass) that is exact. When one path contains the other it is the *shallower*
|
|
147
|
+
* path's parent, i.e. broader than strictly needed; never narrower, which would
|
|
148
|
+
* put a path outside the mount.
|
|
149
|
+
*/
|
|
150
|
+
function commonAncestorDir(a, b) {
|
|
151
|
+
const left = path.resolve(a).split(path.sep);
|
|
152
|
+
const right = path.resolve(b).split(path.sep);
|
|
153
|
+
const shared = [];
|
|
154
|
+
for (let i = 0; i < Math.min(left.length, right.length) - 1; i++) {
|
|
155
|
+
if (left[i] !== right[i])
|
|
156
|
+
break;
|
|
157
|
+
shared.push(left[i]);
|
|
158
|
+
}
|
|
159
|
+
return shared.join(path.sep) || path.sep;
|
|
160
|
+
}
|
|
107
161
|
/**
|
|
108
162
|
* Rows from a `docker compose … --format json` call. Compose has emitted both a
|
|
109
163
|
* single JSON array and one object per line across its 2.x line, so accept
|
|
@@ -876,13 +930,16 @@ class AppInstaller {
|
|
|
876
930
|
* are disabled. The timer is `unref`'d so it never holds the event loop open.
|
|
877
931
|
*/
|
|
878
932
|
/**
|
|
879
|
-
* Reclaim update
|
|
880
|
-
*
|
|
881
|
-
* snapshots. Staging moved next to the install path so the swap is a
|
|
933
|
+
* Reclaim the `.cg-update-*` staging checkout a crashed or killed update left
|
|
934
|
+
* behind. Staging moved next to the install path so the swap is a
|
|
882
935
|
* same-filesystem rename, which also means `/tmp` cleanup no longer collects
|
|
883
936
|
* it — without this sweep a mid-update crash leaks a full app checkout
|
|
884
937
|
* forever.
|
|
885
938
|
*
|
|
939
|
+
* Release snapshots (`-old-`/`-failed-`) are **reported, never removed**: see
|
|
940
|
+
* {@link RELEASE_SNAPSHOT_DIR_RE}. They can hold the only copy of a live bind
|
|
941
|
+
* mount, and this sweep deletes with root.
|
|
942
|
+
*
|
|
886
943
|
* **Boot only.** An update in flight owns directories matching these names,
|
|
887
944
|
* so this must run before any update can start. Never throws; a directory it
|
|
888
945
|
* cannot remove is reported and skipped.
|
|
@@ -906,11 +963,16 @@ class AppInstaller {
|
|
|
906
963
|
return swept; // registry unreadable — do not guess
|
|
907
964
|
}
|
|
908
965
|
for (const name of names) {
|
|
909
|
-
if (!STALE_UPDATE_DIR_RE.test(name))
|
|
910
|
-
continue;
|
|
911
966
|
const full = path.join(this.appsDir, name);
|
|
912
967
|
if (live.has(full))
|
|
913
968
|
continue;
|
|
969
|
+
if (RELEASE_SNAPSHOT_DIR_RE.test(name)) {
|
|
970
|
+
console.warn(`[installer] keeping app release snapshot "${full}" — it may hold the only copy of a `
|
|
971
|
+
+ 'bind mount an update could not move back. Recover or delete it by hand.');
|
|
972
|
+
continue;
|
|
973
|
+
}
|
|
974
|
+
if (!STALE_UPDATE_DIR_RE.test(name))
|
|
975
|
+
continue;
|
|
914
976
|
try {
|
|
915
977
|
this.rmrf(full);
|
|
916
978
|
swept.push(full);
|
|
@@ -1542,6 +1604,7 @@ class AppInstaller {
|
|
|
1542
1604
|
const finalDir = entry.installPath;
|
|
1543
1605
|
const oldBackupDir = `${finalDir}-old-${crypto.randomUUID()}`;
|
|
1544
1606
|
let swapped = false;
|
|
1607
|
+
let reachedContainerStart = false;
|
|
1545
1608
|
let failedDir = null;
|
|
1546
1609
|
const movedBindMounts = [];
|
|
1547
1610
|
try {
|
|
@@ -1562,17 +1625,25 @@ class AppInstaller {
|
|
|
1562
1625
|
}
|
|
1563
1626
|
// ── Start new containers ────────────────────────────────────────────
|
|
1564
1627
|
this.log(job, 'Starting new containers');
|
|
1628
|
+
reachedContainerStart = true;
|
|
1565
1629
|
this.composeUp(appName, finalDir, job);
|
|
1566
1630
|
}
|
|
1567
1631
|
catch (upErr) {
|
|
1568
|
-
|
|
1632
|
+
// The catch spans the whole swap, so a failure here is not necessarily a
|
|
1633
|
+
// container failure — saying it is sends diagnosis to the wrong
|
|
1634
|
+
// subsystem (a bind-mount move that threw looked exactly like a crashed
|
|
1635
|
+
// container in the job log).
|
|
1636
|
+
this.log(job, reachedContainerStart
|
|
1637
|
+
? 'New containers failed — rolling back to previous version'
|
|
1638
|
+
: `Update failed during the directory swap — rolling back to previous version: ${upErr.message}`);
|
|
1569
1639
|
let rollbackFailed = false;
|
|
1570
1640
|
failedDir = `${finalDir}-failed-${crypto.randomUUID()}`;
|
|
1571
1641
|
try {
|
|
1642
|
+
let unrestored = [];
|
|
1572
1643
|
if (swapped) {
|
|
1573
1644
|
fs.renameSync(finalDir, failedDir);
|
|
1574
1645
|
fs.renameSync(oldBackupDir, finalDir);
|
|
1575
|
-
this.restoreBindMounts(failedDir, finalDir, movedBindMounts, job);
|
|
1646
|
+
unrestored = this.restoreBindMounts(failedDir, finalDir, movedBindMounts, job);
|
|
1576
1647
|
}
|
|
1577
1648
|
else if (fs.existsSync(oldBackupDir)) {
|
|
1578
1649
|
fs.renameSync(oldBackupDir, finalDir);
|
|
@@ -1581,7 +1652,25 @@ class AppInstaller {
|
|
|
1581
1652
|
// before this update built over it, so the restored source and the
|
|
1582
1653
|
// restored image are the same release. Falls back to rebuilding from
|
|
1583
1654
|
// the restored source when an old image is no longer on disk.
|
|
1655
|
+
//
|
|
1656
|
+
// This has to happen even when the bind restore below refuses to
|
|
1657
|
+
// start the app: the source is already back on the previous release,
|
|
1658
|
+
// so `<app>-<service>:latest` must name that release's build before
|
|
1659
|
+
// *anything* starts it. Leaving it on the failed release's build
|
|
1660
|
+
// means an operator who finishes the recovery by hand brings old
|
|
1661
|
+
// source up on a new image — the crash-loop `preserveRunningImages`
|
|
1662
|
+
// exists to prevent.
|
|
1584
1663
|
const rebuild = !this.restorePreservedImages(preservedImages, finalDir, job);
|
|
1664
|
+
if (unrestored.length > 0) {
|
|
1665
|
+
// Throwing here is deliberate: it skips both the container start
|
|
1666
|
+
// below and the safeRmrf of failedDir, so the only copy of that
|
|
1667
|
+
// data stays on disk. Starting the app on a half-restored
|
|
1668
|
+
// directory is worse than not starting it — postgres on an empty
|
|
1669
|
+
// pgdata initialises a fresh cluster and then looks healthy.
|
|
1670
|
+
throw new Error(`live bind-mount data for ${unrestored.map((r) => `"${r}"`).join(', ')} could not be moved back `
|
|
1671
|
+
+ `— it is still in "${failedDir}", which has been kept. Move those paths back into `
|
|
1672
|
+
+ `"${finalDir}" before starting the app.`);
|
|
1673
|
+
}
|
|
1585
1674
|
const upArgs = ['docker', 'compose', '-p', appName, 'up', '-d'];
|
|
1586
1675
|
if (rebuild)
|
|
1587
1676
|
upArgs.push('--build');
|
|
@@ -1603,6 +1692,20 @@ class AppInstaller {
|
|
|
1603
1692
|
this.log(job, `ROLLBACK FAILED — app "${appName}" may be in a broken state: ${rollbackErr.message}`);
|
|
1604
1693
|
}
|
|
1605
1694
|
if (rollbackFailed) {
|
|
1695
|
+
// Keep the private `cg-rollback-*` tags: they are the only remaining
|
|
1696
|
+
// reference to the pre-update build, and the outer catch drops them.
|
|
1697
|
+
// Clearing the list here is what stops that — an incomplete rollback
|
|
1698
|
+
// is finished by hand, and that recovery needs the old image to still
|
|
1699
|
+
// exist. Untagged, containerd is free to reclaim it.
|
|
1700
|
+
if (preservedImages.some((i) => i.backupRef)) {
|
|
1701
|
+
this.log(job, `Pre-update images kept for manual recovery: ${preservedImages.filter((i) => i.backupRef).map((i) => `"${i.backupRef}" (for "${i.ref}")`).join(', ')}`);
|
|
1702
|
+
}
|
|
1703
|
+
preservedImages = [];
|
|
1704
|
+
// The success path above sets 'running'. Leaving the registry on the
|
|
1705
|
+
// pre-update 'running' here would report a healthy app that is not
|
|
1706
|
+
// running at all — and this branch now includes the case where the
|
|
1707
|
+
// app was deliberately not restarted.
|
|
1708
|
+
await this.registry.updateStatus(appName, 'error').catch(() => { });
|
|
1606
1709
|
throw new Error(`Update failed and rollback also failed — app "${appName}" may be in a broken state. Check job logs for details.`);
|
|
1607
1710
|
}
|
|
1608
1711
|
throw upErr;
|
|
@@ -2111,7 +2214,7 @@ class AppInstaller {
|
|
|
2111
2214
|
const code = err.code;
|
|
2112
2215
|
// Containers running as root leave root-owned files (e.g. postgres pgdata)
|
|
2113
2216
|
// that the gateway user cannot delete — fs.rmSync surfaces EACCES or EPERM.
|
|
2114
|
-
if (
|
|
2217
|
+
if (isPermissionError(err)) {
|
|
2115
2218
|
console.warn(`[installer] ${code} removing "${dirPath}" — falling back to sudo rm -rf`);
|
|
2116
2219
|
this.run(['sudo', 'rm', '-rf', dirPath]);
|
|
2117
2220
|
}
|
|
@@ -2218,7 +2321,7 @@ class AppInstaller {
|
|
|
2218
2321
|
destStat = null;
|
|
2219
2322
|
}
|
|
2220
2323
|
if (destStat === null) {
|
|
2221
|
-
|
|
2324
|
+
this.moveBindPath(source, destination, rel, job);
|
|
2222
2325
|
moved?.push(rel);
|
|
2223
2326
|
return;
|
|
2224
2327
|
}
|
|
@@ -2232,24 +2335,78 @@ class AppInstaller {
|
|
|
2232
2335
|
// into the restored previous app dir. Exact rollback beats the renames,
|
|
2233
2336
|
// which are same-filesystem and only walk a directory the release also
|
|
2234
2337
|
// ships content in.
|
|
2235
|
-
|
|
2236
|
-
|
|
2338
|
+
let entries;
|
|
2339
|
+
try {
|
|
2340
|
+
entries = fs.readdirSync(source);
|
|
2341
|
+
}
|
|
2342
|
+
catch (err) {
|
|
2343
|
+
// The live directory is one the app's container owns and the gateway
|
|
2344
|
+
// user cannot even list (postgres leaves pgdata 0700). There is no way
|
|
2345
|
+
// to merge into it entry by entry, so preserve it wholesale — the same
|
|
2346
|
+
// trade the non-directory collision below already makes.
|
|
2347
|
+
if (!isPermissionError(err))
|
|
2348
|
+
throw err;
|
|
2349
|
+
entries = null;
|
|
2350
|
+
}
|
|
2351
|
+
if (entries !== null) {
|
|
2352
|
+
// Each child that needs the root helper costs its own container. That
|
|
2353
|
+
// is bounded by the live directory's own entry count, and only reached
|
|
2354
|
+
// when a release ships tracked content *inside* a path whose children
|
|
2355
|
+
// the gateway user cannot rename — rare enough not to trade the exact
|
|
2356
|
+
// rollback above for one wholesale move.
|
|
2357
|
+
for (const name of entries) {
|
|
2358
|
+
this.moveBindEntry(fromDir, toDir, `${rel}/${name}`, job, moved);
|
|
2359
|
+
}
|
|
2360
|
+
return;
|
|
2237
2361
|
}
|
|
2238
|
-
return;
|
|
2239
2362
|
}
|
|
2240
|
-
this.log(job, `Warning: preserved existing bind-mount data at "${rel}" — the updated release's copy of that path
|
|
2363
|
+
this.log(job, `Warning: preserved existing bind-mount data at "${rel}" — the updated release's copy of that path `
|
|
2364
|
+
+ `was discarded (${this.describeDiscarded(destination)})`);
|
|
2241
2365
|
fs.rmSync(destination, { recursive: true, force: true });
|
|
2242
|
-
|
|
2366
|
+
this.moveBindPath(source, destination, rel, job);
|
|
2243
2367
|
moved?.push(rel);
|
|
2244
2368
|
}
|
|
2245
2369
|
/**
|
|
2246
|
-
*
|
|
2247
|
-
*
|
|
2370
|
+
* Name what a preserve-the-live-copy collision is about to delete.
|
|
2371
|
+
*
|
|
2372
|
+
* The discarded side is the release's own checkout, so it is always readable
|
|
2373
|
+
* by the gateway user even when the live side is not. Without this the log
|
|
2374
|
+
* says only that "the release's copy was discarded" — which reads as a
|
|
2375
|
+
* `.gitkeep` and hides the case that matters: a release shipping a new
|
|
2376
|
+
* `init.sql` or entrypoint script *inside* a path whose live copy wins, where
|
|
2377
|
+
* the file is dropped on every update and nothing ever says which.
|
|
2378
|
+
*/
|
|
2379
|
+
describeDiscarded(destination, limit = 10) {
|
|
2380
|
+
let names;
|
|
2381
|
+
try {
|
|
2382
|
+
names = fs.statSync(destination).isDirectory()
|
|
2383
|
+
? fs.readdirSync(destination).sort()
|
|
2384
|
+
: [path.basename(destination)];
|
|
2385
|
+
}
|
|
2386
|
+
catch {
|
|
2387
|
+
return 'contents unreadable';
|
|
2388
|
+
}
|
|
2389
|
+
if (names.length === 0)
|
|
2390
|
+
return 'it was empty';
|
|
2391
|
+
const shown = names.slice(0, limit).map((n) => `"${n}"`).join(', ');
|
|
2392
|
+
return names.length > limit
|
|
2393
|
+
? `${shown} and ${names.length - limit} more`
|
|
2394
|
+
: shown;
|
|
2395
|
+
}
|
|
2396
|
+
/**
|
|
2397
|
+
* Undo {@link moveBindMounts}. Returns the paths it could **not** move back:
|
|
2398
|
+
* those still live only under `fromDir`, so the caller must keep that
|
|
2399
|
+
* directory rather than clean it up.
|
|
2400
|
+
*
|
|
2401
|
+
* A path that is missing at the source, or already present at the
|
|
2402
|
+
* destination, is not a failure — it needs no move and is not reported.
|
|
2248
2403
|
*/
|
|
2249
2404
|
restoreBindMounts(fromDir, toDir, moved, job) {
|
|
2405
|
+
const unrestored = [];
|
|
2250
2406
|
for (const rel of [...moved].reverse()) {
|
|
2251
2407
|
if (!this.isSafeBindRel(fromDir, toDir, rel)) {
|
|
2252
2408
|
this.log(job, `Warning: skipping bind-mount path outside the app directory: "${rel}"`);
|
|
2409
|
+
unrestored.push(rel);
|
|
2253
2410
|
continue;
|
|
2254
2411
|
}
|
|
2255
2412
|
const source = path.resolve(fromDir, rel);
|
|
@@ -2258,12 +2415,70 @@ class AppInstaller {
|
|
|
2258
2415
|
continue;
|
|
2259
2416
|
try {
|
|
2260
2417
|
this.ensureDirWithinNoSymlink(toDir, path.dirname(destination));
|
|
2261
|
-
|
|
2418
|
+
this.moveBindPath(source, destination, rel, job);
|
|
2262
2419
|
}
|
|
2263
2420
|
catch (err) {
|
|
2264
2421
|
this.log(job, `Warning: could not restore bind-mount path "${rel}": ${err.message}`);
|
|
2422
|
+
unrestored.push(rel);
|
|
2265
2423
|
}
|
|
2266
2424
|
}
|
|
2425
|
+
return unrestored;
|
|
2426
|
+
}
|
|
2427
|
+
/**
|
|
2428
|
+
* Move one bind-mount path between app directories.
|
|
2429
|
+
*
|
|
2430
|
+
* `rename(2)` that moves a **directory** to a different parent needs write
|
|
2431
|
+
* permission on the directory itself — the kernel has to update its `..`
|
|
2432
|
+
* entry — not just on the two parents, which is exactly what the swap does. A bind mount the app's own container created is owned by that
|
|
2433
|
+
* image's uid (postgres initdb leaves its data directory mode 0700), so the
|
|
2434
|
+
* gateway user cannot rename it and the update swap failed with EACCES on
|
|
2435
|
+
* every attempt.
|
|
2436
|
+
*
|
|
2437
|
+
* The same host-vs-container ownership split is already handled elsewhere in
|
|
2438
|
+
* this file — backups tar through a root helper container, {@link rmrf} falls
|
|
2439
|
+
* back to `sudo rm -rf`. The swap was the one path still doing it bare, so
|
|
2440
|
+
* the fallback here matches the backup mechanism: a throwaway root container.
|
|
2441
|
+
*
|
|
2442
|
+
* The helper mounts the **nearest common ancestor** of the two paths rather
|
|
2443
|
+
* than each parent separately, and that is load-bearing: two bind mounts are
|
|
2444
|
+
* two mount points, so `rename(2)` across them fails EXDEV and busybox `mv`
|
|
2445
|
+
* silently degrades to copy-then-unlink. Measured by inode, two mounts give a
|
|
2446
|
+
* new inode (a copy) where one mount preserves it (a real rename). For a
|
|
2447
|
+
* database directory the degraded path means duplicating it on disk and
|
|
2448
|
+
* losing the atomicity the swap depends on.
|
|
2449
|
+
*
|
|
2450
|
+
* The single mount is therefore the apps root, not one app's directory — the
|
|
2451
|
+
* two app directories are siblings under it. The helper is a throwaway
|
|
2452
|
+
* container whose only command is the `mv`, and it is reached only after the
|
|
2453
|
+
* gateway user's own rename was refused.
|
|
2454
|
+
*/
|
|
2455
|
+
moveBindPath(source, destination, rel, job) {
|
|
2456
|
+
try {
|
|
2457
|
+
fs.renameSync(source, destination);
|
|
2458
|
+
return;
|
|
2459
|
+
}
|
|
2460
|
+
catch (err) {
|
|
2461
|
+
if (!isPermissionError(err))
|
|
2462
|
+
throw err;
|
|
2463
|
+
}
|
|
2464
|
+
const base = commonAncestorDir(source, destination);
|
|
2465
|
+
const from = path.relative(base, source);
|
|
2466
|
+
const to = path.relative(base, destination);
|
|
2467
|
+
if (base === path.parse(base).root || base.includes(':') || from === '' || to === ''
|
|
2468
|
+
|| from.split(path.sep).includes('..') || to.split(path.sep).includes('..')) {
|
|
2469
|
+
// A colon would be read as the mount separator by `docker -v`, silently
|
|
2470
|
+
// mounting something other than what was asked for.
|
|
2471
|
+
throw new Error(`Cannot move bind-mount path "${rel}" as root: unsafe mount base "${base}"`);
|
|
2472
|
+
}
|
|
2473
|
+
this.log(job, `Bind-mount path "${rel}" is not writable by the gateway user (created by the app's container) `
|
|
2474
|
+
+ '— moving it with a root helper container');
|
|
2475
|
+
this.run([
|
|
2476
|
+
'docker', 'run', '--rm',
|
|
2477
|
+
'-v', `${base}:/mnt`,
|
|
2478
|
+
BACKUP_HELPER_IMAGE,
|
|
2479
|
+
'mv', path.posix.join('/mnt', from.split(path.sep).join('/')),
|
|
2480
|
+
path.posix.join('/mnt', to.split(path.sep).join('/')),
|
|
2481
|
+
], undefined, BIND_MOVE_TIMEOUT_MS);
|
|
2267
2482
|
}
|
|
2268
2483
|
/**
|
|
2269
2484
|
* Stop conflicting containers then run `docker compose up -d --wait`.
|