Pi 번들 — arm64 빌드 머신이 필요하다고 적어 둔 것은 과한 가정이었다. --cross 모드에서 pip 이 태그로 aarch64/cp311 휠을 받으므로 네트워크만 있으면 된다. 휠 11개 전부 aarch64. "Pi 번들 미빌드"로 스킵되던 테스트가 이제 실제로 돈다. MoveNet — P3-3 의 잔여였던 죽은 원본을 유효한 tf2onnx 변환본으로 교체하고 sha256+size 로 고정했다. 고정 전에 어댑터 계약과 대조했다: 입력 [1,192,192,3] int32 NHWC, 출력 [1,1,17,3]. 레이아웃이 다른 변환본은 onnxruntime 에 그냥 로드되고 좌표만 틀리는 실패라, 파일이 바뀌었는지가 아니라 계약이 맞는지를 테스트로 박았다. 자격증명 회전 — 손으로 적던 SQL 절차를 명령 하나로 바꿨다. DB 에 접속하지 않고 붙여넣을 SQL 만 낸다(운영자의 창구가 phpMyAdmin 이므로 접속 정보 없이 어느 머신에서나 돌아야 한다). 비밀번호는 STDIN 으로만 받는다 — 명령행은 ps 와 셸 히스토리에 남는다. 테스트가 잠그는 것은 서버 키와 Pi 키의 일치, 그리고 앱이 그 해시를 검증하는지 두 가지다. 회전 실행 자체는 운영 계정 조작이라 저장소에서 수행하지 않는다. 검증: phpunit 276 passed(스킵 0, 신규 7) · pytest 스킵 2(환경) Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EBjuR7DW6PNNMows4KnbYU
87 lines
3.9 KiB
Python
87 lines
3.9 KiB
Python
"""빌드/온라인 머신 전용: MoveNet SinglePose Lightning ONNX 다운로드(폐쇄망 번들). Apache-2.0.
|
|
|
|
Integrity is enforced (spec: SHA256/GPG verify of every vendored model). The download is staged
|
|
to a temp file, validated, and only then moved into place — a truncated / HTML-error-page /
|
|
tampered download must never be saved as the model (onnxruntime would then fail to load it, and
|
|
the operator would see a cryptic runtime error instead of a clear fetch failure).
|
|
|
|
SOURCE: the historical PINTO_model_zoo raw URL is DEAD (that repo hosts its models on external
|
|
storage, not as repo blobs — the raw path returns a GitHub HTML 404). The pinned source below is
|
|
a tf2onnx export of Google's MoveNet SinglePose Lightning v4, checked to match what
|
|
edge/adapters/movenet_pose.py feeds and reads: input [1,192,192,3] int32 NHWC, output
|
|
[1,1,17,3] float (y, x, score). A conversion with a different layout would load fine and then
|
|
produce silently wrong keypoints, so the digest below is what makes this safe to fetch — not the
|
|
hostname. For the Hailo delivery path this model is optional (POSE_BACKEND=hailo).
|
|
"""
|
|
import hashlib
|
|
import os
|
|
import sys
|
|
import tempfile
|
|
import urllib.request
|
|
|
|
URL = os.environ.get(
|
|
"MOVENET_ONNX_URL",
|
|
"https://huggingface.co/Xenova/movenet-singlepose-lightning/resolve/main/onnx/model.onnx",
|
|
)
|
|
DEST = os.path.join(os.path.dirname(__file__), "..", "..", "models", "movenet_lightning.onnx")
|
|
|
|
# Pinned from the bytes of the source above. An override must bring its own digest — pointing
|
|
# MOVENET_ONNX_URL elsewhere while keeping this pin just makes the download fail, which is the
|
|
# intended direction of failure.
|
|
PINNED_SHA256 = os.environ.get(
|
|
"MOVENET_ONNX_SHA256", "1ad4f8d6c2f776a9967db3993c9ca740bc350104f9d37c151dc183fc29a464ad")
|
|
PINNED_SIZE = int(os.environ.get("MOVENET_ONNX_SIZE", "9413268") or "0")
|
|
|
|
|
|
def _looks_like_onnx(head: bytes) -> bool:
|
|
# ONNX is a protobuf ModelProto; it is NOT text. Reject an HTML error page / LFS pointer
|
|
# outright (the current dead URL returns exactly that).
|
|
stripped = head.lstrip()
|
|
if stripped[:1] in (b"<", b"{") or stripped[:5].lower() == b"<!doc":
|
|
return False
|
|
if b"version https://git-lfs" in head:
|
|
return False
|
|
return True
|
|
|
|
|
|
def main():
|
|
dest = os.path.abspath(DEST)
|
|
os.makedirs(os.path.dirname(dest), exist_ok=True)
|
|
print(f"downloading MoveNet ONNX\n {URL}\n -> {dest}")
|
|
tmp = None
|
|
try:
|
|
fd, tmp = tempfile.mkstemp(dir=os.path.dirname(dest), prefix=".movenet.", suffix=".part")
|
|
os.close(fd)
|
|
urllib.request.urlretrieve(URL, tmp)
|
|
|
|
with open(tmp, "rb") as f:
|
|
head = f.read(64)
|
|
if not _looks_like_onnx(head):
|
|
raise SystemExit(
|
|
f"ERROR: {URL}\n did not return an ONNX model (looks like HTML / an error page /"
|
|
f" an LFS pointer).\n Set MOVENET_ONNX_URL to a valid source and pin"
|
|
f" MOVENET_ONNX_SHA256. (Hailo path does not need this model.)"
|
|
)
|
|
|
|
size = os.path.getsize(tmp)
|
|
digest = hashlib.sha256(open(tmp, "rb").read()).hexdigest()
|
|
if PINNED_SHA256 and (digest != PINNED_SHA256 or (PINNED_SIZE and size != PINNED_SIZE)):
|
|
raise SystemExit(
|
|
f"ERROR: MoveNet ONNX does not match the pinned digest.\n"
|
|
f" expected: {PINNED_SIZE} bytes, sha256 {PINNED_SHA256}\n"
|
|
f" got : {size} bytes, sha256 {digest}\n Refusing to install it."
|
|
)
|
|
if not PINNED_SHA256:
|
|
print(f" WARNING: integrity not pinned (MOVENET_ONNX_SHA256 unset). Got {size} bytes,"
|
|
f" sha256 {digest}. Pin this once the source is trusted.", file=sys.stderr)
|
|
|
|
os.replace(tmp, dest)
|
|
tmp = None
|
|
print(f"done: {dest} ({size} bytes)")
|
|
finally:
|
|
if tmp and os.path.exists(tmp):
|
|
os.remove(tmp)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|