From .restic.sh to .rustic.sh: Adopting Native rustic Profiles and Fixing Silent Traps
How I finally retired the legacy ~/.restic.sh script in favor of ~/.rustic.sh using native TOML profiles, config inheritance, a single backup command per repository, silent exclusion fixes with as-path, and a dual disaster-recovery guide for both rustic and restic.
In the previous post, My new backup routine with rustic, I talked about swapping out the engine of my backup routine — which originally started in My backup routine with restic — moving from restic to the Rust-based rustic.
There was just one thing still nagging at me every time I opened a terminal or checked crontab -e: I had replaced the engine, but kept the entire legacy restic bodywork intact. The script still lived at ~/.restic.sh, fired off four separate backup commands in a row with dozens of repeated CLI flags, and carried over exclusion patterns untouched on autopilot.
Today I finally paid down that technical debt. I migrated ~/.restic.sh to ~/.rustic.sh, adopted rustic's native TOML configuration profiles, and uncovered a few silent traps along the way that could have wasted cloud storage or caused nasty surprises during a real restore.
Why move from repeated CLI flags to native profiles?
When you merely swap restic for rustic inside an old shell script, you leave most of the ergonomics and efficiency gains of rustic's configuration files and profiles on the table.
Here is why the upgrade was worth doing:
- No more legacy name confusion in cron: Keeping a
rusticroutine named~/.restic.shonly made sense during the initial transition. Renaming it and updatingcrontab -eto~/.rustic.shkeeps the setup honest. - A single base profile with inheritance (
use-profiles): Instead of duplicating the password file path, retention rules (keep-daily,keep-weekly,keep-monthly,keep-tags,prune), exclusions, and source lists inside Bash loops for every repository, shared settings live in/etc/rustic/base.toml. Bothcloud.toml(plusrcloneretryandtimeoutoptions) andlocal.tomlsimply inherit frombaseand declare their repository location. - Shared visibility for both
rootand regular users: Because scheduled backups run viasudo, placing the profiles under/etc/rustic/with644permissions guarantees that cron runs and ad-hoc manual commands read the exact same definitions. - Four backups become one per repository: By defining
[[backup.snapshots]]blocks in TOML, a singlesudo rustic -P cloud backupopens the remote repository index once, starts the Rclone transport once, and creates all four snapshots sequentially — cutting overhead and connection churn. - Much shorter everyday CLI commands: No more typing long
--repoor--password-fileflags in the terminal. Withrustic.tomlpointing tolocal, runningsudo rustic snapshotsqueries the local NAS repository directly, andsudo rustic -P cloud snapshotschecks the cloud copy.
Technical gains and traps uncovered along the way
Rewriting the script forced me to test every piece against temporary repositories and inspect older snapshots. Four findings made the whole effort pay off immediately.
1. The silent !./var/tmp/* glob bug and the cleanliness of as-path = "/"
In the old script, backing up the root filesystem via a read-only Btrfs snapshot meant cd /.snap_root followed by rustic backup . with exclusions like --glob "!./tmp/*" and --glob "!./var/tmp/*".
When I inspected an earlier system snapshot with rustic cat tree, I got a surprise: files inside /var/tmp — including service scratch directories and even temporary Docker export files — were actually being backed up. That leading ./ prefix simply did not match the way I assumed in rustic.
After checking rustic's file exclusion documentation and testing in a scratch repository, the native fix turned out much cleaner:
- Point
sources = ["/.snap_root"]directly; - Set
as-path = "/"so snapshots store/as their canonical path instead of the temporary/.snap_rootmount prefix; - Anchor exclusion globs explicitly to the source directory:
"!/.snap_root/tmp/*","!/.snap_root/var/tmp/*","!/.snap_root/timeshift".
In practice, empty /tmp and /var/tmp directories are still preserved for clean restores, temporary scratch files stay out, and new snapshots no longer require moving files out of /.snap_root/* after restoring.
2. Profile globs in [[backup.snapshots]] replace rather than append
This subtlety is documented in the official config/full.toml reference, and it is easy to miss: when a [[backup.snapshots]] block omits globs or exclude-if-present, it inherits them from [backup]. However, once a snapshot block defines its own globs = [...], it replaces [backup].globs instead of appending to it.
That is why personal-data and sync-folder explicitly repeat "!**/.cache" alongside exclude-if-present = ["CACHEDIR.TAG", ".nobackup"] inside their snapshot blocks. Inspecting the merged config via rustic show-config verified this before any live run touched production backups.
3. Execution hygiene, trap EXIT, and honest notifications
If a backup is interrupted mid-flight, a leftover read-only Btrfs snapshot at /.snap_root or a stale /var/tmp/docker_bkp_temp directory can linger on disk. To harden the workflow:
- I added
trap cleanup_temps EXIT, guaranteeing automatic cleanup on exit — even on failures or validation runs; - I also run
cleanup_tempsright before creating a fresh/.snap_rootsnapshot; - I replaced shell redirections paired with
sudo(sudo cmd > file, which break outside a root shell) with| sudo tee ... > /dev/nullandmapfile -tfor Docker container and image IDs; - I added an
ERRORScounter so the ntfy success ping only fires whenERRORS -eq 0— eliminating false "Backup Completed" notifications if an earlier stage failed; - I added three maintenance flags:
-n | --dry-run,--only-profiles(regenerates/etc/rustic/*.tomlwithout running backups), and--validate(exercises Btrfs snapshot creation, Docker metadata capture, profile loading forlocalandcloud,--dry-runbackup and forget, and automatic cleanup viatrap EXIT).
Note for anyone exploring further:
rusticalso supports native hooks inside TOML profiles (hooks.backup-before,hooks.backup-after), though I chose to keep Btrfs snapshotting and Docker exports in the Bash wrapper so both repositories (cloudandlocal) share a single preparation and cleanup cycle while keeping validation modes andntfyerror reporting straightforward.
4. The restore syntax trap between rustic and restic (plus Btrfs subvolumes)
One of the best things about rustic is repository-format compatibility with the restic design specification. In an emergency, you can recover your machine using either rustic or restic, whichever is easier to grab on a Live USB.
There was, however, a dangerous trap lurking in the old disaster-recovery notes at the bottom of my script: a quick search-and-replace from earlier had produced rustic restore latest --tag ... --target ..., which is invalid syntax. The flags differ between the two tools:
- In rustic (
restore): tag filtering uses--filter-tags "<tag>", and the destination path is positional (rustic restore latest --filter-tags "root-system" /mnt/@). - In restic (
restore): tag filtering uses--tag "<tag>"with--target <dest>(restic restore latest --tag "root-system" --target /mnt/@).
Furthermore, on Manjaro using the default Btrfs subvolume layout (@ for / and @home for /home), recreating @ from a Live USB requires mounting subvol=@home onto /mnt/@/home before restoring personal data — otherwise /home files land inside @ instead of @home. Both complete recovery paths are now documented side-by-side in the script itself.
How the profiles are structured under /etc/rustic
The script automatically generates and keeps four TOML files synced in /etc/rustic/ (shown below with anonymized paths, remotes, topics, and host details):
# /etc/rustic/base.toml [global] log-level = "info" progress-interval = "1min" group-by = "host,label,paths" [repository] password-file = "/secure/path/.password.txt" [backup] exclude-if-present = ["CACHEDIR.TAG", ".nobackup"] globs = ["!**/.cache"] [[backup.snapshots]] name = "root-system" sources = ["/.snap_root"] as-path = "/" tags = ["root-system"] one-file-system = true globs = [ "!/.snap_root/tmp/*", "!/.snap_root/var/tmp/*", "!/.snap_root/timeshift", ] [[backup.snapshots]] name = "sync-folder" sources = ["/home/user/Sync"] tags = ["sync-folder"] exclude-if-present = ["CACHEDIR.TAG", ".nobackup"] globs = ["!**/.cache"] [[backup.snapshots]] name = "personal-data" sources = ["/home", "/data"] tags = ["personal-data"] exclude-if-present = ["CACHEDIR.TAG", ".nobackup"] globs = [ "!/data/backup", "!**/.cache", ] [[backup.snapshots]] name = "docker-data" sources = ["/var/lib/docker/volumes", "/var/tmp/docker_bkp_temp"] tags = ["docker-data"] [forget] keep-daily = 7 keep-weekly = 2 keep-monthly = 1 keep-tags = ["keep"] prune = true
And the repository-specific profiles stay clean and concise:
# /etc/rustic/cloud.toml [global] use-profiles = ["base"] [repository] repository = "rclone:my_remote:backup/desktop" [repository.options] retry = "default" timeout = "10min"
# /etc/rustic/local.toml [global] use-profiles = ["base"] [repository] repository = "/mnt/nas/backup"
# /etc/rustic/rustic.toml [global] use-profiles = ["local"]
The full ~/.rustic.sh script and crontab -e
Here is the complete updated script (kept alongside an identical synced copy, with real paths, remote names, notification endpoints, and secrets anonymized for publication):
#!/usr/bin/env bash # ============================================================================== # FULL BACKUP SCRIPT (SYSTEM + DATA + DOCKER) - RUSTIC (PROFILES) & BTRFS # ============================================================================== set -u LOG_FILE="/home/user/rustic_backup_completo.log" PASSWORD_FILE="/secure/path/.password.txt" SYNC_DIR="/home/user/Sync" NTFY_URL="https://ntfy.example.work/my-secret-topic" DOCKER_BKP_DIR="/var/tmp/docker_bkp_temp" RUSTIC_CONFIG_DIR="/etc/rustic" REPO_CLOUD="rclone:my_remote:backup/desktop" REPO_LOCAL="/mnt/nas/backup" DRY_RUN=0 ONLY_PROFILES=0 VALIDATE=0 ERRORS=0 for arg in "$@"; do case "$arg" in -n|--dry-run) DRY_RUN=1 ;; --only-profiles) ONLY_PROFILES=1 ;; --validate) VALIDATE=1 DRY_RUN=1 ;; *) echo "Usage: $0 [-n|--dry-run] [--only-profiles] [--validate]" >&2 exit 2 ;; esac done # Redirect stdout and stderr to the timestamped log file during normal runs if [ "$ONLY_PROFILES" -eq 0 ] && [ "$VALIDATE" -eq 0 ]; then exec > >(awk -v logfile="$LOG_FILE" '{ print strftime("[%Y-%m-%d %H:%M:%S]"), $0 >> logfile; close(logfile) }') 2>&1 fi # Send error alerts via ntfy (Priority: default) notify_error() { local message="$1" ERRORS=$((ERRORS + 1)) echo "[ALERT] $message" if [ "$DRY_RUN" -eq 1 ]; then echo "[DRY-RUN] ntfy notification suppressed." return 0 fi curl -s -o /dev/null \ -H "Title: Backup Failure (Desktop)" \ -H "Tags: rotating_light,skull" \ -H "Priority: default" \ -d "$message" \ "$NTFY_URL" || true } # Notify success only when no stage failed (Priority: min) notify_success() { if [ "$DRY_RUN" -eq 1 ]; then echo "[DRY-RUN] ntfy success notification suppressed." return 0 fi curl -s -o /dev/null \ -H "Title: Backup Completed (Desktop)" \ -H "Tags: white_check_mark,floppy_disk" \ -H "Priority: min" \ -d "Backup and verification routine completed with zero interruptions." \ "$NTFY_URL" || true } # Remove the Btrfs snapshot and temporary Docker export dir on exit or before start cleanup_temps() { cd / || return 0 if sudo btrfs subvolume show /.snap_root >/dev/null 2>&1; then sudo btrfs subvolume delete /.snap_root >/dev/null 2>&1 || true fi if [ -d "$DOCKER_BKP_DIR" ]; then sudo rm -rf "$DOCKER_BKP_DIR" || true fi } # Generate/update native TOML profiles in /etc/rustic (readable by root and user) ensure_rustic_profiles() { sudo mkdir -p "$RUSTIC_CONFIG_DIR" sudo chmod 755 "$RUSTIC_CONFIG_DIR" sudo tee "$RUSTIC_CONFIG_DIR/base.toml" > /dev/null <<EOF # Base profile shared across all repositories (inherited via use-profiles) [global] log-level = "info" progress-interval = "1min" group-by = "host,label,paths" [repository] password-file = "$PASSWORD_FILE" [backup] exclude-if-present = ["CACHEDIR.TAG", ".nobackup"] globs = ["!**/.cache"] [[backup.snapshots]] name = "root-system" sources = ["/.snap_root"] as-path = "/" tags = ["root-system"] one-file-system = true globs = [ "!/.snap_root/tmp/*", "!/.snap_root/var/tmp/*", "!/.snap_root/timeshift", ] [[backup.snapshots]] name = "sync-folder" sources = ["$SYNC_DIR"] tags = ["sync-folder"] exclude-if-present = ["CACHEDIR.TAG", ".nobackup"] globs = ["!**/.cache"] [[backup.snapshots]] name = "personal-data" sources = ["/home", "/data"] tags = ["personal-data"] exclude-if-present = ["CACHEDIR.TAG", ".nobackup"] globs = [ "!/data/backup", "!**/.cache", ] [[backup.snapshots]] name = "docker-data" sources = ["/var/lib/docker/volumes", "$DOCKER_BKP_DIR"] tags = ["docker-data"] [forget] keep-daily = 7 keep-weekly = 2 keep-monthly = 1 keep-tags = ["keep"] prune = true EOF sudo tee "$RUSTIC_CONFIG_DIR/cloud.toml" > /dev/null <<EOF # Remote cloud repository profile (via rclone) [global] use-profiles = ["base"] [repository] repository = "$REPO_CLOUD" [repository.options] retry = "default" timeout = "10min" EOF sudo tee "$RUSTIC_CONFIG_DIR/local.toml" > /dev/null <<EOF # Local / NAS repository profile ($REPO_LOCAL) [global] use-profiles = ["base"] [repository] repository = "$REPO_LOCAL" EOF sudo tee "$RUSTIC_CONFIG_DIR/rustic.toml" > /dev/null <<'EOF' # Default profile when -P is omitted on the command line [global] use-profiles = ["local"] EOF sudo chmod 644 \ "$RUSTIC_CONFIG_DIR/base.toml" \ "$RUSTIC_CONFIG_DIR/cloud.toml" \ "$RUSTIC_CONFIG_DIR/local.toml" \ "$RUSTIC_CONFIG_DIR/rustic.toml" } ensure_rustic_profiles if [ "$ONLY_PROFILES" -eq 1 ]; then echo "[INFO] rustic profiles updated in $RUSTIC_CONFIG_DIR." exit 0 fi trap cleanup_temps EXIT echo "=== Starting FULL backup routine (Native Profiles) ===" # 1. Clean any leftover artifacts and freeze the root filesystem (Read-Only Btrfs Snapshot) cleanup_temps echo "[INFO] Creating root filesystem (/) snapshot..." sudo btrfs subvolume snapshot -r / /.snap_root || notify_error "Failed to create root Btrfs snapshot." # 2. Prepare Docker backup (Images and Metadata) echo "[INFO] Collecting Docker metadata and images..." sudo mkdir -p "$DOCKER_BKP_DIR" mapfile -t CONTAINER_IDS < <(sudo docker ps -aq) if [ "${#CONTAINER_IDS[@]}" -gt 0 ]; then sudo docker ps -a | sudo tee "$DOCKER_BKP_DIR/containers_list.txt" > /dev/null sudo docker inspect "${CONTAINER_IDS[@]}" 2>/dev/null | sudo tee "$DOCKER_BKP_DIR/containers_inspect.json" > /dev/null || true else echo "No containers found." | sudo tee "$DOCKER_BKP_DIR/containers_list.txt" > /dev/null fi if [ "$VALIDATE" -eq 1 ]; then echo "[VALIDATION] Full docker_images.tar export skipped in --validate mode." echo "Dry-run validation." | sudo tee "$DOCKER_BKP_DIR/docker_images_validation.txt" > /dev/null else echo "[INFO] Exporting Docker images to .tar archive..." mapfile -t IMAGE_IDS < <(sudo docker images -q) if [ "${#IMAGE_IDS[@]}" -gt 0 ]; then sudo docker save "${IMAGE_IDS[@]}" -o "$DOCKER_BKP_DIR/docker_images.tar" 2>/dev/null || notify_error "Failed to export Docker images." else echo "No images found." | sudo tee "$DOCKER_BKP_DIR/docker_images.txt" > /dev/null fi fi # Fast end-to-end validation mode (Btrfs + Docker + Profiles + trap) if [ "$VALIDATE" -eq 1 ]; then echo "[VALIDATION] Verifying local and cloud profile loading..." sudo rustic -P local show-config > /dev/null sudo rustic -P cloud show-config > /dev/null echo "[VALIDATION] Testing backup --dry-run (sync-folder) and forget --dry-run on local profile..." sudo rustic -P local --dry-run backup --name sync-folder sudo rustic -P local --dry-run forget echo "=== Validation succeeded (cleanup handled by trap EXIT) ===" exit 0 fi # 3. Repository Profiles (/etc/rustic/<profile>.toml) PROFILES=( "cloud" "local" ) RUSTIC_ARGS=() if [ "$DRY_RUN" -eq 1 ]; then RUSTIC_ARGS+=(--dry-run) fi # 4. Run for each profile (a single backup command creates all [[backup.snapshots]]) for PROFILE in "${PROFILES[@]}"; do echo "--> Processing profile: $PROFILE" # 4.1 Unified backup (root-system, sync-folder, personal-data, and docker-data) echo "[INFO] Running backup of all snapshots for profile $PROFILE..." sudo rustic -P "$PROFILE" "${RUSTIC_ARGS[@]}" backup \ || notify_error "Backup error on profile: $PROFILE" # 4.2 Retention policy and pruning (prune = true is set in [forget] of base.toml) echo "[INFO] Applying retention policy and prune on profile $PROFILE..." sudo rustic -P "$PROFILE" "${RUSTIC_ARGS[@]}" forget \ || notify_error "Retention/prune (forget) error on profile: $PROFILE" # 4.3 Repository integrity check if [ "$DRY_RUN" -eq 1 ]; then echo "[DRY-RUN] Heavy integrity check skipped for profile: $PROFILE" else echo "[INFO] Checking repository integrity ($PROFILE)..." sudo rustic -P "$PROFILE" check \ || notify_error "WARNING: INTEGRITY failure detected on profile: $PROFILE" fi echo "--> Finished: $PROFILE" echo "----------------------------------------" done # 5. Final Cleanup (Btrfs and Docker) echo "[INFO] Removing temporary Btrfs snapshot and temporary Docker export..." cleanup_temps if [ "$ERRORS" -eq 0 ]; then echo "=== Backup completed successfully ===" notify_success else echo "=== Backup finished with $ERRORS failure(s) ===" exit 1 fi # ============================================================================== # DISASTER RECOVERY MANUAL (BARE METAL RESTORE) # ============================================================================== # rustic's repository format is 100% compatible with restic. # In an emergency you can restore using EITHER tool (rustic OR restic), # depending on what is easier to obtain in your Live USB environment. # # SYNTAX WARNING: # - rustic restore uses `--filter-tags "<tag>"` and a POSITIONAL destination (no `--target`). # - restic restore uses `--tag "<tag>"` and `--target <destination>`. # # 1. BOOT FROM A LIVE USB (e.g. Manjaro Live USB). # # 2. MOUNT THE MAIN BTRFS PARTITION (Replace sdXN with your root partition, e.g. nvme0n1p2): # sudo mount /dev/sdXN /mnt # # 3. DELETE THE BROKEN SUBVOLUME AND CREATE A CLEAN ONE: # sudo btrfs subvolume delete /mnt/@ # sudo btrfs subvolume create /mnt/@ # (If you also need to restore @home from scratch, recreate /mnt/@home and mount it at /mnt/@/home: # sudo mkdir -p /mnt/@/home && sudo mount -o subvol=@home /dev/sdXN /mnt/@/home) # # ------------------------------------------------------------------------------ # OPTION A: RESTORING WITH RUSTIC # ------------------------------------------------------------------------------ # 4A. INSTALL RUSTIC ON THE LIVE USB: # sudo pacman -Sy rustic # # Or via prebuilt static binary (handy if the package is unavailable): # curl -L https://github.com/rustic-rs/rustic/releases/latest/download/rustic-x86_64-unknown-linux-musl.tar.gz \ # | tar -xz && sudo mv rustic /usr/local/bin/ # # 5A. SET CREDENTIALS AND RESTORE SNAPSHOTS WITH RUSTIC: # export RUSTIC_PASSWORD="your_password_here" # export RUSTIC_REPOSITORY="rclone:my_remote:backup/desktop" # or /path/to/local/repo # # Operating system (root): # sudo -E rustic restore latest --filter-tags "root-system" /mnt/@ # # Personal data (/home and /data) and Sync folder: # sudo -E rustic restore latest --filter-tags "personal-data" /mnt/@ # sudo -E rustic restore latest --filter-tags "sync-folder" /mnt/@ # # (Tip: if the system is already running with /etc/rustic/ profiles, just run: # # sudo rustic -P local restore latest --filter-tags "sync-folder" /) # # ------------------------------------------------------------------------------ # OPTION B: RESTORING WITH RESTIC # ------------------------------------------------------------------------------ # 4B. INSTALL RESTIC ON THE LIVE USB (if not already present): # sudo pacman -Sy restic # # 5B. SET CREDENTIALS AND RESTORE SNAPSHOTS WITH RESTIC: # export RESTIC_PASSWORD="your_password_here" # export RESTIC_REPOSITORY="rclone:my_remote:backup/desktop" # or /path/to/local/repo # # Operating system (root): # sudo -E restic restore latest --tag "root-system" --target /mnt/@ # # Personal data (/home and /data) and Sync folder: # sudo -E restic restore latest --tag "personal-data" --target /mnt/@ # sudo -E restic restore latest --tag "sync-folder" --target /mnt/@ # # ------------------------------------------------------------------------------ # 6. NOTE ON RESTORED FILE PATHS: # - New snapshots (created with `as-path = "/"` in rustic profiles) restore # /etc, /usr, /var, etc. directly into /mnt/@/. No extra moves needed! # - Only if restoring an OLD snapshot (prior to migration, storing the # /.snap_root prefix), flatten paths with: # sudo mv /mnt/@/.snap_root/* /mnt/@/ # sudo mv /mnt/@/.snap_root/.* /mnt/@/ 2>/dev/null || true # sudo rmdir /mnt/@/.snap_root # # 7. UNMOUNT TEMPORARY SUBVOLUMES AND REINSTALL THE BOOTLOADER (GRUB): # sudo umount -R /mnt # Manjaro includes the native `manjaro-chroot` utility: # sudo manjaro-chroot -a # (Select the restored installation). Inside the chroot, run: # grub-install /dev/sdX # Replace sdX with the disk (e.g. /dev/nvme0n1), NO partition number # update-grub # exit # # 8. DOCKER RESTORE (if rebuilding separately after booting into the system): # - Using rustic: # sudo -E rustic restore latest --filter-tags "docker-data" / # # (or with a profile: sudo rustic -P local restore latest --filter-tags "docker-data" /) # - Or using restic: # sudo -E restic restore latest --tag "docker-data" --target / # - This restores volumes to /var/lib/docker/volumes and metadata/images to # /var/tmp/docker_bkp_temp/. # - Reload Docker images: # sudo docker load -i /var/tmp/docker_bkp_temp/docker_images.tar # - Recreate containers using your original docker-compose.yml files or by # inspecting /var/tmp/docker_bkp_temp/containers_list.txt and containers_inspect.json. # - Clean up the temporary directory when done: # sudo rm -rf /var/tmp/docker_bkp_temp # # 9. REBOOT AND VERIFY: # reboot # ==============================================================================
And in crontab -e, updating the entry was just one line:
0 3 * * * sudo /home/user/.rustic.sh >/dev/null 2>&1
Validating it in the real world: commands and observed results
After passing bash -n ~/.rustic.sh && shellcheck ~/.rustic.sh with zero warnings, live checks confirmed how much cleaner daily operations became:
# 1. Generate/refresh only the TOML profiles in /etc/rustic/ ~/.rustic.sh --only-profiles # 2. Inspect the resolved merged config (base + target profile) sudo rustic -P local show-config sudo rustic -P cloud show-config # 3. Query snapshots without typing --repo or --password-file sudo rustic snapshots # reads /etc/rustic/rustic.toml -> local (117 snapshots) sudo rustic -P cloud snapshots # queries the remote cloud repo (108 snapshots) # 4. Run a single targeted snapshot from the profile (e.g., Sync folder) sudo rustic -P local backup --name sync-folder
In that targeted incremental run, rustic processed 756 files (1.3 GiB) in one focused pass, picked up 1 new file and 1 changed file (the synced copy of the updated script itself), uploaded 59.3 MiB compressed (from 79.2 MiB raw) to the local repository, and saved snapshot 26450f62 without touching the other three targets. Running sudo rustic -P local forget --dry-run and sudo rustic -P local check right after confirmed retention policy evaluation and repository integrity.
Finally, whenever I change something in the script and want to verify the entire pipeline before the 3 AM cron job fires — Btrfs snapshotting, Docker metadata collection, profile loading for both repositories, backup --dry-run, forget --dry-run, and clean teardown via trap EXIT — one command covers everything:
~/.rustic.sh --validate
Now the script's filename actually matches what is under the hood, configuration lives cleanly in native profiles, exclusions do what they promise, and if disaster ever strikes, both rustic and trusty restic have verified restore steps waiting right inside the file.
Sources and references
- Previous post on switching to rustic: My new backup routine with rustic (Quadrante do Giuliano)
- Original restic backup post: My backup routine with restic (Quadrante do Giuliano)
- Official rustic configuration file and profiles docs: Configuration file — rustic docs
- Official rustic profiles guide in the repository: config/README.md — rustic-rs/rustic (GitHub)
- Complete rustic TOML reference: config/full.toml — rustic-rs/rustic (GitHub)
- Official rustic exclusions and globbing docs: Excluding files — rustic docs
- Official rustic hooks docs: Hooks — rustic docs
- Official rustic restore documentation: Restore — rustic docs
- Official restic restore documentation: Restoring from backup — restic docs
- Restic repository format specification: References & Design Document — restic docs
- ArchWiki guide on Btrfs: Btrfs — ArchWiki
- Official Btrfs documentation: BTRFS documentation
- Linux distribution referenced in the recovery steps: Manjaro Linux
- Container platform included in the backup: Docker
- Cloud storage transport used for the remote repository: Rclone
- Push notification service used for error and success alerts: ntfy