command_backup.sh 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338
  1. #!/bin/bash
  2. # LinuxGSM command_backup.sh module
  3. # Author: Daniel Gibbs
  4. # Contributors: https://linuxgsm.com/contrib
  5. # Website: https://linuxgsm.com
  6. # Description: Creates a .tar.gz file in the backup directory.
  7. commandname="BACKUP"
  8. commandaction="Backup"
  9. moduleselfname="$(basename "$(readlink -f "${BASH_SOURCE[0]}")")"
  10. fn_firstcommand_set
  11. # Trap to remove lockfile on quit.
  12. fn_backup_trap() {
  13. if [ "$(type -t fn_stop_warn_players_cancel)" == "function" ]; then
  14. fn_stop_warn_players_cancel
  15. fi
  16. echo -e ""
  17. echo -en "backup ${backupname}.${compressext}..."
  18. fn_print_canceled_eol_nl
  19. fn_script_log_info "Backup ${backupname}.${compressext}: CANCELED"
  20. rm -f "${backupdir:?}/${backupname}.${compressext}" | tee -a "${lgsmlog}"
  21. echo -en "backup ${backupname}.${compressext}..."
  22. fn_print_removed_eol_nl
  23. fn_script_log_info "Backup ${backupname}.${compressext}: REMOVED"
  24. # Remove backup lockfile.
  25. rm -f "${lockdir:?}/backup.lock"
  26. fn_backup_start_server
  27. unset exitbypass
  28. core_exit.sh
  29. }
  30. # Check if a backup is pending or has been aborted using backup.lock.
  31. fn_backup_check_lockfile() {
  32. # Remove stale lockfile, unless a long backup is still running.
  33. local backupsrunning
  34. backupsrunning="$(pgrep -fc -u "$(id -u)" "^([^ ]*/)?bash ([^ ]*/)?${selfname} (backup|b)( |$)")"
  35. if [ -f "${lockdir}/backup.lock" ] && [ "${backupsrunning:-0}" -le 1 ]; then
  36. if [ "$(find "${lockdir}/backup.lock" -mmin +60)" ]; then
  37. fn_print_dots "Lockfile found: "
  38. fn_print_checking_eol
  39. fn_print_warn "Lockfile found: Removing stale lockfile: "
  40. fn_print_warn_eol
  41. fn_script_log_warn "Lockfile found: Removing stale lockfile"
  42. rm -f "${lockdir:?}/backup.lock"
  43. fi
  44. fi
  45. if [ -f "${lockdir}/backup.lock" ]; then
  46. fn_print_info_nl "Lockfile found: Backup is currently running"
  47. fn_script_log_error "Lockfile found: Backup is currently running: ${lockdir}/backup.lock"
  48. core_exit.sh
  49. fi
  50. }
  51. # Initialisation.
  52. fn_backup_init() {
  53. # Backup file name with selfname and current date.
  54. backupname="${selfname}-$(date '+%Y-%m-%d-%H%M%S')"
  55. info_distro.sh
  56. fn_print_dots "Starting backup"
  57. fn_script_log_info "Starting backup"
  58. if [ ! -d "${backupdir}" ] || [ "${backupcount}" == "0" ]; then
  59. fn_print_info_nl "Starting backup: No previous backups found"
  60. fn_script_log_info "No previous backups found"
  61. else
  62. fn_print_info_nl "Starting backup: Previous backups found"
  63. fn_script_log_info "Previous backups found"
  64. if [ "${lastbackupdaysago}" == "0" ]; then
  65. daysago="less than 1 day ago"
  66. elif [ "${lastbackupdaysago}" == "1" ]; then
  67. daysago="1 day ago"
  68. else
  69. daysago="${lastbackupdaysago} days ago"
  70. fi
  71. echo -e "* Previous backup was created ${daysago}, total size ${lastbackupsize}"
  72. fi
  73. }
  74. # Check if server is started and whether to stop it.
  75. fn_backup_stop_server() {
  76. check_status.sh
  77. # Server is running but will not be stopped.
  78. if [ "${stoponbackup}" == "off" ]; then
  79. fn_print_warn_nl "${selfname} is currently running"
  80. echo -e "* Although unlikely; creating a backup while ${selfname} is running might corrupt the backup."
  81. fn_script_log_warn "${selfname} is currently running"
  82. fn_script_log_warn "Although unlikely; creating a backup while ${selfname} is running might corrupt the backup"
  83. # Server is running and will be stopped if stoponbackup=on or unset.
  84. # If server is started
  85. elif [ "${status}" != "0" ]; then
  86. fn_print_restart_warning
  87. startserver="1"
  88. exitbypass=1
  89. command_stop.sh
  90. fn_firstcommand_reset
  91. fi
  92. }
  93. # Create required folders.
  94. fn_backup_dir() {
  95. # Create backupdir if it doesn't exist.
  96. if [ ! -d "${backupdir}" ]; then
  97. mkdir -p "${backupdir}"
  98. fi
  99. }
  100. fn_backup_create_lockfile() {
  101. # Create lockfile.
  102. date '+%s' > "${lockdir:?}/backup.lock"
  103. fn_script_log_info "Backup lockfile generated"
  104. fn_script_log_info "${lockdir}/backup.lock"
  105. # trap to remove lockfile on quit.
  106. trap fn_backup_trap INT
  107. }
  108. fn_select_compression() {
  109. if command -v zstd > /dev/null 2>&1; then
  110. compressprog="zstd"
  111. compressext="tar.zst"
  112. compressflag="--zstd"
  113. elif command -v pigz > /dev/null 2>&1; then
  114. compressprog="pigz"
  115. compressext="tar.gz"
  116. compressflag="--use-compress-program=pigz"
  117. elif command -v gzip > /dev/null 2>&1; then
  118. compressprog="gzip"
  119. compressext="tar.gz"
  120. compressflag="--gzip"
  121. else
  122. compressprog=""
  123. compressext="tar"
  124. compressflag=""
  125. fi
  126. }
  127. # Game data kept outside rootdir, such as worlds saved under the home directory.
  128. fn_backup_extradirs() {
  129. local candidates=()
  130. local covered=() dir dirreal link coveredpath
  131. backupextradirs=()
  132. if [ "${shortname}" == "bt" ]; then
  133. candidates+=("${XDG_DATA_HOME:-${HOME}/.local/share}/Daedalic Entertainment GmbH/Barotrauma")
  134. elif [ "${shortname}" == "ct" ]; then
  135. candidates+=("${serverfiles}DedicatedServerSave")
  136. elif [ "${shortname}" == "dst" ]; then
  137. candidates+=("${persistentstorageroot}")
  138. elif [ "${shortname}" == "pz" ]; then
  139. candidates+=("${HOME}/Zomboid")
  140. elif [ "${shortname}" == "scpsl" ]; then
  141. candidates+=("${HOME}/.config/SCP Secret Laboratory/config")
  142. elif [ "${shortname}" == "sdtd" ]; then
  143. candidates+=("${HOME}/.local/share/7DaysToDie/Saves")
  144. fi
  145. candidates+=("${savedir}" "${servercfgdir}")
  146. covered+=("$(readlink -f "${rootdir}")")
  147. while IFS= read -r link; do
  148. covered+=("$(readlink -f "${link}")")
  149. done < <(find "${rootdir}" -maxdepth 2 -type l 2> /dev/null)
  150. for dir in "${candidates[@]}"; do
  151. if [ -z "${dir}" ] || [ ! -d "${dir}" ]; then
  152. continue
  153. fi
  154. dirreal="$(readlink -f "${dir}")"
  155. for coveredpath in "${covered[@]}" "${backupextradirs[@]}"; do
  156. if [[ "${dirreal}/" == "${coveredpath}/"* ]]; then
  157. continue 2
  158. fi
  159. done
  160. backupextradirs+=("${dirreal}")
  161. done
  162. }
  163. # Compressing files.
  164. fn_backup_compression() {
  165. fn_backup_extradirs
  166. backupextras=()
  167. for extradir in "${backupextradirs[@]}"; do
  168. echo -e "* Including ${extradir}"
  169. fn_script_log_info "Including ${extradir}"
  170. backupextras+=(-C / "${extradir#/}")
  171. done
  172. fn_print_info "A total of ${rootdirduexbackup} will be compressed."
  173. fn_script_log_info "A total of ${rootdirduexbackup} will be compressed: ${backupdir}/${backupname}.${compressext}"
  174. fn_print_dots "Backup (${rootdirduexbackup}) ${backupname}.${compressext}, in progress ..."
  175. fn_script_log_info "Backup ${rootdirduexbackup} ${backupname}.${compressext}, in progress"
  176. excludedir=$(fn_backup_relpath)
  177. if [ ! -d "${rootdir}/${excludedir}" ]; then
  178. fn_print_fail_nl "Problem identifying the previous backup directory for exclusion."
  179. fn_script_log_fail "Problem identifying the previous backup directory for exclusion"
  180. core_exit.sh
  181. fi
  182. backupexcludes=(--exclude "${excludedir}" --exclude "${lockdir}" --exclude "${tmpdir}")
  183. if [[ "${backupdir}" == "${rootdir}/"* ]]; then
  184. backupexcludes+=(--exclude "./${backupdir#"${rootdir}"/}")
  185. fi
  186. if [ -n "${compressflag}" ]; then
  187. tar ${compressflag} -hcf "${backupdir}/${backupname}.${compressext}" "${backupexcludes[@]}" -C "${rootdir}" ./. "${backupextras[@]}"
  188. else
  189. tar -hcf "${backupdir}/${backupname}.${compressext}" "${backupexcludes[@]}" -C "${rootdir}" ./. "${backupextras[@]}"
  190. fi
  191. exitcode=$?
  192. if [ "${exitcode}" -ne 0 ]; then
  193. fn_print_fail_eol
  194. fn_script_log_fail "Backup in progress: FAIL"
  195. echo -e "${extractcmd}" | tee -a "${lgsmlog}"
  196. fn_print_fail_nl "Starting backup"
  197. fn_script_log_fail "Starting backup"
  198. else
  199. fn_print_ok_eol
  200. fn_print_ok "Completed: ${italic}${backupname}.${compressext}${default}, total size $(du -sh "${backupdir}/${backupname}.${compressext}" | awk '{print $1}')"
  201. fn_script_log_pass "Backup created: ${backupname}.${compressext}, total size $(du -sh "${backupdir}/${backupname}.${compressext}" | awk '{print $1}')"
  202. alert="backup"
  203. alert.sh
  204. # A postponed backup is complete.
  205. rm -f "${lockdir:?}/${selfname}-backup-request.lock"
  206. fi
  207. }
  208. # Clear old backups according to maxbackups and maxbackupdays variables.
  209. fn_backup_prune() {
  210. # Clear if backup variables are set.
  211. if [ "${maxbackups}" ] && [ -n "${maxbackupdays}" ]; then
  212. # How many backups there are.
  213. info_distro.sh
  214. # How many backups exceed maxbackups.
  215. backupquotadiff=$((backupcount - maxbackups))
  216. # How many backups exceed maxbackupdays.
  217. backupsoudatedcount=$(find "${backupdir}"/ -type f -name "*.tar.*" -mtime +"${maxbackupdays}" | wc -l)
  218. # If anything can be cleared.
  219. if [ "${backupquotadiff}" -gt "0" ] || [ "${backupsoudatedcount}" -gt "0" ]; then
  220. fn_print_dots "Pruning"
  221. fn_script_log_info "Backup pruning activated"
  222. fn_print_ok_nl "Pruning"
  223. # If maxbackups greater or equal to backupsoutdatedcount, then it is over maxbackupdays.
  224. if [ "${backupquotadiff}" -ge "${backupsoudatedcount}" ]; then
  225. # Display how many backups will be cleared.
  226. echo -e "* Pruning: ${backupquotadiff} backup(s) has exceeded the ${maxbackups} backups limit"
  227. fn_script_log_info "Pruning: ${backupquotadiff} backup(s) has exceeded the ${maxbackups} backups limit"
  228. fn_sleep_time_1
  229. fn_print_dots "Pruning: Clearing ${backupquotadiff} backup(s)"
  230. fn_script_log_info "Pruning: Clearing ${backupquotadiff} backup(s)"
  231. # Clear backups over quota.
  232. find "${backupdir}"/ -type f -name "*.tar.*" -printf '%T@ %p\n' | sort -rn | tail -${backupquotadiff} | cut -f2- -d" " | xargs rm
  233. fn_print_ok_nl "Pruning: Clearing ${backupquotadiff} backup(s)"
  234. fn_script_log_pass "Pruning: Cleared ${backupquotadiff} backup(s)"
  235. # If maxbackupdays is used over maxbackups.
  236. elif [ "${backupquotadiff}" -lt "${backupsoudatedcount}" ]; then
  237. # Display how many backups will be cleared.
  238. echo -e "* Pruning: ${backupsoudatedcount} backup(s) are older than ${maxbackupdays} days."
  239. fn_script_log_info "Pruning: ${backupsoudatedcount} backup(s) older than ${maxbackupdays} days."
  240. fn_sleep_time_1
  241. fn_print_dots "Pruning: Clearing ${backupquotadiff} backup(s)."
  242. fn_script_log_info "Pruning: Clearing ${backupquotadiff} backup(s)"
  243. # Clear backups over quota
  244. find "${backupdir}"/ -type f -mtime +"${maxbackupdays}" -exec rm -f {} \;
  245. fn_print_ok_nl "Pruning: Clearing ${backupquotadiff} backup(s)"
  246. fn_script_log_pass "Pruning: Cleared ${backupquotadiff} backup(s)"
  247. fi
  248. fi
  249. fi
  250. }
  251. fn_backup_relpath() {
  252. # Written by CedarLUG as a "realpath --relative-to" alternative in bash.
  253. # Populate an array of tokens initialized from the rootdir components.
  254. mapfile -t rdirtoks < <(readlink -f "${rootdir}" | sed "s/\//\n/g")
  255. if [ ${#rdirtoks[@]} -eq 0 ]; then
  256. fn_print_fail_nl "Problem assessing rootdir during relative path assessment"
  257. fn_script_log_fail "Problem assessing rootdir during relative path assessment: ${rootdir}"
  258. core_exit.sh
  259. fi
  260. # Populate an array of tokens initialized from the backupdir components.
  261. mapfile -t bdirtoks < <(readlink -f "${backupdir}" | sed "s/\//\n/g")
  262. if [ ${#bdirtoks[@]} -eq 0 ]; then
  263. fn_print_fail_nl "Problem assessing backupdir during relative path assessment"
  264. fn_script_log_fail "Problem assessing backupdir during relative path assessment: ${rootdir}"
  265. core_exit.sh
  266. fi
  267. # Compare the leading entries of each array. These common elements will be clipped off.
  268. # for the relative path output.
  269. for ((base = 0; base < ${#rdirtoks[@]}; base++)); do
  270. [[ "${rdirtoks[$base]}" != "${bdirtoks[$base]}" ]] && break
  271. done
  272. # Next, climb out of the remaining rootdir location with updir references.
  273. for ((x = base; x < ${#rdirtoks[@]}; x++)); do
  274. echo -n "../"
  275. done
  276. # Climb down the remaining components of the backupdir location.
  277. for ((x = base; x < $((${#bdirtoks[@]} - 1)); x++)); do
  278. echo -n "${bdirtoks[$x]}/"
  279. done
  280. # In the event there were no directories left in the backupdir above to
  281. # traverse down, just add a newline. Otherwise at this point, there is
  282. # one remaining directory component in the backupdir to navigate.
  283. if (("$base" < "${#bdirtoks[@]}")); then
  284. echo -e "${bdirtoks[$((${#bdirtoks[@]} - 1))]}"
  285. else
  286. echo
  287. fi
  288. }
  289. # Start the server if it was stopped for the backup.
  290. fn_backup_start_server() {
  291. if [ -n "${startserver}" ]; then
  292. exitbypass=1
  293. command_start.sh
  294. fn_firstcommand_reset
  295. fi
  296. }
  297. fn_print_dots ""
  298. check.sh
  299. core_logs.sh
  300. fn_select_compression
  301. fn_backup_check_lockfile
  302. fn_backup_create_lockfile
  303. fn_backup_init
  304. fn_backup_stop_server
  305. fn_backup_dir
  306. fn_backup_compression
  307. fn_backup_prune
  308. fn_backup_start_server
  309. # Remove backup lockfile.
  310. rm -f "${lockdir:?}/backup.lock"
  311. core_exit.sh