Files
arrr-scripts/setup-arrr-lightwalletd.sh

1174 lines
34 KiB
Bash
Executable File

#!/usr/bin/env bash
set -euo pipefail
IFS=$'\n\t'
export DEBIAN_FRONTEND=noninteractive
DEFAULT_GO_VERSION="1.21.5"
DEFAULT_NODE_VERSION="24.19.0"
DEFAULT_BIND_ADDR="127.0.0.1:9067"
DEFAULT_HTTP_BIND_ADDR="127.0.0.1:9068"
DEFAULT_DATA_DIR="/var/lib/lightwalletd"
DEFAULT_P2P_PORT="45452"
DEFAULT_EXPLORER_PORT="3001"
DEFAULT_EXPLORER_ZMQ_PORT="28332"
# Pirate 6.0.0 is the Ironwood network-upgrade release. The corresponding
# lightwalletd revision includes the v6 transaction parser and Ironwood
# commitment-tree RPC support. Keep the pair pinned so a re-run is
# reproducible instead of silently changing with upstream master.
DEFAULT_PIRATE_REF="v6.0.0"
DEFAULT_LIGHTWALLETD_REF="43b29b4229cc68adae8a807931d26f9a5f5d98a6"
# Keep the optional public services reproducible too. These revisions are the
# current Pirate-maintained seed-node components tested with the v6 network.
DEFAULT_DNSSEED_REF="6d3ed9c6d188c01846bb2f4daf7d67b8b75ab218"
DEFAULT_BITCORE_NODE_REF="f08d0f3d3e158d7128e8e2d72e8fbb0db1f5c9e9"
DEFAULT_INSIGHT_API_REF="d7414b4f37c9fae937c2cfeb3c4ca33c33969821"
DEFAULT_INSIGHT_UI_REF="8fad6bc0109bce14475a9e9cd91368288a48b8d7"
script_name=$(basename "$0")
usage() {
cat <<EOF
Usage: $script_name [options]
Sets up the Pirate Chain daemon and ARRRwallet lightwalletd server on Ubuntu.
Options:
--hostname HOSTNAME Configure nginx for HOSTNAME + proxy lightwalletd (implies port 80)
--explorer-hostname HOSTNAME
Enable the HTTPS Insight block explorer at HOSTNAME
(requires --lets-encrypt and --email)
--email EMAIL Email address to register with certbot when --lets-encrypt is used
--lets-encrypt Run certbot against configured nginx hostname(s) (requires --email)
--tls-cert PATH Use a custom TLS certificate when not using nginx
--tls-key PATH Use a custom TLS key when not using nginx
--bind-addr HOST:PORT Lightwalletd gRPC bind address (default: $DEFAULT_BIND_ADDR)
--http-bind-addr HOST:PORT Lightwalletd HTTP bind address (default: $DEFAULT_HTTP_BIND_ADDR)
--go-version VERSION Go toolchain version to install (default: $DEFAULT_GO_VERSION)
--node-version VERSION Node.js version for the optional explorer (default: $DEFAULT_NODE_VERSION)
--data-dir DIR Lightwalletd data directory (default: $DEFAULT_DATA_DIR)
--pirate-ref REF Pirate source tag or commit (default: $DEFAULT_PIRATE_REF)
--lightwalletd-ref REF Lightwalletd source tag or commit (default: $DEFAULT_LIGHTWALLETD_REF)
--explorer-port PORT Local Insight web port (default: $DEFAULT_EXPLORER_PORT)
--explorer-zmq-port PORT Local pirated ZMQ port for Insight (default: $DEFAULT_EXPLORER_ZMQ_PORT)
--dnsseed-host HOSTNAME Authoritative DNS seed hostname, e.g. dnsseed.example.com
--dnsseed-ns HOSTNAME Nameserver hostname, e.g. ns-dnsseed.example.com
--dnsseed-mbox RNAME SOA contact, e.g. admin.example.com (not an email address)
--dnsseed-port PORT DNS seed UDP port (default: 53)
--p2p-port PORT Pirate P2P port to expose with a DNS seed (default: $DEFAULT_P2P_PORT)
--dnsseed-ref REF pirate-seeder source tag or commit (default: $DEFAULT_DNSSEED_REF)
--dnsseed-tor-proxy HOST:PORT
Optional SOCKS5 Tor proxy for crawling onion peers
--help|-h Show this message
EOF
}
log() {
printf "==> %s\n" "$*"
}
err() {
printf "ERROR: %s\n" "$*" >&2
}
if [ "$(id -u)" -eq 0 ]; then
sudo_cmd=""
else
sudo_cmd="sudo"
fi
TARGET_USER="${SUDO_USER:-${USER:-$(whoami)}}"
if [ -z "$TARGET_USER" ]; then
err "Unable to determine the target user"
exit 1
fi
TARGET_HOME="$(eval echo "~$TARGET_USER")"
if [ -z "$TARGET_HOME" ]; then
err "Unable to determine $TARGET_USER's home directory"
exit 1
fi
HOSTNAME=""
explorer_hostname=""
EMAIL=""
enable_lets_encrypt=false
tls_cert=""
tls_key=""
go_version="$DEFAULT_GO_VERSION"
node_version="$DEFAULT_NODE_VERSION"
bind_addr="$DEFAULT_BIND_ADDR"
http_bind_addr="$DEFAULT_HTTP_BIND_ADDR"
data_dir="$DEFAULT_DATA_DIR"
pirate_ref="$DEFAULT_PIRATE_REF"
lightwalletd_ref="$DEFAULT_LIGHTWALLETD_REF"
explorer_port="$DEFAULT_EXPLORER_PORT"
explorer_zmq_port="$DEFAULT_EXPLORER_ZMQ_PORT"
dnsseed_host=""
dnsseed_ns=""
dnsseed_mbox=""
dnsseed_port="53"
p2p_port="$DEFAULT_P2P_PORT"
dnsseed_ref="$DEFAULT_DNSSEED_REF"
dnsseed_tor_proxy=""
certbot_root="/var/www/certbot"
while [[ $# -gt 0 ]]; do
case "$1" in
--hostname)
HOSTNAME="$2"
shift 2
;;
--explorer-hostname)
explorer_hostname="$2"
shift 2
;;
--email)
EMAIL="$2"
shift 2
;;
--lets-encrypt)
enable_lets_encrypt=true
shift
;;
--tls-cert)
tls_cert="$2"
shift 2
;;
--tls-key)
tls_key="$2"
shift 2
;;
--bind-addr)
bind_addr="$2"
shift 2
;;
--http-bind-addr)
http_bind_addr="$2"
shift 2
;;
--data-dir)
data_dir="$2"
shift 2
;;
--go-version)
go_version="$2"
shift 2
;;
--node-version)
node_version="$2"
shift 2
;;
--pirate-ref)
pirate_ref="$2"
shift 2
;;
--lightwalletd-ref)
lightwalletd_ref="$2"
shift 2
;;
--explorer-port)
explorer_port="$2"
shift 2
;;
--explorer-zmq-port)
explorer_zmq_port="$2"
shift 2
;;
--dnsseed-host)
dnsseed_host="$2"
shift 2
;;
--dnsseed-ns)
dnsseed_ns="$2"
shift 2
;;
--dnsseed-mbox)
dnsseed_mbox="$2"
shift 2
;;
--dnsseed-port)
dnsseed_port="$2"
shift 2
;;
--p2p-port)
p2p_port="$2"
shift 2
;;
--dnsseed-ref)
dnsseed_ref="$2"
shift 2
;;
--dnsseed-tor-proxy)
dnsseed_tor_proxy="$2"
shift 2
;;
--help|-h)
usage
exit 0
;;
*)
err "Unknown option: $1"
usage
exit 1
;;
esac
done
if [[ "$bind_addr" != *:* ]]; then
err "--bind-addr must include a port (e.g. 127.0.0.1:9067)"
exit 1
fi
if [[ "$http_bind_addr" != *:* ]]; then
err "--http-bind-addr must include a port (e.g. 127.0.0.1:9068)"
exit 1
fi
if $enable_lets_encrypt && [ -z "$HOSTNAME" ] && [ -z "$explorer_hostname" ]; then
err "--lets-encrypt requires --hostname and/or --explorer-hostname"
exit 1
fi
if $enable_lets_encrypt && [ -z "$EMAIL" ]; then
err "--lets-encrypt requires --email"
exit 1
fi
if { [ -n "$tls_cert" ] && [ -z "$tls_key" ]; } || { [ -z "$tls_cert" ] && [ -n "$tls_key" ]; }; then
err "Both --tls-cert and --tls-key must be provided together"
exit 1
fi
if [ -n "$tls_cert" ] && [ ! -f "$tls_cert" ]; then
err "TLS certificate $tls_cert does not exist"
exit 1
fi
if [ -n "$tls_key" ] && [ ! -f "$tls_key" ]; then
err "TLS key $tls_key does not exist"
exit 1
fi
if [ -z "$data_dir" ]; then
err "--data-dir cannot be empty"
exit 1
fi
if [ -z "$pirate_ref" ] || [ -z "$lightwalletd_ref" ]; then
err "--pirate-ref and --lightwalletd-ref cannot be empty"
exit 1
fi
if [ -z "$node_version" ] || [ -z "$dnsseed_ref" ]; then
err "--node-version and --dnsseed-ref cannot be empty"
exit 1
fi
is_valid_port() {
[[ "$1" =~ ^[0-9]+$ ]] && [ "$1" -ge 1 ] && [ "$1" -le 65535 ]
}
if ! is_valid_port "$explorer_port"; then
err "--explorer-port must be an integer from 1 to 65535"
exit 1
fi
if ! is_valid_port "$explorer_zmq_port"; then
err "--explorer-zmq-port must be an integer from 1 to 65535"
exit 1
fi
if ! is_valid_port "$dnsseed_port"; then
err "--dnsseed-port must be an integer from 1 to 65535"
exit 1
fi
if ! is_valid_port "$p2p_port"; then
err "--p2p-port must be an integer from 1 to 65535"
exit 1
fi
if [ -n "$explorer_hostname" ] && ! $enable_lets_encrypt; then
err "--explorer-hostname requires --lets-encrypt so nginx has a trusted certificate"
exit 1
fi
if [ -n "$HOSTNAME" ] && [ "$HOSTNAME" = "$explorer_hostname" ]; then
err "--hostname and --explorer-hostname must be distinct hostnames"
exit 1
fi
if [ -n "$dnsseed_host" ] || [ -n "$dnsseed_ns" ] || [ -n "$dnsseed_mbox" ] || [ -n "$dnsseed_tor_proxy" ]; then
if [ -z "$dnsseed_host" ] || [ -z "$dnsseed_ns" ] || [ -z "$dnsseed_mbox" ]; then
err "--dnsseed-host, --dnsseed-ns, and --dnsseed-mbox must be supplied together"
exit 1
fi
fi
ENABLE_EXPLORER=false
if [ -n "$explorer_hostname" ]; then
ENABLE_EXPLORER=true
fi
ENABLE_DNSSEED=false
if [ -n "$dnsseed_host" ]; then
ENABLE_DNSSEED=true
fi
USE_NGINX=false
if [ -n "$HOSTNAME" ] || $ENABLE_EXPLORER; then
USE_NGINX=true
fi
if [ -n "$tls_cert" ] && [ "$USE_NGINX" = "true" ]; then
log "Ignoring direct TLS settings because nginx will terminate TLS for $HOSTNAME"
tls_cert=""
tls_key=""
fi
lited_tls_args=(--no-tls-very-insecure)
if [ -n "$tls_cert" ] && [ -n "$tls_key" ]; then
lited_tls_args=(--tls-cert "$tls_cert" --tls-key "$tls_key")
fi
lited_port="${bind_addr##*:}"
pirate_dir="$TARGET_HOME/pirate"
lightwalletd_dir="$TARGET_HOME/lightwalletd"
conf_dir="$TARGET_HOME/.komodo/PIRATE"
conf_file="$conf_dir/PIRATE.conf"
log_dir="/var/log/lited"
explorer_dir="$TARGET_HOME/arrr-explorer"
explorer_config="$explorer_dir/bitcore-node.json"
explorer_node_bin=""
explorer_npm_bin=""
dnsseed_dir="$TARGET_HOME/pirate-seeder"
dnsseed_data_dir="/var/lib/pirate-seeder"
as_target_user() {
if [ "$(id -un)" = "$TARGET_USER" ]; then
"$@"
elif command -v sudo >/dev/null 2>&1; then
sudo -H -u "$TARGET_USER" "$@"
else
runuser -u "$TARGET_USER" -- "$@"
fi
}
install_base_packages() {
log "Installing build tooling and runtime dependencies"
packages=(
build-essential pkg-config libc6-dev m4 g++-multilib autoconf libtool
libncurses-dev unzip git python3 python-is-python3 zlib1g-dev wget bsdmainutils automake
libboost-all-dev libssl-dev libprotobuf-dev protobuf-compiler libqrencode-dev
libdb++-dev ntp ntpdate nano software-properties-common curl libevent-dev
libcurl4-gnutls-dev cmake clang libsodium-dev htop jq libcap2-bin
bison liblz4-dev python3-zmq zip
)
$sudo_cmd apt-get update
$sudo_cmd apt-get install -y "${packages[@]}"
}
install_optional_network() {
if [ "$USE_NGINX" = "true" ]; then
log "Installing nginx/certbot for reverse proxy configuration"
$sudo_cmd apt-get install -y nginx-full certbot python3-certbot-nginx
fi
}
install_optional_explorer_packages() {
if ! $ENABLE_EXPLORER; then
return
fi
# `zeromq` is the Node binding used by bitcore-node-pirate to consume
# pirated's raw-transaction and block notifications.
log "Installing Insight explorer build dependencies"
$sudo_cmd apt-get install -y libzmq3-dev
}
ensure_certbot_snippets() {
if [ "$USE_NGINX" != "true" ]; then
return
fi
local options_path="/etc/letsencrypt/options-ssl-nginx.conf"
local dhparam_path="/etc/letsencrypt/ssl-dhparams.pem"
if [ ! -f "$options_path" ]; then
log "Creating fallback Certbot nginx options snippet"
cat <<'EOF' | $sudo_cmd tee "$options_path" >/dev/null
ssl_session_cache shared:le_nginx_SSL:10m;
ssl_session_timeout 1440m;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers off;
ssl_ciphers 'ECDHE-ECDSA+AESGCM:ECDHE-RSA+AESGCM:ECDHE-ECDSA+CHACHA20:ECDHE-RSA+CHACHA20:ECDHE-ECDSA+AES256:ECDHE-RSA+AES256:DHE-RSA+AES256:AES256-GCM-SHA384:AES256-SHA';
ssl_ecdh_curve secp384r1;
ssl_stapling on;
ssl_stapling_verify on;
resolver 1.1.1.1 1.0.0.1 valid=300s;
resolver_timeout 10s;
EOF
fi
if [ ! -f "$dhparam_path" ]; then
log "Generating DH params for Certbot defaults (only once)"
$sudo_cmd openssl dhparam -out "$dhparam_path" 2048
fi
}
install_go() {
log "Installing Go $go_version"
case "$(uname -m)" in
x86_64|amd64)
go_arch="amd64"
;;
aarch64|arm64)
go_arch="arm64"
;;
*)
err "Unsupported CPU architecture for the official Go Linux archive: $(uname -m)"
exit 1
;;
esac
archive="go${go_version}.linux-${go_arch}.tar.gz"
url="https://go.dev/dl/${archive}"
tmpfile="/tmp/${archive}"
if command -v go >/dev/null 2>&1; then
current_version="$(go version | awk '{print $3}')"
log "Existing Go version detected: $current_version"
fi
curl -fsSL "$url" -o "$tmpfile"
$sudo_cmd rm -rf /usr/local/go
$sudo_cmd tar -C /usr/local -xzf "$tmpfile"
rm -f "$tmpfile"
cat <<'EOF' | $sudo_cmd tee /etc/profile.d/go.sh >/dev/null
export PATH=$PATH:/usr/local/go/bin
EOF
$sudo_cmd chmod 644 /etc/profile.d/go.sh
export PATH="/usr/local/go/bin:$PATH"
log "Go version: $(go version)"
}
install_explorer_node() {
if ! $ENABLE_EXPLORER; then
return
fi
log "Installing Node.js $node_version for the Insight explorer"
local nvm_dir="$TARGET_HOME/.nvm"
as_target_user env NVM_DIR="$nvm_dir" NODE_VERSION="$node_version" bash -s <<'EOF'
set -euo pipefail
if [ ! -s "$NVM_DIR/nvm.sh" ]; then
installer="$(mktemp)"
trap 'rm -f "$installer"' EXIT
curl -fsSL https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh -o "$installer"
bash "$installer"
fi
# shellcheck source=/dev/null
. "$NVM_DIR/nvm.sh"
nvm install "$NODE_VERSION"
nvm alias default "$NODE_VERSION"
EOF
explorer_node_bin="$nvm_dir/versions/node/v$node_version/bin/node"
explorer_npm_bin="$nvm_dir/versions/node/v$node_version/bin/npm"
if [ ! -x "$explorer_node_bin" ] || [ ! -x "$explorer_npm_bin" ]; then
err "Node.js $node_version was not installed under $nvm_dir"
exit 1
fi
local node_major
node_major="$("$explorer_node_bin" --version | sed -E 's/^v([0-9]+).*/\1/')"
if ! [[ "$node_major" =~ ^[0-9]+$ ]] || [ "$node_major" -lt 18 ]; then
err "Insight explorer requires Node.js 18 or newer; found $($explorer_node_bin --version)"
exit 1
fi
}
sync_repo() {
local url="$1"
local dest="$2"
local ref="$3"
if [ -d "$dest/.git" ]; then
# The installer itself leaves untracked build outputs (notably `lited`) in
# this checkout. They are safe to retain because the build command
# overwrites them. Protect real source edits instead; `git checkout` still
# refuses any untracked path that would collide with the requested ref.
if ! as_target_user git -C "$dest" diff --quiet || \
! as_target_user git -C "$dest" diff --cached --quiet; then
err "Refusing to overwrite tracked changes in $dest; commit, stash, or revert them first"
exit 1
fi
log "Fetching $url into $dest"
as_target_user git -C "$dest" fetch --tags --prune origin
else
log "Cloning $url into $dest"
as_target_user git clone --no-checkout "$url" "$dest"
fi
as_target_user git -C "$dest" checkout --detach "$ref"
as_target_user git -C "$dest" submodule update --init --recursive
log "Using $(as_target_user git -C "$dest" rev-parse --short=12 HEAD) in $dest"
}
build_pirate() {
log "Building Pirate Chain daemon"
as_target_user env PIRATE_DIR="$pirate_dir" bash -s <<'EOF'
set -e
cd "$PIRATE_DIR"
# Pirate 6.0.0 fetches trusted parameters from the signed GitHub release and
# verifies their SHA-256 hashes. Do not weaken transport security with the
# legacy bootstrap.arrr.black HTTP fallback.
./zcutil/fetch-params.sh
./zcutil/build.sh -j$(nproc)
EOF
}
build_lightwalletd() {
log "Building ARRRwallet lightwalletd with Ironwood support"
export PATH="/usr/local/go/bin:$PATH"
as_target_user bash -lc "
set -e
cd \"$lightwalletd_dir\"
GO111MODULE=on /usr/local/go/bin/go build -buildvcs=false -mod=readonly -o lited .
"
}
link_binaries() {
log "Linking pirate binaries system-wide"
$sudo_cmd ln -sf "$pirate_dir/src/pirate-cli" /usr/local/bin/pirate-cli
$sudo_cmd ln -sf "$pirate_dir/src/pirated" /usr/local/bin/pirated
log "Linking lightwalletd binary"
$sudo_cmd ln -sf "$lightwalletd_dir/lited" /usr/local/bin/lited
}
create_pirate_conf() {
if [ -f "$conf_file" ]; then
log "PIRATE.conf already exists, skipping regeneration"
return
fi
log "Creating Pirate Chain configuration for lightwalletd"
mkdir -p "$conf_dir"
rpc_user="user$(openssl rand -hex 16)"
rpc_pass="pass$(openssl rand -hex 16)"
cat <<EOF > "$conf_file"
rpcuser=$rpc_user
rpcpassword=$rpc_pass
server=1
rpcbind=127.0.0.1
rpcport=45453
txindex=1
addressindex=1
timestampindex=1
spentindex=1
insightexplorer=1
experimentalfeatures=1
rpcallowip=127.0.0.1
EOF
chmod 600 "$conf_file"
chown -R "$TARGET_USER:$TARGET_USER" "$conf_dir"
}
check_pirate_v6_configuration() {
if [ ! -f "$conf_file" ]; then
return
fi
# These options were removed in Pirate 6.0.0. Leaving any of them in an
# existing operator-managed PIRATE.conf prevents pirated from starting, so
# report the migration before replacing the live daemon binary.
local legacy_options
legacy_options="$(awk -F= '
/^[[:space:]]*[#;]/ { next }
/^[[:space:]]*(consolidation|consolidationtxfee|consolidateaddress|sweepsaplingaddress|sweepironwoodaddress)[[:space:]]*=/ {
key=$1
sub(/^[[:space:]]*/, "", key)
sub(/[[:space:]]*$/, "", key)
print key
}
' "$conf_file")"
if [ -n "$legacy_options" ]; then
err "PIRATE.conf contains removed Pirate 6.0.0 option(s): ${legacy_options//$'\n'/, }"
err "Replace consolidation* with saplingconsolidation* or ironwoodconsolidation*,"
err "consolidateaddress with consolidate{sapling,ironwood}address, and"
err "sweepsaplingaddress/sweepironwoodaddress with sweepaddress; then rerun."
exit 1
fi
}
pirate_conf_value() {
local key="$1"
awk -v wanted="$key" '
/^[[:space:]]*[#;]/ { next }
{
equals = index($0, "=")
if (!equals) next
option = substr($0, 1, equals - 1)
value = substr($0, equals + 1)
gsub(/^[[:space:]]+|[[:space:]]+$/, "", option)
gsub(/^[[:space:]]+|[[:space:]]+$/, "", value)
if (option == wanted) result = value
}
END { print result }
' "$conf_file"
}
append_pirate_conf_option() {
local key="$1"
local value="$2"
printf '\n%s=%s\n' "$key" "$value" | $sudo_cmd tee -a "$conf_file" >/dev/null
$sudo_cmd chmod 600 "$conf_file"
$sudo_cmd chown "$TARGET_USER:$TARGET_USER" "$conf_file"
}
is_loopback_zmq_endpoint() {
[[ "$1" =~ ^tcp://(127\.0\.0\.1|localhost|\[::1\]):[0-9]+$ ]]
}
configure_explorer_pirate_conf() {
if ! $ENABLE_EXPLORER; then
return
fi
local rawtx_endpoint hashblock_endpoint default_endpoint endpoint_port
default_endpoint="tcp://127.0.0.1:$explorer_zmq_port"
rawtx_endpoint="$(pirate_conf_value zmqpubrawtx)"
hashblock_endpoint="$(pirate_conf_value zmqpubhashblock)"
if [ -z "$rawtx_endpoint" ] && [ -z "$hashblock_endpoint" ]; then
log "Enabling local ZMQ notifications for the Insight explorer"
append_pirate_conf_option zmqpubrawtx "$default_endpoint"
append_pirate_conf_option zmqpubhashblock "$default_endpoint"
rawtx_endpoint="$default_endpoint"
elif [ -z "$rawtx_endpoint" ]; then
log "Adding missing zmqpubrawtx setting for the Insight explorer"
append_pirate_conf_option zmqpubrawtx "$hashblock_endpoint"
rawtx_endpoint="$hashblock_endpoint"
elif [ -z "$hashblock_endpoint" ]; then
log "Adding missing zmqpubhashblock setting for the Insight explorer"
append_pirate_conf_option zmqpubhashblock "$rawtx_endpoint"
hashblock_endpoint="$rawtx_endpoint"
fi
if [ "$rawtx_endpoint" != "$hashblock_endpoint" ]; then
err "Insight requires zmqpubrawtx and zmqpubhashblock to use the same endpoint in $conf_file"
exit 1
fi
if ! is_loopback_zmq_endpoint "$rawtx_endpoint"; then
err "Insight requires its ZMQ endpoint to be loopback-only; found $rawtx_endpoint in $conf_file"
exit 1
fi
endpoint_port="${rawtx_endpoint##*:}"
if ! is_valid_port "$endpoint_port"; then
err "Invalid ZMQ port in $rawtx_endpoint"
exit 1
fi
}
install_and_configure_explorer() {
if ! $ENABLE_EXPLORER; then
return
fi
local rpc_user rpc_pass rpc_port rawtx_endpoint
rpc_user="$(pirate_conf_value rpcuser)"
rpc_pass="$(pirate_conf_value rpcpassword)"
rpc_port="$(pirate_conf_value rpcport)"
rawtx_endpoint="$(pirate_conf_value zmqpubrawtx)"
if [ -z "$rpc_user" ] || [ -z "$rpc_pass" ] || [ -z "$rawtx_endpoint" ]; then
err "Insight requires rpcuser, rpcpassword, and zmqpubrawtx in $conf_file"
exit 1
fi
if [ -z "$rpc_port" ]; then
rpc_port="45453"
fi
if ! is_valid_port "$rpc_port"; then
err "Invalid rpcport in $conf_file: $rpc_port"
exit 1
fi
log "Installing pinned Pirate Insight explorer packages"
as_target_user mkdir -p "$explorer_dir"
as_target_user "$explorer_npm_bin" --prefix "$explorer_dir" install \
--omit=dev --no-audit --no-fund \
"git+https://github.com/PirateNetwork/bitcore-node-pirate.git#$DEFAULT_BITCORE_NODE_REF" \
"git+https://github.com/PirateNetwork/insight-api-pirate.git#$DEFAULT_INSIGHT_API_REF" \
"git+https://github.com/PirateNetwork/insight-ui-pirate.git#$DEFAULT_INSIGHT_UI_REF"
log "Writing Insight explorer configuration for the existing pirated service"
jq -n \
--arg rpc_user "$rpc_user" \
--arg rpc_pass "$rpc_pass" \
--arg rpc_port "$rpc_port" \
--arg zmq_endpoint "$rawtx_endpoint" \
--argjson web_port "$explorer_port" \
'{
network: "livenet",
port: $web_port,
services: ["bitcoind", "web", "insight-api-pirate", "insight-ui-pirate"],
servicesConfig: {
bitcoind: {
connect: [{
rpchost: "127.0.0.1",
rpcport: ($rpc_port | tonumber),
rpcuser: $rpc_user,
rpcpassword: $rpc_pass,
zmqpubrawtx: $zmq_endpoint
}]
},
"insight-api-pirate": {routePrefix: "insight-api-pirate"},
"insight-ui-pirate": {apiPrefix: "insight-api-pirate", routePrefix: ""}
}
}' | $sudo_cmd tee "$explorer_config" >/dev/null
$sudo_cmd chown "$TARGET_USER:$TARGET_USER" "$explorer_config"
$sudo_cmd chmod 600 "$explorer_config"
}
build_dnsseeder() {
if ! $ENABLE_DNSSEED; then
return
fi
sync_repo https://github.com/PirateNetwork/pirate-seeder "$dnsseed_dir" "$dnsseed_ref"
log "Building pirate-seeder"
as_target_user env DNSSEED_DIR="$dnsseed_dir" bash -s <<'EOF'
set -e
cd "$DNSSEED_DIR"
make -j"$(nproc)"
EOF
$sudo_cmd ln -sf "$dnsseed_dir/pirate-seeder" /usr/local/bin/pirate-seeder
$sudo_cmd mkdir -p "$dnsseed_data_dir"
$sudo_cmd chown "$TARGET_USER:$TARGET_USER" "$dnsseed_data_dir"
}
configure_optional_firewall() {
if ! command -v ufw >/dev/null 2>&1 || ! $sudo_cmd ufw status | grep -q "Status: active"; then
if $ENABLE_EXPLORER; then
log "UFW is not active; ensure your cloud/network firewall blocks TCP/$explorer_port so Insight is reachable only through nginx"
fi
return
fi
if [ "$USE_NGINX" = "true" ]; then
$sudo_cmd ufw allow 80/tcp >/dev/null
$sudo_cmd ufw allow 443/tcp >/dev/null
fi
if $ENABLE_EXPLORER; then
# bitcore-node-pirate listens on all interfaces, so nginx alone cannot
# prevent a direct HTTP listener from being reachable.
$sudo_cmd ufw deny "$explorer_port/tcp" >/dev/null
fi
if $ENABLE_DNSSEED; then
$sudo_cmd ufw allow "$dnsseed_port/udp" >/dev/null
$sudo_cmd ufw allow "$p2p_port/tcp" >/dev/null
fi
}
configure_nginx_minimal() {
if [ "$USE_NGINX" != "true" ]; then
return
fi
local server_names=""
if [ -n "$HOSTNAME" ]; then
server_names="$HOSTNAME"
fi
if $ENABLE_EXPLORER; then
server_names="${server_names:+$server_names }$explorer_hostname"
fi
log "Temporarily configuring nginx for Certbot challenge handling"
$sudo_cmd mkdir -p "$certbot_root"
$sudo_cmd chown -R "$TARGET_USER:$TARGET_USER" "$certbot_root"
cat <<EOF | $sudo_cmd tee /etc/nginx/sites-available/arrr-lightwalletd.conf >/dev/null
server {
listen 80;
listen [::]:80;
server_name $server_names;
location ^~ /.well-known/acme-challenge/ {
root $certbot_root;
default_type "text/plain";
try_files \$uri =404;
}
location / {
return 301 https://\$host\$request_uri;
}
}
EOF
$sudo_cmd ln -sf /etc/nginx/sites-available/arrr-lightwalletd.conf /etc/nginx/sites-enabled/arrr-lightwalletd.conf
$sudo_cmd rm -f /etc/nginx/sites-enabled/default
$sudo_cmd nginx -t
$sudo_cmd systemctl reload nginx
}
configure_nginx_final() {
if [ "$USE_NGINX" != "true" ]; then
return
fi
local server_names=""
local lited_tls_block=""
local explorer_tls_block=""
if [ -n "$HOSTNAME" ]; then
server_names="$HOSTNAME"
fi
if $ENABLE_EXPLORER; then
server_names="${server_names:+$server_names }$explorer_hostname"
fi
log "Configuring nginx reverse proxy for $server_names"
$sudo_cmd mkdir -p "$certbot_root"
$sudo_cmd chown -R "$TARGET_USER:$TARGET_USER" "$certbot_root"
ensure_certbot_snippets
if [ -n "$HOSTNAME" ]; then
lited_tls_block="$(cat <<EOF
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name $HOSTNAME;
ssl_certificate /etc/letsencrypt/live/$HOSTNAME/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/$HOSTNAME/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
grpc_connect_timeout 10s;
grpc_read_timeout 600s;
grpc_send_timeout 600s;
# --- Compatibility shim: old client -> new server
# Rewrite ONLY the gRPC :path (URI), leaving method name intact.
# OLD clients (cash.*) -> NEW server (pirate.*)
location ~ ^/cash\.z\.wallet\.sdk\.rpc\.CompactTxStreamer/(.*)$ {
rewrite ^/cash\.z\.wallet\.sdk\.rpc\.CompactTxStreamer/(.*)$ /pirate.wallet.sdk.rpc.CompactTxStreamer/\$1 break;
grpc_pass grpc://127.0.0.1:$lited_port;
grpc_set_header TE trailers;
grpc_set_header X-Real-IP \$remote_addr;
grpc_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
grpc_set_header X-Forwarded-Proto \$scheme;
}
# Native pirate namespace
location ^~ /pirate.wallet.sdk.rpc.CompactTxStreamer/ {
grpc_pass grpc://127.0.0.1:$lited_port;
grpc_set_header TE trailers;
grpc_set_header X-Real-IP \$remote_addr;
grpc_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
grpc_set_header X-Forwarded-Proto \$scheme;
}
# Catch-all: anything else passes through unchanged (reflection, health, etc.)
location / {
grpc_pass grpc://127.0.0.1:$lited_port;
grpc_set_header X-Real-IP \$remote_addr;
grpc_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
grpc_set_header X-Forwarded-Proto \$scheme;
grpc_set_header TE trailers;
}
location ^~ /.well-known/acme-challenge/ {
root $certbot_root;
default_type "text/plain";
try_files \$uri =404;
}
}
EOF
)"
fi
if $ENABLE_EXPLORER; then
explorer_tls_block="$(cat <<EOF
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name $explorer_hostname;
ssl_certificate /etc/letsencrypt/live/$explorer_hostname/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/$explorer_hostname/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
location / {
proxy_pass http://127.0.0.1:$explorer_port;
proxy_http_version 1.1;
proxy_set_header Host \$host;
proxy_set_header X-Real-IP \$remote_addr;
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto \$scheme;
proxy_set_header Upgrade \$http_upgrade;
proxy_set_header Connection "upgrade";
}
location ^~ /.well-known/acme-challenge/ {
root $certbot_root;
default_type "text/plain";
try_files \$uri =404;
}
}
EOF
)"
fi
cat <<EOF | $sudo_cmd tee /etc/nginx/sites-available/arrr-lightwalletd.conf >/dev/null
server {
listen 80;
listen [::]:80;
server_name $server_names;
location ^~ /.well-known/acme-challenge/ {
root $certbot_root;
default_type "text/plain";
try_files \$uri =404;
}
location / {
return 301 https://\$host\$request_uri;
}
}
$lited_tls_block
$explorer_tls_block
EOF
$sudo_cmd ln -sf /etc/nginx/sites-available/arrr-lightwalletd.conf /etc/nginx/sites-enabled/arrr-lightwalletd.conf
$sudo_cmd rm -f /etc/nginx/sites-enabled/default
$sudo_cmd nginx -t
$sudo_cmd systemctl reload nginx
}
obtain_letsencrypt_certificate() {
if ! $enable_lets_encrypt; then
return
fi
local certificate_hostname
local certificate_hostnames=()
if [ -n "$HOSTNAME" ]; then
certificate_hostnames+=("$HOSTNAME")
fi
if $ENABLE_EXPLORER; then
certificate_hostnames+=("$explorer_hostname")
fi
for certificate_hostname in "${certificate_hostnames[@]}"; do
if [ -f "/etc/letsencrypt/live/$certificate_hostname/fullchain.pem" ]; then
log "Let's Encrypt certificate already exists for $certificate_hostname"
continue
fi
log "Requesting Let's Encrypt certificate for $certificate_hostname"
$sudo_cmd certbot certonly --webroot --webroot-path "$certbot_root" \
--non-interactive --agree-tos --email "$EMAIL" -d "$certificate_hostname"
done
$sudo_cmd nginx -t
$sudo_cmd systemctl reload nginx
}
create_systemd_service() {
log "Writing systemd unit for pirated"
cat <<EOF | $sudo_cmd tee /etc/systemd/system/pirated.service >/dev/null
[Unit]
Description=Pirate Chain daemon
After=network.target
[Service]
User=$TARGET_USER
Group=$TARGET_USER
Environment=HOME=$TARGET_HOME
WorkingDirectory=$pirate_dir
ExecStart=/usr/local/bin/pirated
Restart=on-failure
RestartSec=5
LimitNOFILE=8192
[Install]
WantedBy=multi-user.target
EOF
if $ENABLE_EXPLORER; then
log "Writing systemd unit for the Insight explorer"
cat <<EOF | $sudo_cmd tee /etc/systemd/system/arrr-explorer.service >/dev/null
[Unit]
Description=Pirate Chain Insight explorer
After=pirated.service network-online.target
Requires=pirated.service
[Service]
User=$TARGET_USER
Group=$TARGET_USER
Environment=HOME=$TARGET_HOME
Environment=NODE_ENV=production
WorkingDirectory=$explorer_dir
ExecStart=$explorer_node_bin $explorer_dir/node_modules/bitcore-node-pirate/bin/bitcore-node start --config "$explorer_dir"
Restart=on-failure
RestartSec=10
LimitNOFILE=8192
[Install]
WantedBy=multi-user.target
EOF
fi
if $ENABLE_DNSSEED; then
local dnsseed_exec_args=""
local dnsseed_capabilities=""
local dnsseed_args=(
-h "$dnsseed_host"
-n "$dnsseed_ns"
-m "$dnsseed_mbox"
-p "$dnsseed_port"
--db "$dnsseed_data_dir/dnsseed.dat"
--pirate-conf "$conf_file"
)
if [ -n "$dnsseed_tor_proxy" ]; then
dnsseed_args+=(-o "$dnsseed_tor_proxy")
fi
printf -v dnsseed_exec_args ' %q' "${dnsseed_args[@]}"
if [ "$dnsseed_port" -lt 1024 ]; then
dnsseed_capabilities=$'CapabilityBoundingSet=CAP_NET_BIND_SERVICE\nAmbientCapabilities=CAP_NET_BIND_SERVICE'
fi
log "Writing systemd unit for pirate-seeder"
cat <<EOF | $sudo_cmd tee /etc/systemd/system/pirate-seeder.service >/dev/null
[Unit]
Description=Pirate Chain DNS seeder
After=network-online.target
Wants=network-online.target
[Service]
User=$TARGET_USER
Group=$TARGET_USER
Environment=HOME=$TARGET_HOME
WorkingDirectory=$dnsseed_dir
ExecStart=/usr/local/bin/pirate-seeder$dnsseed_exec_args
Restart=on-failure
RestartSec=10
LimitNOFILE=8192
$dnsseed_capabilities
[Install]
WantedBy=multi-user.target
EOF
fi
log "Writing systemd unit for lightwalletd"
$sudo_cmd mkdir -p "$log_dir"
$sudo_cmd chown "$TARGET_USER:$TARGET_USER" "$log_dir"
$sudo_cmd mkdir -p "$data_dir"
$sudo_cmd chown "$TARGET_USER:$TARGET_USER" "$data_dir"
printf -v lited_tls_flag_string ' %q' "${lited_tls_args[@]}"
cat <<EOF | $sudo_cmd tee /etc/systemd/system/lited.service >/dev/null
[Unit]
Description=ARRRwallet lightwalletd
After=pirated.service network.target
Requires=pirated.service
[Service]
User=$TARGET_USER
Group=$TARGET_USER
Environment=HOME=$TARGET_HOME
WorkingDirectory=$lightwalletd_dir
ExecStart=/usr/local/bin/lited --grpc-bind-addr "$bind_addr" --http-bind-addr "$http_bind_addr"${lited_tls_flag_string} --data-dir "$data_dir" --pirate-conf-path "$conf_file" --log-file "$log_dir/lited.log"
Restart=on-failure
RestartSec=5
LimitNOFILE=8192
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
AmbientCapabilities=CAP_NET_BIND_SERVICE
[Install]
WantedBy=multi-user.target
EOF
$sudo_cmd systemctl daemon-reload
$sudo_cmd systemctl enable pirated.service
$sudo_cmd systemctl enable lited.service
if $ENABLE_EXPLORER; then
$sudo_cmd systemctl enable arrr-explorer.service
fi
if $ENABLE_DNSSEED; then
$sudo_cmd systemctl enable pirate-seeder.service
fi
# `enable --now` does not restart an already-active service, which meant a
# previous version of this script could rebuild and relink binaries without
# actually applying an upgrade. Stop the dependent service first, then
# restart the daemon. Pirate 6.0.0 detects its raised minimum index version
# and automatically performs the required full reindex.
$sudo_cmd systemctl stop lited.service || true
if $ENABLE_EXPLORER; then
$sudo_cmd systemctl stop arrr-explorer.service || true
fi
$sudo_cmd systemctl restart pirated.service
$sudo_cmd systemctl restart lited.service
if $ENABLE_EXPLORER; then
$sudo_cmd systemctl restart arrr-explorer.service
fi
if $ENABLE_DNSSEED; then
$sudo_cmd systemctl restart pirate-seeder.service
fi
}
main() {
install_base_packages
install_optional_network
install_optional_explorer_packages
configure_optional_firewall
install_go
install_explorer_node
sync_repo https://github.com/PirateNetwork/pirate "$pirate_dir" "$pirate_ref"
sync_repo https://github.com/PirateNetwork/lightwalletd "$lightwalletd_dir" "$lightwalletd_ref"
build_dnsseeder
build_pirate
link_binaries
create_pirate_conf
check_pirate_v6_configuration
configure_explorer_pirate_conf
install_and_configure_explorer
if [ "$USE_NGINX" = "true" ] && $enable_lets_encrypt; then
configure_nginx_minimal
fi
if $enable_lets_encrypt; then
obtain_letsencrypt_certificate
fi
if [ "$USE_NGINX" = "true" ]; then
configure_nginx_final
fi
build_lightwalletd
link_binaries
create_systemd_service
log "Setup completed. Pirate 6.0.0 will reindex existing chain data before it is fully synced."
if [ "$USE_NGINX" = "true" ]; then
if [ -n "$HOSTNAME" ]; then
log "nginx is proxying lightwalletd at $HOSTNAME. After DNS propagates, confirm certificates with 'sudo certbot certificates'."
fi
if $ENABLE_EXPLORER; then
log "Insight explorer is available at https://$explorer_hostname (service: arrr-explorer)."
fi
else
log "Lightwalletd gRPC is listening on $bind_addr and HTTP on $http_bind_addr; point clients at the matching endpoint."
fi
if $ENABLE_DNSSEED; then
log "DNS seeder is serving $dnsseed_host on UDP/$dnsseed_port (service: pirate-seeder)."
fi
}
main "$@"