103 lines
13 KiB
Markdown
103 lines
13 KiB
Markdown
# DSH Web 面板 API(MVP)
|
||
|
||
默认监听 127.0.0.1:8088。JSON 错误统一为 {error:string}。登录使用 HttpOnly session/access/refresh cookie;写请求需 X-CSRF-Token 和精确同源 Origin(未配置 PANEL_PUBLIC_URL 时也校验)。access JWT 有效期5分钟,refresh轮换且受24小时session限制。
|
||
|
||
## 配置
|
||
- UPLOAD_ALLOWED_EXTENSIONS:可选逗号分隔扩展名白名单(例如 .txt,.md);空表示不限制。大小写不敏感,非内容/恶意软件检测。
|
||
|
||
## 管理员孤儿回收
|
||
|
||
GET /api/orphans 返回可回收的完整容器ID数组。DELETE /api/orphans?id=<id>&confirm=<相同完整id> 二次核验后非强制删除,保留bind数据及网络。仅接受本项目身份/挂载匹配、已停止、无实例或升级引用的容器;普通用户403,确认不匹配400,状态不安全409。
|
||
|
||
## 其他配置
|
||
- LISTEN_ADDR(默认 127.0.0.1:8088)
|
||
- DATA_ROOT(默认 ./data)
|
||
- HOST_DATA_ROOT(默认等于 DATA_ROOT 的绝对路径,用于 Docker bind source;容器部署必须显式设置宿主绝对路径)
|
||
- CONTAINER_DATA_ROOT(默认 DATA_ROOT)
|
||
- DOCKER_HOST(默认 unix:///var/run/docker.sock)
|
||
- PANEL_PUBLIC_URL(可选)
|
||
- ADMIN_USERNAME / ADMIN_PASSWORD(首次启动创建管理员;密码必须 12–72 字节,不设置则拒绝启动,不打印密码)
|
||
- WEB_DIR(可选静态前端目录)
|
||
- PANEL_CONTAINER_ID(面板容器名/ID,用于加入实例网络;宿主进程运行时留空)
|
||
- DEFAULT_QUOTA_BYTES(默认10GiB)
|
||
- DSH_DOCKER_ROOT_VISIBLE(与DockerRootDir相同路径的明确映射,用于构建前容量检查;未知容量拒绝新构建)
|
||
|
||
## 认证
|
||
- POST /api/login {username,password} → {user,csrf_token};user 字段为 id,username,admin,quota_bytes,created_at
|
||
- POST /api/logout → {ok:true}
|
||
- POST /api/refresh → {user,csrf_token}(刷新HttpOnly cookie,需Origin和CSRF;并发轮换冲突且session仍有效时返回409,不签发cookie,客户端可复验 `/api/me` 后重试原请求;会话撤销/过期返回401,存储异常不能当作注销)
|
||
- GET /api/me → {user,csrf_token}
|
||
- POST /api/me/password {current_password,password} → {ok:true,reauthenticate:true}(普通用户及管理员仅修改本人;不接收目标 ID,未知 JSON 字段返回 400)。需有效 access/session、同源 Origin 和 X-CSRF-Token;新密码为 UTF-8 12–72 字节,非法 JSON、缺少当前密码或长度不符返回 400。错误当前密码返回 400 而非 401,客户端不应因此退出登录。未登录 401,Origin/CSRF 不符 403(失效 session 在 guard 中也可能返回 403),已认证的非 POST 请求返回 405。
|
||
- 与登录、管理员重设共用 authLoginMu,等待后复验会话与当前密码;沿用账户登录失败计数,累计 5 次错误锁定 15 分钟,锁定期间返回 429(即使当前密码正确),已登录不能绕过。认证存储故障返回 503。
|
||
- 同一事务保存 bcrypt hash、撤销本人全部 session/refresh、清除失败计数,并记录 user.change_password 审计(操作者和目标均为本人 ID,detail 为空,无密码/hash/token);事务失败返回 500 并完整回滚,其他用户不受影响。成功后客户端应清理登录状态并重新登录;旧密码、旧 access/session/refresh 不再可用。已建立的 runtime WS/SSE 不主动断开,下一次请求或重连重新鉴权。
|
||
|
||
## 管理
|
||
- GET /api/users(管理员)
|
||
- POST /api/users {username,password,admin?,quota_bytes?}(管理员)
|
||
- PATCH /api/users {id,quota_bytes}(管理员修改正数配额)
|
||
- POST /api/users/reset-password {id,password} → {ok:true,reauthenticate:boolean}(仅管理员;需有效 access/session、同源 Origin 和 X-CSRF-Token)。密码按 UTF-8 字节计为 12–72 字节,以 bcrypt 保存;缺少 ID、非法 JSON 或长度不符返回 400,目标不存在 404,未认证 401,非管理员或 Origin/CSRF 不符 403(失效 session 在 guard 中也可能返回 403),已认证的非 POST 请求返回 405。
|
||
- 同一事务更新密码、删除目标全部 session/refresh、清除目标登录失败锁定,并记录 user.reset_password 审计(操作者 ID、目标 ID、时间,detail 为空,不含密码/hash/token)。任何数据库失败返回 500 并回滚,不影响其他用户会话。与登录串行,等待后重新检查管理员会话和权限。
|
||
- 允许重设自身,此时 reauthenticate:true,当前会话也立即失效,客户端应清理登录状态并回到登录页;重设他人返回 false。旧密码不能再登录,旧 access/refresh 不能发起新的认证请求。已建立的 runtime WS/SSE 不会被主动立即断开:代理仅在请求/握手时鉴权,下一次请求或重连会被拒绝;现有 runtime 页面刷新认证脚本不是即时断连保证。
|
||
- GET /api/audit(管理员)
|
||
|
||
## 镜像与构建
|
||
|
||
这是 breaking change:`/api/latest`、`/api/compatibility`、`POST /api/builds` 及 `/api/intents`(包括子路径的查询、创建、取消)已移除,不提供 410 兼容入口。`GET /api/builds` 与 `GET /api/builds/{id}` 仅用于构建历史读取。旧构建及旧创建意图不迁移、不恢复执行,不在后台续跑或完成旧预约;不因此删除已有实例或数据。管理员改用 Git 发布接口构建,用户以已验证且准入的精确本地镜像 ID 创建实例。
|
||
|
||
已认证请求中,`POST /api/builds` 返回 405;`/api/latest`、`/api/compatibility` 和 `/api/intents`(含子路径)返回 404。历史意图记录留在数据库中,但不再提供存储操作、轮询或恢复流程,也不计入配额或作为镜像删除引用。
|
||
|
||
- GET /api/releases/latest(仅管理员;获取并校验配置的 Git 发布分支,返回 release_id、version、commit、repository、branch、recipe_rev、platforms、compatible、reason,以及可选的 build。检查会下载构建文件,但不执行 Dockerfile;未知部署协议或平台不允许构建)
|
||
- POST /api/releases/build {commit}(仅管理员、同源和 CSRF;提交检查时看到的精确 commit,分支已变化返回 409,需重新检查。返回 202 构建任务;不自动升级实例)
|
||
- Git 构建任务附带 release_id、source_commit、source_repository 来源信息;发布身份独立于 DSH 版本及部署布局,允许同一 DSH 版本构建新的脚本修订。可信发布通过现有镜像身份和启动验证后可准入,显式禁用仍优先。新构建仅允许管理员通过 Git 发布接口提交;main 不再包含内置 runtime 构建资产。
|
||
- GET /api/images(仅返回 Docker 本地实际存在的镜像;每项含 admission、admission_reason、selectable;只有 status=available 且 selectable=true 才可新建/升级。inspect 确认 404 后原子删除镜像索引行并将成功构建记录标为 missing,保留构建历史、显式准入策略和实例;missing 历史不会重建索引。Docker 列表或检查失败时请求失败,不删除索引行)
|
||
- POST /api/images/admission {image_id,state,evidence}(管理员、同源和CSRF;state为recommended/compatible/existing_only/blocked,evidence必填。正向准入要求精确镜像身份、成功验证记录、受支持挂载布局与平台;策略与审计持久化。单镜像准入不授权新构建)
|
||
- DELETE /api/images?image_id=sha256:...(管理员;保护实例、活动任务、升级及全部Docker容器引用,不force)
|
||
- GET /api/builds?limit=20&offset=0(摘要数组,不含日志正文;limit默认20、最大100,offset非负;X-Has-More响应头表示后续是否有记录)
|
||
- GET /api/builds/{id}(按需获取单个任务及脱敏日志;仅已登录用户,日志最多64 KiB)
|
||
|
||
镜像策略:recommended/compatible可在实际验证与身份检查通过后用于新建/升级;existing_only允许已有实例运行和启停,不能新建/作为升级目标;blocked还拒绝新的start/restart及受控配置非GET操作(含Profile保存、备份和恢复),但不会自动停止已运行容器或断开工作台,通用文件管理仍允许。experimental是成功实验镜像未准入时的展示状态,不是兼容性结论。missing/unverified不可选。
|
||
|
||
历史兼容基线为 linux/amd64、模板7、0.2.0-rc.1(compatible)和0.2.0-rc.2(recommended)。其他平台/版本不继承这些验收结论。模板3–6的历史成功镜像默认仅存量,具有精确验证记录并经管理员验收可准入;未知模板布局不可正向准入,模板7向旧共享布局降级仍拒绝。
|
||
|
||
## 实例
|
||
- GET /api/instances → Instance[];字段 id,uid,name,version,image_id,status,container_id,created_at
|
||
- POST /api/instances {name,image_id,version} → Instance(每用户最多 2 个;image_id 必须指向已验证且准入的精确本地镜像,不接受构建预约)
|
||
- POST /api/instances/{id}/start|stop|restart
|
||
- DELETE /api/instances/{id} → {ok:true}
|
||
|
||
所有成功 JSON 列表均为顶层数组(不是 data 包装);错误均为 {error:string}。实现与验收仍在进行,不应仅凭接口列在此处认定已验收。
|
||
- GET /api/instances/{id}/logs
|
||
- /dsh/{id}/...(登录和 owner 校验;需同 Docker 网络)
|
||
|
||
## 工作区
|
||
|
||
指定实例时文件根为该所有者实例的完整home;允许下载凭据及目录ZIP,不承诺write-only。下载提示仅在界面防误操作,API不按敏感文件名过滤。通用文件修改不强制走专用Profile的停机/YAML校验流程;归属、路径安全、配额、恢复状态和现有文件操作并发保护仍有效。上传不覆盖同名文件(409)。
|
||
|
||
- GET /api/ws/tree?path=.
|
||
- GET /api/ws/usage
|
||
- POST /api/ws/mkdir {path}
|
||
- POST /api/ws/rename {path,new_path}
|
||
- DELETE /api/ws/delete?path=...
|
||
- POST /api/ws/upload multipart file,path
|
||
- GET /api/ws/download?path=...:普通文件保持原始流与 Range 支持;目录返回 `application/zip` 附件(目录名.zip),空 path 或 `.` 下载整个工作区(workspace.zip)。ZIP 条目相对所选目录,保留空目录,不额外包裹顶层目录,使用 Store 模式(不压缩),不支持 ZIP Range。
|
||
- ZIP 先校验有界文件清单,再逐文件流式输出,不生成临时归档,不持 workspace/global 锁跨网络传输。每次最多 10,000 个文件/目录条目、64 层、10 GiB 文件总大小;进程最多同时 4 个目录下载,超出分别返回 413 / 429;扫描和传输合计限时 2 分钟。
|
||
- 路径必须位于工作区内;下载拒绝任意路径分量的符号链接、跨挂载点、特殊文件和多硬链接普通文件。目录含上述条目或含反斜杠/冒号的不安全 ZIP 文件名时整体拒绝(400),不静默漏文件。依赖 Linux openat2,内核不支持时安全失败。
|
||
- 下载不是原子快照:流式读取前复核文件身份、大小、类型及修改时间;并发修改、取消或传输错误可能导致连接中断/不完整 ZIP,请停止写入后重试。单文件下载不受 ZIP 数量、大小、并发和时限约束。
|
||
|
||
## profile / 备份
|
||
- GET /api/instances/{id}/profile
|
||
- PUT /api/instances/{id}/profile {content,restart?}(停机原子写;restart=true启动并检查就绪)
|
||
- GET /api/instances/{id}/profile-preview(bundle清单和脱敏dump-config,字符串值隐藏;运行中才能dump)
|
||
- POST /api/instances/{id}/backups {password}(当前登录密码加密;自动恢复原运行状态)
|
||
- POST /api/instances/{id}/backups multipart file,password(导入外部包,password为原包密码;不立即覆盖home)
|
||
- GET /api/instances/{id}/backups
|
||
- GET /api/instances/{id}/backups?download={backup_id}
|
||
- POST /api/instances/{id}/restore {backup_id,password}(原备份密码;旧平台key包可留空)
|
||
- POST /api/instances/{id}/upgrade {image_id,password}(当前登录密码,加密pre-upgrade备份)
|
||
|
||
Profile写入要求实例先停止。备份/还原/升级会受控停启;恢复保留原运行状态,升级成功运行新版,失败恢复旧状态。模板2入口先准备插件再启动应用。删除实例仅删除容器与网络并移除索引,保留数据目录。升级失败恢复实例home,不撤销共享workspace或外部系统副作用。
|
||
|
||
创建恢复:Docker 创建响应丢失时,首次 inspect 404 不代表原请求已结束。实例保留为可见的 error 状态并继续占用名额,用户可重试删除;后台不会仅凭名称删除身份不匹配资源。不确定创建记录不会自动过期:若资源始终不存在,需管理员确认 Docker 原创建请求已终止后再处理保留记录,不能仅凭等待时长判定安全。构建验证容器在创建前持久登记任务身份,启动时即使没有排队构建也会回收并重试失败清理;持续不存在的未知结果仍保留登记以捕获晚到容器。构建终态和镜像索引事务失败会独立重试并记录日志,不阻塞后续构建;数据库关闭停止重试,重启将未完成构建标记失败。
|
||
|
||
已实现并通过核心集成检查:镜像复用验证、Docker 生命周期、token/cookie 桥接、加密备份恢复、同镜像替换升级。WS/SSE 代理有单元测试,真实完整会话待验证。硬配额与跨公网租户域名隔离未实现;当前 dsh 发行版不支持 --public-url。默认仅 loopback 监听,不应直接暴露公网。完整限制见 README.md。
|