#!/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 < %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 < "$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 </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 </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 </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 </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 "$@"