385 lines
13 KiB
Bash
385 lines
13 KiB
Bash
#!/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"
|