SimpleX setup with dispvm

Here is: “named disposable AppImage SimpleX qube” with Whonix Workstation model that downloads, checks the hash of the latest SimpleX AppImage, and uses sys-whonix as NetVM. Includes the icon in the menu to directly open the AppImage.

It is in the testing phase...

Sometimes downloading with curl on GitHub doesn't work, so I'm studying the parameters --tlsv1.2 --http1.1. This is related to the “API rate limit” and errors related to HTTP/2.

The integrity verification procedure is not very good because the way the checksum is provided by the SimpleX project is poor.


whonix-simplex-named-disposable.sh

#!/bin/bash

#######################################################################
# File Name    : whonix-simplex-named-disposable.sh
# Description  : This script creates a named disposable SimpleX qube
#                based on Whonix Workstation template. It downloads
#                and verify the hash of the last SimpleX AppImage and
#                uses sys-whonix as NetVM.
# Dependencies : curl
# Usage        : • Transfer this script from appvm to dom0 with:
#                [user@dom0 ~]$ qvm-run -p appvm 'cat ~/whonix-simplex-named-disposable.sh' > ~/whonix-simplex-named-disposable.sh
#                • Make the script executable with:
#                [user@dom0 ~]$ chmod +x ~/whonix-simplex-named-disposable.sh
#                • Run the script with:
#                [user@dom0 ~]$ bash ~/whonix-simplex-named-disposable.sh
#                • To update, run the script in whonix-simplex-dvm (disposable template):
#                bash ~/update-simplex-chat.sh
# Author       : Me and the bois
# License      : Free of charge, no warranty
# Last edited  : 2025-09-19
#######################################################################

# Safety check
set -eu

# Define Variables
APP_NAME="simplex-chat"
BASE_TEMPLATE="whonix-workstation-17"
CUSTOM_TEMPLATE="whonix-simplex-template"
DISP_TEMPLATE="whonix-simplex-dvm"
NAMED_DISP_VM="disp-whonix-simplex"
NET_VM="sys-whonix"
GITHUB_API_URL="https://api.github.com/repos/simplex-chat/simplex-chat/releases/latest"

# Step 1: Install and update the Qubes Template
echo -e "\n[1/7] Checking for Qubes 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

# Update the template whether it was just installed or already existed
echo "Updating $BASE_TEMPLATE..."
sudo qubesctl --show-output --skip-dom0 --targets=$BASE_TEMPLATE state.sls update.qubes-vm

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

# Step 2: Create custom base template
echo -e "\n[2/7] Creating custom template by cloning..."
qvm-clone "$BASE_TEMPLATE" "$CUSTOM_TEMPLATE"
qvm-prefs "$CUSTOM_TEMPLATE" label black

# Step 3: Install dependencies
echo -e "\n[3/7] Installing dependencies..."
qvm-run -p -u root "$CUSTOM_TEMPLATE" "echo 'TERM=xterm' >> /etc/environment"
qvm-run -p -u root "$CUSTOM_TEMPLATE" "echo -e 'LANGUAGE=en_US.UTF-8\nLC_ALL=en_US.UTF-8\nLANG=en_US.UTF-8\nLC_CTYPE=en_US.UTF-8' >> /etc/locale.conf"
qvm-run -p -u root "$CUSTOM_TEMPLATE" "echo -e 'LANGUAGE=en_US.UTF-8\nLC_ALL=en_US.UTF-8\nLANG=en_US.UTF-8\nLC_CTYPE=en_US.UTF-8' >> /etc/default/locale"
qvm-run -p -u root "$CUSTOM_TEMPLATE" "/usr/sbin/locale-gen"

# Shutdown custom template to apply changes
qvm-shutdown --wait "$CUSTOM_TEMPLATE"

# Step 4: Create DVM template based on custom template
echo -e "\n[4/7] Creating DVM template..."
qvm-create --template "$CUSTOM_TEMPLATE" --label red "$DISP_TEMPLATE"
qvm-prefs "$DISP_TEMPLATE" template_for_dispvms True

# Step 5: Create named disposable VM instance
echo -e "\n[5/7] Creating named disposable VM instance..."
qvm-create --class DispVM --template "$DISP_TEMPLATE" --label red \
    --property netvm="$NET_VM" \
    --property include_in_backups=False \
    "$NAMED_DISP_VM"
qvm-features "$NAMED_DISP_VM" appmenus-dispvm 1

# Step 6: Download and verify in disposable template
echo -e "\n[6/7] Configuring $APP_NAME in disposable template..."

# Start template if not running
if ! qvm-check --running "$DISP_TEMPLATE"; then
    qvm-start "$DISP_TEMPLATE" && sleep 5
fi

# Download
echo "Fetching latest release information..."

RELEASE_JSON=""
if RELEASE_JSON=$(qvm-run -p "$DISP_TEMPLATE" "curl --tlsv1.2 --http1.1 -sL '$GITHUB_API_URL'"); then
    # Check for rate limit error
    if echo "$RELEASE_JSON" | grep -q "API rate limit exceeded"; then
        echo "ERROR: Failed to fetch release info from GitHub (rate limited)" >&2
        exit 1
    fi
fi

# Extract download URL
DOWNLOAD_URL=$(echo "$RELEASE_JSON" | grep -o '"browser_download_url": *"[^"]*"' | \
    grep -i "simplex-desktop-x86_64\.AppImage" | cut -d '"' -f 4 | head -n 1)

if [[ -z "$DOWNLOAD_URL" ]]; then
    echo "ERROR: Could not find AppImage download URL" >&2
    exit 1
fi

echo "Downloading AppImage from: $DOWNLOAD_URL"

# Try download
qvm-run -p "$DISP_TEMPLATE" "curl --tlsv1.2 --http1.1 -L -o '$HOME/$APP_NAME.AppImage' '$DOWNLOAD_URL'"

# Hash verification
echo "Verifying integrity..."

RELEASE_BODY=$(echo "$RELEASE_JSON" | grep -zo '"body": *"[^"]*"' | cut -d'"' -f4 | tr -d '\0')
EXPECTED_HASH=$(echo "$RELEASE_BODY" | grep -o "SHA2-256(simplex-desktop-x86_64.AppImage)= [a-f0-9]*" | cut -d' ' -f2)

if [[ -z "$EXPECTED_HASH" ]]; then
    echo "ERROR: Could not extract AppImage hash from release info!" >&2
    echo "release page content:" >&2
    echo "$RELEASE_BODY" >&2
    qvm-run "$DISP_TEMPLATE" "rm -f '$HOME/$APP_NAME.AppImage'"
    exit 1
fi

echo "Expected hash: $EXPECTED_HASH"
ACTUAL_HASH=$(qvm-run -p "$DISP_TEMPLATE" "sha256sum '$HOME/$APP_NAME.AppImage' | cut -d' ' -f1")
echo "Found hash:    $ACTUAL_HASH"

if [[ "$EXPECTED_HASH" != "$ACTUAL_HASH" ]]; then
    echo "ERROR: Hash verification failed!" >&2
    echo "Hashes do not match!" >&2
    qvm-run "$DISP_TEMPLATE" "rm -f '$HOME/$APP_NAME.AppImage'"
    exit 1
fi

echo "Hash verification passed"

# Adjust user permissions
qvm-run -u root "$DISP_TEMPLATE" "chown user:user '$HOME/$APP_NAME.AppImage'"
qvm-run "$DISP_TEMPLATE" "chmod +x '$HOME/$APP_NAME.AppImage'"

# Desktop entry (the root space works differently in the template)
echo "Creating desktop entry..."
DESKTOP_ENTRY="[Desktop Entry]
Version=1.0
Type=Application
Name=$APP_NAME
GenericName=$APP_NAME
Comment=Private and secure open-source messenger
Exec=/home/user/$APP_NAME.AppImage
Icon=starred
Terminal=false
Categories=Network;Chat;
StartupNotify=true"

if ! qvm-run -p -u root "$CUSTOM_TEMPLATE" "
    mkdir -p /usr/share/applications && \
    cat > /usr/share/applications/$APP_NAME.desktop <&2
    exit 1
fi

# Updater script to use in disposable template
cat > /tmp/updater_script.sh <&2
    exit 1
fi

dl_url=$(echo "$release" | grep -o '"browser_download_url": *"[^"]*"' | grep -i "simplex-desktop-x86_64\.AppImage" | cut -d '"' -f 4)

[[ -z "$dl_url" ]] && { echo "Error: No download URL found" >&2; exit 1; }

echo "Downloading update from: $dl_url"
curl --tlsv1.2 --http1.1 -L -o "$HOME/$APP_NAME.AppImage" "$dl_url"

# Hash verification
echo "Verifying integrity..."

release_body=$(echo "$release" | grep -zo '"body": *"[^"]*"' | cut -d'"' -f4 | tr -d '\0')
expected_hash=$(echo "$release_body" | grep -o "SHA2-256(simplex-desktop-x86_64.AppImage)= [a-f0-9]*" | cut -d' ' -f2)

if [[ -z "$expected_hash" ]]; then
    echo "ERROR: Could not extract AppImage hash from release info!" >&2
    echo "release page content:" >&2
    echo "$release_body" >&2
    rm -f "$HOME/$APP_NAME.AppImage"
    exit 1
fi

echo "Expected hash: $expected_hash"
actual_hash=$(sha256sum "$HOME/$APP_NAME.AppImage" | cut -d' ' -f1)
echo "Found hash:    $actual_hash"

if [[ "$expected_hash" != "$actual_hash" ]]; then
    echo "ERROR: Hash verification failed!" >&2
    echo "Hashes do not match!" >&2
    rm -f "$HOME/$APP_NAME.AppImage"
    exit 1
fi

echo "Hash verification passed"

# Adjust user permissions
chmod +x "$HOME/$APP_NAME.AppImage"
echo "Update complete and verified"
EOF

qvm-run -p "$DISP_TEMPLATE" "cat > /home/user/update-$APP_NAME.sh" < /tmp/updater_script.sh
qvm-run "$DISP_TEMPLATE" "chmod 700 /home/user/update-$APP_NAME.sh"
rm -f /tmp/updater_script.sh

qvm-shutdown --wait "$DISP_TEMPLATE"
echo "Template configuration complete"

# Step 7: Configure menu items
echo -e "\n[7/7] Configuring menu items..."
qvm-sync-appmenus "$CUSTOM_TEMPLATE"
qvm-shutdown --wait "$CUSTOM_TEMPLATE"
qvm-features "$CUSTOM_TEMPLATE" menu-items "$APP_NAME.desktop thunar.desktop debian-xterm.desktop xfce4-terminal.desktop"
qvm-features "$DISP_TEMPLATE" menu-items "$APP_NAME.desktop thunar.desktop debian-xterm.desktop xfce4-terminal.desktop"
qvm-features "$NAMED_DISP_VM" menu-items "$APP_NAME.desktop thunar.desktop"

# Finalize
echo -e "\nFinish!"

To make your settings permanent, open whonix-simplex-dvm (disposable template) and make the minimum changes to be saved when you open disp-whonix-simplex (named disposable vm).

This script could be a base to integrate AppImages in Qubes.

1 Like