#!/bin/sh ####### ### Backup Seafile/Seafile Pro server on bare-metal (NOT DOCKER) ####### ### text formatting presents norm=$(tput sgr0) err=$(tput bold)$(tput setaf 1) warn=$(tput bold)$(tput setaf 3) ok=$(tput setaf 2) yellow=$(tput setaf 3) cyan=$(tput setaf 6) mag=$(tput setaf 5) ### functions # bad parameter passed to script badParam () { if [ "$1" = "dne" ]; then printf "${err}\nError: '%s %s'\n" "$2" "$3" printf "file or directory does not exist.${norm}\n\n" exit 1 elif [ "$1" = "empty" ]; then printf "${err}\nError: '%s' cannot have a NULL (empty) value.\n" "$2" printf "${cyan}Please use '--help' for assistance${norm}\n\n" exit 1 elif [ "$1" = "svc" ]; then printf "${err}\nError: '%s %s': Service does not exist!${norm}\n\n" \ "$2" "$3" exit 1 fi } # cleanup cleanup () { # cleanup 503 if copied if [ "$err503Copied" -eq 1 ]; then if ! rm -f "$webroot/$err503File" 2>>"$logFile"; then printf "${warn}[%s] -- [WARNING] Could not remove 503 error page." \ "$(stamp)" >> "$logFile" printf " Web interface will not function until this file is " \ >> "$logFile" printf "removed --${norm}\n" >> "$logFile" else prinf "${cyan}[%s] -- [INFO] 503 error page removed --${norm}\n" \ "$(stamp)" >> "$logFile" fi fi if [ "$sqlCopied" -eq 1 ]; then if ! rm -rf "$sqlDumpDir" 2>>"$logFile"; then printf "${warn}[%s] -- [WARNING] Could not remove temporary " \ "$(stamp)" >> "$logFile" printf "SQL dump directory at %s. " "$sqlDumpDir" >> "$logFile" printf "Remove manually to free up space.${norm}\n" >> "$logFile" else prinf "${cyan}[%s] -- [INFO] Temporary SQL dump directory " \ "$(stamp)" >> "$logFile" printf "removed --${norm}\n" >> "$logFile" fi fi if [ "$offlineBackup" -eq 1 ]; then seafSvc start fi } exitError () { printf "${err}[%s] -- [ERROR] %s: %s --${norm}\n" \ "$(stamp)" "$1" "$2" >> "$logFile" cleanup exit "$1" } # control seafile services (systemd) seafSvc () { if [ "$1" = "start" ]; then if ! systemctl start "${seafService}" >> "$logFile" 2>&1; then exitError 100 "Could not start ${seafService}" fi if ! systemctl start "${seafHub}" >> "$logFile" 2>&1; then exitError 101 "Could not start ${seafHub}" fi elif [ "$1" = "stop" ]; then if ! systemctl stop "${seafHub}" >> "$logFile" 2>&1; then exitError 103 "Could not stop ${seafHub}" fi if ! systemctl stop "${seafService}" >> "$logFile" 2>&1; then exitError 102 "Could not stop ${seafService}" fi fi } # generate dynamic timestamps stamp () { (date +%F" "%T) } ### end of functions ### default variable values ## script related # store logfile in the same directory as this script file using the same file # name as the script but with the extension '.log' scriptPath="$( CDPATH='' cd -- "$( dirname -- "$0" )" && pwd -P )" scriptName="$( basename "$0" )" logFile="$scriptPath/${scriptName%.*}.log" # borg output verbosity -- normal borgCreateParams='--stats' borgPruneParams='--list' configDetails="$scriptPath/seafbackup.details" err503Copied=0 sqlCopied=0 # 503 related use503=0 err503Path="$scriptPath/503_backup.html" err503File="${err503Path##*/}" webroot="/usr/share/nginx/html" # seafile related offlineBackup=0 seafService="seafile.service" seafHub="seahub.service" seafDir="/opt/seafile" seafData="/var/seafile" ### process startup parameters while [ $# -gt 0 ]; do case "$1" in -h|-\?|--help) # display help scriptHelp exit 0 ;; -l|--log) # set log file location if [ -n "$2" ]; then logFile="${2%/}" shift else badParam empty "$@" fi ;; -v|--verbose) # set verbose logging from borg borgCreateParams='--list --stats' borgPruneParams='--list' ;; -c|--config|--details) # location of config details file if [ -n "$2" ]; then if [ -f "$2" ]; then configDetails="${2%/}" shift else badParam dne "$@" fi else badParam empty "$@" fi ;; -5|--use-503) # enable copying 503 error page to webroot use503=1 ;; --503-path) # FULL path to 503 file if [ -n "$2" ]; then if [ -f "$2" ]; then err503Path="${2%/}" err503File="${2##*/}" shift else badParam dne "$@" fi else badParam empty "$@" fi ;; -w|--webroot) # path to webroot (copy 503) if [ -n "$2" ]; then if [ -d "$2" ]; then webroot="${2%/}" shift else badParam dne "$@" fi else badParam empty "$@" fi ;; -p|--seaf|--seafdir) # path to seafile program directory if [ -n "$2" ]; then if [ -d "$2" ]; then seafDir="${2%/}" shift else badParam dne "$@" fi else badParam empty "$@" fi ;; -d|--data|--datadir|--seafdata) # path to seafile data directory if [ -n "$2" ]; then if [ -d "$2" ]; then seafData="${2%/}" shift else badParam dne "$@" fi else badParam empty "$@" fi ;; --seafile-service) # name of seafile service if [ -n "$2" ]; then if ! systemctl list-unit-files | grep -Eq "^$2 |^$2.service" then badParam svc "$@" else seafService="${2}" shift fi else badParam empty "$@" fi ;; --seahub-service) # name of seahub service if [ -n "$2" ]; then if ! systemctl list-unit-files | grep -Eq "^$2 |^$2.service" then badParam svc "$@" else seafHub="${2}" shift fi else badParam empty "$@" fi ;; -o|--offline) # shutdown seafile during backup offlineBackup=1 ;; *) printf "${err}\nUnknown option: %s\n" "$1" printf "${cyan}Use '--help' for valid options.{$norm}\n\n" exit 1 ;; esac shift done ### check pre-requisites and default values # does the details file exist? if [ ! -f "$configDetails" ]; then badParam dne "(--details default)" "$configDetails" fi # is borg installed? if ! command -v borg > /dev/null; then printf "\n${err}ERROR: BORG is not installed on this system!${norm}\n\n" exit 3 fi # seafile directories if [ ! -d "$seafDir" ]; then badParam dne "(--seafdir default)" "$seafDir" elif [ ! -d "$seafData" ]; then badParam dne "(--seafdata default)" "$seafData" fi # offline backup if [ "$offlineBackup" -eq 1 ]; then if ! systemctl list-unit-files | \ grep -Eq "^$seafService |^$seafService.service" then badParam svc "(--seafile-service default)" "$seafService" elif ! systemctl list-unit-files | \ grep -Eq "^$seafHub |^$seafHub.service" then badParam svc "(--seahub-service default)" "$seafHub" fi fi # if 503 functionality is enabled, do 503 related files exist? if [ "$use503" -eq 1 ]; then if [ ! -f "$err503Path" ]; then badParam dne "(--503-path default)" "$err503Path" elif [ ! -d "$webroot" ]; then badParam dne "(--webroot default)" "$webroot" fi fi ### start logging printf "${mag}[%s] --- Start %s execution ---${norm}\n" \ "$(stamp)" "$scriptName" >> "$logFile" printf "${cyan}[%s] -- [INFO] Log located at ${yellow}%s${cyan} --${norm}\n" \ "$(stamp)" "$logFile" >> "$logFile" ### 503 functionality if [ "$use503" -eq 1 ]; then printf "${cyan}[%s] -- [INFO] Copying 503 error page to " \ "$(stamp)" >> "$logFile" printf "webroot -- ${norm}\n" >> "$logFile" if ! \cp --force "${err503Path}" "${webroot}/${err503File}" 2>> "$logFile" then printf "${warn}[%s] -- [WARNING] Failed to copy 503 error page. " \ "$(stamp)" >> "$logFile" printf "Web users will NOT be notified --${norm}\n" >> "$logFile" else printf "${ok}[%s] -- [SUCCESS] 503 error page copied --${norm}\n" \ "$(stamp)" >> "$logFile" # set cleanup flag err503Copied=1 fi fi ### stop seahub and seafile service if offline backup requested if [ "$offlineBackup" -eq 1 ]; then printf "${cyan}[%s] -- [INFO] Stopping seafile services --${norm}\n" \ "$(stamp)" >> "$logFile" seafSvc stop printf "${ok}[%s] -- [SUCCESS] Seafile services STOPPED --${norm}\n" \ "$(stamp)" >> "$logFile" fi ### read details file to get variables needed to dump sql and run borg # check if config details file was provided as a relative or absolute path case "${configDetails}" in /*) # absolute path, no need to rewrite variable . "${configDetails}" ;; *) # relative path, prepend './' to create absolute path . "./${configDetails}" ;; esac printf "${cyan}[%s] -- [INFO] ${yellow}%s${cyan} imported --${norm}\n" \ "$(stamp)" "$configDetails" >> "$logFile" ### dump sql databases printf "${cyan}[%s] -- [INFO] Dumping SQL databases --${norm}\n" \ "$(stamp)" >> "$logFile" # create temporary directory to dump files before borg backup if ! sqlDumpDir=$( mktemp -d 2>>"$logFile"); then exitError 111 "Could not create temporary directory to dump SQL files" fi # set cleanup flag sqlCopied=1 printf "${cyan}[%s] -- [INFO] SQL dump files will be temporarily stored in:" \ "$(stamp)" >> "$logFile" printf "\n${yellow}%s/${cyan} --${norm}\n" "$sqlDumpDir" >> "$logFile" # create unique names for database dump files sqlDump_ccnet="backup-$(date +%Y%m%d_%H%M%S)_${ccnetDB_name}.sql" sqlDump_seafile="backup-$(date +%Y%m%d_%H%M%S)_${seafileDB_name}.sql" sqlDump_seahub="backup-$(date +%Y%m%d_%H%M%S)_${seahubDB_name}.sql" ## dump databases # dump CCNET-DB if ! mysqldump -h"${sqlServer}" -u"${sqlUser}" -p"${sqlPass}" \ --opt ccnet-db > "${sqlDumpDir}/${sqlDump_ccnet}" 2>> "$logFile"; then exitError 115 "Could not dump ${ccnetDB_name} database" fi # dump SEAFILE-DB if ! mysqldump -h"${sqlServer}" -u"${sqlUser}" -p"${sqlPass}" \ --opt ccnet-db > "${sqlDumpDir}/${sqlDump_seafile}" 2>> "$logFile"; then exitError 116 "Could not dump ${seafileDB_name} database" fi # dump CCNET-DB if ! mysqldump -h"${sqlServer}" -u"${sqlUser}" -p"${sqlPass}" \ --opt ccnet-db > "${sqlDumpDir}/${sqlDump_seahub}" 2>> "$logFile"; then exitError 117 "Could not dump ${seahubDB_name} database" fi printf "${ok}[%s] -- [SUCCESS] SQL databases dumped successfully --${norm}\n" \ "$(stamp)" >> "$logFile" exit 0 ### error codes # 1: parameter error # 2: not run as root # 3: borg not installed # 100: could not start seafile service # 101: could not start seahub service # 102: could not stop seafile service # 103: could not stop seahub service # 111: could not create tmp dir for SQL dump files # 115: could not dump CCNET-DB # 116: could not dump SEAFILE-DB # 117: could not dump SEAHUB-DB