Migrated to '/bin/sh'
All checks were successful
backup.sh / bash (push) Successful in 12s

This commit is contained in:
Marco Cetica 2024-02-27 10:03:12 +01:00
parent 0918139225
commit 39a15d2f2a
Signed by: marco
GPG Key ID: 45060A949E90D0FD
5 changed files with 150 additions and 220 deletions

View File

@ -105,7 +105,7 @@ After that, you will find the final backup archive in `/home/john/backup-<HOSTNA
You can also use `backup.sh` from a crontab rule:
```sh
$> sudo crontab -e
30 03 * * 6 EKEY=$(cat /home/john/.ekey) bash -c '/usr/local/bin/backup.sh -b /usr/local/etc/sources.bk /home/john $EKEY' > /dev/null 2>&1
30 03 * * 6 EKEY=$(cat /home/john/.ekey) sh -c '/usr/local/bin/backup.sh -b /usr/local/etc/sources.bk /home/john $EKEY' > /dev/null 2>&1
```
@ -163,7 +163,7 @@ $> gpg -a \
--cipher-algo=AES256 \
--no-symkey-cache \
--pinentry-mode=loopback \
--batch --passphrase-fd 3 3<<< "$PASSWORD" \
--batch --passphrase "$PASSWORD" \
--output "$OUTPUT" \
"$INPUT"
```

152
backup.sh
View File

@ -1,4 +1,4 @@
#!/usr/bin/env bash
#!/bin/sh -e
# backup.sh is a POSIX compliant, modular and lightweight
# backup utility to save and encrypt your files.
#
@ -27,32 +27,31 @@
#
# You can read the full guide on https://github.com/ice-bit/backup.sh
# or on the manual page.
# Copyright (c) 2018,2023 Marco Cetica <email@marcocetica.com>
# Copyright (c) 2018,2023,2024 Marco Cetica <email@marcocetica.com>
#
set -e
checkdeps() {
# Check if dependencies are installed
missing_dep=0
deps="rsync tar gpg"
# Check if dependencies are installed
missing_dep=0
deps=("rsync" "tar" "gpg")
for dep in $deps; do
if ! command -v "$dep" > /dev/null 2>&1; then
printf "Cannot find '%s', please install it.\n" "$dep"
missing_dep=1
fi
done
for dep in "${deps[@]}"; do
if ! command -v "$dep" > /dev/null 2>&1; then
echo "Cannot find '$dep', please install it."
missing_dep=1
if [ $missing_dep -ne 0 ]; then
exit 1
fi
done
if [ $missing_dep -ne 0 ]; then
exit 1
fi
}
checksum() {
BACKUP_SH_FILENAME="$1"
BACKUP_SH_OS="$(uname)"
BACKUP_SH_OS="${BACKUP_SH_OS^^}"
BACKUP_SH_OS="$(uname | tr '[:lower:]' '[:upper:]')"
if [[ $BACKUP_SH_OS == "LINUX" ]]; then
if [ "$BACKUP_SH_OS" = "LINUX" ]; then
RES="$(md5sum "$BACKUP_SH_FILENAME" | awk '{print $1}')"
else
RES="$(md5 -q "$BACKUP_SH_FILENAME")"
@ -72,8 +71,6 @@ make_backup() {
BACKUP_SH_START_TIME="$(date +%s)"
BACKUP_SH_FILENAME="$BACKUP_SH_OUTPATH/backup-$(uname -n)-$BACKUP_SH_DATE.tar.gz.enc"
declare -A BACKUP_SH_SOURCES
# Check for root permissions
if [ "$(id -u)" -ne 0 ]; then
echo "Run this tool as root!"
@ -86,28 +83,22 @@ make_backup() {
exit 1
fi
# Read associative array from file
readarray -t lines < "$BACKUP_SH_SOURCES_PATH"
for line in "${lines[@]}"; do
label=${line%%=*}
path=${line#*=}
BACKUP_SH_SOURCES[$label]=$path
done
# Create temporary directory
mkdir -p "$BACKUP_SH_OUTPUT"
# For each item in the array, make a backup
BACKUP_SH_TOTAL=$(wc -l < "$BACKUP_SH_SOURCES_PATH")
BACKUP_SH_PROGRESS=1
for item in "${!BACKUP_SH_SOURCES[@]}"; do
while IFS='=' read -r label path; do
# Define a subdir for each backup entry
BACKUP_SH_SUBDIR="$BACKUP_SH_OUTPUT/backup-$item-$BACKUP_SH_DATE"
BACKUP_SH_SUBDIR="$BACKUP_SH_OUTPUT/backup-$label-$BACKUP_SH_DATE"
mkdir -p "$BACKUP_SH_SUBDIR"
echo "Copying $item($BACKUP_SH_PROGRESS/${#BACKUP_SH_SOURCES[*]})"
$BACKUP_SH_COMMAND "${BACKUP_SH_SOURCES[$item]}" "$BACKUP_SH_SUBDIR"
printf "Copying %s(%s/%s)\n" "$label" "$BACKUP_SH_PROGRESS" "$BACKUP_SH_TOTAL"
$BACKUP_SH_COMMAND "$path" "$BACKUP_SH_SUBDIR"
BACKUP_SH_PROGRESS=$((BACKUP_SH_PROGRESS+1))
done
done < "$BACKUP_SH_SOURCES_PATH"
# Compress backup directory
echo "Compressing backup..."
@ -121,7 +112,7 @@ make_backup() {
--cipher-algo=AES256 \
--no-symkey-cache \
--pinentry-mode=loopback \
--batch --passphrase-fd 3 3<<< "$BACKUP_SH_PASS" \
--batch --passphrase "$BACKUP_SH_PASS" \
--output "$BACKUP_SH_FILENAME" \
"$BACKUP_SH_OUTPATH/backup.sh.tar.gz" > /dev/null 2>&1
@ -138,7 +129,7 @@ make_backup() {
echo "File name: $BACKUP_SH_FILENAME"
echo "File size: $BACKUP_SH_FILE_SIZE($BACKUP_SH_FILE_SIZE_H)"
echo "File hash: $BACKUP_SH_HASH"
echo "Elapsed time: $(("$BACKUP_SH_END_TIME" - "$BACKUP_SH_START_TIME")) seconds."
printf "Elapsed time: %s seconds.\n" "$((BACKUP_SH_END_TIME - BACKUP_SH_START_TIME))"
}
extract_backup() {
@ -150,7 +141,7 @@ extract_backup() {
--decrypt \
--no-symkey-cache \
--pinentry-mode=loopback \
--batch --passphrase-fd 3 3<<<"$BACKUP_SH_ARCHIVE_PW" \
--batch --passphrase "$BACKUP_SH_ARCHIVE_PW" \
--output backup.sh.tar.gz \
"$BACKUP_SH_ARCHIVE_PATH"
@ -178,49 +169,56 @@ Report bugs to: Marco Cetica(<email@marcocetica.com>)
EOF
}
main() {
# Check whether dependecies are installed
checkdeps
if [ $# -eq 0 ]; then
echo "Please, specify an argument."
echo "For more information, try --help."
exit 1
fi
if [ $# -eq 0 ]; then
echo "Please, specify an argument."
echo "For more information, try --help."
exit 1
fi
# Parse CLI arguments
while [ $# -gt 0 ]; do
case $1 in
-b|--backup)
BACKUP_SH_SOURCES_PATH="$2"
BACKUP_SH_OUTPATH="$3"
BACKUP_SH_PASSWORD="$4"
# Parse CLI arguments
while [ $# -gt 0 ]; do
case $1 in
-b|--backup)
BACKUP_SH_SOURCES_PATH="$2"
BACKUP_SH_OUTPATH="$3"
BACKUP_SH_PASSWORD="$4"
if [ -z "$BACKUP_SH_SOURCES_PATH" ] || [ -z "$BACKUP_SH_OUTPATH" ] || [ -z "$BACKUP_SH_PASSWORD" ]; then
echo "Please, specify a source file, an output path and a password."
echo "For more informatio, try --help"
if [ -z "$BACKUP_SH_SOURCES_PATH" ] || [ -z "$BACKUP_SH_OUTPATH" ] || [ -z "$BACKUP_SH_PASSWORD" ]; then
echo "Please, specify a source file, an output path and a password."
echo "For more informatio, try --help"
exit 1
fi
make_backup "$BACKUP_SH_SOURCES_PATH" "$BACKUP_SH_OUTPATH" "$BACKUP_SH_PASSWORD"
exit 0
;;
-e|--extract)
BACKUP_SH_ARCHIVE_PATH="$2"
BACKUP_SH_ARCHIVE_PW="$3"
if [ -z "$BACKUP_SH_ARCHIVE_PATH" ] || [ -z "$BACKUP_SH_ARCHIVE_PW" ]; then
echo "Please, specify an encrypted archive and a password."
echo "For more informatio, try --help"
exit 1
fi
extract_backup "$BACKUP_SH_ARCHIVE_PATH" "$BACKUP_SH_ARCHIVE_PW"
exit 0
;;
-h|--help)
helper "$0"
exit 0
;;
*)
echo "Unknown option $1."
echo "For more information, try --help"
exit 1
fi
make_backup "$BACKUP_SH_SOURCES_PATH" "$BACKUP_SH_OUTPATH" "$BACKUP_SH_PASSWORD"
exit 0
;;
-e|--extract)
BACKUP_SH_ARCHIVE_PATH="$2"
BACKUP_SH_ARCHIVE_PW="$3"
;;
esac
done
}
if [ -z "$BACKUP_SH_ARCHIVE_PATH" ] || [ -z "$BACKUP_SH_ARCHIVE_PW" ]; then
echo "Please, specify an encrypted archive and a password."
echo "For more informatio, try --help"
exit 1
fi
extract_backup "$BACKUP_SH_ARCHIVE_PATH" "$BACKUP_SH_ARCHIVE_PW"
exit 0
;;
-h|--help)
helper "$0"
exit 0
;;
*)
echo "Unknown option $1."
echo "For more information, try --help"
exit 1
;;
esac
done
main "$@"
# vim: ts=4 sw=4 softtabstop=4 expandtab:

View File

@ -1,38 +1,19 @@
.\" Automatically generated by Pandoc 2.17.1.1
.\" Automatically generated by Pandoc 3.1.8
.\"
.\" Define V font for inline verbatim, using C font in formats
.\" that render this, and otherwise B font.
.ie "\f[CB]x\f[]"x" \{\
. ftr V B
. ftr VI BI
. ftr VB B
. ftr VBI BI
.\}
.el \{\
. ftr V CR
. ftr VI CI
. ftr VB CB
. ftr VBI CBI
.\}
.TH "backup.sh" "1" "October 10, 2023" "Marco Cetica" "General Commands Manual"
.hy
.TH "backup.sh" "1" "February 27, 2024" "Marco Cetica" "General Commands Manual"
.SH NAME
.PP
\f[B]backup.sh\f[R] - POSIX compliant, modular and lightweight backup
utility to save and encrypt your files.
.SH SYNOPSIS
.IP
.nf
\f[C]
.EX
Syntax: backup.sh [-b|-e|-h]
options:
-b|--backup SOURCES DEST PASS Backup folders from SOURCES file.
-e|--extract ARCHIVE PASS Extract ARCHIVE using PASS.
-h|--help Show this helper.
\f[R]
.fi
.EE
.SH DESCRIPTION
.PP
\f[B]backup.sh\f[R] is a POSIX compliant, modular and lightweight backup
utility to save and encrypt your files.
This tool is intended to be used on small scale UNIX environment such as
@ -40,95 +21,78 @@ VPS, small servers and workstations.
\f[B]backup.sh\f[R] uses \f[I]rsync\f[R], \f[I]tar\f[R] and
\f[I]gpg\f[R] to copy, compress and encrypt the backup.
.SH OPTIONS
.PP
\f[B]backup.sh\f[R] supports two options: \f[I]backup creation\f[R] and
\f[I]backup extraction\f[R].
The former requires root permissions, while the latter does not.
Let us see them in details:
.SS Backup creation
.PP
To specify the directories to backup, \f[B]backup.sh\f[R] uses an
associative array defined in a text file(called sources file) with the
following syntax:
.IP
.nf
\f[C]
.EX
<LABEL>=<PATH>
\f[R]
.fi
.EE
.PP
Where \f[V]<LABEL>\f[R] is the name of the backup and \f[V]<PATH>\f[R]
Where \f[CR]<LABEL>\f[R] is the name of the backup and \f[CR]<PATH>\f[R]
is its path.
For example, if you want to back up \f[I]/etc/nginx\f[R] and
\f[I]/etc/ssh\f[R], add the following entries to the sources file:
.IP
.nf
\f[C]
.EX
nginx=/etc/nginx/
ssh=/etc/ssh/
\f[R]
.fi
.EE
.PP
\f[B]backup.sh\f[R] will create two folders inside the backup archive
with the following syntax:
.IP
.nf
\f[C]
.EX
backup-<LABEL>-<YYYYMMDD>
\f[R]
.fi
.EE
.PP
In the previous example, this would be:
.IP
.nf
\f[C]
.EX
backup-nginx-<YYYYMMDD>
backup-ssh-<YYYYMMDD>
\f[R]
.fi
.EE
.PP
You can add as many entries as you want, just be sure to use the proper
syntax.
In particular, the sources file, \f[I]should not\f[R] includes:
.IP
.nf
\f[C]
.EX
- Spaces between the label and the equal sign;
- Empty lines;
- Comments.
\f[R]
.fi
.EE
.PP
You can find a sample sources file at \f[V]sources.bk\f[R](or at
\f[V]/usr/local/etc/sources.bk\f[R]).
You can find a sample sources file at \f[CR]sources.bk\f[R](or at
\f[CR]/usr/local/etc/sources.bk\f[R]).
.PP
After having defined the sources file, you can invoke
\f[B]backup.sh\f[R] using the following syntax:
.IP
.nf
\f[C]
.EX
$> sudo ./backup.sh --backup <SOURCES_FILE> <DEST> <ENCRYPTION_PASSWORD>
\f[R]
.fi
.EE
.PP
Where \f[V]<SOURCES_FILE>\f[R] is the \f[I]sources file\f[R],
\f[V]<DEST>\f[R] is the absolute path of the output of the backup
\f[I]without trailing slashes\f[R] and \f[V]<ENCRYPTION_PASSWORD>\f[R]
Where \f[CR]<SOURCES_FILE>\f[R] is the \f[I]sources file\f[R],
\f[CR]<DEST>\f[R] is the absolute path of the output of the backup
\f[I]without trailing slashes\f[R] and \f[CR]<ENCRYPTION_PASSWORD>\f[R]
is the password to encrypt the compressed archive.
.PP
In the previous example, this would be:
.IP
.nf
\f[C]
.EX
$> sudo ./backup.sh --backup sources.bk /home/john badpw1234
\f[R]
.fi
.EE
.PP
The backup utility will begin to copy the files defined in the sources
file:
.IP
.nf
\f[C]
.EX
Copying nginx(1/2)
Copying ssh(2/2)
Compressing backup...
@ -137,195 +101,165 @@ File name: /home/marco/backup-<HOSTNAME>-<YYYYMMDD>.tar.gz.enc
File size: 7336400696(6.9G)
File hash: 0e75ca393117f389d9e8edfea7106d98
Elapsed time: 259 seconds.
\f[R]
.fi
.EE
.PP
After that, you will find the final backup archive in
\f[V]/home/john/backup-<HOSTNAME>-<YYYYMMDD>.tar.gz.enc\f[R].
\f[CR]/home/john/backup-<HOSTNAME>-<YYYYMMDD>.tar.gz.enc\f[R].
.PP
You can also use \f[B]backup.sh\f[R] from a crontab rule:
.IP
.nf
\f[C]
.EX
$> sudo crontab -e
30 03 * * 6 EKEY=$(cat /home/john/.ekey) bash -c \[aq]/usr/local/bin/backup.sh -b /usr/local/etc/sources.bk /home/john $EKEY\[aq] > /dev/null 2>&1
\f[R]
.fi
30 03 * * 6 EKEY=$(cat /home/john/.ekey) sh -c \[aq]/usr/local/bin/backup.sh -b /usr/local/etc/sources.bk /home/john $EKEY\[aq] > /dev/null 2>&1
.EE
.PP
This will automatically run \f[B]backup.sh\f[R] every Saturday morning
at 03:30 AM.
In the example above, the encryption key is stored in a local file(with
fixed permissions) to avoid password leaking in crontab logs.
You can also adopt this practice while using the \f[V]--extract\f[R]
You can also adopt this practice while using the \f[CR]--extract\f[R]
option to avoid password leaking in shell history.
.SS Backup extraction
.PP
\f[B]backup.sh\f[R] can also extract the encrypted backup archive using
the following syntax:
.IP
.nf
\f[C]
.EX
$> ./backup.sh --extract <ENCRYPTED_ARCHIVE> <ARCHIVE_PASSWORD>
\f[R]
.fi
.EE
.PP
Where \f[V]<ENCRYPTED_ARCHIVE>\f[R] is the encrypted backup and
\f[V]<ARCHIVE_PASSWORD>\f[R] is the backup password.
Where \f[CR]<ENCRYPTED_ARCHIVE>\f[R] is the encrypted backup and
\f[CR]<ARCHIVE_PASSWORD>\f[R] is the backup password.
.PP
For instance:
.IP
.nf
\f[C]
.EX
$> ./backup.sh --extract backup-<hostname>-<YYYYMMDD>.tar.gz.enc badpw1234
\f[R]
.fi
.EE
.PP
This will create a new folder called \f[V]backup.sh.tmp\f[R] in your
This will create a new folder called \f[CR]backup.sh.tmp\f[R] in your
local directory.
Be sure to rename any directory with that name to avoid collisions.
From the previous example, you should have the following directories:
.IP
.nf
\f[C]
.EX
backup-nginx-<YYYYMMDD>
backup-ssh-<YYYYMMDD>
\f[R]
.fi
.EE
.SS How does backup.sh work?
.PP
\f[B]backup.sh\f[R] uses \f[I]rsync\f[R] to copy the files,
\f[I]tar\f[R] to compress the backup and \f[I]gpg\f[R] to encrypt it.
By default, rsync is being used with the following parameters:
.IP
.nf
\f[C]
.EX
$> rsync -aPhrq --delete
\f[R]
.fi
.EE
.PP
That is:
.IP
.nf
\f[C]
.EX
- a: archive mode: rsync copies files recursively while preserving as much metadata as possible;
- P: progress/partial: allows rsync to resume interrupted transfers and to shows progress information;
- h: human readable output, rsync shows output numbers in a more readable way;
- r: recursive mode: forces rsync to copy directories and their content;
- q: quiet mode: reduces the amount of information rsync produces;
- delete: delete mode: forces rsync to delete any extraneous files at the destination dir.
\f[R]
.fi
.EE
.PP
After that the backup folder is being encrypted using gpg.
By default, it is used with the following parameters:
.IP
.nf
\f[C]
.EX
$> gpg -a \[rs]
--symmetric \[rs]
--cipher-algo=AES256 \[rs]
--no-symkey-cache \[rs]
--pinentry-mode=loopback \[rs]
--batch --passphrase-fd 3 3<<< \[dq]$PASSWORD\[dq] \[rs]
--batch --passphrase-fd \[dq]$PASSWORD\[dq] \[rs]
--output \[dq]$OUTPUT\[dq] \[rs]
\[dq]$INPUT\[dq]
\f[R]
.fi
.EE
.PP
This command encrypts the backup using the AES-256 symmetric encryption
algorithm with a 256bit key.
Here is what each flag do: - \f[V]--symmetric\f[R]: Use symmetric
Here is what each flag do: - \f[CR]--symmetric\f[R]: Use symmetric
encryption;
.PD 0
.P
.PD
- \f[V]--cipher-algo=AES256\f[R]: Use AES256 algorithm;
- \f[CR]--cipher-algo=AES256\f[R]: Use AES256 algorithm;
.PD 0
.P
.PD
- \f[V]--no-symkey-cache\f[R]: Do not save password on GPG\[cq]s cache;
- \f[CR]--no-symkey-cache\f[R]: Do not save password on GPG\[cq]s cache;
.PD 0
.P
.PD
- \f[V]--pinentry-mode=loopback --batch\f[R]: Do not prompt the user;
- \f[CR]--pinentry-mode=loopback --batch\f[R]: Do not prompt the user;
.PD 0
.P
.PD
- \f[V]--passphrase-fd 3 3<< \[dq]$PASSWORD\[dq]\f[R]: Read password
without revealing it on \f[V]ps\f[R];
- \f[CR]--passphrase-fd 3 3<< \[dq]$PASSWORD\[dq]\f[R]: Read password
without revealing it on \f[CR]ps\f[R];
.PD 0
.P
.PD
- \f[V]--output\f[R]: Specify output file;
- \f[CR]--output\f[R]: Specify output file;
.PD 0
.P
.PD
- \f[V]$INPUT\f[R]: Specify input file.
- \f[CR]$INPUT\f[R]: Specify input file.
.SH EXAMPLES
.PP
Below there are some examples that demonstrate \f[B]backup.sh\f[R]\[cq]s
usage.
.IP "1." 3
Create a backup of \f[V]/etc/ssh\f[R], \f[V]/var/www\f[R] and
\f[V]/var/log\f[R] inside the \f[V]/tmp\f[R] directory using a password
stored in \f[V]/home/op1/.backup_pw\f[R]
Create a backup of \f[CR]/etc/ssh\f[R], \f[CR]/var/www\f[R] and
\f[CR]/var/log\f[R] inside the \f[CR]/tmp\f[R] directory using a
password stored in \f[CR]/home/op1/.backup_pw\f[R]
.PP
The first thing to do is to define the source paths inside a
\f[I]sources file\f[R]:
.IP
.nf
\f[C]
.EX
$> cat sources.bk
ssh=/etc/ssh
web_root=/var/www
logs=/var/log
\f[R]
.fi
.EE
.PP
After that we can load our encryption key from the specified file inside
a environment variable:
.IP
.nf
\f[C]
.EX
$> ENC_KEY=$(cat /home/op1/.backup_pw)
\f[R]
.fi
.EE
.PP
Finally, we can start the backup process with:
.IP
.nf
\f[C]
.EX
$> sudo backup.sh --backup sources.bk /tmp $ENC_KEY
\f[R]
.fi
.EE
.IP "2." 3
Extract the content of a backup made on 2023-03-14 with the password
`Ax98f!'
.PP
To do this, we can simply issue the following command:
.IP
.nf
\f[C]
.EX
$> backup.sh --extract backup-af9a8e6bfe15-20230314.tar.gz.enc \[dq]Ax98f!\[dq]
\f[R]
.fi
.EE
.IP "3." 3
Extract the content of a backup made on 2018-04-25 using the password in
\f[V]/home/john/.pw\f[R]
\f[CR]/home/john/.pw\f[R]
.PP
This example is very similar to the previous one, we just need to read
the password from the text file:
.IP
.nf
\f[C]
.EX
$> backup.sh --extract backup-af9a8e6bfe15-20180425.tar.gz.enc \[dq]$(cat /home/john/.pw)\[dq]
\f[R]
.fi
.EE
.SH AUTHORS
.PP
\f[B]backup.sh\f[R] was written by Marco Cetica on late 2018.
.SH BUGS
.PP
Submit bug reports online at: <email@marcocetica.com> or open an issue
on the issue tracker of the GitHub page of this project:
https://github.com/ice-bit/backup.sh

6
man.md
View File

@ -3,7 +3,7 @@ title: backup.sh
section: 1
header: General Commands Manual
footer: Marco Cetica
date: October 10, 2023
date: February 27, 2024
---
# NAME
@ -98,7 +98,7 @@ You can also use **backup.sh** from a crontab rule:
```
$> sudo crontab -e
30 03 * * 6 EKEY=$(cat /home/john/.ekey) bash -c '/usr/local/bin/backup.sh -b /usr/local/etc/sources.bk /home/john $EKEY' > /dev/null 2>&1
30 03 * * 6 EKEY=$(cat /home/john/.ekey) sh -c '/usr/local/bin/backup.sh -b /usr/local/etc/sources.bk /home/john $EKEY' > /dev/null 2>&1
```
This will automatically run **backup.sh** every Saturday morning at 03:30 AM.
@ -154,7 +154,7 @@ $> gpg -a \
--cipher-algo=AES256 \
--no-symkey-cache \
--pinentry-mode=loopback \
--batch --passphrase-fd 3 3<<< "$PASSWORD" \
--batch --passphrase-fd "$PASSWORD" \
--output "$OUTPUT" \
"$INPUT"
```

View File

@ -1,12 +1,10 @@
#!/bin/bash
#!/usr/bin/env bash
# Unit tests for backup.sh
# This tool is NOT intended to be used outside
# of a testing environment, please use at your own risk.
# By Marco Cetica 2023 (<email@marcocetica.com>)
#
set -e
helper() {
cat <<EOF
backup.sh unit testing suite.