main
This commit is contained in:
Executable
+158
@@ -0,0 +1,158 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# ZBrain Backup
|
||||
# ==============
|
||||
# Делает дамп всех Postgres БД (brainhub + все gbrain_*) и конфигов.
|
||||
# Шифрует архивы через age (нужен age и публичный ключ).
|
||||
#
|
||||
# Запуск из cron, например:
|
||||
# 0 3 * * * root /opt/zbrain/scripts/backup.sh
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# ============================================================
|
||||
# Конфигурация
|
||||
# ============================================================
|
||||
BACKUP_DIR="${BACKUP_DIR:-/var/backups/zbrain}"
|
||||
RETENTION_DAYS="${RETENTION_DAYS:-14}"
|
||||
AGE_RECIPIENT_FILE="${AGE_RECIPIENT_FILE:-/etc/zbrain/backup.age.pub}"
|
||||
LOG_FILE="${LOG_FILE:-/var/log/zbrain/backup.log}"
|
||||
ZBRAIN_CONFIG_DIR="/etc/zbrain"
|
||||
ZBRAIN_DATA_DIR="/var/lib/zbrain"
|
||||
|
||||
DATE=$(date +%Y%m%d-%H%M%S)
|
||||
BACKUP_PATH="${BACKUP_DIR}/${DATE}"
|
||||
|
||||
log() {
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOG_FILE"
|
||||
}
|
||||
|
||||
err() {
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] ERROR: $*" | tee -a "$LOG_FILE" >&2
|
||||
}
|
||||
|
||||
# ============================================================
|
||||
# Проверки
|
||||
# ============================================================
|
||||
[[ $EUID -eq 0 ]] || { err "Требуется root"; exit 1; }
|
||||
|
||||
if ! command -v age &>/dev/null; then
|
||||
err "age не установлен. apt install age"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ ! -f "$AGE_RECIPIENT_FILE" ]]; then
|
||||
err "Файл получателя age не найден: $AGE_RECIPIENT_FILE"
|
||||
err "Создай ключ: age-keygen -o /etc/zbrain/backup.age"
|
||||
err "И публичную часть в $AGE_RECIPIENT_FILE"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mkdir -p "$BACKUP_PATH" "$(dirname "$LOG_FILE")"
|
||||
chmod 700 "$BACKUP_DIR"
|
||||
|
||||
log "=== ZBrain backup: $DATE ==="
|
||||
|
||||
# ============================================================
|
||||
# Postgres dump (каждая БД отдельно)
|
||||
# ============================================================
|
||||
log "Дамп Postgres БД..."
|
||||
|
||||
# Список всех ZBrain БД (brainhub + gbrain_*)
|
||||
DATABASES=$(sudo -u postgres psql -tAc "
|
||||
SELECT datname FROM pg_database
|
||||
WHERE datname = 'brainhub' OR datname LIKE 'gbrain_%'
|
||||
ORDER BY datname
|
||||
")
|
||||
|
||||
for db in $DATABASES; do
|
||||
log " ${db}"
|
||||
dump_file="${BACKUP_PATH}/${db}.sql"
|
||||
sudo -u postgres pg_dump --format=custom --compress=9 --file="${dump_file}.dump" "$db"
|
||||
|
||||
# Шифруем
|
||||
age -R "$AGE_RECIPIENT_FILE" -o "${dump_file}.dump.age" "${dump_file}.dump"
|
||||
rm "${dump_file}.dump"
|
||||
log " ✓ ${db}.sql.dump.age ($(du -h "${dump_file}.dump.age" | cut -f1))"
|
||||
done
|
||||
|
||||
# pg_dumpall для глобальных объектов (roles, tablespaces)
|
||||
log "Дамп глобальных Postgres объектов..."
|
||||
sudo -u postgres pg_dumpall --globals-only > "${BACKUP_PATH}/_globals.sql"
|
||||
age -R "$AGE_RECIPIENT_FILE" -o "${BACKUP_PATH}/_globals.sql.age" "${BACKUP_PATH}/_globals.sql"
|
||||
rm "${BACKUP_PATH}/_globals.sql"
|
||||
|
||||
# ============================================================
|
||||
# Конфиги
|
||||
# ============================================================
|
||||
log "Архив конфигов..."
|
||||
|
||||
tar czf "${BACKUP_PATH}/config.tar.gz" \
|
||||
-C / \
|
||||
etc/zbrain \
|
||||
etc/postgresql/16/main/postgresql.conf \
|
||||
etc/postgresql/16/main/conf.d \
|
||||
etc/postgresql/16/main/pg_hba.conf \
|
||||
etc/systemd/system/zbrain-*.service \
|
||||
etc/nginx 2>/dev/null || true
|
||||
|
||||
age -R "$AGE_RECIPIENT_FILE" -o "${BACKUP_PATH}/config.tar.gz.age" "${BACKUP_PATH}/config.tar.gz"
|
||||
rm "${BACKUP_PATH}/config.tar.gz"
|
||||
|
||||
log "✓ config.tar.gz.age"
|
||||
|
||||
# ============================================================
|
||||
# gbrain data dir (config.json per-brain содержит пароли)
|
||||
# ============================================================
|
||||
log "Архив brain configs..."
|
||||
|
||||
if [[ -d "$ZBRAIN_DATA_DIR/brains" ]]; then
|
||||
tar czf "${BACKUP_PATH}/brains-meta.tar.gz" \
|
||||
-C "$ZBRAIN_DATA_DIR" \
|
||||
--exclude='brains/*/data' \
|
||||
brains/
|
||||
age -R "$AGE_RECIPIENT_FILE" -o "${BACKUP_PATH}/brains-meta.tar.gz.age" "${BACKUP_PATH}/brains-meta.tar.gz"
|
||||
rm "${BACKUP_PATH}/brains-meta.tar.gz"
|
||||
log "✓ brains-meta.tar.gz.age"
|
||||
fi
|
||||
|
||||
# ============================================================
|
||||
# Манифест
|
||||
# ============================================================
|
||||
cat > "${BACKUP_PATH}/MANIFEST.txt" <<EOF
|
||||
ZBrain Backup
|
||||
=============
|
||||
Date: $(date -Iseconds)
|
||||
Hostname: $(hostname -f)
|
||||
Postgres version: $(sudo -u postgres psql -tAc 'SHOW server_version')
|
||||
|
||||
Databases:
|
||||
$(echo "$DATABASES" | sed 's/^/ - /')
|
||||
|
||||
Files:
|
||||
$(ls -lah "$BACKUP_PATH" | tail -n +2 | awk '{print " - " $NF " (" $5 ")"}')
|
||||
|
||||
Восстановление:
|
||||
bash /opt/zbrain/scripts/restore.sh ${DATE}
|
||||
EOF
|
||||
|
||||
# ============================================================
|
||||
# Размер итогового бэкапа
|
||||
# ============================================================
|
||||
TOTAL_SIZE=$(du -sh "$BACKUP_PATH" | cut -f1)
|
||||
log "✓ Backup завершён: $BACKUP_PATH ($TOTAL_SIZE)"
|
||||
|
||||
# ============================================================
|
||||
# Retention - удаляем старые бэкапы
|
||||
# ============================================================
|
||||
log "Чистка бэкапов старше $RETENTION_DAYS дней..."
|
||||
find "$BACKUP_DIR" -maxdepth 1 -type d -name '????????-??????' -mtime +$RETENTION_DAYS -exec rm -rf {} \;
|
||||
log "✓ Чистка завершена"
|
||||
|
||||
# ============================================================
|
||||
# (опционально) push на удалённое хранилище
|
||||
# ============================================================
|
||||
# rsync -avz "$BACKUP_PATH" backup@remote-host:/backups/zbrain/
|
||||
# или rclone copy "$BACKUP_PATH" "remote:zbrain-backups/${DATE}"
|
||||
|
||||
log "=== Готово ==="
|
||||
Executable
+485
@@ -0,0 +1,485 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# ZBrain VM Bootstrap
|
||||
# ====================
|
||||
# Подготавливает чистую Ubuntu 22.04 VM к работе как ZBrain хост.
|
||||
# Устанавливает: Postgres 16 + pgvector, Node 22 LTS, Bun, Docker, gbrain.
|
||||
#
|
||||
# Использование:
|
||||
# sudo bash scripts/bootstrap-vm.sh
|
||||
#
|
||||
# Предполагается: запуск от root на свежей Ubuntu 22.04 LTS.
|
||||
# Скрипт идемпотентный - можно перезапускать.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# ============================================================
|
||||
# Конфигурация - правь под себя ПЕРЕД запуском
|
||||
# ============================================================
|
||||
ZBRAIN_USER="${ZBRAIN_USER:-zbrain}"
|
||||
ZBRAIN_HOME="/opt/zbrain"
|
||||
ZBRAIN_CONFIG_DIR="/etc/zbrain"
|
||||
ZBRAIN_DATA_DIR="/var/lib/zbrain"
|
||||
ZBRAIN_LOG_DIR="/var/log/zbrain"
|
||||
|
||||
# FNA-прокси для исходящих запросов к OpenAI/Anthropic
|
||||
FNA_PROXY="${FNA_PROXY:-http://client_001:PASSWORD@fna.zetit.ru:3128}"
|
||||
|
||||
POSTGRES_VERSION="16"
|
||||
NODE_VERSION="22"
|
||||
GBRAIN_VERSION="${GBRAIN_VERSION:-master}" # потом закрепим тег после первого деплоя
|
||||
GBRAIN_REPO="${GBRAIN_REPO:-https://git.zetit.ru/zuevav/gbrain-mirror.git}"
|
||||
|
||||
# RAM в гигабайтах (для тюнинга Postgres)
|
||||
TOTAL_RAM_GB="${TOTAL_RAM_GB:-8}"
|
||||
|
||||
# ============================================================
|
||||
# Утилиты
|
||||
# ============================================================
|
||||
log() {
|
||||
echo -e "\033[1;34m[$(date +'%H:%M:%S')] $*\033[0m"
|
||||
}
|
||||
|
||||
err() {
|
||||
echo -e "\033[1;31m[ERROR] $*\033[0m" >&2
|
||||
}
|
||||
|
||||
require_root() {
|
||||
if [[ $EUID -ne 0 ]]; then
|
||||
err "Этот скрипт должен запускаться от root (sudo)."
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
check_ubuntu() {
|
||||
if ! grep -q "Ubuntu 22.04" /etc/os-release 2>/dev/null; then
|
||||
err "Скрипт рассчитан на Ubuntu 22.04 LTS."
|
||||
err "Текущая система:"
|
||||
cat /etc/os-release | grep PRETTY_NAME >&2 || true
|
||||
read -p "Продолжить на свой страх и риск? [y/N] " -n 1 -r
|
||||
echo
|
||||
[[ $REPLY =~ ^[Yy]$ ]] || exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# ============================================================
|
||||
# Шаг 1: Базовые пакеты и обновления
|
||||
# ============================================================
|
||||
step_base_packages() {
|
||||
log "Шаг 1: Базовые пакеты"
|
||||
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
apt-get update -qq
|
||||
apt-get install -y -qq \
|
||||
curl wget gnupg lsb-release ca-certificates \
|
||||
build-essential git \
|
||||
ufw fail2ban \
|
||||
htop iotop net-tools \
|
||||
unzip jq \
|
||||
software-properties-common \
|
||||
apt-transport-https \
|
||||
chrony # точное время для аудит-логов
|
||||
|
||||
log "Шаг 1: ✓"
|
||||
}
|
||||
|
||||
# ============================================================
|
||||
# Шаг 2: Создание пользователя zbrain
|
||||
# ============================================================
|
||||
step_create_user() {
|
||||
log "Шаг 2: Пользователь $ZBRAIN_USER"
|
||||
|
||||
if ! id "$ZBRAIN_USER" &>/dev/null; then
|
||||
useradd --system --create-home --shell /bin/bash "$ZBRAIN_USER"
|
||||
log "Создан пользователь $ZBRAIN_USER"
|
||||
fi
|
||||
|
||||
mkdir -p "$ZBRAIN_HOME" "$ZBRAIN_CONFIG_DIR" "$ZBRAIN_DATA_DIR" "$ZBRAIN_LOG_DIR"
|
||||
chown -R "$ZBRAIN_USER:$ZBRAIN_USER" "$ZBRAIN_HOME" "$ZBRAIN_DATA_DIR" "$ZBRAIN_LOG_DIR"
|
||||
chmod 750 "$ZBRAIN_CONFIG_DIR" # секреты - 750
|
||||
|
||||
log "Шаг 2: ✓"
|
||||
}
|
||||
|
||||
# ============================================================
|
||||
# Шаг 3: Системные параметры (sysctl, swap, файрвол)
|
||||
# ============================================================
|
||||
step_system_tuning() {
|
||||
log "Шаг 3: Системные параметры"
|
||||
|
||||
# Swap для защиты от OOM
|
||||
if [[ ! -f /swapfile ]]; then
|
||||
fallocate -l 4G /swapfile
|
||||
chmod 600 /swapfile
|
||||
mkswap /swapfile
|
||||
swapon /swapfile
|
||||
echo '/swapfile none swap sw 0 0' >> /etc/fstab
|
||||
log "Создан swap 4G"
|
||||
fi
|
||||
|
||||
# sysctl для Postgres + сети
|
||||
cat > /etc/sysctl.d/99-zbrain.conf <<EOF
|
||||
# ZBrain system tuning
|
||||
vm.swappiness = 10
|
||||
vm.overcommit_memory = 2
|
||||
vm.overcommit_ratio = 80
|
||||
vm.dirty_background_ratio = 5
|
||||
vm.dirty_ratio = 10
|
||||
|
||||
# Сетевые буферы
|
||||
net.core.rmem_max = 16777216
|
||||
net.core.wmem_max = 16777216
|
||||
net.ipv4.tcp_rmem = 4096 87380 16777216
|
||||
net.ipv4.tcp_wmem = 4096 65536 16777216
|
||||
|
||||
# Защита
|
||||
net.ipv4.tcp_syncookies = 1
|
||||
net.ipv4.conf.all.rp_filter = 1
|
||||
EOF
|
||||
sysctl -p /etc/sysctl.d/99-zbrain.conf >/dev/null
|
||||
|
||||
# UFW firewall
|
||||
ufw --force reset >/dev/null
|
||||
ufw default deny incoming
|
||||
ufw default allow outgoing
|
||||
ufw allow 22/tcp comment 'SSH'
|
||||
ufw allow 80/tcp comment 'HTTP (redirect to HTTPS)'
|
||||
ufw allow 443/tcp comment 'HTTPS'
|
||||
# Postgres - ТОЛЬКО localhost, никаких ufw allow 5432!
|
||||
ufw --force enable
|
||||
|
||||
log "Шаг 3: ✓"
|
||||
}
|
||||
|
||||
# ============================================================
|
||||
# Шаг 4: PostgreSQL 16 + pgvector
|
||||
# ============================================================
|
||||
step_postgres() {
|
||||
log "Шаг 4: PostgreSQL $POSTGRES_VERSION + pgvector"
|
||||
|
||||
# Репозиторий PGDG
|
||||
if [[ ! -f /etc/apt/sources.list.d/pgdg.list ]]; then
|
||||
sh -c 'echo "deb https://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list'
|
||||
wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | apt-key add -
|
||||
apt-get update -qq
|
||||
fi
|
||||
|
||||
apt-get install -y -qq \
|
||||
postgresql-$POSTGRES_VERSION \
|
||||
postgresql-contrib-$POSTGRES_VERSION \
|
||||
postgresql-$POSTGRES_VERSION-pgvector \
|
||||
postgresql-client-$POSTGRES_VERSION
|
||||
|
||||
# Тюнинг postgresql.conf под объём RAM
|
||||
local pg_conf="/etc/postgresql/$POSTGRES_VERSION/main/postgresql.conf"
|
||||
local shared_buffers=$((TOTAL_RAM_GB * 1024 / 4)) # 25% от RAM в MB
|
||||
local effective_cache=$((TOTAL_RAM_GB * 1024 / 2)) # 50% от RAM в MB
|
||||
|
||||
cat > /etc/postgresql/$POSTGRES_VERSION/main/conf.d/zbrain.conf <<EOF
|
||||
# ZBrain Postgres tuning
|
||||
# Сгенерировано bootstrap-vm.sh для VM ${TOTAL_RAM_GB}GB RAM
|
||||
|
||||
# Память
|
||||
shared_buffers = ${shared_buffers}MB
|
||||
effective_cache_size = ${effective_cache}MB
|
||||
maintenance_work_mem = 512MB
|
||||
work_mem = 32MB
|
||||
|
||||
# Соединения
|
||||
max_connections = 100
|
||||
listen_addresses = 'localhost'
|
||||
|
||||
# WAL
|
||||
wal_buffers = 16MB
|
||||
checkpoint_completion_target = 0.9
|
||||
min_wal_size = 1GB
|
||||
max_wal_size = 4GB
|
||||
|
||||
# Параллелизм (для HNSW index build + сложных запросов)
|
||||
max_parallel_workers_per_gather = 2
|
||||
max_parallel_maintenance_workers = 2
|
||||
|
||||
# SSD
|
||||
random_page_cost = 1.1
|
||||
effective_io_concurrency = 200
|
||||
|
||||
# Логи
|
||||
log_min_duration_statement = 1000
|
||||
log_checkpoints = on
|
||||
log_connections = on
|
||||
log_disconnections = on
|
||||
log_lock_waits = on
|
||||
log_temp_files = 0
|
||||
log_line_prefix = '%t [%p] %u@%d '
|
||||
EOF
|
||||
|
||||
# pg_hba.conf - только localhost
|
||||
cat > /etc/postgresql/$POSTGRES_VERSION/main/pg_hba.conf <<EOF
|
||||
# ZBrain pg_hba.conf
|
||||
local all postgres peer
|
||||
local all all scram-sha-256
|
||||
host all all 127.0.0.1/32 scram-sha-256
|
||||
host all all ::1/128 scram-sha-256
|
||||
EOF
|
||||
|
||||
systemctl enable postgresql
|
||||
systemctl restart postgresql
|
||||
|
||||
# Создание главной БД brainhub и расширений
|
||||
sudo -u postgres psql -c "CREATE DATABASE brainhub;" 2>/dev/null || log "БД brainhub уже существует"
|
||||
sudo -u postgres psql -d brainhub -c "CREATE EXTENSION IF NOT EXISTS vector;"
|
||||
sudo -u postgres psql -d brainhub -c "CREATE EXTENSION IF NOT EXISTS pg_trgm;"
|
||||
sudo -u postgres psql -d brainhub -c "CREATE EXTENSION IF NOT EXISTS pgcrypto;"
|
||||
|
||||
log "Шаг 4: ✓ Postgres работает, BD brainhub готова"
|
||||
log " ВАЖНО: создай пароли для пользователей postgres и brainhub:"
|
||||
log " sudo -u postgres psql"
|
||||
log " ALTER USER postgres PASSWORD '...';"
|
||||
log " CREATE USER brainhub WITH PASSWORD '...';"
|
||||
log " GRANT ALL PRIVILEGES ON DATABASE brainhub TO brainhub;"
|
||||
}
|
||||
|
||||
# ============================================================
|
||||
# Шаг 5: Node.js 22 LTS + Bun
|
||||
# ============================================================
|
||||
step_node_bun() {
|
||||
log "Шаг 5: Node.js $NODE_VERSION LTS + Bun"
|
||||
|
||||
# Node через NodeSource
|
||||
if ! command -v node &>/dev/null || [[ "$(node -v)" != v${NODE_VERSION}* ]]; then
|
||||
curl -fsSL https://deb.nodesource.com/setup_${NODE_VERSION}.x | bash -
|
||||
apt-get install -y -qq nodejs
|
||||
fi
|
||||
|
||||
# Bun (для gbrain)
|
||||
if ! sudo -u "$ZBRAIN_USER" bash -c 'command -v bun' &>/dev/null; then
|
||||
sudo -u "$ZBRAIN_USER" bash -c 'curl -fsSL https://bun.sh/install | bash'
|
||||
# Добавим в PATH
|
||||
sudo -u "$ZBRAIN_USER" bash -c 'echo "export PATH=\$HOME/.bun/bin:\$PATH" >> ~/.bashrc'
|
||||
fi
|
||||
|
||||
log " Node: $(node -v)"
|
||||
log " npm: $(npm -v)"
|
||||
log "Шаг 5: ✓"
|
||||
}
|
||||
|
||||
# ============================================================
|
||||
# Шаг 6: Docker + Compose
|
||||
# ============================================================
|
||||
step_docker() {
|
||||
log "Шаг 6: Docker"
|
||||
|
||||
if ! command -v docker &>/dev/null; then
|
||||
# Официальный репо Docker (не snap!)
|
||||
install -m 0755 -d /etc/apt/keyrings
|
||||
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg
|
||||
chmod a+r /etc/apt/keyrings/docker.gpg
|
||||
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" > /etc/apt/sources.list.d/docker.list
|
||||
apt-get update -qq
|
||||
apt-get install -y -qq docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
|
||||
fi
|
||||
|
||||
# zbrain в группу docker
|
||||
usermod -aG docker "$ZBRAIN_USER"
|
||||
|
||||
systemctl enable docker
|
||||
systemctl start docker
|
||||
|
||||
log " Docker: $(docker --version)"
|
||||
log " Compose: $(docker compose version)"
|
||||
log "Шаг 6: ✓"
|
||||
}
|
||||
|
||||
# ============================================================
|
||||
# Шаг 7: gbrain (клонирование из mirror, установка зависимостей)
|
||||
# ============================================================
|
||||
step_gbrain() {
|
||||
log "Шаг 7: gbrain (из $GBRAIN_REPO)"
|
||||
|
||||
local gbrain_dir="$ZBRAIN_DATA_DIR/gbrain"
|
||||
|
||||
if [[ ! -d "$gbrain_dir" ]]; then
|
||||
sudo -u "$ZBRAIN_USER" git clone "$GBRAIN_REPO" "$gbrain_dir"
|
||||
else
|
||||
log " gbrain уже склонирован в $gbrain_dir"
|
||||
sudo -u "$ZBRAIN_USER" bash -c "cd '$gbrain_dir' && git fetch"
|
||||
fi
|
||||
|
||||
sudo -u "$ZBRAIN_USER" bash -c "cd '$gbrain_dir' && git checkout '$GBRAIN_VERSION'"
|
||||
|
||||
# Установка зависимостей через bun (используем FNA-прокси)
|
||||
log " Устанавливаю зависимости gbrain (через FNA proxy)..."
|
||||
sudo -u "$ZBRAIN_USER" bash -c "
|
||||
export HTTPS_PROXY='$FNA_PROXY'
|
||||
export HTTP_PROXY='$FNA_PROXY'
|
||||
export PATH=\$HOME/.bun/bin:\$PATH
|
||||
cd '$gbrain_dir' && bun install
|
||||
"
|
||||
|
||||
log "Шаг 7: ✓"
|
||||
}
|
||||
|
||||
# ============================================================
|
||||
# Шаг 8: Конфиг-файлы и шаблоны
|
||||
# ============================================================
|
||||
step_config_templates() {
|
||||
log "Шаг 8: Шаблоны конфигов"
|
||||
|
||||
# .env шаблон
|
||||
cat > "$ZBRAIN_CONFIG_DIR/env.example" <<'EOF'
|
||||
# ZBrain Environment Configuration
|
||||
# Скопируй в .env и заполни значения
|
||||
|
||||
# === База данных ===
|
||||
DATABASE_URL=postgresql://brainhub:CHANGE_ME@localhost:5432/brainhub
|
||||
POSTGRES_ADMIN_URL=postgresql://postgres:CHANGE_ME@localhost:5432/postgres
|
||||
|
||||
# === FNA Proxy (для исходящих к OpenAI/Anthropic) ===
|
||||
HTTPS_PROXY=http://client_001:PASSWORD@fna.zetit.ru:3128
|
||||
HTTP_PROXY=http://client_001:PASSWORD@fna.zetit.ru:3128
|
||||
NO_PROXY=localhost,127.0.0.1,.zetit.ru,.zetit.local
|
||||
|
||||
# === API ключи ===
|
||||
OPENAI_API_KEY=sk-...
|
||||
ANTHROPIC_API_KEY=sk-ant-...
|
||||
|
||||
# === ZBrain ===
|
||||
ZBRAIN_BASE_URL=https://brain.zetit.ru
|
||||
ZBRAIN_INTERNAL_URL=http://brain.zetit.local
|
||||
SESSION_SECRET=GENERATE_RANDOM_64_CHARS
|
||||
JWT_SECRET=GENERATE_RANDOM_64_CHARS
|
||||
TOKEN_ENCRYPTION_KEY=GENERATE_RANDOM_32_BYTES_HEX
|
||||
|
||||
# === OAuth ===
|
||||
YANDEX_OAUTH_CLIENT_ID=
|
||||
YANDEX_OAUTH_CLIENT_SECRET=
|
||||
GITHUB_OAUTH_CLIENT_ID=
|
||||
GITHUB_OAUTH_CLIENT_SECRET=
|
||||
|
||||
# === Порты ===
|
||||
BRAINHUB_INTERNAL_PORT=3000
|
||||
BRAINHUB_PUBLIC_PORT=3010
|
||||
GBRAIN_PORT_RANGE_START=3001
|
||||
GBRAIN_PORT_RANGE_END=3099
|
||||
|
||||
# === Логирование ===
|
||||
LOG_LEVEL=info
|
||||
LOG_FILE=/var/log/zbrain/brainhub.log
|
||||
EOF
|
||||
|
||||
chmod 640 "$ZBRAIN_CONFIG_DIR/env.example"
|
||||
chown root:"$ZBRAIN_USER" "$ZBRAIN_CONFIG_DIR/env.example"
|
||||
|
||||
log " Шаблон env: $ZBRAIN_CONFIG_DIR/env.example"
|
||||
log " Не забудь: cp $ZBRAIN_CONFIG_DIR/env.example $ZBRAIN_CONFIG_DIR/.env && chmod 600 $ZBRAIN_CONFIG_DIR/.env"
|
||||
log "Шаг 8: ✓"
|
||||
}
|
||||
|
||||
# ============================================================
|
||||
# Шаг 9: Самопроверка
|
||||
# ============================================================
|
||||
step_verify() {
|
||||
log "Шаг 9: Самопроверка"
|
||||
|
||||
local errors=0
|
||||
|
||||
check() {
|
||||
local name="$1"
|
||||
local cmd="$2"
|
||||
if eval "$cmd" &>/dev/null; then
|
||||
log " ✓ $name"
|
||||
else
|
||||
err " ✗ $name (команда: $cmd)"
|
||||
errors=$((errors+1))
|
||||
fi
|
||||
}
|
||||
|
||||
check "PostgreSQL запущен" "systemctl is-active postgresql"
|
||||
check "Postgres доступен" "sudo -u postgres psql -c 'SELECT version()'"
|
||||
check "pgvector установлен" "sudo -u postgres psql -d brainhub -c 'SELECT * FROM pg_extension WHERE extname = '\''vector'\''' | grep -q vector"
|
||||
check "Node.js установлен" "node --version"
|
||||
check "Docker запущен" "systemctl is-active docker"
|
||||
check "Bun установлен" "sudo -u $ZBRAIN_USER bash -c 'PATH=\$HOME/.bun/bin:\$PATH bun --version'"
|
||||
check "gbrain склонирован" "test -d $ZBRAIN_DATA_DIR/gbrain"
|
||||
check "UFW активен" "ufw status | grep -q 'Status: active'"
|
||||
check "Swap включён" "swapon --show | grep -q swapfile"
|
||||
|
||||
if [[ $errors -gt 0 ]]; then
|
||||
err "Обнаружено $errors ошибок. Проверь логи выше."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log "Шаг 9: ✓ Всё работает"
|
||||
}
|
||||
|
||||
# ============================================================
|
||||
# Итоговый отчёт
|
||||
# ============================================================
|
||||
final_report() {
|
||||
cat <<EOF
|
||||
|
||||
╔════════════════════════════════════════════════════════════╗
|
||||
║ ZBrain VM Bootstrap готов ║
|
||||
╚════════════════════════════════════════════════════════════╝
|
||||
|
||||
Следующие шаги:
|
||||
|
||||
1. Создай пароли для Postgres:
|
||||
sudo -u postgres psql
|
||||
ALTER USER postgres PASSWORD 'strong-password';
|
||||
CREATE USER brainhub WITH PASSWORD 'another-strong-password';
|
||||
GRANT ALL PRIVILEGES ON DATABASE brainhub TO brainhub;
|
||||
\\q
|
||||
|
||||
2. Скопируй и заполни .env:
|
||||
sudo cp $ZBRAIN_CONFIG_DIR/env.example $ZBRAIN_CONFIG_DIR/.env
|
||||
sudo chmod 600 $ZBRAIN_CONFIG_DIR/.env
|
||||
sudo chown $ZBRAIN_USER:$ZBRAIN_USER $ZBRAIN_CONFIG_DIR/.env
|
||||
sudo nano $ZBRAIN_CONFIG_DIR/.env
|
||||
|
||||
3. Создай первый брейн:
|
||||
sudo bash $ZBRAIN_HOME/scripts/create-brain.sh zetit "ZETIT MSP" 3001
|
||||
|
||||
4. (опционально) Склонируй ZBrain код:
|
||||
sudo -u $ZBRAIN_USER git clone git@git.zetit.ru:zuevav/ZBrain.git $ZBRAIN_HOME/app
|
||||
|
||||
5. Запусти первый gbrain instance:
|
||||
см. docs/DEPLOYMENT.md
|
||||
|
||||
Параметры этой VM:
|
||||
RAM: ${TOTAL_RAM_GB} GB
|
||||
Postgres shared_buffers: $((TOTAL_RAM_GB * 1024 / 4)) MB
|
||||
ZBrain home: $ZBRAIN_HOME
|
||||
ZBrain data: $ZBRAIN_DATA_DIR
|
||||
ZBrain config: $ZBRAIN_CONFIG_DIR
|
||||
ZBrain logs: $ZBRAIN_LOG_DIR
|
||||
|
||||
EOF
|
||||
}
|
||||
|
||||
# ============================================================
|
||||
# Main
|
||||
# ============================================================
|
||||
main() {
|
||||
require_root
|
||||
check_ubuntu
|
||||
|
||||
log "Старт ZBrain VM bootstrap"
|
||||
log "Целевая RAM: ${TOTAL_RAM_GB} GB"
|
||||
log "FNA proxy: ${FNA_PROXY%%:*}://***"
|
||||
echo
|
||||
|
||||
step_base_packages
|
||||
step_create_user
|
||||
step_system_tuning
|
||||
step_postgres
|
||||
step_node_bun
|
||||
step_docker
|
||||
step_gbrain
|
||||
step_config_templates
|
||||
step_verify
|
||||
|
||||
final_report
|
||||
}
|
||||
|
||||
main "$@"
|
||||
Executable
+273
@@ -0,0 +1,273 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# Создание нового gbrain instance
|
||||
# ================================
|
||||
# Создаёт изолированную БД + пользователя Postgres + systemd unit
|
||||
# для gbrain serve --http на отдельном порту.
|
||||
#
|
||||
# Использование:
|
||||
# sudo bash scripts/create-brain.sh <name> "<display name>" <port>
|
||||
#
|
||||
# Примеры:
|
||||
# sudo bash scripts/create-brain.sh zetit "ZETIT MSP" 3001
|
||||
# sudo bash scripts/create-brain.sh telerapharma "TeleraPharma" 3002
|
||||
# sudo bash scripts/create-brain.sh personal "Personal" 3003
|
||||
# sudo bash scripts/create-brain.sh community "Smolenskaya 10" 3004
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# ============================================================
|
||||
# Аргументы
|
||||
# ============================================================
|
||||
if [[ $# -lt 3 ]]; then
|
||||
cat <<EOF
|
||||
Usage: $0 <name> "<display-name>" <port>
|
||||
|
||||
Arguments:
|
||||
name slug-имя брейна (только [a-z0-9_], <=32 символов)
|
||||
display-name человекочитаемое имя в кавычках
|
||||
port TCP-порт для gbrain serve --http (3001-3099)
|
||||
|
||||
Example:
|
||||
$0 zetit "ZETIT MSP" 3001
|
||||
EOF
|
||||
exit 1
|
||||
fi
|
||||
|
||||
BRAIN_NAME="$1"
|
||||
BRAIN_DISPLAY="$2"
|
||||
BRAIN_PORT="$3"
|
||||
|
||||
# Валидация
|
||||
if [[ ! "$BRAIN_NAME" =~ ^[a-z0-9_]{1,32}$ ]]; then
|
||||
echo "ERROR: name должно содержать только [a-z0-9_] и быть не длиннее 32 символов" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ ! "$BRAIN_PORT" =~ ^[0-9]+$ ]] || [[ $BRAIN_PORT -lt 3001 ]] || [[ $BRAIN_PORT -gt 3099 ]]; then
|
||||
echo "ERROR: port должен быть числом в диапазоне 3001-3099" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# ============================================================
|
||||
# Константы
|
||||
# ============================================================
|
||||
ZBRAIN_USER="zbrain"
|
||||
ZBRAIN_DATA_DIR="/var/lib/zbrain"
|
||||
ZBRAIN_CONFIG_DIR="/etc/zbrain"
|
||||
GBRAIN_DIR="$ZBRAIN_DATA_DIR/gbrain"
|
||||
|
||||
DB_NAME="gbrain_${BRAIN_NAME}"
|
||||
DB_USER="gbrain_${BRAIN_NAME}"
|
||||
SYSTEMD_UNIT="zbrain-gbrain-${BRAIN_NAME}"
|
||||
BRAIN_HOME="$ZBRAIN_DATA_DIR/brains/$BRAIN_NAME"
|
||||
|
||||
log() { echo -e "\033[1;34m[$(date +'%H:%M:%S')] $*\033[0m"; }
|
||||
err() { echo -e "\033[1;31m[ERROR] $*\033[0m" >&2; }
|
||||
|
||||
# ============================================================
|
||||
# Проверки
|
||||
# ============================================================
|
||||
[[ $EUID -eq 0 ]] || { err "Требуется sudo"; exit 1; }
|
||||
[[ -d "$GBRAIN_DIR" ]] || { err "gbrain не установлен. Запусти сначала bootstrap-vm.sh"; exit 1; }
|
||||
[[ -f "$ZBRAIN_CONFIG_DIR/.env" ]] || { err "$ZBRAIN_CONFIG_DIR/.env не существует"; exit 1; }
|
||||
|
||||
# Проверка занятости порта
|
||||
if ss -tln | grep -q ":${BRAIN_PORT} "; then
|
||||
err "Порт $BRAIN_PORT уже занят"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Проверка существования
|
||||
if sudo -u postgres psql -lqt | cut -d \| -f 1 | grep -qw "$DB_NAME"; then
|
||||
err "БД $DB_NAME уже существует"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# ============================================================
|
||||
# Создание Postgres пользователя и БД
|
||||
# ============================================================
|
||||
log "Создаю Postgres пользователя $DB_USER и БД $DB_NAME"
|
||||
|
||||
DB_PASSWORD=$(openssl rand -hex 24)
|
||||
|
||||
sudo -u postgres psql <<EOF
|
||||
CREATE USER ${DB_USER} WITH PASSWORD '${DB_PASSWORD}';
|
||||
CREATE DATABASE ${DB_NAME} OWNER ${DB_USER};
|
||||
GRANT ALL PRIVILEGES ON DATABASE ${DB_NAME} TO ${DB_USER};
|
||||
EOF
|
||||
|
||||
sudo -u postgres psql -d "$DB_NAME" <<EOF
|
||||
CREATE EXTENSION IF NOT EXISTS vector;
|
||||
CREATE EXTENSION IF NOT EXISTS pg_trgm;
|
||||
CREATE EXTENSION IF NOT EXISTS pgcrypto;
|
||||
GRANT ALL ON SCHEMA public TO ${DB_USER};
|
||||
EOF
|
||||
|
||||
log "✓ БД создана"
|
||||
|
||||
# ============================================================
|
||||
# Brain home directory + конфиг
|
||||
# ============================================================
|
||||
log "Создаю $BRAIN_HOME"
|
||||
mkdir -p "$BRAIN_HOME"
|
||||
chown -R "$ZBRAIN_USER:$ZBRAIN_USER" "$BRAIN_HOME"
|
||||
|
||||
# gbrain config
|
||||
cat > "$BRAIN_HOME/config.json" <<EOF
|
||||
{
|
||||
"name": "${BRAIN_NAME}",
|
||||
"display_name": "${BRAIN_DISPLAY}",
|
||||
"database_url": "postgresql://${DB_USER}:${DB_PASSWORD}@localhost:5432/${DB_NAME}",
|
||||
"port": ${BRAIN_PORT},
|
||||
"data_dir": "${BRAIN_HOME}/data"
|
||||
}
|
||||
EOF
|
||||
chown "$ZBRAIN_USER:$ZBRAIN_USER" "$BRAIN_HOME/config.json"
|
||||
chmod 600 "$BRAIN_HOME/config.json"
|
||||
|
||||
mkdir -p "$BRAIN_HOME/data"
|
||||
chown -R "$ZBRAIN_USER:$ZBRAIN_USER" "$BRAIN_HOME/data"
|
||||
|
||||
log "✓ Конфиг $BRAIN_HOME/config.json"
|
||||
|
||||
# ============================================================
|
||||
# Инициализация gbrain схемы
|
||||
# ============================================================
|
||||
log "Инициализирую gbrain схему"
|
||||
|
||||
# Загружаем .env для FNA proxy + OpenAI/Anthropic ключей
|
||||
set -a
|
||||
source "$ZBRAIN_CONFIG_DIR/.env"
|
||||
set +a
|
||||
|
||||
sudo -u "$ZBRAIN_USER" bash -c "
|
||||
export PATH=\$HOME/.bun/bin:\$PATH
|
||||
export HTTPS_PROXY='${HTTPS_PROXY}'
|
||||
export HTTP_PROXY='${HTTP_PROXY}'
|
||||
export OPENAI_API_KEY='${OPENAI_API_KEY}'
|
||||
export ANTHROPIC_API_KEY='${ANTHROPIC_API_KEY}'
|
||||
cd '$GBRAIN_DIR' && \
|
||||
bun run gbrain init --url 'postgresql://${DB_USER}:${DB_PASSWORD}@localhost:5432/${DB_NAME}' --yes
|
||||
"
|
||||
|
||||
log "✓ Схема инициализирована"
|
||||
|
||||
# ============================================================
|
||||
# Systemd unit
|
||||
# ============================================================
|
||||
log "Создаю systemd unit $SYSTEMD_UNIT"
|
||||
|
||||
cat > "/etc/systemd/system/${SYSTEMD_UNIT}.service" <<EOF
|
||||
[Unit]
|
||||
Description=ZBrain gbrain instance: ${BRAIN_DISPLAY}
|
||||
Documentation=https://git.zetit.ru/zuevav/ZBrain
|
||||
After=postgresql.service network-online.target
|
||||
Wants=network-online.target
|
||||
Requires=postgresql.service
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=${ZBRAIN_USER}
|
||||
Group=${ZBRAIN_USER}
|
||||
WorkingDirectory=${GBRAIN_DIR}
|
||||
|
||||
# Окружение
|
||||
Environment="PATH=/home/${ZBRAIN_USER}/.bun/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
|
||||
Environment="DATABASE_URL=postgresql://${DB_USER}:${DB_PASSWORD}@localhost:5432/${DB_NAME}"
|
||||
Environment="GBRAIN_DATA_DIR=${BRAIN_HOME}/data"
|
||||
EnvironmentFile=${ZBRAIN_CONFIG_DIR}/.env
|
||||
|
||||
# Запуск gbrain в HTTP MCP режиме
|
||||
ExecStart=/home/${ZBRAIN_USER}/.bun/bin/bun run gbrain serve --http --port ${BRAIN_PORT} --bind 127.0.0.1
|
||||
|
||||
# Restart policy
|
||||
Restart=on-failure
|
||||
RestartSec=10s
|
||||
StartLimitInterval=300
|
||||
StartLimitBurst=5
|
||||
|
||||
# Security hardening
|
||||
NoNewPrivileges=true
|
||||
PrivateTmp=true
|
||||
ProtectSystem=strict
|
||||
ProtectHome=true
|
||||
ReadWritePaths=${BRAIN_HOME} /var/log/zbrain
|
||||
ProtectKernelTunables=true
|
||||
ProtectKernelModules=true
|
||||
ProtectControlGroups=true
|
||||
RestrictNamespaces=true
|
||||
RestrictRealtime=true
|
||||
LockPersonality=true
|
||||
|
||||
# Resource limits
|
||||
LimitNOFILE=65536
|
||||
MemoryMax=2G
|
||||
CPUQuota=200%
|
||||
|
||||
# Логи
|
||||
StandardOutput=append:/var/log/zbrain/${BRAIN_NAME}.log
|
||||
StandardError=append:/var/log/zbrain/${BRAIN_NAME}.error.log
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOF
|
||||
|
||||
systemctl daemon-reload
|
||||
systemctl enable "$SYSTEMD_UNIT"
|
||||
systemctl start "$SYSTEMD_UNIT"
|
||||
|
||||
sleep 3
|
||||
|
||||
if systemctl is-active --quiet "$SYSTEMD_UNIT"; then
|
||||
log "✓ Сервис запущен: $SYSTEMD_UNIT"
|
||||
else
|
||||
err "Сервис не стартовал. Логи:"
|
||||
journalctl -u "$SYSTEMD_UNIT" -n 20 --no-pager
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# ============================================================
|
||||
# Проверка health
|
||||
# ============================================================
|
||||
log "Проверяю доступность на localhost:$BRAIN_PORT"
|
||||
|
||||
if curl -sf "http://localhost:${BRAIN_PORT}/health" >/dev/null 2>&1; then
|
||||
log "✓ Health check OK"
|
||||
else
|
||||
log "⚠ Health endpoint недоступен (возможно gbrain ещё стартует или endpoint называется иначе)"
|
||||
fi
|
||||
|
||||
# ============================================================
|
||||
# Финальный отчёт
|
||||
# ============================================================
|
||||
cat <<EOF
|
||||
|
||||
╔════════════════════════════════════════════════════════════╗
|
||||
║ Брейн '${BRAIN_NAME}' создан
|
||||
╚════════════════════════════════════════════════════════════╝
|
||||
|
||||
Display name: ${BRAIN_DISPLAY}
|
||||
Postgres DB: ${DB_NAME}
|
||||
Postgres user: ${DB_USER}
|
||||
Port: ${BRAIN_PORT}
|
||||
Home: ${BRAIN_HOME}
|
||||
Systemd unit: ${SYSTEMD_UNIT}
|
||||
MCP endpoint: http://localhost:${BRAIN_PORT}/mcp
|
||||
|
||||
Полезные команды:
|
||||
systemctl status ${SYSTEMD_UNIT}
|
||||
journalctl -u ${SYSTEMD_UNIT} -f
|
||||
tail -f /var/log/zbrain/${BRAIN_NAME}.log
|
||||
|
||||
Импорт данных:
|
||||
sudo -u ${ZBRAIN_USER} bash -c '
|
||||
export PATH=\$HOME/.bun/bin:\$PATH
|
||||
export DATABASE_URL="postgresql://${DB_USER}:***@localhost:5432/${DB_NAME}"
|
||||
cd ${GBRAIN_DIR} && bun run gbrain import /path/to/markdown/
|
||||
'
|
||||
|
||||
DB password сохранён в:
|
||||
${BRAIN_HOME}/config.json (доступ только root и ${ZBRAIN_USER})
|
||||
|
||||
EOF
|
||||
Executable
+153
@@ -0,0 +1,153 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# ZBrain Restore
|
||||
# ===============
|
||||
# Восстанавливает ZBrain из бэкапа созданного backup.sh.
|
||||
#
|
||||
# Использование:
|
||||
# sudo bash scripts/restore.sh <backup-timestamp>
|
||||
#
|
||||
# Где <backup-timestamp> - имя папки в BACKUP_DIR (например, 20260520-031500)
|
||||
#
|
||||
# ВАЖНО: для расшифровки нужен приватный age ключ.
|
||||
# Положи его на VM перед запуском restore (например, /tmp/backup.age).
|
||||
# Скрипт спросит путь к ключу.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# ============================================================
|
||||
# Конфигурация
|
||||
# ============================================================
|
||||
BACKUP_DIR="${BACKUP_DIR:-/var/backups/zbrain}"
|
||||
LOG_FILE="/var/log/zbrain/restore.log"
|
||||
|
||||
log() {
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOG_FILE"
|
||||
}
|
||||
|
||||
err() {
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] ERROR: $*" | tee -a "$LOG_FILE" >&2
|
||||
}
|
||||
|
||||
# ============================================================
|
||||
# Аргументы
|
||||
# ============================================================
|
||||
if [[ $# -lt 1 ]]; then
|
||||
echo "Usage: $0 <backup-timestamp>"
|
||||
echo ""
|
||||
echo "Available backups:"
|
||||
ls -1 "$BACKUP_DIR" 2>/dev/null | grep -E '^[0-9]{8}-[0-9]{6}$' | sort -r | head -10
|
||||
exit 1
|
||||
fi
|
||||
|
||||
TIMESTAMP="$1"
|
||||
BACKUP_PATH="$BACKUP_DIR/$TIMESTAMP"
|
||||
|
||||
[[ -d "$BACKUP_PATH" ]] || { err "Бэкап не найден: $BACKUP_PATH"; exit 1; }
|
||||
[[ $EUID -eq 0 ]] || { err "Требуется sudo"; exit 1; }
|
||||
|
||||
# ============================================================
|
||||
# Age ключ для расшифровки
|
||||
# ============================================================
|
||||
read -p "Путь к age приватному ключу: " AGE_KEY_PATH
|
||||
[[ -f "$AGE_KEY_PATH" ]] || { err "Файл не найден: $AGE_KEY_PATH"; exit 1; }
|
||||
|
||||
# Тест ключа
|
||||
if ! age -d -i "$AGE_KEY_PATH" "$BACKUP_PATH/_globals.sql.age" > /dev/null 2>&1; then
|
||||
err "Не могу расшифровать с этим ключом"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# ============================================================
|
||||
# Подтверждение
|
||||
# ============================================================
|
||||
cat <<EOF
|
||||
|
||||
╔════════════════════════════════════════════════════════════╗
|
||||
║ ВНИМАНИЕ ║
|
||||
║ ║
|
||||
║ Восстановление перезапишет все существующие БД ZBrain! ║
|
||||
║ ║
|
||||
║ Бэкап: $TIMESTAMP ║
|
||||
║ Файлы: ║
|
||||
$(ls "$BACKUP_PATH" | sed 's/^/║ /' | awk '{printf "%-60s║\n", $0}')
|
||||
║ ║
|
||||
╚════════════════════════════════════════════════════════════╝
|
||||
|
||||
EOF
|
||||
|
||||
read -p "Введите 'YES' для продолжения: " CONFIRM
|
||||
[[ "$CONFIRM" == "YES" ]] || { log "Отменено пользователем"; exit 0; }
|
||||
|
||||
log "=== Начинаю restore из $TIMESTAMP ==="
|
||||
|
||||
# ============================================================
|
||||
# Восстановление глобальных объектов
|
||||
# ============================================================
|
||||
log "Восстанавливаю глобальные Postgres объекты..."
|
||||
age -d -i "$AGE_KEY_PATH" -o /tmp/_globals.sql "$BACKUP_PATH/_globals.sql.age"
|
||||
sudo -u postgres psql -f /tmp/_globals.sql 2>&1 | tee -a "$LOG_FILE" | grep -v "already exists" || true
|
||||
rm /tmp/_globals.sql
|
||||
log "✓ Globals"
|
||||
|
||||
# ============================================================
|
||||
# Восстановление БД
|
||||
# ============================================================
|
||||
for encrypted_dump in "$BACKUP_PATH"/*.sql.dump.age; do
|
||||
[[ -f "$encrypted_dump" ]] || continue
|
||||
db_name=$(basename "$encrypted_dump" .sql.dump.age)
|
||||
[[ "$db_name" == "_globals" ]] && continue
|
||||
|
||||
log "Восстанавливаю $db_name..."
|
||||
|
||||
# Дропаем существующую БД (если есть)
|
||||
sudo -u postgres dropdb --if-exists "$db_name"
|
||||
|
||||
# Создаём пустую
|
||||
sudo -u postgres createdb "$db_name"
|
||||
|
||||
# Расшифровываем dump
|
||||
dump_file="/tmp/${db_name}.dump"
|
||||
age -d -i "$AGE_KEY_PATH" -o "$dump_file" "$encrypted_dump"
|
||||
|
||||
# Восстанавливаем
|
||||
sudo -u postgres pg_restore --dbname="$db_name" --no-owner --role=postgres "$dump_file" 2>&1 | tee -a "$LOG_FILE" || true
|
||||
|
||||
rm "$dump_file"
|
||||
log "✓ $db_name"
|
||||
done
|
||||
|
||||
# ============================================================
|
||||
# Конфиги (опционально)
|
||||
# ============================================================
|
||||
read -p "Восстановить конфиги? (включая /etc/zbrain) [y/N]: " RESTORE_CONFIG
|
||||
if [[ "$RESTORE_CONFIG" =~ ^[Yy]$ ]]; then
|
||||
log "Восстанавливаю конфиги..."
|
||||
age -d -i "$AGE_KEY_PATH" -o /tmp/config.tar.gz "$BACKUP_PATH/config.tar.gz.age"
|
||||
tar xzf /tmp/config.tar.gz -C /
|
||||
rm /tmp/config.tar.gz
|
||||
log "✓ Конфиги"
|
||||
|
||||
if [[ -f "$BACKUP_PATH/brains-meta.tar.gz.age" ]]; then
|
||||
log "Восстанавливаю метаданные брейнов..."
|
||||
age -d -i "$AGE_KEY_PATH" -o /tmp/brains-meta.tar.gz "$BACKUP_PATH/brains-meta.tar.gz.age"
|
||||
tar xzf /tmp/brains-meta.tar.gz -C /var/lib/zbrain/
|
||||
rm /tmp/brains-meta.tar.gz
|
||||
log "✓ Brain configs"
|
||||
fi
|
||||
fi
|
||||
|
||||
# ============================================================
|
||||
# Финал
|
||||
# ============================================================
|
||||
log "=== Restore завершён ==="
|
||||
log ""
|
||||
log "Следующие шаги:"
|
||||
log " 1. Перезапусти системные сервисы:"
|
||||
log " sudo systemctl restart 'zbrain-gbrain-*.service'"
|
||||
log " 2. Перезапусти brainhub:"
|
||||
log " cd /opt/zbrain/deploy/docker && docker compose restart"
|
||||
log " 3. Проверь health endpoint каждого сервиса"
|
||||
log ""
|
||||
log "ВАЖНО: удали age приватный ключ с этой VM после restore!"
|
||||
log " shred -u $AGE_KEY_PATH"
|
||||
Reference in New Issue
Block a user