pmcf 1.52.5 → 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.
- package/bin/pmcf-package +37 -0
- package/package.json +3 -5
- package/src/base.mjs +13 -0
- package/src/dns.mjs +219 -0
- package/src/host.mjs +24 -0
- package/src/location.mjs +51 -1
- package/src/owner.mjs +12 -0
- package/types/base.d.mts +7 -0
- package/types/cluster.d.mts +10 -0
- package/types/dns.d.mts +8 -0
- package/types/location.d.mts +10 -0
- package/types/network.d.mts +5 -0
- package/types/owner.d.mts +5 -0
- package/types/root.d.mts +10 -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.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-
|
|
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,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
|
|
|
@@ -8,6 +11,7 @@ const DNSServiceTypeDefinition = {
|
|
|
8
11
|
properties: {
|
|
9
12
|
hasSVRRecords: { type: "boolean", collection: false, writeable: true },
|
|
10
13
|
hasCatalog: { type: "boolean", collection: false, writeable: true },
|
|
14
|
+
notify: { type: "boolean", collection: false, writeable: true },
|
|
11
15
|
recordTTL: { type: "string", collection: false, writeable: true },
|
|
12
16
|
refresh: { type: "string", collection: false, writeable: true },
|
|
13
17
|
retry: { type: "string", collection: false, writeable: true },
|
|
@@ -23,6 +27,7 @@ export class DNSService extends Base {
|
|
|
23
27
|
recordTTL = "1W";
|
|
24
28
|
hasSVRRecords = true;
|
|
25
29
|
hasCatalog = true;
|
|
30
|
+
notify = true;
|
|
26
31
|
#forwardsTo = [];
|
|
27
32
|
|
|
28
33
|
refresh = 36000;
|
|
@@ -44,6 +49,8 @@ export class DNSService extends Base {
|
|
|
44
49
|
}
|
|
45
50
|
super(owner, data);
|
|
46
51
|
this.read(data, DNSServiceTypeDefinition);
|
|
52
|
+
|
|
53
|
+
owner.addObject(this);
|
|
47
54
|
}
|
|
48
55
|
|
|
49
56
|
get soaUpdates() {
|
|
@@ -95,4 +102,216 @@ export class DNSService extends Base {
|
|
|
95
102
|
LLMNR: "no"
|
|
96
103
|
};
|
|
97
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
|
+
);
|
|
98
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/cluster.d.mts
CHANGED
|
@@ -97,6 +97,11 @@ export class Cluster extends Owner {
|
|
|
97
97
|
collection: boolean;
|
|
98
98
|
writeable: boolean;
|
|
99
99
|
};
|
|
100
|
+
notify: {
|
|
101
|
+
type: string;
|
|
102
|
+
collection: boolean;
|
|
103
|
+
writeable: boolean;
|
|
104
|
+
};
|
|
100
105
|
recordTTL: {
|
|
101
106
|
type: string;
|
|
102
107
|
collection: boolean;
|
|
@@ -266,6 +271,11 @@ export class Cluster extends Owner {
|
|
|
266
271
|
collection: boolean;
|
|
267
272
|
writeable: boolean;
|
|
268
273
|
};
|
|
274
|
+
notify: {
|
|
275
|
+
type: string;
|
|
276
|
+
collection: boolean;
|
|
277
|
+
writeable: boolean;
|
|
278
|
+
};
|
|
269
279
|
recordTTL: {
|
|
270
280
|
type: string;
|
|
271
281
|
collection: boolean;
|
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;
|
|
@@ -14,6 +16,11 @@ export class DNSService extends Base {
|
|
|
14
16
|
collection: boolean;
|
|
15
17
|
writeable: boolean;
|
|
16
18
|
};
|
|
19
|
+
notify: {
|
|
20
|
+
type: string;
|
|
21
|
+
collection: boolean;
|
|
22
|
+
writeable: boolean;
|
|
23
|
+
};
|
|
17
24
|
recordTTL: {
|
|
18
25
|
type: string;
|
|
19
26
|
collection: boolean;
|
|
@@ -55,6 +62,7 @@ export class DNSService extends Base {
|
|
|
55
62
|
recordTTL: string;
|
|
56
63
|
hasSVRRecords: boolean;
|
|
57
64
|
hasCatalog: boolean;
|
|
65
|
+
notify: boolean;
|
|
58
66
|
refresh: number;
|
|
59
67
|
retry: number;
|
|
60
68
|
expire: number;
|
package/types/location.d.mts
CHANGED
|
@@ -97,6 +97,11 @@ export class Location extends Owner {
|
|
|
97
97
|
collection: boolean;
|
|
98
98
|
writeable: boolean;
|
|
99
99
|
};
|
|
100
|
+
notify: {
|
|
101
|
+
type: string;
|
|
102
|
+
collection: boolean;
|
|
103
|
+
writeable: boolean;
|
|
104
|
+
};
|
|
100
105
|
recordTTL: {
|
|
101
106
|
type: string;
|
|
102
107
|
collection: boolean;
|
|
@@ -266,6 +271,11 @@ export class Location extends Owner {
|
|
|
266
271
|
collection: boolean;
|
|
267
272
|
writeable: boolean;
|
|
268
273
|
};
|
|
274
|
+
notify: {
|
|
275
|
+
type: string;
|
|
276
|
+
collection: boolean;
|
|
277
|
+
writeable: boolean;
|
|
278
|
+
};
|
|
269
279
|
recordTTL: {
|
|
270
280
|
type: string;
|
|
271
281
|
collection: boolean;
|
package/types/network.d.mts
CHANGED
package/types/owner.d.mts
CHANGED
package/types/root.d.mts
CHANGED
|
@@ -101,6 +101,11 @@ export class Root extends Location {
|
|
|
101
101
|
collection: boolean;
|
|
102
102
|
writeable: boolean;
|
|
103
103
|
};
|
|
104
|
+
notify: {
|
|
105
|
+
type: string;
|
|
106
|
+
collection: boolean;
|
|
107
|
+
writeable: boolean;
|
|
108
|
+
};
|
|
104
109
|
recordTTL: {
|
|
105
110
|
type: string;
|
|
106
111
|
collection: boolean;
|
|
@@ -270,6 +275,11 @@ export class Root extends Location {
|
|
|
270
275
|
collection: boolean;
|
|
271
276
|
writeable: boolean;
|
|
272
277
|
};
|
|
278
|
+
notify: {
|
|
279
|
+
type: string;
|
|
280
|
+
collection: boolean;
|
|
281
|
+
writeable: boolean;
|
|
282
|
+
};
|
|
273
283
|
recordTTL: {
|
|
274
284
|
type: string;
|
|
275
285
|
collection: boolean;
|
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 yes;`);
|
|
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
|
-
}
|