blockyard 0.0.9 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/CHANGELOG.md +251 -1
  2. package/README.md +42 -23
  3. package/bin/blockyard.js +2 -1
  4. package/docs/API.md +16 -14
  5. package/docs/ARCHITECTURE.md +92 -5
  6. package/docs/CONFIGURATION.md +33 -26
  7. package/docs/GETTING-STARTED.md +5 -2
  8. package/docs/INSTALL.md +90 -33
  9. package/docs/MEASUREMENTS.md +147 -0
  10. package/docs/SECURITY.md +32 -15
  11. package/docs/TROUBLESHOOTING.md +35 -1
  12. package/docs/USER-GUIDE.md +266 -26
  13. package/package.json +1 -1
  14. package/public/404.html +1 -1
  15. package/public/css/app.css +306 -82
  16. package/public/donate-qr.png +0 -0
  17. package/public/index.html +295 -103
  18. package/public/js/agents.js +228 -51
  19. package/public/js/app.js +82 -8
  20. package/public/js/blockscene3d.js +179 -27
  21. package/public/js/charts.js +21 -21
  22. package/public/js/depthchart.js +31 -27
  23. package/public/js/details3d.js +1456 -71
  24. package/public/js/doom.js +31 -0
  25. package/public/js/dosaudio.js +48 -0
  26. package/public/js/dosgame.js +389 -0
  27. package/public/js/dosio.js +186 -0
  28. package/public/js/dospc.js +1353 -0
  29. package/public/js/dosworker.js +196 -0
  30. package/public/js/login.js +5 -0
  31. package/public/js/markets.js +46 -8
  32. package/public/js/mining.js +310 -32
  33. package/public/js/panels.js +14 -10
  34. package/public/js/pricechart.js +14 -13
  35. package/public/js/quake.js +20 -0
  36. package/public/js/settings.js +103 -21
  37. package/public/js/soundcard.js +459 -0
  38. package/public/js/theme.js +235 -0
  39. package/public/js/wolf3d.js +22 -0
  40. package/public/js/x86.js +1978 -0
  41. package/scripts/donate-qr.py +12 -9
  42. package/scripts/dos-bench.js +56 -0
  43. package/scripts/setup.js +34 -12
  44. package/scripts/shots.mjs +6 -0
  45. package/scripts/smoke.sh +1 -1
  46. package/scripts/tls.js +31 -0
  47. package/server/chain/index/build.js +21 -4
  48. package/server/collect/monitor.js +30 -1
  49. package/server/collect/network.js +295 -0
  50. package/server/config.js +46 -22
  51. package/server/http/api.js +49 -5
  52. package/server/http/games.js +77 -0
  53. package/server/http/server.js +8 -0
  54. package/server/main.js +53 -8
  55. package/server/tls/selfsigned.js +160 -0
  56. package/systemd/blockyard.service +7 -5
  57. package/docs/PRIVATE-LEADERBOARD.md +0 -230
  58. package/docs/STATE-2026-09-09.md +0 -200
@@ -192,8 +192,9 @@ it directly; running the file as a script calls it and prints the banner.
192
192
  missing is skipped with a logged reason rather than kept as a permanently
193
193
  offline panel. `wireMonitor()` connects each monitor's events to the SSE hub
194
194
  and coalesces pushes to one snapshot per second.
195
- 8. `MarketFeed` is created if `markets.enabled` is set. It stays idle until the
196
- Markets API is requested.
195
+ 8. `MarketFeed` is created if `markets.enabled` is set. It stays idle until the Markets
196
+ API is requested with the **Enable market polling** setting on (off by default; read from
197
+ the shared settings file per request, `marketsPollingOn` in `http/api.js`).
197
198
  9. One HTTP(S) server is created per bound address, all sharing the same `app`.
198
199
  An address missing at boot is skipped with a warning. Boot is fatal only when
199
200
  none of the configured addresses exist.
@@ -346,7 +347,9 @@ Bitstamp, Bitfinex and OKX.
346
347
  with its error, never zero-filled. Tickers older than three intervals are marked
347
348
  `stale`.
348
349
 
349
- `BLOCKYARD_MARKETS=0` (or `markets.enabled: false`) disables all of it.
350
+ Polling is off by default the **Enable market polling** Display setting turns it on, and a
351
+ request with it off parks the feed. `BLOCKYARD_MARKETS=0` (or `markets.enabled: false`)
352
+ removes the feed altogether.
350
353
 
351
354
  ### 2.4 The RPC lane (`server/rpc/client.js`)
352
355
 
@@ -523,11 +526,12 @@ flowchart LR
523
526
 
524
527
  ### 2.7 Auth (`server/auth/*`)
525
528
 
526
- - **Open by default.** With `auth.enabled: false` (the default), every request is
529
+ - **Sign-in by default** (since 2026-09-15; it shipped open in 0.0.9), bound to
530
+ `127.0.0.1`. With `auth.enabled: false` (`BLOCKYARD_AUTH=0`), every request is
527
531
  served as a frozen anonymous user with role `viewer`. That ceiling is hardcoded,
528
532
  not configurable. Admin routes return 403 in both modes, CSRF is not needed
529
533
  because there is no cookie to ride, and rate limits apply per client address.
530
- - **Accounts.** `BLOCKYARD_AUTH=1` turns on:
534
+ - **Accounts** (the default; `BLOCKYARD_AUTH=1` restores them after an override):
531
535
  - scrypt password hashes (upgraded to current parameters on login)
532
536
  - session tokens that are stored hashed, with idle and absolute TTLs
533
537
  - double-submit CSRF (the token in a header or body is compared with the
@@ -659,6 +663,7 @@ loaded from the same origin. There is no build step and no framework.
659
663
  | `settings.js` | display settings: `DEFAULTS`, the `PANEL` rows of the settings dialog, `normalise()`, and the option builders (`spaceOptions`, `enabledEffects`, ...) the boards read; stored on the server (`/api/settings`) with a `localStorage` copy |
660
664
  | `about.js` | the About page (version, system and node info) |
661
665
  | `tetris.js` / `tetrust.js`, `breakout.js` / `blockout.js`, `arkanoid.js` / `blockanoid.js`, `tetsound.js` | the Diversions: pure game rules in the first file of each pair, the tab drawn on the 3D engine in the second, and Tetrust's sound |
666
+ | `x86.js`, `dospc.js`, `soundcard.js`, `dosworker.js`, `dosaudio.js`, `dosio.js`, `dosgame.js`, `doom.js`, `quake.js` | the DOS Diversions: an i386 interpreter, the PC around it (DOS/4GW for DOOM, the go32 stub and CWSDPMI for Quake), a Sound Blaster Pro 2 with an OPL3, the worker the machine runs in, the AudioWorklet it plays through, the pure keyboard/config/text-mode helpers, the shared tab, and each game's own few lines (section 3.4) |
662
667
  | `fmt.js` | formatters: decimal units (as the node prints them), `–` for anything absent |
663
668
  | `login.js` | the login page (a separate file because of the CSP) |
664
669
 
@@ -790,6 +795,88 @@ What this means for front-end code:
790
795
 
791
796
  ---
792
797
 
798
+ ### 3.4 The DOS Diversions: Wolfenstein 3D, DOOM and Quake
799
+
800
+ The shareware `WOLF3D.EXE` v1.4, `DOOM.EXE` v1.9 and `QUAKE.EXE` v1.06 run unmodified on a PC emulated in
801
+ the browser.
802
+ Nothing is ported and no dependency is used; every layer is this repository's own:
803
+
804
+ ```
805
+ wolf3d.js / doom.js / quake.js -> dosgame.js (page) --scancodes, mouse, run/pause--> dosworker.js (Worker)
806
+ ^ |
807
+ | frames (320x200 indices + palette), text-mode cells, stats | createPC() dospc.js
808
+ +----------------------------------------------------------------------+ createCpu() x86.js
809
+ dosaudio.js (AudioWorklet) <--stereo PCM over a MessagePort-----------------+ soundcard.js (SB Pro 2 + OPL3)
810
+ ```
811
+
812
+ - **`x86.js`** is a user-mode i386 with an x87, interpreted. No paging or rings: a DOS extender's
813
+ program runs in protected mode, and segment registers carry a base and a size. EIP is
814
+ kept linear and converted at the edges (a pushed return address, a loaded jump target) against
815
+ the code segment's base, ESP is an offset in SS: DOS/4GW's segments are all at 0, DJGPP's at the
816
+ program's memory block. A code segment whose descriptor is 16-bit decodes 16-bit (DJGPP's
817
+ start-up and exit run small 16-bit helpers in DOS memory). **Real mode** is a switch
818
+ (`cpu.realMode`) for Wolfenstein 3D: a segment's base is its value times sixteen, code is 16-bit,
819
+ the stack pointer is SP and wraps inside its segment, the string instructions count with SI, DI
820
+ and CX, and an interrupt pushes a 16-bit frame and goes through the vector table at 0:0. Real-mode
821
+ code runs through `step()` rather than the decoded cache: it needs about a third of the speed it
822
+ gets. The page's CSP forbids eval, so there
823
+ is no JIT; speed comes from keeping every value an int32 (a `>>> 0` above 2^31 is a double and,
824
+ in a closure variable, an allocation), lazy flags recorded in an `Int32Array`, one try/catch
825
+ around the loop rather than each instruction, and 32-bit fast paths for the instructions
826
+ compilers emit most. The FPU keeps its stack in a Float64Array and converts operands through
827
+ typed-array views. **Decoded instructions are cached**: each is decoded once into three int32s
828
+ (a handler number with its registers packed beside it, a displacement, an immediate) in an
829
+ Int32Array per 4 KB page, and `run()` dispatches on the handler number with one switch; the forms
830
+ it does not specialise run through `step()`. Not closures -- a closure per instruction made every
831
+ call megamorphic and ran slower than the interpreter. Writes into a page holding decoded code
832
+ clear only the instructions they overlap (DOOM and Quake both patch constants into their own span
833
+ drawers on every call), and the machine calls `cpu.invalidate()` after it writes memory directly.
834
+ The ALU operations have a handler each (their flag bookkeeping inline, not one shared routine
835
+ branching on the operation), so do the x87 forms Quake runs most, and the loop forms a memory
836
+ operand's address inline before the switch: the switch is too big for V8 to inline helpers into
837
+ every case, and those calls were most of what was left. About 141 million instructions a second on
838
+ Quake headless on one core and 115-118 in a Chromium worker; Quake's timedemo 52.5 fps of wall time
839
+ (MEASUREMENTS §32-35).
840
+ - **`dospc.js`** is the machine, and it plays whichever DOS extender the program was bound to.
841
+ `boot()` tells them apart by the file: an EXE with neither an LE nor a COFF image inside is a
842
+ plain DOS program, and `bootMZ` loads it as DOS did -- a PSP, the image after it with its segment
843
+ relocations applied, the rest of conventional memory its block, the vector table filled with
844
+ pointers at stubs the machine answers (a program that installs its own handler gets it called),
845
+ and DOS's allocator behind INT 21h 48h/49h/4Ah, which Wolfenstein 3D uses to shrink itself and
846
+ claim the rest. `loadLE` finds DOOM's LE executable inside the DOS/4GW
847
+ stub, loads it at +1 MB and applies its fixups; `parseCoff`/`bootCoff` find Quake's COFF image
848
+ behind the go32 stub and do what that stub leaves behind -- a memory block with the sections in
849
+ it, selectors based at it, a transfer buffer with the PSP right below it (DJGPP's libc finds the
850
+ PSP by subtracting 100h), and the "stubinfo" crt0 reads through FS. The extenders themselves never
851
+ run; this file answers INT 21h (files from an in-memory, case-insensitive directory tree, written
852
+ files handed to the host on close, and a real system file table, because DJGPP's `fstat` walks
853
+ it), INT 31h DPMI (descriptors with base and size, memory blocks, protected-mode vectors,
854
+ simulated real-mode interrupts), INT 10h/16h/33h, and the hardware a DOS game programs directly:
855
+ the 8259s, the 8254 (with the BIOS tick count kept in step with it, which DJGPP's `uclock` reads),
856
+ the keyboard controller, and a VGA with planar memory, unchained mode, write mode 1's latched copy
857
+ (Wolfenstein 3D and DOOM copy between pages with it) and CRTC page flipping (how DOOM and
858
+ Wolfenstein 3D draw) as well as the linear window (how Quake does). The clock is injected (`now()`): wall
859
+ time in the worker, instruction count in tests, so a headless boot is the same run every time.
860
+ - **`soundcard.js`** is a Sound Blaster Pro 2 at 220h/IRQ 7/DMA 1 — the DSP's command set and the
861
+ 8237 DMA controller it pulls samples through — and an OPL3 modelled as operators with
862
+ documented envelope rates. `tick(t)` produces output for the machine time that passed and raises
863
+ the end-of-block interrupt from inside the same loop.
864
+ - **`dosworker.js`** takes a game's name, fetches its files (`dosio.js` `GAMES`), and runs the
865
+ machine in ~10 ms slices, yielding between them so input arrives. It sends a frame when the CRTC
866
+ start address or the palette changed, or once the linear window has been written and a slice has
867
+ passed without more writes (never half of Quake's copy). The frame buffer bounces between the
868
+ worker and the page so no frame allocates. Savegames and configs are kept in IndexedDB, one
869
+ database a game.
870
+ - **`dosgame.js`** is a game's tab: it draws, captures input, pauses when the tab is not on screen
871
+ (the worker's clock stops, so nothing moves), and uses a ScriptProcessor when `audioWorklet` is
872
+ unavailable — a plain-HTTP LAN address is not a secure context. `doom.js` and `quake.js` give it
873
+ names, key lists and switches, and so does `wolf3d.js`.
874
+
875
+ The game files are served from `games/<game>_dos/` by `server/http/games.js`
876
+ (`/games/<game>/<path>`: a game it names, at most one directory and 8.3 names of `.EXE`, `.WAD`,
877
+ `.PAK`, `.CFG` and `.WL1`, behind the session when accounts are on) rather than from `public/`, whose every
878
+ file feeds the build id.
879
+
793
880
  ## 4. The 3D engine
794
881
 
795
882
  The 3D viewer turns a set of transactions, or any caller-supplied tiles, into
@@ -95,18 +95,20 @@ Durations are in milliseconds unless the name says otherwise (`retentionHours`).
95
95
 
96
96
  | key | default | meaning |
97
97
  |---|---|---|
98
- | `server.host` | `"0.0.0.0"` | Address to listen on. Kept for compatibility; `server.hosts` wins when both are set. |
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
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
100
  | `server.port` | `21000` | TCP port. It is the same port on every address. It must be an integer from 1 to 65535. |
101
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
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.cert` | `null` | Path to a PEM certificate. TLS is on only when `cert` and `key` are both set, and then every listener serves HTTPS. |
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`. |
104
105
  | `server.tls.key` | `null` | Path to the PEM private key for `cert`. This file is secret. |
105
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. |
106
107
 
107
108
  When TLS is on, `auth.secureCookie` is forced to `true`. At startup the server logs
108
109
  the certificate's SHA-256 fingerprint and whether it is self-signed. It also warns
109
- when the certificate expires within 14 days.
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.
110
112
 
111
113
  ### nodes
112
114
 
@@ -340,17 +342,18 @@ Available actions:
340
342
  The Markets tab fetches public BTC/USD prices, hourly candles and order books from
341
343
  five exchanges over HTTPS: Coinbase, Kraken, Bitstamp, Bitfinex and OKX (OKX quotes
342
344
  BTC/USDT). The fetches run on the server, not in the browser. This is BlockYard's
343
- only outbound connection other than the node. Exchanges see this server's IP
344
- address and a User-Agent, nothing about the node. Polling starts when a browser asks
345
- for market data -- the Markets and Kiosk tabs, and Overview's price line, which is on by
346
- default (Display settings Markets & Price Price line on Overview) -- and stops
347
- `idleAfterMs` after the last request, so an unwatched monitor makes no exchange traffic.
348
- With that line switched off, only Markets and Kiosk start it. The exchange list is fixed
349
- in code.
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.
350
353
 
351
354
  | key | default | meaning |
352
355
  |---|---|---|
353
- | `markets.enabled` | `true` | `false` removes the feed entirely, and no request is ever made. |
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. |
354
357
  | `markets.tickerMs` | `15000` | Ticker (last, bid, ask, 24 h volume) refresh. |
355
358
  | `markets.candleMs` | `300000` | Hourly candle refresh. |
356
359
  | `markets.bookMs` | `30000` | Order book refresh, for the depth chart. |
@@ -378,12 +381,13 @@ Environment variables override `config/local.json`.
378
381
  | variable | sets | type | default | meaning |
379
382
  |---|---|---|---|---|
380
383
  | `BLOCKYARD_CONFIG` | *(which file is read)* | path or `none` | `<repo>/config/local.json` | Configuration file to read. `none`/`off`/`no`/`-` reads no file. |
381
- | `BLOCKYARD_BIND` | `server.host` | list | `0.0.0.0` | Listen address(es), for example `127.0.0.1` 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)). |
382
- | `BLOCKYARD_HOST` | `server.host` | list | `0.0.0.0` | Same as `BLOCKYARD_BIND`. |
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`. |
383
386
  | `BLOCKYARD_PORT` | `server.port` | number | `21000` | Listen port. |
384
387
  | `BLOCKYARD_ALLOW_CIDRS` | `server.allowCidrs` | list | *(empty: everyone)* | Client allowlist, for example `192.0.2.0/24,2001:db8::/32`. |
385
388
  | `BLOCKYARD_TRUST_PROXY` | `server.trustProxy` | boolean | `false` | Take the client address from `X-Forwarded-For`. |
386
- | `BLOCKYARD_TLS_CERT` | `server.tls.cert` | path | unset | PEM certificate. Set it together with `BLOCKYARD_TLS_KEY`. |
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`. |
387
391
  | `BLOCKYARD_TLS_KEY` | `server.tls.key` | path | unset | PEM private key. Set it together with `BLOCKYARD_TLS_CERT`. |
388
392
  | `BLOCKYARD_NODE_URL` | `nodes[0].rpcUrl` | URL | `http://127.0.0.1:8332` | RPC endpoint of the first node. |
389
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. |
@@ -404,7 +408,7 @@ Environment variables override `config/local.json`.
404
408
  | `BLOCKYARD_ALLOW_WRITES_WITHOUT_AUTH` | `actions.allowWritesWithoutAuth` | boolean | `false` | Permit actions while accounts are off. |
405
409
  | `BLOCKYARD_LOG_SOURCE` | `log.enabled` | boolean | `false` | `1` tails node log files; `0` (or unset) runs on RPC alone. |
406
410
  | `BLOCKYARD_LOG_LEVEL` | `log.level` | string | `info` | `debug`, `info`, `warn` or `error`. |
407
- | `BLOCKYARD_MARKETS` | `markets.enabled` | boolean | `true` | `0` turns the Markets feed off. |
411
+ | `BLOCKYARD_MARKETS` | `markets.enabled` | boolean | `true` | `0` removes the Markets feed; the polling checkbox then cannot turn it on. |
408
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. |
409
413
  | `BLOCKYARD_MINING_BACKFILL` | *(none)* | number | `36` | How many recent blocks are attributed to miners at startup. |
410
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. |
@@ -577,10 +581,10 @@ instead:
577
581
  BLOCKYARD_ADMIN_PASSWORD='choose-a-long-passphrase' npm start
578
582
  ```
579
583
 
580
- Then create personal accounts with `npm run user -- create <name> <role>`. Without
581
- TLS, the startup log warns that the session cookie crosses the network in clear
582
- text. Pair accounts with one of the TLS options below, or with a loopback bind and
583
- an SSH tunnel.
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.
584
588
 
585
589
  Accounts plus two node writes that operators may run:
586
590
 
@@ -596,7 +600,8 @@ Accounts plus two node writes that operators may run:
596
600
 
597
601
  ### TLS
598
602
 
599
- Built-in HTTPS on every listener:
603
+ HTTPS is on by default, with a certificate the server makes for itself. A certificate of your
604
+ own, on every listener:
600
605
 
601
606
  ```json
602
607
  {
@@ -632,9 +637,9 @@ matters here too: it makes the proxy the only way in.
632
637
 
633
638
  ### Binding to specific addresses
634
639
 
635
- The default `0.0.0.0` listens on every IPv4 interface the machine has, including VPN
636
- tunnels and container bridges. To serve exactly one LAN address and one VPN
637
- address, and admit only clients from those networks:
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:
638
643
 
639
644
  ```json
640
645
  {
@@ -661,9 +666,11 @@ Things to know about specific binds:
661
666
  - For this machine only, use `"hosts": ["127.0.0.1"]` and reach it with
662
667
  `ssh -L 21000:127.0.0.1:21000 user@monitor-host`.
663
668
 
664
- ### Markets off
669
+ ### Markets off, for good
665
670
 
666
- No outbound connections except to the node:
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:
667
674
 
668
675
  ```json
669
676
  {
@@ -713,7 +720,7 @@ sudo systemctl edit blockyard
713
720
 
714
721
  ```ini
715
722
  [Service]
716
- Environment=BLOCKYARD_AUTH=1
723
+ Environment=BLOCKYARD_BIND=192.0.2.10
717
724
  Environment=BLOCKYARD_DATA=/var/lib/blockyard
718
725
  ```
719
726
 
@@ -158,12 +158,15 @@ existing `config/local.json` (with a backup).
158
158
  npm start
159
159
  ```
160
160
 
161
- The log says `BlockYard 0.0.9 listening on http://127.0.0.1:21000`, then `address index: building
161
+ The log says `BlockYard 0.1.0 listening on https://127.0.0.1:21000` (its own self-signed
162
+ certificate, made on this first start; the browser warns once and remembers it), then `created the
163
+ first admin account (admin)` with a generated password **shown once** — copy it, or set
164
+ `BLOCKYARD_ADMIN_PASSWORD` before the first start to choose it — then `address index: building
162
165
  /Users/you/blockyard/data/index from main's block files with 4 workers -- the Overview shows the
163
166
  progress` (and `address index build: paused while the node's RPC is answering in … s` /
164
167
  `resumed` if the node struggles) and, when that is done, `address index built: … rows to block N
165
168
  in … min -- address pages are live` followed by `address index /Users/you/blockyard/data/index:
166
- following main from block N`. Open <http://127.0.0.1:21000>. The
169
+ following main from block N`. Open <https://127.0.0.1:21000> and sign in as `admin`. The
167
170
  Overview fills in within about thirty seconds; Block space lands a little after. Open Explorer,
168
171
  click the latest block, then any output address: while the index is building the page says so
169
172
  with the progress; once it is built, its balance, history and unspent outputs appear.
package/docs/INSTALL.md CHANGED
@@ -12,7 +12,7 @@ machines you choose. Every setting mentioned here is described in full in
12
12
  - [6. Run it as a service](#6-run-it-as-a-service)
13
13
  - [7. Decide who can reach it](#7-decide-who-can-reach-it)
14
14
  - [8. Accounts (optional)](#8-accounts-optional)
15
- - [9. HTTPS (optional)](#9-https-optional)
15
+ - [9. HTTPS (on by default)](#9-https-on-by-default)
16
16
  - [10. Behind a reverse proxy (optional)](#10-behind-a-reverse-proxy-optional)
17
17
  - [11. Updating](#11-updating)
18
18
  - [12. Uninstalling](#12-uninstalling)
@@ -146,9 +146,10 @@ Balances are checked against the node: 40 of 40 sampled addresses equal `scantxo
146
146
  satoshi (`node scripts/index-benchmark.js` runs that check and the lookup timings against your
147
147
  own build).
148
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).
149
+ **Outbound network access**: none, out of the box everything talks only to your node. The
150
+ Markets and Kiosk tabs and the explorer's dollar figures need the exchange feed (HTTPS to five
151
+ exchanges' public APIs), which is off until you tick **Display settings → Markets & Price → Enable market polling**
152
+ in the browser. See [SECURITY.md](SECURITY.md#outbound-connections).
152
153
 
153
154
  ## 2. Get the code
154
155
 
@@ -240,8 +241,8 @@ real-time explorer data over RPC was a failed idea.
240
241
 
241
242
  `rpcUser` / `rpcPassword` are still accepted, for a node that authenticates with `rpcauth` rather
242
243
  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`.
244
+ form takes no password on purpose -- taking one over a web endpoint is not something to add
245
+ quietly -- so credentials go in `config/local.json`.
245
246
 
246
247
  ##### `bitcoin.conf` settings worth having
247
248
 
@@ -284,17 +285,20 @@ npm run check # every configured node: RPC, credentials, txindex, getblock
284
285
  npm start
285
286
  ```
286
287
 
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>.
288
+ Watch the start-up lines. You should see `listening on https://127.0.0.1:21000` (the first start
289
+ makes the monitor its own self-signed certificate under `data/tls/`; the browser warns once per
290
+ address and remembers it), a line per node, and because accounts are on by default — `created
291
+ the first admin account (admin)` with a generated password **shown once** (set
292
+ `BLOCKYARD_ADMIN_PASSWORD` before the first start to choose it; `blockyard user` changes it
293
+ later). If the node entry names an `addressIndex` directory with no index in it, `address index:
294
+ building … with N workers -- the Overview shows the progress` follows, and the build runs on in
295
+ the background (see [Building the address index](#building-the-address-index)). Then open
296
+ <https://127.0.0.1:21000> and sign in.
293
297
 
294
- Check it from the shell:
298
+ Check it from the shell (`-k`, because the certificate is self-signed):
295
299
 
296
300
  ```bash
297
- curl -s http://127.0.0.1:21000/api/health
301
+ curl -sk https://127.0.0.1:21000/api/health
298
302
  ```
299
303
 
300
304
  If a node shows as offline, see [TROUBLESHOOTING.md](TROUBLESHOOTING.md#a-node-shows-offline).
@@ -356,10 +360,10 @@ Where the monitor listens is a security decision. Set `server.host` in `config/l
356
360
 
357
361
  | bind | who can connect | typical use |
358
362
  |---|---|---|
359
- | `"127.0.0.1"` | this machine only | reach it with an SSH tunnel: `ssh -L 21000:127.0.0.1:21000 you@host` |
363
+ | `"127.0.0.1"` (the default) | this machine only | reach it with an SSH tunnel: `ssh -L 21000:127.0.0.1:21000 you@host` |
360
364
  | `"192.0.2.10"` (a LAN address) | anything that can route to that address | a home or office LAN |
361
365
  | `["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 |
366
+ | `"0.0.0.0"` | every interface | behind a firewall you control |
363
367
 
364
368
  Notes:
365
369
 
@@ -377,15 +381,17 @@ Notes:
377
381
  - `server.allowCidrs` (or `BLOCKYARD_ALLOW_CIDRS=192.0.2.0/24,2001:db8::/32`) makes the
378
382
  monitor itself refuse clients outside those networks, as a second line of defence.
379
383
 
380
- ## 8. Accounts (optional)
384
+ ## 8. Accounts (on by default)
381
385
 
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.
386
+ Sign-in is required out of the box. The first start with an empty data directory creates an
387
+ `admin` account and prints its password **once** in the log (under systemd: `journalctl -u
388
+ blockyard`). Set your own instead with `BLOCKYARD_ADMIN_PASSWORD` for that first start.
385
389
 
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.
390
+ To open the monitor to readers with no account, set `"auth": { "enabled": false }` or
391
+ `BLOCKYARD_AUTH=0` and restart: anyone who can reach the port then reads it as a `viewer` —
392
+ charts, the explorer, the event stream, the read-only RPC console while user
393
+ administration, the audit trail and every node write stay closed. The boot log names the
394
+ addresses that leaves readable.
389
395
 
390
396
  Manage accounts from the Admin page, or from the shell — for example, to reset the admin
391
397
  password:
@@ -398,17 +404,35 @@ Roles: `viewer` reads; `operator` may also run node actions that you have enable
398
404
  also manages users and reads the audit trail. Node actions are off unless you enable them
399
405
  explicitly — see [SECURITY.md](SECURITY.md#node-writes).
400
406
 
401
- ## 9. HTTPS (optional)
407
+ ## 9. HTTPS (on by default)
402
408
 
403
- Name a certificate and key and every listener serves HTTPS:
409
+ Every listener serves HTTPS out of the box. With no certificate of your own named, the first
410
+ start makes one: a self-signed certificate and key under `<data>/tls/` (`data/tls/cert.pem` and
411
+ `key.pem`, the key readable by the service account only), naming the addresses the monitor is
412
+ reached on — the bound hosts, this machine's addresses and hostname, `localhost`. It is remade
413
+ by itself when it nears expiry (825 days) or stops naming a bound address. Browsers warn once
414
+ per address about a self-signed certificate and then remember it; the start-up log prints its
415
+ fingerprint so you can compare.
416
+
417
+ To add a name or address (say, a DNS name you gave the machine) or start over:
418
+
419
+ ```bash
420
+ blockyard tls --san monitor.lan.example # or: node scripts/tls.js --san ...
421
+ blockyard tls --force # a fresh key and certificate
422
+ blockyard tls --print > blockyard.crt # the certificate, for another machine's trust store
423
+ ```
424
+
425
+ To use a certificate of your own instead, name it and every listener serves that:
404
426
 
405
427
  ```bash
406
428
  BLOCKYARD_TLS_CERT=/etc/blockyard/cert.pem BLOCKYARD_TLS_KEY=/etc/blockyard/key.pem npm start
407
429
  ```
408
430
 
409
- or in `config/local.json`: `"server": { "tls": { "cert": "...", "key": "..." } }`.
431
+ or in `config/local.json`: `"server": { "tls": { "cert": "...", "key": "..." } }`. To serve plain
432
+ HTTP — behind a reverse proxy that terminates TLS (section 10) — set `BLOCKYARD_TLS=0` or
433
+ `"server": { "tls": { "enabled": false } }`.
410
434
 
411
- A self-signed certificate is fine on a LAN (expect one browser warning per address):
435
+ If you would rather make the certificate with openssl yourself:
412
436
 
413
437
  ```bash
414
438
  sudo mkdir -p /etc/blockyard
@@ -424,8 +448,8 @@ rather than silently serving plain HTTP.
424
448
 
425
449
  ## 10. Behind a reverse proxy (optional)
426
450
 
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
451
+ If you already run nginx, Caddy or similar, bind the monitor to `127.0.0.1`, set
452
+ `BLOCKYARD_TLS=0` so it speaks plain HTTP to the proxy, and let the proxy terminate TLS. The live stream (`/api/stream`) is Server-Sent Events, so the proxy must not
429
453
  buffer it. An nginx example:
430
454
 
431
455
  ```nginx
@@ -468,8 +492,41 @@ Your `config/local.json` and `data/` directory are untouched by updates. Read
468
492
  up new front-end files on the next page load; the header shows a notice when the page you
469
493
  have open is older than the server.
470
494
 
495
+ **Updating from 0.0.9.** The defaults hardened in 0.1.0, and a `config/local.json` written by
496
+ 0.0.9's installer does not name them, so the first start after the update behaves like a fresh
497
+ install in three ways:
498
+
499
+ - **HTTPS.** The monitor makes itself a self-signed certificate under `data/tls/` and serves
500
+ HTTPS on the same port; `http://…:21000` stops answering. Open `https://`, accept the
501
+ certificate once. Behind your own reverse proxy, set `BLOCKYARD_TLS=0` (§10).
502
+ - **Sign-in.** Accounts are on. The first start creates the `admin` account and prints its
503
+ password **once** in the log (`journalctl -u blockyard` under systemd); set
504
+ `BLOCKYARD_ADMIN_PASSWORD` before that start to choose it. To keep the monitor open as before,
505
+ put `"auth": { "enabled": false }` in `config/local.json` or start with `BLOCKYARD_AUTH=0`.
506
+ - **This machine only.** With no `server.host` in the config the bind is `127.0.0.1`. A
507
+ 0.0.9 config written by the installer names the host it chose, so a LAN bind stays; if yours
508
+ does not, add `BLOCKYARD_BIND` or `server.hosts` (§7).
509
+ - **Market polling is off** until someone ticks **Display settings → Markets & Price → Enable
510
+ market polling** — once, for every screen.
511
+
471
512
  ## 12. Uninstalling
472
513
 
514
+ BlockYard touches nothing outside its own directories: it reads `bitcoin.conf` and never writes
515
+ it, and it leaves the node as it found it. Removing it is deleting those directories.
516
+
517
+ **Installed from npm** (`npm install -g blockyard`): stop it, then
518
+
519
+ ```bash
520
+ npm uninstall -g blockyard
521
+ rm -rf ~/.blockyard # local.json, data/ (history, accounts, the audit trail) and data/index
522
+ ```
523
+
524
+ **A checkout** run with `npm start`: stop it and delete the checkout; `config/` and `data/`,
525
+ the index included, live inside it. If you pointed the index elsewhere at setup time
526
+ (`addressIndex` in `config/local.json`), delete that directory too.
527
+
528
+ **The systemd service** of section 6:
529
+
473
530
  ```bash
474
531
  sudo systemctl disable --now blockyard
475
532
  sudo rm /etc/systemd/system/blockyard.service && sudo systemctl daemon-reload
@@ -484,7 +541,7 @@ sudo userdel blockyard
484
541
  - [ ] `npm run check` passes: Core 25.0+, `txindex` synced, the block files readable, no pruning
485
542
  - [ ] the index directory has ~125 GB free and is writable by the service account
486
543
  - [ ] 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
544
+ - [ ] you have decided who can reach the port: it binds `127.0.0.1` until you say otherwise (bind, firewall, `allowCidrs`)
545
+ - [ ] accounts stay on (the default) if the port is reachable by people who should not see your node; you have the admin password from the first start
546
+ - [ ] HTTPS is on by default with the monitor's own certificate; a certificate of your own, or a proxy / tunnel in front, if you want no browser warning
547
+ - [ ] **Enable market polling** ticked in Display settings if you want the Markets and Kiosk tabs (off by default); `BLOCKYARD_MARKETS=0` on machines that must make no outbound connections at all