Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion deploy/node/node_install.py
Original file line number Diff line number Diff line change
Expand Up @@ -807,7 +807,9 @@ def service_environment(environment):
return ""
direct = "localhost,127.0.0.1,::1"
for name in ("no_proxy", "NO_PROXY"):
settings[name] = ",".join(filter(None, (settings.get(name, "").strip(), direct)))
entries = [entry.strip() for entry in settings.get(name, "").split(",") if entry.strip()]
known = {entry.lower() for entry in entries}
settings[name] = ",".join(entries + [entry for entry in direct.split(",") if entry not in known])
if any(set(value) & {"\n", "\r", "\0"} for value in settings.values()):
raise InstallError("A proxy setting contains a line break; fix it, then rerun the command." + NOTHING_CHANGED)
return "".join(name + '="' + value.replace("\\", "\\\\").replace('"', '\\"') + '"\n' for name, value in sorted(settings.items()) if value)
Expand Down Expand Up @@ -1362,6 +1364,9 @@ def uninstall_system(args):
(SYSTEM_RECORDS / (args.installation_id + ".env")).unlink(missing_ok=True)
print("Node for installation " + args.installation_id + " uninstalled from this host.")
release_account(account, account_record)
if sys.argv[0].endswith(".pyz"):
cache = plain(str(Path(sys.argv[0]).resolve().parent), sys.stdout.encoding)
print("Kept the downloaded installers in " + cache + ". Remove them with `rm -f " + cache + "/*.pyz`.")


def release_account(account, record):
Expand Down Expand Up @@ -1394,6 +1399,7 @@ def release_account(account, record):
print("Removed " + SERVICE_USER + " from the " + group + " group, which this installer added.")
if record.get("home_mode") and SERVICE_HOME.is_dir() and not SERVICE_HOME.is_symlink():
os.chmod(SERVICE_HOME, int(record["home_mode"], 8))
print("Kept the " + SERVICE_USER + " user and " + str(SERVICE_HOME) + ", which existed before this installer.")
(SYSTEM_RECORDS / "account.json").unlink()
with contextlib.suppress(OSError):
SYSTEM_RECORDS.rmdir()
Expand Down
6 changes: 4 additions & 2 deletions deploy/node/test_node_install.py
Original file line number Diff line number Diff line change
Expand Up @@ -1000,8 +1000,10 @@ def test_uninstall_leaves_an_adopted_account_as_found(self):
self.assertFalse([call for call, _ in self.calls if call[:1] == ["useradd"]])
self.assertEqual(json.loads((system / "etc/account.json").read_text())["created"], False)
self.calls.clear()
with mock.patch.object(installer, "open_request", side_effect=urllib.error.HTTPError("https://core.example", 401, "", {}, None)):
installer.uninstall_system(SimpleNamespace(installation_id=self.args.installation_id, force=False))
with mock.patch.object(installer.sys, "argv", ["/home/me/.oac/node-bootstrap/abc.pyz"]):
output = self.uninstall_output(False)
self.assertIn("Kept the oac-node user and", output)
self.assertIn("rm -f /home/me/.oac/node-bootstrap/*.pyz", output)
commands = [call for call, _ in self.calls]
self.assertNotIn(["userdel", "oac-node"], commands)
self.assertIn(["gpasswd", "--delete", "oac-node", "docker"], commands)
Expand Down
2 changes: 2 additions & 0 deletions deploy/node/test_node_proxy.py
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,8 @@ def test_service_keeps_the_installer_proxy_in_a_private_file(self):
self.assertIn('https_proxy="' + secret + '"\n', content)
self.assertIn('HTTPS_PROXY="' + secret + '"\n', content)
self.assertIn('no_proxy="internal.invalid,localhost,127.0.0.1,::1"\n', content)
self.assertIn('NO_PROXY="Localhost,internal.invalid,127.0.0.1,::1"\n', installer.service_environment(
{"https_proxy": secret, "NO_PROXY": "Localhost,internal.invalid"}))
self.assertEqual(installer.service_environment({"no_proxy": "internal.invalid"}), "")
with tempfile.TemporaryDirectory() as directory:
path = Path(directory) / "node.env"
Expand Down
2 changes: 1 addition & 1 deletion docs/getting-started/nodes.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ The [reset contract](../../contracts/agents-api/sandbox-deployment.md#generation
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. **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.

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/<sha256>.pyz --uninstall --installation-id <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-<installation-id>.service` and in `/etc/oac-node/<installation-id>.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.
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/<sha256>.pyz --uninstall --installation-id <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-<installation-id>.service` and in `/etc/oac-node/<installation-id>.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. The output lists what it kept: an adopted account with its home `/var/lib/oac-node`, and the downloaded installers in `~/.oac/node-bootstrap`, which `rm -f ~/.oac/node-bootstrap/*.pyz` removes.

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 <store>`); 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.

Expand Down
4 changes: 2 additions & 2 deletions docs/zh/getting-started/nodes.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
title: "添加和管理节点"
source: docs/getting-started/nodes.md
source_hash: 0933f59c941d62b9b8a23c31c50f8cbc72f7b013c1103363366c8b075a286d98
source_hash: b6c4b068edb9c7ecd3b59e105ef77ea05d39e9b8a2eae90f0f2bbd30d826e3aa
---

节点是一台 Linux 主机,在沙箱提供商为 microsandbox(推荐)或 Docker 时,为 Core 托管 Session 运行沙箱。Core 将新 Session 分配给有空余容量的节点;节点创建沙箱,沙箱回连 Core。E2B 不需要节点。应用为自己的 Session 连接的机器是[自托管执行器](self-hosted.md),而不是节点。
Expand Down Expand Up @@ -116,7 +116,7 @@ root 只准备账号、组和服务单元;其他操作(包括 Docker 网络
1. 在 Web 打开 **节点**,在节点页面选择 **移除节点**,或在列表行选择 **移除**,再选择 **确认移除**。节点仍持有沙箱、快照或待清理资源时,Core 拒绝操作;等待完成或归档对应 Session。移除是永久的:主机只能作为新节点重新加入。移除所有节点仍保留沙箱配置。
2. Web 随后展示 **清理主机** 和卸载命令,在主机执行。**节点** 页的 **清理主机** 随时展示同一条命令:主机早先已移除、部署重置使其节点失效,或主机从未注册,都可使用。

卸载先向节点注册时使用的 Core 地址确认节点是否已移除;Core 仍列出该节点时拒绝卸载。注册地址与当前公开 URL 不同时,对话框还展示 **旧 Core 地址已失效?**:该地址不再响应时,提供带 `--force` 的命令以跳过检查;先在 **节点** 页面移除节点。Core 已不存在时 Web 也不在了:在主机用原命令留下的安装程序执行 `sudo python3 ~/.oac/node-bootstrap/<sha256>.pyz --uninstall --installation-id <installation-id> --force`。安装 ID 在单元名 `oac-node-<installation-id>.service` 和 `/etc/oac-node/<installation-id>.json` 中;Web 在 **设置** → **服务与部署** → **安装详情** 中也会展示。卸载停止并移除服务、节点状态、记录、代理设置和 Docker 网络。只有安装程序创建了 `oac-node` 且不再有节点时,才删除该账号;复用的账号仅移除安装程序添加的组。
卸载先向节点注册时使用的 Core 地址确认节点是否已移除;Core 仍列出该节点时拒绝卸载。注册地址与当前公开 URL 不同时,对话框还展示 **旧 Core 地址已失效?**:该地址不再响应时,提供带 `--force` 的命令以跳过检查;先在 **节点** 页面移除节点。Core 已不存在时 Web 也不在了:在主机用原命令留下的安装程序执行 `sudo python3 ~/.oac/node-bootstrap/<sha256>.pyz --uninstall --installation-id <installation-id> --force`。安装 ID 在单元名 `oac-node-<installation-id>.service` 和 `/etc/oac-node/<installation-id>.json` 中;Web 在 **设置** → **服务与部署** → **安装详情** 中也会展示。卸载停止并移除服务、节点状态、记录、代理设置和 Docker 网络。只有安装程序创建了 `oac-node` 且不再有节点时,才删除该账号;复用的账号仅移除安装程序添加的组。输出会列出保留的内容:复用的账号及其主目录 `/var/lib/oac-node`,以及 `~/.oac/node-bootstrap` 中下载的安装程序,可用 `rm -f ~/.oac/node-bootstrap/*.pyz` 删除。

程序不删除 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 <store>`);microVM 可能仍使用存储,请先检查 `pgrep -u oac-node`,再重新卸载以完成账号移除。输出会列出已删除和已保留的内容。可以重复卸载直到完成。

Expand Down
Loading