Files
zuevav f4bca8449e main
2026-05-20 19:33:02 +03:00

274 lines
9.0 KiB
Bash
Executable File

#!/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