Files
yakpanel-core/YakPanel-server/install.sh
2026-04-07 05:12:03 +05:30

392 lines
12 KiB
Bash

#!/usr/bin/env bash
# YakPanel (YakPanel-server) — native Linux installer (systemd + Nginx + Redis).
#
# One-liners (run as root):
# curl -fsSL https://www.yakpanel.com/YakPanel-server/install.sh | bash
# wget -qO- https://www.yakpanel.com/YakPanel-server/install.sh | bash
#
# Preserve environment through sudo:
# sudo -E bash install.sh
#
# Environment (optional):
# REPO_URL Git URL (default: https://source.yakpanel.com/admin/yakpanel-core.git)
# YAKPANEL_BRANCH Branch/tag for shallow clone (default: default branch)
# GIT_REF Alias for YAKPANEL_BRANCH
# INSTALL_PATH Install dir (default: /www/server/YakPanel-server)
# PANEL_PORT Nginx listen port (default: 8888)
# BACKEND_PORT Uvicorn port (default: 8889)
# YAKPANEL_SOURCE_DIR If set, skip git: must contain backend/ and frontend/ (air-gap / local tree)
#
# From an already-cloned YakPanel-server repo:
# sudo YAKPANEL_SOURCE_DIR="$(pwd)" bash install.sh
set -euo pipefail
REPO_URL="${REPO_URL:-https://source.yakpanel.com/admin/yakpanel-core.git}"
INSTALL_PATH="${INSTALL_PATH:-/www/server/YakPanel-server}"
PANEL_PORT="${PANEL_PORT:-8888}"
BACKEND_PORT="${BACKEND_PORT:-8889}"
YAKPANEL_BRANCH="${YAKPANEL_BRANCH:-${GIT_REF:-}}"
SYSTEMD_UNIT="YakPanel-server"
while [ $# -gt 0 ]; do
case "$1" in
--repo-url)
REPO_URL="$2"
shift 2
;;
--install-path)
INSTALL_PATH="$2"
shift 2
;;
--branch|--ref)
YAKPANEL_BRANCH="$2"
shift 2
;;
--source-dir)
YAKPANEL_SOURCE_DIR="$2"
shift 2
;;
--panel-port)
PANEL_PORT="$2"
shift 2
;;
--backend-port)
BACKEND_PORT="$2"
shift 2
;;
-h|--help)
grep '^#' "$0" | grep -v '^#!/' | sed 's/^# \{0,1\}//'
exit 0
;;
*)
echo "Unknown option: $1 (use --help)"
exit 1
;;
esac
done
echo "=========================================="
echo " YakPanel Installer (YakPanel-server)"
echo "=========================================="
if [ "${EUID:-$(id -u)}" -ne 0 ]; then
echo "Run as root, e.g.: sudo -E bash $0"
exit 1
fi
detect_pkg_manager() {
if command -v apt-get >/dev/null 2>&1; then
echo "apt"
elif command -v dnf >/dev/null 2>&1; then
echo "dnf"
elif command -v yum >/dev/null 2>&1; then
echo "yum"
else
echo ""
fi
}
PKG=$(detect_pkg_manager)
if [ -z "$PKG" ]; then
echo "No supported package manager (apt-get, dnf, or yum). Use Docker instead; see README.md."
exit 1
fi
install_base_packages() {
echo ""
echo "[1/6] Installing system dependencies ($PKG)..."
case "$PKG" in
apt)
apt-get update -qq
DEBIAN_FRONTEND=noninteractive apt-get install -y \
git python3 python3-venv python3-pip python3-dev build-essential \
redis-server nginx curl ca-certificates
;;
dnf|yum)
if [ "$PKG" = dnf ]; then
$PKG install -y dnf-plugins-core 2>/dev/null || true
fi
if [ "$PKG" = dnf ] && [ -f /etc/almalinux-release ]; then
dnf config-manager --set-enabled crb 2>/dev/null || dnf config-manager --set-enabled powertools 2>/dev/null || true
fi
if [ "$PKG" = dnf ] && [ -f /etc/rocky-release ]; then
dnf config-manager --set-enabled crb 2>/dev/null || true
fi
$PKG install -y git python3 python3-pip nginx redis curl ca-certificates \
gcc-c++ make python3-devel openssl-devel || \
$PKG install -y git python3 python3-pip nginx redis curl ca-certificates gcc-c++ make
;;
esac
}
# Default dist Python (3.10+ on Ubuntu 22.04) is enough; otherwise install 3.11 from apt.
ensure_python_for_backend() {
if [ -n "${PYTHON_CMD:-}" ]; then
return 0
fi
if python3 -c 'import sys; raise SystemExit(0 if sys.version_info >= (3, 10) else 1)' 2>/dev/null; then
PYTHON_CMD=python3
return 0
fi
if command -v python3.11 >/dev/null 2>&1 && python3.11 -c 'import sys; raise SystemExit(0 if sys.version_info >= (3, 10) else 1)' 2>/dev/null; then
PYTHON_CMD=python3.11
return 0
fi
if [ "$PKG" = apt ]; then
echo "Python 3.10+ required for the backend; installing python3.11 from apt..."
DEBIAN_FRONTEND=noninteractive apt-get install -y python3.11 python3.11-venv python3.11-dev || {
echo "Could not install python3.11. Enable universe or install Python 3.10+ manually."
exit 1
}
PYTHON_CMD=python3.11
return 0
fi
echo "Python 3.10+ required. Current: $(python3 -V 2>/dev/null || echo 'python3 missing')"
exit 1
}
ensure_node_18_plus() {
local major
if command -v node >/dev/null 2>&1; then
major="$(node -v | sed 's/^v//' | cut -d. -f1)"
if [ "${major:-0}" -ge 18 ] 2>/dev/null; then
return 0
fi
fi
echo "Installing Node.js 20 (NodeSource)..."
case "$PKG" in
apt)
curl -fsSL https://deb.nodesource.com/setup_20.x | bash -
DEBIAN_FRONTEND=noninteractive apt-get install -y nodejs
;;
dnf|yum)
curl -fsSL https://rpm.nodesource.com/setup_20.x | bash -
$PKG install -y nodejs
;;
esac
}
find_yakpanel_root_in_tree() {
local root="$1"
if [ -d "$root/YakPanel-server/backend" ] && [ -d "$root/YakPanel-server/frontend" ]; then
echo "$root/YakPanel-server"
elif [ -d "$root/YakPanel-master/YakPanel-server/backend" ] && [ -d "$root/YakPanel-master/YakPanel-server/frontend" ]; then
echo "$root/YakPanel-master/YakPanel-server"
elif [ -d "$root/yakpanel-server/backend" ] && [ -d "$root/yakpanel-server/frontend" ]; then
echo "$root/yakpanel-server"
elif [ -d "$root/YakPanel-master/yakpanel-server/backend" ] && [ -d "$root/YakPanel-master/yakpanel-server/frontend" ]; then
echo "$root/YakPanel-master/yakpanel-server"
elif [ -d "$root/backend" ] && [ -d "$root/frontend" ]; then
echo "$root"
else
echo ""
fi
}
stage_source_to_install_path() {
local src="$1"
mkdir -p "$(dirname "$INSTALL_PATH")"
rm -rf "$INSTALL_PATH"
cp -a "$src" "$INSTALL_PATH"
}
install_base_packages
ensure_python_for_backend
ensure_node_18_plus
echo ""
echo "[2/6] Obtaining YakPanel source..."
if [ -n "${YAKPANEL_SOURCE_DIR:-}" ]; then
if [ ! -d "$YAKPANEL_SOURCE_DIR/backend" ] || [ ! -d "$YAKPANEL_SOURCE_DIR/frontend" ]; then
echo "YAKPANEL_SOURCE_DIR must contain backend/ and frontend/: $YAKPANEL_SOURCE_DIR"
exit 1
fi
stage_source_to_install_path "$YAKPANEL_SOURCE_DIR"
else
TMP_DIR="$(mktemp -d)"
trap 'rm -rf "$TMP_DIR"' EXIT
export GIT_TERMINAL_PROMPT=0
# Disable credential helpers for this clone so anonymous HTTPS public repos work
# from curl|bash (no TTY); USER:TOKEN in REPO_URL is still used if present.
CLONE_ARGS=(git -c credential.helper= clone --depth 1)
if [ -n "$YAKPANEL_BRANCH" ]; then
CLONE_ARGS+=(--branch "$YAKPANEL_BRANCH")
fi
CLONE_ARGS+=("$REPO_URL" "$TMP_DIR/repo")
if ! "${CLONE_ARGS[@]}"; then
echo "Git clone failed. Check REPO_URL=$REPO_URL, DNS, and outbound HTTPS."
echo "If the server requires sign-in: allow anonymous clone for HTTP(S), or use a token in the URL, e.g."
echo " REPO_URL=https://USER:TOKEN@source.yakpanel.com/admin/yakpanel-core.git"
echo "Or override: REPO_URL=https://your.other.git/mirror.git"
exit 1
fi
SRC_DIR="$(find_yakpanel_root_in_tree "$TMP_DIR/repo")"
if [ -z "$SRC_DIR" ]; then
echo "Could not find YakPanel-server layout (backend/ + frontend/) under cloned repo."
exit 1
fi
stage_source_to_install_path "$SRC_DIR"
trap - EXIT
rm -rf "$TMP_DIR"
fi
echo ""
echo "[3/6] Setting up backend..."
cd "$INSTALL_PATH/backend"
"$PYTHON_CMD" -m venv venv
# shellcheck source=/dev/null
source venv/bin/activate
pip install -q -r requirements.txt
./venv/bin/python -c "import app.main" || {
echo "ERROR: Backend failed to import (see traceback above). Check Python version and requirements."
exit 1
}
python scripts/seed_admin.py
deactivate
echo ""
echo "[4/6] Building frontend..."
cd "$INSTALL_PATH/frontend"
npm install --silent
# npm life-cycle scripts often shell-exec node_modules/.bin/* — fails on noexec or missing +x.
chmod +x node_modules/.bin/* 2>/dev/null || true
npm run build
echo ""
echo "[5/6] Configuring systemd..."
REDIS_AFTER="redis.target"
REDIS_WANTS=""
case "$PKG" in
apt)
REDIS_AFTER="redis-server.service"
REDIS_WANTS="Wants=redis-server.service"
;;
dnf|yum)
REDIS_AFTER="redis.service"
REDIS_WANTS="Wants=redis.service"
;;
esac
# Redis must be up before Uvicorn; starting it after the panel caused 502 (nginx OK, API down).
case "$PKG" in
apt)
systemctl enable redis-server 2>/dev/null || true
systemctl start redis-server 2>/dev/null || true
;;
dnf|yum)
systemctl enable redis 2>/dev/null || true
systemctl start redis 2>/dev/null || true
;;
esac
cat > "/etc/systemd/system/${SYSTEMD_UNIT}.service" << EOF
[Unit]
Description=YakPanel Backend
After=network.target $REDIS_AFTER
$REDIS_WANTS
[Service]
Type=simple
User=root
WorkingDirectory=$INSTALL_PATH/backend
# Include system paths: systemd does not expand \$PATH reliably; venv-only PATH breaks dnf/apt from the API.
Environment="PATH=$INSTALL_PATH/backend/venv/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
Environment=PYTHONUNBUFFERED=1
ExecStart=$INSTALL_PATH/backend/venv/bin/uvicorn app.main:app --host 127.0.0.1 --port $BACKEND_PORT
Restart=always
RestartSec=3
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable "$SYSTEMD_UNIT"
systemctl restart "$SYSTEMD_UNIT" || systemctl start "$SYSTEMD_UNIT"
backend_ok=0
for _ in {1..120}; do
if curl -sfS --max-time 2 "http://127.0.0.1:${BACKEND_PORT}/api/health" >/dev/null 2>&1; then
backend_ok=1
break
fi
sleep 0.5
done
if [ "$backend_ok" -ne 1 ]; then
echo ""
echo "WARNING: Backend did not respond on http://127.0.0.1:${BACKEND_PORT}/api/health (login will show 502 via Nginx)."
echo "Check: systemctl status $SYSTEMD_UNIT --no-pager"
echo "Logs: journalctl -u $SYSTEMD_UNIT -n 80 --no-pager"
echo "If SELinux is Enforcing: setsebool -P httpd_can_network_connect 1"
fi
write_nginx_site() {
local target="$1"
cat > "$target" << NGINXEOF
server {
listen $PANEL_PORT;
server_name _;
root $INSTALL_PATH/frontend/dist;
index index.html;
location / {
try_files \$uri \$uri/ /index.html;
}
location /api {
proxy_pass http://127.0.0.1:$BACKEND_PORT;
proxy_http_version 1.1;
proxy_set_header Host \$host;
proxy_set_header X-Real-IP \$remote_addr;
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto \$scheme;
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}
}
NGINXEOF
}
echo ""
echo "[6/6] Configuring Nginx..."
if [ -d /etc/nginx/sites-available ] && [ -d /etc/nginx/sites-enabled ]; then
write_nginx_site /etc/nginx/sites-available/YakPanel-server
ln -sf /etc/nginx/sites-available/YakPanel-server /etc/nginx/sites-enabled/YakPanel-server
else
write_nginx_site /etc/nginx/conf.d/YakPanel-server.conf
fi
systemctl enable nginx 2>/dev/null || true
systemctl start nginx 2>/dev/null || true
nginx -t
systemctl reload nginx 2>/dev/null || systemctl restart nginx
# Nginx -> upstream (e.g. 127.0.0.1:$BACKEND_PORT) is blocked by default on EL + SELinux => 502.
if command -v getenforce >/dev/null 2>&1 && [ "$(getenforce 2>/dev/null)" = "Enforcing" ]; then
if command -v setsebool >/dev/null 2>&1; then
setsebool -P httpd_can_network_connect 1 2>/dev/null || true
fi
fi
if systemctl is-active --quiet firewalld 2>/dev/null; then
firewall-cmd --permanent --add-port="${PANEL_PORT}/tcp" >/dev/null 2>&1 || true
firewall-cmd --reload >/dev/null 2>&1 || true
fi
if command -v ufw >/dev/null 2>&1; then
ufw allow "${PANEL_PORT}/tcp" 2>/dev/null || true
fi
echo ""
echo "=========================================="
echo " YakPanel installed successfully!"
echo "=========================================="
echo ""
echo " Access: http://YOUR_SERVER_IP:$PANEL_PORT"
echo " Login: admin / admin"
echo ""
echo " Change your password after first login."
echo " UI works but login shows 502? Backend down or SELinux: systemctl status $SYSTEMD_UNIT; journalctl -u $SYSTEMD_UNIT -n 50"
echo " SELinux: installer calls setsebool httpd_can_network_connect if Enforcing; see README for 403 on static files."
echo "=========================================="