#!/usr/bin/env bash # One-command AppImage build. Needs network the first time (Python, models, Electron). set -euo pipefail cd "$(dirname "$0")/.." ROOT="$(pwd)" echo "== Install Node packages ==" npm install PY="$ROOT/build/python/bin/python3" if ! "$PY" -c "import oemer.ete, onnxruntime" >/dev/null 2>&1; then echo "== Download a relocatable Python and oemer ==" mkdir -p "$ROOT/build/cache" TARBALL="$ROOT/build/cache/cpython.tar.gz" URL="https://github.com/astral-sh/python-build-standalone/releases/download/20261003/cpython-3.12.15%2B20261003-x86_64-unknown-linux-gnu-install_only.tar.gz" if [[ ! -f "$TARBALL" ]]; then curl -fL --retry 3 -o "$TARBALL" "$URL" fi rm -rf "$ROOT/build/python" tar -xzf "$TARBALL" -C "$ROOT/build" "$PY" -m pip install --upgrade pip "$PY" -m pip install \ "opencv-python-headless>=4.10,<5" \ "onnxruntime>=1.20" \ numpy scipy scikit-learn pillow matplotlib typing_extensions "$PY" -m pip install --no-deps oemer fi SITE="$("$PY" -c "import oemer, pathlib; print(pathlib.Path(oemer.__file__).parent)")" mkdir -p "$SITE/checkpoints/unet_big" "$SITE/checkpoints/seg_net" if [[ ! -f "$SITE/checkpoints/unet_big/model.onnx" ]]; then curl -fL --retry 3 -o "$SITE/checkpoints/unet_big/model.onnx" \ "https://github.com/BreezeWhite/oemer/releases/download/checkpoints/1st_model.onnx" fi if [[ ! -f "$SITE/checkpoints/seg_net/model.onnx" ]]; then curl -fL --retry 3 -o "$SITE/checkpoints/seg_net/model.onnx" \ "https://github.com/BreezeWhite/oemer/releases/download/checkpoints/2nd_model.onnx" fi MPLBACKEND=Agg "$PY" -c "import oemer.ete, cv2; from cv2.typing import RotatedRect; print('oemer', cv2.__version__)" echo "== Trim the Python prefix ==" rm -rf \ "$ROOT/build/python/lib/python3.12/test" \ "$ROOT/build/python/lib/python3.12/ensurepip" \ "$ROOT/build/python/lib/python3.12/idlelib" \ "$ROOT/build/python/lib/python3.12/turtledemo" \ "$ROOT/build/python/lib/python3.12/site-packages/pip" \ "$ROOT/build/python/lib/python3.12/site-packages/pip-"*.dist-info \ "$ROOT/build/python/lib/tcl9.0" \ "$ROOT/build/python/lib/tcl9" \ "$ROOT/build/python/lib/tk9.0" \ "$ROOT/build/python/lib/itcl4.3.8" \ "$ROOT/build/python/lib/libtcl9tk9.0.so" \ "$ROOT/build/python/lib/libtcl9.0.so" \ "$SITE/checkpoints/unet_big/weights.h5" \ "$SITE/checkpoints/seg_net/weights.h5" || true find "$ROOT/build/python" -type d -name __pycache__ -prune -exec rm -rf {} + || true find "$ROOT/build/python" -type d -name tests -path '*/site-packages/*' -prune -exec rm -rf {} + || true find "$ROOT/build/python" -name '*.a' -delete || true # Leave the CPython binary and libpython intact. Stripping those breaks the interpreter. find "$ROOT/build/python/lib/python3.12/site-packages" -type f -name '*.so' \ -exec strip --strip-unneeded {} + || true if [[ ! -f "$ROOT/electron/icon.png" ]]; then "$PY" - <<'PY' from PIL import Image, ImageDraw image = Image.new("RGB", (512, 512), "#f3ecdf") draw = ImageDraw.Draw(image) draw.rounded_rectangle((206, 36, 306, 470), radius=48, fill="#e4cfae", outline="#6a4328", width=8) draw.ellipse((156, 92, 214, 150), outline="#6a4328", width=6) for y in (160, 210, 260, 330, 390, 450): draw.ellipse((228, y, 284, y + 48), fill="#1c3830") image.save("electron/icon.png") PY fi echo "== Build the Next.js server ==" npm run build rm -rf .next/standalone/public .next/standalone/.next/static .next/standalone/scripts mkdir -p .next/standalone/.next cp -a public .next/standalone/public cp -a .next/static .next/standalone/.next/static cp -a scripts .next/standalone/scripts echo "== Package the AppImage ==" export CSC_IDENTITY_AUTO_DISCOVERY=false npx electron-builder --linux AppImage APPIMAGE="$(find dist -maxdepth 1 -name '*.AppImage' -printf '%T@ %p\n' | sort -nr | head -1 | cut -d' ' -f2-)" chmod +x "$APPIMAGE" SIZE="$(stat -c%s "$APPIMAGE")" echo "Built $APPIMAGE ($SIZE bytes)" LIMIT=$((600 * 1024 * 1024)) if (( SIZE > LIMIT )); then echo "AppImage is over 600MB." >&2 exit 1 fi