blockyard 0.1.0 → 0.1.2
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 +112 -0
- package/README.md +13 -11
- package/SECURITY.md +2 -2
- package/docs/API.md +1 -1
- package/docs/ARCHITECTURE.md +36 -5
- package/docs/CONFIGURATION.md +6 -4
- package/docs/DEFECTS.md +4 -1
- package/docs/GETTING-STARTED.md +14 -7
- package/docs/INSTALL.md +7 -4
- package/docs/PLAN-SCORCHED-YARD.md +456 -0
- package/docs/PLAN-SKIES.md +142 -0
- package/docs/SECURITY-AUDIT-2026-09-16.md +647 -0
- package/docs/SECURITY.md +26 -7
- package/docs/TROUBLESHOOTING.md +10 -5
- package/docs/USER-GUIDE.md +247 -9
- package/package.json +4 -2
- package/public/css/app.css +87 -0
- package/public/index.html +58 -6
- package/public/js/app.js +60 -19
- package/public/js/blockanoid.js +15 -7
- package/public/js/blockout.js +15 -7
- package/public/js/blockscene3d.js +51 -11
- package/public/js/depthchart.js +1 -1
- package/public/js/details3d.js +25 -2
- package/public/js/explorer.js +7 -1
- package/public/js/livingsky.js +494 -0
- package/public/js/login.js +3 -2
- package/public/js/mining.js +4 -4
- package/public/js/panels.js +27 -18
- package/public/js/safenext.js +14 -0
- package/public/js/scorched.js +1071 -0
- package/public/js/scorchedai.js +268 -0
- package/public/js/scorchedair.js +286 -0
- package/public/js/scorchedfx.js +376 -0
- package/public/js/scorchedshop.js +105 -0
- package/public/js/scorchedwind.js +69 -0
- package/public/js/scorchedyard.js +1361 -0
- package/public/js/settings.js +266 -80
- package/public/js/tetrust.js +15 -6
- package/public/js/tetsound.js +35 -5
- package/scripts/check.js +46 -0
- package/scripts/index-build.js +9 -2
- package/scripts/pool-map.js +152 -36
- package/scripts/setup.js +108 -10
- package/scripts/shots.mjs +27 -0
- package/scripts/smoke.sh +6 -5
- package/scripts/ui.js +4 -2
- package/server/auth/sessions.js +33 -13
- package/server/chain/blockfile.js +64 -5
- package/server/chain/index/build.js +432 -56
- package/server/chain/index/heights.js +29 -3
- package/server/chain/index/live.js +13 -7
- package/server/chain/index/rows.js +6 -1
- package/server/chain/index/store.js +28 -5
- package/server/chain/index/worker.js +23 -11
- package/server/collect/logparse.js +65 -18
- package/server/collect/markets.js +76 -7
- package/server/collect/mining.js +32 -0
- package/server/collect/monitor.js +24 -11
- package/server/collect/network.js +19 -9
- package/server/config.js +7 -0
- package/server/http/api.js +70 -13
- package/server/http/server.js +22 -5
- package/server/http/sse.js +53 -7
- package/server/main.js +13 -3
- package/server/rpc/allowlist.js +26 -0
- package/server/rpc/client.js +30 -2
- package/server/store/audit.js +6 -1
- package/server/store/history.js +19 -3
- package/server/store/ledger.js +15 -4
- package/systemd/blockyard.service +34 -3
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,118 @@ All notable changes to this project are documented here. The format follows
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.1.2] — 2026-09-17
|
|
10
|
+
|
|
11
|
+
A day of hardening, and a new game. The headline is the **third AI security audit**, run on
|
|
12
|
+
2026-09-16 over the whole codebase by four auditors in parallel, and every one of its findings
|
|
13
|
+
fixed the same day with a test that holds it: one high (a client that opened the live stream and
|
|
14
|
+
stopped reading was never dropped, so memory grew without bound), eight medium, seventeen low and
|
|
15
|
+
six informational. The **address index** learned to resume an interrupted build instead of starting
|
|
16
|
+
its hours-long read again, and is now checked in the suite against a second, deliberately naive
|
|
17
|
+
implementation; property tests over Bitcoin Core's file formats found and fixed three flaws in
|
|
18
|
+
reading undo data. The **skies** were made clear: every board picks the Galaxy or the Earth sky in
|
|
19
|
+
one place, and the Earth sky gained a warm sun with a halo, a moon that glows through its real
|
|
20
|
+
phases, and softer clouds. And under Diversions, **Scorched Yard**: Scorched Earth on the block
|
|
21
|
+
engine, with the manual's weapons and computer players who each wear their own colour and aim like
|
|
22
|
+
people, up to eight tanks, a cheat mode that draws
|
|
23
|
+
the firing solution, and wind that is a real fluid simulation flowing over the hills. The games now
|
|
24
|
+
load only when their page is first opened. 1,070 tests, from 945.
|
|
25
|
+
|
|
26
|
+
### Upgrading from 0.1.0
|
|
27
|
+
|
|
28
|
+
- **Node.js 22.2 or newer** is required (was 22.0): the resumable index build uses Node's built-in
|
|
29
|
+
CRC-32.
|
|
30
|
+
- **With accounts off, the node connection form works only from the monitor's own machine**
|
|
31
|
+
(`127.0.0.1`). `auth.openNodeConfigFromNetwork: true` restores the old reach; with accounts on,
|
|
32
|
+
an admin can use it from anywhere, as before.
|
|
33
|
+
- **Wallet RPCs are refused** in the RPC console, reads included (`getbalance`, `listunspent`, …).
|
|
34
|
+
- **The shipped systemd unit is sandboxed.** If you install it again, edit its `ReadWritePaths=`
|
|
35
|
+
lines to your install's `data` and `config` directories, and add your address index directory.
|
|
36
|
+
- **The address index build refuses a directory** that holds anything an index does not write, a
|
|
37
|
+
symlink, or a home, working or blocks directory. Point `addressIndex` at a new or empty
|
|
38
|
+
directory, or at an existing index.
|
|
39
|
+
- **Sky settings are migrated** from the old global sky and per-board star switches to one Sky
|
|
40
|
+
choice per board; each board keeps what it drew.
|
|
41
|
+
- **`scripts/pool-map.js`** asks before replacing the pool map (`--yes` to skip), and
|
|
42
|
+
**`scripts/setup.js`** takes the RPC password from `--rpc-password-file` or stdin; the plain
|
|
43
|
+
`--rpc-password` still works, with a warning.
|
|
44
|
+
|
|
45
|
+
### Security
|
|
46
|
+
|
|
47
|
+
- **The third audit's low and informational findings, fixed the same day** (`docs/SECURITY-AUDIT-2026-09-16.md`, tests in `test/audit-2026-09-16-low-*.test.js`), so every finding of that audit is closed. *Browser*: the post-sign-in redirect stays on this site; the page's key-value lists escape every value unless it is markup the page built, and the server passes the node's chain name, pruned and IBD flags and unbroadcast count on only in their own types; RPC URLs shown to viewers lose any username and password; viewers see the last two parts of cookie, log and settings paths; a malformed explorer link opens the explorer's home. *HTTP*: a malformed escape in a route is a 400; the sign-in lockout is per username and address, so one address can no longer lock an account for everyone (an address trying many accounts, or an account tried from many addresses, still locks at a higher count). *Collectors*: a coinbase with a truncated push no longer throws or stalls pool attribution; an order book far from the others is not drawn, the depth grid is capped and market responses are size-capped with redirects refused; long log lines can no longer take seconds to parse. *Index*: the height table is sized from the tip and cannot loop; a transaction whose input count disagrees with its undo record fails the build; a lock file stops two builds sharing a directory; files are created owner-only and temporary files are never written through a symlink; `--workers` is validated; malformed self-written files are cut or skipped rather than crashing. *Scripts and supply chain*: `setup.js` no longer echoes the RPC password and takes it from a file or stdin; `pool-map.js` refuses non-HTTPS redirects and shows a diff before replacing the map; CI actions are pinned to commit hashes; `config/*.bak-*` is ignored; the game files have a hash manifest. `SECURITY.md` names 0.1.x as supported, and the smoke script's open-access instance now really opens access
|
|
48
|
+
- **The third audit's high and medium findings, fixed the same day** (`docs/SECURITY-AUDIT-2026-09-16.md`, tests in `test/audit-2026-09-16.test.js`). *Stalled stream readers*: a client whose socket is full is sent nothing more until it drains (the newest snapshot waits, as the stream's comments always claimed), and is dropped at 4 MB buffered or after a minute blocked; one address or account holds at most 16 streams. *Open mode and scripts*: with accounts off, the node connection form (save and test) answers only a caller on this machine's loopback address, since a browser check cannot stop a script; `auth.openNodeConfigFromNetwork` restores the old reach. A save that moves the node to another host drops the old endpoint's `rpcUser`, `rpcPassword` and `cookieFile`, and testing a foreign endpoint reports the kind of failure, never what it answered. *The index build* refuses a symlink, the filesystem root, a home or working directory, the node's blocks directory, or a directory holding anything an index does not write, and only ever removes its own files. *Wallet RPCs* are all refused by name, reads included. *Request bodies* must arrive within 30 seconds; the event stream is unaffected. *Audit rows* clamp every string to 1,024 characters. *The npm package* no longer includes the gitignored private notes, and a test holds it to tracked files. *The shipped systemd unit* is sandboxed (`ProtectSystem=strict` with `ReadWritePaths` for `data/` and `config/`, `ProtectHome=read-only`, no capabilities, a system-call filter, `UMask=0077`), and the app was started and exercised under exactly those settings
|
|
49
|
+
- **Third AI security audit** (`docs/SECURITY-AUDIT-2026-09-16.md`), every finding fixed the same day (above). One high: a client that opens the live event stream and stops reading is never dropped, and 400 such connections took a test instance from 73 MB to 1.5 GB in three minutes. Eight medium: in open mode a script (not a browser) can rewrite the node connection and receive the node cookie after a restart, and can use the connection probe to read internal URLs; the address index build deletes its output directory recursively without checking it; the RPC console admits `listdescriptors` and `gethdkeys`, which return private keys from an unlocked wallet; request bodies have no read deadline; oversized RPC method names flush the audit trail; the npm package includes the gitignored private notes; the shipped systemd unit is barely sandboxed. Seventeen low and six informational. Both earlier audits' fixes re-verified
|
|
50
|
+
|
|
51
|
+
### Address index
|
|
52
|
+
|
|
53
|
+
- **An interrupted address index build resumes.** Stopping BlockYard, Ctrl-C in the installer, a crash or a reboot used to throw away the whole build, which reads about 880 GB and runs for hours on a shared disk. The build now keeps a small journal beside its output, written atomically with a checksum of its own contents, and the next start carries on from it: block files already scanned are not read again, and buckets already sorted are not sorted again. Nothing it cannot prove finished is used: the bucket files are cut back to the length the journal recorded, so rows from a file that was half appended are gone, and a bucket about to be sorted is checked against the length and CRC-32 the scan recorded. The resumed index is byte for byte the one an uninterrupted build writes, which the tests check after stops at seven points, from the directory the stopped build left and from a copy taken at the instant of the stop. A journal for another index format or file selection, for a tip the node's chain no longer has, or that is torn or damaged is thrown away, the log says why, and the build starts over. The manifest is still written last. The progress and its ETA pick up from the resumed position. Also fixed on the way: a worker that died between two jobs left the pool waiting for it forever, and when two things went wrong at once the build reported the second rather than the cause
|
|
54
|
+
- **Fixed: three flaws in reading Bitcoin Core's undo files, found by new property tests.** The suite now generates thousands of seeded encodings (CompactSizes, Core VARINTs, compressed amounts, undo records, legacy and segwit transactions, blocks, block and undo files), checks each decoder against an oracle written from Core's format, and truncates and flips bytes to hold every decoder to throwing or decoding exactly. An output amount above about 10 million BTC was rounded when read, and is now exact. An undo record cut inside its last script was accepted as whole, and is now refused. A corrupt VARINT could decode as an enormous height, and is now refused past Core's own 32-bit limit. No real chain data reaches the first flaw
|
|
55
|
+
- **The address index is checked automatically against a second implementation.** Until now its correctness was checked by hand, with scripts against a live node, and history had no independent check at all. The suite now builds a seeded synthetic chain (301 blocks, 6,346 transactions, 384 addresses across eight script types, XOR-obfuscated block and undo files with a stale block and reordered, duplicated records), indexes it with the real build, and compares every address's balance and whole history, walked through the store's paged queries past the counting limit, with a deliberately naive replay of the UTXO set that shares no indexing code. It compares again after the live follower folds and merges sixty more blocks, after a reorganisation inside the tail and a restart, and asserts a reorganisation into folded layers is refused as stale. It found no discrepancy; it fails on each of five deliberately introduced faults. About three seconds
|
|
56
|
+
|
|
57
|
+
### Performance
|
|
58
|
+
|
|
59
|
+
- **Games load when first opened.** The seven games (Tetrust, Blockout, Blockanoid, Scorched Yard and the three DOS ports) were imported by the app shell at startup, so every page paid for all of them. They are now fetched the first time their page is opened: the modules loaded at startup drop from 39 to 21, and from 1.42 MB to 1.09 MB. A failed load shows a message and is retried on the next visit. A test holds the shell's static imports free of any game module
|
|
60
|
+
|
|
61
|
+
### Skies and lighting
|
|
62
|
+
|
|
63
|
+
- **The moon glows, and its phases can be watched.** The same two-layer halo the sun has, in moonlight silver, stronger the fuller the moon. The phase has always followed the real calendar (full on the real full-moon dates, new on the new ones, the quarters between); under the cycle clock, where a day is 24 minutes, the calendar now runs a day per cycle day, so a whole month of phases goes by in about twelve hours on the wall. The disc is a little larger
|
|
64
|
+
- **The sun has a halo, and no rings.** Its soft edge was twelve steps, which on a big panel is several pixels a step and showed as concentric rings; the ring counts are left to the fill's own rule now, about one a pixel. And a halo in front of the clouds, a wide faint corona and a tighter bright one, so the sun sits in its own light rather than being a coin on the sky
|
|
65
|
+
- **The Earth sky's cirrus is gas, not a chain of discs.** Each high wisp was a row of hard-edged flat discs fourteen pixels apart, and where they overlapped the outlines stacked into a visible row of rings. A wisp is a scatter of soft-edged puffs now, built the way the cumulus is, jittered off the line and stretched along it, so nothing in it has an edge to band
|
|
66
|
+
- **The Earth sky's sun and moon are there to be seen.** The sun is half again as big and its disc is drawn in front of the clouds (its glow still behind them), so it is no longer a pale spot under a cumulus at noon; the moon likewise. A board can say where its horizon is — Scorched Yard's land fills the lower third, so its sun sets behind the hills instead of under them. And a **Moon** choice under the Earth: up every night, highest at midnight and never thinner than a fat crescent (the shipped choice), or its real track and phase, which some nights means no moon at all
|
|
67
|
+
- **One sky per board: the Galaxy or the Earth** (docs/PLAN-SKIES.md; operator: "The skybox settings are not clear in the preferences, for which sky settings apply to which panel"). There were two skies described from two directions and no board named anywhere: one global Space-or-Living switch for every board at once, six scattered "Star field" toggles that under the living sky meant "draw the day or draw nothing", and four private galaxy switches beside the Sky tab's own. Now: the Sky tab opens with a table of every board that has a sky and the one it draws — Block space (and the Kiosk's left panel), Markets & Price (and its right), Tetrust, Blockout, Blockanoid, Scorched Yard — each a select for **Galaxy**, **Earth** or **None**; below it, the two skies with only their own controls, never dimmed. Each board's tab carries the same one **Sky** choice in place of its old star and galaxy toggles, and the games' panels have one **sky** button round the three. The star-field sky is named the **Galaxy** (not Space, which is Block space's word), its arms toggle is **Spiral arms** and its placement **Arms centre**; the living sky is the **Earth**. Scorched Yard ships under the Earth, everything else under the Galaxy. A stored setting comes forward drawing exactly what it drew: stars off becomes none, stars on becomes whichever sky the old global switch named; the step is idempotent, because server-stored settings carry no version and re-run it on every boot
|
|
68
|
+
- **A board can ask for a harder lamp.** The renderer put every face of every cube between 0.34 and 0.86 of its own colour with the top at 0.8 — a narrow band, which reads as tinted rather than lit. `lightGain` stretches that range about its middle and `topLight` sets where the tops sit; 1 and 0.8 are exactly what every board drew before. Scorched Yard's field uses a hard lamp at the viewer, so the front of the land carries the light and the strata separate
|
|
69
|
+
- **The sun is not white.** The Living sky's sun ran to a near-white disc at noon; it is deep orange on the horizon and warm gold overhead now, which is what a sun looks like to an eye rather than to a camera
|
|
70
|
+
- **The Living sky no longer depends on the Star field switch.** With the Living sky chosen and that switch off, every board went black: a control about stars was hiding the whole day. A board that asks for the Living sky now draws it whatever the star switch says, and the star field still comes out at night by itself
|
|
71
|
+
- **The deep sky belongs to the star field.** The spiral galaxy, nebulae, dust lanes, halo clusters and distant galaxies are off while the Living sky is drawing, the way the Space effects already are, and their rows in Display settings → Sky are dimmed and say "Star field only" (the Living sky's own rows dim the same way under Space). Nothing is forgotten: choosing Space brings them all back as saved. Scorched Yard's panel hides its star and galaxy switches under the Living sky for the same reason
|
|
72
|
+
- **The Block space lamp hangs in six places at three heights**: straight above, top left, top right, bottom left, bottom right, or at the viewer, each low, middle or high (Display settings → Block space → Light and Light height). A saved upper-left, upper-right or front comes forward as top left, top right and the viewer
|
|
73
|
+
- **The Living sky**: a second sky type (Display settings → Sky → Sky: Living sky) for every board that draws a sky — a real day from this machine's clock. The sun rises on the left and sets on the right, the dome passes through night, dawn, day and dusk by the sun's altitude, a twilight glow stands at the sun's side, clouds drift lit from the sun (cumulus in clusters of soft discs, cirrus above them), the moon comes up at tonight's phase with its terminator, and the star field fades in as the sun goes down. A clock setting (real time, a day every 24 minutes, a fixed hour), weather (clear, scattered, overcast, storm with rain and lightning), cloud cover, an optional latitude for real sunrise and season, and switches for crepuscular rays, a rainbow and shooting stars. Drawn under the same canvas rules as everything else (no gradients or blend modes: the dome is one huge soft disc cached until the sun moves half a degree). The Space effects are off while the Living sky is on, their switches kept
|
|
74
|
+
|
|
75
|
+
### Scorched Yard
|
|
76
|
+
|
|
77
|
+
- **Fixed: Scorched Yard's Shooter and Chooser were far too accurate.** Both fired the exact shot the physics search found, with no error: over 200 simulated games the Shooter hit with 60% of its first shots and the Chooser with 95%, as often as the Spoiler the manual calls nearly perfect. Every personality that searches now aims the way a person does, off by an error that grows with the distance and shrinks with each shot at the same target. First-shot hit rates now: Moron and Tosser about 5%, Shooter and Poolshark about 23%, Chooser about 43%, Spoiler about 84%, Cyborg about 76%, and the Shooter closes from 23% to 32% by its third shot. A test holds the order and the bands
|
|
78
|
+
- **Scorched Yard: each personality has its own colour.** A tank took its colour from its seat, so the first opponent was blue and the second green whatever played there. Now You are orange, the Moron yellow, the Shooter blue, the Poolshark cyan, the Tosser green, the Chooser purple, the Spoiler red, the Cyborg pink and the Unknown grey (which gives nothing away about the personality it draws), and a second or third of one kind is a lighter or darker shade. The scoreboard's dot follows the tank's colour too; it had been a colour per seat, so a red Spoiler was listed with a blue dot
|
|
79
|
+
- **Scorched Yard's opponents are shuffled every game.** With the shipped mix, the seats were filled from a fixed list in order, so two opponents were a Shooter and a Tosser in every game. Each new game now deals the six personalities (Shooter, Tosser, Chooser, Spoiler, Cyborg, Poolshark) in a fresh order: two opponents are a different pair each time, up to six are all different kinds, and a seventh starts a new deal. Naming one kind in *Their kind* still fills every seat with it
|
|
80
|
+
- **Scorched Yard: the rest of the control plan.** *Correcting*: click an enemy tank to mark it; after your shot the panel says how it did against it (*10 over*, *6 short*, *close*, *hit*) and **C** sets the power that miss implies, from your own last shot. *Weapons*: **W** opens a grid of what you own, biggest blast first, with counts and keys; **1–9** pick in the shop's order; **Q** picks your last weapon; the item pills are buttons. *The aim guide*: a ghost of the first fifth of the flight while you aim, or the whole flight, or off. *The fire button* names what it will send, and the last of a weapon sold one at a time asks once before it goes. A press on your own tank turns the barrel without touching the power. The keys are a table. Three new settings: Aim guide, Correction helper, Confirm the last of a weapon
|
|
81
|
+
- **Scorched Yard: the old wind code is gone.** The four earlier wind pictures (marching particles, dashed currents, a plasma wash, bowing bands), the smoke dye the fluid first carried, and the air clock only they used are deleted, with the tests that covered only them: the wind module drops from about 400 lines to 69, and the air module loses its dye grid. What is drawn is unchanged — the simulated air, its flow lines, and the sky's faint ripple
|
|
82
|
+
- **Fixed: Scorched Yard's computer players seemed to aim at tanks already destroyed.** A computer player chose its shot only when its thinking pause ran out, so for the whole pause its gauge (and, in cheat mode, its trajectory) still showed its previous shot, usually aimed at the tank it had just destroyed. It now plans the moment its turn begins, against the tanks alive then, and its barrel and power ease from the old aim to the new one while it thinks. Cheat mode draws only your own firing solution. And the random shot that the Moron fires, and that the Shooter and Poolshark fall back on, heads toward a living enemy instead of the middle of the field, which had kept shelling the empty half once the tanks there were gone: in 4,357 simulated shots none now fires away from every tank still standing, against 20 before
|
|
83
|
+
- **Fixed: Scorched Yard froze when the land was blown up.** Measured on a nuke in the browser: frames of 150 ms, and 136 frames over 33 ms in seven seconds. Four causes, each measured and fixed. The simulated air re-ran six seconds of warm-up and read the land canvas back from the GPU on every crater; it now does that only for a new round's land, and a crater moves the air's floor from the rules' own column heights. The land's redraw used the renderer's general paint sort, which builds an outline for every cube and tests pairs for overlap, 43 ms for 1,800 resting cubes twice a blast; resting land now draws in a known order (back rows first, columns outside in), pixel-identical in a full-canvas diff and 7 ms. The flow lines were 5,500 separate round-capped segments a frame; they are continuous paths at half the points. And the wind layer redrew at sixty frames a second during every shot; it is held to thirty, and the effects' soft fills name a modest ring count rather than one per pixel of radius. The same nuke now runs with a worst frame of 33 ms, like the idle game
|
|
84
|
+
- **Scorched Yard: the flow lines fade out at the edges instead of vanishing.** A tracer was removed the moment it crossed the field's edge, so its whole line disappeared at once. Each line segment now fades by how near it is to the left, right or top edge, and a tracer runs on past the boundary, out of sight and already faded, before it is reborn
|
|
85
|
+
- **Scorched Yard: the simulated air is shown as streaklines, not smoke.** Any soft density drifting across the sky reads as smoke however it is tuned. The picture is now the wind-map kind: 320 weightless tracers carried by the simulated velocity, each drawn as a thin line along its recent path, so the lines curve where the air curves — up over a hill, round the eddy behind it. They taper to nothing at both ends with no bright head, and the whole field is four strokes, about a millisecond and a half to draw. The simulation's smoke is no longer computed in the game, which also brings its per-frame cost back to under half a millisecond
|
|
86
|
+
- **Scorched Yard: the air is an actual fluid simulation.** Every earlier wind effect was kinematic, sprites and finally sine curves told where to be at a given moment, so nothing in them could flow round anything. The wind layer now runs a small Stable Fluids solver (96 by 48 cells, about half a millisecond a step): the wind drives the air toward its speed and direction, the land is the solid floor so the air rises over the hills and rolls into eddies behind them, vorticity confinement keeps the eddies alive, and streams of smoke breathed in on the upwind edge are carried, bent and pulled apart by that flow. A new round's land gets eight simulated seconds of warm-up, so the air is already flowing when it is seen; a change of wind turns the air round by its own momentum. The smoke's opacity is compressed so thin smoke downstream stays visible and the dense streams at the edge do not dominate, and it reads stronger against a bright sky than a dark one. The bowing bands are gone; the faint refraction of the sky underneath stays
|
|
87
|
+
- **Scorched Yard: twice the bands of air, and they hold up in daylight.** Ten bowing bands instead of five. Pale air on a pale blue sky has a fraction of the contrast it has on black, so the bands sample how bright the sky behind them is every second and a half and carry up to about three times more at noon, and no more than before at night
|
|
88
|
+
- **Scorched Yard: the wind is seen by what it does to the sky.** Every streak had a bright head and a fading tail, which is the shape of a meteor, so on a starry sky the wind read as shooting stars. The streaks and the dashed currents are gone. The sky behind the land now refracts: it is copied across the wind plane in narrow columns, each lifted or lowered by a wave that rolls downwind at the wind's speed, so the stars, the moon and the clouds sway a few pixels as if seen through moving air; the land in front is untouched. Over it, a handful of broad, faint bands of air bow as they cross, with no head and no point anywhere in them. Both follow a signed distance the air has travelled, so they only move downwind and bend round through zero when the wind turns, and neither draws anything in still air
|
|
89
|
+
- **Scorched Yard: the air at half the count, tuned for night as well as day.** Each particle is a little brighter and thicker with a longer tail, so the wind still reads at a glance with half the particles it had; the currents are few, thin and faint, since on a dark sky at a strong wind they were a wall of dashes
|
|
90
|
+
- **Scorched Yard: half the air particles.** The wind layer runs half as many particles as it did (at most 210 on a wide panel, down from 420); the tails and the currents carry the motion, so the picture keeps its flow at half the per-frame cost
|
|
91
|
+
- **Scorched Yard: every one of the twenty-seven shells that fly looks like itself.** Every projectile was the same white dot. Each weapon now has its own entry: its core colour and size, a glow in its colour, a trail in its manner at its own length, count, width and colour, and a ring for the heavy ones. Within a family the baby is small and pale, the plain one middling, the heavy one big, dark and ringed, so a missile is not a baby missile and a Death's Head is not a MIRV. The manners: a missile's exhaust, a nuke's pulsing green glow and falling sparks, a comet tail for the MIRV, the Leap Frog and the Death's Head, a Funky Bomb's rainbow (its bomblets spin it faster), a tracer's thin grey dots and no glow, a smoke tracer's grey puffs, sparks behind the rollers, pale wisps behind the riot bombs, shed clods behind the dirt shells and droplets behind liquid dirt, flickering fire and dripping embers on napalm and a blue-white flame on hot napalm, spinning arms of glint on the diggers and sandhogs
|
|
92
|
+
- **Scorched Yard's land is brighter.** The tanks and the land go through the same lamp; the tanks looked lit because their paint is bright. Each stratum is lifted a fifth to a quarter in value at the same saturation, so the front of the land sits where the front of a tank does
|
|
93
|
+
- **Scorched Yard: the mouse drag clicks.** The keys and the wheel clicked as the aim moved; dragging on the field was silent. It clicks on every whole degree and every ten of power it crosses, no more than every 45 ms, so a slow drag ticks and a fast one whirs
|
|
94
|
+
- **Fixed: Scorched Yard's panel showed the wrong tank's cash while you shopped.** A purchase always took the money; the "This turn" panel was showing the tank whose turn it was when the round ended, and when that was a computer player its cash sat there unmoved while yours went down in the shop's own header. Between rounds the panel shows the human, marked "shopping"
|
|
95
|
+
- **Scorched Yard: vibrant strata, and a lamp that no longer bleaches them.** The land's five strata were dust-and-shadow browns that read as tinted whatever the lamp did; they are the same strata at full saturation now — hot magma, slate rock with some blue in it, terracotta clay, golden soil, a green that is green. The lamp gain comes down from 2.15 to 1.7: the front face sits at about 1.1 of its own colour and the face turned away under 0.2, where the higher gain had pushed the front past the point a channel clips and a saturated colour goes pale, which was the wash that kept being reported
|
|
96
|
+
- **Fixed: tanks in the air and in the ground after a new Scorched Yard game.** The land canvas was redrawn only when the land's version number moved, and a fresh game restarts that count where the last one began, so a new game after changing the opponent count (or a restart before a shot) placed the new tanks over the old terrain. The cache is cleared on every new game and every new round. The placement itself was always right: a test now stands three, five and eight seats on their ground over forty seeds each
|
|
97
|
+
- **Scorched Yard: the air is a steady flow with currents.** Reborn particles used to re-enter at the upwind edge, so in a light wind the field was dense there and empty downwind, and the density itself read as a front rolling in; a particle that runs out of life is reborn anywhere now, and only one that leaves the field comes back at the edge. Each particle carries a tail, drawn as a ribbon that thins and fades, so what moves reads as carried rather than as dots. And the currents themselves are drawn: a few dozen streamlines traced through the same field, bending round the same eddies, animated by a dash offset that runs downwind at the wind's speed
|
|
98
|
+
- **Fixed: Scorched Yard's air rushed backwards through a change of wind.** The wind layer's wash and its eddies took the page's clock and multiplied it by a rate that depended on the wind, so on a page a day old the smallest change in the wind moved them by a day's worth of travel, and flipped them end to end when the direction turned. The air keeps its own clock now, integrated a frame at a time at the current wind's rate: a change of wind changes how fast it runs from there on, and nothing else. And the field no longer scrolls its two octaves against each other vertically, which read as motion against the wind: it morphs in place, and the wind's drift is the only travel there is
|
|
99
|
+
- **Scorched Yard: eight seats.** Computer players go up to seven, for the original's eight-tank table; two is still the shipped game. Eight distinct tank colours, and the field seats eight without overlap
|
|
100
|
+
- **Scorched Yard: cheat mode.** A switch on the panel and in Display settings draws the firing solution while you aim: the shell's own path under this round's wind and the game's gravity, clipped where it would meet the dirt, with a ring where it lands. It is recomputed every frame the aim moves, so the arc bends live under the wind. It is the rules' own trajectory, so what it draws is what the shot does
|
|
101
|
+
- **Scorched Yard: the air is a flow field.** The wind layer is no longer dashes told where to be at time t: it is particles advected through the curl of a drifting noise field, over a faint plasma wash of the same flow, so what moves is material being carried, bent by eddies on the way. The swirl is capped at a third of the base wind, so every particle nets downwind and no streak ever points upwind. A change of wind bends the flow into the new speed and direction over half a second instead of swapping layers
|
|
102
|
+
- **Scorched Yard: the wind's strength changes every turn, its direction once a round.** Holding both for a whole round made the wind something you read once and forgot; holding neither was the flip-flop that started this. The round draws a direction and keeps it, and every turn draws a fresh strength within it
|
|
103
|
+
- **Scorched Yard: the air runs at one steady speed.** The wind layer carried a gust — a slow wave that lengthened and quickened every streak together by a third either way. It never reversed (measured frame by frame, no streak ever moves upwind) but a stream that surges and eases while the gauge reads one number does not look like that number. The gust is gone: the streaks advance at exactly the rate the gauge says
|
|
104
|
+
- **Scorched Yard: a restart button.** A war is five rounds, and a player knocked out in round one had nothing to do but watch the computer finish it. **Restart** on the panel, or **F2**, starts a fresh war from round one at any moment, and a knocked-out player is told so
|
|
105
|
+
- **Scorched Yard: the wind changes by crossfade, and says which way it blows.** A new wind used to replace the old one between two frames, so every streak jumped. The old air thins away while the new air comes up over it, already at its own speed and in its own direction, over about half a second; the new one leads, so the direction is readable long before the old one is gone. A row of chevrons at the top of the field points downwind, as many as the wind is strong, brightest just after a change
|
|
106
|
+
- **Scorched Yard's wind holds for a round.** A new wind mode, **Once a round**, is how the game ships: the wind is drawn when the round starts and keeps its direction and its strength until the round ends, so the air blows one way while you read it and every shot in the round is judged against the same gauge. The original's every-turn wind is still there under Display settings → Scorched Yard → Wind, and it can still turn right around between two shots
|
|
107
|
+
- **Fixed: nothing could be bought in Scorched Yard's shop.** Every panel decided whether to redraw by comparing its new markup with `box.innerHTML`, which a browser hands back normalised, so the comparison never matched and the markup was rebuilt on every draw. That was harmless while the board only drew on events and fatal once the wind kept it drawing thirty times a second: a button replaced between the press and the release is never clicked. Each panel remembers what it wrote, and the shop keeps its scroll position when something is bought
|
|
108
|
+
- **Scorched Yard's controls, the first step of the rescoped plan.** A held arrow accelerates (one a step, then two, then five, so the whole arc is about a second and a half and the last degree is still one press); Shift is fine and Ctrl coarse, and the keys line says so; **, and .** nudge the power by one for bracketing; the **wheel** over the field changes power, and with Shift the angle; **click the angle or the power** on the panel and type it; **R** fires the last shot again
|
|
109
|
+
- **Scorched Yard's explosions are painted, not built out of tiles.** The renderer already knew how to draw a firework — a white core, a shockwave, sparks that curve and trail and twinkle, and smoke built from flat discs that add up to a soft cloud — and a shell landing now draws with the same machinery, through the board's own projection, so the fire is exactly over the crater it made. A tank going up takes its own colour, and the dirt's dust is a low puff. `details3d` gained one seam for it: a board may hand the renderer an `overlay` to paint after the cubes, with the projector and `softStops`
|
|
110
|
+
- **Scorched Yard: the aim gauge.** A protractor stands over the tank whose turn it is — ticks every fifteen degrees, the round numbers written in, a needle along the current angle whose length is the power, and the angle and power printed at its tip. It dims for a computer player's turn, so you can see what it is about to do
|
|
111
|
+
- **Scorched Yard is lit from the viewer.** The field was lit from straight overhead, which lit the tops of the cubes and left every face you actually look at on one flat shade. The lamp stands low and at the viewer now, so the front of the board carries the light and the strata separate
|
|
112
|
+
- **Scorched Yard, the wind is its own flat layer.** It used to be tiles in the 3D field: they bobbed as they crossed, they were drawn over the hills they should have passed behind, and a gust that eased off dragged them backwards (the wind appeared to change direction while you were thinking). The air is now a plain 2D plane between the sky and the land — level streaks whose length is the wind's speed, in three bands of depth, the land in front of them hiding whatever goes behind a hill. It keeps blowing while the board is idle, at its own rate, and only ever blows one way
|
|
113
|
+
- **Scorched Yard, the blast is made of blocks.** The fireball, the shockwave, the embers and the smoke were spheres, and the engine gives a sphere a rim and a glint, so an explosion was a heap of bubbles on the dirt. They are blocks now, like the land, the tanks and the shells: a fireball on the field's own grid, hot at the centre and dark at the rim, a ring racing out, embers thrown on ballistic paths, and smoke that rises, spreads, drifts downwind and thins by its alpha rather than shrinking away
|
|
114
|
+
- **Scorched Yard, attract mode** (M5): a **watch** switch on the game's panel (and Display settings → Scorched Yard → Attract mode) hands your chair to another computer player — every seat is a computer player, a round rolls into the next without waiting at the shop, and when one of them wins the war a fresh one begins. Nothing is written to the high-score table for a war nobody played
|
|
115
|
+
- **Scorched Yard**: the field runs to the frame's edges and its bottom (no sky under or beside the land); the wind is visible — motes of dust drift across the field at its speed and in its direction, a pennant on every turret streams downwind and lifts with the strength, and under the Living sky the clouds drift with the round's wind
|
|
116
|
+
- **Scorched Yard, the look** (M4): a shockwave ring and smoke that rises, grows and drifts on the wind after every blast; a tank's death as sparks in its colour and three pieces of hull tumbling; dust where fallen dirt lands; a tank's fall animated, by gravity or slowly under a drawn parachute; tracks under the hull; the tanks talk ("Nuke 'em!", "Ouch!", "Tell my wife…") in a bubble over the field; a march in D minor as the game's music; under the Living sky each round draws its own hour; music, talk and per-round-sky switches; a screenshot in the guide
|
|
117
|
+
- **Scorched Yard, the computer players** (M3): the manual's eight personalities — Moron, Shooter (straight shots), Poolshark (bank shots off rubber walls), Tosser (lobs, correcting by halves), Chooser (the best of the three), Spoiler (nearly perfect, from the wind and gravity), Cyborg (a Spoiler with a grudge for whoever hit it, the weakest, the leader), Unknown (drawn each round) — planning with a side-effect-free shot simulator that carries the walls, the dirt and the tanks; they use batteries and shields before firing and shop to their tastes; a setting fields a mix or one kind
|
|
118
|
+
- **Scorched Yard, the roster and the shop** (M2): the manual's thirty-three weapons at the manual's prices, packs and blast radii (SCORCH.DOC) with the original's behaviours — the four blasts, Leap Frog's three growing warheads, Funky Bomb, MIRV and the nine-head Death's Head, the tracers, three rollers, the riot charge and blast as wedges cut from the turret, the riot bombs, the dirt shells, liquid dirt that oozes and fills, the dirt charge, the earth disrupter, napalm that flows and burns, the diggers and sandhogs that bore, plasma thrown from the tank, the laser — and eleven accessories (Shield, Force and Heavy shields, Mag Deflector and Super Mag, parachute, battery, auto defense, fuel, contact trigger, heat guidance); the manual's six wall modes with none as the default; several shells in flight at once; cash for damage, kills and survival, interest between rounds, and the shop in the round-end overlay; the items line and cash on the HUD; keys to drive, heal, shield and arm; the computer players shop too; fire and beams on screen
|
|
119
|
+
- **Scorched Yard, the first cut** (docs/PLAN-SCORCHED-YARD.md, M1): Scorched Earth on the block engine, under Diversions. A 96×48 field of dirt cubes drawn as runs by stratum, tanks with a turning barrel, a shell that flies under gravity and wind with the original's angle and power, craters, dirt that falls and settles, fall damage, death and its blast, the four wall modes, four missiles, one human against two computer players (the Moron; more opponents and personalities to come), turns, a game of rounds, a HUD with the wind and every tank's health, the mouse to aim, a high-score table, and a settings group for the rules
|
|
120
|
+
|
|
9
121
|
## [0.1.0] — 2026-09-16
|
|
10
122
|
|
|
11
123
|
The first minor release, two days after the initial 0.0.9, and everything since it in one place:
|
package/README.md
CHANGED
|
@@ -11,7 +11,7 @@ connection but to your node out of the box, and read-only toward your node by de
|
|
|
11
11
|
**This is 100% machine-generated code, directed by a human operator.** Every line of the
|
|
12
12
|
server, the browser app, the 3D engine, the tests and these documents was written by an AI
|
|
13
13
|
under a human's direction, and all auditing has been performed by AI and is published in this
|
|
14
|
-
repository ([docs/SECURITY-AUDIT.md](docs/SECURITY-AUDIT.md), [docs/SECURITY-AUDIT-2026-09-14.md](docs/SECURITY-AUDIT-2026-09-14.md), [docs/DEFECTS.md](docs/DEFECTS.md),
|
|
14
|
+
repository ([docs/SECURITY-AUDIT.md](docs/SECURITY-AUDIT.md), [docs/SECURITY-AUDIT-2026-09-14.md](docs/SECURITY-AUDIT-2026-09-14.md), [docs/SECURITY-AUDIT-2026-09-16.md](docs/SECURITY-AUDIT-2026-09-16.md), [docs/DEFECTS.md](docs/DEFECTS.md),
|
|
15
15
|
[docs/MEASUREMENTS.md](docs/MEASUREMENTS.md)). It is **experimental pre-release software: expect
|
|
16
16
|
bugs.**
|
|
17
17
|
|
|
@@ -26,7 +26,7 @@ bugs.**
|
|
|
26
26
|
| **An explorer that looks the part.** Search a height, block hash, txid or address. Transaction pages with fee, fee rate and dollar value, feature badges, a flow diagram from inputs to outputs, and links to where every coin came from and went. **Address pages with full history and balance** — Bitcoin Core has no address index, so BlockYard builds its own from the node's block files (**a few hours** on first start, 124 GB) and keeps it current as blocks arrive. |  |
|
|
27
27
|
| **Markets.** Five exchanges' public prices: a 3D candle chart with a neon price line, a precise flat candlestick chart, an exchange table, and a bitcoinity-style order-book depth chart with change bars. Off until you tick **Enable market polling** — it is the one thing that talks to anyone but your node — and, once on, fetched by the server only while someone is looking. |  |
|
|
28
28
|
| **Kiosk.** The 3D markets board, a price panel and the block-space board side by side, full screen with one click. |  |
|
|
29
|
-
| **Tetrust, Blockout and Blockanoid.** Three playable games built on the same 3D engine — trust, but verify. Tetrust is Tetris: the well is the block-space board and the music is synthesised in the browser. Blockout is Breakout, where the wall is made of block-space stones and the bat follows your mouse. Blockanoid is Arkanoid: a different wall every level, silver bricks that take more than one hit, gold that takes none, and capsules that fall out of what you break — laser, wide, catch, slow, three balls, a life. All three pause when you look away and keep high scores per browser. And **Wolfenstein 3D**, **DOOM** and **Quake**: the real shareware `WOLF3D.EXE` (1992), `DOOM.EXE` (1993) and `QUAKE.EXE` (1996), unmodified, on a PC emulated in the browser — processor in real and protected mode, FPU, DOS and both extenders, VGA and Sound Blaster written from scratch, still with no dependencies. So if anyone asks *"well, can it run DOOM?"*, the answer is an emphatic **"Naturally. What sort of AI slop generator do you take me for?"** |  |
|
|
29
|
+
| **Tetrust, Blockout and Blockanoid.** Three playable games built on the same 3D engine — trust, but verify. Tetrust is Tetris: the well is the block-space board and the music is synthesised in the browser. Blockout is Breakout, where the wall is made of block-space stones and the bat follows your mouse. Blockanoid is Arkanoid: a different wall every level, silver bricks that take more than one hit, gold that takes none, and capsules that fall out of what you break — laser, wide, catch, slow, three balls, a life. All three pause when you look away and keep high scores per browser. **Scorched Yard** is Scorched Earth on the same engine: a landscape of dirt cubes that craters and falls, the original's thirty-three weapons and eleven accessories at the manual's prices, its eight computer personalities, cash and a shop between rounds, and the wind you can see. And **Wolfenstein 3D**, **DOOM** and **Quake**: the real shareware `WOLF3D.EXE` (1992), `DOOM.EXE` (1993) and `QUAKE.EXE` (1996), unmodified, on a PC emulated in the browser — processor in real and protected mode, FPU, DOS and both extenders, VGA and Sound Blaster written from scratch, still with no dependencies. So if anyone asks *"well, can it run DOOM?"*, the answer is an emphatic **"Naturally. What sort of AI slop generator do you take me for?"** |  |
|
|
30
30
|
|
|
31
31
|
Also on board: a sync viewer with an honest ETA, Block flow (projected blocks, the block
|
|
32
32
|
being built, recent blocks), mempool and fee charts, a peer table, bandwidth, the node's
|
|
@@ -37,7 +37,7 @@ Nous, GitHub, Catppuccin, or nine colours of your own.
|
|
|
37
37
|
|
|
38
38
|
## Quick start
|
|
39
39
|
|
|
40
|
-
You need **Node.js 22 or newer** and a running **Bitcoin Core 25.0 or later** with `server=1`
|
|
40
|
+
You need **Node.js 22.2 or newer** and a running **Bitcoin Core 25.0 or later** with `server=1`
|
|
41
41
|
and `txindex=1`, **on the same machine** — BlockYard reads the node's block files to build the
|
|
42
42
|
explorer's address index, and a node on another machine is not supported. Without `txindex` the
|
|
43
43
|
explorer cannot look a confirmed transaction up by id; everything else works without it (see
|
|
@@ -47,7 +47,7 @@ top of the node's own ~875 GB of block files.
|
|
|
47
47
|
```bash
|
|
48
48
|
git clone https://github.com/BobClawblaw/blockyard.git
|
|
49
49
|
cd blockyard
|
|
50
|
-
npm test # optional:
|
|
50
|
+
npm test # optional: 1070 unit tests, all built in
|
|
51
51
|
npm run setup # reads the node's bitcoin.conf, checks the node, writes config/local.json
|
|
52
52
|
npm start # builds the address index in the background (a few hours); open https://127.0.0.1:21000
|
|
53
53
|
# and sign in as admin with the password the first start prints once
|
|
@@ -136,7 +136,7 @@ Details in [docs/SECURITY.md](docs/SECURITY.md). To report a vulnerability, see
|
|
|
136
136
|
npm run dev # fake node doing a simulated sync, port 18088
|
|
137
137
|
npm run setup # interactive install: read bitcoin.conf, check the node, write config/local.json
|
|
138
138
|
npm run check # the same checks (every call timed) against every configured node; exits 1 on a FAIL
|
|
139
|
-
npm test #
|
|
139
|
+
npm test # 1070 unit tests (node:test, no dependencies)
|
|
140
140
|
npm run smoke # boots the real server and checks the HTTP contract
|
|
141
141
|
npm run counts:fix # keep the documented test count in step with the suite
|
|
142
142
|
```
|
|
@@ -146,9 +146,10 @@ RPC etiquette) and [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) for how it fits
|
|
|
146
146
|
|
|
147
147
|
## Status
|
|
148
148
|
|
|
149
|
-
Version **0.1.
|
|
150
|
-
|
|
151
|
-
|
|
149
|
+
Version **0.1.2** (2026-09-17) — a hardening release after 0.1.0: the third security audit with
|
|
150
|
+
every finding fixed, a resumable address index build, and Scorched Yard. Pre-release software: the
|
|
151
|
+
word is meant literally. Releases are published on [npm](https://www.npmjs.com/package/blockyard)
|
|
152
|
+
as `blockyard`, as [GitHub releases](https://github.com/BobClawblaw/blockyard/releases),
|
|
152
153
|
and announced on
|
|
153
154
|
[bitcointalk](https://bitcointalk.org/index.php?topic=5594141.msg67144312) — questions, bug
|
|
154
155
|
reports and reviews are welcome there and in [issues](https://github.com/BobClawblaw/blockyard/issues).
|
|
@@ -156,7 +157,7 @@ It ships **hardened**: bound to this machine, sign-in on, HTTPS with a certifica
|
|
|
156
157
|
and **zero telemetry** — no outbound connection to anyone but your node until you tick the market
|
|
157
158
|
polling switch yourself.
|
|
158
159
|
The test suite is
|
|
159
|
-
comprehensive (
|
|
160
|
+
comprehensive (1070 tests, plus a live smoke run), the monitoring side is solid, and the
|
|
160
161
|
explorer's biggest gap is closed: **address history and balances**, which Bitcoin Core cannot
|
|
161
162
|
answer at any setting, now come from an **address index BlockYard builds itself** from the
|
|
162
163
|
node's block and undo files and keeps current as blocks arrive. It is checked against the node
|
|
@@ -167,8 +168,9 @@ zero. An address's unspent outputs are listed too (for a history of up to 100 tr
|
|
|
167
168
|
What it does not yet have: an address's mempool transactions.
|
|
168
169
|
|
|
169
170
|
Everything here was written by an AI directed by a human, and audited by AI:
|
|
170
|
-
[docs/SECURITY-AUDIT.md](docs/SECURITY-AUDIT.md)
|
|
171
|
-
[docs/SECURITY-AUDIT-2026-09-14.md](docs/SECURITY-AUDIT-2026-09-14.md)
|
|
171
|
+
[docs/SECURITY-AUDIT.md](docs/SECURITY-AUDIT.md),
|
|
172
|
+
[docs/SECURITY-AUDIT-2026-09-14.md](docs/SECURITY-AUDIT-2026-09-14.md) and
|
|
173
|
+
[docs/SECURITY-AUDIT-2026-09-16.md](docs/SECURITY-AUDIT-2026-09-16.md) are the audits, findings
|
|
172
174
|
and remediation included. The test suite runs in CI on Ubuntu, macOS and Windows (Node 22 and 24);
|
|
173
175
|
a real install has been done on macOS (Core 29.1) and Linux, and Windows has only the test suite.
|
|
174
176
|
[docs/DEFECTS.md](docs/DEFECTS.md) lists five open items, honestly stated, with the
|
package/SECURITY.md
CHANGED
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
|
|
5
5
|
| version | supported |
|
|
6
6
|
|---|---|
|
|
7
|
-
| 0.
|
|
8
|
-
|
|
|
7
|
+
| 0.1.x | yes |
|
|
8
|
+
| 0.0.9 and earlier | no: upgrade. 0.0.9 shipped bound to every interface with no sign-in, and later audits fixed issues it still has |
|
|
9
9
|
|
|
10
10
|
## Reporting a vulnerability
|
|
11
11
|
|
package/docs/API.md
CHANGED
|
@@ -1155,7 +1155,7 @@ The default is **deny**. The rules are applied in this order, and the first matc
|
|
|
1155
1155
|
Consequences worth knowing:
|
|
1156
1156
|
|
|
1157
1157
|
- The list is fixed in code, and no setting widens it.
|
|
1158
|
-
-
|
|
1158
|
+
- Wallet RPCs are refused by name, reads included (`getbalance`, `listunspent`, `listdescriptors`, `gethdkeys`, …): some of them return private keys, and the monitor has no use for a wallet.
|
|
1159
1159
|
- `createrawtransaction` is allowed: it only builds an unsigned transaction and changes nothing.
|
|
1160
1160
|
- `/api/config` → `allowlist` publishes a summary: the prefixes, the size of the deny list and the default decision.
|
|
1161
1161
|
|
package/docs/ARCHITECTURE.md
CHANGED
|
@@ -212,8 +212,8 @@ it directly; running the file as a script calls it and prints the banner.
|
|
|
212
212
|
`address-index-building` quality flag, `index` events at start, finish and
|
|
213
213
|
failure, and the follower started on completion (section 2.8).
|
|
214
214
|
`app.shutdown()` stops timers, closes streams, stops monitors, saves history
|
|
215
|
-
and sessions, and closes the listeners. A build in flight
|
|
216
|
-
the next start
|
|
215
|
+
and sessions, and closes the listeners. A build in flight stops with the
|
|
216
|
+
process and resumes on the next start from its journal (section 2.8).
|
|
217
217
|
|
|
218
218
|
`boot({ log })` and `loadConfig({ ifaces, now })` are **seams**: tests inject a
|
|
219
219
|
logger, a fake interface list, or a fake clock instead of intercepting
|
|
@@ -577,7 +577,24 @@ files. The numbers are in `docs/MEASUREMENTS.md` §28-30 and the history in
|
|
|
577
577
|
bucket files; a check that every height is indexed exactly once, or the build
|
|
578
578
|
stops rather than publish a hole; each bucket sorted into `seg-XX.rows` plus a
|
|
579
579
|
sparse `seg-XX.idx` (one key per 4,096 rows); and a manifest written last, so an
|
|
580
|
-
index without one is unfinished.
|
|
580
|
+
index without one is unfinished. **An interrupted build resumes** from
|
|
581
|
+
`build-journal.json`, written beside the output with a temporary name, fsync and
|
|
582
|
+
rename, and carrying a SHA-256 of its own body. The scan resumes per block file:
|
|
583
|
+
a checkpoint (at most once a minute, and when the scan ends or fails in an orderly
|
|
584
|
+
way) fsyncs the buckets written since the last one and records the files
|
|
585
|
+
finished, each bucket's length and running CRC-32, the heights seen and the
|
|
586
|
+
counters; a resume cuts every bucket back to its journaled length, so rows from a
|
|
587
|
+
half-appended file are gone, and scans that file again. The sort resumes per
|
|
588
|
+
bucket: the worker checks the input against the journaled length and CRC, writes
|
|
589
|
+
the segment with fsync and rename, the journal records it, and only then is the
|
|
590
|
+
input removed. The journal names the journal version, index format, row and
|
|
591
|
+
block sizes, chain, tip height and hash and the `--files` selection; a resume
|
|
592
|
+
works to the journal's tip, not the node's newer one, so the result is byte for
|
|
593
|
+
byte the index an uninterrupted build would have written. A journal that is torn,
|
|
594
|
+
of another format or selection, whose tip hash the node's active chain no longer
|
|
595
|
+
has, or whose files on disk disagree with it is discarded with the reason logged,
|
|
596
|
+
and the build starts over. The manifest is still last; the journal is removed
|
|
597
|
+
after it. Measured on the whole chain: 29 min 45 s on 16
|
|
581
598
|
workers, 5.89 billion rows, 123.7 GB (§30). The build reads ~880 GB and writes
|
|
582
599
|
~120 GB, so `--out` should be a different device from the block files.
|
|
583
600
|
- **Lookups** (`chain/index/store.js`, `IndexStore`). The sparse keys of the 256
|
|
@@ -607,8 +624,8 @@ files. The numbers are in `docs/MEASUREMENTS.md` §28-30 and the history in
|
|
|
607
624
|
the phase's own rate, `paused while the node's RPC is slow`), refreshed at most
|
|
608
625
|
every 5 s; `index` events mark start, finish and failure; on finish the follower
|
|
609
626
|
starts and address pages go live with no restart; failure leaves
|
|
610
|
-
`address-index-build-failed` naming the command to run by hand.
|
|
611
|
-
|
|
627
|
+
`address-index-build-failed` naming the command to run by hand. A build stopped with the server resumes next time; the flag's ETA is
|
|
628
|
+
computed from what this run has done (`from` in the progress), not from zero.
|
|
612
629
|
- **Following the chain** (`chain/index/live.js`, `LiveIndex`). The base is
|
|
613
630
|
immutable and covers the chain to the block it was built at. The follower polls
|
|
614
631
|
the node's tip, rolls the tail back to the fork if a block it holds is no longer
|
|
@@ -877,6 +894,20 @@ The game files are served from `games/<game>_dos/` by `server/http/games.js`
|
|
|
877
894
|
`.PAK`, `.CFG` and `.WL1`, behind the session when accounts are on) rather than from `public/`, whose every
|
|
878
895
|
file feeds the build id.
|
|
879
896
|
|
|
897
|
+
**Provenance** (2026-09-16, audit I6). The files are the three shareware releases, kept byte for byte
|
|
898
|
+
(`.gitattributes` marks `games/**` binary so git never rewrites them), and are served to the browser,
|
|
899
|
+
never executed on the server:
|
|
900
|
+
|
|
901
|
+
| Directory | Release | Its own terms, as shipped |
|
|
902
|
+
|---|---|---|
|
|
903
|
+
| `games/wolf3d_dos/` | Wolfenstein 3D v1.4 shareware (Apogee Software, 1992) | none beyond `file_id.diz`: the release's shareware licence text is not bundled |
|
|
904
|
+
| `games/doom_dos/` | DOOM v1.9 shareware (id Software; `DOOM1.WAD`) | `README.TXT`, `ORDER.FRM`, the DOOM FAQ v6.666 |
|
|
905
|
+
| `games/quake_dos/` | Quake v1.06 shareware (id Software, 1 October 1996) | `LICINFO.TXT`, `SLICNSE.TXT`, `READV106.TXT` |
|
|
906
|
+
|
|
907
|
+
`games/SHA256SUMS` lists the SHA-256 of every tracked file under `games/` (`cd games && sha256sum -c
|
|
908
|
+
SHA256SUMS`), and `test/audit-2026-09-16-low-scripts.test.js` recomputes them, so a changed game file
|
|
909
|
+
is a failing test rather than an unnoticed one.
|
|
910
|
+
|
|
880
911
|
## 4. The 3D engine
|
|
881
912
|
|
|
882
913
|
The 3D viewer turns a set of transactions, or any caller-supplied tiles, into
|
package/docs/CONFIGURATION.md
CHANGED
|
@@ -129,7 +129,7 @@ node gets its own charts and event stream.
|
|
|
129
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
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
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
|
|
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 resumes on the next start: a journal in the index directory records the block files already scanned and the buckets already sorted, so only the work it cannot prove finished is done again (at most about a minute of scanning), and the log says where it resumed. A journal for another tip that the node no longer has, another index format, or one that is damaged is thrown away with the reason in the log, and the build starts over. `npm run setup --build-later` writes this key. |
|
|
133
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
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
135
|
| `color` | `"#f7931a"` | Accent colour for this node in the UI. |
|
|
@@ -252,7 +252,7 @@ sessions, CSRF protection and a per-user audit trail.
|
|
|
252
252
|
|
|
253
253
|
| key | default | meaning |
|
|
254
254
|
|---|---|---|
|
|
255
|
-
| `auth.enabled` | `
|
|
255
|
+
| `auth.enabled` | `true` | Accounts. `false` is open mode: anyone who can reach the port reads the monitor as a viewer. |
|
|
256
256
|
| `auth.dataDir` | same as `store.dir` | Where `users.json` and `sessions.json` live. |
|
|
257
257
|
| `auth.sessionTtlMs` | `259200000` (72 h) | Absolute session lifetime, counted from sign-in. |
|
|
258
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. |
|
|
@@ -264,6 +264,7 @@ sessions, CSRF protection and a per-user audit trail.
|
|
|
264
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
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
266
|
| `auth.lockoutMs` | `600000` (10 min) | How long a username stays locked after too many failures. |
|
|
267
|
+
| `auth.openNodeConfigFromNetwork` | `false` | With accounts off, the node connection form (test and save) answers only a caller on this machine's loopback address, and never behind `server.trustProxy`. A browser check cannot stop a script, and a script that can save the connection decides where the node's cookie is sent after the next restart. `true` lets any client that reaches the port use the form. With accounts on, the form needs an admin either way. |
|
|
267
268
|
| `auth.cookieName` | `"blockyard_sid"` | Name of the session cookie. |
|
|
268
269
|
| `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
|
|
|
@@ -400,7 +401,8 @@ Environment variables override `config/local.json`.
|
|
|
400
401
|
| `BLOCKYARD_RPC_STALE_DROP` | `rpc.staleDropMs` | number | `12000` | Drop poll answers older than this. |
|
|
401
402
|
| `BLOCKYARD_DATA` | `store.dir` | path | `<repo>/data` | Data directory. Also the default `auth.dataDir`. |
|
|
402
403
|
| `BLOCKYARD_RETENTION_HOURS` | `store.retentionHours` | number | `72` | Chart history retention. |
|
|
403
|
-
| `BLOCKYARD_AUTH` | `auth.enabled` | boolean | `
|
|
404
|
+
| `BLOCKYARD_AUTH` | `auth.enabled` | boolean | `true` | Accounts; `0` is open mode. |
|
|
405
|
+
| `BLOCKYARD_OPEN_NODE_CONFIG_FROM_NETWORK` | `auth.openNodeConfigFromNetwork` | boolean | `false` | In open mode, let any client use the node connection form, not only this machine. |
|
|
404
406
|
| `BLOCKYARD_SECURE_COOKIE` | `auth.secureCookie` | boolean | `false` | `Secure` session cookie. Use it behind a TLS terminator; it is automatic with built-in TLS. |
|
|
405
407
|
| `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
408
|
| `BLOCKYARD_ENABLE_ACTIONS` | `actions.enabled` | boolean | `false` | Master switch for node writes. |
|
|
@@ -745,7 +747,7 @@ accounts and chart history, and never commit or publish it.
|
|
|
745
747
|
| `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
748
|
| `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
749
|
| `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. |
|
|
750
|
+
| `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. Before replacing an existing file it prints the pools added, removed and changed and writes only with `--yes` or a "y" at the prompt; `--expect-sha256 <hex>` refuses a download that is not exactly those bytes; redirects are followed to HTTPS only. |
|
|
749
751
|
| `fake-node.log` | development mode only | no | Log of the simulated node when `BLOCKYARD_FAKE_NODE=1`. |
|
|
750
752
|
| `*.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
753
|
|
package/docs/DEFECTS.md
CHANGED
|
@@ -744,7 +744,10 @@ Kept as checked rather than deleted, so nobody re-derives them.
|
|
|
744
744
|
budget has no room for a fourth figure and rules 4/9 forbid merging them. They now
|
|
745
745
|
appear as three separately-labelled rows behind `detail`, where "not printed by this
|
|
746
746
|
build" is a possible answer and the absence is visible.
|
|
747
|
-
- [
|
|
747
|
+
- [x] **An interrupted index build starts over.** Noted 2026-09-14. Fixed 2026-09-16: the build
|
|
748
|
+
keeps `build-journal.json` beside its output and resumes per scanned block file and per sorted
|
|
749
|
+
bucket, cutting the buckets back to what the journal proves (`server/chain/index/build.js`, THE
|
|
750
|
+
BUILD JOURNAL). What was noted: `buildIndex` empties its output
|
|
748
751
|
directory before it begins (`server/chain/index/build.js`), and the server treats an index as
|
|
749
752
|
built only when `manifest.json` exists (`server/main.js`), so a build stopped by Ctrl-C, a crash
|
|
750
753
|
or a reboot keeps nothing of its scan: the installer says so when it is stopped, and the server's
|
package/docs/GETTING-STARTED.md
CHANGED
|
@@ -137,8 +137,8 @@ node scripts/index-build.js --out data/index --workers 4
|
|
|
137
137
|
```
|
|
138
138
|
|
|
139
139
|
Last question: **start BlockYard now, in this terminal?** — yes runs it right there (Ctrl-C
|
|
140
|
-
stops it, and stops a background build with it;
|
|
141
|
-
|
|
140
|
+
stops it, and stops a background build with it; the next start resumes the build where it
|
|
141
|
+
stopped); `--start` does the same without asking.
|
|
142
142
|
|
|
143
143
|
Scripted, with no questions (a fresh machine, a Makefile):
|
|
144
144
|
|
|
@@ -147,10 +147,17 @@ node scripts/setup.js --yes --rpc-url http://127.0.0.1:8332 \
|
|
|
147
147
|
--datadir "$HOME/Library/Application Support/Bitcoin" --index-dir "$PWD/data/index" --workers 4
|
|
148
148
|
```
|
|
149
149
|
|
|
150
|
-
Every question has a flag: `--datadir`, `--rpc-url`, `--label`, `--rpc-user` /
|
|
151
|
-
`--host`, `--port`, `--index-dir`, `--workers`. `--build-here` builds in the
|
|
152
|
-
`--build-later` leaves it to you; `--start` boots the monitor at the end; `--force`
|
|
153
|
-
existing `config/local.json` (with a backup).
|
|
150
|
+
Every question has a flag: `--datadir`, `--rpc-url`, `--label`, `--rpc-user` /
|
|
151
|
+
`--rpc-password-file`, `--host`, `--port`, `--index-dir`, `--workers`. `--build-here` builds in the
|
|
152
|
+
terminal and `--build-later` leaves it to you; `--start` boots the monitor at the end; `--force`
|
|
153
|
+
replaces an existing `config/local.json` (with a backup, also mode `0600`, and git-ignored).
|
|
154
|
+
|
|
155
|
+
The RPC password, when the node needs one: `--rpc-password-file PATH` reads the first line of a
|
|
156
|
+
file, and `--rpc-password -` reads it from stdin (with `--yes`, e.g.
|
|
157
|
+
`pass show node/rpc | node scripts/setup.js --yes --rpc-user monitor --rpc-password - ...`).
|
|
158
|
+
`--rpc-password P` still works but prints a warning: a password on the command line is visible to
|
|
159
|
+
every user of the machine in `ps` and stays in your shell history. Typed at the interactive prompt,
|
|
160
|
+
it is not echoed.
|
|
154
161
|
|
|
155
162
|
## 5. Run it
|
|
156
163
|
|
|
@@ -158,7 +165,7 @@ existing `config/local.json` (with a backup).
|
|
|
158
165
|
npm start
|
|
159
166
|
```
|
|
160
167
|
|
|
161
|
-
The log says `BlockYard 0.1.
|
|
168
|
+
The log says `BlockYard 0.1.2 listening on https://127.0.0.1:21000` (its own self-signed
|
|
162
169
|
certificate, made on this first start; the browser warns once and remembers it), then `created the
|
|
163
170
|
first admin account (admin)` with a generated password **shown once** — copy it, or set
|
|
164
171
|
`BLOCKYARD_ADMIN_PASSWORD` before the first start to choose it — then `address index: building
|
package/docs/INSTALL.md
CHANGED
|
@@ -22,7 +22,7 @@ machines you choose. Every setting mentioned here is described in full in
|
|
|
22
22
|
|
|
23
23
|
| need | notes |
|
|
24
24
|
|---|---|
|
|
25
|
-
| **Node.js 22 or newer** | `node -v` must print `v22` or later. Older runtimes fail on syntax at start-up, which looks like a bug in the app. Install from [nodejs.org](https://nodejs.org), your distribution's backports, or a version manager such as `nvm`. |
|
|
25
|
+
| **Node.js 22.2 or newer** | `node -v` must print `v22.2` or later (the index build uses its built-in CRC-32). Older runtimes fail on syntax at start-up, which looks like a bug in the app. Install from [nodejs.org](https://nodejs.org), your distribution's backports, or a version manager such as `nvm`. |
|
|
26
26
|
| **Bitcoin Core 25.0 or later** | [Bitcoin Core](https://github.com/bitcoin/bitcoin) with `server=1` and `txindex=1`, **on the same machine** as BlockYard, which reads the node's block files for the explorer's address index. A node on another machine is not supported. 25.0 is where `getblock` verbosity 3, which the index follower uses, arrived; 29.1 is what the macOS install was done against. `coinstatsindex=1` is optional (without it the UTXO figures are blank and the node is not asked for them). Not a pruned node: the index needs every block file. |
|
|
27
27
|
| **RPC credentials** | Either read access to the node's cookie file (`<datadir>/<chain>/.cookie`, the usual case on the same machine) or an RPC user and password. |
|
|
28
28
|
| **macOS or Linux** | There is nothing to compile, and the server calls no platform-specific API (no `child_process`, no `/proc`, no `systemctl`). Developed on Linux; a real install has been done on macOS (Core 29.1). The test suite runs in CI on Ubuntu, macOS and Windows (Node 22 and 24), but on Windows nothing more than the suite has been tried. Only the *service* instructions in section 6 are Linux-specific (they use systemd); on macOS run it in a terminal, or write a `launchd` plist. The index store opens its files per lookup, so macOS's default limit of 256 open files is enough. |
|
|
@@ -70,8 +70,10 @@ while every page keeps working: the Overview's "What this panel cannot tell you"
|
|
|
70
70
|
progress (phase, files done, rows so far, an ETA that settles after the first few files), the
|
|
71
71
|
address page says the same in place of a history, and an event — which the browser shows as a
|
|
72
72
|
notification — marks the start, the finish and a failure. When it finishes the follower starts
|
|
73
|
-
on the spot, so address pages work without a restart. Stopping BlockYard stops the build
|
|
74
|
-
|
|
73
|
+
on the spot, so address pages work without a restart. Stopping BlockYard stops the build, and the
|
|
74
|
+
next start resumes it: the files already scanned and the buckets already sorted are kept, and
|
|
75
|
+
only what was not finished is read again (at most about a minute of scanning). Three keys on the
|
|
76
|
+
node entry control it:
|
|
75
77
|
|
|
76
78
|
| key | meaning |
|
|
77
79
|
|---|---|
|
|
@@ -334,7 +336,8 @@ If a node shows as offline, see [TROUBLESHOOTING.md](TROUBLESHOOTING.md#a-node-s
|
|
|
334
336
|
```
|
|
335
337
|
|
|
336
338
|
Set `User=` / `Group=` to the account from step 1, `WorkingDirectory=` to where the code
|
|
337
|
-
lives,
|
|
339
|
+
lives, the two `ReadWritePaths=` lines to that directory's `data` and `config` (and add one for
|
|
340
|
+
your address index directory; the unit makes everything else read-only to the service), and replace the `Environment=` lines that name paths with your own (or delete
|
|
338
341
|
them and keep everything in `config/local.json`). Point `ExecStart` at an absolute Node
|
|
339
342
|
22 binary — `/usr/bin/env node` can resolve to an older system Node under systemd:
|
|
340
343
|
|