Privacy, Cypherpunk, #bitcoin #nostr на русском. и немного философии... Love Qubes OS, Graphene OS, SimpleX, Amethyst 😎 Добавьте себе эти дополнительные реле и увидите намного больше контента в nostr: wss://nostr.data.haus wss://relay.ditto.pub wss://relay.momostr.pink
Public Key
npub1cv8cl9nfqtrkdn3k0nk05z8mjte6hxtdws6j4x65jxc06nk5tu2s4ulsnl Profile Code
nprofile1qqsvxru0je5s93mxecm8em86prae9uatn9khgdf2nd2frv8afm2979gpp4mhxue69uhkummn9ekx7mqpz3mhxue69uhhyetvv9ujuerpd46hxtnfdu0nzuwv
Show more details
Published at
2026-07-29T15:41:24+02:00 Event JSON
{
"id": "b1204570b2e108a011fcbeb1936ead8554bdf8d0bb3ff99220820049a2d482c4" ,
"pubkey": "c30f8f966902c766ce367cecfa08fb92f3ab996d74352a9b5491b0fd4ed45f15" ,
"created_at": 1785332484 ,
"kind": 0 ,
"tags": [
[
"alt",
"User profile for linux_privacy"
],
[
"name",
"linux_privacy"
],
[
"picture",
"https://blossom.primal.net/3b0783e6ff4c57e9ed5886e3d58db31592c28d0c06d6aff9157fca59291d6523.webp"
],
[
"banner",
"https://image.nostr.build/6bd3db5d6ad9157665747bc1ccc12b121517d138eada4ec389d175e02b6b1bf5.jpg"
],
[
"about",
"Privacy, Cypherpunk, #bitcoin #nostr на русском. и немного философии...\nLove Qubes OS, Graphene OS, SimpleX, Amethyst 😎\n\nДобавьте себе эти дополнительные реле и увидите намного больше контента в nostr:\nwss://nostr.data.haus\nwss://relay.ditto.pub\nwss://relay.momostr.pink"
],
[
"lud16",
"[email protected] "
],
[
"client",
"Amethyst"
]
],
"content": "{\"name\":\"linux_privacy\",\"about\":\"Privacy, Cypherpunk, #bitcoin #nostr на русском. и немного философии...\\nLove Qubes OS, Graphene OS, SimpleX, Amethyst 😎\\n\\nДобавьте себе эти дополнительные реле и увидите намного больше контента в nostr:\\nwss://nostr.data.haus\\nwss://relay.ditto.pub\\nwss://relay.momostr.pink\",\"gender\":\"\",\"area\":\"\",\"picture\":\"https://blossom.primal.net/3b0783e6ff4c57e9ed5886e3d58db31592c28d0c06d6aff9157fca59291d6523.webp\",\"banner\":\"https://image.nostr.build/6bd3db5d6ad9157665747bc1ccc12b121517d138eada4ec389d175e02b6b1bf5.jpg\",\"lud16\":\"[email protected] \",\"pubkey\":\"c30f8f966902c766ce367cecfa08fb92f3ab996d74352a9b5491b0fd4ed45f15\",\"is_deleted\":false}" ,
"sig": "156ec7f571e94c80757d959ee84ad201c3b278e450db582af92fe9e3d164a9ccd6c495a466126f09c86010abe3b359bc032c235ee36c7cbc1090ca663bee5309"
}
Last Notes npub1cv8cl9nfqtrkdn3k0nk05z8mjte6hxtdws6j4x65jxc06nk5tu2s4ulsnl linux_privacy It's not about a specific relay, but about how the app handles relays. The same relay list shows me all posts in the feed in other apps. But my friends and I both have issues in Amethyst feed with this bridge-relays. And I'll repeat myself again: a post only disappears from the feed, if I go to those authors' accounts, I can see all their posts, and after that, those old posts immediately reappear in the feed too npub1cv8cl9nfqtrkdn3k0nk05z8mjte6hxtdws6j4x65jxc06nk5tu2s4ulsnl linux_privacy Accounts and their notes load quickly. But there are no notes in the feed. I don't see any notes from relay.momostr.pink in the feed at all, and 80% notes from nostr.data.haus. This is the only bug that makes it unpleasant to use Amethyst — forcing you to check each account individually instead of scrolling through a feed. Otherwise, Amethyst is awesome. Maybe just add clear browser cookies. npub1cv8cl9nfqtrkdn3k0nk05z8mjte6hxtdws6j4x65jxc06nk5tu2s4ulsnl linux_privacy Seems like it, yes, same. It seems like this bridge-relay "relay.momostr.pink" doesn't appear in the feed at all until you go into the authors' accounts. For example, here's npubs: npub1em3c48ch5y3vn6n3jrfrs8lnwc8myvdl6aw87f04asyglwhk29cqnp3u4v npub1hpwuqgk7w8cfcuyrjk7pwcl3gf652ssgnq8ylfmh3av489tuhl6q9qcd0d Try checking this account for example (posts are published frequently): npub1xumnple866kt7fzjryjqw37ucr4cxv4704hxpa2856p8sx6h8t8qhg3zsv And here's for "nostr.data.haus". These aren't all the missing npubs, just 2 examples: npub1ykhehcxdysg20y29f6k86panx7zadhv84azuugthhlljwjakeghsxqws3h npub15fx0k7893jcx8kqrw4d84zywglafzqr0fdjaxth4e5a59tltxvjqs4075n But these RSS accounts don't disappear and I always see their posts in the feed: npub1jadusj2qgcc2qr9xzeq5vmdt3qesqgrxtvzd65t689kqc0llqjwq8nwvuv npub1lqvjhgwv2ak92a6jatfk9pjlr49p9uj2zqst0wwwfvew6f7tmrws9wyvwp Try subscribing, then restart the app. If you go into the accounts or adjust something in the relay settings, all posts may temporarily appear. But if you close the app and launch it later, many posts will disappear from the feed again. I'm clarifying once more: the issue is only in the feed. I can see all posts if I personally visit the authors' accounts. npub1cv8cl9nfqtrkdn3k0nk05z8mjte6hxtdws6j4x65jxc06nk5tu2s4ulsnl linux_privacy I cleared the cache and restarted Amethyst. Now it skipped a lot of posts from all my feeds - it scrolled through the timeline going back two days very quickly. It seems like Amethyst takes a very long time reading feed caches, which is causing the post issues. But I distinctly remember that last year there were no such problems with posts. npub1cv8cl9nfqtrkdn3k0nk05z8mjte6hxtdws6j4x65jxc06nk5tu2s4ulsnl linux_privacy I didn't set up any filters. I even disabled the spam filter. I see some posts from these relays, but not all. A lot of posts disappear from the feed, but I can see all those posts if I go to the author's page. And it's definitely not a tagging issue. So far, I can say that wisp and 0xchat show all posts from both public and private contacts. I hope this will be fixed in Amethyst, because right now Amethyst seems almost perfect as a Nostr app in terms of functionality. Once, I somehow managed to fix this, but after restarting it stopped working again (maybe the cache wasn't saved). npub1cv8cl9nfqtrkdn3k0nk05z8mjte6hxtdws6j4x65jxc06nk5tu2s4ulsnl linux_privacy Basically, message disappear from these relays nostr.data.haus relay.ditto.pub poorly displayed posts of bridge-accounts (mastodon, rss, mastodon). npub1cv8cl9nfqtrkdn3k0nk05z8mjte6hxtdws6j4x65jxc06nk5tu2s4ulsnl linux_privacy @nprofile…pt5w Some posts are disappearing in Amethyst. I don't know what's causing this, but if you visit the post author's account, the post will appear in feed. Wisp doesn't have this problem — I can see all posts from everyone I follow. Please check it. I haven't encountered this issue in older versions. Currently, I'm not seeing approximately 20% of posts — all necessary relays have been added. npub1cv8cl9nfqtrkdn3k0nk05z8mjte6hxtdws6j4x65jxc06nk5tu2s4ulsnl linux_privacy Первая часть разбора шпионских функций приложений Яндекс. Иногда там всё страшнее, чем в максе. https://habr.com/ru/articles/1064698/ npub1cv8cl9nfqtrkdn3k0nk05z8mjte6hxtdws6j4x65jxc06nk5tu2s4ulsnl linux_privacy Там блочат всё, что можно, чего даже официально нет в списках запрещенки. Поэтому надо подбирать реле, чтоб всё работало норм. Популярные реле могут вообще не работать во многих регионах. npub1cv8cl9nfqtrkdn3k0nk05z8mjte6hxtdws6j4x65jxc06nk5tu2s4ulsnl linux_privacy А твой реле случаем не из тех, которые пашут в рф без впн? А то я его быстро везде распиарю npub1cv8cl9nfqtrkdn3k0nk05z8mjte6hxtdws6j4x65jxc06nk5tu2s4ulsnl linux_privacy Amethyst бомба! #nevent1q…7mhj npub1cv8cl9nfqtrkdn3k0nk05z8mjte6hxtdws6j4x65jxc06nk5tu2s4ulsnl linux_privacy :locked: 1. Ephemeral Encrypted Volatile DVMs (for devices up to 16 GB RAM) Script deploys a systemd service that creates a fresh LUKS-encrypted LVM thin pool on every boot, clones all DVM templates into it as ephemeral -ephemeral variants, and completely destroys the pool - including all VM volumes, encryption keys, and the underlying image file - on shutdown. This guarantees that all DVM session data resides exclusively on encrypted volatile storage that is cryptographically irrecoverable after poweroff, leaving no forensic traces on disk. This solution does not impose any additional memory load, which is critical for devices with less than 16 GB of RAM. My friends with 16 GB devices experienced severe issues when running DVMs entirely in RAM - DVMs would crash unexpectedly, or dom0 would start glitching: panel and app menu artifacts, cursor freezes. Make sure there is sufficient free space (more than 4 GB) in dom0. You can resize dom0 these commands: sudo lvresize --size 4G /dev/mapper/qubes_dom0-root sudo resize2fs /dev/mapper/qubes_dom0-root sudo lvresize -L +4G qubes_dom0/root-pool Run this simple script in dom0 terminal: #!/bin/bash sudo tee /etc/systemd/system/ephemeral-pool.service << 'EOF' [Unit] Description=Fresh ephemeral encrypted LVM pool with DVM auto-clone After=local-fs.target qubesd.service Requires=qubesd.service[email protected] [email protected] [Service] Type=oneshot RemainAfterExit=yes ExecStartPre=-/usr/local/bin/ephemeral-pool-destroy.sh ExecStart=/usr/local/bin/ephemeral-pool-create.sh ExecStop=/usr/local/bin/ephemeral-pool-destroy.sh [Install] WantedBy=multi-user.target EOF sudo tee /usr/local/bin/ephemeral-pool-create.sh << 'EOF' #!/bin/bash set -uo pipefail POOL_NAME="ephemeral_pool" MOUNT_BASE="/var/tmp/ephemeral" KEY_FILE="/dev/shm/ephemeral.key" IMG_FILE="${MOUNT_BASE}/pool.img" LUKS_NAME="ephemeral_crypt" POOL_SIZE="4G" ROOT_DEV=$(findmnt -n -o SOURCE / 2>/dev/null || echo "") if echo "$ROOT_DEV" | grep -qE "(overlay|/dev/zram0)"; then echo "[*] Detected amnesiac mode: $ROOT_DEV" echo " Destroying old ephemeral VMs and artifacts..." for vm in $(qvm-ls --raw-list --running 2>/dev/null); do if qvm-volume list "$vm" 2>/dev/null | grep -q "${POOL_NAME}"; then echo " -> Stopping: $vm" qvm-shutdown --wait --timeout 10 "$vm" 2>/dev/null || \ qvm-kill "$vm" 2>/dev/null || true fi done for vm in $(qvm-ls --raw-list 2>/dev/null); do if qvm-volume list "$vm" 2>/dev/null | grep -q "${POOL_NAME}"; then echo " -> Removing VM: $vm" qvm-remove --force "$vm" 2>/dev/null || true fi done qvm-pool remove "${POOL_NAME}" 2>/dev/null || true vgchange -an ephemeral_vg 2>/dev/null || true vgremove -f ephemeral_vg 2>/dev/null || true cryptsetup close "${LUKS_NAME}" 2>/dev/null || true rm -f "${KEY_FILE}" "${IMG_FILE}" echo "[+] Cleanup completed. Pool creation skipped in amnesiac mode." exit 0 fi for vm in $(qvm-ls --raw-list 2>/dev/null); do if qvm-volume list "$vm" 2>/dev/null | grep -q "${POOL_NAME}"; then echo " -> Removing VM: $vm" qvm-kill "$vm" 2>/dev/null || true sleep 1 qvm-remove --force "$vm" 2>/dev/null || true sleep 1 fi done qvm-pool remove "${POOL_NAME}" 2>/dev/null || true sleep 1 vgchange -an ephemeral_vg 2>/dev/null || true vgremove -f ephemeral_vg 2>/dev/null || true cryptsetup close "${LUKS_NAME}" 2>/dev/null || true rm -f "${KEY_FILE}" "${IMG_FILE}" mkdir -p "${MOUNT_BASE}" install -m600 /dev/null "${KEY_FILE}" && dd if=/dev/urandom bs=1 count=4096 of="${KEY_FILE}" iflag=fullblock truncate -s "${POOL_SIZE}" "${IMG_FILE}" LOOP_DEV=$(losetup -f --show "${IMG_FILE}") cryptsetup luksFormat -q --key-file "${KEY_FILE}" "${LOOP_DEV}" cryptsetup open --key-file "${KEY_FILE}" "${LOOP_DEV}" "${LUKS_NAME}" pvcreate "/dev/mapper/${LUKS_NAME}" vgcreate "ephemeral_vg" "/dev/mapper/${LUKS_NAME}" lvcreate -T -n "thin_pool" -l +100%FREE "ephemeral_vg" losetup -d "${LOOP_DEV}" || true if qvm-pool list 2>/dev/null | grep -q "^${POOL_NAME}"; then qvm-pool remove "${POOL_NAME}" 2>/dev/null || true sleep 1 fi qvm-pool add "${POOL_NAME}" lvm_thin --option volume_group=ephemeral_vg --option thin_pool=thin_pool 2>/dev/null || \ qvm-pool add "${POOL_NAME}" lvm_thin -o volume_group=ephemeral_vg,thin_pool=thin_pool echo "[*] Copying DVM templates..." qvm-ls --raw-list 2>/dev/null | grep -i 'dvm' | grep -v '\-ephemeral$' | while read vm; do [ -n "$vm" ] || continue clone_name="${vm}-ephemeral" qvm-check -q "$clone_name" 2>/dev/null && { qvm-kill "$clone_name" 2>/dev/null || true qvm-remove --force "$clone_name" 2>/dev/null || true } echo " -> $vm -> ${clone_name}" qvm-clone -P "${POOL_NAME}" "$vm" "$clone_name" && { qvm-prefs "$clone_name" template_for_dispvms True 2>/dev/null || true qvm-prefs "$clone_name" autostart False 2>/dev/null || true } done echo "[+] Done!" EOF sudo tee /usr/local/bin/ephemeral-pool-destroy.sh << 'EOF' #!/bin/bash set -uo pipefail POOL_NAME="ephemeral_pool" LUKS_NAME="ephemeral_crypt" KEY_FILE="/dev/shm/ephemeral.key" IMG_FILE="/var/tmp/ephemeral/pool.img" for vm in $(qvm-ls --raw-list 2>/dev/null); do if qvm-volume list "$vm" 2>/dev/null | grep -q "${POOL_NAME}"; then qvm-kill "$vm" 2>/dev/null || true sleep 1 qvm-remove --force "$vm" 2>/dev/null || true sleep 1 fi done qvm-pool remove "${POOL_NAME}" 2>/dev/null || true sleep 1 qvm-pool remove "${POOL_NAME}" 2>/dev/null || true vgchange -an ephemeral_vg 2>/dev/null || true vgremove -f ephemeral_vg 2>/dev/null || true cryptsetup close "${LUKS_NAME}" 2>/dev/null || true for loop in $(losetup -j "${IMG_FILE}" 2>/dev/null | cut -d: -f1 || true); do [ -n "$loop" ] && losetup -d "$loop" 2>/dev/null || true done rm -f "${KEY_FILE}" "${IMG_FILE}" EOF sudo chmod +x /usr/local/bin/ephemeral-pool-create.sh sudo chmod +x /usr/local/bin/ephemeral-pool-destroy.sh sudo systemctl daemon-reload sudo systemctl enable --now ephemeral-pool.service To remove pool and disable the systemd service: sudo systemctl stop ephemeral-pool.service sudo systemctl disable ephemeral-pool.service Start the service and pool again: sudo systemctl enable --now ephemeral-pool.service :card_index_dividers: 2. RAM-based DVMs on a Zram disk (for devices with 20-24 GB of RAM) This script creates a compressed RAM disk (zram) that acts as a super-fast ephemeral storage pool for your DVMs. On every boot, it sets aside a portion of your RAM - compressed with lz4 algorithm to save space - builds an LVM thin pool on top of it, and clones all your DVM templates there as -ephemeral variants. When you shut down the system, everything in that RAM disk vanishes instantly without ever touching your physical disk. The key advantage of zram is built-in compression: your 3 GB pool can hold 6-7 GB of actual VM data, giving you more usable space than raw RAM would allow. It is also significantly faster than disk-based storage, so DVMs launch and run with near-native speed. Unlike tmpfs, zram does not compete with your system memory for cache. First, increase the maximum dom0 memory in this file sudo nano /etc/default/grub edit this value dom0_mem=max:4096M to dom0_mem=max:6144M, update GRUB sudo grub2-mkconfig -o /boot/grub2/grub.cfg and reboot Qubes OS. Run this simple script in dom0 terminal: #!/bin/bash sudo tee /etc/systemd/system/zram-pool.service << 'EOF' [Unit] Description=ZRAM Ephemeral Pool After=qubesd.service[email protected] [Service] Type=oneshot ExecStart=/usr/local/bin/zram-pool-create.sh RemainAfterExit=yes [Install] WantedBy=multi-user.target EOF sudo tee /usr/local/bin/zram-pool-create.sh << 'EOF' #!/bin/bash set -euo pipefail POOL_NAME="zram_pool" ZRAM_SIZE="4G" VG_NAME="zram_vg" EXTRA_VMS=( #"whonix" #"sys-whonix" ) ROOT_DEV=$(findmnt -n -o SOURCE / 2>/dev/null || echo "") if echo "$ROOT_DEV" | grep -qE "(overlay|/dev/zram0)"; then echo "[*] Detected amnesiac mode: $ROOT_DEV" echo " Cleaning up old VMs..." for vm in $(qvm-ls --raw-list --running 2>/dev/null); do if qvm-volume list "$vm" 2>/dev/null | grep -q "${POOL_NAME}"; then echo " -> Stopping: $vm" qvm-shutdown --wait --timeout 10 "$vm" 2>/dev/null || \ qvm-kill "$vm" 2>/dev/null || true fi done for vm in $(qvm-ls --raw-list 2>/dev/null); do if qvm-volume list "$vm" 2>/dev/null | grep -q "${POOL_NAME}"; then echo " -> Removing: $vm" qvm-remove --force "$vm" 2>/dev/null || true fi done qvm-pool remove "${POOL_NAME}" 2>/dev/null || true vgchange -an "${VG_NAME}" 2>/dev/null || true vgremove -f "${VG_NAME}" 2>/dev/null || true for dev in /dev/zram*; do [ -b "$dev" ] || continue zramctl --reset "$dev" 2>/dev/null || true done echo "[+] Cleanup completed." exit 0 fi if [ "$EUID" -ne 0 ]; then echo "[!] Root privileges required" exit 1 fi echo "[*] Creating zram device (${ZRAM_SIZE})..." modprobe zram 2>/dev/null || true ZRAM_DEV="" for dev in /dev/zram*; do [ -b "$dev" ] || continue if ! zramctl "$dev" 2>/dev/null | grep -q "mounted\|active"; then ZRAM_DEV="$dev" break fi done if [ -z "$ZRAM_DEV" ]; then ZRAM_DEV=$(zramctl --find --size "$ZRAM_SIZE" --algorithm lz4) else zramctl --reset "$ZRAM_DEV" 2>/dev/null || true ZRAM_DEV=$(zramctl --find --size "$ZRAM_SIZE" --algorithm lz4) fi echo " Device: $ZRAM_DEV" echo "[*] Removing VMs from pool ${POOL_NAME}..." for vm in $(qvm-ls --raw-list --running 2>/dev/null); do if qvm-volume list "$vm" 2>/dev/null | grep -q "${POOL_NAME}"; then echo " -> Stopping: $vm" qvm-shutdown --wait --timeout 30 "$vm" 2>/dev/null || { echo " -> Force killing: $vm" qvm-kill "$vm" 2>/dev/null || true } fi done for vm in $(qvm-ls --raw-list 2>/dev/null); do if qvm-volume list "$vm" 2>/dev/null | grep -q "${POOL_NAME}"; then echo " -> Removing: $vm" qvm-remove --force "$vm" 2>/dev/null || true fi done echo "[*] Cleaning up old infrastructure..." qvm-pool remove "${POOL_NAME}" 2>/dev/null || true vgchange -an "${VG_NAME}" 2>/dev/null || true vgremove -f "${VG_NAME}" 2>/dev/null || true # Detach old loops on zram for loopdev in $(losetup -a 2>/dev/null | grep "$ZRAM_DEV" | cut -d: -f1); do echo " -> Detaching loop: $loopdev" losetup -d "$loopdev" 2>/dev/null || true done echo "[*] Creating loop on ${ZRAM_DEV}..." LOOP_DEV=$(losetup -f --show "$ZRAM_DEV") echo " Loop: $LOOP_DEV" echo "[*] Creating LVM on ${LOOP_DEV}..." pvcreate -q "$LOOP_DEV" vgcreate -q "${VG_NAME}" "$LOOP_DEV" lvcreate -q -T -n "thin_pool" -l +100%FREE "${VG_NAME}" echo "[*] Registering pool ${POOL_NAME}..." if qvm-pool list 2>/dev/null | grep -q "^${POOL_NAME}"; then qvm-pool remove "${POOL_NAME}" 2>/dev/null || true sleep 1 fi qvm-pool add "${POOL_NAME}" lvm_thin --option volume_group="${VG_NAME}" --option thin_pool=thin_pool 2>/dev/null || \ qvm-pool add "${POOL_NAME}" lvm_thin -o volume_group="${VG_NAME}",thin_pool=thin_pool echo " Pool created:" qvm-pool info "${POOL_NAME}" echo "[*] Cloning VMs into ephemeral pool..." echo " [DVM templates]" qvm-ls --raw-list 2>/dev/null | while read -r vm; do [ -n "$vm" ] || continue is_dvm=$(qvm-prefs "$vm" template_for_dispvms 2>/dev/null || echo "False") [ "$is_dvm" = "True" ] || continue if qvm-volume list "$vm" 2>/dev/null | grep -q "${POOL_NAME}"; then continue fi clone_name="${vm}-ephemeral" if qvm-ls --raw-list 2>/dev/null | grep -q "^${clone_name}$"; then echo " -> Removing old copy: ${clone_name}" qvm-kill "$clone_name" 2>/dev/null || true qvm-remove --force "$clone_name" 2>/dev/null || true fi echo " -> Cloning: $vm -> ${clone_name}" if qvm-clone -P "${POOL_NAME}" "$vm" "$clone_name"; then qvm-prefs "$clone_name" template_for_dispvms True 2>/dev/null || true qvm-prefs "$clone_name" autostart False 2>/dev/null || true echo " [+] OK" else echo " [!] ERROR" fi done if [ ${#EXTRA_VMS[@]} -gt 0 ]; then echo " [Extra VMs]" for vm in "${EXTRA_VMS[@]}"; do if ! qvm-ls --raw-list 2>/dev/null | grep -q "^${vm}$"; then echo " [!] VM not found: $vm" continue fi if qvm-volume list "$vm" 2>/dev/null | grep -q "${POOL_NAME}"; then echo " -> Skipping (already in zram): $vm" continue fi clone_name="${vm}-ephemeral" if qvm-ls --raw-list 2>/dev/null | grep -q "^${clone_name}$"; then echo " -> Removing old copy: ${clone_name}" qvm-kill "$clone_name" 2>/dev/null || true qvm-remove --force "$clone_name" 2>/dev/null || true fi echo " -> Cloning: $vm -> ${clone_name}" if qvm-clone -P "${POOL_NAME}" "$vm" "$clone_name"; then qvm-prefs "$clone_name" template_for_dispvms False 2>/dev/null || true qvm-prefs "$clone_name" autostart False 2>/dev/null || true echo " [+] OK" else echo " [!] ERROR" fi done fi EOF sudo chmod +x /usr/local/bin/zram-pool-create.sh sudo systemctl daemon-reload sudo systemctl enable --now zram-pool.service To remove zram pool and disable the systemd service run this script: #!/bin/bash # Step 1: Stop and disable the systemd service sudo systemctl stop zram-pool.service sudo systemctl disable zram-pool.service # Step 2: Remove all VMs from zram_pool echo "[*] Removing VMs from zram_pool..." for vm in $(qvm-ls --raw-list 2>/dev/null); do if qvm-volume list "$vm" 2>/dev/null | grep -q "zram_pool"; then echo " -> Removing: $vm" qvm-kill "$vm" 2>/dev/null || true sleep 1 qvm-remove --force "$vm" 2>/dev/null || true sleep 1 fi done # Step 3: Remove the Qubes storage pool echo "[*] Removing zram_pool..." qvm-pool remove zram_pool 2>/dev/null || true # Step 4: Deactivate and remove LVM volume group echo "[*] Cleaning up LVM..." vgchange -an zram_vg 2>/dev/null || true vgremove -f zram_vg 2>/dev/null || true # Step 5: Detach loop device from zram echo "[*] Detaching loop devices..." for loopdev in $(losetup -a 2>/dev/null | grep "/dev/zram" | cut -d: -f1); do echo " -> Detaching: $loopdev" losetup -d "$loopdev" 2>/dev/null || true done # Step 6: Reset zram device echo "[*] Resetting zram device..." for dev in /dev/zram*; do [ -b "$dev" ] || continue zramctl --reset "$dev" 2>/dev/null || true done echo "[+] zram pool completely removed. Reboot to clear all traces from memory." Start the service and pool again: sudo systemctl enable --now zram-pool.service :tornado: 3. RAM-based DVMs on TMPFS (for devices with 32 GB of RAM) This variant creates a pure RAM-based ephemeral pool using tmpfs. On every boot, it allocates a portion of your system memory as a temporary filesystem, builds an LVM thin pool inside it, and clones all your DVM templates there as ephemeral variants. When you shut down or reboot, the entire pool and all its VMs vanish instantly. Direct RAM access means DVMs launch faster than any other storage type. No encryption overhead, no zram compression delays or disk I/O bottlenecks - just RAM and LVM. The entire pool size is reserved from RAM immediately, making this best suited for systems with 32 GB or more. First, increase the maximum dom0 memory in this file sudo nano /etc/default/grub edit this value dom0_mem=max:4096M to dom0_mem=max:8192M, update GRUB sudo grub2-mkconfig -o /boot/grub2/grub.cfg and reboot Qubes OS. Run this simple script in dom0 terminal: #!/bin/bash sudo tee /etc/systemd/system/tmpfs-pool.service << 'EOF' [Unit] Description=TMPFS Ephemeral Pool After=qubesd.service[email protected] [Service] Type=oneshot ExecStart=/usr/local/bin/tmpfs-pool-create.sh RemainAfterExit=yes [Install] WantedBy=multi-user.target EOF sudo tee /usr/local/bin/tmpfs-pool-create.sh << 'EOF' #!/bin/bash set -uo pipefail POOL_NAME="tmpfs_pool" MOUNT_BASE="/dev/shm/ephemeral" IMG_FILE="${MOUNT_BASE}/pool.img" POOL_SIZE="4G" VG_NAME="ephemeral_vg" ROOT_DEV=$(findmnt -n -o SOURCE / 2>/dev/null || echo "") if echo "$ROOT_DEV" | grep -qE "(overlay|/dev/zram)"; then echo "[*] Detected amnesiac mode: $ROOT_DEV" echo " Destroying old ephemeral VMs and artifacts..." for vm in $(qvm-ls --raw-list --running 2>/dev/null); do if qvm-volume list "$vm" 2>/dev/null | grep -q "${POOL_NAME}"; then echo " -> Stopping: $vm" qvm-shutdown --wait --timeout 10 "$vm" 2>/dev/null || \ qvm-kill "$vm" 2>/dev/null || true fi done for vm in $(qvm-ls --raw-list 2>/dev/null); do if qvm-volume list "$vm" 2>/dev/null | grep -q "${POOL_NAME}"; then echo " -> Removing: $vm" qvm-remove --force "$vm" 2>/dev/null || true fi done qvm-pool remove "${POOL_NAME}" 2>/dev/null || true vgchange -an "${VG_NAME}" 2>/dev/null || true vgremove -f "${VG_NAME}" 2>/dev/null || true rm -rf "${MOUNT_BASE}" echo "[+] Cleanup completed. Pool creation skipped in amnesiac mode." exit 0 fi echo "[*] Phase 1: Removing ALL VMs from ephemeral pool..." for vm in $(qvm-ls --raw-list 2>/dev/null); do if qvm-volume list "$vm" 2>/dev/null | grep -q "${POOL_NAME}"; then echo " -> Removing VM: $vm" qvm-kill "$vm" 2>/dev/null || true sleep 1 qvm-remove --force "$vm" 2>/dev/null || true sleep 1 fi done # Pool in RAM — simply delete files echo "[*] Phase 2: Cleaning old pool from RAM..." qvm-pool remove "${POOL_NAME}" 2>/dev/null || true vgchange -an "${VG_NAME}" 2>/dev/null || true vgremove -f "${VG_NAME}" 2>/dev/null || true # Remove old files (if any remain) rm -rf "${MOUNT_BASE}" echo "[*] Phase 3: Creating fresh ephemeral pool in RAM..." mkdir -p "${MOUNT_BASE}" # New image in RAM truncate -s "${POOL_SIZE}" "${IMG_FILE}" # Loop device LOOP_DEV=$(losetup -f --show "${IMG_FILE}") # LVM thin pool directly on loop (no encryption) pvcreate "$LOOP_DEV" vgcreate "${VG_NAME}" "$LOOP_DEV" lvcreate -T -n "thin_pool" -l +100%FREE "${VG_NAME}" # Detach loop losetup -d "${LOOP_DEV}" || true echo "[*] Phase 4: Registering pool..." if qvm-pool list 2>/dev/null | grep -q "^${POOL_NAME}"; then qvm-pool remove "${POOL_NAME}" 2>/dev/null || true sleep 1 fi qvm-pool add "${POOL_NAME}" lvm_thin --option volume_group="${VG_NAME}" --option thin_pool=thin_pool 2>/dev/null || \ qvm-pool add "${POOL_NAME}" lvm_thin -o volume_group="${VG_NAME}",thin_pool=thin_pool if ! qvm-pool list 2>/dev/null | grep -q "^${POOL_NAME}"; then echo "[!] Pool registration failed" exit 1 fi echo "[+] Pool ready in RAM" echo "[*] Phase 5: Copying DVM templates..." echo " [DVM templates]" qvm-ls --raw-list 2>/dev/null | while read -r vm; do [ -n "$vm" ] || continue is_dvm=$(qvm-prefs "$vm" template_for_dispvms 2>/dev/null || echo "False") [ "$is_dvm" = "True" ] || continue if qvm-volume list "$vm" 2>/dev/null | grep -q "${POOL_NAME}"; then continue fi clone_name="${vm}-ephemeral" if qvm-ls --raw-list 2>/dev/null | grep -q "^${clone_name}$"; then echo " -> Removing old copy: ${clone_name}" qvm-kill "$clone_name" 2>/dev/null || true qvm-remove --force "$clone_name" 2>/dev/null || true fi echo " -> Cloning: $vm -> ${clone_name}" if qvm-clone -P "${POOL_NAME}" "$vm" "$clone_name"; then qvm-prefs "$clone_name" template_for_dispvms True 2>/dev/null || true qvm-prefs "$clone_name" autostart False 2>/dev/null || true echo " [+] OK" else echo " [!] ERROR" fi done echo "[+] Done!" echo "" echo "VMs in ephemeral pool:" for vm in $(qvm-ls --raw-list 2>/dev/null); do if qvm-volume list "$vm" 2>/dev/null | grep -q "${POOL_NAME}"; then echo " - $vm" fi done EOF sudo chmod +x /usr/local/bin/tmpfs-pool-create.sh sudo systemctl daemon-reload sudo systemctl enable --now tmpfs-pool.service To remove zram pool and disable the systemd service run this script: #!/bin/bash # Step 1: Stop the service (prevents auto-restart on boot) sudo systemctl stop tmpfs-pool.service sudo systemctl disable tmpfs-pool.service # Step 2: Remove all VMs from tmpfs_pool echo "[*] Removing VMs from tmpfs_pool..." for vm in $(qvm-ls --raw-list 2>/dev/null); do if qvm-volume list "$vm" 2>/dev/null | grep -q "tmpfs_pool"; then echo " -> Removing: $vm" qvm-kill "$vm" 2>/dev/null || true sleep 1 qvm-remove --force "$vm" 2>/dev/null || true sleep 1 fi done # Step 3: Remove the Qubes storage pool echo "[*] Removing tmpfs_pool..." qvm-pool remove tmpfs_pool 2>/dev/null || true # Step 4: Clean up LVM echo "[*] Cleaning up LVM..." vgchange -an ephemeral_vg 2>/dev/null || true vgremove -f ephemeral_vg 2>/dev/null || true # Step 5: Free RAM by removing the image echo "[*] Freeing RAM..." rm -rf /dev/shm/ephemeral echo "[+] tmpfs pool stopped and cleaned up." Start the service and pool again: sudo systemctl enable --now tmpfs-pool.service :gear: 4. Modifying default DVMs This solution is for those who want to configure the default DVMs for amnesic operation - without creating new pools or new DVMs. Сreate a systemd service that remounts the root in the DVM as an ephemeral encrypted volatile volume: #!/bin/bash sudo tee /etc/systemd/system/dvm-root.service << 'EOF' [Unit] Description=DVM root rw False After=qubesd.service Requires=qubesd.service [Service] Type=oneshot RemainAfterExit=yes ExecStart=/usr/local/bin/dvm-root.sh [Install] WantedBy=multi-user.target EOF sudo tee /usr/local/bin/dvm-root.sh << 'EOF' #!/bin/bash set -euo pipefail while read -r vm; do [ -n "$vm" ] || continue is_dvm=$(qvm-prefs "$vm" template_for_dispvms 2>/dev/null || echo "False") [ "$is_dvm" = "True" ] || continue qvm-volume config "${vm}:root" rw false done < <(qvm-ls --raw-list) EOF sudo chmod +x /usr/local/bin/dvm-root.sh sudo systemctl daemon-reload sudo systemctl enable dvm-root.service qvm-pool set vm-pool -o ephemeral_volatile=True TMPFS Script for Debian/Fedora/Kicksecure-based DVM templates: OVERLAY_BASE="/run/home-overlay" LOWERDIR="/home" UPPERDIR="$OVERLAY_BASE/upper" WORKDIR="$OVERLAY_BASE/work" MOUNTPOINT="/home" # Create directories mkdir -p "$UPPERDIR" "$WORKDIR" # Mount tmpfs for overlay upper+work layers mount -t tmpfs -o size=2G,mode=0755 tmpfs "$OVERLAY_BASE" # Recreate upper/work inside tmpfs after mount mkdir -p "$UPPERDIR" "$WORKDIR" # Mount overlayfs mount -t overlay overlay \ -o lowerdir="$LOWERDIR",upperdir="$UPPERDIR",workdir="$WORKDIR" \ "$MOUNTPOINT" Or alternative Script: Zram-disk for Debian/Fedora/Kicksecure-based DVM templates (If you want an overlay with memory compression - saves RAM, but adds slight CPU overhead): ZRAM_DEV="" ZRAM_SIZE="2G" LOWERDIR="/home" OVERLAY_BASE="/run/home-overlay" UPPERDIR="$OVERLAY_BASE/upper" WORKDIR="$OVERLAY_BASE/work" MOUNTPOINT="/home" # --- 1. Create and configure zram device --- # Load zram module if not loaded if ! lsmod | grep -q "^zram"; then modprobe zram num_devices=1 || modprobe zram fi # Wait for zram control interface for _ in {1..10}; do if [ -d /sys/class/zram-control ]; then break fi sleep 0.1 done # Find first free zram device for i in /sys/block/zram*; do [ -e "$i" ] || continue if [ "$(cat "$i"/disksize)" = "0" ]; then ZRAM_DEV="/dev/$(basename "$i")" break fi done if [ -z "$ZRAM_DEV" ]; then # No free device; try to add one via hot_add if [ -f /sys/class/zram-control/hot_add ]; then idx=$(cat /sys/class/zram-control/hot_add) ZRAM_DEV="/dev/zram$idx" else echo "ERROR: no free zram device found and hot_add not available" >&2 exit 1 fi fi echo "Using $ZRAM_DEV" # Configure compression and size echo lz4 > /sys/block/$(basename "$ZRAM_DEV")/comp_algorithm 2>/dev/null || true echo "$ZRAM_SIZE" > /sys/block/$(basename "$ZRAM_DEV")/disksize # Format and mount zram as ext4 (needed for overlay upper+work) mkfs.ext4 -q "$ZRAM_DEV" mkdir -p "$OVERLAY_BASE" mount -t ext4 "$ZRAM_DEV" "$OVERLAY_BASE" # --- 2. Prepare overlay directories --- mkdir -p "$UPPERDIR" "$WORKDIR" # --- 3. Mount overlayfs --- mount -t overlay overlay \ -o lowerdir="$LOWERDIR",upperdir="$UPPERDIR",workdir="$WORKDIR" \ "$MOUNTPOINT" For additional anti-forensic protection, you can use this script and redirect all system journal logs to memory (volatile storage). Additionally, this script deletes files containing metadata about removed VMs (this is simple file deletion, which has anti-forensic value only in the context of TRIM/discard on NVMe SSD, but is not direct anti-forensics). The basis of this script (files removing) is the second script from this guide: #!/bin/bash sudo tee /etc/systemd/journald.conf << EOF [Journal] Storage=volatile EOF sudo systemctl restart systemd-journald sudo tee /etc/systemd/system/clean.service << 'EOF' [Unit] Description=Clean logs of removed Qubes VMs After=qubesd.service [Service] Type=oneshot ExecStart=/usr/local/bin/clean.sh RemainAfterExit=no [Install] WantedBy=multi-user.target EOF sudo tee /usr/local/bin/clean.sh << 'EOF' #!/bin/bash set -euo pipefail readonly LOGDIR='/var/log' readonly TEMPDIR_ROOT='/home/user/tmp' readonly MENUDIR='/home/user/.config/menus/applications-merged' existing_qubes=$(qvm-ls --fields=name --raw-data | sort) all_qube_names='' all_qube_names+=$(find "${LOGDIR}/libvirt/libxl/" \ -type f \ -regextype posix-egrep \ -regex '.*\.log((\.old)|(-[0-9]{8}))?(\.gz)?$' \ -exec basename "{}" \; \ | sed -r 's/\.log((\.old)|(-[0-9]{8}))?(\.gz)?$//g' \ | sed -r 's/^(guid|qrexec|qubesdb)\.//g' \ | sort | uniq)$'\n' all_qube_names+=$(find "${LOGDIR}/qubes/" \ -type f \ -regextype posix-egrep \ -regex '.*\.log((\.old)|(-[0-9]{8}))?(\.gz)?$' \ -exec basename "{}" \; \ | sed -r 's/\.log((\.old)|(-[0-9]{8}))?(\.gz)?$//g' \ | sed -r 's/^(guid|qrexec|qubesdb)\.//g' \ | sort | uniq)$'\n' all_qube_names+=$(find "${LOGDIR}/xen/console/" \ -type f \ -regextype posix-egrep \ -regex '.*\/guest-.*\.log((\.old)|(-[0-9]{8}))?(\.gz)?$' \ -exec basename "{}" \; \ | sed -r 's/\.log((\.old)|(-[0-9]{8}))?(\.gz)?$//g' \ | sed -r 's/^guest-//g' \ | sort | uniq)$'\n' set +e ram_pools=$(qvm-pool list | grep -Eio '^ram_pool_[^ ]+' | sort | uniq) set -e all_qube_names+=$(echo "${ram_pools}" | sed -r 's/^ram_pool_//g')$'\n' if [ -d "${TEMPDIR_ROOT}" ]; then all_qube_names+=$(find "${TEMPDIR_ROOT}" \ -mindepth 1 -maxdepth 1 -type d \ -exec basename "{}" \; \ | sort | uniq)$'\n' fi all_qube_names+=$(find "${MENUDIR}" \ -regextype posix-egrep \ -regex '.*\/user-qubes-.*\.menu$' \ -exec basename "{}" \; \ | sed -r 's/\.menu$//g' \ | sed -r 's/^user-qubes-(disp)?vm-directory(_|-)//g' \ | sort | uniq)$'\n' all_qube_names=$(echo "${all_qube_names}" \ | sed -r 's/^(Domain-0|libxl-driver)$//g' \ | sed -r '/^\s*$/d' \ | sort | uniq) set +e qubes_to_remove=$(diff --new-line-format='' --unchanged-line-format='' \ <(echo "${all_qube_names}") <(echo "${existing_qubes}") \ | sed -r '/^\s*$/d') set -e for qube_name in ${qubes_to_remove}; do decoded_qube_name=$(echo "${qube_name}" \ | sed -r 's/_d/-/g' \ | sed -r 's/_u/_/g') log_pattern="${qube_name}\.log((\.old)|(-[0-9]{8}))?(\.gz)?" menu_pattern="user-qubes-(disp)?vm-directory(_|-)${qube_name}\.menu" declare -A targets targets=(["${LOGDIR}/libvirt/libxl"]="${log_pattern}" ["${LOGDIR}/qubes"]="((guid|qrexec|qubesdb)\.)?${log_pattern}" ["${LOGDIR}/xen/console"]="guest-${log_pattern}" ["${MENUDIR}"]="${menu_pattern}") if [ -d "${TEMPDIR_ROOT}" ]; then targets+=("${TEMPDIR_ROOT}"="${qube_name}") fi for search_dir in "${!targets[@]}"; do mapfile -d $'\0' found_files < <(find "${search_dir}" \ -regextype posix-egrep \ -regex ".*\/${targets[${search_dir}]}$" \ -print0) for file in "${found_files[@]}"; do [ -z "${file}" ] && continue rm -rf "${file}" done done done for pool_name in ${ram_pools}; do qube_name=$(echo "${pool_name}" | sed -r 's/^ram_pool_//') if ! echo "${existing_qubes}" | grep -qx "${qube_name}"; then pool_mountpoint=$(qvm-pool info "${pool_name}" \ | grep -E '^dir_path' \ | sed -r 's/^dir_path\s+//g') qvm-pool remove "${pool_name}" 2>/dev/null || true umount "${pool_mountpoint}" 2>/dev/null || true rm -rf "${pool_mountpoint}" 2>/dev/null || true fi done find "${LOGDIR}/qubes/" -maxdepth 1 -type f -name '*.log.old' -delete EOF sudo chmod +x /usr/local/bin/clean.sh sudo systemctl daemon-reload sudo systemctl enable clean.service npub1cv8cl9nfqtrkdn3k0nk05z8mjte6hxtdws6j4x65jxc06nk5tu2s4ulsnl linux_privacy Encrypted AppVMs and Templates. Encrypted pool. Secret vault in a LUKS-Pool. :gear: Install: Make sure there is sufficient free space (more than 3 GB) in dom0. You can resize dom0 these commands: sudo lvresize --size 3G /dev/mapper/qubes_dom0-root sudo resize2fs /dev/mapper/qubes_dom0-root sudo lvresize -L +3G qubes_dom0/root-pool I recommend disabling disk swap for maximum security: Qubes OS uses zram for compressed RAM swap, but also maintains a fallback swap partition at /dev/dm-5 which is not encrypted. For a vault pool setup, this is a security risk - sensitive memory pages from your secret VMs could be written to disk in plaintext. sudo sed -i '/\/dev\/mapper\/swap/!{/^[[:space:]]*#/!{/\<swap\>/s/^/# /}}' /etc/fstab sudo sed -i '/\/dev\/mapper\/swap/b; /[[:space:]]\+swap[[:space:]]\+/s/^/# /' /etc/fstab sudo dracut --force Run this simple script for create 3 GB encrypted pool vault (edit POOL_SIZE="3G" if you need a different pool size) #!/bin/bash set -euo pipefail POOL_DIR="/var/lib/qubes/pools" POOL_IMG="$POOL_DIR/vault.img" POOL_SIZE="3G" VG_NAME="vault_vg" LV_NAME="vault_thin" MAPPER_NAME="vault_crypt" cleanup() { local dev="${LOOP_DEV:-}" if [ -n "$dev" ] && losetup -a | grep -q "$dev"; then echo "[*] Cleanup: detaching $dev" losetup -d "$dev" 2>/dev/null || true fi } trap cleanup EXIT echo "========================================" echo " Creating an encrypted Qubes pool" echo "========================================" echo if [ "$EUID" -ne 0 ]; then echo "[!] This script must be run as root (dom0)" exit 1 fi if [ -f "$POOL_IMG" ]; then echo "[!] File $POOL_IMG already exists." read -r -p " Delete and recreate? Type YES to confirm: " confirm if [ "$confirm" != "YES" ]; then echo "[!] Aborted by user" exit 1 fi rm -f "$POOL_IMG" fi echo "[*] Creating directory $POOL_DIR" mkdir -p "$POOL_DIR" echo "[*] Creating a loop file of size $POOL_SIZE" truncate -s "$POOL_SIZE" "$POOL_IMG" echo "[*] Attaching loop device" LOOP_DEV=$(losetup -f --show "$POOL_IMG") echo " Device: $LOOP_DEV" echo echo "[*] Encrypting device $LOOP_DEV" echo " Enter the LUKS passphrase (twice)" cryptsetup luksFormat "$LOOP_DEV" echo echo "[*] Opening the LUKS container" cryptsetup open "$LOOP_DEV" "$MAPPER_NAME" echo echo "[*] Creating LVM: PV -> VG -> Thin Pool" pvcreate "/dev/mapper/$MAPPER_NAME" vgcreate "$VG_NAME" "/dev/mapper/$MAPPER_NAME" lvcreate -T -n "$LV_NAME" -l +100%FREE "$VG_NAME" echo echo "[*] Detaching the loop device (LVM stays active)" losetup -d "$LOOP_DEV" unset LOOP_DEV qvm-pool --add vault lvm_thin \ -o volume_group=vault_vg,thin_pool=vault_thin,revisions_to_keep=2 echo echo "========================================" echo " Pool created successfully!" echo "========================================" cat > /usr/local/bin/vault-open << 'EOF' #!/bin/bash # open-and-register-vault.sh set -euo pipefail if [ "$EUID" -ne 0 ]; then echo "[!] Must be run as root" exit 1 fi # 1. Attach loop LOOP_DEV=$(losetup -f --show /var/lib/qubes/pools/vault.img) echo "[*] Loop: $LOOP_DEV" # 2. Unlock LUKS cryptsetup open "$LOOP_DEV" vault_crypt echo "[*] LUKS opened" # 3. Activate LVM vgchange -ay vault_vg echo "[*] LVM activated" # 4. Register pool in Qubes if not already present if ! qvm-pool --list | grep -q "^vault "; then echo "[*] Registering pool in Qubes" qvm-pool --add vault lvm_thin \ -o volume_group=vault_vg,thin_pool=vault_thin,revisions_to_keep=2 else echo "[*] Pool already registered" fi echo "[*] Done. Verification:" qvm-pool --info vault EOF cat > /usr/local/bin/vault-close << 'EOF' #!/bin/bash # close-vault.sh set -euo pipefail if [ "$EUID" -ne 0 ]; then echo "[!] Must be run as root" exit 1 fi # 1. Stop all running VMs from the vault pool RUNNING_VMS=$(qvm-ls --running --fields=name,pool | grep vault | awk '{print $1}' || true) if [ -n "$RUNNING_VMS" ]; then echo "[*] Stopping VMs from the vault pool..." for vm in $RUNNING_VMS; do echo " -> $vm" qvm-shutdown --wait "$vm" done echo "[*] All vault pool VMs stopped" fi # 2. Deactivate LVM vgchange -an vault_vg || true # 3. Close LUKS (wipes key from kernel memory) cryptsetup close vault_crypt || true # 4. Detach loop LOOP_DEV=$(losetup -j /var/lib/qubes/pools/vault.img 2>/dev/null | head -1 | cut -d: -f1) if [ -n "$LOOP_DEV" ]; then losetup -d "$LOOP_DEV" fi echo "[*] Pool locked and key wiped from memory" EOF Confirm creation: When prompted, type YES to proceed (this overwrites any existing vault pool file). Set your LUKS passphrase: Enter and confirm a strong passphrase when prompted - this will be required every time you unlock the pool. Open the pool when needed in dom0: sudo vault-open and enter your LUKS passphrase. Assign VMs to the vault pool via Qubes Manager: click clone qube and in Advanced select vault in Storage pool. Or create a new qube, and select vault Storage pool in the Advanced Options. 1 1751×251 21.3 KB Now your secret VMs can run. Close the pool when finished: sudo vault-close this shuts down all vault VMs, deactivates LVM, wipes the encryption key from kernel memory, and locks the container. :eyes: :eyes: Opening large pools takes time. If you created a large pool and it contains significant data (tens of GB), opening it with vault-open may take 10-20 seconds - this is normal, as LVM needs to scan and activate the thin pool metadata. Backup the container along with your VMs. You can copy the encrypted container file together with backups of your AppVMs from this pool. The container is located at: /var/lib/qubes/pools/vault.img Since the container is fully encrypted, you can store it on external media or in cloud storage without additional encryption - the LUKS passphrase protects all data inside. If you need additional disk swap, you can create an ephemeral encrypted swap - there’s a simple guide in the description of this guide. :bomb: Removing Encrypted Pool To completely remove the vault pool and all associated data, use this script. This will permanently delete all VMs stored in the pool. (When prompted, type DELETE to proceed - this permanently destroys all vault VMs and their data) #!/bin/bash set -euo pipefail POOL_NAME="vault" POOL_DIR="/var/lib/qubes/pools" POOL_IMG="$POOL_DIR/vault.img" VG_NAME="vault_vg" LV_NAME="vault_thin" MAPPER_NAME="vault_crypt" echo "========================================" echo " Removing encrypted pool" echo "========================================" echo if [ "$EUID" -ne 0 ]; then echo "[!] This script must be run as root (dom0)" exit 1 fi echo "[!] WARNING: ALL data in the pool will be destroyed!" read -r -p " Type DELETE to confirm: " confirm if [ "$confirm" != "DELETE" ]; then echo "[!] Aborted by user" exit 1 fi for vm in $(qvm-ls --raw-list 2>/dev/null); do if qvm-volume list "$vm" 2>/dev/null | grep -q "${POOL_NAME}"; then echo " -> Removing VM: $vm" qvm-kill "$vm" 2>/dev/null || true sleep 1 qvm-remove --force "$vm" 2>/dev/null || true sleep 1 fi done if lvs "$VG_NAME/$LV_NAME" &>/dev/null; then echo "[*] Deactivating thin pool $VG_NAME/$LV_NAME" lvchange -an "$VG_NAME/$LV_NAME" || true fi if vgs "$VG_NAME" &>/dev/null; then echo "[*] Deactivating Volume Group $VG_NAME" vgchange -an "$VG_NAME" || true fi if dmsetup info "$MAPPER_NAME" &>/dev/null; then echo "[*] Closing LUKS container /dev/mapper/$MAPPER_NAME" cryptsetup close "$MAPPER_NAME" || true else echo "[*] LUKS container already closed" fi if vgs "$VG_NAME" &>/dev/null; then echo "[*] Removing Volume Group $VG_NAME" vgremove -y "$VG_NAME" || true else echo "[*] Volume Group $VG_NAME not found or already removed" fi if [ -f "$POOL_IMG" ]; then LOOP_DEV=$(losetup -j "$POOL_IMG" 2>/dev/null | head -1 | cut -d: -f1) if [ -n "$LOOP_DEV" ]; then echo "[*] Detaching loop device $LOOP_DEV" losetup -d "$LOOP_DEV" 2>/dev/null || true else echo "[*] Loop device already detached" fi fi if [ -f "$POOL_IMG" ]; then echo "[*] Deleting container file $POOL_IMG" rm -f "$POOL_IMG" fi if [ -d "$POOL_DIR" ] && [ -z "$(ls -A "$POOL_DIR" 2>/dev/null)" ]; then echo "[*] Removing empty directory $POOL_DIR" rmdir "$POOL_DIR" 2>/dev/null || true fi qvm-pool remove $POOL_NAME echo echo "========================================" echo " Pool removed" echo "========================================" npub1cv8cl9nfqtrkdn3k0nk05z8mjte6hxtdws6j4x65jxc06nk5tu2s4ulsnl linux_privacy This guide adds a hidden “nuke” password to your LUKS-encrypted disk. If you ever type this special password instead of your real one during boot, the system instantly and permanently destroys all encryption keys on the disk, making the data completely unrecoverable. Here’s how it works: During boot, your system asks for a password to unlock the disk. This guide installs a small wrapper that sits between the boot process and the standard disk-unlocking tool. Every time you enter a password, the wrapper quietly checks it against the nuke password hash stored on the system. If it’s your normal password, the wrapper simply passes it along to the regular unlocking tool and your disk opens as usual. But if it detects the nuke password, it immediately wipes all encryption key slots on the disk and overwrites the LUKS header with random data, effectively destroying the disk beyond recovery. The whole thing integrates seamlessly with the existing boot process, so the password prompt looks identical whether you’re entering your real password or the nuke one - there’s no visible indication that anything unusual happened until it’s too late. :warning: Warning! Do not test this script on your working system! You can install and configure the nuke module, but do not enter the nuke password for testing during Qubes OS boot! If you want to test the script, install a Fedora ISO and nuke it (I destroyed Fedora 43 and 44 with nuke password). Install: Install openssl: sudo qubes-dom0-update openssl Create dir for nuke password: sudo mkdir -p /etc/cryptsetup-nuke-ng sudo chmod 700 /etc/cryptsetup-nuke-ng Enter this command and then enter nuke password (hidden input): read -rs NUKE_PASS echo Generate random salt (enter this command and again enter nuke password (hidden input)): read -rs NUKE_PASS; echo; SALT=$(tr -dc 'a-zA-Z0-9' </dev/urandom | head -c 8); printf '%s' "$NUKE_PASS" | openssl passwd -6 -salt "$SALT" -stdin | sudo tee /etc/cryptsetup-nuke-ng/password_hash >/dev/null; sudo chmod 600 /etc/cryptsetup-nuke-ng/password_hash; NUKE_PASS=$(dd if=/dev/urandom bs=64 count=1 2>/dev/null | base64); unset NUKE_PASS Check hash format: sudo cat /etc/cryptsetup-nuke-ng/password_hash (should be: $6$salt$very_long_hash_string...) Password verification (enter this command and again enter nuke password (hidden input)): read -rs TEST_PASS; echo; HASH=$(sudo cat /etc/cryptsetup-nuke-ng/password_hash); SALT=$(echo "$HASH" | sed 's/^\$6\$\([^$]*\)\$.*/\1/'); printf '%s' "$TEST_PASS" | openssl passwd -6 -salt "$SALT" -stdin | grep -q "^$(sudo cat /etc/cryptsetup-nuke-ng/password_hash)$" && echo "✓ MATCH" || echo "✗ NO MATCH"; unset TEST_PASS (should be: ✓ MATCH) Create dracut module: sudo mkdir -p /usr/lib/dracut/modules.d/99nuke-systemd sudo tee /usr/lib/dracut/modules.d/99nuke-systemd/module-setup.sh << 'EOF' #!/bin/bash check() { if [ ! -f /etc/cryptsetup-nuke-ng/password_hash ]; then derror "nuke password hash not found" return 1 fi return 0 } depends() { echo systemd crypt return 0 } install() { inst /etc/cryptsetup-nuke-ng/password_hash inst_multiple dd mktemp rm cat printf stty cryptsetup openssl systemd-ask-password # Find the real systemd-cryptsetup local real_binary="" for path in "$initdir/usr/bin/systemd-cryptsetup" "$initdir/usr/lib/systemd/systemd-cryptsetup"; do if [ -f "$path" ] && [ ! -L "$path" ]; then real_binary="$path" break fi done if [ -z "$real_binary" ]; then real_binary=$(find "$initdir" -name "systemd-cryptsetup" -type f ! -type l 2>/dev/null | head -n1) fi if [ -z "$real_binary" ]; then dfatal "Cannot find real systemd-cryptsetup binary in initramfs" return 1 fi dinfo "Found systemd-cryptsetup at: $real_binary" # Save the original mv "$real_binary" "${real_binary}.real" # Create the wrapper cat > "$real_binary" << 'WRAPPER' #!/bin/bash set -e NUKE_HASH_FILE="/etc/cryptsetup-nuke-ng/password_hash" SYSTEMD_CRYPTSETUP="${0}.real" # If no hash present — just run the original if [ ! -f "$NUKE_HASH_FILE" ]; then exec "$SYSTEMD_CRYPTSETUP" "$@" fi NUKE_HASH=$(cat "$NUKE_HASH_FILE") NUKE_SALT=$(echo "$NUKE_HASH" | sed 's/^\$6\$\([^$]*\)\$.*/\1/') # Only for attach without keyfile if [ "$1" = "attach" ]; then KEYFILE="$4" DEVICE="$3" NAME="$2" # If keyfile is specified and not "-"/"none", the password is not requested interactively if [ -n "$KEYFILE" ] && [ "$KEYFILE" != "-" ] && [ "$KEYFILE" != "none" ]; then exec "$SYSTEMD_CRYPTSETUP" "$@" fi # Request password via systemd-ask-password (Plymouth compatible!) PASSWORD=$(systemd-ask-password --no-tty "Please enter passphrase for disk $NAME:" 2>/dev/null || true) # If systemd-ask-password didn't work, fallback to TTY if [ -z "$PASSWORD" ] && [ -t 0 ]; then printf 'Please unlock disk %s: ' "$NAME" >&2 stty -echo 2>/dev/null || true IFS= read -r PASSWORD stty echo 2>/dev/null || true printf '\n' >&2 fi # Verify nuke password via openssl if [ -n "$PASSWORD" ]; then COMPUTED=$(printf '%s' "$PASSWORD" | openssl passwd -6 -salt "$NUKE_SALT" -stdin) if [ "$COMPUTED" = "$NUKE_HASH" ]; then printf '[NUKE] Nuke password detected! Destroying keys...\n' >&2 # Destroy all keyslots for slot in 0 1 2 3 4 5 6 7; do cryptsetup luksKillSlot "$DEVICE" "$slot" 2>/dev/null || true done # Overwrite the header for extra certainty if command -v dd >/dev/null 2>&1; then dd if=/dev/urandom of="$DEVICE" bs=1M count=4 2>/dev/null || true fi printf '[NUKE] Device %s nuked.\n' "$DEVICE" >&2 exit 1 fi fi # Pass the password via temporary file (keyfile) KEYFILE=$(mktemp -p /dev/shm 2>/dev/null || mktemp) chmod 600 "$KEYFILE" printf '%s' "$PASSWORD" > "$KEYFILE" PASSWORD="" # Call the original with keyfile instead of interactive prompt set +e "$SYSTEMD_CRYPTSETUP" "$1" "$2" "$3" "$KEYFILE" "$5" STATUS=$? set -e # Clean up keyfile dd if=/dev/urandom of="$KEYFILE" bs=512 count=1 2>/dev/null || true rm -f "$KEYFILE" exit $STATUS fi # For all other commands — run the original exec "$SYSTEMD_CRYPTSETUP" "$@" WRAPPER chmod +x "$real_binary" # Update symlink if needed if [ -L "$initdir/usr/lib/systemd/systemd-cryptsetup" ]; then rm -f "$initdir/usr/lib/systemd/systemd-cryptsetup" ln -s "../../bin/systemd-cryptsetup" "$initdir/usr/lib/systemd/systemd-cryptsetup" fi dinfo "Installed nuke wrapper for systemd-cryptsetup" } EOF sudo chmod +x /usr/lib/dracut/modules.d/99nuke-systemd/module-setup.sh Dracut update: sudo dracut --force --verbose You will see this logs: ... dracut[I]: *** Including module: nuke-systemd *** dracut[I]: Found systemd-cryptsetup at: /var/tmp/dracut.o6togY/initramfs/usr/bin/systemd-cryptsetup dracut[I]: Installed nuke wrapper for systemd-cryptsetup ... :white_check_mark: Done! npub1cv8cl9nfqtrkdn3k0nk05z8mjte6hxtdws6j4x65jxc06nk5tu2s4ulsnl linux_privacy Duress password is integrated at two authentication points: the lightdm display manager for system login, and the xscreensaver lock screen for session unlock. This ensures the duress action can be triggered both at initial boot authentication and when unlocking an existing session. See this guide for creating a duress nuke-password for a LUKS-encrypted disk: Hidden “nuke” duress-password to LUKS-encrypted disk. Qubes OS LUKS self-destruct module Make backups of all your VMs beforehand. Download this repository in dvm: git clone https://github.com/nuvious/pam-duress.git then add it to a ZIP archive, for example pam.zip Send it to dom0 and extract archive qvm-run --pass-io dispXXX 'cat /home/user/pam.zip' > pam.zip unzip pam.zip Run these commands to build the module in dom0 and create duress-password: sudo qubes-dom0-update gcc gcc-c++ make cmake automake pam-devel openssl-devel cd pam-duress make sudo make install PAM_DIR=/usr/lib64/security make clean mkdir -p ~/.duress sudo mkdir -p /etc/duress.d Create a scripts with two scenarios: 1. Scenario: Delete some VMs and delete this script itself: cat > ~/remove_vm.sh << 'EOF' #!/bin/bash if ! [ -w /run/qubesd.sock ] && [ -z "$DURESS_REEXEC" ]; then exec sg qubes "DURESS_REEXEC=1 $0 $*" fi export PATH="/usr/bin:/usr/sbin:/bin:/sbin:$PATH" # Add the names of VMs to be deleted here 'test_vm' /usr/bin/qvm-remove --force test_vm EOF chmod +x ~/remove_vm.sh ( :backhand_index_pointing_up: Add the names of VMs to be deleted here ‘test_vm’) 2. Scenario: Total system destruction: cat > ~/remove_qubes.sh << 'EOF' #!/bin/bash # WARNING: This script irreversibly destroys data. Use only for duress password. if ! [ -w /run/qubesd.sock ] && [ -z "$DURESS_REEXEC" ]; then exec sg qubes "DURESS_REEXEC=1 $0 $*" fi export PATH="/usr/bin:/usr/sbin:/bin:/sbin:$PATH" # ========== PHASE 0: REMOVE RAM-WIPE MODULE ========== RAM_WIPE_DIR="/usr/lib/dracut/modules.d/40ram-wipe" if [ -d "$RAM_WIPE_DIR" ]; then echo "[*] Removing ram-wipe dracut module to prevent shutdown stall..." rm -rf "$RAM_WIPE_DIR" fi # ========== PHASE 1: DESTROY LUKS HEADER ========== LUKS_DEV="" for dev in /dev/nvme0n1p3 /dev/sda2 /dev/vda2 /dev/mapper/luks-*; do cryptsetup isLuks "$dev" 2>/dev/null && LUKS_DEV="$dev" && break done if [ -n "$LUKS_DEV" ]; then echo "[*] Erasing LUKS header on $LUKS_DEV..." if cryptsetup luksErase --batch-mode "$LUKS_DEV" 2>/dev/null; then echo "[*] LUKS header destroyed. Triggering emergency reboot..." echo 1 > /proc/sys/kernel/sysrq echo b > /proc/sysrq-trigger xl debug-keys R 2>/dev/null || true exit 0 fi fi # ========== PHASE 2: FALLBACK - DESTROY ALL VMs + CORRUPT LVM ========== echo "[!] LUKS erase failed or not found. Executing fallback destruction..." echo "[*] Removing all qubes..." qvm-remove --all -f 2>/dev/null || true echo "[*] Corrupting LVM metadata..." vgremove -ff qubes_dom0 2>/dev/null || true for pv in /dev/nvme0n1p3 /dev/sda2 /dev/vda2; do [ -b "$pv" ] && dd if=/dev/urandom of="$pv" bs=1M count=4 conv=notrunc 2>/dev/null && break done # ========== PHASE 3: INSTANT REBOOT ========== echo "[*] Triggering emergency reboot..." echo 1 > /proc/sys/kernel/sysrq echo b > /proc/sysrq-trigger xl debug-keys R 2>/dev/null || true EOF chmod +x ~/remove_qubes.sh Then add scripts in duress module: cat > ~/.duress/remove_passwd.sh << 'EOF' #!/bin/sh /home/user/remove_vm.sh rm -rf /home/user/remove_vm.sh rm -rf /var/log/qubes EOF cat > ~/.duress/remove_passwd2.sh << 'EOF' #!/bin/sh /home/user/remove_qubes.sh EOF then chmod 500 ~/.duress/remove_passwd.sh chmod 500 ~/.duress/remove_passwd2.sh duress_sign ~/.duress/remove_passwd.sh add alternative password for destroying VMs duress_sign ~/.duress/remove_passwd2.sh add alternative password for destroying system and then: chmod 400 ~/.duress/remove_passwd.sh.sha256 chmod 400 ~/.duress/remove_passwd2.sh.sha256 Check: ls -la ~/.duress/ Module created. Now we need to modify this file /etc/pam.d/system-auth sudo nano /etc/system-auth Add this line auth sufficient pam_duress.so so that the first 6 auth-lines look like this: auth required pam_env.so auth required pam_faildelay.so delay=2000000 auth sufficient pam_fprintd.so auth sufficient pam_unix.so nullok auth sufficient pam_duress.so auth required pam_deny.so then click Ctrl + C and Ctrl + X Done. :fire: Now first duress password will remove some VMs. :radioactive: Second duress password will destroy and forcibly shutdown the system. It should protect against forensics (disk will become unreadable). npub1cv8cl9nfqtrkdn3k0nk05z8mjte6hxtdws6j4x65jxc06nk5tu2s4ulsnl linux_privacy Install Camoufox v150: Run in debian-13-xfce terminal: sudo apt install python3.13-venv pip python3-pyqt5 python3-pyqt5.qtwebengine Shutdown debian-13-xfce template. Run in AppVM terminal: nano cam.py write the configuration code according to the Camoufox documentation, for example like this: from camoufox.utils import launch_options from playwright.sync_api import sync_playwright import os import time PROFILE_DIR = "/home/user/my_profile" if not os.path.exists(PROFILE_DIR): raise FileNotFoundError(f"Профиль не найден: {PROFILE_DIR}") options = launch_options( headless=False, os="windows", locale="en-US", window=(1696, 1026), config={ "timezone": "Europe/Istanbul", "geolocation:latitude": 41.0082, "geolocation:longitude": 28.9784, "navigator.language": "en-US", "navigator.languages": ["en-US", "en"], "headers.Accept-Language": "en-US,en;q=0.9", "screen.width": 1700, "screen.height": 970, "screen.availWidth": 1700, "screen.availHeight": 940, "screen.colorDepth": 24, "screen.pixelDepth": 24, }, i_know_what_im_doing=True, ) options.pop("executable_path", None) options.pop("headless", None) options["viewport"] = {"width": 1700, "height": 970} custom_args = ["-new-instance", "-no-remote"] if "args" in options: custom_args = options.pop("args") + custom_args with sync_playwright() as p: context = p.firefox.launch_persistent_context( user_data_dir=PROFILE_DIR, executable_path="/home/user/.cache/camoufox/browsers/official/150.0.2-alpha.26/camoufox-bin", headless=False, args=custom_args, **options ) page = context.pages[0] if context.pages else context.new_page() try: page.goto("https://abrahamjuliot.github.io/creepjs", wait_until="commit", timeout=60000) except Exception as e: print(f"Failed to load page: {e}") while True: try: if context.pages is None: break pages = context.pages if not pages: break page.evaluate("1") time.sleep(1) except Exception: break try: context.close() except: pass then click CTRL + O and CTRL + X python3 -m venv ~/venvs/camoufox150 source ~/venvs/camoufox150/bin/activate pip install cloverlabs-camoufox[geoip] python3 -m camoufox fetch official/stable/v150.0.2-alpha.26 pip install playwright==1.51.0 python3 cam.py You can also use a profile created in Donut Browser. Profiles are located in /home/user/.local/share/DonutBrowser/profiles/. Copy profile to, for example, /home/user, rename it for simplicity, for example donut_profile, and add profile to Camoufox configuration into PROFILE_DIR = for example, PROFILE_DIR = "/home/user/donut_profile" or add this profile in camoufox gui manager. Create files .sh for launch this profiles: nano /home/user/camoufox.sh #!/usr/bin/env bash source ~/venvs/camoufox150/bin/activate python3 cam.py then click CTRL + O and CTRL + X chmod +x /home/user/camoufox.sh You can add GUI profile manager for camoufox v150 GitHub - TechQaiser/camoufox-profile-manager: Manage multiple Camoufox browser profiles with proxy, storage, and fullscreen — GoLogin-style GUI built in PyQt5. · GitHub git clone https://github.com/TechQaiser/camoufox-profile-manager.git cd camoufox-profile-manager python3 -m venv venv then change py configs for camoufox v150 (default works on camoufox 135): nano /home/user/camoufox-profile-manager/run.py add this: import os import sys import subprocess import importlib.util from pathlib import Path REQUIREMENTS = ["PyQt5", "cloverlabs-camoufox[geoip]"] HERE = Path(__file__).resolve().parent ENTRY_CANDIDATES = [ "main_window.py", ] def run(cmd, cwd=None): print(f"[\~] Running: {' '.join(map(str, cmd))}") subprocess.check_call(cmd, cwd=str(cwd) if cwd else None) def pip_install(pkg): run([sys.executable, "-m", "pip", "install", pkg], cwd=HERE) def check_and_install(): try: import pip except ImportError: print("[x] pip is not installed. Please install pip and re-run.") sys.exit(1) for spec in REQUIREMENTS: base = spec.split("[", 1)[0] if importlib.util.find_spec(base) is None: print(f"[!] Missing {spec}, installing…") pip_install(spec) else: print(f"[✓] {base} already installed") def ensure_camoufox_browser(): """Skip browser check""" print("[✓] Skipping browser auto-install — using manually installed v150.0.2-alpha.26") return def find_entry_file() -> Path: for name in ENTRY_CANDIDATES: candidate = HERE / name if candidate.exists(): return candidate print("[x] Could not find an entry file. Expected one of:") for n in ENTRY_CANDIDATES: print(f" - {n} (in {HERE})") sys.exit(2) def main(): check_and_install() ensure_camoufox_browser() entry = find_entry_file() print("\n[✓] Environment ready. Launching Camoufox Manager…\n") try: run([sys.executable, str(entry.name)], cwd=HERE) except subprocess.CalledProcessError as e: print("\n[x] Failed to launch the app.") print(f" Command: {' '.join(map(str, e.cmd))}") print(f" Exit code: {e.returncode}") sys.exit(e.returncode) if __name__ == "__main__": main() then click CTRL + O and CTRL + X nano /home/user/camoufox-profile-manager/main_window.py add this: import json import os import sys import time from dataclasses import dataclass, asdict, field from typing import Dict, Any, Optional, List from pathlib import Path from PyQt5 import QtCore, QtGui, QtWidgets, uic # ---- Camoufox ---------------------------------------------------------------- try: from camoufox.utils import launch_options CAMOUFOX_OK = True except Exception: CAMOUFOX_OK = False PROFILES_FILE = "profiles.json" CAMOUFOX_V150_EXE: Optional[str] = "/home/user/.cache/camoufox/browsers/official/150.0.2-alpha.26/camoufox-bin" if CAMOUFOX_V150_EXE is None and CAMOUFOX_OK: from platformdirs import user_cache_dir cache_dir = Path(user_cache_dir("camoufox")) version_dir = cache_dir / "browsers" / "official" / "150.0.2-alpha.26" if sys.platform == "linux": candidate = version_dir / "camoufox-bin" if candidate.exists(): CAMOUFOX_V150_EXE = str(candidate) elif sys.platform == "darwin": candidate = version_dir / "Camoufox.app" / "Contents" / "Windows" / "camoufox" if candidate.exists(): CAMOUFOX_V150_EXE = str(candidate) elif sys.platform == "win32": candidate = version_dir / "camoufox.exe" if candidate.exists(): CAMOUFOX_V150_EXE = str(candidate) # ===== Data models ===== @dataclass class ProxyConfig: host: str = "" port: int = 0 username: str = "" password: str = "" def to_proxy_dict(self) -> Optional[Dict[str, Any]]: if not self.host or not self.port: return None d = {"server": f"http://{self.host}:{self.port}"} if self.username: d["username"] = self.username if self.password: d["password"] = self.password return d @dataclass class Profile: name: str = "Profile" viewport_width: int = 1700 viewport_height: int = 970 fullscreen: bool = False persistent_dir: str = "" use_geoip: bool = False proxy: ProxyConfig = field(default_factory=ProxyConfig) def to_dict(self) -> Dict[str, Any]: d = asdict(self) d["proxy"] = asdict(self.proxy) return d @staticmethod def from_dict(d: Dict[str, Any]) -> "Profile": raw_proxy = d.get("proxy", {}) if not isinstance(raw_proxy, dict): raw_proxy = {} name = d.get("name", "Profile") persistent_dir = d.get("persistent_dir", "") if not persistent_dir: persistent_dir = os.path.join("C:\\", name) return Profile( name=name, viewport_width=int(d.get("viewport_width", 1700)), viewport_height=int(d.get("viewport_height", 970)), fullscreen=bool(d.get("fullscreen", False)), persistent_dir=persistent_dir, use_geoip=bool(d.get("use_geoip", False)), proxy=ProxyConfig( host=raw_proxy.get("host", ""), port=int(raw_proxy.get("port", 0) or 0), username=raw_proxy.get("username", ""), password=raw_proxy.get("password", ""), ), ) # ===== Persistence ===== def load_profiles() -> List[Profile]: if not os.path.exists(PROFILES_FILE): return [] with open(PROFILES_FILE, "r", encoding="utf-8") as f: raw = json.load(f) return [Profile.from_dict(x) for x in raw] def save_profiles(profiles: List[Profile]) -> None: with open(PROFILES_FILE, "w", encoding="utf-8") as f: json.dump([p.to_dict() for p in profiles], f, indent=2) # ===== Worker thread ===== class CamoufoxWorker(QtCore.QThread): started_ok = QtCore.pyqtSignal(str) error = QtCore.pyqtSignal(str) stopped = QtCore.pyqtSignal(str) def __init__(self, profile: Profile, launch_size: Optional[tuple[int, int]] = None, parent=None): super().__init__(parent) self.profile = profile self.launch_size = launch_size self._stop = False self._context = None self._playwright = None def run(self): if not CAMOUFOX_OK: self.error.emit( "Camoufox Python package not available.\n" "Install: pip install -U 'cloverlabs-camoufox[geoip]'" ) return if not CAMOUFOX_V150_EXE or not os.path.exists(CAMOUFOX_V150_EXE): self.error.emit( f"Camoufox v150 executable not found.\n" f"Expected at: {CAMOUFOX_V150_EXE or '<auto-detect failed>'}\n\n" f"Please set CAMOUFOX_V150_EXE in the script to the correct path.\n" f"Find it with: python3 -m camoufox list installed --path\n" f"Or: ls ~/.cache/camoufox/browsers/official/" ) return try: from playwright.sync_api import sync_playwright VIEWPORT_W, VIEWPORT_H = 1700, 970 options = launch_options( headless=False, os="windows", locale="en-US", window=(1696, 1026), config={ "timezone": "Europe/Berlin", "navigator.language": "en-US", "navigator.languages": ["en-US", "en"], "headers.Accept-Language": "en-US,en;q=0.9", "screen.width": 1700, "screen.height": 970, "screen.availWidth": 1700, "screen.availHeight": 940, "screen.colorDepth": 24, "screen.pixelDepth": 24, }, i_know_what_im_doing=True, ) options.pop("executable_path", None) options.pop("headless", None) options["viewport"] = {"width": VIEWPORT_W, "height": VIEWPORT_H} custom_args = ["-new-instance", "-no-remote"] if "args" in options: custom_args = options.pop("args") + custom_args user_data_dir = None if self.profile.persistent_dir: user_data_dir = os.path.abspath(self.profile.persistent_dir) os.makedirs(user_data_dir, exist_ok=True) px = self.profile.proxy.to_proxy_dict() if px: options["proxy"] = px with sync_playwright() as p: self._playwright = p self._context = p.firefox.launch_persistent_context( user_data_dir=user_data_dir, executable_path=CAMOUFOX_V150_EXE, headless=False, args=custom_args, **options ) page = self._context.pages[0] if self._context.pages else self._context.new_page() try: page.set_viewport_size({"width": VIEWPORT_W, "height": VIEWPORT_H}) except Exception: pass try: page.goto("https://abrahamjuliot.github.io/creepjs", wait_until="commit", timeout=60000) except Exception: pass if self.profile.fullscreen: try: page.keyboard.press("F11") except Exception: pass self.started_ok.emit(f"Session started for '{self.profile.name}'.") while not self._stop: time.sleep(0.2) self._context.close() except Exception as e: self.error.emit(f"Failed to start Camoufox: {e}") finally: if self._context is not None: try: self._context.close() except Exception: pass self.stopped.emit(f"Session stopped for '{self.profile.name}'.") def request_stop(self): self._stop = True # ===== MainWindow Controller ===== class MainWindow(QtWidgets.QMainWindow): def __init__(self): super().__init__() uic.loadUi("camoufox_manager.ui", self) self.profileList: QtWidgets.QListWidget self.newProfileButton: QtWidgets.QPushButton self.deleteProfileButton: QtWidgets.QPushButton self.nameEdit: QtWidgets.QLineEdit self.spinW: QtWidgets.QSpinBox self.spinH: QtWidgets.QSpinBox self.fullscreenCheck: QtWidgets.QCheckBox self.proxyHostEdit: QtWidgets.QLineEdit self.proxyPortSpin: QtWidgets.QSpinBox self.proxyUserEdit: QtWidgets.QLineEdit self.proxyPassEdit: QtWidgets.QLineEdit self.geoipCheck: QtWidgets.QCheckBox self.storageEdit: QtWidgets.QLineEdit self.browseStorageButton: QtWidgets.QPushButton self.saveButton: QtWidgets.QPushButton self.launchButton: QtWidgets.QPushButton self.stopButton: QtWidgets.QPushButton self.profiles: List[Profile] = load_profiles() self.current_index: int = -1 self.worker: Optional[CamoufoxWorker] = None self.profileList.itemSelectionChanged.connect(self._on_select_profile) self.newProfileButton.clicked.connect(self._new_profile) self.deleteProfileButton.clicked.connect(self._delete_profile) self.saveButton.clicked.connect(self._save_changes) self.browseStorageButton.clicked.connect(self._browse_storage) self.launchButton.clicked.connect(self._launch) self.stopButton.clicked.connect(self._stop) self.launchButton.setObjectName("primary") self.stopButton.setObjectName("danger") self._refresh_list() if self.profiles: self.profileList.setCurrentRow(0) self._set_running(False) QtWidgets.QApplication.setStyle("Fusion") self._apply_palette() self.statusbar.showMessage("Ready") def _apply_palette(self): p = QtGui.QPalette() base = QtGui.QColor(248, 249, 251) text = QtGui.QColor(33, 37, 41) highlight = QtGui.QColor(76, 110, 245) p.setColor(QtGui.QPalette.Window, base) p.setColor(QtGui.QPalette.Base, QtGui.QColor(255, 255, 255)) p.setColor(QtGui.QPalette.AlternateBase, QtGui.QColor(245, 246, 248)) p.setColor(QtGui.QPalette.WindowText, text) p.setColor(QtGui.QPalette.Text, text) p.setColor(QtGui.QPalette.ButtonText, text) p.setColor(QtGui.QPalette.Highlight, highlight) p.setColor(QtGui.QPalette.HighlightedText, QtGui.QColor(255, 255, 255)) self.setPalette(p) def _refresh_list(self): self.profileList.clear() for p in self.profiles: self.profileList.addItem(p.name) def _current(self) -> Optional[Profile]: if 0 <= self.current_index < len(self.profiles): return self.profiles[self.current_index] return None def _populate_form(self, p: Optional[Profile]): if not p: self.nameEdit.setText("") self.spinW.setValue(1700) self.spinH.setValue(970) self.fullscreenCheck.setChecked(False) self.proxyHostEdit.setText("") self.proxyPortSpin.setValue(0) self.proxyUserEdit.setText("") self.proxyPassEdit.setText("") self.geoipCheck.setChecked(False) self.storageEdit.setText("") return self.nameEdit.setText(p.name) self.spinW.setValue(p.viewport_width) self.spinH.setValue(p.viewport_height) self.fullscreenCheck.setChecked(p.fullscreen) self.proxyHostEdit.setText(p.proxy.host) self.proxyPortSpin.setValue(p.proxy.port) self.proxyUserEdit.setText(p.proxy.username) self.proxyPassEdit.setText(p.proxy.password) self.geoipCheck.setChecked(p.use_geoip) self.storageEdit.setText(p.persistent_dir) def _gather_form(self) -> Profile: p = self._current() or Profile() p.name = self.nameEdit.text().strip() or "Profile" p.viewport_width = int(self.spinW.value()) p.viewport_height = int(self.spinH.value()) p.fullscreen = self.fullscreenCheck.isChecked() p.proxy.host = self.proxyHostEdit.text().strip() p.proxy.port = int(self.proxyPortSpin.value()) p.proxy.username = self.proxyUserEdit.text().strip() p.proxy.password = self.proxyPassEdit.text().strip() p.use_geoip = self.geoipCheck.isChecked() s = self.storageEdit.text().strip() if not s: s = os.path.join("C:\\", p.name) p.persistent_dir = s return p def _set_running(self, running: bool): self.launchButton.setEnabled(not running) self.stopButton.setEnabled(running) for w in [ self.profileList, self.newProfileButton, self.deleteProfileButton, self.nameEdit, self.spinW, self.spinH, self.fullscreenCheck, self.proxyHostEdit, self.proxyPortSpin, self.proxyUserEdit, self.proxyPassEdit, self.geoipCheck, self.storageEdit, self.browseStorageButton, self.saveButton ]: w.setEnabled(not running) def _on_select_profile(self): self.current_index = self.profileList.currentRow() self._populate_form(self._current()) def _new_profile(self): p = Profile(name=f"Profile {len(self.profiles)+1}") p.persistent_dir = os.path.join("C:\\", p.name) self.profiles.append(p) save_profiles(self.profiles) self._refresh_list() self.profileList.setCurrentRow(len(self.profiles)-1) self.statusbar.showMessage("New profile created", 3000) def _delete_profile(self): row = self.profileList.currentRow() if row < 0: return name = self.profiles[row].name if QtWidgets.QMessageBox.question(self, "Confirm Delete", f"Delete profile '{name}'?") != QtWidgets.QMessageBox.Yes: return del self.profiles[row] save_profiles(self.profiles) self._refresh_list() self._populate_form(None) self.current_index = -1 self.statusbar.showMessage(f"Deleted '{name}'", 3000) def _save_changes(self): if self.current_index == -1: self._new_profile() return self.profiles[self.current_index] = self._gather_form() save_profiles(self.profiles) self._refresh_list() self.profileList.setCurrentRow(self.current_index) self.statusbar.showMessage("Profile saved", 3000) def _browse_storage(self): d = QtWidgets.QFileDialog.getExistingDirectory(self, "Select Storage Directory") if d: self.storageEdit.setText(d) def _launch(self): if self.worker and self.worker.isRunning(): self.statusbar.showMessage("A session is already running", 3000) return if self.current_index == -1: QtWidgets.QMessageBox.information(self, "No profile", "Create or select a profile first.") return prof = self._gather_form() self.profiles[self.current_index] = prof save_profiles(self.profiles) if not CAMOUFOX_OK: QtWidgets.QMessageBox.warning( self, "Camoufox not available", "Install with:\n pip install -U 'cloverlabs-camoufox[geoip]'" ) return if not CAMOUFOX_V150_EXE or not os.path.exists(CAMOUFOX_V150_EXE): QtWidgets.QMessageBox.warning( self, "Camoufox v150 not found", f"Executable not found at:\n{CAMOUFOX_V150_EXE or '<auto-detect failed>'}\n\n" f"Set CAMOUFOX_V150_EXE in the script, or run:\n" f" python3 -m camoufox list installed --path\n" f"to find the correct path." ) return if prof.fullscreen: screen = QtWidgets.QApplication.primaryScreen().availableGeometry() launch_size = (screen.width(), screen.height()) else: launch_size = (1700, 970) self.worker = CamoufoxWorker(prof, launch_size) self.worker.started_ok.connect(lambda m: self.statusbar.showMessage(m, 5000)) self.worker.error.connect(lambda m: QtWidgets.QMessageBox.critical(self, "Session Error", m)) self.worker.stopped.connect(self._on_stopped) self.worker.start() self._set_running(True) def _stop(self): if self.worker and self.worker.isRunning(): self.worker.request_stop() self.worker.wait(5000) self.statusbar.showMessage("Stopping session…", 3000) else: self.statusbar.showMessage("No session to stop", 3000) def _on_stopped(self, msg: str): self.statusbar.showMessage(msg, 5000) self._set_running(False) def apply_qss(app, path="dark.qss"): full = os.path.abspath(path) if not os.path.exists(full): raise FileNotFoundError(f"QSS not found: {full}") with open(full, "r", encoding="utf-8") as f: app.setStyleSheet(f.read()) def main(): QtCore.QCoreApplication.setAttribute(QtCore.Qt.AA_EnableHighDpiScaling, True) QtCore.QCoreApplication.setAttribute(QtCore.Qt.AA_UseHighDpiPixmaps, True) app = QtWidgets.QApplication(sys.argv) apply_qss(app, "dark.qss") win = MainWindow() win.show() sys.exit(app.exec_()) if __name__ == "__main__": main() then click CTRL + O and CTRL + X then run: source venv/bin/activate pip install PyQt5 Create launch file for GUI manager: nano camoufox_gui.sh #!/usr/bin/env bash cd camoufox-profile-manager source venv/bin/activate python3 run.py then click CTRL + O and CTRL + X chmod +x /home/user/camoufox_gui.sh Set this proxy value in user.js to enable internet access in the donor profile: user_pref("network.proxy.type", 0); npub1cv8cl9nfqtrkdn3k0nk05z8mjte6hxtdws6j4x65jxc06nk5tu2s4ulsnl linux_privacy Installation of AmneziaVPN app. Create a debian‑12-xfce or debian‑13-xfce template Open Start → Settings → Qubes Tools → Qubes Template Manager. Install the debian‑13‑xfce template After installation, update the template via Qubes Update If servers for Qubes are blocked in your country, use Whonix with Snowflake bridges: Tor Control Panel → Configure → Bridges type → snowflake Install Required Packages in the Template Launch a terminal inside the debian‑13‑xfce template. Run: sudo apt install libxcb-cursor0 libxcb-xinerama0 libnss-resolve iptables Download AmneziaVPN Open a browser in the default‑dvm (a disposable VM). Download the Linux version of AmneziaVPN: Releases · amnezia-vpn/amnezia-client · GitHub Mirror Copy the Downloaded File to the Debian Template Right‑click the downloaded file, choose Copy to other qube, then select the target VM debian‑13‑xfce. In the debian‑13‑xfce VM, open Thunar. Navigate to your home directory → QubesIncoming → the folder whose name starts with disp…. Inside you’ll find the AmneziaVPN archive. Extract the archive and run the installer. Refresh the Application Menu Open Qube Manager (click the blue cube icon on the panel). Select debian‑13‑xfce, then click App Shortcuts at the top. Click Refresh Applications to update the menu list. Shut down debian‑13‑xfce (right‑click → Shutdown). Create a new VPN AppVM In Qube Manager, create a new AppVM based on the debian-13-xfce template. Name it sys‑vpn or sys-amnezia. Go to App Shortcuts for sys‑vpn and move AmneziaVPN and Thunar to the right side. Install AmneziaVPN in the sys‑vpn VM Copy the AmneziaVPN installer file (the one you downloaded earlier) into sys‑vpn. Extract the archive again inside sys‑vpn and run the installer (Otherwise, AmneziaVPN might fail to start). Launch and configure AmneziaVPN AppMenu → sys‑vpn → AmneziaVPN Click + and activate the VPN using a key, a configuration file, a connection to your own VPS, or use the free and premium VPN by Amnezia. 1 1376×637 34.6 KB Enable VLESS protocol in settings. Set up autostart if desired. Set Up a Kill Switch (Manual Configuration) The built‑in kill switch in the AmneziaVPN app does not work under Qubes OS, so configure it manually in sys‑vpn Open a terminal in sys‑vpn and start Thunar with root privileges: sudo thunar Edit the file /rw/config/qubes-firewall-user-script and append the following rules at the bottom: nft add rule ip qubes custom-forward tcp flags syn / syn,rst tcp option maxseg size set rt mtu # Prevent the qube from forwarding traffic outside of the VPN nft add rule qubes custom-forward oifname eth0 counter drop nft add rule ip6 qubes custom-forward oifname eth0 counter drop (The first command fixes slow connection issues on Linux by adjusting the MTU). Route Traffic Through the VPN Assign sys‑vpn as the Net qube for sys‑whonix and for any other AppVMs where you want to hide the IP address. Edit Global Update Settings Open Global Settings → Updates. Enable “Disable checking for updates for all existing qubes.” In the “Except for following qubes, for which checking for updates will be enabled” field, add sys‑vpn and sys‑whonix. Remember that new AppVMs will be added to the exceptions list for update checks. You’ll need to manually delete any unnecessary AppVMs (with the real IP). a a1572×1236 104 KB You can also use regular WireGuard config file and add it to the AmneziaVPN GUI application: Click + into AmneziaVPN app, Then select “File with connection settings”, Select wg.conf file and click “enable WireGuard obfuscation” option. awg awg1186×756 94.5 KB :white_check_mark: Done! Сelf‑host installation of AmneziaVPN. Buy a server (VPS), if you don’t have one Minimum system requirements for VPS: Operating System - Linux, suitable for Ubuntu 22.04 or Debian 11. Supported processor architecture - x86-64. Virtualization - KVM. IPv4 address support Random Access Memory (RAM) - recommended 2 GB, but not less than 1 GB. Pre-installed software and control panel are not required. To make it more convenient for you, we have selected several hosting providers. Choose a suitable server from the options provided below and follow the step-by-step instructions. You can also choose any other VPS provider at your discretion. All servers are provided as examples. :warning: Servers on Reg.ru and Yandex Cloud are not suitable for installing VPN with Amnezia :exclamation: These hosting providers are provided as an example, we do not take responsibility for the quality of services provided by these companies and for potential risks. The best VPS for Amnezia: https://amnezia.host/en You will receive an email from the hosting provider The email should include the IP address, User name or User ID, and Password. You will need these for setting up the VPN on the server in the next step. |100%xauto |100%xauto2000×738 38.6 KB :warning: Some hosting providers do not send server details via email. In such cases, you can usually find the server information in your hosting provider’s account dashboard. If you encounter difficulties, contact their support. Download the AmneziaVPN app and enter the server details After installation, launch the application. On the first screen, select “I have connection details”, then “Set up your own server”, and enter the data from the email sent by the hosting provider. Press “Continue” for automatic installation and wait a moment. You can also choose the manual installation method - it’s just as easy! Click “Manual” and continue. Select the protocol. The best options are AmneziaWG for very fast speeds or X‑Ray to counter aggressive internet blocking. Then specify the port - you can keep the default port 443. Finally, click Install and wait a few minutes. That’s all! If you want to add other protocols, click your server, then the gear :gear: icon, and select additional protocol. :white_check_mark: Done! Installation of AWG – WG fork with the best obfuscation. Install the kernel module in template GitHub - amnezia-vpn/amneziawg-linux-kernel-module: AmneziaWG Linux kernel module · GitHub Open terminal in template fedora-43-xfce or debian-13-xfce. Use this guide for Manual build https://github.com/amnezia-vpn/amneziawg-linux-kernel-module: http_proxy=http://127.0.0.1:8082 https_proxy=http://127.0.0.1:8082 git clone https://github.com/amnezia-vpn/amneziawg-linux-kernel-module.git cd amneziawg-linux-kernel-module/src Skip step 2 from github guide (you’re using the kernel from dom0). make sudo make install Activate the module and check it. sudo modprobe amneziawg lsmod | grep amneziawg Add module to autostart. echo "amneziawg" | sudo tee /etc/modules-load.d/amneziawg.conf Install amnezia-tools. GitHub - amnezia-vpn/amneziawg-tools: Tools for configuring Amnezia-WG · GitHub http_proxy=http://127.0.0.1:8082 https_proxy=http://127.0.0.1:8082 git clone https://github.com/amnezia-vpn/amneziawg-tools.git cd amneziawg-tools/src make sudo make install Now connect WireGuard configurations with awg and awg-quick tools for hide the VPN connection! Create a configuration file for your WG server or in your VPN app, or generate a awg-WARP config using generators: 1 Перенаправление на проект 2 https://warp-generator.vercel.app/ 3 WARP WireGuard Config Generator 4 https://warp-gen.vercel.app/ 5 GitHub - ImMALWARE/bash-warp-generator: Генератор конфига Cloudflare WARP для AmneziaVPN · GitHub Remane it to awg0.conf. Edit file if you use your wireguard (add awg options), The configuration for AWG looks like this: [Interface] PrivateKey = ... S1 = 0 S2 = 0 Jc = 4 Jmin = 40 Jmax = 70 H1 = 1 H2 = 2 H3 = 3 H4 = 4 MTU = 1280 Address = ... DNS = ... [Peer] PublicKey = ... AllowedIPs = ... Endpoint = ... Move awg0.conf into the template and paste it into /etc/amnezia/amneziawg/ and then shutdown the template. Create a sys-awg qube Create a new AppVM based on the template with AWG, name it something like sys‑awg and enable the “Provides network” option. Then, in sys‑awg terminal enter: sudo nano /rw/config/rc.local and paste those commands: sudo awg-quick up awg0 sudo systemctl enable --now [email protected] Then set sys‑awg as the NetVM for the required appVMs. If the traffic isn’t being routed to appVMs, you need to add your awg’s DNS entry to /rw/config/rc.local in appVMs, for example: sudo nano /rw/config/rc.local and paste those commands: echo 'nameserver 1.1.1.1' > /etc/resolv.conf echo 'nameserver 1.0.0.1' > /etc/resolv.conf Enter this command in sys-awg to test the connection to awg: sudo awg show all :white_check_mark: Done! npub1cv8cl9nfqtrkdn3k0nk05z8mjte6hxtdws6j4x65jxc06nk5tu2s4ulsnl linux_privacy DNScrypt-proxy qube. sys-dns with Encrypted DNS 1. Install dnscrypt-proxy in the fedora-xfce template: sudo dnf install -y dnscrypt-proxy sudo systemctl enable dnscrypt-proxy 2. Add dnscrypt-proxy configuration in fedora-xfce template: This setup enables DNSCrypt with anonymized DNS (via relays), enforces DNSSEC, no-logging, and no-filter policies, and disables DoH and Oblivious DoH: sudo tee /etc/dnscrypt-proxy/dnscrypt-proxy.toml << 'EOF' server_names = [] listen_addresses = ['127.0.0.1:53', '0.0.0.0:53', '[::1]:53'] max_clients = 250 ipv4_servers = true ipv6_servers = false dnscrypt_servers = true doh_servers = false odoh_servers = false require_dnssec = true require_nolog = true require_nofilter = true disabled_server_names = ['scaleway', 'scaleway-ams'] force_tcp = false timeout = 5000 keepalive = 30 cert_refresh_delay = 240 bootstrap_resolvers = ['9.9.9.11:53', '8.8.8.8:53'] ignore_system_dns = true log_files_max_size = 10 log_files_max_age = 7 log_files_max_backups = 1 block_ipv6 = false block_unqualified = true block_undelegated = true reject_ttl = 10 cache = true cache_size = 4096 cache_min_ttl = 2400 cache_max_ttl = 86400 cache_neg_min_ttl = 60 cache_neg_max_ttl = 600 lb_strategy = 'wp2' lb_estimator = true [sources.public-resolvers] urls = [ 'https://raw.githubusercontent.com/DNSCrypt/dnscrypt-resolvers/master/v3/public-resolvers.md', 'https://download.dnscrypt.info/resolvers-list/v3/public-resolvers.md', 'https://cdn.jsdelivr.net/gh/DNSCrypt/dnscrypt-resolvers@master/v3/public-resolvers.md' ] cache_file = 'public-resolvers.md' minisign_key = 'RWQf6LRCGA9i53mlYecO4IzT51TGPpvWucNSCh1CBM0QTaLn73Y7GFO3' refresh_delay = 73 prefix = '' [sources.relays] urls = [ 'https://raw.githubusercontent.com/DNSCrypt/dnscrypt-resolvers/master/v3/relays.md', 'https://download.dnscrypt.info/resolvers-list/v3/relays.md', 'https://cdn.jsdelivr.net/gh/DNSCrypt/dnscrypt-resolvers@master/v3/relays.md' ] cache_file = 'relays.md' minisign_key = 'RWQf6LRCGA9i53mlYecO4IzT51TGPpvWucNSCh1CBM0QTaLn73Y7GFO3' refresh_delay = 73 prefix = '' [anonymized_dns] routes = [ { server_name='*', via=['anon-scaleway', 'anon-scaleway-ams', 'anon-kama', 'anon-tiarap'] } ] skip_incompatible = true EOF Note: The 10.139.1.1 and 10.139.1.2 addresses are the standard Qubes OS DNS IPs assigned to downstream VMs. Binding to 0.0.0.0:53 ensures dnscrypt-proxy can accept queries on these interfaces. 3. Create and configure sys-dns qube: Create fedora-based AppVM sys-dns and add option Provides network in Qube Manager (advansed settings) The next commands are executed in the sys-dns terminal. 4. Disable systemd-resolved in sys-dns: sudo tee /rw/config/rc.local << 'EOF' #!/bin/bash # Disable systemd-resolved to free up port 53 systemctl stop systemd-resolved 2>/dev/null systemctl disable systemd-resolved 2>/dev/null systemctl mask systemd-resolved 2>/dev/null systemctl stop systemd-resolved-varlink.socket 2>/dev/null systemctl disable systemd-resolved-varlink.socket 2>/dev/null systemctl mask systemd-resolved-varlink.socket 2>/dev/null systemctl stop systemd-resolved-monitor.socket 2>/dev/null systemctl disable systemd-resolved-monitor.socket 2>/dev/null systemctl mask systemd-resolved-monitor.socket 2>/dev/null # Assign Qubes standard DNS IPs to eth0 for downstream VMs # These IPs are dynamically provided to downstream qubes via Qubes mechanisms ip addr add 10.139.1.1/32 dev eth0 2>/dev/null ip addr add 10.139.1.2/32 dev eth0 2>/dev/null # start dnscrypt-proxy (let's add it just in case) systemctl start dnscrypt-proxy EOF 5. Configure nftables (Accept Rules for DNS) in sys-dns: sudo tee /rw/config/qubes-firewall-user-script << 'EOF' #!/bin/bash # Allow DNS queries from downstream qubes to 10.139.1.1/2 # vif* are interfaces for downstream qubes, group 2 nft add rule ip qubes custom-input iifgroup 2 ip daddr { 10.139.1.1, 10.139.1.2 } udp dport 53 accept 2>/dev/null nft add rule ip qubes custom-input iifgroup 2 ip daddr { 10.139.1.1, 10.139.1.2 } tcp dport 53 accept 2>/dev/null EOF Restart sys-dns to apply all changes. npub1cv8cl9nfqtrkdn3k0nk05z8mjte6hxtdws6j4x65jxc06nk5tu2s4ulsnl linux_privacy Qubes OS live mode. dom0 in RAM. Non-persistent Boot. RAM-Wipe. Protection against forensics. Tails mode. Hardening dom0. Root read‑only. Paranoid Security. Ephemeral Encryption #!/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:]]*#/!{/\<swap\>/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 sleep 10 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 /mnt umount /sysroot mount -o ro $Qubes_Root /mnt modprobe zram echo $DOM0_MAX_GBG > /sys/block/zram0/disksize /mnt/usr/sbin/mkfs.ext2 /dev/zram0 mount -o nodev,nosuid,noatime,nodiratime /dev/zram0 /sysroot cp -a /mnt/* /sysroot 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 <[email protected] > ## 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 <[email protected] > ## 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 <[email protected] > ## 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 <[email protected] > ## 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 usbcore.authorized_default=0 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 usbcore.authorized_default=0 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 echo echo "Done!" npub1cv8cl9nfqtrkdn3k0nk05z8mjte6hxtdws6j4x65jxc06nk5tu2s4ulsnl linux_privacy Топовые гайды по Qubes OS в комментах (шпаргалка, чтоб не потерять) npub1cv8cl9nfqtrkdn3k0nk05z8mjte6hxtdws6j4x65jxc06nk5tu2s4ulsnl linux_privacy Более крутой подход к режиму несохранения - эфемерно зашифрованный dm-container вместо tmpfs. Это вообще не тратит ценную память телефона. Заранее установите sudo apt install e2fsprogs -y #!/bin/bash # заранее установите sudo apt install e2fsprogs -y # === Конфигурация === UPPER_BASE="/mnt/overlay-persistent" DMCRYPT_FILE="/var/lib/overlay-crypt.img" DMCRYPT_SIZE_MB=10240 DMCRYPT_NAME="overlaycrypt" STATE_FILE="/run/overlay-active" TARGET_DIRS="/etc /var /home /root /usr /opt /userdata" # === Проверка зависимостей === for cmd in cryptsetup losetup mkfs.ext4 mount umount; do if ! command -v "$cmd" >/dev/null 2>&1; then echo "ERROR: Missing $cmd" exit 1 fi done # === Проверка, не активен ли уже === if [ -f "$STATE_FILE" ]; then echo "Already active" exit 0 fi # === Создание sparse-файла === if [ ! -f "$DMCRYPT_FILE" ]; then mkdir -p "$(dirname "$DMCRYPT_FILE")" dd if=/dev/zero of="$DMCRYPT_FILE" bs=1M count=0 seek="$DMCRYPT_SIZE_MB" status=progress fi # === Loop-устройство === LOOP_DEV=$(losetup -f --show "$DMCRYPT_FILE") echo "Loop device: $LOOP_DEV" # === dm-crypt с случайным ключом === cryptsetup open --type plain --key-file /dev/urandom \ --cipher aes-xts-plain64 --key-size 512 \ --sector-size 4096 "$LOOP_DEV" "$DMCRYPT_NAME" # === Форматирование ext4 === if ! blkid "/dev/mapper/$DMCRYPT_NAME" >/dev/null 2>&1; then mkfs.ext4 -F -L "overlaycrypt" "/dev/mapper/$DMCRYPT_NAME" fi # === Монтирование контейнера === mkdir -p "$UPPER_BASE" mount "/dev/mapper/$DMCRYPT_NAME" "$UPPER_BASE" # === Overlay на каждую директорию === for dir in $TARGET_DIRS; do name=$(echo "$dir" | tr '/' '-') upper="$UPPER_BASE/$name-upper" work="$UPPER_BASE/$name-work" lower="$UPPER_BASE/$name-lower" mkdir -p "$upper" "$work" "$lower" mount --bind "$dir" "$lower" mount --make-private "$lower" mount -t overlay -o "lowerdir=$lower,upperdir=$upper,workdir=$work" overlay "$dir" echo "Overlay active: $dir" done touch "$STATE_FILE" echo "Overlay mode ON (dm-crypt ephemeral)" npub1cv8cl9nfqtrkdn3k0nk05z8mjte6hxtdws6j4x65jxc06nk5tu2s4ulsnl linux_privacy Установка pam-duress на Droidian, чтобы создавать ложные пинкоды / пароли, которые уничтожат все опасные файлы и приложения при разблокировке телефона sudo apt-get update sudo apt-get install -y build-essential libpam0g-dev libssl-dev git clone https://github.com/nuvious/pam-duress.git cd pam-duress make sudo make install make clean mkdir -p ~/.duress sudo mkdir -p /etc/duress.d # сюда пишем аварийные команды для удаления / изменения cat > ~/.duress/remove_test.sh << 'EOF' #!/bin/sh rm -rf /home/droidian/test EOF chmod 500 ~/.duress/remove_test.sh duress_sign ~/.duress/remove_test.sh chmod 400 ~/.duress/remove_test.sh.sha256 # проверка ls -la ~/.duress/ # меняем полностью /etc/pam.d/common-auth sudo nano /etc/pam.d/common-auth # here are the per-package modules (the "Primary" block) auth [success=2 default=ignore] pam_unix.so nullok auth [success=1 default=ignore] pam_duress.so # here's the fallback if no module succeeds auth requisite pam_deny.so # prime the stack with a positive return value if there isn't one already; # this avoids us returning an error just because nothing sets a success code # since the modules above will each just jump around auth required pam_permit.so # and here are more per-package modules (the "Additional" block) auth optional pam_cap.so # end of pam-auth-update config # проверка mkdir -p /home/droidian/test touch /home/user/test/test_file.txt npub1cv8cl9nfqtrkdn3k0nk05z8mjte6hxtdws6j4x65jxc06nk5tu2s4ulsnl linux_privacy допольнительные тулзы для помощи RAM от перегрева. # OOM-менеджер: earlyoom sudo apt update sudo apt install earlyoom sudo systemctl enable --now earlyoom Настройка в /etc/default/earlyoom: EARLYOOM_ARGS="-m 8,8 -s 15,15 --prefer '(Web Content|Isolated Web|brave-browser|brave|firefox|firefox-esr|chromium|chromium-browser|waydroid|waydroid-full-ui)' --avoid '(phosh|phoc|systemd|ofono|NetworkManager|pulseaudio|lxc@android|gnome-session|dbus)' -r 3600" # Скрипт периодического сброса кэша под давлением: sudo nano /usr/local/bin/droidian-memory-guard.sh #!/bin/bash THRESHOLD=85 COOLDOWN=600 while true; do MEM_USED=$(free | awk '/Mem:/ {printf "%.0f", ($2-$7)/$2*100}') if [ "$MEM_USED" -gt "$THRESHOLD" ]; then NOW=$(date +%s) LAST=$(cat /run/droidian-memguard.last 2>/dev/null || echo 0) if [ $((NOW - LAST)) -gt "$COOLDOWN" ]; then sync echo 3 > /proc/sys/vm/drop_caches echo "$NOW" > /run/droidian-memguard.last logger -t droidian-memguard "Caches dropped at ${MEM_USED}%" fi fi sleep 60 done sudo chmod +x /usr/local/bin/droidian-memory-guard.sh sudo nano /etc/systemd/system/droidian-memguard.service [Unit] Description=Droidian Memory Guard After=network.target [Service] Type=simple ExecStart=/usr/local/bin/droidian-memory-guard.sh Restart=always [Install] WantedBy=multi-user.target sudo systemctl enable --now droidian-memguard # Вырубим анимацию в Phosh gsettings set org.gnome.desktop.interface enable-animations false # Еще можно установить предел памяти для waydroid: sudo nano /var/lib/waydroid/lxc/waydroid/config lxc.cgroup.memory.limit_in_bytes = 900M # Eсли хотите увеличить размер zram0, то: sudo su swapoff /dev/zram0 echo 1 > /sys/block/zram0/reset echo 2500M > /sys/block/zram0/disksize mkswap /dev/zram0 swapon /dev/zram0 -p 50 # или ставьте 100, если надо, чтобы zram0 работал на пределе без помощи swapfile npub1cv8cl9nfqtrkdn3k0nk05z8mjte6hxtdws6j4x65jxc06nk5tu2s4ulsnl linux_privacy Режим работы в памяти уже отличается сильно от мобиан - не получится просто так засунуть скрипт в initramfs, чтобы система, как в tails, запускалась в памяти при запуске. Поэтому будем пользоваться этим режимом в уже запущенной системе, создав переключатель, в котором надо будет просто ввести команду sudo overlay on. в скрипте в TARGET_DIRS можно указать либо весь корень, либо отдельные папки. Корень лучше не указывать, иначе swapfile не будет работать и напряг памяти увеличится. создайте скрипт в /usr/local/bin/overlay: #!/bin/bash # Toggle overlay mode for /etc and /var UPPER_BASE="/mnt/overlay-persistent" # Change to tmpfs path for ephemeral STATE_FILE="/run/overlay-active" TARGET_DIRS="/etc /var /home /root /usr /opt" toggle_on() { [ -f "$STATE_FILE" ] && { echo "Already active"; exit 1; } mkdir -p "$UPPER_BASE" mount -t tmpfs -o size=50% tmpfs "$UPPER_BASE" for dir in $TARGET_DIRS; do local name=$(echo "$dir" | tr '/' '-') local upper="$UPPER_BASE/$name-upper" local work="$UPPER_BASE/$name-work" local lower="$UPPER_BASE/$name-lower" mkdir -p "$upper" "$work" "$lower" # Bind-mount original to preserve access mount --bind "$dir" "$lower" mount --make-private "$lower" # Mount overlay mount -t overlay -o "lowerdir=$lower,upperdir=$upper,workdir=$work" overlay "$dir" echo "Overlay active: $dir" done touch "$STATE_FILE" echo "Overlay mode ON" } toggle_off() { [ ! -f "$STATE_FILE" ] && { echo "Not active"; exit 1; } for dir in $TARGET_DIRS; do umount "$dir" 2>/dev/null || true done # Clean up bind mounts for mnt in $(mount | grep "$UPPER_BASE" | awk '{print $3}' | tac); do umount "$mnt" 2>/dev/null || true done rm -f "$STATE_FILE" echo "Overlay mode OFF" sync reboot } show_status() { if [ -f "$STATE_FILE" ]; then echo "Overlay: ACTIVE" echo "Upperdir location: $UPPER_BASE" echo "Changes are stored on: $(df -T "$UPPER_BASE" | tail -1 | awk '{print $2}')" else echo "Overlay: INACTIVE" fi } case "$1" in on) toggle_on ;; off) toggle_off ;; status) show_status ;; *) echo "Usage: $0 {on|off|status}" ;; esac npub1cv8cl9nfqtrkdn3k0nk05z8mjte6hxtdws6j4x65jxc06nk5tu2s4ulsnl linux_privacy Дополнительный зашифрованный swap на 4 гига - для старых телефонов свопов много не бывает. Работать будет вместо с zram0: #!/bin/bash set -e SWAP_SIZE="4G" # Reduced for eMMC longevity echo "[*] Creating ${SWAP_SIZE} swap file..." # Use dd instead of fallocate for better compatibility with encrypted filesystems # fallocate may create holes that don't work well with encryption dd if=/dev/zero of=/swapfile bs=1M count=4096 status=progress chmod 600 /swapfile echo "[*] Creating encrypted swap script..." cat > /usr/local/bin/encrypted-swap.sh <<'SCRIPT' #!/bin/bash set -e # Ждём, пока zram будет создан for i in {1..30}; do if [ -b /dev/zram0 ]; then break fi sleep 1 done # Отключаем существующие swap swapoff /dev/zram0 2>/dev/null || true swapoff /dev/mapper/swapfile 2>/dev/null || true cryptsetup close swapfile 2>/dev/null || true losetup -D 2>/dev/null || true rm -f /run/encrypted-swap.loop # Настраиваем loop для swapfile LOOPDEV=$(losetup -f --show /swapfile) echo "$LOOPDEV" > /run/encrypted-swap.loop # Открываем с шифрованием cryptsetup open --type plain \ --key-file /dev/urandom \ --cipher aes-xts-plain64 \ --key-size 256 \ "$LOOPDEV" swapfile mkswap -f /dev/mapper/swapfile # Активируем ОБА swap с ОДИНАКОВЫМ приоритетом swapon -p 50 /dev/zram0 swapon -p 50 /dev/mapper/swapfile SCRIPT chmod +x /usr/local/bin/encrypted-swap.sh echo "[*] Creating systemd service..." cat > /etc/systemd/system/encrypted-swap.service <<'SERVICE' [Unit] Description=Encrypted Swap After=local-fs.target # Start after zram so we can set lower priority After=zramswap.service Wants=zramswap.service [Service] Type=oneshot RemainAfterExit=yes ExecStart=/usr/local/bin/encrypted-swap.sh ExecStop=/bin/bash -c 'swapoff /dev/mapper/swapfile 2>/dev/null; cryptsetup close swapfile 2>/dev/null; LOOPDEV=$(cat /run/encrypted-swap.loop 2>/dev/null) && losetup -d $LOOPDEV 2>/dev/null || true; rm -f /run/encrypted-swap.loop' [Install] WantedBy=multi-user.target SERVICE echo "[*] Cleaning up crypttab and fstab..." sed -i '/swap/d' /etc/crypttab 2>/dev/null || true sed -i '/swap/d' /etc/fstab 2>/dev/null || true echo "[*] Configuring swap preferences..." # Moderate swappiness - let kernel decide, but prefer zram cat > /etc/sysctl.d/99-swap.conf <<'EOF' # Prefer zram over disk swap vm.swappiness=100 vm.vfs_cache_pressure=200 EOF echo "[*] Enabling encrypted swap service..." systemctl daemon-reload systemctl enable encrypted-swap.service echo "[*] Activating encrypted swap..." /usr/local/bin/encrypted-swap.sh echo "" echo "[*] Verification:" swapon --show cryptsetup status swapfile 2>/dev/null || echo "cryptsetup status failed, checking lsblk:" lsblk | grep -i swap || true free -h | grep -i swap echo "" echo "[*] Encrypted swap setup complete." echo "[*] New random key generated at each boot." echo "[*] Swap data is unrecoverable after shutdown." echo "[*] WARNING: Disk swap on eMMC wears storage. Consider larger zram instead." npub1cv8cl9nfqtrkdn3k0nk05z8mjte6hxtdws6j4x65jxc06nk5tu2s4ulsnl linux_privacy Droidian сидит на андроид-ядре, поэтому там, в отличие от мобиан, iptables. значит запуск всего трафика через тор будет по этому скрипту (у waydroid будет свой трафик и туда можно амнезию поставить): #!/bin/bash # Flush ALL iptables rules completely (be careful if remote!) sudo iptables -F sudo iptables -X sudo iptables -t nat -F sudo iptables -t nat -X sudo iptables -t mangle -F sudo iptables -t mangle -X sudo iptables -P INPUT ACCEPT sudo iptables -P FORWARD ACCEPT sudo iptables -P OUTPUT ACCEPT # Verify clean state sudo iptables -L -v echo "[*] Installing Tor..." sudo apt update sudo apt install -y tor echo "[*] Configuring Tor..." # Backup original torrc sudo cp /etc/tor/torrc /etc/tor/torrc.backup # Configure Tor for transparent proxy sudo tee /etc/tor/torrc > /dev/null << 'EOF' SocksPort 127.0.0.1:9050 TransPort 127.0.0.1:9040 DNSPort 127.0.0.1:9053 VirtualAddrNetworkIPv4 10.192.0.0/10 AutomapHostsOnResolve 1 RunAsDaemon 1 EOF echo "[*] Restarting Tor..." sudo systemctl restart tor sleep 2 echo "[*] Checking Tor ports..." sudo ss -tlnp | grep -E ':(9050|9040|9053)' echo "[*] Setting up iptables rules..." # IMPORTANT: First check if rules already exist to avoid duplicates # Flush existing rules in our custom chains (or create them) sudo iptables -t nat -F 2>/dev/null || true # Create new chain for Tor (or flush if exists) sudo iptables -t nat -N TOR_REDIRECT 2>/dev/null || sudo iptables -t nat -F TOR_REDIRECT # Redirect DNS to Tor DNSPort sudo iptables -t nat -A OUTPUT -p udp --dport 53 -j REDIRECT --to-ports 9053 # Redirect TCP traffic to Tor TransPort (except Tor itself) sudo iptables -t nat -A OUTPUT -p tcp --syn -m owner ! --uid-owner debian-tor -j REDIRECT --to-ports 9040 # Allow loopback sudo iptables -t nat -A OUTPUT -o lo -j RETURN # Don't redirect local/private networks (optional - adjust as needed) sudo iptables -t nat -A OUTPUT -d 127.0.0.0/8 -j RETURN sudo iptables -t nat -A OUTPUT -d 192.168.0.0/16 -j RETURN sudo iptables -t nat -A OUTPUT -d 10.0.0.0/8 -j RETURN echo "[*] Current iptables nat rules:" sudo iptables -t nat -L -v echo "[*] Tor transparent proxy is now active" echo "[*] Test with: curl --socks5 127.0.0.1:9050 https://check.torproject.org" echo "[*] Saving iptables rules..." sudo mkdir -p /etc/iptables sudo iptables-save > /etc/iptables/rules.v4 echo "[*] Creating systemd service for autostart..." sudo tee /etc/systemd/system/tor-firewall.service > /dev/null << 'EOF' [Unit] Description=Tor Transparent Proxy Firewall After=network.target tor.service Wants=tor.service [Service] Type=oneshot RemainAfterExit=yes ExecStart=/sbin/iptables-restore /etc/iptables/rules.v4 ExecStop=/sbin/iptables -F; /sbin/iptables -t nat -F; /sbin/iptables -t mangle -F [Install] WantedBy=multi-user.target EOF sudo systemctl daemon-reload sudo systemctl enable tor-firewall.service echo "[*] Done. Rules will persist after reboot." npub1cv8cl9nfqtrkdn3k0nk05z8mjte6hxtdws6j4x65jxc06nk5tu2s4ulsnl linux_privacy Droidian - более мобильный аналог Mobian. https://droidian.org/ Если Mobian - это чистый дебиан на телефоне, то droidian - это дебиан с ограничениями, но зато с хорошо работающими функциями телефона: камера, звонки, уведомления работают отлично. Установка очень простая - через UBports просто нажав пару кнопок, но важно, чтобы телефон был сброшен до определенной версии андроида. На телефоне пиксель 3а - это андроид 9. Устанавливаем пакеты: sudo apt install adb fastboot Заходим на сайт flash.android.com с гугл хрома (да, это бесит, но не страшно - можно засунуть хром в контейнер и трафик с тора пустить), подключаем телефон к юсб в режиме fastboot, даем разрешения сайту на отладку и выбираем андроид 9, затем нажимаем на карандаш и убираем галочку с "заблокировать загрузчик". минут 5 ждем и готово. Затем скачиваем UBports с сайта убунты тач https://devices.ubuntu-touch.io/installer/appimage (кстати, убунту тач - фигня полная), и запускаем там установку в простом графическом интерфейсе. ждем минут 5 и телефон перезагрузится в Droidian. Пинкод будет 1234, как в мобина. По мне так это самый удобный мобильный линукс в плане работы конкретно функций телефона. После включения идите в настройки и запускайте шифрование диска - система перенесется в новый зашифрованный раздел. https://blossom.primal.net/4fb6eda527c7329a5eb5720873aa2bf32fc83ab73339c15caf9bc0aae0886776.jpg npub1cv8cl9nfqtrkdn3k0nk05z8mjte6hxtdws6j4x65jxc06nk5tu2s4ulsnl linux_privacy если что-то пойдет не так, то на телефонах пиксель проще всего вернуть обычный андроид и попробовать снова. просто заходишь на сайт android flash tools и подключаешь телефон к usb. главное - перед восстановлением нажать на редактирование и убрать галочку с "заблокировать загрузчик". но там вряд ли что-то не так пойдет при установке - она слишком простая. npub1cv8cl9nfqtrkdn3k0nk05z8mjte6hxtdws6j4x65jxc06nk5tu2s4ulsnl linux_privacy создаем команды для запуска и деактивации overlayroot для выхода из overlayroot создайте в /usr/local/bin/stop-overlay: #!/bin/sh sudo mount -o remount,rw /live/image sudo touch /live/image/ sudo mount -o remount,ro /live/image sudo reboot и затем в терминале sudo stop-overlay после перезагрузки всё пишется снова на диск. для запуска overlayroot создайте /usr/local/bin/start-overlay: rm /.overlayroot-disable sudo reboot и потом в терминале введи sudo start-overlay p.s. естеснна, все файлы в /usr/local/bin надо делать исполняемыми sudo chmod +x npub1cv8cl9nfqtrkdn3k0nk05z8mjte6hxtdws6j4x65jxc06nk5tu2s4ulsnl linux_privacy затем устанавливаем свой вариант overlayroot в initramfs для работы в RAM и потере данных при перезагрузке из режима overlayroot: sudo tee /etc/initramfs-tools/scripts/init-bottom/overlayroot >/dev/null <<EOF #!/bin/sh # Overlayroot initramfs hook для Debian/Mobian case "$1" in prereqs) echo "" exit 0 ;; esac . /scripts/functions PATH=/usr/sbin:/usr/bin:/sbin:/bin MYTAG="overlayroot" # === НАСТРОЙКИ === root_rw=/media/root-rw root_ro=/media/root-ro NEWROOT="${rootmnt:-/root}" OVERLAYROOT_DEBUG=1 # === ЛОГИРОВАНИЕ === log() { "log_${1}_msg" "$MYTAG: $2" _debug "[$1]:" "$2" } log_fail() { log failure "$*"; } log_success() { log success "$*"; } log_warn() { log warning "$*"; } fail() { [ $# -eq 0 ] || log_fail "$@" exit 0 } _debug() { if [ "${DEBUG_BUSTED:-0}" = "0" ]; then { echo "$@" >> "/dev/.initramfs/${MYTAG}.log"; } 2>/dev/null || { DEBUG_BUSTED=1; logwarn "debug is busted"; } fi } debug() { _debug "$@" [ "${OVERLAYROOT_DEBUG:-0}" = "0" ] && return echo "$MYTAG:" "$@" } # === ПАРСИНГ КОМАНДНОЙ СТРОКИ ЯДРА === # Читаем /proc/cmdline и ищем параметры overlayroot* parse_cmdline() { local cmdline="" read cmdline < /proc/cmdline || return 1 OVERLAY_MODE="" OVERLAY_CHROOT=0 for tok in $cmdline; do case "$tok" in overlayroot=off) OVERLAY_MODE="disabled" ;; overlayroot=chroot) OVERLAY_CHROOT=1 ;; overlayroot=tmpfs) OVERLAY_MODE="tmpfs" ;; overlayroot=*) OVERLAY_MODE="${tok#overlayroot=}" ;; esac done } # === ПРОВЕРКА ФЛАГОВ В ОРИГИНАЛЬНОМ КОРНЕ === check_disable_flags() { # Проверяем флаг-файл в оригинальном корне (до overlay) if [ -f "$NEWROOT/.overlayroot-disable" ]; then log_success "found .overlayroot-disable flag, disabling overlay" rm -f "$NEWROOT/.overlayroot-disable" return 0 fi # Проверяем флаг-файл для одноразового chroot if [ -f "$NEWROOT/.overlayroot-chroot" ]; then log_success "found .overlayroot-chroot flag, enabling chroot mode" OVERLAY_CHROOT=1 rm -f "$NEWROOT/.overlayroot-chroot" return 1 fi return 1 } # === МОНТИРОВАНИЕ TMPFS ДЛЯ OVERLAY === mount_overlay_tmpfs() { local size="${1:-50%}" mkdir -p "$root_rw" || fail "failed to create $root_rw" mount -t tmpfs -o mode=0755,size=80% tmpfs-root "$root_rw" || fail "failed to mount tmpfs on $root_rw" mkdir -p "$root_rw/overlay" "$root_rw/work" || fail "failed to create overlay dirs on tmpfs" } # === МОНТИРОВАНИЕ ОРИГИНАЛЬНОГО КОРНЯ === mount_original_root() { mkdir -p "$root_ro" || fail "failed to create $root_ro" # Если корень уже примонтирован в NEWROOT — биндим его if [ -d "$NEWROOT/bin" ]; then mount --bind "$NEWROOT" "$root_ro" || fail "failed to bind $NEWROOT to $root_ro" else fail "$NEWROOT is not mounted" fi } # === МОНТИРОВАНИЕ OVERLAY === mount_overlay() { local lowerdir="$1" local upperdir="$2" local workdir="$3" local target="$4" mkdir -p "$target" || fail "failed to create $target" # Проверяем версию ядра для workdir local mount_opts="lowerdir=$lowerdir,upperdir=$upperdir" case "$(uname -r)" in 2*|3.1[01234567]*|3.[0-9].*) # Старые ядра не требуют workdir ;; *) mount_opts="$mount_opts,workdir=$workdir" ;; esac mount -t overlay -o "$mount_opts" overlayroot "$target" || fail "failed to mount overlay on $target" } # === ПЕРЕМЕЩЕНИЕ MOUNTPOINT'ОВ === move_mounts() { local source="$1" local dest="$2" # Переносим все дочерние mountpoint'ы (кроме самого source) local mounts="" mounts=$(awk -v mnt="$source" '$2 ~ "^"mnt && $2 != mnt {print $2}' /proc/mounts | sort) for mp in $mounts; do local rel="${mp#$source}" [ -z "$rel" ] && continue mkdir -p "$dest$rel" || log_warn "failed to create $dest$rel" mount --move "$mp" "$dest$rel" || log_warn "failed to move $mp to $dest$rel" done } # === ОБНОВЛЕНИЕ FSTAB === update_fstab() { local root_ro="$1" local root_rw="$2" [ -f "${NEWROOT}/etc/fstab" ] || return 0 # Сохраняем оригинальный fstab cp "${NEWROOT}/etc/fstab" "${NEWROOT}/etc/fstab.orig" 2>/dev/null || true cat <<EOF >"${NEWROOT}/etc/fstab" # This fstab is in an overlayfs. # The real one can be found at ${root_ro}/etc/fstab # To permanently modify files, chroot into writable view: # sudo mount -o remount,rw ${root_ro} # sudo chroot ${root_ro} # EOF # Копируем оригинальные записи с изменёнными путями while read spec file vfstype opts pass freq; do # Пропускаем комментарии и пустые строки case "$spec" in \#*|""|swap) continue ;; esac # Корневую ФС заменяем на overlay if [ "$file" = "/" ]; then echo "overlayroot / overlay defaults 0 0" echo "${root_ro} ${root_ro} none bind,ro 0 0" echo "${root_rw} ${root_rw} none bind,rw 0 0" continue fi # Виртуальные ФС пропускаем case "$vfstype" in proc|sys|tmpfs|devtmpfs|devpts|overlay) continue ;; esac # Остальные монтируем в ro-слой echo "$spec ${root_ro}${file} $vfstype ro,$opts $pass $freq" done < "${NEWROOT}/etc/fstab.orig" >> "${NEWROOT}/etc/fstab" } # === СОЗДАНИЕ ТОЧЕК ДОСТУПА /live/* === create_live_access() { local root_ro="$1" local root_rw="$2" # Создаём /live/image (доступ к оригинальному ro корню) mkdir -p "${NEWROOT}/live/image" || log_warn "failed to create /live/image" mount --bind "$root_ro" "${NEWROOT}/live/image" || log_warn "failed to bind $root_ro to /live/image" # Создаём /live/cow (доступ к rw слою) mkdir -p "${NEWROOT}/live/cow" || log_warn "failed to create /live/cow" mount --bind "$root_rw" "${NEWROOT}/live/cow" || log_warn "failed to bind $root_rw to /live/cow" } # === ОСНОВНАЯ ЛОГИКА === # 1. Парсим командную строку ядра parse_cmdline # 2. Проверяем флаги отключения check_disable_flags if [ $? -eq 0 ]; then OVERLAY_MODE="disabled" fi # 3. Если overlay отключён — выходим case "${OVERLAY_MODE:-enabled}" in disabled|off|no) log_success "overlayroot disabled, booting normal root" exit 0 ;; esac # 4. Проверяем что корень смонтирован if [ ! -d "$NEWROOT/bin" ]; then log_fail "$NEWROOT is not mounted" exit 0 fi # 5. Создаём временные директории в RAM mkdir -p /run/overlay mount -t tmpfs -o mode=0755,size=100% tmpfs /run/overlay || fail "failed to mount tmpfs on /run/overlay" # 6. Монтируем оригинальный корень в ro-слой mount_original_root # 7. Монтируем tmpfs для rw-слоя mount_overlay_tmpfs # 8. Если запрошен chroot-режим — монтируем overlay отдельно if [ "$OVERLAY_CHROOT" = "1" ]; then log_warn "chroot mode requested, overlay will be available at /overlay" # Монтируем overlay в /overlay внутри нового корня mkdir -p "${NEWROOT}/overlay" mount_overlay "$root_ro" "$root_rw/overlay" "$root_rw/work" "${NEWROOT}/overlay" # Создаём точки доступа create_live_access "$root_ro" "$root_rw" # Переносим дочерние mountpoint'ы move_mounts "$NEWROOT" "${NEWROOT}/overlay" # Обновляем fstab update_fstab "$root_ro" "$root_rw" log_success "chroot overlay configured at /overlay" exit 0 fi # 9. Обычный режим: перемещаем корень в ro-слой, монтируем overlay на его место # Перемещаем оригинальный корень mount --move "$NEWROOT" "$root_ro" || fail "failed to move $NEWROOT to $root_ro" # Монтируем overlay на место оригинального корня mount_overlay "$root_ro" "$root_rw/overlay" "$root_rw/work" "$NEWROOT" # 10. Переносим mountpoint'ы внутрь overlay move_mounts "$root_ro" "$NEWROOT" # 11. Перемещаем ro и rw слои внутрь overlay (чтобы были доступны из системы) mkdir -p "${NEWROOT}${root_ro}" "${NEWROOT}${root_rw}" mount --move "$root_ro" "${NEWROOT}${root_ro}" || log_warn "failed to move $root_ro into overlay" mount --move "$root_rw" "${NEWROOT}${root_rw}" || log_warn "failed to move $root_rw into overlay" # 12. Создаём точки доступа /live/* create_live_access "${NEWROOT}${root_ro}" "${NEWROOT}${root_rw}" # 13. Обновляем fstab update_fstab "$root_ro" "$root_rw" # 14. Если ядро просило ro — перемонтируем read cmdline < /proc/cmdline case " $cmdline " in *\ ro\ *) mount -o remount,ro "$NEWROOT" || log_warn "failed to remount overlay read-only" ;; esac log_success "overlayroot configured with /live/image and /live/cow access" exit 0 EOF sudo chmod 755 /etc/initramfs-tools/scripts/init-bottom/overlayroot echo "overlay" | sudo tee -a /etc/initramfs-tools/modules sudo update-initramfs -u -k all sudo dpkg-reconfigure linux-image-$(uname -r) npub1cv8cl9nfqtrkdn3k0nk05z8mjte6hxtdws6j4x65jxc06nk5tu2s4ulsnl linux_privacy Если хотите иметь еще и VPN, например WG, то запустите его по классике и сделайте такой переключатель из тора в вг и обратно в /usr/local/bin (переключение будет через команды sudo toggle-net wg или sudo toggle-net tor): #!/bin/bash # Toggle between Tor transparent proxy and WireGuard VPN # Usage: sudo ./toggle-tor-wireguard.sh [tor|wg|status] set -e MODE="${1:-status}" NFT_CONF_TOR="/etc/nftables.conf" NFT_CONF_WG="/etc/nftables.wg.conf" WIREGUARD_CONF="/etc/wireguard/wg0.conf" show_status() { echo "=== Current Status ===" echo "" echo "--- nftables rules ---" sudo nft list ruleset 2>/dev/null || echo "No nftables rules loaded" echo "" echo "--- Tor service ---" systemctl is-active tor 2>/dev/null || echo "Tor: inactive" echo "" echo "--- WireGuard ---" wg show 2>/dev/null || echo "WireGuard: no interfaces" echo "" echo "--- Default route ---" ip route | grep default || true echo "" echo "--- Checking Tor connectivity ---" curl -s https://check.torproject.org | grep -i "congratulations\|sorry" } disable_tor() { echo "[*] Disabling Tor transparent proxy..." # Stop Tor service if systemctl is-active --quiet tor 2>/dev/null; then sudo systemctl stop tor echo " Tor service stopped" fi # Flush nftables (remove Tor rules) sudo nft flush ruleset 2>/dev/null || true echo " nftables flushed" # Restore basic nftables or leave empty for WireGuard sudo nft -f - <<'EOF' 2>/dev/null || true #!/usr/sbin/nft -f table ip filter { chain input { type filter hook input priority filter; policy accept; } chain forward { type filter hook forward priority filter; policy accept; } chain output { type filter hook output priority filter; policy accept; } } EOF echo "[*] Tor disabled. Basic firewall restored." } enable_wireguard() { echo "[*] Enabling WireGuard..." # Check if WireGuard config exists if [ ! -f "$WIREGUARD_CONF" ]; then echo "[!] WireGuard config not found: $WIREGUARD_CONF" echo " Please create your WireGuard configuration first." exit 1 fi # Make sure Tor is stopped disable_tor # Enable IP forwarding (required for WireGuard) sudo sysctl -w net.ipv4.ip_forward=1 >/dev/null # Start WireGuard sudo wg-quick up wg0 2>/dev/null || true # Ensure WireGuard starts on boot sudo systemctl enable wg-quick@wg0 2>/dev/null || true sudo systemctl start wg-quick@wg0 2>/dev/null || true echo "[*] WireGuard enabled on wg0" } enable_tor() { echo "[*] Enabling Tor transparent proxy..." # Stop WireGuard if active if wg show wg0 >/dev/null 2>&1; then echo " Stopping WireGuard..." sudo wg-quick down wg0 2>/dev/null || true sudo systemctl stop wg-quick@wg0 2>/dev/null || true sudo systemctl disable wg-quick@wg0 2>/dev/null || true fi # Restore Tor nftables rules if [ -f "$NFT_CONF_TOR" ]; then sudo nft -f "$NFT_CONF_TOR" echo " Tor nftables rules restored" else echo "[!] Tor nftables config not found: $NFT_CONF_TOR" exit 1 fi # Start Tor sudo systemctl start tor sudo systemctl enable tor echo "[*] Tor transparent proxy enabled" } case "$MODE" in tor|t) enable_tor ;; wg|wireguard|w) enable_wireguard ;; status|s|"") show_status ;; *) echo "Usage: $0 [tor|wg|status]" echo "" echo " tor - Enable Tor transparent proxy, disable WireGuard" echo " wg - Enable WireGuard VPN, disable Tor" echo " status - Show current network status (default)" echo "" exit 1 ;; esac echo "" show_status npub1cv8cl9nfqtrkdn3k0nk05z8mjte6hxtdws6j4x65jxc06nk5tu2s4ulsnl linux_privacy После запуска системы запускаем обновления. Затем отправляем весь трафик через Tor, запустив этот скрипт (оставляем только дырку для Waydroid, в который для торификации установите Invizible Pro и в настройках VPN включите блокировку трафика при отключении приложения): #!/bin/bash set -e TOR_UID=108 # Step 1: Install Tor echo "[*] Installing Tor..." sudo apt update sudo apt install -y tor # Step 2: Configure Tor echo "[*] Configuring Tor..." sudo tee /etc/tor/torrc >/dev/null <<'EOF' TransPort 127.0.0.1:9040 DNSPort 127.0.0.1:9053 AutomapHostsOnResolve 1 VirtualAddrNetworkIPv4 10.192.0.0/10 EOF # Step 3: Restart and enable Tor echo "[*] Restarting and enabling Tor service..." sudo systemctl restart tor sudo systemctl enable tor # Step 4: Verify Tor is listening echo "[*] Checking Tor listening ports..." sudo ss -tulpn | grep tor # Step 5: Configure nftables echo "[*] Writing nftables configuration..." sudo tee /etc/nftables.conf >/dev/null <<EOF #!/usr/sbin/nft -f flush ruleset define uid = ${TOR_UID} define waydroid_net = 192.168.240.0/24 define unrouteables = { 127.0.0.0/8, 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 0.0.0.0/8, 100.64.0.0/10, 169.254.0.0/16, 192.0.0.0/24, 192.0.2.0/24, 192.88.99.0/24, 198.18.0.0/15, 198.51.100.0/24, 203.0.113.0/24, 224.0.0.0/4, 240.0.0.0/4 } table ip nat { set unrouteables { type ipv4_addr flags interval elements = \$unrouteables } chain prerouting { type nat hook prerouting priority dstnat; policy accept; } chain postrouting { type nat hook postrouting priority srcnat; policy accept; oifname != "waydroid0" ip saddr \$waydroid_net counter masquerade } chain output { type nat hook output priority dstnat; policy accept; ct state established,related accept ip daddr \$waydroid_net return ip saddr \$waydroid_net return meta l4proto tcp ip daddr 10.192.0.0/10 redirect to :9040 ip daddr 127.0.0.1 udp dport 53 redirect to :9053 meta skuid \$uid return oifname "lo" return ip daddr @unrouteables return meta l4proto tcp redirect to :9040 } } table ip filter { set unrouteables { type ipv4_addr flags interval elements = \$unrouteables } chain input { type filter hook input priority filter; policy drop; ct state established,related accept iifname "lo" accept iifname "waydroid0" udp dport { 53, 67 } accept iifname "waydroid0" tcp dport { 53, 67 } accept counter log prefix "TOR-INPUT-DROP: " drop } chain forward { type filter hook forward priority filter; policy drop; ct state established,related accept iifname "waydroid0" oifname != "waydroid0" accept iifname != "waydroid0" oifname "waydroid0" ct state established,related accept } chain output { type filter hook output priority filter; policy drop; ct state established,related accept oifname "lo" accept ip daddr \$waydroid_net accept oifname "waydroid0" accept meta skuid \$uid accept ip daddr @unrouteables accept meta l4proto tcp accept udp dport 53 accept counter log prefix "TOR-KILLSWITCH: " drop } } EOF # Step 6: Validate and apply nftables echo "[*] Validating nftables configuration..." sudo nft -c -f /etc/nftables.conf echo "[*] Applying nftables rules..." sudo nft -f /etc/nftables.conf # Step 7: Enable and start nftables echo "[*] Enabling nftables service..." sudo systemctl enable nftables sudo systemctl start nftables # Step 8: Show active ruleset echo "[*] Current nftables ruleset:" sudo nft list ruleset # Step 9: Verify Tor connectivity echo "[*] Checking Tor connectivity..." curl -s https://check.torproject.org | grep -i "congratulations\|sorry" npub1cv8cl9nfqtrkdn3k0nk05z8mjte6hxtdws6j4x65jxc06nk5tu2s4ulsnl linux_privacy Tails на телефоне. Создаем зашифрованную мобильную ОС, которая будет терять все данные после перезагрузки. Сначала устанавливаем Mobian - дебиан для телефонов (проще всего на старый pixel 3a, который можно купить за 3 копейки). Для полнодискового шифрования нужно собрать ОС. устанавливаем зависимости и клонируем репозиторий sudo apt install debos bmap-tools xz-utils android-sdk-libsparse-utils yq mkbootimg cryptsetup git clone https://salsa.debian.org/Mobian-team/mobian-recipes.git -b mobian-trixie cd mobian-recipes и запускаем сборку указав свой пароль от диска (sdm670 - это для pixel) ./build.sh -t sdm670 -c -R <password> после сборки ставим образ в телефон sudo apt install adb fastboot google-android-platform-tools-installer включаем режим разработчика: Настройки → Система → Об устройстве → 7 раз нажми "Номер сборки" включаем OEM-разблокировку: Настройки → Система → Параметры разработчика → "Разблокировка OEM" перезагружаем в fastboot: power + volume down при выключенном устройстве. затем вводим эти команды fastboot flashing unlock fastboot flash boot mobian-*-phosh-*.boot-sargo.img #название созданного boot-sargo.img fastboot -S 100M flash userdata mobian-*-phosh-*.rootfs.img #название созданного rootfs.img fastboot erase dtbo fastboot oem uart enable fastboot reboot и вводим пароль шифрования при запуске системы. дефолтный пинкод будет 1234. система установлена, продолжение ниже. npub1cv8cl9nfqtrkdn3k0nk05z8mjte6hxtdws6j4x65jxc06nk5tu2s4ulsnl linux_privacy upd: this bug android 17, not graphene os. npub1cv8cl9nfqtrkdn3k0nk05z8mjte6hxtdws6j4x65jxc06nk5tu2s4ulsnl linux_privacy upd: connecting phone to pc is working. only sim card problem. npub1cv8cl9nfqtrkdn3k0nk05z8mjte6hxtdws6j4x65jxc06nk5tu2s4ulsnl linux_privacy @npub1hxx…g75y after update graphene os, physical sim card won't turn on - nothing happens after pressing "turn on sim". also phone is not visible on pc when connected via usb. problems only in android 17. check please. npub1cv8cl9nfqtrkdn3k0nk05z8mjte6hxtdws6j4x65jxc06nk5tu2s4ulsnl linux_privacy @npub142g…xrj0 @npub1gcx…nj5z @npub1utx…50e8 @npub1wsp…7rqf Please add a pincode in Amethyst and Dark Wisp to encrypt an account database npub1cv8cl9nfqtrkdn3k0nk05z8mjte6hxtdws6j4x65jxc06nk5tu2s4ulsnl linux_privacy chainflip.io добавил поддержку сети tron и теперь это точно лучший кроссчейн-обменник для децентрализованной покупки чистого нативного биткоина на стейблкоины из сетей ethereum, solana, tron без kyc. chainflip имеет встроенный aml и хакерские битки там не пройдут. chainflip прошел кучу аудитов и имеет лучшие оценки безопасности для кроссчейнов с нативным битком на hindenrank. и там нет mev-атак при свопах ethereum. npub1cv8cl9nfqtrkdn3k0nk05z8mjte6hxtdws6j4x65jxc06nk5tu2s4ulsnl linux_privacy RuStore это не магазин, а пегасус с арсеналом шпионажа за телефоном похлеще гуглов. https://habr.com/ru/articles/1046710/ А это РуСтор "здорового человека" (хотя, сейчас все рус приложения - пегасусы: от яндексов до авито и сбера). https://codeberg.org/mi6e4ka/openstore npub1cv8cl9nfqtrkdn3k0nk05z8mjte6hxtdws6j4x65jxc06nk5tu2s4ulsnl linux_privacy самый удобный открытокодный антидетект браузер из всех, что пока попадались. если говорить об удобстве и простоте. https://github.com/ProxyShard/ShardBrowser npub1cv8cl9nfqtrkdn3k0nk05z8mjte6hxtdws6j4x65jxc06nk5tu2s4ulsnl linux_privacy новый ресурс, претендующий на звание лучшего хаба вселенной #Nostr https://nostrhub.io/ npub1cv8cl9nfqtrkdn3k0nk05z8mjte6hxtdws6j4x65jxc06nk5tu2s4ulsnl linux_privacy about telegram "privacy" https://www.whonix.org/wiki/Telegram npub1cv8cl9nfqtrkdn3k0nk05z8mjte6hxtdws6j4x65jxc06nk5tu2s4ulsnl linux_privacy только в репозиториях самих клиентов если. нет такого сайта, где вот прям все все клиенты собраны, т.к. клиентов всё больше, постоянно новые появляются. звонки пока добавлены в amethyst, 0xchat, nospeak. в общем, в отличие от джабьера, тут всё сложнее, т.к. клиентов намного больше и это мы говорим только о соцсети - еще вспомогательных ностр-тулзов десятки. npub1cv8cl9nfqtrkdn3k0nk05z8mjte6hxtdws6j4x65jxc06nk5tu2s4ulsnl linux_privacy I can't see notes from private contacts, notes from relay-bridges (ditto.pub mostr.pub and nostr.data.haus). these problems didn't exist in the old versions 1.06 > App has improved a lot functionally, but now it is impossible to use it to read and search for notes - many notes have disappeared npub1cv8cl9nfqtrkdn3k0nk05z8mjte6hxtdws6j4x65jxc06nk5tu2s4ulsnl linux_privacy Zapstore - мой любимый магазин приложений. Заслужил грант. #note15a3…cjrl npub1cv8cl9nfqtrkdn3k0nk05z8mjte6hxtdws6j4x65jxc06nk5tu2s4ulsnl linux_privacy Скиньте дырову, когда он снова будет постить всякую хуйню про ультра-приватность в телеге. Аудит подъехал. Telegram's MTProto: Assessing Deanonymization Potential for a Network Attacker. Dr. Nadim Kobeissi Symbolic Software https://static.istories.media/uploaded/documents/d92f02fe94f64906887d290489e26d14.pdf npub1cv8cl9nfqtrkdn3k0nk05z8mjte6hxtdws6j4x65jxc06nk5tu2s4ulsnl linux_privacy @npub142g…xrj0 also I don't see notes from relay.data.haus (rss). it works well in 0xchat and worked in the last version of Amethyst npub1cv8cl9nfqtrkdn3k0nk05z8mjte6hxtdws6j4x65jxc06nk5tu2s4ulsnl linux_privacy @npub142g…xrj0 @npub1gcx…nj5z I don't see notes from my private contacts in my feed, don't see notes from mastodon accounts with ditto-mostr relay, don't see some of my notes and some of my subscribers after the latest updates. check it please. npub1cv8cl9nfqtrkdn3k0nk05z8mjte6hxtdws6j4x65jxc06nk5tu2s4ulsnl linux_privacy я думаю, сейчас постепенно все клиенты ностр будут переходить к золотому стандарту: поддержка звонков, мармот е2ее чатов с прямой секретностью, тор/прокси, приватных подписок, шифрования локальной базы данных + всякие штуки из телеги: анимированные стикеры и тд. этот год будет поворотным в крутых обновлениях, будут закрыты последние баги, и ностр станет фантастической мега удобной соцсетью с высоченным уровнем приватности, децентрализации и свободы. npub1cv8cl9nfqtrkdn3k0nk05z8mjte6hxtdws6j4x65jxc06nk5tu2s4ulsnl linux_privacy бомба! аметист становится действительно топовым клиентом nostr. приватность, анонимность и куча крутых функций. #note1kxu…8cy8 npub1cv8cl9nfqtrkdn3k0nk05z8mjte6hxtdws6j4x65jxc06nk5tu2s4ulsnl linux_privacy Интересные исследования шифрования whatsapp через реверс-инженеринг + аудит trail of bits. По сути, сквозное шифрование сообщений и прямую секретность доказать удалось, чтоб там павел дыров не кричал. Конечно, статьи нашли достаточно уязвимых мест, в основном, для метаданных, и отклонений от протокола signal (цукерберг тоже врун), но это всё равно лучше, чем облачные переписки в телеге, особенно, в групповых чатах. 1. https://eprint.iacr.org/2025/794.pdf 2. https://arxiv.org/html/2511.11385v2 3. https://blog.trailofbits.com/2026/04/07/what-we-learned-about-tee-security-from-auditing-whatsapps-private-inference/ npub1cv8cl9nfqtrkdn3k0nk05z8mjte6hxtdws6j4x65jxc06nk5tu2s4ulsnl linux_privacy @npub1utx…50e8 hello! add a private following - private contact list with hidden subs, like in Amethyst and 0xchat. many users will not see all the following list on Wisp after Amethyst and 0xchat. and please add a PIN code that encrypts the database. https://media.libernet.app/s/yhmR0P.jpg npub1cv8cl9nfqtrkdn3k0nk05z8mjte6hxtdws6j4x65jxc06nk5tu2s4ulsnl linux_privacy "шифрование" винды - это цирк с блэкджеком и бэкдорами https://cybernews.com/security/researcher-releases-bitlocker-bypass-and-privilege-escalation-exploit/ npub1cv8cl9nfqtrkdn3k0nk05z8mjte6hxtdws6j4x65jxc06nk5tu2s4ulsnl linux_privacy Крутой инструмент обхода белых списков на базе белых звонков/стримингов https://github.com/openlibrecommunity/olcrtc И подробные инструкции как это запустить самому https://habr.com/ru/articles/1020114/ https://www.youtube.com/watch?v=kxv7ET2cV1U npub1cv8cl9nfqtrkdn3k0nk05z8mjte6hxtdws6j4x65jxc06nk5tu2s4ulsnl linux_privacy юзерам из рф особый поклон #note1wvu…8952 npub1cv8cl9nfqtrkdn3k0nk05z8mjte6hxtdws6j4x65jxc06nk5tu2s4ulsnl linux_privacy Amethyst, Wisp and 0xchat npub1cv8cl9nfqtrkdn3k0nk05z8mjte6hxtdws6j4x65jxc06nk5tu2s4ulsnl linux_privacy Nospeak added support for audio and video calls! very cool secure nostr messenger. @npub174t…mqm6 thanks! what's next? notes? marmot chats? Nospeak теперь один из лучших и стабильно работающих мессенджеров #nostr https://github.com/psic4t/nospeak npub1cv8cl9nfqtrkdn3k0nk05z8mjte6hxtdws6j4x65jxc06nk5tu2s4ulsnl linux_privacy Simplex Chat добавил функцию каналов. Каналы реализованы по похожей на Nostr модели - передача контента пойдет через реле с сохранением состояния, при этом, реле не имеют никакой инфы о владельцах или пользователях канала, а контент зашифрован криптографией. Админы каналов полностью владеют клачами и контентом - никаких банов, как в телеге у Дырова нет + полная анонимность участников. Вот что значит - делать с заботов о людях и их приватности. Учись Пашка и не рассказывай сказки о приватности в своей дырявой помойке. https://github.com/simplex-chat/simplex-chat/blob/master/docs/protocol/channels-overview.md npub1cv8cl9nfqtrkdn3k0nk05z8mjte6hxtdws6j4x65jxc06nk5tu2s4ulsnl linux_privacy Выявление слежки в 30 популярных российских приложениях https://rks.global/files/research/russian_apps_search_for_vpn_ru.pdf npub1cv8cl9nfqtrkdn3k0nk05z8mjte6hxtdws6j4x65jxc06nk5tu2s4ulsnl linux_privacy Cloudflare пометил макс, как шпионское ПО. В идеале, как минимум, так надо пометить еще и яндекс с вк/ок, но до этих у Cloudflare, наверно, руки не дойдут https://media.libernet.app/s/THqFrf.jpg npub1cv8cl9nfqtrkdn3k0nk05z8mjte6hxtdws6j4x65jxc06nk5tu2s4ulsnl linux_privacy Cool! Warp is now open-source! https://www.warp.dev/blog/warp-is-now-open-source npub1cv8cl9nfqtrkdn3k0nk05z8mjte6hxtdws6j4x65jxc06nk5tu2s4ulsnl linux_privacy Delta Chat может работать в белых списках. Вот что для этого нужно: Подготовка «транспорта»: Не используйте свой личный ящик, иначе Delta Chat смешает ваши почтовые письма с чатами. Зарегистрируйте новый чистый аккаунт, например, на яндекс почте или на вк почте. Разрешение доступа (Критический шаг): Современные почтовые сервисы запрещают подключение по IMAP с основным паролем от аккаунта в целях безопасности. Вам нужно создать Пароль приложения. В Яндексе: Зайдите в аккаунт -> Безопасность -> Пароли приложений. Создайте новый пароль (выберите тип “Почта”). Скопируйте сгенерированный 16-значный код. Убедитесь, что в настройках почтового ящика (Почтовые программы) включен доступ по IMAP. В VK Почте: Перейдите в настройки безопасности аккаунта VK ID -> Безопасность и вход -> Пароли для внешних приложений. Сгенерируйте пароль. Установка и первый запуск: Скачайте клиент Delta Chat. Приложения доступны для Android, iOS, Windows, macOS и Linux. При запуске выберите «Войти в свой аккаунт». Запустите приложение и выберите "Использовать электронную почту как релей". Введите ваш созданный e-mail и вставьте пароль приложения. Приложение автоматически подтянет настройки серверов Яндекса или VK. Крутая настройка для усиления приватности перед почтовым провайдером: Зайдите в Настройки Delta Chat -> Продвинутые настройки. Опционально можно включить скрытие заголовков Message-ID и Chat-Version, чтобы ваши письма даже для почтовых алгоритмов Яндекса выглядели максимально стандартно, без следов автоматизации мессенджером. В Delta Chat через ботов можно читать крупные публичные телеграм каналы и rss-ленты. В итоге, получится телеграм на минималках, но зато с шифрованием. Список публичных ботов Delta Chat: https://deltachat-bot.github.io/public-bots/#/home npub1cv8cl9nfqtrkdn3k0nk05z8mjte6hxtdws6j4x65jxc06nk5tu2s4ulsnl linux_privacy @npub16zx…4ynp please add a PIN code and local database encryption to the mobile and desktop clients npub1cv8cl9nfqtrkdn3k0nk05z8mjte6hxtdws6j4x65jxc06nk5tu2s4ulsnl linux_privacy @npub1hru…f3yh chats nip-17 and marmot are very good👍 add a multi-account and maybe nostr web apps for viewing news and notes. also need a function to hide messages in notifications. npub1cv8cl9nfqtrkdn3k0nk05z8mjte6hxtdws6j4x65jxc06nk5tu2s4ulsnl linux_privacy @npub180c…h6w6 Hello. Are Nostr messages using NIP-17, NIP-EE, or Marmot protected against extraction from the device or forensic analysis (including deleted messages)? Can intelligence agencies and police recover data after the application has been uninstalled? npub1cv8cl9nfqtrkdn3k0nk05z8mjte6hxtdws6j4x65jxc06nk5tu2s4ulsnl linux_privacy Hello! You have a wonderful app! The browser works really great. But I noticed some issues with chats: it's impossible to have one Signal chat synced between phone and desktop. Also, messages disappear in the NIP-17 chat when using the account on both phone and desktop simultaneously. I tested this on Debian 13 and Android 16. Also, why is there a separate chat for my NIP-17 messages and notifications from there? (Maybe allow muting notifications for individual chats?). It would be cool if the app had a single unified chat on both phone and desktop, syncing all messages for both NIP-17 and Signal. npub1cv8cl9nfqtrkdn3k0nk05z8mjte6hxtdws6j4x65jxc06nk5tu2s4ulsnl linux_privacy @npub1wht…r3ec @npub1h0u…rwx8 White Noise and Keychat not compatible now? (different encryption protocols?) #nostr #signal #mls npub1cv8cl9nfqtrkdn3k0nk05z8mjte6hxtdws6j4x65jxc06nk5tu2s4ulsnl linux_privacy https://github.com/ExTV/Podroid Запуск Linux-контейнеров на Android без root-прав. Podroid позволяет пользователю запускать на устройстве под управлением Android (Android 9+ arm64) облегчённую виртуальную машину Alpine Linux с использованием QEMU. npub1cv8cl9nfqtrkdn3k0nk05z8mjte6hxtdws6j4x65jxc06nk5tu2s4ulsnl linux_privacy Tor начал разрабатывать собственные ram-серверы, уничтожающие все данные при изъятии. Мощная защита от криминалистики. https://www.techradar.com/vpn/vpn-privacy-security/beyond-no-log-tor-looks-into-seizure-proof-servers-that-forget-your-data npub1cv8cl9nfqtrkdn3k0nk05z8mjte6hxtdws6j4x65jxc06nk5tu2s4ulsnl linux_privacy Sorry, I found how to delete cookies. Cool! npub1cv8cl9nfqtrkdn3k0nk05z8mjte6hxtdws6j4x65jxc06nk5tu2s4ulsnl linux_privacy @npub1h0u…rwx8 hello! you are amazing! please add deleting cookies in your browser. and I don't receive messages in nip-17 - I get a new message notification, but there is no message. check it please. mls chats work great! and Keychat works great on Linux! npub1cv8cl9nfqtrkdn3k0nk05z8mjte6hxtdws6j4x65jxc06nk5tu2s4ulsnl linux_privacy @npub17n4…fyh9 thanks for dm! please add support of private following / private contacts (secret subscriptions that no one sees, like contacts in 0xchat and dm-contacts in Amethyst). npub1cv8cl9nfqtrkdn3k0nk05z8mjte6hxtdws6j4x65jxc06nk5tu2s4ulsnl linux_privacy Jumble added DM with signal mls encryption. The first Nostr web client with the most powerful encryption. Now you can use mls encryption in 0xchat in Jumble (napps). Джамбл добавил личку с супер-шифрованием сигнала - теперь у нас есть и вэб-клиент ностр с мощнейшим шифрованием. В 0хchat можно совмещать шифрование с nip-17 (в лс 0х) + млс-шифрование в джамбл (в napps). https://jumble.social/ npub1cv8cl9nfqtrkdn3k0nk05z8mjte6hxtdws6j4x65jxc06nk5tu2s4ulsnl linux_privacy wow okay 😂 npub1cv8cl9nfqtrkdn3k0nk05z8mjte6hxtdws6j4x65jxc06nk5tu2s4ulsnl linux_privacy @npub1gcx…nj5z add ntfy support for notifications. amethyst has no notification in graphene os npub1cv8cl9nfqtrkdn3k0nk05z8mjte6hxtdws6j4x65jxc06nk5tu2s4ulsnl linux_privacy FBI Extracts Suspect’s Deleted Signal Messages Saved in iPhone Notification Database. Вот почему криминалистическая экспертиза - вещь серьезная, и от нее надо мощно защищаться (лучше всего, с Graphene OS) https://lifehacker.com/tech/fbi-extracted-deleted-signal-messages-from-a-defendants-iphone npub1cv8cl9nfqtrkdn3k0nk05z8mjte6hxtdws6j4x65jxc06nk5tu2s4ulsnl linux_privacy Cloudflare признал Telega шпионским ПО https://kod.ru/cloudflare-vs-telega npub1cv8cl9nfqtrkdn3k0nk05z8mjte6hxtdws6j4x65jxc06nk5tu2s4ulsnl linux_privacy ну и третий спасительный вариант - использовать graphene os и расскидать вк-яндексы и ютубы-инстаграмы в разные профили. #note1seg…3wn2 npub1cv8cl9nfqtrkdn3k0nk05z8mjte6hxtdws6j4x65jxc06nk5tu2s4ulsnl linux_privacy На гитхабе появились приложения для андроид с проверкой обнаружения впн на устройстве. Реализовано всё это по методичке РКН. RKNHardering https://github.com/xtclovver/RKNHardering yourvpndead https://github.com/loop-uh/yourvpndead Можно затестить спалят ли вашу амнезию впн приложухи макс, вк, яндекс (скорее всего спалят и очень легко). Чтобы избежать этого есть 2 пути: телефон без рус гос приложений и впн на роутере с openwrt npub1cv8cl9nfqtrkdn3k0nk05z8mjte6hxtdws6j4x65jxc06nk5tu2s4ulsnl linux_privacy Nostr Mail должен позволить болтать с юзерами из Delta Chat. #note1dya…hvkc npub1cv8cl9nfqtrkdn3k0nk05z8mjte6hxtdws6j4x65jxc06nk5tu2s4ulsnl linux_privacy #nostr https://dweb.link/ipfs/Qmc2RYRDsmzTbadvALNMZxRRjMGXuBzL65wDgQBLVDnhM7?filename=17756314221504841775631399613.jpg npub1cv8cl9nfqtrkdn3k0nk05z8mjte6hxtdws6j4x65jxc06nk5tu2s4ulsnl linux_privacy Use 0xchat nostr client. You can use telegram web in 0xchat. Web version of telegram is the safest. 0xchat has mini-browser. npub1cv8cl9nfqtrkdn3k0nk05z8mjte6hxtdws6j4x65jxc06nk5tu2s4ulsnl linux_privacy Telegram can be even worse than WhatsApp. The app sends unencrypted traffic - this traffic can leak your metadata and even geolocation to local police. WhatsApp doesn't have such traffic. https://rys.io/en/179.html https://www.whonix.org/wiki/Telegram npub1cv8cl9nfqtrkdn3k0nk05z8mjte6hxtdws6j4x65jxc06nk5tu2s4ulsnl linux_privacy dont use telegram. chats not encrypted, secret chats have backdoors, metadata not encrypted. telegram shares user data with police npub1cv8cl9nfqtrkdn3k0nk05z8mjte6hxtdws6j4x65jxc06nk5tu2s4ulsnl linux_privacy 0xchat.com #nostr #meme #memes https://dweb.link/ipfs/QmcCuap3BRdFy6JbhvJpPCfxuMRJbXiqi3NRfjcgQDGzCN?filename=1775467057500353IMG_20260406_091311.jpg npub1cv8cl9nfqtrkdn3k0nk05z8mjte6hxtdws6j4x65jxc06nk5tu2s4ulsnl linux_privacy Yes, but they mainly use Tor for Telegram. They even install Telegram in Whonix despite that https://www.whonix.org/wiki/Telegram And they use Telegram Web in Tor Browser. Telegram's the new religion in Russia. Durov's got everyone hypnotized) npub1cv8cl9nfqtrkdn3k0nk05z8mjte6hxtdws6j4x65jxc06nk5tu2s4ulsnl linux_privacy Android for privacy, iOS for dumb flexing npub1cv8cl9nfqtrkdn3k0nk05z8mjte6hxtdws6j4x65jxc06nk5tu2s4ulsnl linux_privacy тут не каналы, а просто аккаунты, публикующие новости и прочий контент. искать можно в контактах сверху в поиске. включи реле, указанные в моем описании, чтоб видеть максимум контента (можешь включить даже вообще все реле среди предлагаемого списка в настройках. для поиска по постам используй поиск "moments" в тех же контактах. то есть, в самом 0xchat весь поиск всего и вся в разделе контакты. дополнительно можно использовать поиск в других приложениях ностр в napps - там самый крутой поиск в jumble, также можно искать в yakihonne. napps - это просто набор других приложений ностр, для удобства. чтобы тут на кого-то подписаться - добавляй его в контакты, в 0х автор аккаунта не увидит, что ты на него подписался. с друзьями обменивайся публичными ключами, приватный ключ никому не показывай и не теряй, иначе потеряешь аккаунт npub1cv8cl9nfqtrkdn3k0nk05z8mjte6hxtdws6j4x65jxc06nk5tu2s4ulsnl linux_privacy Keep away from telegram npub1cv8cl9nfqtrkdn3k0nk05z8mjte6hxtdws6j4x65jxc06nk5tu2s4ulsnl linux_privacy Yes, total dumb. npub1cv8cl9nfqtrkdn3k0nk05z8mjte6hxtdws6j4x65jxc06nk5tu2s4ulsnl linux_privacy Most russians consider Telegram the most secure messenger. They believe Durov that Telegram is more private than WhatsApp and Signal. Signal is not popular in Russia, Simplex chat is more popular, but only advanced users use it. npub1cv8cl9nfqtrkdn3k0nk05z8mjte6hxtdws6j4x65jxc06nk5tu2s4ulsnl linux_privacy I am Russian. You will laugh, but in Russia users idolize Telegram, they protect Telegram, and now they are trying to bypass the blocking of Telegram. Very very few users use Bitchat. 99.9% don't know about Nostr. npub1cv8cl9nfqtrkdn3k0nk05z8mjte6hxtdws6j4x65jxc06nk5tu2s4ulsnl linux_privacy вместо кучи поддержки, как это было в 18м году, павел дыров получил очередной мощный критический разбор. уже от крупного админа телеги и ютубе с почти лямом подписчиков. почти по всем пунктам не поспоришь. за последние лет 6 дыров угробил репутацию и наконец-то многие стали просыпаться и понимать, что это всегда был небезопасный мессенджер без шифрования, а еще с кучей скама со стороны самого дырова https://www.youtube.com/watch?v=TGeyXjWgsZk npub1cv8cl9nfqtrkdn3k0nk05z8mjte6hxtdws6j4x65jxc06nk5tu2s4ulsnl linux_privacy @npub1gcx…nj5z all you have to do is add a browser for nostr web apps and a pin code, and amethyst will become a copy of 0xchat 🙂 npub1cv8cl9nfqtrkdn3k0nk05z8mjte6hxtdws6j4x65jxc06nk5tu2s4ulsnl linux_privacy Хороший ресурс для тех, кто хочет создать свое nostr-приложение или запустить на сервере свой публичный реле https://nostrcg.github.io/devguide/ npub1cv8cl9nfqtrkdn3k0nk05z8mjte6hxtdws6j4x65jxc06nk5tu2s4ulsnl linux_privacy still high memory consumption. I ran noornote (deb) in debian-vm (qubes os) with 4 gb memory. then I looked my feed of posts for the last 4 hours - it used all 4 gb of memory. for comparison, 0xchat desktop (appimage) consumes 1.1 gb of memory when viewing a feed. by the way, I asked 0xchat dev add noornote web to 0xchat napps (0xchat nostr web apps) in the next release. npub1cv8cl9nfqtrkdn3k0nk05z8mjte6hxtdws6j4x65jxc06nk5tu2s4ulsnl linux_privacy @npub1g53…drvk add vless or awg (amnezia wireguard) protocols with obfuscation to Nostr-VPN to work for Russia, China, Turkmenistan, Iran, Belarus and other countries where states use dpi firewall for internet censorship. npub1cv8cl9nfqtrkdn3k0nk05z8mjte6hxtdws6j4x65jxc06nk5tu2s4ulsnl linux_privacy by the way, I follow you on 0xchat, so you don’t see me in the followers) npub1cv8cl9nfqtrkdn3k0nk05z8mjte6hxtdws6j4x65jxc06nk5tu2s4ulsnl linux_privacy I liked 0xchat and keychat better. these apps have many other web nostr apps (napps). it is very convenient when you have many nostr apps in one client. keychat also has nip-ee, like whitenoise. 0xchat has private subscriptions by default - no one sees which accounts you follow. I would like whitenoise to take the experience of these apps: you have an encrypted chat with friends, but also interact with other nostr users in web clients. npub1cv8cl9nfqtrkdn3k0nk05z8mjte6hxtdws6j4x65jxc06nk5tu2s4ulsnl linux_privacy Павел Дыров из своей виллы расскидывает уведомления для юзеров из рф о покупке премиума на годы вперед, мол на днях оплата рублями может пропасть. При этом, он не пишет что и телега может на днях полностью пропасть)) или что он предпримет что-то для обхода блокировки. Вы заплатите, а там уж сами крутитесь, разблокирывайте как хотите. https://cdn.nostrcheck.me/74853ad69b9ce9ed92e995f5a69acad5aa00885cdb48910c93c295787de1bd6d.webp