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

545 lines
15 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_BIND_ADDR="127.0.0.1:9067"
DEFAULT_HTTP_BIND_ADDR="127.0.0.1:9068"
DEFAULT_DATA_DIR="/var/lib/lightwalletd"
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)
--email EMAIL Email address to register with certbot when --lets-encrypt is used
--lets-encrypt Run certbot against the configured hostname (requires --hostname and --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)
--data-dir DIR Lightwalletd data directory (default: $DEFAULT_DATA_DIR)
--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=""
EMAIL=""
enable_lets_encrypt=false
tls_cert=""
tls_key=""
go_version="$DEFAULT_GO_VERSION"
bind_addr="$DEFAULT_BIND_ADDR"
http_bind_addr="$DEFAULT_HTTP_BIND_ADDR"
data_dir="$DEFAULT_DATA_DIR"
certbot_root="/var/www/certbot"
while [[ $# -gt 0 ]]; do
case "$1" in
--hostname)
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
;;
--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" ]; then
err "--lets-encrypt requires --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
USE_NGINX=false
if [ -n "$HOSTNAME" ]; 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"
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
)
$sudo_cmd apt-get update
$sudo_cmd apt-get upgrade -y
$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
}
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"
archive="go${go_version}.linux-amd64.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)"
}
clone_or_update_repo() {
local url="$1"
local dest="$2"
if [ -d "$dest/.git" ]; then
log "Updating repository in $dest"
sudo -u "$TARGET_USER" git -C "$dest" pull --ff-only
else
log "Cloning $url into $dest"
sudo -u "$TARGET_USER" git clone "$url" "$dest"
fi
}
build_pirate() {
log "Building Pirate Chain daemon"
sudo -u "$TARGET_USER" bash -lc "
set -e
cd \"$pirate_dir\"
./zcutil/fetch-params.sh
./zcutil/build.sh -j\$(nproc)
"
}
build_lightwalletd() {
log "Building ARRRwallet lightwalletd"
export PATH="/usr/local/go/bin:$PATH"
sudo -u "$TARGET_USER" bash -lc "
set -e
cd \"$lightwalletd_dir\"
GO111MODULE=on /usr/local/go/bin/go build -mod=mod -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"
}
configure_nginx_minimal() {
if [ "$USE_NGINX" != "true" ]; then
return
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 $HOSTNAME;
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
log "Configuring nginx reverse proxy for $HOSTNAME"
$sudo_cmd mkdir -p "$certbot_root"
$sudo_cmd chown -R "$TARGET_USER:$TARGET_USER" "$certbot_root"
ensure_certbot_snippets
cat <<EOF | $sudo_cmd tee /etc/nginx/sites-available/arrr-lightwalletd.conf >/dev/null
server {
if (\$host = $HOSTNAME) {
return 301 https://\$host\$request_uri;
} # managed by Certbot
listen 80;
listen [::]:80;
server_name $HOSTNAME;
location ^~ /.well-known/acme-challenge/ {
root $certbot_root;
default_type "text/plain";
try_files \$uri =404;
}
location / {
return 301 https://\$host\$request_uri;
}
}
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;
# Optional but nice
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
$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
log "Requesting Let's Encrypt certificate for $HOSTNAME"
$sudo_cmd certbot certonly --webroot --webroot-path "$certbot_root" \
--non-interactive --agree-tos --email "$EMAIL" -d "$HOSTNAME"
$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
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 --now pirated.service
$sudo_cmd systemctl enable --now lited.service
}
main() {
install_base_packages
install_optional_network
install_go
clone_or_update_repo https://github.com/PirateNetwork/pirate "$pirate_dir"
clone_or_update_repo https://github.com/PirateNetwork/lightwalletd "$lightwalletd_dir"
build_pirate
link_binaries
create_pirate_conf
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. Make sure $TARGET_USER runs 'pirated' until the blockchain syncs."
if [ "$USE_NGINX" = "true" ]; then
log "nginx is proxying $HOSTNAME. After DNS propagates, confirm certificate with 'sudo certbot certificates' if you ran --lets-encrypt."
else
log "Lightwalletd gRPC is listening on $bind_addr and HTTP on $http_bind_addr; point clients at the matching endpoint."
fi
}
main "$@"