From a64434ef8be7cbf06090e14c3234d2063123e2a3 Mon Sep 17 00:00:00 2001 From: SaladDay <1203511142@qq.com> Date: Thu, 15 Oct 2026 12:40:29 +0000 Subject: [PATCH 1/2] Show the node uninstall command any time --- apps/web/e2e/nodes.spec.ts | 7 +++++++ .../src/features/sandbox/NodeCleanupDialog.tsx | 15 +++++++++------ .../src/features/sandbox/SandboxManagerView.tsx | 3 +++ apps/web/src/i18n/locales/zh-CN/sandbox.ts | 4 ++++ deploy/node/node_spec.py | 6 ++++-- deploy/node/test_node_spec.py | 7 +++++++ docs/getting-started/nodes.md | 13 ++----------- docs/getting-started/operations.md | 4 ++-- docs/zh/getting-started/nodes.md | 15 +++------------ docs/zh/getting-started/operations.md | 6 +++--- 10 files changed, 44 insertions(+), 36 deletions(-) diff --git a/apps/web/e2e/nodes.spec.ts b/apps/web/e2e/nodes.spec.ts index cb1c8f3e5..51c22b49b 100644 --- a/apps/web/e2e/nodes.spec.ts +++ b/apps/web/e2e/nodes.spec.ts @@ -163,6 +163,13 @@ test("removes a node after confirmation", async ({ page, request }) => { await expect(page.getByRole("heading", { name: "Nodes", level: 1 })).toBeFocused(); }); +test("gives a host's uninstall command at any time from Clean up a host", async ({ page, request }) => { + await openConsole(page, request, "nodes"); + await page.getByRole("button", { name: "Clean up a host" }).click(); + const cleanup = page.getByRole("dialog", { name: "Clean up the host" }); + await expect(cleanup.getByLabel("Uninstall command", { exact: true })).toHaveValue(/--uninstall --installation-id '7f3c2a90-5b1e-4c2d-9e3f-0a1b2c3d4e5f'\)$/); +}); + test("marks a node on an old Core address in its row, beside each node's limit", async ({ page, request }) => { await openConsole(page, request, "nodes", { installation: "stale" }); const row = page.getByRole("row", { name: /core-01/ }); diff --git a/apps/web/src/features/sandbox/NodeCleanupDialog.tsx b/apps/web/src/features/sandbox/NodeCleanupDialog.tsx index cc8a41c0f..f631f418e 100644 --- a/apps/web/src/features/sandbox/NodeCleanupDialog.tsx +++ b/apps/web/src/features/sandbox/NodeCleanupDialog.tsx @@ -7,9 +7,9 @@ import { nodeSourceUrl } from "./core-origin"; import { nodeUninstallCommand } from "./enrollment-command"; import { CommandBlock } from "./node-commands"; -/** A node Core has just removed, with what builds its host's uninstall command. */ +/** What builds a host's uninstall command; `name` is the node Core has just removed, or null for any host. */ export interface NodeCleanup { - name: string; + name: string | null; installationId: string; scriptDigest: string; /** The Core address the node enrolled with, when it is no longer the deployment's; else null. */ @@ -17,7 +17,8 @@ export interface NodeCleanup { } /** - * After Remove: the command that removes the node's service and files from its + * After Remove, or from Clean up a host at any time (also for a host that never + * registered): the command that removes the node's service and files from its * host (deploy/node/node_install.py `uninstall_system`). * The installer first confirms with Core, at the node's own address, that the * node is removed, which holds from the removal on. The command uses sudo unless the shell is already root. A node enrolled with an earlier address may find it gone; then @@ -35,7 +36,7 @@ export function NodeCleanupDialog({ cleanup, open, onClose }: { cleanup: NodeCle const sourceUrl = installation.data ? nodeSourceUrl(installation.data) : null; const command = (force = false) => cleanup && sourceUrl ? nodeUninstallCommand({ sourceUrl, installationId: cleanup.installationId, scriptDigest: cleanup.scriptDigest, force }) : ""; - const stays = cleanup ? t("{{name}} is removed from Core, but its service and files stay on the host.", { name: cleanup.name }) : ""; + const stays = cleanup ? cleanup.name === null ? t("A node's service and files stay on its host until it is uninstalled there.") : t("{{name}} is removed from Core, but its service and files stay on the host.", { name: cleanup.name }) : ""; return {t("Done")}}> {cleanup && !installation.data ?
{installation.isError @@ -44,13 +45,15 @@ export function NodeCleanupDialog({ cleanup, open, onClose }: { cleanup: NodeCle
: cleanup && installation.data && !sourceUrl ?

{join(stays, t("Other machines can't reach this installation's public URL, {{url}}, so no uninstall command can be given.", { url: installation.data.public_url }))}

: cleanup ?
-

{t("{{name}} is removed from Core. To remove its service and files from the host, run:", { name: cleanup.name })}

+

{cleanup.name === null + ? join(t("To remove a node's service and files from its host, run this on the host."), t("Core must no longer list the node, so remove it on this page first. A host that never registered needs no removal.")) + : t("{{name}} is removed from Core. To remove its service and files from the host, run:", { name: cleanup.name })}

{t("It never deletes sandboxes, volumes or images.")}

{cleanup.oldAddress !== null ?
{t("Old Core address gone?")}
-

{join(t("{{name}} still points at the old Core address {{address}}.", { name: cleanup.name, address: cleanup.oldAddress }), +

{join(t("{{name}} still points at the old Core address {{address}}.", { name: cleanup.name ?? "", address: cleanup.oldAddress }), t("If this node's old Core address no longer responds, first remove it on the Nodes page, then add --force to the uninstall command."))}

diff --git a/apps/web/src/features/sandbox/SandboxManagerView.tsx b/apps/web/src/features/sandbox/SandboxManagerView.tsx index c00069381..efb35dc85 100644 --- a/apps/web/src/features/sandbox/SandboxManagerView.tsx +++ b/apps/web/src/features/sandbox/SandboxManagerView.tsx @@ -216,6 +216,9 @@ function SandboxManager({ consoleConfig }: { consoleConfig: SandboxConsoleConfig const actions = <> {localOnly && hostedNodes ? {tCommon("installationNotice.addBlocked")} : null} {hostedNodes ? : null} + {consoleConfig.node_installer && snapshot ? : null} {refreshButton} {hostedNodes && snapshot ? : null} ; diff --git a/apps/web/src/i18n/locales/zh-CN/sandbox.ts b/apps/web/src/i18n/locales/zh-CN/sandbox.ts index 4e61b2b21..ac3e36099 100644 --- a/apps/web/src/i18n/locales/zh-CN/sandbox.ts +++ b/apps/web/src/i18n/locales/zh-CN/sandbox.ts @@ -162,6 +162,10 @@ export const sandbox = { "The command creates the oac-node service user and a system service. It installs no software; if something is missing it stops and says what to install.": "命令会创建 oac-node 服务用户和一个系统服务。它不安装任何软件;缺少什么时会停下并说明要装什么。", "Set a public address other machines can reach before adding nodes.": "添加节点前,请先设置其他机器能访问的公开地址。", "Clean up the host": "清理主机", + "Clean up a host": "清理主机", + "A node's service and files stay on its host until it is uninstalled there.": "节点的服务和文件会一直留在它的主机上,直到在主机上卸载。", + "To remove a node's service and files from its host, run this on the host.": "要删除节点在主机上的服务和文件,请在该主机上运行下面的命令。", + "Core must no longer list the node, so remove it on this page first. A host that never registered needs no removal.": "Core 不能再列出该节点,所以请先在本页移除它。从未注册过的主机无需移除。", "{{name}} is removed from Core. To remove its service and files from the host, run:": "{{name}} 已从 Core 移除。要删除它在主机上的服务和文件,请运行:", "{{name}} is removed from Core, but its service and files stay on the host.": "{{name}} 已从 Core 移除,但它的服务和文件仍留在主机上。", "Other machines can't reach this installation's public URL, {{url}}, so no uninstall command can be given.": "其他机器无法访问本安装的公开地址 {{url}},因此无法生成卸载命令。", diff --git a/deploy/node/node_spec.py b/deploy/node/node_spec.py index 0a3425986..5f9b47d45 100644 --- a/deploy/node/node_spec.py +++ b/deploy/node/node_spec.py @@ -3,6 +3,8 @@ import http.client import json import re +import shlex +import sys import urllib.error import urllib.request import uuid @@ -143,8 +145,8 @@ def fetch(args, token, retained, open_request, allow_enrollment=False, generatio except urllib.error.HTTPError as error: if error.code == 401 and retained is not None and not allow_enrollment: raise SpecificationError("Core no longer accepts this node: it was removed on the Nodes page, or a sandbox " - "deployment change retired it. Uninstall it with node-install.pyz --uninstall " - "--installation-id " + args.installation_id + ", then add the host with a new command.") from None + "deployment change retired it. Uninstall it with: sudo python3 " + shlex.quote(sys.argv[0]) + + " --uninstall --installation-id " + shlex.quote(args.installation_id) + ", then add the host with a new command.") from None if error.code == 404: raise SpecificationError("Core node configuration was not found (HTTP 404); route /api/v1 on the Core origin directly to Core, not to Web") from None if error.code == 409: diff --git a/deploy/node/test_node_spec.py b/deploy/node/test_node_spec.py index 3e3846c59..4d30565b8 100644 --- a/deploy/node/test_node_spec.py +++ b/deploy/node/test_node_spec.py @@ -61,6 +61,13 @@ def test_changed_public_url_says_to_generate_a_new_command(self): with self.assertRaisesRegex(node_spec.SpecificationError, "public URL changed"): node_spec.fetch(self.args, "once", None, mock.Mock(return_value=self.response())) + def test_retired_node_gets_a_runnable_uninstall_command(self): + rejected = urllib.error.HTTPError("https://core.example", 401, "", {}, None) + with mock.patch.object(node_spec.sys, "argv", ["/home/me/.oac/node-bootstrap/abc.pyz"]): + with self.assertRaises(node_spec.SpecificationError) as raised: + node_spec.fetch(self.args, "", self.retained, mock.Mock(side_effect=rejected)) + self.assertIn("sudo python3 /home/me/.oac/node-bootstrap/abc.pyz --uninstall --installation-id " + self.args.installation_id, str(raised.exception)) + def test_failures_name_their_cause(self): for failure, message in ((urllib.error.HTTPError("https://core.example", 404, "", {}, None), "route /api/v1"), (urllib.error.HTTPError("https://core.example", 409, "", {}, None), "resetting"), diff --git a/docs/getting-started/nodes.md b/docs/getting-started/nodes.md index a6961bfa1..790219276 100644 --- a/docs/getting-started/nodes.md +++ b/docs/getting-started/nodes.md @@ -112,18 +112,9 @@ The [reset contract](../../contracts/agents-api/sandbox-deployment.md#generation ## Remove a node 1. In Web, open **Nodes** and choose **Remove node** on the node's page, or **Remove** in its list row, then **Confirm removal**. Core refuses while the node still holds sandboxes, snapshots or pending cleanup; let them finish, or archive their Sessions. Removal is permanent: the host can come back only as a new node. Removing every node keeps the sandbox configuration. -2. Web then shows **Clean up the host** with the uninstall command. Run it on the host: +2. Web then shows **Clean up the host** with the uninstall command. Run it on the host. **Clean up a host** on **Nodes** shows the same command at any time, for a host you removed earlier, after a deployment reset retired its node, or for a host that never registered. - ```sh - (umask 077; d=$(mktemp -d) || exit; trap 'rm -rf "$d"' EXIT; s=; [ "$(id -u)" -eq 0 ] || s=sudo - printf '\n==> Downloading node installer...\n' && - curl -fsS --max-time 30 --max-filesize 1048576 'https://core.example/node-install/node-install.pyz' -o "$d/node-install.pyz" && - printf '==> Verifying node installer...\n' && - printf '%s %s\n' '' "$d/node-install.pyz" | sha256sum -c --status && - $s python3 "$d/node-install.pyz" ${NO_COLOR+--no-color} --uninstall --installation-id '') - ``` - -Uninstall first asks Core, at the address the node enrolled with, whether the node was removed, and refuses while Core still lists it. When the node enrolled with an address other than the current public URL, the dialog also shows **Old Core address gone?**: if that address no longer responds, it gives the command with `--force`, which skips the check; remove the node on the Nodes page first. Without the dialog, take the installer's SHA-256 from the `node-install.pyz` line of `https://core.example/node-install/SHA256SUMS`. Uninstall stops and removes the service, the node state, the records, the proxy settings and the Docker network. It deletes the `oac-node` account only if the installer created it and no node remains; an adopted account only loses the groups the installer added. +Uninstall first asks Core, at the address the node enrolled with, whether the node was removed, and refuses while Core still lists it. When the node enrolled with an address other than the current public URL, the dialog also shows **Old Core address gone?**: if that address no longer responds, it gives the command with `--force`, which skips the check; remove the node on the Nodes page first. If Core is gone, so is Web: run `sudo python3 ~/.oac/node-bootstrap/.pyz --uninstall --installation-id --force` on the host, with the installer the original command left in that directory. The installation ID is in the unit name `oac-node-.service` and in `/etc/oac-node/.json`; Web shows it under **Settings** → **Service and deployment** → **Installation details**. Uninstall stops and removes the service, the node state, the records, the proxy settings and the Docker network. It deletes the `oac-node` account only if the installer created it and no node remains; an adopted account only loses the groups the installer added. It never deletes Docker images or volumes. It keeps the Runtime image and prints the `sudo docker image rm` command. For microsandbox, once Core has confirmed the removal, it also deletes the node's store under `/var/lib/oac-node/.oac/m/` (about 1.5 GB), and with it the created account. With `--force` Core isn't asked, so the store is kept and the output prints the command that deletes it (`sudo -u oac-node rm -rf `); microVMs may still use it, so check `pgrep -u oac-node` first, then rerun uninstall to finish removing the account. The output lists what was deleted and what was kept. Uninstall can be rerun until it completes. diff --git a/docs/getting-started/operations.md b/docs/getting-started/operations.md index 4428daa27..fe3ba1d73 100644 --- a/docs/getting-started/operations.md +++ b/docs/getting-started/operations.md @@ -228,7 +228,7 @@ The upgrade leaves the sandbox configuration unchanged: nodes keep serving the s Three connections require the same protocol version on both sides: Core and its nodes, a node and its microsandbox helper, and Core and the Runtime. The upgrade changes no node host and no running sandbox. A release that changes one of these protocols says so in its release notes and names which of these actions it requires: -- reinstall each node: [remove it](./nodes.md#remove-a-node), clean up the host, then add the host again with **Add node**; +- reinstall each node: [remove it](./nodes.md#remove-a-node), clean up the host, then add the host again with **Add node**. Core refuses to remove a node that still holds sandboxes, so archive those Sessions before you upgrade; - apply the release's Runtime right after the upgrade; sandboxes on earlier generations stop working; - [build a new E2B template](https://github.com/MiniMax-AI/OpenAgentCore/blob/main/services/core/deploy/e2b/README.md#build-a-template) and select it in [the sandbox configuration](./nodes.md#change-the-sandbox-configuration); - reconnect [self-hosted machines](./self-hosted.md#reconnect-an-existing-session). @@ -253,7 +253,7 @@ All data goes with it: Projects and API keys, Session history, stored credential Uninstall stops no sandbox: node sandboxes keep running on their nodes, and E2B sandboxes keep running, and billing, at E2B. While Core is still up, archive their Sessions or [reset the deployment](./nodes.md#change-the-sandbox-configuration) and let it complete. -Nodes on other hosts keep running. To uninstall them the usual way, remove them in Web first, as in [Remove a node](./nodes.md#remove-a-node). After the installation is gone, their Core is gone: on each node host, run the node uninstall command with `--force`, using `node-install.pyz` from the release that installed them and the installation ID that `oac uninstall` printed. The data volume held the ID, so if you didn't note it, read it from the `oac-node-.service` unit on a node host. +Nodes on other hosts keep running. To uninstall them the usual way, remove them in Web first, as in [Remove a node](./nodes.md#remove-a-node). After the installation is gone, their Core is gone: on each node host, run the node uninstall command with `--force`, using the installer the original command left in `~/.oac/node-bootstrap/`, as in [Remove a node](./nodes.md#remove-a-node), and the installation ID that `oac uninstall` printed. If you didn't note it, read it from the node's unit name `oac-node-.service`. If the installation directory was deleted by hand, its containers keep running and keep the Web port. Find the project and remove it without any installation file: diff --git a/docs/zh/getting-started/nodes.md b/docs/zh/getting-started/nodes.md index 5d20a185f..0d8383d39 100644 --- a/docs/zh/getting-started/nodes.md +++ b/docs/zh/getting-started/nodes.md @@ -1,7 +1,7 @@ --- title: "添加和管理节点" source: docs/getting-started/nodes.md -source_hash: 8398432d9a47fa5588fca9d0ffdea96db5407c3ef7b259c892865e5d58da9417 +source_hash: 145fda7f31f290b1d592a5796ed093f89cb2a12d6de51b919fa8570622aee850 --- 节点是一台 Linux 主机,在沙箱提供商为 microsandbox(推荐)或 Docker 时,为 Core 托管 Session 运行沙箱。Core 将新 Session 分配给有空余容量的节点;节点创建沙箱,沙箱回连 Core。E2B 不需要节点。应用为自己的 Session 连接的机器是[自托管执行器](self-hosted.md),而不是节点。 @@ -114,18 +114,9 @@ root 只准备账号、组和服务单元;其他操作(包括 Docker 网络 ## 移除节点 {#remove-a-node} 1. 在 Web 打开 **节点**,在节点页面选择 **移除节点**,或在列表行选择 **移除**,再选择 **确认移除**。节点仍持有沙箱、快照或待清理资源时,Core 拒绝操作;等待完成或归档对应 Session。移除是永久的:主机只能作为新节点重新加入。移除所有节点仍保留沙箱配置。 -2. Web 随后展示 **清理主机** 和卸载命令。在主机执行: +2. Web 随后展示 **清理主机** 和卸载命令,在主机执行。**节点** 页的 **清理主机** 随时展示同一条命令:主机早先已移除、部署重置使其节点失效,或主机从未注册,都可使用。 - ```sh - (umask 077; d=$(mktemp -d) || exit; trap 'rm -rf "$d"' EXIT; s=; [ "$(id -u)" -eq 0 ] || s=sudo - printf '\n==> Downloading node installer...\n' && - curl -fsS --max-time 30 --max-filesize 1048576 'https://core.example/node-install/node-install.pyz' -o "$d/node-install.pyz" && - printf '==> Verifying node installer...\n' && - printf '%s %s\n' '' "$d/node-install.pyz" | sha256sum -c --status && - $s python3 "$d/node-install.pyz" ${NO_COLOR+--no-color} --uninstall --installation-id '') - ``` - -卸载先向节点注册时使用的 Core 地址确认节点是否已移除;Core 仍列出该节点时拒绝卸载。注册地址与当前公开 URL 不同时,对话框还展示 **旧 Core 地址已失效?**:该地址不再响应时,提供带 `--force` 的命令以跳过检查;先在 **节点** 页面移除节点。不使用对话框时,从 `https://core.example/node-install/SHA256SUMS` 的 `node-install.pyz` 行获取安装程序 SHA-256。卸载停止并移除服务、节点状态、记录、代理设置和 Docker 网络。只有安装程序创建了 `oac-node` 且不再有节点时,才删除该账号;复用的账号仅移除安装程序添加的组。 +卸载先向节点注册时使用的 Core 地址确认节点是否已移除;Core 仍列出该节点时拒绝卸载。注册地址与当前公开 URL 不同时,对话框还展示 **旧 Core 地址已失效?**:该地址不再响应时,提供带 `--force` 的命令以跳过检查;先在 **节点** 页面移除节点。Core 已不存在时 Web 也不在了:在主机用原命令留下的安装程序执行 `sudo python3 ~/.oac/node-bootstrap/.pyz --uninstall --installation-id --force`。安装 ID 在单元名 `oac-node-.service` 和 `/etc/oac-node/.json` 中;Web 在 **设置** → **服务与部署** → **安装详情** 中也会展示。卸载停止并移除服务、节点状态、记录、代理设置和 Docker 网络。只有安装程序创建了 `oac-node` 且不再有节点时,才删除该账号;复用的账号仅移除安装程序添加的组。 程序不删除 Docker 镜像或卷。保留 Runtime 镜像并输出 `sudo docker image rm` 命令。microsandbox 在 Core 确认节点已移除后,还会删除节点位于 `/var/lib/oac-node/.oac/m/` 的存储(约 1.5 GB),随后删除所创建的账号。使用 `--force` 时不询问 Core,因此保留存储,并输出删除命令(`sudo -u oac-node rm -rf `);microVM 可能仍使用存储,请先检查 `pgrep -u oac-node`,再重新卸载以完成账号移除。输出会列出已删除和已保留的内容。可以重复卸载直到完成。 diff --git a/docs/zh/getting-started/operations.md b/docs/zh/getting-started/operations.md index b4a031f21..5700c6c4b 100644 --- a/docs/zh/getting-started/operations.md +++ b/docs/zh/getting-started/operations.md @@ -1,7 +1,7 @@ --- title: "运维" source: docs/getting-started/operations.md -source_hash: f200c2e22c60ff8c4e1779321aec0045e265a1a22ed7d1ce89e89b37b0fa2296 +source_hash: c4e6dc4ae4e0167591ecad9e5a8534271c523e655a95d605c5cd7f3902db4876 --- 安装运维人员负责 Core 主机、存储和可用性。节点主机运行各自的服务;参阅[节点](nodes.md)。设置见[配置参考](../configuration.md)。 @@ -230,7 +230,7 @@ v0.0.10 及更高版本的安装可以原地升级到更新的发布版本,并 以下三种连接要求两端的协议版本相同:Core 与节点、节点与 microsandbox 辅助程序、Core 与 Runtime。升级不会改动任何节点主机或正在运行的沙箱。更改其中某个协议的版本会在发布说明中注明,并指出需要以下哪些操作: -- 重新安装每个节点:[移除节点](nodes.md#remove-a-node),清理主机,再通过 **添加节点** 重新添加该主机; +- 重新安装每个节点:[移除节点](nodes.md#remove-a-node),清理主机,再通过 **添加节点** 重新添加该主机。Core 拒绝移除仍持有沙箱的节点,所以升级前先归档这些 Session; - 升级后立即应用该版本的 Runtime;仍在较早代次上的沙箱将停止工作; - [构建新的 E2B 模板](https://github.com/MiniMax-AI/OpenAgentCore/blob/main/services/core/deploy/e2b/README.md#build-a-template),并在[沙箱配置](nodes.md#change-the-sandbox-configuration)中选择它; - 重新连接[自托管机器](self-hosted.md#reconnect-an-existing-session)。 @@ -255,7 +255,7 @@ Windows 上运行 `& "$HOME/.oac/core/oac.exe" uninstall`。命令会展示 Comp 卸载不停止沙箱:节点沙箱在节点继续运行,E2B 沙箱在 E2B 继续运行并计费。Core 仍运行时,归档它们的 Session,或[重置部署](nodes.md#change-the-sandbox-configuration)并等待完成。 -其他主机上的节点继续运行。按常规方式卸载时,先在 Web 移除,见[移除节点](nodes.md#remove-a-node)。安装删除后,它们的 Core 已不存在:在各节点主机使用当时发布版的 `node-install.pyz`,执行带 `--force` 的节点卸载命令,并传入 `oac uninstall` 打印的安装 ID。安装 ID 保存在数据卷中,如果没有记下,可以在节点主机的 `oac-node-.service` 单元名中读取。 +其他主机上的节点继续运行。按常规方式卸载时,先在 Web 移除,见[移除节点](nodes.md#remove-a-node)。安装删除后,它们的 Core 已不存在:在各节点主机用原命令留在 `~/.oac/node-bootstrap/` 的安装程序,执行带 `--force` 的节点卸载命令(见[移除节点](nodes.md#remove-a-node)),并传入 `oac uninstall` 打印的安装 ID。如果没有记下,可以从节点的单元名 `oac-node-.service` 中读取。 如果安装目录被手动删除,其容器会继续运行并占用 Web 端口。不依赖任何安装文件即可找到并移除该项目: From 81265c211f644604c15573f9aa4ca47c0c784815 Mon Sep 17 00:00:00 2001 From: SaladDay <1203511142@qq.com> Date: Thu, 15 Oct 2026 13:07:04 +0000 Subject: [PATCH 2/2] Say what node uninstall keeps --- apps/web/src/features/sandbox/NodeCleanupDialog.tsx | 2 +- apps/web/src/i18n/locales/zh-CN/sandbox.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/web/src/features/sandbox/NodeCleanupDialog.tsx b/apps/web/src/features/sandbox/NodeCleanupDialog.tsx index f631f418e..8ec0f28c0 100644 --- a/apps/web/src/features/sandbox/NodeCleanupDialog.tsx +++ b/apps/web/src/features/sandbox/NodeCleanupDialog.tsx @@ -49,7 +49,7 @@ export function NodeCleanupDialog({ cleanup, open, onClose }: { cleanup: NodeCle ? join(t("To remove a node's service and files from its host, run this on the host."), t("Core must no longer list the node, so remove it on this page first. A host that never registered needs no removal.")) : t("{{name}} is removed from Core. To remove its service and files from the host, run:", { name: cleanup.name })}

-

{t("It never deletes sandboxes, volumes or images.")}

+

{t("It keeps Docker images and volumes; a microsandbox store goes once Core confirmed the removal.")}

{cleanup.oldAddress !== null ?
{t("Old Core address gone?")}
diff --git a/apps/web/src/i18n/locales/zh-CN/sandbox.ts b/apps/web/src/i18n/locales/zh-CN/sandbox.ts index ac3e36099..850fd74c3 100644 --- a/apps/web/src/i18n/locales/zh-CN/sandbox.ts +++ b/apps/web/src/i18n/locales/zh-CN/sandbox.ts @@ -151,7 +151,7 @@ export const sandbox = { "Copy command with --force": "复制命令(--force)", "Checking this installation's public URL…": "正在检查这个安装的公网地址…", "The installation couldn't be read, so no command can be issued.": "无法读取安装信息,暂时不能生成命令。", - "It never deletes sandboxes, volumes or images.": "它不会删除任何沙箱、卷或镜像。", + "It keeps Docker images and volumes; a microsandbox store goes once Core confirmed the removal.": "它保留 Docker 镜像和卷;Core 确认移除后,microsandbox 存储会一并删除。", "Old Core address gone?": "旧 Core 地址已失效?", "{{name}} still points at the old Core address {{address}}.": "{{name}} 仍指向旧的 Core 地址 {{address}}。", "If this node's old Core address no longer responds, first remove it on the Nodes page, then add --force to the uninstall command.": "如果这个节点的旧 Core 地址已无法访问,请先在“节点”页移除它,再在卸载命令后加上 --force。",