Files
2026-02-25 10:48:22 +00:00

477 lines
16 KiB
Bash
Executable File

#!/bin/bash
set -e
# Unset conflicting Android SDK environment variable if it exists
unset ANDROID_SDK_ROOT
# Check if running in exploit-build-only mode
EXPLOIT_BUILD_ONLY=false
if [ "$1" == "--exploit-build-only" ]; then
EXPLOIT_BUILD_ONLY=true
fi
ANDROID_COMPILE_SDK="android-34"
ANDROID_BUILD_TOOLS="35.0.0"
log() {
echo "[SETUP] $1"
}
error() {
echo "[ERROR] $1"
}
warn() {
echo "[WARN] $1"
}
install_if_missing() {
local package=$1
local display_name=${2:-$package}
if ! dpkg-query -W -f'${Status}' "$package" 2>/dev/null | grep -q 'ok installed'; then
log "Installing $display_name..."
sudo apt install -y "$package"
else
echo "$display_name is already installed"
fi
}
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
ANDROID_SHELLSERVER_DIR="$SCRIPT_DIR/android_shellserver"
# Skip KVM check in exploit-build-only mode
if [ "$EXPLOIT_BUILD_ONLY" = false ]; then
if [ "$(grep -c -w 'vmx\|svm' /proc/cpuinfo)" == 0 ]; then
error "Virtualization with KVM is not available."
exit 1;
fi
fi
# System & Runtime Dependencies
log "Updating package lists..."
sudo apt-get update
log "Installing system dependencies..."
install_if_missing "git" "Git"
install_if_missing "curl" "Curl"
install_if_missing "wget" "Wget"
install_if_missing "unzip" "Unzip"
install_if_missing "build-essential" "Build Essential"
# Only install runtime dependencies if not in exploit-build-only mode
if [ "$EXPLOIT_BUILD_ONLY" = false ]; then
install_if_missing "socat" "Socat"
# Python dependencies
install_if_missing "python3-gmpy2" "python3-gmpy2"
install_if_missing "python-gmpy2-common" "python-gmpy2-common"
install_if_missing "python3-ecdsa" "python3-ecdsa"
install_if_missing "libguestfs-tools" "libguestfs-tools"
fi
# Cuttlefish Installation - skip in exploit-build-only mode
if [ "$EXPLOIT_BUILD_ONLY" = false ]; then
log "Checking Cuttlefish installation..."
# Ensure repository is configured
if [ ! -f "/etc/apt/sources.list.d/artifact-registry.list" ]; then
log "Adding Artifact Registry GPG key..."
sudo curl -fsSL https://us-apt.pkg.dev/doc/repo-signing-key.gpg \
-o /etc/apt/trusted.gpg.d/artifact-registry.asc
sudo chmod a+r /etc/apt/trusted.gpg.d/artifact-registry.asc
log "Adding Cuttlefish repository..."
echo "deb https://us-apt.pkg.dev/projects/android-cuttlefish-artifacts android-cuttlefish main" \
| sudo tee /etc/apt/sources.list.d/artifact-registry.list > /dev/null
log "Updating package lists..."
sudo apt-get update
fi
# Check if Cuttlefish is installed
if dpkg-query -W -f'${Status}' 'cuttlefish-base' 2>/dev/null | grep -q 'ok installed'; then
log "Cuttlefish is installed. Checking for updates..."
# Refresh package information
sudo apt-get update -qq
# Get installed and available versions
INSTALLED_VERSION=$(dpkg-query -W -f='${Version}' cuttlefish-base 2>/dev/null)
AVAILABLE_VERSION=$(apt-cache policy cuttlefish-base | grep Candidate | awk '{print $2}')
echo " Installed version: $INSTALLED_VERSION"
echo " Available version: $AVAILABLE_VERSION"
# Compare versions using dpkg --compare-versions
if dpkg --compare-versions "$INSTALLED_VERSION" lt "$AVAILABLE_VERSION"; then
log "Newer version available. Upgrading Cuttlefish..."
sudo apt-get install -y --only-upgrade cuttlefish-base cuttlefish-user
log "Cuttlefish upgrade complete"
else
log "Cuttlefish is already up to date."
fi
else
log "Cuttlefish not found. Installing from Artifact Registry..."
# Make sure we have the latest package lists
sudo apt-get update -qq
log "Installing Cuttlefish packages..."
sudo apt-get install -y cuttlefish-base cuttlefish-user
log "Cuttlefish installation complete"
fi
# Cuttlefish Configuration & Verification
log "Verifying Cuttlefish environment configuration..."
echo "Updating user groups..."
sudo usermod -aG kvm,cvdnetwork,render $USER
echo "Verifying permissions..."
if [ -e /dev/kvm ]; then
ls -la /dev/kvm
else
warn "/dev/kvm not found. KVM support may not be available."
fi
if [ -e /dev/net/tun ]; then
ls -la /dev/net/tun
else
warn "/dev/net/tun not found."
fi
# Group Membership Check
NEEDS_RELOAD=false
if ! groups | grep -q cvdnetwork; then NEEDS_RELOAD=true; fi
if ! groups | grep -q kvm; then NEEDS_RELOAD=true; fi
if ! groups | grep -q render; then NEEDS_RELOAD=true; fi
if [ "$NEEDS_RELOAD" = true ]; then
warn "Group membership updated."
warn "You need to log out and log back in for these changes to persist."
warn "A temporary shell with new permissions will be spawned at the end of this script."
else
log "Group membership verified."
fi
fi
# Android SDK & Build Tools
install_if_missing "android-sdk" "Android SDK"
# Detect ANDROID_HOME location after apt installation (universal detection)
if [ -d "/usr/lib/android-sdk" ]; then
export ANDROID_HOME=/usr/lib/android-sdk
elif [ -d "/usr/local/lib/android/sdk" ]; then
export ANDROID_HOME=/usr/local/lib/android/sdk
elif [ -n "$ANDROID_HOME" ] && [ -d "$ANDROID_HOME" ]; then
# Use existing ANDROID_HOME if set and valid
log "Using existing ANDROID_HOME: $ANDROID_HOME"
else
# Try to find it using dpkg
ANDROID_HOME=$(dpkg -L android-sdk 2>/dev/null | grep -m1 "bin/sdkmanager$" | xargs dirname | xargs dirname || echo "")
if [ -z "$ANDROID_HOME" ] || [ ! -d "$ANDROID_HOME" ]; then
error "Could not detect ANDROID_HOME after installing android-sdk package"
error "Please set ANDROID_HOME manually"
exit 1
fi
fi
log "ANDROID_HOME set to: $ANDROID_HOME"
if [ ! -d "$ANDROID_HOME" ]; then
error "ANDROID_HOME directory ($ANDROID_HOME) does not exist."
error "The 'android-sdk' package might not have installed correctly."
exit 1
fi
if [ ! -d "$ANDROID_HOME/cmdline-tools/latest" ]; then
# Get latest cmdline-tools version from Android SDK repository
log "Detecting latest command-line tools version..."
LATEST_CMDLINE_VERSION=$(curl -s https://dl.google.com/android/repository/repository2-3.xml | \
grep -oP 'commandlinetools-linux-\K[0-9]+' | \
sort -n | \
tail -1)
if [ -n "$LATEST_CMDLINE_VERSION" ]; then
CMDLINE_TOOLS_URL="https://dl.google.com/android/repository/commandlinetools-linux-${LATEST_CMDLINE_VERSION}_latest.zip"
log "Using latest command-line tools version: $LATEST_CMDLINE_VERSION"
else
warn "Could not detect latest version, using default"
CMDLINE_TOOLS_URL="https://dl.google.com/android/repository/commandlinetools-linux-13114758_latest.zip"
fi
log "Downloading command line tools..."
wget -q --show-progress "$CMDLINE_TOOLS_URL" -O cmdline_tools.zip
unzip -q cmdline_tools.zip -d cmdline-tools
# Attempting to install cmdline-tools.
# WARNING: If ANDROID_HOME is not writable by the current user, this step will fail.
if [ ! -w "$ANDROID_HOME" ]; then
warn "$ANDROID_HOME is not writable. Trying with sudo..."
sudo mkdir -p "$ANDROID_HOME/cmdline-tools/latest"
sudo mv cmdline-tools/cmdline-tools/* "$ANDROID_HOME/cmdline-tools/latest/"
else
mkdir -p "$ANDROID_HOME/cmdline-tools/latest"
mv cmdline-tools/cmdline-tools/* "$ANDROID_HOME/cmdline-tools/latest/"
fi
rm -rf cmdline-tools cmdline_tools.zip
export PATH=$ANDROID_HOME/cmdline-tools/latest/bin:$PATH
else
echo "Cmdline-tools is already installed"
fi
SDKMANAGER="$ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager"
if [ -f "$SDKMANAGER" ]; then
log "Accepting licenses via SDKManager..."
# If not writable, we must use sudo to accept licenses/install
if [ ! -w "$ANDROID_HOME" ]; then
log "Using sudo for sdkmanager (system directory detected)..."
yes | sudo $SDKMANAGER --licenses >/dev/null 2>&1
log "Installing Android platform and build tools..."
sudo $SDKMANAGER --install "platforms;${ANDROID_COMPILE_SDK}"
sudo $SDKMANAGER --install "build-tools;${ANDROID_BUILD_TOOLS}"
else
yes | $SDKMANAGER --licenses >/dev/null 2>&1
$SDKMANAGER --install "platforms;${ANDROID_COMPILE_SDK}"
$SDKMANAGER --install "build-tools;${ANDROID_BUILD_TOOLS}"
fi
log "Platform and build tools installation complete"
else
error "sdkmanager not found at $SDKMANAGER"
exit 1
fi
# Android NDK Installation
log "Checking Android NDK installation..."
# Get the latest available NDK version from sdkmanager
NDK_VERSION=$($SDKMANAGER --list 2>/dev/null | grep "ndk;" | grep -v "ndk-bundle" | tail -1 | awk '{print $1}' | cut -d';' -f2)
if [ -z "$NDK_VERSION" ]; then
error "Could not detect latest NDK version from sdkmanager"
exit 1
fi
# Check if NDK is already installed
if [ -d "$ANDROID_HOME/ndk/$NDK_VERSION" ]; then
log "NDK $NDK_VERSION is already installed"
else
log "Installing Android NDK version: $NDK_VERSION"
if [ ! -w "$ANDROID_HOME" ]; then
sudo $SDKMANAGER "ndk;${NDK_VERSION}"
else
$SDKMANAGER "ndk;${NDK_VERSION}"
fi
log "NDK installation complete"
fi
# Set NDK environment variables
export ANDROID_NDK_HOME="$ANDROID_HOME/ndk/$NDK_VERSION"
NDK_TOOLCHAIN_PATH="$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64/bin"
# Add NDK to PATH
export PATH="$NDK_TOOLCHAIN_PATH:$PATH"
log "ANDROID_NDK_HOME set to: $ANDROID_NDK_HOME"
log "NDK toolchain added to PATH"
# Check and fix AppArmor namespace restrictions (only when Cuttlefish will be used)
if [ "$EXPLOIT_BUILD_ONLY" = false ]; then
log "Checking AppArmor namespace restrictions..."
CURRENT_USERNS_RESTRICTION=$(sudo sysctl -n kernel.apparmor_restrict_unprivileged_userns 2>/dev/null || echo "not_found")
if [ "$CURRENT_USERNS_RESTRICTION" = "not_found" ]; then
log "AppArmor userns restriction not present on this system (likely not Ubuntu 24.04+)"
elif [ "$CURRENT_USERNS_RESTRICTION" = "1" ]; then
warn "AppArmor is restricting unprivileged user namespaces"
warn "This will cause Cuttlefish to fail with 'unshare(CLONE_NEWNS) failed: Operation not permitted'"
echo ""
if [ -n "$CI" ] || [ -n "$GITHUB_ACTIONS" ]; then
log "CI environment detected - applying fix automatically..."
sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0
log "AppArmor restriction disabled (temporary)"
else
echo "To fix this, you have two options:"
echo ""
echo "Option 1 (Temporary - until reboot):"
echo " sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0"
echo ""
echo "Option 2 (Permanent):"
echo " echo 'kernel.apparmor_restrict_unprivileged_userns = 0' | sudo tee /etc/sysctl.d/99-cuttlefish-userns.conf"
echo " sudo sysctl -p /etc/sysctl.d/99-cuttlefish-userns.conf"
echo ""
read -p "Would you like to apply the temporary fix now? (y/N): " -r REPLY
echo ""
if [[ $REPLY =~ ^[Yy]$ ]]; then
log "Applying temporary fix..."
sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0
log "AppArmor restriction disabled (temporary - until reboot)"
echo ""
warn "To make this permanent, run:"
warn " echo 'kernel.apparmor_restrict_unprivileged_userns = 0' | sudo tee /etc/sysctl.d/99-cuttlefish-userns.conf"
warn " sudo sysctl -p /etc/sysctl.d/99-cuttlefish-userns.conf"
else
warn "Skipping AppArmor fix - Cuttlefish may fail to launch"
warn "You can apply the fix manually later if needed"
fi
fi
else
log "AppArmor userns restriction is already disabled (value: $CURRENT_USERNS_RESTRICTION)"
fi
echo ""
fi
# Build android_shellserver APK (only when not in exploit-build-only mode)
if [ "$EXPLOIT_BUILD_ONLY" = false ]; then
if [ ! -d "$ANDROID_SHELLSERVER_DIR" ]; then
error "Directory not found: $ANDROID_SHELLSERVER_DIR"
exit 1
fi
cd "$ANDROID_SHELLSERVER_DIR"
if [ ! -f "gradlew" ]; then
error "gradlew not found. Not a valid Gradle project?"
exit 1
fi
NUM_CORES=$(nproc)
log "Detected $NUM_CORES CPU cores"
log "Configuring Gradle..."
if [ -f "gradle.properties" ]; then
warn "gradle.properties exists. Backing up to gradle.properties.bak"
cp gradle.properties gradle.properties.bak
fi
cat > gradle.properties << EOF
# AndroidX Support
android.useAndroidX=true
android.enableJetifier=true
# Performance optimizations
org.gradle.parallel=true
org.gradle.caching=true
org.gradle.configureondemand=true
org.gradle.workers.max=$NUM_CORES
org.gradle.jvmargs=-Xmx4g -XX:MaxMetaspaceSize=512m -XX:+HeapDumpOnOutOfMemoryError --enable-native-access=ALL-UNNAMED
org.gradle.daemon=true
org.gradle.build-cache=true
EOF
log "Checking Gradle version..."
./gradlew --version --quiet 2>&1 | grep "Gradle "
./gradlew --stop > /dev/null 2>&1 || true
log "Cleaning build directories..."
rm -rf app/build build .gradle
export GRADLE_USER_HOME="$ANDROID_SHELLSERVER_DIR/.gradle"
if [ -n "$CI" ] || [ -n "$GITHUB_ACTIONS" ]; then
log "Running in CI environment - optimized for CI/CD"
GRADLE_OPTS="--parallel --build-cache --no-daemon"
else
log "Running locally - using daemon for faster subsequent builds"
GRADLE_OPTS="--parallel --build-cache"
fi
log "Generating keystore..."
./gradlew generateKeystore $GRADLE_OPTS --quiet
log "Building release APK (parallel mode with $NUM_CORES workers)..."
./gradlew --refresh-dependencies assembleRelease $GRADLE_OPTS --quiet
APK_PATH="app/build/outputs/apk/release/app-release.apk"
if [ -f "$APK_PATH" ]; then
log "Build successful!"
echo "APK: $(realpath "$APK_PATH")"
ls -lh "$APK_PATH"
else
error "Build failed: APK not found"
exit 1
fi
cd $SCRIPT_DIR
fi
# Export environment variables
if [ -n "$CI" ] || [ -n "$GITHUB_ACTIONS" ]; then
# CI/CD environment (GitHub Actions, etc.)
if [ -n "$GITHUB_ENV" ] && [ -n "$GITHUB_PATH" ]; then
log "Exporting environment variables for GitHub Actions..."
echo "ANDROID_HOME=$ANDROID_HOME" >> $GITHUB_ENV
echo "ANDROID_NDK_HOME=$ANDROID_NDK_HOME" >> $GITHUB_ENV
echo "$NDK_TOOLCHAIN_PATH" >> $GITHUB_PATH
log "Environment variables exported to GitHub Actions:"
log " ANDROID_HOME=$ANDROID_HOME"
log " ANDROID_NDK_HOME=$ANDROID_NDK_HOME"
log " NDK in PATH: $NDK_TOOLCHAIN_PATH"
fi
else
# Local development environment
ENV_FILE="$SCRIPT_DIR/android_env.sh"
log "Creating environment file: $ENV_FILE"
cat > "$ENV_FILE" << EOF
# Android development environment
# Source this file to set up your environment:
# source $ENV_FILE
export ANDROID_HOME="$ANDROID_HOME"
export ANDROID_NDK_HOME="$ANDROID_NDK_HOME"
export PATH="$NDK_TOOLCHAIN_PATH:\$PATH"
echo "Android development environment loaded:"
echo " ANDROID_HOME=\$ANDROID_HOME"
echo " ANDROID_NDK_HOME=\$ANDROID_NDK_HOME"
echo " NDK toolchain added to PATH"
EOF
chmod +x "$ENV_FILE"
echo ""
warn "==============================================="
warn "Environment variables have been saved to:"
warn " $ENV_FILE"
warn ""
warn "To use them in your current shell, run:"
warn " source $ENV_FILE"
warn "==============================================="
echo ""
fi
log "... The End ..."
if [ "$EXPLOIT_BUILD_ONLY" = false ] && [ "$NEEDS_RELOAD" = true ]; then
echo ""
if [ -n "$CI" ] || [ -n "$GITHUB_ACTIONS" ]; then
warn "Running in CI environment - group changes detected"
warn "Groups will be activated using 'sudo --user \"$USER\""
warn "--preserve-env --preserve-env=PATH -- env --' command"
warn "when running Cuttlefish"
else
warn "Spawning new shell with updated group permissions..."
exec sudo su - $USER
fi
fi