Waydroid template

Script for easy deployment and maintenence.


debian-waydroid-advanced-aria2.sh

#!/bin/bash

################################################################################
# File Name    : debian-waydroid-advanced-aria2.sh
# Description  : Waydroid AppVM and DispVM setup for Qubes OS with Debian
#                minimal. Creates a secure Android environment with choice of
#                VANILLA or GApps Android images. Features include network via
#                NFTables, fullscreen Sway compositor, bidirectional clipboard
#                sync, APK installation scripts, and desktop integration. This
#                script uses aria2 for instable or slow network stacks.
# Usage        : • Transfer to dom0:
#                qvm-run -p appvm 'cat ~/debian-waydroid-advanced-aria2.sh' > ~/debian-waydroid-advanced-aria2.sh
#                • Make executable:
#                chmod +x ~/debian-waydroid-advanced-aria2.sh
#                • Run:
#                bash ~/debian-waydroid-advanced-aria2.sh
# Author       : Based on apparatus guide from Qubes OS Forum and the bois
# License      : Free of charge, no warranty
# Last edited  : 2025-11-02
################################################################################

# Safety first
set -euo pipefail

# Configuration (set default values here)
APPVM_NAME="waydroid"
NAMED_DISP_VM="disp-waydroid"
BASE_TEMPLATE="debian-12-minimal"
WAYDROID_CUSTOM_TEMPLATE="debian-waydroid-template"
WAYDROID_DISP_TEMPLATE="debian-waydroid-template-dvm"
WAYDROID_IMAGE_TYPE="waydroid init -s GApps"  # Android image with GApps support
# WAYDROID_IMAGE_TYPE="waydroid init"         # VANILLA Android image
NET_VM="sys-whonix"                           # Whonix may cause issues

# Step 1: Verify and create base template
create_base_template() {
  echo -e "\nStep 1: Setting up base template $BASE_TEMPLATE..."

  if ! qvm-check "$BASE_TEMPLATE" 2>/dev/null; then
    echo "Installing $BASE_TEMPLATE..."
    sudo qubes-dom0-update "qubes-template-$BASE_TEMPLATE"
  fi

  # Ensure template is shut down before updating
  qvm-shutdown --wait "$BASE_TEMPLATE" 2>/dev/null || true

  if qvm-check "$BASE_TEMPLATE"; then
    echo "Updating $BASE_TEMPLATE..."
    sudo qubesctl --show-output --skip-dom0 --targets="$BASE_TEMPLATE" state.sls update.qubes-vm
  fi

  echo -e "\nBase template setup complete"
}

# Step 2: Create custom base template
create_waydroid_custom_template() {
  echo -e "\nStep 2: Creating $WAYDROID_CUSTOM_TEMPLATE..."

  if qvm-check "$WAYDROID_CUSTOM_TEMPLATE"; then
    echo "$WAYDROID_CUSTOM_TEMPLATE already exists, skipping creation"
    return 0
  fi

  echo "Cloning $BASE_TEMPLATE to $WAYDROID_CUSTOM_TEMPLATE..."
  qvm-clone "$BASE_TEMPLATE" "$WAYDROID_CUSTOM_TEMPLATE"
  qvm-prefs "$WAYDROID_CUSTOM_TEMPLATE" label black

  echo -e "\nWaydroid template created"
}

# Step 3: Install packages in Waydroid template (change as needed)
install_template_packages() {
  echo -e "\nStep 3: Installing packages in $WAYDROID_CUSTOM_TEMPLATE..."

  qvm-start --skip-if-running "$WAYDROID_CUSTOM_TEMPLATE"

  # Basic system configuration
  echo "Configuring system environment..."
  qvm-run -p -u root "$WAYDROID_CUSTOM_TEMPLATE" "echo 'TERM=xterm' >> /etc/environment"
  qvm-run -p -u root "$WAYDROID_CUSTOM_TEMPLATE" "sed -i 's/^# *\(en_US.UTF-8\)/\1/' /etc/locale.gen"
  qvm-run -p -u root "$WAYDROID_CUSTOM_TEMPLATE" "locale-gen en_US.UTF-8"

  # Install core packages
  echo "Installing core packages..."
  qvm-run -p -u root "$WAYDROID_CUSTOM_TEMPLATE" "
  apt-get install -y --no-install-recommends \
    curl \
    ca-certificates \
    software-properties-common \
    qubes-core-agent-networking \
    qubes-core-agent-passwordless-root \
    thunar \
    qubes-core-agent-thunar \
    wireplumber \
    pulseaudio-qubes \
    less \
    psmisc \
    xfce4-terminal
"
}

# Function: Install Waydroid and aria2 for faster downloads
install_waydroid_packages() {
  echo -e "\nInstalling Waydroid..."

  qvm-run -p -u root "$WAYDROID_CUSTOM_TEMPLATE" "
  export DEBIAN_FRONTEND=noninteractive
  export http_proxy=http://127.0.0.1:8082
  export https_proxy=http://127.0.0.1:8082

  # Install extrepo and enable Waydroid repository
  apt-get install -y extrepo
  extrepo enable waydroid
  apt-get update

  # Install Waydroid core packages
  apt-get install -y \
    waydroid \
    sway \
    xclip \
    wl-clipboard \
    x11-utils \
    build-essential \
    libx11-dev \
    libxtst-dev \
    python3 \
    python3-venv \
    python3-pip \
    unzip \
    aria2
"
}

# Step 4: Configure Waydroid container service
configure_waydroid_service() {
  echo -e "\nStep 4: Configuring Waydroid container service..."

  local temp_script=$(mktemp)
  cat > "$temp_script" < /etc/systemd/system/waydroid-container.service.d/override.conf < "$temp_script" </dev/null &
eval "${wl_x11}" &>/dev/null
pstree -A -p $$ | grep -Eow "[0-9]+" | xargs kill &>/dev/null
EOF

  qvm-copy-to-vm "$WAYDROID_CUSTOM_TEMPLATE" "$temp_script"
  qvm-run -p -u root "$WAYDROID_CUSTOM_TEMPLATE" "
  mv '/home/user/QubesIncoming/dom0/$(basename $temp_script)' /opt/bin/x11-wl-clip.sh
  chmod +x /opt/bin/x11-wl-clip.sh
  mkdir -p /etc/sway/config.d
  echo 'exec /opt/bin/x11-wl-clip.sh' > /etc/sway/config.d/99-x11-wl-clip.conf
  echo 'export PATH=\"/opt/bin:$PATH\"' > /etc/profile.d/opt-bin.sh
"
  rm -f "$temp_script"

  echo "Clipboard support installed"
}

# Step 6: Install pyclip for Waydroid
install_pyclip() {
  echo -e "\nStep 6: Installing pyclip..."

  qvm-run -p -u root "$WAYDROID_CUSTOM_TEMPLATE" "
  export DEBIAN_FRONTEND=noninteractive
  export http_proxy=http://127.0.0.1:8082
  export https_proxy=http://127.0.0.1:8082

  python3 -m venv /opt/venv/pyclip
  source /opt/venv/pyclip/bin/activate
  pip install --no-cache-dir pyclip
  deactivate

  echo 'export PATH=\"\$PATH:/opt/venv/pyclip/bin\"' > /etc/profile.d/python-venv.sh
  echo 'export PYTHONPATH=\"\$PYTHONPATH:/opt/venv/pyclip/lib/python3.11/site-packages\"' >> /etc/profile.d/python-venv.sh
"

  echo "Pyclip installed"
}

# Step 7: Configure firewall for Waydroid
configure_firewall() {
  echo -e "\nStep 7: Configuring firewall..."

  # Create firewall service file
  local temp_script=$(mktemp)
  cat > "$temp_script" < /etc/systemd/system/waydroid-firewall.service </dev/null; then nft add rule ip qubes custom-input jump waydroid-input; fi"
ExecStart=/usr/bin/bash -c "if (nft create chain ip qubes waydroid-forward) 2>/dev/null; then nft add rule ip qubes custom-forward jump waydroid-forward; fi"
ExecStart=/usr/sbin/nft add rule ip qubes waydroid-input iifname "waydroid0" meta l4proto {tcp, udp} th dport { 53, 67 } accept
ExecStart=/usr/sbin/nft add rule ip qubes waydroid-forward iifname "waydroid0" oifgroup 1 accept
ExecStart=/usr/sbin/nft add rule ip qubes waydroid-forward oifname "waydroid0" iifgroup 1 accept
ExecStop=/usr/sbin/nft flush chain ip qubes waydroid-input
ExecStop=/usr/sbin/nft flush chain ip qubes waydroid-forward
RemainAfterExit=yes

[Install]
WantedBy=waydroid-container.service
INNEREOF

systemctl daemon-reload
systemctl enable waydroid-firewall.service
EOF

  qvm-copy-to-vm "$WAYDROID_CUSTOM_TEMPLATE" "$temp_script"
  qvm-run -p -u root "$WAYDROID_CUSTOM_TEMPLATE" "bash '/home/user/QubesIncoming/dom0/$(basename $temp_script)'"
  qvm-run -p -u root "$WAYDROID_CUSTOM_TEMPLATE" "rm -f '/home/user/QubesIncoming/dom0/$(basename $temp_script)'"
  rm -f "$temp_script"

  echo "Firewall configured"
}

# Step 8: Configure Sway for fullscreen Waydroid
configure_sway() {
  echo -e "\nStep 8: Configuring Sway..."

  qvm-run -p -u root "$WAYDROID_CUSTOM_TEMPLATE" "
  echo 'default_border none' > /etc/sway/config.d/94-disable-window-titlebar.conf

  # Remove status bar from default config if it exists
  if [ -f /etc/sway/config ]; then
    sed -i '/^bar {/,/^}/d' /etc/sway/config
  fi
"

  echo "Sway configured"
}

# Step 9: Create Waydroid scripts
create_startup_scripts() {
  echo -e "\nStep 9: Creating Waydroid scripts..."

  # Create main Waydroid startup script
  local temp_script1=$(mktemp)
  cat > "$temp_script1" </dev/null &
WAYLAND_DISPLAY="wayland-1" XDG_SESSION_TYPE="wayland" DISPLAY=":1" waydroid first-launch &>/dev/null &

for i in $(seq 1 3); do
  if xwininfo -name "wlroots - X11-1" &>/dev/null; then
    break
  fi
  sleep 1
done

while xwininfo -name "wlroots - X11-1" &>/dev/null; do
  sleep 2
done

WAYLAND_DISPLAY="wayland-1" XDG_SESSION_TYPE="wayland" DISPLAY=":1" waydroid session stop &>/dev/null
pstree -A -p $$ | grep -Eow "[0-9]+" | xargs kill &>/dev/null
EOF

  # Create APK installation script
  local temp_script2=$(mktemp)
  cat > "$temp_script2" < "$temp_script3" </dev/null || true
  rmdir '/home/user/QubesIncoming' 2>/dev/null || true
"

  rm -f "$temp_script1" "$temp_script2" "$temp_script3"
  echo "Startup scripts created"
}

# Step 10: Create desktop entry
create_desktop_entry() {
  echo -e "\nStep 10: Creating desktop entry..."

  local temp_script=$(mktemp)
  cat > "$temp_script" < /usr/share/applications/Waydroid-Sway.desktop < "$temp_script" </dev/null; then
  rm -f system.zip
  echo "[+] System image extracted successfully"
else
  # If unzip fails, try to see if it's already an image
  if file system.zip | grep -q "Android sparse image"; then
    mv system.zip system.img
    echo "[+] File is already Android sparse image"
  else
    echo "ERROR: Failed to extract system image from system.zip"
    exit 1
  fi
fi

# Download vendor image if available
if [[ -n "\$vendor_file" ]]; then
  download "\$vendor_file" "vendor.zip" "vendor"

  echo "[+] Extracting vendor image..."
  if unzip -o vendor.zip vendor.img 2>/dev/null; then
    rm -f vendor.zip
    echo "[+] Vendor image extracted successfully"
  else
    # If unzip fails, try to see if it's already an image
    if file vendor.zip | grep -q "Android sparse image"; then
      mv vendor.zip vendor.img
      echo "[+] File is already Android sparse image"
    else
      echo "ERROR: Failed to extract vendor image from vendor.zip"
      exit 1
    fi
  fi
fi

# Verify downloaded images
echo "[+] Verifying downloaded images..."
if [[ -f "system.img" ]]; then
  system_size=\$(du -h system.img | cut -f1)
  echo "[+] System image ready: \$system_size"
else
  echo "ERROR: System image download failed"
  exit 1
fi

if [[ -f "vendor.img" ]]; then
  vendor_size=\$(du -h vendor.img | cut -f1)
  echo "[+] Vendor image ready: \$vendor_size"
fi

# Stop waydroid if running
systemctl stop waydroid-container 2>/dev/null || true

# Copy images to Waydroid directory
echo "[+] Installing images to Waydroid data directory..."
mkdir -p /var/lib/waydroid/images
cp system.img /var/lib/waydroid/images/
if [[ -f vendor.img ]]; then
  cp vendor.img /var/lib/waydroid/images/
fi

# Set proper permissions
chmod 644 /var/lib/waydroid/images/*.img

# Initialize Waydroid with existing images
echo "[+] Initializing Waydroid with local images..."
if [[ -f vendor.img ]]; then
  waydroid init -f
else
  # If no vendor image, still try to initialize
  waydroid init -f
fi

# Verify initialization
if waydroid status 2>/dev/null | grep -q "CONTAINER"; then
  echo "[+] Waydroid initialized successfully with latest \$IMAGE_TYPE images"
else
  echo "[+] Waydroid initialization completed"
  waydroid status || true
fi
EOF

  # Copy and execute the script
  echo "Downloading latest Waydroid images with aria2..."
  qvm-copy-to-vm "$WAYDROID_CUSTOM_TEMPLATE" "$temp_script"

  qvm-run -p -u root "$WAYDROID_CUSTOM_TEMPLATE" "
    export http_proxy=http://127.0.0.1:8082
    export https_proxy=http://127.0.0.1:8082
    bash /home/user/QubesIncoming/dom0/$(basename "$temp_script")
"

  local result=$?

  # Clean up
  qvm-run -p "$WAYDROID_CUSTOM_TEMPLATE" "rm -f /home/user/QubesIncoming/dom0/$(basename "$temp_script")" 2>/dev/null || true
  rm -f "$temp_script"

  if [ $result -eq 0 ]; then
    echo "Waydroid initialization completed successfully with latest images"
  else
    echo "ERROR: Waydroid initialization failed"
    return 1
  fi

  qvm-shutdown --wait "$WAYDROID_CUSTOM_TEMPLATE"
}

# Step 12: Create AppVM
create_appvm() {
  echo -e "\nStep 12: Creating AppVM $APPVM_NAME..."

  if qvm-check "$APPVM_NAME"; then
    echo "$APPVM_NAME already exists, skipping creation"
    return 0
  fi

  echo "Creating AppVM from template $WAYDROID_CUSTOM_TEMPLATE..."
  qvm-create --template "$WAYDROID_CUSTOM_TEMPLATE" --label green \
    --property netvm="$NET_VM" \
    --property provides_network=False \
    --property vcpus=2 \
    --property memory=1024 \
    --property maxmem=2048 \
    --property autostart=False \
    --property include_in_backups=False \
    "$APPVM_NAME"

  echo "AppVM $APPVM_NAME created successfully"
}

# Step 13: Create DVM template based on custom template
create_dispvm_template() {
echo -e "\nStep 13: Creating DVM template..."
qvm-create --template "$WAYDROID_CUSTOM_TEMPLATE" --label red "$WAYDROID_DISP_TEMPLATE"
qvm-prefs "$WAYDROID_DISP_TEMPLATE" template_for_dispvms True
}

# Step 14: Create named disposable VM instance
create_named_disposable() {
echo -e "\nStep 14: Creating named disposable VM instance..."
if ! qvm-check "$NAMED_DISP_VM" 2>/dev/null; then
    qvm-create --class DispVM --template "$WAYDROID_DISP_TEMPLATE" --label red \
    --property netvm="$NET_VM" \
    --property provides_network=False \
    --property vcpus=2 \
    --property memory=1024 \
    --property maxmem=2048 \
    --property autostart=False \
    --property include_in_backups=False \
    "$NAMED_DISP_VM"
    qvm-features "$NAMED_DISP_VM" appmenus-dispvm 1
else
    echo "Named disposable already exists, skipping creation."
fi
}

# Finalize
finalize() {
  echo -e "\nFinalizing..."

  qvm-start --skip-if-running "$WAYDROID_CUSTOM_TEMPLATE"
  qvm-sync-appmenus "$WAYDROID_CUSTOM_TEMPLATE"
  qvm-shutdown --wait "$WAYDROID_CUSTOM_TEMPLATE"

  qvm-features "$WAYDROID_CUSTOM_TEMPLATE" menu-items "Waydroid-Sway.desktop thunar.desktop debian-xterm.desktop xfce4-terminal.desktop"
  qvm-features "$APPVM_NAME" menu-items "Waydroid-Sway.desktop thunar.desktop debian-xterm.desktop xfce4-terminal.desktop"
  qvm-features "$WAYDROID_DISP_TEMPLATE" menu-items "Waydroid-Sway.desktop thunar.desktop debian-xterm.desktop xfce4-terminal.desktop"
  qvm-features "$NAMED_DISP_VM" menu-items "Waydroid-Sway.desktop thunar.desktop debian-xterm.desktop xfce4-terminal.desktop"
}

# Main execution function
main() {
  echo "Starting Waydroid setup..."

  create_base_template
  create_waydroid_custom_template
  install_template_packages
  install_waydroid_packages
  configure_waydroid_service
  install_clipboard_support
  install_pyclip
  configure_firewall
  configure_sway
  create_startup_scripts
  create_desktop_entry
  initialize_waydroid
  create_appvm
  create_dispvm_template
  create_named_disposable
  finalize

  echo -e "\nSetup completed!"
}

# Run main function
main "$@"

debian-waydroid-advanced-aria2.sh.log (22.2 KB)


Google Play Certification in Waydroid GApp custom ROM

Note, to acess Play Store you will need a Gmail account, a phone number confirmation, CloudFlare confirmation, another email for support confirmation. The desired application architecture should be x86_64 compatible.

  1. Open a Terminal
  2. Run the following command and copy the returned number ID:
    sudo waydroid shell -- sh -c "sqlite3 /data/data/*/*/gservices.db 'select * from main where name = \"android_id\";'"
  3. Use the numbers printed to register the device on your Google Account at:
    https://www.google.com/android/uncertified
  4. Give the Google services 10-20 minutes for the device to get registered, then clear Google Play Service's cache and restart Waydroid with:
    waydroid session stop
    sudo systemctl restart waydroid-container and session
    (Note: A full container restart is often more reliable than just stopping the session).
  5. Deactivate Google Play Protection in three dots.
  6. Try logging into the Google Play Store
  7. For more details, see the official guide: Waydroid FAQ: Google Play Certification

References

Qubes OS Forum Discussions

WayDroid Resources

Community Scripts and Tools

Android Distributions and ROMs

  • Android-x86 - The main project for porting Android to x86 architectures
  • BlissOS - An Android-based OS with a focus on features and customization
  • LineageOS - A popular, privacy-focused aftermarket Android ROM
  • Genymotion - A professional Android emulator for desktop

GApps and System Modifications

  • Open GApps - Source for open-source Google
  • microG Project - A free re-implementation of Google's proprietary Android user space apps and libraries
  • Magisk Delta - A fork of Magisk with enhanced features for root management

Other Useful Links

1 Like