diff --git a/TROUBLESHOOTING.md b/TROUBLESHOOTING.md new file mode 100644 index 0000000..4edee9a --- /dev/null +++ b/TROUBLESHOOTING.md @@ -0,0 +1,241 @@ +# AutonetSellCar 트러블슈팅 가이드 + +이 문서는 배포 및 운영 중 발생한 문제와 해결 방법을 정리합니다. + +--- + +## 2024-12-30: 운영서버 이미지 표시 문제 + +### 증상 +- 개발서버(192.168.0.204)에서는 이미지가 정상 표시 +- 운영서버(192.168.0.202)에서 외부 PC로 접속 시 이미지가 표시되지 않음 +- 브라우저 개발자 도구에서 이미지 요청 URL이 `http://localhost:8000/...`으로 되어 있음 + +### 원인 분석 + +#### 1차 원인: 서버2 소스 코드 미동기화 + +**문제**: 서버2의 프론트엔드 소스 코드가 개발서버와 동기화되지 않아 오래된 코드가 배포됨 + +| 위치 | 코드 | +|------|------| +| 서버4 (개발) | `process.env.NEXT_PUBLIC_API_URL \|\| 'http://localhost:8000'` | +| 서버2 (운영) | `'http://localhost:8000'` (하드코딩) | + +**영향받은 파일**: +- `/admin/hero-banners/page.tsx` +- `/admin/cars/page.tsx` +- 기타 관리자 페이지 + +#### 2차 원인: 차량 상세 페이지 특수 패턴 + +**문제**: `/cars/[id]/page.tsx`에서 다른 방식으로 localhost가 하드코딩됨 + +```typescript +// 문제 코드 +const getImageUrl = (url: string | undefined): string => { + if (!url) return ''; + if (url.startsWith('http://') || url.startsWith('https://')) { + return url; + } + // 포트만 동적으로 추출하고 localhost는 하드코딩 + const backendPort = process.env.NEXT_PUBLIC_API_URL?.includes('8001') ? 8001 : 8000; + return `http://localhost:${backendPort}${url}`; +}; +``` + +### 해결 방법 + +#### Step 1: 소스 코드 동기화 + +```bash +# 서버4 (Windows)에서 실행 +scp -r D:/Workspace/claudeCode/AutonetSellCar.com/frontend/src damon@192.168.0.202:/opt/autonet/production/frontend/ +``` + +#### Step 2: 차량 상세 페이지 수정 + +```typescript +// 수정된 코드 (/cars/[id]/page.tsx) +const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:8000'; + +const getImageUrl = (url: string | undefined): string => { + if (!url) return ''; + if (url.startsWith('http://') || url.startsWith('https://')) { + return url; + } + return `${API_BASE_URL}${url}`; +}; +``` + +#### Step 3: Docker 이미지 재빌드 + +```bash +# 서버2에서 실행 +cd /opt/autonet/production/frontend + +# 캐시 없이 재빌드 (중요!) +docker build --no-cache -t autonet-frontend-v2 . + +# 컨테이너 교체 +docker stop autonet-frontend +docker rm autonet-frontend +docker run -d --name autonet-frontend -p 3000:3000 autonet-frontend-v2 +``` + +#### Step 4: 확인 + +```bash +# localhost:8000이 없는지 확인 +docker exec autonet-frontend sh -c "grep -l 'localhost:8000' /app/.next/static/chunks/app/**/*.js 2>/dev/null || echo 'No localhost:8000 found'" + +# 올바른 URL이 있는지 확인 +docker exec autonet-frontend sh -c "grep -o '192.168.0.202:8000' /app/.next/static/chunks/app/**/*.js 2>/dev/null | wc -l" +``` + +### 핵심 개념: Next.js 환경변수 + +> **중요**: Next.js의 `NEXT_PUBLIC_*` 환경변수는 **빌드 시점**에 JavaScript 번들에 포함됩니다. + +| 시점 | 설명 | +|------|------| +| 빌드 시점 | `NEXT_PUBLIC_*` 변수 값이 JS 코드에 직접 삽입됨 | +| 런타임 | 이미 삽입된 값이 사용됨 (변경 불가) | + +따라서: +- `.env.production` 수정 후 반드시 **재빌드** 필요 +- Docker 환경에서는 Dockerfile에 `ARG`/`ENV` 설정 필요 + +```dockerfile +# Dockerfile 예시 (builder 단계) +ARG NEXT_PUBLIC_API_URL=http://192.168.0.202:8000 +ENV NEXT_PUBLIC_API_URL=${NEXT_PUBLIC_API_URL} +RUN npm run build +``` + +--- + +## 올바른 이미지 URL 패턴 + +모든 프론트엔드 파일에서 이미지 URL을 생성할 때 다음 패턴을 사용해야 합니다: + +```typescript +// 올바른 패턴 +const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:8000'; + +const getImageUrl = (url: string): string => { + if (!url) return ''; + if (url.startsWith('http://') || url.startsWith('https://')) { + return url; + } + return `${API_BASE_URL}${url}`; +}; +``` + +**잘못된 패턴 (사용 금지)**: +```typescript +// 하드코딩 - 절대 사용 금지! +return `http://localhost:8000${url}`; + +// 포트만 추출 - 사용 금지! +const port = process.env.NEXT_PUBLIC_API_URL?.includes('8001') ? 8001 : 8000; +return `http://localhost:${port}${url}`; +``` + +--- + +## 디버깅 명령어 모음 + +### 프론트엔드 환경변수 확인 + +```bash +# 컨테이너 환경변수 +docker exec autonet-frontend printenv | grep API + +# .env.production 파일 +cat /opt/autonet/production/frontend/.env.production + +# Dockerfile ARG/ENV 확인 +grep -E "(ARG|ENV).*NEXT_PUBLIC" /opt/autonet/production/frontend/Dockerfile +``` + +### 빌드된 JS 파일 검사 + +```bash +# localhost:8000 검색 +docker exec autonet-frontend sh -c "grep -r 'localhost:8000' /app/.next/static/chunks/ | head -5" + +# 올바른 URL 검색 +docker exec autonet-frontend sh -c "grep -r '192.168.0.202:8000' /app/.next/static/chunks/ | head -5" +``` + +### 이미지 직접 접근 테스트 + +```bash +# 백엔드에서 이미지 직접 접근 +curl -I http://192.168.0.202:8000/uploads/cars/1/image_0.jpg + +# API 응답에서 이미지 URL 확인 +curl -s http://192.168.0.202:8000/api/cars/1 | jq '.images' +``` + +### 컨테이너 상태 확인 + +```bash +docker ps -a | grep autonet +docker logs autonet-frontend --tail 20 +docker logs autonet-backend --tail 20 +``` + +--- + +## 예방 조치 + +### 1. 코드 동기화 자동화 + +개발 완료 후 운영 서버 배포 시 항상 최신 코드를 동기화: + +```bash +# 배포 전 동기화 스크립트 +scp -r frontend/src damon@192.168.0.202:/opt/autonet/production/frontend/ +``` + +### 2. 빌드 전 환경변수 확인 + +```bash +# 빌드 전 확인 사항 +echo "=== .env.production ===" +cat /opt/autonet/production/frontend/.env.production + +echo "=== Dockerfile ENV ===" +grep "NEXT_PUBLIC" /opt/autonet/production/frontend/Dockerfile +``` + +### 3. 빌드 후 검증 + +```bash +# 빌드 후 localhost 하드코딩 검사 +docker exec autonet-frontend sh -c "grep -r 'localhost:8000' /app/.next/static/chunks/ && echo 'ERROR: localhost found!' || echo 'OK: No localhost hardcoding'" +``` + +--- + +## 체크리스트 + +배포 시 확인해야 할 항목: + +- [ ] 서버2 소스 코드가 최신인가? +- [ ] `.env.production`에 올바른 URL이 설정되어 있는가? +- [ ] Dockerfile에 `ARG`/`ENV`가 설정되어 있는가? +- [ ] `--no-cache` 옵션으로 빌드했는가? +- [ ] 빌드된 JS에 `localhost:8000`이 없는가? +- [ ] 외부 PC에서 이미지가 정상 표시되는가? + +--- + +## 변경 이력 + +| 날짜 | 문제 | 해결 | +|------|------|------| +| 2024-12-30 | 운영서버 이미지 미표시 (배너) | 소스 코드 동기화 + Docker 재빌드 | +| 2024-12-30 | 차량 상세 이미지 미표시 | `getImageUrl()` 함수 수정 | diff --git a/deploy_ru_server2.sh b/deploy_ru_server2.sh new file mode 100644 index 0000000..95bb771 --- /dev/null +++ b/deploy_ru_server2.sh @@ -0,0 +1,90 @@ +#!/bin/bash +# 서버2에서 러시아어 지원 배포 스크립트 + +echo "=== 1. DB 컬럼 추가 및 번역 업데이트 ===" + +# Docker 컨테이너 내부에서 DB 업데이트 +docker exec -i autonet-backend python3 << 'EOF' +import sqlite3 +conn = sqlite3.connect("/app/autonet.db") +cursor = conn.cursor() + +# 1. 컬럼 추가 (이미 있으면 무시) +try: + cursor.execute("ALTER TABLE hero_banners ADD COLUMN title_ru VARCHAR(100)") + print("Added title_ru column") +except: + print("title_ru column already exists") + +try: + cursor.execute("ALTER TABLE hero_banners ADD COLUMN subtitle_ru VARCHAR(200)") + print("Added subtitle_ru column") +except: + print("subtitle_ru column already exists") + +conn.commit() + +# 2. 러시아어 번역 추가 +translations = { + "모하비 더 마스터": ("Mohave The Master", "Мохаве Мастер", "Мохаве Мастер"), + "신형 K5(DL3)": ("New K5 (DL3)", "Шинэ K5 (DL3)", "Новый K5 (DL3)"), + "더 뉴그랜드스타렉스": ("The New Grand Starex", "Шинэ Гранд Старекс", "Новый Гранд Старекс"), + "더 뉴Santa Fe": ("The New Santa Fe", "Шинэ Санта Фе", "Новый Санта Фе"), + "더 뉴 K7": ("The New K7", "Шинэ K7", "Новый K7"), +} + +cursor.execute("SELECT id, title_ko FROM hero_banners") +for row in cursor.fetchall(): + banner_id, title_ko = row + if title_ko in translations: + en, mn, ru = translations[title_ko] + cursor.execute( + "UPDATE hero_banners SET title_en=?, title_mn=?, title_ru=? WHERE id=?", + (en, mn, ru, banner_id) + ) + print(f"Updated banner {banner_id}: {title_ko} -> RU: {ru}") + +conn.commit() + +# 3. 확인 +cursor.execute("SELECT id, title_ko, title_en, title_mn, title_ru FROM hero_banners") +print("\n=== Current Banner Data ===") +for row in cursor.fetchall(): + print(row) + +print("\nDB Update Done!") +EOF + +echo "" +echo "=== 2. Backend 컨테이너 재빌드 ===" +cd /opt/autonet/production +docker stop autonet-backend || true +docker rm autonet-backend || true +docker build -t autonet-backend-prod ./backend +docker run -d --name autonet-backend \ + -p 8000:8000 \ + -v /opt/autonet/production/backend/uploads:/app/uploads \ + -v /opt/autonet/production/backend/autonet.db:/app/autonet.db \ + autonet-backend-prod + +echo "" +echo "=== 3. Frontend 컨테이너 재빌드 ===" +docker stop autonet-frontend || true +docker rm autonet-frontend || true +docker build -t autonet-frontend-prod \ + --build-arg NEXT_PUBLIC_API_URL=http://192.168.0.202:8000 \ + ./frontend +docker run -d --name autonet-frontend \ + -p 3000:3000 \ + -e NEXT_PUBLIC_API_URL=http://192.168.0.202:8000 \ + autonet-frontend-prod + +echo "" +echo "=== 4. 결과 확인 ===" +sleep 5 +echo "Testing Russian API..." +curl -s 'http://localhost:8000/api/hero-banners/?lang=ru' | head -200 + +echo "" +echo "=== 배포 완료 ===" +docker ps diff --git a/update_db_ru.py b/update_db_ru.py new file mode 100644 index 0000000..94a8447 --- /dev/null +++ b/update_db_ru.py @@ -0,0 +1,48 @@ +import sqlite3 +conn = sqlite3.connect("/app/autonet.db") +cursor = conn.cursor() + +# 1. 컬럼 추가 (이미 있으면 무시) +try: + cursor.execute("ALTER TABLE hero_banners ADD COLUMN title_ru VARCHAR(100)") + print("Added title_ru column") +except: + print("title_ru column already exists") + +try: + cursor.execute("ALTER TABLE hero_banners ADD COLUMN subtitle_ru VARCHAR(200)") + print("Added subtitle_ru column") +except: + print("subtitle_ru column already exists") + +conn.commit() + +# 2. 러시아어 번역 추가 +translations = { + "모하비 더 마스터": ("Mohave The Master", "Мохаве Мастер", "Мохаве Мастер"), + "신형 K5(DL3)": ("New K5 (DL3)", "Шинэ K5 (DL3)", "Новый K5 (DL3)"), + "더 뉴그랜드스타렉스": ("The New Grand Starex", "Шинэ Гранд Старекс", "Новый Гранд Старекс"), + "더 뉴Santa Fe": ("The New Santa Fe", "Шинэ Санта Фе", "Новый Санта Фе"), + "더 뉴 K7": ("The New K7", "Шинэ K7", "Новый K7"), +} + +cursor.execute("SELECT id, title_ko FROM hero_banners") +for row in cursor.fetchall(): + banner_id, title_ko = row + if title_ko in translations: + en, mn, ru = translations[title_ko] + cursor.execute( + "UPDATE hero_banners SET title_en=?, title_mn=?, title_ru=? WHERE id=?", + (en, mn, ru, banner_id) + ) + print(f"Updated banner {banner_id}: {title_ko} -> EN: {en}, MN: {mn}, RU: {ru}") + +conn.commit() + +# 3. 확인 +cursor.execute("SELECT id, title_ko, title_en, title_mn, title_ru FROM hero_banners") +print("\n=== Current Banner Data ===") +for row in cursor.fetchall(): + print(row) + +print("\nDone!")