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.
- package/CHANGELOG.md +929 -0
- package/LICENSE +202 -0
- package/NOTICE +4 -0
- package/README.md +191 -4
- package/SECURITY.md +38 -0
- package/bin/blockyard.js +41 -0
- package/config/pool-map.json +2620 -0
- package/docs/API.md +1577 -0
- package/docs/ARCHITECTURE.md +1394 -0
- package/docs/AUTO-UPDATE.md +269 -0
- package/docs/CONFIGURATION.md +847 -0
- package/docs/DEFECTS.md +813 -0
- package/docs/EFFECTS-AGENTS.md +448 -0
- package/docs/GETTING-STARTED.md +205 -0
- package/docs/INSTALL.md +547 -0
- package/docs/MEASUREMENTS.md +1401 -0
- package/docs/RULES.md +681 -0
- package/docs/SECURITY-AUDIT-2026-09-14.md +177 -0
- package/docs/SECURITY-AUDIT.md +258 -0
- package/docs/SECURITY.md +212 -0
- package/docs/TROUBLESHOOTING.md +332 -0
- package/docs/USER-GUIDE.md +1262 -0
- package/package.json +53 -5
- package/public/404.html +9 -0
- package/public/css/app.css +2009 -0
- package/public/donate-qr.png +0 -0
- package/public/index.html +1085 -0
- package/public/js/about.js +112 -0
- package/public/js/agents.js +1141 -0
- package/public/js/app.js +1386 -0
- package/public/js/arkanoid.js +806 -0
- package/public/js/blockanoid.js +347 -0
- package/public/js/blockout.js +347 -0
- package/public/js/blockpack.js +428 -0
- package/public/js/blockscene3d.js +2830 -0
- package/public/js/breakout.js +224 -0
- package/public/js/charts.js +635 -0
- package/public/js/depthchart.js +315 -0
- package/public/js/details3d.js +4342 -0
- package/public/js/doom.js +31 -0
- package/public/js/dosaudio.js +48 -0
- package/public/js/dosgame.js +389 -0
- package/public/js/dosio.js +186 -0
- package/public/js/dospc.js +1353 -0
- package/public/js/dosworker.js +196 -0
- package/public/js/explorer.js +405 -0
- package/public/js/feepalette.js +149 -0
- package/public/js/fmt.js +162 -0
- package/public/js/goggles.js +886 -0
- package/public/js/kiosk.js +41 -0
- package/public/js/login.js +88 -0
- package/public/js/markets.js +395 -0
- package/public/js/mining.js +1416 -0
- package/public/js/panels.js +970 -0
- package/public/js/pricechart.js +189 -0
- package/public/js/quake.js +20 -0
- package/public/js/settings.js +1096 -0
- package/public/js/soundcard.js +459 -0
- package/public/js/tetris.js +226 -0
- package/public/js/tetrust.js +356 -0
- package/public/js/tetsound.js +175 -0
- package/public/js/theme.js +235 -0
- package/public/js/wolf3d.js +22 -0
- package/public/js/x86.js +1978 -0
- package/public/login.html +33 -0
- package/scripts/blockfile-measure.js +156 -0
- package/scripts/browser-check.mjs +286 -0
- package/scripts/check.js +173 -0
- package/scripts/decode-check.js +81 -0
- package/scripts/doc-counts.js +109 -0
- package/scripts/donate-qr.py +23 -0
- package/scripts/dos-bench.js +56 -0
- package/scripts/fake-node.js +534 -0
- package/scripts/index-bench.js +216 -0
- package/scripts/index-benchmark.js +117 -0
- package/scripts/index-build.js +40 -0
- package/scripts/live-render-check.mjs +89 -0
- package/scripts/manage-users.js +132 -0
- package/scripts/motion-check.mjs +138 -0
- package/scripts/pool-map.js +157 -0
- package/scripts/setup.js +432 -0
- package/scripts/shots.mjs +278 -0
- package/scripts/smoke.sh +327 -0
- package/scripts/tls.js +31 -0
- package/scripts/ui.js +174 -0
- package/server/auth/sessions.js +221 -0
- package/server/auth/users.js +243 -0
- package/server/chain/blockfile.js +234 -0
- package/server/chain/index/build.js +210 -0
- package/server/chain/index/heights.js +36 -0
- package/server/chain/index/live.js +276 -0
- package/server/chain/index/rows.js +145 -0
- package/server/chain/index/store.js +154 -0
- package/server/chain/index/worker.js +109 -0
- package/server/chain/tx.js +310 -0
- package/server/collect/gbt.js +229 -0
- package/server/collect/logparse.js +765 -0
- package/server/collect/logtail.js +189 -0
- package/server/collect/markets.js +333 -0
- package/server/collect/mining.js +333 -0
- package/server/collect/monitor.js +2545 -0
- package/server/collect/network.js +295 -0
- package/server/collect/nextblock.js +275 -0
- package/server/collect/sync.js +386 -0
- package/server/config.js +644 -0
- package/server/http/api.js +1319 -0
- package/server/http/explorer.js +418 -0
- package/server/http/games.js +77 -0
- package/server/http/server.js +420 -0
- package/server/http/sse.js +176 -0
- package/server/http/static.js +212 -0
- package/server/main.js +673 -0
- package/server/netinfo.js +253 -0
- package/server/rpc/allowlist.js +130 -0
- package/server/rpc/client.js +414 -0
- package/server/store/audit.js +148 -0
- package/server/store/history.js +220 -0
- package/server/store/ledger.js +290 -0
- package/server/store/ring.js +173 -0
- package/server/tls/selfsigned.js +160 -0
- package/server/util/fmt.js +29 -0
- package/systemd/blockyard.service +102 -0
|
@@ -0,0 +1,847 @@
|
|
|
1
|
+
# Configuration reference
|
|
2
|
+
|
|
3
|
+
This is the complete reference for configuring BlockYard: every configuration
|
|
4
|
+
key, every environment variable, what lives in the data directory, and which
|
|
5
|
+
mistakes stop the server from starting.
|
|
6
|
+
|
|
7
|
+
BlockYard needs no configuration file to start. The built-in defaults describe
|
|
8
|
+
a stock local Bitcoin Core node, though, so most deployments want at least a
|
|
9
|
+
`config/local.json` that says where the node is.
|
|
10
|
+
|
|
11
|
+
- [How configuration is resolved](#how-configuration-is-resolved)
|
|
12
|
+
- [Creating config/local.json](#creating-configlocaljson)
|
|
13
|
+
- [Configuration keys](#configuration-keys)
|
|
14
|
+
- [server](#server)
|
|
15
|
+
- [nodes](#nodes)
|
|
16
|
+
- [rpc](#rpc)
|
|
17
|
+
- [poll](#poll)
|
|
18
|
+
- [store](#store)
|
|
19
|
+
- [auth](#auth)
|
|
20
|
+
- [actions](#actions)
|
|
21
|
+
- [log](#log)
|
|
22
|
+
- [markets](#markets)
|
|
23
|
+
- [Environment variables](#environment-variables)
|
|
24
|
+
- [Examples](#examples)
|
|
25
|
+
- [The data directory](#the-data-directory)
|
|
26
|
+
- [Validation: what stops the boot](#validation-what-stops-the-boot)
|
|
27
|
+
- [Known quirks](#known-quirks)
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## How configuration is resolved
|
|
32
|
+
|
|
33
|
+
Three layers, highest priority first:
|
|
34
|
+
|
|
35
|
+
1. **Environment variables** (`BLOCKYARD_*`, see [the table](#environment-variables)).
|
|
36
|
+
2. **The configuration file**: `config/local.json` in the repository root, or the
|
|
37
|
+
file named by `BLOCKYARD_CONFIG`.
|
|
38
|
+
3. **Built-in defaults** (`DEFAULTS` in `server/config.js`).
|
|
39
|
+
|
|
40
|
+
Merge rules:
|
|
41
|
+
|
|
42
|
+
- **Objects are merged key by key.** `{"server": {"port": 9000}}` changes the port
|
|
43
|
+
and keeps every other `server` key at its default.
|
|
44
|
+
- **Arrays replace the default wholesale.** This matters for `nodes`. As soon as your
|
|
45
|
+
file has a `nodes` array, the built-in node entry is gone and none of its fields
|
|
46
|
+
are inherited. Every node you list must be complete on its own.
|
|
47
|
+
- **Unknown keys are ignored silently.** No schema check runs, so a misspelled key
|
|
48
|
+
such as `"allowCIDRs"` has no effect and produces no error.
|
|
49
|
+
|
|
50
|
+
`BLOCKYARD_CONFIG` controls which file is read:
|
|
51
|
+
|
|
52
|
+
| value | effect |
|
|
53
|
+
|---|---|
|
|
54
|
+
| unset or empty | read `<repo>/config/local.json` if it exists |
|
|
55
|
+
| a path | read that file instead |
|
|
56
|
+
| `none`, `off`, `no` or `-` (any case) | read no file; defaults plus environment only |
|
|
57
|
+
|
|
58
|
+
A path that does not exist, or is not a regular file (for example `/dev/null`),
|
|
59
|
+
counts as "no file". **It is not an error**, so a typo in the path quietly gives you
|
|
60
|
+
the defaults. A file that exists but is not valid JSON stops the boot (see
|
|
61
|
+
[Validation](#validation-what-stops-the-boot)).
|
|
62
|
+
|
|
63
|
+
`npm run dev` sets `BLOCKYARD_CONFIG=none`, so a development run never picks up the
|
|
64
|
+
machine's real configuration.
|
|
65
|
+
|
|
66
|
+
## Creating config/local.json
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
cd /path/to/blockyard
|
|
70
|
+
mkdir -p config
|
|
71
|
+
$EDITOR config/local.json # see the examples below
|
|
72
|
+
chmod 600 config/local.json # if it contains rpcPassword or other secrets
|
|
73
|
+
npm start
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
`config/local.json` is listed in `.gitignore`. Machine-specific facts (addresses,
|
|
77
|
+
paths, credentials) belong there and never in a committed file.
|
|
78
|
+
|
|
79
|
+
The file is read once at startup. Restart the server after editing it.
|
|
80
|
+
|
|
81
|
+
Two things write into `config/` while the server runs, both from the web UI: the **Node
|
|
82
|
+
connection** form on Node & RPC saves `nodes[0]` back into this file (`POST /api/config/node`;
|
|
83
|
+
a restart applies it), and **Display settings** are kept in `config/blockyard.json` beside it
|
|
84
|
+
(`POST /api/settings`; it holds no secrets and is also in `.gitignore`). Both are written mode
|
|
85
|
+
`0600` by temporary file, fsync and rename, and with accounts on both need the admin role. The
|
|
86
|
+
service account therefore needs write access to `config/` if either is to be saved from the UI.
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
## Configuration keys
|
|
91
|
+
|
|
92
|
+
Durations are in milliseconds unless the name says otherwise (`retentionHours`).
|
|
93
|
+
|
|
94
|
+
### server
|
|
95
|
+
|
|
96
|
+
| key | default | meaning |
|
|
97
|
+
|---|---|---|
|
|
98
|
+
| `server.host` | `"127.0.0.1"` | Address to listen on — this machine only, by default. Kept for compatibility; `server.hosts` wins when both are set. |
|
|
99
|
+
| `server.hosts` | *(unset; falls back to `host`)* | Addresses to listen on: an array (`["192.0.2.10", "2001:db8::10"]`), or a single string with commas (`"192.0.2.10,198.51.100.7"`). One HTTP server is started per address, and all of them share sessions, rate limits and monitors. Entries must be address literals: IPv4, IPv6, `0.0.0.0`, `::`, or `localhost`. Hostnames are refused. See [Binding](#binding-to-specific-addresses). |
|
|
100
|
+
| `server.port` | `21000` | TCP port. It is the same port on every address. It must be an integer from 1 to 65535. |
|
|
101
|
+
| `server.allowCidrs` | `[]` | Client allowlist. Empty means every client that can reach the port is admitted. Otherwise only addresses inside one of the networks connect, and everyone else gets HTTP 403 (the reason goes to the server log). Entries are CIDRs or bare addresses (a bare address means `/32` or `/128`), IPv4 or IPv6, compared bit by bit. An entry that cannot be parsed stops the boot. |
|
|
102
|
+
| `server.trustProxy` | `false` | When `true`, the client address is the **first** entry of the `X-Forwarded-For` header instead of the socket's peer address. That address feeds the CIDR allowlist, the rate limits and the audit log. Turn it on only when a reverse proxy you control is the sole way in and it sets that header. Otherwise any client can pick its own address and walk past `allowCidrs`. |
|
|
103
|
+
| `server.tls.enabled` | `true` | HTTPS on every listener. With no `cert`/`key` named, the server makes its own self-signed certificate under `<data>/tls/` on first start (see INSTALL §9). `false` serves plain HTTP, for a reverse proxy that terminates TLS. |
|
|
104
|
+
| `server.tls.cert` | `null` | Path to a PEM certificate of your own, used instead of the made one. Set it together with `key`. |
|
|
105
|
+
| `server.tls.key` | `null` | Path to the PEM private key for `cert`. This file is secret. |
|
|
106
|
+
| `server.tls.hstsMs` | `172800000` (2 days) | `max-age` of the `Strict-Transport-Security` header. It is sent on TLS responses only. `0` turns it off. `includeSubDomains` and `preload` are never sent. |
|
|
107
|
+
|
|
108
|
+
When TLS is on, `auth.secureCookie` is forced to `true`. At startup the server logs
|
|
109
|
+
the certificate's SHA-256 fingerprint and whether it is self-signed. It also warns
|
|
110
|
+
when the certificate expires within 14 days — and remakes its own, if it made it, at that
|
|
111
|
+
point or when the certificate no longer names a bound address.
|
|
112
|
+
|
|
113
|
+
### nodes
|
|
114
|
+
|
|
115
|
+
`nodes` is an array with one object per Bitcoin Core node to monitor. It must not
|
|
116
|
+
be empty. If you list more than one node, the web UI shows a node picker, and every
|
|
117
|
+
node gets its own charts and event stream.
|
|
118
|
+
|
|
119
|
+
| field | default | meaning |
|
|
120
|
+
|---|---|---|
|
|
121
|
+
| `id` | `node-<index>` | Stable identifier, used in URLs (`?node=<id>`) and to tag stored history. Changing it orphans that node's past chart data. |
|
|
122
|
+
| `label` | same as `id` | Display name in the UI. |
|
|
123
|
+
| `rpcUrl` | **required** | `http://host:port` or `https://host:port` of the node's JSON-RPC server. |
|
|
124
|
+
| `datadir` | none | The node's data directory, used to find its RPC cookie (see below). |
|
|
125
|
+
| `chainHint` | none | Name of the chain subdirectory inside `datadir` that holds the cookie, for example `main`. |
|
|
126
|
+
| `cookieFile` | `null` | Explicit path to the RPC cookie file. When set, it is tried first. |
|
|
127
|
+
| `rpcUser` | `null` | RPC username. It is used only when no cookie file can be read. |
|
|
128
|
+
| `rpcPassword` | `null` | RPC password for `rpcUser`. It is a secret, so protect the config file. |
|
|
129
|
+
| `logFile` | none | The node's log file, tailed only when `log.enabled` is `true`. With the log source off (the default) this field is ignored, and a line in the startup log says so. |
|
|
130
|
+
| `logStaleMs` | *(uses `log.staleMs`)* | Per-node override for how long the log may go without new bytes before the monitor reports it as silent. Use a smaller value for a node that is known to log often. |
|
|
131
|
+
| `addressIndex` | none | Directory of the address index, built from this node's own block files (`<datadir>/blocks`). With it, the explorer's address page shows history, balance, unspent outputs and per-transaction amounts; without it, Core cannot answer those and the page says so. If the directory holds no finished index (no `manifest.json`) when the server starts, **the server builds one in the background** (see the next two keys). One index serves every node on the same chain. The server keeps it current as blocks arrive (a follower per directory writes `live.log` and `layers/` inside it, so the directory must be writable by the service); the page says if it is behind or has stopped following. See `docs/MEASUREMENTS.md` §30. |
|
|
132
|
+
| `addressIndexBuild` | *(unset)* | `"manual"` keeps the server from building a missing address index on start. Otherwise a missing index is built inside the server on worker threads while every page keeps serving: the progress is the `address-index-building` quality flag on the Overview (phase, done of total, rows, time left, and whether it is paused), an event of kind `index` marks the start, the finish and a failure (the browser shows each as a notification), the follower starts the moment the build finishes so address pages go live with no restart, and a failure raises `address-index-build-failed` with the command to run by hand. The build is paced by the node's own RPC: it holds while the node is failing, its breaker is open or its average latency is above `rpc.slowLatencyMs`, and eases off above 40% of it. A build interrupted by a stop does not resume; the next start begins it again. `npm run setup --build-later` writes this key. |
|
|
133
|
+
| `addressIndexWorkers` | half of a dedicated build's count, at most 4 | Worker threads for the background build. A dedicated build (`scripts/index-build.js`) uses `cpus − 4`, one per ~2.5 GB of memory, at most 16; the server takes half of that, at most 4, because the node shares the disk. **Use 1 on a spinning disk**: parallel readers seek against each other and against the node. The installer writes the number you give it. |
|
|
134
|
+
| `optional` | `false` | Marks a node whose absence is expected, such as a test or benchmark node. Its failures are logged at a lower severity, a missing datadir is reported at `info` instead of `warn`, and it does not count as a required node in `/api/health`. |
|
|
135
|
+
| `color` | `"#f7931a"` | Accent colour for this node in the UI. |
|
|
136
|
+
| `systemdUnit` | *(see defaults)* | Name of the node's systemd unit. It is informational and not currently used by the server. |
|
|
137
|
+
|
|
138
|
+
**Credentials.** A node needs **either** a cookie path (`datadir`, optionally with `chainHint`,
|
|
139
|
+
or an explicit `cookieFile`) **or** `rpcUser` + `rpcPassword`. Cookie auth is the usual case on the
|
|
140
|
+
same machine; user/password is for a node that authenticates with `rpcauth` rather than the cookie
|
|
141
|
+
file. (BlockYard runs on the node's machine; see INSTALL, "It runs on the node's machine".) A
|
|
142
|
+
node with neither is
|
|
143
|
+
refused at boot, and `rpcUser` without `rpcPassword` is refused too, rather than sending
|
|
144
|
+
`Basic dXNlcjo=` and failing with a confusing 401.
|
|
145
|
+
Before every connection that lacks a credential, and again after any HTTP 401, the
|
|
146
|
+
client looks for a credential in this order and uses the first one found:
|
|
147
|
+
|
|
148
|
+
1. `cookieFile`
|
|
149
|
+
2. `<datadir>/<chainHint>/.cookie`
|
|
150
|
+
3. `<datadir>/.cookie`
|
|
151
|
+
4. `<datadir>/<every subdirectory>/.cookie`
|
|
152
|
+
5. `rpcUser` / `rpcPassword`
|
|
153
|
+
|
|
154
|
+
A cookie file that can be read always wins over `rpcUser`. The node rewrites its
|
|
155
|
+
cookie on every restart, and the monitor picks up the new one automatically. The
|
|
156
|
+
account running BlockYard needs read access to the cookie file, and to `logFile` if
|
|
157
|
+
you use one.
|
|
158
|
+
|
|
159
|
+
**Skipped nodes.** A node that has `datadir`, has no `cookieFile`, and whose
|
|
160
|
+
`datadir` does not exist at startup is **skipped** with a log line. The server does
|
|
161
|
+
not stop. This lets a node whose directory has been removed disappear cleanly
|
|
162
|
+
instead of showing as permanently offline.
|
|
163
|
+
|
|
164
|
+
**Built-in default node.** Without a `nodes` array in your file, one node is
|
|
165
|
+
configured. It is Bitcoin Core's own mainnet layout: RPC on the standard port
|
|
166
|
+
8332, with the data directory of a `bitcoin` service account:
|
|
167
|
+
|
|
168
|
+
| field | built-in value |
|
|
169
|
+
|---|---|
|
|
170
|
+
| `id` | `main` |
|
|
171
|
+
| `label` | `Bitcoin Core (mainnet)` |
|
|
172
|
+
| `rpcUrl` | `http://127.0.0.1:8332` |
|
|
173
|
+
| `datadir` | `/home/bitcoin/.bitcoin` |
|
|
174
|
+
| `chainHint` | `main` |
|
|
175
|
+
| `logFile` | `/home/bitcoin/.bitcoin/debug.log` |
|
|
176
|
+
| `systemdUnit` | `bitcoind.service` |
|
|
177
|
+
| `color` | `#f7931a` |
|
|
178
|
+
|
|
179
|
+
If your node lives elsewhere, override it with a `nodes` array in
|
|
180
|
+
`config/local.json`, or for the first node only, with `BLOCKYARD_NODE_URL`,
|
|
181
|
+
`BLOCKYARD_DATADIR`, `BLOCKYARD_COOKIE`, `BLOCKYARD_LOGFILE` and `BLOCKYARD_NODE_LABEL`.
|
|
182
|
+
A node built from source or run by a distribution package may keep its data directory
|
|
183
|
+
somewhere else, and a node with an `rpcport=` line of its own is not on 8332 at all: a
|
|
184
|
+
non-default RPC port is the most common reason this monitor reports a healthy node
|
|
185
|
+
as offline, so check the node's own `rpcport`.
|
|
186
|
+
|
|
187
|
+
**The label follows the node.** If you set `BLOCKYARD_NODE_URL` to an address other
|
|
188
|
+
than the one the node was already configured for, and do not also set
|
|
189
|
+
`BLOCKYARD_NODE_LABEL`, the first node is renamed to `node @ host:port` rather than
|
|
190
|
+
keeping the built-in name, which would now describe a different node. Restating the
|
|
191
|
+
address it already had redirects nothing, so in that case the built-in name stands.
|
|
192
|
+
Give it a name of your own with `BLOCKYARD_NODE_LABEL`, or with `label` in a `nodes`
|
|
193
|
+
entry; either one wins over both.
|
|
194
|
+
|
|
195
|
+
### rpc
|
|
196
|
+
|
|
197
|
+
The node's RPC server handles one connection at a time on a single thread.
|
|
198
|
+
BlockYard therefore sends requests one at a time, in priority order, and these
|
|
199
|
+
limits protect the node from the monitor. They apply to each node separately.
|
|
200
|
+
|
|
201
|
+
| key | default | meaning |
|
|
202
|
+
|---|---|---|
|
|
203
|
+
| `rpc.maxInFlight` | `1` | Requests outstanding at once. **Currently advisory:** the lane is serialised by construction and does not read this value (measured 2026-09-13 at 1, 4 and 8 -- peak concurrency was 1 in every case). It must still be at least 1. |
|
|
204
|
+
| `rpc.minIntervalMs` | `250` | Minimum gap between the start of one request and the next. |
|
|
205
|
+
| `rpc.maxRatePerSec` | `4` | Hard ceiling on requests per second, whatever the poll tiers ask for. |
|
|
206
|
+
| `rpc.timeoutMs` | `90000` | Timeout for ordinary calls. It is deliberately generous, because a busy but healthy node can take tens of seconds to answer. |
|
|
207
|
+
| `rpc.heavyTimeoutMs` | `300000` | Timeout for calls that are known to be expensive (UTXO-set statistics and similar). |
|
|
208
|
+
| `rpc.staleDropMs` | `12000` | A poll answer that arrives later than this after it was requested is thrown away, not shown as current state. |
|
|
209
|
+
| `rpc.slowLatencyMs` | `5000` | Above this average latency, the UI says the node is slow instead of implying the monitor is broken. The background address index build holds above it and eases off above 40% of it (see `addressIndexBuild` under [nodes](#nodes)). |
|
|
210
|
+
| `rpc.breakerThreshold` | `3` | Consecutive failures before the circuit breaker opens and the monitor stops sending requests for a while. |
|
|
211
|
+
| `rpc.breakerCooldownMs` | `30000` | How long the breaker stays open before the next attempt. |
|
|
212
|
+
|
|
213
|
+
### poll
|
|
214
|
+
|
|
215
|
+
Each tier is a group of RPC reads that runs on its own cadence. The cadence adapts
|
|
216
|
+
under load: a tier stretches to at least twice the observed RPC latency, and the
|
|
217
|
+
heavy tiers are thinned while the node is slow.
|
|
218
|
+
|
|
219
|
+
| key | default | what it reads |
|
|
220
|
+
|---|---|---|
|
|
221
|
+
| `poll.fastMs` | `4000` | Chain info, mempool info, connection count, network totals, uptime. It must be at least 1000. |
|
|
222
|
+
| `poll.midMs` | `15000` | Mining info, fee estimates, chain tips, mempool transaction ids. |
|
|
223
|
+
| `poll.poolMs` | `20000` | The verbose mempool, which feeds the block-space viewer and the mempool map. If it is missing, `slowMs` is used. |
|
|
224
|
+
| `poll.slowMs` | `60000` | Index info, UTXO-set info, chain transaction statistics. |
|
|
225
|
+
| `poll.rareMs` | `900000` | Peer info, deployment info, RPC server info. |
|
|
226
|
+
| `poll.blockBackfill` | `30` | How many recent blocks are fetched at startup when no block history is loaded. |
|
|
227
|
+
|
|
228
|
+
### store
|
|
229
|
+
|
|
230
|
+
| key | default | meaning |
|
|
231
|
+
|---|---|---|
|
|
232
|
+
| `store.dir` | `<repo>/data` | Data directory for history snapshots, the audit log and the mining label files (`pool-aliases.json`, and a `pool-map.json` that overrides the shipped `config/pool-map.json`). It is created if missing. See [The data directory](#the-data-directory). |
|
|
233
|
+
| `store.retentionHours` | `72` | How long chart history is kept. |
|
|
234
|
+
| `store.ringCapacity` | `20000` | Maximum points kept per chart series. |
|
|
235
|
+
| `store.maxEventLog` | `5000` | Maximum entries kept in the event feed. |
|
|
236
|
+
| `store.snapshotEveryMs` | `120000` | How often changed history is written to `history.json`. Writes are atomic (temporary file, fsync, rename). History is also saved on a clean shutdown. |
|
|
237
|
+
| `store.blockMapCap` | `12000` | Maximum blocks kept in the in-memory block map. It must be an integer of at least 100. |
|
|
238
|
+
| `store.auditMaxBytes` | `8388608` (8 MiB) | Size at which `audit.jsonl` is rotated. It must be at least 65536. |
|
|
239
|
+
| `store.auditKeep` | `5` | Rotated audit files to keep (`audit.1.jsonl` … `audit.5.jsonl`). The oldest is deleted when a new one is created. |
|
|
240
|
+
|
|
241
|
+
### auth
|
|
242
|
+
|
|
243
|
+
Accounts are **off by default**. With accounts off, anyone who can reach a listen
|
|
244
|
+
address reads the monitor without signing in, as role `viewer`: charts, event feed,
|
|
245
|
+
peer and mempool detail, and the read-only RPC console. User administration, the
|
|
246
|
+
audit trail and password changes stay closed. Node writes stay closed unless
|
|
247
|
+
[`actions`](#actions) explicitly opens them. A warning at every startup names the
|
|
248
|
+
addresses left open.
|
|
249
|
+
|
|
250
|
+
With accounts on you get sign-in, three roles (`viewer` < `operator` < `admin`),
|
|
251
|
+
sessions, CSRF protection and a per-user audit trail.
|
|
252
|
+
|
|
253
|
+
| key | default | meaning |
|
|
254
|
+
|---|---|---|
|
|
255
|
+
| `auth.enabled` | `false` | Turn accounts on. |
|
|
256
|
+
| `auth.dataDir` | same as `store.dir` | Where `users.json` and `sessions.json` live. |
|
|
257
|
+
| `auth.sessionTtlMs` | `259200000` (72 h) | Absolute session lifetime, counted from sign-in. |
|
|
258
|
+
| `auth.idleTtlMs` | `28800000` (8 h) | A session unused for this long expires. Until 2026-09-13 the two defaults were the other way round, so the idle check could never fire and a session was 8 h whatever you did. |
|
|
259
|
+
| `auth.scrypt.N` | `16384` | scrypt cost parameter for password hashes. |
|
|
260
|
+
| `auth.scrypt.r` | `8` | scrypt block size. |
|
|
261
|
+
| `auth.scrypt.p` | `1` | scrypt parallelism. |
|
|
262
|
+
| `auth.scrypt.keylen` | `32` | Derived key length in bytes. |
|
|
263
|
+
| `auth.minPasswordChars` | `12` | Minimum password length. Passwords are also refused if they contain the username, begin with a common breached password (`password`, `admin`, `qwerty`, `bitcoin`, …), or are one character repeated. |
|
|
264
|
+
| `auth.loginMaxAttempts` | `8` | Intended: failed logins per username before lockout. See [Known quirks](#known-quirks); the effective value is 8 whatever you set. |
|
|
265
|
+
| `auth.loginWindowMs` | `300000` (5 min) | Intended: window in which failed attempts are counted. See [Known quirks](#known-quirks); the effective value is 5 minutes. |
|
|
266
|
+
| `auth.lockoutMs` | `600000` (10 min) | How long a username stays locked after too many failures. |
|
|
267
|
+
| `auth.cookieName` | `"blockyard_sid"` | Name of the session cookie. |
|
|
268
|
+
| `auth.secureCookie` | `false` | Mark the session cookie `Secure`. Forced to `true` when `server.tls` is on. Set it yourself only when a TLS-terminating reverse proxy sits in front, because browsers never send a `Secure` cookie over plain HTTP. |
|
|
269
|
+
|
|
270
|
+
Separately from these settings, login requests are rate-limited per client address
|
|
271
|
+
(a burst of 10, then one attempt every 2 seconds). All API requests are
|
|
272
|
+
rate-limited too.
|
|
273
|
+
|
|
274
|
+
**The first admin account.** When accounts are on and `users.json` holds no users, the
|
|
275
|
+
server creates a user named `admin` at startup:
|
|
276
|
+
|
|
277
|
+
- If `BLOCKYARD_ADMIN_PASSWORD` is set, that is the password. It must pass the
|
|
278
|
+
password rules above; for example, it may not contain `admin`. A password that
|
|
279
|
+
fails them stops the boot.
|
|
280
|
+
- Otherwise a random 20-character password is generated and printed **once** in the
|
|
281
|
+
startup banner. Only its scrypt hash is stored.
|
|
282
|
+
|
|
283
|
+
**Managing accounts from the command line**, for example after a lost password.
|
|
284
|
+
Passwords are always read from the terminal without echo, never from arguments:
|
|
285
|
+
|
|
286
|
+
```bash
|
|
287
|
+
npm run user -- list
|
|
288
|
+
npm run user -- create alice operator
|
|
289
|
+
npm run user -- passwd alice
|
|
290
|
+
npm run user -- role alice admin
|
|
291
|
+
npm run user -- disable alice # or: enable alice
|
|
292
|
+
npm run user -- rm alice
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
(`npm run user` is `node scripts/manage-users.js`.) The tool loads the same
|
|
296
|
+
configuration as the server and edits `<auth.dataDir>/users.json`. It will not
|
|
297
|
+
disable, demote or delete the last enabled admin. If accounts are off in the
|
|
298
|
+
configuration it loads, it says so, because the server ignores `users.json` in that
|
|
299
|
+
state.
|
|
300
|
+
|
|
301
|
+
### actions
|
|
302
|
+
|
|
303
|
+
Actions are the only node **writes** BlockYard can make. Everything is off by
|
|
304
|
+
default. An action runs only when all of these are true:
|
|
305
|
+
|
|
306
|
+
1. `actions.enabled` is `true`;
|
|
307
|
+
2. the action's name is listed in `actions.allow`;
|
|
308
|
+
3. accounts are on **and** the signed-in user has at least the role the action
|
|
309
|
+
requires, **or** accounts are off and `actions.allowWritesWithoutAuth` is `true`;
|
|
310
|
+
4. the request carries a typed confirmation of the action's name.
|
|
311
|
+
|
|
312
|
+
Every attempt, allowed or denied, is written to the audit log.
|
|
313
|
+
|
|
314
|
+
| key | default | meaning |
|
|
315
|
+
|---|---|---|
|
|
316
|
+
| `actions.enabled` | `false` | Master switch for node writes. |
|
|
317
|
+
| `actions.allow` | `[]` | Names of the actions to enable (see below). |
|
|
318
|
+
| `actions.requireAdmin` | `true` | Not currently consulted; each action's minimum role is fixed (see the table below and [Known quirks](#known-quirks)). |
|
|
319
|
+
| `actions.allowWritesWithoutAuth` | `false` | Required, deliberately and separately, to enable actions while accounts are off. With accounts off there is no role to check, so every allowed action can be called by anyone who can reach the port. |
|
|
320
|
+
|
|
321
|
+
Available actions:
|
|
322
|
+
|
|
323
|
+
| name | node RPC | minimum role |
|
|
324
|
+
|---|---|---|
|
|
325
|
+
| `broadcast` | `sendrawtransaction` | `operator` |
|
|
326
|
+
| `savemempool` | `savemempool` | `operator` |
|
|
327
|
+
| `testmempoolaccept` | `testmempoolaccept` (a dry run, changes nothing) | `viewer` |
|
|
328
|
+
| `verifychain_l1` | `verifychain` at check level 2, depth 6 | `admin` |
|
|
329
|
+
|
|
330
|
+
### log
|
|
331
|
+
|
|
332
|
+
| key | default | meaning |
|
|
333
|
+
|---|---|---|
|
|
334
|
+
| `log.level` | `"info"` | Server log verbosity: `debug`, `info`, `warn` or `error`. The server logs to stdout. Under systemd that goes to the journal. |
|
|
335
|
+
| `log.enabled` | `false` | Tail each node's `logFile` as an extra data source. **Off by default, and there is currently no log format this parses for Bitcoin Core** -- see the note below. With it off, figures only a log could provide are shown as unavailable rather than as zero. |
|
|
336
|
+
| `log.tailBytes` | `2097152` (2 MiB) | Size of the read-back window the log tail uses. |
|
|
337
|
+
| `log.staleMs` | `1800000` (30 min) | How long a tailed log may go without a single new byte before the monitor reports it as silent. A synced, idle node can legitimately stay quiet for about 20 minutes. Override per node with `logStaleMs`. |
|
|
338
|
+
| `log.healthMs` | `30000` | How often that check runs. It costs no RPC. |
|
|
339
|
+
|
|
340
|
+
### markets
|
|
341
|
+
|
|
342
|
+
The Markets tab fetches public BTC/USD prices, hourly candles and order books from
|
|
343
|
+
five exchanges over HTTPS: Coinbase, Kraken, Bitstamp, Bitfinex and OKX (OKX quotes
|
|
344
|
+
BTC/USDT). The fetches run on the server, not in the browser. This is BlockYard's
|
|
345
|
+
only outbound connection other than the node, and **polling is off by default**: a fresh
|
|
346
|
+
install makes no request to anyone but the node until someone ticks **Display settings → Markets & Price → Enable market polling** in the browser (a Display
|
|
347
|
+
setting, stored in `config/blockyard.json` and shared by every screen; no restart).
|
|
348
|
+
Exchanges then see this server's IP address and a User-Agent, nothing about the node. Polling starts when a browser asks for market data
|
|
349
|
+
-- the Markets and Kiosk tabs, and Overview's price line (Display settings → Markets &
|
|
350
|
+
Price → Price line on Overview) -- and stops `idleAfterMs` after the last request, so an
|
|
351
|
+
unwatched monitor makes no exchange traffic. With that line switched off, only Markets
|
|
352
|
+
and Kiosk start it. The exchange list is fixed in code.
|
|
353
|
+
|
|
354
|
+
| key | default | meaning |
|
|
355
|
+
|---|---|---|
|
|
356
|
+
| `markets.enabled` | `true` | `false` removes the feed entirely: no request is ever made, Markets and Kiosk say so, and the **Enable market polling** checkbox cannot turn it on. |
|
|
357
|
+
| `markets.tickerMs` | `15000` | Ticker (last, bid, ask, 24 h volume) refresh. |
|
|
358
|
+
| `markets.candleMs` | `300000` | Hourly candle refresh. |
|
|
359
|
+
| `markets.bookMs` | `30000` | Order book refresh, for the depth chart. |
|
|
360
|
+
| `markets.idleAfterMs` | `600000` | Stop polling this long after the last Markets request. |
|
|
361
|
+
| `markets.timeoutMs` | `8000` | Timeout for each exchange request. |
|
|
362
|
+
|
|
363
|
+
---
|
|
364
|
+
|
|
365
|
+
## Environment variables
|
|
366
|
+
|
|
367
|
+
Environment variables override `config/local.json`.
|
|
368
|
+
|
|
369
|
+
- **Booleans**: `1`, `true`, `yes` or `on` (any case) mean true. Any other non-empty
|
|
370
|
+
value means false.
|
|
371
|
+
- **Empty values**: a variable set to the empty string counts as unset.
|
|
372
|
+
- **Numbers**: parsed with `Number()`. A non-numeric value becomes `NaN`. Only
|
|
373
|
+
`BLOCKYARD_PORT` is range-checked, so check spelling.
|
|
374
|
+
- **Lists**: comma-separated, with spaces around entries trimmed.
|
|
375
|
+
- **Node variables** (`BLOCKYARD_NODE_URL`, `BLOCKYARD_DATADIR`, `BLOCKYARD_COOKIE`,
|
|
376
|
+
`BLOCKYARD_LOGFILE`, `BLOCKYARD_UNIT`, `BLOCKYARD_NODE_LABEL`) change **only the first
|
|
377
|
+
entry** of `nodes`, whether that entry comes from the defaults or from your file.
|
|
378
|
+
|
|
379
|
+
### Server variables
|
|
380
|
+
|
|
381
|
+
| variable | sets | type | default | meaning |
|
|
382
|
+
|---|---|---|---|---|
|
|
383
|
+
| `BLOCKYARD_CONFIG` | *(which file is read)* | path or `none` | `<repo>/config/local.json` | Configuration file to read. `none`/`off`/`no`/`-` reads no file. |
|
|
384
|
+
| `BLOCKYARD_BIND` | `server.host` | list | `127.0.0.1` | Listen address(es), for example `0.0.0.0` or `192.0.2.10,2001:db8::10`. If both are set, this wins over `BLOCKYARD_HOST`. Ignored when the file sets `server.hosts` (see [Known quirks](#known-quirks)). |
|
|
385
|
+
| `BLOCKYARD_HOST` | `server.host` | list | `127.0.0.1` | Same as `BLOCKYARD_BIND`. |
|
|
386
|
+
| `BLOCKYARD_PORT` | `server.port` | number | `21000` | Listen port. |
|
|
387
|
+
| `BLOCKYARD_ALLOW_CIDRS` | `server.allowCidrs` | list | *(empty: everyone)* | Client allowlist, for example `192.0.2.0/24,2001:db8::/32`. |
|
|
388
|
+
| `BLOCKYARD_TRUST_PROXY` | `server.trustProxy` | boolean | `false` | Take the client address from `X-Forwarded-For`. |
|
|
389
|
+
| `BLOCKYARD_TLS` | `server.tls.enabled` | boolean | `true` | `0` serves plain HTTP (behind a TLS-terminating proxy). |
|
|
390
|
+
| `BLOCKYARD_TLS_CERT` | `server.tls.cert` | path | unset | PEM certificate of your own, instead of the made one. Set it together with `BLOCKYARD_TLS_KEY`. |
|
|
391
|
+
| `BLOCKYARD_TLS_KEY` | `server.tls.key` | path | unset | PEM private key. Set it together with `BLOCKYARD_TLS_CERT`. |
|
|
392
|
+
| `BLOCKYARD_NODE_URL` | `nodes[0].rpcUrl` | URL | `http://127.0.0.1:8332` | RPC endpoint of the first node. |
|
|
393
|
+
| `BLOCKYARD_DATADIR` | `nodes[0].datadir` | path | `/home/bitcoin/.bitcoin` | Data directory of the first node. It also **clears** `nodes[0].cookieFile`, so the cookie is looked up under the new datadir. |
|
|
394
|
+
| `BLOCKYARD_COOKIE` | `nodes[0].cookieFile` | path | unset | Explicit cookie file for the first node. It is applied after `BLOCKYARD_DATADIR`, so it wins. |
|
|
395
|
+
| `BLOCKYARD_LOGFILE` | `nodes[0].logFile` | path | see [defaults](#nodes) | Log file of the first node. Used only when the log source is on -- which is not supported for Bitcoin Core; see [RPC-only mode](#rpc-only-mode-and-the-log-source). |
|
|
396
|
+
| `BLOCKYARD_UNIT` | `nodes[0].systemdUnit` | string | `bitcoind.service` | systemd unit name of the first node. Informational only. |
|
|
397
|
+
| `BLOCKYARD_NODE_LABEL` | `nodes[0].label` | string | `Bitcoin Core (mainnet)` | Display name of the first node, shown in the header. Setting `BLOCKYARD_NODE_URL` to a *different* address without this renames the node to `node @ host:port`, so a redirected instance cannot keep a built-in name that would describe the wrong node. Restating the address the node already had renames nothing. |
|
|
398
|
+
| `BLOCKYARD_RPC_TIMEOUT` | `rpc.timeoutMs` | number | `90000` | RPC timeout for ordinary calls. |
|
|
399
|
+
| `BLOCKYARD_RPC_MIN_INTERVAL` | `rpc.minIntervalMs` | number | `250` | Minimum gap between RPC requests. |
|
|
400
|
+
| `BLOCKYARD_RPC_STALE_DROP` | `rpc.staleDropMs` | number | `12000` | Drop poll answers older than this. |
|
|
401
|
+
| `BLOCKYARD_DATA` | `store.dir` | path | `<repo>/data` | Data directory. Also the default `auth.dataDir`. |
|
|
402
|
+
| `BLOCKYARD_RETENTION_HOURS` | `store.retentionHours` | number | `72` | Chart history retention. |
|
|
403
|
+
| `BLOCKYARD_AUTH` | `auth.enabled` | boolean | `false` | Turn accounts on. |
|
|
404
|
+
| `BLOCKYARD_SECURE_COOKIE` | `auth.secureCookie` | boolean | `false` | `Secure` session cookie. Use it behind a TLS terminator; it is automatic with built-in TLS. |
|
|
405
|
+
| `BLOCKYARD_ADMIN_PASSWORD` | *(none)* | string | *(generated)* | Password for the `admin` account created on first boot when accounts are on and no users exist. It is ignored once any user exists. It is a secret, so do not leave it in a unit file after first boot. |
|
|
406
|
+
| `BLOCKYARD_ENABLE_ACTIONS` | `actions.enabled` | boolean | `false` | Master switch for node writes. |
|
|
407
|
+
| `BLOCKYARD_ACTIONS` | `actions.allow` | list | *(empty)* | Actions to enable, for example `testmempoolaccept,savemempool`. |
|
|
408
|
+
| `BLOCKYARD_ALLOW_WRITES_WITHOUT_AUTH` | `actions.allowWritesWithoutAuth` | boolean | `false` | Permit actions while accounts are off. |
|
|
409
|
+
| `BLOCKYARD_LOG_SOURCE` | `log.enabled` | boolean | `false` | `1` tails node log files; `0` (or unset) runs on RPC alone. |
|
|
410
|
+
| `BLOCKYARD_LOG_LEVEL` | `log.level` | string | `info` | `debug`, `info`, `warn` or `error`. |
|
|
411
|
+
| `BLOCKYARD_MARKETS` | `markets.enabled` | boolean | `true` | `0` removes the Markets feed; the polling checkbox then cannot turn it on. |
|
|
412
|
+
| `BLOCKYARD_MINING` | *(none)* | `0` or anything | on | `0` turns off miner attribution, which decodes each block's coinbase to show the pool tag. Block sizes, fees and weights are unaffected. Only the literal `0` disables it. |
|
|
413
|
+
| `BLOCKYARD_MINING_BACKFILL` | *(none)* | number | `36` | How many recent blocks are attributed to miners at startup. |
|
|
414
|
+
| `BLOCKYARD_MINING_TEMPLATE` | *(none)* | `0` or anything | on | `0` turns off the "block being built" card. Since 2026-09-13 the template is **assembled from the mempool this monitor already reads**, so leaving it on costs your node no RPC call at all — it costs this process ~50-70 ms of CPU per assembly. Before that it was a `getblocktemplate` worth over a second of the node's single RPC thread, which is why the switch exists. Only the literal `0` disables it. |
|
|
415
|
+
| `BLOCKYARD_POOL_MAP` | *(none)* | path | `<store.dir>/pool-map.json` if it exists, else `<repo>/config/pool-map.json` | Pool label map to load. It overrides both the shipped map and the one in the data directory (see [The data directory](#the-data-directory)). |
|
|
416
|
+
| `BLOCKYARD_FAKE_NODE` | *(none)* | boolean | `false` | Development mode: start a built-in simulated node and monitor **only** that. Every configured node is replaced. Never set this in production. |
|
|
417
|
+
| `FAKE_PORT` | *(none)* | number | `18331` | Port of the simulated node, with `BLOCKYARD_FAKE_NODE`. It is also used by `npm run fake-node`. |
|
|
418
|
+
| `FAKE_IBD` | *(none)* | `0` or anything | on | `0` starts the simulated node already synced instead of in initial block download. |
|
|
419
|
+
| `FAKE_RATE` | *(none)* | number | `9` | Blocks per second the simulated node catches up while "syncing". |
|
|
420
|
+
| `BLOCKYARD_LEDGER_ENGINE` | *(none)* | `sqlite` or `jsonl` | `sqlite` | Storage engine for the mining ledger module (`node:sqlite` with an append-only-file fallback). The running server does not currently open a ledger, so this has no effect today. |
|
|
421
|
+
|
|
422
|
+
### Development and tooling variables
|
|
423
|
+
|
|
424
|
+
These are read only by scripts under `scripts/`, never by the server.
|
|
425
|
+
|
|
426
|
+
| variable | used by | meaning |
|
|
427
|
+
|---|---|---|
|
|
428
|
+
| `FAKE_LOG` | `scripts/fake-node.js` (standalone) | Log file the standalone simulated node writes. Default `/tmp/blockyard-fake/bitcoin.main.log`. |
|
|
429
|
+
| `BLOCKYARD_SMOKE_PORT`, `BLOCKYARD_SMOKE_FAKE` | `scripts/smoke.sh` | Ports for the smoke run's monitor (default `18099`) and its simulated node (default `18461`). |
|
|
430
|
+
| `BLOCKYARD_CA_FILE` | `scripts/pool-map.js` | CA certificate used for the optional `--check` against a TLS-serving monitor. It has a built-in default path; see `scripts/pool-map.js`. |
|
|
431
|
+
| `BLOCKYARD_BASE` | browser and render check scripts | Base URL of the monitor to test. |
|
|
432
|
+
| `BLOCKYARD_CA` | `live-render-check.mjs`, `motion-check.mjs` | CA certificate for a TLS-serving monitor. |
|
|
433
|
+
| `BROWSER_CDP` | browser probe scripts | Chrome DevTools Protocol endpoint of the browser to drive. |
|
|
434
|
+
| `MOTION_OUT` | `motion-check.mjs` | Output PNG path. |
|
|
435
|
+
| `PROBE_ID`, `PROBE_GAP` | canvas probe scripts | Element id to probe, and the gap between samples. |
|
|
436
|
+
| `VISION_BASE`, `VISION_MODEL` | `browser-check.mjs` | Optional vision-model endpoint and model name for screenshot review. |
|
|
437
|
+
|
|
438
|
+
### npm scripts
|
|
439
|
+
|
|
440
|
+
| command | what it runs |
|
|
441
|
+
|---|---|
|
|
442
|
+
| `npm start` | `node server/main.js`, with your configuration |
|
|
443
|
+
| `npm run dev` | The server with `BLOCKYARD_CONFIG=none BLOCKYARD_BIND=127.0.0.1 BLOCKYARD_PORT=18088 BLOCKYARD_FAKE_NODE=1`: a self-contained development run on `http://127.0.0.1:18088` against a simulated node |
|
|
444
|
+
| `npm run fake-node` | The simulated node on its own |
|
|
445
|
+
| `npm run user -- <command>` | Account administration (see [auth](#auth)) |
|
|
446
|
+
| `npm run setup` | The installer (`scripts/setup.js`): reads the node's `bitcoin.conf` for the chain, RPC port and credentials, checks the node, writes `config/local.json`, and either builds the address index or leaves it to the server's first start (`--build-later` writes `addressIndexBuild: "manual"`; `--workers N` is written as `addressIndexWorkers`) |
|
|
447
|
+
| `npm run check` | The same checks against every configured node (`scripts/check.js`), each RPC call timed; exit 1 on a failure |
|
|
448
|
+
| `npm test` | The test suite |
|
|
449
|
+
|
|
450
|
+
---
|
|
451
|
+
|
|
452
|
+
## Examples
|
|
453
|
+
|
|
454
|
+
All examples are complete `config/local.json` files. Addresses are documentation
|
|
455
|
+
addresses; substitute your own.
|
|
456
|
+
|
|
457
|
+
### A single node on the same machine
|
|
458
|
+
|
|
459
|
+
The usual case: the node runs on this machine, and BlockYard reads its cookie from
|
|
460
|
+
the data directory. The node here keeps its data in `/var/lib/bitcoind`, with the
|
|
461
|
+
mainnet cookie at `/var/lib/bitcoind/main/.cookie`:
|
|
462
|
+
|
|
463
|
+
```json
|
|
464
|
+
{
|
|
465
|
+
"nodes": [
|
|
466
|
+
{
|
|
467
|
+
"id": "main",
|
|
468
|
+
"label": "mainnet",
|
|
469
|
+
"rpcUrl": "http://127.0.0.1:8332",
|
|
470
|
+
"datadir": "/var/lib/bitcoind",
|
|
471
|
+
"chainHint": "main"
|
|
472
|
+
}
|
|
473
|
+
]
|
|
474
|
+
}
|
|
475
|
+
```
|
|
476
|
+
|
|
477
|
+
The same thing without a file, using only the environment:
|
|
478
|
+
|
|
479
|
+
```bash
|
|
480
|
+
BLOCKYARD_NODE_URL=http://127.0.0.1:8332 BLOCKYARD_DATADIR=/var/lib/bitcoind npm start
|
|
481
|
+
```
|
|
482
|
+
|
|
483
|
+
Without `chainHint`, the monitor tries `<datadir>/.cookie` and then every
|
|
484
|
+
subdirectory, so the cookie is usually found anyway. Setting `chainHint` makes the
|
|
485
|
+
choice explicit when several chains share one data directory.
|
|
486
|
+
|
|
487
|
+
### A node with a username and password (`rpcauth`)
|
|
488
|
+
|
|
489
|
+
For a node that authenticates with `rpcuser`/`rpcpassword` or `rpcauth` instead of the cookie
|
|
490
|
+
file:
|
|
491
|
+
|
|
492
|
+
```json
|
|
493
|
+
{
|
|
494
|
+
"nodes": [
|
|
495
|
+
{
|
|
496
|
+
"id": "main",
|
|
497
|
+
"label": "My node",
|
|
498
|
+
"rpcUrl": "http://127.0.0.1:8332",
|
|
499
|
+
"datadir": "/home/you/.bitcoin",
|
|
500
|
+
"rpcUser": "monitor",
|
|
501
|
+
"rpcPassword": "a-long-random-secret"
|
|
502
|
+
}
|
|
503
|
+
]
|
|
504
|
+
}
|
|
505
|
+
```
|
|
506
|
+
|
|
507
|
+
Keep `datadir`: the explorer's address index is built from the block files under it, and the
|
|
508
|
+
credential lookup only falls through to `rpcUser`/`rpcPassword` when no cookie is readable there.
|
|
509
|
+
The RPC connection stays on this machine (`127.0.0.1`), so the password never crosses a network.
|
|
510
|
+
|
|
511
|
+
### The address index
|
|
512
|
+
|
|
513
|
+
What `npm run setup` writes for a node whose address index the server should build on its first
|
|
514
|
+
start, with two worker threads (a spinning disk would want one):
|
|
515
|
+
|
|
516
|
+
```json
|
|
517
|
+
{
|
|
518
|
+
"nodes": [
|
|
519
|
+
{
|
|
520
|
+
"id": "main",
|
|
521
|
+
"label": "mainnet",
|
|
522
|
+
"rpcUrl": "http://127.0.0.1:8332",
|
|
523
|
+
"datadir": "/var/lib/bitcoind",
|
|
524
|
+
"chainHint": "main",
|
|
525
|
+
"addressIndex": "/var/lib/blockyard/index",
|
|
526
|
+
"addressIndexWorkers": 2
|
|
527
|
+
}
|
|
528
|
+
]
|
|
529
|
+
}
|
|
530
|
+
```
|
|
531
|
+
|
|
532
|
+
Add `"addressIndexBuild": "manual"` to build it yourself instead:
|
|
533
|
+
`node scripts/index-build.js --out /var/lib/blockyard/index --workers 16`, then restart. The
|
|
534
|
+
directory must be writable by the service account either way, because the follower writes into it.
|
|
535
|
+
|
|
536
|
+
### Two nodes
|
|
537
|
+
|
|
538
|
+
Listing more than one node turns on the node picker. The second node here is
|
|
539
|
+
marked `optional`, so when it is down the monitor reports it at a lower severity and
|
|
540
|
+
does not count it against overall health:
|
|
541
|
+
|
|
542
|
+
```json
|
|
543
|
+
{
|
|
544
|
+
"nodes": [
|
|
545
|
+
{
|
|
546
|
+
"id": "main",
|
|
547
|
+
"label": "mainnet",
|
|
548
|
+
"rpcUrl": "http://127.0.0.1:8332",
|
|
549
|
+
"datadir": "/var/lib/bitcoind",
|
|
550
|
+
"chainHint": "main"
|
|
551
|
+
},
|
|
552
|
+
{
|
|
553
|
+
"id": "test",
|
|
554
|
+
"label": "test node",
|
|
555
|
+
"rpcUrl": "http://127.0.0.1:18332",
|
|
556
|
+
"datadir": "/srv/bitcoind-test/data",
|
|
557
|
+
"chainHint": "main",
|
|
558
|
+
"color": "#4aa3df",
|
|
559
|
+
"optional": true
|
|
560
|
+
}
|
|
561
|
+
]
|
|
562
|
+
}
|
|
563
|
+
```
|
|
564
|
+
|
|
565
|
+
Every RPC limit in [`rpc`](#rpc) applies to each node separately. Two nodes on one
|
|
566
|
+
machine are two sets of polling, so avoid monitoring a node that is running a
|
|
567
|
+
benchmark.
|
|
568
|
+
|
|
569
|
+
### Accounts on
|
|
570
|
+
|
|
571
|
+
```json
|
|
572
|
+
{
|
|
573
|
+
"auth": { "enabled": true }
|
|
574
|
+
}
|
|
575
|
+
```
|
|
576
|
+
|
|
577
|
+
On first boot the banner prints the generated `admin` password once. To choose it
|
|
578
|
+
instead:
|
|
579
|
+
|
|
580
|
+
```bash
|
|
581
|
+
BLOCKYARD_ADMIN_PASSWORD='choose-a-long-passphrase' npm start
|
|
582
|
+
```
|
|
583
|
+
|
|
584
|
+
Then create personal accounts with `npm run user -- create <name> <role>`. HTTPS is on by
|
|
585
|
+
default with the monitor's own certificate; if you turn it off (`BLOCKYARD_TLS=0`) on a bind
|
|
586
|
+
that crosses the LAN, the startup log warns that the session cookie crosses the network in
|
|
587
|
+
clear text. Pair that with a TLS-terminating proxy, or with a loopback bind and an SSH tunnel.
|
|
588
|
+
|
|
589
|
+
Accounts plus two node writes that operators may run:
|
|
590
|
+
|
|
591
|
+
```json
|
|
592
|
+
{
|
|
593
|
+
"auth": { "enabled": true },
|
|
594
|
+
"actions": {
|
|
595
|
+
"enabled": true,
|
|
596
|
+
"allow": ["testmempoolaccept", "savemempool"]
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
```
|
|
600
|
+
|
|
601
|
+
### TLS
|
|
602
|
+
|
|
603
|
+
HTTPS is on by default, with a certificate the server makes for itself. A certificate of your
|
|
604
|
+
own, on every listener:
|
|
605
|
+
|
|
606
|
+
```json
|
|
607
|
+
{
|
|
608
|
+
"server": {
|
|
609
|
+
"tls": {
|
|
610
|
+
"cert": "/etc/blockyard/cert.pem",
|
|
611
|
+
"key": "/etc/blockyard/key.pem"
|
|
612
|
+
}
|
|
613
|
+
},
|
|
614
|
+
"auth": { "enabled": true }
|
|
615
|
+
}
|
|
616
|
+
```
|
|
617
|
+
|
|
618
|
+
Both files must be readable by the account running blockyard. The certificate
|
|
619
|
+
must be valid PEM X.509 and not expired. A self-signed certificate works, but the
|
|
620
|
+
browser warns on first visit to each address.
|
|
621
|
+
|
|
622
|
+
Behind a reverse proxy that terminates TLS (nginx, Caddy, …), serve plain HTTP on
|
|
623
|
+
loopback only and let the proxy own the certificate:
|
|
624
|
+
|
|
625
|
+
```json
|
|
626
|
+
{
|
|
627
|
+
"server": {
|
|
628
|
+
"hosts": ["127.0.0.1"],
|
|
629
|
+
"trustProxy": true
|
|
630
|
+
},
|
|
631
|
+
"auth": { "enabled": true, "secureCookie": true }
|
|
632
|
+
}
|
|
633
|
+
```
|
|
634
|
+
|
|
635
|
+
Only set `trustProxy` if the proxy overwrites `X-Forwarded-For`. The loopback bind
|
|
636
|
+
matters here too: it makes the proxy the only way in.
|
|
637
|
+
|
|
638
|
+
### Binding to specific addresses
|
|
639
|
+
|
|
640
|
+
The default `127.0.0.1` answers this machine only. `0.0.0.0` listens on every IPv4
|
|
641
|
+
interface the machine has, including VPN tunnels and container bridges. To serve exactly
|
|
642
|
+
one LAN address and one VPN address, and admit only clients from those networks:
|
|
643
|
+
|
|
644
|
+
```json
|
|
645
|
+
{
|
|
646
|
+
"server": {
|
|
647
|
+
"hosts": ["192.0.2.10", "2001:db8::10"],
|
|
648
|
+
"port": 21000,
|
|
649
|
+
"allowCidrs": ["192.0.2.0/24", "2001:db8::/32"]
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
```
|
|
653
|
+
|
|
654
|
+
Things to know about specific binds:
|
|
655
|
+
|
|
656
|
+
- `127.0.0.1` stops working on the host itself unless you list it. From the host,
|
|
657
|
+
use one of the listed addresses. The startup log names the interfaces left
|
|
658
|
+
unserved.
|
|
659
|
+
- An address the machine does not have at startup, such as a tunnel interface that
|
|
660
|
+
comes up later, is **skipped with a warning**. The server fails only when **none**
|
|
661
|
+
of the listed addresses exist. Under systemd, order the unit after the tunnel's
|
|
662
|
+
service if you need that address from the first second.
|
|
663
|
+
- Binding picks a destination address, not an ingress interface. Traffic that can
|
|
664
|
+
be routed to a bound address still reaches it. If "LAN only" must be enforced,
|
|
665
|
+
use a firewall or `allowCidrs`.
|
|
666
|
+
- For this machine only, use `"hosts": ["127.0.0.1"]` and reach it with
|
|
667
|
+
`ssh -L 21000:127.0.0.1:21000 user@monitor-host`.
|
|
668
|
+
|
|
669
|
+
### Markets off, for good
|
|
670
|
+
|
|
671
|
+
Out of the box there are no outbound connections except to the node, because market polling is
|
|
672
|
+
a checkbox that ships unticked (**Display settings → Markets & Price → Enable market polling**). To be certain a machine never reaches out whatever anyone
|
|
673
|
+
ticks, remove the feed from the server:
|
|
674
|
+
|
|
675
|
+
```json
|
|
676
|
+
{
|
|
677
|
+
"markets": { "enabled": false }
|
|
678
|
+
}
|
|
679
|
+
```
|
|
680
|
+
|
|
681
|
+
or `BLOCKYARD_MARKETS=0`.
|
|
682
|
+
|
|
683
|
+
### RPC-only mode, and the log source
|
|
684
|
+
|
|
685
|
+
RPC-only is the **default and the supported mode**: `log.enabled` is `false`, and every
|
|
686
|
+
node's `logFile` is ignored. To make it explicit, and to survive a future default change:
|
|
687
|
+
|
|
688
|
+
```json
|
|
689
|
+
{
|
|
690
|
+
"log": { "enabled": false }
|
|
691
|
+
}
|
|
692
|
+
```
|
|
693
|
+
|
|
694
|
+
or `BLOCKYARD_LOG_SOURCE=0`.
|
|
695
|
+
|
|
696
|
+
> **Log parsing does not currently support Bitcoin Core.** The parsers were written and
|
|
697
|
+
> measured against an experimental node implementation with a different log grammar, and
|
|
698
|
+
> that is the only format they have been tested against. Measured 2026-09-13 against real
|
|
699
|
+
> Core `debug.log` lines: every line comes back as an unstructured `raw` event with **no
|
|
700
|
+
> figures extracted**, and the timestamp falls back to the time of reading rather than the
|
|
701
|
+
> time in the line -- so enabling it against Core adds nothing and misdates the event feed.
|
|
702
|
+
>
|
|
703
|
+
> Nothing in the UI depends on it: every panel reads RPC. Support for Core's log format
|
|
704
|
+
> would mean new parser rules and Core fixtures, not a configuration change.
|
|
705
|
+
|
|
706
|
+
### Running under systemd
|
|
707
|
+
|
|
708
|
+
`systemd/blockyard.service` is a template. Edit `User=`, `Group=`,
|
|
709
|
+
`WorkingDirectory=` and the path to a Node.js 22+ binary before installing it.
|
|
710
|
+
|
|
711
|
+
**The shipped unit sets `Environment=` lines for `BLOCKYARD_PORT`, `BLOCKYARD_NODE_URL`,
|
|
712
|
+
`BLOCKYARD_DATADIR`, `BLOCKYARD_LOGFILE` and `BLOCKYARD_LOG_LEVEL`.** Environment
|
|
713
|
+
variables beat `config/local.json`, so those lines override the first node in your
|
|
714
|
+
file. Either edit them to match, or delete them and keep everything in
|
|
715
|
+
`config/local.json`. To add settings without editing the unit, use a drop-in:
|
|
716
|
+
|
|
717
|
+
```bash
|
|
718
|
+
sudo systemctl edit blockyard
|
|
719
|
+
```
|
|
720
|
+
|
|
721
|
+
```ini
|
|
722
|
+
[Service]
|
|
723
|
+
Environment=BLOCKYARD_BIND=192.0.2.10
|
|
724
|
+
Environment=BLOCKYARD_DATA=/var/lib/blockyard
|
|
725
|
+
```
|
|
726
|
+
|
|
727
|
+
The service account needs read access to the node's cookie file (and log file, if
|
|
728
|
+
used), write access to the data directory, and write access to `config/` if the Node
|
|
729
|
+
connection form or Display settings are to be saved from the web UI.
|
|
730
|
+
|
|
731
|
+
---
|
|
732
|
+
|
|
733
|
+
## The data directory
|
|
734
|
+
|
|
735
|
+
`store.dir` (`BLOCKYARD_DATA`, default `<repo>/data`) holds runtime state. The server
|
|
736
|
+
creates it at startup. The account files live in `auth.dataDir`, which defaults to
|
|
737
|
+
the same directory. `<repo>/data/` is in `.gitignore`. Back it up if you care about
|
|
738
|
+
accounts and chart history, and never commit or publish it.
|
|
739
|
+
|
|
740
|
+
| file | written by | secret? | contents |
|
|
741
|
+
|---|---|---|---|
|
|
742
|
+
| `users.json` | the server (first boot) and `npm run user` | **yes** | Accounts: usernames, roles, scrypt hashes and salts. Mode `0600`. Only in `auth.dataDir`. |
|
|
743
|
+
| `sessions.json` | the server | **yes** | Active sessions, stored as token hashes, with creation time, last use, client address and user agent. Mode `0600`. Only in `auth.dataDir`. |
|
|
744
|
+
| `audit.jsonl` | the server | sensitive | Append-only log of sign-ins, RPC console calls, actions (allowed and denied) and account changes, with usernames and client addresses. Passwords and RPC credentials are removed before writing. |
|
|
745
|
+
| `audit.1.jsonl` … `audit.N.jsonl` | the server | sensitive | Rotated audit files, newest first. There are at most `store.auditKeep` of them, and rotation happens at `store.auditMaxBytes`. |
|
|
746
|
+
| `history.json` | the server | no, but reveals node details | Snapshot of chart series, events and recent blocks, restored on startup and pruned to `store.retentionHours`. Can be tens of MB. |
|
|
747
|
+
| `pool-aliases.json` | **you**, optionally | no | Hand-written display names for mining pools: a JSON object from pool key to label, for example `{"examplepool": "Example Pool"}`. The pool key is the lowercased coinbase tag the Mining page shows, or `unknown:<hex>` for blocks without a readable tag. Absent by default, in which case the coinbase text is shown as written. |
|
|
748
|
+
| `pool-map.json` | `node scripts/pool-map.js` | no | Coinbase-tag-to-pool-name map built from the public mempool/mining-pools data set (MIT), with source URL and content hash. **A copy ships in `config/pool-map.json`** (151 pools), so pools are labelled from the first start; one here, written by the script (which needs network access), **overrides** the shipped copy. The server reads `BLOCKYARD_POOL_MAP` if set, else `<store.dir>/pool-map.json` if it exists, else the shipped file. The script always writes `<repo>/data/pool-map.json`, so move it, or set `BLOCKYARD_POOL_MAP`, if you use a different `store.dir`. `--file <pools-v2.json>` builds it offline. |
|
|
749
|
+
| `fake-node.log` | development mode only | no | Log of the simulated node when `BLOCKYARD_FAKE_NODE=1`. |
|
|
750
|
+
| `*.tmp` | the server | as the target file | Short-lived files from atomic writes (write, fsync, rename). A leftover one after a crash is safe to delete. |
|
|
751
|
+
|
|
752
|
+
Other secrets outside the data directory:
|
|
753
|
+
|
|
754
|
+
- `config/local.json`, if it contains `rpcPassword`.
|
|
755
|
+
- The TLS private key named by `server.tls.key`.
|
|
756
|
+
- The node's own cookie file, which BlockYard reads but never copies or logs.
|
|
757
|
+
|
|
758
|
+
The server's own log (stdout, or the journal under systemd) masks credentials that
|
|
759
|
+
look like passwords, cookies or URL passwords. The banner line that prints a
|
|
760
|
+
generated first-admin password is deliberate and appears once.
|
|
761
|
+
|
|
762
|
+
---
|
|
763
|
+
|
|
764
|
+
## Validation: what stops the boot
|
|
765
|
+
|
|
766
|
+
The configuration is checked once, after merging all three layers. Every problem
|
|
767
|
+
found is reported together, and the process exits with:
|
|
768
|
+
|
|
769
|
+
```
|
|
770
|
+
Error: Invalid configuration:
|
|
771
|
+
- <problem>
|
|
772
|
+
- <problem>
|
|
773
|
+
```
|
|
774
|
+
|
|
775
|
+
| mistake | message |
|
|
776
|
+
|---|---|
|
|
777
|
+
| The configuration file is not valid JSON | `config: cannot parse <file>: <parser message>` (reported on its own, before any other check) |
|
|
778
|
+
| `nodes` is empty or not an array | `nodes must be non-empty` |
|
|
779
|
+
| Port missing, not an integer, or out of range (including a non-numeric `BLOCKYARD_PORT`) | `server.port invalid` |
|
|
780
|
+
| Empty host list | `server.hosts is empty; nothing would be served` |
|
|
781
|
+
| A hostname in the host list | `server.hosts entry "<name>" is not an address literal; use an IPv4/IPv6 address, 0.0.0.0, or localhost` |
|
|
782
|
+
| None of the listed addresses exists on this machine | `none of the configured bind addresses (<list>) exist on this machine; refusing to start with nothing to serve` |
|
|
783
|
+
| `rpc.maxInFlight` below 1 | `rpc.maxInFlight must be >= 1` |
|
|
784
|
+
| `poll.fastMs` below 1000 | `poll.fastMs below 1s risks hammering a single-threaded RPC server` |
|
|
785
|
+
| An unparseable `allowCidrs` entry | `server.allowCidrs entry "<entry>" is unusable: <reason>`. The reason is, for example, `"<x>" is not an IPv4 or IPv6 address` or `/33 is wider than an ipv4 address (32 bits)` |
|
|
786
|
+
| Only one of `tls.cert` / `tls.key` set | `server.tls needs BOTH cert and key (got cert only)` (or `key only`) `; a half-configured TLS would fall back to plaintext on a port you believe is HTTPS` |
|
|
787
|
+
| A TLS file cannot be read | `server.tls.cert cannot be read (<path>: <error code>)`, and likewise for `key` |
|
|
788
|
+
| The certificate is not X.509 PEM | `server.tls.cert is not a parseable X.509 certificate: <details>` |
|
|
789
|
+
| The certificate has no usable validity dates | `server.tls.cert has no parseable validity window` |
|
|
790
|
+
| The certificate has expired | `server.tls.cert expired <date>; the browser will refuse the connection, and the dashboard cannot tell you so from behind that refusal` |
|
|
791
|
+
| Actions enabled while accounts are off, without the explicit override | `node actions are enabled while accounts are OFF, which would let any address that can reach the port call them (there is no role to check). Either set BLOCKYARD_AUTH=1, or set BLOCKYARD_ALLOW_WRITES_WITHOUT_AUTH=1 deliberately alongside BLOCKYARD_ACTIONS.` |
|
|
792
|
+
| `store.blockMapCap` not an integer of at least 100 | `store.blockMapCap must be an integer >= 100; …` |
|
|
793
|
+
| `store.auditMaxBytes` below 65536 | `store.auditMaxBytes must be >= 65536; below that the audit rotates on every write` |
|
|
794
|
+
| A node's `rpcUrl` missing or not `http(s)://` | `node <id>: rpcUrl must be http(s)://host:port` |
|
|
795
|
+
| A node with neither a cookie path (`datadir` / `cookieFile`) nor `rpcUser` + `rpcPassword` | `node <id>: needs either datadir/cookieFile (cookie auth, same machine) or rpcUser + rpcPassword (a node authenticating with rpcauth)` |
|
|
796
|
+
| `rpcUser` set without `rpcPassword` | `node <id>: rpcUser is set but rpcPassword is empty` |
|
|
797
|
+
|
|
798
|
+
Failures after validation, while the server starts:
|
|
799
|
+
|
|
800
|
+
| situation | what happens |
|
|
801
|
+
|---|---|
|
|
802
|
+
| A listed address is taken, or the port is privileged | Exit 1. The log explains the cause: already in use (probably a second instance), permission denied (ports below 1024), or no such address, and lists the addresses the machine has. |
|
|
803
|
+
| `BLOCKYARD_ADMIN_PASSWORD` fails the password rules on first boot | The boot throws with the rule it broke, for example `password must be at least 12 characters` or `password must not contain the username`. |
|
|
804
|
+
| `BLOCKYARD_FAKE_NODE=1` and the simulated node cannot start (for example, `FAKE_PORT` in use) | `dev mode needs a local fake node: <error>` |
|
|
805
|
+
|
|
806
|
+
Reported but **not** fatal:
|
|
807
|
+
|
|
808
|
+
- A listed bind address that the machine does not currently have is skipped with a
|
|
809
|
+
warning, as long as at least one address works.
|
|
810
|
+
- A certificate expiring within 14 days produces a warning at startup.
|
|
811
|
+
- A node whose `datadir` does not exist (and has no `cookieFile`) is skipped.
|
|
812
|
+
- Accounts off: a warning names the addresses that anyone can read.
|
|
813
|
+
- Accounts on without TLS: a warning that the session cookie travels in clear text.
|
|
814
|
+
- A node `logFile` while the log source is off: an info line says it is ignored.
|
|
815
|
+
- A node that cannot be reached or authenticated is **not** a configuration error.
|
|
816
|
+
The server starts, shows the node as offline, and keeps retrying. An
|
|
817
|
+
authentication failure is reported as `RPC 401 and no credential found (checked
|
|
818
|
+
cookie paths)` or `RPC authentication failed (cookie rejected)`.
|
|
819
|
+
|
|
820
|
+
---
|
|
821
|
+
|
|
822
|
+
## Known quirks
|
|
823
|
+
|
|
824
|
+
These are current behaviours of the code that a configuration author should know
|
|
825
|
+
about.
|
|
826
|
+
|
|
827
|
+
- **`rpcUser`/`rpcPassword` alone are fine** since 2026-09-13. They used to fail validation,
|
|
828
|
+
and the documented workaround was to point `cookieFile` at a path that does not exist. That is
|
|
829
|
+
no longer needed: a node authenticated by username and password needs no `datadir` and no
|
|
830
|
+
`cookieFile`. If you copied the old workaround, it still works -- but you can delete the line.
|
|
831
|
+
- **`server.hosts` in the file beats `BLOCKYARD_BIND`/`BLOCKYARD_HOST`.** The
|
|
832
|
+
environment variables set `server.host`, but a `server.hosts` array takes
|
|
833
|
+
precedence over `server.host`. If your file uses `hosts`, change the bind there,
|
|
834
|
+
or run with `BLOCKYARD_CONFIG=none`.
|
|
835
|
+
- **Node environment variables change only `nodes[0]`.** With several nodes in the
|
|
836
|
+
file, `BLOCKYARD_NODE_URL` and friends rewrite the first one and leave the rest
|
|
837
|
+
alone. The shipped systemd unit sets several of them.
|
|
838
|
+
- **`auth.loginMaxAttempts` and `auth.loginWindowMs` are not read.** The login guard
|
|
839
|
+
takes its limits from keys named `maxAttempts` and `windowMs` and otherwise falls
|
|
840
|
+
back to 8 attempts per 5 minutes. Those are the same numbers as the documented
|
|
841
|
+
defaults, so nothing changes unless you try to change them. `auth.lockoutMs` is
|
|
842
|
+
read.
|
|
843
|
+
- **`actions.requireAdmin` is not consulted.** Each action's minimum role is fixed in
|
|
844
|
+
code (see [actions](#actions)).
|
|
845
|
+
- **`systemdUnit` is informational.** The server does not query or control systemd.
|
|
846
|
+
- **A missing `BLOCKYARD_CONFIG` file is silent.** A path that does not exist gives you
|
|
847
|
+
the defaults without an error.
|