pinnace 0.1.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/README.md +155 -6
- package/dist/cli/run.d.ts +16 -0
- package/dist/cli/run.d.ts.map +1 -1
- package/dist/cli/run.js +245 -20
- package/dist/cli/run.js.map +1 -1
- package/dist/node/node-commands.js +9 -5
- package/dist/node/node-commands.js.map +1 -1
- package/dist/provision/cloud-init.d.ts +25 -0
- package/dist/provision/cloud-init.d.ts.map +1 -1
- package/dist/provision/cloud-init.js +77 -7
- package/dist/provision/cloud-init.js.map +1 -1
- package/package.json +1 -1
- package/src/cli/run.ts +336 -20
- package/src/node/node-commands.ts +9 -5
- package/src/provision/cloud-init.ts +102 -7
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"node-commands.js","sourceRoot":"","sources":["../../src/node/node-commands.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"node-commands.js","sourceRoot":"","sources":["../../src/node/node-commands.ts"],"names":[],"mappings":"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"}
|
|
@@ -41,6 +41,18 @@
|
|
|
41
41
|
* (interval well under the record-expiry window, strategy `all`).
|
|
42
42
|
* - Caddy HTTPS reverse proxy for the RPC API (auto TLS), which forwards the
|
|
43
43
|
* operator's bearer header straight through to the token-guarded Kubo API.
|
|
44
|
+
*
|
|
45
|
+
* INSTALL CHANNEL + BOOT-SAFETY (task `cloud-init-pinnace-install-channel`):
|
|
46
|
+
* - The box installs a PINNED `pinnace` ({@link DEFAULT_PINNACE_VERSION}) on a
|
|
47
|
+
* named current Node LTS ({@link DEFAULT_NODE_MAJOR}), both overridable per
|
|
48
|
+
* box. Pinned (never floating `latest`) so a boot is reproducible.
|
|
49
|
+
* - The install is BOOT-SAFE: `pinnace-setup.sh` is invoked with `|| true`, so
|
|
50
|
+
* a transient npm/registry failure can NOT abort the boot. Kubo, the
|
|
51
|
+
* firewall and Caddy come up regardless of the agent install.
|
|
52
|
+
* - The dedicated `ipfs` service user is created by cloud-init's `users:`
|
|
53
|
+
* module, which runs BEFORE `runcmd`, so no boot step can hit "invalid user
|
|
54
|
+
* 'ipfs'" (closes the first-boot race in
|
|
55
|
+
* work/notes/observations/cloud-init-first-boot-ipfs-user-race-and-set-e-abort.md).
|
|
44
56
|
*/
|
|
45
57
|
import type { HostRole } from '../config/config-resolution.js';
|
|
46
58
|
/** The hosts pinnace can provision for. v1 = Hetzner ONLY. */
|
|
@@ -92,6 +104,19 @@ export interface ProvisionInput {
|
|
|
92
104
|
gateways?: string[];
|
|
93
105
|
/** The Kubo version to install. Defaults to a pinned known-good release. */
|
|
94
106
|
kuboVersion?: string;
|
|
107
|
+
/**
|
|
108
|
+
* The `pinnace` version to install on the box (`npm install -g
|
|
109
|
+
* pinnace@<this>`). Defaults to {@link DEFAULT_PINNACE_VERSION} (the current
|
|
110
|
+
* published release). PINNED, never floating `latest`, so a box boot is
|
|
111
|
+
* reproducible. Overridable per-box (e.g. to roll a box onto a newer agent).
|
|
112
|
+
*/
|
|
113
|
+
pinnaceVersion?: string;
|
|
114
|
+
/**
|
|
115
|
+
* The Node.js major version to install via NodeSource (`setup_<this>.x`).
|
|
116
|
+
* Defaults to {@link DEFAULT_NODE_MAJOR} (a current active LTS). A named knob
|
|
117
|
+
* so the LTS bump is one obvious edit, not a literal buried in a shell line.
|
|
118
|
+
*/
|
|
119
|
+
nodeMajor?: string;
|
|
95
120
|
/** The MFS directory sites live under. Defaults to `/sites`. */
|
|
96
121
|
sitesDir?: string;
|
|
97
122
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cloud-init.d.ts","sourceRoot":"","sources":["../../src/provision/cloud-init.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"cloud-init.d.ts","sourceRoot":"","sources":["../../src/provision/cloud-init.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuDG;AACH,OAAO,KAAK,EAAC,QAAQ,EAAC,MAAM,gCAAgC,CAAC;AAE7D,8DAA8D;AAC9D,MAAM,MAAM,QAAQ,GAAG,SAAS,CAAC;AAEjC,yEAAyE;AACzE,eAAO,MAAM,cAAc,EAAE,SAAS,QAAQ,EAAgB,CAAC;AAE/D;;;;GAIG;AACH,MAAM,WAAW,cAAc;IAC9B,mDAAmD;IACnD,IAAI,EAAE,QAAQ,CAAC;IACf,0EAA0E;IAC1E,SAAS,EAAE,MAAM,CAAC;IAClB,gFAAgF;IAChF,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,2DAA2D;IAC3D,SAAS,EAAE,MAAM,CAAC;IAClB;;;OAGG;IACH,WAAW,EAAE,MAAM,CAAC;IACpB;;;;;;OAMG;IACH,IAAI,EAAE,QAAQ,CAAC;IACf;;;OAGG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,4EAA4E;IAC5E,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;;OAKG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,gEAAgE;IAChE,QAAQ,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,+DAA+D;AAC/D,MAAM,WAAW,WAAW;IAC3B,gFAAgF;IAChF,IAAI,EAAE,MAAM,CAAC;IACb,8BAA8B;IAC9B,QAAQ,EAAE,MAAM,CAAC;CACjB;AAED,wEAAwE;AACxE,MAAM,WAAW,eAAe;IAC/B,mEAAmE;IACnE,IAAI,EAAE,QAAQ,CAAC;IACf,6CAA6C;IAC7C,SAAS,EAAE,WAAW,CAAC;CACvB;AAED;;;;;;GAMG;AACH,MAAM,WAAW,YAAY;IAC5B,6CAA6C;IAC7C,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IACxB,0EAA0E;IAC1E,SAAS,CAAC,KAAK,EAAE,cAAc,GAAG,eAAe,CAAC;CAClD;AAqcD;;;GAGG;AACH,eAAO,MAAM,mBAAmB,EAAE,YAWjC,CAAC;AAOF;;;;;GAKG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,cAAc,GAAG,eAAe,CAUhE"}
|
|
@@ -2,6 +2,23 @@
|
|
|
2
2
|
export const HOST_PROVIDERS = ['hetzner'];
|
|
3
3
|
/** Defaults kept in one place so the template + docs never drift. */
|
|
4
4
|
const DEFAULT_KUBO_VERSION = 'v0.38.1';
|
|
5
|
+
/**
|
|
6
|
+
* The pinned `pinnace` version the box installs (`npm install -g
|
|
7
|
+
* pinnace@<this>`). Mirrors {@link DEFAULT_KUBO_VERSION}: a NAMED knob, not a
|
|
8
|
+
* literal, so a release bump is one obvious edit here. PINNED (never floating
|
|
9
|
+
* `latest`) so a box boot is reproducible: the same cloud-init always installs
|
|
10
|
+
* the same agent. Overridable per-box via {@link ProvisionInput.pinnaceVersion}.
|
|
11
|
+
* `pinnace@0.1.0` is the first published release (npm, public, OIDC provenance).
|
|
12
|
+
*/
|
|
13
|
+
const DEFAULT_PINNACE_VERSION = '0.2.0';
|
|
14
|
+
/**
|
|
15
|
+
* The pinned Node.js major the box installs via NodeSource (`setup_<this>.x`).
|
|
16
|
+
* Node 22 is a current active LTS; Node 20 (the old literal) is the OLDEST LTS
|
|
17
|
+
* (EOL ~2026-04) and incoherent with the repo's own Node 24 toolchain. A NAMED
|
|
18
|
+
* knob (mirrors {@link DEFAULT_KUBO_VERSION}) so the LTS bump is one edit.
|
|
19
|
+
* Overridable per-box via {@link ProvisionInput.nodeMajor}.
|
|
20
|
+
*/
|
|
21
|
+
const DEFAULT_NODE_MAJOR = '22';
|
|
5
22
|
const DEFAULT_SITES_DIR = '/sites';
|
|
6
23
|
const DEFAULT_GATEWAYS = [
|
|
7
24
|
'https://{cid}.ipfs.dweb.link/',
|
|
@@ -88,6 +105,8 @@ function renderHetznerCloudInit(input) {
|
|
|
88
105
|
const publisherEndpoint = input.role === 'replica' ? (input.publisherEndpoint ?? '') : '';
|
|
89
106
|
const gateways = input.gateways ?? DEFAULT_GATEWAYS;
|
|
90
107
|
const kuboVersion = input.kuboVersion ?? DEFAULT_KUBO_VERSION;
|
|
108
|
+
const pinnaceVersion = input.pinnaceVersion ?? DEFAULT_PINNACE_VERSION;
|
|
109
|
+
const nodeMajor = input.nodeMajor ?? DEFAULT_NODE_MAJOR;
|
|
91
110
|
const sitesDir = input.sitesDir ?? DEFAULT_SITES_DIR;
|
|
92
111
|
const warmGateways = gateways.join(' ');
|
|
93
112
|
const timerUnits = TIMERS.map(renderTimerUnits).join('\n');
|
|
@@ -123,6 +142,22 @@ packages:
|
|
|
123
142
|
- debian-archive-keyring
|
|
124
143
|
- apt-transport-https
|
|
125
144
|
|
|
145
|
+
# ---------------------------------------------------------------------------
|
|
146
|
+
# Service users. cloud-init's \`users:\` module runs BEFORE \`runcmd\`, so the
|
|
147
|
+
# dedicated \`ipfs\` user is GUARANTEED to exist before any boot step uses it
|
|
148
|
+
# (e.g. \`install -o ipfs ...\`). Creating it here (not mid-\`ipfs-setup.sh\`,
|
|
149
|
+
# which is a \`set -e\` block that could abort before its \`useradd\`) closes the
|
|
150
|
+
# first-boot race where a later step hit "invalid user 'ipfs'".
|
|
151
|
+
# \`default\` keeps cloud-init's normal login user; we only ADD \`ipfs\`.
|
|
152
|
+
# ---------------------------------------------------------------------------
|
|
153
|
+
users:
|
|
154
|
+
- default
|
|
155
|
+
- name: ipfs
|
|
156
|
+
system: true
|
|
157
|
+
home: /var/lib/ipfs
|
|
158
|
+
shell: /usr/sbin/nologin
|
|
159
|
+
lock_passwd: true
|
|
160
|
+
|
|
126
161
|
# ---------------------------------------------------------------------------
|
|
127
162
|
# On-box environment consumed by the pinnace node timers + the setup scripts.
|
|
128
163
|
# ---------------------------------------------------------------------------
|
|
@@ -145,6 +180,21 @@ write_files:
|
|
|
145
180
|
# MFS directory that holds your sites (one entry per site).
|
|
146
181
|
SITES_DIR="${sitesDir}"
|
|
147
182
|
|
|
183
|
+
# On-box PATHS the \`pinnace node\` verbs read (they assemble their
|
|
184
|
+
# NodeCommandContext from these keys):
|
|
185
|
+
# DASHBOARD_DIR - where \`node status\` writes status.json (the dashboard
|
|
186
|
+
# vhost \${DASH_DOMAIN} serves this dir).
|
|
187
|
+
# RECORDS_DIR - where \`node republish\` (publisher) EXPORTS the signed
|
|
188
|
+
# record; UNDER the dashboard dir so it is served at
|
|
189
|
+
# \${DASH_DOMAIN}/records/<id>.ipns-record, exactly where a
|
|
190
|
+
# replica's PUBLISHER_ENDPOINT + /records/ fetch looks.
|
|
191
|
+
# CACHE_DIR - where \`node mirror\` (replica) CACHES the last good
|
|
192
|
+
# record for the publisher-outage fallback (under the
|
|
193
|
+
# ipfs user's writable home).
|
|
194
|
+
DASHBOARD_DIR="/var/www/ipfs-dash"
|
|
195
|
+
RECORDS_DIR="/var/www/ipfs-dash/records"
|
|
196
|
+
CACHE_DIR="/var/lib/ipfs/records-cache"
|
|
197
|
+
|
|
148
198
|
# Role: "publisher" holds IPNS keys and signs/exports records; "replica"
|
|
149
199
|
# holds NO key and MIRRORS the publisher's signed records. The pinnace node
|
|
150
200
|
# timers self-gate on this, so scheduling all timers on every box is safe.
|
|
@@ -155,6 +205,12 @@ write_files:
|
|
|
155
205
|
|
|
156
206
|
KUBO_VERSION="${kuboVersion}"
|
|
157
207
|
|
|
208
|
+
# Node.js major installed via NodeSource (setup_<major>.x) and the PINNED
|
|
209
|
+
# pinnace version installed on the box. Both are reproducible: the same
|
|
210
|
+
# cloud-init always installs the same agent on the same runtime.
|
|
211
|
+
NODE_MAJOR="${nodeMajor}"
|
|
212
|
+
PINNACE_VERSION="${pinnaceVersion}"
|
|
213
|
+
|
|
158
214
|
# -- Kubo installer / initializer -----------------------------------------
|
|
159
215
|
- path: /usr/local/sbin/ipfs-setup.sh
|
|
160
216
|
permissions: "0755"
|
|
@@ -253,13 +309,19 @@ write_files:
|
|
|
253
309
|
content: |
|
|
254
310
|
#!/usr/bin/env bash
|
|
255
311
|
set -euo pipefail
|
|
256
|
-
|
|
257
|
-
#
|
|
312
|
+
source /etc/pinnace-node.env
|
|
313
|
+
# Install Node.js (for npm) then the PINNED pinnace binary globally. The
|
|
314
|
+
# on-box timers invoke \`pinnace node <verb>\`: one codebase, client +
|
|
315
|
+
# on-box. NODE_MAJOR / PINNACE_VERSION come from /etc/pinnace-node.env so
|
|
316
|
+
# the runtime + agent version are reproducible and one-edit overridable.
|
|
317
|
+
# This script is invoked NON-FATALLY at boot (\`|| true\`): a transient
|
|
318
|
+
# npm/registry hiccup must NOT abort provisioning (Kubo, the firewall and
|
|
319
|
+
# Caddy are already up by the time this runs). Re-run it manually to retry.
|
|
258
320
|
if ! command -v npm >/dev/null 2>&1; then
|
|
259
|
-
curl -fsSL https://deb.nodesource.com/
|
|
321
|
+
curl -fsSL "https://deb.nodesource.com/setup_\${NODE_MAJOR}.x" | bash -
|
|
260
322
|
apt-get install -y nodejs
|
|
261
323
|
fi
|
|
262
|
-
npm install -g pinnace
|
|
324
|
+
npm install -g "pinnace@\${PINNACE_VERSION}"
|
|
263
325
|
pinnace version
|
|
264
326
|
|
|
265
327
|
# -- Caddy reverse proxy for the HTTPS API (auto TLS) ---------------------
|
|
@@ -335,12 +397,20 @@ runcmd:
|
|
|
335
397
|
# Configure the HTTPS API proxy (no-op if API_DOMAIN unset)
|
|
336
398
|
- /usr/local/sbin/write-caddyfile.sh
|
|
337
399
|
|
|
338
|
-
#
|
|
339
|
-
|
|
400
|
+
# Dashboard dir, owned by the ipfs service user (guaranteed to exist: created
|
|
401
|
+
# by the \`users:\` module above, BEFORE runcmd). Done BEFORE the pinnace
|
|
402
|
+
# install so a transient install failure can never skip it.
|
|
403
|
+
- install -d -o ipfs -g ipfs /var/www/ipfs-dash
|
|
404
|
+
|
|
405
|
+
# Install the pinned pinnace binary (the box runs the same CLI as the client).
|
|
406
|
+
# BOOT-SAFE: \`|| true\` so a transient npm/registry failure does NOT abort the
|
|
407
|
+
# boot (Kubo, the firewall and Caddy are already up). Re-run
|
|
408
|
+
# /usr/local/sbin/pinnace-setup.sh manually to retry; the timers below pick it
|
|
409
|
+
# up on their next tick once the binary is present.
|
|
410
|
+
- /usr/local/sbin/pinnace-setup.sh || true
|
|
340
411
|
|
|
341
412
|
# Enable the pinnace node timers. republish self-gates to publisher, mirror to
|
|
342
413
|
# replica, so enabling all of them on every box is safe (ADR-0002).
|
|
343
|
-
- install -d -o ipfs -g ipfs /var/www/ipfs-dash
|
|
344
414
|
${enableTimers}
|
|
345
415
|
|
|
346
416
|
# =============================================================================
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cloud-init.js","sourceRoot":"","sources":["../../src/provision/cloud-init.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"cloud-init.js","sourceRoot":"","sources":["../../src/provision/cloud-init.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
package/src/cli/run.ts
CHANGED
|
@@ -15,14 +15,48 @@
|
|
|
15
15
|
* read or mutated — mirroring the `NodeCommandOps` injectable-ops pattern in
|
|
16
16
|
* node-commands and the explicit-`env` resolver in config-resolution).
|
|
17
17
|
*
|
|
18
|
-
* The on-box `pinnace node <verb>` and the `pinnace site <verb>` namespaces
|
|
19
|
-
*
|
|
20
|
-
*
|
|
18
|
+
* The on-box `pinnace node <verb>` and the `pinnace site <verb>` namespaces,
|
|
19
|
+
* the `promote` verb, dispatch through the SAME {@link RunContext}/
|
|
20
|
+
* {@link ClientDeps} seam (they are NOT a forked dispatch idiom): the on-box
|
|
21
|
+
* `node` verbs assemble a {@link NodeCommandContext} from the box env
|
|
22
|
+
* (`/etc/pinnace-node.env`, exported into `process.env` by the systemd timer's
|
|
23
|
+
* `EnvironmentFile`) and call the core `runNodeCommand`; `site`/`promote`
|
|
24
|
+
* assemble a per-host {@link KuboRpcClient} from the resolved config and call
|
|
25
|
+
* the site / promote core.
|
|
21
26
|
*/
|
|
22
27
|
import {readFileSync} from 'node:fs';
|
|
23
28
|
import {name} from '../index.js';
|
|
24
|
-
import {
|
|
25
|
-
|
|
29
|
+
import {
|
|
30
|
+
NODE_VERBS,
|
|
31
|
+
runNodeCommand as coreRunNodeCommand,
|
|
32
|
+
type NodeVerb,
|
|
33
|
+
type NodeCommandContext,
|
|
34
|
+
type NodeCommandResult,
|
|
35
|
+
} from '../node/node-commands.js';
|
|
36
|
+
import {
|
|
37
|
+
SITE_VERBS,
|
|
38
|
+
listSites as coreListSites,
|
|
39
|
+
removeSite as coreRemoveSite,
|
|
40
|
+
addSite as coreAddSite,
|
|
41
|
+
type SiteVerb,
|
|
42
|
+
type ListSitesInput,
|
|
43
|
+
type SiteListing,
|
|
44
|
+
type RemoveSiteInput,
|
|
45
|
+
type RemoveSiteResult,
|
|
46
|
+
type AddSiteInput,
|
|
47
|
+
type AddSiteResult,
|
|
48
|
+
} from '../site/site-management.js';
|
|
49
|
+
import {makeStatusOp} from '../status/status-report.js';
|
|
50
|
+
import {
|
|
51
|
+
promoteReplicaToPublisher as corePromoteReplicaToPublisher,
|
|
52
|
+
type PromoteReplicaInput,
|
|
53
|
+
type PromoteReplicaResult,
|
|
54
|
+
} from '../publisher/record-sequence.js';
|
|
55
|
+
import {
|
|
56
|
+
deriveIpnsKey as coreDeriveIpnsKey,
|
|
57
|
+
type DeriveIpnsInput as DeriveIpnsKeyInput,
|
|
58
|
+
type DerivedIpnsKey,
|
|
59
|
+
} from '../derive/ipns-key-derivation.js';
|
|
26
60
|
import {KuboRpcClient} from '../rpc/kubo-rpc-client.js';
|
|
27
61
|
import {
|
|
28
62
|
provision as coreProvision,
|
|
@@ -81,6 +115,23 @@ export interface ClientDeps {
|
|
|
81
115
|
statusReport(input: StatusReportInput): Promise<StatusReport>;
|
|
82
116
|
/** `derive` -> the master + site `id` -> IPNS id derivation (no deploy). */
|
|
83
117
|
deriveIpnsId(input: DeriveIpnsInput): string;
|
|
118
|
+
/** `node <verb>` -> the on-box command runner (context assembled by the CLI). */
|
|
119
|
+
runNodeCommand(
|
|
120
|
+
verb: NodeVerb,
|
|
121
|
+
ctx: NodeCommandContext,
|
|
122
|
+
): Promise<NodeCommandResult>;
|
|
123
|
+
/** `site list` -> enumerate the node's MFS sites. */
|
|
124
|
+
listSites(input: ListSitesInput): Promise<SiteListing[]>;
|
|
125
|
+
/** `site remove` -> drop an MFS site + unpin its content. */
|
|
126
|
+
removeSite(input: RemoveSiteInput): Promise<RemoveSiteResult>;
|
|
127
|
+
/** `site add` -> place an already-imported CID into MFS as a site. */
|
|
128
|
+
addSite(input: AddSiteInput): Promise<AddSiteResult>;
|
|
129
|
+
/** `promote` -> derive the per-site key material from the master + `id`. */
|
|
130
|
+
deriveIpnsKey(input: DeriveIpnsKeyInput): DerivedIpnsKey;
|
|
131
|
+
/** `promote` -> import the key + flip the node's role to publisher (story 14). */
|
|
132
|
+
promoteReplicaToPublisher(
|
|
133
|
+
input: PromoteReplicaInput,
|
|
134
|
+
): Promise<PromoteReplicaResult>;
|
|
84
135
|
}
|
|
85
136
|
|
|
86
137
|
/** The real core, used when a caller does not inject stubs. */
|
|
@@ -90,6 +141,12 @@ const DEFAULT_DEPS: ClientDeps = {
|
|
|
90
141
|
emitCi: coreEmitCi,
|
|
91
142
|
statusReport: coreStatusReport,
|
|
92
143
|
deriveIpnsId: coreDeriveIpnsId,
|
|
144
|
+
runNodeCommand: coreRunNodeCommand,
|
|
145
|
+
listSites: coreListSites,
|
|
146
|
+
removeSite: coreRemoveSite,
|
|
147
|
+
addSite: coreAddSite,
|
|
148
|
+
deriveIpnsKey: coreDeriveIpnsKey,
|
|
149
|
+
promoteReplicaToPublisher: corePromoteReplicaToPublisher,
|
|
93
150
|
};
|
|
94
151
|
|
|
95
152
|
/**
|
|
@@ -263,6 +320,9 @@ export async function run(
|
|
|
263
320
|
if (command === 'derive' || command === 'ipns-id') {
|
|
264
321
|
return runDerive(rest, rc);
|
|
265
322
|
}
|
|
323
|
+
if (command === 'promote') {
|
|
324
|
+
return runPromote(rest, rc);
|
|
325
|
+
}
|
|
266
326
|
|
|
267
327
|
rc.err(`${name()}: unknown command '${command}'`);
|
|
268
328
|
return 1;
|
|
@@ -340,9 +400,11 @@ function parseArgs(argv: readonly string[]): ParsedArgs {
|
|
|
340
400
|
|
|
341
401
|
/**
|
|
342
402
|
* `provision --host <h> --api-domain <d> --acme-email <e> --bearer-token <t>
|
|
343
|
-
* --role <r> [
|
|
344
|
-
*
|
|
345
|
-
*
|
|
403
|
+
* --role <r> [--pinnace-version <v>] [--node-major <n>] [--kubo-version <v>]
|
|
404
|
+
* [...]` -> core {@link ClientDeps.provision}. Purely arg-driven (provisioning
|
|
405
|
+
* inputs are per-box and not stored in `pinnace.json`); prints the generated
|
|
406
|
+
* cloud-init to stdout. The pinned pinnace/Node/Kubo versions default to the
|
|
407
|
+
* generator's named knobs and are overridable per-box via these flags.
|
|
346
408
|
*/
|
|
347
409
|
function runProvision(argv: readonly string[], rc: ResolvedRunContext): number {
|
|
348
410
|
const {flags} = parseArgs(argv);
|
|
@@ -380,6 +442,9 @@ function runProvision(argv: readonly string[], rc: ResolvedRunContext): number {
|
|
|
380
442
|
input.dashboardDomain = flags['dashboard-domain'];
|
|
381
443
|
if (flags['publisher-endpoint'])
|
|
382
444
|
input.publisherEndpoint = flags['publisher-endpoint'];
|
|
445
|
+
if (flags['kubo-version']) input.kuboVersion = flags['kubo-version'];
|
|
446
|
+
if (flags['pinnace-version']) input.pinnaceVersion = flags['pinnace-version'];
|
|
447
|
+
if (flags['node-major']) input.nodeMajor = flags['node-major'];
|
|
383
448
|
|
|
384
449
|
const result = rc.deps.provision(input);
|
|
385
450
|
rc.out(result.cloudInit.contents);
|
|
@@ -611,14 +676,22 @@ function cliOverridesFromFlags(flags: Record<string, string>): CliOverrides {
|
|
|
611
676
|
}
|
|
612
677
|
|
|
613
678
|
/**
|
|
614
|
-
*
|
|
615
|
-
*
|
|
616
|
-
*
|
|
617
|
-
*
|
|
618
|
-
*
|
|
679
|
+
* `pinnace site <verb> [args] [--host <name>]` -> the site core
|
|
680
|
+
* ({@link ClientDeps.listSites}/{@link ClientDeps.removeSite}/
|
|
681
|
+
* {@link ClientDeps.addSite}). Assembles a per-host {@link KuboRpcClient} from
|
|
682
|
+
* the resolved config (endpoint) + the host's env-only token, then dispatches:
|
|
683
|
+
* - `list` -> listSites({client})
|
|
684
|
+
* - `remove <id>` -> removeSite({client, id})
|
|
685
|
+
* - `add <id> <cid>` -> addSite({client, id, cid})
|
|
686
|
+
* The `--host <name>` selects which configured node to act on; it may be
|
|
687
|
+
* omitted only when the config has exactly one host (see {@link selectHost}).
|
|
619
688
|
*/
|
|
620
|
-
function runSiteCli(
|
|
621
|
-
|
|
689
|
+
async function runSiteCli(
|
|
690
|
+
argv: readonly string[],
|
|
691
|
+
rc: ResolvedRunContext,
|
|
692
|
+
): Promise<number> {
|
|
693
|
+
const {flags, positionals} = parseArgs(argv);
|
|
694
|
+
const [verb, ...verbArgs] = positionals;
|
|
622
695
|
if (!verb) {
|
|
623
696
|
rc.err(`pinnace site: expected a verb (${SITE_VERBS.join(', ')})`);
|
|
624
697
|
return 1;
|
|
@@ -629,16 +702,68 @@ function runSiteCli(argv: readonly string[], rc: ResolvedRunContext): number {
|
|
|
629
702
|
);
|
|
630
703
|
return 1;
|
|
631
704
|
}
|
|
705
|
+
|
|
706
|
+
const cli = cliOverridesFromFlags(flags);
|
|
707
|
+
const cfg = resolveConfig({file: rc.file, env: rc.env, cli});
|
|
708
|
+
const client = buildHostClient('pinnace site', flags['host'], cfg, rc, cli);
|
|
709
|
+
if (!client) return 1; // buildHostClient already emitted the loud error.
|
|
710
|
+
|
|
711
|
+
if (verb === 'list') {
|
|
712
|
+
const sites = await rc.deps.listSites({client});
|
|
713
|
+
for (const site of sites) {
|
|
714
|
+
rc.out(
|
|
715
|
+
`${site.id}: cid ${site.cid}${site.ipns ? ` ipns ${site.ipns}` : ''}`,
|
|
716
|
+
);
|
|
717
|
+
}
|
|
718
|
+
return 0;
|
|
719
|
+
}
|
|
720
|
+
if (verb === 'remove') {
|
|
721
|
+
const [id] = verbArgs;
|
|
722
|
+
if (!id) {
|
|
723
|
+
rc.err(
|
|
724
|
+
'pinnace site remove: usage: pinnace site remove <id> [--host <name>]',
|
|
725
|
+
);
|
|
726
|
+
return 1;
|
|
727
|
+
}
|
|
728
|
+
const result = await rc.deps.removeSite({client, id});
|
|
729
|
+
rc.out(
|
|
730
|
+
`removed ${result.id}${result.cid ? ` (cid ${result.cid}, unpinned=${result.unpinned})` : ''}`,
|
|
731
|
+
);
|
|
732
|
+
return 0;
|
|
733
|
+
}
|
|
734
|
+
// verb === 'add'
|
|
735
|
+
const [id, cid] = verbArgs;
|
|
736
|
+
if (!id || !cid) {
|
|
737
|
+
rc.err(
|
|
738
|
+
'pinnace site add: usage: pinnace site add <id> <cid> [--host <name>]',
|
|
739
|
+
);
|
|
740
|
+
return 1;
|
|
741
|
+
}
|
|
742
|
+
const result = await rc.deps.addSite({client, id, cid});
|
|
743
|
+
rc.out(`added ${result.id} -> ${result.cid}`);
|
|
632
744
|
return 0;
|
|
633
745
|
}
|
|
634
746
|
|
|
635
747
|
/**
|
|
636
|
-
*
|
|
637
|
-
*
|
|
638
|
-
*
|
|
639
|
-
*
|
|
748
|
+
* `pinnace node <verb>` -> the on-box command core ({@link
|
|
749
|
+
* ClientDeps.runNodeCommand}). This is the load-bearing on-box wiring: the verb
|
|
750
|
+
* runs ON a provisioned box (invoked by the systemd timer, whose
|
|
751
|
+
* `EnvironmentFile=/etc/pinnace-node.env` exports the box config into the
|
|
752
|
+
* environment {@link RunContext.env} reads). It assembles a
|
|
753
|
+
* {@link NodeCommandContext} from that env — a LOCAL Kubo client (127.0.0.1:5001
|
|
754
|
+
* + `RPC_BEARER_TOKEN`), the box `role` (`NODE_ROLE`), and the on-box paths
|
|
755
|
+
* (`RECORDS_DIR`/`CACHE_DIR`/`DASHBOARD_DIR`/`SITES_DIR`), the replica
|
|
756
|
+
* `PUBLISHER_ENDPOINT`, and the `WARM_GATEWAYS` list — and injects the OWNED
|
|
757
|
+
* `status` op ({@link makeStatusOp}, the real per-site announce/gateway report)
|
|
758
|
+
* so the production status path is not the thin default stub. It then invokes
|
|
759
|
+
* the core (NOT a validate-and-return-0). A live run proved that without this
|
|
760
|
+
* the `republish`/`mirror` timers are no-ops. The token is env-only and its
|
|
761
|
+
* absence is a LOUD, named error (never a silent empty bearer / 401).
|
|
640
762
|
*/
|
|
641
|
-
function runNodeCli(
|
|
763
|
+
async function runNodeCli(
|
|
764
|
+
argv: readonly string[],
|
|
765
|
+
rc: ResolvedRunContext,
|
|
766
|
+
): Promise<number> {
|
|
642
767
|
const [verb] = argv;
|
|
643
768
|
if (!verb) {
|
|
644
769
|
rc.err(`pinnace node: expected a verb (${NODE_VERBS.join(', ')})`);
|
|
@@ -650,5 +775,196 @@ function runNodeCli(argv: readonly string[], rc: ResolvedRunContext): number {
|
|
|
650
775
|
);
|
|
651
776
|
return 1;
|
|
652
777
|
}
|
|
778
|
+
|
|
779
|
+
const env = rc.env;
|
|
780
|
+
const token = env['RPC_BEARER_TOKEN'];
|
|
781
|
+
if (!token) {
|
|
782
|
+
rc.err(
|
|
783
|
+
'pinnace node: RPC_BEARER_TOKEN not set — the on-box bearer is env-only ' +
|
|
784
|
+
'(read from /etc/pinnace-node.env); never a silent empty token',
|
|
785
|
+
);
|
|
786
|
+
return 1;
|
|
787
|
+
}
|
|
788
|
+
const role = env['NODE_ROLE'];
|
|
789
|
+
if (role !== 'publisher' && role !== 'replica') {
|
|
790
|
+
rc.err(
|
|
791
|
+
"pinnace node: NODE_ROLE must be 'publisher' or 'replica' " +
|
|
792
|
+
'(read from /etc/pinnace-node.env)',
|
|
793
|
+
);
|
|
794
|
+
return 1;
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
// The box's OWN daemon: the local Kubo RPC on 127.0.0.1:5001 (Caddy fronts it
|
|
798
|
+
// for external clients, but the on-box agent speaks to it directly).
|
|
799
|
+
const client = new KuboRpcClient({
|
|
800
|
+
baseUrl: 'http://127.0.0.1:5001',
|
|
801
|
+
token,
|
|
802
|
+
});
|
|
803
|
+
|
|
804
|
+
const ctx: NodeCommandContext = {
|
|
805
|
+
client,
|
|
806
|
+
role,
|
|
807
|
+
// The production status path uses the OWNED status op (real announce +
|
|
808
|
+
// gateway report), not the thin defaultStatus stand-in.
|
|
809
|
+
ops: {status: makeStatusOp()},
|
|
810
|
+
};
|
|
811
|
+
if (env['SITES_DIR']) ctx.sitesDir = env['SITES_DIR'];
|
|
812
|
+
if (env['RECORDS_DIR']) ctx.recordsDir = env['RECORDS_DIR'];
|
|
813
|
+
if (env['CACHE_DIR']) ctx.cacheDir = env['CACHE_DIR'];
|
|
814
|
+
if (env['DASHBOARD_DIR']) ctx.dashboardDir = env['DASHBOARD_DIR'];
|
|
815
|
+
if (env['PUBLISHER_ENDPOINT'])
|
|
816
|
+
ctx.publisherEndpoint = env['PUBLISHER_ENDPOINT'];
|
|
817
|
+
const gateways = splitWarmGateways(env['WARM_GATEWAYS']);
|
|
818
|
+
if (gateways.length > 0) ctx.gateways = gateways;
|
|
819
|
+
|
|
820
|
+
const result = await rc.deps.runNodeCommand(verb as NodeVerb, ctx);
|
|
821
|
+
if (result.skipped) {
|
|
822
|
+
rc.out(`pinnace node ${verb}: skipped (${result.skippedReason})`);
|
|
823
|
+
return 0;
|
|
824
|
+
}
|
|
825
|
+
for (const site of result.sites) {
|
|
826
|
+
rc.out(
|
|
827
|
+
` ${site.id}${site.cid ? ` cid ${site.cid}` : ''}${site.status ? ` (${site.status})` : ''}`,
|
|
828
|
+
);
|
|
829
|
+
}
|
|
653
830
|
return 0;
|
|
654
831
|
}
|
|
832
|
+
|
|
833
|
+
/**
|
|
834
|
+
* `pinnace promote <id> [--host <name>]` -> {@link
|
|
835
|
+
* ClientDeps.promoteReplicaToPublisher} (spec user story 14). Derives the
|
|
836
|
+
* per-site key from the env-only master + the site `id` (the KDF input,
|
|
837
|
+
* {@link ClientDeps.deriveIpnsKey}), assembles the chosen host's client, and
|
|
838
|
+
* promotes it: import the key + flip the role to publisher, recovering the name
|
|
839
|
+
* within the record's validity window without content downtime. The master is
|
|
840
|
+
* env-ONLY (never from `pinnace.json`); its absence is a LOUD error.
|
|
841
|
+
*/
|
|
842
|
+
async function runPromote(
|
|
843
|
+
argv: readonly string[],
|
|
844
|
+
rc: ResolvedRunContext,
|
|
845
|
+
): Promise<number> {
|
|
846
|
+
const {flags, positionals} = parseArgs(argv);
|
|
847
|
+
const [siteId] = positionals;
|
|
848
|
+
if (!siteId) {
|
|
849
|
+
rc.err('pinnace promote: usage: pinnace promote <id> [--host <name>]');
|
|
850
|
+
return 1;
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
const master = resolveMasterSecret({env: rc.env});
|
|
854
|
+
if (!master) {
|
|
855
|
+
rc.err(
|
|
856
|
+
'pinnace promote: master secret not set — export PINNACE_MASTER (env-only; never read from pinnace.json)',
|
|
857
|
+
);
|
|
858
|
+
return 1;
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
const cli = cliOverridesFromFlags(flags);
|
|
862
|
+
const cfg = resolveConfig({file: rc.file, env: rc.env, cli});
|
|
863
|
+
const host = pickHost('pinnace promote', flags['host'], cfg, rc);
|
|
864
|
+
if (!host) return 1;
|
|
865
|
+
const client = clientForHost('pinnace promote', host, rc, cli);
|
|
866
|
+
if (!client) return 1;
|
|
867
|
+
|
|
868
|
+
// The single `id` IS the KDF input (no separate keyId), matching `derive`.
|
|
869
|
+
const id = cfg.sites.find((s) => s.id === siteId)?.id ?? siteId;
|
|
870
|
+
const derived = rc.deps.deriveIpnsKey({master, keyId: id});
|
|
871
|
+
const result = await rc.deps.promoteReplicaToPublisher({
|
|
872
|
+
client,
|
|
873
|
+
currentRole: host.role,
|
|
874
|
+
keyName: id,
|
|
875
|
+
derived,
|
|
876
|
+
});
|
|
877
|
+
rc.out(
|
|
878
|
+
`promoted ${result.keyName} to ${result.role}${result.ipns ? ` (ipns ${result.ipns})` : ''}`,
|
|
879
|
+
);
|
|
880
|
+
return 0;
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
// ---------------------------------------------------------------------------
|
|
884
|
+
// Host selection + client assembly (shared by site + promote).
|
|
885
|
+
// ---------------------------------------------------------------------------
|
|
886
|
+
|
|
887
|
+
/**
|
|
888
|
+
* Pick the configured host to act on. `--host <name>` selects it by name; it
|
|
889
|
+
* may be omitted ONLY when the config has exactly one host (the unambiguous
|
|
890
|
+
* case). Zero hosts, an unknown name, or an omitted name with several hosts is
|
|
891
|
+
* a LOUD, specific error (returns undefined after emitting it).
|
|
892
|
+
*/
|
|
893
|
+
function pickHost(
|
|
894
|
+
prefix: string,
|
|
895
|
+
hostName: string | undefined,
|
|
896
|
+
cfg: {hosts: Array<{name: string; endpoint: string; role: HostRole}>},
|
|
897
|
+
rc: ResolvedRunContext,
|
|
898
|
+
): {name: string; endpoint: string; role: HostRole} | undefined {
|
|
899
|
+
if (cfg.hosts.length === 0) {
|
|
900
|
+
rc.err(`${prefix}: no hosts configured (add hosts to pinnace.json)`);
|
|
901
|
+
return undefined;
|
|
902
|
+
}
|
|
903
|
+
if (hostName) {
|
|
904
|
+
const match = cfg.hosts.find((h) => h.name === hostName);
|
|
905
|
+
if (!match) {
|
|
906
|
+
rc.err(
|
|
907
|
+
`${prefix}: unknown host '${hostName}'; configured hosts: ${cfg.hosts
|
|
908
|
+
.map((h) => h.name)
|
|
909
|
+
.join(', ')}`,
|
|
910
|
+
);
|
|
911
|
+
return undefined;
|
|
912
|
+
}
|
|
913
|
+
return match;
|
|
914
|
+
}
|
|
915
|
+
if (cfg.hosts.length > 1) {
|
|
916
|
+
rc.err(
|
|
917
|
+
`${prefix}: multiple hosts configured; pass --host <name> (one of ${cfg.hosts
|
|
918
|
+
.map((h) => h.name)
|
|
919
|
+
.join(', ')})`,
|
|
920
|
+
);
|
|
921
|
+
return undefined;
|
|
922
|
+
}
|
|
923
|
+
return cfg.hosts[0];
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
/**
|
|
927
|
+
* Build a {@link KuboRpcClient} for a chosen host, resolving its bearer token
|
|
928
|
+
* env-only (CLI > env, no file). A missing token is a LOUD, named error
|
|
929
|
+
* (returns undefined after emitting it).
|
|
930
|
+
*/
|
|
931
|
+
function clientForHost(
|
|
932
|
+
prefix: string,
|
|
933
|
+
host: {name: string; endpoint: string},
|
|
934
|
+
rc: ResolvedRunContext,
|
|
935
|
+
cli: CliOverrides,
|
|
936
|
+
): KuboRpcClient | undefined {
|
|
937
|
+
let token: string;
|
|
938
|
+
try {
|
|
939
|
+
token = resolveHostToken({hostName: host.name, env: rc.env, cli});
|
|
940
|
+
} catch (error) {
|
|
941
|
+
if (error instanceof MissingHostTokenError) {
|
|
942
|
+
rc.err(`${prefix}: ${error.message}`);
|
|
943
|
+
return undefined;
|
|
944
|
+
}
|
|
945
|
+
throw error;
|
|
946
|
+
}
|
|
947
|
+
return new KuboRpcClient({baseUrl: host.endpoint, token});
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
/** Pick a host AND build its client in one step (the common site path). */
|
|
951
|
+
function buildHostClient(
|
|
952
|
+
prefix: string,
|
|
953
|
+
hostName: string | undefined,
|
|
954
|
+
cfg: {hosts: Array<{name: string; endpoint: string; role: HostRole}>},
|
|
955
|
+
rc: ResolvedRunContext,
|
|
956
|
+
cli: CliOverrides,
|
|
957
|
+
): KuboRpcClient | undefined {
|
|
958
|
+
const host = pickHost(prefix, hostName, cfg, rc);
|
|
959
|
+
if (!host) return undefined;
|
|
960
|
+
return clientForHost(prefix, host, rc, cli);
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
/** Split the space-separated `WARM_GATEWAYS` env value into a template list. */
|
|
964
|
+
function splitWarmGateways(value: string | undefined): string[] {
|
|
965
|
+
if (!value) return [];
|
|
966
|
+
return value
|
|
967
|
+
.split(/\s+/)
|
|
968
|
+
.map((s) => s.trim())
|
|
969
|
+
.filter(Boolean);
|
|
970
|
+
}
|