diff --git a/deploy/cloudflare/src/steps.ts b/deploy/cloudflare/src/steps.ts index d54f8e8..615861d 100644 --- a/deploy/cloudflare/src/steps.ts +++ b/deploy/cloudflare/src/steps.ts @@ -69,6 +69,11 @@ export async function discoverModules(rootDir: string, selectedIds: string[]): P return refs; } +/** 多级子域判定:Universal SSL 只覆盖 apex + 一级通配(*.zone),更深的需要 Total TLS。 */ +export function needsTotalTls(domain: string, zoneName: string): boolean { + return domain.split('.').length > zoneName.split('.').length + 1; +} + /** 九步主流程。返回部署摘要(供测试断言与部署输出)。 */ export async function runNineSteps(input: { rootDir: string; @@ -181,8 +186,14 @@ export async function runNineSteps(input: { await removeLegacyCustomDomains({ accountId, domain: config.domain, apiToken: token, log: rep.log }); }); await cleanup(); - // 多级子域不在 Universal SSL 覆盖内:提前触发 Total TLS 签发(幂等) + // 多级子域不在 Universal SSL 覆盖内:提前触发 Total TLS 签发(幂等)。 + // 一级子域(*.zone)由 Universal SSL 通配证书覆盖,无需 Total TLS(且免费计划无 ACM 会报 1450) + const needsTls = needsTotalTls(config.domain, zone.name); const totalTls = input.ensureTotalTls ?? (async () => { + if (!needsTls) { + rep.log(`跳过 Total TLS:${config.domain} 为一级子域(Universal SSL 覆盖)`); + return; + } await ensureTotalTls({ zoneId: zone.id, apiToken: process.env.CLOUDFLARE_API_TOKEN ?? '', diff --git a/deploy/cloudflare/test/steps.test.ts b/deploy/cloudflare/test/steps.test.ts index da833d8..b4a04ea 100644 --- a/deploy/cloudflare/test/steps.test.ts +++ b/deploy/cloudflare/test/steps.test.ts @@ -1,6 +1,6 @@ // SPDX-License-Identifier: AGPL-3.0-only import { describe, expect, it } from 'vitest'; -import { runNineSteps } from '../src/steps'; +import { needsTotalTls, runNineSteps } from '../src/steps'; import type { Wrangler } from '../src/wrangler'; /** @@ -100,6 +100,18 @@ const SMOKE_OK = { ), }; +describe('needsTotalTls(Universal SSL 覆盖边界)', () => { + it('一级子域:覆盖,不需要', () => { + expect(needsTotalTls('unself.handywote.top', 'handywote.top')).toBe(false); + }); + it('多级子域:不覆盖,需要', () => { + expect(needsTotalTls('unself.demo.handywote.top', 'handywote.top')).toBe(true); + }); + it('apex 自身:覆盖,不需要', () => { + expect(needsTotalTls('handywote.top', 'handywote.top')).toBe(false); + }); +}); + describe('runNineSteps(九步编排 · 幂等收敛)', () => { it('空账号首跑:命令序列覆盖九步;二跑零 create/put(收敛)', { timeout: 120_000 }, async () => { const first = makeFakeWrangler();