diff --git a/docs/superpowers/plans/2026-05-18-plan-b-base-redis-wsl2.md b/docs/superpowers/plans/2026-05-18-plan-b-base-redis-wsl2.md new file mode 100644 index 0000000..cb5f124 --- /dev/null +++ b/docs/superpowers/plans/2026-05-18-plan-b-base-redis-wsl2.md @@ -0,0 +1,635 @@ +# Plan-B-Base — NAS Redis 컨테이너 + Windows WSL2/Docker/Tailscale/SMB Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** 분산 아키텍처 base 인프라 셋업 — NAS에 24/7 Redis 컨테이너 신설 + Windows AI 머신에 WSL2 + Docker Engine + Tailscale + NAS SMB 마운트 구성. 후속 Plan-B-Insta/Music/Video/Infra 트랙의 prerequisite. + +**Architecture:** SP-1 (NAS Redis) = docker-compose service 추가 + deployer auto-rebuild. SP-2 (Windows) = 박재오 머신 192.168.45.59에서 직접 셋업 (WSL2 Ubuntu 22.04 + Docker Engine + Tailscale + cifs-utils로 NAS SMB 마운트). 두 SP가 모두 끝나야 후속 트랙의 worker가 NAS ↔ Windows 양방향 통신 가능. + +**Tech Stack:** Redis 7-alpine, WSL2, Ubuntu 22.04, Docker Engine 24+, Tailscale, cifs-utils (SMB 3.0). PowerShell (관리자) + bash (WSL2 내부). + +**Spec:** `web-backend/docs/superpowers/specs/2026-05-18-nas-windows-distributed-architecture-design.md` §4 SP-1·SP-2, §10 SP-1·SP-2 상세 + +--- + +## 사전 확인 사항 + +- **박재오 자격증명 필요**: NAS SMB 마운트용 user/password (Synology DSM 사용자, SMB 권한 보유) +- **Windows AI 머신 직접 접근 필요**: WSL2 설치는 관리자 PowerShell + 재부팅 1회. Claude는 별도 머신이라 명령 직접 실행 불가 — **Task 4~7은 박재오가 콘솔에서 직접 수행**. 명령어와 검증 방법 명시. +- **NAS deployer 사용자**: Gitea webhook으로 docker compose up -d 자동 실행. 새 redis 서비스도 추가 시 자동 startup. + +## File Structure + +### SP-1 — NAS 측 (Modify) + +| 파일 | 변경 | 책임 | +|------|------|------| +| `web-backend/docker-compose.yml` | `redis:` 서비스 블록 추가 | 컨테이너 정의 (image, volume, healthcheck) | + +### SP-2 — Windows 측 (Create, 박재오 머신 로컬) + +| 파일/위치 | 변경 | 책임 | +|----------|------|------| +| (Windows AI) WSL2 Ubuntu-22.04 | install | Linux 런타임 | +| WSL2 `/etc/apt/keyrings/docker.gpg` | install | Docker Engine apt key | +| WSL2 `/etc/apt/sources.list.d/docker.list` | install | Docker Engine apt source | +| (Windows AI) Tailscale | install + auth | 사설망 100.x.x.x | +| WSL2 `/etc/nas-smb-credentials` (신규) | NAS user/password | SMB 자격증명 (chmod 600) | +| WSL2 `/etc/fstab` (수정) | SMB 마운트 항목 추가 | 부팅 시 자동 마운트 | +| WSL2 `/mnt/nas` | mkdir | 마운트 포인트 | + +--- + +## Task 1: NAS docker-compose.yml에 redis 서비스 추가 + +**Files:** +- Modify: `C:/Users/jaeoh/Desktop/workspace/web-backend/docker-compose.yml` + +- [ ] **Step 1: 현재 docker-compose.yml 끝부분 확인 (deployer 위치)** + +Run: `tail -20 C:/Users/jaeoh/Desktop/workspace/web-backend/docker-compose.yml` +Expected: `deployer` 서비스가 마지막. line ~277-293 영역. + +- [ ] **Step 2: redis 서비스 블록 추가** + +`C:/Users/jaeoh/Desktop/workspace/web-backend/docker-compose.yml` 파일 **끝**에 (deployer 서비스 다음, volumes 블록 있다면 그 전에) 다음 블록 추가. 들여쓰기는 다른 서비스(`lotto:`, `stock:` 등)와 동일하게 services 아래 2칸 들여쓰기: + +```yaml + + redis: + image: redis:7-alpine + container_name: redis + restart: unless-stopped + ports: + - "6379:6379" + volumes: + - ${RUNTIME_PATH}/redis-data:/data + command: redis-server --appendonly yes --maxmemory 256mb --maxmemory-policy allkeys-lru + healthcheck: + test: ["CMD", "redis-cli", "ping"] + interval: 60s + timeout: 5s + retries: 3 + networks: + - default +``` + +**주의:** +- 파일 끝에 추가하되, 만약 `networks:` / `volumes:` top-level 블록이 services 다음에 있다면 그 블록들 **앞에** 삽입 +- 첫 줄에 빈 줄 1개 두기 (deployer와 분리) +- `${RUNTIME_PATH}` 환경변수는 다른 서비스에서도 사용 중. 자동 적용됨 + +- [ ] **Step 3: yaml 문법 검증** + +Run: +```bash +python -c "import yaml; yaml.safe_load(open('C:/Users/jaeoh/Desktop/workspace/web-backend/docker-compose.yml'))" && echo "yaml OK" +``` +Expected: `yaml OK` + +만약 실패하면 indent 또는 trailing space 확인. + +- [ ] **Step 4: redis 서비스가 services dict에 들어갔는지 확인** + +Run: +```bash +python -c "import yaml; d=yaml.safe_load(open('C:/Users/jaeoh/Desktop/workspace/web-backend/docker-compose.yml')); print(sorted(d['services'].keys()))" +``` +Expected: 리스트에 `'redis'` 포함. 다른 서비스(`lotto`, `stock`, `music-lab`, `insta-lab`, `realestate-lab`, `agent-office`, `personal`, `packs-lab`, `travel-proxy`, `frontend`, `deployer`)도 모두 그대로. + +- [ ] **Step 5: 커밋** + +```bash +cd C:/Users/jaeoh/Desktop/workspace/web-backend +git add docker-compose.yml +git commit -m "$(cat <<'EOF' +feat(infra): add redis container as 24/7 queue + cache base (SP-1) + +redis:7-alpine, 256MB maxmemory, AOF appendonly ON, allkeys-lru. +docker volume ${RUNTIME_PATH}/redis-data로 영속화. +Plan-B 후속 트랙(insta-render/music-render/video-render Windows +워커)의 BLPOP 큐 + NAS↔Windows pub/sub의 base. + +Co-Authored-By: Claude Opus 4.7 (1M context) +EOF +)" +``` + +- [ ] **Step 6: push (Gitea webhook → NAS deployer 자동 적용)** + +```bash +cd C:/Users/jaeoh/Desktop/workspace/web-backend +git push origin main +``` + +자격증명 prompt 시 입력. 1회 실패 시 1회 재시도 패턴. + +Expected: push 성공. NAS deployer가 webhook 수신 → `git pull` → `docker compose up -d redis` 자동 실행. + +--- + +## Task 2: NAS Redis 컨테이너 헬스 확인 + +**Files:** 없음 (NAS 검증) + +- [ ] **Step 1: deployer 완료까지 대기 (통상 30초~2분)** + +Run (Windows 로컬에서): +```bash +for i in 1 2 3 4 5 6 7 8 9 10; do + code=$(curl -s -o /dev/null -w "%{http_code}" https://gahusb.synology.me/api/stock/news -m 5) + echo "[try $i] HTTP $code" + if [ "$code" = "200" ]; then break; fi + sleep 15 +done +``` + +Expected: HTTP 200 응답 — NAS 컨테이너 안정 상태. redis 컨테이너는 별도 endpoint 없으나 deployer가 build 완료했음을 시사. + +- [ ] **Step 2: NAS에서 redis 컨테이너 확인 (박재오 SSH)** + +NAS bash: +```bash +ssh -p 22 박재오@gahusb.synology.me +cd /volume1/docker/webpage +docker compose ps redis +``` + +또는 한 번에: +```bash +ssh -p 22 박재오@gahusb.synology.me "cd /volume1/docker/webpage && docker compose ps redis && docker exec redis redis-cli PING" +``` + +Expected: +- `docker compose ps redis` → `redis ... healthy` 또는 `Up X seconds (health: starting)` 후 곧 healthy +- `redis-cli PING` → `PONG` + +만약 `docker compose ps`에 redis가 안 보이면: +```bash +cd /volume1/docker/webpage && docker compose up -d redis +``` + +수동 실행해서 startup 확인. + +- [ ] **Step 3: redis-data 볼륨 생성 확인 (Z: drive로)** + +Run (Windows): +```powershell +Test-Path "Z:\webpage\redis-data" +``` + +또는 NAS bash: +```bash +ls -la /volume1/docker/webpage/redis-data/ +``` + +Expected: 디렉토리 존재. 그 안에 `appendonlydir/` 또는 `dump.rdb` 등의 redis 데이터 파일. + +- [ ] **Step 4: AOF append-only 작동 확인 (선택, 데이터 영속성 검증)** + +```bash +ssh -p 22 박재오@gahusb.synology.me 'docker exec redis redis-cli SET test_key "hello"' +ssh -p 22 박재오@gahusb.synology.me 'docker exec redis redis-cli RESTART' # 또는 docker restart +# 잠시 대기 +ssh -p 22 박재오@gahusb.synology.me 'docker exec redis redis-cli GET test_key' +``` + +Expected: `"hello"` — 재시작 후에도 값 유지 (AOF 영속화 작동). + +테스트 후 정리: `docker exec redis redis-cli DEL test_key` + +--- + +## Task 3: Windows AI에 WSL2 + Ubuntu 22.04 설치 + +**Files:** 없음 (Windows AI 머신 192.168.45.59에서 박재오 직접 실행) + +**전제:** Windows 10 build 19041+ 또는 Windows 11. 박재오 9800X3D 머신 충족. + +- [ ] **Step 1: 관리자 PowerShell 실행** + +박재오 Windows AI 머신에서 시작 메뉴 → "PowerShell" 우클릭 → "관리자 권한으로 실행". + +- [ ] **Step 2: WSL2 + Ubuntu 22.04 설치** + +```powershell +wsl --install -d Ubuntu-22.04 +``` + +Expected: 다운로드 progress + "Ubuntu-22.04 has been installed". **재부팅 필요할 수 있음.** + +- [ ] **Step 3: 재부팅 (필요 시)** + +설치 완료 메시지에 "재시작이 필요합니다"가 보이면 재부팅. 자동 재부팅 안 됨. + +- [ ] **Step 4: Ubuntu 초기 설정 (재부팅 후 자동 실행 또는 시작 메뉴에서 "Ubuntu" 클릭)** + +새 콘솔이 열리고 다음 입력 요청됨: +- 새 UNIX username: `jaeoh` 또는 박재오 선호 username (이후 모든 sudo에 사용) +- 비밀번호: 박재오가 정하는 값. 잘 기억할 것. + +Expected: `jaeoh@:~$` 프롬프트 표시 → WSL2 진입 성공. + +- [ ] **Step 5: WSL 버전 확인** + +WSL2 내부에서 PowerShell로 잠시 돌아와서: +```powershell +wsl -l -v +``` + +Expected: +``` + NAME STATE VERSION +* Ubuntu-22.04 Running 2 +``` + +VERSION=2 확인. 만약 1이면: +```powershell +wsl --set-version Ubuntu-22.04 2 +``` + +- [ ] **Step 6: WSL2 안 진입 (이후 작업)** + +```powershell +wsl -d Ubuntu-22.04 +``` + +이후 Task 4~7은 모두 WSL2 안 bash에서 실행. + +--- + +## Task 4: WSL2 안 Docker Engine 설치 (Docker Desktop 사용 X) + +**Files:** (WSL2 내부) `/etc/apt/keyrings/docker.gpg`, `/etc/apt/sources.list.d/docker.list` + +**위치:** WSL2 Ubuntu-22.04 bash 프롬프트. + +- [ ] **Step 1: 패키지 인덱스 + 기본 의존성 설치** + +```bash +sudo apt update +sudo apt install -y ca-certificates curl gnupg lsb-release +``` + +Expected: 에러 없이 완료. + +- [ ] **Step 2: Docker apt key 등록** + +```bash +sudo install -m 0755 -d /etc/apt/keyrings +curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg +sudo chmod a+r /etc/apt/keyrings/docker.gpg +``` + +Expected: 에러 없이 완료. `/etc/apt/keyrings/docker.gpg` 파일 생성. + +- [ ] **Step 3: Docker repository 추가** + +```bash +echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | \ + sudo tee /etc/apt/sources.list.d/docker.list > /dev/null +sudo apt update +``` + +Expected: `Hit:N https://download.docker.com/linux/ubuntu jammy InRelease` 라인 보임. + +- [ ] **Step 4: Docker Engine + Compose 설치** + +```bash +sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin +``` + +Expected: 설치 완료. 용량 ~400MB. + +- [ ] **Step 5: 현재 사용자를 docker 그룹에 추가** + +```bash +sudo usermod -aG docker $USER +``` + +Expected: 출력 없음 (정상). **새 셸 열어야 적용됨.** + +- [ ] **Step 6: Docker 서비스 시작 + 자동 시작 설정** + +```bash +sudo systemctl enable docker +sudo systemctl start docker +sudo systemctl status docker | head -5 +``` + +Expected: `Active: active (running)`. + +만약 `systemctl: command not found` 또는 systemd 미지원 시: +```bash +sudo service docker start +``` + +WSL2 systemd 활성화는 `/etc/wsl.conf`에 `[boot]\nsystemd=true` 추가 후 PowerShell에서 `wsl --shutdown` 후 재진입. (Ubuntu-22.04는 보통 기본 활성) + +- [ ] **Step 7: docker 명령 동작 확인** + +새 셸로 (PowerShell에서 다시 `wsl -d Ubuntu-22.04` 또는 현재 셸 종료 후 재진입): + +```bash +docker version +docker run --rm hello-world +``` + +Expected: +- `docker version`: Client + Server 둘 다 표시 (Server에 Engine version) +- `hello-world`: "Hello from Docker!" 출력 + +--- + +## Task 5: WSL2 안 Tailscale 설치 + 가입 + +**Files:** Tailscale은 systemd service 등록 (별도 path 신경 안 써도 됨) + +- [ ] **Step 1: Tailscale 설치** + +WSL2 bash: +```bash +curl -fsSL https://tailscale.com/install.sh | sh +``` + +Expected: 패키지 install 후 "Installation complete!" 출력. + +- [ ] **Step 2: Tailscale 가입 (브라우저 OAuth)** + +```bash +sudo tailscale up +``` + +Expected: `To authenticate, visit: https://login.tailscale.com/a/...` URL 표시. + +브라우저에서 그 URL 열기 → Google/Microsoft/GitHub 등으로 로그인 → 박재오 Tailscale 네트워크에 가입 (기존 계정 없으면 생성). + +- [ ] **Step 3: 가입 완료 확인** + +```bash +tailscale status +``` + +Expected: +- 첫 줄에 Windows AI 머신의 100.x.x.x IP 표시 +- (이미 가입된) NAS도 같은 네트워크에 있다면 NAS의 100.x.x.x IP도 표시 + +- [ ] **Step 4: NAS와 Tailscale ping (양방향 사설망 확인)** + +NAS의 Tailscale IP를 `tailscale status` 출력에서 찾아 (예: `100.64.0.10`): +```bash +tailscale ping 100.64.0.10 +``` + +Expected: `pong from ` (직접 LAN 또는 DERP 중계). 만약 NAS가 Tailscale 미가입이면 별도로 NAS DSM Tailscale 패키지 셋업 필요 — 이는 박재오 결정 사항이라 plan 외. + +> **참고:** Tailscale은 spec §3 sense의 사설망 layer 보조. LAN(192.168.45.0/24) 안에서만 작업한다면 Tailscale 없이도 작동. 외부 출장 등에서 NAS↔Windows 통신을 위해 권장. + +--- + +## Task 6: WSL2 안 NAS SMB 자격증명 파일 + 마운트 포인트 준비 + +**Files:** `/etc/nas-smb-credentials`, `/mnt/nas` + +- [ ] **Step 1: cifs-utils 설치 (SMB 마운트 패키지)** + +```bash +sudo apt install -y cifs-utils +``` + +Expected: 설치 완료. + +- [ ] **Step 2: SMB 자격증명 파일 생성** + +박재오 NAS 계정의 username과 password를 사용. 파일 위치는 system-wide `/etc/`. + +```bash +sudo bash -c 'cat > /etc/nas-smb-credentials < **share name 변형:** 박재오 NAS는 메모리(`feedback_nas_deploy_paths.md`)에 따르면 SMB 매핑이 `/volume1/docker/`를 share `docker`로 노출. 만약 다른 share name(예: `webpage`)이라면 그것으로 교체. + +- [ ] **Step 2: 마운트 결과 확인** + +```bash +ls /mnt/nas/ +``` + +Expected: `webpage/` 디렉토리 + 다른 share 내 디렉토리 보임. + +```bash +ls /mnt/nas/webpage/data/ +``` + +Expected: `insta/`, `music/` 등 후속 트랙에서 사용할 디렉토리. 없으면 후속 트랙에서 생성됨. + +- [ ] **Step 3: 마운트 해제 후 fstab으로 자동화** + +```bash +sudo umount /mnt/nas +``` + +Expected: 출력 없음. + +`/etc/fstab` 끝에 다음 라인 추가: +```bash +sudo bash -c 'cat >> /etc/fstab <&1 | head -5 +mount | grep cifs +``` + +Expected: +- `mount -a` 출력 없음 (성공) +- `ls /mnt/nas/webpage/data/` 디렉토리 내용 표시 +- `mount | grep cifs` 라인에 마운트 정보 보임 + +- [ ] **Step 5: WSL2 재시작 시 자동 마운트 확인** + +PowerShell에서 (관리자 권한 불필요): +```powershell +wsl --shutdown +wsl -d Ubuntu-22.04 +``` + +WSL2 다시 진입 후: +```bash +ls /mnt/nas/webpage/data/ +``` + +Expected: 정상 디렉토리 목록. 자동 마운트 성공. + +만약 마운트 안 됨: +- `dmesg | grep cifs` 확인 +- `nofail` 때문에 boot은 통과했으나 마운트 실패 가능. 수동 `sudo mount -a` 후 동작 확인 → fstab syntax 재검토 + +--- + +## Task 8: 통합 검증 — base 인프라 동작 확인 + +**Files:** 없음 (검증) + +- [ ] **Step 1: NAS Redis 외부 ping (Windows 로컬에서)** + +```powershell +# Windows AI 또는 박재오 PC에서 +Test-NetConnection -ComputerName 192.168.45.54 -Port 6379 +``` + +Expected: `TcpTestSucceeded : True` + +> 외부 6379 노출은 LAN 한정. 가능하면 NAS firewall (DSM Control Panel)에서 6379 LAN-only allowed로 한정 권장. (이번 plan에 포함 안 됨, 별도 사용자 작업) + +- [ ] **Step 2: WSL2에서 NAS Redis 접속** + +WSL2 bash: +```bash +docker run --rm redis:7-alpine redis-cli -h 192.168.45.54 PING +``` + +또는 Tailscale 사용 시: +```bash +docker run --rm redis:7-alpine redis-cli -h PING +``` + +Expected: `PONG` + +- [ ] **Step 3: NAS volume 쓰기 테스트 (Windows→NAS 양방향)** + +WSL2 bash: +```bash +echo "Plan-B-Base test $(date)" | sudo tee /mnt/nas/webpage/data/.plan-b-test.txt +cat /mnt/nas/webpage/data/.plan-b-test.txt +sudo rm /mnt/nas/webpage/data/.plan-b-test.txt +``` + +Expected: +- `tee` 출력에 같은 내용 + 파일 생성됨 +- `cat` 으로 확인 성공 +- 파일 삭제 성공 + +`sudo` 필요 시 chmod로 uid 1000 쓰기 권한 확인. 또는 mount option `uid=1000,gid=1000` 적용 후 일반 사용자도 쓰기 가능. 만약 안 되면 NAS DSM에서 SMB user의 write 권한 확인. + +- [ ] **Step 4: WSL2 Docker로 hello-world 한 번 더 (재진입 후 상태 확인)** + +```bash +docker run --rm hello-world +``` + +Expected: "Hello from Docker!" + +- [ ] **Step 5: 모든 검증 완료 후 보고 — 후속 트랙으로 진입 가능 상태** + +다음 plan(Plan-B-Insta 등)이 가정하는 상태: +- ✅ NAS `redis:6379` PING/PONG 성공 +- ✅ Windows WSL2 Ubuntu-22.04 작동 + Docker Engine 실행 +- ✅ `/mnt/nas/webpage/data/` 양방향 read·write 성공 +- ✅ Tailscale 가입 (선택, 외부 출장 시 필요) + +--- + +## Self-Review + +### Spec 커버리지 + +| Spec 요구사항 | 구현 Task | +|---------------|-----------| +| §4 SP-1: NAS Redis 컨테이너 | Task 1 (compose 추가) + Task 2 (헬스 검증) | +| §10 SP-1: redis:7-alpine + 256MB + AOF + healthcheck | Task 1 Step 2 | +| §4 SP-2: Windows WSL2 + Docker Engine | Task 3 (WSL2) + Task 4 (Docker) | +| §10 SP-2: Tailscale | Task 5 | +| §10 SP-2: NAS SMB mount `/mnt/nas` | Task 6 (자격증명·포인트) + Task 7 (마운트+fstab) | +| §10 SP-2: 검증 (docker ps, tailscale status, ls /mnt/nas) | Task 8 | +| §6 Redis 키 컨벤션 사용 가능 | Task 2 Step 2 (PING) — 컨벤션 자체는 후속 트랙에서 RPUSH로 시작 | + +### Placeholder 스캔 + +- TBD/TODO 없음 ✓ +- 모든 명령어가 그대로 실행 가능한 형태 ✓ +- 한 가지 예외: Task 6 Step 2 — `박재오NAS사용자명/박재오NAS비밀번호`는 사용자 자격증명이라 placeholder가 의도된 것. 실행 전 교체 명시 ✓ +- Task 5 Step 4 — ``는 `tailscale status` 출력에서 박재오가 보고 입력. 사용자 환경에서만 결정 가능, plan에 명시 ✓ + +### Type/이름 consistency + +- `redis` 서비스명 (Task 1, 2, 8 모두 동일) ✓ +- `/mnt/nas` 마운트 포인트 (Task 6, 7, 8 모두 동일) ✓ +- `/etc/nas-smb-credentials` 자격증명 파일 (Task 6, 7 동일) ✓ +- share name `docker` (Task 7 Step 1, fstab 동일) ✓ +- Ubuntu-22.04 (Task 3, 4 동일) ✓ + +### 위험·주의 + +| 위험 | 완화 | +|------|------| +| Windows 재부팅 시 WSL2 자동 시작 안 함 | 향후 Plan-B-Infra(SP-9)에서 NSSM으로 자동 시작 | +| WSL2 systemd 미지원 시 docker service 자동 시작 안 함 | Task 4 Step 6의 fallback `sudo service docker start` 또는 `/etc/wsl.conf` 수정 | +| SMB 마운트 자격증명 노출 | `/etc/nas-smb-credentials` chmod 600 + root:root | +| NAS firewall에서 6379 외부 노출 | 권장: LAN(192.168.45.0/24) only allow. 본 plan 외 (DSM 수동) | +| Tailscale 미가입 시 NAS↔Windows 외부 통신 불가 | LAN 내에선 작동. 외부 출장 시 필요할 때만 가입 | +| /mnt/nas 쓰기 권한 부족 | uid=1000 mount option + NAS DSM에서 SMB user의 share write 권한 확인 | + +--- + +## 완료 후 다음 단계 + +Plan-B-Base 완료 후 spec §14 권장 순서대로: + +1. **Plan-B-Insta** — SP-3 (insta-render Windows worker) + SP-4 (NAS insta-lab 분할) +2. **Plan-B-Music** — SP-5 + SP-6 +3. **Plan-B-Video** — SP-7 + SP-8 +4. **Plan-B-Infra** — SP-9 (NSSM 자동 시작) + SP-10 (task-watcher) + +각 후속 plan은 본 plan이 제공한 base 인프라(Redis + WSL2/Docker + /mnt/nas)에 의존.