非持久化dom0和appvm/Non-persistent dom0 and appvm

#非持久化dom0

###参考该帖子forum post的方案一的方法

该方案优点:
1.不额外消耗内存 dom0修改本身几乎不占用多少硬盘
2.所有对dom0的修改都会放到一个临时加密overlay中,关机时清除ram中密钥 保证无法解密
实现dom0的非持久化

缺点:
1.:warning:该模式仅仅针对dom0 非持久化
对其他各种vm没有非持久化(添加 删除vm 在appvm里编辑文件等 重启后不会恢复原状态)
appvm非持久化参考第二部分
2.安装后会将部分目录如/boot只读 更新dom0内核相关包前 先删除该功能(remove参数)

使用ai编写成完整的部署脚本 测试后 运行良好(:warning:请根据自己需求修改部分参数 如果你没有将vm clone到varlibqubes pool运行 使该appvm也非持久化的化 dom0 overlay的大小不需要太大1G都够用)

使用前提:qubes os安装时使用lvm/lvm简单配置进行安装
在dom0 xfce4-terminal 使用

sudo lvs
sudo vgs

查看有无输出判断是否是该安装方式

使用方法 传到dom0终端运行:

sudo ./xx.sh install
sudo ./xx.sh remove

###部署脚本如下

#!/bin/bash
# ============================================================
# Qubes OS dom0 Live Mode - 加密覆盖层无痕模式
# ============================================================
# 功能:为dom0提供无痕启动模式,所有运行时修改写入LUKS2加密容器,
#       关机后数据自动销毁,防止取证分析。
#
# 【原理说明】
# 1. 将原系统根分区以只读方式挂载为lowerdir
# 2. 在/var/lib/创建LUKS2加密的稀疏容器文件作为upperdir
# 3. 使用OverlayFS将两者合并,所有写操作重定向到加密层
# 4. 关机时自动销毁加密密钥,容器数据不可读
#
# 【使用限制】
# - 系统必须使用LVM分区方案,卷组名为'qubes_dom0'
# - 根分区需有足够空闲空间存放加密容器
# - dom0更新需在正常持久模式下进行
# - 本脚本已适配 Qubes OS 4.2+(使用独立的 root-pool 和 vm-pool)
# ============================================================

# ============================================================
# 【可修改的配置参数】- 请根据实际情况调整
# ============================================================

# --- 加密容器大小 (单位: MB) ---
# 用途:定义存放dom0运行时修改的LUKS2加密容器大小(稀疏文件)
# 影响:设置过大会占用过多硬盘空间;设置过小可能导致运行时空间耗尽
# 建议值:普通使用 20480 (20GB);轻量使用 10240 (10GB)
# 可修改范围:5120 - 51200 (5GB - 50GB)
OVERLAY_CONTAINER_SIZE_MB="5000"

# --- dom0根逻辑卷目标大小 (单位: GB) ---
# 用途:确保 / 根逻辑卷的虚拟大小足够容纳加密容器
# 影响:如果当前根逻辑卷太小,容器可能无法创建
# 建议值:比容器大小至少大10GB(如容器20GB,则此值设为30GB)
# 可修改范围:大于容器大小+5GB
TARGET_ROOT_LV_SIZE_GB="40"

# --- Thin Pool 扩容触发阈值 (百分比) ---
# 用途:当 root-pool 的 Data% 超过此值时,尝试自动扩容
# 影响:阈值太低可能导致频繁扩容,太高可能来不及扩容
# 建议值:80 (即 Data% > 80% 时触发)
# 可修改范围:60 - 90
POOL_EXTEND_THRESHOLD_PERCENT="80"

# --- Thin Pool 每次扩容的增量 (单位: GB) ---
# 用途:当需要扩容时,给 pool 增加多少空间
# 影响:增量太小可能无法满足需求,太大可能浪费空间
# 建议值:20 (每次扩展20GB)
# 可修改范围:10 - 50
POOL_EXTEND_STEP_GB="20"

# --- dom0最大内存限制 (单位: MB) ---
# 用途:限制dom0可使用的最大内存量
# 影响:设置过低可能影响dom0性能;过高可能挤占其他VM内存
# 建议值:系统总内存的80%(自动计算),或手动指定
# 可修改范围:4096 - 系统总内存
# 特殊值:设为 "auto" 则自动使用总内存的80%
DOM0_MAX_MEM="4000"

# --- 是否启用RAM-Wipe ---
# 用途:关机时覆写内存,防止冷启动攻击
# 建议值:true(启用)
# 可修改值:true / false
ENABLE_RAM_WIPE="true"

# --- 是否启用内核加固 ---
# 用途:应用sysctl安全加固参数
# 建议值:true(启用)
# 可修改值:true / false
ENABLE_HARDENING="true"

# --- GRUB启动项名称 ---
# 用途:自定义GRUB菜单中显示的启动项名称
# 建议值:保持默认
# 可修改值:任意字符串
GRUB_MENU_TITLE="Qubes Encrypted-Overlay Amnesic Mode"

# ============================================================

set -euo pipefail

# --- 颜色输出 ---
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
print_info() { echo -e "${GREEN}[INFO]${NC} $*"; }
print_warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
print_error() { echo -e "${RED}[ERROR]${NC} $*" >&2; exit 1; }
print_success() { echo -e "${GREEN}[SUCCESS]${NC} $*"; }

check_root() {
    if [[ $EUID -ne 0 ]]; then
        print_error "This script must be run as root (sudo)."
    fi
}

# --- 获取系统信息 ---
get_boot_uuid() {
    local uuid
    uuid=$(findmnt -n -o UUID /boot 2>/dev/null)
    if [[ -z "$uuid" ]]; then
        uuid=$(blkid -s UUID -o value "$(findmnt -n -o SOURCE /boot 2>/dev/null)" 2>/dev/null)
    fi
    echo "$uuid"
}

get_luks_uuid() {
    local device uuid
    device=$(blkid -t TYPE="crypto_LUKS" -o device 2>/dev/null | head -n1)
    if [[ -n "$device" ]]; then
        uuid=$(cryptsetup luksUUID "$device" 2>/dev/null)
    fi
    echo "$uuid"
}

get_latest_xen() {
    ls /boot/xen*.gz 2>/dev/null | sort -V | tail -1 | xargs basename 2>/dev/null || echo "/xen-4.19.4.gz"
}

get_latest_kernel_initrd() {
    local kernel initrd
    kernel=$(ls /boot/vmlinuz-*qubes*.x86_64 2>/dev/null | grep -E 'qubes\.fc[0-9]+' | sort -V | tail -1 | xargs basename)
    initrd="/initramfs-${kernel#vmlinuz-}.img"
    echo "$kernel|$initrd"
}

# --- 存储相关辅助函数 ---

# 获取存放 / 根文件系统的 Thin Pool 名称
get_root_pool() {
    # 方法:从 /dev/mapper/qubes_dom0-root 的 thin pool 属性获取
    local pool
    pool=$(lvs -o pool_lv --noheadings --nosuffix qubes_dom0/root 2>/dev/null | tr -d ' ')
    if [[ -z "$pool" ]]; then
        # 尝试查找带有 "pool" 且与 root 关联的池
        pool=$(lvs -o lv_name,pool_lv --noheadings --nosuffix qubes_dom0 | grep -E "\s+root$" | awk '{print $2}' | tr -d ' ')
    fi
    if [[ -z "$pool" ]]; then
        # 如果还没有,可能命名为 pool00
        pool="pool00"
        # 验证是否存在
        if ! lvs qubes_dom0/"$pool" &>/dev/null; then
            print_error "Cannot determine root thin pool. Please check LVM layout."
        fi
    fi
    echo "$pool"
}

# 获取根逻辑卷的当前大小(GB)
get_root_lv_size_gb() {
    lvs -o lv_size --noheadings --nosuffix --units g qubes_dom0/root 2>/dev/null | tr -d ' ' | sed 's/g//' | awk '{print int($1)}'
}

# 获取 Thin Pool 的 Data% (整数)
get_pool_data_percent() {
    local pool="$1"
    lvs -o data_percent --noheadings --nosuffix qubes_dom0/"$pool" 2>/dev/null | tr -d ' ' | sed 's/\..*//'
}

# 获取 Thin Pool 的当前大小(GB)
get_pool_size_gb() {
    local pool="$1"
    lvs -o lv_size --noheadings --nosuffix --units g qubes_dom0/"$pool" 2>/dev/null | tr -d ' ' | sed 's/g//' | awk '{print int($1)}'
}

# 获取卷组空闲空间(GB)
get_vg_free_gb() {
    vgs -o vg_free --noheadings --nosuffix --units g qubes_dom0 2>/dev/null | tr -d ' ' | sed 's/g//' | awk '{print int($1)}'
}

# 扩容 root-pool(如果 Data% 高于阈值且有足够空闲空间)
extend_pool_if_needed() {
    local pool="$1"
    local current_data_percent
    current_data_percent=$(get_pool_data_percent "$pool")
    if [[ -z "$current_data_percent" ]]; then
        print_warn "Unable to get Data% for pool $pool. Skipping pool extension."
        return 1
    fi
    if [[ "$current_data_percent" -lt "$POOL_EXTEND_THRESHOLD_PERCENT" ]]; then
        print_info "Pool $pool Data% = ${current_data_percent}% (threshold: ${POOL_EXTEND_THRESHOLD_PERCENT}%) - no need to extend."
        return 0
    fi
    print_warn "Pool $pool Data% = ${current_data_percent}% exceeds threshold ${POOL_EXTEND_THRESHOLD_PERCENT}%."

    local vg_free
    vg_free=$(get_vg_free_gb)
    if [[ "$vg_free" -lt "$POOL_EXTEND_STEP_GB" ]]; then
        print_error "Insufficient free space in VG (only ${vg_free}GB) to extend pool by ${POOL_EXTEND_STEP_GB}GB. Aborting."
    fi

    local current_pool_size
    current_pool_size=$(get_pool_size_gb "$pool")
    local new_pool_size=$((current_pool_size + POOL_EXTEND_STEP_GB))
    print_info "Extending pool $pool from ${current_pool_size}GB to ${new_pool_size}GB..."
    if lvextend -L "${new_pool_size}G" qubes_dom0/"$pool"; then
        print_success "Pool $pool extended successfully."
    else
        print_error "Failed to extend pool $pool."
    fi
}

# 确保根逻辑卷的大小足够容纳加密容器
extend_root_lv_if_needed() {
    local container_size_gb=$(( (OVERLAY_CONTAINER_SIZE_MB + 1023) / 1024 ))  # 向上取整
    local required_root_size=$((container_size_gb + 5))  # 额外5GB余量
    if [[ "$required_root_size" -lt "$TARGET_ROOT_LV_SIZE_GB" ]]; then
        required_root_size="$TARGET_ROOT_LV_SIZE_GB"
    fi

    local current_root_size
    current_root_size=$(get_root_lv_size_gb)
    if [[ -z "$current_root_size" ]]; then
        print_error "Cannot determine current root LV size."
    fi

    if [[ "$current_root_size" -ge "$required_root_size" ]]; then
        print_info "Root LV size ${current_root_size}GB is sufficient (need ${required_root_size}GB)."
        return 0
    fi

    print_warn "Root LV size ${current_root_size}GB is less than required ${required_root_size}GB. Extending..."
    if lvextend -L "${required_root_size}G" qubes_dom0/root; then
        print_success "Root LV extended to ${required_root_size}GB."
        # 扩展文件系统
        if resize2fs /dev/mapper/qubes_dom0-root 2>/dev/null; then
            print_info "File system resized successfully."
        else
            print_warn "File system resize failed. You may need to run 'resize2fs /dev/mapper/qubes_dom0-root' manually."
        fi
    else
        print_error "Failed to extend root LV."
    fi
}

# --- 主要的存储空间检查(在安装时调用)---
ensure_storage_space() {
    print_info "Checking storage space for overlay container..."

    local pool
    pool=$(get_root_pool)
    print_info "Root thin pool detected: $pool"

    # 1. 检查并扩展 pool 如果过满
    extend_pool_if_needed "$pool"

    # 2. 确保根逻辑卷大小足够
    extend_root_lv_if_needed

    print_success "Storage space check completed."
}

# --- 安装功能 ---
install_mode() {
    print_info "Starting installation of Qubes OS Ephemeral Overlay Mode..."

    # 收集系统信息
    BOOT_UUID=$(get_boot_uuid)
    LUKS_UUID=$(get_luks_uuid)
    XEN_PATH=$(get_latest_xen)
    KERNEL_INITRD=$(get_latest_kernel_initrd)
    LATEST_KERNEL="${KERNEL_INITRD%|*}"
    LATEST_INITRAMFS="${KERNEL_INITRD#*|}"
    USER_HOME=$(getent passwd "$SUDO_USER" | cut -d: -f6 2>/dev/null || echo "$HOME")

    # 计算dom0内存限制
    if [[ "$DOM0_MAX_MEM" == "auto" ]]; then
        system_total_mb=$(xl info 2>/dev/null | grep total_memory | awk '{print $3}' || echo "0")
        if [[ -n "$system_total_mb" ]] && [[ "$system_total_mb" -gt 0 ]]; then
            DOM0_MAX_MB=$((system_total_mb * 80 / 100))
        else
            DOM0_MAX_MB=10240
        fi
    else
		DOM0_MAX_MB=$DOM0_MAX_MEM
    fi
    DOM0_MAX_RAM="dom0_mem=max:${DOM0_MAX_MB}M"

    print_info "Detected system parameters:"
    echo "  BOOT_UUID: $BOOT_UUID"
    echo "  LUKS_UUID: $LUKS_UUID"
    echo "  XEN: $XEN_PATH"
    echo "  Kernel: $LATEST_KERNEL"
    echo "  Initramfs: $LATEST_INITRAMFS"
    echo "  Dom0 Max Memory: ${DOM0_MAX_MB}M"

    # 1. 确保存储空间足够
    ensure_storage_space

    # 2. 禁用swap
    print_info "Disabling swap in /etc/fstab..."
    if grep -q '^[^#].*swap' /etc/fstab; then
        sed -i.bak '/swap/s/^/# /' /etc/fstab
        swapoff -a 2>/dev/null || true
    fi

    # 3. 创建Dracut模块: 90overlay-crypt
    print_info "Creating Dracut module: 90overlay-crypt..."
    local MODULE_DIR="/usr/lib/dracut/modules.d/90overlay-crypt"
    mkdir -p "$MODULE_DIR"

    cat > "$MODULE_DIR/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 "$MODULE_DIR/module-setup.sh"

    cat > "$MODULE_DIR/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

mkdir -p /var/lib
dd if=/dev/zero of=/var/lib/overlay-crypt.img bs=1M count=0 seek=$OVERLAY_CONTAINER_SIZE_MB status=none

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 "$MODULE_DIR/overlay-crypt.sh"

    # 4. 创建RAM-Wipe模块(可选)
    if [[ "$ENABLE_RAM_WIPE" == "true" ]]; then
        print_info "Creating Dracut module: 40ram-wipe..."
        local RW_DIR="/usr/lib/dracut/modules.d/40ram-wipe"
        mkdir -p "$RW_DIR"

        cat > "$RW_DIR/module-setup.sh" << 'EOF'
#!/bin/bash
check() {
    require_binaries sync || return 1
    require_binaries sleep || return 1
    require_binaries dmsetup || return 1
    return 0
}
depends() { return 0; }
install() {
    inst_multiple sync sleep dmsetup
    inst_hook shutdown 40 "$moddir/wipe-ram.sh"
    inst_hook cleanup 80 "$moddir/wipe-ram-needshutdown.sh"
}
installkernel() { return 0; }
EOF
        chmod +x "$RW_DIR/module-setup.sh"

        cat > "$RW_DIR/wipe-ram.sh" << 'EOF'
#!/bin/sh
. /lib/dracut-lib.sh
kernel_wiperam_setting="$(getarg wiperam)"
if [ "$kernel_wiperam_setting" = "skip" ]; then
    echo "wipe-ram.sh: Skip."
    return 0
fi
echo "wipe-ram.sh: Starting RAM wipe..."
sync
echo 3 > /proc/sys/vm/drop_caches
sync
echo "wipe-ram.sh: Completed."
EOF
        chmod +x "$RW_DIR/wipe-ram.sh"

        cat > "$RW_DIR/wipe-ram-needshutdown.sh" << 'EOF'
#!/bin/sh
. /lib/dracut-lib.sh
if [ "$(getarg wiperam)" != "skip" ]; then
    need_shutdown
fi
EOF
        chmod +x "$RW_DIR/wipe-ram-needshutdown.sh"
    fi

    # 5. 创建Dracut配置
    print_info "Creating Dracut configuration..."
    echo 'add_dracutmodules+=" ram-wipe "' > /etc/dracut.conf.d/30-ram-wipe.conf

    # 6. 更新Initramfs
    print_info "Updating initramfs with dracut..."
    dracut --verbose --force

    # 7. 创建GRUB自定义菜单项
    print_info "Creating GRUB custom entries in /etc/grub.d/40_custom..."
    cat > /etc/grub.d/40_custom << EOF
#!/usr/bin/sh
exec tail -n +3 \$0

menuentry '$GRUB_MENU_TITLE' --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 xen_privcmd.unrestricted init_on_alloc=1 init_on_free=1
    echo 'Loading initial ramdisk ...'
    insmod multiboot2
    module2 --nounzip $LATEST_INITRAMFS
}
EOF
    chmod 755 /etc/grub.d/40_custom

    # 8. 更新GRUB
    print_info "Updating GRUB configuration..."
    grub2-mkconfig -o /boot/grub2/grub.cfg

    # 9. 创建硬化和提示脚本
    if [[ "$ENABLE_HARDENING" == "true" ]]; then
        print_info "Creating hardening autostart script..."
        mkdir -p "$USER_HOME/.config"
        cat > "$USER_HOME/.config/harden.sh" << 'EOF'
#!/bin/bash
sleep 6
if findmnt -n -o SOURCE / | grep -qE "(overlay)"; then
    notify-send --expire-time=20000 "Amnesic session is running" "dom0 mode: $(findmnt -n -o SOURCE /)" --icon=dialog-information
    sudo sysctl -w kernel.sysrq=0 2>/dev/null
    sudo sysctl -w kernel.perf_event_paranoid=3 2>/dev/null
    sudo sysctl -w kernel.kptr_restrict=2 2>/dev/null
    sudo sysctl -w kernel.panic=5 2>/dev/null
    sudo sysctl -w fs.protected_regular=2 2>/dev/null
    sudo sysctl -w fs.protected_fifos=2 2>/dev/null
    sudo sysctl -w kernel.printk="3 3 3 3" 2>/dev/null
    sudo sysctl -w kernel.kexec_load_disabled=1 2>/dev/null
    sudo sysctl -w kernel.io_uring_disabled=2 2>/dev/null
    sudo chattr +i /boot/grub2/grub.cfg 2>/dev/null
    sudo chattr +i /boot 2>/dev/null
fi
EOF
        chmod 755 "$USER_HOME/.config/harden.sh"

        mkdir -p "$USER_HOME/.config/autostart"
        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
    fi

    print_success "Installation completed. Reboot and select '$GRUB_MENU_TITLE' from GRUB."
    print_info "IMPORTANT: If USB keyboard/mouse does not work in the live mode,"
    print_info "please refer to: https://forum.qubes-os.org/t/38868"
    print_info "The recommended fix is to use 'usbcore.authorized_default=0' and 'rd.qubes.dom0_usb=<BDF>'"
    print_info "instead of the deprecated 'rd.qubes.hide_all_usb' parameter."
}

# --- 卸载功能 ---
uninstall_mode() {
    print_info "Starting uninstallation of Qubes OS Ephemeral Overlay Mode..."

    # 1. 移除GRUB菜单项
    print_info "Removing GRUB custom entries..."
    if [[ -f /etc/grub.d/40_custom ]]; then
        sed -i '/exec tail -n +3 \$0/,$d' /etc/grub.d/40_custom
        if ! grep -q "exec tail -n +3 \$0" /etc/grub.d/40_custom; then
            echo '#!/usr/bin/sh' > /etc/grub.d/40_custom
            echo 'exec tail -n +3 $0' >> /etc/grub.d/40_custom
        fi
        chmod 755 /etc/grub.d/40_custom
    fi

    # 2. 移除Dracut模块
    print_info "Removing Dracut modules..."
    local modules=(
        "/usr/lib/dracut/modules.d/90overlay-crypt"
        "/usr/lib/dracut/modules.d/40ram-wipe"
    )
    for mod in "${modules[@]}"; do
        if [[ -d "$mod" ]]; then
            rm -rf "$mod"
            print_info "Removed: $mod"
        fi
    done

    # 3. 移除Dracut配置文件
    sudo chattr -i /boot 2>/dev/null || true
	sudo chattr -i /boot/grub2/grub.cfg 2>/dev/null || true
    print_info "Removing Dracut configuration files..."
    rm -f /etc/dracut.conf.d/30-ram-wipe.conf

    # 4. 恢复swap
    print_info "Restoring swap in /etc/fstab..."
    if [[ -f /etc/fstab.bak ]]; then
        mv /etc/fstab.bak /etc/fstab
    fi

    # 5. 更新Initramfs
    print_info "Updating initramfs..."
    dracut --verbose --force

    # 6. 更新GRUB
    print_info "Updating GRUB configuration..."
    grub2-mkconfig -o /boot/grub2/grub.cfg

    # 7. 删除加密容器文件
    if [[ -f /var/lib/overlay-crypt.img ]]; then
        print_info "Removing encrypted container file..."
        rm -f /var/lib/overlay-crypt.img
    fi

    # 8. 移除硬化脚本
    if [[ -f "$USER_HOME/.config/harden.sh" ]]; then
        rm -f "$USER_HOME/.config/harden.sh"
    fi
    if [[ -f "$USER_HOME/.config/autostart/harden.desktop" ]]; then
        rm -f "$USER_HOME/.config/autostart/harden.desktop"
    fi

    print_success "Uninstallation completed. System restored to default boot behavior."
}

# --- 主逻辑 ---
main() {
    check_root

    # 获取当前用户HOME
    USER_HOME=$(getent passwd "$SUDO_USER" | cut -d: -f6 2>/dev/null || echo "$HOME")

    case "$1" in
        install)
            install_mode
            ;;
        remove|uninstall)
            uninstall_mode
            ;;
        *)
            echo "Usage: $0 {install|remove}"
            echo "  install   - Deploy ephemeral overlay mode for dom0"
            echo "  remove    - Remove all changes and restore original boot"
            exit 1
            ;;
    esac
}

main "$@"

#非持久化appvm
参考官方文档以及帖子post

qvm-volume config VMname:root rw False

将会让Vmname的root只读 修改会重定向到volatile层

qvm-pool -s vm-pool -o ephemeral_volatile=True 

或者

qvm-volume config VMname:volatile ephemeral True

volatile层会使用临时加密 关闭后清空密钥实现非持久化

那么只剩下private卷(/rw 或者说/home/user下所有的修改没有好方法解决 非持久化)

使用脚本(/rw/config/rc.local)将/home/user或者/home/user/下的敏感目录 使用overlay挂到tmpfs保证不落盘即可实现非持久化

使用注意:使用overlay的目录大小要根据实际设计好 比如.cache .local防止浏览器 app写缓存写爆overlay层

使用方法:传到目标appvm:

sudo cat /home/user/PATH/myrc.local >> /rw/config/rc.local

重启后使用mount df验证是否成功挂载

###参考脚本


# /rw/config/rc.local 使用 Overlay 覆盖 /home/user 下的目录,并限制 upperdir 大小


USER_HOME="/home/user"
TARGET_DIRS=".cache .config .local"

# 为不同目录设置大小限制(可根据需要调整)
TMPFS_SIZE_CACHE="450M"
TMPFS_SIZE_CONFIG="50M"
TMPFS_SIZE_LOCAL="450M"

# 检查 mountpoint 命令
if ! command -v mountpoint &> /dev/null; then
    echo "Warning: mountpoint not found, skipping mount checks."
    is_mounted() { return 1; }
else
    is_mounted() { mountpoint -q "$1"; }
fi

handle_directory() {
    local dir_name="$1"
    local target_path="${USER_HOME}/${dir_name}"

    # 如果已经挂载,跳过
    if is_mounted "$target_path"; then
        echo "Skipping $target_path: already mounted"
        return
    fi

    # 如果路径是软链接,跳过
    if [ -L "$target_path" ]; then
        echo "Skipping $target_path: is a symlink"
        return
    fi

    # 确保目录存在(若不存在则创建空目录作为 lowerdir)
    if [ ! -d "$target_path" ]; then
        echo "Directory $target_path does not exist, creating empty one."
        mkdir -p "$target_path"
        chown 1000:1000 "$target_path"
        chmod 700 "$target_path"
    fi

    # 根据目录名决定大小限制
    local size_limit
    case "$dir_name" in
        ".cache")
            size_limit="$TMPFS_SIZE_CACHE"
            ;;
        ".config")
            size_limit="$TMPFS_SIZE_CONFIG"
            ;;
        ".local")
            size_limit="$TMPFS_SIZE_LOCAL"
            ;;
        *)
            size_limit="50M"
            # 默认值
            ;;
    esac

    local safe_name=$(echo "$dir_name" | tr '/' '_')

    # 创建一个专用的 tmpfs 挂载点,用于存放 upperdir 和 workdir
    local upper_mount="/tmp/overlay_upper_${safe_name}"
    mkdir -p "$upper_mount"

    # 挂载 tmpfs,限制大小
    mount -t tmpfs -o size="$size_limit",mode=700,uid=1000,gid=1000 tmpfs "$upper_mount"
    echo "tmpfs mounted on $upper_mount (size=$size_limit)"

    # 在 tmpfs 内创建 upper 和 work 子目录
    local upperdir="${upper_mount}/upper"
    local workdir="${upper_mount}/work"
    mkdir -p "$upperdir" "$workdir"

    # 挂载 Overlay
    mount -t overlay overlay \
        -o lowerdir="$target_path",upperdir="$upperdir",workdir="$workdir" \
        "$target_path"

    echo "Overlay mounted on $target_path (upperdir in $upper_mount, size=$size_limit)"
}

# 处理所有目录
for dir in $TARGET_DIRS; do
    handle_directory "$dir"
done

exit 0

#Non-persistent dom0

###Refer to the method of Option 1 of this forum post

Advantages of this program:

  1. No additional memory is consumed. The dom0 modification itself takes up almost no hard disk.
  2. All modifications to dom0 will be placed in a temporary encryption overlay, and the key in the ram will be cleared when shutting down to ensure that it cannot be decrypted.
    Implement non-persistence of DOM0

Disadvantages:
1.:warning:This mode is only for dom0 non-persistence
There is no non-persistence for various other vm (adding, deleting vm, editing files in appvm, etc. will not restore the original state after restarting)
appvm non-persistent reference part 2
2. After installation, some directories such as /boot will be read-only. Before updating the dom0 kernel related packages, delete this function (remove parameter)

Use ai to write a complete deployment script. After testing, it runs well (:warning:Please modify some parameters according to your own needs. If you do not clone the vm to the varlibqubes pool to run, the appvm will not be persistent. The size of the dom0 overlay does not need to be too large. 1G is enough)

Prerequisites for use: Use lvm/lvm simple configuration when installing qubes os.
Used in dom0 xfce4-terminal

sudo lvs 
sudo vgs 

Check whether there is output to determine whether this is the installation method.

How to use: Pass to dom0 terminal and run:

sudo ./xx.sh install 
sudo ./xx.sh remove 

###The deployment script is as follows


#Non-persistent appvm
Refer to official documents and post

qvm-volume config VMname: root rw False 

Will make the root of Vmname read-only and modifications will be redirected to the volatile layer

qvm-pool -s vm-pool -o ephemeral_volatile=True 

or

qvm-volume config VMname: volatile ephemeral True 

The volatile layer will use temporary encryption and clear the key after closing to achieve non-persistence.

Then only the private volume is left (there is no good way to solve all the modifications under /rw or /home/user, non-persistent)

Use a script (/rw/config/rc.local) to hang sensitive directories under /home/user or /home/user/ using overlay to tmpfs to ensure non-persistence without losing disk space.

Note on usage: The size of the directory using overlay should be designed according to the actual situation. For example, .cache.local prevents the browser app from writing cache and exploding the overlay layer.

Use method:pass to target appvm:

sudo cat /home/user/PATH/myrc.local >> /rw/config/rc.local 

After restarting, use mount df to verify whether the mount is successful.

###Reference script:


1 Like

我会说一点中文。因为我的路还很长,所以差不多看不懂你的中文写的,但给你点赞 :+1: 加油