From b70caddff1bdcce65c097d07975df34d2386ecf9 Mon Sep 17 00:00:00 2001 From: gahusb Date: Sat, 23 May 2026 11:46:45 +0900 Subject: [PATCH] =?UTF-8?q?feat(image-lab):=20Dockerfile=20+=20compose=20e?= =?UTF-8?q?ntry=20+=20scripts=206=EC=9C=84=EC=B9=98=20+=20nginx=20?= =?UTF-8?q?=EC=B0=A8=EB=8B=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Task 5 of Video Studio backend plan. Wires image-lab Python code (T1-T4) into NAS Docker infrastructure on port 18802. - image-lab/Dockerfile (python:3.12-slim + uvicorn) - image-lab/requirements.txt (fastapi, redis, httpx) - image-lab/env.example (INTERNAL_API_KEY, IMAGE_DATA_DIR, REDIS_URL, CORS) - docker-compose.yml: image-lab service block (port 18802, redis depends_on, healthcheck, volume ${RUNTIME_PATH}/image-data:/app/data) + frontend depends_on entry - scripts/deploy-nas.sh: SERVICES += image-lab - scripts/deploy.sh: BUILD_TARGETS/CONTAINER_NAMES/HEALTH_ENDPOINTS += image-lab, DATA_DIRS += image - nginx/default.conf: /api/internal/image/ 3-layer block (IP allowlist + deny all + X-Internal-Key forward) mirroring /api/internal/video/ Plan-B-Video lesson: 6-location registration enforced per feedback_nas_deploy_paths.md rule 3 to avoid 'transferring dockerfile: 2B' deploy failure. Tests: image-lab pytest 11 passed (no regression). Co-Authored-By: Claude Opus 4.7 (1M context) --- docker-compose.yml | 22 ++++++++++++++++++++++ image-lab/Dockerfile | 7 +++++++ image-lab/env.example | 4 ++++ image-lab/requirements.txt | 5 +++++ nginx/default.conf | 20 ++++++++++++++++++++ scripts/deploy-nas.sh | 2 +- scripts/deploy.sh | 8 ++++---- 7 files changed, 63 insertions(+), 5 deletions(-) create mode 100644 image-lab/Dockerfile create mode 100644 image-lab/env.example create mode 100644 image-lab/requirements.txt diff --git a/docker-compose.yml b/docker-compose.yml index 07207a1..f8237cc 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -113,6 +113,27 @@ services: timeout: 5s retries: 3 + image-lab: + build: ./image-lab + container_name: image-lab + restart: unless-stopped + ports: + - "18802:8000" + environment: + - REDIS_URL=redis://redis:6379 + - INTERNAL_API_KEY=${INTERNAL_API_KEY} + - IMAGE_DATA_DIR=/app/data + - CORS_ALLOW_ORIGINS=http://localhost:3007,http://localhost:8080 + volumes: + - ${RUNTIME_PATH}/image-data:/app/data + depends_on: + - redis + healthcheck: + test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')"] + interval: 60s + timeout: 5s + retries: 3 + insta-lab: build: context: ./insta-lab @@ -289,6 +310,7 @@ services: - packs-lab - travel-proxy - video-lab + - image-lab ports: - "8080:80" volumes: diff --git a/image-lab/Dockerfile b/image-lab/Dockerfile new file mode 100644 index 0000000..9b2f793 --- /dev/null +++ b/image-lab/Dockerfile @@ -0,0 +1,7 @@ +FROM python:3.12-slim +WORKDIR /app +COPY requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt +COPY app ./app +EXPOSE 8000 +CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000", "--workers", "1"] diff --git a/image-lab/env.example b/image-lab/env.example new file mode 100644 index 0000000..ca731da --- /dev/null +++ b/image-lab/env.example @@ -0,0 +1,4 @@ +INTERNAL_API_KEY=replace-me +IMAGE_DATA_DIR=/app/data +CORS_ALLOW_ORIGINS=http://localhost:3007,http://localhost:8080 +REDIS_URL=redis://redis:6379 diff --git a/image-lab/requirements.txt b/image-lab/requirements.txt new file mode 100644 index 0000000..8186e90 --- /dev/null +++ b/image-lab/requirements.txt @@ -0,0 +1,5 @@ +fastapi==0.115.0 +uvicorn[standard]==0.30.6 +pydantic==2.9.2 +redis==5.0.8 +httpx==0.27.2 diff --git a/nginx/default.conf b/nginx/default.conf index 0fa655a..f32c9a0 100644 --- a/nginx/default.conf +++ b/nginx/default.conf @@ -276,6 +276,26 @@ server { proxy_pass http://$video_internal_backend$request_uri; } + # Video Studio — Windows image-render → NAS image-lab internal webhook + # Layer 1·2: nginx IP 화이트리스트 (LAN + Tailscale) + # Layer 3: X-Internal-Key (FastAPI dependency) + location /api/internal/image/ { + allow 192.168.45.0/24; # LAN 화이트리스트 + allow 100.64.0.0/10; # Tailscale CGNAT + allow 127.0.0.1; # NAS 내부 + deny all; + + resolver 127.0.0.11 valid=10s; + set $image_internal_backend image-lab:8000; + + proxy_http_version 1.1; + 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-Internal-Key $http_x_internal_key; + proxy_pass http://$image_internal_backend$request_uri; + } + # portfolio API (Stock) — trailing slash 유무 모두 매칭 location /api/portfolio { proxy_http_version 1.1; diff --git a/scripts/deploy-nas.sh b/scripts/deploy-nas.sh index 5e2d318..c62f876 100644 --- a/scripts/deploy-nas.sh +++ b/scripts/deploy-nas.sh @@ -2,7 +2,7 @@ set -euo pipefail # ── 서비스 목록 (한 곳에서만 관리) ── -SERVICES="lotto travel-proxy deployer stock music-lab insta-lab realestate-lab agent-office personal packs-lab video-lab nginx scripts" +SERVICES="lotto travel-proxy deployer stock music-lab insta-lab realestate-lab agent-office personal packs-lab video-lab image-lab nginx scripts" # 1. 자동 감지: Docker 컨테이너 내부인가? if [ -d "/repo" ] && [ -d "/runtime" ]; then diff --git a/scripts/deploy.sh b/scripts/deploy.sh index 427d2e3..79a26ba 100644 --- a/scripts/deploy.sh +++ b/scripts/deploy.sh @@ -15,15 +15,15 @@ flock -n 200 || { echo "Deploy already running, skipping"; exit 0; } # ── 서비스 목록 (한 곳에서만 관리) ── # docker compose 서비스명 (deployer 제외 — 자기 자신을 재빌드하면 스크립트 중단) -BUILD_TARGETS="lotto travel-proxy stock music-lab insta-lab realestate-lab agent-office personal packs-lab video-lab frontend" +BUILD_TARGETS="lotto travel-proxy stock music-lab insta-lab realestate-lab agent-office personal packs-lab video-lab image-lab frontend" # 컨테이너 이름 (고아 정리용 — blog-lab은 폐기 대상으로 정리 리스트에 유지) -CONTAINER_NAMES="lotto stock music-lab insta-lab blog-lab realestate-lab agent-office personal packs-lab travel-proxy video-lab frontend" +CONTAINER_NAMES="lotto stock music-lab insta-lab blog-lab realestate-lab agent-office personal packs-lab travel-proxy video-lab image-lab frontend" # Infra 서비스 (image-based, 영속 데이터 보존을 위해 stop/rm 없이 up만) INFRA_SERVICES="redis" # 헬스체크 대상 -HEALTH_ENDPOINTS="lotto stock travel-proxy music-lab insta-lab realestate-lab agent-office personal packs-lab video-lab redis" +HEALTH_ENDPOINTS="lotto stock travel-proxy music-lab insta-lab realestate-lab agent-office personal packs-lab video-lab image-lab redis" # data 디렉토리 (packs-lab은 별도 media/packs 사용) -DATA_DIRS="music stock insta realestate agent-office personal video" +DATA_DIRS="music stock insta realestate agent-office personal video image" # 1. 자동 감지: Docker 컨테이너 내부인가? if [ -d "/repo" ] && [ -d "/runtime" ]; then