blockyard 0.0.1 → 0.0.9

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 (107) hide show
  1. package/CHANGELOG.md +679 -0
  2. package/LICENSE +202 -0
  3. package/NOTICE +4 -0
  4. package/README.md +172 -4
  5. package/SECURITY.md +38 -0
  6. package/bin/blockyard.js +40 -0
  7. package/config/pool-map.json +2620 -0
  8. package/docs/API.md +1575 -0
  9. package/docs/ARCHITECTURE.md +1307 -0
  10. package/docs/AUTO-UPDATE.md +269 -0
  11. package/docs/CONFIGURATION.md +840 -0
  12. package/docs/DEFECTS.md +813 -0
  13. package/docs/EFFECTS-AGENTS.md +448 -0
  14. package/docs/GETTING-STARTED.md +202 -0
  15. package/docs/INSTALL.md +490 -0
  16. package/docs/MEASUREMENTS.md +1254 -0
  17. package/docs/PRIVATE-LEADERBOARD.md +230 -0
  18. package/docs/RULES.md +681 -0
  19. package/docs/SECURITY-AUDIT-2026-09-14.md +177 -0
  20. package/docs/SECURITY-AUDIT.md +258 -0
  21. package/docs/SECURITY.md +195 -0
  22. package/docs/STATE-2026-09-09.md +200 -0
  23. package/docs/TROUBLESHOOTING.md +298 -0
  24. package/docs/USER-GUIDE.md +1022 -0
  25. package/package.json +53 -5
  26. package/public/404.html +9 -0
  27. package/public/css/app.css +1785 -0
  28. package/public/index.html +893 -0
  29. package/public/js/about.js +112 -0
  30. package/public/js/agents.js +964 -0
  31. package/public/js/app.js +1312 -0
  32. package/public/js/arkanoid.js +806 -0
  33. package/public/js/blockanoid.js +347 -0
  34. package/public/js/blockout.js +347 -0
  35. package/public/js/blockpack.js +428 -0
  36. package/public/js/blockscene3d.js +2678 -0
  37. package/public/js/breakout.js +224 -0
  38. package/public/js/charts.js +635 -0
  39. package/public/js/depthchart.js +311 -0
  40. package/public/js/details3d.js +2957 -0
  41. package/public/js/explorer.js +405 -0
  42. package/public/js/feepalette.js +149 -0
  43. package/public/js/fmt.js +162 -0
  44. package/public/js/goggles.js +886 -0
  45. package/public/js/kiosk.js +41 -0
  46. package/public/js/login.js +83 -0
  47. package/public/js/markets.js +357 -0
  48. package/public/js/mining.js +1138 -0
  49. package/public/js/panels.js +966 -0
  50. package/public/js/pricechart.js +188 -0
  51. package/public/js/settings.js +1014 -0
  52. package/public/js/tetris.js +226 -0
  53. package/public/js/tetrust.js +356 -0
  54. package/public/js/tetsound.js +175 -0
  55. package/public/login.html +33 -0
  56. package/scripts/blockfile-measure.js +156 -0
  57. package/scripts/browser-check.mjs +286 -0
  58. package/scripts/check.js +173 -0
  59. package/scripts/decode-check.js +81 -0
  60. package/scripts/doc-counts.js +109 -0
  61. package/scripts/donate-qr.py +20 -0
  62. package/scripts/fake-node.js +534 -0
  63. package/scripts/index-bench.js +216 -0
  64. package/scripts/index-benchmark.js +117 -0
  65. package/scripts/index-build.js +40 -0
  66. package/scripts/live-render-check.mjs +89 -0
  67. package/scripts/manage-users.js +132 -0
  68. package/scripts/motion-check.mjs +138 -0
  69. package/scripts/pool-map.js +157 -0
  70. package/scripts/setup.js +410 -0
  71. package/scripts/shots.mjs +272 -0
  72. package/scripts/smoke.sh +327 -0
  73. package/scripts/ui.js +174 -0
  74. package/server/auth/sessions.js +221 -0
  75. package/server/auth/users.js +243 -0
  76. package/server/chain/blockfile.js +234 -0
  77. package/server/chain/index/build.js +193 -0
  78. package/server/chain/index/heights.js +36 -0
  79. package/server/chain/index/live.js +276 -0
  80. package/server/chain/index/rows.js +145 -0
  81. package/server/chain/index/store.js +154 -0
  82. package/server/chain/index/worker.js +109 -0
  83. package/server/chain/tx.js +310 -0
  84. package/server/collect/gbt.js +229 -0
  85. package/server/collect/logparse.js +765 -0
  86. package/server/collect/logtail.js +189 -0
  87. package/server/collect/markets.js +333 -0
  88. package/server/collect/mining.js +333 -0
  89. package/server/collect/monitor.js +2516 -0
  90. package/server/collect/nextblock.js +275 -0
  91. package/server/collect/sync.js +386 -0
  92. package/server/config.js +620 -0
  93. package/server/http/api.js +1275 -0
  94. package/server/http/explorer.js +418 -0
  95. package/server/http/server.js +412 -0
  96. package/server/http/sse.js +176 -0
  97. package/server/http/static.js +212 -0
  98. package/server/main.js +628 -0
  99. package/server/netinfo.js +253 -0
  100. package/server/rpc/allowlist.js +130 -0
  101. package/server/rpc/client.js +414 -0
  102. package/server/store/audit.js +148 -0
  103. package/server/store/history.js +220 -0
  104. package/server/store/ledger.js +290 -0
  105. package/server/store/ring.js +173 -0
  106. package/server/util/fmt.js +29 -0
  107. package/systemd/blockyard.service +100 -0
@@ -0,0 +1,490 @@
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 (optional)](#9-https-optional)
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** is needed only for the Markets and Kiosk tabs and for the
150
+ explorer's dollar figures (HTTPS to five exchanges' public APIs). Everything else talks only
151
+ to your node. See [SECURITY.md](SECURITY.md#outbound-connections).
152
+
153
+ ## 2. Get the code
154
+
155
+ ```bash
156
+ git clone https://github.com/BobClawblaw/blockyard.git
157
+ cd blockyard
158
+ ```
159
+
160
+ There is **no `npm install`** — the project has no dependencies. Optionally confirm your
161
+ runtime is good:
162
+
163
+ ```bash
164
+ npm test
165
+ ```
166
+
167
+ ## 3. Try it without a node
168
+
169
+ ```bash
170
+ npm run dev
171
+ ```
172
+
173
+ This starts the monitor on <http://127.0.0.1:18088> against a built-in fake node that
174
+ simulates a node syncing, with no configuration file read. It is the quickest way to see
175
+ every page, and it is what the test suite uses. Stop it with `Ctrl-C`.
176
+
177
+ ## 4. Point it at your node
178
+
179
+ **The short way:** `npm run setup` asks for the node's data directory first and reads its
180
+ `bitcoin.conf` (chain, `rpcport`, `rpcconnect`, `rpcuser`/`rpcpassword`, `rpcauth` users, a cookie
181
+ file elsewhere, `server=`, `txindex=`, `prune=`, chain sections, `includeconf=`), so the RPC URL
182
+ and credentials arrive as defaults; checks them against the node (RPC, credentials, chain,
183
+ `txindex`, `getblock 3`, a verbose mempool read, the block files, the log -- every call timed);
184
+ asks bind address, port, index directory and workers; writes `config/local.json`; and lets
185
+ BlockYard build the address index in the background when it starts --
186
+ [GETTING-STARTED.md](GETTING-STARTED.md). What follows is the same configuration by hand, and
187
+ what each key means.
188
+
189
+ Create `config/local.json` (it is git-ignored, so your settings never end up in a commit).
190
+
191
+ **Same machine, cookie authentication** — the common case. The monitor finds the cookie
192
+ at `<datadir>/<chainHint>/.cookie`:
193
+
194
+ ```json
195
+ {
196
+ "nodes": [
197
+ {
198
+ "id": "main",
199
+ "label": "My node",
200
+ "rpcUrl": "http://127.0.0.1:8332",
201
+ "datadir": "/home/you/.bitcoin",
202
+ "chainHint": "main",
203
+ "addressIndex": "/home/you/blockyard/data/index",
204
+ "addressIndexWorkers": 4
205
+ }
206
+ ]
207
+ }
208
+ ```
209
+
210
+ **User/password authentication** -- a node that uses `rpcauth` instead of the cookie file. Keep
211
+ `datadir`: the block files are still read from it.
212
+
213
+ ```json
214
+ {
215
+ "nodes": [
216
+ {
217
+ "id": "main",
218
+ "label": "My node",
219
+ "rpcUrl": "http://127.0.0.1:8332",
220
+ "datadir": "/home/you/.bitcoin",
221
+ "rpcUser": "monitor",
222
+ "rpcPassword": "a long random password"
223
+ }
224
+ ]
225
+ }
226
+ ```
227
+
228
+ Use the RPC port your node is configured with (`rpcport` in its configuration file; Core's
229
+ mainnet default is 8332). Any value you set in `config/local.json` or in the environment
230
+ overrides the built-in defaults.
231
+
232
+ #### It runs on the node's machine
233
+
234
+ BlockYard is installed **on the machine that runs Bitcoin Core**, and nowhere else. The explorer's
235
+ address index is built from the node's own block files (`<datadir>/blocks`), the way
236
+ mempool.space's `electrs` does it ([Building the address index](#building-the-address-index)),
237
+ because Core cannot answer an address's history over RPC at any setting. A node on another
238
+ machine is **not supported**: reading one over RPC alone was tried (2026-09-13) and dropped --
239
+ real-time explorer data over RPC was a failed idea.
240
+
241
+ `rpcUser` / `rpcPassword` are still accepted, for a node that authenticates with `rpcauth` rather
242
+ than the cookie file (see [Configuration](CONFIGURATION.md#nodes)). The web UI's node-connection
243
+ form takes no password on purpose -- taking one over an endpoint that is open by default is not
244
+ something to add quietly -- so credentials go in `config/local.json`.
245
+
246
+ ##### `bitcoin.conf` settings worth having
247
+
248
+ ```conf
249
+ txindex=1 # required for the explorer's transaction pages
250
+ coinstatsindex=1 # the Chain page's UTXO figures; rebuilds from genesis, which takes hours
251
+ dbcache=4096 # or what the machine can spare: the expensive reads are disk-bound
252
+ rpcservertimeout=120 # keeps the node from closing a connection under a slow call
253
+ ```
254
+
255
+ | line | what it does for this monitor |
256
+ |---|---|
257
+ | `txindex=1` | **Required** for the explorer's transaction pages. Without it a confirmed transaction cannot be looked up by id. |
258
+ | `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. |
259
+ | `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. |
260
+ | `rpcservertimeout=120` | The monitor's own ceilings are 90 s ordinary / 300 s heavy, so this only matters on a heavily loaded node. |
261
+ | `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. |
262
+ | `rest=1` | **Nothing.** This monitor makes no REST calls; it is JSON-RPC only. |
263
+
264
+ Restart the node after changing these: `bitcoin.conf` is read at start-up. `peerinfo-partial` is
265
+ normal on Core -- bytes from peers that have since disconnected remain in `getnettotals` but
266
+ leave no per-peer row, so the two do not sum. **Log parsing does not support Core** (see below);
267
+ leave the log source off, nothing in the UI depends on it.
268
+
269
+ **Several nodes** — add more entries to `nodes`; a node picker appears in the header and
270
+ every chart, table and stream is per node.
271
+
272
+ **The node's log.** The monitor works from RPC alone, and that is the supported mode.
273
+ **Log parsing does not currently support Bitcoin Core**: the parsers were written against an
274
+ experimental node with a different log grammar, and fed real Core `debug.log` lines they
275
+ extract no figures and misdate the entries (measured 2026-09-13). Leave `log.enabled` off --
276
+ it is off by default -- and ignore `logFile`. Nothing is lost on Core: `getnettotals` and per-peer
277
+ byte counts are served over RPC (verified 2026-09-13). The Node & RPC page
278
+ lists exactly which figures each source provides.
279
+
280
+ ## 5. First run
281
+
282
+ ```bash
283
+ npm run check # every configured node: RPC, credentials, txindex, getblock 3, mempool, block files, index -- every call timed
284
+ npm start
285
+ ```
286
+
287
+ Watch the start-up lines. You should see the addresses it listens on, a line per node,
288
+ and — because accounts are off by default — a warning that names who can read the monitor. If
289
+ the node entry names an `addressIndex` directory with no index in it, `address index: building
290
+ … with N workers -- the Overview shows the progress` follows, and the build runs on in the
291
+ background (see [Building the address index](#building-the-address-index)). Then open
292
+ <http://127.0.0.1:21000>.
293
+
294
+ Check it from the shell:
295
+
296
+ ```bash
297
+ curl -s http://127.0.0.1:21000/api/health
298
+ ```
299
+
300
+ If a node shows as offline, see [TROUBLESHOOTING.md](TROUBLESHOOTING.md#a-node-shows-offline).
301
+
302
+ ## 6. Run it as a service
303
+
304
+ > **macOS:** this section is Linux/systemd. On a Mac, either leave `npm start` running in a
305
+ > terminal, or wrap it in a `launchd` plist -- there is no other platform-specific step, and the
306
+ > configuration in section 4 is identical.
307
+
308
+ 1. **Create an account for it** that can read the node's cookie and its `blocks/` directory
309
+ (the index is built from the block files), and that can write the index directory. Usually
310
+ that means adding it to the node's group:
311
+
312
+ ```bash
313
+ sudo useradd --system --home /opt/blockyard --shell /usr/sbin/nologin blockyard
314
+ sudo usermod -aG <node-group> blockyard
315
+ ```
316
+
317
+ 2. **Put the code somewhere stable**, for example `/opt/blockyard`, owned by that account:
318
+
319
+ ```bash
320
+ sudo git clone https://github.com/BobClawblaw/blockyard.git /opt/blockyard
321
+ sudo cp config/local.json /opt/blockyard/config/ # the file from step 4
322
+ sudo chown -R blockyard:blockyard /opt/blockyard
323
+ ```
324
+
325
+ 3. **Install the unit** shipped in `systemd/blockyard.service` and edit it:
326
+
327
+ ```bash
328
+ sudo cp /opt/blockyard/systemd/blockyard.service /etc/systemd/system/
329
+ sudo systemctl edit --full blockyard
330
+ ```
331
+
332
+ Set `User=` / `Group=` to the account from step 1, `WorkingDirectory=` to where the code
333
+ lives, and replace the `Environment=` lines that name paths with your own (or delete
334
+ them and keep everything in `config/local.json`). Point `ExecStart` at an absolute Node
335
+ 22 binary — `/usr/bin/env node` can resolve to an older system Node under systemd:
336
+
337
+ ```ini
338
+ ExecStart=/usr/local/bin/node server/main.js
339
+ ```
340
+
341
+ 4. **Start it:**
342
+
343
+ ```bash
344
+ sudo systemctl daemon-reload
345
+ sudo systemctl enable --now blockyard
346
+ journalctl -u blockyard -f
347
+ ```
348
+
349
+ The unit restarts the monitor if it ever exits and gives it time to save its history on
350
+ shutdown. Stop and start with `systemctl stop|start blockyard`.
351
+
352
+ ## 7. Decide who can reach it
353
+
354
+ Where the monitor listens is a security decision. Set `server.host` in `config/local.json`
355
+ (a string or a list) or `BLOCKYARD_BIND`:
356
+
357
+ | bind | who can connect | typical use |
358
+ |---|---|---|
359
+ | `"127.0.0.1"` | this machine only | reach it with an SSH tunnel: `ssh -L 21000:127.0.0.1:21000 you@host` |
360
+ | `"192.0.2.10"` (a LAN address) | anything that can route to that address | a home or office LAN |
361
+ | `["192.0.2.10", "198.51.100.7"]` | exactly those addresses | LAN plus a VPN such as Tailscale or WireGuard |
362
+ | `"0.0.0.0"` (the default) | every interface | behind a firewall you control |
363
+
364
+ Notes:
365
+
366
+ - Binding a single LAN address means `127.0.0.1` on the host itself stops answering — test
367
+ with the address you bound.
368
+ - An address the machine does not have at start-up is skipped with a warning; start-up fails
369
+ only if none of the listed addresses exist.
370
+ - A bind chooses a destination address, not an incoming interface. If "LAN only" must hold
371
+ against containers or tunnels on the same host, add a firewall rule, for example:
372
+
373
+ ```bash
374
+ sudo ufw allow from 192.0.2.0/24 to any port 21000 proto tcp
375
+ ```
376
+
377
+ - `server.allowCidrs` (or `BLOCKYARD_ALLOW_CIDRS=192.0.2.0/24,2001:db8::/32`) makes the
378
+ monitor itself refuse clients outside those networks, as a second line of defence.
379
+
380
+ ## 8. Accounts (optional)
381
+
382
+ By default anyone who can reach the port reads the monitor as a `viewer` — charts, the
383
+ explorer, the event stream, the read-only RPC console. User administration, the audit trail
384
+ and every node write stay closed.
385
+
386
+ To require sign-in, set `"auth": { "enabled": true }` or `BLOCKYARD_AUTH=1` and restart. The
387
+ first start with an empty data directory creates an `admin` account and prints its password
388
+ **once** in the log. Set your own instead with `BLOCKYARD_ADMIN_PASSWORD` for that first start.
389
+
390
+ Manage accounts from the Admin page, or from the shell — for example, to reset the admin
391
+ password:
392
+
393
+ ```bash
394
+ node scripts/manage-users.js passwd admin
395
+ ```
396
+
397
+ Roles: `viewer` reads; `operator` may also run node actions that you have enabled; `admin`
398
+ also manages users and reads the audit trail. Node actions are off unless you enable them
399
+ explicitly — see [SECURITY.md](SECURITY.md#node-writes).
400
+
401
+ ## 9. HTTPS (optional)
402
+
403
+ Name a certificate and key and every listener serves HTTPS:
404
+
405
+ ```bash
406
+ BLOCKYARD_TLS_CERT=/etc/blockyard/cert.pem BLOCKYARD_TLS_KEY=/etc/blockyard/key.pem npm start
407
+ ```
408
+
409
+ or in `config/local.json`: `"server": { "tls": { "cert": "...", "key": "..." } }`.
410
+
411
+ A self-signed certificate is fine on a LAN (expect one browser warning per address):
412
+
413
+ ```bash
414
+ sudo mkdir -p /etc/blockyard
415
+ sudo openssl req -x509 -newkey rsa:3072 -nodes -days 825 \
416
+ -keyout /etc/blockyard/key.pem -out /etc/blockyard/cert.pem \
417
+ -subj "/CN=blockyard" -addext "subjectAltName=IP:192.0.2.10,DNS:blockyard.lan.example"
418
+ sudo chown blockyard:blockyard /etc/blockyard/*.pem && sudo chmod 600 /etc/blockyard/key.pem
419
+ ```
420
+
421
+ With TLS on, the session cookie is marked `Secure` and a short HSTS header is sent. A
422
+ half-configured pair (only a cert, or only a key) and an expired certificate stop the start-up
423
+ rather than silently serving plain HTTP.
424
+
425
+ ## 10. Behind a reverse proxy (optional)
426
+
427
+ If you already run nginx, Caddy or similar, bind the monitor to `127.0.0.1` and let the proxy
428
+ terminate TLS. The live stream (`/api/stream`) is Server-Sent Events, so the proxy must not
429
+ buffer it. An nginx example:
430
+
431
+ ```nginx
432
+ server {
433
+ listen 443 ssl;
434
+ server_name monitor.example.org;
435
+ # ssl_certificate / ssl_certificate_key ...
436
+
437
+ location / {
438
+ proxy_pass http://127.0.0.1:21000;
439
+ proxy_set_header Host $host;
440
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
441
+ proxy_set_header X-Forwarded-Proto $scheme;
442
+ }
443
+ location /api/stream {
444
+ proxy_pass http://127.0.0.1:21000;
445
+ proxy_http_version 1.1;
446
+ proxy_set_header Connection "";
447
+ proxy_buffering off;
448
+ proxy_cache off;
449
+ proxy_read_timeout 1h;
450
+ }
451
+ }
452
+ ```
453
+
454
+ Then set `"server": { "trustProxy": true }` so the monitor uses the forwarded client
455
+ address for rate limits and the CIDR gate, and `BLOCKYARD_SECURE_COOKIE=1` so the session
456
+ cookie is marked `Secure` behind the proxy's TLS.
457
+
458
+ ## 11. Updating
459
+
460
+ ```bash
461
+ cd /opt/blockyard
462
+ sudo -u blockyard git pull
463
+ sudo systemctl restart blockyard
464
+ ```
465
+
466
+ Your `config/local.json` and `data/` directory are untouched by updates. Read
467
+ [CHANGELOG.md](../CHANGELOG.md) for anything that needs your attention. The browser picks
468
+ up new front-end files on the next page load; the header shows a notice when the page you
469
+ have open is older than the server.
470
+
471
+ ## 12. Uninstalling
472
+
473
+ ```bash
474
+ sudo systemctl disable --now blockyard
475
+ sudo rm /etc/systemd/system/blockyard.service && sudo systemctl daemon-reload
476
+ sudo rm -rf /opt/blockyard # includes data/: history, accounts and the audit trail
477
+ sudo userdel blockyard
478
+ ```
479
+
480
+ ## Checklist
481
+
482
+ - [ ] `node -v` prints v22 or later for the account the service runs as
483
+ - [ ] `config/local.json` names your node's RPC URL and a readable cookie (or user/password)
484
+ - [ ] `npm run check` passes: Core 25.0+, `txindex` synced, the block files readable, no pruning
485
+ - [ ] the index directory has ~125 GB free and is writable by the service account
486
+ - [ ] the start-up log shows the addresses you intended, and no node offline
487
+ - [ ] you have decided who can reach the port (bind, firewall, `allowCidrs`)
488
+ - [ ] accounts on if the port is reachable by people who should not see your node
489
+ - [ ] HTTPS on, or a proxy / tunnel in front, if the network is not trusted
490
+ - [ ] `BLOCKYARD_MARKETS=0` if the machine must make no outbound connections