#!/usr/bin/env bash # 在生产机(有 Bitget API key 白名单的那台)上装实盘执行器。 # # 只装执行侧:标准库 + aiohttp。**不装** Docker / Hummingbot / pandas / # chanlun 引擎——那些是采集与研究侧的依赖,理由见 live/live_exec.py 文件头。 # # sudo ./install.sh # 从当前 checkout 安装 # sudo REPO=git@host:jack/chan.git ./install.sh # 或指定远程克隆 # # 幂等:重复跑只会更新代码与依赖,不动 /etc/chan-live/live.env 和状态目录。 set -euo pipefail APP=/opt/chan STATE=/var/lib/chan-live CONF=/etc/chan-live USER_NAME=chan BRANCH="${BRANCH:-chan}" REPO="${REPO:-}" die() { echo "⛔ $*" >&2; exit 1; } say() { echo " $*"; } [[ $EUID -eq 0 ]] || die "要 root:sudo $0" # ── 1. 系统依赖 ──────────────────────────────────────────────────── say "装系统包" if command -v apt-get >/dev/null; then export DEBIAN_FRONTEND=noninteractive apt-get update -qq # chrony 不是可选项:staleness 闸靠两机时钟一致才有意义, # 采集机时钟快 5 分钟就等于把闸放宽 5 分钟(见 ship_signals.py) apt-get install -y -qq python3-venv python3-pip git chrony openssh-client elif command -v dnf >/dev/null; then dnf install -y -q python3 python3-pip git chrony openssh-clients else die "只认 apt/dnf,其他发行版请手工装 python3-venv git chrony" fi systemctl enable --now chrony 2>/dev/null || systemctl enable --now chronyd # ── 2. 专用用户与目录 ────────────────────────────────────────────── if ! id -u "$USER_NAME" >/dev/null 2>&1; then say "建系统用户 $USER_NAME(无登录 shell)" useradd --system --home-dir "$STATE/home" --create-home \ --shell /usr/sbin/nologin "$USER_NAME" fi install -d -o "$USER_NAME" -g "$USER_NAME" -m 750 "$STATE" "$STATE/state" "$STATE/home" install -d -o "$USER_NAME" -g "$USER_NAME" -m 700 "$STATE/home/.ssh" install -d -o root -g "$USER_NAME" -m 750 "$CONF" # ── 3. 代码 ──────────────────────────────────────────────────────── if [[ -n "$REPO" ]]; then if [[ -d "$APP/.git" ]]; then say "更新已有 checkout" git -C "$APP" fetch --quiet origin "$BRANCH" git -C "$APP" checkout --quiet "$BRANCH" git -C "$APP" reset --hard --quiet "origin/$BRANCH" else say "克隆 $REPO" rm -rf "$APP"; git clone --quiet --branch "$BRANCH" "$REPO" "$APP" fi else SRC="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" [[ -f "$SRC/live/live_exec.py" ]] || die "$SRC 不像仓库根(缺 live/live_exec.py)" if [[ "$SRC" != "$APP" ]]; then say "从 $SRC 同步代码到 $APP" install -d "$APP" # 只同步生产要的那一个子树。研究侧的 research/ 不上生产机: # 它带 pandas/pyarrow/hummingbot,而且探针 OOM 过一次(14.9GB) cp -a "$SRC/live" "$APP/" fi fi chown -R root:root "$APP/live" find "$APP/live" -type f -exec chmod 644 {} + ; chmod 755 "$APP/live" "$APP/live/deploy" chmod 755 "$APP"/live/deploy/*.sh # ── 4. venv ─────────────────────────────────────────────────────── say "建 venv 并装依赖(应当只有 aiohttp)" [[ -d "$APP/.venv" ]] || python3 -m venv "$APP/.venv" "$APP/.venv/bin/pip" install --quiet --upgrade pip "$APP/.venv/bin/pip" install --quiet -r "$APP/live/requirements.txt" # 断言生产进程没被拖进重量级依赖。这条会真挡住——曾经为读两个常量 # import 研究侧的 step43,把 numpy/pandas/pyarrow 全拉进实盘进程 say "验依赖面" "$APP/.venv/bin/python" - <<'PY' || die "生产进程拖进了重量级依赖,看上面输出" import sys sys.path.insert(0, "/opt/chan/live") import live_exec live_exec.assert_decomposable() heavy = [m for m in ("numpy", "pandas", "pyarrow", "hummingbot", "scipy") if m in sys.modules] if heavy: print(f" ⛔ 启动路径加载了 {heavy}") raise SystemExit(1) print(f" ✓ 只有标准库 + aiohttp · 出场结构 " f"{live_exec.SL_ATR}/{live_exec.SCALE_ATR}/{live_exec.RUNNER_ATR} ATR " f"/{live_exec.MAXB} 根") PY # ── 5. 配置模板 ──────────────────────────────────────────────────── if [[ ! -f "$CONF/live.env" ]]; then say "写配置模板 $CONF/live.env(密钥要你手工填)" install -o root -g "$USER_NAME" -m 640 \ "$APP/live/deploy/live.env.example" "$CONF/live.env" NEED_FILL=1 else say "$CONF/live.env 已存在,不动" fi # ── 6. systemd ──────────────────────────────────────────────────── say "装 systemd 单元" install -m 644 "$APP"/live/deploy/chan-live-*.service /etc/systemd/system/ systemctl daemon-reload echo echo "装好了。接下来按顺序做(**不要**跳过空跑那步):" echo if [[ -n "${NEED_FILL:-}" ]]; then echo " 1. 填密钥与参数:sudo vi $CONF/live.env" echo " BITGET_API_KEY / SECRET / PASSPHRASE 用只读+交易权限," echo " **不要开提币权限**。SHIP_FROM 填采集机的 ssh 目标。" echo fi echo " 2. 装拉总线用的 ssh key,并确认能连上采集机:" echo " sudo -u $USER_NAME ssh-keygen -t ed25519 -N '' -f $STATE/home/.ssh/id_ed25519" echo " # 把 $STATE/home/.ssh/id_ed25519.pub 加到采集机的 authorized_keys" echo " sudo -u $USER_NAME ssh -o BatchMode=yes <采集机> 'echo ok'" echo echo " 3. 空跑验全链(不下真单,跑够看到一次心跳再停):" echo " sudo $APP/live/deploy/dryrun.sh" echo echo " 4. 真跑:" echo " sudo systemctl enable --now chan-live-ship chan-live-exec" echo " $APP/live/deploy/status.sh"