Первый коммит: модульный монолит ядра (.NET 10) и gRPC-сервисы ai/ml/telegram, фронтенд Vue 3/Vite/Tailwind, документация (ТЗ, инструкция пользователя, техдокументация, код-стайл), бэклог, скрипты развёртывания и архив прототипа LeadRadar.
185 lines
10 KiB
Bash
185 lines
10 KiB
Bash
#!/usr/bin/env bash
|
|
#
|
|
# deal-backup-lib.sh — общие для scripts/backup.sh и scripts/restore.sh env-дефолты и хелперы
|
|
# (этап 7 Task 15, Ruling 8). Скрипт НЕ исполняется напрямую: подключается из backup.sh/restore.sh
|
|
# . "$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)/deal-backup-lib.sh"
|
|
#
|
|
# Параметры — окружение (секреты скрипты НЕ логируют; в лог — только имена файлов и размеры):
|
|
#
|
|
# BACKUP_DIR / DEAL_BACKUP_DIR корень бэкапов (по умолчанию <репозиторий>/data/backups)
|
|
# RETENTION_DAYS / DEAL_RETENTION_DAYS
|
|
# сколько дней хранить снапшоты (удаление по дате YYYYMMDD в имени; 14)
|
|
# DEAL_DATA_DIR корень host-каталогов данных (по умолчанию <репозиторий>/data)
|
|
# DEAL_TAR_DIRS относительные к DEAL_DATA_DIR каталоги для тара (по умолчанию
|
|
# "attachments ml telegram_sessions"; контейнерный путь каталога
|
|
# сессий telegram-service — /data/sessions)
|
|
# DEAL_TAR_VOLUMES если задан (список docker volume'ов через пробел) — файловые данные
|
|
# берутся из томов busybox-контейнером (Ruling 8), DEAL_TAR_DIRS
|
|
# игнорируется
|
|
#
|
|
# Postgres (docker-режим по умолчанию — exec в контейнер; прямой pg_dump — при заданном DEAL_PG_HOST):
|
|
# DEAL_PG_HOST / DEAL_PG_PORT (5432) / DEAL_PG_USER (deal) / DEAL_PG_PASSWORD / DEAL_PG_DB (deal)
|
|
# DEAL_PG_CONTAINER имя контейнера (авто: deal-postgres → compose-сервис 'postgres')
|
|
#
|
|
# MinIO / mc (бэкап = выгрузка ИЗ MinIO в BACKUP_DIR; restore — обратно):
|
|
# DEAL_MINIO_ENDPOINT пусто → host-mc: http://localhost:9000 (dev: опубликованный порт);
|
|
# docker-mc: http://minio:9000 (алиас compose-сервиса — работает и в
|
|
# compose.dev, и в compose.prod). prod (compose.prod) порты MinIO НЕ
|
|
# публикует — хостовый mc его не достанет: нужен docker-режим (дефолт,
|
|
# endpoint minio:9000) либо достижимый DEAL_MINIO_ENDPOINT.
|
|
# DEAL_MINIO_ACCESS_KEY (fallback: MINIO_ROOT_USER; иначе deal_minio)
|
|
# DEAL_MINIO_SECRET_KEY (fallback: MINIO_ROOT_PASSWORD; иначе deal_minio_secret)
|
|
# DEAL_MINIO_BUCKET (deal-files)
|
|
# DEAL_MINIO_SKIP 1 → шаг MinIO пропускается (Local-хранилище без MinIO)
|
|
# DEAL_MINIO_MIRROR_REMOVE restore: 1 → mc mirror --remove (бакет = снимок 1:1)
|
|
# DEAL_MC_IMAGE docker-режим mc (minio/mc; в проде зафиксируйте тег, напр.
|
|
# minio/mc:RELEASE.2025-04-16T15-23-31Z)
|
|
# DEAL_MC_CONTAINER контейнер MinIO для сети (авто: deal-minio → compose-сервис 'minio')
|
|
set -u
|
|
|
|
# --- Корень репозитория и каталоги -----------------------------------------------
|
|
SCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
|
|
REPO_DIR=$(CDPATH= cd -- "$SCRIPT_DIR/.." && pwd)
|
|
|
|
BACKUP_DIR=${BACKUP_DIR:-${DEAL_BACKUP_DIR:-"$REPO_DIR/data/backups"}}
|
|
RETENTION_DAYS=${RETENTION_DAYS:-${DEAL_RETENTION_DAYS:-14}}
|
|
DEAL_DATA_DIR=${DEAL_DATA_DIR:-"$REPO_DIR/data"}
|
|
DEAL_TAR_DIRS=${DEAL_TAR_DIRS:-"attachments ml telegram_sessions"}
|
|
DEAL_TAR_VOLUMES=${DEAL_TAR_VOLUMES:-""}
|
|
|
|
# --- Postgres ----------------------------------------------------------------------
|
|
DEAL_PG_DB=${DEAL_PG_DB:-deal}
|
|
DEAL_PG_USER=${DEAL_PG_USER:-deal}
|
|
DEAL_PG_HOST=${DEAL_PG_HOST:-""}
|
|
DEAL_PG_PORT=${DEAL_PG_PORT:-5432}
|
|
DEAL_PG_PASSWORD=${DEAL_PG_PASSWORD:-""}
|
|
DEAL_PG_CONTAINER=${DEAL_PG_CONTAINER:-""}
|
|
|
|
# --- MinIO / mc --------------------------------------------------------------------
|
|
DEAL_MINIO_BUCKET=${DEAL_MINIO_BUCKET:-deal-files}
|
|
DEAL_MINIO_ENDPOINT=${DEAL_MINIO_ENDPOINT:-""}
|
|
DEAL_MINIO_ACCESS_KEY=${DEAL_MINIO_ACCESS_KEY:-${MINIO_ROOT_USER:-deal_minio}}
|
|
DEAL_MINIO_SECRET_KEY=${DEAL_MINIO_SECRET_KEY:-${MINIO_ROOT_PASSWORD:-deal_minio_secret}}
|
|
DEAL_MINIO_SKIP=${DEAL_MINIO_SKIP:-0}
|
|
DEAL_MINIO_MIRROR_REMOVE=${DEAL_MINIO_MIRROR_REMOVE:-0}
|
|
DEAL_MC_IMAGE=${DEAL_MC_IMAGE:-minio/mc}
|
|
DEAL_MC_CONTAINER=${DEAL_MC_CONTAINER:-""}
|
|
|
|
# Найденные рантайм-значения (заполняют функции ниже)
|
|
PG_CONTAINER=""
|
|
MINIO_CONTAINER=""
|
|
MC_MODE="" # skip | host (клиент mc) | docker (разовый контейнер minio/mc)
|
|
MC_ENDPOINT="$DEAL_MINIO_ENDPOINT"
|
|
|
|
# --- Утилиты -----------------------------------------------------------------------
|
|
say() { printf '%s\n' "$*"; }
|
|
log() { printf '[%s] %s\n' "$(date '+%F %T')" "$*"; }
|
|
die() { log "ОШИБКА: $*"; exit 1; }
|
|
have() { command -v "$1" >/dev/null 2>&1; }
|
|
docker_ok() { docker info >/dev/null 2>&1; }
|
|
|
|
# --- Windows (Git Bash/MSYS): docker не понимает /c/... пути и ломает контейнерные
|
|
# /out, /in (MSYS конвертирует их в C:/Program Files/Git/...). Отключаем конвертацию
|
|
# аргументов и переводим host-пути в Windows-вид (C:/...) для -v/-cp. На Linux хелпер
|
|
# возвращает путь как есть — поведение не меняется.
|
|
if uname -s 2>/dev/null | grep -qiE 'mingw|msys|cygwin'; then
|
|
export MSYS_NO_PATHCONV=1 MSYS2_ARG_CONV_EXCL='*'
|
|
host_docker_path() { cygpath -m "$1"; }
|
|
else
|
|
host_docker_path() { printf '%s' "$1"; }
|
|
fi
|
|
|
|
container_running() { # имя контейнера → 0, если запущен
|
|
[ -n "${1:-}" ] && [ "$(docker inspect -f '{{.State.Running}}' "$1" 2>/dev/null)" = "true" ]
|
|
}
|
|
|
|
compose_container() { # метка com.docker.compose.service → имя running-контейнера (или пусто)
|
|
docker ps --filter "label=com.docker.compose.service=$1" --format '{{.Names}}' 2>/dev/null | head -n 1 || true
|
|
}
|
|
|
|
# --- Поиск контейнеров Postgres / MinIO --------------------------------------------
|
|
resolve_pg_container() {
|
|
local c
|
|
if [ -n "$DEAL_PG_CONTAINER" ]; then
|
|
container_running "$DEAL_PG_CONTAINER" \
|
|
|| die "контейнер DEAL_PG_CONTAINER='$DEAL_PG_CONTAINER' не найден/не запущен"
|
|
PG_CONTAINER="$DEAL_PG_CONTAINER"
|
|
return 0
|
|
fi
|
|
if container_running deal-postgres; then PG_CONTAINER=deal-postgres; return 0; fi
|
|
c=$(compose_container postgres)
|
|
if [ -n "$c" ] && container_running "$c"; then PG_CONTAINER="$c"; return 0; fi
|
|
return 1
|
|
}
|
|
|
|
resolve_minio_container() {
|
|
local c
|
|
if [ -n "$DEAL_MC_CONTAINER" ]; then
|
|
container_running "$DEAL_MC_CONTAINER" \
|
|
|| die "контейнер DEAL_MC_CONTAINER='$DEAL_MC_CONTAINER' не найден/не запущен"
|
|
MINIO_CONTAINER="$DEAL_MC_CONTAINER"
|
|
return 0
|
|
fi
|
|
if container_running deal-minio; then MINIO_CONTAINER=deal-minio; return 0; fi
|
|
c=$(compose_container minio)
|
|
if [ -n "$c" ] && container_running "$c"; then MINIO_CONTAINER="$c"; return 0; fi
|
|
return 1
|
|
}
|
|
|
|
minio_network() { # первая docker-сеть контейнера MinIO (для разового контейнера mc)
|
|
docker inspect -f '{{range $k, $v := .NetworkSettings.Networks}}{{$k}} {{end}}' \
|
|
"$MINIO_CONTAINER" 2>/dev/null | awk '{ print $1 }'
|
|
}
|
|
|
|
# --- mc: выбор режима и запуск -------------------------------------------------------
|
|
select_mc_mode() { # заполняет MC_MODE / MC_ENDPOINT
|
|
if [ "$DEAL_MINIO_SKIP" = "1" ] || [ "$DEAL_MINIO_SKIP" = "true" ]; then
|
|
MC_MODE=skip
|
|
elif have mc; then
|
|
MC_MODE=host
|
|
[ -n "$MC_ENDPOINT" ] || MC_ENDPOINT=http://localhost:9000
|
|
elif docker_ok; then
|
|
MC_MODE=docker
|
|
# minio — алиас compose-сервиса: резолвится и в compose.dev (container_name deal-minio), и в
|
|
# compose.prod (container_name нет, сервис 'minio'); deal-minio:9000 в prod-сети не существует.
|
|
[ -n "$MC_ENDPOINT" ] || MC_ENDPOINT=http://minio:9000
|
|
else
|
|
die "MinIO: клиент mc не найден и docker недоступен — установите mc или задайте DEAL_MINIO_SKIP=1"
|
|
fi
|
|
}
|
|
|
|
mc_cmd() { # выполняет «mc <аргументы…>» в выбранном режиме; alias 'deal' — только env MC_HOST_deal
|
|
# (секрет в конфиг mc на диск НЕ пишется; значение env нигде не логируется)
|
|
# MC_HOST_deal — единый URL http(s)://user:pass@host:port: схему берём из MC_ENDPOINT (если задана),
|
|
# иначе http. Убираем схему из host:port — иначе URL получает двойную схему и mc его отвергает.
|
|
local host_value endpoint scheme
|
|
endpoint="$MC_ENDPOINT"
|
|
scheme=http
|
|
case "$endpoint" in
|
|
https://*) scheme=https; endpoint=${endpoint#https://} ;;
|
|
http://*) endpoint=${endpoint#http://} ;;
|
|
esac
|
|
host_value="$scheme://$DEAL_MINIO_ACCESS_KEY:$DEAL_MINIO_SECRET_KEY@$endpoint"
|
|
case "$MC_MODE" in
|
|
skip)
|
|
log "MinIO: шаг пропущен (DEAL_MINIO_SKIP=1)"
|
|
return 0
|
|
;;
|
|
host)
|
|
MC_HOST_deal="$host_value" mc "$@"
|
|
;;
|
|
docker)
|
|
resolve_minio_container \
|
|
|| die "MinIO: контейнер не найден (искал deal-minio и compose-сервис 'minio') — поднимите стек, задайте DEAL_MINIO_ENDPOINT или DEAL_MINIO_SKIP=1"
|
|
log "MinIO: разовый контейнер $DEAL_MC_IMAGE (сеть: $(minio_network), бакет $DEAL_MINIO_BUCKET)"
|
|
docker run --rm -i --network "$(minio_network)" \
|
|
-e "MC_HOST_deal=$host_value" \
|
|
-v "$(host_docker_path "$BACKUP_DIR"):$BACKUP_DIR" \
|
|
"$DEAL_MC_IMAGE" "$@"
|
|
;;
|
|
*)
|
|
die "MinIO: режим mc не выбран (MC_MODE='$MC_MODE') — вызовите select_mc_mode"
|
|
;;
|
|
esac
|
|
}
|