הצטרף ל-Nostr
2026-07-19 15:04:49 CEST
in reply to

linux_privacy on Nostr: :locked: 1. Ephemeral Encrypted Volatile DVMs (for devices up to 16 GB RAM) Script ...

:locked: 1. Ephemeral Encrypted Volatile DVMs (for devices up to 16 GB RAM)

Script deploys a systemd service that creates a fresh LUKS-encrypted LVM thin pool on every boot, clones all DVM templates into it as ephemeral -ephemeral variants, and completely destroys the pool - including all VM volumes, encryption keys, and the underlying image file - on shutdown. This guarantees that all DVM session data resides exclusively on encrypted volatile storage that is cryptographically irrecoverable after poweroff, leaving no forensic traces on disk.

This solution does not impose any additional memory load, which is critical for devices with less than 16 GB of RAM. My friends with 16 GB devices experienced severe issues when running DVMs entirely in RAM - DVMs would crash unexpectedly, or dom0 would start glitching: panel and app menu artifacts, cursor freezes.

Make sure there is sufficient free space (more than 4 GB) in dom0. You can resize dom0 these commands:

sudo lvresize --size 4G /dev/mapper/qubes_dom0-root
sudo resize2fs /dev/mapper/qubes_dom0-root
sudo lvresize -L +4G qubes_dom0/root-pool

Run this simple script in dom0 terminal:

#!/bin/bash

sudo tee /etc/systemd/system/ephemeral-pool.service << 'EOF'
[Unit]
Description=Fresh ephemeral encrypted LVM pool with DVM auto-clone
After=local-fs.target qubesd.service
Requires=qubesd.service
[email protected] [email protected]

[Service]
Type=oneshot
RemainAfterExit=yes
ExecStartPre=-/usr/local/bin/ephemeral-pool-destroy.sh
ExecStart=/usr/local/bin/ephemeral-pool-create.sh
ExecStop=/usr/local/bin/ephemeral-pool-destroy.sh

[Install]
WantedBy=multi-user.target
EOF

sudo tee /usr/local/bin/ephemeral-pool-create.sh << 'EOF'
#!/bin/bash
set -uo pipefail

POOL_NAME="ephemeral_pool"
MOUNT_BASE="/var/tmp/ephemeral"
KEY_FILE="/dev/shm/ephemeral.key"
IMG_FILE="${MOUNT_BASE}/pool.img"
LUKS_NAME="ephemeral_crypt"
POOL_SIZE="4G"

ROOT_DEV=$(findmnt -n -o SOURCE / 2>/dev/null || echo "")

if echo "$ROOT_DEV" | grep -qE "(overlay|/dev/zram0)"; then
echo "[*] Detected amnesiac mode: $ROOT_DEV"
echo " Destroying old ephemeral VMs and artifacts..."

for vm in $(qvm-ls --raw-list --running 2>/dev/null); do
if qvm-volume list "$vm" 2>/dev/null | grep -q "${POOL_NAME}"; then
echo " -> Stopping: $vm"
qvm-shutdown --wait --timeout 10 "$vm" 2>/dev/null || \
qvm-kill "$vm" 2>/dev/null || true
fi
done

for vm in $(qvm-ls --raw-list 2>/dev/null); do
if qvm-volume list "$vm" 2>/dev/null | grep -q "${POOL_NAME}"; then
echo " -> Removing VM: $vm"
qvm-remove --force "$vm" 2>/dev/null || true
fi
done

qvm-pool remove "${POOL_NAME}" 2>/dev/null || true
vgchange -an ephemeral_vg 2>/dev/null || true
vgremove -f ephemeral_vg 2>/dev/null || true
cryptsetup close "${LUKS_NAME}" 2>/dev/null || true
rm -f "${KEY_FILE}" "${IMG_FILE}"

echo "[+] Cleanup completed. Pool creation skipped in amnesiac mode."
exit 0
fi

for vm in $(qvm-ls --raw-list 2>/dev/null); do
if qvm-volume list "$vm" 2>/dev/null | grep -q "${POOL_NAME}"; then
echo " -> Removing VM: $vm"
qvm-kill "$vm" 2>/dev/null || true
sleep 1
qvm-remove --force "$vm" 2>/dev/null || true
sleep 1
fi
done

qvm-pool remove "${POOL_NAME}" 2>/dev/null || true
sleep 1
vgchange -an ephemeral_vg 2>/dev/null || true
vgremove -f ephemeral_vg 2>/dev/null || true
cryptsetup close "${LUKS_NAME}" 2>/dev/null || true
rm -f "${KEY_FILE}" "${IMG_FILE}"

mkdir -p "${MOUNT_BASE}"
install -m600 /dev/null "${KEY_FILE}" && dd if=/dev/urandom bs=1 count=4096 of="${KEY_FILE}" iflag=fullblock
truncate -s "${POOL_SIZE}" "${IMG_FILE}"
LOOP_DEV=$(losetup -f --show "${IMG_FILE}")
cryptsetup luksFormat -q --key-file "${KEY_FILE}" "${LOOP_DEV}"
cryptsetup open --key-file "${KEY_FILE}" "${LOOP_DEV}" "${LUKS_NAME}"
pvcreate "/dev/mapper/${LUKS_NAME}"
vgcreate "ephemeral_vg" "/dev/mapper/${LUKS_NAME}"
lvcreate -T -n "thin_pool" -l +100%FREE "ephemeral_vg"
losetup -d "${LOOP_DEV}" || true

if qvm-pool list 2>/dev/null | grep -q "^${POOL_NAME}"; then
qvm-pool remove "${POOL_NAME}" 2>/dev/null || true
sleep 1
fi
qvm-pool add "${POOL_NAME}" lvm_thin --option volume_group=ephemeral_vg --option thin_pool=thin_pool 2>/dev/null || \
qvm-pool add "${POOL_NAME}" lvm_thin -o volume_group=ephemeral_vg,thin_pool=thin_pool

echo "[*] Copying DVM templates..."
qvm-ls --raw-list 2>/dev/null | grep -i 'dvm' | grep -v '\-ephemeral$' | while read vm; do
[ -n "$vm" ] || continue
clone_name="${vm}-ephemeral"
qvm-check -q "$clone_name" 2>/dev/null && {
qvm-kill "$clone_name" 2>/dev/null || true
qvm-remove --force "$clone_name" 2>/dev/null || true
}
echo " -> $vm -> ${clone_name}"
qvm-clone -P "${POOL_NAME}" "$vm" "$clone_name" && {
qvm-prefs "$clone_name" template_for_dispvms True 2>/dev/null || true
qvm-prefs "$clone_name" autostart False 2>/dev/null || true
}
done

echo "[+] Done!"
EOF

sudo tee /usr/local/bin/ephemeral-pool-destroy.sh << 'EOF'
#!/bin/bash
set -uo pipefail

POOL_NAME="ephemeral_pool"
LUKS_NAME="ephemeral_crypt"
KEY_FILE="/dev/shm/ephemeral.key"
IMG_FILE="/var/tmp/ephemeral/pool.img"

for vm in $(qvm-ls --raw-list 2>/dev/null); do
if qvm-volume list "$vm" 2>/dev/null | grep -q "${POOL_NAME}"; then
qvm-kill "$vm" 2>/dev/null || true
sleep 1
qvm-remove --force "$vm" 2>/dev/null || true
sleep 1
fi
done

qvm-pool remove "${POOL_NAME}" 2>/dev/null || true
sleep 1
qvm-pool remove "${POOL_NAME}" 2>/dev/null || true
vgchange -an ephemeral_vg 2>/dev/null || true
vgremove -f ephemeral_vg 2>/dev/null || true
cryptsetup close "${LUKS_NAME}" 2>/dev/null || true
for loop in $(losetup -j "${IMG_FILE}" 2>/dev/null | cut -d: -f1 || true); do
[ -n "$loop" ] && losetup -d "$loop" 2>/dev/null || true
done
rm -f "${KEY_FILE}" "${IMG_FILE}"
EOF

sudo chmod +x /usr/local/bin/ephemeral-pool-create.sh
sudo chmod +x /usr/local/bin/ephemeral-pool-destroy.sh
sudo systemctl daemon-reload
sudo systemctl enable --now ephemeral-pool.service

To remove pool and disable the systemd service:

sudo systemctl stop ephemeral-pool.service
sudo systemctl disable ephemeral-pool.service

Start the service and pool again:

sudo systemctl enable --now ephemeral-pool.service

:card_index_dividers: 2. RAM-based DVMs on a Zram disk (for devices with 20-24 GB of RAM)

This script creates a compressed RAM disk (zram) that acts as a super-fast ephemeral storage pool for your DVMs. On every boot, it sets aside a portion of your RAM - compressed with lz4 algorithm to save space - builds an LVM thin pool on top of it, and clones all your DVM templates there as -ephemeral variants. When you shut down the system, everything in that RAM disk vanishes instantly without ever touching your physical disk. The key advantage of zram is built-in compression: your 3 GB pool can hold 6-7 GB of actual VM data, giving you more usable space than raw RAM would allow. It is also significantly faster than disk-based storage, so DVMs launch and run with near-native speed. Unlike tmpfs, zram does not compete with your system memory for cache.

First, increase the maximum dom0 memory in this file
sudo nano /etc/default/grub
edit this value dom0_mem=max:4096M to dom0_mem=max:6144M,
update GRUB sudo grub2-mkconfig -o /boot/grub2/grub.cfg and reboot Qubes OS.

Run this simple script in dom0 terminal:

#!/bin/bash

sudo tee /etc/systemd/system/zram-pool.service << 'EOF'
[Unit]
Description=ZRAM Ephemeral Pool
After=qubesd.service
[email protected]

[Service]
Type=oneshot
ExecStart=/usr/local/bin/zram-pool-create.sh
RemainAfterExit=yes

[Install]
WantedBy=multi-user.target
EOF

sudo tee /usr/local/bin/zram-pool-create.sh << 'EOF'
#!/bin/bash
set -euo pipefail

POOL_NAME="zram_pool"
ZRAM_SIZE="4G"
VG_NAME="zram_vg"

EXTRA_VMS=(
#"whonix"
#"sys-whonix"
)

ROOT_DEV=$(findmnt -n -o SOURCE / 2>/dev/null || echo "")

if echo "$ROOT_DEV" | grep -qE "(overlay|/dev/zram0)"; then
echo "[*] Detected amnesiac mode: $ROOT_DEV"
echo " Cleaning up old VMs..."

for vm in $(qvm-ls --raw-list --running 2>/dev/null); do
if qvm-volume list "$vm" 2>/dev/null | grep -q "${POOL_NAME}"; then
echo " -> Stopping: $vm"
qvm-shutdown --wait --timeout 10 "$vm" 2>/dev/null || \
qvm-kill "$vm" 2>/dev/null || true
fi
done

for vm in $(qvm-ls --raw-list 2>/dev/null); do
if qvm-volume list "$vm" 2>/dev/null | grep -q "${POOL_NAME}"; then
echo " -> Removing: $vm"
qvm-remove --force "$vm" 2>/dev/null || true
fi
done

qvm-pool remove "${POOL_NAME}" 2>/dev/null || true
vgchange -an "${VG_NAME}" 2>/dev/null || true
vgremove -f "${VG_NAME}" 2>/dev/null || true

for dev in /dev/zram*; do
[ -b "$dev" ] || continue
zramctl --reset "$dev" 2>/dev/null || true
done

echo "[+] Cleanup completed."
exit 0
fi

if [ "$EUID" -ne 0 ]; then
echo "[!] Root privileges required"
exit 1
fi

echo "[*] Creating zram device (${ZRAM_SIZE})..."
modprobe zram 2>/dev/null || true

ZRAM_DEV=""
for dev in /dev/zram*; do
[ -b "$dev" ] || continue
if ! zramctl "$dev" 2>/dev/null | grep -q "mounted\|active"; then
ZRAM_DEV="$dev"
break
fi
done

if [ -z "$ZRAM_DEV" ]; then
ZRAM_DEV=$(zramctl --find --size "$ZRAM_SIZE" --algorithm lz4)
else
zramctl --reset "$ZRAM_DEV" 2>/dev/null || true
ZRAM_DEV=$(zramctl --find --size "$ZRAM_SIZE" --algorithm lz4)
fi

echo " Device: $ZRAM_DEV"

echo "[*] Removing VMs from pool ${POOL_NAME}..."
for vm in $(qvm-ls --raw-list --running 2>/dev/null); do
if qvm-volume list "$vm" 2>/dev/null | grep -q "${POOL_NAME}"; then
echo " -> Stopping: $vm"
qvm-shutdown --wait --timeout 30 "$vm" 2>/dev/null || {
echo " -> Force killing: $vm"
qvm-kill "$vm" 2>/dev/null || true
}
fi
done

for vm in $(qvm-ls --raw-list 2>/dev/null); do
if qvm-volume list "$vm" 2>/dev/null | grep -q "${POOL_NAME}"; then
echo " -> Removing: $vm"
qvm-remove --force "$vm" 2>/dev/null || true
fi
done

echo "[*] Cleaning up old infrastructure..."
qvm-pool remove "${POOL_NAME}" 2>/dev/null || true
vgchange -an "${VG_NAME}" 2>/dev/null || true
vgremove -f "${VG_NAME}" 2>/dev/null || true

# Detach old loops on zram
for loopdev in $(losetup -a 2>/dev/null | grep "$ZRAM_DEV" | cut -d: -f1); do
echo " -> Detaching loop: $loopdev"
losetup -d "$loopdev" 2>/dev/null || true
done

echo "[*] Creating loop on ${ZRAM_DEV}..."
LOOP_DEV=$(losetup -f --show "$ZRAM_DEV")
echo " Loop: $LOOP_DEV"

echo "[*] Creating LVM on ${LOOP_DEV}..."
pvcreate -q "$LOOP_DEV"
vgcreate -q "${VG_NAME}" "$LOOP_DEV"
lvcreate -q -T -n "thin_pool" -l +100%FREE "${VG_NAME}"

echo "[*] Registering pool ${POOL_NAME}..."
if qvm-pool list 2>/dev/null | grep -q "^${POOL_NAME}"; then
qvm-pool remove "${POOL_NAME}" 2>/dev/null || true
sleep 1
fi

qvm-pool add "${POOL_NAME}" lvm_thin --option volume_group="${VG_NAME}" --option thin_pool=thin_pool 2>/dev/null || \
qvm-pool add "${POOL_NAME}" lvm_thin -o volume_group="${VG_NAME}",thin_pool=thin_pool

echo " Pool created:"
qvm-pool info "${POOL_NAME}"

echo "[*] Cloning VMs into ephemeral pool..."

echo " [DVM templates]"
qvm-ls --raw-list 2>/dev/null | while read -r vm; do
[ -n "$vm" ] || continue

is_dvm=$(qvm-prefs "$vm" template_for_dispvms 2>/dev/null || echo "False")
[ "$is_dvm" = "True" ] || continue

if qvm-volume list "$vm" 2>/dev/null | grep -q "${POOL_NAME}"; then
continue
fi

clone_name="${vm}-ephemeral"

if qvm-ls --raw-list 2>/dev/null | grep -q "^${clone_name}$"; then
echo " -> Removing old copy: ${clone_name}"
qvm-kill "$clone_name" 2>/dev/null || true
qvm-remove --force "$clone_name" 2>/dev/null || true
fi

echo " -> Cloning: $vm -> ${clone_name}"

if qvm-clone -P "${POOL_NAME}" "$vm" "$clone_name"; then
qvm-prefs "$clone_name" template_for_dispvms True 2>/dev/null || true
qvm-prefs "$clone_name" autostart False 2>/dev/null || true
echo " [+] OK"
else
echo " [!] ERROR"
fi
done

if [ ${#EXTRA_VMS[@]} -gt 0 ]; then
echo " [Extra VMs]"
for vm in "${EXTRA_VMS[@]}"; do
if ! qvm-ls --raw-list 2>/dev/null | grep -q "^${vm}$"; then
echo " [!] VM not found: $vm"
continue
fi

if qvm-volume list "$vm" 2>/dev/null | grep -q "${POOL_NAME}"; then
echo " -> Skipping (already in zram): $vm"
continue
fi

clone_name="${vm}-ephemeral"

if qvm-ls --raw-list 2>/dev/null | grep -q "^${clone_name}$"; then
echo " -> Removing old copy: ${clone_name}"
qvm-kill "$clone_name" 2>/dev/null || true
qvm-remove --force "$clone_name" 2>/dev/null || true
fi

echo " -> Cloning: $vm -> ${clone_name}"

if qvm-clone -P "${POOL_NAME}" "$vm" "$clone_name"; then
qvm-prefs "$clone_name" template_for_dispvms False 2>/dev/null || true
qvm-prefs "$clone_name" autostart False 2>/dev/null || true
echo " [+] OK"
else
echo " [!] ERROR"
fi
done
fi
EOF

sudo chmod +x /usr/local/bin/zram-pool-create.sh
sudo systemctl daemon-reload
sudo systemctl enable --now zram-pool.service

To remove zram pool and disable the systemd service run this script:

#!/bin/bash

# Step 1: Stop and disable the systemd service
sudo systemctl stop zram-pool.service
sudo systemctl disable zram-pool.service

# Step 2: Remove all VMs from zram_pool
echo "[*] Removing VMs from zram_pool..."
for vm in $(qvm-ls --raw-list 2>/dev/null); do
if qvm-volume list "$vm" 2>/dev/null | grep -q "zram_pool"; then
echo " -> Removing: $vm"
qvm-kill "$vm" 2>/dev/null || true
sleep 1
qvm-remove --force "$vm" 2>/dev/null || true
sleep 1
fi
done

# Step 3: Remove the Qubes storage pool
echo "[*] Removing zram_pool..."
qvm-pool remove zram_pool 2>/dev/null || true

# Step 4: Deactivate and remove LVM volume group
echo "[*] Cleaning up LVM..."
vgchange -an zram_vg 2>/dev/null || true
vgremove -f zram_vg 2>/dev/null || true

# Step 5: Detach loop device from zram
echo "[*] Detaching loop devices..."
for loopdev in $(losetup -a 2>/dev/null | grep "/dev/zram" | cut -d: -f1); do
echo " -> Detaching: $loopdev"
losetup -d "$loopdev" 2>/dev/null || true
done

# Step 6: Reset zram device
echo "[*] Resetting zram device..."
for dev in /dev/zram*; do
[ -b "$dev" ] || continue
zramctl --reset "$dev" 2>/dev/null || true
done

echo "[+] zram pool completely removed. Reboot to clear all traces from memory."

Start the service and pool again:

sudo systemctl enable --now zram-pool.service

:tornado: 3. RAM-based DVMs on TMPFS (for devices with 32 GB of RAM)

This variant creates a pure RAM-based ephemeral pool using tmpfs. On every boot, it allocates a portion of your system memory as a temporary filesystem, builds an LVM thin pool inside it, and clones all your DVM templates there as ephemeral variants. When you shut down or reboot, the entire pool and all its VMs vanish instantly. Direct RAM access means DVMs launch faster than any other storage type. No encryption overhead, no zram compression delays or disk I/O bottlenecks - just RAM and LVM. The entire pool size is reserved from RAM immediately, making this best suited for systems with 32 GB or more.

First, increase the maximum dom0 memory in this file
sudo nano /etc/default/grub
edit this value dom0_mem=max:4096M to dom0_mem=max:8192M,
update GRUB sudo grub2-mkconfig -o /boot/grub2/grub.cfg and reboot Qubes OS.

Run this simple script in dom0 terminal:

#!/bin/bash

sudo tee /etc/systemd/system/tmpfs-pool.service << 'EOF'
[Unit]
Description=TMPFS Ephemeral Pool
After=qubesd.service
[email protected]

[Service]
Type=oneshot
ExecStart=/usr/local/bin/tmpfs-pool-create.sh
RemainAfterExit=yes

[Install]
WantedBy=multi-user.target
EOF

sudo tee /usr/local/bin/tmpfs-pool-create.sh << 'EOF'
#!/bin/bash
set -uo pipefail

POOL_NAME="tmpfs_pool"
MOUNT_BASE="/dev/shm/ephemeral"
IMG_FILE="${MOUNT_BASE}/pool.img"
POOL_SIZE="4G"
VG_NAME="ephemeral_vg"

ROOT_DEV=$(findmnt -n -o SOURCE / 2>/dev/null || echo "")

if echo "$ROOT_DEV" | grep -qE "(overlay|/dev/zram)"; then
echo "[*] Detected amnesiac mode: $ROOT_DEV"
echo " Destroying old ephemeral VMs and artifacts..."

for vm in $(qvm-ls --raw-list --running 2>/dev/null); do
if qvm-volume list "$vm" 2>/dev/null | grep -q "${POOL_NAME}"; then
echo " -> Stopping: $vm"
qvm-shutdown --wait --timeout 10 "$vm" 2>/dev/null || \
qvm-kill "$vm" 2>/dev/null || true
fi
done

for vm in $(qvm-ls --raw-list 2>/dev/null); do
if qvm-volume list "$vm" 2>/dev/null | grep -q "${POOL_NAME}"; then
echo " -> Removing: $vm"
qvm-remove --force "$vm" 2>/dev/null || true
fi
done

qvm-pool remove "${POOL_NAME}" 2>/dev/null || true
vgchange -an "${VG_NAME}" 2>/dev/null || true
vgremove -f "${VG_NAME}" 2>/dev/null || true
rm -rf "${MOUNT_BASE}"

echo "[+] Cleanup completed. Pool creation skipped in amnesiac mode."
exit 0
fi

echo "[*] Phase 1: Removing ALL VMs from ephemeral pool..."

for vm in $(qvm-ls --raw-list 2>/dev/null); do
if qvm-volume list "$vm" 2>/dev/null | grep -q "${POOL_NAME}"; then
echo " -> Removing VM: $vm"
qvm-kill "$vm" 2>/dev/null || true
sleep 1
qvm-remove --force "$vm" 2>/dev/null || true
sleep 1
fi
done

# Pool in RAM — simply delete files
echo "[*] Phase 2: Cleaning old pool from RAM..."

qvm-pool remove "${POOL_NAME}" 2>/dev/null || true
vgchange -an "${VG_NAME}" 2>/dev/null || true
vgremove -f "${VG_NAME}" 2>/dev/null || true

# Remove old files (if any remain)
rm -rf "${MOUNT_BASE}"

echo "[*] Phase 3: Creating fresh ephemeral pool in RAM..."

mkdir -p "${MOUNT_BASE}"

# New image in RAM
truncate -s "${POOL_SIZE}" "${IMG_FILE}"

# Loop device
LOOP_DEV=$(losetup -f --show "${IMG_FILE}")

# LVM thin pool directly on loop (no encryption)
pvcreate "$LOOP_DEV"
vgcreate "${VG_NAME}" "$LOOP_DEV"
lvcreate -T -n "thin_pool" -l +100%FREE "${VG_NAME}"

# Detach loop
losetup -d "${LOOP_DEV}" || true

echo "[*] Phase 4: Registering pool..."

if qvm-pool list 2>/dev/null | grep -q "^${POOL_NAME}"; then
qvm-pool remove "${POOL_NAME}" 2>/dev/null || true
sleep 1
fi

qvm-pool add "${POOL_NAME}" lvm_thin --option volume_group="${VG_NAME}" --option thin_pool=thin_pool 2>/dev/null || \
qvm-pool add "${POOL_NAME}" lvm_thin -o volume_group="${VG_NAME}",thin_pool=thin_pool

if ! qvm-pool list 2>/dev/null | grep -q "^${POOL_NAME}"; then
echo "[!] Pool registration failed"
exit 1
fi

echo "[+] Pool ready in RAM"

echo "[*] Phase 5: Copying DVM templates..."

echo " [DVM templates]"
qvm-ls --raw-list 2>/dev/null | while read -r vm; do
[ -n "$vm" ] || continue

is_dvm=$(qvm-prefs "$vm" template_for_dispvms 2>/dev/null || echo "False")
[ "$is_dvm" = "True" ] || continue

if qvm-volume list "$vm" 2>/dev/null | grep -q "${POOL_NAME}"; then
continue
fi

clone_name="${vm}-ephemeral"

if qvm-ls --raw-list 2>/dev/null | grep -q "^${clone_name}$"; then
echo " -> Removing old copy: ${clone_name}"
qvm-kill "$clone_name" 2>/dev/null || true
qvm-remove --force "$clone_name" 2>/dev/null || true
fi

echo " -> Cloning: $vm -> ${clone_name}"

if qvm-clone -P "${POOL_NAME}" "$vm" "$clone_name"; then
qvm-prefs "$clone_name" template_for_dispvms True 2>/dev/null || true
qvm-prefs "$clone_name" autostart False 2>/dev/null || true
echo " [+] OK"
else
echo " [!] ERROR"
fi
done

echo "[+] Done!"
echo ""
echo "VMs in ephemeral pool:"
for vm in $(qvm-ls --raw-list 2>/dev/null); do
if qvm-volume list "$vm" 2>/dev/null | grep -q "${POOL_NAME}"; then
echo " - $vm"
fi
done
EOF

sudo chmod +x /usr/local/bin/tmpfs-pool-create.sh
sudo systemctl daemon-reload
sudo systemctl enable --now tmpfs-pool.service

To remove zram pool and disable the systemd service run this script:

#!/bin/bash

# Step 1: Stop the service (prevents auto-restart on boot)
sudo systemctl stop tmpfs-pool.service
sudo systemctl disable tmpfs-pool.service

# Step 2: Remove all VMs from tmpfs_pool
echo "[*] Removing VMs from tmpfs_pool..."
for vm in $(qvm-ls --raw-list 2>/dev/null); do
if qvm-volume list "$vm" 2>/dev/null | grep -q "tmpfs_pool"; then
echo " -> Removing: $vm"
qvm-kill "$vm" 2>/dev/null || true
sleep 1
qvm-remove --force "$vm" 2>/dev/null || true
sleep 1
fi
done

# Step 3: Remove the Qubes storage pool
echo "[*] Removing tmpfs_pool..."
qvm-pool remove tmpfs_pool 2>/dev/null || true

# Step 4: Clean up LVM
echo "[*] Cleaning up LVM..."
vgchange -an ephemeral_vg 2>/dev/null || true
vgremove -f ephemeral_vg 2>/dev/null || true

# Step 5: Free RAM by removing the image
echo "[*] Freeing RAM..."
rm -rf /dev/shm/ephemeral

echo "[+] tmpfs pool stopped and cleaned up."

Start the service and pool again:

sudo systemctl enable --now tmpfs-pool.service

:gear: 4. Modifying default DVMs

This solution is for those who want to configure the default DVMs for amnesic operation - without creating new pools or new DVMs.

Сreate a systemd service that remounts the root in the DVM as an ephemeral encrypted volatile volume:

#!/bin/bash

sudo tee /etc/systemd/system/dvm-root.service << 'EOF'
[Unit]
Description=DVM root rw False
After=qubesd.service
Requires=qubesd.service

[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/usr/local/bin/dvm-root.sh

[Install]
WantedBy=multi-user.target
EOF

sudo tee /usr/local/bin/dvm-root.sh << 'EOF'
#!/bin/bash
set -euo pipefail

while read -r vm; do
[ -n "$vm" ] || continue
is_dvm=$(qvm-prefs "$vm" template_for_dispvms 2>/dev/null || echo "False")
[ "$is_dvm" = "True" ] || continue
qvm-volume config "${vm}:root" rw false
done < <(qvm-ls --raw-list)
EOF

sudo chmod +x /usr/local/bin/dvm-root.sh
sudo systemctl daemon-reload
sudo systemctl enable dvm-root.service
qvm-pool set vm-pool -o ephemeral_volatile=True

TMPFS Script for Debian/Fedora/Kicksecure-based DVM templates:

OVERLAY_BASE="/run/home-overlay"
LOWERDIR="/home"
UPPERDIR="$OVERLAY_BASE/upper"
WORKDIR="$OVERLAY_BASE/work"
MOUNTPOINT="/home"

# Create directories
mkdir -p "$UPPERDIR" "$WORKDIR"

# Mount tmpfs for overlay upper+work layers
mount -t tmpfs -o size=2G,mode=0755 tmpfs "$OVERLAY_BASE"

# Recreate upper/work inside tmpfs after mount
mkdir -p "$UPPERDIR" "$WORKDIR"

# Mount overlayfs
mount -t overlay overlay \
-o lowerdir="$LOWERDIR",upperdir="$UPPERDIR",workdir="$WORKDIR" \
"$MOUNTPOINT"

Or alternative Script: Zram-disk for Debian/Fedora/Kicksecure-based DVM templates (If you want an overlay with memory compression - saves RAM, but adds slight CPU overhead):

ZRAM_DEV=""
ZRAM_SIZE="2G"
LOWERDIR="/home"
OVERLAY_BASE="/run/home-overlay"
UPPERDIR="$OVERLAY_BASE/upper"
WORKDIR="$OVERLAY_BASE/work"
MOUNTPOINT="/home"

# --- 1. Create and configure zram device ---

# Load zram module if not loaded
if ! lsmod | grep -q "^zram"; then
modprobe zram num_devices=1 || modprobe zram
fi

# Wait for zram control interface
for _ in {1..10}; do
if [ -d /sys/class/zram-control ]; then
break
fi
sleep 0.1
done

# Find first free zram device
for i in /sys/block/zram*; do
[ -e "$i" ] || continue
if [ "$(cat "$i"/disksize)" = "0" ]; then
ZRAM_DEV="/dev/$(basename "$i")"
break
fi
done

if [ -z "$ZRAM_DEV" ]; then
# No free device; try to add one via hot_add
if [ -f /sys/class/zram-control/hot_add ]; then
idx=$(cat /sys/class/zram-control/hot_add)
ZRAM_DEV="/dev/zram$idx"
else
echo "ERROR: no free zram device found and hot_add not available" >&2
exit 1
fi
fi

echo "Using $ZRAM_DEV"

# Configure compression and size
echo lz4 > /sys/block/$(basename "$ZRAM_DEV")/comp_algorithm 2>/dev/null || true
echo "$ZRAM_SIZE" > /sys/block/$(basename "$ZRAM_DEV")/disksize

# Format and mount zram as ext4 (needed for overlay upper+work)
mkfs.ext4 -q "$ZRAM_DEV"

mkdir -p "$OVERLAY_BASE"
mount -t ext4 "$ZRAM_DEV" "$OVERLAY_BASE"

# --- 2. Prepare overlay directories ---

mkdir -p "$UPPERDIR" "$WORKDIR"

# --- 3. Mount overlayfs ---

mount -t overlay overlay \
-o lowerdir="$LOWERDIR",upperdir="$UPPERDIR",workdir="$WORKDIR" \
"$MOUNTPOINT"


For additional anti-forensic protection, you can use this script and redirect all system journal logs to memory (volatile storage). Additionally, this script deletes files containing metadata about removed VMs (this is simple file deletion, which has anti-forensic value only in the context of TRIM/discard on NVMe SSD, but is not direct anti-forensics). The basis of this script (files removing) is the second script from this guide:

#!/bin/bash

sudo tee /etc/systemd/journald.conf << EOF
[Journal]
Storage=volatile
EOF
sudo systemctl restart systemd-journald

sudo tee /etc/systemd/system/clean.service << 'EOF'
[Unit]
Description=Clean logs of removed Qubes VMs
After=qubesd.service

[Service]
Type=oneshot
ExecStart=/usr/local/bin/clean.sh
RemainAfterExit=no

[Install]
WantedBy=multi-user.target
EOF

sudo tee /usr/local/bin/clean.sh << 'EOF'
#!/bin/bash

set -euo pipefail

readonly LOGDIR='/var/log'
readonly TEMPDIR_ROOT='/home/user/tmp'
readonly MENUDIR='/home/user/.config/menus/applications-merged'


existing_qubes=$(qvm-ls --fields=name --raw-data | sort)


all_qube_names=''


all_qube_names+=$(find "${LOGDIR}/libvirt/libxl/" \
-type f \
-regextype posix-egrep \
-regex '.*\.log((\.old)|(-[0-9]{8}))?(\.gz)?$' \
-exec basename "{}" \; \
| sed -r 's/\.log((\.old)|(-[0-9]{8}))?(\.gz)?$//g' \
| sed -r 's/^(guid|qrexec|qubesdb)\.//g' \
| sort | uniq)$'\n'


all_qube_names+=$(find "${LOGDIR}/qubes/" \
-type f \
-regextype posix-egrep \
-regex '.*\.log((\.old)|(-[0-9]{8}))?(\.gz)?$' \
-exec basename "{}" \; \
| sed -r 's/\.log((\.old)|(-[0-9]{8}))?(\.gz)?$//g' \
| sed -r 's/^(guid|qrexec|qubesdb)\.//g' \
| sort | uniq)$'\n'


all_qube_names+=$(find "${LOGDIR}/xen/console/" \
-type f \
-regextype posix-egrep \
-regex '.*\/guest-.*\.log((\.old)|(-[0-9]{8}))?(\.gz)?$' \
-exec basename "{}" \; \
| sed -r 's/\.log((\.old)|(-[0-9]{8}))?(\.gz)?$//g' \
| sed -r 's/^guest-//g' \
| sort | uniq)$'\n'


set +e
ram_pools=$(qvm-pool list | grep -Eio '^ram_pool_[^ ]+' | sort | uniq)
set -e
all_qube_names+=$(echo "${ram_pools}" | sed -r 's/^ram_pool_//g')$'\n'


if [ -d "${TEMPDIR_ROOT}" ]; then
all_qube_names+=$(find "${TEMPDIR_ROOT}" \
-mindepth 1 -maxdepth 1 -type d \
-exec basename "{}" \; \
| sort | uniq)$'\n'
fi


all_qube_names+=$(find "${MENUDIR}" \
-regextype posix-egrep \
-regex '.*\/user-qubes-.*\.menu$' \
-exec basename "{}" \; \
| sed -r 's/\.menu$//g' \
| sed -r 's/^user-qubes-(disp)?vm-directory(_|-)//g' \
| sort | uniq)$'\n'


all_qube_names=$(echo "${all_qube_names}" \
| sed -r 's/^(Domain-0|libxl-driver)$//g' \
| sed -r '/^\s*$/d' \
| sort | uniq)


set +e
qubes_to_remove=$(diff --new-line-format='' --unchanged-line-format='' \
<(echo "${all_qube_names}") <(echo "${existing_qubes}") \
| sed -r '/^\s*$/d')
set -e


for qube_name in ${qubes_to_remove}; do
decoded_qube_name=$(echo "${qube_name}" \
| sed -r 's/_d/-/g' \
| sed -r 's/_u/_/g')

log_pattern="${qube_name}\.log((\.old)|(-[0-9]{8}))?(\.gz)?"
menu_pattern="user-qubes-(disp)?vm-directory(_|-)${qube_name}\.menu"

declare -A targets
targets=(["${LOGDIR}/libvirt/libxl"]="${log_pattern}"
["${LOGDIR}/qubes"]="((guid|qrexec|qubesdb)\.)?${log_pattern}"
["${LOGDIR}/xen/console"]="guest-${log_pattern}"
["${MENUDIR}"]="${menu_pattern}")

if [ -d "${TEMPDIR_ROOT}" ]; then
targets+=("${TEMPDIR_ROOT}"="${qube_name}")
fi

for search_dir in "${!targets[@]}"; do
mapfile -d $'\0' found_files < <(find "${search_dir}" \
-regextype posix-egrep \
-regex ".*\/${targets[${search_dir}]}$" \
-print0)
for file in "${found_files[@]}"; do
[ -z "${file}" ] && continue
rm -rf "${file}"
done
done
done


for pool_name in ${ram_pools}; do
qube_name=$(echo "${pool_name}" | sed -r 's/^ram_pool_//')
if ! echo "${existing_qubes}" | grep -qx "${qube_name}"; then
pool_mountpoint=$(qvm-pool info "${pool_name}" \
| grep -E '^dir_path' \
| sed -r 's/^dir_path\s+//g')
qvm-pool remove "${pool_name}" 2>/dev/null || true
umount "${pool_mountpoint}" 2>/dev/null || true
rm -rf "${pool_mountpoint}" 2>/dev/null || true
fi
done

find "${LOGDIR}/qubes/" -maxdepth 1 -type f -name '*.log.old' -delete
EOF

sudo chmod +x /usr/local/bin/clean.sh
sudo systemctl daemon-reload
sudo systemctl enable clean.service