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.
Files changed (83) hide show
  1. package/LICENSE +661 -0
  2. package/README.md +164 -0
  3. package/dist/car/car-build.d.ts +26 -0
  4. package/dist/car/car-build.d.ts.map +1 -0
  5. package/dist/car/car-build.js +113 -0
  6. package/dist/car/car-build.js.map +1 -0
  7. package/dist/ci/ci-emit.d.ts +116 -0
  8. package/dist/ci/ci-emit.d.ts.map +1 -0
  9. package/dist/ci/ci-emit.js +190 -0
  10. package/dist/ci/ci-emit.js.map +1 -0
  11. package/dist/cli/bin.d.ts +3 -0
  12. package/dist/cli/bin.d.ts.map +1 -0
  13. package/dist/cli/bin.js +17 -0
  14. package/dist/cli/bin.js.map +1 -0
  15. package/dist/cli/run.d.ts +94 -0
  16. package/dist/cli/run.d.ts.map +1 -0
  17. package/dist/cli/run.js +731 -0
  18. package/dist/cli/run.js.map +1 -0
  19. package/dist/config/config-resolution.d.ts +186 -0
  20. package/dist/config/config-resolution.d.ts.map +1 -0
  21. package/dist/config/config-resolution.js +137 -0
  22. package/dist/config/config-resolution.js.map +1 -0
  23. package/dist/deploy/deploy.d.ts +121 -0
  24. package/dist/deploy/deploy.d.ts.map +1 -0
  25. package/dist/deploy/deploy.js +150 -0
  26. package/dist/deploy/deploy.js.map +1 -0
  27. package/dist/derive/ipns-key-derivation.d.ts +37 -0
  28. package/dist/derive/ipns-key-derivation.d.ts.map +1 -0
  29. package/dist/derive/ipns-key-derivation.js +130 -0
  30. package/dist/derive/ipns-key-derivation.js.map +1 -0
  31. package/dist/index.d.ts +27 -0
  32. package/dist/index.d.ts.map +1 -0
  33. package/dist/index.js +29 -0
  34. package/dist/index.js.map +1 -0
  35. package/dist/node/node-commands.d.ts +115 -0
  36. package/dist/node/node-commands.d.ts.map +1 -0
  37. package/dist/node/node-commands.js +231 -0
  38. package/dist/node/node-commands.js.map +1 -0
  39. package/dist/provision/cloud-init.d.ts +162 -0
  40. package/dist/provision/cloud-init.d.ts.map +1 -0
  41. package/dist/provision/cloud-init.js +460 -0
  42. package/dist/provision/cloud-init.js.map +1 -0
  43. package/dist/publisher/key-import.d.ts +105 -0
  44. package/dist/publisher/key-import.d.ts.map +1 -0
  45. package/dist/publisher/key-import.js +76 -0
  46. package/dist/publisher/key-import.js.map +1 -0
  47. package/dist/publisher/record-sequence.d.ts +90 -0
  48. package/dist/publisher/record-sequence.d.ts.map +1 -0
  49. package/dist/publisher/record-sequence.js +250 -0
  50. package/dist/publisher/record-sequence.js.map +1 -0
  51. package/dist/rpc/kubo-rpc-client.d.ts +154 -0
  52. package/dist/rpc/kubo-rpc-client.d.ts.map +1 -0
  53. package/dist/rpc/kubo-rpc-client.js +207 -0
  54. package/dist/rpc/kubo-rpc-client.js.map +1 -0
  55. package/dist/rpc/mock-kubo.d.ts +87 -0
  56. package/dist/rpc/mock-kubo.d.ts.map +1 -0
  57. package/dist/rpc/mock-kubo.js +129 -0
  58. package/dist/rpc/mock-kubo.js.map +1 -0
  59. package/dist/site/site-management.d.ts +126 -0
  60. package/dist/site/site-management.d.ts.map +1 -0
  61. package/dist/site/site-management.js +123 -0
  62. package/dist/site/site-management.js.map +1 -0
  63. package/dist/status/status-report.d.ts +145 -0
  64. package/dist/status/status-report.d.ts.map +1 -0
  65. package/dist/status/status-report.js +192 -0
  66. package/dist/status/status-report.js.map +1 -0
  67. package/package.json +45 -2
  68. package/src/car/car-build.ts +135 -0
  69. package/src/ci/ci-emit.ts +284 -0
  70. package/src/cli/bin.ts +20 -0
  71. package/src/cli/run.ts +970 -0
  72. package/src/config/config-resolution.ts +264 -0
  73. package/src/deploy/deploy.ts +250 -0
  74. package/src/derive/ipns-key-derivation.ts +173 -0
  75. package/src/index.ts +146 -0
  76. package/src/node/node-commands.ts +395 -0
  77. package/src/provision/cloud-init.ts +646 -0
  78. package/src/publisher/key-import.ts +141 -0
  79. package/src/publisher/record-sequence.ts +336 -0
  80. package/src/rpc/kubo-rpc-client.ts +281 -0
  81. package/src/rpc/mock-kubo.ts +194 -0
  82. package/src/site/site-management.ts +241 -0
  83. 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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"}