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
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:
@@ -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,
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
Reference in New Issue
Block a user