#!/bin/bash ####################################################################### # File Name : whonix-simplex-bwrap-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. It runs the application # using bubblewrap (bwrap) to allow unprivileged # users to use container features to defining its own # security model, and choosing appropriate bubblewrap # command-line arguments. # Dependencies : curl, bubblewrap # 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-10-11 ####################################################################### # Safety check set -eu # Define Variables APP_NAME="simplex-chat" BASE_TEMPLATE="whonix-workstation-17" CUSTOM_TEMPLATE="whonix-simplex-template" DISP_TEMPLATE="whonix-simplex-template-dvm" NAMED_DISP_VM="disp-whonix-simplex" NET_VM="sys-whonix" SOURCE_URL="https://api.github.com/repos/simplex-chat/simplex-chat/releases/latest" # Define bwrap rules get_bwrap_cmd() { local DISPLAY_VALUE="${DISPLAY:-:0}" local USER_ID=$(id -u) local DOWNLOAD_DIR="${XDG_DOWNLOAD_DIR:-$HOME/Downloads}" cat << EOF bwrap \ --share-net \ --unshare-uts --unshare-user --unshare-pid --unshare-ipc --unshare-cgroup \ --hostname RESTRICTED \ --clearenv \ --setenv HOME "$HOME" \ --setenv XDG_RUNTIME_DIR "/run/user/$USER_ID" \ --setenv DISPLAY "$DISPLAY_VALUE" \ --setenv QT_QPA_PLATFORM "xcb" \ --setenv QT_DEBUG_PLUGINS "1" \ --setenv TZ "UTC" \ --new-session \ --die-with-parent \ --proc /proc \ --dev /dev \ --bind "/run/user/$USER_ID" "/run/user/$USER_ID" \ --bind "$DOWNLOAD_DIR" "$DOWNLOAD_DIR" \ --bind "$HOME" "$HOME" \ --ro-bind /tmp/.X11-unix /tmp/.X11-unix \ --ro-bind /sys /sys \ --ro-bind /bin /bin \ --ro-bind /lib64 /lib64 \ --ro-bind /lib /lib \ --ro-bind /usr /usr \ --ro-bind /usr/share/icons /usr/share/icons \ --ro-bind /usr/share/fonts /usr/share/fonts \ --ro-bind /usr/share/X11/xkb /usr/share/X11/xkb \ --ro-bind /etc/resolv.conf /etc/resolv.conf \ --ro-bind /etc/fonts /etc/fonts \ --tmpfs /tmp \ --tmpfs /run \ "$HOME/$APP_NAME.AppImage" EOF } # 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-start --skip-if-running "$CUSTOM_TEMPLATE" qvm-run -p -u root "$CUSTOM_TEMPLATE" "echo 'TERM=xterm' >> /etc/environment" qvm-run -p -u root "$CUSTOM_TEMPLATE" "sed -i 's/^# *\(en_US.UTF-8\)/\1/' /etc/locale.gen" qvm-run -p -u root "$CUSTOM_TEMPLATE" "locale-gen en_US.UTF-8" # Shutdown qvm-shutdown --wait "$CUSTOM_TEMPLATE" # Step 4: Create DVM template based on custom template echo -e "\n[4/7] Creating DVM template..." if ! qvm-check "$DISP_TEMPLATE" 2>/dev/null; then qvm-create --template "$CUSTOM_TEMPLATE" --label red "$DISP_TEMPLATE" qvm-prefs "$DISP_TEMPLATE" template_for_dispvms True else echo "DVM template $DISP_TEMPLATE already exists, skipping creation." fi # Step 5: Download and verify in disposable template (we need to use userspace in dvm) echo -e "\n[5/7] Configuring disposable template..." # Start template if not running if ! qvm-check --running "$DISP_TEMPLATE"; then qvm-start "$DISP_TEMPLATE" && sleep 3 fi # Fetch latest release information echo "Fetching latest release information..." RELEASE_JSON="" if RELEASE_JSON=$(qvm-run -p "$DISP_TEMPLATE" "curl --tlsv1.2 --http1.1 -sL '$SOURCE_URL'"); then # Check for rate limit error if echo "$RELEASE_JSON" | grep -q "API rate limit exceeded"; then echo -e "\nERROR: Failed to fetch release info from API (rate limited)" >&2 qvm-shutdown --wait "$CUSTOM_TEMPLATE" 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 download URL" >&2 echo "Available browser_download_url entries:" >&2 echo "$RELEASE_JSON" | grep -o '"browser_download_url": *"[^"]*"' >&2 exit 1 fi echo "Downloading from: $DOWNLOAD_URL" # Download qvm-run -p "$DISP_TEMPLATE" "curl --tlsv1.2 --http1.1 -L -o '$HOME/$APP_NAME.AppImage' '$DOWNLOAD_URL'" # Hash verification echo "Verifying integrity..." # Extract the hash from API ASSET_NAME=$(basename "$DOWNLOAD_URL") EXPECTED_HASH=$(echo "$RELEASE_JSON" | \ grep -A 30 "\"name\":.*\"$ASSET_NAME\"" | \ grep '"digest"' | \ head -n 1 | \ sed -n 's/.*"digest": *"sha256:\([a-f0-9]*\)".*/\1/p') if [[ -z "$EXPECTED_HASH" ]]; then echo "ERROR: Could not extract hash from release info!" >&2 echo "Looking for asset: $ASSET_NAME" >&2 echo "Available assets:" >&2 echo "$RELEASE_JSON" | grep -A 5 -B 5 "\"name\":.*\"$ASSET_NAME\"" >&2 qvm-run -p "$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 permissions qvm-run -u root "$DISP_TEMPLATE" "chown user:user '$HOME/$APP_NAME.AppImage'" qvm-run "$DISP_TEMPLATE" "chmod +x '$HOME/$APP_NAME.AppImage'" # Create desktop entry using bwrap rules echo "Creating desktop entry..." # Generate the bwrap command BWRAP_CMD=$(get_bwrap_cmd) # Create desktop entry echo "Creating desktop entry..." DESKTOP_ENTRY="[Desktop Entry] Version=1.0 Type=Application Name=$APP_NAME Comment=Private and secure open-source messenger Exec=$BWRAP_CMD Icon=starred Terminal=false Categories=Network;Chat; StartupNotify=true" # Write desktop entry to the custom template qvm-start --skip-if-running "$CUSTOM_TEMPLATE" sleep 3 if ! qvm-run -p -u root "$CUSTOM_TEMPLATE" " mkdir -p /usr/share/applications && \ cat > /usr/share/applications/$APP_NAME.desktop <<'EOL' $DESKTOP_ENTRY EOL chown root:root /usr/share/applications/$APP_NAME.desktop && \ chmod 644 /usr/share/applications/$APP_NAME.desktop && \ update-desktop-database /usr/share/applications " < /dev/null; then echo "ERROR: Failed to setup desktop entry" >&2 exit 1 fi # Updater script to use in disposable template cat > /tmp/updater_script.sh << EOF #!/bin/bash set -eu APP_NAME="$APP_NAME" SOURCE_URL="$SOURCE_URL" echo "USE IT INSIDE THE DISPOSABLE TEMPLATE VM" # Download echo "Fetching latest release information..." release=\$(curl --tlsv1.2 -sL "\$SOURCE_URL") dl_url=\$(echo "\$release" | \\ grep -o '"browser_download_url": *"[^"]*"' | \\ grep -i "simplex-desktop-x86_64\.AppImage" | \\ cut -d '"' -f 4) # Check for rate limit if echo "\$release" | grep -q "API rate limit exceeded"; then echo "ERROR: GitHub API rate limit exceeded" >&2 exit 1 fi if [[ -z "\$dl_url" ]]; then echo "Error: No download URL found" >&2 echo "Available browser_download_url entries:" >&2 echo "\$release" | grep -o '"browser_download_url": *"[^"]*"' >&2 exit 1 fi echo "Downloading update from: \$dl_url" curl --tlsv1.2 --http1.1 -L -o "\$HOME/\$APP_NAME.AppImage" "\$dl_url" # Hash verification echo "Verifying integrity..." asset_name=\$(basename "\$dl_url") # Extract the hash from API expected_hash=\$(echo "\$release" | \\ grep -A 30 "\"name\": *\"\$asset_name\"" | \\ grep '"digest"' | \\ head -n 1 | \\ sed -n 's/.*"digest": *"sha256:\\([a-f0-9]*\\)".*/\\1/p') if [[ -z "\$expected_hash" ]]; then echo "ERROR: Could not extract hash from release info!" >&2 echo "Looking for asset: \$asset_name" >&2 echo "Available assets:" >&2 echo "\$release" | grep -A 5 -B 5 "\"name\": *\"\$asset_name\"" >&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 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 6: Create named disposable VM instance echo -e "\n[6/7] Creating named disposable VM instance..." if ! qvm-check "$NAMED_DISP_VM" 2>/dev/null; then 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 else echo "Named disposable VM $NAMED_DISP_VM already exists, skipping creation." fi # Step 7: Configure menu items echo -e "\n[7/7] Configuring menu items..." qvm-start --skip-if-running "$CUSTOM_TEMPLATE" 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 xfce4-terminal.desktop" # Finalize echo -e "\nFinish!"