92 lines
3.5 KiB
Bash
92 lines
3.5 KiB
Bash
|
|
#!/usr/bin/env bash
|
||
|
|
# glchat: восстановление из бэкапа (AGENT.md 10.8).
|
||
|
|
set -euo pipefail
|
||
|
|
|
||
|
|
GLCHAT_ETC_DIR="${GLCHAT_ETC_DIR:-/opt/glchat}"
|
||
|
|
GLCHAT_DATA_DIR="${GLCHAT_DATA_DIR:-${GLCHAT_ETC_DIR}/data}"
|
||
|
|
ENV_FILE="${GLCHAT_ETC_DIR}/.env"
|
||
|
|
COMPOSE_FILE="${GLCHAT_ETC_DIR}/compose.yaml"
|
||
|
|
|
||
|
|
log() { printf '[restore] %s\n' "$*"; }
|
||
|
|
die() {
|
||
|
|
printf '[restore] ошибка: %s\n' "$*" >&2
|
||
|
|
exit 1
|
||
|
|
}
|
||
|
|
|
||
|
|
ARCHIVE="${1:-}"
|
||
|
|
[ -n "$ARCHIVE" ] || die "использование: glchat restore <файл.tar.gz[.age]>"
|
||
|
|
[ -f "$ARCHIVE" ] || die "файл не найден: $ARCHIVE"
|
||
|
|
[ -f "$ENV_FILE" ] || die "не найден ${ENV_FILE}"
|
||
|
|
[ -f "$COMPOSE_FILE" ] || die "не найден ${COMPOSE_FILE}"
|
||
|
|
|
||
|
|
# shellcheck disable=SC1090
|
||
|
|
set -a && . "$ENV_FILE" && set +a
|
||
|
|
|
||
|
|
WORK_DIR="$(mktemp -d)"
|
||
|
|
cleanup() { rm -rf "$WORK_DIR"; }
|
||
|
|
trap cleanup EXIT
|
||
|
|
|
||
|
|
case "$ARCHIVE" in
|
||
|
|
*.age)
|
||
|
|
command -v age >/dev/null 2>&1 || die "age не установлен"
|
||
|
|
KEY_FILE="${AGE_KEY_FILE:-${GLCHAT_DATA_DIR}/backups/age-key.txt}"
|
||
|
|
[ -f "$KEY_FILE" ] || die "не найден приватный age-ключ (${KEY_FILE}); укажите AGE_KEY_FILE"
|
||
|
|
log "расшифровываем ${ARCHIVE}"
|
||
|
|
age -d -i "$KEY_FILE" -o "${WORK_DIR}/backup.tar.gz" "$ARCHIVE" || die "не удалось расшифровать бэкап"
|
||
|
|
;;
|
||
|
|
*) cp "$ARCHIVE" "${WORK_DIR}/backup.tar.gz" ;;
|
||
|
|
esac
|
||
|
|
|
||
|
|
log "распаковываем"
|
||
|
|
tar -xzf "${WORK_DIR}/backup.tar.gz" -C "$WORK_DIR"
|
||
|
|
[ -f "${WORK_DIR}/glchat.db" ] || die "в архиве нет glchat.db — это не бэкап glchat"
|
||
|
|
|
||
|
|
log "текущее состояние"
|
||
|
|
(cd "$GLCHAT_ETC_DIR" && docker compose ps) || true
|
||
|
|
|
||
|
|
log "создаём страховочный бэкап текущих данных"
|
||
|
|
if [ "${GLCHAT_SKIP_PREBACKUP:-0}" != "1" ]; then
|
||
|
|
GLCHAT_ETC_DIR="$GLCHAT_ETC_DIR" bash "${GLCHAT_ETC_DIR}/backup.sh" || die "страховочный бэкап не удался; восстановление остановлено"
|
||
|
|
fi
|
||
|
|
|
||
|
|
printf 'Восстановление заменит текущую БД и файлы. Продолжить? (yes/no): '
|
||
|
|
read -r reply
|
||
|
|
[ "$reply" = "yes" ] || {
|
||
|
|
log "отменено"
|
||
|
|
exit 0
|
||
|
|
}
|
||
|
|
|
||
|
|
log "останавливаем приложение"
|
||
|
|
(cd "$GLCHAT_ETC_DIR" && docker compose stop app) || true
|
||
|
|
|
||
|
|
rollback_dir="${GLCHAT_DATA_DIR}/pre-restore-$(date -u +%Y%m%dT%H%M%SZ)"
|
||
|
|
mkdir -p "$rollback_dir"
|
||
|
|
cp -a "${GLCHAT_DATA_DIR}/db" "$rollback_dir/" 2>/dev/null || true
|
||
|
|
log "текущие данные скопированы в ${rollback_dir}"
|
||
|
|
|
||
|
|
log "заменяем базу"
|
||
|
|
install -m 640 "${WORK_DIR}/glchat.db" "${GLCHAT_DATA_DIR}/db/glchat.db"
|
||
|
|
rm -f "${GLCHAT_DATA_DIR}/db/glchat.db-wal" "${GLCHAT_DATA_DIR}/db/glchat.db-shm"
|
||
|
|
|
||
|
|
if [ -d "${WORK_DIR}/files" ]; then
|
||
|
|
log "заменяем файлы"
|
||
|
|
rm -rf "${GLCHAT_DATA_DIR}/files"
|
||
|
|
mkdir -p "${GLCHAT_DATA_DIR}/files"
|
||
|
|
cp -a "${WORK_DIR}/files/." "${GLCHAT_DATA_DIR}/files/"
|
||
|
|
fi
|
||
|
|
|
||
|
|
log "запускаем приложение"
|
||
|
|
(cd "$GLCHAT_ETC_DIR" && docker compose start app) || (cd "$GLCHAT_ETC_DIR" && docker compose up -d app)
|
||
|
|
|
||
|
|
for _ in $(seq 1 30); do
|
||
|
|
if curl -fsS "http://127.0.0.1:${APP_PORT:-8080}/readyz" >/dev/null 2>&1; then
|
||
|
|
log "приложение готово"
|
||
|
|
GLCHAT_ETC_DIR="$GLCHAT_ETC_DIR" bash "${GLCHAT_ETC_DIR}/smoke.sh" || true
|
||
|
|
log "восстановление завершено"
|
||
|
|
exit 0
|
||
|
|
fi
|
||
|
|
sleep 2
|
||
|
|
done
|
||
|
|
|
||
|
|
die "приложение не поднялось после восстановления; откат: ${rollback_dir}"
|