tech_notes

A collection of notes on setups and implementations.
Log | Files | Refs | README

restic_backup.md (4490B)


      1 # Restic backup
      2 
      3 We back up the server to a local Mac using restic.
      4 The backup is triggered automatically by the Mac via a launchd agent that runs every 30 minutes.
      5 If the last snapshot is less than 7 days old the script exits immediately, so a full backup runs at most once a week.
      6 
      7 **What is backed up:**
      8 - `/home/julian` (excludes `~/.cache`)
      9 - `/var/www`
     10 - `/srv/git`
     11 - `/usr/local/bin`
     12 - `/etc/nginx`, `/etc/postfix`, `/etc/dovecot`, `/etc/letsencrypt`, `/etc/systemd/system`
     13 
     14 The restic repository lives on the Mac at `~/backups/server`.
     15 
     16 ## Communication
     17 
     18 The Mac and the server each need an SSH connection to the other:
     19 
     20 ```
     21 Mac ──SSH──▶ Server   (Mac triggers the backup by running backup.sh on the server)
     22 Server ──SFTP──▶ Mac  (restic on the server stores snapshots on the Mac)
     23 ```
     24 
     25 The Mac already has an SSH key trusted by the server (used for normal login).
     26 We additionally generate a dedicated key on the server so it can reach the Mac via SFTP.
     27 
     28 ## Prerequisites
     29 
     30 We assume restic is not yet installed on the server and that `Remote Login` (SSH) is enabled on the Mac under `System Settings → General → Sharing`.
     31 
     32 ## Install restic on the server
     33 
     34 ```console
     35 # pacman -S restic
     36 ```
     37 
     38 ## Password file
     39 
     40 We create a password file that restic uses to encrypt all snapshots.
     41 Store this password somewhere safe — without it the repository cannot be decrypted.
     42 
     43 ```console
     44 $ echo 'your-strong-password' > ~/.restic-password
     45 $ chmod 600 ~/.restic-password
     46 ```
     47 
     48 ## SSH key from server to Mac
     49 
     50 We generate a dedicated key pair on the server and copy the public key to the Mac:
     51 
     52 ```console
     53 $ ssh-keygen -t ed25519 -f ~/.ssh/id_backup -N ""
     54 $ ssh-copy-id -i ~/.ssh/id_backup.pub julian@mac.local
     55 ```
     56 
     57 Test the connection:
     58 
     59 ```console
     60 $ ssh -i ~/.ssh/id_backup julian@mac.local echo ok
     61 ```
     62 
     63 ## Backup directory on the Mac
     64 
     65 ```console
     66 $ mkdir -p ~/backups/server
     67 ```
     68 
     69 ## Initialise the repository
     70 
     71 We initialise the restic repository on the Mac from the server:
     72 
     73 ```console
     74 $ restic -r sftp:julian@mac.local:/Users/julian/backups/server \
     75     --password-file ~/.restic-password init
     76 ```
     77 
     78 ## Backup script
     79 
     80 We create `/home/julian/backup.sh` on the server:
     81 
     82 ```bash
     83 #!/bin/bash
     84 REPO="sftp:julian@mac.local:/Users/julian/backups/server"
     85 PASSWORD_FILE=~/.restic-password
     86 
     87 # Skip if last snapshot is less than 7 days old
     88 LAST_JSON=$(restic -r "$REPO" --password-file "$PASSWORD_FILE" snapshots --last --json 2>/dev/null)
     89 LAST_TIME=$(echo "$LAST_JSON" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d[-1]['time'] if d else '')" 2>/dev/null)
     90 
     91 if [ -n "$LAST_TIME" ]; then
     92   LAST_EPOCH=$(date -d "$LAST_TIME" +%s 2>/dev/null)
     93   NOW_EPOCH=$(date +%s)
     94   [ $(( (NOW_EPOCH - LAST_EPOCH) / 86400 )) -lt 7 ] && exit 0
     95 fi
     96 
     97 restic -r "$REPO" --password-file "$PASSWORD_FILE" \
     98   --exclude ~/.cache \
     99   backup \
    100   /home/julian \
    101   /var/www \
    102   /srv/git \
    103   /usr/local/bin \
    104   /etc/nginx \
    105   /etc/postfix \
    106   /etc/dovecot \
    107   /etc/letsencrypt \
    108   /etc/systemd/system
    109 ```
    110 
    111 ```console
    112 $ chmod +x /home/julian/backup.sh
    113 ```
    114 
    115 Run it once manually to verify everything works:
    116 
    117 ```console
    118 $ ~/backup.sh
    119 ```
    120 
    121 ## Launchd agent on the Mac
    122 
    123 We create `~/Library/LaunchAgents/com.backup.server.plist` on the Mac.
    124 The easiest way is to copy it from the server:
    125 
    126 ```console
    127 $ scp julian@server.local:/home/julian/com.backup.server.plist \
    128     ~/Library/LaunchAgents/com.backup.server.plist
    129 ```
    130 
    131 The plist content (also at `/home/julian/com.backup.server.plist` on the server):
    132 
    133 ```xml
    134 <?xml version="1.0" encoding="UTF-8"?>
    135 <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
    136   "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
    137 <plist version="1.0">
    138 <dict>
    139   <key>Label</key>
    140   <string>com.backup.server</string>
    141   <key>ProgramArguments</key>
    142   <array>
    143     <string>/usr/bin/ssh</string>
    144     <string>julian@server.local</string>
    145     <string>/home/julian/backup.sh</string>
    146   </array>
    147   <key>StartInterval</key>
    148   <integer>1800</integer>
    149   <key>StandardOutPath</key>
    150   <string>/tmp/server-backup.log</string>
    151   <key>StandardErrorPath</key>
    152   <string>/tmp/server-backup.log</string>
    153 </dict>
    154 </plist>
    155 ```
    156 
    157 Load the agent:
    158 
    159 ```console
    160 $ launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.backup.server.plist
    161 ```
    162 
    163 Verify it is registered:
    164 
    165 ```console
    166 $ launchctl list com.backup.server
    167 ```
    168 
    169 To reload after editing the plist:
    170 
    171 ```console
    172 $ launchctl bootout gui/$(id -u)/com.backup.server
    173 $ launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.backup.server.plist
    174 ```