Files
crypto_key/deploy/manage.sh
T
dekun 83fd9c1fd9 feat: image lightbox and curl|bash Ubuntu deploy
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>
2026-07-17 20:43:54 +08:00

322 lines
9.1 KiB
Bash
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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 20PM2 需要)..."
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 "$@"