# WING-OPS 설치 매뉴얼 ## 목차 1. [시스템 요구사항](#1-시스템-요구사항) 2. [프로젝트 구조](#2-프로젝트-구조) 3. [온라인 설치](#3-온라인-설치) 4. [오프라인 설치](#4-오프라인-설치) 5. [DB 초기화 및 마이그레이션](#5-db-초기화-및-마이그레이션) 6. [개발 서버 실행](#6-개발-서버-실행) 7. [운영 서버 배포](#7-운영-서버-배포) 8. [CI/CD 자동 배포](#8-cicd-자동-배포) 9. [접속 정보 요약](#9-접속-정보-요약) 10. [트러블슈팅](#10-트러블슈팅) --- ## 1. 시스템 요구사항 ### 필수 소프트웨어 | 소프트웨어 | 최소 버전 | 권장 버전 | 용도 | |-----------|----------|----------|------| | Node.js | v20 | v20 LTS | Frontend/Backend 실행 | | npm | v10 | v10+ | 패키지 관리 (Node.js 포함) | | PostgreSQL | v15 | v16 | 운영 DB + 인증 DB | | PostGIS | v3.3 | v3.4 | 공간 데이터 처리 | | Git | v2.30 | v2.40+ | 소스 코드 관리 | ### 하드웨어 권장 사양 | 항목 | 개발 환경 | 운영 환경 | |------|----------|----------| | CPU | 2 Core | 4 Core | | RAM | 4 GB | 8 GB | | Disk | 10 GB | 50 GB | | OS | macOS / Linux / Windows | Rocky Linux 9 / CentOS 9 | ### Node.js 설치 (fnm 권장) fnm(Fast Node Manager)으로 Node.js 버전을 관리한다. 프로젝트 루트의 `.node-version` 파일에 `20`이 지정되어 있다. **macOS / Linux:** ```bash # fnm 설치 curl -fsSL https://fnm.vercel.app/install | bash # 셸 재시작 후 fnm install 20 fnm use 20 # 확인 node -v # v20.x.x npm -v # v10.x.x ``` **수동 설치 (fnm 없이):** https://nodejs.org 에서 Node.js 20 LTS를 다운로드하여 설치한다. --- ## 2. 프로젝트 구조 ``` wing/ ├── frontend/ React 19 + Vite 7 + TypeScript 5.9 + Tailwind CSS 3 │ ├── src/ │ │ ├── App.tsx 메인 (탭 라우팅, 감사 로그 자동 기록) │ │ ├── common/ 공통 모듈 (@common/ alias) │ │ │ ├── components/ auth/, layer/, layout/, map/, ui/ │ │ │ ├── hooks/ useLayers, useSubMenu, useAuth │ │ │ ├── services/ api.ts (Axios), authApi.ts, layerService.ts │ │ │ ├── store/ authStore, menuStore (Zustand) │ │ │ ├── types/ backtrack, boomLine, hns, navigation │ │ │ └── utils/ coordinates, geo, sanitize │ │ └── components/ 탭 단위 패키지 (11개, MPA 구조) │ │ ├── prediction/ 확산 예측 │ │ ├── hns/ HNS 분석 │ │ ├── rescue/ 구조 시나리오 │ │ ├── aerial/ 항공 방제 │ │ ├── weather/ 해양 기상 │ │ ├── incidents/ 사건/사고 관리 │ │ ├── board/ 게시판 │ │ ├── reports/ 보고서 │ │ ├── assets/ 자산 관리 │ │ ├── scat/ Pre-SCAT 조사 │ │ └── admin/ 관리자 │ ├── package.json │ └── vite.config.ts ├── backend/ Express 4 + TypeScript + PostgreSQL │ ├── src/ │ │ ├── server.ts 진입점 (보안 미들웨어 + 라우터 등록) │ │ ├── auth/ 인증 (JWT, Google OAuth) │ │ ├── users/ 사용자 관리 │ │ ├── roles/ 역할/권한 (RBAC 2차원 권한) │ │ ├── db/ DB Pool (wingDb, authDb), seed │ │ ├── middleware/ 보안 (입력 살균, rate-limit) │ │ └── {도메인}/ 도메인별 모듈 (Router + Service) │ ├── package.json │ └── tsconfig.json ├── database/ SQL 스크립트 │ ├── init.sql wing DB 초기 스키마 (PostGIS) │ ├── auth_init.sql wing_auth DB 초기 스키마 │ └── migration/ 마이그레이션 (001 ~ 016) ├── docs/ 개발 문서 ├── .githooks/ Git Hooks (pre-commit, commit-msg) ├── .gitea/workflows/ CI/CD (Gitea Actions) ├── .node-version Node.js 20 └── .npmrc npm 레지스트리 (Nexus 프록시) ``` --- ## 3. 온라인 설치 인터넷이 가능한 환경에서의 설치 절차. ### 3-1. 소스 코드 클론 ```bash git clone https://gitea.gc-si.dev/gc/wing-ops.git wing cd wing ``` ### 3-2. Git Hooks 설정 ```bash # pre-commit (TypeScript + ESLint 검증), commit-msg (Conventional Commits 검증) git config core.hooksPath .githooks chmod +x .githooks/pre-commit .githooks/commit-msg ``` ### 3-3. 의존성 설치 npm 레지스트리는 프로젝트 루트의 `.npmrc`에 Nexus 프록시로 설정되어 있다. ```bash # Frontend cd frontend npm install # Backend cd ../backend npm install ``` ### 3-4. 환경변수 설정 #### Backend (`backend/.env`) ```bash # =========================================== # 서버 설정 # =========================================== PORT=3001 NODE_ENV=development # =========================================== # wing DB (운영 데이터 - PostgreSQL + PostGIS) # =========================================== WING_DB_HOST=211.208.115.83 WING_DB_PORT=5432 WING_DB_USER=wing WING_DB_PASS=<비밀번호> WING_DB_NAME=wing # =========================================== # wing_auth DB (인증/권한 - PostgreSQL) # =========================================== AUTH_DB_HOST=211.208.115.83 AUTH_DB_PORT=5432 AUTH_DB_USER=wing_auth AUTH_DB_PASS=<비밀번호> AUTH_DB_NAME=wing_auth # =========================================== # JWT 인증 # =========================================== JWT_SECRET=<랜덤 시크릿 문자열> JWT_EXPIRES_IN=24h # =========================================== # CORS (프론트엔드 출처) # =========================================== FRONTEND_URL=http://localhost:5173 # =========================================== # Google OAuth (선택) # =========================================== GOOGLE_CLIENT_ID= ``` #### Frontend (`frontend/.env`) ```bash # 백엔드 API URL VITE_API_URL=http://localhost:3001/api # Google OAuth (선택) VITE_GOOGLE_CLIENT_ID= # 공공데이터 API 키 (해양기상, 선택) VITE_DATA_GO_KR_API_KEY= VITE_WEATHER_API_KEY=<기상 API 키> ``` > `.env` 파일은 `.gitignore`에 포함된다. 실제 값은 팀 내부에서 공유한다. ### 3-5. 실행 ```bash # 터미널 1: 백엔드 cd backend npm run dev # 출력: 서버가 포트 3001에서 실행 중입니다. # 터미널 2: 프론트엔드 cd frontend npm run dev # 출력: VITE vX.X.X ready in XXXms # -> Local: http://localhost:5173/ ``` 브라우저에서 http://localhost:5173 접속하여 확인한다. --- ## 4. 오프라인 설치 인터넷이 불가능한 폐쇄망 환경에서의 설치 절차. ### 4-1. 온라인 환경에서 패키지 준비 인터넷이 되는 PC에서 다음을 실행한다: ```bash # 1. 프로젝트 클론 및 의존성 설치 git clone https://gitea.gc-si.dev/gc/wing-ops.git wing cd wing/frontend && npm install cd ../backend && npm install # 2. 전체 프로젝트 압축 (node_modules 포함) cd ../.. tar -czf wing_full.tar.gz wing/ ``` ### 4-2. Node.js 오프라인 설치 파일 준비 대상 OS에 맞는 설치 파일을 다운로드한다: | OS | 파일 | 다운로드 | |----|------|---------| | Linux (x64) | `node-v20.x.x-linux-x64.tar.xz` | https://nodejs.org/dist/v20.x.x/ | | macOS (arm64) | `node-v20.x.x-darwin-arm64.tar.gz` | https://nodejs.org/dist/v20.x.x/ | | macOS (x64) | `node-v20.x.x-darwin-x64.tar.gz` | https://nodejs.org/dist/v20.x.x/ | | Windows | `node-v20.x.x-x64.msi` | https://nodejs.org/dist/v20.x.x/ | ### 4-3. 대상 서버에 설치 ```bash # 1. Node.js 설치 (Linux 예시) tar -xJf node-v20.x.x-linux-x64.tar.xz export PATH=$PWD/node-v20.x.x-linux-x64/bin:$PATH # .bashrc 또는 .profile에 PATH 영구 등록 # 2. 프로젝트 압축 해제 tar -xzf wing_full.tar.gz cd wing # 3. 환경변수 설정 (3-4 참조) vi backend/.env vi frontend/.env # 4. 실행 (node_modules가 이미 포함되어 있으므로 npm install 불필요) cd backend && npm run dev # 터미널 1 cd frontend && npm run dev # 터미널 2 ``` ### 4-4. 오프라인 환경 주의사항 - `npm install` 불가하므로 패키지 추가 시 온라인 환경에서 설치 후 재배포해야 한다. - DB는 대상 환경에서 접근 가능한 PostgreSQL을 사용해야 한다. - 공공데이터 API (기상, 해양) 키가 필요한 기능은 외부 네트워크 접근이 필요하다. --- ## 5. DB 초기화 및 마이그레이션 ### 5-1. DB 구성 프로젝트는 동일 PostgreSQL 서버에 2개의 데이터베이스를 사용한다: | DB | 용도 | 확장 | |----|------|------| | `wing` | 운영 데이터 (사고, 예측, 자산 등) | PostGIS | | `wing_auth` | 인증/권한 (사용자, 역할, 메뉴) | uuid-ossp, pgcrypto | ### 5-2. 신규 DB 초기화 PostgreSQL에 최초 설치 시 아래 순서로 실행한다. ```bash # 1. wing DB 초기화 (PostgreSQL superuser로 실행) psql -U postgres -f database/init.sql # 2. wing_auth DB 초기화 psql -U postgres -f database/auth_init.sql ``` `init.sql`에 포함된 내용: - 사용자(wing) 및 데이터베이스(wing) 생성 - PostGIS 확장 설치 - 공통코드, 시뮬레이션, 사고, 예측 등 핵심 테이블 생성 `auth_init.sql`에 포함된 내용: - 사용자(wing_auth) 및 데이터베이스(wing_auth) 생성 - uuid-ossp, pgcrypto 확장 설치 - 조직, 역할, 사용자, 권한, 메뉴, 설정, 감사로그 테이블 생성 ### 5-3. 마이그레이션 적용 초기 스키마 이후 추가된 변경 사항은 `database/migration/` 디렉토리에 순번대로 관리된다. ``` database/migration/ ├── 001_layer_table.sql 레이어 테이블 ├── 002_hns_substance.sql HNS 물질 데이터 ├── 003_perm_tree.sql 권한 트리 ├── 004_oper_cd.sql 오퍼레이션 코드 ├── 005_db_consolidation.sql DB 통합 ├── 006_board.sql 게시판 ├── 007_reports.sql 보고서 ├── 008_assets.sql 자산 ├── 008_assets_seed.sql 자산 시드 데이터 ├── 009_incidents.sql 사건/사고 ├── 010_postgis_geom.sql PostGIS 지오메트리 ├── 011_scat.sql SCAT 조사 ├── 012_board_ext.sql 게시판 확장 ├── 013_hns_analysis.sql HNS 분석 ├── 014_prediction.sql 확산 예측 ├── 015_aerial.sql 항공 방제 └── 016_rescue.sql 구조 시나리오 ``` **마이그레이션 실행 (수동):** ```bash # wing DB에 순번대로 적용 psql -h <호스트> -p 5432 -U wing -d wing -f database/migration/001_layer_table.sql psql -h <호스트> -p 5432 -U wing -d wing -f database/migration/002_hns_substance.sql # ... 순번대로 계속 ``` > 이미 적용된 마이그레이션을 다시 실행해도 대부분 `IF NOT EXISTS` 조건이 포함되어 있어 안전하다. ### 5-4. 시드 데이터 초기 데이터(관리자 계정, 공통코드 등)를 입력한다: ```bash cd backend npm run db:seed ``` --- ## 6. 개발 서버 실행 ### 6-1. 명령어 요약 **Frontend:** | 명령어 | 설명 | |--------|------| | `npm run dev` | Vite 개발 서버 (localhost:5173, HMR 지원) | | `npm run build` | 프로덕션 빌드 (`tsc -b && vite build` -> `dist/`) | | `npm run lint` | ESLint 검증 | | `npm run preview` | 빌드 결과 미리보기 | **Backend:** | 명령어 | 설명 | |--------|------| | `npm run dev` | tsx watch 개발 서버 (localhost:3001, 파일 변경 시 자동 재시작) | | `npm run build` | TypeScript 컴파일 (`tsc` -> `dist/`) | | `npm start` | 프로덕션 실행 (`node dist/server.js`) | | `npm run db:seed` | DB 시드 데이터 입력 | ### 6-2. 개발 서버 시작 ```bash # 터미널 1: 백엔드 (먼저 실행) cd backend npm run dev # 서버가 포트 3001에서 실행 중입니다. # wing DB 연결 성공 (211.208.115.83:5432/wing) # 터미널 2: 프론트엔드 cd frontend npm run dev # VITE v7.x.x ready in XXXms # -> Local: http://localhost:5173/ ``` ### 6-3. 검증 명령어 ```bash # TypeScript 타입 체크 cd frontend && npx tsc --noEmit cd backend && npx tsc --noEmit # ESLint cd frontend && npx eslint src/ # Prettier cd frontend && npx prettier --check src/ cd frontend && npx prettier --write src/ # 자동 수정 ``` --- ## 7. 운영 서버 배포 ### 7-1. 서버 환경 준비 운영 서버(Rocky Linux 9 기준)에 다음을 설치한다: ```bash # Node.js 20 설치 (fnm 또는 직접 설치) curl -fsSL https://fnm.vercel.app/install | bash source ~/.bashrc fnm install 20 fnm use 20 # PostgreSQL 16 + PostGIS 설치 (dnf 또는 직접 설치) # 이미 설치된 경우 생략 ``` ### 7-2. 수동 배포 절차 #### Frontend ```bash cd frontend # 의존성 설치 npm ci # 환경변수 설정 (빌드 시 Vite가 주입) export VITE_API_URL=/api export VITE_GOOGLE_CLIENT_ID=<클라이언트ID> export VITE_DATA_GO_KR_API_KEY=<키> export VITE_WEATHER_API_KEY=<키> # 프로덕션 빌드 npx vite build # 빌드 결과를 웹 서버 디렉토리로 복사 cp -r dist/* /deploy/wing-demo/ ``` #### Backend ```bash cd backend # 의존성 설치 npm ci # TypeScript 컴파일 npx tsc # 프로덕션 의존성만 유지 (devDependencies 제거) npm prune --omit=dev # 배포 디렉토리로 복사 mkdir -p /deploy/wing-demo-backend/dist cp -r dist/* /deploy/wing-demo-backend/dist/ cp -r node_modules /deploy/wing-demo-backend/ cp package.json /deploy/wing-demo-backend/ ``` ### 7-3. systemd 서비스 등록 Backend를 systemd 서비스로 등록하여 자동 시작/재시작을 설정한다. **서비스 파일 생성** (`/etc/systemd/system/wing-demo-api.service`): ```ini [Unit] Description=WING-OPS Backend API After=network.target postgresql.service [Service] Type=simple User=deploy WorkingDirectory=/deploy/wing-demo-backend ExecStart=/home/deploy/.local/share/fnm/aliases/default/bin/node dist/server.js Restart=on-failure RestartSec=5 # 환경변수 Environment=NODE_ENV=production Environment=PORT=3001 Environment=WING_DB_HOST=211.208.115.83 Environment=WING_DB_PORT=5432 Environment=WING_DB_USER=wing Environment=WING_DB_PASS=<비밀번호> Environment=WING_DB_NAME=wing Environment=AUTH_DB_HOST=211.208.115.83 Environment=AUTH_DB_PORT=5432 Environment=AUTH_DB_USER=wing_auth Environment=AUTH_DB_PASS=<비밀번호> Environment=AUTH_DB_NAME=wing_auth Environment=JWT_SECRET=<시크릿> Environment=JWT_EXPIRES_IN=24h Environment=FRONTEND_URL=https://wing-demo.gc-si.dev Environment=GOOGLE_CLIENT_ID=<클라이언트ID> [Install] WantedBy=multi-user.target ``` **서비스 등록 및 시작:** ```bash sudo systemctl daemon-reload sudo systemctl enable wing-demo-api sudo systemctl start wing-demo-api # 상태 확인 sudo systemctl status wing-demo-api # 로그 확인 sudo journalctl -u wing-demo-api -f ``` ### 7-4. 리버스 프록시 (Nginx) Frontend 정적 파일과 Backend API를 하나의 도메인으로 서비스한다. ```nginx server { listen 443 ssl; server_name wing-demo.gc-si.dev; ssl_certificate /etc/ssl/certs/wing-demo.crt; ssl_certificate_key /etc/ssl/private/wing-demo.key; # Frontend 정적 파일 root /deploy/wing-demo; index index.html; location / { try_files $uri $uri/ /index.html; } # Backend API 프록시 location /api/ { proxy_pass http://127.0.0.1:3001/api/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } # 헬스 체크 location /health { proxy_pass http://127.0.0.1:3001/health; } } # HTTP -> HTTPS 리다이렉트 server { listen 80; server_name wing-demo.gc-si.dev; return 301 https://$host$request_uri; } ``` --- ## 8. CI/CD 자동 배포 ### 8-1. Gitea Actions 파이프라인 main 브랜치에 push(머지)되면 `.gitea/workflows/deploy.yml`이 자동 실행된다. ``` main 브랜치 push | [Checkout] 소스 코드 체크아웃 | [Setup Node.js] Node.js 24 설정 | [Configure npm] Nexus 프록시 레지스트리 설정 | [Frontend Build] npm ci -> vite build -> /deploy/wing-demo/ | [Backend Build] npm ci -> tsc -> npm prune --omit=dev | -> /deploy/wing-demo-backend/ | [Deploy Trigger] .deploy-trigger 파일 생성 -> cron/watchdog이 감지 -> 서비스 재시작 ``` ### 8-2. Gitea Secrets 설정 CI/CD에서 사용하는 시크릿을 Gitea에 등록해야 한다: **등록 경로:** Settings -> Actions -> Secrets -> Add Secret | Secret 이름 | 용도 | |-------------|------| | `NEXUS_NPM_AUTH` | npm Nexus 프록시 인증 토큰 | | `GOOGLE_CLIENT_ID` | Google OAuth 클라이언트 ID | | `DATA_GO_KR_API_KEY` | 공공데이터 API 키 | | `WEATHER_API_KEY` | 기상 API 키 | ### 8-3. 배포 확인 ```bash # 프론트엔드 응답 확인 curl -s -o /dev/null -w '%{http_code}' https://wing-demo.gc-si.dev/ # 기대: 200 # 백엔드 헬스 체크 curl -s https://wing-demo.gc-si.dev/api/health # 기대: {"status":"ok"} # 백엔드 API 정보 curl -s https://wing-demo.gc-si.dev/api/ # 기대: {"name":"WING Backend API","version":"1.0.0","status":"running"} ``` --- ## 9. 접속 정보 요약 ### 개발 환경 | 서비스 | URL | 포트 | |--------|-----|------| | Frontend (Vite dev) | http://localhost:5173 | 5173 | | Backend API | http://localhost:3001 | 3001 | | Backend 헬스 체크 | http://localhost:3001/health | 3001 | | PostgreSQL (wing) | 211.208.115.83:5432/wing | 5432 | | PostgreSQL (wing_auth) | 211.208.115.83:5432/wing_auth | 5432 | ### 운영 환경 | 서비스 | URL | |--------|-----| | Frontend | https://wing-demo.gc-si.dev | | Backend API | https://wing-demo.gc-si.dev/api/ | | 헬스 체크 | https://wing-demo.gc-si.dev/api/health | | Gitea | https://gitea.gc-si.dev/gc/wing-ops | ### API 엔드포인트 | 경로 | 용도 | |------|------| | `GET /` | API 정보 | | `GET /health` | 헬스 체크 | | `POST /api/auth/login` | 로그인 | | `POST /api/auth/logout` | 로그아웃 | | `GET /api/auth/me` | 현재 사용자 | | `GET /api/users` | 사용자 목록 | | `GET /api/roles` | 역할 목록 | | `GET /api/menus` | 메뉴 목록 | | `GET /api/settings` | 시스템 설정 | | `GET /api/audit` | 감사 로그 | | `GET /api/board` | 게시판 | | `GET /api/layers` | 레이어 | | `GET /api/simulation` | 시뮬레이션 | | `GET /api/hns` | HNS 물질 | | `GET /api/reports` | 보고서 | | `GET /api/assets` | 자산 | | `GET /api/incidents` | 사건/사고 | | `GET /api/scat` | SCAT 조사 | | `GET /api/prediction` | 확산 예측 | | `GET /api/aerial` | 항공 방제 | | `GET /api/rescue` | 구조 시나리오 | --- ## 10. 트러블슈팅 ### 의존성 설치 | 증상 | 원인 | 해결 | |------|------|------| | `npm install` 실패 (ENETUNREACH) | Nexus 프록시 접근 불가 | `.npmrc`의 registry URL 확인, VPN/네트워크 상태 확인 | | `npm install` 실패 (EAUTH) | Nexus 인증 토큰 만료 | `.npmrc`의 `_auth` 값 갱신 | | `npm install` 실패 (peer dependency) | 패키지 버전 충돌 | `npm install --legacy-peer-deps` 시도 | | `MODULE_NOT_FOUND` | node_modules 누락/손상 | `rm -rf node_modules package-lock.json && npm install` | ### 서버 실행 | 증상 | 원인 | 해결 | |------|------|------| | 포트 5173/3001 충돌 | 이미 다른 프로세스가 사용 중 | `lsof -i :5173` 또는 `lsof -i :3001`로 PID 확인 후 `kill -9 ` | | 백엔드 시작 시 DB 연결 실패 | DB 접속 정보 오류 또는 네트워크 | `backend/.env`의 DB 호스트/포트/사용자/비밀번호 확인, `nc -zv <호스트> 5432`로 네트워크 확인 | | 프론트엔드에서 API 호출 CORS 에러 | 백엔드 CORS 설정 불일치 | `backend/.env`의 `FRONTEND_URL`이 프론트엔드 URL과 일치하는지 확인 | | `env: tsx: No such file or directory` | tsx 미설치 | `cd backend && npm install` 재실행 | ### DB | 증상 | 원인 | 해결 | |------|------|------| | `relation "XXX" does not exist` | 테이블 미생성 | `database/init.sql` 또는 해당 마이그레이션 SQL 실행 | | PostGIS 함수 에러 | PostGIS 확장 미설치 | `psql -U postgres -d wing -c "CREATE EXTENSION IF NOT EXISTS postgis"` | | 인코딩 문제 (한글 깨짐) | DB 인코딩 설정 | DB 생성 시 `ENCODING='UTF8' LC_COLLATE='ko_KR.UTF-8'` 지정 | | seed 실패 | 이미 데이터 존재 또는 FK 제약 | 에러 메시지 확인 후 해당 테이블 데이터 정리 | ### Git Hooks | 증상 | 원인 | 해결 | |------|------|------| | pre-commit이 실행되지 않음 | hooksPath 미설정 | `git config core.hooksPath .githooks` | | pre-commit permission denied | 실행 권한 없음 | `chmod +x .githooks/pre-commit .githooks/commit-msg` | | TypeScript 에러로 커밋 차단 | 타입 에러 존재 | `npx tsc --noEmit`으로 에러 확인 후 수정 | | commit-msg 형식 오류 | Conventional Commits 미준수 | `type(scope): subject` 형식 준수 (type: feat, fix, docs 등) | ### 빌드 | 증상 | 원인 | 해결 | |------|------|------| | `vite build` 메모리 부족 | Node.js 힙 메모리 부족 | `export NODE_OPTIONS=--max-old-space-size=4096` 후 재시도 | | `tsc` 타입 에러 | TypeScript strict 모드 위반 | `npx tsc --noEmit`으로 에러 목록 확인 후 수정 | | 빌드 후 라우팅 404 | SPA 서버 설정 누락 | Nginx `try_files $uri $uri/ /index.html` 설정 확인 |