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 ```