install.sh
Rama master · réplica de lectura del repositorio original.
#!/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; then
printf "%s" "$message" >/dev/tty
IFS= read -r value </dev/tty || value="$default"
elif [ -t 0 ]; then
read -r -p "$message" value || value="$default"
else
value="$default"
echo "${message}${value}"
fi
if [ -z "$value" ] && [ -n "$default" ]; then
value="$default"
fi
printf -v "$__var" '%s' "$value"
}
require_root() {
if [ "$(id -u)" != "0" ]; then
err "请使用 root 用户运行此脚本"
fi
}
install_dependencies() {
log "检查系统依赖..."
if command -v apt-get >/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" <<EOF
[Unit]
Description=Server Panel - Central Server Management Panel
After=network.target
[Service]
Type=simple
User=root
Group=root
ExecStart=$BIN_PATH --config=$CONFIG_FILE
Restart=always
RestartSec=5
StandardOutput=journal
StandardError=journal
SyslogIdentifier=server-panel
LimitNOFILE=65536
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable server-panel >/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" <<EOF
{
"panel": {
"version": "$VERSION",
"tls_port": $TLS_PORT,
"tls_cert_path": "$cert_file",
"tls_key_path": "$key_file",
"tls_mode": "self_signed",
"domain": "$DOMAIN",
"public_url": "$PUBLIC_URL",
"acme_email": "",
"acme_directory_url": "",
"acme_storage_path": "",
"acme_challenge_port": 80,
"random_suffix": "$random_suffix",
"data_dir": "$INSTALL_DIR",
"log_dir": "$INSTALL_DIR/logs",
"panel_title": "Server Panel"
},
"sqlite": {
"path": "$DB_PATH"
},
"admin": {
"username": "spadmin",
"password_hash": "$web_hash"
},
"basic_auth": {
"username": "admin",
"password_hash": "$basic_hash"
},
"security": {
"basic_auth_enabled": true,
"max_login_attempts": 5,
"attempt_window_minutes": 5,
"ban_duration_hours": 24
},
"systemd": {
"service_name": "server-panel",
"service_path": "$SERVICE_PATH",
"binary_path": "$BIN_PATH"
}
}
EOF
chmod 600 "$CONFIG_FILE"
RANDOM_SUFFIX="$random_suffix"
}
print_summary() {
local server_ip panel_url
if [ -n "$DOMAIN" ]; then
panel_url="https://${DOMAIN}:${TLS_PORT}/${RANDOM_SUFFIX}/login"
else
server_ip="$(hostname -I 2>/dev/null | awk '{print $1}')"
panel_url="https://${server_ip:-YOUR_SERVER_IP}:${TLS_PORT}/${RANDOM_SUFFIX}/login"
fi
echo ""
echo "============================================"
echo " Server Panel 安装完成"
echo "============================================"
echo ""
echo -e " ${CYAN}面板地址:${NC} $panel_url"
echo -e " ${CYAN}BasicAuth:${NC} admin / ${GREEN}${BASIC_PASSWORD}${NC}"
echo -e " ${CYAN}面板登录:${NC} spadmin / ${GREEN}${WEB_PASSWORD}${NC}"
echo -e " ${CYAN}安装目录:${NC} $INSTALL_DIR"
echo -e " ${CYAN}配置文件:${NC} $CONFIG_FILE"
echo ""
echo -e " ${YELLOW}请立即保存以上地址和密码。自签证书会有浏览器安全提示,公网使用建议放在 Nginx/Caddy 反向代理后。${NC}"
echo ""
echo " 管理命令:"
echo " systemctl status server-panel # 查看服务运行状态"
echo " systemctl restart server-panel # 重启服务(使手动改动的配置生效)"
echo " journalctl -u server-panel -f # 实时跟踪服务日志"
echo " server-panel --reset-password # 重置管理员密码(同时重置 BasicAuth 与面板登录,并自动重启服务)"
echo " server-panel --unban-all # 清空所有封禁 IP 与登录失败记录(自己被误封时自救,立即生效无需重启)"
echo ""
}
main() {
require_root
install_dependencies
handle_existing_install
mkdir -p "$INSTALL_DIR/certs" "$INSTALL_DIR/logs"
echo ""
echo "============================================"
echo " Server Panel TLS 配置"
echo "============================================"
DOMAIN="${DOMAIN:-}"
PUBLIC_URL="${PUBLIC_URL:-}"
if [ -n "$DOMAIN" ]; then
PUBLIC_URL="${PUBLIC_URL:-https://${DOMAIN}:${TLS_PORT}}"
log "使用环境变量中的面板域名: $DOMAIN"
else
prompt bind_domain "是否绑定面板域名? [y/N]: " "n"
fi
if [ -z "$DOMAIN" ] && [[ "${bind_domain:-}" =~ ^[Yy] ]]; then
prompt DOMAIN "面板域名(如 panel.example.com): "
if [ -n "$DOMAIN" ]; then
prompt tls_port_input "HTTPS 端口 (默认 ${TLS_PORT}): "
TLS_PORT="${tls_port_input:-$TLS_PORT}"
PUBLIC_URL="https://${DOMAIN}:${TLS_PORT}"
fi
fi
validate_tls_port
download_binary
generate_tls_cert
write_config
write_service
configure_firewall
restart_service
print_summary
}
main "$@"