first commit

This commit is contained in:
Oliver Walter
2026-06-17 01:54:53 +02:00
commit 1ea0d846bb
15 changed files with 742 additions and 0 deletions
+214
View File
@@ -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
```
+92
View File
@@ -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}) ==="
+29
View File
@@ -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
+27
View File
@@ -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
+47
View File
@@ -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
+12
View File
@@ -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
+17
View File
@@ -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
+13
View File
@@ -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