Compare commits

...

5 commits

9 changed files with 951 additions and 4 deletions

View file

@ -90,9 +90,7 @@ require("lazy").setup({
{ "wellle/targets.vim" }, { "wellle/targets.vim" },
{ {
"junegunn/fzf", "junegunn/fzf",
build = function() build = "./install --bin",
vim.fn["fzf#install"]()
end,
}, },
{ "junegunn/fzf.vim" }, { "junegunn/fzf.vim" },
{ "jremmen/vim-ripgrep" }, { "jremmen/vim-ripgrep" },
@ -404,6 +402,9 @@ vim.g.syntastic_enable_elixir_checker = 1
vim.g.syntastic_elixir_checkers = { "elixir" } vim.g.syntastic_elixir_checkers = { "elixir" }
vim.g.LargeFile = 20 vim.g.LargeFile = 20
vim.g.airline_powerline_fonts = 1 vim.g.airline_powerline_fonts = 1
local airline_symbols = vim.g.airline_symbols or {}
airline_symbols.branch = "\u{e0a0}"
vim.g.airline_symbols = airline_symbols
vim.g.airline_section_x = "" vim.g.airline_section_x = ""
vim.g.airline_section_y = "" vim.g.airline_section_y = ""
vim.g.airline_section_warning = "" vim.g.airline_section_warning = ""
@ -1033,4 +1034,4 @@ vim.cmd("hi GitSignsChangeVirtLnInline ctermfg=blue ctermbg=none guibg=#0043AE g
-- Colorscheme application -- Colorscheme application
pcall(vim.cmd, "colorscheme codedark") pcall(vim.cmd, "colorscheme codedark")
_G.SetTransparent() _G.SetTransparent()
vim.cmd("hi CursorLine guibg=NONE ctermbg=NONE gui=bold,undercurl cterm=bold guisp=#777777") vim.cmd("hi CursorLine guibg=NONE ctermbg=NONE gui=bold cterm=bold guisp=#777777")

65
aurora/.stignore Normal file
View file

@ -0,0 +1,65 @@
// =============================================
// 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

View file

@ -0,0 +1,2 @@
# 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"

View file

@ -0,0 +1,18 @@
[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

199
aurora/backup-to-portable Executable file
View file

@ -0,0 +1,199 @@
#!/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

352
aurora/restore-from-portable Executable file
View file

@ -0,0 +1,352 @@
#!/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 Executable file
View file

@ -0,0 +1,205 @@
#!/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 ""

40
install-interface-design.sh Executable file
View file

@ -0,0 +1,40 @@
#!/usr/bin/env bash
set -euo pipefail
REPO="https://github.com/Dammyjay93/interface-design.git"
SKILL_DIR="${HOME}/.config/opencode/skills/interface-design"
CMD_DIR="${HOME}/.config/opencode/commands"
TMP_DIR=$(mktemp -d)
cleanup() { rm -rf "$TMP_DIR"; }
trap cleanup EXIT
echo "Installing interface-design skill for opencode..."
# Clone latest
git clone --depth 1 --quiet "$REPO" "$TMP_DIR" 2>/dev/null || {
echo "Error: Failed to clone $REPO" >&2
exit 1
}
# Create directories
mkdir -p "$SKILL_DIR/references" "$SKILL_DIR/reference/examples" "$CMD_DIR"
# Install skill files
cp "$TMP_DIR/.claude/skills/interface-design/SKILL.md" "$SKILL_DIR/"
cp "$TMP_DIR/.claude/skills/interface-design/references/"*.md "$SKILL_DIR/references/"
cp "$TMP_DIR/reference/system-template.md" "$SKILL_DIR/reference/"
cp "$TMP_DIR/reference/examples/"*.md "$SKILL_DIR/reference/examples/"
# Install commands, fixing the relative path to skill
for cmd in "$TMP_DIR/.claude/commands/"*.md; do
filename=$(basename "$cmd")
sed 's|\.\./skills/interface-design/SKILL\.md|~/.config/opencode/skills/interface-design/SKILL.md|g' \
"$cmd" > "$CMD_DIR/$filename"
done
echo "Done. Installed to:"
echo " Skill: $SKILL_DIR/"
echo " Commands: $CMD_DIR/"
echo ""
echo "Restart opencode to pick up changes."

65
restore-backup.sh Executable file
View file

@ -0,0 +1,65 @@
#!/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"