furniture-3d-system-Wu/scripts/create-wyj-test-deploy.ps1

234 lines
8.3 KiB
PowerShell

param(
[string]$ProjectRoot = "D:\AI Code\3danzhuang-wuyongjie-mainline\furniture-3d-system",
[string]$OutputRoot = ""
)
$ErrorActionPreference = 'Stop'
if ([string]::IsNullOrWhiteSpace($OutputRoot)) {
$OutputRoot = Join-Path $env:TEMP '3danzhuang-deploy-artifacts\wyj-test'
}
if (-not (Test-Path -LiteralPath $OutputRoot)) {
New-Item -ItemType Directory -Path $OutputRoot -Force | Out-Null
}
if (-not (Test-Path -LiteralPath $ProjectRoot)) {
throw "项目目录不存在: $ProjectRoot"
}
function Get-EnvValue {
param([string]$Path, [string]$Key)
if (-not (Test-Path -LiteralPath $Path)) { return $null }
$line = Get-Content -LiteralPath $Path | Where-Object { $_ -match "^$([regex]::Escape($Key))=" } | Select-Object -First 1
if (-not $line) { return $null }
$value = $line.Substring($line.IndexOf('=') + 1).Trim()
if ($value.Length -ge 2) {
if (($value.StartsWith('"') -and $value.EndsWith('"')) -or ($value.StartsWith("'") -and $value.EndsWith("'"))) {
$value = $value.Substring(1, $value.Length - 2)
}
}
return $value
}
$localEnvPath = Join-Path $ProjectRoot '.env'
$jwtSecret = Get-EnvValue -Path $localEnvPath -Key 'JWT_SECRET'
if ([string]::IsNullOrWhiteSpace($jwtSecret) -or $jwtSecret.Length -lt 32) {
$jwtSecret = 'wyj-test-jwt-secret-20260410-solidjoin-3d'
}
$nextAuthSecret = Get-EnvValue -Path $localEnvPath -Key 'NEXTAUTH_SECRET'
if ([string]::IsNullOrWhiteSpace($nextAuthSecret)) {
$nextAuthSecret = $jwtSecret
}
$timestamp = Get-Date -Format 'yyyyMMdd-HHmmss'
$archiveName = "deploy-furniture-wyj-test-$timestamp.tar.gz"
$scriptName = "deploy-furniture-wyj-test-$timestamp.sh"
$archivePath = Join-Path $OutputRoot $archiveName
$scriptPath = Join-Path $OutputRoot $scriptName
$includePaths = @(
'furniture-3d-system/package.json'
'furniture-3d-system/package-lock.json'
'furniture-3d-system/.npmrc'
'furniture-3d-system/next.config.js'
'furniture-3d-system/postcss.config.mjs'
'furniture-3d-system/tsconfig.json'
'furniture-3d-system/next-env.d.ts'
'furniture-3d-system/prisma.config.ts'
'furniture-3d-system/src'
'furniture-3d-system/public'
'furniture-3d-system/prisma'
'furniture-3d-system/scripts'
'furniture-3d-system/lib'
)
Push-Location (Split-Path $ProjectRoot -Parent)
try {
if (Test-Path -LiteralPath $archivePath) { Remove-Item -LiteralPath $archivePath -Force }
tar -czf $archivePath @includePaths
} finally { Pop-Location }
$shellScript = @'
set -e
BASE=/root/codex-workspaces/furniture-3d-system-wyj-test
APP=$BASE/furniture-3d-system
ARCHIVE=/root/codex-workspaces/__ARCHIVE_NAME__
DB_NAME=furniture3d_wyj_test
DB_USER=furniture3d_wyj_test
DB_PASS='F3dWyjTest_3200_20260407'
APP_PORT=3200
BUCKET=furniture-3d-models-wyj-test
MINIO_USER=furniture3dtest
MINIO_PASS='F3dMinio_39100_20260331'
S3_ENDPOINT='http://127.0.0.1:39100'
S3_PUBLIC_ENDPOINT='http://192.168.42.134:39100'
mkdir -p $BASE
mkdir -p $APP
OLD_LOCK_HASH=""
if [ -f "$APP/package-lock.json" ]; then
OLD_LOCK_HASH=$(sha256sum "$APP/package-lock.json" | awk '{print $1}')
fi
if [ -d "$APP/.next/cache" ]; then
mv "$APP/.next/cache" /tmp/next-cache-backup-$APP_PORT || true
fi
rm -rf $APP/src $APP/public $APP/prisma $APP/scripts $APP/lib $APP/.next
rm -f $APP/package.json $APP/package-lock.json $APP/.npmrc $APP/next.config.js $APP/postcss.config.mjs $APP/tsconfig.json $APP/next-env.d.ts $APP/prisma.config.ts
cd $BASE
tar -xzf $ARCHIVE
if [ -d /tmp/next-cache-backup-$APP_PORT ]; then
mkdir -p "$APP/.next"
mv /tmp/next-cache-backup-$APP_PORT "$APP/.next/cache" || true
fi
NEW_LOCK_HASH=""
if [ -f "$APP/package-lock.json" ]; then
NEW_LOCK_HASH=$(sha256sum "$APP/package-lock.json" | awk '{print $1}')
fi
sudo -u postgres psql -d postgres <<SQL
DO \$\$
BEGIN
IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = '$DB_USER') THEN
CREATE ROLE $DB_USER LOGIN PASSWORD '$DB_PASS';
END IF;
END
\$\$;
SQL
if ! sudo -u postgres psql -d postgres -Atqc "SELECT 1 FROM pg_database WHERE datname='$DB_NAME'" | grep -q 1; then
sudo -u postgres createdb -O $DB_USER $DB_NAME
sudo -u postgres pg_dump -d furniture3d_test --no-owner --no-privileges | sudo -u postgres psql -d $DB_NAME
fi
sudo -u postgres psql -d postgres <<SQL
GRANT ALL PRIVILEGES ON DATABASE $DB_NAME TO $DB_USER;
ALTER DATABASE $DB_NAME OWNER TO $DB_USER;
SQL
sudo -u postgres psql -d $DB_NAME <<SQL
GRANT ALL PRIVILEGES ON SCHEMA public TO $DB_USER;
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO $DB_USER;
GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public TO $DB_USER;
GRANT ALL PRIVILEGES ON ALL FUNCTIONS IN SCHEMA public TO $DB_USER;
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON TABLES TO $DB_USER;
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON SEQUENCES TO $DB_USER;
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON FUNCTIONS TO $DB_USER;
SQL
# 精准增量 .env 迁移逻辑
if [ -f "$BASE/.env" ]; then
echo "Found existing env, applying patch..."
else
echo "Initializing new env from template..."
cp /root/codex-workspaces/furniture-3d-system-test/.env $BASE/.env || touch $BASE/.env
fi
python3 - <<PY
from pathlib import Path
p = Path('$BASE/.env')
content = p.read_text() if p.exists() else ""
lines = content.splitlines()
repl = {
'DATABASE_URL': '"postgresql://$DB_USER:$DB_PASS@127.0.0.1:5432/$DB_NAME"',
'DIRECT_URL': '"postgresql://$DB_USER:$DB_PASS@127.0.0.1:5432/$DB_NAME"',
'NEXTAUTH_URL': 'http://192.168.42.134:$APP_PORT',
'NEXTAUTH_SECRET': '"__NEXTAUTH_SECRET__"',
'NEXT_PUBLIC_APP_URL': 'http://192.168.42.134:$APP_PORT',
'S3_ENDPOINT': '"$S3_ENDPOINT"',
'S3_PUBLIC_ENDPOINT': '"$S3_PUBLIC_ENDPOINT"',
'S3_PUBLIC_URL': '"$S3_PUBLIC_ENDPOINT"',
'S3_ACCESS_KEY': '"$MINIO_USER"',
'S3_SECRET_KEY': '"$MINIO_PASS"',
'S3_REGION': '"us-east-1"',
'S3_BUCKET': '"$BUCKET"',
'JWT_SECRET': '"__JWT_SECRET__"',
}
out=[]
seen=set()
for line in lines:
if '=' in line and not line.lstrip().startswith('#'):
k,_ = line.split('=',1)
if k in repl:
out.append(f'{k}={repl[k]}')
seen.add(k)
else:
out.append(line)
else:
out.append(line)
for k,v in repl.items():
if k not in seen:
out.append(f'{k}={v}')
p.write_text('\n'.join(out)+'\n')
PY
ln -sfn $BASE/.env $APP/.env
docker exec furniture-3d-system-test-minio sh -lc "mc alias set local http://127.0.0.1:9000 $MINIO_USER $MINIO_PASS >/dev/null && mc mb -p local/$BUCKET >/dev/null 2>&1 || true && mc anonymous set download local/$BUCKET >/dev/null"
cd $APP
export PUPPETEER_SKIP_DOWNLOAD=true
if [ ! -d "$APP/node_modules" ] || [ "$OLD_LOCK_HASH" != "$NEW_LOCK_HASH" ]; then
npm install --no-audit --no-fund
fi
sudo -u postgres psql -d $DB_NAME <<SQL
ALTER TABLE "User"
ADD COLUMN IF NOT EXISTS "loginSource" TEXT NOT NULL DEFAULT 'LOCAL',
ADD COLUMN IF NOT EXISTS "dingtalkUserId" TEXT,
ADD COLUMN IF NOT EXISTS "dingtalkUnionId" TEXT,
ADD COLUMN IF NOT EXISTS "dingtalkBoundAt" TIMESTAMP(3);
CREATE UNIQUE INDEX IF NOT EXISTS "User_dingtalkUserId_key" ON "User"("dingtalkUserId");
CREATE UNIQUE INDEX IF NOT EXISTS "User_dingtalkUnionId_key" ON "User"("dingtalkUnionId");
CREATE INDEX IF NOT EXISTS "User_loginSource_idx" ON "User"("loginSource");
SQL
npx prisma generate
npm run build
pm2 delete furniture-3d-system-wyj-test >/dev/null 2>&1 || true
pm2 start /bin/bash --name furniture-3d-system-wyj-test --cwd $APP -- -lc "set -a; source ./.env; set +a; mkdir -p .next/standalone/.next; rm -rf .next/standalone/.next/static .next/standalone/public; cp -R .next/static .next/standalone/.next/static; cp -R public .next/standalone/public; exec env PORT=$APP_PORT HOSTNAME=0.0.0.0 node .next/standalone/server.js"
pm2 save >/dev/null
sleep 3
echo '=== DONE ==='
pm2 describe furniture-3d-system-wyj-test | sed -n '1,35p'
'@
$finalScript = $shellScript.Replace('__ARCHIVE_NAME__', $archiveName)
$finalScript = $finalScript.Replace('__JWT_SECRET__', $jwtSecret)
$finalScript = $finalScript.Replace('__NEXTAUTH_SECRET__', $nextAuthSecret)
# 使用最终极的 [System.IO.File] 写法,杜绝 0 字节和路径错误
$absoluteScriptPath = [System.IO.Path]::GetFullPath($scriptPath)
$finalScript = $finalScript -replace "`r`n", "`n"
[System.IO.File]::WriteAllText($absoluteScriptPath, $finalScript, [System.Text.UTF8Encoding]::new($false))
[PSCustomObject]@{
Archive = $archivePath
Script = $absoluteScriptPath
Size = (Get-Item $absoluteScriptPath).Length
}