All notable changes to Pithead are documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
Pithead ships as one product, one version — the version lives in the top-level
VERSION file and every released image is tagged with it. Releases are cut
per the process in docs/dev/releasing.md.
pithead applynow finishes a caddy restart that an earlier, interrupted apply never completed, so the dashboard login always matches the rendered config (#3332).pithead restoreafterpithead config-resetno longer refuses on the appliance with "could not verify that the stack is stopped": the configless running-container check lists container states once instead of asking Podman for therestartingstate it does not have (#3346).- A fleet
workers.api_portoutside 1-65535 (or not an integer) is now refused at dashboard preview and bypithead apply, instead of rendering an invalidXMRIG_API_PORT(#3358). - A Configuration preview is now rejected at commit if the live config changed after it was made, so a stale preview from another tab can no longer silently overwrite a newer edit; preview again (#3352).
- A one-field save in the dashboard Configuration view no longer adds untouched default arrays (
workers.list,notifications.webhooks) to a minimalconfig.json(#3355). - A Configuration change rejected before anything was written (a stale preview, a missing confirmation) is now reported as not applied or saved, not as "saved but is not running" (#3376).
-
Upgrading from 1.x.x is best-effort; a fresh setup may be required.
-
Take
./pithead backup --with-chainsbefore upgrading. -
Leave free space for the Tari migration: both database copies occupy the data volume at its peak. The upgrade requires the current
data.mdbsize plus 5 GiB free. -
Tari reads "loading" for about 2.5 hours during its first-start migration. Do not stop, restart or
applythe stack until the migration finishes. -
If
tari.modeisremote, upgrade the serving node to v6.0.1-pre.0 before this stack. -
The node and wallet database migrations are one-way. There is no way back to 1.x Tari.
Pithead 2.0.0 is the first release of Pithead OS, the appliance: a bootable image that
installs itself on a machine you dedicate to it, is set up from a browser, and updates as one
signed image that goes back to the previous version on its own when an update does not boot.
The Docker-Compose install (the DIY path) ships in the same release, with the same stack, the
same dashboard and the same configuration. Entries below apply to both channels unless they say
otherwise. The appliance guide is docs/appliance.md.
-
Tari outage alerts now describe the required policy and actual worker rejection state. Optional Tari keeps mining Monero; recovery claims readmission only after workers were rejected and the proxy successfully restarted (#3119).
-
Tari v6.0.1-pre.0 and P2Pool 4.18.1, upgraded together (#1129). The Tari 6.0 hard fork activates at mainnet block 350,000; a node on an older version forks off the network at that height. P2Pool 4.18.1 changes how it sends Tari merge-mined work and requires a Tari node on 6.0.0 or newer, so the two move as one pair. The node and console wallet images now come from
ghcr.io/tari-project, pinned by digest to thev6.0.1-pre.0-mainnetindexes: a 6.0.0 node on a database migrated from 5.3.1 rejects canonical block 350,008 as below target difficulty and stays on a dead fork, and 6.0.1-pre.0 carries the upstream fix (#2604, tari#8046). Pre-2.0.0 installs are off the canonical chain until they upgrade.- The first start migrates the Tari database, and there is no way back. The node runs a
one-time migration in two phases: a JMT v1 → v2 rebuild
(
[MIGRATIONS] Blockchain database is at v6, thenv6: Starting JMT v1 → v2 rebuild, ending atJMT rebuild complete), then a compaction that copies the live data into a newdata.mdb(Compacting LMDB env,[MIGRATIONS] Pre-compaction data.mdb size). On a mainnet-sized database the rebuild took about one to one and a half hours and the compaction about 80 minutes more, shrinking the database from 161 GB to about 55 GB. The node opens gRPC only after both phases, so the dashboard shows Tari as loading with no progress for the whole time; follow the phases withdocker logs tari. The node config keeps 2 GiB of LMDB map headroom so that dropping the old tables at the end of the rebuild does not fail withMDB_MAP_FULL(#2593). The compacted copy sits beside the old database, so both are on the data volume at once. Before it starts or recreates any container,./pithead upgraderequires free space there of the currentdata.mdb's size plus 5 GiB, and otherwise refuses, naming the volume, the size needed and the size free (#2636). On the appliance,pithead os-updateand the dashboard's OS-update verify and install steps refuse a bundle that declares a data migration against the same bound, before anything is installed (#2645). The bound is conservative: the copy is smaller than the original. Do not stop, restart orapplythe stack until the node reports progress again: the container is killed one minute after a stop, and upstream says not to interrupt the migration. The payout wallet (tari.view_key) migrates its database on its first start too. Tari 5.3.1 cannot open either database afterwards, so returning to an older Pithead release does not return Tari to a working state. Take a backup first (./pithead backup --with-chains). - A node that followed the dead 5.3.1 branch past 350,000 is rewound on its own
(#2618). Such a node bans
every canonical peer for
Invalid Proof of workafter the migration and never syncs. The Tari entrypoint waits while the node's gRPC is closed or answersUNAVAILABLE(it does for the whole database migration), then compares the node's block header at 350,000 with the canonical hash. On a mismatch it rewinds the chain to 349,900, deletes the peer database (the bans) and starts the node again. A node below 350,000 or on the canonical chain is left as it is. Each step is logged indocker logs tariwith the prefix[pithead fork-check]. - Remote Tari (
tari.mode: remote): upgrade the serving node to 6.0.1-pre.0 first. P2Pool 4.18.1 cannot merge-mine against a node older than 6.0.0, and a 6.0.0 node stops at 350,008. - The payout-confirmation scan counts Tari 6.0.0's new
*_CONFIRMED_LOCKEDtransaction statuses (a mined output that has not matured yet), so a payout is still recorded when it is mined.
- The first start migrates the Tari database, and there is no way back. The node runs a
one-time migration in two phases: a JMT v1 → v2 rebuild
(
Beta means the feature is optional and off unless you turn it on, and a defect seen only with it enabled may ship as a known issue. These are beta in 2.0.0; the label lives in the docs, this changelog and the wizard text only, and dashboard badges come in 2.0.1.
- Tari merge-mining: off by default in the setup wizards (beta); upgrades and configs that omit
tari.modekeep the bundled node. A yes in a new-install wizard also writesdashboard.tari_required: false, and a reinstall that keeps its Tari chain keeps Enter on yes (#3333). - XvB raffle: off for new installs.
- Payout confirmation: the Monero and Tari view keys (
monero.view_key,tari.view_key). With Tari payout confirmation on, someone who knows your Tari address can make the view-only wallet show a fake incoming amount. It cannot spend (GHSA-g2r4-jvjg-h942). - Remote rig control: an appliance rig listens for control at every boot, pinned to the
coordinator and gated by its token. Dashboard writes to a rig need adoption first.
On rigs whose home directories are mode
0750(the stock Ubuntu default), dashboard adoption and control do not work. Mining is unaffected (rigforge#362).
-
On an appliance rig, a pool changed from Worker Inspect reverts at the next reboot: the rig's
rig.jsonowns the pool. Change it in the rig'srig.jsoninstead (rigforge#583). The other Worker Inspect edits (DONATION,autotune,watchdog,watchdog_interval_min,max_temp_c) survive a reboot. -
On a no-login appliance, Tor auto-heal does nothing unless
dashboard.control.enabledis set (#3166). -
On a DIY host whose GRUB configuration has malformed quoting, later
update-grubruns can fail (#3305). Run setup with--skip-optimize, or remove/etc/default/grub.d/zz-pithead-hugepages.cfg. -
The order of the DIY firewall rules against Docker at reboot is untested (#2677).
-
Automatic Tor recovery is a named soak risk: it restarts Tor on its own, and its long-run behaviour has had no soak time yet.
-
On the appliance, the built-in miner can stay at 0 H/s after a dashboard backup or a recovery boot. The dashboard shows 0 H/s for it. Run
sudo systemctl restart xmrigor reboot. Fixed in 2.0.1 (#3375). -
After a dashboard update, the appliance can mint a new self-signed certificate for the same addresses, so the browser asks you to trust it again. Fixed in 2.0.1 (#3414).
-
On the appliance, a restore started from the command line while the setup page is still starting can leave the setup page up instead of the dashboard. Mining is unaffected. Fixed in 2.0.1 (#3390).
-
Setting the stratum port in Configuration to a port another service already uses is not caught by the preview. Apply then fails part-way with the proxy stopped; Configuration's recovery restores it (#3417).
-
After a rejected stale Configuration change, the message can say it was saved (#3376).
-
The Configuration preview can warn about a sidechain/PPLNS reset for a routing-only change (#3382), or about Tari sync settings that are off (#3383).
-
The dashboard can show a LAN stratum URL for a loopback-only listener (#3384).
-
An Advanced JSON edit that slips in a null webhook entry saves it without the preview listing it. Nothing is sent. Fixed in 2.0.1 (#3388).
-
The preview shows a null worker API port as "unset"; the effective port is 8080 (#3377).
-
Dark mode can make text in dialogs (#3378), password placeholders (#3379) and the empty Workers Alive card's guide link (#3380) hard to read. The Advanced topology heading can be clipped (#3381).
-
DIY shows the appliance-only built-in-miner toggle (#3357).
-
Recovery after an interrupted image build during DIY source setup is undocumented (#3348).
-
Read-only
config_versioninconfig.json, hidden from Configuration. Only newer configs warn; restoring a backup from a newer release requires updating first (#3109). -
Stratum password is opt-in, and its connection details are shown wherever the pool URL is. Both wizards ask
Enable stratum password?and default to no. The setup hand-off card, the Connect a miner block (signed in, or on a dashboard with no login) and the CLI show the LAN pool URL and the password, or that none is set (#3092). -
The XMR Network card now says whether monerod is at the tip with peers. It shows the outgoing and incoming peer counts and the age of the last height change, and goes red with the numbers after 10 minutes with no outgoing peers or 30 minutes with no new height.
./pithead doctorandstatusname the same condition, the monerod container reportsunhealthyafter 10 minutes with no outgoing peers, and each condition sends one alert through thenode_downtoggle. Nothing is restarted for you (#2499). -
A saturated Tor circuit history is reported, and
tor-recovercan reset it while Monero stays synchronized (#3052). Tor can finish bootstrapping with its circuit-build-time history full and still fail every clearnet request; a NEWNYM refresh does not clear it. Withtor.auto_healon, the dashboard reads Tor's circuit-history state on every heal round, logs a saturated history and sends one alert per outage naming./pithead tor-recover, retrying until a channel delivers it../pithead doctorreports the same condition as a FAIL, which the Tor section of Service Diagnostics shows after a health check.tor-recover checkandapplynow also accept two failed heal rounds from the same outage at least 15 minutes apart, with both clearnet probes still failing through Tor, so a synchronized Monero node no longer blocks the reset. When the history stays saturated through the heal rounds,tor.auto_healruns the same gatedtor-recover applyitself (#3118), keeping its cooldown, evidence and onion-identity checks. Its refreshes and recovery go through the host control runner, so the heal needsdashboard.control.enabled, which the appliance turns on only when a dashboard login is set. Withouttor.auto_healthe reset stays an operator command. -
The dashboard onion's client key without a shell. With Tor client authorization on — the default, and mandatory whenever the config editor is on — a published
.oniondoes not answer a browser that has no client key, and the key was printed by exactly one thing:pithead onion-client-key, on a host shell. An appliance has none, so turning the onion on there produced an address that was published, shown in the dashboard header, and impossible to open, under a note naming a command the reader could not run (#1882). The header's client-authorization note now carries a Show client key button wherever the config editor is on. The host answers once — both Tor client forms — and wipes its own copy on the same timer the backup kit uses; every reveal is recorded in the config-change audit log. The key is still not in the dashboard container's environment (#1880 stands): the container asks, the host decides, and the answer crosses once through the read-only results spool. -
Per-worker authentication reaches the dashboard's read probes (#2349, #1950, #2415). An explicit
workers.list[].api_tokensupplies a read-only probe credential bound to that worker's host and API port. RigForge's write-capableworkers.list[].tokenstays on the host; the dashboard receives only its separately derived read bearer. Missing or stale credentials fail closed, and the editor's config copy keeps both fields masked. -
Pithead OS, the appliance. Verify
pithead-os-v2.0.0.img.xz, write its decompressed image to a USB stick, and boot the machine from it: it installs itself and serves a one-page setup wizard to your browser. The page asks what the machine is — a full coordinator, a coordinator that also mines with its own CPU, or a mining rig (#797) — which disk to use, and the same questions the DIY installer asks. A machine without a monitor can be set up from a file dropped on the stick (#924), and settings can be changed later the same way; a stick rewrite changes only the settings it names (#910, #965). -
Updates that fall back on their own. The appliance keeps two copies of the system and writes an update to the idle one. It reboots into the new version, checks that the stack came up, and returns to the previous copy by itself when it did not. Updates are signed with a release key the machine verifies before installing; a release build refuses to run without an explicit key. An update is checked for, downloaded and installed from the dashboard's OS-update control (#976), or applied from a downloaded bundle with
pithead os-update; the chain services start only after the new slot has committed, so a forward-only database migration never runs on a slot that might be rolled back. -
Backups and restores.
pithead backupexports an encrypted archive (#908), and the setup wizard can restore one on a fresh machine (#909). A restored machine keeps its own identity: the machine-id and SSH host keys live on the data partition (#894, #895), and the journal follows the machine rather than the boot (#1659). -
Two resets instead of an uninstall. A config reset clears the settings and reopens the setup wizard, keeping the synced chain, the wallet and the Tor keys; a factory reset wipes the data partition. A data partition damaged by a power cut is repaired rather than erased (#1062), and a container store left inconsistent by an interrupted write is rebuilt on the next boot (#1029).
-
Host tuning baked into the image. Hugepages are reserved at boot in proportion to the fitted RAM (#977) up to a declared ceiling that, on a single-socket machine, neither the host nor the miner unit can grow past (#1103, #1724); the CPU governor is set to performance; a hardware watchdog resets a box whose kernel or init has hung, with nobody present. The Tor-only egress firewall is enforced under the appliance's own container engine, and IPv6 fails closed. The mining-rig role ships RigForge 1.18.0 code at commit
b2d4c3d9ca1da74d0e6d5ccce772d401acc8be03, before its release tag (#1826, #3029, #3116). -
Service Diagnostics in the dashboard (#913, #943): the host doctor's detail and a bounded, redacted tail of each service's log, without a shell.
-
The dashboard says where a rig's running configuration came from (#1345), persists the revision each rig serves (#1551), detects a rig running something other than what was applied (#1367), and records a rig configuration change nothing else had recorded (#1558). Worker Inspect is prefilled from the rig's own configuration (#1235), and each node card says whether that node runs locally or remotely (#1040). The XvB decision table is rebuilt as per-tier blocks (#1316).
-
The boot menu says what each entry boots, and offers "Set up again" (#1318, #1838). A set-up-again boot opens the setup page beside the saved role; the page opens by naming what the machine already is, with its rig data kept and offered back.
-
An appliance rig mints its own control token and shows it once (#1836), beside the address to adopt it at; it serves the sister feed and pins control to its coordinator.
-
pithead doctor --jsonandpithead support-bundle: a machine-readable doctor report, and a redacted bundle for a support request that masks wallet and onion addresses as well as credentials (#1585).
-
Enabling or re-pointing Tari on a mining box keeps Monero mining while Tari syncs. The preview row says so. The sync hold then applies to Monero only, so the miner and workers carry on; merge-mining starts when Tari has synced. The first-install hold on both chains is unchanged (#3094).
-
New installs start from safer defaults in both wizards. Tari is off unless you say yes (#3333), the XvB raffle is off and not asked about, a dashboard login is generated and shown once unless you choose no login, and the first sync runs over Tor. The faster sync is an opt-in that covers each chain run locally and warns that it exposes your IP. Upgrades keep their config (#3099).
-
setup,upandapplyalways print the pool URL and the stratum password state. The password readsnone setwhen there is none. Anapplythat only toggleslocal_minerconverges the local miner instead of reporting no changes (#3090). -
The command line confirms a payout address change by its last 8 characters, as the dashboard already did; it used to ask for the first 8 (#3097).
-
applyand the dashboard refuse a config with a duplicate JSON key or aPASTE_orYOUR_placeholder value. The refusal names the key and never prints the value (#3098). -
Appliance documentation corrections for identity and update recovery (#3100).
-
Payout confirmation keeps one view-only wallet per payout address and view key. Changing the address or key opens a separate wallet instead of reusing the old one, reverting reopens the earlier wallet, and an upgrade adopts the existing wallet without recreating it. A new automatic Monero wallet starts 100 blocks behind the local node's tip.
applyrefuses a view key that does not belong to the configured address, for Monero and Tari, so a key from the wrong wallet fails at once instead of scanning forever (#3096). -
Tari payout confirmation needs only the view key.
applyderives the public spend key from the dual-keytari.wallet_address;tari.spend_public_keyis optional and, withtari.view_keyset, must match the address. A single-key Tari address is refused whiletari.view_keyis set; mining to it still works (#2732). -
The docs show where each view key lives in the Monero GUI wallet and Tari Universe, and
apply's view-key messages point there (#3112). -
The Configuration view works the same, minus the Telegram round-trip. A disruptive change still asks you to type
APPLY. A payout change also asks for the last characters of the new address, after the host validates its checksum and network. Future rewards go to the new address. The action button reads "Confirm & apply" in every case — there is no longer an "Approve & apply" variant. A sensitive commit no longer depends on Telegram being set up at all: on a stack that never configured the bot, these changes used to fail with an approval-unavailable error and now apply normally. -
Sensitive commits audit without an approver.
commit-approvedand the audit log'sapproverfield had exactly one writer, the Telegram verifier, so sensitive commits now record ascommitorcommit-confirmedagainst the signed-in dashboard user. Existing log rows are unchanged. -
Remote nodes on a dual-stack LAN must be entered by their private IPv4 address (#2351). With the default Tor egress firewall, the setup wizard refuses a dual-stack hostname and names the private-address remedy.
-
The worker list keeps RigForge status chips short (#3031). Power and temperature stay in the list alongside warnings; version, mainboard, HugePages, a healthy governor, tune target and autotune details move to Worker Inspect, which keeps every existing row.
-
Every mutating
pitheadverb runs behind one mutation lock (#1342, #1482). Setup, apply, upgrade, the resets, rotate-secrets and the OS-update verbs serialise; a second invocation that arrives while one holds the lock is refused and told why, instead of two writers racing on the same files. -
The Tari disk budget is 200 GiB (#1011), which raises the free space setup and
doctorask for. -
The appliance builds docker-compose and cosign from source in its rootfs instead of downloading release binaries.
-
A failed setup keeps the machine's existing configuration (#1059) rather than discarding it, and the CLI degrades instead of aborting when the optional dashboard auth key is absent (#1246).
-
The setup wizard opens on Pithead + RigForge, and the miner select is the switch (#1830).
-
The dashboard's Configuration view never shows
ssh.*and never changes it (#1850), and its Advanced pane says what it does with a key.
-
Release images no longer accept the
ssh.*configuration options. SSH is now a debug-build property; a carried 1.xssh.enabled: truesetting is ignored and reported rather than blocking an update. -
Telegram is a read-only notification channel. The bot still answers
/status,/info,/hashrate,/workers,/sync,/system,/pool,/xvb,/earnings,/luckand/help, and still sends every event alert and the daily summary. Its two write surfaces are gone (#2076):- The
/restartand/applycontrol commands (#338), with thetelegram.controlconfig block (enabled,allowed_ids,confirm_timeout). - The Telegram approval tap on a sensitive Configuration-view commit (#911). Changing a payout wallet, a node endpoint or any other sensitive setting no longer sends a prompt to Telegram and no longer waits for anyone to tap a button.
applydropstelegram.controlfrom an existingconfig.jsonon the next run, so no manual edit is needed. If you had the control commands enabled, it says so once as it removes the key. - The
-
The two 1.x configuration aliases (#1832).
dashboard.workers[]isworkers.list[], andxmrig_proxy.{enabled,url,donor_id}isxvb.*. A 1.x configuration is migrated in place once, the first time 2.0.0 reads it; after that the old names are unknown to the product. A configuration that sets a non-default old value and its replacement to different values is refused.
-
After a dashboard update, the original tab leaves "Rebooting" by itself once the appliance reports the new version (#3415).
-
An upgrade with a data migration commits unattended. This holds even when a mining release was saved before the update (#3267).
-
Boot health probes no longer count as failed logins, so they do not raise failed-login warnings (#3269).
-
Tor self-heal resets its outage clock when Tor is restarted outside the healer (#3302).
-
An apply that restarts the dashboard keeps it available when the old container's stop times out (#3307).
-
Discarding a rejected configuration preview clears its error. The dashboard no longer shows the old preview error above the clean form after Discard edits, including after you move to another view and back (#3354).
-
Configuration drafts survive a view change. Edits in the configuration editor are kept when you move to another dashboard view and back, and after an apply that fails (#3273).
-
The setup wizard's plain-HTTP port redirects to HTTPS. A plain
http://LAN URL is redirected to an HTTPS host address: the host you typed, or the first host address orpithead.local. Caddy keeps a recognised typed host after provisioning (#3236). -
The boot menu names the boot disk. Each GRUB entry says whether it boots the USB stick or the internal disk, and a debug build's label appears only in the slot title (#3230, #3238).
-
The boot health wait shows progress. The appliance reports what it is waiting for on the console during the health gate (#3203).
-
Failed-login counts survive Caddy log rotation. The security view keeps its recent failed-login history when the access log rotates (#3270).
-
restoreworks afterconfig-reseton the Docker-Compose install (#3282). -
pithead doctorshows no tip age for an unset Monero tip timestamp, instead of an age measured from 1970 (#3280). -
Expected Tor recovery refusals are not reported as aborts. A healthy Tor that refuses
tor-recoverno longer leaves abort diagnostics (#3266). -
The backup kit survives a failed archive download. A download that fails (the Chrome case included) keeps the kit available for a retry; the backup guide has the clean-profile workaround (#3272).
-
P2Pool pool statistics are hidden while the sidechain syncs, instead of showing bootstrap values (#3250).
-
Update errors can be dismissed, and the dashboard explains how to reconnect after the certificate changes (#3243).
-
The what-if calculator rejects a malformed hashrate and parses grouped numbers (#3268).
-
Worker Inspect edits survive an appliance rig's reboot. The rig role's boot-time rebuild of RigForge's config no longer drops
DONATION,autotune,watchdog,watchdog_interval_minandmax_temp_c; a pool change still reverts (see Known issues) (#3204). -
Worker Inspect says a
DONATIONbelow the miner's built-in minimum has no effect (#3206). -
XvB keeps its full P2Pool dwell when the raffle is disabled (#3226).
-
Workers and rigs are probed only while online, or while their feed last answered (#3211); workers are kept across a transient proxy fetch failure (#3201).
-
The setup wizard rejects duplicate JSON keys and template placeholders (#3200).
-
pithead doctoraccounts for stratum protection and Tor control in its exposure checks (#3202). -
A legacy payout wallet is verified before it is adopted (#3177).
-
A crashed LAN-published node restarts while the LAN-guard source rule is live (#3295).
-
cosignanddocker-composeare built on Go 1.26.9, which clears two Go standard-library CVEs in the image scan (#3294). -
Persistent HugePages setup keeps cloud GRUB arguments. Setup writes its own
/etc/default/grub.d/zz-pithead-hugepages.cfg, keeps existing boot arguments including cloud console settings, verifies the generated kernel entries afterupdate-grub, repairs earlier Pithead reservations on re-run, and asks for no second reboot when the running kernel already has the flags (#3281). -
A Tari outage no longer rejects workers at once. With
tari_required, workers are rejected only after a sustained Tari RPC outage; migrating, starting and syncing only alert. Once the Monero node has answered, an outage, local or remote, always rejects (#3091). -
Tor recovers from a saturated circuit-build history. An early alert, and under
tor.auto_heala self-heal, reset Tor through the gated./pithead tor-recover; the doctor and the Monero card's advice name it (#3118). -
An IPv6 address that arrives after boot no longer leaves a permanent doctor FAIL (#2463). A new timer checks the machine's addresses every five minutes and, when they changed since the last render, re-renders the dashboard certificate and Caddyfile and restarts Caddy if either changed. The certificate check also compared an IPv6 address's two spellings (openssl's expanded form and
hostname -I's compressed one) as different strings, so any IPv6 address was reported uncovered and re-minted the certificate on every render; both sides are now canonicalised. -
Clearnet initial sync works behind the default egress firewall (#2649, #2678). An opted-in Monero or Tari node gets its own temporary direct-dial exception while other containers stay restricted. On sync, the host closes and verifies that exception before the node restarts on Tor; the dashboard keeps the transition warning until the host verifies the live Tor daemon and rules. A completed sync stays on Tor across apply and reboot.
-
monerod flushes every chain-database commit to disk (#2471). monerod's default database mode,
fast:async, opens LMDB withMDB_NOSYNCwhile the node is syncing and only syncs its commits once it reaches the chain tip, so a power cut during the initial sync or a catch-up could lose commits the node had already made. The bundled node now runs withdb-sync-mode=safe, which syncs every commit, so a power cut can no longer take the chain back below a height it had already committed. At the tip nothing changes. While syncing, each commit now waits for two disk flushes, and the bytes written are the same. Counted from the monerod 0.18.5.1 source, a full pruned mainnet sync to height 3.77 million makes at most about 295,000 commits when every download batch is full, and at most about 7.56 million if every batch holds one block. The added time is the number of flushes times the disk's flush time, which was not measured: for each millisecond a flush takes, about 10 minutes with full batches and at most 4.2 hours. -
Tari payout confirmation now finds payouts (#2731). The view-only wallet's
tari.payout_scan_birthdaycounts days since 2022-01-01, Tari's unit (Tari Universe'swallet_birthdayworks as-is).autowas computed from 1970, a day in 2078, so the wallet started at the chain tip and missed every earlier payout; a birthday later than today is now refused. The wallet also scans through the local Tari node's wallet HTTP service on the internal network only; it had no working base-node setting and fell back to Tari's public node over clearnet. The wallet also never started. Its volume was mounted where the image's uid-1000 user could not write, so it crash-looped creating its config directory. It now uses a new volume,tari_wallet_db, on the image's own/var/tari/wallet, so every install creates the wallet fresh and scans from the birthday. The oldtari_wallet_datavolume never held a wallet;uninstallremoves it. -
A slow first Tor bootstrap no longer fails provisioning (#2648). monerod and tari wait for Tor's healthcheck, and the healthcheck marked Tor unhealthy about 3.5 minutes after it started. A cold bootstrap on a fresh Tor data directory has taken 5 minutes. When it ran that long,
docker compose upstopped withdependency tor failed to startand never started the nodes. On the appliance, the setup wizard reopened with the configuration marked as failed. Tor now has 10 minutes to bootstrap before failed checks count against it. A Tor that bootstraps sooner is marked healthy at its next 30-second check, as before. A Tor that never bootstraps now failsupafter about 12.5 minutes instead of 3.5. -
Inbound peers reach the Monero onion, and the P2Pool onion on main and nano (#2936). The bundled monerod bound its anonymous P2P listener to its own container's loopback, which the Tor container cannot reach, so the Monero onion answered no peer. It now listens on the stack's container bridge at
:18084; the port is still not published on the host. The P2Pool onion always forwarded to37888, the mini sidechain's port, so on main or nano it led nowhere. It now forwards the selected sidechain's P2P port:37889main,37888mini,37890nano. RPC access and remote nodes are unchanged. -
The Tor self-heal no longer leaves Tor stopped after a restart (#3032). With
tor.auto_healon, the heal's stop request gave up after 60 seconds, just before a wedged Tor finished stopping. The start that followed was answered "already running" by the Tor that was still going down, so Tor stayed stopped and monerod sat with no outgoing peers until someone started it. The stop now waits up to two minutes, an unconfirmed stop gets up to 30 seconds to settle before the start, and an unconfirmed start is tried up to three times, five seconds apart. -
./pithead tor-recover checkaccepts a peerless Monero node that still reads synchronized (#3033). The check, whichtor-recover applyrepeats before it resets a saturated Tor circuit history, looks for a local Monero node stalled with no peers. It requiredsynchronized: false, but monerod keeps its last value after losing every peer, so a node held at one height with 0 outgoing peers for three minutes was refused. The check now takes 0 outgoing peers at an unchanged height as the stall, whateversynchronizedsays. Every other guard is unchanged. -
The Monero payout wallet stays healthy while a restarted wallet catches up (#2756). The scan grace applied only to a newly created wallet. A reopened wallet that had to catch up, for example after the Monero node came back from remote mode, blocked its RPC for the whole catch-up and
pithead statusreported it unhealthy. The wallet now marks a scan on every start, bounded by the same 24-hour grace. -
The Monero payout wallet's scan grace ends at monerod's tip, and a crash loop no longer renews it (#2720). The RPC answers between refresh passes, so the first answer no longer retires the grace mid-scan; the healthcheck clears it once the wallet height reaches monerod's block count. A restart keeps an existing marker's age, so a wallet that never catches up still turns unhealthy after 24 hours. Its ring database moved into the wallet volume, off the read-only root filesystem.
-
Worker Inspect can adopt a rig again (#2641). The former perimeter policy refused every change to
workers.list[], including the append the Adopt this rig form sends, so the form always failed at the preview. An appliance rig set up by the wizard had no way to be adopted short of a configuration stick. The host now lets an append through: every existing descriptor must come back unchanged, a new rig may not reuse an existing rig's name, its host must not resolve to loopback, link-local or the stack's own docker-bridge subnet, and the commit needs the typedAPPLY. The preview names the rig and the address the dashboard will send its control token to, and the audit log records the commit as confirmed withworkers.listas its key. Repointing or removing a rig the dashboard already controls is still refused. -
An unreachable image registry is no longer reported as a bad signature (#2735). When cosign cannot reach the registry, for example
no route to host, the start and upgrade paths still refuse to pull, and now say the image is unverified because of a network error. Before, they said the published image did not match the release key, which sent operators looking for a tampered image. -
A restore at setup no longer carries the source machine's released miner onto new hardware (#2626). The backup's dashboard database records that the source machine's chains had synced and its miner was released. Restored onto a machine whose chains had not synced, the dashboard never held
p2pool, which ran without its stratum port and stayed unhealthy, so the appliance boot never committed. The wizard and carried restore doors now leave a marker that makes the dashboard hold the miner until this machine's own chains are synced../pithead restore, the same-box recovery command, is unaffected: its box's chains never desynced, so it keeps the backup's gate state as before. -
A source checkout starts the whole stack after
uninstallor on a new host (#2654).setup,up,applyandupgradeon a source checkout run Compose with--pull neverso the local:devimages are built, not pulled. The digest-pinned Tari, Caddy and socket-proxy images have no build context, so onceuninstallhad removed them onlytorstarted.pitheadnow pulls the missing images that have no build context before it starts the stack. An explicitPITHEAD_PULLstill overrides this. -
pitheadno longer logs a falsepithead aborted unexpectedly (exit 2)(#3047). Tidying the dashboard's control results printed that error whenever there was no backup archive to prune, for example at every appliance boot, while the command carried on and finished. What is pruned is unchanged. -
A restore at setup that fails while writing its files no longer leaves the machine half restored (#2689). It used to replace
config.jsonand.envfirst and could then fail on the Tor keys or the dashboard database, leaving the archive's configuration beside this machine's own keys. Every item is now staged beside its destination and swapped in only when all are ready; any failure puts back the previous configuration, Tor keys and database, and removes the chain files the restore added. -
pithead doctorno longer reports HugePages OK for a pool too small to use (#2610). Any non-zeroHugePages_Totalread OK, so a box with 186 pages passed while P2Pool's RandomX dataset and caches need 1296. doctor now holds the pool to this machine's budget (3072 pages, or the appliance's reduced pool, never below 1296) and warns when it is short. The warning gives the shortfall and the memory P2Pool uses outside the pool instead. It stays a warning, never a failure, because the appliance's update commit gate takes doctor's exit code. -
P2Pool no longer restart-loops with exit 137 when the HugePages reservation is short (#2562). Without enough free HugePages, P2Pool puts its 2592 MiB RandomX dataset and caches in ordinary memory. Its 1 GB container ceiling OOM-killed it while it filled the dataset, on every start. That happened on a host where
setupskipped the persistent GRUB change and was then rebooted, and on a pool other processes had used up. The ceiling is now 4 GB, both in Compose and in the appliance's units. When the reservation holds the dataset, which is still the fast path, nothing changes. -
Mining no longer starts on a Monero chain that has not synced (#2472). A local monerod that has just restarted and has no peers yet reports a target height of 0. The dashboard read that as "synced", released
p2poolandxmrig-proxy, and saved the release, so every later dashboard restart kept the miner running. The sync gate now also waits for monerod's ownsynchronizedflag, and it counts an empty or partial node reading as not synced. -
An approved configuration apply is no longer failed by a container that is merely mid-restart (#2218). The dashboard stops and starts p2pool on its own for the sync gate and for node-down worker failover, so a
docker compose upcould reach that container between states. Compose aborts the wholeupwhen one container is in an improper lifecycle state, which failed the apply outright — the configuration was written, the containers were not recreated, and the box was left needing a manualpithead applynobody was there to run. Dashboard container start/stop requests now take the same advisory lock as CLI mutations, so either operation waits for the other to finish instead of sending overlapping lifecycle requests to the engine. -
The setup wizard's restore accepts a genuine backup from a prior supported release. A backup made by the v1.20.0 Compose bundle stores its files under whatever directory the operator ran it from, not this appliance's own working directory. The wizard's restore used to compare every archive member against its own directory only, so a real v1.20.0 archive was refused before it ever reached configuration validation (#2181). It now finds the archive's own working directory from where
config.jsonsits and accepts the same backup layout rooted there, still refusing anything that mixes roots or strays outside it. -
The installer-carried restore lands on a
wipe=keeptarget, and never forces a chain resync (#2195). Awipe=keepreinstall keeps the target's priorconfig.json, and firstboot used to skip the carried restore entirely whenever that file was already present — it now always attempts it, and a presentconfig.jsonis simply what the restore replaces. Restoringdata/monero,data/tarianddata/p2poolalso used to delete the target's own directory outright before writing the archive's; it now merges the archive's files in instead, so the target's already- synced chain data survives a restore that carries none of its own. -
An editor-saved 1.x configuration upgrades without a false XvB conflict (#2690, #2699). Untouched
xmrig_proxy.*reference defaults no longer conflict with customisedxvb.*values in either CLI migration or wizard restore;xvb.*wins and the removed block is dropped. A non-default legacy value that differs from its replacement is still refused. -
The boot health gate re-mints a TLS certificate that the machine's address outran (#1265),
applyreaches that re-mint on an unchanged configuration, and a rollback names the check that held the gate instead of a generic message. A slot that fails its health gate reboots once instead of stalling (#1065), and the post-commit release of the chain services retries a container caught mid-transition and alerts when it cannot start (#1684). -
A failed data-migration fallback restores the data-partition floor (#1393) from the record the raise now leaves, instead of leaving it wrongly raised.
-
The setup wizard's plain-port redirect (#1118) and a provisioned machine's port-80 redirect (#1123) no longer trust the Host header; the dashboard is kept off globally routable addresses (#1021); boot asks for the dashboard's own site and tells it apart from the default virtual host (#1140); a wizard retry keeps its TLS session (#1063).
-
The installer's remote-node preflight tests for a live ZMQ publisher (#1497), not just an open port. A checksum-invalid Monero or Tari payout address is refused before anything launches.
-
Slow image loads on USB media are narrated (#1028) with a rising elapsed count, so a working box no longer looks like a hung one, and setup states its stop reason on every console.
-
The xmrig-proxy healthcheck no longer raises a false alarm on an image that predates its healthcheck script (#1098).
-
doctor's remedial text names what the operator can actually reach on the surface they are on (#1213), and looks for the control units where
applywrites them (#1151). The stratum-exposure warning names the one remedy an appliance operator has and stops printing the host's public address (#1772); the missing-data-dir warning is worded the same way (#1776); and a failed configuration save in the dashboard labels the host'sapplylog as the machine's own log and names only the controls on that page (#1769). -
Dashboard: the XvB Odds cell names what it is waiting for instead of showing a dash (#1231); an audit row id no longer collides with another rig's (#1566); a failing payout sync no longer skips the steps beneath it (#1644); the poll counter advances through a failed poll (#1637); Monero clients validate the shape of a response body (#1592); a full history window no longer fails to settle who changed a configuration (#1369), and a failed history read is no longer shown as another dashboard's change (#1409).
-
A reinstall's pre-fill drops the login but keeps the switches that need it (#1846), and a rig running from the USB stick is no longer told the disk is being copied (#1835).
-
The confirm countdown for settings applied from the stick stays bounded when a login prompt contends for the console (#1823); the console read that could park forever now runs under a timeout, at the cost of a countdown up to twice its length on a contended console.
-
The persistent journal has one home (#1791): its bind onto
/var/log/journalis ordered after the/varoverlay, sojournalctl --list-bootslists every boot instead of the ones that won a race. On a mining rig the bind stands down, and the rig's write-minimising reclaim no longer fails the boot when it meets a journal bind that is still mounted (#1817).
-
The dashboard password hash no longer reaches the appliance journal (#3131).
-
The Tor-only egress firewall now survives a DIY host reboot. A reboot emptied
DOCKER-USERwhile the containers restarted on their own, so a DIY host mined without the fail-closed rules until someone ran./pithead up.up,applyandupgradenow installpithead-egress.service, ordered beforedocker.service, which restores the rules before any container starts;doctorwarns when it is not enabled (#2460). -
The Tari node no longer runs its own Tor (#2653). The upstream
minotari_nodeimage is built with Tari'slibtorfeature, anduse_libtordefaults to on. Under thetortransport the node therefore started an in-process Tor, gave it its control port and hidden service, and let it dial Tor relays straight from the tari container rather than through the stack'storcontainer. Tari's source shows the same default in the released 5.3.1 pin. The egress firewall drops those dials. A tier-4 run found one still open after a fault test briefly removed and reinstalled the rules; the firewall's established-flow accept kept it (#2672). With the firewall off, nothing stopped them. Tari now uses thesocks5transport through the stack's Tor SOCKS port withuse_libtor = false. Onion and/ip4peers are both dialled through Tor, and inbound peers reach the node through the stack Tor's Tari onion, which now has a listener behind it. A node upgraded from thetortransport keeps its old onion inconfig/base_node_id.jsonunder the Tari data dir, next to the stack's one. Nothing serves the old onion any more. That is harmless: peers still reach the node through the stack's onion. -
The LAN switches now enforce LAN sources (#2616).
monero.rpc_lan_access,monero.zmq_lan_accessandtari.grpc_lan_accessaccept connections only from loopback, private and CGNAT (100.64.0.0/10) addresses; before, their ports took any source that could route to the host. See LAN-only sources. -
The LAN-only source rule now survives a DIY host reboot. A reboot cleared the rule while Docker restarted the node containers still published on every interface, so
18081,18083and18142took any source until./pithead up.pithead-lan-guard.service, ordered beforedocker.service, now restores the rule. The node containers that publish a LAN port run with restart policyno, andpithead-lan-hold.servicestarts them only after the guard succeeds, so a guard that fails at boot leaves them stopped instead of exposed. While the rule is missing, the nodes refuse to start with a LAN bind however they are started (docker start, a compose run outside pithead), and./pithead restartand the Tor auto-heal do not try. Docker no longer restarts a crashedmonerodortarion such a host;./pithead doctorand acontainer_unhealthyalert name the node and the reason, and./pithead uprecovers. The firstupafter upgrading recreates those containers once. If either unit cannot be installed, the ports stay on127.0.0.1(#2749). -
The dashboard alerts when the Tor-only egress firewall is missing. The dashboard took the firewall's state from
network.tor_egress_firewall, so it reported "blocked by the egress firewall" over an open egress.pithead-egress.timernow runspithead egress-statusevery two minutes and writes the host's live verdict for the dashboard. A missing firewall turns the egress badge and panel into a warning and sends oneclearnet_exposedalert, with one more when the rules are back. A missing or stale verdict reads as unverified, not as green (#2599). -
Dashboard authentication is the configuration-editing perimeter (#1959, #2305, #2428). Low-risk operational settings commit directly; other reference settings use the typed confirmation. A signed-in operator can change payout addresses, view keys, node and stratum credentials, XvB destinations, notification credentials and URLs, onion exposure, LAN binds, the control-channel switch and the Tor egress firewall. The dashboard password and the wallet-change and clearnet-exposure alarm toggles also confirm behind typed
APPLYand the confirmation envelope. The preview names the password's session-lockout cost (on the appliance it also changes the consolerootlogin), silenced alarms and changed exposure.ssh.*is absent from release images. -
Confirmation prevents mistakes; it is not a second identity. The Telegram tap is gone. A compromised dashboard process can supply its own actor,
APPLY, confirmation envelope and payout suffix. The audit records the signed-in actor but cannot prove that actor approved a request created after compromise. Host-side schema and payout-address validation, authenticated node reachability checks, credential-to-destination binding and data-root confinement still apply. A payout change cannot share a commit withdashboard.data_dir, preserving the wallet-change alarm's baseline through the supported commit path. The dashboard's own database and notifiers are not independent protection against a full process compromise. -
Existing rig descriptors remain host-only. The dashboard may append a new rig behind typed
APPLYand host-side target validation, but cannot edit, repoint, reorder or remove an adopted rig, including its credentials. Those changes use./pithead applyon a DIY host or a configuration stick on an appliance. Host-side bearer requests are pinned to the numeric address that passed validation. SeeSECURITY.md. -
Dashboard passwords stay out of process arguments (#2799, #2803). Setup, apply, dashboard password changes and restore feed the password to Caddy's hash command through Docker stdin, rather than
--plaintextin a command line readable by another local user. -
Secrets stay out of what the product prints. A Monero address in log body text is redacted, not only on the launch line (#1750); the p2pool entrypoint's audit line masks secret values and keeps flag names visible (#1586); the pool credential is masked rather than stripped so a dashboard Apply cannot wipe it (#1548); the pool password is no longer kept in the worker-change record (#1543); an audit row's id is bounded and digested so an unauthenticated rig cannot grow it without limit (#1561); adopting a rig from the dashboard passes a resolve-and-check gate against server-side request forgery.
-
A rig's own input cannot stop or grow the dashboard. The out-of-band change audit's flood cap bounds how many device-chosen names hold a window at once, so a rig that varies its name cannot grow the dashboard's memory without limit (#1695); during a flood a name with no live window is refused and one marker records the episode. A change id, reason or worker name that sqlite cannot encode is handled at the bind instead of ending the poll step (#1696).
-
Shipped images carry current fixes. openssl is patched past the digest-pinned base images (CVE-2026-14456, #1438); x/crypto and grpc are raised so the appliance rootfs scan is clean (#1649); a CVE in the appliance rootfs is fixed at its source (#1153).
These ship in 2.0.0 and are fixed after it.
- Tor auto-heal needs dashboard control. Without
dashboard.control.enabled, which includes every appliance set up with No login,tor.auto_healonly detects and logs Tor trouble and cannot refresh or recover Tor;pithead doctorsays so. Turn dashboard control on, or restart Tor by hand with./pithead restart tor(#3166).
Older releases (before 2.0.0) are archived in docs/changelog-archive.md.