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