blockyard 0.0.1 → 0.1.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 (122) hide show
  1. package/CHANGELOG.md +929 -0
  2. package/LICENSE +202 -0
  3. package/NOTICE +4 -0
  4. package/README.md +191 -4
  5. package/SECURITY.md +38 -0
  6. package/bin/blockyard.js +41 -0
  7. package/config/pool-map.json +2620 -0
  8. package/docs/API.md +1577 -0
  9. package/docs/ARCHITECTURE.md +1394 -0
  10. package/docs/AUTO-UPDATE.md +269 -0
  11. package/docs/CONFIGURATION.md +847 -0
  12. package/docs/DEFECTS.md +813 -0
  13. package/docs/EFFECTS-AGENTS.md +448 -0
  14. package/docs/GETTING-STARTED.md +205 -0
  15. package/docs/INSTALL.md +547 -0
  16. package/docs/MEASUREMENTS.md +1401 -0
  17. package/docs/RULES.md +681 -0
  18. package/docs/SECURITY-AUDIT-2026-09-14.md +177 -0
  19. package/docs/SECURITY-AUDIT.md +258 -0
  20. package/docs/SECURITY.md +212 -0
  21. package/docs/TROUBLESHOOTING.md +332 -0
  22. package/docs/USER-GUIDE.md +1262 -0
  23. package/package.json +53 -5
  24. package/public/404.html +9 -0
  25. package/public/css/app.css +2009 -0
  26. package/public/donate-qr.png +0 -0
  27. package/public/index.html +1085 -0
  28. package/public/js/about.js +112 -0
  29. package/public/js/agents.js +1141 -0
  30. package/public/js/app.js +1386 -0
  31. package/public/js/arkanoid.js +806 -0
  32. package/public/js/blockanoid.js +347 -0
  33. package/public/js/blockout.js +347 -0
  34. package/public/js/blockpack.js +428 -0
  35. package/public/js/blockscene3d.js +2830 -0
  36. package/public/js/breakout.js +224 -0
  37. package/public/js/charts.js +635 -0
  38. package/public/js/depthchart.js +315 -0
  39. package/public/js/details3d.js +4342 -0
  40. package/public/js/doom.js +31 -0
  41. package/public/js/dosaudio.js +48 -0
  42. package/public/js/dosgame.js +389 -0
  43. package/public/js/dosio.js +186 -0
  44. package/public/js/dospc.js +1353 -0
  45. package/public/js/dosworker.js +196 -0
  46. package/public/js/explorer.js +405 -0
  47. package/public/js/feepalette.js +149 -0
  48. package/public/js/fmt.js +162 -0
  49. package/public/js/goggles.js +886 -0
  50. package/public/js/kiosk.js +41 -0
  51. package/public/js/login.js +88 -0
  52. package/public/js/markets.js +395 -0
  53. package/public/js/mining.js +1416 -0
  54. package/public/js/panels.js +970 -0
  55. package/public/js/pricechart.js +189 -0
  56. package/public/js/quake.js +20 -0
  57. package/public/js/settings.js +1096 -0
  58. package/public/js/soundcard.js +459 -0
  59. package/public/js/tetris.js +226 -0
  60. package/public/js/tetrust.js +356 -0
  61. package/public/js/tetsound.js +175 -0
  62. package/public/js/theme.js +235 -0
  63. package/public/js/wolf3d.js +22 -0
  64. package/public/js/x86.js +1978 -0
  65. package/public/login.html +33 -0
  66. package/scripts/blockfile-measure.js +156 -0
  67. package/scripts/browser-check.mjs +286 -0
  68. package/scripts/check.js +173 -0
  69. package/scripts/decode-check.js +81 -0
  70. package/scripts/doc-counts.js +109 -0
  71. package/scripts/donate-qr.py +23 -0
  72. package/scripts/dos-bench.js +56 -0
  73. package/scripts/fake-node.js +534 -0
  74. package/scripts/index-bench.js +216 -0
  75. package/scripts/index-benchmark.js +117 -0
  76. package/scripts/index-build.js +40 -0
  77. package/scripts/live-render-check.mjs +89 -0
  78. package/scripts/manage-users.js +132 -0
  79. package/scripts/motion-check.mjs +138 -0
  80. package/scripts/pool-map.js +157 -0
  81. package/scripts/setup.js +432 -0
  82. package/scripts/shots.mjs +278 -0
  83. package/scripts/smoke.sh +327 -0
  84. package/scripts/tls.js +31 -0
  85. package/scripts/ui.js +174 -0
  86. package/server/auth/sessions.js +221 -0
  87. package/server/auth/users.js +243 -0
  88. package/server/chain/blockfile.js +234 -0
  89. package/server/chain/index/build.js +210 -0
  90. package/server/chain/index/heights.js +36 -0
  91. package/server/chain/index/live.js +276 -0
  92. package/server/chain/index/rows.js +145 -0
  93. package/server/chain/index/store.js +154 -0
  94. package/server/chain/index/worker.js +109 -0
  95. package/server/chain/tx.js +310 -0
  96. package/server/collect/gbt.js +229 -0
  97. package/server/collect/logparse.js +765 -0
  98. package/server/collect/logtail.js +189 -0
  99. package/server/collect/markets.js +333 -0
  100. package/server/collect/mining.js +333 -0
  101. package/server/collect/monitor.js +2545 -0
  102. package/server/collect/network.js +295 -0
  103. package/server/collect/nextblock.js +275 -0
  104. package/server/collect/sync.js +386 -0
  105. package/server/config.js +644 -0
  106. package/server/http/api.js +1319 -0
  107. package/server/http/explorer.js +418 -0
  108. package/server/http/games.js +77 -0
  109. package/server/http/server.js +420 -0
  110. package/server/http/sse.js +176 -0
  111. package/server/http/static.js +212 -0
  112. package/server/main.js +673 -0
  113. package/server/netinfo.js +253 -0
  114. package/server/rpc/allowlist.js +130 -0
  115. package/server/rpc/client.js +414 -0
  116. package/server/store/audit.js +148 -0
  117. package/server/store/history.js +220 -0
  118. package/server/store/ledger.js +290 -0
  119. package/server/store/ring.js +173 -0
  120. package/server/tls/selfsigned.js +160 -0
  121. package/server/util/fmt.js +29 -0
  122. package/systemd/blockyard.service +102 -0
@@ -0,0 +1,547 @@
1
+ # Installing BlockYard
2
+
3
+ This guide takes you from nothing to a monitor running as a service, reachable from the
4
+ machines you choose. Every setting mentioned here is described in full in
5
+ [CONFIGURATION.md](CONFIGURATION.md).
6
+
7
+ - [1. Requirements](#1-requirements)
8
+ - [2. Get the code](#2-get-the-code)
9
+ - [3. Try it without a node](#3-try-it-without-a-node)
10
+ - [4. Point it at your node](#4-point-it-at-your-node)
11
+ - [5. First run](#5-first-run)
12
+ - [6. Run it as a service](#6-run-it-as-a-service)
13
+ - [7. Decide who can reach it](#7-decide-who-can-reach-it)
14
+ - [8. Accounts (optional)](#8-accounts-optional)
15
+ - [9. HTTPS (on by default)](#9-https-on-by-default)
16
+ - [10. Behind a reverse proxy (optional)](#10-behind-a-reverse-proxy-optional)
17
+ - [11. Updating](#11-updating)
18
+ - [12. Uninstalling](#12-uninstalling)
19
+ - [Checklist](#checklist)
20
+
21
+ ## 1. Requirements
22
+
23
+ | need | notes |
24
+ |---|---|
25
+ | **Node.js 22 or newer** | `node -v` must print `v22` or later. Older runtimes fail on syntax at start-up, which looks like a bug in the app. Install from [nodejs.org](https://nodejs.org), your distribution's backports, or a version manager such as `nvm`. |
26
+ | **Bitcoin Core 25.0 or later** | [Bitcoin Core](https://github.com/bitcoin/bitcoin) with `server=1` and `txindex=1`, **on the same machine** as BlockYard, which reads the node's block files for the explorer's address index. A node on another machine is not supported. 25.0 is where `getblock` verbosity 3, which the index follower uses, arrived; 29.1 is what the macOS install was done against. `coinstatsindex=1` is optional (without it the UTXO figures are blank and the node is not asked for them). Not a pruned node: the index needs every block file. |
27
+ | **RPC credentials** | Either read access to the node's cookie file (`<datadir>/<chain>/.cookie`, the usual case on the same machine) or an RPC user and password. |
28
+ | **macOS or Linux** | There is nothing to compile, and the server calls no platform-specific API (no `child_process`, no `/proc`, no `systemctl`). Developed on Linux; a real install has been done on macOS (Core 29.1). The test suite runs in CI on Ubuntu, macOS and Windows (Node 22 and 24), but on Windows nothing more than the suite has been tried. Only the *service* instructions in section 6 are Linux-specific (they use systemd); on macOS run it in a terminal, or write a `launchd` plist. The index store opens its files per lookup, so macOS's default limit of 256 open files is enough. |
29
+ | **Disk** | About **125 GB** for the address index (124 GB measured at height 966,930, growing ~55 MB a day), on top of the node's own ~875 GB of block files, which the build reads once. A different disk from the node's is best. A few hundred MB besides for history, sessions and the audit trail (`./data` by default). |
30
+ | **Memory** | About 2.5 GB per index-build worker while the build runs (four by default); little after. |
31
+ | **A modern browser** | Any current Chrome, Edge, Firefox or Safari. The 3D views use a 2D canvas and run without WebGL; a GPU helps with the dense viewer mode. |
32
+
33
+ ### Node indexes
34
+
35
+ **`txindex=1` is required for the explorer's transaction pages.** A transaction page asks the node
36
+ for `getrawtransaction <txid> 2` with no block hash, and a node without a transaction index can
37
+ only answer that for transactions still in its mempool — so without it, looking up a confirmed
38
+ transaction by id fails even though the node is perfectly healthy. Set it in `bitcoin.conf`:
39
+
40
+ ```
41
+ txindex=1
42
+ ```
43
+
44
+ Adding it to a node that has been running without it triggers a one-off reindex, which takes a
45
+ while and is unavoidable; the node reports progress, and `getindexinfo` tells you when it is
46
+ `synced`. Everything else in the monitor — the dashboard, block space, mempool, fees, peers,
47
+ mining, the block pages — works without it.
48
+
49
+ Two further points affect the explorer:
50
+
51
+ - an **address index** — address pages (balance, received, sent, transaction history). **Bitcoin
52
+ Core does not have one, at any setting**, so there is no flag here to turn on: `getaddressbalance`
53
+ and `getaddresstxids` are insight-style extensions carried by forks such as Bitcore, and stock
54
+ Core answers `Method not found` (measured 2026-09-13 against two Core nodes). **BlockYard builds its own** from the node's block files — see
55
+ [Building the address index](#building-the-address-index) below. Without one, the address page
56
+ still confirms an address and its type (`validateaddress` needs no index) and marks balance and
57
+ history as *not indexed*. Searching by transaction id or block is unaffected: that uses
58
+ `txindex` above.
59
+ - **"spent by" links** come from `gettxspendingprevout`, which Core (24.0 and later) answers from
60
+ its **mempool** only: an output spent by an unconfirmed transaction is linked, one spent in a
61
+ block is not, and there is no index to turn on for that.
62
+
63
+ Pages that need an index the node does not have say so, rather than showing empty data.
64
+
65
+ ### Building the address index
66
+
67
+ **By default BlockYard builds it itself**, in the background, the first time it starts with an
68
+ `addressIndex` directory that holds no index. The build runs on worker threads inside the server
69
+ while every page keeps working: the Overview's "What this panel cannot tell you" box shows the
70
+ progress (phase, files done, rows so far, an ETA that settles after the first few files), the
71
+ address page says the same in place of a history, and an event — which the browser shows as a
72
+ notification — marks the start, the finish and a failure. When it finishes the follower starts
73
+ on the spot, so address pages work without a restart. Stopping BlockYard stops the build; there
74
+ is no resume, so the next start begins it again. Three keys on the node entry control it:
75
+
76
+ | key | meaning |
77
+ |---|---|
78
+ | `addressIndex` | the index directory (the installer's default is `data/index` inside the checkout) |
79
+ | `addressIndexWorkers` | how many worker threads the build uses; the installer writes the number you gave it (default 4, never more than 4 by default). Without the key the server uses half of what a dedicated build would, at most four |
80
+ | `addressIndexBuild: "manual"` | do not build automatically; the installer's **(l)ater** writes this. Run the command below yourself and restart |
81
+
82
+ **The build is paced by the node.** Its workers read the block files the node is also reading,
83
+ so before each file it looks at the monitor's own RPC telemetry: while the node's RPC is failing
84
+ or averaging above the monitor's slow threshold (`rpc.slowLatencyMs`, 5 s by default) it holds,
85
+ checking every 10 s; while merely slow it eases off. The progress line and the log say when it is
86
+ paused and when it resumed. The build's own RPC calls (cheap: block hashes for the height table)
87
+ go over a second connection so they are not queued behind the monitor's mempool and block reads.
88
+ On **spinning disks** use one worker (`addressIndexWorkers: 1`, or answer 1 to the installer):
89
+ parallel readers only seek against each other and against the node, and the build takes hours
90
+ there whatever the number.
91
+
92
+ The index is built once from the node's own `blocks/blk*.dat` and `rev*.dat` files, so the
93
+ build needs to run **on a machine that can read the node's data directory** -- the node's own
94
+ machine, which is where BlockYard runs. After that the server keeps it current over RPC.
95
+
96
+ **Expect the build to take a few hours** — about two on the installer's default of four workers on NVMe, longer on spinning disks — during which every other page works and address pages show the build's progress in place of a history.
97
+
98
+ What it costs, measured on the full chain at height 966,930 (`docs/MEASUREMENTS.md` §30):
99
+ **29 min 45 s** with 16 workers on NVMe (7.8 CPU-hours; peak 30 GB of memory, so about 2.5 GB
100
+ per worker), and **124 GB** of disk for 5.89 billion 21-byte rows, one per (address, transaction)
101
+ with the net amount, so a balance is a sum and never a node call; it grows about 55 MB a day. It
102
+ stores no transactions — `txindex` does that — which is why it is a tenth the size of
103
+ mempool.space's `electrs` (1.3 TB, hours to build). Put it on a different disk from the block
104
+ files if you can; the build reads ~880 GB once.
105
+
106
+ To build by hand (the installer's **(h)ere** runs the same build in the terminal, with the same
107
+ pacing):
108
+
109
+ ```bash
110
+ node scripts/index-build.js --out data/index --workers 4
111
+ ```
112
+
113
+ Progress goes to stderr once a second; the manifest, with every phase's timings, to stdout at
114
+ the end. Each worker holds its file pair and a row buffer, so memory scales with `--workers`
115
+ (the 16-worker build above peaked at 30 GB): on a 16-32 GB machine use `--workers 4`, which
116
+ takes roughly four times as long. The block files are found through the node's `datadir` in
117
+ `config/local.json` (`<datadir>/blocks`), so that must be set -- on macOS Core's default is
118
+ `~/Library/Application Support/Bitcoin`. Then name the directory in the node's config and restart:
119
+
120
+ ```json
121
+ {
122
+ "nodes": [
123
+ { "id": "main", "...": "...", "addressIndex": "/opt/blockyard/data/index", "addressIndexWorkers": 4 }
124
+ ]
125
+ }
126
+ ```
127
+
128
+ One index serves every node on the same chain. The server starts a follower per directory,
129
+ which polls every 30 s, fetches each new block with `getblock <hash> 3` (up to 50 blocks a poll
130
+ when catching up), and writes `live.log` and `layers/` **inside the index directory — so it must
131
+ be writable by the service user**. A restart replays the log; a reorganisation rolls the tail
132
+ back; blocks 100 deep are folded into sorted layers. The address page says when the index is
133
+ behind the node or has stopped following, and rows above the node's current tip are never shown
134
+ as history. A reorganisation deeper than the tail it holds (100 blocks) cannot be repaired in
135
+ place: the page says to rebuild: stop the server, run the same command again (or delete the
136
+ directory and let the server build it), start it. **The build empties `--out` first**, the
137
+ follower's log and layers included, so nothing of the old index survives it (tested in
138
+ `test/chain-index-live.test.js`).
139
+
140
+ What the address page shows from it: the history, the balance, received and sent (each
141
+ transaction's net for the address), and — for an address with up to 100 transactions — its
142
+ unspent outputs, checked one by one against the node's `gettxout`; a longer history gets a note
143
+ instead. Not yet: an address's mempool transactions.
144
+
145
+ Balances are checked against the node: 40 of 40 sampled addresses equal `scantxoutset` to the
146
+ satoshi (`node scripts/index-benchmark.js` runs that check and the lookup timings against your
147
+ own build).
148
+
149
+ **Outbound network access**: none, out of the box — everything talks only to your node. The
150
+ Markets and Kiosk tabs and the explorer's dollar figures need the exchange feed (HTTPS to five
151
+ exchanges' public APIs), which is off until you tick **Display settings → Markets & Price → Enable market polling**
152
+ in the browser. See [SECURITY.md](SECURITY.md#outbound-connections).
153
+
154
+ ## 2. Get the code
155
+
156
+ ```bash
157
+ git clone https://github.com/BobClawblaw/blockyard.git
158
+ cd blockyard
159
+ ```
160
+
161
+ There is **no `npm install`** — the project has no dependencies. Optionally confirm your
162
+ runtime is good:
163
+
164
+ ```bash
165
+ npm test
166
+ ```
167
+
168
+ ## 3. Try it without a node
169
+
170
+ ```bash
171
+ npm run dev
172
+ ```
173
+
174
+ This starts the monitor on <http://127.0.0.1:18088> against a built-in fake node that
175
+ simulates a node syncing, with no configuration file read. It is the quickest way to see
176
+ every page, and it is what the test suite uses. Stop it with `Ctrl-C`.
177
+
178
+ ## 4. Point it at your node
179
+
180
+ **The short way:** `npm run setup` asks for the node's data directory first and reads its
181
+ `bitcoin.conf` (chain, `rpcport`, `rpcconnect`, `rpcuser`/`rpcpassword`, `rpcauth` users, a cookie
182
+ file elsewhere, `server=`, `txindex=`, `prune=`, chain sections, `includeconf=`), so the RPC URL
183
+ and credentials arrive as defaults; checks them against the node (RPC, credentials, chain,
184
+ `txindex`, `getblock 3`, a verbose mempool read, the block files, the log -- every call timed);
185
+ asks bind address, port, index directory and workers; writes `config/local.json`; and lets
186
+ BlockYard build the address index in the background when it starts --
187
+ [GETTING-STARTED.md](GETTING-STARTED.md). What follows is the same configuration by hand, and
188
+ what each key means.
189
+
190
+ Create `config/local.json` (it is git-ignored, so your settings never end up in a commit).
191
+
192
+ **Same machine, cookie authentication** — the common case. The monitor finds the cookie
193
+ at `<datadir>/<chainHint>/.cookie`:
194
+
195
+ ```json
196
+ {
197
+ "nodes": [
198
+ {
199
+ "id": "main",
200
+ "label": "My node",
201
+ "rpcUrl": "http://127.0.0.1:8332",
202
+ "datadir": "/home/you/.bitcoin",
203
+ "chainHint": "main",
204
+ "addressIndex": "/home/you/blockyard/data/index",
205
+ "addressIndexWorkers": 4
206
+ }
207
+ ]
208
+ }
209
+ ```
210
+
211
+ **User/password authentication** -- a node that uses `rpcauth` instead of the cookie file. Keep
212
+ `datadir`: the block files are still read from it.
213
+
214
+ ```json
215
+ {
216
+ "nodes": [
217
+ {
218
+ "id": "main",
219
+ "label": "My node",
220
+ "rpcUrl": "http://127.0.0.1:8332",
221
+ "datadir": "/home/you/.bitcoin",
222
+ "rpcUser": "monitor",
223
+ "rpcPassword": "a long random password"
224
+ }
225
+ ]
226
+ }
227
+ ```
228
+
229
+ Use the RPC port your node is configured with (`rpcport` in its configuration file; Core's
230
+ mainnet default is 8332). Any value you set in `config/local.json` or in the environment
231
+ overrides the built-in defaults.
232
+
233
+ #### It runs on the node's machine
234
+
235
+ BlockYard is installed **on the machine that runs Bitcoin Core**, and nowhere else. The explorer's
236
+ address index is built from the node's own block files (`<datadir>/blocks`), the way
237
+ mempool.space's `electrs` does it ([Building the address index](#building-the-address-index)),
238
+ because Core cannot answer an address's history over RPC at any setting. A node on another
239
+ machine is **not supported**: reading one over RPC alone was tried (2026-09-13) and dropped --
240
+ real-time explorer data over RPC was a failed idea.
241
+
242
+ `rpcUser` / `rpcPassword` are still accepted, for a node that authenticates with `rpcauth` rather
243
+ than the cookie file (see [Configuration](CONFIGURATION.md#nodes)). The web UI's node-connection
244
+ form takes no password on purpose -- taking one over a web endpoint is not something to add
245
+ quietly -- so credentials go in `config/local.json`.
246
+
247
+ ##### `bitcoin.conf` settings worth having
248
+
249
+ ```conf
250
+ txindex=1 # required for the explorer's transaction pages
251
+ coinstatsindex=1 # the Chain page's UTXO figures; rebuilds from genesis, which takes hours
252
+ dbcache=4096 # or what the machine can spare: the expensive reads are disk-bound
253
+ rpcservertimeout=120 # keeps the node from closing a connection under a slow call
254
+ ```
255
+
256
+ | line | what it does for this monitor |
257
+ |---|---|
258
+ | `txindex=1` | **Required** for the explorer's transaction pages. Without it a confirmed transaction cannot be looked up by id. |
259
+ | `coinstatsindex=1` | **Optional.** The Chain page's UTXO figures come from `gettxoutsetinfo muhash`; unindexed, that call walks the whole UTXO set (41 s measured), so on a node that reports no synced `coinstatsindex` the monitor does not ask for them at all, leaves the figures blank and flags `utxo-unindexed`. The index **rebuilds from genesis** and takes hours -- until it finishes those figures stay unavailable and the rebuild competes with everything else for the disk. |
260
+ | `dbcache=4096` | Measured 2026-09-13 on one Core 31.1.0 node that shipped with 450 MB: raised to 4096 together with the RPC settings here, the slowest call went from 4.0-4.5 s to 488-565 ms and the monitor's lane stopped timing out. Which line deserved the credit was not isolated, so they are recommended together. |
261
+ | `rpcservertimeout=120` | The monitor's own ceilings are 90 s ordinary / 300 s heavy, so this only matters on a heavily loaded node. |
262
+ | `rpcthreads`, `rpcworkqueue` | **Not for us:** this monitor issues one RPC at a time, so extra node threads do not speed it up. They matter where other software (Electrs, LND) shares the same bitcoind. |
263
+ | `rest=1` | **Nothing.** This monitor makes no REST calls; it is JSON-RPC only. |
264
+
265
+ Restart the node after changing these: `bitcoin.conf` is read at start-up. `peerinfo-partial` is
266
+ normal on Core -- bytes from peers that have since disconnected remain in `getnettotals` but
267
+ leave no per-peer row, so the two do not sum. **Log parsing does not support Core** (see below);
268
+ leave the log source off, nothing in the UI depends on it.
269
+
270
+ **Several nodes** — add more entries to `nodes`; a node picker appears in the header and
271
+ every chart, table and stream is per node.
272
+
273
+ **The node's log.** The monitor works from RPC alone, and that is the supported mode.
274
+ **Log parsing does not currently support Bitcoin Core**: the parsers were written against an
275
+ experimental node with a different log grammar, and fed real Core `debug.log` lines they
276
+ extract no figures and misdate the entries (measured 2026-09-13). Leave `log.enabled` off --
277
+ it is off by default -- and ignore `logFile`. Nothing is lost on Core: `getnettotals` and per-peer
278
+ byte counts are served over RPC (verified 2026-09-13). The Node & RPC page
279
+ lists exactly which figures each source provides.
280
+
281
+ ## 5. First run
282
+
283
+ ```bash
284
+ npm run check # every configured node: RPC, credentials, txindex, getblock 3, mempool, block files, index -- every call timed
285
+ npm start
286
+ ```
287
+
288
+ Watch the start-up lines. You should see `listening on https://127.0.0.1:21000` (the first start
289
+ makes the monitor its own self-signed certificate under `data/tls/`; the browser warns once per
290
+ address and remembers it), a line per node, and — because accounts are on by default — `created
291
+ the first admin account (admin)` with a generated password **shown once** (set
292
+ `BLOCKYARD_ADMIN_PASSWORD` before the first start to choose it; `blockyard user` changes it
293
+ later). If the node entry names an `addressIndex` directory with no index in it, `address index:
294
+ building … with N workers -- the Overview shows the progress` follows, and the build runs on in
295
+ the background (see [Building the address index](#building-the-address-index)). Then open
296
+ <https://127.0.0.1:21000> and sign in.
297
+
298
+ Check it from the shell (`-k`, because the certificate is self-signed):
299
+
300
+ ```bash
301
+ curl -sk https://127.0.0.1:21000/api/health
302
+ ```
303
+
304
+ If a node shows as offline, see [TROUBLESHOOTING.md](TROUBLESHOOTING.md#a-node-shows-offline).
305
+
306
+ ## 6. Run it as a service
307
+
308
+ > **macOS:** this section is Linux/systemd. On a Mac, either leave `npm start` running in a
309
+ > terminal, or wrap it in a `launchd` plist -- there is no other platform-specific step, and the
310
+ > configuration in section 4 is identical.
311
+
312
+ 1. **Create an account for it** that can read the node's cookie and its `blocks/` directory
313
+ (the index is built from the block files), and that can write the index directory. Usually
314
+ that means adding it to the node's group:
315
+
316
+ ```bash
317
+ sudo useradd --system --home /opt/blockyard --shell /usr/sbin/nologin blockyard
318
+ sudo usermod -aG <node-group> blockyard
319
+ ```
320
+
321
+ 2. **Put the code somewhere stable**, for example `/opt/blockyard`, owned by that account:
322
+
323
+ ```bash
324
+ sudo git clone https://github.com/BobClawblaw/blockyard.git /opt/blockyard
325
+ sudo cp config/local.json /opt/blockyard/config/ # the file from step 4
326
+ sudo chown -R blockyard:blockyard /opt/blockyard
327
+ ```
328
+
329
+ 3. **Install the unit** shipped in `systemd/blockyard.service` and edit it:
330
+
331
+ ```bash
332
+ sudo cp /opt/blockyard/systemd/blockyard.service /etc/systemd/system/
333
+ sudo systemctl edit --full blockyard
334
+ ```
335
+
336
+ Set `User=` / `Group=` to the account from step 1, `WorkingDirectory=` to where the code
337
+ lives, and replace the `Environment=` lines that name paths with your own (or delete
338
+ them and keep everything in `config/local.json`). Point `ExecStart` at an absolute Node
339
+ 22 binary — `/usr/bin/env node` can resolve to an older system Node under systemd:
340
+
341
+ ```ini
342
+ ExecStart=/usr/local/bin/node server/main.js
343
+ ```
344
+
345
+ 4. **Start it:**
346
+
347
+ ```bash
348
+ sudo systemctl daemon-reload
349
+ sudo systemctl enable --now blockyard
350
+ journalctl -u blockyard -f
351
+ ```
352
+
353
+ The unit restarts the monitor if it ever exits and gives it time to save its history on
354
+ shutdown. Stop and start with `systemctl stop|start blockyard`.
355
+
356
+ ## 7. Decide who can reach it
357
+
358
+ Where the monitor listens is a security decision. Set `server.host` in `config/local.json`
359
+ (a string or a list) or `BLOCKYARD_BIND`:
360
+
361
+ | bind | who can connect | typical use |
362
+ |---|---|---|
363
+ | `"127.0.0.1"` (the default) | this machine only | reach it with an SSH tunnel: `ssh -L 21000:127.0.0.1:21000 you@host` |
364
+ | `"192.0.2.10"` (a LAN address) | anything that can route to that address | a home or office LAN |
365
+ | `["192.0.2.10", "198.51.100.7"]` | exactly those addresses | LAN plus a VPN such as Tailscale or WireGuard |
366
+ | `"0.0.0.0"` | every interface | behind a firewall you control |
367
+
368
+ Notes:
369
+
370
+ - Binding a single LAN address means `127.0.0.1` on the host itself stops answering — test
371
+ with the address you bound.
372
+ - An address the machine does not have at start-up is skipped with a warning; start-up fails
373
+ only if none of the listed addresses exist.
374
+ - A bind chooses a destination address, not an incoming interface. If "LAN only" must hold
375
+ against containers or tunnels on the same host, add a firewall rule, for example:
376
+
377
+ ```bash
378
+ sudo ufw allow from 192.0.2.0/24 to any port 21000 proto tcp
379
+ ```
380
+
381
+ - `server.allowCidrs` (or `BLOCKYARD_ALLOW_CIDRS=192.0.2.0/24,2001:db8::/32`) makes the
382
+ monitor itself refuse clients outside those networks, as a second line of defence.
383
+
384
+ ## 8. Accounts (on by default)
385
+
386
+ Sign-in is required out of the box. The first start with an empty data directory creates an
387
+ `admin` account and prints its password **once** in the log (under systemd: `journalctl -u
388
+ blockyard`). Set your own instead with `BLOCKYARD_ADMIN_PASSWORD` for that first start.
389
+
390
+ To open the monitor to readers with no account, set `"auth": { "enabled": false }` or
391
+ `BLOCKYARD_AUTH=0` and restart: anyone who can reach the port then reads it as a `viewer` —
392
+ charts, the explorer, the event stream, the read-only RPC console — while user
393
+ administration, the audit trail and every node write stay closed. The boot log names the
394
+ addresses that leaves readable.
395
+
396
+ Manage accounts from the Admin page, or from the shell — for example, to reset the admin
397
+ password:
398
+
399
+ ```bash
400
+ node scripts/manage-users.js passwd admin
401
+ ```
402
+
403
+ Roles: `viewer` reads; `operator` may also run node actions that you have enabled; `admin`
404
+ also manages users and reads the audit trail. Node actions are off unless you enable them
405
+ explicitly — see [SECURITY.md](SECURITY.md#node-writes).
406
+
407
+ ## 9. HTTPS (on by default)
408
+
409
+ Every listener serves HTTPS out of the box. With no certificate of your own named, the first
410
+ start makes one: a self-signed certificate and key under `<data>/tls/` (`data/tls/cert.pem` and
411
+ `key.pem`, the key readable by the service account only), naming the addresses the monitor is
412
+ reached on — the bound hosts, this machine's addresses and hostname, `localhost`. It is remade
413
+ by itself when it nears expiry (825 days) or stops naming a bound address. Browsers warn once
414
+ per address about a self-signed certificate and then remember it; the start-up log prints its
415
+ fingerprint so you can compare.
416
+
417
+ To add a name or address (say, a DNS name you gave the machine) or start over:
418
+
419
+ ```bash
420
+ blockyard tls --san monitor.lan.example # or: node scripts/tls.js --san ...
421
+ blockyard tls --force # a fresh key and certificate
422
+ blockyard tls --print > blockyard.crt # the certificate, for another machine's trust store
423
+ ```
424
+
425
+ To use a certificate of your own instead, name it and every listener serves that:
426
+
427
+ ```bash
428
+ BLOCKYARD_TLS_CERT=/etc/blockyard/cert.pem BLOCKYARD_TLS_KEY=/etc/blockyard/key.pem npm start
429
+ ```
430
+
431
+ or in `config/local.json`: `"server": { "tls": { "cert": "...", "key": "..." } }`. To serve plain
432
+ HTTP — behind a reverse proxy that terminates TLS (section 10) — set `BLOCKYARD_TLS=0` or
433
+ `"server": { "tls": { "enabled": false } }`.
434
+
435
+ If you would rather make the certificate with openssl yourself:
436
+
437
+ ```bash
438
+ sudo mkdir -p /etc/blockyard
439
+ sudo openssl req -x509 -newkey rsa:3072 -nodes -days 825 \
440
+ -keyout /etc/blockyard/key.pem -out /etc/blockyard/cert.pem \
441
+ -subj "/CN=blockyard" -addext "subjectAltName=IP:192.0.2.10,DNS:blockyard.lan.example"
442
+ sudo chown blockyard:blockyard /etc/blockyard/*.pem && sudo chmod 600 /etc/blockyard/key.pem
443
+ ```
444
+
445
+ With TLS on, the session cookie is marked `Secure` and a short HSTS header is sent. A
446
+ half-configured pair (only a cert, or only a key) and an expired certificate stop the start-up
447
+ rather than silently serving plain HTTP.
448
+
449
+ ## 10. Behind a reverse proxy (optional)
450
+
451
+ If you already run nginx, Caddy or similar, bind the monitor to `127.0.0.1`, set
452
+ `BLOCKYARD_TLS=0` so it speaks plain HTTP to the proxy, and let the proxy terminate TLS. The live stream (`/api/stream`) is Server-Sent Events, so the proxy must not
453
+ buffer it. An nginx example:
454
+
455
+ ```nginx
456
+ server {
457
+ listen 443 ssl;
458
+ server_name monitor.example.org;
459
+ # ssl_certificate / ssl_certificate_key ...
460
+
461
+ location / {
462
+ proxy_pass http://127.0.0.1:21000;
463
+ proxy_set_header Host $host;
464
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
465
+ proxy_set_header X-Forwarded-Proto $scheme;
466
+ }
467
+ location /api/stream {
468
+ proxy_pass http://127.0.0.1:21000;
469
+ proxy_http_version 1.1;
470
+ proxy_set_header Connection "";
471
+ proxy_buffering off;
472
+ proxy_cache off;
473
+ proxy_read_timeout 1h;
474
+ }
475
+ }
476
+ ```
477
+
478
+ Then set `"server": { "trustProxy": true }` so the monitor uses the forwarded client
479
+ address for rate limits and the CIDR gate, and `BLOCKYARD_SECURE_COOKIE=1` so the session
480
+ cookie is marked `Secure` behind the proxy's TLS.
481
+
482
+ ## 11. Updating
483
+
484
+ ```bash
485
+ cd /opt/blockyard
486
+ sudo -u blockyard git pull
487
+ sudo systemctl restart blockyard
488
+ ```
489
+
490
+ Your `config/local.json` and `data/` directory are untouched by updates. Read
491
+ [CHANGELOG.md](../CHANGELOG.md) for anything that needs your attention. The browser picks
492
+ up new front-end files on the next page load; the header shows a notice when the page you
493
+ have open is older than the server.
494
+
495
+ **Updating from 0.0.9.** The defaults hardened in 0.1.0, and a `config/local.json` written by
496
+ 0.0.9's installer does not name them, so the first start after the update behaves like a fresh
497
+ install in three ways:
498
+
499
+ - **HTTPS.** The monitor makes itself a self-signed certificate under `data/tls/` and serves
500
+ HTTPS on the same port; `http://…:21000` stops answering. Open `https://`, accept the
501
+ certificate once. Behind your own reverse proxy, set `BLOCKYARD_TLS=0` (§10).
502
+ - **Sign-in.** Accounts are on. The first start creates the `admin` account and prints its
503
+ password **once** in the log (`journalctl -u blockyard` under systemd); set
504
+ `BLOCKYARD_ADMIN_PASSWORD` before that start to choose it. To keep the monitor open as before,
505
+ put `"auth": { "enabled": false }` in `config/local.json` or start with `BLOCKYARD_AUTH=0`.
506
+ - **This machine only.** With no `server.host` in the config the bind is `127.0.0.1`. A
507
+ 0.0.9 config written by the installer names the host it chose, so a LAN bind stays; if yours
508
+ does not, add `BLOCKYARD_BIND` or `server.hosts` (§7).
509
+ - **Market polling is off** until someone ticks **Display settings → Markets & Price → Enable
510
+ market polling** — once, for every screen.
511
+
512
+ ## 12. Uninstalling
513
+
514
+ BlockYard touches nothing outside its own directories: it reads `bitcoin.conf` and never writes
515
+ it, and it leaves the node as it found it. Removing it is deleting those directories.
516
+
517
+ **Installed from npm** (`npm install -g blockyard`): stop it, then
518
+
519
+ ```bash
520
+ npm uninstall -g blockyard
521
+ rm -rf ~/.blockyard # local.json, data/ (history, accounts, the audit trail) and data/index
522
+ ```
523
+
524
+ **A checkout** run with `npm start`: stop it and delete the checkout; `config/` and `data/`,
525
+ the index included, live inside it. If you pointed the index elsewhere at setup time
526
+ (`addressIndex` in `config/local.json`), delete that directory too.
527
+
528
+ **The systemd service** of section 6:
529
+
530
+ ```bash
531
+ sudo systemctl disable --now blockyard
532
+ sudo rm /etc/systemd/system/blockyard.service && sudo systemctl daemon-reload
533
+ sudo rm -rf /opt/blockyard # includes data/: history, accounts and the audit trail
534
+ sudo userdel blockyard
535
+ ```
536
+
537
+ ## Checklist
538
+
539
+ - [ ] `node -v` prints v22 or later for the account the service runs as
540
+ - [ ] `config/local.json` names your node's RPC URL and a readable cookie (or user/password)
541
+ - [ ] `npm run check` passes: Core 25.0+, `txindex` synced, the block files readable, no pruning
542
+ - [ ] the index directory has ~125 GB free and is writable by the service account
543
+ - [ ] the start-up log shows the addresses you intended, and no node offline
544
+ - [ ] you have decided who can reach the port: it binds `127.0.0.1` until you say otherwise (bind, firewall, `allowCidrs`)
545
+ - [ ] accounts stay on (the default) if the port is reachable by people who should not see your node; you have the admin password from the first start
546
+ - [ ] HTTPS is on by default with the monitor's own certificate; a certificate of your own, or a proxy / tunnel in front, if you want no browser warning
547
+ - [ ] **Enable market polling** ticked in Display settings if you want the Markets and Kiosk tabs (off by default); `BLOCKYARD_MARKETS=0` on machines that must make no outbound connections at all