├── Config
├── RcloneConfig
│ └── .gitkeep
├── DockerConfig
│ ├── DefaultDockerConfig.txt
│ └── ExampleDockerConfig.txt
├── FilterConfig
│ ├── ResticFilter
│ │ └── DefaultResticFilter.txt
│ └── RcloneFilter
│ │ └── DefaultRcloneFilter.txt
└── RepositoryPassword
│ └── restic-repo.password.txt
├── SetupInstruction
├── 2. Create_Rclone_Config.txt
├── 3. Setup_Job.txt
├── 1. Create_Repository.txt
├── 4. Interact_Restic.txt
└── 5. Restore_Data.txt
├── LICENSE
├── Executor
├── ResticPruneExec
├── ResticForgetExec
├── RcloneExec
├── ResticBackupExec
├── Notifier
├── ConditionHandler
├── MainExec
└── DockerHandler
├── Jobs
└── JobTemplate
└── README.md
/Config/RcloneConfig/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/Config/DockerConfig/DefaultDockerConfig.txt:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/Config/FilterConfig/ResticFilter/DefaultResticFilter.txt:
--------------------------------------------------------------------------------
1 | # Docs: "https://restic.readthedocs.io/en/latest/040_backup.html#excluding-files"
2 | ########################## Excludes ##########################
3 |
4 | # Exclusion of logging files
5 | *.log
6 |
--------------------------------------------------------------------------------
/Config/RepositoryPassword/restic-repo.password.txt:
--------------------------------------------------------------------------------
1 | Replace this text with your restic repository password in clear text.
2 | Save the file and remove the .txt extension from the filename.
3 | If you want to change the filename, you need to change the name in
4 | the restic docker commands and also in your repository.
5 |
--------------------------------------------------------------------------------
/Config/FilterConfig/RcloneFilter/DefaultRcloneFilter.txt:
--------------------------------------------------------------------------------
1 | # Docs: https://rclone.org/filtering/#patterns-for-matching-path-file-names
2 | ########################## includes ##########################
3 | #+ include pattern
4 |
5 | ########################## excludes ##########################
6 | #- exclude pattern
7 |
8 | # Exclude complete directory
9 | #- Directory/**
10 |
11 | # Exclusion of logging files
12 | - *.log
13 |
14 | # Exclusion of logging files
15 | - *.log
16 |
--------------------------------------------------------------------------------
/SetupInstruction/2. Create_Rclone_Config.txt:
--------------------------------------------------------------------------------
1 | ###################### Create Rclone Config #####################
2 |
3 | # Run this docker command to create or change your rclone configurations.
4 | # Replace /PATH/TO/ to the location of your BackupScript directory.
5 | # Rclone will start an interactive session where you will be guided
6 | # through the configuration process.
7 | # Visit the rclone documentations to setup your config.
8 |
9 | # https://rclone.org/docs/
10 |
11 |
12 |
13 | docker run -i --rm \
14 | --volume /PATH/TO/BackupScript/Config/RcloneConfig:/config/rclone \
15 | --user $(id -u):$(id -g) \
16 | rclone/rclone config
--------------------------------------------------------------------------------
/SetupInstruction/3. Setup_Job.txt:
--------------------------------------------------------------------------------
1 | ###################### Make Job Executable ################
2 |
3 | # Use to make a job file executable
4 | # Change the /PATH/TO/ to point to BackupScript directory
5 | # Change YourJobFileName to the name of your job file
6 |
7 |
8 | chmod +x /PATH/TO/BackupScript/Jobs/YourJobFileName
9 |
10 |
11 | ###################### Create Cron Job ################
12 | # https://www.freecodecamp.org/news/cron-jobs-in-linux/
13 |
14 | # Open a terminal window on your system.
15 | # Open your cron job overview with an editor using
16 |
17 | contab -e
18 |
19 | # Add a job to the file. Example:
20 |
21 | * 1 * * * /PATH/TO/BackupScript/Jobs/YourJobFileName
22 |
--------------------------------------------------------------------------------
/SetupInstruction/1. Create_Repository.txt:
--------------------------------------------------------------------------------
1 | ###################### Create New Repository ################
2 |
3 | # Run this command to create a new restic repository
4 | # Replace /PATH/TO/ with the location of your BackupScript directory
5 | # Create a new empty directory for your repository in the desired location
6 | # Replace /PATH/TO/YOUR/REPOSITORIE with the path to your repository
7 | # Edit or create a new restic-repo.password file under "BackupScript > Config > RepositoryPassword"
8 | # Replace PASSWORD.FILE with the correct file name
9 | # If you want to have different passwords you can change the name of the file.
10 | # Do not change the location of the password files
11 | # Please make sure to change the name in below docker command and also your backup job
12 | # Run the command
13 |
14 | # https://restic.readthedocs.io/en/latest/
15 |
16 |
17 |
18 | docker run -i --rm \
19 | --volume /PATH/TO/BackupScript:/home \
20 | --volume /PATH/TO/YOUR/REPOSITORIE:/repo \
21 | --user $(id -u):$(id -g) \
22 | restic/restic \
23 | --password-file=/home/Config/RepositoryPassword/PASSWORD.FILE \
24 | init -r repo
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 David Krumm
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Config/DockerConfig/ExampleDockerConfig.txt:
--------------------------------------------------------------------------------
1 | # Configuration for Docker containers that are stopped during the backup and started again afterwards.
2 | #
3 | # Format:
4 | # Add Container to start/stop
5 | # + ContainerName_or_ID
6 | #
7 | # Exclude Container from start/stop
8 | # - ContainerName_or_ID
9 | #
10 | # Behavior:
11 | # The list is processed from top to bottom.
12 | # The first entry in the list is stopped/started first.
13 | # When "reverse_docker_start_sequence" is set to "true", the list is processed in reverse order for starting.
14 | # Comments and empty lines are ignored.
15 | # If no containers are to be stopped, the file can be left empty.
16 |
17 |
18 | # Examples:
19 |
20 | # Add individual containers to proceed in a specific order
21 | + FirstContainerToStop
22 | + SecondContainerToStop
23 |
24 | # Use pattern:
25 | # Adds "Container" "Container-Server" "Container-Database"
26 | + Contai*
27 |
28 | # Adds "Container1-Server" "Container2-Server"; Dose not add "Container-Database"
29 | + Contai*-Server
30 |
31 |
32 | # To stop all containers or all remaining ones.
33 | + *
34 |
35 |
36 | # Excluded explicitly from starting and stopping.
37 | - ContainerName
38 | - Contai*
39 | - Contai*-Server
40 |
--------------------------------------------------------------------------------
/SetupInstruction/4. Interact_Restic.txt:
--------------------------------------------------------------------------------
1 | ###################### Interact with Repository ################
2 |
3 | # Use this Docker command to interact with the repository.
4 | # Replace /PATH/TO/ to the location of your BackupScript directory.
5 | # Replace /PATH/TO/YOUR/REPOSITORIE to the location of your Repository directory.
6 | # Replace PASSWORD.FILE with the correct file name
7 | # Replace YourResticCommands with the commands you want to use.
8 |
9 | # https://restic.readthedocs.io/en/latest/
10 |
11 |
12 |
13 | docker run -i --rm \
14 | --volume /PATH/TO/BackupScript:/home \
15 | --volume /PATH/TO/YOUR/REPOSITORIE:/repo \
16 | --user $(id -u):$(id -g) \
17 | restic/restic \
18 | --password-file=/home/Config/RepositoryPassword/PASSWORD.FILE \
19 | -r repo \
20 | YourResticCommands
21 |
22 |
23 |
24 | ###################### Remove Lock from Repository ################
25 |
26 | # Use this Docker command to remove a lock of your repository.
27 | # Replace /PATH/TO/ to the location of your BackupScript directory.
28 | # Replace /PATH/TO/YOUR/REPOSITORIE to the location of your Repository directory.
29 |
30 | # https://restic.readthedocs.io/en/latest/
31 |
32 |
33 |
34 | docker run -i --rm \
35 | --volume /PATH/TO/BackupScript:/home \
36 | --volume /PATH/TO/YOUR/REPOSITORIE:/repo \
37 | --user $(id -u):$(id -g) \
38 | restic/restic \
39 | --password-file=/home/Config/RepositoryPassword/PASSWORD.FILE \
40 | -r repo unlock
--------------------------------------------------------------------------------
/SetupInstruction/5. Restore_Data.txt:
--------------------------------------------------------------------------------
1 | ###################### Mount Repo for Data Restore #########
2 |
3 | ###################### Mounting
4 |
5 | # Run this command to mount your repository to a empty folder
6 | # Replace /PATH/TO/ to the location of your BackupScript directory
7 | # Replace /PATH/TO/YOUR/DATA to your data directory
8 | # Replace /PATH/TO/YOUR/REPOSITORIE to your repository
9 | # Replace PASSWORD.FILE with the correct file name
10 | # Replace /PATH/TO/EMPTY/DIR to a empty directory
11 | # Run the command and keep the terminal window open
12 |
13 | # https://restic.readthedocs.io/en/latest/
14 |
15 |
16 |
17 | docker run --rm \
18 | --name ResticMounted \
19 | --device /dev/fuse --cap-add SYS_ADMIN \
20 | --volume /PATH/TO/BackupScript:/home \
21 | --volume /PATH/TO/YOUR/DATA:/restore \
22 | --volume /PATH/TO/YOUR/REPOSITORIE:/repo \
23 | --volume /PATH/TO/EMPTY/DIR:/mountdir \
24 | --user $(id -u):$(id -g) \
25 | restic/restic \
26 | --password-file=/home/Config/RepositoryPassword/PASSWORD.FILE \
27 | -r repo mount /mountdir
28 |
29 |
30 |
31 |
32 | ###################### Enter Mount
33 | # Run this command in a second terminal window to enter the mounted restic instance
34 |
35 | docker exec -it ResticMounted sh
36 |
37 |
38 | ###################### Enter Filesystem
39 | # Run this command after you entered the restic instance to enter your repository
40 |
41 | cd mountdir
42 |
43 |
44 | ###################### Copy Files
45 | # Copy file or directories from source path to destination paht including all metadata like rights and ownership
46 |
47 | cp -a /SOURCE-PATH/IN/REPOSITORY /restore/PATH/TO/DESTINATION
--------------------------------------------------------------------------------
/Executor/ResticPruneExec:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | # BackupScript version 2.0.0
3 | #################################### License ################################
4 | # MIT License Copyright (c) 2023 David Krumm #
5 | # All rights reserved. #
6 | # #
7 | # This source code is licensed under the MIT license found in the #
8 | # LICENSE file in the root directory of this source tree. #
9 | #############################################################################
10 |
11 | ######################### Docker Volume Propagation #########################
12 |
13 | repo="$REPOSITORY"
14 |
15 | if [[ $repo == *":"* ]]; then
16 | repo_prop=":$(echo "$repo" | cut -d ":" -f 2)"
17 | repo=$(echo "$repo" | cut -d ":" -f 1)
18 | else
19 | repo_prop=""
20 | fi
21 |
22 | ################################### Restic ##################################
23 |
24 | $NOTIFIER
25 | $NOTIFIER --message "Starting 'Prune' job"
26 | $NOTIFIER --message "Repository '$repo'"
27 |
28 | ################################### Prune
29 |
30 | cmd="docker run --rm --name $JOB_NAME-ResticPrune \
31 | --volume $(dirname "$RESTIC_PW"):/password \
32 | --volume $repo:/repo$repo_prop \
33 | --user $(id -u):$(id -g) \
34 | restic/restic \
35 | --password-file=/password/$(basename "$RESTIC_PW") \
36 | -r /repo prune"
37 |
38 | # progress message
39 | $NOTIFIER
40 | $NOTIFIER --message "Prune in progress ... "
41 | $NOTIFIER --timestamps "false"
42 | #$NOTIFIER --message "$cmd" --timestamps "false"
43 |
44 | output=$(eval "$cmd" 2>&1)
45 | exit_code=$?
46 | $NOTIFIER --message "$output" --timestamps "false"
47 |
48 | ################################# Evaluation ################################
49 |
50 | if [[ "$exit_code" == 0 ]]; then
51 | $NOTIFIER --timestamps "false"
52 | $NOTIFIER --message "Completed 'Prune' job of '$repo' successfully"
53 | exit 0
54 | else
55 | $NOTIFIER --timestamps "false"
56 | $NOTIFIER --channel "important" --type "warning" --message "ERROR $JOB_NAME: Prune of '$repo' failed with exit_code=$exit_code"
57 | $NOTIFIER --timestamps "false"
58 | exit 1
59 | fi
--------------------------------------------------------------------------------
/Executor/ResticForgetExec:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | # BackupScript version 2.0.0
3 | #################################### License ################################
4 | # MIT License Copyright (c) 2023 David Krumm #
5 | # All rights reserved. #
6 | # #
7 | # This source code is licensed under the MIT license found in the #
8 | # LICENSE file in the root directory of this source tree. #
9 | #############################################################################
10 |
11 | ################################# Parameters ################################
12 |
13 | keep_rules="$1" # Rules for keeping snapshots
14 |
15 | ######################### Docker Volume Propagation #########################
16 |
17 | repo="$REPOSITORY"
18 |
19 | if [[ $repo == *":"* ]]; then
20 | repo_prop=":$(echo "$repo" | cut -d ":" -f 2)"
21 | repo=$(echo "$repo" | cut -d ":" -f 1)
22 | else
23 | repo_prop=""
24 | fi
25 |
26 | ################################### Restic ##################################
27 |
28 | $NOTIFIER
29 | $NOTIFIER --message "Starting 'Forget' job"
30 | $NOTIFIER --message "Repository '$repo'"
31 |
32 | ################################### Forget
33 |
34 | cmd="docker run --rm --name $JOB_NAME-ResticForget \
35 | --volume $(dirname "$RESTIC_PW"):/password \
36 | --volume $repo:/repo$repo_prop \
37 | --user $(id -u):$(id -g) \
38 | restic/restic \
39 | --password-file=/password/$(basename "$RESTIC_PW") \
40 | -r /repo forget $keep_rules"
41 |
42 | # progress message
43 | $NOTIFIER
44 | $NOTIFIER --message "Forget in progress ... "
45 | $NOTIFIER --timestamps "false"
46 | #$NOTIFIER --message "$cmd" --timestamps "false"
47 |
48 | output=$(eval "$cmd" 2>&1)
49 | exit_code=$?
50 | $NOTIFIER --message "$output" --timestamps "false"
51 |
52 | ################################# Evaluation ################################
53 |
54 | if [[ "$exit_code" == 0 ]]; then
55 | $NOTIFIER --timestamps "false"
56 | $NOTIFIER --message "Completed 'Forget' job of '$repo' successfully"
57 | exit 0
58 | else
59 | $NOTIFIER --timestamps "false"
60 | $NOTIFIER --channel "important" --type "warning" --message "ERROR $JOB_NAME: Forget of '$repo' failed with exit_code=$exit_code"
61 | $NOTIFIER --timestamps "false"
62 | exit 1
63 | fi
--------------------------------------------------------------------------------
/Executor/RcloneExec:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | # BackupScript version 2.0.0
3 | #################################### License ################################
4 | # MIT License Copyright (c) 2023 David Krumm #
5 | # All rights reserved. #
6 | # #
7 | # This source code is licensed under the MIT license found in the #
8 | # LICENSE file in the root directory of this source tree. #
9 | #############################################################################
10 |
11 | ################################# Parameters ################################
12 |
13 | logging_options="$1" # Settings for the logging of rclone
14 | options="$2" # Additional rclone options
15 |
16 | ######################### Docker Volume Propagation #########################
17 |
18 | source="$REPOSITORY"
19 |
20 | if [[ $source == *":"* ]]; then
21 | source_prop=":$(echo "$source" | cut -d ":" -f 2)"
22 | source=$(echo "$source" | cut -d ":" -f 1)
23 | else
24 | source_prop=""
25 | fi
26 |
27 | ################################# Rclone ####################################
28 |
29 | $NOTIFIER
30 | $NOTIFIER --message "Starting 'Rclone' job"
31 | $NOTIFIER --message "Source '$source'"
32 | $NOTIFIER --message "Remote '$REMOTE'"
33 |
34 |
35 | ################################### Backup
36 |
37 | cmd="docker run --rm --name $JOB_NAME-RcloneBackup \
38 | --volume $HOME_PATH/Config/RcloneConfig:/config/rclone \
39 | --volume $(dirname "$RCLONE_FILTER"):/filterfile \
40 | --volume $HOME_PATH/LogFiles/${JOB_NAME}:/logfiles \
41 | --volume $source:/source$source_prop \
42 | --user $(id -u):$(id -g) \
43 | rclone/rclone sync /source $REMOTE \
44 | --log-file /logfiles/${JOB_NAME}_$(date +'%Y-%m').log \
45 | --filter-from /filterfile/$(basename "$RCLONE_FILTER") \
46 | $options"
47 |
48 | # progress message
49 | $NOTIFIER
50 | $NOTIFIER --message "Rclone in progress ... "
51 | $NOTIFIER --timestamps "false"
52 | #$NOTIFIER --message "$cmd" --timestamps "false"
53 |
54 | eval $cmd
55 | exit_code=$?
56 |
57 | ################################# Evaluation ################################
58 |
59 | if [[ "$exit_code" == 0 ]]; then
60 | $NOTIFIER --timestamps "false"
61 | $NOTIFIER --message "Completed 'Rclone' job to '$REMOTE' successfully"
62 | exit 0
63 | else
64 | $NOTIFIER --timestamps "false"
65 | $NOTIFIER --channel "important" --type "warning" --message "ERROR $JOB_NAME: Rclone to '$REMOTE' failed with exit_code=$exit_code"
66 | $NOTIFIER --timestamps "false"
67 | exit 1
68 | fi
--------------------------------------------------------------------------------
/Executor/ResticBackupExec:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | # BackupScript version 2.0.0
3 | #################################### License ################################
4 | # MIT License Copyright (c) 2023 David Krumm #
5 | # All rights reserved. #
6 | # #
7 | # This source code is licensed under the MIT license found in the #
8 | # LICENSE file in the root directory of this source tree. #
9 | #############################################################################
10 |
11 | ################################# Parameters ################################
12 |
13 | hostname="$1" # Set host name for detection of snapshots in the repo
14 | tags="$2" # Tags for marking snapshots
15 | options="$3" # Additional Restic options
16 |
17 | ######################### Docker Volume Propagation #########################
18 |
19 | source="$SOURCE"
20 | repo="$REPOSITORY"
21 |
22 | if [[ $source == *":"* ]]; then
23 | source_prop=":$(echo "$source" | cut -d ":" -f 2)"
24 | source=$(echo "$source" | cut -d ":" -f 1)
25 | else
26 | source_prop=""
27 | fi
28 |
29 | if [[ $repo == *":"* ]]; then
30 | repo_prop=":$(echo "$repo" | cut -d ":" -f 2)"
31 | repo=$(echo "$repo" | cut -d ":" -f 1)
32 | else
33 | repo_prop=""
34 | fi
35 |
36 | ################################### Restic ##################################
37 |
38 | $NOTIFIER
39 | $NOTIFIER --message "Starting 'Backup' job"
40 | $NOTIFIER --message "Source '$source'"
41 | $NOTIFIER --message "Repository '$repo'"
42 | $NOTIFIER --message "Filter '$(basename "$RESTIC_FILTER")'"
43 |
44 | ################################### Backup
45 |
46 | cmd="docker run --rm --name $JOB_NAME-ResticBackup \
47 | --hostname $hostname \
48 | --volume $(dirname "$RESTIC_PW"):/password \
49 | --volume $(dirname "$RESTIC_FILTER"):/filterfile \
50 | --volume $source:/source$source_prop \
51 | --volume $repo:/repo$repo_prop \
52 | --user $(id -u):$(id -g) \
53 | restic/restic \
54 | --password-file=/password/$(basename "$RESTIC_PW") \
55 | -r /repo backup /source $tags \
56 | --exclude-file=/filterfile/$(basename "$RESTIC_FILTER") \
57 | $options"
58 |
59 | # progress message
60 | $NOTIFIER
61 | $NOTIFIER --message "Backup in progress ... "
62 | $NOTIFIER --timestamps "false"
63 | #$NOTIFIER --message "$cmd" --timestamps "false"
64 |
65 | output=$(eval "$cmd" 2>&1)
66 | exit_code=$?
67 | $NOTIFIER --message "$output" --timestamps "false"
68 |
69 | ################################# Evaluation ################################
70 |
71 | if [[ "$exit_code" == 0 ]]; then
72 | $NOTIFIER --timestamps "false"
73 | $NOTIFIER --message "Completed 'Backup' job of '$source' successfully"
74 | $NOTIFIER
75 | exit 0
76 | else
77 | $NOTIFIER --timestamps "false"
78 | $NOTIFIER --channel "important" --type "warning" --message "ERROR $JOB_NAME: Backup of '$source' failed with exit_code=$exit_code"
79 | $NOTIFIER --timestamps "false"
80 | exit 1
81 | fi
--------------------------------------------------------------------------------
/Executor/Notifier:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | # BackupScript version 2.0.0
3 | #################################### License ################################
4 | # MIT License Copyright (c) 2023 David Krumm #
5 | # All rights reserved. #
6 | # #
7 | # This source code is licensed under the MIT license found in the #
8 | # LICENSE file in the root directory of this source tree. #
9 | #############################################################################
10 |
11 | ################################# Variables #################################
12 |
13 | timestamp="$(date +"%Y-%m-%d %H:%M:%S") - " # Log timestamp
14 | log_file="$HOME_PATH/LogFiles/${JOB_NAME}/${JOB_NAME}_$(date +'%Y-%m').log" # Path to log file
15 | mkdir -p "$(dirname "$log_file")"
16 |
17 | ############################## Parse Arguments ##############################
18 |
19 | channel="normal" # Output channel of notification
20 | type="normal" # Type of message "normal, warning"
21 | message="" # Message to send
22 |
23 | # Parse arguments
24 | while [[ $# -gt 0 ]]; do
25 | case "$1" in
26 | --channel)
27 | channel="$2" # normal or important
28 | shift 2
29 | ;;
30 | --message)
31 | message="$2"
32 | shift 2
33 | ;;
34 | --type)
35 | type="$2" # Type of message "normal, warning"
36 | shift 2
37 | ;;
38 | --timestamps)
39 | if [ "$2" == "false" ]; then # Deactivate Timestamps
40 | timestamp=""
41 | fi
42 | shift 2
43 | ;;
44 | *)
45 | echo "ERROR: $JOB_NAME Notifier unknown option: $1"
46 | echo "ERROR: $JOB_NAME Notifier unknown option: $1" >> "$log_file"
47 | exit 1
48 | ;;
49 | esac
50 | done
51 |
52 |
53 | #############################################################################
54 | # Adjust the code below to suit your needs #
55 | #############################################################################
56 | ################################# Interfaces ################################
57 |
58 | to_terminal() {
59 | # Send message to log and log file
60 | local msg="$1"
61 | printf %b "$msg\n"
62 | }
63 |
64 | to_logfile() {
65 | # Send message to log and log file
66 | local msg="$1"
67 | printf %b "$msg\n" >> "$log_file"
68 | }
69 |
70 | to_unraid_notification() {
71 | # Send to Unraid notification system
72 | local severity="$1"
73 | local msg="$2"
74 | "/usr/local/emhttp/webGui/scripts/notify" -i "$severity" -e "BackupScript" -s "$JOB_NAME" -d "$msg"
75 | }
76 |
77 | to_mail() {
78 | local msg="$1"
79 | #
80 | # Implement your code
81 | #
82 | }
83 |
84 | implement_your_nofification_system() {
85 | local msg="$1"
86 | #
87 | # Implement your code
88 | #
89 | }
90 |
91 | ########################### Output Channels #################################
92 |
93 | if [[ "$channel" == "normal" ]]; then # Message to logfile
94 | to_terminal "${timestamp}${message}"
95 | to_logfile "${timestamp}${message}"
96 |
97 | elif [[ "$channel" == "important" ]]; then # Message to notification system
98 | to_terminal "${timestamp}${message}"
99 | to_logfile "${timestamp}${message}"
100 | # to_unraid_notification "$type" "$message" # Uncomment if you use Unraid as syste
101 | # implement_your_nofification_system # Implement your code to use
102 | # to_mail # Implement your code to use
103 | fi
--------------------------------------------------------------------------------
/Executor/ConditionHandler:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | # BackupScript version 2.0.0
3 | #################################### License ################################
4 | # MIT License Copyright (c) 2023 David Krumm #
5 | # All rights reserved. #
6 | # #
7 | # This source code is licensed under the MIT license found in the #
8 | # LICENSE file in the root directory of this source tree. #
9 | #############################################################################
10 |
11 | ############################## Parse Arguments ##############################
12 |
13 | # Set default values
14 | process=""
15 | schedule="always"
16 |
17 | # Parse arguments
18 | while [[ $# -gt 0 ]]; do
19 | case "$1" in
20 | --task)
21 | task="$2" # evaluate or release
22 | shift 2
23 | ;;
24 | --process)
25 | process="$2" # name of subprocess
26 | shift 2
27 | ;;
28 | --type)
29 | type="$2" # execution, lock or pulldate
30 | shift 2
31 | ;;
32 | --schedule)
33 | schedule="$2" # Schedule for execution
34 | shift 2
35 | ;;
36 | *)
37 | $NOTIFIER --channel "important" --type "warning" --message "ERROR: $JOB_NAME ConditionHandler unknown option: $1"
38 | exit 1
39 | ;;
40 | esac
41 | done
42 |
43 | # Check for necessary arguments.
44 | if [[ -z "$task" || -z "$type" ]]; then
45 | $NOTIFIER --channel "important" --type "warning" --message "ERROR: $JOB_NAME ConditionHandler --task and --type are required"
46 | exit 1
47 | fi
48 |
49 | ################################# Variables #################################
50 |
51 | timestamp=$(date +%Y%m%d)
52 | dir_path="$HOME_PATH/ConditionMarks"
53 | mkdir -p $dir_path
54 | if [ -z "$process" ]; then
55 | marker_file="$dir_path/${JOB_NAME}_${timestamp}.$type"
56 | else
57 | marker_file="$dir_path/${JOB_NAME}_${process}_${timestamp}.$type"
58 | fi
59 |
60 | ################################# Funktions #################################
61 |
62 | cleanup_locks() {
63 | # Iterate through files that match the pattern filename_*.lock
64 | for file in "$dir_path"/*.$type; do
65 | # Extract the filename without the path
66 | local file_name=$(basename "$file")
67 |
68 | # Extract the date from the filename (assuming the format filename_YYYYMMDD.lock)
69 | if [[ "$file_name" =~ [0-9]{8} ]]; then
70 | file_date=${BASH_REMATCH[0]}
71 |
72 | # Compare the extracted date with today's date
73 | if [ "$file_date" != "$timestamp" ]; then
74 | # Delete the file if the date does not match today's date
75 | rm "$file"
76 | fi
77 | fi
78 | done
79 | }
80 |
81 | decode_schedule() {
82 | local sched=$1
83 | local current_date=$(date +%F)
84 | local current_day_of_week=$(date +%a)
85 | local current_day_of_month=$(date +%d)
86 |
87 | case "$sched" in
88 | "always")
89 | echo "execute"
90 | return 0
91 | ;;
92 | weekly:*)
93 | # "weekly: Mon Tue Wed Thu Fri Sat Sun"
94 | local days=${sched#weekly: }
95 | if [[ "$days" == *"$current_day_of_week"* ]]; then
96 | echo "execute"
97 | else
98 | echo "skip"
99 | fi
100 | return 0
101 | ;;
102 | monthly:*)
103 | # "monthly: 1 15"
104 | local days=${sched#monthly: }
105 | for day in $days; do
106 | if [[ "$current_day_of_month" -eq "$day" ]]; then
107 | echo "execute"
108 | return 0
109 | fi
110 | done
111 | echo "skip"
112 | return 0
113 | ;;
114 | "never")
115 | echo "skip"
116 | return 0
117 | ;;
118 | *)
119 | echo "invalid"
120 | return 0
121 | ;;
122 | esac
123 |
124 | return 1
125 | }
126 |
127 | ################################## Jobs #####################################
128 |
129 | if [ "$task" == "evaluate" ] && [ "$type" == "execution" ]; then
130 |
131 | if [[ -f "$marker_file" ]]; then
132 | $NOTIFIER
133 | $NOTIFIER --channel "important" --type "normal" --message "$JOB_NAME: Mark for 'execution' is already set"
134 | exit 99
135 |
136 | else
137 | touch "$marker_file"
138 | $NOTIFIER
139 | $NOTIFIER --message "Create '$(basename "$marker_file")'"
140 | exit 0
141 | fi
142 |
143 |
144 | elif [ "$task" == "release" ] && [ "$type" == "execution" ]; then
145 |
146 | rm $marker_file
147 | $NOTIFIER
148 | $NOTIFIER --message "Delete Mark '$(basename "$marker_file")'"
149 | exit 0
150 |
151 |
152 | elif [ "$task" == "evaluate" ] && [ "$type" == "lock" ]; then
153 |
154 | cleanup_locks
155 |
156 | if [[ -f "$marker_file" && $schedule != "always" ]]; then
157 | $NOTIFIER
158 | $NOTIFIER --message "Mark for '$(basename "$marker_file")' is already set"
159 | exit 99
160 | fi
161 |
162 | result=$(decode_schedule "$schedule")
163 |
164 | if [[ $result == "execute" ]] ; then
165 | touch "$marker_file"
166 | $NOTIFIER
167 | $NOTIFIER --message "Create '$(basename "$marker_file")'"
168 | exit 0
169 |
170 | elif [[ $result == "skip" ]]; then
171 | $NOTIFIER
172 | $NOTIFIER --message "Skipped '$process' due to the schedule '$schedule'"
173 | exit 99
174 | elif [[ $result == "invalid" ]]; then
175 | $NOTIFIER --channel "important" --type "warning" --message "ERROR: $process schedule '$schedule' is no valid option"
176 | $NOTIFIER --message "Here are some examples of valid options:"
177 | $NOTIFIER --message "'always'"
178 | $NOTIFIER --message "'weekly: Mon, Tue, Thu, Sat'"
179 | $NOTIFIER --message "'monthly: 12'"
180 | $NOTIFIER --message "'never'"
181 | exit 1
182 | else
183 | $NOTIFIER --channel "important" --type "warning" --message "ERROR: $JOB_NAME decode schedule '$schedule' failed"
184 | exit 1
185 | fi
186 |
187 |
188 | else
189 | $NOTIFIER --channel "important" --type "warning" --message "ERROR: $JOB_NAME ConditionHandler command unknown"
190 | exit 1
191 | fi
192 |
--------------------------------------------------------------------------------
/Jobs/JobTemplate:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | # BackupScript version 2.0.0
3 | #################################### License ####################################
4 | # MIT License Copyright (c) 2023 David Krumm #
5 | # All rights reserved. #
6 | # #
7 | # This source code is licensed under the MIT license found in the #
8 | # LICENSE file in the root directory of this source tree. #
9 | #################################################################################
10 | # BackupScript #
11 | #################################################################################
12 | #
13 | unique_job_name="ChangeMeToUniqueName" # Unique job name. Do not use space or underscore!!!
14 | #
15 | system_id_name="HOSTNAME" # Name to identify your System in Snapshots.
16 | #
17 | schedule_update_Restic_and_Rclone="monthly: 1" # Schedule for the execution of the respective sub-process:
18 | # "never", "always", "weekly: Mon Tue Wed Thu Fri Sat Sun", "monthly: 1 7 14 21 28"
19 | # Executed only once per day if the weekly or monthly pattern is used.
20 | #
21 | notify_after_completion="true" # (true/false) The notification system must be set up. See documentation.
22 | #
23 | ############################## Backup (Restic) ##################################
24 | #
25 | schedule_backup="always" #
26 | #
27 | path_to_the_directory_to_be_backed_up="/PATH/TO/DATA:ro" # Source directory to be backed up. Support for Docker volume propagation! E.G. "/PATH/TO/DATA:rw,slave" ro=read-only, rw=read-write
28 | #
29 | path_to_restic_repository="/PATH/TO/REPO:rw,slave" # Path to the backup repository. Support for Docker volume propagation! E.G. "/PATH/TO/REPO:rw,slave" ro=read-only, rw=read-write
30 | #
31 | name_restic_password_file="restic-repo.password" # Name of a password file in the path "BackupScript > Config > RepositoryPassword".
32 | #
33 | restic_backup_tags="--tag FirstTag --tag SecondTag" # Tags to be applied to the backup snapshots.
34 | #
35 | name_restic_filter_file="DefaultResticFilter.txt" # Name of a configuration file in the path "BackupScript > Config > ResticFilter".
36 | #
37 | restic_options="" # Additional options specific to Restic. Check with the documentation.
38 | #
39 | name_docker_config_file="DefaultDockerConfig.txt" # Name of a configuration file in the path "BackupScript > Config > DockerConfig".
40 | #
41 | reverse_docker_start_sequence="false" # (true/false) Option to reverse the order of the Docker containers when starting.
42 | #
43 | ############################## Forget (Restic) ##################################
44 | #
45 | schedule_forget="weekly: Mon Wed Sat" #
46 | #
47 | keep_hourly_for="48h" # Number of hours to keep hourly snapshots.
48 | keep_daily_for="7d" # Number of days to keep daily snapshots.
49 | keep_weekly_for="3m" # Number of weeks to keep weekly snapshots.
50 | keep_monthly_for="1y" # Number of months to keep monthly snapshots.
51 | keep_yearly_for="5y" # Number of years to keep yearly snapshots.
52 | #
53 | ############################## Prune (Restic) ###################################
54 | #
55 | schedule_prune="monthly: 1 15" #
56 | #
57 | ############################## Remote Copy (Rclone) #############################
58 | #
59 | schedule_rclone="weekly: The Sun" #
60 | #
61 | rclone_remote_path="RemoteName:/PATH/ON/REMOTE" # Remote destination for Rclone. Does not support Docker volume propagation!
62 | #
63 | name_rclone_filter_file="DefaultRcloneFilter.txt" # Name of a configuration file in the path "BackupScript > Config > RcloneFilter".
64 | #
65 | rclone_options="--log-level INFO" # Additional options specific to Rclone. Check with the documentation.
66 | # Example: "--dry-run" "--max-delete=50" "--bwlimit 10M" "--log-level DEBUG".
67 | #
68 | #################################################################################
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 | #################################################################################
101 | # Don't change the following lines #
102 | #################################################################################
103 | path_to_BackupScript=$(dirname "$(dirname "$(readlink -f "$0")")")
104 | error_message="\
105 | !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ERROR !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n\
106 | \n\
107 | Project 'BackupsScript' was set up incorrectly.\n\
108 | Please check the setup by following the instructions.\n\
109 | This path '$path_to_BackupScript' should point to the root directory of BackupScript.\n\
110 | \n\
111 | Compare:\n\
112 | \n\
113 | └── BackupScript\n\
114 | ├── Config\n\
115 | │ ├── DockerConfig\n\
116 | │ ├── FilterConfig\n\
117 | │ ├── RcloneConfig\n\
118 | │ └── RepositoryPassword\n\
119 | ├── Executor\n\
120 | ├── Jobs\n\
121 | │ └── YourBackupJob\n\
122 | └── SetupInstructions\n\
123 | \n\
124 | !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ERROR !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n\
125 | "
126 | if [ ! -f "$path_to_BackupScript/Executor/MainExec" ]; then
127 | printf "%b" "$error_message"
128 | exit 1
129 | fi
130 | source $path_to_BackupScript/Executor/MainExec
131 | #################################################################################
--------------------------------------------------------------------------------
/Executor/MainExec:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | # BackupScript version 2.0.0
3 | #################################### License ####################################
4 | # MIT License Copyright (c) 2023 David Krumm #
5 | # All rights reserved. #
6 | # #
7 | # This source code is licensed under the MIT license found in the #
8 | # LICENSE file in the root directory of this source tree. #
9 | #################################################################################
10 | # #
11 | # BackupScript #
12 | # #
13 | #################################################################################
14 |
15 | ############################# Setup Paths #######################################
16 |
17 | export JOB_NAME="$unique_job_name"
18 | export HOME_PATH="$path_to_BackupScript"
19 | export SOURCE="$path_to_the_directory_to_be_backed_up"
20 | export REPOSITORY="$path_to_restic_repository"
21 | export REMOTE="$rclone_remote_path"
22 |
23 | export DOCKER_SHUTDOWN_CONFIG="$HOME_PATH/Config/DockerConfig/$name_docker_config_file"
24 | export RESTIC_PW="$HOME_PATH/Config/RepositoryPassword/$name_restic_password_file"
25 | export RESTIC_FILTER="$HOME_PATH/Config/FilterConfig/ResticFilter/$name_restic_filter_file"
26 | export RCLONE_FILTER="$HOME_PATH/Config/FilterConfig/RcloneFilter/$name_rclone_filter_file"
27 |
28 | export NOTIFIER="$HOME_PATH/Executor/Notifier"
29 | ConditionHandler="$HOME_PATH/Executor/ConditionHandler"
30 | DockerHandler="$HOME_PATH/Executor/DockerHandler"
31 | ResticBackupExec="$HOME_PATH/Executor/ResticBackupExec"
32 | ResticForgetExec="$HOME_PATH/Executor/ResticForgetExec"
33 | ResticPruneExec="$HOME_PATH/Executor/ResticPruneExec"
34 | RcloneExec="$HOME_PATH/Executor/RcloneExec"
35 |
36 | ############################## Check Preconditions ##############################
37 |
38 | directory_pahts=("$HOME_PATH" "$SOURCE" "$REPOSITORY")
39 | config_files=("$DOCKER_SHUTDOWN_CONFIG" "$RESTIC_PW" "$RESTIC_FILTER" "$RCLONE_FILTER")
40 | executable_files=("$NOTIFIER" "$ConditionHandler" "$DockerHandler" "$ResticBackupExec" "$ResticForgetExec" "$ResticPruneExec" "$RcloneExec")
41 |
42 | for directory in "${directory_pahts[@]}"; do
43 | directory=$(echo "$directory" | cut -d':' -f1)
44 |
45 | if [ ! -d "$directory" ]; then
46 | echo "ERROR: The directory $directory does not exist."
47 | exit 1
48 | fi
49 | done
50 |
51 | for file in "${config_files[@]}"; do
52 |
53 | if [ ! -f "$file" ]; then
54 | echo "ERROR: The file $file does not exist."
55 | exit 1
56 |
57 |
58 | elif [ "$(tail -c 1 "$file"; echo x)" != "$(echo x)" ] && [ -n "$(tail -n 1 "$file")" ]; then
59 | echo >> "$file"
60 | fi
61 | done
62 |
63 | for file in "${executable_files[@]}"; do
64 |
65 | if [ ! -f "$file" ]; then
66 | echo "ERROR: $file does not exist"
67 | exit 1
68 |
69 | elif [ ! -x "$file" ]; then
70 | echo "ERROR: $file exists but is not executable"
71 |
72 | chmod +x "$file"
73 |
74 | if [ $? -eq 0 ]; then
75 | echo "-> $file was provided with execution rights"
76 | else
77 | echo "ERROR: The attempt to assign execution rights to the $file failed."
78 | echo "-> Read the instructions and the setup files in the 'BackupScript/SetupInstruction' directory"
79 | echo "-> Use 'sudo chmod +x $file' to give the file the required rights"
80 | exit 1
81 | fi
82 | fi
83 | done
84 |
85 | ############################## Jobs #############################################
86 |
87 | exec_backup=" "
88 | exec_forget=" "
89 | exec_prune=" "
90 | exec_rclone=" "
91 |
92 | $NOTIFIER --timestamps "false" --message \
93 | "\n\
94 | ####################################################################################################\n\
95 | \tStarting '$JOB_NAME'\n\
96 | \tDate: $(date +"%Y-%m-%d")\n\
97 | \tTime: $(date +"%H:%M:%S")\n\
98 | ####################################################################################################"
99 |
100 |
101 | $ConditionHandler --task "evaluate" --type "execution"
102 | job_is_already_executed=$?
103 | if [[ "$job_is_already_executed" == 99 ]]; then
104 | exit 0
105 | elif [[ "$job_is_already_executed" == 1 ]]; then
106 | exit 1
107 | fi
108 |
109 | ############################## Update Restic & Rclone
110 |
111 | $ConditionHandler --task "evaluate" --type "lock" --process "Update" --schedule "$schedule_update_Restic_and_Rclone"
112 | evaluation=$?
113 |
114 | if [[ "$evaluation" == 0 ]]; then
115 | $DockerHandler --action "update"
116 | fi
117 |
118 | ############################## Backup
119 |
120 | $ConditionHandler --task "evaluate" --type "lock" --process "Backup" --schedule "$schedule_backup"
121 | evaluation=$?
122 |
123 | ############### Stop Docker
124 |
125 | if [[ "$evaluation" == 0 ]]; then
126 | $DockerHandler --action "stop"
127 | stop_exit_code=$?
128 | fi
129 |
130 | if [[ "$evaluation" == 1 || "$stop_exit_code" == 1 ]]; then
131 | $ConditionHandler --task "release" --type "execution"
132 | exit 1
133 | fi
134 |
135 | ############### Backup
136 |
137 | if [[ "$evaluation" == 0 ]]; then
138 | $ResticBackupExec "$system_id_name" "$restic_backup_tags" "$restic_options"
139 | backup_exit_code=$?
140 | exec_backup="X"
141 | fi
142 |
143 | if [[ "$backup_exit_code" == 1 ]]; then
144 | $ConditionHandler --task "release" --type "execution"
145 | exit 1
146 | fi
147 |
148 | ############### Start Docker
149 |
150 | if [[ "$evaluation" == 0 ]]; then
151 | $DockerHandler --action "start" --reverse "$reverse_docker_start_sequence"
152 | start_exit_code=$?
153 | fi
154 |
155 | if [[ "$start_exit_code" == 1 ]]; then
156 | $ConditionHandler --task "release" --type "execution"
157 | exit 1
158 | fi
159 |
160 | ############################## Forget
161 |
162 | keep_rules="--keep-within-hourly $keep_hourly_for --keep-within-daily $keep_daily_for"
163 | keep_rules+=" --keep-within-weekly $keep_weekly_for --keep-within-monthly $keep_monthly_for"
164 | keep_rules+=" --keep-within-yearly $keep_yearly_for"
165 |
166 | $ConditionHandler --task "evaluate" --type "lock" --process "Forget" --schedule "$schedule_forget"
167 | evaluation=$?
168 |
169 | if [[ "$evaluation" == 0 ]]; then
170 | $ResticForgetExec "$keep_rules"
171 | forget_exit_code=$?
172 | exec_forget="X"
173 | fi
174 |
175 | if [[ "$evaluation" == 1 || "$forget_exit_code" == 1 ]]; then
176 | $ConditionHandler --task "release" --type "execution"
177 | exit 1
178 | fi
179 |
180 | ############################## Prune
181 |
182 | $ConditionHandler --task "evaluate" --type "lock" --process "Prune" --schedule "$schedule_prune"
183 | evaluation=$?
184 |
185 | if [[ "$evaluation" == 0 ]]; then
186 | $ResticPruneExec
187 | prune_exit_code=$?
188 | exec_prune="X"
189 | fi
190 |
191 | if [[ "$evaluation" == 1 || "$prune_exit_code" == 1 ]]; then
192 | $ConditionHandler --task "release" --type "execution"
193 | exit 1
194 | fi
195 |
196 | ############################## Rclone
197 |
198 | $ConditionHandler --task "evaluate" --type "lock" --process "Rclone" --schedule "$schedule_rclone"
199 | evaluation=$?
200 |
201 | if [[ "$evaluation" == 0 ]]; then
202 | $RcloneExec "--log-file /LogFiles/${JOB_NAME}_$(date +'%Y-%m').log" "$rclone_options"
203 | rclone_exit_code=$?
204 | exec_rclone="X"
205 | fi
206 |
207 | if [[ "$evaluation" == 1 || "$rclone_exit_code" == 1 ]]; then
208 | $ConditionHandler --task "release" --type "execution"
209 | exit 1
210 | fi
211 |
212 | ############################## Finished
213 |
214 | $ConditionHandler --task "release" --type "execution"
215 |
216 | completion_channel="normal"
217 | if [[ "$notify_after_completion" = "true" ]]; then
218 | completion_channel="important"
219 | fi
220 |
221 | $NOTIFIER --timestamps "false" --message \
222 | "\n\
223 | ####################################################################################################"
224 | $NOTIFIER --channel "$completion_channel" --timestamps "false" --message \
225 | "Execution of '$JOB_NAME' successfully finished.\n\n\
226 | Executed parts:\n\
227 | \t[$exec_backup] Backup\n\
228 | \t[$exec_forget] Forget\n\
229 | \t[$exec_prune] Prune\n\
230 | \t[$exec_rclone] Rclone"
231 | $NOTIFIER --timestamps "false" --message \
232 | "####################################################################################################\n"
233 |
234 | #################################################################################
235 | # End #
236 | #################################################################################
--------------------------------------------------------------------------------
/Executor/DockerHandler:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | # BackupScript version 2.0.0
3 | #################################### License ################################
4 | # MIT License Copyright (c) 2023 David Krumm #
5 | # All rights reserved. #
6 | # #
7 | # This source code is licensed under the MIT license found in the #
8 | # LICENSE file in the root directory of this source tree. #
9 | #############################################################################
10 |
11 | ############################## Parse Arguments ##############################
12 |
13 | # Default values
14 | reverse="false"
15 |
16 | # Parse arguments
17 | while [[ $# -gt 0 ]]; do
18 | case "$1" in
19 | --action)
20 | action="$2" # start, stop, update
21 | shift 2
22 | ;;
23 | --reverse)
24 | reverse="$2" # reverse order on start
25 | shift 2
26 | ;;
27 | *)
28 | $NOTIFIER --channel "important" --type "warning" --message "ERROR: $JOB_NAME DockerHandler unknown option: $1"
29 | exit 1
30 | ;;
31 | esac
32 | done
33 |
34 | # Check for necessary arguments
35 | if [[ -z "$action" ]]; then
36 | $NOTIFIER --channel "important" --type "warning" --message "ERROR: $JOB_NAME DockerHandler --action is required"
37 | exit 1
38 | fi
39 |
40 | # Variablen definieren
41 | timeout=60
42 | dir_path="$HOME_PATH/ConditionMarks"
43 | mkdir -p "$dir_path"
44 | started_containers="$dir_path/${JOB_NAME}_Started_Container.temp"
45 | stoped_containers="$dir_path/${JOB_NAME}_Stoped_Container.temp"
46 |
47 | ################################# Funktions #################################
48 |
49 | pull_image() {
50 | local image_name=$1
51 | pull_output=$(docker pull "$image_name" 2>&1)
52 |
53 | if [ $? -ne 0 ]; then
54 | return 1
55 | fi
56 |
57 | if echo "$pull_output" | grep -q 'Downloaded newer image'; then
58 | $NOTIFIER --message "$JOB_NAME: The image '$image_name' has been successfully updated."
59 | elif echo "$pull_output" | grep -q 'Image is up to date'; then
60 | $NOTIFIER --message "$JOB_NAME: The local image '$image_name' is already the latest version."
61 | else
62 | $NOTIFIER --message "$JOB_NAME: Unknown status when the image '$image_name' is being pulled: $pull_output"
63 | fi
64 | }
65 |
66 | container_status() {
67 | docker inspect -f '{{.State.Status}}' "$1" 2>/dev/null
68 | }
69 |
70 | list_started_containers() {
71 | if [ -f "$started_containers" ]; then
72 | rm "$started_containers"
73 | fi
74 |
75 | docker ps --format "{{.ID}} {{.Names}}" > "$started_containers"
76 |
77 | if [ $? -ne 0 ]; then
78 | $NOTIFIER --channel "important" --type "warning" --message "$JOB_NAME: Failed to query started containers"
79 | exit 1
80 | fi
81 | }
82 |
83 | trim() {
84 | local var="$1"
85 | var="${var#"${var%%[![:space:]]*}"}" # Remove leading space
86 | var="${var%"${var##*[![:space:]]}"}" # Remove trailing spaces
87 | echo -n "$var"
88 | }
89 |
90 | start_container() {
91 | local id=$1
92 | local name=$2
93 | docker start "$id" > /dev/null 2>&1
94 | local start_time=$(date +%s)
95 | sleep 2
96 | while [[ "$(container_status "$id")" != "running" ]]; do
97 | sleep 1
98 | if (( $(date +%s) - start_time >= timeout )); then
99 | $NOTIFIER --channel "important" --message "Error: $JOB_NAME timeout while starting container $name ($id)"
100 | exit 1
101 | fi
102 | done
103 | $NOTIFIER --message "Container $name ($id) started successfully."
104 | return 0
105 | }
106 |
107 | stop_container() {
108 | local id=$1
109 | local name=$2
110 | docker stop "$id" > /dev/null 2>&1
111 | local start_time=$(date +%s)
112 | sleep 2
113 | while [[ "$(container_status "$id")" != "exited" ]]; do
114 | sleep 1
115 | if (( $(date +%s) - start_time >= timeout )); then
116 | $NOTIFIER --channel "important" --message "Error: $JOB_NAME timeout while stoping container $name ($id)"
117 | exit 1
118 | fi
119 | done
120 | $NOTIFIER --message "Container $name ($id) stoped successfully."
121 | return 0
122 | }
123 |
124 | decode_config() {
125 |
126 | included_pattern=()
127 | exclude_pattern=()
128 | stop_remaining="false"
129 |
130 |
131 | while IFS= read -r line; do
132 | local flag=$(trim "$(echo "$line" | awk '{print $1}')")
133 | local pattern=$(trim "$(echo "$line" | awk '{print $2}')")
134 |
135 | if [[ $flag == "+" ]]; then
136 |
137 | if [[ "$pattern" == "*" ]]; then
138 | stop_remaining="true"
139 | else
140 | included_pattern+=("$pattern")
141 | fi
142 |
143 | elif [[ $flag == "-" ]]; then
144 | exclude_pattern+=("$pattern")
145 | fi
146 |
147 | done < "$DOCKER_SHUTDOWN_CONFIG"
148 |
149 | }
150 |
151 | process_remaining() {
152 |
153 | list_started_containers
154 |
155 | while IFS= read -r line; do
156 | local id=$(trim "$(echo "$line" | awk '{print $1}')")
157 | local name=$(trim "$(echo "$line" | awk '{print $2}')")
158 | local execute_stop="true"
159 |
160 | for pattern in "${exclude_pattern[@]}"; do
161 |
162 | pattern=$(trim "$pattern")
163 |
164 | if [[ "$id" == $pattern || "$name" == $pattern ]]; then
165 | execute_stop="false"
166 | break
167 | fi
168 | done
169 |
170 | if [[ "$execute_stop" == "true" ]]; then
171 | stop_container "$id" "$name"
172 | echo "$id $name" >> "$stoped_containers"
173 | fi
174 | done < "$started_containers"
175 |
176 | rm "$started_containers"
177 | }
178 |
179 | process_stop() {
180 |
181 | if [ -f "$stoped_containers" ]; then
182 | rm "$stoped_containers"
183 | fi
184 |
185 | touch $stoped_containers
186 | list_started_containers
187 |
188 | local started_containers_id=()
189 | local started_containers_name=()
190 | local stopped_flags=()
191 |
192 | while IFS= read -r line; do
193 | local id=$(trim "$(echo "$line" | awk '{print $1}')")
194 | local name=$(trim "$(echo "$line" | awk '{print $2}')")
195 |
196 | started_containers_id+=("$id")
197 | started_containers_name+=("$name")
198 | stopped_flags+=("false")
199 |
200 | done < "$started_containers"
201 |
202 | for pattern in "${included_pattern[@]}"; do
203 |
204 | pattern=$(trim "$pattern")
205 |
206 | for i in "${!started_containers_id[@]}"; do
207 |
208 | if [[ ${stopped_flags[$i]} == "true" ]]; then
209 | continue
210 | fi
211 |
212 | local id="${started_containers_id[$i]}"
213 | local name="${started_containers_name[$i]}"
214 |
215 | if [[ "$id" == $pattern || "$name" == $pattern ]]; then
216 | stop_container "$id" "$name"
217 | echo "$id $name" >> "$stoped_containers"
218 | stopped_flags[$i]="true"
219 | break
220 | fi
221 |
222 | done
223 | done
224 |
225 | rm "$started_containers"
226 |
227 | if [[ $stop_remaining == "true" ]]; then
228 | process_remaining
229 | fi
230 | }
231 |
232 | process_start() {
233 |
234 | if [ ! -f "$stoped_containers" ]; then
235 | $NOTIFIER --channel "important" --type "warning" --message "$JOB_NAME: Start containers faild"
236 | $NOTIFIER --message "Temporary file of the stopped container was lost"
237 | exit 1
238 | fi
239 |
240 | local ids_to_start=()
241 | local names_to_start=()
242 |
243 | while IFS= read -r line; do
244 | local id=$(trim "$(echo "$line" | awk '{print $1}')")
245 | local name=$(trim "$(echo "$line" | awk '{print $2}')")
246 |
247 | ids_to_start+=("$id")
248 | names_to_start+=("$name")
249 |
250 | done < "$stoped_containers"
251 |
252 | if [[ "$reverse" == "true" ]]; then
253 | ids_to_start=($(echo "${ids_to_start[@]}" | tac -s ' '))
254 | names_to_start=($(echo "${names_to_start[@]}" | tac -s ' '))
255 | fi
256 |
257 | for i in "${!ids_to_start[@]}"; do
258 | start_container "${ids_to_start[$i]}" "${names_to_start[$i]}"
259 | done
260 |
261 | rm "$stoped_containers"
262 |
263 | }
264 |
265 | ################################## Jobs #####################################
266 |
267 | if [[ "$action" == "update" ]]; then
268 |
269 | $NOTIFIER
270 | $NOTIFIER --message "Starting 'Update' job"
271 | $NOTIFIER
272 |
273 | pull_image "restic/restic"
274 | update_restic_result=$?
275 | pull_image "rclone/rclone"
276 | update_rclone_result=$?
277 |
278 | if [[ "$update_restic_result" == 1 || "$update_rclone_result" == 1 ]]; then
279 | $NOTIFIER --channel "important" --type "warning" --message "$JOB_NAME: Pull or update of Restic or Rclone failed."
280 | $NOTIFIER --message "Restic exit code: '$update_restic_result'"
281 | $NOTIFIER --message "Rclone exit code: '$update_rclone_result'"
282 | $NOTIFIER --message "An attempt is made to continue."
283 | exit 0
284 | fi
285 |
286 |
287 | elif [[ "$action" == "stop" ]]; then
288 |
289 | $NOTIFIER
290 | $NOTIFIER --message "Stopping the Docker containers"
291 | $NOTIFIER --message "Config '$(basename "$DOCKER_SHUTDOWN_CONFIG")'"
292 | $NOTIFIER
293 |
294 | decode_config
295 | process_stop
296 | exit 0
297 |
298 |
299 | elif [[ "$action" == "start" ]]; then
300 |
301 | process_start
302 | exit 0
303 |
304 |
305 | else
306 | $NOTIFIER --channel "important" --type "warning" --message "ERROR: $JOB_NAME DockerHandler unknown action: $action"
307 | exit 1
308 | fi
309 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # BackupScript with Restic and Rclone
2 |
3 | ## Introduction
4 |
5 | Welcome to the BackupScripts Repository! This small private project aims to create fast, easy and reliable backups. After setup, all you need to do for this is to customize a template for the desired backup job. The powerful tools Restic and Rclone are used for the backup process. Since the official Docker containers of Restic and Rclone are installed automatically, no additional installation is required. All that is needed is a Unix system (Linux or macOS) on which Docker containers can be run.
6 |
7 | [Restic](https://restic.net/) is an open source backup utility to protect your data by safely storing and restoring incremental backups. It is easy to use, efficient and reliable and offers a straightforward and flexible solution for backing up and restoring data. With Restic, you can manage your backup process, encrypt your data, store it and ensure the security and accessibility of your important files.
8 |
9 | [Rclone](https://rclone.org/) is a versatile command line programme that allows you to synchronise files and directories between different storage locations. Its main purpose is to provide a unified interface for managing and transferring data between different storage platforms such as Google Drive, Dropbox, Amazon S3 or your own remote server. Rclone provides the ability to copy, move and synchronise files, as well as perform advanced operations such as encryption, deduplication and bandwidth control, making it a powerful tool for managing cloud storage.
10 |
11 | The combination of Restic and Rclone provides a flexible and robust solution for performing backups that ensures the security and integrity of your data. The scripts provided in this repository simplify the configuration and execution of backup operations and make it easy for you to set up and maintain a reliable backup system.
12 |
13 | Only basic command line knowledge is required for use and installation. The automation of the individually created backup jobs can be realised via cron jobs or similar. The scripts are not connected to Restic or Rclone. No liability is assumed for the use or loss of data.
14 |
15 | ### Features
16 | - Local and remote backups: The scripts support both local and remote backup scenarios. You can choose to back up your data only to a local directory or use different cloud storage providers supported by Rclone.
17 | - Incremental backups: Restic performs efficient incremental backups by identifying changes in files, minimising storage requirements and network bandwidth usage.
18 | - Encryption and security: Restic encrypts your data before it is stored, ensuring privacy and security. If desired, Rclone supports further encryption on the remote system.
19 | - Configurable retention policies: You can customise retention policies to determine how long backups should be kept, balancing storage usage with backup history.
20 | - Docker handeling: The script allows different docker containers to be stopped and then restarted in a defined order for the backup.
21 | - Notification support: Scripts are provided with support for Unraid's notification system. Deviating solutions must be implemented by the user.
22 |
23 | ## Table of Contents
24 |
25 | 1. [Project Description](#project-description)
26 | 2. [Installation](#installation)
27 | 3. [Usage](#usage)
28 | 4. [Final Note](#final-note)
29 | 4. [License](#license)
30 |
31 | ## Project Description
32 |
33 | The BackupScript project is a set of bash scripts designed to manage local and remote backups with the Restic and Rclone tools. Its main purpose is to provide a flexible and customisable solution for performing incremental backups, rotating snapshots and moving backups to a remote system.
34 |
35 | The script is structured with a job template. By customising this template and defining values such as the source directory for the backup, the path to the backup repository, the password file for the repository us, multiple backup jobs can be created easily and flexibly. Since the official Docker containers of the respective projects are used to run Rclone and Restic, no direct installation on the system is necessary.
36 |
37 | The script offers functions for handling Docker containers during the backup, so that containers are stopped in a defined order and then restarted. By scheduling the created backup jobs with cron jobs or similar solutions, the entire backup process can be automated.
38 |
39 | ## Installation
40 |
41 | To install and set up the project, please follow the steps below:
42 |
43 | ### Requirements ###
44 |
45 | - **Operating System**
46 | - The script is designed to run on a Unix-like operating system such as Linux or macOS.
47 |
48 | - **Dependencies**
49 | - Docker: The host system should have Docker installed and properly configured to run containers.
50 | - Restic and Rclone: The official Docker images [Restic](https://hub.docker.com/r/restic/restic) and [Rclone](https://hub.docker.com/r/rclone/rclone) are used for execution and installed automatically. Ensure that the system has access to the Internet. Otherwise, the images must be deployed locally.
51 |
52 | - **Setup/Job Configuration**
53 | - Read the instructions to ensure the correct setup and function of backup scripts.
54 | - It is helpful if you inform yourself about the basic functions of Restic and Rclone in order to gain a better understanding of the function and structure of backup scripts.
55 | - Restic and Rclone are command line programs. Detailed instructions are provided for their use. However, there is no graphical user interface.
56 | - As Docker containers are used, you should have basic knowledge of working with Docker containers.
57 |
58 | - **Access and Permissions**
59 | - You should have sufficient access rights on the system to run the script and access the required directories, files, and Docker resources.
60 |
61 | - **Notification Setup**
62 | - The script offers the option of sending notifications. This is already prepared for Unraid. Other output sources (e.g. e-mail) must be implemented yourself.
63 |
64 | - **Script Execution/Scheduling**
65 | - The use of backup scripts requires the execution of bash scripts. Detailed instructions are provided for this.
66 | - To automate backup tasks, the execution of the backup job must be scheduled. Under Unraid, the "User Scripts" application can be used for this purpose. Alternatively, a cron job can be created directly under Linux systems. Detailed instructions are provided for this.
67 |
68 | ### **1. Download the repository** ###
69 | - Download the entire repository and place all the directories and files it contains in a new directory. The name "BackupScript" is recommended. If "BackupScript" is not selected as the name of the root directory, this must be taken into account in the Docker commands during setup. The resulting folder structure is shown below. Do not change the name or structure of the repository content unless you are explicitly requested to do so in the instructions. Place the filled "BackupScript" directory in a suitable location of your choice on your system.
70 |
71 | ```
72 | └── BackupScript
73 | ├── Config
74 | │ ├── DockerConfig
75 | │ ├── FilterConfig
76 | │ ├── RcloneConfig
77 | │ └── RepositoryPassword
78 | ├── Executor
79 | ├── Jobs
80 | └── SetupInstructions
81 | ```
82 |
83 | ### **2. Setup backup repository** ###
84 | - A repository must be created before use. This is where the incremental backups are stored. You can add all backup jobs to the same repository or create a separate repository for each job. Restic [documentation](https://restic.readthedocs.io/en/latest/) is used to create the backups. To create a repository, follow the instructions in "BackupScript > SetupInstruction > Create_Repository.txt".
85 |
86 | ### **3. (Optional) Setup remote system** ###
87 | - To increase the security of the backups, the repository can be transferred to a remote system or cloud. Rclone [documentation](https://rclone.org/docs/) is used for this purpose. Rclone offers a variety of options for connecting to a remote system or cloud. Before using a new connection for the first time, it must be configured according to the Rclone [documentation](https://rclone.org/docs/#configure). If no remote backups are required, this step can be skipped. However, the schedule for Rclone in the backup job must then be set to "never". If you have decided to use a remote system, you can start the interactive setup process by following the instructions in "BackupScript > SetupInstruction > Create_Rclone_Config.txt". All information required for the setup process is provided by the Rclone documentation. Adding new connections or changing existing ones is possible at any time.
88 |
89 | ### **4. Setup backup job** ###
90 | - New backup jobs are created by configuring a job. A pre-filled template is provided for this purpose. Create a copy of the "JobTemplate" file in the "BackupScript > Jobs" directory and give it a unique name (avoid using spaces and underscores). A description of the configuration of new jobs is given in the section [Usage](#usage). By copying the template, any number of jobs with different configurations can be created.
91 |
92 | ### **5. (Optional) Setup notification** ###
93 | - If important events occur during the execution of a script, a notification can be sent. This is already prepared for the unraid notification system. To activate this function, the script "BackupScript > Executor > Notifier" must be opened with an editor. Activation is done by removing the "#" at the beginning of line 100. "Notifier" is designed so that further interfaces for notifications can be added as functions. Implement the interfaces required for your needs and add the methods in the desired "Output Channel".
94 |
95 | ## Usage
96 |
97 | ### Setting up a backup job ###
98 |
99 | Once the setup is complete, any number of backup jobs can be created. To quickly and easily create a backup job, a job template is provided. The file "JobTemplate" can be found under "BackupScript > Jobs". By creating a copy, the template can be customized to the desired backup job. For this purpose, a number of variables are available that can be used to configure the backup job. Please do not change the names of the variables or the range under them. It is also not necessary to change the files in the executor directory, with the exception of "Notifier" for setting up custom notification interfaces.
100 |
101 | A backup job consists of a total of four sub-processes:
102 | 1. **Backup (Restic)** In this step, the source directory is scanned and a new snapshot is added to the repository. As in some cases Docker containers have to be stopped for a backup (e.g. when backing up a database), they can be stopped during the backup using a configuration file and then restarted.
103 | 2. **Forget (Restic)**: Snapshots can be given a shelf life. If this is exceeded, the snapshots in question are removed from the repository. Retention guidelines are available for this purpose. During the "Forget" process, these policies are applied to the snapshots in the repository and removed.
104 | 3. **Prune (Restic)**: When "forgetting", only the incremental links are removed, but no data is deleted from the repository. This is achieved by running "Prune". This searches for files that are not linked to any existing snapshot and can therefore be deleted.
105 | 4. **Remote Copy (Rclone)**: Rclone is used to transfer the local repository to a remote system. In this process, the selected Rclone configuration is used and the repository is transferred. The script uses the "sync" mode of Rclone. Therefore, files that are no longer present in the local repository are also removed from the remote repository.
106 |
107 | The sub-processes "Backup", "Forget", "Prune" and "Remote Copy" have their own schedule. This is independent of the job's execution schedule. Regardless of how often the job is executed, the respective sub-process is only executed if this is provided for by the respective schedule. Sub-processes can be executed "never", "always", on certain days of the week "weekly" or on certain days of the month "monthly". This means that "weekly: Mon Thu Sat" is executed on the weekdays Monday, Thursday and Saturday. In contrast, "monthly: 7 21 28" is executed on the respective calendar days of a month. If the "weekly" or "monthly" pattern is used, the sub-process is only executed once on the day in question. This makes it possible, for example, to create a local backup every 5 minutes by executing the job at this interval and setting the schedule for "Backup" to "always", but only copying to the remote system on certain days using a "weekly" schedule. To prevent re-execution, "Lock files" are created under the path "BackupScript > ConditionMarks" with the designation of the job name and the sub-process. If a sub-process is to be executed a second time, the schedule can be set to "always" for a short time or the relevant lock file must be deleted manually from the "ConditionMarks" directory. Obsolete lock files are deleted automatically
108 |
109 | An overview of all the variables available is listed below with descriptions. It is recommended that you read about the variables before using this script. In all other cases, a look at the documentation of Restic and Rclone will also help.
110 |
111 | ### Job Configuration ###
112 |
113 | The most important variables to configure are:
114 |
115 | - `unique_job_name`: The name of the script or backup job. Must be unique as it is used to recognise whether the backup task is already running. Avoid spaces or underscores!
116 | - `system_id_name`: The name used to identify your system in the snapshots.
117 | - `schedule_update_Restic_and_Rclone`: Schedule for the search for updates for Restic and Rclone. Examples: "never" "always" "weekly: Mon Thu Sat" "monthly: 7 21 28"
118 | - `notify_after_completion`: Option to send a notification after the job has been successfully executed. Options: "true" "false"
119 | - `schedule_backup`: Schedule for the execution of the "Backup" sub-process. Examples: "never" "always" "weekly: Mon Thu Sat" "monthly: 7 21 28"
120 | - `path_to_the_directory_to_be_backed_up`: The source directory to be backed up. Support for [Docker volume propagation](https://docs.docker.com/storage/bind-mounts/)! E.G. "/PATH/TO/DIRECTORY:rw,slave". By using "ro", access can be restricted to read only.
121 | - `path_to_restic_repository`: The path to the backup repository where the backups will be stored. Support for [Docker volume propagation](https://docs.docker.com/storage/bind-mounts/)! E.G. "/PATH/TO/REPO:rw,slave". Write authorisation is required!
122 | - `name_restic_password_file`: Name of a password file in the path "BackupScript > Config > RepositoryPassword" for encrypting your backup repository.
123 | - `restig_backup_tags`: Tags to be assigned to the snapshots of the backup. Can be used to label snapshots. Tags are searchable.
124 | - `name_restic_filter_file`: Name of a configuration file in the path "BackupScript > Config > ResticFilter" to exclude directories/files from the backup. To set it up, see the [documentation](https://restic.readthedocs.io/en/latest/040_backup.html#excluding-files).
125 | - `restic_options`: Possibility to add further options specific to Restic according to the [documentation](https://restic.readthedocs.io/en/latest/040_backup.html#). Example: "--dry-run"
126 | - `name_docker_config_file`: Name of a configuration file in the path "BackupScript > Config > DockerConfig" to control the handling of running Docker containers during the backup. A sample configuration is provided.
127 | - `reverse_docker_start_sequence`: Option to reverse the order of the Docker containers when starting: Options: "true" "false"
128 | - `schedule_forget`: Schedule for the execution of the "Forget" sub-process. Examples: "never" "always" "weekly: Mon Thu Sat" "monthly: 7 21 28"
129 | - `keep_*_for`: Time periods for keeping snapshots.
130 | - `schedule_prune`: Schedule for the execution of the "Prune" sub-process. Examples: "never" "always" "weekly: Mon Thu Sat" "monthly: 7 21 28"
131 | - `rclone_remote_path`: Name of the configuration and path for the remote system. Example: "RemoteName:/PATH/ON/REMOTE"
132 | - `name_rclone_filter_file`: Name of a configuration file in the path "BackupScript > Config > RcloneFilter" to exclude directories/files from the copy to remote system. To set it up, see the [documentation](https://rclone.org/filtering/).
133 | - `rclone_options`: Possibility to add further options specific to Rclone according to the [documentation](https://rclone.org/docs/). Example: "--dry-run" "--max-delete=50" "--bwlimit 10M" "--log-level DEBUG"
134 |
135 | ### Execution of the script ###
136 |
137 | To execute a backup job, the job must be given the necessary authorisation. To do this, follow the instructions "BackupScript > SetupInstruction > Setup_Job.txt".
138 | The "User Scripts" plugin can be used under Unraid to automate the execution of a backup job. Alternatively, every Linux system offers the option of setting up automation via a cron job. You can find detailed instructions here: [Cron-Jobs](https://www.freecodecamp.org/news/cron-jobs-in-linux/). The backup job is then executed at the specified frequency.
139 |
140 | ### Restore Data ###
141 |
142 | With Restic, you can make the content of a backup repository accessible like a normal file system. This allows you to access individual files or directories within the repository without having to restore the entire backup. This process is known as mounting. You can find out how to mount a repository and restore data in the instructions of "BackupScript > SetupInstuctions > Restore_Data.txt". You can find all the options for restoring a backup in the [documentation](https://restic.readthedocs.io/en/latest/050_restore.html#) from restic.
143 |
144 |
145 |
146 |
147 | ## Final Note
148 |
149 | I hope these scripts help you to simplify your backup process. The intention for creating these scripts and writing this guide came about mainly because it took me a relatively long time to use Restic and Rclone over docker containers. Unfortunately, there were no suitable solutions or instructions for me. Therefore, I hope that this will make the way to your backups a little easier for you.
150 |
151 | If you have any questions, comments or errors, please let me know.
152 |
153 | With this in mind, I wish you happy backups!
154 |
155 |
156 |
157 | ## License
158 |
159 | MIT License
160 |
161 | Copyright (c) 2023 David Krumm
162 |
163 | Permission is hereby granted, free of charge, to any person obtaining a copy
164 | of this software and associated documentation files (the "Software"), to deal
165 | in the Software without restriction, including without limitation the rights
166 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
167 | copies of the Software, and to permit persons to whom the Software is
168 | furnished to do so, subject to the following conditions:
169 |
170 | The above copyright notice and this permission notice shall be included in all
171 | copies or substantial portions of the Software.
172 |
173 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
174 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
175 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
176 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
177 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
178 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
179 | SOFTWARE.
180 |
--------------------------------------------------------------------------------