Files
joke-app/docs/11-deployment-ops.md
EJClaw 373231b553 위협 가이드를 관리자 전용 옵션으로(기본 꺼짐) + 최고관리자 추가 + 계정 메뉴에 관리자 메뉴
- 엔진: GameDefinition.adminOnlyOptions — 방은 항상 관리자 값 사용(잠금처럼 강제, 잠금 안내는 안 띄움)
- 오목·체스·장기 threatHints, 바둑 atariWarning: 기본 false, 대기실 설정에서 뺌, 관리자 화면 게임 기본 규칙에서만 켬
- 체스·장기 hints는 off / moves만 남김
- 최고관리자 기본값에 1352267557213573160 추가(.env.example·문서 포함)
- 상단 계정 메뉴: 관리자·최고관리자면 [🛠 관리자 메뉴] → /admin
- 테스트: admin.test(관리자 전용 옵션 강제, 두 번째 최고관리자), admin.e2e(계정 메뉴, 위협 가이드 켜기 저장)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 15:59:32 +09:00

4.7 KiB
Raw Blame History

11. 배포와 운영

1. 구성

deploy/
├─ Dockerfile            # 멀티 스테이지: bun install → web 빌드 → server 번들 → oven/bun:1-slim 실행 이미지
├─ docker-compose.yml    # app + caddy, 볼륨: app-data(SQLite), caddy-data(인증서)
├─ Caddyfile             # HTTPS 자동, /ws·/api → app:3000, 나머지 정적 파일
└─ .env.example          # 필요한 환경 변수 목록(값 없음)

환경 변수

이름 설명
PUBLIC_ORIGIN https://<도메인> (Origin 검사, OAuth 리다이렉트, 공유 링크)
DISCORD_CLIENT_ID / DISCORD_CLIENT_SECRET 디스코드 앱
SESSION_SECRET 짧은 수명 서명 쿠키(OAuth state)용 32바이트
DB_PATH 기본 /data/app.db
SUPERADMIN_DISCORD_IDS 슈퍼어드민 디스코드 ID(쉼표 구분). 기본 293719842274541579,1352267557213573160
TRUST_PROXY 1이면 Caddy가 붙인 X-Forwarded-For로 IP 판단
PORT 기본 3000
LOG_LEVEL info

Caddy

  • encode zstd gzip
  • /assets/*: Cache-Control: public, max-age=31536000, immutable (파일명에 해시)
  • index.html: no-cache (배포 즉시 반영)
  • /sw.js·/manifest.webmanifest·/offline.html: no-cache(서비스 워커 갱신). 워커는 페이지와 /api·/ws를 저장하지 않는다(07 문서 11절).
  • 보안 헤더: CSP(04 문서 7절), X-Content-Type-Options: nosniff, Referrer-Policy: strict-origin-when-cross-origin, HSTS.
  • WebSocket 프록시는 Caddy 기본 지원. 연결 유지 시간 제한 없음.

2. 배포 절차

  1. main에 커밋·푸시(Gitea tkrmagid/joke-app).
  2. 서버에서 git pull && docker compose -f deploy/docker-compose.yml up -d --build.
  3. 옛 컨테이너가 SIGTERM을 받으면 bye: restart 전송 후 종료(stop_grace_period: 15s), 새 컨테이너가 방 복구 후 시작.
  4. 확인: /healthz(DB 열림, 방 복구 완료) 200, 로그에 오류 없음, 테스트 방 하나 만들어 오목 한 수.
  5. 문제가 있으면 이전 이미지 태그로 되돌림(docker compose 이미지에 커밋 해시 태그).

3. 개발·테스트 환경

  • 로컬: bun install && bun run dev → 웹 Vite(5173) + 서버(3000), Vite가 /api·/ws를 서버로 프록시.
  • 디스코드 로그인 없이도 게스트로 모든 기능 테스트 가능(디스코드 값이 없으면 버튼 숨김).
  • 공용 테스트 서버(사용자가 직접 접속해 보는 곳)는 배포 위치가 정해지면 같은 compose로 띄운다.

4. 백업과 복구

  • 매일 04:00 VACUUM INTO '/data/backup/app-YYYYMMDD.db', 14개 보관.
  • 복구: 컨테이너 중지 → 백업 파일을 app.db로 복사 → 시작. 진행 중이던 방은 백업 시점 상태로 돌아간다.

5. 감시

  • /healthz 1분마다 확인(외부 업타임 감시 또는 호스트 cron), 실패 3번이면 알림(디스코드 웹훅, 선택).
  • 지표 /internal/metrics는 Caddy에서 외부 차단.
  • 로그: Docker json-file, 크기 제한 50MB × 5.

6. 정해야 할 것 (사용자 결정 필요)

항목 선택지 기본 제안
서버 위치 이 호스트 / .5 Docker 호스트 / 외부 VPS .5로 결정(2026-10-06), 7절
도메인 game.tkrmagid.kr (확정) 모든 기능 완성 후 DNS 연결
디스코드 앱 개발자 포털에서 생성 → client ID/secret, Redirect URI 등록 생성됨(2026-10-06), 값은 .5의 deploy/.env

7. 실제 배포: .5 Docker 호스트 + NPMplus (2026-10-06)

.5(192.168.10.5)는 80/443을 쓰지 않고, 집 공인 IP의 80/443은 192.168.10.3의 NPMplus가 받는다(다른 tkrmagid.kr 사이트와 같은 방식). 그래서 Caddy 없이 앱만 띄우고 HTTPS·보안 헤더는 NPMplus가 맡는다.

인터넷 → game.tkrmagid.kr(211.213.11.141) → NPMplus 192.168.10.3:443 → 192.168.10.5:2970 → app:3000
  • 코드: root@192.168.10.5:/root/other/joke-app (Gitea tkrmagid/joke-app clone)
  • 설정: 같은 곳 deploy/.env (권한 600, APP_PORT=2970, SESSION_SECRET은 서버에서 생성)
  • 실행: cd /root/other/joke-app/deploy && docker compose -f docker-compose.proxy.yml up -d --build (프로젝트 이름 joke-app, 볼륨 joke-app_app-data)
  • NPMplus 프록시 호스트 game.tkrmagid.kr → http://192.168.10.5:2970, WebSocket 허용. Advanced 칸에는 deploy/npmplus-advanced.conf(CSP 등 Caddyfile과 같은 헤더)를 넣는다.
  • 인증서: DNS에 game 레코드(CNAME tkrmagid.kr 또는 A 211.213.11.141)가 생긴 뒤 NPMplus에서 Let's Encrypt 인증서를 받고 Force SSL·HSTS·HTTP/2를 켠다.
  • 업데이트: git pull && docker compose -f docker-compose.proxy.yml up -d --build
  • 확인: curl http://192.168.10.5:2970/healthz, curl -I https://game.tkrmagid.kr/