Files
wireguard-ui/src/scripts/install-wg-ui.sh
T
tben ad94385bcb Improve wg-ui host setup: IP availability checks and current-user install script
- setup-main-host-button: live availability status, grab-next-subnet helper, error/success feedback
- setup-wireguard-host: harden generated shell scripts with set -e
- check-private-ip-address-availability: add user access check, drop manual body validation
- setup-main-host: pass host object with wg_ip_address
- install-wg-ui.sh: run service as the invoking user (root or sudo) instead of a dedicated wgui user, and skip systemd/OpenRC daemon setup when NODE_ENV=development
2026-09-13 11:43:26 +01:00

293 lines
8.5 KiB
Bash
Executable File

#!/bin/bash
# This script installs wg-ui from the git repo
# and sets up the appropriate system process
# manager (systemd or whichever) for the
# appropriate OS. The preferred directory for
# assets is /var/lib/wgui/webapp
set -euo pipefail
WGUI_LIB_DIR="/var/lib/wgui"
INSTALL_DIR="${INSTALL_DIR:-$WGUI_LIB_DIR/webapp}"
SERVICE_USER="${SERVICE_USER:-}"
SERVICE_GROUP="${SERVICE_GROUP:-}"
SERVICE_NAME="${SERVICE_NAME:-wgui}"
REPO_URL="${REPO_URL:-}"
BRANCH="${BRANCH:-main}"
BUN_INSTALL_DIR="${BUN_INSTALL_DIR:-/opt/bun}"
BUN_BIN="/usr/local/bin/bun"
UPDATE=false
DEV_MODE=false
if [ "${NODE_ENV:-}" = "development" ]; then
DEV_MODE=true
log "NODE_ENV=development — skipping system service installation (daemon is assumed to already be running)"
fi
log() { echo "==> $*"; }
fail() { echo "error: $*" >&2; exit 1; }
require_root() {
if [ "$(id -u)" -ne 0 ]; then
if ! command -v sudo >/dev/null 2>&1; then
fail "this script must be run as root, and sudo is not installed"
fi
log "not running as root — re-executing with sudo ..."
sudo -v || fail "current user does not have sudo privileges — run this script as root or grant the current user sudo access"
exec sudo -E "$0" "$@"
fi
}
resolve_service_user() {
if [ -n "${SUDO_USER:-}" ] && [ "$SUDO_USER" != "root" ] && id -u "$SUDO_USER" >/dev/null 2>&1; then
echo "$SUDO_USER"
else
echo "root"
fi
}
require_root
SERVICE_USER="${SERVICE_USER:-$(resolve_service_user)}"
SERVICE_GROUP="${SERVICE_GROUP:-$(id -gn "$SERVICE_USER")}"
log "running the wg-ui service as $SERVICE_USER (group: $SERVICE_GROUP)"
if [ -z "$REPO_URL" ]; then
fail "REPO_URL is not set — pass the git URL of the wg-ui repo, e.g.
REPO_URL=https://git.example.com/org/wireguard-ui.git $0"
fi
detect_distro() {
if [ -f /etc/os-release ]; then
# shellcheck disable=SC1091
. /etc/os-release
echo "$ID"
else
echo "unknown"
fi
}
detect_init_system() {
if command -v systemctl >/dev/null 2>&1; then
echo "systemd"
elif [ -x /sbin/openrc-run ] || [ -f /etc/alpine-release ]; then
echo "openrc"
else
echo "unknown"
fi
}
DISTRO="$(detect_distro)"
INIT_SYSTEM="$(detect_init_system)"
log "detected distro: $DISTRO, init system: $INIT_SYSTEM"
install_deps() {
case "$DISTRO" in
debian | ubuntu | linuxmint | raspbian)
export DEBIAN_FRONTEND=noninteractive
apt-get update -y
apt-get install -y git curl ca-certificates openssl
;;
fedora | rhel | centos | rocky | almalinux)
dnf install -y git curl ca-certificates openssl
;;
arch | manjaro | endeavouros)
pacman -Syu --noconfirm --needed git curl ca-certificates openssl
;;
alpine)
apk add --no-cache bash curl ca-certificates-bundle git openssl
;;
*)
fail "unsupported distro: $DISTRO"
;;
esac
}
install_bun() {
if [ ! -x "$BUN_BIN" ]; then
log "installing bun to $BUN_INSTALL_DIR ..."
mkdir -p "$BUN_INSTALL_DIR"
curl -fsSL https://bun.sh/install | BUN_INSTALL="$BUN_INSTALL_DIR" bash
if [ ! -x "$BUN_INSTALL_DIR/bun" ]; then
fail "bun binary not found at $BUN_INSTALL_DIR/bun after install"
fi
ln -sf "$BUN_INSTALL_DIR/bun" "$BUN_BIN"
fi
log "bun: $($BUN_BIN --version)"
}
setup_service_user() {
mkdir -p "$WGUI_LIB_DIR/iptables" "$WGUI_LIB_DIR/keys" "$WGUI_LIB_DIR/clients"
chown -R "$SERVICE_USER:$SERVICE_GROUP" "$WGUI_LIB_DIR"
}
clone_or_update() {
mkdir -p "$(dirname "$INSTALL_DIR")"
if [ -d "$INSTALL_DIR/.git" ]; then
UPDATE=true
log "updating existing install at $INSTALL_DIR ..."
git -C "$INSTALL_DIR" fetch --depth 1 origin "$BRANCH"
local db_backup="$INSTALL_DIR/db/.wgui-pre-update.db"
if [ -f "$INSTALL_DIR/db/wgui" ]; then
cp -a "$INSTALL_DIR/db/wgui" "$db_backup"
fi
git -C "$INSTALL_DIR" reset --hard "origin/$BRANCH"
if [ -f "$db_backup" ]; then
mv -f "$db_backup" "$INSTALL_DIR/db/wgui"
fi
else
log "cloning wg-ui ($REPO_URL, branch $BRANCH) ..."
git clone --depth 1 --branch "$BRANCH" "$REPO_URL" "$INSTALL_DIR"
fi
chown -R "$SERVICE_USER:$SERVICE_GROUP" "$INSTALL_DIR"
}
run_as_service_user() {
local cmd
cmd="cd $(printf '%q' "$INSTALL_DIR") && export PATH=/usr/local/bin:/usr/bin:/bin && $(printf '%q ' "$@")"
if command -v runuser >/dev/null 2>&1; then
runuser -u "$SERVICE_USER" -- bash -c "$cmd"
else
su -s /bin/bash -c "$cmd" "$SERVICE_USER"
fi
}
install_dependencies() {
log "installing app dependencies with bun ..."
run_as_service_user "$BUN_BIN" install
}
ensure_env_file() {
if [ ! -f "$INSTALL_DIR/.env" ]; then
log "generating $INSTALL_DIR/.env with fresh encryption secrets ..."
{
echo "NODE_ENV=${NODE_ENV:-production}"
echo "ENCRYPTION_KEY=$(openssl rand -base64 32 | tr -d '\n')"
echo "ENCRYPTION_SALT=$(openssl rand -base64 32 | tr -d '\n')"
echo "DATA_DIR=$INSTALL_DIR/.data"
} > "$INSTALL_DIR/.env"
chown "$SERVICE_USER:$SERVICE_GROUP" "$INSTALL_DIR/.env"
chmod 600 "$INSTALL_DIR/.env"
fi
}
setup_wireguard() {
local setup_script="$INSTALL_DIR/src/scripts/setup-wireguard.sh"
if [ -x "$setup_script" ] && [ "${SKIP_WIREGUARD_SETUP:-0}" != "1" ]; then
log "running $(basename "$setup_script") to install wireguard on this host ..."
"$setup_script"
fi
}
grant_runtime_access() {
log "granting $SERVICE_USER access to /etc/wireguard ..."
mkdir -p /etc/wireguard
chown "root:$SERVICE_GROUP" /etc/wireguard
chmod 770 /etc/wireguard
}
install_systemd_unit() {
local unit="/etc/systemd/system/$SERVICE_NAME.service"
log "writing systemd unit $unit ..."
cat > "$unit" <<EOF
[Unit]
Description=Wireguard UI
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=$SERVICE_USER
Group=$SERVICE_GROUP
WorkingDirectory=$INSTALL_DIR
Environment=NODE_ENV=production
ExecStart=$BUN_BIN src/server.ts
Restart=on-failure
RestartSec=5
PrivateTmp=true
UMask=0077
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
log "enabling and starting $SERVICE_NAME ..."
systemctl enable --now "$SERVICE_NAME.service"
if [ "$UPDATE" = true ]; then
log "restarting $SERVICE_NAME to load updated code ..."
systemctl restart "$SERVICE_NAME.service"
fi
}
install_openrc_unit() {
local init_script="/etc/init.d/$SERVICE_NAME"
log "writing openrc init script $init_script ..."
cat > "$init_script" <<EOF
#!/sbin/openrc-run
name="$SERVICE_NAME"
description="Wireguard UI"
command="$BUN_BIN"
command_args="src/server.ts"
command_user="$SERVICE_USER"
directory="$INSTALL_DIR"
output_log="/var/log/$SERVICE_NAME.log"
error_log="/var/log/$SERVICE_NAME.log"
pidfile="/run/\${RC_SVCNAME}.pid"
export NODE_ENV="production"
start_pre() {
umask 077
}
depend() {
need net
}
EOF
chmod +x "$init_script"
log "adding $SERVICE_NAME to default runlevel and starting ..."
rc-update add "$SERVICE_NAME" default
if [ "$UPDATE" = true ]; then
rc-service "$SERVICE_NAME" restart
else
rc-service "$SERVICE_NAME" start
fi
}
grab_port() {
grep -oE 'ServerPort: [0-9]+' "$INSTALL_DIR/src/data/site-data.ts" 2>/dev/null | grep -oE '[0-9]+' | head -1 || echo "10752"
}
install_deps
install_bun
setup_service_user
clone_or_update
install_dependencies
ensure_env_file
setup_wireguard
grant_runtime_access
if [ "$DEV_MODE" = false ]; then
case "$INIT_SYSTEM" in
systemd)
install_systemd_unit
;;
openrc)
install_openrc_unit
;;
*)
log "no supported init system found — start manually with:
su -s /bin/bash $SERVICE_USER -c 'cd $INSTALL_DIR && NODE_ENV=production $BUN_BIN src/server.ts'
(add the line above to your boot scripts)"
;;
esac
fi
PORT="$(grab_port)"
log "wg-ui install complete."
log "webapp: $INSTALL_DIR"
log "runtime: $WGUI_LIB_DIR (keys, iptables, client configs)"
if [ "$DEV_MODE" = true ]; then
log "process: development mode — no system service installed"
else
log "process: managed by $INIT_SYSTEM as $SERVICE_NAME"
fi
log "open http://$(hostname -I 2>/dev/null | awk '{print $1}' || echo localhost):$PORT in your browser"