184 lines
5.4 KiB
Bash
184 lines
5.4 KiB
Bash
#!/usr/bin/env bash
|
||
set -Eeuo pipefail
|
||
|
||
REPO_ROOT="${AIOS_REPO_ROOT:-$(cd "$(dirname "${BASH_SOURCE[0]}")/../../.." && pwd)}"
|
||
BASE_REF="${AIOS_DOCS_BASE_REF:-}"
|
||
RUN_LIVE_HEALTHCHECK="${AIOS_RUN_LIVE_HEALTHCHECK:-0}"
|
||
RESULT_DIR="${AIOS_ACCEPTANCE_RESULT_DIR:-$REPO_ROOT/.aios-evidence/acceptance}"
|
||
STAMP="$(date -u +%Y%m%dT%H%M%SZ)"
|
||
RUN_DIR="$RESULT_DIR/$STAMP"
|
||
SUMMARY="$RUN_DIR/summary.json"
|
||
|
||
install -d -m 0750 "$RUN_DIR"
|
||
|
||
pass=0
|
||
failed=0
|
||
warnings=0
|
||
steps=()
|
||
|
||
record() {
|
||
local name="$1" status="$2" code="$3" log="$4"
|
||
steps+=("$name|$status|$code|$log")
|
||
case "$status" in
|
||
通过) pass=$((pass + 1)) ;;
|
||
失败) failed=$((failed + 1)) ;;
|
||
警告) warnings=$((warnings + 1)) ;;
|
||
esac
|
||
printf '%s:%s(退出码 %s)\n' "$status" "$name" "$code"
|
||
}
|
||
|
||
run_step() {
|
||
local name="$1"
|
||
shift
|
||
local safe_name
|
||
safe_name="$(printf '%s' "$name" | tr ' /:' '___')"
|
||
local log="$RUN_DIR/${safe_name}.log"
|
||
set +e
|
||
(cd "$REPO_ROOT" && "$@") >"$log" 2>&1
|
||
local code=$?
|
||
set -e
|
||
if [[ "$code" -eq 0 ]]; then
|
||
record "$name" "通过" "$code" "$log"
|
||
else
|
||
record "$name" "失败" "$code" "$log"
|
||
fi
|
||
}
|
||
|
||
run_optional_step() {
|
||
local name="$1"
|
||
shift
|
||
local safe_name
|
||
safe_name="$(printf '%s' "$name" | tr ' /:' '___')"
|
||
local log="$RUN_DIR/${safe_name}.log"
|
||
set +e
|
||
(cd "$REPO_ROOT" && "$@") >"$log" 2>&1
|
||
local code=$?
|
||
set -e
|
||
if [[ "$code" -eq 0 ]]; then
|
||
record "$name" "通过" "$code" "$log"
|
||
else
|
||
record "$name" "警告" "$code" "$log"
|
||
fi
|
||
}
|
||
|
||
printf 'AIOS V2R1 Candidate 全量验收开始\n'
|
||
printf '仓库:%s\n' "$REPO_ROOT"
|
||
printf '结果目录:%s\n' "$RUN_DIR"
|
||
|
||
[[ -d "$REPO_ROOT" ]] || { echo "错误:仓库不存在" >&2; exit 2; }
|
||
|
||
DOCS_ARGS=(python3 tools/aios_v2_docs_code_check.py --repo "$REPO_ROOT")
|
||
if [[ -n "$BASE_REF" ]]; then
|
||
DOCS_ARGS+=(--base-ref "$BASE_REF")
|
||
fi
|
||
run_step "文档与代码匹配" "${DOCS_ARGS[@]}"
|
||
|
||
run_step "Python 语法编译" python3 -m compileall -q \
|
||
active-runtime/app/aios_v2 \
|
||
active-runtime/app/aios_openclaw \
|
||
active-runtime/app/aios_hermes
|
||
|
||
run_step "候选单元与集成测试" python3 -m unittest discover \
|
||
-s tests/aios_v2_candidate \
|
||
-p 'test_*.py' \
|
||
-v
|
||
|
||
run_step "Shell 脚本语法" bash -c '
|
||
set -Eeuo pipefail
|
||
while IFS= read -r -d "" path; do
|
||
bash -n "$path"
|
||
done < <(find deploy/aios-v2 -type f -name "*.sh" -print0)
|
||
'
|
||
|
||
run_step "文档 Git 冲突标记" bash -c '
|
||
set -Eeuo pipefail
|
||
if grep -RInE "^(<<<<<<<|=======|>>>>>>>)" docs/aios-v2; then
|
||
exit 1
|
||
fi
|
||
'
|
||
|
||
run_step "明文密钥基础扫描" python3 -c '
|
||
from pathlib import Path
|
||
import re
|
||
import sys
|
||
|
||
roots = [Path("active-runtime/app"), Path("deploy/aios-v2"), Path("docs/aios-v2"), Path("tasks")]
|
||
patterns = [
|
||
re.compile(r"(?i)(api[_-]?key|password|private[_-]?key|access[_-]?token)\s*[:=]\s*[\"\x27][^\"\x27\n]{12,}[\"\x27]"),
|
||
re.compile(r"-----BEGIN (?:RSA |EC |OPENSSH )?PRIVATE KEY-----"),
|
||
]
|
||
violations = []
|
||
for root in roots:
|
||
if not root.exists():
|
||
continue
|
||
for path in root.rglob("*"):
|
||
if not path.is_file() or path.suffix in {".pyc", ".db", ".sqlite3"}:
|
||
continue
|
||
text = path.read_text(encoding="utf-8", errors="ignore")
|
||
if any(pattern.search(text) for pattern in patterns):
|
||
violations.append(str(path))
|
||
if violations:
|
||
print("疑似明文密钥文件:", *violations, sep="\n", file=sys.stderr)
|
||
raise SystemExit(1)
|
||
'
|
||
|
||
if command -v systemd-analyze >/dev/null 2>&1; then
|
||
run_optional_step "systemd 单元验证" systemd-analyze verify \
|
||
deploy/aios-v2/systemd/aios-v2.service
|
||
else
|
||
record "systemd 单元验证" "警告" "127" "未安装 systemd-analyze"
|
||
fi
|
||
|
||
if command -v docker >/dev/null 2>&1 && docker compose version >/dev/null 2>&1; then
|
||
run_optional_step "Docker Compose 配置验证" docker compose \
|
||
-f deploy/aios-v2/docker/compose.yaml config
|
||
else
|
||
record "Docker Compose 配置验证" "警告" "127" "未安装 Docker Compose"
|
||
fi
|
||
|
||
if [[ "$RUN_LIVE_HEALTHCHECK" == "1" ]]; then
|
||
run_step "在线健康检查" bash deploy/aios-v2/healthcheck/check.sh
|
||
else
|
||
record "在线健康检查" "警告" "0" "未启用 AIOS_RUN_LIVE_HEALTHCHECK=1"
|
||
fi
|
||
|
||
branch="$(git -C "$REPO_ROOT" branch --show-current 2>/dev/null || true)"
|
||
commit="$(git -C "$REPO_ROOT" rev-parse HEAD 2>/dev/null || true)"
|
||
dirty="$(git -C "$REPO_ROOT" status --porcelain=v1 2>/dev/null || true)"
|
||
|
||
python3 - "$SUMMARY" "$branch" "$commit" "$dirty" "$pass" "$failed" "$warnings" "${steps[@]}" <<'PY'
|
||
import json
|
||
import pathlib
|
||
import sys
|
||
from datetime import datetime, timezone
|
||
|
||
summary_path = pathlib.Path(sys.argv[1])
|
||
branch, commit, dirty = sys.argv[2:5]
|
||
passed, failed, warnings = map(int, sys.argv[5:8])
|
||
steps = []
|
||
for item in sys.argv[8:]:
|
||
name, status, code, log = item.split("|", 3)
|
||
steps.append({"name": name, "status": status, "exit_code": int(code), "log": log})
|
||
payload = {
|
||
"schema": "aios.v2.acceptance.v1",
|
||
"generated_at": datetime.now(timezone.utc).isoformat().replace("+00:00", "Z"),
|
||
"branch": branch or None,
|
||
"commit": commit or None,
|
||
"workspace_dirty": bool(dirty),
|
||
"passed": passed,
|
||
"failed": failed,
|
||
"warnings": warnings,
|
||
"steps": steps,
|
||
"final_status": "passed" if failed == 0 else "failed",
|
||
}
|
||
summary_path.write_text(json.dumps(payload, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
|
||
print(json.dumps(payload, ensure_ascii=False, indent=2))
|
||
PY
|
||
|
||
printf '验收结果:通过 %s,失败 %s,警告 %s\n' "$pass" "$failed" "$warnings"
|
||
printf '证据:%s\n' "$SUMMARY"
|
||
|
||
if [[ "$failed" -ne 0 ]]; then
|
||
exit 1
|
||
fi
|