@north-light/cli 0.3.580 → 0.3.582
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/api/client.d.ts +6 -1
- package/dist/api/client.js +3 -3
- package/dist/api/contract.d.ts +1 -1
- package/dist/api/contract.js +1 -1
- package/dist/api/dto/execution-start.d.ts +14 -0
- package/dist/api/dto/health.d.ts +22 -0
- package/dist/api/dto/imports.d.ts +142 -0
- package/dist/api/dto/imports.js +0 -0
- package/dist/api/error-codes.d.ts +5 -0
- package/dist/api/error-codes.js +1 -1
- package/dist/api/errors.js +1 -1
- package/dist/api/index.d.ts +1 -0
- package/dist/api/index.js +1 -1
- package/dist/api/routes.d.ts +2 -0
- package/dist/api/routes.js +1 -1
- package/dist/build-root.d.ts +11 -4
- package/dist/build-root.js +1 -1
- package/dist/cli.js +7 -7
- package/dist/clients/attach/session/connection.d.ts +3 -1
- package/dist/clients/attach/session/connection.js +1 -1
- package/dist/clients/attach/viewer.js +529 -529
- package/dist/commands/api-client.d.ts +12 -4
- package/dist/commands/api-client.js +3 -3
- package/dist/commands/sys/daemon.js +1 -1
- package/dist/commands/sys/desktop.d.ts +5 -0
- package/dist/commands/sys/desktop.js +1 -0
- package/dist/commands/sys/import/move.d.ts +55 -0
- package/dist/commands/sys/import/move.js +8 -0
- package/dist/commands/sys/import/resume.d.ts +1 -0
- package/dist/commands/sys/import/resume.js +1 -0
- package/dist/commands/sys/import/start.d.ts +4 -0
- package/dist/commands/sys/import/start.js +1 -0
- package/dist/commands/sys/join.d.ts +2 -0
- package/dist/commands/sys/join.js +1 -1
- package/dist/commands/sys/machine/rotate.d.ts +1 -0
- package/dist/commands/sys/machine/rotate.js +1 -0
- package/dist/commands/sys/signin.d.ts +7 -0
- package/dist/commands/sys/signin.js +2 -0
- package/dist/commands/sys.js +1 -1
- package/dist/core/blobs/r2.d.ts +4 -0
- package/dist/core/blobs/r2.js +7 -4
- package/dist/core/canvas/daemon-owner.d.ts +1 -1
- package/dist/core/canvas/executions.d.ts +1 -1
- package/dist/core/canvas/node-tokens.d.ts +2 -0
- package/dist/core/canvas/node-tokens.js +3 -3
- package/dist/core/canvas/pid.d.ts +6 -0
- package/dist/core/canvas/pid.js +5 -5
- package/dist/core/command-plugins/compose.js +1 -1
- package/dist/core/command.d.ts +17 -8
- package/dist/core/command.js +9 -9
- package/dist/core/directory/issuer.d.ts +3 -0
- package/dist/core/directory/issuer.js +1 -1
- package/dist/core/directory/person-credential.d.ts +61 -0
- package/dist/core/directory/person-credential.js +1 -0
- package/dist/core/directory/reach.d.ts +3 -0
- package/dist/core/directory/reach.js +1 -0
- package/dist/core/grants/mirror.d.ts +3 -0
- package/dist/core/grants/mirror.js +2 -2
- package/dist/core/graph/bodies-boot-copy.d.ts +16 -2
- package/dist/core/graph/bodies-boot-copy.js +1 -1
- package/dist/core/graph/bodies.d.ts +13 -4
- package/dist/core/graph/bodies.js +1 -1
- package/dist/core/graph/deletion.d.ts +4 -1
- package/dist/core/graph/deletion.js +3 -3
- package/dist/core/graph/documents.d.ts +4 -0
- package/dist/core/graph/documents.js +9 -9
- package/dist/core/graph/reader.js +2 -2
- package/dist/core/graph/repo-sync/exchange.js +2 -2
- package/dist/core/graph/repo-sync/ref-format.d.ts +7 -2
- package/dist/core/graph/repo-sync/ref-format.js +2 -2
- package/dist/core/import/bundle/blob-keys.d.ts +10 -0
- package/dist/core/import/bundle/blob-keys.js +1 -0
- package/dist/core/import/bundle/layout.d.ts +90 -0
- package/dist/core/import/bundle/layout.js +5 -0
- package/dist/core/import/export/archive.d.ts +11 -0
- package/dist/core/import/export/archive.js +1 -0
- package/dist/core/import/export/cloud.d.ts +23 -0
- package/dist/core/import/export/cloud.js +1 -0
- package/dist/core/import/export/dump.d.ts +34 -0
- package/dist/core/import/export/dump.js +3 -0
- package/dist/core/import/export/files.d.ts +22 -0
- package/dist/core/import/export/files.js +1 -0
- package/dist/core/import/export/inline.d.ts +3 -0
- package/dist/core/import/export/inline.js +1 -0
- package/dist/core/import/export/source.d.ts +2 -0
- package/dist/core/import/export/source.js +1 -0
- package/dist/core/import/export/stores.d.ts +9 -0
- package/dist/core/import/export/stores.js +1 -0
- package/dist/core/import/load/bind.d.ts +19 -0
- package/dist/core/import/load/bind.js +15 -0
- package/dist/core/import/load/blobs.d.ts +10 -0
- package/dist/core/import/load/blobs.js +1 -0
- package/dist/core/import/load/catalog.d.ts +32 -0
- package/dist/core/import/load/catalog.js +9 -0
- package/dist/core/import/load/copy.d.ts +26 -0
- package/dist/core/import/load/copy.js +6 -0
- package/dist/core/import/load/path-map.d.ts +7 -0
- package/dist/core/import/load/path-map.js +1 -0
- package/dist/core/import/load/prepare.d.ts +37 -0
- package/dist/core/import/load/prepare.js +10 -0
- package/dist/core/import/load/rewrite.d.ts +39 -0
- package/dist/core/import/load/rewrite.js +13 -0
- package/dist/core/import/load/rollback.d.ts +3 -0
- package/dist/core/import/load/rollback.js +6 -0
- package/dist/core/import/load/run.d.ts +22 -0
- package/dist/core/import/load/run.js +3 -0
- package/dist/core/import/load/stage.d.ts +15 -0
- package/dist/core/import/load/stage.js +1 -0
- package/dist/core/import/load/stores.d.ts +24 -0
- package/dist/core/import/load/stores.js +2 -0
- package/dist/core/import/load/verify.d.ts +4 -0
- package/dist/core/import/load/verify.js +12 -0
- package/dist/core/io.js +4 -4
- package/dist/core/northlight/previews.d.ts +22 -0
- package/dist/core/northlight/previews.js +1 -0
- package/dist/core/providers/call.js +1 -1
- package/dist/core/providers/device.js +1 -1
- package/dist/core/providers/files.d.ts +2 -1
- package/dist/core/providers/files.js +1 -1
- package/dist/core/runtime/broker/cli-relay.js +1 -1
- package/dist/core/runtime/broker/model-adapter.js +3 -3
- package/dist/core/runtime/broker-ownership.d.ts +1 -0
- package/dist/core/runtime/broker-ownership.js +1 -0
- package/dist/core/runtime/broker.js +1 -1
- package/dist/core/runtime/first-run-offer.d.ts +7 -2
- package/dist/core/runtime/first-run-offer.js +10 -2
- package/dist/core/runtime/front-door.js +4 -4
- package/dist/core/runtime/revive.d.ts +5 -2
- package/dist/core/runtime/revive.js +2 -2
- package/dist/core/runtime/runner/blaxel/runner.js +3 -3
- package/dist/core/runtime/runner/index.d.ts +1 -0
- package/dist/core/runtime/runner/index.js +1 -1
- package/dist/core/runtime/runner/machine/call.d.ts +8 -0
- package/dist/core/runtime/runner/machine/call.js +3 -0
- package/dist/core/runtime/runner/machine/environment.d.ts +1 -0
- package/dist/core/runtime/runner/machine/environment.js +1 -0
- package/dist/core/runtime/runner/machine/executor.d.ts +29 -0
- package/dist/core/runtime/runner/machine/executor.js +1 -0
- package/dist/core/runtime/runner/machine/machine-executor.test.d.ts +1 -0
- package/dist/core/runtime/runner/machine/machine-executor.test.js +4 -0
- package/dist/core/runtime/runner/machine/ops.d.ts +127 -0
- package/dist/core/runtime/runner/machine/ops.js +1 -0
- package/dist/core/runtime/runner/machine/runner.d.ts +12 -0
- package/dist/core/runtime/runner/machine/runner.js +1 -0
- package/dist/core/runtime/runner/types.d.ts +7 -1
- package/dist/core/runtime/warm-pool.d.ts +2 -0
- package/dist/core/runtime/warm-pool.js +1 -1
- package/dist/core/secrets/env-rows.d.ts +1 -0
- package/dist/core/secrets/env-rows.js +1 -1
- package/dist/core/storage/purge.d.ts +6 -0
- package/dist/core/storage/purge.js +4 -0
- package/dist/core/subscription-state.d.ts +1 -0
- package/dist/core/subscription-state.js +1 -1
- package/dist/daemon/api/bridge.js +3 -3
- package/dist/daemon/api/handlers/attach.js +1 -1
- package/dist/daemon/api/handlers/crons.js +1 -1
- package/dist/daemon/api/handlers/daemon.js +1 -1
- package/dist/daemon/api/handlers/execution-start.js +2 -2
- package/dist/daemon/api/handlers/files.js +1 -1
- package/dist/daemon/api/handlers/imports.d.ts +2 -0
- package/dist/daemon/api/handlers/imports.js +1 -0
- package/dist/daemon/api/handlers/machine-credential.d.ts +2 -0
- package/dist/daemon/api/handlers/machine-credential.js +1 -0
- package/dist/daemon/api/handlers/profiles.js +1 -1
- package/dist/daemon/api/handlers/providers.js +1 -1
- package/dist/daemon/api/handlers/runs.js +1 -1
- package/dist/daemon/api/import-auth.d.ts +6 -0
- package/dist/daemon/api/import-auth.js +1 -0
- package/dist/daemon/api/operations.d.ts +2 -2
- package/dist/daemon/api/operations.js +1 -1
- package/dist/daemon/api/router.d.ts +7 -1
- package/dist/daemon/api/router.js +1 -1
- package/dist/daemon/api/server.js +1 -1
- package/dist/daemon/boot-migrations.js +1 -1
- package/dist/daemon/broker-signals/bus.d.ts +3 -2
- package/dist/daemon/broker-signals/bus.js +1 -1
- package/dist/daemon/cron/armed-context.d.ts +1 -0
- package/dist/daemon/cron/armed-context.js +1 -1
- package/dist/daemon/cron/closed.d.ts +31 -0
- package/dist/daemon/cron/closed.js +1 -0
- package/dist/daemon/cron/ended.js +2 -2
- package/dist/daemon/cron/lease-recovery.js +1 -1
- package/dist/daemon/cron/machine-exec.d.ts +11 -0
- package/dist/daemon/cron/machine-exec.js +1 -0
- package/dist/daemon/cron/machine-fire.d.ts +26 -0
- package/dist/daemon/cron/machine-fire.js +1 -0
- package/dist/daemon/cron/recurrence.d.ts +14 -0
- package/dist/daemon/cron/recurrence.js +1 -0
- package/dist/daemon/cron/run-token.d.ts +7 -0
- package/dist/daemon/cron/run-token.js +1 -0
- package/dist/daemon/cron-run.js +2 -2
- package/dist/daemon/crtrd-cli.js +1 -1
- package/dist/daemon/crtrd.d.ts +3 -3
- package/dist/daemon/crtrd.js +14 -14
- package/dist/daemon/drain.d.ts +25 -0
- package/dist/daemon/drain.js +9 -0
- package/dist/daemon/due-sources.d.ts +1 -1
- package/dist/daemon/due-sources.js +1 -1
- package/dist/daemon/due.js +8 -8
- package/dist/daemon/fleet.js +5 -5
- package/dist/daemon/import-dump-run.d.ts +1 -0
- package/dist/daemon/import-dump-run.js +5 -0
- package/dist/daemon/machine-daemon.d.ts +10 -0
- package/dist/daemon/machine-daemon.js +1 -0
- package/dist/daemon/machine-link/client.d.ts +29 -0
- package/dist/daemon/machine-link/client.js +1 -0
- package/dist/daemon/machine-link/config.d.ts +45 -0
- package/dist/daemon/machine-link/config.js +3 -0
- package/dist/daemon/machine-link/credential.d.ts +7 -0
- package/dist/daemon/machine-link/credential.js +2 -0
- package/dist/daemon/machine-link/forward.d.ts +16 -0
- package/dist/daemon/machine-link/forward.js +1 -0
- package/dist/daemon/machine-link/frames.d.ts +49 -0
- package/dist/daemon/machine-link/frames.js +1 -0
- package/dist/daemon/machine-link/holder.d.ts +73 -0
- package/dist/daemon/machine-link/holder.js +9 -0
- package/dist/daemon/machine-link/move-notify.d.ts +4 -0
- package/dist/daemon/machine-link/move-notify.js +1 -0
- package/dist/daemon/manage.d.ts +19 -3
- package/dist/daemon/manage.js +3 -3
- package/dist/daemon/reconcilers/cron-lane.js +1 -1
- package/dist/daemon/reconcilers/delivery-lane.js +2 -2
- package/dist/daemon/reconcilers/import-lane.d.ts +9 -0
- package/dist/daemon/reconcilers/import-lane.js +1 -0
- package/dist/daemon/reconcilers/node-lifecycle/freeze-lane.js +1 -1
- package/dist/daemon/reconcilers/node-lifecycle/remote-wake.d.ts +3 -0
- package/dist/daemon/reconcilers/node-lifecycle/remote-wake.js +1 -1
- package/dist/daemon/reconcilers/node-lifecycle/tick.js +1 -1
- package/dist/daemon/reconcilers/purge-due-work.d.ts +13 -0
- package/dist/daemon/reconcilers/purge-due-work.js +1 -0
- package/dist/daemon/reconcilers/row-claim.d.ts +1 -0
- package/dist/daemon/reconcilers/row-claim.js +2 -2
- package/dist/daemon/runs/observer.js +3 -3
- package/package.json +2 -2
- package/packages/crouter-identity/package.json +1 -1
- package/runtime.lock.json +13 -13
- package/scripts/sandbox-start.mjs +185 -2
|
@@ -9,10 +9,13 @@
|
|
|
9
9
|
import { spawn, spawnSync } from 'node:child_process';
|
|
10
10
|
import { createServer } from 'node:http';
|
|
11
11
|
import { randomBytes } from 'node:crypto';
|
|
12
|
-
import { existsSync, lstatSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, readlinkSync, realpathSync, rmSync, symlinkSync, writeFileSync } from 'node:fs';
|
|
12
|
+
import { chmodSync, closeSync, constants as fsConstants, createWriteStream, existsSync, fsyncSync, lstatSync, mkdirSync, mkdtempSync, openSync, readFileSync, readSync, readdirSync, readlinkSync, realpathSync, renameSync, rmSync, symlinkSync, unlinkSync, writeFileSync, writeSync } from 'node:fs';
|
|
13
13
|
import { tmpdir } from 'node:os';
|
|
14
|
-
import { dirname, join, resolve, sep } from 'node:path';
|
|
14
|
+
import { basename, dirname, join, resolve, sep } from 'node:path';
|
|
15
15
|
import { fileURLToPath } from 'node:url';
|
|
16
|
+
import { Readable } from 'node:stream';
|
|
17
|
+
import { pipeline } from 'node:stream/promises';
|
|
18
|
+
import { createZstdDecompress } from 'node:zlib';
|
|
16
19
|
|
|
17
20
|
export const EXIT_NO_BROKER = 3;
|
|
18
21
|
const here = dirname(fileURLToPath(import.meta.url));
|
|
@@ -134,6 +137,171 @@ function gitAsync(cwd, args, timeoutMs, env) {
|
|
|
134
137
|
});
|
|
135
138
|
}
|
|
136
139
|
|
|
140
|
+
const archiveInvalid = (detail) => new StartFailure('import_archive_invalid', detail);
|
|
141
|
+
|
|
142
|
+
/** Create `dir` (which must lie lexically in `root`) one component at a time; no existing component may resolve outside `rootReal`. */
|
|
143
|
+
function ensureDir(root, rootReal, dir) {
|
|
144
|
+
let current = root;
|
|
145
|
+
for (const segment of dir.slice(root.length).split(sep).filter(Boolean)) {
|
|
146
|
+
current = join(current, segment);
|
|
147
|
+
if (!lstatExists(current)) mkdirSync(current);
|
|
148
|
+
if (!within(rootReal, realpathSync(current))) throw archiveInvalid(`${current} resolves outside ${root}`);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/** Each entry of a tar file: `{name, type, size, mode, linkname, offset}`; `offset` is where the data starts. Handles ustar, pax (`x`) and GNU long names (`L`, `K`). */
|
|
153
|
+
export function* tarEntries(fd, fileSize) {
|
|
154
|
+
const block = Buffer.alloc(512);
|
|
155
|
+
const text = (b, from, to) => { const slice = b.subarray(from, to); const end = slice.indexOf(0); return slice.subarray(0, end < 0 ? slice.length : end).toString('utf8'); };
|
|
156
|
+
const octal = (b, from, to) => {
|
|
157
|
+
if (b[from] & 0x80) { let n = 0n; for (let i = from + 1; i < to; i++) n = (n << 8n) | BigInt(b[i]); return Number(n); }
|
|
158
|
+
const t = text(b, from, to).trim();
|
|
159
|
+
return t === '' ? 0 : parseInt(t, 8);
|
|
160
|
+
};
|
|
161
|
+
const readAt = (buffer, position) => { const n = readSync(fd, buffer, 0, buffer.length, position); if (n !== buffer.length) throw archiveInvalid('the archive is truncated'); };
|
|
162
|
+
const readText = (position, size) => { const b = Buffer.alloc(size); readAt(b, position); return b; };
|
|
163
|
+
let position = 0;
|
|
164
|
+
let overrides = {};
|
|
165
|
+
let zeroBlocks = 0;
|
|
166
|
+
while (position + 512 <= fileSize) {
|
|
167
|
+
readAt(block, position);
|
|
168
|
+
position += 512;
|
|
169
|
+
if (block.every((x) => x === 0)) { if (++zeroBlocks === 2) return; continue; }
|
|
170
|
+
zeroBlocks = 0;
|
|
171
|
+
let sum = 0;
|
|
172
|
+
for (let i = 0; i < 512; i++) sum += i >= 148 && i < 156 ? 32 : block[i];
|
|
173
|
+
if (sum !== parseInt(text(block, 148, 156).trim() || 'x', 8)) throw archiveInvalid('a tar header fails its checksum');
|
|
174
|
+
const type = String.fromCharCode(block[156] || 48);
|
|
175
|
+
const size = octal(block, 124, 136);
|
|
176
|
+
if (!Number.isSafeInteger(size) || size < 0 || position + size > fileSize) throw archiveInvalid('a tar entry runs past the end of the archive');
|
|
177
|
+
const dataAt = position;
|
|
178
|
+
position += Math.ceil(size / 512) * 512;
|
|
179
|
+
if (type === 'x') {
|
|
180
|
+
const pax = readText(dataAt, size).toString('utf8');
|
|
181
|
+
for (let i = 0; i < pax.length;) {
|
|
182
|
+
const space = pax.indexOf(' ', i);
|
|
183
|
+
const length = Number(pax.slice(i, space));
|
|
184
|
+
if (!(length > 0)) throw archiveInvalid('a pax header is malformed');
|
|
185
|
+
const [key, ...value] = Buffer.from(pax).subarray(i, i + length - 1).toString('utf8').slice(space - i + 1).split('=');
|
|
186
|
+
overrides[key] = value.join('=');
|
|
187
|
+
i += length;
|
|
188
|
+
}
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
if (type === 'g') continue;
|
|
192
|
+
if (type === 'L') { overrides.path = text(readText(dataAt, size), 0, size); continue; }
|
|
193
|
+
if (type === 'K') { overrides.linkpath = text(readText(dataAt, size), 0, size); continue; }
|
|
194
|
+
const prefix = text(block, 345, 500);
|
|
195
|
+
const ustar = text(block, 257, 262) === 'ustar';
|
|
196
|
+
const name = overrides.path ?? (ustar && prefix ? `${prefix}/${text(block, 0, 100)}` : text(block, 0, 100));
|
|
197
|
+
const linkname = overrides.linkpath ?? text(block, 157, 257);
|
|
198
|
+
overrides = {};
|
|
199
|
+
yield { name, type, size, mode: octal(block, 100, 108) & 0o7777, linkname, offset: dataAt };
|
|
200
|
+
}
|
|
201
|
+
throw archiveInvalid('the archive has no end-of-archive marker');
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/** The target of an archive entry: the path map entry whose `from` (without its leading slash) is the longest leading run of the entry's segments, then
|
|
205
|
+
* the rest under that entry's `to`. Throws `import_archive_invalid` for an absolute name, a `..` segment, or a name under no prefix of the map. */
|
|
206
|
+
export function archiveTarget(pathMap, name) {
|
|
207
|
+
if (name.startsWith('/')) throw archiveInvalid(`entry ${name} has an absolute path`);
|
|
208
|
+
const segments = name.split('/').filter((x) => x !== '' && x !== '.');
|
|
209
|
+
if (segments.includes('..')) throw archiveInvalid(`entry ${name} escapes its prefix`);
|
|
210
|
+
let best = null;
|
|
211
|
+
for (const entry of pathMap) {
|
|
212
|
+
const from = entry.from.split('/').filter(Boolean);
|
|
213
|
+
if (from.length <= segments.length && from.every((x, i) => segments[i] === x) && (best === null || from.length > best.length)) best = { entry, length: from.length };
|
|
214
|
+
}
|
|
215
|
+
if (best === null) throw archiveInvalid(`entry ${name} lies under no prefix of the path map`);
|
|
216
|
+
const rest = segments.slice(best.length);
|
|
217
|
+
return { root: best.entry.to, path: join(best.entry.to, ...rest), rest };
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/** Where an archive's entries for `root` are written first, beside it (so the final move is a rename on one filesystem). */
|
|
221
|
+
const stagingOf = (root) => join(dirname(root), `.${basename(root)}.import-staging`);
|
|
222
|
+
|
|
223
|
+
/** Move what was staged for `root` into it, replacing a same-named entry a retried or interrupted earlier attempt left. */
|
|
224
|
+
function placeStaged(stage, root) {
|
|
225
|
+
mkdirSync(root, { recursive: true });
|
|
226
|
+
for (const child of readdirSync(stage)) {
|
|
227
|
+
const to = join(root, child);
|
|
228
|
+
if (lstatExists(to)) rmSync(to, { recursive: true, force: true });
|
|
229
|
+
renameSync(join(stage, child), to);
|
|
230
|
+
}
|
|
231
|
+
rmSync(stage, { recursive: true, force: true });
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/** Download the node's archive and unpack every entry under its path-map target; nothing is written outside the targets, whatever the archive names.
|
|
235
|
+
* Entries are written to a staging directory beside each target and moved in only after the whole archive has been read, so an attempt that fails
|
|
236
|
+
* partway leaves no half-unpacked target and a retry starts clean. */
|
|
237
|
+
export async function unpackImportedFiles(files, scratch) {
|
|
238
|
+
const response = await fetch(files.url, { signal: AbortSignal.timeout(600_000) });
|
|
239
|
+
if (!response.ok || response.body === null) throw new StartFailure('import_archive_unreachable', `the archive download answered ${response.status}`);
|
|
240
|
+
const tarFile = join(scratch, 'import.tar');
|
|
241
|
+
await pipeline(Readable.fromWeb(response.body), createZstdDecompress(), createWriteStream(tarFile));
|
|
242
|
+
const targets = [...new Set(files.path_map.map((m) => m.to))];
|
|
243
|
+
for (const root of targets) rmSync(stagingOf(root), { recursive: true, force: true });
|
|
244
|
+
const fd = openSync(tarFile, 'r');
|
|
245
|
+
try {
|
|
246
|
+
const size = lstatSync(tarFile).size;
|
|
247
|
+
const stages = new Map();
|
|
248
|
+
const stageOf = (root) => {
|
|
249
|
+
if (!stages.has(root)) {
|
|
250
|
+
const stage = stagingOf(root);
|
|
251
|
+
mkdirSync(stage, { recursive: true });
|
|
252
|
+
stages.set(root, { stage, real: realpathSync(stage) });
|
|
253
|
+
}
|
|
254
|
+
return stages.get(root);
|
|
255
|
+
};
|
|
256
|
+
for (const entry of tarEntries(fd, size)) {
|
|
257
|
+
const { root, rest } = archiveTarget(files.path_map, entry.name);
|
|
258
|
+
const { stage, real } = stageOf(root);
|
|
259
|
+
const path = join(stage, ...rest);
|
|
260
|
+
if (entry.type === '5') { ensureDir(stage, real, path); continue; }
|
|
261
|
+
if (rest.length === 0) throw archiveInvalid(`entry ${entry.name} is a ${entry.type === '0' ? 'file' : 'link'} where the prefix directory belongs`);
|
|
262
|
+
ensureDir(stage, real, dirname(path));
|
|
263
|
+
if (entry.type === '0') {
|
|
264
|
+
if (lstatExists(path)) unlinkSync(path);
|
|
265
|
+
const out = openSync(path, fsConstants.O_WRONLY | fsConstants.O_CREAT | fsConstants.O_EXCL | fsConstants.O_NOFOLLOW, 0o600);
|
|
266
|
+
try {
|
|
267
|
+
const buffer = Buffer.alloc(Math.min(1 << 20, Math.max(entry.size, 1)));
|
|
268
|
+
for (let done = 0; done < entry.size;) {
|
|
269
|
+
const n = readSync(fd, buffer, 0, Math.min(buffer.length, entry.size - done), entry.offset + done);
|
|
270
|
+
if (n === 0) throw archiveInvalid('the archive is truncated');
|
|
271
|
+
writeSync(out, buffer, 0, n);
|
|
272
|
+
done += n;
|
|
273
|
+
}
|
|
274
|
+
} finally { closeSync(out); }
|
|
275
|
+
chmodSync(path, entry.mode & 0o777);
|
|
276
|
+
} else if (entry.type === '2') {
|
|
277
|
+
// A link is kept only when it resolves, from where it lies, inside the same target (an absolute one is checked against the final target).
|
|
278
|
+
const resolved = resolve(dirname(path), entry.linkname);
|
|
279
|
+
if (entry.linkname.startsWith('/') ? !within(root, entry.linkname) : !within(stage, resolved)) throw archiveInvalid(`symlink ${entry.name} points outside its prefix`);
|
|
280
|
+
if (lstatExists(path)) unlinkSync(path);
|
|
281
|
+
symlinkSync(entry.linkname, path);
|
|
282
|
+
} else {
|
|
283
|
+
throw archiveInvalid(`entry ${entry.name} has type ${JSON.stringify(entry.type)}, which an import archive does not hold`);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
for (const [root, { stage }] of stages) placeStaged(stage, root);
|
|
287
|
+
} finally {
|
|
288
|
+
closeSync(fd);
|
|
289
|
+
for (const root of targets) rmSync(stagingOf(root), { recursive: true, force: true });
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/** The sandbox's own record that this node's archive was unpacked here: written after a complete unpack, synced to disk, so a sandbox that stopped and
|
|
294
|
+
* woke (or a restarted start script) never unpacks over the work done since. A fresh sandbox has none. */
|
|
295
|
+
const unpackMarker = (root, nodeId) => join(root, 'import-unpacked', nodeId);
|
|
296
|
+
|
|
297
|
+
function writeUnpackMarker(path) {
|
|
298
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
299
|
+
const fd = openSync(path, 'w');
|
|
300
|
+
try { writeSync(fd, `${new Date().toISOString()}\n`); fsyncSync(fd); } finally { closeSync(fd); }
|
|
301
|
+
const dir = openSync(dirname(path), 'r');
|
|
302
|
+
try { fsyncSync(dir); } finally { closeSync(dir); }
|
|
303
|
+
}
|
|
304
|
+
|
|
137
305
|
async function start(env) {
|
|
138
306
|
const need = (name) => { const v = env[name]; if (!v) throw new StartFailure('bad_environment', `${name} is not set`); return v; };
|
|
139
307
|
const apiUrl = need('CRTR_API_URL');
|
|
@@ -225,6 +393,21 @@ async function start(env) {
|
|
|
225
393
|
} finally { await relay?.close(); }
|
|
226
394
|
}
|
|
227
395
|
|
|
396
|
+
// 5.6b a hosted node's imported files (design-import, "Sandbox import step"). The control plane names the archive for as long as it exists, so a
|
|
397
|
+
// replacement sandbox gets it too; this sandbox unpacks only when it holds no marker of its own. The marker is written last: a start that fails
|
|
398
|
+
// anywhere before it (the download, a hostile entry, the report) leaves nothing half-unpacked in place and unpacks again on retry.
|
|
399
|
+
if (bundle.import_node_files) {
|
|
400
|
+
const marker = unpackMarker(root, bundle.node.id);
|
|
401
|
+
if (!existsSync(marker)) {
|
|
402
|
+
const scratch = mkdtempSync(join(tmpdir(), 'crtr-import-'));
|
|
403
|
+
try { await unpackImportedFiles(bundle.import_node_files, scratch); }
|
|
404
|
+
finally { rmSync(scratch, { recursive: true, force: true }); }
|
|
405
|
+
const reported = await api(apiUrl, token, 'POST', `/v1/executions/${executionId}/import-unpacked`, {}).catch((error) => ({ ok: false, status: String(error?.message ?? error) }));
|
|
406
|
+
if (!reported.ok) throw new StartFailure('import_report_failed', `reporting the unpack: ${reported.status}`);
|
|
407
|
+
writeUnpackMarker(marker);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
228
411
|
// 5.7 the managed worktree request (D102): the node's branch in the recipe repository's one clone, never a second checkout.
|
|
229
412
|
const worktree = bundle.launch.worktree;
|
|
230
413
|
if (worktree) {
|