diff --git a/debug.md b/debug.md deleted file mode 100644 index dfc80c3..0000000 --- a/debug.md +++ /dev/null @@ -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 ... - -# 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/delete-custom.sh b/delete-custom.sh deleted file mode 100644 index 52a4b69..0000000 --- a/delete-custom.sh +++ /dev/null @@ -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 ] [-s ] [-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 сертификаты (если были)" diff --git a/delete.sh b/delete.sh index 10fcbb2..50235ea 100644 --- a/delete.sh +++ b/delete.sh @@ -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 diff --git a/forwarding_delete.sh b/forwarding_delete.sh index fd7bf78..61b1768 100644 --- a/forwarding_delete.sh +++ b/forwarding_delete.sh @@ -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 "Откат завершён. Для окончательного применения изменений перезагрузите систему!" \ No newline at end of file +log "Откат завершён. Для окончательного применения изменений система будет перезагружена!" + +(sleep 3 && reboot) >/dev/null 2>&1 & + +exit 0 \ No newline at end of file diff --git a/install-custom.sh b/install-custom.sh deleted file mode 100644 index 807feba..0000000 --- a/install-custom.sh +++ /dev/null @@ -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 ] [-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 && \ - 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 < 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/install.sh b/install.sh index bd37e0e..471a18e 100644 --- a/install.sh +++ b/install.sh @@ -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 -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 -else - UI_HOST=$INPUT_HOST - SKIP_SSL_SETUP=false -fi +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 -# --- 2. Настройка SSL (Только если введен домен) --- -if [[ "$SKIP_SSL_SETUP" == "false" ]]; then - # Пытаемся автоматически найти сертификаты Let's Encrypt - LE_CERT="/etc/letsencrypt/live/$UI_HOST/fullchain.pem" - LE_KEY="/etc/letsencrypt/live/$UI_HOST/privkey.pem" +case "$ssl_choice" in + 1) + echo "" + read -rp "Введите домен (должен быть привязан к IP этого сервера): " INPUT_HOST - if [[ -f "$LE_CERT" && -f "$LE_KEY" ]]; then - log "Найдены сертификаты Let's Encrypt." - USE_SSL=true - CERT_PATH="$LE_CERT" - KEY_PATH="$LE_KEY" + if [ -z "$INPUT_HOST" ]; then + warn "Домен не указан. Переключение на HTTP." else - # Спрашиваем пользователя, если авто-поиск не дал результата - read -rp "Использовать SSL (свои сертификаты)? (y/n): " ssl_ans - if [[ "$ssl_ans" =~ ^[Yy]$ ]]; then - 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" - else - warn "Файлы сертификатов не найдены. Будет использоваться HTTP." - fi + 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 -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 ################################# diff --git a/server/src/tunnels/tunnels.service.ts b/server/src/tunnels/tunnels.service.ts index efac23e..1ba1a2f 100644 --- a/server/src/tunnels/tunnels.service.ts +++ b/server/src/tunnels/tunnels.service.ts @@ -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( diff --git a/update-custom.sh b/update-custom.sh deleted file mode 100644 index 9aeabb2..0000000 --- a/update-custom.sh +++ /dev/null @@ -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 -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_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/ 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" </dev/null"; then - log "Scanner binary найден в backend-контейнере" -else - warn "Scanner binary не найден в backend-контейнере" -fi - -log "Готово: VPS обновлен на ваш fork/branch" diff --git a/update.sh b/update.sh index fb441dd..9a8f102 100644 --- a/update.sh +++ b/update.sh @@ -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 "Учётные данные безопасны ✅"