@highstate/common 0.9.14 → 0.9.16

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 (34) hide show
  1. package/dist/chunk-HZBJ6LLS.js +1057 -0
  2. package/dist/chunk-HZBJ6LLS.js.map +1 -0
  3. package/dist/highstate.manifest.json +9 -9
  4. package/dist/index.js +2 -50
  5. package/dist/index.js.map +1 -1
  6. package/dist/units/dns/record-set/index.js +4 -6
  7. package/dist/units/dns/record-set/index.js.map +1 -1
  8. package/dist/units/existing-server/index.js +7 -13
  9. package/dist/units/existing-server/index.js.map +1 -1
  10. package/dist/units/network/l3-endpoint/index.js +6 -9
  11. package/dist/units/network/l3-endpoint/index.js.map +1 -1
  12. package/dist/units/network/l4-endpoint/index.js +6 -9
  13. package/dist/units/network/l4-endpoint/index.js.map +1 -1
  14. package/dist/units/script/index.js +6 -9
  15. package/dist/units/script/index.js.map +1 -1
  16. package/dist/units/server-dns/index.js +7 -11
  17. package/dist/units/server-dns/index.js.map +1 -1
  18. package/dist/units/server-patch/index.js +7 -11
  19. package/dist/units/server-patch/index.js.map +1 -1
  20. package/dist/units/ssh/key-pair/index.js +18 -12
  21. package/dist/units/ssh/key-pair/index.js.map +1 -1
  22. package/package.json +22 -7
  23. package/src/shared/command.ts +19 -9
  24. package/src/shared/files.ts +730 -0
  25. package/src/shared/index.ts +1 -0
  26. package/src/shared/network.ts +88 -1
  27. package/src/shared/ssh.ts +47 -19
  28. package/src/units/existing-server/index.ts +1 -1
  29. package/src/units/remote-folder/index.ts +0 -0
  30. package/src/units/server-dns/index.ts +1 -1
  31. package/src/units/server-patch/index.ts +1 -1
  32. package/src/units/ssh/key-pair/index.ts +12 -2
  33. package/dist/chunk-2JB6FMYR.js +0 -531
  34. package/dist/chunk-2JB6FMYR.js.map +0 -1
@@ -3,3 +3,4 @@ export * from "./dns"
3
3
  export * from "./passwords"
4
4
  export * from "./ssh"
5
5
  export * from "./network"
6
+ export * from "./files"
@@ -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
  *
@@ -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/ssh.ts CHANGED
@@ -1,7 +1,6 @@
1
1
  import type { common, network, ssh } from "@highstate/library"
2
2
  import {
3
3
  getOrCreateSecret,
4
- getUnitInstanceName,
5
4
  output,
6
5
  Output,
7
6
  secret,
@@ -11,7 +10,8 @@ import {
11
10
  import getKeys, { PrivateExport } from "micro-key-producer/ssh.js"
12
11
  import { randomBytes } from "micro-key-producer/utils.js"
13
12
  import { local, remote } from "@pulumi/command"
14
- import { l3EndpointToString, l3ToL4Endpoint, l4EndpointToString } from "./network"
13
+ import * as images from "../../assets/images.json"
14
+ import { l3EndpointToString, l3ToL4Endpoint } from "./network"
15
15
 
16
16
  export function createSshTerminal(
17
17
  credentials: Input<ssh.Credentials | undefined>,
@@ -40,25 +40,44 @@ export function createSshTerminal(
40
40
 
41
41
  return {
42
42
  name: "ssh",
43
- title: `SSH: ${getUnitInstanceName()}`,
44
- description: "Connect to the server via SSH",
45
- image: "ghcr.io/exeteres/highstate/terminal-ssh",
46
- command,
47
43
 
48
- files: {
49
- "/password": credentials.password,
50
-
51
- "/private_key": {
52
- content: credentials.keyPair?.privateKey,
53
- mode: 0o600,
54
- },
44
+ meta: {
45
+ title: "Shell",
46
+ description: "Connect to the server via SSH",
47
+ icon: "gg:remote",
48
+ },
55
49
 
56
- "/known_hosts": {
57
- content: `${l3EndpointToString(endpoint)} ${credentials.hostKey}`,
58
- mode: 0o644,
50
+ spec: {
51
+ image: images["terminal-ssh"].image,
52
+ command,
53
+
54
+ files: {
55
+ "/password": credentials.password,
56
+
57
+ "/private_key": credentials.keyPair?.privateKey && {
58
+ content: {
59
+ type: "embedded",
60
+ value: credentials.keyPair?.privateKey,
61
+ },
62
+ meta: {
63
+ name: "private_key",
64
+ mode: 0o600,
65
+ },
66
+ },
67
+
68
+ "/known_hosts": {
69
+ content: {
70
+ type: "embedded",
71
+ value: `${l3EndpointToString(endpoint)} ${credentials.hostKey}`,
72
+ },
73
+ meta: {
74
+ name: "known_hosts",
75
+ mode: 0o644,
76
+ },
77
+ },
59
78
  },
60
79
  },
61
- }
80
+ } satisfies InstanceTerminal
62
81
  })
63
82
  }
64
83
 
@@ -75,13 +94,13 @@ export function privateKeyToKeyPair(privateKeyString: Input<string>): Output<ssh
75
94
  // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
76
95
  const privKey = privateKeyStruct.keys[0].privKey.privKey as Uint8Array
77
96
 
78
- const { fingerprint, publicKey, privateKey } = getKeys(privKey.slice(0, 32))
97
+ const { fingerprint, publicKey } = getKeys(privKey.slice(0, 32))
79
98
 
80
99
  return output({
81
100
  type: "ed25519" as const,
82
101
  fingerprint,
83
102
  publicKey,
84
- privateKey: secret(privateKey),
103
+ privateKey: secret(privateKeyString),
85
104
  })
86
105
  })
87
106
  }
@@ -114,6 +133,7 @@ export function createServerEntity(
114
133
  sshUser = "root",
115
134
  sshPassword?: Input<string | undefined>,
116
135
  sshPrivateKey?: Input<string>,
136
+ hasSsh = true,
117
137
  ): Output<common.Server> {
118
138
  const connection = output({
119
139
  host: l3EndpointToString(endpoint),
@@ -124,8 +144,16 @@ export function createServerEntity(
124
144
  dialErrorLimit: 3,
125
145
  })
126
146
 
147
+ if (!hasSsh) {
148
+ return output({
149
+ hostname: fallbackHostname,
150
+ endpoints: [endpoint],
151
+ })
152
+ }
153
+
127
154
  const command = new local.Command("check-ssh", {
128
155
  create: `nc -zv ${l3EndpointToString(endpoint)} ${sshPort} && echo "up" || echo "down"`,
156
+ triggers: [Date.now()],
129
157
  })
130
158
 
131
159
  return command.stdout.apply(result => {
@@ -25,7 +25,7 @@ export default outputs({
25
25
  server,
26
26
  endpoints: server.endpoints,
27
27
 
28
- $status: {
28
+ $statusFields: {
29
29
  hostname: server.hostname,
30
30
  endpoints: server.endpoints.apply(endpoints => endpoints.map(l3EndpointToString)),
31
31
  },
File without changes
@@ -20,7 +20,7 @@ export default outputs({
20
20
 
21
21
  endpoints,
22
22
 
23
- $status: {
23
+ $statusFields: {
24
24
  endpoints: endpoints.map(l3EndpointToString),
25
25
  },
26
26
  })
@@ -19,7 +19,7 @@ export default outputs({
19
19
 
20
20
  endpoints,
21
21
 
22
- $status: {
22
+ $statusFields: {
23
23
  endpoints: endpoints.map(l3EndpointToString),
24
24
  },
25
25
  })
@@ -2,14 +2,24 @@ import { ssh } from "@highstate/library"
2
2
  import { forUnit, getOrCreateSecret } from "@highstate/pulumi"
3
3
  import { generatePrivateKey, privateKeyToKeyPair } from "../../../shared"
4
4
 
5
- const { secrets, outputs } = forUnit(ssh.keyPair)
5
+ const { name, secrets, outputs } = forUnit(ssh.keyPair)
6
6
 
7
7
  const privateKey = getOrCreateSecret(secrets, "privateKey", generatePrivateKey)
8
8
  const keyPair = privateKeyToKeyPair(privateKey)
9
9
 
10
10
  export default outputs({
11
11
  keyPair: privateKeyToKeyPair(privateKey),
12
- $status: {
12
+ publicKeyFile: {
13
+ meta: {
14
+ name: `${name}.pub`,
15
+ mode: 0o644,
16
+ },
17
+ content: {
18
+ type: "embedded",
19
+ value: keyPair.publicKey,
20
+ },
21
+ },
22
+ $statusFields: {
13
23
  fingerprint: keyPair.fingerprint,
14
24
  publicKey: keyPair.publicKey,
15
25
  },