@highstate/library 0.14.2 → 0.16.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.
Files changed (44) hide show
  1. package/dist/highstate.library.msgpack +0 -0
  2. package/dist/index.js +1721 -953
  3. package/dist/index.js.map +1 -1
  4. package/package.json +3 -3
  5. package/src/abbreviations.ts +1 -0
  6. package/src/common/access-point.ts +2 -2
  7. package/src/common/files.ts +10 -0
  8. package/src/common/server.ts +15 -57
  9. package/src/databases/etcd.ts +97 -0
  10. package/src/databases/index.ts +1 -0
  11. package/src/databases/mariadb.ts +48 -2
  12. package/src/databases/mongodb.ts +48 -2
  13. package/src/databases/postgresql.ts +51 -2
  14. package/src/databases/redis.ts +48 -2
  15. package/src/databases/s3.ts +65 -6
  16. package/src/databases/shared.ts +12 -6
  17. package/src/dns.ts +59 -49
  18. package/src/k8s/apps/etcd.ts +46 -0
  19. package/src/k8s/apps/index.ts +2 -0
  20. package/src/k8s/apps/mariadb.ts +0 -5
  21. package/src/k8s/apps/minio.ts +0 -5
  22. package/src/k8s/apps/mongodb.ts +0 -5
  23. package/src/k8s/apps/postgresql.ts +0 -5
  24. package/src/k8s/apps/shared.ts +10 -1
  25. package/src/k8s/apps/traefik.ts +16 -1
  26. package/src/k8s/apps/valkey.ts +0 -5
  27. package/src/k8s/apps/wg-feed-server.ts +34 -0
  28. package/src/k8s/reduced-access.ts +23 -53
  29. package/src/k8s/resources.ts +78 -35
  30. package/src/k8s/service.ts +21 -10
  31. package/src/k8s/shared.ts +60 -90
  32. package/src/k8s/workload.ts +87 -26
  33. package/src/network/address-space.ts +94 -0
  34. package/src/network/address.ts +33 -0
  35. package/src/network/dynamic-endpoint.ts +39 -0
  36. package/src/network/endpoint-schema.ts +116 -0
  37. package/src/network/endpoint.ts +347 -0
  38. package/src/network/index.ts +6 -0
  39. package/src/network/subnet.ts +31 -0
  40. package/src/ssh.ts +66 -10
  41. package/src/third-party/cloudflare.ts +1 -0
  42. package/src/utils.ts +41 -11
  43. package/src/wireguard.ts +340 -150
  44. package/src/network.ts +0 -391
package/src/wireguard.ts CHANGED
@@ -1,9 +1,25 @@
1
- import { defineEntity, defineUnit, z } from "@highstate/contract"
2
- import { omit } from "remeda"
1
+ import {
2
+ $args,
3
+ $inputs,
4
+ $outputs,
5
+ defineEntity,
6
+ defineUnit,
7
+ genericNameSchema,
8
+ z,
9
+ } from "@highstate/contract"
10
+ import { pick } from "remeda"
11
+ import { fileEntity } from "./common"
3
12
  import { serverEntity } from "./common/server"
4
- import { clusterEntity, exposableWorkloadEntity, networkInterfaceEntity } from "./k8s"
5
- import { l3EndpointEntity, l4EndpointEntity } from "./network"
6
- import { arrayPatchModeSchema } from "./utils"
13
+ import { etcdEntity } from "./databases"
14
+ import { clusterEntity, networkInterfaceEntity, workloadEntity } from "./k8s"
15
+ import {
16
+ addressEntity,
17
+ l3EndpointEntity,
18
+ l4EndpointEntity,
19
+ l7EndpointEntity,
20
+ subnetEntity,
21
+ } from "./network"
22
+ import { toPatchArgs } from "./utils"
7
23
 
8
24
  export const backendSchema = z.enum(["wireguard", "amneziawg"])
9
25
 
@@ -15,12 +31,13 @@ const networkArgs = {
15
31
  *
16
32
  * Possible values are:
17
33
  * - `wireguard` - the default backend;
18
- * - `amneziawg` - the censorship-resistant fork of WireGuard.
34
+ * - `amneziawg` - the censorship-resistant fork of WireGuard (NOT SUPPORTED YET).
19
35
  */
20
36
  backend: backendSchema.default("wireguard"),
21
37
 
22
38
  /**
23
39
  * Whether to enable IPv4 support in the network.
40
+ * Affects addresses inside network, not the endpoints of peers.
24
41
  *
25
42
  * By default, IPv4 support is enabled.
26
43
  */
@@ -28,6 +45,7 @@ const networkArgs = {
28
45
 
29
46
  /**
30
47
  * Whether to enable IPv6 support in the network.
48
+ * Affects addresses inside network, not the endpoints of peers.
31
49
  *
32
50
  * By default, IPv6 support is disabled.
33
51
  */
@@ -50,14 +68,45 @@ export const nodeExposePolicySchema = z.enum(["always", "when-has-endpoint", "ne
50
68
  export const peerEntity = defineEntity({
51
69
  type: "wireguard.peer.v1",
52
70
 
71
+ includes: {
72
+ /**
73
+ * The endpoints where the WireGuard peer can be reached.
74
+ */
75
+ endpoints: {
76
+ entity: l4EndpointEntity,
77
+ multiple: true,
78
+ },
79
+ },
80
+
53
81
  schema: z.object({
54
- name: z.string(),
82
+ /**
83
+ * The name of the WireGuard peer.
84
+ */
85
+ name: genericNameSchema,
86
+
87
+ /**
88
+ * The network to which the WireGuard peer belongs.
89
+ *
90
+ * Holds shared configuration for all identities, peers, and nodes.
91
+ */
55
92
  network: networkEntity.schema.optional(),
93
+
94
+ /**
95
+ * The addresses of the WireGuard interface.
96
+ */
97
+ addresses: addressEntity.schema.array(),
98
+
99
+ /**
100
+ * The allowed subnets of the WireGuard peer.
101
+ *
102
+ * Will be used to configure the `AllowedIPs` of the peer.
103
+ */
104
+ allowedSubnets: subnetEntity.schema.array(),
105
+
106
+ /**
107
+ * The public key of the WireGuard peer.
108
+ */
56
109
  publicKey: z.string(),
57
- address: z.string().optional(),
58
- allowedIps: z.string().array(),
59
- endpoints: l4EndpointEntity.schema.array(),
60
- allowedEndpoints: z.union([l3EndpointEntity.schema, l4EndpointEntity.schema]).array(),
61
110
 
62
111
  /**
63
112
  * The pre-shared key of the WireGuard peer.
@@ -71,13 +120,23 @@ export const peerEntity = defineEntity({
71
120
  /**
72
121
  * The pre-shared key part of the WireGuard peer.
73
122
  *
74
- * If both peers have `presharedKeyPart` set, their `presharedKey` will be calculated as XOR of the two parts.
123
+ * If both peers have `presharedKeyPart` set, their `presharedKey` will be calculated as sha256 of the two parts.
75
124
  */
76
125
  presharedKeyPart: z.string().optional(),
77
126
 
78
- excludedIps: z.string().array(),
79
- dns: z.string().array(),
80
- listenPort: z.number().optional(),
127
+ /**
128
+ * The list of DNS servers to setup for the interface connected to the WireGuard peer.
129
+ */
130
+ dns: addressEntity.schema.array(),
131
+
132
+ /**
133
+ * The port where the WireGuard peer is listening.
134
+ *
135
+ * Will be used:
136
+ * 1. For implementations if the listen port is not set elsewhere.
137
+ * 2. To map L3 endpoints to L4 endpoints with this port.
138
+ */
139
+ listenPort: z.number().default(51820),
81
140
 
82
141
  /**
83
142
  * The keepalive interval in seconds that will be used by all nodes connecting to this peer.
@@ -85,6 +144,16 @@ export const peerEntity = defineEntity({
85
144
  * If set to 0, keepalive is disabled.
86
145
  */
87
146
  persistentKeepalive: z.number().int().nonnegative().default(0),
147
+
148
+ /**
149
+ * The peers which are relayed through this peer.
150
+ *
151
+ * All their allowed IPs will be added to this peer's allowed IPs
152
+ * and will be used to setup routing for all other peers except the relayed ones.
153
+ */
154
+ get relayedPeers() {
155
+ return peerEntity.schema.array().optional()
156
+ },
88
157
  }),
89
158
 
90
159
  meta: {
@@ -95,8 +164,17 @@ export const peerEntity = defineEntity({
95
164
  export const identityEntity = defineEntity({
96
165
  type: "wireguard.identity.v1",
97
166
 
167
+ includes: {
168
+ /**
169
+ * The WireGuard peer representing this identity.
170
+ */
171
+ peer: peerEntity,
172
+ },
173
+
98
174
  schema: z.object({
99
- peer: peerEntity.schema,
175
+ /**
176
+ * The private key of the WireGuard identity.
177
+ */
100
178
  privateKey: z.string(),
101
179
  }),
102
180
 
@@ -105,10 +183,67 @@ export const identityEntity = defineEntity({
105
183
  },
106
184
  })
107
185
 
186
+ export const feedDisplayInfoSchema = z.object({
187
+ /**
188
+ * The display title of the tunnel.
189
+ */
190
+ title: z.string(),
191
+
192
+ /**
193
+ * The display description of the tunnel.
194
+ */
195
+ description: z.string().optional(),
196
+
197
+ /**
198
+ * The display icon URL of the tunnel.
199
+ *
200
+ * Must only be `data:` URL with SVG image.
201
+ */
202
+ iconUrl: z.url().optional(),
203
+ })
204
+
205
+ export const feedMetadataSchema = z.object({
206
+ /**
207
+ * The ID of the tunnel in the feed.
208
+ */
209
+ id: z.string(),
210
+
211
+ /**
212
+ * The suggested name of the interface for the tunnel.
213
+ */
214
+ name: genericNameSchema,
215
+
216
+ /**
217
+ * The display information of the tunnel.
218
+ */
219
+ displayInfo: feedDisplayInfoSchema,
220
+ })
221
+
222
+ export const configEntity = defineEntity({
223
+ type: "wireguard.config.v1",
224
+
225
+ includes: {
226
+ /**
227
+ * The file containing the wg-quick configuration.
228
+ */
229
+ file: fileEntity,
230
+ },
231
+
232
+ schema: z.object({
233
+ /**
234
+ * The metadata to include in the wg-feed for this config.
235
+ *
236
+ * Must be provided for the configs uploaded to wg-feed.
237
+ */
238
+ feedMetadata: feedMetadataSchema.optional(),
239
+ }),
240
+ })
241
+
108
242
  export type Network = z.infer<typeof networkEntity.schema>
109
243
  export type Identity = z.infer<typeof identityEntity.schema>
110
244
  export type Peer = z.infer<typeof peerEntity.schema>
111
245
  export type NodeExposePolicy = z.infer<typeof nodeExposePolicySchema>
246
+ export type Config = z.infer<typeof configEntity.schema>
112
247
 
113
248
  /**
114
249
  * Holds the shared configuration for WireGuard identities, peers, and nodes.
@@ -135,7 +270,7 @@ export const network = defineUnit({
135
270
  },
136
271
  })
137
272
 
138
- const sharedPeerArgs = {
273
+ const sharedPeerArgs = $args({
139
274
  /**
140
275
  * The name of the WireGuard peer.
141
276
  *
@@ -144,11 +279,11 @@ const sharedPeerArgs = {
144
279
  peerName: z.string().optional(),
145
280
 
146
281
  /**
147
- * The address of the WireGuard interface.
282
+ * The addresses of the WireGuard interface.
148
283
  *
149
284
  * The address may be any IPv4 or IPv6 address. CIDR notation is also supported.
150
285
  */
151
- address: z.string().optional(),
286
+ addresses: z.string().array().default([]),
152
287
 
153
288
  /**
154
289
  * The convenience option to set `allowedIps` to `0.0.0.0/0, ::/0`.
@@ -157,16 +292,15 @@ const sharedPeerArgs = {
157
292
  */
158
293
  exitNode: z.boolean().default(false),
159
294
 
295
+ /**
296
+ * The list of IP ranges to include in the allowed IPs of the peer.
297
+ */
298
+ allowedSubnets: z.string().array().default([]),
299
+
160
300
  /**
161
301
  * The list of IP ranges to exclude from the tunnel.
162
- *
163
- * Implementation notes:
164
- *
165
- * - this list will not be used to generate the allowed IPs for the peer;
166
- * - instead, the node will setup extra direct routes to these IPs via default gateway;
167
- * - this allows to use `0.0.0.0/0, ::/0` in the `allowedIps` (and corresponding fwmark magic) and still have some IPs excluded from the tunnel.
168
302
  */
169
- excludedIps: z.string().array().default([]),
303
+ excludedSubnets: z.string().array().default([]),
170
304
 
171
305
  /**
172
306
  * The convenience option to exclude private IPs from the tunnel.
@@ -184,7 +318,7 @@ const sharedPeerArgs = {
184
318
  *
185
319
  * Will be merged with `excludedIps` if provided.
186
320
  */
187
- excludePrivateIps: z.boolean().default(false),
321
+ excludePrivateSubnets: z.boolean().default(false),
188
322
 
189
323
  /**
190
324
  * The endpoints of the WireGuard peer.
@@ -192,18 +326,18 @@ const sharedPeerArgs = {
192
326
  endpoints: z.string().array().default([]),
193
327
 
194
328
  /**
195
- * The allowed endpoints of the WireGuard peer.
329
+ * The DNS servers that should be used by the interface connected to the WireGuard peer.
196
330
  *
197
- * The non `hostname` endpoints will be added to the `allowedIps` of the peer.
331
+ * If multiple peers define DNS servers, the node will merge them into a single list (but this is discouraged).
198
332
  */
199
- allowedEndpoints: z.string().array().default([]),
333
+ dns: z.string().array().default([]),
200
334
 
201
335
  /**
202
- * The DNS servers that should be used by the interface connected to the WireGuard peer.
336
+ * The convenience option to include the addresses to the allowed IPs.
203
337
  *
204
- * If multiple peers define DNS servers, the node will merge them into a single list (but this is discouraged).
338
+ * By default, is `true`.
205
339
  */
206
- dns: z.string().array().default([]),
340
+ includeAddresses: z.boolean().default(true),
207
341
 
208
342
  /**
209
343
  * The convenience option to include the DNS servers to the allowed IPs.
@@ -215,7 +349,7 @@ const sharedPeerArgs = {
215
349
  /**
216
350
  * The port to listen on.
217
351
  */
218
- listenPort: z.number().optional(),
352
+ listenPort: z.number().default(51820),
219
353
 
220
354
  /**
221
355
  * The keepalive interval in seconds that will be used by all nodes connecting to this peer.
@@ -223,9 +357,9 @@ const sharedPeerArgs = {
223
357
  * If set to 0, keepalive is disabled.
224
358
  */
225
359
  persistentKeepalive: z.number().int().nonnegative().default(0),
226
- }
360
+ })
227
361
 
228
- const sharedPeerInputs = {
362
+ const sharedPeerInputs = $inputs({
229
363
  /**
230
364
  * The network to use for the WireGuard identity.
231
365
  *
@@ -237,65 +371,54 @@ const sharedPeerInputs = {
237
371
  },
238
372
 
239
373
  /**
240
- * The L3 endpoints of the identity.
374
+ * The L3/L4 endpoints of the identity.
241
375
  *
242
- * Will produce L4 endpoints for each of the provided L3 endpoints.
376
+ * All L3 endpoints will be adjusted to L4 endpoints with listen port of the identity.
243
377
  */
244
- l3Endpoints: {
378
+ endpoints: {
245
379
  entity: l3EndpointEntity,
246
380
  multiple: true,
247
381
  required: false,
248
382
  },
249
383
 
250
384
  /**
251
- * The L4 endpoints of the identity.
252
- *
253
- * Will take priority over all calculated endpoints if provided.
385
+ * the endpoints to add to the allowed IPs of the identity.
254
386
  */
255
- l4Endpoints: {
256
- entity: l4EndpointEntity,
257
- required: false,
387
+ allowedEndpoints: {
388
+ entity: l3EndpointEntity,
258
389
  multiple: true,
390
+ required: false,
259
391
  },
260
392
 
261
393
  /**
262
- * The L3 endpoints to add to the allowed IPs of the identity.
263
- *
264
- * `hostname` endpoints will be ignored.
265
- *
266
- * If the endpoint contains k8s service metadata of the cluster where the identity node is deployed,
267
- * the corresponding network policy will be created.
394
+ * The subnets to add to the allowed IPs of the identity.
268
395
  */
269
- allowedL3Endpoints: {
270
- entity: l3EndpointEntity,
396
+ allowedSubnets: {
397
+ entity: subnetEntity,
271
398
  multiple: true,
272
399
  required: false,
273
400
  },
274
401
 
275
402
  /**
276
- * The L4 endpoints to add to the allowed IPs of the identity.
277
- *
278
- * If the endpoint contains k8s service metadata of the cluster where the identity node is deployed,
279
- * the corresponding network policy will be created.
403
+ * The peers which are relayed through this peer.
280
404
  */
281
- allowedL4Endpoints: {
282
- entity: l4EndpointEntity,
405
+ relayedPeers: {
406
+ entity: peerEntity,
283
407
  multiple: true,
284
408
  required: false,
285
409
  },
286
- } as const
410
+ })
287
411
 
288
- const sharedPeerOutputs = {
412
+ const sharedPeerOutputs = $outputs({
289
413
  peer: peerEntity,
414
+ })
290
415
 
291
- endpoints: {
292
- entity: l4EndpointEntity,
293
- required: false,
294
- multiple: true,
295
- },
296
- } as const
297
-
298
- export type SharedPeerArgs = z.infer<z.ZodObject<typeof sharedPeerArgs>>
416
+ export type SharedPeerArgs = z.infer<
417
+ z.ZodObject<{
418
+ // @ts-expect-error idk why
419
+ [K in keyof typeof sharedPeerArgs]: (typeof sharedPeerArgs)[K]["schema"]
420
+ }>
421
+ >
299
422
 
300
423
  /**
301
424
  * The WireGuard peer with the public key.
@@ -341,52 +464,14 @@ export const peer = defineUnit({
341
464
  export const peerPatch = defineUnit({
342
465
  type: "wireguard.peer-patch.v1",
343
466
 
344
- args: {
345
- /**
346
- * The endpoints of the WireGuard peer.
347
- */
348
- endpoints: z.string().array().default([]),
349
-
350
- /**
351
- * The mode to use for patching the endpoints.
352
- *
353
- * - `prepend`: prepend the new endpoints to the existing ones (default);
354
- * - `replace`: replace the existing endpoints with the new ones.
355
- */
356
- endpointsPatchMode: arrayPatchModeSchema.default("prepend"),
357
-
358
- /**
359
- * The allowed endpoints of the WireGuard peer.
360
- *
361
- * The non `hostname` endpoints will be added to the `allowedIps` of the peer.
362
- */
363
- allowedEndpoints: z.string().array().default([]),
364
-
365
- /**
366
- * The mode to use for patching the allowed endpoints.
367
- *
368
- * - `prepend`: prepend the new endpoints to the existing ones (default);
369
- * - `replace`: replace the existing endpoints with the new ones.
370
- */
371
- allowedEndpointsPatchMode: arrayPatchModeSchema.default("prepend"),
372
-
373
- ...omit(sharedPeerArgs, ["endpoints", "allowedEndpoints"]),
374
- },
467
+ args: toPatchArgs(sharedPeerArgs),
375
468
 
376
469
  inputs: {
377
470
  peer: peerEntity,
378
471
  ...sharedPeerInputs,
379
472
  },
380
473
 
381
- outputs: {
382
- peer: peerEntity,
383
-
384
- endpoints: {
385
- entity: l4EndpointEntity,
386
- required: false,
387
- multiple: true,
388
- },
389
- },
474
+ outputs: sharedPeerOutputs,
390
475
 
391
476
  meta: {
392
477
  title: "WireGuard Peer Patch",
@@ -408,25 +493,7 @@ export const peerPatch = defineUnit({
408
493
  export const identity = defineUnit({
409
494
  type: "wireguard.identity.v1",
410
495
 
411
- args: {
412
- ...sharedPeerArgs,
413
-
414
- /**
415
- * The port to listen on.
416
- *
417
- * Used by the implementation of the identity and to calculate the endpoint of the peer.
418
- */
419
- listenPort: z.number().optional(),
420
-
421
- /**
422
- * The endpoint of the WireGuard peer.
423
- *
424
- * If overridden, does not affect node which implements the identity, but is used in the peer configuration of other nodes.
425
- *
426
- * Will take priority over all calculated endpoints and `l4Endpoint` input.
427
- */
428
- endpoints: z.string().array().default([]),
429
- },
496
+ args: sharedPeerArgs,
430
497
 
431
498
  secrets: {
432
499
  /**
@@ -448,7 +515,6 @@ export const identity = defineUnit({
448
515
 
449
516
  outputs: {
450
517
  identity: identityEntity,
451
- ...sharedPeerOutputs,
452
518
  },
453
519
 
454
520
  meta: {
@@ -509,7 +575,7 @@ export const nodeK8s = defineUnit({
509
575
  *
510
576
  * Useful for peer isolation where you want to prevent cross-peer communication.
511
577
  */
512
- forwardRestrictedIps: z.string().array().default([]),
578
+ forwardRestrictedSubnets: z.string().array().default([]),
513
579
  },
514
580
 
515
581
  inputs: {
@@ -517,7 +583,7 @@ export const nodeK8s = defineUnit({
517
583
  k8sCluster: clusterEntity,
518
584
 
519
585
  workload: {
520
- entity: exposableWorkloadEntity,
586
+ entity: workloadEntity,
521
587
  required: false,
522
588
  },
523
589
 
@@ -534,20 +600,16 @@ export const nodeK8s = defineUnit({
534
600
  },
535
601
 
536
602
  outputs: {
603
+ workload: {
604
+ entity: workloadEntity,
605
+ },
606
+
537
607
  interface: {
538
608
  entity: networkInterfaceEntity,
539
- required: false,
540
609
  },
541
610
 
542
611
  peer: {
543
612
  entity: peerEntity,
544
- required: false,
545
- },
546
-
547
- endpoints: {
548
- entity: l4EndpointEntity,
549
- required: false,
550
- multiple: true,
551
613
  },
552
614
  },
553
615
 
@@ -579,13 +641,6 @@ export const node = defineUnit({
579
641
  */
580
642
  interfaceName: z.string().optional(),
581
643
 
582
- /**
583
- * The name of the default interface for excluded routes.
584
- *
585
- * This is used to route excluded IPs through the default interface instead of the WireGuard tunnel.
586
- */
587
- defaultInterface: z.string().default("eth0"),
588
-
589
644
  /**
590
645
  * List of CIDR blocks that should be blocked from forwarding through this WireGuard node.
591
646
  *
@@ -665,6 +720,17 @@ export const node = defineUnit({
665
720
  },
666
721
  })
667
722
 
723
+ const sharedArgs = $args({
724
+ /**
725
+ * The filter to use when selecting endpoints for each peer.
726
+ *
727
+ * The first matching endpoint will be used.
728
+ *
729
+ * If not provided, all endpoints will be considered.
730
+ */
731
+ peerEndpointFilter: z.string().optional().meta({ language: "javascript" }),
732
+ })
733
+
668
734
  /**
669
735
  * Just the WireGuard configuration for the identity and peers.
670
736
  */
@@ -672,12 +738,34 @@ export const config = defineUnit({
672
738
  type: "wireguard.config.v1",
673
739
 
674
740
  args: {
741
+ ...sharedArgs,
742
+
675
743
  /**
676
- * The name of the "default" interface where non-tunneled traffic should go.
677
- *
678
- * If not provided, the config will not respect `excludedIps`.
744
+ * The metadata to include in the wg-feed for this config.
679
745
  */
680
- defaultInterface: z.string().optional(),
746
+ feedMetadata: z
747
+ .discriminatedUnion("enabled", [
748
+ z.object({
749
+ /**
750
+ * Whether this config is enabled for upload to wg-feed.
751
+ *
752
+ * You must fill the metadata fields.
753
+ */
754
+ enabled: z.literal("true"),
755
+
756
+ ...pick(feedMetadataSchema.shape, ["id", "name"]),
757
+
758
+ // Highstate does not support nested objects in UI
759
+ ...feedDisplayInfoSchema.shape,
760
+ }),
761
+ z.object({
762
+ /**
763
+ * Whether this config is enabled for upload to wg-feed.
764
+ */
765
+ enabled: z.literal("false"),
766
+ }),
767
+ ])
768
+ .prefault({ enabled: "false" }),
681
769
  },
682
770
 
683
771
  inputs: {
@@ -689,6 +777,10 @@ export const config = defineUnit({
689
777
  },
690
778
  },
691
779
 
780
+ outputs: {
781
+ config: configEntity,
782
+ },
783
+
692
784
  meta: {
693
785
  title: "WireGuard Config",
694
786
  icon: "simple-icons:wireguard",
@@ -709,6 +801,10 @@ export const config = defineUnit({
709
801
  export const configBundle = defineUnit({
710
802
  type: "wireguard.config-bundle.v1",
711
803
 
804
+ args: {
805
+ ...sharedArgs,
806
+ },
807
+
712
808
  inputs: {
713
809
  identity: identityEntity,
714
810
  peers: {
@@ -722,6 +818,13 @@ export const configBundle = defineUnit({
722
818
  },
723
819
  },
724
820
 
821
+ outputs: {
822
+ configs: {
823
+ entity: configEntity,
824
+ multiple: true,
825
+ },
826
+ },
827
+
725
828
  meta: {
726
829
  title: "WireGuard Config Bundle",
727
830
  icon: "simple-icons:wireguard",
@@ -735,3 +838,90 @@ export const configBundle = defineUnit({
735
838
  path: "config-bundle",
736
839
  },
737
840
  })
841
+
842
+ /**
843
+ * Uploads WireGuard configs to the etcd to be consumed by wg-feed clients.
844
+ */
845
+ export const feed = defineUnit({
846
+ type: "wireguard.feed.v1",
847
+
848
+ args: {
849
+ /**
850
+ * The TTL seconds to suggest to wg-feed clients.
851
+ *
852
+ * By default, is 900 seconds (15 minutes).
853
+ */
854
+ ttlSeconds: z.number().int().positive().default(900),
855
+
856
+ /**
857
+ * The endpoints of the wg-feed servers to use for generating the subscription URLs.
858
+ *
859
+ * At least one endpoint must be provided either here or via `serverEndpoints` input.
860
+ *
861
+ * The resulting subscription URL will be inferred as: `https://{firstEndpoint}/{feedId}#{privateKey}`.
862
+ */
863
+ serverEndpoints: z.string().array().default([]),
864
+
865
+ /**
866
+ * The AGE public key (x25519 recipient) to encrypt the configs with.
867
+ *
868
+ * Note: If you provide this, you must provide the corresponding private key to the clients.
869
+ * Resulting subscription URL will not contain the private key.
870
+ */
871
+ publicKey: z.string().optional(),
872
+
873
+ /**
874
+ * The display information of the feed.
875
+ */
876
+ displayInfo: feedDisplayInfoSchema,
877
+ },
878
+
879
+ secrets: {
880
+ /**
881
+ * The cuidv2 of the feed.
882
+ * Will be used as path of the feed in etcd/subscription URL.
883
+ *
884
+ * In most cases, you don't want to provide this and let it be generated automatically.
885
+ *
886
+ * The `id` field of the feed document will be inferred from this value as `uuidv5(feedId, "2b5e358c-3510-48fb-b1cf-a8aee788925a")`.
887
+ */
888
+ feedId: z.string().optional(),
889
+
890
+ /**
891
+ * The AGE private key (x25519 identity) to embed in the subscription URL.
892
+ *
893
+ * If not provided and `publicKey` is not provided, a new key pair will be generated.
894
+ */
895
+ privateKey: z.string().optional(),
896
+ },
897
+
898
+ inputs: {
899
+ etcd: etcdEntity,
900
+ serverEndpoints: {
901
+ entity: l4EndpointEntity,
902
+ required: false,
903
+ multiple: true,
904
+ },
905
+ configs: {
906
+ entity: configEntity,
907
+ multiple: true,
908
+ },
909
+ },
910
+
911
+ outputs: {
912
+ endpoint: l7EndpointEntity,
913
+ },
914
+
915
+ source: {
916
+ package: "@highstate/wireguard",
917
+ path: "feed",
918
+ },
919
+
920
+ meta: {
921
+ title: "WireGuard Feed",
922
+ icon: "simple-icons:wireguard",
923
+ iconColor: "#88171a",
924
+ secondaryIcon: "mdi:rss",
925
+ category: "VPN",
926
+ },
927
+ })