#!/bin/bash # 设置变量 DATA_DIR="/data" SCRIPT_DIR="/boot/脚本" COMPOSE_FILE="$SCRIPT_DIR/ru.yaml" LOG_FILE="/var/log/npm-deploy.log" # 日志函数 log_message() { echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" | tee -a "$LOG_FILE" } # 检查是否以 root 权限运行 if [ "$EUID" -ne 0 ]; then echo "请使用 sudo 运行此脚本" exit 1 fi # 检查必要命令 check_requirements() { local missing=() for cmd in docker netstat ss lsof; do if ! command -v "$cmd" &> /dev/null; then missing+=("$cmd") fi done if [ ${#missing[@]} -ne 0 ]; then echo "缺少必要命令: ${missing[*]}" echo "请安装: apt-get update && apt-get install -y ${missing[*]}" exit 1 fi } # 函数:彻底清理 Docker 资源 cleanup_docker() { log_message "开始清理 Docker 资源..." # 停止并删除所有相关容器 docker stop nginx-proxy-manager 2>/dev/null || true docker rm nginx-proxy-manager 2>/dev/null || true # 删除 Docker Compose 项目 docker compose -p nginx down 2>/dev/null || true # 强制清理可能残留的容器 docker ps -a --filter name=nginx --format "{{.ID}}" | xargs -r docker rm -f 2>/dev/null || true # 清理网络 docker network prune -f # 清理未使用的镜像 docker image prune -af 2>/dev/null || true log_message "Docker 资源清理完成" } # 函数:更准确的端口检查 check_port_advanced() { local port=$1 local protocol=${2:-tcp} log_message "深度检查端口 $port/$protocol ..." # 检查端口是否在有效范围内 if [ "$port" -lt 1 ] || [ "$port" -gt 65535 ]; then echo "错误: 端口 $port 不在有效范围内 (1-65535)" return 2 fi local is_occupied=0 # 方法1: netstat if netstat -tulpn 2>/dev/null | grep -q ":${port} "; then log_message "netstat 发现端口 $port 被占用" netstat -tulpn | grep ":${port} " | head -5 is_occupied=1 fi # 方法2: ss if ss -tulpn 2>/dev/null | grep -q ":${port} "; then log_message "ss 发现端口 $port 被占用" ss -tulpn | grep ":${port} " | head -5 is_occupied=1 fi # 方法3: lsof if lsof -i :${port} 2>/dev/null | grep -q "LISTEN"; then log_message "lsof 发现端口 $port 被占用" lsof -i :${port} | head -5 is_occupied=1 fi # 方法4: 检查 Docker 容器映射 if docker ps --format "table {{.Names}}\t{{.Ports}}" 2>/dev/null | grep -q ":${port}->"; then log_message "Docker 容器正在使用端口 $port" docker ps --format "table {{.Names}}\t{{.Ports}}" | grep ":${port}->" is_occupied=1 fi # 方法5: 使用 /proc 检查 if [ -d /proc ]; then for pid in /proc/[0-9]*; do if [ -d "$pid" ] && ls -la "$pid/fd" 2>/dev/null | grep -q "socket:.*:${port}"; then local process_pid=$(basename "$pid") local process_name=$(ps -p "$process_pid" -o comm= 2>/dev/null) log_message "进程 $process_name (PID: $process_pid) 正在使用端口 $port" is_occupied=1 fi done fi if [ $is_occupied -eq 0 ]; then log_message "端口 $port 可用" return 0 else return 1 fi } # 函数:检查端口可用性 check_ports_availability() { local http_port=$1 local admin_port=$2 local https_port=$3 local all_available=0 log_message "检查端口可用性: HTTP=$http_port, Admin=$admin_port, HTTPS=$https_port" check_port_advanced $http_port local http_available=$? check_port_advanced $admin_port local admin_available=$? check_port_advanced $https_port local https_available=$? if [ $http_available -ne 0 ] || [ $admin_available -ne 0 ] || [ $https_available -ne 0 ]; then return 1 fi return 0 } # 函数:使用备用端口部署 deploy_with_alternate_ports() { local http_port="8080" local admin_port="8081" local https_port="8443" # 检查备用端口是否可用 if ! check_ports_availability $http_port $admin_port $https_port; then log_message "备用端口也被占用,尝试自动寻找可用端口..." # 自动寻找可用端口 for base_port in 8080 8082 8084 8880 8888; do http_port=$((base_port)) admin_port=$((base_port + 1)) https_port=$((base_port + 2)) if check_ports_availability $http_port $admin_port $https_port; then log_message "找到可用端口: HTTP=$http_port, Admin=$admin_port, HTTPS=$https_port" break fi done fi log_message "使用端口部署: HTTP=$http_port, 管理界面=$admin_port, HTTPS=$https_port" create_compose_file $http_port $admin_port $https_port deploy_service $http_port $admin_port $https_port } # 函数:创建 Compose 文件 create_compose_file() { local http_port=$1 local admin_port=$2 local https_port=$3 cat > "$COMPOSE_FILE" << EOF # Nginx Proxy Manager 配置 services: app: image: 'docker.io/jc21/nginx-proxy-manager:latest' container_name: nginx-proxy-manager restart: unless-stopped ports: - '${http_port}:80' - '${admin_port}:81' - '${https_port}:443' volumes: - ./data:/data - ./letsencrypt:/etc/letsencrypt environment: - DISABLE_IPV6=false networks: - npm-network networks: npm-network: driver: bridge EOF log_message "创建 Docker Compose 文件: $COMPOSE_FILE" } # 函数:部署服务 deploy_service() { local http_port=$1 local admin_port=$2 local https_port=$3 # 切换到脚本目录 cd "$SCRIPT_DIR" || { log_message "错误: 无法切换到目录 $SCRIPT_DIR" return 1 } # 检查 Docker 是否运行 if ! systemctl is-active --quiet docker; then log_message "启动 Docker 服务..." systemctl start docker sleep 5 # 再次检查 if ! systemctl is-active --quiet docker; then log_message "错误: Docker 服务启动失败" return 1 fi fi # 拉取最新镜像 log_message "拉取 Docker 镜像..." docker pull jc21/nginx-proxy-manager:latest || { log_message "警告: 镜像拉取失败,尝试使用本地镜像" } # 部署服务 log_message "启动 Nginx Proxy Manager..." if docker compose -p nginx -f "$COMPOSE_FILE" up -d; then log_message "等待服务启动..." # 等待服务完全启动 local max_attempts=30 local attempt=1 while [ $attempt -le $max_attempts ]; do if docker ps --filter name=nginx-proxy-manager --filter status=running --format "{{.Names}}" | grep -q nginx-proxy-manager; then if curl -s http://localhost:${admin_port} > /dev/null; then break fi fi log_message "等待服务启动... ($attempt/$max_attempts)" sleep 2 ((attempt++)) done if [ $attempt -gt $max_attempts ]; then log_message "警告: 服务启动超时,但容器已运行" fi show_success_message $http_port $admin_port $https_port return 0 else log_message "错误: 容器启动失败" log_message "检查 Docker 日志:docker logs nginx-proxy-manager" return 1 fi } # 显示成功信息 show_success_message() { local http_port=$1 local admin_port=$2 local https_port=$3 SERVER_IP=$(hostname -I | awk '{print $1}') echo "==================================================" echo "✅ Nginx Proxy Manager 部署成功!" echo "管理界面: http://${SERVER_IP}:${admin_port}" echo "默认账号: admin@example.com" echo "默认密码: changeme" echo "" echo "HTTP 端口: ${http_port}" echo "HTTPS 端口: ${https_port}" echo "" echo "常用命令:" echo "查看日志: docker logs nginx-proxy-manager" echo "停止服务: docker compose -p nginx -f $COMPOSE_FILE down" echo "重启服务: docker compose -p nginx -f $COMPOSE_FILE restart" echo "==================================================" log_message "部署完成 - 管理界面: http://${SERVER_IP}:${admin_port}" } # 显示系统信息 show_system_info() { echo "=== 系统信息 ===" echo "主机名: $(hostname)" echo "IP 地址: $(hostname -I | awk '{print $1}')" echo "操作系统: $(grep PRETTY_NAME /etc/os-release | cut -d= -f2 | tr -d '\"')" echo "内核版本: $(uname -r)" echo "Docker 版本: $(docker --version 2>/dev/null | cut -d' ' -f3 | tr -d ',')" echo "" } # 主程序 main() { log_message "开始 Nginx Proxy Manager 部署流程" show_system_info check_requirements log_message "创建必要的目录..." mkdir -p "$DATA_DIR" mkdir -p "$SCRIPT_DIR" mkdir -p "$(dirname "$LOG_FILE")" # 显示当前状态 log_message "检查系统状态..." check_port_advanced 80 check_port_advanced 81 check_port_advanced 443 echo "" log_message "检查现有 Docker 容器..." docker ps -a --filter name=nginx | grep nginx && echo "发现现有 nginx 容器" || echo "未发现 nginx 相关容器" echo "" echo "请选择部署方案:" echo "1) 彻底清理后使用默认端口重试" echo "2) 直接使用备用端口 (8080, 8081, 8443) - 推荐" echo "3) 手动指定端口" echo "4) 仅清理不部署" echo "5) 退出" read -p "请输入选择 (1-5): " choice case $choice in 1) log_message "用户选择: 彻底清理后使用默认端口" cleanup_docker sleep 3 if check_ports_availability 80 81 443; then create_compose_file 80 81 443 deploy_service 80 81 443 else log_message "默认端口不可用,自动切换到备用端口" deploy_with_alternate_ports fi ;; 2) log_message "用户选择: 使用备用端口" cleanup_docker deploy_with_alternate_ports ;; 3) log_message "用户选择: 手动指定端口" echo "请手动指定端口:" read -p "HTTP 端口 (推荐 8080): " custom_http read -p "管理界面端口 (推荐 8081): " custom_admin read -p "HTTPS 端口 (推荐 8443): " custom_https HTTP_PORT=${custom_http:-8080} ADMIN_PORT=${custom_admin:-8081} HTTPS_PORT=${custom_https:-8443} # 验证端口输入 if ! [[ "$HTTP_PORT" =~ ^[0-9]+$ ]] || ! [[ "$ADMIN_PORT" =~ ^[0-9]+$ ]] || ! [[ "$HTTPS_PORT" =~ ^[0-9]+$ ]]; then echo "错误: 端口必须是数字" exit 1 fi cleanup_docker if check_ports_availability $HTTP_PORT $ADMIN_PORT $HTTPS_PORT; then create_compose_file $HTTP_PORT $ADMIN_PORT $HTTPS_PORT deploy_service $HTTP_PORT $ADMIN_PORT $HTTPS_PORT else log_message "指定端口不可用,请重新选择" exit 1 fi ;; 4) log_message "用户选择: 仅清理不部署" cleanup_docker echo "✅ 清理完成" ;; 5) log_message "用户选择: 退出脚本" exit 0 ;; *) log_message "无效选择,使用备用端口" cleanup_docker deploy_with_alternate_ports ;; esac log_message "部署流程结束" } # 执行主程序 main "$@"