Iris — Release & build runbook¶
Command-by-command for cutting one release: compile → build → sign → publish → installers,
for the commercial (detection-only) edition and both world / RU channels. Run on a
build host with internet (PyPI/GitHub/PyTorch/HF reachable) + Docker + a GPU is not needed to
build. See docs/CODE_PROTECTION.md, COMPLIANCE.md, DISTRIBUTION_AND_UPDATES.md.
0. One-time setup (do once, keep secret)¶
# Vendor signing key — signs licenses, trials, AND release manifests. NEVER ship the private part.
python3 scripts/gen_vendor_key.py --bits 2048 --out vendor_private.json
# -> prints the PUBLIC env (IRIS_LICENSE_PUBKEY_N/E) to bake into the image/.env.
vendor_private.json offline (HSM/secrets manager). The PUBLIC key ships in the image so the
device verifies licenses + manifests offline. (Optional: a separate online issuance key for the
license server vs the offline release key — see docs/CODE_PROTECTION.md.)
1. Bump version (3 files + changelog)¶
# VERSION, pyproject.toml, app/__init__.py -> X.Y.Z ; add a CHANGELOG entry. Then tag.
git tag -a vX.Y.Z -m "Release vX.Y.Z" && git push origin vX.Y.Z
2. Compile the crown-jewel modules (free Nuitka → native .so)¶
scripts/compile_core.sh # reid/faces/audit/workers/licensing -> .so ; source .py removed
.venv/bin/pytest -q # full suite must stay green against the compiled tree
3. Build the sealed, license-clean image¶
# Apache detector + drop AGPL YOLO; do NOT bundle non-commercial weights (COMPLIANCE.md §5).
python3 scripts/fetch_rtdetr.py --out models/rtdetr.onnx
rm -f models/yolov8n.onnx models/buffalo_l* models/osnet* # commercial bundle excludes these
# LGPL ffmpeg (multi-stage; see COMPLIANCE.md §5c) instead of Debian GPL ffmpeg.
YT_DLP_VERSION=2025.06.30 MIVOLO_ARCHIVE=<sha> \
scripts/build_sealed_image.sh ghcr.io/yakden/iris:X.Y.Z dist
# -> dist/iris-image-*.tar.gz (+ .sha256) for air-gap, and the local image tag for push.
gen_integrity_manifest.py runs inside the Dockerfile build stage and bakes the signed manifest in.
4. Sign the image (Cosign — free, no CA, no sanctions)¶
cosign sign --yes --bundle dist/iris-image.cosign.bundle ghcr.io/yakden/iris:X.Y.Z
docker push ghcr.io/yakden/iris:X.Y.Z # online channel; air-gap uses the tar from step 3
5. Publish signed update manifests — BOTH channels¶
DIGEST=$(docker inspect --format '{{index .RepoDigests 0}}' ghcr.io/yakden/iris:X.Y.Z)
for ch in stable stable-ru; do
python3 scripts/publish_release.py --key vendor_private.json --version X.Y.Z \
--channel "$ch" --image "$DIGEST" --min-version 1.40.0 --out manifest-$ch.json
curl -fsS -X POST "$UPDATE_SERVER/channels/$ch/publish" \
-H "X-Admin-Token: $UPDATE_ADMIN" -H 'content-type: application/json' --data @manifest-$ch.json
done
GET /api/system/update (signature + channel + subscription gated)
and the host agent applies it with health-gated rollback (iris_update.sh).
6. Installers (thin host bootstrap; carry no secrets)¶
IRIS_VERSION=X.Y.Z nfpm package -f packaging/iris.nfpm.yaml -p deb -t dist/ # Linux .deb
IRIS_VERSION=X.Y.Z nfpm package -f packaging/iris.nfpm.yaml -p rpm -t dist/ # Linux .rpm
# Windows (.exe) — on a Windows runner: ISCC.exe /DMyAppVersion=X.Y.Z packaging\windows\iris-installer.iss
.github/workflows/release.yml to run on git tag vX.Y.Z.)
7. Cloud services (deploy once; keep running)¶
# License/activation/trial/billing/portal — your VPS, NOT shipped to clients.
IRIS_LS_ADMIN_TOKEN=<secret> IRIS_LS_KEY=vendor_private.json \
IRIS_STRIPE_WEBHOOK_SECRET=whsec_… IRIS_YOOKASSA_SECRET=<secret> \
IRIS_TRIAL_DAYS=7 IRIS_TRIAL_MODULES= IRIS_TRIAL_CAMERAS= \
IRIS_HEARTBEAT_URL=https://lic.example.com/heartbeat \
uvicorn cloud.license_server:app --port 8300
# Trial term = 7 days (the standard week-long evaluation; matches legal/EULA.md §1a and the code
# default — do NOT raise it without updating the EULA). IRIS_TRIAL_MODULES empty = full-feature
# trial; set e.g. "faces,reid" to limit. _CAMERAS unset = unlimited.
8. Provision an appliance (model B) — commercial defaults¶
EDITION=commercial scripts/provision_appliance.sh # biometrics off, RT-DETR, integrity on,
# derived key, local auth + offline + license enforce. Prints the hardware fingerprint to license.
Customer activation (two automatic types)¶
- Trial (online-only): first boot, no license,
trial_auto_enabled=true+trial_server_url→ device auto-requests a signed, hardware-bound 7-day trial (full features). One per machine; heartbeat required (can't run offline / clock-rollback). After 7 days / expiry → enforcement restricts. The 7-day term is stated in the EULA (§1a) accepted at install. - Paid: purchase → cloud issues an activation token → device
POST /activate {token, fingerprint}→ signed full license; works offline (anti-rollback clock keeps the timer honest).
Release hygiene checklist¶
- [ ] VERSION + pyproject +
app/__init__.pybumped, CHANGELOG entry, tag pushed. - [ ] Suite green against the compiled tree.
- [ ] AGPL/non-commercial weights NOT in the commercial bundle; ffmpeg LGPL.
- [ ] Image cosign-signed; manifests published to both channels.
- [ ] Installers built; cloud services up with trial/heartbeat env set.
- [ ]
gh release create vX.Y.Zwith notes.