first commit
This commit is contained in:
@@ -0,0 +1,119 @@
|
|||||||
|
# Restic user backup → TrueNAS REST server
|
||||||
|
|
||||||
|
User-level backup for personal machines (desktop, laptop).
|
||||||
|
No `sudo` required — everything runs as your own user.
|
||||||
|
|
||||||
|
## Differences from the system-level setup
|
||||||
|
|
||||||
|
| | System (`backup/`) | User (`backup-user/`) |
|
||||||
|
|---|---|---|
|
||||||
|
| Runs as | root | your user |
|
||||||
|
| Config | `/etc/restic/` | `~/.config/restic/` |
|
||||||
|
| Script | `/usr/local/bin/` | `~/.local/bin/` |
|
||||||
|
| Systemd units | `/etc/systemd/system/` | `~/.config/systemd/user/` |
|
||||||
|
| Commands | `sudo systemctl` | `systemctl --user` |
|
||||||
|
| Logs | `journalctl -u` | `journalctl --user -u` |
|
||||||
|
| Package dump | ✅ (has root) | ❌ (not needed) |
|
||||||
|
| Boot timer trigger | `OnBootSec` (system boot) | `OnStartupSec` (after login) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Setup
|
||||||
|
|
||||||
|
### 1. Create config directories
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mkdir -p ~/.config/restic
|
||||||
|
mkdir -p ~/.config/systemd/user
|
||||||
|
mkdir -p ~/.local/bin
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Install and fill in the env file
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cp env.example ~/.config/restic/env
|
||||||
|
nano ~/.config/restic/env
|
||||||
|
chmod 600 ~/.config/restic/env
|
||||||
|
```
|
||||||
|
|
||||||
|
Set these values:
|
||||||
|
- `MACHINE_NAME` — unique name for this machine (e.g. `desktop`, `laptop`)
|
||||||
|
- `RESTIC_PASSWORD` — generate with `openssl rand -base64 32`
|
||||||
|
- `BACKUP_PATHS` — adjust to what matters to you (default covers `.config`, `.local/share`, Documents, Pictures, Desktop)
|
||||||
|
|
||||||
|
### 3. Install the excludes file
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cp excludes.txt ~/.config/restic/excludes.txt
|
||||||
|
# Optionally add machine-specific paths to skip
|
||||||
|
nano ~/.config/restic/excludes.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Initialize the repository on the REST server
|
||||||
|
|
||||||
|
```bash
|
||||||
|
source <(set -a && cat ~/.config/restic/env) && restic init
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. Install the backup script
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cp backup.sh ~/.local/bin/restic-backup.sh
|
||||||
|
chmod +x ~/.local/bin/restic-backup.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6. Install the systemd user units
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cp restic-backup.service ~/.config/systemd/user/
|
||||||
|
cp restic-backup.timer ~/.config/systemd/user/
|
||||||
|
cp restic-backup-boot.timer ~/.config/systemd/user/
|
||||||
|
|
||||||
|
systemctl --user daemon-reload
|
||||||
|
systemctl --user enable --now restic-backup.timer
|
||||||
|
systemctl --user enable --now restic-backup-boot.timer
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7. Run a first backup to verify
|
||||||
|
|
||||||
|
```bash
|
||||||
|
systemctl --user start restic-backup.service
|
||||||
|
journalctl --user -u restic-backup.service -f
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Useful commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Check timer status
|
||||||
|
systemctl --user status restic-backup.timer restic-backup-boot.timer
|
||||||
|
|
||||||
|
# List all snapshots
|
||||||
|
source <(set -a && cat ~/.config/restic/env) && restic snapshots
|
||||||
|
|
||||||
|
# Browse a snapshot interactively
|
||||||
|
source <(set -a && cat ~/.config/restic/env) && restic mount ~/mnt/restic
|
||||||
|
|
||||||
|
# Restore a single directory
|
||||||
|
source <(set -a && cat ~/.config/restic/env) && \
|
||||||
|
restic restore latest --target /tmp/restore --include "$HOME/.config/nvim"
|
||||||
|
|
||||||
|
# Check repo integrity
|
||||||
|
source <(set -a && cat ~/.config/restic/env) && restic check
|
||||||
|
|
||||||
|
# Watch backup logs live
|
||||||
|
journalctl --user -u restic-backup.service -f
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Password recovery
|
||||||
|
|
||||||
|
Store a `recovery.txt` on TrueNAS alongside the repo — same as the system setup.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# On TrueNAS
|
||||||
|
cp recovery.txt /mnt/pool/backups/recovery-desktop.txt
|
||||||
|
chmod 600 /mnt/pool/backups/recovery-desktop.txt
|
||||||
|
```
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# restic-backup.sh — user-level backup script
|
||||||
|
# Deployed to: ~/.local/bin/restic-backup.sh
|
||||||
|
#
|
||||||
|
# Runs as a regular user — no sudo required.
|
||||||
|
# Config: ~/.config/restic/env and ~/.config/restic/excludes.txt
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*"; }
|
||||||
|
|
||||||
|
# Load environment — set -a auto-exports all variables to child processes
|
||||||
|
set -a && source "${XDG_CONFIG_HOME:-$HOME/.config}/restic/env" && set +a
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Server reachability check with retry
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
BACKUP_HOST="${BACKUP_SERVER%%:*}"
|
||||||
|
BACKUP_PORT="${BACKUP_SERVER##*:}"
|
||||||
|
MAX_RETRIES=3
|
||||||
|
RETRY_MIN=600 # 10 min
|
||||||
|
RETRY_MAX=1800 # 30 min
|
||||||
|
|
||||||
|
server_reachable() {
|
||||||
|
(echo >/dev/tcp/"${BACKUP_HOST}"/"${BACKUP_PORT}") 2>/dev/null
|
||||||
|
}
|
||||||
|
|
||||||
|
wait_for_server() {
|
||||||
|
local attempt=1
|
||||||
|
while ! server_reachable; do
|
||||||
|
if [ "$attempt" -ge "$MAX_RETRIES" ]; then
|
||||||
|
log "ERROR: Backup server ${BACKUP_SERVER} unreachable after ${MAX_RETRIES} attempts. Giving up."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
local delay=$(( RANDOM % (RETRY_MAX - RETRY_MIN + 1) + RETRY_MIN ))
|
||||||
|
log "Backup server unreachable (attempt ${attempt}/${MAX_RETRIES}). Retrying in $(( delay / 60 )) min..."
|
||||||
|
sleep "$delay"
|
||||||
|
(( attempt++ ))
|
||||||
|
done
|
||||||
|
log "Backup server reachable."
|
||||||
|
}
|
||||||
|
|
||||||
|
log "=== Backup started on $(hostname) (${MACHINE_NAME}) as $(whoami) ==="
|
||||||
|
wait_for_server
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Backup
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
log "Running restic backup..."
|
||||||
|
|
||||||
|
# Word-split BACKUP_PATHS intentionally — each path is a separate argument
|
||||||
|
# shellcheck disable=SC2086
|
||||||
|
restic backup \
|
||||||
|
$BACKUP_PATHS \
|
||||||
|
--exclude-file "${XDG_CONFIG_HOME:-$HOME/.config}/restic/excludes.txt" \
|
||||||
|
--one-file-system \
|
||||||
|
--verbose
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Retention
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
log "Applying retention policy (daily=${KEEP_DAILY} weekly=${KEEP_WEEKLY} monthly=${KEEP_MONTHLY})..."
|
||||||
|
|
||||||
|
restic forget \
|
||||||
|
--keep-daily "${KEEP_DAILY}" \
|
||||||
|
--keep-weekly "${KEEP_WEEKLY}" \
|
||||||
|
--keep-monthly "${KEEP_MONTHLY}" \
|
||||||
|
--prune
|
||||||
|
|
||||||
|
log "=== Backup finished on $(hostname) (${MACHINE_NAME}) ==="
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
# =============================================================
|
||||||
|
# Per-machine restic configuration — USER level
|
||||||
|
# Copy to ~/.config/restic/env and fill in all values.
|
||||||
|
# chmod 600 ~/.config/restic/env
|
||||||
|
# =============================================================
|
||||||
|
|
||||||
|
# --- Machine identity ----------------------------------------
|
||||||
|
# Each machine MUST have a unique name (e.g. desktop, laptop, workstation).
|
||||||
|
export MACHINE_NAME=change-me
|
||||||
|
|
||||||
|
# --- REST server ---------------------------------------------
|
||||||
|
export BACKUP_SERVER=nas.box:30248
|
||||||
|
export RESTIC_REPOSITORY=rest:http://oliver:oli1oli1@${BACKUP_SERVER}/${MACHINE_NAME}
|
||||||
|
|
||||||
|
# --- Encryption ----------------------------------------------
|
||||||
|
# Generate with: openssl rand -base64 32
|
||||||
|
# Store in recovery.txt on TrueNAS and your personal password manager.
|
||||||
|
export RESTIC_PASSWORD=change-me
|
||||||
|
|
||||||
|
# --- Paths to back up ----------------------------------------
|
||||||
|
# $HOME is expanded at runtime — adjust to what matters to you.
|
||||||
|
export BACKUP_PATHS="$HOME/.config $HOME/.local/share $HOME/Documents $HOME/Pictures $HOME/Desktop"
|
||||||
|
|
||||||
|
# --- Retention policy ----------------------------------------
|
||||||
|
export KEEP_DAILY=5
|
||||||
|
export KEEP_WEEKLY=3
|
||||||
|
export KEEP_MONTHLY=3
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
# =============================================================
|
||||||
|
# Restic exclude patterns — USER level
|
||||||
|
# Copy to ~/.config/restic/excludes.txt and customize.
|
||||||
|
# =============================================================
|
||||||
|
|
||||||
|
# Caches
|
||||||
|
.cache
|
||||||
|
Cache
|
||||||
|
cache
|
||||||
|
.npm
|
||||||
|
.gradle
|
||||||
|
.maven
|
||||||
|
|
||||||
|
# Browser data (large, recoverable)
|
||||||
|
.config/google-chrome/Default/Cache
|
||||||
|
.config/chromium/Default/Cache
|
||||||
|
.config/BraveSoftware/Brave-Browser/Default/Cache
|
||||||
|
.mozilla/firefox/*.default/cache2
|
||||||
|
|
||||||
|
# Build artefacts
|
||||||
|
node_modules
|
||||||
|
__pycache__
|
||||||
|
*.pyc
|
||||||
|
target/
|
||||||
|
.next/
|
||||||
|
dist/
|
||||||
|
|
||||||
|
# Temporary files
|
||||||
|
*.tmp
|
||||||
|
*.log
|
||||||
|
*.swp
|
||||||
|
|
||||||
|
# Large media you can recover elsewhere
|
||||||
|
*.iso
|
||||||
|
*.img
|
||||||
|
*.vmdk
|
||||||
|
|
||||||
|
# Trash
|
||||||
|
.local/share/Trash
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=Run restic user backup once after login (10-30 min delay)
|
||||||
|
|
||||||
|
[Timer]
|
||||||
|
# OnStartupSec = time since the user's systemd session started (i.e. after login)
|
||||||
|
OnStartupSec=10min
|
||||||
|
# Add a random 0-20 min on top → fires 10-30 min after login
|
||||||
|
RandomizedDelaySec=1200
|
||||||
|
Unit=restic-backup.service
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=timers.target
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=Restic user backup to TrueNAS
|
||||||
|
After=network-online.target
|
||||||
|
Wants=network-online.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=oneshot
|
||||||
|
EnvironmentFile=%h/.config/restic/env
|
||||||
|
ExecStart=%h/.local/bin/restic-backup.sh
|
||||||
|
|
||||||
|
# Restic cache lives naturally in ~/.cache when $HOME is set — no override needed
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=Run restic user backup daily at 02:00
|
||||||
|
|
||||||
|
[Timer]
|
||||||
|
OnCalendar=*-*-* 02:00:00
|
||||||
|
# Do NOT set Persistent=true — boot timer handles the catch-up for personal machines
|
||||||
|
RandomizedDelaySec=600
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=timers.target
|
||||||
@@ -0,0 +1,214 @@
|
|||||||
|
# Restic backup → TrueNAS REST server
|
||||||
|
|
||||||
|
Generic backup setup for any Linux machine (server, laptop, VM).
|
||||||
|
Backs up over WireGuard to a restic REST server running on TrueNAS.
|
||||||
|
|
||||||
|
## File overview
|
||||||
|
|
||||||
|
| File | Purpose | Edit per machine? |
|
||||||
|
|---|---|---|
|
||||||
|
| `backup.sh` | Backup script | ❌ Never |
|
||||||
|
| `restic-backup.service` | Systemd service | ❌ Never |
|
||||||
|
| `restic-backup.timer` | Daily timer (02:00) | ❌ Never |
|
||||||
|
| `restic-backup-boot.timer` | Boot timer — personal machines only | ❌ Never |
|
||||||
|
| `env.example` | Machine config template | ✅ Yes — copy & fill in |
|
||||||
|
| `excludes.txt` | Exclude patterns template | ✅ Yes — copy & customize |
|
||||||
|
| `recovery.txt` | Emergency credentials | ✅ Yes — fill in, store on TrueNAS |
|
||||||
|
|
||||||
|
`backup.sh`, the service, and the timers are **identical on every machine**.
|
||||||
|
Only the env and excludes files are machine-specific.
|
||||||
|
|
||||||
|
### Which timers to install
|
||||||
|
|
||||||
|
| Machine type | `restic-backup.timer` | `restic-backup-boot.timer` |
|
||||||
|
|---|---|---|
|
||||||
|
| **Server** (always on) | ✅ | ❌ |
|
||||||
|
| **Personal** (laptop, desktop) | ✅ | ✅ |
|
||||||
|
|
||||||
|
For personal machines the daily timer covers the case where the machine
|
||||||
|
happens to be on at 02:00 (e.g. left overnight), while the boot timer
|
||||||
|
ensures a backup runs whenever you start the machine during the day.
|
||||||
|
|
||||||
|
> **Note for personal machines:** `Persistent=true` in the daily timer
|
||||||
|
> means systemd will catch up a missed 02:00 run at next boot — which
|
||||||
|
> would fire at the same time as the boot timer. Disable it on personal
|
||||||
|
> machines:
|
||||||
|
> ```bash
|
||||||
|
> sudo systemctl edit restic-backup.timer
|
||||||
|
> # Add:
|
||||||
|
> # [Timer]
|
||||||
|
> # Persistent=false
|
||||||
|
> ```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
- TrueNAS REST server reachable (confirmed ✅ at `nas.box:30248`)
|
||||||
|
- WireGuard tunnel active
|
||||||
|
- `restic` installed
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Debian / Ubuntu
|
||||||
|
apt install restic
|
||||||
|
|
||||||
|
# Arch
|
||||||
|
pacman -S restic
|
||||||
|
|
||||||
|
# Any distro — latest binary from GitHub
|
||||||
|
# https://github.com/restic/restic/releases
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Setup (repeat for each machine)
|
||||||
|
|
||||||
|
### 1. Create the config directory
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo mkdir -p /etc/restic
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Install and fill in the env file
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo cp env.example /etc/restic/env
|
||||||
|
sudo nano /etc/restic/env
|
||||||
|
sudo chmod 600 /etc/restic/env
|
||||||
|
```
|
||||||
|
|
||||||
|
Set these values:
|
||||||
|
- `MACHINE_NAME` — unique name for this machine (e.g. `netcup`, `laptop`, `homeserver`)
|
||||||
|
- `RESTIC_PASSWORD` — generate with `openssl rand -base64 32`
|
||||||
|
- `BACKUP_PATHS` — space-separated list of paths to back up
|
||||||
|
|
||||||
|
### 3. Install the excludes file
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo cp excludes.txt /etc/restic/excludes.txt
|
||||||
|
# Edit to add any machine-specific paths to skip
|
||||||
|
sudo nano /etc/restic/excludes.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Initialize the repository on the REST server
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo bash -c 'set -a && source /etc/restic/env && restic init'
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. Install the backup script
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo cp backup.sh /usr/local/bin/restic-backup.sh
|
||||||
|
sudo chmod +x /usr/local/bin/restic-backup.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6. Install the systemd units
|
||||||
|
|
||||||
|
**Server:**
|
||||||
|
```bash
|
||||||
|
sudo cp restic-backup.service /etc/systemd/system/
|
||||||
|
sudo cp restic-backup.timer /etc/systemd/system/
|
||||||
|
|
||||||
|
sudo systemctl daemon-reload
|
||||||
|
sudo systemctl enable --now restic-backup.timer
|
||||||
|
```
|
||||||
|
|
||||||
|
**Personal machine (laptop / desktop):**
|
||||||
|
```bash
|
||||||
|
sudo cp restic-backup.service /etc/systemd/system/
|
||||||
|
sudo cp restic-backup.timer /etc/systemd/system/
|
||||||
|
sudo cp restic-backup-boot.timer /etc/systemd/system/
|
||||||
|
|
||||||
|
sudo systemctl daemon-reload
|
||||||
|
sudo systemctl enable --now restic-backup.timer
|
||||||
|
sudo systemctl enable --now restic-backup-boot.timer
|
||||||
|
|
||||||
|
# Disable catch-up on the daily timer to avoid double backup at boot
|
||||||
|
sudo systemctl edit restic-backup.timer
|
||||||
|
# Add these lines, save and close:
|
||||||
|
# [Timer]
|
||||||
|
# Persistent=false
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7. Run a first backup to verify
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo systemctl start restic-backup.service
|
||||||
|
sudo journalctl -u restic-backup.service -f
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Useful commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Check timer status and next run time
|
||||||
|
systemctl status restic-backup.timer restic-backup-boot.timer
|
||||||
|
|
||||||
|
# List all snapshots
|
||||||
|
sudo bash -c 'set -a && source /etc/restic/env && restic snapshots'
|
||||||
|
|
||||||
|
# Browse a snapshot interactively
|
||||||
|
sudo bash -c 'set -a && source /etc/restic/env && restic mount /mnt/restic'
|
||||||
|
|
||||||
|
# Restore a single file or directory
|
||||||
|
sudo bash -c 'set -a && source /etc/restic/env && restic restore latest --target /tmp/restore --include /etc/wireguard'
|
||||||
|
|
||||||
|
# Check repo integrity
|
||||||
|
sudo bash -c 'set -a && source /etc/restic/env && restic check'
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Password recovery — avoid the bootstrap trap
|
||||||
|
|
||||||
|
If your password manager runs on the machine being backed up, losing that
|
||||||
|
machine means losing access to the password — and the repo is unrecoverable.
|
||||||
|
|
||||||
|
**Solution:** store `recovery.txt` on TrueNAS, outside the restic repo.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# On TrueNAS — one file per machine
|
||||||
|
cp recovery.txt /mnt/pool/backups/recovery-netcup.txt
|
||||||
|
chmod 600 /mnt/pool/backups/recovery-netcup.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
/mnt/pool/backups/
|
||||||
|
├── netcup/ ← restic repo (encrypted)
|
||||||
|
├── laptop/ ← restic repo (encrypted)
|
||||||
|
├── recovery-netcup.txt ← credentials + restore steps
|
||||||
|
└── recovery-laptop.txt ← credentials + restore steps
|
||||||
|
```
|
||||||
|
|
||||||
|
**Recommended redundancy:**
|
||||||
|
|
||||||
|
| Copy | Survives |
|
||||||
|
|---|---|
|
||||||
|
| TrueNAS `recovery-<machine>.txt` | Machine loss |
|
||||||
|
| Personal device password manager | TrueNAS loss |
|
||||||
|
| Printed in a safe | Everything digital |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Server reconstruction
|
||||||
|
|
||||||
|
On a fresh machine:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Install restic
|
||||||
|
apt install restic
|
||||||
|
|
||||||
|
# 2. Restore all files
|
||||||
|
RESTIC_PASSWORD=<from recovery.txt> \
|
||||||
|
restic -r rest:http://oliver:oli1oli1@nas.box:30248/<MACHINE_NAME> \
|
||||||
|
restore latest --target /
|
||||||
|
|
||||||
|
# 3. Reinstall packages (Debian/Ubuntu)
|
||||||
|
dpkg --set-selections < /etc/backup-package-list.txt
|
||||||
|
apt-get dselect-upgrade
|
||||||
|
|
||||||
|
# 4. Reload systemd
|
||||||
|
systemctl daemon-reload
|
||||||
|
```
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# restic-backup.sh — generic backup script
|
||||||
|
# Deployed to: /usr/local/bin/restic-backup.sh
|
||||||
|
#
|
||||||
|
# All machine-specific config lives in /etc/restic/env and
|
||||||
|
# /etc/restic/excludes.txt — this script never needs editing.
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*"; }
|
||||||
|
|
||||||
|
# Load environment — set -a auto-exports all variables to child processes
|
||||||
|
set -a && source /etc/restic/env && set +a
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Server reachability check with retry
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
BACKUP_HOST="${BACKUP_SERVER%%:*}"
|
||||||
|
BACKUP_PORT="${BACKUP_SERVER##*:}"
|
||||||
|
MAX_RETRIES=3
|
||||||
|
RETRY_MIN=600 # 10 min
|
||||||
|
RETRY_MAX=1800 # 30 min
|
||||||
|
|
||||||
|
server_reachable() {
|
||||||
|
# Dependency-free TCP check using bash built-ins
|
||||||
|
(echo >/dev/tcp/"${BACKUP_HOST}"/"${BACKUP_PORT}") 2>/dev/null
|
||||||
|
}
|
||||||
|
|
||||||
|
wait_for_server() {
|
||||||
|
local attempt=1
|
||||||
|
while ! server_reachable; do
|
||||||
|
if [ "$attempt" -ge "$MAX_RETRIES" ]; then
|
||||||
|
log "ERROR: Backup server ${BACKUP_SERVER} unreachable after ${MAX_RETRIES} attempts. Giving up."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
local delay=$(( RANDOM % (RETRY_MAX - RETRY_MIN + 1) + RETRY_MIN ))
|
||||||
|
log "Backup server unreachable (attempt ${attempt}/${MAX_RETRIES}). Retrying in $(( delay / 60 )) min..."
|
||||||
|
sleep "$delay"
|
||||||
|
(( attempt++ ))
|
||||||
|
done
|
||||||
|
log "Backup server reachable."
|
||||||
|
}
|
||||||
|
|
||||||
|
log "=== Backup started on $(hostname) (${MACHINE_NAME}) ==="
|
||||||
|
wait_for_server
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Pre-backup hooks — dump system state so it's included in the snapshot
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
# Debian / Ubuntu
|
||||||
|
if command -v dpkg &>/dev/null; then
|
||||||
|
log "Dumping installed packages (dpkg)..."
|
||||||
|
dpkg --get-selections > /etc/backup-package-list.txt
|
||||||
|
# RHEL / Fedora / CentOS
|
||||||
|
elif command -v rpm &>/dev/null; then
|
||||||
|
log "Dumping installed packages (rpm)..."
|
||||||
|
rpm -qa > /etc/backup-package-list.txt
|
||||||
|
# Arch Linux
|
||||||
|
elif command -v pacman &>/dev/null; then
|
||||||
|
log "Dumping installed packages (pacman)..."
|
||||||
|
pacman -Qqe > /etc/backup-package-list.txt
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Backup
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
log "Running restic backup..."
|
||||||
|
|
||||||
|
# Word-split BACKUP_PATHS intentionally — each path is a separate argument
|
||||||
|
# shellcheck disable=SC2086
|
||||||
|
restic backup \
|
||||||
|
$BACKUP_PATHS \
|
||||||
|
--exclude-file /etc/restic/excludes.txt \
|
||||||
|
--one-file-system \
|
||||||
|
--verbose
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Retention
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
log "Applying retention policy (daily=${KEEP_DAILY} weekly=${KEEP_WEEKLY} monthly=${KEEP_MONTHLY})..."
|
||||||
|
|
||||||
|
restic forget \
|
||||||
|
--keep-daily "${KEEP_DAILY}" \
|
||||||
|
--keep-weekly "${KEEP_WEEKLY}" \
|
||||||
|
--keep-monthly "${KEEP_MONTHLY}" \
|
||||||
|
--prune
|
||||||
|
|
||||||
|
log "=== Backup finished on $(hostname) (${MACHINE_NAME}) ==="
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
# =============================================================
|
||||||
|
# Per-machine restic configuration
|
||||||
|
# Copy to /etc/restic/env and fill in all values.
|
||||||
|
# chmod 600 /etc/restic/env
|
||||||
|
# =============================================================
|
||||||
|
|
||||||
|
# --- Machine identity ----------------------------------------
|
||||||
|
# Used as the repo name on the REST server.
|
||||||
|
# Each machine MUST have a unique name (e.g. netcup, laptop, homeserver).
|
||||||
|
export MACHINE_NAME=change-me
|
||||||
|
|
||||||
|
# --- REST server ---------------------------------------------
|
||||||
|
# BACKUP_SERVER is used for reachability checks (host:port).
|
||||||
|
export BACKUP_SERVER=nas.box:30248
|
||||||
|
export RESTIC_REPOSITORY=rest:http://oliver:oli1oli1@${BACKUP_SERVER}/${MACHINE_NAME}
|
||||||
|
|
||||||
|
# --- Encryption ----------------------------------------------
|
||||||
|
# Generate with: openssl rand -base64 32
|
||||||
|
# Store in recovery.txt on TrueNAS and your personal password manager.
|
||||||
|
export RESTIC_PASSWORD=change-me
|
||||||
|
|
||||||
|
# --- Paths to back up ----------------------------------------
|
||||||
|
# Space-separated list of absolute paths.
|
||||||
|
export BACKUP_PATHS="/etc /root /var/spool/cron"
|
||||||
|
|
||||||
|
# --- Retention policy ----------------------------------------
|
||||||
|
export KEEP_DAILY=5
|
||||||
|
export KEEP_WEEKLY=3
|
||||||
|
export KEEP_MONTHLY=3
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
# =============================================================
|
||||||
|
# Restic exclude patterns
|
||||||
|
# Copy to /etc/restic/excludes.txt and customize per machine.
|
||||||
|
# Patterns without a leading / match anywhere in the path.
|
||||||
|
# Patterns with a leading / match from the root of the backup.
|
||||||
|
# =============================================================
|
||||||
|
|
||||||
|
# Caches
|
||||||
|
.cache
|
||||||
|
.npm
|
||||||
|
.gradle
|
||||||
|
|
||||||
|
# Build artefacts
|
||||||
|
node_modules
|
||||||
|
__pycache__
|
||||||
|
*.pyc
|
||||||
|
target/
|
||||||
|
.next/
|
||||||
|
|
||||||
|
# Temporary files
|
||||||
|
*.tmp
|
||||||
|
*.log
|
||||||
|
*.swp
|
||||||
|
|
||||||
|
# Large data you can re-generate or re-pull
|
||||||
|
*.iso
|
||||||
|
*.img
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
# ============================================================
|
||||||
|
# RESTIC RECOVERY CREDENTIALS
|
||||||
|
# Store this file on TrueNAS at:
|
||||||
|
# /mnt/pool/backups/recovery.txt
|
||||||
|
# chmod 600 /mnt/pool/backups/recovery.txt
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
## Restic repository
|
||||||
|
RESTIC_REPOSITORY=rest:http://oliver:oli1oli1@nas.box:30248/netcup
|
||||||
|
|
||||||
|
## Restic encryption password ← fill this in before storing
|
||||||
|
RESTIC_PASSWORD=xxx
|
||||||
|
|
||||||
|
## REST server
|
||||||
|
Host: nas.box:30248
|
||||||
|
Username: oliver
|
||||||
|
Password: oli1oli1
|
||||||
|
|
||||||
|
## Retention policy
|
||||||
|
keep-daily: 5
|
||||||
|
keep-weekly: 3
|
||||||
|
keep-monthly: 3
|
||||||
|
|
||||||
|
## Backup paths (for reference when rebuilding)
|
||||||
|
/etc
|
||||||
|
/home/oliver
|
||||||
|
/opt
|
||||||
|
/var/data
|
||||||
|
/var/spool/cron
|
||||||
|
/root
|
||||||
|
|
||||||
|
## To restore on a fresh server:
|
||||||
|
##
|
||||||
|
## 1. Install restic
|
||||||
|
## apt install restic
|
||||||
|
##
|
||||||
|
## 2. Restore all files
|
||||||
|
## RESTIC_PASSWORD=<password above> \
|
||||||
|
## restic -r rest:http://oliver:oli1oli1@nas.box:30248/netcup \
|
||||||
|
## restore latest --target /
|
||||||
|
##
|
||||||
|
## 3. Reinstall packages
|
||||||
|
## dpkg --set-selections < /etc/backup-package-list.txt
|
||||||
|
## apt-get dselect-upgrade
|
||||||
|
##
|
||||||
|
## 4. Reload systemd
|
||||||
|
## systemctl daemon-reload
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=Run restic backup once after boot (10-30 min delay)
|
||||||
|
|
||||||
|
[Timer]
|
||||||
|
# Start no earlier than 10 min after boot
|
||||||
|
OnBootSec=10min
|
||||||
|
# Add a random 0-20 min on top → fires somewhere between 10 and 30 min after boot
|
||||||
|
RandomizedDelaySec=1200
|
||||||
|
Unit=restic-backup.service
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=timers.target
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=Restic backup to TrueNAS
|
||||||
|
After=network-online.target
|
||||||
|
Wants=network-online.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=oneshot
|
||||||
|
EnvironmentFile=/etc/restic/env
|
||||||
|
ExecStart=/usr/local/bin/restic-backup.sh
|
||||||
|
|
||||||
|
# Give restic a cache directory (avoids "no $HOME" warning)
|
||||||
|
CacheDirectory=restic
|
||||||
|
Environment=RESTIC_CACHE_DIR=/var/cache/restic
|
||||||
|
|
||||||
|
# Security hardening
|
||||||
|
PrivateTmp=true
|
||||||
|
NoNewPrivileges=true
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=Run restic backup daily at 02:00
|
||||||
|
|
||||||
|
[Timer]
|
||||||
|
# Run every day at 05:00
|
||||||
|
OnCalendar=*-*-* 05:00:00
|
||||||
|
# If the server was off at 05:00, run as soon as it's back up
|
||||||
|
Persistent=true
|
||||||
|
# Random delay up to 10 min to avoid exact-time load spikes
|
||||||
|
RandomizedDelaySec=600
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=timers.target
|
||||||
Reference in New Issue
Block a user