diff --git a/docker/Dockerfile b/docker/Dockerfile index b92aa420..802d2886 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,86 +1,51 @@ -FROM ubuntu:22.04 +# Toolchain image: matches the build environment in +# .github/workflows/release.yml — ubuntu-latest -> ubuntu:24.04, +# actions/setup-java -> Temurin JDK 21, and AGP-managed Android build-tools/NDK +# (CI pins none) — BUT Flutter is intentionally NOT installed here. +# +# Flutter comes from the repo's pinned .flutter submodule (mounted at runtime by +# docker/builder.sh), so the Flutter version stays reproducible and matches +# build.sh. (This is the one deliberate divergence from release.yml, which uses +# the floating `stable` channel.) +# +# For a fully reproducible image, pin by digest: FROM ubuntu:24.04@sha256: +FROM ubuntu:24.04 -ENV DEBIAN_FRONTEND noninteractive -ENV ANDROID_SDK_ROOT /opt/android-sdk-linux -ENV PATH "${PATH}:/opt/flutter/bin:/root/.pub-cache/bin:${ANDROID_SDK_ROOT}/cmdline-tools/latest/bin:/opt/android-sdk-linux/platform-tools" +ENV DEBIAN_FRONTEND=noninteractive +ENV JAVA_HOME=/opt/java/temurin-21 +ENV ANDROID_HOME=/opt/android-sdk +ENV ANDROID_SDK_ROOT=/opt/android-sdk +ENV PATH="${PATH}:${JAVA_HOME}/bin:${ANDROID_HOME}/cmdline-tools/latest/bin:${ANDROID_HOME}/platform-tools" -ENV HOME=/root - -# Prereqs -RUN \ - apt-get update &&\ +# System deps for Flutter Android builds (no Linux desktop / GTK toolchain). +RUN apt-get update && \ apt-get install -y --no-install-recommends \ - bash \ - curl \ - file \ - git 2.x \ - unzip \ - xz-utils \ - zip \ - libglu1-mesa \ - libxi-dev \ - libxmu-dev \ - libglu1-mesa-dev \ - git-lfs \ - openssl \ - wget + bash ca-certificates curl file git git-lfs openssl unzip wget xz-utils zip && \ + apt-get clean && rm -rf /var/lib/apt/lists/* && \ + git config --system --add safe.directory '*' -# Build prereqs -RUN \ - apt-get install -y \ - cmake curl git wget unzip libgconf-2-4 gdb libstdc++6 libglu1-mesa fonts-droid-fallback lib32stdc++6 python3 sed \ - cmake ninja-build build-essential libgl1-mesa-dev libegl1-mesa-dev libgles2-mesa-dev clang pkg-config libgtk-3-dev \ - liblzma-dev libmount-dev libblkid-dev libgcrypt20-dev libgpg-error-dev libssl-dev libpng-dev libjpeg-dev \ - libtiff-dev libgif-dev libgtk-3-dev +# Temurin JDK 21 — matches actions/setup-java (distribution: temurin, java-version: 21). +# The Adoptium "latest 21 GA" URL mirrors what setup-java resolves. +RUN mkdir -p /opt/java && \ + wget -q "https://api.adoptium.net/v3/binary/latest/21/ga/linux/x64/jdk/hotspot/normal/eclipse" -O /tmp/jdk.tar.gz && \ + tar -xzf /tmp/jdk.tar.gz -C /opt/java && \ + rm /tmp/jdk.tar.gz && \ + mv /opt/java/jdk-21* "${JAVA_HOME}" -# Android SDK prequisites -# https://developer.android.com/studio#command-tools -RUN \ - apt-get install -y --no-install-recommends \ - git \ - git-lfs \ - openssl \ - wget \ - unzip +# Android command-line tools + a base SDK. NDK/cmake are deliberately NOT +# pre-installed: AGP auto-downloads them at build time (exactly like CI, which +# pins none; the NDK version still comes from build.gradle.kts). ANDROID_HOME is +# made writable so the non-root runtime user can install them on demand. +ARG CMDLINE_TOOLS_URL=https://dl.google.com/android/repository/commandlinetools-linux-13114758_latest.zip +RUN mkdir -p ${ANDROID_HOME}/cmdline-tools && \ + wget -q "${CMDLINE_TOOLS_URL}" -O /tmp/tools.zip && \ + unzip -q /tmp/tools.zip -d ${ANDROID_HOME}/cmdline-tools && \ + mv ${ANDROID_HOME}/cmdline-tools/cmdline-tools ${ANDROID_HOME}/cmdline-tools/latest && \ + rm /tmp/tools.zip && \ + mkdir -p /root/.android && touch /root/.android/repositories.cfg && \ + yes | sdkmanager --licenses && \ + sdkmanager "platform-tools" "platforms;android-35" "build-tools;35.0.0" && \ + chmod -R a+w ${ANDROID_HOME} -# Android SDK -RUN \ - wget --quiet https://dl.google.com/android/repository/commandlinetools-linux-13114758_latest.zip -O /tmp/tools.zip && \ - mkdir -p ${ANDROID_SDK_ROOT}/cmdline-tools && \ - unzip -q /tmp/tools.zip -d ${ANDROID_SDK_ROOT}/cmdline-tools && \ - mv ${ANDROID_SDK_ROOT}/cmdline-tools/cmdline-tools ${ANDROID_SDK_ROOT}/cmdline-tools/latest && \ - rm -v /tmp/tools.zip && \ - mkdir -p /root/.android/ && touch /root/.android/repositories.cfg &&\ - apt-get install -y --no-install-recommends openjdk-21-jdk openjdk-21-jre &&\ - yes | sdkmanager --licenses &&\ - sdkmanager --update - - # Platform tools -# Get latest with sdkmanager --list -RUN sdkmanager --install "build-tools;33.0.1" -RUN sdkmanager --install "ndk;26.3.11579264" -RUN sdkmanager --install "cmake;4.0.2" -RUN sdkmanager --install platform-tools -RUN sdkmanager --install emulator -RUN sdkmanager --install tools -RUN sdkmanager --install "platforms;android-28" -RUN sdkmanager --install "platforms;android-31" -RUN sdkmanager --install "platforms;android-32" -RUN sdkmanager --install "platforms;android-33" -RUN sdkmanager --install "platforms;android-34" -RUN sdkmanager --install "platforms;android-35" - -# Flutter -ARG DEV_UID=0 -RUN \ - wget --quiet https://storage.googleapis.com/flutter_infra_release/releases/stable/linux/flutter_linux_3.29.3-stable.tar.xz -O /tmp/flutter.tar.xz &&\ - mkdir -p /opt &&\ - cd /opt &&\ - tar xf /tmp/flutter.tar.xz &&\ - rm /tmp/flutter.tar.xz &&\ - git config --global --add safe.directory /opt/flutter &&\ - dart pub global activate cider &&\ - chown -R ${DEV_UID} /opt/flutter -RUN flutter --disable-analytics -RUN flutter upgrade -RUN chmod a+w /opt/flutter/packages -R +# Flutter is provided by the repo's pinned .flutter submodule at runtime +# (see docker/builder.sh), NOT installed in the image. diff --git a/docker/builder.sh b/docker/builder.sh index f5c1c168..6ec3a4b5 100755 --- a/docker/builder.sh +++ b/docker/builder.sh @@ -1,21 +1,45 @@ #!/usr/bin/env bash +# Run the build image (matches release.yml's environment: ubuntu 24.04, Temurin +# 21, AGP-managed Android SDK) with the repo mounted. Flutter is provided by the +# repo's pinned .flutter submodule, NOT the image — keeping the Flutter version +# reproducible and consistent with build.sh. +# +# ./docker/builder.sh # interactive shell +# ./docker/builder.sh ./build.sh # run a build non-interactively (CI) +# ./docker/builder.sh flutter build apk --release --flavor normal +set -euo pipefail -SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) +SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &>/dev/null && pwd) +REPO_DIR=$(cd -- "${SCRIPT_DIR}/.." &>/dev/null && pwd) +IMAGE=${IMAGE:-flutter-builder-obtainium:latest} +SDK=/opt/android-sdk +JDK=/opt/java/temurin-21 -cd ${SCRIPT_DIR}/.. -mkdir -p ./data/home -docker run \ +# Flutter comes from the repo's pinned submodule, not the image. +if [ ! -x "${REPO_DIR}/.flutter/bin/flutter" ]; then + echo "WARNING: .flutter submodule not initialised. Run:" >&2 + echo " git submodule update --init --recursive" >&2 +fi + +# Persistent pub/gradle caches so rebuilds are fast (data/ is git-ignored). +mkdir -p "${REPO_DIR}/data/home" + +# Allocate a TTY only when attached to one (so CI/piped use works too). +TTY_FLAGS=(-i) +[ -t 0 ] && TTY_FLAGS+=(-t) + +# Put the submodule's Flutter first on PATH; JDK + Android SDK tools come from +# the image. JAVA_HOME/ANDROID_HOME carry over from the image's ENV. +exec docker run \ --rm \ - -ti \ - --net host \ - -v "${PWD}/../:${PWD}/../":z \ - -w "${PWD}" \ - --name flutter-dev-obtainium \ - --user $(id -u) \ - -v ./data/home:/home/${USER}:z \ - -e USER=${USER} \ - -e HOME=/home/${USER} \ - -e ANDROID_USER_HOME=${HOME}/.android \ - -e GRADLE_USER_HOME=${HOME}/.gradle \ - -e PS1="${debian_chroot:+($debian_chroot)}${USER}@\h:\w\$ " \ - flutter-builder-obtainium \ No newline at end of file + "${TTY_FLAGS[@]}" \ + --user "$(id -u):$(id -g)" \ + -v "${REPO_DIR}:${REPO_DIR}:z" \ + -v "${REPO_DIR}/data/home:/home/builder:z" \ + -w "${REPO_DIR}" \ + -e HOME=/home/builder \ + -e GRADLE_USER_HOME=/home/builder/.gradle \ + -e ANDROID_USER_HOME=/home/builder/.android \ + -e PATH="${REPO_DIR}/.flutter/bin:/home/builder/.pub-cache/bin:${JDK}/bin:${SDK}/cmdline-tools/latest/bin:${SDK}/platform-tools:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" \ + "${IMAGE}" \ + "$@" diff --git a/docker/mkbuilder.sh b/docker/mkbuilder.sh index 77fd71c8..f729fd2f 100755 --- a/docker/mkbuilder.sh +++ b/docker/mkbuilder.sh @@ -1,14 +1,16 @@ #!/usr/bin/env bash +# Build the toolchain image used by ./docker/builder.sh. +set -euo pipefail -SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) -D=$(date +'%Y%m%d.%H%M%S%3N') +SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &>/dev/null && pwd) +IMAGE=${IMAGE:-flutter-builder-obtainium} +STAMP=$(date +'%Y%m%d.%H%M%S') -set -e - -cd "${SCRIPT_DIR}/.." -# Create the builder image +# The Dockerfile COPYs nothing (the source tree is mounted at runtime), so use +# docker/ as the build context. This keeps the context tiny instead of shipping +# the whole repo — including the multi-GB .flutter submodule — to the daemon. docker build \ - -t flutter-builder-obtainium \ - -f ./docker/Dockerfile \ - --build-arg="DEV_UID=$(id -u)" \ - . + -t "${IMAGE}:latest" \ + -t "${IMAGE}:${STAMP}" \ + -f "${SCRIPT_DIR}/Dockerfile" \ + "${SCRIPT_DIR}" diff --git a/sign.sh b/sign.sh index 00bd0383..7ef85b6e 100755 --- a/sign.sh +++ b/sign.sh @@ -19,19 +19,26 @@ KEYSTORE_LOCATION="$1" BUILD_DIR="$2" read -s -p "Enter your keystore password: " KEYSTORE_PASSWORD +export KEYSTORE_PASSWORD if [ -z "$ANDROID_HOME" ]; then - ANDROID_HOME=~/Android/Sdk + ANDROID_HOME="$HOME/Android/Sdk" fi if [ ! -d "$ANDROID_HOME" ]; then echo "Could not find Android SDK!" >&2 exit 1 fi +APKSIGNER=$(find "${ANDROID_HOME}/build-tools" -maxdepth 2 -name 'apksigner' -type f | sort -V | tail -1) +if [ -z "$APKSIGNER" ]; then + echo "Could not find apksigner in ${ANDROID_HOME}/build-tools!" >&2 + exit 1 +fi + for apk in "$BUILD_DIR"/*-release*.apk; do unsignedApk=${apk/-release/-unsigned} mv "$apk" "$unsignedApk" - ${ANDROID_HOME}/build-tools/$(ls ${ANDROID_HOME}/build-tools/ | tail -1)/apksigner sign --ks "$KEYSTORE_LOCATION" --ks-pass pass:"${KEYSTORE_PASSWORD}" --out "${apk}" "${unsignedApk}" + "$APKSIGNER" sign --ks "$KEYSTORE_LOCATION" --ks-pass env:KEYSTORE_PASSWORD --out "${apk}" "${unsignedApk}" sha256sum ${apk} | cut -d " " -f 1 >"$apk".sha256 gpg --batch --sign --detach-sig "$apk".sha256 rm "$unsignedApk"