Add wg-ui install script with least-privilege service user
This commit is contained in:
Executable
+247
@@ -0,0 +1,247 @@
|
||||
#!/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:-wgui}"
|
||||
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"
|
||||
|
||||
log() { echo "==> $*"; }
|
||||
fail() { echo "error: $*" >&2; exit 1; }
|
||||
|
||||
if [ "$(id -u)" -ne 0 ]; then
|
||||
fail "this script must be run as root"
|
||||
fi
|
||||
|
||||
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() {
|
||||
if ! id -u "$SERVICE_USER" >/dev/null 2>&1; then
|
||||
useradd --system --home-dir "$WGUI_LIB_DIR" --shell /usr/sbin/nologin "$SERVICE_USER"
|
||||
fi
|
||||
mkdir -p "$WGUI_LIB_DIR/iptables" "$WGUI_LIB_DIR/keys" "$WGUI_LIB_DIR/clients"
|
||||
chown -R "$SERVICE_USER:$SERVICE_USER" "$WGUI_LIB_DIR"
|
||||
}
|
||||
|
||||
clone_or_update() {
|
||||
mkdir -p "$(dirname "$INSTALL_DIR")"
|
||||
if [ -d "$INSTALL_DIR/.git" ]; then
|
||||
log "updating existing install at $INSTALL_DIR ..."
|
||||
git -C "$INSTALL_DIR" fetch --depth 1 origin "$BRANCH"
|
||||
git -C "$INSTALL_DIR" reset --hard "origin/$BRANCH"
|
||||
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_USER" "$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=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_USER" "$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_USER" /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_USER
|
||||
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"
|
||||
}
|
||||
|
||||
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
|
||||
rc-service "$SERVICE_NAME" start
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
PORT="$(grab_port)"
|
||||
log "wg-ui install complete."
|
||||
log "webapp: $INSTALL_DIR"
|
||||
log "runtime: $WGUI_LIB_DIR (keys, iptables, client configs)"
|
||||
log "process: managed by $INIT_SYSTEM as $SERVICE_NAME"
|
||||
log "open http://$(hostname -I 2>/dev/null | awk '{print $1}' || echo localhost):$PORT in your browser"
|
||||
@@ -59,6 +59,10 @@ case "$DISTRO" in
|
||||
echo "detected Arch-family distro: $DISTRO"
|
||||
pacman -Syu --noconfirm --needed wireguard-tools
|
||||
;;
|
||||
alpine)
|
||||
echo "detected Alpine distro: $DISTRO"
|
||||
apk add --no-cache wireguard-tools
|
||||
;;
|
||||
*)
|
||||
echo "error: unsupported distro: $DISTRO" >&2
|
||||
echo "install wireguard manually (kernel module + wg/wg-quick tools)" >&2
|
||||
|
||||
Reference in New Issue
Block a user