optimized script
This commit is contained in:
@@ -1,458 +0,0 @@
|
||||
# Локальная отладка 3dp-manager
|
||||
|
||||
> ⚠️ **Внимание:** Локальный стенд использует учётные данные `admin/admin` **только для разработки**. Production-скрипты (`install.sh`, `update.sh`) автоматически генерируют безопасные пароли.
|
||||
|
||||
## Порты локального стенда
|
||||
|
||||
| Сервис | Порт | Доступ |
|
||||
|--------|------|--------|
|
||||
| **frontend** | `http://localhost:8080` | ✅ Доступен из браузера |
|
||||
| **backend API** | `http://backend:3100/api` | ❌ Скрыт внутри Docker network |
|
||||
| **postgres** | `5432/tcp` | ❌ Скрыт внутри Docker network |
|
||||
|
||||
> 🔒 **Безопасность:** Backend и PostgreSQL не проброшены наружу — доступны только внутри Docker network.
|
||||
|
||||
## Учётные данные (по умолчанию)
|
||||
|
||||
**Локальный стенд (`.env`):**
|
||||
```
|
||||
ADMIN_LOGIN: admin
|
||||
ADMIN_PASSWORD: admin
|
||||
POSTGRES_USER: admin
|
||||
POSTGRES_PASSWORD: admin
|
||||
POSTGRES_DB: 3dp_manager
|
||||
JWT_SECRET: localDevSecretKey12345678901234567890
|
||||
```
|
||||
|
||||
> ⚠️ **Важно:** Для локальной разработки используется `admin/admin`. Production-скрипты автоматически генерируют случайные пароли при установке/обновлении.
|
||||
|
||||
## Запуск/перезапуск
|
||||
|
||||
### Полный запуск (с пересозданием volumes)
|
||||
```powershell
|
||||
# Первый запуск или полная пересборка
|
||||
docker compose -f docker-compose.local.yml --env-file .env up -d --build --force-recreate
|
||||
|
||||
# Если нужно очистить БД и пересоздать volumes
|
||||
docker compose -f docker-compose.local.yml --env-file .env down -v
|
||||
docker compose -f docker-compose.local.yml --env-file .env up -d --build
|
||||
```
|
||||
|
||||
### Частичный перезапуск
|
||||
```powershell
|
||||
# Только frontend (без пересборки)
|
||||
docker compose -f docker-compose.local.yml --env-file .env restart frontend
|
||||
|
||||
# Только backend (без пересборки)
|
||||
docker compose -f docker-compose.local.yml --env-file .env restart backend
|
||||
|
||||
# Frontend с пересборкой
|
||||
docker compose -f docker-compose.local.yml --env-file .env up -d --build --no-deps frontend
|
||||
|
||||
# Backend с пересборкой
|
||||
docker compose -f docker-compose.local.yml --env-file .env up -d --build --no-deps backend
|
||||
```
|
||||
|
||||
### Статус контейнеров
|
||||
```powershell
|
||||
docker compose -f docker-compose.local.yml --env-file .env ps
|
||||
```
|
||||
|
||||
### Просмотр логов
|
||||
```powershell
|
||||
# Backend логи
|
||||
docker compose -f docker-compose.local.yml --env-file .env logs -f backend
|
||||
|
||||
# Frontend логи
|
||||
docker compose -f docker-compose.local.yml --env-file .env logs -f frontend
|
||||
|
||||
# Последние 50 строк
|
||||
docker compose -f docker-compose.local.yml --env-file .env logs --tail 50 backend
|
||||
```
|
||||
|
||||
### Остановка
|
||||
```powershell
|
||||
# Остановка без удаления volumes
|
||||
docker compose -f docker-compose.local.yml --env-file .env down
|
||||
|
||||
# Полная очистка (включая volumes)
|
||||
docker compose -f docker-compose.local.yml --env-file .env down -v
|
||||
```
|
||||
|
||||
## Быстрые проверки
|
||||
```powershell
|
||||
# Проверка frontend (должен вернуть 200)
|
||||
curl --noproxy "*" -s -o /dev/null -w "%{http_code}" http://localhost:8080
|
||||
|
||||
# Проверка backend API через frontend-proxy (должен вернуть 401 без токена)
|
||||
curl --noproxy "*" -s -o /dev/null -w "%{http_code}" http://localhost:8080/api/settings
|
||||
|
||||
# Проверка login endpoint
|
||||
curl --noproxy "*" -s -X POST http://localhost:8080/api/auth/login -H "Content-Type: application/json" -d '{"login":"admin","password":"admin"}'
|
||||
```
|
||||
|
||||
## Тесты и линтинг
|
||||
|
||||
### Backend
|
||||
```powershell
|
||||
cd server
|
||||
npm run lint
|
||||
npm run test
|
||||
npm run test:cov
|
||||
```
|
||||
|
||||
### Frontend
|
||||
```powershell
|
||||
cd client
|
||||
npm run lint
|
||||
npm run test
|
||||
npm run test:cov
|
||||
```
|
||||
|
||||
## Что уже встроено
|
||||
- Backend scanner (RealiTLScanner) собирается в `server/Dockerfile` и кладется в контейнер backend.
|
||||
- Блок `Автопоиск SNI (backend scanner)` показывается только если бинарник сканера доступен в контейнере.
|
||||
- Во время сканирования UI получает статус с backend (`/api/domains/scan/status`) и показывает реальный countdown.
|
||||
- Если обновить страницу во время скана, UI подхватывает активный запуск по backend-статусу и продолжает отсчет.
|
||||
- После завершения скана результат можно получить из backend (`/api/domains/scan/last-result`) даже если исходный запрос был прерван (например, после F5).
|
||||
- Предварительный список сканера:
|
||||
- ручная проверка ссылками,
|
||||
- удаление позиций,
|
||||
- очистка,
|
||||
- экспорт в `.txt`,
|
||||
- сохранение состояния после F5.
|
||||
- Основной список:
|
||||
- ссылки на домены,
|
||||
- hover-подсветка строк,
|
||||
- экспорт полного списка в `.txt`.
|
||||
|
||||
## Нормализация доменов (импорт и ручное добавление)
|
||||
- Единые правила применяются и для загрузки файла, и для добавления по одному.
|
||||
- `*.domain.com` нормализуется в `domain.com`.
|
||||
- Комментарии и мусор отбрасываются (`#`, `;`, `//`, URL/пути/порты и т.п.).
|
||||
- Дубликаты убираются без учета регистра.
|
||||
- Явно некорректные значения отклоняются.
|
||||
|
||||
## Тестовый "больной" список
|
||||
- Файл для проверки импорта: `checker/domains-import-fuzz.txt`
|
||||
|
||||
## Быстрые проверки
|
||||
```powershell
|
||||
Invoke-WebRequest -UseBasicParsing http://localhost:8080
|
||||
Invoke-WebRequest -UseBasicParsing http://localhost:8080/api/settings
|
||||
```
|
||||
|
||||
## Остановка
|
||||
```powershell
|
||||
docker compose -f docker-compose.local.yml down
|
||||
```
|
||||
|
||||
## Перед PR
|
||||
|
||||
### 1. Проверка локального стенда
|
||||
```powershell
|
||||
# Убедиться, что все контейнеры работают
|
||||
docker compose -f docker-compose.local.yml --env-file .env.local ps
|
||||
|
||||
# Проверить логи на наличие ошибок
|
||||
docker compose -f docker-compose.local.yml --env-file .env.local logs --tail 100 backend
|
||||
docker compose -f docker-compose.local.yml --env-file .env.local logs --tail 100 frontend
|
||||
|
||||
# Быстрый тест API
|
||||
curl --noproxy "*" -s -o /dev/null -w "%{http_code}" http://localhost:8080/api/settings
|
||||
```
|
||||
|
||||
### 2. Прогнать тесты
|
||||
```powershell
|
||||
# Backend тесты
|
||||
cd server
|
||||
npm run test
|
||||
|
||||
# Frontend тесты
|
||||
cd client
|
||||
npm run test
|
||||
```
|
||||
|
||||
### 3. Прогнать линтинг
|
||||
```powershell
|
||||
# Backend
|
||||
cd server
|
||||
npm run lint
|
||||
|
||||
# Frontend
|
||||
cd client
|
||||
npm run lint
|
||||
```
|
||||
|
||||
### 4. Проверка изменений
|
||||
```powershell
|
||||
git status --short
|
||||
git diff --name-only
|
||||
```
|
||||
|
||||
Проверяем, что в PR идут только целевые изменения фичи.
|
||||
|
||||
## Тестирование на виртуальной машине (VMware)
|
||||
|
||||
> VMWare имитирует чистый VPS — Ubuntu 24.04, без Docker, без 3dp-manager.
|
||||
> VMware Network: **NAT** (интернет на VM, доступ из хоста по IP VM).
|
||||
|
||||
### Подготовка VM
|
||||
|
||||
1. **Узнать IP виртуалки:**
|
||||
```bash
|
||||
hostname -I
|
||||
# или
|
||||
ip addr show | grep "inet " | grep -v 127.0.0.1
|
||||
```
|
||||
|
||||
2. **SSH с хост-машины (Windows):**
|
||||
```powershell
|
||||
ssh iqubik@192.168.186.128
|
||||
```
|
||||
|
||||
3. **Разрешить sudo без пароля** (нужно для скриптов установки):
|
||||
```bash
|
||||
# На VM — ввести пароль один раз:
|
||||
echo 'iqubik ALL=(ALL) NOPASSWD:ALL' | sudo tee /etc/sudoers.d/iqubik-nopasswd
|
||||
```
|
||||
После этого `sudo` работает без запроса пароля — скрипты могут выполняться автоматически.
|
||||
|
||||
4. **Проверка:**
|
||||
```bash
|
||||
sudo whoami # должно вывести: root
|
||||
```
|
||||
|
||||
### Custom-установка на VM (с нуля)
|
||||
|
||||
```bash
|
||||
# Скачать скрипт (без кэширования) и запустить от root
|
||||
curl -fsSL -H "Cache-Control: no-cache" -H "Pragma: no-cache" https://raw.githubusercontent.com/iqubik/3dp-manager/dp-custom/install-custom.sh -o /tmp/install-custom.sh && sudo bash /tmp/install-custom.sh
|
||||
```
|
||||
|
||||
**Что делает:**
|
||||
- Устанавливает Docker (если нет)
|
||||
- Клонирует код из `dp-custom` в `/opt/3dp-manager-src`
|
||||
- Спрашивает домен (можно пропустить — будет IP без HTTPS)
|
||||
- **Выбор типа SSL:**
|
||||
- `1)` Let's Encrypt автоматически (нужны открытые порты 80/443)
|
||||
- `2)` Self-signed — самоподписанный (для тестов, VMWare)
|
||||
- `3)` Свои сертификаты (указать пути к fullchain.pem / privkey.pem)
|
||||
- `4)` Без SSL — HTTP
|
||||
- Генерирует пароли, ставит Hysteria 2
|
||||
- Собирает backend/frontend из исходников `dp-custom`
|
||||
- Запускает контейнеры
|
||||
|
||||
После установки скрипт выведет **ADMIN_LOGIN / ADMIN_PASSWORD** — сохрани!
|
||||
|
||||
> 💡 **Для VMWare без домена:** выбери `2)` Self-signed — сгенерируется самоподписанный сертификат на 365 дней. Браузер будет ругаться — «Принять риск» → продолжить.
|
||||
|
||||
### Custom-обновление на VM
|
||||
|
||||
```bash
|
||||
curl -fsSL -H "Cache-Control: no-cache" -H "Pragma: no-cache" https://raw.githubusercontent.com/iqubik/3dp-manager/dp-custom/update-custom.sh -o /tmp/update-custom.sh && sudo bash /tmp/update-custom.sh
|
||||
```
|
||||
|
||||
> ⚠️ **Важно:** Заголовки `Cache-Control: no-cache` обязательны — GitHub CDN кэширует raw-файлы до 5 минут. Без них можно получить старую версию скрипта.
|
||||
|
||||
**Что делает:**
|
||||
- `git fetch` + `git merge` последних коммитов из `dp-custom`
|
||||
- Пересобирает backend + frontend из обновлённых исходников
|
||||
- Перезапускает контейнеры
|
||||
|
||||
### Удаление с VM
|
||||
|
||||
```bash
|
||||
# Удалить 3dp-manager (Hysteria 2 останется)
|
||||
curl -fsSL -H "Cache-Control: no-cache" -H "Pragma: no-cache" https://raw.githubusercontent.com/iqubik/3dp-manager/dp-custom/delete-custom.sh -o /tmp/delete-custom.sh && sudo bash /tmp/delete-custom.sh
|
||||
|
||||
# Полное удаление (включая Hysteria 2)
|
||||
curl -fsSL -H "Cache-Control: no-cache" -H "Pragma: no-cache" https://raw.githubusercontent.com/iqubik/3dp-manager/dp-custom/delete-custom.sh | bash -s -- -H
|
||||
```
|
||||
|
||||
> ⚠️ **Важно:** Кэширование также влияет на `delete-custom.sh`. Без `Cache-Control: no-cache` можно получить старую версию.
|
||||
|
||||
### Диагностика на VM
|
||||
|
||||
```bash
|
||||
# Статус контейнеров
|
||||
sudo docker compose -f /opt/3dp-manager/docker-compose.yml ps
|
||||
|
||||
# Логи backend
|
||||
sudo docker compose -f /opt/3dp-manager/docker-compose.yml logs --tail 50 backend
|
||||
|
||||
# Логи frontend
|
||||
sudo docker compose -f /opt/3dp-manager/docker-compose.yml logs --tail 50 frontend
|
||||
|
||||
# Содержимое .env (пароли)
|
||||
sudo cat /opt/3dp-manager/.env
|
||||
|
||||
# docker-compose.yml
|
||||
sudo cat /opt/3dp-manager/docker-compose.yml
|
||||
|
||||
# Дисковое пространство
|
||||
df -h
|
||||
|
||||
# Оперативная память
|
||||
free -m
|
||||
```
|
||||
|
||||
### Полная проверка хвостов после удаления
|
||||
|
||||
```bash
|
||||
ssh iqubik@192.168.186.128 "echo '=== Docker контейнеры ===' && sudo docker ps -a --filter 'name=3dp-' && echo '' && echo '=== Docker образы 3dp ===' && sudo docker images --format '{{.Repository}}:{{.Tag}}' | grep -i 3dp || echo 'пусто' && echo '' && echo '=== Директории ===' && ls -d /opt/3dp-manager /opt/3dp-manager-src 2>&1 || true && echo '' && echo '=== Systemd hysteria ===' && systemctl list-unit-files | grep hysteria || echo 'пусто' && echo '' && echo '=== Hysteria бинарник ===' && which hysteria 2>&1 || echo 'не найден' && echo '' && echo '=== Hysteria конфиг ===' && ls -la /etc/hysteria/ 2>&1 || echo 'нет' && echo '' && echo '=== Процессы ===' && ps aux | grep -E '3dp|hysteria|3x-ui' | grep -v grep || echo 'пусто' && echo '' && echo '=== UFW ===' && sudo ufw status numbered 2>/dev/null || echo 'ufw не активен' && echo '' && echo '=== SSL ===' && ls /etc/letsencrypt/live/ 2>/dev/null || echo 'пусто' && echo '' && echo '=== Cron ===' && sudo crontab -l 2>/dev/null || echo 'пусто' && echo '' && echo '=== Диск ===' && df -h /"
|
||||
```
|
||||
|
||||
Ожидаемый результат после `delete-custom.sh -H`: всё «пусто» / «нет» / «не найден».
|
||||
|
||||
### Снапшоты VMware
|
||||
|
||||
Перед экспериментами — делай снапшот чистой Ubuntu:
|
||||
- **VMware → VM → Snapshot → Take Snapshot** (или `Ctrl+Shift+S`)
|
||||
- Перед `install-custom.sh` — снапшот "clean"
|
||||
- После успешной установки — снапшот "installed"
|
||||
- Перед `update-custom.sh` — снапшот "pre-update"
|
||||
|
||||
### Откат к снапшоту
|
||||
- **VMware → VM → Snapshot → Revert to Snapshot**
|
||||
- VM возвращается в сохранённое состояние за секунды
|
||||
|
||||
## Обновление production (кастомная ветка)
|
||||
|
||||
> ⚠️ **Важно:** Начиная с версии 2.0.3, скрипты обновления автоматически проверяют и заменяют учётные данные по умолчанию (`admin/admin`) на безопасные. Новые пароли выводятся в лог при обновлении.
|
||||
|
||||
Для сервера, где `bash <(curl ...)` не работает из-за `/dev/fd`, используем пайп:
|
||||
|
||||
```bash
|
||||
curl -fsSL https://raw.githubusercontent.com/iqubik/3dp-manager/dp-fix/update-custom.sh | bash -s -- -r https://github.com/iqubik/3dp-manager.git -b dp-fix
|
||||
```
|
||||
|
||||
Что делает команда:
|
||||
- подтягивает исходники из `iqubik/3dp-manager` ветки `dp-fix` в `/opt/3dp-manager-src`,
|
||||
- собирает `backend/frontend` из этих исходников,
|
||||
- поднимает контейнеры через `docker-compose.custom.yml` поверх текущей установки в `/opt/3dp-manager`.
|
||||
|
||||
Проверка после обновления:
|
||||
|
||||
```bash
|
||||
cd /opt/3dp-manager
|
||||
docker compose -f docker-compose.yml -f docker-compose.custom.yml ps
|
||||
docker compose -f docker-compose.yml -f docker-compose.custom.yml logs --tail 120 backend
|
||||
docker compose -f docker-compose.yml -f docker-compose.custom.yml logs --tail 120 frontend
|
||||
```
|
||||
|
||||
Ожидаемо и нормально в логах frontend:
|
||||
- `can not modify /etc/nginx/conf.d/default.conf (read-only file system?)`
|
||||
|
||||
Это штатно при `:ro`-монтировании nginx-конфига.
|
||||
|
||||
## Полная custom-установка на VPS (dp-custom)
|
||||
|
||||
> ⚠️ **Важно:** Скрипт `install-custom.sh` автоматически генерирует безопасные пароли при установке. Учётные данные выводятся в лог после установки — сохраните их!
|
||||
|
||||
Сценарий "с нуля или поверх существующей установки":
|
||||
|
||||
```bash
|
||||
curl -fsSL https://raw.githubusercontent.com/iqubik/3dp-manager/dp-custom/install-custom.sh | bash -s -- -r https://github.com/iqubik/3dp-manager.git -b dp-custom
|
||||
```
|
||||
|
||||
Только обновление custom-кода (без базовой установки):
|
||||
|
||||
```bash
|
||||
curl -fsSL https://raw.githubusercontent.com/iqubik/3dp-manager/dp-custom/update-custom.sh | bash -s -- -r https://github.com/iqubik/3dp-manager.git -b dp-custom
|
||||
```
|
||||
|
||||
## Git workflow: dp-custom -> dp-fix -> автор
|
||||
Роли веток:
|
||||
- `dp-custom`: рабочая ветка для всех локальных/продовых/вспомогательных правок (включая `update-custom.sh`, `debug.md`, `pub.md`).
|
||||
- `dp-fix`: чистая ветка для PR в репозиторий автора (только код, который должен попасть upstream).
|
||||
|
||||
Почему GitHub Desktop "не даёт":
|
||||
- Он не всегда удобно поддерживает сценарий "перенос отдельных коммитов между ветками одного репо".
|
||||
- Для этого используем terminal и `cherry-pick`.
|
||||
|
||||
Базовый цикл работы:
|
||||
|
||||
```bash
|
||||
# 1) Работаем в dp-custom
|
||||
git checkout dp-custom
|
||||
# ... правки ...
|
||||
git add .
|
||||
git commit -m "feat: ... / fix: ..."
|
||||
git push origin dp-custom
|
||||
```
|
||||
|
||||
Проверка прода из `dp-custom`:
|
||||
|
||||
```bash
|
||||
curl -fsSL https://raw.githubusercontent.com/iqubik/3dp-manager/dp-custom/update-custom.sh | bash -s -- -r https://github.com/iqubik/3dp-manager.git -b dp-custom
|
||||
```
|
||||
|
||||
Перенос только нужных коммитов в `dp-fix`:
|
||||
|
||||
```bash
|
||||
# 2) Смотрим, что нового в dp-custom относительно dp-fix
|
||||
git log --oneline dp-fix..dp-custom
|
||||
|
||||
# 3) Переходим в чистую ветку PR
|
||||
git checkout dp-fix
|
||||
|
||||
# 4) Переносим только нужные коммиты
|
||||
git cherry-pick <commit_sha_1> <commit_sha_2> ...
|
||||
|
||||
# 5) Пушим чистую ветку и обновляем PR автору
|
||||
git push origin dp-fix
|
||||
```
|
||||
|
||||
Если конфликт при cherry-pick:
|
||||
|
||||
```bash
|
||||
git status
|
||||
# исправить конфликтные файлы
|
||||
git add <resolved_files>
|
||||
git cherry-pick --continue
|
||||
```
|
||||
|
||||
Если нужно отменить текущий перенос:
|
||||
|
||||
```bash
|
||||
git cherry-pick --abort
|
||||
```
|
||||
|
||||
Проверка перед push в `dp-fix`:
|
||||
|
||||
```bash
|
||||
git status --short
|
||||
git diff --name-only upstream/dp-fix..HEAD
|
||||
```
|
||||
|
||||
Ожидаем в `dp-fix` только те файлы, которые должны уйти автору.
|
||||
|
||||
## Файл .env.local
|
||||
|
||||
Файл `.env.local` в корне проекта содержит переменные окружения для локального стенда:
|
||||
|
||||
```bash
|
||||
# Локальные учётные данные для разработки
|
||||
POSTGRES_USER=admin
|
||||
POSTGRES_PASSWORD=admin
|
||||
POSTGRES_DB=3dp_manager
|
||||
JWT_SECRET=localDevSecretKey12345678901234567890
|
||||
ADMIN_LOGIN=admin
|
||||
ADMIN_PASSWORD=admin
|
||||
PORT=3100
|
||||
ALLOWED_ORIGINS=http://localhost:8080,http://localhost
|
||||
```
|
||||
|
||||
> ⚠️ **Не коммитьте `.env.local` в репозиторий!** Файл должен быть добавлен в `.gitignore` для безопасности.
|
||||
|
||||
### Изменение учётных данных
|
||||
|
||||
Если нужно изменить логин/пароль для локальной разработки:
|
||||
|
||||
1. Отредактируйте `.env.local`
|
||||
2. Пересоздайте контейнеры с очисткой volumes:
|
||||
```powershell
|
||||
docker compose -f docker-compose.local.yml --env-file .env.local down -v
|
||||
docker compose -f docker-compose.local.yml --env-file .env.local up -d --build
|
||||
```
|
||||
@@ -1,245 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
#################################
|
||||
# TRAP
|
||||
#################################
|
||||
trap 'echo -e "\033[1;31m[ERROR]\033[0m Ошибка в строке $LINENO"; exit 1' ERR
|
||||
|
||||
#################################
|
||||
# HELPERS
|
||||
#################################
|
||||
log() { echo -e "\033[1;32m[INFO]\033[0m $1"; }
|
||||
warn() { echo -e "\033[1;33m[WARN]\033[0m $1"; }
|
||||
die() { echo -e "\033[1;31m[ERROR]\033[0m $1"; exit 1; }
|
||||
|
||||
need_root() {
|
||||
[[ $EUID -eq 0 ]] || die "Запускать только от root"
|
||||
}
|
||||
|
||||
#################################
|
||||
# PARSE ARGS
|
||||
#################################
|
||||
PROJECT_DIR="/opt/3dp-manager"
|
||||
SOURCE_DIR="/opt/3dp-manager-src"
|
||||
REMOVE_HYSTERIA=0
|
||||
|
||||
while getopts ":p:s:hH" opt; do
|
||||
case "$opt" in
|
||||
p) PROJECT_DIR="$OPTARG" ;;
|
||||
s) SOURCE_DIR="$OPTARG" ;;
|
||||
H) REMOVE_HYSTERIA=1 ;;
|
||||
h)
|
||||
echo "Удаление 3dp-manager (custom-ветка dp-custom)"
|
||||
echo ""
|
||||
echo "Использование:"
|
||||
echo " delete-custom.sh [-p <project_dir>] [-s <source_dir>] [-H]"
|
||||
echo ""
|
||||
echo "Параметры:"
|
||||
echo " -p Папка установки (по умолчанию: /opt/3dp-manager)"
|
||||
echo " -s Папка исходников (по умолчанию: /opt/3dp-manager-src)"
|
||||
echo " -H Удалить Hysteria 2 вместе с 3dp-manager"
|
||||
echo " -h Показать эту справку"
|
||||
echo ""
|
||||
echo "Пример:"
|
||||
echo " delete-custom.sh -H"
|
||||
exit 0
|
||||
;;
|
||||
:) die "Параметр -$OPTARG требует значение" ;;
|
||||
\?) die "Неизвестный параметр: -$OPTARG" ;;
|
||||
esac
|
||||
done
|
||||
|
||||
#################################
|
||||
# CONFIRMATION
|
||||
#################################
|
||||
read -r -p "Вы уверены, что хотите удалить 3dp-manager (custom)? (y/n): " answer
|
||||
|
||||
case "$answer" in
|
||||
y|Y)
|
||||
log "Начинаю удаление..."
|
||||
;;
|
||||
*)
|
||||
log "Удаление отменено"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
#################################
|
||||
# START
|
||||
#################################
|
||||
need_root
|
||||
|
||||
log "Удаляем 3dp-manager (custom-ветка)"
|
||||
|
||||
#################################
|
||||
# DOCKER COMPOSE DOWN
|
||||
#################################
|
||||
resolve_compose_cmd() {
|
||||
if docker compose version >/dev/null 2>&1; then
|
||||
COMPOSE_CMD=("docker" "compose")
|
||||
elif command -v docker-compose >/dev/null 2>&1; then
|
||||
COMPOSE_CMD=("docker-compose")
|
||||
else
|
||||
warn "Docker Compose не найден — пропуск"
|
||||
COMPOSE_CMD=()
|
||||
fi
|
||||
}
|
||||
|
||||
resolve_compose_cmd
|
||||
|
||||
if [[ ${#COMPOSE_CMD[@]} -gt 0 ]]; then
|
||||
# Попробуем с docker-compose.custom.yml (если есть)
|
||||
if [[ -d "$PROJECT_DIR" ]] && [[ -f "$PROJECT_DIR/docker-compose.yml" ]]; then
|
||||
cd "$PROJECT_DIR"
|
||||
if [[ -f "$PROJECT_DIR/docker-compose.custom.yml" ]]; then
|
||||
log "Останавливаем custom-контейнеры"
|
||||
"${COMPOSE_CMD[@]}" -f docker-compose.yml -f docker-compose.custom.yml down --volumes --remove-orphans || warn "Ошибка при custom compose down"
|
||||
else
|
||||
log "Останавливаем контейнеры"
|
||||
"${COMPOSE_CMD[@]}" down --volumes --remove-orphans || warn "Ошибка при docker compose down"
|
||||
fi
|
||||
else
|
||||
# Попробуем остановить любые контейнеры 3dp-manager
|
||||
running=$(docker ps --filter "name=3dp-" --format '{{.Names}}' 2>/dev/null || true)
|
||||
if [[ -n "$running" ]]; then
|
||||
log "Найдены запущенные контейнеры 3dp: $running"
|
||||
docker stop $running || warn "Ошибка при остановке контейнеров"
|
||||
docker rm $running || warn "Ошибка при удалении контейнеров"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
#################################
|
||||
# CLEAN IMAGES
|
||||
#################################
|
||||
log "Удаляем custom-образы (если есть)"
|
||||
docker images --format '{{.Repository}}:{{.Tag}}' \
|
||||
| grep -E '3dp-manager-server:custom|3dp-manager-client:custom' \
|
||||
| xargs -r docker rmi -f 2>/dev/null || true
|
||||
|
||||
log "Удаляем образы 3dp-manager (общие)"
|
||||
docker images --format '{{.Repository}}:{{.Tag}}' \
|
||||
| grep '3dp-manager' \
|
||||
| xargs -r docker rmi -f 2>/dev/null || true
|
||||
|
||||
# Dangling images
|
||||
docker image prune -f 2>/dev/null || true
|
||||
|
||||
#################################
|
||||
# REMOVE HYSTERIA 2 (опционально)
|
||||
#################################
|
||||
if [[ $REMOVE_HYSTERIA -eq 1 ]]; then
|
||||
log "Удаление Hysteria 2..."
|
||||
|
||||
if systemctl is-active --quiet hysteria-server 2>/dev/null; then
|
||||
systemctl stop hysteria-server || warn "Ошибка при остановке hysteria-server"
|
||||
systemctl disable hysteria-server || true
|
||||
fi
|
||||
|
||||
# Удаляем systemd unit
|
||||
if [[ -f /etc/systemd/system/hysteria-server.service ]]; then
|
||||
rm -f /etc/systemd/system/hysteria-server.service
|
||||
log "Unit hysteria-server.service удалён"
|
||||
fi
|
||||
|
||||
# Удаляем шаблон unit'а (hysteria-server@.service)
|
||||
if [[ -f /etc/systemd/system/hysteria-server@.service ]]; then
|
||||
rm -f /etc/systemd/system/hysteria-server@.service
|
||||
log "Unit hysteria-server@.service удалён"
|
||||
fi
|
||||
|
||||
# Перезагружаем systemd
|
||||
systemctl daemon-reload
|
||||
|
||||
# Удаляем бинарник
|
||||
if command -v hysteria &>/dev/null; then
|
||||
HYSTERIA_BIN="$(command -v hysteria)"
|
||||
rm -f "$HYSTERIA_BIN"
|
||||
log "Бинарник hysteria удалён: $HYSTERIA_BIN"
|
||||
fi
|
||||
|
||||
# Удаляем конфиг и пустую директорию
|
||||
if [[ -d /etc/hysteria ]]; then
|
||||
rm -rf /etc/hysteria
|
||||
log "Директория /etc/hysteria удалена"
|
||||
fi
|
||||
|
||||
# Удаляем firewall правила Hysteria
|
||||
if command -v ufw &>/dev/null; then
|
||||
# Удаляем правило для порта 8443 (если это только Hysteria)
|
||||
warn "Правила UFW для Hysteria (UDP 8443) нужно удалить вручную: sudo ufw status numbered"
|
||||
fi
|
||||
|
||||
log "Hysteria 2 удалён"
|
||||
else
|
||||
warn "Hysteria 2 НЕ удалён. Для удаления: delete-custom.sh -H"
|
||||
fi
|
||||
|
||||
#################################
|
||||
# FIREWALL — удалить правила 3dp-manager
|
||||
#################################
|
||||
if command -v ufw &>/dev/null; then
|
||||
log "Удаляем правила UFW для 3dp-manager..."
|
||||
# Удаляем правила для портов 443, 8443, 10000-20000, 30000-60000 (только те, что добавлял 3dp-manager)
|
||||
# Это может затронуть и другие сервисы, поэтому просто предупреждаем
|
||||
warn "Правила UFW (443, 8443, 10000-60000) нужно проверить/удалить вручную:"
|
||||
warn "sudo ufw status numbered"
|
||||
fi
|
||||
|
||||
#################################
|
||||
# REMOVE DIRECTORIES
|
||||
#################################
|
||||
log "Удаляем $PROJECT_DIR"
|
||||
rm -rf "$PROJECT_DIR"
|
||||
|
||||
log "Удаляем $SOURCE_DIR"
|
||||
rm -rf "$SOURCE_DIR"
|
||||
|
||||
#################################
|
||||
# REMOVE SYSTEMD SERVICE (если есть)
|
||||
#################################
|
||||
if [[ -f /etc/systemd/system/3dp-manager.service ]]; then
|
||||
systemctl stop 3dp-manager 2>/dev/null || true
|
||||
systemctl disable 3dp-manager 2>/dev/null || true
|
||||
rm -f /etc/systemd/system/3dp-manager.service
|
||||
systemctl daemon-reload
|
||||
log "Systemd unit 3dp-manager.service удалён"
|
||||
fi
|
||||
|
||||
#################################
|
||||
# REMOVE CRON ENTRIES (если есть)
|
||||
#################################
|
||||
# Скрипты могли добавлять cron для автоматического обновления
|
||||
# Проверяем cron текущего пользователя и root
|
||||
for user in root "${SUDO_USER:-}"; do
|
||||
if [[ -n "$user" ]]; then
|
||||
cron_jobs=$(sudo -u "$user" crontab -l 2>/dev/null | grep -v '3dp-manager' || true)
|
||||
if [[ -z "$cron_jobs" ]]; then
|
||||
sudo -u "$user" crontab -r 2>/dev/null || true
|
||||
log "Cron jobs для $user удалены"
|
||||
else
|
||||
echo "$cron_jobs" | sudo -u "$user" crontab - 2>/dev/null || true
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
#################################
|
||||
# DONE
|
||||
#################################
|
||||
log "✔ 3dp-manager (custom) полностью удалён"
|
||||
log ""
|
||||
log "Что было удалено:"
|
||||
log " • Контейнеры и образы Docker"
|
||||
log " • $PROJECT_DIR"
|
||||
log " • $SOURCE_DIR"
|
||||
log ""
|
||||
if [[ $REMOVE_HYSTERIA -eq 1 ]]; then
|
||||
log " • Hysteria 2"
|
||||
else
|
||||
warn " Hysteria 2 НЕ удалён (используйте -H для удаления)"
|
||||
fi
|
||||
warn ""
|
||||
warn "Проверьте вручную:"
|
||||
warn " • sudo ufw status numbered — правила firewall"
|
||||
warn " • /etc/letsencrypt/live/ — SSL сертификаты (если были)"
|
||||
@@ -17,6 +17,7 @@ need_root() {
|
||||
[[ $EUID -eq 0 ]] || die "Запускать только от root"
|
||||
}
|
||||
|
||||
echo "Перед удалением самостоятельно удалите подписки в 3DP-MANAGER, чтобы инбунды удалились в панели 3x-ui"
|
||||
read -r -p "Вы уверены, что хотите удалить? (y/n): " answer
|
||||
|
||||
case "$answer" in
|
||||
|
||||
+5
-40
@@ -14,43 +14,6 @@ warn() { echo -e "\033[1;33m[WARN]\033[0m $1"; }
|
||||
|
||||
[[ $EUID -eq 0 ]] || { echo "Запускать нужно от root"; exit 1; }
|
||||
|
||||
read -r -p "Вы уверены, что хотите удалить? (y/n): " answer
|
||||
|
||||
case "$answer" in
|
||||
y|Y)
|
||||
echo "Начинаю удаление..."
|
||||
;;
|
||||
*)
|
||||
echo "Удаление отменено"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
#################################
|
||||
# PATHS
|
||||
#################################
|
||||
BASE_DIR="/opt/3dp-manager"
|
||||
UFW_BEFORE="/etc/ufw/before.rules"
|
||||
UFW_NAT_MARKER="3dp-manager NAT"
|
||||
NGINX_PORT=$(cd "$BASE_DIR" && docker compose exec -T node printenv ORIGIN_PORT | tr -d '\r')
|
||||
|
||||
#################################
|
||||
# DOCKER CLEAN
|
||||
#################################
|
||||
if [[ -d "$BASE_DIR" ]]; then
|
||||
log "Остановка Docker"
|
||||
cd "$BASE_DIR"
|
||||
docker compose down --remove-orphans || true
|
||||
else
|
||||
warn "Каталог проекта не найден"
|
||||
fi
|
||||
|
||||
#################################
|
||||
# REMOVE FILES
|
||||
#################################
|
||||
log "Удаление файлов проекта"
|
||||
rm -rf "$BASE_DIR"
|
||||
|
||||
#################################
|
||||
# UFW NAT CLEAN
|
||||
#################################
|
||||
@@ -71,7 +34,6 @@ ufw delete allow 443/tcp
|
||||
ufw delete allow 443/udp
|
||||
ufw delete allow 8443/tcp
|
||||
ufw delete allow 8443/udp
|
||||
ufw delete allow "$NGINX_PORT"/tcp
|
||||
ufw delete allow 10000:60000/tcp
|
||||
ufw delete allow 10000:60000/udp
|
||||
|
||||
@@ -85,7 +47,10 @@ ufw reload
|
||||
# RESULT
|
||||
#################################
|
||||
|
||||
echo "Docker контейнеры удалены"
|
||||
echo "UFW NAT очищен"
|
||||
echo "ip_forward отключён"
|
||||
log "Откат завершён. Для окончательного применения изменений перезагрузите систему!"
|
||||
log "Откат завершён. Для окончательного применения изменений система будет перезагружена!"
|
||||
|
||||
(sleep 3 && reboot) >/dev/null 2>&1 &
|
||||
|
||||
exit 0
|
||||
@@ -1,731 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
#################################
|
||||
# КОНФИГУРАЦИЯ И ПЕРЕМЕННЫЕ
|
||||
#################################
|
||||
PROJECT_DIR="/opt/3dp-manager"
|
||||
SOURCE_DIR="/opt/3dp-manager-src"
|
||||
DEFAULT_REPO="https://github.com/iqubik/3dp-manager.git"
|
||||
DEFAULT_BRANCH="dp-custom"
|
||||
|
||||
# Цвета
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m'
|
||||
|
||||
log() { echo -e "${GREEN}[INFO]${NC} $1"; }
|
||||
warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
|
||||
die() { echo -e "${RED}[ERROR]${NC} $1"; exit 1; }
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
Использование:
|
||||
install-custom.sh [-r <repo_url>] [-b <branch>] [-p <project_dir>] [-s <source_dir>]
|
||||
|
||||
Параметры:
|
||||
-r Git URL форка (по умолчанию: https://github.com/iqubik/3dp-manager.git)
|
||||
-b Ветка форка (по умолчанию: dp-custom)
|
||||
-p Папка установки (по умолчанию: /opt/3dp-manager)
|
||||
-s Папка исходников (по умолчанию: /opt/3dp-manager-src)
|
||||
-h Показать эту справку
|
||||
|
||||
Пример:
|
||||
./install-custom.sh -r https://github.com/iqubik/3dp-manager.git -b dp-custom
|
||||
EOF
|
||||
}
|
||||
|
||||
need_root() {
|
||||
[[ $EUID -eq 0 ]] || die "Этот скрипт должен быть запущен от имени root"
|
||||
}
|
||||
|
||||
need_cmd() {
|
||||
command -v "$1" >/dev/null 2>&1 || die "Не найдена команда: $1"
|
||||
}
|
||||
|
||||
resolve_compose_cmd() {
|
||||
if docker compose version >/dev/null 2>&1; then
|
||||
COMPOSE_CMD=("docker" "compose")
|
||||
return 0
|
||||
fi
|
||||
|
||||
if command -v docker-compose >/dev/null 2>&1; then
|
||||
COMPOSE_CMD=("docker-compose")
|
||||
return 0
|
||||
fi
|
||||
|
||||
warn "Не найден Docker Compose. Пытаемся установить..."
|
||||
apt-get update
|
||||
apt-get install -y docker-compose-plugin || true
|
||||
|
||||
if docker compose version >/dev/null 2>&1; then
|
||||
COMPOSE_CMD=("docker" "compose")
|
||||
return 0
|
||||
fi
|
||||
|
||||
apt-get install -y docker-compose || true
|
||||
if command -v docker-compose >/dev/null 2>&1; then
|
||||
COMPOSE_CMD=("docker-compose")
|
||||
return 0
|
||||
fi
|
||||
|
||||
die "Не удалось установить Docker Compose. Установите docker compose plugin (v2) или docker-compose (v1)."
|
||||
}
|
||||
|
||||
check_containers_running() {
|
||||
log "Проверка статуса контейнеров..."
|
||||
local timeout=${1:-60}
|
||||
local elapsed=0
|
||||
local failed=0
|
||||
|
||||
while [ $elapsed -lt $timeout ]; do
|
||||
failed=0
|
||||
while IFS=$'\t' read -r container_name status; do
|
||||
if [ -n "$container_name" ] && [ -n "$status" ]; then
|
||||
if ! echo "$status" | grep -qiE "^up|running|healthy|restarting"; then
|
||||
failed=1
|
||||
warn "Контейнер $container_name в статусе: $status"
|
||||
fi
|
||||
fi
|
||||
done < <("${COMPOSE_CMD[@]}" ps --format "table {{.Name}}\t{{.Status}}" --all 2>/dev/null | tail -n +2)
|
||||
|
||||
if [ $failed -eq 0 ]; then
|
||||
log "Все контейнеры запущены успешно"
|
||||
return 0
|
||||
fi
|
||||
|
||||
sleep 2
|
||||
elapsed=$((elapsed + 2))
|
||||
done
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
get_random_port() {
|
||||
local MIN=${1:-3000}
|
||||
local MAX=${2:-6999}
|
||||
while :; do
|
||||
PORT=$(shuf -i "$MIN-$MAX" -n 1)
|
||||
if ! ss -ltun | awk '{print $4}' | grep -q ":$PORT\$"; then
|
||||
echo "$PORT"
|
||||
return
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
#################################
|
||||
# PARSE ARGS
|
||||
#################################
|
||||
REPO_URL="$DEFAULT_REPO"
|
||||
BRANCH="$DEFAULT_BRANCH"
|
||||
|
||||
while getopts ":r:b:p:s:h" opt; do
|
||||
case "$opt" in
|
||||
r) REPO_URL="$OPTARG" ;;
|
||||
b) BRANCH="$OPTARG" ;;
|
||||
p) PROJECT_DIR="$OPTARG" ;;
|
||||
s) SOURCE_DIR="$OPTARG" ;;
|
||||
h) usage; exit 0 ;;
|
||||
:) die "Параметр -$OPTARG требует значение" ;;
|
||||
\?) die "Неизвестный параметр: -$OPTARG" ;;
|
||||
esac
|
||||
done
|
||||
|
||||
#################################
|
||||
# ASCII-баннер
|
||||
#################################
|
||||
echo "==================================================="
|
||||
echo " ____ ____ _ ___ _ "
|
||||
echo " / __ \___ ____ / __ \(_) (_)___ ______(_)___ ___ "
|
||||
echo " / / / / _ \/ __ \/ /_/ / / / / __ \/ ___/ / __ \`__ \ "
|
||||
echo " / /_/ / __/ / / / ____/ / / / /_/ / / / / / / / / /"
|
||||
echo "/_____/\___/_/ /_/_/ /_/_/_/\__, /_/ /_/_/ /_/ /_/ "
|
||||
echo " /____/ "
|
||||
echo ""
|
||||
echo " 3DP-MANAGER — CUSTOM INSTALL "
|
||||
echo "==================================================="
|
||||
echo ""
|
||||
log "Репозиторий: $REPO_URL"
|
||||
log "Ветка: $BRANCH"
|
||||
|
||||
#################################
|
||||
# ПРОВЕРКИ
|
||||
#################################
|
||||
need_root
|
||||
|
||||
. /etc/os-release
|
||||
if [[ "$ID" != "ubuntu" && "$ID" != "debian" ]]; then
|
||||
die "Этот скрипт поддерживает только Ubuntu или Debian: $ID"
|
||||
fi
|
||||
|
||||
if [ "$(free -m | grep Mem: | awk '{print $2}')" -lt 2000 ]; then
|
||||
if [ "$(free -m | grep Swap: | awk '{print $2}')" -eq 0 ]; then
|
||||
log "Мало RAM и нет Swap. Создаем swap-файл 2GB..."
|
||||
fallocate -l 2G /swapfile || dd if=/dev/zero of=/swapfile bs=1M count=2048
|
||||
chmod 600 /swapfile
|
||||
mkswap /swapfile
|
||||
swapon /swapfile
|
||||
echo '/swapfile none swap sw 0 0' >> /etc/fstab
|
||||
log "Swap создан."
|
||||
fi
|
||||
fi
|
||||
|
||||
log "Проверка зависимостей..."
|
||||
need_cmd curl
|
||||
if ! command -v jq &> /dev/null; then apt-get update && apt-get install -y jq; fi
|
||||
if ! command -v openssl &> /dev/null; then apt-get install -y openssl; fi
|
||||
if ! command -v tar &> /dev/null; then apt-get install -y tar; fi
|
||||
if ! command -v hostname &> /dev/null; then apt-get install -y net-tools || apt-get install -y hostname; fi
|
||||
need_cmd git
|
||||
|
||||
#################################
|
||||
# DOCKER
|
||||
#################################
|
||||
log "Проверка Docker"
|
||||
if command -v docker >/dev/null 2>&1; then
|
||||
log "Docker уже установлен"
|
||||
else
|
||||
log "Docker не найден, будет установлен из официального репозитория"
|
||||
apt update
|
||||
apt install -y ca-certificates curl
|
||||
install -m 0755 -d /etc/apt/keyrings
|
||||
if [[ "$ID" == "ubuntu" ]]; then
|
||||
curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc || die "Ошибка добавления ключа Docker"
|
||||
else
|
||||
curl -fsSL https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc || die "Ошибка добавления ключа Docker"
|
||||
fi
|
||||
chmod a+r /etc/apt/keyrings/docker.asc
|
||||
|
||||
CODENAME=${UBUNTU_CODENAME:-$VERSION_CODENAME}
|
||||
tee /etc/apt/sources.list.d/docker.sources <<EOF
|
||||
Types: deb
|
||||
URIs: https://download.docker.com/linux/$ID
|
||||
Suites: $CODENAME
|
||||
Components: stable
|
||||
Signed-By: /etc/apt/keyrings/docker.asc
|
||||
EOF
|
||||
|
||||
apt update
|
||||
apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
|
||||
systemctl enable docker
|
||||
systemctl start docker
|
||||
fi
|
||||
|
||||
resolve_compose_cmd
|
||||
log "Compose команда: ${COMPOSE_CMD[*]}"
|
||||
|
||||
#################################
|
||||
# КЛОНИРОВАНИЕ ИСХОДНИКОВ
|
||||
#################################
|
||||
log "Клонирование исходников из $REPO_URL (ветка: $BRANCH)..."
|
||||
if [[ -d "$SOURCE_DIR/.git" ]]; then
|
||||
log "Исходники уже существуют, обновляем..."
|
||||
cd "$SOURCE_DIR"
|
||||
git remote set-url origin "$REPO_URL"
|
||||
git fetch origin "$BRANCH"
|
||||
if git show-ref --verify --quiet "refs/heads/$BRANCH"; then
|
||||
git checkout "$BRANCH"
|
||||
git merge --ff-only FETCH_HEAD || die "Не удалось fast-forward merge."
|
||||
else
|
||||
git checkout -b "$BRANCH" FETCH_HEAD
|
||||
fi
|
||||
else
|
||||
rm -rf "$SOURCE_DIR"
|
||||
git clone --single-branch --branch "$BRANCH" "$REPO_URL" "$SOURCE_DIR"
|
||||
fi
|
||||
|
||||
#################################
|
||||
# СБОР ДАННЫХ: SSL / HTTPS
|
||||
#################################
|
||||
UI_HOST=""
|
||||
USE_SSL=false
|
||||
CERT_PATH=""
|
||||
KEY_PATH=""
|
||||
|
||||
echo ""
|
||||
echo -e "${GREEN}Выберите тип SSL/HTTPS сертификации:${NC}"
|
||||
echo " 1) HTTPS — Let's Encrypt (нужен реальный домен, привязанный к IP, + открыты порты 80/443 в UFW)"
|
||||
echo " 2) HTTPS — Self-signed (самоподписанный на IP сервера, для тестов/VM)"
|
||||
echo " 3) HTTPS — Свои сертификаты (указать пути)"
|
||||
echo " 4) HTTP — без шифрования"
|
||||
echo ""
|
||||
printf "Ваш выбор (1/2/3/4) [\033[0;31m4\033[0m]: "
|
||||
read -r ssl_choice
|
||||
|
||||
case "$ssl_choice" in
|
||||
1)
|
||||
echo ""
|
||||
read -rp "Введите домен (должен быть привязан к IP этого сервера): " INPUT_HOST
|
||||
|
||||
if [ -z "$INPUT_HOST" ]; then
|
||||
warn "Домен не указан. Переключение на HTTP."
|
||||
else
|
||||
UI_HOST="$INPUT_HOST"
|
||||
LE_CERT="/etc/letsencrypt/live/$UI_HOST/fullchain.pem"
|
||||
LE_KEY="/etc/letsencrypt/live/$UI_HOST/privkey.pem"
|
||||
|
||||
if [[ -f "$LE_CERT" && -f "$LE_KEY" ]]; then
|
||||
# Проверяем что сертификат не истёк и ключ соответствует
|
||||
if openssl x509 -checkend 0 -noout -in "$LE_CERT" 2>/dev/null && \
|
||||
openssl x509 -noout -modulus -in "$LE_CERT" 2>/dev/null | md5sum > /tmp/cert_hash && \
|
||||
openssl rsa -noout -modulus -in "$LE_KEY" 2>/dev/null | md5sum > /tmp/key_hash && \
|
||||
diff -q /tmp/cert_hash /tmp/key_hash >/dev/null 2>&1; then
|
||||
log "Найдены валидные сертификаты для $UI_HOST."
|
||||
USE_SSL=true
|
||||
CERT_PATH="$LE_CERT"
|
||||
KEY_PATH="$LE_KEY"
|
||||
else
|
||||
warn "Найдены сертификаты для $UI_HOST, но они невалидны. Перегенерируем."
|
||||
# Удаляем старые и генерируем новые
|
||||
rm -rf "/etc/letsencrypt/live/$UI_HOST"
|
||||
mkdir -p "/etc/letsencrypt/live/$UI_HOST"
|
||||
openssl req -x509 -nodes -days 365 \
|
||||
-newkey rsa:2048 \
|
||||
-keyout "/etc/letsencrypt/live/$UI_HOST/privkey.pem" \
|
||||
-out "/etc/letsencrypt/live/$UI_HOST/fullchain.pem" \
|
||||
-subj "/CN=$UI_HOST" \
|
||||
-addext "subjectAltName=IP:$UI_HOST" 2>/dev/null
|
||||
if [[ -f "$LE_CERT" && -f "$LE_KEY" ]]; then
|
||||
USE_SSL=true
|
||||
CERT_PATH="$LE_CERT"
|
||||
KEY_PATH="$LE_KEY"
|
||||
log "Self-signed сертификат перегенерирован для $UI_HOST"
|
||||
warn "Браузер будет предупреждать — это нормально для тестов."
|
||||
else
|
||||
warn "Не удалось сгенерировать сертификат. Переключение на HTTP."
|
||||
fi
|
||||
fi
|
||||
else
|
||||
log "Получение Let's Encrypt сертификата для $UI_HOST..."
|
||||
read -e -p "Email для уведомлений Let's Encrypt: " LE_EMAIL
|
||||
LE_EMAIL=$(echo "$LE_EMAIL" | tr -cd 'a-zA-Z0-9.@_-')
|
||||
|
||||
if command -v certbot &>/dev/null; then
|
||||
log "Certbot уже установлен."
|
||||
else
|
||||
log "Установка certbot..."
|
||||
apt update
|
||||
apt install -y certbot
|
||||
fi
|
||||
|
||||
certbot certonly --standalone \
|
||||
--agree-tos \
|
||||
--non-interactive \
|
||||
--email "$LE_EMAIL" \
|
||||
-d "$UI_HOST" || die "Не удалось получить сертификат Let's Encrypt"
|
||||
|
||||
if [[ -f "$LE_CERT" && -f "$LE_KEY" ]]; then
|
||||
USE_SSL=true
|
||||
CERT_PATH="$LE_CERT"
|
||||
KEY_PATH="$LE_KEY"
|
||||
log "Let's Encrypt сертификат получен для $UI_HOST"
|
||||
else
|
||||
warn "Сертификат не получен. Переключение на HTTP."
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
;;
|
||||
|
||||
2)
|
||||
UI_HOST=$(hostname -I | awk '{print $1}')
|
||||
log "Генерация самоподписанного сертификата для $UI_HOST..."
|
||||
mkdir -p "/etc/letsencrypt/live/$UI_HOST"
|
||||
openssl req -x509 -nodes -days 365 \
|
||||
-newkey rsa:2048 \
|
||||
-keyout "/etc/letsencrypt/live/$UI_HOST/privkey.pem" \
|
||||
-out "/etc/letsencrypt/live/$UI_HOST/fullchain.pem" \
|
||||
-subj "/CN=$UI_HOST" \
|
||||
-addext "subjectAltName=IP:$UI_HOST" 2>/dev/null
|
||||
|
||||
if [[ -f "/etc/letsencrypt/live/$UI_HOST/fullchain.pem" && -f "/etc/letsencrypt/live/$UI_HOST/privkey.pem" ]]; then
|
||||
USE_SSL=true
|
||||
CERT_PATH="/etc/letsencrypt/live/$UI_HOST/fullchain.pem"
|
||||
KEY_PATH="/etc/letsencrypt/live/$UI_HOST/privkey.pem"
|
||||
log "Self-signed сертификат сгенерирован для $UI_HOST"
|
||||
warn "Браузер будет предупреждать — это нормально для тестов."
|
||||
else
|
||||
warn "Не удалось сгенерировать сертификат. Переключение на HTTP."
|
||||
fi
|
||||
;;
|
||||
|
||||
3)
|
||||
read -rp "Путь к fullchain.pem: " user_cert
|
||||
read -rp "Путь к privkey.pem: " user_key
|
||||
if [[ -f "$user_cert" && -f "$user_key" ]]; then
|
||||
USE_SSL=true
|
||||
CERT_PATH="$user_cert"
|
||||
KEY_PATH="$user_key"
|
||||
UI_HOST=$(hostname -I | awk '{print $1}')
|
||||
else
|
||||
warn "Файлы не найдены. Переключение на HTTP."
|
||||
fi
|
||||
;;
|
||||
|
||||
*)
|
||||
UI_HOST=$(hostname -I | awk '{print $1}')
|
||||
log "Будет использоваться HTTP."
|
||||
;;
|
||||
esac
|
||||
|
||||
if [ -z "$UI_HOST" ]; then
|
||||
UI_HOST=$(hostname -I | awk '{print $1}')
|
||||
log "Используется IP сервера: $UI_HOST"
|
||||
fi
|
||||
|
||||
#################################
|
||||
# СБОР ДАННЫХ: порты и пароли
|
||||
#################################
|
||||
FINAL_PORT=$(get_random_port)
|
||||
|
||||
# Генерация паролей
|
||||
DB_PASS=$(openssl rand -base64 12 | tr -dc 'A-Za-z0-9' | cut -c1-12)
|
||||
JWT_SECRET=$(openssl rand -base64 32)
|
||||
ADMIN_USER=$(openssl rand -base64 8 | tr -dc 'A-Za-z0-9' | cut -c1-8)
|
||||
ADMIN_PASS=$(openssl rand -base64 12 | tr -dc 'A-Za-z0-9' | cut -c1-12)
|
||||
|
||||
# ALLOWED_ORIGINS — зависит от USE_SSL, а не от формата хоста
|
||||
if [[ "$USE_SSL" == "true" ]]; then
|
||||
ALLOWED_ORIGINS="https://${UI_HOST}:${FINAL_PORT}"
|
||||
else
|
||||
ALLOWED_ORIGINS="http://${UI_HOST}:${FINAL_PORT}"
|
||||
fi
|
||||
|
||||
log "Сгенерированы секретные ключи для БД и JWT."
|
||||
|
||||
echo ""
|
||||
echo "==================================================="
|
||||
echo " УЧЁТНЫЕ ДАННЫЕ АДМИНИСТРАТОРА"
|
||||
echo "==================================================="
|
||||
echo " ADMIN_LOGIN: ${ADMIN_USER}"
|
||||
echo " ADMIN_PASSWORD: ${ADMIN_PASS}"
|
||||
echo " POSTGRES_PASSWORD: ${DB_PASS}"
|
||||
echo " JWT_SECRET: ${JWT_SECRET}"
|
||||
echo ""
|
||||
echo " ⚠️ СОХРАНИТЕ ЭТИ ДАННЫЕ В БЕЗОПАСНОМ МЕСТЕ!"
|
||||
echo "==================================================="
|
||||
echo ""
|
||||
|
||||
#################################
|
||||
# Hysteria 2
|
||||
#################################
|
||||
if ! systemctl cat hysteria-server.service &> /dev/null; then
|
||||
log "Установка Hysteria 2..."
|
||||
bash <(curl -fsSL https://get.hy2.sh/) < /dev/null || true
|
||||
|
||||
RANDOM_FREE_PORT=$(get_random_port 10000 20000)
|
||||
GENERATED_PASSWORD=$(openssl rand -base64 16 | tr -dc 'A-Za-z0-9' | cut -c1-16)
|
||||
GENERATED_OBFS_PASSWORD=$(openssl rand -base64 16 | tr -dc 'A-Za-z0-9' | cut -c1-16)
|
||||
|
||||
echo "=== Настройка Hysteria 2 ==="
|
||||
read -e -p "Введите email для уведомлений Let's Encrypt: " HYSTERIA_EMAIL
|
||||
HYSTERIA_EMAIL=$(echo "$HYSTERIA_EMAIL" | tr -cd 'a-zA-Z0-9.@_-')
|
||||
|
||||
cat > /etc/hysteria/config.yaml <<EOF
|
||||
listen: :$RANDOM_FREE_PORT
|
||||
|
||||
acme:
|
||||
domains:
|
||||
- $UI_HOST
|
||||
email: $HYSTERIA_EMAIL
|
||||
|
||||
auth:
|
||||
type: password
|
||||
password: $GENERATED_PASSWORD
|
||||
|
||||
obfs:
|
||||
type: salamander
|
||||
salamander:
|
||||
password: $GENERATED_OBFS_PASSWORD
|
||||
|
||||
masquerade:
|
||||
type: proxy
|
||||
proxy:
|
||||
url: https://ya.ru/
|
||||
rewriteHost: true
|
||||
EOF
|
||||
|
||||
systemctl daemon-reload
|
||||
systemctl enable --now hysteria-server.service
|
||||
systemctl restart hysteria-server.service
|
||||
|
||||
log "Hysteria 2 установлена на порту $RANDOM_FREE_PORT"
|
||||
else
|
||||
log "Hysteria 2 уже установлена, пропускаем."
|
||||
fi
|
||||
|
||||
#################################
|
||||
# ГЕНЕРАЦИЯ КОНФИГОВ
|
||||
#################################
|
||||
log "Генерация конфигурационных файлов..."
|
||||
|
||||
mkdir -p "$PROJECT_DIR/server"
|
||||
mkdir -p "$PROJECT_DIR/client"
|
||||
cd "$PROJECT_DIR"
|
||||
|
||||
cat > server/.env <<EOF
|
||||
DB_HOST=localhost
|
||||
DB_PORT=5432
|
||||
DB_USERNAME=admin
|
||||
DB_PASSWORD=${DB_PASS}
|
||||
DB_NAME=3dp_manager
|
||||
ADMIN_LOGIN=${ADMIN_USER}
|
||||
ADMIN_PASSWORD=${ADMIN_PASS}
|
||||
PORT=3100
|
||||
LOG_LEVEL=error
|
||||
ALLOWED_ORIGINS=${ALLOWED_ORIGINS}
|
||||
EOF
|
||||
|
||||
if [[ "$USE_SSL" == "true" ]]; then
|
||||
cat > client/nginx-client.conf <<EOF
|
||||
server {
|
||||
listen 443 ssl;
|
||||
server_name $UI_HOST;
|
||||
root /usr/share/nginx/html;
|
||||
index index.html;
|
||||
client_max_body_size 50M;
|
||||
|
||||
ssl_certificate /etc/nginx/certs/fullchain.pem;
|
||||
ssl_certificate_key /etc/nginx/certs/privkey.pem;
|
||||
|
||||
location / {
|
||||
try_files \$uri \$uri/ /index.html;
|
||||
}
|
||||
location /api/ {
|
||||
proxy_pass http://backend:3100/api/;
|
||||
proxy_set_header Host \$http_host;
|
||||
proxy_set_header X-Real-IP \$remote_addr;
|
||||
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto \$scheme;
|
||||
proxy_connect_timeout 10s;
|
||||
proxy_send_timeout 650s;
|
||||
proxy_read_timeout 650s;
|
||||
}
|
||||
location /bus/ {
|
||||
proxy_pass http://backend:3100/bus/;
|
||||
proxy_set_header Host \$http_host;
|
||||
proxy_set_header X-Real-IP \$remote_addr;
|
||||
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto \$scheme;
|
||||
proxy_connect_timeout 10s;
|
||||
proxy_send_timeout 650s;
|
||||
proxy_read_timeout 650s;
|
||||
}
|
||||
}
|
||||
EOF
|
||||
|
||||
cat > docker-compose.yml <<EOF
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:18-alpine
|
||||
container_name: 3dp-postgres
|
||||
restart: always
|
||||
environment:
|
||||
POSTGRES_USER: admin
|
||||
POSTGRES_PASSWORD: ${DB_PASS}
|
||||
POSTGRES_DB: 3dp_manager
|
||||
volumes:
|
||||
- pg_data:/var/lib/postgresql/data
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U admin -d 3dp_manager"]
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
networks:
|
||||
- app-network
|
||||
|
||||
backend:
|
||||
build: $SOURCE_DIR/server
|
||||
container_name: 3dp-backend
|
||||
restart: always
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
environment:
|
||||
DB_HOST: postgres
|
||||
DB_PORT: 5432
|
||||
DB_USERNAME: admin
|
||||
DB_PASSWORD: ${DB_PASS}
|
||||
DB_NAME: 3dp_manager
|
||||
JWT_SECRET: ${JWT_SECRET}
|
||||
ADMIN_LOGIN: ${ADMIN_USER}
|
||||
ADMIN_PASSWORD: ${ADMIN_PASS}
|
||||
PORT: 3100
|
||||
LOG_LEVEL: error
|
||||
ALLOWED_ORIGINS: ${ALLOWED_ORIGINS}
|
||||
volumes:
|
||||
- /etc/hysteria/config.yaml:/etc/hysteria/config.yaml:ro
|
||||
networks:
|
||||
- app-network
|
||||
|
||||
frontend:
|
||||
build: $SOURCE_DIR/client
|
||||
container_name: 3dp-frontend
|
||||
restart: always
|
||||
depends_on:
|
||||
- backend
|
||||
ports:
|
||||
- "${FINAL_PORT}:443"
|
||||
volumes:
|
||||
- ./client/nginx-client.conf:/etc/nginx/conf.d/default.conf:ro
|
||||
- ${CERT_PATH}:/etc/nginx/certs/fullchain.pem:ro
|
||||
- ${KEY_PATH}:/etc/nginx/certs/privkey.pem:ro
|
||||
networks:
|
||||
- app-network
|
||||
|
||||
volumes:
|
||||
pg_data:
|
||||
|
||||
networks:
|
||||
app-network:
|
||||
driver: bridge
|
||||
EOF
|
||||
|
||||
else
|
||||
cat > client/nginx-client.conf <<EOF
|
||||
server {
|
||||
listen 80;
|
||||
server_name localhost;
|
||||
root /usr/share/nginx/html;
|
||||
index index.html;
|
||||
client_max_body_size 50M;
|
||||
|
||||
location / {
|
||||
try_files \$uri \$uri/ /index.html;
|
||||
}
|
||||
location /api/ {
|
||||
proxy_pass http://backend:3100/api/;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade \$http_upgrade;
|
||||
proxy_set_header Connection 'upgrade';
|
||||
proxy_set_header Host \$http_host;
|
||||
proxy_set_header X-Forwarded-Proto \$scheme;
|
||||
proxy_cache_bypass \$http_upgrade;
|
||||
proxy_set_header X-Real-IP \$remote_addr;
|
||||
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
|
||||
proxy_connect_timeout 10s;
|
||||
proxy_send_timeout 650s;
|
||||
proxy_read_timeout 650s;
|
||||
}
|
||||
location /bus/ {
|
||||
proxy_pass http://backend:3100/bus/;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade \$http_upgrade;
|
||||
proxy_set_header Connection 'upgrade';
|
||||
proxy_set_header Host \$http_host;
|
||||
proxy_set_header X-Forwarded-Proto \$scheme;
|
||||
proxy_cache_bypass \$http_upgrade;
|
||||
proxy_set_header X-Real-IP \$remote_addr;
|
||||
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
|
||||
proxy_connect_timeout 10s;
|
||||
proxy_send_timeout 650s;
|
||||
proxy_read_timeout 650s;
|
||||
}
|
||||
}
|
||||
EOF
|
||||
|
||||
cat > docker-compose.yml <<EOF
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:18-alpine
|
||||
container_name: 3dp-postgres
|
||||
restart: always
|
||||
environment:
|
||||
POSTGRES_USER: admin
|
||||
POSTGRES_PASSWORD: ${DB_PASS}
|
||||
POSTGRES_DB: 3dp_manager
|
||||
volumes:
|
||||
- pg_data:/var/lib/postgresql/data
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U admin -d 3dp_manager"]
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
networks:
|
||||
- app-network
|
||||
|
||||
backend:
|
||||
build: $SOURCE_DIR/server
|
||||
container_name: 3dp-backend
|
||||
restart: always
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
environment:
|
||||
DB_HOST: postgres
|
||||
DB_PORT: 5432
|
||||
DB_USERNAME: admin
|
||||
DB_PASSWORD: ${DB_PASS}
|
||||
DB_NAME: 3dp_manager
|
||||
JWT_SECRET: ${JWT_SECRET}
|
||||
ADMIN_LOGIN: ${ADMIN_USER}
|
||||
ADMIN_PASSWORD: ${ADMIN_PASS}
|
||||
PORT: 3100
|
||||
LOG_LEVEL: error
|
||||
ALLOWED_ORIGINS: ${ALLOWED_ORIGINS}
|
||||
volumes:
|
||||
- /etc/hysteria/config.yaml:/etc/hysteria/config.yaml:ro
|
||||
networks:
|
||||
- app-network
|
||||
|
||||
frontend:
|
||||
build: $SOURCE_DIR/client
|
||||
container_name: 3dp-frontend
|
||||
restart: always
|
||||
depends_on:
|
||||
- backend
|
||||
ports:
|
||||
- "${FINAL_PORT}:80"
|
||||
volumes:
|
||||
- ./client/nginx-client.conf:/etc/nginx/conf.d/default.conf:ro
|
||||
networks:
|
||||
- app-network
|
||||
|
||||
volumes:
|
||||
pg_data:
|
||||
|
||||
networks:
|
||||
app-network:
|
||||
driver: bridge
|
||||
EOF
|
||||
fi
|
||||
|
||||
#################################
|
||||
# СБОРКА И ЗАПУСК
|
||||
#################################
|
||||
log "Сборка и запуск контейнеров..."
|
||||
"${COMPOSE_CMD[@]}" down || true
|
||||
"${COMPOSE_CMD[@]}" up --build -d --remove-orphans
|
||||
|
||||
if ! check_containers_running 60; then
|
||||
error "Не удалось запустить контейнеры. Логи:"
|
||||
"${COMPOSE_CMD[@]}" logs --tail=50
|
||||
die "Установка прервана из-за ошибки запуска контейнеров"
|
||||
fi
|
||||
|
||||
log "Очистка кэша сборки..."
|
||||
docker image prune -f
|
||||
|
||||
# Firewall
|
||||
if LC_ALL=C ufw status 2>/dev/null | grep -q "Status: active"; then
|
||||
log "Найден активный UFW. Настраиваю правила..."
|
||||
ufw allow 443/tcp
|
||||
ufw allow 443/udp
|
||||
ufw allow 8443/tcp
|
||||
ufw allow 8443/udp
|
||||
ufw allow 10000:60000/tcp
|
||||
ufw allow 10000:60000/udp
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "==================================================="
|
||||
if [[ "$USE_SSL" == "true" ]]; then
|
||||
echo -e "${GREEN}✔ Установка завершена! Доступно по адресу: https://${UI_HOST}:${FINAL_PORT}${NC}"
|
||||
else
|
||||
echo -e "${GREEN}✔ Установка завершена! Доступно по адресу: http://${UI_HOST}:${FINAL_PORT}${NC}"
|
||||
fi
|
||||
echo -e "${GREEN}Логин: ${ADMIN_USER}${NC}"
|
||||
echo -e "${GREEN}Пароль: ${ADMIN_PASS}${NC}"
|
||||
echo ""
|
||||
echo "Немедленно измените пароль в Настройках утилиты!"
|
||||
echo "==================================================="
|
||||
+128
-51
@@ -83,6 +83,19 @@ check_containers_running() {
|
||||
return 1
|
||||
}
|
||||
|
||||
get_random_port() {
|
||||
local MIN=${1:-3000}
|
||||
local MAX=${2:-6999}
|
||||
|
||||
while :; do
|
||||
PORT=$(shuf -i "$MIN-$MAX" -n 1)
|
||||
if ! ss -ltun | awk '{print $4}' | grep -q ":$PORT\$"; then
|
||||
echo "$PORT"
|
||||
return
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
#################################
|
||||
# ASCII-баннер
|
||||
#################################
|
||||
@@ -178,67 +191,147 @@ mkdir -p "$PROJECT_DIR/client"
|
||||
cd "$PROJECT_DIR"
|
||||
|
||||
#################################
|
||||
# СБОР ДАННЫХ
|
||||
# СБОР ДАННЫХ: SSL / HTTPS
|
||||
#################################
|
||||
read -rp "Введите домен сервера (если пропустить, будет использоваться IP без HTTPS): " INPUT_HOST
|
||||
|
||||
UI_HOST=""
|
||||
USE_SSL=false
|
||||
CERT_PATH=""
|
||||
KEY_PATH=""
|
||||
SKIP_SSL_SETUP=false
|
||||
|
||||
echo ""
|
||||
echo -e "${GREEN}Выберите тип SSL/HTTPS сертификации:${NC}"
|
||||
echo " 1) HTTPS — Let's Encrypt (нужен реальный домен, привязанный к IP, + открыты порты 80/443 в UFW)"
|
||||
echo " 2) HTTPS — Self-signed (самоподписанный на IP сервера, для тестов/VM)"
|
||||
echo " 3) HTTPS — Свои сертификаты (указать пути)"
|
||||
echo " 4) HTTP — без шифрования"
|
||||
echo ""
|
||||
printf "Ваш выбор (1/2/3/4) [\033[0;31m4\033[0m]: "
|
||||
read -r ssl_choice
|
||||
|
||||
case "$ssl_choice" in
|
||||
1)
|
||||
echo ""
|
||||
read -rp "Введите домен (должен быть привязан к IP этого сервера): " INPUT_HOST
|
||||
|
||||
if [ -z "$INPUT_HOST" ]; then
|
||||
UI_HOST=$(hostname -I | awk '{print $1}')
|
||||
log "Домен не указан. Используется локальный IP: $UI_HOST"
|
||||
log "Режим HTTPS принудительно отключен для IP-адреса."
|
||||
|
||||
USE_SSL=false
|
||||
SKIP_SSL_SETUP=true
|
||||
warn "Домен не указан. Переключение на HTTP."
|
||||
else
|
||||
UI_HOST=$INPUT_HOST
|
||||
SKIP_SSL_SETUP=false
|
||||
fi
|
||||
|
||||
# --- 2. Настройка SSL (Только если введен домен) ---
|
||||
if [[ "$SKIP_SSL_SETUP" == "false" ]]; then
|
||||
# Пытаемся автоматически найти сертификаты Let's Encrypt
|
||||
UI_HOST="$INPUT_HOST"
|
||||
LE_CERT="/etc/letsencrypt/live/$UI_HOST/fullchain.pem"
|
||||
LE_KEY="/etc/letsencrypt/live/$UI_HOST/privkey.pem"
|
||||
|
||||
if [[ -f "$LE_CERT" && -f "$LE_KEY" ]]; then
|
||||
log "Найдены сертификаты Let's Encrypt."
|
||||
# Проверяем что сертификат не истёк и ключ соответствует
|
||||
if openssl x509 -checkend 0 -noout -in "$LE_CERT" 2>/dev/null && \
|
||||
openssl x509 -noout -modulus -in "$LE_CERT" 2>/dev/null | md5sum > /tmp/cert_hash && \
|
||||
openssl rsa -noout -modulus -in "$LE_KEY" 2>/dev/null | md5sum > /tmp/key_hash && \
|
||||
diff -q /tmp/cert_hash /tmp/key_hash >/dev/null 2>&1; then
|
||||
log "Найдены валидные сертификаты для $UI_HOST."
|
||||
USE_SSL=true
|
||||
CERT_PATH="$LE_CERT"
|
||||
KEY_PATH="$LE_KEY"
|
||||
else
|
||||
# Спрашиваем пользователя, если авто-поиск не дал результата
|
||||
read -rp "Использовать SSL (свои сертификаты)? (y/n): " ssl_ans
|
||||
if [[ "$ssl_ans" =~ ^[Yy]$ ]]; then
|
||||
warn "Найдены сертификаты для $UI_HOST, но они невалидны. Перегенерируем."
|
||||
# Удаляем старые и генерируем новые
|
||||
rm -rf "/etc/letsencrypt/live/$UI_HOST"
|
||||
mkdir -p "/etc/letsencrypt/live/$UI_HOST"
|
||||
openssl req -x509 -nodes -days 365 \
|
||||
-newkey rsa:2048 \
|
||||
-keyout "/etc/letsencrypt/live/$UI_HOST/privkey.pem" \
|
||||
-out "/etc/letsencrypt/live/$UI_HOST/fullchain.pem" \
|
||||
-subj "/CN=$UI_HOST" \
|
||||
-addext "subjectAltName=IP:$UI_HOST" 2>/dev/null
|
||||
if [[ -f "$LE_CERT" && -f "$LE_KEY" ]]; then
|
||||
USE_SSL=true
|
||||
CERT_PATH="$LE_CERT"
|
||||
KEY_PATH="$LE_KEY"
|
||||
log "Self-signed сертификат перегенерирован для $UI_HOST"
|
||||
warn "Браузер будет предупреждать — это нормально для тестов."
|
||||
else
|
||||
warn "Не удалось сгенерировать сертификат. Переключение на HTTP."
|
||||
fi
|
||||
fi
|
||||
else
|
||||
log "Получение Let's Encrypt сертификата для $UI_HOST..."
|
||||
read -e -p "Email для уведомлений Let's Encrypt: " LE_EMAIL
|
||||
LE_EMAIL=$(echo "$LE_EMAIL" | tr -cd 'a-zA-Z0-9.@_-')
|
||||
|
||||
if command -v certbot &>/dev/null; then
|
||||
log "Certbot уже установлен."
|
||||
else
|
||||
log "Установка certbot..."
|
||||
apt update
|
||||
apt install -y certbot
|
||||
fi
|
||||
|
||||
certbot certonly --standalone \
|
||||
--agree-tos \
|
||||
--non-interactive \
|
||||
--email "$LE_EMAIL" \
|
||||
-d "$UI_HOST" || die "Не удалось получить сертификат Let's Encrypt"
|
||||
|
||||
if [[ -f "$LE_CERT" && -f "$LE_KEY" ]]; then
|
||||
USE_SSL=true
|
||||
CERT_PATH="$LE_CERT"
|
||||
KEY_PATH="$LE_KEY"
|
||||
log "Let's Encrypt сертификат получен для $UI_HOST"
|
||||
else
|
||||
warn "Сертификат не получен. Переключение на HTTP."
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
;;
|
||||
|
||||
2)
|
||||
UI_HOST=$(hostname -I | awk '{print $1}')
|
||||
log "Генерация самоподписанного сертификата для $UI_HOST..."
|
||||
mkdir -p "/etc/letsencrypt/live/$UI_HOST"
|
||||
openssl req -x509 -nodes -days 365 \
|
||||
-newkey rsa:2048 \
|
||||
-keyout "/etc/letsencrypt/live/$UI_HOST/privkey.pem" \
|
||||
-out "/etc/letsencrypt/live/$UI_HOST/fullchain.pem" \
|
||||
-subj "/CN=$UI_HOST" \
|
||||
-addext "subjectAltName=IP:$UI_HOST" 2>/dev/null
|
||||
|
||||
if [[ -f "/etc/letsencrypt/live/$UI_HOST/fullchain.pem" && -f "/etc/letsencrypt/live/$UI_HOST/privkey.pem" ]]; then
|
||||
USE_SSL=true
|
||||
CERT_PATH="/etc/letsencrypt/live/$UI_HOST/fullchain.pem"
|
||||
KEY_PATH="/etc/letsencrypt/live/$UI_HOST/privkey.pem"
|
||||
log "Self-signed сертификат сгенерирован для $UI_HOST"
|
||||
warn "Браузер будет предупреждать — это нормально для тестов."
|
||||
else
|
||||
warn "Не удалось сгенерировать сертификат. Переключение на HTTP."
|
||||
fi
|
||||
;;
|
||||
|
||||
3)
|
||||
read -rp "Путь к fullchain.pem: " user_cert
|
||||
read -rp "Путь к privkey.pem: " user_key
|
||||
if [[ -f "$user_cert" && -f "$user_key" ]]; then
|
||||
USE_SSL=true
|
||||
CERT_PATH="$user_cert"
|
||||
KEY_PATH="$user_key"
|
||||
UI_HOST=$(hostname -I | awk '{print $1}')
|
||||
else
|
||||
warn "Файлы сертификатов не найдены. Будет использоваться HTTP."
|
||||
fi
|
||||
fi
|
||||
warn "Файлы не найдены. Переключение на HTTP."
|
||||
fi
|
||||
;;
|
||||
|
||||
*)
|
||||
UI_HOST=$(hostname -I | awk '{print $1}')
|
||||
log "Будет использоваться HTTP."
|
||||
;;
|
||||
esac
|
||||
|
||||
if [ -z "$UI_HOST" ]; then
|
||||
UI_HOST=$(hostname -I | awk '{print $1}')
|
||||
log "Используется IP сервера: $UI_HOST"
|
||||
fi
|
||||
|
||||
get_random_port() {
|
||||
local MIN=${1:-3000}
|
||||
local MAX=${2:-6999}
|
||||
#################################
|
||||
# СБОР ДАННЫХ
|
||||
################################
|
||||
|
||||
while :; do
|
||||
PORT=$(shuf -i "$MIN-$MAX" -n 1)
|
||||
if ! ss -ltun | awk '{print $4}' | grep -q ":$PORT\$"; then
|
||||
echo "$PORT"
|
||||
return
|
||||
fi
|
||||
done
|
||||
}
|
||||
FINAL_PORT=$(get_random_port)
|
||||
|
||||
# --- 4. Генерация паролей ---
|
||||
@@ -258,22 +351,6 @@ else
|
||||
fi
|
||||
log "Сгенерированы секретные ключи для БД и JWT."
|
||||
|
||||
#################################
|
||||
# ЛОГИРОВАНИЕ УЧЁТНЫХ ДАННЫХ
|
||||
#################################
|
||||
echo ""
|
||||
echo "==================================================="
|
||||
echo " УЧЁТНЫЕ ДАННЫЕ АДМИНИСТРАТОРА"
|
||||
echo "==================================================="
|
||||
echo " ADMIN_LOGIN: ${ADMIN_USER}"
|
||||
echo " ADMIN_PASSWORD: ${ADMIN_PASS}"
|
||||
echo " POSTGRES_PASSWORD: ${DB_PASS}"
|
||||
echo " JWT_SECRET: ${JWT_SECRET}"
|
||||
echo ""
|
||||
echo " ⚠️ СОХРАНИТЕ ЭТИ ДАННЫЕ В БЕЗОПАСНОМ МЕСТЕ!"
|
||||
echo "==================================================="
|
||||
echo ""
|
||||
|
||||
#################################
|
||||
# Hysteria 2
|
||||
#################################
|
||||
|
||||
@@ -55,7 +55,7 @@ export class TunnelsService {
|
||||
`Начинаем установку редиректа на ${tunnel.ip} -> ${mainServerIp}`,
|
||||
);
|
||||
|
||||
const command = `export ORIGIN_IP="${mainServerIp}" && bash <(curl -fsSL https://raw.githubusercontent.com/denpiligrim/3dp-manager/main/forwarding_install.sh)`;
|
||||
const command = `sudo ORIGIN_IP="${mainServerIp}" bash -c "$(curl -sSL https://raw.githubusercontent.com/denpiligrim/3dp-manager/main/forwarding_install.sh)"`;
|
||||
|
||||
try {
|
||||
const output = await this.sshService.executeCommand(
|
||||
|
||||
@@ -1,384 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
#################################
|
||||
# TRAP
|
||||
#################################
|
||||
trap 'echo -e "\033[1;31m[ERROR]\033[0m Ошибка в строке $LINENO"; exit 1' ERR
|
||||
|
||||
#################################
|
||||
# HELPERS
|
||||
#################################
|
||||
log() { echo -e "\033[1;32m[INFO]\033[0m $1"; }
|
||||
warn() { echo -e "\033[1;33m[WARN]\033[0m $1"; }
|
||||
die() { echo -e "\033[1;31m[ERROR]\033[0m $1"; exit 1; }
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
Использование:
|
||||
update-custom.sh -r <repo_url> -b <branch> [-p <project_dir>] [-s <source_dir>]
|
||||
|
||||
Параметры:
|
||||
-r Git URL вашего форка (обязательно), например:
|
||||
https://github.com/<user>/3dp-manager.git
|
||||
-b Ветка с вашими правками (обязательно), например:
|
||||
dp-custom
|
||||
-p Папка установленного 3dp-manager (по умолчанию /opt/3dp-manager)
|
||||
-s Папка исходников для сборки (по умолчанию /opt/3dp-manager-src)
|
||||
-h Показать эту справку
|
||||
|
||||
Пример:
|
||||
./update-custom.sh -r https://github.com/iqubik/3dp-manager.git -b dp-custom
|
||||
EOF
|
||||
}
|
||||
|
||||
need_root() {
|
||||
[[ $EUID -eq 0 ]] || die "Запускать только от root"
|
||||
}
|
||||
|
||||
resolve_compose_cmd() {
|
||||
if docker compose version >/dev/null 2>&1; then
|
||||
COMPOSE_CMD=("docker" "compose")
|
||||
return 0
|
||||
fi
|
||||
|
||||
if command -v docker-compose >/dev/null 2>&1; then
|
||||
COMPOSE_CMD=("docker-compose")
|
||||
return 0
|
||||
fi
|
||||
|
||||
die "Не найден Docker Compose (ни v2 plugin, ни v1 binary)"
|
||||
}
|
||||
|
||||
check_containers_running() {
|
||||
log "Проверка статуса контейнеров..."
|
||||
local timeout=${1:-60}
|
||||
local elapsed=0
|
||||
local failed=0
|
||||
|
||||
while [ $elapsed -lt $timeout ]; do
|
||||
failed=0
|
||||
# Формат: NAME\tSTATUS (например: "3dp-postgres\tUp 2 days" или "3dp-postgres\tError")
|
||||
while IFS=$'\t' read -r container_name status; do
|
||||
if [ -n "$container_name" ] && [ -n "$status" ]; then
|
||||
# Проверяем, что статус содержит Up/running/healthy/restarting
|
||||
# Up, Up 2 days, Up Less than a second, (healthy), running, restarting
|
||||
if ! echo "$status" | grep -qiE "^up|running|healthy|restarting"; then
|
||||
failed=1
|
||||
warn "Контейнер $container_name в статусе: $status"
|
||||
fi
|
||||
fi
|
||||
done < <("${COMPOSE_CMD[@]}" ps --format "table {{.Name}}\t{{.Status}}" --all 2>/dev/null | tail -n +2)
|
||||
|
||||
if [ $failed -eq 0 ]; then
|
||||
log "Все контейнеры запущены успешно"
|
||||
return 0
|
||||
fi
|
||||
|
||||
sleep 2
|
||||
elapsed=$((elapsed + 2))
|
||||
done
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
check_and_fix_credentials() {
|
||||
log "Проверка учётных данных на безопасность..."
|
||||
|
||||
local env_file=".env"
|
||||
local credentials_changed=0
|
||||
|
||||
# Проверяем, существует ли .env файл
|
||||
if [[ ! -f "$env_file" ]]; then
|
||||
log "Создание .env файла с безопасными учётными данными..."
|
||||
|
||||
local db_pass jwt_secret admin_login admin_pass
|
||||
|
||||
db_pass=$(openssl rand -base64 12 | tr -dc 'A-Za-z0-9' | cut -c1-12)
|
||||
jwt_secret=$(openssl rand -base64 32)
|
||||
admin_login=$(openssl rand -base64 8 | tr -dc 'A-Za-z0-9' | cut -c1-8)
|
||||
admin_pass=$(openssl rand -base64 12 | tr -dc 'A-Za-z0-9' | cut -c1-12)
|
||||
|
||||
# Создаём .env файл
|
||||
cat > "$env_file" <<EOF
|
||||
POSTGRES_USER=admin
|
||||
POSTGRES_PASSWORD=${db_pass}
|
||||
POSTGRES_DB=3dp_manager
|
||||
JWT_SECRET=${jwt_secret}
|
||||
ADMIN_LOGIN=${admin_login}
|
||||
ADMIN_PASSWORD=${admin_pass}
|
||||
PORT=3100
|
||||
LOG_LEVEL=error
|
||||
ALLOWED_ORIGINS=
|
||||
EOF
|
||||
|
||||
log "Сгенерированы новые учётные данные:"
|
||||
log " ADMIN_LOGIN: ${admin_login}"
|
||||
log " ADMIN_PASSWORD: ${admin_pass}"
|
||||
log " POSTGRES_PASSWORD: ${db_pass}"
|
||||
log " JWT_SECRET: ${jwt_secret}"
|
||||
log "⚠️ Сохраните эти данные в безопасном месте!"
|
||||
|
||||
credentials_changed=1
|
||||
else
|
||||
local admin_login_val admin_pass_val jwt_secret_val db_pass_val
|
||||
|
||||
admin_login_val=$(grep -E "^ADMIN_LOGIN=" "$env_file" 2>/dev/null | cut -d'=' -f2 | tr -d '"' | tr -d "'" || echo "")
|
||||
admin_pass_val=$(grep -E "^ADMIN_PASSWORD=" "$env_file" 2>/dev/null | cut -d'=' -f2 | tr -d '"' | tr -d "'" || echo "")
|
||||
jwt_secret_val=$(grep -E "^JWT_SECRET=" "$env_file" 2>/dev/null | cut -d'=' -f2 | tr -d '"' | tr -d "'" || echo "")
|
||||
db_pass_val=$(grep -E "^POSTGRES_PASSWORD=" "$env_file" 2>/dev/null | cut -d'=' -f2 | tr -d '"' | tr -d "'" || echo "")
|
||||
|
||||
local needs_update=0
|
||||
|
||||
if [[ "$admin_login_val" == "admin" ]] || [[ -z "$admin_login_val" ]]; then
|
||||
warn "Обнаружен дефолтный ADMIN_LOGIN=admin"
|
||||
needs_update=1
|
||||
fi
|
||||
|
||||
if [[ "$admin_pass_val" == "admin" ]] || [[ -z "$admin_pass_val" ]]; then
|
||||
warn "Обнаружен дефолтный ADMIN_PASSWORD=admin"
|
||||
needs_update=1
|
||||
fi
|
||||
|
||||
if [[ "$jwt_secret_val" == "secretKey" ]] || [[ -z "$jwt_secret_val" ]]; then
|
||||
warn "Обнаружен дефолтный JWT_SECRET=secretKey"
|
||||
needs_update=1
|
||||
fi
|
||||
|
||||
if [[ "$db_pass_val" == "admin" ]] || [[ -z "$db_pass_val" ]]; then
|
||||
warn "Обнаружен дефолтный POSTGRES_PASSWORD=admin"
|
||||
needs_update=1
|
||||
fi
|
||||
|
||||
if [[ $needs_update -eq 1 ]]; then
|
||||
log "Генерация новых безопасных учётных данных..."
|
||||
|
||||
local new_db_pass new_jwt_secret new_admin_login new_admin_pass
|
||||
|
||||
new_db_pass=$(openssl rand -base64 12 | tr -dc 'A-Za-z0-9' | cut -c1-12)
|
||||
new_jwt_secret=$(openssl rand -base64 32)
|
||||
new_admin_login=$(openssl rand -base64 8 | tr -dc 'A-Za-z0-9' | cut -c1-8)
|
||||
new_admin_pass=$(openssl rand -base64 12 | tr -dc 'A-Za-z0-9' | cut -c1-12)
|
||||
|
||||
# Сохраняем существующие значения, которые не нужно менять
|
||||
local existing_postgres_user
|
||||
existing_postgres_user=$(grep -E "^POSTGRES_USER=" "$env_file" 2>/dev/null | cut -d'=' -f2 || echo "admin")
|
||||
|
||||
# Создаём новый .env файл
|
||||
cat > "$env_file" <<EOF
|
||||
POSTGRES_USER=${existing_postgres_user:-admin}
|
||||
POSTGRES_PASSWORD=${new_db_pass}
|
||||
POSTGRES_DB=3dp_manager
|
||||
JWT_SECRET=${new_jwt_secret}
|
||||
ADMIN_LOGIN=${new_admin_login}
|
||||
ADMIN_PASSWORD=${new_admin_pass}
|
||||
PORT=3100
|
||||
LOG_LEVEL=error
|
||||
ALLOWED_ORIGINS=
|
||||
EOF
|
||||
|
||||
log "Сгенерированы новые учётные данные:"
|
||||
log " ADMIN_LOGIN: ${new_admin_login}"
|
||||
log " ADMIN_PASSWORD: ${new_admin_pass}"
|
||||
log " POSTGRES_PASSWORD: ${new_db_pass}"
|
||||
log " JWT_SECRET: ${new_jwt_secret}"
|
||||
log "⚠️ Сохраните эти данные в безопасном месте!"
|
||||
|
||||
credentials_changed=1
|
||||
else
|
||||
log "Учётные данные безопасны ✅"
|
||||
fi
|
||||
fi
|
||||
|
||||
return $credentials_changed
|
||||
}
|
||||
|
||||
ensure_nginx_api_timeouts() {
|
||||
local nginx_conf="$1"
|
||||
[[ -f "$nginx_conf" ]] || return 0
|
||||
|
||||
local tmp_file
|
||||
tmp_file="$(mktemp)"
|
||||
|
||||
awk '
|
||||
BEGIN { in_api = 0; in_bus = 0; injected = 0 }
|
||||
{
|
||||
line = $0
|
||||
|
||||
if (line ~ /^[[:space:]]*location[[:space:]]+\/api\/[[:space:]]*\{/) {
|
||||
in_api = 1
|
||||
in_bus = 0
|
||||
injected = 0
|
||||
}
|
||||
|
||||
if (line ~ /^[[:space:]]*location[[:space:]]+\/bus\/[[:space:]]*\{/) {
|
||||
in_bus = 1
|
||||
in_api = 0
|
||||
injected = 0
|
||||
}
|
||||
|
||||
if ((in_api || in_bus) && line ~ /proxy_(connect|send|read)_timeout[[:space:]]+[0-9]+s;/) {
|
||||
next
|
||||
}
|
||||
|
||||
print line
|
||||
|
||||
if ((in_api || in_bus) && line ~ /proxy_set_header[[:space:]]+X-Forwarded-For[[:space:]]+/ && injected == 0) {
|
||||
print " proxy_set_header X-Forwarded-Proto $scheme;"
|
||||
print " proxy_connect_timeout 10s;"
|
||||
print " proxy_send_timeout 650s;"
|
||||
print " proxy_read_timeout 650s;"
|
||||
injected = 1
|
||||
}
|
||||
|
||||
if ((in_api || in_bus) && line ~ /^[[:space:]]*}/) {
|
||||
in_api = 0
|
||||
in_bus = 0
|
||||
injected = 0
|
||||
}
|
||||
}
|
||||
' "$nginx_conf" > "$tmp_file"
|
||||
|
||||
mv "$tmp_file" "$nginx_conf"
|
||||
}
|
||||
|
||||
ensure_bus_location() {
|
||||
local nginx_conf="$1"
|
||||
[[ -f "$nginx_conf" ]] || return 0
|
||||
|
||||
# Проверяем, есть ли уже location /bus/
|
||||
if grep -q "location /bus/" "$nginx_conf"; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
local tmp_file
|
||||
tmp_file="$(mktemp)"
|
||||
|
||||
awk '
|
||||
{
|
||||
print $0
|
||||
if ($0 ~ /^[[:space:]]*location[[:space:]]+\/api\//) {
|
||||
found_api = 1
|
||||
}
|
||||
if (found_api && $0 ~ /^[[:space:]]*\}/) {
|
||||
print ""
|
||||
print " location /bus/ {"
|
||||
print " proxy_pass http://backend:3100/bus/;"
|
||||
print " proxy_set_header Host $http_host;"
|
||||
print " proxy_set_header X-Real-IP $remote_addr;"
|
||||
print " proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;"
|
||||
print " proxy_set_header X-Forwarded-Proto $scheme;"
|
||||
print " proxy_connect_timeout 10s;"
|
||||
print " proxy_send_timeout 650s;"
|
||||
print " proxy_read_timeout 650s;"
|
||||
print " }"
|
||||
found_api = 0
|
||||
}
|
||||
}
|
||||
' "$nginx_conf" > "$tmp_file"
|
||||
|
||||
mv "$tmp_file" "$nginx_conf"
|
||||
}
|
||||
|
||||
#################################
|
||||
# PARSE ARGS
|
||||
#################################
|
||||
REPO_URL="https://github.com/iqubik/3dp-manager.git"
|
||||
BRANCH="dp-custom"
|
||||
PROJECT_DIR="/opt/3dp-manager"
|
||||
SOURCE_DIR="/opt/3dp-manager-src"
|
||||
|
||||
while getopts ":r:b:p:s:h" opt; do
|
||||
case "$opt" in
|
||||
r) REPO_URL="$OPTARG" ;;
|
||||
b) BRANCH="$OPTARG" ;;
|
||||
p) PROJECT_DIR="$OPTARG" ;;
|
||||
s) SOURCE_DIR="$OPTARG" ;;
|
||||
h) usage; exit 0 ;;
|
||||
:) die "Параметр -$OPTARG требует значение" ;;
|
||||
\?) die "Неизвестный параметр: -$OPTARG" ;;
|
||||
esac
|
||||
done
|
||||
|
||||
need_root
|
||||
[[ -d "$PROJECT_DIR" ]] || die "Папка проекта не найдена: $PROJECT_DIR"
|
||||
[[ -f "$PROJECT_DIR/docker-compose.yml" ]] || die "Не найден docker-compose.yml в $PROJECT_DIR"
|
||||
|
||||
command -v git >/dev/null 2>&1 || die "git не установлен"
|
||||
command -v docker >/dev/null 2>&1 || die "docker не установлен"
|
||||
resolve_compose_cmd
|
||||
log "Compose команда: ${COMPOSE_CMD[*]}"
|
||||
|
||||
#################################
|
||||
# CHECK AND FIX CREDENTIALS
|
||||
#################################
|
||||
check_and_fix_credentials || true
|
||||
|
||||
#################################
|
||||
# FETCH AND UPDATE SOURCE
|
||||
#################################
|
||||
log "Подготовка исходников в $SOURCE_DIR"
|
||||
if [[ ! -d "$SOURCE_DIR/.git" ]]; then
|
||||
if [[ -e "$SOURCE_DIR" ]]; then
|
||||
die "Путь $SOURCE_DIR существует, но это не git-репозиторий. Укажите другой -s или подготовьте папку вручную."
|
||||
fi
|
||||
git clone --single-branch --branch "$BRANCH" "$REPO_URL" "$SOURCE_DIR"
|
||||
else
|
||||
cd "$SOURCE_DIR"
|
||||
git remote set-url origin "$REPO_URL"
|
||||
git fetch origin "$BRANCH"
|
||||
|
||||
if git show-ref --verify --quiet "refs/heads/$BRANCH"; then
|
||||
git checkout "$BRANCH"
|
||||
# Merge from FETCH_HEAD to support repos cloned with --single-branch
|
||||
# where origin/<branch> may not exist.
|
||||
git merge --ff-only FETCH_HEAD || die "Не удалось fast-forward merge. Проверьте локальные изменения в $SOURCE_DIR."
|
||||
else
|
||||
git checkout -b "$BRANCH" FETCH_HEAD
|
||||
fi
|
||||
fi
|
||||
|
||||
#################################
|
||||
# CREATE COMPOSE OVERRIDE
|
||||
#################################
|
||||
COMPOSE_OVERRIDE="$PROJECT_DIR/docker-compose.custom.yml"
|
||||
cat > "$COMPOSE_OVERRIDE" <<EOF
|
||||
services:
|
||||
backend:
|
||||
build: $SOURCE_DIR/server
|
||||
image: 3dp-manager-server:custom
|
||||
frontend:
|
||||
build: $SOURCE_DIR/client
|
||||
image: 3dp-manager-client:custom
|
||||
EOF
|
||||
|
||||
#################################
|
||||
# FIX NGINX CONFIG
|
||||
#################################
|
||||
ensure_nginx_api_timeouts "$PROJECT_DIR/client/nginx-client.conf"
|
||||
ensure_bus_location "$PROJECT_DIR/client/nginx-client.conf"
|
||||
|
||||
#################################
|
||||
# BUILD AND RESTART
|
||||
#################################
|
||||
log "Сборка custom-образов backend/frontend"
|
||||
cd "$PROJECT_DIR"
|
||||
"${COMPOSE_CMD[@]}" -f docker-compose.yml -f docker-compose.custom.yml build backend frontend
|
||||
|
||||
log "Перезапуск контейнеров с custom-образами"
|
||||
"${COMPOSE_CMD[@]}" -f docker-compose.yml -f docker-compose.custom.yml up -d --remove-orphans backend frontend
|
||||
|
||||
# Проверка: все ли контейнеры запустились
|
||||
if ! check_containers_running 60; then
|
||||
error "Не удалось запустить контейнеры. Логи:"
|
||||
"${COMPOSE_CMD[@]}" -f docker-compose.yml -f docker-compose.custom.yml logs --tail=50
|
||||
die "Обновление прервано из-за ошибки запуска контейнеров"
|
||||
fi
|
||||
|
||||
if "${COMPOSE_CMD[@]}" -f docker-compose.yml -f docker-compose.custom.yml exec -T backend sh -lc "command -v RealiTLScanner-linux-64 >/dev/null"; then
|
||||
log "Scanner binary найден в backend-контейнере"
|
||||
else
|
||||
warn "Scanner binary не найден в backend-контейнере"
|
||||
fi
|
||||
|
||||
log "Готово: VPS обновлен на ваш fork/branch"
|
||||
@@ -93,13 +93,6 @@ LOG_LEVEL=error
|
||||
ALLOWED_ORIGINS=
|
||||
EOF
|
||||
|
||||
log "Сгенерированы новые учётные данные:"
|
||||
log " ADMIN_LOGIN: ${admin_login}"
|
||||
log " ADMIN_PASSWORD: ${admin_pass}"
|
||||
log " POSTGRES_PASSWORD: ${db_pass}"
|
||||
log " JWT_SECRET: ${jwt_secret}"
|
||||
log "⚠️ Сохраните эти данные в безопасном месте!"
|
||||
|
||||
credentials_changed=1
|
||||
else
|
||||
# Проверяем, не используются ли дефолтные значения
|
||||
@@ -163,13 +156,6 @@ ADMIN_LOGIN=${new_admin_login}
|
||||
ADMIN_PASSWORD=${new_admin_pass}
|
||||
EOF
|
||||
|
||||
log "Сгенерированы новые учётные данные:"
|
||||
log " ADMIN_LOGIN: ${new_admin_login}"
|
||||
log " ADMIN_PASSWORD: ${new_admin_pass}"
|
||||
log " POSTGRES_PASSWORD: ${new_db_pass}"
|
||||
log " JWT_SECRET: ${new_jwt_secret}"
|
||||
log "⚠️ Сохраните эти данные в безопасном месте!"
|
||||
|
||||
credentials_changed=1
|
||||
else
|
||||
log "Учётные данные безопасны ✅"
|
||||
|
||||
Reference in New Issue
Block a user