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.
- Open a Terminal
- 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\";'"
- Use the numbers printed to register the device on your Google Account at:
https://www.google.com/android/uncertified - 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
(Note: A full container restart is often more reliable than just stopping the session).
sudo systemctl restart waydroid-container and session - Deactivate Google Play Protection in three dots.
- Try logging into the Google Play Store
- For more details, see the official guide: Waydroid FAQ: Google Play Certification
References
Qubes OS Forum Discussions
- Waydroid template - Community Guides- Qubes OS Forum
- Use Android apps in QubesOS- Qubes OS Forum
- Android VM with Qubes OS? - User support- Qubes OS Forum
- Is it possible to emulate android on qubes os in 2024?- Qubes OS Forum
- Android template/qubes? - User support- Qubes OS Forum
- Android VM options, and BlissOS installation in a raw VM (I.E. no qubes integration)(collecting all related information)- Qubes OS Forum
- Is a LineageOS VM doable on 4.2?- Qubes OS Forum
- Android-x86 qube installation guide - Community Guides- Qubes OS Forum
WayDroid Resources
- WayDroid Documentation - WayDroid Official
- WayDroid GitHub Repository - WayDroid Official
- Google Play Certification Guide - WayDroid Official
- Using Custom Waydroid Images - WayDroid Official
Community Scripts and Tools
- Waydroid Script by Casualsnek - A popular tool to automate setups
- wd-scripts - Another collection of scripts for managing Waydroid
- Spoof Device - Waydroid Script by Quackdoc
- Smart Dock - A taskbar for Android on desktop Linux, useful for Waydroid
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
- Waydroid Image Downloads (SourceForge) - Download manually
- aria2 Manual - For image downloads