采集机那侧的 authorized_keys 用了强制命令,把这把 key 锁成只能跑 tail, 拿不到 shell。副作用是任何请求都变成 tail -F 而它永不返回,原先 `ssh <host> 'echo ok'` 的探测会永久挂住——ConnectTimeout 只管建连,不管 命令时长。改成照 ship_signals 的真实用法读流:tail -c +0 先吐出整个文件, 数行数就等于对端总线条数,之后由 timeout 收掉。 失败诊断原先一律说"装 key",但 Host key verification failed 是 known_hosts 空的,装 key 修不了,会把人引到错方向。现在分三类:指纹未确认、认证被拒、 其他,各给对应做法。指纹那条明确不建议 StrictHostKeyChecking=no——这条链路 上跑的是下单信号。 install.sh 的第 2 步补上 known_hosts:原先只说装 key,而服务跑起来会撞同一 个墙(BatchMode=yes 不允许交互确认)。 实测:强制命令下故意请求错路径,仍拿到真总线内容且 stderr 干净。 Co-authored-by: Cursor <cursoragent@cursor.com>
104 lines
4.9 KiB
Bash
Executable File
104 lines
4.9 KiB
Bash
Executable File
#!/usr/bin/env bash
|
||
# 空跑验全链:真连交易所读规则/持仓,**不下任何真单**。
|
||
#
|
||
# 上真单之前必须过这一步。它验的是那些"上线才会暴露、且暴露方式是花钱"的
|
||
# 环节:密钥能不能用、IP 白名单对不对、时钟同不同步、搬运通不通、
|
||
# 数量与价位取整合不合交易所规则。
|
||
set -euo pipefail
|
||
|
||
APP=/opt/chan
|
||
CONF=/etc/chan-live/live.env
|
||
STATE=/var/lib/chan-live
|
||
USER_NAME=chan
|
||
SECS="${SECS:-90}"
|
||
|
||
die() { echo "⛔ $*" >&2; exit 1; }
|
||
ok() { echo " ✓ $*"; }
|
||
|
||
[[ $EUID -eq 0 ]] || die "要 root:sudo $0"
|
||
[[ -f "$CONF" ]] || die "缺 $CONF,先跑 install.sh"
|
||
|
||
set -a; . "$CONF"; set +a
|
||
|
||
echo "── 1. 配置自检 ──"
|
||
for v in BITGET_API_KEY BITGET_API_SECRET BITGET_PASSPHRASE SHIP_FROM; do
|
||
[[ -n "${!v:-}" ]] || die "$CONF 里 $v 还是空的"
|
||
done
|
||
ok "密钥三项与 SHIP_FROM 都已填"
|
||
[[ "$LIVE_HOME" != /opt/chan* ]] || die "LIVE_HOME 不能指到仓库里(会被 git 清掉)"
|
||
ok "LIVE_HOME=$LIVE_HOME 在仓库外"
|
||
|
||
echo "── 2. 时钟 ──"
|
||
# staleness 闸靠两机时钟一致才有意义。这里只能验本机;跨机偏差由
|
||
# ship_signals 在收到信号时报「时间倒流」
|
||
if command -v chronyc >/dev/null; then
|
||
chronyc tracking | grep -E "Reference ID|System time|Leap status" | sed 's/^/ /'
|
||
src="$(chronyc tracking | awk '/Reference ID/{print $NF}')"
|
||
[[ "$src" != "()" && -n "$src" ]] || die "chrony 还没同步上,等一会再跑"
|
||
ok "chrony 已同步"
|
||
else
|
||
die "没装 chrony。staleness 闸不可信,先 apt install chrony"
|
||
fi
|
||
|
||
echo "── 3. 出口 IP 是否在白名单内 ──"
|
||
myip="$(curl -s --max-time 10 https://api.ipify.org || true)"
|
||
[[ -n "$myip" ]] && echo " 本机出口 IP:$myip" || echo " ⚠ 取不到出口 IP"
|
||
echo " 对照 Bitget 后台该 key 的 IP 白名单,不一致下面会报 40018 之类"
|
||
|
||
echo "── 4. 能否 ssh 到采集机 ──"
|
||
# 不能用 ssh <host> 'echo ok' 来试:采集机那侧的 authorized_keys 用了强制命令
|
||
# (把这把 key 锁成只能跑 tail,拿不到 shell),任何请求都会变成 tail -F,
|
||
# 而它**永不返回**——ConnectTimeout 只管建连不管命令时长,测试会永久挂住。
|
||
# 所以照 ship_signals 的真实用法读流:tail -c +0 会先把整个文件吐出来,
|
||
# 数一下就等于对端总线的条数,之后它挂着等新内容,由 timeout 收掉。
|
||
rb="${SHIP_REMOTE_BUS:-\$HOME/chan-live/state/signals_live.jsonl}"
|
||
err="$(mktemp)"; outf="$(mktemp)"
|
||
timeout 12 sudo -u "$USER_NAME" ssh -T -o BatchMode=yes -o ConnectTimeout=10 \
|
||
"$SHIP_FROM" "tail -c +0 -F $rb" >"$outf" 2>"$err" || true
|
||
n="$(wc -l < "$outf")"
|
||
if grep -qi "Host key verification failed" "$err"; then
|
||
fp="(在采集机上跑 ssh-keygen -lf /etc/ssh/ssh_host_ed25519_key.pub 拿)"
|
||
rm -f "$err" "$outf"
|
||
die "主机指纹没确认过。这不是 key 的问题,装 key 也修不了。
|
||
$USER_NAME 的 known_hosts 是空的,而 BatchMode=yes 不允许交互确认。
|
||
**不要**用 StrictHostKeyChecking=no 糊过去,那等于放弃中间人防护。
|
||
正确做法:在采集机上读出权威指纹 $fp,
|
||
再在这台上写入并核对:
|
||
sudo -u $USER_NAME ssh-keyscan -t ed25519 <采集机IP> \\
|
||
| sudo -u $USER_NAME tee -a $STATE/home/.ssh/known_hosts
|
||
ssh-keygen -lf $STATE/home/.ssh/known_hosts # 比对指纹是否一致"
|
||
elif grep -qiE "Permission denied|publickey" "$err"; then
|
||
rm -f "$err" "$outf"
|
||
die "认证被拒。指纹是通的,是 key 没装到采集机上:
|
||
sudo -u $USER_NAME ssh-keygen -t ed25519 -N '' -f $STATE/home/.ssh/id_ed25519
|
||
再把 $STATE/home/.ssh/id_ed25519.pub 加到采集机的 authorized_keys"
|
||
elif [[ -s "$err" ]] && ! grep -q "^" "$outf" 2>/dev/null; then
|
||
msg="$(head -3 "$err")"; rm -f "$err" "$outf"
|
||
die "ssh $SHIP_FROM 不通:$msg"
|
||
else
|
||
ok "ssh $SHIP_FROM 通"
|
||
echo " 对端总线现有 $n 条信号"
|
||
[[ "$n" -gt 0 ]] || echo " ⚠ 对端总线是空的。信号 6.8 个/天,刚重启过就是空的很正常;但要确认采集机接了总线"
|
||
rm -f "$err" "$outf"
|
||
fi
|
||
|
||
echo "── 5. 执行器空跑 ${SECS}s(真连交易所,不下单)──"
|
||
# --dry-run 下 Bitget 客户端只读不写:合约规则、持仓、费率照查,
|
||
# 下单一律只打印。所以这一步会真实验到密钥与白名单
|
||
# 让 chan 自己 source 配置($CONF 是 640 root:chan,它读得到)。
|
||
# 不用 `env "$(grep ...)"` 那种拼法:值里有空格就会被切开
|
||
set +e
|
||
sudo -u "$USER_NAME" bash -c \
|
||
"set -a; . '$CONF'; set +a; exec timeout $SECS \
|
||
'$APP/.venv/bin/python' '$APP/live/live_exec.py' --dry-run"
|
||
rc=$?
|
||
set -e
|
||
# timeout 到点是 124,属于预期
|
||
[[ $rc -eq 124 || $rc -eq 0 ]] || die "空跑退出码 $rc,看上面报错"
|
||
ok "空跑没有报错退出"
|
||
|
||
echo
|
||
echo "空跑过了。真跑:"
|
||
echo " sudo systemctl enable --now chan-live-ship chan-live-exec"
|
||
echo " $APP/live/deploy/status.sh"
|