571 lines
17 KiB
YAML
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
|