Skip to content

QubesOS Salt

Drew Daniels edited this page Oct 6, 2026 · 2 revisions

QubesOS Salt

Sketch for using Salt (via qubesctl in dom0) to automate template VM setup and qube configuration that currently requires manual steps.

What Salt Can Automate

Salt runs from dom0 and can manage both dom0 configuration and template VM contents. This covers the manual steps in the QubesOS wiki page that happen before chezmoi init --apply runs in the personal AppVM.

Qube creation and configuration

Currently manual (Qube Manager clicks + CLI):

# /srv/salt/qubes/personal-template.sls

# Clone fedora-42-xfce into fedora-42-xfce-clone-1
fedora-42-xfce-clone-1:
  qvm.clone:
    - source: fedora-42-xfce

# Set disk size on the template
fedora-42-xfce-clone-1-prefs:
  qvm.prefs:
    - name: fedora-42-xfce-clone-1
    - maxmem: 0
    - disk: 20G

# Configure personal qube
personal:
  qvm.prefs:
    - template: fedora-42-xfce-clone-1
    - disk: 20G
# /srv/salt/qubes/whonix-clones.sls

whonix-gateway-17-clone-1:
  qvm.clone:
    - source: whonix-gateway-17

whonix-workstation-17-clone-1:
  qvm.clone:
    - source: whonix-workstation-17

sys-whonix-template:
  qvm.prefs:
    - name: sys-whonix
    - template: whonix-gateway-17-clone-1

anon-whonix-template:
  qvm.prefs:
    - name: anon-whonix
    - template: whonix-workstation-17-clone-1

Template VM package installation

Salt can install packages from dom0 without granting the template permanent network access (Salt uses qrexec, not the network):

# /srv/salt/template-packages.sls

install-dev-packages:
  pkg.installed:
    - pkgs:
      # Core CLI tools
      - wget
      - jq
      - ripgrep
      - fd-find
      - bat
      - fzf
      - git-delta
      - zoxide
      - yq
      - du-dust
      - tree-sitter
      - hexyl
      - lsd
      - pandoc
      - difftastic
      - plocate
      # Shell & prompt
      - fish
      - shfmt
      - ShellCheck
      # Editor & dev tools
      - neovim
      - tmux
      - ctags
      - direnv
      - gitleaks
      - automake
      - clang-tools-extra
      # Git tools
      - git-credential-oauth
      - gh
      - glab
      # Language runtimes & build tools
      - rust-analyzer
      - compat-lua
      - luarocks
      - ImageMagick
      - ImageMagick-devel
      # Desktop & window management
      - rofi
      - bemenu
      - feh
      - qt6ct
      - xclip
      - chafa
      - foliate
      # Web & chat
      - weechat
      # Misc
      - dnf-plugins-core

COPR packages

# /srv/salt/template-copr.sls

yazi-copr:
  cmd.run:
    - name: dnf copr enable -y lihaohong/yazi
    - unless: dnf repolist | grep -q lihaohong-yazi

yazi-install:
  pkg.installed:
    - name: yazi
    - require:
      - cmd: yazi-copr

ghostty-copr:
  cmd.run:
    - name: dnf copr enable -y scottames/ghostty
    - unless: dnf repolist | grep -q scottames-ghostty

ghostty-install:
  pkg.installed:
    - name: ghostty
    - require:
      - cmd: ghostty-copr

chezmoi with cosign verification

# /srv/salt/template-chezmoi.sls

chezmoi-install:
  cmd.script:
    - name: salt://scripts/install-chezmoi.sh
    - unless: which chezmoi

Where install-chezmoi.sh is the existing verification script (cosign + checksum) placed in /srv/salt/scripts/.

cosign

# /srv/salt/template-cosign.sls

cosign-install:
  cmd.run:
    - name: |
        LATEST_VERSION=$(curl https://api.github.com/repos/sigstore/cosign/releases/latest | grep tag_name | cut -d : -f2 | tr -d "v\", ")
        curl -O -L "https://github.com/sigstore/cosign/releases/latest/download/cosign-${LATEST_VERSION}-1.x86_64.rpm"
        rpm -ivh cosign-"${LATEST_VERSION}"-1.x86_64.rpm
    - unless: which cosign

Top file

# /srv/salt/personal-setup.top

base:
  fedora-42-xfce-clone-1:
    - template-packages
    - template-copr
    - template-cosign
    - template-chezmoi
  dom0:
    - qubes.personal-template
    - qubes.whonix-clones

How to apply

From dom0:

# Create qubes and set preferences (dom0 only)
sudo qubesctl state.apply

# Install packages in the template
sudo qubesctl --targets=fedora-42-xfce-clone-1 state.apply

# Or do everything at once
sudo qubesctl --all state.apply

What stays manual

Some things can't or shouldn't be automated via Salt:

  • electrum-ltc - requires interactive GPG key trust decisions
  • Whonix configuration - systemcheck, repository-dist, upgrade-nonroot are interactive
  • Monero wallet isolation - complex multi-qube setup with GPG verification and manual trust decisions
  • i3 in dom0 - qubes-dom0-update i3 i3-settings-qubes could be Salt, but the initial config generation is interactive
  • Clipboard shortcuts - GUI-only setting in Qube Manager

Relationship to Chezmoi

dom0 (Salt)
  |
  +-- manages qube creation, template cloning, qube preferences
  +-- manages template VM packages (dnf, COPR, cosign, chezmoi)
  |
  v
personal AppVM (chezmoi hooks)
  |
  +-- manages user-level software (rustup, cargo crates, LSPs)
  +-- manages dotfile-adjacent tools (debug adapters, feeds)
  +-- manages dotfiles (chezmoi's core job)

Salt handles the infrastructure layer (qubes + template contents). Chezmoi handles the user layer (everything in $HOME inside the personal AppVM). They don't overlap.

File layout in dotfiles repo

Salt states live in the chezmoi source dir:

~/.local/share/chezmoi/
  salt/             # copied to /srv/salt in dom0
    qubes/
      personal-template.sls
      whonix-clones.sls
    template-packages.sls
    template-copr.sls
    template-cosign.sls
    template-chezmoi.sls
    scripts/
      install-chezmoi.sh
    personal-setup.top

To deploy, copy the salt states into dom0:

# From dom0
qvm-copy-to-vm dom0 /home/user/.local/share/chezmoi/salt/
# Then move to /srv/salt/
sudo cp -r ~/QubesIncoming/personal/salt/* /srv/salt/
sudo qubesctl top.enable personal-setup
sudo qubesctl --all state.apply

Clone this wiki locally