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
With resticprofile, you can consolidate this into a single YAML file:
~/
└── .config/
└── resticprofile/
└── profiles.yaml # All settings
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
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
Verify Installation
resticprofile version
# resticprofile version 0.31.0 commit 8a3d7f2e
Step 1: Create a Minimal YAML Configuration
First, let's migrate the previous restic configuration to resticprofile.
Create Directory
mkdir -p ~/.config/resticprofile
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"
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 withresticprofile 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)
Step 2: Verify Operation
Execute Backup
resticprofile backup
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'
This shows that resticprofile is reading the configuration file and executing the appropriate restic commands.
Check Snapshots
resticprofile snapshots
Dry Run (Pre-Execution Check)
resticprofile --dry-run backup
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'
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
Points:
- With
after-backup: true, old snapshots will be automatically deleted after a successful backup -
prune: trueexecutes physical deletion as well
Verify Operation
resticprofile backup
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'
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
Register the Schedule
resticprofile schedule
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'
resticprofile automatically generates the launchd plist and executes launchctl load.
Check the Schedule
resticprofile status
Example output:
Schedule: default/backup
Status: enabled
Next run: 2026-01-13 03:00:00
Remove the Schedule
resticprofile unschedule
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
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
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
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
- Create an account at healthchecks.io (free plan allows up to 20 checks)
- Create a new check (e.g.,
restic-backup) - 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
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]
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
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
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'
Usage Examples
# Execute backup
rpb
# Check snapshots
rps
# Check schedule status
rpst
# Verification
rpc
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
Features
- Base Profile: Manage common settings in one place
-
Environment Variables: Manage API keys and UUIDs with environment variables (export in
.zshrc) - Multiple Backup Destinations: Local (daily) + Cloud (weekly)
- Automatic Verification: Monthly, read 10% of the data for integrity checks
- 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
Password File Not Found
# Specify absolute path
password-file: "/Users/tumf/.restic-password"
# Or use environment variable
password-file: "${HOME}/.restic-password"
Environment Variables Not Expanding
# Check in shell
echo $B2_ACCOUNT_ID
# Verify if exported in .zshrc
grep B2_ACCOUNT_ID ~/.zshrc
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:
- Configuration Consolidation: Manage passwords, exclusion patterns, generation management, and scheduling all in YAML
- Scheduler Abstraction: Register schedules without worrying about launchd/systemd, using a unified syntax
- Multiple Profiles: Manage settings for local, cloud, NAS, etc., in one file
- Inheritance Feature: Summarize common settings in a base profile for DRY
- Monitoring Integration: Instant detection of failures with healthchecks.io
-
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!
Top comments (0)