pmcf 1.52.6 → 1.53.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,37 @@
1
+ #!/usr/bin/env node
2
+
3
+ import {
4
+ FileContentProvider,
5
+ createPublishingDetails,
6
+ ARCH
7
+ } from "npm-pkgbuild";
8
+ import { prepare } from "../src/cmd.mjs";
9
+
10
+ const { root, args, options } = await prepare();
11
+
12
+ const objectName = args[0];
13
+ const object = await root.load(objectName);
14
+
15
+ console.log(object);
16
+ const publishingDetails = createPublishingDetails(["somewhere"]);
17
+
18
+ const { properties } = await object.preparePackage(options.output);
19
+
20
+ properties.version = "0.0.0";
21
+
22
+ console.log(properties);
23
+
24
+ const sources = [options.output].map(base =>
25
+ new FileContentProvider({
26
+ base
27
+ })[Symbol.asyncIterator]()
28
+ );
29
+
30
+ const output = new ARCH(properties);
31
+
32
+ const fileName = await output.create(sources, [], {}, publishingDetails, {
33
+ destination: "dist",
34
+ verbose: true
35
+ });
36
+
37
+ console.log(fileName);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pmcf",
3
- "version": "1.52.6",
3
+ "version": "1.53.0",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -22,10 +22,8 @@
22
22
  ],
23
23
  "license": "0BSD",
24
24
  "bin": {
25
- "pmcf-host-defs": "./bin/pmcf-host-defs",
25
+ "pmcf-package": "./bin/pmcf-package",
26
26
  "pmcf-info": "./bin/pmcf-info",
27
- "pmcf-location-defs": "./bin/pmcf-location-defs",
28
- "pmcf-named-defs": "./bin/pmcf-named-defs",
29
27
  "pmcf-network": "./bin/pmcf-network"
30
28
  },
31
29
  "scripts": {
@@ -44,7 +42,7 @@
44
42
  "pacc": "^3.3.0"
45
43
  },
46
44
  "devDependencies": {
47
- "@types/node": "^22.13.5",
45
+ "@types/node": "^22.13.8",
48
46
  "ava": "^6.2.0",
49
47
  "c8": "^10.1.3",
50
48
  "documentation": "^14.0.3",
package/src/base.mjs CHANGED
@@ -284,6 +284,19 @@ export class Base {
284
284
  : this.owner.fullName;
285
285
  }
286
286
 
287
+ get packageName() {
288
+ return `${this.constructor.typeDefinition.name}-${this.name}`;
289
+ }
290
+
291
+ async preparePackage(stagingDir) {
292
+ return {
293
+ properties: {
294
+ name: this.packageName,
295
+ description: `${this.constructor.typeDefinition.name} definitions for ${this.fullName}`
296
+ }
297
+ };
298
+ }
299
+
287
300
  expand(object) {
288
301
  switch (typeof object) {
289
302
  case "string":
package/src/dns.mjs CHANGED
@@ -1,3 +1,6 @@
1
+ import { join } from "node:path";
2
+ import { createHmac } from "node:crypto";
3
+ import { writeLines, isIPv6Address, normalizeIPAddress } from "./utils.mjs";
1
4
  import { Base } from "./base.mjs";
2
5
  import { addType } from "./types.mjs";
3
6
 
@@ -46,6 +49,8 @@ export class DNSService extends Base {
46
49
  }
47
50
  super(owner, data);
48
51
  this.read(data, DNSServiceTypeDefinition);
52
+
53
+ owner.addObject(this);
49
54
  }
50
55
 
51
56
  get soaUpdates() {
@@ -97,4 +102,216 @@ export class DNSService extends Base {
97
102
  LLMNR: "no"
98
103
  };
99
104
  }
105
+
106
+ async preparePackage(stagingDir) {
107
+ const { properties } = await super.preparePackage(stagingDir);
108
+
109
+ await generateNamedDefs(this, stagingDir);
110
+
111
+ properties.depends = ["mf-named"];
112
+ properties.replaces = ["mf-named-zones"];
113
+
114
+ return { properties };
115
+ }
116
+ }
117
+
118
+ function fullName(name) {
119
+ return name.endsWith(".") ? name : name + ".";
120
+ }
121
+
122
+ async function generateNamedDefs(dns, targetDir) {
123
+ const ttl = dns.recordTTL;
124
+ const updates = [Math.ceil(Date.now() / 1000), ...dns.soaUpdates].join(" ");
125
+
126
+ for (const domain of dns.domains) {
127
+ const zones = [];
128
+ const records = new Set();
129
+
130
+ const nameserver = (await dns.owner.findService({ type: "dns" }))?.owner;
131
+ const rname = dns.administratorEmail.replace(/@/, ".");
132
+
133
+ let maxKeyLength;
134
+
135
+ const createRecord = (key, type, ...values) => {
136
+ values = values.map(v =>
137
+ typeof v === "number" ? String(v).padStart(3) : v
138
+ );
139
+
140
+ return {
141
+ key,
142
+ toString: () =>
143
+ `${key.padEnd(maxKeyLength, " ")} ${ttl} IN ${type.padEnd(
144
+ 5,
145
+ " "
146
+ )} ${values.join(" ")}`
147
+ };
148
+ };
149
+
150
+ for await (const mail of dns.owner.findServices({ type: "smtp" })) {
151
+ records.add(
152
+ createRecord("@", "MX", mail.priority, fullName(mail.owner.domainName))
153
+ );
154
+ }
155
+
156
+ console.log(dns.owner.fullName, domain, nameserver?.hostName, rname);
157
+ const reverseZones = new Map();
158
+
159
+ const SOARecord = createRecord(
160
+ "@",
161
+ "SOA",
162
+ fullName(nameserver?.domainName),
163
+ fullName(rname),
164
+ `(${updates})`
165
+ );
166
+
167
+ const NSRecord = createRecord("@", "NS", fullName(nameserver?.rawAddress));
168
+
169
+ const catalogZone = {
170
+ id: `catalog.${domain}`,
171
+ file: `catalog.${domain}.zone`,
172
+ records: new Set([
173
+ SOARecord,
174
+ NSRecord,
175
+ createRecord(fullName(`version.${domain}`), "TXT", '"2"')
176
+ ])
177
+ };
178
+
179
+ const zone = {
180
+ id: domain,
181
+ file: `${domain}.zone`,
182
+ records: new Set([SOARecord, NSRecord, ...records])
183
+ };
184
+ zones.push(zone);
185
+
186
+ const hosts = new Set();
187
+ const addresses = new Set();
188
+
189
+ for await (const {
190
+ address,
191
+ subnet,
192
+ networkInterface
193
+ } of dns.owner.networkAddresses()) {
194
+ const host = networkInterface.host;
195
+
196
+ if (!addresses.has(address)) {
197
+ addresses.add(address);
198
+
199
+ zone.records.add(
200
+ createRecord(
201
+ fullName(host.domainName),
202
+ isIPv6Address(address) ? "AAAA" : "A",
203
+ normalizeIPAddress(address)
204
+ )
205
+ );
206
+
207
+ if (subnet) {
208
+ let reverseZone = reverseZones.get(subnet.address);
209
+
210
+ if (!reverseZone) {
211
+ const reverseArpa = reverseArpaAddress(subnet.prefix);
212
+ reverseZone = {
213
+ id: reverseArpa,
214
+ file: `${reverseArpa}.zone`,
215
+ records: new Set([SOARecord, NSRecord])
216
+ };
217
+ zones.push(reverseZone);
218
+ reverseZones.set(subnet.address, reverseZone);
219
+ }
220
+ reverseZone.records.add(
221
+ createRecord(
222
+ fullName(reverseArpaAddress(address)),
223
+ "PTR",
224
+ fullName(host.domainName)
225
+ )
226
+ );
227
+ }
228
+ }
229
+
230
+ if (!hosts.has(host)) {
231
+ hosts.add(host);
232
+ for (const service of host.findServices()) {
233
+ if (service.master && service.alias) {
234
+ zone.records.add(
235
+ createRecord(service.alias, "CNAME", fullName(host.domainName))
236
+ );
237
+ }
238
+
239
+ if (dns.hasSVRRecords && service.srvPrefix) {
240
+ zone.records.add(
241
+ createRecord(
242
+ fullName(`${service.srvPrefix}.${host.domainName}`),
243
+ "SRV",
244
+ service.priority,
245
+ service.weight,
246
+ service.port,
247
+ fullName(host.domainName)
248
+ )
249
+ );
250
+ }
251
+ }
252
+ }
253
+ }
254
+
255
+ const zoneConfig = [];
256
+
257
+ if (dns.hasCatalog) {
258
+ zones.push(catalogZone);
259
+ }
260
+
261
+ for (const zone of zones) {
262
+ if (zone !== catalogZone) {
263
+ const hash = createHmac("md5", zone.id).digest("hex");
264
+ catalogZone.records.add(
265
+ createRecord(`${hash}.zones.${domain}.`, "PTR", `${zone.id}.`)
266
+ );
267
+ }
268
+
269
+ zoneConfig.push(`zone \"${zone.id}\" {`);
270
+ zoneConfig.push(` type master;`);
271
+ zoneConfig.push(` file \"${zone.file}\";`);
272
+
273
+ zoneConfig.push(
274
+ ` allow-update { ${
275
+ dns.allowedUpdates.length ? dns.allowedUpdates.join(";") : "none"
276
+ }; };`
277
+ );
278
+ zoneConfig.push(` notify ${dns.notify ? "yes" : "no"};`);
279
+ zoneConfig.push(`};`);
280
+ zoneConfig.push("");
281
+
282
+ maxKeyLength = 0;
283
+ for (const r of zone.records) {
284
+ if (r.key.length > maxKeyLength) {
285
+ maxKeyLength = r.key.length;
286
+ }
287
+ }
288
+
289
+ writeLines(join(targetDir, "var/lib/named"), zone.file, zone.records);
290
+ }
291
+
292
+ writeLines(
293
+ join(targetDir, "etc/named.d/zones"),
294
+ `${domain}.zone.conf`,
295
+ zoneConfig
296
+ );
297
+ }
298
+ }
299
+
300
+ export function reverseAddress(address) {
301
+ if (isIPv6Address(address)) {
302
+ return normalizeIPAddress(address)
303
+ .replaceAll(":", "")
304
+ .split("")
305
+ .reverse()
306
+ .join(".");
307
+ }
308
+
309
+ return address.split(".").reverse().join(".");
310
+ }
311
+
312
+ export function reverseArpaAddress(address) {
313
+ return (
314
+ reverseAddress(address) +
315
+ (isIPv6Address(address) ? ".ip6.arpa" : ".in-addr.arpa")
316
+ );
100
317
  }
package/src/host.mjs CHANGED
@@ -11,6 +11,12 @@ import {
11
11
  hasWellKnownSubnet
12
12
  } from "./utils.mjs";
13
13
  import { addType } from "./types.mjs";
14
+ import {
15
+ generateNetworkDefs,
16
+ generateMachineInfo,
17
+ copySshKeys,
18
+ generateKnownHosts
19
+ } from "./host-utils.mjs";
14
20
 
15
21
  const HostTypeDefinition = {
16
22
  name: "host",
@@ -318,6 +324,24 @@ export class Host extends Base {
318
324
  async publicKey(type = "ed25519") {
319
325
  return readFile(join(this.directory, `ssh_host_${type}_key.pub`), "utf8");
320
326
  }
327
+
328
+ async preparePackage(stagingDir) {
329
+ const { properties } = await super.preparePackage(stagingDir);
330
+ await generateNetworkDefs(this, stagingDir);
331
+ await generateMachineInfo(this, stagingDir);
332
+ await copySshKeys(this, stagingDir);
333
+ await generateKnownHosts(
334
+ this.owner.hosts(),
335
+ join(stagingDir, "root", ".ssh")
336
+ );
337
+
338
+ properties.provides = [...this.provides];
339
+ properties.depends = [this.location.packageName, ...this.depends];
340
+ properties.replaces = [`mf-${this.hostName}`, ...this.replaces];
341
+ properties.backup = "root/.ssh/known_hosts";
342
+
343
+ return { properties };
344
+ }
321
345
  }
322
346
 
323
347
  const NetworkInterfaceTypeDefinition = {
package/src/location.mjs CHANGED
@@ -1,5 +1,8 @@
1
+ import { mkdir, copyFile } from "node:fs/promises";
2
+ import { join } from "node:path";
1
3
  import { Owner } from "./owner.mjs";
2
4
  import { addType } from "./types.mjs";
5
+ import { writeLines, sectionLines } from "./utils.mjs";
3
6
 
4
7
  const LocationTypeDefinition = {
5
8
  name: "location",
@@ -30,7 +33,6 @@ export class Location extends Owner {
30
33
  return this;
31
34
  }
32
35
 
33
-
34
36
  locationNamed(name) {
35
37
  if (this.isNamed(name)) {
36
38
  return this;
@@ -42,4 +44,52 @@ export class Location extends Owner {
42
44
  get network() {
43
45
  return [...this.typeList("network")][0] || super.network;
44
46
  }
47
+
48
+ async preparePackage(stagingDir) {
49
+ const { properties } = await super.preparePackage(stagingDir);
50
+
51
+ await writeLines(
52
+ join(stagingDir, "etc/systemd/resolved.conf.d"),
53
+ `${this.name}.conf`,
54
+ sectionLines("Resolve", await this.dns.resolvedConfig())
55
+ );
56
+
57
+ await writeLines(
58
+ join(stagingDir, "etc/systemd/journald.conf.d"),
59
+ `${this.name}.conf`,
60
+ sectionLines("Journal", {
61
+ Compress: "yes",
62
+ SystemMaxUse: "500M",
63
+ SyncIntervalSec: "15m"
64
+ })
65
+ );
66
+
67
+ await writeLines(
68
+ join(stagingDir, "etc/systemd/timesyncd.conf.d"),
69
+ `${this.name}.conf`,
70
+ sectionLines("Time", {
71
+ NTP: this.ntp.servers.join(" "),
72
+ PollIntervalMinSec: 60,
73
+ SaveIntervalSec: 3600
74
+ })
75
+ );
76
+
77
+ const locationDir = join(stagingDir, "etc", "location");
78
+
79
+ await mkdir(locationDir, { recursive: true });
80
+
81
+ await copyFile(
82
+ join(this.directory, "location.json"),
83
+ join(locationDir, "location.json")
84
+ );
85
+
86
+ properties.provides = [
87
+ "location",
88
+ "mf-location",
89
+ `mf-location-${this.name}`
90
+ ];
91
+ properties.replaces = [`mf-location-${this.name}`];
92
+
93
+ return { properties };
94
+ }
45
95
  }
package/src/owner.mjs CHANGED
@@ -75,6 +75,18 @@ export class Owner extends Base {
75
75
  return object;
76
76
  }
77
77
  }
78
+
79
+ // TODO cascade
80
+ const parts = name.split(/\//);
81
+
82
+ if (parts.length >= 2) {
83
+ const last = parts.pop();
84
+
85
+ const next = this.named(parts.join("/"));
86
+ if (next) {
87
+ return next.named(last);
88
+ }
89
+ }
78
90
  }
79
91
 
80
92
  typeNamed(typeName, name) {
package/types/base.d.mts CHANGED
@@ -59,6 +59,13 @@ export class Base {
59
59
  set directory(directory: any);
60
60
  get directory(): any;
61
61
  get fullName(): any;
62
+ get packageName(): string;
63
+ preparePackage(stagingDir: any): Promise<{
64
+ properties: {
65
+ name: string;
66
+ description: string;
67
+ };
68
+ }>;
62
69
  expand(object: any): any;
63
70
  finalize(action: any): void;
64
71
  execFinalize(): void;
package/types/dns.d.mts CHANGED
@@ -1,3 +1,5 @@
1
+ export function reverseAddress(address: any): any;
2
+ export function reverseArpaAddress(address: any): string;
1
3
  export class DNSService extends Base {
2
4
  static get typeDefinition(): {
3
5
  name: string;
@@ -1,31 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- import { join } from "node:path";
4
- import { types } from "pmcf";
5
- import { prepare } from "../src/cmd.mjs";
6
- import {
7
- generateNetworkDefs,
8
- generateMachineInfo,
9
- copySshKeys,
10
- generateKnownHosts
11
- } from "../src/host-utils.mjs";
12
-
13
- const { root, args, options } = await prepare();
14
-
15
- const hostName = args[0];
16
-
17
- const host = await root.load(hostName, { type: types.host });
18
-
19
- await generateNetworkDefs(host, options.output);
20
- await generateMachineInfo(host, options.output);
21
- await copySshKeys(host, options.output);
22
- await generateKnownHosts(
23
- host.owner.hosts(),
24
- join(options.output, "root", ".ssh")
25
- );
26
-
27
- console.log("provides", "host", ...host.provides);
28
- console.log("depends", `location-${host.location.name}`, ...host.depends);
29
- console.log("replaces", `mf-${host.hostName}`, ...host.replaces);
30
- console.log("description", `host definitions for ${host.domainName}`);
31
- console.log("backup", "root/.ssh/known_hosts");
@@ -1,59 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- import { mkdir, copyFile } from "node:fs/promises";
4
- import { join } from "node:path";
5
- import { types } from "pmcf";
6
- import { writeLines, sectionLines } from "../src/utils.mjs";
7
- import { prepare } from "../src/cmd.mjs";
8
-
9
- const { root, args, options } = await prepare();
10
-
11
- const location = await root.load(args[0], { type: types.location });
12
-
13
- await generateLocationDefs(location, options.output);
14
-
15
- console.log(
16
- "provides",
17
- "location",
18
- "mf-location",
19
- `mf-location-${location.name}`
20
- );
21
- console.log("replaces", `mf-location-${location.name}`);
22
- console.log("description", `location definitions for ${location.name}`);
23
-
24
- async function generateLocationDefs(location, dir) {
25
- await writeLines(
26
- join(dir, "etc/systemd/resolved.conf.d"),
27
- `${location.name}.conf`,
28
- sectionLines("Resolve", await location.dns.resolvedConfig())
29
- );
30
-
31
- await writeLines(
32
- join(dir, "etc/systemd/journald.conf.d"),
33
- `${location.name}.conf`,
34
- sectionLines("Journal", {
35
- Compress: "yes",
36
- SystemMaxUse: "500M",
37
- SyncIntervalSec: "15m"
38
- })
39
- );
40
-
41
- await writeLines(
42
- join(dir, "etc/systemd/timesyncd.conf.d"),
43
- `${location.name}.conf`,
44
- sectionLines("Time", {
45
- NTP: location.ntp.servers.join(" "),
46
- PollIntervalMinSec: 60,
47
- SaveIntervalSec: 3600
48
- })
49
- );
50
-
51
- const locationDir = join(dir, "etc", "location");
52
-
53
- await mkdir(locationDir, { recursive: true });
54
-
55
- await copyFile(
56
- join(location.directory, "location.json"),
57
- join(locationDir, "location.json")
58
- );
59
- }
@@ -1,222 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- import { join } from "node:path";
4
- import { createHmac } from "node:crypto";
5
- import {
6
- writeLines,
7
- isIPv6Address,
8
- normalizeIPAddress
9
- } from "../src/utils.mjs";
10
- import { prepare } from "../src/cmd.mjs";
11
-
12
- const { root, args, options } = await prepare();
13
-
14
- const owner = await root.load(args[0]);
15
-
16
- await generateNamedDefs(owner, options.output);
17
-
18
- console.log("depends", "mf-named");
19
- console.log("replaces", "mf-named-zones");
20
- console.log("description", `named defintions for ${owner.name}`);
21
-
22
- function fullName(name) {
23
- return name.endsWith(".") ? name : name + ".";
24
- }
25
-
26
- async function generateNamedDefs(owner, targetDir) {
27
- const dns = owner.dns;
28
- const ttl = dns.recordTTL;
29
- const updates = [Math.ceil(Date.now() / 1000), ...dns.soaUpdates].join(" ");
30
-
31
- for (const domain of dns.domains) {
32
- const zones = [];
33
- const records = new Set();
34
-
35
- const nameserver = (await owner.findService({ type: "dns" }))?.owner;
36
- const rname = dns.administratorEmail.replace(/@/, ".");
37
-
38
- let maxKeyLength;
39
-
40
- const createRecord = (key, type, ...values) => {
41
- values = values.map(v =>
42
- typeof v === "number" ? String(v).padStart(3) : v
43
- );
44
-
45
- return {
46
- key,
47
- toString: () =>
48
- `${key.padEnd(maxKeyLength, " ")} ${ttl} IN ${type.padEnd(
49
- 5,
50
- " "
51
- )} ${values.join(" ")}`
52
- };
53
- };
54
-
55
- for await (const mail of owner.findServices({ type: "smtp" })) {
56
- records.add(
57
- createRecord("@", "MX", mail.priority, fullName(mail.owner.domainName))
58
- );
59
- }
60
-
61
- console.log(owner.fullName, domain, nameserver?.hostName, rname);
62
- const reverseZones = new Map();
63
-
64
- const SOARecord = createRecord(
65
- "@",
66
- "SOA",
67
- fullName(nameserver?.domainName),
68
- fullName(rname),
69
- `(${updates})`
70
- );
71
-
72
- const NSRecord = createRecord("@", "NS", fullName(nameserver?.rawAddress));
73
-
74
- const catalogZone = {
75
- id: `catalog.${domain}`,
76
- file: `catalog.${domain}.zone`,
77
- records: new Set([
78
- SOARecord,
79
- NSRecord,
80
- createRecord(fullName(`version.${domain}`), "TXT", '"2"')
81
- ])
82
- };
83
-
84
- const zone = {
85
- id: domain,
86
- file: `${domain}.zone`,
87
- records: new Set([SOARecord, NSRecord, ...records])
88
- };
89
- zones.push(zone);
90
-
91
- const hosts = new Set();
92
- const addresses = new Set();
93
-
94
- for await (const {
95
- address,
96
- subnet,
97
- networkInterface
98
- } of owner.networkAddresses()) {
99
- const host = networkInterface.host;
100
-
101
- if (!addresses.has(address)) {
102
- addresses.add(address);
103
-
104
- zone.records.add(
105
- createRecord(
106
- fullName(host.domainName),
107
- isIPv6Address(address) ? "AAAA" : "A",
108
- normalizeIPAddress(address)
109
- )
110
- );
111
-
112
- if (subnet) {
113
- let reverseZone = reverseZones.get(subnet.address);
114
-
115
- if (!reverseZone) {
116
- const reverseArpa = reverseArpaAddress(subnet.prefix);
117
- reverseZone = {
118
- id: reverseArpa,
119
- file: `${reverseArpa}.zone`,
120
- records: new Set([SOARecord, NSRecord])
121
- };
122
- zones.push(reverseZone);
123
- reverseZones.set(subnet.address, reverseZone);
124
- }
125
- reverseZone.records.add(
126
- createRecord(
127
- fullName(reverseArpaAddress(address)),
128
- "PTR",
129
- fullName(host.domainName)
130
- )
131
- );
132
- }
133
- }
134
-
135
- if (!hosts.has(host)) {
136
- hosts.add(host);
137
- for (const service of host.findServices()) {
138
- if (service.master && service.alias) {
139
- zone.records.add(
140
- createRecord(service.alias, "CNAME", fullName(host.domainName))
141
- );
142
- }
143
-
144
- if (dns.hasSVRRecords && service.srvPrefix) {
145
- zone.records.add(
146
- createRecord(
147
- fullName(`${service.srvPrefix}.${host.domainName}`),
148
- "SRV",
149
- service.priority,
150
- service.weight,
151
- service.port,
152
- fullName(host.domainName)
153
- )
154
- );
155
- }
156
- }
157
- }
158
- }
159
-
160
- const zoneConfig = [];
161
-
162
- if (dns.hasCatalog) {
163
- zones.push(catalogZone);
164
- }
165
-
166
- for (const zone of zones) {
167
- if (zone !== catalogZone) {
168
- const hash = createHmac("md5", zone.id).digest("hex");
169
- catalogZone.records.add(
170
- createRecord(`${hash}.zones.${domain}.`, "PTR", `${zone.id}.`)
171
- );
172
- }
173
-
174
- zoneConfig.push(`zone \"${zone.id}\" {`);
175
- zoneConfig.push(` type master;`);
176
- zoneConfig.push(` file \"${zone.file}\";`);
177
-
178
- zoneConfig.push(
179
- ` allow-update { ${
180
- dns.allowedUpdates.length ? dns.allowedUpdates.join(";") : "none"
181
- }; };`
182
- );
183
- zoneConfig.push(` notify ${dns.notify ? "yes" : "no"};`);
184
- zoneConfig.push(`};`);
185
- zoneConfig.push("");
186
-
187
- maxKeyLength = 0;
188
- for (const r of zone.records) {
189
- if (r.key.length > maxKeyLength) {
190
- maxKeyLength = r.key.length;
191
- }
192
- }
193
-
194
- writeLines(join(targetDir, "var/lib/named"), zone.file, zone.records);
195
- }
196
-
197
- writeLines(
198
- join(targetDir, "etc/named.d/zones"),
199
- `${domain}.zone.conf`,
200
- zoneConfig
201
- );
202
- }
203
- }
204
-
205
- export function reverseAddress(address) {
206
- if (isIPv6Address(address)) {
207
- return normalizeIPAddress(address)
208
- .replaceAll(":", "")
209
- .split("")
210
- .reverse()
211
- .join(".");
212
- }
213
-
214
- return address.split(".").reverse().join(".");
215
- }
216
-
217
- export function reverseArpaAddress(address) {
218
- return (
219
- reverseAddress(address) +
220
- (isIPv6Address(address) ? ".ip6.arpa" : ".in-addr.arpa")
221
- );
222
- }