ваше сообщение коммита

This commit is contained in:
2025-07-06 00:36:20 +03:00
parent 6182c2ced1
commit af3771ad8c
32 changed files with 3935 additions and 3166 deletions

247
scripts/README.md Normal file
View File

@@ -0,0 +1,247 @@
# Скрипты миграции приложения
Этот набор скриптов позволяет полностью перенести приложение с базами данных между различными провайдерами.
## Файлы
- `migrate-app.sh` - Основной скрипт миграции приложения
- `update-dns.sh` - Скрипт обновления DNS записей
- `README.md` - Эта инструкция
## Что мигрируется
Скрипт переносит **всё приложение**:
### ✅ **Контейнеры:**
- Frontend (Vue.js)
- Backend (Node.js)
- PostgreSQL (база данных)
- Ollama (AI модели)
- Vector Search
### ✅ **Данные:**
- PostgreSQL база данных (полный дамп)
- Ollama модели (список для восстановления)
- Переменные окружения
- Конфигурационные файлы
### ❌ **Исключается:**
- `node_modules/` (пересобирается)
- `.git/` (не нужен на продакшене)
- `*.log` (логи)
- `temp/` (временные файлы)
- `sessions/` (сессии)
## Требования
### На исходном сервере:
- Docker и Docker Compose
- SSH доступ
- Достаточно места для бэкапа
### На целевом сервере:
- Docker и Docker Compose
- SSH доступ
- Достаточно места для приложения
### На локальной машине:
- SSH ключи настроены для обоих серверов
- `dig` (для проверки DNS)
- `curl` (для API запросов)
- `jq` (для работы с JSON)
## Использование
### 1. Миграция приложения
```bash
# Сделать скрипт исполняемым
chmod +x scripts/migrate-app.sh
# Запустить миграцию
./scripts/migrate-app.sh source-server target-server app-path
```
**Пример:**
```bash
./scripts/migrate-app.sh user@hostland-server.com user@aws-server.com /home/user/dapp
```
### 2. Обновление DNS записей
```bash
# Сделать скрипт исполняемым
chmod +x scripts/update-dns.sh
# Запустить обновление DNS
./scripts/update-dns.sh domain new-server-ip
```
**Пример:**
```bash
./scripts/update-dns.sh mydapp.site 123.456.789.10
```
## Автоматическое обновление DNS
Скрипт поддерживает автоматическое обновление DNS через API:
### Cloudflare
```bash
export CLOUDFLARE_API_TOKEN="your-api-token"
./scripts/update-dns.sh mydapp.site 123.456.789.10
```
### GoDaddy
```bash
export GODADDY_API_KEY="your-api-key"
export GODADDY_API_SECRET="your-api-secret"
./scripts/update-dns.sh mydapp.site 123.456.789.10
```
### Namecheap
```bash
export NAMECHEAP_API_USER="your-username"
export NAMECHEAP_API_KEY="your-api-key"
./scripts/update-dns.sh mydapp.site 123.456.789.10
```
## Процесс миграции
### 1. Подготовка
- Проверка подключений к серверам
- Проверка установки Docker
- Создание временных файлов
### 2. Создание бэкапа
- Остановка приложения
- Создание дампа PostgreSQL
- Сохранение списка Ollama моделей
- Создание полного архива
### 3. Восстановление
- Копирование архива на целевой сервер
- Распаковка файлов
- Восстановление PostgreSQL
- Восстановление Ollama моделей
- Запуск приложения
### 4. Проверка
- Проверка статуса контейнеров
- Проверка логов
- Проверка доступности портов
- Очистка временных файлов
## Примеры использования
### Миграция с Hostland на AWS
```bash
# 1. Мигрируем приложение
./scripts/migrate-app.sh user@hostland.com ec2-user@aws-server.com /home/ec2-user/dapp
# 2. Получаем новый IP
NEW_IP=$(ssh ec2-user@aws-server.com "curl -s ifconfig.me")
# 3. Обновляем DNS
./scripts/update-dns.sh mydapp.site $NEW_IP
```
### Миграция с AWS на DigitalOcean
```bash
# 1. Мигрируем приложение
./scripts/migrate-app.sh ec2-user@aws-server.com root@digitalocean.com /root/dapp
# 2. Получаем новый IP
NEW_IP=$(ssh root@digitalocean.com "curl -s ifconfig.me")
# 3. Обновляем DNS
./scripts/update-dns.sh mydapp.site $NEW_IP
```
## Мониторинг процесса
Скрипты выводят подробную информацию о процессе:
- `[INFO]` - Информационные сообщения
- `[SUCCESS]` - Успешные операции
- `[WARNING]` - Предупреждения
- `[ERROR]` - Ошибки
## Устранение неполадок
### Ошибка подключения SSH
```bash
# Проверьте SSH ключи
ssh -T user@server.com
# Добавьте ключ в ssh-agent
ssh-add ~/.ssh/id_rsa
```
### Ошибка Docker
```bash
# Установите Docker на сервере
curl -fsSL https://get.docker.com | sh
sudo usermod -aG docker $USER
```
### Ошибка DNS
```bash
# Проверьте DNS записи
dig mydapp.site
# Обновите вручную в панели управления доменом
```
### Ошибка восстановления PostgreSQL
```bash
# Проверьте логи PostgreSQL
docker compose logs postgres
# Создайте базу вручную
docker compose exec postgres createdb -U dapp_user dapp_db
```
## Безопасность
### SSH ключи
- Используйте SSH ключи вместо паролей
- Ограничьте доступ к серверам
- Регулярно обновляйте ключи
### API ключи
- Храните API ключи в переменных окружения
- Не коммитьте ключи в репозиторий
- Используйте минимальные права доступа
### Данные
- Бэкапы содержат чувствительные данные
- Удаляйте временные файлы после миграции
- Шифруйте бэкапы при необходимости
## Резервное копирование
Рекомендуется создавать резервные копии перед миграцией:
```bash
# Создать бэкап вручную
cd /path/to/app
docker compose down
tar -czf backup-$(date +%Y%m%d).tar.gz .
```
## Поддержка
При возникновении проблем:
1. Проверьте логи скриптов
2. Проверьте статус контейнеров
3. Проверьте подключения к серверам
4. Проверьте DNS записи
5. Обратитесь к документации провайдера
## Лицензия
Эти скрипты распространяются под MIT лицензией.

245
scripts/migrate-app.sh Executable file
View File

@@ -0,0 +1,245 @@
#!/bin/bash
# Скрипт для полной миграции приложения между провайдерами
# Использование: ./migrate-app.sh source-server target-server app-path
set -e # Остановка при ошибке
# Цвета для вывода
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Функции для логирования
log_info() {
echo -e "${BLUE}[INFO]${NC} $1"
}
log_success() {
echo -e "${GREEN}[SUCCESS]${NC} $1"
}
log_warning() {
echo -e "${YELLOW}[WARNING]${NC} $1"
}
log_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
# Проверка аргументов
if [ $# -ne 3 ]; then
echo "Использование: $0 <source-server> <target-server> <app-path>"
echo "Пример: $0 user@hostland-server.com user@aws-server.com /home/user/dapp"
exit 1
fi
SOURCE_SERVER=$1
TARGET_SERVER=$2
APP_PATH=$3
BACKUP_NAME="app-migration-$(date +%Y%m%d-%H%M%S)"
log_info "Начинаем миграцию приложения"
log_info "Источник: $SOURCE_SERVER:$APP_PATH"
log_info "Цель: $TARGET_SERVER:$APP_PATH"
log_info "Резервная копия: $BACKUP_NAME"
# Функция для проверки подключения к серверу
check_connection() {
local server=$1
log_info "Проверяем подключение к $server..."
if ! ssh -o ConnectTimeout=10 -o BatchMode=yes "$server" "echo 'Connection OK'" 2>/dev/null; then
log_error "Не удалось подключиться к $server"
log_error "Убедитесь, что SSH ключи настроены правильно"
exit 1
fi
log_success "Подключение к $server установлено"
}
# Функция для проверки Docker на сервере
check_docker() {
local server=$1
log_info "Проверяем Docker на $server..."
if ! ssh "$server" "docker --version" >/dev/null 2>&1; then
log_error "Docker не установлен на $server"
log_error "Установите Docker: curl -fsSL https://get.docker.com | sh"
exit 1
fi
log_success "Docker установлен на $server"
}
# Функция для создания полного бэкапа
create_backup() {
local server=$1
local path=$2
local backup_name=$3
log_info "Создаём полный бэкап на $server..."
# Останавливаем приложение
log_info "Останавливаем приложение..."
ssh "$server" "cd $path && docker compose down" || log_warning "Приложение уже остановлено"
# Создаём бэкап базы данных
log_info "Создаём бэкап PostgreSQL..."
ssh "$server" "cd $path && docker compose up -d postgres" || true
sleep 5 # Ждём запуска PostgreSQL
# Бэкап PostgreSQL
ssh "$server" "cd $path && docker compose exec -T postgres pg_dump -U dapp_user dapp_db > postgres-backup.sql" || {
log_warning "Не удалось создать бэкап PostgreSQL, продолжаем без него"
}
# Бэкап Ollama моделей
log_info "Создаём бэкап Ollama моделей..."
ssh "$server" "cd $path && docker compose exec -T ollama ollama list > ollama-models.txt" || log_warning "Ollama не найден"
# Создаём полный архив
log_info "Создаём полный архив приложения..."
ssh "$server" "cd $path && tar -czf $backup_name.tar.gz \
. \
postgres-backup.sql \
ollama-models.txt \
--exclude='node_modules' \
--exclude='.git' \
--exclude='*.log' \
--exclude='temp' \
--exclude='sessions'"
log_success "Бэкап создан: $backup_name.tar.gz"
}
# Функция для восстановления на целевом сервере
restore_backup() {
local server=$1
local path=$2
local backup_name=$3
log_info "Восстанавливаем приложение на $server..."
# Создаём директорию если не существует
ssh "$server" "mkdir -p $path"
# Копируем архив
log_info "Копируем архив на целевой сервер..."
scp "$SOURCE_SERVER:$APP_PATH/$backup_name.tar.gz" "$server:/tmp/"
# Распаковываем
log_info "Распаковываем архив..."
ssh "$server" "cd $path && tar -xzf /tmp/$backup_name.tar.gz"
# Восстанавливаем PostgreSQL
if ssh "$server" "test -f $path/postgres-backup.sql"; then
log_info "Восстанавливаем PostgreSQL..."
ssh "$server" "cd $path && docker compose up -d postgres"
sleep 10 # Ждём запуска PostgreSQL
ssh "$server" "cd $path && docker compose exec -T postgres psql -U dapp_user -d dapp_db < postgres-backup.sql" || {
log_warning "Не удалось восстановить PostgreSQL, создаём новую БД"
ssh "$server" "cd $path && docker compose exec -T postgres createdb -U dapp_user dapp_db 2>/dev/null || true"
}
fi
# Восстанавливаем Ollama модели
if ssh "$server" "test -f $path/ollama-models.txt"; then
log_info "Восстанавливаем Ollama модели..."
ssh "$server" "cd $path && docker compose up -d ollama"
sleep 10 # Ждём запуска Ollama
ssh "$server" "cd $path && cat ollama-models.txt | grep -v 'NAME' | awk '{print \$1}' | xargs -I {} docker compose exec -T ollama ollama pull {}" || {
log_warning "Не удалось восстановить все Ollama модели"
}
fi
# Запускаем приложение
log_info "Запускаем приложение..."
ssh "$server" "cd $path && docker compose up -d"
log_success "Приложение восстановлено на $server"
}
# Функция для проверки статуса приложения
check_app_status() {
local server=$1
local path=$2
log_info "Проверяем статус приложения на $server..."
# Проверяем контейнеры
ssh "$server" "cd $path && docker compose ps"
# Проверяем логи
log_info "Последние логи приложения:"
ssh "$server" "cd $path && docker compose logs --tail=20"
# Проверяем доступность сервисов
log_info "Проверяем доступность сервисов..."
# Получаем IP сервера
SERVER_IP=$(ssh "$server" "curl -s ifconfig.me")
# Проверяем порты
for port in 80 443 8000 5173; do
if ssh "$server" "netstat -tlnp | grep :$port" >/dev/null 2>&1; then
log_success "Порт $port доступен"
else
log_warning "Порт $port недоступен"
fi
done
}
# Функция для очистки временных файлов
cleanup() {
local server=$1
local backup_name=$2
log_info "Очищаем временные файлы..."
ssh "$server" "rm -f $APP_PATH/$backup_name.tar.gz"
ssh "$server" "rm -f $APP_PATH/postgres-backup.sql"
ssh "$server" "rm -f $APP_PATH/ollama-models.txt"
ssh "$server" "rm -f /tmp/$backup_name.tar.gz"
}
# Основной процесс миграции
main() {
log_info "=== НАЧАЛО МИГРАЦИИ ПРИЛОЖЕНИЯ ==="
# Проверяем подключения
check_connection "$SOURCE_SERVER"
check_connection "$TARGET_SERVER"
# Проверяем Docker
check_docker "$SOURCE_SERVER"
check_docker "$TARGET_SERVER"
# Создаём бэкап на исходном сервере
create_backup "$SOURCE_SERVER" "$APP_PATH" "$BACKUP_NAME"
# Восстанавливаем на целевом сервере
restore_backup "$TARGET_SERVER" "$APP_PATH" "$BACKUP_NAME"
# Проверяем статус
check_app_status "$TARGET_SERVER" "$APP_PATH"
# Очищаем временные файлы
cleanup "$SOURCE_SERVER" "$BACKUP_NAME"
cleanup "$TARGET_SERVER" "$BACKUP_NAME"
log_success "=== МИГРАЦИЯ ЗАВЕРШЕНА УСПЕШНО ==="
log_info "Приложение перенесено с $SOURCE_SERVER на $TARGET_SERVER"
log_info "Не забудьте обновить DNS записи на новый IP сервера"
# Показываем новый IP
NEW_IP=$(ssh "$TARGET_SERVER" "curl -s ifconfig.me")
log_info "Новый IP сервера: $NEW_IP"
}
# Обработка ошибок
trap 'log_error "Миграция прервана из-за ошибки"; exit 1' ERR
# Запуск основной функции
main "$@"

250
scripts/update-dns.sh Executable file
View File

@@ -0,0 +1,250 @@
#!/bin/bash
# Скрипт для обновления DNS записей после миграции
# Использование: ./update-dns.sh domain new-server-ip
set -e
# Цвета для вывода
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
log_info() {
echo -e "${BLUE}[INFO]${NC} $1"
}
log_success() {
echo -e "${GREEN}[SUCCESS]${NC} $1"
}
log_warning() {
echo -e "${YELLOW}[WARNING]${NC} $1"
}
log_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
# Проверка аргументов
if [ $# -ne 2 ]; then
echo "Использование: $0 <domain> <new-server-ip>"
echo "Пример: $0 mydapp.site 123.456.789.10"
exit 1
fi
DOMAIN=$1
NEW_IP=$2
log_info "Обновляем DNS записи для домена $DOMAIN на IP $NEW_IP"
# Функция для проверки DNS
check_dns() {
local domain=$1
local expected_ip=$2
log_info "Проверяем DNS записи для $domain..."
# Получаем A запись
local dns_ip=$(dig +short A "$domain" | head -1)
if [ "$dns_ip" = "$expected_ip" ]; then
log_success "DNS A запись корректна: $domain -> $dns_ip"
return 0
else
log_warning "DNS A запись не совпадает: ожидается $expected_ip, получено $dns_ip"
return 1
fi
}
# Функция для проверки доступности сайта
check_site_availability() {
local domain=$1
local max_attempts=30
local attempt=1
log_info "Проверяем доступность сайта $domain..."
while [ $attempt -le $max_attempts ]; do
if curl -s --max-time 10 "https://$domain" >/dev/null 2>&1; then
log_success "Сайт $domain доступен!"
return 0
else
log_info "Попытка $attempt/$max_attempts: сайт пока недоступен..."
sleep 10
attempt=$((attempt + 1))
fi
done
log_error "Сайт $domain недоступен после $max_attempts попыток"
return 1
}
# Функция для автоматического обновления DNS через API
update_dns_api() {
local domain=$1
local new_ip=$2
log_info "Пытаемся автоматически обновить DNS через API..."
# Проверяем различные DNS провайдеров
if [ -n "$CLOUDFLARE_API_TOKEN" ]; then
log_info "Пробуем Cloudflare API..."
update_cloudflare_dns "$domain" "$new_ip"
elif [ -n "$GODADDY_API_KEY" ]; then
log_info "Пробуем GoDaddy API..."
update_godaddy_dns "$domain" "$new_ip"
elif [ -n "$NAMECHEAP_API_KEY" ]; then
log_info "Пробуем Namecheap API..."
update_namecheap_dns "$domain" "$new_ip"
else
log_warning "API ключи не настроены, требуется ручное обновление DNS"
return 1
fi
}
# Функция для обновления DNS через Cloudflare API
update_cloudflare_dns() {
local domain=$1
local new_ip=$2
# Получаем zone ID
local zone_id=$(curl -s -H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
"https://api.cloudflare.com/client/v4/zones?name=$domain" | \
jq -r '.result[0].id')
if [ "$zone_id" = "null" ]; then
log_error "Не удалось найти zone ID для домена $domain"
return 1
fi
# Получаем record ID для A записи
local record_id=$(curl -s -H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
"https://api.cloudflare.com/client/v4/zones/$zone_id/dns_records?type=A&name=$domain" | \
jq -r '.result[0].id')
if [ "$record_id" = "null" ]; then
log_error "Не удалось найти A запись для домена $domain"
return 1
fi
# Обновляем A запись
local response=$(curl -s -X PUT \
-H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
-H "Content-Type: application/json" \
-d "{\"type\":\"A\",\"name\":\"$domain\",\"content\":\"$new_ip\",\"ttl\":1}" \
"https://api.cloudflare.com/client/v4/zones/$zone_id/dns_records/$record_id")
if echo "$response" | jq -e '.success' >/dev/null; then
log_success "DNS обновлён через Cloudflare API"
return 0
else
log_error "Ошибка обновления DNS через Cloudflare API"
return 1
fi
}
# Функция для обновления DNS через GoDaddy API
update_godaddy_dns() {
local domain=$1
local new_ip=$2
local response=$(curl -s -X PUT \
-H "Authorization: sso-key $GODADDY_API_KEY:$GODADDY_API_SECRET" \
-H "Content-Type: application/json" \
-d "[{\"data\":\"$new_ip\",\"ttl\":600}]" \
"https://api.godaddy.com/v1/domains/$domain/records/A/@")
if [ $? -eq 0 ]; then
log_success "DNS обновлён через GoDaddy API"
return 0
else
log_error "Ошибка обновления DNS через GoDaddy API"
return 1
fi
}
# Функция для обновления DNS через Namecheap API
update_namecheap_dns() {
local domain=$1
local new_ip=$2
local response=$(curl -s "https://api.sandbox.namecheap.com/xml.response" \
-d "ApiUser=$NAMECHEAP_API_USER" \
-d "ApiKey=$NAMECHEAP_API_KEY" \
-d "UserName=$NAMECHEAP_API_USER" \
-d "Command=namecheap.domains.dns.setHosts" \
-d "ClientIp=$new_ip" \
-d "SLD=$(echo $domain | cut -d. -f1)" \
-d "TLD=$(echo $domain | cut -d. -f2)" \
-d "HostName1=@" \
-d "RecordType1=A" \
-d "Address1=$new_ip" \
-d "TTL1=600")
if echo "$response" | grep -q "Status=\"OK\""; then
log_success "DNS обновлён через Namecheap API"
return 0
else
log_error "Ошибка обновления DNS через Namecheap API"
return 1
fi
}
# Основная функция
main() {
log_info "=== ОБНОВЛЕНИЕ DNS ЗАПИСЕЙ ==="
# Пытаемся автоматически обновить DNS
if update_dns_api "$DOMAIN" "$NEW_IP"; then
log_success "DNS обновлён автоматически"
else
log_warning "Автоматическое обновление не удалось"
log_info "Требуется ручное обновление DNS записей"
log_info "Домен: $DOMAIN"
log_info "Новый IP: $NEW_IP"
log_info "Тип записи: A"
log_info "TTL: 600 (или минимальное значение)"
fi
# Ждём обновления DNS
log_info "Ждём обновления DNS записей (может занять до 30 минут)..."
local max_wait=30
local wait_count=0
while [ $wait_count -lt $max_wait ]; do
if check_dns "$DOMAIN" "$NEW_IP"; then
log_success "DNS записи обновлены!"
break
else
log_info "Ожидание... ($((wait_count + 1))/$max_wait)"
sleep 60
wait_count=$((wait_count + 1))
fi
done
if [ $wait_count -ge $max_wait ]; then
log_warning "DNS записи не обновились в течение 30 минут"
log_info "Проверьте настройки DNS в панели управления доменом"
fi
# Проверяем доступность сайта
if check_site_availability "$DOMAIN"; then
log_success "=== САЙТ УСПЕШНО МИГРИРОВАН ==="
log_info "Домен: $DOMAIN"
log_info "IP: $NEW_IP"
log_info "Статус: Доступен"
else
log_error "Сайт недоступен после миграции"
log_info "Проверьте:"
log_info "1. DNS записи обновлены"
log_info "2. Приложение запущено на новом сервере"
log_info "3. Файрволы настроены правильно"
fi
}
# Запуск основной функции
main "$@"