Repository Public
1#!/usr/bin/env bash2# Ship a reconstruction model to htr.spboucher.ai (run from the laptop).3# scripts/add_model.sh <laptop-model-dir> <id> [node]4# <laptop-model-dir> must contain the weights and a model_card.json (see models/README.md). The directory is5# rsynced to <node>:~/apps/htr/models/<id>/, PM2 `htr-web` is restarted, /api/models is checked for the id and the6# tracked card files are committed on the node. Weights never enter git.7set -euo pipefail8SRC=${1:?usage: add_model.sh <laptop-model-dir> <id> [node]}9ID=${2:?usage: add_model.sh <laptop-model-dir> <id> [node]}10NODE=${3:-m4mm}11APP='~/apps/htr'12[[ -d "$SRC" ]] || { echo "error: $SRC is not a directory" >&2; exit 1; }13[[ -f "$SRC/model_card.json" ]] || { echo "error: $SRC/model_card.json missing (see models/README.md)" >&2; exit 1; }14[[ "$ID" =~ ^[A-Za-z0-9._-]+$ ]] || { echo "error: id must match [A-Za-z0-9._-]+" >&2; exit 1; }15python3 -I - "$SRC/model_card.json" "$ID" <<'PY'16import json, sys17card = json.load(open(sys.argv[1]))18missing = [k for k in ("id", "name", "family", "n_params", "licence", "default", "speed_hint", "metrics", "notes", "trained_on", "exported_at") if k not in card]19assert not missing, f"model_card.json lacks {missing}"20assert card["family"] in ("htr", "hf-seq2seq", "hf-causal"), f"unknown family {card['family']!r}"21assert card["id"] == sys.argv[2], f"card id {card['id']!r} != {sys.argv[2]!r}"22if card["family"] != "htr":23 assert any("pretrain" in str(n).lower() for n in card.get("notes", [])), "backbone cards must keep the pretraining-corpus caveat in notes"24print(f"card ok: {card['name']} ({card['family']}, {card['n_params'] / 1e6:.1f} M params, default={card['default']})")25PY26echo "→ rsync $SRC/ → $NODE:$APP/models/$ID/"27rsync -az --exclude __pycache__ --exclude .DS_Store "$SRC/" "$NODE:$APP/models/$ID/"28echo "→ restart htr-web on $NODE"29ssh -A "$NODE" "export PATH=/opt/homebrew/bin:\$PATH; cd $APP && pm2 restart htr-web --update-env >/dev/null && \30 for i in \$(seq 1 60); do sleep 2; curl -sf localhost:8540/api/models >/tmp/htr-models.json && break; done; \31 python3 -c 'import json,sys; d=json.load(open(\"/tmp/htr-models.json\")); ids=[m[\"id\"] for m in d[\"models\"]]; print(\"models:\", ids, \"default:\", d[\"default\"]); sys.exit(0 if \"$ID\" in ids else 1)' && \32 git add models/$ID/model_card.json \$(ls models/$ID/README.md models/$ID/LICENSE-MODEL.txt 2>/dev/null) && \33 (git diff --cached --quiet || (git commit -qm 'models: add $ID' && git push -q origin main && echo 'card committed and pushed')) || true"34echo "→ https://htr.spboucher.ai/model-card/$ID"35