# Admin Dashboard 표준 배포 방식 전환 및 구조 리팩토링 **날짜**: 2025-11-17 **작성자**: admin **관련 파일**: - `admin-dashboard/docker-compose.yml` - `admin-dashboard/backend/main.py` - `admin-dashboard/frontend/` - `robeing-gateway/app/main.py` - `nginx-infra/server-nginx-default` --- ## 문제 상황 ### 1. 비표준 배포 방식 - FastAPI 백엔드가 정적 HTML 파일을 FileResponse로 서빙 - 표준 방식(nginx 직접 서빙)과 불일치 - frontend-customer는 이미 표준 방식으로 전환 완료 ### 2. 폴더 구조 혼란 - `frontend-base` 폴더명이 역할을 명확히 표현하지 않음 - `admin-ui` 폴더명이 `frontend`보다 덜 직관적 ### 3. Gateway JWT 검증 문제 - `/admin` 라우팅에서 JWT 검증을 필수로 하면, JWT가 없는 사용자는 401로 HTML을 받지 못해 로그인 페이지를 볼 수 없음 ### 4. 토큰 불일치 - frontend-base가 `adminToken`을 사용하나, Gateway는 표준 JWT(`auth_token`)를 기대 ## 해결 방안 ### 1. 구조 변경 **폴더 리네임**: - `frontend-base` → `admin-dashboard` - `admin-ui` → `frontend` **최종 구조**: ``` admin-dashboard/ ├── backend/ # FastAPI 서버 (API만 처리) ├── frontend/ # React + Tailwind + shadcn + Vite └── docker-compose.yml ``` ### 2. 표준 배포 방식 채택 **nginx 설정** (`nginx-infra/server-nginx-default`): ```nginx location /admin { alias /home/admin/admin-dashboard/frontend/; try_files $uri $uri/ /admin/index.html; index index.html; } ``` **권한 설정**: ```bash sudo chmod o+x /home sudo chmod o+x /home/admin ``` **빌드 및 배포**: - Vite로 `npm run build` 실행 → `dist/` 폴더 생성 - nginx가 `/home/admin/admin-dashboard/frontend/dist/` 직접 서빙 - FastAPI는 API만 처리 (`/admin/api/*`) ### 3. Gateway JWT 검증 수정 **robeing-gateway/app/main.py** - JWT 검증을 선택적으로 처리: ```python @app.api_route("/admin/{path:path}", methods=["GET", "POST", "PUT", "DELETE"]) async def admin_proxy( path: str, request: Request, user_uuid: Optional[str] = Depends(get_verified_user_optional) # 선택적 ): # JWT가 없어도 HTML 반환, frontend JavaScript가 로그인 페이지 표시 ``` ### 4. 토큰 통일 **frontend/index.html** - `adminToken` → `auth_token`으로 변경: ```javascript // Before: localStorage.setItem('adminToken', token) // After: localStorage.setItem('auth_token', token) ``` ## 구현 완료 **커밋**: 920672a **일시**: 2025-11-17 **브랜치**: main **변경 사항**: - `admin-dashboard/docker-compose.yml`: 컨테이너명, 볼륨 경로 수정 - `admin-dashboard/backend/main.py`: frontend 경로 수정 - `admin-dashboard/README.md`: 구조 설명 업데이트 - Git 원격 저장소: `admin-dashboard.git`로 변경 - `admin-dashboard/.gitea/workflows/deploy.yml`: 배포 스크립트 경로 수정 및 포트 충돌 방지 - `nginx-infra/server-nginx-default`: `/admin` 404 해결 - `root` → `alias` 변경 및 `try_files` 경로 수정 ## 교훈 ### 표준 방식의 중요성 - nginx가 정적 파일을 직접 서빙하는 것이 성능과 보안 측면에서 최적 - FastAPI는 API 처리에 집중, 정적 파일 서빙은 웹서버가 담당 - 참고: `250717_happybell80_auth서버구축및정적빌드배포전환.md` ### 폴더 구조 명확성 - 프로젝트명은 역할을 명확히 표현해야 함 (`admin-dashboard`) - `frontend`가 `admin-ui`보다 직관적이고 표준적 ### JWT 검증 전략 - HTML은 공개되어도 되고, 실제 데이터 API는 별도로 JWT 검증 - Gateway는 HTML 요청은 통과시키고, API 요청만 검증하는 선택적 검증 필요 ### 토큰 통일 - 프로젝트 전체에서 표준 JWT 키(`auth_token`) 사용으로 일관성 확보 - Gateway와 frontend 간 토큰 키 불일치 방지 ### CI/CD 배포 스크립트 수정 **문제**: 배포 스크립트가 이전 폴더명(`frontend-base`)을 참조하여 배포 실패 **해결**: 1. **배포 경로 수정** (`admin-dashboard/.gitea/workflows/deploy.yml`): - `/home/admin/frontend-base` → `/home/admin/admin-dashboard` - 백업 경로: `frontend-base.backup.*` → `admin-dashboard.backup.*` 2. **포트 충돌 방지**: - 기존 `frontend-base` 컨테이너 강제 제거 - 포트 8000을 사용하는 모든 컨테이너 중지 및 제거 ```bash # 강제로 컨테이너 제거 (포트 충돌 방지) sudo docker ps -a --filter "name=admin-dashboard" --format "{{.ID}}" | xargs -r sudo docker rm -f || true # frontend-base 컨테이너도 정리 (이전 이름) sudo docker ps -a --filter "name=frontend-base" --format "{{.ID}}" | xargs -r sudo docker rm -f || true # 포트 8000을 사용하는 컨테이너 확인 및 제거 sudo docker ps --filter "publish=8000" --format "{{.ID}}" | xargs -r sudo docker stop || true sudo docker ps --filter "publish=8000" --format "{{.ID}}" | xargs -r sudo docker rm -f || true ``` **교훈**: - 폴더명 변경 시 CI/CD 스크립트도 함께 업데이트 필요 - 배포 전 기존 컨테이너 완전 정리로 포트 충돌 방지 - 이전 이름의 컨테이너도 정리하여 혼란 방지 ### nginx 설정 적용 및 404 해결 **문제**: `/admin` 경로에서 404 에러 발생 **원인**: - nginx 설정 파일(`server-nginx-default`)은 수정했으나 실제 적용된 설정(`/etc/nginx/sites-enabled/default`)은 이전 설정 유지 - `root` 방식과 `alias` 방식의 차이로 인한 경로 불일치 **해결**: 1. **설정 파일 동기화**: ```bash sudo cp /home/admin/nginx-infra/server-nginx-default /etc/nginx/sites-enabled/default sudo nginx -t sudo systemctl reload nginx ``` 2. **최종 nginx 설정**: ```nginx location /admin { alias /home/admin/admin-dashboard/frontend/; try_files $uri $uri/ /admin/index.html; index index.html; } ``` **검증**: ```bash curl -I http://localhost/admin/ # HTTP/1.1 200 OK ``` **교훈**: - 설정 파일 수정 후 반드시 실제 nginx 설정에 적용 필요 - `root` vs `alias` 차이 이해: `root`는 경로를 합치고, `alias`는 경로를 대체 - `try_files`의 fallback 경로도 `alias` 사용 시 `/admin/index.html`로 명시 필요 ### 터미널 bash 문제 해결 **문제**: Cursor IDE 터미널에서 명령어 실행 시 오류 발생 **증상**: - `cd` 명령어 실행 실패 - Git 명령어 실행 시 오류 **해결**: - **Cursor IDE 재시작**: 터미널 설정이 다시 로드되어 문제 해결됨 - 또는 `Ctrl+Shift+P` → "Developer: Reload Window" **교훈**: - IDE 터미널 문제 발생 시 가장 먼저 IDE 재시작 시도 - 재시작으로 해결되지 않으면 shell 경로 확인 (`/bin/bash`) ### Admin 로그인 API 라우팅 문제 해결 **문제**: `/admin/api/login` POST 요청 시 404 오류 발생 **원인**: 1. **프론트엔드 JavaScript 실행 문제**: 폼 제출 시 POST 요청이 발생하지 않고 GET 요청으로 처리됨 2. **Gateway 라우팅 문제**: Gateway 컨테이너에서 `localhost:8000`으로 접근 시 자기 자신을 가리킴 3. **Docker 네트워크 문제**: 컨테이너 간 통신은 컨테이너 이름 또는 Docker 네트워크 IP 사용 필요 **해결**: 1. **프론트엔드 JavaScript 수정** (`admin-dashboard/frontend/index.html`): - form 태그에 `method="POST"` 및 `action="javascript:void(0);"` 추가 - JavaScript 이벤트 리스너를 IIFE로 감싸서 확실히 실행되도록 수정 - 에러 처리 개선 (showError 함수 의존성 제거) 2. **Gateway 라우팅 수정** (`robeing-gateway/app/routers/admin.py`): ```python # Before: ADMIN_BACKEND_URL = "http://localhost:8000" # After: ADMIN_BACKEND_URL = "http://admin-dashboard-backend:8000" ``` - Docker 컨테이너 내부에서는 `localhost` 대신 컨테이너 이름 사용 - 같은 Docker 네트워크(`appnet`)에 있는 컨테이너는 이름으로 접근 가능 3. **Gateway 라우터 등록 확인** (`robeing-gateway/app/main.py`): ```python app.include_router(admin.router, prefix="") ``` - admin router에 prefix가 없으므로 main.py에서도 prefix="" 명시 **검증**: ```bash curl -X POST http://localhost:8100/admin/api/login \ -H "Content-Type: application/json" \ -d '{"password":"19800508"}' # {"access_token":"...", "token_type":"bearer"} ``` **교훈**: - Docker 컨테이너 간 통신은 `localhost`가 아닌 컨테이너 이름 또는 네트워크 IP 사용 - 같은 Docker 네트워크에 있는 컨테이너는 이름으로 자동 DNS 해석됨 - 프론트엔드 JavaScript는 브라우저 환경에서 실행되므로 상대 경로(`/admin/api/login`) 사용 가능 - Gateway는 nginx를 통해 프록시되므로 nginx 설정(`/admin/api/` → Gateway) 확인 필요