# ============================================================================ # scripts/deploy-nas.ps1 # D3RO Voice — Automated NAS Docker Deployment & Packaging Script (PowerShell) # ============================================================================ param ( [string]$NasHost = "", [string]$NasUser = "", [int]$NasSshPort = 0, [string]$NasPath = "", [int]$Port = 0, [switch]$SkipBuild = $false, [switch]$DeploySsh = $false ) $ErrorActionPreference = "Stop" # Load variables from root .env if present $rootEnvFile = Join-Path $PSScriptRoot "..\.env" $envDict = @{} if (Test-Path $rootEnvFile) { Get-Content $rootEnvFile | ForEach-Object { $line = $_.Trim() if ($line -and -not $line.StartsWith("#") -and $line.Contains("=")) { $parts = $line.Split("=", 2) $envDict[$parts[0].Trim()] = $parts[1].Trim() } } } # Fallback to .env values if parameters are not provided if (-not $NasHost -and $envDict.ContainsKey("NAS_HOST")) { $NasHost = $envDict["NAS_HOST"] } if (-not $NasUser -and $envDict.ContainsKey("NAS_USER")) { $NasUser = $envDict["NAS_USER"] } if ($NasSshPort -eq 0 -and $envDict.ContainsKey("NAS_SSH_PORT")) { [int]::TryParse($envDict["NAS_SSH_PORT"], [ref]$NasSshPort) | Out-Null } if (-not $NasPath -and $envDict.ContainsKey("NAS_DEPLOY_PATH")) { $NasPath = $envDict["NAS_DEPLOY_PATH"] } if ($Port -eq 0 -and $envDict.ContainsKey("PORT")) { [int]::TryParse($envDict["PORT"], [ref]$Port) | Out-Null } # Defaults if still empty if (-not $NasUser) { $NasUser = "yunchan" } if ($NasSshPort -eq 0) { $NasSshPort = 22 } if (-not $NasPath) { $NasPath = "/volume1/docker/d3ro" } if ($Port -eq 0) { $Port = 5000 } Write-Host "========================================================" -ForegroundColor Cyan Write-Host " D3RO Voice — NAS Docker Deployment & Packaging Tool " -ForegroundColor Cyan Write-Host "========================================================" -ForegroundColor Cyan if ($NasHost) { Write-Host " [Target NAS] $NasUser@$($NasHost):$NasSshPort (Path: $NasPath)" -ForegroundColor Gray } # 1. Output directory setup $outDir = Join-Path $PSScriptRoot "..\out" $nasPkgDir = Join-Path $outDir "nas-package" if (-not (Test-Path $nasPkgDir)) { New-Item -ItemType Directory -Path $nasPkgDir -Force | Out-Null } # 2. Build Docker Image if (-not $SkipBuild) { Write-Host "`n[1/4] Building D3RO Voice API & Promotion Site Image..." -ForegroundColor Yellow docker build -t d3ro-voice-api:latest ./apps/api-server if ($LASTEXITCODE -ne 0) { Write-Error "API Docker image build failed!" exit 1 } Write-Host " Docker image built: d3ro-voice-api:latest" -ForegroundColor Green Write-Host "`n[1.5/4] Building D3RO Voice Next.js Admin CRM Image..." -ForegroundColor Yellow docker build -t d3ro-voice-admin:latest -f apps/admin/Dockerfile . if ($LASTEXITCODE -ne 0) { Write-Error "Admin Docker image build failed!" exit 1 } Write-Host " Docker image built: d3ro-voice-admin:latest" -ForegroundColor Green } else { Write-Host "`n[1/4] Skipping Docker build (-SkipBuild specified)" -ForegroundColor Gray } # 3. Export Docker Image Archives $tarPath = Join-Path $nasPkgDir "d3ro-voice-api.tar" $adminTarPath = Join-Path $nasPkgDir "d3ro-voice-admin.tar" Write-Host "`n[2/4] Exporting Docker Images to Archives..." -ForegroundColor Yellow docker save -o $tarPath d3ro-voice-api:latest docker save -o $adminTarPath d3ro-voice-admin:latest Write-Host " Image archives exported: $tarPath, $adminTarPath" -ForegroundColor Green # 4. Prepare Standalone NAS Package Write-Host "`n[3/4] Packaging NAS deployment files..." -ForegroundColor Yellow # Copy docker-compose.nas.yml as docker-compose.yml in package Copy-Item (Join-Path $PSScriptRoot "..\docker-compose.nas.yml") (Join-Path $nasPkgDir "docker-compose.yml") -Force # Generate an allowlisted deployment environment. Never copy the repository # .env because it can also contain Git, SSH, and NAS credentials. $requiredDeploymentSecrets = @( "JWT_SECRET", "JWT_ISSUER", "JWT_AUDIENCE", "ADMIN_BOOTSTRAP_TOKEN", "D3RO_API_TOKEN", "ADMIN_SESSION_SECRET", "API_SERVER_URL", "CORS_ALLOWED_ORIGINS", "ALLOWED_HOSTS" ) $deploymentSecrets = @{} foreach ($name in $requiredDeploymentSecrets) { $value = if ($envDict.ContainsKey($name)) { $envDict[$name] } else { [Environment]::GetEnvironmentVariable($name) } if ([string]::IsNullOrWhiteSpace($value)) { throw "$name is required for a NAS deployment package." } if ($value.Contains("`r") -or $value.Contains("`n")) { throw "$name must be a single-line value." } $deploymentSecrets[$name] = $value } if ([Text.Encoding]::UTF8.GetByteCount($deploymentSecrets["D3RO_API_TOKEN"]) -lt 32) { throw "D3RO_API_TOKEN must contain at least 32 UTF-8 bytes." } $envContent = @" PORT=$Port DATA_PATH=./data JWT_SECRET=$($deploymentSecrets["JWT_SECRET"]) JWT_ISSUER=$($deploymentSecrets["JWT_ISSUER"]) JWT_AUDIENCE=$($deploymentSecrets["JWT_AUDIENCE"]) ADMIN_BOOTSTRAP_TOKEN=$($deploymentSecrets["ADMIN_BOOTSTRAP_TOKEN"]) D3RO_API_TOKEN=$($deploymentSecrets["D3RO_API_TOKEN"]) ADMIN_SESSION_SECRET=$($deploymentSecrets["ADMIN_SESSION_SECRET"]) API_SERVER_URL=$($deploymentSecrets["API_SERVER_URL"]) CORS_ALLOWED_ORIGINS=$($deploymentSecrets["CORS_ALLOWED_ORIGINS"]) ALLOWED_HOSTS=$($deploymentSecrets["ALLOWED_HOSTS"]) TZ=Asia/Seoul "@ $deploymentEnvPath = Join-Path $nasPkgDir ".env" Set-Content -Path $deploymentEnvPath -Value $envContent -Encoding UTF8 $deploymentEnvAcl = Get-Acl -LiteralPath $deploymentEnvPath $deploymentEnvAcl.SetAccessRuleProtection($true, $false) $deploymentEnvAcl.AddAccessRule([System.Security.AccessControl.FileSystemAccessRule]::new( [System.Security.Principal.WindowsIdentity]::GetCurrent().Name, [System.Security.AccessControl.FileSystemRights]::FullControl, [System.Security.AccessControl.AccessControlType]::Allow )) Set-Acl -LiteralPath $deploymentEnvPath -AclObject $deploymentEnvAcl # Copy control script Copy-Item (Join-Path $PSScriptRoot "nas-control.sh") (Join-Path $nasPkgDir "nas-control.sh") -Force -ErrorAction SilentlyContinue # Create Quick Instructions $readmeContent = @" ======================================================================== D3RO Voice — Standalone NAS Deployment Package ======================================================================== [Synology / QNAP / Linux NAS 배포 방법] 1. 이 폴더의 모든 파일 (d3ro-voice-api.tar, docker-compose.yml, .env, nas-control.sh)을 NAS의 Docker 작업 폴더 ($NasPath)에 업로드합니다. 2. NAS SSH 터미널에 접속하여 해당 폴더로 이동합니다: cd $NasPath 3. Docker 이미지를 로드합니다: docker load < d3ro-voice-api.tar 4. 서비스를 시작합니다: docker compose up -d 5. 브라우저에서 접속 확인: - 포털 메인: http://:$Port/ - 관리자 백오피스: http://:$Port/admin - API Swagger: http://:$Port/swagger - 헬스체크: http://:$Port/health "@ Set-Content -Path (Join-Path $nasPkgDir "README.txt") -Value $readmeContent -Encoding UTF8 Write-Host " NAS Deployment Package ready at: $nasPkgDir" -ForegroundColor Green # 5. Remote SSH Deployment (Optional or if -DeploySsh is specified) if ($DeploySsh -and $NasHost) { Write-Host "`n[4/4] Deploying to Remote NAS ($NasUser@$($NasHost):$NasPath on SSH Port $NasSshPort)..." -ForegroundColor Yellow $sshCmd = "ssh -p $NasSshPort" $scpCmd = "scp -O -P $NasSshPort" Write-Host " [1/3] Creating remote directory on NAS ($NasPath/data)..." -ForegroundColor Gray ssh -p $NasSshPort "$NasUser@$NasHost" "mkdir -p $NasPath/data" Write-Host " [2/3] Uploading deployment package to NAS (SCP)..." -ForegroundColor Gray scp -O -P $NasSshPort "$tarPath" "$adminTarPath" (Join-Path $nasPkgDir "docker-compose.yml") (Join-Path $nasPkgDir ".env") (Join-Path $nasPkgDir "nas-control.sh") "$NasUser@$($NasHost):$NasPath/" Write-Host " [3/3] Loading Docker images and launching services on NAS..." -ForegroundColor Gray ssh -p $NasSshPort "$NasUser@$NasHost" "cd $NasPath && chmod +x nas-control.sh 2>/dev/null; docker load < d3ro-voice-api.tar && docker load < d3ro-voice-admin.tar && docker compose down 2>/dev/null; docker compose up -d" Write-Host "`n========================================================" -ForegroundColor Green Write-Host " NAS Deployment Completed Successfully!" -ForegroundColor Green Write-Host " Access your D3RO Cloud Services at:" -ForegroundColor Cyan Write-Host " - Official Promotion Site: http://$($NasHost):$Port/" -ForegroundColor White Write-Host " - Next.js Admin CRM: http://$($NasHost):3001/" -ForegroundColor White Write-Host " - Swagger API Docs: http://$($NasHost):$Port/swagger" -ForegroundColor White Write-Host " - Health Check: http://$($NasHost):$Port/health" -ForegroundColor White Write-Host "========================================================" -ForegroundColor Green } else { Write-Host "`n========================================================" -ForegroundColor Green Write-Host " NAS Package Ready!" -ForegroundColor Green Write-Host " Output Folder: $nasPkgDir" -ForegroundColor White Write-Host " Files generated:" -ForegroundColor White Write-Host " - d3ro-voice-api.tar (Docker image archive)" -ForegroundColor Gray Write-Host " - docker-compose.yml (NAS Compose configuration)" -ForegroundColor Gray Write-Host " - .env (Environment variables)" -ForegroundColor Gray Write-Host " - nas-control.sh (Container management script)" -ForegroundColor Gray Write-Host " - README.txt (Deployment guide)" -ForegroundColor Gray Write-Host "`n To deploy automatically via SSH, run:" -ForegroundColor Cyan Write-Host " .\scripts\deploy-nas.ps1 -DeploySsh" -ForegroundColor Yellow Write-Host "========================================================" -ForegroundColor Green }