#!/bin/bash server_name="${SERVER_NAME:?SERVER_NAME not set}" restic_password_file="${RESTIC_PASSWORD_FILE:?RESTIC_PASSWORD_FILE not set}" restic_host="${RESTIC_HOST:?RESTIC_HOST not set}" backup_path="${BACKUP_DEST:?BACKUP_DEST not set}" # shellcheck disable=SC2153 ssh_key_file="${SSH_KEY_FILE}" s3_key_file="${AWS_SECRET_ACCESS_KEY_FILE}" restic_repo= restic_extra_options= if [ -n "$ssh_key_file" ] && [ -f "$ssh_key_file" ]; then restic_repo="sftp:$restic_host:/$server_name" # Only check server against provided SSH_HOST_KEY, if set if [ -n "$SSH_HOST_KEY" ]; then tmpfile=$(mktemp) echo "$SSH_HOST_KEY" >>"$tmpfile" echo "using host key $SSH_HOST_KEY" ssh_options="-o 'UserKnownHostsFile $tmpfile'" elif [ "$SSH_HOST_KEY_DISABLE" = "1" ]; then echo "disabling SSH host key checking" ssh_options="-o 'StrictHostKeyChecking=No'" else echo "neither SSH_HOST_KEY nor SSH_HOST_KEY_DISABLE set" fi restic_extra_options="sftp.command=ssh $ssh_options -i $ssh_key_file $restic_host -s sftp" fi if [ -n "$s3_key_file" ] && [ -f "$s3_key_file" ] && [ -n "$AWS_ACCESS_KEY_ID" ]; then AWS_SECRET_ACCESS_KEY="$(cat "${s3_key_file}")" export AWS_SECRET_ACCESS_KEY restic_repo="s3:$restic_host:/$server_name" fi if [ -z "$restic_repo" ]; then echo "you must configure either SFTP or S3 storage, see README" exit 1 fi echo "restic_repo: $restic_repo" # Pre-bake-in some default restic options _restic() { if [ -z "$restic_extra_options" ]; then # shellcheck disable=SC2068 restic -p "$restic_password_file" \ --quiet -r "$restic_repo" \ $@ else # shellcheck disable=SC2068 restic -p "$restic_password_file" \ --quiet -r "$restic_repo" \ -o "$restic_extra_options" \ $@ fi } if [ -n "$SERVICES_OVERRIDE" ]; then # this is fine because docker service names should never include spaces or # glob characters # shellcheck disable=SC2206 services=($SERVICES_OVERRIDE) else mapfile -t services < <(docker service ls --format '{{ .Name }}') fi if [[ \ $*\ != *\ --skip-backup\ * ]]; then rm -rf "${backup_path}" for service in "${services[@]}"; do echo "service: $service" details=$(docker service inspect "$service" --format "{{ json .Spec.Labels }}") if echo "$details" | jq -r '.["backupbot.backup"]' | grep -q 'true'; then pre=$(echo "$details" | jq -r '.["backupbot.backup.pre-hook"]') post=$(echo "$details" | jq -r '.["backupbot.backup.post-hook"]') path=$(echo "$details" | jq -r '.["backupbot.backup.path"]') if [ "$path" = "null" ]; then echo "ERROR: missing 'path' for $service" continue # or maybe exit? fi container=$(docker container ls -f "name=$service" --format '{{ .ID }}') echo "backing up $service" if [ "$pre" != "null" ]; then # run the precommand # shellcheck disable=SC2086 docker exec "$container" sh -c "$pre" fi # run the backup for p in ${path//,/ }; do # creates the parent folder, so `docker cp` has reliable behaviour no matter if $p ends with `/` or `/.` dir=$backup_path/$service/$(dirname "$p") test -d "$dir" || mkdir -p "$dir" docker cp -a "$container:$p" "$dir/$(basename "$p")" done if [ "$post" != "null" ]; then # run the postcommand # shellcheck disable=SC2086 docker exec "$container" sh -c "$post" fi fi done # check if restic repo exists, initialise if not if [ -z "$(_restic cat config)" ] 2>/dev/null; then echo "initializing restic repo" _restic init fi fi if [[ \ $*\ != *\ --skip-upload\ * ]]; then _restic backup --host "$server_name" --tag coop-cloud "$backup_path" fi