Co-authored-by: Cursor <cursoragent@cursor.com>
15 KiB
文件管理:自动提权、归属显示、权限修改 — 设计文档
| 项 | 内容 |
|---|---|
| 日期 | 2026-06-01 |
| 状态 | 已实现 Phase 1–5 + 二期递归 chmod/不可读提示(2026-06-01) |
| 范围 | #/files 远程文件管理(浏览、写入、压缩、chmod 等) |
| 关联 | sync_v2.py · asyncssh_pool.py · remote_archive.py · FilesPage.vue |
1. 背景与目标
1.1 背景
当前文件管理已具备浏览、编辑、上传、压缩、右键 chmod 等能力,但生产环境普遍存在:
- SSH 登录用户为 deploy/www 等非 root 账号;
- 站点目录与文件属主为 root 或 www,普通用户 读得到、写不进;
- 列表展示
drwxr-xr-x等原始ls权限串,运维难以一眼看出「这是谁家的文件」; - chmod 仅支持八进制输入,无预设、无 chown,且无 sudo 回退,与保存/压缩已做的提权能力不一致。
用户诉求(原文):
- 自动获取 root 权限(文件操作应能自动提权,而不是每次失败再手工改服务器);
- 列表显示权限,且 简化为用户维度(直接显示
www/root等,而非冗长权限串); - 右键修改权限,弹出 结构化对话框(而非仅一个 chmod 输入框)。
1.2 目标
| 目标 | 可验收标准 |
|---|---|
| G1 自动提权 | 写、删、压缩、chmod/chown 等在配置允许时自动 sudo -n 重试,成功率与 root 登录接近 |
| G2 归属可读 | 列表默认列显示 属主(如 www),悬停可看完整 权限 + 用户:组 |
| G3 权限可改 | 右键「修改权限」打开对话框:预设 + 自定义八进制 + 可选改属主/属组 |
| G4 安全可审计 | 提权范围可配置、命令白名单、所有 chmod/chown 记审计日志 |
| G5 不破坏架构 | API → Service/SSH 基础设施层,不在路由里散落 sudo 逻辑 |
1.3 非目标(本期不做)
- 不要求 Nexus 在目标机上 永久改写 sudoers(类似 Agent 安装的临时注入)作为默认路径——仅文档 + 可选一键脚本;
- 不实现交互式
sudo密码输入(违反无人值守运维); - 不把文件管理改成「必须 root SSH 才能用」。
2. 现状摘要
| 能力 | 现状 | 缺口 |
|---|---|---|
| 浏览 | ls -la,解析 perms,未返回 owner/group |
前端无法显示 www/root |
| 保存/上传 | SFTP → SSH tee/mv,sudo -n 部分已做 |
未统一、chmod 未接入 |
| 压缩 | remote_archive.py 已 sudo 回退 |
— |
| chmod | 仅 chmod mode path,无 sudo |
非 root 常失败 |
| 提权模型 | Agent 安装用 _sudo_wrap 临时 sudoers |
文件管理未复用同一策略 |
后端 browse 已解析 parts[2](owner)、parts[3](group),但未写入 API 响应(见 sync_v2.py 约 270–302 行)。
3. 方案对比
3.1 自动提权
| 方案 | 说明 | 优点 | 缺点 |
|---|---|---|---|
| A. 仅 SSH 用户 | 不 sudo,失败即报错 | 最简单 | 无法满足用户「自动 root」 |
| B. 统一 sudo -n 回退 | 先普通执行,Permission denied 再 sudo -n bash -c |
与现网 partial 实现一致;可审计 | 需目标机配置 NOPASSWD 白名单 |
| C. 每次临时写 sudoers | 复用 servers._sudo_wrap |
无预先配置 | 安全风险高、慢、需 root 写 sudoers |
| D. 文件管理强制 root SSH | 凭据层只用 root | 权限足 | 违背最小权限;很多站点不给 root |
选定:B(主) + 安装向导/文档配置 NOPASSWD(辅)
- 业务命令统一走
exec_ssh_command_with_fallback()(从remote_archive提升到remote_shell.py)。 - 对 非 root SSH 用户:失败且 stderr 含 permission denied → 自动
sudo -n bash -c '...'。 - root SSH 用户:不包 sudo。
- 提供 服务器级开关(见 §5.2),默认
auto_sudo。
不推荐 C 作为默认:与 Agent 安装场景不同,文件管理高频、面过大。
3.2 权限展示
| 方案 | 列表主列 | 说明 |
|---|---|---|
| S1 | drwxr-xr-x |
现状 |
| S2 | www |
仅属主 |
| S3 | www · 755 |
属主 + 八进制简写(从 perms 解析) |
| S4 | Chip www + tooltip 全文 |
推荐:主信息属主,详情悬停 |
选定:S4
- 列名:归属(
owner字段,展示owner或owner:group若不同)。 - 辅助解析
mode_octal:755/644供排序或二级展示(可选列,默认隐藏或放在 tooltip)。 symlink仍显示→ target,归属列显示-或链接目标属主。
3.3 修改权限对话框
| 方案 | 内容 |
|---|---|
| P1 | 仅八进制 chmod(现状) |
| P2 | 预设按钮 + 八进制 + chown(推荐) |
| P3 | 完整 Unix 权限矩阵(rwx 勾选) |
选定:P2 — 满足运维习惯,实现成本低于 P3。
4. 选定方案详设
4.1 统一远程 Shell 执行层
新增模块(建议路径):
server/infrastructure/ssh/remote_shell.py
职责:
# 概念接口(实现时写完整类型)
class FilesElevation(str, Enum):
OFF = "off" # 从不 sudo
AUTO = "auto_sudo" # 默认:失败且权限错误时 sudo -n
ALWAYS = "always_sudo" # 所有命令直接 sudo -n(可选,仅排障)
async def exec_remote_command(
server: Server,
command: str,
*,
timeout: int = 30,
elevation: FilesElevation | None = None, # None → 读服务器/全局配置
) -> CommandResult: ...
def wrap_sudo(command: str) -> str:
return f"sudo -n bash -c {shlex.quote(command)}"
接入范围(须全部走统一层)
| 操作 | 当前入口 | 改造 |
|---|---|---|
| browse | exec_ssh_command(ls) |
保持直接执行(只读,一般不需 sudo) |
| write-file / upload | remote_write_bytes |
已有多级回退,并入 elevation 策略 |
| delete / mkdir / rename | file-ops |
加 fallback |
| compress / decompress | remote_archive |
已有 fallback,迁到 remote_shell |
| chmod / chown | chmod 端点 |
扩展 payload + fallback |
| read-file | cat |
一般只读;可选对不可读文件 sudo cat(二期) |
提权判定
exit_code != 0
且 (
"permission denied" in stderr.lower()
或 "operation not permitted" in stderr.lower()
)
→ 若 elevation == AUTO,重试 wrap_sudo(command)
4.2 目标机 sudo 配置(运维一次性)
在 docs/deploy/ 增加模板 nexus-files.sudoers(示例):
# /etc/sudoers.d/nexus-files — 由运维审阅后安装
deploy ALL=(ALL) NOPASSWD: /bin/chmod, /bin/chown, /usr/bin/chown,
/bin/mv, /bin/cp, /bin/mkdir, /bin/rm, /bin/tar, /usr/bin/zip,
/usr/bin/unzip, /usr/bin/tee, /usr/bin/cat
说明:
- Nexus 不会默认自动创建该文件(避免未授权扩权);
- 安装向导或「服务器详情 → 文件管理」页提供 检测 API:
GET /api/servers/{id}/files-capability- 检测项:
ssh_user、can_sudo_nopasswd、whitelist_ok;
- 检测项:
- 检测失败时,前端 Toast 链到文档锚点。
若 SSH 用户已是 root:can_sudo_nopasswd=true,无需 sudoers。
4.3 浏览 API 扩展
POST /api/sync/browse 响应项扩展
{
"name": "index.php",
"type": "file",
"size": 1234,
"modified": "Jun 1 12:00",
"permissions": "rw-r--r--",
"mode_octal": "644",
"owner": "www",
"group": "www",
"link_target": null
}
| 字段 | 来源 | 说明 |
|---|---|---|
permissions |
ls 第 1 列 |
保留,供 tooltip / 高级用户 |
mode_octal |
从 perms 解析 | rwxr-xr-x → 755(含 setuid 等用 4 位,少见则原样) |
owner |
ls 第 3 列 |
列表主展示 |
group |
ls 第 4 列 |
与 owner 相同时 UI 只显示 owner |
解析工具:frontend/src/utils/fileMode.ts + 后端 server/utils/unix_ls.py(避免前后端不一致,以后端为准)。
browse 可选增强(二期):ls -la --time-style=long-iso 统一时间格式。
4.4 前端列表 UI
列调整(FilesPage.vue)
| 列 | 默认 | 展示 |
|---|---|---|
| 名称 | 显示 | 不变 |
| 归属 | 显示(新) | v-chip:www / root;www:nogroup 简化为 www |
| 大小 | 显示 | 不变 |
| 修改时间 | 显示 | 不变 |
| 权限 | 隐藏(可选展开) | tooltip:drwxr-xr-x · 755 |
样式约定
owner === 'root'→ chip 颜色warning(提醒系统文件)- 当前 SSH 用户无写权限且非 root 属主 → 行尾可选 icon
mdi-shield-lock(仅提示,不阻断只读)
4.5 右键「修改权限」对话框
入口:已有右键 chmod 菜单项,改为打开新组件 FilePermissionDialog.vue。
对话框结构
┌─ 修改权限 ─────────────────────────────┐
│ 文件:index.php │
│ 当前:www · 644 (rw-r--r--) │
├─────────────────────────────────────────┤
│ 常用预设(按 type 过滤) │
│ [ 644 文件 ] [ 755 目录 ] [ 600 私密 ] │
│ [ 775 协作目录 ] [ 自定义… ] │
├─────────────────────────────────────────┤
│ 八进制权限 [ 644 ] (验证 3–4 位) │
│ 属主 [ www ] (可选) │
│ 属组 [ www ] (可选) │
│ □ 同时修改子项(仅目录,二期) │
├─────────────────────────────────────────┤
│ 提权:将使用 sudo 执行(SSH 用户 deploy) │
│ [ 检测提权能力 ] 链接文档 │
├─────────────────────────────────────────┤
│ [ 取消 ] [ 应用 ] │
└─────────────────────────────────────────┘
预设表(前端常量,后端可校验)
| 标签 | mode | 适用 |
|---|---|---|
| 文件默认 | 644 | file |
| 目录默认 | 755 | directory |
| 仅本人 | 600 | file |
| 协作目录 | 775 | directory |
| 可执行 | 755 | file(脚本) |
API:POST /api/sync/chmod 扩展
class FileChmod(BaseModel):
server_id: int
path: str = Field(..., min_length=1, max_length=4096)
mode: str = Field(..., pattern=r"^[0-7]{3,4}$")
owner: str | None = Field(None, max_length=64) # 可选 chown
group: str | None = Field(None, max_length=64)
recursive: bool = False # 二期:chmod -R
远程命令(经 exec_remote_command + AUTO elevation)
# 单文件
chmod 644 '/path/file' && chown www:www '/path/file'
# 仅 chmod
chmod 644 '/path/file'
顺序:先 chmod 再 chown(或反之,实现时固定一种并写测试)。
响应
{
"success": true,
"path": "/www/.../index.php",
"mode": "644",
"owner": "www",
"group": "www",
"elevated": true
}
前端成功后 browseCurrent() 刷新列表。
4.6 服务器级配置(三写之一扩展)
在 服务器编辑 或 settings 增加(MySQL servers 表或 settings JSON):
| 字段 | 类型 | 默认 | 说明 |
|---|---|---|---|
files_elevation |
enum | auto_sudo |
off / auto_sudo / always_sudo |
不在安装向导强制,避免与 Agent sudoers 混淆;在服务器表单「高级 → 文件管理」折叠区配置。
5. 安全与性能
5.1 安全
- 最小权限:
sudo -n仅允许白名单命令,禁止ALL=(ALL) NOPASSWD: ALL; - 禁止在 API 中拼接未校验的 owner/group(仅
[a-zA-Z0-9_-.]+); - 路径继续
normalize_remote_abs_path,禁止..; - 审计:
file_chmod/file_chown合并为file_permission_change,记录 mode、owner、group、elevated; - 失败信息不泄露 sudoers 路径内容。
5.2 性能
- browse 仍单次
ls -la,不增加 SSH 往返; - sudo 回退最多 2 倍 命令耗时(仅失败路径),可接受;
- 批量 chmod(二期
-R)需限制路径深度或要求确认框。
6. 实现步骤(供技术文档拆分)
| 阶段 | 内容 | 预估 |
|---|---|---|
| Phase 1 | remote_shell.py + 迁移 write/compress/chmod/file-ops |
1d |
| Phase 2 | browse 返回 owner/group/mode_octal;前端归属列 | 0.5d |
| Phase 3 | FilePermissionDialog + chmod API 扩展 chown |
1d |
| Phase 4 | files-capability 检测 + 文档 + 设置项 files_elevation |
0.5d |
| Phase 5 | 测试 + changelog + 审计 | 0.5d |
依赖顺序:Phase 1 → 2/3 可并行 → 4 → 5。
7. 验收标准
L1 提权
- SSH 用户
deploy+ 已配置nexus-files.sudoers:编辑、保存、压缩、chmod 均成功,elevated: true出现在审计(若执行了 sudo)。 - SSH 用户
root:不调用 sudo,功能正常。 - 无 sudo 且非 root:操作失败,提示含「配置免密 sudo」链接,不返回误导性「SSH 连接失败」。
L2 展示
- 列表「归属」列显示
www/root,悬停可见完整权限串。 - 不再默认展示
drwxr-xr-x宽列(可列设置打开)。
L3 修改权限
- 右键单文件/目录 → 对话框预设 644/755 一键填充。
- 修改属主为
www后,ls刷新显示更新。 - 非法 mode/owner 前端校验 + 后端 400。
L4 回归
- 文件树、编辑器、剪贴板粘贴、下载不受回归影响。
8. 风险与对策
| 风险 | 对策 |
|---|---|
| 用户以为「自动 root」= Nexus 破解权限 | UI 文案写清:在服务器已授权 sudo 前提下自动提权 |
| sudo 白名单遗漏命令 | capability 检测 + 失败时记录完整 stderr |
ls 解析在不同 OS 上列错位 |
优先 GNU coreutils;异常行跳过并记日志 |
| chown 误操作 | 对话框展示当前值;危险路径(/etc、/usr)二次确认(二期) |
9. 相关文档(实现后补齐)
- 技术实施:
docs/design/plans/2026-06-01-files-root-ownership-ui.md(待写) - Changelog:
docs/changelog/2026-06-01-files-root-ownership-ui.md(待写) - 运维:
docs/deploy/nexus-files-sudoers.example
10. 决策记录
| 决策 | 选择 | 理由 |
|---|---|---|
| 自动 root 实现 | sudo -n 回退,非临时写 sudoers |
安全、可审计、与现有 partial 实现一致 |
| 列表权限 | 主显 属主,权限进 tooltip | 符合用户「www/root」认知 |
| chmod UI | 预设 + 八进制 + 可选 chown | 平衡能力与复杂度 |
| browse 是否 sudo | 否(默认) | 列目录极少需要 root;减少噪音 |
请评审确认后进入 Phase 1 实现。 若需「始终 sudo」或「强制 root SSH」,可在服务器级 files_elevation 中开启 always_sudo。