83fd9c1fd9
Add click-to-zoom for uploaded images, move manage.sh to deploy/ for curl|bash bootstrap, and remove Windows docs and PowerShell scripts. Co-authored-by: Cursor <cursoragent@cursor.com>
322 lines
9.1 KiB
Bash
322 lines
9.1 KiB
Bash
#!/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/tty
|
||
}
|
||
|
||
bootstrap_repo() {
|
||
if repo_ready "${INSTALL_ROOT}"; then
|
||
REPO_ROOT="${INSTALL_ROOT}"
|
||
DEPLOY_DIR="${REPO_ROOT}/deploy"
|
||
sync_repo_if_present "${REPO_ROOT}"
|
||
# 管道执行时切到仓库内最新脚本,并从终端读菜单
|
||
if [[ "${_from_pipe}" -eq 1 ]]; then
|
||
reexec_local "$@"
|
||
fi
|
||
return 0
|
||
fi
|
||
|
||
if [[ -n "${REPO_ROOT}" ]] && repo_ready "${REPO_ROOT}"; then
|
||
# 在源码树内直接运行(开发/已 clone 目录)
|
||
DEPLOY_DIR="${REPO_ROOT}/deploy"
|
||
return 0
|
||
fi
|
||
|
||
echo "crypto_key 部署管理器 — 首次自举"
|
||
echo "将克隆到: ${INSTALL_ROOT}"
|
||
require_root
|
||
|
||
if ! command -v git >/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:-<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 "$@"
|