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,90 @@
|
|
|
1
|
+
import type { KuboRpcClient } from '../rpc/kubo-rpc-client.js';
|
|
2
|
+
import type { HostRole } from '../config/config-resolution.js';
|
|
3
|
+
import type { DerivedIpnsKey } from '../derive/ipns-key-derivation.js';
|
|
4
|
+
import type { DiscoveredSite, NodeCommandContext, NodeOpResult } from '../node/node-commands.js';
|
|
5
|
+
/**
|
|
6
|
+
* Records are ~72h valid, refreshed with a ~1h ttl (the reference values). The
|
|
7
|
+
* republish timer fires well within 72h so the record never lapses. Exported so
|
|
8
|
+
* tests pin the exact validity contract and callers do not re-magic-number it.
|
|
9
|
+
*/
|
|
10
|
+
export declare const RECORD_LIFETIME = "72h";
|
|
11
|
+
export declare const RECORD_TTL = "1h";
|
|
12
|
+
/**
|
|
13
|
+
* PUBLISHER op. For each discovered site the node holds a key for:
|
|
14
|
+
* 1. `name/publish` to refresh the signed record (~72h lifetime, ~1h ttl),
|
|
15
|
+
* 2. `routing/get` to EXPORT the raw signed record, then
|
|
16
|
+
* 3. write the record + its ipns id under {@link NodeCommandContext.recordsDir}
|
|
17
|
+
* where replicas fetch them.
|
|
18
|
+
* Sites the node holds NO key for are ipfs-mode only and are left alone
|
|
19
|
+
* (reported `no-key`, never published). This is the ONLY place a signing
|
|
20
|
+
* primitive (`name/publish`) runs in the whole sequence.
|
|
21
|
+
*/
|
|
22
|
+
export declare function republishAndExport(ctx: NodeCommandContext, sites: DiscoveredSite[]): Promise<NodeOpResult>;
|
|
23
|
+
/**
|
|
24
|
+
* REPLICA op. For each discovered site:
|
|
25
|
+
* 1. FETCH the publisher's exported record (+ its ipns id) from the publisher
|
|
26
|
+
* endpoint;
|
|
27
|
+
* 2. on ANY fetch failure, FALL BACK to the last cached record for that site;
|
|
28
|
+
* 3. re-announce whichever record it got via `routing/put`.
|
|
29
|
+
* A freshly fetched record is cached so a later publisher outage can fall back
|
|
30
|
+
* to it. A site with neither a live publisher NOR a cache is REPORTED
|
|
31
|
+
* (`no-record`), never thrown — one unreachable site must not fail the others.
|
|
32
|
+
*
|
|
33
|
+
* A replica NEVER signs: this op issues NO `name/publish`. It only re-announces
|
|
34
|
+
* a record the publisher already signed.
|
|
35
|
+
*/
|
|
36
|
+
export declare function mirrorAndReannounce(ctx: NodeCommandContext, sites: DiscoveredSite[]): Promise<NodeOpResult>;
|
|
37
|
+
/**
|
|
38
|
+
* Adapt {@link republishAndExport} into a {@link NodeCommandContext} `republish`
|
|
39
|
+
* op so the on-box `republish` verb injects it (its `ops.republish` seam). The
|
|
40
|
+
* command layer discovers sites and passes them in; this op signs+exports over
|
|
41
|
+
* them. So the box runs THIS code, not a re-implementation.
|
|
42
|
+
*/
|
|
43
|
+
export declare function makeRepublishOp(): (ctx: NodeCommandContext, sites: DiscoveredSite[]) => Promise<NodeOpResult>;
|
|
44
|
+
/**
|
|
45
|
+
* Adapt {@link mirrorAndReannounce} into a {@link NodeCommandContext} `mirror`
|
|
46
|
+
* op so the on-box `mirror` verb injects it (its `ops.mirror` seam). Same core,
|
|
47
|
+
* one implementation.
|
|
48
|
+
*/
|
|
49
|
+
export declare function makeMirrorOp(): (ctx: NodeCommandContext, sites: DiscoveredSite[]) => Promise<NodeOpResult>;
|
|
50
|
+
/** Inputs to {@link promoteReplicaToPublisher}. */
|
|
51
|
+
export interface PromoteReplicaInput {
|
|
52
|
+
/** The Kubo RPC client for the node being promoted (per-node, bearer-guarded). */
|
|
53
|
+
client: KuboRpcClient;
|
|
54
|
+
/** The node's CURRENT role (typically `replica`; `publisher` is a safe re-run). */
|
|
55
|
+
currentRole: HostRole;
|
|
56
|
+
/** The keystore key name to import under (the site name / `key/list` Name). */
|
|
57
|
+
keyName: string;
|
|
58
|
+
/** The derived per-site key (seed + public key) from `ipns-key-derivation`. */
|
|
59
|
+
derived: DerivedIpnsKey;
|
|
60
|
+
}
|
|
61
|
+
/** The outcome of a promotion: the flipped role + the key that was imported. */
|
|
62
|
+
export interface PromoteReplicaResult {
|
|
63
|
+
/** Always `publisher` after a successful promotion (the flipped role). */
|
|
64
|
+
role: HostRole;
|
|
65
|
+
/** The key name imported into the (now) publisher's keystore. */
|
|
66
|
+
keyName: string;
|
|
67
|
+
/** The IPNS id the imported key resolves to (from the `key/import` response). */
|
|
68
|
+
ipns?: string;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* PROMOTE a keyless replica to publisher (spec user story 14): import the
|
|
72
|
+
* derived key into the node's keystore and flip its role to `publisher`, so the
|
|
73
|
+
* former replica can now sign/refresh the record itself — recovering the name
|
|
74
|
+
* without downtime of the CONTENT (the CID stays pinned throughout).
|
|
75
|
+
*
|
|
76
|
+
* This is a client-driven operation (run by the operator against the target
|
|
77
|
+
* node's RPC), NOT an on-box recurring verb: it reuses the `key-import-publisher`
|
|
78
|
+
* seam ({@link importIpnsKeyIntoPublisher}) to land the key material. Because
|
|
79
|
+
* that seam REFUSES a non-publisher role, we call it with role `publisher` (the
|
|
80
|
+
* target role) — promotion is precisely the act of making this node the
|
|
81
|
+
* publisher, so importing under the publisher role is correct.
|
|
82
|
+
*
|
|
83
|
+
* MUST happen WITHIN the current record's validity window: while the old
|
|
84
|
+
* publisher's ~72h record is still valid (kept alive by replica re-announcement
|
|
85
|
+
* during the grace window), the promoted node's first `name/publish` re-signs
|
|
86
|
+
* before the record lapses, so the name never goes dark. Promotion imports the
|
|
87
|
+
* key ONLY; it does not itself sign — the node signs on its next `republish`.
|
|
88
|
+
*/
|
|
89
|
+
export declare function promoteReplicaToPublisher(input: PromoteReplicaInput): Promise<PromoteReplicaResult>;
|
|
90
|
+
//# sourceMappingURL=record-sequence.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"record-sequence.d.ts","sourceRoot":"","sources":["../../src/publisher/record-sequence.ts"],"names":[],"mappings":"AA6CA,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,2BAA2B,CAAC;AAC7D,OAAO,KAAK,EAAC,QAAQ,EAAC,MAAM,gCAAgC,CAAC;AAC7D,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,kCAAkC,CAAC;AAErE,OAAO,KAAK,EACX,cAAc,EACd,kBAAkB,EAClB,YAAY,EAGZ,MAAM,0BAA0B,CAAC;AAElC;;;;GAIG;AACH,eAAO,MAAM,eAAe,QAAQ,CAAC;AACrC,eAAO,MAAM,UAAU,OAAO,CAAC;AAW/B;;;;;;;;;GASG;AACH,wBAAsB,kBAAkB,CACvC,GAAG,EAAE,kBAAkB,EACvB,KAAK,EAAE,cAAc,EAAE,GACrB,OAAO,CAAC,YAAY,CAAC,CAoCvB;AAMD;;;;;;;;;;;;GAYG;AACH,wBAAsB,mBAAmB,CACxC,GAAG,EAAE,kBAAkB,EACvB,KAAK,EAAE,cAAc,EAAE,GACrB,OAAO,CAAC,YAAY,CAAC,CA4DvB;AAOD;;;;;GAKG;AACH,wBAAgB,eAAe,IAAI,CAClC,GAAG,EAAE,kBAAkB,EACvB,KAAK,EAAE,cAAc,EAAE,KACnB,OAAO,CAAC,YAAY,CAAC,CAEzB;AAED;;;;GAIG;AACH,wBAAgB,YAAY,IAAI,CAC/B,GAAG,EAAE,kBAAkB,EACvB,KAAK,EAAE,cAAc,EAAE,KACnB,OAAO,CAAC,YAAY,CAAC,CAEzB;AAMD,mDAAmD;AACnD,MAAM,WAAW,mBAAmB;IACnC,kFAAkF;IAClF,MAAM,EAAE,aAAa,CAAC;IACtB,mFAAmF;IACnF,WAAW,EAAE,QAAQ,CAAC;IACtB,+EAA+E;IAC/E,OAAO,EAAE,MAAM,CAAC;IAChB,+EAA+E;IAC/E,OAAO,EAAE,cAAc,CAAC;CACxB;AAED,gFAAgF;AAChF,MAAM,WAAW,oBAAoB;IACpC,0EAA0E;IAC1E,IAAI,EAAE,QAAQ,CAAC;IACf,iEAAiE;IACjE,OAAO,EAAE,MAAM,CAAC;IAChB,iFAAiF;IACjF,IAAI,CAAC,EAAE,MAAM,CAAC;CACd;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAsB,yBAAyB,CAC9C,KAAK,EAAE,mBAAmB,GACxB,OAAO,CAAC,oBAAoB,CAAC,CAW/B"}
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The **publisher / keyless-replica IPNS record SEQUENCE** — pinnace's C-2
|
|
3
|
+
* grace-window machinery (CONTEXT.md `publisher`, `replica`, `IPNS record`;
|
|
4
|
+
* spec "Publisher / keyless-replica model" + user stories 12, 13, 14).
|
|
5
|
+
*
|
|
6
|
+
* A shared IPNS name stays reachable with a grace window even if the publisher
|
|
7
|
+
* dies. Exactly ONE publisher per name holds the derived key and:
|
|
8
|
+
* - refreshes the record's validity by re-SIGNING it (`name/publish`,
|
|
9
|
+
* ~72h lifetime, ~1h ttl — refreshed well within validity), then
|
|
10
|
+
* - EXPORTS the raw signed record (`routing/get`) plus its ipns id to a
|
|
11
|
+
* records dir where keyless replicas fetch it.
|
|
12
|
+
* Keyless REPLICAS hold no key. For each site they:
|
|
13
|
+
* - FETCH the publisher's exported record from the publisher endpoint,
|
|
14
|
+
* - re-announce it (`routing/put`) — they NEVER sign,
|
|
15
|
+
* - FALL BACK to their last cached record if the publisher is unreachable,
|
|
16
|
+
* so the name keeps announcing through a publisher outage.
|
|
17
|
+
*
|
|
18
|
+
* THE LOAD-BEARING INVARIANT (spec Out of Scope C-1; ADR-0003): a replica NEVER
|
|
19
|
+
* signs. The only signing primitive in the whole sequence is the publisher's
|
|
20
|
+
* `name/publish` (the NODE signs, owning sequence numbers + validity). A replica
|
|
21
|
+
* issues ONLY `routing/put` (re-announce) — never `name/publish`. Re-announcing
|
|
22
|
+
* a signed record is not signing it. The tests assert no `name/publish` ever
|
|
23
|
+
* reaches a replica's Kubo, including on the cache-fallback path.
|
|
24
|
+
*
|
|
25
|
+
* ONE IMPLEMENTATION (ADR-0002): this module is the SINGLE home of the record
|
|
26
|
+
* sequence. The on-box `pinnace node republish|mirror` verbs
|
|
27
|
+
* (`../node/node-commands.ts`) do NOT re-implement it — they inject the ops
|
|
28
|
+
* this module exposes ({@link makeRepublishOp}, {@link makeMirrorOp}) through
|
|
29
|
+
* their `NodeCommandOps` seam, exactly as `status-report` supplies the `status`
|
|
30
|
+
* op via `makeStatusOp`. So the client and the box run the same code; there is
|
|
31
|
+
* no bash/TS behaviour drift like the reference prototype had.
|
|
32
|
+
*
|
|
33
|
+
* Behaviour ported (NOT copied as bash) from the reference cloud-init scripts
|
|
34
|
+
* `ipfs-ipns-publish.sh` (publisher: `name publish` + `routing get` export to
|
|
35
|
+
* the dashboard vhost) and `ipfs-ipns-mirror.sh` (replica: curl the publisher's
|
|
36
|
+
* `/records/<name>.ipns-record`, `routing put`, fall back to cache)
|
|
37
|
+
* (`~/searches/ipfs-hetzner/cloud-init.yaml`).
|
|
38
|
+
*
|
|
39
|
+
* Promotion (story 14) — {@link promoteReplicaToPublisher} — reuses the
|
|
40
|
+
* `key-import-publisher` seam ({@link importIpnsKeyIntoPublisher}) to import the
|
|
41
|
+
* derived key and flip the role, recovering the name within the record's
|
|
42
|
+
* validity window without content downtime.
|
|
43
|
+
*/
|
|
44
|
+
import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
45
|
+
import { join } from 'node:path';
|
|
46
|
+
import { importIpnsKeyIntoPublisher } from './key-import.js';
|
|
47
|
+
/**
|
|
48
|
+
* Records are ~72h valid, refreshed with a ~1h ttl (the reference values). The
|
|
49
|
+
* republish timer fires well within 72h so the record never lapses. Exported so
|
|
50
|
+
* tests pin the exact validity contract and callers do not re-magic-number it.
|
|
51
|
+
*/
|
|
52
|
+
export const RECORD_LIFETIME = '72h';
|
|
53
|
+
export const RECORD_TTL = '1h';
|
|
54
|
+
/** The exported record file suffix (raw signed record bytes). */
|
|
55
|
+
const RECORD_SUFFIX = '.ipns-record';
|
|
56
|
+
/** The exported ipns-id file suffix (the `k51...` name `routing/put` targets). */
|
|
57
|
+
const NAME_SUFFIX = '.ipns-name';
|
|
58
|
+
// ---------------------------------------------------------------------------
|
|
59
|
+
// Publisher: refresh (sign) + export.
|
|
60
|
+
// ---------------------------------------------------------------------------
|
|
61
|
+
/**
|
|
62
|
+
* PUBLISHER op. For each discovered site the node holds a key for:
|
|
63
|
+
* 1. `name/publish` to refresh the signed record (~72h lifetime, ~1h ttl),
|
|
64
|
+
* 2. `routing/get` to EXPORT the raw signed record, then
|
|
65
|
+
* 3. write the record + its ipns id under {@link NodeCommandContext.recordsDir}
|
|
66
|
+
* where replicas fetch them.
|
|
67
|
+
* Sites the node holds NO key for are ipfs-mode only and are left alone
|
|
68
|
+
* (reported `no-key`, never published). This is the ONLY place a signing
|
|
69
|
+
* primitive (`name/publish`) runs in the whole sequence.
|
|
70
|
+
*/
|
|
71
|
+
export async function republishAndExport(ctx, sites) {
|
|
72
|
+
const keys = await listKeys(ctx.client);
|
|
73
|
+
const outcomes = [];
|
|
74
|
+
for (const site of sites) {
|
|
75
|
+
const ipns = keys.get(site.id);
|
|
76
|
+
if (!ipns) {
|
|
77
|
+
// ipfs-mode site (no key): nothing to sign or export.
|
|
78
|
+
outcomes.push({ id: site.id, cid: site.cid, status: 'no-key' });
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
// The NODE signs here (refreshing validity). Not the client, not a replica.
|
|
82
|
+
await ctx.client.namePublish({
|
|
83
|
+
cidPath: `/ipfs/${site.cid}`,
|
|
84
|
+
key: site.id,
|
|
85
|
+
lifetime: RECORD_LIFETIME,
|
|
86
|
+
ttl: RECORD_TTL,
|
|
87
|
+
allowOffline: true,
|
|
88
|
+
});
|
|
89
|
+
// Export the raw signed record for replicas to mirror.
|
|
90
|
+
const record = await ctx.client.routingGet(`/ipns/${ipns}`);
|
|
91
|
+
if (ctx.recordsDir) {
|
|
92
|
+
await mkdir(ctx.recordsDir, { recursive: true });
|
|
93
|
+
await writeFile(join(ctx.recordsDir, site.id + NAME_SUFFIX), ipns);
|
|
94
|
+
await writeFile(join(ctx.recordsDir, site.id + RECORD_SUFFIX), Buffer.from(record));
|
|
95
|
+
}
|
|
96
|
+
outcomes.push({ id: site.id, cid: site.cid, ipns, status: 'exported' });
|
|
97
|
+
}
|
|
98
|
+
return { sites: outcomes };
|
|
99
|
+
}
|
|
100
|
+
// ---------------------------------------------------------------------------
|
|
101
|
+
// Replica: fetch + re-announce, with cache fallback. NEVER signs.
|
|
102
|
+
// ---------------------------------------------------------------------------
|
|
103
|
+
/**
|
|
104
|
+
* REPLICA op. For each discovered site:
|
|
105
|
+
* 1. FETCH the publisher's exported record (+ its ipns id) from the publisher
|
|
106
|
+
* endpoint;
|
|
107
|
+
* 2. on ANY fetch failure, FALL BACK to the last cached record for that site;
|
|
108
|
+
* 3. re-announce whichever record it got via `routing/put`.
|
|
109
|
+
* A freshly fetched record is cached so a later publisher outage can fall back
|
|
110
|
+
* to it. A site with neither a live publisher NOR a cache is REPORTED
|
|
111
|
+
* (`no-record`), never thrown — one unreachable site must not fail the others.
|
|
112
|
+
*
|
|
113
|
+
* A replica NEVER signs: this op issues NO `name/publish`. It only re-announces
|
|
114
|
+
* a record the publisher already signed.
|
|
115
|
+
*/
|
|
116
|
+
export async function mirrorAndReannounce(ctx, sites) {
|
|
117
|
+
const base = (ctx.publisherEndpoint ?? '').replace(/\/+$/, '');
|
|
118
|
+
const fetchRecord = ctx.publisherFetch ?? httpFetchText;
|
|
119
|
+
const outcomes = [];
|
|
120
|
+
for (const site of sites) {
|
|
121
|
+
let record;
|
|
122
|
+
let ipnsId;
|
|
123
|
+
let fromCache = false;
|
|
124
|
+
// Try the publisher first; on ANY failure fall back to the cache.
|
|
125
|
+
if (base) {
|
|
126
|
+
try {
|
|
127
|
+
record = await fetchRecord(`${base}/records/${site.id}${RECORD_SUFFIX}`);
|
|
128
|
+
ipnsId = (await fetchRecord(`${base}/records/${site.id}${NAME_SUFFIX}`)).trim();
|
|
129
|
+
}
|
|
130
|
+
catch {
|
|
131
|
+
record = undefined;
|
|
132
|
+
ipnsId = undefined;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
if (record === undefined && ctx.cacheDir) {
|
|
136
|
+
const cached = await readCached(ctx.cacheDir, site.id);
|
|
137
|
+
if (cached) {
|
|
138
|
+
record = cached.record;
|
|
139
|
+
ipnsId = cached.ipnsId;
|
|
140
|
+
fromCache = true;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
if (record === undefined || !ipnsId) {
|
|
144
|
+
// Neither a live publisher nor a cache: report, don't throw.
|
|
145
|
+
outcomes.push({ id: site.id, status: 'no-record' });
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
// Persist a freshly-fetched record so a later outage can fall back to it.
|
|
149
|
+
// (Cache-sourced records are already on disk.)
|
|
150
|
+
if (!fromCache && ctx.cacheDir) {
|
|
151
|
+
await mkdir(ctx.cacheDir, { recursive: true });
|
|
152
|
+
await writeFile(join(ctx.cacheDir, site.id + RECORD_SUFFIX), record);
|
|
153
|
+
await writeFile(join(ctx.cacheDir, site.id + NAME_SUFFIX), ipnsId);
|
|
154
|
+
}
|
|
155
|
+
// Re-announce ONLY. No signing — this is a replica.
|
|
156
|
+
await ctx.client.routingPut(`/ipns/${ipnsId}`, new Uint8Array(Buffer.from(record)));
|
|
157
|
+
outcomes.push({
|
|
158
|
+
id: site.id,
|
|
159
|
+
ipns: ipnsId,
|
|
160
|
+
status: fromCache ? 're-announced-cached' : 're-announced',
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
return { sites: outcomes };
|
|
164
|
+
}
|
|
165
|
+
// ---------------------------------------------------------------------------
|
|
166
|
+
// Node-command adapters: supply the owned ops behind the SAME NodeCommandOps
|
|
167
|
+
// seam the on-box `republish`/`mirror` verbs inject (mirrors makeStatusOp).
|
|
168
|
+
// ---------------------------------------------------------------------------
|
|
169
|
+
/**
|
|
170
|
+
* Adapt {@link republishAndExport} into a {@link NodeCommandContext} `republish`
|
|
171
|
+
* op so the on-box `republish` verb injects it (its `ops.republish` seam). The
|
|
172
|
+
* command layer discovers sites and passes them in; this op signs+exports over
|
|
173
|
+
* them. So the box runs THIS code, not a re-implementation.
|
|
174
|
+
*/
|
|
175
|
+
export function makeRepublishOp() {
|
|
176
|
+
return (ctx, sites) => republishAndExport(ctx, sites);
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Adapt {@link mirrorAndReannounce} into a {@link NodeCommandContext} `mirror`
|
|
180
|
+
* op so the on-box `mirror` verb injects it (its `ops.mirror` seam). Same core,
|
|
181
|
+
* one implementation.
|
|
182
|
+
*/
|
|
183
|
+
export function makeMirrorOp() {
|
|
184
|
+
return (ctx, sites) => mirrorAndReannounce(ctx, sites);
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* PROMOTE a keyless replica to publisher (spec user story 14): import the
|
|
188
|
+
* derived key into the node's keystore and flip its role to `publisher`, so the
|
|
189
|
+
* former replica can now sign/refresh the record itself — recovering the name
|
|
190
|
+
* without downtime of the CONTENT (the CID stays pinned throughout).
|
|
191
|
+
*
|
|
192
|
+
* This is a client-driven operation (run by the operator against the target
|
|
193
|
+
* node's RPC), NOT an on-box recurring verb: it reuses the `key-import-publisher`
|
|
194
|
+
* seam ({@link importIpnsKeyIntoPublisher}) to land the key material. Because
|
|
195
|
+
* that seam REFUSES a non-publisher role, we call it with role `publisher` (the
|
|
196
|
+
* target role) — promotion is precisely the act of making this node the
|
|
197
|
+
* publisher, so importing under the publisher role is correct.
|
|
198
|
+
*
|
|
199
|
+
* MUST happen WITHIN the current record's validity window: while the old
|
|
200
|
+
* publisher's ~72h record is still valid (kept alive by replica re-announcement
|
|
201
|
+
* during the grace window), the promoted node's first `name/publish` re-signs
|
|
202
|
+
* before the record lapses, so the name never goes dark. Promotion imports the
|
|
203
|
+
* key ONLY; it does not itself sign — the node signs on its next `republish`.
|
|
204
|
+
*/
|
|
205
|
+
export async function promoteReplicaToPublisher(input) {
|
|
206
|
+
// Reuse the key-import seam. It refuses any non-publisher role, so we pass
|
|
207
|
+
// the TARGET role (`publisher`): promotion IS making this node the publisher.
|
|
208
|
+
const imported = await importIpnsKeyIntoPublisher({
|
|
209
|
+
client: input.client,
|
|
210
|
+
role: 'publisher',
|
|
211
|
+
keyName: input.keyName,
|
|
212
|
+
derived: input.derived,
|
|
213
|
+
});
|
|
214
|
+
// Flip the role. From here the node is the single signer for this name.
|
|
215
|
+
return { role: 'publisher', keyName: input.keyName, ipns: imported.Id };
|
|
216
|
+
}
|
|
217
|
+
// ---------------------------------------------------------------------------
|
|
218
|
+
// Small shared helpers.
|
|
219
|
+
// ---------------------------------------------------------------------------
|
|
220
|
+
/** Map site/key name -> IPNS id from `key/list -l`. */
|
|
221
|
+
async function listKeys(client) {
|
|
222
|
+
const res = await client.keyList();
|
|
223
|
+
const map = new Map();
|
|
224
|
+
for (const k of res.Keys ?? []) {
|
|
225
|
+
if (k?.Name && k?.Id)
|
|
226
|
+
map.set(k.Name, k.Id);
|
|
227
|
+
}
|
|
228
|
+
return map;
|
|
229
|
+
}
|
|
230
|
+
/** Read a cached record + its ipns id for a site, or undefined if absent. */
|
|
231
|
+
async function readCached(cacheDir, name) {
|
|
232
|
+
try {
|
|
233
|
+
const record = await readFile(join(cacheDir, name + RECORD_SUFFIX), 'utf8');
|
|
234
|
+
const ipnsId = (await readFile(join(cacheDir, name + NAME_SUFFIX), 'utf8')).trim();
|
|
235
|
+
if (!ipnsId)
|
|
236
|
+
return undefined;
|
|
237
|
+
return { record, ipnsId };
|
|
238
|
+
}
|
|
239
|
+
catch {
|
|
240
|
+
return undefined;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
/** Production publisher-record fetch: GET the URL and return its body text. */
|
|
244
|
+
async function httpFetchText(url) {
|
|
245
|
+
const res = await fetch(url);
|
|
246
|
+
if (!res.ok)
|
|
247
|
+
throw new Error(`fetch ${url} -> ${res.status}`);
|
|
248
|
+
return await res.text();
|
|
249
|
+
}
|
|
250
|
+
//# sourceMappingURL=record-sequence.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"record-sequence.js","sourceRoot":"","sources":["../../src/publisher/record-sequence.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A typed, per-node Kubo RPC client. It wraps `POST {baseUrl}/api/v0/<path>`
|
|
3
|
+
* with an `Authorization: Bearer <token>` header on EVERY call. No Kubo binary
|
|
4
|
+
* runs on the client — all interaction is HTTP.
|
|
5
|
+
*
|
|
6
|
+
* This is the seam every host-agnostic pinnace operation (deploy, publish,
|
|
7
|
+
* status, site management) speaks. It is deliberately PER-NODE (one base URL +
|
|
8
|
+
* one token); multi-target fan-out lives in the deploy task, not here.
|
|
9
|
+
*
|
|
10
|
+
* Behaviour ported (not copied) from the reference prototype
|
|
11
|
+
* `~/searches/ipfs-hetzner/deploy-car.mjs`: the exact query params
|
|
12
|
+
* (`files/mkdir?arg=/sites&parents=true`, `dag/import?pin-roots=true`, ...) and
|
|
13
|
+
* the throw-on-`!ok` error handling.
|
|
14
|
+
*/
|
|
15
|
+
/** A minimal `fetch`-shaped function, so tests can inject a recording mock. */
|
|
16
|
+
export type FetchLike = (input: string | URL, init?: {
|
|
17
|
+
method?: string;
|
|
18
|
+
headers?: Record<string, string>;
|
|
19
|
+
body?: unknown;
|
|
20
|
+
}) => Promise<Response>;
|
|
21
|
+
/** Options for constructing a per-node {@link KuboRpcClient}. */
|
|
22
|
+
export interface KuboRpcClientOptions {
|
|
23
|
+
/** The node's Kubo RPC base URL, e.g. `https://ipfs-api.example.com`. */
|
|
24
|
+
baseUrl: string;
|
|
25
|
+
/** The node's bearer token (sent on every call). */
|
|
26
|
+
token: string;
|
|
27
|
+
/** Injectable fetch (defaults to the global `fetch`); tests pass the mock. */
|
|
28
|
+
fetchImpl?: FetchLike;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* A loud error carrying the endpoint + HTTP status of a non-2xx Kubo response,
|
|
32
|
+
* so a caller (and a log) can see exactly which call failed and how.
|
|
33
|
+
*/
|
|
34
|
+
export declare class KuboRpcError extends Error {
|
|
35
|
+
/** The `/api/v0/<endpoint>` path that failed. */
|
|
36
|
+
readonly endpoint: string;
|
|
37
|
+
/** The HTTP status returned. */
|
|
38
|
+
readonly status: number;
|
|
39
|
+
/** The response body text (for diagnostics). */
|
|
40
|
+
readonly bodyText: string;
|
|
41
|
+
constructor(
|
|
42
|
+
/** The `/api/v0/<endpoint>` path that failed. */
|
|
43
|
+
endpoint: string,
|
|
44
|
+
/** The HTTP status returned. */
|
|
45
|
+
status: number,
|
|
46
|
+
/** The response body text (for diagnostics). */
|
|
47
|
+
bodyText: string);
|
|
48
|
+
}
|
|
49
|
+
/** Flags accepted by `files/mkdir`. */
|
|
50
|
+
export interface FilesMkdirOptions {
|
|
51
|
+
/** Create parent directories as needed (`parents=true`). */
|
|
52
|
+
parents?: boolean;
|
|
53
|
+
}
|
|
54
|
+
/** Flags accepted by `files/rm`. */
|
|
55
|
+
export interface FilesRmOptions {
|
|
56
|
+
/** Remove directories recursively (`recursive=true`). */
|
|
57
|
+
recursive?: boolean;
|
|
58
|
+
/** Ignore nonexistent files (`force=true`). */
|
|
59
|
+
force?: boolean;
|
|
60
|
+
}
|
|
61
|
+
/** Parameters for `name/publish`. */
|
|
62
|
+
export interface NamePublishOptions {
|
|
63
|
+
/** The `/ipfs/<cid>` path to publish. */
|
|
64
|
+
cidPath: string;
|
|
65
|
+
/** The keystore key name to sign with. */
|
|
66
|
+
key: string;
|
|
67
|
+
/** Record lifetime (e.g. `72h`). */
|
|
68
|
+
lifetime?: string;
|
|
69
|
+
/** Record TTL (e.g. `1h`). */
|
|
70
|
+
ttl?: string;
|
|
71
|
+
/** Publish even with no online peers (`allow-offline=true`). */
|
|
72
|
+
allowOffline?: boolean;
|
|
73
|
+
}
|
|
74
|
+
export declare class KuboRpcClient {
|
|
75
|
+
private readonly baseUrl;
|
|
76
|
+
private readonly token;
|
|
77
|
+
private readonly fetchImpl;
|
|
78
|
+
constructor(options: KuboRpcClientOptions);
|
|
79
|
+
/**
|
|
80
|
+
* The core request primitive: POST `/api/v0/<endpoint>?<query>` with the
|
|
81
|
+
* bearer token. Throws {@link KuboRpcError} on any non-2xx. Returns the raw
|
|
82
|
+
* {@link Response} so callers can decode JSON or bytes as they need.
|
|
83
|
+
*/
|
|
84
|
+
request(endpoint: string, query?: URLSearchParams, body?: unknown, extraHeaders?: Record<string, string>): Promise<Response>;
|
|
85
|
+
/** POST an endpoint and parse the JSON response body. */
|
|
86
|
+
private requestJson;
|
|
87
|
+
/** `id` — the node's identity (PeerID etc.). */
|
|
88
|
+
id<T = unknown>(): Promise<T>;
|
|
89
|
+
/**
|
|
90
|
+
* `add` — add raw bytes (returned shape is Kubo-defined). Kubo requires the
|
|
91
|
+
* payload as a `multipart/form-data` `file` part, NOT a raw body, so we send
|
|
92
|
+
* a {@link FormData} and let `fetch` set the multipart boundary itself (see
|
|
93
|
+
* {@link fileUpload}).
|
|
94
|
+
*/
|
|
95
|
+
add<T = unknown>(data: Uint8Array): Promise<T>;
|
|
96
|
+
/** `dag/import?pin-roots=true` — import a CAR and pin its roots. */
|
|
97
|
+
dagImport<T = unknown>(car: Uint8Array): Promise<T>;
|
|
98
|
+
/**
|
|
99
|
+
* `pin/rm?arg=<cid>` — UNPIN content so Kubo can garbage-collect it and
|
|
100
|
+
* reclaim storage. The counterpart to `dag/import?pin-roots=true`: removing a
|
|
101
|
+
* site unpins its CID here so it stops being served/announced. `arg` takes a
|
|
102
|
+
* bare CID (or an `/ipfs/<cid>` path); callers pass the CID from `files/stat`.
|
|
103
|
+
*/
|
|
104
|
+
pinRm<T = unknown>(cid: string): Promise<T>;
|
|
105
|
+
/** `files/mkdir?arg=<path>[&parents=true]`. */
|
|
106
|
+
filesMkdir(path: string, options?: FilesMkdirOptions): Promise<void>;
|
|
107
|
+
/** `files/rm?arg=<path>[&recursive=true][&force=true]`. */
|
|
108
|
+
filesRm(path: string, options?: FilesRmOptions): Promise<void>;
|
|
109
|
+
/** `files/cp?arg=<from>&arg=<to>`. */
|
|
110
|
+
filesCp(from: string, to: string): Promise<void>;
|
|
111
|
+
/** `files/stat?arg=<path>` — returns the MFS entry stat (Hash, Type, ...). */
|
|
112
|
+
filesStat<T = unknown>(path: string): Promise<T>;
|
|
113
|
+
/** `files/ls?arg=<path>[&l=true]` — list an MFS directory. */
|
|
114
|
+
filesLs<T = unknown>(path: string, long?: boolean): Promise<T>;
|
|
115
|
+
/** `key/list?l=true` — list keystore keys with their IPNS ids. */
|
|
116
|
+
keyList<T = unknown>(): Promise<T>;
|
|
117
|
+
/** `key/gen?arg=<name>&type=ed25519` — generate a new keystore key. */
|
|
118
|
+
keyGen<T = unknown>(name: string): Promise<T>;
|
|
119
|
+
/**
|
|
120
|
+
* `key/import?arg=<name>` — import key MATERIAL (libp2p/protobuf bytes) into
|
|
121
|
+
* the keystore. The node, not the client, signs records with it.
|
|
122
|
+
*/
|
|
123
|
+
keyImport<T = unknown>(name: string, keyBytes: Uint8Array): Promise<T>;
|
|
124
|
+
/**
|
|
125
|
+
* Kubo's file-upload endpoints require the payload as a `multipart/form-data`
|
|
126
|
+
* body with the bytes as a named file part (confirmed by the Kubo RPC docs'
|
|
127
|
+
* `-F <field>=@…` cURL examples and a live daemon). A raw
|
|
128
|
+
* `application/octet-stream` body is rejected with
|
|
129
|
+
* `400 file argument '<field>' is required`.
|
|
130
|
+
*
|
|
131
|
+
* The field name is endpoint-specific: `add`, `dag/import` and `key/import`
|
|
132
|
+
* expect `file` (the default), while `routing/put` expects `value-file`.
|
|
133
|
+
*
|
|
134
|
+
* We build a {@link FormData} and pass it as the body WITHOUT a hand-set
|
|
135
|
+
* `content-type`: `fetch` serialises the FormData and sets
|
|
136
|
+
* `content-type: multipart/form-data; boundary=…` itself. Setting it manually
|
|
137
|
+
* would omit/override the boundary and break the request.
|
|
138
|
+
*/
|
|
139
|
+
private fileUpload;
|
|
140
|
+
/** `name/publish?arg=<cidPath>&key=<key>&lifetime=..&ttl=..` — sign+publish IPNS. */
|
|
141
|
+
namePublish<T = unknown>(options: NamePublishOptions): Promise<T>;
|
|
142
|
+
/** `routing/get?arg=<ipnsPath>` — fetch the raw signed record for a name. */
|
|
143
|
+
routingGet(ipnsPath: string): Promise<Uint8Array>;
|
|
144
|
+
/**
|
|
145
|
+
* `routing/put?arg=<ipnsPath>` — (re-)announce a signed record body. Kubo's
|
|
146
|
+
* `routing/put` takes the record as a `multipart/form-data` file part named
|
|
147
|
+
* **`value-file`** (NOT the generic `file` the other upload endpoints use); a
|
|
148
|
+
* raw `application/octet-stream` body is rejected with
|
|
149
|
+
* `400 file argument 'value-file' is required`. Goes through {@link fileUpload}
|
|
150
|
+
* so `fetch` owns the multipart boundary (no hand-set content-type).
|
|
151
|
+
*/
|
|
152
|
+
routingPut(ipnsPath: string, record: Uint8Array): Promise<void>;
|
|
153
|
+
}
|
|
154
|
+
//# sourceMappingURL=kubo-rpc-client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"kubo-rpc-client.d.ts","sourceRoot":"","sources":["../../src/rpc/kubo-rpc-client.ts"],"names":[],"mappings":"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"}
|