Initial standalone crypto_okx with one-click deploy.

Add deploy/manage.sh bootstrap for git.bz121.com/dekun/crypto_okx and point docs at this repo.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-08-13 20:00:59 +08:00
commit a1abe159fa
238 changed files with 69214 additions and 0 deletions
+101
View File
@@ -0,0 +1,101 @@
# crypto_okx 环境一键部署(Ubuntu / root /opt)
**`/opt/crypto_okx`** 下以 **root** 部署独立 OKX 期权/对冲服务,使用 **PM2** 常驻.
仓库: [https://git.bz121.com/dekun/crypto_okx.git](https://git.bz121.com/dekun/crypto_okx.git)
完整步骤见根目录 **[部署文档.md](../部署文档.md)**.
---
## 一键部署管理器(推荐)
新服务器**无需先 clone**,一条命令进入菜单:
```bash
curl -fsSL https://git.bz121.com/dekun/crypto_okx/raw/branch/main/deploy/manage.sh | bash
```
已安装机器:
```bash
bash /opt/crypto_okx/deploy/manage.sh
```
### 菜单
| 选项 | 功能 |
|------|------|
| **1) 一键部署** | 环境识别 + 系统依赖 + Node/PM2 + venv + 密钥 + 启动 `crypto_okx` |
| **2) 一键卸载** | 备份 `.env` → 停 PM2 → 移走目录 |
| **3) 更新** | 快速更新 / 依赖更新 |
| **4) 仅检测** | 识别 OS / Python / Node / PM2 / 项目状态(不安装) |
| **0) 退出** | |
### 部署完成后
- 访问: `http://<服务器IP>:5004` (端口以 `.env``APP_PORT` 为准)
- 默认入口: `/options`
- 登录账号: **admin**(密码见 `.env``APP_PASSWORD`,首次脚本可写成 `admin123`)
---
## 脚本结构
```
deploy/
├── manage.sh # 入口(自举 + 菜单)
├── lib/
│ ├── common.sh # 公共函数 / 环境识别 / 验收
│ ├── install.sh # 一键部署
│ ├── uninstall.sh # 一键卸载
│ ├── update.sh # 更新子菜单
│ └── doctor.sh # 仅检测
├── setup_env.sh # venv + pip(被 install 调用)
└── pull_and_restart.sh # 快速更新
```
---
## 前置条件
- **Ubuntu 22.04 / 24.04**,用户 **root**
-`git clone` [crypto_okx](https://git.bz121.com/dekun/crypto_okx.git) 到 `/opt/crypto_okx`
---
## 分步安装(仍可用)
```bash
cd /opt
git clone https://git.bz121.com/dekun/crypto_okx.git crypto_okx
cd /opt/crypto_okx
bash deploy/setup_env.sh --install-system-deps
pm2 start ecosystem.config.cjs
pm2 save && pm2 startup
```
`setup_env.sh` 常用参数:
```bash
bash deploy/setup_env.sh --recreate-venv # 重建虚拟环境
bash deploy/setup_env.sh --skip-pm2 # 不尝试安装 pm2
bash deploy/setup_env.sh --skip-env-copy # 不复制 .env.example
```
若在 Windows 编辑过脚本后报 `pipefail` 错误,先转 LF:
```bash
sed -i 's/\r$//' deploy/manage.sh deploy/lib/*.sh deploy/*.sh
```
---
## 环境变量(可选)
```bash
INSTALL_ROOT=/opt/crypto_okx
GIT_URL=https://git.bz121.com/dekun/crypto_okx.git
GIT_BRANCH=main
BACKUP_ROOT=/root/backups
```
+451
View File
@@ -0,0 +1,451 @@
#!/usr/bin/env bash
# deploy/lib/common.sh — crypto_okx 部署公共函数
set -e
set -u
if [ -n "${BASH_VERSION:-}" ]; then
set -o pipefail
fi
INSTALL_ROOT="${INSTALL_ROOT:-/opt/crypto_okx}"
GIT_URL="${GIT_URL:-https://git.bz121.com/dekun/crypto_okx.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:-5004}"
PM2_APP_NAME="${PM2_APP_NAME:-crypto_okx}"
LIB_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
DEPLOY_DIR="$(cd "${LIB_DIR}/.." && pwd)"
REPO_ROOT="$(cd "${DEPLOY_DIR}/.." && pwd)"
PM2_APPS=(
crypto_okx
)
# 历史进程名(仅 stop/delete 时兼容)
PM2_APPS_LEGACY=(
crypto-monitor-okx
crypto-monitor
)
CONFIG_PATHS=(
.env
)
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}"
}
confirm_yes() {
local msg="$1"
local ans=""
cm_read ans "${msg} [y/N] "
[[ "${ans}" == [yY] || "${ans}" == [yY][eE][sS] ]]
}
confirm_uninstall() {
local ans=""
echo "此操作将停止 crypto_okx PM2 进程并移走安装目录."
cm_read ans "输入 UNINSTALL 确认卸载: "
[[ "${ans}" == "UNINSTALL" ]]
}
# curl | bash 时 stdin 是管道,须从 /dev/tty 读取用户输入
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}"
}
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
}
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}安装完成"
}
install_backup_cron() {
step "安装每日备份 cron"
local inst="${REPO_ROOT}/scripts/install_backup_cron.sh"
if [[ -f "${inst}" ]]; then
chmod +x "${inst}"
bash "${inst}" || log "警告: cron 安装失败"
else
log "未找到 scripts/install_backup_cron.sh,跳过"
fi
}
remove_backup_cron() {
step "移除备份 cron"
local tmp removed=0
tmp="$(mktemp)"
if ! crontab -l 2>/dev/null >"${tmp}"; then
rm -f "${tmp}"
return 0
fi
local filtered
filtered="$(grep -vF "backup_data.sh" "${tmp}" || true)"
if [[ "${filtered}" != "$(cat "${tmp}")" ]]; then
printf '%s\n' "${filtered}" | awk '
BEGIN { tz = 0 }
/^CRON_TZ=Asia\/Shanghai$/ {
if (tz++) next
}
{ print }
' | crontab -
removed=1
fi
rm -f "${tmp}"
if [[ "${removed}" -eq 1 ]]; then
log "已移除 backup_data.sh 相关 cron"
fi
}
pm2_app_exists() {
local name="$1"
pm2 pid "${name}" >/dev/null 2>&1
}
pm2_stop_project_apps() {
if ! command -v pm2 >/dev/null 2>&1; then
log "未安装 pm2,跳过"
return 0
fi
local name stopped=0
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
stopped=$((stopped + 1))
fi
done
if [[ "${stopped}" -eq 0 ]]; then
log "未发现本项目 PM2 进程(其它 PM2 不受影响)"
fi
}
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"
pm2 restart "${PM2_APP_NAME}" --update-env
else
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
}
is_deployed() {
local root="$1"
[[ -x "${root}/.venv/bin/python" ]]
}
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:-5004}"
}
check_http() {
local url="$1"
local code
code="$(curl -sS -o /dev/null -w '%{http_code}' --connect-timeout 5 "${url}" 2>/dev/null || echo "000")"
[[ "${code}" == "200" || "${code}" == "302" || "${code}" == "301" ]]
}
verify_deployment() {
local ip="${1:-$(detect_server_ip)}"
local port
local ok=1
port="$(read_app_port)"
step "部署验收"
if command -v pm2 >/dev/null 2>&1 && pm2_app_exists "${PM2_APP_NAME}"; then
echo " [✓] PM2 ${PM2_APP_NAME} 已注册"
else
echo " [✗] PM2 缺少进程 ${PM2_APP_NAME}"
ok=0
pm2 list 2>/dev/null || true
fi
if check_http "http://127.0.0.1:${port}/"; then
echo " [✓] 页面可访问 (http://127.0.0.1:${port}/)"
else
echo " [✗] 页面不可访问 (http://127.0.0.1:${port}/)"
ok=0
fi
if [[ "${ok}" -eq 1 ]]; then
echo ""
log "验收通过: 进程在线 + 页面可打开"
return 0
fi
echo ""
log "验收未完全通过,可执行: pm2 logs ${PM2_APP_NAME} --lines 30"
return 1
}
print_post_install_guide() {
local ip="${1:-$(detect_server_ip)}"
local port
port="$(read_app_port)"
cat <<EOF
═══════════════════════════════════════════
✅ crypto_okx 部署完成
访问: http://${ip}:${port}
默认入口: /options (期权)
登录账号: admin
登录密码: 见 .env 中 APP_PASSWORD(首次多为 admin123)
(请尽快在「系统设置 / env 配置」中修改)
下一步:
1. 浏览器打开上方地址
2. 系统设置 → 填 OKX API / 企业微信 / 代理
3. 需要实盘时开启 LIVE_TRADING_ENABLED 并保存重启
管理:
bash ${INSTALL_ROOT}/deploy/manage.sh
仓库:
${GIT_URL}
═══════════════════════════════════════════
EOF
}
doctor_report() {
step "环境识别"
if [[ -f /etc/os-release ]]; then
# shellcheck source=/dev/null
source /etc/os-release
echo " OS: ${PRETTY_NAME:-${ID:-unknown} ${VERSION_ID:-}}"
else
echo " OS: unknown"
fi
echo " 用户: $(id -un) (uid=$(id -u))"
echo " 主机: $(hostname 2>/dev/null || echo '?')"
echo " IP: $(detect_server_ip)"
echo " 安装根: ${INSTALL_ROOT}"
echo " 仓库: ${GIT_URL} (${GIT_BRANCH})"
step "依赖检测"
local item cmd
for item in "python3:python3" "git:git" "curl:curl" "node:node" "npm:npm" "pm2:pm2"; do
cmd="${item#*:}"
if command -v "${cmd}" >/dev/null 2>&1; then
case "${cmd}" in
python3) echo " [✓] python3: $(python3 --version 2>&1)" ;;
node) echo " [✓] node: $(node -v 2>&1)" ;;
npm) echo " [✓] npm: $(npm -v 2>&1)" ;;
pm2) echo " [✓] pm2: $(pm2 -v 2>&1)" ;;
*) echo " [✓] ${cmd}: $(command -v "${cmd}")" ;;
esac
else
echo " [✗] ${cmd}: 未安装"
fi
done
if command -v python3 >/dev/null 2>&1; then
local ver major minor
ver="$(python3 -c 'import sys; print(f"{sys.version_info.major}.{sys.version_info.minor}")')"
major="${ver%%.*}"
minor="${ver#*.}"
if [[ "${major}" -gt 3 ]] || [[ "${major}" -eq 3 && "${minor}" -ge 10 ]]; then
echo " [✓] Python 版本满足 3.10+ (${ver})"
else
echo " [✗] Python 需要 3.10+,当前 ${ver}"
fi
fi
step "项目状态"
local root=""
if root="$(resolve_repo_root 2>/dev/null)"; then
echo " [✓] 仓库就绪: ${root}"
[[ -f "${root}/requirements.txt" ]] && echo " [✓] requirements.txt" || echo " [✗] requirements.txt"
[[ -f "${root}/ecosystem.config.cjs" ]] && echo " [✓] ecosystem.config.cjs" || echo " [✗] ecosystem.config.cjs"
[[ -f "${root}/.env" ]] && echo " [✓] .env 已存在" || echo " [!] .env 尚未生成"
if [[ -x "${root}/.venv/bin/python" ]]; then
echo " [✓] .venv: $("${root}/.venv/bin/python" --version 2>&1)"
else
echo " [!] .venv 未创建"
fi
else
echo " [!] 尚未安装到 ${INSTALL_ROOT}"
fi
if command -v pm2 >/dev/null 2>&1; then
step "PM2"
pm2 list 2>/dev/null | head -n 40 || true
fi
}
+22
View File
@@ -0,0 +1,22 @@
#!/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() {
if root="$(resolve_repo_root 2>/dev/null)"; then
REPO_ROOT="${root}"
fi
doctor_report
echo ""
log "检测完成(未修改系统).需要安装请返回菜单选 1."
}
main_doctor "$@"
+110
View File
@@ -0,0 +1,110 @@
#!/usr/bin/env bash
# deploy/lib/install.sh — crypto_okx 一键部署
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 "修复环境(保留数据与配置)"
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
install_backup_cron
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 "$@"
+68
View File
@@ -0,0 +1,68 @@
#!/usr/bin/env bash
# deploy/lib/uninstall.sh — crypto_okx 一键卸载
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-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
remove_backup_cron
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 "$@"
+62
View File
@@ -0,0 +1,62 @@
#!/usr/bin/env bash
# deploy/lib/update.sh — crypto_okx 更新
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"
require_installed() {
if ! repo_ready "${REPO_ROOT}"; then
die "未找到安装,请先执行「1) 一键部署」"
fi
}
update_quick() {
step "快速更新"
bash "${REPO_ROOT}/deploy/pull_and_restart.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 依赖"
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
require_installed
show_update_menu
}
main_update "$@"
+153
View File
@@ -0,0 +1,153 @@
#!/usr/bin/env bash
# crypto_okx 部署管理器 — 一键部署 / 卸载 / 更新
#
# 新服务器(免克隆):
# curl -fsSL https://git.bz121.com/dekun/crypto_okx/raw/branch/main/deploy/manage.sh | bash
#
# 已安装:
# bash /opt/crypto_okx/deploy/manage.sh
#
set -e
if [ -n "${BASH_VERSION:-}" ]; then
set -o pipefail
fi
INSTALL_ROOT="${INSTALL_ROOT:-/opt/crypto_okx}"
GIT_URL="${GIT_URL:-https://git.bz121.com/dekun/crypto_okx.git}"
GIT_BRANCH="${GIT_BRANCH:-main}"
_script_src="${BASH_SOURCE[0]:-}"
if [[ -n "${_script_src}" && -f "${_script_src}" ]]; then
DEPLOY_DIR="$(cd "$(dirname "${_script_src}")" && pwd)"
REPO_ROOT="$(cd "${DEPLOY_DIR}/.." && pwd)"
LIB_DIR="${DEPLOY_DIR}/lib"
else
DEPLOY_DIR=""
REPO_ROOT=""
LIB_DIR=""
fi
unset _script_src
set -u
repo_ready() {
[[ -f "${1}/deploy/setup_env.sh" && -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
git -C "${root}" pull -q --ff-only 2>/dev/null || true
fi
}
bootstrap_repo() {
if repo_ready "${INSTALL_ROOT}"; then
REPO_ROOT="${INSTALL_ROOT}"
DEPLOY_DIR="${REPO_ROOT}/deploy"
LIB_DIR="${DEPLOY_DIR}/lib"
sync_repo_if_present "${REPO_ROOT}"
return 0
fi
if [[ -n "${REPO_ROOT}" ]] && repo_ready "${REPO_ROOT}"; then
DEPLOY_DIR="${REPO_ROOT}/deploy"
LIB_DIR="${DEPLOY_DIR}/lib"
return 0
fi
echo "crypto_okx 部署管理器 — 首次自举"
echo "将克隆到: ${INSTALL_ROOT}"
if [[ "$(id -u)" -ne 0 ]]; then
echo "错误: 请使用 root 执行" >&2
exit 1
fi
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
echo "错误: 未找到 git" >&2
exit 1
fi
fi
if [[ -d "${INSTALL_ROOT}" ]]; then
echo "错误: ${INSTALL_ROOT} 已存在但不是有效仓库" >&2
echo "请手动处理后再运行,或设置 INSTALL_ROOT 指向其它路径" >&2
exit 1
fi
mkdir -p "$(dirname "${INSTALL_ROOT}")"
git clone -b "${GIT_BRANCH}" "${GIT_URL}" "${INSTALL_ROOT}"
exec bash "${INSTALL_ROOT}/deploy/manage.sh" "$@" </dev/tty
}
show_banner() {
local ip path
path="${INSTALL_ROOT}"
if repo_ready "${REPO_ROOT:-}"; then
path="${REPO_ROOT}"
fi
if [[ -f "${LIB_DIR}/common.sh" ]]; then
# shellcheck source=/dev/null
source "${LIB_DIR}/common.sh"
ip="$(detect_server_ip)"
else
ip="?"
fi
echo ""
echo "╔══════════════════════════════════════╗"
echo "║ crypto_okx 部署管理器 ║"
echo "║ 仓库: git.bz121.com/dekun/crypto_okx"
echo "║ 路径: ${path}"
echo "║ IP: ${ip}"
echo "╚══════════════════════════════════════╝"
echo ""
}
show_menu() {
echo " 1) 一键部署(检测环境 + 依赖 + PM2)"
echo " 2) 一键卸载"
echo " 3) 更新"
echo " 4) 仅检测环境/依赖(不安装)"
echo " 0) 退出"
echo ""
}
main_menu() {
bootstrap_repo
# shellcheck source=lib/common.sh
source "${LIB_DIR}/common.sh"
REPO_ROOT="$(resolve_repo_root || echo "${INSTALL_ROOT}")"
while true; do
show_banner
show_menu
local choice=""
cm_read choice "请选择 [0-4]: "
case "${choice}" in
1)
bash "${LIB_DIR}/install.sh"
;;
2)
bash "${LIB_DIR}/uninstall.sh"
;;
3)
bash "${LIB_DIR}/update.sh"
;;
4)
bash "${LIB_DIR}/doctor.sh"
;;
0)
echo "再见."
exit 0
;;
*)
echo "无效选项,请输入 0-4"
;;
esac
echo ""
cm_read _ "按 Enter 返回菜单..."
done
}
main_menu "$@"
+42
View File
@@ -0,0 +1,42 @@
#!/usr/bin/env bash
# 服务器上拉代码并重启 PM2.
# 用法(/opt/crypto_okx 下 root):
# bash deploy/pull_and_restart.sh
# bash deploy/pull_and_restart.sh --dry-run
set -e
set -u
if [ -n "${BASH_VERSION:-}" ]; then
set -o pipefail
fi
REPO="${REPO:-/opt/crypto_okx}"
PM2_APP_NAME="${PM2_APP_NAME:-crypto_okx}"
DRY=0
if [[ "${1:-}" == "--dry-run" ]]; then
DRY=1
echo "(dry-run mode)"
fi
cd "${REPO}"
echo ">>> git pull"
git pull
if [[ "${DRY}" -eq 1 ]]; then
echo "(dry-run, skip pm2 restart)"
exit 0
fi
echo ">>> pm2 restart ${PM2_APP_NAME} --update-env"
if command -v pm2 >/dev/null 2>&1; then
if pm2 pid "${PM2_APP_NAME}" >/dev/null 2>&1; then
pm2 restart "${PM2_APP_NAME}" --update-env
elif [[ -f ecosystem.config.cjs ]]; then
pm2 start ecosystem.config.cjs
else
echo "warn: 未找到 PM2 进程 ${PM2_APP_NAME}" >&2
fi
else
echo "warn: 未安装 pm2" >&2
fi
echo "done"
+213
View File
@@ -0,0 +1,213 @@
#!/usr/bin/env bash
# crypto_okx 一键环境部署(Ubuntu / root /opt/crypto_okx)
#
# 用法:
# bash deploy/setup_env.sh
# bash deploy/setup_env.sh --skip-pm2
# bash deploy/setup_env.sh --recreate-venv
# bash deploy/setup_env.sh --install-system-deps
#
set -e
set -u
if [ -n "${BASH_VERSION:-}" ]; then
set -o pipefail
fi
DEPLOY_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "${DEPLOY_DIR}/.." && pwd)"
REQ_FILE="${REPO_ROOT}/requirements.txt"
# shellcheck source=lib/common.sh
source "${DEPLOY_DIR}/lib/common.sh"
SKIP_PM2=0
SKIP_ENV_COPY=0
RECREATE_VENV=0
INSTALL_APT_DEPS=0
PY=""
usage() {
sed -n '2,10p' "$0" | sed 's/^# \?//'
exit "${1:-0}"
}
while [[ $# -gt 0 ]]; do
case "$1" in
--skip-pm2) SKIP_PM2=1; shift ;;
--skip-env-copy) SKIP_ENV_COPY=1; shift ;;
--recreate-venv) RECREATE_VENV=1; shift ;;
--install-system-deps) INSTALL_APT_DEPS=1; shift ;;
-h|--help) usage 0 ;;
*) echo "未知参数: $1" >&2; usage 1 ;;
esac
done
find_python() {
if command -v python3 >/dev/null 2>&1; then
echo python3
return
fi
if command -v python >/dev/null 2>&1; then
echo python
return
fi
echo "未找到 python3/python,请先安装 Python 3.10+" >&2
exit 1
}
check_python_version() {
local py="$1"
local ver
ver="$("${py}" -c 'import sys; print(f"{sys.version_info.major}.{sys.version_info.minor}")')"
local major minor
major="${ver%%.*}"
minor="${ver#*.}"
if [[ "${major}" -lt 3 ]] || [[ "${major}" -eq 3 && "${minor}" -lt 10 ]]; then
echo "需要 Python 3.10+,当前: ${ver}" >&2
exit 1
fi
echo "Python: $("${py}" --version 2>&1)"
}
python_minor_version() {
local py="$1"
"${py}" -c 'import sys; print(f"{sys.version_info.major}.{sys.version_info.minor}")'
}
check_venv_available() {
local py="$1"
local tmp
tmp="$(mktemp -d 2>/dev/null || mktemp -d -t cmvenv)"
if "${py}" -m venv "${tmp}" >/dev/null 2>&1 && [[ -x "${tmp}/bin/python" ]]; then
rm -rf "${tmp}"
return 0
fi
rm -rf "${tmp}" 2>/dev/null || true
return 1
}
install_debian_venv_packages() {
local py="$1"
local ver
ver="$(python_minor_version "${py}")"
if ! command -v apt-get >/dev/null 2>&1; then
echo " 未检测到 apt-get,请手动安装 python${ver}-venv 与 python3-pip" >&2
return 1
fi
if [[ "$(id -u)" -ne 0 ]]; then
echo " 需要 root 安装系统包,请执行:" >&2
echo " sudo apt update && sudo apt install -y python${ver}-venv python3-pip curl" >&2
echo " 或: sudo bash deploy/setup_env.sh --install-system-deps" >&2
return 1
fi
step "安装系统依赖 (python${ver}-venv) ..."
export DEBIAN_FRONTEND=noninteractive
apt-get update -qq
if ! apt-get install -y "python${ver}-venv" python3-pip curl ca-certificates git; then
apt-get install -y python3-venv python3-pip curl ca-certificates git
fi
}
ensure_venv_prereqs() {
local py="$1"
if check_venv_available "${py}"; then
return 0
fi
echo " 当前 Python 无法创建 venv(缺少 ensurepip,常见于未安装 python*-venv)" >&2
if [[ "${INSTALL_APT_DEPS}" -eq 1 ]] || [[ "$(id -u)" -eq 0 ]]; then
install_debian_venv_packages "${py}" || exit 1
if check_venv_available "${py}"; then
return 0
fi
fi
local ver
ver="$(python_minor_version "${py}")"
echo "请安装后重试:" >&2
echo " apt update && apt install -y python${ver}-venv python3-pip" >&2
echo " bash deploy/setup_env.sh" >&2
exit 1
}
create_project_venv() {
local py="$1"
if [[ "${RECREATE_VENV}" -eq 1 && -d .venv ]]; then
echo " 删除旧 venv ..."
rm -rf .venv
fi
if [[ -d .venv && ! -x .venv/bin/python ]]; then
echo " 清理未完成的 venv ..."
rm -rf .venv
fi
if [[ -x .venv/bin/python ]]; then
return 0
fi
echo " 创建 venv ..."
if ! "${py}" -m venv .venv; then
rm -rf .venv 2>/dev/null || true
echo " venv 创建失败" >&2
exit 1
fi
}
setup_app() {
step "crypto_okx"
cd "${REPO_ROOT}"
create_project_venv "${PY}"
pip_upgrade_tools ".venv/bin/pip"
pip_install_requirements ".venv/bin/pip" "${REQ_FILE}" "crypto_okx 依赖"
if [[ "${SKIP_ENV_COPY}" -eq 0 ]]; then
if [[ -f .env.example && ! -f .env ]]; then
cp -n .env.example .env 2>/dev/null || cp .env.example .env
echo " 已复制 .env.example -> .env"
elif [[ -f .env ]]; then
echo " 保留已有 .env"
else
echo " 无 .env.example,请手动配置 .env"
fi
fi
mkdir -p static/images/order_charts
echo " 完成: ${REPO_ROOT}/.venv/bin/python"
}
install_pm2_optional() {
if [[ "${SKIP_PM2}" -eq 1 ]]; then
return
fi
step "PM2(可选)"
if ! command -v node >/dev/null 2>&1; then
echo " 未检测到 Node.js,跳过.安装后执行: npm install -g pm2"
return
fi
if command -v pm2 >/dev/null 2>&1; then
echo " PM2 已安装: $(pm2 -v)"
return
fi
echo " 正在安装 pm2 ..."
npm install -g pm2
}
echo "crypto_okx 环境部署"
echo "仓库根目录: ${REPO_ROOT}"
echo "远端仓库: ${GIT_URL}"
[[ -f "${REQ_FILE}" ]] || { echo "缺少 ${REQ_FILE}" >&2; exit 1; }
PY="$(find_python)"
check_python_version "${PY}"
ensure_venv_prereqs "${PY}"
setup_app
install_pm2_optional
step "部署密钥(首次自动生成,不覆盖已有)"
if command -v python3 >/dev/null 2>&1; then
python3 "${REPO_ROOT}/scripts/bootstrap_deploy_secrets.py" || true
else
echo " 跳过 bootstrap_deploy_secrets(未找到 python3)"
fi
echo ""
echo "部署完成.下一步:"
echo " 1. 编辑 ${REPO_ROOT}/.env (OKX API / 代理 / 登录密码)"
echo " 2. pm2 start ecosystem.config.cjs"
echo " 或: bash deploy/manage.sh # 选 1"
echo ""