#!/usr/bin/env bash # crypto_key 交互式管理脚本 # 用法: sudo bash scripts/manage.sh # 菜单: 1 一键部署 | 2 更新(git pull) | 3 一键卸载 # 严禁使用 scp;所有代码更新一律 git pull set -euo pipefail REPO_URL="https://git.bz121.com/dekun/crypto_key.git" INSTALL_DIR="/opt/crypto_key" APP_NAME="api-key-manager" BACKUP_ROOT="/root/crypto_key_backups" CRON_MARKER="# crypto_key-auto-backup" 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 "==> $*"; } require_root() { if [ "$(id -u)" -ne 0 ]; then red "请使用 root 或 sudo 运行: sudo bash scripts/manage.sh" exit 1 fi } detect_app_user() { echo "${SUDO_USER:-${USER:-root}}" } 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_DIR}" 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 scripts/manage.sh scripts/install-ubuntu.sh 2>/dev/null || true } setup_env() { cd "${INSTALL_DIR}" 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_DIR} && ./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() { local app_user app_user="$(detect_app_user)" cd "${INSTALL_DIR}" if [ -n "${app_user}" ] && [ "${app_user}" != "root" ]; then chown -R "${app_user}:${app_user}" "${INSTALL_DIR}" || true fi # 以调用用户身份跑 PM2(若可能) if [ -n "${app_user}" ] && [ "${app_user}" != "root" ] && id "${app_user}" >/dev/null 2>&1; then sudo -u "${app_user}" bash -c "cd '${INSTALL_DIR}' && pm2 delete '${APP_NAME}' 2>/dev/null || true; pm2 start ecosystem.config.cjs && pm2 save" else pm2 delete "${APP_NAME}" 2>/dev/null || true pm2 start ecosystem.config.cjs pm2 save fi } do_deploy() { info "一键部署到 ${INSTALL_DIR}" install_deps mkdir -p /opt if [ -d "${INSTALL_DIR}/.git" ]; then info "目录已存在,执行 git pull 更新代码(不用 scp)..." cd "${INSTALL_DIR}" git pull origin main 2>/dev/null || git pull origin master 2>/dev/null || git pull else info "git clone ${REPO_URL} ..." git clone "${REPO_URL}" "${INSTALL_DIR}" 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_DIR}" 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 "开机自启: pm2 startup (按提示执行 sudo 命令后再 pm2 save)" } do_update() { if [ ! -d "${INSTALL_DIR}/.git" ]; then red "未找到 ${INSTALL_DIR},请先选择「1 一键部署」" exit 1 fi info "更新(仅 git pull,严禁 scp)..." cd "${INSTALL_DIR}" # 更新前备份关键数据 mkdir -p "${BACKUP_ROOT}" if [ -f "${INSTALL_DIR}/venv/bin/python" ]; then "${INSTALL_DIR}/venv/bin/python" -c "from backup import create_backup_archive; print(create_backup_archive())" \ && grn "更新前已自动备份" || ylw "更新前备份跳过(可手动备份)" fi git fetch --all --prune git pull origin main 2>/dev/null || git pull origin master 2>/dev/null || git pull setup_python setup_backup_cron start_pm2 grn "更新完成(git pull + 依赖 + PM2 重启)" } do_uninstall() { ylw "即将卸载 crypto_key" echo " - 停止并删除 PM2 进程 ${APP_NAME}" echo " - 移除自动备份 cron" echo " - 删除目录 ${INSTALL_DIR}" echo " - 保留 ${BACKUP_ROOT}(如存在)" read -r -p "确认卸载?输入 yes 继续: " confirm if [ "${confirm}" != "yes" ]; then echo "已取消" return 0 fi local app_user app_user="$(detect_app_user)" if [ -n "${app_user}" ] && [ "${app_user}" != "root" ] && id "${app_user}" >/dev/null 2>&1; then sudo -u "${app_user}" pm2 delete "${APP_NAME}" 2>/dev/null || true sudo -u "${app_user}" pm2 save 2>/dev/null || true else pm2 delete "${APP_NAME}" 2>/dev/null || true pm2 save 2>/dev/null || true fi remove_backup_cron if [ -d "${INSTALL_DIR}" ]; 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_DIR}" .env data.json settings.json uploads 2>/dev/null || true rm -rf "${INSTALL_DIR}" grn "已删除 ${INSTALL_DIR}" fi grn "卸载完成。备份目录保留在: ${BACKUP_ROOT}" } show_menu() { echo "" echo "========================================" echo " crypto_key 管理脚本" echo " 仓库: ${REPO_URL}" echo " 部署: ${INSTALL_DIR}" echo "========================================" echo " 1) 一键部署" echo " 2) 更新(git pull,严禁 scp)" echo " 3) 一键卸载" echo " 0) 退出" echo "========================================" } main() { require_root # 非交互:支持参数直接执行 if [ "${1:-}" = "deploy" ] || [ "${1:-}" = "1" ]; then do_deploy; exit 0; fi if [ "${1:-}" = "update" ] || [ "${1:-}" = "2" ]; then do_update; exit 0; fi if [ "${1:-}" = "uninstall" ] || [ "${1:-}" = "3" ]; then do_uninstall; exit 0; fi while true; do show_menu read -r -p "请选择 [0-3]: " choice case "${choice}" in 1) do_deploy ;; 2) do_update ;; 3) do_uninstall ;; 0) echo "再见"; exit 0 ;; *) red "无效选项" ;; esac done } main "$@"