fix(deploy): SSH secrets 加载、Docker 运行时检测与外网探测脚本修复
使 deploy-production.sh 可读本机 secrets 并在非 docker 组用户下用 sudo 识别 1Panel 栈;修复 security_probe_external.sh 的 curl shift 误用与 WebSocket 自动 venv。
This commit is contained in:
@@ -17,6 +17,12 @@ set -euo pipefail
|
||||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
cd "${ROOT}"
|
||||
|
||||
SECRETS="${ROOT}/deploy/nexus-1panel.secrets.sh"
|
||||
if [[ -f "$SECRETS" ]]; then
|
||||
# shellcheck disable=SC1090
|
||||
source "$SECRETS"
|
||||
fi
|
||||
|
||||
NEXUS_SSH_HOST="${NEXUS_SSH:-nexus}"
|
||||
SSH_OPTS=(-o BatchMode=yes -o ConnectTimeout=15)
|
||||
if [[ -n "${NEXUS_SSH_KEY:-}" ]]; then
|
||||
@@ -36,22 +42,34 @@ fi
|
||||
|
||||
REMOTE_DETECT=$(ssh_cmd 'bash -s' <<'REMOTE'
|
||||
set -euo pipefail
|
||||
docker_cmd() {
|
||||
if docker "$@" 2>/dev/null; then return 0; fi
|
||||
sudo docker "$@" 2>/dev/null
|
||||
}
|
||||
compose_cmd() {
|
||||
if docker compose "$@" 2>/dev/null; then return 0; fi
|
||||
sudo docker compose "$@" 2>/dev/null
|
||||
}
|
||||
for d in /opt/nexus /www/wwwroot/api.synaglobal.vip; do
|
||||
[[ -d "$d/server" ]] || continue
|
||||
echo "ROOT=$d"
|
||||
if [[ -f "$d/docker/.env.prod" ]] && docker compose version >/dev/null 2>&1; then
|
||||
if [[ -f "$d/docker/.env.prod" ]] && compose_cmd version >/dev/null 2>&1; then
|
||||
prof="$d/docker/.host-profile"
|
||||
[[ -f "$prof" ]] || prof="$d/docker/profiles/2c8g.env"
|
||||
if [[ -f "$prof" ]]; then
|
||||
args=(-f "$d/docker/docker-compose.prod.yml")
|
||||
docker network inspect 1panel-network >/dev/null 2>&1 && \
|
||||
docker_cmd network inspect 1panel-network >/dev/null 2>&1 && \
|
||||
[[ -f "$d/docker/docker-compose.1panel.yml" ]] && \
|
||||
args+=(-f "$d/docker/docker-compose.1panel.yml")
|
||||
q=$(cd "$d" && docker compose "${args[@]}" \
|
||||
q=$(cd "$d" && compose_cmd "${args[@]}" \
|
||||
--env-file "$d/docker/.env.prod" --env-file "$prof" ps -q nexus 2>/dev/null || true)
|
||||
[[ -n "$q" ]] && echo RUNTIME=docker && exit 0
|
||||
fi
|
||||
fi
|
||||
if [[ -f "$d/deploy/nexus-1panel.sh" ]] && command -v nx >/dev/null 2>&1; then
|
||||
q=$(docker_cmd ps --format '{{.Names}}' 2>/dev/null | grep -E '^nexus-prod-nexus-' || true)
|
||||
[[ -n "$q" ]] && echo RUNTIME=docker && exit 0
|
||||
fi
|
||||
if command -v supervisorctl >/dev/null 2>&1 && supervisorctl status nexus >/dev/null 2>&1; then
|
||||
echo RUNTIME=supervisor
|
||||
exit 0
|
||||
|
||||
@@ -226,3 +226,43 @@
|
||||
{"ts":"2026-06-07T12:02:49+08:00","date":"2026-06-07","gate":"shell_eol","result":"PASS","detail":"all tracked sh LF"}
|
||||
{"ts":"2026-06-07T12:02:49+08:00","date":"2026-06-07","gate":"text_eol","result":"PASS","detail":"git index LF"}
|
||||
{"ts":"2026-06-07T12:02:49+08:00","date":"2026-06-07","gate":"summary","result":"PASS","detail":"7/7"}
|
||||
{"ts":"2026-06-07T12:32:11+08:00","date":"2026-06-07","gate":"changelog","result":"PASS","detail":"2026-06-07-code-review-followups.md 42lines"}
|
||||
{"ts":"2026-06-07T12:32:11+08:00","date":"2026-06-07","gate":"audit","result":"PASS","detail":"2026-06-07-code-review-followups.md"}
|
||||
{"ts":"2026-06-07T12:32:11+08:00","date":"2026-06-07","gate":"test","result":"PASS","detail":"all passed"}
|
||||
{"ts":"2026-06-07T12:32:11+08:00","date":"2026-06-07","gate":"lint","result":"PASS","detail":"0 violations"}
|
||||
{"ts":"2026-06-07T12:32:11+08:00","date":"2026-06-07","gate":"import","result":"PASS","detail":"ok"}
|
||||
{"ts":"2026-06-07T12:32:11+08:00","date":"2026-06-07","gate":"security","result":"PASS","detail":"0 HIGH"}
|
||||
{"ts":"2026-06-07T12:32:11+08:00","date":"2026-06-07","gate":"review","result":"PASS","detail":"audit covers changes"}
|
||||
{"ts":"2026-06-07T12:32:11+08:00","date":"2026-06-07","gate":"shell_eol","result":"PASS","detail":"all tracked sh LF"}
|
||||
{"ts":"2026-06-07T12:32:11+08:00","date":"2026-06-07","gate":"text_eol","result":"PASS","detail":"git index LF"}
|
||||
{"ts":"2026-06-07T12:32:11+08:00","date":"2026-06-07","gate":"summary","result":"PASS","detail":"7/7"}
|
||||
{"ts":"2026-06-07T12:35:38+08:00","date":"2026-06-07","gate":"changelog","result":"PASS","detail":"2026-06-07-deploy-ssh-secrets.md 29lines"}
|
||||
{"ts":"2026-06-07T12:35:38+08:00","date":"2026-06-07","gate":"audit","result":"PASS","detail":"2026-06-07-code-review-followups.md"}
|
||||
{"ts":"2026-06-07T12:35:38+08:00","date":"2026-06-07","gate":"test","result":"PASS","detail":"all passed"}
|
||||
{"ts":"2026-06-07T12:35:38+08:00","date":"2026-06-07","gate":"lint","result":"PASS","detail":"0 violations"}
|
||||
{"ts":"2026-06-07T12:35:38+08:00","date":"2026-06-07","gate":"import","result":"PASS","detail":"ok"}
|
||||
{"ts":"2026-06-07T12:35:38+08:00","date":"2026-06-07","gate":"security","result":"PASS","detail":"0 HIGH"}
|
||||
{"ts":"2026-06-07T12:35:38+08:00","date":"2026-06-07","gate":"review","result":"BLOCK","detail":"audit missing changed files"}
|
||||
{"ts":"2026-06-07T12:35:38+08:00","date":"2026-06-07","gate":"shell_eol","result":"PASS","detail":"all tracked sh LF"}
|
||||
{"ts":"2026-06-07T12:35:38+08:00","date":"2026-06-07","gate":"text_eol","result":"PASS","detail":"git index LF"}
|
||||
{"ts":"2026-06-07T12:35:38+08:00","date":"2026-06-07","gate":"summary","result":"BLOCK","detail":"6/7"}
|
||||
{"ts":"2026-06-07T12:35:53+08:00","date":"2026-06-07","gate":"changelog","result":"PASS","detail":"2026-06-07-deploy-ssh-secrets.md 29lines"}
|
||||
{"ts":"2026-06-07T12:35:53+08:00","date":"2026-06-07","gate":"audit","result":"PASS","detail":"2026-06-07-code-review-followups.md"}
|
||||
{"ts":"2026-06-07T12:35:53+08:00","date":"2026-06-07","gate":"test","result":"PASS","detail":"all passed"}
|
||||
{"ts":"2026-06-07T12:35:53+08:00","date":"2026-06-07","gate":"lint","result":"PASS","detail":"0 violations"}
|
||||
{"ts":"2026-06-07T12:35:53+08:00","date":"2026-06-07","gate":"import","result":"PASS","detail":"ok"}
|
||||
{"ts":"2026-06-07T12:35:53+08:00","date":"2026-06-07","gate":"security","result":"PASS","detail":"0 HIGH"}
|
||||
{"ts":"2026-06-07T12:35:53+08:00","date":"2026-06-07","gate":"review","result":"PASS","detail":"audit covers changes"}
|
||||
{"ts":"2026-06-07T12:35:53+08:00","date":"2026-06-07","gate":"shell_eol","result":"PASS","detail":"all tracked sh LF"}
|
||||
{"ts":"2026-06-07T12:35:53+08:00","date":"2026-06-07","gate":"text_eol","result":"PASS","detail":"git index LF"}
|
||||
{"ts":"2026-06-07T12:35:53+08:00","date":"2026-06-07","gate":"summary","result":"PASS","detail":"7/7"}
|
||||
{"ts":"2026-06-07T17:25:18+08:00","date":"2026-06-07","gate":"changelog","result":"PASS","detail":"2026-06-07-probe-curl-parse-fix.md 35lines"}
|
||||
{"ts":"2026-06-07T17:25:18+08:00","date":"2026-06-07","gate":"audit","result":"PASS","detail":"2026-06-07-code-review-followups.md"}
|
||||
{"ts":"2026-06-07T17:25:18+08:00","date":"2026-06-07","gate":"test","result":"PASS","detail":"all passed"}
|
||||
{"ts":"2026-06-07T17:25:18+08:00","date":"2026-06-07","gate":"lint","result":"PASS","detail":"0 violations"}
|
||||
{"ts":"2026-06-07T17:25:18+08:00","date":"2026-06-07","gate":"import","result":"PASS","detail":"ok"}
|
||||
{"ts":"2026-06-07T17:25:18+08:00","date":"2026-06-07","gate":"security","result":"PASS","detail":"0 HIGH"}
|
||||
{"ts":"2026-06-07T17:25:18+08:00","date":"2026-06-07","gate":"review","result":"PASS","detail":"audit covers changes"}
|
||||
{"ts":"2026-06-07T17:25:18+08:00","date":"2026-06-07","gate":"shell_eol","result":"PASS","detail":"all tracked sh LF"}
|
||||
{"ts":"2026-06-07T17:25:18+08:00","date":"2026-06-07","gate":"text_eol","result":"PASS","detail":"git index LF"}
|
||||
{"ts":"2026-06-07T17:25:18+08:00","date":"2026-06-07","gate":"summary","result":"PASS","detail":"7/7"}
|
||||
|
||||
@@ -15,3 +15,7 @@ NEXUS_DOMAIN='api.synaglobal.vip'
|
||||
NEXUS_ROOT='/opt/nexus'
|
||||
NEXUS_PROFILE='2c8g'
|
||||
NEXUS_FROM_ENV='/www/wwwroot/api.synaglobal.vip/.env'
|
||||
|
||||
# 本机生产 SSH(deploy-production.sh 读取;私钥路径勿提交真实 .pem)
|
||||
# NEXUS_SSH='azureuser@20.24.218.235'
|
||||
# NEXUS_SSH_KEY='/path/to/nz-admin.pem'
|
||||
|
||||
@@ -14,12 +14,18 @@
|
||||
| `web/agent/agent.py` | 心跳 401 退出循环(对齐 BL-06 中心端) |
|
||||
| `server/api/install.py` | 路由级 lock Depends;归档先于写 lock;归档失败 fail-closed |
|
||||
| `.cursor/rules/nexus-security.mdc` | Agent 认证矩阵:仅 per-server key |
|
||||
| `scripts/security_probe_external.sh` | WS 无 token HTTP 403 → PASS |
|
||||
| `scripts/security_probe_external.sh` | curl `shift` 修复;`resolve_ws_python`;WS 403 → PASS |
|
||||
| `docs/changelog/2026-06-07-deploy-ssh-secrets.md` | 本机 SSH secrets 文档 |
|
||||
| `docs/changelog/2026-06-07-probe-curl-parse-fix.md` | 探测脚本 curl/WS 修复 |
|
||||
| `docs/reports/2026-06-07-bl07-deploy-verification.md` | 生产部署验证报告 |
|
||||
| `tests/test_install_locked_404.py` | create-admin / test-credentials 锁定 404;router Depends 断言 |
|
||||
| `tests/test_security_unit.py` | `_reject_if_locked` 期望 404 |
|
||||
| `deploy/install-nx-cli.sh` | CRLF → LF(门控 pre-flight) |
|
||||
| `deploy/update.sh` | CRLF → LF |
|
||||
| `deploy/gate_log.jsonl` | 门控运行记录(自动生成) |
|
||||
| `deploy/deploy-production.sh` | source `nexus-1panel.secrets.sh` 加载 SSH |
|
||||
| `deploy/nexus-1panel.secrets.sh.example` | SSH 变量占位 |
|
||||
| `docs/project/linux-dev-paths.md` | 本机 SSH/部署 SSOT |
|
||||
|
||||
## Step 3 规则扫描
|
||||
|
||||
@@ -30,6 +36,8 @@
|
||||
| H3 | 归档失败仍 lock 成功 | FIXED 归档先于 lock;OSError → RuntimeError → 500 |
|
||||
| H4 | 安全 SSOT 与 BL-06 漂移 | FIXED `nexus-security.mdc` |
|
||||
| H5 | 外网 WS 403 误判反代 | FIXED 探测脚本 PASS + 报告澄清 |
|
||||
| H6 | 探测 curl `shift 3` 误报 FAIL | FIXED `probe()` shift 2/3 + 独立 code 文件 |
|
||||
| H7 | WS 探测 SKIP(无 websockets) | FIXED `resolve_ws_python()` 自动 venv |
|
||||
|
||||
## Closure
|
||||
|
||||
@@ -39,7 +47,9 @@
|
||||
| H2 | FIXED | `install.py` `APIRouter(..., dependencies=[Depends(...)])` |
|
||||
| H3 | FIXED | `_finalize_install_lock` 先 `_archive_install_wizard_html` |
|
||||
| H4 | FIXED | `nexus-security.mdc` 认证矩阵 |
|
||||
| H5 | FIXED | `security_probe_external.sh` L167-168 |
|
||||
| H5 | FIXED | `security_probe_external.sh` WS 403 PASS |
|
||||
| H6 | FIXED | `probe()` `shift 2` + `code_file` |
|
||||
| H7 | FIXED | `resolve_ws_python()` |
|
||||
|
||||
## 验证
|
||||
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
# Changelog — 生产 SSH 凭据纳入 secrets + deploy 自动加载
|
||||
|
||||
**日期**:2026-06-07
|
||||
|
||||
## 摘要
|
||||
|
||||
本机生产 SSH 私钥路径写入 `deploy/nexus-1panel.secrets.sh`(gitignore);`deploy-production.sh` 启动时自动 source,与 `git-push.sh` 同一凭据文件。
|
||||
|
||||
## 动机
|
||||
|
||||
本机 `Host nexus` 未指向生产 VPS;部署脚本需 `NEXUS_SSH` / `NEXUS_SSH_KEY`,避免每次手动 export。
|
||||
|
||||
## 涉及文件
|
||||
|
||||
- `deploy/deploy-production.sh`
|
||||
- `deploy/nexus-1panel.secrets.sh.example`
|
||||
- `docs/project/linux-dev-paths.md`
|
||||
- `deploy/nexus-1panel.secrets.sh`(本地 only,不入 git)
|
||||
|
||||
## 迁移 / 重启
|
||||
|
||||
无。
|
||||
|
||||
## 验证
|
||||
|
||||
```bash
|
||||
source deploy/nexus-1panel.secrets.sh
|
||||
ssh -i "$NEXUS_SSH_KEY" "$NEXUS_SSH" "echo SSH_OK"
|
||||
```
|
||||
@@ -0,0 +1,35 @@
|
||||
# Changelog — 外网探测脚本 curl / WebSocket 修复
|
||||
|
||||
**日期**:2026-06-07
|
||||
|
||||
## 摘要
|
||||
|
||||
修复 `security_probe_external.sh` 中 `probe()` 对 `shift 3` 的误用,消除 `code=000000ok200` 类误报 `[FAIL]`;WebSocket 段自动解析项目 `venv` 或引导临时 venv 安装 `websockets`。
|
||||
|
||||
## 动机
|
||||
|
||||
`probe GET /path` 仅 2 个参数时,bash `shift 3` 不移位,`GET` 与 `/health` 被当作 curl 额外 URL,stdout 混入 `000` 错误与响应体,判定逻辑全部失真。
|
||||
|
||||
## 涉及文件
|
||||
|
||||
- `scripts/security_probe_external.sh`
|
||||
|
||||
## 变更
|
||||
|
||||
- GET:`shift 2`;非 GET:`shift 2` + `shift 1`(消费 body),剩余 `$@` 为额外 curl 参数
|
||||
- HTTP 状态码写入独立临时文件,避免与 body 混淆
|
||||
- 统一 `-X "$method"`
|
||||
- `script-callback` 探测 body 满足 Pydantic 最小长度,确保测到 401 而非 422
|
||||
- 新增 `resolve_ws_python()`:`NEXUS_PROBE_PYTHON` → `venv/` → 系统 python → `/tmp/nexus-probe-venv` + `websockets==14.1`
|
||||
|
||||
## 迁移 / 重启
|
||||
|
||||
无。
|
||||
|
||||
## 验证
|
||||
|
||||
```bash
|
||||
NEXUS_PROBE_BASE=https://api.synaglobal.vip bash scripts/security_probe_external.sh
|
||||
# GET /health → [PASS];install POST → [PASS] 404;无 code=000000... 误报
|
||||
# WebSocket → [PASS] HTTP 403(不再 [SKIP])
|
||||
```
|
||||
@@ -20,12 +20,16 @@
|
||||
|
||||
| 用途 | Linux |
|
||||
|------|--------|
|
||||
| SSH 配置 | `~/.ssh/config` 中 `Host nexus` |
|
||||
| 私钥 | `~/.ssh/id_rsa_nexus` |
|
||||
| 生产目录 | `/www/wwwroot/api.synaglobal.vip/` |
|
||||
| SSH 目标 | `azureuser@20.24.218.235`(生产 VPS) |
|
||||
| 私钥 | 本机路径写在 `deploy/nexus-1panel.secrets.sh` 的 `NEXUS_SSH_KEY`(**不入 git**) |
|
||||
| 一键部署 | `bash deploy/pre_deploy_check.sh && bash deploy/deploy-production.sh`(自动 source secrets) |
|
||||
| 生产目录 | `/opt/nexus`(Docker)或 `/www/wwwroot/api.synaglobal.vip/`(Supervisor) |
|
||||
|
||||
```bash
|
||||
ssh nexus "cd /www/wwwroot/api.synaglobal.vip && git pull && supervisorctl restart nexus"
|
||||
# secrets 已配置时无需 export;否则:
|
||||
# export NEXUS_SSH=azureuser@20.24.218.235
|
||||
# export NEXUS_SSH_KEY='/path/to/nz-admin.pem'
|
||||
bash deploy/deploy-production.sh
|
||||
```
|
||||
|
||||
## Cursor MCP
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
# BL-07 / code-review 跟进 — 生产部署验证(2026-06-07)
|
||||
|
||||
## Git
|
||||
|
||||
| 项 | 值 |
|
||||
|----|-----|
|
||||
| Commit | `72d82d7` — `fix(security): BL-07 WS 403 澄清与 code-review 跟进加固` |
|
||||
| 上一版 | `e5e2582` |
|
||||
| 远程 | Gitea `main` 已同步 |
|
||||
|
||||
## 部署
|
||||
|
||||
| 项 | 状态 |
|
||||
|----|------|
|
||||
| 触发 | 菜单 **1** → `deploy-production.sh` 运行时检测为 `unknown`(azureuser 无 docker 组) |
|
||||
| 实际执行 | `ssh azureuser@20.24.218.235` → `sudo bash /opt/nexus/deploy/nexus-1panel.sh upgrade` |
|
||||
| MySQL 备份 | `/var/backups/nexus/nexus-before-upgrade-20260607-043622.sql.gz` |
|
||||
| 容器 | `nexus-prod-nexus-1` 重建 **healthy** |
|
||||
| `/health` | `ok` |
|
||||
| `/app/` | HTTP 200 |
|
||||
| install.html | 已归档 → HTTP 404 |
|
||||
|
||||
## 外网探测(部署后)
|
||||
|
||||
`NEXUS_PROBE_BASE=https://api.synaglobal.vip`
|
||||
|
||||
| 探测项 | HTTP | 判定 |
|
||||
|--------|------|------|
|
||||
| `GET /health` | 200 | ✅ |
|
||||
| `GET /health/detail` | 401 | ✅ BL-02 |
|
||||
| `GET /openapi.json` `/docs` `/redoc` | 404 | ✅ BL-04 |
|
||||
| `GET /api/install/*` | 404 | ✅ BL-05 |
|
||||
| `POST /api/install/init-db` | **404** | ✅ 路由级 lock(此前为 422) |
|
||||
| `POST /api/install/create-admin` 等 | 404 | ✅ |
|
||||
| `POST /api/settings/bing-wallpapers/sync` | 401 | ✅ BL-03 |
|
||||
| Agent 无 key / 假 key | 422 / 401 | ✅ BL-06 |
|
||||
| `wss://.../ws/alerts` 无 token | **403** | ✅ BL-07 |
|
||||
| `wss://.../ws/sync` 无 token | **403** | ✅ BL-07 |
|
||||
|
||||
**备注**:2026-06-07 已修复 `security_probe_external.sh` 的 `shift 3` 误用;重跑应全 `[PASS]`(WS 需本机 `websockets` 包)。
|
||||
|
||||
## 本地门控(推送前)
|
||||
|
||||
```bash
|
||||
bash deploy/pre_deploy_check.sh # 7/7 PASS
|
||||
bash scripts/local_verify.sh # 全绿
|
||||
```
|
||||
|
||||
## 后续
|
||||
|
||||
- `deploy/deploy-production.sh` 已补 `sudo docker` 回退检测,下次菜单 **1** 应可自动识别 Docker。
|
||||
- 边端 Agent 需从 `/agent/agent.py` 拉取新版本(401 停循环);子机不自动升级。
|
||||
- 可选:修 probe 脚本解析、commit deploy-ssh 相关文档(不含 secrets)。
|
||||
@@ -3,8 +3,10 @@
|
||||
# Usage: NEXUS_PROBE_BASE=https://api.synaglobal.vip bash scripts/security_probe_external.sh
|
||||
set -euo pipefail
|
||||
|
||||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
BASE="${NEXUS_PROBE_BASE:-https://api.synaglobal.vip}"
|
||||
REPORT_DIR="${NEXUS_PROBE_REPORT_DIR:-/tmp/nexus-external-probe}"
|
||||
PROBE_VENV="${NEXUS_PROBE_VENV_DIR:-/tmp/nexus-probe-venv}"
|
||||
mkdir -p "$REPORT_DIR"
|
||||
OUT="$REPORT_DIR/probe-$(date -u +%Y%m%dT%H%M%SZ).txt"
|
||||
|
||||
@@ -15,24 +17,65 @@ fail() { log " [FAIL] $*"; }
|
||||
info() { log " [INFO] $*"; }
|
||||
warn() { log " [WARN] $*"; }
|
||||
|
||||
# Resolve a Python with `websockets` for BL-07 probes (project venv → system → bootstrap venv).
|
||||
resolve_ws_python() {
|
||||
local py cand
|
||||
if [[ -n "${NEXUS_PROBE_PYTHON:-}" && -x "${NEXUS_PROBE_PYTHON}" ]]; then
|
||||
if "${NEXUS_PROBE_PYTHON}" -c 'import websockets' 2>/dev/null; then
|
||||
echo "${NEXUS_PROBE_PYTHON}"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
for cand in "${ROOT}/venv/bin/python" "${ROOT}/.venv/bin/python"; do
|
||||
[[ -x "$cand" ]] || continue
|
||||
if "$cand" -c 'import websockets' 2>/dev/null; then
|
||||
echo "$cand"
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
if command -v python3 >/dev/null 2>&1; then
|
||||
if python3 -c 'import websockets' 2>/dev/null; then
|
||||
echo python3
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
if ! command -v python3 >/dev/null 2>&1; then
|
||||
return 1
|
||||
fi
|
||||
if [[ ! -x "${PROBE_VENV}/bin/python" ]]; then
|
||||
python3 -m venv "${PROBE_VENV}" 2>>"$OUT" || return 1
|
||||
fi
|
||||
if ! "${PROBE_VENV}/bin/python" -c 'import websockets' 2>/dev/null; then
|
||||
"${PROBE_VENV}/bin/pip" install -q "websockets==14.1" 2>>"$OUT" || return 1
|
||||
fi
|
||||
echo "${PROBE_VENV}/bin/python"
|
||||
}
|
||||
|
||||
# probe METHOD PATH [JSON_BODY] [extra curl args...]
|
||||
# Prints HTTP status code to stdout only.
|
||||
probe() {
|
||||
local method="$1" path="$2" body="${3:-}"
|
||||
shift 3 || true
|
||||
# shift 3 is wrong for GET (only 2 args): bash leaves $@ untouched when n > $#.
|
||||
shift 2
|
||||
if [[ "$method" != "GET" ]]; then
|
||||
shift 1
|
||||
fi
|
||||
local url="${BASE}${path}"
|
||||
local tmp
|
||||
local tmp code_file
|
||||
tmp="$(mktemp)"
|
||||
local -a curl_args=(-sS -o "$tmp" -w "%{http_code}" --max-time 20)
|
||||
if [[ "$method" == "GET" ]]; then
|
||||
:
|
||||
else
|
||||
curl_args+=(-X "$method" -H "Content-Type: application/json")
|
||||
code_file="$(mktemp)"
|
||||
local -a curl_args=(-sS -o "$tmp" -w "%{http_code}" --max-time 20 -X "$method")
|
||||
if [[ "$method" != "GET" ]]; then
|
||||
curl_args+=(-H "Content-Type: application/json")
|
||||
[[ -n "$body" ]] && curl_args+=(-d "$body")
|
||||
fi
|
||||
# shellcheck disable=SC2206
|
||||
local code
|
||||
code=$(curl "${curl_args[@]}" "$@" "$url" 2>>"$OUT" || echo "000")
|
||||
if ! curl "${curl_args[@]}" "$@" "$url" >"$code_file" 2>>"$OUT"; then
|
||||
code="000"
|
||||
else
|
||||
code="$(tr -d '\r\n' <"$code_file")"
|
||||
fi
|
||||
rm -f "$code_file"
|
||||
local snippet
|
||||
snippet="$(head -c 400 "$tmp" | tr '\n' ' ')"
|
||||
plog ""
|
||||
@@ -116,7 +159,7 @@ c=$(probe POST /api/agent/heartbeat '{"server_id":1,"is_online":true}' -H "X-API
|
||||
[[ "$c" == "401" || "$c" == "422" ]] && pass "POST /api/agent/heartbeat bad key rejected ($c)" || fail "code=$c"
|
||||
|
||||
c=$(probe POST /api/agent/script-callback \
|
||||
'{"job_id":"probe","server_id":1,"secret":"x","exit_code":0}' \
|
||||
'{"job_id":"probe0001","server_id":1,"secret":"invalid-probe-secret","exit_code":0}' \
|
||||
-H "X-API-Key: invalid-probe-key-000")
|
||||
[[ "$c" == "401" || "$c" == "404" || "$c" == "429" ]] && pass "POST script-callback bad key ($c)" || fail "code=$c"
|
||||
|
||||
@@ -136,16 +179,13 @@ c=$(probe GET /agent/install.sh)
|
||||
[[ "$c" == "200" ]] && info "GET /agent/install.sh public (expected)" || info "code=$c"
|
||||
|
||||
# ── WebSocket ──
|
||||
if command -v python3 >/dev/null 2>&1; then
|
||||
WS_PY="$(resolve_ws_python || true)"
|
||||
if [[ -n "${WS_PY}" ]]; then
|
||||
log ""
|
||||
log "── WebSocket probes"
|
||||
python3 - "$BASE" 2>>"$OUT" <<'PY' | tee -a "$OUT" || true
|
||||
log "── WebSocket probes (python=${WS_PY})"
|
||||
"${WS_PY}" - "$BASE" 2>>"$OUT" <<'PY' | tee -a "$OUT" || true
|
||||
import asyncio, sys
|
||||
try:
|
||||
import websockets
|
||||
except ImportError:
|
||||
print(" [SKIP] websockets not installed")
|
||||
sys.exit(0)
|
||||
import websockets
|
||||
|
||||
base = sys.argv[1].rstrip("/")
|
||||
ws_base = base.replace("https://", "wss://").replace("http://", "ws://")
|
||||
@@ -177,6 +217,8 @@ async def main():
|
||||
|
||||
asyncio.run(main())
|
||||
PY
|
||||
else
|
||||
warn "WebSocket probes skipped: no python with websockets (set NEXUS_PROBE_PYTHON or install venv)"
|
||||
fi
|
||||
|
||||
log ""
|
||||
|
||||
Reference in New Issue
Block a user