3 minute read

This procedure was scary and felt like stumbling around a maze in a blindfold. Borg is way too powerful and customizable for my needs. I think it works. We’ll see.

This post is a part of my Homelab Series. See the index here.

Installation

apt update
apt install pipx
pipx ensurepath
apt install borgbackup
pipx install borgmatic

Check if it’s installed:

borgmatic --version

Configuration

Generate the config file:

borgmatic config generate --destination ~/.config/borgmatic/config.yaml

I don’t know why borgmatic didn’t pick up on the custom --destination, so I had to fix it, caveman-style:

sudo mkdir /etc/borgmatic
sudo ln -s ~/.config/borgmatic/config.yaml /etc/borgmatic/config.yaml

Here’s my config:

source_directories:
    - /home/pe8er
    - /mnt/ssd-sandisk

repositories:
    - path: /mnt/media/borgbackups
      label: g3-backup
      encryption: repokey-blake2
    # - path: ssh://[email protected]/./repo < HETZNER GOES HERE once I buy it

exclude_patterns:
    - /home/pe8er/.cache
    - /home/pe8er/Downloads
    - /home/pe8er/Movies
    - /home/pe8er/TV
    - /home/pe8er/snap
    - '*/*tmp'
    - '*/log'
    - '*/logs'
    - '*/*.log'
    - '*/.stversions'
    - '*/.vscodium-server'
    - '*/.local'
    - '*/.npm'
    - '*/.ssh'
    - '*/.secrets'

encryption_passcommand: cat /home/pe8er/.secrets/borg

keep_daily: 7
keep_weekly: 4
keep_monthly: 3
keep_yearly: 1
# keep_13weekly: 13
# keep_3monthly: 3

ntfy:
    topic: borg
    server: http://192.168.1.199:8787
    username: NTFY-USERNAME
    password: NTFY-PASSWORD

    finish:
        title: A borgmatic backup completed successfully
        message: Nice!
        tags: borgmatic,+1
        priority: min
    fail:
        title: A borgmatic backup failed
        message: You should probably fix it
        tags: borgmatic,-1,skull
        priority: max
    states:
        - finish
        - fail

Holy shit, the way keep_* options are designed is absolutely beyond my comprehension.

Validate config:

borgmatic -v 2 config validate

Launch the Borg!!!

First, initiate the repository:

borgmatic init --encryption repokey-blake2

(Apparently, repokey-blake2 is the fastest option on an Intel CPU). Once initiated, make sure to export the repository key and place it outside the repository:

borg key export /mnt/media/borgbackups/ ~/.secrets/borgrepokey

And create the backup!

borgmatic create --verbosity 2 --list --stats

Oh damn it works! And it’s pretty damn fast.

g3-backup: Pruning archives
Keeping archive (rule: daily #1):            g3-2025-07-11T14:50:55.642919        Fri, 2025-07-11 14:50:55 [1678cb92d646bca4a8912c4479ab9d96a9f3c38f81e8a26bdbcd79dbdd2f95cd]
Pruning archive (1/1):                       g3-2025-07-11T14:49:49.867765        Fri, 2025-07-11 14:49:50 [35cef225b9c6a0bcf06fb4c228166e158f3f602f6cf6bf300dbd14fe827fdeeb]
Keeping archive (rule: daily[oldest] #2):    g3-2025-07-11T10:43:31.991167        Fri, 2025-07-11 10:43:32 [9248f2607a766f9bbb686173cdc1ae59df100cc54ec8155f16e1fffb4516e9d1]
------------------------------------------------------------------------------
                       Original size      Compressed size    Deduplicated size
Deleted data:             -738.30 GB           -715.41 GB             -4.71 MB
All archives:                1.48 TB              1.43 TB            642.51 GB
                       Unique chunks         Total chunks
Chunk index:                  364881               831003
------------------------------------------------------------------------------
g3-backup: Compacting segments
compaction freed about 4.71 MB repository space.
g3-backup: Running consistency checks
g3-backup: Skipping repository check due to configured frequency; 29 days, 22:33:44.729506 until next check (use --force to check anyway)
g3-backup: Skipping archives check due to configured frequency; 29 days, 22:33:44.728086 until next check (use --force to check anyway)

summary:
/etc/borgmatic/config.yaml: Successfully ran configuration file

Automation

Create service file:

sudo nano /etc/systemd/system/borgmatic.service
[Unit]
Description=borgmatic backup
Wants=network-online.target
After=network-online.target
ConditionACPower=true

[Service]
Type=oneshot

LockPersonality=true
MemoryDenyWriteExecute=no
NoNewPrivileges=yes
PrivateDevices=yes
PrivateTmp=yes
ProtectClock=yes
ProtectControlGroups=yes
ProtectHostname=yes
ProtectKernelLogs=yes
ProtectKernelModules=yes
ProtectKernelTunables=yes
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6 AF_NETLINK
RestrictNamespaces=yes
RestrictRealtime=yes
RestrictSUIDSGID=yes
SystemCallArchitectures=native
SystemCallFilter=@system-service
SystemCallErrorNumber=EPERM
ProtectSystem=full

CapabilityBoundingSet=CAP_DAC_READ_SEARCH CAP_NET_RAW

Nice=19
CPUSchedulingPolicy=batch
IOSchedulingClass=best-effort
IOSchedulingPriority=7
IOWeight=100

Restart=no
LogRateLimitIntervalSec=0

ExecStartPre=sleep 1m
ExecStart=systemd-inhibit --who="borgmatic" --why="Prevent interrupting scheduled backup" /home/pe8er/.local/bin/borgmatic --verbosity 2 --list --stats --syslog-verbosity 2

Test it:

sudo systemctl start borgmatic.service
journalctl -u borgmatic -f

Create and enable timer service:

sudo nano /etc/systemd/system/borgmatic.timer
[Unit]
Description=Run Borgmatic Backup

[Timer]
OnCalendar=*-*-* 4:00:00
Persistent=true

[Install]
WantedBy=timers.target
systemctl enable --now borgmatic.timer

Does it work?

systemctl list-timers
journalctl -u borgmatic -f

Browsing and Restoring Files

List backups:

sudo borgmatic list

Perform pruning, compaction, create backups according to your config file, and check backups for consistency:

sudo borgmatic --verbosity 2 --list --stats

Search for a file:

sudo borgmatic list --find foo.txt

Extract a file:

sudo borgmatic extract --archive latest --path "home/pe8er/bin/foo.txt"

Warning: Borgmatic understands file paths extremely literally, which means for the above to work, I need to cd all the way to the top level directory, otherwise I will end up with /home/pe8er/bin/home/pe8er/bin/foo.txt. How dumb is that.

Mount a backup:

sudo borgmatic list

g3-backup: Listing archives
g3-2025-07-16T10:00:37.300055        Wed, 2025-07-16 10:00:38 [c1a5caa39e9b191a95b7a32de954fcba5ab26858700cf9b9058d6545d73604e2]
g3-2025-07-16T16:46:19.923572        Wed, 2025-07-16 16:46:20 [fe6fce96d5e030b74dc9f6b63697923945bccf2a60af03c059d916f09cc3ee61]

# mount last backup to ~/mount
mkdir -p ~/mount
sudo borg mount -o uid=1000,gid=1000 /mnt/media/borgbackups/::g3-2025-07-16T10:00:37.300055 ~/mount

Note: Since systemd runs as root, mounted backup is not readable to me. Duh. I need to experiment with -o uid=1000,gid=1000 or -o ignore_permissions once the backup finishes.

Thanks to André Sterba for a tutorial that makes much more sense to me than the official documentation..