pmcf 4.19.1 → 4.20.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.
Files changed (82) hide show
  1. package/README.md +93 -72
  2. package/bin/pmcf-diagram +1 -1
  3. package/package.json +19 -19
  4. package/src/base.mjs +24 -237
  5. package/src/cli.mjs +4 -4
  6. package/src/cluster.mjs +15 -13
  7. package/src/extra-source-service.mjs +7 -10
  8. package/src/host.mjs +30 -44
  9. package/src/initialization-context.mjs +324 -0
  10. package/src/location.mjs +4 -14
  11. package/src/module.mjs +1 -0
  12. package/src/network-interfaces/ethernet.mjs +3 -5
  13. package/src/network-interfaces/loopback.mjs +2 -4
  14. package/src/network-interfaces/network-interface.mjs +3 -5
  15. package/src/network-interfaces/skeleton.mjs +9 -3
  16. package/src/network-interfaces/tun.mjs +2 -4
  17. package/src/network-interfaces/wireguard.mjs +3 -5
  18. package/src/network-interfaces/wlan.mjs +4 -7
  19. package/src/network.mjs +10 -8
  20. package/src/owner.mjs +8 -32
  21. package/src/root.mjs +4 -73
  22. package/src/service-owner.mjs +19 -18
  23. package/src/service.mjs +13 -18
  24. package/src/services/alpm.mjs +4 -9
  25. package/src/services/bind.mjs +424 -388
  26. package/src/services/chrony.mjs +3 -5
  27. package/src/services/headscale.mjs +2 -4
  28. package/src/services/influxdb.mjs +2 -4
  29. package/src/services/kea.mjs +6 -10
  30. package/src/services/mosquitto.mjs +2 -4
  31. package/src/services/openldap.mjs +2 -4
  32. package/src/services/postfix.mjs +2 -4
  33. package/src/services/systemd-journal-remote.mjs +2 -4
  34. package/src/services/systemd-journal-upload.mjs +9 -10
  35. package/src/services/systemd-journald.mjs +2 -4
  36. package/src/services/systemd-resolved.mjs +42 -42
  37. package/src/services/systemd-timesyncd.mjs +9 -13
  38. package/src/services/tailscale.mjs +5 -2
  39. package/src/subnet.mjs +9 -5
  40. package/types/base.d.mts +0 -179
  41. package/types/cli.d.mts +0 -10
  42. package/types/cluster.d.mts +0 -507
  43. package/types/dns-utils.d.mts +0 -14
  44. package/types/endpoint.d.mts +0 -79
  45. package/types/extra-source-service.d.mts +0 -1033
  46. package/types/hooks.d.mts +0 -2
  47. package/types/host-utils.d.mts +0 -1
  48. package/types/host.d.mts +0 -285
  49. package/types/location.d.mts +0 -379
  50. package/types/module.d.mts +0 -36
  51. package/types/network-address.d.mts +0 -41
  52. package/types/network-interfaces/ethernet.d.mts +0 -1189
  53. package/types/network-interfaces/loopback.d.mts +0 -1140
  54. package/types/network-interfaces/network-interface.d.mts +0 -1158
  55. package/types/network-interfaces/skeleton.d.mts +0 -30
  56. package/types/network-interfaces/tun.d.mts +0 -1131
  57. package/types/network-interfaces/wireguard.d.mts +0 -1131
  58. package/types/network-interfaces/wlan.d.mts +0 -1734
  59. package/types/network-support.d.mts +0 -193
  60. package/types/network.d.mts +0 -702
  61. package/types/owner.d.mts +0 -235
  62. package/types/root.d.mts +0 -10
  63. package/types/service-owner.d.mts +0 -14
  64. package/types/service-types.d.mts +0 -246
  65. package/types/service.d.mts +0 -689
  66. package/types/services/alpm.d.mts +0 -805
  67. package/types/services/bind.d.mts +0 -1574
  68. package/types/services/chrony.d.mts +0 -1310
  69. package/types/services/headscale.d.mts +0 -801
  70. package/types/services/influxdb.d.mts +0 -812
  71. package/types/services/kea.d.mts +0 -945
  72. package/types/services/mosquitto.d.mts +0 -876
  73. package/types/services/openldap.d.mts +0 -793
  74. package/types/services/postfix.d.mts +0 -784
  75. package/types/services/systemd-journal-remote.d.mts +0 -1030
  76. package/types/services/systemd-journal-upload.d.mts +0 -932
  77. package/types/services/systemd-journald.d.mts +0 -1317
  78. package/types/services/systemd-resolved.d.mts +0 -1644
  79. package/types/services/systemd-timesyncd.d.mts +0 -1459
  80. package/types/services/tailscale.d.mts +0 -781
  81. package/types/subnet.d.mts +0 -57
  82. package/types/utils.d.mts +0 -37
@@ -5,15 +5,15 @@ import { isLinkLocal, reverseArpa } from "ip-utilties";
5
5
  import {
6
6
  addType,
7
7
  default_attribute_writable,
8
- string_attribute_writable,
9
- string_collection_attribute_writable,
8
+ duration_attribute_writable,
10
9
  string_set_attribute_writable,
11
10
  boolean_attribute_writable_true,
12
11
  boolean_attribute_writable_false,
13
- number_attribute_writable,
12
+ integer_attribute_writable,
14
13
  name_attribute_writable
15
14
  } from "pacc";
16
15
  import {
16
+ Base,
17
17
  ExtraSourceService,
18
18
  serviceEndpoints,
19
19
  addresses,
@@ -31,346 +31,142 @@ import { ServiceTypeDefinition } from "../service.mjs";
31
31
  import { ExtraSourceServiceTypeDefinition } from "../extra-source-service.mjs";
32
32
  import { addHook } from "../hooks.mjs";
33
33
 
34
- const BindServiceViewTypeDefinition = {
35
- name: "bind-view",
36
- key: "name",
37
- attributes: {
38
- name: { ...name_attribute_writable },
39
- access: {
40
- type: networkAddressType,
41
- collection: true,
42
- writable: true
43
- }
44
- }
45
- };
34
+ const bindNetworkAddressTypes = networkAddressType + "|bind_group";
46
35
 
47
- const BindServiceTypeDefinition = {
48
- name: "bind",
49
- extends: ExtraSourceServiceTypeDefinition,
50
- specializationOf: ServiceTypeDefinition,
51
- owners: ServiceTypeDefinition.owners,
36
+ const BindGroupTypeDefinition = {
37
+ name: "bind_group",
38
+ priority: 1,
52
39
  key: "name",
53
40
  attributes: {
54
- /*views: {
55
- type: "object", //BindServiceViewTypeDefinition,
41
+ name: name_attribute_writable,
42
+ access: {
43
+ type: bindNetworkAddressTypes,
56
44
  collection: true,
57
45
  writable: true
58
- },*/
59
- zones: {
46
+ },
47
+ excludeInterfaceKinds: string_set_attribute_writable,
48
+ exclude: {
49
+ ...default_attribute_writable,
50
+ type: networkAddressType,
51
+ collection: true
52
+ },
53
+ entries: {
60
54
  type: networkAddressType + "|location|owner",
61
55
  collection: true,
62
56
  writable: true
63
57
  },
64
- trusted: {
65
- type: networkAddressType,
58
+ sharedWith: { ...default_attribute_writable, type: "bind_group" },
59
+ allowedUpdates: {
60
+ type: bindNetworkAddressTypes,
66
61
  collection: true,
67
62
  writable: true
68
63
  },
69
- protected: {
70
- ...default_attribute_writable,
71
- type: networkAddressType,
72
- collection: true
73
- },
74
- internal: {
75
- ...default_attribute_writable,
76
- type: networkAddressType,
77
- collection: true
78
- },
79
- hasSVRRecords: boolean_attribute_writable_false,
64
+ notify: boolean_attribute_writable_false,
80
65
  hasCatalog: boolean_attribute_writable_true,
66
+ hasReverse: boolean_attribute_writable_false,
67
+ hasSVRRecords: boolean_attribute_writable_false,
81
68
  hasLinkLocalAdresses: boolean_attribute_writable_false,
82
69
  hasLocationRecord: boolean_attribute_writable_true,
83
- excludeInterfaceKinds: string_set_attribute_writable,
84
- exclude: {
85
- ...default_attribute_writable,
86
- type: networkAddressType,
87
- collection: true
70
+
71
+ recordTTL: { ...duration_attribute_writable, default: "1W" },
72
+ serial: {
73
+ ...integer_attribute_writable,
74
+ default: Math.ceil(Date.now() / 1000)
88
75
  },
89
- notify: boolean_attribute_writable_false,
90
- recordTTL: { ...string_attribute_writable, default: "1W" },
91
- serial: number_attribute_writable,
92
- refresh: { ...string_attribute_writable, default: 36000 },
93
- retry: { ...string_attribute_writable, default: 72000 },
94
- expire: { ...string_attribute_writable, default: 600000 },
95
- minimum: { ...string_attribute_writable, default: 60000 },
96
- allowedUpdates: string_collection_attribute_writable,
97
- primaries: {
98
- ...default_attribute_writable,
99
- type: networkAddressType,
100
- collection: true
101
- }
102
- },
103
- service: {
104
- systemdService: "bind.service",
105
- extends: ["dns"],
106
- services: {
107
- "bind-statistics": {
108
- endpoints: [
109
- {
110
- family: "IPv4",
111
- port: 19521,
112
- protocol: "tcp",
113
- pathname: "/",
114
- tls: false,
115
- kind: "loopback"
116
- },
117
- {
118
- family: "IPv6",
119
- port: 19521,
120
- protocol: "tcp",
121
- pathname: "/",
122
- tls: false,
123
- kind: "loopback"
124
- }
125
- ]
126
- },
127
- "bind-rdnc": {
128
- endpoints: [
129
- {
130
- family: "IPv4",
131
- port: 953,
132
- protocol: "tcp",
133
- tls: false,
134
- kind: "loopback"
135
- }
136
- ]
137
- }
138
- }
76
+ refresh: { ...duration_attribute_writable, default: 36000 },
77
+ retry: { ...duration_attribute_writable, default: 72000 },
78
+ expire: { ...duration_attribute_writable, default: 600000 },
79
+ minimum: { ...duration_attribute_writable, default: 60000 }
139
80
  }
140
81
  };
141
82
 
142
- function addressesStatement(prefix, objects, generateEmpty = false) {
143
- const body = asArray(objects).map(name => ` ${name};`);
144
-
145
- if (body.length || generateEmpty) {
146
- return [`${prefix} {`, body, "};"];
83
+ class BindGroup extends Base {
84
+ static typeDefinition = BindGroupTypeDefinition;
85
+ static {
86
+ addType(this);
147
87
  }
148
88
 
149
- return [];
150
- }
151
-
152
- export class BindService extends ExtraSourceService {
89
+ access = [];
153
90
  allowedUpdates = [];
154
- recordTTL = "1W";
155
- hasSVRRecords = true;
91
+ entries = [];
92
+ exclude = new Set();
93
+ excludeInterfaceKinds = new Set();
94
+ notify = true;
156
95
  hasCatalog = true;
96
+ hasSVRRecords = true;
157
97
  hasLinkLocalAdresses =
158
- BindServiceTypeDefinition.attributes.hasLinkLocalAdresses.default;
159
- hasLocationRecord = true;
160
- notify = true;
161
- _zones = [];
162
- _trusted = [];
163
- _exclude = new Set([]);
164
-
165
- excludeInterfaceKinds = new Set();
166
-
167
- serial = Math.ceil(Date.now() / 1000);
168
- refresh = 36000;
169
- retry = 72000;
170
- expire = 600000;
171
- minimum = 60000;
172
- static {
173
- addType(this);
174
- addServiceType(this.typeDefinition.service, this.typeDefinition.name);
175
- }
176
-
177
- static get typeDefinition() {
178
- return BindServiceTypeDefinition;
179
- }
180
-
181
- constructor(owner, data) {
182
- super(owner, data);
183
-
184
- this.views = {};
185
-
186
- for (const name of ["internal", "protected"]) {
187
- this.views[name] = {
188
- name,
189
- access: []
190
- };
191
- }
192
-
193
- this.views.protected.inView = this.views.internal;
194
- this.views.protected.access = ["!internal"];
195
- }
98
+ BindGroupTypeDefinition.attributes.hasLinkLocalAdresses.default;
196
99
 
197
- get type() {
198
- return BindServiceTypeDefinition.name;
199
- }
100
+ recordTTL = "1W";
200
101
 
201
- get serverType()
202
- {
203
- return this.primaries ? "secondary" : "primary";
102
+ get service() {
103
+ return this.owner;
204
104
  }
205
105
 
206
106
  get soaUpdates() {
207
107
  return [this.serial, this.refresh, this.retry, this.expire, this.minimum];
208
108
  }
209
109
 
210
- set zones(value) {
211
- this._zones.push(value);
212
- }
213
-
214
- get zones() {
215
- return this._zones;
216
- }
217
-
218
- set protected(value) {
219
- this.views.protected.access.push(value);
220
- }
221
-
222
- get protected() {
223
- return this.views.protected.access;
224
- }
225
-
226
- set internal(value) {
227
- this.views.internal.access.push(value);
228
- }
110
+ get defaultRecords() {
111
+ /*
112
+ const ss = this.location.services.filter(s=>s.types.has('dns') && s.priority>=300);
113
+ const ss = [
114
+ ...this.location.expression("services[types['dns'] && priority>=300]")
115
+ ].sort(sortAscendingByPriority)
116
+ */
117
+ const nameService = this.owner; //ss[0];
118
+
119
+ console.log(
120
+ "nameService",
121
+ nameService.fullName,
122
+ nameService.domainName,
123
+ nameService.address()
124
+ );
229
125
 
230
- get internal() {
231
- return this.views.internal.access;
126
+ return [
127
+ DNSRecord(
128
+ "@",
129
+ "SOA",
130
+ dnsFullName(nameService.domainName),
131
+ dnsFullName(this.administratorEmail.replace(/@/, ".")),
132
+ `(${this.soaUpdates.join(" ")})`
133
+ ),
134
+ DNSRecord("@", "NS", dnsFullName(nameService.address()))
135
+ ];
232
136
  }
233
137
 
234
- set trusted(value) {
235
- this._trusted.push(value);
236
- }
138
+ async packageContent(outputControl) {
139
+ let hasContent = false;
237
140
 
238
- get trusted() {
239
- return this._trusted;
240
- }
141
+ if (this.access.length) {
142
+ hasContent ||= await this.generateACLs(outputControl);
143
+ }
241
144
 
242
- set exclude(value) {
243
- this._exclude.add(value);
244
- }
145
+ if (this.entries.length) {
146
+ hasContent ||= await this.generateZoneDefs(outputControl, this.entries);
147
+ }
245
148
 
246
- get exclude() {
247
- return this._exclude;
149
+ return hasContent;
248
150
  }
249
151
 
250
- async *preparePackages(dir) {
251
- const sources = this.zones.length ? this.zones : [this.owner];
252
- const names = sources.map(a => a.fullName).join(" ");
253
- const name = this.owner.owner.name || this.owner.name;
254
-
255
- const configPackageDir = join(dir, "config") + "/";
256
- const packageData = {
257
- outputs: this.outputs,
258
- sources: [new FileContentProvider(configPackageDir)],
259
- properties: {
260
- name: `named-${name}`,
261
- description: `named definitions for ${names}`,
262
- access: "private"
263
- }
264
- };
265
-
266
- const forwarders = serviceEndpoints(this.source, {
267
- services: 'type="dns" && priority>=100 && priority<200',
268
- endpoints: endpoint => endpoint.family !== "dns",
269
- select: e => e.address,
270
- limit: 5
271
- });
272
-
273
- if (forwarders.length) {
274
- await writeLines(
275
- join(configPackageDir, "etc/named/options"),
276
- `forwarders.conf`,
277
- addressesStatement("forwarders", forwarders)
278
- );
279
- }
280
-
152
+ async generateACLs(outputControl) {
281
153
  const acls = addressesStatement(
282
- "acl trusted",
283
- addresses(this.trusted, { aggregate: true })
154
+ `acl ${this.name}`,
155
+ addresses(this.access, { aggregate: true })
284
156
  );
285
157
 
286
- for (const view of Object.values(this.views)) {
287
- acls.push(
288
- ...addressesStatement(
289
- `acl ${view.name}`,
290
- addresses(view.access, { aggregate: true }),
291
- true
292
- )
293
- );
294
- }
295
-
296
- if (this.internal?.length) {
158
+ if (acls.length) {
297
159
  await writeLines(
298
- join(configPackageDir, "etc/named"),
299
- `0-acl-${name}.conf`,
160
+ join(outputControl.dir, "etc/named"),
161
+ `0-acl-${this.name}.conf`,
300
162
  acls
301
163
  );
302
164
  }
303
- if (forwarders.length || this.internal?.length) {
304
- yield packageData;
305
- }
306
-
307
- const ownerAndGroup = { owner: "named", group: "named" };
308
- const filePermissions = [
309
- { ...ownerAndGroup, mode: 0o644 },
310
- { ...ownerAndGroup, mode: 0o755 }
311
- ];
312
-
313
- const zonesPackageDir = join(dir, "zones") + "/";
314
-
315
- packageData.sources = [
316
- new FileContentProvider(zonesPackageDir, ...filePermissions)
317
- ];
318
- packageData.properties = {
319
- name: `named-zones-${name}`,
320
- description: `zone definitions for ${names}`,
321
- dependencies: ["mf-named"],
322
- access: "private"
323
- };
324
-
325
- yield this.generateZoneDefs(newOutputControl(packageData, zonesPackageDir), sources);
326
-
327
- const location = "outfacing";
328
-
329
- const outfacingZonesPackageDir = join(dir, location) + "/";
330
-
331
- packageData.sources = [
332
- new FileContentProvider(outfacingZonesPackageDir, ...filePermissions)
333
- ];
334
- packageData.properties = {
335
- name: `named-zones-${name}-${location}`,
336
- description: `${location} zone definitions for ${names}`,
337
- access: "private"
338
- };
339
-
340
- yield* this.generateOutfacingDefs(newOutputControl(packageData, outfacingZonesPackageDir), sources);
341
- }
342
-
343
- async *generateOutfacingDefs(outputControl, sources) {
344
- for (const source of sources) {
345
- for (const host of source.hosts()) {
346
- this.outfacingZones(
347
- outputControl,
348
- host,
349
- this.views.internal,
350
- this.defaultRecords
351
- );
352
- }
353
- }
354
165
 
355
- if (outputControl.configs.length) {
356
- addHook(
357
- outputControl.packageData,
358
- "post_upgrade",
359
- `/usr/bin/named-hostname-update ${outputControl.configs
360
- .map(config => config.zones.map(zone => zone.id))
361
- .flat()
362
- .join(" ")}`
363
- );
364
-
365
- await this.writeZones(outputControl);
366
-
367
- yield outputControl.packageData;
368
- }
166
+ return acls.length > 0;
369
167
  }
370
168
 
371
169
  async generateZoneDefs(outputControl, sources) {
372
- const view = this.views.internal;
373
-
374
170
  for (const source of sources) {
375
171
  console.log(
376
172
  "ZONE",
@@ -383,9 +179,8 @@ export class BindService extends ExtraSourceService {
383
179
  const reverseZones = new Map();
384
180
 
385
181
  const config = {
386
- view,
387
182
  name: `${domain}.zone.conf`,
388
- type: this.serverType,
183
+ type: this.service.serverType,
389
184
  zones: []
390
185
  };
391
186
  outputControl.configs.push(config);
@@ -498,12 +293,6 @@ export class BindService extends ExtraSourceService {
498
293
  }
499
294
  }
500
295
  }
501
- outputControl.configs.push({
502
- view: this.views.protected,
503
- inView: this.views.protected.inView,
504
- name: config.name,
505
- zones: config.zones
506
- });
507
296
  }
508
297
  }
509
298
 
@@ -512,77 +301,31 @@ export class BindService extends ExtraSourceService {
512
301
  return outputControl.packageData;
513
302
  }
514
303
 
515
- outfacingZones(outputControl, host, view, records) {
516
- host.foreignDomainNames.map(domain => {
517
- const wildcard = domain.startsWith("*.");
518
- if (wildcard) {
519
- domain = domain.substring(2);
520
- }
304
+ assignCatalog(outputControl, zone, name) {
305
+ if (!this.hasCatalog) {
306
+ return;
307
+ }
521
308
 
522
- const zone = {
523
- id: domain,
524
- file: `${host.location.name}/outfacing/${domain}.zone`,
525
- records: new Set(records)
309
+ const directory = dirname(zone.file);
310
+
311
+ let catalogZone = outputControl.catalogs.get(directory);
312
+
313
+ if (!catalogZone) {
314
+ catalogZone = {
315
+ catalog: true,
316
+ id: `catalog.${name}`,
317
+ file: `${directory}/catalog.${name}.zone`,
318
+ records: new Set([
319
+ ...this.defaultRecords,
320
+ DNSRecord("version", "TXT", '"2"')
321
+ ])
526
322
  };
323
+ outputControl.catalogs.set(directory, catalogZone);
324
+
527
325
  const config = {
528
- view,
529
- name: `${domain}.zone.conf`,
530
- type: this.serverType,
531
- zones: [zone]
532
- };
533
- zone.config = config;
534
- outputControl.configs.push(config);
535
-
536
- if (this.hasLocationRecord) {
537
- zone.records.add(DNSRecord("location", "TXT", host.location.name));
538
- }
539
- for (const na of host.networkAddresses(
540
- na => na.networkInterface.kind !== "loopback"
541
- )) {
542
- zone.records.add(
543
- DNSRecord("@", dnsRecordTypeForAddressFamily(na.family), na.address)
544
- );
545
-
546
- if (wildcard) {
547
- zone.records.add(
548
- DNSRecord("*", dnsRecordTypeForAddressFamily(na.family), na.address)
549
- );
550
- }
551
- }
552
-
553
- this.assignCatalog(
554
- outputControl,
555
- zone,
556
- `outfacting.${host.location.name}`
557
- );
558
- });
559
- }
560
-
561
- assignCatalog(outputControl, zone, name) {
562
- if (!this.hasCatalog) {
563
- return;
564
- }
565
-
566
- const directory = dirname(zone.file);
567
-
568
- let catalogZone = outputControl.catalogs.get(directory);
569
-
570
- if (!catalogZone) {
571
- catalogZone = {
572
- catalog: true,
573
- id: `catalog.${name}`,
574
- file: `${directory}/catalog.${name}.zone`,
575
- records: new Set([
576
- ...this.defaultRecords,
577
- DNSRecord("version", "TXT", '"2"')
578
- ])
579
- };
580
- outputControl.catalogs.set(directory, catalogZone);
581
- const config = {
582
- view: zone.config.view,
583
- name: `catalog.${name}.zone.conf`,
584
- type: this.serverType,
585
- zones: [catalogZone]
326
+ name: `catalog.${name}.zone.conf`,
327
+ type: this.service.serverType,
328
+ zones: [catalogZone]
586
329
  };
587
330
  catalogZone.config = config;
588
331
  outputControl.configs.push(config);
@@ -597,25 +340,9 @@ export class BindService extends ExtraSourceService {
597
340
  return catalogZone;
598
341
  }
599
342
 
600
- get defaultRecords() {
601
- const nameService = this.findService('in("dns",types) && priority>=300');
602
-
603
- const SOARecord = DNSRecord(
604
- "@",
605
- "SOA",
606
- dnsFullName(nameService.domainName),
607
- dnsFullName(this.administratorEmail.replace(/@/, ".")),
608
- `(${[...this.soaUpdates].join(" ")})`
609
- );
610
-
611
- const NSRecord = DNSRecord("@", "NS", dnsFullName(nameService.address()));
612
-
613
- return [SOARecord, NSRecord];
614
- }
615
-
616
343
  async writeZones(outputControl) {
617
344
  for (const config of outputControl.configs) {
618
- console.log(`config: ${config.view.name}/${config.name}`);
345
+ console.log(`config: ${this.name}/${config.name}`);
619
346
 
620
347
  const content = [];
621
348
 
@@ -624,8 +351,8 @@ export class BindService extends ExtraSourceService {
624
351
 
625
352
  content.push(`zone \"${zone.id}\" {`);
626
353
 
627
- if (config.inView) {
628
- content.push(` in-view ${config.inView.name};`);
354
+ if (this.sharedWith) {
355
+ content.push(` in-view ${this.sharedWith.name};`);
629
356
  } else {
630
357
  content.push(` type ${config.type};`);
631
358
  content.push(` file \"${zone.file}\";`);
@@ -657,7 +384,7 @@ export class BindService extends ExtraSourceService {
657
384
  }
658
385
 
659
386
  await writeLines(
660
- join(outputControl.dir, `etc/named/${config.view.name}`),
387
+ join(outputControl.dir, `etc/named/${this.name}`),
661
388
  config.name,
662
389
  content
663
390
  );
@@ -665,6 +392,315 @@ export class BindService extends ExtraSourceService {
665
392
  }
666
393
  }
667
394
 
395
+ const BindServiceTypeDefinition = {
396
+ name: "bind",
397
+ extends: ExtraSourceServiceTypeDefinition,
398
+ specializationOf: ServiceTypeDefinition,
399
+ owners: ServiceTypeDefinition.owners,
400
+ key: "name",
401
+ attributes: {
402
+ groups: {
403
+ ...default_attribute_writable,
404
+ type: BindGroupTypeDefinition,
405
+ collection: true,
406
+ writable: true
407
+ },
408
+ primaries: {
409
+ ...default_attribute_writable,
410
+ type: networkAddressType,
411
+ collection: true
412
+ }
413
+ },
414
+ service: {
415
+ systemdService: "bind.service",
416
+ extends: ["dns"],
417
+ services: {
418
+ "bind-statistics": {
419
+ endpoints: [
420
+ {
421
+ family: "IPv4",
422
+ port: 19521,
423
+ protocol: "tcp",
424
+ pathname: "/",
425
+ tls: false,
426
+ kind: "loopback"
427
+ },
428
+ {
429
+ family: "IPv6",
430
+ port: 19521,
431
+ protocol: "tcp",
432
+ pathname: "/",
433
+ tls: false,
434
+ kind: "loopback"
435
+ }
436
+ ]
437
+ },
438
+ "bind-rdnc": {
439
+ endpoints: [
440
+ {
441
+ family: "IPv4",
442
+ port: 953,
443
+ protocol: "tcp",
444
+ tls: false,
445
+ kind: "loopback"
446
+ }
447
+ ]
448
+ }
449
+ }
450
+ }
451
+ };
452
+
453
+ function addressesStatement(prefix, objects, generateEmpty = false) {
454
+ const body = asArray(objects).map(name => ` ${name};`);
455
+
456
+ if (body.length || generateEmpty) {
457
+ return [`${prefix} {`, body, "};"];
458
+ }
459
+
460
+ return [];
461
+ }
462
+
463
+ export class BindService extends ExtraSourceService {
464
+ static typeDefinition = BindServiceTypeDefinition;
465
+
466
+ static {
467
+ addType(this);
468
+ addServiceType(this.typeDefinition.service, this.typeDefinition.name);
469
+ }
470
+
471
+ groups = {};
472
+
473
+ get type() {
474
+ return BindServiceTypeDefinition.name;
475
+ }
476
+
477
+ materializeExtends() {
478
+ super.materializeExtends();
479
+
480
+ //console.log("ME", this.fullName, this.extends);
481
+ for (const service of this.walkDirections(["extends"])) {
482
+ console.log(
483
+ "BindService materializeExtends",
484
+ this.fullName,
485
+ service.fullName
486
+ );
487
+
488
+ for (const group of Object.values(service.groups)) {
489
+ const present = this.groups[group.name];
490
+
491
+ if (present) {
492
+ //console.log("LINK", present.fullName, group.fullName);
493
+ present.extends.push(group);
494
+ } else {
495
+ this.groups[group.name] = group.forOwner(this);
496
+
497
+ console.log(
498
+ group.fullName,
499
+ this.groups[group.name].entries.map(e => e.fullName)
500
+ );
501
+ }
502
+
503
+ /*
504
+ console.log(
505
+ eg.fullName,
506
+ eg.entries.map(e => e.fullName)
507
+ );*/
508
+ //console.log("EXTENDS", this.fullName, service.fullName, eg.fullName, eg.owner.fullName);
509
+ }
510
+ }
511
+ }
512
+
513
+ _traverse(...args) {
514
+ if (super._traverse(...args)) {
515
+ for (const group of Object.values(this.groups)) {
516
+ group._traverse(...args);
517
+ }
518
+ return true;
519
+ }
520
+
521
+ return false;
522
+ }
523
+
524
+ typeNamed(type, name) {
525
+ if (type === BindGroupTypeDefinition.name) {
526
+ return this.groups[name];
527
+ }
528
+
529
+ return super.typeNamed(type, name);
530
+ }
531
+
532
+ get serverType() {
533
+ return this.primaries ? "secondary" : "primary";
534
+ }
535
+
536
+ async writeForwarders(outputControl) {
537
+ const forwarders = serviceEndpoints(this.source, {
538
+ services: 'services[types[dns]" && priority>=100 && priority<200]',
539
+ endpoints: endpoint => endpoint.family !== "dns",
540
+ select: e => e.address,
541
+ limit: 5
542
+ });
543
+
544
+ if (forwarders.length) {
545
+ await writeLines(
546
+ join(outputControl.dir, "etc/named/options"),
547
+ `forwarders.conf`,
548
+ addressesStatement("forwarders", forwarders)
549
+ );
550
+ }
551
+
552
+ return forwarders.length > 0;
553
+ }
554
+
555
+ async *preparePackages(dir) {
556
+ const basePackageDir = dir;
557
+ const packageData = this.packageData;
558
+ packageData.sources.push(new FileContentProvider(basePackageDir));
559
+
560
+ const outputControl = newOutputControl(packageData, basePackageDir);
561
+
562
+ let hasContent = false;
563
+
564
+ console.log("PAKAGE", Object.keys(this.groups));
565
+
566
+ for (const group of Object.values(this.groups)) {
567
+ hasContent ||= await group.packageContent(outputControl);
568
+ }
569
+
570
+ hasContent ||= await this.writeForwarders(outputControl);
571
+
572
+ if (hasContent) {
573
+ yield packageData;
574
+ }
575
+
576
+ /*
577
+ const sources = this.zones.length ? this.zones : [this.owner];
578
+ const names = sources.map(a => a.fullName).join(" ");
579
+ const name = this.owner.owner.name || this.owner.name;
580
+
581
+ Object.assign(packageData.properties, {
582
+ name: `named-${name}`,
583
+ description: `named definitions for ${names}`
584
+ });
585
+
586
+ const ownerAndGroup = { owner: "named", group: "named" };
587
+ const filePermissions = [
588
+ { ...ownerAndGroup, mode: 0o644 },
589
+ { ...ownerAndGroup, mode: 0o755 }
590
+ ];
591
+
592
+ const zonesPackageDir = join(dir, "zones") + "/";
593
+
594
+ packageData.sources = [
595
+ new FileContentProvider(zonesPackageDir, ...filePermissions)
596
+ ];
597
+ packageData.properties = {
598
+ name: `named-zones-${name}`,
599
+ description: `zone definitions for ${names}`,
600
+ dependencies: ["mf-named"],
601
+ access: "private"
602
+ };
603
+
604
+ yield this.generateZoneDefs(
605
+ newOutputControl(packageData, zonesPackageDir),
606
+ sources
607
+ );
608
+
609
+ const location = "outfacing";
610
+
611
+ const outfacingZonesPackageDir = join(dir, location) + "/";
612
+
613
+ packageData.sources = [
614
+ new FileContentProvider(outfacingZonesPackageDir, ...filePermissions)
615
+ ];
616
+ packageData.properties = {
617
+ name: `named-zones-${name}-${location}`,
618
+ description: `${location} zone definitions for ${names}`,
619
+ access: "private"
620
+ };
621
+
622
+ yield* this.generateOutfacingDefs(
623
+ newOutputControl(packageData, outfacingZonesPackageDir),
624
+ sources
625
+ );
626
+ */
627
+ }
628
+
629
+ async *generateOutfacingDefs(outputControl, sources) {
630
+ for (const source of sources) {
631
+ for (const host of source.hosts) {
632
+ this.outfacingZones(
633
+ outputControl,
634
+ host,
635
+ this.groups.internal,
636
+ this.defaultRecords
637
+ );
638
+ }
639
+ }
640
+
641
+ if (outputControl.configs.length) {
642
+ addHook(
643
+ outputControl.packageData,
644
+ "post_upgrade",
645
+ `/usr/bin/named-hostname-update ${outputControl.configs
646
+ .map(config => config.zones.map(zone => zone.id))
647
+ .flat()
648
+ .join(" ")}`
649
+ );
650
+
651
+ await this.writeZones(outputControl);
652
+
653
+ yield outputControl.packageData;
654
+ }
655
+ }
656
+
657
+ outfacingZones(outputControl, host, group, records) {
658
+ host.foreignDomainNames.map(domain => {
659
+ const wildcard = domain.startsWith("*.");
660
+ if (wildcard) {
661
+ domain = domain.substring(2);
662
+ }
663
+
664
+ const zone = {
665
+ id: domain,
666
+ file: `${host.location.name}/outfacing/${domain}.zone`,
667
+ records: new Set(records)
668
+ };
669
+ const config = {
670
+ group,
671
+ name: `${domain}.zone.conf`,
672
+ type: this.serverType,
673
+ zones: [zone]
674
+ };
675
+ zone.config = config;
676
+ outputControl.configs.push(config);
677
+
678
+ if (this.hasLocationRecord) {
679
+ zone.records.add(DNSRecord("location", "TXT", host.location.name));
680
+ }
681
+ for (const na of host.networkAddresses(
682
+ na => na.networkInterface.kind !== "loopback"
683
+ )) {
684
+ zone.records.add(
685
+ DNSRecord("@", dnsRecordTypeForAddressFamily(na.family), na.address)
686
+ );
687
+
688
+ if (wildcard) {
689
+ zone.records.add(
690
+ DNSRecord("*", dnsRecordTypeForAddressFamily(na.family), na.address)
691
+ );
692
+ }
693
+ }
694
+
695
+ this.assignCatalog(
696
+ outputControl,
697
+ zone,
698
+ `outfacting.${host.location.name}`
699
+ );
700
+ });
701
+ }
702
+ }
703
+
668
704
  function newOutputControl(packageData, dir) {
669
705
  return { configs: [], catalogs: new Map(), packageData, dir };
670
706
  }