feat(dns): add functions to handle missing DNS records and mark records as synced
CD / quality (push) Successful in 1m16s
quality / changes (push) Successful in 9s
quality / web (push) Skipped
quality / api (push) Successful in 1m3s
quality / commitlint (push) Skipped
quality / docker-check (push) Skipped
CD / update-wiki (push) Successful in 5s
CD / publish (push) Successful in 1m24s

- Introduced `isMissingCfDnsRecord` to identify missing Cloudflare DNS records based on error messages.
- Added `markSynced` function to update DNS record fields in the database and return the updated record.
- Refactored `pushRecord` to utilize `markSynced` for better code organization and clarity.
- Enhanced error handling for cases where DNS records need to be recreated after manual edits.
This commit is contained in:
Denozordec
2026-09-03 18:52:39 +07:00
parent de3dbe8521
commit df5d5ef4ab
3 changed files with 150 additions and 68 deletions
+52 -15
View File
@@ -64,6 +64,41 @@ function toCfPayload(
};
}
function isMissingCfDnsRecord(error: unknown): boolean {
const message = error instanceof Error ? error.message : String(error);
return /record does not exist|81044/i.test(message);
}
async function markSynced(
db: Db,
domainId: number,
record: DnsRecord,
cfRec: {
id?: string | null;
type?: string;
name: string;
content: string;
ttl: number;
proxied?: boolean | null;
priority?: number | null;
},
): Promise<DnsRecord> {
repos.updateDnsFields(
db,
record.id,
cfRec.type ?? record.record_type,
cfRec.name,
cfRec.content,
cfRec.ttl,
cfRec.proxied ?? false,
cfRec.priority ?? null,
SYNC_SYNCED,
cfRec.id ?? null,
null,
);
return repos.getDnsRecord(db, domainId, record.id);
}
async function pushRecord(
db: Db,
cf: CloudflareClient,
@@ -84,22 +119,24 @@ async function pushRecord(
const cfRec = record.cf_record_id
? await cf.updateDnsRecord(cfZoneId, record.cf_record_id, payload)
: await cf.createDnsRecord(cfZoneId, payload);
repos.updateDnsFields(
db,
record.id,
cfRec.type ?? record.record_type,
cfRec.name,
cfRec.content,
cfRec.ttl,
cfRec.proxied ?? false,
cfRec.priority ?? null,
SYNC_SYNCED,
cfRec.id ?? null,
null,
);
return repos.getDnsRecord(db, domainId, record.id);
return markSynced(db, domainId, record, cfRec);
} catch (e) {
// Stale cf_record_id after manual CF edits / prior buggy sync — recreate.
if (record.cf_record_id && isMissingCfDnsRecord(e)) {
try {
const created = await cf.createDnsRecord(cfZoneId, payload);
return markSynced(db, domainId, record, created);
} catch (createErr) {
repos.setDnsSyncStatus(
db,
record.id,
SYNC_ERROR,
null,
createErr instanceof Error ? createErr.message : String(createErr),
);
throw createErr;
}
}
repos.setDnsSyncStatus(
db,
record.id,
+13 -25
View File
@@ -313,16 +313,20 @@ function desiredAIps(
scope === "binding"
? getBindingLbState(db, refId)
: getGroupLbState(db, refId);
const serviceIps =
// Configured binding/group IPs stay intact; DNS publishes only enabled ones.
const enabledIps =
scope === "binding"
? enabledServiceIps(db, repos.getBinding(db, refId).service_id)
: fallbackIps;
const enabledSet = new Set(enabledIps);
const activeFallback = fallbackIps.filter((ip) => enabledSet.has(ip));
const activeRows = state.rows.filter((row) => enabledSet.has(row.ip));
return resolveDesiredAIps(
state.config,
state.rows,
fallbackIps,
activeRows,
activeFallback,
Date.now(),
serviceIps,
enabledIps,
);
}
@@ -1129,10 +1133,12 @@ async function collectGroupDnsIps(
const ips: string[] = [];
for (const service of services) {
if (!service.enabled) continue;
const enabled = new Set(enabledServiceIps(db, service.id));
const bindings = repos.listBindingsByService(db, service.id);
for (const binding of bindings) {
for (const ip of repos.listBindingIps(db, binding.id)) {
if (!ips.includes(ip)) ips.push(ip);
if (!enabled.has(ip) || ips.includes(ip)) continue;
ips.push(ip);
}
}
}
@@ -1653,26 +1659,8 @@ export async function toggleServiceIp(
repos.updateNode(db, node.id, { enabled });
}
const bindings = repos.listBindingsByService(db, serviceId);
for (const binding of bindings) {
if (binding.cname_target?.trim()) continue;
const current = repos.listBindingIpsWithMeta(db, binding.id);
const hasIp = current.some((entry) => entry.ip === ip);
if (enabled && !hasIp) {
repos.replaceBindingIpsWithMeta(db, binding.id, [
...current,
{ ip, weight: 1, priority: 1 },
]);
continue;
}
if (!enabled && hasIp) {
repos.replaceBindingIpsWithMeta(
db,
binding.id,
current.filter((entry) => entry.ip !== ip),
);
}
}
// Keep binding IP membership stable (common FQDN = full pool). DNS sync
// filters by enabledServiceIps via desiredAIps — do not reshuffle bindings.
const service = repos.getService(db, serviceId);
if (shouldPushDns(db, service)) {