feat(web): serve the web app under /app and send every billing link there (WS-B)
apps/web was never deployed, so /billing on the public domain returned the landing page and d3ro.dev (desktop "upgrade") did not resolve. - apps/web runs with basePath /app and output standalone; /download and /releases redirect to the site's #download. A Dockerfile and a d3ro-web compose service (port 3002) deploy it to the NAS with the other images. - The site bridge worker forwards /app/* to WEB_APP_ORIGIN (the tunnel host) and rewrites upstream redirects; everything else still goes to Pages. With no origin configured /app answers 503 instead of the landing page. - Desktop upgrade, desktop Stripe return, mobile subscription management, the web checkout/portal returns and the site all use billingUrl(); the return query is success=1 / canceled=1, which the billing page reads. The billing page highlights ?tier=pro|pro_plus, and signing in from a billing link returns to the same plan. - auth/callback pins the redirect origin in production and rejects protocol-relative next= values (open redirect). - Mobile legal links use SITE_URLS (fixes the missing slash on /terms). - Compose drops the unused NEXT_PUBLIC_API_URL and the dead wwwroot legal mounts; deploy scripts add the web image and the SUPABASE_* values the NAS compose already required; .dockerignore keeps app .env files out of images. - Supabase auth redirects allow /app/** (remote dashboard must match). Policy: docs/REFACTOR_POLICY.md Wave 3, W3-3 and W3-4.
This commit is contained in:
parent
88f24d84a1
commit
b6fe588a7c
30 changed files with 493 additions and 95 deletions
|
|
@ -58,7 +58,7 @@ if (-not (Test-Path $nasPkgDir)) {
|
|||
|
||||
# 2. Build Docker Image
|
||||
if (-not $SkipBuild) {
|
||||
Write-Host "`n[1/4] Building D3RO Voice API & Promotion Site Image..." -ForegroundColor Yellow
|
||||
Write-Host "`n[1/4] Building D3RO Voice API Image..." -ForegroundColor Yellow
|
||||
docker build -t d3ro-voice-api:latest ./apps/api-server
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Error "API Docker image build failed!"
|
||||
|
|
@ -73,6 +73,30 @@ if (-not $SkipBuild) {
|
|||
exit 1
|
||||
}
|
||||
Write-Host " Docker image built: d3ro-voice-admin:latest" -ForegroundColor Green
|
||||
|
||||
# Web app (@d3ro/web, served at /app). NEXT_PUBLIC_* values are baked in at build time.
|
||||
Write-Host "`n[1.6/4] Building D3RO Voice Next.js Web App Image..." -ForegroundColor Yellow
|
||||
$webBuildArgs = @()
|
||||
foreach ($name in @("NEXT_PUBLIC_SUPABASE_URL", "NEXT_PUBLIC_SUPABASE_ANON_KEY", "NEXT_PUBLIC_PAYPLE_CLIENT_KEY")) {
|
||||
$value = if ($envDict.ContainsKey($name)) { $envDict[$name] } else { [Environment]::GetEnvironmentVariable($name) }
|
||||
if ([string]::IsNullOrWhiteSpace($value)) {
|
||||
if ($name -eq "NEXT_PUBLIC_PAYPLE_CLIENT_KEY") {
|
||||
Write-Warning "$name is empty: the web app will build with Payple checkout disabled."
|
||||
continue
|
||||
}
|
||||
throw "$name is required to build the web app image."
|
||||
}
|
||||
if ($value.Contains("`r") -or $value.Contains("`n")) {
|
||||
throw "$name must be a single-line value."
|
||||
}
|
||||
$webBuildArgs += @("--build-arg", "$name=$value")
|
||||
}
|
||||
docker build -t d3ro-voice-web:latest -f apps/web/Dockerfile @webBuildArgs .
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Error "Web Docker image build failed!"
|
||||
exit 1
|
||||
}
|
||||
Write-Host " Docker image built: d3ro-voice-web:latest" -ForegroundColor Green
|
||||
} else {
|
||||
Write-Host "`n[1/4] Skipping Docker build (-SkipBuild specified)" -ForegroundColor Gray
|
||||
}
|
||||
|
|
@ -80,10 +104,12 @@ if (-not $SkipBuild) {
|
|||
# 3. Export Docker Image Archives
|
||||
$tarPath = Join-Path $nasPkgDir "d3ro-voice-api.tar"
|
||||
$adminTarPath = Join-Path $nasPkgDir "d3ro-voice-admin.tar"
|
||||
$webTarPath = Join-Path $nasPkgDir "d3ro-voice-web.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
|
||||
docker save -o $webTarPath d3ro-voice-web:latest
|
||||
Write-Host " Image archives exported: $tarPath, $adminTarPath, $webTarPath" -ForegroundColor Green
|
||||
|
||||
# 4. Prepare Standalone NAS Package
|
||||
Write-Host "`n[3/4] Packaging NAS deployment files..." -ForegroundColor Yellow
|
||||
|
|
@ -102,7 +128,9 @@ $requiredDeploymentSecrets = @(
|
|||
"ADMIN_SESSION_SECRET",
|
||||
"API_SERVER_URL",
|
||||
"CORS_ALLOWED_ORIGINS",
|
||||
"ALLOWED_HOSTS"
|
||||
"ALLOWED_HOSTS",
|
||||
"SUPABASE_URL",
|
||||
"SUPABASE_SERVICE_ROLE_KEY"
|
||||
)
|
||||
$deploymentSecrets = @{}
|
||||
foreach ($name in $requiredDeploymentSecrets) {
|
||||
|
|
@ -130,6 +158,8 @@ 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"])
|
||||
SUPABASE_URL=$($deploymentSecrets["SUPABASE_URL"])
|
||||
SUPABASE_SERVICE_ROLE_KEY=$($deploymentSecrets["SUPABASE_SERVICE_ROLE_KEY"])
|
||||
TZ=Asia/Seoul
|
||||
"@
|
||||
$deploymentEnvPath = Join-Path $nasPkgDir ".env"
|
||||
|
|
@ -154,14 +184,16 @@ $readmeContent = @"
|
|||
|
||||
[Synology / QNAP / Linux NAS 배포 방법]
|
||||
|
||||
1. 이 폴더의 모든 파일 (d3ro-voice-api.tar, docker-compose.yml, .env, nas-control.sh)을
|
||||
NAS의 Docker 작업 폴더 ($NasPath)에 업로드합니다.
|
||||
1. 이 폴더의 모든 파일 (d3ro-voice-api.tar, d3ro-voice-admin.tar, d3ro-voice-web.tar,
|
||||
docker-compose.yml, .env, nas-control.sh)을 NAS의 Docker 작업 폴더 ($NasPath)에 업로드합니다.
|
||||
|
||||
2. NAS SSH 터미널에 접속하여 해당 폴더로 이동합니다:
|
||||
cd $NasPath
|
||||
|
||||
3. Docker 이미지를 로드합니다:
|
||||
docker load < d3ro-voice-api.tar
|
||||
docker load < d3ro-voice-admin.tar
|
||||
docker load < d3ro-voice-web.tar
|
||||
|
||||
4. 서비스를 시작합니다:
|
||||
docker compose up -d
|
||||
|
|
@ -171,6 +203,7 @@ $readmeContent = @"
|
|||
- 관리자 백오피스: http://<NAS_IP>:$Port/admin
|
||||
- API Swagger: http://<NAS_IP>:$Port/swagger
|
||||
- 헬스체크: http://<NAS_IP>:$Port/health
|
||||
- 웹앱: http://<NAS_IP>:3002/app/login (공개 주소 https://d3ro.chanpaca.net/app 는 사이트 브리지 워커 경유)
|
||||
"@
|
||||
Set-Content -Path (Join-Path $nasPkgDir "README.txt") -Value $readmeContent -Encoding UTF8
|
||||
|
||||
|
|
@ -187,16 +220,16 @@ if ($DeploySsh -and $NasHost) {
|
|||
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/"
|
||||
scp -O -P $NasSshPort "$tarPath" "$adminTarPath" "$webTarPath" (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"
|
||||
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 load < d3ro-voice-web.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 " - Next.js Web App: http://$($NasHost):3002/app/login" -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
|
||||
|
|
@ -206,6 +239,8 @@ if ($DeploySsh -and $NasHost) {
|
|||
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 " - d3ro-voice-admin.tar (Docker image archive)" -ForegroundColor Gray
|
||||
Write-Host " - d3ro-voice-web.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
|
||||
|
|
|
|||
|
|
@ -15,6 +15,11 @@ set -e
|
|||
: "${API_SERVER_URL:?API_SERVER_URL is required}"
|
||||
: "${CORS_ALLOWED_ORIGINS:?CORS_ALLOWED_ORIGINS is required}"
|
||||
: "${ALLOWED_HOSTS:?ALLOWED_HOSTS is required}"
|
||||
: "${SUPABASE_URL:?SUPABASE_URL is required}"
|
||||
: "${SUPABASE_SERVICE_ROLE_KEY:?SUPABASE_SERVICE_ROLE_KEY is required}"
|
||||
# Web app (@d3ro/web) build args — NEXT_PUBLIC_* values are baked into the bundle at build time.
|
||||
: "${NEXT_PUBLIC_SUPABASE_URL:?NEXT_PUBLIC_SUPABASE_URL is required}"
|
||||
: "${NEXT_PUBLIC_SUPABASE_ANON_KEY:?NEXT_PUBLIC_SUPABASE_ANON_KEY is required}"
|
||||
|
||||
for value in \
|
||||
"$JWT_SECRET" \
|
||||
|
|
@ -25,7 +30,12 @@ for value in \
|
|||
"$ADMIN_SESSION_SECRET" \
|
||||
"$API_SERVER_URL" \
|
||||
"$CORS_ALLOWED_ORIGINS" \
|
||||
"$ALLOWED_HOSTS"; do
|
||||
"$ALLOWED_HOSTS" \
|
||||
"$SUPABASE_URL" \
|
||||
"$SUPABASE_SERVICE_ROLE_KEY" \
|
||||
"$NEXT_PUBLIC_SUPABASE_URL" \
|
||||
"$NEXT_PUBLIC_SUPABASE_ANON_KEY" \
|
||||
"${NEXT_PUBLIC_PAYPLE_CLIENT_KEY:-}"; do
|
||||
case "$value" in
|
||||
*$'\n'*|*$'\r'*) echo "Deployment values must be single-line" >&2; exit 1 ;;
|
||||
esac
|
||||
|
|
@ -61,10 +71,29 @@ echo -e "\n${YELLOW}[1/4] Building D3RO Voice API & BackOffice Docker Image...${
|
|||
docker build -t d3ro-voice-api:latest -f "$ROOT_DIR/apps/api-server/Dockerfile" "$ROOT_DIR/apps/api-server"
|
||||
echo -e "${GREEN}Docker image built: d3ro-voice-api:latest${NC}"
|
||||
|
||||
echo -e "\n${YELLOW}[1.5/4] Building D3RO Voice Next.js Admin CRM Image...${NC}"
|
||||
docker build -t d3ro-voice-admin:latest -f "$ROOT_DIR/apps/admin/Dockerfile" "$ROOT_DIR"
|
||||
echo -e "${GREEN}Docker image built: d3ro-voice-admin:latest${NC}"
|
||||
|
||||
echo -e "\n${YELLOW}[1.6/4] Building D3RO Voice Next.js Web App Image (/app)...${NC}"
|
||||
if [ -z "${NEXT_PUBLIC_PAYPLE_CLIENT_KEY:-}" ]; then
|
||||
echo -e "${YELLOW}NEXT_PUBLIC_PAYPLE_CLIENT_KEY is empty: Payple checkout will be disabled in the web app.${NC}"
|
||||
fi
|
||||
docker build -t d3ro-voice-web:latest -f "$ROOT_DIR/apps/web/Dockerfile" \
|
||||
--build-arg "NEXT_PUBLIC_SUPABASE_URL=$NEXT_PUBLIC_SUPABASE_URL" \
|
||||
--build-arg "NEXT_PUBLIC_SUPABASE_ANON_KEY=$NEXT_PUBLIC_SUPABASE_ANON_KEY" \
|
||||
--build-arg "NEXT_PUBLIC_PAYPLE_CLIENT_KEY=${NEXT_PUBLIC_PAYPLE_CLIENT_KEY:-}" \
|
||||
"$ROOT_DIR"
|
||||
echo -e "${GREEN}Docker image built: d3ro-voice-web:latest${NC}"
|
||||
|
||||
TAR_PATH="$OUT_DIR/d3ro-voice-api.tar"
|
||||
echo -e "\n${YELLOW}[2/4] Exporting Docker Image to Archive ($TAR_PATH)...${NC}"
|
||||
ADMIN_TAR_PATH="$OUT_DIR/d3ro-voice-admin.tar"
|
||||
WEB_TAR_PATH="$OUT_DIR/d3ro-voice-web.tar"
|
||||
echo -e "\n${YELLOW}[2/4] Exporting Docker Images to Archives...${NC}"
|
||||
docker save -o "$TAR_PATH" d3ro-voice-api:latest
|
||||
echo -e "${GREEN}Image archive exported: $TAR_PATH${NC}"
|
||||
docker save -o "$ADMIN_TAR_PATH" d3ro-voice-admin:latest
|
||||
docker save -o "$WEB_TAR_PATH" d3ro-voice-web:latest
|
||||
echo -e "${GREEN}Image archives exported: $TAR_PATH, $ADMIN_TAR_PATH, $WEB_TAR_PATH${NC}"
|
||||
|
||||
echo -e "\n${YELLOW}[3/4] Packaging NAS deployment files...${NC}"
|
||||
cp "$ROOT_DIR/docker-compose.nas.yml" "$OUT_DIR/docker-compose.yml"
|
||||
|
|
@ -83,6 +112,8 @@ ADMIN_SESSION_SECRET=$ADMIN_SESSION_SECRET
|
|||
API_SERVER_URL=$API_SERVER_URL
|
||||
CORS_ALLOWED_ORIGINS=$CORS_ALLOWED_ORIGINS
|
||||
ALLOWED_HOSTS=$ALLOWED_HOSTS
|
||||
SUPABASE_URL=$SUPABASE_URL
|
||||
SUPABASE_SERVICE_ROLE_KEY=$SUPABASE_SERVICE_ROLE_KEY
|
||||
TZ=Asia/Seoul
|
||||
EOF
|
||||
chmod 600 "$OUT_DIR/.env"
|
||||
|
|
@ -94,14 +125,16 @@ cat <<EOF > "$OUT_DIR/README.txt"
|
|||
|
||||
[Synology / QNAP / Linux NAS 배포 방법]
|
||||
|
||||
1. 이 폴더의 모든 파일 (d3ro-voice-api.tar, docker-compose.yml, .env, nas-control.sh)을
|
||||
NAS의 Docker 작업 폴더 (예: /volume1/docker/d3ro)에 업로드합니다.
|
||||
1. 이 폴더의 모든 파일 (d3ro-voice-api.tar, d3ro-voice-admin.tar, d3ro-voice-web.tar,
|
||||
docker-compose.yml, .env, nas-control.sh)을 NAS의 Docker 작업 폴더 (예: /volume1/docker/d3ro)에 업로드합니다.
|
||||
|
||||
2. NAS SSH 터미널에 접속하여 해당 폴더로 이동합니다:
|
||||
cd /volume1/docker/d3ro
|
||||
|
||||
3. Docker 이미지를 로드합니다:
|
||||
docker load < d3ro-voice-api.tar
|
||||
docker load < d3ro-voice-admin.tar
|
||||
docker load < d3ro-voice-web.tar
|
||||
|
||||
4. 서비스를 시작합니다:
|
||||
./nas-control.sh start (또는 docker compose up -d)
|
||||
|
|
@ -111,6 +144,7 @@ cat <<EOF > "$OUT_DIR/README.txt"
|
|||
- 관리자 백오피스: http://<NAS_IP>:$PORT/admin
|
||||
- API Swagger: http://<NAS_IP>:$PORT/swagger
|
||||
- 헬스체크: http://<NAS_IP>:$PORT/health
|
||||
- 웹앱: http://<NAS_IP>:3002/app/login (공개 주소 https://d3ro.chanpaca.net/app 는 사이트 브리지 워커 경유)
|
||||
EOF
|
||||
|
||||
echo -e "${GREEN}NAS Deployment Package ready at: $OUT_DIR${NC}"
|
||||
|
|
@ -118,8 +152,8 @@ echo -e "${GREEN}NAS Deployment Package ready at: $OUT_DIR${NC}"
|
|||
if [ -n "$NAS_HOST" ]; then
|
||||
echo -e "\n${YELLOW}[4/4] Deploying to Remote NAS ($NAS_USER@$NAS_HOST:$NAS_PATH)...${NC}"
|
||||
ssh "$NAS_USER@$NAS_HOST" "mkdir -p $NAS_PATH/data"
|
||||
scp -O "$TAR_PATH" "$OUT_DIR/docker-compose.yml" "$OUT_DIR/.env" "$OUT_DIR/nas-control.sh" "$NAS_USER@$NAS_HOST:$NAS_PATH/"
|
||||
ssh "$NAS_USER@$NAS_HOST" "cd $NAS_PATH && chmod +x nas-control.sh && docker load < d3ro-voice-api.tar && docker compose down 2>/dev/null || true; docker compose up -d"
|
||||
scp -O "$TAR_PATH" "$ADMIN_TAR_PATH" "$WEB_TAR_PATH" "$OUT_DIR/docker-compose.yml" "$OUT_DIR/.env" "$OUT_DIR/nas-control.sh" "$NAS_USER@$NAS_HOST:$NAS_PATH/"
|
||||
ssh "$NAS_USER@$NAS_HOST" "cd $NAS_PATH && chmod +x nas-control.sh && docker load < d3ro-voice-api.tar && docker load < d3ro-voice-admin.tar && docker load < d3ro-voice-web.tar && docker compose down 2>/dev/null || true; docker compose up -d"
|
||||
|
||||
echo -e "\n${GREEN}========================================================${NC}"
|
||||
echo -e "${GREEN} NAS Deployment Completed Successfully!${NC}"
|
||||
|
|
@ -128,6 +162,7 @@ if [ -n "$NAS_HOST" ]; then
|
|||
echo -e " - Admin: http://$NAS_HOST:$PORT/admin"
|
||||
echo -e " - Swagger: http://$NAS_HOST:$PORT/swagger"
|
||||
echo -e " - Health: http://$NAS_HOST:$PORT/health"
|
||||
echo -e " - Web App: http://$NAS_HOST:3002/app/login"
|
||||
echo -e "${GREEN}========================================================${NC}"
|
||||
else
|
||||
echo -e "\n${GREEN}========================================================${NC}"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue