# visionA Dev 環境設置(一鍵 docker compose) > 對應 `.autoflow/04-architecture/oidc-tdd.md` §12 + Phase 0.6 OD1 任務 > > 適用對象:剛 clone 下來、第一次想跑起 visionA + Member Center OIDC flow 的開發者 > > 預期時間:第一次 build ~10 分鐘;之後重啟 < 30 秒 --- ## TL;DR ```bash # 1. 確認 ../member_center 與本 repo 平行擺放 # visionA/ 與 member_center/ 在同一個 parent dir ls -d ../member_center # 應該存在 # 2. 複製 env 範本 cp .env.dev.example .env.dev # 視情況編輯(多半預設值即可) # 3. 起所有 service docker compose -f docker-compose.dev.yml --env-file .env.dev up -d --build # 第一次:~10 分鐘(dotnet 8 build + go build) # 之後 :< 30 秒 # 4. 等所有 service healthy(< 1 分鐘) docker compose -f docker-compose.dev.yml ps # 全部要看到 (healthy) # 5. 註冊 visionA OAuth client(手動,~2 分鐘,見下方第 5 節) # 6. 把 client_id 寫進 .env.dev → 重啟 api docker compose -f docker-compose.dev.yml --env-file .env.dev up -d visiona-api # 7. 另開 terminal 跑 frontend cd visionA-frontend && pnpm install && pnpm dev # 8. 開 http://localhost:3000 → 點登入 → 完成 OIDC flow ``` --- ## 1. 前置需求 | 工具 | 版本 | 安裝 | |------|------|------| | Docker Desktop | 最新 | https://docker.com/products/docker-desktop | | Docker Compose | v2.30+ | 通常隨 Docker Desktop | | pnpm | 9+ | `brew install pnpm` 或 `npm i -g pnpm` | | jq | 任意 | `brew install jq`(auto seed script 用)| 確認: ```bash docker --version # >= 24.x docker compose version # >= v2.30 node --version # >= 18 LTS(pnpm 需要) ``` --- ## 2. 目錄結構假設 ``` parent/ ├── visionA/ ← 你在這(this repo) │ ├── docker-compose.dev.yml │ ├── .env.dev.example │ ├── visionA-backend/ │ └── visionA-frontend/ └── member_center/ ← 平行擺放(用 git clone) └── src/MemberCenter.Api/Dockerfile ``` 如果你的 `member_center` 在別處: ```bash # 編輯 .env.dev MEMBER_CENTER_PATH=/Users/me/code/member_center ``` --- ## 3. 服務拓撲 | Service | Container | Host port | 用途 | |---------|-----------|-----------|------| | postgres | visiona-dev-postgres | 5432 | MC 的資料庫 | | member-center | visiona-dev-member-center | **5050** | MC API(OIDC IdP) | | member-center-web | visiona-dev-member-center-web | **5060** | MC 後台管理 UI | | member-center-init | visiona-dev-member-center-init | – | 一次性 job:DB migrate + 建 admin | | visiona-proxy | visiona-dev-proxy | 3800 (tunnel WS) | local agent 連入 | | visiona-api | visiona-dev-api | **3721** | visionA 後端 API | | frontend | (host pnpm dev) | **3000** | Next.js dev server | --- ## 4. 起環境步驟 ### 4.1 第一次(含 build) ```bash cd /path/to/visionA cp .env.dev.example .env.dev # Build 所有 image(耐心等 ~10 分鐘) docker compose -f docker-compose.dev.yml --env-file .env.dev build # 起來 docker compose -f docker-compose.dev.yml --env-file .env.dev up -d ``` ### 4.2 確認都健康 ```bash docker compose -f docker-compose.dev.yml ps ``` 預期看到: ``` NAME STATUS visiona-dev-postgres Up (healthy) visiona-dev-member-center Up (healthy) visiona-dev-member-center-web Up visiona-dev-member-center-init Exited (0) ← 正常,一次性 job visiona-dev-proxy Up (healthy) visiona-dev-api Up (healthy) ``` 如果 visiona-api 卡在 unhealthy / restarting → 看 §7 故障排除。 ### 4.3 確認 OIDC discovery 通 ```bash curl -s http://localhost:5050/.well-known/openid-configuration | head -c 200 ``` 應該看到 JSON,含 `"issuer":"http://localhost:5050/"`。 --- ## 5. 註冊 visionA OAuth Client(**必做**) 第一次 build 起來時 visionA-api 還沒有合法的 OIDC client_id(env 是 `CHANGE_ME`),點登入會 fail。 需先在 MC 註冊一個 OAuth client。 ### 5.1 走 MC Web UI(推薦,目前唯一可行路徑) > 為什麼不用 script 自動化:MC password grant 在 Identity user 上有個 bug(`mandatory subject claim was missing`),無法用 admin 帳密拿 token 呼叫 admin API。已於 MC 端開 issue。 **步驟**: 1. **開 MC 後台**:http://localhost:5060 2. **登入**: - Email: `admin@visiona.local` - Password: `Admin12345!` - (這組密碼來自 `.env.dev` 的 `MC_ADMIN_EMAIL` / `MC_ADMIN_PASSWORD`) 3. **建 Tenant**: - 進「Tenants」頁 - 點「Create」/「新增」 - Name: `visionA` - Domains: `visiona.cloud, localhost` - Status: `active` - 送出 4. **建 OAuth Client**: - 進「OAuth Clients」頁 - 點「Create」 - Tenant: 選剛剛建的 `visionA` - Name: `visionA Cloud` - Client Type: `confidential` - Usage: `webhook_outbound` - (MC 目前沒有 `web_app` 類型,雛形借用 `webhook_outbound`,是唯一允許 redirect_uris 的非 client-credentials usage;對應 oidc-tdd §11.2) - Redirect URIs: `http://localhost:3721/api/auth/callback` - 送出 5. **取 client_id / client_secret**: - 建立成功後,畫面會顯示 client_id(GUID 格式) - **client_secret**:MC 應該會顯示一次(如果 UI 沒給 secret,是 MC 的另一個 limitation;雛形階段可改成 `client_type=public` 不帶 secret) ### 5.2 把 client_id / secret 寫進 .env.dev ```bash # 編輯 .env.dev VISIONA_OIDC_CLIENT_ID=<剛剛拿到的 client_id GUID> VISIONA_OIDC_CLIENT_SECRET=<剛剛拿到的 secret,沒拿到就留空> ``` ### 5.3 重啟 visiona-api ```bash docker compose -f docker-compose.dev.yml --env-file .env.dev up -d visiona-api docker compose -f docker-compose.dev.yml logs visiona-api --tail 5 ``` 預期看到: ``` {"msg":"OIDC initialized","issuer":"http://localhost:5050/","client_id":"<你的 guid>","redirect_url":"http://localhost:3721/api/auth/callback"} {"msg":"api-server listening","addr":"0.0.0.0:3721"} ``` --- ## 6. 跑 Frontend + 完成 demo flow ```bash # 另開 terminal cd visionA-frontend pnpm install pnpm dev # → http://localhost:3000 ``` **Demo flow**: 1. 開 http://localhost:3000 2. 點「登入」按鈕(會跳到 backend /api/auth/login) 3. backend 302 到 MC `/oauth/authorize?...` 4. MC 顯示登入頁 → 用 `admin@visiona.local / Admin12345!` 登入 (或先在 MC Web UI 建 demo user 帳號) 5. 同意授權後 redirect 回 `http://localhost:3721/api/auth/callback?code=...` 6. backend 換 token、驗 id_token、建 session 7. 302 回 frontend dashboard,cookie 已設 --- ## 7. 故障排除 ### 7.1 visiona-api 卡 unhealthy / restarting 最常見:OIDC 沒設好 → backend crash loop。 ```bash docker compose -f docker-compose.dev.yml logs visiona-api --tail 30 ``` | 錯誤關鍵字 | 原因 | 解法 | |----------|------|------| | `discovery fetch failed` | MC 還沒起來 | 等 30 秒再看;或 `docker compose ps member-center` 確認 healthy | | `issuer URL ... did not match` | issuer trailing slash 不一致 | 確認 `.env.dev` 的 `VISIONA_OIDC_ISSUER_URL=http://localhost:5050/`(**斜線結尾**) | | `IssuerURL invalid` / `ClientID required` | env 沒帶到 | 確認啟動時用 `--env-file .env.dev` | | `connection refused` | container 內找不到 host | 不應該發生(compose 已加 `extra_hosts: localhost:host-gateway`) | ### 7.2 port 衝突 | port | 衝突來源 | 解法 | |------|---------|-----| | 3721 | 本機跑著 visionA-local(local-tool 那一邊) | 暫停 local-tool,或在 `.env.dev` 把 `VISIONA_API_PORT=3722` 改一下、frontend 端 base URL 也改 | | 5432 | 本機 PostgreSQL | `.env.dev` 改 `POSTGRES_PORT=5433` | | 5050 | 已被占用 | `.env.dev` 改 `MEMBER_CENTER_PORT=5051` + `VISIONA_OIDC_ISSUER_URL=http://localhost:5051/` | | 5060 | 已被占用 | `.env.dev` 改 `MEMBER_CENTER_WEB_PORT=5061` | ### 7.3 MC build 失敗 / Dockerfile 錯誤 如果 `MEMBER_CENTER_PATH` 路徑不對: ```bash # 確認 path ls -d ${MEMBER_CENTER_PATH:-../member_center} # 用絕對路徑 MEMBER_CENTER_PATH=/Users/you/code/member_center docker compose -f docker-compose.dev.yml build member-center ``` ### 7.4 MC password grant 500 error ``` System.InvalidOperationException: The specified principal was rejected because the mandatory subject claim was missing. ``` **已知 MC bug**(Identity user 缺 sub claim)。Phase 0.6 雛形請走 admin Web UI 註冊 OAuth client,不用 password flow。 ### 7.5 一鍵完全重置 ```bash docker compose -f docker-compose.dev.yml down -v # -v 會刪除 volumes(包括 postgres data + visiona storage) # 下次 up -d 時會重新跑 member-center-init 建 admin 帳號 ``` ### 7.6 OIDC flow 執行階段問題 > 這節對應跑完 §5(OAuth client 註冊)後,實際走 login flow 卻卡住的情境。 > 詳細逐步驗證見 `docs/SMOKE-TEST.md`,這裡只列高頻錯誤對照表。 | 症狀 | 通常原因 | 解法 | |------|---------|------| | 點登入後 callback 噴 `state_mismatch` | `visiona_pending_sid` cookie 沒帶過去 | 確認 `VISIONA_FRONTEND_URL` 與 frontend 真實 URL 一致;不要設 cookie `Domain`;瀏覽器 DevTools → Application → Cookies 看 cookie 是否存進去 | | callback 噴 `pending_session_not_found` | pending 過 10 分鐘 TTL,或 backend 重啟清掉 in-memory store | 重新點登入;雛形 in-memory 重啟即清空 | | callback 噴 `id_token_invalid` | iss / aud / nonce 對不上,或 JWKS cache 過時 | 看 backend log `detail` 欄;嘗試 `docker compose restart visiona-api` 清 JWKS cache | | callback 噴 `token_exchange_failed` | client_secret 不對 / redirect_uri 與 MC 設定不一致 | 比對 `.env.dev` 與 MC admin UI 中 client 設定 | | `GET /api/auth/me` 永遠 401 | cookie 沒帶 / cookie 簽章對不上 / session 過期 | DevTools 看 request 是否帶 `Cookie:` header;frontend `fetch` 必須 `credentials: 'include'`;`VISIONA_SESSION_SECRET` 改過會讓舊 cookie 失效 → 清 cookie 重登 | | Pairing token 綁的 user_id 是 `demo-user` 而非 OIDC sub | `VISIONA_AUTH_TYPE` 還是 `static`,或 OB5 沒拔乾淨 | 確認 `.env.dev` 是 `VISIONA_AUTH_TYPE=oidc` 且重啟過 visiona-api;`docker compose logs visiona-api \| grep -i pairing` 看實際 binding | | 登出後仍能訪問受保護頁面 | `Set-Cookie: visiona_session=; Max-Age=0` 沒設對,或 frontend route guard 沒擋 | DevTools 確認 cookie 真的被清;訪 `/api/auth/me` 不帶 cookie 應 401 | --- ## 8. 常用命令 ```bash # 看所有 service log docker compose -f docker-compose.dev.yml logs -f # 看單一 service docker compose -f docker-compose.dev.yml logs -f visiona-api # 重啟單一 service docker compose -f docker-compose.dev.yml restart visiona-api # 進 postgres docker exec -it visiona-dev-postgres psql -U postgres -d membercenter # 看 OpenIddict apps docker exec -it visiona-dev-postgres psql -U postgres -d membercenter \ -c 'SELECT "ClientId", "DisplayName", "ClientType", "RedirectUris" FROM "OpenIddictApplications";' # 完全停止(保留 data) docker compose -f docker-compose.dev.yml down # 完全停止 + 清乾淨 docker compose -f docker-compose.dev.yml down -v # 重 build(程式碼變了) docker compose -f docker-compose.dev.yml up -d --build visiona-api ``` --- ## 9. 已知 limitation(Phase 0.6 雛形) | Limitation | 影響 | 後續處理 | |-----------|------|---------| | MC admin API 無法注入 client_secret | 必須走 Web UI 建 OAuth client | MC 端開 issue:admin API 補 ClientSecret 欄位 | | MC password grant 缺 sub claim → 500 | seed script 跑不通 | MC 端開 issue:TokenController 補 Subject claim mapping | | MC 只有 `webhook_outbound` 支援 redirect_uris | usage 命名語意不對(雛形借用) | MC 端開 issue:新增 `web_app` usage(對應 oidc-tdd §11.2) | | host visiona-local 占 3721 → 跟 docker compose 衝突 | 兩個只能擇一跑 | 預期;docs 已說明 | --- ## 10. 相關文件 - `.autoflow/04-architecture/oidc-tdd.md`:OIDC 接入 TDD(§11 MC 端、§12 docker-compose、§13 env) - `.autoflow/04-architecture/adr/adr-010-oidc-bff.md`:BFF 架構決策 - `visionA-backend/.env.example`:visionA-backend 完整 env 列表 - `~/member_center/docs/INSTALL.md`:MC installer 詳細用法