git / moonbite-network / deploy/railway-node/entrypoint.sh · raw

#!/usr/bin/env bash# Entrypoint for the MoonBite seed node on Railway.## Data lives on a Railway Volume mounted at /data (set the Volume mount path# to /data in the service settings so the chain survives restarts/redeploys).## RPC credentials come from Railway service Variables:#   MOONBITE_RPC_USER, MOONBITE_RPC_PASSWORD   (required)#   MINE=1                                    (optional: mine blocks on this node)#   MINE_ADDRESS=<moonbite address>           (optional: where mined coins go)set -euo pipefailDATADIR=/datamkdir -p "$DATADIR"# Credentials. DEPLOY.md told you to set BIGCOIN_RPC_USER/PASSWORD while this# script demanded MOONBITE_RPC_USER/PASSWORD and hard-failed without them, so a# service configured from the docs exited on the first line it reached. Accept# either spelling rather than making the name the thing that breaks the deploy.MOONBITE_RPC_USER="${MOONBITE_RPC_USER:-${BIGCOIN_RPC_USER:-}}"MOONBITE_RPC_PASSWORD="${MOONBITE_RPC_PASSWORD:-${BIGCOIN_RPC_PASSWORD:-}}": "${MOONBITE_RPC_USER:?set MOONBITE_RPC_USER (or BIGCOIN_RPC_USER) in Railway Variables}": "${MOONBITE_RPC_PASSWORD:?set MOONBITE_RPC_PASSWORD (or BIGCOIN_RPC_PASSWORD) in Railway Variables}"# The chain this binary considers valid. A /data Volume survives redeploys, so# after a consensus change (the 10 -> 50 MBITE subsidy, which moved genesis) the# volume still holds the OLD chain and the node sits on a fork nothing else# recognises: it handshakes with peers, then never answers getheaders. Stamp the# expected genesis alongside the data and wipe chain state when it disagrees.# Set RESET_CHAIN=1 to force the wipe. Wallets are left alone.EXPECTED_GENESIS=3d053c590c9dcaa972d12f20793cde15e060f6cbcd1ea7bd05f4c80724f39573STAMP="$DATADIR/.genesis"if [[ "${RESET_CHAIN:-0}" == "1" ]] || { [[ -e "$DATADIR/blocks" ]] && [[ "$(cat "$STAMP" 2>/dev/null || echo none)" != "$EXPECTED_GENESIS" ]]; }; then  echo "Chain data does not match genesis $EXPECTED_GENESIS - resetting to genesis."  rm -rf "$DATADIR"/blocks "$DATADIR"/chainstate "$DATADIR"/indexes "$DATADIR"/mweb  rm -f  "$DATADIR"/leaf*.dat "$DATADIR"/mempool.dat "$DATADIR"/peers.dat          "$DATADIR"/banlist.dat "$DATADIR"/anchors.dat "$DATADIR"/fee_estimates.dat  rm -rf "$DATADIR"/probefiecho "$EXPECTED_GENESIS" > "$STAMP"# P2P is public (via Railway TCP Proxy on 9444); RPC is NOT.# SECURITY: the RPC was previously bound to 0.0.0.0 + ::  with rpcallowip# 0.0.0.0/0 + ::/0, which exposes a wallet-loaded node's spend/mining/admin RPC# to anything that can reach the port, behind only Basic auth — one weak/leaked# rpcpassword = full wallet drain. RPC is now loopback-only (fail closed). If# this (retired) Railway path is ever revived with an internal explorer that# needs the node RPC, bind it explicitly to the service's PRIVATE network# interface and set rpcallowip to that private subnet only — never 0.0.0.0/0,# and never attach a public domain/proxy to the RPC port.ARGS=(  -datadir="$DATADIR"  -server=1  -txindex=1  -listen=1  -bind=0.0.0.0:9444  -rpcbind=127.0.0.1  -rpcallowip=127.0.0.1  -rpcport=9445  -rpcuser="$MOONBITE_RPC_USER"  -rpcpassword="$MOONBITE_RPC_PASSWORD"  -printtoconsole  # --- Memory caps so the container is not OOM-killed on small Railway plans. ---  # dbcache defaults to ~450MB; on a 512MB container that alone triggers SIGKILL  # ("Killed"). These keep the resident set well under ~256MB. Override any of  # them via Railway Variables (DBCACHE / MAXMEMPOOL / PAR / MAXCONNECTIONS).  -dbcache="${DBCACHE:-64}"  -maxmempool="${MAXMEMPOOL:-50}"  -par="${PAR:-1}"  -maxconnections="${MAXCONNECTIONS:-40}")# Outbound peers. Without at least one, this node only ever sees INBOUND# connections (everything arriving through the TCP proxy is inbound), and a# node in initial block download refuses to fetch block bodies from inbound# peers - so it accepted headers from the network and sat at height 0 forever.# Dialling the seed makes that link outbound from our side, which block# download trusts. Comma-separated host:port list, overridable.IFS=',' read -ra _PEERS <<< "${ADDNODES:-67.205.154.64:9444}"for _peer in "${_PEERS[@]}"; do  [[ -n "$_peer" ]] && ARGS+=( -addnode="$_peer" )done# nMinimumChainWork is already zero in this binary's chainparams (verified in# source and in the shipped binary), so no override is needed. The variable# stays as an escape hatch only.if [[ -n "${MINIMUMCHAINWORK:-}" ]]; then  ARGS+=( -minimumchainwork="$MINIMUMCHAINWORK" )fi# Optional: advertise the public P2P address so peers can dial back in.if [[ -n "${EXTERNAL_IP:-}" ]]; then  ARGS+=( -externalip="$EXTERNAL_IP" -discover=0 )fiecho "Starting moonbited (MoonBite seed node) ..."moonbited "${ARGS[@]}" &NODE_PID=$!# Optional built-in miner so the fresh chain actually produces blocks.# A brand-new chain with zero miners stays at height 0 forever.if [[ "${MINE:-0}" == "1" ]]; then  echo "MINE=1 -> will mine to ${MINE_ADDRESS:-<new wallet address>}"  (    # Wait for RPC to come up.    until moonbite-cli -datadir="$DATADIR" -rpcport=9445 \        -rpcuser="$MOONBITE_RPC_USER" -rpcpassword="$MOONBITE_RPC_PASSWORD" \        getblockchaininfo >/dev/null 2>&1; do      sleep 2    done    mcli() {      moonbite-cli -datadir="$DATADIR" -rpcport=9445 \        -rpcuser="$MOONBITE_RPC_USER" -rpcpassword="$MOONBITE_RPC_PASSWORD" "$@"    }    ADDR="${MINE_ADDRESS:-}"    if [[ -z "$ADDR" ]]; then      # First boot: create the "miner" wallet. On every later restart the wallet      # already exists on the /data volume but Core does NOT auto-load it, so      # createwallet fails and we must loadwallet instead. Try both (ignoring the      # "already exists / already loaded" errors) so the wallet is loaded in all      # three states: absent, present-unloaded, present-loaded.      mcli createwallet miner >/dev/null 2>&1 || true      mcli loadwallet miner   >/dev/null 2>&1 || true      # Do not start mining until we actually hold an address; a blank ADDR makes      # every generatetoaddress call fail silently and the chain never advances.      for _ in $(seq 1 15); do        ADDR=$(mcli getnewaddress 2>/dev/null || true)        [[ -n "$ADDR" ]] && break        mcli loadwallet miner >/dev/null 2>&1 || true        sleep 2      done      echo "Mining to new wallet address: ${ADDR:-<none: wallet load failed>}"    fi    if [[ -z "$ADDR" ]]; then      echo "Mining disabled: could not obtain a wallet address. Node stays up."      exit 0    fi    # Auto-stop after MINE_BLOCKS blocks (default 20) so a test run cannot    # drain Railway credit. Set MINE_BLOCKS=0 to mine indefinitely.    TARGET="${MINE_BLOCKS:-20}"    HEIGHT=$(moonbite-cli -datadir="$DATADIR" -rpcport=9445 \      -rpcuser="$MOONBITE_RPC_USER" -rpcpassword="$MOONBITE_RPC_PASSWORD" \      getblockcount 2>/dev/null || echo 0)    STOP_AT=$(( HEIGHT + TARGET ))    echo "Mining test: current height $HEIGHT, will mine to $STOP_AT then stop."    # Slow, steady mining loop (1 block attempt every few seconds) so we don't    # peg the container CPU. RandomX is CPU-heavy; keep the batch small.    while kill -0 "$NODE_PID" 2>/dev/null; do      moonbite-cli -datadir="$DATADIR" -rpcport=9445 \        -rpcuser="$MOONBITE_RPC_USER" -rpcpassword="$MOONBITE_RPC_PASSWORD" \        generatetoaddress 1 "$ADDR" >/dev/null 2>&1 || true      if [[ "$TARGET" != "0" ]]; then        HEIGHT=$(moonbite-cli -datadir="$DATADIR" -rpcport=9445 \          -rpcuser="$MOONBITE_RPC_USER" -rpcpassword="$MOONBITE_RPC_PASSWORD" \          getblockcount 2>/dev/null || echo "$HEIGHT")        if (( HEIGHT >= STOP_AT )); then          echo "=========================================================="          echo " TEST COMPLETE: mined $TARGET blocks, height is now $HEIGHT."          echo " Mining stopped to save credit. Node still running (idle)."          echo " Pause this Railway service now to stop all spending."          echo "=========================================================="          break        fi      fi      sleep 5    done  ) &fiwait "$NODE_PID"