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
|
||||
Reference in New Issue
Block a user