#!/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 log() { echo "==> $*"; } fail() { echo "error: $*" >&2; exit 1; } WGUI_LIB_DIR="/var/lib/wgui" WG_QUICK_HELPER="$WGUI_LIB_DIR/scripts/wg-quick-manage.sh" INSTALL_DIR="${INSTALL_DIR:-}" SERVICE_NAME="${SERVICE_NAME:-wgui}" REPO_URL="${REPO_URL:-https://git.tben.me/Moduletrace/wireguard-ui.git}" BRANCH="${BRANCH:-main}" BUN_INSTALL_DIR="${BUN_INSTALL_DIR:-/opt/bun}" BUN_VERSION="${BUN_VERSION:-1.3.14}" BUN_BIN="/usr/local/bin/bun" UPDATE=false DEV_MODE=false if [ "${NODE_ENV:-}" = "development" ]; then DEV_MODE=true fi SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")" && pwd)" REPO_ROOT="$(dirname "$(dirname "$SCRIPT_DIR")")" 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 } require_root log "running the wg-ui service as root" if [ "$DEV_MODE" = true ]; then INSTALL_DIR="${INSTALL_DIR:-$REPO_ROOT}" log "NODE_ENV=development — using local repo at $INSTALL_DIR, skipping clone and system service installation (daemon is assumed to already be running)" else INSTALL_DIR="${INSTALL_DIR:-$WGUI_LIB_DIR/webapp}" 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 zip unzip ;; fedora | rhel | centos | rocky | almalinux | ol) dnf install -y git curl ca-certificates openssl zip unzip ;; arch | manjaro | endeavouros) pacman -Syu --noconfirm --needed git curl ca-certificates openssl zip unzip ;; alpine) apk add --no-cache bash curl ca-certificates-bundle git openssl zip unzip ;; *) fail "unsupported distro: $DISTRO" ;; esac } install_bun() { local bun_binary="$BUN_INSTALL_DIR/bin/bun" if [ ! -x "$BUN_BIN" ]; then log "installing bun $BUN_VERSION to $BUN_INSTALL_DIR ..." mkdir -p "$BUN_INSTALL_DIR" curl -fsSL https://bun.sh/install | BUN_INSTALL="$BUN_INSTALL_DIR" bash -s "bun-v$BUN_VERSION" if [ ! -x "$bun_binary" ]; then fail "bun binary not found at $bun_binary after install" fi ln -sf "$bun_binary" "$BUN_BIN" fi log "bun: $($BUN_BIN --version)" } setup_lib_dirs() { mkdir -p "$WGUI_LIB_DIR/iptables" "$WGUI_LIB_DIR/keys" "$WGUI_LIB_DIR/clients" "$WGUI_LIB_DIR/hosts" "$WGUI_LIB_DIR/scripts" chmod 700 "$WGUI_LIB_DIR/iptables" "$WGUI_LIB_DIR/keys" "$WGUI_LIB_DIR/clients" "$WGUI_LIB_DIR/hosts" chmod 755 "$WGUI_LIB_DIR/scripts" } 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 } install_dependencies() { log "installing app dependencies with bun ..." cd "$INSTALL_DIR" "$BUN_BIN" install "$BUN_BIN" bun-sqlite schema } 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" 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 } install_wg_quick_helper() { local helper_src="$INSTALL_DIR/src/scripts/wg-quick-manage.sh" if [ ! -f "$helper_src" ]; then fail "missing $helper_src — cannot install the wg-quick helper" fi log "installing wg-quick helper to $WG_QUICK_HELPER ..." install -m 755 -o root -g root "$helper_src" "$WG_QUICK_HELPER" } install_systemd_unit() { local unit="/etc/systemd/system/$SERVICE_NAME.service" log "writing systemd unit $unit ..." cat > "$unit" < "$init_script" </dev/null | grep -oE '[0-9]+' | head -1 || echo "10752" } configure_firewall() { local port="$1" log "configuring firewall to allow inbound traffic on port $port/tcp ..." # Check for firewalld (RHEL, CentOS, Fedora, Rocky, Alma) if command -v firewall-cmd >/dev/null 2>&1 && systemctl is-active --quiet firewalld 2>/dev/null; then log "firewalld is active. opening port $port/tcp ..." firewall-cmd --permanent --add-port="${port}/tcp" >/dev/null 2>&1 || fail "failed to add port to firewalld" firewall-cmd --reload >/dev/null 2>&1 || fail "failed to reload firewalld" # Check for ufw (Ubuntu, Debian, Mint) elif command -v ufw >/dev/null 2>&1 && ufw status 2>/dev/null | grep -q "Status: active"; then log "ufw is active. opening port $port/tcp ..." ufw allow "${port}/tcp" >/dev/null 2>&1 || log "warning: failed to add port to ufw" # Fallback for Alpine or manual iptables else log "no active high-level firewall manager detected (firewalld/ufw). if you use iptables/nftables directly, ensure port $port/tcp is allowed." fi } install_deps install_bun setup_lib_dirs if [ "$DEV_MODE" = false ]; then clone_or_update fi install_dependencies ensure_env_file setup_wireguard install_wg_quick_helper 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: cd $INSTALL_DIR && NODE_ENV=production $BUN_BIN src/server.ts (add the line above to your boot scripts)" ;; esac fi PORT="$(grab_port)" if [ "$DEV_MODE" = false ]; then configure_firewall "$PORT" fi log "wg-ui install complete." log "webapp: $INSTALL_DIR" log "runtime: $WGUI_LIB_DIR (host configs, keys, iptables, client configs)" log "tunnels: managed by the web server via wg-quick up/down (configs in $WGUI_LIB_DIR/hosts — /etc/wireguard is never touched)" if [ "$DEV_MODE" = true ]; then log "process: development mode — no system service installed; run the dev server as root (e.g. after sudo -i)" 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"