feat: add ops center and node onboarding flow

This commit is contained in:
Your Name
2026-04-18 23:52:51 +08:00
parent 246838ae4c
commit b9c29481b5
142 changed files with 89727 additions and 186 deletions

151
verify_domain_release.sh Executable file
View File

@@ -0,0 +1,151 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
RELEASE_ROOT="${DOMAINCHECK_RELEASE_ROOT:-${ROOT_DIR}/release}"
PACKAGE_PATH="${1:-}"
HASH_PATH="${2:-}"
python3 - "${RELEASE_ROOT}" "${PACKAGE_PATH}" "${HASH_PATH}" <<'PY'
import hashlib
import json
import sys
import tarfile
import zipfile
from pathlib import Path
release_root = Path(sys.argv[1])
package_path_arg = sys.argv[2].strip()
hash_path_arg = sys.argv[3].strip()
def resolve_latest_package(root: Path) -> Path:
candidates = sorted(
[
item
for item in root.glob("*")
if item.is_file() and (item.name.endswith(".tar.gz") or item.name.endswith(".zip"))
],
key=lambda item: item.stat().st_mtime,
reverse=True,
)
if not candidates:
raise SystemExit("No release archive found")
return candidates[0]
def derive_hash_path(root: Path, package_path: Path) -> Path:
if package_path.name.endswith(".tar.gz"):
base_name = package_path.name[:-7]
else:
base_name = package_path.stem
candidate = root / f"{base_name}.sha256.txt"
if candidate.exists():
return candidate
candidates = sorted(root.glob("*.sha256.txt"), key=lambda item: item.stat().st_mtime, reverse=True)
if not candidates:
raise SystemExit("No hash file found")
return candidates[0]
def compute_sha256(path: Path) -> str:
sha = hashlib.sha256()
with path.open("rb") as handle:
for chunk in iter(lambda: handle.read(1024 * 1024), b""):
sha.update(chunk)
return sha.hexdigest().lower()
def normalize_entry_names(names: list[str]) -> list[str]:
normalized: list[str] = []
for name in names:
item = name.replace("\\", "/")
while item.startswith("./"):
item = item[2:]
item = item.strip("/")
if item:
normalized.append(item)
return normalized
def read_archive_entries(path: Path) -> tuple[list[str], dict]:
if path.name.endswith(".zip"):
with zipfile.ZipFile(path, "r") as archive:
entry_names = normalize_entry_names(archive.namelist())
manifest = {}
if "release_manifest.json" in entry_names:
with archive.open("release_manifest.json", "r") as handle:
manifest = json.loads(handle.read().decode("utf-8"))
return entry_names, manifest
with tarfile.open(path, "r:*") as archive:
entry_names = normalize_entry_names(archive.getnames())
manifest = {}
if "release_manifest.json" in entry_names:
manifest_member = archive.extractfile("./release_manifest.json") or archive.extractfile("release_manifest.json")
if manifest_member is not None:
manifest = json.loads(manifest_member.read().decode("utf-8"))
return entry_names, manifest
package_path = Path(package_path_arg) if package_path_arg else resolve_latest_package(release_root)
if not package_path.exists():
raise SystemExit(f"archive not found: {package_path}")
hash_path = Path(hash_path_arg) if hash_path_arg else derive_hash_path(release_root, package_path)
if not hash_path.exists():
raise SystemExit(f"hash file not found: {hash_path}")
expected_hash = ""
expected_file = ""
for line in hash_path.read_text(encoding="utf-8").splitlines():
if line.startswith("sha256="):
expected_hash = line.split("=", 1)[1].strip().lower()
elif line.startswith("file="):
expected_file = line.split("=", 1)[1].strip()
if not expected_hash:
raise SystemExit(f"sha256 entry missing in {hash_path}")
actual_hash = compute_sha256(package_path)
entry_names, manifest = read_archive_entries(package_path)
entry_set = set(entry_names)
required_entries = [
"README_RELEASE.txt",
"release_manifest.json",
"domain-api/README.md",
"domain-web/README.md",
"scripts/package_domain_release.ps1",
"scripts/verify_domain_release.ps1",
"scripts/package_domain_release.sh",
"scripts/verify_domain_release.sh",
"scripts/smoke_test_stack.ps1",
]
missing_entries = [item for item in required_entries if item not in entry_set]
smoke_report = str(((manifest.get("smoke_test") or {}).get("report") or "")).strip()
if smoke_report and smoke_report not in entry_set:
missing_entries.append(smoke_report)
ok = (
actual_hash == expected_hash
and not missing_entries
and (not expected_file or expected_file == package_path.name)
)
report = {
"ok": ok,
"archive": str(package_path),
"hash_file": str(hash_path),
"expected_file": expected_file,
"actual_file": package_path.name,
"expected_sha256": expected_hash,
"actual_sha256": actual_hash,
"missing_entries": missing_entries,
"package_type": "tar.gz" if package_path.name.endswith(".tar.gz") else "zip",
}
print(json.dumps(report, ensure_ascii=False, indent=2))
raise SystemExit(0 if ok else 1)
PY