Salt Formulas for Qubes OS.
This project provides SaltStack Formulas for Qubes OS users to automate system configuration and management tasks. Based on patterns from qusal, it provides a clean, modular approach to Qubes configuration management.
Note: Files are deployed to /srv/salt/slchris/ and the minion config sets this as the ONLY file_root. This means you don't need a prefix - just run qubesctl state.apply debian-minimal.clone (same approach as qusal).
Features:
- Qusal-style patterns: Uses
slsdotpath,clone_templatemacro, andload_yamlfor clean state definitions - Pre-configured templates: Development, Multimedia, IM, Tools, GPG, Vault, VPN
- Single config file: all user settings in
salt/config.jinja(no pillar) - Modular design: Apply only what you need
- Reusable macros: Clone templates, sync appmenus, manage policies
See the detailed installation instructions, or the deployment checklist for an ordered, check-off guide from a fresh machine to templates + management + remote debug.
For a fast local development loop (package on your dev machine → push over the LAN → deploy to dom0), see the local development deploy guide.
If template downloads are slow or stall (e.g. far from the ITL CDN), you can optionally point Qubes at a faster mirror — see the mirror guide.
# 1. Clone in a DomU qube
git clone https://github.com/slchris/qubes-salt-config.git ~/qubes-salt-config
# 2. Copy to Dom0
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}"
# 3. Run setup
cd ~/QubesIncoming/"${qube}"/qubes-salt-config
sudo ./scripts/setup.sh
# 4. Install base templates (REQUIRED FIRST)
sudo qubesctl state.apply debian-minimal.clone
sudo qubesctl state.apply fedora-minimal.clone
# 5. Create base DVM templates
sudo qubesctl state.apply debian-minimal.create
sudo qubesctl state.apply fedora-minimal.create| Template | Base | Description | Qubes Created |
|---|---|---|---|
| dev | debian-13-minimal | Development environment | tpl-dev, dvm-dev, dev |
| media | debian-13-minimal | Multimedia playback | tpl-media, dvm-media |
| im | debian-13-minimal | Instant messaging | tpl-im, im |
| tools | debian-13-minimal | General utilities | tpl-tools, dvm-tools |
| gpg | debian-13-minimal | GPG key management (offline) | tpl-gpg, gpg |
| vault | debian-13-minimal | Password management (offline) | tpl-vault, vault |
| vpn | fedora-43-minimal | VPN gateway | tpl-vpn, sys-vpn |
| project-net | debian-13-minimal | Per-project WireGuard gateway (fail-closed) | tpl-project-net, sys-project-net |
| mcp | debian-13-minimal | MCP server & AI app development | tpl-mcp, dvm-mcp, mcp |
| ai | debian-13-minimal | Claude Desktop + agents behind project VPN | tpl-ai, dvm-ai, ai |
| gentoo-dev | gentoo-xfce | Ebuild/package development | tpl-gentoo-dev, gentoo-dev |
Note: gentoo-dev requires a Gentoo base template, which Qubes does not ship
ready-to-clone. See salt/gentoo for how to build it with
qubes-builder before applying gentoo-dev.
# Create the dev qubes (in dom0)
sudo qubesctl state.apply templates.dev.create
# Install packages in template
sudo qubesctl --skip-dom0 --targets=tpl-dev state.apply templates.dev.install
# Configure with your dotfiles (optional)
sudo qubesctl --skip-dom0 --targets=dev state.apply templates.dev.configure# GPG key management (no network)
sudo qubesctl state.apply templates.gpg.create
sudo qubesctl --skip-dom0 --targets=tpl-gpg state.apply templates.gpg.install
# Password vault (no network)
sudo qubesctl state.apply templates.vault.create
sudo qubesctl --skip-dom0 --targets=tpl-vault state.apply templates.vault.installsudo 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# Sync all salt modules
sudo qubesctl saltutil.sync_all
# Test mode (dry run)
sudo qubesctl state.apply templates.dev.create test=True
# Edit configuration (no pillar — config is in config.jinja)
sudo vim /srv/salt/slchris/config.jinja
# Package for deployment
./scripts/package.sh v1.0.0salt/
debian/ # Debian base template
template.jinja # Version from config.jinja
clone.sls # Install from repo
debian-minimal/ # Debian minimal base
template.jinja # Version from config.jinja
clone.sls # Install from repo
create.sls # Create base DVM
fedora/ # Fedora base template
template.jinja # Version from config.jinja
clone.sls # Install from repo
fedora-minimal/ # Fedora minimal base
template.jinja # Version from config.jinja
clone.sls # Install from repo
create.sls # Create base DVM
templates/ # Pre-configured templates
dev/ # Development
clone.sls # Uses clone_template macro
create.sls # Uses slsdotpath and load_yaml
install.sls # Package installation
media/ # Multimedia
im/ # Instant messaging
tools/ # General utilities
gpg/ # GPG (offline)
vault/ # Password (offline)
vpn/ # VPN gateway
utils/
macros/
clone-template.sls # Template cloning macro
update-admin.sls # Admin VM update macro
sync-appmenus.sls # Appmenu sync macro
policy.sls # RPC policy macros
dotfiles/ # User dotfiles
config.jinja # All user configuration (no pillar)
scripts/
setup.sh # Setup script for dom0
package.sh # Package for deployment
qubes-mirror.sh # Optional: point Qubes at a download mirror
lint.sh # Run yamllint + salt-lint locally
check_top_pairing.py # Enforce .sls <-> .top pairing
docs/
install.md # Detailed installation guide
deploy-checklist.md # Ordered deploy checklist (fresh machine -> everything)
dev-deploy.md # Local dev loop: package -> LAN -> dom0
mirror.md # Optional download mirror (TUNA / kernel.org)
remote-debug.md # SSH-driven debugging via a jump qube
All state files use slsdotpath to reference themselves dynamically:
include:
- {{ slsdotpath }}.clone
{% load_yaml as defaults -%}
name: tpl-{{ slsdotpath }}
...Simplifies template cloning with the clone_template macro:
{% from 'utils/macros/clone-template.sls' import clone_template -%}
{{ clone_template('debian-minimal', sls_path) }}Uses load_yaml and qvm/template.jinja for clean qube definitions:
{%- from "qvm/template.jinja" import load -%}
{% load_yaml as defaults -%}
name: tpl-{{ slsdotpath }}
force: True
require:
- sls: {{ slsdotpath }}.clone
prefs:
- label: green
- memory: 400
{%- endload %}
{{ load(defaults) }}Template versions are configured in salt/config.jinja under cfg.qvm:
"qvm": {
"debian": {"version": "13"}, # Change to upgrade (e.g., "14")
"fedora": {"version": "43"}, # Change to upgrade (e.g., "44")
},After changing versions:
- Rename existing templates in Qube Manager (add
-oldsuffix) - Rerun formulas:
sudo qubesctl state.apply templates.dev.create(config.jinja is read at apply time — no pillar refresh needed)
See Template Upgrade Guide for details.
This project is based on patterns from qusal by Benjamin Grande and the Qubes OS SaltStack community.
Project repository: github.com/slchris/qubes-salt-config
SPDX-License-Identifier: MIT
Copyright 2026 Chris Su