#!/usr/bin/env bash
# XDC devnet5050 installer (chainId 5050, legacy XDPoSChain, mainnet-parity).
# Public artifacts only (binary + genesis + bootnodes). Keys are NEVER fetched from
# the web — for validators the coinbase keystore is placed privately (KEYSTORE_SRC).
#
#   NODETYPE=bootnode bash install-devnet5050.sh
#   NODETYPE=validator NODEID=1 KEYSTORE_SRC=/root/net5050/node1/keystore PW_FILE=/root/net5050/pw.txt P2P_PORT=30550 bash install-devnet5050.sh
#   NODETYPE=full HTTP_ADDR=0.0.0.0 bash install-devnet5050.sh
# Knobs: NODETYPE(validator|bootnode|full|fast|archive) NODEID CLIENT WORKDIR
#   P2P_PORT HTTP_PORT WS_PORT BOOTPORT HTTP_ADDR NAME STATS BASE EXTRA_BOOTNODES KEYSTORE_SRC PW_FILE
set -euo pipefail

BASE="${BASE:-https://xdc.network/snapshots/devnet5050}"
NETWORK=devnet5050; NETWORKID=5050
CLIENT="${CLIENT:-legacy}"; NODETYPE="${NODETYPE:-full}"; NODEID="${NODEID:-}"
WORKDIR="${WORKDIR:-/root/xdc-${NETWORK}${NODEID}}"
P2P_PORT="${P2P_PORT:-30550}"; HTTP_PORT="${HTTP_PORT:-8550}"; WS_PORT="${WS_PORT:-8551}"; BOOTPORT="${BOOTPORT:-30551}"
HTTP_ADDR="${HTTP_ADDR:-127.0.0.1}"        # full/fast/archive RPC bind; localhost by default
STATS="${STATS:-xdc_openscan_stats_2026@stats.xdcindia.com:443}"
NAME="${NAME:-net5050-$(hostname)-${NODETYPE}${NODEID}}"

OS=$(uname -s | tr '[:upper:]' '[:lower:]')
ARCH=$(uname -m); case "$ARCH" in x86_64) ARCH=amd64;; aarch64|arm64) ARCH=arm64;; esac
# Advertise the node's public IP so bootnode discovery lets peers dial back (cloud/NAT fix).
NAT_EXTIP="${NAT_EXTIP:-$(curl -fsS -m5 https://api.ipify.org 2>/dev/null || curl -fsS -m5 https://ifconfig.me 2>/dev/null || hostname -I 2>/dev/null | awk '{print $1}')}"
NATARG=""; [ -n "$NAT_EXTIP" ] && NATARG="--nat extip:$NAT_EXTIP"
echo "[devnet5050] client=$CLIENT type=$NODETYPE id=${NODEID:-none} os=$OS arch=$ARCH name=$NAME extip=${NAT_EXTIP:-auto}"

mkdir -p "$WORKDIR/bin"
dl() { curl -fsSL "$1" -o "$2"; }
echo "[*] fetch binary + genesis + bootnodes (public)"
# Skip the (large) binary re-download on restart unless FORCE_DL=1.
if [ "${FORCE_DL:-0}" = 1 ] || [ ! -x "$WORKDIR/bin/XDC" ]; then
  dl "$BASE/XDC-${OS}-${ARCH}" "$WORKDIR/bin/XDC"; chmod +x "$WORKDIR/bin/XDC"
fi
dl "$BASE/bootnode-${OS}-${ARCH}" "$WORKDIR/bin/bootnode" && chmod +x "$WORKDIR/bin/bootnode" || true
dl "$BASE/genesis.json" "$WORKDIR/genesis.json"
BOOTNODES="$(curl -fsSL "$BASE/bootnodes.txt" 2>/dev/null | grep -E '^enode://' | paste -sd, -)"
[ -n "${EXTRA_BOOTNODES:-}" ] && BOOTNODES="${BOOTNODES:+$BOOTNODES,}$EXTRA_BOOTNODES"
XDC="$WORKDIR/bin/XDC"

if [ "$NODETYPE" = bootnode ]; then
  [ -f "$WORKDIR/boot.key" ] || "$WORKDIR/bin/bootnode" -genkey "$WORKDIR/boot.key"
  pkill -f "bootnode -nodekey $WORKDIR/boot.key" 2>/dev/null || true
  nohup "$WORKDIR/bin/bootnode" -nodekey "$WORKDIR/boot.key" -addr ":$BOOTPORT" -verbosity 4 > "$WORKDIR/bootnode.log" 2>&1 &
  echo "[ok] bootnode pid=$! :$BOOTPORT enode=$("$WORKDIR/bin/bootnode" -nodekey "$WORKDIR/boot.key" -writeaddress 2>/dev/null)"
  exit 0
fi

[ -d "$WORKDIR/data/XDC/chaindata" ] || { echo "[*] init genesis"; "$XDC" --datadir "$WORKDIR/data" init "$WORKDIR/genesis.json"; }

# Save knobs so ctl.sh can restart with the same config.
cat > "$WORKDIR/node.env" <<ENV
NODETYPE=$NODETYPE
NODEID=$NODEID
P2P_PORT=$P2P_PORT
HTTP_PORT=$HTTP_PORT
WS_PORT=$WS_PORT
HTTP_ADDR=$HTTP_ADDR
NAME=$NAME
ENV

# Per-node control script: start|stop|restart|attach|logs|status|peers
cat > "$WORKDIR/ctl.sh" <<CTLHEAD
#!/usr/bin/env bash
WORKDIR="$WORKDIR"; BASE="$BASE"
CTLHEAD
cat >> "$WORKDIR/ctl.sh" <<'CTLBODY'
XDC="$WORKDIR/bin/XDC"; IPC="$WORKDIR/data/XDC.ipc"; LOG="$WORKDIR/node.log"
set -a; . "$WORKDIR/node.env" 2>/dev/null; set +a
UNIT="net5050-${NODETYPE}${NODEID}"
js(){ printf '%s\n' "$1" | "$XDC" attach "$IPC" 2>/dev/null; }
case "${1:-status}" in
  start|restart)
    curl -fsSL "$BASE/install.sh" -o /tmp/i5050.sh 2>/dev/null || { echo "[err] installer download failed"; exit 1; }
    env NODETYPE="$NODETYPE" NODEID="$NODEID" P2P_PORT="$P2P_PORT" HTTP_PORT="$HTTP_PORT" \
        WS_PORT="$WS_PORT" HTTP_ADDR="$HTTP_ADDR" NAME="$NAME" WORKDIR="$WORKDIR" bash /tmp/i5050.sh ;;
  stop)  systemctl stop "$UNIT" 2>/dev/null; systemctl reset-failed "$UNIT" 2>/dev/null; pkill -f "XDC --datadir $WORKDIR/data " 2>/dev/null; echo "[stopped $UNIT]" ;;
  attach) exec "$XDC" attach "$IPC" ;;
  logs)  exec tail -n "${2:-100}" -f "$LOG" ;;
  status)
    echo "unit=$UNIT active=$(systemctl is-active "$UNIT" 2>/dev/null)"
    [ -S "$IPC" ] && js 'console.log("block="+eth.blockNumber+" peers="+net.peerCount+" mining="+eth.mining)' | grep "block="
    echo "type=$NODETYPE id=$NODEID workdir=$WORKDIR" ;;
  peers) js 'console.log(JSON.stringify(admin.peers.map(function(p){return p.network.remoteAddress})))' | grep "\[" ;;
  *) echo "usage: $(basename "$0") {start|stop|restart|attach|logs [N]|status|peers}" ;;
esac
CTLBODY
chmod +x "$WORKDIR/ctl.sh"
echo "[ok] control script: $WORKDIR/ctl.sh {start|stop|restart|attach|logs|status|peers}"

SYNCMODE=full; GCMODE=""; case "$NODETYPE" in fast) SYNCMODE=fast;; archive) GCMODE="--gcmode archive";; esac
UNIT="net5050-${NODETYPE}${NODEID:-}"

if [ "$NODETYPE" = validator ]; then
  [ -z "$NODEID" ] && { echo "ERROR: validator needs NODEID=1..4"; exit 1; }
  KSRC="${KEYSTORE_SRC:-/root/net5050/node${NODEID}/keystore}"
  PW="${PW_FILE:-/root/net5050/pw.txt}"
  [ -d "$KSRC" ] && [ -f "$PW" ] || { echo "ERROR: place coinbase keystore in $KSRC + password at $PW (private, not web)"; exit 1; }
  mkdir -p "$WORKDIR/data/keystore"; cp "$KSRC"/* "$WORKDIR/data/keystore/"; cp "$PW" "$WORKDIR/pw.txt"
  COINBASE="0x$(ls "$WORKDIR/data/keystore" | grep -oE '[0-9a-fA-F]{40}$' | head -1)"
  echo "[*] validator coinbase=$COINBASE (RPC NOT exposed → no insecure-unlock)"
  # MINTER: --unlock with NO --http/--ws, so geth permits unlock without --allow-insecure-unlock
  cat > "$WORKDIR/launch.sh" <<LAUNCH
#!/usr/bin/env bash
exec "$XDC" --datadir "$WORKDIR/data" --networkid $NETWORKID --syncmode full --port $P2P_PORT --bootnodes "$BOOTNODES" --maxpeers 50 $NATARG --mine --etherbase "$COINBASE" --unlock "$COINBASE" --password "$WORKDIR/pw.txt" --ethstats "$NAME:$STATS" --verbosity 3
LAUNCH
else
  cat > "$WORKDIR/launch.sh" <<LAUNCH
#!/usr/bin/env bash
exec "$XDC" --datadir "$WORKDIR/data" --networkid $NETWORKID --syncmode $SYNCMODE $GCMODE --port $P2P_PORT --bootnodes "$BOOTNODES" --maxpeers 50 $NATARG --http --http.addr $HTTP_ADDR --http.port $HTTP_PORT --http.corsdomain '*' --http.vhosts '*' --http.api eth,net,web3,XDPoS,debug,txpool --ws --ws.addr $HTTP_ADDR --ws.port $WS_PORT --ws.origins '*' --ethstats "$NAME:$STATS" --verbosity 3
LAUNCH
fi
chmod +x "$WORKDIR/launch.sh"
# Robust supervision: a transient systemd unit survives SSH logout. Plain nohup does NOT —
# modern systemd session cleanup kills session-scoped procs on logout (this killed the legacy
# validators). Logs → $WORKDIR/node.log so ctl.sh logs/status keep working.
systemctl stop "$UNIT" 2>/dev/null || true; systemctl reset-failed "$UNIT" 2>/dev/null || true
pkill -f "XDC --datadir $WORKDIR/data " 2>/dev/null || true; sleep 1
systemd-run --unit="$UNIT" --collect /bin/bash -c "exec $WORKDIR/launch.sh > $WORKDIR/node.log 2>&1"
echo "[ok] $NODETYPE unit=$UNIT p2p=$P2P_PORT log=$WORKDIR/node.log (systemd-supervised)"

# Persistent static mesh (discovery-independent): dial every known net5050 peer directly
# via IPC once the node is up. Survives restarts since the installer re-runs this each start.
PEERS="$(curl -fsSL "$BASE/peers.txt" 2>/dev/null | grep -E '^enode://')"
if [ -n "$PEERS" ]; then
  ( for i in $(seq 1 30); do [ -S "$WORKDIR/data/XDC.ipc" ] && break; sleep 2; done
    n=0; while IFS= read -r e; do
      [ -z "$e" ] && continue
      printf 'admin.addPeer("%s")\n' "$e" | "$XDC" attach "$WORKDIR/data/XDC.ipc" >/dev/null 2>&1 && n=$((n+1))
    done <<EOF2
$PEERS
EOF2
    echo "[mesh] dialed $n static peers from peers.txt" >> "$WORKDIR/node.log" ) &
  echo "[ok] static-mesh dialer armed ($(echo "$PEERS" | grep -c enode) peers)"
fi
echo "[i] bootnodes: $BOOTNODES"
