docs(plan): Plan-B-Video port 18800 → 18801 (realestate-lab 충돌)

T6 implementer가 발견: realestate-lab이 이미 18800 점유.
video-lab 포트를 18801로 정정. plan 18 occurrence 일괄 변경.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-19 08:32:56 +09:00
parent 37ca8e594e
commit 211aff1e45

View File

@@ -53,7 +53,7 @@
| `web-backend/video-lab/tests/__init__.py` (Create) | 빈 marker | tests pkg | | `web-backend/video-lab/tests/__init__.py` (Create) | 빈 marker | tests pkg |
| `web-backend/video-lab/tests/test_auth.py` (Create) | 3 tests | TDD | | `web-backend/video-lab/tests/test_auth.py` (Create) | 3 tests | TDD |
| `web-backend/video-lab/tests/test_internal_router.py` (Create) | 5 tests | TDD | | `web-backend/video-lab/tests/test_internal_router.py` (Create) | 5 tests | TDD |
| `web-backend/docker-compose.yml` (Modify) | video-lab service 추가 (port 18800) + depends_on redis | compose | | `web-backend/docker-compose.yml` (Modify) | video-lab service 추가 (port 18801) + depends_on redis | compose |
| `web-backend/nginx/default.conf` (Modify) | `/api/video/` proxy + `/media/video/` alias + `/api/internal/video/` 3-layer 차단 | routing | | `web-backend/nginx/default.conf` (Modify) | `/api/video/` proxy + `/media/video/` alias + `/api/internal/video/` 3-layer 차단 | routing |
### Phase 2 — Windows web-ai/services/ ### Phase 2 — Windows web-ai/services/
@@ -746,7 +746,7 @@ EOF
container_name: video-lab container_name: video-lab
restart: unless-stopped restart: unless-stopped
ports: ports:
- "18800:8000" - "18801:8000"
environment: environment:
- TZ=${TZ:-Asia/Seoul} - TZ=${TZ:-Asia/Seoul}
- REDIS_URL=${REDIS_URL:-redis://redis:6379} - REDIS_URL=${REDIS_URL:-redis://redis:6379}
@@ -825,7 +825,7 @@ git add docker-compose.yml nginx/default.conf
git commit -m "$(cat <<'EOF' git commit -m "$(cat <<'EOF'
feat(video-lab): docker-compose entry + nginx routing (SP-8) feat(video-lab): docker-compose entry + nginx routing (SP-8)
video-lab service: port 18800, REDIS_URL/INTERNAL_API_KEY env, video-lab service: port 18801, REDIS_URL/INTERNAL_API_KEY env,
depends_on redis, /app/data volume mount. depends_on redis, /app/data volume mount.
nginx: /api/video/ proxy + /media/video/ direct serve alias. nginx: /api/video/ proxy + /media/video/ direct serve alias.
frontend depends_on + volume mount 추가. frontend depends_on + volume mount 추가.
@@ -949,8 +949,8 @@ CMD ["python", "-m", "uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000
# NAS Redis 큐 # NAS Redis 큐
REDIS_URL=redis://192.168.45.54:6379 REDIS_URL=redis://192.168.45.54:6379
# NAS internal webhook (video-lab port 18800) # NAS internal webhook (video-lab port 18801)
NAS_BASE_URL=http://192.168.45.54:18800 NAS_BASE_URL=http://192.168.45.54:18801
INTERNAL_API_KEY=__copy_from_nas_dotenv__ INTERNAL_API_KEY=__copy_from_nas_dotenv__
# Sora 2 (OpenAI) # Sora 2 (OpenAI)
@@ -992,13 +992,13 @@ from nas_client import webhook_update_task
@pytest.fixture(autouse=True) @pytest.fixture(autouse=True)
def _env(monkeypatch): def _env(monkeypatch):
monkeypatch.setenv("NAS_BASE_URL", "http://nas-test:18800") monkeypatch.setenv("NAS_BASE_URL", "http://nas-test:18801")
monkeypatch.setenv("INTERNAL_API_KEY", "test-key") monkeypatch.setenv("INTERNAL_API_KEY", "test-key")
@respx.mock @respx.mock
def test_webhook_update_task_sends_x_internal_key(): def test_webhook_update_task_sends_x_internal_key():
route = respx.post("http://nas-test:18800/api/internal/video/update").mock( route = respx.post("http://nas-test:18801/api/internal/video/update").mock(
return_value=httpx.Response(200, json={"ok": True}) return_value=httpx.Response(200, json={"ok": True})
) )
webhook_update_task("task-1", "processing", 30, message="downloading") webhook_update_task("task-1", "processing", 30, message="downloading")
@@ -1014,7 +1014,7 @@ def test_webhook_update_task_sends_x_internal_key():
@respx.mock @respx.mock
def test_webhook_update_task_with_video_url(): def test_webhook_update_task_with_video_url():
route = respx.post("http://nas-test:18800/api/internal/video/update").mock( route = respx.post("http://nas-test:18801/api/internal/video/update").mock(
return_value=httpx.Response(200, json={"ok": True}) return_value=httpx.Response(200, json={"ok": True})
) )
webhook_update_task("task-2", "succeeded", 100, message="완료", webhook_update_task("task-2", "succeeded", 100, message="완료",
@@ -1027,7 +1027,7 @@ def test_webhook_update_task_with_video_url():
@respx.mock @respx.mock
def test_webhook_update_task_with_error(): def test_webhook_update_task_with_error():
route = respx.post("http://nas-test:18800/api/internal/video/update").mock( route = respx.post("http://nas-test:18801/api/internal/video/update").mock(
return_value=httpx.Response(200, json={"ok": True}) return_value=httpx.Response(200, json={"ok": True})
) )
webhook_update_task("task-3", "failed", 0, error="Sora API rate limit") webhook_update_task("task-3", "failed", 0, error="Sora API rate limit")
@@ -1038,7 +1038,7 @@ def test_webhook_update_task_with_error():
@respx.mock @respx.mock
def test_webhook_swallows_network_error(caplog): def test_webhook_swallows_network_error(caplog):
respx.post("http://nas-test:18800/api/internal/video/update").mock( respx.post("http://nas-test:18801/api/internal/video/update").mock(
side_effect=httpx.ConnectError("no host") side_effect=httpx.ConnectError("no host")
) )
webhook_update_task("task-5", "processing", 10) webhook_update_task("task-5", "processing", 10)
@@ -1047,7 +1047,7 @@ def test_webhook_swallows_network_error(caplog):
@respx.mock @respx.mock
def test_webhook_swallows_non_200(caplog): def test_webhook_swallows_non_200(caplog):
respx.post("http://nas-test:18800/api/internal/video/update").mock( respx.post("http://nas-test:18801/api/internal/video/update").mock(
return_value=httpx.Response(500, text="server error") return_value=httpx.Response(500, text="server error")
) )
webhook_update_task("task-6", "processing", 50) webhook_update_task("task-6", "processing", 50)
@@ -1081,7 +1081,7 @@ _TIMEOUT = 10.0
def _post(payload: Dict[str, Any]) -> None: def _post(payload: Dict[str, Any]) -> None:
nas_base_url = os.getenv("NAS_BASE_URL", "http://192.168.45.54:18800") nas_base_url = os.getenv("NAS_BASE_URL", "http://192.168.45.54:18801")
internal_api_key = os.getenv("INTERNAL_API_KEY", "") internal_api_key = os.getenv("INTERNAL_API_KEY", "")
url = f"{nas_base_url}/api/internal/video/update" url = f"{nas_base_url}/api/internal/video/update"
try: try:
@@ -2085,7 +2085,7 @@ Expected: 숫자 출력 (>=1, /health).
environment: environment:
- TZ=Asia/Seoul - TZ=Asia/Seoul
- REDIS_URL=${REDIS_URL:-redis://192.168.45.54:6379} - REDIS_URL=${REDIS_URL:-redis://192.168.45.54:6379}
- NAS_BASE_URL=${NAS_BASE_URL:-http://192.168.45.54:18800} - NAS_BASE_URL=${NAS_BASE_URL:-http://192.168.45.54:18801}
- INTERNAL_API_KEY=${INTERNAL_API_KEY:-} - INTERNAL_API_KEY=${INTERNAL_API_KEY:-}
- OPENAI_API_KEY=${OPENAI_API_KEY:-} - OPENAI_API_KEY=${OPENAI_API_KEY:-}
- GOOGLE_PROJECT_ID=${GOOGLE_PROJECT_ID:-} - GOOGLE_PROJECT_ID=${GOOGLE_PROJECT_ID:-}
@@ -2107,7 +2107,7 @@ Expected: 숫자 출력 (>=1, /health).
``` ```
**중요:** **중요:**
- `NAS_BASE_URL` 기본값 18800(video-lab) — Plan-B-Music 학습 적용: `services/.env``NAS_BASE_URL` 없어야 service-local default 적용됨. - `NAS_BASE_URL` 기본값 18801(video-lab) — Plan-B-Music 학습 적용: `services/.env``NAS_BASE_URL` 없어야 service-local default 적용됨.
- `GCP_SA_JSON_HOST_PATH` — 박재오가 host에 service account JSON 두는 경로. `.env`에서 override. - `GCP_SA_JSON_HOST_PATH` — 박재오가 host에 service account JSON 두는 경로. `.env`에서 override.
### Step 4: 커밋 ### Step 4: 커밋
@@ -2119,7 +2119,7 @@ git commit -m "$(cat <<'EOF'
feat(video-render): main.py + services/docker-compose entry (SP-7) feat(video-render): main.py + services/docker-compose entry (SP-7)
FastAPI lifespan에서 worker_loop 스폰. /health endpoint. FastAPI lifespan에서 worker_loop 스폰. /health endpoint.
docker-compose: port 18712, NAS_BASE_URL default=18800 (video-lab), docker-compose: port 18712, NAS_BASE_URL default=18801 (video-lab),
4 provider env (OPENAI_API_KEY, GOOGLE_*, PIAPI_API_KEY, SEEDANCE_API_KEY), 4 provider env (OPENAI_API_KEY, GOOGLE_*, PIAPI_API_KEY, SEEDANCE_API_KEY),
GCP service account JSON read-only mount. GCP service account JSON read-only mount.
Plan-B-Video Phase 2. Plan-B-Video Phase 2.
@@ -2267,7 +2267,7 @@ docker logs video-render -f
``` ```
... INFO Kling API 호출 중... ... INFO Kling API 호출 중...
... INFO Kling 작업 등록됨 ... INFO Kling 작업 등록됨
... POST http://192.168.45.54:18800/api/internal/video/update "HTTP/1.1 200 OK" ← 18800! ... POST http://192.168.45.54:18801/api/internal/video/update "HTTP/1.1 200 OK" ← 18801!
... INFO Kling 생성 중... (Processing) ... INFO Kling 생성 중... (Processing)
... INFO Kling 결과 다운로드 중... ... INFO Kling 결과 다운로드 중...
... INFO Kling 생성 완료 ... INFO Kling 생성 완료
@@ -2313,7 +2313,7 @@ Plan-B-Video 2026-05-19 완료. 17 task. spec §10 SP-7 4 provider로 축소 변
## 구조 ## 구조
NAS video-lab (신규 컨테이너, port 18800): NAS video-lab (신규 컨테이너, port 18801):
- POST /api/video/generate → Redis RPUSH queue:video-render - POST /api/video/generate → Redis RPUSH queue:video-render
- GET /api/video/tasks/{id} - GET /api/video/tasks/{id}
- POST /api/internal/video/update (X-Internal-Key) - POST /api/internal/video/update (X-Internal-Key)
@@ -2336,7 +2336,7 @@ Windows video-render (port 18712):
1. WSL2 mirror mode: 이미 영구 적용 (`.wslconfig networkingMode=mirrored`) 1. WSL2 mirror mode: 이미 영구 적용 (`.wslconfig networkingMode=mirrored`)
2. Redis chown 999:999: 이미 영구 적용 2. Redis chown 999:999: 이미 영구 적용
3. services/.env의 NAS_BASE_URL: 없는 상태 유지. video-render compose default 18800 적용 3. services/.env의 NAS_BASE_URL: 없는 상태 유지. video-render compose default 18801 적용
## 다음 plan ## 다음 plan