Files
Behavision/release.sh
Suriyakumarvijayanayagam 4bd1718491 A demo build can claim a real shop instead of running on its own
The first demo sealed the office cameras into the package and ran the
PC standalone - a copy of the product with no head office. The bundle
can now carry an installation code instead: setup redeems it exactly as
the app's Setup screen does, the PC joins the shop, and its cameras
arrive from head office on the first sync. The demo then IS the product
- login, Loya, head office - not a local imitation of it. The code is
single-use, so one bundle is one install. release.sh ships the bundle
with DEMO_PACK=.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KGcjxF1cNLcuwc3DAPcnfj
2026-09-19 13:51:01 +05:30

78 lines
4.6 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 dist/Behavision-v0.4.2-windows-x64.zip and publish
# PUBLISH=0 ./release.sh v0.4.2 build only
# 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"
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"
(cd desktop && CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -trimpath \
-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.
.venv/bin/python -m pip wheel --no-deps --ignore-requires-python -q -w "$STAGE/engine-src" . 2>&1 | grep -v "DEPRECATION\|WARNING: Ignoring" || true
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}'
[ "${PUBLISH:-1}" = "1" ] || { echo "built, not published"; exit 0; }
step "5. 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
echo " published: https://gitapp.workolik.com/Loyaly/Behavision/releases/tag/$TAG"