pmcf 2.29.2 → 2.31.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/README.md CHANGED
@@ -14,21 +14,27 @@
14
14
 
15
15
  ## Poor mans configuration management
16
16
 
17
-
18
17
  # API
19
18
 
20
19
  <!-- Generated by documentation.js. Update this documentation by updating the source code. -->
21
20
 
22
21
  ### Table of Contents
23
22
 
24
- * [NetworkAddress](#networkaddress)
23
+ * [networkAddresses](#networkaddresses)
25
24
  * [Parameters](#parameters)
26
- * [Properties](#properties)
25
+ * [NetworkAddress](#networkaddress)
26
+ * [Parameters](#parameters-1)
27
27
  * [subnet](#subnet)
28
28
  * [networkInterface](#networkinterface)
29
29
  * [address](#address)
30
- * [networkAddresses](#networkaddresses)
31
- * [Parameters](#parameters-1)
30
+
31
+ ## networkAddresses
32
+
33
+ ### Parameters
34
+
35
+ * `filter` **[object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)** (optional, default `n=>true`)
36
+
37
+ Returns **Iterable<[NetworkAddress](#networkaddress)>**&#x20;
32
38
 
33
39
  ## NetworkAddress
34
40
 
@@ -38,14 +44,6 @@
38
44
  * `address` &#x20;
39
45
  * `subnet` &#x20;
40
46
 
41
- ### Properties
42
-
43
- * `networkInterface` **NetworkInterface**&#x20;
44
- * `address` **([string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String) | [Uint8Array](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array) | [Uint16Array](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Uint16Array))**&#x20;
45
- * `family` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)**&#x20;
46
- * `subnet` **Subnet**&#x20;
47
- * `domainNames` **[Set](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Set)<[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)>**&#x20;
48
-
49
47
  ### subnet
50
48
 
51
49
  Type: Subnet
@@ -58,14 +56,6 @@ Type: NetworkInterface
58
56
 
59
57
  Type: ([string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String) | [Uint8Array](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array) | [Uint16Array](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Uint16Array))
60
58
 
61
- ## networkAddresses
62
-
63
- ### Parameters
64
-
65
- * `filter` **[object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)** (optional, default `n=>true`)
66
-
67
- Returns **Iterable<[NetworkAddress](#networkaddress)>**&#x20;
68
-
69
59
  # install
70
60
 
71
61
  With [npm](http://npmjs.org) do:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pmcf",
3
- "version": "2.29.2",
3
+ "version": "2.31.0",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
package/src/base.mjs CHANGED
@@ -190,7 +190,7 @@ export class Base {
190
190
  assign(property, value);
191
191
  } else {
192
192
  const factory =
193
- property.type.factoryFor?.(value) || property.type.clazz;
193
+ property.type.factoryFor?.(this, value) || property.type.clazz;
194
194
 
195
195
  assign(
196
196
  property,
@@ -1,8 +1,5 @@
1
- import { writeFile, mkdir } from "node:fs/promises";
2
1
  import { join } from "node:path";
3
- import { writeLines, sectionLines } from "../src/utils.mjs";
4
- import { addHook } from "./hooks.mjs";
5
- import { cidrAddresses } from "./network-support.mjs";
2
+ import { writeLines } from "../src/utils.mjs";
6
3
 
7
4
  export async function generateMachineInfo(host, packageData) {
8
5
  const etcDir = join(packageData.dir, "etc");
@@ -22,115 +19,6 @@ export async function generateMachineInfo(host, packageData) {
22
19
  await writeLines(etcDir, "hostname", host.hostName);
23
20
  }
24
21
 
25
- export async function generateNetworkDefs(host, packageData) {
26
- const networkDir = join(packageData.dir, "etc/systemd/network");
27
-
28
- for (const ni of host.networkInterfaces.values()) {
29
- await ni.systemdDefinitions(packageData);
30
-
31
- switch (ni.kind) {
32
- case "loopback":
33
- continue;
34
- }
35
-
36
- if (ni.name !== "eth0" && ni.hwaddr) {
37
- await writeLines(networkDir, `${ni.name}.link`, [
38
- sectionLines("Match", { MACAddress: ni.hwaddr }),
39
- "",
40
- sectionLines("Link", { Name: ni.name })
41
- ]);
42
- }
43
-
44
- const networkSections = [sectionLines("Match", { Name: ni.name })];
45
-
46
- for (const Address of cidrAddresses(ni.networkAddresses())) {
47
- networkSections.push(
48
- "",
49
- sectionLines("Address", {
50
- Address
51
- })
52
- );
53
- }
54
-
55
- switch (ni.kind) {
56
- case "ethernet":
57
- case "wlan":
58
- const routeSectionExtra = ni.destination
59
- ? { Destination: ni.destination }
60
- : { Gateway: ni.gatewayAddress };
61
-
62
- const networkSectionExtra = ni.arpbridge
63
- ? {
64
- IPForward: "yes",
65
- IPv4ProxyARP: "yes"
66
- }
67
- : {};
68
-
69
- networkSections.push(
70
- "",
71
- sectionLines("Network", {
72
- ...networkSectionExtra,
73
- DHCP: "no",
74
- DHCPServer: "no",
75
- MulticastDNS: ni.network.multicastDNS ? "yes" : "no",
76
- LinkLocalAddressing: "ipv6",
77
- IPv6LinkLocalAddressGenerationMode: "stable-privacy"
78
- }),
79
- "",
80
- sectionLines("Route", {
81
- ...routeSectionExtra,
82
- Scope: ni.scope,
83
- Metric: ni.metric,
84
- InitialCongestionWindow: 20,
85
- InitialAdvertisedReceiveWindow: 20
86
- }),
87
- "",
88
- sectionLines("IPv6AcceptRA", {
89
- UseAutonomousPrefix: "true",
90
- UseOnLinkPrefix: "true",
91
- DHCPv6Client: "false",
92
- Token: "eui64"
93
- })
94
- );
95
-
96
- if (ni.arpbridge) {
97
- networkSections.push(
98
- "",
99
- sectionLines("Link", { Promiscuous: "yes" })
100
- );
101
- }
102
- }
103
-
104
- await writeLines(networkDir, `${ni.name}.network`, networkSections);
105
-
106
- switch (ni.kind) {
107
- case "wifi": {
108
- const d = join(packageData.dir, "etc/wpa_supplicant");
109
- await mkdir(d, { recursive: true });
110
- writeFile(
111
- join(d, `wpa_supplicant-${ni.name}.conf`),
112
- `country=${host.location.country}
113
- ctrl_interface=DIR=/run/wpa_supplicant GROUP=netdev
114
- update_config=1
115
- p2p_disabled=1
116
- network={
117
- ssid="${ni.ssid}"
118
- psk=${ni.psk}
119
- scan_ssid=1
120
- }`,
121
- "utf8"
122
- );
123
-
124
- addHook(
125
- packageData.properties.hooks,
126
- "post_install",
127
- `systemctl enable wpa_supplicant@${ni.name}.service`
128
- );
129
- }
130
- }
131
- }
132
- }
133
-
134
22
  export async function generateKnownHosts(hosts, dir) {
135
23
  const keys = [];
136
24
  for await (const host of hosts) {
package/src/host.mjs CHANGED
@@ -7,12 +7,8 @@ import { domainFromDominName, domainName } from "./utils.mjs";
7
7
  import { objectFilter } from "./filter.mjs";
8
8
  import { addType, types } from "./types.mjs";
9
9
  import { loadHooks } from "./hooks.mjs";
10
- import {
11
- generateNetworkDefs,
12
- generateMachineInfo,
13
- generateKnownHosts
14
- } from "./host-utils.mjs";
15
- import { NetworkInterfaceTypeDefinition } from "./network-interface.mjs";
10
+ import { generateMachineInfo, generateKnownHosts } from "./host-utils.mjs";
11
+ import { NetworkInterfaceTypeDefinition } from "./network-interfaces/network-interface.mjs";
16
12
 
17
13
  const HostTypeDefinition = {
18
14
  name: "host",
@@ -45,12 +41,17 @@ const HostTypeDefinition = {
45
41
  weight: { type: "number", collection: false, writeable: true },
46
42
  serial: { type: "string", collection: false, writeable: true },
47
43
  vendor: { type: "string", collection: false, writeable: true },
48
- chassis: { type: "string", collection: false, writeable: true },
44
+ chassis: {
45
+ type: "string",
46
+ collection: false,
47
+ writeable: true,
48
+ values: ["phone", "tablet", "router", "desktop", "server"]
49
+ },
49
50
  architecture: {
50
51
  type: "string",
51
52
  collection: false,
52
53
  writeable: true,
53
- values: ["x86", "aarch64", "armv7"]
54
+ values: ["x86", "x86_64", "aarch64", "armv7"]
54
55
  },
55
56
  replaces: { type: "string", collection: true, writeable: true },
56
57
  depends: { type: "string", collection: true, writeable: true },
@@ -446,6 +447,12 @@ export class Host extends Base {
446
447
  return addresses(this.networkAddresses());
447
448
  }
448
449
 
450
+ *subnets() {
451
+ for (const networkInterface of this.networkInterfaces.values()) {
452
+ yield* networkInterface.subnets;
453
+ }
454
+ }
455
+
449
456
  async publicKey(type = "ed25519") {
450
457
  return readFile(join(this.directory, `ssh_host_${type}_key.pub`), "utf8");
451
458
  }
@@ -483,7 +490,10 @@ export class Host extends Base {
483
490
  }
484
491
  };
485
492
 
486
- await generateNetworkDefs(this, packageData);
493
+ for (const ni of this.networkInterfaces.values()) {
494
+ await ni.systemdDefinitions(packageData);
495
+ }
496
+
487
497
  await generateMachineInfo(this, packageData);
488
498
  await generateKnownHosts(this.owner.hosts(), join(dir, "root", ".ssh"));
489
499
 
package/src/module.mjs CHANGED
@@ -8,7 +8,11 @@ export * from "./subnet.mjs";
8
8
  export * from "./network.mjs";
9
9
  export * from "./network-address.mjs";
10
10
  export * from "./network-support.mjs";
11
- export * from "./network-interface.mjs";
11
+ export * from "./network-interfaces/network-interface.mjs";
12
+ export * from "./network-interfaces/loopback.mjs";
13
+ export * from "./network-interfaces/ethernet.mjs";
14
+ export * from "./network-interfaces/wlan.mjs";
15
+ export * from "./network-interfaces/wireguard.mjs";
12
16
  export * from "./host.mjs";
13
17
  export * from "./service.mjs";
14
18
  export * from "./endpoint.mjs";
@@ -2,11 +2,7 @@ import { familyIP, formatCIDR } from "ip-utilties";
2
2
  import { Subnet } from "./subnet.mjs";
3
3
 
4
4
  /**
5
- * @property {NetworkInterface} networkInterface
6
- * @property {string|Uint8Array|Uint16Array} address
7
- * @property {string} family
8
- * @property {Subnet} subnet
9
- * @property {Set<string>} domainNames
5
+ *
10
6
  */
11
7
  export class NetworkAddress {
12
8
  /** @type {Subnet} */ subnet;
@@ -0,0 +1,37 @@
1
+ import { addType } from "../types.mjs";
2
+ import {
3
+ NetworkInterface,
4
+ NetworkInterfaceTypeDefinition
5
+ } from "./network-interface.mjs";
6
+
7
+ export const EthernetNetworkInterfaceTypeDefinition = {
8
+ name: "ethernet",
9
+ specializationOf: NetworkInterfaceTypeDefinition,
10
+ owners: NetworkInterfaceTypeDefinition.owners,
11
+ extends: NetworkInterfaceTypeDefinition,
12
+ priority: 0.1,
13
+ properties: {
14
+ arpbridge: { type: "network_interface", collection: true, writeable: true }
15
+ }
16
+ };
17
+
18
+ export class EthernetNetworkInterface extends NetworkInterface {
19
+ arpbridge;
20
+
21
+ static {
22
+ addType(this);
23
+ }
24
+
25
+ static get typeDefinition() {
26
+ return EthernetNetworkInterfaceTypeDefinition;
27
+ }
28
+
29
+ constructor(owner, data) {
30
+ super(owner, data);
31
+ this.read(data, EthernetNetworkInterfaceTypeDefinition);
32
+ }
33
+
34
+ get kind() {
35
+ return EthernetNetworkInterfaceTypeDefinition.name;
36
+ }
37
+ }
@@ -0,0 +1,44 @@
1
+ import { SUBNET_LOCALHOST_IPV4, SUBNET_LOCALHOST_IPV6 } from "pmcf";
2
+ import { SkeletonNetworkInterface } from "./skeleton.mjs";
3
+ import { NetworkInterfaceTypeDefinition } from "./network-interface.mjs";
4
+ import { addType } from "../types.mjs";
5
+
6
+ const LoopbackNetworkInterfaceTypeDefinition = {
7
+ name: "loopback",
8
+ specializationOf: NetworkInterfaceTypeDefinition,
9
+ owners: NetworkInterfaceTypeDefinition.owners,
10
+ extends: NetworkInterfaceTypeDefinition,
11
+ priority: 0.1,
12
+ properties: {}
13
+ };
14
+
15
+ const _localAddresses = new Map([
16
+ ["127.0.0.1", SUBNET_LOCALHOST_IPV4],
17
+ ["::1", SUBNET_LOCALHOST_IPV6]
18
+ ]);
19
+
20
+ export class LoopbackNetworkInterface extends SkeletonNetworkInterface {
21
+ static {
22
+ addType(this);
23
+ }
24
+
25
+ static get typeDefinition() {
26
+ return LoopbackNetworkInterfaceTypeDefinition;
27
+ }
28
+
29
+ get kind() {
30
+ return LoopbackNetworkInterfaceTypeDefinition.name;
31
+ }
32
+
33
+ get scope() {
34
+ return "host";
35
+ }
36
+
37
+ get hostName() {
38
+ return "localhost";
39
+ }
40
+
41
+ get ipAddresses() {
42
+ return _localAddresses;
43
+ }
44
+ }
@@ -0,0 +1,245 @@
1
+ import { join } from "node:path";
2
+ import { hasWellKnownSubnet, normalizeIP } from "ip-utilties";
3
+ import { Base } from "pmcf";
4
+ import {
5
+ networkProperties,
6
+ networkAddressProperties
7
+ } from "../network-support.mjs";
8
+ import { asArray, writeLines, sectionLines } from "../utils.mjs";
9
+ import { addType } from "../types.mjs";
10
+ import { cidrAddresses } from "../network-support.mjs";
11
+ import { SkeletonNetworkInterface } from "./skeleton.mjs";
12
+
13
+ export const NetworkInterfaceTypeDefinition = {
14
+ name: "network_interface",
15
+ priority: 0.4,
16
+ owners: ["host"],
17
+ extends: Base.typeDefinition,
18
+ specializations: {},
19
+ factoryFor(owner, value) {
20
+ const kind = value.kind;
21
+ const t = NetworkInterfaceTypeDefinition.specializations[kind];
22
+
23
+ if (!t) {
24
+ console.warn("FACTORY", owner.name, value, t?.name);
25
+ }
26
+ if (t) {
27
+ delete value.type;
28
+ delete value.kind;
29
+ return t.clazz;
30
+ }
31
+
32
+ return NetworkInterface;
33
+ },
34
+ properties: {
35
+ ...networkProperties,
36
+ ...networkAddressProperties,
37
+ hostName: { type: "string", collection: false, writeable: true },
38
+ ipAddresses: { type: "string", collection: true, writeable: true },
39
+
40
+ hwaddr: { type: "string", collection: false, writeable: true },
41
+ network: { type: "network", collection: false, writeable: true },
42
+ destination: { type: "string", collection: false, writeable: true }
43
+ }
44
+ };
45
+
46
+ export class NetworkInterface extends SkeletonNetworkInterface {
47
+ static {
48
+ addType(this);
49
+ }
50
+
51
+ static get typeDefinition() {
52
+ return NetworkInterfaceTypeDefinition;
53
+ }
54
+
55
+ _ipAddresses = new Map();
56
+ _scope;
57
+ _metric;
58
+ _kind;
59
+ _hostName;
60
+ _hwaddr;
61
+ _class;
62
+
63
+ constructor(owner, data) {
64
+ super(owner, data);
65
+ this.read(data, NetworkInterfaceTypeDefinition);
66
+ }
67
+
68
+ addSubnet(address) {
69
+ if (!this.network) {
70
+ if (!hasWellKnownSubnet(address)) {
71
+ this.error("Missing network", address);
72
+ }
73
+ } else {
74
+ return this.network.addSubnet(address);
75
+ }
76
+ }
77
+
78
+ get ipAddresses() {
79
+ return this._ipAddresses;
80
+ }
81
+
82
+ set ipAddresses(value) {
83
+ for (const address of asArray(value)) {
84
+ this._ipAddresses.set(normalizeIP(address), this.addSubnet(address));
85
+ }
86
+ }
87
+
88
+ subnetForAddress(address) {
89
+ return (
90
+ this.network?.subnetForAddress(address) ||
91
+ this.host.owner.subnetForAddress(address)
92
+ );
93
+ }
94
+
95
+ get gateway() {
96
+ return this.network?.gateway;
97
+ }
98
+
99
+ get gatewayAddress() {
100
+ for (const a of this.gateway.networkAddresses()) {
101
+ if (a.networkInterface.network === this.network) {
102
+ return a.address;
103
+ }
104
+ }
105
+ }
106
+
107
+ get hostName() {
108
+ return this.extendedProperty("_hostName") ?? this.host.hostName;
109
+ }
110
+
111
+ set hostName(value) {
112
+ this._hostName = value;
113
+ }
114
+
115
+ get domainNames() {
116
+ return this.hostName
117
+ ? new Set([[this.hostName, this.host.domain].join(".")])
118
+ : this.host.directDomainNames;
119
+ }
120
+
121
+ set scope(value) {
122
+ this._scope = value;
123
+ }
124
+
125
+ get scope() {
126
+ return (
127
+ this.extendedProperty("_scope") ??
128
+ this.network?.scope ??
129
+ networkProperties.scope.default
130
+ );
131
+ }
132
+
133
+ set hwaddr(value) {
134
+ this._hwaddr = value;
135
+ }
136
+
137
+ get hwaddr() {
138
+ return this.extendedProperty("_hwaddr");
139
+ }
140
+
141
+ set metric(value) {
142
+ this._metric = value;
143
+ }
144
+
145
+ get metric() {
146
+ return (
147
+ this.extendedProperty("_metric") ??
148
+ this.network?.metric ??
149
+ networkProperties.metric.default
150
+ );
151
+ }
152
+
153
+ set MTU(value) {
154
+ this._MTU = value;
155
+ }
156
+
157
+ get MTU() {
158
+ return this.extendedProperty("_MTU") ?? networkProperties.MTU.default;
159
+ }
160
+
161
+ set class(value) {
162
+ this._class = value;
163
+ }
164
+
165
+ get class() {
166
+ return this.extendedProperty("_class") ?? this.network?.class;
167
+ }
168
+
169
+ set kind(value) {
170
+ this._kind = value;
171
+ }
172
+
173
+ get kind() {
174
+ return this.extendedProperty("_kind") ?? this.network?.kind;
175
+ }
176
+
177
+ async systemdDefinitions(packageData) {
178
+ await super.systemdDefinitions(packageData);
179
+
180
+ const networkDir = join(packageData.dir, "etc/systemd/network");
181
+
182
+ if (this.name !== "eth0" && this.hwaddr) {
183
+ await writeLines(networkDir, `${this.name}.link`, [
184
+ sectionLines("Match", { MACAddress: this.hwaddr }),
185
+ "",
186
+ sectionLines("Link", { Name: this.name })
187
+ ]);
188
+ }
189
+
190
+ const networkSections = [sectionLines("Match", { Name: this.name })];
191
+
192
+ for (const Address of cidrAddresses(this.networkAddresses())) {
193
+ networkSections.push(
194
+ "",
195
+ sectionLines("Address", {
196
+ Address
197
+ })
198
+ );
199
+ }
200
+
201
+ const routeSectionExtra = this.destination
202
+ ? { Destination: this.destination }
203
+ : { Gateway: this.gatewayAddress };
204
+
205
+ const networkSectionExtra = this.arpbridge
206
+ ? {
207
+ IPForward: "yes",
208
+ IPv4ProxyARP: "yes"
209
+ }
210
+ : {};
211
+
212
+ networkSections.push(
213
+ "",
214
+ sectionLines("Network", {
215
+ ...networkSectionExtra,
216
+ DHCP: "no",
217
+ DHCPServer: "no",
218
+ MulticastDNS: this.network.multicastDNS ? "yes" : "no",
219
+ LinkLocalAddressing: "ipv6",
220
+ IPv6LinkLocalAddressGenerationMode: "stable-privacy"
221
+ }),
222
+ "",
223
+ sectionLines("Route", {
224
+ ...routeSectionExtra,
225
+ Scope: this.scope,
226
+ Metric: this.metric,
227
+ InitialCongestionWindow: 20,
228
+ InitialAdvertisedReceiveWindow: 20
229
+ }),
230
+ "",
231
+ sectionLines("IPv6AcceptRA", {
232
+ UseAutonomousPrefix: "true",
233
+ UseOnLinkPrefix: "true",
234
+ DHCPv6Client: "false",
235
+ Token: "eui64"
236
+ })
237
+ );
238
+
239
+ if (this.arpbridge) {
240
+ networkSections.push("", sectionLines("Link", { Promiscuous: "yes" }));
241
+ }
242
+
243
+ await writeLines(networkDir, `${this.name}.network`, networkSections);
244
+ }
245
+ }