ThreadCount Community edition
Uniform stock management for healthcare linen rooms: the coordinator app, the phone counter and the staff app, for your own server. Built from e2d6d42 on 2026-09-13. Licensed under the Functional Source License (FSL-1.1-ALv2).
This commit is contained in:
Executable
+99
@@ -0,0 +1,99 @@
|
||||
#!/usr/bin/env bash
|
||||
# Build the ThreadCount counter app bundle.
|
||||
#
|
||||
# bash scripts/build-counter-aab.sh → android/app/build/outputs/bundle/release/*.aab
|
||||
# bash scripts/build-counter-aab.sh debug → a debug APK, for putting on a phone quickly
|
||||
#
|
||||
# The sibling of scripts/build-staff-aab.sh, and deliberately not the same shape, because the two
|
||||
# apps are not the same shape. This one KEEPS the barcode scanner: native libraries, a CAMERA
|
||||
# permission and VIBRATE are expected here, and the staff script's refusal to ship any of them would
|
||||
# be wrong. What this one has instead is the alignment check below, which the staff app cannot fail
|
||||
# because it ships no native code at all.
|
||||
#
|
||||
# **The toolchain is user-space.** JDK 17 and the SDK live under ~/.cache/ca-android — no sudo, no
|
||||
# system Java, nothing installed outside the home directory.
|
||||
set -euo pipefail
|
||||
cd "$(dirname "$0")/.."
|
||||
|
||||
MODE=${1:-release}
|
||||
export JAVA_HOME="${JAVA_HOME:-$HOME/.cache/ca-android/jdk}"
|
||||
export ANDROID_HOME="${ANDROID_HOME:-$HOME/.cache/ca-android/sdk}"
|
||||
export ANDROID_SDK_ROOT="$ANDROID_HOME"
|
||||
|
||||
[ -x "$JAVA_HOME/bin/java" ] || { echo "FATAL: no JDK at $JAVA_HOME"; exit 1; }
|
||||
[ -d "$ANDROID_HOME/platforms" ] || { echo "FATAL: no Android SDK at $ANDROID_HOME"; exit 1; }
|
||||
echo "sdk.dir=$ANDROID_HOME" > android/local.properties
|
||||
|
||||
echo "==> syncing the counter shell into android"
|
||||
# No TC_APP, so capacitor.config.ts resolves to the counter app and android/ is the target.
|
||||
npx cap sync android
|
||||
|
||||
if [ "$MODE" = "debug" ]; then
|
||||
echo "==> assembling a debug APK"
|
||||
( cd android && ./gradlew --no-daemon assembleDebug )
|
||||
find android/app/build/outputs/apk -name "*.apk" -print
|
||||
exit 0
|
||||
fi
|
||||
|
||||
KEYS="$HOME/threadcount-keys/keystore.properties"
|
||||
[ -f "$KEYS" ] || echo "NOTE: $KEYS is missing — the bundle will be unsigned."
|
||||
|
||||
echo "==> building the release bundle"
|
||||
( cd android && ./gradlew --no-daemon bundleRelease )
|
||||
|
||||
AAB=$(find android/app/build/outputs/bundle/release -name "*.aab" | head -1)
|
||||
echo "==> $AAB"
|
||||
|
||||
echo "==> checks"
|
||||
if unzip -l "$AAB" | grep -qiE "META-INF/.*\.(RSA|EC|DSA)"; then
|
||||
echo " ✓ signed"
|
||||
else
|
||||
echo " ! UNSIGNED — Play will refuse it"
|
||||
fi
|
||||
if unzip -p "$AAB" BUNDLE-METADATA/com.android.tools.build.obfuscation/proguard.map >/dev/null 2>&1; then
|
||||
echo " ✓ mapping file bundled — Play can symbolicate a stack trace"
|
||||
else
|
||||
echo " ! no mapping file; Play will warn about deobfuscation"
|
||||
fi
|
||||
|
||||
# 16 KB memory pages, the check that actually stops an upload.
|
||||
#
|
||||
# Devices from Android 15 can run 16 KB pages and Play rejects a bundle whose 64-bit native
|
||||
# libraries are only 4 KB-aligned. It is a 64-bit feature: armeabi-v7a and x86 cannot use 16 KB
|
||||
# pages, so a 32-bit library at 0x1000 is correct and must not be reported as a fault — a blunter
|
||||
# version of this check cried wolf over the 32-bit libbarhopper_v3.so and would have sent somebody
|
||||
# hunting a problem that was not there.
|
||||
#
|
||||
# The pins in android/variables.gradle are what make this pass; the check is here because a pin can
|
||||
# be lost in a merge and the failure is otherwise invisible until Play says no.
|
||||
echo " --- 16 KB alignment (64-bit only, which is what Play checks) ---"
|
||||
WORK=$(mktemp -d); trap 'rm -rf "$WORK"' EXIT
|
||||
BAD=0
|
||||
for so in $(unzip -l "$AAB" | grep -oE "base/lib/[^ ]*\.so" | sort -u); do
|
||||
arch=$(basename "$(dirname "$so")")
|
||||
case "$arch" in arm64-v8a|x86_64) ;; *) continue ;; esac
|
||||
unzip -o -q -j "$AAB" "$so" -d "$WORK"
|
||||
al=$(readelf -lW "$WORK/$(basename "$so")" 2>/dev/null | awk '$1=="LOAD"{print $NF}' | sort -u | head -1)
|
||||
case "$al" in
|
||||
0x4000|0x10000) printf " ok %-12s %-40s %s\n" "$arch" "$(basename "$so")" "$al" ;;
|
||||
*) printf " BAD %-12s %-40s %s\n" "$arch" "$(basename "$so")" "$al"; BAD=$((BAD + 1)) ;;
|
||||
esac
|
||||
done
|
||||
if [ "$BAD" -gt 0 ]; then
|
||||
echo " ! $BAD 64-bit library(ies) are not 16 KB aligned — Play would reject this upload."
|
||||
echo " Check the CameraX and MLKit pins in android/variables.gradle; the MLKit plugin reads"
|
||||
echo " all four androidxCamera* names and silently falls back to 1.1.0 if one is missing."
|
||||
exit 1
|
||||
fi
|
||||
echo " ✓ every 64-bit native library is 16 KB aligned"
|
||||
|
||||
PERMS=$("$JAVA_HOME/bin/java" -jar "$HOME/.cache/ca-android/bundletool.jar" dump manifest --bundle "$AAB" 2>/dev/null \
|
||||
| grep -oE '<uses-permission[^>]*android:name="[^"]+"' | grep -oE '"[^"]+"$' | tr -d '"' | sort -u)
|
||||
echo " permissions: $(echo "$PERMS" | tr '\n' ' ')"
|
||||
# CAMERA and VIBRATE belong to this app — it scans garments and buzzes on a good read. Anything
|
||||
# beyond this list is a plugin that got in without anyone deciding it should.
|
||||
if echo "$PERMS" | grep -qE 'RECORD_AUDIO|ACCESS_FINE_LOCATION|READ_CONTACTS|READ_EXTERNAL_STORAGE'; then
|
||||
echo " ! this app asks for more than it needs"
|
||||
exit 1
|
||||
fi
|
||||
echo " ✓ nothing beyond what the counter actually uses"
|
||||
Reference in New Issue
Block a user