#!/bin/bash # ============================================================ # Nginx Proxy Manager 一键管理脚本 v1 # 子命令: install(默认向导) / uninstall / status / update / backup # 用法: # bash npm.sh → 智能入口(已装→菜单, 未装→安装) # bash npm.sh install → 安装向导 # bash npm.sh status → 状态 # bash npm.sh update → 更新镜像并重启 # bash npm.sh backup → 备份数据 # bash npm.sh uninstall → 卸载 # ============================================================ RED='\[;31m'; GREEN='\[;32m'; YELLOW='\[;33m'; BLUE='\[;34m'; NC='\[0m' info(){ echo -e "${GREEN}[✓]${NC} $"; } warn(){ echo -e "${YELLOW}[!]${NC} $"; } err(){ echo -e "${RED}[✗]${NC} $"; } NPM_DIR="/opt/npm" COMPOSE="$NPM_DIR/docker-compose.yml" # ============================ 检测 ============================ need_docker(){ ! command -v docker >/dev/null >&; then warn "未安装 Docker, 开始安装..." curl -fsSL https://get.docker.com | bash >& | tail - systemctl enable --now docker >/dev/null fi # docker compose 插件 docker compose version >/dev/null >& && warn "安装 docker compose 插件..." mkdir -p /usr/local/lib/docker/cli-plugins [ -f /usr/lib/docker/cli-plugins/docker-compose ]; then ln -sf /usr/lib/docker/cli-plugins/docker-compose /usr/local/lib/docker/cli-plugins/docker-compose curl -sL -m -o /usr/local/lib/docker/cli-plugins/docker-compose \ "https://ghproxy.net/https://github.com/docker/compose/releases/latest/download/docker-compose-linux-x86_64" \ && chmod +x /usr/local/lib/docker/cli-plugins/docker-compose fi docker compose version >/dev/null >& || { err "docker compose 不可用"; exit ; } } is_installed(){ [ -f "$COMPOSE" ] && docker ps --format '{{.Names}}' >/dev/null | grep -q npm } # ============================ 状态 ============================ do_status(){ echo -e "${BLUE}========== NPM 状态 ==========${NC}" is_installed; then ST=$(docker inspect -f '{{.State.Status}}' npm >/dev/null) echo -e " ${GREEN}●${NC} 容器 npm: $ST" docker ps --format 'table {{.Names}}\t{{.Ports}}\t{{.Status}}' | head - UPTIME=$(docker inspect -f '{{.State.StartedAt}}' npm >/dev/null | cut -c1- | tr T ' ') echo " 启动时间: $UPTIME" warn "NPM 未安装或未运行" fi exit } # ============================ 备份 ============================ do_backup(){ [ -d "$NPM_DIR/data" ] || { err "无数据可备份"; exit ; } TS=$(date +%Y%m%d%H%M%S) BK="/root/npm-backup-$TS.tar.gz" tar -czf "$BK" -C "$NPM_DIR" data letsencrypt >/dev/null info "备份完成: $BK ($(du -h "$BK" | cut -f1))" exit } # ============================ update ============================ do_update(){ echo -e "${BLUE}========== NPM 升级 ==========${NC}" cd "$NPM_DIR" info "拉取最新镜像..." docker compose pull >& | tail - info "重建容器(数据保留)..." docker compose up -d >& | tail - info "升级完成, 数据/证书/配置全部保留" exit } # ============================ 卸载 ============================ do_uninstall(){ echo -e "${BLUE}========== NPM 卸载 ==========${NC}" cd "$NPM_DIR" >/dev/null || { warn "未安装"; exit ; } read -p "是否保留数据(证书/配置/数据库)? [Y/n]: " KEEP docker compose down >& | tail - info "容器已停止移除" [[ "$KEEP" == "n" || "$KEEP" == "N" ]]; then rm -rf "$NPM_DIR" info "已删除 $NPM_DIR (数据一并清除)" warn "数据保留在 $NPM_DIR (重装自动恢复)" fi docker images | grep -E "jc21/nginx-proxy-manager" | awk '{ $}' | xargs -r docker rmi >/dev/null info "镜像已清理" exit } # ============================ 安装 ============================ do_install(){ echo -e "${BLUE}=============================================${NC}" echo -e "${BLUE} Nginx Proxy Manager 一键部署${NC}" echo -e "${BLUE}=============================================${NC}" # 依赖 need_docker # ---------- 镜像来源 ---------- echo "" echo "镜像来源:" echo " ) 在线拉取 (官方+镜像加速回退)" echo " ) 离线安装 (本地/自定义地址的 docker 镜像包, docker load)" read -p "选择 [/, 默认1]: " IMG_SRC IMG_SRC=${IMG_SRC:-} IMG_TAR="" [ "$IMG_SRC" = "" ]; then echo "离线包获取方式:" echo " a) 本地文件路径 (如 /root/npm.tar)" echo " b) 自定义下载地址 (http/https)" read -p "选择 [a/b]: " OFF_MODE case "$OFF_MODE" b|B) read -p "输入下载地址: " OFF_URL [ -z "$OFF_URL" ] && { err "地址为空"; exit ; } IMG_TAR="/tmp/npm-image.tar" info "下载镜像包..." DL_OK= curl -sL -m --connect-timeout -o "$IMG_TAR" "$OFF_URL" && [ -s "$IMG_TAR" ] && DL_OK= [ $DL_OK -eq ]; then warn "直连失败, 尝试镜像加速..." curl -sL -m -o "$IMG_TAR" "https://ghproxy.net/${OFF_URL}" && [ -s "$IMG_TAR" ] && DL_OK= fi [ $DL_OK -eq ] && { err "下载失败, 请检查地址"; exit ; } ;; *) read -p "输入本地镜像包路径: " OFF_PATH [ ! -f "$OFF_PATH" ] && { err "文件不存在: $OFF_PATH"; exit ; } IMG_TAR="$(readlink -f "$OFF_PATH")" ;; esac # 校验是 docker 镜像包 ! docker load -i "$IMG_TAR" >& | tail -; then err "docker load 失败: 文件不是有效的 docker 镜像包" exit fi info "镜像已导入" fi # 端口输入(带冲突检测) read -p "管理界面端口 [默认 ]: " ADMIN_PORT ADMIN_PORT=${ADMIN_PORT:-} read -p "HTTP 端口 [默认 ]: " HTTP_PORT HTTP_PORT=${HTTP_PORT:-} read -p "HTTPS 端口 [默认 ]: " HTTPS_PORT HTTPS_PORT=${HTTPS_PORT:-} p "$ADMIN_PORT" "$HTTP_PORT" "$HTTPS_PORT"; do ss -tln | grep -q ":${p} "; then err "端口 $p 已被占用:" ss -tlnp | grep ":${p} " | head - read -p "换一个端口? [y/N]: " CH [[ "$CH" == "y" || "$CH" == "Y" ]] || exit fi done # 确认本地导入的镜像名, 并打标准 tag [ "$IMG_SRC" = "" ]; then LOADED=$(docker images --format '{{.Repository}}:{{.Tag}}' | grep -iE "nginx-proxy-manager" | head -) [ -z "$LOADED" ] && LOADED=$(docker images --format '{{.Repository}}:{{.Tag}}' | grep -iE "npm|proxy" | head -) [ -n "$LOADED" ] && [ "$LOADED" != "jc21/nginx-proxy-manager:latest" ]; then docker tag "$LOADED" "jc21/nginx-proxy-manager:latest" info "已打标: $LOADED → jc21/nginx-proxy-manager:latest" fi [ -z "$LOADED" ] && { err "导入后未找到 NPM 镜像, 请确认包内容"; exit ; } fi mkdir -p "$NPM_DIR/data" "$NPM_DIR/letsencrypt" cat > "$COMPOSE" <<EOF services: app: image: jc21/nginx-proxy-manager:latest container_name: npm restart: unless-stopped ports: - "${HTTP_PORT}:" - "${HTTPS_PORT}:" - "${ADMIN_PORT}:" environment: DISABLE_IPV6: "true" volumes: - ${NPM_DIR}/data:/data - ${NPM_DIR}/letsencrypt:/etc/letsencrypt EOF [ "$IMG_SRC" = "" ]; then echo "" info "拉取镜像 (约 500MB, 视网络 - 分钟)..." cd "$NPM_DIR" PULL_OK= # 尝试直连, 失败换国内镜像 docker compose pull >/dev/null >& && PULL_OK= [ $PULL_OK -eq ]; then warn "直连失败, 尝试国内镜像加速..." mkdir -p /etc/docker [ ! -f /etc/docker/daemon.json ]; then echo '{"registry-mirrors":["https://dockerproxy.net","https://docker.m.daocloud.io"]}' > /etc/docker/daemon.json systemctl restart docker; sleep fi docker compose pull >/dev/null >& && PULL_OK= [ $PULL_OK -eq ]; then # 手动逐源拉取再打标 src "dockerproxy.net/jc21/nginx-proxy-manager:latest" "docker.m.daocloud.io/jc21/nginx-proxy-manager:latest"; do docker pull "$src" && docker tag "$src" "jc21/nginx-proxy-manager:latest" && PULL_OK= && done fi fi [ $PULL_OK -eq ] && { err "镜像拉取失败, 检查网络后重跑本脚本(或选离线安装)"; exit ; } info "离线模式: 使用已导入镜像, 跳过拉取" fi cd "$NPM_DIR" docker compose up -d sleep ST=$(docker inspect -f '{{.State.Status}}' npm >/dev/null) echo "" info "=========================================" info " NPM 部署完成! ($ST)" info " 管理界面: http://本机IP:${ADMIN_PORT}" info " 默认账号: admin@example.com / changeme" info " (首次登录强制修改, 记得改!)" info " HTTP:${HTTP_PORT} / HTTPS:${HTTPS_PORT}" info " 数据目录: $NPM_DIR (证书/配置持久化)" info " 管理: bash /usr/local/bin/npm.sh status | update | backup | uninstall" # 自落地: 把脚本保存到固定路径, 方便以后管理 [[ "$" == /dev/fd* || "$" == *curl* ]] || [ ! -f /usr/local/bin/npm.sh ]; then cp "$" /usr/local/bin/npm.sh >/dev/null && chmod +x /usr/local/bin/npm.sh \ && info "脚本已落地: /usr/local/bin/npm.sh (以后直接 bash npm.sh 管理)" fi info "=========================================" exit } # ============================ 菜单 ============================ do_menu(){ true; do echo "" echo -e "${BLUE}=============================================${NC}" echo -e "${BLUE} Nginx Proxy Manager 管理菜单${NC}" echo -e "${BLUE}=============================================${NC}" is_installed; then ST=$(docker inspect -f '{{.State.Status}}' npm >/dev/null) echo -e " ${GREEN}●${NC} 容器: $ST" fi echo -e "${BLUE}---------------------------------------------${NC}" echo " ) 安装" echo " ) 启动" echo " ) 停止" echo " ) 重启" echo " ) 状态详情" echo " ) 升级 (拉最新镜像)" echo " ) 备份数据" echo " ) 查看日志 (最近30行)" echo " ) 卸载" echo " ) 退出" read -p "选择 [-]: " C case "$C" ) do_install ;; ) cd "$NPM_DIR" && docker compose start >/dev/null && info "已启动" ;; ) cd "$NPM_DIR" && docker compose stop >/dev/null && info "已停止" ;; ) cd "$NPM_DIR" && docker compose restart >/dev/null && info "已重启" ;; ) do_status ;; ) do_update ;; ) do_backup ;; ) docker logs npm --tail >/dev/null | less -R || warn "未运行" ;; ) do_uninstall ;; ) exit ;; *) warn "无效选择" ;; esac read -p "按回车返回菜单..." _ done } # ============================ 主入口 ============================ case "$" install) do_install ;; status) do_status ;; update) do_update ;; backup) do_backup ;; uninstall) do_uninstall ;; menu) do_menu ;; "") do_menu ;; -h|--help) echo "用法: bash npm.sh [install|menu|status|update|backup|uninstall]" exit ;; *) err "未知命令: $"; exit ;; esac