pinnace 0.2.0 → 0.3.1

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 CHANGED
@@ -26,7 +26,9 @@ Requires Node >= 22.
26
26
 
27
27
  ## Configuration + secrets
28
28
 
29
- Every setting resolves **CLI arg > env (via `ldenv`) > `pinnace.json`**.
29
+ Every setting resolves **CLI arg > exported env > `.env.local` > `.env` > `pinnace.json`**.
30
+
31
+ On startup the `pinnace` bin auto-loads `.env` then `.env.local` from the current directory into the environment (via `ldenv`), so a global install (`npm install -g pinnace`) picks up your secrets with no wrapper: just drop them in `.env.local` and run `pinnace …`. Loading is silent and cwd-only (never a home/global location), and it only AUGMENTS the environment: a value you exported explicitly still wins over the file (`.env.local` overrides `.env`, both sit below an exported var and above `pinnace.json`).
30
32
 
31
33
  `pinnace.json` holds only NON-secret structure (commit-safe):
32
34
 
@@ -46,13 +48,13 @@ Every setting resolves **CLI arg > env (via `ldenv`) > `pinnace.json`**.
46
48
  Secrets are **env-only, never in the config file** (structurally: the resolver has no file path for them). Each host's bearer token is read from `PINNACE_HOST_<NAME>_TOKEN` (the name upper-cased), and the master from `PINNACE_MASTER`:
47
49
 
48
50
  ```sh
49
- # .env.local (git-ignored) — the ONLY place secrets live
51
+ # .env.local (git-ignored) — the ONLY place secrets live; auto-loaded from cwd
50
52
  PINNACE_MASTER=<your high-entropy master secret>
51
53
  PINNACE_HOST_PUBLISHER_TOKEN=<publisher bearer token>
52
54
  PINNACE_HOST_REPLICA_TOKEN=<replica bearer token>
53
55
  ```
54
56
 
55
- A missing token fails loud (naming the exact env var), never a silent empty token. Point the CLI at a config anywhere with `--config <path>`.
57
+ This `.env.local` is loaded automatically from the directory you run `pinnace` in (no `export` needed); an explicitly exported value still takes precedence over it. A missing token fails loud (naming the exact env var), never a silent empty token. Point the CLI at a config anywhere with `--config <path>`.
56
58
 
57
59
  ## The end-to-end setup
58
60
 
package/dist/cli/bin.js CHANGED
@@ -8,9 +8,15 @@
8
8
  * on-box `pinnace node <verb>` subcommands share this same binary but are wired
9
9
  * by their own task. Config/env/core are all injectable there so the dispatch
10
10
  * is unit-tested without a process or the operator's real env/config.
11
+ *
12
+ * Startup: the bin calls the {@link main} shim (`./startup.ts`), which loads the
13
+ * cwd's `.env` / `.env.local` into `process.env` via `ldenv` BEFORE `run()`
14
+ * reads env — so a global `npm install -g pinnace` gets the env-only secrets
15
+ * model with no `pnpm ldenv` dev wrapper. The pure `run(argv, {env})` path stays
16
+ * hermetic (the dotenv load lives in the shim, not in `run()`).
11
17
  */
12
- import { run } from './run.js';
13
- run(process.argv.slice(2)).then((code) => process.exit(code), (err) => {
18
+ import { main } from './startup.js';
19
+ main(process.argv.slice(2)).then((code) => process.exit(code), (err) => {
14
20
  console.error(err instanceof Error ? err.message : String(err));
15
21
  process.exit(1);
16
22
  });
@@ -1 +1 @@
1
- {"version":3,"file":"bin.js","sourceRoot":"","sources":["../../src/cli/bin.ts"],"names":[],"mappings":";AACA;;;;;;;;;GASG;AACH,OAAO,EAAC,GAAG,EAAC,MAAM,UAAU,CAAC;AAE7B,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAC9B,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAC5B,CAAC,GAAG,EAAE,EAAE;IACP,OAAO,CAAC,KAAK,CAAC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;IAChE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AACjB,CAAC,CACD,CAAC"}
1
+ {"version":3,"file":"bin.js","sourceRoot":"","sources":["../../src/cli/bin.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,EAAC,IAAI,EAAC,MAAM,cAAc,CAAC;AAElC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAC/B,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAC5B,CAAC,GAAG,EAAE,EAAE;IACP,OAAO,CAAC,KAAK,CAAC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;IAChE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AACjB,CAAC,CACD,CAAC"}
@@ -0,0 +1,19 @@
1
+ /** Injectable seams for the startup shim (defaulted to the real ones). */
2
+ export interface StartupOptions {
3
+ /**
4
+ * Load `.env` / `.env.local` from the cwd into `process.env`. Defaults to
5
+ * `ldenv`'s `loadEnv` (reads `.env` then `.env.local`, local overriding,
6
+ * never overriding an already-exported value). Injected only for tests.
7
+ */
8
+ loadDotEnv?: () => void;
9
+ }
10
+ /**
11
+ * The CLI startup entry: load the cwd's `.env` / `.env.local` into
12
+ * `process.env` (so a global install gets the env-only secrets), THEN dispatch
13
+ * `run()` over the process argv. Returns the process exit code.
14
+ *
15
+ * This is what the `pinnace` bin calls. It keeps the `loadEnv()` side effect
16
+ * out of the pure `run()` path so `run(argv, {env})` stays hermetic.
17
+ */
18
+ export declare function main(argv?: readonly string[], options?: StartupOptions): Promise<number>;
19
+ //# sourceMappingURL=startup.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"startup.d.ts","sourceRoot":"","sources":["../../src/cli/startup.ts"],"names":[],"mappings":"AA+BA,0EAA0E;AAC1E,MAAM,WAAW,cAAc;IAC9B;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,IAAI,CAAC;CACxB;AAED;;;;;;;GAOG;AACH,wBAAsB,IAAI,CACzB,IAAI,GAAE,SAAS,MAAM,EAA0B,EAC/C,OAAO,GAAE,cAAmB,GAC1B,OAAO,CAAC,MAAM,CAAC,CAMjB"}
@@ -0,0 +1,46 @@
1
+ /**
2
+ * The `pinnace` CLI STARTUP shim: the thin process-entry glue that runs BEFORE
3
+ * the pure {@link run} dispatch. Its whole job is to make the env-only secrets
4
+ * model (CONTEXT.md `master key` / `token`) ergonomic for EVERY user, including
5
+ * a global `npm install -g pinnace`, by loading `.env` / `.env.local` from the
6
+ * cwd into `process.env` at startup, so no `pnpm ldenv` dev wrapper is needed.
7
+ *
8
+ * WHY a shim (and not inside {@link run}): `run(argv, {env})` is pure and
9
+ * hermetically unit-tested with an INJECTED `env` record; it must never read a
10
+ * real `.env` / touch a real cwd. So the ONE impure, filesystem-touching step
11
+ * (`ldenv`'s {@link loadEnv}) lives HERE, in the bin/startup layer, in front of
12
+ * `run()`. Config resolution + {@link resolveMasterSecret} read `process.env`,
13
+ * so `loadEnv()` must have mutated it BEFORE `run()` runs.
14
+ *
15
+ * PRECEDENCE (preserved, not changed). `loadEnv()` reads `.env` then
16
+ * `.env.local` (local overriding default) and MUTATES `process.env`, but it
17
+ * does NOT override a value ALREADY set in `process.env`. So dotenv slots in
18
+ * BELOW an explicitly-exported env var and ABOVE `pinnace.json`, keeping the
19
+ * documented "env > file" rule intact. The effective chain becomes:
20
+ * **CLI arg > exported `process.env` > `.env.local` > `.env` > `pinnace.json`**.
21
+ * The master + host tokens stay env-only: `.env.local` is merely a FILE SOURCE
22
+ * for those env vars, never a `pinnace.json` field.
23
+ *
24
+ * The `loadEnv` call is injected (defaulting to the real `ldenv` one) purely so
25
+ * the shim is unit-testable; production always uses the real `ldenv.loadEnv`,
26
+ * which is cwd-based only (never a global/home location), silent, and only
27
+ * AUGMENTS env — an already-exported value is left untouched.
28
+ */
29
+ import { loadEnv } from 'ldenv';
30
+ import { run } from './run.js';
31
+ /**
32
+ * The CLI startup entry: load the cwd's `.env` / `.env.local` into
33
+ * `process.env` (so a global install gets the env-only secrets), THEN dispatch
34
+ * `run()` over the process argv. Returns the process exit code.
35
+ *
36
+ * This is what the `pinnace` bin calls. It keeps the `loadEnv()` side effect
37
+ * out of the pure `run()` path so `run(argv, {env})` stays hermetic.
38
+ */
39
+ export async function main(argv = process.argv.slice(2), options = {}) {
40
+ const loadDotEnv = options.loadDotEnv ?? (() => void loadEnv());
41
+ // Load the cwd's dotenv files BEFORE run() reads process.env. This only
42
+ // augments process.env; an already-exported value still wins.
43
+ loadDotEnv();
44
+ return run(argv);
45
+ }
46
+ //# sourceMappingURL=startup.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"startup.js","sourceRoot":"","sources":["../../src/cli/startup.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,OAAO,EAAC,OAAO,EAAC,MAAM,OAAO,CAAC;AAC9B,OAAO,EAAC,GAAG,EAAC,MAAM,UAAU,CAAC;AAY7B;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,IAAI,CACzB,OAA0B,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,EAC/C,UAA0B,EAAE;IAE5B,MAAM,UAAU,GAAG,OAAO,CAAC,UAAU,IAAI,CAAC,GAAG,EAAE,CAAC,KAAK,OAAO,EAAE,CAAC,CAAC;IAChE,wEAAwE;IACxE,8DAA8D;IAC9D,UAAU,EAAE,CAAC;IACb,OAAO,GAAG,CAAC,IAAI,CAAC,CAAC;AAClB,CAAC"}
@@ -1 +1 @@
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"}
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;AA8cD;;;GAGG;AACH,eAAO,MAAM,mBAAmB,EAAE,YAWjC,CAAC;AAOF;;;;;GAKG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,cAAc,GAAG,eAAe,CAUhE"}
@@ -10,7 +10,7 @@ const DEFAULT_KUBO_VERSION = 'v0.38.1';
10
10
  * the same agent. Overridable per-box via {@link ProvisionInput.pinnaceVersion}.
11
11
  * `pinnace@0.1.0` is the first published release (npm, public, OIDC provenance).
12
12
  */
13
- const DEFAULT_PINNACE_VERSION = '0.2.0';
13
+ const DEFAULT_PINNACE_VERSION = '0.3.1';
14
14
  /**
15
15
  * The pinned Node.js major the box installs via NodeSource (`setup_<this>.x`).
16
16
  * Node 22 is a current active LTS; Node 20 (the old literal) is the OLDEST LTS
@@ -252,7 +252,10 @@ write_files:
252
252
  cfg Routing.Type "auto"
253
253
  # Re-announce everything we serve; interval well under the record expiry.
254
254
  # (Kubo 0.38 uses Provide.*; the pre-0.38 keys FATAL the daemon at boot.)
255
- cfg Provide.Interval "12h"
255
+ # Provide.Interval is a DURATION value Kubo requires as JSON. Without --json,
256
+ # setting it errors (not found / maybe use --json) and, under set -e, aborts
257
+ # this whole setup script (leaving the box half-provisioned).
258
+ cfg --json Provide.Interval '"12h"'
256
259
  cfg Provide.Strategy "all"
257
260
 
258
261
  # --- Resource hygiene for a small box ---
@@ -288,6 +291,12 @@ write_files:
288
291
  User=ipfs
289
292
  Group=ipfs
290
293
  Environment=IPFS_PATH=/var/lib/ipfs/.ipfs
294
+ # Guarantee the datadir exists (owned by ipfs) BEFORE the sandboxed daemon
295
+ # starts: ReadWritePaths=/var/lib/ipfs requires the path to exist at unit
296
+ # start, so a missing datadir otherwise fails namespace setup with
297
+ # 226/NAMESPACE and crash-loops. The leading '+' runs this as root, outside
298
+ # the sandbox; it is idempotent.
299
+ ExecStartPre=+/usr/bin/install -d -o ipfs -g ipfs /var/lib/ipfs
291
300
  ExecStart=/usr/local/bin/ipfs daemon --migrate=true --enable-gc
292
301
  Restart=on-failure
293
302
  RestartSec=5
@@ -1 +1 @@
1
- {"version":3,"file":"cloud-init.js","sourceRoot":"","sources":["../../src/provision/cloud-init.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"cloud-init.js","sourceRoot":"","sources":["../../src/provision/cloud-init.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pinnace",
3
- "version": "0.2.0",
3
+ "version": "0.3.1",
4
4
  "license": "AGPL-3.0-only",
5
5
  "description": "Self-host a static website on IPFS across one or more self-owned Kubo nodes.",
6
6
  "repository": {
@@ -27,7 +27,8 @@
27
27
  ],
28
28
  "dependencies": {
29
29
  "files-from-path": "^1.1.4",
30
- "ipfs-car": "^3.1.0"
30
+ "ipfs-car": "^3.1.0",
31
+ "ldenv": "^0.3.16"
31
32
  },
32
33
  "devDependencies": {
33
34
  "@ipld/car": "^5.4.6",
package/src/cli/bin.ts CHANGED
@@ -8,10 +8,16 @@
8
8
  * on-box `pinnace node <verb>` subcommands share this same binary but are wired
9
9
  * by their own task. Config/env/core are all injectable there so the dispatch
10
10
  * is unit-tested without a process or the operator's real env/config.
11
+ *
12
+ * Startup: the bin calls the {@link main} shim (`./startup.ts`), which loads the
13
+ * cwd's `.env` / `.env.local` into `process.env` via `ldenv` BEFORE `run()`
14
+ * reads env — so a global `npm install -g pinnace` gets the env-only secrets
15
+ * model with no `pnpm ldenv` dev wrapper. The pure `run(argv, {env})` path stays
16
+ * hermetic (the dotenv load lives in the shim, not in `run()`).
11
17
  */
12
- import {run} from './run.js';
18
+ import {main} from './startup.js';
13
19
 
14
- run(process.argv.slice(2)).then(
20
+ main(process.argv.slice(2)).then(
15
21
  (code) => process.exit(code),
16
22
  (err) => {
17
23
  console.error(err instanceof Error ? err.message : String(err));
@@ -0,0 +1,59 @@
1
+ /**
2
+ * The `pinnace` CLI STARTUP shim: the thin process-entry glue that runs BEFORE
3
+ * the pure {@link run} dispatch. Its whole job is to make the env-only secrets
4
+ * model (CONTEXT.md `master key` / `token`) ergonomic for EVERY user, including
5
+ * a global `npm install -g pinnace`, by loading `.env` / `.env.local` from the
6
+ * cwd into `process.env` at startup, so no `pnpm ldenv` dev wrapper is needed.
7
+ *
8
+ * WHY a shim (and not inside {@link run}): `run(argv, {env})` is pure and
9
+ * hermetically unit-tested with an INJECTED `env` record; it must never read a
10
+ * real `.env` / touch a real cwd. So the ONE impure, filesystem-touching step
11
+ * (`ldenv`'s {@link loadEnv}) lives HERE, in the bin/startup layer, in front of
12
+ * `run()`. Config resolution + {@link resolveMasterSecret} read `process.env`,
13
+ * so `loadEnv()` must have mutated it BEFORE `run()` runs.
14
+ *
15
+ * PRECEDENCE (preserved, not changed). `loadEnv()` reads `.env` then
16
+ * `.env.local` (local overriding default) and MUTATES `process.env`, but it
17
+ * does NOT override a value ALREADY set in `process.env`. So dotenv slots in
18
+ * BELOW an explicitly-exported env var and ABOVE `pinnace.json`, keeping the
19
+ * documented "env > file" rule intact. The effective chain becomes:
20
+ * **CLI arg > exported `process.env` > `.env.local` > `.env` > `pinnace.json`**.
21
+ * The master + host tokens stay env-only: `.env.local` is merely a FILE SOURCE
22
+ * for those env vars, never a `pinnace.json` field.
23
+ *
24
+ * The `loadEnv` call is injected (defaulting to the real `ldenv` one) purely so
25
+ * the shim is unit-testable; production always uses the real `ldenv.loadEnv`,
26
+ * which is cwd-based only (never a global/home location), silent, and only
27
+ * AUGMENTS env — an already-exported value is left untouched.
28
+ */
29
+ import {loadEnv} from 'ldenv';
30
+ import {run} from './run.js';
31
+
32
+ /** Injectable seams for the startup shim (defaulted to the real ones). */
33
+ export interface StartupOptions {
34
+ /**
35
+ * Load `.env` / `.env.local` from the cwd into `process.env`. Defaults to
36
+ * `ldenv`'s `loadEnv` (reads `.env` then `.env.local`, local overriding,
37
+ * never overriding an already-exported value). Injected only for tests.
38
+ */
39
+ loadDotEnv?: () => void;
40
+ }
41
+
42
+ /**
43
+ * The CLI startup entry: load the cwd's `.env` / `.env.local` into
44
+ * `process.env` (so a global install gets the env-only secrets), THEN dispatch
45
+ * `run()` over the process argv. Returns the process exit code.
46
+ *
47
+ * This is what the `pinnace` bin calls. It keeps the `loadEnv()` side effect
48
+ * out of the pure `run()` path so `run(argv, {env})` stays hermetic.
49
+ */
50
+ export async function main(
51
+ argv: readonly string[] = process.argv.slice(2),
52
+ options: StartupOptions = {},
53
+ ): Promise<number> {
54
+ const loadDotEnv = options.loadDotEnv ?? (() => void loadEnv());
55
+ // Load the cwd's dotenv files BEFORE run() reads process.env. This only
56
+ // augments process.env; an already-exported value still wins.
57
+ loadDotEnv();
58
+ return run(argv);
59
+ }
@@ -164,7 +164,7 @@ const DEFAULT_KUBO_VERSION = 'v0.38.1';
164
164
  * the same agent. Overridable per-box via {@link ProvisionInput.pinnaceVersion}.
165
165
  * `pinnace@0.1.0` is the first published release (npm, public, OIDC provenance).
166
166
  */
167
- const DEFAULT_PINNACE_VERSION = '0.2.0';
167
+ const DEFAULT_PINNACE_VERSION = '0.3.1';
168
168
  /**
169
169
  * The pinned Node.js major the box installs via NodeSource (`setup_<this>.x`).
170
170
  * Node 22 is a current active LTS; Node 20 (the old literal) is the OLDEST LTS
@@ -432,7 +432,10 @@ write_files:
432
432
  cfg Routing.Type "auto"
433
433
  # Re-announce everything we serve; interval well under the record expiry.
434
434
  # (Kubo 0.38 uses Provide.*; the pre-0.38 keys FATAL the daemon at boot.)
435
- cfg Provide.Interval "12h"
435
+ # Provide.Interval is a DURATION value Kubo requires as JSON. Without --json,
436
+ # setting it errors (not found / maybe use --json) and, under set -e, aborts
437
+ # this whole setup script (leaving the box half-provisioned).
438
+ cfg --json Provide.Interval '"12h"'
436
439
  cfg Provide.Strategy "all"
437
440
 
438
441
  # --- Resource hygiene for a small box ---
@@ -468,6 +471,12 @@ write_files:
468
471
  User=ipfs
469
472
  Group=ipfs
470
473
  Environment=IPFS_PATH=/var/lib/ipfs/.ipfs
474
+ # Guarantee the datadir exists (owned by ipfs) BEFORE the sandboxed daemon
475
+ # starts: ReadWritePaths=/var/lib/ipfs requires the path to exist at unit
476
+ # start, so a missing datadir otherwise fails namespace setup with
477
+ # 226/NAMESPACE and crash-loops. The leading '+' runs this as root, outside
478
+ # the sandbox; it is idempotent.
479
+ ExecStartPre=+/usr/bin/install -d -o ipfs -g ipfs /var/lib/ipfs
471
480
  ExecStart=/usr/local/bin/ipfs daemon --migrate=true --enable-gc
472
481
  Restart=on-failure
473
482
  RestartSec=5