From 34347f6602df929f9523b84634ecd5f1e0e201c0 Mon Sep 17 00:00:00 2001 From: phroi <90913182+phroi@users.noreply.github.com> Date: Mon, 26 Oct 2026 00:38:33 +0100 Subject: [PATCH] feat(node): opt-in automatic updates from master, between turns Three commented lines in each tracked unit: a start timeout, `git pull --ff-only` under `timeout` with the `-` prefix, and `pnpm install --frozen-lockfile` without it. A unit alone on a checkout of master that uncomments them updates between turns, costing no ready window, from the one channel the repository already guards with its merge rules. A failed pull lets the turn run; a failed install does not, so a turn never runs new source on old node_modules and waits until the install completes, which an offline no-op install never blocks. git and pnpm are native binaries, so an install interrupted mid Node bump, which removes the checkout's only Node, is finished by the next start rather than looping forever, which is why no Node-based updater can do this job. The tracked examples keep the lines commented: a development checkout, like the laptop runners, must not move under the operator, and two actors on one host get a clone each (user decision 2026-10-26). The README's host install names the Node-free pnpm binary, since pnpm is what fetches Node. --- README.md | 2 +- sdk/node/README.md | 2 +- sdk/node/ickb-bot-testnet.service | 9 +++++++++ sdk/node/ickb-stimulus-testnet.service | 9 +++++++++ 4 files changed, 20 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index c9c93bc9..211b7d77 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ Stack assumes user-owned cells have [locks whose signatures bind the whole trans ## Dependencies and checks -Install the pinned pnpm 12.4.2 with npm; `pnpm install` then fetches the Node the lockfile pins (`devEngines.runtime` in package.json) into `node_modules/node` and runs every script on it; a host that runs the bot from source points its units at that binary. Inside the checkout run npm as `pnpm exec npm ...`: npm 11 refuses to run on a host Node that differs from the pin, and `pnpm exec` puts the pinned one first on PATH. From a plain checkout: +Install pnpm 12 as a native binary, from its standalone installer or Homebrew, no Node needed; it switches itself to the version `packageManager` names, and `pnpm install` then fetches the Node the lockfile pins (`devEngines.runtime` in package.json) into `node_modules/node` and runs every script on it; a host that runs the bot from source points its units at that binary. Inside the checkout run npm as `pnpm exec npm ...`: npm 11 refuses to run on a host Node that differs from the pin, and `pnpm exec` puts the pinned one first on PATH. From a plain checkout: ```bash pnpm install --frozen-lockfile diff --git a/sdk/node/README.md b/sdk/node/README.md index c0d5753a..d3f276f2 100644 --- a/sdk/node/README.md +++ b/sdk/node/README.md @@ -203,7 +203,7 @@ systemctl --user status ickb-bot-testnet.service systemctl --user restart ickb-bot-testnet.service ``` -To update, stop every unit that shares the checkout, move the checkout to the reviewed revision, run `pnpm install --frozen-lockfile`, then start them again; a turn must never observe a half-updated tree. Editing a unit needs `systemd-analyze --user verify`, `systemctl --user daemon-reload`, and a restart of that unit, because a reload alone keeps the running process. +To update by hand, stop every unit that shares the checkout, move the checkout to the reviewed revision, run `pnpm install --frozen-lockfile`, then start them again; a turn must never observe a half-updated tree. To update automatically, uncomment the three lines in the unit, which is allowed only for a unit alone on a checkout of `master` with an anonymous HTTPS remote (two actors on one host get one clone each): before every turn, `git pull --ff-only` and `pnpm install --frozen-lockfile` run as `ExecStartPre`, so an update lands between turns and costs no ready window. Both are native binaries, so the updater never depends on the Node it replaces: an install interrupted by a kill or a reboot is finished by the next turn's install, which is a no-op when nothing changed, offline included. The `-` prefix lets the turn run when the pull fails; the install line has none, so a turn never runs new source on old `node_modules` and instead waits, visibly in the journal, until the install completes. `timeout` caps a fetch against an unreachable GitHub at a minute, and `TimeoutStartSec` gives a cold install that fetches a new Node the time it needs. What reaches the bot is exactly what passed the gate and the merge rules on master, so the repository's merge protection is the update policy. Rejected: a timer unit, which would have to stop a possibly running turn; an updater written in TypeScript, which runs on the Node a failed install may have removed; signed release tags verified on the host, a second key that belongs only to a setup where GitHub no longer holds the first. A turn never holds: every exit starts the next turn after `RestartSec`, and an underfunded account skips turn after turn until it is funded. To see why a unit keeps skipping or failing, inspect the journal for the last event and the exit status: diff --git a/sdk/node/ickb-bot-testnet.service b/sdk/node/ickb-bot-testnet.service index 2c946a93..4bb89898 100644 --- a/sdk/node/ickb-bot-testnet.service +++ b/sdk/node/ickb-bot-testnet.service @@ -8,6 +8,15 @@ Description=iCKB bot testnet # Type=exec makes `systemctl start` fail when the node binary cannot be executed. Type=exec WorkingDirectory=/home/user/Projects/ickb/stack +# Opt in to automatic updates by uncommenting these three lines, only when this unit is +# alone on a checkout of master with an anonymous HTTPS remote: before each turn, pull +# fast-forward and install. `-` lets the turn run when the pull fails; the install has +# none, so a turn never runs new source on old node_modules and waits until the install +# completes. git and pnpm need no Node, so an interrupted install repairs itself. The +# start timeout covers each step; `timeout` bounds a fetch against a black hole. +#TimeoutStartSec=15min +#ExecStartPre=-/usr/bin/timeout 60 /usr/bin/git pull --ff-only --quiet +#ExecStartPre=/usr/local/bin/pnpm install --frozen-lockfile --silent # The user manager does not see the shell PATH, so name the node binary by absolute path. ExecStart=/home/user/Projects/ickb/stack/node_modules/node/bin/node sdk/node/src/bot.ts Environment=BOT_CHAIN=testnet diff --git a/sdk/node/ickb-stimulus-testnet.service b/sdk/node/ickb-stimulus-testnet.service index 01bb59a3..5c65b67b 100644 --- a/sdk/node/ickb-stimulus-testnet.service +++ b/sdk/node/ickb-stimulus-testnet.service @@ -8,6 +8,15 @@ Description=iCKB stimulus generator testnet # Type=exec makes `systemctl start` fail when the node binary cannot be executed. Type=exec WorkingDirectory=/home/user/Projects/ickb/stack +# Opt in to automatic updates by uncommenting these three lines, only when this unit is +# alone on a checkout of master with an anonymous HTTPS remote: before each turn, pull +# fast-forward and install. `-` lets the turn run when the pull fails; the install has +# none, so a turn never runs new source on old node_modules and waits until the install +# completes. git and pnpm need no Node, so an interrupted install repairs itself. The +# start timeout covers each step; `timeout` bounds a fetch against a black hole. +#TimeoutStartSec=15min +#ExecStartPre=-/usr/bin/timeout 60 /usr/bin/git pull --ff-only --quiet +#ExecStartPre=/usr/local/bin/pnpm install --frozen-lockfile --silent # The user manager does not see the shell PATH, so name the node binary by absolute path. ExecStart=/home/user/Projects/ickb/stack/node_modules/node/bin/node sdk/node/src/stimulus.ts Environment=STIMULUS_CHAIN=testnet