The catalog's default version was {"version": "latest", image:
bitcoin-knots:29.3.knots20260508, default: true}, so a fresh install — or
anyone selecting "latest" — silently got the BIP110/RDTS build. Meanwhile
the manifest's own :latest tag resolves to 29.3.knots20260210 in the
registry, so the two paths installed different daemons.
That matters because 20260508 does not merely enable RDTS, it HALTS until
an operator sets consensusrules=rdts. Node 100.64.204.114 runs it and is
frozen at block 961,692 — blocks AND headers static across 30s, 11 peers,
unpruned — while reporting initialblockdownload=false and
verificationprogress 0.99998, i.e. "synced". The two nodes on 20260210 sit
at the tip, 961,706. Re-pointing a tag could have done that to every node
at once.
Pinned in all three places that would otherwise re-introduce a moving tag:
the manifest, image-versions.sh, and the catalog VERSIONS table. The
"latest" pseudo-version is removed outright — a moving default is not
appropriate for a consensus-critical app — and the default is now the
explicit non-RDTS 29.3.knots20260210. 20260508 remains selectable, so
adopting RDTS stays possible but becomes deliberate.
Verified: both tags' daemons read via `bitcoind --version` — :latest and
:29.3.knots20260210 are both v29.3.knots20260210, so this pin is
behaviour-neutral on nodes already running latest.
NOT DONE HERE: releases/app-catalog.json still carries the old signed
entry. It must be regenerated and re-signed by the operator before it
takes effect — catalog entries override on-disk manifests (origin-wins).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
126 lines
5.3 KiB
YAML
126 lines
5.3 KiB
YAML
app:
|
|
id: bitcoin-knots
|
|
name: Bitcoin Knots
|
|
version: 28.1.0
|
|
description: Full Bitcoin Knots node with dynamic prune/full-mode startup based on host disk.
|
|
|
|
container_name: bitcoin-knots
|
|
|
|
container:
|
|
# Pinned deliberately — NEVER use :latest for a consensus-critical app.
|
|
# Knots 20260508 applies the BIP110/RDTS network upgrade and HALTS until an
|
|
# operator explicitly sets consensusrules=rdts: a node running it sits frozen
|
|
# (blocks and headers both static) while reporting itself synced. A moving
|
|
# :latest tag can therefore stop the whole fleet following the chain without
|
|
# anyone choosing that. Changing this line is a consensus decision.
|
|
image: source.archipelago-foundation.org/lfg2025/bitcoin-knots:29.3.knots20260210
|
|
pull_policy: if-not-present
|
|
network: archy-net
|
|
entrypoint: ["sh", "-lc"]
|
|
custom_args:
|
|
# Sync-speed flags: -par=0 uses every core (was capped at 2 by
|
|
# --cpus=2, now removed for bitcoin/electrumx). -dbcache sized to
|
|
# the IBD sweet spot - 4GB on full nodes, 1GB on pruned. Container
|
|
# --memory=8g (config.rs::get_memory_limit) leaves headroom for
|
|
# mempool + connections.
|
|
#
|
|
# -printtoconsole=0: foreground bitcoind defaults console logging ON,
|
|
# which pushed every IBD "UpdateTip" line through conmon into journald
|
|
# (>1 GB/day on a fresh node). bitcoind still writes debug.log in the
|
|
# datadir (/var/lib/archipelago/bitcoin/debug.log, self-shrunk on
|
|
# restart) — use that for deep debugging; podman logs only carries
|
|
# entrypoint/startup errors.
|
|
- >-
|
|
BITCOIND="$(command -v bitcoind || true)";
|
|
if [ -z "$BITCOIND" ]; then
|
|
BITCOIND="$(find /opt -path '*/bin/bitcoind' -type f 2>/dev/null | sort | tail -n 1)";
|
|
fi;
|
|
if [ -z "$BITCOIND" ]; then
|
|
echo "bitcoind not found in image" >&2;
|
|
exit 127;
|
|
fi;
|
|
RPC_USER="$(printenv BITCOIN_RPC_USER)";
|
|
RPC_PASS="$(printenv BITCOIN_RPC_PASS)";
|
|
RPC_CONF="/tmp/rpc.conf";
|
|
umask 077;
|
|
{ echo "rpcuser=$RPC_USER"; echo "rpcpassword=$RPC_PASS"; } > "$RPC_CONF";
|
|
if [ -f /home/bitcoin/.bitcoin/bitcoin.conf ]; then
|
|
echo "archipelago: ignoring legacy datadir bitcoin.conf; RPC config comes from $RPC_CONF" >&2;
|
|
fi;
|
|
RPC_TXRELAY_AUTH="$(printenv BITCOIN_RPC_TXRELAY_RPCAUTH || true)";
|
|
DISK_GB_VALUE="$(printenv DISK_GB || true)";
|
|
RPC_HEADROOM="-rpcthreads=16 -rpcworkqueue=256";
|
|
RPC_TXRELAY_FLAGS="-rpcwhitelistdefault=0";
|
|
if [ -n "$RPC_TXRELAY_AUTH" ]; then
|
|
RPC_TXRELAY_FLAGS="$RPC_TXRELAY_FLAGS -rpcauth=$RPC_TXRELAY_AUTH -rpcwhitelist=txrelay:sendrawtransaction,submitpackage,testmempoolaccept,getmempoolinfo,getrawmempool,getmempoolentry,getnetworkinfo,getblockchaininfo,getblockcount,getblockhash,getblock,getblockheader,getrawtransaction,gettxout,gettxspendingprevout,decoderawtransaction,decodescript,estimatesmartfee,uptime,ping,getconnectioncount,getpeerinfo,getindexinfo,getdeploymentinfo,getchaintips";
|
|
fi;
|
|
if [ "${DISK_GB_VALUE:-0}" -lt 1000 ]; then
|
|
exec "$BITCOIND" -datadir=/home/bitcoin/.bitcoin -conf="$RPC_CONF" -allowignoredconf=1 -printtoconsole=0 -server=1 -prune=550 -rpcallowip=0.0.0.0/0 -rpcbind=0.0.0.0:8332 -listen=1 -bind=0.0.0.0:8333 -dbcache=2048 -par=0 -maxconnections=125 $RPC_HEADROOM $RPC_TXRELAY_FLAGS;
|
|
else
|
|
exec "$BITCOIND" -datadir=/home/bitcoin/.bitcoin -conf="$RPC_CONF" -allowignoredconf=1 -printtoconsole=0 -server=1 -txindex=1 -rpcallowip=0.0.0.0/0 -rpcbind=0.0.0.0:8332 -listen=1 -bind=0.0.0.0:8333 -dbcache=4096 -par=0 -maxconnections=125 $RPC_HEADROOM $RPC_TXRELAY_FLAGS;
|
|
fi
|
|
derived_env:
|
|
- key: DISK_GB
|
|
template: "{{DISK_GB}}"
|
|
secret_env:
|
|
- key: BITCOIN_RPC_PASS
|
|
secret_file: bitcoin-rpc-password
|
|
- key: BITCOIN_RPC_TXRELAY_RPCAUTH
|
|
secret_file: bitcoin-rpc-txrelay-rpcauth
|
|
data_uid: "100101:100101"
|
|
|
|
dependencies:
|
|
- storage: 500Gi
|
|
|
|
resources:
|
|
cpu_limit: 0
|
|
memory_limit: 8Gi
|
|
disk_limit: 500Gi
|
|
|
|
security:
|
|
capabilities: [CHOWN, FOWNER, SETUID, SETGID, DAC_OVERRIDE]
|
|
readonly_root: false
|
|
network_policy: isolated
|
|
|
|
ports:
|
|
# RPC is auth-only: publish host-local ONLY - the LAN cannot reach
|
|
# nodeIP:8332. In-node consumers (lnd, fedimint, btcpay, mempool-api)
|
|
# dial the container's archy-net alias directly (bitcoin-knots:8332),
|
|
# which needs no publish at all. Do NOT bind the archy-net gateway
|
|
# (10.89.0.1): rootlessport binds in the HOST netns where that address
|
|
# does not exist, and the whole unit crash-loops (2026-07-09, .228).
|
|
# P2P 8333 stays public.
|
|
- host: 8332
|
|
container: 8332
|
|
protocol: tcp
|
|
bind: 127.0.0.1
|
|
auth: local
|
|
- host: 8333
|
|
container: 8333
|
|
protocol: tcp
|
|
auth: none
|
|
auth_rationale: >-
|
|
Bitcoin p2p gossip. Peers are anonymous by design and speak the Bitcoin wire protocol, not HTTP.
|
|
|
|
volumes:
|
|
- type: bind
|
|
source: /var/lib/archipelago/bitcoin
|
|
target: /home/bitcoin/.bitcoin
|
|
options: [rw]
|
|
|
|
environment:
|
|
- BITCOIN_RPC_USER=archipelago
|
|
|
|
health_check:
|
|
type: tcp
|
|
endpoint: localhost:8332
|
|
interval: 30s
|
|
timeout: 5s
|
|
retries: 3
|
|
|
|
bitcoin_integration:
|
|
rpc_access: admin
|
|
sync_required: true
|
|
testnet_support: false
|
|
pruning_support: true
|