Compare commits
No commits in common. "75d4c9ab031c35a74ed3fa04f00b8fdc840396d1" and "1a1e467feb94a77d2fe6536eae01ddc724610efc" have entirely different histories.
75d4c9ab03
...
1a1e467feb
7 changed files with 0 additions and 906 deletions
|
|
@ -1,65 +0,0 @@
|
||||||
// =============================================
|
|
||||||
// Syncthing excludes for home directory sync
|
|
||||||
// Everything NOT listed here will be synced
|
|
||||||
// =============================================
|
|
||||||
|
|
||||||
// --- Caches (all regeneratable) ---
|
|
||||||
.cache
|
|
||||||
.bun
|
|
||||||
.npm
|
|
||||||
.nvm
|
|
||||||
go
|
|
||||||
.local/share/Trash
|
|
||||||
|
|
||||||
// --- Package managers / build artifacts ---
|
|
||||||
**/node_modules
|
|
||||||
**/__pycache__
|
|
||||||
**/.venv
|
|
||||||
**/target
|
|
||||||
**/.tox
|
|
||||||
**/.mypy_cache
|
|
||||||
**/.pytest_cache
|
|
||||||
**/dist
|
|
||||||
**/build
|
|
||||||
**/.next
|
|
||||||
**/.turbo
|
|
||||||
|
|
||||||
// --- AI tool session logs (large, not portable) ---
|
|
||||||
.local/share/opencode
|
|
||||||
.local/share/claude
|
|
||||||
.opencode
|
|
||||||
.claude
|
|
||||||
|
|
||||||
// --- Containers (rebuild per-machine) ---
|
|
||||||
.local/share/containers
|
|
||||||
|
|
||||||
// --- Flatpak app data (reinstall regenerates) ---
|
|
||||||
.var/app/com.valvesoftware.Steam
|
|
||||||
.var/app/com.microsoft.Edge
|
|
||||||
.var/app/com.google.Chrome
|
|
||||||
.var/app/com.stremio.Stremio
|
|
||||||
.var/app/org.mozilla.firefox
|
|
||||||
|
|
||||||
// --- Machine-specific / KDE desktop state ---
|
|
||||||
.local/share/baloo
|
|
||||||
.local/share/kactivitymanagerd
|
|
||||||
.local/share/klipper
|
|
||||||
.local/share/recently-used.xbel
|
|
||||||
auto-cpufreq
|
|
||||||
|
|
||||||
// --- SQLite DBs (live sync corrupts) ---
|
|
||||||
.open-webui
|
|
||||||
|
|
||||||
// --- Transient ---
|
|
||||||
Downloads
|
|
||||||
|
|
||||||
// --- Syncthing own data ---
|
|
||||||
.config/syncthing
|
|
||||||
.local/state/syncthing
|
|
||||||
|
|
||||||
// --- Common junk patterns ---
|
|
||||||
*.swp
|
|
||||||
*.swo
|
|
||||||
*~
|
|
||||||
.DS_Store
|
|
||||||
Thumbs.db
|
|
||||||
|
|
@ -1,2 +0,0 @@
|
||||||
# Trigger backup when Seagate Portable drive is connected
|
|
||||||
ACTION=="add", SUBSYSTEM=="block", ENV{ID_SERIAL_SHORT}=="NT3D9HDX", ENV{DEVTYPE}=="partition", TAG+="systemd", ENV{SYSTEMD_WANTS}="backup-portable.service"
|
|
||||||
|
|
@ -1,18 +0,0 @@
|
||||||
[Unit]
|
|
||||||
Description=Backup home to Portable drive (BTRFS send/receive)
|
|
||||||
# Give the drive a moment to fully settle after plug-in
|
|
||||||
After=local-fs.target
|
|
||||||
|
|
||||||
[Service]
|
|
||||||
Type=oneshot
|
|
||||||
ExecStartPre=/bin/sleep 5
|
|
||||||
ExecStart=/usr/local/bin/backup-to-portable
|
|
||||||
TimeoutStartSec=7200
|
|
||||||
Nice=10
|
|
||||||
IOSchedulingClass=idle
|
|
||||||
IOSchedulingPriority=7
|
|
||||||
# Don't kill backup if user logs out
|
|
||||||
KillMode=process
|
|
||||||
|
|
||||||
[Install]
|
|
||||||
WantedBy=multi-user.target
|
|
||||||
|
|
@ -1,199 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
# =============================================================================
|
|
||||||
# Backup to Portable Drive
|
|
||||||
# BTRFS snapshot + send/receive with LUKS encryption
|
|
||||||
# For Aurora (Universal Blue) on BTRFS
|
|
||||||
# =============================================================================
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
# --- Configuration ---
|
|
||||||
LUKS_DEVICE="/dev/disk/by-id/usb-Seagate_Portable_NT3D9HDX-0:0-part1"
|
|
||||||
LUKS_KEYFILE="/etc/backup-drive.key"
|
|
||||||
LUKS_NAME="backup-drive"
|
|
||||||
BACKUP_MOUNT="/mnt/backup-drive"
|
|
||||||
BTRFS_DEVICE="/dev/nvme0n1p3"
|
|
||||||
BTRFS_TOP="/mnt/btrfs-root"
|
|
||||||
SNAP_DIR="snapshots"
|
|
||||||
KEEP_LOCAL=2 # local snapshots (need at least 1 for incremental parent)
|
|
||||||
KEEP_REMOTE=10 # remote snapshots on backup drive
|
|
||||||
TIMESTAMP=$(date +%Y%m%d-%H%M%S)
|
|
||||||
LOGFILE="/var/log/backup-portable.log"
|
|
||||||
LOCK="/run/backup-portable.lock"
|
|
||||||
|
|
||||||
# --- Helpers ---
|
|
||||||
log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOGFILE"; }
|
|
||||||
|
|
||||||
notify() {
|
|
||||||
local urgency="${3:-normal}"
|
|
||||||
local uid bus
|
|
||||||
uid=$(id -u joey 2>/dev/null) || return 0
|
|
||||||
bus="unix:path=/run/user/${uid}/bus"
|
|
||||||
sudo -u joey DBUS_SESSION_BUS_ADDRESS="$bus" \
|
|
||||||
notify-send -u "$urgency" -i drive-removable-media "$1" "$2" 2>/dev/null || true
|
|
||||||
}
|
|
||||||
|
|
||||||
die() { log "ERROR: $*"; notify "Backup Failed" "$*" critical; exit 1; }
|
|
||||||
|
|
||||||
cleanup() {
|
|
||||||
log "Cleaning up mounts..."
|
|
||||||
mountpoint -q "$BACKUP_MOUNT" 2>/dev/null && umount "$BACKUP_MOUNT" || true
|
|
||||||
mountpoint -q "$BTRFS_TOP" 2>/dev/null && umount "$BTRFS_TOP" || true
|
|
||||||
[ -e "/dev/mapper/$LUKS_NAME" ] && cryptsetup luksClose "$LUKS_NAME" 2>/dev/null || true
|
|
||||||
rm -f "$LOCK"
|
|
||||||
}
|
|
||||||
trap cleanup EXIT
|
|
||||||
|
|
||||||
# --- Preflight ---
|
|
||||||
[ "$(id -u)" -eq 0 ] || die "Must run as root"
|
|
||||||
|
|
||||||
# Single-instance lock
|
|
||||||
if [ -f "$LOCK" ]; then
|
|
||||||
pid=$(cat "$LOCK" 2>/dev/null || true)
|
|
||||||
if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null; then
|
|
||||||
die "Backup already running (PID $pid)"
|
|
||||||
fi
|
|
||||||
log "Removing stale lock"
|
|
||||||
rm -f "$LOCK"
|
|
||||||
fi
|
|
||||||
echo $$ > "$LOCK"
|
|
||||||
|
|
||||||
# Check drive is present
|
|
||||||
[ -e "$LUKS_DEVICE" ] || die "Backup drive not connected"
|
|
||||||
|
|
||||||
log "========================================="
|
|
||||||
log "Starting backup: $TIMESTAMP"
|
|
||||||
log "========================================="
|
|
||||||
notify "Backup Started — Do NOT unplug drive" "Backing up home to Portable drive..." critical
|
|
||||||
|
|
||||||
# --- Open LUKS ---
|
|
||||||
if [ ! -e "/dev/mapper/$LUKS_NAME" ]; then
|
|
||||||
log "Opening LUKS volume..."
|
|
||||||
cryptsetup luksOpen --key-file "$LUKS_KEYFILE" "$LUKS_DEVICE" "$LUKS_NAME" \
|
|
||||||
|| die "Failed to open LUKS volume"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# --- Mount filesystems ---
|
|
||||||
mkdir -p "$BACKUP_MOUNT" "$BTRFS_TOP"
|
|
||||||
|
|
||||||
if ! mountpoint -q "$BACKUP_MOUNT"; then
|
|
||||||
mount /dev/mapper/"$LUKS_NAME" "$BACKUP_MOUNT" \
|
|
||||||
|| die "Failed to mount backup drive"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if ! mountpoint -q "$BTRFS_TOP"; then
|
|
||||||
mount -o subvolid=5 "$BTRFS_DEVICE" "$BTRFS_TOP" \
|
|
||||||
|| die "Failed to mount source BTRFS top-level"
|
|
||||||
fi
|
|
||||||
|
|
||||||
mkdir -p "$BTRFS_TOP/$SNAP_DIR"
|
|
||||||
|
|
||||||
# --- Create read-only snapshot of home ---
|
|
||||||
SNAP_NAME="home-$TIMESTAMP"
|
|
||||||
log "Creating snapshot: $SNAP_NAME"
|
|
||||||
btrfs subvolume snapshot -r "$BTRFS_TOP/home" "$BTRFS_TOP/$SNAP_DIR/$SNAP_NAME" \
|
|
||||||
|| die "Failed to create snapshot"
|
|
||||||
|
|
||||||
# --- Send snapshot to backup drive ---
|
|
||||||
# Find parent for incremental send: latest snapshot that exists on BOTH local and remote
|
|
||||||
PARENT_FLAG=""
|
|
||||||
LATEST_REMOTE=$(ls -1d "$BACKUP_MOUNT/backups"/home-* 2>/dev/null | sort | tail -1 || true)
|
|
||||||
if [ -n "$LATEST_REMOTE" ]; then
|
|
||||||
PARENT_NAME=$(basename "$LATEST_REMOTE")
|
|
||||||
if [ -d "$BTRFS_TOP/$SNAP_DIR/$PARENT_NAME" ]; then
|
|
||||||
PARENT_FLAG="-p $BTRFS_TOP/$SNAP_DIR/$PARENT_NAME"
|
|
||||||
log "Incremental send (parent: $PARENT_NAME)"
|
|
||||||
else
|
|
||||||
log "Parent snapshot not found locally, falling back to full send"
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
log "No remote snapshots found, performing full send"
|
|
||||||
fi
|
|
||||||
|
|
||||||
log "Sending snapshot to backup drive..."
|
|
||||||
# shellcheck disable=SC2086
|
|
||||||
btrfs send $PARENT_FLAG "$BTRFS_TOP/$SNAP_DIR/$SNAP_NAME" \
|
|
||||||
| btrfs receive "$BACKUP_MOUNT/backups/" \
|
|
||||||
|| die "btrfs send/receive failed"
|
|
||||||
|
|
||||||
log "Snapshot sent successfully"
|
|
||||||
|
|
||||||
# --- Save metadata ---
|
|
||||||
log "Saving metadata..."
|
|
||||||
META_DIR="$BACKUP_MOUNT/metadata/$TIMESTAMP"
|
|
||||||
mkdir -p "$META_DIR"
|
|
||||||
|
|
||||||
# Flatpak apps
|
|
||||||
flatpak list --app --columns=application > "$META_DIR/flatpak-apps.txt" 2>/dev/null || true
|
|
||||||
|
|
||||||
# rpm-ostree status
|
|
||||||
rpm-ostree status > "$META_DIR/rpm-ostree-status.txt" 2>/dev/null || true
|
|
||||||
|
|
||||||
# Layered packages
|
|
||||||
rpm-ostree status --json 2>/dev/null | python3 -c "
|
|
||||||
import sys, json
|
|
||||||
try:
|
|
||||||
d = json.load(sys.stdin)
|
|
||||||
pkgs = d.get('deployments', [{}])[0].get('requested-packages', [])
|
|
||||||
print('\n'.join(pkgs))
|
|
||||||
except: pass
|
|
||||||
" > "$META_DIR/layered-packages.txt" 2>/dev/null || true
|
|
||||||
|
|
||||||
# /etc (small but important on ostree systems)
|
|
||||||
tar czf "$META_DIR/etc-backup.tar.gz" -C / etc/ 2>/dev/null || true
|
|
||||||
|
|
||||||
# Hostname + date for identification
|
|
||||||
echo "$(hostname) - $(date)" > "$META_DIR/info.txt"
|
|
||||||
|
|
||||||
# --- Cleanup old local snapshots ---
|
|
||||||
# Keep KEEP_LOCAL most recent, but ALWAYS keep the one matching latest remote
|
|
||||||
log "Cleaning up old local snapshots..."
|
|
||||||
mapfile -t LOCAL_SNAPS < <(ls -1d "$BTRFS_TOP/$SNAP_DIR"/home-* 2>/dev/null | sort)
|
|
||||||
LOCAL_COUNT=${#LOCAL_SNAPS[@]}
|
|
||||||
|
|
||||||
if [ "$LOCAL_COUNT" -gt "$KEEP_LOCAL" ]; then
|
|
||||||
# The latest local snap is the one we just created, keep it
|
|
||||||
# Also keep the most recent remote's parent if different
|
|
||||||
for ((i=0; i < LOCAL_COUNT - KEEP_LOCAL; i++)); do
|
|
||||||
OLD_SNAP="${LOCAL_SNAPS[$i]}"
|
|
||||||
OLD_NAME=$(basename "$OLD_SNAP")
|
|
||||||
# Don't delete if it's still needed as a remote parent reference
|
|
||||||
if [ -d "$BACKUP_MOUNT/backups/$OLD_NAME" ]; then
|
|
||||||
log "Keeping local snapshot $OLD_NAME (exists on remote)"
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
log "Deleting old local snapshot: $OLD_NAME"
|
|
||||||
btrfs subvolume delete "$OLD_SNAP" 2>/dev/null || true
|
|
||||||
done
|
|
||||||
fi
|
|
||||||
|
|
||||||
# --- Cleanup old remote snapshots ---
|
|
||||||
log "Cleaning up old remote snapshots..."
|
|
||||||
mapfile -t REMOTE_SNAPS < <(ls -1d "$BACKUP_MOUNT/backups"/home-* 2>/dev/null | sort)
|
|
||||||
REMOTE_COUNT=${#REMOTE_SNAPS[@]}
|
|
||||||
|
|
||||||
if [ "$REMOTE_COUNT" -gt "$KEEP_REMOTE" ]; then
|
|
||||||
for ((i=0; i < REMOTE_COUNT - KEEP_REMOTE; i++)); do
|
|
||||||
OLD_REMOTE="${REMOTE_SNAPS[$i]}"
|
|
||||||
log "Deleting old remote snapshot: $(basename "$OLD_REMOTE")"
|
|
||||||
btrfs subvolume delete "$OLD_REMOTE" 2>/dev/null || true
|
|
||||||
done
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Cleanup old metadata dirs
|
|
||||||
mapfile -t META_DIRS < <(ls -1d "$BACKUP_MOUNT/metadata"/20* 2>/dev/null | sort)
|
|
||||||
META_COUNT=${#META_DIRS[@]}
|
|
||||||
if [ "$META_COUNT" -gt "$KEEP_REMOTE" ]; then
|
|
||||||
for ((i=0; i < META_COUNT - KEEP_REMOTE; i++)); do
|
|
||||||
rm -rf "${META_DIRS[$i]}"
|
|
||||||
done
|
|
||||||
fi
|
|
||||||
|
|
||||||
# --- Final sync ---
|
|
||||||
log "Syncing drive..."
|
|
||||||
sync
|
|
||||||
USAGE=$(df -h /dev/mapper/"$LUKS_NAME" | tail -1 | awk '{print $3 " used / " $2 " total (" $5 ")"}')
|
|
||||||
DURATION=$(( $(date +%s) - $(date -d "${TIMESTAMP:0:8} ${TIMESTAMP:9:2}:${TIMESTAMP:11:2}:${TIMESTAMP:13:2}" +%s) ))
|
|
||||||
DURATION_MIN=$(( DURATION / 60 ))
|
|
||||||
log "Backup complete: $SNAP_NAME (${DURATION_MIN}m)"
|
|
||||||
log "Drive usage: $USAGE"
|
|
||||||
notify "Backup Complete — Safe to unplug" "Snapshot: $SNAP_NAME\nDrive: $USAGE\nDuration: ${DURATION_MIN} min" critical
|
|
||||||
|
|
@ -1,352 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
# =============================================================================
|
|
||||||
# Restore from Portable Backup Drive
|
|
||||||
# Browse snapshots, mount for file recovery, or full home restore
|
|
||||||
# =============================================================================
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
# --- Configuration ---
|
|
||||||
LUKS_DEVICE="/dev/disk/by-id/usb-Seagate_Portable_NT3D9HDX-0:0-part1"
|
|
||||||
LUKS_KEYFILE="/etc/backup-drive.key"
|
|
||||||
LUKS_NAME="backup-drive"
|
|
||||||
BACKUP_MOUNT="/mnt/backup-drive"
|
|
||||||
BTRFS_DEVICE="/dev/nvme0n1p3"
|
|
||||||
BTRFS_TOP="/mnt/btrfs-root"
|
|
||||||
BROWSE_MOUNT="/mnt/backup-browse"
|
|
||||||
|
|
||||||
# --- Helpers ---
|
|
||||||
bold() { echo -e "\033[1m$*\033[0m"; }
|
|
||||||
green() { echo -e "\033[32m$*\033[0m"; }
|
|
||||||
red() { echo -e "\033[31m$*\033[0m"; }
|
|
||||||
yellow(){ echo -e "\033[33m$*\033[0m"; }
|
|
||||||
|
|
||||||
die() { red "ERROR: $*"; exit 1; }
|
|
||||||
|
|
||||||
cleanup() {
|
|
||||||
mountpoint -q "$BROWSE_MOUNT" 2>/dev/null && umount "$BROWSE_MOUNT" 2>/dev/null || true
|
|
||||||
# Don't auto-close LUKS/unmount backup in restore mode — user may still need it
|
|
||||||
}
|
|
||||||
trap cleanup EXIT
|
|
||||||
|
|
||||||
# --- Preflight ---
|
|
||||||
[ "$(id -u)" -eq 0 ] || die "Must run as root (sudo)"
|
|
||||||
[ -e "$LUKS_DEVICE" ] || die "Backup drive not connected"
|
|
||||||
|
|
||||||
# --- Open LUKS + Mount ---
|
|
||||||
if [ ! -e "/dev/mapper/$LUKS_NAME" ]; then
|
|
||||||
echo "Opening LUKS volume..."
|
|
||||||
if [ -f "$LUKS_KEYFILE" ]; then
|
|
||||||
cryptsetup luksOpen --key-file "$LUKS_KEYFILE" "$LUKS_DEVICE" "$LUKS_NAME"
|
|
||||||
else
|
|
||||||
echo "Keyfile not found. Enter passphrase:"
|
|
||||||
cryptsetup luksOpen "$LUKS_DEVICE" "$LUKS_NAME"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
mkdir -p "$BACKUP_MOUNT"
|
|
||||||
if ! mountpoint -q "$BACKUP_MOUNT"; then
|
|
||||||
mount /dev/mapper/"$LUKS_NAME" "$BACKUP_MOUNT"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# --- List snapshots ---
|
|
||||||
list_snapshots() {
|
|
||||||
bold "\nAvailable backup snapshots:"
|
|
||||||
echo "─────────────────────────────────────────"
|
|
||||||
local i=1
|
|
||||||
mapfile -t SNAPSHOTS < <(ls -1d "$BACKUP_MOUNT/backups"/home-* 2>/dev/null | sort -r)
|
|
||||||
|
|
||||||
if [ ${#SNAPSHOTS[@]} -eq 0 ]; then
|
|
||||||
die "No snapshots found on backup drive"
|
|
||||||
fi
|
|
||||||
|
|
||||||
for snap in "${SNAPSHOTS[@]}"; do
|
|
||||||
local name date_str
|
|
||||||
name=$(basename "$snap")
|
|
||||||
# Parse timestamp from name: home-YYYYMMDD-HHMMSS
|
|
||||||
date_str=$(echo "$name" | sed 's/home-//' | sed 's/\([0-9]\{8\}\)-\([0-9]\{2\}\)\([0-9]\{2\}\)\([0-9]\{2\}\)/\1 \2:\3:\4/')
|
|
||||||
printf " %2d) %s (%s)\n" "$i" "$name" "$date_str"
|
|
||||||
((i++))
|
|
||||||
done
|
|
||||||
echo ""
|
|
||||||
}
|
|
||||||
|
|
||||||
# --- Show metadata for a snapshot ---
|
|
||||||
show_metadata() {
|
|
||||||
local snap_name="$1"
|
|
||||||
local ts="${snap_name#home-}"
|
|
||||||
local meta_dir="$BACKUP_MOUNT/metadata/$ts"
|
|
||||||
|
|
||||||
if [ -d "$meta_dir" ]; then
|
|
||||||
bold "\nMetadata for $snap_name:"
|
|
||||||
echo "─────────────────────────────────────────"
|
|
||||||
[ -f "$meta_dir/info.txt" ] && echo " Host: $(cat "$meta_dir/info.txt")"
|
|
||||||
[ -f "$meta_dir/flatpak-apps.txt" ] && echo " Flatpaks: $(wc -l < "$meta_dir/flatpak-apps.txt") apps"
|
|
||||||
[ -f "$meta_dir/layered-packages.txt" ] && echo " Layered pkgs: $(cat "$meta_dir/layered-packages.txt" | grep -c . || echo 0)"
|
|
||||||
[ -f "$meta_dir/etc-backup.tar.gz" ] && echo " /etc backup: $(du -h "$meta_dir/etc-backup.tar.gz" | cut -f1)"
|
|
||||||
echo ""
|
|
||||||
else
|
|
||||||
yellow " (no metadata for this snapshot)"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# --- Browse a snapshot (mount read-only) ---
|
|
||||||
browse_snapshot() {
|
|
||||||
local snap_path="$1"
|
|
||||||
local snap_name
|
|
||||||
snap_name=$(basename "$snap_path")
|
|
||||||
|
|
||||||
mkdir -p "$BROWSE_MOUNT"
|
|
||||||
if mountpoint -q "$BROWSE_MOUNT"; then
|
|
||||||
umount "$BROWSE_MOUNT"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Bind-mount the snapshot for easy browsing
|
|
||||||
mount --bind "$snap_path" "$BROWSE_MOUNT"
|
|
||||||
|
|
||||||
green "\nSnapshot mounted read-only at: $BROWSE_MOUNT"
|
|
||||||
echo ""
|
|
||||||
echo "You can now browse and copy files:"
|
|
||||||
echo " ls $BROWSE_MOUNT/"
|
|
||||||
echo " cp $BROWSE_MOUNT/joey/Documents/file.txt ~/Documents/"
|
|
||||||
echo ""
|
|
||||||
yellow "When done, run: sudo umount $BROWSE_MOUNT"
|
|
||||||
echo "Or press Enter here to unmount and return to menu."
|
|
||||||
read -r
|
|
||||||
umount "$BROWSE_MOUNT" 2>/dev/null || true
|
|
||||||
}
|
|
||||||
|
|
||||||
# --- Restore specific files/directories ---
|
|
||||||
restore_files() {
|
|
||||||
local snap_path="$1"
|
|
||||||
local snap_name
|
|
||||||
snap_name=$(basename "$snap_path")
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
bold "Restore files from $snap_name"
|
|
||||||
echo "Enter path relative to home (e.g., joey/Documents, joey/.config/Code):"
|
|
||||||
read -r rel_path
|
|
||||||
|
|
||||||
[ -z "$rel_path" ] && { yellow "No path entered."; return; }
|
|
||||||
|
|
||||||
local src="$snap_path/$rel_path"
|
|
||||||
if [ ! -e "$src" ]; then
|
|
||||||
red "Path not found in snapshot: $rel_path"
|
|
||||||
return
|
|
||||||
fi
|
|
||||||
|
|
||||||
local dest_base="/var/home"
|
|
||||||
local dest="$dest_base/$rel_path"
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
echo "Source: $src"
|
|
||||||
echo "Destination: $dest"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
if [ -e "$dest" ]; then
|
|
||||||
yellow "WARNING: Destination exists and will be overwritten."
|
|
||||||
echo -n "Create backup of current version first? [Y/n] "
|
|
||||||
read -r yn
|
|
||||||
if [ "$yn" != "n" ] && [ "$yn" != "N" ]; then
|
|
||||||
local backup="${dest}.pre-restore-$(date +%Y%m%d-%H%M%S)"
|
|
||||||
cp -a "$dest" "$backup"
|
|
||||||
green "Current version backed up to: $backup"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo -n "Proceed with restore? [y/N] "
|
|
||||||
read -r confirm
|
|
||||||
if [ "$confirm" = "y" ] || [ "$confirm" = "Y" ]; then
|
|
||||||
if [ -d "$src" ]; then
|
|
||||||
mkdir -p "$(dirname "$dest")"
|
|
||||||
rsync -a --delete "$src/" "$dest/"
|
|
||||||
else
|
|
||||||
mkdir -p "$(dirname "$dest")"
|
|
||||||
cp -a "$src" "$dest"
|
|
||||||
fi
|
|
||||||
# Restore ownership to joey
|
|
||||||
chown -R joey:joey "$dest"
|
|
||||||
green "Restored: $rel_path"
|
|
||||||
else
|
|
||||||
yellow "Cancelled."
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# --- Full home restore via btrfs send/receive ---
|
|
||||||
full_restore() {
|
|
||||||
local snap_path="$1"
|
|
||||||
local snap_name
|
|
||||||
snap_name=$(basename "$snap_path")
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
red "╔══════════════════════════════════════════════════════╗"
|
|
||||||
red "║ FULL HOME RESTORE — THIS IS DESTRUCTIVE ║"
|
|
||||||
red "║ Your current /var/home will be replaced entirely. ║"
|
|
||||||
red "║ A snapshot of the current state will be saved. ║"
|
|
||||||
red "╚══════════════════════════════════════════════════════╝"
|
|
||||||
echo ""
|
|
||||||
echo "Restoring from: $snap_name"
|
|
||||||
echo ""
|
|
||||||
echo -n "Type 'RESTORE' to confirm: "
|
|
||||||
read -r confirm
|
|
||||||
[ "$confirm" = "RESTORE" ] || { yellow "Cancelled."; return; }
|
|
||||||
|
|
||||||
# Mount top-level BTRFS
|
|
||||||
mkdir -p "$BTRFS_TOP"
|
|
||||||
if ! mountpoint -q "$BTRFS_TOP"; then
|
|
||||||
mount -o subvolid=5 "$BTRFS_DEVICE" "$BTRFS_TOP"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Snapshot current home as safety net
|
|
||||||
local safety_name="home-pre-restore-$(date +%Y%m%d-%H%M%S)"
|
|
||||||
echo "Creating safety snapshot of current home: $safety_name"
|
|
||||||
btrfs subvolume snapshot -r "$BTRFS_TOP/home" "$BTRFS_TOP/snapshots/$safety_name"
|
|
||||||
green "Safety snapshot created: $safety_name"
|
|
||||||
|
|
||||||
# Strategy: receive the backup snapshot, then swap subvolumes
|
|
||||||
echo "Receiving snapshot from backup drive..."
|
|
||||||
local restore_name="home-restoring-$(date +%Y%m%d-%H%M%S)"
|
|
||||||
|
|
||||||
btrfs send "$snap_path" | btrfs receive "$BTRFS_TOP/snapshots/" \
|
|
||||||
|| die "btrfs receive failed"
|
|
||||||
|
|
||||||
# The received snapshot is read-only. Create a writable snapshot from it.
|
|
||||||
echo "Creating writable home from snapshot..."
|
|
||||||
|
|
||||||
# Rename current home subvolume out of the way
|
|
||||||
mv "$BTRFS_TOP/home" "$BTRFS_TOP/home-old-$(date +%Y%m%d-%H%M%S)"
|
|
||||||
|
|
||||||
# Create writable snapshot as new home
|
|
||||||
btrfs subvolume snapshot "$BTRFS_TOP/snapshots/$snap_name" "$BTRFS_TOP/home"
|
|
||||||
|
|
||||||
green ""
|
|
||||||
green "═══════════════════════════════════════════"
|
|
||||||
green " Full restore complete!"
|
|
||||||
green " Old home saved as: home-old-*"
|
|
||||||
green " REBOOT to use restored home."
|
|
||||||
green "═══════════════════════════════════════════"
|
|
||||||
echo ""
|
|
||||||
yellow "After reboot, once verified, clean up old home with:"
|
|
||||||
echo " sudo mount -o subvolid=5 /dev/nvme0n1p3 /mnt/btrfs-root"
|
|
||||||
echo " sudo btrfs subvolume delete /mnt/btrfs-root/home-old-*"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
umount "$BTRFS_TOP" 2>/dev/null || true
|
|
||||||
}
|
|
||||||
|
|
||||||
# --- Restore system metadata ---
|
|
||||||
restore_metadata() {
|
|
||||||
local snap_name="$1"
|
|
||||||
local ts="${snap_name#home-}"
|
|
||||||
local meta_dir="$BACKUP_MOUNT/metadata/$ts"
|
|
||||||
|
|
||||||
[ -d "$meta_dir" ] || { red "No metadata found for $snap_name"; return; }
|
|
||||||
|
|
||||||
bold "\nRestore system metadata from $snap_name"
|
|
||||||
echo "─────────────────────────────────────────"
|
|
||||||
echo " 1) Reinstall flatpak apps"
|
|
||||||
echo " 2) Reinstall layered rpm-ostree packages"
|
|
||||||
echo " 3) Restore /etc from backup"
|
|
||||||
echo " 4) Show all metadata (view only)"
|
|
||||||
echo " b) Back"
|
|
||||||
echo ""
|
|
||||||
echo -n "Choice: "
|
|
||||||
read -r choice
|
|
||||||
|
|
||||||
case "$choice" in
|
|
||||||
1)
|
|
||||||
if [ -f "$meta_dir/flatpak-apps.txt" ]; then
|
|
||||||
echo "Installing flatpak apps..."
|
|
||||||
while IFS= read -r app; do
|
|
||||||
[ -z "$app" ] && continue
|
|
||||||
echo " Installing: $app"
|
|
||||||
flatpak install -y flathub "$app" 2>/dev/null || yellow " Failed: $app"
|
|
||||||
done < "$meta_dir/flatpak-apps.txt"
|
|
||||||
green "Done."
|
|
||||||
fi
|
|
||||||
;;
|
|
||||||
2)
|
|
||||||
if [ -f "$meta_dir/layered-packages.txt" ] && [ -s "$meta_dir/layered-packages.txt" ]; then
|
|
||||||
echo "Layered packages to install:"
|
|
||||||
cat "$meta_dir/layered-packages.txt"
|
|
||||||
echo ""
|
|
||||||
echo -n "Install all? [y/N] "
|
|
||||||
read -r yn
|
|
||||||
if [ "$yn" = "y" ] || [ "$yn" = "Y" ]; then
|
|
||||||
# shellcheck disable=SC2046
|
|
||||||
rpm-ostree install $(cat "$meta_dir/layered-packages.txt" | tr '\n' ' ')
|
|
||||||
yellow "Reboot required to apply layered packages."
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
yellow "No layered packages recorded."
|
|
||||||
fi
|
|
||||||
;;
|
|
||||||
3)
|
|
||||||
if [ -f "$meta_dir/etc-backup.tar.gz" ]; then
|
|
||||||
red "WARNING: This will overwrite files in /etc"
|
|
||||||
echo -n "Proceed? [y/N] "
|
|
||||||
read -r yn
|
|
||||||
if [ "$yn" = "y" ] || [ "$yn" = "Y" ]; then
|
|
||||||
tar xzf "$meta_dir/etc-backup.tar.gz" -C /
|
|
||||||
green "/etc restored."
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
;;
|
|
||||||
4)
|
|
||||||
echo ""
|
|
||||||
[ -f "$meta_dir/info.txt" ] && { bold "System info:"; cat "$meta_dir/info.txt"; echo ""; }
|
|
||||||
[ -f "$meta_dir/flatpak-apps.txt" ] && { bold "Flatpak apps:"; cat "$meta_dir/flatpak-apps.txt"; echo ""; }
|
|
||||||
[ -f "$meta_dir/layered-packages.txt" ] && { bold "Layered packages:"; cat "$meta_dir/layered-packages.txt"; echo ""; }
|
|
||||||
[ -f "$meta_dir/rpm-ostree-status.txt" ] && { bold "rpm-ostree status:"; cat "$meta_dir/rpm-ostree-status.txt"; echo ""; }
|
|
||||||
;;
|
|
||||||
b|B) return ;;
|
|
||||||
esac
|
|
||||||
}
|
|
||||||
|
|
||||||
# ═══════════════════════════════════════════
|
|
||||||
# Main Menu
|
|
||||||
# ═══════════════════════════════════════════
|
|
||||||
while true; do
|
|
||||||
list_snapshots
|
|
||||||
|
|
||||||
bold "Actions:"
|
|
||||||
echo " 1) Browse snapshot (mount read-only, copy files manually)"
|
|
||||||
echo " 2) Restore specific files/directories"
|
|
||||||
echo " 3) Full home restore (replace entire /var/home)"
|
|
||||||
echo " 4) Restore system metadata (flatpaks, packages, /etc)"
|
|
||||||
echo " q) Quit"
|
|
||||||
echo ""
|
|
||||||
echo -n "Choice: "
|
|
||||||
read -r action
|
|
||||||
|
|
||||||
case "$action" in
|
|
||||||
q|Q)
|
|
||||||
echo "Closing backup drive..."
|
|
||||||
umount "$BACKUP_MOUNT" 2>/dev/null || true
|
|
||||||
cryptsetup luksClose "$LUKS_NAME" 2>/dev/null || true
|
|
||||||
green "Done."
|
|
||||||
exit 0
|
|
||||||
;;
|
|
||||||
1|2|3|4)
|
|
||||||
echo -n "Snapshot number: "
|
|
||||||
read -r num
|
|
||||||
if [[ ! "$num" =~ ^[0-9]+$ ]] || [ "$num" -lt 1 ] || [ "$num" -gt "${#SNAPSHOTS[@]}" ]; then
|
|
||||||
red "Invalid selection."
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
SELECTED="${SNAPSHOTS[$((num-1))]}"
|
|
||||||
SELECTED_NAME=$(basename "$SELECTED")
|
|
||||||
|
|
||||||
show_metadata "$SELECTED_NAME"
|
|
||||||
|
|
||||||
case "$action" in
|
|
||||||
1) browse_snapshot "$SELECTED" ;;
|
|
||||||
2) restore_files "$SELECTED" ;;
|
|
||||||
3) full_restore "$SELECTED" ;;
|
|
||||||
4) restore_metadata "$SELECTED_NAME" ;;
|
|
||||||
esac
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
red "Invalid choice."
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
done
|
|
||||||
205
aurora/setup.sh
205
aurora/setup.sh
|
|
@ -1,205 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
# =============================================================================
|
|
||||||
# Aurora Machine Setup — Backup Drive + Syncthing
|
|
||||||
# Run after a fresh Aurora install or home restore
|
|
||||||
# Usage: sudo bash setup.sh
|
|
||||||
# =============================================================================
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
||||||
|
|
||||||
bold() { echo -e "\033[1m$*\033[0m"; }
|
|
||||||
green() { echo -e "\033[32m$*\033[0m"; }
|
|
||||||
yellow(){ echo -e "\033[33m$*\033[0m"; }
|
|
||||||
red() { echo -e "\033[31m$*\033[0m"; }
|
|
||||||
|
|
||||||
die() { red "ERROR: $*"; exit 1; }
|
|
||||||
|
|
||||||
[ "$(id -u)" -eq 0 ] || die "Run with sudo: sudo bash $0"
|
|
||||||
|
|
||||||
REAL_USER="${SUDO_USER:-joey}"
|
|
||||||
REAL_HOME=$(eval echo "~$REAL_USER")
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
bold "========================================="
|
|
||||||
bold " Aurora Machine Setup"
|
|
||||||
bold "========================================="
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# =============================================================================
|
|
||||||
# 1. BACKUP DRIVE SETUP
|
|
||||||
# =============================================================================
|
|
||||||
bold "[1/2] Backup Drive Setup"
|
|
||||||
echo "─────────────────────────────────────────"
|
|
||||||
|
|
||||||
# Install backup + restore scripts
|
|
||||||
cp "$SCRIPT_DIR/backup-to-portable" /usr/local/bin/backup-to-portable
|
|
||||||
cp "$SCRIPT_DIR/restore-from-portable" /usr/local/bin/restore-from-portable
|
|
||||||
chmod 755 /usr/local/bin/backup-to-portable /usr/local/bin/restore-from-portable
|
|
||||||
green " ✓ Installed backup/restore scripts to /usr/local/bin/"
|
|
||||||
|
|
||||||
# Install systemd service
|
|
||||||
cp "$SCRIPT_DIR/backup-portable.service" /etc/systemd/system/backup-portable.service
|
|
||||||
systemctl daemon-reload
|
|
||||||
systemctl enable backup-portable.service
|
|
||||||
green " ✓ Installed and enabled backup-portable.service"
|
|
||||||
|
|
||||||
# Install udev rule
|
|
||||||
cp "$SCRIPT_DIR/99-backup-portable.rules" /etc/udev/rules.d/99-backup-portable.rules
|
|
||||||
udevadm control --reload-rules
|
|
||||||
green " ✓ Installed udev auto-backup rule"
|
|
||||||
|
|
||||||
# Generate LUKS keyfile if not present
|
|
||||||
if [ ! -f /etc/backup-drive.key ]; then
|
|
||||||
dd if=/dev/urandom of=/etc/backup-drive.key bs=4096 count=1 2>/dev/null
|
|
||||||
chmod 400 /etc/backup-drive.key
|
|
||||||
green " ✓ Generated new LUKS keyfile at /etc/backup-drive.key"
|
|
||||||
echo ""
|
|
||||||
yellow " ⚠ New keyfile generated — you need to add it to the backup drive:"
|
|
||||||
echo " 1. Plug in the backup drive"
|
|
||||||
echo " 2. sudo cryptsetup luksAddKey /dev/sdX1 /etc/backup-drive.key"
|
|
||||||
echo " (Enter your existing passphrase when prompted)"
|
|
||||||
echo ""
|
|
||||||
else
|
|
||||||
green " ✓ LUKS keyfile already exists at /etc/backup-drive.key"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Create snapshots directory on source BTRFS
|
|
||||||
BTRFS_DEVICE=$(findmnt -n -o SOURCE /var/home | sed 's/\[.*//')
|
|
||||||
if [ -n "$BTRFS_DEVICE" ]; then
|
|
||||||
mkdir -p /mnt/btrfs-root
|
|
||||||
mount -o subvolid=5 "$BTRFS_DEVICE" /mnt/btrfs-root 2>/dev/null || true
|
|
||||||
if mountpoint -q /mnt/btrfs-root; then
|
|
||||||
mkdir -p /mnt/btrfs-root/snapshots
|
|
||||||
green " ✓ Snapshot directory ready on source BTRFS"
|
|
||||||
umount /mnt/btrfs-root
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Create BTRFS subvolumes for excluded paths (if not already subvolumes)
|
|
||||||
create_exclude_subvol() {
|
|
||||||
local path="$1"
|
|
||||||
local full_path="$REAL_HOME/$path"
|
|
||||||
|
|
||||||
# Skip if path doesn't exist or is already a subvolume
|
|
||||||
[ -d "$full_path" ] || return 0
|
|
||||||
if btrfs subvolume show "$full_path" &>/dev/null; then
|
|
||||||
green " ✓ $path already a BTRFS subvolume (excluded from snapshots)"
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
yellow " Converting $path to BTRFS subvolume (excludes from backup snapshots)..."
|
|
||||||
mv "$full_path" "${full_path}.setup-bak"
|
|
||||||
btrfs subvolume create "$full_path"
|
|
||||||
cp -a "${full_path}.setup-bak/." "$full_path/" 2>/dev/null || true
|
|
||||||
chown "$REAL_USER:$REAL_USER" "$full_path"
|
|
||||||
rm -rf "${full_path}.setup-bak"
|
|
||||||
green " ✓ $path → BTRFS subvolume"
|
|
||||||
}
|
|
||||||
|
|
||||||
create_exclude_subvol ".cache"
|
|
||||||
create_exclude_subvol ".local/share/containers"
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# =============================================================================
|
|
||||||
# 2. SYNCTHING SETUP
|
|
||||||
# =============================================================================
|
|
||||||
bold "[2/2] Syncthing Setup"
|
|
||||||
echo "─────────────────────────────────────────"
|
|
||||||
|
|
||||||
HPSERVER_ID="5JLI4YY-6TKV3VM-ZJ6J3HT-J3P5UBX-ZCO7OUN-REF4O2P-XHZPI6B-4SNFAAU"
|
|
||||||
|
|
||||||
# Install .stignore
|
|
||||||
cp "$SCRIPT_DIR/.stignore" "$REAL_HOME/.stignore"
|
|
||||||
chown "$REAL_USER:$REAL_USER" "$REAL_HOME/.stignore"
|
|
||||||
green " ✓ Installed .stignore"
|
|
||||||
|
|
||||||
# Check if syncthing is available
|
|
||||||
SYNCTHING_BIN=""
|
|
||||||
if command -v syncthing &>/dev/null; then
|
|
||||||
SYNCTHING_BIN="syncthing"
|
|
||||||
elif [ -x /home/linuxbrew/.linuxbrew/bin/syncthing ]; then
|
|
||||||
SYNCTHING_BIN="/home/linuxbrew/.linuxbrew/bin/syncthing"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -z "$SYNCTHING_BIN" ]; then
|
|
||||||
yellow " Syncthing not installed. Installing via brew..."
|
|
||||||
if command -v brew &>/dev/null; then
|
|
||||||
sudo -u "$REAL_USER" brew install syncthing
|
|
||||||
SYNCTHING_BIN="/home/linuxbrew/.linuxbrew/bin/syncthing"
|
|
||||||
elif sudo -u "$REAL_USER" bash -c 'command -v brew' &>/dev/null; then
|
|
||||||
sudo -u "$REAL_USER" brew install syncthing
|
|
||||||
SYNCTHING_BIN="/home/linuxbrew/.linuxbrew/bin/syncthing"
|
|
||||||
else
|
|
||||||
yellow " ⚠ Homebrew not found. Install syncthing manually:"
|
|
||||||
echo " brew install syncthing"
|
|
||||||
echo " Then re-run this script, or manually:"
|
|
||||||
echo " brew services start syncthing"
|
|
||||||
echo " syncthing cli config devices add --device-id $HPSERVER_ID --name hpserver"
|
|
||||||
echo " syncthing cli config devices $HPSERVER_ID addresses add tcp://vgm.joeypayne.com:22000"
|
|
||||||
echo " syncthing cli config folders add --id home-sync --label Home --path ~/"
|
|
||||||
echo " syncthing cli config folders home-sync devices add --device-id $HPSERVER_ID"
|
|
||||||
echo ""
|
|
||||||
bold "Backup setup complete. Syncthing needs manual install."
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
green " ✓ Syncthing found: $($SYNCTHING_BIN --version | head -1)"
|
|
||||||
|
|
||||||
# Start syncthing as user if not running
|
|
||||||
if ! sudo -u "$REAL_USER" systemctl --user is-active syncthing.service &>/dev/null && \
|
|
||||||
! sudo -u "$REAL_USER" systemctl --user is-active homebrew.syncthing.service &>/dev/null; then
|
|
||||||
yellow " Starting syncthing..."
|
|
||||||
sudo -u "$REAL_USER" brew services start syncthing 2>/dev/null || \
|
|
||||||
sudo -u "$REAL_USER" systemctl --user enable --now syncthing.service 2>/dev/null || \
|
|
||||||
yellow " ⚠ Could not auto-start syncthing. Run: brew services start syncthing"
|
|
||||||
sleep 3
|
|
||||||
fi
|
|
||||||
green " ✓ Syncthing running"
|
|
||||||
|
|
||||||
# Configure: add hpserver device + home folder share
|
|
||||||
sudo -u "$REAL_USER" "$SYNCTHING_BIN" cli config devices add \
|
|
||||||
--device-id "$HPSERVER_ID" --name "hpserver" 2>/dev/null || true
|
|
||||||
|
|
||||||
# Add explicit address so hpserver is reachable from anywhere (not just LAN)
|
|
||||||
sudo -u "$REAL_USER" "$SYNCTHING_BIN" cli config devices "$HPSERVER_ID" addresses add \
|
|
||||||
"tcp://vgm.joeypayne.com:22000" 2>/dev/null || true
|
|
||||||
|
|
||||||
sudo -u "$REAL_USER" "$SYNCTHING_BIN" cli config folders add \
|
|
||||||
--id "home-sync" --label "Home" --path "$REAL_HOME" 2>/dev/null || true
|
|
||||||
|
|
||||||
sudo -u "$REAL_USER" "$SYNCTHING_BIN" cli config folders "home-sync" devices add \
|
|
||||||
--device-id "$HPSERVER_ID" 2>/dev/null || true
|
|
||||||
|
|
||||||
green " ✓ Syncthing configured: home-sync → hpserver"
|
|
||||||
|
|
||||||
# Get this device's ID for pairing on hpserver
|
|
||||||
LOCAL_ID=$(sudo -u "$REAL_USER" "$SYNCTHING_BIN" cli config devices list 2>/dev/null | head -1)
|
|
||||||
echo ""
|
|
||||||
yellow " ⚠ If this is a NEW machine, add it to hpserver:"
|
|
||||||
echo " ssh hpserver \"syncthing cli config devices add --device-id '$LOCAL_ID' --name 'NEW-MACHINE-NAME'\""
|
|
||||||
echo " ssh hpserver \"syncthing cli config folders home-sync devices add --device-id '$LOCAL_ID'\""
|
|
||||||
|
|
||||||
# =============================================================================
|
|
||||||
# DONE
|
|
||||||
# =============================================================================
|
|
||||||
echo ""
|
|
||||||
bold "========================================="
|
|
||||||
green " Setup complete!"
|
|
||||||
bold "========================================="
|
|
||||||
echo ""
|
|
||||||
echo " Backup:"
|
|
||||||
echo " • Plug in Seagate Portable → backup runs automatically"
|
|
||||||
echo " • Notification when safe to unplug"
|
|
||||||
echo " • Restore: sudo restore-from-portable"
|
|
||||||
if [ ! -f /etc/backup-drive.key ] || [ "$(stat -c%s /etc/backup-drive.key 2>/dev/null)" = "4096" ]; then
|
|
||||||
echo " • Remember to add keyfile to drive if this is a new machine"
|
|
||||||
fi
|
|
||||||
echo ""
|
|
||||||
echo " Sync:"
|
|
||||||
echo " • Syncthing syncs home → hpserver whenever online"
|
|
||||||
echo " • Excludes: caches, containers, downloads, browser data, AI logs"
|
|
||||||
echo " • Web UI: http://127.0.0.1:8384"
|
|
||||||
echo ""
|
|
||||||
|
|
@ -1,65 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
# Restore from Portable backup drive on a fresh Aurora install
|
|
||||||
# Finds the drive, opens LUKS, mounts, and runs the restore script
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
bold() { echo -e "\033[1m$*\033[0m"; }
|
|
||||||
red() { echo -e "\033[31m$*\033[0m"; }
|
|
||||||
green() { echo -e "\033[32m$*\033[0m"; }
|
|
||||||
|
|
||||||
die() { red "ERROR: $*"; exit 1; }
|
|
||||||
|
|
||||||
[ "$(id -u)" -eq 0 ] || die "Run with sudo: sudo bash restore-backup.sh"
|
|
||||||
|
|
||||||
LUKS_NAME="backup-drive"
|
|
||||||
MOUNT="/mnt/backup-drive"
|
|
||||||
|
|
||||||
# --- Find the drive ---
|
|
||||||
# Try stable by-id path first (Seagate Portable), fall back to scanning for LUKS partitions
|
|
||||||
DEVICE=""
|
|
||||||
for dev in /dev/disk/by-id/usb-Seagate_Portable_NT3D9HDX-*-part1; do
|
|
||||||
[ -e "$dev" ] && DEVICE="$dev" && break
|
|
||||||
done
|
|
||||||
|
|
||||||
if [ -z "$DEVICE" ]; then
|
|
||||||
bold "Seagate Portable not found by ID. Scanning for LUKS partitions..."
|
|
||||||
for part in /dev/sd?1 /dev/nvme?n1p?; do
|
|
||||||
[ -b "$part" ] || continue
|
|
||||||
if cryptsetup isLuks "$part" 2>/dev/null; then
|
|
||||||
echo " Found LUKS partition: $part"
|
|
||||||
DEVICE="$part"
|
|
||||||
break
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
fi
|
|
||||||
|
|
||||||
[ -n "$DEVICE" ] || die "No backup drive found. Plug it in and try again."
|
|
||||||
bold "Using device: $DEVICE"
|
|
||||||
|
|
||||||
# --- Open LUKS ---
|
|
||||||
if [ ! -e "/dev/mapper/$LUKS_NAME" ]; then
|
|
||||||
echo ""
|
|
||||||
bold "Enter your backup drive passphrase:"
|
|
||||||
cryptsetup luksOpen "$DEVICE" "$LUKS_NAME" || die "Failed to unlock drive"
|
|
||||||
fi
|
|
||||||
green "LUKS unlocked."
|
|
||||||
|
|
||||||
# --- Mount ---
|
|
||||||
mkdir -p "$MOUNT"
|
|
||||||
if ! mountpoint -q "$MOUNT"; then
|
|
||||||
mount /dev/mapper/"$LUKS_NAME" "$MOUNT" || die "Failed to mount"
|
|
||||||
fi
|
|
||||||
green "Mounted at $MOUNT"
|
|
||||||
|
|
||||||
# --- Verify it's a backup drive ---
|
|
||||||
if [ ! -d "$MOUNT/backups" ] || [ ! -f "$MOUNT/restore.sh" ]; then
|
|
||||||
die "Drive doesn't look like a backup drive (missing backups/ or restore.sh)"
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
green "Backup drive ready."
|
|
||||||
bold "Launching restore menu..."
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# --- Run restore ---
|
|
||||||
bash "$MOUNT/restore.sh"
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue