Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)

## 상표 안내
Expand Down
34 changes: 34 additions & 0 deletions docs/development-log.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`이면
스크립트가 중단되도록 하여, 잘못된 정적 빌드가 배포되는 것을 방지했습니다.
4 changes: 4 additions & 0 deletions docs/development.md
Original file line number Diff line number Diff line change
Expand Up @@ -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로 분리합니다.
Expand Down
64 changes: 64 additions & 0 deletions docs/mini-pc-web-deploy.md
Original file line number Diff line number Diff line change
@@ -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을 허용합니다.
165 changes: 165 additions & 0 deletions scripts/deploy/mini-pc-web.sh
Original file line number Diff line number Diff line change
@@ -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 <<EOF
Refusing to deploy a public build with a local API URL.

PUBLIC_URL=$PUBLIC_URL
NEXT_PUBLIC_API_BASE_URL=$API_BASE_URL

Set NEXT_PUBLIC_API_BASE_URL to the public API URL, or set
TECHCASE_ALLOW_LOCAL_API=1 only for a deliberate local test.
EOF
exit 1
fi
}

build_web() {
require_command npm
validate_api_base_url

echo "Building web app"
echo "NEXT_PUBLIC_API_BASE_URL=$API_BASE_URL"
(
cd "$WEB_DIR"
NEXT_PUBLIC_API_BASE_URL="$API_BASE_URL" npm run build
)

if ! grep -R -F -q "$API_BASE_URL" "$WEB_OUT"; then
echo "Built output does not contain NEXT_PUBLIC_API_BASE_URL=$API_BASE_URL" >&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