2
0
Fork 0
rootfs/usr/local/bin/sh.zio.backup

417 lines
11 KiB
Plaintext
Raw Normal View History

2023-08-15 05:49:27 +02:00
#!/usr/bin/env bash
if [[ -n $ZIO_HELPERS_DIR ]] && [[ -d $ZIO_HELPERS_DIR ]]; then
2023-08-22 01:22:40 +02:00
. "$ZIO_HELPERS_DIR/bash.sh"
2023-08-15 05:49:27 +02:00
else
2023-08-22 01:22:40 +02:00
if [[ "$(realpath "$(dirname "$(realpath -s "$0")")/../../../")" == "/" ]]; then
. /usr/local/libexec/zio/helpers/bash.sh
else
. "$(dirname "$(realpath -s "$0")")/../libexec/zio/helpers/bash.sh"
fi
2023-08-15 05:49:27 +02:00
fi
2023-08-15 07:20:02 +02:00
me_filename="$(basename "$(realpath -s "$0")")"
2023-08-15 05:49:27 +02:00
backup_scripts_dir="$(get_config_dir "sh.zio.backup")/scripts"
2023-08-22 01:38:45 +02:00
cache_dir="$(get_config_dir "sh.zio.backup" "/var/cache")/restic"
2023-08-15 05:49:27 +02:00
secrets_dir="$(get_config_dir "sh.zio.backup")/secrets"
host="$(hostname -s)"
now="$(date +"%Y-%m-%d %H:%M:%S")"
2023-08-22 22:51:38 +02:00
restic_mount_path="/mnt/restic"
2023-08-15 05:49:27 +02:00
restic_path=""
restic_repo_file="$secrets_dir/restic-repo"
restic_repo_passwd_file="$secrets_dir/restic-repo-passwd"
2023-08-22 22:51:38 +02:00
restic_version="0.16.0"
2023-08-15 05:49:27 +02:00
function download_restic() {
restic_version="$1"
restic_download_url="https://github.com/restic/restic/releases/download/v${restic_version}/restic_${restic_version}_linux_amd64.bz2"
2023-08-15 17:47:55 +02:00
restic_path="/tmp/restic-v$restic_version"
2023-08-15 05:49:27 +02:00
restic_archive_path="${restic_path}_$(date +%s).${restic_download_url##*.}"
if [[ ! -f "$restic_path" ]]; then
say info "Downloading Restic ($restic_version)..."
2023-08-15 05:49:27 +02:00
curl -L -s -o "$restic_archive_path" "$restic_download_url"
bzip2 -dc "$restic_archive_path" > "$restic_path"
rm -f "$restic_archive_path"
2023-08-15 05:49:27 +02:00
chmod +x "$restic_path"
2023-08-15 05:49:27 +02:00
if [[ ! "$(echo "$("$restic_path" version)")" == "restic $restic_version"* ]]; then
die "Unexpected output from '$restic_path version'"
rm -f "$restic_path"
fi
2023-08-15 05:49:27 +02:00
fi
}
function invoke_restic() {
command="$1"
args="${@:2}"
case "$(cat "$restic_repo_file")" in
"/"*)
2023-08-22 02:46:37 +02:00
if [[ ! -d "$(cat "$restic_repo_file")" ]]; then
say warning "'$(cat "$restic_repo_file")' does not exist. Creating directory..."
mkdir -p "$(cat "$restic_repo_file")"
fi
;;
2023-08-15 05:49:27 +02:00
"b2"*)
b2_account_id_file="$secrets_dir/b2-account-id"
b2_account_key_file="$secrets_dir/b2-account-key"
test_file "$b2_account_id_file"
test_file "$b2_account_key_file"
export B2_ACCOUNT_ID="$(cat "$b2_account_id_file")"
export B2_ACCOUNT_KEY="$(cat "$b2_account_key_file")"
;;
*)
die "Repository unsupported ("$(cat "$restic_repo_file")")"
;;
esac
if [[ -z $command ]]; then
say warning "No command specified. Not running"
2024-04-01 22:26:52 +02:00
break
2023-08-22 22:51:38 +02:00
elif [[ $command == "generate" || $command == "self-update" ]]; then
say warning "Unsupported command: $command"
2024-04-01 22:26:52 +02:00
break
fi
"$restic_path" \
--cache-dir "$cache_dir" \
--password-file "$restic_repo_passwd_file" \
--repo "$(cat $restic_repo_file)" \
$command $args
2023-08-15 05:49:27 +02:00
}
2023-08-15 07:20:02 +02:00
function invoke_script() {
backup_script="$1"
backup_script_filename="$(basename "$backup_script")"
backup_script_name="${backup_script_filename%.*}"
backup_script_name_length="${#backup_script_name}"
say primary "-[$backup_script_name]$(repeat "-" $((80-3-$backup_script_name_length)))"
chmod +x "$backup_script"
export -f backup_dir
export -f backup_dumps
2023-08-25 02:26:15 +02:00
export -f backup_files
export -f create_tmp_file
2023-08-15 07:20:02 +02:00
export -f die
export -f forget_backup
export -f get_config_dir
2023-08-22 03:29:47 +02:00
export -f get_dump_dir
2023-08-15 07:20:02 +02:00
export -f get_real_path
2023-08-22 23:48:19 +02:00
export -f get_secret
2023-08-15 07:20:02 +02:00
export -f invoke_restic
export -f podman_exec
2023-08-15 07:20:02 +02:00
export -f prune_backup
2023-08-22 04:35:01 +02:00
export -f prune_dumps
2023-08-15 07:20:02 +02:00
export -f say
export -f start_service
export -f stop_service
export -f test_file
export backup_scripts_dir
export cache_dir
export host
export me_filename
2023-08-21 22:08:55 +02:00
export now
2023-08-15 07:20:02 +02:00
export restic_repo_file
export restic_repo_passwd_file
export restic_path
export secrets_dir
2024-04-01 22:29:11 +02:00
set -o pipefail
exec 3>&1
2024-04-02 00:25:20 +02:00
script_error_log_path="$(create_tmp_file)"
script_output=$("$backup_scripts_dir/$backup_script_filename" 2>$script_error_log_path | tee /dev/fd/3)
script_result="$?"
script_log_path="$(create_log "$script_output")"
2024-04-02 00:20:42 +02:00
2024-04-02 00:25:20 +02:00
if [[ -f "$script_error_log_path" ]]; then
cat "$script_error_log_path"
cat "$script_error_log_path" >> "$script_log_path"
2024-04-02 00:20:42 +02:00
fi
2024-04-01 22:32:53 +02:00
2024-04-02 00:25:20 +02:00
if [[ "$script_result" == 0 ]]; then
trigger_notify "success" "Backup script succeeded: $backup_script_name" "$script_log_path"
2024-04-01 22:32:53 +02:00
else
trigger_notify "error" "Backup script failed: $backup_script_name" "$(cat "$script_error_log_path")" "$script_log_path"
2024-04-01 22:32:53 +02:00
fi
2024-04-01 22:29:11 +02:00
2024-04-02 00:25:20 +02:00
rm -f "$script_error_log_path"
2024-04-02 00:15:12 +02:00
2024-04-01 22:29:11 +02:00
set +o pipefail
exec 3>&-
2023-08-15 07:20:02 +02:00
}
2024-04-01 22:32:53 +02:00
function trigger_notify() {
level="$1"
title="$2"
2024-04-02 00:25:20 +02:00
message="$(echo "$3" | sed -r "s/\x1B\[[0-9;]*[JKmsu]//g")"
log_path="$4"
2024-04-01 22:32:53 +02:00
notify_prog="/usr/local/bin/sh.zio.notify"
2024-04-01 22:37:08 +02:00
2024-04-02 00:25:20 +02:00
if [[ $log_path == "" ]]; then
log_path="$message"
message=""
fi
2024-04-02 00:15:12 +02:00
2024-04-01 22:32:53 +02:00
if [[ ! -f "$notify_prog" ]]; then
say warning "'$notify_prog' not found. Not sending notification"
else
"$notify_prog" \
2024-04-02 00:15:12 +02:00
--file "$log_path" \
2024-04-01 22:32:53 +02:00
--level "$level" \
2024-04-02 00:51:33 +02:00
--message '\\`\\`\\`\\n'"$message"'\\n\\`\\`\\`' \
2024-04-01 22:32:53 +02:00
--title "$title"
2024-04-02 00:33:40 +02:00
#--message '```\\n'"${message//$'\n'/'\\n'}"'\\n```' \
2024-04-01 22:32:53 +02:00
fi
}
2023-08-25 02:21:32 +02:00
function backup_files() {
patterns="$1"
args="${@:2}"
2023-08-25 02:26:15 +02:00
files_from_path="$(create_tmp_file "files-from")"
2023-08-25 02:21:32 +02:00
echo "$patterns" > "$files_from_path"
sed -i -e 's/:/\n/g' "$files_from_path"
say info "Backing up: $patterns ➔ $(cat $restic_repo_file)"
invoke_restic \
backup \
--files-from "$files_from_path" \
--iexclude "__MACOSX" \
--iexclude ".cache" \
--iexclude ".DS_Store" \
--iexclude "cache" \
--iexclude "CachedData" \
--iexclude "CachedExtensionVSIXs" \
--iexclude "Code Cache" \
--iexclude "GPUCache" \
--iexclude "GrSharedCache" \
--iexclude "ShaderCache" \
--iexclude "system-cache" \
--iexclude "thumbs.db" \
--iexclude "tmp" \
--exclude "containers/storage/overlay" \
--exclude "containers/storage/overlay-containers" \
--exclude "containers/storage/overlay-images" \
--exclude "containers/storage/overlay-layers" \
--exclude-if-present ".nobackup" \
--host "$host" \
--tag "$me_filename" \
--tag "$(basename "$0")" \
$args
2023-08-25 02:21:32 +02:00
rm -f "$files_from_path"
}
2023-08-15 05:49:27 +02:00
function backup_dir() {
path="$1"
args="${@:2}"
if [[ ! -d "$path" ]]; then
say warning "'$path' does not exist. Not backing up"
else
2023-08-25 02:26:15 +02:00
backup_files "$path" $args
2023-08-15 05:49:27 +02:00
fi
}
2023-08-22 03:49:33 +02:00
function backup_dumps() {
service="$1"
service_dumps_dir="/srv/dumps/$host/$service"
backup_dir "$service_dumps_dir"
if [[ $? == 0 ]]; then
rm -rf "$service_dumps_dir"
2023-08-22 04:35:01 +02:00
prune_dumps
2023-08-22 03:49:33 +02:00
fi
}
2023-08-15 05:49:27 +02:00
function forget_backup() {
timeframe="$1"
if [[ -z $timeframe ]]; then
timeframe="0y0m7d0h"
fi
say info "Forgetting: $timeframe ($host)"
invoke_restic \
forget \
--compact \
2023-08-15 05:49:27 +02:00
--keep-within "$timeframe" \
--host "$host"
}
function prune_backup() {
say info "Pruning"
invoke_restic \
prune \
--dry-run
}
2023-08-22 04:35:01 +02:00
function prune_dumps() {
[[ -z "$(ls -A "/srv/dumps/$host")" ]] && rm -rf "/srv/dumps/$host"
[[ -z "$(ls -A "/srv/dumps")" ]] && rm -rf "/srv/dumps"
}
2023-08-22 03:29:47 +02:00
function get_dump_dir() {
service="$1"
2023-08-22 04:31:31 +02:00
2023-08-22 03:49:33 +02:00
specific_dumps_dir="/srv/dumps/$host/$service/$(date +"%Y%m%d%H%M%S")"
mkdir -p "$specific_dumps_dir"
[[ $? == "0" ]] && echo "$specific_dumps_dir"
2023-08-22 03:29:47 +02:00
}
2023-08-22 23:48:19 +02:00
function get_secret() {
secret_path="$secrets_dir/$1"
2023-08-23 01:17:23 +02:00
if [[ -f "$secret_path" ]]; then
2023-08-22 23:48:19 +02:00
cat "$secret_path"
fi
}
2023-08-15 05:49:27 +02:00
function start_service() {
service="$1"
say info "Starting service: $service"
systemctl start $service
}
function stop_service() {
service="$1"
say info "Stopping service: $service"
systemctl stop $service
}
2023-08-15 07:20:02 +02:00
if [[ "$@" == "help" ]]; then
echo "$me_filename
Usage:
$me_filename
Run all backup scripts available in $backup_scripts_dir
$me_filename <script>
Run a specific script from a given path
$me_filename rescue <snapshot-ID>
Restore <snapshot-ID> to /srv/dumps/$host/restic/<snapshot-ID>
2023-08-22 04:31:31 +02:00
2023-08-22 22:51:38 +02:00
$me_filename toggle-mount
Mount (or unmount) restic to /mnt/restic
2023-08-15 07:20:02 +02:00
2023-08-22 22:51:38 +02:00
$me_filename <command> [arguments]
Execute restic with arbitrary commands and optional arguments.
2023-08-15 07:20:02 +02:00
This command is bootstrapped with some arguments — which are not
overridable — as follows:
* --cache-dir
* --password-file
* --repo
$me_filename help
Output this usage text. Use '--help' or 'help <command>' to output
2023-08-15 15:27:43 +02:00
restic's help text
2023-08-15 07:20:02 +02:00
"
exit 0
fi
2023-08-15 05:49:27 +02:00
test_root
test_prog "bzip2"
test_prog "curl"
test_prog "grep"
test_prog "hostname"
mkdir -p "$backup_scripts_dir"
mkdir -p "$cache_dir"
mkdir -p "$secrets_dir"
chmod -R 711 "$secrets_dir"
2023-08-22 22:51:38 +02:00
download_restic $restic_version
2023-08-15 05:49:27 +02:00
test_file "$restic_repo_file"
test_file "$restic_repo_passwd_file"
2023-08-21 22:07:45 +02:00
if [[ -z "$1" || -f "$1" ]]; then
if [[ -z "$1" ]]; then
say info "Running backup scripts..."
2023-08-15 05:49:27 +02:00
2023-08-21 22:07:45 +02:00
if ! [[ "$(ls -A $backup_scripts_dir)" ]]; then
die "No scripts found in '$backup_scripts_dir'"
fi
2023-08-15 05:49:27 +02:00
2023-08-21 22:07:45 +02:00
for backup_script in $backup_scripts_dir/*; do
invoke_script $backup_script
done
else
say info "Running script: $(basename "$1")"
invoke_script "$1"
fi
say primary "$(repeat "-" 80)"
2023-08-21 22:07:45 +02:00
invoke_restic snapshots \
--compact \
--latest 1 \
--host "$host" \
--tag "$(basename "$0")" \
| sed -E ":begin;$!N;s/$(basename "$0")\n\s+?//;tbegin;P;D"
2023-08-22 22:51:38 +02:00
elif [[ "$1" == "rescue" ]]; then
2023-08-22 04:31:31 +02:00
snapshot="$2"
2023-08-22 23:12:03 +02:00
rescue_dir="/srv/dumps/$host/restic/$snapshot"
2023-08-22 04:31:31 +02:00
[[ -z "$snapshot" ]] && die "No snapshot ID provided"
2023-08-22 23:12:03 +02:00
say info "Rescuing: $snapshot (to '$rescue_dir')"
2023-08-22 04:31:31 +02:00
invoke_restic restore \
2023-08-22 23:12:03 +02:00
--target "$rescue_dir" \
2023-08-22 04:31:31 +02:00
"$snapshot"
2023-08-22 22:51:38 +02:00
elif [[ "$1" == "toggle-mount" ]]; then
2023-08-22 23:01:09 +02:00
function test_restic_mount() {
mountpoint "$restic_mount_path" &>/dev/null
echo $?
}
2023-08-22 22:54:43 +02:00
mkdir -p "$restic_mount_path"
2023-08-22 22:51:38 +02:00
2023-08-22 23:01:09 +02:00
if [[ $(test_restic_mount) == 0 ]]; then
say info "Unmounting: $restic_mount_path"
2023-08-22 22:51:38 +02:00
umount "$restic_mount_path"
if [[ $? == 0 ]]; then
rm -rf "$restic_mount_path"
else
2023-08-22 23:07:13 +02:00
die "Failed to unmount (are you still in '$restic_mount_path'?)"
fi
2023-08-22 22:51:38 +02:00
else
say info "Mounting: $restic_mount_path"
2023-08-22 22:51:38 +02:00
invoke_restic mount "$restic_mount_path" &>/dev/null & disown;
while true; do
if [[ $(test_restic_mount) == 0 ]]; then
exit 0
fi
done
2023-08-22 22:51:38 +02:00
fi
else
command="$1"
arguments="${@:2}"
2023-08-22 04:09:10 +02:00
2023-08-22 04:31:31 +02:00
[[ $command == "purge" ]] && command="prune"
2023-08-22 04:09:10 +02:00
say info "Running: $restic_path $command $arguments"
invoke_restic $command $arguments
exit $?
fi