From f8569724a669f06159d135aed2ec1bf8c8716b8d Mon Sep 17 00:00:00 2001 From: June Kelly Date: Fri, 27 Aug 2021 09:38:34 +0100 Subject: [PATCH 01/10] Update gitignore --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 4ab00655..1d35d633 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,6 @@ config/**/* data/**/* !data/.gitkeep + +## backup directories +backup/ From 778770386aa886d678a7004b79582a78cb65c3d2 Mon Sep 17 00:00:00 2001 From: June Kelly Date: Fri, 27 Aug 2021 09:38:52 +0100 Subject: [PATCH 02/10] Add rsync dependency to doctor --- bin/doctor | 1 + 1 file changed, 1 insertion(+) diff --git a/bin/doctor b/bin/doctor index 24246b34..2775a7ac 100755 --- a/bin/doctor +++ b/bin/doctor @@ -118,6 +118,7 @@ function check_dependencies() { perl awk openssl + rsync ) for binary in "${binaries[@]}"; do From 67fcc6968762696566c3ba331b1378a49a67ad65 Mon Sep 17 00:00:00 2001 From: June Kelly Date: Fri, 27 Aug 2021 09:39:11 +0100 Subject: [PATCH 03/10] Add bin/backup script --- bin/backup | 155 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 155 insertions(+) create mode 100755 bin/backup diff --git a/bin/backup b/bin/backup new file mode 100755 index 00000000..0b63fd1d --- /dev/null +++ b/bin/backup @@ -0,0 +1,155 @@ +#! /usr/bin/env bash + +set -euo pipefail + +#### Detect Toolkit Project Root #### +# if realpath is not available, create a semi-equivalent function +command -v realpath >/dev/null 2>&1 || realpath() { + [[ $1 = /* ]] && echo "$1" || echo "$PWD/${1#./}" +} +SCRIPT_PATH="$(realpath "${BASH_SOURCE[0]}")" +SCRIPT_DIR="$(dirname "$SCRIPT_PATH")" +TOOLKIT_ROOT="$(realpath "$SCRIPT_DIR/..")" +if [[ ! -d "$TOOLKIT_ROOT/bin" ]] || [[ ! -d "$TOOLKIT_ROOT/config" ]]; then + echo "ERROR: could not find root of overleaf-toolkit project (inferred project root as '$TOOLKIT_ROOT')" + exit 1 +fi + +TMP_ROOT_DIR="$TOOLKIT_ROOT/tmp" + +function usage() { + cat <&2 + exit 1 + fi + mkdir -p "$tmp_dir/backup" + echo "$tmp_dir" +} + +function get-container-name () { + local name="$1" + "$TOOLKIT_ROOT/bin/docker-compose" ps | grep "$name" | cut -d ' ' -f 1 | head -n 1 +} + +function dump-mongo () { + local tmp_dir="$1" + local mongo_tmp_dir="$tmp_dir/backup/mongo" + mkdir "$mongo_tmp_dir" + + "$TOOLKIT_ROOT/bin/docker-compose" up -d mongo + sleep 5 + + # shellcheck disable=SC1004 + "$TOOLKIT_ROOT/bin/docker-compose" exec mongo bash -lc '\ + [[ -d /tmp/dump ]] && rm -rf /tmp/dump; \ + cd /tmp && mongodump --quiet;' + + docker cp "$(get-container-name mongo)":/tmp/dump \ + "$mongo_tmp_dir/dump" + + if [[ ! -d "$mongo_tmp_dir/dump" ]]; then + echo "Error: did not get mongo backup" >&2 + exit 1 + fi + + # shellcheck disable=SC1004 + "$TOOLKIT_ROOT/bin/docker-compose" exec mongo bash -lc '\ + rm -rf /tmp/dump;' +} + +function copy-data-files () { + local tmp_dir="$1" + local sharelatex_tmp_dir="$tmp_dir/backup/data/sharelatex" + mkdir -p "$sharelatex_tmp_dir" + + rsync -a "$TOOLKIT_ROOT/data/sharelatex/" "$sharelatex_tmp_dir" +} + +function backup-tar () { + local tmp_dir="$1" + local backup_name + backup_name="$(basename "$tmp_dir")" + local tar_file="$TOOLKIT_ROOT/backup/${backup_name}.tar.gz" + echo "Writing backup to backup/$(basename "$tar_file")" + pushd "$tmp_dir" 1>/dev/null + tar zcvf "$tar_file" backup info.txt > /dev/null + popd 1>/dev/null +} + +function write-info-file () { + local tmp_dir="$1" + cat < "$tmp_dir/info.txt" +Backup info: +- time: $(date '+%F-%H%M%S') +- user: $(whoami) +EOF + +} + +function _main() { + ## Help, and such + if [[ "${1:-null}" == '--help' ]] || [[ "${1:-null}" == "help" ]]; then + usage + exit 0 + fi + + ## Get a temp directory + local tmp_dir + tmp_dir="$(create-tmp-dir)" + echo "Using temp directory: $tmp_dir" + + ## Dump mongo + echo "Dumping mongo..." + dump-mongo "$tmp_dir" + + ## Copy data files + echo "Copying data/ files..." + copy-data-files "$tmp_dir" + + ## Add info file + echo "Writing info file..." + write-info-file "$tmp_dir" + + ## Prepare backup directory + [[ ! -d "$TOOLKIT_ROOT/backup" ]] && mkdir "$TOOLKIT_ROOT/backup" + + ## Archive structure: + ## - backup/ + ## - mongo/ + ## - data/ + ## - ... + ## - info.txt + + ## Create backup archive + echo "Creating tar.gz archive..." + backup-tar "$tmp_dir" + + ## Clean up temp dir + echo "Removing temp files..." + rm -rf "$tmp_dir" + + echo "Done" + exit 0 +} + +_main "$@" From 51f3e318380cf6e2b387c8c0f58062069a233faf Mon Sep 17 00:00:00 2001 From: June Kelly Date: Fri, 27 Aug 2021 10:23:53 +0100 Subject: [PATCH 04/10] Restore script, and update backup --- bin/backup | 4 +- bin/restore | 194 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 197 insertions(+), 1 deletion(-) create mode 100755 bin/restore diff --git a/bin/backup b/bin/backup index 0b63fd1d..c868ab47 100755 --- a/bin/backup +++ b/bin/backup @@ -57,7 +57,9 @@ function dump-mongo () { mkdir "$mongo_tmp_dir" "$TOOLKIT_ROOT/bin/docker-compose" up -d mongo - sleep 5 + + # TODO: can we port over the wait-for-mongo script? + sleep 10 # shellcheck disable=SC1004 "$TOOLKIT_ROOT/bin/docker-compose" exec mongo bash -lc '\ diff --git a/bin/restore b/bin/restore new file mode 100755 index 00000000..db78f148 --- /dev/null +++ b/bin/restore @@ -0,0 +1,194 @@ +#! /usr/bin/env bash + +set -euo pipefail + +#### Detect Toolkit Project Root #### +# if realpath is not available, create a semi-equivalent function +command -v realpath >/dev/null 2>&1 || realpath() { + [[ $1 = /* ]] && echo "$1" || echo "$PWD/${1#./}" +} +SCRIPT_PATH="$(realpath "${BASH_SOURCE[0]}")" +SCRIPT_DIR="$(dirname "$SCRIPT_PATH")" +TOOLKIT_ROOT="$(realpath "$SCRIPT_DIR/..")" +if [[ ! -d "$TOOLKIT_ROOT/bin" ]] || [[ ! -d "$TOOLKIT_ROOT/config" ]]; then + echo "ERROR: could not find root of overleaf-toolkit project (inferred project root as '$TOOLKIT_ROOT')" + exit 1 +fi + +TMP_ROOT_DIR="$TOOLKIT_ROOT/tmp" + +function usage() { + cat <&2 + exit 1 + fi + mkdir "$tmp_dir" + echo "$tmp_dir" +} + +function get-container-name () { + local name="$1" + "$TOOLKIT_ROOT/bin/docker-compose" ps | grep "$name" | cut -d ' ' -f 1 | head -n 1 +} + +function restore-mongo () { + local tmp_dir="$1" + local mongo_tmp_dir="$tmp_dir/backup/mongo" + + "$TOOLKIT_ROOT/bin/docker-compose" up -d mongo + "$TOOLKIT_ROOT/bin/docker-compose" up -d sharelatex + + # TODO: can we port over the wait-for-mongo script? + sleep 10 + + ## Copy data into container + docker cp \ + "$mongo_tmp_dir/dump" \ + "$(get-container-name mongo)":/tmp/dump + + ## Run mongo restore + # shellcheck disable=SC1004 + "$TOOLKIT_ROOT/bin/docker-compose" exec mongo bash -lc '\ + cd /tmp && mongorestore --drop --quiet ./dump \ + && rm -rf /tmp/dump;' + + # TODO: select either server-ce or server-pro migrations + ## Run web migrations + # shellcheck disable=SC1004 + + "$TOOLKIT_ROOT/bin/docker-compose" exec sharelatex bash -lc '\ + cd /var/www/sharelatex/web && \ + [[ -d migrations ]] && npm run migrations -- migrate -t server-pro' + + ## Stop services again + "$TOOLKIT_ROOT/bin/docker-compose" stop sharelatex + "$TOOLKIT_ROOT/bin/docker-compose" stop mongo +} + +function restore-data-files () { + local tmp_dir="$1" + local sharelatex_tmp_dir="$tmp_dir/backup/data/sharelatex" + + # TODO: can we detect if we need this sudo? + sudo rsync -a --delete "$sharelatex_tmp_dir/" "$TOOLKIT_ROOT/data/sharelatex" +} + +function _main () { + ## Help, and such + if [[ "${1:-null}" == '--help' ]] || [[ "${1:-null}" == "help" ]]; then + usage + exit 0 + fi + + if [[ "${1:-null}" == 'null' ]]; then + echo "Error: no backup file supplied" >&2 + usage + exit 1 + fi + + local tar_file + local tmp_dir + + ## Handle 'latest' + if [[ "${1:-null}" == '--latest' ]] || [[ "${1:-null}" == "latest" ]]; then + # Figure out latest + tar_file="$(find "$TOOLKIT_ROOT/backup" -name '*.tar.gz' | sort -r | head -n 1)" + if [[ -z "$tar_file" ]]; then + echo "Error: Could not find 'latest' archive in backup directory" >&2 + exit 1 + else + echo "Using 'latest' archive: $tar_file" + fi + else + ## Or, just use the path supplied + tar_file="$1" + fi + + ## Verify backup file exists + if ! [[ -f "$tar_file" ]]; then + echo "Error: backup file does not exist - $tar_file" >&2 + exit 1 + fi + + ## Get a temp directory + tmp_dir="$(create-tmp-dir)" + + echo "Restoring ${tar_file}" + echo "Using temp directory $tmp_dir" + + ## Extract file + echo "Extracting data..." + tar xf "$tar_file" --directory "$tmp_dir" + + ## Verify backup structure + if ! [[ -d "$tmp_dir/backup" ]]; then + echo "Error: invalid backup format" >&2 + exit 1 + fi + + if [[ -f "$tmp_dir/info.txt" ]]; then + echo "Printing backup info..." + awk '{ print " " $0 }' "$tmp_dir/info.txt" + fi + + ## Shut down services + echo "Stopping docker-compose services..." + "$TOOLKIT_ROOT/bin/docker-compose" stop 2>/dev/null + + ## Restore mongo + echo "Restoring mongo..." + restore-mongo "$tmp_dir" + + ## Restore data files + echo "Restoring data/..." + restore-data-files "$tmp_dir" + + ## Stop docker services + echo "Stopping docker-compose services..." + "$TOOLKIT_ROOT/bin/docker-compose" stop 2>/dev/null + + ## Clear CLSI local database + echo "Removing CLSI cache database..." + if [[ -f "$TOOLKIT_ROOT/data/sharelatex/data/db.sqlite" ]]; then + rm "$TOOLKIT_ROOT/data/sharelatex/data/"db* + fi + + ## Clean up temp + echo "Removing temp files..." + rm -rf "$tmp_dir" + + ## Done + echo "Done" + exit 0 +} + +_main "$@" From 0173872cc2075e86ae92a70aa9037c70617f2eb2 Mon Sep 17 00:00:00 2001 From: June Kelly Date: Fri, 27 Aug 2021 10:49:51 +0100 Subject: [PATCH 05/10] Fix how we run web migrations after restore --- bin/restore | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/bin/restore b/bin/restore index db78f148..898ce569 100755 --- a/bin/restore +++ b/bin/restore @@ -84,10 +84,9 @@ function restore-mongo () { # TODO: select either server-ce or server-pro migrations ## Run web migrations # shellcheck disable=SC1004 - "$TOOLKIT_ROOT/bin/docker-compose" exec sharelatex bash -lc '\ cd /var/www/sharelatex/web && \ - [[ -d migrations ]] && npm run migrations -- migrate -t server-pro' + if [[ -d migrations ]]; then npm run migrations -- migrate -t server-pro; fi' ## Stop services again "$TOOLKIT_ROOT/bin/docker-compose" stop sharelatex From 1aaa465b74dd36ee2f516329579246694faaced0 Mon Sep 17 00:00:00 2001 From: June Kelly Date: Fri, 27 Aug 2021 11:20:57 +0100 Subject: [PATCH 06/10] Add server-pro marker to backup --- bin/backup | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/bin/backup b/bin/backup index c868ab47..cffe576b 100755 --- a/bin/backup +++ b/bin/backup @@ -17,6 +17,9 @@ fi TMP_ROOT_DIR="$TOOLKIT_ROOT/tmp" +IS_SERVER_PRO="$(grep -q 'SERVER_PRO=true' \ + "$TOOLKIT_ROOT/config/overleaf.rc" && echo 'true' || echo 'false')" + function usage() { cat < Date: Fri, 27 Aug 2021 11:36:21 +0100 Subject: [PATCH 07/10] Use server-pro flag in restore script --- bin/restore | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/bin/restore b/bin/restore index 898ce569..43c18e21 100755 --- a/bin/restore +++ b/bin/restore @@ -17,6 +17,9 @@ fi TMP_ROOT_DIR="$TOOLKIT_ROOT/tmp" +IS_SERVER_PRO="$(grep -q 'SERVER_PRO=true' \ + "$TOOLKIT_ROOT/config/overleaf.rc" && echo 'true' || echo 'false')" + function usage() { cat < Date: Fri, 27 Aug 2021 11:43:00 +0100 Subject: [PATCH 08/10] Wait for mongo, instead of a static sleep --- bin/backup | 11 ++++++++--- bin/restore | 11 ++++++++--- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/bin/backup b/bin/backup index cffe576b..012945d8 100755 --- a/bin/backup +++ b/bin/backup @@ -32,6 +32,13 @@ This file can then be consumed by the bin/restore script. EOF } +function wait-for-mongo () { + while ! "$TOOLKIT_ROOT/bin/docker-compose" exec -T mongo \ + mongo --eval "db.version()" \ + > /dev/null; do echo '[mongo is not ready]' && sleep 1; done + echo '[mongo is ready]' +} + function create-tmp-dir () { local now now="$(date '+%F-%H%M%S')" @@ -60,9 +67,7 @@ function dump-mongo () { mkdir "$mongo_tmp_dir" "$TOOLKIT_ROOT/bin/docker-compose" up -d mongo - - # TODO: can we port over the wait-for-mongo script? - sleep 10 + wait-for-mongo # shellcheck disable=SC1004 "$TOOLKIT_ROOT/bin/docker-compose" exec mongo bash -lc '\ diff --git a/bin/restore b/bin/restore index 43c18e21..de4cc1c5 100755 --- a/bin/restore +++ b/bin/restore @@ -41,6 +41,13 @@ Examples: EOF } +function wait-for-mongo () { + while ! "$TOOLKIT_ROOT/bin/docker-compose" exec -T mongo \ + mongo --eval "db.version()" \ + > /dev/null; do echo '[mongo is not ready]' && sleep 1; done + echo '[mongo is ready]' +} + function create-tmp-dir () { local now now="$(date '+%F-%H%M%S')" @@ -68,11 +75,9 @@ function restore-mongo () { local mongo_tmp_dir="$tmp_dir/backup/mongo" "$TOOLKIT_ROOT/bin/docker-compose" up -d mongo + wait-for-mongo "$TOOLKIT_ROOT/bin/docker-compose" up -d sharelatex - # TODO: can we port over the wait-for-mongo script? - sleep 10 - ## Copy data into container docker cp \ "$mongo_tmp_dir/dump" \ From 27842b922c996afc4085f0a771f4e8282148f4d5 Mon Sep 17 00:00:00 2001 From: June Kelly Date: Fri, 27 Aug 2021 13:40:50 +0100 Subject: [PATCH 09/10] WIP: detect need for sudo, and smooth over it --- bin/restore | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/bin/restore b/bin/restore index de4cc1c5..0e1e6ec5 100755 --- a/bin/restore +++ b/bin/restore @@ -28,6 +28,8 @@ Restores a backup created by bin/backup. Passing 'latest' restores the most recent backup in the backup directory. This process will stop the overleaf services. +Restoring the data files will likely require a sudo priveleges, +which will be prompted for when the script starts. Note, when restoring in a fresh environment, you must run "bin/init" and "bin/up" first. @@ -41,6 +43,10 @@ Examples: EOF } +function needs-sudo () { + [[ "$(stat -c '%U' "$TOOLKIT_ROOT/data/sharelatex/data")" != "$(whoami)" ]] +} + function wait-for-mongo () { while ! "$TOOLKIT_ROOT/bin/docker-compose" exec -T mongo \ mongo --eval "db.version()" \ @@ -109,8 +115,12 @@ function restore-data-files () { local tmp_dir="$1" local sharelatex_tmp_dir="$tmp_dir/backup/data/sharelatex" - # TODO: can we detect if we need this sudo? - sudo rsync -a --delete "$sharelatex_tmp_dir/" "$TOOLKIT_ROOT/data/sharelatex" + ## Try to detect whether we need to run rsync with sudo + local rsync_command="rsync" + if needs-sudo; then + rsync_command="sudo ${rsync_command}" + fi + $rsync_command -a --delete "$sharelatex_tmp_dir/" "$TOOLKIT_ROOT/data/sharelatex" } function _main () { @@ -156,6 +166,11 @@ function _main () { echo "Restoring ${tar_file}" echo "Using temp directory $tmp_dir" + if needs-sudo; then + echo "Require elevated privileges. Invoking sudo..." + sudo echo 'elevate privileges' + fi + ## Extract file echo "Extracting data..." tar xf "$tar_file" --directory "$tmp_dir" From aa6c7dbc8cef3980bb4f562efd9e1577c5832d57 Mon Sep 17 00:00:00 2001 From: June Kelly Date: Fri, 27 Aug 2021 14:24:15 +0100 Subject: [PATCH 10/10] Stop services before and after backup --- bin/backup | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/bin/backup b/bin/backup index 012945d8..aa378ca7 100755 --- a/bin/backup +++ b/bin/backup @@ -129,6 +129,10 @@ function _main() { tmp_dir="$(create-tmp-dir)" echo "Using temp directory: $tmp_dir" + ## Stop docker services + echo "Stopping docker-compose services..." + "$TOOLKIT_ROOT/bin/docker-compose" stop 2>/dev/null + ## Dump mongo echo "Dumping mongo..." dump-mongo "$tmp_dir" @@ -159,6 +163,10 @@ function _main() { echo "Removing temp files..." rm -rf "$tmp_dir" + ## Stop docker services + echo "Stopping docker-compose services..." + "$TOOLKIT_ROOT/bin/docker-compose" stop 2>/dev/null + echo "Done" exit 0 }