pmcf 1.52.6 → 1.53.1
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.
- package/bin/pmcf-package +37 -0
- package/package.json +3 -5
- package/src/base.mjs +14 -0
- package/src/dns.mjs +217 -0
- package/src/host.mjs +24 -0
- package/src/location.mjs +51 -1
- package/src/owner.mjs +12 -0
- package/types/base.d.mts +8 -0
- package/types/dns.d.mts +2 -0
- package/bin/pmcf-host-defs +0 -31
- package/bin/pmcf-location-defs +0 -59
- package/bin/pmcf-named-defs +0 -222
package/bin/pmcf-package
ADDED
|
@@ -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.
|
|
3
|
+
"version": "1.53.1",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
@@ -22,10 +22,8 @@
|
|
|
22
22
|
],
|
|
23
23
|
"license": "0BSD",
|
|
24
24
|
"bin": {
|
|
25
|
-
"pmcf-
|
|
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.
|
|
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,20 @@ 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
|
+
access: "private"
|
|
297
|
+
}
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
|
|
287
301
|
expand(object) {
|
|
288
302
|
switch (typeof object) {
|
|
289
303
|
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,14 @@ 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
|
+
access: string;
|
|
68
|
+
};
|
|
69
|
+
}>;
|
|
62
70
|
expand(object: any): any;
|
|
63
71
|
finalize(action: any): void;
|
|
64
72
|
execFinalize(): void;
|
package/types/dns.d.mts
CHANGED
package/bin/pmcf-host-defs
DELETED
|
@@ -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");
|
package/bin/pmcf-location-defs
DELETED
|
@@ -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
|
-
}
|
package/bin/pmcf-named-defs
DELETED
|
@@ -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
|
-
}
|