dgb-wallet/ops/deploy-wallet-web.sh
Digibyte.X d50286a9a5 DigiByte.X Wallet — recovery-first web wallet monorepo
Packages: core (BIP39/32/44/49/84/86 derivation, WIF), keystore (PBKDF2 +
AES-GCM), psbt, p2p (BIP157/158 SPV daemon, Dandelion++ broadcast with
relay-echo verification), blockbook, rpc, electrum, net, bns, web-ui
(React/Vite), mobile (Expo). ops/ holds the nginx vhost, systemd unit and
push.sh deploy script for digibyte.x/web.
2026-09-20 20:16:52 +02:00

206 lines
8 KiB
Bash

#!/usr/bin/env bash
# Bootstrap the DigiByte.X Wallet SUBPATH deployment — the web bundle
# served at `https://<host>/web/` alongside whatever else that host
# already serves at `/`. Companion to Caddyfile.wallet-web-snippet.
#
# Difference from deploy-wallet.sh:
# deploy-wallet.sh — wallet OWNS the whole site (Caddyfile.wallet at :443)
# deploy-wallet-web — wallet is ONE section of an existing site
# (Caddyfile.wallet-web-snippet imported into the
# host's existing Caddyfile)
#
# Usage:
# scp -r . user@vps:/opt/dgb-wallet
# ssh user@vps
# sudo /opt/dgb-wallet/ops/deploy-wallet-web.sh
# (optional) --merge /etc/caddy/Caddyfile
# (optional) --site-root /var/www/dgb-wallet
#
# Steps:
# 1. install caddy + node20 (if missing)
# 2. install monorepo deps, build web-ui (plain production build →
# relative ./assets base + runtime <base> fix in index.html)
# 3. create dgb-wallet system user, sync bundle to <SITE_ROOT>/web/
# 4. install dgb-daemon.service, enable + start
# 5. render snippet with SITE_ROOT into /etc/caddy/snippets/dgb-wallet.caddyfile
# 6. (--merge) insert `import` line into the given Caddyfile between
# sentinel markers; else print manual-merge instructions
# 7. caddy validate, reload
# 8. print status
set -euo pipefail
if [[ $EUID -ne 0 ]]; then
echo "Run as root (sudo)." >&2
exit 1
fi
REPO_DIR="${REPO_DIR:-/opt/dgb-wallet}"
SITE_ROOT="/var/www/dgb-wallet"
DAEMON_HOME="/var/lib/dgb-wallet"
SNIPPET_DIR="/etc/caddy/snippets"
SNIPPET_FILE="$SNIPPET_DIR/dgb-wallet.caddyfile"
MERGE_TARGET=""
OPS_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
while [[ $# -gt 0 ]]; do
case "$1" in
--merge) MERGE_TARGET="${2:-}"; shift 2 ;;
--site-root) SITE_ROOT="${2:-}"; shift 2 ;;
-h|--help)
sed -n '2,25p' "$0"; exit 0 ;;
*) echo "Unknown flag: $1" >&2; exit 1 ;;
esac
done
log() { echo -e "\n\033[1;36m>>> $*\033[0m"; }
log "Step 1/7: install prerequisites (caddy, node 20)"
apt-get update -qq
apt-get install -y -qq curl gpg ca-certificates rsync debian-keyring debian-archive-keyring apt-transport-https
if ! command -v caddy >/dev/null; then
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | tee /etc/apt/sources.list.d/caddy-stable.list >/dev/null
apt-get update -qq
apt-get install -y -qq caddy
fi
if ! command -v node >/dev/null || ! node -v | grep -qE '^v(20|22|24)\.'; then
curl -fsSL https://deb.nodesource.com/setup_20.x | bash -
apt-get install -y -qq nodejs
fi
log "Step 2/7: install deps and build web-ui bundle (relative ./ base)"
cd "$REPO_DIR"
npm install --silent
# Plain production build. vite.config.ts defaults production to a
# RELATIVE base ('./'), and index.html carries an inline script that
# re-anchors a <base href> at <origin>…/web/ at runtime. That single
# bundle therefore works both at https://<host>/web/ and behind the
# navigate.st BNS gateway (which injects its own <base>). Do NOT set
# DGB_UI_BASE=/web/ here — an absolute base breaks the gateway path.
if [[ -n "${DGB_UI_BASE:-}" ]]; then
echo "ERROR: DGB_UI_BASE is set ('$DGB_UI_BASE'). Unset it — the subpath deploy relies on the relative './' base." >&2
exit 1
fi
npm run build -w @dgb-wallet/web-ui
# Sanity check — asset URLs are relative (./assets/*), not absolute.
if ! grep -q 'src="\./assets/' "$REPO_DIR/packages/web-ui/dist/index.html"; then
echo "ERROR: dist/index.html has no ./assets/ references — build did not produce a relative base" >&2
exit 1
fi
if ! grep -q 'indexOf(./web/.)' "$REPO_DIR/packages/web-ui/dist/index.html"; then
echo "ERROR: dist/index.html is missing the runtime <base> fix script — /web/ subpath serving will break" >&2
exit 1
fi
mkdir -p "$SITE_ROOT/web"
rsync -a --delete "$REPO_DIR/packages/web-ui/dist/" "$SITE_ROOT/web/"
log "Step 3/7: create dgb-wallet user"
if ! id -u dgb-wallet >/dev/null 2>&1; then
useradd --system --home "$DAEMON_HOME" --shell /usr/sbin/nologin dgb-wallet
fi
mkdir -p "$DAEMON_HOME"
chown -R dgb-wallet:dgb-wallet "$DAEMON_HOME" "$REPO_DIR"
log "Step 4/7: install dgb-daemon.service"
install -m 0644 "$OPS_DIR/dgb-daemon.service" /etc/systemd/system/dgb-daemon.service
systemctl daemon-reload
systemctl enable --now dgb-daemon.service
sleep 2
systemctl --no-pager --lines=5 status dgb-daemon.service || true
log "Step 5/7: render Caddy snippet → $SNIPPET_FILE"
mkdir -p "$SNIPPET_DIR"
sed -e "s|@SITE_ROOT@|$SITE_ROOT|g" \
"$OPS_DIR/Caddyfile.wallet-web-snippet" > "$SNIPPET_FILE"
log "Step 6/7: wire snippet into the host Caddyfile"
IMPORT_LINE=" import $SNIPPET_FILE"
START_MARK="# --- START DGB WALLET IMPORT ---"
END_MARK="# --- END DGB WALLET IMPORT ---"
if [[ -n "$MERGE_TARGET" ]]; then
if [[ ! -f "$MERGE_TARGET" ]]; then
echo "ERROR: --merge target does not exist: $MERGE_TARGET" >&2
exit 1
fi
# Idempotent: if the sentinel block already exists, replace between
# markers; otherwise insert before the LAST `}` in the file (assumed
# to close the site block).
if grep -qF "$START_MARK" "$MERGE_TARGET"; then
awk -v s="$START_MARK" -v e="$END_MARK" -v line="$IMPORT_LINE" '
$0 ~ s { print; print line; skip=1; next }
$0 ~ e { skip=0 }
!skip { print }
' "$MERGE_TARGET" > "$MERGE_TARGET.tmp"
mv "$MERGE_TARGET.tmp" "$MERGE_TARGET"
echo " updated existing import block in $MERGE_TARGET"
else
# Insert marker block before the last `}` on its own line.
LAST_BRACE_LINE=$(grep -n '^\s*}\s*$' "$MERGE_TARGET" | tail -n1 | cut -d: -f1)
if [[ -z "$LAST_BRACE_LINE" ]]; then
echo "ERROR: could not locate a site-block close brace in $MERGE_TARGET." >&2
echo " Add these three lines inside your :443 block by hand:" >&2
echo " $START_MARK" >&2
echo " $IMPORT_LINE" >&2
echo " $END_MARK" >&2
exit 1
fi
awk -v n="$LAST_BRACE_LINE" -v s="$START_MARK" -v line="$IMPORT_LINE" -v e="$END_MARK" '
NR == n { print " " s; print line; print " " e }
{ print }
' "$MERGE_TARGET" > "$MERGE_TARGET.tmp"
mv "$MERGE_TARGET.tmp" "$MERGE_TARGET"
echo " inserted import block into $MERGE_TARGET before line $LAST_BRACE_LINE"
fi
caddy validate --config "$MERGE_TARGET"
systemctl reload caddy || systemctl restart caddy
else
cat <<EOF
No --merge target given. Snippet rendered to:
$SNIPPET_FILE
Add this single line INSIDE your existing :443 { … } site block in
the host Caddyfile (e.g. /etc/caddy/Caddyfile):
$START_MARK
$IMPORT_LINE
$END_MARK
Then: sudo caddy validate --config /etc/caddy/Caddyfile
sudo systemctl reload caddy
Re-run this script with --merge <path> to have it done automatically.
EOF
fi
log "Step 7/7: DONE."
VPS_IP="$(curl -s -4 https://ifconfig.me || echo '<unknown>')"
cat <<EOF
===================================================================
DigiByte.X Wallet — subpath deploy complete
===================================================================
Site root: $SITE_ROOT/web/
Snippet: $SNIPPET_FILE
Daemon: dgb-daemon.service (bound 127.0.0.1:5174)
VPS IPv4: $VPS_IP
Wallet endpoints (once the host Caddyfile imports the snippet):
https://<host>/web/ static bundle
https://<host>/p2p/health daemon health (reverse-proxied)
https://<host>/p2p/balance BIP158 scan
https://<host>/p2p/broadcast Dandelion++ broadcast
To re-deploy after a code change, from the DEV BOX:
ops/push.sh # web bundle + daemon sources, restart daemon
ops/push.sh web # bundle only
ops/push.sh daemon # daemon only
(builds locally, ships over ssh, swaps atomically, verifies the served hash)
Tail logs:
journalctl -u dgb-daemon -u caddy -f
EOF