|
|
@ -386,7 +386,7 @@ EOF |
|
|
|
stopped_containers=1 |
|
|
|
fi |
|
|
|
|
|
|
|
if [[ -n "$path" ]]; then |
|
|
|
if [[ "$path" == "/"* ]]; then |
|
|
|
|
|
|
|
## |
|
|
|
## Additional intelligence to simple file copy |
|
|
@ -425,76 +425,130 @@ EOF |
|
|
|
fi |
|
|
|
fi |
|
|
|
else |
|
|
|
|
|
|
|
for vol in postfix rspamd redis crypt vmail{,-attachments}; do |
|
|
|
volume_name="mailcowdockerized_${vol}-vol-1" |
|
|
|
volume_dir="/var/lib/docker/volumes/${volume_name}/_data" |
|
|
|
if ! backup:path_exists "${volume_dir}/"; then |
|
|
|
warn "No '$volume_name' in backup. This might be expected." |
|
|
|
continue |
|
|
|
fi |
|
|
|
## Create volumes if not existent |
|
|
|
if ! ssh:run "root@$vps" -- " |
|
|
|
[ -d '${volume_dir}' ] || |
|
|
|
docker run --rm -v ${volume_name}:/tmp/dummy docker.0k.io/alpine:3.9 |
|
|
|
[ -d '${volume_dir}' ] |
|
|
|
"; then |
|
|
|
err "Couldn't find nor create '${volume_dir}'." |
|
|
|
ALL_TARGETS=(mailcow postfix rspamd redis crypt vmail{,-attachments} mysql) |
|
|
|
|
|
|
|
if [[ -n "$path" ]]; then |
|
|
|
targets=() |
|
|
|
bad_targets=() |
|
|
|
for target in ${path//,/ }; do |
|
|
|
if [[ " ${ALL_TARGETS[*]} " != *" $target "* ]]; then |
|
|
|
bad_targets+=("$target") |
|
|
|
fi |
|
|
|
targets+=("$target") |
|
|
|
done |
|
|
|
if [ "${#bad_targets[@]}" -gt 0 ]; then |
|
|
|
bad_target_msg=$(printf "%s, " "${bad_targets[@]}") |
|
|
|
err "Unknown components: ${bad_target_msg%, }. These are allowed components:" |
|
|
|
printf " - %s\n" "${ALL_TARGETS[@]}" >&2 |
|
|
|
return 1 |
|
|
|
fi |
|
|
|
|
|
|
|
echo "${WHITE}Downloading of $volume_name${NORMAL}" |
|
|
|
backup:rsync "${volume_dir}/" "${volume_dir}" || return 1 |
|
|
|
done |
|
|
|
|
|
|
|
## Mailcow git base |
|
|
|
COMPOSE_FILE= |
|
|
|
for mailcow_dir in /opt/{apps/,}mailcow-dockerized; do |
|
|
|
backup:path_exists "${mailcow_dir}/" || continue |
|
|
|
## this possibly change last value |
|
|
|
COMPOSE_FILE="$mailcow_dir/docker-compose.yml" |
|
|
|
ENV_FILE="$mailcow_dir/.env" |
|
|
|
echo "${WHITE}Download of $mailcow_dir${NORMAL}" |
|
|
|
backup:rsync "${mailcow_dir}"/ "${mailcow_dir}" || return 1 |
|
|
|
break |
|
|
|
done |
|
|
|
|
|
|
|
if [ -z "$COMPOSE_FILE" ]; then |
|
|
|
err "Can't find mailcow base installation path in backup." |
|
|
|
return 1 |
|
|
|
msg_target="Partial mailcow backup" |
|
|
|
else |
|
|
|
targets=("${ALL_TARGETS[@]}") |
|
|
|
msg_target="Full mailcow backup" |
|
|
|
fi |
|
|
|
|
|
|
|
## Mysql database |
|
|
|
echo "${WHITE}Downloading last backup of mysql backups${NORMAL}" |
|
|
|
backup:rsync "/var/backups/mysql/" "/var/backups/mysql" || return 1 |
|
|
|
|
|
|
|
if ! env_content=$(echo "cat '$ENV_FILE'" | ssh:run "root@$vps" -- bash); then |
|
|
|
err "Can't access env file: '$ENV_FILE'." |
|
|
|
return 1 |
|
|
|
fi |
|
|
|
for target in "${targets[@]}"; do |
|
|
|
case "$target" in |
|
|
|
postfix|rspamd|redis|crypt|vmail|vmail-attachments) |
|
|
|
|
|
|
|
volume_name="mailcowdockerized_${target}-vol-1" |
|
|
|
volume_dir="/var/lib/docker/volumes/${volume_name}/_data" |
|
|
|
if ! backup:path_exists "${volume_dir}/"; then |
|
|
|
warn "No '$volume_name' in backup. This might be expected." |
|
|
|
continue |
|
|
|
fi |
|
|
|
## Create volumes if not existent |
|
|
|
if ! ssh:run "root@$vps" -- " |
|
|
|
[ -d '${volume_dir}' ] || |
|
|
|
docker run --rm -v ${volume_name}:/tmp/dummy docker.0k.io/alpine:3.9 |
|
|
|
[ -d '${volume_dir}' ] |
|
|
|
"; then |
|
|
|
err "Couldn't find nor create '${volume_dir}'." |
|
|
|
return 1 |
|
|
|
fi |
|
|
|
|
|
|
|
echo "${WHITE}Downloading of $volume_name${NORMAL}" |
|
|
|
backup:rsync "${volume_dir}/" "${volume_dir}" || return 1 |
|
|
|
;; |
|
|
|
mailcow) |
|
|
|
## Mailcow git base |
|
|
|
COMPOSE_FILE= |
|
|
|
for mailcow_dir in /opt/{apps/,}mailcow-dockerized; do |
|
|
|
backup:path_exists "${mailcow_dir}/" || continue |
|
|
|
## this possibly change last value |
|
|
|
COMPOSE_FILE="$mailcow_dir/docker-compose.yml" |
|
|
|
ENV_FILE="$mailcow_dir/.env" |
|
|
|
echo "${WHITE}Download of $mailcow_dir${NORMAL}" |
|
|
|
backup:rsync "${mailcow_dir}"/ "${mailcow_dir}" || return 1 |
|
|
|
break |
|
|
|
done |
|
|
|
if [ -z "$COMPOSE_FILE" ]; then |
|
|
|
err "Can't find mailcow base installation path in backup." |
|
|
|
return 1 |
|
|
|
fi |
|
|
|
|
|
|
|
;; |
|
|
|
mysql) |
|
|
|
if [ -z "$COMPOSE_FILE" ]; then |
|
|
|
## Mailcow git base |
|
|
|
compose_files=() |
|
|
|
for mailcow_dir in /opt/{apps/,}mailcow-dockerized; do |
|
|
|
ssh:run "root@$vps" -- "[ -e \"$mailcow_dir/docker-compose.yml\" ]" || continue |
|
|
|
## this possibly change last value |
|
|
|
compose_files+=("$mailcow_dir/docker-compose.yml") |
|
|
|
done |
|
|
|
if [ "${#compose_files[@]}" == 0 ]; then |
|
|
|
err "No compose file found for mailcow installation." |
|
|
|
return 1 |
|
|
|
elif [ "${#compose_files[@]}" -gt 1 ]; then |
|
|
|
err "Multiple compose files for mailcow found:" |
|
|
|
for f in "${compose_files[@]}"; do |
|
|
|
echo " - $f" >&2 |
|
|
|
done |
|
|
|
echo "Can't decide which to use for mounting mysql container." >&2 |
|
|
|
return 1 |
|
|
|
fi |
|
|
|
COMPOSE_FILE="${compose_files[0]}" |
|
|
|
ENV_FILE="${COMPOSE_FILE%/*}/.env" |
|
|
|
if ! ssh:run "root@$vps" -- "[ -e \"${COMPOSE_FILE%/*}/.env\" ]"; then |
|
|
|
err "No env file in '$ENV_FILE' found." |
|
|
|
return 1 |
|
|
|
fi |
|
|
|
fi |
|
|
|
|
|
|
|
## Mysql database |
|
|
|
echo "${WHITE}Downloading last backup of mysql backups${NORMAL}" |
|
|
|
backup:rsync "/var/backups/mysql/" "/var/backups/mysql" || return 1 |
|
|
|
|
|
|
|
if ! env_content=$(echo "cat '$ENV_FILE'" | ssh:run "root@$vps" -- bash); then |
|
|
|
err "Can't access env file: '$ENV_FILE'." |
|
|
|
return 1 |
|
|
|
fi |
|
|
|
|
|
|
|
root_password=$(printf "%s\n" "$env_content" | grep ^DBROOT= | cut -f 2 -d =) |
|
|
|
root_password=$(printf "%s\n" "$env_content" | grep ^DBROOT= | cut -f 2 -d =) |
|
|
|
|
|
|
|
echo "${WHITE}Bringing mysql-mailcow up${NORMAL}" |
|
|
|
if ! image=$(cat <<EOF | ssh:run "root@$vps" -- bash |
|
|
|
echo "${WHITE}Bringing mysql-mailcow up${NORMAL}" |
|
|
|
if ! image=$(cat <<EOF | ssh:run "root@$vps" -- bash |
|
|
|
shyaml get-value services.mysql-mailcow.image < "${COMPOSE_FILE}" |
|
|
|
EOF |
|
|
|
); then |
|
|
|
err "Failed to get image name of service 'mysql-mailcow' in 'compose.yml'." |
|
|
|
return 1 |
|
|
|
fi |
|
|
|
if [ -z "$(ssh:run "root@$vps" -- docker images -q "$image")" ]; then |
|
|
|
info "Image '$image' not available, pull it." |
|
|
|
if ! ssh:run "root@$vps" -- \ |
|
|
|
docker-compose -f "${COMPOSE_FILE}" --env-file "${ENV_FILE}" \ |
|
|
|
pull mysql-mailcow; then |
|
|
|
|
|
|
|
err "Failed to pull image of service 'mysql-mailcow'." |
|
|
|
return 1 |
|
|
|
fi |
|
|
|
fi |
|
|
|
|
|
|
|
if ! container_id=$(cat <<EOF | ssh:run "root@$vps" -- bash |
|
|
|
); then |
|
|
|
err "Failed to get image name of service 'mysql-mailcow' in 'compose.yml'." |
|
|
|
return 1 |
|
|
|
fi |
|
|
|
if [ -z "$(ssh:run "root@$vps" -- docker images -q "$image")" ]; then |
|
|
|
info "Image '$image' not available, pull it." |
|
|
|
if ! ssh:run "root@$vps" -- \ |
|
|
|
docker-compose -f "${COMPOSE_FILE}" --env-file "${ENV_FILE}" \ |
|
|
|
pull mysql-mailcow; then |
|
|
|
|
|
|
|
err "Failed to pull image of service 'mysql-mailcow'." |
|
|
|
return 1 |
|
|
|
fi |
|
|
|
fi |
|
|
|
|
|
|
|
if ! container_id=$(cat <<EOF | ssh:run "root@$vps" -- bash |
|
|
|
echo "[client] |
|
|
|
password=$root_password" > "$VPS_TMP_DIR/my.cnf" |
|
|
|
|
|
|
@ -504,35 +558,35 @@ docker-compose -f "${COMPOSE_FILE}" --env-file "${ENV_FILE}" \ |
|
|
|
mysql-mailcow |
|
|
|
|
|
|
|
EOF |
|
|
|
); then |
|
|
|
err "Failed to bring up mysql-mailcow" |
|
|
|
return 1 |
|
|
|
fi |
|
|
|
|
|
|
|
START="$SECONDS" |
|
|
|
retries=0 |
|
|
|
timeout=600 |
|
|
|
while true; do |
|
|
|
((retries++)) |
|
|
|
echo " waiting for mysql db..." \ |
|
|
|
"(retry $retries, $(($SECONDS - $START))s elapsed, timeout is ${timeout}s)" >&2 |
|
|
|
cat <<EOF | ssh:run "root@$vps" -- bash && break |
|
|
|
); then |
|
|
|
err "Failed to bring up mysql-mailcow" |
|
|
|
return 1 |
|
|
|
fi |
|
|
|
|
|
|
|
START="$SECONDS" |
|
|
|
retries=0 |
|
|
|
timeout=600 |
|
|
|
while true; do |
|
|
|
((retries++)) |
|
|
|
echo " waiting for mysql db..." \ |
|
|
|
"(retry $retries, $(($SECONDS - $START))s elapsed, timeout is ${timeout}s)" >&2 |
|
|
|
cat <<EOF | ssh:run "root@$vps" -- bash && break |
|
|
|
echo "SELECT 1;" | docker exec -i "$container_id" mysql >/dev/null 2>&1 |
|
|
|
EOF |
|
|
|
if (($SECONDS - $START > $timeout)); then |
|
|
|
err "Failed to connect to mysql-mailcow." |
|
|
|
echo "docker-compose -f \"${COMPOSE_FILE}\" --env-file \"${ENV_FILE}\" down" | |
|
|
|
ssh:run "root@$vps" -- bash |
|
|
|
return 1 |
|
|
|
fi |
|
|
|
sleep 0.4 |
|
|
|
done |
|
|
|
if (($SECONDS - $START > $timeout)); then |
|
|
|
err "Failed to connect to mysql-mailcow." |
|
|
|
echo "docker-compose -f \"${COMPOSE_FILE}\" --env-file \"${ENV_FILE}\" down" | |
|
|
|
ssh:run "root@$vps" -- bash |
|
|
|
return 1 |
|
|
|
fi |
|
|
|
sleep 0.4 |
|
|
|
done |
|
|
|
|
|
|
|
DBUSER=$(printf "%s\n" "$env_content" | grep ^DBUSER= | cut -f 2 -d =) |
|
|
|
DBPASS=$(printf "%s\n" "$env_content" | grep ^DBPASS= | cut -f 2 -d =) |
|
|
|
DBUSER=$(printf "%s\n" "$env_content" | grep ^DBUSER= | cut -f 2 -d =) |
|
|
|
DBPASS=$(printf "%s\n" "$env_content" | grep ^DBPASS= | cut -f 2 -d =) |
|
|
|
|
|
|
|
echo "${WHITE}Uploading mysql dump${NORMAL}" |
|
|
|
cat <<EOF | ssh:run "root@$vps" -- bash |
|
|
|
echo "${WHITE}Uploading mysql dump${NORMAL}" |
|
|
|
cat <<EOF | ssh:run "root@$vps" -- bash |
|
|
|
|
|
|
|
echo " |
|
|
|
DROP DATABASE IF EXISTS mailcow; |
|
|
@ -543,18 +597,24 @@ echo " |
|
|
|
zcat /var/backups/mysql/mailcow/*.gz | docker exec -i "$container_id" mysql mailcow |
|
|
|
|
|
|
|
EOF |
|
|
|
if [ "$?" != 0 ]; then |
|
|
|
err "Failed to load mysql dump." |
|
|
|
echo "docker-compose -f \"${COMPOSE_FILE}\" --env-file \"${ENV_FILE}\" down" | |
|
|
|
ssh:run "root@$vps" -- bash |
|
|
|
return 1 |
|
|
|
fi |
|
|
|
if [ "$?" != 0 ]; then |
|
|
|
err "Failed to load mysql dump." |
|
|
|
echo "docker-compose -f \"${COMPOSE_FILE}\" --env-file \"${ENV_FILE}\" down" | |
|
|
|
ssh:run "root@$vps" -- bash |
|
|
|
return 1 |
|
|
|
fi |
|
|
|
|
|
|
|
echo "${WHITE}Bringing mysql-mailcow down${NORMAL}" |
|
|
|
echo "docker-compose -f \"${COMPOSE_FILE}\" --env-file \"${ENV_FILE}\" down" | |
|
|
|
ssh:run "root@$vps" -- bash |
|
|
|
;; |
|
|
|
*) |
|
|
|
err "Unknown component '$target'. Bailing out." |
|
|
|
return 1 |
|
|
|
esac |
|
|
|
done |
|
|
|
|
|
|
|
echo "${WHITE}Bringing mysql-mailcow down${NORMAL}" |
|
|
|
echo "docker-compose -f \"${COMPOSE_FILE}\" --env-file \"${ENV_FILE}\" down" | |
|
|
|
ssh:run "root@$vps" -- bash |
|
|
|
ssh:run "root@$vps" -- "rm -rf '$VPS_TMP_DIR'" |
|
|
|
msg_target="Full mailcow backup" |
|
|
|
fi |
|
|
|
|
|
|
|
if [ -n "$stopped_containers" ]; then |
|
|
@ -619,7 +679,7 @@ vps_backup_recover() { |
|
|
|
## from vps to backup server. |
|
|
|
backup:setup-rsync "$admin" "$vps" "$server" "$id" || return 1 |
|
|
|
|
|
|
|
if [ -n "$path" ]; then |
|
|
|
if [[ "$path" == "/"* ]]; then |
|
|
|
if ! backup:path_exists "${path}"; then |
|
|
|
err "File or directory '$path' not found in backup." |
|
|
|
return 1 |
|
|
@ -640,13 +700,19 @@ vps_backup_recover() { |
|
|
|
mailcow:vps_backup_recover "$admin" "$server" "$id" "$path" "$vps" "$vps_path" |
|
|
|
;; |
|
|
|
*-*) |
|
|
|
if [ -n "$path" ]; then |
|
|
|
if [[ "$path" == "/"* ]]; then |
|
|
|
## For now, will require having $path and $vps_path set, no additional behaviors |
|
|
|
file:vps_backup_recover "$admin" "$server" "$id" "$path" "$vps" "$vps_path" |
|
|
|
else |
|
|
|
err "Full recover of ${rtype:-unknown} backup type on" \ |
|
|
|
"${type:-unknown} type VPS is not yet implemented." |
|
|
|
return 1 |
|
|
|
if [ -n "$path" ]; then |
|
|
|
err "Partial component recover of ${rtype:-unknown} backup type on" \ |
|
|
|
"${type:-unknown} type VPS is not yet implemented." |
|
|
|
return 1 |
|
|
|
else |
|
|
|
err "Full recover of ${rtype:-unknown} backup type on" \ |
|
|
|
"${type:-unknown} type VPS is not yet implemented." |
|
|
|
return 1 |
|
|
|
fi |
|
|
|
fi |
|
|
|
;; |
|
|
|
esac |
|
|
@ -1000,7 +1066,8 @@ cmdline.spec:vps-backup:cmd:recover:run() { |
|
|
|
|
|
|
|
: :posarg: BACKUP_ID 'Backup id. |
|
|
|
(ie: myadmin@backup.domain.org:10023#mx.myvps.org |
|
|
|
myadmin@ark-01.org#myid:/a/path)' |
|
|
|
myadmin@ark-01.org#myid:/a/path |
|
|
|
admin@ark-02.io#myid:myqsl,mailcow)' |
|
|
|
: :posarg: VPS_PATH 'Target host(s) to check. |
|
|
|
(ie: myvps.com |
|
|
|
myvps.com:/a/path)' |
|
|
|