证书管家此前只做「探针」(查剩余天数),现补齐签发+部署两个环节,
参照 certimate(MIT)的 DNS-01 流程自行实现,不再依赖闭源 certd。
新增(纯 WebCrypto,零 npm 依赖):
- lib/acme.ts ACME v2 客户端:ES256 JWS(原始 r||s)、RFC7638
thumbprint、EAB、badNonce 重试、DNS-01、手写 DER CSR
- lib/dnsprovider.ts DNS-01 适配:DNSPod(TC3-HMAC-SHA256)、Cloudflare
- lib/deployer.ts 部署适配:多吉云 CDN、1Panel 站点(幂等换证书)
- lib/certissue.ts 编排:探针判剩余天数 → 注册/复用账户 → 签发 → 落库
→ 逐目标部署;RENEW_BEFORE_DAYS=30
- routes/ssl.ts 新增 POST /ssl/issue、GET /ssl/renew-check、
POST /ssl/selfcheck(环境自检,只读不签发)
- index.ts + cron 每日 04:10 自动续期检查;cpu_ms 提到 60s
与 certimate 的差异:certimate 每个 workflow 每天无条件重跑,
这里改为先探针查剩余天数、低于阈值才签,省 CA 限速额度。
实测修正(易误判,勿回退):
- 多吉云 bind 参数是 {id, domain},非 {cert_id}(用假 id 对照实验确认:
cert_id 回「域名不存在」= 参数被无视)
- 多吉云上传私钥字段是 private;列域名用 /cdn/domain/list.json
- 1Panel 必须用 /api/v2/(v1 返回 HTTP 200 但正文是 HTML 停用页)
- 1Panel HTTPS 配置字段是 SSL(大写),写错会导致每次续期都重绑
- LiteSSL ACME 目录须带 /v2:acme.trustasia.com/acme/v2/directory
测试:selftest-acme 16/16(CSR 过 openssl 验签、JWS 过 Node crypto 验签)、
selftest-deploy 18/18、selftest:ssl 117/117、UI 全过、tsc 干净
154 lines
8.3 KiB
TOML
154 lines
8.3 KiB
TOML
name = "artalk-cf"
|
||
main = "src/index.ts"
|
||
compatibility_date = "2026-07-24"
|
||
|
||
# ★ 证书探针需要 node:tls —— 只有它能在 Workers 里拿到**对端证书正文**
|
||
# (`cloudflare:sockets` 的 Socket 接口没有 getPeerCertificate,
|
||
# 实测线上只能确认「可达」但读不到 notAfter/SAN/issuer,那样证书管家等于瞎的)。
|
||
# 2026-08-04 之后的 compatibility_date 才默认带 nodejs_compat,
|
||
# 本项目定在 2026-07-24,所以必须显式打开这个 flag。
|
||
compatibility_flags = ["nodejs_compat"]
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# ★ Workers Cache —— 缓存 Worker 自己生成的响应(无需回源 fetch)
|
||
#
|
||
# 为什么必须开:Worker 位于 zone 缓存之前("Workers sits in front of cache"),
|
||
# 所以 **zone 级 Cache Rules / Page Rules 对 Worker 响应完全无效**。
|
||
# 想让 /api/favicon 这类「Worker 直接返回」的响应被边缘缓存、从而不再执行
|
||
# Worker(省 KV 读 + 省 Worker 请求),唯一正解是这个 [cache] enabled。
|
||
#
|
||
# 生效条件(缺一不可):
|
||
# 1. 这里是 fetch 处理器(scheduled 等不参与缓存)
|
||
# 2. 响应带可缓存的 Cache-Control(本项目已带 s-maxage=86400)
|
||
# 3. 只缓存 GET/HEAD;带 Set-Cookie 的响应自动绕过
|
||
# 验证:连续两次 curl -I,应依次看到 cf-cache-status: MISS → HIT
|
||
# ---------------------------------------------------------------------------
|
||
[cache]
|
||
enabled = true
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# D1 (必须) 创建:npx wrangler d1 create artalk-cf
|
||
# 然后把返回的 database_id 填到下面
|
||
# ---------------------------------------------------------------------------
|
||
[[d1_databases]]
|
||
binding = "DB"
|
||
database_name = "artalk-cf"
|
||
database_id = "78140776-2bfd-439a-8b05-c3eebe5d5b47"
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# KV —— RSS 订阅数据(rss-robot 合并进来;KV 与 D1 共存于同一 Worker,
|
||
# 数据沿用原 rss-robot-cf 的 namespace,零迁移)
|
||
# ---------------------------------------------------------------------------
|
||
[[kv_namespaces]]
|
||
binding = "RSS_KV"
|
||
id = "d7f86a0fb43f4450a10b786fc2635128"
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# 普通变量(可以明文写在仓库里)
|
||
# SITE_DEFAULT 默认站点名(博客里 site: '优世界',必须一致)
|
||
# SITE_URL 默认站点地址,用于把相对 page_key 拼成完整 page_url
|
||
# ADMIN_NAME/EMAIL 初始管理员(首次部署后调 /api/v2/setup 初始化)
|
||
# ALLOWED_ORIGINS CORS 白名单,逗号分隔;**必须包含你博客的域名**
|
||
# ---------------------------------------------------------------------------
|
||
[vars]
|
||
SITE_DEFAULT = "优世界"
|
||
SITE_URL = "https://usj.cc"
|
||
# 站内公开 API 基址(用于把 /api/favicon 这类相对地址拼成绝对地址,友圈页在博客域名下用)
|
||
PUBLIC_API_BASE = "https://api.200181.xyz"
|
||
ADMIN_NAME = "admin"
|
||
ADMIN_EMAIL = "admin@200181.xyz"
|
||
ALLOWED_ORIGINS = "https://usj.cc,https://www.usj.cc,https://post.usj.cc,https://artalk.200181.xyz,https://api.200181.xyz,http://localhost:1313,http://127.0.0.1:1313,http://localhost:8799,http://127.0.0.1:8799"
|
||
ITEMS_PER_PAGE = "20"
|
||
PENDING_DEFAULT = "false"
|
||
|
||
# ---- RSS 订阅(rss-robot 合并进来的变量)----
|
||
# SCF_PROXY_URL 腾讯云 SCF 国内代理(scfapi.usj.cc),抓国内博客用
|
||
# FEEDS_URL 远程订阅源 JSON(回退用;KV 里已有 feeds_config,留空避免 Worker 自调用)
|
||
# FEISHU_WEBHOOK_URL 飞书机器人 webhook(可选)
|
||
SCF_PROXY_URL = "https://scfapi.usj.cc"
|
||
FEEDS_URL = ""
|
||
FEISHU_WEBHOOK_URL = ""
|
||
# 文章编辑后端(自建 editor-api 容器,Worker 只做鉴权+反代)。
|
||
# 留空 = 模块自动禁用(/api/v2/editor/* 一律 503)。令牌另走 secret,别写这里。
|
||
# writeapi.usj.cc → 国内机 119.29.215.187 的 nginx 反代 → 127.0.0.1:8017 容器
|
||
# (换机时只改这条 + 新机重跑 deploy/editor-api/bootstrap.sh,域名指向新机即可)
|
||
EDITOR_API_BASE = "https://writeapi.usj.cc"
|
||
|
||
# ⚠️ 下面两个是机密,不要写在这里,用命令写入(详见「部署清单.md」第 4 步):
|
||
# npx wrangler secret put TOKEN_SECRET
|
||
# npx wrangler secret put ADMIN_PASSWORD
|
||
# 本地开发用 .dev.vars 文件(见 .dev.vars.example)
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# 邮件通知(可选)
|
||
#
|
||
# 为什么走 HTTP API 而不是 SMTP:**Cloudflare Workers 没有 TCP socket**,
|
||
# 根本连不上 SMTP 服务器。(Cloudflare 2026-04 推出的原生 Email Service
|
||
# 要 Workers Paid,本项目跑在免费版上。)
|
||
#
|
||
# 用 Resend:免费 3000 封/月(**每天上限 100 封**),本项目内置了日配额保护。
|
||
# 三个都走 secret / 环境变量:
|
||
# npx wrangler secret put RESEND_API_KEY # re_xxxxxxxx(Resend 后台创建)
|
||
# npx wrangler secret put MAIL_FROM # 形如 artalk <noreply@你的域名>
|
||
# npx wrangler secret put MAIL_ADMIN # 管理员收件邮箱;不配则不发管理员通知
|
||
#
|
||
# ⚠️ MAIL_FROM 里的域名必须先在 Resend 后台验证(加 SPF/DKIM 的 DNS 记录)。
|
||
# ⚠️ 三者任一缺失都只是「少发一类邮件」,不会影响评论提交 —— 邮件全程 try/catch。
|
||
# ---------------------------------------------------------------------------
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# R2(可选)—— 评论里插图上传用。没建这个桶就自动禁用上传功能。
|
||
# 创建:npx wrangler r2 bucket create artalk-uploads
|
||
# ---------------------------------------------------------------------------
|
||
# [[r2_buckets]]
|
||
# binding = "UPLOADS"
|
||
# bucket_name = "artalk-uploads"
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# 静态资源(必须)
|
||
# public/sidebar/ = 官方管理后台(客户端用它打开 iframe:{server}/sidebar/)
|
||
# public/dist/ = 官方客户端与插件,博客若想直接从服务端引也可以用
|
||
# 来源:Artalk v2.8.7 的 artalk_ui.tar.gz(预构建,无需前端构建环境)
|
||
# ---------------------------------------------------------------------------
|
||
[assets]
|
||
directory = "public"
|
||
binding = "ASSETS"
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# 自定义域名
|
||
# 200181.xyz 的 NS 就在 Cloudflare 上,所以直接绑 artalk.200181.xyz 即可——
|
||
# custom_domain = true 会让 Cloudflare 自动建 DNS 记录 + 签 SSL 证书。
|
||
#
|
||
# 对比:usj.cc 的 NS 在 DNSPod,所以它不能这么绑(绑了会破坏又拍云/EdgeOne 调度)。
|
||
# ---------------------------------------------------------------------------
|
||
# ---------------------------------------------------------------------------
|
||
# 自定义域名
|
||
# 2026-10-03 起统一到 api.200181.xyz(评论 /api/v2/* + 订阅 /api/* 同一
|
||
# Worker),artalk.200181.xyz 已解绑废弃。usj.cc 的 NS 在 DNSPod 不能这样绑。
|
||
# ---------------------------------------------------------------------------
|
||
[[routes]]
|
||
pattern = "api.200181.xyz"
|
||
custom_domain = true
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# 定时任务(合并 rss-robot 的抓取):
|
||
# 0 * * * * RSS 轮转抓取(每小时一批,约 3 小时覆盖全部源)
|
||
# 17 3 * * * 评论 GC(限流/验证码清理 + healthz 缓存刷新)
|
||
# 10 4 * * * 证书续期检查(探针查剩余天数,≤30 天才真正签发+部署)
|
||
# ---------------------------------------------------------------------------
|
||
[triggers]
|
||
crons = ["17 3 * * *", "0 * * * *", "10 4 * * *"]
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# 资源上限
|
||
# ★ CPU 时间:证书签发要跑多次加密(ECDSA 签名 / SHA-256)与 DER 编码,
|
||
# 虽然总耗时主要是等在网络上(不算 CPU),但给足余量避免 1102 错误。
|
||
# cron 的默认上限就是 30s;HTTP 请求默认也是 30s(付费版可提到 5 分钟)。
|
||
# 免费版会忽略这个字段(免费版硬顶 10ms,签发功能在免费版上不可用)。
|
||
# ---------------------------------------------------------------------------
|
||
[limits]
|
||
cpu_ms = 60000
|
||
|
||
[observability]
|
||
enabled = true
|