pmcf 1.52.2 → 1.52.4

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.
@@ -1,10 +1,14 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import { writeFile, mkdir, copyFile, glob, chmod } from "node:fs/promises";
4
3
  import { join } from "node:path";
5
4
  import { types } from "pmcf";
6
- import { writeLines, sectionLines } from "../src/utils.mjs";
7
5
  import { prepare } from "../src/cmd.mjs";
6
+ import {
7
+ generateNetworkDefs,
8
+ generateMachineInfo,
9
+ copySshKeys,
10
+ generateKnownHosts
11
+ } from "../src/host-utils.mjs";
8
12
 
9
13
  const { root, args, options } = await prepare();
10
14
 
@@ -25,157 +29,3 @@ console.log("depends", `location-${host.location.name}`, ...host.depends);
25
29
  console.log("replaces", `mf-${host.hostName}`, ...host.replaces);
26
30
  console.log("description", `host definitions for ${host.domainName}`);
27
31
  console.log("backup", "root/.ssh/known_hosts");
28
-
29
- async function generateMachineInfo(host, dir) {
30
- const etcDir = join(dir, "etc");
31
- await writeLines(
32
- etcDir,
33
- "machine-info",
34
- Object.entries({
35
- CHASSIS: host.chassis,
36
- DEPLOYMENT: host.deployment,
37
- LOCATION: host.location.name,
38
- HARDWARE_VENDOR: host.vendor,
39
- HARDWARE_MODEL: host.modelName
40
- }).map(([k, v]) => `${k}=${v}`)
41
- );
42
-
43
- await writeLines(etcDir, "machine-id", host["machine-id"]);
44
- await writeLines(etcDir, "hostname", host.hostName);
45
- }
46
-
47
- async function generateNetworkDefs(host, dir) {
48
- const networkDir = join(dir, "etc/systemd/network");
49
-
50
- for (const ni of host.networkInterfaces.values()) {
51
- if (ni.name !== "eth0" && ni.hwaddr) {
52
- await writeLines(networkDir, `${ni.name}.link`, [
53
- sectionLines("Match", { MACAddress: ni.hwaddr }),
54
- "",
55
- sectionLines("Link", { Name: ni.name })
56
- ]);
57
- }
58
-
59
- const networkSections = [sectionLines("Match", { Name: ni.name })];
60
-
61
- for (const Address of ni.cidrAddresses) {
62
- networkSections.push(
63
- "",
64
- sectionLines("Address", {
65
- Address
66
- })
67
- );
68
- }
69
-
70
- switch (ni.kind) {
71
- case "ethernet":
72
- case "wifi":
73
- const routeSectionExtra = ni.destination
74
- ? { Destination: ni.destination }
75
- : { Gateway: ni.gatewayAddress };
76
-
77
- const networkSectionExtra = ni.arpbridge
78
- ? {
79
- IPForward: "yes",
80
- IPv4ProxyARP: "yes"
81
- }
82
- : {};
83
-
84
- networkSections.push(
85
- "",
86
- sectionLines("Network", {
87
- ...networkSectionExtra,
88
- DHCP: "no",
89
- DHCPServer: "no",
90
- MulticastDNS: "yes",
91
- LinkLocalAddressing: "ipv6",
92
- IPv6LinkLocalAddressGenerationMode: "stable-privacy"
93
- }),
94
- "",
95
- sectionLines("Route", {
96
- ...routeSectionExtra,
97
- Scope: ni.scope,
98
- Metric: ni.metric,
99
- InitialCongestionWindow: 20,
100
- InitialAdvertisedReceiveWindow: 20
101
- }),
102
- "",
103
- sectionLines("IPv6AcceptRA", {
104
- UseAutonomousPrefix: "true",
105
- UseOnLinkPrefix: "true",
106
- DHCPv6Client: "false",
107
- Token: "eui64"
108
- })
109
- );
110
-
111
- if (ni.arpbridge) {
112
- networkSections.push(
113
- "",
114
- sectionLines("Link", { Promiscuous: "yes" })
115
- );
116
- }
117
- }
118
-
119
- await writeLines(networkDir, `${ni.name}.network`, networkSections);
120
-
121
- switch (ni?.kind) {
122
- case "wireguard":
123
- {
124
- }
125
- break;
126
- case "wifi": {
127
- const d = join(dir, "etc/wpa_supplicant");
128
- await mkdir(d, { recursive: true });
129
- writeFile(
130
- join(d, `wpa_supplicant-${ni.name}.conf`),
131
- `country=${host.location.country}
132
- ctrl_interface=DIR=/run/wpa_supplicant GROUP=netdev
133
- update_config=1
134
- p2p_disabled=1
135
- network={
136
- ssid="${ni.ssid}"
137
- psk=${ni.psk}
138
- scan_ssid=1
139
- }`,
140
- "utf8"
141
- );
142
-
143
- host.postinstall.push(
144
- `systemctl enable wpa_supplicant@${ni.name}.service`
145
- );
146
- }
147
- }
148
- }
149
- }
150
-
151
- async function copySshKeys(host, dir) {
152
- const sshDir = join(dir, "etc", "ssh");
153
-
154
- await mkdir(sshDir, { recursive: true });
155
-
156
- for await (const file of glob("ssh_host_*", { cwd: host.directory })) {
157
- const destinationFileName = join(sshDir, file);
158
- await copyFile(join(host.directory, file), destinationFileName);
159
- await chmod(
160
- destinationFileName,
161
- destinationFileName.endsWith(".pub") ? 0o0644 : 0o0600
162
- );
163
- }
164
- }
165
-
166
- async function generateKnownHosts(hosts, dir) {
167
- const keys = [];
168
- for await (const host of hosts) {
169
- try {
170
- const [alg, key, desc] = (await host.publicKey("ed25519")).split(/\s+/);
171
-
172
- keys.push(`${host.domainName} ${alg} ${key}`);
173
-
174
- for await (const addr of host.networkAddresses()) {
175
- keys.push(`${addr.address} ${alg} ${key}`);
176
- }
177
- } catch {}
178
- }
179
-
180
- await writeLines(dir, "known_hosts", keys);
181
- }
@@ -100,7 +100,7 @@ async function generateNamedDefs(owner, targetDir) {
100
100
 
101
101
  if (!addresses.has(address)) {
102
102
  addresses.add(address);
103
-
103
+
104
104
  zone.records.add(
105
105
  createRecord(
106
106
  fullName(host.domainName),
@@ -108,6 +108,28 @@ async function generateNamedDefs(owner, targetDir) {
108
108
  normalizeIPAddress(address)
109
109
  )
110
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
+ }
111
133
  }
112
134
 
113
135
  if (!hosts.has(host)) {
@@ -132,28 +154,6 @@ async function generateNamedDefs(owner, targetDir) {
132
154
  );
133
155
  }
134
156
  }
135
-
136
- if (subnet) {
137
- let reverseZone = reverseZones.get(subnet.address);
138
-
139
- if (!reverseZone) {
140
- const reverseArpa = reverseArpaAddress(subnet.prefix);
141
- reverseZone = {
142
- id: reverseArpa,
143
- file: `${reverseArpa}.zone`,
144
- records: new Set([SOARecord, NSRecord])
145
- };
146
- zones.push(reverseZone);
147
- reverseZones.set(subnet.address, reverseZone);
148
- }
149
- reverseZone.records.add(
150
- createRecord(
151
- fullName(reverseArpaAddress(address)),
152
- "PTR",
153
- fullName(host.domainName)
154
- )
155
- );
156
- }
157
157
  }
158
158
  }
159
159
 
@@ -205,18 +205,18 @@ async function generateNamedDefs(owner, targetDir) {
205
205
  export function reverseAddress(address) {
206
206
  if (isIPv6Address(address)) {
207
207
  return normalizeIPAddress(address)
208
- .replaceAll(":", "")
209
- .split("")
210
- .reverse()
211
- .join(".");
208
+ .replaceAll(":", "")
209
+ .split("")
210
+ .reverse()
211
+ .join(".");
212
212
  }
213
213
 
214
- return address.split(".").reverse().join(".");
214
+ return address.split(".").reverse().join(".");
215
215
  }
216
216
 
217
217
  export function reverseArpaAddress(address) {
218
218
  return (
219
219
  reverseAddress(address) +
220
- (isIPv6Address(address) ? ".ip6.arpa" : ".in-addr.arpa")
220
+ (isIPv6Address(address) ? ".ip6.arpa" : ".in-addr.arpa")
221
221
  );
222
222
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pmcf",
3
- "version": "1.52.2",
3
+ "version": "1.52.4",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -40,6 +40,7 @@
40
40
  "lint:typescript": "tsc --allowJs --checkJs --noEmit --resolveJsonModule --target es2024 --lib esnext -m esnext --module nodenext --moduleResolution nodenext ./src**/*.mjs"
41
41
  },
42
42
  "dependencies": {
43
+ "npm-pkgbuild": "^16.0.1",
43
44
  "pacc": "^3.3.0"
44
45
  },
45
46
  "devDependencies": {
@@ -48,7 +49,7 @@
48
49
  "c8": "^10.1.3",
49
50
  "documentation": "^14.0.3",
50
51
  "semantic-release": "^24.2.3",
51
- "typescript": "^5.7.3"
52
+ "typescript": "^5.8.2"
52
53
  },
53
54
  "engines": {
54
55
  "node": ">=22.14.0"
@@ -0,0 +1,157 @@
1
+ import { writeFile, mkdir, copyFile, glob, chmod } from "node:fs/promises";
2
+ import { join } from "node:path";
3
+ import { writeLines, sectionLines } from "../src/utils.mjs";
4
+
5
+ export async function generateMachineInfo(host, dir) {
6
+ const etcDir = join(dir, "etc");
7
+ await writeLines(
8
+ etcDir,
9
+ "machine-info",
10
+ Object.entries({
11
+ CHASSIS: host.chassis,
12
+ DEPLOYMENT: host.deployment,
13
+ LOCATION: host.location.name,
14
+ HARDWARE_VENDOR: host.vendor,
15
+ HARDWARE_MODEL: host.modelName
16
+ }).map(([k, v]) => `${k}=${v}`)
17
+ );
18
+
19
+ await writeLines(etcDir, "machine-id", host["machine-id"]);
20
+ await writeLines(etcDir, "hostname", host.hostName);
21
+ }
22
+
23
+ export async function generateNetworkDefs(host, dir) {
24
+ const networkDir = join(dir, "etc/systemd/network");
25
+
26
+ for (const ni of host.networkInterfaces.values()) {
27
+ if (ni.name !== "eth0" && ni.hwaddr) {
28
+ await writeLines(networkDir, `${ni.name}.link`, [
29
+ sectionLines("Match", { MACAddress: ni.hwaddr }),
30
+ "",
31
+ sectionLines("Link", { Name: ni.name })
32
+ ]);
33
+ }
34
+
35
+ const networkSections = [sectionLines("Match", { Name: ni.name })];
36
+
37
+ for (const Address of ni.cidrAddresses) {
38
+ networkSections.push(
39
+ "",
40
+ sectionLines("Address", {
41
+ Address
42
+ })
43
+ );
44
+ }
45
+
46
+ switch (ni.kind) {
47
+ case "ethernet":
48
+ case "wifi":
49
+ const routeSectionExtra = ni.destination
50
+ ? { Destination: ni.destination }
51
+ : { Gateway: ni.gatewayAddress };
52
+
53
+ const networkSectionExtra = ni.arpbridge
54
+ ? {
55
+ IPForward: "yes",
56
+ IPv4ProxyARP: "yes"
57
+ }
58
+ : {};
59
+
60
+ networkSections.push(
61
+ "",
62
+ sectionLines("Network", {
63
+ ...networkSectionExtra,
64
+ DHCP: "no",
65
+ DHCPServer: "no",
66
+ MulticastDNS: "yes",
67
+ LinkLocalAddressing: "ipv6",
68
+ IPv6LinkLocalAddressGenerationMode: "stable-privacy"
69
+ }),
70
+ "",
71
+ sectionLines("Route", {
72
+ ...routeSectionExtra,
73
+ Scope: ni.scope,
74
+ Metric: ni.metric,
75
+ InitialCongestionWindow: 20,
76
+ InitialAdvertisedReceiveWindow: 20
77
+ }),
78
+ "",
79
+ sectionLines("IPv6AcceptRA", {
80
+ UseAutonomousPrefix: "true",
81
+ UseOnLinkPrefix: "true",
82
+ DHCPv6Client: "false",
83
+ Token: "eui64"
84
+ })
85
+ );
86
+
87
+ if (ni.arpbridge) {
88
+ networkSections.push(
89
+ "",
90
+ sectionLines("Link", { Promiscuous: "yes" })
91
+ );
92
+ }
93
+ }
94
+
95
+ await writeLines(networkDir, `${ni.name}.network`, networkSections);
96
+
97
+ switch (ni?.kind) {
98
+ case "wireguard":
99
+ {
100
+ }
101
+ break;
102
+ case "wifi": {
103
+ const d = join(dir, "etc/wpa_supplicant");
104
+ await mkdir(d, { recursive: true });
105
+ writeFile(
106
+ join(d, `wpa_supplicant-${ni.name}.conf`),
107
+ `country=${host.location.country}
108
+ ctrl_interface=DIR=/run/wpa_supplicant GROUP=netdev
109
+ update_config=1
110
+ p2p_disabled=1
111
+ network={
112
+ ssid="${ni.ssid}"
113
+ psk=${ni.psk}
114
+ scan_ssid=1
115
+ }`,
116
+ "utf8"
117
+ );
118
+
119
+ host.postinstall.push(
120
+ `systemctl enable wpa_supplicant@${ni.name}.service`
121
+ );
122
+ }
123
+ }
124
+ }
125
+ }
126
+
127
+ export async function copySshKeys(host, dir) {
128
+ const sshDir = join(dir, "etc", "ssh");
129
+
130
+ await mkdir(sshDir, { recursive: true });
131
+
132
+ for await (const file of glob("ssh_host_*", { cwd: host.directory })) {
133
+ const destinationFileName = join(sshDir, file);
134
+ await copyFile(join(host.directory, file), destinationFileName);
135
+ await chmod(
136
+ destinationFileName,
137
+ destinationFileName.endsWith(".pub") ? 0o0644 : 0o0600
138
+ );
139
+ }
140
+ }
141
+
142
+ export async function generateKnownHosts(hosts, dir) {
143
+ const keys = [];
144
+ for await (const host of hosts) {
145
+ try {
146
+ const [alg, key, desc] = (await host.publicKey("ed25519")).split(/\s+/);
147
+
148
+ keys.push(`${host.domainName} ${alg} ${key}`);
149
+
150
+ for await (const addr of host.networkAddresses()) {
151
+ keys.push(`${addr.address} ${alg} ${key}`);
152
+ }
153
+ } catch {}
154
+ }
155
+
156
+ await writeLines(dir, "known_hosts", keys);
157
+ }
@@ -0,0 +1,4 @@
1
+ export function generateMachineInfo(host: any, dir: any): Promise<void>;
2
+ export function generateNetworkDefs(host: any, dir: any): Promise<void>;
3
+ export function copySshKeys(host: any, dir: any): Promise<void>;
4
+ export function generateKnownHosts(hosts: any, dir: any): Promise<void>;