## 十八、统一 KEY:管理员统一设置,用户只能使用管理员 KEY 和模型(2026-09-08 20:40) **决策**(用户确认):① KEY 存放 = 复用 admin 密钥库(桌面/API 自助换 key 即全局生效);② 用户 dsh 界面自配暴露面先实测再定,本次不做 dsh patch。 **改动(commit df5cc84,6 文件 +49/-14)**: | 文件 | 改动 | |------|------| | `src/web/server.ts` | `resolveApiKey` 忽略入参 userId,改为 `listPublicUsers().filter(role==='admin')` 取 admin 的启用 key——**所有用户 spawn 注入同一把管理员 key** | | `src/supervisor/spawner.ts` | Spawner 接口新增 `restartAllMains()` | | `src/supervisor/orchestrator.ts` | LocalSpawner 实现:遍历内存 `mains` 逐个 `restartMain`(单个失败 console.error 不中断广播) | | `src/supervisor/k8s-spawner.ts` | K8sSpawner 实现:遍历 `db.listInstancesByRole('main')` 逐个重启 | | `src/web/routes/auth.ts` | `/api/me/keys` GET/POST/select/delete 由 `requireAuth` 改 **`requireAdmin`**(普通用户 403);POST/select 后 `restartAllMains()` 广播(key 是 spawn 时注入 env 的快照,不重启运行实例不刷新) | | `web/desktop.html` | 非 admin 隐藏"管理密钥"按钮;面板文案改"全局 API 密钥(所有用户共用,仅管理员可改)" | **验证(全链路实测)**: - admin GET `/api/me/keys` → 200,deepseek-main 在列 ✅ - 注册 testuser(pending)→ admin approve(active)→ 登录 ✅ - 普通用户 GET/POST `/api/me/keys` → **403 forbidden** ✅ - **testuser launch DSH → 打开 200 SPA → `llm/listProviders` ok:true deepseek-official** ——证明普通用户无自配 key 也拿到 admin 全局 key ✅ **遗留**:用户 dsh 界面内自配 key/模型的能力面未实测(proxy 伪装 loopback 可能放行 dsh Settings)——第二道防线待 P1 实测后再定。testuser 账号保留作普通用户全链路验证种子。