234 lines
8.3 KiB
PowerShell
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
|
|
}
|