pinnace 0.0.0 → 0.2.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 +661 -0
- package/README.md +164 -0
- package/dist/car/car-build.d.ts +26 -0
- package/dist/car/car-build.d.ts.map +1 -0
- package/dist/car/car-build.js +113 -0
- package/dist/car/car-build.js.map +1 -0
- package/dist/ci/ci-emit.d.ts +116 -0
- package/dist/ci/ci-emit.d.ts.map +1 -0
- package/dist/ci/ci-emit.js +190 -0
- package/dist/ci/ci-emit.js.map +1 -0
- package/dist/cli/bin.d.ts +3 -0
- package/dist/cli/bin.d.ts.map +1 -0
- package/dist/cli/bin.js +17 -0
- package/dist/cli/bin.js.map +1 -0
- package/dist/cli/run.d.ts +94 -0
- package/dist/cli/run.d.ts.map +1 -0
- package/dist/cli/run.js +731 -0
- package/dist/cli/run.js.map +1 -0
- package/dist/config/config-resolution.d.ts +186 -0
- package/dist/config/config-resolution.d.ts.map +1 -0
- package/dist/config/config-resolution.js +137 -0
- package/dist/config/config-resolution.js.map +1 -0
- package/dist/deploy/deploy.d.ts +121 -0
- package/dist/deploy/deploy.d.ts.map +1 -0
- package/dist/deploy/deploy.js +150 -0
- package/dist/deploy/deploy.js.map +1 -0
- package/dist/derive/ipns-key-derivation.d.ts +37 -0
- package/dist/derive/ipns-key-derivation.d.ts.map +1 -0
- package/dist/derive/ipns-key-derivation.js +130 -0
- package/dist/derive/ipns-key-derivation.js.map +1 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +29 -0
- package/dist/index.js.map +1 -0
- package/dist/node/node-commands.d.ts +115 -0
- package/dist/node/node-commands.d.ts.map +1 -0
- package/dist/node/node-commands.js +231 -0
- package/dist/node/node-commands.js.map +1 -0
- package/dist/provision/cloud-init.d.ts +162 -0
- package/dist/provision/cloud-init.d.ts.map +1 -0
- package/dist/provision/cloud-init.js +460 -0
- package/dist/provision/cloud-init.js.map +1 -0
- package/dist/publisher/key-import.d.ts +105 -0
- package/dist/publisher/key-import.d.ts.map +1 -0
- package/dist/publisher/key-import.js +76 -0
- package/dist/publisher/key-import.js.map +1 -0
- package/dist/publisher/record-sequence.d.ts +90 -0
- package/dist/publisher/record-sequence.d.ts.map +1 -0
- package/dist/publisher/record-sequence.js +250 -0
- package/dist/publisher/record-sequence.js.map +1 -0
- package/dist/rpc/kubo-rpc-client.d.ts +154 -0
- package/dist/rpc/kubo-rpc-client.d.ts.map +1 -0
- package/dist/rpc/kubo-rpc-client.js +207 -0
- package/dist/rpc/kubo-rpc-client.js.map +1 -0
- package/dist/rpc/mock-kubo.d.ts +87 -0
- package/dist/rpc/mock-kubo.d.ts.map +1 -0
- package/dist/rpc/mock-kubo.js +129 -0
- package/dist/rpc/mock-kubo.js.map +1 -0
- package/dist/site/site-management.d.ts +126 -0
- package/dist/site/site-management.d.ts.map +1 -0
- package/dist/site/site-management.js +123 -0
- package/dist/site/site-management.js.map +1 -0
- package/dist/status/status-report.d.ts +145 -0
- package/dist/status/status-report.d.ts.map +1 -0
- package/dist/status/status-report.js +192 -0
- package/dist/status/status-report.js.map +1 -0
- package/package.json +45 -2
- package/src/car/car-build.ts +135 -0
- package/src/ci/ci-emit.ts +284 -0
- package/src/cli/bin.ts +20 -0
- package/src/cli/run.ts +970 -0
- package/src/config/config-resolution.ts +264 -0
- package/src/deploy/deploy.ts +250 -0
- package/src/derive/ipns-key-derivation.ts +173 -0
- package/src/index.ts +146 -0
- package/src/node/node-commands.ts +395 -0
- package/src/provision/cloud-init.ts +646 -0
- package/src/publisher/key-import.ts +141 -0
- package/src/publisher/record-sequence.ts +336 -0
- package/src/rpc/kubo-rpc-client.ts +281 -0
- package/src/rpc/mock-kubo.ts +194 -0
- package/src/site/site-management.ts +241 -0
- package/src/status/status-report.ts +291 -0
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Host-agnostic **deploy** — build one CAR and land the SAME CID on every node.
|
|
3
|
+
*
|
|
4
|
+
* This is the core operation user stories 4, 6, 7 describe: build a site's
|
|
5
|
+
* **CAR** ONCE (via the `car-build` seam), then fan out across every configured
|
|
6
|
+
* **node** (each with its OWN bearer token) so all nodes serve the IDENTICAL
|
|
7
|
+
* **CID** with no single point of failure. Per node the flow is: import + pin
|
|
8
|
+
* the CAR (`dag/import?pin-roots=true`), then place it in MFS at `/sites/<id>`
|
|
9
|
+
* (mkdir parents / rm old / cp `/ipfs/<cid>`) so `gateway warming`, IPNS
|
|
10
|
+
* republish, and `status` auto-discover it. Deploy speaks ONLY the Kubo RPC seam
|
|
11
|
+
* ({@link KuboRpcClient}); it is not host-specific.
|
|
12
|
+
*
|
|
13
|
+
* Behaviour ported (not copied) from the reference prototype
|
|
14
|
+
* `~/searches/ipfs-hetzner/deploy-car.mjs`: the multi-target fan-out, the MFS
|
|
15
|
+
* placement, the per-`mode` branch, and the `Promise.allSettled` partial-failure
|
|
16
|
+
* semantics (a non-empty subset of nodes succeeding is still an overall success:
|
|
17
|
+
* "the site is up on the rest").
|
|
18
|
+
*
|
|
19
|
+
* PER-SITE `mode` BRANCH (CONTEXT.md `mode`; spec's "Per-site mode branch"):
|
|
20
|
+
* - `ipfs` mode: land + pin + MFS ONLY (no key, no publish). ENS uses
|
|
21
|
+
* `ipfs://<cid>`, updated per deploy.
|
|
22
|
+
* - `ipns` mode: everything above PLUS the publish path (`key/list` then
|
|
23
|
+
* `name/publish`) and ONLY on a PUBLISHER target. A `replica` (or a target
|
|
24
|
+
* with {@link DeployTarget.publish} disabled) NEVER signs — it re-announces
|
|
25
|
+
* the publisher's record via the on-box `mirror` verb instead. This mirrors
|
|
26
|
+
* the prototype's `doPublish = mode === "ipns" && PUBLISH_IPNS` gate, with
|
|
27
|
+
* role standing in for the prototype's `PUBLISH_IPNS=0` replica flag.
|
|
28
|
+
*
|
|
29
|
+
* SEAM BOUNDARY (see the sibling tasks): this module wires the per-mode call
|
|
30
|
+
* SEQUENCE only. The full publisher/replica record export+mirror lives in
|
|
31
|
+
* `publisher-replica-model`, and importing the derived key into the publisher's
|
|
32
|
+
* keystore lives in `key-import-publisher`. So deploy here does NOT `key/gen`
|
|
33
|
+
* or `key/import` — it assumes the publisher already holds the key (looked up
|
|
34
|
+
* via `key/list`) and issues `name/publish`. If a publisher has no matching key
|
|
35
|
+
* yet, the publish is reported as skipped rather than silently generating one
|
|
36
|
+
* (key provisioning is that other task's job, not deploy's).
|
|
37
|
+
*/
|
|
38
|
+
import { KuboRpcClient } from '../rpc/kubo-rpc-client.js';
|
|
39
|
+
import { buildCar } from '../car/car-build.js';
|
|
40
|
+
import { placeInMfs } from '../site/site-management.js';
|
|
41
|
+
/** The MFS directory sites live under (matches site-management). */
|
|
42
|
+
const DEFAULT_SITES_DIR = '/sites';
|
|
43
|
+
/** Reference record values (ported from the prototype / on-box republish). */
|
|
44
|
+
const RECORD_LIFETIME = '72h';
|
|
45
|
+
const RECORD_TTL = '1h';
|
|
46
|
+
/**
|
|
47
|
+
* Deploy a site: build the CAR ONCE, then import the SAME CAR into every target
|
|
48
|
+
* (each with its own token), pin it, place it in MFS, and (in `ipns` mode, on
|
|
49
|
+
* publisher targets) publish the IPNS record. Fans out with
|
|
50
|
+
* `Promise.allSettled`: a node that fails is reported in {@link DeployResult.failed}
|
|
51
|
+
* and does not sink the others; a non-empty success subset is still an overall
|
|
52
|
+
* success ({@link DeployResult.success}). Throws only if NO node succeeds is
|
|
53
|
+
* NOT the contract here — deploy always RESOLVES with the per-node breakdown;
|
|
54
|
+
* callers inspect `success`.
|
|
55
|
+
*
|
|
56
|
+
* @throws if neither `sourceDir` nor `car` is supplied, or both are.
|
|
57
|
+
*/
|
|
58
|
+
export async function deploy(input) {
|
|
59
|
+
const { sourceDir, car, id, mode, targets } = input;
|
|
60
|
+
if ((sourceDir === undefined) === (car === undefined)) {
|
|
61
|
+
throw new Error('deploy requires exactly one of `sourceDir` or `car`');
|
|
62
|
+
}
|
|
63
|
+
const sitesDir = input.sitesDir ?? DEFAULT_SITES_DIR;
|
|
64
|
+
// Build the CAR ONCE — the same bytes (and thus the same CID) land on every
|
|
65
|
+
// node (redundancy, no single point of failure).
|
|
66
|
+
const built = car ?? (await buildCar(sourceDir));
|
|
67
|
+
// Fan out. allSettled so one node's failure never sinks the others.
|
|
68
|
+
const settled = await Promise.allSettled(targets.map((target) => deployToNode(target, built, id, mode, sitesDir)));
|
|
69
|
+
const ok = [];
|
|
70
|
+
const failed = [];
|
|
71
|
+
settled.forEach((outcome, i) => {
|
|
72
|
+
const baseUrl = targets[i].baseUrl;
|
|
73
|
+
if (outcome.status === 'fulfilled') {
|
|
74
|
+
ok.push(outcome.value);
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
failed.push({ baseUrl, error: asError(outcome.reason) });
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
return {
|
|
81
|
+
cid: built.rootCid,
|
|
82
|
+
mode,
|
|
83
|
+
ok,
|
|
84
|
+
failed,
|
|
85
|
+
success: ok.length > 0,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Deploy the (already-built) CAR to ONE node: import + pin, place in MFS, then
|
|
90
|
+
* (in ipns mode on a publishing publisher) key/list + name/publish. Rejects on
|
|
91
|
+
* any RPC failure so the caller's allSettled records it as a per-node failure.
|
|
92
|
+
*/
|
|
93
|
+
async function deployToNode(target, built, id, mode, sitesDir) {
|
|
94
|
+
const client = new KuboRpcClient({
|
|
95
|
+
baseUrl: target.baseUrl,
|
|
96
|
+
token: target.token,
|
|
97
|
+
fetchImpl: target.fetchImpl,
|
|
98
|
+
});
|
|
99
|
+
// 1. Import + pin the CAR (same CID as every other node).
|
|
100
|
+
await client.dagImport(built.carBytes);
|
|
101
|
+
// 2. Place it in MFS /sites/<id> (mkdir parents / rm old / cp /ipfs/<cid>).
|
|
102
|
+
// Reuses the single implementation of that sequence (site-management).
|
|
103
|
+
await placeInMfs(client, sitesDir, id, built.rootCid);
|
|
104
|
+
// 3. Mode branch: ipns mode ADDS publish, and ONLY on a publishing publisher.
|
|
105
|
+
if (mode === 'ipns' && shouldPublish(target)) {
|
|
106
|
+
const ipns = await publish(client, id, built.rootCid);
|
|
107
|
+
return { baseUrl: target.baseUrl, cid: built.rootCid, ipns, published: true };
|
|
108
|
+
}
|
|
109
|
+
return { baseUrl: target.baseUrl, cid: built.rootCid, published: false };
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Whether this target signs the IPNS record: a `publisher` whose publish switch
|
|
113
|
+
* is not explicitly disabled. A `replica` NEVER publishes (it re-announces the
|
|
114
|
+
* publisher's signed record via the on-box `mirror` verb instead).
|
|
115
|
+
*/
|
|
116
|
+
function shouldPublish(target) {
|
|
117
|
+
return target.role === 'publisher' && target.publish !== false;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* The publish path (ipns mode, publisher only): `key/list` to resolve the site
|
|
121
|
+
* key's IPNS id, then `name/publish` to sign+refresh the record for
|
|
122
|
+
* `/ipfs/<cid>`. The keystore key name is the site's single `id` (the same
|
|
123
|
+
* value key-import imports under — one identifier, so the lookup cannot miss by
|
|
124
|
+
* a name/keyId split). Does NOT `key/gen`/`key/import`: the key is provisioned
|
|
125
|
+
* by the sibling `key-import-publisher` task; here we assume it exists. If it
|
|
126
|
+
* does not yet, we skip the publish (returning undefined) rather than silently
|
|
127
|
+
* generating a key deploy has no business owning.
|
|
128
|
+
*/
|
|
129
|
+
async function publish(client, id, cid) {
|
|
130
|
+
const keys = await client.keyList();
|
|
131
|
+
const ipns = (keys.Keys ?? []).find((k) => k?.Name === id)?.Id;
|
|
132
|
+
if (!ipns) {
|
|
133
|
+
// The publisher has no key for this site yet (key-import-publisher owns
|
|
134
|
+
// provisioning it). Land the content but do not sign.
|
|
135
|
+
return undefined;
|
|
136
|
+
}
|
|
137
|
+
await client.namePublish({
|
|
138
|
+
cidPath: `/ipfs/${cid}`,
|
|
139
|
+
key: id,
|
|
140
|
+
lifetime: RECORD_LIFETIME,
|
|
141
|
+
ttl: RECORD_TTL,
|
|
142
|
+
allowOffline: true,
|
|
143
|
+
});
|
|
144
|
+
return ipns;
|
|
145
|
+
}
|
|
146
|
+
/** Coerce an unknown rejection reason into an Error. */
|
|
147
|
+
function asError(reason) {
|
|
148
|
+
return reason instanceof Error ? reason : new Error(String(reason));
|
|
149
|
+
}
|
|
150
|
+
//# sourceMappingURL=deploy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deploy.js","sourceRoot":"","sources":["../../src/deploy/deploy.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AACH,OAAO,EAAC,aAAa,EAAiB,MAAM,2BAA2B,CAAC;AACxE,OAAO,EAAC,QAAQ,EAAgB,MAAM,qBAAqB,CAAC;AAC5D,OAAO,EAAC,UAAU,EAAC,MAAM,4BAA4B,CAAC;AAGtD,oEAAoE;AACpE,MAAM,iBAAiB,GAAG,QAAQ,CAAC;AAEnC,8EAA8E;AAC9E,MAAM,eAAe,GAAG,KAAK,CAAC;AAC9B,MAAM,UAAU,GAAG,IAAI,CAAC;AA0ExB;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,KAAK,UAAU,MAAM,CAAC,KAAkB;IAC9C,MAAM,EAAC,SAAS,EAAE,GAAG,EAAE,EAAE,EAAE,IAAI,EAAE,OAAO,EAAC,GAAG,KAAK,CAAC;IAClD,IAAI,CAAC,SAAS,KAAK,SAAS,CAAC,KAAK,CAAC,GAAG,KAAK,SAAS,CAAC,EAAE,CAAC;QACvD,MAAM,IAAI,KAAK,CAAC,qDAAqD,CAAC,CAAC;IACxE,CAAC;IACD,MAAM,QAAQ,GAAG,KAAK,CAAC,QAAQ,IAAI,iBAAiB,CAAC;IAErD,4EAA4E;IAC5E,iDAAiD;IACjD,MAAM,KAAK,GAAa,GAAG,IAAI,CAAC,MAAM,QAAQ,CAAC,SAAmB,CAAC,CAAC,CAAC;IAErE,oEAAoE;IACpE,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,UAAU,CACvC,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,YAAY,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE,EAAE,IAAI,EAAE,QAAQ,CAAC,CAAC,CACxE,CAAC;IAEF,MAAM,EAAE,GAAmB,EAAE,CAAC;IAC9B,MAAM,MAAM,GAAwB,EAAE,CAAC;IACvC,OAAO,CAAC,OAAO,CAAC,CAAC,OAAO,EAAE,CAAC,EAAE,EAAE;QAC9B,MAAM,OAAO,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC;QACnC,IAAI,OAAO,CAAC,MAAM,KAAK,WAAW,EAAE,CAAC;YACpC,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;QACxB,CAAC;aAAM,CAAC;YACP,MAAM,CAAC,IAAI,CAAC,EAAC,OAAO,EAAE,KAAK,EAAE,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,EAAC,CAAC,CAAC;QACxD,CAAC;IACF,CAAC,CAAC,CAAC;IAEH,OAAO;QACN,GAAG,EAAE,KAAK,CAAC,OAAO;QAClB,IAAI;QACJ,EAAE;QACF,MAAM;QACN,OAAO,EAAE,EAAE,CAAC,MAAM,GAAG,CAAC;KACtB,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,KAAK,UAAU,YAAY,CAC1B,MAAoB,EACpB,KAAe,EACf,EAAU,EACV,IAAc,EACd,QAAgB;IAEhB,MAAM,MAAM,GAAG,IAAI,aAAa,CAAC;QAChC,OAAO,EAAE,MAAM,CAAC,OAAO;QACvB,KAAK,EAAE,MAAM,CAAC,KAAK;QACnB,SAAS,EAAE,MAAM,CAAC,SAAS;KAC3B,CAAC,CAAC;IAEH,0DAA0D;IAC1D,MAAM,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;IAEvC,4EAA4E;IAC5E,0EAA0E;IAC1E,MAAM,UAAU,CAAC,MAAM,EAAE,QAAQ,EAAE,EAAE,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC;IAEtD,8EAA8E;IAC9E,IAAI,IAAI,KAAK,MAAM,IAAI,aAAa,CAAC,MAAM,CAAC,EAAE,CAAC;QAC9C,MAAM,IAAI,GAAG,MAAM,OAAO,CAAC,MAAM,EAAE,EAAE,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC;QACtD,OAAO,EAAC,OAAO,EAAE,MAAM,CAAC,OAAO,EAAE,GAAG,EAAE,KAAK,CAAC,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAC,CAAC;IAC7E,CAAC;IAED,OAAO,EAAC,OAAO,EAAE,MAAM,CAAC,OAAO,EAAE,GAAG,EAAE,KAAK,CAAC,OAAO,EAAE,SAAS,EAAE,KAAK,EAAC,CAAC;AACxE,CAAC;AAED;;;;GAIG;AACH,SAAS,aAAa,CAAC,MAAoB;IAC1C,OAAO,MAAM,CAAC,IAAI,KAAK,WAAW,IAAI,MAAM,CAAC,OAAO,KAAK,KAAK,CAAC;AAChE,CAAC;AAED;;;;;;;;;GASG;AACH,KAAK,UAAU,OAAO,CACrB,MAAqB,EACrB,EAAU,EACV,GAAW;IAEX,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,OAAO,EAE7B,CAAC;IACL,MAAM,IAAI,GAAG,CAAC,IAAI,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,KAAK,EAAE,CAAC,EAAE,EAAE,CAAC;IAC/D,IAAI,CAAC,IAAI,EAAE,CAAC;QACX,wEAAwE;QACxE,sDAAsD;QACtD,OAAO,SAAS,CAAC;IAClB,CAAC;IACD,MAAM,MAAM,CAAC,WAAW,CAAC;QACxB,OAAO,EAAE,SAAS,GAAG,EAAE;QACvB,GAAG,EAAE,EAAE;QACP,QAAQ,EAAE,eAAe;QACzB,GAAG,EAAE,UAAU;QACf,YAAY,EAAE,IAAI;KAClB,CAAC,CAAC;IACH,OAAO,IAAI,CAAC;AACb,CAAC;AAED,wDAAwD;AACxD,SAAS,OAAO,CAAC,MAAe;IAC/B,OAAO,MAAM,YAAY,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC;AACrE,CAAC"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/** The frozen `info`-string prefix. The version (`v1`) is encoded HERE. */
|
|
2
|
+
export declare const IPNS_INFO_PREFIX = "pinnace:ipns:v1:";
|
|
3
|
+
/** The master secret: a UTF-8 string or raw bytes. Never read from a node/file. */
|
|
4
|
+
export type Master = string | Uint8Array;
|
|
5
|
+
/** Inputs to the derivation: the master secret and the frozen per-site keyId. */
|
|
6
|
+
export interface DeriveIpnsInput {
|
|
7
|
+
/** The operator master secret (env-only in production; see config module). */
|
|
8
|
+
master: Master;
|
|
9
|
+
/**
|
|
10
|
+
* The site's frozen, internal key identity — the SOLE per-site KDF input.
|
|
11
|
+
* NOT the ENS name (which is mutable and never enters derivation).
|
|
12
|
+
*/
|
|
13
|
+
keyId: string;
|
|
14
|
+
}
|
|
15
|
+
/** A fully derived per-site IPNS key. */
|
|
16
|
+
export interface DerivedIpnsKey {
|
|
17
|
+
/** The 32-byte ed25519 private seed (the raw HKDF output). */
|
|
18
|
+
readonly seed: Uint8Array;
|
|
19
|
+
/** The 32-byte raw ed25519 public key. */
|
|
20
|
+
readonly publicKey: Uint8Array;
|
|
21
|
+
/** The IPNS name: a CIDv1 (libp2p-key, base36) `k51...` id. */
|
|
22
|
+
readonly ipnsId: string;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Derive the full per-site IPNS key (seed + public key + `k51...` id) from the
|
|
26
|
+
* master secret and the frozen `keyId`. Pure and deterministic — no node,
|
|
27
|
+
* network, or deploy. This is the FROZEN CONTRACT; see the module doc.
|
|
28
|
+
*/
|
|
29
|
+
export declare function deriveIpnsKey(input: DeriveIpnsInput): DerivedIpnsKey;
|
|
30
|
+
/**
|
|
31
|
+
* Derive-and-print path (user story 22): return a site's `k51...` IPNS id from
|
|
32
|
+
* the master + keyId with NO deploy and NO network, so an operator can set the
|
|
33
|
+
* ENS contenthash to `ipns://<id>` before the first deploy. The id depends ONLY
|
|
34
|
+
* on (master, keyId) — never the ENS name.
|
|
35
|
+
*/
|
|
36
|
+
export declare function deriveIpnsId(input: DeriveIpnsInput): string;
|
|
37
|
+
//# sourceMappingURL=ipns-key-derivation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ipns-key-derivation.d.ts","sourceRoot":"","sources":["../../src/derive/ipns-key-derivation.ts"],"names":[],"mappings":"AAgCA,2EAA2E;AAC3E,eAAO,MAAM,gBAAgB,qBAAqB,CAAC;AAEnD,mFAAmF;AACnF,MAAM,MAAM,MAAM,GAAG,MAAM,GAAG,UAAU,CAAC;AAEzC,iFAAiF;AACjF,MAAM,WAAW,eAAe;IAC/B,8EAA8E;IAC9E,MAAM,EAAE,MAAM,CAAC;IACf;;;OAGG;IACH,KAAK,EAAE,MAAM,CAAC;CACd;AAED,yCAAyC;AACzC,MAAM,WAAW,cAAc;IAC9B,8DAA8D;IAC9D,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAC1B,0CAA0C;IAC1C,QAAQ,CAAC,SAAS,EAAE,UAAU,CAAC;IAC/B,+DAA+D;IAC/D,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACxB;AA+FD;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,eAAe,GAAG,cAAc,CAKpE;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,eAAe,GAAG,MAAM,CAE3D"}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Master-key -> per-site IPNS key derivation. **THIS IS A FROZEN CONTRACT.**
|
|
3
|
+
*
|
|
4
|
+
* Every `ipns`-mode site's key is derived deterministically from one operator
|
|
5
|
+
* master secret, so names are recoverable from the master alone and
|
|
6
|
+
* provisioning is stateless (CONTEXT.md `master key`, `keyId`). The scheme,
|
|
7
|
+
* pinned FOREVER by docs/adr/0001-frozen-ipns-key-derivation.md:
|
|
8
|
+
*
|
|
9
|
+
* seed = HKDF-SHA256(ikm = master, salt = "", info = "pinnace:ipns:v1:" + keyId, length = 32)
|
|
10
|
+
* -> the 32 bytes ARE the ed25519 private seed
|
|
11
|
+
* -> its public key IS the IPNS name, rendered as a CIDv1 `k51...` id.
|
|
12
|
+
*
|
|
13
|
+
* Frozen invariants (changing ANY of these moves every live name irreversibly):
|
|
14
|
+
* - the KDF is HKDF-SHA256;
|
|
15
|
+
* - the `info` prefix is exactly `pinnace:ipns:v1:` (the version `v1` lives in
|
|
16
|
+
* the `info` string, NOT in a separate parameter — a v2 scheme would use a
|
|
17
|
+
* new prefix and never re-derive existing names);
|
|
18
|
+
* - `keyId` is the SOLE per-site input, appended verbatim (UTF-8) to the info
|
|
19
|
+
* prefix. The ENS name is mutable and MUST NEVER enter derivation, so this
|
|
20
|
+
* surface has no `ensName` parameter at all;
|
|
21
|
+
* - the HKDF `salt` is the RFC 5869 default (empty / zero-length). The spec
|
|
22
|
+
* pins only ikm/info/length; an empty salt is the standard "no salt" case
|
|
23
|
+
* and is pinned here so the contract is total (see ADR-0001);
|
|
24
|
+
* - the 32 HKDF output bytes are used directly as the ed25519 seed (RFC 8032
|
|
25
|
+
* private key), NOT hashed or expanded again.
|
|
26
|
+
*
|
|
27
|
+
* Key IMPORT into a node keystore and the "no client signing" boundary are a
|
|
28
|
+
* SEPARATE concern (the `key-import-publisher` task): this module derives the
|
|
29
|
+
* key + id only, with no node, network, or deploy.
|
|
30
|
+
*/
|
|
31
|
+
import { createPrivateKey, createPublicKey, hkdfSync } from 'node:crypto';
|
|
32
|
+
/** The frozen `info`-string prefix. The version (`v1`) is encoded HERE. */
|
|
33
|
+
export const IPNS_INFO_PREFIX = 'pinnace:ipns:v1:';
|
|
34
|
+
/** PKCS#8 DER prefix wrapping a raw 32-byte ed25519 seed (RFC 8410). */
|
|
35
|
+
const ED25519_PKCS8_PREFIX = Buffer.from('302e020100300506032b657004220420', 'hex');
|
|
36
|
+
/** base36 lowercase alphabet (multibase `k`), per the multiformats spec. */
|
|
37
|
+
const BASE36_ALPHABET = '0123456789abcdefghijklmnopqrstuvwxyz';
|
|
38
|
+
function toBytes(master) {
|
|
39
|
+
return typeof master === 'string' ? new TextEncoder().encode(master) : master;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* HKDF-SHA256(master, salt = "", info = IPNS_INFO_PREFIX + keyId, 32).
|
|
43
|
+
* Returns the 32-byte ed25519 seed. Frozen — see the module doc.
|
|
44
|
+
*/
|
|
45
|
+
function deriveSeed(master, keyId) {
|
|
46
|
+
const info = new TextEncoder().encode(IPNS_INFO_PREFIX + keyId);
|
|
47
|
+
const seed = hkdfSync('sha256', toBytes(master), new Uint8Array(0), // RFC 5869 default (empty) salt — pinned by ADR-0001.
|
|
48
|
+
info, 32);
|
|
49
|
+
return new Uint8Array(seed);
|
|
50
|
+
}
|
|
51
|
+
/** Derive the raw 32-byte ed25519 public key from a 32-byte private seed. */
|
|
52
|
+
function ed25519PublicKeyFromSeed(seed) {
|
|
53
|
+
const pkcs8 = Buffer.concat([ED25519_PKCS8_PREFIX, Buffer.from(seed)]);
|
|
54
|
+
const priv = createPrivateKey({ key: pkcs8, format: 'der', type: 'pkcs8' });
|
|
55
|
+
const spki = createPublicKey(priv).export({ format: 'der', type: 'spki' });
|
|
56
|
+
// An ed25519 SPKI DER is a fixed 12-byte header + the 32 raw public bytes.
|
|
57
|
+
return new Uint8Array(spki.subarray(spki.length - 32));
|
|
58
|
+
}
|
|
59
|
+
/** Encode bytes as multibase-less base36 (lowercase), preserving leading zeros. */
|
|
60
|
+
function base36Encode(bytes) {
|
|
61
|
+
const digits = [0];
|
|
62
|
+
for (const byte of bytes) {
|
|
63
|
+
let carry = byte;
|
|
64
|
+
for (let i = 0; i < digits.length; i++) {
|
|
65
|
+
const value = digits[i] * 256 + carry;
|
|
66
|
+
digits[i] = value % 36;
|
|
67
|
+
carry = (value / 36) | 0;
|
|
68
|
+
}
|
|
69
|
+
while (carry > 0) {
|
|
70
|
+
digits.push(carry % 36);
|
|
71
|
+
carry = (carry / 36) | 0;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
let out = '';
|
|
75
|
+
for (let i = 0; i < bytes.length && bytes[i] === 0; i++)
|
|
76
|
+
out += '0';
|
|
77
|
+
for (let i = digits.length - 1; i >= 0; i--)
|
|
78
|
+
out += BASE36_ALPHABET[digits[i]];
|
|
79
|
+
return out;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Encode a raw 32-byte ed25519 public key as an IPNS name (`k51...`).
|
|
83
|
+
*
|
|
84
|
+
* The name is a CIDv1 with the libp2p-key multicodec, whose multihash inlines
|
|
85
|
+
* the libp2p PublicKey protobuf via the identity hash (the default for
|
|
86
|
+
* ed25519), rendered in case-insensitive base36 with the `k` multibase prefix:
|
|
87
|
+
*
|
|
88
|
+
* 0x01 (CIDv1) 0x72 (libp2p-key)
|
|
89
|
+
* 0x00 (identity mh) 0x24 (len 36)
|
|
90
|
+
* 0x08 0x01 (protobuf field1 Type = Ed25519) 0x12 0x20 (field2 Data, len 32)
|
|
91
|
+
* <32 raw public key bytes>
|
|
92
|
+
*
|
|
93
|
+
* @see https://specs.ipfs.tech/ipns/ipns-record/
|
|
94
|
+
*/
|
|
95
|
+
function ipnsIdFromPublicKey(publicKey) {
|
|
96
|
+
const pbHeader = Uint8Array.from([0x08, 0x01, 0x12, 0x20]);
|
|
97
|
+
const protobuf = new Uint8Array(pbHeader.length + publicKey.length);
|
|
98
|
+
protobuf.set(pbHeader, 0);
|
|
99
|
+
protobuf.set(publicKey, pbHeader.length);
|
|
100
|
+
const multihash = new Uint8Array(2 + protobuf.length);
|
|
101
|
+
multihash[0] = 0x00; // identity multihash function
|
|
102
|
+
multihash[1] = protobuf.length; // digest length (36)
|
|
103
|
+
multihash.set(protobuf, 2);
|
|
104
|
+
const cid = new Uint8Array(2 + multihash.length);
|
|
105
|
+
cid[0] = 0x01; // CIDv1
|
|
106
|
+
cid[1] = 0x72; // libp2p-key multicodec
|
|
107
|
+
cid.set(multihash, 2);
|
|
108
|
+
return 'k' + base36Encode(cid); // multibase base36 (lowercase) prefix.
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Derive the full per-site IPNS key (seed + public key + `k51...` id) from the
|
|
112
|
+
* master secret and the frozen `keyId`. Pure and deterministic — no node,
|
|
113
|
+
* network, or deploy. This is the FROZEN CONTRACT; see the module doc.
|
|
114
|
+
*/
|
|
115
|
+
export function deriveIpnsKey(input) {
|
|
116
|
+
const seed = deriveSeed(input.master, input.keyId);
|
|
117
|
+
const publicKey = ed25519PublicKeyFromSeed(seed);
|
|
118
|
+
const ipnsId = ipnsIdFromPublicKey(publicKey);
|
|
119
|
+
return { seed, publicKey, ipnsId };
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Derive-and-print path (user story 22): return a site's `k51...` IPNS id from
|
|
123
|
+
* the master + keyId with NO deploy and NO network, so an operator can set the
|
|
124
|
+
* ENS contenthash to `ipns://<id>` before the first deploy. The id depends ONLY
|
|
125
|
+
* on (master, keyId) — never the ENS name.
|
|
126
|
+
*/
|
|
127
|
+
export function deriveIpnsId(input) {
|
|
128
|
+
return deriveIpnsKey(input).ipnsId;
|
|
129
|
+
}
|
|
130
|
+
//# sourceMappingURL=ipns-key-derivation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ipns-key-derivation.js","sourceRoot":"","sources":["../../src/derive/ipns-key-derivation.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* pinnace library core — the public TypeScript API.
|
|
3
|
+
*
|
|
4
|
+
* All logic lives in the core (see CONTEXT.md `core vs cli`); the `pinnace`
|
|
5
|
+
* CLI bin (src/cli/bin.ts) is a thin wrapper that imports from here, so every
|
|
6
|
+
* operation is equally usable as a TypeScript API. Subsequent tracer-bullet
|
|
7
|
+
* tasks (Kubo RPC client, CAR build, key derivation, cloud-init, CI emitter,
|
|
8
|
+
* status, config, deploy, site management, CLI) hang off this entrypoint.
|
|
9
|
+
*/
|
|
10
|
+
/** The package name, exposed so the CLI and API can report a consistent identity. */
|
|
11
|
+
export declare const PINNACE = "pinnace";
|
|
12
|
+
/** Returns the pinnace package name (a trivial seam proving the toolchain is wired). */
|
|
13
|
+
export declare function name(): string;
|
|
14
|
+
export { KuboRpcClient, KuboRpcError, type KuboRpcClientOptions, type FetchLike, type FilesMkdirOptions, type FilesRmOptions, type NamePublishOptions, } from './rpc/kubo-rpc-client.js';
|
|
15
|
+
export { MockKuboApi, type RecordedRequest, type MockResponseSpec, } from './rpc/mock-kubo.js';
|
|
16
|
+
export { buildCar, writeCar, type BuiltCar } from './car/car-build.js';
|
|
17
|
+
export { deriveIpnsKey, deriveIpnsId, IPNS_INFO_PREFIX, type Master, type DeriveIpnsInput, type DerivedIpnsKey, } from './derive/ipns-key-derivation.js';
|
|
18
|
+
export { resolveConfig, resolveMasterSecret, resolveHostToken, hostTokenEnvVar, MissingHostTokenError, type PinnaceConfigFile, type ResolvedConfig, type HostConfig, type SiteConfig, type HostRole, type SiteMode, type EnvRecord, type CliOverrides, type ResolveConfigInput, type ResolveMasterInput, type ResolveHostTokenInput, } from './config/config-resolution.js';
|
|
19
|
+
export { emitCi, githubCiProvider, CI_SYSTEMS, type CiSystem, type CIProvider, type EmitCiInput, type EmittedCi, type EmittedFile, type RequiredCiSetting, } from './ci/ci-emit.js';
|
|
20
|
+
export { provision, hetznerHostProvider, HOST_PROVIDERS, type HostName, type HostProvider, type ProvisionInput, type ProvisionResult, } from './provision/cloud-init.js';
|
|
21
|
+
export { serializeIpnsKeyForImport, importIpnsKeyIntoPublisher, KeyImportRoleError, LIBP2P_ED25519_PRIVATE_KEY_PREFIX, type ImportIpnsKeyInput, type KeyImportResult, } from './publisher/key-import.js';
|
|
22
|
+
export { republishAndExport, mirrorAndReannounce, makeRepublishOp, makeMirrorOp, promoteReplicaToPublisher, RECORD_LIFETIME, RECORD_TTL, type PromoteReplicaInput, type PromoteReplicaResult, } from './publisher/record-sequence.js';
|
|
23
|
+
export { runNodeCommand, discoverSites, NODE_VERBS, type NodeVerb, type NodeCommandContext, type NodeCommandOps, type NodeCommandResult, type NodeOpResult, type DiscoveredSite, type SiteOutcome, type PublisherFetch, type GatewayFetch, } from './node/node-commands.js';
|
|
24
|
+
export { listSites, removeSite, addSite, placeInMfs, SITE_VERBS, type SiteVerb, type SiteListing, type ListSitesInput, type RemoveSiteInput, type RemoveSiteResult, type AddSiteInput, type AddSiteResult, } from './site/site-management.js';
|
|
25
|
+
export { deploy, type DeployInput, type DeployTarget, type DeployResult, type DeployNodeOk, type DeployNodeFailure, } from './deploy/deploy.js';
|
|
26
|
+
export { statusReport, makeStatusOp, defaultProvidersLookup, defaultGatewayProbe, type StatusReport, type SiteStatus, type StatusReportInput, type ProvidersLookup, type ProvidersResponse, type GatewayProbe, } from './status/status-report.js';
|
|
27
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* pinnace library core — the public TypeScript API.
|
|
3
|
+
*
|
|
4
|
+
* All logic lives in the core (see CONTEXT.md `core vs cli`); the `pinnace`
|
|
5
|
+
* CLI bin (src/cli/bin.ts) is a thin wrapper that imports from here, so every
|
|
6
|
+
* operation is equally usable as a TypeScript API. Subsequent tracer-bullet
|
|
7
|
+
* tasks (Kubo RPC client, CAR build, key derivation, cloud-init, CI emitter,
|
|
8
|
+
* status, config, deploy, site management, CLI) hang off this entrypoint.
|
|
9
|
+
*/
|
|
10
|
+
/** The package name, exposed so the CLI and API can report a consistent identity. */
|
|
11
|
+
export const PINNACE = 'pinnace';
|
|
12
|
+
/** Returns the pinnace package name (a trivial seam proving the toolchain is wired). */
|
|
13
|
+
export function name() {
|
|
14
|
+
return PINNACE;
|
|
15
|
+
}
|
|
16
|
+
export { KuboRpcClient, KuboRpcError, } from './rpc/kubo-rpc-client.js';
|
|
17
|
+
export { MockKuboApi, } from './rpc/mock-kubo.js';
|
|
18
|
+
export { buildCar, writeCar } from './car/car-build.js';
|
|
19
|
+
export { deriveIpnsKey, deriveIpnsId, IPNS_INFO_PREFIX, } from './derive/ipns-key-derivation.js';
|
|
20
|
+
export { resolveConfig, resolveMasterSecret, resolveHostToken, hostTokenEnvVar, MissingHostTokenError, } from './config/config-resolution.js';
|
|
21
|
+
export { emitCi, githubCiProvider, CI_SYSTEMS, } from './ci/ci-emit.js';
|
|
22
|
+
export { provision, hetznerHostProvider, HOST_PROVIDERS, } from './provision/cloud-init.js';
|
|
23
|
+
export { serializeIpnsKeyForImport, importIpnsKeyIntoPublisher, KeyImportRoleError, LIBP2P_ED25519_PRIVATE_KEY_PREFIX, } from './publisher/key-import.js';
|
|
24
|
+
export { republishAndExport, mirrorAndReannounce, makeRepublishOp, makeMirrorOp, promoteReplicaToPublisher, RECORD_LIFETIME, RECORD_TTL, } from './publisher/record-sequence.js';
|
|
25
|
+
export { runNodeCommand, discoverSites, NODE_VERBS, } from './node/node-commands.js';
|
|
26
|
+
export { listSites, removeSite, addSite, placeInMfs, SITE_VERBS, } from './site/site-management.js';
|
|
27
|
+
export { deploy, } from './deploy/deploy.js';
|
|
28
|
+
export { statusReport, makeStatusOp, defaultProvidersLookup, defaultGatewayProbe, } from './status/status-report.js';
|
|
29
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,qFAAqF;AACrF,MAAM,CAAC,MAAM,OAAO,GAAG,SAAS,CAAC;AAEjC,wFAAwF;AACxF,MAAM,UAAU,IAAI;IACnB,OAAO,OAAO,CAAC;AAChB,CAAC;AAED,OAAO,EACN,aAAa,EACb,YAAY,GAMZ,MAAM,0BAA0B,CAAC;AAClC,OAAO,EACN,WAAW,GAGX,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAC,QAAQ,EAAE,QAAQ,EAAgB,MAAM,oBAAoB,CAAC;AACrE,OAAO,EACN,aAAa,EACb,YAAY,EACZ,gBAAgB,GAIhB,MAAM,iCAAiC,CAAC;AACzC,OAAO,EACN,aAAa,EACb,mBAAmB,EACnB,gBAAgB,EAChB,eAAe,EACf,qBAAqB,GAYrB,MAAM,+BAA+B,CAAC;AACvC,OAAO,EACN,MAAM,EACN,gBAAgB,EAChB,UAAU,GAOV,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACN,SAAS,EACT,mBAAmB,EACnB,cAAc,GAKd,MAAM,2BAA2B,CAAC;AACnC,OAAO,EACN,yBAAyB,EACzB,0BAA0B,EAC1B,kBAAkB,EAClB,iCAAiC,GAGjC,MAAM,2BAA2B,CAAC;AACnC,OAAO,EACN,kBAAkB,EAClB,mBAAmB,EACnB,eAAe,EACf,YAAY,EACZ,yBAAyB,EACzB,eAAe,EACf,UAAU,GAGV,MAAM,gCAAgC,CAAC;AACxC,OAAO,EACN,cAAc,EACd,aAAa,EACb,UAAU,GAUV,MAAM,yBAAyB,CAAC;AACjC,OAAO,EACN,SAAS,EACT,UAAU,EACV,OAAO,EACP,UAAU,EACV,UAAU,GAQV,MAAM,2BAA2B,CAAC;AACnC,OAAO,EACN,MAAM,GAMN,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EACN,YAAY,EACZ,YAAY,EACZ,sBAAsB,EACtB,mBAAmB,GAOnB,MAAM,2BAA2B,CAAC"}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import type { HostRole } from '../config/config-resolution.js';
|
|
2
|
+
import type { KuboRpcClient } from '../rpc/kubo-rpc-client.js';
|
|
3
|
+
/** The four on-box verbs, under the `node` namespace. */
|
|
4
|
+
export type NodeVerb = 'republish' | 'mirror' | 'warm' | 'status';
|
|
5
|
+
/** The verbs, in a stable order (for help text / iteration). */
|
|
6
|
+
export declare const NODE_VERBS: readonly NodeVerb[];
|
|
7
|
+
/** One site as auto-discovered from MFS `/sites/*`: its `id` and current CID. */
|
|
8
|
+
export interface DiscoveredSite {
|
|
9
|
+
/** The MFS entry `id` under `/sites/` (the site's single identifier). */
|
|
10
|
+
id: string;
|
|
11
|
+
/** The current content root CID (`files/stat --hash`). */
|
|
12
|
+
cid: string;
|
|
13
|
+
}
|
|
14
|
+
/** Per-site outcome line a verb reports (shape is verb-specific but uniform). */
|
|
15
|
+
export interface SiteOutcome {
|
|
16
|
+
/** The site's single `id` (its MFS entry / KDF input). */
|
|
17
|
+
id: string;
|
|
18
|
+
cid?: string;
|
|
19
|
+
ipns?: string;
|
|
20
|
+
/** A short machine-readable status token (e.g. `re-announced`, `no-record`). */
|
|
21
|
+
status?: string;
|
|
22
|
+
/**
|
|
23
|
+
* `status`-verb only: whether the network announces this node for the CID
|
|
24
|
+
* (the delegated-routing providers list contains our PeerID). Owned + filled
|
|
25
|
+
* by the `status-report` core op; absent for other verbs.
|
|
26
|
+
*/
|
|
27
|
+
announced?: boolean;
|
|
28
|
+
/** `status`-verb only: whether a cold public gateway served the CID (2xx/206). */
|
|
29
|
+
gatewayServes?: boolean;
|
|
30
|
+
/** `status`-verb only: the raw HTTP status the cold-gateway probe returned. */
|
|
31
|
+
gatewayHttp?: number;
|
|
32
|
+
}
|
|
33
|
+
/** The uniform result an op returns: the per-site outcomes it produced. */
|
|
34
|
+
export interface NodeOpResult {
|
|
35
|
+
sites: SiteOutcome[];
|
|
36
|
+
}
|
|
37
|
+
/** The full result {@link runNodeCommand} returns for one verb invocation. */
|
|
38
|
+
export interface NodeCommandResult extends NodeOpResult {
|
|
39
|
+
/** The verb that ran. */
|
|
40
|
+
verb: NodeVerb;
|
|
41
|
+
/** True when role-gating (or a missing precondition) skipped the op. */
|
|
42
|
+
skipped?: boolean;
|
|
43
|
+
/** Why it was skipped (e.g. `role publisher required, this box is replica`). */
|
|
44
|
+
skippedReason?: string;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* An injectable HTTP fetch for a single URL, returning the body text. Used for
|
|
48
|
+
* fetching the publisher's exported record on a replica. Tests inject a fake;
|
|
49
|
+
* production passes a `fetch`-backed implementation. Throwing signals the
|
|
50
|
+
* endpoint is unreachable (the replica then falls back to its cache).
|
|
51
|
+
*/
|
|
52
|
+
export type PublisherFetch = (url: string) => Promise<string>;
|
|
53
|
+
/**
|
|
54
|
+
* An injectable HTTP fetch for gateway WARMING: fetch the URL (a small range is
|
|
55
|
+
* enough to seat the cache) and return the HTTP status. Tests inject a fake so
|
|
56
|
+
* no live gateway is hit.
|
|
57
|
+
*/
|
|
58
|
+
export type GatewayFetch = (url: string) => Promise<number>;
|
|
59
|
+
/**
|
|
60
|
+
* The four core operations, injectable so the parallel `publisher-replica-model`
|
|
61
|
+
* (republish/mirror) and `status-report` (status) tasks can supply the owned,
|
|
62
|
+
* fully-tested implementations behind this SAME seam. Any op left unset uses
|
|
63
|
+
* this module's thin default (the direct Kubo wiring the reference describes).
|
|
64
|
+
*/
|
|
65
|
+
export interface NodeCommandOps {
|
|
66
|
+
republish: (ctx: NodeCommandContext, sites: DiscoveredSite[]) => Promise<NodeOpResult>;
|
|
67
|
+
mirror: (ctx: NodeCommandContext, sites: DiscoveredSite[]) => Promise<NodeOpResult>;
|
|
68
|
+
warm: (ctx: NodeCommandContext, sites: DiscoveredSite[]) => Promise<NodeOpResult>;
|
|
69
|
+
status: (ctx: NodeCommandContext, sites: DiscoveredSite[]) => Promise<NodeOpResult>;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Everything a verb needs to run on the box. On-box PATHS (records/cache/
|
|
73
|
+
* dashboard) are explicit fields so tests isolate them to temp fixtures and
|
|
74
|
+
* production points them at the real box locations (`/var/www/ipfs-dash/...`,
|
|
75
|
+
* `/var/lib/ipfs/records`) — no global default location is baked into the
|
|
76
|
+
* logic.
|
|
77
|
+
*/
|
|
78
|
+
export interface NodeCommandContext {
|
|
79
|
+
/** The LOCAL Kubo RPC client (this box's daemon). */
|
|
80
|
+
client: KuboRpcClient;
|
|
81
|
+
/** This box's role; gates `republish` (publisher) and `mirror` (replica). */
|
|
82
|
+
role: HostRole;
|
|
83
|
+
/** The MFS directory sites live under (default `/sites`). */
|
|
84
|
+
sitesDir?: string;
|
|
85
|
+
/** Gateway URL templates with a `{cid}` placeholder to warm through. */
|
|
86
|
+
gateways?: string[];
|
|
87
|
+
/** For replicas: base URL to fetch the publisher's exported records from. */
|
|
88
|
+
publisherEndpoint?: string;
|
|
89
|
+
/** Where the publisher EXPORTS signed records (replicas fetch these). */
|
|
90
|
+
recordsDir?: string;
|
|
91
|
+
/** Where a replica CACHES the last good record for fallback. */
|
|
92
|
+
cacheDir?: string;
|
|
93
|
+
/** Where `status` writes its dashboard JSON. */
|
|
94
|
+
dashboardDir?: string;
|
|
95
|
+
/** Injected publisher-record fetch (replica); defaults to a `fetch` call. */
|
|
96
|
+
publisherFetch?: PublisherFetch;
|
|
97
|
+
/** Injected gateway warm fetch; defaults to a `fetch` range request. */
|
|
98
|
+
gatewayFetch?: GatewayFetch;
|
|
99
|
+
/** Override any of the four core ops (parallel tasks supply the owned impl). */
|
|
100
|
+
ops?: Partial<NodeCommandOps>;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Auto-discover sites from MFS `/sites/*`: list the directory, then stat each
|
|
104
|
+
* entry for its current CID. Entries without a resolvable CID are skipped.
|
|
105
|
+
* Shared by every verb (the reference scripts all begin with this same walk).
|
|
106
|
+
*/
|
|
107
|
+
export declare function discoverSites(client: KuboRpcClient, sitesDir?: string): Promise<DiscoveredSite[]>;
|
|
108
|
+
/**
|
|
109
|
+
* Run one on-box verb. Resolves the role gate first (a wrong-role verb is a
|
|
110
|
+
* SKIP — a clean no-op that touches no Kubo RPC, so scheduling all timers on
|
|
111
|
+
* every box is safe), then discovers sites and dispatches to the (possibly
|
|
112
|
+
* injected) core op. Throws on an unknown verb (loud, never a silent no-op).
|
|
113
|
+
*/
|
|
114
|
+
export declare function runNodeCommand(verb: NodeVerb, ctx: NodeCommandContext): Promise<NodeCommandResult>;
|
|
115
|
+
//# sourceMappingURL=node-commands.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"node-commands.d.ts","sourceRoot":"","sources":["../../src/node/node-commands.ts"],"names":[],"mappings":"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"}
|