Files
getDomain/package_domain_release.sh

357 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}/process_count.json" \
"${DOMAINCHECK_TARGET}/node_process_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}"