目录

Qubes OS Templates 管理实践:从手动配置到 Salt 自动化

这篇文章记录了我在 Qubes OS 4.3 中使用 Template 功能来组织和维护日常工作环境的实践经验,从最初的手动配置逐步演进到 Salt 管理。进阶部分用我自己的 qubes-salt-config 仓库做例子。

Qubes OS 是一个基于安全隔离原则的操作系统,通过 Xen 虚拟化技术创建和管理多个独立的虚拟机(称为 Qube),实现"安全通过隔离"(Security by Isolation)的设计理念。

本文基于 Qubes OS 4.3 系统,使用 Debian 13 和 Fedora 44 作为基础模板,涵盖项目开发、日常使用以及安全研究等多种工作场景。

文章结构:

  • 第一部分:Qubes OS 核心概念和架构
  • 第二部分:手动 Template 管理实践
  • 第三部分:用 qubes-salt-config 做 Salt 自动化

Template 是 Qubes OS 中的一个特殊类型的虚拟机,它包含了操作系统的根文件系统(root filesystem)。Template 主要用于:

  • 安装和更新系统软件包
  • 作为 App Qube 的基础镜像
  • 集中管理软件依赖

根据官方文档,Template 的设计带来了以下优势:

  • 安全性:App Qube 对其基础 Template 只有只读访问权限,即使 App Qube 被入侵,也无法感染 Template 或其他基于该 Template 的 Qube
  • 存储优化:基于 Template 的 Qube 只需存储用户数据,大幅节省磁盘空间
  • 快速创建:由于根文件系统已在 Template 中存在,创建新的 App Qube 非常快速
  • 集中更新:更新 Template 后,所有基于它的 Qube 重启后自动使用更新

App Qube 是基于 Template 创建的虚拟机,用于运行日常应用程序。App Qube 的特点:

  • 继承 Template 的根文件系统(只读)
  • 拥有独立的用户目录(/home)和持久化存储(/rw)
  • 根文件系统的任何更改在关机后会丢失
  • /home、/usr/local 和 /rw/config 中的更改会持久化

Disposable 是临时的虚拟机,关闭后所有数据都会被销毁,适用于不可信的操作,如打开可疑邮件附件或浏览不信任的网站。

Dom0 是 Qubes OS 的管理域,运行 Xen hypervisor 和 Qubes 管理工具。Dom0 没有网络访问,以最大化安全性。

graph TB
    Dom0["<b>Dom0 (管理域)</b><br/>Xen Hypervisor + Qubes Manager"]
    
    Dom0 --> SysNet["sys-net<br/>(NetVM)"]
    Dom0 --> SysFirewall["sys-firewall<br/>(ProxyVM)"]
    Dom0 --> SysUSB["sys-usb<br/>(USB VM)"]
    Dom0 --> Templates["Templates<br/>(模板)"]
    
    SysNet --> SysFirewall
    SysFirewall --> AppQubes
    
    Templates -->|只读继承| AppQubes["<b>App Qubes (工作域)</b>"]
    
    AppQubes -.-> Work["work (工作)"]
    AppQubes -.-> Personal["personal (个人)"]
    AppQubes -.-> Banking["banking (银行)"]
    AppQubes -.-> Dev["dev (开发)"]
    AppQubes -.-> Social["social (社交)"]
    
    style Dom0 fill:#c92a2a,stroke:#ff6b6b,stroke-width:3px,color:#fff
    style Templates fill:#1971c2,stroke:#4dabf7,stroke-width:2px,color:#fff
    style AppQubes fill:#2f9e44,stroke:#51cf66,stroke-width:2px,color:#fff
    style SysNet fill:#f59f00,stroke:#ffd43b,stroke-width:2px,color:#000
    style SysFirewall fill:#f59f00,stroke:#ffd43b,stroke-width:2px,color:#000
    style SysUSB fill:#f59f00,stroke:#ffd43b,stroke-width:2px,color:#000
graph TB
    subgraph High["高信任区域 (Banking, Vault)"]
        Banking["Banking"]
        Vault["Vault"]
    end
    
    subgraph Medium["中信任区域 (Work, Personal)"]
        Work["Work"]
        Personal["Personal"]
    end
    
    subgraph Low["低信任区域 (Random, Untrusted)"]
        Random["Random"]
        Untrusted["Untrusted"]
    end
    
    subgraph Network["网络区域 (sys-net, sys-firewall)"]
        SysNet["sys-net"]
        SysFirewall["sys-firewall"]
    end
    
    Network <-->|严格限制| Low
    Low <-->|受控数据流| Medium
    Medium <-->|受控数据流| High
    
    SysNet --> SysFirewall
    
    style High fill:#862e9c,stroke:#be4bdb,stroke-width:2px,color:#fff
    style Medium fill:#1864ab,stroke:#339af0,stroke-width:2px,color:#fff
    style Low fill:#d9480f,stroke:#ff6b6b,stroke-width:2px,color:#fff
    style Network fill:#2f9e44,stroke:#51cf66,stroke-width:2px,color:#fff

在 Qubes OS 4.3 环境中,我需要支持多种不同的工作场景:项目开发、日常娱乐和安全研究。使用 Template 系统的优势:

  • 软件包的安装和更新在 Template 中统一进行
  • 减少重复配置,一次配置多处使用
  • 简化维护流程,降低出错概率
  • 不同用途的 App Qube 使用不同的 Template
  • 限制攻击面,防止交叉感染
  • 实现不同信任级别的工作环境隔离
  • 基于已配置好的 Template 快速创建新的 App Qube
  • 无需每次从头安装和配置软件
  • 适合测试和临时任务
  • 多个 App Qube 共享同一个 Template 的根文件系统
  • 显著节省磁盘空间
  • 便于管理多个工作环境

根据不同的使用场景和安全需求,我将 Template 划分为以下几个类别:

用途:网络和 VPN 工具集

安装的软件:

  • Tailscale:零配置 VPN,基于 WireGuard 协议
  • WireGuard:现代高性能 VPN 协议
  • OpenVPN:传统 VPN 解决方案
  • 网络诊断工具(如 tcpdump、wireshark)

基础模板:fedora-44

选择原因:Fedora 自带 NetworkManager,非常方便管理 OpenVPN、WireGuard 等 VPN 配置,可以通过图形界面或命令行轻松导入和切换 VPN 配置文件

安全考虑:此类 Template 主要用于网络隔离和加密通信,应避免安装不必要的软件以减少攻击面

用途:软件开发环境

安装的软件:

  • VSCode:微软开发的代码编辑器
  • Emacs:可扩展的文本编辑器
  • Git:版本控制系统
  • 各类编译器和开发工具链
  • Docker CLI(如需要)
  • Fcitx5:中文输入法框架,支持拼音、五笔等多种输入方式

基础模板:debian-13

选择原因:Debian 稳定性高、包管理简洁,适合作为开发环境的基础。Debian 的软件仓库齐全,依赖关系清晰,非常适合构建可靠的开发环境

安全考虑:开发环境可能需要执行第三方代码,建议为不同项目创建独立的 App Qube

用途:社交通讯应用

安装的软件:

  • Telegram Desktop:即时通讯应用
  • WeeChat:终端 IRC 客户端
  • Signal Desktop:端到端加密通讯
  • 邮件客户端
  • Fcitx5:中文输入法框架,方便在聊天中输入中文

基础模板:fedora-44

安全考虑:社交应用可能接收不可信内容,应严格限制其文件系统访问权限

用途:加密和密钥管理(配合 Split GPG)

安装的软件:

  • GnuPG 2:OpenPGP 加密和签名工具
  • Pinentry:密码输入界面
  • 密钥管理工具

基础模板:debian-13-minimal

安全考虑:这是最敏感的 Template 之一,应保持最小化安装,仅包含必需组件。建议配合 Qubes 的 Split GPG 功能使用,将私钥存储在隔离的 Vault VM 中

用途:日常工具和多媒体

安装的软件:

  • GIMP:图像编辑软件
  • Inkscape:矢量图形编辑器
  • LibreOffice:办公套件
  • 媒体播放器

基础模板:fedora-44

安全考虑:多媒体软件可能处理不可信文件,建议在隔离的 App Qube 中打开未知来源文件


在 Dom0 中使用 qvm-template 命令安装所需的基础模板:

# 列出可用的模板
qvm-template list --available

# 安装 Fedora 44 模板
qvm-template install fedora-44
qvm-template install fedora-44-minimal

# 安装 Debian 13 模板
qvm-template install debian-13
qvm-template install debian-13-minimal

为避免直接修改官方模板,建议先克隆一份:

# 克隆 Debian 13 作为开发模板
qvm-clone debian-13 tpl-dev

# 克隆 Fedora 44 作为网络 VPN 模板
qvm-clone fedora-44 tpl-network-vpn
qvm-start tpl-dev

以 dev Template 为例:

# 在 Dom0 中打开 Template 终端(没有 xterm 就换成模板里已有的终端)
qvm-run -u root tpl-dev -- xterm

在 Template 终端中:

# 更新系统
sudo apt update && sudo apt upgrade -y

# 安装开发工具
sudo apt install -y git build-essential cmake emacs vim

# 安装 VSCode(微软官方 apt 仓库)
sudo apt install -y wget gpg
wget -qO- https://packages.microsoft.com/keys/microsoft.asc | \
  gpg --dearmor > /usr/share/keyrings/microsoft.gpg
echo "deb [arch=amd64 signed-by=/usr/share/keyrings/microsoft.gpg] https://packages.microsoft.com/repos/code stable main" \
  > /etc/apt/sources.list.d/vscode.list
sudo apt update
sudo apt install -y code
# 清理缓存
sudo apt clean

# 关闭 Template
qvm-shutdown tpl-dev
# 创建开发用 App Qube
qvm-create -t tpl-dev -l blue dev-work

# 创建社交用 App Qube
qvm-create -t tpl-social -l green social-personal

# 设置网络连接(通过 sys-firewall)
qvm-prefs dev-work netvm sys-firewall
# 为 App Qube 分配更多内存
qvm-prefs dev-work memory 4096
qvm-prefs dev-work maxmem 4096

# 设置自动启动
qvm-prefs dev-work autostart true

# 设置提供网络给其他 Qube(如 VPN ProxyVM)
qvm-prefs vpn-proxy provides_network true

Template 更新和维护

# 在 Dom0 中执行
qubes-update-gui

或者使用命令行:

# 更新 Debian 模板
qvm-run -u root tpl-dev -- apt update && apt upgrade -y
qvm-run -u root debian-13 -- apt update && apt upgrade -y

# 更新 Fedora 模板
qvm-run -u root tpl-network-vpn -- dnf update -y

当新版本的 Template 可用时:

# 安装新版本(版本号按实际替换)
qvm-template install debian-13

# 克隆新模板
qvm-clone debian-13 tpl-dev-new

# 在新模板中重新安装软件
# (参考初始化流程第3步)

# 测试无误后,切换 App Qube 的模板
qvm-prefs dev-work template tpl-dev-new

# 重启 App Qube 应用更改
qvm-shutdown dev-work
qvm-start dev-work

# 确认无问题后,删除旧模板
qvm-remove tpl-dev

由于 RPM 安装的模板是"系统管理"的,不能使用 Qubes 内置备份功能备份。建议:

  1. 记录安装的软件包列表
  2. 备份配置文件
  3. 维护 Template 配置脚本

示例脚本:

#!/bin/bash
# save-template-config.sh

TEMPLATE_NAME="tpl-dev"
BACKUP_DIR="$HOME/template-configs"

mkdir -p "$BACKUP_DIR/$TEMPLATE_NAME"

# 导出已安装软件包列表
qvm-run -u root $TEMPLATE_NAME -- dpkg-query -W > "$BACKUP_DIR/$TEMPLATE_NAME/packages.txt"

# 备份重要配置文件
qvm-run -u root $TEMPLATE_NAME -- tar czf /tmp/configs.tar.gz /etc/apt/sources.list.d /etc/profile.d
qvm-run $TEMPLATE_NAME -- cat /tmp/configs.tar.gz > "$BACKUP_DIR/$TEMPLATE_NAME/configs.tar.gz"

前面用 qvm-clone、qvm-prefs 管几个模板还可以,模板一多就会重复:每加一个环境,都要重新克隆、装包、改 prefs、配 appmenus,升级时还要再来一遍。Qubes 自带 Salt,正好可以把这些步骤写成状态文件。

不过 Qubes 里的 Salt 和普通 SaltStack 不太一样:

  • dom0 是 minion,没有 master,日常用 qubesctl 而不是 salt。
  • 对 DomU 执行状态要加 --skip-dom0 --targets=<qube>,Qubes 会临时起一个管理 DispVM 来跑。
  • 自定义 pillar 在 dom0 上容易出问题:Qubes 的 topd/_tops 机制不一定认你放进 pillar_root 的 top.sls,严重时会影响整个 dom0 的 pillar 加载。

我把自己的模板配置整理成了一个仓库:qubes-salt-config。它参考了 qusal 的组织方式,不使用 pillar,所有非敏感配置都在 salt/config.jinja 里。下面以这个仓库为例,讲怎么把模板管理落到 Salt 上。

概念 在 Qubes 里的含义
state 描述某个 Qube 应该处于什么状态,比如存在、使用哪个模板、装哪些包
SLS Salt State 文件,通常一个 .sls 对应一个状态
top 把状态绑定到目标,比如“在 dom0 上执行 templates.dev.create”
qubesctl Qubes 包装过的 Salt 命令,替代 salt-call --local
管理 DispVM 执行 DomU 状态时临时创建的 qube,执行完销毁

几个常用命令:

# 查看状态会做什么,不实际执行
sudo qubesctl state.apply templates.dev.create test=True

# 在 dom0 执行
sudo qubesctl state.apply templates.dev.create

# 在指定 DomU 执行
sudo qubesctl --skip-dom0 --targets=tpl-dev state.apply templates.dev.install

# 看某个 SLS 展开后的内容
sudo qubesctl state.show_sls templates.dev.create
qubes-salt-config/
├── docs/                     # install、deploy-checklist、dev-deploy、mirror
├── minion.d/slchris.conf     # dom0 的 file_roots,指向 /srv/salt/slchris
├── salt/
│   ├── config.jinja          # 唯一的配置入口(不用 pillar)
│   ├── config.local.jinja.example
│   ├── debian-minimal/       # Debian 基础模板的 clone/create
│   ├── fedora-minimal/       # Fedora 基础模板
│   ├── templates/
│   │   ├── dev/              # 开发环境:tpl-dev、dvm-dev、dev
│   │   ├── media/            # 多媒体
│   │   ├── im/               # 即时通讯
│   │   ├── tools/            # 常用工具
│   │   ├── gpg/              # 离线 GPG
│   │   ├── vault/            # 离线密码库
│   │   ├── vpn/              # VPN 网关
│   │   ├── project-net/      # 按项目隔离的 WireGuard 网关
│   │   └── gentoo-dev/       # Gentoo 开发环境
│   ├── mgmt/                 # 管理 DispVM、镜像、remote-debug
│   └── utils/macros/         # clone_template、sync_appmenus、policy
├── scripts/
│   ├── setup.sh              # 把 salt/ 和 minion.d/ 部署到 dom0
│   ├── package.sh            # 打部署用的 tarball
│   ├── lint.sh               # yamllint + salt-lint
│   └── check_top_pairing.py  # 检查 .sls 和 .top 是否成对
└── .github/workflows/        # lint、上游版本 pin 检查、magpie bump

先在联网的 DomU 里克隆仓库,再通过 qvm-run --pass-io 送进 dom0。不要直接把来源不明的文件拷进 dom0。

# DomU
git clone https://github.com/slchris/qubes-salt-config.git ~/qubes-salt-config
# dom0:把 qube 里的仓库流式复制出来
qube="YOUR_QUBE"
mkdir -p ~/QubesIncoming/"${qube}"
qvm-run --no-gui --pass-io -- "${qube}" "tar -cf - -C ~ qubes-salt-config" | \
  tar -xf - -C ~/QubesIncoming/"${qube}"

cd ~/QubesIncoming/"${qube}"/qubes-salt-config
sudo ./scripts/setup.sh

setup.sh 会做三件事:

  1. 把 minion.d/slchris.conf 放到 /etc/salt/minion.d/,把 /srv/salt/slchris 加进 file_roots。
  2. 把 salt/ 复制到 /srv/salt/slchris/。
  3. 删除旧版本可能留下的 /srv/pillar/slchris,然后 saltutil.sync_all。

装完先确认配置在:

ls /srv/salt/slchris/config.jinja

接着安装基础模板。先确认最小模板在,顺序不能反,后面的模板都依赖它们:

# dom0:检查基础模板,缺了就装
qvm-check debian-13-minimal || sudo qubes-dom0-update --clean qubes-template-debian-13-minimal
qvm-check fedora-44-minimal || sudo qubes-dom0-update --clean qubes-template-fedora-44-minimal

sudo qubesctl state.apply debian-minimal.clone
sudo qubesctl state.apply fedora-minimal.clone

sudo qubesctl state.apply debian-minimal.create
sudo qubesctl state.apply fedora-minimal.create

然后安装管理环境。没有这一步,qubesctl --skip-dom0 --targets=... 会失败:

sudo qubesctl state.apply mgmt.create
sudo qubesctl --skip-dom0 --targets=tpl-mgmt state.apply mgmt.install

普通 Salt 习惯把配置放 pillar,但这个仓库刻意不用。原因写在 minion.d/slchris.conf 和 config.jinja 的注释里:

Qubes dom0 对自定义 pillar 的支持并不可靠。一个普通的 top.sls 放进自定义 pillar_root,不一定会被 Qubes 的 topd/_tops 机制注册,严重时会把整个 dom0 的 pillar 加载弄坏。

所以所有非敏感配置都放在 salt/config.jinja,状态文件直接 import:

{% from 'config.jinja' import cfg with context %}

{{ cfg.qvm.debian.version }}
{{ cfg.mirror.enabled }}

常见配置项:

  • cfg.qvm.debian.version / cfg.qvm.fedora.version:基础模板版本,升级时改这里。
  • cfg.mirror.enabled:是否把 Qubes 模板下载指向镜像。
  • cfg.remote_debug.*:远程调试用的 qube、SSH 端口、允许的网段和公钥。
  • cfg.mgmt.*:管理 DispVM 相关设置。

本机特有的配置放在 salt/config.local.jinja,这个文件被 .gitignore 忽略,package.sh 也不会打进去。示例:

{% set _ = cfg['hotspot'].update({'password': ''}) %}

AppVM 里的 token、密码不要写进 config.jinja。这个文件是在 dom0 渲染的,写了就是 dom0 上的明文。应用侧密钥放在单独的 sops 仓库里,在 AppVM 内解密;config.jinja 只放 dom0 需要知道的配置。

改完配置后重新部署并同步:

sudo ./scripts/setup.sh
sudo qubesctl saltutil.sync_all

仓库里已经准备好的模板:

模板 基础 创建的 Qube 说明
dev debian-13-minimal tpl-dev、dvm-dev、dev 开发环境
media debian-13-minimal tpl-media、dvm-media 多媒体播放
im debian-13-minimal tpl-im、im 即时通讯
tools debian-13-minimal tpl-tools、dvm-tools 常用工具
gpg debian-13-minimal tpl-gpg、gpg 离线 GPG
vault debian-13-minimal tpl-vault、vault 离线密码库
vpn fedora-44-minimal tpl-vpn、sys-vpn VPN 网关
project-net debian-13-minimal tpl-project-net、sys-project-net 按项目隔离的 WireGuard 网关
gentoo-dev gentoo-xfce tpl-gentoo-dev、gentoo-dev Gentoo 开发

前面手动示例里的 dev 模板用的是 Fedora;仓库里的 dev 基于 Debian 13-minimal,装包用 apt。基础模板可以按自己的习惯换,state 的结构不变。

创建开发环境:

sudo qubesctl state.apply templates.dev.create
sudo qubesctl --skip-dom0 --targets=tpl-dev state.apply templates.dev.install
sudo qubesctl --skip-dom0 --targets=dev state.apply templates.dev.configure

GPG 和 vault 是离线的,状态里把 netvm 设为空:

sudo qubesctl state.apply templates.gpg.create
sudo qubesctl --skip-dom0 --targets=tpl-gpg state.apply templates.gpg.install

sudo qubesctl state.apply templates.vault.create
sudo qubesctl --skip-dom0 --targets=tpl-vault state.apply templates.vault.install

VPN 网关:

sudo qubesctl state.apply templates.vpn.create
sudo qubesctl --skip-dom0 --targets=tpl-vpn state.apply templates.vpn.install
sudo qubesctl --skip-dom0 --targets=sys-vpn state.apply templates.vpn.configure

以 templates/dev 为例,目录里通常是这几个文件:

salt/templates/dev/
├── clone.sls      # 从 debian-minimal 克隆 tpl-dev
├── create.sls     # 创建 tpl-dev、dvm-dev、dev
├── create.top     # 把 create.sls 绑定到 dom0
├── install.sls    # 在 tpl-dev 里装包
├── install.top    # 把 install.sls 绑定到 tpl-dev
├── configure.sls  # 配置 dev/dvm-dev
└── configure.top

clone.sls 用 clone_template 宏,把基础模板克隆成 tpl-dev:

{% from 'utils/macros/clone-template.sls' import clone_template -%}
{% set name = slsdotpath.split('.')[-1] -%}
{{ clone_template('debian-minimal', name) }}

create.sls 用 slsdotpath 和 load_yaml 生成 Qube。slsdotpath 是当前状态的路径,比如这里是 templates.dev,所以 name 取最后一段就是 dev:

{%- from "qvm/template.jinja" import load -%}
{% set name = slsdotpath.split('.')[-1] %}

{% load_yaml as defaults -%}
name: tpl-{{ name }}
force: True
require:
- sls: {{ slsdotpath }}.clone
prefs:
- label: green
- vcpus: 4
- memory: 400
- maxmem: 8000
{%- endload %}
{{ load(defaults) }}

create.top 把状态绑到 dom0:

base:
  'dom0':
    - match: nodegroup
    - templates.dev.create

install.top 绑到模板:

base:
  'tpl-dev':
    - templates.dev.install

新加模板时,照着复制一个目录,改名称、基础模板、包列表和 prefs,然后跑仓库自带的检查:

./scripts/lint.sh
python3 scripts/check_top_pairing.py

日常改配置不需要每次都走 git clone。仓库提供了两种方式:

在 DomU 里改,推送到 dom0:适合正式发布,docs/install.md 里有完整步骤。

本地开发循环:在开发机上改,scripts/package.sh 打包,通过局域网 HTTP 送到 transfer qube,再复制进 dom0。docs/dev-deploy.md 有详细流程:

# 开发机
./scripts/package.sh dev
cd dist && python3 -m http.server 8000

# transfer qube
wget http://<dev-ip>:8000/qubes-salt-config-dev.tar.gz
tar -xzf qubes-salt-config-dev.tar.gz

# dom0
qvm-run --no-gui --pass-io -- "dev" "tar -cf - -C ~ qubes-salt-config" | \
  tar -xf - -C ~/QubesIncoming/dev
cd ~/QubesIncoming/dev/qubes-salt-config
sudo ./scripts/setup.sh

更新模板时,先改 salt/config.jinja 里的版本,再重跑对应的 create 状态。旧的模板建议先改名加 -old,验证新模板没问题再删:

sudo qubesctl state.apply templates.dev.create
sudo qubesctl --skip-dom0 --targets=tpl-dev state.apply templates.dev.install

更完整的升级步骤在 docs/install.md 的 Template Upgrade 一节。

state.apply 找不到状态:确认 setup.sh 跑过,并且 /srv/salt/slchris/ 下有对应的 .sls。minion.d/slchris.conf 只把 /srv/salt/slchris 加进 file_roots,所以调用时不用写 slchris. 前缀。

--skip-dom0 执行失败:先确认 mgmt.create 和 tpl-mgmt 的 mgmt.install 都跑过。Qubes 需要管理 DispVM 才能进 DomU 执行状态。

pillar 相关报错:这个仓库不用 pillar。如果从旧版本升级过来,setup.sh 会删掉 /srv/pillar/slchris;不要手动再建一个 top.sls 放进去。

模板下载慢:docs/mirror.md 里有把 Qubes 模板下载指向镜像的说明,也可以用 scripts/qubes-mirror.sh。这是可选项,不影响 Salt 本身。

先看状态会做什么:

sudo qubesctl state.apply templates.dev.create test=True
sudo qubesctl state.show_sls templates.dev.create
sudo qubesctl --skip-dom0 --targets=tpl-dev state.apply templates.dev.install test=True

Qubes OS 的 Template 系统把“根文件系统”和“用户数据”分开,模板只读、App Qube 可写,这是隔离和快速创建的基础。手动管理几个模板没问题,但模板一多,还是要落到 Salt 上。

这篇的路线是:

  1. 先理解 Template、App Qube、Disposable 和 Dom0 的关系。
  2. 按用途和信任级别规划模板,不要把开发、社交、GPG 混在一个模板里。
  3. 用 qvm-clone / qvm-prefs 手动跑通一个模板,确认软件和配置没问题。
  4. 把同样的步骤写成 Salt 状态;模板多了之后,用 qubes-salt-config 这种仓库化管理,配置、模板、策略和部署脚本都进 Git。
  5. 升级时改配置里的版本、重跑状态,先验证新模板再删旧的。