@highstate/common 0.9.15 → 0.9.18
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/dist/chunk-YYNV3MVT.js +1141 -0
- package/dist/chunk-YYNV3MVT.js.map +1 -0
- package/dist/highstate.manifest.json +9 -9
- package/dist/index.js +2 -50
- package/dist/index.js.map +1 -1
- package/dist/units/dns/record-set/index.js +4 -6
- package/dist/units/dns/record-set/index.js.map +1 -1
- package/dist/units/existing-server/index.js +16 -22
- package/dist/units/existing-server/index.js.map +1 -1
- package/dist/units/network/l3-endpoint/index.js +6 -9
- package/dist/units/network/l3-endpoint/index.js.map +1 -1
- package/dist/units/network/l4-endpoint/index.js +6 -9
- package/dist/units/network/l4-endpoint/index.js.map +1 -1
- package/dist/units/script/index.js +6 -9
- package/dist/units/script/index.js.map +1 -1
- package/dist/units/server-dns/index.js +7 -11
- package/dist/units/server-dns/index.js.map +1 -1
- package/dist/units/server-patch/index.js +7 -11
- package/dist/units/server-patch/index.js.map +1 -1
- package/dist/units/ssh/key-pair/index.js +20 -15
- package/dist/units/ssh/key-pair/index.js.map +1 -1
- package/package.json +20 -6
- package/src/shared/command.ts +257 -73
- package/src/shared/files.ts +725 -0
- package/src/shared/index.ts +1 -0
- package/src/shared/network.ts +90 -3
- package/src/shared/passwords.ts +38 -2
- package/src/shared/ssh.ts +249 -81
- package/src/units/existing-server/index.ts +12 -11
- package/src/units/remote-folder/index.ts +0 -0
- package/src/units/server-dns/index.ts +1 -1
- package/src/units/server-patch/index.ts +1 -1
- package/src/units/ssh/key-pair/index.ts +16 -7
- package/dist/chunk-NISDP46H.js +0 -546
- package/dist/chunk-NISDP46H.js.map +0 -1
package/src/shared/index.ts
CHANGED
package/src/shared/network.ts
CHANGED
@@ -19,6 +19,13 @@ export type InputL3Endpoint = network.L3Endpoint | string
|
|
19
19
|
*/
|
20
20
|
export type InputL4Endpoint = network.L4Endpoint | string
|
21
21
|
|
22
|
+
/**
|
23
|
+
* The L7 endpoint for some service.
|
24
|
+
*
|
25
|
+
* The format is: `appProtocol://endpoint[:port][/resource]`
|
26
|
+
*/
|
27
|
+
export type InputL7Endpoint = network.L7Endpoint | string
|
28
|
+
|
22
29
|
/**
|
23
30
|
* Stringifies a L3 endpoint object into a string.
|
24
31
|
*
|
@@ -40,7 +47,6 @@ export function l3EndpointToString(l3Endpoint: network.L3Endpoint): string {
|
|
40
47
|
* Stringifies a L4 endpoint object into a string.
|
41
48
|
*
|
42
49
|
* @param l4Endpoint The L4 endpoint object to stringify.
|
43
|
-
*
|
44
50
|
* @returns The string representation of the L4 endpoint.
|
45
51
|
*/
|
46
52
|
export function l4EndpointToString(l4Endpoint: network.L4Endpoint): string {
|
@@ -51,6 +57,37 @@ export function l4EndpointToString(l4Endpoint: network.L4Endpoint): string {
|
|
51
57
|
return `${l3EndpointToString(l4Endpoint)}:${l4Endpoint.port}`
|
52
58
|
}
|
53
59
|
|
60
|
+
/**
|
61
|
+
* Stringifies a L4 endpoint object into a string with protocol.
|
62
|
+
*
|
63
|
+
* @param l4Endpoint The L4 endpoint object to stringify.
|
64
|
+
* @returns The string representation of the L4 endpoint with protocol.
|
65
|
+
*/
|
66
|
+
export function l4EndpointWithProtocolToString(l4Endpoint: network.L4Endpoint): string {
|
67
|
+
const protocol = `${l4Endpoint.protocol}://`
|
68
|
+
|
69
|
+
return `${protocol}${l4EndpointToString(l4Endpoint)}`
|
70
|
+
}
|
71
|
+
|
72
|
+
/**
|
73
|
+
* Stringifies a L7 endpoint object into a string.
|
74
|
+
*
|
75
|
+
* The format is: `appProtocol://endpoint[:port][/resource]`
|
76
|
+
* @param l7Endpoint The L7 endpoint object to stringify.
|
77
|
+
* @returns The string representation of the L7 endpoint.
|
78
|
+
*/
|
79
|
+
export function l7EndpointToString(l7Endpoint: network.L7Endpoint): string {
|
80
|
+
const protocol = `${l7Endpoint.appProtocol}://`
|
81
|
+
|
82
|
+
let endpoint = l4EndpointToString(l7Endpoint)
|
83
|
+
|
84
|
+
if (l7Endpoint.resource) {
|
85
|
+
endpoint += `/${l7Endpoint.resource}`
|
86
|
+
}
|
87
|
+
|
88
|
+
return `${protocol}${endpoint}`
|
89
|
+
}
|
90
|
+
|
54
91
|
/**
|
55
92
|
* Stringifies a L3 or L4 endpoint object into a string.
|
56
93
|
*
|
@@ -68,6 +105,9 @@ export function l34EndpointToString(l34Endpoint: network.L34Endpoint): string {
|
|
68
105
|
const L34_ENDPOINT_RE =
|
69
106
|
/^(?:(?<protocol>[a-z]+):\/\/)?(?:(?:\[?(?<ipv6>[0-9A-Fa-f:]+)\]?)|(?<ipv4>(?:\d{1,3}\.){3}\d{1,3})|(?<hostname>[a-zA-Z0-9-*]+(?:\.[a-zA-Z0-9-*]+)*))(?::(?<port>\d{1,5}))?$/
|
70
107
|
|
108
|
+
const L7_ENDPOINT_RE =
|
109
|
+
/^(?<appProtocol>[a-z]+):\/\/(?:(?:\[?(?<ipv6>[0-9A-Fa-f:]+)\]?)|(?<ipv4>(?:\d{1,3}\.){3}\d{1,3})|(?<hostname>[a-zA-Z0-9-*]+(?:\.[a-zA-Z0-9-*]+)*))(?::(?<port>\d{1,5}))?(?:\/(?<resource>.*))?$/
|
110
|
+
|
71
111
|
/**
|
72
112
|
* Parses a L3 or L4 endpoint from a string.
|
73
113
|
*
|
@@ -208,14 +248,14 @@ export async function requireInputL4Endpoint(
|
|
208
248
|
}
|
209
249
|
|
210
250
|
/**
|
211
|
-
*
|
251
|
+
* Converts L3 endpoint to L4 endpoint by adding a port and protocol.
|
212
252
|
*
|
213
253
|
* @param l3Endpoint The L3 endpoint to convert.
|
214
254
|
* @param port The port to add to the L3 endpoint.
|
215
255
|
* @param protocol The protocol to add to the L3 endpoint. Defaults to "tcp".
|
216
256
|
* @returns The L4 endpoint with the port and protocol added.
|
217
257
|
*/
|
218
|
-
export function
|
258
|
+
export function l3EndpointToL4(
|
219
259
|
l3Endpoint: InputL3Endpoint,
|
220
260
|
port: number,
|
221
261
|
protocol: network.L4Protocol = "tcp",
|
@@ -275,6 +315,53 @@ export function l3EndpointToCidr(l3Endpoint: network.L3Endpoint): string {
|
|
275
315
|
}
|
276
316
|
}
|
277
317
|
|
318
|
+
const udpAppProtocols = ["dns", "dhcp"]
|
319
|
+
|
320
|
+
/**
|
321
|
+
* Parses a L7 endpoint from a string.
|
322
|
+
*
|
323
|
+
* The format is: `appProtocol://endpoint[:port][/resource]`
|
324
|
+
*
|
325
|
+
* @param l7Endpoint The L7 endpoint string to parse.
|
326
|
+
* @returns The parsed L7 endpoint object.
|
327
|
+
*/
|
328
|
+
export function parseL7Endpoint(l7Endpoint: InputL7Endpoint): network.L7Endpoint {
|
329
|
+
if (typeof l7Endpoint === "object") {
|
330
|
+
return l7Endpoint
|
331
|
+
}
|
332
|
+
|
333
|
+
const match = l7Endpoint.match(L7_ENDPOINT_RE)
|
334
|
+
if (!match) {
|
335
|
+
throw new Error(`Invalid L7 endpoint: "${l7Endpoint}"`)
|
336
|
+
}
|
337
|
+
|
338
|
+
const { appProtocol, ipv6, ipv4, hostname, port, resource } = match.groups!
|
339
|
+
|
340
|
+
let visibility: network.EndpointVisibility = "public"
|
341
|
+
|
342
|
+
if (ipv4 && IPV4_PRIVATE_REGEX.test(ipv4)) {
|
343
|
+
visibility = "external"
|
344
|
+
} else if (ipv6 && IPV6_PRIVATE_REGEX.test(ipv6)) {
|
345
|
+
visibility = "external"
|
346
|
+
}
|
347
|
+
|
348
|
+
return {
|
349
|
+
type: ipv6 ? "ipv6" : ipv4 ? "ipv4" : "hostname",
|
350
|
+
visibility,
|
351
|
+
address: ipv6 || ipv4,
|
352
|
+
hostname: hostname,
|
353
|
+
|
354
|
+
// Default port for L7 endpoints (TODO: add more specific defaults for common protocols)
|
355
|
+
port: port ? parseInt(port, 10) : 443,
|
356
|
+
|
357
|
+
// L7 endpoints typically use TCP, but can also use UDP for specific protocols
|
358
|
+
protocol: udpAppProtocols.includes(appProtocol) ? "udp" : "tcp",
|
359
|
+
|
360
|
+
appProtocol,
|
361
|
+
resource: resource || "",
|
362
|
+
} as network.L7Endpoint
|
363
|
+
}
|
364
|
+
|
278
365
|
/**
|
279
366
|
* Updates the endpoints based on the given mode.
|
280
367
|
*
|
package/src/shared/passwords.ts
CHANGED
@@ -1,6 +1,42 @@
|
|
1
|
-
import { randomBytes } from "@noble/hashes/utils"
|
1
|
+
import { bytesToHex, randomBytes } from "@noble/hashes/utils"
|
2
2
|
import { secureMask } from "micro-key-producer/password.js"
|
3
3
|
|
4
|
-
|
4
|
+
/**
|
5
|
+
* Generates a secure random password strong enough for online use.
|
6
|
+
*
|
7
|
+
* It uses "Safari Keychain Secure Password" format.
|
8
|
+
*
|
9
|
+
* The approximate entropy is 71 bits (https://support.apple.com/guide/security/automatic-strong-passwords-secc84c811c4/web).
|
10
|
+
*/
|
11
|
+
export function generatePassword(): string {
|
5
12
|
return secureMask.apply(randomBytes(32)).password
|
6
13
|
}
|
14
|
+
|
15
|
+
type KeyFormatMap = {
|
16
|
+
raw: Uint8Array
|
17
|
+
hex: string
|
18
|
+
base64: string
|
19
|
+
}
|
20
|
+
|
21
|
+
/**
|
22
|
+
* Generates a secure random key strong enough for online use such as encryption.
|
23
|
+
*
|
24
|
+
* The strong entropy is 256 bits.
|
25
|
+
*
|
26
|
+
* @param format The format of the generated key. By default, it is "hex".
|
27
|
+
*/
|
28
|
+
export function generateKey<TFormat extends keyof KeyFormatMap>(
|
29
|
+
format: TFormat = "hex" as TFormat,
|
30
|
+
): KeyFormatMap[TFormat] {
|
31
|
+
const bytes = randomBytes(32)
|
32
|
+
|
33
|
+
if (format === "raw") {
|
34
|
+
return bytes as KeyFormatMap[TFormat]
|
35
|
+
}
|
36
|
+
|
37
|
+
if (format === "base64") {
|
38
|
+
return Buffer.from(bytes).toString("base64") as KeyFormatMap[TFormat]
|
39
|
+
}
|
40
|
+
|
41
|
+
return bytesToHex(bytes) as KeyFormatMap[TFormat]
|
42
|
+
}
|
package/src/shared/ssh.ts
CHANGED
@@ -1,17 +1,20 @@
|
|
1
1
|
import type { common, network, ssh } from "@highstate/library"
|
2
2
|
import {
|
3
|
-
|
3
|
+
ensureSecretValue,
|
4
4
|
output,
|
5
5
|
Output,
|
6
6
|
secret,
|
7
|
+
toPromise,
|
7
8
|
type Input,
|
8
9
|
type InstanceTerminal,
|
10
|
+
type UnitSecret,
|
9
11
|
} from "@highstate/pulumi"
|
10
12
|
import getKeys, { PrivateExport } from "micro-key-producer/ssh.js"
|
11
13
|
import { randomBytes } from "micro-key-producer/utils.js"
|
12
|
-
import {
|
14
|
+
import { remote } from "@pulumi/command"
|
13
15
|
import * as images from "../../assets/images.json"
|
14
|
-
import { l3EndpointToString,
|
16
|
+
import { l3EndpointToString, l3EndpointToL4 } from "./network"
|
17
|
+
import { Command } from "./command"
|
15
18
|
|
16
19
|
export function createSshTerminal(
|
17
20
|
credentials: Input<ssh.Credentials | undefined>,
|
@@ -40,134 +43,299 @@ export function createSshTerminal(
|
|
40
43
|
|
41
44
|
return {
|
42
45
|
name: "ssh",
|
43
|
-
title: "Shell",
|
44
|
-
description: "Connect to the server via SSH",
|
45
|
-
icon: "gg:remote",
|
46
46
|
|
47
|
-
|
48
|
-
|
47
|
+
meta: {
|
48
|
+
title: "Shell",
|
49
|
+
description: "Connect to the server via SSH",
|
50
|
+
icon: "gg:remote",
|
51
|
+
},
|
49
52
|
|
50
|
-
|
51
|
-
"
|
53
|
+
spec: {
|
54
|
+
image: images["terminal-ssh"].image,
|
55
|
+
command,
|
52
56
|
|
53
|
-
|
54
|
-
|
55
|
-
mode: 0o600,
|
56
|
-
},
|
57
|
+
files: {
|
58
|
+
"/password": credentials.password,
|
57
59
|
|
58
|
-
|
59
|
-
|
60
|
-
|
60
|
+
"/private_key": credentials.keyPair?.privateKey && {
|
61
|
+
content: {
|
62
|
+
type: "embedded",
|
63
|
+
value: credentials.keyPair?.privateKey,
|
64
|
+
},
|
65
|
+
meta: {
|
66
|
+
name: "private_key",
|
67
|
+
mode: 0o600,
|
68
|
+
},
|
69
|
+
},
|
70
|
+
|
71
|
+
"/known_hosts": {
|
72
|
+
content: {
|
73
|
+
type: "embedded",
|
74
|
+
value: `${l3EndpointToString(endpoint)} ${credentials.hostKey}`,
|
75
|
+
},
|
76
|
+
meta: {
|
77
|
+
name: "known_hosts",
|
78
|
+
mode: 0o644,
|
79
|
+
},
|
80
|
+
},
|
61
81
|
},
|
62
82
|
},
|
63
83
|
} satisfies InstanceTerminal
|
64
84
|
})
|
65
85
|
}
|
66
86
|
|
67
|
-
|
87
|
+
/**
|
88
|
+
* Generates a secure random SSH private key.
|
89
|
+
* The key is generated using the Ed25519 algorithm.
|
90
|
+
*
|
91
|
+
* @returns The generated SSH private key in PEM format.
|
92
|
+
*/
|
93
|
+
export function generateSshPrivateKey(): string {
|
68
94
|
const seed = randomBytes(32)
|
69
95
|
|
70
96
|
return getKeys(seed).privateKey
|
71
97
|
}
|
72
98
|
|
73
|
-
|
99
|
+
/**
|
100
|
+
* Converts a private SSH key string to a KeyPair object.
|
101
|
+
*
|
102
|
+
* @param privateKeyString The private key string to convert.
|
103
|
+
* @returns An Output of the KeyPair object.
|
104
|
+
*/
|
105
|
+
export function sshPrivateKeyToKeyPair(privateKeyString: Input<string>): Output<ssh.KeyPair> {
|
74
106
|
return output(privateKeyString).apply(privateKeyString => {
|
75
107
|
const privateKeyStruct = PrivateExport.decode(privateKeyString)
|
76
108
|
|
77
109
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
78
110
|
const privKey = privateKeyStruct.keys[0].privKey.privKey as Uint8Array
|
79
111
|
|
80
|
-
const { fingerprint, publicKey
|
112
|
+
const { fingerprint, publicKey } = getKeys(privKey.slice(0, 32))
|
81
113
|
|
82
114
|
return output({
|
83
115
|
type: "ed25519" as const,
|
84
116
|
fingerprint,
|
85
117
|
publicKey,
|
86
|
-
privateKey: secret(
|
118
|
+
privateKey: secret(privateKeyString),
|
87
119
|
})
|
88
120
|
})
|
89
121
|
}
|
90
122
|
|
91
|
-
|
92
|
-
|
123
|
+
/**
|
124
|
+
* Ensures that the provided SSH key is available and generates a new key pair if not.
|
125
|
+
* If an existing key pair is provided, it will be used instead of generating a new one.
|
126
|
+
*
|
127
|
+
* @param privateKey The secret containing the private key.
|
128
|
+
* @param existingKeyPair An optional existing key pair to use if available.
|
129
|
+
* @returns An Output of the KeyPair object.
|
130
|
+
*/
|
131
|
+
export function ensureSshKeyPair(
|
132
|
+
privateKey: UnitSecret<string | undefined>,
|
133
|
+
existingKeyPair?: Input<ssh.KeyPair>,
|
134
|
+
): Output<ssh.KeyPair> {
|
135
|
+
if (existingKeyPair) {
|
136
|
+
return output(existingKeyPair)
|
137
|
+
}
|
138
|
+
|
139
|
+
return ensureSecretValue(privateKey, generateSshPrivateKey).value.apply(sshPrivateKeyToKeyPair)
|
93
140
|
}
|
94
141
|
|
95
|
-
export type
|
142
|
+
export type ServerOptions = {
|
143
|
+
/**
|
144
|
+
* The local name of the server to namespace resources.
|
145
|
+
*/
|
146
|
+
name: string
|
147
|
+
|
148
|
+
/**
|
149
|
+
* The fallback hostname to use if the server cannot be determined.
|
150
|
+
*
|
151
|
+
* If not provided, the `name` will be used as the fallback hostname.
|
152
|
+
*/
|
153
|
+
fallbackHostname?: string
|
154
|
+
|
155
|
+
/**
|
156
|
+
* The L3 endpoints of the server.
|
157
|
+
*/
|
158
|
+
endpoints: network.L3Endpoint[]
|
159
|
+
|
160
|
+
/**
|
161
|
+
* The L4 endpoint of the server.
|
162
|
+
*
|
163
|
+
* If not provided, the first endpoint will be used + `sshPort`.
|
164
|
+
*/
|
165
|
+
sshEndpoint?: network.L4Endpoint
|
166
|
+
|
167
|
+
/**
|
168
|
+
* The SSH port to use for the server.
|
169
|
+
*
|
170
|
+
* Will be ignored if `sshEndpoint` is provided.
|
171
|
+
*/
|
172
|
+
sshPort?: number
|
173
|
+
|
174
|
+
/**
|
175
|
+
* The SSH user to use for the server.
|
176
|
+
*/
|
177
|
+
sshUser?: string
|
178
|
+
|
179
|
+
/**
|
180
|
+
* The SSH password to use for the server.
|
181
|
+
*/
|
182
|
+
sshPassword?: Input<string | undefined>
|
183
|
+
|
184
|
+
/**
|
185
|
+
* The SSH private key to use for the server.
|
186
|
+
*/
|
96
187
|
sshPrivateKey?: Input<string>
|
97
|
-
}
|
98
188
|
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
189
|
+
/**
|
190
|
+
* Whether the server is expected to have SSH access.
|
191
|
+
* If false, the server will be created without SSH credentials.
|
192
|
+
*
|
193
|
+
* By default, this is true.
|
194
|
+
*/
|
195
|
+
hasSsh?: boolean
|
196
|
+
|
197
|
+
/**
|
198
|
+
* Whether to wait for the server to respond to a ping command before returning.
|
199
|
+
*
|
200
|
+
* If true, the command will wait for a successful ping response before proceeding.
|
201
|
+
*
|
202
|
+
* By default, this is equal to `!waitForSsh`, so when `waitForSsh` is true, no extra ping is performed.
|
203
|
+
*/
|
204
|
+
waitForPing?: boolean
|
205
|
+
|
206
|
+
/**
|
207
|
+
* The interval in seconds to wait between ping attempts.
|
208
|
+
*
|
209
|
+
* Only used if `waitForPing` is true.
|
210
|
+
* By default, it will wait 5 seconds between attempts.
|
211
|
+
*/
|
212
|
+
pingInterval?: number
|
213
|
+
|
214
|
+
/**
|
215
|
+
* The timeout in seconds to wait for the server to respond to a ping command.
|
216
|
+
*
|
217
|
+
* Only used if `waitForPing` is true.
|
218
|
+
* By default, it will wait 5 minutes (300 seconds) before timing out.
|
219
|
+
*/
|
220
|
+
pingTimeout?: number
|
221
|
+
|
222
|
+
/**
|
223
|
+
* Whether to wait for the SSH service to be available before returning.
|
224
|
+
*
|
225
|
+
* If true, the command will wait for the SSH service to respond before proceeding.
|
226
|
+
*
|
227
|
+
* By default, this is true if `hasSsh` is true.
|
228
|
+
*/
|
229
|
+
waitForSsh?: boolean
|
106
230
|
|
107
|
-
|
231
|
+
/**
|
232
|
+
* The interval in seconds to wait between SSH connection attempts.
|
233
|
+
*
|
234
|
+
* Only used if `waitForSsh` is true.
|
235
|
+
* By default, it will wait 5 seconds between attempts.
|
236
|
+
*/
|
237
|
+
sshCheckInterval?: number
|
108
238
|
|
109
|
-
|
239
|
+
/**
|
240
|
+
* The timeout in seconds to wait for the SSH service to respond.
|
241
|
+
*
|
242
|
+
* Only used if `waitForSsh` is true.
|
243
|
+
* By default, it will wait 5 minutes (300 seconds) before timing out.
|
244
|
+
*/
|
245
|
+
sshCheckTimeout?: number
|
110
246
|
}
|
111
247
|
|
112
|
-
|
113
|
-
|
114
|
-
|
248
|
+
/**
|
249
|
+
* Creates a server entity with the provided options.
|
250
|
+
* It will create a command to check the SSH service and return the server entity.
|
251
|
+
*
|
252
|
+
* @param options The options for creating the server entity.
|
253
|
+
* @returns A promise that resolves to the created server entity.
|
254
|
+
*/
|
255
|
+
export async function createServerEntity({
|
256
|
+
name,
|
257
|
+
fallbackHostname,
|
258
|
+
endpoints,
|
259
|
+
sshEndpoint,
|
115
260
|
sshPort = 22,
|
116
261
|
sshUser = "root",
|
117
|
-
sshPassword
|
118
|
-
sshPrivateKey
|
262
|
+
sshPassword,
|
263
|
+
sshPrivateKey,
|
119
264
|
hasSsh = true,
|
120
|
-
|
265
|
+
pingInterval,
|
266
|
+
pingTimeout,
|
267
|
+
waitForPing,
|
268
|
+
waitForSsh,
|
269
|
+
sshCheckInterval,
|
270
|
+
sshCheckTimeout,
|
271
|
+
}: ServerOptions): Promise<common.Server> {
|
272
|
+
if (endpoints.length === 0) {
|
273
|
+
throw new Error("At least one L3 endpoint is required to create a server entity")
|
274
|
+
}
|
275
|
+
|
276
|
+
fallbackHostname ??= name
|
277
|
+
waitForSsh ??= hasSsh
|
278
|
+
waitForPing ??= !waitForSsh
|
279
|
+
|
280
|
+
if (waitForPing) {
|
281
|
+
await Command.waitFor(`${name}.ping`, {
|
282
|
+
host: "local",
|
283
|
+
create: `ping -c 1 ${l3EndpointToString(endpoints[0])}`,
|
284
|
+
timeout: pingTimeout ?? 300,
|
285
|
+
interval: pingInterval ?? 5,
|
286
|
+
triggers: [Date.now()],
|
287
|
+
}).wait()
|
288
|
+
}
|
289
|
+
|
290
|
+
if (!hasSsh) {
|
291
|
+
return {
|
292
|
+
hostname: name,
|
293
|
+
endpoints,
|
294
|
+
}
|
295
|
+
}
|
296
|
+
|
297
|
+
sshEndpoint ??= l3EndpointToL4(endpoints[0], sshPort)
|
298
|
+
|
299
|
+
if (waitForSsh) {
|
300
|
+
await Command.waitFor(`${name}.ssh`, {
|
301
|
+
host: "local",
|
302
|
+
create: `nc -zv ${l3EndpointToString(sshEndpoint)} ${sshPort}`,
|
303
|
+
timeout: sshCheckTimeout ?? 300,
|
304
|
+
interval: sshCheckInterval ?? 5,
|
305
|
+
triggers: [Date.now()],
|
306
|
+
}).wait()
|
307
|
+
}
|
308
|
+
|
121
309
|
const connection = output({
|
122
|
-
host: l3EndpointToString(
|
123
|
-
port:
|
310
|
+
host: l3EndpointToString(sshEndpoint),
|
311
|
+
port: sshEndpoint.port,
|
124
312
|
user: sshUser,
|
125
313
|
password: sshPassword,
|
126
314
|
privateKey: sshPrivateKey,
|
127
315
|
dialErrorLimit: 3,
|
128
316
|
})
|
129
317
|
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
})
|
135
|
-
}
|
136
|
-
|
137
|
-
const command = new local.Command("check-ssh", {
|
138
|
-
create: `nc -zv ${l3EndpointToString(endpoint)} ${sshPort} && echo "up" || echo "down"`,
|
318
|
+
const hostnameResult = new remote.Command("hostname", {
|
319
|
+
connection,
|
320
|
+
create: "hostname",
|
321
|
+
triggers: [Date.now()],
|
139
322
|
})
|
140
323
|
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
})
|
147
|
-
}
|
148
|
-
|
149
|
-
const hostnameResult = new remote.Command("hostname", {
|
150
|
-
connection,
|
151
|
-
create: "hostname",
|
152
|
-
triggers: [Date.now()],
|
153
|
-
})
|
154
|
-
|
155
|
-
const hostKeyResult = new remote.Command("host-key", {
|
156
|
-
connection,
|
157
|
-
create: "cat /etc/ssh/ssh_host_ed25519_key.pub",
|
158
|
-
triggers: [Date.now()],
|
159
|
-
})
|
324
|
+
const hostKeyResult = new remote.Command("host-key", {
|
325
|
+
connection,
|
326
|
+
create: "cat /etc/ssh/ssh_host_ed25519_key.pub",
|
327
|
+
triggers: [Date.now()],
|
328
|
+
})
|
160
329
|
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
})
|
330
|
+
return await toPromise({
|
331
|
+
endpoints,
|
332
|
+
hostname: hostnameResult.stdout.apply(x => x.trim()),
|
333
|
+
ssh: {
|
334
|
+
endpoints: [sshEndpoint],
|
335
|
+
user: sshUser,
|
336
|
+
hostKey: hostKeyResult.stdout.apply(x => x.trim()),
|
337
|
+
password: sshPassword,
|
338
|
+
keyPair: sshPrivateKey ? sshPrivateKeyToKeyPair(sshPrivateKey) : undefined,
|
339
|
+
},
|
172
340
|
})
|
173
341
|
}
|
@@ -1,8 +1,9 @@
|
|
1
1
|
import { common } from "@highstate/library"
|
2
|
-
import { forUnit
|
2
|
+
import { forUnit } from "@highstate/pulumi"
|
3
3
|
import {
|
4
4
|
createServerEntity,
|
5
5
|
createSshTerminal,
|
6
|
+
ensureSshKeyPair,
|
6
7
|
l3EndpointToString,
|
7
8
|
requireInputL3Endpoint,
|
8
9
|
} from "../../shared"
|
@@ -10,24 +11,24 @@ import {
|
|
10
11
|
const { name, args, inputs, secrets, outputs } = forUnit(common.existingServer)
|
11
12
|
|
12
13
|
const endpoint = await requireInputL3Endpoint(args.endpoint, inputs.endpoint)
|
13
|
-
const
|
14
|
+
const keyPair = ensureSshKeyPair(secrets.sshPrivateKey, inputs.sshKeyPair)
|
14
15
|
|
15
|
-
const server = createServerEntity(
|
16
|
+
const server = await createServerEntity({
|
16
17
|
name,
|
17
|
-
endpoint,
|
18
|
-
args.sshPort,
|
19
|
-
args.sshUser,
|
20
|
-
secrets.sshPassword,
|
21
|
-
privateKey,
|
22
|
-
)
|
18
|
+
endpoints: [endpoint],
|
19
|
+
sshPort: args.sshPort,
|
20
|
+
sshUser: args.sshUser,
|
21
|
+
sshPassword: secrets.sshPassword.value,
|
22
|
+
sshPrivateKey: keyPair.privateKey,
|
23
|
+
})
|
23
24
|
|
24
25
|
export default outputs({
|
25
26
|
server,
|
26
27
|
endpoints: server.endpoints,
|
27
28
|
|
28
|
-
$
|
29
|
+
$statusFields: {
|
29
30
|
hostname: server.hostname,
|
30
|
-
endpoints: server.endpoints.
|
31
|
+
endpoints: server.endpoints.map(l3EndpointToString),
|
31
32
|
},
|
32
33
|
|
33
34
|
$terminals: [createSshTerminal(server.ssh)],
|
File without changes
|
@@ -1,15 +1,24 @@
|
|
1
1
|
import { ssh } from "@highstate/library"
|
2
|
-
import { forUnit
|
3
|
-
import {
|
2
|
+
import { forUnit } from "@highstate/pulumi"
|
3
|
+
import { ensureSshKeyPair } from "../../../shared"
|
4
4
|
|
5
|
-
const { secrets, outputs } = forUnit(ssh.keyPair)
|
5
|
+
const { name, secrets, outputs } = forUnit(ssh.keyPair)
|
6
6
|
|
7
|
-
const
|
8
|
-
const keyPair = privateKeyToKeyPair(privateKey)
|
7
|
+
const keyPair = ensureSshKeyPair(secrets.privateKey)
|
9
8
|
|
10
9
|
export default outputs({
|
11
|
-
keyPair
|
12
|
-
|
10
|
+
keyPair,
|
11
|
+
publicKeyFile: {
|
12
|
+
meta: {
|
13
|
+
name: `${name}.pub`,
|
14
|
+
mode: 0o644,
|
15
|
+
},
|
16
|
+
content: {
|
17
|
+
type: "embedded",
|
18
|
+
value: keyPair.publicKey,
|
19
|
+
},
|
20
|
+
},
|
21
|
+
$statusFields: {
|
13
22
|
fingerprint: keyPair.fingerprint,
|
14
23
|
publicKey: keyPair.publicKey,
|
15
24
|
},
|