DEV Community

Cover image for resticprofile: Consolidating Exclusion Patterns, Generation Management, and Scheduling in a Single YAML File
tumf
tumf

Posted on • Originally published at blog.tumf.dev

resticprofile: Consolidating Exclusion Patterns, Generation Management, and Scheduling in a Single YAML File

Originally published on 2026-01-14
Original article (Japanese): resticprofile: 除外パターン・世代管理・スケジュールを1つのYAMLで完結させる

In the previous article, "restic: Designing a 'Restorable Development Environment' While Excluding node_modules and .git," we discussed the basic usage of restic and how to automate it using launchd/systemd.

However, managing multiple configuration files and writing plist or unit files by hand can be quite tedious. Maintaining different configurations for macOS and Linux can also become cumbersome.

In this article, I will introduce a practical method to consolidate backup settings into a single YAML file using resticprofile. Experience the convenience of declaratively managing password management, exclusion lists, generation management, and scheduling—all in one place.

What is resticprofile?

resticprofile is a wrapper tool for centrally managing the configuration of restic. It is developed in Go and features cross-platform compatibility, just like restic.

Key Features

  • Configuration File Driven: Write configurations in YAML/TOML/JSON/HCL
  • Scheduler Abstraction: Uniformly handle macOS (launchd), Linux (systemd), and Windows (Task Scheduler)
  • Profile Management: Manage multiple backup settings in a single file
  • Monitoring Integration: Supports notifications via healthchecks.io, Prometheus, Slack/Discord
  • JSON Schema Support: Provides autocompletion and validation in editors like VSCode

Official site: https://creativeprojects.github.io/resticprofile/

Why Use resticprofile?

In the previous article, the following configurations were scattered:

~/
├── .restic-password         # Password file
├── .restic-excludes         # Exclusion patterns
├── .zshrc                   # Environment variables
└── Library/LaunchAgents/    # macOS scheduling settings
    └── dev.tumf.restic-backup.plist
Enter fullscreen mode Exit fullscreen mode

With resticprofile, you can consolidate this into a single YAML file:

~/
└── .config/
    └── resticprofile/
        └── profiles.yaml    # All settings
Enter fullscreen mode Exit fullscreen mode

Moreover, you can share the same configuration file between macOS and Linux, making it easier to manage multiple machines.

Installation

macOS

brew tap creativeprojects/tap
brew install resticprofile
Enter fullscreen mode Exit fullscreen mode

Linux (Direct Binary Installation)

# Download the latest version
curl -LO https://github.com/creativeprojects/resticprofile/releases/latest/download/resticprofile_linux_amd64.tar.gz

# Extract and install
tar xzf resticprofile_linux_amd64.tar.gz
sudo mv resticprofile /usr/local/bin/
sudo chmod +x /usr/local/bin/resticprofile
Enter fullscreen mode Exit fullscreen mode

Verify Installation

resticprofile version
# resticprofile version 0.31.0 commit 8a3d7f2e
Enter fullscreen mode Exit fullscreen mode

Step 1: Create a Minimal YAML Configuration

First, let's migrate the previous restic configuration to resticprofile.

Create Directory

mkdir -p ~/.config/resticprofile
Enter fullscreen mode Exit fullscreen mode

Minimal profiles.yaml

Create ~/.config/resticprofile/profiles.yaml:

# yaml-language-server: $schema=https://creativeprojects.github.io/resticprofile/jsonschema/config-1.json

version: "1"

# Global settings
global:
  default-command: snapshots
  initialize: false

# Default profile
default:
  repository: "/Volumes/Backup/restic-repo"
  password-file: "${HOME}/.restic-password"

  backup:
    source:
      - "${HOME}/work"
    exclude-caches: true
    exclude-file:
      - "${HOME}/.restic-excludes"
Enter fullscreen mode Exit fullscreen mode

Points:

  • You can expand environment variables with ${HOME}
  • The backup: section allows you to write restic options directly
  • If the profile name is default, it will be automatically selected with resticprofile backup

Password File and Exclusion File Remain the Same

# Password file (previously created)
cat ~/.restic-password
# your-secure-password

# Exclusion file (previously created)
cat ~/.restic-excludes
# **/node_modules
# **/.venv
# ... (using the previous settings as is)
Enter fullscreen mode Exit fullscreen mode

Step 2: Verify Operation

Execute Backup

resticprofile backup
Enter fullscreen mode Exit fullscreen mode

Example output:

2026/01/12 10:30:00 using configuration file: /Users/tumf/.config/resticprofile/profiles.yaml
2026/01/12 10:30:00 profile 'default': starting 'backup'
repository 6a3e8a9b opened (version 2, compression level auto)
created new cache in /Users/tumf/.cache/restic

Files:        8234 new,     0 changed,     0 unmodified
Dirs:          983 new,     0 changed,     0 unmodified
Added to the repository: 1.2 GiB (892 MiB stored)

processed 8234 files, 1.8 GiB in 0:45
snapshot b7e2c9a1 saved
2026/01/12 10:30:45 profile 'default': finished 'backup'
Enter fullscreen mode Exit fullscreen mode

This shows that resticprofile is reading the configuration file and executing the appropriate restic commands.

Check Snapshots

resticprofile snapshots
Enter fullscreen mode Exit fullscreen mode

Dry Run (Pre-Execution Check)

resticprofile --dry-run backup
Enter fullscreen mode Exit fullscreen mode

Example output:

2026/01/12 10:32:00 using configuration file: /Users/tumf/.config/resticprofile/profiles.yaml
2026/01/12 10:32:00 profile 'default': starting 'backup'
2026/01/12 10:32:00 dry-run: /opt/homebrew/bin/restic backup \
    --password-file /Users/tumf/.restic-password \
    --repo /Volumes/Backup/restic-repo \
    --exclude-caches \
    --exclude-file /Users/tumf/.restic-excludes \
    /Users/tumf/work
2026/01/12 10:32:00 profile 'default': finished 'backup'
Enter fullscreen mode Exit fullscreen mode

By specifying --dry-run before the command, you can verify the operation of resticprofile.

Step 3: Add Generation Management

In the previous article, we manually executed the restic forget command, but this can also be written in YAML.

Add Retention Section

Add to profiles.yaml:

version: "1"

default:
  repository: "/Volumes/Backup/restic-repo"
  password-file: "${HOME}/.restic-password"

  backup:
    source:
      - "${HOME}/work"
    exclude-caches: true
    exclude-file:
      - "${HOME}/.restic-excludes"

  # Add generation management settings
  retention:
    keep-last: 5
    keep-daily: 7
    keep-weekly: 4
    keep-monthly: 6
    prune: true
    after-backup: true  # Automatically executed after backup
Enter fullscreen mode Exit fullscreen mode

Points:

  • With after-backup: true, old snapshots will be automatically deleted after a successful backup
  • prune: true executes physical deletion as well

Verify Operation

resticprofile backup
Enter fullscreen mode Exit fullscreen mode

After the backup completes, forget + prune will be executed automatically:

2026/01/12 10:35:00 profile 'default': starting 'backup'
[... backup log ...]
snapshot b7e2c9a1 saved
2026/01/12 10:35:45 profile 'default': finished 'backup'

2026/01/12 10:35:45 profile 'default': starting 'forget'
Applying Policy: keep 5 latest, 7 daily, 4 weekly, 6 monthly snapshots
keep 12 snapshots:
ID        Time                 Host        Tags        Reasons
------------------------------------------------------------------------
b7e2c9a1  2026-01-12 10:35:00  macbook                 last snapshot, daily snapshot
[...]
remove 3 snapshots:
ID        Time                 Host        Tags
------------------------------------------------------------------------
[...]

2026/01/12 10:35:48 profile 'default': starting 'prune'
counting files in repo
[...]
2026/01/12 10:36:10 profile 'default': finished 'prune'
Enter fullscreen mode Exit fullscreen mode

Step 4: Set Up Scheduling

Finally, the main event. There’s no need to write launchd plist or systemd unit files.

Add Schedule Section

Refer to the Schedules documentation for the format of schedule strings.

Add to profiles.yaml:

version: "1"

default:
  repository: "/Volumes/Backup/restic-repo"
  password-file: "${HOME}/.restic-password"

  backup:
    source:
      - "${HOME}/work"
    exclude-caches: true
    exclude-file:
      - "${HOME}/.restic-excludes"

    # Add schedule settings
    schedule: "03:00"  # Every day at 3 AM
    schedule-permission: "user"  # Run with user permissions

  retention:
    keep-last: 5
    keep-daily: 7
    keep-weekly: 4
    keep-monthly: 6
    prune: true
    after-backup: true
Enter fullscreen mode Exit fullscreen mode

Register the Schedule

resticprofile schedule
Enter fullscreen mode Exit fullscreen mode

Example output (for macOS):

2026/01/12 10:40:00 using configuration file: /Users/tumf/.config/resticprofile/profiles.yaml
2026/01/12 10:40:00 profile 'default': starting 'schedule'
2026/01/12 10:40:00 creating schedule backup@03:00
2026/01/12 10:40:00 analyzing crontab entry "03:00"
2026/01/12 10:40:00 schedule for backup: @daily at 03:00
2026/01/12 10:40:00 writing launchd configuration file: /Users/tumf/Library/LaunchAgents/resticprofile.backup.default.plist
2026/01/12 10:40:00 loading schedule from /Users/tumf/Library/LaunchAgents/resticprofile.backup.default.plist
2026/01/12 10:40:00 schedule loaded successfully
2026/01/12 10:40:00 profile 'default': finished 'schedule'
Enter fullscreen mode Exit fullscreen mode

resticprofile automatically generates the launchd plist and executes launchctl load.

Check the Schedule

resticprofile status
Enter fullscreen mode Exit fullscreen mode

Example output:

Schedule: default/backup
Status: enabled
Next run: 2026-01-13 03:00:00
Enter fullscreen mode Exit fullscreen mode

Remove the Schedule

resticprofile unschedule
Enter fullscreen mode Exit fullscreen mode

Step 5: Manage Multiple Profiles

In practical operations, you may have multiple backup settings. For example:

  • default: Daily development environment backup (local disk)
  • cloud: Weekly cloud backup (Backblaze B2)
  • photos: Photo archive (NAS)

You can manage these in a single YAML file.

Example Configuration for Multiple Profiles

version: "1"

# Default profile (daily local)
default:
  repository: "/Volumes/Backup/restic-repo"
  password-file: "${HOME}/.restic-password"

  backup:
    source:
      - "${HOME}/work"
    exclude-caches: true
    exclude-file:
      - "${HOME}/.restic-excludes"
    schedule: "03:00"
    schedule-permission: "user"

  retention:
    keep-last: 5
    keep-daily: 7
    keep-weekly: 4
    keep-monthly: 6
    prune: true
    after-backup: true

# Cloud profile (weekly)
cloud:
  repository: "b2:my-bucket:restic-repo"
  password-file: "${HOME}/.restic-password"

  env:
    B2_ACCOUNT_ID: "${B2_ACCOUNT_ID}"
    B2_ACCOUNT_KEY: "${B2_ACCOUNT_KEY}"

  backup:
    source:
      - "${HOME}/work"
    exclude-caches: true
    exclude-file:
      - "${HOME}/.restic-excludes"
    schedule: "Sun 02:00"  # Every Sunday at 2 AM
    schedule-permission: "user"

  retention:
    keep-last: 3
    keep-weekly: 8
    keep-monthly: 12
    prune: true
    after-backup: true

# Photo archive profile (monthly)
photos:
  repository: "sftp:user@nas.local:/backup/photos"
  password-file: "${HOME}/.restic-password-photos"

  backup:
    source:
      - "${HOME}/Pictures"
    schedule: "1 04:00"  # Every 1st of the month at 4 AM
    schedule-permission: "user"

  retention:
    keep-last: 2
    keep-monthly: 24
    prune: true
    after-backup: true
Enter fullscreen mode Exit fullscreen mode

Switching Profiles

# Default profile
resticprofile backup

# Specify a specific profile
resticprofile --name cloud backup
resticprofile --name photos backup

# Register all profiles for scheduling
resticprofile schedule
resticprofile --name cloud schedule
resticprofile --name photos schedule
Enter fullscreen mode Exit fullscreen mode

Step 6: Use the Inherit Feature for DRY

If there are many common settings across multiple profiles, the inherit feature can reduce duplication.

Define a Base Profile

version: "1"

# Common settings (base profile)
base:
  password-file: "${HOME}/.restic-password"

  backup:
    exclude-caches: true
    exclude-file:
      - "${HOME}/.restic-excludes"

  retention:
    prune: true
    after-backup: true

# Default profile (inherits from base)
default:
  inherit: base
  repository: "/Volumes/Backup/restic-repo"

  backup:
    source:
      - "${HOME}/work"
    schedule: "03:00"
    schedule-permission: "user"

  retention:
    keep-last: 5
    keep-daily: 7
    keep-weekly: 4
    keep-monthly: 6

# Cloud profile (inherits from base)
cloud:
  inherit: base
  repository: "b2:my-bucket:restic-repo"

  env:
    B2_ACCOUNT_ID: "${B2_ACCOUNT_ID}"
    B2_ACCOUNT_KEY: "${B2_ACCOUNT_KEY}"

  backup:
    source:
      - "${HOME}/work"
    schedule: "Sun 02:00"
    schedule-permission: "user"

  retention:
    keep-last: 3
    keep-weekly: 8
    keep-monthly: 12
Enter fullscreen mode Exit fullscreen mode

With inherit: base, the settings from the base profile are merged. If the same key exists, it will be overwritten by the child profile's settings.

Step 7: Monitoring Integration (healthchecks.io)

Not noticing that a backup has failed can be critical. Let’s integrate with healthchecks.io using the HTTP Hooks feature of resticprofile.

Setting Up healthchecks.io

  1. Create an account at healthchecks.io (free plan allows up to 20 checks)
  2. Create a new check (e.g., restic-backup)
  3. Copy the Ping URL (e.g., https://hc-ping.com/your-uuid)

Add send-* Hooks

version: "1"

default:
  repository: "/Volumes/Backup/restic-repo"
  password-file: "${HOME}/.restic-password"

  backup:
    source:
      - "${HOME}/work"
    exclude-caches: true
    exclude-file:
      - "${HOME}/.restic-excludes"
    schedule: "03:00"
    schedule-permission: "user"

    # Add monitoring hooks
    send-before:
      - method: GET
        url: "https://hc-ping.com/your-uuid/start"

    send-after:
      - method: GET
        url: "https://hc-ping.com/your-uuid"

    send-after-fail:
      method: POST
      url: "https://hc-ping.com/your-uuid/fail"
      body: "${ERROR}\n\n${ERROR_STDERR}"
      headers:
        - name: "Content-Type"
          value: "text/plain; charset=UTF-8"

  retention:
    keep-last: 5
    keep-daily: 7
    keep-weekly: 4
    keep-monthly: 6
    prune: true
    after-backup: true
Enter fullscreen mode Exit fullscreen mode

Points:

  • send-before: Notification at the start of the backup
  • send-after: Notification on success
  • send-after-fail: POST error details on failure

Operation Flow

graph TD
    START[resticprofile backup] --> LOCK[Acquire Lock]
    LOCK --> BEFORE[send-before: /start]
    BEFORE --> RUN[Execute restic backup]
    RUN -->|Success| AFTER[send-after: /]
    RUN -->|Failure| FAIL[send-after-fail: /fail]
    AFTER --> FINALLY[send-finally]
    FAIL --> FINALLY
    FINALLY --> UNLOCK[Release Lock]
    UNLOCK --> END[End]
Enter fullscreen mode Exit fullscreen mode

This way, if a backup fails, you can receive immediate notifications via email/Slack/Discord.

Step 8: Prometheus Metrics Output (Optional)

If you are monitoring backups with Prometheus, you can output metrics.

prometheus-push Configuration

version: "1"

default:
  repository: "/Volumes/Backup/restic-repo"
  password-file: "${HOME}/.restic-password"

  # Send metrics to Prometheus Pushgateway
  prometheus-push: "http://localhost:9091/"
  prometheus-push-job: "restic-backup"

  backup:
    source:
      - "${HOME}/work"
    exclude-caches: true
    exclude-file:
      - "${HOME}/.restic-excludes"
    schedule: "03:00"
    schedule-permission: "user"
    extended-status: true  # Enable extended metrics

  retention:
    keep-last: 5
    keep-daily: 7
    keep-weekly: 4
    keep-monthly: 6
    prune: true
    after-backup: true
Enter fullscreen mode Exit fullscreen mode

After executing the backup, the following metrics will be sent to Prometheus:

resticprofile_backup_status{profile="default"} 2  # 0=fail, 1=warning, 2=success
resticprofile_backup_duration_seconds{profile="default"} 45.3
resticprofile_backup_files_processed{profile="default"} 8234
resticprofile_backup_added_bytes{profile="default"} 1288490188
Enter fullscreen mode Exit fullscreen mode

Step 9: Set Up Aliases for Convenience

Typing resticprofile every time can be long, so let’s set up aliases.

Add to .zshrc / .bashrc

# resticprofile aliases
alias rp='resticprofile'
alias rpb='resticprofile backup'
alias rps='resticprofile snapshots'
alias rpf='resticprofile forget'
alias rpc='resticprofile check'
alias rpst='resticprofile status'
Enter fullscreen mode Exit fullscreen mode

Usage Examples

# Execute backup
rpb

# Check snapshots
rps

# Check schedule status
rpst

# Verification
rpc
Enter fullscreen mode Exit fullscreen mode

Practical Example: My profiles.yaml

Finally, here’s the configuration I actually use.

# yaml-language-server: $schema=https://creativeprojects.github.io/resticprofile/jsonschema/config-1.json

version: "1"

global:
  default-command: snapshots
  initialize: false

# Common settings
base:
  password-file: "${HOME}/.restic-password"

  backup:
    exclude-caches: true
    exclude-file:
      - "${HOME}/.restic-excludes"
    extended-status: true

    # Common monitoring settings
    send-before:
      - method: GET
        url: "https://hc-ping.com/${HEALTHCHECK_UUID}/start"
    send-after:
      - method: GET
        url: "https://hc-ping.com/${HEALTHCHECK_UUID}"
    send-after-fail:
      method: POST
      url: "https://hc-ping.com/${HEALTHCHECK_UUID}/fail"
      body: "${ERROR}"

  retention:
    prune: true
    after-backup: true

# Default profile (daily local)
default:
  inherit: base
  repository: "/Volumes/Backup/restic-repo"

  env:
    HEALTHCHECK_UUID: "your-local-backup-uuid"

  backup:
    source:
      - "${HOME}/work"
      - "${HOME}/Documents"
    schedule: "03:00"
    schedule-permission: "user"

  retention:
    keep-last: 5
    keep-daily: 7
    keep-weekly: 4
    keep-monthly: 6

  # Regular verification (monthly)
  check:
    schedule: "1 05:00"  # Every 1st of the month at 5 AM
    schedule-permission: "user"
    read-data-subset: "10%"  # Verify 10% of the data

# Cloud profile (weekly)
cloud:
  inherit: base
  repository: "b2:my-bucket:restic-repo"

  env:
    B2_ACCOUNT_ID: "${B2_ACCOUNT_ID}"
    B2_ACCOUNT_KEY: "${B2_ACCOUNT_KEY}"
    HEALTHCHECK_UUID: "your-cloud-backup-uuid"

  backup:
    source:
      - "${HOME}/work"
      - "${HOME}/Documents"
    schedule: "Sun 02:00"
    schedule-permission: "user"

  retention:
    keep-last: 3
    keep-weekly: 8
    keep-monthly: 12
Enter fullscreen mode Exit fullscreen mode

Features

  1. Base Profile: Manage common settings in one place
  2. Environment Variables: Manage API keys and UUIDs with environment variables (export in .zshrc)
  3. Multiple Backup Destinations: Local (daily) + Cloud (weekly)
  4. Automatic Verification: Monthly, read 10% of the data for integrity checks
  5. Monitoring Integration: Notifications to healthchecks.io across all profiles

Troubleshooting

Schedule Not Executing (macOS)

# Check schedule status
launchctl list | grep resticprofile

# Check logs
tail -f ~/Library/Logs/resticprofile.backup.default.log
Enter fullscreen mode Exit fullscreen mode

Password File Not Found

# Specify absolute path
password-file: "/Users/tumf/.restic-password"

# Or use environment variable
password-file: "${HOME}/.restic-password"
Enter fullscreen mode Exit fullscreen mode

Environment Variables Not Expanding

# Check in shell
echo $B2_ACCOUNT_ID

# Verify if exported in .zshrc
grep B2_ACCOUNT_ID ~/.zshrc
Enter fullscreen mode Exit fullscreen mode

When executing a schedule, the shell initialization files (like .zshrc) may not be loaded. You can set environment variables directly in ~/.config/resticprofile/profiles.yaml or specify them in the EnvironmentVariables section of the launchd.plist.

Also, since password files and API keys can be critical, ensure their permissions are secure (e.g., chmod 600 ~/.restic-password).

Conclusion

By using resticprofile, you can consolidate backup settings into a single YAML file.

What was achieved in this article:

  1. Configuration Consolidation: Manage passwords, exclusion patterns, generation management, and scheduling all in YAML
  2. Scheduler Abstraction: Register schedules without worrying about launchd/systemd, using a unified syntax
  3. Multiple Profiles: Manage settings for local, cloud, NAS, etc., in one file
  4. Inheritance Feature: Summarize common settings in a base profile for DRY
  5. Monitoring Integration: Instant detection of failures with healthchecks.io
  6. Automation: Automatic execution of generation management with after-backup: true

The tedious aspects of manual management that were noted in the previous restic article have all been resolved. Particularly, the ability to share the same configuration file between macOS and Linux is a significant advantage for developers managing multiple machines.

In the next article, I plan to discuss using HTTP Hooks in resticprofile for Slack/Discord notifications and building dashboards with Prometheus + Grafana.

If you're interested, please give it a try!

Reference Links

Top comments (0)