545 lines
15 KiB
Bash
Executable File
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 "$@"
|