import fs from "node:fs"; import os from "node:os"; import path from "node:path"; import { normalizeLowercaseStringOrEmpty } from "../shared/string-coerce.js"; import { CONFIG_DIR, ensureDir } from "../utils.js";
function formatYyyyMmDd(date: Date): string { const y = date.getUTCFullYear(); const m = String(date.getUTCMonth() + 1).padStart(2, "0"); const d = String(date.getUTCDate()).padStart(2, "0"); return `${y}${m}${d}`;
}
function nextSerial(existingSerial: number | null, now: Date): number { const today = formatYyyyMmDd(now); const base = Number.parseInt(`${today}01`, 10); if (!existingSerial || !Number.isFinite(existingSerial)) { return base;
} const existing = String(existingSerial); if (existing.startsWith(today)) { return existingSerial + 1;
} return base;
}
function extractSerial(zoneText: string): number | null { const match = zoneText.match(/^\s*@\s+IN\s+SOA\s+\S+\s+\S+\s+(\d+)\s+/m); if (!match) { returnnull;
} const parsed = Number.parseInt(match[1], 10); return Number.isFinite(parsed) ? parsed : null;
}
function extractContentHash(zoneText: string): string | null { const match = zoneText.match(/^\s*;\s*openclaw-content-hash:\s*(\S+)\s*$/m); return match?.[1] ?? null;
}
function computeContentHash(body: string): string { // Cheap stable hash; avoids importing crypto (and keeps deterministic across runtimes).
let h = 2166136261; for (let i = 0; i < body.length; i++) {
h ^= body.charCodeAt(i);
h = Math.imul(h, 16777619);
} return (h >>> 0).toString(16).padStart(8, "0");
}
const txt = [
`displayName=${opts.displayName.trim() || hostname}`,
`role=gateway`,
`transport=gateway`,
`gatewayPort=${opts.gatewayPort}`,
]; if (opts.gatewayTlsEnabled) {
txt.push(`gatewayTls=1`); if (opts.gatewayTlsFingerprintSha256) {
txt.push(`gatewayTlsSha256=${opts.gatewayTlsFingerprintSha256}`);
}
} if (opts.tailnetDns?.trim()) {
txt.push(`tailnetDns=${opts.tailnetDns.trim()}`);
} if (typeof opts.sshPort === "number" && opts.sshPort > 0) {
txt.push(`sshPort=${opts.sshPort}`);
} if (opts.cliPath?.trim()) {
txt.push(`cliPath=${opts.cliPath.trim()}`);
}
const records: string[] = [];
records.push(`$ORIGIN ${domain}`);
records.push(`$TTL 60`); const soaLine = `@ IN SOA ns1 hostmaster ${opts.serial} 72003600120960060`;
records.push(soaLine);
records.push(`@ IN NS ns1`);
records.push(`ns1 IN A ${opts.tailnetIPv4}`);
records.push(`${hostLabel} IN A ${opts.tailnetIPv4}`); if (opts.tailnetIPv6) {
records.push(`${hostLabel} IN AAAA ${opts.tailnetIPv6}`);
}
records.push(`_openclaw-gw._tcp IN PTR ${instanceLabel}._openclaw-gw._tcp`);
records.push(`${instanceLabel}._openclaw-gw._tcp IN SRV 00 ${opts.gatewayPort} ${hostLabel}`);
records.push(`${instanceLabel}._openclaw-gw._tcp IN TXT ${txt.map(txtQuote).join(" ")}`);
const contentBody = `${records.join("\n")}\n`; const hashBody = `${records
.map((line) =>
line === soaLine ? `@ IN SOA ns1 hostmaster SERIAL 72003600120960060` : line,
)
.join("\n")}\n`; const contentHash = computeContentHash(hashBody);
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.