Companion to KamadoPool's df5e65d. The fallback submitter has been scrapped — primary bitcoind remains the sole submission target, with upstream's indefinite-retry behavior preserved. Drops the block-broadcast-fallback union from advanced config and the BACKUP_RPC_URLS / PENDING_BLOCKS_DIR / PENDING_BLOCKS_GRACE exports from docker_entrypoint.
317 lines
12 KiB
Bash
Executable File
317 lines
12 KiB
Bash
Executable File
#!/bin/bash
|
|
# Kamado Pool StartOS entrypoint.
|
|
#
|
|
# Reads /root/.kamado/start9/config.yaml via yq, resolves the chosen
|
|
# bitcoind variant (mainnet vs testnet4), exports the env vars the
|
|
# upstream ckpool entrypoint expects, renders ckpool.conf from the
|
|
# template shipped alongside this script, and then supervises
|
|
# ckpool-solo + kamado-api as a pair.
|
|
set -euo pipefail
|
|
|
|
CONFIG_FILE="/root/.kamado/start9/config.yaml"
|
|
if [[ ! -f "${CONFIG_FILE}" ]]; then
|
|
echo "kamado-entrypoint: config file missing: ${CONFIG_FILE}" >&2
|
|
exit 1
|
|
fi
|
|
|
|
q() { yq -r "$1" "${CONFIG_FILE}"; }
|
|
|
|
BITCOIND_VARIANT=$(q '.bitcoind.type')
|
|
case "${BITCOIND_VARIANT}" in
|
|
bitcoind)
|
|
export BITCOIN_RPC_HOST="bitcoind.embassy"
|
|
export BITCOIN_RPC_PORT=8332
|
|
# Satoshi's genesis block coinbase address. Used only for
|
|
# ckpool's startup coinbase-builder self-test; never credited
|
|
# a satoshi since solo mode pays the worker's stratum address.
|
|
SELFTEST_ADDRESS="1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa"
|
|
;;
|
|
bitcoind-testnet)
|
|
export BITCOIN_RPC_HOST="bitcoind-testnet.embassy"
|
|
export BITCOIN_RPC_PORT=48332
|
|
# Testnet genesis coinbase address — valid P2PKH on testnet4.
|
|
SELFTEST_ADDRESS="mipcBbFg9gMiCh81Kj8tqqdgoZub1ZJRfn"
|
|
;;
|
|
*)
|
|
echo "kamado-entrypoint: unknown bitcoind variant: ${BITCOIND_VARIANT}" >&2
|
|
exit 1
|
|
;;
|
|
esac
|
|
|
|
export BITCOIN_RPC_USER=$(q '.bitcoind.user')
|
|
export BITCOIN_RPC_PASSWORD=$(q '.bitcoind.password')
|
|
export STRATUM_PORT=$(q '.stratum-port // 3333')
|
|
export POOL_BTCSIG=$(q '.advanced.pool-identifier // "/Kamado/"')
|
|
export STARTDIFF=$(q '.advanced.startdiff // 16384')
|
|
export MINDIFF=$(q '.advanced.mindiff // 1000')
|
|
export MAXDIFF=$(q '.advanced.maxdiff // 0')
|
|
export DROPIDLE=$(q '.advanced.dropidle // 0')
|
|
LOG_LEVEL=$(q '.advanced.log-level // "info"')
|
|
ZMQ_ENABLED=$(q '.advanced.zmq-enabled // true')
|
|
TLS_ENABLED=$(q '.tls.enabled // "disabled"')
|
|
TLS_PORT=$(q '.tls.port // 3334')
|
|
|
|
# Empty MEMPOOL_BASE_URL means "use mempool.space defaults". When the
|
|
# user picks "Custom URL" in advanced config, we surface the value so
|
|
# kamado-api can include it in the snapshot and the UI can rewrite
|
|
# explorer links to point at the user's own mempool instance.
|
|
MEMPOOL_TYPE=$(q '.advanced.mempool-explorer.type // "default"')
|
|
if [[ "${MEMPOOL_TYPE}" == "custom" ]]; then
|
|
MEMPOOL_BASE_URL=$(q '.advanced.mempool-explorer.url // ""')
|
|
else
|
|
MEMPOOL_BASE_URL=""
|
|
fi
|
|
export MEMPOOL_BASE_URL
|
|
|
|
# CKPool-solo uses the worker's stratum username as the payout
|
|
# address and refuses to authenticate workers whose username is not
|
|
# a valid address on the active network. The conf `btcaddress` is
|
|
# only consulted once at startup, for ckpool's coinbase-builder
|
|
# self-test: it builds and validates a sample coinbase transaction
|
|
# against bitcoind before accepting any workers. Since no worker has
|
|
# connected yet at that point, we hand it the genesis block coinbase
|
|
# address for the active network — it's always valid, and solo mode
|
|
# never credits it a satoshi.
|
|
export POOL_BTCADDRESS="${SELFTEST_ADDRESS}"
|
|
|
|
# LOGDIR lives on the ckpool data volume so ckpool's own state files
|
|
# (users/, workers/, pool/pool.status, daily log files) survive restart.
|
|
# SOCKET_DIR is ephemeral — sockets are re-created on each start.
|
|
export LOGDIR=/root/.ckpool/logs
|
|
export SOCKET_DIR=/run/ckpool
|
|
|
|
# ckpool has TWO independent new-block detection paths. Wire up both
|
|
# so we're never blind to a tip change (every second of stale work
|
|
# in solo mode is hashrate burned on a dead block).
|
|
#
|
|
# 1. Blockpoll thread: polls bitcoind's getbestblockhash every
|
|
# BLOCKPOLL_MS. This only runs when notify=false — with
|
|
# notify=true the thread sleeps 5s and returns immediately.
|
|
# So keep notify=false.
|
|
# 2. ZMQ hashblock subscriber: instant push notifications from
|
|
# bitcoind. ckpool defaults zmqblock to tcp://127.0.0.1:28332
|
|
# which is useless in this container, so point it at the real
|
|
# endpoint explicitly.
|
|
export BITCOIN_NOTIFY=false
|
|
export ZMQ_BLOCK="tcp://${BITCOIN_RPC_HOST}:28332"
|
|
export BLOCKPOLL_MS=100
|
|
export UPDATE_INTERVAL_S=30
|
|
|
|
# ckpool binds two stratum sockets — a public plaintext port and a
|
|
# loopback-only port that stunnel forwards TLS traffic to. Clients
|
|
# arriving via the second bind get client.server == 1 in the runtime
|
|
# JSON, which lets the dashboard tag them with a TLS lock icon
|
|
# without resorting to source-IP heuristics. Even when TLS is
|
|
# disabled the second bind is harmless (nothing connects to it).
|
|
export TLS_INTERNAL_PORT=3437
|
|
|
|
mkdir -p "${LOGDIR}" "${SOCKET_DIR}" /etc/ckpool
|
|
|
|
# Render ckpool.conf using the same sed approach as the upstream
|
|
# KamadoPool ckpool entrypoint — the template is bundled into the
|
|
# image at build time.
|
|
TEMPLATE=/etc/ckpool/ckpool.conf.template
|
|
CONF=/etc/ckpool/ckpool.conf
|
|
sed \
|
|
-e "s|\${BITCOIN_RPC_HOST}|${BITCOIN_RPC_HOST}|g" \
|
|
-e "s|\${BITCOIN_RPC_PORT}|${BITCOIN_RPC_PORT}|g" \
|
|
-e "s|\${BITCOIN_RPC_USER}|${BITCOIN_RPC_USER}|g" \
|
|
-e "s|\${BITCOIN_RPC_PASSWORD}|${BITCOIN_RPC_PASSWORD}|g" \
|
|
-e "s|\${BITCOIN_NOTIFY}|${BITCOIN_NOTIFY}|g" \
|
|
-e "s|\${POOL_BTCADDRESS}|${POOL_BTCADDRESS}|g" \
|
|
-e "s|\${POOL_BTCSIG}|${POOL_BTCSIG}|g" \
|
|
-e "s|\${BLOCKPOLL_MS}|${BLOCKPOLL_MS}|g" \
|
|
-e "s|\${UPDATE_INTERVAL_S}|${UPDATE_INTERVAL_S}|g" \
|
|
-e "s|\${STRATUM_PORT}|${STRATUM_PORT}|g" \
|
|
-e "s|\${TLS_INTERNAL_PORT}|${TLS_INTERNAL_PORT}|g" \
|
|
-e "s|\${MINDIFF}|${MINDIFF}|g" \
|
|
-e "s|\${STARTDIFF}|${STARTDIFF}|g" \
|
|
-e "s|\${MAXDIFF}|${MAXDIFF}|g" \
|
|
-e "s|\${DROPIDLE}|${DROPIDLE}|g" \
|
|
-e "s|\${LOGDIR}|${LOGDIR}|g" \
|
|
-e "s|\${ZMQ_BLOCK}|${ZMQ_BLOCK}|g" \
|
|
"${TEMPLATE}" > "${CONF}"
|
|
|
|
echo "kamado-entrypoint: starting ckpool (solo, ${BITCOIND_VARIANT}) on port ${STRATUM_PORT}"
|
|
/usr/local/bin/ckpool --btcsolo --config "${CONF}" --sockdir "${SOCKET_DIR}" --log-shares &
|
|
CKPOOL_PID=$!
|
|
|
|
# DB_PATH must live on the persisted main volume; the default
|
|
# /var/lib/kamado/kamado.db is ephemeral container storage.
|
|
KAMADO_DATA_DIR=/root/.kamado/data
|
|
mkdir -p "${KAMADO_DATA_DIR}"
|
|
export LISTEN_ADDR=":8080"
|
|
export CKPOOL_SOCKDIR="${SOCKET_DIR}"
|
|
export CKPOOL_LOGFILE="${LOGDIR}/ckpool.log"
|
|
export DB_PATH="${KAMADO_DATA_DIR}/kamado.db"
|
|
export BITCOIN_RPC_URL="http://${BITCOIN_RPC_HOST}:${BITCOIN_RPC_PORT}"
|
|
export POLL_INTERVAL=5s
|
|
export KAMADO_LOG_LEVEL="${LOG_LEVEL}"
|
|
if [[ "${ZMQ_ENABLED}" == "true" ]]; then
|
|
export BITCOIN_ZMQ_BLOCK="tcp://${BITCOIN_RPC_HOST}:28332"
|
|
else
|
|
export BITCOIN_ZMQ_BLOCK=""
|
|
fi
|
|
|
|
echo "kamado-entrypoint: starting kamado-api"
|
|
/usr/local/bin/kamado-api &
|
|
API_PID=$!
|
|
|
|
# Optional TLS stratum via stunnel sidecar.
|
|
STUNNEL_PID=""
|
|
if [[ "${TLS_ENABLED}" == "enabled" ]]; then
|
|
TLS_DIR=/root/.kamado/tls
|
|
CRT="${TLS_DIR}/stratum.crt"
|
|
KEY="${TLS_DIR}/stratum.key"
|
|
CERT="${TLS_DIR}/stratum.pem"
|
|
MARKER="${TLS_DIR}/cert_version"
|
|
mkdir -p "${TLS_DIR}"
|
|
|
|
# Bump TLS_CERT_VERSION any time the cert format/extensions change.
|
|
# The startup check regenerates whenever the marker file is missing
|
|
# or doesn't match this version. This is more reliable than poking
|
|
# at the existing cert's extensions — we know *exactly* when a new
|
|
# shape is required and the upgrade self-heals on next boot.
|
|
# v4: broaden subjectAltName to cover mDNS / LAN / Tor hostnames
|
|
# so miner firmwares that verify the SAN against the hostname
|
|
# they were pointed at (e.g. AxeOS connecting to host.local)
|
|
# stop failing with MBEDTLS_ERR_X509_CERT_VERIFY_FAILED.
|
|
TLS_CERT_VERSION=4
|
|
|
|
NEEDS_REGEN=false
|
|
if [[ ! -f "${CERT}" || ! -f "${CRT}" || ! -f "${KEY}" ]]; then
|
|
NEEDS_REGEN=true
|
|
elif [[ ! -f "${MARKER}" ]] \
|
|
|| [[ "$(cat "${MARKER}" 2>/dev/null)" != "${TLS_CERT_VERSION}" ]]; then
|
|
echo "kamado-entrypoint: TLS cert is older format (want v${TLS_CERT_VERSION}); regenerating"
|
|
NEEDS_REGEN=true
|
|
fi
|
|
|
|
if [[ "${NEEDS_REGEN}" == "true" ]]; then
|
|
echo "kamado-entrypoint: generating self-signed stratum TLS cert v${TLS_CERT_VERSION}"
|
|
# Write the extensions to a config file rather than rely on
|
|
# `-addext`: some openssl builds emit them into unpredictable
|
|
# locations (e.g. CSR instead of the cert), and this is the
|
|
# documented, cross-version way to pin the full extension set.
|
|
CONF=$(mktemp)
|
|
cat > "${CONF}" <<'OPENSSL_CONF'
|
|
[ req ]
|
|
default_bits = 2048
|
|
default_md = sha256
|
|
prompt = no
|
|
distinguished_name = req_dn
|
|
x509_extensions = v3_cert
|
|
|
|
[ req_dn ]
|
|
CN = kamado-pool
|
|
|
|
[ v3_cert ]
|
|
basicConstraints = critical, CA:FALSE
|
|
keyUsage = critical, digitalSignature, keyEncipherment
|
|
extendedKeyUsage = serverAuth
|
|
subjectKeyIdentifier = hash
|
|
subjectAltName = @alt_names
|
|
|
|
[ alt_names ]
|
|
# Specific StartOS / local names the pool might be reached through.
|
|
DNS.1 = kamado-pool.embassy
|
|
DNS.2 = kamado-pool
|
|
DNS.3 = localhost
|
|
# Wildcard SANs covering the TLDs miners typically use:
|
|
# *.local -> mDNS / Bonjour (e.g. obese-admirer.local on AxeOS)
|
|
# *.embassy -> StartOS inter-service hostnames
|
|
# *.onion -> Tor hidden services
|
|
# *.home.arpa -> RFC 8375 home network namespace
|
|
# *.lan -> common consumer router default TLD
|
|
# *.internal -> some LAN setups
|
|
# Strictly leftmost-label wildcards per RFC 6125; libraries that
|
|
# enforce this (mbedtls, OpenSSL, BoringSSL, Go crypto/tls) all
|
|
# accept them.
|
|
DNS.4 = *.local
|
|
DNS.5 = *.embassy
|
|
DNS.6 = *.onion
|
|
DNS.7 = *.home.arpa
|
|
DNS.8 = *.lan
|
|
DNS.9 = *.internal
|
|
IP.1 = 127.0.0.1
|
|
OPENSSL_CONF
|
|
|
|
openssl req -x509 -newkey rsa:2048 -nodes \
|
|
-keyout "${KEY}" \
|
|
-out "${CRT}" \
|
|
-days 3650 \
|
|
-config "${CONF}" \
|
|
>/dev/null 2>&1
|
|
rm -f "${CONF}"
|
|
|
|
# stunnel reads cert+key in either order, but cert-first is the
|
|
# convention openssl and most tooling expect.
|
|
cat "${CRT}" "${KEY}" > "${CERT}"
|
|
chmod 600 "${KEY}" "${CERT}"
|
|
printf '%s\n' "${TLS_CERT_VERSION}" > "${MARKER}"
|
|
|
|
# Log the extensions so operators can verify the cert is sane
|
|
# from the service logs without needing to exec into the
|
|
# container.
|
|
echo "kamado-entrypoint: cert extensions:"
|
|
openssl x509 -in "${CRT}" -noout -ext subjectAltName,extendedKeyUsage,keyUsage 2>&1 \
|
|
| sed 's/^/ /'
|
|
fi
|
|
|
|
FINGERPRINT=$(openssl x509 -in "${CRT}" -noout -fingerprint -sha256 | cut -d= -f2)
|
|
printf '%s\n' "${FINGERPRINT}" > "${TLS_DIR}/fingerprint.txt"
|
|
echo "kamado-entrypoint: stratum TLS SHA256 fingerprint: ${FINGERPRINT}"
|
|
|
|
STUNNEL_CONF=/etc/stunnel/stratum.conf
|
|
mkdir -p /etc/stunnel
|
|
cat > "${STUNNEL_CONF}" <<EOF
|
|
foreground = yes
|
|
pid =
|
|
output = /dev/stdout
|
|
# debug = 5 (notice) so each successful TLS handshake produces a
|
|
# "Service [stratum] accepted connection" / "connected from" pair
|
|
# in the service logs. Failures (bad cert, alert messages, cipher
|
|
# rejection) still surface at level 3, so both happy- and sad-path
|
|
# events are visible without flipping levels per incident.
|
|
debug = 5
|
|
# Pin a modern TLS floor. Any miner firmware younger than ~2018
|
|
# speaks TLS 1.2, and TLS 1.0/1.1 are deprecated anyway.
|
|
sslVersion = all
|
|
options = NO_SSLv2
|
|
options = NO_SSLv3
|
|
options = NO_TLSv1
|
|
options = NO_TLSv1_1
|
|
|
|
[stratum]
|
|
accept = 0.0.0.0:${TLS_PORT}
|
|
# Forward to ckpool's loopback-only TLS bind, not the public plaintext
|
|
# port. ckpool tags traffic by serverurl index (server == 1 -> TLS),
|
|
# which the dashboard reads to render a lock icon next to the client.
|
|
connect = 127.0.0.1:${TLS_INTERNAL_PORT}
|
|
cert = ${CERT}
|
|
# No client-cert auth — stratum over TLS is opportunistic encryption;
|
|
# the stratum protocol layer handles miner auth via username.
|
|
verify = 0
|
|
EOF
|
|
|
|
echo "kamado-entrypoint: starting stunnel on :${TLS_PORT} -> :${STRATUM_PORT}"
|
|
/usr/bin/stunnel4 "${STUNNEL_CONF}" &
|
|
STUNNEL_PID=$!
|
|
fi
|
|
|
|
term() {
|
|
echo "kamado-entrypoint: SIGTERM — shutting down"
|
|
kill -TERM "${API_PID}" "${CKPOOL_PID}" ${STUNNEL_PID:-} 2>/dev/null || true
|
|
wait "${API_PID}" "${CKPOOL_PID}" ${STUNNEL_PID:-} 2>/dev/null || true
|
|
exit 0
|
|
}
|
|
trap term TERM INT
|
|
|
|
# shellcheck disable=SC2086
|
|
wait -n ${CKPOOL_PID} ${API_PID} ${STUNNEL_PID:-}
|
|
EXIT_CODE=$?
|
|
echo "kamado-entrypoint: a supervised process exited (${EXIT_CODE}), stopping the rest"
|
|
kill -TERM "${API_PID}" "${CKPOOL_PID}" ${STUNNEL_PID:-} 2>/dev/null || true
|
|
wait || true
|
|
exit "${EXIT_CODE}"
|