mcpField
D
deploy-to-cloud-engine

deploy-to-cloud-engine

github.com/dfinity云与 DevOps39178 次安装今天更新
代码仓库

关于 deploy-to-cloud-engine

供编码 Agent 使用的技能,可将已能构建的项目部署到用户拥有的 Internet Computer 云引擎。重点是将 CLI 身份关联到引擎控制台,并部署到指定子网,同时处理本地、远程和离线环境。

主要功能

  1. 01将 CLI 身份关联到引擎控制台
  2. 02部署到指定子网
  3. 03处理本地与远程登录
  4. 04提供离线部署交接方案
  5. 05检查代理 canister 需求

适用场景

  1. 01将已构建应用部署到云引擎
  2. 02将 CLI 关联到引擎管理员身份
  3. 03在无网络环境准备部署
  4. 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 或之前的对话中查找。其中一个值有默认值;其他值必须向用户询问:

  1. 控制台源地址 — 用户登录云引擎控制台所用的网址。默认为 https://opencloud.org(OpenCloud 主控制台)。步骤 1 中会将其用作 --auth 源地址,以便关联的 CLI 身份派生出管理该引擎的同一主体。使用默认值,但在关联前告知用户,并给他们机会更改:
    • 告知用户:“我会使用默认控制台 https://opencloud.org 关联 CLI。如果你登录引擎控制台时使用的是其他网址,现在请告诉我。”
    • 只有用户明确提供其他源地址时才使用它——绝不要自行替换为其他网址;--auth 源地址决定派生出的主体(见注意事项 2)。
  2. 子网 ID:引擎要部署到的子网,由 icp deploy --subnet 指定。没有默认值;绝不要猜测。用户可以在控制台中引擎的设置页面(引擎标识符下方)找到它,也可以从控制台的命令面板复制。如果没有,请询问用户,在获得 ID 前不要继续:
    • 询问:“你的引擎子网 ID 是什么?可以在控制台中引擎的设置页面找到。”
  3. 代理容器 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,且你可以在那里运行命令,最简单的方法就是直接在那里运行整个技能;否则:

  1. 在 CLI 主机(沙箱)上——使用新的会话密钥创建待处理身份:

    icp identity delegation request <your-identity-name>
    

    命令会将会话公钥以 PEM 格式输出到 stdout。将该 PEM 区块发给用户。(如果默认的 --storage keyring 在无头沙箱中失败,请改用 --storage plaintext 重试。)

  2. 在用户的机器上——如果用户尚未关联身份,请先在本机完成关联(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>(常规浏览器流程,可在本机正常运行)后重试。

  3. 在 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 无法访问网络,用户的实际终端就是部署机器——由于他们的终端和浏览器共享回环地址,因此常规关联流程可以正常运行;无需进行委派交接。你的任务转为准备工作:

  1. 将构建好的项目放到用户终端可访问的目录中。

  2. 在项目根目录编写一个脚本,完成整个需要联网的操作序列——根据此模板进行调整(填入真实的身份名称、控制台源地址和子网 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>
    
  3. 请用户在终端中运行脚本,在浏览器打开时完成 Internet Identity 登录,并将输出粘贴回来。

  4. 根据粘贴的输出(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 的 URL https://<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:

  1. 在控制台中打开引擎 → 在引擎侧边栏中选择 Canisters(而不是列出已部署应用的 Applications)→ 找到 Proxy canisters 部分。
  2. 点击 Deploy a proxy,然后在对话框中点击 Deploy proxy。无需选择金额,也不会从卡片扣款:代理会使用引擎余额,在普通 Application 子网上创建,初始获得价值几美元的 cycles。
  3. 从表格中复制代理容器 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,其中包含相关规则、代理接口和常见问题。

常见问题

  1. 未完成登录。 运行 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 中的委托交接。
  2. --auth 来源错误。 使用与用户登录时所用控制台来源不同的任何 URL,都会派生出不同的主体,导致引擎以未授权为由拒绝部署。请使用确切的控制台 URL 重新关联。如果使用默认的 https://opencloud.org 关联后,部署因未授权而遭拒,请向用户询问其登录时使用的确切 URL,然后重新关联。
  3. 猜测子网 id。 切勿自行编造:部署会失败或指向错误的子网。该 id 位于控制台中引擎的 Settings 页面;请向用户询问。
  4. 使用匿名身份部署。 默认的本地身份是匿名身份,并非引擎管理员。必须先关联身份,然后运行 icp identity default <your-identity-name>。
  5. 使用 dfx。 此生态系统使用 icp,绝不使用 dfx。正确顺序是 icp identity link web <name> --auth <console-origin>(步骤 1),然后运行 icp deploy -e ic --subnet <subnet-id>(步骤 3)。请参阅 icp-cli 技能。
  6. 跳过应用元数据。 如果没有 __META_PROJECT(步骤 2),Canister 仍会部署并正常工作,但在控制台中会显示为未命名、仅带主体 id 的行。设置 __META_* 才能生成带名称的应用、带标签的 Canister,以及“Open”按钮。
  7. __META_MAIN_CANISTER 值错误。 它按精确字符串 "true" 匹配。布尔值、"True",或将多个 Canister 标记为主 Canister,都会导致没有(或出现错误的)“Open”按钮。请只将一个入口 Canister 标记为主 Canister。
  8. 臆造图标变量。 图标变量是 __META_ICON_PATH(相对于 __META_BASE_URL 解析的路径)。不要猜测 __META_ICON、__META_LOGO 或 __META_ICON_LINK——这些变量会被忽略,因此图标不会显示,而且不会提示错误。
  9. 在错误的 Canister 上设置图标,或未设置基础 URL。 只有从主 Canister 读取图标,并且需要同时设置 __META_BASE_URL(有效的绝对 https:// URL)和 __META_ICON_PATH。在辅助 Canister 上设置图标路径、遗漏基础 URL,或提供非 https / data: 基础 URL,都会导致图标无法显示。(“Open”按钮仍然有效——它会回退到主 Canister 的网关 URL;因此,错误的基础 URL 会导致图标和自定义 Open URL 无法使用,但不会影响按钮。)
  10. 误以为浏览器和 CLI 共用 localhost。 icp identity link web 会将委托返回到 CLI 主机上的 127.0.0.1:<port>。如果 CLI 在远程沙盒中运行,而用户的浏览器位于另一台机器上,登录就无法完成,后续命令也会因授权错误而失败——无论重复运行关联命令多少次都无法解决。请参阅步骤 1.0:优先使用本地会话,让浏览器和 CLI 共用回环地址;只有用户没有本地选项时,才使用委托交接(icp identity delegation request / sign / use)。请在沙盒中开始构建之前询问,而不是事后才问。
  11. 误以为委托交接能解决网络连接问题。 交接转移的是签名权限,而不是网络连接——运行 icp deploy 的 Shell 仍需要联网。如果 CLI Shell 完全无法联网(例如沙盒设备桥接环境:DNS 被阻断、HTTPS 失败),就无法在那里运行任何 icp 网络命令,无论是关联还是部署。不要提议从该 Shell“完成剩下的工作”,也不要建立隧道——应将一个脚本交给用户,让其在自己的实际终端中运行(见步骤 1.0 中的“无网络 CLI 主机”);此时常规关联流程即可正常工作,因为终端和浏览器共用回环地址。
  12. wasm 元数据中的构建时间戳。 元数据会嵌入 wasm 中,因此必须保持确定性——构建时间($(date))即使在源代码完全相同的情况下也会随每次构建而变化,从而破坏可重现性,并导致每次部署时模块哈希都发生变化。确定性的替代方案是上一条提交的日期:service:git:updated_at = $(git log -1 --format=%cI)——它是源代码树的属性,而不是构建过程的属性。部署时间本身来自网络记录的 Canister 历史记录,绝不能来自元数据。
  13. 在非 git 项目中使用 Git 元数据替换。 在 git 仓库之外,$(git rev-parse HEAD) 不会导致构建失败——它会悄悄嵌入错误数据:service:git:sha 会变成字面值 +dirty,而 service:git:origin 则为空。请先检查是否存在 git 仓库(git rev-parse HEAD 能否成功);如果不存在,只设置带显式值的 service:version(或者,如果本来就需要版本控制,则运行 git init 并在部署前提交)。
  14. 未固定派生来源便让引擎应用开始收集登录信息。 Internet Identity 主体按来源区分,因此稍后添加自定义域名,会让所有现有用户在新地址下变成陌生人;而且无法事后修复,否则会导致旧来源下创建的账户变成孤立账户。首次部署任何使用 II 的应用时,请将 derivationOrigin 设置为提供前端服务的 Canister 地址,并使用其 Canister id 构造该地址(https://<frontend-canister-id>.icp.net),即使目前它是应用唯一的来源也应如此。不要复制 __META_BASE_URL:它允许指向自定义域名,而自定义域名恰恰不能成为派生来源。关于 .well-known/ii-alternative-origins 部分,请参阅 internet-identity 技能。
  15. 尝试通过 CLI 或 API 部署或充值控制台代理。 没有用于此操作的 icp 命令;而且控制台按钮背后的端点使用 Internet Identity 登录时的浏览器会话 Cookie 进行身份验证(不支持令牌身份验证),因此 Agent 无法驱动这些端点。只有引擎所有者才能部署控制台代理。请将控制台操作步骤交给用户(引擎 → Canisters → Proxy canisters → Deploy a proxy),并等待其提供代理 Canister id。该代理无需充值:费用由引擎余额支付。Agent 可以无人值守地完成整套自部署代理流程,但那是授权方式不同的另一个 Canister(步骤 5)。
  16. 将自部署代理部署到引擎的子网上。 重用步骤 3 中的 --subnet <engine-subnet-id> 会将代理放到 CloudEngine 子网上;代理在该子网上持有 0 cycles,可能也无法发送携带 cycles 的消息,因此无法执行代理唯一要做的事。请省略 --subnet,让它部署到普通的 Application 子网上。
  17. 误以为自部署代理会接受引擎 Canister 发起的调用。 它只授权控制器。引擎 Canister 调用它时会收到 UnauthorizedUser,除非先使用 icp canister settings update <proxy> --add-controller <canister-id> -e ic 将该 Canister 的主体添加为控制器;之后每新增一个 Canister,也都必须再次添加。控制台代理会授权整个引擎 Canister id 范围,因此它才是适用于应用代码的代理。
  18. 删除签名应用用于派生密钥的代理。 删除按钮会将未用完的 cycles 退回引擎余额,看起来像是在做清理。但事实并非如此:阈值密钥是根据代理自身的主体派生的,因此删除代理(或将应用指向另一个代理)会改变应用拥有的每个 Bitcoin/Ethereum 地址,并使旧地址上的资金无法取用。在处理应用已经用于签名的代理之前,请参阅 cloud-engine-canisters 中的派生规则。
  19. 混淆余额。 “Canister 的 cycles 用完了”在不同情况下含义不同:引擎 Canister 按设计持有 0 cycles,不能充值;引擎余额不足需要在其 Billing 页面点击 Add funds;而中继调用返回的 InsufficientCycles 指的是代理余额(对于从引擎余额获取补充的控制台代理,这同样意味着需要给引擎充值)。充值前先确认是哪种情况,并注意,引擎余额耗尽会导致引擎被删除。
  20. 未取得代理 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。

评论

支持 Markdown