Disposable Session Messenger Setup: "send messages, not metadata"

Before following this guide, please read the word of caution below.

What is Session?

From the Session Lightpaper: “Session is a decentralised messenger that supports completely private, secure, and anonymous communications. Session provides one-on-one (direct message, or ‘DM’), group chats, and voice calls.”

What is session-desktop and how does the Session protocol differ from Signal protocol?

Oxen’s session-desktop is a fork of Signal’s signal-desktop, an E2EE messaging app available for Linux. The key contributions of Session are decentralization of E2EE and the addition of onion routing (lokinet) to protect metadata. The Session protocol does not implement PFS or deniability, but it does provide similar protections by way of anonymity. For example, use of a public key (SessionID) as an identifier in place of a personal phone number. session-desktop does not require a link to any other devices, whereas signal-desktop requires periodic linking to the Signal app on one’s mobile device to function.

For all of the details read:

How to install session-desktop in Qubes

The following adapts the official (Debian) Linux install instructions from Oxen. To maintain good security practice, please also read the Qubes guide on Verifying Signatures.

1. (optional) Create a new templateVM, <template>.
In the following we will assume <template> is a clone of debian-12-minimal.

    [user@dom0 ~]$ sudo qubesctl --skip-dom0 --targets=debian-12-minimal --show-output state.sls update.qubes-vm
    [user@dom0 ~]$ qvm-clone debian-12-minimal <template>

Open a root terminal in the (new) template.

[user@dom0 ~]$ qvm-run -u root <template> xterm

Run the following commands as root, which will install the curl program needed to download the Oxen signing key, a notification daemon for use with session-desktop, and the Qubes networking package. Add the Oxen GPG signing key and the Session repository. Finally, fetch all repositories (including the Session repo) and install Session:
Note: If using cacher, replace “https://” with “http://HTTPS///” in each command below.

    root@<template>:~# apt install curl xfce4-notifyd qubes-core-agent-networking glib-networking
    root@<template>:~# curl -so /etc/apt/trusted.gpg.d/oxen.gpg --proxy 127.0.0.1:8082 https://deb.oxen.io/pub.gpg
    root@<template>:~# echo "deb https://deb.oxen.io bookworm main" | tee /etc/apt/sources.list.d/oxen.list  
    root@<template>:~# apt update && apt install session-desktop

Optional: Installing color emojis will improve the messaging experience, as will installation of a file manager in the minimal template (substitute nautilus for thunar based on personal preference).

root@<template>:~# apt install fonts-noto-color-emoji thunar qubes-core-agent-thunar qubes-pdf-converter qubes-img-converter

Exit the root terminal and shutdown the templateVM.

[user@dom0 ~]$ qvm-shutdown <template>

2. Create a disposableVM template, <dvm-template>,
based on the <template> with session-desktop installed. Choose a color from the Qubes scheme for the label, which is red in this example. The final command will create an app menu for the disposableVM.

   [user@dom0 ~]$ qvm-create --template <template> --label red <dvm-template>
   [user@dom0 ~]$ qvm-prefs <dvm-template> template_for_dispvms True
   [user@dom0 ~]$ qvm-features <dvm-template> appmenus-dispvm 1

By default this setup will use sys-firewall as the netVM, but a sys-vpn as netVM will also work. To display the file manager and Session app menu entries for the disposableVM choose the following from the Qubes app menu: “Template (disp): <dvm-template>” → “<dvm-template>: Qube Settings” → “Applications” and select the appropriate apps.

3. Create a SessionID in a disposableVM and make it persistent

The following instructions demonstrate how to persist the SessionID while using disposableVMs. This setup will work well if you prefer ephemeral messaging. If you want messages to persist, it’s easier to just create an appVM (non-disposable) based on <template> in place of the following setup.

Open a disposable terminal and execute session-desktop from the terminal, which will maintain access to the config files after closing the app.

     [user@dom0 ~]$ qvm-run --dispvm=<dvm-template> --service qubes.StartApp+debian-xterm

     [user@disp<#> ~]$ session-desktop
  • In the Session window select “Create Session ID”, then “Continue” and enter a personalized display name.
  • Select “Get Started”.
  • Show the recovery code to finish the setup process and store it in your vault (there are no passwords to remember).
  • While leaving the terminal open, close the Session chat window to free up the terminal.
  • Copy the following two, automatically generated, files to the disposableVM template, <dvm-template>.
        [user@disp<#> ~]$ qvm-copy .config/Session/config.json
        [user@disp<#> ~]$ qvm-copy .config/Session/sql/db.sqlite
  • In <dvm-template> use bind-dirs to make the above two files persist. (Note, bind-dirs on the /home directory is probably overkill here, but it works.)
  • The following instructions will ensure that each disposable Session instance spawned by <dvm-template> is configured with the above SessionID from the get go.
        [user@dom0 ~]$ qvm-run -u root <dvm-template> xterm

        [root@<dvm-template> ~]$ mkdir -p /rw/config/qubes-bind-dirs.d
        [root@<dvm-template> ~]$ touch /rw/config/qubes-bind-dirs.d/50_user.conf
        [root@<dvm-template> ~]$ echo "binds+=( '/home/user/.config/Session/sql' )" >> /rw/config/qubes-bind-dirs.d/50_user.conf
        [root@<dvm-template> ~]$ echo "binds+=( '/home/user/.config/Session' )" >> /rw/config/qubes-bind-dirs.d/50_user.conf
        [root@<dvm-template> ~]$ mkdir -p /rw/bind-dirs/home/user/.config/Session/sql                             
        [root@<dvm-template> ~]$ mv /home/user/QubesIncoming/disp<#>/config.json /rw/bind-dirs/home/user/.config/Session/
        [root@<dvm-template> ~]$ mv /home/user/QubesIncoming/disp<#>/db.sqlite /rw/bind-dirs/home/user/.config/Session/sql/
        [root@<dvm-template> ~]$ echo "chown -R user /home/user/.config/Session" >> /rw/config/rc.local

4. Enjoy

With setup complete, we can now close our disposable template and run Session as a disposable app with an anonymous, but persistent, SessionID.

        [user@dom0 ~]$ qvm-shutdown <dvm-template>
        [user@dom0 ~]$ qvm-run --dispvm=<dvm-template> --service qubes.StartApp+session-desktop

Note: session-desktop will pull in approximately two weeks worth of existing messages after opening a new dispVM. You can read more of the details of how this works in the Session whitepaper.

Work-in-progress?

  • Eliminate the recovery phrase popup with each respawn of the dispVMs
  • Persistence of contacts

*Thanks to the authors of the Qubes community guide for Signal, which provided a ready template for this guide.

1 Like

Can I use Session in whonix-vm?

This question was posed on the Whonix forum soon after Session was released. The answer at that time was “most likely” yes. While I haven’t tried it, I have successfully used signal-desktop in a whonix vm. Since session-desktop has its own onion router, use of Session in a whonix vm would be along the lines of “other anonymizing networks over tor”.

Here are the relevant Whonix forum links:

https://forum.whonix.org//t/session-private-messenger/13264
https://www.whonix.org//wiki/Other_Anonymizing_Networks

Any progress on the recovery phrase popup with each respawn of the dispVMs ?
I used the qubes-copy-to-vm to copy my dispVM /home/user/.config/Session to get persistence of my contacts, but I still get the popup.

Thanks for the reminder. I’ll try playing around with it again and see if I can make some progress on the recovery phrase. Hasn’t been a priority since I respawn relatively infrequently, but just discovered today that I lost a contact after a recent shutdown so that’s some motivation…

How can i install session on Fedora Template, is there not just an easy repo i can use please?

You can download the AppImage from the official site and use it in an app qube:
https://getsession.org/linux

how do i get the repo so i can add it to a template please?

I don’t think there is a fedora repository for it.

when i downloaded the app image url you kindly sent me it seemed to want to restore it to a disk image and it said if i did it it would wipe the disk as if it was reformatting a whole disk or something so I opened it with a mounter disk instead yet i cant find the image to open it and run it?
do you know where i should look to find it inside the qube please as i still see it sat inside downloads folder but i not find it anywhere else?
i went into settings - applications & refreshed it but its not in there.

I went up to USB icon at top task bar and right clicked on the session disk and attached it to the qube which i downloaded it on but still no find, im clearly doing a lot wrong here and im very newbie

Github only lists a Debian repo for session-desktop, so I don’t think a Fedora repo exists. There is a .rpm package on github though.

Flatpak has a verified session-desktop, so that may be a better route for a Fedora install.

1 Like

i downloaded that flatpak you kindly sent but cant find it anywhere i also did same with freetube, it all downloaded correctly but i just cant find its existence anywhere, due to user competence im sure but i feel ive looked everywhere and im so so baffled as no desktop to look on like normal OS.

Where did you install it? In template or in app qube?
How did you install it? As user or as root (system-wide)?

If you’ve installed it in app qube as user then open this qube’s Settings → Applications tab and press the “Refresh applications” button.
If you want to install it in template then you can use this guide:

1 Like

i open a TemplateVM Terminal and then i type;
$ sudo su
is this the correct way to do it as im just guessing this is the correct way as no one ever told me how to do it?

If you want to install it in template then you need to follow the guide linked in my previous post.

thanx

I was curious about this guide and Session messenger (although I’m aware of some critics against it)…

Instead of the bind-dirs thing, I just created the Session config folder in dvm-template*:

mkdir -p $HOME/.config/Session/sql
mv $HOME/QubesIncoming/disp*/config.json $HOME/.config/Session
mv $HOME/QubesIncoming/disp*/db.sqlite   $HOME/.config/Session/sql

And, when starting a new disposable from this dvm-template, it works.

Skipping the part “3. Create a SessionID in a disposableVM and make it persistent” and just creating a SessionID in the disposable template also works and seems to remove the recovery phrase popup in the new disposables.

Thanks for verifying this, which I’ll use to further streamline the guide the next time I update it.

I’m also aware of the soatok blog’s critique of Session, which gave me pause, but seemed half-baked given Session’s thoughtful response.

Edit: The Debian repo is presently up to date, but only because my questions prompted them to perform proper maintenance. So I’m leaving this caution in place for now. There is still no extrepo option.

Word of caution, hopefully temporary. Management of Session has recently transitioned to the Session Foundation. While the github fork’s read-me still points to the repo at https://deb.oxen.io, this repo has not been updated since the transition (last update was September 2024). As a consequence, the session-desktop version implemented in this guide remains, as of now, at 1.14.1. The latest version in the forked repo is currently 1.14.5.
Flathub is up to date, so that’s a viable option. This guide was written as an adaptation of the Oxen Debian guide to Qubes. Once a properly maintained Debian repo is available, I’ll modify it use extrepo.

Im using SimpleX. I’m having trouble placing the icons and getting appimage to open directly. The “disposable template appvm” has also caused me some doubts and I don’t know if I’ve implemented it correctly. Could an experienced user check this?

#!/bin/bash

#######################################################################
# File Name    : whonix-simplex-dvm.sh
# Description  : This script creates a SimpleX qube and template based
#                in Whonix DisposableVM template just with persistent
#                identity across disposables. It downloads and verify
#                the hash of the last SimpleX AppImage and uses sys-
#                whonix NetVM. For ease of use the script aggregates
#                shortcuts to application menu.
# Dependencies : curl
# Usage        : • Transfer this script from appvm to dom0 with:
#                [user@dom0 ~]$ qvm-run --pass-io appvm 'cat ~/whonix-simplex-dvm.sh' > ~/whonix-simplex-dvm.sh
#                • Make the script executable with:
#                [user@dom0 ~]$ chmod +x ~/whonix-simplex-dvm.sh
#                • Run the script with:
#                [user@dom0 ~]$ bash ~/whonix-simplex-dvm.sh
#                • To update, run the script in whonix-simplex-template:
#                bash ~/update-whonix-simplex-dvm.sh
# Author       : Me and the bois
# License      : Free of charge, no warranty
#######################################################################

# Safety check
set -eu

# Define Variables
APP_NAME="simplex-chat.AppImage"
APP_NAME_MENU="SimpleX Chat"
APP_DESKTOP="/usr/share/applications/simplex-chat.desktop"
BASE_TEMPLATE="whonix-workstation-17"
DISP_TEMPLATE_APPVM="whonix-simplex-template"
DISPVM_INSTANCE="whonix-simplex-dvm"
NET_VM="sys-whonix"
DOWNLOAD_URL="https://api.github.com/repos/simplex-chat/simplex-chat/releases/latest"
SIMPLEX_PERSISTENT_DIR="/rw/config/simplex-persistent"
SIMPLEX_CONFIG_DIR="$HOME/.config/simplex"

# Verify base template exists
if ! qvm-check "$BASE_TEMPLATE" >/dev/null 2>&1; then
    echo -e "\nError: Base template $BASE_TEMPLATE not found!" >&2
    exit 1
fi

# Update base template using qubesctl
echo -e "\nUpdating base template..."
sudo qubesctl --show-output --skip-dom0 --targets="$BASE_TEMPLATE" state.sls update.qubes-vm

# Create new AppVM based on Whonix template
echo -e "\nCreating $DISP_TEMPLATE_APPVM based on $BASE_TEMPLATE..."
qvm-create --class AppVM --property template="$BASE_TEMPLATE" --property label=gray "$DISP_TEMPLATE_APPVM"
echo -e "\nShutting down $DISP_TEMPLATE_APPVM to save changes..."
qvm-shutdown --wait "$DISP_TEMPLATE_APPVM"

# Configure it as DisposableVM template
echo -e "\nConfiguring $DISP_TEMPLATE_APPVM as DisposableVM template..."
qvm-prefs "$DISP_TEMPLATE_APPVM" template_for_dispvms True
qvm-prefs "$DISP_TEMPLATE_APPVM" netvm "$NET_VM"
qvm-features "$DISP_TEMPLATE_APPVM" appmenus-dispvm 1

# Install dependencies for new template
echo -e "\nInstalling dependencies..."
qvm-run -u root "$DISP_TEMPLATE_APPVM" 'apt update -y && apt install -y curl'

# Setup persistent storage
echo -e "\nConfiguring persistent storage..."
qvm-run -u root "$DISP_TEMPLATE_APPVM" "mkdir -p '$SIMPLEX_PERSISTENT_DIR' && \
    chown user:user '$SIMPLEX_PERSISTENT_DIR' && \
    ln -s '$SIMPLEX_PERSISTENT_DIR' '$SIMPLEX_CONFIG_DIR'"

# Download function
download_appimage() {
    echo -e "\nFetching release info from GitHub..."

    # Get release JSON with --pass-io for reliable transfer
    RELEASE_JSON=$(qvm-run --pass-io "$DISP_TEMPLATE_APPVM" "curl -sL '$DOWNLOAD_URL'")

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

    # Fallback to any AppImage if specific one not found
    if [[ -z "$DOWNLOAD_URL" ]]; then
        DOWNLOAD_URL=$(echo -e "\n$RELEASE_JSON" | grep -o '"browser_download_url": *"[^"]*"' | \
            grep -i '\.AppImage"' | cut -d '"' -f 4 | head -n 1)
    fi

    if [[ -z "$DOWNLOAD_URL" ]]; then
        echo -e "\nError: Could not find AppImage download URL in:" >&2
        echo -e "\n$RELEASE_JSON" >&2
        exit 1
    fi

    echo -e "\nDownloading AppImage from: $DOWNLOAD_URL"
    if ! qvm-run --pass-io "$DISP_TEMPLATE_APPVM" "curl -L -o '$HOME/$APP_NAME' '$DOWNLOAD_URL'"; then
        echo -e "\nDownload failed!" >&2
        exit 1
    fi

    qvm-run "$DISP_TEMPLATE_APPVM" "chmod +x '$HOME/$APP_NAME'"
}

# Hash verification function
verify_hash() {
    echo -e "\nExtracting expected hash from release info..."

    # Get the release body text that contains the hashes
    local release_body=$(echo "$RELEASE_JSON" | grep -zoP '"body":\s*".*?"' | cut -d'"' -f4 | tr -d '\0')

    # Extract ONLY the AppImage hash from the release body
    local 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
        exit 1
    fi

    echo -e "\nCalculating actual hash of downloaded file..."
    local actual_hash=$(qvm-run --pass-io "$DISP_TEMPLATE_APPVM" "sha256sum '$HOME/$APP_NAME' | cut -d' ' -f1")

    echo -e "\nVerifying hashes..."
    echo "Expected: $expected_hash"
    echo "Actual:   $actual_hash"

    if [[ "$expected_hash" != "$actual_hash" ]]; then
        echo "ERROR: Hash verification failed!" >&2
        exit 1
    fi
    echo "Hash verification passed"
}

# Download and verify
download_appimage
verify_hash

# Create desktop entry and menu entry
setup_desktop_entry() {
    echo -e "\nSetting up desktop entry and app menu integration..."

    # Define the desktop filename using Qubes naming convention
    local QUBES_DESKTOP_FILENAME="org.qubes-os.vm._whonix_dsimplex_dtemplate.simplex-chat.desktop"
    local DESKTOP_FILE_PATH="/usr/share/applications/$QUBES_DESKTOP_FILENAME"

    # Create the desktop entry file with proper contents
    qvm-run "$DISP_TEMPLATE_APPVM" "cat > '/tmp/$QUBES_DESKTOP_FILENAME'" <<"EOF"
[Desktop Entry]
Version=1.0
Type=Application
Name=SimpleX Chat
GenericName=Secure Messenger
Comment=Private and secure messaging with SimpleX
Terminal=false
TryExec=$HOME/$APP_NAME
Exec=$HOME/$APP_NAME
Icon=internet
Categories=Network;InstantMessaging;
StartupNotify=true
EOF

    # Move it to the correct system location
    qvm-run -u root "$DISP_TEMPLATE_APPVM" "mkdir -p /usr/share/applications && \
        mv '/tmp/$QUBES_DESKTOP_FILENAME' '$DESKTOP_FILE_PATH' && \
        chmod 644 '$DESKTOP_FILE_PATH'"

    # Create applications-merged directory in template
    qvm-run "$DISP_TEMPLATE_APPVM" "mkdir -p /home/user/.config/menus/applications-merged"

    # Create proper menu file in template
    qvm-run "$DISP_TEMPLATE_APPVM" "cat > /home/user/.config/menus/applications-merged/user-qubes-vm-directory_whonix_dsimplex_dtemplate.menu" <<EOF
<!DOCTYPE Menu PUBLIC "-//freedesktop//DTD Menu 1.0//EN"
    "http://www.freedesktop.org/standards/menu-spec/menu-1.0.dtd">
<Menu>
    <Name>Applications</Name>
    <Menu>
        <Name>qubes-vm-directory_whonix_dsimplex_dtemplate</Name>
        <Directory>qubes-vm-directory_whonix_dsimplex_dtemplate.directory</Directory>
        <Include>
            <Filename>$QUBES_DESKTOP_FILENAME</Filename>
            <Filename>org.qubes-os.vm._whonix_dsimplex_dtemplate.xfce4-terminal.desktop</Filename>
            <Filename>org.qubes-os.vm._whonix_dsimplex_dtemplate.janondisttorbrowser.desktop</Filename>
            <Filename>org.qubes-os.vm._whonix_dsimplex_dtemplate.systemcheck.desktop</Filename>
            <Filename>org.qubes-os.vm._whonix_dsimplex_dtemplate.thunar.desktop</Filename>
            <Filename>org.qubes-os.vm._whonix_dsimplex_dtemplate.anondist-torbrowser_update.desktop</Filename>
            <Filename>org.qubes-os.qubes-vm-settings._whonix_dsimplex_dtemplate.desktop</Filename>
            <Filename>org.qubes-os.vm._whonix_dsimplex_dtemplate.qubes-start.desktop</Filename>
        </Include>
    </Menu>
</Menu>
EOF

    # Force menu refresh
    echo -e "\nForce refreshing application menus..."
    qvm-run "$DISP_TEMPLATE_APPVM" "/etc/qubes-rpc/qubes.GetAppmenus > /dev/null"

    # Sync menus to dom0
    echo -e "\nSyncing application menus to dom0..."
    qvm-sync-appmenus "$DISP_TEMPLATE_APPVM"

    echo -e "\nDesktop entry and app menu setup complete!"
}

# Call the function
setup_desktop_entry

# Create updater script in the template with proper variable handling
create_updater_script() {
    echo -e "\nCreating updater in template...update only in the template!"

    # Define the script content
    local UPDATER_SCRIPT=$(cat <<EOF
#!/bin/bash

# Update only in the template!

# Variables
APP_NAME="simplex-chat.AppImage"
DOWNLOAD_URL="https://api.github.com/repos/simplex-chat/simplex-chat/releases/latest"
SIMPLEX_PERSISTENT_DIR="/rw/config/simplex-persistent"
SIMPLEX_CONFIG_DIR="\$HOME/.config/simplex"

$(declare -f download_appimage)
$(declare -f verify_hash)

# Ensure persistent directory link exists
if [[ ! -L "\$SIMPLEX_CONFIG_DIR" ]]; then
    ln -s "\$SIMPLEX_PERSISTENT_DIR" "\$SIMPLEX_CONFIG_DIR"
fi

# Main execution
download_appimage
verify_hash

echo -e "\\nSimpleX Chat update completed successfully!"
EOF
)
    # Transfer to template
    echo "$UPDATER_SCRIPT" | qvm-run --pass-io "$DISP_TEMPLATE_APPVM" "cat > \$HOME/update-simplex.sh"

    # Make executable
    qvm-run "$DISP_TEMPLATE_APPVM" "chmod +x \$HOME/update-simplex.sh"

    echo "Updater script created in $DISP_TEMPLATE_APPVM"
}
create_updater_script

echo -e "\nShutting down $BASE_TEMPLATE..."
qvm-shutdown --wait "$BASE_TEMPLATE"

echo -e "\nSyncing DISP_TEMPLATE_APPVM application menus..."
qvm-sync-appmenus "$DISP_TEMPLATE_APPVM"


# Finalize

echo -e "\nShutting down $DISP_TEMPLATE_APPVM to create $DISPVM_INSTANCE..."
qvm-shutdown --wait "$DISP_TEMPLATE_APPVM"

echo -e "\nCreating disposable VM..."
qvm-create --property template="$DISP_TEMPLATE_APPVM" --class DispVM --property label=red "$DISPVM_INSTANCE"
qvm-features "$DISPVM_INSTANCE" appmenus-dispvm ''

echo -e "\nInstallation complete!"
echo -e "\nUse '$DISPVM_INSTANCE' from Qubes application menu."
SimpleX References: