关于 deploy-to-cloud-engine
供编码 Agent 使用的技能,可将已能构建的项目部署到用户拥有的 Internet Computer 云引擎。重点是将 CLI 身份关联到引擎控制台,并部署到指定子网,同时处理本地、远程和离线环境。
主要功能
- 01将 CLI 身份关联到引擎控制台
- 02部署到指定子网
- 03处理本地与远程登录
- 04提供离线部署交接方案
- 05检查代理 canister 需求
适用场景
- 01将已构建应用部署到云引擎
- 02将 CLI 关联到引擎管理员身份
- 03在无网络环境准备部署
- 04检查应用是否需要代理 canister
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ dfinity/ icskills --skill deploy-to-cloud-engine安装后在 Claude Code 中输入/deploy-to-cloud-engine确认已加载。
详细介绍
部署到云引擎
这是什么
云引擎是用户拥有的一部分互联网计算机容量,通过 Web 控制台管理(默认地址为 https://opencloud.org)。每个引擎都运行在一个子网上。此技能会通过编码 Agent,将已能构建的项目部署到该引擎上。
此技能仅涵盖云引擎专属步骤:将 CLI 关联到引擎的控制台身份,以及针对特定子网进行部署。关于 CLI 的其他所有内容(icp.yaml、配方、环境、绑定、身份),请加载 icp-cli 技能。
编写任何代码前,先确认运行环境。 常规登录 icp identity link web 只有在 icp 与用户的浏览器运行于同一台机器时才能完成(见步骤 1.0)。如果你在远程或云沙盒中运行,请在构建任何内容前告知用户,并请他们改为启动本地会话,例如,在 Claude 桌面应用中启动会话时使用本地环境,或在他们自己的计算机上使用编码 Agent(Claude Code、Codex CLI、Cursor)。云会话之后无法迁移到他们的计算机上,因此要在构建前询问,而不是构建后,并等待答复。仅当用户没有本地选项时,才在沙盒中继续。此时请先按照步骤 1.0 完成交接,然后仅在沙盒 CLI 以用户身份登录后再进行构建:不要等到部署时才登录。
运行任何不确定的 icp 命令前,请先运行 icp <subcommand> --help(例如 icp identity link --help、icp deploy --help),确认命令和标志存在。不要自行推断标志。权威参考:https://cli.internetcomputer.org/llms.txt
所需信息
需要两个值,某些应用还需要第三个值。请先在 icp.yaml 或之前的对话中查找。其中一个值有默认值;其他值必须向用户询问:
- 控制台源地址 — 用户登录云引擎控制台所用的网址。默认为
https://opencloud.org(OpenCloud 主控制台)。步骤 1 中会将其用作--auth源地址,以便关联的 CLI 身份派生出管理该引擎的同一主体。使用默认值,但在关联前告知用户,并给他们机会更改:- 告知用户:“我会使用默认控制台
https://opencloud.org关联 CLI。如果你登录引擎控制台时使用的是其他网址,现在请告诉我。” - 只有用户明确提供其他源地址时才使用它——绝不要自行替换为其他网址;
--auth源地址决定派生出的主体(见注意事项 2)。
- 告知用户:“我会使用默认控制台
- 子网 ID:引擎要部署到的子网,由
icp deploy --subnet指定。没有默认值;绝不要猜测。用户可以在控制台中引擎的设置页面(引擎标识符下方)找到它,也可以从控制台的命令面板复制。如果没有,请询问用户,在获得 ID 前不要继续:- 询问:“你的引擎子网 ID 是什么?可以在控制台中引擎的设置页面找到。”
- 代理容器 ID:仅当应用调用阈值 ECDSA/Schnorr、vetKD、汇率容器、Bitcoin API 或 EVM/SOL RPC 容器时才需要(查找
PROXY_CANISTER_ID变量,或对proxy方法的调用)。没有默认值;你无法自行创建控制台代理,自行部署的代理也不能替代它(步骤 5)。如果没有,请在部署前询问用户:- 询问:“此应用需要你引擎的代理容器。如果引擎已有代理,请把它的 ID 发给我(引擎 → 容器 → 代理容器)。如果没有,引擎所有者可以在那里通过 Deploy a proxy 部署一个:费用从引擎余额中扣除,无需银行卡。然后把它的 ID 发给我。”
记录这些信息,以免在本次会话中重复询问。
前置条件
$PATH中有icp——请参阅icp-cli技能了解安装方法。使用icp --version验证(此技能中的命令已针对 0.3.0 和 1.0.2 验证)。如果安装的版本不同,请在运行前使用icp <cmd> --help确认标志集——不同主版本之间的标志有所变化。- 一个已能正常构建的项目。如果项目尚不能构建或打包,请先完成相关设置(参阅
icp-cli技能),然后再回到这里。 - 还没有项目,也没有项目描述? 在编写任何代码或创建任何文件之前,先询问用户要构建什么:“你想构建什么应用?”不要自行选择示例应用(如留言簿、计数器或待办事项列表)。如果提示词只有“构建并部署一个应用”,这表示需要先提问,而不是自行选择。
- macOS:
icp会将数据存储在~/Library/Application Support/org.dfinity.icp-cli/下。如果 Shell 无法写入该目录(例如,命令通过桥接运行时被 macOS TCC 拦截并返回Operation not permitted),请在本次会话中将数据重定向到不受保护的路径——HOME=/tmp/icp-home icp …——并在之后的每条icp命令中使用相同的HOME,否则后续命令将无法识别已关联的身份。
第 1 步 — 将 CLI 关联到你的引擎身份(每台机器只需一次)
CLI 必须以管理引擎的同一身份签名——也就是你登录控制台时使用的主体。
第 1.0 步 — 先确定 CLI 相对于浏览器的运行位置
icp identity link web 会通过重定向到 CLI 所在机器上的 http://127.0.0.1:<port> 完成登录。用户完成 Internet Identity 登录时所用的浏览器必须能够访问该回环地址。关联之前,请先判断运行环境:
- CLI 和浏览器在同一台联网机器上(本地开发,或在用户自己的机器上集成于终端的 Agent)→ 使用下方的常规关联流程。
- CLI 在联网的远程或隔离沙盒中运行(Agent 在云端运行;用户的浏览器在另一台机器上)→ 常规流程无法使用:登录 URL 中带有绑定到沙盒的
callback=http://127.0.0.1:<port>,用户的浏览器会将127.0.0.1解析为自己的机器,因此委托凭证无法传回 CLI。之后的命令会因授权错误而失败。请先让用户切换到本地会话(参阅“这是什么”)。只有在用户无法切换时,才使用下方的委托交接流程。 - 完全无法联网的 CLI Shell(例如,运行在用户自己机器上的沙盒设备桥接环境中:DNS 被阻止、HTTPS 请求失败、写入仅限于
/tmp)→ 任何icp网络命令都无法在此运行——包括关联命令和icp deploy。委托交接也无济于事:它只能转移签名权限,不能提供网络访问。不要重试、建立隧道或代理。请准备好项目,并给用户一份脚本,让用户在自己的真实终端中运行——参阅下方的“无网络 CLI 主机”。
不确定时,请在关联前进行探测:如果在 CLI Shell 中运行 curl -sI https://<console-origin> 失败(或 DNS 无法解析),就属于无网络情况。
切勿向用户提供沙盒中的 127.0.0.1 URL 并让其在浏览器中打开——用户的机器无法访问该地址。也不要为 icp identity link web 虚构无头模式标志:截至 icp-cli 1.0.2,该命令没有此类标志(请使用 icp identity link web --help 确认)。
委派交接(CLI 主机和浏览器位于不同机器上)
icp identity delegation 会将用户机器上关联的身份的签名权限转移给 CLI 主机上的会话密钥,且浏览器无需访问 CLI 主机。用户的机器上也必须安装 icp,此功能在 icp-cli 1.0.x 中提供——请在两台机器上使用 icp identity delegation --help 确认。如果用户的机器上已安装 icp,且你可以在那里运行命令,最简单的方法就是直接在那里运行整个技能;否则:
-
在 CLI 主机(沙箱)上——使用新的会话密钥创建待处理身份:
icp identity delegation request <your-identity-name>命令会将会话公钥以 PEM 格式输出到 stdout。将该 PEM 区块发给用户。(如果默认的
--storage keyring在无头沙箱中失败,请改用--storage plaintext重试。) -
在用户的机器上——如果用户尚未关联身份,请先在本机完成关联(
icp identity link web <local-name> --auth <console-origin>——常规流程可在本机正常运行),然后将 PEM 保存到文件,并为其签署委派:icp identity delegation sign --identity <local-name> --key-pem session-key.pem --duration 8h > delegation.json--duration可使用30m、8h、1d等值;也可以使用--canisters <ids>限制委派范围。用户会将delegation.json发回。如果sign失败并提示delegation for identity <name> has expired or will expire within 5 minutes,说明本地 Web 会话已失效——运行icp identity reauth <local-name>(常规浏览器流程,可在本机正常运行)后重试。 -
在 CLI 主机上——完成待处理身份并激活:
icp identity delegation use <your-identity-name> --from-json delegation.json icp identity default <your-identity-name> icp identity principal # must print the user's console principal
将 delegation.json 视为有时限的凭证:任何持有沙箱会话密钥的人,都可以在凭证过期前以用户的控制台主体身份签名。请缩短有效时长,并在凭证失效时重新进行交接。
CLI 主机无法联网——交给用户一个脚本
如果 CLI shell 无法访问网络,用户的实际终端就是部署机器——由于他们的终端和浏览器共享回环地址,因此常规关联流程可以正常运行;无需进行委派交接。你的任务转为准备工作:
-
将构建好的项目放到用户终端可访问的目录中。
-
在项目根目录编写一个脚本,完成整个需要联网的操作序列——根据此模板进行调整(填入真实的身份名称、控制台源地址和子网 ID;不要保留占位符):
#!/usr/bin/env bash set -euo pipefail icp --version icp identity list | grep -E '^\*? *<your-identity-name> ' >/dev/null || icp identity link web <your-identity-name> --auth <console-origin> icp identity default <your-identity-name> icp deploy -e ic --subnet <subnet-id> -
请用户在终端中运行脚本,在浏览器打开时完成 Internet Identity 登录,并将输出粘贴回来。
-
根据粘贴的输出(canister ID、前端 URL)进行验证并继续——针对 Step 2 中
__META_*变量重新部署时,也要使用同一脚本。
常规链接流程(CLI 和浏览器共用一个回环)
先检查已有内容:
icp identity list # names + principals; * marks the active identity
列表不会显示某个身份是否关联了某个控制台(如果有的话)——无法通过 CLI 确定。按以下方式判断:
- 列表中只有
anonymous(或普通本地身份)——不存在关联了 Web 的身份;运行下面的链接命令。 - 某个身份是用户认得的引擎身份(按名称或主体判断)——将其设为活动身份(见下文),然后跳到步骤 2。
- 不确定——询问用户,或直接用新名称重新关联;再次关联成本低且安全。
要进行关联,请运行以下命令,并替换为用户选定的名称——<your-identity-name> 是任意本地标签,并非固定值(不要硬编码类似 my-engine-admin 的名称);在下面的所有命令中重复使用同一个名称:
icp identity link web <your-identity-name> --auth <console-origin>
-
除非用户指定了其他控制台,否则将
https://opencloud.org用作<console-origin>。切勿省略--auth:该标志有一个内置默认值(https://id.ai),它不是你的控制台,并且会静默地推导出错误的主体。 -
命令会先停在 “Press Enter to log in” 提示处,之后才会执行任何操作。可以交互时,请以交互方式运行;在非交互式 Shell 中(例如后台进程),请通过管道输入一个实际的换行符:
printf '\n' | icp identity link web <your-identity-name> --auth <console-origin>不要将 stdin 重定向到
/dev/null——单独的 EOF 无法满足提示要求,命令会无限期停留在 “Press Enter to log in” 状态,也不会打开浏览器。 -
按 Enter 后,会打开一个浏览器标签页。由用户在该页面完成 Internet Identity 登录。请等待用户确认后再继续——你无法代替用户完成登录。
-
--auth必须是控制台的确切源(协议 + 主机),例如https://opencloud.org。源不匹配会推导出不同的主体,引擎会以未经授权为由拒绝部署。 -
这是一个每台机器仅需执行一次的步骤。
然后将其设为活动身份并进行验证:
icp identity default <your-identity-name>
icp identity default # prints the active identity name
icp identity principal # prints the principal the deploy will sign as
步骤 2 — 在控制台中为应用命名(并设置图标)(推荐)
默认情况下,通过 CLI 部署的 canister 会在引擎控制台的「Applications」页面中显示为没有名称的行,标签只有其主体 ID。通过一组 canister 环境变量,控制台会将它们归入一个命名应用,并显示易读的各 canister 标签、「Open」按钮和图标。在项目配置中设置一次即可:
__META_PROJECT— 应用名称。具有相同值的 canister 会归入同一个命名应用,因此请在应用的每个 canister 上设置相同的值。__META_NAME— 各 canister 的显示标签(例如Backend、Frontend)。__META_MAIN_CANISTER— 在且仅在一个 canister 上设置字面字符串"true"(入口点,通常是前端/资产 canister)。这会将该 canister 标记为应用的主 canister:控制台只会从中读取__META_BASE_URL和__META_ICON_PATH,「Open」按钮也会指向它。__META_BASE_URL— 主 canister 上设置的绝对https://URL(例如前端 canister 的 URLhttps://<frontend-canister-id>.icp.net,或自定义域名)。存在且有效时,「Open」按钮会打开此 URL;未设置或不是https时,「Open」按钮会回退到主 canister 的网关 URL。它也是解析__META_ICON_PATH的基础 URL。__META_ICON_PATH— 应用图标的路径,会基于__META_BASE_URL解析,形成控制台渲染的图标 URL(例如/favicon.svg→https://<base>/favicon.svg)。请将其与__META_BASE_URL一起设置在主 canister 上。
图标和「Open」链接只从主 canister(标记为 __META_MAIN_CANISTER: "true" 的 canister)读取——其他任何 canister 上的 __META_BASE_URL/__META_ICON_PATH 都会被忽略。
请在每个 canister 的 settings.environment_variables 下设置这些变量——它们可以与 recipe 一起使用。使用各 canister 独立的 canister.yaml 文件时:
# frontend/canister.yaml
name: frontend
recipe:
type: "@dfinity/[email protected]"
configuration:
build:
- npm install
- npm run build
dir: dist
settings:
environment_variables:
__META_PROJECT: "My App"
__META_NAME: "Frontend"
__META_MAIN_CANISTER: "true"
__META_BASE_URL: "https://<frontend-canister-id>.icp.net"
__META_ICON_PATH: "/favicon.svg"
# backend/canister.yaml
name: backend
recipe:
type: "@dfinity/[email protected]" # v5 reads main/candid from mops.toml ([canisters.backend]) — see the icp-cli skill
settings:
environment_variables:
__META_PROJECT: "My App"
__META_NAME: "Backend"
对于单个内联 icp.yaml(canister 直接定义在其中),请在每个 canister 条目下放置相同的 settings.environment_variables 块。注意内联形式:canisters 是由 {name, recipe, settings} 项组成的数组,而不是以 canister 名称为键的映射:
# icp.yaml — canisters defined inline
canisters:
- name: frontend
recipe: # … as in the canister.yaml example above
settings:
environment_variables:
__META_PROJECT: "My App"
__META_NAME: "Frontend"
__META_MAIN_CANISTER: "true"
__META_BASE_URL: "https://<frontend-canister-id>.icp.net"
__META_ICON_PATH: "/favicon.svg"
- name: backend
recipe: # … as in the canister.yaml example above
settings:
environment_variables:
__META_PROJECT: "My App"
__META_NAME: "Backend"
注意:
- icp-cli 会将这些变量与其在部署时自动注入的
PUBLIC_CANISTER_ID:<name>变量合并——资产 canister 会继续提供服务,应用也会继续运行。(已针对 icp-cli 0.3.0 验证。) - 所有值都是字符串;
__META_MAIN_CANISTER必须是确切的字符串"true"。 - 它们会在
icp deploy期间应用(「Setting environment variables」步骤)。部署后,请使用icp canister settings show <name> -e ic确认。
图标说明(控制台会将 __META_BASE_URL + __META_ICON_PATH 拼接为图标 URL):
- 图标要显示,两者都必须存在且设置在主 canister 上——没有回退机制。单独设置
__META_ICON_PATH不会起作用。 __META_BASE_URL必须能解析为绝对的https://URL。单独的主机名、http://URL 或data:/javascript:值都会被拒绝:此时图标不会渲染,而「Open」按钮会回退到主 canister 的网关 URL(不会消失)。(控制台会在使用前验证协议。)__META_ICON_PATH必须是前端实际提供的资源路径(例如/favicon.svg),不能是内联图片。解析后的 URL 会作为<img>的src渲染,因此必须返回图片。请勿在此处填写data:URI:引擎环境变量的长度有限制(已观察到上限为 128 个字符),因此无法容纳,而且该字段设计上就是路径。- 前端 canister 的 ID 只有在首次部署后才会确定。通常的流程是:先部署一次,从输出中读取前端 canister ID,将
__META_BASE_URL设置为https://<that-id>.icp.net(并设置__META_ICON_PATH),然后重新部署以应用设置。如果你可以为应用管理自定义域名,也可以预先设置。
固定 Internet Identity 的派生源(如果应用会让用户登录)
Internet Identity 会按源派生主体。通过 canister 地址和自定义域名访问的应用,会将同一个人登录为两个不同的用户。
引擎控制台会将应用的 canister 地址列为派生源。此地址无法重命名或移除,因此添加、更改或移除自定义域名后,它仍会保留。该地址属于提供前端服务的 canister(即浏览器加载应用所用的资产/静态站点 canister),由该 canister 的 ID 构建:
https://<frontend-canister-id>.icp.net
通常,这是你标记为 __META_MAIN_CANISTER: "true" 的 canister,但此标记只是控制台的显示设置,并非保证。如果你标记的是后端 canister,仍应使用前端 canister 进行派生。后端 canister 不能作为派生源:浏览器不会位于该源上,而且它无法提供 Internet Identity 从该源获取并用于验证声明的 .well-known/ii-alternative-origins 文件。
应根据 canister ID 构建此值,而不是使用 __META_BASE_URL。只有在 __META_BASE_URL 仍指向 canister 地址时,两者才一致。将它指向自定义域名很常见,也受支持(参见上方的图标说明),此时两者便不一致。自定义域名是绝不能用作派生源的值。
请在应用拥有用户之前完成此操作,而不是事后再做。 已收集过登录信息的域名,无法在不导致该域名下创建的所有账户失效的情况下更改派生源。如果应用将来可能会使用自定义域名,请在首次部署时就设置 derivationOrigin,即使当时只有 canister 地址这一来源也一样——现在设置无需成本,且之后若要补设,就必须舍弃已有账户。
有关具体实现——derivationOrigin 选项和 .well-known/ii-alternative-origins 文件,包括 @dfinity/static-site 配方所需的 _headers 条目——请加载 internet-identity 技能。
Caffeine 应用是例外:它们的地址由 Caffeine 持有,因此不属于应用可用于派生的来源。控制台不会为这些应用显示 canister 地址行。
版本元数据(推荐)
将构建来源信息嵌入每个 canister 的 wasm 中,以便工具和控制台显示当前运行的版本(控制台支持正在逐步推出;元数据已可读取,请参阅下方的验证说明)。默认情况下,每次部署都添加这些信息——不要等用户提出要求。所有官方配方(motoko、rust、static-site、asset-canister、prebuilt)都支持在配方的 configuration 下添加 metadata 列表;每个条目都会作为自定义区段写入 wasm。值会在构建时插入 shell 命令,因此支持 $(…) 命令替换。(已通过 icp-cli 1.0.2 对 @dfinity/motoko v4.1.0 和 v5.0.0 进行实时构建验证。)
Git 项目(先检查:git rev-parse HEAD 成功):
recipe:
type: "@dfinity/[email protected]"
configuration:
metadata:
- name: service:git:sha
value: $(git rev-parse HEAD)$(git diff --quiet HEAD 2>/dev/null || echo +dirty)
- name: service:git:origin
value: $(git remote get-url origin)
- name: service:git:updated_at
value: $(git log -1 --format=%cI)
- name: service:version
value: $(node -p "require('./package.json').version" 2>/dev/null || echo 1.0.0)
非 Git 项目——上面的 Git 替换不会导致构建失败;它们会静默写入无效值(service:git:sha 会变成字面值 +dirty,service:git:origin 则为空)。只使用显式版本号,并在后续每次部署时递增:
metadata:
- name: service:version
value: "1.0.0"
说明:
- 使用这些确切名称(
service:git:sha、service:git:origin、service:git:updated_at、service:version)——这是控制台将读取的约定名称;名称不同的条目不会被识别。 - 尽可能优先使用命令结果,而不是字面值:sha 中的
+dirty表示存在未提交的更改,因此可以追溯部署对应的确切源代码状态。 - 对于给定的源代码树,值必须是确定性的:sha、origin、最后一次提交的日期、版本字符串。绝不要嵌入构建时间(
$(date))或其他随构建时间变化的值。允许使用提交日期(%cI),因为它是提交的属性,而非构建的属性——但请注意,它表示“代码最后更改时间”,而不是“最后部署时间”:部署旧提交时会显示旧日期。部署时间本身来自网络自动记录的 canister 历史。 - 如果项目没有
package.json(或没有版本字段),则采用回退值1.0.0;如果项目有实际的版本方案,请将其替换为该方案。 - 这些区段会作为
icp:private注入,可由 canister 的控制器(你,以及以你的身份运行的控制台)通过 read_state 读取——无需调用 canister。部署后,在已激活关联身份的情况下验证:icp canister metadata <canister-name> service:git:sha -e ic。
步骤 3 — 部署到引擎的子网
在项目根目录下运行:
icp deploy -e ic --subnet <subnet-id>
-e ic指定主网(引擎运行在 IC 子网上);--subnet <subnet-id>将部署固定到你的引擎所在的子网。如果不确定,请先运行icp deploy --help确认确切的标志。- 部署会占用引擎容量;请确保引擎有足够空间。
替代方式:上传打包文件。 如果项目以构建好的 .icp 包形式分发,且无法直接使用 icp deploy,用户可以改为上传该包:引擎 → 应用程序 → 构建并部署应用,按提示选择引擎并上传 .icp 包。同一菜单(“更多部署方式”)还提供适用于现成应用的 App Store,以及本技能自动化执行的 CLI 说明。
步骤 4 — 验证
icp deploy输出会报告已部署的 canister ID。- canister 会显示在控制台引擎的应用程序页面中;每个 canister 的详情视图都提供“在浏览器中打开”链接。
- 如果在步骤 2 中设置了元数据,canister 会按你的
__META_PROJECT名称分组,并显示各自的__META_NAME标签;主 canister 还会显示“打开”按钮,而不再只是列出 principal。如果设置了__META_BASE_URL+__META_ICON_PATH,应用还会显示其图标(控制台缓存可能有短暂延迟)。 - 前端(asset)canister 可通过
https://<frontend-canister-id>.icp.net访问。
将已部署的 canister ID(以及前端 URL,如有)报告给用户。
步骤 5:部署代理 canister(仅当应用需要链密钥服务时)
如果应用不会调用阈值 ECDSA 或 Schnorr(Bitcoin、Ethereum)、vetKD / VetKeys、汇率 canister(XRC)、Bitcoin API 或 EVM / SOL RPC canister,请跳过此步骤:控制台代理不会为引擎的其他 canister 中继任何内容(具体列表见 cloud-engine-canisters)。其他所有功能(应用自身的执行、存储、消息传递和 HTTPS 外部调用)在引擎上均免费,完全不需要代理。
这些服务位于其他子网,并按调用收取 cycles;引擎 canister 既不能持有 cycles,也不能跨越引擎边界发送携带 cycles 的消息。普通 Application 子网上的代理 canister可以代替引擎发起调用,并从自身余额中支付。
如果你正在编写或部署此类应用,但还没有代理 ID,请现在就申请(What You Need,第 3 项),不要使用占位符进行部署:首次代理调用会失败。
选择哪种代理
有两种不同的 canister 可以承担此角色,选错会导致配置看似正常,却在首次调用时失败:
| 控制台代理 | 自行部署的代理 | |
|---|---|---|
| 部署者 | 引擎的所有者,通过控制台部署 | 你,使用 icp 部署 |
| 获准调用者 | 引擎的canister ID 范围,以及控制器 | 仅控制器 |
| 你的引擎 canister 是否可以调用它 | 可以,自动获准 | 只有将每个引擎 canister 添加为控制器后才可以 |
| 为它们中继的内容 | 仅链密钥、Bitcoin 和汇率 / RPC 服务(列表见 cloud-engine-canisters) | 任何内容 |
| 你的 CLI 身份是否可以调用它 | 不可以(会被拒绝为 UnauthorizedUser) | 可以:你是控制器 |
| 阈值密钥派生 | 按调用 canister 隔离 | 不隔离 |
| 资金来源 | 引擎余额,自动提供(无需银行卡) | 你持有的 cycles(icp cycles mint) |
- 应用代码调用链密钥服务 → 使用控制台代理。 它允许你的引擎 canister 调用,无需逐个配置。
- 你从 CLI 调用某项功能(
icp canister call --proxy、icp deploy --proxy、canister_info或raw_rand等仅限 canister 的管理方法)→ 使用你自己的代理。 控制台代理无法提供此服务:它会拒绝来自任何非控制器主体的入口调用,而你的 CLI 身份不在其中。
二者不能互相替代,且派生的密钥不同:从一种代理切换到另一种之前,请参阅 cloud-engine-canisters 中的派生警告。
控制台代理:由引擎所有者部署(通过控制台操作,而非 CLI)
只有引擎的所有者才能部署,而且只能通过控制台操作。没有对应的 icp 命令,也没有 Agent 能调用的创建方式:按钮背后的控制台端点使用由 Internet Identity 登录生成的浏览器会话 Cookie 进行身份验证。请将以下步骤交给用户,并等待他们提供代理容器 ID:
- 在控制台中打开引擎 → 在引擎侧边栏中选择 Canisters(而不是列出已部署应用的 Applications)→ 找到 Proxy canisters 部分。
- 点击 Deploy a proxy,然后在对话框中点击 Deploy proxy。无需选择金额,也不会从卡片扣款:代理会使用引擎余额,在普通 Application 子网上创建,初始获得价值几美元的 cycles。
- 从表格中复制代理容器 ID;该行带有 Engine balance 徽标。应用会使用这个 ID。
如果引擎未在运行,或引擎的可用余额不足以支付代理的初始 cycles,部署就会被拒绝:所有者需先为引擎充值,然后重试。
资金如何补充。 只要代理余额低于较低阈值,代理就会每隔几分钟申请充值,资金从引擎余额中划出:每小时的金额有上限,且绝不会动用引擎用于支付节点费用的资金。代理没有 Top up 按钮,也没有自动充值设置;其所在行会显示余额和已提取的 cycles。如果代理仍然耗尽资金,请为引擎充值(见下文“为引擎付费”)。
该表格还提供 Refresh balance(实时读取,因为显示的数值是缓存的)和 Delete proxy(停止并删除容器;扣除容器运行所需的少量储备后,未用完的 cycles 会退回引擎余额)。
在引擎资金支持的代理推出之前部署的代理属于卡片付费类型:它会显示 Top up(立即从卡片扣款)和 Automatic top-up(余额不足时从卡片扣款),删除后则会将剩余余额退还至付款方式。
一个引擎可以有多个代理(“Deploy another proxy”)。每个代理都有自己的余额(均从同一个引擎余额补充);对于签名应用,每个代理也有自己的密钥命名空间。
将代理 ID 接入应用
应用需要在运行时获取该 ID。请将它作为容器环境变量传入,而不要硬编码在源代码中,这样同一份代码就可以对接本地模拟环境和引擎:
settings:
environment_variables:
PROXY_CANISTER_ID: "<proxy-canister-id>"
像读取其他环境变量一样,在容器中读取该变量,并将其视为永久配置:对于派生阈值密钥的应用,更改此值会改变应用拥有的每个地址。
自行部署代理:CLI 操作路径
用于代理你自己的调用。该代理采用上游的 dfinity/proxy-canister,从模板部署:
icp new my-proxy --subfolder proxy # pre-built wasm, no init args
cd my-proxy
icp deploy -e ic # NOT --subnet <engine-subnet>: see below
export PROXY_ID=$(icp canister status -e ic --id-only proxy)
使用 cycles 账本为其充值并检查状态:
icp cycles balance -e ic # what you hold
icp cycles mint --icp 1 -e ic # ICP -> cycles, if you need more
icp canister top-up "$PROXY_ID" --amount 5t -e ic
icp canister status "$PROXY_ID" -e ic # the proxy's own balance
然后将其传递给任何需要 cycles 或仅限容器使用的管理方法的命令:
icp canister call my-canister charge_me '()' -e ic --proxy "$PROXY_ID" --cycles 1_000_000_000_000
icp deploy -e ic --proxy "$PROXY_ID"
以下三点务必注意:
- 不要将它部署到引擎所在的子网上。 部署在
CloudEngine子网上的代理毫无用处:它没有 cycles,也无法发送带有 cycles 的消息。省略--subnet(步骤 3 中的引擎子网标志是给应用用的,与此无关),这样它就会部署到普通 Application 子网上。 - 它只授权控制器。 若要让引擎容器调用它,必须将该容器的主体添加为控制器(
icp canister settings update "$PROXY_ID" --add-controller <canister-id> -e ic),每个容器都要单独调用一次,并在添加容器时重复操作。这正是控制台代理旨在消除的维护负担。 - 它不会隔离密钥派生。 任何获授权的调用方都可以请求任意派生路径,且它生成的密钥与控制台代理生成的密钥不同。不要随意让签名应用使用它。
为引擎付费:使用哪种余额
有两种余额,充值方式各不相同,应用自身的 canister 都不持有这两种余额。在确定具体指哪一种之前,不要在引擎上“为 canister 充值”:
| 余额 | 用途 | 充值方式 | 余额耗尽时会发生什么 |
|---|---|---|---|
| 引擎余额 | 支付引擎本身(其节点的每日费用),以及为其控制台代理充值 | 在引擎的 Billing 页面点击 Add funds(费用从已登记的银行卡扣除),以及自动充值(会在余额即将耗尽前约一周从银行卡扣款) | 引擎会被删除,其上所有内容也会一并删除:无法恢复,因此余额不足的预警必须紧急处理。如果引擎的自动充值持续失败,引擎会被暂停,付款到账后即可恢复运行 |
| 代理周期余额 | 仅支付代理转发的调用(签名、vetKD、XRC、Bitcoin API、EVM/SOL RPC) | 控制台代理会从引擎余额中自行充值(步骤 5);较早的银行卡付费代理提供 Top up 和 Automatic top-up;自行部署的代理使用 icp canister top-up | 转发调用会因 InsufficientCycles 失败;代理会被冻结而非删除,并会在后续充值后恢复 |
控制台代理的充值费用从引擎余额中扣除,因此繁忙的代理会缩短引擎的可运行时间,但扣除额不会超过引擎节点的应付费用。
应用自身的 canister 不持有以上任何一种余额:按设计,它们的 cycles 数量为 0,且无法充值。引擎 canister 显示“0 cycles”是正常情况(参见 cloud-engine-canisters,常见问题 5)。
在引擎上运行的代码
云引擎运行在 CloudEngine subnet 上,其协议级调用规则与普通 Application subnet 不同:引擎 canister 持有 0 cycles,且绝不能在任何调用中附加 cycles;跨 subnet 调用必须使用有界等待;HTTPS 出站调用会直接发起且免费;需要携带 cycles 的跨 subnet 目标(汇率 XRC、阈值 ECDSA/Schnorr、vetKD)则通过引擎已充值的控制台代理 canister访问。在编写或调试任何将在引擎上运行的 canister 代码之前,请先加载 cloud-engine-canisters skill,其中包含相关规则、代理接口和常见问题。
常见问题
- 未完成登录。 运行
icp identity link web …后,如果没有在浏览器中完成 Internet Identity 登录,CLI 就不会完成关联;后续命令会因授权错误而失败。请重新运行并等待用户确认浏览器流程已完成。如果浏览器始终没有打开,说明命令停在“Press Enter to log in”提示处——请通过管道传入换行符重新启动:printf '\n' | icp identity link web …,切勿使用< /dev/null(见步骤 1)。如果 CLI 在远程沙盒中运行,重新运行也无法完成——请参阅陷阱 10 和步骤 1.0 中的委托交接。 --auth来源错误。 使用与用户登录时所用控制台来源不同的任何 URL,都会派生出不同的主体,导致引擎以未授权为由拒绝部署。请使用确切的控制台 URL 重新关联。如果使用默认的https://opencloud.org关联后,部署因未授权而遭拒,请向用户询问其登录时使用的确切 URL,然后重新关联。- 猜测子网 id。 切勿自行编造:部署会失败或指向错误的子网。该 id 位于控制台中引擎的 Settings 页面;请向用户询问。
- 使用匿名身份部署。 默认的本地身份是匿名身份,并非引擎管理员。必须先关联身份,然后运行
icp identity default <your-identity-name>。 - 使用
dfx。 此生态系统使用icp,绝不使用dfx。正确顺序是icp identity link web <name> --auth <console-origin>(步骤 1),然后运行icp deploy -e ic --subnet <subnet-id>(步骤 3)。请参阅icp-cli技能。 - 跳过应用元数据。 如果没有
__META_PROJECT(步骤 2),Canister 仍会部署并正常工作,但在控制台中会显示为未命名、仅带主体 id 的行。设置__META_*才能生成带名称的应用、带标签的 Canister,以及“Open”按钮。 __META_MAIN_CANISTER值错误。 它按精确字符串"true"匹配。布尔值、"True",或将多个 Canister 标记为主 Canister,都会导致没有(或出现错误的)“Open”按钮。请只将一个入口 Canister 标记为主 Canister。- 臆造图标变量。 图标变量是
__META_ICON_PATH(相对于__META_BASE_URL解析的路径)。不要猜测__META_ICON、__META_LOGO或__META_ICON_LINK——这些变量会被忽略,因此图标不会显示,而且不会提示错误。 - 在错误的 Canister 上设置图标,或未设置基础 URL。 只有从主 Canister 读取图标,并且需要同时设置
__META_BASE_URL(有效的绝对https://URL)和__META_ICON_PATH。在辅助 Canister 上设置图标路径、遗漏基础 URL,或提供非https/data:基础 URL,都会导致图标无法显示。(“Open”按钮仍然有效——它会回退到主 Canister 的网关 URL;因此,错误的基础 URL 会导致图标和自定义 Open URL 无法使用,但不会影响按钮。) - 误以为浏览器和 CLI 共用 localhost。
icp identity link web会将委托返回到 CLI 主机上的127.0.0.1:<port>。如果 CLI 在远程沙盒中运行,而用户的浏览器位于另一台机器上,登录就无法完成,后续命令也会因授权错误而失败——无论重复运行关联命令多少次都无法解决。请参阅步骤 1.0:优先使用本地会话,让浏览器和 CLI 共用回环地址;只有用户没有本地选项时,才使用委托交接(icp identity delegation request/sign/use)。请在沙盒中开始构建之前询问,而不是事后才问。 - 误以为委托交接能解决网络连接问题。 交接转移的是签名权限,而不是网络连接——运行
icp deploy的 Shell 仍需要联网。如果 CLI Shell 完全无法联网(例如沙盒设备桥接环境:DNS 被阻断、HTTPS 失败),就无法在那里运行任何icp网络命令,无论是关联还是部署。不要提议从该 Shell“完成剩下的工作”,也不要建立隧道——应将一个脚本交给用户,让其在自己的实际终端中运行(见步骤 1.0 中的“无网络 CLI 主机”);此时常规关联流程即可正常工作,因为终端和浏览器共用回环地址。 - wasm 元数据中的构建时间戳。 元数据会嵌入 wasm 中,因此必须保持确定性——构建时间(
$(date))即使在源代码完全相同的情况下也会随每次构建而变化,从而破坏可重现性,并导致每次部署时模块哈希都发生变化。确定性的替代方案是上一条提交的日期:service:git:updated_at=$(git log -1 --format=%cI)——它是源代码树的属性,而不是构建过程的属性。部署时间本身来自网络记录的 Canister 历史记录,绝不能来自元数据。 - 在非 git 项目中使用 Git 元数据替换。 在 git 仓库之外,
$(git rev-parse HEAD)不会导致构建失败——它会悄悄嵌入错误数据:service:git:sha会变成字面值+dirty,而service:git:origin则为空。请先检查是否存在 git 仓库(git rev-parse HEAD能否成功);如果不存在,只设置带显式值的service:version(或者,如果本来就需要版本控制,则运行git init并在部署前提交)。 - 未固定派生来源便让引擎应用开始收集登录信息。 Internet Identity 主体按来源区分,因此稍后添加自定义域名,会让所有现有用户在新地址下变成陌生人;而且无法事后修复,否则会导致旧来源下创建的账户变成孤立账户。首次部署任何使用 II 的应用时,请将
derivationOrigin设置为提供前端服务的 Canister 地址,并使用其 Canister id 构造该地址(https://<frontend-canister-id>.icp.net),即使目前它是应用唯一的来源也应如此。不要复制__META_BASE_URL:它允许指向自定义域名,而自定义域名恰恰不能成为派生来源。关于.well-known/ii-alternative-origins部分,请参阅internet-identity技能。 - 尝试通过 CLI 或 API 部署或充值控制台代理。 没有用于此操作的
icp命令;而且控制台按钮背后的端点使用 Internet Identity 登录时的浏览器会话 Cookie 进行身份验证(不支持令牌身份验证),因此 Agent 无法驱动这些端点。只有引擎所有者才能部署控制台代理。请将控制台操作步骤交给用户(引擎 → Canisters → Proxy canisters → Deploy a proxy),并等待其提供代理 Canister id。该代理无需充值:费用由引擎余额支付。Agent 可以无人值守地完成整套自部署代理流程,但那是授权方式不同的另一个 Canister(步骤 5)。 - 将自部署代理部署到引擎的子网上。 重用步骤 3 中的
--subnet <engine-subnet-id>会将代理放到CloudEngine子网上;代理在该子网上持有 0 cycles,可能也无法发送携带 cycles 的消息,因此无法执行代理唯一要做的事。请省略--subnet,让它部署到普通的 Application 子网上。 - 误以为自部署代理会接受引擎 Canister 发起的调用。 它只授权控制器。引擎 Canister 调用它时会收到
UnauthorizedUser,除非先使用icp canister settings update <proxy> --add-controller <canister-id> -e ic将该 Canister 的主体添加为控制器;之后每新增一个 Canister,也都必须再次添加。控制台代理会授权整个引擎 Canister id 范围,因此它才是适用于应用代码的代理。 - 删除签名应用用于派生密钥的代理。 删除按钮会将未用完的 cycles 退回引擎余额,看起来像是在做清理。但事实并非如此:阈值密钥是根据代理自身的主体派生的,因此删除代理(或将应用指向另一个代理)会改变应用拥有的每个 Bitcoin/Ethereum 地址,并使旧地址上的资金无法取用。在处理应用已经用于签名的代理之前,请参阅
cloud-engine-canisters中的派生规则。 - 混淆余额。 “Canister 的 cycles 用完了”在不同情况下含义不同:引擎 Canister 按设计持有 0 cycles,不能充值;引擎余额不足需要在其 Billing 页面点击 Add funds;而中继调用返回的
InsufficientCycles指的是代理余额(对于从引擎余额获取补充的控制台代理,这同样意味着需要给引擎充值)。充值前先确认是哪种情况,并注意,引擎余额耗尽会导致引擎被删除。 - 未取得代理 id 就部署依赖代理的应用。 需要签名、派生 vetKeys、读取汇率,或调用 Bitcoin API 或 EVM/SOL RPC 的应用,即使用占位符、猜测的 id 或来自其他引擎的 id,也能成功部署,但首次代理调用时就会失败(
UnauthorizedUser,或生成属于其他代理的密钥和地址)。请在部署前索取该 id(“需要准备的内容”第 3 项)。如果引擎没有代理,应由所有者从控制台部署一个(步骤 5);切勿用自部署代理替代它。
其他参考资料
- 加载
cloud-engine-canisters,了解在引擎上运行的代码的调用规则:绝不附加 cycles、跨子网调用使用有界等待、直接进行 HTTPS 外呼,以及针对需要 cycles 的跨子网目标(XRC、阈值签名、vetKD)使用控制台代理。在编写或调试引擎 canister 代码之前加载此资料。 - 加载
icp-cli,了解通用的 icp CLI 用法(icp.yaml、配方、环境、绑定、身份)。对于此云引擎部署流程以外的任何内容,都应加载此资料;尤其是项目尚无法构建或打包时。 - 加载
internet-identity,了解步骤 1 在浏览器中触发的 Internet Identity 登录详情。 - 加载
vetkeys,了解 vetKD 是什么以及如何使用 VetKeys(前提是已设置代理):其调用代码面向普通子网,因此请参阅cloud-engine-canisters,了解引擎所需的代理形式。 - 加载
cycles-management,了解普通(非引擎)canister 的 cycles:余额、冻结阈值,以及通过 CMC 将 ICP 转换为 cycles。