# 2026-05-25 — SSH 密钥预设系统：下拉菜单 + 后端自动解析

## 变更摘要

新增 SSH 密钥预设功能，与密码预设采用完全一致的模式：
1. 新增 `ssh_key_presets` 表 — 存储加密私钥 + 公钥
2. 新增 `/api/ssh-key-presets/` CRUD + reveal API
3. Server 模型新增 `ssh_key_preset_id` 字段，关联密钥预设
4. 服务器表单密钥模式新增「密钥预设」下拉菜单，选预设后路径/私钥输入框自动隐藏
5. 后端自动解析 `ssh_key_preset_id` → 解密预设私钥 → 加密存储到 `ssh_key_private`

## 动机

- 密码预设已有下拉菜单 + 后端自动解析（Task #21），密钥模式应保持一致的 UX
- 旧方案：密钥模式只能手动输入路径或粘贴私钥，无法复用已存储的密钥
- 新方案：选密钥预设下拉 → 路径和私钥输入框自动隐藏 → 保存时后端自动解密预设私钥并加密存储
- 前端零密钥暴露，与密码预设安全模型一致

## 涉及文件

| 文件 | 改动 |
|------|------|
| `server/domain/models/__init__.py` | 新增 `SshKeyPreset` 模型；Server 新增 `ssh_key_preset_id` 列 |
| `server/api/schemas.py` | 新增 `SshKeyPresetCreate`/`SshKeyPresetUpdate`；ServerCreate/ServerUpdate 新增 `ssh_key_preset_id` |
| `server/infrastructure/database/ssh_key_preset_repo.py` | 新增密钥预设 Repository |
| `server/api/settings.py` | 新增 `ssh_key_preset_router` — CRUD + reveal + 审计日志 |
| `server/api/servers.py` | create_server/update_server 自动解析 `ssh_key_preset_id`；`_server_to_dict` 输出 `ssh_key_preset_id`；`ALLOWED_FIELDS` 加入 `ssh_key_preset_id` |
| `server/main.py` | 注册 `ssh_key_preset_router` |
| `web/app/servers.html` | 密钥区=预设下拉+路径输入+私钥textarea(联动)；`loadSshKeyPresetOptions()`；`saveServer()` 发送 `ssh_key_preset_id` |

## 是否需迁移/重启

- **DB 迁移**: 
  - `CREATE TABLE ssh_key_presets (...)`
  - `ALTER TABLE servers ADD COLUMN ssh_key_preset_id INT NULL COMMENT '关联的密钥预设ID(SSH认证)' AFTER preset_id;`
- **需重启**: 是（Python 后端模型+路由变更）

## 安全考量

- 前端不接触预设私钥明文 → 与密码预设安全模型一致
- `ssh_key_preset_id` 仅在后端解析，前端只传 ID → 无 XSS 泄露风险
- 预设不存在时静默忽略，不报错（用户仍可手动输入密钥路径/私钥）
- reveal 端点需二次验证 + 审计日志

## 验证方式

1. `POST /api/ssh-key-presets/` 传 `name+private_key+public_key` → 返回 `id, name`
2. `GET /api/ssh-key-presets/` → 列出预设（不含私钥）
3. `POST /api/servers/` 传 `auth_method=key, ssh_key_preset_id=1` → 返回 `ssh_key_private_set:true, ssh_key_preset_id:1, ssh_key_configured:true`
4. 浏览器打开添加服务器 → 切换到密钥模式：密钥预设下拉可见，选预设后路径和私钥输入框隐藏
