From 88c7c632bfe904b624c2f4dd2ff6b43d0c311e9f Mon Sep 17 00:00:00 2001 From: Sejong Yoo Date: Wed, 12 Aug 2026 15:10:59 +0900 Subject: [PATCH] fix CI lockfile sync and simplify Docker docs. Regenerate package-lock for npm ci, bump Node to 22, and trim compose env to secrets only. Co-authored-by: Cursor --- .gitea/workflows/ci.yml | 2 +- Dockerfile | 6 +- README.md | 90 ++++++++++++++++++++++-- docker-compose.yml | 9 +-- package-lock.json | 152 +++++++++++++++++++--------------------- 5 files changed, 163 insertions(+), 96 deletions(-) diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index ebd72ff..72d20e7 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -24,7 +24,7 @@ jobs: - name: Setup Node.js uses: actions/setup-node@v4 with: - node-version: "20" + node-version: "22" cache: npm - name: Install dependencies diff --git a/Dockerfile b/Dockerfile index aa7dab9..7c44228 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,19 +1,19 @@ # syntax=docker/dockerfile:1 -FROM node:20-alpine AS deps +FROM node:22-alpine AS deps WORKDIR /app COPY package.json package-lock.json* ./ COPY prisma ./prisma RUN if [ -f package-lock.json ]; then npm ci; else npm install; fi -FROM node:20-alpine AS build +FROM node:22-alpine AS build WORKDIR /app COPY --from=deps /app/node_modules ./node_modules COPY . . ENV DATABASE_URL="file:./data/sejong.db" RUN mkdir -p data && npx prisma generate && npm run build -FROM node:20-alpine AS runner +FROM node:22-alpine AS runner WORKDIR /app ENV NODE_ENV=production ENV HOST=0.0.0.0 diff --git a/README.md b/README.md index 046434e..2538442 100644 --- a/README.md +++ b/README.md @@ -4,35 +4,111 @@ Nuxt 3 기반 멀티 스크린 화면 공유 플랫폼 (Native WebRTC P2P Mesh + ## 빠른 시작 (로컬) +로컬 개발은 **`.env` 파일**로 설정을 관리합니다. + ```bash cp .env.example .env +# .env 에서 JWT_SECRET, ADMIN_USERNAME, ADMIN_PASSWORD 등 수정 npm install npx prisma migrate dev npm run dev ``` +`.env.example`을 복사한 뒤 값을 수정합니다. + +### 환경 변수 + +로컬(`.env`)과 Docker(`environment`)에서 동일하게 사용합니다. + +| 변수 | 필수 | 기본값 | 설명 | +|------|------|--------|------| +| `JWT_SECRET` | 예 | — | 관리자 JWT·방 accessToken 서명 키. 프로덕션에서는 긴 난수로 변경 | +| `ADMIN_USERNAME` | 예 | `admin` | 관리자 로그인 아이디 | +| `ADMIN_PASSWORD` | 예 | — | 관리자 로그인 비밀번호 (반드시 변경) | +| `DATABASE_URL` | 아니오* | 로컬 `file:../data/sejong.db` / 이미지 `file:/app/data/sejong.db` | Prisma SQLite DB 경로 (*Docker 이미지가 기본값 포함) | +| `MAX_ROOM_MEMBERS` | 아니오 | `8` | 방당 최대 참여자 수 (하드 리밋) | +| `MAX_CONCURRENT_STREAMS` | 아니오 | `4` | 방당 동시 화면 공유(방송) 수 상한 | +| `ROOM_ACCESS_TTL_DAYS` | 아니오 | `7` | 방 비밀번호 통과 후 발급되는 accessToken 유효 기간(일) | +| `HOST` / `NITRO_HOST` | 아니오* | `0.0.0.0` | 바인딩 주소 (*Docker 이미지 기본값) | +| `PORT` / `NITRO_PORT` | 아니오* | `3000` | 리스닝 포트 (*Docker 이미지 기본값) | + +Docker Compose에서는 보통 **`JWT_SECRET` / `ADMIN_USERNAME` / `ADMIN_PASSWORD`만** 지정하면 됩니다. + - 앱: http://localhost:3000 -- 관리자: http://localhost:3000/admin (기본 `admin` / `admin` — `.env`에서 변경) +- 관리자: http://localhost:3000/admin (계정은 위 `ADMIN_*` 값) + +> **참고:** `localhost`의 HTTP는 브라우저가 예외로 허용합니다. 다른 PC·사설 IP·도메인으로 접속할 때는 아래 HTTPS 요구사항을 보세요. + +## HTTPS 요구사항 (필수) + +**화면 공유·시스템 오디오 등 핵심 기능을 쓰려면 HTTPS로 접속해야 합니다.** + +브라우저는 `navigator.mediaDevices.getDisplayMedia()`(화면/창 캡처)를 **보안 컨텍스트(Secure Context)** 에서만 허용합니다. + +| 접속 방식 | 화면 공유 | 비고 | +|-----------|-----------|------| +| `https://도메인` | 가능 | 프로덕션·LAN 공용 권장 (NPM Plus SSL) | +| `http://localhost` | 가능 | 개발 PC 본인만 | +| `http://192.168.x.x` 등 사설 IP | **불가** | UI·입장은 열려도 화면 공유가 차단됨 | + +따라서 같은 네트워크의 다른 PC와 테스트하거나 실제로 쓰려면 **NPM Plus 등으로 SSL을 켠 뒤 `https://...`로 접속**하세요. Socket.io 시그널링도 같은 호스트의 **WSS**(WebSocket over TLS)로 붙는 구성이 안전합니다. ## Docker + NPM Plus -레지스트리 이미지 예: +### 레지스트리 이미지로 실행 (권장) -```bash -docker pull docker.sejong.dev/sejong-tv:latest +아래 `docker-compose.yml` 예시처럼 **시크릿·관리자 계정만** `environment`에 넣고 실행합니다. +(`DATABASE_URL`, 포트 등은 이미지 기본값 사용. 로컬 개발은 `.env` 파일.) + +```yaml +services: + app: + image: docker.sejong.dev/sejong-tv:latest + container_name: sejong-tv + restart: unless-stopped + environment: + JWT_SECRET: change-me-to-a-long-random-string + ADMIN_USERNAME: admin + ADMIN_PASSWORD: change-me + volumes: + - sqlite_data:/app/data + # 직접 포트 노출 (로컬/테스트) + ports: + - "3000:3000" + # NPM Plus 연동 시: ports를 제거하고 아래 네트워크를 사용 + # networks: + # - npmplus + +volumes: + sqlite_data: + +# networks: +# npmplus: +# external: true +# # docker network ls 로 NPM Plus 네트워크 이름 확인 후 수정 ``` -로컬 빌드: +```bash +docker compose pull +docker compose up -d +``` + +NPM Plus Proxy Host 예: `http` → `sejong-tv:3000`, **Websockets Support ON**, **SSL(HTTPS) 필수 활성화**. +화면 공유를 포함한 전체 기능은 `https://도메인` 접속에서만 정상 동작합니다. +자세한 설정은 [docs/npmplus.md](docs/npmplus.md) 참고. + +### 로컬에서 이미지 빌드 ```bash docker compose up -d --build ``` -프록시 설정은 [docs/npmplus.md](docs/npmplus.md) 참고. +### Gitea CI -Gitea CI: `main` 푸시 시 `docker.sejong.dev/sejong-tv:latest` 및 `sha-` 태그로 푸시합니다. +`main` 푸시 시 `docker.sejong.dev/sejong-tv:latest` 및 `sha-` 태그로 푸시합니다. 시크릿 `REGISTRY_USERNAME`, `REGISTRY_PASSWORD`가 필요합니다. + ## 주요 기능 - 관리자 방 생성/삭제 (영속, 방 비밀번호) diff --git a/docker-compose.yml b/docker-compose.yml index e15050d..4ba65cb 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -3,13 +3,10 @@ services: build: . container_name: sejong-tv restart: unless-stopped - env_file: .env environment: - DATABASE_URL: file:/app/data/sejong.db - HOST: 0.0.0.0 - PORT: 3000 - NITRO_HOST: 0.0.0.0 - NITRO_PORT: 3000 + JWT_SECRET: change-me-to-a-long-random-string + ADMIN_USERNAME: admin + ADMIN_PASSWORD: change-me volumes: - sqlite_data:/app/data ports: diff --git a/package-lock.json b/package-lock.json index 6e80b84..6ac279a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1648,6 +1648,24 @@ "nuxt": "^3.21.11" } }, + "node_modules/@nuxt/schema": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/@nuxt/schema/-/schema-4.5.2.tgz", + "integrity": "sha512-h9g1kt9O2iU9nX6HDFu9gvwtpn+8oSJX0RSTWRBnEx/Pkz+WlOHtjuS0SbkTAXw5aT1+H1GysWVwNTjJfBY/0w==", + "extraneous": true, + "license": "MIT", + "dependencies": { + "@vue/shared": "^3.5.40", + "defu": "^6.1.7", + "nostics": "^1.2.0", + "pathe": "^2.0.3", + "pkg-types": "^2.3.1", + "std-env": "^4.2.0" + }, + "engines": { + "node": "^14.18.0 || >=16.10.0" + } + }, "node_modules/@nuxt/telemetry": { "version": "2.8.0", "resolved": "https://registry.npmjs.org/@nuxt/telemetry/-/telemetry-2.8.0.tgz", @@ -3093,6 +3111,18 @@ "giget": "dist/cli.mjs" } }, + "node_modules/@prisma/config/node_modules/magicast": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.3.5.tgz", + "integrity": "sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==", + "extraneous": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.25.4", + "@babel/types": "^7.25.4", + "source-map-js": "^1.2.0" + } + }, "node_modules/@prisma/config/node_modules/perfect-debounce": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz", @@ -4544,27 +4574,6 @@ "node": ">= 0.6" } }, - "node_modules/accepts/node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/accepts/node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "license": "MIT", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, "node_modules/acorn": { "version": "8.18.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.18.0.tgz", @@ -5239,29 +5248,6 @@ "node": ">= 6.0.0" } }, - "node_modules/cache-content-type/node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cache-content-type/node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dev": true, - "license": "MIT", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, "node_modules/call-bind-apply-helpers": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", @@ -5457,6 +5443,16 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "license": "MIT" }, + "node_modules/commander": { + "version": "15.0.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-15.0.0.tgz", + "integrity": "sha512-z67u4ZhzCL/Tydu1lJARtEZYWbWaN7oYLHbsuzocr6y4N6WZAagG3RQ4FW61V1/0+jImpj293XfrcYnd1qxtPg==", + "extraneous": true, + "license": "MIT", + "engines": { + "node": ">=22.12.0" + } + }, "node_modules/commondir": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", @@ -8395,28 +8391,24 @@ } }, "node_modules/mime-db": { - "version": "1.54.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", - "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", "license": "MIT", "engines": { "node": ">= 0.6" } }, "node_modules/mime-types": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", - "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "license": "MIT", "dependencies": { - "mime-db": "^1.54.0" + "mime-db": "1.52.0" }, "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" + "node": ">= 0.6" } }, "node_modules/mimic-fn": { @@ -10779,6 +10771,31 @@ "url": "https://opencollective.com/express" } }, + "node_modules/send/node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/send/node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/serialize-javascript": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-7.1.0.tgz", @@ -11805,29 +11822,6 @@ "node": ">= 0.6" } }, - "node_modules/type-is/node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/type-is/node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dev": true, - "license": "MIT", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, "node_modules/typescript": { "version": "5.9.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",