#!/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 "=========================================="