The SSL fix shipped and did not reach the machine it was written for. That log said so, one line above the tick: behavision is already installed with the same version as the provided wheel. Use --force-reinstall to force an installation of the wheel. [ok] Engine and dependencies installed and the traceback below it still pointed at model_assets.py line 59, urllib.request.urlretrieve - code the fix had deleted. Two frozen literals caused it: version = "1.1.0" in pyproject.toml and __version__ = "1.0.0" in behavision/__init__.py. They disagreed with each other and neither tracked a release, so every release built behavision-1.1.0-py3-none-any.whl and pip install --upgrade on a machine that already had 1.1.0 is a no-op. The comment beside that call claimed the opposite. The shape of the damage is what makes it bad. The Go binaries - app, agent, setup tool - are rebuilt every release and updated normally. So a shop PC ran a current app supervising an engine several releases old, and nothing said which: /api/health reported the model, the paths, the cameras and the gallery, and no version at all. - One version, in the package, read by pyproject through [tool.setuptools.dynamic]. In a checkout it reads 0.0.0+dev: a plausible-looking number on a developer's /api/health is worse than none. - pip install --force-reinstall --no-deps <wheel>, after the ordinary --upgrade. --upgrade settles the dependencies; the second call guarantees our own code is the code in the folder. --no-deps keeps it cheap - forcing the dependencies too would re-download ~300 MB every run. A rebuild at an unchanged version is the ordinary case while developing, so this must not rely on the version moving. - release.sh stamps the tag: v0.5.6-demo -> 0.5.6+demo, valid PEP 440. Into a copy of the line, reverted in a trap, so the tree is never left dirty. /api/health reports version now. Without it there is no way to tell a shop PC three releases behind from a current one, which is how this survived several releases. Reproduced end to end before fixing, against real wheels on Python 3.12: two builds of the same version, --upgrade leaves the old code in place and prints the same sentence the colleague's Mac printed, --force-reinstall --no-deps replaces it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KGcjxF1cNLcuwc3DAPcnfj
136 lines
8.1 KiB
Bash
Executable File
136 lines
8.1 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Build the Windows shop-PC package and publish it as a Gitea release.
|
|
#
|
|
# ./release.sh v0.4.2 build the Windows zip and publish
|
|
# PUBLISH=0 ./release.sh v0.4.2 build only
|
|
# MAC=1 ./release.sh v0.4.2 also build and attach the macOS zip
|
|
# DEMO_PACK=demo-cameras.enc ./release.sh v0.4.4-demo
|
|
# a demo build: the sealed bundle from
|
|
# behavision-demo-pack ships in engine-src,
|
|
# and setup asks for its unlock code
|
|
#
|
|
# The package is a SOURCE install: the Go binaries are cross-compiled here, the
|
|
# engine ships as a pure-Python wheel and behavision-setup.exe builds a venv on
|
|
# the shop PC. PyInstaller cannot cross-compile, so a frozen engine needs a
|
|
# Windows build machine we do not have; this is what lets a release happen
|
|
# from this Mac at all. Layout matches what behavision-setup expects and what
|
|
# INSTALL.txt describes.
|
|
set -euo pipefail
|
|
cd "$(dirname "$0")"
|
|
export PATH="$PATH:$HOME/go/bin:/opt/homebrew/bin"
|
|
|
|
TAG=${1:?usage: release.sh vX.Y.Z}
|
|
REPO_API=https://gitapp.workolik.com/api/v1/repos/Loyaly/Behavision
|
|
STAGE=dist/Behavision
|
|
ZIP="dist/Behavision-$TAG-windows-x64.zip"
|
|
MACZIP="dist/Behavision-$TAG-macos-arm64.zip"
|
|
MACSTAGE=dist/Behavision-mac
|
|
|
|
step() { printf '\n\033[1m%s\033[0m\n' "$*"; }
|
|
case "$(git describe --tags --always --dirty)" in *-dirty) echo "refusing to release uncommitted changes" >&2; exit 1;; esac
|
|
if git rev-parse -q --verify "refs/tags/$TAG" >/dev/null; then
|
|
[ "$(git rev-parse "$TAG^{}")" = "$(git rev-parse HEAD)" ] || { echo "$TAG exists and is not HEAD" >&2; exit 1; }
|
|
fi
|
|
|
|
step "1. Desktop app (Wails, pure-Go Windows target)"
|
|
(cd desktop/frontend && npm run build >/dev/null)
|
|
rm -rf "$STAGE" && mkdir -p "$STAGE/engine-src"
|
|
# -tags desktop,production is what `wails build` passes; without them the
|
|
# binary starts, shows "Wails applications will not build without the correct
|
|
# build tags" and exits. Measured on the first Windows install of v0.4.4-demo.
|
|
(cd desktop && CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -trimpath -tags desktop,production \
|
|
-ldflags "-H windowsgui -s -w -X main.version=$TAG" -o "../$STAGE/Behavision.exe" .)
|
|
|
|
step "2. Agent and setup tool"
|
|
(cd agent && CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -trimpath \
|
|
-ldflags "-s -w -X main.version=$TAG" -o "../$STAGE/behavision-agent.exe" . \
|
|
&& CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -trimpath \
|
|
-ldflags "-s -w -X main.version=$TAG" -o "../$STAGE/behavision-setup.exe" ./cmd/behavision-setup)
|
|
|
|
step "3. Engine source and wheel"
|
|
# The wheel is built with the checkout's own interpreter; requires-python is a
|
|
# statement about the SHOP PC, which setup enforces when it finds Python there.
|
|
# Stamp the tag into the wheel version. Without this every release built
|
|
# behavision-1.1.0-py3-none-any.whl, and `pip install --upgrade` on a machine
|
|
# that already had 1.1.0 is a no-op - so an engine fix reached nobody who had
|
|
# ever run setup, while the Go binaries beside it updated normally. Nothing
|
|
# reported a version either, so there was no way to tell a shop PC three
|
|
# releases behind from a current one.
|
|
#
|
|
# v0.5.6-demo -> 0.5.6+demo, which is valid PEP 440: a local segment takes
|
|
# alphanumerics and dots, never hyphens.
|
|
TMPVER=$(mktemp)
|
|
PEP440=$(printf '%s' "${TAG#v}" | sed 's/-/+/; s/[^0-9A-Za-z.+]/./g')
|
|
trap 'git checkout -- behavision/__init__.py 2>/dev/null || true; rm -f "$TMPVER"' EXIT
|
|
# A temp file rather than `sed -i`, whose argument handling differs between
|
|
# BSD and GNU - this script is run from a Mac today and that is not a reason
|
|
# to plant a portability trap in a release path.
|
|
sed "s/^__version__ = .*/__version__ = \"$PEP440\"/" behavision/__init__.py > "$TMPVER"
|
|
cat "$TMPVER" > behavision/__init__.py
|
|
.venv/bin/python -m pip wheel --no-deps --ignore-requires-python -q -w "$STAGE/engine-src" . 2>&1 | grep -v "DEPRECATION\|WARNING: Ignoring" || true
|
|
git checkout -- behavision/__init__.py
|
|
rm -f "$TMPVER"
|
|
trap - EXIT
|
|
echo " engine wheel version $PEP440"
|
|
ls "$STAGE"/engine-src/behavision-*.whl >/dev/null || { echo "wheel was not built" >&2; exit 1; }
|
|
cp pyproject.toml requirements.txt "$STAGE/engine-src/"
|
|
mkdir -p "$STAGE/engine-src/config" && cp config/default.yaml "$STAGE/engine-src/config/"
|
|
rsync -a --exclude '__pycache__' behavision/ "$STAGE/engine-src/behavision/"
|
|
cp installer/INSTALL.txt installer/run-with-lan-head-office.cmd "$STAGE/"
|
|
if [ -n "${DEMO_PACK:-}" ]; then
|
|
case "$TAG" in *-demo*) ;; *) echo "a DEMO_PACK build must be tagged -demo" >&2; exit 1;; esac
|
|
cp "$DEMO_PACK" "$STAGE/engine-src/demo-cameras.enc" && echo " demo bundle: $(basename "$DEMO_PACK")"
|
|
fi
|
|
|
|
step "4. Package"
|
|
rm -f "$ZIP" && (cd dist && zip -qr "$(basename "$ZIP")" Behavision) && ls -la "$ZIP" | awk '{print " " $5 " bytes " $9}'
|
|
unzip -l "$ZIP" | grep -E "Behavision\.exe|agent\.exe|setup\.exe|\.whl|INSTALL" | awk '{print " " $4}'
|
|
|
|
# --- macOS, same SOURCE-install shape as Windows -------------------------
|
|
#
|
|
# Worth stating because it is the reason this is cheap: the Windows package
|
|
# already ships a pure-Python WHEEL and builds a venv on the target machine,
|
|
# because PyInstaller cannot cross-compile. macOS needs nothing different -
|
|
# the same wheel, the same setup tool, a natively built .app instead of an
|
|
# .exe. No frozen engine, no 200 MB, no second packaging story.
|
|
#
|
|
# Two things it is NOT, and the release notes should say so:
|
|
# * not notarised. macOS blocks an unsigned download outright rather than
|
|
# warning like SmartScreen, so the first launch needs right-click > Open.
|
|
# Notarising needs an Apple Developer account.
|
|
# * arm64 only. Every Mac worth demoing on since 2020, and building a
|
|
# universal binary doubles the size for machines nobody here has.
|
|
if [ "${MAC:-0}" = "1" ]; then
|
|
step "5. macOS package"
|
|
command -v wails >/dev/null || { echo "wails CLI not found; go install github.com/wailsapp/wails/v2/cmd/wails@latest" >&2; exit 1; }
|
|
rm -rf "$MACSTAGE" && mkdir -p "$MACSTAGE/engine-src"
|
|
(cd desktop && wails build -platform darwin/arm64 -tags "desktop,production" -skipbindings >/dev/null)
|
|
cp -R desktop/build/bin/Behavision.app "$MACSTAGE/"
|
|
(cd agent && CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 go build -trimpath \
|
|
-ldflags "-s -w -X main.version=$TAG" -o "../$MACSTAGE/behavision-agent" . \
|
|
&& CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 go build -trimpath \
|
|
-ldflags "-s -w -X main.version=$TAG" -o "../$MACSTAGE/behavision-setup" ./cmd/behavision-setup)
|
|
# The identical engine payload the Windows package carries.
|
|
cp -R "$STAGE/engine-src/." "$MACSTAGE/engine-src/"
|
|
rm -f "$MACZIP" && (cd dist && zip -qry "$(basename "$MACZIP")" Behavision-mac)
|
|
ls -la "$MACZIP" | awk '{print " " $5 " bytes " $9}'
|
|
fi
|
|
|
|
[ "${PUBLISH:-1}" = "1" ] || { echo "built, not published"; exit 0; }
|
|
|
|
step "6. Tag and publish"
|
|
git rev-parse -q --verify "refs/tags/$TAG" >/dev/null || git tag -a "$TAG" -m "$TAG"
|
|
git push -q origin "$TAG"
|
|
# The notes come from a file so they are reviewed, not typed into a shell.
|
|
NOTES=${NOTES:-dist/RELEASE-NOTES-$TAG.md}
|
|
[ -f "$NOTES" ] || { echo "write the release notes to $NOTES first" >&2; exit 1; }
|
|
# Same credential git pushes with; Gitea accepts it as Basic auth for the API.
|
|
CRED=$(printf 'protocol=https\nhost=gitapp.workolik.com\n' | git credential fill)
|
|
USER=$(printf '%s' "$CRED" | sed -n 's/^username=//p'); PASS=$(printf '%s' "$CRED" | sed -n 's/^password=//p')
|
|
BODY=$(python3 -c 'import json,sys;print(json.dumps({"tag_name":sys.argv[1],"name":sys.argv[2],"body":open(sys.argv[3]).read(),"prerelease":True}))' "$TAG" "$TAG — $(head -1 "$NOTES" | sed 's/^#* *//')" "$NOTES")
|
|
REL=$(curl -sS -u "$USER:$PASS" -H 'content-type: application/json' -d "$BODY" "$REPO_API/releases")
|
|
ID=$(printf '%s' "$REL" | python3 -c 'import json,sys;print(json.load(sys.stdin)["id"])')
|
|
curl -sS -u "$USER:$PASS" -F "attachment=@$ZIP" "$REPO_API/releases/$ID/assets?name=$(basename "$ZIP")" >/dev/null
|
|
[ "${MAC:-0}" = "1" ] && curl -sS -u "$USER:$PASS" -F "attachment=@$MACZIP" "$REPO_API/releases/$ID/assets?name=$(basename "$MACZIP")" >/dev/null
|
|
echo " published: https://gitapp.workolik.com/Loyaly/Behavision/releases/tag/$TAG"
|