Files
blog/blog-admin/wrangler.toml
T
zqlit 43dbc8b75f feat(ssl): ACME 自动签发与自动续期,实现证书全生命周期闭环
证书管家此前只做「探针」(查剩余天数),现补齐签发+部署两个环节,
参照 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 干净
2026-10-06 16:59:37 +08:00

154 lines
8.3 KiB
TOML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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