#!/usr/bin/env bash # crypto_key 部署管理器 — 一键部署 / 更新 / 卸载 # # 新服务器(免预先克隆): # curl -fsSL https://git.bz121.com/dekun/crypto_key/raw/branch/main/deploy/manage.sh | bash # # 已安装: # bash /opt/crypto_key/deploy/manage.sh # # 更新一律 git pull,严禁 scp。 # set -e if [ -n "${BASH_VERSION:-}" ]; then set -o pipefail fi INSTALL_ROOT="${INSTALL_ROOT:-/opt/crypto_key}" GIT_URL="${GIT_URL:-https://git.bz121.com/dekun/crypto_key.git}" GIT_BRANCH="${GIT_BRANCH:-main}" APP_NAME="api-key-manager" BACKUP_ROOT="/root/crypto_key_backups" CRON_MARKER="# crypto_key-auto-backup" # curl | bash 时脚本从 stdin 执行,BASH_SOURCE[0] 为空;须先安全探测再 set -u _script_src="${BASH_SOURCE[0]:-}" _from_pipe=0 if [[ -n "${_script_src}" && -f "${_script_src}" ]]; then DEPLOY_DIR="$(cd "$(dirname "${_script_src}")" && pwd)" REPO_ROOT="$(cd "${DEPLOY_DIR}/.." && pwd)" else DEPLOY_DIR="" REPO_ROOT="" _from_pipe=1 fi unset _script_src set -u red() { printf '\033[31m%s\033[0m\n' "$*"; } grn() { printf '\033[32m%s\033[0m\n' "$*"; } ylw() { printf '\033[33m%s\033[0m\n' "$*"; } info() { echo "==> $*"; } # 从终端读入,兼容 curl | bash(管道 stdin 已 EOF) cm_read() { local __var="$1" local __prompt="${2:-}" local __val="" if [[ -r /dev/tty ]]; then printf "%s" "${__prompt}" > /dev/tty IFS= read -r __val < /dev/tty || true else printf "%s" "${__prompt}" IFS= read -r __val || true fi printf -v "${__var}" '%s' "${__val}" } require_root() { if [[ "$(id -u)" -ne 0 ]]; then red "错误: 请使用 root 执行" echo " curl -fsSL https://git.bz121.com/dekun/crypto_key/raw/branch/main/deploy/manage.sh | bash" exit 1 fi } repo_ready() { [[ -f "${1}/deploy/manage.sh" && -f "${1}/app.py" ]] } sync_repo_if_present() { local root="$1" if [[ -d "${root}/.git" ]] && command -v git >/dev/null 2>&1; then info "git pull(${root})..." git -C "${root}" fetch --all --prune 2>/dev/null || true git -C "${root}" pull --ff-only origin "${GIT_BRANCH}" 2>/dev/null \ || git -C "${root}" pull --ff-only 2>/dev/null \ || true fi } reexec_local() { exec bash "${INSTALL_ROOT}/deploy/manage.sh" "$@" /dev/null 2>&1; then if command -v apt-get >/dev/null 2>&1; then export DEBIAN_FRONTEND=noninteractive apt-get update -qq apt-get install -y git ca-certificates curl else red "错误: 未找到 git" exit 1 fi fi if [[ -d "${INSTALL_ROOT}" ]]; then red "错误: ${INSTALL_ROOT} 已存在但不是有效仓库" echo "请手动处理后再运行,或设置 INSTALL_ROOT 指向其它路径" exit 1 fi mkdir -p "$(dirname "${INSTALL_ROOT}")" git clone -b "${GIT_BRANCH}" "${GIT_URL}" "${INSTALL_ROOT}" # clone 后用仓库内脚本 + 终端 stdin(管道已 EOF) reexec_local "$@" } install_deps() { info "安装系统依赖..." export DEBIAN_FRONTEND=noninteractive apt-get update -qq apt-get install -y -qq python3 python3-venv python3-pip git curl ca-certificates if ! command -v node >/dev/null 2>&1; then info "安装 Node.js 20(PM2 需要)..." curl -fsSL https://deb.nodesource.com/setup_20.x | bash - apt-get install -y -qq nodejs fi if ! command -v pm2 >/dev/null 2>&1; then info "安装 PM2..." npm install -g pm2 fi } setup_python() { info "创建/更新 Python 虚拟环境..." cd "${INSTALL_ROOT}" python3 -m venv venv # shellcheck disable=SC1091 source venv/bin/activate pip install -q --upgrade pip pip install -q -r requirements.txt mkdir -p logs uploads chmod +x pm2-start.sh pm2-stop.sh deploy/manage.sh 2>/dev/null || true } setup_env() { cd "${INSTALL_ROOT}" if [[ ! -f .env ]]; then if [[ -f .env.example ]]; then cp .env.example .env ylw "已从 .env.example 生成 .env(首次启动会自动补全密钥)" else touch .env fi fi } setup_backup_cron() { mkdir -p "${BACKUP_ROOT}" local cron_line="0 3 * * * cd ${INSTALL_ROOT} && ./venv/bin/python -c \"from backup import create_backup_archive; create_backup_archive()\" >> ${BACKUP_ROOT}/cron.log 2>&1 ${CRON_MARKER}" (crontab -l 2>/dev/null | grep -v "${CRON_MARKER}" || true; echo "${cron_line}") | crontab - info "已配置每日 03:00 自动备份到 ${BACKUP_ROOT}" } remove_backup_cron() { if crontab -l 2>/dev/null | grep -q "${CRON_MARKER}"; then crontab -l 2>/dev/null | grep -v "${CRON_MARKER}" | crontab - || true info "已移除自动备份 cron" fi } start_pm2() { cd "${INSTALL_ROOT}" pm2 delete "${APP_NAME}" 2>/dev/null || true pm2 start ecosystem.config.cjs pm2 save } do_deploy() { require_root info "一键部署到 ${INSTALL_ROOT}" install_deps mkdir -p /opt if [[ -d "${INSTALL_ROOT}/.git" ]]; then info "目录已存在,执行 git pull(不用 scp)..." sync_repo_if_present "${INSTALL_ROOT}" else info "git clone ${GIT_URL} ..." git clone -b "${GIT_BRANCH}" "${GIT_URL}" "${INSTALL_ROOT}" fi setup_env setup_python setup_backup_cron start_pm2 local lan_ip lan_ip="$(hostname -I 2>/dev/null | awk '{print $1}')" echo "" grn "========== 部署完成 ==========" echo " 目录: ${INSTALL_ROOT}" echo " 本机: http://127.0.0.1:5200" echo " 局域网: http://${lan_ip:-}:5200" echo " 自动备份: ${BACKUP_ROOT}" echo " 状态: pm2 status" echo " 日志: pm2 logs ${APP_NAME}" echo "==============================" echo "" echo "日常管理: bash ${INSTALL_ROOT}/deploy/manage.sh" echo "或: curl -fsSL https://git.bz121.com/dekun/crypto_key/raw/branch/main/deploy/manage.sh | bash" echo "开机自启: pm2 startup (按提示执行后再 pm2 save)" } do_update() { require_root if [[ ! -d "${INSTALL_ROOT}/.git" ]]; then red "未找到 ${INSTALL_ROOT},请先选择「1 一键部署」" exit 1 fi info "更新(仅 git pull,严禁 scp)..." cd "${INSTALL_ROOT}" mkdir -p "${BACKUP_ROOT}" if [[ -f "${INSTALL_ROOT}/venv/bin/python" ]]; then "${INSTALL_ROOT}/venv/bin/python" -c "from backup import create_backup_archive; print(create_backup_archive())" \ && grn "更新前已自动备份" || ylw "更新前备份跳过(可手动备份)" fi sync_repo_if_present "${INSTALL_ROOT}" setup_python setup_backup_cron start_pm2 grn "更新完成(git pull + 依赖 + PM2 重启)" } do_uninstall() { require_root ylw "即将卸载 crypto_key" echo " - 停止并删除 PM2 进程 ${APP_NAME}" echo " - 移除自动备份 cron" echo " - 删除目录 ${INSTALL_ROOT}" echo " - 保留 ${BACKUP_ROOT}(如存在)" local confirm="" cm_read confirm "确认卸载?输入 yes 继续: " if [[ "${confirm}" != "yes" ]]; then echo "已取消" return 0 fi pm2 delete "${APP_NAME}" 2>/dev/null || true pm2 save 2>/dev/null || true remove_backup_cron if [[ -d "${INSTALL_ROOT}" ]]; then mkdir -p "${BACKUP_ROOT}" local stamp stamp="$(date +%Y%m%d_%H%M%S)" tar czf "${BACKUP_ROOT}/uninstall_backup_${stamp}.tar.gz" \ -C "${INSTALL_ROOT}" .env data.json settings.json uploads 2>/dev/null || true rm -rf "${INSTALL_ROOT}" grn "已删除 ${INSTALL_ROOT}" fi grn "卸载完成。备份目录保留在: ${BACKUP_ROOT}" } show_menu() { echo "" echo "========================================" echo " crypto_key 管理脚本" echo " 仓库: ${GIT_URL}" echo " 部署: ${INSTALL_ROOT}" echo "========================================" echo " 1) 一键部署" echo " 2) 更新(git pull,严禁 scp)" echo " 3) 一键卸载" echo " 0) 退出" echo "========================================" } main() { bootstrap_repo "$@" # 非交互参数 case "${1:-}" in deploy|1) do_deploy; exit 0 ;; update|2) do_update; exit 0 ;; uninstall|3) do_uninstall; exit 0 ;; esac require_root while true; do show_menu local choice="" cm_read choice "请选择 [0-3]: " case "${choice}" in 1) do_deploy ;; 2) do_update ;; 3) do_uninstall ;; 0) echo "再见"; exit 0 ;; *) red "无效选项" ;; esac echo "" cm_read _ "按 Enter 返回菜单..." done } main "$@"