#!/usr/bin/env bash set -euo pipefail # ============================================================ # Server Panel one-click installer # Usage: # curl -fsSL https://raw.githubusercontent.com/naibabiji/server-panel/master/install.sh | bash # # Optional env: # REPO=naibabiji/server-panel # VERSION=v1.0.0 # default: latest # TLS_PORT=8444 # default: 8444 # INSTALL_DIR=/www/server/server-panel # INSTALL_MODE=upgrade # existing install only: upgrade, reinstall, exit # ============================================================ RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' CYAN='\033[0;36m' NC='\033[0m' REPO="${REPO:-naibabiji/server-panel}" VERSION="${VERSION:-latest}" INSTALL_DIR="${INSTALL_DIR:-/www/server/server-panel}" TLS_PORT="${TLS_PORT:-8444}" CONFIG_FILE="$INSTALL_DIR/config.json" DB_PATH="$INSTALL_DIR/server-panel.db" BIN_PATH="/usr/local/bin/server-panel" SERVICE_PATH="/etc/systemd/system/server-panel.service" log() { echo -e "${GREEN}[+]${NC} $1"; } warn() { echo -e "${YELLOW}[!]${NC} $1"; } err() { echo -e "${RED}[x]${NC} $1"; exit 1; } prompt() { local __var="$1" local message="$2" local default="${3:-}" local value="" if [ -r /dev/tty ] && [ -w /dev/tty ] && tty -s 2>/dev/null /dev/tty IFS= read -r value /dev/null 2>&1; then export DEBIAN_FRONTEND=noninteractive apt-get update -y apt-get install -y ca-certificates curl openssl util-linux e2fsprogs parted nftables elif command -v dnf >/dev/null 2>&1; then dnf install -y ca-certificates curl openssl util-linux e2fsprogs parted if ! command -v nft >/dev/null 2>&1 && ! dnf install -y nftables; then warn "当前系统无法安装 nftables,将保留应用层封禁;建议手动配置系统防火墙" fi elif command -v yum >/dev/null 2>&1; then yum install -y ca-certificates curl openssl util-linux e2fsprogs parted if ! command -v nft >/dev/null 2>&1 && ! yum install -y nftables; then warn "当前系统无法安装 nftables,将保留应用层封禁;建议手动配置系统防火墙" fi else for bin in curl openssl lsblk findmnt wipefs mkfs.ext4 parted partprobe; do command -v "$bin" >/dev/null 2>&1 || err "缺少依赖: $bin,请先安装磁盘管理依赖" done fi command -v systemctl >/dev/null 2>&1 || err "当前系统缺少 systemd/systemctl,暂不支持一键安装" if command -v nft >/dev/null 2>&1; then log "nftables 命令可用,将由面板初始化 IPv4/IPv6 自动封禁规则" else warn "未检测到 nft 命令,网络层自动封禁不可用;面板仍保留认证和应用层封禁" fi } detect_arch() { case "$(uname -m)" in x86_64|amd64) echo "amd64" ;; aarch64|arm64) echo "arm64" ;; *) err "暂不支持当前架构: $(uname -m)" ;; esac } release_url() { local arch="$1" local asset="server-panel-linux-${arch}" if [ "$VERSION" = "latest" ]; then echo "https://github.com/${REPO}/releases/latest/download/${asset}" else echo "https://github.com/${REPO}/releases/download/${VERSION}/${asset}" fi } download_file() { local url="$1" local dest="$2" local timeout="${3:-60}" curl -fL --retry 3 --connect-timeout 15 --max-time "$timeout" "$url" -o "$dest" } install_downloaded_binary() { local url="$1" local label="$2" local tmp_bin tmp_bin="$(mktemp)" log "尝试下载 Server Panel 二进制: ${label}" if download_file "$url" "$tmp_bin" 120; then install -m 0755 "$tmp_bin" "$BIN_PATH" rm -f "$tmp_bin" log "面板二进制下载完成: ${label}" return 0 fi rm -f "$tmp_bin" warn "${label} 下载失败" return 1 } download_binary() { local arch asset url script_dir arch="$(detect_arch)" asset="server-panel-linux-${arch}" url="$(release_url "$arch")" script_dir="" if [ -f "$0" ] && [ "$(basename "$0")" != "bash" ] && [ "$(basename "$0")" != "sh" ]; then script_dir="$(cd "$(dirname "$0")" && pwd)" fi if [ -n "$script_dir" ] && [ -s "${script_dir}/server-panel" ]; then log "使用脚本同目录的 server-panel 二进制" install -m 0755 "${script_dir}/server-panel" "$BIN_PATH" return fi if [ -n "$script_dir" ] && [ -s "${script_dir}/${asset}" ]; then log "使用脚本同目录的 ${asset} 二进制" install -m 0755 "${script_dir}/${asset}" "$BIN_PATH" return fi if ! install_downloaded_binary "$url" "GitHub Releases 直连"; then err "无法获取正式版二进制。解决方案: 1. 检查服务器能否访问 GitHub Releases 2. 手动下载 release 附件 ${asset} 后,和 install.sh 放在同一目录重新运行 3. 或在本机编译后上传为 server-panel,与 install.sh 放在同一目录重新运行" fi } generate_password() { openssl rand -base64 32 | tr -dc 'A-Za-z0-9' | head -c 20 } hash_password() { local password="$1" "$BIN_PATH" --hash-password "$password" } write_service() { log "写入 systemd 服务..." cat > "$SERVICE_PATH" </dev/null } restart_service() { log "启动服务..." systemctl stop server-panel 2>/dev/null || true systemctl start server-panel sleep 2 if systemctl is-active --quiet server-panel; then log "服务已启动" else warn "服务可能未正常启动,请检查: journalctl -u server-panel -n 50" fi } validate_tls_port() { if ! [[ "$TLS_PORT" =~ ^[0-9]+$ ]] || [ "$TLS_PORT" -lt 1 ] || [ "$TLS_PORT" -gt 65535 ]; then err "HTTPS 端口必须是 1-65535 之间的整数,当前值: $TLS_PORT" fi } load_existing_tls_port() { local configured_port configured_port="$(sed -n 's/^[[:space:]]*"tls_port"[[:space:]]*:[[:space:]]*\([0-9][0-9]*\).*/\1/p' "$CONFIG_FILE" | head -n 1)" if [ -n "$configured_port" ]; then TLS_PORT="$configured_port" else warn "无法从现有配置读取 HTTPS 端口,将检查默认端口 $TLS_PORT" fi validate_tls_port } configure_firewall() { log "检查系统防火墙..." if command -v ufw >/dev/null 2>&1 && ufw status 2>/dev/null | grep -q '^Status: active'; then if ufw allow "${TLS_PORT}/tcp" >/dev/null; then log "UFW 已放行面板端口 ${TLS_PORT}/tcp" else warn "UFW 放行 ${TLS_PORT}/tcp 失败,请手动执行: ufw allow ${TLS_PORT}/tcp" fi return fi if command -v firewall-cmd >/dev/null 2>&1 && firewall-cmd --state >/dev/null 2>&1; then if firewall-cmd --permanent --add-port="${TLS_PORT}/tcp" >/dev/null && firewall-cmd --reload >/dev/null; then log "firewalld 已放行面板端口 ${TLS_PORT}/tcp" else warn "firewalld 放行 ${TLS_PORT}/tcp 失败,请手动检查防火墙规则" fi return fi warn "未检测到已启用的 UFW 或 firewalld;如有云防火墙/安全组,请手动放行 ${TLS_PORT}/tcp" } upgrade_existing() { load_existing_tls_port download_binary write_service configure_firewall restart_service echo "" log "升级完成,配置和数据库已保留: $CONFIG_FILE" echo " systemctl status server-panel" echo " journalctl -u server-panel -f" } remove_service_only() { systemctl stop server-panel 2>/dev/null || true systemctl disable server-panel 2>/dev/null || true rm -f "$SERVICE_PATH" systemctl daemon-reload } handle_existing_install() { local mode if [ ! -f "$CONFIG_FILE" ]; then return fi echo "" warn "检测到已有安装: $CONFIG_FILE" mode="${INSTALL_MODE:-}" case "$mode" in ""|upgrade) upgrade_existing exit 0 ;; reinstall) remove_service_only return ;; exit) exit 0 ;; *) err "INSTALL_MODE 只能是 upgrade、reinstall 或 exit" ;; esac } generate_tls_cert() { local cert_file key_file cert_san cert_cn cert_file="$INSTALL_DIR/certs/panel.crt" key_file="$INSTALL_DIR/certs/panel.key" if [ -n "$DOMAIN" ]; then cert_san="DNS:$DOMAIN,IP:127.0.0.1" cert_cn="$DOMAIN" else cert_san="IP:127.0.0.1" cert_cn="Server-Panel-SelfSigned" fi log "生成自签 TLS 证书..." openssl req -x509 -nodes -days 3650 -newkey rsa:2048 \ -keyout "$key_file" \ -out "$cert_file" \ -subj "/CN=$cert_cn" \ -addext "subjectAltName=$cert_san" \ 2>/dev/null chmod 600 "$key_file" chmod 644 "$cert_file" } write_config() { local cert_file key_file web_hash basic_hash random_suffix cert_file="$INSTALL_DIR/certs/panel.crt" key_file="$INSTALL_DIR/certs/panel.key" random_suffix="$(openssl rand -base64 24 | tr -dc 'A-Za-z0-9' | head -c 16)" WEB_PASSWORD="$(generate_password)" BASIC_PASSWORD="$(generate_password)" web_hash="$(hash_password "$WEB_PASSWORD")" basic_hash="$(hash_password "$BASIC_PASSWORD")" log "写入配置文件..." cat > "$CONFIG_FILE" <