############################################################################### # Task Scheduler 環境變數範本(Phase 0.8b 收斂版) # # 三類分區(依顯示順序): # 1. 必填(production 必須設真實值)— 缺漏會 fail-fast,process exit code 1 # 2. 可選(合理預設)— 不設會用程式內 default # 3. 開發 placeholder — 用 RFC 2606 `.invalid` TLD 確保不會誤連到真實服務 # # 部署準則: # - 切勿 commit `.env`(已在 .gitignore;歷史 commit 待 D7 處理) # - production 用 secret manager(Vault / AWS Secrets Manager),不要直接設環境變數 # - 任何含 `REPLACE-ME` 字樣或 `.invalid` TLD 的值,部署前必須替換 # # Phase 0.8b A4 砍除(visionA → converter 改用 API key、不再走 OAuth/JWKS): # - MEMBER_CENTER_ISSUER / MEMBER_CENTER_JWKS_URL # - KNERON_CONVERTER_AUDIENCE / CONVERTER_TENANT_ID # - CONVERTER_SCOPE_WRITE / CONVERTER_SCOPE_READ # - JWKS_CACHE_MAX_AGE_MS / JWKS_COOLDOWN_MS / JWT_CLOCK_TOLERANCE_SEC # converter → FAA OAuth client 鏈條保留不動(MEMBER_CENTER_TOKEN_URL / # KNERON_CONVERTER_CLIENT_* / FILE_ACCESS_AGENT_* / OAUTH_TOKEN_*),那條與 # visionA → converter 對外認證無關。 ############################################################################### # ============================================================================= # 1. 應用基本設定 # ============================================================================= # 監聽 port(必填,但有合理預設) PORT=4000 # Node 環境(development / staging / production) # - production 時 FILE_ACCESS_AGENT_BASE_URL 強制 HTTPS NODE_ENV=development # Log 等級(debug / info / warn / error) LOG_LEVEL=info # ============================================================================= # 2. Redis(必填) # ============================================================================= # - 不設會用 default,但實際部署需指向真實 Redis # - 帶 password:redis://:password@host:6379 REDIS_URL=redis://localhost:6379 # ============================================================================= # 3. Job 資料目錄(local storage 用) # ============================================================================= # - STORAGE_BACKEND=local 時,此目錄為 worker / scheduler 共用 volume # - STORAGE_BACKEND=minio 時,仍會用此目錄存暫時檔(如 health check) JOB_DATA_DIR=/data/jobs # ============================================================================= # 4. CORS(必填) # ============================================================================= FRONTEND_URL=http://localhost:3000 # ============================================================================= # 5. Storage backend(必填) # ============================================================================= # - "local":用 JOB_DATA_DIR 共用 volume(單機開發 / docker-compose) # - "minio":用 MinIO / S3-compatible(production 推薦;POST /api/v1/jobs 必須 minio) STORAGE_BACKEND=local # ============================================================================= # 6. MinIO / S3 設定 # ============================================================================= # - STORAGE_BACKEND=minio 時為必填 # - STORAGE_BACKEND=local 時可留空 # - 注意:production 不要把真實 secret 寫在這裡,改用 secret manager MINIO_ENDPOINT_URL=http://192.168.0.130:9000 MINIO_BUCKET=convertet-working-space MINIO_ACCESS_KEY=convuser MINIO_SECRET_KEY=REPLACE-ME-IN-PRODUCTION MINIO_REGION=us-east-1 # bucket lifecycle(天)— 上傳後 N 天自動清,避免 orphan 累積 MINIO_LIFECYCLE_DAYS=7 # ============================================================================= # 7. visionA → converter API Key 認證(Phase 0.8b 必填) # ============================================================================= # # visionA-backend ↔ converter 為 1:1 internal trust,採 pre-shared API key # 取代 OAuth client_credentials(ADR-015 v2.1)。converter 端只需這一把 key, # 無 issuer / audience / scope / tenant 概念。 # # 產生: # $ openssl rand -hex 32 # # 輸出 64 hex chars(128 bits 熵),對齊 NIST SP 800-131A # # 部署: # - converter `.env`:CONVERTER_API_KEY=<產出字串> # - visionA `.env.stage`:VISIONA_CONVERTER_API_KEY=<相同字串> # - **兩端必須完全相同**;rotate 時雙端同時更新 # # 安全: # - 絕不 commit 進 git(`.env` 已在 .gitignore) # - 絕不寫入 log / Slack / email / 對話 # - 每個環境獨立 key(dev / stage / prod 各自 `openssl rand -hex 32`) # - 詳見 docs/autoflow/04-architecture/auth.md §1 + §4 # # 為什麼是 optional(不 requireEnv): # - dev 環境可能還沒設 key(local 跑 legacy Web UI 路徑也應該能啟動) # - apiKeyMiddleware 自己會做 fail-fast(env 未設時對外 API 一律回 503) # - 啟動時會印 warn log 提醒,避免無聲問題 CONVERTER_API_KEY= # ============================================================================= # 8. Member Center Token Endpoint(converter → FAA OAuth client 用,保留) # ============================================================================= # # 給 oauthClient.js 取 service token 用(promote 流程把結果檔 PUT 到 FAA 之前要 # 先用 client_credentials grant 取 token)。visionA → converter 路線**不再經此**。 # # ⚠️ `.invalid` 為 RFC 2606 保留 TLD,DNS 永不解析。本地開發跑「不需 promote 的 # legacy /jobs 流程」可直接照抄;production 部署前務必替換為真實 Member Center # URL,否則 promote 階段取 token 會 DNS 失敗。 MEMBER_CENTER_TOKEN_URL=https://auth.example.invalid/oauth/token # ============================================================================= # 9. Converter OAuth Client 身份(converter → FAA 用,保留) # ============================================================================= # # Converter 在 promote 階段以 OAuth client 身份去 Member Center 取 # `files:upload.write` scope token,再 PUT 到 File Access Agent。 # 兩者必須成對出現。 KNERON_CONVERTER_CLIENT_ID=kneron_converter_dev KNERON_CONVERTER_CLIENT_SECRET=REPLACE-ME-IN-PRODUCTION # ============================================================================= # 10. File Access Agent(必填) # ============================================================================= # # Promote 時 Converter 把產出 stream PUT 到 FAA。 # - URL 必須是合法 http(s) URL;NODE_ENV=production 強制 https # - 本地開發可用 placeholder(.invalid TLD),不影響非 promote 流程 FILE_ACCESS_AGENT_BASE_URL=https://files.example.invalid FILE_ACCESS_AGENT_AUDIENCE=file_access_api # ============================================================================= # 11. Promote 行為(可選) # ============================================================================= # 單檔 PUT timeout(毫秒)。預設 300000(300s,覆蓋 500MB @ 5MB/s 最壞)。 # 部署環境檔案普遍較小可調低;GB 級檔案可調高。 # PROMOTE_TIMEOUT_MS=300000 # ============================================================================= # 12. OAuth Client cache(converter → FAA 用,可選) # ============================================================================= # OAUTH_TOKEN_REFRESH_SKEW_MS=60000 # token 距 expiresAt 還剩多少 ms 主動 refresh # OAUTH_TOKEN_TIMEOUT_MS=10000 # 取 token timeout(10s) # ============================================================================= # 13. Multipart 上傳上限(可選,T10 修 D5) # ============================================================================= # # 為什麼用 env: # 不同部署環境記憶體配額差異大(dev 容器 2GB / prod 16GB),固定 500MB 不夠彈性。 # 調這些值不需改原始碼。 # # 三個值都必須 > 0;非法值會 fail-fast。 # MULTIPART_MODEL_MAX_BYTES=524288000 # 500MB(model 檔案上限) # MULTIPART_REF_IMAGE_MAX_BYTES=10485760 # 10MB(單張 ref_image 上限) # MULTIPART_REF_IMAGES_MAX_COUNT=100 # ref_images 張數上限 # ============================================================================= # 14. Upload concurrency(可選,T10 修 D5) # ============================================================================= # # 為什麼需要: # multer memoryStorage 把整份 multipart load 進 buffer,每個並發 upload 吃掉 # model size 大小的 heap。5 並發 × 500MB ≈ 2.5GB heap,4GB 容器有風險。 # per-process counter 限制同時間 multipart parse + handler 進行中的請求數量。 # # 超過上限時:直接 503 service_busy + Retry-After header(不 queue),讓 client 主動 backoff。 # MAX_CONCURRENT_UPLOADS=5 # 同時間最多 5 個 upload 進行中 # UPLOAD_RETRY_AFTER_SECONDS=30 # 503 response 的 Retry-After(秒) # ============================================================================= # 15. Per-client_id rate limit(可選,T3 起) # ============================================================================= # 對 /api/v1/* 套用,window 內每個 client_id 最多 max 個 request。 # 預設 5min / 300 req(對齊 TDD §1.1)。 # # Phase 0.8b A3 後,clientId 在 API key 路線下寫死為 `'visionA-service'`; # rate limit 仍然套用、實質為 visionA → converter 整體上限。 # API_V1_RATE_LIMIT_WINDOW_MS=300000 # API_V1_RATE_LIMIT_MAX=300 # ============================================================================= # 16. Trust Proxy(可選,Phase 0.8b A7) # ============================================================================= # 給 Express `app.set('trust proxy', ...)` 用,影響 `req.ip` 取得真實 caller IP。 # 直接影響 audit log 中 `source_ip` 欄位的 forensic 價值。 # # 為什麼需要: # converter 跑在 Nginx / cloud LB 後面時,Node 看到的 remote address 永遠是 # 反代理的內網 IP;必須信任 `X-Forwarded-For` header 才能取真實 caller IP。 # # ⚠️ 安全提醒(極度重要): # - 設過寬(如 `true`)→ attacker 可偽造 `X-Forwarded-For` 欺騙 audit log # - 設過嚴(如 stage / prod 留 'loopback')→ source_ip 永遠是反代理 IP、forensic 失效 # - 必須跟實際部署架構一致;不確定時請問 DevOps / SRE # # 部署建議: # - local dev / 測試環境:留空(預設 `loopback`,只信任 127.0.0.1 / ::1) # - stage / prod(前面 1 層 Nginx):TRUST_PROXY=1 # - stage / prod(cloud LB + Nginx 兩層):TRUST_PROXY=2 # - 明確 CIDR:TRUST_PROXY="loopback, 10.0.0.0/8" # # 接受值:boolean(`true` / `false`)/ 整數 / 字串(含 'loopback' / 'linklocal' # / 'uniquelocal' / CIDR)。詳見 Express 文件: # https://expressjs.com/en/guide/behind-proxies.html # TRUST_PROXY=1 # ============================================================================= # 17. GET /api/v1/jobs/:id/result(Phase 0.8b Phase B 新增) # ============================================================================= # # `/result` 端點給 visionA-backend streaming proxy NEF binary。所有限制 # 都以 `token_fingerprint`(sha256(api_key).slice(0,12))為 bucket key。 # # 設計:docs/autoflow/04-architecture/api/api-result.md §9 / §15 # # 為什麼 single instance 部署可接受 in-memory counter: # - Phase 0.8b 部署是單 Node process;counter 在 process 內 atomic # - Phase 2 多 instance 部署前必切 Redis(否則 limit 會被「乘以 instance 數」 # 放鬆);見 security.md 候補 #8(HIGH) # Stream response timeout(AC-7)— 預設 5 分鐘(300_000 ms) # 5 min 最低 throughput ≈ 1.7 MB/s;合法 client 即使中等網路也能拿完 500MB # RESULT_STREAM_TIMEOUT_MS=300000 # Concurrent stream cap(AC-4)— 預設 10 個同時 stream(per-instance) # 超過 → 503 service_busy + Retry-After: 30 # 平衡:normal load P95 < 5、留 2× headroom;blast radius 可控 # MAX_CONCURRENT_RESULT_STREAMS=10 # Burst rate limit(AC-2)— 預設 5 req / 10 sec(per token_fingerprint) # 阻擋短時間 burst 攻擊;允許 visionA retry pattern # RESULT_RATE_LIMIT_BURST_PER_10S=5 # RESULT_RATE_LIMIT_BURST_WINDOW_MS=10000 # Sustained rate limit(AC-2)— 預設 20 req / 1 min(per token_fingerprint) # 涵蓋 visionA P95 normal load + 1.7× headroom;阻擋持續 mass request # RESULT_RATE_LIMIT_SUSTAINED_PER_MIN=20 # RESULT_RATE_LIMIT_SUSTAINED_WINDOW_MS=60000 # Bandwidth quota(AC-3)— 預設 1 GB / hr + 6 GB / 24hr(per token_fingerprint) # 阻擋 attacker 用「剛好踩線 req count」配大檔的 bandwidth abuse # 1 GB = 1073741824 / 6 GB = 6442450944 # RESULT_BANDWIDTH_QUOTA_PER_HOUR_BYTES=1073741824 # RESULT_BANDWIDTH_QUOTA_PER_DAY_BYTES=6442450944