254 lines
10 KiB
Markdown
254 lines
10 KiB
Markdown
# UNIARE 실행·배포 가이드
|
|
|
|
필요한 부분만 골라서 읽으면 된다.
|
|
|
|
1. 내 컴퓨터에서 실행: `1. 로컬 실행`
|
|
2. NAS에 처음 설치: `2. NAS 최초 배포`
|
|
3. 이미지나 카드 데이터 반영: `3. 정적 파일 업데이트`
|
|
4. API나 Docker 설정 반영: `4. 전체 업데이트`
|
|
|
|
로컬 주소는 `http://localhost:44082`, 운영 주소는 `https://uniare.sori.studio`이다.
|
|
Git 저장소는 `https://git.sori.studio/sori.studio/uniare.sori.studio.git`이다.
|
|
|
|
## 시작 전 확인
|
|
|
|
- 명령은 프로젝트 폴더에서 실행한다.
|
|
- 로컬과 NAS는 각각 다른 `.env`와 DB를 사용한다.
|
|
- `.env`, DB, 비밀번호는 Git에 올리지 않는다.
|
|
- 카드 이미지는 Git LFS로 관리한다.
|
|
- 로컬 위치는 `<LOCAL_PROJECT_DIR>/uniare.sori.studio`, NAS 위치는 `<NAS_PROJECT_DIR>/uniare.sori.studio`로 표기한다.
|
|
|
|
## 1. 로컬 실행
|
|
|
|
최초 한 번 프로젝트 폴더에서 `cp .env.example .env`를 실행한다.
|
|
|
|
`.env`에서 다음 값을 변경한다.
|
|
|
|
```env
|
|
JWT_SECRET=32자_이상의_랜덤_문자열
|
|
ADMIN_EMAIL=관리자_이메일
|
|
ADMIN_PASSWORD=8자_이상의_비밀번호
|
|
WEB_PORT=44082
|
|
APP_BASE_URL=http://localhost:44082
|
|
RESEND_API_KEY=Resend_API_Key
|
|
RESEND_FROM_EMAIL=UNIARE <인증된_발신주소>
|
|
STATS_API_TOKEN=대시보드용_토큰_또는_빈값
|
|
```
|
|
|
|
랜덤 문자열은 `openssl rand -hex 32`로 만들 수 있다.
|
|
|
|
실행은 `npm start`, 종료는 `npm stop`, 로그 확인은 `npm run logs`를 사용한다.
|
|
실행 후 브라우저에서 `http://localhost:44082`에 접속한다.
|
|
|
|
## 2. NAS 최초 배포
|
|
|
|
NAS에 Git, Git LFS, Docker, Docker Compose가 설치되어 있어야 한다.
|
|
|
|
저장소를 받는다.
|
|
|
|
```bash
|
|
cd <NAS_PROJECT_DIR>
|
|
git clone https://git.sori.studio/sori.studio/uniare.sori.studio.git uniare.sori.studio
|
|
cd uniare.sori.studio
|
|
git lfs pull
|
|
cp .env.example .env
|
|
```
|
|
|
|
`.env`에서 다음 값을 운영 환경에 맞게 변경한다.
|
|
|
|
```env
|
|
JWT_SECRET=32자_이상의_랜덤_문자열
|
|
ADMIN_EMAIL=관리자_이메일
|
|
ADMIN_PASSWORD=8자_이상의_비밀번호
|
|
ADMIN_USERNAME=admin
|
|
WEB_PORT=44082
|
|
CORS_ORIGINS=https://uniare.sori.studio
|
|
APP_BASE_URL=https://uniare.sori.studio
|
|
RESEND_API_KEY=Resend_API_Key
|
|
RESEND_FROM_EMAIL=UNIARE <account@인증된_발신도메인>
|
|
STATS_API_TOKEN=대시보드용_긴_랜덤_문자열
|
|
```
|
|
|
|
Resend에서 소유한 도메인 또는 발신용 하위 도메인을 먼저 추가하고 DNS 인증을 완료해야 한다. API Key에는 메일 발송 권한이 필요하며 `.env` 외에는 기록하지 않는다.
|
|
|
|
Docker 실행: `docker compose -f docker-compose.prod.yml up -d --build`
|
|
|
|
상태 확인: `docker compose -f docker-compose.prod.yml ps`
|
|
|
|
API 확인: `curl -fsS http://localhost:44082/api/v1/health`
|
|
|
|
운영 지표 확인: `curl -fsS http://localhost:44082/api/stats`
|
|
|
|
정상이면 `{"ok":true,"version":"4.0.16"}`이 표시된다.
|
|
|
|
이메일 기능 확인:
|
|
|
|
1. 실제 수신 가능한 주소로 신규 가입한다.
|
|
2. 인증 메일 링크를 누르기 전 로그인이 차단되고 로그인 화면에 `인증 메일 다시 보내기`가 표시되는지 확인한다.
|
|
3. 로그인 화면에서 재발송 후 60초 대기와 최초 발송 포함 월 3회 안내가 표시되는지 확인한다.
|
|
4. 인증 후 기본 한국어 완료 안내와 로그인이 정상 동작하는지 확인한다.
|
|
5. 로그인 화면의 `비밀번호를 잊으셨나요?`와 비밀번호 찾기·재설정 화면이 선택 언어로 표시되는지 확인한다.
|
|
6. 같은 계정에서 인증·재설정 메일을 합산해 월 3회 초과 시 관리자 문의 안내가 표시되는지 확인한다.
|
|
7. 관리자 화면에서 미인증 회원의 `이메일 인증 승인` 버튼으로 수동 승인 후 로그인되는지 확인한다.
|
|
|
|
회원 탈퇴 확인:
|
|
|
|
1. 일반 회원 프로필 최하단의 작은 `회원 탈퇴` 문구를 눌러 모달이 열리는지 확인한다.
|
|
2. `DELETE`를 정확히 입력하기 전에는 `YES` 버튼이 비활성화되는지 확인한다.
|
|
3. 탈퇴 후 로그아웃되고 같은 계정으로 로그인할 수 없는지 확인한다.
|
|
4. 탈퇴 회원의 덱은 삭제되지 않고 작성자가 `익명`으로 표시되는지 확인한다.
|
|
5. 관리자 회원 수정 모달에서 다른 회원을 탈퇴 처리할 수 있고, 자신의 관리자 계정은 삭제할 수 없는지 확인한다.
|
|
6. 관리자 덱 관리에서 복수 선택·현재 목록 전체 선택·일괄 삭제와 썸네일 정리가 동작하는지 확인한다.
|
|
|
|
NAS 리버스 프록시는 `https://uniare.sori.studio`을 `http://<NAS_IP>:44082`로 연결한다.
|
|
|
|
## 3. 정적 파일 업데이트
|
|
|
|
이미지, 카드 데이터, HTML, CSS, 프론트 JavaScript는 Docker 이미지를 다시 빌드하지 않아도 된다.
|
|
|
|
### 이미지만 변경
|
|
|
|
NAS 프로젝트 폴더에서 `git pull --ff-only`와 `git lfs pull`을 실행한 뒤 브라우저를 강력 새로고침한다. 컨테이너 재시작은 필요 없다.
|
|
|
|
### `datas/*.js` 변경
|
|
|
|
NAS 프로젝트 폴더에서 다음 명령을 실행한다.
|
|
|
|
```bash
|
|
git pull --ff-only
|
|
git lfs pull
|
|
docker compose -f docker-compose.prod.yml restart uniare-api
|
|
```
|
|
|
|
API가 카드 데이터를 메모리에 저장하므로 `datas/` 변경 시 API만 재시작한다. 이미지와 `datas/*.js`를 함께 변경한 경우도 같은 절차를 사용한다.
|
|
|
|
확인할 내용:
|
|
|
|
- `imgSrc`와 실제 이미지 파일명이 같은가?
|
|
- 파일명 대소문자가 같은가?
|
|
- 신규 시리즈가 번역 파일 3개에 등록되어 있는가?
|
|
- 카드 목록과 저장 덱에서 모두 정상 표시되는가?
|
|
|
|
### HTML·일반 CSS·프론트 JavaScript 변경
|
|
|
|
NAS는 npm이 없어도 된다. `style/output.css`는 로컬에서 빌드해 Git에 포함되므로 **`git pull`만 하면** 스타일이 반영된다.
|
|
|
|
```bash
|
|
git pull --ff-only
|
|
git lfs pull
|
|
```
|
|
|
|
배포 후 확인 (NAS 프로젝트 폴더):
|
|
|
|
```bash
|
|
wc -c style/output.css # 약 55000 (55KB) — 44000대면 구버전, pull 누락
|
|
head -1 login.html # output.css?v= 버전 쿼리 포함 여부
|
|
```
|
|
|
|
브라우저에서도 `개발자 도구 → Network → output.css`·`deck-favorites.js` 크기·상태를 확인한다. Cloudflare를 쓰면 구 파일이 `immutable`로 남을 수 있어 HTML에 `?v=` 쿼리를 붙인다 (v4.0.6+ CSS, v4.0.7+ 덱 페이지 JS).
|
|
|
|
`script/deck-favorites.js`는 `window.UaDeckFavorites.init`이 있어야 한다. 없으면 `git pull` 누락 또는 JS CDN 캐시다.
|
|
|
|
`nginx/default.conf`를 변경했다면 **반드시** 웹 컨테이너를 재생성한다 (볼륨 마운트 반영):
|
|
|
|
```bash
|
|
docker compose -f docker-compose.prod.yml up -d --build uniare-web
|
|
```
|
|
|
|
배포 검증 (NAS 프로젝트 폴더):
|
|
|
|
```bash
|
|
# 호스트 파일 — v4.0.8·shim 포함 여부
|
|
rg "4\.0\.8|deck-favorites-shim" deckHistory.html
|
|
|
|
# 컨테이너가 같은 파일을 보는지 (다르면 compose 경로/마운트 문제)
|
|
docker exec uniare-web rg "deck-favorites-shim|4\.0\.8" /usr/share/nginx/html/deckHistory.html
|
|
|
|
# Docker 직접 접속 (Cloudflare 우회) — 위와 같아야 함
|
|
curl -s http://127.0.0.1:44082/deckHistory.html | rg "deck-favorites-shim|4\.0\.8"
|
|
curl -s http://127.0.0.1:44082/script/deck-favorites.js | tail -1
|
|
```
|
|
|
|
호스트·컨테이너·`curl 127.0.0.1`은 최신인데 `https://uniare.sori.studio`만 구버전이면 **Cloudflare 캐시 Purge**가 필요하다.
|
|
|
|
HTML·`style/`·`script/`만 바뀐 경우 API 컨테이너 재시작은 필요 없다.
|
|
|
|
이메일 기능은 API와 Compose 환경 변수가 함께 바뀌므로 `git pull`만으로 반영되지 않는다. 반드시 `.env` 설정 후 전체 스택을 다시 빌드한다.
|
|
|
|
## 4. 전체 업데이트
|
|
|
|
`api/`, `nginx/`, Dockerfile, Compose 설정을 변경했다면 NAS 프로젝트 폴더에서 다음 명령을 실행한다.
|
|
|
|
```bash
|
|
git pull --ff-only
|
|
git lfs pull
|
|
docker compose -f docker-compose.prod.yml up -d --build
|
|
```
|
|
|
|
상태 확인은 `docker compose -f docker-compose.prod.yml ps`, API 확인은 `curl -fsS http://localhost:44082/api/v1/health`를 사용한다.
|
|
|
|
`.env`를 변경했다면 `docker compose -f docker-compose.prod.yml up -d --force-recreate`로 컨테이너를 재생성한다.
|
|
|
|
## 5. CSS 빌드
|
|
|
|
Tailwind 클래스를 새로 추가했을 때만 로컬에서 `npm install`과 `npm run build:css`를 실행한다.
|
|
생성된 `style/output.css`도 Git에 포함한다.
|
|
이미지, 카드 데이터, 기존 CSS만 수정했다면 실행하지 않아도 된다.
|
|
|
|
## 6. 자주 쓰는 운영 명령
|
|
|
|
- 전체 재시작: `docker compose -f docker-compose.prod.yml restart`
|
|
- API만 재시작: `docker compose -f docker-compose.prod.yml restart uniare-api`
|
|
- 로그 확인: `docker compose -f docker-compose.prod.yml logs -f`
|
|
- 종료: `docker compose -f docker-compose.prod.yml down`
|
|
- 다시 실행: `docker compose -f docker-compose.prod.yml up -d`
|
|
|
|
## 7. 백업
|
|
|
|
DB는 Docker 볼륨의 `/data/uadeck.db`에 있다.
|
|
덱 미리보기와 아바타는 `api/data/previews/`, `api/data/avatars/`에 있다.
|
|
|
|
DB 백업:
|
|
|
|
```bash
|
|
mkdir -p backup
|
|
BACKUP_FILE="uadeck-$(date +%Y%m%d-%H%M%S).db"
|
|
docker exec uniare-api node --input-type=module -e "import Database from 'better-sqlite3'; const db = new Database('/data/uadeck.db'); await db.backup('/data/$BACKUP_FILE'); db.close();"
|
|
docker cp "uniare-api:/data/$BACKUP_FILE" "./backup/$BACKUP_FILE"
|
|
docker exec uniare-api rm "/data/$BACKUP_FILE"
|
|
```
|
|
|
|
미리보기와 아바타 백업: `tar -czf "./backup/uploads-$(date +%Y%m%d-%H%M%S).tar.gz" api/data/previews api/data/avatars`
|
|
|
|
`.env`는 Git이 아닌 접근 제한된 별도 위치에 보관한다.
|
|
|
|
## 8. 롤백
|
|
|
|
문제가 발생하면 DB를 먼저 백업하고 `git log --oneline -10`으로 이전 정상 커밋을 확인한다.
|
|
|
|
```bash
|
|
git checkout <정상_커밋_해시>
|
|
git lfs pull
|
|
docker compose -f docker-compose.prod.yml up -d --build
|
|
```
|
|
|
|
API 확인: `curl -fsS http://localhost:44082/api/v1/health`
|
|
|
|
최신 `main`으로 돌아갈 때는 `git checkout main`과 `git pull --ff-only`를 실행한다.
|
|
|
|
## 9. 문제가 생겼을 때
|
|
|
|
웹이 열리지 않으면 `docker compose -f docker-compose.prod.yml ps`와 `docker compose -f docker-compose.prod.yml logs --tail=100`을 확인한다.
|
|
|
|
로그인이나 저장이 안 되면 `curl -fsS http://localhost:44082/api/v1/health`로 API 상태를 확인한다.
|
|
|
|
카드 이미지가 보이지 않으면 `git lfs pull`을 실행하고 `datas/*.js`의 `imgSrc`와 실제 이미지 파일명을 비교한다.
|
|
|
|
## 완료 확인
|
|
|
|
- `uniare-web`, `uniare-api`가 실행 중인가?
|
|
- API가 `ok: true`를 반환하는가?
|
|
- 홈과 카드 이미지가 열리는가?
|
|
- 로그인과 덱 저장이 동작하는가?
|
|
- `.env`와 DB가 Git에 포함되지 않았는가?
|