DEV Community

Cover image for Designing a Restore-First Desktop Backup Tool Around Restic (From Bash Script to Qt/C++)
Ashraf Mehdaly
Ashraf Mehdaly

Posted on

Designing a Restore-First Desktop Backup Tool Around Restic (From Bash Script to Qt/C++)

Reflections on trust, restore safety, threading, and building a desktop orchestration layer around a CLI engine.

Backups Are Easy. Restores Are Not.

Taking backups is usually simple.

Restoring them — confidently, correctly, and without hesitation — is where anxiety begins.

Over time, I realized that most tools are designed around creating backups. Very few are designed around restoring safely. And psychologically, restore confidence is what really matters.

This realization is what led me from a small Bash script wrapped in KDialog to a full multithreaded Qt/C++ desktop application built around restic.


Phase 1: The Script That Solved One Problem

The first version of this project was nothing more than:

  • A Bash script
  • Hardcoded include paths
  • restic CLI calls
  • KDialog progress dialogs

It removed repetitive typing. That was enough — for a while.

But as I started using restic to manage configuration state (Docker stacks, system configs, reverse proxies, etc.), new requirements emerged:

  • Multiple structured backup definitions
  • Clean separation between “what to back up” and “how to execute”
  • Safer restore workflows
  • Snapshot comparison
  • Elevated execution handling
  • Persistence beyond flat config files

The script had reached its natural limits.


The Philosophy: Orchestrator, Not Engine

One rule shaped everything that followed:

Restic remains the engine. The application must remain an orchestrator.

I did not want:

  • A custom backup format
  • A metadata layer that breaks CLI compatibility
  • Tight coupling to restic internals
  • A forked engine

Repositories remain 100% standard restic repositories.

You can always use the CLI directly.

This separation is intentional. It preserves transparency and trust.


Why Qt/C++?

The move to Qt Widgets + C++ was pragmatic:

  • Cross-platform (Linux + Windows)
  • Strong threading primitives
  • Clean process management (QProcess)
  • Mature event system
  • Deterministic memory ownership

This was not about building something flashy.

It was about building something predictable.

Backup software should feel boring — in the best possible way.


Threading and Process Isolation

One non-negotiable requirement:

The UI must never block during backup or restore operations.

Architecture:

UI Thread

→ Operation Controller

→ Worker Object (QObject-based)

→ QThread

→ QProcess (restic invocation)

Key principles:

  • Restic runs as an external process.
  • Output streams are parsed asynchronously.
  • No nested event loops.
  • No UI updates from worker threads.
  • Strict signal-slot boundaries.

This preserves responsiveness and reduces risk of undefined state during long operations.

The application does not embed restic. It orchestrates it.


Profiles as “State Definitions”

The biggest conceptual shift from the Bash prototype was introducing structured profiles.

A profile defines:

  • Repository path
  • Multiple include directories
  • Selective file includes
  • Glob-based exclude patterns
  • Pre/Post scripts
  • Elevation requirements
  • Default restore behavior

Instead of thinking in terms of “run backup command”, I started thinking in terms of:

Define the state of a machine.

For example:

Profile: docker-host-01

Includes:

  • /etc/docker
  • /srv/docker-compose
  • /etc/nginx
  • specific .env files

Excludes:

  • /logs/
  • /cache/
  • transient runtime directories

This allows treating configuration as versioned infrastructure.


Restore Is a First-Class Citizen

Most CLI usage patterns focus on restic backup.

I chose to design the UI around restore semantics.

Two restore modes emerged:

Mirror Mode

The target directory becomes identical to the snapshot.

Useful for:

  • Rebuilding a machine
  • Migrating to a clean VM
  • Rehydrating configuration state

Update Mode

Only overwrite files that exist in the snapshot.

Useful for:

  • Partial correction
  • Non-destructive repair
  • Controlled restoration

Internally, restore operations are staged and validated before final application.

Restore must feel deliberate — not destructive.


Snapshot Diff and File-Level Diff

Restic already tracks content efficiently.

But understanding what changed is equally important.

The application can:

  • Compare two snapshots
  • Identify added/modified/deleted files
  • Extract specific file versions
  • Perform file-level diff analysis

This turns backups into something closer to version history.

For configuration-heavy environments, this visibility matters.


Elevated Execution and Hooks

System paths often require root/admin access.

The application supports:

  • Explicit elevated execution
  • Controlled privilege escalation
  • Pre/Post scripts per profile

Examples:

  • Stop Docker before backup
  • Dump a database before snapshot
  • Restart services after restore

These are not advanced features — they are practical realities in homelab and system environments.

Automation without context can be dangerous.

Controlled hooks provide flexibility without losing structure.


Persistence and Portability

SQLite is used for:

  • Profile definitions
  • Operation history
  • Application settings

Import/Export allows moving profile definitions between machines.

The repository remains external and portable.

The application’s state is separate from backup data.

Again — boundaries matter.


Build System and Determinism

The project uses CMake with clear target separation:

  • Core execution module
  • Worker module
  • UI module
  • Database layer

Shared library outputs are controlled explicitly.
No versioned SONAME chains where unnecessary.
Deployment artifacts are predictable.

Backup tooling should not surprise the user — including at build time.


A Calm Conclusion

This project evolved gradually:

Bash script → structured orchestration → multi-threaded Qt desktop application.

The most important insight was not about threading or CMake.

It was this:

Backup software is about confidence.

Confidence comes from:

  • Predictable behavior
  • Clear boundaries
  • Restore safety
  • Transparency
  • Compatibility

Restic remains the engine.

The desktop layer exists to improve workflow — not to obscure it.

If there is one architectural lesson here, it is this:

When wrapping a CLI tool with a GUI, preserve the engine’s integrity — and design around the human moment of restore.


Project Reference

I packaged this into a desktop application called EasyBackupManager (EBM).

If you're curious to explore it, see screenshots, or try the Windows/Linux builds which is completely free, you can find it here:

https://easybackupmanager.com

The repositories remain fully standard restic repositories and are always accessible via the CLI.

Top comments (0)