Hello. Does this guide still work? I keep seeing bash: waydroid-install-apk: command not found in the appVM
It works for me.
How did you create your Waydroid template?
Did you use the âEasy wayâ or the âAdvanced wayâ?
Easy way
ps. Advanced way work ![]()
Maybe you didnât copy the whole code block with commands from Easy way. The commands to create the waydroid-install-apk script are at the end of the code block.
Thank you @apparatus for the excellent guide! I can confirm that the default/easy method works perfectly. The instructions are accurate.
Question: My WayDroid device is listed as a âWayDroid x86_64 Deviceâ. Is there an easy way to spoof this information, and should I do so? Or is it a useless practice? Since you recommended the pvgrub2-pvh kernel setup, spoofing this information might be worth considering.
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
fantastic work
Hello.
Can I upgrade this to debian-13-minimal? Or will there be any problems?
Does anyone have an answer to this? @apparatus
Thanks!
Hi all.
After going through the Easy steps and then launching Waydroid, it seems to be stuck on this blue loading screen with âSWAYâ and its logo on it. It seems to very quickly flicker to a black screen every 5 seconds or so.
Itâs been an hour and nothing happens; tried restartng the VM, the host, and same.
Does anyone know how Iâd debug this further? Or has anyone already gone through this and found a solution?
Thanks
EDIT: Waydroid template - #187 by brainchild059 seems to be the same issue @brainchild059 has faced there, but I am using the debian-12-minimal template. Iâll try switching to the dom0 kernel for now. EDIT2: Thatâs worked, but Iâd still like to use the in-VM kernel ![]()