diff --git a/README.md b/README.md index 57c5689..6722b57 100644 --- a/README.md +++ b/README.md @@ -76,6 +76,7 @@ TechCase는 신뢰도 높은 기업 기술 블로그를 기반으로, 개발자 - [검색 설계](./docs/search-design.md) - [검색 평가](./docs/search-evaluation.md) - [AWS 인프라](./docs/aws-infra.md) +- [미니PC 웹 배포](./docs/mini-pc-web-deploy.md) - [로고 자산 관리](./docs/logo-assets.md) ## 상표 안내 diff --git a/docs/development-log.md b/docs/development-log.md index 6f58cb5..5cb3bab 100644 --- a/docs/development-log.md +++ b/docs/development-log.md @@ -3723,3 +3723,37 @@ uv run ruff check app/crawler/sitemap.py tests/conftest.py tests/test_sitemap_cr 4 passed All checks passed ``` + +## 77. 미니PC 웹 배포 스크립트 추가 + +운영 배포 중 `NEXT_PUBLIC_API_BASE_URL` 없이 정적 빌드를 실행하면 브라우저가 +기본값인 `http://localhost:8000`으로 API를 호출하는 문제가 발생할 수 있어, +mini PC 웹 배포 절차를 스크립트로 고정했습니다. + +추가: + +```text +scripts/deploy/mini-pc-web.sh +docs/mini-pc-web-deploy.md +``` + +스크립트 기본값: + +```text +Public URL: https://techcase.dadamda.site +Deploy host: home-2 +Remote web out: /home/godhkekf24/apps/techcase/current/apps/web/out +API base URL: https://techcase.dadamda.site +``` + +동작 순서: + +```text +1. NEXT_PUBLIC_API_BASE_URL을 운영 URL로 설정해 apps/web 빌드 +2. apps/web/out에 운영 URL이 포함되었는지 확인 +3. rsync -az --delete로 mini PC 정적 디렉터리에 동기화 +4. /, /health, /api/search, /api/suggest 스모크 체크 +``` + +운영 공개 URL에 배포하면서 API URL이 `localhost` 또는 `127.0.0.1`이면 +스크립트가 중단되도록 하여, 잘못된 정적 빌드가 배포되는 것을 방지했습니다. diff --git a/docs/development.md b/docs/development.md index 8386a55..ec48778 100644 --- a/docs/development.md +++ b/docs/development.md @@ -489,6 +489,10 @@ Terraform 실행 원칙은 다음과 같습니다. - NAT Instance: private EC2의 outbound internet 접근 제공 - Terraform: AWS 인프라 생성과 변경 관리 +AWS 정식 배포 전 mini PC에서 운영 검증할 때는 `scripts/deploy/mini-pc-web.sh`로 +웹 정적 빌드와 동기화, 공개 엔드포인트 스모크 체크를 함께 실행합니다. 자세한 +절차는 [미니PC 웹 배포](./mini-pc-web-deploy.md)에 기록합니다. + 나중에 트래픽과 데이터가 늘어나면 다음 순서로 분리합니다. 1. Elasticsearch를 별도 private EC2로 분리합니다. diff --git a/docs/mini-pc-web-deploy.md b/docs/mini-pc-web-deploy.md new file mode 100644 index 0000000..8e992d1 --- /dev/null +++ b/docs/mini-pc-web-deploy.md @@ -0,0 +1,64 @@ +# Mini PC Web Deploy + +TechCase 웹 프론트엔드는 Next.js static export 결과물인 `apps/web/out`을 +mini PC의 정적 파일 디렉터리로 동기화해서 배포합니다. + +운영 기본값은 다음과 같습니다. + +```text +Public URL: https://techcase.dadamda.site +Deploy host: home-2 +Remote web out: /home/godhkekf24/apps/techcase/current/apps/web/out +``` + +## 웹 배포 + +```bash +scripts/deploy/mini-pc-web.sh +``` + +스크립트는 다음 순서로 동작합니다. + +1. `NEXT_PUBLIC_API_BASE_URL`을 정적 빌드에 반영합니다. +2. `apps/web`에서 `npm run build`를 실행합니다. +3. `apps/web/out`을 mini PC로 `rsync --delete` 동기화합니다. +4. 공개 URL의 `/`, `/health`, `/api/search`, `/api/suggest`를 스모크 체크합니다. + +기본 API URL은 `TECHCASE_PUBLIC_URL`과 같은 +`https://techcase.dadamda.site`입니다. 운영 배포에서 +`http://localhost:8000` 같은 로컬 API URL이 들어가면 스크립트가 중단됩니다. + +## 사전 확인 + +실제 파일 동기화 없이 확인하려면 dry run을 사용합니다. + +```bash +scripts/deploy/mini-pc-web.sh --dry-run +``` + +이미 빌드된 `apps/web/out`만 동기화하려면 다음처럼 실행합니다. + +```bash +scripts/deploy/mini-pc-web.sh --skip-build +``` + +배포 없이 공개 엔드포인트만 확인하려면 다음처럼 실행합니다. + +```bash +scripts/deploy/mini-pc-web.sh --smoke-only +``` + +## 환경 변수 + +운영 기본값과 다른 환경에서 검증할 때만 환경 변수로 덮어씁니다. + +```bash +TECHCASE_DEPLOY_HOST=home-2 \ +TECHCASE_REMOTE_WEB_OUT=/home/godhkekf24/apps/techcase/current/apps/web/out \ +TECHCASE_PUBLIC_URL=https://techcase.dadamda.site \ +NEXT_PUBLIC_API_BASE_URL=https://techcase.dadamda.site \ +scripts/deploy/mini-pc-web.sh +``` + +로컬 테스트 용도로 공개 URL이 아닌 환경에 배포할 때는 +`TECHCASE_ALLOW_LOCAL_API=1`을 명시해야 로컬 API URL을 허용합니다. diff --git a/scripts/deploy/mini-pc-web.sh b/scripts/deploy/mini-pc-web.sh new file mode 100755 index 0000000..a29ea8a --- /dev/null +++ b/scripts/deploy/mini-pc-web.sh @@ -0,0 +1,165 @@ +#!/usr/bin/env bash +set -euo pipefail + +usage() { + cat <<'USAGE' +Usage: scripts/deploy/mini-pc-web.sh [options] + +Build and deploy the static TechCase web app to the mini PC. + +Options: + --dry-run Run rsync in dry-run mode. + --skip-build Reuse the existing apps/web/out directory. + --skip-smoke Skip public endpoint smoke checks after rsync. + --smoke-only Run smoke checks only. No build or rsync. + -h, --help Show this help. + +Environment overrides: + TECHCASE_DEPLOY_HOST SSH host alias. Default: home-2 + TECHCASE_REMOTE_WEB_OUT Remote static web directory. + Default: /home/godhkekf24/apps/techcase/current/apps/web/out + TECHCASE_PUBLIC_URL Public site URL. Default: https://techcase.dadamda.site + NEXT_PUBLIC_API_BASE_URL API base URL baked into the static build. + Default: same as TECHCASE_PUBLIC_URL + TECHCASE_ALLOW_LOCAL_API=1 Allow localhost API URL for a non-local public URL. +USAGE +} + +REMOTE_HOST="${TECHCASE_DEPLOY_HOST:-home-2}" +REMOTE_WEB_OUT="${TECHCASE_REMOTE_WEB_OUT:-/home/godhkekf24/apps/techcase/current/apps/web/out}" +PUBLIC_URL="${TECHCASE_PUBLIC_URL:-https://techcase.dadamda.site}" +API_BASE_URL="${NEXT_PUBLIC_API_BASE_URL:-$PUBLIC_URL}" + +DRY_RUN=0 +SKIP_BUILD=0 +SKIP_SMOKE=0 +SMOKE_ONLY=0 + +while (($#)); do + case "$1" in + --dry-run) + DRY_RUN=1 + ;; + --skip-build) + SKIP_BUILD=1 + ;; + --skip-smoke) + SKIP_SMOKE=1 + ;; + --smoke-only) + SMOKE_ONLY=1 + ;; + -h|--help) + usage + exit 0 + ;; + *) + echo "Unknown option: $1" >&2 + usage >&2 + exit 2 + ;; + esac + shift +done + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" +WEB_DIR="$REPO_ROOT/apps/web" +WEB_OUT="$WEB_DIR/out" + +require_command() { + if ! command -v "$1" >/dev/null 2>&1; then + echo "Required command not found: $1" >&2 + exit 1 + fi +} + +is_local_url() { + case "$1" in + http://localhost*|https://localhost*|http://127.0.0.1*|https://127.0.0.1*) + return 0 + ;; + *) + return 1 + ;; + esac +} + +validate_api_base_url() { + if is_local_url "$API_BASE_URL" && ! is_local_url "$PUBLIC_URL" && [[ "${TECHCASE_ALLOW_LOCAL_API:-0}" != "1" ]]; then + cat >&2 <&2 + exit 1 + fi +} + +deploy_web() { + require_command rsync + + if [[ ! -d "$WEB_OUT" ]]; then + echo "Missing build output: $WEB_OUT" >&2 + echo "Run without --skip-build first." >&2 + exit 1 + fi + + local rsync_args=(-az --delete) + if [[ "$DRY_RUN" == "1" ]]; then + rsync_args+=(--dry-run) + fi + + echo "Deploying web output to $REMOTE_HOST:$REMOTE_WEB_OUT" + rsync "${rsync_args[@]}" "$WEB_OUT/" "$REMOTE_HOST:$REMOTE_WEB_OUT/" +} + +smoke_check() { + require_command curl + + echo "Running smoke checks against $PUBLIC_URL" + curl -fsSI "$PUBLIC_URL/" >/dev/null + curl -fsS "$PUBLIC_URL/health" >/dev/null + curl -fsS "$PUBLIC_URL/api/search?q=RAG&sort=relevance&page=1&page_size=1" >/dev/null + curl -fsS "$PUBLIC_URL/api/suggest?q=rag" >/dev/null +} + +if [[ "$SMOKE_ONLY" == "1" ]]; then + smoke_check + exit 0 +fi + +if [[ "$SKIP_BUILD" != "1" ]]; then + build_web +else + validate_api_base_url +fi + +deploy_web + +if [[ "$SKIP_SMOKE" != "1" && "$DRY_RUN" != "1" ]]; then + smoke_check +elif [[ "$DRY_RUN" == "1" ]]; then + echo "Skipping smoke checks for dry-run deploy" +fi