Add seafile backup scripts
This commit is contained in:
1 parent
a0218b91c8
commit
a6ad39e605
2 files changed
+971
No files matched your search
@@ -0,0 +1,407 @@
|
||||
#!/bin/sh
|
||||
# =============================================================================
|
||||
# backup_create.sh - create a complete backup of a Seafile server that was
|
||||
# installed with install-seafile.sh (Docker Compose, Seafile 13.x CE).
|
||||
# =============================================================================
|
||||
# What gets backed up (one directory per backup, written atomically):
|
||||
#
|
||||
# database.sql.gz logical dump of every Seafile database (ccnet_db,
|
||||
# seafile_db, seahub_db, ...) taken from the MariaDB container
|
||||
# seafile-data.tar libraries, config (seafile/conf), avatars, logs, ...
|
||||
# seadoc-data.tar SeaDoc data (only if present)
|
||||
# seafile-caddy.tar Caddy / Let's Encrypt certificates (only if present)
|
||||
# config.tar the install directory: compose files and .env (SECRETS!)
|
||||
# service.systemd / service.openrc the boot service file (if any)
|
||||
# backup.info metadata used by backup_restore.sh
|
||||
# SHA256SUMS checksums of all of the above
|
||||
#
|
||||
# Consistency: by default the backup is "hot" (the server keeps running). The
|
||||
# database is dumped first and the files afterwards, which is the order
|
||||
# recommended by the Seafile manual. Use --stop for a fully consistent
|
||||
# "cold" backup (Seafile is unavailable for the duration of the file copy).
|
||||
#
|
||||
# Usage: sudo sh backup_create.sh [options]
|
||||
# sudo sh backup_create.sh --help
|
||||
#
|
||||
# Suitable for cron, e.g.: 30 3 * * * /root/backup_create.sh --keep 14
|
||||
#
|
||||
# POSIX sh only (works with dash, ash/busybox, bash) - no bashisms.
|
||||
# =============================================================================
|
||||
|
||||
set -eu
|
||||
umask 077
|
||||
|
||||
PROG=${0##*/}
|
||||
|
||||
# ------------------------------- defaults ------------------------------------
|
||||
INSTALL_DIR=${SEAFILE_DIR:-/opt/seafile}
|
||||
BACKUP_ROOT=${SEAFILE_BACKUP_DIR:-/var/backups/seafile}
|
||||
KEEP=${SEAFILE_BACKUP_KEEP:-7}
|
||||
STOP_STACK=0
|
||||
CHECKSUM=1
|
||||
SKIP_CHECKS=0
|
||||
SERVICE=seafile
|
||||
UNIT_DIR=${SEAFILE_UNIT_DIR:-/etc/systemd/system}
|
||||
INITD_DIR=${SEAFILE_INITD_DIR:-/etc/init.d}
|
||||
|
||||
ENV_FILE=
|
||||
DATA_DIR=
|
||||
DB_CONTAINER=
|
||||
DB_CLIENT=
|
||||
DB_DUMP=
|
||||
DBS=
|
||||
PARTIAL=
|
||||
LOCK=
|
||||
STOPPED=0
|
||||
IS_GNU_TAR=0
|
||||
TAR_OPTS=
|
||||
|
||||
# ------------------------------- helpers -------------------------------------
|
||||
if [ -t 1 ]; then
|
||||
C_RED=$(printf '\033[31m'); C_YEL=$(printf '\033[33m')
|
||||
C_GRN=$(printf '\033[32m'); C_OFF=$(printf '\033[0m')
|
||||
else
|
||||
C_RED=; C_YEL=; C_GRN=; C_OFF=
|
||||
fi
|
||||
log() { printf '%s==>%s %s\n' "$C_GRN" "$C_OFF" "$*"; }
|
||||
warn() { printf '%sWARNING:%s %s\n' "$C_YEL" "$C_OFF" "$*" >&2; }
|
||||
die() { printf '%sERROR:%s %s\n' "$C_RED" "$C_OFF" "$*" >&2; exit 1; }
|
||||
have() { command -v "$1" >/dev/null 2>&1; }
|
||||
try() { "$@" >/dev/null 2>&1; }
|
||||
|
||||
usage() {
|
||||
cat <<EOF
|
||||
Usage: sudo sh $PROG [options]
|
||||
|
||||
Creates a timestamped backup of Seafile (database + data + configuration).
|
||||
|
||||
Options:
|
||||
--backup-dir PATH Where backups are stored (default: $BACKUP_ROOT)
|
||||
--dir PATH Seafile install directory with the .env (default: $INSTALL_DIR)
|
||||
--keep N Keep only the newest N backups, delete older ones
|
||||
(default: $KEEP, 0 = keep everything)
|
||||
--stop Cold backup: stop the Seafile containers while copying
|
||||
files (consistent, but causes downtime)
|
||||
--no-checksum Skip the SHA256SUMS step (faster for very large installs)
|
||||
--skip-checks Skip the free-disk-space check
|
||||
-h, --help Show this help
|
||||
|
||||
Environment variables: SEAFILE_DIR, SEAFILE_BACKUP_DIR, SEAFILE_BACKUP_KEEP
|
||||
(same meaning as the options above).
|
||||
EOF
|
||||
}
|
||||
|
||||
set_opt() { # option value
|
||||
case $1 in
|
||||
--backup-dir) BACKUP_ROOT=$2 ;;
|
||||
--dir) INSTALL_DIR=$2 ;;
|
||||
--keep) KEEP=$2 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
parse_args() {
|
||||
while [ $# -gt 0 ]; do
|
||||
case $1 in
|
||||
--backup-dir|--dir|--keep)
|
||||
[ $# -ge 2 ] || die "Option $1 needs a value."
|
||||
set_opt "$1" "$2"
|
||||
shift ;;
|
||||
--backup-dir=*|--dir=*|--keep=*)
|
||||
set_opt "${1%%=*}" "${1#*=}" ;;
|
||||
--stop) STOP_STACK=1 ;;
|
||||
--no-checksum) CHECKSUM=0 ;;
|
||||
--skip-checks) SKIP_CHECKS=1 ;;
|
||||
-h|--help) usage; exit 0 ;;
|
||||
*) die "Unknown argument: $1 (see --help)" ;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
}
|
||||
|
||||
validate_inputs() {
|
||||
INSTALL_DIR=${INSTALL_DIR%/}
|
||||
BACKUP_ROOT=${BACKUP_ROOT%/}
|
||||
for _p in "$INSTALL_DIR" "$BACKUP_ROOT"; do
|
||||
case $_p in
|
||||
/*) ;;
|
||||
*) die "Paths must be absolute: '$_p'" ;;
|
||||
esac
|
||||
case $_p in
|
||||
*[!A-Za-z0-9_./-]*) die "Unsupported characters in path '$_p' (allowed: letters, digits, _ . / -)." ;;
|
||||
esac
|
||||
done
|
||||
case $KEEP in
|
||||
''|*[!0-9]*) die "--keep must be a non-negative number." ;;
|
||||
esac
|
||||
ENV_FILE=$INSTALL_DIR/.env
|
||||
}
|
||||
|
||||
require_root() {
|
||||
if [ "$(id -u)" -eq 0 ]; then return 0; fi
|
||||
if have sudo && [ -f "$0" ]; then
|
||||
log "Not running as root - re-executing with sudo."
|
||||
exec sudo -E sh "$0" "$@"
|
||||
fi
|
||||
die "Run this script as root (for example: sudo sh $PROG)."
|
||||
}
|
||||
|
||||
get_env() { # key -> value (quotes stripped) from $ENV_FILE
|
||||
sed -n "s/^$1=//p" "$ENV_FILE" | head -n 1 | tr -d "'\""
|
||||
}
|
||||
|
||||
compose_in_dir() { (cd "$INSTALL_DIR" && docker compose "$@"); }
|
||||
|
||||
detect_tar() {
|
||||
if tar --version 2>/dev/null | grep -qi 'gnu tar'; then IS_GNU_TAR=1; fi
|
||||
_d=$(mktemp -d)
|
||||
if try tar --numeric-owner -cf /dev/null -C "$_d" .; then TAR_OPTS=--numeric-owner; fi
|
||||
rmdir "$_d"
|
||||
}
|
||||
|
||||
tar_dir() { # base-dir name output-file
|
||||
_rc=0
|
||||
# shellcheck disable=SC2086
|
||||
tar $TAR_OPTS -C "$1" -cf "$3" "$2" || _rc=$?
|
||||
if [ "$_rc" -eq 1 ] && [ "$IS_GNU_TAR" -eq 1 ]; then
|
||||
warn "Some files changed while '$2' was being archived (normal for a running server)."
|
||||
elif [ "$_rc" -ne 0 ]; then
|
||||
die "Archiving '$2' failed (tar exit code $_rc)."
|
||||
fi
|
||||
}
|
||||
|
||||
# ------------------------- locking & cleanup ---------------------------------
|
||||
cleanup() {
|
||||
_rc=$?
|
||||
trap - EXIT INT TERM HUP
|
||||
if [ "$STOPPED" -eq 1 ]; then
|
||||
log "Restarting the Seafile containers..."
|
||||
compose_in_dir up -d --remove-orphans || warn "Could not restart Seafile - run: cd $INSTALL_DIR && docker compose up -d"
|
||||
fi
|
||||
if [ -n "$PARTIAL" ] && [ -d "$PARTIAL" ]; then rm -rf "$PARTIAL"; fi
|
||||
if [ -n "$LOCK" ]; then rmdir "$LOCK" 2>/dev/null || true; fi
|
||||
exit "$_rc"
|
||||
}
|
||||
|
||||
acquire_lock() {
|
||||
_base=/tmp
|
||||
if [ -d /run ] && [ -w /run ]; then _base=/run; fi
|
||||
_l=$_base/seafile-backup.lock
|
||||
if mkdir "$_l" 2>/dev/null; then
|
||||
LOCK=$_l
|
||||
else
|
||||
die "Another backup seems to be running (lock: $_l). If not, remove that directory and retry."
|
||||
fi
|
||||
}
|
||||
|
||||
# ------------------------------ database -------------------------------------
|
||||
db_service() {
|
||||
compose_in_dir config --services 2>/dev/null | grep -E '^(db|mysql|mariadb)$' | head -n 1 || true
|
||||
}
|
||||
|
||||
find_db_container() {
|
||||
_svc=$(db_service)
|
||||
_id=
|
||||
if [ -n "$_svc" ]; then
|
||||
_id=$(compose_in_dir ps -q "$_svc" 2>/dev/null | head -n 1 || true)
|
||||
fi
|
||||
if [ -z "$_id" ] && try docker inspect seafile-mysql; then _id=seafile-mysql; fi
|
||||
printf '%s' "$_id"
|
||||
}
|
||||
|
||||
find_in_container() { # container cmd1 cmd2 ... -> first command that exists
|
||||
_c=$1; shift
|
||||
for _x in "$@"; do
|
||||
if docker exec "$_c" sh -c "command -v $_x" >/dev/null 2>&1; then
|
||||
printf '%s' "$_x"
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
prepare_database() {
|
||||
DB_CONTAINER=$(find_db_container)
|
||||
[ -n "$DB_CONTAINER" ] || die "Could not find the MariaDB container. Is Seafile running? (cd $INSTALL_DIR && docker compose ps)"
|
||||
[ "$(docker inspect -f '{{.State.Running}}' "$DB_CONTAINER" 2>/dev/null || true)" = true ] \
|
||||
|| die "The database container is not running - start Seafile first (systemctl start $SERVICE)."
|
||||
|
||||
_pw=$(get_env INIT_SEAFILE_MYSQL_ROOT_PASSWORD)
|
||||
if [ -z "$_pw" ]; then
|
||||
_pw=$(docker exec "$DB_CONTAINER" printenv MYSQL_ROOT_PASSWORD 2>/dev/null || true)
|
||||
fi
|
||||
[ -n "$_pw" ] || die "Could not determine the MariaDB root password (INIT_SEAFILE_MYSQL_ROOT_PASSWORD in $ENV_FILE)."
|
||||
MYSQL_PWD=$_pw
|
||||
export MYSQL_PWD # handed to 'docker exec -e MYSQL_PWD' without appearing in 'ps'
|
||||
|
||||
DB_CLIENT=$(find_in_container "$DB_CONTAINER" mariadb mysql) || die "No mariadb/mysql client found in the database container."
|
||||
DB_DUMP=$(find_in_container "$DB_CONTAINER" mariadb-dump mysqldump) || die "No mariadb-dump/mysqldump found in the database container."
|
||||
|
||||
DBS=$(docker exec -e MYSQL_PWD "$DB_CONTAINER" "$DB_CLIENT" -uroot -N -B -e 'SHOW DATABASES' 2>/dev/null \
|
||||
| grep -Ev '^(information_schema|performance_schema|mysql|sys)$' | tr '\n' ' ' | sed 's/ *$//' || true)
|
||||
[ -n "$DBS" ] || die "No Seafile databases found (wrong root password or empty database server?)."
|
||||
case $DBS in
|
||||
*[!A-Za-z0-9_\ ]*) die "Unexpected characters in database names: $DBS" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
dump_database() {
|
||||
log "Dumping databases: $DBS"
|
||||
_sql=$PARTIAL/database.sql
|
||||
# shellcheck disable=SC2086
|
||||
docker exec -e MYSQL_PWD "$DB_CONTAINER" "$DB_DUMP" -uroot \
|
||||
--single-transaction --quick --routines --triggers --events --hex-blob \
|
||||
--databases $DBS > "$_sql" \
|
||||
|| die "The database dump failed."
|
||||
tail -c 300 "$_sql" | grep -q 'Dump completed' || die "The database dump looks incomplete."
|
||||
gzip -f "$_sql"
|
||||
}
|
||||
|
||||
# ------------------------------ preflight ------------------------------------
|
||||
check_space() {
|
||||
if [ "$SKIP_CHECKS" -eq 1 ]; then return 0; fi
|
||||
_need=0
|
||||
for _n in seafile-data seadoc-data seafile-caddy; do
|
||||
[ -d "$DATA_DIR/$_n" ] || continue
|
||||
_k=$(du -sk "$DATA_DIR/$_n" 2>/dev/null | awk '{ print $1 }')
|
||||
_need=$((_need + ${_k:-0}))
|
||||
done
|
||||
_avail=$(df -Pk "$BACKUP_ROOT" 2>/dev/null | awk 'NR == 2 { print $4 }')
|
||||
if [ -n "$_avail" ] && [ "$_avail" -lt "$_need" ]; then
|
||||
die "Not enough free space in $BACKUP_ROOT: need about $((_need / 1024)) MB, have $((_avail / 1024)) MB (use --skip-checks to ignore)."
|
||||
fi
|
||||
}
|
||||
|
||||
# ------------------------------ retention ------------------------------------
|
||||
prune_old() {
|
||||
[ "$KEEP" -gt 0 ] || return 0
|
||||
_n=0
|
||||
for _d in $(ls -1d "$BACKUP_ROOT"/seafile-backup-* 2>/dev/null | sort -r); do
|
||||
[ -f "$_d/backup.info" ] || continue
|
||||
_n=$((_n + 1))
|
||||
if [ "$_n" -gt "$KEEP" ]; then
|
||||
log "Removing old backup $_d"
|
||||
rm -rf "$_d"
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
# -------------------------------- main ---------------------------------------
|
||||
main() {
|
||||
for _a in "$@"; do
|
||||
case $_a in -h|--help) usage; exit 0 ;; esac
|
||||
done
|
||||
require_root "$@"
|
||||
parse_args "$@"
|
||||
validate_inputs
|
||||
|
||||
[ "$(uname -s)" = Linux ] || die "This script supports Linux only."
|
||||
[ -f "$ENV_FILE" ] || die "No Seafile installation found in $INSTALL_DIR (no .env). Use --dir to point to it."
|
||||
have docker || die "Docker is not installed."
|
||||
docker info >/dev/null 2>&1 || die "The Docker daemon is not running."
|
||||
docker compose version >/dev/null 2>&1 || die "The Docker Compose v2 plugin is missing."
|
||||
have gzip || die "gzip is required."
|
||||
have tar || die "tar is required."
|
||||
|
||||
DATA_DIR=$(get_env BASIC_STORAGE_PATH)
|
||||
DATA_DIR=${DATA_DIR:-/opt}
|
||||
DATA_DIR=${DATA_DIR%/}
|
||||
[ -d "$DATA_DIR/seafile-data" ] || die "Data directory $DATA_DIR/seafile-data not found (BASIC_STORAGE_PATH in $ENV_FILE)."
|
||||
|
||||
case $BACKUP_ROOT/ in
|
||||
"$DATA_DIR"/seafile-data/*|"$DATA_DIR"/seadoc-data/*|"$DATA_DIR"/seafile-caddy/*|"$DATA_DIR"/seafile-mysql/*|"$INSTALL_DIR"/*)
|
||||
die "The backup directory must not be inside the Seafile data or install directory." ;;
|
||||
esac
|
||||
|
||||
mkdir -p "$BACKUP_ROOT"
|
||||
chmod 700 "$BACKUP_ROOT"
|
||||
detect_tar
|
||||
check_space
|
||||
|
||||
trap cleanup EXIT
|
||||
trap 'exit 130' INT
|
||||
trap 'exit 143' TERM HUP
|
||||
acquire_lock
|
||||
|
||||
_stamp=$(date +%Y%m%d-%H%M%S)
|
||||
_final=$BACKUP_ROOT/seafile-backup-$_stamp
|
||||
PARTIAL=$BACKUP_ROOT/.partial-$_stamp
|
||||
[ ! -e "$_final" ] || die "$_final already exists."
|
||||
mkdir "$PARTIAL"
|
||||
|
||||
prepare_database
|
||||
_mode=hot
|
||||
|
||||
if [ "$STOP_STACK" -eq 1 ]; then
|
||||
_mode=cold
|
||||
_svcs=$(compose_in_dir config --services 2>/dev/null | grep -Ev '^(db|mysql|mariadb)$' | tr '\n' ' ' || true)
|
||||
[ -n "$_svcs" ] || die "Could not determine which services to stop."
|
||||
log "Stopping the Seafile containers (database stays up): $_svcs"
|
||||
STOPPED=1
|
||||
# shellcheck disable=SC2086
|
||||
compose_in_dir stop -t 120 $_svcs
|
||||
fi
|
||||
|
||||
# Database first, files afterwards (order recommended by the Seafile manual).
|
||||
dump_database
|
||||
|
||||
log "Archiving $DATA_DIR/seafile-data (this can take a long time for big libraries)..."
|
||||
tar_dir "$DATA_DIR" seafile-data "$PARTIAL/seafile-data.tar"
|
||||
for _n in seadoc-data seafile-caddy; do
|
||||
if [ -d "$DATA_DIR/$_n" ]; then
|
||||
log "Archiving $DATA_DIR/$_n..."
|
||||
tar_dir "$DATA_DIR" "$_n" "$PARTIAL/$_n.tar"
|
||||
fi
|
||||
done
|
||||
|
||||
log "Archiving configuration ($INSTALL_DIR)..."
|
||||
tar_dir "$INSTALL_DIR" . "$PARTIAL/config.tar"
|
||||
|
||||
if [ -f "$UNIT_DIR/$SERVICE.service" ]; then
|
||||
cp "$UNIT_DIR/$SERVICE.service" "$PARTIAL/service.systemd"
|
||||
elif [ -f "$INITD_DIR/$SERVICE" ]; then
|
||||
cp "$INITD_DIR/$SERVICE" "$PARTIAL/service.openrc"
|
||||
fi
|
||||
|
||||
if [ "$STOPPED" -eq 1 ]; then
|
||||
log "Restarting the Seafile containers..."
|
||||
compose_in_dir up -d --remove-orphans || warn "Restart failed - run: cd $INSTALL_DIR && docker compose up -d"
|
||||
STOPPED=0
|
||||
fi
|
||||
|
||||
_image=$(docker inspect -f '{{.Config.Image}}' seafile 2>/dev/null || true)
|
||||
{
|
||||
printf 'format=1\n'
|
||||
printf 'created=%s\n' "$(date '+%Y-%m-%dT%H:%M:%S%z')"
|
||||
printf 'host=%s\n' "$(hostname 2>/dev/null || echo unknown)"
|
||||
printf 'mode=%s\n' "$_mode"
|
||||
printf 'install_dir=%s\n' "$INSTALL_DIR"
|
||||
printf 'data_dir=%s\n' "$DATA_DIR"
|
||||
printf 'databases=%s\n' "$DBS"
|
||||
printf 'image=%s\n' "$_image"
|
||||
printf 'server_hostname=%s\n' "$(get_env SEAFILE_SERVER_HOSTNAME)"
|
||||
} > "$PARTIAL/backup.info"
|
||||
|
||||
if [ "$CHECKSUM" -eq 1 ]; then
|
||||
if have sha256sum; then
|
||||
log "Computing checksums..."
|
||||
_files=$(cd "$PARTIAL" && ls)
|
||||
# shellcheck disable=SC2086
|
||||
(cd "$PARTIAL" && sha256sum $_files > SHA256SUMS)
|
||||
else
|
||||
warn "sha256sum not found - the backup will not carry checksums."
|
||||
fi
|
||||
fi
|
||||
|
||||
mv "$PARTIAL" "$_final"
|
||||
PARTIAL=
|
||||
prune_old
|
||||
|
||||
_size=$(du -sh "$_final" 2>/dev/null | awk '{ print $1 }')
|
||||
printf '\n'
|
||||
log "Backup finished: $_final (${_size:-?}, $_mode backup)"
|
||||
printf ' Restore with: sudo sh backup_restore.sh %s\n' "$_final"
|
||||
printf ' The backup contains your .env with all secrets - store it somewhere safe,\n'
|
||||
printf ' and copy it off this machine (rsync, rclone, scp, ...).\n'
|
||||
}
|
||||
|
||||
main "$@"
|
||||
@@ -0,0 +1,564 @@
|
||||
#!/bin/sh
|
||||
# =============================================================================
|
||||
# backup_restore.sh - restore a backup made by backup_create.sh.
|
||||
# =============================================================================
|
||||
# What it does, fully automatically:
|
||||
#
|
||||
# 1. picks the newest backup (or the one you name) and verifies its checksums,
|
||||
# 2. stops the running Seafile stack,
|
||||
# 3. MOVES the current install directory and data directories aside
|
||||
# (<name>.pre-restore-<timestamp>) - nothing is deleted,
|
||||
# 4. restores the compose files + .env, the Seafile data (and SeaDoc / Caddy
|
||||
# data if they were backed up),
|
||||
# 5. starts a fresh MariaDB, recreates the Seafile DB user and imports the
|
||||
# database dump,
|
||||
# 6. re-installs the boot service if it is missing, starts Seafile and waits
|
||||
# until the web UI is up.
|
||||
#
|
||||
# It also works on a NEW machine: first get Docker + the service in place with
|
||||
# sudo sh install-seafile.sh install --no-start
|
||||
# then run this script - the freshly generated config is moved aside and
|
||||
# replaced by the backed-up one (including all passwords/secrets).
|
||||
#
|
||||
# Usage: sudo sh backup_restore.sh [BACKUP_DIR] [options]
|
||||
# sudo sh backup_restore.sh --help
|
||||
#
|
||||
# POSIX sh only (works with dash, ash/busybox, bash) - no bashisms.
|
||||
# =============================================================================
|
||||
|
||||
set -eu
|
||||
umask 022
|
||||
|
||||
PROG=${0##*/}
|
||||
|
||||
# ------------------------------- defaults ------------------------------------
|
||||
SRC=
|
||||
BACKUP_ROOT=${SEAFILE_BACKUP_DIR:-/var/backups/seafile}
|
||||
INSTALL_DIR=${SEAFILE_DIR:-}
|
||||
DATA_DIR=${SEAFILE_DATA_DIR:-}
|
||||
WAIT_TIMEOUT=${SEAFILE_WAIT_TIMEOUT:-900}
|
||||
START_NOW=1
|
||||
VERIFY=1
|
||||
SKIP_CHECKS=0
|
||||
ASSUME_YES=0
|
||||
SERVICE=seafile
|
||||
UNIT_DIR=${SEAFILE_UNIT_DIR:-/etc/systemd/system}
|
||||
INITD_DIR=${SEAFILE_INITD_DIR:-/etc/init.d}
|
||||
|
||||
INIT=none
|
||||
ENV_FILE=
|
||||
DB_CONTAINER=
|
||||
DB_CLIENT=
|
||||
DBS=
|
||||
ASIDE=
|
||||
TS=
|
||||
IS_GNU_TAR=0
|
||||
TAR_OPTS=
|
||||
|
||||
# ------------------------------- helpers -------------------------------------
|
||||
if [ -t 1 ]; then
|
||||
C_RED=$(printf '\033[31m'); C_YEL=$(printf '\033[33m')
|
||||
C_GRN=$(printf '\033[32m'); C_OFF=$(printf '\033[0m')
|
||||
else
|
||||
C_RED=; C_YEL=; C_GRN=; C_OFF=
|
||||
fi
|
||||
log() { printf '%s==>%s %s\n' "$C_GRN" "$C_OFF" "$*"; }
|
||||
warn() { printf '%sWARNING:%s %s\n' "$C_YEL" "$C_OFF" "$*" >&2; }
|
||||
die() { printf '%sERROR:%s %s\n' "$C_RED" "$C_OFF" "$*" >&2; exit 1; }
|
||||
have() { command -v "$1" >/dev/null 2>&1; }
|
||||
try() { "$@" >/dev/null 2>&1; }
|
||||
|
||||
usage() {
|
||||
cat <<EOF
|
||||
Usage: sudo sh $PROG [BACKUP_DIR] [options]
|
||||
|
||||
Restores Seafile from a backup created by backup_create.sh.
|
||||
Without BACKUP_DIR the newest backup in $BACKUP_ROOT is used.
|
||||
|
||||
Options:
|
||||
--backup-dir PATH Where to look for the newest backup (default: $BACKUP_ROOT)
|
||||
--dir PATH Install directory to restore into (default: as in the backup)
|
||||
--data-dir PATH Data directory to restore into (default: as in the backup)
|
||||
--no-start Restore everything but do not start Seafile
|
||||
--no-verify Do not verify the SHA256SUMS of the backup
|
||||
--skip-checks Skip the free-disk-space check
|
||||
-y, --yes Do not ask for confirmation (required when not run from a terminal)
|
||||
-h, --help Show this help
|
||||
|
||||
Existing Seafile directories are moved to <name>.pre-restore-<timestamp>,
|
||||
never deleted.
|
||||
EOF
|
||||
}
|
||||
|
||||
set_opt() { # option value
|
||||
case $1 in
|
||||
--backup-dir) BACKUP_ROOT=$2 ;;
|
||||
--dir) INSTALL_DIR=$2 ;;
|
||||
--data-dir) DATA_DIR=$2 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
parse_args() {
|
||||
while [ $# -gt 0 ]; do
|
||||
case $1 in
|
||||
--backup-dir|--dir|--data-dir)
|
||||
[ $# -ge 2 ] || die "Option $1 needs a value."
|
||||
set_opt "$1" "$2"
|
||||
shift ;;
|
||||
--backup-dir=*|--dir=*|--data-dir=*)
|
||||
set_opt "${1%%=*}" "${1#*=}" ;;
|
||||
--no-start) START_NOW=0 ;;
|
||||
--no-verify) VERIFY=0 ;;
|
||||
--skip-checks) SKIP_CHECKS=1 ;;
|
||||
-y|--yes) ASSUME_YES=1 ;;
|
||||
-h|--help) usage; exit 0 ;;
|
||||
-*) die "Unknown argument: $1 (see --help)" ;;
|
||||
*)
|
||||
[ -z "$SRC" ] || die "Only one backup directory can be given."
|
||||
SRC=$1 ;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
}
|
||||
|
||||
check_path() { # path
|
||||
case $1 in
|
||||
/*) ;;
|
||||
*) die "Paths must be absolute: '$1'" ;;
|
||||
esac
|
||||
case $1 in
|
||||
*[!A-Za-z0-9_./-]*) die "Unsupported characters in path '$1' (allowed: letters, digits, _ . / -)." ;;
|
||||
esac
|
||||
}
|
||||
|
||||
require_root() {
|
||||
if [ "$(id -u)" -eq 0 ]; then return 0; fi
|
||||
if have sudo && [ -f "$0" ]; then
|
||||
log "Not running as root - re-executing with sudo."
|
||||
exec sudo -E sh "$0" "$@"
|
||||
fi
|
||||
die "Run this script as root (for example: sudo sh $PROG)."
|
||||
}
|
||||
|
||||
# --- .env editing (same conventions as install-seafile.sh) ---
|
||||
env_quote() {
|
||||
case $1 in
|
||||
*[!A-Za-z0-9_.,:@/+=-]*) printf "'%s'" "$1" ;;
|
||||
*) printf '%s' "$1" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
set_env() { # key value : replace KEY=... in $ENV_FILE, or append it
|
||||
_v=$(env_quote "$2")
|
||||
_tmp=$(mktemp "$ENV_FILE.XXXXXX")
|
||||
SE_KEY=$1 SE_VAL=$_v awk '
|
||||
BEGIN { k = ENVIRON["SE_KEY"]; v = ENVIRON["SE_VAL"]; done = 0 }
|
||||
{
|
||||
i = index($0, "=")
|
||||
if (!done && i > 1 && substr($0, 1, i - 1) == k) { print k "=" v; done = 1 }
|
||||
else print
|
||||
}
|
||||
END { if (!done) print k "=" v }
|
||||
' "$ENV_FILE" > "$_tmp"
|
||||
cat "$_tmp" > "$ENV_FILE"
|
||||
rm -f "$_tmp"
|
||||
}
|
||||
|
||||
get_env() { # key -> value (quotes stripped) from $ENV_FILE
|
||||
sed -n "s/^$1=//p" "$ENV_FILE" | head -n 1 | tr -d "'\""
|
||||
}
|
||||
|
||||
info_get() { # key -> value from the backup's backup.info
|
||||
sed -n "s/^$1=//p" "$SRC/backup.info" | head -n 1
|
||||
}
|
||||
|
||||
compose_in_dir() { (cd "$INSTALL_DIR" && docker compose "$@"); }
|
||||
|
||||
detect_init() {
|
||||
case ${SEAFILE_INIT_SYSTEM:-} in
|
||||
systemd|openrc|none) INIT=$SEAFILE_INIT_SYSTEM; return 0 ;;
|
||||
esac
|
||||
if [ -d /run/systemd/system ] && have systemctl; then
|
||||
INIT=systemd
|
||||
elif have rc-service && have rc-update; then
|
||||
INIT=openrc
|
||||
else
|
||||
INIT=none
|
||||
fi
|
||||
}
|
||||
|
||||
detect_tar() {
|
||||
if tar --version 2>/dev/null | grep -qi 'gnu tar'; then IS_GNU_TAR=1; fi
|
||||
_d=$(mktemp -d)
|
||||
if try tar --numeric-owner -cf /dev/null -C "$_d" .; then TAR_OPTS=--numeric-owner; fi
|
||||
rmdir "$_d"
|
||||
}
|
||||
|
||||
untar() { # archive destination-dir
|
||||
# shellcheck disable=SC2086
|
||||
tar $TAR_OPTS -C "$2" -xpf "$1" || die "Extracting $1 failed."
|
||||
}
|
||||
|
||||
on_exit() {
|
||||
_rc=$?
|
||||
trap - EXIT
|
||||
if [ "$_rc" -ne 0 ] && [ -n "$ASIDE" ]; then
|
||||
warn "The restore did not complete. Your previous files were moved to:"
|
||||
for _a in $ASIDE; do warn " $_a"; done
|
||||
warn "Move them back (or re-run this script) to recover."
|
||||
fi
|
||||
exit "$_rc"
|
||||
}
|
||||
|
||||
# ------------------------------- locating the backup -------------------------
|
||||
find_backup() {
|
||||
if [ -z "$SRC" ]; then
|
||||
check_path "$BACKUP_ROOT"
|
||||
SRC=$(ls -1d "${BACKUP_ROOT%/}"/seafile-backup-* 2>/dev/null | sort | tail -n 1 || true)
|
||||
[ -n "$SRC" ] || die "No backups found in $BACKUP_ROOT (use --backup-dir or pass a backup directory)."
|
||||
log "Using the newest backup: $SRC"
|
||||
fi
|
||||
[ -d "$SRC" ] || die "Backup directory not found: $SRC"
|
||||
SRC=$(cd "$SRC" && pwd)
|
||||
check_path "$SRC"
|
||||
[ -f "$SRC/backup.info" ] || die "$SRC is not a Seafile backup (backup.info missing)."
|
||||
[ -f "$SRC/database.sql.gz" ] || die "database.sql.gz is missing in $SRC."
|
||||
[ -f "$SRC/seafile-data.tar" ] || die "seafile-data.tar is missing in $SRC."
|
||||
[ -f "$SRC/config.tar" ] || die "config.tar is missing in $SRC."
|
||||
}
|
||||
|
||||
verify_backup() {
|
||||
[ "$VERIFY" -eq 1 ] || return 0
|
||||
if [ ! -f "$SRC/SHA256SUMS" ]; then
|
||||
warn "This backup has no SHA256SUMS - skipping verification."
|
||||
return 0
|
||||
fi
|
||||
if ! have sha256sum; then
|
||||
warn "sha256sum not found - skipping verification."
|
||||
return 0
|
||||
fi
|
||||
log "Verifying backup checksums..."
|
||||
(cd "$SRC" && sha256sum -c SHA256SUMS >/dev/null 2>&1) \
|
||||
|| die "Checksum verification FAILED - the backup is damaged or incomplete (use --no-verify to override)."
|
||||
gzip -t "$SRC/database.sql.gz" || die "database.sql.gz is corrupt."
|
||||
}
|
||||
|
||||
# ------------------------------- docker --------------------------------------
|
||||
check_docker() {
|
||||
have docker || die "Docker is not installed. On a new machine run first: sudo sh install-seafile.sh install --no-start"
|
||||
docker info >/dev/null 2>&1 || die "The Docker daemon is not running."
|
||||
docker compose version >/dev/null 2>&1 || die "The Docker Compose v2 plugin is missing (sudo sh install-seafile.sh install --no-start installs it)."
|
||||
}
|
||||
|
||||
check_space() {
|
||||
if [ "$SKIP_CHECKS" -eq 1 ]; then return 0; fi
|
||||
_need=0
|
||||
for _f in "$SRC"/*.tar; do
|
||||
[ -f "$_f" ] || continue
|
||||
_k=$(du -sk "$_f" 2>/dev/null | awk '{ print $1 }')
|
||||
_need=$((_need + ${_k:-0}))
|
||||
done
|
||||
mkdir -p "$DATA_DIR"
|
||||
_avail=$(df -Pk "$DATA_DIR" 2>/dev/null | awk 'NR == 2 { print $4 }')
|
||||
if [ -n "$_avail" ] && [ "$_avail" -lt "$_need" ]; then
|
||||
die "Not enough free space in $DATA_DIR: need about $((_need / 1024)) MB, have $((_avail / 1024)) MB (use --skip-checks to ignore)."
|
||||
fi
|
||||
}
|
||||
|
||||
confirm() {
|
||||
printf '%s [y/N]: ' "$1" >&2
|
||||
_a=
|
||||
read -r _a || _a=
|
||||
case $_a in y|Y|yes|YES|Yes) return 0 ;; esac
|
||||
return 1
|
||||
}
|
||||
|
||||
aside() { # path : rename an existing path out of the way
|
||||
if [ -e "$1" ] || [ -L "$1" ]; then
|
||||
mv "$1" "$1.pre-restore-$TS"
|
||||
ASIDE="$ASIDE $1.pre-restore-$TS"
|
||||
log "Moved aside: $1 -> $1.pre-restore-$TS"
|
||||
fi
|
||||
}
|
||||
|
||||
# ------------------------------- database ------------------------------------
|
||||
db_service() {
|
||||
_s=$(compose_in_dir config --services 2>/dev/null | grep -E '^(db|mysql|mariadb)$' | head -n 1 || true)
|
||||
printf '%s' "${_s:-db}"
|
||||
}
|
||||
|
||||
find_db_container() {
|
||||
_svc=$(db_service)
|
||||
_id=$(compose_in_dir ps -q "$_svc" 2>/dev/null | head -n 1 || true)
|
||||
if [ -z "$_id" ] && try docker inspect seafile-mysql; then _id=seafile-mysql; fi
|
||||
printf '%s' "$_id"
|
||||
}
|
||||
|
||||
find_in_container() { # container cmd1 cmd2 ... -> first command that exists
|
||||
_c=$1; shift
|
||||
for _x in "$@"; do
|
||||
if docker exec "$_c" sh -c "command -v $_x" >/dev/null 2>&1; then
|
||||
printf '%s' "$_x"
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
wait_db() {
|
||||
_t=0
|
||||
while [ "$_t" -lt 300 ]; do
|
||||
DB_CONTAINER=$(find_db_container)
|
||||
if [ -n "$DB_CONTAINER" ] \
|
||||
&& [ "$(docker inspect -f '{{.State.Running}}' "$DB_CONTAINER" 2>/dev/null || true)" = true ]; then
|
||||
if [ -z "$DB_CLIENT" ]; then DB_CLIENT=$(find_in_container "$DB_CONTAINER" mariadb mysql || true); fi
|
||||
# -h 127.0.0.1: MariaDB's temporary init server has no TCP, so this only
|
||||
# succeeds once the real server is up.
|
||||
if [ -n "$DB_CLIENT" ] \
|
||||
&& docker exec -e MYSQL_PWD "$DB_CONTAINER" "$DB_CLIENT" -h 127.0.0.1 -uroot -e 'SELECT 1' >/dev/null 2>&1; then
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
sleep 3
|
||||
_t=$((_t + 3))
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
restore_database() {
|
||||
_pw=$(get_env INIT_SEAFILE_MYSQL_ROOT_PASSWORD)
|
||||
[ -n "$_pw" ] || die "INIT_SEAFILE_MYSQL_ROOT_PASSWORD is missing in $ENV_FILE."
|
||||
MYSQL_PWD=$_pw
|
||||
export MYSQL_PWD
|
||||
|
||||
_dbuser=$(get_env SEAFILE_MYSQL_DB_USER)
|
||||
_dbuser=${_dbuser:-seafile}
|
||||
_dbpw=$(get_env SEAFILE_MYSQL_DB_PASSWORD)
|
||||
[ -n "$_dbpw" ] || die "SEAFILE_MYSQL_DB_PASSWORD is missing in $ENV_FILE."
|
||||
case $_dbuser$_dbpw in
|
||||
*"'"*|*'\'*) die "The database user/password contain a quote or backslash - restore the database manually." ;;
|
||||
esac
|
||||
|
||||
log "Starting a fresh database container..."
|
||||
compose_in_dir up -d "$(db_service)" || die "Could not start the database container."
|
||||
wait_db || die "The database did not become ready in time (docker logs seafile-mysql)."
|
||||
|
||||
log "Creating the database user '$_dbuser'..."
|
||||
if ! {
|
||||
printf "CREATE USER IF NOT EXISTS '%s'@'%%' IDENTIFIED BY '%s';\n" "$_dbuser" "$_dbpw"
|
||||
printf "ALTER USER '%s'@'%%' IDENTIFIED BY '%s';\n" "$_dbuser" "$_dbpw"
|
||||
for _d in $DBS; do
|
||||
printf 'GRANT ALL PRIVILEGES ON `%s`.* TO '"'%s'@'%%'"';\n' "$_d" "$_dbuser"
|
||||
done
|
||||
printf 'FLUSH PRIVILEGES;\n'
|
||||
} | docker exec -i -e MYSQL_PWD "$DB_CONTAINER" "$DB_CLIENT" -uroot; then
|
||||
die "Creating the database user failed."
|
||||
fi
|
||||
|
||||
log "Importing the database dump..."
|
||||
if ! gzip -dc "$SRC/database.sql.gz" | docker exec -i -e MYSQL_PWD "$DB_CONTAINER" "$DB_CLIENT" -uroot; then
|
||||
die "Importing the database dump failed."
|
||||
fi
|
||||
}
|
||||
|
||||
# ------------------------------- service -------------------------------------
|
||||
restore_service() {
|
||||
case $INIT in
|
||||
systemd)
|
||||
if [ ! -f "$UNIT_DIR/$SERVICE.service" ] && [ -f "$SRC/service.systemd" ]; then
|
||||
mkdir -p "$UNIT_DIR"
|
||||
cp "$SRC/service.systemd" "$UNIT_DIR/$SERVICE.service"
|
||||
sed -i "s|^WorkingDirectory=.*|WorkingDirectory=$INSTALL_DIR|" "$UNIT_DIR/$SERVICE.service"
|
||||
systemctl daemon-reload
|
||||
log "Re-installed the systemd unit $UNIT_DIR/$SERVICE.service."
|
||||
fi
|
||||
if [ -f "$UNIT_DIR/$SERVICE.service" ]; then
|
||||
try systemctl enable "$SERVICE" || warn "Could not enable the $SERVICE service at boot."
|
||||
fi ;;
|
||||
openrc)
|
||||
if [ ! -f "$INITD_DIR/$SERVICE" ] && [ -f "$SRC/service.openrc" ]; then
|
||||
mkdir -p "$INITD_DIR"
|
||||
cp "$SRC/service.openrc" "$INITD_DIR/$SERVICE"
|
||||
chmod 755 "$INITD_DIR/$SERVICE"
|
||||
log "Re-installed the OpenRC script $INITD_DIR/$SERVICE."
|
||||
fi
|
||||
if [ -f "$INITD_DIR/$SERVICE" ]; then
|
||||
try rc-update add "$SERVICE" default || warn "Could not add $SERVICE to the default runlevel."
|
||||
fi ;;
|
||||
esac
|
||||
}
|
||||
|
||||
start_stack() {
|
||||
log "Starting Seafile..."
|
||||
# 'restart' (not 'start'): the oneshot unit may still count as "active" from
|
||||
# before the restore, in which case 'start' would do nothing.
|
||||
if [ "$INIT" = systemd ] && [ -f "$UNIT_DIR/$SERVICE.service" ]; then
|
||||
systemctl restart "$SERVICE" || die "The $SERVICE service failed to start (journalctl -u $SERVICE)."
|
||||
elif [ "$INIT" = openrc ] && [ -f "$INITD_DIR/$SERVICE" ]; then
|
||||
rc-service "$SERVICE" restart || die "The $SERVICE service failed to start."
|
||||
else
|
||||
compose_in_dir up -d --remove-orphans || die "'docker compose up' failed."
|
||||
fi
|
||||
}
|
||||
|
||||
wait_ready() {
|
||||
log "Waiting for Seafile to come up (can take a few minutes)..."
|
||||
_t=0
|
||||
while [ "$_t" -lt "$WAIT_TIMEOUT" ]; do
|
||||
_h=$(docker inspect -f '{{.State.Health.Status}}' seafile 2>/dev/null || true)
|
||||
if [ "$_h" = healthy ]; then return 0; fi
|
||||
if docker logs --tail 300 seafile 2>&1 | grep -q 'Seahub is started'; then return 0; fi
|
||||
sleep 5
|
||||
_t=$((_t + 5))
|
||||
if [ $((_t % 30)) -eq 0 ]; then log " ...still starting (${_t}s)"; fi
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
# -------------------------------- main ---------------------------------------
|
||||
main() {
|
||||
for _a in "$@"; do
|
||||
case $_a in -h|--help) usage; exit 0 ;; esac
|
||||
done
|
||||
require_root "$@"
|
||||
parse_args "$@"
|
||||
[ "$(uname -s)" = Linux ] || die "This script supports Linux only."
|
||||
have tar || die "tar is required."
|
||||
have gzip || die "gzip is required."
|
||||
|
||||
find_backup
|
||||
verify_backup
|
||||
|
||||
# Directories: command line > backup.info
|
||||
_orig_install=$(info_get install_dir)
|
||||
_orig_data=$(info_get data_dir)
|
||||
INSTALL_DIR=${INSTALL_DIR:-$_orig_install}
|
||||
DATA_DIR=${DATA_DIR:-$_orig_data}
|
||||
[ -n "$INSTALL_DIR" ] && [ -n "$DATA_DIR" ] || die "Cannot determine target directories - use --dir and --data-dir."
|
||||
INSTALL_DIR=${INSTALL_DIR%/}
|
||||
DATA_DIR=${DATA_DIR%/}
|
||||
check_path "$INSTALL_DIR"
|
||||
check_path "$DATA_DIR"
|
||||
case $DATA_DIR in
|
||||
"$INSTALL_DIR"|"$INSTALL_DIR"/*) die "The data directory must not be inside the install directory." ;;
|
||||
esac
|
||||
case $INSTALL_DIR in
|
||||
"$DATA_DIR"/seafile-data|"$DATA_DIR"/seafile-data/*|"$DATA_DIR"/seafile-mysql|"$DATA_DIR"/seafile-mysql/*) \
|
||||
die "The install directory must not be inside the data directories." ;;
|
||||
esac
|
||||
ENV_FILE=$INSTALL_DIR/.env
|
||||
|
||||
DBS=$(info_get databases)
|
||||
[ -n "$DBS" ] || die "backup.info lists no databases."
|
||||
case $DBS in
|
||||
*[!A-Za-z0-9_\ ]*) die "Unexpected characters in the database list: $DBS" ;;
|
||||
esac
|
||||
|
||||
check_docker
|
||||
detect_init
|
||||
detect_tar
|
||||
check_space
|
||||
|
||||
_has_seadoc=0; _has_caddy=0
|
||||
[ -f "$SRC/seadoc-data.tar" ] && _has_seadoc=1
|
||||
[ -f "$SRC/seafile-caddy.tar" ] && _has_caddy=1
|
||||
|
||||
printf '\nRestore Seafile from:\n'
|
||||
printf ' backup %s\n' "$SRC"
|
||||
printf ' created %s (%s backup, host %s)\n' "$(info_get created)" "$(info_get mode)" "$(info_get host)"
|
||||
printf ' image %s\n' "$(info_get image)"
|
||||
printf ' config %s\n' "$INSTALL_DIR"
|
||||
printf ' data %s\n' "$DATA_DIR"
|
||||
printf ' databases %s\n\n' "$DBS"
|
||||
printf 'Seafile will be stopped and the current directories above will be moved\n'
|
||||
printf 'aside (not deleted) and replaced by the backup.\n\n'
|
||||
if [ "$ASSUME_YES" -eq 0 ]; then
|
||||
[ -t 0 ] || die "Not running from a terminal - re-run with --yes to confirm the restore."
|
||||
confirm "Continue with the restore?" || die "Aborted by user."
|
||||
fi
|
||||
|
||||
TS=$(date +%Y%m%d-%H%M%S)
|
||||
trap on_exit EXIT
|
||||
|
||||
# 1. stop whatever is running
|
||||
if [ -f "$INSTALL_DIR/.env" ]; then
|
||||
log "Stopping the current Seafile stack..."
|
||||
compose_in_dir down -t 120 || warn "'docker compose down' reported a problem."
|
||||
fi
|
||||
for _n in seafile seafile-mysql seafile-redis seafile-caddy seadoc; do
|
||||
if try docker inspect "$_n"; then
|
||||
log "Removing leftover container '$_n'..."
|
||||
docker rm -f "$_n" >/dev/null 2>&1 || warn "Could not remove container $_n."
|
||||
fi
|
||||
done
|
||||
|
||||
# 2. move current state aside
|
||||
aside "$INSTALL_DIR"
|
||||
aside "$DATA_DIR/seafile-data"
|
||||
aside "$DATA_DIR/seafile-mysql"
|
||||
if [ "$_has_seadoc" -eq 1 ]; then aside "$DATA_DIR/seadoc-data"; fi
|
||||
if [ "$_has_caddy" -eq 1 ]; then aside "$DATA_DIR/seafile-caddy"; fi
|
||||
|
||||
# 3. configuration
|
||||
log "Restoring configuration to $INSTALL_DIR..."
|
||||
mkdir -p "$INSTALL_DIR"
|
||||
untar "$SRC/config.tar" "$INSTALL_DIR"
|
||||
[ -f "$ENV_FILE" ] || die "The backup's config.tar contains no .env."
|
||||
chmod 600 "$ENV_FILE"
|
||||
if [ "$(get_env BASIC_STORAGE_PATH)" != "$DATA_DIR" ]; then
|
||||
set_env BASIC_STORAGE_PATH "$DATA_DIR"
|
||||
log "Updated BASIC_STORAGE_PATH in $ENV_FILE to $DATA_DIR."
|
||||
fi
|
||||
|
||||
# 4. data
|
||||
mkdir -p "$DATA_DIR"
|
||||
log "Restoring $DATA_DIR/seafile-data (this can take a long time for big libraries)..."
|
||||
untar "$SRC/seafile-data.tar" "$DATA_DIR"
|
||||
if [ "$_has_seadoc" -eq 1 ]; then
|
||||
log "Restoring $DATA_DIR/seadoc-data..."
|
||||
untar "$SRC/seadoc-data.tar" "$DATA_DIR"
|
||||
fi
|
||||
if [ "$_has_caddy" -eq 1 ]; then
|
||||
log "Restoring $DATA_DIR/seafile-caddy..."
|
||||
untar "$SRC/seafile-caddy.tar" "$DATA_DIR"
|
||||
fi
|
||||
mkdir -p "$DATA_DIR/seafile-mysql/db" "$DATA_DIR/seafile-caddy"
|
||||
if [ "$_has_seadoc" -eq 1 ] || [ -d "$DATA_DIR/seadoc-data" ]; then mkdir -p "$DATA_DIR/seadoc-data"; fi
|
||||
|
||||
compose_in_dir config -q || die "The restored compose configuration is invalid ($ENV_FILE)."
|
||||
|
||||
# 5. database
|
||||
restore_database
|
||||
|
||||
# 6. service + start
|
||||
restore_service
|
||||
if [ "$START_NOW" -eq 0 ]; then
|
||||
log "Restore finished; Seafile was not started (--no-start). The database container is running."
|
||||
log "Start everything with: systemctl restart $SERVICE (or: cd $INSTALL_DIR && docker compose up -d)"
|
||||
else
|
||||
start_stack
|
||||
if wait_ready; then
|
||||
log "Seafile is up and running."
|
||||
else
|
||||
warn "Seafile did not report 'ready' within ${WAIT_TIMEOUT}s - check: docker logs -f seafile"
|
||||
ASIDE=
|
||||
exit 2
|
||||
fi
|
||||
fi
|
||||
|
||||
_proto=$(get_env SEAFILE_SERVER_PROTOCOL)
|
||||
_host=$(get_env SEAFILE_SERVER_HOSTNAME)
|
||||
printf '\n URL %s://%s\n' "$_proto" "$_host"
|
||||
printf ' Admin %s (password: as it was when the backup was made)\n' "$(get_env INIT_SEAFILE_ADMIN_EMAIL)"
|
||||
printf ' Settings %s\n' "$ENV_FILE"
|
||||
if [ -n "$ASIDE" ]; then
|
||||
printf '\nThe previous state was kept here - delete it once you have checked the restore:\n'
|
||||
for _a in $ASIDE; do printf ' rm -rf %s\n' "$_a"; done
|
||||
fi
|
||||
printf '\nIf this machine has a different address than the one in the backup (%s),\n' "$_host"
|
||||
printf 'update SEAFILE_SERVER_HOSTNAME in %s, restart the service, and adjust\n' "$ENV_FILE"
|
||||
printf 'SERVICE_URL / FILE_SERVER_ROOT in System Admin > Settings (or seahub_settings.py).\n'
|
||||
ASIDE=
|
||||
}
|
||||
|
||||
main "$@"
|
||||
Reference in new issue
Block a user