Fix runtime DKIM signer synchronization

This commit is contained in:
OpenAI Codex committed 2026-10-15 13:33:20 +08:00
1 parent 465cd13bbc
commit b2dee4040c
2 files changed
+67 -3

No files matched your search

+17 -3
View File
@@ -1,7 +1,7 @@
import { and, eq } from 'drizzle-orm'
import { db } from '@/db'
import { dnsRecords } from '@/db/schema'
import { DMS_CONFIG_DIR, DmsResult, dkim as dmsDkim, readContainerFile } from './dms'
import { DMS_CONFIG_DIR, DmsResult, dkim as dmsDkim, readContainerFile, syncDkimRuntime } from './dms'
/**
* DMS 生成的 mail.txt 是 **DNS zone 片段**,不是裸值,例如:
@@ -34,13 +34,19 @@ export async function resolveDkimPublicKey(domainId: number, domainName: string)
const fromDms = await dmsDkim.publicKey(domainName)
if (fromDms.ok) {
const parsed = parseDkimDnsValue(fromDms.stdout)
if (parsed) return parsed
if (parsed) {
await applyDkimRuntimeConfig()
return parsed
}
}
const legacy = await readContainerFile(`${DMS_CONFIG_DIR}/opendkim/keys/${domainName}/mail.txt`)
if (legacy.ok) {
const parsed = parseDkimDnsValue(legacy.stdout)
if (parsed) return parsed
if (parsed) {
await applyDkimRuntimeConfig()
return parsed
}
}
// DMS 16:DKIM 密钥不是自动生成的,需要显式跑 `setup config dkim`
@@ -63,7 +69,15 @@ export async function ensureDkim(domainName: string): Promise<string | null> {
// 刚导入、还没建邮箱的域必须用 `domain <域>` 才会生成(官方支持)。
const gen = await dmsDkim.regenerate(domainName)
if (!(gen as DmsResult).ok) return null
await applyDkimRuntimeConfig()
const again = await dmsDkim.publicKey(domainName)
if (!again.ok) return null
return parseDkimDnsValue(again.stdout)
}
async function applyDkimRuntimeConfig(): Promise<void> {
const result = await syncDkimRuntime()
if (!result.ok) {
throw new Error(`DKIM 配置已生成,但同步到 OpenDKIM 运行配置失败:${result.error}${result.stderr ? ` (${result.stderr})` : ''}`)
}
}
+50
View File
@@ -49,6 +49,56 @@ export function readContainerFile(absPath: string): Promise<DmsResult> {
return dockerExec(['exec', config.dmsContainer, 'cat', absPath], 15_000)
}
let dkimRuntimeSync: Promise<DmsResult> | null = null
/**
* DMS `setup config dkim` writes durable files under /tmp/docker-mailserver,
* while OpenDKIM reads its startup copy under /etc/opendkim. Keep the runtime
* copy current without restarting the whole mail server. Concurrent panel
* requests share one sync/reload operation.
*/
export function syncDkimRuntime(): Promise<DmsResult> {
if (!dkimRuntimeSync) {
const script = `
set -eu
src=/tmp/docker-mailserver/opendkim
dst=/etc/opendkim
[ -s "$src/SigningTable" ] && [ -s "$src/KeyTable" ] && [ -d "$src/keys" ] || exit 0
changed=0
for name in SigningTable KeyTable TrustedHosts; do
if [ -f "$src/$name" ] && { [ ! -f "$dst/$name" ] || ! cmp -s "$src/$name" "$dst/$name"; }; then
changed=1
fi
done
for source_file in $(find "$src/keys" -type f); do
relative_path=\${source_file#"$src/"}
if [ ! -f "$dst/$relative_path" ] || ! cmp -s "$source_file" "$dst/$relative_path"; then
changed=1
break
fi
done
status=$(supervisorctl status opendkim 2>/dev/null || true)
if ! printf '%s' "$status" | grep -Eq '^opendkim[[:space:]]+RUNNING[[:space:]]'; then changed=1; fi
[ "$changed" -eq 1 ] || { echo unchanged; exit 0; }
# Match DMS's own startup setup: copy durable config, set key ownership and
# permissions, then restart only the DKIM signer.
cp -a "$src/." "$dst/"
chown -R opendkim:opendkim "$dst"
chmod -R 0700 "$dst/keys"
if printf '%s' "$status" | grep -Eq '^opendkim[[:space:]]+RUNNING[[:space:]]'; then
supervisorctl restart opendkim
else
supervisorctl start opendkim
fi
`
dkimRuntimeSync = dockerExec(['exec', config.dmsContainer, 'sh', '-ec', script], 20_000)
.finally(() => { dkimRuntimeSync = null })
}
return dkimRuntimeSync
}
/** 服务状态探活:supervisord 管理的各进程 RUNNING/STOPPED 一览 */
export async function supervisorStatus(): Promise<DmsResult> {
try {