Add snapshot usage and protected cleanup, preserve unsaved profile edits, avoid holding SQL connections during snapshot deletion, and fix download filenames. Include regression tests and API documentation.
21 KiB
本机或可信团队试运行运维
当前仅支持模板 7 的独立实例布局:实例 home 挂载到 /workspace,不再创建或使用 workspaces//files,也不再迁移旧共享挂载布局。历史 files 目录可在确认旧容器已停止并移除挂载后,由管理员在维护窗口删除;面板不会在启动或查询用量时自动递归删除历史数据。旧版本已经删除实例所留下的目录也需核对数据库及容器引用后单独清理。
本版本面向本机或受信任团队,不应直接用于公网不可信租户。DSH 与面板使用同源路径代理;Docker socket 权限等价于宿主 root。
启动
- 复制
.env.example为.env,填写专用绝对目录DSH_HOST_DATA_ROOT和 12–72 字节的随机管理员密码。限制.env的读取权限,不要提交。管理员账号创建后该密码不再被读取,应从.env中移除,再运行docker compose up -d --force-recreate panel重建面板容器,清除容器环境变量中保留的明文密码(docker inspect可见)。仅修改.env或重启原容器不会更新其环境变量;重建面板会短暂中断面板访问。 - 面板以 UID/GID 1000 运行。首次部署前由管理员创建专用数据目录并赋予 1000:1000 所有权、0700 权限;不要对已有未知目录递归改权限。将
stat -c %g /var/run/docker.sock的结果填入.env的DOCKER_GID,以附加组访问 socket。然后运行docker compose up --build -d。默认访问http://127.0.0.1:8088;远程管理优先使用 SSH 本地端口转发,保留此访问地址。Compose 定义了dsh-mgr -healthcheck健康检查(探测本机/healthz);连续探测失败时容器会被标记为 unhealthy,但该状态本身不会触发重启。restart: unless-stopped用于进程退出后的自动拉起;/healthz仅检查HTTP存活,不检查数据库、Docker或实例健康。 - 管理员登录后在镜像页检查 Git 发布并确认精确 commit 后构建;普通用户选择已验证且准入的本地镜像创建实例。没有可选镜像时等待管理员构建,不再按 npm 版本或预约构建后创建。镜像不推送至仓库。
- 创建实例后从面板进入 DSH。不要给 runtime 发布宿主端口或挂载 Docker socket。
首次启动创建管理员。修改环境变量不会重设已有账号密码。容器内 /data 必须对应 HOST_DATA_ROOT 指向的同一宿主目录,否则 sibling 容器会挂载错误数据。
新镜像构建前必须检查 Docker 存储空间:将 docker info --format '{{.DockerRootDir}}' 返回的绝对目录填入 DSH_DOCKER_ROOT_DIR。Compose 将该目录以相同路径只读挂载,面板只用 statfs 检查至少 2 GiB 可用空间;这不是授权浏览或修改 Docker 内部文件。映射无法确认、不可访问或空间不足时,新构建拒绝,已有本地镜像复用仍可进行。不要用面板数据盘冒充 Docker 存储盘。
公网 HTTPS 入口(仅可信账号)
反向代理与证书由部署管理员管理,面板仍建议只监听代理可达的私有入口。设置 PANEL_PUBLIC_URL=https://你的域名(仅 origin,不含业务子路径);Compose 默认值仍为本机 HTTP。此值用于 Origin/CSRF 校验,HTTPS值会启用 Cookie 的 Secure 属性(直接TLS请求也会启用),不从客户端的 X-Forwarded-Proto/Host 推断。
默认只按直连来源 IP 限流,不信任 X-Forwarded-For。经反代部署时,设置 TRUSTED_PROXY_CIDRS 为面板实际看到的、逗号分隔的代理来源CIDR(单个IPv4/IPv6地址须写成 /32 或 /128),且确保只有这些代理能连接后端、代理正确维护转发链;不要填写全网 CIDR。未设置时,代理后所有用户将共享该代理来源的每分钟登录额度。公网开放前应实际验证不同客户端来源识别、登录/刷新/退出、DSH iframe、WS/SSE及插件设置接口。
账号由管理员创建,不支持不可信租户。软配额、同源插件与 Docker socket 权限边界不因 HTTPS 而改变;本版本不承诺抵御分布式拒绝服务。
配置与全量备份
实例所有者有完整 home 读写权限,包括自己的凭据。通用文件管理、DSH 终端及 agent 不受专用 Profile 编辑器的 YAML 校验或停机流程限制;这不是 write-only 凭据系统。文件下载和目录 ZIP 可包含明文密钥,不等同于下述加密配置备份。提示可帮助避免误操作,但无法阻止所有者导出凭据。共享下载文件前应自行检查,修改运行中配置前建议先停机和备份。
面板提供加密配置与资源备份:固定配置、插件清单/锁文件、skins、pets、agent-presets、.agent-presets及manifest;不包含会话、node_modules或工作区。新包用创建时的登录密码经Argon2id派生密钥、AES-GCM加密,不依赖或创建 backup.key。导入/恢复使用原备份密码;修改登录密码不会重加密旧包。旧版密钥包仍兼容,只有这类包依赖原 backup.key,应单独安全保管,不能因升级删除。所有包均可能含凭据。
配置备份自动停止原运行实例并恢复运行;恢复先校验包,再快照、写入、启动验证,失败尝试回滚,成功保留原运行/停止状态。当前模板7启动前按清单协调插件;历史跨版本及离线失败验证范围见 ACCEPTANCE.md,不保证任意第三方插件兼容。配置内容上限32 MiB/4096文件,备份入口最多保留20个包;快照和保留上限需管理员监控清理。此流程不能代替全量备份。
要获得一致的全量备份,先通过面板停止所有实例,再停止面板,使用宿主备份工具复制专用数据目录(包含数据库、可能存在的 WAL 文件、工作区、实例 home、快照和密钥)。只停止面板不会自动停止 sibling 实例。恢复时保留权限和所有权,将整个目录恢复到同一部署的配置路径;不要将运行中的数据库文件单独覆盖。备份应在隔离环境定期验证。
升级与故障
镜像兼容验收与准入
管理员 Git 发布构建会执行基础验证,但不能代替完整上线验收。准入前应在独立测试环境核验精确镜像的启动、认证/代理、终端与流式连接、文件读写、插件协调、配置备份恢复,以及所需升级/回滚路径;记录版本、模板、平台、镜像ID、测试范围和证据位置。不要在验收依据中粘贴凭据,也不要仅填写“构建成功”。本次实现不自动启动待验收镜像供普通用户使用,不自动运行真实部署测试。
在镜像页“准入管理”选择推荐或兼容并填写依据,后端还会检查精确镜像身份、成功验证记录和可支持的挂载布局。审批只作用于该镜像;新镜像需要自己的验证与审批。退为仅存量后不允许新建/升级目标;标为禁止使用还阻止新的启动/重启及受控配置修改、备份/恢复写操作,避免其自动重启绕过禁用。已有配置读取和通用home文件管理仍保留,不会自动停止正在运行的实例。若需要立即隔离已运行实例,管理员必须另行确认并执行停止操作。
当前准入仅支持模板7,使用完整home作为 /workspace;旧模板的独立 /data/dsh-home 与用户共享 /workspace 布局不再支持准入。升级也要求原容器只有实例home到 /workspace 的单一bind挂载,不会迁移旧共享布局;不符合时返回409。处理历史实例前先做好完整备份。
运行时认证材料由面板首次成功连接实例时提取,并使用 auth-signing.key 派生的密钥加密保存在数据根的 runtime-auth/(不在用户home内)。按容器 ID 和启动时间绑定,不会将旧启动周期凭据用于新容器;不同 Host 分别缓存 cookie。全量备份应连同签名密钥和该目录保存,不要向用户公开。
运行时返回401/403时会使该Host的cookie缓存失效,下次请求重新交换。不会自动重放写请求,避免插件操作重复执行;遇到认证失败请重新加载页面后确认操作结果。若runtime自行轮转了启动token且旧token也不可交换,需要管理员处理,不能保证任意上游认证机制变更自动恢复。
升级到本版本后应在维护窗口逐个访问现有实例,完成首次认证持久化。若在首次成功连接前,启动token已被Docker日志轮转删除,本版本无法凭空恢复;先保存工作,再由管理员安排重启相关实例并访问验证。不要为此删除或重建实例。初次日志扫描上限64 MiB,常规每文件10 MiB/3份保留在此范围内。
- 实例升级选择已经启动验证且准入的本地模板7镜像并提供当前登录密码,面板自动停机、创建密码配置备份与home快照、启动验证及失败回滚,无需先手动停止。home快照最大1 GiB;允许受约束的内部相对链接以支持pnpm,拒绝不安全链接/特殊文件。模板7的
/workspace是实例home,包含在home回滚内;旧共享挂载布局会在升级前被拒绝。跨DSH版本历史验证范围见 ACCEPTANCE.md。 - Profile是例外:当前需先停止实例,再使用“保存并启动”;页面包含Monaco和脱敏配置预览。不要把备份/升级自动停启理解为Profile可在线改写。
- 若实例进入
error,不要直接修改数据库状态强制启动。保留原容器、实例目录与upgrade_journal记录,检查实际容器与 journal 的旧/新容器 ID、阶段、快照路径是否一致。 - 人工恢复前停止相关容器并另做完整备份。中断点不同,不能统一用一次目录覆盖或状态修改处理;当前无自动灾难恢复命令。
- 删除实例会删除其容器、网络、实例目录(含 home 和快照)及专属备份,无法撤销;请事先下载需要保留的数据。清理失败保留 error 实例,修复存储问题后重试删除。未完成升级恢复的实例仍禁止删除。
Git 分支发布运行镜像构建配置
发布源默认为本仓库 https://git.misaka.ren/m1saka/dsh-web.git 的 dockerfile 分支,通过 DSH_RELEASE_REPOSITORY / DSH_RELEASE_BRANCH 配置。它发布的是构建文件,不是预构建镜像,也不是面板程序自身的更新。修改配置后需重新创建面板容器。
直接运行面板二进制时需要安装 git;面板 Dockerfile 已包含该依赖。当前通道仅支持无需认证的 HTTPS 仓库,不读取宿主 Git 凭据、不跟随重定向,也不支持 SSH 或 URL 内嵌令牌。私有仓库认证需另行扩展,不能把访问令牌写入仓库地址。
release.json 和完整的 image/ 目录仅在 dockerfile 发布分支维护,main 不再保留或打包这些文件。发布分支根目录放 release.json;发布时一起提交 Dockerfile、Caddyfile、entrypoint 和全部安装/适配脚本。只把 Dockerfile 单独放到分支上是不完整的发布。不要把 .env、私钥、凭据、用户数据或 .git 放进构建目录;.dockerignore 只是辅助措施,发布者仍须审查内容。
Git 通道直接向 Docker API 发送构建上下文,不执行 Docker CLI 的 .dockerignore 过滤。面板会排除 .git、.env 系列和常见凭据文件,并拒绝链接、特殊文件及越界路径,但不能识别任意文件名下的秘密。构建归档最大 32 MiB;Git 获取有 45 秒超时及文件大小限制,建议发布分支仅保留发布所需文件。
release.json 示例:
{
"schema_version": 1,
"release_id": "runtime-7.1",
"version": "0.2.0-rc.2",
"deployment_schema": 7,
"platforms": ["linux/amd64"]
}
version 是精确 DSH 版本;release_id 是构建配置发布编号,修改脚本或 Dockerfile 后即使 DSH 版本不变,也应使用新的编号。deployment_schema 声明运行布局兼容协议,当前 Git 发布通道支持协议 7:实例 home 是唯一bind挂载,目标为 /workspace,不挂载共享目录 /shared,代理端口为8080,并保留现有认证与启动约定。它不应随普通构建修订递增;不兼容的运行约定需要面板新增支持。platforms 只能声明发布者实际验证的平台。
首次发布时,在独立 checkout 或 worktree 准备 dockerfile 分支,将 release.json 和完整 image/ 目录提交并推送到同一远程仓库。无需切换正在开发的主工作目录,也无需 CI。此功能的代码安装本身不会替你创建或推送远程分支;分支未发布前,检查失败是预期状态,旧实例不受影响,但不会回退到内置构建。这是 breaking change:移除 /api/latest、/api/compatibility、POST /api/builds 及 /api/intents 的全部查询、创建和取消接口,不提供 410 兼容入口。仅保留 GET /api/builds 和 GET /api/builds/{id} 构建历史读取。不迁移或恢复旧构建及旧创建意图,不在后台续跑或完成旧预约;这不意味着删除已有实例或数据。
管理员打开镜像页会检查 Git 发布,页面保持打开时每五分钟复查,也可手动刷新。确认构建后面板以固定 commit 获取文件;确认期间分支发生变化会拒绝并要求重新检查。构建任务记录发布编号、仓库和 commit,同一 DSH 版本的新构建发布不会误用旧版本构建任务。构建与验证成功后才可选择镜像;不会自动升级任何已有实例,升级仍走密码确认、备份、快照和失败回滚流程。
该分支是可执行代码信任边界:限制写权限,发布前审查 Dockerfile 与脚本,不向不可信仓库开放构建。可信发布只替代内置版本名单,不替代完整的上线验收;应在发布前验证启动、代理/认证、文件访问、插件以及升级恢复。管理员对精确镜像设置的禁用策略仍然有效。上游依赖、基础镜像和网络变化可能影响重建结果,commit 固定并不意味着所有外部依赖都已固定。Git 故障不会停止已有实例。
创建与构建恢复记录
创建失败不代表 Docker 没有创建资源。面板在发请求前记录 runtime_creates(实例 ID、容器/网络结果待确认标志及其置位时间)及 image_build_checks(构建 ID、镜像 ID、已确认的验证容器 ID、记录创建时间),已确认网络身份保存在 runtime_networks。资源可见后按身份校验再清理;已记录 ID 时按 ID 查询,不能把名称消失当成资源删除。身份不一致时拒绝删除。构建验证残留在启动时及后台重试回收,无需新建构建任务触发。
创建请求失联后,确定性名称的 404 仍视为结果不确定,不会仅因经过一分钟而清除恢复依据:实例保持 error 并占用名额,资源迟到出现后可重试删除并按身份清理;验证容器的不确定记录保留并后台降频重试、审计记录 build.cleanup_error,不会每秒新增审计。即使恢复记录的时间戳缺失或损坏,也不能据此推断创建已结束。一直未出现的资源需要按下述人工确认流程处理,不能保证所有失联创建自动释放预约。删除实例成功后总是清理其 runtime_creates/runtime_networks 行。数据库暂时不可写时,构建终态事务独立重试,不阻挡后续构建;面板退出后未提交的任务按中断失败处理,不承诺恢复为成功。
人工确认流程(仅部署管理员,不能因单次404直接改库):
- 停止面板接收新操作并停止面板进程,备份数据库(含 WAL)及相关恢复记录。确认 Docker daemon 健康,并结合 daemon 日志确认原创建请求已结束;必要的 daemon 维护会影响其他容器,应单独安排。
- 根据精确实例/构建 ID 核查
dsh-inst-<instance-id>网络与容器,或dsh-build-check-<build-id>验证容器;已记录容器 ID 时还必须按 ID 查询,不能仅按名称判断不存在。核对归属标签、镜像及名称,不运行全局 prune。 - 资源存在且身份一致时,优先恢复面板并走正常删除/自动回收;身份冲突则保留记录,先调查冲突来源。
- 只有确认原请求终结、对应资源确实不存在后,才可在维护备份基础上用 SQLite 事务清除该精确 ID 的待确认标志,或删除该精确 build ID 的
image_build_checks行。不得批量清空恢复表,不修改实例为 running。重新启动面板后重试正常删除,并核对实例名额、构建状态与 Docker 资源。
容量与网络
实例文件请求只在检查归属和登记使用状态时取得生命周期锁,上传/下载期间不持全局生命周期锁。同一实例存在文件操作时,启停、删除、配置修改、恢复及升级返回 409,等待传输结束后重试。文件树单目录最多列出 10000 项,超限返回 413;用量与快照遍历仍有总条目/深度上限。此限制不是文件系统硬配额。
用户软配额覆盖工作区与实例目录,已接入上传、Profile增长、恢复/升级快照的写前检查及写入串行化,并每5分钟巡检记录超额/恢复审计;覆盖范围见 ACCEPTANCE.md。它不能阻止DSH直接写盘或插件安装增长。监控数据目录和Docker存储空间,快照也消耗容量。硬quota需宿主文件系统和管理员配置,是可选增强,不是默认路线必做项。
每个实例配置独立bridge、非root、只读根文件系统和资源上限,但并非完整网络安全边界:未限制访问宿主、局域网或互联网。公网不可信租户需另外设计独立origin、可信TLS入口与出站策略,并按威胁模型评估硬配额。不要给runtime发布端口;server-held DSH cookie不下发浏览器,不能代替面板归属鉴权。
验收
按版本分层记录的验收结果见 ACCEPTANCE.md。以下命令是检查入口;历史通过记录不能替代当前版本回归,自动化测试不能替代实际公网域名与浏览器验收。
bash scripts/check-trial.sh
该脚本执行本地自动化测试、race、静态检查及前后端构建,不启动容器、不安装依赖、不部署。需提前准备 Go/Node/npm 和前端依赖。可通过 TEST_TMPDIR 指定已存在的临时目录。面板构建单测已隔离真实磁盘空闲量;生产磁盘检查仍保留。image/init-profile.test.mjs 属于发布分支的 runtime 测试,不在 main 的检查脚本中运行。
scripts/test-panel.mjs 必须指向独立测试部署,通过 TEST_PANEL_URL、TEST_USERNAME、TEST_PASSWORD 指定访问参数;TEST_UPGRADE=1 包含同镜像替换升级。运行前需由管理员通过 Git 发布构建准备已验证且准入的本地 runtime 镜像;可用 TEST_FROM_VERSION / TEST_TO_VERSION 选择源和目标版本。脚本不提交构建,只复用这些镜像,会创建及删除测试实例,删除时一并清理实例数据。不要对生产账号运行。scripts/smoke-image.sh <image-id> 和 scripts/test-mobile-image.mjs <image-id> 仅测试已有镜像的运行行为,不读取 main 的构建源;仅在隔离测试环境使用。旧构建脚本和构建后创建意图的 live 测试已删除。
手动备份空间管理
实例备份包和历史完整工作区快照必须由用户显式删除,不执行自动清理。快照列表与删除接口见 API.md。删除快照会降低工作区逻辑用量;加密包在独立 backups 目录,不计入当前工作区软配额。快照大小不等于物理磁盘占用,硬链接按路径重复计数;size_error 表示无法完整扫描,应显示未知而非零。
恢复失败、未完成升级及恢复日志不可读时,列表仍报告保护状态,但禁止删除包和快照。先完成恢复并核实运行状态和升级日志,不要为了释放空间直接修改日志状态。home 永远不属于历史快照管理对象。删除不需要停机或 Docker 可用,也不会触发重启。
文件删除与 SQLite 提交不具备跨系统原子性。若加密包已删除而索引提交失败,接口明确返回500;重试相同删除会清理残留索引。快照删除先持久化 snapshot.delete.started,失败则不触碰目录;递归删除期间不占用 SQL 连接或事务,仍保留实例锁和用户锁。随后分别记录 snapshot.delete.completed 或 snapshot.delete.failed。终态审计写入失败返回500;只有 started 的记录也可能表示进程中断或操作仍在进行,不代表删除成功,应由管理员核实目录和审计记录。目录删除中途遇到存储错误可能部分完成,排除故障后再重试。明确不安全路径返回400,权限或 I/O 故障返回500,缺失快照删除返回404(列表为空)。
会话有效期
在 .env 设置 SESSION_TTL=24h(默认),七天可设置为 168h。使用 Go duration 单位 h、m、s,不支持 d;必须至少一秒且为整秒,非法值会阻止启动。修改后运行 docker compose up -d --force-recreate panel。新登录使用新期限;已有会话保留原绝对到期时间,刷新不会延长。access token签发有效期为五分钟,但数据库会话过期或被撤销后,即使token尚未到期也不能认证。修改自己的密码或管理员重设密码会撤销该用户所有会话,需要重新登录。.env 由 Compose 读取,直接运行二进制时应在进程环境中导出 SESSION_TTL。