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:
@@ -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
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user