From 76ea21e32cc9824b5c44dbf4402c4cc3110873c5 Mon Sep 17 00:00:00 2001 From: igorsatsyuk Date: Sat, 9 May 2026 19:18:50 +0300 Subject: [PATCH 1/2] Add local dev start scripts and fix compose bootstrap flow --- docker-compose.yml | 43 +++++++++++++++--- scripts/START.md | 17 +++++++ scripts/install-common.ps1 | 21 +++++++++ scripts/start-autocomplete-service.ps1 | 49 +++++++++++++++++++++ scripts/start-cdc-service.ps1 | 50 +++++++++++++++++++++ scripts/start-frontend.ps1 | 29 ++++++++++++ scripts/start-infra.ps1 | 27 ++++++++++++ scripts/start-search-service.ps1 | 61 ++++++++++++++++++++++++++ 8 files changed, 290 insertions(+), 7 deletions(-) create mode 100644 scripts/START.md create mode 100644 scripts/install-common.ps1 create mode 100644 scripts/start-autocomplete-service.ps1 create mode 100644 scripts/start-cdc-service.ps1 create mode 100644 scripts/start-frontend.ps1 create mode 100644 scripts/start-infra.ps1 create mode 100644 scripts/start-search-service.ps1 diff --git a/docker-compose.yml b/docker-compose.yml index 753ffec..f036a3d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -60,12 +60,14 @@ services: depends_on: - zookeeper ports: - - "127.0.0.1:${KAFKA_PORT:-9092}:9092" + - "127.0.0.1:${KAFKA_PORT:-9092}:29092" environment: KAFKA_BROKER_ID: ${KAFKA_BROKER_ID:-1} KAFKA_ZOOKEEPER_CONNECT: ${KAFKA_ZOOKEEPER_CONNECT:-zookeeper:2181} - KAFKA_ADVERTISED_LISTENERS: ${KAFKA_ADVERTISED_LISTENERS:-PLAINTEXT://kafka:9092} - KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: ${KAFKA_LISTENER_SECURITY_PROTOCOL_MAP:-PLAINTEXT:PLAINTEXT} + KAFKA_LISTENERS: ${KAFKA_LISTENERS:-INTERNAL://0.0.0.0:9092,EXTERNAL://0.0.0.0:29092} + KAFKA_ADVERTISED_LISTENERS: ${KAFKA_ADVERTISED_LISTENERS:-INTERNAL://kafka:9092,EXTERNAL://localhost:${KAFKA_PORT:-9092}} + KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: ${KAFKA_LISTENER_SECURITY_PROTOCOL_MAP:-INTERNAL:PLAINTEXT,EXTERNAL:PLAINTEXT} + KAFKA_INTER_BROKER_LISTENER_NAME: ${KAFKA_INTER_BROKER_LISTENER_NAME:-INTERNAL} KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: ${KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR:-1} networks: - autocomplete-net @@ -133,20 +135,47 @@ services: -e 's/|/\\|/g' } + normalize_env_value() { + value="$$(printf '%s' "$$1" | tr -d '\r\n')" + case "$$value" in + "\""*"\"") + value="$${value#\"}" + value="$${value%\"}" + ;; + esac + printf '%s' "$$value" + } + validate_env_value() { value="$$1" name="$$2" - quote='"' - cr="$$(printf '\r')" - lf="$$(printf '\n')" case "$$value" in - *"$$quote"*|*"$$cr"*|*"$$lf"*) + *'"'*) echo "Invalid $$name: value contains unsupported characters for JSON template (quote/newline)." >&2 exit 1 ;; esac + + normalized="$$(printf '%s' "$$value" | tr -d '\r\n')" + if [ "$$normalized" != "$$value" ]; then + echo "Invalid $$name: value contains unsupported characters for JSON template (quote/newline)." >&2 + exit 1 + fi } + DEBEZIUM_CONNECTOR_NAME="$$(normalize_env_value "$$DEBEZIUM_CONNECTOR_NAME")" + DEBEZIUM_CONNECT_URL="$$(normalize_env_value "$$DEBEZIUM_CONNECT_URL")" + DEBEZIUM_DB_HOST="$$(normalize_env_value "$$DEBEZIUM_DB_HOST")" + DEBEZIUM_DB_PORT="$$(normalize_env_value "$$DEBEZIUM_DB_PORT")" + POSTGRES_USER="$$(normalize_env_value "$$POSTGRES_USER")" + POSTGRES_PASSWORD="$$(normalize_env_value "$$POSTGRES_PASSWORD")" + POSTGRES_DB="$$(normalize_env_value "$$POSTGRES_DB")" + DEBEZIUM_SERVER_NAME="$$(normalize_env_value "$$DEBEZIUM_SERVER_NAME")" + DEBEZIUM_TABLE_INCLUDE_LIST="$$(normalize_env_value "$$DEBEZIUM_TABLE_INCLUDE_LIST")" + DEBEZIUM_SLOT_NAME="$$(normalize_env_value "$$DEBEZIUM_SLOT_NAME")" + DEBEZIUM_PUBLICATION_NAME="$$(normalize_env_value "$$DEBEZIUM_PUBLICATION_NAME")" + DEBEZIUM_TOPIC_PREFIX="$$(normalize_env_value "$$DEBEZIUM_TOPIC_PREFIX")" + validate_env_value "$$DEBEZIUM_CONNECTOR_NAME" "DEBEZIUM_CONNECTOR_NAME" validate_env_value "$$DEBEZIUM_DB_HOST" "DEBEZIUM_DB_HOST" validate_env_value "$$DEBEZIUM_DB_PORT" "DEBEZIUM_DB_PORT" diff --git a/scripts/START.md b/scripts/START.md new file mode 100644 index 0000000..e7bc604 --- /dev/null +++ b/scripts/START.md @@ -0,0 +1,17 @@ +# Start Infrastructure +Set-Location C:\Users\igors\IdeaProjects\autocomplete-system +.\scripts\start-infra.ps1 + +# Register Debezium Connector (one-off, without pulling search-service) +docker compose run --rm --no-deps debezium-init + +# Install Common +Set-Location C:\Users\igors\IdeaProjects\autocomplete-system +.\scripts\install-common.ps1 + +# Start Services +Set-Location C:\Users\igors\IdeaProjects\autocomplete-system +.\scripts\start-search-service.ps1 +.\scripts\start-cdc-service.ps1 +.\scripts\start-autocomplete-service.ps1 +.\scripts\start-frontend.ps1 \ No newline at end of file diff --git a/scripts/install-common.ps1 b/scripts/install-common.ps1 new file mode 100644 index 0000000..64e9dd1 --- /dev/null +++ b/scripts/install-common.ps1 @@ -0,0 +1,21 @@ +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = Split-Path -Parent $PSScriptRoot +$commonDir = Join-Path $repoRoot "common" +$commonPom = Join-Path $commonDir "pom.xml" + +if (-not (Test-Path $commonPom)) { + throw "Не найден common/pom.xml. Проверьте структуру репозитория." +} + +Write-Host "[INFO] Устанавливаю модуль common в локальный Maven-репозиторий..." -ForegroundColor Cyan +Push-Location $commonDir +try { + mvn -B -DskipTests install + Write-Host "[OK] common установлен." -ForegroundColor Green +} +finally { + Pop-Location +} + diff --git a/scripts/start-autocomplete-service.ps1 b/scripts/start-autocomplete-service.ps1 new file mode 100644 index 0000000..de04b92 --- /dev/null +++ b/scripts/start-autocomplete-service.ps1 @@ -0,0 +1,49 @@ +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +function Get-EnvValue { + param( + [string]$Path, + [string]$Key, + [string]$Default = "" + ) + + $match = Select-String -Path $Path -Pattern "^$Key=(.*)$" | Select-Object -First 1 + if ($null -eq $match) { + return $Default + } + + return $match.Matches[0].Groups[1].Value.Trim() +} + +$repoRoot = Split-Path -Parent $PSScriptRoot +$envFile = Join-Path $repoRoot ".env" +$serviceDir = Join-Path $repoRoot "autocomplete-service" +$servicePom = Join-Path $serviceDir "pom.xml" + +if (-not (Test-Path $envFile)) { + throw "Не найден .env в корне репозитория." +} + +if (-not (Test-Path $servicePom)) { + throw "Не найден autocomplete-service/pom.xml." +} + +$profile = Get-EnvValue -Path $envFile -Key "SPRING_PROFILES_ACTIVE" -Default "strict" +$redisPort = Get-EnvValue -Path $envFile -Key "REDIS_PORT" -Default "6379" +$serverPort = Get-EnvValue -Path $envFile -Key "AUTOCOMPLETE_SERVICE_PORT" -Default "8081" + +$env:SPRING_PROFILES_ACTIVE = $profile +$env:SPRING_DATA_REDIS_HOST = "localhost" +$env:SPRING_DATA_REDIS_PORT = $redisPort +$env:SERVER_PORT = $serverPort + +Write-Host "[INFO] Запускаю autocomplete-service с локальными override переменными..." -ForegroundColor Cyan +Push-Location $serviceDir +try { + mvn -B -ntp spring-boot:run +} +finally { + Pop-Location +} + diff --git a/scripts/start-cdc-service.ps1 b/scripts/start-cdc-service.ps1 new file mode 100644 index 0000000..7349d5e --- /dev/null +++ b/scripts/start-cdc-service.ps1 @@ -0,0 +1,50 @@ +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +function Get-EnvValue { + param( + [string]$Path, + [string]$Key, + [string]$Default = "" + ) + + $match = Select-String -Path $Path -Pattern "^$Key=(.*)$" | Select-Object -First 1 + if ($null -eq $match) { + return $Default + } + + return $match.Matches[0].Groups[1].Value.Trim() +} + +$repoRoot = Split-Path -Parent $PSScriptRoot +$envFile = Join-Path $repoRoot ".env" +$serviceDir = Join-Path $repoRoot "cdc-service" +$servicePom = Join-Path $serviceDir "pom.xml" + +if (-not (Test-Path $envFile)) { + throw "Не найден .env в корне репозитория." +} + +if (-not (Test-Path $servicePom)) { + throw "Не найден cdc-service/pom.xml." +} + +$profile = Get-EnvValue -Path $envFile -Key "SPRING_PROFILES_ACTIVE" -Default "strict" +$kafkaPort = Get-EnvValue -Path $envFile -Key "KAFKA_PORT" -Default "9092" +$redisPort = Get-EnvValue -Path $envFile -Key "REDIS_PORT" -Default "6379" +$serverPort = Get-EnvValue -Path $envFile -Key "CDC_SERVICE_PORT" -Default "8084" + +$env:SPRING_PROFILES_ACTIVE = $profile +$env:SPRING_KAFKA_BOOTSTRAP_SERVERS = "localhost:$kafkaPort" +$env:SPRING_DATA_REDIS_HOST = "localhost" +$env:SPRING_DATA_REDIS_PORT = $redisPort +$env:SERVER_PORT = $serverPort + +Write-Host "[INFO] Запускаю cdc-service с локальными override переменными..." -ForegroundColor Cyan +Push-Location $serviceDir +try { + mvn -B -ntp spring-boot:run +} +finally { + Pop-Location +} diff --git a/scripts/start-frontend.ps1 b/scripts/start-frontend.ps1 new file mode 100644 index 0000000..52627c9 --- /dev/null +++ b/scripts/start-frontend.ps1 @@ -0,0 +1,29 @@ +param( + [switch]$SkipInstall +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = Split-Path -Parent $PSScriptRoot +$frontendDir = Join-Path $repoRoot "frontend" +$packageJson = Join-Path $frontendDir "package.json" + +if (-not (Test-Path $packageJson)) { + throw "Не найден frontend/package.json." +} + +Push-Location $frontendDir +try { + if (-not $SkipInstall) { + Write-Host "[INFO] Устанавливаю frontend-зависимости (npm ci)..." -ForegroundColor Cyan + npm ci + } + + Write-Host "[INFO] Запускаю frontend (npm start)..." -ForegroundColor Cyan + npm start +} +finally { + Pop-Location +} + diff --git a/scripts/start-infra.ps1 b/scripts/start-infra.ps1 new file mode 100644 index 0000000..8b0eb78 --- /dev/null +++ b/scripts/start-infra.ps1 @@ -0,0 +1,27 @@ +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = Split-Path -Parent $PSScriptRoot +$composeFile = Join-Path $repoRoot "docker-compose.yml" +$envFile = Join-Path $repoRoot ".env" + +if (-not (Test-Path $composeFile)) { + throw "Не найден docker-compose.yml. Запустите скрипт из репозитория autocomplete-system." +} + +if (-not (Test-Path $envFile)) { + throw "Не найден .env. Создайте его из .env.example перед запуском инфраструктуры." +} + +Write-Host "[INFO] Поднимаю инфраструктуру Docker Compose..." -ForegroundColor Cyan +Push-Location $repoRoot +try { + # debezium-init намеренно не запускаем здесь: он depends_on search-service. + docker compose up -d postgres redis zookeeper kafka kafka-init debezium kafka-ui + docker compose ps + Write-Host "[OK] Инфраструктура запущена." -ForegroundColor Green +} +finally { + Pop-Location +} + diff --git a/scripts/start-search-service.ps1 b/scripts/start-search-service.ps1 new file mode 100644 index 0000000..29a8973 --- /dev/null +++ b/scripts/start-search-service.ps1 @@ -0,0 +1,61 @@ +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +function Get-EnvValue { + param( + [string]$Path, + [string]$Key, + [string]$Default = "" + ) + + $match = Select-String -Path $Path -Pattern "^$Key=(.*)$" | Select-Object -First 1 + if ($null -eq $match) { + return $Default + } + + return $match.Matches[0].Groups[1].Value.Trim() +} + +$repoRoot = Split-Path -Parent $PSScriptRoot +$envFile = Join-Path $repoRoot ".env" +$serviceDir = Join-Path $repoRoot "search-service" +$servicePom = Join-Path $serviceDir "pom.xml" + +if (-not (Test-Path $envFile)) { + throw "Не найден .env в корне репозитория." +} + +if (-not (Test-Path $servicePom)) { + throw "Не найден search-service/pom.xml." +} + +$profile = Get-EnvValue -Path $envFile -Key "SPRING_PROFILES_ACTIVE" -Default "strict" +$postgresDb = Get-EnvValue -Path $envFile -Key "POSTGRES_DB" -Default "autocomplete" +$postgresUser = Get-EnvValue -Path $envFile -Key "POSTGRES_USER" -Default "autocomplete" +$postgresPassword = Get-EnvValue -Path $envFile -Key "POSTGRES_PASSWORD" +$kafkaPort = Get-EnvValue -Path $envFile -Key "KAFKA_PORT" -Default "9092" +$postgresPort = Get-EnvValue -Path $envFile -Key "POSTGRES_PORT" -Default "5432" +$serverPort = Get-EnvValue -Path $envFile -Key "SEARCH_SERVICE_PORT" -Default "8082" +$managementExposure = Get-EnvValue -Path $envFile -Key "SEARCH_MANAGEMENT_ENDPOINTS_EXPOSURE" -Default "health,info" + +if ([string]::IsNullOrWhiteSpace($postgresPassword)) { + throw "POSTGRES_PASSWORD не задан в .env." +} + +$env:SPRING_PROFILES_ACTIVE = $profile +$env:SPRING_KAFKA_BOOTSTRAP_SERVERS = "localhost:$kafkaPort" +$env:SPRING_KAFKA_STREAMS_BOOTSTRAP_SERVERS = "localhost:$kafkaPort" +$env:SPRING_DATASOURCE_URL = "jdbc:postgresql://localhost:$postgresPort/$postgresDb" +$env:SPRING_DATASOURCE_USERNAME = $postgresUser +$env:SPRING_DATASOURCE_PASSWORD = $postgresPassword +$env:MANAGEMENT_ENDPOINTS_WEB_EXPOSURE_INCLUDE = $managementExposure +$env:SERVER_PORT = $serverPort + +Write-Host "[INFO] Запускаю search-service с локальными override переменными..." -ForegroundColor Cyan +Push-Location $serviceDir +try { + mvn -B -ntp spring-boot:run +} +finally { + Pop-Location +} From 806c059f20375ca66b336785770267f7cbbf725a Mon Sep 17 00:00:00 2001 From: igorsatsyuk Date: Sat, 9 May 2026 19:27:04 +0300 Subject: [PATCH 2/2] chore: streamline START.md by removing redundant Set-Location commands --- scripts/START.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/scripts/START.md b/scripts/START.md index e7bc604..37345eb 100644 --- a/scripts/START.md +++ b/scripts/START.md @@ -1,16 +1,13 @@ # Start Infrastructure -Set-Location C:\Users\igors\IdeaProjects\autocomplete-system .\scripts\start-infra.ps1 # Register Debezium Connector (one-off, without pulling search-service) docker compose run --rm --no-deps debezium-init # Install Common -Set-Location C:\Users\igors\IdeaProjects\autocomplete-system .\scripts\install-common.ps1 # Start Services -Set-Location C:\Users\igors\IdeaProjects\autocomplete-system .\scripts\start-search-service.ps1 .\scripts\start-cdc-service.ps1 .\scripts\start-autocomplete-service.ps1