Files
gandc 02850ee1f6
Build RustDesk Android / Build RustDesk Android ARM64 (push) Successful in 15m35s
universal build workflow
2026-09-13 16:20:28 +03:00

1070 lines
30 KiB
YAML

name: Build RustDesk Android
on:
workflow_dispatch:
push:
branches:
- main
paths:
- ".gitea/workflows/android.yml"
env:
# Change only this value to build another RustDesk tag/release.
RUSTDESK_VERSION: "1.4.9"
# Target constants, not software versions.
ANDROID_ABI: arm64-v8a
RUST_TARGET: aarch64-linux-android
FLUTTER_TARGET: android-arm64
jobs:
build-android:
name: Build RustDesk Android ARM64
runs-on: rustdesk-android
steps:
# -----------------------------------------------------------------------
# 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
# -----------------------------------------------------------------------
# Resolve versions from the selected upstream RustDesk tag.
# -----------------------------------------------------------------------
- name: Resolve upstream build configuration
run: |
set -euxo pipefail
python3 <<'PY' > build-config.env
import re
from pathlib import Path
repo = Path("rustdesk")
flutter = (
repo / ".github/workflows/flutter-build.yml"
).read_text(encoding="utf-8")
bridge = (
repo / ".github/workflows/bridge.yml"
).read_text(encoding="utf-8")
def env_value(text, name, required=True):
match = re.search(
rf'(?m)^\s*{re.escape(name)}:\s*["\']?([^"\'#\s]+)',
text,
)
if match:
return match.group(1).strip()
if required:
raise SystemExit(
f"Unable to resolve upstream variable: {name}"
)
return ""
android_flutter = (
env_value(
flutter,
"ANDROID_FLUTTER_VERSION",
required=False,
)
or env_value(flutter, "FLUTTER_VERSION")
)
# Select the default bridge artifact used by Android.
bridge_flutter = ""
for block in re.findall(r"\{(.*?)\}", bridge, flags=re.S):
artifact = re.search(
r'artifact-name:\s*["\']([^"\']+)["\']',
block,
)
version = re.search(
r'flutter-version:\s*["\']([^"\']+)["\']',
block,
)
if (
artifact
and artifact.group(1) == "bridge-artifact"
and version
):
bridge_flutter = version.group(1)
break
if not bridge_flutter:
raise SystemExit(
"Unable to resolve default bridge Flutter version"
)
values = {
"RUST_VERSION":
env_value(flutter, "RUST_VERSION"),
"CARGO_NDK_VERSION":
env_value(flutter, "CARGO_NDK_VERSION"),
"ANDROID_FLUTTER_VERSION":
android_flutter,
"NDK_VERSION":
env_value(flutter, "NDK_VERSION"),
"VCPKG_COMMIT_ID":
env_value(flutter, "VCPKG_COMMIT_ID"),
"BRIDGE_RUST_VERSION":
env_value(bridge, "RUST_VERSION"),
"BRIDGE_FLUTTER_VERSION":
bridge_flutter,
"CARGO_EXPAND_VERSION":
env_value(bridge, "CARGO_EXPAND_VERSION"),
"FLUTTER_RUST_BRIDGE_VERSION":
env_value(
bridge,
"FLUTTER_RUST_BRIDGE_VERSION",
),
}
for key, value in values.items():
print(f"{key}={value}")
PY
echo "Resolved upstream configuration:"
cat build-config.env
while IFS='=' read -r key value; do
[ -z "${key}" ] && continue
echo "${key}=${value}" >> "${GITHUB_ENV}"
done < build-config.env
# -----------------------------------------------------------------------
# Prepare exact toolchain requested by the selected tag.
# Prefer the prebuilt builder image where it already matches.
# -----------------------------------------------------------------------
- name: Prepare builder environment
run: |
set -euxo pipefail
echo "=== Rust ==="
rustup toolchain install \
"${RUST_VERSION}" \
--profile minimal \
--component rustfmt
rustup target add \
--toolchain "${RUST_VERSION}" \
"${RUST_TARGET}"
export RUSTUP_TOOLCHAIN="${RUST_VERSION}"
current_cargo_ndk="$(
cargo ndk --version 2>/dev/null |
awk '{print $2}' ||
true
)"
if [ "${current_cargo_ndk}" != "${CARGO_NDK_VERSION}" ]; then
cargo install \
cargo-ndk \
--version "${CARGO_NDK_VERSION}" \
--locked \
--force
fi
echo "RUSTUP_TOOLCHAIN=${RUST_VERSION}" \
>> "${GITHUB_ENV}"
rustc --version
cargo --version
cargo ndk --version
echo
echo "=== Flutter SDKs ==="
flutter_version() {
local root="$1"
if [ ! -x "${root}/bin/flutter" ]; then
return 1
fi
git config --global \
--add safe.directory "${root}" \
>/dev/null 2>&1 \
|| true
git \
-C "${root}" \
describe \
--tags \
--exact-match \
HEAD \
2>/dev/null \
|| true
}
prepare_flutter() {
local requested="$1"
local preferred="$2"
local fallback="$3"
local root=""
if (
[ -n "${preferred}" ] &&
[ "$(flutter_version "${preferred}")" = "${requested}" ]
); then
root="${preferred}"
else
root="${fallback}"
if [ "$(flutter_version "${root}")" != "${requested}" ]; then
rm -rf "${root}"
mkdir -p "$(dirname "${root}")"
git clone \
--branch "${requested}" \
--depth 1 \
https://github.com/flutter/flutter.git \
"${root}"
fi
fi
git config --global \
--add safe.directory "${root}" \
>/dev/null 2>&1 \
|| true
printf '%s\n' "${root}"
}
BRIDGE_FLUTTER_ROOT="$(
prepare_flutter \
"${BRIDGE_FLUTTER_VERSION}" \
"${FLUTTER_BRIDGE_HOME:-}" \
"${HOME}/.cache/rustdesk/flutter-${BRIDGE_FLUTTER_VERSION}"
)"
ANDROID_FLUTTER_ROOT="$(
prepare_flutter \
"${ANDROID_FLUTTER_VERSION}" \
"${FLUTTER_ANDROID_HOME:-}" \
"${HOME}/.cache/rustdesk/flutter-${ANDROID_FLUTTER_VERSION}"
)"
echo "BRIDGE_FLUTTER_ROOT=${BRIDGE_FLUTTER_ROOT}" \
>> "${GITHUB_ENV}"
echo "ANDROID_FLUTTER_ROOT=${ANDROID_FLUTTER_ROOT}" \
>> "${GITHUB_ENV}"
"${BRIDGE_FLUTTER_ROOT}/bin/flutter" \
config \
--no-analytics \
>/dev/null
"${ANDROID_FLUTTER_ROOT}/bin/flutter" \
config \
--no-analytics \
>/dev/null
echo "Bridge Flutter:"
"${BRIDGE_FLUTTER_ROOT}/bin/flutter" --version
echo
echo "Android Flutter:"
"${ANDROID_FLUTTER_ROOT}/bin/flutter" --version
echo
echo "=== Android NDK ==="
# Convert r28c -> expected Pkg.Revision prefix 28.2.
NDK_EXPECTED_PREFIX="$(
python3 <<'PY'
import os
import re
version = os.environ["NDK_VERSION"]
match = re.fullmatch(r"r(\d+)([a-z]?)", version)
if not match:
raise SystemExit(
f"Unsupported NDK version format: {version}"
)
major = int(match.group(1))
letter = match.group(2)
if letter:
minor = ord(letter) - ord("a")
print(f"{major}.{minor}.")
else:
print(f"{major}.")
PY
)"
current_ndk_ok=0
if (
[ -n "${ANDROID_NDK_HOME:-}" ] &&
[ -f "${ANDROID_NDK_HOME}/source.properties" ]
); then
CURRENT_NDK_REVISION="$(
sed -n \
's/^Pkg\.Revision[[:space:]]*=[[:space:]]*//p' \
"${ANDROID_NDK_HOME}/source.properties"
)"
if [[ "${CURRENT_NDK_REVISION}" == "${NDK_EXPECTED_PREFIX}"* ]]; then
current_ndk_ok=1
fi
fi
if [ "${current_ndk_ok}" -eq 0 ]; then
ANDROID_NDK_HOME="${HOME}/.cache/rustdesk/android-ndk-${NDK_VERSION}"
if [ ! -f "${ANDROID_NDK_HOME}/source.properties" ]; then
rm -rf "${ANDROID_NDK_HOME}"
tmp_zip="${HOME}/.cache/rustdesk/android-ndk-${NDK_VERSION}.zip"
tmp_extract="${HOME}/.cache/rustdesk/ndk-extract-${NDK_VERSION}"
mkdir -p "${HOME}/.cache/rustdesk"
rm -rf "${tmp_extract}"
curl -fL \
"https://dl.google.com/android/repository/android-ndk-${NDK_VERSION}-linux.zip" \
-o "${tmp_zip}"
python3 -m zipfile \
-e \
"${tmp_zip}" \
"${tmp_extract}"
mv \
"${tmp_extract}/android-ndk-${NDK_VERSION}" \
"${ANDROID_NDK_HOME}"
rm -rf "${tmp_extract}"
fi
fi
test -f "${ANDROID_NDK_HOME}/source.properties"
echo "ANDROID_NDK_HOME=${ANDROID_NDK_HOME}" \
>> "${GITHUB_ENV}"
echo "ANDROID_NDK_ROOT=${ANDROID_NDK_HOME}" \
>> "${GITHUB_ENV}"
cat "${ANDROID_NDK_HOME}/source.properties"
echo
echo "=== vcpkg ==="
test -n "${VCPKG_ROOT:-}"
test -d "${VCPKG_ROOT}/.git"
current_vcpkg="$(
git -C "${VCPKG_ROOT}" rev-parse HEAD
)"
if [ "${current_vcpkg}" != "${VCPKG_COMMIT_ID}" ]; then
git -C "${VCPKG_ROOT}" fetch \
origin \
"${VCPKG_COMMIT_ID}"
git -C "${VCPKG_ROOT}" checkout \
--force \
"${VCPKG_COMMIT_ID}"
rm -rf \
"${VCPKG_ROOT}/installed/arm64-android" \
"${VCPKG_ROOT}/buildtrees" \
"${VCPKG_ROOT}/packages"
fi
"${VCPKG_ROOT}/bootstrap-vcpkg.sh" \
-disableMetrics
"${VCPKG_ROOT}/vcpkg" version
echo
echo "Builder environment prepared"
# -----------------------------------------------------------------------
# Generate Flutter/Rust bridge in THE SAME checkout, like the original
# working workflow. Use version-specific preparation commands from the
# selected tag's upstream bridge.yml.
# -----------------------------------------------------------------------
- name: Generate Flutter Rust bridge
run: |
set -euxo pipefail
export RUSTUP_TOOLCHAIN="${BRIDGE_RUST_VERSION}"
export PATH="${BRIDGE_FLUTTER_ROOT}/bin:${CARGO_HOME}/bin:${PATH}"
rustup toolchain install \
"${BRIDGE_RUST_VERSION}" \
--profile minimal \
--component rustfmt
cd rustdesk
python3 <<'PY'
import re
from pathlib import Path
text = Path(
".github/workflows/bridge.yml"
).read_text(encoding="utf-8")
def run_blocks(text):
lines = text.splitlines()
blocks = []
for index, line in enumerate(lines):
match = re.match(r"^(\s*)run:\s*\|\s*$", line)
if not match:
continue
base_indent = len(match.group(1))
body = []
for child in lines[index + 1:]:
if child.strip():
indent = len(child) - len(child.lstrip())
if indent <= base_indent:
break
cut = min(
len(child),
base_indent + 2,
)
body.append(child[cut:])
blocks.append("\n".join(body) + "\n")
return blocks
blocks = run_blocks(text)
prepare = next(
(
block
for block in blocks
if "cargo install cargo-expand" in block
and "flutter_rust_bridge_codegen" in block
),
None,
)
generate = next(
(
block
for block in blocks
if "flutter_rust_bridge_codegen --rust-input" in block
),
None,
)
if not prepare:
raise SystemExit(
"Unable to find upstream bridge preparation block"
)
if not generate:
raise SystemExit(
"Unable to find upstream bridge generation block"
)
def replace_expression(
body,
expression,
replacement,
):
pattern = (
r"\$\{\{\s*"
+ re.escape(expression)
+ r"\s*\}\}"
)
return re.sub(
pattern,
lambda _match: replacement,
body,
)
replacements = (
(
"env.CARGO_EXPAND_VERSION",
"${CARGO_EXPAND_VERSION}",
),
(
"env.FLUTTER_RUST_BRIDGE_VERSION",
"${FLUTTER_RUST_BRIDGE_VERSION}",
),
(
"matrix.job.flutter-version",
"${BRIDGE_FLUTTER_VERSION}",
),
)
for expression, replacement in replacements:
prepare = replace_expression(
prepare,
expression,
replacement,
)
generate = replace_expression(
generate,
expression,
replacement,
)
# Upstream bridge.yml may call the codegen binary through
# ~/.cargo/bin/... . Our builder intentionally uses a custom
# CARGO_HOME (for example /opt/rust/cargo), so a hard-coded HOME
# cargo path is not portable. The selected tool version has already
# been installed above; resolve it through PATH instead.
cargo_bin_patterns = (
r"~/.cargo/bin/flutter_rust_bridge_codegen",
r"\$HOME/.cargo/bin/flutter_rust_bridge_codegen",
r"\$\{HOME\}/.cargo/bin/flutter_rust_bridge_codegen",
r"/root/.cargo/bin/flutter_rust_bridge_codegen",
)
for pattern in cargo_bin_patterns:
generate = re.sub(
pattern,
"flutter_rust_bridge_codegen",
generate,
)
if re.search(r"\$\{\{", prepare + generate):
raise SystemExit(
"Unsupported upstream expression remains "
"in bridge commands"
)
Path("bridge-prepare.sh").write_text(
"#!/usr/bin/env bash\n"
"set -euxo pipefail\n"
+ prepare,
encoding="utf-8",
)
Path("bridge-generate.sh").write_text(
"#!/usr/bin/env bash\n"
"set -euxo pipefail\n"
+ generate,
encoding="utf-8",
)
PY
chmod +x \
bridge-prepare.sh \
bridge-generate.sh
bash -n bridge-prepare.sh
bash -n bridge-generate.sh
echo
echo "Bridge codegen binary:"
command -v flutter_rust_bridge_codegen
flutter_rust_bridge_codegen --version
echo
echo "Generated bridge command:"
grep -n "flutter_rust_bridge_codegen" \
bridge-generate.sh
./bridge-prepare.sh
./bridge-generate.sh
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
echo
echo "Generated bridge:"
ls -lh \
src/bridge_generated.rs \
src/bridge_generated.io.rs \
flutter/lib/generated_bridge.dart \
flutter/lib/generated_bridge.freezed.dart
# Restore only tracked changes made by bridge preparation.
# Generated bridge files are untracked and remain in place.
rm -f \
bridge-prepare.sh \
bridge-generate.sh
git restore \
--worktree \
--source=HEAD \
-- \
flutter/pubspec.yaml \
flutter/pubspec.lock \
2>/dev/null \
|| true
rm -rf flutter/.dart_tool
test -f src/bridge_generated.rs
test -f src/bridge_generated.io.rs
echo
echo "Flutter Rust bridge generated successfully"
# -----------------------------------------------------------------------
# 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")
text, id_count = re.subn(
r"pub const RENDEZVOUS_SERVERS\s*:\s*[^=]+?="
r"\s*&\[(?:.|\n)*?\];",
"pub const RENDEZVOUS_SERVERS: &[&str] = &["
+ json.dumps(id_server)
+ "];",
text,
count=1,
)
text, key_count = re.subn(
r"pub const RS_PUB_KEY\s*:\s*[^=]+?="
r"\s*.*?;",
"pub const RS_PUB_KEY: &str = "
+ json.dumps(server_key)
+ ";",
text,
count=1,
flags=re.S,
)
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
grep -n \
"RENDEZVOUS_SERVERS" \
libs/hbb_common/src/config.rs
echo "Relay configuration untouched: yes"
# -----------------------------------------------------------------------
# Apply Flutter compatibility patches supplied by the selected tag.
# Only patches that cleanly apply to the selected Android Flutter SDK
# are applied.
# -----------------------------------------------------------------------
- name: Patch Android Flutter SDK
run: |
set -euxo pipefail
if [ ! -d "rustdesk/.github/patches" ]; then
exit 0
fi
shopt -s nullglob
for patch in rustdesk/.github/patches/flutter_*.diff; do
if git \
-C "${ANDROID_FLUTTER_ROOT}" \
apply \
--check \
"${PWD}/${patch}" \
>/dev/null 2>&1; then
echo "Applying Flutter patch: ${patch}"
git \
-C "${ANDROID_FLUTTER_ROOT}" \
apply \
"${PWD}/${patch}"
fi
done
# -----------------------------------------------------------------------
# Android native dependencies.
# -----------------------------------------------------------------------
- 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 "${ANDROID_ABI}"; 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.
#
# Important: copy the .so into jniLibs in THIS SAME step, matching
# upstream RustDesk Android CI. The next Flutter step no longer depends
# on target/ surviving as a build-output directory.
# -----------------------------------------------------------------------
- name: Build RustDesk native library
run: |
set -euxo pipefail
export RUSTUP_TOOLCHAIN="${RUST_VERSION}"
cd rustdesk
rustc --version
cargo --version
cargo ndk --version
test -f src/bridge_generated.rs
test -f src/bridge_generated.io.rs
if ! rustup \
target list \
--installed \
--toolchain "${RUST_VERSION}" \
| grep -qx "${RUST_TARGET}"; then
echo "ERROR: ${RUST_TARGET} Rust target is missing"
exit 1
fi
./flutter/ndk_arm64.sh
NATIVE_LIB="target/${RUST_TARGET}/release/liblibrustdesk.so"
test -f "${NATIVE_LIB}"
mkdir -p \
"flutter/android/app/src/main/jniLibs/${ANDROID_ABI}"
cp \
"${NATIVE_LIB}" \
"flutter/android/app/src/main/jniLibs/${ANDROID_ABI}/librustdesk.so"
test -f \
"flutter/android/app/src/main/jniLibs/${ANDROID_ABI}/librustdesk.so"
echo
echo "Native library:"
ls -lh \
"${NATIVE_LIB}" \
"flutter/android/app/src/main/jniLibs/${ANDROID_ABI}/librustdesk.so"
# -----------------------------------------------------------------------
# Permanent Android release signing.
# -----------------------------------------------------------------------
- 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
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
keytool \
-list \
-keystore app/rustdesk-release.jks \
-storepass "${ANDROID_KEYSTORE_PASSWORD}" \
-alias "${ANDROID_KEY_ALIAS}" \
>/dev/null
echo "Android release signing configured successfully."
# -----------------------------------------------------------------------
# Build signed ARM64 APK.
# -----------------------------------------------------------------------
- name: Build signed APK
run: |
set -euxo pipefail
export RUSTUP_TOOLCHAIN="${RUST_VERSION}"
export PATH="${ANDROID_FLUTTER_ROOT}/bin:${CARGO_HOME}/bin:${PATH}"
flutter --version
cd rustdesk
JNI_DIR="flutter/android/app/src/main/jniLibs/${ANDROID_ABI}"
test -f "${JNI_DIR}/librustdesk.so"
cp \
"${ANDROID_NDK_HOME}/toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr/lib/aarch64-linux-android/libc++_shared.so" \
"${JNI_DIR}/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 "${JNI_DIR}/"
echo
echo "Signing files:"
ls -lh \
flutter/android/key.properties \
flutter/android/app/rustdesk-release.jks
pushd flutter
flutter clean
flutter pub get
flutter build apk \
--release \
--target-platform "${FLUTTER_TARGET}" \
--split-per-abi
popd
APK_SOURCE="flutter/build/app/outputs/flutter-apk/app-arm64-v8a-release.apk"
test -f "${APK_SOURCE}"
mkdir -p output
cp \
"${APK_SOURCE}" \
"output/rustdesk-${RUSTDESK_VERSION}-custom-arm64.apk"
echo
echo "Built APK:"
ls -lh \
"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="$(
find "${ANDROID_SDK_ROOT}/build-tools" \
-type f \
-name apksigner \
-print \
| sort -V \
| tail -n 1
)"
if [ -z "${APKSIGNER}" ]; then
echo "ERROR: apksigner was not found"
exit 1
fi
if [ ! -f "${APK}" ]; then
echo "ERROR: APK not found: ${APK}"
exit 1
fi
echo "Using apksigner: ${APKSIGNER}"
"${APKSIGNER}" verify \
--verbose \
--print-certs \
"${APK}"
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.
# Keep the same action family as the original known-working workflow.
# -----------------------------------------------------------------------
- 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