mcpField
C
custom-domains

custom-domains

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

关于 custom-domains

这是一项指导开发者将 Internet Computer canister 部署到自定义域名的技能,涵盖 DNS 配置、所有权验证、注册、TLS 证书配置及域名更新。

主要功能

  1. 01配置 DNS 记录
  2. 02部署域名所有权文件
  3. 03验证并注册域名
  4. 04检查证书和注册状态
  5. 05更新或移除域名

适用场景

  1. 01通过品牌域名发布 canister
  2. 02排查自定义域名注册失败
  3. 03将域名切换到其他 canister

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/dfinity/icskills --skill custom-domains

安装后在 Claude Code 中输入/custom-domains确认已加载。

详细介绍

自定义域名

功能介绍

默认情况下,金丝雀可通过 <canister-id>.icp.net 访问。自定义域名服务可让你通过自己的域名(例如 yourdomain.com)提供任意金丝雀的服务。你需要配置 DNS、将域名所有权文件部署到金丝雀,并通过 REST API 注册。之后,HTTP 网关会自动处理 TLS 证书的签发、续期和路由。

自定义域名在边界节点层级生效——它们通过 DNS 将域名映射到任意金丝雀 ID。任何能通过 HTTP 提供 /.well-known/ic-domains 的金丝雀都可以使用此功能,不仅限于资产金丝雀。包括资产金丝雀、Juno 卫星和实现了 http_request 的自定义金丝雀。

前提条件

  • 在任意域名注册商处注册的域名(例如 Namecheap、GoDaddy、Cloudflare)
  • 有权限编辑该域名的 DNS 记录
  • 已部署且能通过 HTTP 提供 /.well-known/ic-domains 的金丝雀(资产金丝雀、Juno 卫星或任何实现了 http_request 的金丝雀)
  • 用于调用注册 API 的 curl
  • jq(可选,用于格式化 JSON 响应)

可能导致配置失败的错误

  1. 未关闭 DNS 提供商的 SSL/TLS。 Cloudflare 等提供商默认启用 Universal SSL。这会干扰 IC 用于签发证书的 ACME 挑战流程,并可能导致证书无法续期。注册前,请关闭 DNS 提供商提供的所有证书/SSL/TLS 服务。

  2. 在顶级域名上设置 CNAME。 许多 DNS 提供商不允许在顶级域名上设置 CNAME 记录(例如,不带子域名的 example.com)。如果提供商支持,请使用 ANAME 或 ALIAS 记录类型(CNAME 扁平化)。否则,请使用 www.example.com 这类子域名。

  3. 缺少 _acme-challenge CNAME。 如果没有将 _acme-challenge.CUSTOM_DOMAIN 指向 _acme-challenge.CUSTOM_DOMAIN.icp2.io,HTTP 网关就无法获取 TLS 证书,注册也会失败。

  4. _canister-id 上存在多条 TXT 记录。 如果 _canister-id.CUSTOM_DOMAIN 存在多条 TXT 记录,注册就会失败。请确保只保留一条包含你的金丝雀 ID 的记录。

  5. 忘记 .well-known/ic-domains 文件。 金丝雀必须提供列有自定义域名的 /.well-known/ic-domains。如果缺少此文件,注册期间的域名所有权验证就会失败。

  6. DNS 提供商留下了过期的 _acme-challenge TXT 记录。 提供商之前发起的 ACME 挑战可能会在 _acme-challenge.CUSTOM_DOMAIN 上留下 TXT 记录,这些记录不会显示在控制面板中。它们会与 IC 的 ACME 流程冲突。请关闭提供商提供的所有 TLS 服务以清除这些记录。使用 dig TXT _acme-challenge.CUSTOM_DOMAIN 验证。

  7. 未明确注册域名。 仅配置 DNS 并不足够。你必须调用 POST /custom-domains/v1/CUSTOM_DOMAIN 来启动注册。注册不会自动进行。

  8. 将 HttpAgent 的 host 设置为自定义域名。 host 是金丝雀调用所使用的 API 端点,不是前端的访问域名。自定义域名是 HTTP 网关,不提供 /api/v2,因此将 host 指向该域名(或 window.location.origin)会导致调用失败。你无需设置 host:在自定义域名上,较新版本的 @icp-sdk/core HttpAgent 会在省略 host 时将其解析为 https://icp-api.io(主网 API 边界节点)。请将其留空,或明确设置为 https://icp-api.io——切勿设置为网关域名。

  9. 为已有用户的应用添加自定义域名。 II 主体按来源区分,因此新域名会为每位回访用户创建全新账户。请固定派生来源——金丝雀地址,自定义域名无法取代它——并在域名上线 之前 将域名列入 .well-known/ii-alternative-origins。域名一旦已有用户登录,就无法在不导致这些账户失联的情况下重新指向其他地址。请参阅 internet-identity 技能。

实现

步骤 1:配置 DNS 记录

添加三条 DNS 记录(将 CUSTOM_DOMAIN 替换为你的域名,例如 app.example.com):

记录类型主机值
CNAMECUSTOM_DOMAINCUSTOM_DOMAIN.icp1.io
TXT_canister-id.CUSTOM_DOMAIN你的 canister ID(例如 hwvjt-wqaaa-aaaam-qadra-cai)
CNAME_acme-challenge.CUSTOM_DOMAIN_acme-challenge.CUSTOM_DOMAIN.icp2.io

有些 DNS 提供商会省略主域名后缀。对于这类提供商上的 app.example.com:

  • 使用 app,而不是 app.example.com
  • 使用 _canister-id.app,而不是 _canister-id.app.example.com
  • 使用 _acme-challenge.app,而不是 _acme-challenge.app.example.com

对于不支持 CNAME 的顶级域名,请使用提供商的 ANAME 或 ALIAS 记录类型,并将其指向 CUSTOM_DOMAIN.icp1.io。

步骤 2:创建 ic-domains 文件

你的 canister 必须通过 HTTP 提供 /.well-known/ic-domains。创建此文件,每行列出一个自定义域名:

app.example.com
www.example.com

前端 canister 用户: 将 .well-known/ 放在 public/ 目录(Vite 项目)中,或放在源文件旁边,确保它最终进入 dir。使用推荐的 @dfinity/static-site recipe 时,.well-known/ 会自动上传(无需配置)。使用旧版 @dfinity/asset-canister recipe 时,请确保 .ic-assets.json5 包含 { "match": ".well-known", "ignore": false },以便部署这个隐藏目录。有关文件放置位置的详情,请参阅 static-site 技能。

自定义 http_request canister: 在 HTTP 请求处理程序中直接提供 /.well-known/ic-domains 的文件内容。

步骤 3:部署

部署你的 canister,确保可通过 https://<canister-id>.icp.net/.well-known/ic-domains 访问 /.well-known/ic-domains。

步骤 4:验证

注册前检查 DNS 记录和 canister 配置:

curl -sL -X GET "https://icp.net/custom-domains/v1/CUSTOM_DOMAIN/validate" | jq

成功响应:

{
  "status": "success",
  "message": "Domain is eligible for registration: DNS records are valid and canister ownership is verified",
  "data": {
    "domain": "CUSTOM_DOMAIN",
    "canister_id": "CANISTER_ID",
    "validation_status": "valid"
  }
}

如果验证失败,常见错误及修复方法如下:

错误修复方法
缺少 DNS CNAME 记录添加 _acme-challenge CNAME,并将其指向 _acme-challenge.CUSTOM_DOMAIN.icp2.io
缺少 DNS TXT 记录添加 _canister-id TXT 记录,并填入你的 canister ID
DNS TXT 记录无效确保 TXT 值是有效的 canister ID
DNS TXT 记录多于一条删除重复的 _canister-id TXT 记录,只保留一条
无法获取已知域名确保已部署 /.well-known/ic-domains,且 canister 会提供该文件
域名不在列表中将域名添加到 ic-domains 文件并重新部署
步骤 5:注册
curl -sL -X POST "https://icp.net/custom-domains/v1/CUSTOM_DOMAIN" | jq

成功响应:

{
  "status": "success",
  "message": "Domain registration request accepted and may take a few minutes to process",
  "data": {
    "domain": "CUSTOM_DOMAIN",
    "canister_id": "CANISTER_ID"
  }
}
步骤 6:等待证书签发

轮询,直到 registration_status 为 registered:

curl -sL -X GET "https://icp.net/custom-domains/v1/CUSTOM_DOMAIN" | jq

状态值:registering → registered(成功),或 failed(检查错误消息)。

状态变为 registered 后,再等待几分钟,让变更传播到所有 HTTP 网关,然后再进行测试。

更新自定义域名

要将现有自定义域名指向其他 canister:

  1. 将 _canister-id TXT 记录更新为新的 canister ID。
  2. 通知服务:
curl -sL -X PATCH "https://icp.net/custom-domains/v1/CUSTOM_DOMAIN" | jq
  1. 检查状态:
curl -sL -X GET "https://icp.net/custom-domains/v1/CUSTOM_DOMAIN" | jq

移除自定义域名

  1. 从 DNS 中移除 _canister-id TXT 记录和 _acme-challenge CNAME。
  2. 通知服务:
curl -sL -X DELETE "https://icp.net/custom-domains/v1/CUSTOM_DOMAIN" | jq
  1. 确认已删除(应返回 404):
curl -sL -X GET "https://icp.net/custom-domains/v1/CUSTOM_DOMAIN" | jq

HttpAgent 配置

通过自定义域名提供的前端仍会通过主网 API 边界节点(https://icp-api.io)调用 canister,而不是通过其自身提供服务的域名调用。HttpAgent 的 host 是该 API 端点,而不是 前端的来源。自定义域名是 HTTP 网关,不提供 /api/v2。

你无需设置 host。省略该参数时,较新的 @icp-sdk/core HttpAgent 在自定义域名(以及 icp.net)上会解析到 https://icp-api.io。不要将 host 指向你的自定义域名或 window.location.origin——那是网关,因此调用会失败。

import { HttpAgent } from "@icp-sdk/core/agent";

// host omitted on a custom domain → resolves to https://icp-api.io
const agent = await HttpAgent.create();

// equivalent, explicit:
const agentExplicit = await HttpAgent.create({ host: "https://icp-api.io" });

部署与测试

# 1. Deploy your canister with the ic-domains file served at /.well-known/ic-domains

# 2. Validate DNS + canister config
curl -sL -X GET "https://icp.net/custom-domains/v1/yourdomain.com/validate" | jq

# 3. Register
curl -sL -X POST "https://icp.net/custom-domains/v1/yourdomain.com" | jq

# 4. Poll until registered
curl -sL -X GET "https://icp.net/custom-domains/v1/yourdomain.com" | jq

验证是否正常工作

# 1. Verify DNS records
dig CNAME yourdomain.com
# Expected: yourdomain.com. CNAME yourdomain.com.icp1.io.

dig TXT _canister-id.yourdomain.com
# Expected: "<your-canister-id>"

dig CNAME _acme-challenge.yourdomain.com
# Expected: _acme-challenge.yourdomain.com. CNAME _acme-challenge.yourdomain.com.icp2.io.

# 2. Verify ic-domains file is served by the canister
curl -sL "https://<canister-id>.icp.net/.well-known/ic-domains"
# Expected: your domain listed

# 3. Verify registration status is "registered"
curl -sL -X GET "https://icp.net/custom-domains/v1/yourdomain.com" | jq '.data.registration_status'
# Expected: "registered"

# 4. Verify the custom domain serves your canister
curl -sI "https://yourdomain.com"
# Expected: HTTP/2 200

# 5. Verify no stale ACME TXT records
dig TXT _acme-challenge.yourdomain.com
# Expected: no TXT records (only the CNAME)

评论

支持 Markdown