# Provision Redis + MinIO (bucket inquiry-robot) + MySQL/PG SQL prep $ErrorActionPreference = "Continue" $Root = "E:\wwwroot" $Ops = Join-Path $Root "ops" $SqlDir = Join-Path $Ops "sql" New-Item -ItemType Directory -Force -Path $Ops, $SqlDir | Out-Null $Bucket = "inquiry-robot" # S3: no underscore function New-Secret([int]$len = 48) { $bytes = New-Object byte[] $len [System.Security.Cryptography.RandomNumberGenerator]::Create().GetBytes($bytes) return ([Convert]::ToBase64String($bytes) -replace '[+/=]', 'x').Substring(0, [Math]::Min(64, $len)) } function Upsert-Env([string]$path, [hashtable]$pairs) { $lines = @() if (Test-Path $path) { $lines = @(Get-Content $path) } $out = New-Object System.Collections.Generic.List[string] $seen = @{} foreach ($line in $lines) { if ($line -match '^\s*#' -or $line -notmatch '=' -or [string]::IsNullOrWhiteSpace($line)) { [void]$out.Add($line) continue } $k = ($line.Split('=', 2))[0] if ($pairs.ContainsKey($k)) { [void]$out.Add("$k=$($pairs[$k])") $seen[$k] = $true } else { [void]$out.Add($line) $seen[$k] = $true } } foreach ($k in $pairs.Keys) { if (-not $seen.ContainsKey($k)) { [void]$out.Add("$k=$($pairs[$k])") } } Set-Content -Path $path -Value $out.ToArray() -Encoding UTF8 } Write-Output "=== Redis ===" $redisCred = Get-Content "C:\yutongda\secure\redis-credentials.json" -Raw | ConvertFrom-Json $rcli = "C:\yutongda\tools\redis-5.0.14.1\redis-cli.exe" $redisScript = "AUTH $($redisCred.application_username) $($redisCred.application_password)`nSELECT 1`nPING`nSET inquiry_robot:provision:ping ok EX 300`nGET inquiry_robot:provision:ping`nQUIT`n" $redisOut = $redisScript | & $rcli -h $redisCred.host -p $redisCred.port 2>&1 $joined = ($redisOut | ForEach-Object { "$_" }) -join "`n" if ($joined -notmatch 'PONG') { Write-Output $joined; throw "redis ping failed" } Write-Output "REDIS_OK" $redisUrl = "redis://$($redisCred.application_username):$($redisCred.application_password)@$($redisCred.host):$($redisCred.port)/1" Write-Output "=== MinIO bucket $Bucket ===" $minioRoot = @{} Get-Content "C:\yutongda\config\minio.env" | ForEach-Object { if ($_ -match '^(MINIO_ROOT_USER|MINIO_ROOT_PASSWORD)=(.*)$') { $minioRoot[$matches[1]] = $matches[2] } } $minioApp = @{} Get-Content "C:\yutongda\config\minio-app.env" | ForEach-Object { if ($_ -match '^(OBJECT_STORAGE_ACCESS_KEY|OBJECT_STORAGE_SECRET_KEY)=(.*)$') { $minioApp[$matches[1]] = $matches[2] } } $mc = "C:\yutongda\downloads\mc.exe" & $mc alias set localminio http://127.0.0.1:9000 $minioRoot["MINIO_ROOT_USER"] $minioRoot["MINIO_ROOT_PASSWORD"] --api S3v4 | Out-Null $mb = & $mc mb "localminio/$Bucket" --ignore-existing 2>&1 Write-Output ("mb=" + ($mb | ForEach-Object { "$_" })) $policyPath = Join-Path $SqlDir "minio-inquiry-robot-policy.json" @" { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": ["s3:*"], "Resource": ["arn:aws:s3:::$Bucket", "arn:aws:s3:::$Bucket/*"] } ] } "@ | Set-Content $policyPath -Encoding ASCII & $mc admin policy create localminio inquiry_robot_full $policyPath 2>&1 | ForEach-Object { "policy_create=$_" } & $mc admin policy attach localminio inquiry_robot_full --user $minioApp["OBJECT_STORAGE_ACCESS_KEY"] 2>&1 | ForEach-Object { "policy_attach=$_" } & $mc alias set inquiryapp http://127.0.0.1:9000 $minioApp["OBJECT_STORAGE_ACCESS_KEY"] $minioApp["OBJECT_STORAGE_SECRET_KEY"] --api S3v4 | Out-Null $smokeLocal = Join-Path $env:TEMP "inquiry_robot_smoke.txt" Set-Content $smokeLocal -Value ("smoke " + (Get-Date).ToString("o")) -Encoding ASCII $cp = & $mc cp $smokeLocal "inquiryapp/$Bucket/provision/smoke.txt" 2>&1 Write-Output ("cp=" + ($cp | ForEach-Object { "$_" })) $ls = & $mc ls "inquiryapp/$Bucket/provision/" 2>&1 Write-Output ("ls=" + ($ls | ForEach-Object { "$_" })) if (($cp | Out-String) -match 'ERROR' -or ($ls | Out-String) -match 'ERROR') { # fallback: put with root alias Write-Output "APP_PUT_FAIL_TRY_ROOT" & $mc cp $smokeLocal "localminio/$Bucket/provision/smoke.txt" 2>&1 | ForEach-Object { "root_cp=$_" } & $mc ls "localminio/$Bucket/provision/" 2>&1 | ForEach-Object { "root_ls=$_" } } Write-Output "MINIO_OK" Write-Output "=== MySQL/PG SQL ===" $mysqlPass = New-Secret 40 $pgPass = New-Secret 40 @" CREATE DATABASE IF NOT EXISTS ``inquiry_robot`` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; CREATE USER IF NOT EXISTS 'inquiry_robot'@'10.206.0.15' IDENTIFIED BY '$mysqlPass'; CREATE USER IF NOT EXISTS 'inquiry_robot'@'%' IDENTIFIED BY '$mysqlPass'; GRANT ALL PRIVILEGES ON ``inquiry_robot``.* TO 'inquiry_robot'@'10.206.0.15'; GRANT ALL PRIVILEGES ON ``inquiry_robot``.* TO 'inquiry_robot'@'%'; FLUSH PRIVILEGES; "@ | Set-Content (Join-Path $SqlDir "01_create_inquiry_robot_mysql.sql") -Encoding UTF8 @" CREATE USER inquiry_robot_runtime WITH PASSWORD '$pgPass'; CREATE DATABASE inquiry_robot_runtime OWNER inquiry_robot_runtime ENCODING 'UTF8' TEMPLATE template0; GRANT ALL PRIVILEGES ON DATABASE inquiry_robot_runtime TO inquiry_robot_runtime; "@ | Set-Content (Join-Path $SqlDir "02_create_inquiry_robot_pg.sql") -Encoding UTF8 @{ mysql_user = "inquiry_robot" mysql_password = $mysqlPass pg_user = "inquiry_robot_runtime" pg_password = $pgPass createdAt = (Get-Date).ToString("o") } | ConvertTo-Json | Set-Content (Join-Path $Ops "inquiry_robot_db_secrets.json") -Encoding UTF8 # Try admin-secrets.env if exists $adminEnv = Join-Path $Ops "admin-secrets.env" $mysqlCreated = $false $pgCreated = $false if (Test-Path $adminEnv) { $admin = @{} Get-Content $adminEnv | ForEach-Object { if ($_ -match '^(MYSQL_ADMIN_USER|MYSQL_ADMIN_PASSWORD|PG_ADMIN_USER|PG_ADMIN_PASSWORD)=(.*)$') { $admin[$matches[1]] = $matches[2] } } if ($admin.MYSQL_ADMIN_USER -and $admin.MYSQL_ADMIN_PASSWORD) { $mysqlExe = "C:\yutongda\tools\mysql-8.0.39-winx64\bin\mysql.exe" Get-Content (Join-Path $SqlDir "01_create_inquiry_robot_mysql.sql") -Raw | & $mysqlExe -h 10.206.0.14 -P 3306 -u $admin.MYSQL_ADMIN_USER "-p$($admin.MYSQL_ADMIN_PASSWORD)" --connect-timeout=15 2>&1 | ForEach-Object { if ($_ -notmatch 'Using a password') { "mysql=$_" } } if ($LASTEXITCODE -eq 0) { $mysqlCreated = $true } } if ($admin.PG_ADMIN_USER -and $admin.PG_ADMIN_PASSWORD) { $env:PGPASSWORD = $admin.PG_ADMIN_PASSWORD $psql = "C:\yutongda\tools\pgsql\bin\psql.exe" & $psql -h 10.206.0.14 -p 5432 -U $admin.PG_ADMIN_USER -d postgres -v ON_ERROR_STOP=1 -c "SELECT 1" 2>&1 | ForEach-Object { "pgprobe=$_" } # create user/db with separate commands (more reliable than multi-statement file) & $psql -h 10.206.0.14 -p 5432 -U $admin.PG_ADMIN_USER -d postgres -v ON_ERROR_STOP=1 -c "DO `$`$ BEGIN IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname = 'inquiry_robot_runtime') THEN CREATE ROLE inquiry_robot_runtime LOGIN PASSWORD '$pgPass'; END IF; END `$`$;" 2>&1 | ForEach-Object { "pguser=$_" } & $psql -h 10.206.0.14 -p 5432 -U $admin.PG_ADMIN_USER -d postgres -v ON_ERROR_STOP=1 -c "SELECT 'exists' FROM pg_database WHERE datname='inquiry_robot_runtime'" 2>&1 | ForEach-Object { "pgdbcheck=$_" } $existsDb = & $psql -h 10.206.0.14 -p 5432 -U $admin.PG_ADMIN_USER -d postgres -tAc "SELECT 1 FROM pg_database WHERE datname='inquiry_robot_runtime'" 2>$null if (-not $existsDb) { & $psql -h 10.206.0.14 -p 5432 -U $admin.PG_ADMIN_USER -d postgres -v ON_ERROR_STOP=1 -c "CREATE DATABASE inquiry_robot_runtime OWNER inquiry_robot_runtime ENCODING 'UTF8' TEMPLATE template0;" 2>&1 | ForEach-Object { "pgdb=$_" } } $pgCreated = $true } } else { Write-Output "NEED_DBA_FOR_MYSQL_PG" } Write-Output "=== patch .env ===" $envPath = Join-Path $Root ".env" if (-not (Test-Path $envPath)) { "YTD_ENV=test`r`nYTD_DEPLOY_ROOT=E:\wwwroot`r`nYTD_CUTOVER_TARGET=replace-yutongda`r`n" | Set-Content $envPath -Encoding UTF8 } $pairs = @{ MYSQL_HOST = "10.206.0.14" MYSQL_PORT = "3306" MYSQL_DB = "inquiry_robot" MYSQL_USER = "inquiry_robot" MYSQL_PASSWORD = $mysqlPass YTD_MYSQL_URL = "jdbc:mysql://10.206.0.14:3306/inquiry_robot?characterEncoding=UTF-8&useUnicode=true&useSSL=true&requireSSL=true&verifyServerCertificate=false&tinyInt1isBit=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai" PG_HOST = "10.206.0.14" PG_PORT = "5432" PG_DATABASE = "inquiry_robot_runtime" PG_USER = "inquiry_robot_runtime" PG_PASSWORD = $pgPass PG_SSLMODE = "prefer" YTD_REDIS_HOST = $redisCred.host YTD_REDIS_PORT = "$($redisCred.port)" YTD_REDIS_USERNAME = $redisCred.application_username REDIS_URL = $redisUrl REDIS_KEY_PREFIX = "inquiry_robot:" REDIS_REQUIRED = "true" OBJECT_STORAGE_ENDPOINT_URL = "http://127.0.0.1:9000" OBJECT_STORAGE_BUCKET = $Bucket OBJECT_STORAGE_ACCESS_KEY = $minioApp["OBJECT_STORAGE_ACCESS_KEY"] OBJECT_STORAGE_SECRET_KEY = $minioApp["OBJECT_STORAGE_SECRET_KEY"] OBJECT_STORAGE_PATH_STYLE = "true" OBJECT_STORAGE_REGION = "us-east-1" } Upsert-Env $envPath $pairs if (Test-Path "E:\wwwroot\agent\current") { Copy-Item $envPath "E:\wwwroot\agent\current\.env" -Force Write-Output "AGENT_ENV_SYNCED" } # verify env keys present (no values) Select-String -Path $envPath -Pattern '^(MYSQL_DB|PG_DATABASE|REDIS_KEY_PREFIX|OBJECT_STORAGE_BUCKET)=' | ForEach-Object { $_.Line.Trim() } @{ redis = $true minioBucket = $Bucket mysqlCreated = $mysqlCreated pgCreated = $pgCreated needDba = (-not $mysqlCreated -or -not $pgCreated) sqlDir = $SqlDir updatedAt = (Get-Date).ToString("o") } | ConvertTo-Json | Tee-Object -FilePath (Join-Path $Ops "data-plane-status.json") Write-Output "PROVISION_DONE"