From 5c8c6df16bbbff6f3a081151b8a6b1afeb97ea2d Mon Sep 17 00:00:00 2001 From: Edison Jwa Date: Mon, 25 May 2026 03:21:21 +0900 Subject: [PATCH] Make beta release artifacts reproducible on Linux and Android Constraint: SRS-118 and SRS-119 require a Linux release package and an Android release AAB, and the current workspace also needs the sibling oboe-rs checkout for Cargo manifest loading. Rejected: Keep release packaging as ad-hoc local knowledge | CI and contributors would still miss the required artifacts and hit the missing oboe-rs prerequisite. Confidence: medium Scope-risk: moderate Directive: If the oboe-rs fork path changes or is vendored, update the helper scripts and workflow checkout steps together. Tested: bash -n tools/build-linux-deb.sh tools/build-android-aab.sh; python3 YAML parse for .github/workflows/ci.yml, .github/workflows/bench-advisory.yml, .github/workflows/bench-baseline-update.yml; git diff --check Not-tested: End-to-end flutter build linux --release; end-to-end flutter build appbundle --release; GitHub Actions runtime execution --- .github/workflows/bench-advisory.yml | 2 + .github/workflows/bench-baseline-update.yml | 2 + .github/workflows/ci.yml | 65 +++++++++ tools/build-android-aab.sh | 116 ++++++++++++++++ tools/build-linux-deb.sh | 146 ++++++++++++++++++++ 5 files changed, 331 insertions(+) create mode 100755 tools/build-android-aab.sh create mode 100755 tools/build-linux-deb.sh diff --git a/.github/workflows/bench-advisory.yml b/.github/workflows/bench-advisory.yml index 265e698..c6ef433 100644 --- a/.github/workflows/bench-advisory.yml +++ b/.github/workflows/bench-advisory.yml @@ -25,6 +25,8 @@ jobs: - uses: actions/checkout@v4 with: fetch-depth: 0 + - name: Checkout oboe-rs fork + run: git clone --depth 1 https://github.com/edisonjwa/oboe-rs.git ../oboe-rs - name: System deps (cpal / Opus) run: | sudo apt-get update diff --git a/.github/workflows/bench-baseline-update.yml b/.github/workflows/bench-baseline-update.yml index cdc04b2..59ba036 100644 --- a/.github/workflows/bench-baseline-update.yml +++ b/.github/workflows/bench-baseline-update.yml @@ -18,6 +18,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + - name: Checkout oboe-rs fork + run: git clone --depth 1 https://github.com/edisonjwa/oboe-rs.git ../oboe-rs - name: System deps (cpal / Opus) run: | sudo apt-get update diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 030c1a5..fed4e98 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,6 +11,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + - name: Checkout oboe-rs fork + run: git clone --depth 1 https://github.com/edisonjwa/oboe-rs.git ../oboe-rs - name: System deps (cpal / Opus / SQLite) run: | sudo apt-get update @@ -38,6 +40,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + - name: Checkout oboe-rs fork + run: git clone --depth 1 https://github.com/edisonjwa/oboe-rs.git ../oboe-rs - uses: EmbarkStudios/cargo-deny-action@v2 with: command: check @@ -51,6 +55,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + - name: Checkout oboe-rs fork + run: git clone --depth 1 https://github.com/edisonjwa/oboe-rs.git ../oboe-rs - uses: dtolnay/rust-toolchain@stable - uses: Swatinem/rust-cache@v2 - name: Install cargo-about @@ -113,12 +119,71 @@ jobs: runs-on: macos-latest steps: - uses: actions/checkout@v4 + - name: Checkout oboe-rs fork + run: git clone --depth 1 https://github.com/edisonjwa/oboe-rs.git ../oboe-rs - uses: subosito/flutter-action@v2 with: channel: stable + - uses: dtolnay/rust-toolchain@stable + - uses: Swatinem/rust-cache@v2 - name: flutter pub get working-directory: apps/chanora_flutter run: flutter pub get - name: flutter build ios --no-codesign working-directory: apps/chanora_flutter run: flutter build ios --release --no-codesign + + flutter-linux-release-package: + name: flutter Linux release package (.deb) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Checkout oboe-rs fork + run: git clone --depth 1 https://github.com/edisonjwa/oboe-rs.git ../oboe-rs + - name: System deps (Flutter Linux + audio + packaging) + run: | + sudo apt-get update + sudo apt-get install -y \ + clang cmake ninja-build pkg-config libgtk-3-dev liblzma-dev \ + libasound2-dev libpulse-dev libopus-dev libsdl2-dev + - uses: subosito/flutter-action@v2 + with: + channel: stable + - uses: dtolnay/rust-toolchain@stable + - uses: Swatinem/rust-cache@v2 + - name: Build Linux .deb package + run: ./tools/build-linux-deb.sh + - name: Upload Linux package artifact + uses: actions/upload-artifact@v4 + with: + name: chanora-linux-deb + path: dist/linux/*.deb + + flutter-android-release-aab: + name: flutter Android release AAB + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Checkout oboe-rs fork + run: git clone --depth 1 https://github.com/edisonjwa/oboe-rs.git ../oboe-rs + - uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: '17' + - uses: android-actions/setup-android@v3 + - uses: subosito/flutter-action@v2 + with: + channel: stable + - uses: dtolnay/rust-toolchain@stable + with: + targets: aarch64-linux-android,x86_64-linux-android + - uses: Swatinem/rust-cache@v2 + - name: Install cargo-ndk + run: cargo install --locked cargo-ndk + - name: Build Android release AAB + run: ./tools/build-android-aab.sh + - name: Upload Android bundle artifact + uses: actions/upload-artifact@v4 + with: + name: chanora-android-aab + path: dist/android/*.aab diff --git a/tools/build-android-aab.sh b/tools/build-android-aab.sh new file mode 100755 index 0000000..0ad2009 --- /dev/null +++ b/tools/build-android-aab.sh @@ -0,0 +1,116 @@ +#!/usr/bin/env bash + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" +APP_DIR="$REPO_ROOT/apps/chanora_flutter" +PUBSPEC="$APP_DIR/pubspec.yaml" +OBOE_DIR="$(cd "$REPO_ROOT/.." && pwd)/oboe-rs" +DIST_DIR="$REPO_ROOT/dist/android" +TMP_DIR="" + +cleanup() { + if [[ -n "$TMP_DIR" && -d "$TMP_DIR" ]]; then + rm -rf "$TMP_DIR" + fi +} +trap cleanup EXIT + +require_cmd() { + local cmd=$1 + if ! command -v "$cmd" >/dev/null 2>&1; then + echo "ERROR: required command '$cmd' is not on PATH." >&2 + exit 1 + fi +} + +require_file() { + local path=$1 + local label=$2 + if [[ ! -f "$path" ]]; then + echo "ERROR: missing $label at $path" >&2 + exit 1 + fi +} + +ensure_release_signing() { + if [[ -n "${CHANORA_RELEASE_STORE_FILE:-}" ]]; then + local missing=() + [[ -n "${CHANORA_RELEASE_STORE_PASSWORD:-}" ]] || missing+=("CHANORA_RELEASE_STORE_PASSWORD") + [[ -n "${CHANORA_RELEASE_KEY_ALIAS:-}" ]] || missing+=("CHANORA_RELEASE_KEY_ALIAS") + [[ -n "${CHANORA_RELEASE_KEY_PASSWORD:-}" ]] || missing+=("CHANORA_RELEASE_KEY_PASSWORD") + if (( ${#missing[@]} > 0 )); then + printf 'ERROR: release signing is partially configured; missing %s\n' "${missing[*]}" >&2 + exit 1 + fi + require_file "$CHANORA_RELEASE_STORE_FILE" "release keystore" + return + fi + + require_cmd keytool + TMP_DIR="$(mktemp -d "${TMPDIR:-/tmp}/chanora-android-aab.XXXXXX")" + + export CHANORA_RELEASE_STORE_FILE="$TMP_DIR/chanora-release.keystore" + export CHANORA_RELEASE_STORE_PASSWORD="${CHANORA_RELEASE_STORE_PASSWORD:-chanora-ci}" + export CHANORA_RELEASE_KEY_ALIAS="${CHANORA_RELEASE_KEY_ALIAS:-chanora-ci}" + export CHANORA_RELEASE_KEY_PASSWORD="${CHANORA_RELEASE_KEY_PASSWORD:-$CHANORA_RELEASE_STORE_PASSWORD}" + + keytool -genkeypair \ + -storetype PKCS12 \ + -keystore "$CHANORA_RELEASE_STORE_FILE" \ + -storepass "$CHANORA_RELEASE_STORE_PASSWORD" \ + -keypass "$CHANORA_RELEASE_KEY_PASSWORD" \ + -alias "$CHANORA_RELEASE_KEY_ALIAS" \ + -keyalg RSA \ + -keysize 2048 \ + -validity 3650 \ + -dname "CN=Chanora CI,O=Chanora,OU=Engineering,L=Seoul,S=Seoul,C=KR" \ + -noprompt >/dev/null 2>&1 + + printf 'Generated ephemeral Android release keystore: %s\n' "$CHANORA_RELEASE_STORE_FILE" +} + +main() { + require_cmd flutter + require_file "$PUBSPEC" "Flutter pubspec" + + if [[ ! -f "$OBOE_DIR/Cargo.toml" ]]; then + cat >&2 <&2 + exit 1 + fi + + ( + cd "$APP_DIR" + flutter pub get + flutter build appbundle --release + ) + + local source_aab="$APP_DIR/build/app/outputs/bundle/release/app-release.aab" + require_file "$source_aab" "Android release app bundle" + + mkdir -p "$DIST_DIR" + local output="$DIST_DIR/chanora-${app_version}-release.aab" + cp "$source_aab" "$output" + + printf 'Built Android release bundle: %s\n' "$output" +} + +main "$@" diff --git a/tools/build-linux-deb.sh b/tools/build-linux-deb.sh new file mode 100755 index 0000000..754cd95 --- /dev/null +++ b/tools/build-linux-deb.sh @@ -0,0 +1,146 @@ +#!/usr/bin/env bash + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" +APP_DIR="$REPO_ROOT/apps/chanora_flutter" +PUBSPEC="$APP_DIR/pubspec.yaml" +OBOE_DIR="$(cd "$REPO_ROOT/.." && pwd)/oboe-rs" +DIST_DIR="$REPO_ROOT/dist/linux" +STAGE_DIR="$(mktemp -d "${TMPDIR:-/tmp}/chanora-linux-deb.XXXXXX")" +PKG_ROOT="$STAGE_DIR/package" +INSTALL_ROOT="$PKG_ROOT/opt/chanora" +ICON_SRC="$APP_DIR/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png" + +cleanup() { + rm -rf "$STAGE_DIR" +} +trap cleanup EXIT + +require_cmd() { + local cmd=$1 + if ! command -v "$cmd" >/dev/null 2>&1; then + echo "ERROR: required command '$cmd' is not on PATH." >&2 + exit 1 + fi +} + +require_file() { + local path=$1 + local label=$2 + if [[ ! -f "$path" ]]; then + echo "ERROR: missing $label at $path" >&2 + exit 1 + fi +} + +resolve_arch() { + if command -v dpkg >/dev/null 2>&1; then + dpkg --print-architecture + return + fi + + case "$(uname -m)" in + x86_64) echo "amd64" ;; + aarch64 | arm64) echo "arm64" ;; + *) + echo "ERROR: unsupported Linux architecture '$(uname -m)'" >&2 + exit 1 + ;; + esac +} + +main() { + require_cmd flutter + require_cmd dpkg-deb + require_file "$PUBSPEC" "Flutter pubspec" + require_file "$ICON_SRC" "launcher icon" + + if [[ ! -f "$OBOE_DIR/Cargo.toml" ]]; then + cat >&2 <&2 + exit 1 + fi + + local deb_version + deb_version="${app_version/-/~}" + + local arch + arch="$(resolve_arch)" + + ( + cd "$APP_DIR" + flutter pub get + flutter build linux --release + ) + + local bundle_dir="$APP_DIR/build/linux/x64/release/bundle" + local app_binary="$bundle_dir/chanora_flutter" + require_file "$app_binary" "Linux release bundle binary" + + mkdir -p \ + "$PKG_ROOT/DEBIAN" \ + "$INSTALL_ROOT" \ + "$PKG_ROOT/usr/bin" \ + "$PKG_ROOT/usr/share/applications" \ + "$PKG_ROOT/usr/share/icons/hicolor/256x256/apps" + + cp -R "$bundle_dir/." "$INSTALL_ROOT/" + + cat >"$PKG_ROOT/DEBIAN/control" <"$PKG_ROOT/usr/bin/chanora" <<'EOF' +#!/bin/sh +exec /opt/chanora/chanora_flutter "$@" +EOF + chmod 0755 "$PKG_ROOT/usr/bin/chanora" + + cat >"$PKG_ROOT/usr/share/applications/chanora.desktop" <<'EOF' +[Desktop Entry] +Type=Application +Name=Chanora +GenericName=Voice Client +Comment=Voice client for TeamSpeak-compatible servers +Exec=/usr/bin/chanora +Icon=chanora +Terminal=false +Categories=Network;Chat;AudioVideo; +StartupNotify=true +EOF + + cp "$ICON_SRC" "$PKG_ROOT/usr/share/icons/hicolor/256x256/apps/chanora.png" + + mkdir -p "$DIST_DIR" + local output="$DIST_DIR/chanora_${deb_version}_${arch}.deb" + dpkg-deb --build --root-owner-group "$PKG_ROOT" "$output" >/dev/null + + printf 'Built Linux package: %s\n' "$output" +} + +main "$@"