212 lines
6.5 KiB
PowerShell
212 lines
6.5 KiB
PowerShell
param(
|
||
[string]$OutputPath,
|
||
[string]$ServerIp = "192.168.42.134",
|
||
[int]$AppPort = 4320,
|
||
[int]$MinioApiPort = 39200,
|
||
[int]$MinioConsolePort = 39201,
|
||
[int]$AgentPort = 3080,
|
||
[int]$AgentGoPort = 9091,
|
||
[string]$ComposeProjectName = "flnxi_sucai_test",
|
||
[string]$ContainerPrefix = "flnxi-sucai-test",
|
||
[string]$AppImage,
|
||
[string]$LicenseServerUrl,
|
||
[string]$LicenseProjectCode = "flnxi-sucai",
|
||
[string]$LicenseRequestSigningSecret,
|
||
[string]$InitAdminEmail = "admin@test.flnxi.local"
|
||
)
|
||
|
||
Set-StrictMode -Version Latest
|
||
$ErrorActionPreference = "Stop"
|
||
|
||
if ([string]::IsNullOrWhiteSpace($LicenseServerUrl)) {
|
||
throw "必须显式传入 -LicenseServerUrl,避免把过期或错误的许可证地址写进测试环境。"
|
||
}
|
||
|
||
if ([string]::IsNullOrWhiteSpace($AppImage)) {
|
||
throw "必须显式传入 -AppImage,测试环境也要固定完整镜像版本,不能依赖 latest。"
|
||
}
|
||
|
||
function New-RandomString {
|
||
param(
|
||
[int]$Length = 32,
|
||
[string]$Alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
|
||
)
|
||
|
||
$bytes = New-Object byte[] ($Length * 2)
|
||
[System.Security.Cryptography.RandomNumberGenerator]::Fill($bytes)
|
||
|
||
$builder = New-Object System.Text.StringBuilder
|
||
foreach ($byte in $bytes) {
|
||
if ($builder.Length -ge $Length) {
|
||
break
|
||
}
|
||
[void]$builder.Append($Alphabet[$byte % $Alphabet.Length])
|
||
}
|
||
|
||
return $builder.ToString()
|
||
}
|
||
|
||
function Get-LicensePublicKey {
|
||
param([string]$ProjectRoot)
|
||
|
||
$candidateFiles = @(
|
||
(Join-Path $ProjectRoot "license-server\.env"),
|
||
(Join-Path $ProjectRoot ".env.production"),
|
||
(Join-Path $ProjectRoot ".env"),
|
||
(Join-Path $ProjectRoot ".env.local")
|
||
)
|
||
|
||
foreach ($file in $candidateFiles) {
|
||
if (-not (Test-Path $file)) {
|
||
continue
|
||
}
|
||
|
||
$content = Get-Content -Raw $file
|
||
|
||
$licenseMatch = [regex]::Match($content, '(?m)^LICENSE_PUBLIC_KEY=(.+)$')
|
||
if ($licenseMatch.Success) {
|
||
return $licenseMatch.Groups[1].Value.Trim().Trim('"')
|
||
}
|
||
|
||
$rsaMatch = [regex]::Match($content, '(?m)^RSA_PUBLIC_KEY=(.+)$')
|
||
if ($rsaMatch.Success) {
|
||
return $rsaMatch.Groups[1].Value.Trim().Trim('"')
|
||
}
|
||
|
||
$privateMatch = [regex]::Match($content, '(?m)^RSA_PRIVATE_KEY=(.+)$')
|
||
if ($privateMatch.Success) {
|
||
$privateKey = $privateMatch.Groups[1].Value.Trim().Trim('"')
|
||
$pem = $privateKey.Replace('\n', [Environment]::NewLine)
|
||
|
||
try {
|
||
$rsa = [System.Security.Cryptography.RSA]::Create()
|
||
try {
|
||
$rsa.ImportFromPem($pem.ToCharArray())
|
||
$publicPem = $rsa.ExportSubjectPublicKeyInfoPem()
|
||
return ([regex]::Replace($publicPem, "(\r\n|\n|\r)", '\n')).Trim()
|
||
} finally {
|
||
$rsa.Dispose()
|
||
}
|
||
} catch {
|
||
throw "检测到 RSA_PRIVATE_KEY,但自动推导公钥失败:$($_.Exception.Message)"
|
||
}
|
||
}
|
||
}
|
||
|
||
throw "未找到可用的 LICENSE_PUBLIC_KEY / RSA_PUBLIC_KEY,无法生成测试环境 .env。"
|
||
}
|
||
|
||
if ([string]::IsNullOrWhiteSpace($OutputPath)) {
|
||
throw "必须提供 -OutputPath。"
|
||
}
|
||
|
||
$projectRoot = Split-Path -Parent $PSScriptRoot
|
||
$licensePublicKey = Get-LicensePublicKey -ProjectRoot $projectRoot
|
||
|
||
$postgresUser = "flnxi_test"
|
||
$postgresDb = "flnxi_sucai_test"
|
||
$postgresPassword = New-RandomString -Length 24
|
||
$minioRootUser = "flnxitest"
|
||
$minioRootPassword = New-RandomString -Length 24
|
||
$nextAuthSecret = New-RandomString -Length 48
|
||
$localEncryptionKey = New-RandomString -Length 48
|
||
$systemInitToken = New-RandomString -Length 40
|
||
if ([string]::IsNullOrWhiteSpace($LicenseRequestSigningSecret)) {
|
||
$LicenseRequestSigningSecret = New-RandomString -Length 48
|
||
}
|
||
$s3Bucket = "flnxi-sucai-test"
|
||
$appUrl = "http://${ServerIp}:${AppPort}"
|
||
$s3PublicDomain = "http://${ServerIp}:${MinioApiPort}/${s3Bucket}"
|
||
|
||
$envContent = @"
|
||
# FLNXI 素材库共享测试机环境变量
|
||
# 用途:192.168.42.134 测试机
|
||
# 生成时间:$(Get-Date -Format "yyyy-MM-dd HH:mm:ss")
|
||
|
||
NODE_ENV=production
|
||
|
||
# Shared-machine isolation
|
||
COMPOSE_PROJECT_NAME=$ComposeProjectName
|
||
APP_CONTAINER_NAME=${ContainerPrefix}-app
|
||
POSTGRES_CONTAINER_NAME=${ContainerPrefix}-postgres
|
||
MINIO_CONTAINER_NAME=${ContainerPrefix}-minio
|
||
MINIO_INIT_CONTAINER_NAME=${ContainerPrefix}-minio-init
|
||
POSTGRES_VOLUME_NAME=${ContainerPrefix}-postgres-data
|
||
MINIO_VOLUME_NAME=${ContainerPrefix}-minio-data
|
||
APP_NETWORK_NAME=${ContainerPrefix}-network
|
||
APP_PORT=$AppPort
|
||
MINIO_API_PORT=$MinioApiPort
|
||
MINIO_CONSOLE_PORT=$MinioConsolePort
|
||
AGENT_PORT=$AgentPort
|
||
AGENT_GO_PORT=$AgentGoPort
|
||
|
||
# Versioned application image
|
||
APP_IMAGE=$AppImage
|
||
|
||
# Public URLs
|
||
SERVER_IP=$ServerIp
|
||
APP_PUBLIC_URL=$appUrl
|
||
NEXTAUTH_URL=$appUrl
|
||
NEXTAUTH_SECRET=$nextAuthSecret
|
||
|
||
# License
|
||
LICENSE_SERVER_URL=$LicenseServerUrl
|
||
LICENSE_PROJECT_CODE=$LicenseProjectCode
|
||
LICENSE_REQUEST_SIGNING_SECRET=$LicenseRequestSigningSecret
|
||
LICENSE_PUBLIC_KEY="$licensePublicKey"
|
||
LOCAL_ENCRYPTION_KEY=$localEncryptionKey
|
||
|
||
# Database
|
||
POSTGRES_USER=$postgresUser
|
||
POSTGRES_PASSWORD=$postgresPassword
|
||
POSTGRES_DB=$postgresDb
|
||
DATABASE_URL=postgresql://${postgresUser}:${postgresPassword}@postgres:5432/${postgresDb}
|
||
DATABASE_DRIVER=node
|
||
|
||
# Object storage
|
||
STORAGE_PROVIDER=s3
|
||
S3_ENDPOINT=http://minio:9000
|
||
S3_BUCKET=$s3Bucket
|
||
S3_ACCESS_KEY_ID=$minioRootUser
|
||
S3_SECRET_ACCESS_KEY=$minioRootPassword
|
||
S3_REGION=us-east-1
|
||
S3_PUBLIC_DOMAIN=$s3PublicDomain
|
||
S3_ENABLE_PATH_STYLE=1
|
||
S3_FILE_PATH=materials/
|
||
S3_ENV_PREFIX=test
|
||
MINIO_ROOT_USER=$minioRootUser
|
||
MINIO_ROOT_PASSWORD=$minioRootPassword
|
||
|
||
# Access control and URL rewriting
|
||
PUBLIC_DOMAINS=$ServerIp
|
||
NEXT_PUBLIC_PUBLIC_DOMAINS=$ServerIp
|
||
ALLOWED_ORIGINS=$appUrl
|
||
IMAGE_REMOTE_HOSTS=$ServerIp
|
||
|
||
# Initial system bootstrap
|
||
SYSTEM_INIT_TOKEN=$systemInitToken
|
||
INIT_ADMIN_EMAIL=$InitAdminEmail
|
||
INIT_ADMIN_PASSWORD=
|
||
|
||
# Optional features disabled by default on shared test server
|
||
DINGTALK_CLIENT_ID=
|
||
DINGTALK_CLIENT_SECRET=
|
||
NEXT_PUBLIC_DINGTALK_CALLBACK_URL=
|
||
GEMINI_API_KEY=
|
||
GEMINI_API_BASE_URL=https://api.huandutech.com/v1
|
||
GEMINI_MODEL=gemini-2.0-flash-exp
|
||
NEXT_PUBLIC_DEFAULT_HEADER_LOGO_URL=
|
||
NEXT_PUBLIC_DEFAULT_LOGIN_LOGO_URL=
|
||
NEXT_PUBLIC_INTRANET_URL=$appUrl
|
||
NEXT_PUBLIC_PORTAL_BASE_URL=$appUrl
|
||
UPLOAD_DIR=./uploads
|
||
"@
|
||
|
||
$parent = Split-Path -Parent $OutputPath
|
||
if ($parent -and -not (Test-Path $parent)) {
|
||
New-Item -ItemType Directory -Path $parent -Force | Out-Null
|
||
}
|
||
|
||
[System.IO.File]::WriteAllText($OutputPath, $envContent, [System.Text.UTF8Encoding]::new($false))
|
||
Write-Host "共享测试机环境文件已生成: $OutputPath"
|