bulletin-deploy 0.7.4 → 0.7.6

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
@@ -1,23 +1,24 @@
1
1
  # bulletin-deploy
2
2
 
3
- Deploy static sites and apps to the Polkadot Triangle network with decentralized storage and human-readable `.dot` domains.
3
+ `bulletin-deploy` publishes a static web app to Bulletin and binds it to a human-readable `.dot` domain.
4
+
5
+ The main CLI is deploy-only. Pool bootstrap and other operator setup live in [`bulletin-bootstrap`](docs/bootstrap.md).
4
6
 
5
7
  ## Quick Start
6
8
 
7
9
  ```bash
8
10
  npm install -g bulletin-deploy
9
- # Build your app (e.g. npm run build)
11
+
12
+ # Build your app first, then deploy it.
10
13
  bulletin-deploy ./dist my-app00.dot
11
14
  ```
12
15
 
13
- Your site is live at `https://my-app00.dot.li`
14
-
15
- > **Stable vs release candidate.** `npm install -g bulletin-deploy` (and `@latest`) always resolves to the latest stable version — never an RC. Release candidates are published under the `rc` dist-tag and can be installed explicitly with `npm install -g bulletin-deploy@rc` (or a literal version pin like `@0.6.9-rc.0`). Only use RCs for testing.
16
+ On success, the CLI prints the CID and the `.dot` domain that now serves your app.
16
17
 
17
- ## Prerequisites
18
+ ## Installation
18
19
 
19
20
  - **Node.js 22+**
20
- - **IPFS Kubo** (for merkleizing directories not needed with `--js-merkle`)
21
+ - **IPFS Kubo** if you want the default merkleization path
21
22
 
22
23
  ```bash
23
24
  # macOS
@@ -31,7 +32,17 @@ sudo bash kubo/install.sh
31
32
  ipfs init
32
33
  ```
33
34
 
34
- > **No Kubo?** Use `--js-merkle` to skip the Kubo requirement entirely. See [Merkleization modes](#merkleization-modes) below.
35
+ If you do not want a Kubo dependency, pass `--js-merkle`.
36
+
37
+ Stable installs:
38
+
39
+ - `npm install -g bulletin-deploy`
40
+ - `npm install -g bulletin-deploy@latest`
41
+
42
+ Release candidates:
43
+
44
+ - `npm install -g bulletin-deploy@rc`
45
+ - `npm install -g bulletin-deploy@<exact-version>`
35
46
 
36
47
  ## CLI Usage
37
48
 
@@ -45,374 +56,109 @@ Examples:
45
56
  # Basic deploy
46
57
  bulletin-deploy ./dist my-app00.dot
47
58
 
48
- # Custom RPC endpoint
49
- bulletin-deploy --rpc wss://custom-bulletin.example.com ./dist my-app00.dot
59
+ # Direct signer deploy
60
+ bulletin-deploy ./dist my-app00.dot --mnemonic "..."
61
+
62
+ # Custom Bulletin RPC
63
+ bulletin-deploy ./dist my-app00.dot --rpc wss://custom-bulletin.example.com
50
64
  ```
51
65
 
52
- ### All options
66
+ ### Options
53
67
 
54
- ```
55
- Options:
56
- --rpc wss://... Bulletin RPC (or set BULLETIN_RPC env var)
57
- --mnemonic "..." DotNS owner mnemonic (or set MNEMONIC env var)
58
- --derivation-path "..." Substrate-style path applied to --mnemonic (e.g. //deploy/3).
59
- Use to select a sub-account of the given mnemonic both the
60
- Bulletin direct signer and the DotNS registration signer then
61
- run as that derived account. Useful when running parallel
62
- deploys against the same root mnemonic without nonce contention.
63
- --js-merkle Use pure-JS merkleization (no IPFS Kubo binary required)
64
- --pool-size N Number of pool accounts (default: 10)
65
- --tag "..." Free-form label attached to the deploy span as deploy.tag
66
- (see Telemetry section). Use to isolate test/benchmark/canary
67
- runs from real-user traffic in Sentry dashboards. Also readable
68
- from DEPLOY_TAG env var.
69
- --gh-pages-mirror After a successful deploy, push the CAR to the current repo's
70
- gh-pages branch as a fast-path HTTP cache for host apps. Opt-in.
71
- See "GitHub Pages mirror" below.
72
- --help Show help
73
- ```
68
+ | Flag | What it does |
69
+ |---|---|
70
+ | `--rpc wss://...` | Override the Bulletin RPC endpoint. Also readable from `BULLETIN_RPC`. |
71
+ | `--mnemonic "..."` | Use a specific mnemonic as the direct signer for Bulletin uploads and DotNS updates. Also readable from `MNEMONIC`. |
72
+ | `--derivation-path "..."` | Apply a Substrate derivation path to `--mnemonic`, for example `//deploy/3`. |
73
+ | `--pool-size N` | Change the number of derived pool accounts available for pool-mode Bulletin uploads. Default: `10`. |
74
+ | `--password "..."` | Encrypt SPA content before upload. Consumers must provide the password to decrypt it. |
75
+ | `--js-merkle` | Use pure-JS merkleization instead of the Kubo binary. |
76
+ | `--tag "..."` | Attach a free-form telemetry label. Also readable from `DEPLOY_TAG`. |
77
+ | `--gh-pages-mirror` | After a successful deploy, push the generated CAR to the current repo's `gh-pages` branch as an HTTP mirror. |
78
+ | `--version` | Print the CLI version. |
79
+ | `--help` | Show help. |
74
80
 
75
- ### GitHub Pages mirror (opt-in, experimental)
81
+ ## Concepts
76
82
 
77
- Loading a dapp via a smoldot light client is slow. As a short-term cache, `--gh-pages-mirror` pushes the CAR to the current repo's `gh-pages` branch so host apps (dot.li, desktop) can fetch it via HTTP while the chain path catches up.
83
+ - `Bulletin`: the chain that stores the app payload in chunked transaction storage.
84
+ - `.dot domain`: the DotNS name that points at the deployed content.
85
+ - `CAR`: the content-addressed archive produced from your build output before upload.
86
+ - `merkleization`: turning a directory into a content-addressed DAG and CAR file.
87
+ - `pool accounts`: derived Bulletin uploader accounts used to spread nonce and authorization load.
88
+ - `PoP`: Proof of Personhood, which some `.dot` names require before registration.
78
89
 
79
- ```bash
80
- bulletin-deploy --gh-pages-mirror ./dist my-app.dot
81
- ```
90
+ ## Domain Rules
82
91
 
83
- After the Bulletin upload + DotNS registration succeed, the CLI commits:
92
+ DotNS classifies labels on-chain and may require a specific Proof of Personhood level before registration.
84
93
 
85
- ```
86
- bulletin/my-app.dot.car # the CAR, byte-for-byte identical to what's on Bulletin
87
- bulletin/my-app.dot.json # {domain, cid, toolVersion, deployedAt, encrypted, bulletinRpc, sourceRepo, sourceCommit}
88
- ```
94
+ Typical cases:
95
+
96
+ | Domain pattern | Typical classification |
97
+ |---|---|
98
+ | Base name, for example `my-app.dot` | `ProofOfPersonhoodFull` |
99
+ | Name with trailing digits, for example `my-app00.dot` | often `NoStatus`, but not guaranteed |
89
100
 
90
- to `gh-pages` and pushes. The mirror URL is printed at the end of the deploy:
101
+ Do not rely on the string shape alone. The on-chain classifier is authoritative.
91
102
 
92
- ```
93
- Mirror: https://<owner>.github.io/<repo>/bulletin/my-app.dot.car
94
- ```
103
+ On testnets, `bulletin-deploy` self-grants PoP only when the classifier says it is needed. On mainnet, PoP cannot be self-granted.
95
104
 
96
- Requirements:
105
+ ## GitHub Pages Mirror
97
106
 
98
- - **GitHub Pages enabled** on the repo with `gh-pages` as the source branch (one-time, via repo Settings → Pages).
99
- - **Push permissions.** In CI, set `permissions: contents: write` on the workflow. Locally, your regular git credentials suffice.
100
- - **CAR ≤ 100 MB** (GitHub's single-file soft limit). Larger CARs are skipped with a log line; a Releases fallback for bigger CARs is a planned follow-up.
101
- - **Directory deploys only.** Pre-chunked `Array<Uint8Array>` / `Uint8Array` / single-file content doesn't produce a CAR and is skipped with a log line.
107
+ `--gh-pages-mirror` is an opt-in cache path for hosts that want an HTTP fetch path in addition to Bulletin.
102
108
 
103
- Limitations / follow-ups:
109
+ ```bash
110
+ bulletin-deploy ./dist my-app.dot --gh-pages-mirror
111
+ ```
104
112
 
105
- - **Discoverability is the caller's problem today.** The URL is printed at deploy time; host apps must be told which `<owner>/<repo>` to fetch from. A DotNS text record (or similar on-chain pointer) is the clean long-term answer and lives in a follow-up.
106
- - **Encrypted deploys mirror encrypted bytes.** `--password` deploys need the password to decrypt from the mirror too.
107
- - **Mirror failures are non-fatal.** The source of truth is Bulletin + DotNS; the mirror is a cache. Failures log and let the deploy succeed.
108
- - **GitHub Pages build latency.** The CAR lands on `gh-pages` immediately; Pages serves it after the build completes (~1–2 min in practice). Hosts should fall back to Bulletin while the 404 window lasts.
113
+ After a successful deploy, the CLI pushes:
109
114
 
110
- ## GitHub Actions
115
+ - `bulletin/<domain>.dot.car`
116
+ - `bulletin/<domain>.dot.json`
111
117
 
112
- 1. Copy `workflows/deploy-on-pr.yml` to your repo's `.github/workflows/` directory
113
- 2. Customize the **Build** step for your framework (Vite, Next.js, etc.)
114
- 3. Push and watch the deploy
118
+ to the current repo's `gh-pages` branch and prints the Pages URL.
115
119
 
116
- The template workflow:
117
- - Deploys on push to main and on PRs
118
- - Uses `nick-fields/retry@v3` for automatic retries on transient failures
119
- - Posts a comment on PRs with the live URL
120
- - Generates domain names as `<repo>-<branch>00.dot`
120
+ Use it when you want to validate or consume the mirror feature. The source of truth remains Bulletin plus DotNS.
121
121
 
122
122
  ## Programmatic API
123
123
 
124
- ```javascript
125
- import { deploy, DotNS } from "bulletin-deploy";
124
+ ```js
125
+ import { deploy } from "bulletin-deploy";
126
126
 
127
127
  const result = await deploy("./dist", "my-app00.dot");
128
128
  console.log(result.cid, result.domainName);
129
129
  ```
130
130
 
131
- ### JS merkleization and custom telemetry
132
-
133
- For environments without Kubo (WebContainers, serverless), use `jsMerkle`. The optional `attributes` field lets you inject telemetry context when git is unavailable:
131
+ For environments without Kubo:
134
132
 
135
- ```javascript
136
- const result = await deploy("./dist", "my-app00.dot", {
137
- jsMerkle: true,
138
- attributes: {
139
- "deploy.source": "revx",
140
- "deploy.repo": "user/project",
141
- },
142
- });
133
+ ```js
134
+ await deploy("./dist", "my-app00.dot", { jsMerkle: true });
143
135
  ```
144
136
 
145
- ## Domain Names and Proof of Personhood
146
-
147
- DotNS domain names are classified by the PopOracle contract on Asset Hub. The classification determines what level of **Proof of Personhood (PoP)** is required to register. PopOracle is the authoritative source — query `POP_RULES.classifyName(label)` on chain for any name. As a rough guide:
148
-
149
- | Domain pattern | Typical classification |
150
- |---|---|
151
- | Base name (no trailing digits) — e.g. `my-app.dot` | `ProofOfPersonhoodFull` required |
152
- | Name with trailing digits — e.g. `my-app00.dot` | Usually `NoStatus` (open to all), **but see caveat below** |
153
-
154
- **Caveat — the classifier is non-obvious.** Two names that both end in trailing digits can land in different buckets. Observed examples on Paseo: `rc069pool00` classifies as `NoStatus`, `rc069dir00` classifies as `ProofOfPersonhoodLite`. The safe approach is to let `POP_RULES.classifyName` tell you — don't assume from the string.
155
-
156
- ### Self-grant on testnets
157
-
158
- `bulletin-deploy` self-grants PoP before registration **only when the classifier says it's needed**. Specifically, when `classifyName(label)` returns something other than `NoStatus`, the tool calls `setUserPopStatus(requiredLevel)` so the signer matches the requirement. If the classifier returns `NoStatus`, no self-grant happens and the signer's current status is used as-is.
159
-
160
- On **mainnet** (Polkadot Hub, Kusama Hub), PoP cannot be self-granted (the contract enforces verification). You must have a verified account. Set `DOTNS_STATUS=none` explicitly or leave it unset.
161
-
162
- You can force a specific self-grant level on any network with `DOTNS_STATUS=full|lite|none`. See `tools/check-pop-status.mjs` to query an address's current status.
163
-
164
- If you see **"Requires Full Personhood verification"**, the deploy will fail early with an actionable error message before any gas is spent on commitment transactions.
165
-
166
137
  ## Environment Variables
167
138
 
168
139
  | Variable | Default | Description |
169
140
  |---|---|---|
170
141
  | `BULLETIN_RPC` | `wss://paseo-bulletin-rpc.polkadot.io` | Bulletin chain WebSocket RPC |
171
- | `BULLETIN_DEPLOY_TELEMETRY` | _(off for external users, on for internal)_ | `1` to opt in, `0` to force off — see **Telemetry** below |
172
- | `BULLETIN_DEPLOY_UPDATE_CHECK` | `1` (enabled) | Set to `0` to disable version check on errors |
173
- | `DOTNS_STATUS` | `full` (testnet) / `none` (mainnet) | PoP level to self-grant before registration: `none`, `lite`, or `full` |
174
- | `IPFS_CID` | _(none)_ | Skip storage, use pre-existing CID |
175
-
176
- ## How It Works
177
-
178
- ```
179
- Build output ──> Merkleize ──> CAR file ──> Chunk upload ──> DotNS
180
- ./dist (Kubo or JS) .car Bulletin Asset Hub
181
- Storage Registry
182
- ```
183
-
184
- 1. **Merkleize** your build directory to produce a content-addressed CAR file
185
- 2. **Chunk and upload** the CAR file to Bulletin's TransactionStorage (1MB chunks, 2 per batch)
186
- 3. **Store the DAG root** that links all chunks together under a single CID
187
- 4. **Register or update** your `.dot` domain on Asset Hub with the new contenthash
188
-
189
- Your site is immediately accessible at `https://your-domain.dot.li`
190
-
191
- ## Merkleization modes
192
-
193
- bulletin-deploy supports two ways to merkleize your build directory into a CAR file:
194
-
195
- | Mode | Flag | Requires | Best for |
196
- |---|---|---|---|
197
- | **Kubo** (default) | _(none)_ | IPFS Kubo binary installed | CI pipelines, local development |
198
- | **JS** | `--js-merkle` | Nothing beyond Node.js | WebContainers, serverless, environments without system binaries |
199
-
200
- Both modes produce valid IPFS UnixFS DAGs. The CIDs may differ between modes for the same input (different chunking implementations), but this is fine — each deploy sets a fresh contenthash on DotNS regardless.
201
-
202
- ### CLI
203
-
204
- ```bash
205
- # Default (Kubo)
206
- bulletin-deploy ./dist my-app00.dot
207
-
208
- # JS merkleization
209
- bulletin-deploy --js-merkle ./dist my-app00.dot
210
- ```
211
-
212
- ### Programmatic
213
-
214
- ```javascript
215
- import { deploy } from "bulletin-deploy";
216
-
217
- // Default (Kubo)
218
- await deploy("./dist", "my-app00.dot");
219
-
220
- // JS merkleization
221
- await deploy("./dist", "my-app00.dot", { jsMerkle: true });
222
- ```
223
-
224
- The JS mode uses `ipfs-unixfs-importer` (the same chunker Kubo uses internally) and `@ipld/car` for CAR serialization. It runs entirely in-memory with no temp files.
225
-
226
- ## Resilience Features
227
-
228
- ### Chunk-level retry
229
-
230
- Each batch of chunks is submitted with `Promise.allSettled`. Failed chunks are retried up to 3 times with a fresh nonce, serialized to avoid nonce conflicts. If a chunk fails all retries, the deploy aborts with a clear error.
231
-
232
- ### Account pool
233
-
234
- Instead of using a single account for all storage transactions, bulletin-deploy derives a pool of accounts from a mnemonic. Each deploy selects the account with the most remaining authorization capacity. This prevents nonce conflicts between concurrent deploys and distributes the storage authorization budget.
235
-
236
- ### Auto-authorization
237
-
238
- When a pool account's authorization drops below thresholds (50 transactions or 50MB), bulletin-deploy automatically tops it up by submitting an `authorize_account` transaction from Alice.
239
-
240
- ## Version Checking
241
-
242
- When a deploy fails, bulletin-deploy checks whether a newer version is available. This helps catch cases where the error was already fixed in a later release.
243
-
244
- Two sources are checked in parallel:
245
- - **npm registry** — reads the `minimumVersion` field from the latest published version
246
- - **GitHub kill switch** (`min-version.json` in the repo) — allows emergency deprecation without a release
247
-
248
- If either source indicates your version is below the minimum, the CLI tells you the version is unsupported and why. If you're simply outdated (but above the minimum), you get a suggestion to update.
249
-
250
- For **paritytech** contributors in an interactive terminal, the CLI offers to update and retry automatically. For everyone else (external users, CI), it prints the update command and exits.
251
-
252
- If you're already on the latest version and hit an error, the CLI offers to open a GitHub issue with collected debug info (paritytech contributors only).
253
-
254
- Set `BULLETIN_DEPLOY_UPDATE_CHECK=0` to disable version checking.
255
-
256
- ## Telemetry
257
-
258
- Sentry telemetry is **off by default for external users**. It's automatically on for deploys originating from inside Parity — either Parity CI, or local development trees whose git origin points at `paritytech/*`, `w3f/*`, or `polkadot-fellows/*`.
259
-
260
- - `BULLETIN_DEPLOY_TELEMETRY=1` — explicit opt-in (useful if you want to help us debug an issue you're seeing).
261
- - `BULLETIN_DEPLOY_TELEMETRY=0` — force off regardless of context.
262
- - When running under **Bun**, the Parity-internal memory-report diagnostic bundle is skipped (basic deploy telemetry still works). The bundle relies on Node's `v8` module, which Bun implements only partially.
263
-
264
- Detection signals (OR'd together):
265
- 1. `GITHUB_REPOSITORY` matches a known-internal org — Parity-owned CI workflow.
266
- 2. `RUNNER_NAME` starts with `parity-` — Parity self-hosted runner.
267
- 3. `git remote get-url origin` points at a known-internal org — internal local dev.
268
-
269
- What's tracked:
270
- - Deploy duration and success/failure
271
- - Storage phase timing (merkleize, chunk upload, root node)
272
- - DotNS phase timing (registration, contenthash update)
273
- - Pool account selection
274
- - Source metadata (repo, branch, PR number, CI vs local)
275
- - Tool version (`deploy.tool_version`)
276
-
277
- ### Using bulletin-deploy as a library under your own Sentry
278
-
279
- If your tool embeds bulletin-deploy as a library and already runs its own Sentry SDK (for example, Parity's `playground-cli`), you can route bulletin-deploy's deploy spans, tags, and diagnostics through your existing Sentry client rather than clobbering it with ours. Set two env vars **before** importing or invoking bulletin-deploy:
280
-
281
- ```sh
282
- BULLETIN_DEPLOY_USE_AMBIENT_SENTRY=1
283
- BULLETIN_DEPLOY_HOST_APP=<your-app-name>
284
- ```
285
-
286
- What happens:
287
-
288
- - `BULLETIN_DEPLOY_USE_AMBIENT_SENTRY=1` makes `initTelemetry()` skip its own `Sentry.init()` call. bulletin-deploy reuses the global Sentry client your app already configured. All deploy spans, breadcrumbs, `captureWarning`/`captureMessage` calls route to your DSN.
289
- - `BULLETIN_DEPLOY_HOST_APP=<name>` attaches `deploy.host_app: <name>` to every deploy span **and** as a Sentry scope tag, so downstream events in the same process carry it too. Use it to facet dashboards by host.
290
-
291
- **Requirements:**
292
-
293
- - Your app must call its own `Sentry.init()` **before** importing or spawning bulletin-deploy; otherwise there is no ambient client to reuse and telemetry is effectively off.
294
- - Your Sentry project should live in the same Sentry organisation as `bulletin-deploy` (`o4511059872841728.ingest.de.sentry.io`) if you want our cross-project dashboards to aggregate your traffic. Different org = different world; no cross-aggregation is possible.
295
- - If your consumer app is maintained by Parity, we can add its name to the `PARITY_HOST_APPS` allowlist in `src/telemetry.ts` so end-user installs of the compiled binary qualify for the same diagnostics as our internal CI. Today: `playground-cli`.
296
-
297
- **Gotchas:**
298
-
299
- - Quotas are per-project. A traffic spike in your project will eat your quota and can drop bulletin-deploy spans routing through it without any signal in our dashboards.
300
- - Issue-feed fingerprints don't dedupe across projects: the same error in your project and ours surfaces as two separate Sentry issues.
301
- - `@sentry/node` major version must be compatible with ours (currently v8.x). Skew risks runtime errors on the first span call.
302
-
303
- ### Tagging test and benchmark runs
304
-
305
- Real-user deploys and automated test/benchmark deploys share the same telemetry pipeline. Use `--tag` (or the `DEPLOY_TAG` env var) to label non-production runs so Sentry dashboards can filter them out:
306
-
307
- ```bash
308
- bulletin-deploy --tag e2e-ci-pr ./build my-app.dot
309
- DEPLOY_TAG=load-test bulletin-deploy ./build my-app.dot
310
- ```
311
-
312
- The `tag` value is attached to the deploy span as `deploy.tag` (plus propagated through every child span).
313
-
314
- Convention used in this repo:
315
- - `e2e-ci-pr` / `e2e-ci-nightly` — CI-driven E2E runs (per-PR and nightly matrices).
316
- - `e2e-local-smoke` / `e2e-local-pr` / `e2e-local-nightly` — maintainer-invoked E2E via `scripts/e2e-pass.sh`.
317
- - Untagged — real-user deploys.
318
-
319
- Sentry filter examples:
320
- - Exclude all test traffic from a dashboard: `!has:deploy.tag`
321
- - Only CI E2E: `deploy.tag:e2e-ci-*`
322
- - Only nightly runs (local or CI): `deploy.tag:e2e-ci-nightly OR deploy.tag:e2e-local-nightly`
323
-
324
- The shipped reusable workflow (`.github/workflows/deploy.yml`) exposes a `tag` input that feeds through to the CLI — pass it from caller workflows that do non-production deploys.
325
-
326
- Dashboards:
327
- - **Bulletin Deploy Health**: https://paritytech.sentry.io/dashboard/1669817/?project=4511093597405264 — overall deploy health across all consumer repos
328
- - **Deploy Failures Detail**: https://paritytech.sentry.io/dashboard/1669818/?project=4511093597405264 — error drill-down
329
- - **E2E Health (bulletin-deploy)**: https://paritytech.sentry.io/dashboard/1732713/?project=4511093597405264 — E2E suite pass rate, duration, and per-signer failure distribution (filtered by `deploy.tag:e2e-*`)
330
-
331
- ## Testing
332
-
333
- Three layers of coverage: offline unit tests for pure helpers, live-testnet end-to-end tests that exercise the shipped reusable workflow, and CI matrices that run both per-PR and nightly.
334
-
335
- ### Offline unit tests
336
-
337
- ```bash
338
- npm test
339
- ```
340
-
341
- Runs `test/test.js` + `test/pool.test.js` + `test/helpers/e2e-helpers.test.js` via `node --test`. No network. ~5 seconds. Always runs on every PR via GitHub Actions.
342
-
343
- ### Live-testnet E2E
344
-
345
- Four scenarios land on Paseo Bulletin:
346
-
347
- - **S1** — happy path on a stable label (`e2epool.dot` / `e2edirect.dot`)
348
- - **S2** — fresh registration via commit-reveal (nightly only)
349
- - **S3** — deploy to `e2eowned.dot` (owned by a different account), expects `EXIT_CODE_NO_RETRY` (78) and the "owned by a different account" error message
350
- - **S4** — deploy with `--gh-pages-mirror`, waits for GitHub Pages to serve the just-pushed manifest (CID-freshness check), then byte-compares the CAR on Pages against a pre-upload dump to confirm the mirror is an exact copy of what went to Bulletin
351
-
352
- **Prerequisites** (one-time per testnet lifetime): see [`docs/e2e-bootstrap.md`](docs/e2e-bootstrap.md). Grants Alice PoP Full, funds+maps Bob, pre-registers `e2eowned.dot` to Bob via `dotns-cli`. S4 additionally needs GitHub Pages enabled on the repo with `gh-pages` as the source branch and a token with `contents: write` (the workflow's `GITHUB_TOKEN` provides this; locally your git credentials must be able to push).
353
-
354
- **Local launchers:**
355
-
356
- ```bash
357
- npm run test:e2e:smoke # 1 scenario (S1 pool/js) ~5 min
358
- npm run test:e2e:pr # 4 scenarios (matches per-PR CI) ~20 min
359
- npm run test:e2e:nightly # 8 scenarios (matches nightly CI) ~30–45 min
360
- ```
361
-
362
- All three run through to completion even if one fails; a colored summary prints at the end with per-scenario pass/fail, timing, JUnit report paths, and a pre-filtered Sentry trace link.
363
-
364
- **Agent-friendly / quiet mode** — `E2E_QUIET=1` suppresses the live streaming output (`node:test` spec reporter + bulletin-deploy stdio + build logs) while still printing the summary and writing structured reports. Works with every mode:
365
-
366
- ```bash
367
- E2E_QUIET=1 npm run test:e2e:smoke
368
- E2E_QUIET=1 npm run test:e2e:pr
369
- E2E_QUIET=1 npm run test:e2e:nightly
370
- ```
371
-
372
- Equivalent flag form: `bash scripts/e2e-pass.sh --quiet <mode>`.
373
-
374
- **JUnit reports** — every scenario writes one to `e2e-reports/<scenario>-<signer>-<merkle>.xml` regardless of quiet mode. Consume via any JUnit-compatible tool, or just `cat` the XML — `<failure>` blocks carry the assertion message and stack.
375
-
376
- ### CI matrices (`.github/workflows/e2e.yml`)
377
-
378
- The CI workflow **calls the shipped reusable `.github/workflows/deploy.yml`** for every scenario — so each E2E job runs exactly the code path real consumers hit. Per-PR tests HEAD via `bulletin-deploy-version: "git+https://...#<sha>"` (the `prepare` npm script builds `dist/` during install). Nightly leaves the version empty so it tests the latest published release.
379
-
380
- - **Per-PR** (3 jobs): on `pull_request` and `push` to `main`. S1 pool/js + S1 direct/kubo + S3 negative. Posts a sticky comment on the PR with results and updates in place on re-runs.
381
- - **Nightly** (11 jobs): scheduled `0 3 * * *` UTC. Full S1 signer×merkle×runner cube + S2 pool/direct fresh-registration + S3. Failures auto-open a GitHub issue.
382
-
383
- Every deploy job passes `tag: e2e-ci-pr` or `tag: e2e-ci-nightly` through to the CLI, so the E2E traffic is tagged in Sentry and can be filtered out of real-user dashboards (see Telemetry → Tagging test and benchmark runs above).
384
-
385
- ### Tag convention in this repo
386
-
387
- | Trigger | `deploy.tag` |
388
- |---|---|
389
- | `npm run test:e2e:smoke` | `e2e-local-smoke` |
390
- | `npm run test:e2e:pr` | `e2e-local-pr` |
391
- | `npm run test:e2e:nightly` | `e2e-local-nightly` |
392
- | E2E workflow per-PR | `e2e-ci-pr` |
393
- | E2E workflow nightly | `e2e-ci-nightly` |
394
- | Real-user deploys | _none_ (`!has:deploy.tag`) |
142
+ | `BULLETIN_DEPLOY_TELEMETRY` | off for external users, on for internal users | `1` to opt in, `0` to force off |
143
+ | `BULLETIN_DEPLOY_UPDATE_CHECK` | `1` | Set to `0` to disable version checks on failure |
144
+ | `DOTNS_STATUS` | `full` on testnet, `none` on mainnet | PoP level to self-grant before registration: `none`, `lite`, or `full` |
145
+ | `IPFS_CID` | unset | Skip storage and reuse an existing CID |
146
+ | `DEPLOY_TAG` | unset | Telemetry label equivalent to `--tag` |
395
147
 
396
148
  ## Troubleshooting
397
149
 
398
- | Error | Solution |
150
+ | Error | What to check |
399
151
  |---|---|
400
- | `Requires Full Personhood verification` | Auto-handled on testnets (v0.5.4+). On mainnet, your account needs verified PoP status. |
401
- | `Payment` or authorization error | Pool account needs storage authorization auto-authorization should handle this |
402
- | `Stale` or dropped from best chain | Bulletin chain reorg. Automatic retry handles this. |
403
- | `IPFS CLI not installed` | Install Kubo: `brew install ipfs && ipfs init` |
404
- | `CommitmentNotFound` | DotNS timing issue during registration. Retry the deploy. |
405
- | `All pool accounts exhausted` | Auto-authorization will top up the best available account |
406
- | `fetchNonce timed out` | Bulletin RPC may be down. Check endpoint or try a different one. |
407
-
408
- ## Configuration for Different Chains
409
-
410
- By default, bulletin-deploy targets the **Paseo testnet**:
411
- - Bulletin: `wss://paseo-bulletin-rpc.polkadot.io`
412
- - Asset Hub: `wss://asset-hub-paseo.dotters.network`
413
-
414
- To point at a different chain, set the `BULLETIN_RPC` environment variable:
415
-
416
- ```bash
417
- BULLETIN_RPC=wss://your-bulletin-rpc.example.com bulletin-deploy ./dist my-app00.dot
418
- ```
152
+ | `Requires Full Personhood verification` | The chosen label needs a higher PoP level. |
153
+ | `Domain ... is owned by a different account` | The `.dot` name is already owned by the account indicated. Use that account as parameter or transfer the domain from that account to the new account you want to use |
154
+ | `Account ... is not authorized for Bulletin storage` | The uploader account is not authorized on Bulletin yet. For operator-managed pools, see [`bulletin-bootstrap`](docs/bootstrap.md). |
155
+ | `fetchNonce timed out` or connection errors | The Bulletin RPC may be unhealthy. Try another endpoint. |
156
+ | `IPFS CLI not installed` | Install Kubo or switch to `--js-merkle`. |
157
+ | Previous deploy did not exit cleanly / OOM hint | Retry with a larger Node heap, for example `NODE_OPTIONS='--max-old-space-size=8192'`. |
158
+
159
+ ## More Docs
160
+
161
+ - [Bootstrap and operator setup](docs/bootstrap.md)
162
+ - [Testing](docs/testing.md)
163
+ - [Telemetry](docs/telemetry.md)
164
+ - [E2E one-time setup](docs/e2e-bootstrap.md)
@@ -0,0 +1,48 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { DEFAULT_BULLETIN_RPC, DEFAULT_POOL_SIZE } from "../dist/deploy.js";
4
+ import { bootstrapPool } from "../dist/pool.js";
5
+ import { VERSION } from "../dist/telemetry.js";
6
+
7
+ const args = process.argv.slice(2);
8
+
9
+ const flags = {};
10
+ for (let i = 0; i < args.length; i++) {
11
+ if (args[i] === "--pool-size") { flags.poolSize = parseInt(args[++i], 10); }
12
+ else if (args[i] === "--mnemonic") { flags.mnemonic = args[++i]; }
13
+ else if (args[i] === "--rpc") { flags.rpc = args[++i]; }
14
+ else if (args[i] === "--version" || args[i] === "-V") { flags.version = true; }
15
+ else if (args[i] === "--help" || args[i] === "-h") { flags.help = true; }
16
+ else {
17
+ console.error(`Error: unknown argument ${args[i]}`);
18
+ process.exit(1);
19
+ }
20
+ }
21
+
22
+ if (flags.version) {
23
+ console.log(`bulletin-bootstrap v${VERSION}`);
24
+ process.exit(0);
25
+ }
26
+
27
+ if (flags.help) {
28
+ console.log(`bulletin-bootstrap v${VERSION}
29
+
30
+ Usage:
31
+ bulletin-bootstrap
32
+
33
+ Options:
34
+ --mnemonic "..." Pool root mnemonic (or set BULLETIN_POOL_MNEMONIC / MNEMONIC env var)
35
+ --rpc wss://... Bulletin RPC (or set BULLETIN_RPC env var)
36
+ --pool-size N Number of pool accounts to initialize (default: 10)
37
+ --version Show version
38
+ --help Show this help
39
+
40
+ Initialize pool accounts for Bulletin storage authorization.`);
41
+ process.exit(0);
42
+ }
43
+
44
+ const rpc = flags.rpc ?? process.env.BULLETIN_RPC ?? DEFAULT_BULLETIN_RPC;
45
+ const poolSize = flags.poolSize ?? parseInt(process.env.BULLETIN_POOL_SIZE ?? String(DEFAULT_POOL_SIZE), 10);
46
+ const mnemonic = flags.mnemonic ?? process.env.BULLETIN_POOL_MNEMONIC ?? process.env.MNEMONIC;
47
+
48
+ await bootstrapPool(rpc, poolSize, mnemonic);