支持 Ubuntu 一键部署与 PM2,并增加系统设置改密。

默认账号改为 admin/admin123,去掉 Windows 部署说明,与客户端同套部署流程。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-10-01 11:03:29 +08:00
parent d20acaf852
commit d726810320
21 changed files with 1456 additions and 105 deletions
+360
View File
@@ -0,0 +1,360 @@
#!/usr/bin/env bash
# deploy/lib/common.sh — crypto_okx_hub 部署公共函数 (Ubuntu / root / PM2)
set -e
set -u
if [ -n "${BASH_VERSION:-}" ]; then
set -o pipefail
fi
INSTALL_ROOT="${INSTALL_ROOT:-/opt/crypto_okx_hub}"
GIT_URL="${GIT_URL:-https://git.bz121.com/dekun/crypto_okx_hub.git}"
GIT_BRANCH="${GIT_BRANCH:-main}"
BACKUP_ROOT="${BACKUP_ROOT:-/root/backups}"
TZ_NAME="${CM_TZ:-Asia/Shanghai}"
NODE_MAJOR="${NODE_MAJOR:-20}"
APP_PORT="${APP_PORT:-5100}"
PM2_APP_NAME="${PM2_APP_NAME:-crypto_okx_hub}"
LIB_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
DEPLOY_DIR="$(cd "${LIB_DIR}/.." && pwd)"
REPO_ROOT="$(cd "${DEPLOY_DIR}/.." && pwd)"
PM2_APPS=(
crypto_okx_hub
)
PM2_APPS_LEGACY=()
CONFIG_PATHS=(
.env
hub.db
)
log() { printf '[%s] %s\n' "$(TZ="${TZ_NAME}" date '+%Y-%m-%d %H:%M:%S')" "$*"; }
step() { echo ""; log "==> $*"; }
die() {
echo "错误: $*" >&2
exit 1
}
require_root() {
if [[ "$(id -u)" -ne 0 ]]; then
die "请使用 root 执行(推荐: sudo -i 后运行)"
fi
}
require_ubuntu() {
if [[ ! -f /etc/os-release ]]; then
log "警告: 未检测到 /etc/os-release,跳过 Ubuntu 版本检查"
return 0
fi
# shellcheck source=/dev/null
source /etc/os-release
if [[ "${ID:-}" != "ubuntu" ]]; then
log "警告: 当前系统为 ${ID:-unknown},官方仅测试 Ubuntu 22.04/24.04"
return 0
fi
local ver="${VERSION_ID:-}"
if [[ "${ver}" != "22.04" && "${ver}" != "24.04" ]]; then
log "警告: Ubuntu ${ver} 未在文档中明确测试,继续执行"
fi
}
detect_server_ip() {
local ip=""
if command -v hostname >/dev/null 2>&1; then
ip="$(hostname -I 2>/dev/null | awk '{print $1}')"
fi
if [[ -z "${ip}" ]]; then
ip="127.0.0.1"
fi
echo "${ip}"
}
cm_read() {
local __var="$1"
local __prompt="$2"
local __val=""
if [[ -r /dev/tty ]]; then
IFS= read -r -p "${__prompt}" __val </dev/tty || true
else
IFS= read -r -p "${__prompt}" __val || true
fi
printf -v "${__var}" '%s' "${__val}"
}
confirm_uninstall() {
local ans=""
echo "此操作将停止 crypto_okx_hub PM2 进程并移走安装目录."
cm_read ans "输入 UNINSTALL 确认卸载: "
[[ "${ans}" == "UNINSTALL" ]]
}
repo_ready() {
[[ -f "${1}/deploy/setup_env.sh" && -f "${1}/deploy/manage.sh" && -f "${1}/app.py" ]]
}
resolve_repo_root() {
if repo_ready "${INSTALL_ROOT}"; then
echo "${INSTALL_ROOT}"
return 0
fi
if repo_ready "${REPO_ROOT}"; then
echo "${REPO_ROOT}"
return 0
fi
return 1
}
is_deployed() {
local root="$1"
[[ -f "${root}/.env" && -x "${root}/.venv/bin/python" && -f "${root}/ecosystem.config.cjs" ]]
}
backup_configs_to() {
local src_root="$1"
local dest="$2"
mkdir -p "${dest}"
local rel copied=0
for rel in "${CONFIG_PATHS[@]}"; do
local src="${src_root}/${rel}"
if [[ -f "${src}" ]]; then
mkdir -p "${dest}/$(dirname "${rel}")"
cp -a "${src}" "${dest}/${rel}"
log "backup ${rel}"
copied=$((copied + 1))
fi
done
if [[ "${copied}" -eq 0 ]]; then
log "提示: 未找到可备份的配置文件"
fi
}
install_system_packages() {
step "安装系统依赖"
if ! command -v apt-get >/dev/null 2>&1; then
die "未检测到 apt-get,请手动安装 python3-venv git curl"
fi
export DEBIAN_FRONTEND=noninteractive
apt-get update -qq
apt-get install -y python3 python3-pip python3-venv curl git ca-certificates
local pyver=""
if command -v python3 >/dev/null 2>&1; then
pyver="$(python3 -c 'import sys; print(f"{sys.version_info.major}.{sys.version_info.minor}")')"
apt-get install -y "python${pyver}-venv" 2>/dev/null || apt-get install -y python3-venv
fi
}
install_node_pm2() {
step "检查 Node.js 与 PM2"
if command -v pm2 >/dev/null 2>&1; then
log "PM2 已安装: $(pm2 -v)"
return 0
fi
if ! command -v node >/dev/null 2>&1; then
log "安装 Node.js ${NODE_MAJOR}.x ..."
curl -fsSL "https://deb.nodesource.com/setup_${NODE_MAJOR}.x" | bash -
apt-get install -y nodejs
fi
log "安装 PM2 ..."
npm install -g pm2
log "PM2: $(pm2 -v)"
}
pip_progress_bar_arg() {
echo "on"
}
pip_upgrade_tools() {
local pip_bin="$1"
local bar
bar="$(pip_progress_bar_arg)"
echo " 升级 pip ..."
"${pip_bin}" install -U pip setuptools wheel \
--disable-pip-version-check \
--progress-bar "${bar}"
}
pip_install_requirements() {
local pip_bin="$1"
local req_file="$2"
local label="${3:-依赖}"
local bar
bar="$(pip_progress_bar_arg)"
echo " 安装${label} (下方为 pip 进度) ..."
[[ -f "${req_file}" ]] || die "缺少依赖文件: ${req_file}"
"${pip_bin}" install -r "${req_file}" \
--disable-pip-version-check \
--retries 5 \
--progress-bar "${bar}"
echo " ${label}安装完成"
}
pm2_app_exists() {
local name="$1"
if ! command -v pm2 >/dev/null 2>&1; then
return 1
fi
if ! command -v python3 >/dev/null 2>&1; then
pm2 describe "${name}" 2>/dev/null | grep -q "status"
return $?
fi
pm2 jlist 2>/dev/null | python3 -c '
import json,sys
name=sys.argv[1]
try:
apps=json.load(sys.stdin)
except Exception:
sys.exit(1)
sys.exit(0 if any(isinstance(a,dict) and a.get("name")==name for a in (apps or [])) else 1)
' "${name}"
}
pm2_stop_project_apps() {
if ! command -v pm2 >/dev/null 2>&1; then
log "未安装 pm2,跳过"
return 0
fi
local name
for name in "${PM2_APPS[@]}" "${PM2_APPS_LEGACY[@]}"; do
if pm2_app_exists "${name}"; then
log "pm2 stop ${name}"
pm2 stop "${name}" 2>/dev/null || true
fi
done
}
pm2_delete_project_apps() {
if ! command -v pm2 >/dev/null 2>&1; then
return 0
fi
local name deleted=0
for name in "${PM2_APPS[@]}" "${PM2_APPS_LEGACY[@]}"; do
if pm2_app_exists "${name}"; then
log "pm2 delete ${name}"
pm2 delete "${name}" 2>/dev/null || true
deleted=$((deleted + 1))
fi
done
if [[ "${deleted}" -gt 0 ]]; then
pm2 save 2>/dev/null || true
fi
}
pm2_start_app() {
step "启动 PM2: ${PM2_APP_NAME}"
cd "${REPO_ROOT}"
[[ -f "${REPO_ROOT}/ecosystem.config.cjs" ]] || die "缺少 ecosystem.config.cjs"
[[ -x "${REPO_ROOT}/.venv/bin/python" ]] || die "缺少 .venv,请先运行 setup_env.sh"
if pm2_app_exists "${PM2_APP_NAME}"; then
log "已存在,执行 restart --update-env"
if ! pm2 restart "${PM2_APP_NAME}" --update-env; then
pm2 start ecosystem.config.cjs
fi
else
log "首次启动: pm2 start ecosystem.config.cjs"
pm2 start ecosystem.config.cjs
fi
}
pm2_save_startup() {
step "PM2 save & startup"
pm2 save 2>/dev/null || true
if pm2 startup systemd -u root --hp /root 2>/dev/null | grep -q "sudo"; then
pm2 startup systemd -u root --hp /root 2>/dev/null | grep "^sudo" | bash || true
else
pm2 startup 2>/dev/null || true
fi
}
read_app_port() {
local env_file="${REPO_ROOT}/.env"
local port="${APP_PORT}"
if [[ -f "${env_file}" ]]; then
local line
line="$(grep -E '^[[:space:]]*APP_PORT=' "${env_file}" | tail -n1 || true)"
if [[ -n "${line}" ]]; then
port="${line#*=}"
port="$(echo "${port}" | tr -d '"' | tr -d "'" | xargs)"
fi
fi
echo "${port:-5100}"
}
verify_deployment() {
local ip="${1:-$(detect_server_ip)}"
local port
port="$(read_app_port)"
step "验收"
if command -v pm2 >/dev/null 2>&1 && pm2_app_exists "${PM2_APP_NAME}"; then
log "PM2: ${PM2_APP_NAME} 已注册"
else
log "警告: 未检测到 PM2 进程 ${PM2_APP_NAME}"
return 1
fi
local code
code="$(curl -sS -o /dev/null -w '%{http_code}' --connect-timeout 2 --max-time 3 "http://127.0.0.1:${port}/login" 2>/dev/null || echo "000")"
log "HTTP /login -> ${code}"
log "访问: http://${ip}:${port}/login"
return 0
}
print_post_install_guide() {
local ip="${1:-$(detect_server_ip)}"
local port
port="$(read_app_port)"
echo ""
echo "======== 部署完成 ========"
echo " 中控: http://${ip}:${port}/login"
echo " 账号: 见 .env 的 HUB_USERNAME / HUB_PASSWORD"
echo " 路径: ${REPO_ROOT}"
echo " 日志: pm2 logs ${PM2_APP_NAME} --lines 100"
echo " 菜单: bash ${REPO_ROOT}/deploy/manage.sh"
echo " 客户端: https://git.bz121.com/dekun/crypto_okx.git"
echo "=========================="
echo ""
}
doctor_report() {
step "环境检测"
require_ubuntu || true
log "用户: $(id -un) uid=$(id -u)"
log "路径候选: INSTALL_ROOT=${INSTALL_ROOT}"
if command -v python3 >/dev/null 2>&1; then
log "Python: $(python3 --version 2>&1)"
else
log "Python: 未安装"
fi
if command -v node >/dev/null 2>&1; then
log "Node: $(node -v)"
else
log "Node: 未安装"
fi
if command -v pm2 >/dev/null 2>&1; then
log "PM2: $(pm2 -v)"
else
log "PM2: 未安装"
fi
local root=""
if root="$(resolve_repo_root 2>/dev/null)"; then
log "仓库: ${root} (就绪)"
REPO_ROOT="${root}"
if [[ -x "${root}/.venv/bin/python" ]]; then
log "venv: 存在"
else
log "venv: 缺失"
fi
if [[ -f "${root}/.env" ]]; then
log ".env: 存在"
else
log ".env: 缺失"
fi
if command -v pm2 >/dev/null 2>&1 && pm2_app_exists "${PM2_APP_NAME}"; then
log "PM2 进程: ${PM2_APP_NAME} 已注册"
else
log "PM2 进程: 未注册 ${PM2_APP_NAME}"
fi
else
log "仓库: 未安装到 ${INSTALL_ROOT}"
fi
}
+23
View File
@@ -0,0 +1,23 @@
#!/usr/bin/env bash
# deploy/lib/doctor.sh — 仅检测,不安装
set -e
set -u
if [ -n "${BASH_VERSION:-}" ]; then
set -o pipefail
fi
LIB_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# shellcheck source=common.sh
source "${LIB_DIR}/common.sh"
main_doctor() {
local root=""
if root="$(resolve_repo_root 2>/dev/null)"; then
REPO_ROOT="${root}"
fi
doctor_report
echo ""
log "检测完成(未修改系统).需要安装请返回菜单选 1."
}
main_doctor "$@"
+109
View File
@@ -0,0 +1,109 @@
#!/usr/bin/env bash
# deploy/lib/install.sh — crypto_okx_hub 一键部署
set -e
set -u
if [ -n "${BASH_VERSION:-}" ]; then
set -o pipefail
fi
LIB_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# shellcheck source=common.sh
source "${LIB_DIR}/common.sh"
install_fresh() {
step "克隆仓库"
if [[ -d "${INSTALL_ROOT}" ]]; then
die "目录已存在: ${INSTALL_ROOT},请选修复环境"
fi
mkdir -p "$(dirname "${INSTALL_ROOT}")"
git clone -b "${GIT_BRANCH}" "${GIT_URL}" "${INSTALL_ROOT}"
}
install_repair() {
step "修复环境(保留 .env 与 hub.db)"
bash "${REPO_ROOT}/deploy/setup_env.sh" --install-system-deps --recreate-venv
}
run_install_pipeline() {
step "环境部署 setup_env.sh"
bash "${REPO_ROOT}/deploy/setup_env.sh" --install-system-deps
pm2_start_app
pm2_save_startup
verify_deployment "$(detect_server_ip)" || true
print_post_install_guide "$(detect_server_ip)"
}
handle_existing_install() {
echo ""
echo "检测到已部署安装: ${INSTALL_ROOT}"
echo " a) 取消"
echo " b) 修复环境(重建 venv,保留 .env 与数据库)"
echo " c) 重新启动 PM2(不改依赖)"
local choice=""
cm_read choice "请选择 [a/b/c]: "
case "${choice}" in
b|B)
install_repair
if command -v pm2 >/dev/null 2>&1; then
pm2_start_app
pm2_save_startup
fi
verify_deployment "$(detect_server_ip)" || true
print_post_install_guide "$(detect_server_ip)"
;;
c|C)
pm2_start_app
pm2_save_startup
verify_deployment "$(detect_server_ip)" || true
print_post_install_guide "$(detect_server_ip)"
;;
*)
log "已取消"
;;
esac
}
ensure_repo_ready() {
if repo_ready "${INSTALL_ROOT}"; then
REPO_ROOT="${INSTALL_ROOT}"
elif [[ -n "${REPO_ROOT:-}" ]] && repo_ready "${REPO_ROOT}"; then
:
else
REPO_ROOT=""
fi
if [[ -z "${REPO_ROOT}" ]]; then
install_system_packages
install_node_pm2
install_fresh
REPO_ROOT="${INSTALL_ROOT}"
else
install_system_packages
install_node_pm2
fi
}
main_install() {
require_root
require_ubuntu
if repo_ready "${INSTALL_ROOT}"; then
REPO_ROOT="${INSTALL_ROOT}"
elif repo_ready "${REPO_ROOT}"; then
:
else
REPO_ROOT=""
fi
if [[ -n "${REPO_ROOT}" ]] && is_deployed "${REPO_ROOT}"; then
handle_existing_install
return 0
fi
ensure_repo_ready
run_install_pipeline
}
main_install "$@"
+66
View File
@@ -0,0 +1,66 @@
#!/usr/bin/env bash
# deploy/lib/uninstall.sh — crypto_okx_hub 一键卸载
set -e
set -u
if [ -n "${BASH_VERSION:-}" ]; then
set -o pipefail
fi
LIB_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# shellcheck source=common.sh
source "${LIB_DIR}/common.sh"
main_uninstall() {
require_root
local root=""
if ! root="$(resolve_repo_root)"; then
if [[ -d "${INSTALL_ROOT}" ]]; then
root="${INSTALL_ROOT}"
else
die "未找到安装目录 ${INSTALL_ROOT}"
fi
fi
REPO_ROOT="${root}"
if ! confirm_uninstall; then
log "已取消卸载"
return 0
fi
local stamp backup_dir removed_dir
stamp="$(TZ="${TZ_NAME}" date +%Y%m%d-%H%M%S)"
backup_dir="${BACKUP_ROOT}/crypto_okx_hub-pre-uninstall-${stamp}"
removed_dir="${INSTALL_ROOT}.removed.${stamp}"
step "备份配置到 ${backup_dir}"
backup_configs_to "${REPO_ROOT}" "${backup_dir}"
{
echo "created_at=${stamp}"
echo "install_root=${INSTALL_ROOT}"
echo "removed_dir=${removed_dir}"
} >"${backup_dir}/uninstall.manifest"
step "停止并移除本项目 PM2 进程(不影响其它 PM2)"
pm2_stop_project_apps
pm2_delete_project_apps
step "移走安装目录"
if [[ -d "${INSTALL_ROOT}" ]]; then
mv "${INSTALL_ROOT}" "${removed_dir}"
log "已移动: ${INSTALL_ROOT} -> ${removed_dir}"
else
log "安装目录不存在,跳过"
fi
echo ""
echo "卸载完成."
echo " 配置备份: ${backup_dir}"
echo " 旧目录: ${removed_dir}"
echo ""
echo "回滚示例:"
echo " mv ${removed_dir} ${INSTALL_ROOT}"
echo " bash ${INSTALL_ROOT}/deploy/manage.sh # 选 1 修复环境"
}
main_uninstall "$@"
+60
View File
@@ -0,0 +1,60 @@
#!/usr/bin/env bash
# deploy/lib/update.sh — crypto_okx_hub 更新
set -e
set -u
if [ -n "${BASH_VERSION:-}" ]; then
set -o pipefail
fi
LIB_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# shellcheck source=common.sh
source "${LIB_DIR}/common.sh"
update_quick() {
step "快速更新"
bash "${REPO_ROOT}/deploy/pull_and_restart.sh"
# shellcheck source=common.sh
source "${LIB_DIR}/common.sh"
verify_deployment "$(detect_server_ip)" || true
}
update_deps() {
step "依赖更新"
if [[ -x "${REPO_ROOT}/.venv/bin/pip" ]]; then
pip_install_requirements "${REPO_ROOT}/.venv/bin/pip" "${REPO_ROOT}/requirements.txt" "crypto_okx_hub 依赖"
else
bash "${REPO_ROOT}/deploy/setup_env.sh" --install-system-deps
fi
update_quick
}
show_update_menu() {
while true; do
echo ""
echo " 更新选项:"
echo " 3-1) 快速更新(git pull + pm2 restart)"
echo " 3-2) 依赖更新(含 pip install)"
echo " 0) 返回主菜单"
local choice=""
cm_read choice "请选择 [0/3-1/3-2]: "
case "${choice}" in
3-1|31|1) update_quick; break ;;
3-2|32|2) update_deps; break ;;
0) break ;;
*) echo "无效选项" ;;
esac
done
}
main_update() {
require_root
if ! REPO_ROOT="$(resolve_repo_root)"; then
die "未找到安装目录 ${INSTALL_ROOT},请先执行「1) 一键部署」"
fi
if ! repo_ready "${REPO_ROOT}"; then
die "未找到安装,请先执行「1) 一键部署」"
fi
show_update_menu
}
main_update "$@"