YASA - Yet Another Salt Alternative (incremental searchable backups & enforceable declarative architecture)

[Nb: Not sure if this fits in Guides or General Discussion]


Lack of incremental searchable backups is the final hurdle that prevents me from using QubesOS as a daily driver.

I experimented with the usual suspects (Salt, Ansible, Wyng) but found that they were either incredibly rigid, lacked some basic functionalities, or required linux-wizard level to use (personal opinion: decent backup solution should be available to all users regardless of their technical level, and without reading through 10 pages of docs).

I ended creating my own tool YASA - Yet Another Salt Alternative (source on Codeberg).


Disclaimer: I’m not here to diss on the existing backup tools. Other contributors have clearly spent an important amount of time building them up, and I’m sure they work great for their use case - unfortunately they absolutely didn’t fit my own expectations. I’m not claiming YASA is objectively better than something like Salt; to me, though, it’s a much smoother experience, and I hope it can help others in the same boat.


Feel free to browse through the README, docs and examples on the repository.

Here’s a quick summary:

  • YASA is a python3 framework that runs on “base” dom0 (no dependencies)
  • Since it’s python, you don’t need to learn a new language. Also, it’s very flexible, if you don’t like my interface you can just write your own wrappers. You can very easily extend the code to fit your own use cases.
  • From the “Salt” side, YASA enables to declaratively define Qubes’s “architecture” (e.g. anything but their volume’s data), as well as run commands and install packages in templates.
  • YASA also enables easy incremental, searchable backups using Kopia. Since the “architecture” is declaratively defined, only the AppVM’s /home needs to be backed-up.

A few examples:

Declaratively define the “architecture”:

def t_signal():
    """Signal-desktop template"""
    base = "debian-13-minimal"
    tname = "T-signal"
    misc.enforce_template_exists(tname, base, "black")

    with misc.ctx_vm_running(tname):
        debian.config_minimal_network(tname)

        cmds = """
if ! command -v signal-desktop &> /dev/null; then
    wget -O- https://updates.signal.org/desktop/apt/keys.asc | gpg --dearmor > signal-desktop-keyring.gpg
    cat signal-desktop-keyring.gpg | sudo tee /usr/share/keyrings/signal-desktop-keyring.gpg > /dev/null
    wget -O signal-desktop.sources https://updates.signal.org/static/desktop/apt/signal-desktop.sources
    cat signal-desktop.sources | sudo tee /etc/apt/sources.list.d/signal-desktop.sources > /dev/nul
fi
"""
        misc.run_commands(tname, cmds, proxy=True)
        debian.install_packages(tname, ["signal-desktop"], update=True)

t_signal()

misc.enforce_vm(
        "personal-signal",
        "T-signal",
        "yellow",
        {"features": {"menu-items": "signal-desktop.desktop debian-xterm.desktop"}},
)

Perform backups:

# Backup a single AppVM
kopia_repo = backup.KopiaRepo(source="dom0:sdc1", path="kopia")
backup.kopia_backup_vm("MyVM", kopia_repo)
# -- wipe vm's data --
backup.kopia_restore_vm("MyVM", kopia_repo)

# Backup all AppVMs
backup.backup_all_appvms(kopia_repo)

# Functional non-destructive test: restore to a new "cloned" (except data) AppVM, then manually check that the data was restored
backup.test_backup_and_restore_vm_to_Z("MyVM", kopia_repo)

Note: This is obviously early-development:

  • I am a QubesOS newcomer
  • I have only tested it on my specific configuration (nb: running from a USB), and am likely missing many edge cases
  • Expect bugs
  • Documentation is far from prefect, but it should be reasonably understandable

I welcome any kind of feedback (and of course contributions).

3 Likes