#!/usr/bin/env bash # build-apk.sh — Build a signed release APK locally without expo.dev / EAS. # # Prerequisites (first time only): # 1. Export EAS keystore: # cd artifacts/postiz-mobile # eas credentials --platform android # → "Download existing keystore" → save to ~/.config/postiz-mobile/postiz-mobile.jks # 2. Fill in signing credentials: # cp ~/.config/postiz-mobile/signing.env.example ~/.config/postiz-mobile/signing.env # $EDITOR ~/.config/postiz-mobile/signing.env # 3. Install Android SDK (if not already): # ./install-android-sdk.sh # # then add ANDROID_HOME to your shell profile and reload it # # Usage: # ./build-apk.sh # → dist/postiz-mobile-YYYYMMDD-HHMM.apk # ./build-apk.sh --aab # → dist/postiz-mobile-YYYYMMDD-HHMM.aab (Play Store) set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" SIGNING_ENV="$HOME/.config/postiz-mobile/signing.env" DIST_DIR="$SCRIPT_DIR/dist" BUILD_TYPE="${1:-}" # ─── Colours ─────────────────────────────────────────────────────────────── RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; NC='\033[0m' info() { echo -e "${GREEN}[build]${NC} $*"; } warn() { echo -e "${YELLOW}[warn]${NC} $*"; } abort() { echo -e "${RED}[error]${NC} $*" >&2; exit 1; } # ─── 1. Signing credentials ──────────────────────────────────────────────── if [ ! -f "$SIGNING_ENV" ]; then abort "Missing signing.env.\n\n cp ~/.config/postiz-mobile/signing.env.example ~/.config/postiz-mobile/signing.env\n # then fill in your keystore path and passwords" fi # shellcheck source=/dev/null source "$SIGNING_ENV" [ -z "${KEYSTORE_PATH:-}" ] && abort "KEYSTORE_PATH not set in signing.env" [ -z "${KEYSTORE_ALIAS:-}" ] && abort "KEYSTORE_ALIAS not set in signing.env" [ -z "${KEYSTORE_STORE_PASSWORD:-}" ] && abort "KEYSTORE_STORE_PASSWORD not set in signing.env" [ -z "${KEYSTORE_KEY_PASSWORD:-}" ] && abort "KEYSTORE_KEY_PASSWORD not set in signing.env" KEYSTORE_PATH_EXPANDED="${KEYSTORE_PATH/#\$HOME/$HOME}" KEYSTORE_PATH_EXPANDED="${KEYSTORE_PATH_EXPANDED/#~/$HOME}" [ ! -f "$KEYSTORE_PATH_EXPANDED" ] && \ abort "Keystore not found: $KEYSTORE_PATH_EXPANDED\n\nExport it from EAS:\n eas credentials --platform android\n → Download existing keystore → save to $KEYSTORE_PATH_EXPANDED" # ─── 2. Java 17/21 — Gradle requires Java ≤ 24 (system Java 25 is too new) ── # Use ~/jdk21 if present, otherwise rely on JAVA_HOME already being set correctly. if [ -z "${JAVA_HOME:-}" ] || "$JAVA_HOME/bin/java" -version 2>&1 | grep -qE '"(2[5-9]|[3-9][0-9])\.' ; then if [ -d "$HOME/jdk21" ]; then export JAVA_HOME="$HOME/jdk21" export PATH="$JAVA_HOME/bin:$PATH" info "Using local JDK 21: $JAVA_HOME" else abort "Java 25+ detected but Gradle only supports ≤ Java 24.\n\nInstall JDK 21 locally:\n wget -O /tmp/jdk21.tar.gz https://github.com/adoptium/temurin21-binaries/releases/download/jdk-21.0.7%2B6/OpenJDK21U-jdk_x64_linux_hotspot_21.0.7_6.tar.gz\n mkdir -p ~/jdk21 && tar -xzf /tmp/jdk21.tar.gz -C ~/jdk21 --strip-components=1" fi fi info "Java: $("$JAVA_HOME/bin/java" -version 2>&1 | head -1)" # ─── 3. Android SDK ──────────────────────────────────────────────────────── if [ -z "${ANDROID_HOME:-}" ]; then for candidate in "$HOME/android-sdk" "$HOME/Android/Sdk" "/opt/android-sdk"; do if [ -d "$candidate/platform-tools" ]; then export ANDROID_HOME="$candidate" break fi done fi if [ -z "${ANDROID_HOME:-}" ]; then abort "Android SDK not found.\n\nRun: ./install-android-sdk.sh\nThen: export ANDROID_HOME=\"\$HOME/android-sdk\" && source ~/.bashrc" fi export PATH="$PATH:$ANDROID_HOME/platform-tools:$ANDROID_HOME/cmdline-tools/latest/bin" info "Android SDK: $ANDROID_HOME" # ─── 4. expo prebuild ────────────────────────────────────────────────────── info "Running expo prebuild (Android)…" cd "$SCRIPT_DIR" pnpm exec expo prebuild --platform android --clean --no-install # ─── 4. Patch build.gradle — inject release signingConfig ────────────────── info "Patching android/app/build.gradle for release signing…" python3 - "$SCRIPT_DIR/android/app/build.gradle" << 'PYEOF' import sys, re path = sys.argv[1] with open(path, 'r') as f: content = f.read() if 'MYAPP_UPLOAD_STORE_FILE' in content: print('[patch] build.gradle already patched, skipping.') sys.exit(0) release_signing = """\n release { if (project.hasProperty('MYAPP_UPLOAD_STORE_FILE')) { storeFile file(MYAPP_UPLOAD_STORE_FILE) storePassword MYAPP_UPLOAD_STORE_PASSWORD keyAlias MYAPP_UPLOAD_KEY_ALIAS keyPassword MYAPP_UPLOAD_KEY_PASSWORD } }""" # Insert release block right after the closing brace of the debug signingConfig content = re.sub( r'(signingConfigs\s*\{[\s\S]*?debug\s*\{[\s\S]*?\})', r'\1' + release_signing, content, count=1 ) # Switch release buildType from debug signing to release signing # Only replace the first occurrence after "release {" (not the debug one) def replace_release_signing(m): return m.group(0).replace('signingConfig signingConfigs.debug', 'signingConfig signingConfigs.release', 1) content = re.sub( r'release\s*\{[^}]*signingConfig\s+signingConfigs\.debug', replace_release_signing, content, count=1, flags=re.DOTALL ) with open(path, 'w') as f: f.write(content) print('[patch] build.gradle patched for release signing.') PYEOF # ─── 5. Inject signing props into gradle.properties ──────────────────────── info "Injecting signing credentials into gradle.properties…" GRADLE_PROPS="$SCRIPT_DIR/android/gradle.properties" # Remove any previous signing block left by this script sed -i '/^# --- postiz release signing/,/^# --- end signing/d' "$GRADLE_PROPS" 2>/dev/null || true cat >> "$GRADLE_PROPS" << EOF # --- postiz release signing (injected by build-apk.sh — wiped after build) MYAPP_UPLOAD_STORE_FILE=$KEYSTORE_PATH_EXPANDED MYAPP_UPLOAD_STORE_PASSWORD=$KEYSTORE_STORE_PASSWORD MYAPP_UPLOAD_KEY_ALIAS=$KEYSTORE_ALIAS MYAPP_UPLOAD_KEY_PASSWORD=$KEYSTORE_KEY_PASSWORD # --- end signing EOF # ─── 6. Gradle build ─────────────────────────────────────────────────────── cd "$SCRIPT_DIR/android" if [ "$BUILD_TYPE" = "--aab" ]; then info "Building AAB (release)…" ./gradlew bundleRelease ARTIFACT="app/build/outputs/bundle/release/app-release.aab" EXT="aab" else info "Building APK (release)…" ./gradlew assembleRelease ARTIFACT="app/build/outputs/apk/release/app-release.apk" EXT="apk" fi # ─── 7. Wipe credentials from gradle.properties ─────────────────────────── sed -i '/^# --- postiz release signing/,/^# --- end signing/d' "$GRADLE_PROPS" info "Signing credentials wiped from gradle.properties." # ─── 8. Copy to dist/ ───────────────────────────────────────────────────── mkdir -p "$DIST_DIR" TIMESTAMP="$(date +%Y%m%d-%H%M)" OUTPUT="$DIST_DIR/postiz-mobile-$TIMESTAMP.$EXT" cp "$ARTIFACT" "$OUTPUT" echo "" info "Build complete!" echo -e " ${GREEN}→ $OUTPUT${NC}" echo ""