diff --git a/README.md b/README.md index 6793ad1..59a169b 100644 --- a/README.md +++ b/README.md @@ -1,63 +1,90 @@ # Raspberry Pi backup script -GitHub repository: [mr-manuel/raspberry-pi-backup](https://github.com/mr-manuel/raspberry-pi-backup) +GitHub repository: [eckonator/raspberry-pi-backup](https://github.com/eckonator/raspberry-pi-backup) + +--- + +### Changelog + +**v0.0.10-gzip-pv** – 2025-10-13 + +- Backups are now automatically compressed using `gzip` or `pigz` to save disk space. +- Live progress and estimated completion time are displayed using `pv`. +- Old backup deletion logic updated to handle `.img.gz` files. +- Maintains full SD card backup including empty blocks. + +--- ### Disclaimer -I wrote this script for myself. I'm not responsible, if you damage something using my script. +I wrote this script for myself. I'm not responsible if you damage something using my script. +--- ### Supporting/Sponsoring this project -You like the project and you want to support me? +You like the project and you want to support the original author? [](https://www.paypal.com/donate/?hosted_button_id=3NEVZBDM5KABW) +--- ### Purpose -The script backups the whole SD card (also empty blocks) of a Raspberry Pi to a SMB/CIFS share on a NAS/computer without the need to remove the SD card. It works also with Venus OS from Victron Energy. +The script backups the whole SD card (including empty blocks) of a Raspberry Pi to a SMB/CIFS share on a NAS or computer without removing the SD card. It works also with Venus OS from Victron Energy. -NOTE: It's not possible to change the used method to not backup empty blocks. The backup will always have the same size as your SD card is. Therefore it works on many systems without installing additional software. +**Note:** The backup will always have the same size as your SD card. This ensures compatibility on many systems without installing additional software. +--- ### Config -Copy the `backup.sample.conf` to `backup.conf` and change the variables in the `backup.conf` with `nano`, `vi`, `vim` or the editor of your choise. - -Standard install path `/opt/raspberry-pi-backup` +Copy the `backup.sample.conf` to `backup.conf` and modify the variables in `backup.conf` using `nano`, `vi`, `vim`, or your preferred editor. -Venus OS install path `/data/etc/raspberry-pi-backup` +- Standard install path: `/opt/raspberry-pi-backup` +- Venus OS install path: `/data/etc/raspberry-pi-backup` +--- ### Install -1. Execute this commands to download and install the script: +1. Download and install the script: + ```bash - wget -O install-backup.sh https://raw.githubusercontent.com/mr-manuel/raspberry-pi-backup/master/install-backup.sh + wget -O install-backup.sh https://raw.githubusercontent.com/eckonator/raspberry-pi-backup/master/install-backup.sh bash install-backup.sh ``` -2. Modify the needed parameters by changing the `backup.sh` script. +2. Modify the needed parameters in `backup.conf`. -3. Run the script on demand or setup a cronjob, if you want to run the backup automatically. You can use the [Crontab Generator](https://crontab-generator.org/). +3. Run the script manually or set up a cronjob to run the backup automatically. You can use the [Crontab Generator](https://crontab-generator.org/). - Command to execute: + **Command to execute:** - On standard Linux `/opt/raspberry-pi-backup/backup.sh` + - On standard Linux: `/opt/raspberry-pi-backup/backup.sh` + - On Venus OS: `/data/etc/raspberry-pi-backup/backup.sh` - On Venus OS `/data/etc/raspberry-pi-backup/backup.sh` + **Note:** After a Venus OS update, you may need to set up the cronjob again. - NOTE: After a Venus OS update you have to setup the cronjob again. +--- ### Uninstall Remove the cronjob (if any) and delete the folder with the script. +--- + ### Debugging -Run the script directly and check the output. +Run the script directly and check the output. If `pv` is installed, a live progress display will show the backup status. + +--- ### Restore SD card -Turn off the Raspberry Pi, remove the SD card and insert it in your computer. Write the backup image file to the SD card with [Balena Etcher](https://github.com/balena-io/etcher) or the tool you prefer. +Turn off the Raspberry Pi, remove the SD card, and insert it into your computer. Write the backup image file (compressed `.img.gz`) to the SD card with [Balena Etcher](https://github.com/balena-io/etcher) or another tool you prefer. + +If the backup file is compressed, decompress it first: + +```bash +gzip -d Backup_hostname_YYYYMMDD_HHMMSS.img.gz diff --git a/install-backup.sh b/install-backup.sh index d708bd2..2b1212d 100644 --- a/install-backup.sh +++ b/install-backup.sh @@ -37,7 +37,7 @@ fi # download latest copy of repository echo "Download latest version..." -wget -P /tmp https://github.com/mr-manuel/raspberry-pi-backup/archive/refs/heads/master.zip +wget -P /tmp https://github.com/eckonator/raspberry-pi-backup/archive/refs/heads/master.zip if [ $? -ne 0 ]; then echo "Error during downloading the file." diff --git a/raspberry-pi-backup/backup.sh b/raspberry-pi-backup/backup.sh index 6b80910..4c6bd04 100644 --- a/raspberry-pi-backup/backup.sh +++ b/raspberry-pi-backup/backup.sh @@ -1,49 +1,25 @@ #!/bin/bash -# script version 0.0.8 (2025.02.08) +# script version 0.0.10-gzip-pv (2025.10.13) -# uncomment for debugging #set -x SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) ################## CONFIG | START ################## if [ -f $SCRIPT_DIR/backup.conf ]; then - # read local configuration . $SCRIPT_DIR/backup.conf else - # specify the remote mount BACKUP_REMOTE_MOUNT="//192.168.1.1/sharename" - - # specify a subfolder (with ending /) if needed, else - # leave completely empty - # NOTE: an additional subfolder with the hostname of - # the device is automatically created BACKUP_SUBFOLDER="Backups/" - - # specify the username for the remote mount BACKUP_REMOTE_MOUNT_USER="username" - - # specify the password for the remote mount BACKUP_REMOTE_MOUNT_PW="password" - - # how may backups should be kept? - # Use 0 to disable old backup deletion BACKUP_COUNT="5" - - # backup hostname (default gets the hostname from the system) BACKUP_HOSTNAME="$(hostname)" - - # device to backup - # SD card: "/dev/mmcblk0" - # USB stick: "/dev/sda" - # NVMe: "/dev/nvme0n1" BACKUP_DEVICE="/dev/mmcblk0" fi ################### CONFIG | END ################### - - BACKUP_MOUNT="/mnt/backup" BACKUP_PATH="${BACKUP_MOUNT}/${BACKUP_SUBFOLDER}${BACKUP_HOSTNAME}" BACKUP_NAME="Backup_${BACKUP_HOSTNAME}" @@ -51,113 +27,67 @@ BACKUP_NAME="Backup_${BACKUP_HOSTNAME}" echo # create mount dir if not exists -if [ ! -d ${BACKUP_MOUNT} ]; then - echo "${BACKUP_MOUNT} does not exist. Creating folder..." - mkdir ${BACKUP_MOUNT} - echo -fi +[ ! -d ${BACKUP_MOUNT} ] && mkdir -p ${BACKUP_MOUNT} -# check if something is already mounted -if [ 1 -eq "$(mount -v | grep -c ${BACKUP_MOUNT})" ]; then - echo "WARNING: There is already mounted something to \"${BACKUP_MOUNT}\". This will be unmounted now." - echo - mount -v | grep ${BACKUP_MOUNT} +# unmount if already mounted +if mount | grep -q ${BACKUP_MOUNT}; then + echo "Unmounting existing mount on ${BACKUP_MOUNT}..." umount ${BACKUP_MOUNT} - echo fi -# mount harddisk +# mount remote share echo "Mounting \"${BACKUP_REMOTE_MOUNT}\" to \"${BACKUP_MOUNT}\"..." -mount -t cifs -o user=${BACKUP_REMOTE_MOUNT_USER},password=${BACKUP_REMOTE_MOUNT_PW},rw,file_mode=0660,dir_mode=0660,nounix,noserverino ${BACKUP_REMOTE_MOUNT} ${BACKUP_MOUNT} +mount -t cifs -o user=${BACKUP_REMOTE_MOUNT_USER},password=${BACKUP_REMOTE_MOUNT_PW},rw,file_mode=0660,dir_mode=0660,nounix,noserverino ${BACKUP_REMOTE_MOUNT} ${BACKUP_MOUNT} || { echo "Mount failed"; exit 1; } -if [ $? -ne 0 ]; then - echo "Error when mounting the remote path." - exit -fi +# create backup folder if it does not exist +mkdir -p "${BACKUP_PATH}" -echo +# choose compression tool +COMPRESS_CMD=$(command -v pigz || echo gzip) -# create folder if it does not exist -if [ ! -d "${BACKUP_PATH}" ]; then - echo "Creating \"${BACKUP_SUBFOLDER}\" on backup mount..." - mkdir -p "${BACKUP_PATH}" - if [ $? -ne 0 ]; then - echo "Error when creating of the backup folder on the remote path." - echo - exit - fi - echo +# check if pv exists +if ! command -v pv &> /dev/null; then + PV_CMD="" +else + PV_CMD="pv -s $(blockdev --getsize64 $BACKUP_DEVICE)" fi -# check if dd is a symlink like in busybox -# if yes then probably the argument "status=progress" will not work, use own dd -if [[ -L "/bin/dd" ]] || [[ -f "/opt/victronenergy/version" ]]; then - # check if backup is already running - pgrep_output=$(pgrep "dd if=$BACKUP_DEVICE") - if [ -n "$pgrep_output" ] && [ "$pgrep_output" -gt 1 ]; then - echo "Backup already running. Exiting..." - echo - exit - fi - # create backup - echo "Using script dd for backup." - "${SCRIPT_DIR}/ext/dd" if=$BACKUP_DEVICE of="${BACKUP_PATH}/${BACKUP_NAME}_$(date +%Y%m%d_%H%M%S).img" bs=1MB status=progress +# check if dd is busybox symlink +DD_CMD="/bin/dd" +[ -L "/bin/dd" ] && DD_CMD="${SCRIPT_DIR}/ext/dd" -# if not use the system dd -else - # check if backup is already running - pgrep_output=$(pgrep "dd if=$BACKUP_DEVICE") - if [ -n "$pgrep_output" ] && [ "$pgrep_output" -gt 1 ]; then - echo "Backup already running. Exiting..." - echo - exit - fi - # create backup - echo "Using system dd for backup." - /bin/dd if=$BACKUP_DEVICE of="${BACKUP_PATH}/${BACKUP_NAME}_$(date +%Y%m%d_%H%M%S).img" bs=1MB status=progress +# check if backup already running +if pgrep -f "dd if=$BACKUP_DEVICE" &> /dev/null; then + echo "Backup already running. Exiting..." + exit fi +# filename with timestamp +BACKUP_FILE="${BACKUP_PATH}/${BACKUP_NAME}_$(date +%Y%m%d_%H%M%S).img.gz" + +# perform backup with pv + compression +echo "Starting backup with compression ($COMPRESS_CMD) and progress display..." +$DD_CMD if=$BACKUP_DEVICE bs=1M status=none | $PV_CMD | $COMPRESS_CMD > "$BACKUP_FILE" + if [ $? -eq 0 ]; then - echo "Backup completed successfully." - echo + echo "Backup completed successfully: $BACKUP_FILE" else - echo "Error when backup of the system." - echo - exit + echo "Backup failed!" + exit 1 fi -# delete old backups if BACKUP_COUNT is greater than 0 -if [ "${BACKUP_COUNT}" -gt "0" ]; then - - # increment count by one to delete files after count +# delete old backups +if [ "$BACKUP_COUNT" -gt 0 ]; then ((BACKUP_COUNT++)) - - # delete old backups - BACKUP_FILES_TO_DELETE_COUNT=$(ls -t ${BACKUP_PATH}/${BACKUP_NAME}* | tail -n +${BACKUP_COUNT} | wc -l) - if [ "${BACKUP_FILES_TO_DELETE_COUNT}" -ne "0" ]; then - - echo "Found this files to delete:" - ls -t ${BACKUP_PATH}/${BACKUP_NAME}* | tail -n +${BACKUP_COUNT} - - # switch to backup folder and remember current folder - pushd ${BACKUP_PATH} || exit - - - # list files, remove newest files and delete the rest - ls -t ${BACKUP_PATH}/${BACKUP_NAME}* | tail -n +${BACKUP_COUNT} | xargs rm - - popd || exit - echo -e "${BACKUP_FILES_TO_DELETE_COUNT} old backups deleted." + OLD_FILES=$(ls -t ${BACKUP_PATH}/${BACKUP_NAME}*.img.gz | tail -n +${BACKUP_COUNT}) + if [ -n "$OLD_FILES" ]; then + echo "Deleting old backups:" + echo "$OLD_FILES" + echo "$OLD_FILES" | xargs rm fi - fi -# unmount harddisk -umount ${BACKUP_MOUNT} - -if [ $? -ne 0 ]; then - echo "Error when unmounting the remote path." -fi +# unmount remote share +umount ${BACKUP_MOUNT} || echo "Warning: could not unmount ${BACKUP_MOUNT}" echo -echo