355 lines
11 KiB
Bash
Executable File
355 lines
11 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
RELEASE_ROOT="${DOMAINCHECK_RELEASE_ROOT:-${ROOT_DIR}/release}"
|
|
TIMESTAMP="${DOMAINCHECK_RELEASE_TIMESTAMP:-$(date +%Y%m%d_%H%M%S)}"
|
|
BASE_PACKAGE_NAME="domaincheck_release_${TIMESTAMP}"
|
|
PACKAGE_NAME="${BASE_PACKAGE_NAME}"
|
|
STAGING_ROOT="${RELEASE_ROOT}/${PACKAGE_NAME}"
|
|
ARCHIVE_PATH="${RELEASE_ROOT}/${PACKAGE_NAME}.tar.gz"
|
|
HASH_PATH="${RELEASE_ROOT}/${PACKAGE_NAME}.sha256.txt"
|
|
LATEST_TXT_PATH="${RELEASE_ROOT}/latest_release.txt"
|
|
LATEST_JSON_PATH="${RELEASE_ROOT}/latest_release.json"
|
|
SMOKE_PYTHON="${DOMAINCHECK_SMOKE_PYTHON:-${ROOT_DIR}/domainCheck/.venv/bin/python}"
|
|
SMOKE_RUNNER="${ROOT_DIR}/domain-api/deploy/linux/smoke_test.py"
|
|
SMOKE_OUTPUT="${STAGING_ROOT}/smoke_test_report.json"
|
|
API_BASE_URL="${API_BASE_URL:-http://127.0.0.1:8100}"
|
|
WEB_URL="${WEB_URL:-}"
|
|
SKIP_SMOKE_TEST="${DOMAINCHECK_SKIP_SMOKE_TEST:-0}"
|
|
RELEASE_COMMIT_SHA="${DOMAINCHECK_RELEASE_COMMIT_SHA:-}"
|
|
RELEASE_COMMIT_REF="${DOMAINCHECK_RELEASE_COMMIT_REF:-}"
|
|
|
|
copy_optional_item() {
|
|
local source_path="$1"
|
|
local destination_path="$2"
|
|
if [[ -e "${source_path}" ]]; then
|
|
cp -a "${source_path}" "${destination_path}"
|
|
fi
|
|
}
|
|
|
|
summarize_smoke_report() {
|
|
local report_path="$1"
|
|
python3 - "${report_path}" <<'PY'
|
|
import json
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
report_path = Path(sys.argv[1])
|
|
if not report_path.exists():
|
|
print("false\treport missing")
|
|
raise SystemExit(0)
|
|
|
|
payload = json.loads(report_path.read_text(encoding="utf-8"))
|
|
checks = [item for item in list(payload.get("checks") or []) if isinstance(item, dict)]
|
|
total = len(checks)
|
|
failed = [str(item.get("name") or "").strip() for item in checks if not bool(item.get("ok"))]
|
|
passed = total - len(failed)
|
|
ok = bool(payload.get("ok", False))
|
|
|
|
if ok:
|
|
message = f"all checks passed ({passed}/{total})" if total else "all checks passed"
|
|
else:
|
|
failed_preview = ", ".join([name for name in failed if name][:5])
|
|
if failed_preview:
|
|
message = f"{passed}/{total} checks passed; failed: {failed_preview}"
|
|
else:
|
|
message = f"{passed}/{total} checks passed"
|
|
|
|
print(f"{str(ok).lower()}\t{message}")
|
|
PY
|
|
}
|
|
|
|
mkdir -p "${RELEASE_ROOT}"
|
|
PACKAGE_SUFFIX=1
|
|
while [[ -e "${STAGING_ROOT}" || -e "${ARCHIVE_PATH}" || -e "${HASH_PATH}" ]]; do
|
|
PACKAGE_NAME="${BASE_PACKAGE_NAME}_${PACKAGE_SUFFIX}"
|
|
STAGING_ROOT="${RELEASE_ROOT}/${PACKAGE_NAME}"
|
|
ARCHIVE_PATH="${RELEASE_ROOT}/${PACKAGE_NAME}.tar.gz"
|
|
HASH_PATH="${RELEASE_ROOT}/${PACKAGE_NAME}.sha256.txt"
|
|
PACKAGE_SUFFIX=$((PACKAGE_SUFFIX + 1))
|
|
done
|
|
|
|
if [[ -z "${RELEASE_COMMIT_SHA}" ]] && command -v git >/dev/null 2>&1; then
|
|
if git -C "${ROOT_DIR}" rev-parse --is-inside-work-tree >/dev/null 2>&1; then
|
|
RELEASE_COMMIT_SHA="$(git -C "${ROOT_DIR}" rev-parse --short=12 HEAD 2>/dev/null || true)"
|
|
RELEASE_COMMIT_REF="$(git -C "${ROOT_DIR}" rev-parse --abbrev-ref HEAD 2>/dev/null || true)"
|
|
fi
|
|
fi
|
|
|
|
rm -rf "${STAGING_ROOT}"
|
|
rm -f "${ARCHIVE_PATH}" "${HASH_PATH}"
|
|
mkdir -p "${STAGING_ROOT}"
|
|
|
|
shopt -s nullglob
|
|
|
|
DOCS_TARGET="${STAGING_ROOT}/docs"
|
|
mkdir -p "${DOCS_TARGET}"
|
|
DOC_FILES=("${ROOT_DIR}"/docs/*.md)
|
|
if ((${#DOC_FILES[@]})); then
|
|
cp -a "${DOC_FILES[@]}" "${DOCS_TARGET}/"
|
|
fi
|
|
|
|
SCRIPTS_TARGET="${STAGING_ROOT}/scripts"
|
|
mkdir -p "${SCRIPTS_TARGET}"
|
|
ROOT_SCRIPTS=(
|
|
"start_domain_api.ps1"
|
|
"stop_domain_api.ps1"
|
|
"start_domain_web.ps1"
|
|
"stop_domain_web.ps1"
|
|
"start_domain_web_background.ps1"
|
|
"start_domain_stack.ps1"
|
|
"stop_domain_stack.ps1"
|
|
"smoke_test_stack.ps1"
|
|
"package_domain_release.ps1"
|
|
"verify_domain_release.ps1"
|
|
"prepare_final_release.ps1"
|
|
"show_latest_release.ps1"
|
|
"package_domain_release.sh"
|
|
"verify_domain_release.sh"
|
|
"prepare_final_release.sh"
|
|
"show_latest_release.sh"
|
|
)
|
|
for script_name in "${ROOT_SCRIPTS[@]}"; do
|
|
copy_optional_item "${ROOT_DIR}/${script_name}" "${SCRIPTS_TARGET}/"
|
|
done
|
|
|
|
API_TARGET="${STAGING_ROOT}/domain-api"
|
|
mkdir -p "${API_TARGET}"
|
|
API_ITEMS=(
|
|
"app"
|
|
"deploy"
|
|
"README.md"
|
|
"requirements.txt"
|
|
".env.example"
|
|
)
|
|
for item in "${API_ITEMS[@]}"; do
|
|
copy_optional_item "${ROOT_DIR}/domain-api/${item}" "${API_TARGET}/"
|
|
done
|
|
|
|
WEB_TARGET="${STAGING_ROOT}/domain-web"
|
|
mkdir -p "${WEB_TARGET}"
|
|
WEB_ITEMS=(
|
|
"src"
|
|
"deploy"
|
|
"README.md"
|
|
"package.json"
|
|
"package-lock.json"
|
|
"tsconfig.json"
|
|
"tsconfig.node.json"
|
|
"vite.config.ts"
|
|
".env.example"
|
|
".env.production.example"
|
|
)
|
|
for item in "${WEB_ITEMS[@]}"; do
|
|
copy_optional_item "${ROOT_DIR}/domain-web/${item}" "${WEB_TARGET}/"
|
|
done
|
|
copy_optional_item "${ROOT_DIR}/domain-web/dist" "${WEB_TARGET}/"
|
|
|
|
DOMAINCHECK_TARGET="${STAGING_ROOT}/domainCheck"
|
|
copy_optional_item "${ROOT_DIR}/domainCheck" "${STAGING_ROOT}/"
|
|
if [[ -d "${DOMAINCHECK_TARGET}" ]]; then
|
|
rm -rf \
|
|
"${DOMAINCHECK_TARGET}/.venv" \
|
|
"${DOMAINCHECK_TARGET}/.pytest_cache" \
|
|
"${DOMAINCHECK_TARGET}/__pycache__" \
|
|
"${DOMAINCHECK_TARGET}/logs" \
|
|
"${DOMAINCHECK_TARGET}/data" \
|
|
"${DOMAINCHECK_TARGET}/runtime"
|
|
rm -f \
|
|
"${DOMAINCHECK_TARGET}/.env" \
|
|
"${DOMAINCHECK_TARGET}/credentials.json" \
|
|
"${DOMAINCHECK_TARGET}/juming_cookies.pkl" \
|
|
"${DOMAINCHECK_TARGET}/detect_options.json" \
|
|
"${DOMAINCHECK_TARGET}/proxy_config.json" \
|
|
"${DOMAINCHECK_TARGET}/thread_count.json" \
|
|
"${DOMAINCHECK_TARGET}/node_thread_counts.json" \
|
|
"${DOMAINCHECK_TARGET}/runtime_settings.json" \
|
|
"${DOMAINCHECK_TARGET}/detect_worker.log"
|
|
find "${DOMAINCHECK_TARGET}" -type d -name "__pycache__" -prune -exec rm -rf {} +
|
|
find "${DOMAINCHECK_TARGET}" -type f \( -name "*.pyc" -o -name "detect_worker*.log" \) -delete
|
|
fi
|
|
|
|
SMOKE_OK="false"
|
|
SMOKE_MESSAGE=""
|
|
if [[ "${SKIP_SMOKE_TEST}" == "1" ]]; then
|
|
SMOKE_MESSAGE="skipped by env"
|
|
elif [[ -x "${SMOKE_PYTHON}" && -f "${SMOKE_RUNNER}" ]]; then
|
|
if smoke_log="$("${SMOKE_PYTHON}" "${SMOKE_RUNNER}" --base-url "${API_BASE_URL}" --web-url "${WEB_URL}" --output "${SMOKE_OUTPUT}" 2>&1)"; then
|
|
if [[ -f "${SMOKE_OUTPUT}" ]]; then
|
|
SMOKE_OK="true"
|
|
SMOKE_MESSAGE="generated"
|
|
else
|
|
SMOKE_MESSAGE="smoke test succeeded but report missing"
|
|
fi
|
|
else
|
|
SMOKE_MESSAGE="${smoke_log}"
|
|
fi
|
|
else
|
|
SMOKE_MESSAGE="smoke test runtime not found"
|
|
fi
|
|
|
|
if [[ -f "${SMOKE_OUTPUT}" ]]; then
|
|
SMOKE_SUMMARY="$(summarize_smoke_report "${SMOKE_OUTPUT}")"
|
|
SMOKE_OK="${SMOKE_SUMMARY%%$'\t'*}"
|
|
SMOKE_MESSAGE="${SMOKE_SUMMARY#*$'\t'}"
|
|
fi
|
|
|
|
python3 - "${STAGING_ROOT}" "${PACKAGE_NAME}" "${ROOT_DIR}" "${SMOKE_OK}" "${SMOKE_MESSAGE}" "${API_BASE_URL}" "${WEB_URL}" "${RELEASE_COMMIT_SHA}" "${RELEASE_COMMIT_REF}" <<'PY'
|
|
import json
|
|
import os
|
|
import sys
|
|
from datetime import datetime
|
|
from pathlib import Path
|
|
|
|
staging_root = Path(sys.argv[1])
|
|
package_name = sys.argv[2]
|
|
root_dir = sys.argv[3]
|
|
smoke_ok = sys.argv[4].lower() == "true"
|
|
smoke_message = sys.argv[5]
|
|
api_base_url = sys.argv[6]
|
|
web_url = sys.argv[7]
|
|
commit_sha = sys.argv[8]
|
|
commit_ref = sys.argv[9]
|
|
|
|
docs_target = staging_root / "docs"
|
|
scripts_target = staging_root / "scripts"
|
|
api_target = staging_root / "domain-api"
|
|
web_target = staging_root / "domain-web"
|
|
smoke_output = staging_root / "smoke_test_report.json"
|
|
|
|
manifest = {
|
|
"package_name": package_name,
|
|
"package_type": "tar.gz",
|
|
"generated_at": datetime.now().isoformat(timespec="seconds"),
|
|
"root": root_dir,
|
|
"commit_sha": commit_sha,
|
|
"commit_ref": commit_ref,
|
|
"smoke_test": {
|
|
"ok": smoke_ok,
|
|
"message": smoke_message,
|
|
"api_base_url": api_base_url,
|
|
"web_url": web_url,
|
|
"report": "smoke_test_report.json" if smoke_output.exists() else "",
|
|
},
|
|
"included": {
|
|
"docs": sorted(item.name for item in docs_target.iterdir() if item.is_file()) if docs_target.exists() else [],
|
|
"scripts": sorted(item.name for item in scripts_target.iterdir() if item.is_file()) if scripts_target.exists() else [],
|
|
"domain_api": sorted(item.name for item in api_target.iterdir()) if api_target.exists() else [],
|
|
"domain_web": sorted(item.name for item in web_target.iterdir()) if web_target.exists() else [],
|
|
"domain_check": sorted(item.name for item in (staging_root / "domainCheck").iterdir()) if (staging_root / "domainCheck").exists() else [],
|
|
},
|
|
}
|
|
|
|
(staging_root / "release_manifest.json").write_text(
|
|
json.dumps(manifest, ensure_ascii=False, indent=2),
|
|
encoding="utf-8",
|
|
)
|
|
PY
|
|
|
|
cat > "${STAGING_ROOT}/README_RELEASE.txt" <<EOF
|
|
domainCheck release package
|
|
generated_at=$(date '+%Y-%m-%d %H:%M:%S')
|
|
|
|
Contents:
|
|
- docs
|
|
- scripts
|
|
- domain-api
|
|
- domain-web
|
|
- domainCheck
|
|
- release_manifest.json
|
|
- smoke_test_report.json (if generated)
|
|
|
|
Recommended order:
|
|
1. Read docs/08_domainCheck_交付说明.md
|
|
2. Read docs/09_domainCheck_交付打包说明.md
|
|
3. Review release_manifest.json and smoke_test_report.json
|
|
4. On Linux run scripts/verify_domain_release.sh
|
|
5. On Windows run scripts/verify_domain_release.ps1
|
|
6. For Linux deployment read domain-api/deploy/linux/README.md
|
|
EOF
|
|
|
|
tar -C "${STAGING_ROOT}" -czf "${ARCHIVE_PATH}" .
|
|
|
|
ARCHIVE_SHA="$(
|
|
python3 - "${ARCHIVE_PATH}" <<'PY'
|
|
import hashlib
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
path = Path(sys.argv[1])
|
|
sha = hashlib.sha256()
|
|
with path.open("rb") as handle:
|
|
for chunk in iter(lambda: handle.read(1024 * 1024), b""):
|
|
sha.update(chunk)
|
|
print(sha.hexdigest().lower())
|
|
PY
|
|
)"
|
|
|
|
cat > "${HASH_PATH}" <<EOF
|
|
algorithm=SHA256
|
|
file=$(basename "${ARCHIVE_PATH}")
|
|
sha256=${ARCHIVE_SHA}
|
|
EOF
|
|
|
|
python3 - "${LATEST_JSON_PATH}" "${LATEST_TXT_PATH}" "${PACKAGE_NAME}" "${STAGING_ROOT}" "${ARCHIVE_PATH}" "${HASH_PATH}" "${ARCHIVE_SHA}" "${SMOKE_OK}" "${SMOKE_MESSAGE}" "${RELEASE_COMMIT_SHA}" "${RELEASE_COMMIT_REF}" <<'PY'
|
|
import json
|
|
import sys
|
|
from datetime import datetime
|
|
from pathlib import Path
|
|
|
|
latest_json_path = Path(sys.argv[1])
|
|
latest_txt_path = Path(sys.argv[2])
|
|
package_name = sys.argv[3]
|
|
staging_path = sys.argv[4]
|
|
archive_path = sys.argv[5]
|
|
sha256_path = sys.argv[6]
|
|
sha256_value = sys.argv[7]
|
|
smoke_ok = sys.argv[8].lower() == "true"
|
|
smoke_message = sys.argv[9]
|
|
commit_sha = sys.argv[10]
|
|
commit_ref = sys.argv[11]
|
|
|
|
payload = {
|
|
"package_name": package_name,
|
|
"package_type": "tar.gz",
|
|
"generated_at": datetime.now().isoformat(timespec="seconds"),
|
|
"staging_path": staging_path,
|
|
"archive_path": archive_path,
|
|
"sha256_path": sha256_path,
|
|
"sha256": sha256_value,
|
|
"smoke_test_ok": smoke_ok,
|
|
"smoke_test_message": smoke_message,
|
|
"smoke_test_report": "smoke_test_report.json" if (Path(staging_path) / "smoke_test_report.json").exists() else "",
|
|
"commit_sha": commit_sha,
|
|
"commit_ref": commit_ref,
|
|
}
|
|
latest_json_path.write_text(json.dumps(payload, ensure_ascii=False, indent=2), encoding="utf-8")
|
|
latest_txt_path.write_text(
|
|
"\n".join(
|
|
[
|
|
f"package_name={payload['package_name']}",
|
|
f"package_type={payload['package_type']}",
|
|
f"staging_path={payload['staging_path']}",
|
|
f"archive_path={payload['archive_path']}",
|
|
f"sha256_path={payload['sha256_path']}",
|
|
f"sha256={payload['sha256']}",
|
|
f"smoke_test_ok={str(payload['smoke_test_ok']).lower()}",
|
|
f"smoke_test_message={payload['smoke_test_message']}",
|
|
f"smoke_test_report={payload['smoke_test_report']}",
|
|
f"commit_sha={payload['commit_sha']}",
|
|
f"commit_ref={payload['commit_ref']}",
|
|
]
|
|
)
|
|
+ "\n",
|
|
encoding="utf-8",
|
|
)
|
|
PY
|
|
|
|
printf 'release package created\n'
|
|
printf 'staging: %s\n' "${STAGING_ROOT}"
|
|
printf 'archive: %s\n' "${ARCHIVE_PATH}"
|
|
printf 'sha256: %s\n' "${HASH_PATH}"
|
|
printf 'latest: %s\n' "${LATEST_JSON_PATH}"
|
|
printf 'smoke_test_ok: %s\n' "${SMOKE_OK}"
|