#!/usr/bin/env bash # XuqmGroup 私有化部署 — 一键安装脚本(已部署环境重新执行将自动升级) # # 首次部署: # curl -fsSL https://xuqinmin.com/xuqmGroup/XuqmGroup-PrivateDeploy/raw/branch/main/install.sh \ # -o install.sh && bash install.sh # # 升级已有部署(推荐使用专用升级脚本): # curl -fsSL https://xuqinmin.com/xuqmGroup/XuqmGroup-PrivateDeploy/raw/branch/main/upgrade.sh \ # -o upgrade.sh && bash upgrade.sh # # 或直接执行(bash <(curl ...) 形式保留终端交互): # bash <(curl -fsSL https://xuqinmin.com/xuqmGroup/XuqmGroup-PrivateDeploy/raw/branch/main/install.sh) # # 环境变量(可选): # DEPLOY_HOST 目标机器 IP / 主机名(默认自动检测本机 IP) # INSTALL_DIR 安装目录(默认 /opt/xuqm-private) # XUQM_BRANCH Gitea 分支(默认 main) set -euo pipefail # --------------------------------------------------------------------------- # 若 stdin 不是终端(curl | bash),强制从 /dev/tty 读取用户输入 # --------------------------------------------------------------------------- if [ ! -t 0 ]; then exec bash "$0" "$@" &2; exit 1; } # --------------------------------------------------------------------------- # Banner # --------------------------------------------------------------------------- printf '\n%b══════════════════════════════════════════════════%b\n' "$CYAN" "$RESET" printf '%b XuqmGroup 私有化部署 — 一键安装向导%b\n' "$BOLD" "$RESET" printf '%b══════════════════════════════════════════════════%b\n\n' "$CYAN" "$RESET" # --------------------------------------------------------------------------- # 检测目标主机 IP # --------------------------------------------------------------------------- if [ -z "$DEPLOY_HOST" ]; then DEPLOY_HOST="$(hostname -I 2>/dev/null | awk '{print $1}' || \ ip route get 1 2>/dev/null | awk '{print $7}' | head -1 || \ echo "127.0.0.1")" fi printf ' 安装目录: %b%s%b\n' "$BOLD" "$INSTALL_DIR" "$RESET" printf ' 目标主机: %b%s%b\n' "$BOLD" "$DEPLOY_HOST" "$RESET" printf ' 部署分支: %s\n\n' "$XUQM_BRANCH" # --------------------------------------------------------------------------- # Step 1 — 检测操作系统 # --------------------------------------------------------------------------- if command -v apt-get >/dev/null 2>&1; then PKG_MGR="apt" apt-get update -qq 2>/dev/null || true elif command -v yum >/dev/null 2>&1; then PKG_MGR="yum" else fail "不支持的操作系统(需要 apt 或 yum)" fi ok "包管理器: $PKG_MGR" pkg_install() { if [ "$PKG_MGR" = "apt" ]; then apt-get install -y -qq "$@" 2>/dev/null else yum install -y -q "$@" 2>/dev/null fi } # --------------------------------------------------------------------------- # Step 2 — 安装 Docker # --------------------------------------------------------------------------- if ! command -v docker >/dev/null 2>&1; then info "Docker 未安装,正在安装..." if [ "$PKG_MGR" = "apt" ]; then pkg_install ca-certificates curl gnupg lsb-release install -m 0755 -d /etc/apt/keyrings curl -fsSL https://download.docker.com/linux/ubuntu/gpg \ | gpg --dearmor -o /etc/apt/keyrings/docker.gpg 2>/dev/null chmod a+r /etc/apt/keyrings/docker.gpg echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] \ https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" \ > /etc/apt/sources.list.d/docker.list apt-get update -qq pkg_install docker-ce docker-ce-cli containerd.io docker-compose-plugin else pkg_install yum-utils yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo 2>/dev/null pkg_install docker-ce docker-ce-cli containerd.io docker-compose-plugin systemctl enable --now docker 2>/dev/null || true fi fi docker info >/dev/null 2>&1 || { systemctl start docker 2>/dev/null || true; sleep 2; } docker info >/dev/null 2>&1 || fail "Docker daemon 未运行,请执行: systemctl start docker" ok "Docker $(docker --version | grep -o '[0-9]*\.[0-9]*\.[0-9]*' | head -1)" # --------------------------------------------------------------------------- # Step 3 — 确认 Docker Compose v2 # --------------------------------------------------------------------------- if ! docker compose version >/dev/null 2>&1; then info "Docker Compose v2 未安装,正在安装..." if [ "$PKG_MGR" = "apt" ]; then pkg_install docker-compose-plugin else COMPOSE_VER="v2.27.0" mkdir -p /usr/local/lib/docker/cli-plugins curl -fsSL \ "https://github.com/docker/compose/releases/download/${COMPOSE_VER}/docker-compose-$(uname -s)-$(uname -m)" \ -o /usr/local/lib/docker/cli-plugins/docker-compose chmod +x /usr/local/lib/docker/cli-plugins/docker-compose fi fi ok "Docker Compose $(docker compose version --short 2>/dev/null || echo 'v2')" # --------------------------------------------------------------------------- # Step 4 — 安装 python3 + bcrypt(新建租户密码哈希) # --------------------------------------------------------------------------- if ! command -v python3 >/dev/null 2>&1; then info "python3 未安装,正在安装..." pkg_install python3 fi if ! python3 -c "import bcrypt" 2>/dev/null; then info "python3-bcrypt 未安装,正在安装..." if [ "$PKG_MGR" = "apt" ]; then pkg_install python3-bcrypt 2>/dev/null || python3 -m pip install -q bcrypt else python3 -m pip install -q bcrypt fi fi python3 -c "import bcrypt" 2>/dev/null || fail "python3-bcrypt 安装失败,请手动执行: pip3 install bcrypt" ok "python3 + bcrypt" # --------------------------------------------------------------------------- # Step 5 — 安装 mysql 客户端(迁移模式需要) # --------------------------------------------------------------------------- if ! command -v mysql >/dev/null 2>&1; then info "mysql 客户端未安装,正在安装(迁移租户模式需要)..." if [ "$PKG_MGR" = "apt" ]; then pkg_install mysql-client 2>/dev/null || pkg_install default-mysql-client 2>/dev/null || \ warn "mysql 客户端安装失败,新建租户模式仍可用" else pkg_install mysql 2>/dev/null || warn "mysql 客户端安装失败,新建租户模式仍可用" fi fi command -v mysql >/dev/null 2>&1 && ok "mysql 客户端" || warn "mysql 客户端不可用(仅影响迁移模式)" # --------------------------------------------------------------------------- # Step 6 — 下载部署包 # --------------------------------------------------------------------------- printf '\n' info "下载部署包 ${ARCHIVE_URL} ..." TMP_PKG="$(mktemp /tmp/xuqm-deploy-XXXXXX.tar.gz)" _BACKUP_DIR="$(mktemp -d /tmp/xuqm-backup-XXXXXX)" trap 'rm -f "$TMP_PKG"; rm -rf "$_BACKUP_DIR"' EXIT curl -fsSL --progress-bar "$ARCHIVE_URL" -o "$TMP_PKG" \ || fail "部署包下载失败,请检查网络或 Gitea 是否可达: $ARCHIVE_URL" # --------------------------------------------------------------------------- # Step 7 — 解压到安装目录(更新时保留关键配置) # --------------------------------------------------------------------------- # 检测是否为已有部署 _IS_UPDATE=0 [ -f "$INSTALL_DIR/.env" ] && [ -f "$INSTALL_DIR/config/secrets.env" ] && _IS_UPDATE=1 # 备份关键配置,防止 tarball 中同名文件意外覆盖 if [ "$_IS_UPDATE" -eq 1 ]; then for _cf in .env config/secrets.env config/xuqm.env; do [ -f "$INSTALL_DIR/$_cf" ] && \ cp "$INSTALL_DIR/$_cf" "$_BACKUP_DIR/$(basename "$_cf").bak" done fi mkdir -p "$INSTALL_DIR" tar -xzf "$TMP_PKG" -C "$INSTALL_DIR" --strip-components=1 --exclude='*/data' chmod +x "$INSTALL_DIR/scripts/"*.sh "$INSTALL_DIR/install.sh" 2>/dev/null || true ok "部署包解压完成: $INSTALL_DIR" # 恢复关键配置文件(确保现有配置不被模板覆盖) if [ "$_IS_UPDATE" -eq 1 ]; then for _cf in .env config/secrets.env config/xuqm.env; do _bak="$_BACKUP_DIR/$(basename "$_cf").bak" [ -f "$_bak" ] && cp "$_bak" "$INSTALL_DIR/$_cf" done ok "已恢复现有配置文件" fi # --------------------------------------------------------------------------- # Step 8 — 路由到更新或全量安装 # --------------------------------------------------------------------------- export DEPLOY_HOST cd "$INSTALL_DIR" if [ "$_IS_UPDATE" -eq 1 ]; then printf '\n' printf '%b 检测到已有部署(%s/.env 存在)%b\n' "$YELLOW" "$INSTALL_DIR" "$RESET" printf ' 请选择操作:\n\n' printf ' %b1%b 仅更新 — 修复配置问题,可选拉取新镜像,重启容器(保留全部数据)\n' "$BOLD" "$RESET" printf ' %b2%b 全量重部署 — 重新运行完整安装向导(会覆盖现有配置,谨慎使用)\n' "$BOLD" "$RESET" printf ' %b3%b 退出\n\n' "$BOLD" "$RESET" read -rp " 请输入 [1/2/3](回车默认 1): " _choice _choice="${_choice:-1}" case "$_choice" in 2) printf '\n%b → 进入全量部署向导 ...%b\n\n' "$GREEN" "$RESET" exec bash scripts/deploy.sh ;; 3) printf ' 已退出\n' exit 0 ;; *) printf '\n%b → 进入更新流程 ...%b\n\n' "$GREEN" "$RESET" exec bash scripts/update.sh ;; esac else printf '\n%b 依赖安装完毕,即将进入交互式部署向导 ...%b\n\n' "$GREEN" "$RESET" exec bash scripts/deploy.sh fi