Files
rustdesk-android-build/.gitea/workflows/android.yml
T
gandc 72ca26f35f
Build RustDesk Android / Build RustDesk Android ARM64 (push) Successful in 13m13s
move to prebuild docker image
2026-09-09 22:14:45 +03:00

571 lines
17 KiB
YAML

name: Build RustDesk Android
on:
workflow_dispatch:
push:
branches:
- main
paths:
- ".gitea/workflows/android.yml"
env:
RUSTDESK_VERSION: "1.4.9"
ANDROID_BUILD_TOOLS_VERSION: "34.0.0"
jobs:
build-android:
name: Build RustDesk Android ARM64
runs-on: rustdesk-android
steps:
# -----------------------------------------------------------------------
# Verify prebuilt builder image
# -----------------------------------------------------------------------
- name: Verify builder environment
run: |
set -euxo pipefail
echo "=== Java ==="
java -version
echo
echo "=== Rust ==="
rustc --version
cargo --version
rustup target list --installed
echo
echo "=== Cargo tools ==="
cargo ndk --version
cargo expand --version
flutter_rust_bridge_codegen --version
echo
echo "=== Android SDK / NDK ==="
echo "ANDROID_SDK_ROOT=${ANDROID_SDK_ROOT}"
echo "ANDROID_NDK_HOME=${ANDROID_NDK_HOME}"
sdkmanager --version
test -d "${ANDROID_NDK_HOME}"
test -x "${ANDROID_SDK_ROOT}/build-tools/${ANDROID_BUILD_TOOLS_VERSION}/apksigner"
echo
echo "=== Flutter bridge ==="
test -x "${FLUTTER_BRIDGE_HOME}/bin/flutter"
"${FLUTTER_BRIDGE_HOME}/bin/flutter" --version
echo
echo "=== Flutter Android ==="
test -x "${FLUTTER_ANDROID_HOME}/bin/flutter"
"${FLUTTER_ANDROID_HOME}/bin/flutter" --version
echo
echo "=== vcpkg ==="
test -x "${VCPKG_ROOT}/vcpkg"
"${VCPKG_ROOT}/vcpkg" version
test -d "${VCPKG_ROOT}/installed/arm64-android"
echo
echo "Builder environment OK"
# -----------------------------------------------------------------------
# RustDesk source
# -----------------------------------------------------------------------
- name: Clone RustDesk
run: |
set -euxo pipefail
rm -rf rustdesk
git clone \
--branch "${RUSTDESK_VERSION}" \
--depth 1 \
--recurse-submodules \
--shallow-submodules \
https://github.com/rustdesk/rustdesk.git \
rustdesk
cd rustdesk
echo "RustDesk commit:"
git rev-parse HEAD
echo
echo "RustDesk tag:"
git describe --tags --always
echo
echo "Submodules:"
git submodule status
# -----------------------------------------------------------------------
# Custom RustDesk ID server + public key
# Relay is NOT modified.
# -----------------------------------------------------------------------
- name: Configure custom ID server and key
env:
CUSTOM_ID_SERVER: ${{ secrets.RUSTDESK_ID_SERVER }}
CUSTOM_SERVER_KEY: ${{ secrets.RUSTDESK_KEY }}
run: |
set -euo pipefail
if [ -z "${CUSTOM_ID_SERVER}" ]; then
echo "ERROR: RUSTDESK_ID_SERVER secret is empty"
exit 1
fi
if [ -z "${CUSTOM_SERVER_KEY}" ]; then
echo "ERROR: RUSTDESK_KEY secret is empty"
exit 1
fi
cd rustdesk
python3 <<'PY'
import json
import os
import re
from pathlib import Path
path = Path("libs/hbb_common/src/config.rs")
if not path.exists():
raise SystemExit(
"libs/hbb_common/src/config.rs does not exist. "
"Check that submodules were cloned correctly."
)
text = path.read_text(encoding="utf-8")
id_server = os.environ["CUSTOM_ID_SERVER"].strip()
server_key = os.environ["CUSTOM_SERVER_KEY"].strip()
if not id_server:
raise SystemExit("RUSTDESK_ID_SERVER is empty")
if not server_key:
raise SystemExit("RUSTDESK_KEY is empty")
# json.dumps() produces a correctly escaped quoted string which is
# also valid as a Rust string literal for these values.
id_server_literal = json.dumps(id_server)
server_key_literal = json.dumps(server_key)
text, id_count = re.subn(
r'pub const RENDEZVOUS_SERVERS:\s*&\[&str\]\s*=\s*&\[[^\n]*\];',
f'pub const RENDEZVOUS_SERVERS: &[&str] = &[{id_server_literal}];',
text,
count=1,
)
text, key_count = re.subn(
r'pub const RS_PUB_KEY:\s*&str\s*=\s*"[^"]*";',
f'pub const RS_PUB_KEY: &str = {server_key_literal};',
text,
count=1,
)
if id_count != 1:
raise SystemExit(
"Failed to patch RENDEZVOUS_SERVERS. "
"RustDesk source layout may have changed."
)
if key_count != 1:
raise SystemExit(
"Failed to patch RS_PUB_KEY. "
"RustDesk source layout may have changed."
)
path.write_text(text, encoding="utf-8")
print(f"ID server configured: {id_server}")
print("Server public key configured successfully")
PY
echo
echo "Checking patched configuration..."
grep -n "RENDEZVOUS_SERVERS" \
libs/hbb_common/src/config.rs
if grep -q 'rs-ny\.rustdesk\.com' \
libs/hbb_common/src/config.rs; then
echo "ERROR: Default RustDesk ID server is still present"
exit 1
fi
if ! grep -q 'pub const RS_PUB_KEY' \
libs/hbb_common/src/config.rs; then
echo "ERROR: RS_PUB_KEY was not found"
exit 1
fi
echo "RS_PUB_KEY patched: yes"
echo "Relay configuration untouched: yes"
# -----------------------------------------------------------------------
# Generate Flutter/Rust bridge with Flutter 3.22.3.
#
# RustDesk 1.4.9 temporarily needs extended_text 13.0.0 for this stage.
# We restore 14.0.0 immediately afterwards for the Android build.
# -----------------------------------------------------------------------
- name: Generate Flutter Rust bridge
run: |
set -euxo pipefail
export PATH="${FLUTTER_BRIDGE_HOME}/bin:${CARGO_HOME}/bin:${PATH}"
echo "Bridge Flutter:"
flutter --version
cd rustdesk
if ! grep -q 'extended_text: 14.0.0' flutter/pubspec.yaml; then
echo "ERROR: Expected extended_text 14.0.0 before bridge generation"
grep -n "extended_text:" flutter/pubspec.yaml || true
exit 1
fi
sed -i \
's/extended_text: 14.0.0/extended_text: 13.0.0/g' \
flutter/pubspec.yaml
echo
echo "Bridge dependency:"
grep -n "extended_text:" flutter/pubspec.yaml
pushd flutter
flutter pub get
popd
flutter_rust_bridge_codegen \
--rust-input ./src/flutter_ffi.rs \
--dart-output ./flutter/lib/generated_bridge.dart \
--c-output ./flutter/macos/Runner/bridge_generated.h
cp \
./flutter/macos/Runner/bridge_generated.h \
./flutter/ios/Runner/bridge_generated.h
test -f src/bridge_generated.rs
test -f src/bridge_generated.io.rs
test -f flutter/lib/generated_bridge.dart
test -f flutter/lib/generated_bridge.freezed.dart
test -f flutter/macos/Runner/bridge_generated.h
test -f flutter/ios/Runner/bridge_generated.h
# Restore the original Android dependency versions. Upstream performs
# bridge and Android builds from separate checkouts; we reproduce that
# behaviour inside one job.
git checkout -- \
flutter/pubspec.yaml \
flutter/pubspec.lock
rm -rf flutter/.dart_tool
echo
echo "Android dependency restored:"
grep -n "extended_text:" flutter/pubspec.yaml
if ! grep -q 'extended_text: 14.0.0' flutter/pubspec.yaml; then
echo "ERROR: extended_text 14.0.0 was not restored"
exit 1
fi
echo
echo "Flutter Rust bridge generated successfully"
# -----------------------------------------------------------------------
# Android native dependencies.
#
# They are prebuilt in rustdesk-android-builder:1.4.9-r1. Running the
# upstream manifest command here verifies the exact RustDesk 1.4.9 state
# and should reuse /opt/vcpkg/installed/arm64-android.
# -----------------------------------------------------------------------
- name: Verify Android native dependencies
run: |
set -euxo pipefail
cd rustdesk
echo "VCPKG_ROOT=${VCPKG_ROOT}"
echo "ANDROID_NDK_HOME=${ANDROID_NDK_HOME}"
if ! ./flutter/build_android_deps.sh arm64-v8a; then
echo
echo "Android dependency verification/build failed."
echo "Dumping vcpkg logs:"
echo
find "${VCPKG_ROOT}" \
-name "*.log" \
-type f \
| while read -r logfile; do
echo "===== ${logfile} ====="
cat "${logfile}" || true
echo
done
exit 1
fi
test -d "${VCPKG_ROOT}/installed/arm64-android"
echo
echo "Android native dependencies OK"
# -----------------------------------------------------------------------
# Build Rust native Android library
# -----------------------------------------------------------------------
- name: Build RustDesk native library
run: |
set -euxo pipefail
cd rustdesk
rustc --version
cargo --version
cargo ndk --version
if ! rustup target list --installed | grep -qx 'aarch64-linux-android'; then
echo "ERROR: aarch64-linux-android Rust target is missing from builder image"
exit 1
fi
./flutter/ndk_arm64.sh
test -f \
target/aarch64-linux-android/release/liblibrustdesk.so
echo
echo "Native library:"
ls -lh \
target/aarch64-linux-android/release/liblibrustdesk.so
# -----------------------------------------------------------------------
# Permanent Android release signing
#
# Required Gitea Actions secrets:
# ANDROID_KEYSTORE_BASE64
# ANDROID_KEYSTORE_PASSWORD
# ANDROID_KEY_ALIAS
#
# key.properties: flutter/android/key.properties
# keystore: flutter/android/app/rustdesk-release.jks
# -----------------------------------------------------------------------
- name: Configure Android release signing
env:
ANDROID_KEYSTORE_BASE64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }}
ANDROID_KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }}
ANDROID_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }}
run: |
set -euo pipefail
if [ -z "${ANDROID_KEYSTORE_BASE64}" ]; then
echo "ERROR: ANDROID_KEYSTORE_BASE64 secret is empty"
exit 1
fi
if [ -z "${ANDROID_KEYSTORE_PASSWORD}" ]; then
echo "ERROR: ANDROID_KEYSTORE_PASSWORD secret is empty"
exit 1
fi
if [ -z "${ANDROID_KEY_ALIAS}" ]; then
echo "ERROR: ANDROID_KEY_ALIAS secret is empty"
exit 1
fi
cd rustdesk/flutter/android
echo "Restoring Android release keystore..."
printf '%s' "${ANDROID_KEYSTORE_BASE64}" \
| base64 --decode \
> app/rustdesk-release.jks
chmod 600 app/rustdesk-release.jks
cat > key.properties <<EOF2
storePassword=${ANDROID_KEYSTORE_PASSWORD}
keyPassword=${ANDROID_KEYSTORE_PASSWORD}
keyAlias=${ANDROID_KEY_ALIAS}
storeFile=rustdesk-release.jks
EOF2
chmod 600 key.properties
if [ ! -s app/rustdesk-release.jks ]; then
echo "ERROR: Restored keystore is empty"
exit 1
fi
echo "Checking keystore..."
keytool \
-list \
-keystore app/rustdesk-release.jks \
-storepass "${ANDROID_KEYSTORE_PASSWORD}" \
-alias "${ANDROID_KEY_ALIAS}" \
>/dev/null
echo "Keystore is valid."
echo "Android release signing configured successfully."
# -----------------------------------------------------------------------
# Build signed ARM64 APK with Flutter 3.24.5 from builder image
# -----------------------------------------------------------------------
- name: Build signed APK
run: |
set -euxo pipefail
export PATH="${FLUTTER_ANDROID_HOME}/bin:${CARGO_HOME}/bin:${PATH}"
echo "Android Flutter:"
flutter --version
cd rustdesk
mkdir -p \
flutter/android/app/src/main/jniLibs/arm64-v8a
cp \
target/aarch64-linux-android/release/liblibrustdesk.so \
flutter/android/app/src/main/jniLibs/arm64-v8a/librustdesk.so
cp \
"${ANDROID_NDK_HOME}/toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr/lib/aarch64-linux-android/libc++_shared.so" \
flutter/android/app/src/main/jniLibs/arm64-v8a/libc++_shared.so
sed -i \
's/org.gradle.jvmargs=-Xmx1024M/org.gradle.jvmargs=-Xmx2g/g' \
flutter/android/gradle.properties
echo
echo "JNI libraries:"
ls -lh \
flutter/android/app/src/main/jniLibs/arm64-v8a/
echo
echo "Signing files:"
ls -lh \
flutter/android/key.properties \
flutter/android/app/rustdesk-release.jks
pushd flutter
echo
echo "Checking Android Flutter dependencies..."
if ! grep -q 'extended_text: 14.0.0' pubspec.yaml; then
echo "ERROR: Wrong extended_text version for Android build"
grep -n "extended_text:" pubspec.yaml || true
exit 1
fi
flutter clean
flutter pub get
echo
echo "Resolved extended_text:"
flutter pub deps | grep -A2 -B2 extended_text || true
flutter build apk \
--release \
--target-platform android-arm64 \
--split-per-abi
popd
test -f \
flutter/build/app/outputs/flutter-apk/app-arm64-v8a-release.apk
mkdir -p output
cp \
flutter/build/app/outputs/flutter-apk/app-arm64-v8a-release.apk \
"output/rustdesk-${RUSTDESK_VERSION}-custom-arm64.apk"
echo
echo "Built APK:"
ls -lh \
"output/rustdesk-${RUSTDESK_VERSION}-custom-arm64.apk"
echo
echo "SHA256:"
sha256sum \
"output/rustdesk-${RUSTDESK_VERSION}-custom-arm64.apk"
# -----------------------------------------------------------------------
# Verify APK release signature
# -----------------------------------------------------------------------
- name: Verify APK signature
run: |
set -euxo pipefail
APK="rustdesk/output/rustdesk-${RUSTDESK_VERSION}-custom-arm64.apk"
APKSIGNER="${ANDROID_SDK_ROOT}/build-tools/${ANDROID_BUILD_TOOLS_VERSION}/apksigner"
if [ ! -x "${APKSIGNER}" ]; then
echo "ERROR: apksigner not found:"
echo "${APKSIGNER}"
exit 1
fi
if [ ! -f "${APK}" ]; then
echo "ERROR: APK not found:"
echo "${APK}"
exit 1
fi
echo
echo "Verifying APK signature..."
echo
"${APKSIGNER}" verify \
--verbose \
--print-certs \
"${APK}"
echo
echo "APK signature verification successful."
# -----------------------------------------------------------------------
# APK checksum
# -----------------------------------------------------------------------
- name: Create checksum
run: |
set -euxo pipefail
cd rustdesk/output
sha256sum \
"rustdesk-${RUSTDESK_VERSION}-custom-arm64.apk" \
> "rustdesk-${RUSTDESK_VERSION}-custom-arm64.apk.sha256"
cat \
"rustdesk-${RUSTDESK_VERSION}-custom-arm64.apk.sha256"
# -----------------------------------------------------------------------
# Upload Gitea Actions artifact
# -----------------------------------------------------------------------
- name: Upload APK
uses: actions/upload-artifact@v4.6.2
with:
name: rustdesk-${{ env.RUSTDESK_VERSION }}-custom-arm64
path: |
rustdesk/output/rustdesk-${{ env.RUSTDESK_VERSION }}-custom-arm64.apk
rustdesk/output/rustdesk-${{ env.RUSTDESK_VERSION }}-custom-arm64.apk.sha256
if-no-files-found: error