Use the Helper Script in Tab 11 which automatically applies kernel parameters to dom0 and all VMs (option #1).
chmod +x auto-script.sh && ./auto-script.sh
Recommend using an external drive with USB 3.2 support to achieve 450–900 MB/s, allowing the system to run and be portable.
Officially supported in Qubes 4.3 – can be left enabled, though you may need to sign kernel modules if you run into issues with certain devices.
BIOS/UEFI Setup
Core system (isolated from the network, updates happen through official channels)
sudo qubes-dom0-update
dom0 parameters are applied in GRUB; VM parameters via qvm-prefs
🔴 Xen parameters: add to GRUB_CMDLINE_XEN_DEFAULT
bootscrub=1 xen_scrub_pages=1
🔴 Linux parameters: add to GRUB_CMDLINE_LINUX
init_on_free=1 init_on_alloc=1 page_poison=1 lockdown=confidentiality module.sig_enforce=1 slab_nomerge slub_debug=FZ vsyscall=none kernel.kptr_restrict=2 oops=panic debugfs=off quiet loglevel=0 spectre_v2=on page_alloc.shuffle=1 preempt=voluntary pti=on mds=full
smt=off – moved to “Expert settings” section (significantly impacts performance, 30–50%).ipv6.disable=1 – not useful in dom0 (dom0 does not directly handle the network card). Apply to VMs via kernelopts.noswap – may cause system freeze. Use vm.swappiness=1 instead.The qvm-prefs VM kernelopts command is limited to 512 characters. This means you cannot add all the parameters below at once.
Solutions: Use the shortened list (under 300 characters) or enable the in‑VM kernel for templates that need extra protection.
These parameters protect your applications from CPU attacks. In Qubes, VMs use the dom0 kernel but their parameters are set separately and do not inherit GRUB parameters.
xen_scrub_pages=1 init_on_free=1 init_on_alloc=1 page_poison=1 mitigations=auto random.trust_cpu=off random.trust_bootloader=off spec_store_bypass_disable=on tsx_async_abort=full mds=full,nosmt page_alloc.shuffle=1 randomize_kstack_offset=on l1tf=full,force ipv6.disable=1 slab_nomerge vsyscall=none retbleed=stuff spectre_v2_user=on kvm.nx_huge_pages=force
init_on_free=1 init_on_alloc=1 page_poison=1 slab_nomerge vsyscall=none mitigations=auto random.trust_cpu=off random.trust_bootloader=off spec_store_bypass_disable=on ipv6.disable=1
| # | Parameter | Explanation | Benefit | Applies to |
|---|---|---|---|---|
| 1 | bootscrub=1 |
Scrubs memory during Xen boot | Prevents data leakage from a previous session | 🔄 Xen |
| 2 | xen_scrub_pages=1 |
Scrubs memory before reusing it between VMs | Prevents data leakage from one VM to another | 🔄 Xen |
| 3 | init_on_free=1 |
Zeroes memory immediately after it is freed | Prevents leakage of passwords and keys | 🔄 All |
| 4 | init_on_alloc=1 |
Zeroes memory before it is allocated | Prevents a malicious app from reading leftover data from another app | 🔄 All |
| 5 | page_poison=1 |
Poison freed memory pages | Makes use‑after‑free exploits harder | 🔄 All |
| 6 | lockdown=confidentiality |
Prevents even root from modifying the kernel or reading its memory | Prevents tampering with the dom0 system | 🖥️ dom0 |
| 7 | module.sig_enforce=1 |
Prevents loading unsigned kernel modules | Blocks rootkit injection | 🖥️ dom0 |
| 8 | ipv6.disable=1 |
Disables IPv6 entirely | Prevents IPv6 data leaks | 🔄 All |
| 9 | noswap |
Disables swap entirely | Prevents data leakage to disk (⚠️ may cause system freeze) | 🖥️ dom0 |
| 10 | slab_nomerge |
Prevents merging of similar kernel objects | Makes heap exploitation harder | 🔄 All |
| 11 | slub_debug=FZ |
Enables memory integrity checks and adds guard zones | Detects buffer overflows | 🔄 All |
| 12 | vsyscall=none |
Disables the old vsyscall interface | Closes an ROP attack surface | 🔄 All |
| 13 | kernel.kptr_restrict=2 |
Hides kernel addresses | Makes ROP attacks harder | 🖥️ dom0 |
| 14 | oops=panic |
Causes the kernel to panic on any oops | Prevents exploitation of errors to continue an attack | 🖥️ dom0 |
| 15 | debugfs=off |
Disables debugfs entirely | Prevents leakage of kernel structure information | 🖥️ dom0 |
| 16 | spectre_v2=on |
Enables extra Spectre v2 protections | Prevents CPU side‑channel attacks | 🖥️ dom0 |
| 17 | preempt=voluntary |
Voluntary kernel preemption scheduling | More stable than full while maintaining good security | 🖥️ dom0 |
| 18 | pti=on |
Enables page table isolation | Stops Meltdown vulnerability | 🖥️ dom0 |
| 19 | mds=full |
Enables full protection against Microarchitectural Data Sampling | Prevents data leakage between threads | 🖥️ dom0 |
| 20 | quiet loglevel=0 |
Suppresses kernel messages during boot | Prevents information leakage to an attacker | 🔄 All |
| 21 | page_alloc.shuffle=1 |
Randomizes memory page allocation | Makes heap exploitation harder | 🔄 All |
| 22 | mitigations=auto |
Automatically enables vulnerability patches | Protects Spectre, Meltdown inside VMs | 📱 AppVMs |
| 23 | random.trust_cpu=off |
Do not trust CPU random number generator | Prevents key guessing via a compromised RNG | 📱 AppVMs |
| 24 | random.trust_bootloader=off |
Do not trust bootloader random number generator | Prevents key guessing via a compromised RNG | 📱 AppVMs |
| 25 | spec_store_bypass_disable=on |
Disables Spectre v4 vulnerability | Prevents data leakage through store buffers | 📱 AppVMs |
| 26 | tsx_async_abort=full |
Enables full TSX Async Abort protection | Prevents CPU data leakage | 📱 AppVMs |
| 27 | mds=full,nosmt |
Full MDS protection with SMT disabled | Prevents data leakage between threads | 📱 AppVMs |
| 28 | l1tf=full,force |
Enables full L1 Terminal Fault protection | Prevents L1 memory data leakage | 📱 AppVMs |
| 29 | randomize_kstack_offset=on |
Randomizes kernel stack offset | Makes stack‑based exploitation harder | 📱 AppVMs |
| 30 | retbleed=stuff |
Protects against Retbleed attack | Prevents data leakage via indirect branch prediction | 📱 AppVMs |
| 31 | spectre_v2_user=on |
Spectre v2 protection for user space | Prevents Spectre v2 attacks from applications | 📱 AppVMs |
| 32 | kvm.nx_huge_pages=force |
Enables nested page tables protection | Prevents VM escape attacks | 📱 AppVMs |
| 33 | smt=off |
Disables Hyper‑Threading (⚠️ experts only) | Prevents Spectre/Meltdown attacks between VMs but severely impacts performance (30–50%) | 🔴 Expert |
🖥️ For dom0: Add the parameters to /etc/default/grub then:
sudo grub2-mkconfig -o /boot/grub2/grub.cfg && sudo reboot
📱 For VMs: Apply to each VM (mind the 512‑character limit):
qvm-prefs VMNAME kernelopts "parameters here"
From the GRUB menu, press e on the boot entry, find the line starting with linux, delete the parameters you added, then press Ctrl+X or F10 to boot.
After booting, restore the original GRUB file from the backup: sudo cp /etc/default/grub.backup.* /etc/default/grub && sudo grub2-mkconfig -o /boot/grub2/grub.cfg
💡 Or use the Helper Script in Tab 11 for automatic application to everything.
💡 For ephemeral (RAM‑based) encryption inside VMs, see Tab 5 (Amnesic Tmpfs) and Tab 6 (RAM Pool).
sys-net, physical isolation, what each party sees). Tor and gateway settings are in Tab 2.
A small router (e.g., GL.iNet) in WISP mode, connected to the Qubes device via Ethernet only, with wireless broadcasting of the internal network disabled.
eth0In Qubes, no one connects directly to the internet except sys-net. Then sys-firewall sits as a mandatory mediator for all other qubes.
sys-net. The correct setup: application ← sys-firewall ← sys-net → internet.
| Party | Normal setup (direct WiFi) | WISP + Ethernet setup |
|---|---|---|
| Intermediary (middle provider) | 🔴 Sees the Qubes device directly (MAC, TCP/IP fingerprint, connection times) | 🟡 Sees only a single router (doesn't know the number or type of devices behind it) |
| Neighbors on the same network | 🔴 See the Qubes device (can scan and try to attack it) | 🟢 See only a single router (cannot see what is behind it) |
| Official ISP | 🟢 Sees only the intermediary (doesn't see you) | 🟢 Sees only the intermediary (doesn't see you) |
| Final destination (without Tor) | 🔴 Sees the official ISP's IP | 🔴 Sees the official ISP's IP |
| Final destination (with Tor) | 🟢 Sees a Tor exit node IP | 🟢 Sees a Tor exit node IP |
| Criteria | 🔴 Intermediary + direct WiFi | 🟡 Official ISP | 🟢 Intermediary + WISP + Ethernet |
|---|---|---|---|
| Qubes device visible to others? | 🔴 Intermediary + neighbors | 🟢 No one | 🟢 No one |
| Risk of WiFi driver exploits | 🔴 High | 🟢 None | 🟢 None |
| What the official ISP sees | 🟢 Only the intermediary | 🔴 You directly | 🟢 Only the intermediary |
| Router compromise = risk inside your device? | — | — | 🟢 No (external) |
| Cost | Nothing | Nothing | 🟡 ~30–50$ |
| Protection level | 🔴 Weak | 🟡 Moderate | 🟢 Excellent |
| # | Plan | Security | When to use? |
|---|---|---|---|
| 🟢 A | WISP + Ethernet | ⭐⭐⭐⭐⭐ | Permanent daily setup |
| 🟢 B | WISP + VPN on router + Ethernet | ⭐⭐⭐⭐⭐ | Extra protection from the intermediary (VPN before data reaches Qubes) |
| 🟡 C | WISP + internal encrypted WiFi (WPA3) | ⭐⭐⭐ | If cable is temporarily unavailable |
| 🟡 D | USB tethering from an Android phone | ⭐⭐⭐ | Traveling, no WISP router available |
| 🟠 E | Direct WiFi + random MAC + firewall | ⭐⭐ | Emergency only, last resort |
| 🔴 F | Ordinary direct WiFi | ⭐ | Never use this |
usb0. Better than direct WiFi, but the phone itself could be compromised.# /usr/lib/NetworkManager/conf.d/31-randomize-mac.conf [connection] ethernet.cloned-mac-address=random wifi.cloned-mac-address=random wifi.scan-rand-mac-address=yes
In the WISP + Ethernet setup, this is only an extra precaution — the router is the one dealing with the external network.
qvm-prefs sys-net kernelopts "ipv6.disable=1"
qvm-prefs sys-firewall kernelopts "ipv6.disable=1"
about:config → media.peerconnection.enabled = false
sys-whonix is the only netvm for your browsing qube.
🔗 Next step: Go to Tab 2 to set up Tor, gateways, and connection types.
anon-work encrypts data with TLS/HTTPS.📖 Result: Even if the exit node is malicious, it sees only TLS‑encrypted data (passwords, messages, etc.).
Your device ← Tor network ← internet
ISP sees Tor connection | destination sees Tor exit IP
Your device ← bridge ← Tor ← internet
ISP sees ordinary encrypted connection | for bypassing censorship
Your device ← VPN ← Tor ← internet
ISP sees VPN only | most private
Your device ← Tor ← VPN ← internet
VPN sees your destination | convenience only
Your device ← VPN ← internet
VPN knows your identity and destinations
Your device ← internet
Everything exposed
| Connection type | ISP sees | Destination sees | Privacy | Speed | Recommendation |
|---|---|---|---|---|---|
| No protection | Everything | Real IP | 🔴 None | 🟢 Fast | ❌ Do not use |
| VPN only | VPN connection | VPN IP | 🟡 Moderate | 🟢 Fast | Regular use |
| Tor only | Tor connection | Tor exit IP | 🟢 High | 🟡 Average | ✅ Baseline |
| Tor + bridges | Ordinary encrypted connection | Tor exit IP | 🟢 Very high | 🟡 Average | 🌉 To bypass censorship |
| VPN → Tor | VPN connection | Tor exit IP | 🟢 Excellent | 🟡 Average | 🔐 Recommended |
| Tor → VPN | Tor connection | VPN IP | 🟡 Good | 🟡 Average | ⚠️ Convenience only |
| Criteria | VPN | Tor (Whonix) | Tor + Bridges | I2P |
|---|---|---|---|---|
| Speed | 🟢 Fast | 🟡 Average | 🟡 Average | 🟡 Average to slow |
| Anonymity | 🟡 Moderate | 🟢 Very high | 🟢 Very high | 🟢 High |
| Censorship resistance | 🟢 High | 🔴 Weak | 🟢 Very high | 🟡 Moderate |
| Hides Tor usage | ❌ No | ❌ No | ✅ Yes | 🟡 Unknown |
| Ease of setup | 🟢 Easy | 🟢 Easy | 🟢 Easy | 🔴 Hard |
| Cost | ~50€/year | Free | Free | Free |
| Gateway (GW) | Workstation (WS) | |
|---|---|---|
| Function | Runs Tor processes and acts as a network bridge for the WS | Runs user applications and performs activities safely |
| VM type in Qubes | AppVM + provides_network true | AppVM (ordinary) |
| Contains Tor | ✅ Yes | ❌ No |
| Has entry guards | ✅ Yes | ❌ No |
| Its netvm | sys-firewall-X | sys-whonix-X |
| provides_network | ✅ true | ❌ false |
sys-net ← sys-firewall-A ← sys-whonix-A ← anon-work-A
Whonix‑Workstation is based on the Kicksecure distribution, a hardened operating system that provides security layers beyond merely routing traffic through Tor.
The entry guard is the first Tor node a gateway connects to. Its change cycle lasts weeks or months. If you land on a malicious node, you stay stuck with it. Using a single gateway for all activities means all your identities are linked to the same guard.
# 1. Disposable firewall (DispVM) qvm-create --class DispVM --label orange --template default-dvm sys-firewall-A qvm-prefs sys-firewall-A provides_network true qvm-prefs sys-firewall-A netvm sys-net # 2. Whonix gateway qvm-create --class AppVM --label red --template whonix-gateway-18 sys-whonix-A qvm-prefs sys-whonix-A provides_network true qvm-prefs sys-whonix-A netvm sys-firewall-A # 3. Workstation qvm-create --class AppVM --label green --template whonix-workstation-18 anon-work-A qvm-prefs anon-work-A netvm sys-whonix-A
# Copy the commands above and change A → B qvm-create --class DispVM --label cyan --template default-dvm sys-firewall-B qvm-prefs sys-firewall-B provides_network true qvm-prefs sys-firewall-B netvm sys-net # ... continue the same way
sys-firewall-A | Type: DisposableVM | Template: default-dvm | Networking: sys-net | Enable Provides networksys-whonix-A | Type: AppVM | Template: whonix-gateway-18 | Networking: sys-firewall-A | Enable Provides networkanon-work-A | Type: AppVM | Template: whonix-workstation-18 | Networking: sys-whonix-AA to Bqvm-clone copies the Tor state including the entry guard. Create a new gateway directly from the template.default-dvm – any change to it will affect all disposable VMs in the system.Bridges are alternative entry points to the Tor network that hide the fact that you are using Tor from your ISP.
🕵️ What does an observer see?
Most Whonix users do not need bridges. But three groups might:
| Group | Goal | Suitable bridge type |
|---|---|---|
| 1️⃣ Those who are blocked | Tor is blocked, any way to connect | obfs4 or Snowflake |
| 2️⃣ Those who want to hide | Hide Tor usage from a serious adversary | Private bridges |
| 3️⃣ Testers | Only testing bridge connectivity | Any type |
| Type | Speed | Censorship resistance | Mechanism | What does the ISP see? | Best for |
|---|---|---|---|---|---|
| obfs4 | 🟡 Average | 🟢 High | Packet obfuscation to remove fixed patterns | Random encrypted traffic that looks unusual | General use, moderate censorship |
| Snowflake | 🟡 Average to good | 🟢 Very high | Relay via volunteers’ browsers using WebRTC | Video call or WebRTC browsing (e.g., Google Meet) | Highly censored environments |
| meek_azure | 🔴 Slow | 🟢 Very high | Hides inside a large CDN server, appears as normal HTTPS browsing | Ordinary visit to a well‑known cloud server (e.g., microsoft.com) | China, Middle East, emergency fallback |
| WebTunnel | 🟢 Good | 🟢 Very high | Hides the connection inside a WebSocket tunnel over HTTPS | Normal secure web connection with no strange fingerprints | Networks that block unfamiliar protocols |
🖥️ Graphical method (easiest):
sys-whonix, click the Tor Control Panel icon in the taskbar.⌨️ Terminal method:
anon-connection-wizard
bridges@torproject.org from Gmail or RiseupSnowflake is a pluggable transport that relays traffic through volunteers' browsers using WebRTC, making it the hardest to detect and block.
✅ Snowflake advantages:
🔧 For manual configuration (advanced): add to /usr/local/etc/torrc.d/50_user.conf:
UseBridges 1 ClientTransportPlugin snowflake exec /usr/bin/snowflake-client Bridge snowflake 192.0.2.3:80 2B280B23E1107BB62ABFC40DDCC8824814F80A72 fingerprint=2B280B23E1107BB62ABFC40DDCC8824814F80A72 url=https://1098762253.rsc.cdn77.org/ fronts=www.cdn77.com,www.phpmyadmin.net ice=stun:stun.l.google.com:19302,stun:stun.antisip.com:3478,stun:stun.bluesip.net:3478,stun:stun.dus.net:3478,stun:stun.epygi.com:3478,stun:stun.sonetel.com:3478,stun:stun.uls.co.za:3478,stun:stun.voipgate.com:3478,stun:stun.voys.nl:3478 utls-imitate=hellorandomizedalpn
#nameserver 10.0.2.3 in /etc/resolv.conf.whonix.
sys-whonix, open Nyx from the application menu or type nyx in the terminal.192.168.0.1 UNKNOWN 1 / Guard
123.45.67.89 RelayName 1 / Guard
To exit Nyx: press q twice.
Two completely different things despite the similar name:
| Term | Meaning |
|---|---|
| Bridges (Tor) | Alternative entry points to the Tor network to bypass censorship |
| Bridged Networking | Setting in virtualisation software (VirtualBox) that should not be changed |
--class AppVM --template whonix-gateway-18 + provides_network true--class AppVM --template whonix-workstation-18 + netvm sys-whonix-XBefore diving into tools and practical scenarios, it is important to understand the core technologies that this guide relies on for data encryption, as well as the advanced double‑encryption scenario.
Linux Unified Key Setup – the disk encryption standard built into the Linux kernel.
cryptsetup and tools like GNOME Disks.🎯 When to use it? When you need native Linux encryption and want the best protection against GPU‑based attacks thanks to Argon2id. It is the best encryption for everyday Linux use if you don't need deniability.
VeraCrypt – TrueCrypt's successor, an open‑source tool for encrypting files and disks.
🎯 When to use it? When you need plausible deniability or cross‑platform compatibility.
What is Argon2id?
A modern key derivation function designed specifically to consume large amounts of memory, making GPU and ASIC attacks impractical. Its real power lies in forcing an attacker to allocate massive memory for each individual guess, which completely kills the massive parallelism that GPUs rely on.
| Algorithm / Setting | Memory | Speed on modern GPU | Security level |
|---|---|---|---|
| SHA‑256 (unsalted) | < 1 MB | ~10,000,000,000/sec (10 billion) | 🔴 Very weak |
| PBKDF2 (VeraCrypt default) | < 1 MB | Millions/sec | 🔴 Weak |
| Argon2id (OWASP minimum) | 46 MiB | ~1,000/sec | 🟡 Good |
| Argon2id (RFC 9106 high) | 2 GiB | ~20–50/sec | 🟢 Excellent |
| Argon2id (this guide – maximum) | 4 GiB | less than 25/sec | 🟢🔒 Maximum |
Numbers from recent academic studies (2025–2026) and OWASP / RFC 9106 recommendations. For the 4 GB setting, the speed collapses because a GPU with 24 GB VRAM cannot run more than ~6 parallel guesses, dropping the rate below 25 guesses/second.
The attacker can try billions of passwords per second using a single graphics card. If your password is 8 random characters, it can be cracked in hours or days.
The same graphics card can only try less than 25 passwords per second. Even with 1000 cards, cracking a 6‑word random passphrase would take thousands of years.
sudo cryptsetup luksDump /dev/sdX | grep Argon2.
Two methods to create a LUKS2 encrypted container with maximum protection against GPU attacks. Choose the one that suits you best: a direct command or an interactive script.
qvm-prefs storage-vault maxmem 6000
sudo cryptsetup luksFormat --type luks2 --pbkdf argon2id --pbkdf-memory 4194304 --iter-time 8000 /home/user/data.img
--type luks2 – use the modern version of LUKS.--pbkdf argon2id – GPU‑resistant KDF.--pbkdf-memory 4194304 – allocate 4 GB of RAM for key derivation.--iter-time 8000 – derivation time ~8 seconds.Script that asks for the path, size, RAM, and time, then creates the container automatically. Supports creating a new container or encrypting an existing file.
You can use the built‑in GNOME Disks tool for quick formatting. But be aware: the tool does not allow you to choose Argon2id (it uses only default settings), so prefer the two methods above for maximum protection.
| Criteria | 🔵 LUKS only | 🟢 VeraCrypt only | 🔴 VC + LUKS |
|---|---|---|---|
| ⚡ Read/write speed | 🟢 1–5 GB/s | 🟡 100–400 MB/s | 🟡 similar to VC |
| 🛡️ Data integrity | ✅ AEAD | ❌ XTS | ✅ from LUKS |
| 🔐 GPU attack resistance | 🟢 Argon2id | 🟡 PBKDF2 | 🟢✅ both |
| 🕵️ Encryption detectability | 🔴 LUKS header | 🟢 random | 🟢 VC hides |
| 🎭 Plausible deniability | ❌ not supported | ✅ hidden volumes | ✅✅ excellent |
| 🪟 Windows compatibility | ❌ | ✅ | ❌ (LUKS inside) |
| 🐧 Qubes OS compatibility | ✅ built‑in | 🟡 manual install | 🟡 manual install |
Idea: Two independent encryption layers: VeraCrypt outer (for plausible deniability) and LUKS inner (for protection and data integrity).
Decoy password → ordinary files. No trace of sensitive data.
✅ Full plausible deniability.
Real password → inner LUKS containing sensitive data (keys, conversations, scripts).
⚠️ Avoid SSDs if plausible deniability is critical (TRIM).
Idea: Encrypt a partition/container with VeraCrypt, then format it directly as LUKS. Used inside Whonix RAM with full automation via a script.
Phase 1: Outer VeraCrypt
Phase 2: Open VC without mounting
/dev/mapper/veracrypt1 with no filesystem.Phase 3: Format directly as LUKS
cryptsetup on the block device.| 1 | In the storage qube: open VeraCrypt without mounting (Do not mount) ← /dev/mapper/veracrypt1 appears. |
| 2 | In the same storage qube: unlock LUKS on /dev/mapper/veracrypt1 without mounting (Unlock only) ← /dev/mapper/luks-... appears. |
| 3 | From dom0: attach the unlocked LUKS block device (Qubes Devices widget) to whonix-work-ram or any Whonix workstation. |
| 4 |
Automatically: within 10 seconds, the mount-usb.service (previously enabled via option 10 in the helper script) detects /dev/xvdi and immediately mounts it at /home/user/usb.No manual commands needed – the drive is ready to use. |
🔑 Encryption flow:
💾 USB → 🔐 VeraCrypt (password 1) → 🧅 LUKS directly (password 2) → 📁 ext4 → ✅ datamount-to-whonix.sh
You can also control the disk manually via this script (run from dom0):
~/Desktop/ram-pool-scripts/mount-to-whonix.sh/home/user/usb.umount inside the VM and then detaches the device from dom0.lsblk inside the VM and shows mount point status.Both are two layers. The difference appears after opening VeraCrypt.
noexec in Whonix noexec issue in Whonix and how the script solves it
Whonix enforces noexec on external devices, which prevents running scripts even after passing the block device.
The script create-ram-vm.sh handles this as follows:
/home/user/usb inside whonix-work-ram (an internal path not subject to noexec)./home/user/usb without hindrance, while the data remains in RAM.💡 Takeaway: The script does not bypass the noexec policy, but cleverly works around it by mounting onto an internal path, achieving both isolation and execution capability.
A normal visible partition (FAT/exFAT) occupies most of the space, and a small encrypted partition (VC+LUKS) at the end looks like unformatted space.
Example 32 GB: 30 GB visible + 2 GB encrypted
The encrypted partition has no filesystem signature, it is opened only via VeraCrypt and then LUKS applied directly.
| Feature | HDD | SSD |
|---|---|---|
| Secure erase | ✅ Easy (overwriting) | ❌ Hard (wear‑leveling) |
| TRIM | ❌ Not present | ✅ May reveal unused areas |
| Data recovery | 🟡 Possible | 🟢 Nearly impossible (after TRIM) |
| Recommended method | wipe / shred | ATA Secure Erase / Crypto Erase |
shred on an SSD! Use ATA Secure Erase or Crypto Erase instead.
Idea: A storage qube (ephemeral) fully decrypts (VC + LUKS) without any mounting, then passes the final block device to a RAM qube (whonix‑work‑ram) which mounts it and works on the data. Data resides only in RAM and disappears when the qube is shut down.
📊 Secure flow (corrected):
📦 storage‑vault (ephemeral) → 🔓 open VC ← 🧅 open LUKS (unlock only, no mount) → 🖱️ pass block device via dom0 → 🧠 whonix‑work‑ram (RAM pool) → ✅ script auto‑mounts onto internal pathVC password + LUKS password are entered only in the storage qube. Data is written to RAM and disappears on shutdown 🧹
create-ram-vm.sh and answer y — it automatically mounts to /home/user/usb.create-ram-vm.shThe script on the desktop does everything automatically:
~/Desktop/ram-pool-scripts/create-ram-vm.sh/home/user/usb directory and makes it executable (solves the noexec issue)⚠️ Pass the block device from dom0 first, then run the script.
/dev/sdb1), not the whole disk (/dev/sdb). The correct block device is the one that appears after opening LUKS in the storage qube.
storage‑vault ← GNOME Disks ← Unlock LUKS (do not mount).whonix‑work‑ram./home/user/usb.The LUKS password never leaves storage‑vault. Data is in RAM. 🛡️
cryptsetup luksHeaderBackup …Problem: You created your system disk with the default PBKDF settings and want to raise memory consumption and derivation time to protect boot. There is no direct command to modify an existing slot on the system disk.
Important note: This is the system disk – the one Qubes boots from. Any mistake will prevent booting. Make sure you have a live USB for emergency before starting.
Only method: Replace the weak password with a strong one, then delete the old one. Both steps are mandatory each time – even when changing the password later.
luksAddKey (high memory and time) → luksRemoveKey (for old) → only one password left → attacker will go for the weakest
| 0 | Before you start: It is advisable to raise dom0 RAM to get the most benefit |
|
⬆️ Via modifying | |
| 1 | Run the script from the main menu |
|
⬆️ Option 9 in the menu. It detects the disk automatically and asks for the desired memory (in GB) and time (in seconds) ⬆️ | |
| 2 | The script will be created on the desktop and run |
|
⬆️ It executes | |
⛔ Never use luksChangeKey for this purpose.
Even with --pbkdf-force-iterations, it never accepts --pbkdf-memory – it will silently revert memory to the default (1 GB).
dom0_mem in GRUB to get the most benefit.luksAddKey + luksRemoveKey automatically using the time you specify.sudo dracut -f.
Use the Helper Script in Tab 11.
luksAddKey, run luksRemoveKey. The attacker will always target the easiest.
Each card represents a real threat – with a simple explanation and mitigation
Your ISP sees every site you visit, even with HTTPS. They can sell this data or hand it over to authorities.
What are they? The adversary monitors the timing of data packets entering and leaving the Tor network, and matches the patterns to discover that the source and destination are the same person.
🎯 How it works:
📖 Real example: In 2013, the FBI arrested a Harvard student who threatened to bomb. They monitored the university network and saw that the timing of his Tor connection exactly matched the timing of the threat message.
🛡️ How to protect yourself:
A small device (Wi‑Fi Pineapple) impersonates a legitimate network and forces your device to connect to it, eavesdropping on traffic.
Protection layers:
Your phone constantly scans networks and devices around you. Google and Apple maintain a database to determine your location.
If you use a VPN that supports only IPv4, IPv6 connections may leak directly to your ISP.
If you land on a malicious entry guard, you stay stuck with it for months, and all your activities can be linked.
Sites collect information about your device: fonts, screen resolution, language. This fingerprint is unique for 99% of users.
Even with Tor, websites can use WebRTC to learn your device's real IP address.
Vulnerabilities like "Leaky Page" allow websites to steal browsing history from other tabs.
Someone gains physical access to your device in your absence and plants a hardware keylogger or copies the disk.
The adversary cools the RAM to extract encryption keys minutes after the device is powered off. This attack is impractical outside a lab.
A USB flash drive impersonates a keyboard and executes malicious commands with root privileges.
Hardware vulnerabilities that allow stealing data from memory across virtual boundaries.
A hidden management chip inside the CPU that runs even when the device is off, with full privileges.
Reconstructing what is shown on a screen or extracting keys through electromagnetic emissions.
Images carry GPS coordinates, camera type, capture date, and author name.
Zoom and Adobe add invisible watermarks. Colour printers print faint yellow dots.
WhatsApp and iCloud keep unencrypted copies of your conversations that can be handed to authorities.
Deleting files does not actually erase them. On HDDs they can be recovered easily. On SSDs, TRIM and garbage collection make recovery harder but not impossible.
Your writing style is unique and can be used to link your anonymous identities to your real identity.
The way you move your mouse, your typing speed, and how you solve CAPTCHAs are used to identify you.
The easiest way to compromise you is to trick you with fake emails or malicious links.
Any photo where you appear can be indexed and linked to you. Clearview AI has billions of images.
Even if your face is covered, you can be identified by the way you walk.
Your bank cards and passport contain chips that can be read remotely to track your movements.
Windows, macOS, and Ubuntu send diagnostic data that can reveal the presence of "suspicious" software.
Some applications use home‑grown encryption protocols that have been proven weak.
VPN services claim not to keep logs, but may be forced to log silently under a court order.
Bitcoin and Ethereum are not anonymous. Every transaction is recorded on the public blockchain.
Your biggest enemy is your mouth. Don't tell anyone – not even your closest people – about your sensitive activities or about using this guide.
If you disappear completely from the digital world, spend all your time on Tor, or have no social media accounts – you draw attention.
The biggest OPSEC mistakes happen when the same email address, the same writing style, or accidentally opening a sensitive link from a personal device occurs.
In most countries (especially the United States), anything you say to the police can be used against you. Even if you are innocent, you might say something that incriminates you.
In an emergency, the last thing you want to worry about is finding a lawyer's number. Memorise a trusted lawyer's number or keep it in a secure place.
Cases like AN0M, EncroChat, and Sky ECC have proven that "ultra‑secure" devices sold to criminals are often traps run by intelligence agencies.
From the official Qubes documentation: There are 3 types of data leaks:
The most serious threat to Qubes. Malware in an isolated VM and malware in a VM with network connectivity communicate via CPU cache. Firewalls do not prevent this. Qubes documentation confirms there are no practical complete solutions for this attack.
Note: These are the only practical defences currently available in Qubes OS against threats targeting the hard disk (forensic analysis) and the host system (dom0). Pay attention to the difference between Ephemeral VM and RAM‑VM – the former is ephemeral encryption on disk (inherited from a DVM template), the latter is a VM entirely in RAM via tmpfs.
Mechanism: Set root rw False on the DVM template ← derived qubes automatically inherit ephemeral encryption on disk.
Activation command:
qvm-volume config DVM_TEMPLATE:root rw False
Mitigates:
Mechanism: VM entirely in RAM via RAM pool + tmpfs.
Activation command:
qvm-create -P ram_pool -t TEMPLATE VMNAME
🔑 Note: All disposable VMs derived from a RAM‑based VM also run in RAM automatically.
Mitigates:
luksSuspend) or full shutdown + RAM‑wipe.
Mechanism: dom0 is mounted read‑only. All changes to dom0 are written to RAM and disappear on shutdown.
Activation command: Add rootovl to GRUB_CMDLINE_LINUX in /etc/default/grub.
Mitigates:
root rw False from a DVM template) protects against disk forensic analysis – fastest for daily sessions. RAM‑VM (via RAM pool) provides maximum data protection – no trace on disk, and all disposable VMs derived from it run in RAM automatically. To protect against Cold Boot: use BusKill (luksSuspend) or full shutdown + RAM‑wipe – the attack targets RAM itself. Live Mode (via rootovl in GRUB) protects the host system itself from tampering and persistence attacks. Together, they form a complete shield against persistence attacks and forensics. However, they do not protect against cooperative covert channels, which represent a limit of Qubes' isolation.
| 🛡️ Technical | 🟡 Network → sys‑whonix (Tab 2) 🔴 Tracking → Kicksecure (Tab 2) 🔴 Physical → Live Mode + encryption 🔵 Data → ExifTool (Tab 3) 🔴 Tor → Multiple gateways (Tab 2) 🔴 CPU → Kernel parameters (Tab 0) 🟡 Financial → Monero only 🧅 IP → Whonix Gateway (Tab 2) 📶 WiFi → WISP+Ethernet (Tab 1) |
| 🧠 Behavioural | 🔴 Talking → Absolute silence 🟡 Lifestyle → Be normal 🔴 Mixing → Complete identity isolation 🔴 Legal → Don't talk to police 🔵 Emergency → Keep a lawyer's number 🔴 Traps → No ready‑made solutions ✍️ Writing → Change style |
| 🚨 Emergency | 🔥 Burned → Delete‑and‑escape plan 🔍 Tampering → Immediate leave plan 💀 Destruction → BusKill 🧹 Forensics → Ephemeral VM (DVM inheritance) ❄️ Cold Boot → BusKill / Shutdown + RAM‑wipe ⚡ Host → Live Mode (rootovl) |
| 💰 Funding | 💵 Cash → iVPN 🪙 Monero → Mullvad / Njalla 🔄 Exchange → sideshift.ai / bisq ❌ Avoid → Bitcoin/Ethereum |
The tmpfs technique makes sensitive directories (logs, cache, user files) reside only in RAM and vanish completely on reboot. This provides an extra layer of privacy and security.
/var/log – system logs (512 MB)/var/cache – cache (512 MB)/var/tmp – temporary files (256 MB)/tmp – temporary files (512 MB)/home/user – user home directory (2 GB)default-dvm makes it inherited by all disposable VMs (DVMs).whonix-workstation-18) only affects that template and is not inherited by derived qubes.
To apply or remove Amnesic Tmpfs, go to
Tab 11 (Helper Script)
and run auto-script.sh.
chmod +x auto-script.sh && ./auto-script.sh
Then choose: 6) Amnesic Tmpfs
You will see a menu:
Idea from the Hitchhiker's Guide: Like Tails, but inside Qubes. Qubes that run in RAM disappear completely on shutdown.
You are working on a highly sensitive document. Suddenly, you hear a knock on the door. You hit shutdown. After 60 seconds, even if the device is seized, there is no way to recover what you were working on. "No data = no crime".
This technique allows you to create a file pool on a tmpfs filesystem, then create AppVMs on that pool so that the qube’s volumes reside in memory.
| Type | Command | Notes |
|---|---|---|
| Permanent change (edit GRUB) | sudo nano /etc/default/grubsudo grub2-mkconfig -o /boot/grub2/grub.cfg |
Find the line GRUB_CMDLINE_XEN_DEFAULT and change the dom0_mem value, e.g., dom0_mem=max:8192M |
To avoid duplication, all the full scripts are available in Tab 11 (Helper Script)
chmod +x auto-script.sh && ./auto-script.sh
Then choose: 5) Create RAM Pool scripts
setup-ram-pool.sh – creates the RAM pool in memorycreate-ram-vm.sh – creates a single qube (with full kernel parameters + DVM template)create-multi-ram-vm.sh – creates several qubes with gateway selection (with full kernel parameters + save capability)cleanup-ram-pool.sh – deletes all qubes and frees the RAM poolram-pool-status.sh – shows RAM pool status/home/user/Desktop/ram-pool-scripts/
To delete all qubes and free the RAM pool manually:
#!/bin/bash
RAM_POOL_PATH="$HOME/ram_pool"
POOL_NAME="ram_pool"
echo "🧹 Cleaning RAM Pool..."
# Delete qubes
for vm in $(qvm-ls --raw-list 2>/dev/null | grep -E "whonix-work-ram|ram-"); do
echo " Deleting: $vm"
qvm-kill "$vm" 2>/dev/null
qvm-remove --force "$vm" 2>/dev/null
done
# Remove pool
qvm-pool remove "$POOL_NAME" 2>/dev/null
# Unmount
mount | grep -q "$RAM_POOL_PATH" && sudo umount "$RAM_POOL_PATH" 2>/dev/null
# Delete folder
[ -d "$RAM_POOL_PATH" ] && sudo rm -rf "$RAM_POOL_PATH"
echo "✅ Cleanup done"
free -h
| Problem | Cause | Solution |
|---|---|---|
qvm-create fails with "No space left" |
RAM pool is full or too small | df -h /home/$USER/ram_pool then sudo mount -o remount,size=8G /home/$USER/ram_pool |
| Data does not disappear after reboot (for an AppVM) | The pool is not mounted as tmpfs | Make sure mount | grep ram_pool shows tmpfs |
| Kernel parameters not applied | The updated script was not used | Make sure you use the updated version of the helper script (includes KERNEL_OPTS) |
ephemeral_volatile does not work |
The property requires a pool restart or a new VM | After enabling the property, create a new VM; old VMs are not affected |
Review these measures regularly – security is an ongoing process, not a one‑time setup
Do not trust any unknown USB. Any USB can be programmed to act as a keyboard and execute root commands.
Basic protection: Qubes automatically isolates USB in sys-usb (a VM separate from dom0). This prevents any malicious USB device from directly accessing dom0.
If booting from USB: install usbguard in dom0:
sudo qubes-dom0-update usbguard
sudo systemctl enable usbguard
sudo usbguard generate-policy > /etc/usbguard/rules.conf
The warning "USB in dom0 is not restricted" is normal when booting from USB.
Threat: Your ISP can see that you are using Tor, even if it cannot see the content of your connection.
Paid solution: Use a trusted VPN before Tor. Path: sys-net → sys-vpn → sys-whonix → anon-whonix. Create a VPN qube and make it the netvm of sys-whonix.
Free alternative: Use Tor + bridges instead of VPN. Bridges hide the fact you are using Tor from your ISP at no cost. See Tab 2 (Whonix) for the bridges section.
Threat: Neglecting updates leaves known vulnerabilities open to exploitation. Apply updates as soon as they appear.
Basic updates:
sudo qubes-dom0-updatesudo apt update && sudo apt upgrade (inside each template)Updates over Tor: Qubes uses UpdatesProxy which automatically routes template updates through sys-whonix.
sudo apt install debsums && sudo debsums -c to verify package integrity.
Threat: Your disk may fail or be stolen. Without an off‑device backup, you may lose all your data.
Basic solution (Qubes Backup):
qubes-backup --encrypt --passphrase-file /path/to/key /mnt/backup
External alternatives:
Threat: Any other operating system (e.g., Windows) can modify your unencrypted /boot and install a bootkit or replace the kernel with a malicious file.
Official sources: Qubes and Kicksecure documentation warn against dual boot because /boot is unencrypted and can be tampered with.
From the Qubes forum: "A bootkit can install itself without the user doing anything wrong."
Threat: Even if you "pass through" a USB to another VM, dom0 must run USB drivers and read Vendor/Product IDs and the partition table.
What can a malicious USB do?
Your situation (booting from USB): The external drive is trusted, but any other USB is a risk. Complete USB isolation is not possible when booting from USB.
lspci | grep USB.
① Evil Maid:
Someone gains physical access to your device in your absence and can install a keylogger, modify the bootloader, or implant firmware‑level malware.
Protection: BIOS password + Secure Boot + Anti Evil Maid (AEM) – requires TPM 1.2 and Intel TXT + Live Mode (OverlayFS) – dom0 in RAM, all changes disappear after power‑off.
⚠️ Note: Live Mode does not protect against UEFI/BIOS tampering itself, and using USB for security involves a trade‑off.
② Cold Boot:
The attacker cools the RAM to extract encryption keys after the device is powered off. Data can be recovered for up to 90 minutes with cooling.
What protects:
luksSuspend erases LUKS keys from RAMWhat does NOT protect:
③ Mode comparison:
| Mode | RAM | Speed | Cold Boot | Evil Maid |
|---|---|---|---|---|
| Sleep | 🔴 Active | 🟢 seconds | ❌ Weak | ❌ |
| Hibernate | 🟢 Dumped | 🟡 10‑30 s | ✅ | 🟡 Partial (image recoverable) |
| Shutdown | 🟢 Cleared | 🔴 30‑60+ s | 🟡 Reduces risk | ✅ |
Scripts available in Tab 11 (choose 8) Emergency Tools):
Type: 💀 Complete destruction with a timer
Mechanism: Executes automatically after a timer (you specify the minutes).
Ideal for: Arrest or coercion – if you do not cancel within the timeout, everything is automatically destroyed.
To cancel: sudo /usr/local/bin/cancel before the timeout.
Location: ~/Desktop/emergency/deadman-switch.sh
Type: 💀 Complete destruction immediate
Mechanism: Executes immediately when run (keyboard shortcut).
Ideal for: Sudden raid or immediate danger requiring fast destruction.
Note: Cannot be cancelled after execution.
Location: ~/Desktop/buskill/buskill-selfdestruct.sh
qvm-kill --allEFI/qubes folder from the EFI partitionddluksSuspend for all devices⚠️ Result: Complete irreversible destruction. Even a LUKS header backup alone will not suffice – you also need to restore /boot and EFI/qubes. A full reinstall may be the easiest recovery path.
Type: 🗑️ Delete the service only
Not related to destruction. Its only function is to delete the systemd service of the Dead Man's Switch.
deadman.serviceNote: Does not delete BusKill itself (/etc/qubes-rpc/buskill.selfDestruct), only removes the timer.
Location: ~/Desktop/emergency/uninstall-deadman.sh
📊 Quick comparison:
| Criteria | ① Dead Man's | ② BusKill | ③ Uninstall |
|---|---|---|---|
| Type | 💀 Delayed destruction | 💀 Immediate destruction | 🗑️ Remove service |
| Timing | After timer | Immediate | — |
| Destruction mechanism | Full | Full | None |
| Deletes EFI/qubes | ✅ | ✅ | ❌ |
| Destroys /boot | ✅ | ✅ | ❌ |
| Kills VMs | ✅ | ✅ | ❌ |
| Overwrites LUKS header | ✅ all disks | ✅ all disks | ❌ |
| luksSuspend | ✅ all devices | ✅ all devices | ❌ |
| Wipes Qubes | ✅ | ✅ | ❌ |
| Cancellable | ✅ before timeout | ❌ | — |
| Recoverability | ⚠️ need /boot + EFI | ⚠️ need /boot + EFI | — |
| Best for | Arrest / coercion | Raid / immediate danger | System cleanup |
① If you are burned (identity exposed):
You have time: Delete the identity from the internet, destroy the SIM and phone (destroy physically and discard), wipe the disk or physically destroy it, keep a lawyer's number, act as if nothing happened.
No time (urgent): Immediately shut down / disconnect power. RAM will fade and data becomes unreadable. Remain silent – don't speak without a lawyer.
② If you discover tampering:
On the laptop: Do not touch the planting – do not use the device. Remove the disk (if encrypted) and leave immediately.
In the room: Assume everything is planted (cameras, recorders). Touch nothing – leave immediately and quietly. Never return.
③ Trick to detect inspection: Take a photo of your desk before leaving and compare when you return. A thin thread or a piece of wax on the drawer.
④ Data recovery: qubes-backup --restore on a fresh, clean device.
① Basics: Strong, unique passwords for every service. Update the system weekly. Use disposable VMs to open suspicious files.
② Network & browsing: Isolate USB in sys‑usb (default in Qubes). Disable JavaScript or use NoScript. Use the Tor Browser inside Whonix for sensitive activities.
③ Storage & encryption: LUKS2 with integrity for sensitive data. RAM pool for ephemeral operation (no disk trace). Kernel parameters: init_on_free=1, init_on_alloc=1.
① Boot protection: Strong BIOS/UEFI password – prevents changing boot settings. Enable Secure Boot. Use TPM to securely store encryption keys.
② Advanced tools: Anti Evil Maid (AEM) – available for Qubes (Intel CPUs). Heads firmware – open‑source BIOS replacement with full TPM verification – stronger than Secure Boot.
③ Specialised hardware: NitroPad (Nitrokey) – laptop with built‑in Heads. Purism Librem – laptop with physical kill switches.
④ While travelling: Do not leave your device unattended. Use a Faraday cage to block wireless communication. Check for strange devices when you return.
Use the Helper Script in Tab 11 to apply Kicksecure settings automatically to all templates.
chmod +x auto-script.sh && ./auto-script.sh
Then choose: 3) Apply Kicksecure settings
Kicksecure is a complete operating system based on Debian, designed from the ground up to be highly hardened.
It is not just "Debian with some extra settings", but a comprehensive security package that applies a multi‑layer security model, starting from the hardened kernel, through user permissions, and all the way to applications and networking.
Result: A system ready for daily use, resistant to viruses and attacks, providing proactive protection instead of reactive defence.
In Qubes OS, Kicksecure acts as a Template to automatically give all your AppVMs its hardening features.
sys-whonix to get Whonix + Kicksecure together.qvm-create --template=kicksecure anon-work && qvm-prefs anon-work netvm sys-whonixIn Qubes OS, all templates share the dom0 kernel and cannot use their own kernel. Therefore, you cannot install a hardened‑kernel inside the template.
Instead, kernel hardening is applied via:
Kicksecure does not rely on a single protection layer, but applies a multi‑layer defence model that includes:
KSPP settings, disable dangerous features, randomise structure layout.
Strict separation of accounts, protects against brute‑force attacks.
Strict firewall, secure time synchronisation.
Unify browser and system information to make your fingerprint statistically indistinguishable from other users.
Full disk encryption, memory scrubbing on shutdown.
Strict digital signature verification at every stage of development.
Tor hides only your IP address. But websites can identify you through browser fingerprinting: fonts, screen resolution, settings… Kicksecure unifies this information among all its users, making your fingerprint indistinguishable.
Step 1: Install the template from the community repositories:
qvm-template --enablerepo qubes-templates-community install kicksecure-18
Step 2: Install the official time‑management add‑on:
sudo qubes-dom0-update --action=install qubes-core-admin-addon-kicksecure
Step 3: Update the template regularly:
sudo qubes-dom0-update --action=update kicksecure
What is tirdad? An old tool that aimed to randomise TCP Initial Sequence Numbers (TCP ISN Randomisation).
Modern kernel parameters in this guide (e.g., init_on_free=1, init_on_alloc=1, page_alloc.shuffle=1) provide more comprehensive and deeper protection against the same types of attacks that tirdad tried to mitigate.
| Feature | 🛡️ Kicksecure | 🧅 Whonix Workstation |
|---|---|---|
| Primary goal | Security hardening | Anonymity |
| Relationship between them | Whonix is built on Kicksecure ← inherits all its security improvements | |
| Network routing | ❌ No mandatory routing | ✅ Mandatory routing through Tor |
| System architecture | Single OS (monolithic) | Two components: Gateway + Workstation |
sudo systemctl mask bluetooth.service cups.service avahi-daemon.service ModemManager.service
echo "blacklist pcspkr" | sudo tee -a /etc/modprobe.d/blacklist.conf
echo "blacklist thunderbolt" | sudo tee -a /etc/modprobe.d/blacklist.conf
echo "* hard core 0" | sudo tee -a /etc/security/limits.conf
echo "kernel.core_pattern=|/bin/false" | sudo tee -a /etc/sysctl.d/99-core.conf
echo "vm.swappiness=10" | sudo tee -a /etc/sysctl.d/99-swappiness.conf
Advanced security package from Kicksecure – requires separate installation
sudo apt update && sudo apt install security-misc
This feature hides sensitive information from /sys and /proc, hardware details (lspci, /proc/cpuinfo), and unique device identifiers.
sudo systemctl enable hide-hardware-info.service && sudo reboot
The hidepid feature restricts non‑root users from viewing process information.
sudo systemctl enable proc-hidepid.service && sudo systemctl start proc-hidepid.service
Expected result: Your fingerprint is indistinguishable within the largest possible group of users.
sys-usb, sys-net, sys-firewall, and all disposable VMs are derived from default-dvm. Changing their template to Kicksecure will harden all core system components at once.
All these steps are automated in the Helper Script in Tab 11.
default-dvm is automatically reflected in everything derived from it.
passwordless‑sudo, which is necessary for this qube to function. Keep it on the official template.sys-usb, sys-net, and sys-firewall.default-dvm.Comprehensive script to set up a Live Mode environment with memory scrubbing and ephemeral DVMs
Use the Helper Script in Tab 11 which automatically runs Live Mode (option 7). The full script is located there.
chmod +x auto-script.sh && ./auto-script.sh
💡 Note: The full live.sh code is inside the helper script in Tab 11, not here – to avoid duplication and simplify maintenance.
This is the complete shield. dom0 runs in RAM, and sensitive data also runs in RAM. No digital evidence remains after shutdown.
| Scenario | dom0 | VM data | Protection level | Requirements |
|---|---|---|---|---|
| Live Mode only | ✅ in RAM | ⚠️ on disk | 🟢 Good | 16 GB RAM |
| RAM‑VM only | ⚠️ on disk | ✅ in RAM | 🟢 Good | depends on VM size |
| Live + RAM‑VM | ✅ in RAM | ✅ in RAM | 🔴 Maximum | 32 GB+ RAM |
When combined: no trace of dom0 nor of sensitive VM data remains on disk. Once the device is shut down, there is no digital evidence of any activity. This is what makes the system fully amnesic.
For daily use: Live Mode alone + Ephemeral VMs is sufficient for most users. For highly sensitive sessions: combining with RAM‑VM is the strongest choice.
This is the only reference comparing all types. See the referenced tabs for full details.
| Feature / Type | Ordinary DVM | Ephemeral DVM | Amnesic Tmpfs | VM in RAM Pool | Dom0 Live Boot | Live + RAM‑VM |
|---|---|---|---|---|---|---|
| Primary storage location | Hard disk | Hard disk (with ephemeral encryption) | RAM (tmpfs) | RAM (ram_pool) all volumes in RAM | RAM (full copy) | RAM (dom0 + VMs) |
Location of /home/user |
Hard disk (deleted) | Hard disk (not RAM) | RAM (tmpfs) | RAM | RAM | RAM |
Writes to / (root) |
N/A | Diverted to volatile volume (on disk) | Written to RAM | Written to RAM | Written to RAM | Written to RAM |
ephemeral property |
❌ | ✅ enabled random‑key ephemeral encryption | ❌ | ❌ (not needed) | N/A | ✅ full |
| RAM wipe | ❌ | ❌ (data on disk) | ❌ | ❌ (only freed) | ✅ yes | ✅ yes |
| Boot speed | 🟢 fast | 🟢 fast | 🟡 moderate | 🔴 slower | 🔴 slower | 🔴 slowest |
| Ideal use | open a file once | work session + ephemeral encryption | make specific directories disappear | max isolation – whole qube in RAM | emergency session – dom0 in RAM | maximum protection – no trace at all |
| Reference tab | – | Tab 9 | Tab 5 | Tab 6 | Tab 9 | 9 + 6 |
/home/user) remains on the hard disk.| Criteria | 🧠 Amnesic Tmpfs (Tab 5) | 💾 RAM Pool (Tab 6) |
|---|---|---|
| What becomes ephemeral? | Specific directories inside an existing VM | The entire VM (system and storage volumes) |
| Where is it applied? | Inside any existing AppVM | At dom0 level (when creating a new VM) |
| Isolation level | 🟡 Partial – only specific directories in RAM | 🟢 Full – entire VM in RAM |
| Best for | Cleaning logs and cache after each session | Very sensitive work sessions, a Tails‑like environment inside Qubes |
/home/user). This provides an extra layer of isolation and privacy, and can easily be carried on a USB flash drive.
torsocks is installed: sudo apt install torsocks -yAn accurate, up‑to‑date comparison of the most important messaging and sharing applications in terms of security and privacy, aligned with the official Whonix page evaluations (2025):
| Criteria | 🔷 SimpleX | 🔴 Cwtch | 🟢 OnionShare | 💬 Gajim | 📘 Element | 💬 XMPP | 🟣 Session | 🟠 Signal | 🟢 Briar | ✈️ Telegram |
|---|---|---|---|---|---|---|---|---|---|---|
| 🔹 Basic features | ||||||||||
| Primary purpose | Messaging | Messaging | File sharing + chat | Messaging | Messaging | Messaging | Messaging | Messaging | Messaging | Messaging |
| Network model | Decentralised (SMP servers) | Peer‑to‑peer (P2P) over Tor v3 | Peer‑to‑peer (P2P) over Tor | Federated (XMPP servers) | Federated (Matrix servers) | Federated (XMPP servers) | Decentralised (Oxen network) | Centralised (Signal servers) | Peer‑to‑peer over Tor/Bluetooth | Centralised (Telegram servers) |
| Encryption protocol | Simplex Messaging Protocol (SMP) | Tapir Protocol | Tor onion protocol | OMEMO | Olm/Megolm | OMEMO | Oxen Protocol | Signal Protocol | Briar Protocol | MTProto |
| E2EE by default | ✅ full (default) | ✅ full (default) | ✅ full (default) | 🟡 OMEMO (manual enable) | 🟡 optional | 🟡 OMEMO (optional) | ✅ full | ✅ full | ✅ full | 🟡 partial (Secret Chats only) |
| 🔹 Security features (according to the official Whonix page) | ||||||||||
| Serverless / Peer‑to‑peer | ✅ single‑hop (server does not store) | ✅ client ← onion | ✅ client ← onion (device is the server) | ❌ no | ❌ no | ❌ no | ❌ no | ❌ no | ✅ client ← onion | ❌ no |
| No onion hosting risks | ✅ yes | ✅ yes | ❌ no (device is the server) | ✅ yes | ✅ yes | ✅ yes | ✅ yes | ✅ yes | ❌ no | ✅ yes |
| E2EE by default | ✅ yes | ✅ yes | ✅ yes | ❌ no | ❌ no | ❌ no | ✅ yes | ✅ yes | ✅ yes | ❌ no |
| Quantum resistance | ✅ yes | ❌ no | ❌ no | ❌ no | ❌ no | ❌ no | ❌ no | ❌ no | ❌ no | ❌ no |
| Verifiability | ✅ (SMP) | ✅ (Tapir) | ✅ (Onion E2EE) | ✅ (OMEMO) | ⚠️ weak | ✅ (OMEMO) | ⚠️ unaudited | ✅ | ✅ | ❌ weak |
| Plausible deniability | ✅ (SMP) | ✅ (Tapir) | ❌ no | ✅ (OMEMO) | ❌ no | ✅ (OMEMO) | ❌ no | ❌ no | ❌ no | ❌ no |
| Message padding | ✅ yes | ✅ yes | ❓ unknown | ✅ (OMEMO) | ❓ unknown | ✅ (OMEMO) | ❓ unknown | ❓ unknown | ❓ unknown | ❌ no |
| 🔹 Additional security features | ||||||||||
| Perfect Forward Secrecy | ✅ (SMP) | ✅ (Tor) | ✅ (Tor) | ✅ (OMEMO) | ❌ weak | ✅ (OMEMO) | ⚠️ (v2 Dec 2025) | ✅ | ✅ | 🟡 partial |
| Metadata resistance | 🟢 excellent (ephemeral IDs) | 🟢 excellent | 🟢 excellent | 🔴 weak | 🔴 weak | 🔴 weak | 🟡 good | 🔴 weak (phone number) | 🟢 excellent | 🔴 very weak |
| 🔹 Technical features | ||||||||||
| Open source | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | 🟡 client only |
| Memory‑safe language | ✅ (Haskell, Kotlin) | ✅ (Go, Rust) | ✅ (Python) | ✅ (Python) | 🟡 partial | 🟡 partial | 🟡 partial | 🟡 partial | 🟡 partial | 🟡 partial |
| Independent security audit | ✅ (Trail of Bits: 2022, 2024) | ❌ not yet | ✅ (Radically Open Security: 2021) | 🟡 protocol only (2016) | 🟡 protocol only | 🟡 protocol only (2016) | ❌ not yet | ✅ | ❌ unknown | ❌ unknown |
| Signed releases | ✅ | ❓ unknown | ✅ | ❓ unknown | ❓ unknown | ❓ unknown | ❓ unknown | ✅ | ❓ unknown | ❓ unknown |
| Reproducible builds | ✅ | ✅ | ❌ no | ✅ | ❓ unknown | ✅ | ❓ unknown | ❓ unknown | ❓ unknown | ❌ no |
| 🔹 Usability features | ||||||||||
| Requires phone number | ❌ no | ❌ no | ❌ no | ❌ no | ❌ no | ❌ no | ❌ no | ✅ required | ❌ no | ✅ required |
| Requires simultaneous connection | ❌ | ✅ (for private chats) | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ (mailbox) | ❌ |
| File size limits | unlimited | unlimited | ✅ unlimited | depends on server | depends on server | depends on server | unlimited | 100 MB | unlimited | 2 GB |
| Works offline (local network) | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ (Bluetooth/Wi‑Fi) | ❌ |
| iOS support | ✅ | ❌ (Android/Linux only) | ❌ (desktop only) | ❌ (Linux only) | ✅ | ✅ | ✅ | ✅ | ❌ (Android only) | ✅ |
| 🔹 Final recommendation | ||||||||||
| Security recommendation in Whonix | ⭐ best choice for anonymous messaging | ⭐ excellent for maximum privacy (no audit yet) | ⭐ for secure sharing and temporary chat | ⚠️ for experts (depends on server) | ⚠️ use with caution (forward secrecy issues) | ⚠️ use with caution (depends on server) | ❌ not recommended (security problems) | ❌ not recommended (mandatory phone number) | ❌ not recommended (Tor‑over‑Tor) | ❌ not recommended for any privacy purpose |
❌ Apps not recommended in Whonix: Signal (mandatory phone number), Session (security problems and lack of PFS), Briar (Tor‑over‑Tor conflict), Telegram (partial encryption and centralised servers), Matrix/Element (forward secrecy problems and metadata leakage).
You ← → [central server] ← → your friend⚠️ The server sees who you talk to and when
You ← [your server] ← → [friend's server] → friend⚠️ Both servers know who you talk to
You ← ← [Tor network] → → your friend✅ No central server watching
You → [channel 1] → [server] → [channel 2] → your friend✅ The server does not know both parties together; ephemeral IDs
No single app does everything. The optimal strategy is to use the right tool for each task:
SimpleX Chat is a decentralised, secure messaging application that does not rely on user identifiers and provides the highest level of privacy. Tested and works excellently in Whonix and Qubes OS without complex configuration.
This is the only method that enables circuit isolation and makes the .onion addresses option appear inside SimpleX. Without it, your connection still goes through Tor but lacks these advanced privacy features. Whonix provides a SOCKS proxy automatically on port 9050.
127.0.0.1 or localhost9050IsolateSOCKSAuth feature in Tor)To access these: enable Developer tools from Settings → About, then go back to Network & Servers → Advanced network settings.
App session: No isolation. All your profiles share the same app session and Tor circuit (higher performance, less privacy).Chat profile (recommended): Gives each profile its own independent Tor circuit, preventing servers and network observers from seeing that traffic comes from the same device.Servers (experimental BETA): Gives each server (contact/group) its own independent Tor circuit. ⚠️ Not recommended for large groups or many contacts because it may create heavy traffic and exceed TCP socket limits; also, creating many separate Tor circuits can be very slow.Unprotected (Tor already hides your IP).When IP hidden to ensure IP is always hidden while allowing connection to older servers.This script makes SimpleX data portable in a folder next to the app. Note: After launching, manually enable SOCKS Proxy from inside the app according to the steps above to benefit from circuit isolation and .onion addresses.
cat > run-simplex.sh << 'EOF'
#!/bin/bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PORTABLE_DIR="${SCRIPT_DIR}/simplex-chat-data"
APP_DIR="${SCRIPT_DIR}/simplex" # subfolder for binaries
mkdir -p "${APP_DIR}"
# 1️⃣ First: look for AppImage in subfolder or top level
APPIMAGE_PATH=$(ls "${APP_DIR}"/simplex*.AppImage 2>/dev/null | head -1)
if [[ -z "${APPIMAGE_PATH}" ]]; then
APPIMAGE_PATH=$(ls "${SCRIPT_DIR}"/simplex*.AppImage 2>/dev/null | head -1)
fi
# 2️⃣ If not found, check for wget then download
if [[ -z "${APPIMAGE_PATH}" ]]; then
echo "📥 SimpleX not found. Need to download."
if ! command -v wget &> /dev/null; then
echo "⚠️ wget not found. Installing now..."
sudo apt update && sudo apt install -y wget
echo "✅ wget installed"
fi
echo "📥 Downloading SimpleX..."
wget -c "https://github.com/simplex-chat/simplex-chat/releases/latest/download/simplex-desktop-x86_64.AppImage" \
-O "${APP_DIR}/simplex-desktop-x86_64.AppImage"
APPIMAGE_PATH="${APP_DIR}/simplex-desktop-x86_64.AppImage"
echo "✅ Downloaded"
fi
# 3️⃣ Final check that file exists
if [[ ! -f "${APPIMAGE_PATH}" ]]; then
echo "❌ Error: AppImage file not found after download."
exit 1
fi
mkdir -p "${PORTABLE_DIR}"
chmod +x "${APPIMAGE_PATH}"
export XDG_CONFIG_HOME="${PORTABLE_DIR}/.config"
export XDG_DATA_HOME="${PORTABLE_DIR}/.local/share"
export XDG_CACHE_HOME="${PORTABLE_DIR}/.cache"
export HOME="${PORTABLE_DIR}"
echo "🚀 Launching SimpleX Chat..."
echo "💡 Reminder: Enable SOCKS Proxy from Settings → Network & Servers → 127.0.0.1:9050"
exec "${APPIMAGE_PATH}" "$@"
EOF
chmod +x run-simplex.sh
Use .onion hosts option appears and works effectively, allowing you to restrict connections to the Tor network only (by choosing Required), thus avoiding malicious exit node attacks.Do not use sudo directly. Replace it with:
qvm-run -u root anon-whonix '<command>'
Example: qvm-run -u root anon-whonix 'whonix_firewall'
Cwtch (pronounced /kʊtʃ/) is a decentralized, metadata-resistant messaging protocol built on Tor v3 Onion Services. Developed by the Open Privacy Research Society (Canada).
cwtch/ ├── cwtch # launch script ├── lib/ │ ├── cwtch # executable │ └── libcwtch.so ├── Tor/ ├── install.sh ├── install-home.sh ├── install-sys.sh ├── install-whonix.sh └── cwtch-whonix.yml
cat > run-cwtch-portable.sh << 'EOF'
#!/bin/sh
SCRIPT=$(realpath "$0")
SCRIPTPATH=$(dirname "$SCRIPT")
cd "$SCRIPTPATH"
export LD_LIBRARY_PATH="$LD_LIBRARY_PATH:$SCRIPTPATH/lib"
export CWTCH_HOME="${SCRIPTPATH}/cwtch-data"
export CWTCH_RESTRICT_PORTS=true
export CWTCH_BIND_EXTERNAL_WHONIX=true
exec ./lib/cwtch
EOF
chmod +x run-cwtch-portable.sh
1. On Whonix-Gateway:
sudo onion-grater-add 40_cwtch
2. On Whonix-Workstation:
sudo mkdir -p /etc/whonix_firewall.d/ echo 'EXTERNAL_OPEN_PORTS+=" $(seq 15000 15378) "' | sudo tee -a /etc/whonix_firewall.d/50_user.conf
Then reload the firewall:
sudo whonix_firewall
3. Inside the Cwtch app (after first launch):
90509051📌 Why? Without entering these ports, Cwtch tries to run its own built-in Tor inside the Workstation (which won't work). By entering them, you tell it to use the Gateway's Tor directly.
⚠️ Without these three steps, Cwtch will not be able to connect to the Tor network.
OnionShare is an open-source tool from the Freedom of the Press Foundation that allows secure and anonymous file sharing or receiving over the Tor network. Files travel directly from your device to the recipient's device without passing through any intermediary server. It also supports website hosting and anonymous chat.
.onion websites.onion link# Open terminal as root and run sudo onion-grater-add 40_onionshare # ⚠️ Critical step: Make sure to decompress the file if it's compressed sudo gunzip /usr/local/etc/onion-grater-merger.d/40_onionshare.yml.gz # To verify success ls /usr/local/etc/onion-grater-merger.d/ # you should see 40_onionshare.yml without .gz
# Open required ports in the firewall sudo mkdir -p /usr/local/etc/whonix_firewall.d echo 'EXTERNAL_OPEN_PORTS+=" $(seq 17600 17659) "' | sudo tee -a /usr/local/etc/whonix_firewall.d/50_user.conf sudo whonix_firewall # Run OnionShare onionshare
| Criterion | 🧅 OnionShare | 📱 Signal | ☁️ Google Drive |
|---|---|---|---|
| File storage location | ✅ Your device only | 🟡 Signal servers (temporarily) | ❌ Google servers |
| File size limit | ✅ Unlimited | 🟡 100MB | ✅ Very large |
| Data logging | ✅ None | 🟡 Minimal | ❌ Logs everything |
| Anonymous chat | ✅ Supported | 🟡 Messaging only | ❌ Not supported |
| Recommendation for journalism | ⭐ Ideal for large files | 🟡 Good for quick messaging | ❌ Not secure |
XMPP is an open, decentralized instant messaging protocol. It supports strong end-to-end encryption via OMEMO and does not require a phone number to register. Choose a trusted server like conversations.im.
🔒 Privacy: Message content is fully encrypted. However, metadata protection is weak, as who talks to whom (social graph) can be monitored.
Gajim requires no extra Gateway setup – it works directly through system Tor. Proxy settings must be configured manually.
sudo apt update sudo apt install gajim gajim-httpupload -y
cat > run-gajim-portable.sh << 'EOF'
#!/bin/bash
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
PORTABLE_DIR="${SCRIPT_DIR}/gajim-data"
mkdir -p "$PORTABLE_DIR"
CONFIG_FILE="${PORTABLE_DIR}/config"
if [ ! -f "$CONFIG_FILE" ]; then
echo "[global]" > "$CONFIG_FILE"
echo "use_keyring = False" >> "$CONFIG_FILE"
fi
if ! command -v gajim &> /dev/null; then
echo "❌ Gajim not installed"
exit 1
fi
gajim -c "$PORTABLE_DIR" &
EOF
chmod +x run-gajim-portable.sh
Matrix is an open, decentralized communication protocol based on a "server-to-server" (Federation) model. According to the Whonix official page, it has serious security concerns:
Per Whonix recommendation, use extrepo to safely add the Element repository:
# Whonix recommended method sudo extrepo enable element.io sudo apt update && sudo apt full-upgrade sudo apt install --no-install-recommends element-desktop
This script makes Element portable, storing all settings, conversations, and encryption keys inside the Element-Data folder on the flash drive.
cat > run-element-portable.sh << 'EOF'
#!/bin/bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
PORTABLE_DATA_DIR="${SCRIPT_DIR}/Element-Data"
mkdir -p "${PORTABLE_DATA_DIR}"
if ! command -v element-desktop &> /dev/null; then
echo "❌ Element not installed"
exit 1
fi
element-desktop --profile-dir="${PORTABLE_DATA_DIR}" &
EOF
chmod +x run-element-portable.sh
This method installs Signal directly into the Whonix Template, making it available to all AppVMs derived from it.
# 0. Install required dependencies sudo apt update > /dev/null 2>&1 sudo apt install wget gnupg -y > /dev/null 2>&1 # 1. Install the official signing key wget -O- https://updates.signal.org/desktop/apt/keys.asc 2> /dev/null | gpg --dearmor > signal-desktop-keyring.gpg cat signal-desktop-keyring.gpg | sudo tee /usr/share/keyrings/signal-desktop-keyring.gpg > /dev/null # 2. Add the official repository to your sources list wget -O signal-desktop.sources https://updates.signal.org/static/desktop/apt/signal-desktop.sources 2> /dev/null cat signal-desktop.sources | sudo tee /etc/apt/sources.list.d/signal-desktop.sources > /dev/null # 3. Update the database and install Signal sudo apt update > /dev/null 2>&1 sudo apt install signal-desktop -y
✅ After installation, re-lock the network for the template. To run it in an AppVM: signal-desktop.
This method does not touch the template at all and does not require enabling any network (the AppVM's network is already open via Tor). The AppImage file is downloaded and run directly inside the isolated AppVM.
cat > run-signal.sh << 'EOF'
#!/bin/bash
set -e
cd "$(dirname "$0")"
SCRIPT_DIR="$(pwd)"
# Search for the AppImage
APPIMAGE=$(find "$SCRIPT_DIR" -maxdepth 1 -iname "signal*.AppImage" -type f 2>/dev/null | head -1)
# If not found: install wget (if needed) then download the official stable release
if [ -z "$APPIMAGE" ]; then
command -v wget &>/dev/null || { echo "⚠️ Installing wget..."; sudo apt update && sudo apt install -y wget; }
echo "📥 Downloading Signal AppImage (stable release)..."
wget -qc --show-progress -O "$SCRIPT_DIR/signal-desktop.AppImage" "https://updates.signal.org/desktop/signal-desktop.AppImage"
APPIMAGE="$SCRIPT_DIR/signal-desktop.AppImage"
else
APPIMAGE="$(basename "$APPIMAGE")"
fi
# Confirm existence + set execute permissions
[ -f "$SCRIPT_DIR/$APPIMAGE" ] || { echo "❌ AppImage not found"; exit 1; }
chmod +x "$SCRIPT_DIR/$APPIMAGE"
# Portable environment (all settings are saved inside the script's folder)
[ -d "$SCRIPT_DIR/Signal.AppImage.home" ] || "$SCRIPT_DIR/$APPIMAGE" --appimage-portable-home
[ -d "$SCRIPT_DIR/Signal.AppImage.config" ] || "$SCRIPT_DIR/$APPIMAGE" --appimage-portable-config
echo "🚀 Running Signal inside the AppVM..."
exec "$SCRIPT_DIR/$APPIMAGE" "$@"
EOF
chmod +x run-signal.sh
sudo privileges (except for installing wget once inside the AppVM).If you prefer the APT method in the template, you can use this script inside the AppVM to save data locally in a separate folder.
cat > run-signal-portable.sh << 'EOF'
#!/bin/bash
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
PORTABLE_DIR="${SCRIPT_DIR}/signal-data"
mkdir -p "$PORTABLE_DIR"
if ! command -v signal-desktop &> /dev/null; then
echo "❌ Signal is not installed in the template. Run the APT commands first."
exit 1
fi
echo "🚀 Running Signal with a portable environment inside the AppVM..."
echo "📁 Data is saved in: $PORTABLE_DIR"
signal-desktop --user-data-dir="$PORTABLE_DIR" --password-store="basic" &
SIGNAL_PID=$!
while kill -0 $SIGNAL_PID 2>/dev/null; do
sleep 1
done
echo "✅ Signal has been closed."
exit 0
EOF
chmod +x run-signal-portable.sh
Requires no additional setup – works directly through the Oxen network.
cat > run-session.sh << 'EOF'
#!/bin/bash
set -e
cd "$(dirname "$0")"
SCRIPT_DIR="$(pwd)"
# Search for AppImage
APPIMAGE=$(find "$SCRIPT_DIR" -maxdepth 1 -iname "session*.AppImage" -type f 2>/dev/null | head -1)
# If not found: install wget (if needed) then download
if [ -z "$APPIMAGE" ]; then
command -v wget &>/dev/null || { echo "⚠️ Installing wget..."; sudo apt update && sudo apt install -y wget; }
echo "📥 Downloading Session..."
wget -qc --show-progress -O "$SCRIPT_DIR/Session.AppImage" "https://getsession.org/linux"
APPIMAGE="$SCRIPT_DIR/Session.AppImage"
else
APPIMAGE="$(basename "$APPIMAGE")"
fi
# Confirm existence + executable permission
[ -f "$SCRIPT_DIR/$APPIMAGE" ] || { echo "❌ AppImage not found"; exit 1; }
chmod +x "$SCRIPT_DIR/$APPIMAGE"
# Portable environment (if needed)
[ -d "$SCRIPT_DIR/Session.AppImage.home" ] || "$SCRIPT_DIR/$APPIMAGE" --appimage-portable-home
[ -d "$SCRIPT_DIR/Session.AppImage.config" ] || "$SCRIPT_DIR/$APPIMAGE" --appimage-portable-config
echo "🚀 Launching Session..."
exec "$SCRIPT_DIR/$APPIMAGE" "$@"
EOF
chmod +x run-session.sh
cat > run-telegram.sh << 'EOF'
#!/bin/bash
set -e
cd "$(dirname "$0")"
TELEGRAM_DIR="./Telegram"
TELEGRAM_BIN="$TELEGRAM_DIR/Telegram"
PORTABLE_FLAG_DIR="$TELEGRAM_DIR/TelegramForcePortable"
# 1. Is Telegram already present?
if [[ ! -f "$TELEGRAM_BIN" ]]; then
echo "ℹ️ Telegram not found, need to download."
# 2. Is download tool wget available?
if ! command -v wget &> /dev/null; then
echo "⚠️ wget not found. Installing now..."
sudo apt update
sudo apt install -y wget
echo "✅ wget installed."
else
echo "✅ wget already present."
fi
# 3. Download Telegram
echo "📥 Downloading Telegram..."
wget -c --show-progress -O telegram.tar.xz "https://telegram.org/dl/desktop/linux"
mkdir -p "$TELEGRAM_DIR"
tar -xf telegram.tar.xz -C "$TELEGRAM_DIR" --strip-components=1
rm telegram.tar.xz
echo "✅ Telegram installed."
else
echo "✅ Telegram already present, no download needed."
fi
# 4. Create ForcePortable folder (required for portable mode)
mkdir -p "$PORTABLE_FLAG_DIR"
echo "🚀 Launching Telegram (portable mode)..."
exec "$TELEGRAM_BIN" "$@"
EOF
chmod +x run-telegram.sh
Even with the strongest messaging apps, images and documents carry hidden data that can reveal you (GPS, camera type, author name, creation date).
mat2 document.pdf
Built into Whonix/Tails – creates a clean copy
exiftool -all= -overwrite_original image.jpg
Most powerful – supports all formats
dangerzone-cli file.pdf
For suspicious files – converts to safe images
sudo apt install mat2 exiftool| App fails to connect over Tor | Ensure you ran sudo onion-grater-add on the Gateway and decompressed .gz files. Try torsocks -i instead of direct run. Make sure Whonix-Gateway is running and online. |
| Extremely slow messaging | Normal with Tor. Using torsocks -i to isolate the circuit may help. Avoid sending very large files all at once. |
| Keys disappear after restart | Ensure app data is directed to the correct portable folder (cwtch-data/, simplex-chat-data/, etc.). Check folder permissions with ls -la. |
| AppImage does not run | Make sure you ran chmod +x application.AppImage. Try running from terminal to see errors. Ensure fuse is installed: sudo apt install fuse -y. |
| Suspected compromise | Close the app immediately. Delete the entire portable folder. Start fresh with all new identifiers. Use torsocks -i for a completely new circuit. |
| "Permission denied" error | Ensure the flash drive is formatted with a filesystem that supports permissions (ext4). FAT32/NTFS systems do not support chmod and may cause issues. |
USB/ ├── PortableApps/ │ ├── SimpleXChat/ │ │ ├── run-simplex-chat.sh │ │ ├── simplex-desktop-x86_64.AppImage │ │ └── simplex-chat-data/ │ ├── Cwtch/ │ │ ├── run-cwtch-portable.sh │ │ ├── lib/ │ │ │ └── cwtch │ │ ├── Tor/ │ │ └── cwtch-data/ │ ├── Signal/ ⚠️ Not secure for anonymity │ │ ├── run-signal-portable.sh │ │ └── signal-data/ │ ├── Session/ ⚠️ Not secure │ │ ├── run-session.sh │ │ ├── Session.AppImage │ │ ├── Session.AppImage.home/ │ │ └── Session.AppImage.config/ │ ├── Element/ │ │ ├── run-element-portable.sh │ │ └── Element-Data/ │ ├── Gajim/ ⚠️ Partially portable │ │ ├── run-gajim-portable.sh │ │ └── gajim-data/ │ └── Telegram/ ⚠️ Not secure │ ├── run-telegram.sh │ └── Telegram/ │ ├── Telegram │ └── TelegramForcePortable/ ├── backups/ 📦 Backups │ └── backup-YYYYMMDD.tar.gz └── README.md
To protect your portable data, create regular backups:
# From the USB root folder tar -czf backups/backup-$(date +%Y%m%d).tar.gz PortableApps/*/simplex-chat-data PortableApps/*/cwtch-data PortableApps/*/Element-Data
# Extract the backup tar -xzf backups/backup-YYYYMMDD.tar.gz
torsocks forces any application to use the Tor network, even if it does not natively support proxies. No gateway configuration required.
torsocks ./application.AppImage
torsocks -i ./application.AppImage
# Make sure curl is installed which curl || sudo apt install curl -y torsocks curl https://check.torproject.org/api/ip
torsocks and torsocks -i:
torsocks: shares the circuit with other applications – may link your activitytorsocks -i: opens a new circuit for each command – prevents linking completely-i.gz files on the gateway.
chmod +x auto-script.sh && ./auto-script.sh
#!/bin/bash
# ============================================================
# auto-script.sh - Qubes OS Security Hardening Script (2026 version)
# After review and verification from official sources
# ============================================================
# Get the real user and paths
REAL_USER=$(logname 2>/dev/null || echo "$SUDO_USER" 2>/dev/null || echo "$USER")
REAL_HOME="/home/$REAL_USER"
DESKTOP_PATH="$REAL_HOME/Desktop"
R='\033[0;31m'; G='\033[0;32m'; Y='\033[1;33m'; B='\033[0;34m'; N='\033[0m'
ok() { echo -e "${G}✓ $1${N}"; }
info() { echo -e "${B}ℹ $1${N}"; }
warn() { echo -e "${Y}⚠ $1${N}"; }
err() { echo -e "${R}✗ $1${N}"; }
pause() { echo; read -p "Press Enter to continue..."; }
BASE_TEMPLATE=$(qubes-prefs default_template)
menu() {
clear
echo "======"
echo " 🔒 Qubes OS + Kicksecure Security Hardening"
echo "======"
echo ""
echo "1) Configure kernel parameters (dom0 + VMs)"
echo "2) MAC Randomization + Disable IPv6"
echo "3) Create Whonix gateways"
echo "4) Install Kicksecure + set as default"
echo "5) Create RAM Pool scripts"
echo "6) Create Amnesic Tmpfs script"
echo "7) Enable Live Mode"
echo "8) 💀 Destruction tools (BusKill + Emergency)"
echo "9) 🛡️ LUKS disk encryption hardening (PBKDF)"
echo "10) Set up automatic disk mounting in Whonix"
echo "11) Run automatic sections (1 to 8)"
echo "0) Exit"
echo ""
read -p "Choose: " c
case $c in
1) kernel_opts_menu ;;
2) mac_random ;;
3) whonix_gws ;;
4) kicksecure ;;
5) ram_pool_scripts ;;
6) amnesic_script ;;
7) live_mode ;;
8) emergency_script ;;
9) luks_hardening ;;
10) whonix_auto_mount ;;
11) run_auto ;;
0) exit 0 ;;
*) menu ;;
esac
}
# ===================================================================
# Section 1: Kernel Parameters Setup (corrected and documented version)
# ===================================================================
# ========
# 1. Kernel Parameters Setup Menu (3 options only)
# ========
kernel_opts_menu() {
clear
echo "======"
echo " ⚙️ Kernel Parameters Setup"
echo "======"
echo ""
echo "1) Configure dom0 (GRUB + Secureblue sysctl)"
echo "2) Configure all VMs (kernelopts)"
echo "3) Restore GRUB from backup"
echo "0) Back to main menu"
echo ""
read -p "Choose: " c
case $c in
1) setup_kernel_opts_dom0 ;;
2) setup_kernel_opts_vms ;;
3) rollback_grub ;;
0) menu ;;
*) kernel_opts_menu ;;
esac
}
# ========
# 1a. Configure dom0 (GRUB + Secureblue sysctl)
# ========
setup_kernel_opts_dom0() {
clear
echo "=========================================="
echo " 🔧 Configuring dom0 (GRUB + Secureblue sysctl)"
echo "=========================================="
# ------------- Part 1: GRUB -------------
echo ""
echo "📌 Part One: GRUB Parameter Configuration"
echo ""
if [ ! -f /etc/default/grub ]; then
err "File /etc/default/grub does not exist!"
pause
return 1
fi
# Extract UUID using multiple methods
CURRENT_UUID=$(grep -oP 'rd\.luks\.uuid=luks-\K[a-f0-9-]+' /etc/default/grub 2>/dev/null)
if [ -z "$CURRENT_UUID" ]; then
CURRENT_UUID=$(sudo cryptsetup luksUUID $(findmnt -n -o SOURCE /) 2>/dev/null)
fi
if [ -z "$CURRENT_UUID" ]; then
err "Cannot extract UUID from current GRUB"
echo ""
read -p "Enter UUID manually (without luks-): " CURRENT_UUID
[ -z "$CURRENT_UUID" ] && { err "No UUID entered"; pause; return 1; }
fi
ok "UUID: $CURRENT_UUID"
# Create a backup
BACKUP_FILE="/etc/default/grub.backup.$(date +%Y%m%d_%H%M%S)"
sudo cp /etc/default/grub "$BACKUP_FILE"
ok "Backup: $BACKUP_FILE"
echo ""
echo "📋 dom0 Parameters:"
echo ""
echo "┌─────────────────────────────────────────────────────────────┐"
echo "│ Xen Parameters: │"
echo "│ bootscrub=1 xen_scrub_pages=1 │"
echo "│ │"
echo "│ Linux Parameters: │"
echo "│ init_on_free=1 init_on_alloc=1 page_poison=1 │"
echo "│ lockdown=confidentiality module.sig_enforce=1 │"
echo "│ slab_nomerge vsyscall=none kernel.kptr_restrict=2 │"
echo "│ oops=panic debugfs=off quiet loglevel=0 spectre_v2=on │"
echo "│ page_alloc.shuffle=1 preempt=voluntary pti=on mds=full │"
echo "└─────────────────────────────────────────────────────────────┘"
echo ""
read -p "Proceed with applying GRUB parameters? (y/n): " confirm
[[ "$confirm" != "y" && "$confirm" != "Y" ]] && { err "Cancelled"; pause; return 0; }
# Create new GRUB file
cat > /tmp/grub_new << 'INNER_EOF'
GRUB_TIMEOUT=5
GRUB_DISTRIBUTOR="$(sed 's, release .*$,,g' /etc/system-release)"
GRUB_DEFAULT=saved
GRUB_DISABLE_SUBMENU=false
GRUB_TERMINAL_OUTPUT="console"
GRUB_CMDLINE_LINUX="rd.luks.uuid=UUID_PLACEHOLDER rd.lvm.lv=qubes_dom0/root rd.lvm.lv=qubes_dom0/swap plymouth.ignore-serial-consoles rhgb quiet init_on_free=1 init_on_alloc=1 page_poison=1 lockdown=confidentiality module.sig_enforce=1 slab_nomerge vsyscall=none kernel.kptr_restrict=2 oops=panic debugfs=off loglevel=0 spectre_v2=on page_alloc.shuffle=1 preempt=voluntary pti=on mds=full"
GRUB_DISABLE_RECOVERY="true"
GRUB_THEME="/boot/grub2/themes/qubes/theme.txt"
GRUB_CMDLINE_XEN_DEFAULT="console=none dom0_mem=min:1024M dom0_mem=max:8192M ucode=scan bootscrub=1 xen_scrub_pages=1 gnttab_max_frames=2048 gnttab_max_maptrack_frames=4096"
GRUB_DISABLE_OS_PROBER="true"
GRUB_CMDLINE_LINUX="$GRUB_CMDLINE_LINUX rd.qubes.hide_all_usb"
INNER_EOF
sed -i "s/UUID_PLACEHOLDER/luks-$CURRENT_UUID/" /tmp/grub_new
sudo cp /tmp/grub_new /etc/default/grub
rm /tmp/grub_new
info "Updating GRUB..."
sudo grub2-mkconfig -o /boot/grub2/grub.cfg
if [ $? -eq 0 ]; then
ok "GRUB updated successfully"
else
err "GRUB update failed! Restoring backup..."
sudo cp "$BACKUP_FILE" /etc/default/grub
sudo grub2-mkconfig -o /boot/grub2/grub.cfg
pause
return 1
fi
# ------------- Part 2: Secureblue sysctl -------------
echo ""
echo "📌 Part Two: Secureblue sysctl Configuration (Additional Hardening Layer)"
echo ""
read -p "Do you want to apply Secureblue sysctl settings? (y/n): " confirm_sysctl
if [[ "$confirm_sysctl" == "y" || "$confirm_sysctl" == "Y" ]]; then
if [ -f /etc/sysctl.d/99-qubes-hardening.conf ]; then
sudo cp /etc/sysctl.d/99-qubes-hardening.conf /etc/sysctl.d/99-qubes-hardening.conf.backup.$(date +%Y%m%d_%H%M%S)
ok "sysctl backup created"
fi
sudo tee /etc/sysctl.d/99-qubes-hardening.conf << 'EOF'
# Secureblue Hardening for Qubes dom0
# Source: https://github.com/secureblue/secureblue
# Prevent memory dump via physical access (SysRq)
kernel.sysrq=0
# Restrict access to monitoring tools (prevents side channels)
kernel.perf_event_paranoid=3
# Hide kernel addresses (makes KASLR exploitation harder)
kernel.kptr_restrict=2
# Auto-reboot after 5 seconds on panic
kernel.panic=5
# Restrict console logging (reduces data leakage)
kernel.printk=3 3 3 3
# Prevent loading an alternate kernel (prevents persistence)
kernel.kexec_load_disabled=1
# Completely disable io_uring (prevents rootkits)
kernel.io_uring_disabled=2
# Protect regular files from symlink attacks
fs.protected_regular=2
# Protect FIFOs from privilege escalation
fs.protected_fifos=2
# Prevent unprivileged users from reading dmesg
kernel.dmesg_restrict=1
# Disable unprivileged eBPF
kernel.unprivileged_bpf_disabled=1
# Set maximum memory map count
vm.max_map_count=1048576
# Disable core dumps
fs.suid_dumpable=0
EOF
sudo sysctl --system
ok "Secureblue sysctl applied"
else
info "Skipped Secureblue sysctl"
fi
echo ""
warn "⚠️ Changes will only take effect after reboot"
echo ""
echo "📋 To verify after reboot, use:"
echo " cat /proc/cmdline | grep -E 'init_on_free|pti'"
echo " sysctl kernel.sysrq kernel.perf_event_paranoid"
echo ""
read -p "Reboot now? (y/n): " reboot_choice
[[ "$reboot_choice" == "y" || "$reboot_choice" == "Y" ]] && sudo reboot
pause
}
# ========
# 1b. Add kernel parameters to all VMs (final version)
# ========
setup_kernel_opts_vms() {
clear
echo "=========================================="
echo " 🔧 Configuring Kernel Parameters for all VMs"
echo "=========================================="
echo ""
# Full list
NEW_OPTS="xen_scrub_pages=1 init_on_free=1 init_on_alloc=1 page_poison=1 mitigations=auto random.trust_cpu=off random.trust_bootloader=off spec_store_bypass_disable=on tsx_async_abort=full mds=full,nosmt page_alloc.shuffle=1 randomize_kstack_offset=on l1tf=full,force ipv6.disable=1 slab_nomerge vsyscall=none retbleed=stuff spectre_v2_user=on kvm.nx_huge_pages=force"
read -p "Apply parameters to all VMs? (y/n): " confirm
[[ "$confirm" != "y" && "$confirm" != "Y" ]] && { info "Cancelled"; return; }
info "Shutting down VMs..."
sudo qvm-shutdown --all 2>/dev/null
sleep 3
local APPLIED_COUNT=0
# Apply to all except dom0
for vm in $(qvm-ls --raw-list 2>/dev/null); do
vm_clean=$(echo "$vm" | tr -d '[:space:]')
[ "$vm_clean" = "dom0" ] && continue
if qvm-prefs "$vm_clean" kernelopts "$NEW_OPTS" 2>/dev/null; then
echo " ✅ $vm_clean: applied"
((APPLIED_COUNT++))
else
warn " ❌ $vm_clean: failed"
fi
done
echo ""
ok "✅ Applied parameters to $APPLIED_COUNT VM(s)"
# Clear gateways specifically
echo ""
info "🧹 Clearing kernelopts for Whonix gateways..."
for gw in $(qvm-ls --raw-list 2>/dev/null | grep -E 'sys-whonix|whonix-gateway'); do
gw_clean=$(echo "$gw" | tr -d '[:space:]')
if qvm-prefs "$gw_clean" kernelopts "" 2>/dev/null; then
echo " 🧹 $gw_clean: cleared (kernelopts=\"\")"
else
warn " ❌ $gw_clean: failed to clear"
fi
done
echo ""
info "Restarting essential services..."
for vm in sys-net sys-firewall sys-usb; do
qvm-start "$vm" 2>/dev/null && echo " 🔄 $vm: started"
done
# Enable ephemeral_volatile
echo ""
echo "╔═══════════════════════════════════════════════════════════════╗"
echo "║ 💧 Enabling ephemeral_volatile ║"
echo "╠═══════════════════════════════════════════════════════════════╣"
echo "║ Enabling transient encryption for the volatile volume ║"
echo "╚═══════════════════════════════════════════════════════════════╝"
echo ""
info "Enabling ephemeral_volatile on pools..."
for pool in vm-pool varlibqubes; do
if qvm-pool ls 2>/dev/null | grep -q "$pool"; then
qvm-pool set "$pool" -o ephemeral_volatile=True 2>/dev/null && \
ok "Enabled ephemeral_volatile on $pool" || \
warn "Failed to enable ephemeral_volatile on $pool"
else
warn "Pool $pool does not exist, skipping"
fi
done
ok "Enabled ephemeral_volatile on $EPHEMERAL_COUNT VM(s)"
echo ""
ok "✅ VM kernel parameter setup completed successfully!"
pause
}
# ========
# 1c. Restore GRUB from backup
# ========
rollback_grub() {
clear
echo "======"
echo " 🔄 Restore GRUB from Backup"
echo "======"
echo ""
echo "📁 Available backups:"
ls -la /etc/default/grub.backup.* 2>/dev/null | head -5 || { err "No backups found"; pause; return 1; }
echo ""
read -p "Enter backup file name (or Enter for latest): " backup_name
if [ -z "$backup_name" ]; then
backup_name=$(ls -t /etc/default/grub.backup.* 2>/dev/null | head -1)
else
backup_name="/etc/default/$backup_name"
fi
[ ! -f "$backup_name" ] && { err "File not found"; pause; return 1; }
sudo cp "$backup_name" /etc/default/grub
ok "File restored"
sudo grub2-mkconfig -o /boot/grub2/grub.cfg
ok "GRUB updated"
read -p "Reboot now? (y/n): " reboot_choice
[[ "$reboot_choice" == "y" || "$reboot_choice" == "Y" ]] && sudo reboot
pause
}
# ========
# 2. MAC Randomization + Disable IPv6 + Shutdown template
# ========
mac_random() {
info "Setting up MAC Randomization: $BASE_TEMPLATE"
info "Checking IPv6 settings for sys-net and sys-firewall..."
for vm in sys-net sys-firewall; do
if qvm-ls "$vm" >/dev/null 2>&1; then
CURRENT_OPTS=$(qvm-prefs "$vm" kernelopts 2>/dev/null | xargs)
if [[ "$CURRENT_OPTS" == *"ipv6.disable=1"* ]]; then
warn "⚠️ $vm: IPv6 already disabled"
else
info "🔧 $vm: Disabling IPv6..."
qvm-prefs "$vm" kernelopts "$CURRENT_OPTS ipv6.disable=1"
ok "✅ IPv6 disabled for $vm"
fi
else
warn "$vm does not exist, skipping"
fi
done
if qvm-ls "$BASE_TEMPLATE" >/dev/null 2>&1; then
info "🔧 Setting up MAC Randomization in $BASE_TEMPLATE..."
qvm-run "$BASE_TEMPLATE" "sudo bash -c 'mkdir -p /usr/lib/NetworkManager/conf.d/ && cat > /usr/lib/NetworkManager/conf.d/31-randomize-mac.conf << EOF
[connection]
ethernet.cloned-mac-address=random
wifi.cloned-mac-address=random
wifi.scan-rand-mac-address=yes
EOF'"
ok "✅ MAC Randomization configured in $BASE_TEMPLATE"
info "Shutting down template $BASE_TEMPLATE..."
qvm-shutdown "$BASE_TEMPLATE" 2>/dev/null || true
ok "$BASE_TEMPLATE shut down"
else
warn "Template $BASE_TEMPLATE does not exist, skipping MAC setup"
fi
info "Note: You may need to restart VMs based on this template"
pause
}
# ========
# 3. Create Whonix Gateways
# ========
whonix_gws() {
for g in "sys-whonix-A:red" "sys-whonix-B:blue"; do
n=${g%:*}; l=${g#*:}
if ! qvm-ls | grep -q "$n"; then
qvm-create --class AppVM --label "$l" --template whonix-gateway-18 "$n"
qvm-prefs "$n" provides_network true
ok "$n created"
else
info "$n already exists, skipping"
fi
done
pause
}
# ===========
4. kicksecure + default-dvm
# ===========
kicksecure() {
clear
echo "=========================================="
echo " 🔒 Install and Configure Kicksecure 18 for Qubes OS"
echo "=========================================="
echo ""
echo "📌 Note: In Qubes OS, templates share the dom0 kernel."
echo " Only userspace hardening is applied inside the template."
echo " Kernel parameters must be set via kernelopts or GRUB."
echo ""
info "Checking for Kicksecure 18 template..."
if qvm-ls | grep -q kicksecure-18; then
ok "Kicksecure 18 template already exists"
info "Skipping installation phase..."
else
info "Kicksecure 18 template not found, installing..."
qvm-template --enablerepo qubes-templates-community install kicksecure-18
if ! qvm-ls | grep -q kicksecure-18; then
err "Failed to install kicksecure-18"
pause
return
fi
ok "Kicksecure-18 installed successfully"
fi
info "Installing addons..."
sudo qubes-dom0-update --action=install -y qubes-core-admin-addon-kicksecure
info "Restarting qubesd service..."
sudo systemctl restart qubesd.service
ok "qubesd.service restarted"
info "Starting Kicksecure 18 template..."
qvm-start kicksecure-18
local MAX_WAIT=30
local WAIT_COUNT=0
while ! qvm-check --running kicksecure-18 2>/dev/null; do
sleep 2
WAIT_COUNT=$((WAIT_COUNT + 1))
if [ $WAIT_COUNT -ge $MAX_WAIT ]; then
warn "Template did not finish starting, proceeding with caution..."
break
fi
done
sleep 5
# 1. Set timezone
info "Setting timezone to UTC..."
qvm-run kicksecure-18 "sudo timedatectl set-timezone Etc/UTC" 2>/dev/null || true
ok "Timezone set"
# 2. MAC Randomization
info "Setting up MAC Randomization..."
qvm-run kicksecure-18 "sudo bash -c 'mkdir -p /usr/lib/NetworkManager/conf.d/ && cat > /usr/lib/NetworkManager/conf.d/31-randomize-mac.conf << \"EOF\"
[connection]
ethernet.cloned-mac-address=random
wifi.cloned-mac-address=random
wifi.scan-rand-mac-address=yes
EOF'" 2>/dev/null || true
ok "MAC Randomization configured"
# 3. Disable unnecessary services
info "Disabling unnecessary services..."
qvm-run kicksecure-18 "sudo systemctl mask bluetooth.service cups.service avahi-daemon.service ModemManager.service 2>/dev/null || true" 2>/dev/null || true
qvm-run kicksecure-18 "sudo systemctl disable --now NetworkManager-wait-online.service 2>/dev/null || true" 2>/dev/null || true
ok "Unnecessary services disabled"
# 4. Remove unnecessary packages
info "Removing unnecessary packages..."
qvm-run kicksecure-18 "sudo apt remove -y --purge thunderbird hexchat transmission-common 2>/dev/null || true" 2>/dev/null || true
qvm-run kicksecure-18 "sudo apt autoremove -y 2>/dev/null || true" 2>/dev/null || true
ok "Unnecessary packages removed"
# 5. Shut down the template
info "Shutting down Kicksecure 18 template..."
qvm-shutdown --wait kicksecure-18 2>/dev/null || true
ok "Kicksecure-18 shut down"
# 6. Create a hardened DVM template (without rw False)
echo ""
echo "╔═══════════════════════════════════════════════════════════════╗"
echo "║ 🧬 Create Hardened DVM Template (Ephemeral Template) ║"
echo "╚═══════════════════════════════════════════════════════════════╝"
echo ""
read -p "Do you want to create a hardened DVM template from Kicksecure? (y/n): " create_dvm
if [[ "$create_dvm" == "y" || "$create_dvm" == "Y" ]]; then
info "Creating hardened DVM template..."
if ! qvm-ls kicksecure-18-dvm >/dev/null 2>&1; then
qvm-create --template kicksecure-18 --label red kicksecure-18-dvm
ok "✓ kicksecure-18-dvm created"
else
info "kicksecure-18-dvm already exists"
fi
qvm-prefs kicksecure-18-dvm template_for_dispvms true
# تم حذف: qvm-volume config kicksecure-18-dvm:root rw False
qvm-prefs kicksecure-18-dvm kernelopts "init_on_free=1 init_on_alloc=1 page_poison=1 slab_nomerge"
ok "✓ kicksecure-18-dvm set as a hardened DVM"
read -p "Do you want to make it the default-dvm? (y/n): " make_default
if [[ "$make_default" == "y" || "$make_default" == "Y" ]]; then
qvm-prefs default-dvm template kicksecure-18-dvm
ok "✓ default-dvm now uses kicksecure-18-dvm"
fi
fi
# 7. Ask to make Kicksecure the default template (now includes sys-usb adjustment)
echo ""
echo "╔═══════════════════════════════════════════════════════════════╗"
echo "║ 🎯 Make Kicksecure 18 the default template for default-dvm ║"
echo "╚═══════════════════════════════════════════════════════════════╝"
echo ""
read -p "Make Kicksecure 18 the default for default-dvm? (y/n): " yn
if [[ "$yn" == "y" || "$yn" == "Y" ]]; then
BASE_TEMPLATE="kicksecure-18"
if ! qvm-ls --raw-list | grep -q "^$BASE_TEMPLATE$"; then
err "Template $BASE_TEMPLATE does not exist"
pause
return
fi
info "Shutting down affected VMs..."
qvm-kill sys-* 2>/dev/null
qvm-kill default-dvm 2>/dev/null
info "Changing default-dvm template..."
qvm-prefs default-dvm template "$BASE_TEMPLATE"
ok "✓ default-dvm now runs from Kicksecure 18"
echo ""
info "Clearing kernelopts for sys-firewall and sys-net..."
qvm-prefs sys-firewall kernelopts "" 2>/dev/null
qvm-prefs sys-net kernelopts "" 2>/dev/null
ok "✓ kernelopts cleared for relevant VMs"
echo ""
info "Adjusting memory settings for sys-net and sys-firewall..."
qvm-prefs sys-net memory 800 2>/dev/null
qvm-prefs sys-net maxmem 800 2>/dev/null
qvm-prefs sys-firewall memory 800 2>/dev/null
qvm-prefs sys-firewall maxmem 800 2>/dev/null
ok "✓ Memory settings increased"
# 🆕 Adjust sys-usb only if user agreed to make Kicksecure default
if qvm-ls sys-usb >/dev/null 2>&1; then
info "Adjusting sys-usb settings..."
qvm-prefs sys-usb memory 800 2>/dev/null
qvm-prefs sys-usb maxmem 800 2>/dev/null
qvm-start sys-usb 2>/dev/null || true
ok "✓ sys-usb memory increased to 800MB"
fi
echo ""
info "Starting VMs..."
qvm-start sys-net 2>/dev/null
qvm-start sys-firewall 2>/dev/null
sleep 5
sudo systemctl restart qubesd.service
ok "✓ Completed successfully"
fi
echo ""
echo "╔═══════════════════════════════════════════════════════════════╗"
echo "║ ✅ Kicksecure Setup Complete! ║"
echo "╠═══════════════════════════════════════════════════════════════╣"
echo "║ 📋 What was applied: ║"
echo "║ • UTC timezone ║"
echo "║ • MAC Randomization ║"
echo "║ • Unnecessary services disabled ║"
echo "║ • Unnecessary packages removed ║"
echo "║ ║"
echo "║ ⚠️ Note: Kernel parameters are applied via: ║"
echo "║ • dom0: GRUB (tab 0 or option 1 of the script) ║"
echo "║ • VMs: kernelopts (option 2 of the script) ║"
echo "╚═══════════════════════════════════════════════════════════════╝"
echo ""
pause
}
# ========
# 5. RAM Pool Scripts
# ========
ram_pool_scripts() {
mkdir -p "$DESKTOP_PATH/ram-pool-scripts"
echo 'org.gnome.DiskUtility.desktop
janondisttorbrowser.desktop
pcmanfm-qt.desktop
qterminal.desktop
anondist-torbrowser_update.desktop' | qvm-appmenus --set-default-whitelist - whonix-workstation-18
qvm-appmenus --update whonix-workstation-18
echo "✅ Disks (org.gnome.DiskUtility.desktop) set as default – make sure it's installed"
# ========== setup-ram-pool.sh ==========
cat > "$DESKTOP_PATH/ram-pool-scripts/setup-ram-pool.sh" << 'EOF'
#!/bin/bash
RAM_POOL_PATH="$HOME/ram_pool"
RAM_POOL_SIZE="4G"
sudo mkdir -p "$RAM_POOL_PATH"
sudo mount -t tmpfs -o size=$RAM_POOL_SIZE tmpfs "$RAM_POOL_PATH"
if ! qvm-pool ls | grep -q ram_pool; then
qvm-pool add ram_pool file -o dir_path="$RAM_POOL_PATH"
fi
qvm-pool set ram_pool -o ephemeral_volatile=True
echo "✅ RAM pool ready with ephemeral_volatile enabled"
EOF
# ========== create-ram-vm.sh (single – no auto-mount) ==========
cat > "$DESKTOP_PATH/ram-pool-scripts/create-ram-vm.sh" << 'SCRIPT_EOF'
#!/bin/bash
# create-ram-vm.sh - Create whonix-work-ram
set -e
GATEWAY="sys-whonix"
WORKSTATION="whonix-work-ram"
TEMPLATE="whonix-workstation-18"
RAM_PATH="$HOME/ram_pool"
KERNEL_OPTS="xen_scrub_pages=1 init_on_free=1 init_on_alloc=1 page_poison=1 mitigations=auto random.trust_cpu=off random.trust_bootloader=off spec_store_bypass_disable=on tsx_async_abort=full mds=full,nosmt page_alloc.shuffle=1 randomize_kstack_offset=on l1tf=full,force ipv6.disable=1 slab_nomerge vsyscall=none retbleed=stuff spectre_v2_user=on kvm.nx_huge_pages=force"
# 1. RAM pool
if ! mount | grep -q "$RAM_PATH"; then
sudo mkdir -p "$RAM_PATH"
sudo mount -t tmpfs -o size=4G tmpfs "$RAM_PATH"
qvm-pool add ram_pool file -o dir_path="$RAM_PATH" 2>/dev/null || true
qvm-pool set ram_pool -o ephemeral_volatile=True 2>/dev/null || true
fi
# 2. Create VM
qvm-kill "$WORKSTATION" 2>/dev/null || true
qvm-remove --force "$WORKSTATION" 2>/dev/null || true
qvm-create -P ram_pool -t "$TEMPLATE" -l red "$WORKSTATION"
qvm-prefs "$WORKSTATION" kernelopts "$KERNEL_OPTS"
echo "✅ $WORKSTATION created"
# 3. Connect gateway (optional – ensure it exists)
qvm-prefs "$WORKSTATION" netvm "$GATEWAY" 2>/dev/null || echo "⚠️ Could not set gateway"
SCRIPT_EOF
chmod +x "$DESKTOP_PATH/ram-pool-scripts/create-ram-vm.sh"
# ========== mount-to-whonix.sh (attach + safe detach) ==========
cat > "$DESKTOP_PATH/ram-pool-scripts/mount-to-whonix.sh" << 'EOF'
#!/bin/bash
# mount-to-whonix.sh - Attach/detach disks in Whonix VMs
set -e
MOUNT_POINT="/home/user/usb"
DEFAULT_DISK="xvdi"
G='\033[0;32m'; Y='\033[1;33m'; R='\033[0;31m'; N='\033[0m'
find_vm() {
mapfile -t VMS < <(qvm-ls --raw-list --running --template-source whonix-workstation-18 whonix-workstation-18-dvm | grep -v "^sys-whonix")
[ ${#VMS[@]} -eq 0 ] && { echo -e "${R}❌ No running Whonix VMs${N}"; exit 1; }
echo -e "${G}Available VMs:${N}"
for i in "${!VMS[@]}"; do echo -e " ${Y}[$((i+1))]${N} ${VMS[$i]}"; done
while true; do
read -p "Select number: " C
[[ "$C" =~ ^[0-9]+$ && $C -ge 1 && $C -le ${#VMS[@]} ]] && { TARGET="${VMS[$((C-1))]}"; break; }
echo -e "${R}Invalid${N}"
done
}
echo -e "\n${G}🔌 Disk Management${N}"
echo -e " [1] Attach and mount\n [2] Safe detach (umount + detach)\n [3] Show status\n [4] Exit"
read -p "Choose: " OP
case $OP in
1) # Attach
find_vm
qvm-run --pass-io -u root "$TARGET" "lsblk -o NAME,SIZE,TYPE,MOUNTPOINT | head -20"
read -p "Disk name [$DEFAULT_DISK]: " DISK; DISK=${DISK:-$DEFAULT_DISK}
qvm-run --pass-io -u root "$TARGET" "test -b /dev/$DISK" || { echo -e "${R}❌ Not found${N}"; exit 1; }
qvm-run --pass-io -u root "$TARGET" "mkdir -p $MOUNT_POINT && mount /dev/$DISK $MOUNT_POINT && echo '✅ Done'" || echo -e "${R}❌ Failed${N}"
;;
2) # Safe detach
find_vm
echo -e "${Y}Devices mounted on $MOUNT_POINT:${N}"
qvm-run --pass-io -u root "$TARGET" "mount | grep $MOUNT_POINT" 2>/dev/null || echo -e "${Y}Nothing mounted${N}"
read -p "Proceed with full detach? (y/n): " CONFIRM
[[ "$CONFIRM" =~ ^[Yy] ]] || exit 0
# Unmount inside VM
echo -e "${Y}Unmounting from VM...${N}"
qvm-run --pass-io -u root "$TARGET" "sync && umount $MOUNT_POINT 2>/dev/null" && echo -e "${G}✅ Unmounted${N}" || echo -e "${Y}⚠️ Not mounted or unmount failed${N}"
# Detach all devices from dom0
echo -e "${Y}Detaching devices from dom0...${N}"
qvm-block list | grep "$TARGET" | while read -r line; do
DEVICE=$(echo "$line" | awk '{print $1}')
echo -e "Detaching $DEVICE..."
qvm-block detach "$TARGET" "$DEVICE" 2>/dev/null && echo -e "${G}✅ $DEVICE detached${N}" || echo -e "${R}❌ Failed to detach $DEVICE${N}"
done
echo -e "${G}🎉 Full detach completed. You can reconnect the device later and the monitor will auto-mount it.${N}"
;;
3) # Show status
find_vm
echo -e "${Y}=== Device Status ===${N}"
qvm-run --pass-io -u root "$TARGET" "lsblk -o NAME,SIZE,TYPE,MOUNTPOINT"
echo -e "\n${Y}=== Mount Status ===${N}"
qvm-run --pass-io -u root "$TARGET" "mountpoint -q $MOUNT_POINT 2>/dev/null && echo '✅ Mounted' || echo '⚠️ Not mounted'"
echo -e "\n${Y}=== Attached Devices ===${N}"
qvm-block list | grep "$TARGET" || echo "No devices attached"
;;
4) echo -e "${G}👋${N}"; exit 0 ;;
*) echo -e "${R}❌ Invalid${N}"; exit 1 ;;
esac
EOF
chmod +x "$DESKTOP_PATH/ram-pool-scripts/mount-to-whonix.sh"
# ========== apply-cwtch-onionshare.sh ==========
cat > "$DESKTOP_PATH/ram-pool-scripts/apply-cwtch-onionshare.sh" << 'EOF'
#!/bin/bash
# apply-cwtch-onionshare.sh - Configure Cwtch & OnionShare on a running Whonix VM
set -e
G='\033[0;32m'; Y='\033[1;33m'; R='\033[0;31m'; N='\033[0m'
echo -e "${G}🔧 Applying Cwtch & OnionShare settings${N}"
# 1. Find running Whonix VMs (excluding sys-whonix)
mapfile -t VMS < <(qvm-ls --running --raw-list | grep -i whonix | grep -v sys-whonix)
if [ ${#VMS[@]} -eq 0 ]; then
echo -e "${R}❌ No running Whonix VMs. Start whonix-work-ram first.${N}"
exit 1
fi
echo -e "${G}Available VMs:${N}"
for i in "${!VMS[@]}"; do
echo -e " ${Y}[$((i+1))]${N} ${VMS[$i]}"
done
# 2. Select VM
while true; do
read -p "👉 Choose VM number to configure (1-${#VMS[@]}): " CHOICE
if [[ "$CHOICE" =~ ^[0-9]+$ ]] && [ "$CHOICE" -ge 1 ] && [ "$CHOICE" -le "${#VMS[@]}" ]; then
WORKSTATION="${VMS[$((CHOICE-1))]}"
break
else
echo -e "${R}❌ Invalid choice.${N}"
fi
done
GATEWAY=$(qvm-prefs "$WORKSTATION" netvm 2>/dev/null | cut -d'|' -f1 | tr -d '[:space:]')
if [ -z "$GATEWAY" ] || [ "$GATEWAY" = "None" ]; then
echo -e "${R}❌ $WORKSTATION has no gateway set.${N}"
exit 1
fi
echo -e "\n${Y}⚙️ Will apply settings to:${N}"
echo -e " Workstation: ${G}$WORKSTATION${N}"
echo -e " Gateway: ${G}$GATEWAY${N}"
read -p "❓ Continue? (y/n): " CONFIRM
if [[ ! "$CONFIRM" =~ ^[Yy]$ ]]; then
echo -e "${R}Cancelled.${N}"
exit 0
fi
# 3. Configure gateway (onion-grater)
echo -e "\n${Y}🔐 Adding onion-grater rules on $GATEWAY ...${N}"
qvm-run --pass-io -u root "$GATEWAY" "onion-grater-add 40_cwtch && onion-grater-add 40_onionshare && systemctl restart onion-grater" && \
echo -e "${G}✅ onion-grater configured${N}" || echo -e "${R}❌ Failed to configure onion-grater${N}"
# 4. Configure workstation (open ports)
echo -e "\n${Y}📡 Opening ports on $WORKSTATION ...${N}"
qvm-run --pass-io -u root "$WORKSTATION" "mkdir -p /usr/local/etc/whonix_firewall.d && echo 'EXTERNAL_OPEN_PORTS+=\" \$(seq 15000 15378) \$(seq 17600 17659) \"' >> /usr/local/etc/whonix_firewall.d/50_user.conf && whonix_firewall" && \
echo -e "${G}✅ Ports opened and firewall reloaded${N}" || echo -e "${R}❌ Failed to configure firewall${N}"
echo -e "\n${G}🎉 Cwtch & OnionShare configuration completed${N}"
EOF
chmod +x "$DESKTOP_PATH/ram-pool-scripts/apply-cwtch-onionshare.sh"
# ========== create-multi-ram-vm.sh (multi – no auto-mount) ==========
cat > "$DESKTOP_PATH/ram-pool-scripts/create-multi-ram-vm.sh" << 'EOF'
#!/bin/bash
BASE_TEMPLATE="whonix-workstation-18"
VM_BASE="whonix-work-ram"
RAM_POOL_PATH="$HOME/ram_pool"
RAM_POOL_SIZE="4G"
SAVE_SCRIPT_PATH="$HOME/Desktop/qubes-auto-create.sh"
G='\033[0;32m'; Y='\033[1;33m'; R='\033[0;31m'; B='\033[0;34m'; N='\033[0m'
KERNEL_OPTS="xen_scrub_pages=1 init_on_free=1 init_on_alloc=1 page_poison=1 mitigations=auto random.trust_cpu=off random.trust_bootloader=off spec_store_bypass_disable=on tsx_async_abort=full mds=full,nosmt page_alloc.shuffle=1 randomize_kstack_offset=on l1tf=full,force ipv6.disable=1 slab_nomerge vsyscall=none retbleed=stuff spectre_v2_user=on kvm.nx_huge_pages=force"
echo -e "${G}🔧 Creating VMs in RAM pool...${N}"
# 1. Number of VMs
echo -e "${Y}How many VMs would you like to create?${N}"
read -p "Count: " VM_COUNT
if ! [[ "$VM_COUNT" =~ ^[0-9]+$ ]] || [ "$VM_COUNT" -lt 1 ]; then
echo -e "${R}❌ Error: please enter a positive number${N}"
exit 1
fi
# 2. Search for sys-whonix gateways
echo -e "${Y}🔍 Searching for available sys-whonix gateways...${N}"
mapfile -t GATEWAYS < <(qvm-ls --raw-data --fields NAME,CLASS 2>/dev/null | grep -E '^sys-whonix(-[0-9]+)?' | awk '{print $1}' | cut -d'|' -f1)
if [ ${#GATEWAYS[@]} -eq 0 ]; then
mapfile -t GATEWAYS < <(qvm-ls 2>/dev/null | grep -E '^sys-whonix(-[0-9]+)?' | awk '{print $1}')
fi
if [ ${#GATEWAYS[@]} -eq 0 ]; then
echo -e "${R}❌ Error: no sys-whonix gateways found${N}"
exit 1
fi
echo -e "${G}✅ Gateways found:${N}"
for i in "${!GATEWAYS[@]}"; do
CLEAN_GATEWAY=$(echo "${GATEWAYS[$i]}" | cut -d'|' -f1 | tr -d '[:space:]')
GATEWAYS[$i]="$CLEAN_GATEWAY"
echo -e " ${Y}[$((i+1))]${N} ${GATEWAYS[$i]}"
done
# 3. Choose gateways
echo -e "\n${Y}Use different gateways for each VM? (y/n)${N}"
read -p "Choice: " USE_DIFFERENT_GATEWAYS
declare -a VM_GATEWAYS
if [[ "$USE_DIFFERENT_GATEWAYS" =~ ^[Yy]$ ]]; then
echo -e "\n${Y}Select gateway number for each VM:${N}"
for (( i=1; i<=VM_COUNT; i++ )); do
while true; do
read -p "VM $i – choose gateway (1-${#GATEWAYS[@]}): " GATEWAY_NUM
if [[ "$GATEWAY_NUM" =~ ^[0-9]+$ ]] && [ "$GATEWAY_NUM" -ge 1 ] && [ "$GATEWAY_NUM" -le ${#GATEWAYS[@]} ]; then
VM_GATEWAYS[$i]=${GATEWAYS[$((GATEWAY_NUM-1))]}
echo -e " ${G}✅ Selected: ${VM_GATEWAYS[$i]}${N}"
break
else
echo -e "${R}❌ Invalid number${N}"
fi
done
done
else
echo -e "\n${Y}Choose gateway number for all VMs:${N}"
while true; do
read -p "Gateway number (1-${#GATEWAYS[@]}): " GATEWAY_NUM
if [[ "$GATEWAY_NUM" =~ ^[0-9]+$ ]] && [ "$GATEWAY_NUM" -ge 1 ] && [ "$GATEWAY_NUM" -le ${#GATEWAYS[@]} ]; then
SELECTED_GATEWAY=${GATEWAYS[$((GATEWAY_NUM-1))]}
for (( i=1; i<=VM_COUNT; i++ )); do
VM_GATEWAYS[$i]=$SELECTED_GATEWAY
done
echo -e "${G}✅ Selected: $SELECTED_GATEWAY${N}"
break
else
echo -e "${R}❌ Invalid number${N}"
fi
done
fi
# 4. Confirmation
echo -e "\n${B}📋 VM summary:${N}"
for (( i=1; i<=VM_COUNT; i++ )); do
echo -e " ${VM_BASE}${i} → ${VM_GATEWAYS[$i]}"
done
echo -e "\n${Y}Proceed? (y/n)${N}"
read -p "Choice: " CONFIRM
if [[ ! "$CONFIRM" =~ ^[Yy]$ ]]; then
echo -e "${R}Cancelled${N}"
exit 0
fi
# 5. Check template
if ! qvm-check --quiet "$BASE_TEMPLATE" 2>/dev/null; then
echo -e "${R}❌ Template $BASE_TEMPLATE not found${N}"
exit 1
fi
# 6. Remove old VMs
echo -e "${Y}🗑️ Removing old VMs...${N}"
for vm in $(qvm-ls --raw-data --fields NAME 2>/dev/null | grep "^${VM_BASE}[0-9]" | cut -d'|' -f1); do
qvm-kill "$vm" 2>/dev/null
qvm-remove --force "$vm" 2>/dev/null
done
echo -e "${G}✅ Removed${N}"
# 7. RAM pool
if ! mount | grep -q "$RAM_POOL_PATH"; then
echo -e "${Y}📁 Creating RAM pool...${N}"
sudo mkdir -p "$RAM_POOL_PATH"
sudo mount -t tmpfs -o size=$RAM_POOL_SIZE tmpfs "$RAM_POOL_PATH"
if ! qvm-pool ls | grep -q ram_pool; then
qvm-pool add ram_pool file -o dir_path="$RAM_POOL_PATH"
qvm-pool set ram_pool -o ephemeral_volatile=True 2>/dev/null
fi
echo -e "${G}✅ RAM pool ready${N}"
fi
# 8. Create VMs
declare -a CREATED_VMS
for (( i=1; i<=VM_COUNT; i++ )); do
VM_NAME="${VM_BASE}${i}"
GATEWAY="${VM_GATEWAYS[$i]}"
CLEAN_GATEWAY=$(echo "$GATEWAY" | cut -d'|' -f1 | tr -d '[:space:]')
echo ""
echo -e "${Y}📦 Creating: $VM_NAME (gateway: $CLEAN_GATEWAY)${N}"
if qvm-create -P ram_pool -t "$BASE_TEMPLATE" -l red "$VM_NAME" 2>/dev/null; then
echo -e " ${G}✅ Created${N}"
CREATED_VMS+=("$VM_NAME")
echo -e " ${Y}⚙️ Kernel parameters...${N}"
qvm-prefs "$VM_NAME" kernelopts "$KERNEL_OPTS" 2>/dev/null && echo -e " ${G}✅ Done${N}" || echo -e " ${Y}⚠️ Failed${N}"
echo -e " ${Y}🔌 Connecting gateway...${N}"
if qvm-prefs "$VM_NAME" netvm "$CLEAN_GATEWAY" 2>/dev/null; then
echo -e " ${G}✅ Connected to $CLEAN_GATEWAY${N}"
else
echo -e " ${R}❌ Connection failed${N}"
fi
qvm-features "$VM_NAME" no-monitor-layout 1 2>/dev/null || true
else
echo -e " ${R}❌ Failed to create $VM_NAME${N}"
fi
done
echo -e "\n${G}🎉 Created ${#CREATED_VMS[@]} VMs${N}"
# 9. Save script (without any auto-mount)
echo -e "\n${B}💾 Save configuration as script?${N}"
read -p "Choice (y/n): " SAVE_SCRIPT
if [[ "$SAVE_SCRIPT" =~ ^[Yy]$ ]]; then
cat > "$SAVE_SCRIPT_PATH" << EOT
#!/bin/bash
# Saved script – create Whonix VMs in RAM
BASE_TEMPLATE="$BASE_TEMPLATE"
VM_BASE="$VM_BASE"
RAM_POOL_PATH="$RAM_POOL_PATH"
RAM_POOL_SIZE="$RAM_POOL_SIZE"
KERNEL_OPTS="$KERNEL_OPTS"
G='\\033[0;32m'
Y='\\033[1;33m'
R='\\033[0;31m'
N='\\033[0m'
echo -e "\${G}🚀 Starting saved creation...\${N}"
if ! qvm-check --quiet "\$BASE_TEMPLATE" 2>/dev/null; then
echo -e "\${R}❌ Template not found\${N}"
exit 1
fi
for vm in \$(qvm-ls --raw-data --fields NAME 2>/dev/null | grep "^\${VM_BASE}[0-9]" | cut -d'|' -f1); do
qvm-kill "\$vm" 2>/dev/null
qvm-remove --force "\$vm" 2>/dev/null
done
if ! mount | grep -q "\$RAM_POOL_PATH"; then
sudo mkdir -p "\$RAM_POOL_PATH"
sudo mount -t tmpfs -o size=\$RAM_POOL_SIZE tmpfs "\$RAM_POOL_PATH"
qvm-pool add ram_pool file -o dir_path="\$RAM_POOL_PATH" 2>/dev/null || true
qvm-pool set ram_pool -o ephemeral_volatile=True 2>/dev/null || true
fi
echo -e "\${Y}📦 Creating VMs...\${N}"
EOT
for (( i=1; i<=VM_COUNT; i++ )); do
cat >> "$SAVE_SCRIPT_PATH" << EOT
VM_NAME="${VM_BASE}${i}"
GATEWAY="${VM_GATEWAYS[$i]}"
echo " Creating: \$VM_NAME (gateway: \$GATEWAY)"
if qvm-create -P ram_pool -t "\$BASE_TEMPLATE" -l red "\$VM_NAME" 2>/dev/null; then
echo -e " \${G}✅ Created\${N}"
qvm-prefs "\$VM_NAME" kernelopts "\$KERNEL_OPTS" 2>/dev/null
qvm-prefs "\$VM_NAME" netvm "\$GATEWAY" 2>/dev/null
qvm-features "\$VM_NAME" no-monitor-layout 1 2>/dev/null
else
echo -e " \${R}❌ Creation failed\${N}"
fi
EOT
done
cat >> "$SAVE_SCRIPT_PATH" << EOT
echo -e "\n\${G}✅ VMs created without auto-mount\${N}"
EOT
chmod +x "$SAVE_SCRIPT_PATH"
echo -e "${G}✅ Saved to: $SAVE_SCRIPT_PATH${N}"
fi
EOF
chmod +x "$DESKTOP_PATH/ram-pool-scripts/create-multi-ram-vm.sh"
# ========== cleanup-ram-pool.sh ==========
cat > "$DESKTOP_PATH/ram-pool-scripts/cleanup-ram-pool.sh" << 'EOF'
#!/bin/bash
RAM_POOL_PATH="$HOME/ram_pool"
echo "🧹 Cleaning RAM Pool..."
for vm in $(qvm-ls --raw-list 2>/dev/null | grep -E "whonix-work-ram|ram-"); do
qvm-kill "$vm" 2>/dev/null
qvm-remove --force "$vm" 2>/dev/null
done
qvm-pool remove ram_pool 2>/dev/null
mount | grep -q "$RAM_POOL_PATH" && sudo umount "$RAM_POOL_PATH"
[ -d "$RAM_POOL_PATH" ] && sudo rm -rf "$RAM_POOL_PATH"
echo "✅ Cleanup complete"
free -h
EOF
# ========== ram-pool-status.sh ==========
cat > "$DESKTOP_PATH/ram-pool-scripts/ram-pool-status.sh" << 'EOF'
#!/bin/bash
echo "=== RAM Pool Status ==="
mount | grep ram_pool && df -h "$HOME/ram_pool"
echo "VMs in RAM Pool:"
qvm-pool volumes ram_pool 2>/dev/null | grep -oE '^[^:]+' | sort -u
echo "Memory usage:"
free -h
EOF
chmod +x "$DESKTOP_PATH/ram-pool-scripts"/*.sh
ok "7 scripts created in $DESKTOP_PATH/ram-pool-scripts/"
pause
}
# ========
# 6. Amnesic Tmpfs Script
# ========
amnesic_script() {
clear
echo "╔═══════════════════════════════════════════════════════════╗"
echo "║ 🧠 Amnesic Tmpfs - RAM folders that disappear on shutdown ║"
echo "╚═══════════════════════════════════════════════════════════╝"
echo ""
echo "1) Apply Amnesic Tmpfs to a VM"
echo "2) Remove Amnesic Tmpfs from a VM"
echo "0) Back"
echo ""
read -p "Choose: " CHOICE
case $CHOICE in
1) apply_amnesic ;;
2) remove_amnesic ;;
0) return ;;
*) amnesic_script ;;
esac
}
apply_amnesic() {
clear
echo "╔═══════════════════════════════════════════════════════════╗"
echo "║ 📌 Apply Amnesic Tmpfs ║"
echo "╚═══════════════════════════════════════════════════════════╝"
echo ""
SCRIPT_PATH="$DESKTOP_PATH/apply-amnesic.sh"
cat > "$SCRIPT_PATH" << 'SCRIPTEOF'
#!/bin/bash
clear
echo "Apply Amnesic Tmpfs"
echo ""
echo "📋 Available VMs:"
echo "----------------------------------------"
qvm-ls --raw-list 2>/dev/null | grep -v dom0
echo "----------------------------------------"
echo ""
read -p "🔧 VM name: " TARGET_VM
[ -z "$TARGET_VM" ] && { echo "❌ No name"; read -p "Enter..."; exit 1; }
! qvm-ls "$TARGET_VM" >/dev/null 2>&1 && { echo "❌ Does not exist"; read -p "Enter..."; exit 1; }
echo ""
echo "🔧 Preparing: $TARGET_VM"
qvm-start "$TARGET_VM" 2>/dev/null
sleep 3
cat > /tmp/rc_local_content.txt << 'RCEOF'
#!/bin/bash
mkdir -p /var/log /var/cache /var/tmp /tmp /home/user/QubesIncoming
mount -t tmpfs tmpfs /var/log -o size=512M,mode=0755,nosuid,nodev
mount -t tmpfs tmpfs /var/cache -o size=512M,mode=0755,nosuid,nodev
mount -t tmpfs tmpfs /var/tmp -o size=256M,mode=1777,nosuid,nodev,noexec
mount -t tmpfs tmpfs /tmp -o size=512M,mode=1777,nosuid,nodev,noexec
mount -t tmpfs tmpfs /home/user -o size=2G,mode=0700,nosuid,nodev
cp -r /etc/skel/. /home/user/ 2>/dev/null
mkdir -p /home/user/QubesIncoming
chown -R user:user /home/user 2>/dev/null
exit 0
RCEOF
qvm-run --pass-io "$TARGET_VM" 'sudo tee /rw/config/rc.local' < /tmp/rc_local_content.txt 2>/dev/null
qvm-run "$TARGET_VM" "sudo chmod +x /rw/config/rc.local" 2>/dev/null
qvm-shutdown "$TARGET_VM" 2>/dev/null
rm -f /tmp/rc_local_content.txt
echo ""
echo "✅ Amnesic Tmpfs applied to: $TARGET_VM"
read -p "Enter..."
SCRIPTEOF
chmod +x "$SCRIPT_PATH"
bash "$SCRIPT_PATH"
rm -f "$SCRIPT_PATH"
}
remove_amnesic() {
clear
echo "╔═══════════════════════════════════════════════════════════╗"
echo "║ 🗑️ Remove Amnesic Tmpfs ║"
echo "╚═══════════════════════════════════════════════════════════╝"
echo ""
SCRIPT_PATH="$DESKTOP_PATH/remove-amnesic.sh"
cat > "$SCRIPT_PATH" << 'SCRIPTEOF'
#!/bin/bash
clear
echo "Remove Amnesic Tmpfs"
echo ""
echo "📋 Available VMs:"
echo "----------------------------------------"
qvm-ls --raw-list 2>/dev/null | grep -v dom0
echo "----------------------------------------"
echo ""
read -p "🔧 VM name: " TARGET_VM
[ -z "$TARGET_VM" ] && { echo "❌ No name"; read -p "Enter..."; exit 1; }
! qvm-ls "$TARGET_VM" >/dev/null 2>&1 && { echo "❌ Does not exist"; read -p "Enter..."; exit 1; }
echo ""
echo "🔧 Preparing: $TARGET_VM"
qvm-start "$TARGET_VM" 2>/dev/null
sleep 3
# Write just #!/bin/bash to rc.local
qvm-run "$TARGET_VM" "sudo bash -c 'echo \"#!/bin/bash\" > /rw/config/rc.local'" 2>/dev/null
qvm-run "$TARGET_VM" "sudo chmod +x /rw/config/rc.local" 2>/dev/null
qvm-shutdown "$TARGET_VM" 2>/dev/null
echo ""
echo "✅ Amnesic Tmpfs removed from: $TARGET_VM"
echo "📌 rc.local restored to default"
read -p "Enter..."
SCRIPTEOF
chmod +x "$SCRIPT_PATH"
bash "$SCRIPT_PATH"
rm -f "$SCRIPT_PATH"
}
# ========
# 7. Live Mode
# ========
live_mode() {
clear
REAL_USER=$(logname 2>/dev/null || echo "$SUDO_USER" 2>/dev/null || echo "$USER")
info "Real user: $REAL_USER"
warn "This script will create live.sh and execute it with sudo privileges"
warn "Execution may take several minutes"
warn "🟢 Overlay-Live + zram"
read -p "Proceed? (y/n): " confirm
[[ "$confirm" != "y" && "$confirm" != "Y" ]] && return
LIVE_SCRIPT="/home/$REAL_USER/Desktop/live.sh"
info "Creating $LIVE_SCRIPT ..."
cat > "$LIVE_SCRIPT" << 'EOOFF'
#!/bin/bash
# Qubes Dom0 Amnesic modes
# ⚠️ Make backup before running! Run as root: sudo ./amnesic.sh
echo "=== Qubes Dom0 Live Boot Setup ==="
#BOOT_UUID
BOOT_UUID=$(findmnt -n -o UUID /boot 2>/dev/null || echo "AUTO_BOOT_NOT_FOUND")
if [ "$BOOT_UUID" = "AUTO_BOOT_NOT_FOUND" ]; then
BOOT_UUID=$(blkid -s UUID -o value -d $(findmnt -n -o SOURCE /boot 2>/dev/null))
fi
# LUKS_UUID
LUKS_DEVICE=$(blkid -t TYPE="crypto_LUKS" -o device 2>/dev/null | head -n1 || echo "")
if [ -n "$LUKS_DEVICE" ]; then
LUKS_UUID=$(sudo cryptsetup luksUUID "$LUKS_DEVICE" 2>/dev/null)
else
LUKS_UUID="AUTO_LUKS_NOT_FOUND"
fi
# Latest XEN_PATH
XEN_PATH=$(ls /boot/xen*.gz 2>/dev/null | sort -V | tail -1 | xargs basename 2>/dev/null || echo "/xen-4.19.4.gz")
# Latest kernel/initramfs
LATEST_KERNEL=$(ls /boot/vmlinuz-*qubes*.x86_64 2>/dev/null | grep -E 'qubes\.fc[0-9]+' | sort -V | tail -1 | xargs basename)
LATEST_INITRAMFS=$(echo "/initramfs-${LATEST_KERNEL#vmlinuz-}.img")
# Max memory dom0
system_total_mb=$(xl info | grep total_memory | awk '{print $3}')
if [ -n "$system_total_mb" ] && [ "$system_total_mb" -gt 0 ] 2>/dev/null; then
# 80% total_memory
DOM0_MAX_MB=$((system_total_mb * 80 / 100))
DOM0_MAX_GB=$((DOM0_MAX_MB / 1024))
DOM0_MAX_RAM="dom0_mem=max:${DOM0_MAX_MB}M"
DOM0_MAX_GBG="${DOM0_MAX_GB}G"
else
DOM0_MAX_RAM="dom0_mem=max:10240M"
DOM0_MAX_GB="10"
DOM0_MAX_GBG="10G"
fi
# qubes_dom0-root
Qubes_Root=$(findmnt -n -o SOURCE /)
# /home/user
if [ -n "$SUDO_USER" ] && [ "$SUDO_USER" != "root" ]; then
USER_HOME=$(getent passwd "$SUDO_USER" | cut -d: -f6)
else
USER_HOME="$HOME"
fi
if [ ! -d "$USER_HOME" ]; then
echo "home dir '$USER_HOME' not found!" >&2
exit 1
fi
# === dom0 resize ===
REQUIRED_FREE_GB=100
MIN_DOM0_SIZE_GB=40
get_dom0_size_gb() {
local size_bytes
size_bytes=$(df -B1 /dev/mapper/qubes_dom0-root 2>/dev/null | awk 'NR==2 {print $2}')
if [[ -z "$size_bytes" ]]; then
echo "Warning: failed to determine dom0 size" >&2
return 1
fi
echo "$((size_bytes / 1024 / 1024 / 1024))"
return 0
}
get_vg_free_gb() {
local lv_size data_pct
lv_size=$(lvs --noheadings --nosuffix --units b -o lv_size qubes_dom0/vm-pool 2>/dev/null | tr -dc '0-9')
data_pct=$(lvs --noheadings -o data_percent qubes_dom0/vm-pool 2>/dev/null | tr ',' '.' | tr -dc '0-9.')
if [[ -z "$lv_size" || -z "$data_pct" ]]; then
echo "Warning: failed to determine free space in vm-pool" >&2
return 1
fi
local free_gb
free_gb=$(awk "BEGIN { printf \"%.0f\", ($lv_size * (100 - $data_pct) / 100) / 1024 / 1024 / 1024 }")
echo "$free_gb"
return 0
}
main() {
if [[ "$EUID" -ne 0 ]]; then
echo "Warning: This script should be run as root (sudo)" >&2
# Changed from exit 1 to just warning
fi
local dom0_size_gb vg_free_gb
dom0_size_gb=$(get_dom0_size_gb) || dom0_size_gb="unknown"
vg_free_gb=$(get_vg_free_gb) || vg_free_gb="unknown"
echo "Current dom0 size: ${dom0_size_gb} GB"
echo "Free space in VM pool: ${vg_free_gb} GB"
if [[ "$vg_free_gb" != "unknown" ]] && ((vg_free_gb < REQUIRED_FREE_GB)); then
echo "Info: free space is less than ${REQUIRED_FREE_GB} GB (available: ${vg_free_gb} GB) - skipping resize"
# Changed from exit 0 to continue
elif [[ "$dom0_size_gb" != "unknown" ]] && ((dom0_size_gb >= MIN_DOM0_SIZE_GB)); then
echo "Info: dom0 size is already ${dom0_size_gb} GB (>= ${MIN_DOM0_SIZE_GB} GB) - skipping resize"
# Changed from exit 0 to continue
else
echo "Conditions met. Starting dom0 resize..."
if lvresize --size 40G /dev/mapper/qubes_dom0-root; then
resize2fs /dev/mapper/qubes_dom0-root
lvresize -L +20G qubes_dom0/root-pool
echo "Done. New dom0 size: $(get_dom0_size_gb) GB"
else
echo "Warning: dom0 resize failed - continuing with other commands"
fi
fi
echo "--- dom0 resize completed ---"
}
main "$@"
# === swapoff ===
sudo sed -i '/\/dev\/mapper\/swap/!{/^[[:space:]]*#/!{/\/s/^/# /}}' /etc/fstab
sudo sed -i '/\/dev\/mapper\/swap/b; /[[:space:]]\+swap[[:space:]]\+/s/^/# /' /etc/fstab
# === harden autostart ===
mkdir -p "$USER_HOME/.config"
if [ ! -f "$USER_HOME/.config/harden.sh" ]; then
cat > "$USER_HOME/.config/harden.sh" << 'EOF'
#!/bin/bash
sleep 1
if findmnt -n -o SOURCE / | grep -qE "(overlay|/dev/zram0)"; then
notify-send --expire-time=20000 "Amnesic session is running" "dom0 mode: $(findmnt -n -o SOURCE /)" --icon=dialog-information
sudo sysctl -w kernel.sysrq=0
sudo sysctl -w kernel.perf_event_paranoid=3
sudo sysctl -w kernel.kptr_restrict=2
sudo sysctl -w kernel.panic=5
sudo sysctl -w fs.protected_regular=2
sudo sysctl -w fs.protected_fifos=2
sudo sysctl -w kernel.printk="3 3 3 3"
sudo sysctl -w kernel.kexec_load_disabled=1
sudo sysctl -w kernel.io_uring_disabled=2
sudo chattr +i /boot/grub2/grub.cfg
sudo chattr +i /boot
else
sudo chattr -i /boot/grub2/grub.cfg
sudo chattr -i /boot
fi
EOF
chmod 755 "$USER_HOME/.config/harden.sh"
echo "Created harden.sh"
else
echo "harden.sh already exists, skipping"
fi
# === autostart desktop entry ===
mkdir -p "$USER_HOME/.config/autostart"
if [ ! -f "$USER_HOME/.config/autostart/harden.desktop" ]; then
cat > "$USER_HOME/.config/autostart/harden.desktop" << EOF
[Desktop Entry]
Encoding=UTF-8
Version=0.9.4
Type=Application
Name=harden
Comment=
Exec=$USER_HOME/.config/harden.sh
OnlyShowIn=XFCE;
RunHook=0
StartupNotify=false
Terminal=false
Hidden=false
EOF
echo "Created harden.desktop"
else
echo "harden.desktop already exists, skipping"
fi
# === Dracut module directories ===
DIR_OVERLAY_CRYPT=/usr/lib/dracut/modules.d/90overlay-crypt
DIR_RAMBOOT=/usr/lib/dracut/modules.d/90ramboot
DIR_OVERLAY=/usr/lib/dracut/modules.d/90overlayfs-root
DIR_RAMWIPE=/usr/lib/dracut/modules.d/40ram-wipe
for d in "$DIR_RAMBOOT" "$DIR_OVERLAY" "$DIR_RAMWIPE" "$DIR_OVERLAY_CRYPT"; do
if [ ! -d "$d" ]; then
mkdir -p "$d"
echo "Created $(basename "$d")"
else
echo "$(basename "$d") already exists, skipping"
fi
done
# === 90overlay-crypt/module-setup.sh ===
if [ ! -f "$DIR_OVERLAY_CRYPT/module-setup.sh" ]; then
cat > "$DIR_OVERLAY_CRYPT/module-setup.sh" << 'EOF'
#!#!/bin/bash
check() {
require_binaries cryptsetup || return 1
require_binaries losetup || return 1
require_binaries mkfs.ext4 || return 1
return 0
}
depends() {
return 0
}
installkernel() {
hostonly='' instmods overlay 2>/dev/null || true
hostonly='' instmods dm-crypt 2>/dev/null || true
}
install() {
inst_multiple cryptsetup losetup mkfs.ext4 dd modprobe mount umount shred
inst_hook pre-pivot 10 "$moddir/overlay-crypt.sh"
}
EOF
chmod 755 "$DIR_OVERLAY_CRYPT/module-setup.sh"
echo "Created 90overlay-crypt/module-setup.sh"
else
echo "90overlay-crypt/module-setup.sh already exists, skipping"
fi
# === 90overlay-crypt/overlay-crypt.sh ===
if [ ! -f "$DIR_OVERLAY_CRYPT/overlay-crypt.sh" ]; then
cat > "$DIR_OVERLAY_CRYPT/overlay-crypt.sh" << 'EOF'
#!/bin/bash
. /lib/dracut-lib.sh
if ! getargbool 0 cryptovl ; then
return
fi
modprobe overlay 2>/dev/null || true
modprobe dm-crypt 2>/dev/null || true
#
mount -o remount,ro /sysroot 2>/dev/null || true
#
mkdir -p /live/image
mount --bind /sysroot /live/image
umount /sysroot
#
dd if=/dev/urandom bs=64 count=1 of=/dev/shm/overlay-key status=none
chmod 600 /dev/shm/overlay-key
# create 20 GB size (seek)
mkdir -p /var/lib
dd if=/dev/zero of=/var/lib/overlay-crypt.img bs=1M count=0 seek=20480 status=none
#
losetup -f
LOOP_DEV=$(losetup -f --show /var/lib/overlay-crypt.img)
#
cryptsetup luksFormat --type luks2 \
--cipher aes-xts-plain64 --key-size 512 \
--hash sha256 --pbkdf pbkdf2 --pbkdf-force-iterations 1000 \
--batch-mode --key-file /dev/shm/overlay-key "$LOOP_DEV"
#
cryptsetup open --type luks2 --key-file /dev/shm/overlay-key "$LOOP_DEV" overlaycrypt
#
mkfs.ext4 -F -L "overlaycrypt" /dev/mapper/overlaycrypt
#
mkdir -p /cow
mount -o noatime,nodiratime,nobarrier /dev/mapper/overlaycrypt /cow
mkdir -p /cow/work /cow/rw
#
mount -t overlay -o noatime,nodiratime,volatile,lowerdir=/live/image,upperdir=/cow/rw,workdir=/cow/work,default_permissions,relatime overlay /sysroot
#
mkdir -p /sysroot/live/cow /sysroot/live/image
mount --bind /cow/rw /sysroot/live/cow
mount --bind /live/image /sysroot/live/image
#
umount /cow 2>/dev/null || true
umount /live/image 2>/dev/null || true
#
shred -u /dev/shm/overlay-key 2>/dev/null || rm -f /dev/shm/overlay-key
EOF
chmod 755 "$DIR_OVERLAY_CRYPT/overlay-crypt.sh"
echo "Created 90overlay-crypt/overlay-crypt.sh"
else
echo "90overlay-crypt/overlay-crypt.sh already exists, skipping"
fi
# === 90ramboot/module-setup.sh ===
if [ ! -f "$DIR_RAMBOOT/module-setup.sh" ]; then
cat > "$DIR_RAMBOOT/module-setup.sh" << 'EOF'
#!/usr/bin/bash
check() {
return 0
}
depends() {
return 0
}
install() {
inst_simple "$moddir/zram-mount.sh"
inst_hook cleanup 00 "$moddir/zram-mount.sh"
}
EOF
chmod 755 "$DIR_RAMBOOT/module-setup.sh"
echo "Created 90ramboot/module-setup.sh"
else
echo "90ramboot/module-setup.sh already exists, skipping"
fi
# === 90ramboot/zram-mount.sh ===
if [ ! -f "$DIR_RAMBOOT/zram-mount.sh" ]; then
cat > "$DIR_RAMBOOT/zram-mount.sh" << EOF
#!/bin/sh
. /lib/dracut-lib.sh
if ! getargbool 0 rootzram ; then
return
fi
mkdir -p /mnt
umount /sysroot
mount -o ro $Qubes_Root /mnt
modprobe zram
echo $DOM0_MAX_GBG > /sys/block/zram0/disksize
#/mnt/usr/sbin/mkfs.xfs -f -m reflink=0 /dev/zram0
/mnt/usr/sbin/mkfs.ext2 /dev/zram0
mount -o nodev,nosuid,noatime,nodiratime /dev/zram0 /sysroot
EXCLUDES=("dev" "proc" "sys" "tmp" "run" "mnt" "media" "lost+found" "var/log")
FIND_EXPR=()
for dir in "${EXCLUDES[@]}"; do
FIND_EXPR+=(-name "$dir" -o)
done
unset 'FIND_EXPR[${#FIND_EXPR[@]}-1]'
find /mnt -mindepth 1 -maxdepth 1 ! \( "${FIND_EXPR[@]}" \) -exec cp -a {} /sysroot \;
for dir in "${EXCLUDES[@]}"; do
mkdir -p "/sysroot/$dir"
done
# Disable swap
if [ -f /sysroot/etc/fstab ]; then
sed -i '/[ \t]swap[ \t]/d' /sysroot/etc/fstab
fi
if [ -d /sysroot/etc/systemd/system ]; then
ln -sf /dev/null /sysroot/etc/systemd/system/dev-mapper-swap.device
fi
umount /mnt
exit 0
EOF
chmod 755 "$DIR_RAMBOOT/zram-mount.sh"
echo "Created 90ramboot/zram-mount.sh"
else
echo "90ramboot/zram-mount.sh already exists, skipping"
fi
# === 90overlayfs-root/module-setup.sh ===
if [ ! -f "$DIR_OVERLAY/module-setup.sh" ]; then
cat > "$DIR_OVERLAY/module-setup.sh" << 'EOF'
#!/bin/bash
check() {
[ -d /lib/modules/$kernel/kernel/fs/overlayfs ] || return 1
}
depends() {
return 0
}
installkernel() {
hostonly='' instmods overlay
}
install() {
inst_hook pre-pivot 10 "$moddir/overlay-mount.sh"
}
EOF
chmod 755 "$DIR_OVERLAY/module-setup.sh"
echo "Created 90overlayfs-root/module-setup.sh"
else
echo "90overlayfs-root/module-setup.sh already exists, skipping"
fi
# === 90overlayfs-root/overlay-mount.sh ===
if [ ! -f "$DIR_OVERLAY/overlay-mount.sh" ]; then
cat > "$DIR_OVERLAY/overlay-mount.sh" << 'EOF'
#!/bin/sh
. /lib/dracut-lib.sh
if ! getargbool 0 rootovl ; then
return
fi
modprobe overlay
mount -o remount,nolock,noatime $NEWROOT
mkdir -p /live/image
mount --bind $NEWROOT /live/image
umount $NEWROOT
mkdir /cow
mount -n -t tmpfs -o mode=0755,size=100%,nr_inodes=500k,noexec,nodev,nosuid,noatime,nodiratime tmpfs /cow
mkdir /cow/work /cow/rw
mount -t overlay -o noatime,nodiratime,volatile,lowerdir=/live/image,upperdir=/cow/rw,workdir=/cow/work,default_permissions,relatime overlay $NEWROOT
mkdir -p $NEWROOT/live/cow
mkdir -p $NEWROOT/live/image
mount --bind /cow/rw $NEWROOT/live/cow
umount /cow
mount --bind /live/image $NEWROOT/live/image
umount /live/image
umount $NEWROOT/live/cow
EOF
chmod 755 "$DIR_OVERLAY/overlay-mount.sh"
echo "Created 90overlayfs-root/overlay-mount.sh"
else
echo "90overlayfs-root/overlay-mount.sh already exists, skipping"
fi
# === ramboot dracut.conf ===
if [ ! -f /etc/dracut.conf.d/ramboot.conf ]; then
cat > /etc/dracut.conf.d/ramboot.conf << 'EOF'
add_drivers+=" zram "
add_dracutmodules+=" ramboot "
EOF
echo "Created ramboot.conf"
else
echo "ramboot.conf already exists, skipping"
fi
# === 40ram-wipe/module-setup.sh ===
if [ ! -f "$DIR_RAMWIPE/module-setup.sh" ]; then
cat > "$DIR_RAMWIPE/module-setup.sh" << 'EOF'
#!/bin/bash
# -*- mode: shell-script; indent-tabs-mode: nil; sh-basic-offset: 4; -*-
# ex: ts=8 sw=4 sts=4 et filetype=sh
## Copyright (C) 2023 - 2025 ENCRYPTED SUPPORT LLC
## See the file COPYING for copying conditions.
check() {
require_binaries sync || return 1
require_binaries sleep || return 1
require_binaries dmsetup || return 1
return 0
}
depends() {
return 0
}
install() {
inst_simple "/usr/libexec/ram-wipe/ram-wipe-lib.sh" "/lib/ram-wipe-lib.sh"
inst_multiple sync
inst_multiple sleep
inst_multiple dmsetup
inst_hook shutdown 40 "$moddir/wipe-ram.sh"
inst_hook cleanup 80 "$moddir/wipe-ram-needshutdown.sh"
}
installkernel() {
return 0
}
EOF
chmod +x "$DIR_RAMWIPE/module-setup.sh"
echo "Created 40ram-wipe/module-setup.sh"
else
echo "40ram-wipe/module-setup.sh already exists, skipping"
fi
# === 40ram-wipe/wipe-ram-needshutdown.sh ===
if [ ! -f "$DIR_RAMWIPE/wipe-ram-needshutdown.sh" ]; then
cat > "$DIR_RAMWIPE/wipe-ram-needshutdown.sh" << 'EOF'
#!/bin/sh
## Copyright (C) 2023 - 2025 ENCRYPTED SUPPORT LLC
## See the file COPYING for copying conditions.
type getarg >/dev/null 2>&1 || . /lib/dracut-lib.sh
. /lib/ram-wipe-lib.sh
ram_wipe_check_needshutdown() {
#local kernel_wiperam_setting
kernel_wiperam_setting="$(getarg wiperam)"
if [ "$kernel_wiperam_setting" = "skip" ]; then
force_echo "wipe-ram-needshutdown.sh: Skip, because wiperam=skip kernel parameter detected, OK."
return 0
fi
true "wipe-ram-needshutdown.sh: Calling dracut function need_shutdown to drop back into initramfs at shutdown, OK."
need_shutdown
return 0
}
ram_wipe_check_needshutdown
EOF
chmod +x "$DIR_RAMWIPE/wipe-ram-needshutdown.sh"
echo "Created 40ram-wipe/wipe-ram-needshutdown.sh"
else
echo "40ram-wipe/wipe-ram-needshutdown.sh already exists, skipping"
fi
# === 40ram-wipe/wipe-ram.sh ===
if [ ! -f "$DIR_RAMWIPE/wipe-ram.sh" ]; then
cat > "$DIR_RAMWIPE/wipe-ram.sh" << 'EOF'
#!/bin/sh
## Copyright (C) 2023 - 2025 ENCRYPTED SUPPORT LLC
## See the file COPYING for copying conditions.
## Credits:
## First version by @friedy10.
## https://github.com/friedy10/dracut/blob/master/modules.d/40sdmem/wipe.sh
. /lib/ram-wipe-lib.sh
drop_caches() {
sync
echo 3 > /proc/sys/vm/drop_caches
sync
}
ram_wipe() {
#local kernel_wiperam_setting
kernel_wiperam_setting="$(getarg wiperam)"
if [ "$kernel_wiperam_setting" = "skip" ]; then
force_echo "wipe-ram.sh: Skip, because wiperam=skip kernel parameter detected, OK."
return 0
fi
force_echo "wipe-ram.sh: RAM extraction attack defense... Starting RAM wipe pass during shutdown..."
drop_caches
force_echo "wipe-ram.sh: RAM wipe pass completed, OK."
}
ram_wipe
EOF
chmod +x "$DIR_RAMWIPE/wipe-ram.sh"
echo "Created 40ram-wipe/wipe-ram.sh"
else
echo "40ram-wipe/wipe-ram.sh already exists, skipping"
fi
# === ram-wipe dracut.conf ===
if [ ! -f /etc/dracut.conf.d/30-ram-wipe.conf ]; then
cat > /etc/dracut.conf.d/30-ram-wipe.conf << 'EOF'
add_dracutmodules+=" ram-wipe "
EOF
echo "Created 30-ram-wipe.conf"
else
echo "30-ram-wipe.conf already exists, skipping"
fi
# === ram-wipe-lib.sh ===
DIR_RAMWIPE_LIB=/usr/libexec/ram-wipe
if [ ! -d "$DIR_RAMWIPE_LIB" ]; then
mkdir -p "$DIR_RAMWIPE_LIB"
fi
if [ ! -f "$DIR_RAMWIPE_LIB/ram-wipe-lib.sh" ]; then
cat > "$DIR_RAMWIPE_LIB/ram-wipe-lib.sh" << 'EOF'
#!/bin/sh
## Copyright (C) 2023 - 2025 ENCRYPTED SUPPORT LLC
## See the file COPYING for copying conditions.
if [ -z "$DRACUT_SYSTEMD" ]; then
force_echo() {
echo "<28>dracut INFO: $*" > /dev/kmsg
echo "dracut INFO: $*" >&2
}
else
force_echo() {
echo "INFO: $*" >&2
}
fi
EOF
chmod +x "$DIR_RAMWIPE_LIB/ram-wipe-lib.sh"
echo "Created ram-wipe-lib.sh"
else
echo "ram-wipe-lib.sh already exists, skipping"
fi
# Update INITRAMFS
dracut --verbose --force
# Create GRUB custom
echo "Creating GRUB custom ..."
cat > /etc/grub.d/40_custom << EOF
#!/usr/bin/sh
exec tail -n +3 \$0
menuentry 'Qubes Encrypted-Overlay Amnesic Mode' --class qubes --class gnu-linux --class gnu --class os --class xen \$menuentry_id_option 'xen-gnulinux-simple-/dev/mapper/qubes_dom0-root' {
insmod part_gpt
insmod ext2
search --no-floppy --fs-uuid --set=root $BOOT_UUID
echo 'Loading Xen ...'
if [ "\$grub_platform" = "pc" -o "\$grub_platform" = "" ]; then
xen_rm_opts=
else
xen_rm_opts="no-real-mode edd=off"
fi
insmod multiboot2
multiboot2 /$XEN_PATH placeholder console=none dom0_mem=min:1024M $DOM0_MAX_RAM ucode=scan smt=off gnttab_max_frames=2048 gnttab_max_maptrack_frames=4096 \${xen_rm_opts}
echo 'Loading Linux $LATEST_KERNEL ...'
module2 /$LATEST_KERNEL placeholder root=/dev/mapper/qubes_dom0-root ro rd.luks.uuid=$LUKS_UUID rd.lvm.lv=qubes_dom0/root rd.lvm.lv=qubes_dom0/swap plymouth.ignore-serial-consoles rhgb cryptovl quiet module.sig_enforce=1 bootscrub=on
echo 'Loading initial ramdisk ...'
insmod multiboot2
module2 --nounzip $LATEST_INITRAMFS
}
menuentry 'Qubes Zram-Live Amnesic Mode' --class qubes --class gnu-linux --class gnu --class os --class xen \$menuentry_id_option 'xen-gnulinux-simple-/dev/mapper/qubes_dom0-root' {
insmod part_gpt
insmod ext2
search --no-floppy --fs-uuid --set=root $BOOT_UUID
echo 'Loading Xen ...'
if [ "\$grub_platform" = "pc" -o "\$grub_platform" = "" ]; then
xen_rm_opts=
else
xen_rm_opts="no-real-mode edd=off"
fi
insmod multiboot2
multiboot2 /$XEN_PATH placeholder console=none dom0_mem=min:1024M $DOM0_MAX_RAM ucode=scan smt=off gnttab_max_frames=2048 gnttab_max_maptrack_frames=4096 \${xen_rm_opts}
echo 'Loading Linux $LATEST_KERNEL ...'
module2 /$LATEST_KERNEL placeholder root=/dev/mapper/qubes_dom0-root ro rd.luks.uuid=$LUKS_UUID rd.lvm.lv=qubes_dom0/root rd.lvm.lv=qubes_dom0/swap plymouth.ignore-serial-consoles rhgb rootzram quiet module.sig_enforce=1 bootscrub=on
echo 'Loading initial ramdisk ...'
insmod multiboot2
module2 --nounzip $LATEST_INITRAMFS
}
EOF
chmod 755 /etc/grub.d/40_custom
# Update GRUB
grub2-mkconfig -o /boot/grub2/grub.cfg
# Cleanup
dnf clean all
journalctl --vacuum-time=1d
rm -rf /var/cache/dnf/* 2>/dev/null || true
echo
echo "Done!"
echo "✓ ALL STEPS COMPLETED SUCCESSFULLY! Reboot Qubes OS, select one of the new GRUB options (Encrypted-Overlay or Zram-Live), and clone your appVMs to the varlibqubes pool to run in full amnesia mode."
EOOFF
chmod +x "$LIVE_SCRIPT"
ok "Created $LIVE_SCRIPT"
info "Executing live.sh with sudo..."
sudo bash "$LIVE_SCRIPT"
info "Deleting $LIVE_SCRIPT..."
rm -f "$LIVE_SCRIPT"
ok "File deleted"
pause
}
# ========
# 8. Emergency Scripts (BusKill)
# ========
emergency_script() {
mkdir -p "$DESKTOP_PATH/emergency"
# ========== Dead Man's Switch script ==========
cat > "$DESKTOP_PATH/emergency/deadman-switch.sh" << 'EOF'
#!/bin/bash
# ⚠️ Dead Man's Switch - Delayed destruction using BusKill
echo "╔═══════════════════════════════════════════════════════════╗"
echo "║ 💀 Dead Man's Switch - Delayed Destruction (BusKill) 💀 ║"
echo "╚═══════════════════════════════════════════════════════════╝"
echo ""
read -p "Timeout in minutes before destruction: " T
[ -z "$T" ] && T=5
read -p "Type YES for final confirmation: " C
[ "$C" != "YES" ] && { echo "Cancelled"; exit 1; }
# Print cancel command first
echo ""
echo "╔═══════════════════════════════════════════════════════════╗"
echo "║ 📌 Cancel command (save it now before continuing): ║"
echo "║ sudo /usr/local/bin/cancel ║"
echo "╚═══════════════════════════════════════════════════════════╝"
echo ""
# Check for BusKill and install if not present
if [ ! -f "/etc/qubes-rpc/buskill.selfDestruct" ]; then
echo "⚠️ BusKill not installed. Installing automatically..."
sudo tee /etc/qubes-rpc/buskill.selfDestruct > /dev/null << 'BUSKILL'
#!/bin/bash
# 1. Delete Qubes folder from EFI
EFI_PART=$(findmnt -n -o SOURCE /boot/efi 2>/dev/null)
if [ -n "$EFI_PART" ]; then
sudo mount $EFI_PART /mnt
sudo rm -rf /mnt/EFI/qubes
sudo umount /mnt
echo "✅ Deleted Qubes folder from $EFI_PART"
else
echo "⚠️ EFI partition not found"
fi
# Delete boot entry from NVRAM
if [ -d "/sys/firmware/efi" ]; then
# Find Qubes entry and delete it
sudo efibootmgr | grep -i qubes | while read -r line; do
bootnum=
sudo efibootmgr -b "" -B
echo "✅ Deleted boot entry: Boot"
done
fi
# 2. Destroy /boot partition
BOOT_PART=$(findmnt -n -o SOURCE /boot)
echo "💀 Starting destruction of $BOOT_PART"
sudo dd if=/dev/urandom of=$BOOT_PART bs=1M status=progress &
WHICH="/usr/bin/which"
sudo qvm-kill --all &
CRYPTSETUP=$($WHICH --skip-alias cryptsetup) || echo "ERROR: Unable to find cryptsetup"
LS=$($WHICH --skip-alias ls) || echo "ERROR: Unable to find ls"
CP=$($WHICH --skip-alias cp) || echo "ERROR: Unable to find cp"
MV=$($WHICH --skip-alias mv) || echo "ERROR: Unable to find mv"
LN=$($WHICH --skip-alias ln) || echo "ERROR: Unable to find ln"
MKDIR=$($WHICH --skip-alias mkdir) || echo "ERROR: Unable to find mkdir"
MOUNT=$($WHICH --skip-alias mount) || echo "ERROR: Unable to find mount"
CAT=$($WHICH --skip-alias cat) || echo "ERROR: Unable to find cat"
GREP=$($WHICH --skip-alias grep) || echo "ERROR: Unable to find grep"
ECHO=$($WHICH --skip-alias echo) || echo "ERROR: Unable to find echo"
AWK=$($WHICH --skip-alias awk) || echo "ERROR: Unable to find awk"
HEAD=$($WHICH --skip-alias head) || echo "ERROR: Unable to find head"
LSBLK=$($WHICH --skip-alias lsblk) || echo "ERROR: Unable to find lsblk"
OD=$($WHICH --skip-alias od) || echo "ERROR: Unable to find od"
SUDO=$($WHICH --skip-alias sudo) || echo "ERROR: Unable to find sudo"
CHMOD=$($WHICH --skip-alias chmod) || echo "ERROR: Unable to find chmod"
BASH=$($WHICH --skip-alias bash) || echo "ERROR: Unable to find bash"
NOHUP=$($WHICH --skip-alias nohup) || echo "ERROR: Unable to find nohup"
SLEEP=$($WHICH --skip-alias sleep) || echo "ERROR: Unable to find sleep"
QVM_KILL=$($WHICH --skip-alias qvm-kill) || echo "ERROR: Unable to find qvm-kill"
CHROOT=$($WHICH --skip-alias chroot) || echo "ERROR: Unable to find chroot"
SYNC=$($WHICH --skip-alias sync) || echo "ERROR: Unable to find sync"
LDD=$($WHICH --skip-alias ldd) || echo "ERROR: Unable to find ldd"
XARGS=$($WHICH --skip-alias xargs) || echo "ERROR: Unable to find xargs"
CHROOT_PATH='/dev/shm/buskill/chroot'
DIE_SCRIPT='/usr/bin/die.sh'
if [[ $EUID -ne 0 ]]; then
exec $SUDO $BASH "$0" "$@"
fi
$NOHUP $SLEEP 60 && echo o > /proc/sysrq-trigger &
$NOHUP $SLEEP 61 && echo b > /proc/sysrq-trigger &
$NOHUP $SLEEP 62 && echo b > /proc/sysrq-trigger &
$QVM_KILL --all &
echo "INFO: shredding LUKS header"
writes=''
oldIFS="$IFS"
IFS=$'\n'
for line in $( $LSBLK --list --output 'UUID,FSTYPE' | $GREP 'crypt' ); do
device="/dev/disk/by-uuid/$(echo "$line" | $AWK '{print $1}')"
echo -e "\t$device"
$CRYPTSETUP luksErase --batch-mode "$device" || $HEAD --bytes 20M /dev/urandom > $device &
writes="$writes $!"
luksVersion=$($OD --skip-bytes 6 --read-bytes 2 --format d2 --endian=big --address-radix "n" "$device")
if [[ $luksVersion -eq 1 ]]; then
payloadOffset=$($OD --skip-bytes 104 --read-bytes 4 --format d4 --endian=big --address-radix "n" "$device")
luksEndByte=$(( 512 * $payloadOffset ))
elif [[ $luksVersion -eq 2 ]]; then
hdr_size=$($OD --skip-bytes 8 --read-bytes 8 --format d8 --endian=big --address-radix "n" "$device")
luksEndByte=$(( 2 * $hdr_size ))
else
luksEndByte=20971520
fi
exec 5<> "$device"
$HEAD --bytes "$luksEndByte" /dev/urandom >&5 &
writes="$writes $!"
exec 5>&-
done
IFS="$oldIFS"
writes=$(echo "$writes" | $XARGS)
if [[ $writes ]]; then wait $writes; fi
sync; echo 3 > /proc/sys/vm/drop_caches
$MKDIR -p "$CHROOT_PATH/proc"
$MKDIR -p "$CHROOT_PATH/sys"
$MKDIR -p "$CHROOT_PATH/dev"
$MKDIR -p "$CHROOT_PATH/pts"
$MKDIR -p "$CHROOT_PATH/bin"
$MKDIR -p "$CHROOT_PATH/lib64"
$MKDIR -p "$CHROOT_PATH/lib/systemd"
$MKDIR -p "$CHROOT_PATH/usr/sbin"
$MKDIR -p "$CHROOT_PATH/usr/bin"
$MOUNT -t proc none "$CHROOT_PATH/proc"
$MOUNT -t sysfs none "$CHROOT_PATH/sys"
$MOUNT -o bind /dev "$CHROOT_PATH/dev"
$MOUNT -o bind /dev/pts "$CHROOT_PATH/dev/pts"
binaries="$BASH $CRYPTSETUP $ECHO $LS $NOHUP $SLEEP $XARGS $SYNC"
for binary in $binaries; do
$CP -vf "$binary" "$CHROOT_PATH/$binary"
libs="$( $LDD "$binary" | $GREP -Eo '/lib.*\.so(\.[0-9]*)*' )"
for lib in $libs; do
$CP -vf "$lib" "$CHROOT_PATH/$lib"
done
done
$MV "$CHROOT_PATH/lib" "$CHROOT_PATH/usr/lib"
$LN -s "usr/lib" "$CHROOT_PATH/lib"
$CAT << EOFF > "$CHROOT_PATH/$DIE_SCRIPT"
#!$BASH
$NOHUP $SLEEP 10 && echo o > /proc/sysrq-trigger &
$NOHUP $SLEEP 11 && echo o > /proc/sysrq-trigger &
echo "INFO: removing decryption keys from memory"
waits=''
for device in \$( $LS -1 "/dev/mapper" ); do
echo -e "\t\$device";
$CRYPTSETUP luksSuspend "\$device" &
waits="\$waits \$!"
done
echo "INFO: finished luksSuspend calls"
waits=\$(echo "\$waits" | $XARGS)
if [[ \$waits ]]; then wait \$waits; fi
echo "INFO: finished waiting"
sync; echo 3 > /proc/sys/vm/drop_caches
echo "INFO: finished syncing"
echo "INFO: Rebooting"
echo o > /proc/sysrq-trigger
EOFF
$CHMOD +x "$CHROOT_PATH/$DIE_SCRIPT"
$CHROOT "$CHROOT_PATH" "$DIE_SCRIPT"
BUSKILL
sudo chmod +x /etc/qubes-rpc/buskill.selfDestruct
sudo tee /etc/qubes-rpc/policy/buskill.selfDestruct > /dev/null << 'POLICY'
sys-usb dom0 allow
@anyvm @anyvm deny
POLICY
echo "✅ BusKill installed automatically"
else
echo "✅ BusKill already exists"
fi
# nuke script
sudo tee /usr/local/bin/nuke > /dev/null << 'NUKE'
#!/bin/bash
/etc/qubes-rpc/buskill.selfDestruct
NUKE
sudo chmod 755 /usr/local/bin/nuke
# cancel script
sudo tee /usr/local/bin/cancel > /dev/null << 'CANCEL'
#!/bin/bash
sudo systemctl stop deadman.service
echo "✓ Destruction cancelled"
CANCEL
sudo chmod 755 /usr/local/bin/cancel
echo "ALL ALL=(root) NOPASSWD: /bin/systemctl stop deadman.service" | sudo tee /etc/sudoers.d/cancel > /dev/null
sudo chmod 440 /etc/sudoers.d/cancel
# systemd service
sudo tee /etc/systemd/system/deadman.service > /dev/null << SERVICE
[Unit]
Description=Dead Man's Switch (BusKill)
After=multi-user.target
[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/bin/sh -c "sleep ${T}m && /usr/local/bin/nuke"
[Install]
WantedBy=multi-user.target
SERVICE
# Enable service
echo "📦 Enabling service..."
sudo systemctl daemon-reload
sudo systemctl enable deadman.service
sudo systemctl start deadman.service --no-block 2>/dev/null &
echo ""
echo "╔═══════════════════════════════════════════════════════════╗"
echo "║ ✅ Dead Man's Switch activated successfully ║"
echo "╠═══════════════════════════════════════════════════════════╣"
echo "║ Timeout: ${T} minutes ║"
echo "║ To cancel: sudo /usr/local/bin/cancel ║"
echo "║ To check: systemctl status deadman.service ║"
echo "╚═══════════════════════════════════════════════════════════╝"
EOF
# ========== Full uninstall script ==========
cat > "$DESKTOP_PATH/emergency/uninstall-deadman.sh" << 'UNINSTALL'
#!/bin/bash
# Complete uninstallation of Dead Man's Switch
echo "╔═══════════════════════════════════════════════════════════╗"
echo "║ 🗑️ Complete Uninstall of Dead Man's Switch ║"
echo "╚═══════════════════════════════════════════════════════════╝"
echo ""
echo "⚠️ Warning: This will completely remove Dead Man's Switch"
echo " (it will not remove BusKill itself, only the timer)"
echo ""
read -p "Type YES to confirm: " CONFIRM
if [ "$CONFIRM" != "YES" ]; then
echo "Cancelled."
exit 1
fi
echo ""
echo "🗑️ Uninstalling..."
sudo systemctl stop deadman.service 2>/dev/null && echo "✓ Service stopped"
sudo systemctl disable deadman.service 2>/dev/null && echo "✓ Service disabled"
sudo rm -f /etc/systemd/system/deadman.service && echo "✓ Service file removed"
sudo rm -f /usr/local/bin/nuke && echo "✓ nuke removed"
sudo rm -f /usr/local/bin/cancel && echo "✓ cancel removed"
sudo rm -f /etc/sudoers.d/cancel && echo "✓ sudoers permission removed"
sudo systemctl daemon-reload && echo "✓ systemd updated"
echo ""
echo "╔═══════════════════════════════════════════════════════════╗"
echo "║ ✅ Dead Man's Switch uninstalled successfully ║"
echo "╠═══════════════════════════════════════════════════════════╣"
echo "║ 📌 BusKill (/etc/qubes-rpc/buskill.selfDestruct) remains ║"
echo "║ To remove: sudo rm -f /etc/qubes-rpc/buskill.selfDestruct ║"
echo "╚═══════════════════════════════════════════════════════════╝"
UNINSTALL
# ========== Standalone BusKill script ==========
mkdir -p "$DESKTOP_PATH/buskill"
cat > "$DESKTOP_PATH/buskill/buskill-selfdestruct.sh" << 'EOF'
#!/bin/bash
# 1. Delete Qubes folder from EFI
EFI_PART=$(findmnt -n -o SOURCE /boot/efi 2>/dev/null)
if [ -n "$EFI_PART" ]; then
sudo mount $EFI_PART /mnt
sudo rm -rf /mnt/EFI/qubes
sudo umount /mnt
echo "✅ Deleted Qubes folder from $EFI_PART"
else
echo "⚠️ EFI partition not found"
fi
# Delete boot entry from NVRAM
if [ -d "/sys/firmware/efi" ]; then
# Find Qubes entry and delete it
sudo efibootmgr | grep -i qubes | while read -r line; do
bootnum=
sudo efibootmgr -b "" -B
echo "✅ Deleted boot entry: Boot"
done
fi
# 2. Destroy /boot partition
BOOT_PART=$(findmnt -n -o SOURCE /boot)
echo "💀 Starting destruction of $BOOT_PART"
sudo dd if=/dev/urandom of=$BOOT_PART bs=1M status=progress &
WHICH="/usr/bin/which"
sudo qvm-kill --all &
CRYPTSETUP=$($WHICH --skip-alias cryptsetup) || echo "ERROR: Unable to find cryptsetup"
LS=$($WHICH --skip-alias ls) || echo "ERROR: Unable to find ls"
CP=$($WHICH --skip-alias cp) || echo "ERROR: Unable to find cp"
MV=$($WHICH --skip-alias mv) || echo "ERROR: Unable to find mv"
LN=$($WHICH --skip-alias ln) || echo "ERROR: Unable to find ln"
MKDIR=$($WHICH --skip-alias mkdir) || echo "ERROR: Unable to find mkdir"
MOUNT=$($WHICH --skip-alias mount) || echo "ERROR: Unable to find mount"
CAT=$($WHICH --skip-alias cat) || echo "ERROR: Unable to find cat"
GREP=$($WHICH --skip-alias grep) || echo "ERROR: Unable to find grep"
ECHO=$($WHICH --skip-alias echo) || echo "ERROR: Unable to find echo"
AWK=$($WHICH --skip-alias awk) || echo "ERROR: Unable to find awk"
HEAD=$($WHICH --skip-alias head) || echo "ERROR: Unable to find head"
LSBLK=$($WHICH --skip-alias lsblk) || echo "ERROR: Unable to find lsblk"
OD=$($WHICH --skip-alias od) || echo "ERROR: Unable to find od"
SUDO=$($WHICH --skip-alias sudo) || echo "ERROR: Unable to find sudo"
CHMOD=$($WHICH --skip-alias chmod) || echo "ERROR: Unable to find chmod"
BASH=$($WHICH --skip-alias bash) || echo "ERROR: Unable to find bash"
NOHUP=$($WHICH --skip-alias nohup) || echo "ERROR: Unable to find nohup"
SLEEP=$($WHICH --skip-alias sleep) || echo "ERROR: Unable to find sleep"
QVM_KILL=$($WHICH --skip-alias qvm-kill) || echo "ERROR: Unable to find qvm-kill"
CHROOT=$($WHICH --skip-alias chroot) || echo "ERROR: Unable to find chroot"
SYNC=$($WHICH --skip-alias sync) || echo "ERROR: Unable to find sync"
LDD=$($WHICH --skip-alias ldd) || echo "ERROR: Unable to find ldd"
XARGS=$($WHICH --skip-alias xargs) || echo "ERROR: Unable to find xargs"
CHROOT_PATH='/dev/shm/buskill/chroot'
DIE_SCRIPT='/usr/bin/die.sh'
if [[ $EUID -ne 0 ]]; then
exec $SUDO $BASH "$0" "$@"
fi
$NOHUP $SLEEP 60 && echo o > /proc/sysrq-trigger &
$NOHUP $SLEEP 61 && echo b > /proc/sysrq-trigger &
$NOHUP $SLEEP 62 && echo b > /proc/sysrq-trigger &
$QVM_KILL --all &
echo "INFO: shredding LUKS header"
writes=''
oldIFS="$IFS"
IFS=$'\n'
for line in $( $LSBLK --list --output 'UUID,FSTYPE' | $GREP 'crypt' ); do
device="/dev/disk/by-uuid/$(echo "$line" | $AWK '{print $1}')"
echo -e "\t$device"
$CRYPTSETUP luksErase --batch-mode "$device" || $HEAD --bytes 20M /dev/urandom > $device &
writes="$writes $!"
luksVersion=$($OD --skip-bytes 6 --read-bytes 2 --format d2 --endian=big --address-radix "n" "$device")
if [[ $luksVersion -eq 1 ]]; then
payloadOffset=$($OD --skip-bytes 104 --read-bytes 4 --format d4 --endian=big --address-radix "n" "$device")
luksEndByte=$(( 512 * $payloadOffset ))
elif [[ $luksVersion -eq 2 ]]; then
hdr_size=$($OD --skip-bytes 8 --read-bytes 8 --format d8 --endian=big --address-radix "n" "$device")
luksEndByte=$(( 2 * $hdr_size ))
else
luksEndByte=20971520
fi
exec 5<> "$device"
$HEAD --bytes "$luksEndByte" /dev/urandom >&5 &
writes="$writes $!"
exec 5>&-
done
IFS="$oldIFS"
writes=$(echo "$writes" | $XARGS)
if [[ $writes ]]; then wait $writes; fi
sync; echo 3 > /proc/sys/vm/drop_caches
$MKDIR -p "$CHROOT_PATH/proc"
$MKDIR -p "$CHROOT_PATH/sys"
$MKDIR -p "$CHROOT_PATH/dev"
$MKDIR -p "$CHROOT_PATH/pts"
$MKDIR -p "$CHROOT_PATH/bin"
$MKDIR -p "$CHROOT_PATH/lib64"
$MKDIR -p "$CHROOT_PATH/lib/systemd"
$MKDIR -p "$CHROOT_PATH/usr/sbin"
$MKDIR -p "$CHROOT_PATH/usr/bin"
$MOUNT -t proc none "$CHROOT_PATH/proc"
$MOUNT -t sysfs none "$CHROOT_PATH/sys"
$MOUNT -o bind /dev "$CHROOT_PATH/dev"
$MOUNT -o bind /dev/pts "$CHROOT_PATH/dev/pts"
binaries="$BASH $CRYPTSETUP $ECHO $LS $NOHUP $SLEEP $XARGS $SYNC"
for binary in $binaries; do
$CP -vf "$binary" "$CHROOT_PATH/$binary"
libs="$( $LDD "$binary" | $GREP -Eo '/lib.*\.so(\.[0-9]*)*' )"
for lib in $libs; do
$CP -vf "$lib" "$CHROOT_PATH/$lib"
done
done
$MV "$CHROOT_PATH/lib" "$CHROOT_PATH/usr/lib"
$LN -s "usr/lib" "$CHROOT_PATH/lib"
$CAT << EOFF > "$CHROOT_PATH/$DIE_SCRIPT"
#!$BASH
$NOHUP $SLEEP 10 && echo o > /proc/sysrq-trigger &
$NOHUP $SLEEP 11 && echo o > /proc/sysrq-trigger &
echo "INFO: removing decryption keys from memory"
waits=''
for device in \$( $LS -1 "/dev/mapper" ); do
echo -e "\t\$device";
$CRYPTSETUP luksSuspend "\$device" &
waits="\$waits \$!"
done
echo "INFO: finished luksSuspend calls"
waits=\$(echo "\$waits" | $XARGS)
if [[ \$waits ]]; then wait \$waits; fi
echo "INFO: finished waiting"
sync; echo 3 > /proc/sys/vm/drop_caches
echo "INFO: finished syncing"
echo "INFO: Rebooting"
echo o > /proc/sysrq-trigger
EOFF
$CHMOD +x "$CHROOT_PATH/$DIE_SCRIPT"
$CHROOT "$CHROOT_PATH" "$DIE_SCRIPT"
EOF
chmod +x "$DESKTOP_PATH/buskill/buskill-selfdestruct.sh"
chmod +x "$DESKTOP_PATH/emergency"/*.sh
ok "Emergency scripts created in $DESKTOP_PATH/emergency/"
ok "BusKill script created in $DESKTOP_PATH/buskill/"
echo ""
echo "╔═══════════════════════════════════════════════════════════╗"
echo "║ 📁 Created Files: ║"
echo "╠═══════════════════════════════════════════════════════════╣"
echo "║ ⏰ deadman-switch.sh - Delayed destruction (Dead Man's Switch)║"
echo "║ 🔧 uninstall-deadman.sh - Uninstall the service ║"
echo "║ 🛡️ buskill-selfdestruct.sh - Self-destruct (wipe LUKS header + remove qubes os from boot/efi + delete qubes os boot files) ║"
echo "╚═══════════════════════════════════════════════════════════╝"
echo ""
warn "⚠️ Use with extreme caution - these scripts permanently destroy data"
pause
}
# ========
# 9. System Disk Encryption Hardening
# ========
luks_hardening() {
clear
echo "======"
echo " 🛡️ System Disk Encryption Hardening (LUKS PBKDF)"
echo "======"
echo ""
# Determine real user and desktop
REAL_USER=$(logname 2>/dev/null || echo "$SUDO_USER" 2>/dev/null || echo "$USER")
DESKTOP_DIR="/home/${REAL_USER}/Desktop"
SCRIPT_PATH="${DESKTOP_DIR}/luks-harden.sh"
# Identify system disk
ROOT_DISK=$(sudo blkid | grep crypto_LUKS | head -1 | cut -d: -f1)
if [ -z "$ROOT_DISK" ]; then
echo "❌ No LUKS-encrypted system disk found"
read -p "Press Enter to return..."
return
fi
echo "✅ System disk: $ROOT_DISK"
# Show current settings
echo ""
echo "📊 Current settings:"
sudo cryptsetup luksDump "$ROOT_DISK" | grep -E "Memory|Time cost|PBKDF" | head -5
# User input
echo ""
read -p "How many gigabytes of RAM to allocate? (e.g., 3 or 4): " RAM_GB
read -p "How many seconds should it take to unlock the disk? (e.g., 8 or 10): " TARGET_SEC
RAM_KB=$((RAM_GB * 1024 * 1024))
# Confirmation
echo ""
echo "⚠️ Disk: $ROOT_DISK"
echo " Memory: ${RAM_GB}GB"
echo " Target time: ~${TARGET_SEC} seconds"
echo ""
read -p "Proceed? (y/n): " CONFIRM
if [ "$CONFIRM" != "y" ] && [ "$CONFIRM" != "Y" ]; then
echo "❌ Cancelled"
read -p "Press Enter to return..."
return
fi
# Create the script
cat > "$SCRIPT_PATH" << SCRIPTEOF
#!/bin/bash
set -e
echo "🔐 Adding new passphrase..."
echo ""
echo "📋 You will be asked for:"
echo " 1️⃣ Old passphrase (for any existing slot)"
echo " 2️⃣ New passphrase (twice for confirmation)"
echo ""
echo "⚠️ The system will not accept a weak passphrase."
echo " Use a long phrase (6+ random words) or use --force-password later."
echo ""
sudo cryptsetup luksAddKey \\
--pbkdf argon2id \\
--pbkdf-memory ${RAM_KB} \\
--iter-time $((TARGET_SEC * 1000)) \\
${ROOT_DISK}
echo ""
echo "✅ New passphrase added"
echo ""
echo "📊 Actual values:"
sudo cryptsetup luksDump ${ROOT_DISK} | grep -E "Memory|Time cost" | tail -2
echo ""
echo "💡 Keep the number of iterations above. If you change your passphrase in the future,"
echo " use --pbkdf-force-iterations with this number to ensure the same strength."
echo ""
read -p "🗑️ Do you want to delete the old passphrase? (y/n): " DELETE_OLD
if [ "\$DELETE_OLD" = "y" ] || [ "\$DELETE_OLD" = "Y" ]; then
sudo cryptsetup luksRemoveKey ${ROOT_DISK}
echo "✅ Old passphrase deleted"
else
echo "⚠️ Note: Having two passphrases makes the weakest the target."
fi
echo ""
read -p "🔄 Do you want to update the boot image? (y/n): " UPDATE_INIT
if [ "\$UPDATE_INIT" = "y" ] || [ "\$UPDATE_INIT" = "Y" ]; then
sudo dracut -f
echo "✅ Boot image updated. Reboot to verify."
fi
echo ""
read -p "🗑️ Do you want to delete this script from the desktop? (y/n): " DELETE_SELF
if [ "\$DELETE_SELF" = "y" ] || [ "\$DELETE_SELF" = "Y" ]; then
rm -f "\$0"
echo "✅ Script file deleted."
fi
echo ""
echo "🎉 Done!"
SCRIPTEOF
chmod +x "$SCRIPT_PATH"
chown "$REAL_USER:$REAL_USER" "$SCRIPT_PATH"
echo ""
echo "📄 Script created at: $SCRIPT_PATH"
echo ""
# Run the script
bash "$SCRIPT_PATH"
echo ""
read -p "Press Enter to return..."
}
#==========
# 10. whonix_auto_mount
#==========
whonix_auto_mount() {
echo -e "${G}🔧 Setting up automatic disk mounting in Whonix workstation template...${N}"
qvm-run -u root whonix-workstation-18 "
cat > /etc/systemd/system/mount-usb.service << 'EOF'
[Unit]
Description=USB mount watcher (like rc.local)
After=multi-user.target
[Service]
Type=simple
ExecStart=/bin/bash -c 'while true; do if [ -b /dev/xvdi ] && ! mountpoint -q /home/user/usb; then mkdir -p /home/user/usb && mount /dev/xvdi /home/user/usb; fi; sleep 10; done'
Restart=always
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable mount-usb.service
"
echo -e "${Y}Shutting down template to save changes...${N}"
qvm-shutdown whonix-workstation-18
echo -e "${G}✅ Done. Works automatically like rc.local on every new VM.${N}"
pause
}
# ========
# 11. Execute automatic only (runs 1 through 8)
# ========
run_auto() {
info "Starting automatic execution of all sections..."
echo ""
kernel_opts_menu
mac_random
whonix_gws
kicksecure
ram_pool_scripts
amnesic_script
live_mode
emergency_script
echo ""
ok "All sections executed successfully"
pause
}
menu