Fix runtime DKIM signer synchronization
This commit is contained in:
1 parent
465cd13bbc
commit
b2dee4040c
2 files changed
+67
-3
No files matched your search
+17
-3
@@ -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})` : ''}`)
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
Reference in new issue
Block a user