From e705232cf22e085565086985fa2bd512999cd193 Mon Sep 17 00:00:00 2001 From: iqubik Date: Sat, 4 Apr 2026 21:12:19 +0300 Subject: [PATCH] local branch --- client/nginx.conf | 9 - debug.md | 333 ++++++++++++++++++++++++ docker-compose.yml | 4 - install-custom.sh | 633 +++++++++++++++++++++++++++++++++++++++++++++ update-custom.sh | 397 ++++++++++++++++++++++++++++ 5 files changed, 1363 insertions(+), 13 deletions(-) create mode 100644 debug.md create mode 100644 install-custom.sh create mode 100644 update-custom.sh diff --git a/client/nginx.conf b/client/nginx.conf index 33fc50b..f45aae1 100644 --- a/client/nginx.conf +++ b/client/nginx.conf @@ -39,12 +39,3 @@ server { proxy_read_timeout 650s; } } - -server { - listen 3100; - server_name localhost; - location / { - proxy_pass http://backend:3100/; - proxy_set_header Host $http_host; - } -} diff --git a/debug.md b/debug.md new file mode 100644 index 0000000..a52bf15 --- /dev/null +++ b/debug.md @@ -0,0 +1,333 @@ +# Локальная отладка 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 идут только целевые изменения фичи. + +## Обновление 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 ... + +# 5) Пушим чистую ветку и обновляем PR автору +git push origin dp-fix +``` + +Если конфликт при cherry-pick: + +```bash +git status +# исправить конфликтные файлы +git add +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 + ``` diff --git a/docker-compose.yml b/docker-compose.yml index a5d090b..aa4e52a 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -7,8 +7,6 @@ services: POSTGRES_USER: ${POSTGRES_USER} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} POSTGRES_DB: ${POSTGRES_DB} - ports: - - "5432:5432" volumes: - pg_data:/var/lib/postgresql/data networks: @@ -32,8 +30,6 @@ services: PORT: ${PORT} LOG_LEVEL: ${LOG_LEVEL} ALLOWED_ORIGINS: ${ALLOWED_ORIGINS:-} - ports: - - "${PORT}:${PORT}" networks: - app-network diff --git a/install-custom.sh b/install-custom.sh new file mode 100644 index 0000000..53785e9 --- /dev/null +++ b/install-custom.sh @@ -0,0 +1,633 @@ +#!/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 ] [-b ] [-p ] [-s ] + +Параметры: + -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 < /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 < server/.env < client/nginx-client.conf < docker-compose.yml < client/nginx-client.conf < docker-compose.yml </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 "===================================================" diff --git a/update-custom.sh b/update-custom.sh new file mode 100644 index 0000000..f299e05 --- /dev/null +++ b/update-custom.sh @@ -0,0 +1,397 @@ +#!/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 -b [-p ] [-s ] + +Параметры: + -r Git URL вашего форка (обязательно), например: + https://github.com//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" </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" < "$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_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/ may not exist. + git merge --ff-only FETCH_HEAD || die "Не удалось fast-forward merge. Проверьте локальные изменения в $SOURCE_DIR." + else + git checkout -b "$BRANCH" FETCH_HEAD + fi +fi + +################################# +# UPDATE DOCKER-COMPOSE FROM REPO +################################# +log "Обновление docker-compose.yml из ветки $BRANCH..." +curl -fsSL "${REPO_URL%.git}/raw/branch/$BRANCH/docker-compose.yml" \ + -o "$PROJECT_DIR/docker-compose.yml" 2>/dev/null || { + # Fallback: скачиваем через raw.githubusercontent.com + if [[ "$REPO_URL" =~ ^https://github\.com/([^/]+)/([^/]+?)(\.git)?$ ]]; then + curl -fsSL "https://raw.githubusercontent.com/${BASH_REMATCH[1]}/${BASH_REMATCH[2]}/$BRANCH/docker-compose.yml" \ + -o "$PROJECT_DIR/docker-compose.yml" || die "Не удалось скачать docker-compose.yml" + else + die "Не удалось скачать docker-compose.yml из $REPO_URL" + fi +} + +################################# +# CREATE COMPOSE OVERRIDE +################################# +COMPOSE_OVERRIDE="$PROJECT_DIR/docker-compose.custom.yml" +cat > "$COMPOSE_OVERRIDE" </dev/null"; then + log "Scanner binary найден в backend-контейнере" +else + warn "Scanner binary не найден в backend-контейнере" +fi + +log "Готово: VPS обновлен на ваш fork/branch"