#!/usr/bin/env bash # VPS backup installer: encrypted, incremental, daily backups to backup.dei.my (Cloudflare R2). # Usage (as root): curl -fsSL https://backup.dei.my | bash # Re-running is safe. set -euo pipefail SERVER="https://backup.dei.my" REPO_NAME="${VPS_NAME:-vps}" CONF_DIR=/etc/vps-backup ENV_FILE=$CONF_DIR/env say() { printf '\033[1;36m==>\033[0m %s\n' "$*"; } die() { printf '\033[1;31mERROR:\033[0m %s\n' "$*" >&2; exit 1; } [ "$(id -u)" = 0 ] || die "Run as root (or: curl -fsSL $SERVER | sudo bash)" command -v apt-get >/dev/null || die "This installer supports Ubuntu/Debian only." exec 3/dev/null || die "Needs an interactive terminal." urlencode() { local s="$1" out="" c i for ((i = 0; i < ${#s}; i++)); do c="${s:i:1}" case "$c" in [a-zA-Z0-9.~_-]) out+="$c" ;; *) out+=$(printf '%%%02X' "'$c") ;; esac done printf '%s' "$out" } if [ -f "$ENV_FILE" ]; then say "Already set up, keeping existing settings" else echo read -r -s -u 3 -p "Backup password: " PW; echo [ -n "$PW" ] || die "Password required." code=$(curl -s -o /dev/null -w '%{http_code}' -u "vps:$PW" "$SERVER/repo/$REPO_NAME/config") [ "$code" != 401 ] || die "Wrong password." [ "$code" = 200 ] || [ "$code" = 404 ] || die "Could not reach $SERVER (HTTP $code)." mkdir -p "$CONF_DIR"; chmod 700 "$CONF_DIR" umask 077 cat > "$ENV_FILE" </dev/null set -a; . "$ENV_FILE"; set +a if restic cat config >/dev/null 2>&1; then say "Connected to existing backups" else say "Creating encrypted backup storage" restic init >/dev/null || die "Could not create backup storage." fi say "Installing the 'vps-backup' command" cat > /usr/local/bin/vps-backup <<'EOS' #!/usr/bin/env bash # vps-backup: run / list / restore backups stored on backup.dei.my set -euo pipefail [ "$(id -u)" = 0 ] || { echo "Run as root"; exit 1; } set -a; . /etc/vps-backup/env; set +a DUMPS=/var/backups/vps-backup run_backup() { mkdir -p "$DUMPS"; chmod 700 "$DUMPS" if command -v mysqldump >/dev/null && (systemctl is-active -q mysql || systemctl is-active -q mariadb); then echo "Dumping MySQL/MariaDB..." mysqldump --all-databases --single-transaction --routines --events | gzip > "$DUMPS/mysql.sql.gz" || echo "WARNING: MySQL dump failed" fi if command -v pg_dumpall >/dev/null && systemctl is-active -q postgresql; then echo "Dumping PostgreSQL..." su postgres -c pg_dumpall | gzip > "$DUMPS/postgres.sql.gz" || echo "WARNING: PostgreSQL dump failed" fi paths=(/) for m in /boot /boot/efi /home /var /srv /opt; do mountpoint -q "$m" && paths+=("$m"); done restic backup "${paths[@]}" --one-file-system --exclude-caches \ --exclude=/tmp --exclude=/var/tmp --exclude=/swap.img --exclude=/swapfile \ --exclude=/var/cache/apt/archives --exclude=/var/cache/restic --exclude=/root/.cache restic forget --keep-daily 7 --keep-weekly 4 --keep-monthly 6 --prune } case "${1:-help}" in now) run_backup ;; list) restic snapshots ;; files) restic ls "${2:-latest}" ;; restore) [ -n "${2:-}" ] || { echo "Usage: vps-backup restore [backup-id]"; exit 1; } dest=/root/restore-$(date +%Y%m%d-%H%M%S) restic restore "${3:-latest}" --target "$dest" --include "$2" echo "Restored to $dest$2" ;; status) systemctl list-timers vps-backup.timer --no-pager echo; journalctl -u vps-backup.service -n 15 --no-pager ;; check) restic check ;; *) cat < [id] Restore a file/folder into /root/restore-/ vps-backup status Next scheduled run and last log vps-backup check Verify backups are intact EOF ;; esac EOS chmod 755 /usr/local/bin/vps-backup say "Scheduling a daily backup (around 03:30)" cat > /etc/systemd/system/vps-backup.service <<'EOF' [Unit] Description=Back up this server to backup.dei.my Wants=network-online.target After=network-online.target [Service] Type=oneshot ExecStart=/usr/local/bin/vps-backup now Nice=10 IOSchedulingClass=idle EOF cat > /etc/systemd/system/vps-backup.timer <<'EOF' [Unit] Description=Daily VPS backup [Timer] OnCalendar=*-*-* 03:30 RandomizedDelaySec=30m Persistent=true [Install] WantedBy=timers.target EOF systemctl daemon-reload systemctl enable --now vps-backup.timer >/dev/null say "Running the first backup now (this can take a while)..." vps-backup now echo say "Done. Daily backups are on. Type 'vps-backup' for commands."