From 1409d033d1270b2662e8f023c79a0eb7ad1edce2 Mon Sep 17 00:00:00 2001 From: Eric Renfro Date: Sun, 1 Sep 2024 10:50:55 -0400 Subject: [PATCH] Initial fork from resticprofile-base, welcome to systemrescue-backup --- .gitignore | 11 + config/.keep | 0 config/sysrescue/.keep | 0 .../sysrescue/build_into_srm/.squashfs-pseudo | 10 + .../iso_add/sysrescue.d/500-settings.yaml | 15 + init.sh | 14 + install.sh | 18 + scripts/consul | 66 ++ scripts/flatpak | 80 +++ scripts/functions.sh | 84 +++ scripts/gitea | 93 +++ scripts/mysql_backup | 58 ++ scripts/openldap | 74 ++ scripts/os | 100 +++ scripts/os_arch | 46 ++ scripts/os_debian | 150 ++++ scripts/os_fedora | 45 ++ scripts/os_macos | 43 ++ scripts/os_solus | 31 + scripts/os_suse | 172 +++++ scripts/partbackup | 0 scripts/partbackup2 | 0 scripts/partbackup3 | 339 +++++++++ scripts/partbackup4 | 388 +++++++++++ scripts/partbackup5 | 651 ++++++++++++++++++ scripts/postgresql | 70 ++ scripts/run-parts | 101 +++ scripts/vaultwarden | 36 + templates/config/base.cfg | 32 + templates/config/consul_backup.cfg | 3 + templates/config/gitea_backup.cfg | 6 + templates/config/mysql_backup.cfg | 4 + templates/config/postgresql.cfg | 4 + 33 files changed, 2744 insertions(+) create mode 100644 .gitignore create mode 100644 config/.keep create mode 100644 config/sysrescue/.keep create mode 100644 config/sysrescue/build_into_srm/.squashfs-pseudo create mode 100644 config/sysrescue/iso_add/sysrescue.d/500-settings.yaml create mode 100755 init.sh create mode 100755 install.sh create mode 100755 scripts/consul create mode 100755 scripts/flatpak create mode 100644 scripts/functions.sh create mode 100755 scripts/gitea create mode 100755 scripts/mysql_backup create mode 100755 scripts/openldap create mode 100755 scripts/os create mode 100755 scripts/os_arch create mode 100755 scripts/os_debian create mode 100755 scripts/os_fedora create mode 100755 scripts/os_macos create mode 100755 scripts/os_solus create mode 100755 scripts/os_suse create mode 100644 scripts/partbackup create mode 100644 scripts/partbackup2 create mode 100644 scripts/partbackup3 create mode 100644 scripts/partbackup4 create mode 100755 scripts/partbackup5 create mode 100755 scripts/postgresql create mode 100755 scripts/run-parts create mode 100755 scripts/vaultwarden create mode 100644 templates/config/base.cfg create mode 100644 templates/config/consul_backup.cfg create mode 100644 templates/config/gitea_backup.cfg create mode 100644 templates/config/mysql_backup.cfg create mode 100644 templates/config/postgresql.cfg diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bf181c4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ +config/* +!config/.keep +!config/sysrescue/ +!config/sysrescue/.keep +config/sysrescue/build_into_srm/usr/* +config/sysrescue/build_into_srm/root/* +!config/sysrescue/build_into_srm/.squashfs-pseudo +!config/sysrescue/iso_add/* +profiles.toml +key +excludes diff --git a/config/.keep b/config/.keep new file mode 100644 index 0000000..e69de29 diff --git a/config/sysrescue/.keep b/config/sysrescue/.keep new file mode 100644 index 0000000..e69de29 diff --git a/config/sysrescue/build_into_srm/.squashfs-pseudo b/config/sysrescue/build_into_srm/.squashfs-pseudo new file mode 100644 index 0000000..36ca862 --- /dev/null +++ b/config/sysrescue/build_into_srm/.squashfs-pseudo @@ -0,0 +1,10 @@ +/root m 700 root root +/root/.ssh m 700 root root +/root/.ssh/authorized_keys m 600 root root +/root/.config m 755 root root +/root/.config/resticprofile m 700 root root +/root/.config/resticprofile/key m 600 root root +/root/.config/resticprofile/profiles.toml m 500 root root +/root/.config/resticprofile/excludes m 600 root root +/root/.config/resticprofile/root-excludes m 600 root root-excludes + diff --git a/config/sysrescue/iso_add/sysrescue.d/500-settings.yaml b/config/sysrescue/iso_add/sysrescue.d/500-settings.yaml new file mode 100644 index 0000000..8dd21e1 --- /dev/null +++ b/config/sysrescue/iso_add/sysrescue.d/500-settings.yaml @@ -0,0 +1,15 @@ +global: + copytoram: true + checksum: true + loadsrm: true + dostartx: true + setkmap: us + +autorun: + ar_disable: false + ar_nowait: false + ar_nodel: false + ar_attempts: 3 + ar_ignorefail: false + ar_suffixes: 1,2,3 + diff --git a/init.sh b/init.sh new file mode 100755 index 0000000..8fff019 --- /dev/null +++ b/init.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +scriptPath="$(dirname "$(readlink -f "$0")")" + +if [[ ! -f "${scriptPath}/profiles.toml" ]]; then + cp "${scriptPath}/templates/profiles.toml" "${scriptPath}/profiles.toml" +fi +if [[ ! -f "${scriptPath}/excludes" ]]; then + cp "${scriptPath}/templates/excludes" "${scriptPath}/excludes" +fi +if [[ ! -f "${scriptPath}/key" ]]; then + resticprofile generate --random-key > "${scriptPath}/key" +fi +chmod go-rwx "${scriptPath}/key" "${scriptPath}/profiles.toml" "${scriptPath}/excludes" diff --git a/install.sh b/install.sh new file mode 100755 index 0000000..8ed5db6 --- /dev/null +++ b/install.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +resticVersion="0.16.4" +resticprofileVersion="0.27.0" + +mkdir /tmp/restic-install || exit 1 +pushd /tmp/restic-install &>/dev/null || exit 1 +wget "https://github.com/restic/restic/releases/download/v${resticVersion}/restic_${resticVersion}_linux_amd64.bz2" +bunzip2 "restic_${resticVersion}_linux_amd64.bz2" +wget "https://github.com/creativeprojects/resticprofile/releases/download/v${resticprofileVersion}/resticprofile_${resticprofileVersion}_linux_amd64.tar.gz" +tar xvfz "resticprofile_${resticprofileVersion}_linux_amd64.tar.gz" +rm LICENSE README.md "resticprofile_${resticprofileVersion}.tar.gz" +chown root:root restic* +chmod 755 restic* +mv "restic_${resticVersion}_linux_amd64" /usr/local/bin/restic +mv resticprofile /usr/local/bin/resticprofile +popd &>/dev/null +rm -rf /tmp/restic-install diff --git a/scripts/consul b/scripts/consul new file mode 100755 index 0000000..b64bd49 --- /dev/null +++ b/scripts/consul @@ -0,0 +1,66 @@ +#!/bin/bash + +restoreDir=/etc/restore +consulDir="/var/backups/consul" +scriptDir="$(dirname "$0")" + +readConfig() { + if [[ -r "${scriptDir}/../config/consul_backup.cfg" ]]; then + source "${scriptDir}/../config/consul_backup.cfg" + fi +} + +check() { + if [[ -d "$consulDir" ]]; then + echo "Cleaning out old Consul backups..." + rm -f "$consulDir"/* || return 1 + else + mkdir -p "$consulDir" | return 1 + fi + + if [[ -n "$CONSUL_PATH" ]]; then + if [[ -x "${CONSUL_PATH}/consul" ]]; then + echo "Found Consul in $CONSUL_PATH" + else + echo "FATAL: Cannot execute Consul in $CONSUL_PATH" + return 3 + fi + else + if ! which consul &>/dev/null; then + echo "Cannot find Consul in PATH" + return 2 + fi + if [[ -x "$(which consul)" ]]; then + CONSUL_PATH="$(dirname "$(which consul)")" + echo "Found Consul in $CONSUL_PATH" + else + echo "FATAL: Cannot execute Consul" + return 3 + fi + fi +} + +hook_before() { + readConfig + check || exit $? + ${CONSUL_PATH}/consul snapshot save "$consulDir/consul.snap" +} + +hook_after() { + : +} + +hook_fail() { + : +} + +hook_final() { + : +} + +case "$1" in + before) hook_before || exit $?;; + after) hook_after || exit $?;; + fail) hook_fail || exit $?;; + finally) hook_final || exit $?;; +esac diff --git a/scripts/flatpak b/scripts/flatpak new file mode 100755 index 0000000..b790392 --- /dev/null +++ b/scripts/flatpak @@ -0,0 +1,80 @@ +#!/bin/bash + +restoreDir="/etc/restore" + +hook_before() { + mkdir -p "$restoreDir" || exit 1 + pushd "$restoreDir" || exit 2 + + which flatpak || exit 0 + + flatpak list --columns=application --app > flatpaks.lst + + cat > restore-flatpak.sh </dev/null +} + +########################################################### +### BACKUP and RESTORE FUNCTIONS +########################################################### + +#dumpPartitions() { +backup_partitions() { + local rootPart rootDisk + + rootPart="$(findmnt -no SOURCE / | sed -E 's/\[.*\]$//')" + rootDisk="/dev/$(lsblk -no pkname "$rootPart" | head -n1)" + + sfdisk --dump "$rootDisk" > "${restoreDir}/sfdisk.dump" + blkid -o export "$rootPart" > "${restoreDir}/blkid.dump" + btrfs subvolume list -p / > "${restoreDir}/btrfs.dump" + mount | grep "$rootPart" > "${restoreDir}/mounts.dump" +} + +#restorePartitions() { +restore_partitions() { + local rootPart rootDisk + + rootPart="$1" + rootDisk="$2" + + if [[ -z "$rootDisk" || -z "$rootPart" ]]; then + echo "ERROR, restorePartitions not supplied with rootDisk and/or rootPartition" + exit 200 + fi + + # Restore Partition Table (without modification) + sfdisk "$rootDisk" < "${restoreDir}/sfdisk.dump" + + # Restore UUIDs + while read -r line; do + if [[ $line == UUID=* ]]; then + eval "$line" + tune2fs "$rootPart" -U "$UUID" + fi + done < "${restoreDir}/blkid.dump" + + echo "Partition table and UUID information have been restored." +} + +#restoreBtrFSSubvolumes() { +restore_btrfs_subvolumes() { + local rootBase subvolID subvolPath + + rootBase="$1" + + while read -r line; do + # Extract the subvolume ID and Path + subvolID="$(echo "$line" | awk '{print $2}')" + subvolPath="$(echo "$line" | awk '{print $NF}')" + + # Restore the subvolume + btrfs subvolume create "${rootBase}/${subvolPath}" + done < "${restoreDir}/btrfs.dump" +} + +#restoreBtrFSMounts() { +restore_btfs_mounts() { + local rootBase mountSource mountDest + + rootBase="$1" + + while read -r line; do + # Extract mount source and destination + mountSource="$(echo "$line" | awk '{print $1}')" + mountDest="$(echo "$line" | awk '{print $3}')" + + # Mount the subvolume + mount "$mountSource" "${rootBase}/${mountDest}" + done < "$restoreDir/mounts.dump" +} diff --git a/scripts/gitea b/scripts/gitea new file mode 100755 index 0000000..4c26521 --- /dev/null +++ b/scripts/gitea @@ -0,0 +1,93 @@ +#!/bin/bash + +restoreDir=/etc/restore +giteaDir="/var/backups/gitea" +scriptDir="$(dirname "$0")" + +readConfig() { + if [[ -r "${scriptDir}/../config/gitea_backup.cfg" ]]; then + source "${scriptDir}/../config/gitea_backup.cfg" + fi + +} + +check() { + GITEA_CONFIG=${GITEA_CONFIG:-/etc/gitea/app.ini} + if [[ ! -r "$GITEA_CONFIG" ]]; then + echo "ERROR: Cannot read gitea app.ini in $GITEA_CONFIG" + return 4 + fi + + GITEA_USER=${GITEA_USER:-git} + GITEA_GROUP=${GITEA_GROUP:-git} + GITEA_HOME=${GITEA_HOME:-$(getent passwd ${GITEA_USER} | cut -f6 -d:)} + if [[ ! -d "$GITEA_HOME" ]]; then + echo "ERROR: Cannot find Gitea home directory" + return 5 + fi + + if [[ -n "$GITEA_BIN" ]]; then + if [[ -x "${GITEA_BIN}" ]]; then + echo "Found Gitea in $GITEA_BIN" + else + echo "FATAL: Cannot execute Gitea" + return 3 + fi + else + if ! which gitea &>/dev/null; then + echo "Cannot find Gitea in PATH" + return 2 + fi + if [[ -x "$(which gitea)" ]]; then + GITEA_BIN="$(which gitea)" + echo "Found Gitea in $GITEA_BIN" + else + echo "FATAL: Cannot execute Gitea" + return 3 + fi + fi + + if [[ -d "$giteaDir" ]]; then + echo "Cleaning out old Gitea backups..." + rm -f "$giteaDir"/* || return 1 + else + mkdir -p "$giteaDir" | return 1 + chgrp "$GITEA_GROUP" "$giteaDir" + chmod g+wx "$giteaDir" + fi +} + +runBackups() { + local backupDate + + backupDate=$(date +"%Y-%m-%d") + giteaHome=$(getent passwd git | cut -f6 -d:) + pushd "${giteaDir}" &>/dev/null + sudo -u "$GITEA_USER" "${GITEA_BIN}" dump --config "${GITEA_CONFIG}" --type tar.xz --file - > "${giteaDir}/gitea-dump-${backupDate}.tar.xz" + popd &>/dev/null +} + +hook_before() { + check || exit $? + runBackups || exit $? +} + +hook_after() { + : +} + +hook_fail() { + : +} + +hook_final() { + : +} + + +case "$1" in + before) hook_before || exit $?;; + after) hook_after || exit $?;; + fail) hook_fail || exit $?;; + finally) hook_final || exit $?;; +esac diff --git a/scripts/mysql_backup b/scripts/mysql_backup new file mode 100755 index 0000000..74074ed --- /dev/null +++ b/scripts/mysql_backup @@ -0,0 +1,58 @@ +#!/bin/bash + +restoreDir=/etc/restore +mysqlDir="$restoreDir/mysql" +scriptDir="$(dirname "$0")" + +function readConfig() { + if [[ -r "${scriptDir}/../config/mysql_backup.cfg" ]]; then + source "${scriptDir}/../config/mysql_backup.cfg" + fi +} + +function runBackups() { + local dbname + + readConfig + + if [[ ! -d "/var/backups/mysql" ]]; then + mkdir -p "/var/backups/mysql" || return 1 + fi + echo "Clearing out old MySQL backups..." + rm -f /var/backups/mysql/* + + while read dbname + do + case "$dbname" in + sys) continue;; + information_schema) continue;; + performance_schema) continue;; + esac + + echo "Backing up database: $dbname" + mysqldump --complete-insert --routines --triggers --single-transaction "$dbname" > /var/backups/mysql/"$dbname".sql + done < <(mysql -N -e 'show databases') +} + +hook_before() { + runBackups || exit $? +} + +hook_after() { + rm -rf "$mysqlDir" || exit 1 +} + +hook_fail() { + : +} + +hook_final() { + : +} + +case "$1" in + before) hook_before || exit $? ;; + after) hook_after || exit $? ;; + fail) hook_fail || exit $? ;; + finally) hook_final || exit $? ;; +esac diff --git a/scripts/openldap b/scripts/openldap new file mode 100755 index 0000000..a1618c5 --- /dev/null +++ b/scripts/openldap @@ -0,0 +1,74 @@ +#!/bin/bash + +restoreDir=/etc/restore +ldapDir="$restoreDir/openldap" + +hook_before() { + if [[ -d "$ldapDir" ]]; then + rm -rf "$ldapDir" || exit 1 + fi + mkdir -p "$ldapDir" || exit 1 + pushd "$restoreDir" || exit 2 + + slapcat -n 0 -l "$ldapDir/config.ldif" + slapcat -n 1 -l "$ldapDir/data.ldif" + + cat > ldaprestore.sh </dev/null; then + "${scriptPath}/flatpak" "$hook" + fi + fi + return $? +} + +hook_before() { + checkOS || exit 200 + pushd "$scriptPath" &>/dev/null || exit 201 + git checkout -- . + git pull + popd &>/dev/null || exit 201 + + createRestoreDir || exit $? + runOsHook before +} + +hook_fail() { + checkOS || exit 200 + runOsHook fail +} + +hook_after() { + checkOS || exit 200 + backup_partitions || exit 200 + runOsHook after +} + +hook_final() { + checkOS || exit 200 + if [[ ! -f "${restoreDir}/.do-not-delete" ]]; then + rm -rf "$restoreDir" + fi + runOsHook final +} + + +case "$1" in + before) hook_before || exit $?;; + after) hook_after || exit $?;; + fail) hook_final || exit $?;; + finally) hook_final || exit $?;; +esac diff --git a/scripts/os_arch b/scripts/os_arch new file mode 100755 index 0000000..5fd8b54 --- /dev/null +++ b/scripts/os_arch @@ -0,0 +1,46 @@ +#!/bin/bash + +restoreDir="/etc/restore" + +hook_before() { + mkdir -p "$restoreDir" || exit 1 + pushd "$restoreDir" || exit 2 + + pacman -Qqe > "pkglist.txt" + comm -13 <(pacman -Qqdt | sort) <(pacman -Qqdtt | sort) > optdeplist.txt + pacman -Qqem > foreignpkglist.txt + + cat > restore.sh </dev/null +} + +hook_before() { + if ! is_bin_in_path rsync; then + echo "rsync needs to be installed for backups to work properly." + exit 1 + fi + + mkdir -p "$restoreDir" || exit 1 + pushd "$restoreDir" || exit 2 + + dpkg --get-selections | sort > Package.list + apt-mark showmanual | sort > InstallOnly.list + cp -a /etc/apt/sources.list "$restoreDir/" + rsync -avhHi /etc/apt/sources.list.d "$restoreDir/" + rsync -avhHi /etc/apt/trusted.gpg.d "$restoreDir/" + [[ -d /etc/apt/keyrings ]] && rsync -avhHi /etc/apt/keyrings "$restoreDir/" + + cat > restore.sh </dev/null || install+=" rsync" +#dpkg-query -s 'borgbackup' &>/dev/null || install+=" borgbackup" +#dpkg-query -s 'borgmatic' &>/dev/null || install+=" borgmatic" +dpkg-query -s 'apt-transport-https' &>/dev/null || install+=" apt-transport-https" + +if [[ -n "\$install" ]]; then + apt -y install \$install +fi + +echo "\${bold} * Enabling 32-bit packages\${normal}" +grep ':i386' InstallOnly.list &>/dev/null && dpkg --add-architecture i386 + +echo "\${bold} * Checking for flatpak\${normal}" +flatpak=0 +grep 'flatpak' InstallOnly.list &>/dev/null && flatpak=1 +if [[ "\$flatpak" -eq 1 ]]; then + echo " * Adding flatpak repo: Flathub" + flatpak remote-add --if-not-exists flathub https://dl.flathub.org/repo/flathub.flatpakrepo +fi + +if [[ -r "sources.list" ]]; then + echo "\${bold}" + echo "==============================" + echo "INSTALL REPOS" + echo "==============================" + echo "\${normal}" + + read -p "\${bold}Do you want to continue to install backup repositories? [Y/n]\${normal} " -n 1 -sr promptRepos + if [[ "\$promptRepos" =~ ^[Yy]$ ]]; then + echo "Yes" + cp -a sources.list /etc/apt/sources.list + rsync --ignore-existing -raz sources.list.d/ /etc/apt/sources.list.d/ + rsync --ignore-existing -raz trusted.gpg.d/ /etc/apt/trusted.gpg.d/ + [[ -d keyrings ]] && rsync --ignore-existing -raz keyrings/ /etc/apt/keyrings/ + apt update + else + echo -e "Cancelled\n" + fi +fi + +echo "\${bold}" +echo "==============================" +echo "INSTALL PACKAGES" +echo "==============================" +echo "\${normal}" + +read -p "\${bold}About to install the system packages per the backup. Do you want to continue? [Y/n]\${normal} " -n 1 -sr promptPkgs +if [[ "\$promptPkgs" =~ ^[Yy] ]]; then + echo "Yes" + comm --nocheck-order -23 InstallOnly.list <(apt-mark showmanual|sort) | grep -Ev 'linux-image|linux-headers' > "\${TMPDIR}/diff.list" + apt-get --simulate install \$(cat "\${TMPDIR}/diff.list") |& awk '/^E: Unable to locate package / {print \$NF}' | sort > "\${TMPDIR}/diff.fail" + comm --nocheck-order -23 "\${TMPDIR}/diff.list" "\${TMPDIR}/diff.fail" | xargs apt-get install + + echo + echo "Packages that were omitted because they could not be found:" + cat "\${TMPDIR}/diff.fail" | tr '\n' ' ' | fold -s + echo + echo + + read -p "\${bold}Did everything above look okay and do you want to proceed? [Y/n]\${normal} " -n 1 -sr promptPkgsDo + if [[ "\$promptPkgsDo" =~ ^[Yy] ]]; then + comm --nocheck-order -23 "\${TMPDIR}/diff.list" "\${TMPDIR}/diff.fail" | xargs apt-get install + + echo + echo + echo "\${bold}Packages that failed to schedule for install:\${normal}" + cat "\${TMPDIR}/diff.fail" | tr '\n' ' ' | fold -s + echo + echo + else + echo -e "Cancelled\n" + fi +else + echo -e "Cancelled\n" +fi +EOF + chmod ug+rx restore.sh + popd || exit 2 +} + +hook_after() { + : +} + +hook_fail() { + : +} + +hook_final() { + : +} + +case "$1" in + before) hook_before || exit $?;; + after) hook_after || exit $?;; + fail) hook_final || exit $?;; + finally) hook_final || exit $?;; +esac diff --git a/scripts/os_fedora b/scripts/os_fedora new file mode 100755 index 0000000..095dd0e --- /dev/null +++ b/scripts/os_fedora @@ -0,0 +1,45 @@ +#!/bin/bash + +restoreDir="/etc/restore" + +hook_before() { + mkdir -p "$restoreDir" || exit 1 + pushd "$restoreDir" || exit 2 + + rpm -qa | sort > Package.versions.list + rpm -qa --queryformat '%{NAME}.%{ARCH}\n' | sort > Package.list + #FIXME Need to put this to use for explicitely installed packages. + dnf repoquery --userinstalled > Package.userinstalled.list + + cat > restore.sh < restore.sh < Packages.list + + popd || exit 2 +} + +hook_after() { + : +} + +hook_fail() { + : +} + +hook_final() { + : +} + +case "$1" in + before) hook_before || exit $?;; + after) hook_after || exit $?;; + fail) hook_fail || exit $?;; + finally) hook_final || exit $?;; +esac diff --git a/scripts/os_suse b/scripts/os_suse new file mode 100755 index 0000000..8a51b3c --- /dev/null +++ b/scripts/os_suse @@ -0,0 +1,172 @@ +#!/bin/bash + +restoreDir="/etc/restore" + +hook_before() { + mkdir -p "$restoreDir" || exit 1 + pushd "$restoreDir" || exit 2 + + rpm -qa | sort > Package.versions.list + rpm -qa --queryformat '%{NAME}.%{ARCH}\n' | sort > Package.all.list + zypper se -i -t pattern | awk '/^i\+/ { print $3 }' | sort > Pattern.list + zypper se -i -t package | awk '/^i\+/ { print $3 }' | sort > Package.list + zypper lr -e Backup.repos + zypper ll | awk '/.*\| package.*/ { print $3 }' > Package.lock + + cat > restore.sh < /tmp/Package.new.list + echo "Here's a list of packages that would be removed:" + comm -13 Package.all.list /tmp/Package.new.list + + read -p "Do you want to omit any removals or cancel? [N, y, c] " -n 1 -sr promptRmVerify + if [[ "\$promptRmVerify" =~ ^[Yy]$ ]]; then + echo + read -p "What do you want to omit, seperated by spaces? " -r promptOmit + omitrm="\$(echo "\$promptOmit" | tr ' ' '|')" + echo + elif [[ "\$promptRmVerify" =~ ^[Cc]$ ]]; then + echo -e "Cancelled\n" + omitrm="CANCEL" + else + omitrm="" + echo + fi + + if [[ "\$omitrm" != "CANCEL" ]]; then + comm -13 Package.all.list /tmp/Package.new.list | egrep -v "(\$omitrm)" | xargs zypper remove + fi +else + echo -e "Cancelled\n" +fi + +# Package Locks +if [[ -r "Package.lock" && "\$(wc -l --total=only Package.list)" -gt 0 ]]; then + echo + echo "==============================" + echo "PACKAGE LOCKS" + echo "==============================" + echo + echo "The following packages are found to be locked:" + cat Package.lock + echo + read -p "Do you want to lock these packages per the backup? [Y/n] " -n 1 -sr promptLock + if [[ "\$promptLock" =~ ^[Yy]$ ]]; then + echo "Yes" + cat Package.lock | xargs zypper addlock + else + echo -e "Cancelled\n" + fi +fi +EOF + + popd || exit 2 +} + +hook_after() { + : +} + +hook_fail() { + : +} + +hook_final() { + : +} + +case "$1" in + before) hook_before || exit $?;; + after) hook_after || exit $?;; + fail) hook_fail || exit $?;; + finally) hook_final || exit $?;; +esac diff --git a/scripts/partbackup b/scripts/partbackup new file mode 100644 index 0000000..e69de29 diff --git a/scripts/partbackup2 b/scripts/partbackup2 new file mode 100644 index 0000000..e69de29 diff --git a/scripts/partbackup3 b/scripts/partbackup3 new file mode 100644 index 0000000..4bc5ea7 --- /dev/null +++ b/scripts/partbackup3 @@ -0,0 +1,339 @@ +#!/bin/bash + +# Global variables for restore directory +restoreDir="/etc/restore" +backupDir="${restoreDir}/data" + +# Function to create backup directory +create_backup_dir() { + echo "Creating backup directory at ${backupDir}..." + mkdir -p "$backupDir" + echo "Backup directory created." +} + +# Function to backup a partition table +backup_partition() { + local device="$1" + + echo "Backing up partition table for $device..." + sfdisk --dump "$device" > "${backupDir}/partition_table_$(basename "$device").backup" + echo "Partition table backed up to ${backupDir}/partition_table_$(basename "$device").backup" +} + +# Function to backup filesystem UUID +backup_uuid() { + local partition="$1" + + echo "Backing up UUID for $partition..." + blkid "$partition" | awk '{print $2}' > "${backupDir}/uuid_$(basename "$partition").backup" + echo "UUID backed up to ${backupDir}/uuid_$(basename "$partition").backup" +} + +# Function to backup Btrfs subvolumes, excluding snapshots and nested subvolumes +backup_btrfs_subvolumes() { + local mount_point="$1" + local subvol_path + local subvol_name + + echo "Backing up Btrfs subvolumes from $mount_point to ${backupDir}..." + mkdir -p "${backupDir}/btrfs_subvolumes" + btrfs subvolume list -p "$mount_point" | while read -r line; do + subvol_path=$(echo "$line" | awk '{print $NF}') + subvol_name=$(basename "$subvol_path") + + # Skip snapshots and subvolumes under .snapshots + if [[ "$subvol_path" == *.snapshots/* || "$subvol_path" == ".snapshots" ]]; then + continue + fi + + echo "Backing up subvolume $subvol_name" + btrfs subvolume snapshot "$mount_point/$subvol_path" "${backupDir}/btrfs_subvolumes/$subvol_name" + done + echo "Btrfs subvolumes backed up to ${backupDir}/btrfs_subvolumes" +} + +# Function to backup current mount points and options +backup_mount_points() { + echo "Backing up current mount points and options..." + mount | grep "^/dev" > "${backupDir}/mount_points.backup" + echo "Mount points backed up to ${backupDir}/mount_points.backup" +} + +# Function to restore Btrfs subvolumes to /mnt/restore, excluding snapshots and nested subvolumes +restore_btrfs_subvolumes() { + local mount_point="$1" + local subvol_snapshot + local subvol_name + + echo "Restoring Btrfs subvolumes from ${backupDir}/btrfs_subvolumes to /mnt/restore/$mount_point..." + mkdir -p "/mnt/restore$mount_point" + for subvol_snapshot in "${backupDir}/btrfs_subvolumes"/*; do + if [[ -d "$subvol_snapshot" ]]; then + subvol_name=$(basename "$subvol_snapshot") + + # Restore subvolume to the mount point + echo "Restoring subvolume $subvol_name" + btrfs subvolume snapshot "$subvol_snapshot" "/mnt/restore$mount_point/$subvol_name" + fi + done + echo "Btrfs subvolumes restored to /mnt/restore$mount_point" +} + +# Function to restore mount points to /mnt/restore, supporting Btrfs subvolumes +restore_mount_points() { + local original_device + local device + local mount_point + local mount_opts + local subvol + local fs_type + + if [[ -f "${backupDir}/mount_points.backup" ]]; then + echo "Restoring mount points from ${backupDir}/mount_points.backup to /mnt/restore..." + mkdir -p /mnt/restore + while IFS= read -r line; do + original_device=$(echo "$line" | awk '{print $1}') + mount_point=$(echo "$line" | awk '{print $3}') + mount_opts=$(echo "$line" | awk -F' ' '{for(i=4;i<=NF;i++){print $i}}' | tr -d '()') + + # Extract and remove subvol and subvolid from mount options + subvol=$(echo "$mount_opts" | grep -oP 'subvol=\K[^,]*') + mount_opts=$(echo "$mount_opts" | sed -e 's/subvol=[^,]*,//g' -e 's/subvolid=[^,]*,//g') + + # Rename device if specified + device="$original_device" + if [[ -n "${rename_map[$device]}" ]]; then + device="${rename_map[$device]}" + fi + + mkdir -p "/mnt/restore$mount_point" + fs_type=$(blkid -o value -s TYPE "$device") + + if [[ "$fs_type" == "btrfs" ]]; then + if [[ -n "$subvol" ]]; then + echo "Mounting Btrfs subvolume $subvol on /mnt/restore$mount_point" + mount -t btrfs -o subvol="$subvol,$mount_opts" "$device" "/mnt/restore$mount_point" + else + echo "Mounting Btrfs volume $device on /mnt/restore$mount_point" + mount -t btrfs -o "$mount_opts" "$device" "/mnt/restore$mount_point" + fi + else + echo "Mounting $device on /mnt/restore$mount_point with options $mount_opts" + mount -o "$mount_opts" "$device" "/mnt/restore$mount_point" + fi + done < "${backupDir}/mount_points.backup" + echo "Mount points restored to /mnt/restore." + else + echo "Backup file ${backupDir}/mount_points.backup not found." + return 1 + fi +} + +# Function to restore a partition table with optional device renaming +restore_partition() { + local device="$1" + local original_device="$device" + local backup_file + local temp_file + + # Check if the device should be renamed + if [[ -n "${rename_map[$device]}" ]]; then + echo "Renaming device $device to ${rename_map[$device]}" + device="${rename_map[$device]}" + fi + + backup_file="${backupDir}/partition_table_$(basename "$original_device").backup" + if [[ ! -f "$backup_file" ]]; then + echo "Backup file $backup_file not found." + return 1 + fi + + # Create a temporary file for the modified backup + temp_file=$(mktemp) + trap 'rm -f "$temp_file"' EXIT + + # Modify the backup file to use the new device name + sed "s|$original_device|$device|g" "$backup_file" > "$temp_file" + + echo "Restoring partition table for $device..." + sfdisk "$device" < "$temp_file" + echo "Partition table restored from $temp_file" +} + +# Function to restore filesystem UUID with optional device renaming +restore_uuid() { + local partition="$1" + local original_partition="$partition" + local uuid + + # Check if the partition should be renamed + if [[ -n "${rename_map[$partition]}" ]]; then + partition="${rename_map[$partition]}" + fi + + if [[ -f "${backupDir}/uuid_$(basename "$original_partition").backup" ]]; then + uuid=$(cut -d'=' -f2 < "${backupDir}/uuid_$(basename "$original_partition").backup" | tr -d '"') + echo "Restoring UUID $uuid to $partition..." + + case "$(blkid -o value -s TYPE "$partition")" in + ext4) + tune2fs -U "$uuid" "$partition" + ;; + xfs) + xfs_admin -U "$uuid" "$partition" + ;; + btrfs) + btrfstune -u "$uuid" "$partition" + ;; + *) + echo "Unsupported filesystem type." + return 1 + ;; + esac + + echo "UUID restored." + else + echo "Backup file ${backupDir}/uuid_$(basename "$original_partition").backup not found." + return 1 + fi +} + +# Function to handle all backup operations +backup_all_partitions() { + echo "Backing up all partitions..." + backup_mount_points + + while IFS= read -r line; do + local device + device=$(echo "$line" | awk '{print $1}') + backup_partition "$device" + backup_uuid "$device" + done < "${backupDir}/mount_points.backup" + + backup_btrfs_subvolumes "/mnt" +} + +# Function to restore all partitions and filesystems from backup files +restore_all() { + echo "Restoring all partitions and filesystems from backup..." + + # Restore partition tables + while IFS= read -r line; do + local device + device=$(echo "$line" | awk '{print $1}') + # Rename device if specified + if [[ -n "${rename_map[$device]}" ]]; then + device="${rename_map[$device]}" + fi + restore_partition "$device" + done < "${backupDir}/mount_points.backup" + + if [[ "$restore_uuid" == true ]]; then + while IFS= read -r line; do + local partition + partition=$(echo "$line" | awk '{print $1}') + # Rename partition if specified + if [[ -n "${rename_map[$partition]}" ]]; then + partition="${rename_map[$partition]}" + fi + restore_uuid "$partition" + done < "${backupDir}/mount_points.backup" + fi + + restore_btrfs_subvolumes "/mnt/restore" +} + +# Main function to handle command-line arguments and invoke appropriate functions +main() { + local exclude_opts=() + local exclude_file + local rename_map=() + local restore_uuid=true + + case "$1" in + backup) + shift + while [[ "$#" -gt 0 ]]; do + case "$1" in + --exclude) + shift + exclude_opts+=("$1") + shift + ;; + --exclude-file) + shift + exclude_file="$1" + if [[ -f "$exclude_file" ]]; then + while IFS= read -r line; do + exclude_opts+=("$line") + done < "$exclude_file" + else + echo "Exclude file $exclude_file does not exist." + exit 1 + fi + shift + ;; + *) + echo "Usage: $0 backup [--exclude ]... [--exclude-file ]" + exit 1 + ;; + esac + done + + create_backup_dir + backup_all_partitions + ;; + + restore) + shift + while [[ "$#" -gt 0 ]]; do + case "$1" in + --no-uuid) + restore_uuid=false + shift + ;; + --exclude) + shift + exclude_opts+=("$1") + shift + ;; + --exclude-file) + shift + exclude_file="$1" + if [[ -f "$exclude_file" ]]; then + while IFS= read -r line; do + exclude_opts+=("$line") + done < "$exclude_file" + else + echo "Exclude file $exclude_file does not exist." + exit 1 + fi + shift + ;; + --rename) + shift + local old_device="$1" + shift + local new_device="$1" + rename_map["$old_device"]="$new_device" + shift + ;; + *) + echo "Usage: $0 restore [--no-uuid] [--exclude ]... [--exclude-file ] [--rename ]" + exit 1 + ;; + esac + done + + restore_all + ;; + + *) + echo "Usage: $0 {backup|restore} [options]" + exit 1 + ;; + esac +} + +# Call the main function with all arguments +main "$@" diff --git a/scripts/partbackup4 b/scripts/partbackup4 new file mode 100644 index 0000000..a73abf3 --- /dev/null +++ b/scripts/partbackup4 @@ -0,0 +1,388 @@ +#!/bin/bash + +# Global variables for restore directory +restoreDir="/etc/restore" +backupDir="${restoreDir}/data" + +# Internal Variables used globally +declare -a exclude_opts +declare -A rename_map +declare restore_uuid=true + +# Function to create backup directory +create_backup_dir() { + echo "Creating backup directory at ${backupDir}..." + mkdir -p "$backupDir" + echo "Backup directory created." +} + +# Function to backup a partition table +backup_partition() { + local device="$1" + + echo "Backing up partition table for $device..." + sfdisk --dump "$device" > "${backupDir}/partition_table_$(basename "$device").backup" + echo "Partition table backed up to ${backupDir}/partition_table_$(basename "$device").backup" +} + +# Function to backup filesystem UUID +backup_uuid() { + local partition="$1" + + echo "Backing up UUID for $partition..." + blkid "$partition" | awk '{print $2}' > "${backupDir}/uuid_$(basename "$partition").backup" + echo "UUID backed up to ${backupDir}/uuid_$(basename "$partition").backup" +} + +# Function to backup Btrfs subvolumes, excluding snapshots and nested subvolumes +backup_btrfs_subvolumes() { + local mount_point="$1" + local subvol_path + local subvol_name + + echo "Backing up Btrfs subvolumes from $mount_point to ${backupDir}..." + mkdir -p "${backupDir}/btrfs_subvolumes" + btrfs subvolume list -p "$mount_point" | while read -r line; do + subvol_path=$(echo "$line" | awk '{print $NF}') + subvol_name=$(basename "$subvol_path") + + # Skip snapshots and subvolumes under .snapshots + if [[ "$subvol_path" == *.snapshots/* || "$subvol_path" == ".snapshots" ]]; then + continue + fi + + echo "Backing up subvolume $subvol_name" + btrfs subvolume snapshot "$mount_point/$subvol_path" "${backupDir}/btrfs_subvolumes/$subvol_name" + done + echo "Btrfs subvolumes backed up to ${backupDir}/btrfs_subvolumes" +} + +# Function to backup current mount points and options +backup_mount_points() { + local line + local device + + echo "Backing up current mount points and options..." + while IFS= read -r line; do + device=$(echo "$line" | awk '{print $1}') + # Skip excluded devices + if is_excluded "$device"; then + echo "Skipping excluded device $device" + continue + fi + backup_partition "$device" + backup_uuid "$device" + done < <(mount | grep "^/dev") + #mount | grep "^/dev" > "${backupDir}/mount_points.backup" + echo "Mount points backed up to ${backupDir}/mount_points.backup" +} + +# Function to restore Btrfs subvolumes to /mnt/restore, excluding snapshots and nested subvolumes +restore_btrfs_subvolumes() { + local mount_point="$1" + local subvol_snapshot + local subvol_name + + echo "Restoring Btrfs subvolumes from ${backupDir}/btrfs_subvolumes to /mnt/restore/$mount_point..." + mkdir -p "/mnt/restore$mount_point" + for subvol_snapshot in "${backupDir}/btrfs_subvolumes"/*; do + if [[ -d "$subvol_snapshot" ]]; then + subvol_name=$(basename "$subvol_snapshot") + + # Restore subvolume to the mount point + echo "Restoring subvolume $subvol_name" + btrfs subvolume snapshot "$subvol_snapshot" "/mnt/restore$mount_point/$subvol_name" + fi + done + echo "Btrfs subvolumes restored to /mnt/restore$mount_point" +} + +# Function to restore mount points to /mnt/restore, supporting Btrfs subvolumes +restore_mount_points() { + local original_device + local device + local mount_point + local mount_opts + local subvol + local fs_type + + if [[ -f "${backupDir}/mount_points.backup" ]]; then + echo "Restoring mount points from ${backupDir}/mount_points.backup to /mnt/restore..." + mkdir -p /mnt/restore + while IFS= read -r line; do + original_device=$(echo "$line" | awk '{print $1}') + mount_point=$(echo "$line" | awk '{print $3}') + mount_opts=$(echo "$line" | awk -F' ' '{for(i=4;i<=NF;i++){print $i}}' | tr -d '()') + + # Extract and remove subvol and subvolid from mount options + subvol=$(echo "$mount_opts" | grep -oP 'subvol=\K[^,]*') + mount_opts=$(echo "$mount_opts" | sed -e 's/subvol=[^,]*,//g' -e 's/subvolid=[^,]*,//g') + + # Rename device if specified + device="$original_device" + if [[ -n "${rename_map[$device]}" ]]; then + device="${rename_map[$device]}" + fi + + mkdir -p "/mnt/restore$mount_point" + fs_type=$(blkid -o value -s TYPE "$device") + + if [[ "$fs_type" == "btrfs" ]]; then + if [[ -n "$subvol" ]]; then + echo "Mounting Btrfs subvolume $subvol on /mnt/restore$mount_point" + mount -t btrfs -o subvol="$subvol,$mount_opts" "$device" "/mnt/restore$mount_point" + else + echo "Mounting Btrfs volume $device on /mnt/restore$mount_point" + mount -t btrfs -o "$mount_opts" "$device" "/mnt/restore$mount_point" + fi + else + echo "Mounting $device on /mnt/restore$mount_point with options $mount_opts" + mount -o "$mount_opts" "$device" "/mnt/restore$mount_point" + fi + done < "${backupDir}/mount_points.backup" + echo "Mount points restored to /mnt/restore." + else + echo "Backup file ${backupDir}/mount_points.backup not found." + return 1 + fi +} + +# Function to restore a partition table with optional device renaming +restore_partition() { + local device="$1" + local original_device="$device" + local backup_file + local temp_file + + # Check if the device should be renamed + if [[ -n "${rename_map[$device]}" ]]; then + echo "Renaming device $device to ${rename_map[$device]}" + device="${rename_map[$device]}" + fi + + backup_file="${backupDir}/partition_table_$(basename "$original_device").backup" + if [[ ! -f "$backup_file" ]]; then + echo "Backup file $backup_file not found." + return 1 + fi + + # Create a temporary file for the modified backup + temp_file=$(mktemp) + trap 'rm -f "$temp_file"' EXIT + + # Modify the backup file to use the new device name + sed "s|$original_device|$device|g" "$backup_file" > "$temp_file" + + echo "Restoring partition table for $device..." + sfdisk "$device" < "$temp_file" + echo "Partition table restored from $temp_file" +} + +# Function to restore filesystem UUID with optional device renaming +restore_uuid() { + local partition="$1" + local original_partition="$partition" + local uuid + + # Check if the partition should be renamed + if [[ -n "${rename_map[$partition]}" ]]; then + partition="${rename_map[$partition]}" + fi + + if [[ -f "${backupDir}/uuid_$(basename "$original_partition").backup" ]]; then + uuid=$(cut -d'=' -f2 < "${backupDir}/uuid_$(basename "$original_partition").backup" | tr -d '"') + echo "Restoring UUID $uuid to $partition..." + + case "$(blkid -o value -s TYPE "$partition")" in + ext4) + tune2fs -U "$uuid" "$partition" + ;; + xfs) + xfs_admin -U "$uuid" "$partition" + ;; + btrfs) + btrfstune -u "$uuid" "$partition" + ;; + *) + echo "Unsupported filesystem type." + return 1 + ;; + esac + + echo "UUID restored." + else + echo "Backup file ${backupDir}/uuid_$(basename "$original_partition").backup not found." + return 1 + fi +} + +# Function to handle exclusion list with wildcard and reverse matching +is_excluded() { + local pattern="$1" + local device="$2" + + for exclude in "${exclude_opts[@]}"; do + if [[ "$exclude" == !* ]]; then + pattern="${exclude#!}" + if [[ "$device" == $pattern ]]; then + return 1 + fi + else + pattern="$exclude" + if [[ "$device" == $pattern ]]; then + return 0 + fi + fi + done + + return 1 +} + + +# Function to handle all backup operations +backup_all_partitions() { + local device + local mount_point + local fs_type + + echo "Backing up all partitions..." + backup_mount_points + + while IFS= read -r line; do + local device + device=$(echo "$line" | awk '{print $1}') + mount_point=$(echo "$line" | awk '{print $3}') + fs_type=$(echo "$line" | awk '{print $5}') + # Skip excluded devices + if is_excluded "$device"; then + echo "Skipping excluded device $device" + continue + fi + backup_partition "$device" + backup_uuid "$device" + done < "${backupDir}/mount_points.backup" + + backup_btrfs_subvolumes "/mnt" +} + +# Function to restore all partitions and filesystems from backup files +restore_all() { + echo "Restoring all partitions and filesystems from backup..." + + # Restore partition tables + while IFS= read -r line; do + local device + device=$(echo "$line" | awk '{print $1}') + # Rename device if specified + if [[ -n "${rename_map[$device]}" ]]; then + device="${rename_map[$device]}" + fi + restore_partition "$device" + done < "${backupDir}/mount_points.backup" + + if [[ "$restore_uuid" == true ]]; then + while IFS= read -r line; do + local partition + partition=$(echo "$line" | awk '{print $1}') + # Rename partition if specified + if [[ -n "${rename_map[$partition]}" ]]; then + partition="${rename_map[$partition]}" + fi + restore_uuid "$partition" + done < "${backupDir}/mount_points.backup" + fi + + restore_btrfs_subvolumes "/mnt/restore" +} + +# Main function to handle command-line arguments and invoke appropriate functions +main() { + local exclude_file + + case "$1" in + backup) + shift + while [[ "$#" -gt 0 ]]; do + case "$1" in + --exclude) + shift + exclude_opts+=("$1") + shift + ;; + --exclude-file) + shift + exclude_file="$1" + if [[ -f "$exclude_file" ]]; then + while IFS= read -r line; do + exclude_opts+=("$line") + done < "$exclude_file" + else + echo "Exclude file $exclude_file does not exist." + exit 1 + fi + shift + ;; + *) + echo "Usage: $0 backup [--exclude ]... [--exclude-file ]" + exit 1 + ;; + esac + done + + create_backup_dir + backup_all_partitions + ;; + + restore) + shift + while [[ "$#" -gt 0 ]]; do + case "$1" in + --no-uuid) + restore_uuid=false + shift + ;; + --exclude) + shift + exclude_opts+=("$1") + shift + ;; + --exclude-file) + shift + exclude_file="$1" + if [[ -f "$exclude_file" ]]; then + while IFS= read -r line; do + exclude_opts+=("$line") + done < "$exclude_file" + else + echo "Exclude file $exclude_file does not exist." + exit 1 + fi + shift + ;; + --rename) + shift + local old_device="$1" + shift + local new_device="$1" + rename_map["$old_device"]="$new_device" + shift + ;; + *) + echo "Usage: $0 restore [--no-uuid] [--exclude ]... [--exclude-file ] [--rename ]" + exit 1 + ;; + esac + done + + restore_all + ;; + + *) + echo "Usage: $0 {backup|restore} [options]" + exit 1 + ;; + esac +} + +# Call the main function with all arguments +main "$@" diff --git a/scripts/partbackup5 b/scripts/partbackup5 new file mode 100755 index 0000000..6255161 --- /dev/null +++ b/scripts/partbackup5 @@ -0,0 +1,651 @@ +#!/bin/bash + +# Global variables for restore directory +restoreDir="/etc/restore" +backupDir="${restoreDir}/data" + +# Internal Variables used globally +declare -a exclude_opts +declare -A rename_map +declare -A btrfs_roots +declare restore_uuid=true +#declare dry_run=false + +# Function to create backup directory +create_backup_dir() { + echo "Creating backup directory at ${backupDir}..." + mkdir -p "$backupDir" || exit_fail 200 "FATAL: Failed to create '$backupDir'" + echo "Backup directory created." +} + +# Function to show command-line help +showHelp() { + echo "Usage: $0 {backup|restore} [options]" + echo + echo "Options:" + echo " --exclude Exclude devices matching the pattern from backup or restore." + echo " --exclude-file Exclude devices listed in the specified file from backup or restore." + echo " --rename Rename a device during the restore process." + echo " --no-uuid Disable restoring of UUIDs during restore." + echo " --help Show this help message and exit." + echo + echo "Commands:" + echo " backup Perform backup of all partitions, UUIDs, and Btrfs subvolumes." + echo " restore Restore all partitions, UUIDs, and Btrfs subvolumes from backup." + echo + echo "Examples:" + echo " $0 backup --exclude /dev/sda1" + echo " $0 restore --rename /dev/sda /dev/sdb" + echo +} + +# Function to run commands based on dry-run mode +#run_command() { +# if [[ "$dry_run" == true ]]; then +# echo "[DRY-RUN] $@" +# else +# "$@" +# fi +#} + +echoerr() { + echo "$*" 1>&2 +} + +exit_fail() { + local rc=$1 + shift + + echoerr "$*" + exit "$rc" +} + +get_base_device() { + local partition=$1 + local devnode basenode + + if [[ -b "$partition" ]]; then + # Remove partition suffix (p2, 2, etc.) but keep the rest of the device name + devnode=$(basename "$partition") + if [[ -s "/sys/class/block/$devnode" ]]; then + basenode=$(basename "$(readlink -f "/sys/class/block/$devnode/..")") + echo "/dev/$basenode" + else + echo "$partition" | sed -E 's/(p[0-9]+|[0-9]+)$//' + fi + else + return 1 + fi +} + +check_in_array() { + local e match="$1" + shift + + for e; do + [[ "$e" == "$match" ]] && return 0 + done + + return 1 +} + +# Function to backup a partition table +backup_partition() { + local device="$1" + local rc=0 + + echo "Backing up partition table for $device..." + sfdisk --dump "$device" > "${backupDir}/partitions_$(basename "$device").backup" || rc=$? + echo "Partition table backed up to ${backupDir}/partitions_$(basename "$device").backup" + + return "$rc" +} + +# Function to backup filesystem UUID +backup_uuid() { + local partition="$1" + + echo "Backing up UUID for $partition..." + blkid -o value -s UUID "$partition" > "${backupDir}/uuid_$(basename "$partition").backup" + echo "UUID backed up to ${backupDir}/uuid_$(basename "$partition").backup" +} + +# Function to backup Btrfs subvolumes, excluding snapshots and nested subvolumes +backup_btrfs_subvolumes() { + local partition="$1" + local mount_point="$2" + local subvol_path + #local parent_id + local backup_file + local results + + echo "Backing up list of Btrfs subvolumes from $mount_point..." + backup_file="${backupDir}/btrfs_$(basename "$partition").backup" + mkdir -p "${backupDir}" + + # Initialize results variable + results="" + + while read -r line; do + subvol_path=$(echo "$line" | awk '{print $NF}') + #parent_id=$(echo "$line" | awk '{print $6}') + + # Skip subvolumes that do not have parent ID 5 + #if [[ "$parent_id" -ne 5 ]]; then + # continue + #fi + + # Skip .snapshots and subvolumes under .snapshots + if [[ "$subvol_path" == ".snapshots" || "$subvol_path" == .snapshots/* ]]; then + continue + fi + + # Skip @snapshots and subvolumes under @snapshots + if [[ "$subvol_path" == "@snapshots" || "$subvol_path" == @snapshots/* ]]; then + continue + fi + + # Skip .veeam_snapshots + if [[ "$subvol_path" == ".veeam_snapshots" || "$subvol_path" == .veeam_snapshots/* ]]; then + continue + fi + + # Append valid subvolume path to results + results+="$subvol_path"$'\n' + done < <(btrfs subvolume list -p "$mount_point" | grep "parent 5") + + # Write results to the backup file + echo "$results" > "$backup_file" + + echo "List of Btrfs subvolumes backed up to $backup_file" +} + +# Function to backup current mount points and options +backup_mounts() { + local line + local device + local mount_point + local fs_type + local results + + # Initialize results variable + results="" + + echo "Backing up current mount points and options..." + while IFS= read -r line; do + if [[ -z "$line" ]]; then + continue + fi + device=$(echo "$line" | awk '{print $1}') + mount_point=$(echo "$line" | awk '{print $3}') + fs_type=$(echo "$line" | awk '{print $5}') + + # Skip excluded devices + if is_excluded "$device"; then + echo "Skipping excluded device $device" + continue + fi + + if [[ -z "${btrfs_roots["$device"]}" ]]; then + if [[ "$fs_type" == "btrfs" ]]; then + # Keep track of initial unique btrfs mounts per device + btrfs_roots["$device"]="$mount_point" + fi + + # Update backups accordingly for the rest + #results+="$line"$'\n' + #backup_partition "$device" + #backup_uuid "$device" + fi + results+="$line"$'\n' + done < <(mount | grep "^/dev") + #mount | grep "^/dev" > "${backupDir}/mounts.backup" + + # Write results to the backup file + echo "$results" > "${backupDir}/mounts.backup" + + echo "Mount points backed up to ${backupDir}/mounts.backup" +} + +# Function to restore mount points to /mnt/restore, supporting Btrfs subvolumes +restore_mount_points() { + local original_partition + local partition + local mount_point + local mount_opts + local subvol + local fs_type + + if [[ -f "${backupDir}/mounts.backup" ]]; then + echo "Restoring mount points from ${backupDir}/mounts.backup to /mnt/restore..." + mkdir -p /mnt/restore + while IFS= read -r line; do + original_partition=$(echo "$line" | awk '{print $1}') + mount_point=$(echo "$line" | awk '{print $3}') + mount_opts=$(echo "$line" | awk -F' ' '{for(i=4;i<=NF;i++){print $i}}' | tr -d '()') + + # Extract and remove subvol and subvolid from mount options + subvol=$(echo "$mount_opts" | grep -oP 'subvol=\K[^,]*') + mount_opts=$(echo "$mount_opts" | sed -e 's/subvol=[^,]*,//g' -e 's/subvolid=[^,]*,//g') + + # Rename partition if specified + partition="$original_partition" + if [[ -n "${rename_map[$partition]}" ]]; then + partition="${rename_map[$partition]}" + echo "Renaming '$original_partition' to '$partition'" + fi + + mkdir -p "/mnt/restore$mount_point" + fs_type=$(blkid -o value -s TYPE "$partition") + + if [[ "$fs_type" == "btrfs" ]]; then + if [[ -n "$subvol" ]]; then + echo "Mounting Btrfs subvolume $subvol on /mnt/restore$mount_point" + mount -t btrfs -o subvol="$subvol,$mount_opts" "$partition" "/mnt/restore$mount_point" + else + echo "Mounting Btrfs volume $partition on /mnt/restore$mount_point" + mount -t btrfs -o "$mount_opts" "$partition" "/mnt/restore$mount_point" + fi + else + echo "Mounting $partition on /mnt/restore$mount_point with options $mount_opts" + mount -o "$mount_opts" "$partition" "/mnt/restore$mount_point" + fi + done < "${backupDir}/mounts.backup" + echo "Mount points restored to /mnt/restore." + else + echo "Backup file ${backupDir}/mounts.backup not found." + return 1 + fi +} + +# Function to restore a partition table with optional device renaming +restore_partition() { + local device="$1" + local original_device="$device" + local backup_file + local temp_file + #local mount_point + + # Check if the device should be renamed + if [[ -n "${rename_map[$device]}" ]]; then + echo "Renaming device $device to ${rename_map[$device]}" + device="${rename_map[$device]}" + fi + + backup_file="${backupDir}/partitions_$(basename "$original_device").backup" + if [[ ! -f "$backup_file" ]]; then + echo "Backup file $backup_file not found." + return 1 + fi + + # Create a temporary file for the modified backup + temp_file=$(mktemp) + trap 'rm -f "$temp_file"' EXIT + + # Modify the backup file to use the new device name + sed "s|$original_device|$device|g" "$backup_file" > "$temp_file" + + echo "Restoring partition table for $device..." + sfdisk "$device" < "$temp_file" + echo "Partition table restored from $backup_file" + + # Check if the filesystem is Btrfs + #if blkid -o value -s TYPE "$device" | grep -q "btrfs"; then + # mount_point="/mnt/restore$(blkid -o value -s UUID "$device")" + # mkdir -p "$mount_point" + # mount -t btrfs "$device" "$mount_point" + # + # # Restore Btrfs subvolumes for the device + # restore_btrfs_subvolumes "$device" "$mount_point" + # + # umount "$mount_point" + # rmdir "$mount_point" + #fi +} + +# Function to restore filesystem UUID with optional device renaming +restore_uuid() { + local partition="$1" + local original_partition="$partition" + local uuid + + # Check if the partition should be renamed + if [[ -n "${rename_map[$partition]}" ]]; then + partition="${rename_map[$partition]}" + fi + + if [[ -f "${backupDir}/uuid_$(basename "$original_partition").backup" ]]; then + uuid=$(<"${backupDir}/uuid_$(basename "$original_partition").backup") + #uuid=$(cut -d'=' -f2 < "${backupDir}/uuid_$(basename "$original_partition").backup" | tr -d '"') + echo "Restoring UUID $uuid to $partition..." + + case "$(blkid -o value -s TYPE "$partition")" in + ext2|ext3|ext4) + tune2fs -U "$uuid" "$partition" + ;; + xfs) + xfs_admin -U "$uuid" "$partition" + ;; + btrfs) + btrfstune -u "$uuid" "$partition" + ;; + *) + echo "Unsupported filesystem type." + return 1 + ;; + esac + + echo "UUID restored." + else + echo "Backup file ${backupDir}/uuid_$(basename "$original_partition").backup not found." + return 1 + fi +} + +# Function to restore Btrfs subvolumes from the backup list to the given mount point +restore_btrfs_subvolumes() { + local partition="$1" + local original_partition="$partition" + local mount_point + local subvol_path + local backup_file + + # Check if the partition should be renamed + if [[ -n "${rename_map[$partition]}" ]]; then + partition="${rename_map[$partition]}" + fi + + backup_file="${backupDir}/btrfs_$(basename "$original_partition").backup" + + echo "Restoring Btrfs subvolumes from $backup_file to $partition..." + + if [[ ! -f "$backup_file" ]]; then + echo "Backup file $backup_file not found." + return 1 + fi + + #mount_point="/mnt/restore_btrfs" + mount_point="/mnt/restore_$(blkid -o value -s UUID "$partition")" + mkdir -p "$mount_point" || exit_fail 200 "FATAL: Cannot mkdir '$mount_point'" + mount -t btrfs "$partition" "$mount_point" || exit_fail 201 "FATAL: Cannot mount btrfs filesystem '$partition' to '$mount_point'" + + while IFS= read -r subvol_path; do + echo "Restoring subvolume $subvol_path" + btrfs subvolume create "$mount_point/$subvol_path" || exit_fail 202 "FATAL: Cannot create subvolume '$subvol_path' on '$mount_point'" + done < "$backup_file" + + umount "$mount_point" + rmdir "$mount_point" + + echo "Btrfs subvolumes restored on $partition" +} + +# Format device/partition +restore_format() { + local partition="$1" + local fs_type="$2" + + if [[ ! -b "$partition" ]]; then + echoerr "Partition '$partition' is not a block device, cannot format" + return 1 + fi + + case "$fs_type" in + ext2|ext3|ext4) + mkfs."$fs_type" "$partition" + ;; + xfs) + mkfs.xfs -f "$partition" + ;; + btrfs) + mkfs.btrfs -f "$partition" + ;; + vfat) + mkfs.vfat "$partition" + ;; + *) + echo "Unsupported filesystem type." + return 1 + ;; + esac +} + +# Function to handle exclusion list with wildcard and reverse matching +is_excluded() { + #local pattern="$" + local device="$1" + + for exclude in "${exclude_opts[@]}"; do + if [[ "$exclude" == !* ]]; then + pattern="${exclude#!}" + if [[ "$device" == $pattern ]]; then + return 1 + fi + else + pattern="$exclude" + if [[ "$device" == $pattern ]]; then + return 0 + fi + fi + done + + return 1 +} + + +# Function to handle all backup operations +backup_all() { + local partition + local base_device + local mount_point + local fs_type + local -a device_nodes + + echo "Backing up all partitions..." + backup_mounts + + while IFS= read -r line; do + partition=$(echo "$line" | awk '{print $1}') + mount_point=$(echo "$line" | awk '{print $3}') + fs_type=$(echo "$line" | awk '{print $5}') + if [[ -z "$line" || -z "$partition" || -z "$mount_point" || -z "$fs_type" ]]; then + continue + fi + + # Skip excluded partition + if is_excluded "$partition"; then + echo "Skipping excluded device $partition" + continue + fi + + if ! check_in_array "$partition" "${device_nodes[@]}"; then + device_nodes+=("$partition") + base_device=$(get_base_device "$partition") + + if ! check_in_array "$base_device" "${device_nodes[@]}"; then + device_nodes+=("$base_device") + backup_partition "$base_device" + fi + + backup_uuid "$partition" + + # If the filesystem type is Btrfs, back up its subvolumes + if [[ "$fs_type" == "btrfs" ]]; then + backup_btrfs_subvolumes "$partition" "$mount_point" + fi + fi + done < "${backupDir}/mounts.backup" +} + +# Function to restore all partitions and filesystems from backup files +restore_all() { + local device + local original_device="$device" + local mount_point + local fs_type + local -a device_nodes + #local partition + + echo "Restoring all partitions and filesystems from backup..." + + # Restore partition tables + while IFS= read -r line; do + device=$(echo "$line" | awk '{print $1}') + mount_point=$(echo "$line" | awk '{print $3}') + fs_type=$(echo "$line" | awk '{print $5}') + if [[ -z "$line" || -z "$device" || -z "$mount_point" || -z "$fs_type" ]]; then + continue + fi + + # Skip excluded partition + if is_excluded "$device"; then + echo "Skipping excluded device $device" + continue + fi + + # Rename device if specified + if [[ -n "${rename_map[$device]}" ]]; then + device="${rename_map[$device]}" + echo "Renaming device '$original_device' to '$device'" + fi + + if ! check_in_array "$device" "${device_nodes[@]}"; then + device_nodes+=("$device") + + restore_partition "$original_device" + restore_format "$device" "$fs_type" + + if [[ "$restore_uuid" == true ]]; then + restore_uuid "$original_device" + fi + + if [[ "$fs_type" == "btrfs" ]]; then + if [[ -z "${btrfs_roots["$device"]}" ]]; then + # Keep track of initial unique btrfs mounts per device + btrfs_roots["$device"]="$mount_point" + restore_btrfs_subvolumes "$original_device" + fi + fi + fi + done < "${backupDir}/mounts.backup" + + #if [[ "$restore_uuid" == true ]]; then + # while IFS= read -r line; do + # partition=$(echo "$line" | awk '{print $1}') + # # Rename partition if specified + # if [[ -n "${rename_map[$partition]}" ]]; then + # partition="${rename_map[$partition]}" + # fi + # restore_uuid "$partition" + # done < "${backupDir}/mounts.backup" + #fi +} + +# Main function to handle command-line arguments and invoke appropriate functions +main() { + local exclude_file + local old_device new_device + + if [[ "$1" == "--help" ]]; then + showHelp + exit 0 + fi + + case "$1" in + backup) + shift + while [[ "$#" -gt 0 ]]; do + case "$1" in + --exclude) + shift + exclude_opts+=("$1") + shift + ;; + --exclude-file) + shift + exclude_file="$1" + if [[ -f "$exclude_file" ]]; then + while IFS= read -r line; do + exclude_opts+=("$line") + done < "$exclude_file" + else + echo "Exclude file $exclude_file does not exist." + exit 1 + fi + shift + ;; + --help) + showHelp + exit 0 + ;; + *) + echo "Invalid option: $1" + echo "Use --help to display the help message." + exit 1 + ;; + esac + done + + create_backup_dir + backup_all + ;; + + restore) + shift + while [[ "$#" -gt 0 ]]; do + case "$1" in + --no-uuid) + restore_uuid=false + shift + ;; + --exclude) + shift + exclude_opts+=("$1") + shift + ;; + --exclude-file) + shift + exclude_file="$1" + if [[ -f "$exclude_file" ]]; then + while IFS= read -r line; do + exclude_opts+=("$line") + done < "$exclude_file" + else + echo "Exclude file $exclude_file does not exist." + exit 1 + fi + shift + ;; + --rename) + shift + old_device="$1" + shift + new_device="$1" + rename_map["$old_device"]="$new_device" + shift + ;; + --help) + showHelp + exit 0 + ;; + *) + echo "Invalid option: $1" + echo "Use --help to display the help message." + exit 1 + ;; + esac + done + + restore_all + ;; + + *) + echo "Invalid command: $1" + echo "Use --help to display the help message." + exit 1 + ;; + esac +} + +# Call the main function with all arguments +main "$@" diff --git a/scripts/postgresql b/scripts/postgresql new file mode 100755 index 0000000..86e3e91 --- /dev/null +++ b/scripts/postgresql @@ -0,0 +1,70 @@ +#!/bin/bash + +restoreDir=/etc/restore +pgsqlDir="$restoreDir/postgresql" +scriptDir="$(dirname "$0")" + +function readConfig() { + local $config + + if [[ -r "${scriptDir}/../config/postgresql.cfg" ]]; then + config="$(readlink -f "${scriptDir}/../config/postgresql.cfg")" + else + exit 2 + fi + + while read -r -a vals + do + [[ "${vals[*]}" =~ ^#.*$ ]] && continue + [[ -z "${vals[0]}" ]] && continue + if [[ "${vals[0]}" -ge 10 ]]; then + echo "${vals[@]}" + fi + done < "$config" +} + +function runBackups() { + local vals + local version + local cluster + + while read -r -a vals + do + version="${vals[0]}" + cluster="${vals[1]}" + + echo "Backing up PostgreSQL $version - $cluster" + pg_backupcluster "$version" "$cluster" createdirectory || return $? + sudo -u postgres pg_backupcluster "$version" "$cluster" basebackup || return $? + sudo -u postgres pg_backupcluster "$version" "$cluster" dump || return $? + sudo -u postgres pg_backupcluster "$version" "$cluster" expirebasebackups 1 || return $? + sudo -u postgres pg_backupcluster "$version" "$cluster" expiredumps 1 || return $? + done < <(readConfig) +} + +hook_before() { + if [[ -d "$pgsqlDir" ]]; then + rm -rf "$pgsqlDir" || exit 1 + fi + + runBackups || exit $? +} + +hook_after() { + rm -rf "$pgsqlDir" || exit 1 +} + +hook_fail() { + : +} + +hook_final() { + : +} + +case "$1" in +before) hook_before || exit $? ;; +after) hook_after || exit $? ;; +fail) hook_fail || exit $? ;; +finally) hook_final || exit $? ;; +esac diff --git a/scripts/run-parts b/scripts/run-parts new file mode 100755 index 0000000..c164b9d --- /dev/null +++ b/scripts/run-parts @@ -0,0 +1,101 @@ +#!/bin/bash +# run-parts - concept taken from Debian + +# keep going when something fails +set +e + +if [ $# -lt 1 ]; then + echo "Usage: run-parts [--args | --list | --test] " + exit 1 +fi + +while [ $# -gt 1 ]; do + case $1 in + --args) + runargs=$2 + shift 2 + break + ;; + --list) + list=1 + shift + break + ;; + --test) + test=1 + shift + break + ;; + --) + # -- end of options + shift + break + ;; + *) + # directory + break + ;; + esac +done + +if [ ! -d $1 ]; then + echo "Not a directory: $1" + exit 1 +fi + +if [ -f /etc/sysconfig/run-parts ]; then + . /etc/sysconfig/run-parts +fi + +# Ignore *~ and *, scripts +for i in $(LC_ALL=C; echo ${1%/}/*[^~,]) ; do + [ -d $i ] && continue + # Don't run *.{rpmsave,rpmorig,rpmnew,swp,cfsaved} scripts + [ "${i%.cfsaved}" != "${i}" ] && continue + [ "${i%.rpmsave}" != "${i}" ] && continue + [ "${i%.rpmorig}" != "${i}" ] && continue + [ "${i%.rpmnew}" != "${i}" ] && continue + [ "${i%.swp}" != "${i}" ] && continue + [ "${i%,v}" != "${i}" ] && continue + + # jobs.deny prevents specific files from being executed + # jobs.allow prohibits all non-named jobs from being run. + # can be used in conjunction but there's no reason to do so. + if [ -r $1/jobs.deny ]; then + grep -q "^$(basename $i)$" $1/jobs.deny && continue + fi + if [ -r $1/jobs.allow ]; then + grep -q "^$(basename $i)$" $1/jobs.allow || continue + fi + + if [ -e $i ]; then + if [ -r $1/whitelist ]; then + grep -q "^$(basename $i)$" $1/whitelist && continue + fi + + if [ ${list:-0} = 1 ]; then + echo $i; + elif [ -x $i ]; then + if [ ${test:-0} = 1 ]; then + echo $i; + continue + fi + if [ "$RANDOMIZE" != "" ]; then + let "rtime = $RANDOM" + if [ "$RANDOMTIME" != "" ]; then + let "rtime %= $RANDOMTIME" + else + let "rtime %= 300" + fi + sleep $rtime + fi + + # run executable files + echo "run-parts[$$]" "($1) starting $(basename $i)" + $i $runargs 2>&1 + echo "run-parts[$$]" "($1) finished $(basename $i)" + fi + fi +done + +exit 0 diff --git a/scripts/vaultwarden b/scripts/vaultwarden new file mode 100755 index 0000000..5986c56 --- /dev/null +++ b/scripts/vaultwarden @@ -0,0 +1,36 @@ +#!/bin/bash + +restoreDir=/etc/restore +vaultwardenDir="$restoreDir/vaultwarden" + +hook_before() { + if [[ -d "$vaultwardenDir" ]]; then + rm -rf "$vaultwardenDir" || exit 1 + fi + + test -x "$(which sqlite3)" || exit 2 + backupDate=$(date +"%Y-%m-%d") + vaultwardenHome=$(getent passwd vaultwarden | cut -f6 -d:) + + mkdir -p "$vaultwardenDir" + sqlite3 "${vaultwardenHome}/data/db.sqlite3" ".backup '${vaultwardenDir}/db-${backupDate}.sqlite3'" +} + +hook_after() { + rm -rf "$vaultwardenDir" || exit 1 +} + +hook_fail() { + : +} + +hook_final() { + : +} + +case "$1" in + before) hook_before || exit $?;; + after) hook_after || exit $?;; + fail) hook_fail || exit $?;; + finally) hook_final || exit $?;; +esac diff --git a/templates/config/base.cfg b/templates/config/base.cfg new file mode 100644 index 0000000..b9e1832 --- /dev/null +++ b/templates/config/base.cfg @@ -0,0 +1,32 @@ +# This is the base configuration used my multiple agents. + +# This is where persistent larger files reside. Including the SystemRescueCD and Backup ISO's reside. +# NOTE: This is not where actual backups will reside. +systemrescueBaseDir="/var/backups/systemrescue-backup" + +# Backup Engine to use: +# Options are either resticprofile or borgmatic. +backupEngine="resticprofile" + +# ResticProfile Options: +# These are versions of restic and resticprofile to download and use with the +# SystemRescueBackup recovery image. +resticVersion="0.17.0" +resticprofileVersion="0.28.0" + +# Borgmatic Options: +# This is the SystemRescueBackup SRM Module to include in the recovery image. +# To generate this, boot a SystemRescueCD in a VM, run: +# pacman -Sy borgmatic +# cowpacman2srm borgmatic.srm +# And upon success, copy the borgmatic.srm to your host system and provide +# the path to it here: +borgmaticSRM="" + +# SystemRescueCD Version to Download and Use. If you're using borgmatic, this +# version will be relevant to the previously provided borgmaticSRM module. +systemrescueVersion="11.02" + +# Temporary working directory, mostly for downloads to generate the +# SystemRescueBackup disc image. +systemrescueTempDir="/tmp/systemrescue-backup" diff --git a/templates/config/consul_backup.cfg b/templates/config/consul_backup.cfg new file mode 100644 index 0000000..8805a6a --- /dev/null +++ b/templates/config/consul_backup.cfg @@ -0,0 +1,3 @@ +# This configuration file is used for providing the path +# to consul's binary. +# Example: CONSUL_PATH=/opt/consul/bin diff --git a/templates/config/gitea_backup.cfg b/templates/config/gitea_backup.cfg new file mode 100644 index 0000000..f3385b0 --- /dev/null +++ b/templates/config/gitea_backup.cfg @@ -0,0 +1,6 @@ +# This configuration file is used for providing configuration parameters to +# the gitea backup agent. +# GITEA_BIN Default: Found in PATH +# GITEA_CONFIG Default: /etc/gitea/app.ini +# GITEA_USER Default: git +# GITEA_HOME Default: Home Directory of GITEA_USER diff --git a/templates/config/mysql_backup.cfg b/templates/config/mysql_backup.cfg new file mode 100644 index 0000000..13421c4 --- /dev/null +++ b/templates/config/mysql_backup.cfg @@ -0,0 +1,4 @@ +# This configuration file is used for providing MySQL login +# credentials as needed. You can use the environment variables +# MYSQL_HOST, MYSQL_PWD, and MYSQL_HOME, for example. +# See: https://mariadb.com/kb/en/mariadb-environment-variables/ diff --git a/templates/config/postgresql.cfg b/templates/config/postgresql.cfg new file mode 100644 index 0000000..f5074f4 --- /dev/null +++ b/templates/config/postgresql.cfg @@ -0,0 +1,4 @@ +# Configuration for pg_backupcluster stylebackups. +# Format per line: VERSION CLUSTER +# Example: 10 main +10 main