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.
- package/bin/pmcf-host-defs +6 -156
- package/bin/pmcf-named-defs +29 -29
- package/package.json +3 -2
- package/src/host-utils.mjs +157 -0
- package/types/host-utils.d.mts +4 -0
package/bin/pmcf-host-defs
CHANGED
|
@@ -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
|
-
}
|
package/bin/pmcf-named-defs
CHANGED
|
@@ -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
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
208
|
+
.replaceAll(":", "")
|
|
209
|
+
.split("")
|
|
210
|
+
.reverse()
|
|
211
|
+
.join(".");
|
|
212
212
|
}
|
|
213
213
|
|
|
214
|
-
|
|
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) ?
|
|
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.
|
|
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.
|
|
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>;
|