나스 서비스 준비
This commit is contained in:
@@ -6,7 +6,7 @@ JWT_SECRET=여기에-32자-이상-랜덤-시크릿-입력
|
||||
JWT_EXPIRES=7d
|
||||
|
||||
# --- 웹 (docker-compose.prod.yml) ---
|
||||
WEB_PORT=8080
|
||||
WEB_PORT=44082
|
||||
|
||||
# --- 관리자 (최초 기동 시 자동 생성) ---
|
||||
ADMIN_EMAIL=admin@example.com
|
||||
@@ -14,7 +14,7 @@ ADMIN_PASSWORD=change-me-8chars
|
||||
ADMIN_USERNAME=admin
|
||||
|
||||
# --- CORS (옆 프로젝트·로컬 개발 포트 포함, 쉼표 구분) ---
|
||||
CORS_ORIGINS=https://uniare.sori.studio,http://localhost:8080,http://127.0.0.1:8080,http://localhost:5500,http://127.0.0.1:5500,http://localhost:5173
|
||||
CORS_ORIGINS=https://uniare.sori.studio,http://localhost:44082,http://127.0.0.1:44082,http://localhost:8080,http://127.0.0.1:8080,http://localhost:5500,http://127.0.0.1:5500,http://localhost:5173
|
||||
|
||||
# --- 로컬 api:dev 전용 (npm run api:dev) ---
|
||||
DATAS_PATH=./datas
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# UADECK v4 프로덕션 스택 (NAS Docker)
|
||||
# 사용: docker compose -f docker-compose.prod.yml up -d --build
|
||||
#
|
||||
# 정적 파일(HTML, images, datas)은 호스트 볼륨 마운트 — 카드/이미지 추가 시 재빌드 불필요
|
||||
# 정적 파일(HTML, images, datas)은 필요한 경로만 read-only 마운트 — 카드/이미지 추가 시 재빌드 불필요
|
||||
|
||||
services:
|
||||
uadeck-api:
|
||||
@@ -35,14 +35,30 @@ services:
|
||||
start_period: 15s
|
||||
|
||||
uadeck-web:
|
||||
image: nginx:alpine
|
||||
build:
|
||||
context: .
|
||||
dockerfile: nginx/Dockerfile
|
||||
image: uadeck-web:latest
|
||||
container_name: uadeck-web
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "${WEB_PORT:-8080}:80"
|
||||
- "${WEB_PORT:-44082}:80"
|
||||
volumes:
|
||||
- ./:/usr/share/nginx/html:ro
|
||||
- ./nginx/default.conf:/etc/nginx/conf.d/default.conf:ro
|
||||
- ./index.html:/usr/share/nginx/html/index.html:ro
|
||||
- ./cardList.html:/usr/share/nginx/html/cardList.html:ro
|
||||
- ./result.html:/usr/share/nginx/html/result.html:ro
|
||||
- ./login.html:/usr/share/nginx/html/login.html:ro
|
||||
- ./signup.html:/usr/share/nginx/html/signup.html:ro
|
||||
- ./deckHistory.html:/usr/share/nginx/html/deckHistory.html:ro
|
||||
- ./preset.html:/usr/share/nginx/html/preset.html:ro
|
||||
- ./profile.html:/usr/share/nginx/html/profile.html:ro
|
||||
- ./admin.html:/usr/share/nginx/html/admin.html:ro
|
||||
- ./favicon.ico:/usr/share/nginx/html/favicon.ico:ro
|
||||
- ./script:/usr/share/nginx/html/script:ro
|
||||
- ./style:/usr/share/nginx/html/style:ro
|
||||
- ./i18n:/usr/share/nginx/html/i18n:ro
|
||||
- ./datas:/usr/share/nginx/html/datas:ro
|
||||
- ./images:/usr/share/nginx/html/images:ro
|
||||
depends_on:
|
||||
uadeck-api:
|
||||
condition: service_healthy
|
||||
|
||||
@@ -17,7 +17,7 @@ cp .env.example .env
|
||||
| `JWT_SECRET` | 32자 이상 랜덤 문자열 (필수) |
|
||||
| `ADMIN_EMAIL` | 최초 관리자 이메일 |
|
||||
| `ADMIN_PASSWORD` | 최초 관리자 비밀번호 (8자+) |
|
||||
| `WEB_PORT` | 웹 노출 포트 (기본 `8080`) |
|
||||
| `WEB_PORT` | 웹 노출 포트 (기본 `44082`, 기존 정적 사이트 포트 유지) |
|
||||
| `CORS_ORIGINS` | 옆 프로젝트 도메인 포함, 쉼표 구분 |
|
||||
|
||||
예시:
|
||||
@@ -26,13 +26,13 @@ cp .env.example .env
|
||||
JWT_SECRET=your-random-secret-at-least-32-chars
|
||||
ADMIN_EMAIL=admin@uniare.sori.studio
|
||||
ADMIN_PASSWORD=your-secure-password
|
||||
WEB_PORT=8080
|
||||
CORS_ORIGINS=https://uniare.sori.studio,https://your-other-app.sori.studio,http://localhost:8080,http://localhost:5500,http://127.0.0.1:5500
|
||||
WEB_PORT=44082
|
||||
CORS_ORIGINS=https://uniare.sori.studio,https://your-other-app.sori.studio,http://localhost:44082,http://127.0.0.1:44082,http://localhost:5500,http://127.0.0.1:5500
|
||||
```
|
||||
|
||||
## 3. 로컬 테스트
|
||||
|
||||
> **한 줄 요약:** 프로젝트 루트에서 `npm start` → 브라우저 **http://localhost:8080**
|
||||
> **한 줄 요약:** 프로젝트 루트에서 `npm start` → 브라우저 **http://localhost:44082**
|
||||
|
||||
회원가입·로그인·덱 저장은 **API 서버**가 떠 있어야 합니다.
|
||||
HTML만 Live Server 등으로 열면 저장·로그인이 **405** 로 실패합니다.
|
||||
@@ -41,7 +41,7 @@ HTML만 Live Server 등으로 열면 저장·로그인이 **405** 로 실패합
|
||||
|
||||
```bash
|
||||
npm start # 백그라운드 실행 (터미널 점유 안 함)
|
||||
# 브라우저: http://localhost:8080
|
||||
# 브라우저: http://localhost:44082
|
||||
|
||||
npm run logs # 로그 보기 (종료: Ctrl+C)
|
||||
npm stop # 서버 종료
|
||||
@@ -57,10 +57,10 @@ npm stop # 서버 종료
|
||||
|
||||
### 저장이 안 될 때
|
||||
|
||||
1. `curl http://localhost:8080/api/v1/health` → `{"ok":true,...}` 가 나와야 함
|
||||
1. `curl http://localhost:44082/api/v1/health` → `{"ok":true,...}` 가 나와야 함
|
||||
2. 안 나오면 `npm start` 다시 실행
|
||||
3. `npm run dev` 후 `Healthy`에서 멈춘 것처럼 보여도 **정상**이지만, `Ctrl+C`하면 서버가 꺼짐 → **`npm start` 사용 권장**
|
||||
4. Live Server(`:5500`)로 HTML만 연 경우 → 아래 방법 B 또는 **반드시 `:8080` 사용**
|
||||
4. Live Server(`:5500`)로 HTML만 연 경우 → 아래 방법 B 또는 **반드시 `:44082` 사용**
|
||||
|
||||
### 방법 A — Docker (`npm start`와 동일)
|
||||
|
||||
@@ -68,7 +68,7 @@ npm stop # 서버 종료
|
||||
npm start
|
||||
```
|
||||
|
||||
브라우저: **http://localhost:8080** (Live Server 포트가 아님)
|
||||
브라우저: **http://localhost:44082** (Live Server 포트가 아님)
|
||||
|
||||
### 방법 B — API만 로컬 실행 (Live Server / Cursor 미리보기 사용 시)
|
||||
|
||||
@@ -115,7 +115,7 @@ docker compose up -d --build
|
||||
확인:
|
||||
|
||||
```bash
|
||||
curl http://localhost:8080/api/v1/health
|
||||
curl http://localhost:44082/api/v1/health
|
||||
# → {"ok":true,"version":"4.0.0"}
|
||||
```
|
||||
|
||||
@@ -126,11 +126,14 @@ curl http://localhost:8080/api/v1/health
|
||||
### 옵션 A — `uadeck-web` 포트만 프록시 (권장)
|
||||
|
||||
```
|
||||
https://uniare.sori.studio → http://NAS_IP:8080
|
||||
https://uniare.sori.studio → http://NAS_IP:44082
|
||||
```
|
||||
|
||||
`uadeck-web` 컨테이너가 `/api/`와 정적 파일을 모두 처리합니다.
|
||||
|
||||
`docker-compose.prod.yml`은 프로젝트 루트 전체를 웹 루트로 열지 않고, 필요한 HTML·정적 폴더만 read-only로 마운트합니다.
|
||||
따라서 `.env`, `.git`, `api/`, `docs/` 같은 내부 파일은 Nginx에서 직접 노출되지 않습니다.
|
||||
|
||||
### 옵션 B — 기존 정적 호스팅 + API만 Docker
|
||||
|
||||
1. `docker compose up -d` (API만, 포트 3000)
|
||||
@@ -171,7 +174,7 @@ docker cp uadeck-api:/data/uadeck.db ./backup/uadeck-$(date +%Y%m%d).db
|
||||
|
||||
## 8. 카드/이미지 업데이트
|
||||
|
||||
`datas/`, `images/`, HTML 파일은 **볼륨 마운트**이므로:
|
||||
`datas/`, `images/`, HTML 파일은 **허용된 경로만 read-only 볼륨 마운트**이므로:
|
||||
|
||||
1. Git pull (또는 파일 동기화)
|
||||
2. **재빌드 불필요** — 브라우저 새로고침만으로 반영
|
||||
|
||||
@@ -3,7 +3,7 @@ FROM nginx:alpine
|
||||
COPY nginx/default.conf /etc/nginx/conf.d/default.conf
|
||||
|
||||
# 정적 사이트 (docker-compose build context = 프로젝트 루트)
|
||||
COPY index.html cardList.html result.html login.html signup.html deckHistory.html preset.html admin.html /usr/share/nginx/html/
|
||||
COPY index.html cardList.html result.html login.html signup.html deckHistory.html preset.html profile.html admin.html favicon.ico /usr/share/nginx/html/
|
||||
COPY script /usr/share/nginx/html/script
|
||||
COPY style /usr/share/nginx/html/style
|
||||
COPY i18n /usr/share/nginx/html/i18n
|
||||
|
||||
@@ -7,6 +7,11 @@ server {
|
||||
|
||||
client_max_body_size 25m;
|
||||
|
||||
# 공유 링크 → 덱 로드 페이지
|
||||
location ~ ^/d/([A-Za-z0-9]+)$ {
|
||||
return 302 /cardList.html?d=$1;
|
||||
}
|
||||
|
||||
# API → 백엔드 컨테이너
|
||||
location /api/ {
|
||||
proxy_pass http://uadeck-api:3000;
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"tailwindcss": "^4.1.12"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "docker compose -f docker-compose.prod.yml up -d --build && printf '\\n✅ UADECK 실행 중 → http://localhost:8080\\n 종료: npm stop\\n 로그: npm run logs\\n\\n'",
|
||||
"start": "docker compose -f docker-compose.prod.yml up -d --build && node -e \"console.log('\\nUADECK running: http://localhost:' + (process.env.WEB_PORT || '44082') + '\\nStop: npm stop\\nLogs: npm run logs\\n')\"",
|
||||
"stop": "docker compose -f docker-compose.prod.yml down",
|
||||
"logs": "docker compose -f docker-compose.prod.yml logs -f",
|
||||
"dev": "docker compose -f docker-compose.prod.yml up --build",
|
||||
|
||||
@@ -15,7 +15,8 @@ const UaDeckApi = (() => {
|
||||
const { hostname, port, protocol } = window.location;
|
||||
const isLocal = hostname === 'localhost' || hostname === '127.0.0.1';
|
||||
// Live Server 등 정적 전용 포트 → API 컨테이너/프로세스(3000)로 직접 연결
|
||||
if (isLocal && port && port !== '8080' && port !== '80' && port !== '443' && port !== '') {
|
||||
const proxiedWebPorts = new Set(['44082', '8080', '80', '443', '']);
|
||||
if (isLocal && port && !proxiedWebPorts.has(port)) {
|
||||
return `${protocol}//${hostname}:3000/api/v1`;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user