While setting up kopia cli for my use-case I came to the point where I am wondering how to properly schedule the snapshots for my repositories?
I want to backup the data of my docker containers which is at the volumes folder of the host where I run all my containers via docker-compose. I setup one repository per container and set the retention policy, everything configured in ansible. But how to do the scheduling part now?
Is it cron? Is it installing copia as server e.g. as systemd service? If I use cron, do I not only have to run the snapshot command but also switch repositories inbetween? I strolled the documentation but I have no clear answer till now, thus I liked to ask.
There are people way more informed that might answer but I’ve run systemd timer script that works well. For each repo that is to be backed up the script will connect to the relevant repo, set up logging, run snapshot and any other maintenacne task you need then disconnect and connect to the next repo and so on. Passwords are loaded as an env variable.
Various repos have different backup schedules and I use the sync-to command to create a backup of the backup repos.
“/usr/local/bin/kopia/kopia_snapshot.sh”
has a lot of code that is probably unhelpful to answering the question and specific to my use case. Also, might raise other random questions that are off topic.
It contains
log initialisation
disk mounting procedure for the source and repo drives
preexec and postexec routines
Repo connection commands
Repo status, info, maintenance info output
Snapshot creation
On schedule it runs a snapshot, sync-to and repo verify.
If you have a more specific question it might be better to start another post.
@ted thanks, this helps already to understand the basic workflow.
I will look up your advanced usage of systemd-unit-naming … and try to adjust it to my needs.
thanks
If you are not so familiar with systemd the repo names are substituted as args into the service file, so that to run the respective service timers directly (immediately) you can also use (in generic form):
systemctl start kopia_host1_snapshot_@<SNAPSHOT_GROUP>.service
systemctl stop kopia_host1_snapshot_@<SNAPSHOT_GROUP>.service
systemctl status kopia_host1_snapshot_@<SNAPSHOT_GROUP>.service
%I -It is possible for systemd services to take a single argument via the
“service@argument.service” syntax. Such services are called “instantiated”
services, while the unit definition without the argument parameter is called
a “template”. An example could be a dhcpcd@.service service template which
takes a network interface as a parameter to form an instantiated service. In
the service file, this parameter or “instance name” can be accessed with %-specifiers.
See systemd.unit(5) for details.
%i lower case i preserves the escapes
Using linux, my computer wakes from suspend at the timer allocated time, does the backup then goes back into suspend, unless I start using it for other purposes or already using it. So backups happen as long as it is in suspend.
I’ve managed to clean up my /usr/local/bin/kopia/kopia_snapshot.sh to give you here in case it helps. Bear in mind it may contain some areas in ‘note form’ and may not present the best way to do something or be useful for your use case. It also contain hidden code from other functions not given.
#!/bin/bash
### Kopia backup script template
### https://github.com/kopia/kopia/releases
### https://kopia.io/docs/reference/command-line/common/snapshot-verify/
### Run this script preferably via systemd service timer or otherwise directly
### from the shell terminal
### nice -n 19 sudo bash ./kopia_snapshot.sh <SNAPSHOT_GROUP>
### nice -n 19 sudo bash ./kopia_snapshot.sh NAS-1-2
### nice -n 19 sudo bash ./kopia_snapshot.sh NAS-1-2-3-4
function usage(){
local usagetext
IFS='' read -r -d '' usagetext <<-'EOFMARKER' || true
Usage:
nice -n 19 sudo bash ./kopia_snapshot.sh <SNAPSHOT_GROUP>
or
systemctl start kopia_host1_snapshot_@<SNAPSHOT_GROUP>.service
systemctl stop kopia_host1_snapshot_@<SNAPSHOT_GROUP>.service
systemctl status kopia_host1_snapshot_@<SNAPSHOT_GROUP>.service
Arg: (1 only)
SNAPSHOT_GROUP The snapshot selection to run that defines the source and
destination of the data.
where SNAPSHOT_GROUP can be one of:
NAS-1-2 host1 snapshot of NAS-1, NAS-2 to backup hdd
NAS-1-2-3-4 host1 snapshot of NAS-1, NAS-2, NAS-3, NAS-4 to backup hdd
NAS-3-4 host1 snapshot of NAS-3, NAS-4 to backup hdd
Example:
Run snapshot for host1 NAS-1-2 hdds:
$ nice -n 19 sudo bash ./kopia_snapshot.sh NAS-1-2-3-4
EOFMARKER
printf "%s" "${usagetext}"
}
function kopia_snapshot(){
text="Starting fn kopia_snapshot"
fn_header ${FUNCNAME[0]} ${LINENO} 'hash' "$text"
#
# Create backups
#
# Security considerations
#--------------------------
export KOPIA_CACHE_DIR="/home/user01/.cache/kopia/"
# Log Kopia version
logit "Kopia version: |$(kopia --version)|"
DATE=$(date +%Y-%m-%d_%H-%M-%S.%3N)
notify info "Starting SNAPSHOT |${DATE}|${SNAPSHOT_GROUP}"
#--------------------------------------------------------------------------
# The backup partitions to be used are mounted here
case "${HOSTNAME}" in
host1)
case "${SNAPSHOT_GROUP}" in
'NAS-1-2')
selective_mount 1 2 5
MOUNTPOINTS=( "/mnt/hdd5" )
REPONAME=( "root" "home" "NAS-1" "NAS-2" )
SOURCE=( "/" "/home/" "/mnt/hdd1/" "/mnt/hdd2/" )
;;
'NAS-3-4')
selective_mount 3 4 5
MOUNTPOINTS=( "/mnt/hdd5" )
REPONAME=( "NAS-3" "NAS-4" )
SOURCE=( "/mnt/hdd3/" "/mnt/hdd4/" )
;;
'NAS-1-2-3-4')
selective_mount 1 2 3 4 5
MOUNTPOINTS=( "/mnt/hdd5" )
REPONAME=( "root" "home" "NAS-1" "NAS-2" "NAS-3" "NAS-4" )
SOURCE=( "/" "/home/" "/mnt/hdd1/" "/mnt/hdd2/" "/mnt/hdd3/" "/mnt/hdd4/" )
;;
*)
logit "ERROR - No SNAPSHOT_GROUP found. Looking for |${SNAPSHOT_GROUP}|"
exit
;;
esac
;;
*)
logit "ERROR - No HOST found. Looking for |${SNAPSHOT_GROUP}|"
exit
;;
esac
#--------------------------------------------------------------------------
for MOUNTPOINT in "${MOUNTPOINTS[@]}"; do # Handle multiple backup copies on various mount points
echo
logit "|${BASH_SOURCE}|$0| Fn: ${FUNCNAME[0]} | FOR LOOP MOUNT POINT Processing: |$MOUNTPOINT|"
BASEDIR="${MOUNTPOINT}/${HOSTNAME}_kopia"
# DO NOT CREATE AUTOMATICALLY (false) BEST DONE MANUALLY AS NEEDED. RATHER USE AS A TEST FOR MOUNTED BELOW
if [ ! -d "${BASEDIR}" ] && false; then
logit "No directory ${BASEDIR}. Creating now..."
install -dvm 777 -o root -g root "${BASEDIR}"
echo -e "Prevent writes to the directory by setting immutable bit i. View bits by running $ lsattr"
/usr/bin/chattr +i "${BASEDIR}"
fi
check_permissions 'root'
mountpoint "$MOUNTPOINT"
[ -d "${BASEDIR}" ] && echo $?
if mountpoint -q "$MOUNTPOINT" && [ -d "${BASEDIR}" ]; then
logit "Mountpoint |$MOUNTPOINT| exists"
else
logit "Mountpoint |$MOUNTPOINT| DNE. Continuing to the next"
continue # If repo DNE then skip to the next
fi
# 2 = lockonly for nas-3-4
# where lock is a file that prevents suspend of computer
if [ "${SNAPSHOT_GROUP}" = "NAS-3-4" ] || [ "${SNAPSHOT_GROUP}" = "testing" ]; then
logit "preexec being locked but not run..."
preexec "${BASEDIR}" lockonly "${SNAPSHOT_GROUP}" "$SCRIPTS_DIR2"
else
logit "preexec running..."
preexec "${BASEDIR}" - "${SNAPSHOT_GROUP}" "$SCRIPTS_DIR2" # "$MOUNTPOINT"
fi
logit "preexec finished"
for ((i=0; i<${#REPONAME[@]}; i++)); do
echo
logit "###############################################################################################################"
logit "|$0| Fn: ${FUNCNAME[0]}() FOR LOOP REPO Processing |i=$i|${SOURCE[i]}|${REPONAME[i]}|"
DATE=$(date +%Y-%m-%d_%H-%M-%S)
# Location of the Kopia repository, create if DNE
REPO="${BASEDIR}/${REPONAME[i]}"
LOGDIR="${BASEDIR}/${REPONAME[i]}_log"
CREATENAME="${HOSTNAME}_${REPONAME[i]}_CREATE_${DATE}"
echo -e "Check REPO:|${REPO}|LOGDIR:|${LOGDIR}|CACHE:|CACHEDIR|"
# Log file used for function log output - Need to initialise
FILEPATHOUT=""
init_logfile "${LOGDIR}" "${CREATENAME}.log" "root"
LOGPATH="${FILEPATHOUT}"
echo -e "Check FILEPATHOUT, LOGPATH |<${FILEPATHOUT}> <${LOGPATH}>|"
# New content generated function log file
init_logfile "${LOGDIR}" "${CREATENAME}_content.log" "root"
CONTENTLOGPATH="${FILEPATHOUT}"
echo -e "Check FILEPATHOUT:<${FILEPATHOUT}>"
echo -e "Check CONTENTLOGPATH:<${CONTENTLOGPATH}>"
echo
notify info "Starting back-up source |${SOURCE[i]}| to repo |${REPO}|$DATE|"
logit "| $SCRIPTS_DIR2 | ${SOURCE[i]} | ${REPONAME[i]} | $REPO |"
echo
if [ ! -d "${REPO}" ]; then
logit "No repo directory for ${REPO}. Creating now..."
install -dvm 700 -o root -g root "${REPO}"
kopia repository create filesystem \
--path "${REPO}" \
-p "$KOPIA_PASSWORD"
logit "NOTE: To validate that your provider is compatible with Kopia, please run: $ kopia repository validate-provider"
kopia repository validate-provider
else
logit "Repo <${REPO}> exists already. No creation necessary."
fi
logit "XXXXXXXXX Connect to the repo |${REPO}| Source: |${SOURCE[i]}|"
kopia repository connect filesystem \
--path "${REPO}" \
--log-dir "${LOGDIR}" \
--log-file "${LOGPATH}" \
--content-log-file "${CONTENTLOGPATH}" \
--file-log-level info \
--log-level info \
-p "$KOPIA_PASSWORD"
if [ $? -eq 1 ]; then
logit "Password incorrect, exiting"
exit 1
else
logit "Connection SUCCESS to |${REPO}|"
fi
echo
logit "Repo |${REPO}| status..."
kopia repository status \
--log-dir "${LOGDIR}" \
--log-file "${LOGPATH}" \
--content-log-file "${CONTENTLOGPATH}" \
--file-log-level info \
--log-level info
echo
list_policy
echo
logit "Repo |${REPO}| content stats raw numbers..."
kopia content stats \
--raw \
--log-dir "${LOGDIR}" \
--log-file "${LOGPATH}" \
--content-log-file "${CONTENTLOGPATH}" \
--file-log-level info \
--log-level info
echo
logit "Repo |${REPO}| content stats..."
kopia content stats \
--log-dir "${LOGDIR}" \
--log-file "${LOGPATH}" \
--content-log-file "${CONTENTLOGPATH}" \
--file-log-level info \
--log-level info
echo
logit "Cache info path only..."
kopia cache info \
--path \
--log-dir "${LOGDIR}" \
--log-file "${LOGPATH}" \
--content-log-file "${CONTENTLOGPATH}" \
--file-log-level info \
--log-level info
echo
logit "Cache info..."
kopia cache info \
--log-dir "${LOGDIR}" \
--log-file "${LOGPATH}" \
--content-log-file "${CONTENTLOGPATH}" \
--file-log-level info \
--log-level info
echo
logit "Show maintenance info"
kopia maintenance info \
--log-dir "${LOGDIR}" \
--log-file "${LOGPATH}" \
--content-log-file "${CONTENTLOGPATH}" \
--file-log-level info \
--log-level info
echo
if false; then
logit "Snapshot ${SOURCE[i]} estimate, show all source data...|$(date +%Y-%m-%d_%H-%M-%S)|++++++++++++++++++++++++++++++++++++++++++++"
kopia snapshot estimate \
--upload-speed 110 \
--log-dir "${LOGDIR}" \
--log-file "${LOGPATH}" \
--content-log-file "${CONTENTLOGPATH}" \
--file-log-level info \
--log-level info \
"${SOURCE[i]}"
else
logit "kopia snapshot estimate ${SOURCE[i]}, removed to dramatically improve completion time|$(date +%Y-%m-%d_%H-%M-%S)|++++++++++++++++++++++++++++++++++++++++++++"
fi
echo
DATE1=$(date)
logit "Create snapshot |${SOURCE[i]}| and write to log |${DATE1})|+++++++++++++++++++++++"
kopia snapshot create \
--description "" \
--force-hash 0 \
--log-dir "${LOGDIR}" \
--log-file "${LOGPATH}" \
--content-log-file "${CONTENTLOGPATH}" \
--file-log-level info \
--log-level info \
"${SOURCE[i]}" \
-p "$KOPIA_PASSWORD"
snapshot_exit=$?
DATE2=$(date)
duration=$(datediff "$DATE1" "$DATE2")
notify info "Snapshot completed.|${REPO}| Start: ${DATE1}| End: ${DATE2}| Duration: $duration |"
#--------------------------------------------------------------------------
if [ ${snapshot_exit} -eq 0 ]; then
notify info "Snapshot SUCCESS.|${REPO}|$(date +%Y-%m-%d_%H-%M-%S)|"
elif [ ${snapshot_exit} -eq 1 ]; then
notify info "Snapshot WARNINGS.|${REPO}|$(date +%Y-%m-%d_%H-%M-%S)|"
fi
sync_data_check
logit "Completed backup for repo ${REPO}"
done
echo -e "Running postexec kopia_snapshot.sh"
postexec "${BASEDIR}" - "$SNAPSHOT_GROUP" "$SCRIPTS_DIR2"
done
}
function sync_data_check(){
text=""
fn_header ${FUNCNAME[0]} ${LINENO} 'hash' "$text"
# Just to be completely paranoid
sync
}
function check_args(){
text=""
fn_header ${FUNCNAME[0]} ${LINENO} 'hash' "$text"
# Test no args at all or
if [ -z $* ]; then
printf "No arguments provided\n"
usage
exit 1
else
printf "SUCCESS #1 - Args have been provided\n"
fi
if [ $# -ne 1 ]; then
printf "Not correct number of arguments\n"
usage
exit 1
else
printf "SUCCESS - No. args = 1\n"
fi
ALLOWLIST='(ROOT|HOME|NAS-1|NAS-2|NAS-3|NAS-4|NAS-1-2|NAS-1-2-3-4|NAS-3-4|TESTING)'
if [[ ${1^^} =~ ^${ALLOWLIST}$ ]]; then
printf "SUCCESS - Arg #1 |%s| is in list %s\n" ${1^^} ${ALLOWLIST}
else
printf "ERROR - Arg #1 |%s| is not in list %s\n" ${1^^} ${ALLOWLIST}
usage
exit 1
fi
}
function main(){
text="$0 Main start"
fn_header ${FUNCNAME[0]} ${LINENO} 'hash' "$text"
check_args "$@"
check_permissions 'root'
# NAS-1-2|NAS-1-2-3-4|NAS-3-4|etc
SNAPSHOT_GROUP="$1"
logit "Snapshot group parameter arg: |$SNAPSHOT_GROUP|"
SCRIPTS_DIR=.
SCRIPTS_DIR2="$SCRIPTS_DIR"/scripts
if [ -z "$KOPIA_PASSWORD" ]; then
export KOPIA_PASSWORD=<Encrypted file extraction goes here>
fi
#-------------------------------------------------------
# For manual running to correct missed days.
RUN_SYNC_TO_ONLY=false
#-------------------------------------------------------
if $RUN_SYNC_TO_ONLY; then
for HOSTNAME_IN in 'host1' 'host2' 'host3' 'host4'; do
# Mount the backup hdds
selective_mount 5 11 12
nice -n 19 /usr/bin/bash ./kopia_sync-to.sh "${HOSTNAME_IN}"
done
else
if true; then
kopia_snapshot
fi
if [ "$HOSTNAME" = host1 ]; then
## Run verify only on Sundays
DOW=$(date +%u)
#---------------------------------------
# veryify.sh schedule
#---------------------------------------
case "$DOW" in
7) # Run verify on Sun after NAS-1-2-3-4 backup
time nice -n 19 /usr/bin/bash ./kopia_verify.sh "${SNAPSHOT_GROUP}"
;;
*)
echo -e "Not running <kopia_verify.sh> today | DOW: |<${DOW}>|"
;;
esac
#---------------------------------------
# sync-to schedule
#---------------------------------------
case "$DOW" in
3|7) # Run verify then sync-to on Sun/Wed after NAS-1-2-3-4 backup
echo
selective_mount 11 12
for HOSTNAME_IN in 'host2' 'host4' 'host1' 'host3'; do
nice -n 19 /usr/bin/bash ./kopia_sync-to.sh "${HOSTNAME_IN}"
done
;;
*)
echo -e "Not running <kopia_sync-to.sh> today | DOW: |<${DOW}>|"
;;
esac
else
# Other PCs verify always
time nice -n 19 /usr/bin/bash ./kopia_verify.sh "${SNAPSHOT_GROUP}"
fi
fi
logit "COMPLETED"
# Dismount the backup hdds
selective_dismount 5 11 12
}
main "$@"
great, thanks! I just came back here to grep your files and start playing with it. Now I find your new replies with detailled explanations, wonderful. Will check asap.
I think of two scenarios currently:
customers: backup customer servers … where kopia would run as root user, via timer etc
my systems: not yet sure if to use it with my non-root user and/or root … learning
I spent alot of time trying both options because when mounting I wanted to do that as a user but had issues trying to run other functions or commands as root. In the end I went back to make it all run as root. In some instances I had to try to get some commands to run as user. In those cases I had to set the environment variables to get them to work. The execution of commands between user and root within a script is something I still have issues with and causes me many problems initially and have not yet found a good concise resource to understanding how to work it properly with the varous permissions and env variables.
In all cases though one has to automate the input of passwords so the timer can run without user input. I created encrypted files to do this loading them into env variables. Perhaps not the best way but a way I understood and got working for my system.