feat(deploy): установщик, docker-скелет, шаблоны Caddy/LiveKit и CLI
- deploy/install.sh: идемпотентная установка, режимы встроенного и внешнего прокси (§10.13), --dry-run/--skip-tls/--reconfigure, auto-детект профиля (§10.10) - deploy/compose.yaml.tpl: app, livekit, caddy с лимитами, healthcheck, ротацией логов, cap_drop и no-new-privileges (§4.3) - deploy/caddy: Caddyfile.tpl (встроенный) и external-proxy.conf.tpl (наш стенд) - deploy/livekit: конфиг с TURN, dynacast и лимитами комнат (§10.6) - deploy/backup.sh + restore.sh: sqlite .backup, tar, age-шифрование (§10.8) - deploy/update.sh: бэкап → pull/сборка → up → миграции → smoke (§9.3) - deploy/smoke.sh: проверки §10.9 (healthz/readyz/meta, порты, UFW, fail2ban, бэкап) - deploy/security-check.sh: аудит SSH, UFW, fail2ban, прав .env и ключей - deploy/ufw-docker.sh, nftables и fail2ban-конфиги (sshd, glchat-auth, recidive) - deploy/glchat-cli.sh: команды §10.7, systemd-юнит glchat.service - scripts/test-install.sh: прогон установщика в чистом Ubuntu 24.04 (systemd-контейнер) - Dockerfile: multi-stage (node → golang CGO → debian-slim), non-root, healthcheck - Makefile: lint/test/build/dev/generate/security/smoke и фиксация версий инструментов
This commit is contained in:
Executable
+91
@@ -0,0 +1,91 @@
|
||||
#!/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}"
|
||||
Reference in New Issue
Block a user