@theford/runly 0.0.0-stage → 0.20.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/LICENSE +202 -0
- package/README.md +157 -2
- package/dist/cli/client.d.ts +80 -0
- package/dist/cli/client.js +294 -0
- package/dist/cli/client.js.map +1 -0
- package/dist/cli/daemon-unit.d.ts +58 -0
- package/dist/cli/daemon-unit.js +289 -0
- package/dist/cli/daemon-unit.js.map +1 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +1482 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/install.d.ts +9 -0
- package/dist/cli/install.js +53 -0
- package/dist/cli/install.js.map +1 -0
- package/dist/cli/logs.d.ts +43 -0
- package/dist/cli/logs.js +232 -0
- package/dist/cli/logs.js.map +1 -0
- package/dist/cli/tether.d.ts +3 -0
- package/dist/cli/tether.js +65 -0
- package/dist/cli/tether.js.map +1 -0
- package/dist/cli/watchdog.d.ts +1 -0
- package/dist/cli/watchdog.js +81 -0
- package/dist/cli/watchdog.js.map +1 -0
- package/dist/core/builds.d.ts +20 -0
- package/dist/core/builds.js +93 -0
- package/dist/core/builds.js.map +1 -0
- package/dist/core/caller-env.d.ts +17 -0
- package/dist/core/caller-env.js +77 -0
- package/dist/core/caller-env.js.map +1 -0
- package/dist/core/diagnostics.d.ts +68 -0
- package/dist/core/diagnostics.js +34 -0
- package/dist/core/diagnostics.js.map +1 -0
- package/dist/core/env-vars.d.ts +35 -0
- package/dist/core/env-vars.js +32 -0
- package/dist/core/env-vars.js.map +1 -0
- package/dist/core/events.d.ts +10 -0
- package/dist/core/events.js +42 -0
- package/dist/core/events.js.map +1 -0
- package/dist/core/exec.d.ts +22 -0
- package/dist/core/exec.js +84 -0
- package/dist/core/exec.js.map +1 -0
- package/dist/core/journal.d.ts +227 -0
- package/dist/core/journal.js +395 -0
- package/dist/core/journal.js.map +1 -0
- package/dist/core/logs.d.ts +56 -0
- package/dist/core/logs.js +259 -0
- package/dist/core/logs.js.map +1 -0
- package/dist/core/manifest.d.ts +292 -0
- package/dist/core/manifest.js +179 -0
- package/dist/core/manifest.js.map +1 -0
- package/dist/core/namespaces.d.ts +7 -0
- package/dist/core/namespaces.js +59 -0
- package/dist/core/namespaces.js.map +1 -0
- package/dist/core/paths.d.ts +31 -0
- package/dist/core/paths.js +65 -0
- package/dist/core/paths.js.map +1 -0
- package/dist/core/policy.d.ts +94 -0
- package/dist/core/policy.js +122 -0
- package/dist/core/policy.js.map +1 -0
- package/dist/core/ports.d.ts +52 -0
- package/dist/core/ports.js +94 -0
- package/dist/core/ports.js.map +1 -0
- package/dist/core/presets.d.ts +25 -0
- package/dist/core/presets.js +86 -0
- package/dist/core/presets.js.map +1 -0
- package/dist/core/procscan.d.ts +111 -0
- package/dist/core/procscan.js +342 -0
- package/dist/core/procscan.js.map +1 -0
- package/dist/core/retention.d.ts +97 -0
- package/dist/core/retention.js +322 -0
- package/dist/core/retention.js.map +1 -0
- package/dist/core/sleep.d.ts +51 -0
- package/dist/core/sleep.js +99 -0
- package/dist/core/sleep.js.map +1 -0
- package/dist/core/tree-ledger.d.ts +39 -0
- package/dist/core/tree-ledger.js +92 -0
- package/dist/core/tree-ledger.js.map +1 -0
- package/dist/core/types.d.ts +21 -0
- package/dist/core/types.js +6 -0
- package/dist/core/types.js.map +1 -0
- package/dist/core/units.d.ts +20 -0
- package/dist/core/units.js +69 -0
- package/dist/core/units.js.map +1 -0
- package/dist/core/upkeep.d.ts +90 -0
- package/dist/core/upkeep.js +176 -0
- package/dist/core/upkeep.js.map +1 -0
- package/dist/core/util.d.ts +46 -0
- package/dist/core/util.js +168 -0
- package/dist/core/util.js.map +1 -0
- package/dist/core/version.d.ts +39 -0
- package/dist/core/version.js +144 -0
- package/dist/core/version.js.map +1 -0
- package/dist/core/worktree.d.ts +51 -0
- package/dist/core/worktree.js +309 -0
- package/dist/core/worktree.js.map +1 -0
- package/dist/daemon/budget.d.ts +155 -0
- package/dist/daemon/budget.js +288 -0
- package/dist/daemon/budget.js.map +1 -0
- package/dist/daemon/election.d.ts +9 -0
- package/dist/daemon/election.js +140 -0
- package/dist/daemon/election.js.map +1 -0
- package/dist/daemon/engine.d.ts +1703 -0
- package/dist/daemon/engine.js +5305 -0
- package/dist/daemon/engine.js.map +1 -0
- package/dist/daemon/index.d.ts +2 -0
- package/dist/daemon/index.js +412 -0
- package/dist/daemon/index.js.map +1 -0
- package/dist/daemon/proxy.d.ts +108 -0
- package/dist/daemon/proxy.js +615 -0
- package/dist/daemon/proxy.js.map +1 -0
- package/dist/daemon/supervisor.d.ts +101 -0
- package/dist/daemon/supervisor.js +493 -0
- package/dist/daemon/supervisor.js.map +1 -0
- package/dist/drivers/appliances.d.ts +11 -0
- package/dist/drivers/appliances.js +174 -0
- package/dist/drivers/appliances.js.map +1 -0
- package/dist/drivers/datastores.d.ts +144 -0
- package/dist/drivers/datastores.js +775 -0
- package/dist/drivers/datastores.js.map +1 -0
- package/dist/drivers/preview.d.ts +69 -0
- package/dist/drivers/preview.js +605 -0
- package/dist/drivers/preview.js.map +1 -0
- package/dist/drivers/types.d.ts +70 -0
- package/dist/drivers/types.js +13 -0
- package/dist/drivers/types.js.map +1 -0
- package/package.json +63 -4
- package/schema/backlot.schema.json +549 -0
- package/schema/runly.schema.json +556 -0
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { createServer } from 'node:net';
|
|
2
|
+
import { readFileSync } from 'node:fs';
|
|
3
|
+
/**
|
|
4
|
+
* Is `port` genuinely free?
|
|
5
|
+
*
|
|
6
|
+
* Binding only 127.0.0.1 misses a foreign process listening on the WILDCARD
|
|
7
|
+
* address: on Linux that bind collides and reports busy, but on macOS it
|
|
8
|
+
* succeeds, so the "port occupied by a foreign process" guard never fired
|
|
9
|
+
* there and the service started into a port someone else already held.
|
|
10
|
+
* Probing the wildcard as well is what makes the two platforms agree.
|
|
11
|
+
*/
|
|
12
|
+
export function probeFree(port) {
|
|
13
|
+
const bind = (host) => new Promise((resolve) => {
|
|
14
|
+
const srv = createServer();
|
|
15
|
+
srv.once('error', () => resolve(false));
|
|
16
|
+
srv.listen(port, host, () => srv.close(() => resolve(true)));
|
|
17
|
+
});
|
|
18
|
+
return bind('127.0.0.1').then((loopback) => (loopback ? bind('0.0.0.0') : false));
|
|
19
|
+
}
|
|
20
|
+
export const DEFAULT_PUBLIC_BLOCK = { lo: 20000, hi: 29999 };
|
|
21
|
+
export const DEFAULT_INTERNAL_BLOCK = { lo: 30000, hi: 31999 };
|
|
22
|
+
export const DEFAULT_TUNNEL_BLOCK = { lo: 32000, hi: 32767 };
|
|
23
|
+
function blockFromEnv(name, fallback) {
|
|
24
|
+
const raw = process.env[name];
|
|
25
|
+
if (!raw)
|
|
26
|
+
return fallback;
|
|
27
|
+
const m = /^\s*(\d+)\s*-\s*(\d+)\s*$/.exec(raw);
|
|
28
|
+
if (!m)
|
|
29
|
+
return fallback;
|
|
30
|
+
const lo = Number(m[1]);
|
|
31
|
+
const hi = Number(m[2]);
|
|
32
|
+
if (!(lo >= 1024 && hi <= 65535 && lo <= hi))
|
|
33
|
+
return fallback;
|
|
34
|
+
return { lo, hi };
|
|
35
|
+
}
|
|
36
|
+
export const publicBlock = () => blockFromEnv('BACKLOT_PORT_RANGE', DEFAULT_PUBLIC_BLOCK);
|
|
37
|
+
export const internalBlock = () => blockFromEnv('BACKLOT_INTERNAL_PORT_RANGE', DEFAULT_INTERNAL_BLOCK);
|
|
38
|
+
export const tunnelBlock = () => blockFromEnv('BACKLOT_TUNNEL_PORT_RANGE', DEFAULT_TUNNEL_BLOCK);
|
|
39
|
+
export const inBlock = (port, block) => port >= block.lo && port <= block.hi;
|
|
40
|
+
/**
|
|
41
|
+
* The OS ephemeral (outbound source-port) range. Linux publishes it; other
|
|
42
|
+
* platforms get the IANA range their kernels use by default.
|
|
43
|
+
*/
|
|
44
|
+
export function ephemeralRange() {
|
|
45
|
+
try {
|
|
46
|
+
const [lo, hi] = readFileSync('/proc/sys/net/ipv4/ip_local_port_range', 'utf8').trim().split(/\s+/).map(Number);
|
|
47
|
+
if (lo && hi && lo <= hi)
|
|
48
|
+
return { lo, hi };
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
/* not Linux */
|
|
52
|
+
}
|
|
53
|
+
return { lo: 49152, hi: 65535 };
|
|
54
|
+
}
|
|
55
|
+
/** Human-readable overlaps of the configured blocks with each other and the ephemeral range. */
|
|
56
|
+
export function blockConflicts() {
|
|
57
|
+
const eph = ephemeralRange();
|
|
58
|
+
const blocks = [['public', publicBlock()], ['internal', internalBlock()], ['tunnel', tunnelBlock()]];
|
|
59
|
+
const overlap = (a, b) => a.lo <= b.hi && b.lo <= a.hi;
|
|
60
|
+
const out = [];
|
|
61
|
+
for (const [name, b] of blocks) {
|
|
62
|
+
if (overlap(b, eph))
|
|
63
|
+
out.push(`the ${name} port block ${b.lo}-${b.hi} overlaps the OS ephemeral range ${eph.lo}-${eph.hi}; ports in the overlap are skipped`);
|
|
64
|
+
}
|
|
65
|
+
for (let i = 0; i < blocks.length; i++) {
|
|
66
|
+
for (let j = i + 1; j < blocks.length; j++) {
|
|
67
|
+
const [an, a] = blocks[i];
|
|
68
|
+
const [bn, b] = blocks[j];
|
|
69
|
+
if (overlap(a, b))
|
|
70
|
+
out.push(`the ${an} port block ${a.lo}-${a.hi} overlaps the ${bn} block ${b.lo}-${b.hi}`);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return out;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* The first port of `block` that is not in `taken`, not ephemeral, and probes
|
|
77
|
+
* free, scanning from a random offset so concurrent daemons (and a daemon
|
|
78
|
+
* restarted with an empty memory) do not all start at the bottom. Returns
|
|
79
|
+
* undefined when the block has nothing left — the caller names the block.
|
|
80
|
+
*/
|
|
81
|
+
export async function allocateInBlock(block, taken, probe = probeFree) {
|
|
82
|
+
const eph = ephemeralRange();
|
|
83
|
+
const span = block.hi - block.lo + 1;
|
|
84
|
+
const start = Math.floor(Math.random() * span);
|
|
85
|
+
for (let i = 0; i < span; i++) {
|
|
86
|
+
const port = block.lo + ((start + i) % span);
|
|
87
|
+
if (taken.has(port) || inBlock(port, eph))
|
|
88
|
+
continue;
|
|
89
|
+
if (await probe(port))
|
|
90
|
+
return port;
|
|
91
|
+
}
|
|
92
|
+
return undefined;
|
|
93
|
+
}
|
|
94
|
+
//# sourceMappingURL=ports.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ports.js","sourceRoot":"","sources":["../../src/core/ports.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AACxC,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAEvC;;;;;;;;GAQG;AACH,MAAM,UAAU,SAAS,CAAC,IAAY;IACpC,MAAM,IAAI,GAAG,CAAC,IAAY,EAAE,EAAE,CAC5B,IAAI,OAAO,CAAU,CAAC,OAAO,EAAE,EAAE;QAC/B,MAAM,GAAG,GAAG,YAAY,EAAE,CAAC;QAC3B,GAAG,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC;QACxC,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAC/D,CAAC,CAAC,CAAC;IACL,OAAO,IAAI,CAAC,WAAW,CAAC,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC;AACpF,CAAC;AAwBD,MAAM,CAAC,MAAM,oBAAoB,GAAc,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,CAAC;AACxE,MAAM,CAAC,MAAM,sBAAsB,GAAc,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,CAAC;AAC1E,MAAM,CAAC,MAAM,oBAAoB,GAAc,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,CAAC;AAExE,SAAS,YAAY,CAAC,IAAY,EAAE,QAAmB;IACrD,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAC9B,IAAI,CAAC,GAAG;QAAE,OAAO,QAAQ,CAAC;IAC1B,MAAM,CAAC,GAAG,2BAA2B,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAChD,IAAI,CAAC,CAAC;QAAE,OAAO,QAAQ,CAAC;IACxB,MAAM,EAAE,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACxB,MAAM,EAAE,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACxB,IAAI,CAAC,CAAC,EAAE,IAAI,IAAI,IAAI,EAAE,IAAI,KAAK,IAAI,EAAE,IAAI,EAAE,CAAC;QAAE,OAAO,QAAQ,CAAC;IAC9D,OAAO,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC;AACpB,CAAC;AAED,MAAM,CAAC,MAAM,WAAW,GAAG,GAAc,EAAE,CAAC,YAAY,CAAC,oBAAoB,EAAE,oBAAoB,CAAC,CAAC;AACrG,MAAM,CAAC,MAAM,aAAa,GAAG,GAAc,EAAE,CAAC,YAAY,CAAC,6BAA6B,EAAE,sBAAsB,CAAC,CAAC;AAClH,MAAM,CAAC,MAAM,WAAW,GAAG,GAAc,EAAE,CAAC,YAAY,CAAC,2BAA2B,EAAE,oBAAoB,CAAC,CAAC;AAE5G,MAAM,CAAC,MAAM,OAAO,GAAG,CAAC,IAAY,EAAE,KAAgB,EAAW,EAAE,CAAC,IAAI,IAAI,KAAK,CAAC,EAAE,IAAI,IAAI,IAAI,KAAK,CAAC,EAAE,CAAC;AAEzG;;;GAGG;AACH,MAAM,UAAU,cAAc;IAC5B,IAAI,CAAC;QACH,MAAM,CAAC,EAAE,EAAE,EAAE,CAAC,GAAG,YAAY,CAAC,wCAAwC,EAAE,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QAChH,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE;YAAE,OAAO,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC;IAC9C,CAAC;IAAC,MAAM,CAAC;QACP,eAAe;IACjB,CAAC;IACD,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,CAAC;AAClC,CAAC;AAED,gGAAgG;AAChG,MAAM,UAAU,cAAc;IAC5B,MAAM,GAAG,GAAG,cAAc,EAAE,CAAC;IAC7B,MAAM,MAAM,GAA+B,CAAC,CAAC,QAAQ,EAAE,WAAW,EAAE,CAAC,EAAE,CAAC,UAAU,EAAE,aAAa,EAAE,CAAC,EAAE,CAAC,QAAQ,EAAE,WAAW,EAAE,CAAC,CAAC,CAAC;IACjI,MAAM,OAAO,GAAG,CAAC,CAAY,EAAE,CAAY,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,EAAE,CAAC;IAC7E,MAAM,GAAG,GAAa,EAAE,CAAC;IACzB,KAAK,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,MAAM,EAAE,CAAC;QAC/B,IAAI,OAAO,CAAC,CAAC,EAAE,GAAG,CAAC;YAAE,GAAG,CAAC,IAAI,CAAC,OAAO,IAAI,eAAe,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,EAAE,oCAAoC,GAAG,CAAC,EAAE,IAAI,GAAG,CAAC,EAAE,oCAAoC,CAAC,CAAC;IAChK,CAAC;IACD,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACvC,KAAK,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;YAC3C,MAAM,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,MAAM,CAAC,CAAC,CAAE,CAAC;YAC3B,MAAM,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,MAAM,CAAC,CAAC,CAAE,CAAC;YAC3B,IAAI,OAAO,CAAC,CAAC,EAAE,CAAC,CAAC;gBAAE,GAAG,CAAC,IAAI,CAAC,OAAO,EAAE,eAAe,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,EAAE,iBAAiB,EAAE,UAAU,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QAC/G,CAAC;IACH,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,KAAgB,EAAE,KAAkB,EAAE,QAA4C,SAAS;IAC/H,MAAM,GAAG,GAAG,cAAc,EAAE,CAAC;IAC7B,MAAM,IAAI,GAAG,KAAK,CAAC,EAAE,GAAG,KAAK,CAAC,EAAE,GAAG,CAAC,CAAC;IACrC,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,IAAI,CAAC,CAAC;IAC/C,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,EAAE,CAAC,EAAE,EAAE,CAAC;QAC9B,MAAM,IAAI,GAAG,KAAK,CAAC,EAAE,GAAG,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;QAC7C,IAAI,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC;YAAE,SAAS;QACpD,IAAI,MAAM,KAAK,CAAC,IAAI,CAAC;YAAE,OAAO,IAAI,CAAC;IACrC,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { type DatastoreSpec, type Manifest } from './manifest.js';
|
|
2
|
+
/** The presets a datastore offers. A manifest without a catalog keeps its historical default choices. */
|
|
3
|
+
export declare function presetCatalog(spec: DatastoreSpec): string[];
|
|
4
|
+
/** A datastore's default preset, validated against its catalog. */
|
|
5
|
+
export declare function defaultPresetFor(name: string, spec: DatastoreSpec): string;
|
|
6
|
+
/**
|
|
7
|
+
* Validate an explicit `--preset` request (decision 0034) and every declared
|
|
8
|
+
* default, before anything is claimed or changed. Returns ONLY the datastores
|
|
9
|
+
* the caller named: an explicit preset reloads that datastore from its
|
|
10
|
+
* template, and a datastore nobody named keeps whatever it holds.
|
|
11
|
+
*/
|
|
12
|
+
export declare function validatePresetRequest(manifest: Manifest, requested?: unknown, file?: string): Record<string, string>;
|
|
13
|
+
/**
|
|
14
|
+
* The preset a datastore is (re)created with when the caller named none: the
|
|
15
|
+
* one it holds, while the catalog still offers it, else the default. A store
|
|
16
|
+
* that is merely KEPT is never touched — this only decides what a restore that
|
|
17
|
+
* happens anyway (a reset, a first creation) restores.
|
|
18
|
+
*/
|
|
19
|
+
export declare function presetToRestore(name: string, spec: DatastoreSpec, held: string | undefined): string;
|
|
20
|
+
/**
|
|
21
|
+
* CLI shorthand is only unambiguous for a single datastore — of the ones an
|
|
22
|
+
* environment has: a `copies_only` datastore (decision 0039) is never reloaded
|
|
23
|
+
* by `up`/`reset-data`, so it does not make `--preset NAME` ambiguous.
|
|
24
|
+
*/
|
|
25
|
+
export declare function parsePresetArgs(manifest: Manifest, values: string[]): Record<string, string> | undefined;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { declaredDefaultPreset, defaultPreset, envDatastoreNames } from './manifest.js';
|
|
2
|
+
import { BrokerError } from './util.js';
|
|
3
|
+
/** The presets a datastore offers. A manifest without a catalog keeps its historical default choices. */
|
|
4
|
+
export function presetCatalog(spec) {
|
|
5
|
+
const declared = declaredDefaultPreset(spec);
|
|
6
|
+
return spec.presets?.length ? spec.presets : [...new Set(['default', ...(declared === undefined ? [] : [declared])])];
|
|
7
|
+
}
|
|
8
|
+
/** A datastore's default preset, validated against its catalog. */
|
|
9
|
+
export function defaultPresetFor(name, spec) {
|
|
10
|
+
const catalog = presetCatalog(spec);
|
|
11
|
+
const declared = declaredDefaultPreset(spec);
|
|
12
|
+
if (declared !== undefined && !catalog.includes(declared))
|
|
13
|
+
throw new BrokerError('work-error', `default preset '${declared}' for '${name}' is not declared in presets`, 'manifest');
|
|
14
|
+
const value = defaultPreset(spec);
|
|
15
|
+
if (!catalog.includes(value)) {
|
|
16
|
+
throw new BrokerError('work-error', `no preset '${value}' for datastore '${name}' (have: ${catalog.join(', ') || 'none'})`, 'manifest');
|
|
17
|
+
}
|
|
18
|
+
return value;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Validate an explicit `--preset` request (decision 0034) and every declared
|
|
22
|
+
* default, before anything is claimed or changed. Returns ONLY the datastores
|
|
23
|
+
* the caller named: an explicit preset reloads that datastore from its
|
|
24
|
+
* template, and a datastore nobody named keeps whatever it holds.
|
|
25
|
+
*/
|
|
26
|
+
export function validatePresetRequest(manifest, requested, file = 'runly.yml') {
|
|
27
|
+
const stores = manifest.datastores ?? {};
|
|
28
|
+
if (requested !== undefined && (requested === null || typeof requested !== 'object' || Array.isArray(requested))) {
|
|
29
|
+
throw new BrokerError('work-error', 'presets must map datastore names to preset names', 'manifest');
|
|
30
|
+
}
|
|
31
|
+
const choices = (requested ?? {});
|
|
32
|
+
for (const [name, value] of Object.entries(choices)) {
|
|
33
|
+
if (!Object.hasOwn(stores, name))
|
|
34
|
+
throw new BrokerError('work-error', `no datastore '${name}' in ${file}`, 'manifest');
|
|
35
|
+
if (typeof value !== 'string' || !value)
|
|
36
|
+
throw new BrokerError('work-error', `preset for '${name}' must be a nonempty name`, 'manifest');
|
|
37
|
+
}
|
|
38
|
+
const out = {};
|
|
39
|
+
for (const [name, spec] of Object.entries(stores)) {
|
|
40
|
+
defaultPresetFor(name, spec);
|
|
41
|
+
if (!Object.hasOwn(choices, name))
|
|
42
|
+
continue;
|
|
43
|
+
const value = choices[name];
|
|
44
|
+
const catalog = presetCatalog(spec);
|
|
45
|
+
if (!catalog.includes(value)) {
|
|
46
|
+
throw new BrokerError('work-error', `no preset '${value}' for datastore '${name}' (have: ${catalog.join(', ') || 'none'})`, 'manifest');
|
|
47
|
+
}
|
|
48
|
+
out[name] = value;
|
|
49
|
+
}
|
|
50
|
+
return out;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* The preset a datastore is (re)created with when the caller named none: the
|
|
54
|
+
* one it holds, while the catalog still offers it, else the default. A store
|
|
55
|
+
* that is merely KEPT is never touched — this only decides what a restore that
|
|
56
|
+
* happens anyway (a reset, a first creation) restores.
|
|
57
|
+
*/
|
|
58
|
+
export function presetToRestore(name, spec, held) {
|
|
59
|
+
if (held !== undefined && presetCatalog(spec).includes(held))
|
|
60
|
+
return held;
|
|
61
|
+
return defaultPresetFor(name, spec);
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* CLI shorthand is only unambiguous for a single datastore — of the ones an
|
|
65
|
+
* environment has: a `copies_only` datastore (decision 0039) is never reloaded
|
|
66
|
+
* by `up`/`reset-data`, so it does not make `--preset NAME` ambiguous.
|
|
67
|
+
*/
|
|
68
|
+
export function parsePresetArgs(manifest, values) {
|
|
69
|
+
if (values.length === 0)
|
|
70
|
+
return undefined;
|
|
71
|
+
const stores = envDatastoreNames(manifest);
|
|
72
|
+
const choices = Object.create(null);
|
|
73
|
+
for (const value of values) {
|
|
74
|
+
const at = value.indexOf('=');
|
|
75
|
+
if (at < 0 && stores.length !== 1) {
|
|
76
|
+
throw new BrokerError('work-error', `--preset NAME requires exactly one datastore an environment has (here: ${stores.join(', ') || 'none'}); use --preset DATASTORE=NAME`, 'manifest');
|
|
77
|
+
}
|
|
78
|
+
const name = at < 0 ? stores[0] : value.slice(0, at);
|
|
79
|
+
const preset = at < 0 ? value : value.slice(at + 1);
|
|
80
|
+
if (Object.hasOwn(choices, name))
|
|
81
|
+
throw new BrokerError('work-error', `preset for '${name}' was selected more than once`, 'manifest');
|
|
82
|
+
choices[name] = preset;
|
|
83
|
+
}
|
|
84
|
+
return choices;
|
|
85
|
+
}
|
|
86
|
+
//# sourceMappingURL=presets.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"presets.js","sourceRoot":"","sources":["../../src/core/presets.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
export declare const ENV_TAG = "BACKLOT_ENV_ID";
|
|
2
|
+
export declare const SERVICE_TAG = "BACKLOT_SERVICE";
|
|
3
|
+
/** Scopes a tag to one state root so parallel installs never reap each other. */
|
|
4
|
+
export declare const ROOT_TAG = "BACKLOT_STATE_ROOT";
|
|
5
|
+
export interface TaggedProc {
|
|
6
|
+
pid: number;
|
|
7
|
+
envId: string;
|
|
8
|
+
service: string;
|
|
9
|
+
startTime: number;
|
|
10
|
+
}
|
|
11
|
+
/** The environment every supervised service is spawned with. */
|
|
12
|
+
/** The service name `runly exec`'s command is tagged with; never reaped (see scanTagged). */
|
|
13
|
+
export declare const EXEC_SERVICE = "exec";
|
|
14
|
+
export declare function serviceTag(envId: string, service: string, stateRoot: string): Record<string, string>;
|
|
15
|
+
export declare const procScanSupported: () => boolean;
|
|
16
|
+
/**
|
|
17
|
+
* Field 22 of /proc/<pid>/stat: the process start time in clock ticks since
|
|
18
|
+
* boot. Unique per pid *life*, so (pid, startTime) survives pid reuse.
|
|
19
|
+
*
|
|
20
|
+
* comm (field 2) is an arbitrary string wrapped in parens and may itself
|
|
21
|
+
* contain spaces or parens, so parse after the LAST ')' rather than splitting
|
|
22
|
+
* the whole line.
|
|
23
|
+
*/
|
|
24
|
+
export declare function startTime(pid: number): number | undefined;
|
|
25
|
+
/**
|
|
26
|
+
* Field 5 of /proc/<pid>/stat: the process group id.
|
|
27
|
+
*
|
|
28
|
+
* `sh -c <cmd>` does NOT reliably exec: dash forks for anything non-trivial,
|
|
29
|
+
* so the pid runly records is often only the wrapper, and the real server is
|
|
30
|
+
* a sibling in the same group. Killing "the leader" and then checking the
|
|
31
|
+
* leader's liveness therefore reports success while the server keeps running —
|
|
32
|
+
* group membership is the only honest liveness question.
|
|
33
|
+
*/
|
|
34
|
+
export declare function processGroup(pid: number): number | undefined;
|
|
35
|
+
/**
|
|
36
|
+
* Is ANY process still in this group?
|
|
37
|
+
*
|
|
38
|
+
* Without /proc, `kill(-pgid, 0)` is still a genuine GROUP query — ESRCH means
|
|
39
|
+
* the group is empty. Checking the leader instead would report "gone" the
|
|
40
|
+
* moment an `sh -c` wrapper exited, while the real server kept running, which
|
|
41
|
+
* is the whole failure this function exists to detect.
|
|
42
|
+
*/
|
|
43
|
+
export declare function groupAlive(pgid: number): boolean;
|
|
44
|
+
/** Is this pid alive at all? Says nothing about whose it is. */
|
|
45
|
+
export declare function isAlive(pid: number): boolean;
|
|
46
|
+
/**
|
|
47
|
+
* Is pid N still the same process that was recorded with `recordedStart`?
|
|
48
|
+
*
|
|
49
|
+
* With no recorded start time (a journal written by an older runly, or a
|
|
50
|
+
* non-Linux host) this can only fall back to liveness, which is exactly the
|
|
51
|
+
* pid-reuse hazard this exists to close — so callers should treat an
|
|
52
|
+
* un-verifiable pid as un-signallable where a mistake would be costly.
|
|
53
|
+
*/
|
|
54
|
+
export declare function sameProcess(pid: number, recordedStart?: number): boolean;
|
|
55
|
+
/**
|
|
56
|
+
* Every live process tagged as belonging to this state root.
|
|
57
|
+
*
|
|
58
|
+
* Note this finds *descendants* too — an `ng serve` inherits the tag from the
|
|
59
|
+
* `sh -c` wrapper runly spawned, which is the whole point: after the wrapper
|
|
60
|
+
* dies the child is still identifiable.
|
|
61
|
+
*/
|
|
62
|
+
export declare function scanTagged(stateRoot: string): TaggedProc[];
|
|
63
|
+
/** Carried by `runly db with`'s command and its descendants: the copy it runs against (decision 0039). */
|
|
64
|
+
export declare const DB_COPY_TAG = "BACKLOT_DB_COPY";
|
|
65
|
+
/** The environment `runly db with` runs its command with, so the daemon can find it again. */
|
|
66
|
+
export declare function dbCopyTag(copy: string, stateRoot: string): Record<string, string>;
|
|
67
|
+
/**
|
|
68
|
+
* Every live process of this state root running against database copy
|
|
69
|
+
* `copy` — `runly db with`'s command and whatever it started (the tag is
|
|
70
|
+
* inherited). Linux only, like `scanTagged`; elsewhere the recorded pid is
|
|
71
|
+
* all there is.
|
|
72
|
+
*/
|
|
73
|
+
export declare function scanDbCopy(copy: string, stateRoot: string): Array<{
|
|
74
|
+
pid: number;
|
|
75
|
+
startTime: number;
|
|
76
|
+
}>;
|
|
77
|
+
/**
|
|
78
|
+
* Every live process whose working directory sits inside `prefix` AND which has
|
|
79
|
+
* no controlling terminal.
|
|
80
|
+
*
|
|
81
|
+
* The tag scan above misses a descendant that scrubbed its environment (a
|
|
82
|
+
* process launched through `env -i`, a re-exec that rebuilds environ, some
|
|
83
|
+
* language runtimes' worker pools). Those still have to live SOMEWHERE. Under
|
|
84
|
+
* the old projection that somewhere was the environment's own tree; since
|
|
85
|
+
* decision 0032 services run in the caller's worktree, where cwd proves
|
|
86
|
+
* nothing, so the engine only ever points this at an environment's PRIVATE
|
|
87
|
+
* directory (see Engine.reapEnvTree).
|
|
88
|
+
*
|
|
89
|
+
* Linux keeps the cwd link readable after the directory is unlinked, appending
|
|
90
|
+
* " (deleted)" to the target — which is precisely the shape of the leak this
|
|
91
|
+
* exists to catch: hundreds of service children still running out of an
|
|
92
|
+
* environment tree that was removed a day earlier. The suffix is stripped so a
|
|
93
|
+
* deleted tree still matches its prefix.
|
|
94
|
+
*
|
|
95
|
+
* The tty exclusion is not a nicety. cwd is NOT proof of ownership — a developer
|
|
96
|
+
* who runs `cd <env-tree>` to look around matches this scan exactly, and callers
|
|
97
|
+
* signal a matched process's whole GROUP, so without the exclusion a teardown
|
|
98
|
+
* would kill that person's shell and every job in it. That is the one outcome
|
|
99
|
+
* this module exists to prevent (see the header): skipping a sweep is safe,
|
|
100
|
+
* signalling a stranger's process is not. A runly service can never be
|
|
101
|
+
* excluded by it, because it never has a terminal to begin with.
|
|
102
|
+
*
|
|
103
|
+
* Even so, deliberately NOT used on the quiesce path, and never on a worktree:
|
|
104
|
+
* only teardown, which is about to delete the environment's private directory
|
|
105
|
+
* anyway, may reap by cwd — and only inside that directory.
|
|
106
|
+
*/
|
|
107
|
+
export declare function scanByCwd(prefix: string): Array<{
|
|
108
|
+
pid: number;
|
|
109
|
+
startTime: number;
|
|
110
|
+
cwd: string;
|
|
111
|
+
}>;
|