@takodotid/circuit 0.43.1 → 1.0.0-rc.2

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 (256) hide show
  1. package/README.md +115 -37
  2. package/package.json +21 -58
  3. package/src/checks/across.ts +402 -0
  4. package/src/checks/built-in.ts +256 -0
  5. package/src/checks/device.ts +436 -0
  6. package/src/checks/index.ts +53 -0
  7. package/src/cli/advisories.ts +108 -0
  8. package/src/cli/app.ts +71 -0
  9. package/src/cli/editor-types.generated.ts +30 -0
  10. package/src/cli/editor-types.ts +69 -0
  11. package/src/cli/format.ts +124 -0
  12. package/src/cli/git-hooks.ts +79 -0
  13. package/src/cli/index.ts +350 -1170
  14. package/src/cli/migrate.ts +554 -0
  15. package/src/cli/project-commands.ts +656 -0
  16. package/src/cli/releases.ts +374 -0
  17. package/src/cli/remember.ts +339 -0
  18. package/src/cli/rename.ts +27 -33
  19. package/src/cli/secrets-password.ts +60 -0
  20. package/src/cli/secrets.ts +258 -0
  21. package/src/cli/setup.ts +300 -0
  22. package/src/cli/signing.ts +23 -0
  23. package/src/cli/text-editor.ts +268 -0
  24. package/src/cli/ui.ts +298 -0
  25. package/src/cli/version.ts +2 -0
  26. package/src/cli/versions.ts +158 -0
  27. package/src/config/address.ts +102 -0
  28. package/src/config/circuit.d.ts +30 -0
  29. package/src/config/common.ts +65 -0
  30. package/src/config/communities.ts +479 -0
  31. package/src/config/define.ts +684 -0
  32. package/src/config/device.ts +164 -0
  33. package/src/config/firewall.ts +150 -0
  34. package/src/config/index.ts +22 -0
  35. package/src/{schema → config}/interfaces.ts +121 -85
  36. package/src/config/keys.ts +434 -0
  37. package/src/config/management.ts +61 -0
  38. package/src/config/merge.ts +335 -0
  39. package/src/config/models.ts +70 -0
  40. package/src/config/monitoring.ts +64 -0
  41. package/src/config/network.ts +180 -0
  42. package/src/config/presets.ts +149 -0
  43. package/src/config/routing.ts +401 -0
  44. package/src/config/services.ts +51 -0
  45. package/src/config/switching.ts +84 -0
  46. package/src/config/system.ts +103 -0
  47. package/src/{schema → config}/time-zones.ts +1 -1
  48. package/src/core/address.ts +1 -1
  49. package/src/core/cabling.ts +8 -43
  50. package/src/core/graph.ts +40 -0
  51. package/src/{integrations → core}/registries.ts +5 -3
  52. package/src/core/secrets-file.ts +539 -0
  53. package/src/core/secrets.ts +83 -64
  54. package/src/core/topology.ts +103 -146
  55. package/src/core/utils.ts +12 -1
  56. package/src/model.ts +83 -0
  57. package/src/platforms/derive.ts +64 -37
  58. package/src/platforms/finding.ts +9 -0
  59. package/src/platforms/fortios/firewall.ts +76 -64
  60. package/src/platforms/fortios/index.ts +189 -125
  61. package/src/platforms/fortios/names.ts +51 -18
  62. package/src/platforms/fortios/render.ts +69 -62
  63. package/src/platforms/fortios/routing.ts +89 -58
  64. package/src/platforms/fortios/vdoms.ts +47 -16
  65. package/src/platforms/junos/TRAPS.md +2 -2
  66. package/src/platforms/junos/bgp.ts +63 -42
  67. package/src/platforms/junos/index.ts +122 -89
  68. package/src/platforms/junos/render.ts +139 -104
  69. package/src/platforms/platform.ts +10 -8
  70. package/src/platforms/raisecom-ros/TRAPS.md +1 -1
  71. package/src/platforms/raisecom-ros/index.ts +89 -70
  72. package/src/platforms/raisecom-ros/render.ts +49 -45
  73. package/src/platforms/routeros/TRAPS.md +3 -3
  74. package/src/platforms/routeros/index.ts +121 -60
  75. package/src/platforms/routeros/render.ts +214 -195
  76. package/src/platforms/vrp/TRAPS.md +1 -1
  77. package/src/platforms/vrp/index.ts +74 -50
  78. package/src/platforms/vrp/render.ts +67 -56
  79. package/src/project/project.ts +725 -0
  80. package/src/studio/app/env.d.ts +9 -0
  81. package/src/studio/app/index.html +18 -0
  82. package/src/studio/app/src/App.vue +95 -0
  83. package/src/studio/app/src/components/AddressesView.vue +38 -0
  84. package/src/studio/app/src/components/CablesView.vue +29 -0
  85. package/src/studio/app/src/components/CompareView.vue +160 -0
  86. package/src/studio/app/src/components/DataTable.vue +49 -0
  87. package/src/studio/app/src/components/DeviceView.vue +237 -0
  88. package/src/studio/app/src/components/DevicesView.vue +32 -0
  89. package/src/studio/app/src/components/Faceplate.vue +75 -0
  90. package/src/studio/app/src/components/GraphView.vue +282 -0
  91. package/src/studio/app/src/components/LineDiff.vue +24 -0
  92. package/src/studio/app/src/components/ProblemsView.vue +42 -0
  93. package/src/studio/app/src/components/SearchView.vue +72 -0
  94. package/src/studio/app/src/components/SecretsView.vue +112 -0
  95. package/src/studio/app/src/components/SessionsView.vue +35 -0
  96. package/src/studio/app/src/components/Settings.vue +95 -0
  97. package/src/studio/app/src/components/ToolsView.vue +118 -0
  98. package/src/studio/app/src/components/VlansView.vue +50 -0
  99. package/src/studio/app/src/lib/csv.ts +19 -0
  100. package/src/studio/{public/diff.js → app/src/lib/diff.ts} +40 -35
  101. package/src/studio/app/src/lib/format.ts +38 -0
  102. package/src/studio/app/src/lib/net.ts +68 -0
  103. package/src/studio/app/src/lib/secrets-crypto.ts +35 -0
  104. package/src/studio/app/src/lib/store.ts +71 -0
  105. package/src/studio/app/src/lib/tools.ts +217 -0
  106. package/src/studio/app/src/main.ts +9 -0
  107. package/src/studio/{public → app/src}/style.css +14 -4
  108. package/src/studio/app/tsconfig.json +17 -0
  109. package/src/studio/app/vite.config.ts +16 -0
  110. package/src/studio/assets.generated.ts +3 -0
  111. package/src/studio/data.ts +96 -188
  112. package/src/studio/git.ts +27 -43
  113. package/src/studio/server.ts +196 -107
  114. package/src/studio/types.ts +141 -0
  115. package/src/transport/console.ts +3 -3
  116. package/src/transport/ssh.ts +1 -1
  117. package/dist/chunks/index-3b343xva.js +0 -177
  118. package/dist/chunks/index-6wb41q67.js +0 -62
  119. package/dist/chunks/index-n7w3tg5y.js +0 -149
  120. package/dist/chunks/index-yzw86rc8.js +0 -921
  121. package/dist/chunks/new-w3zys5y4.js +0 -172
  122. package/dist/chunks/server-cxr9ynxc.js +0 -259
  123. package/dist/cli/index.js +0 -12922
  124. package/dist/helpers/index.js +0 -881
  125. package/dist/index.js +0 -28
  126. package/dist/types/core/address-space.d.ts +0 -62
  127. package/dist/types/core/address.d.ts +0 -44
  128. package/dist/types/core/cabling.d.ts +0 -41
  129. package/dist/types/core/cross-checks.d.ts +0 -28
  130. package/dist/types/core/define.d.ts +0 -79
  131. package/dist/types/core/project-files.d.ts +0 -12
  132. package/dist/types/core/schema-docs.d.ts +0 -25
  133. package/dist/types/core/secrets.d.ts +0 -62
  134. package/dist/types/core/topology.d.ts +0 -96
  135. package/dist/types/core/utils.d.ts +0 -10
  136. package/dist/types/core/validator.d.ts +0 -23
  137. package/dist/types/helpers/bgp.d.ts +0 -195
  138. package/dist/types/helpers/cables.d.ts +0 -69
  139. package/dist/types/helpers/checks.d.ts +0 -36
  140. package/dist/types/helpers/exchange.d.ts +0 -55
  141. package/dist/types/helpers/filters.d.ts +0 -51
  142. package/dist/types/helpers/index.d.ts +0 -14
  143. package/dist/types/helpers/route-server.d.ts +0 -52
  144. package/dist/types/helpers/upstreams.d.ts +0 -192
  145. package/dist/types/index.d.ts +0 -10
  146. package/dist/types/integrations/librenms.d.ts +0 -60
  147. package/dist/types/integrations/netbox.d.ts +0 -89
  148. package/dist/types/integrations/peeringdb.d.ts +0 -24
  149. package/dist/types/integrations/prometheus.d.ts +0 -24
  150. package/dist/types/integrations/registries.d.ts +0 -8
  151. package/dist/types/platforms/bgp-wait.d.ts +0 -24
  152. package/dist/types/platforms/catalog.d.ts +0 -109
  153. package/dist/types/platforms/derive.d.ts +0 -17
  154. package/dist/types/platforms/fortios/firewall.d.ts +0 -47
  155. package/dist/types/platforms/fortios/index.d.ts +0 -140
  156. package/dist/types/platforms/fortios/models.d.ts +0 -14
  157. package/dist/types/platforms/fortios/names.d.ts +0 -52
  158. package/dist/types/platforms/fortios/plan.d.ts +0 -114
  159. package/dist/types/platforms/fortios/render.d.ts +0 -77
  160. package/dist/types/platforms/fortios/routing.d.ts +0 -56
  161. package/dist/types/platforms/fortios/tree.d.ts +0 -37
  162. package/dist/types/platforms/fortios/vdoms.d.ts +0 -50
  163. package/dist/types/platforms/index.d.ts +0 -13
  164. package/dist/types/platforms/junos/bgp.d.ts +0 -44
  165. package/dist/types/platforms/junos/index.d.ts +0 -114
  166. package/dist/types/platforms/junos/models.d.ts +0 -14
  167. package/dist/types/platforms/junos/render.d.ts +0 -154
  168. package/dist/types/platforms/line-diff.d.ts +0 -67
  169. package/dist/types/platforms/platform.d.ts +0 -135
  170. package/dist/types/platforms/raisecom-ros/index.d.ts +0 -71
  171. package/dist/types/platforms/raisecom-ros/models.d.ts +0 -14
  172. package/dist/types/platforms/raisecom-ros/render.d.ts +0 -41
  173. package/dist/types/platforms/routeros/export.d.ts +0 -59
  174. package/dist/types/platforms/routeros/index.d.ts +0 -116
  175. package/dist/types/platforms/routeros/models.d.ts +0 -18
  176. package/dist/types/platforms/routeros/plan.d.ts +0 -58
  177. package/dist/types/platforms/routeros/render.d.ts +0 -250
  178. package/dist/types/platforms/vrp/index.d.ts +0 -83
  179. package/dist/types/platforms/vrp/models.d.ts +0 -12
  180. package/dist/types/platforms/vrp/render.d.ts +0 -96
  181. package/dist/types/schema/common.d.ts +0 -30
  182. package/dist/types/schema/device.d.ts +0 -126
  183. package/dist/types/schema/firewall.d.ts +0 -131
  184. package/dist/types/schema/index.d.ts +0 -8
  185. package/dist/types/schema/interfaces.d.ts +0 -205
  186. package/dist/types/schema/policy.d.ts +0 -107
  187. package/dist/types/schema/routing.d.ts +0 -161
  188. package/dist/types/schema/system.d.ts +0 -198
  189. package/dist/types/schema/time-zones.d.ts +0 -2
  190. package/dist/types/transport/console.d.ts +0 -115
  191. package/dist/types/transport/path-check.d.ts +0 -32
  192. package/dist/types/transport/ssh.d.ts +0 -27
  193. package/src/cli/graph-format.ts +0 -91
  194. package/src/cli/new.ts +0 -234
  195. package/src/core/address-space.ts +0 -320
  196. package/src/core/cross-checks.ts +0 -369
  197. package/src/core/define.ts +0 -124
  198. package/src/core/project-files.ts +0 -35
  199. package/src/core/schema-docs.ts +0 -174
  200. package/src/core/validator.ts +0 -508
  201. package/src/helpers/bgp.ts +0 -473
  202. package/src/helpers/cables.ts +0 -74
  203. package/src/helpers/checks.ts +0 -201
  204. package/src/helpers/exchange.ts +0 -123
  205. package/src/helpers/filters.ts +0 -107
  206. package/src/helpers/index.ts +0 -25
  207. package/src/helpers/route-server.ts +0 -212
  208. package/src/helpers/upstreams.ts +0 -369
  209. package/src/index.ts +0 -13
  210. package/src/integrations/librenms.ts +0 -291
  211. package/src/integrations/netbox.ts +0 -620
  212. package/src/integrations/peeringdb.ts +0 -173
  213. package/src/integrations/prometheus.ts +0 -90
  214. package/src/schema/common.ts +0 -41
  215. package/src/schema/device.ts +0 -145
  216. package/src/schema/firewall.ts +0 -140
  217. package/src/schema/index.ts +0 -8
  218. package/src/schema/policy.ts +0 -115
  219. package/src/schema/routing.ts +0 -173
  220. package/src/schema/system.ts +0 -203
  221. package/src/studio/public/app.js +0 -186
  222. package/src/studio/public/compare.js +0 -250
  223. package/src/studio/public/dom.js +0 -138
  224. package/src/studio/public/evaluate.js +0 -687
  225. package/src/studio/public/export.js +0 -130
  226. package/src/studio/public/graph.js +0 -1541
  227. package/src/studio/public/index.html +0 -83
  228. package/src/studio/public/net.js +0 -134
  229. package/src/studio/public/tables.js +0 -1289
  230. package/src/studio/public/tools.js +0 -610
  231. package/templates/colocation/checks.ts +0 -53
  232. package/templates/colocation/circuit.config.ts +0 -9
  233. package/templates/colocation/edge-01.ts +0 -133
  234. package/templates/colocation/routing.ts +0 -13
  235. package/templates/colocation/tenancy.ts +0 -76
  236. package/templates/colocation/tenants/acme.ts +0 -19
  237. package/templates/colocation/tenants/index.ts +0 -6
  238. package/templates/colocation/tor-01.ts +0 -33
  239. package/templates/edge-router/checks.ts +0 -20
  240. package/templates/edge-router/circuit.config.ts +0 -8
  241. package/templates/edge-router/edge-01.ts +0 -110
  242. package/templates/edge-router/routing.ts +0 -13
  243. package/templates/project/AGENTS.md +0 -51
  244. package/templates/project/CLAUDE.md +0 -1
  245. package/templates/project/README.md +0 -16
  246. package/templates/project/gitignore +0 -5
  247. package/templates/project/tsconfig.json +0 -13
  248. package/templates/single-site/checks.ts +0 -26
  249. package/templates/single-site/circuit.config.ts +0 -8
  250. package/templates/single-site/router.ts +0 -86
  251. package/templates/single-site/site.ts +0 -18
  252. package/templates/single-site/switch.ts +0 -34
  253. /package/src/studio/{public → app/public}/favicon.svg +0 -0
  254. /package/src/studio/{public → app/public}/special-gothic-latin-ext.woff2 +0 -0
  255. /package/src/studio/{public → app/public}/special-gothic-latin.woff2 +0 -0
  256. /package/src/studio/{public → app/public}/special-gothic-license.txt +0 -0
@@ -0,0 +1,434 @@
1
+ // Every key Circuit takes, at run time, so `circuit validate` stops on one it doesn't know. One source: the types. Each shape here is typed
2
+ // `ShapeOf<the type>`, which needs every key of the type and allows no other, so a key added to, renamed in or removed from a type
3
+ // fails `tsc` until its shape says the same. The two can't drift.
4
+
5
+ import type { CommunityScheme } from "./communities";
6
+ import type { AnyDevice } from "./device";
7
+ import type { FilterRule, NatRule } from "./firewall";
8
+ import type { Checks, Integrations, NetworkSecrets, NetworkSettings, Prefixes, Site } from "./network";
9
+ import type { CallRule, PolicyRule, RouteMatch, RouteRule, RouteSet, RoutingRule } from "./routing";
10
+ import type { CommunitiesExport, CommunitiesImport } from "./communities";
11
+ import type { AclRule } from "./switching";
12
+
13
+ /**
14
+ * Where keys may be: `0` holds none Circuit checks (a number, a name, a list of names); a map lists its keys, each with its own shape;
15
+ * `{ "*": shape }` takes any key, such as a VLAN's name; `[shape]` is a list of that item.
16
+ */
17
+ export type Shape = 0 | readonly [Shape] | { readonly [key: string]: Shape };
18
+
19
+ type Prim = string | number | boolean | bigint | symbol | null | undefined;
20
+ type AllKeys<U> = U extends unknown ? keyof U : never;
21
+ type ValueAt<U, K> = U extends unknown ? (K extends keyof U ? U[K] : never) : never;
22
+ type Item<T> = Extract<T, readonly unknown[]> extends readonly (infer E)[] ? E : never;
23
+
24
+ /** A map with any key (an index signature, such as VLANs by name), or one with keys written out. A key typed `never` is no key. */
25
+ type ObjectShape<O> =
26
+ {} extends Required<O>
27
+ ? { readonly "*": ShapeOf<O[keyof O]> }
28
+ : { readonly [K in AllKeys<O> as [NonNullable<ValueAt<O, K>>] extends [never] ? never : K]-?: ShapeOf<ValueAt<O, K>> };
29
+
30
+ /** The shape of type `T`: what each shape here must be, exactly. A list of plain values is `0`, as a plain value. */
31
+ export type ShapeOf<T> = [Exclude<T, Prim>] extends [never]
32
+ ? 0
33
+ : [Exclude<T, Prim | readonly unknown[]>] extends [never]
34
+ ? ShapeOf<Item<T>> extends 0
35
+ ? 0
36
+ : readonly [ShapeOf<Item<T>>]
37
+ : ObjectShape<Exclude<T, Prim | readonly unknown[]>>;
38
+
39
+ const SECRET = { secret: 0, secret_file: 0 } as const;
40
+ const RANGE = { min: 0, max: 0 } as const;
41
+
42
+ // ---------- rules, each kind on its own ----------
43
+
44
+ export const FILTER_MATCH = {
45
+ family: 0,
46
+ protocol: 0,
47
+ src: 0,
48
+ dst: 0,
49
+ src_address_set: 0,
50
+ dst_address_set: 0,
51
+ src_port: RANGE,
52
+ dst_port: RANGE,
53
+ in_interface: 0,
54
+ out_interface: 0,
55
+ in_interface_set: 0,
56
+ out_interface_set: 0,
57
+ connection_state: 0,
58
+ tcp_flags: { set: 0, unset: 0 },
59
+ } as const;
60
+
61
+ export const FILTER_RULE = { description: 0, if: FILTER_MATCH, then: 0, fast_path: 0 } as const satisfies ShapeOf<
62
+ FilterRule<string, string, string>
63
+ >;
64
+ export const NAT_RULE = { description: 0, if: FILTER_MATCH, then: 0, to_address: 0, to_port: 0 } as const satisfies ShapeOf<
65
+ NatRule<string, string, string>
66
+ >;
67
+ export const ACL_RULE = {
68
+ description: 0,
69
+ if: { family: 0, protocol: 0, src: 0, dst: 0, src_port: RANGE, dst_port: RANGE, vlan: 0 },
70
+ then: 0,
71
+ rate: 0,
72
+ } as const satisfies ShapeOf<AclRule<string>>;
73
+ export const ROUTING_RULE = {
74
+ description: 0,
75
+ match: { src: 0, dst: 0, in_interface: 0 },
76
+ routing_table: 0,
77
+ fallback: 0,
78
+ } as const satisfies ShapeOf<RoutingRule<string, string>>;
79
+
80
+ export const ROUTE_MATCH = {
81
+ family: 0,
82
+ prefix: 0,
83
+ prefix_set: 0,
84
+ prefix_length: RANGE,
85
+ as_path_length: RANGE,
86
+ as_path_contains: 0,
87
+ rpki: 0,
88
+ standard_community: 0,
89
+ large_community: 0,
90
+ } as const satisfies ShapeOf<RouteMatch<string>>;
91
+ export const ROUTE_SET = {
92
+ local_pref: 0,
93
+ med: 0,
94
+ prepend: 0,
95
+ standard_communities: 0,
96
+ add_standard_communities: 0,
97
+ remove_standard_communities: 0,
98
+ large_communities: 0,
99
+ add_large_communities: 0,
100
+ remove_large_communities: 0,
101
+ next_hop: 0,
102
+ preferred_source: 0,
103
+ blackhole: 0,
104
+ } as const satisfies ShapeOf<RouteSet>;
105
+ /** A route policy rule of each kind: on routes, a call, a communities import, a communities export. */
106
+ export const ROUTE_RULE = { description: 0, if: ROUTE_MATCH, set: ROUTE_SET, then: 0 } as const satisfies ShapeOf<RouteRule<string>>;
107
+ export const CALL_RULE = { call: 0 } as const satisfies ShapeOf<CallRule<string>>;
108
+ export const COMMUNITIES_IMPORT = { communities: 0, accept_requests: 0, add_tags: 0 } as const satisfies ShapeOf<CommunitiesImport<string>>;
109
+ export const COMMUNITIES_EXPORT = { communities: 0, keep_ours: 0, send_tags: 0 } as const satisfies ShapeOf<CommunitiesExport>;
110
+ export const POLICY_RULE = { ...ROUTE_RULE, ...CALL_RULE, ...COMMUNITIES_IMPORT, ...COMMUNITIES_EXPORT } as const satisfies ShapeOf<
111
+ PolicyRule<string, string>
112
+ >;
113
+
114
+ /** What `then` may be in each kind of rule. */
115
+ export const ACTIONS = {
116
+ filter: ["accept", "drop", "reject"],
117
+ nat: ["masquerade", "snat", "dnat"],
118
+ acl: ["accept", "drop"],
119
+ policy: ["accept", "reject"],
120
+ } as const;
121
+
122
+ type Same<A, B> = [A] extends [B] ? ([B] extends [A] ? true : false) : false;
123
+ // Each list is exactly what its type's `then` takes: `tsc` fails here otherwise.
124
+ true satisfies Same<(typeof ACTIONS.filter)[number], FilterRule<string, string, string>["then"]> &
125
+ Same<(typeof ACTIONS.nat)[number], NatRule<string, string, string>["then"]> &
126
+ Same<(typeof ACTIONS.acl)[number], AclRule<string>["then"]> &
127
+ Same<(typeof ACTIONS.policy)[number], NonNullable<RouteRule<string>["then"]>>;
128
+
129
+ // ---------- a device ----------
130
+
131
+ const SERVICE = { allow: 0, port: 0 } as const;
132
+ const STATIC_ROUTE = {
133
+ prefix: 0,
134
+ next_hop: 0,
135
+ interface: 0,
136
+ blackhole: 0,
137
+ bfd: 0,
138
+ distance: 0,
139
+ vrf: 0,
140
+ routing_table: 0,
141
+ description: 0,
142
+ } as const;
143
+ const ROUTED = {
144
+ addresses: [{ address: 0, peer_address: 0 }],
145
+ ipv6_ra: 0,
146
+ hardware_offload: 0,
147
+ vrrp: [{ id: 0, address: 0, priority: 0, preempt: 0, interval: 0 }],
148
+ vrf: 0,
149
+ exchange: { name: 0, speed: 0, peeringdb_ixlan: 0 },
150
+ } as const;
151
+ const THRESHOLD = { pps: 0, percent: 0 } as const;
152
+ const SWITCHED = {
153
+ access_vlan: 0,
154
+ trunk_vlans: 0,
155
+ native_vlan: 0,
156
+ stp: { edge: 0, cost: 0, priority: 0 },
157
+ storm_control: { broadcast: THRESHOLD, multicast: THRESHOLD, unknown_unicast: THRESHOLD },
158
+ mac_limit: 0,
159
+ ethertypes: 0,
160
+ } as const;
161
+ const BASE = { ...ROUTED, description: 0, mtu: 0 } as const;
162
+ const NEIGHBOR_SETTINGS = {
163
+ remote_as: 0,
164
+ description: 0,
165
+ local_address: 0,
166
+ import_policy: 0,
167
+ export_policy: 0,
168
+ role: 0,
169
+ multihop: 0,
170
+ password: SECRET,
171
+ max_prefixes: 0,
172
+ hold_time: 0,
173
+ keepalive: 0,
174
+ address_families: 0,
175
+ passive: 0,
176
+ bfd: 0,
177
+ routing_table: 0,
178
+ communities: { groups: 0, blackhole: 0 },
179
+ } as const;
180
+ const CHAIN = { default_action: 0, rules: [FILTER_RULE] } as const;
181
+
182
+ /** Every key a device may have, its parts merged in. */
183
+ export const DEVICE = {
184
+ name: 0,
185
+ site: 0,
186
+ role: 0,
187
+ platform: 0,
188
+ model: 0,
189
+ connection: {
190
+ host: 0,
191
+ user: 0,
192
+ port: 0,
193
+ console: { jump: { host: 0, user: 0, password: SECRET, port: 0, cloudflared: 0 }, port: 0, baud: 0, password_only: 0 },
194
+ },
195
+ system: {
196
+ timezone: 0,
197
+ banner: 0,
198
+ release_channel: 0,
199
+ ip_stack: { icmp_redirects: 0, syn_cookies: 0 },
200
+ alarms: { "*": { severity: 0 } },
201
+ dns: { servers: 0 },
202
+ ntp: { servers: 0 },
203
+ logging: { local: { level: 0, files: 0, lines_per_file: 0 } },
204
+ hardware_offload: 0,
205
+ users: { "*": { role: 0, password: SECRET, description: 0, ssh_keys: 0 } },
206
+ certificates: { "*": { certificate_file: 0, private_key: SECRET } },
207
+ // A VDOM's routes have no VRF.
208
+ vdoms: {
209
+ management: {
210
+ name: 0,
211
+ interfaces: 0,
212
+ static_routes: [
213
+ { prefix: 0, next_hop: 0, interface: 0, blackhole: 0, bfd: 0, distance: 0, routing_table: 0, description: 0 },
214
+ ],
215
+ },
216
+ },
217
+ },
218
+ management: {
219
+ allow: 0,
220
+ interfaces: 0,
221
+ ssh: { ...SERVICE, weak_crypto: 0, auth_timeout: 0, auth_retries: 0 },
222
+ telnet: SERVICE,
223
+ http: SERVICE,
224
+ https: { ...SERVICE, certificate: 0 },
225
+ api: SERVICE,
226
+ api_tls: { ...SERVICE, certificate: 0 },
227
+ ftp: SERVICE,
228
+ native_protocol: { ...SERVICE, interfaces: 0 },
229
+ console: { password: SECRET },
230
+ privilege_password: SECRET,
231
+ },
232
+ monitoring: {
233
+ snmp: {
234
+ ...SERVICE,
235
+ community: SECRET,
236
+ users: { "*": { auth: 0, auth_password: SECRET, privacy: 0, privacy_password: SECRET } },
237
+ contact: 0,
238
+ location: 0,
239
+ },
240
+ syslog: [{ address: 0, port: 0, level: 0 }],
241
+ flow_export: { protocol: 0, collectors: [{ address: 0, port: 0 }], sampling: 0, interfaces: 0 },
242
+ transceivers: 0,
243
+ },
244
+ interfaces: {
245
+ ethernet: {
246
+ "*": {
247
+ ...ROUTED,
248
+ ...SWITCHED,
249
+ description: 0,
250
+ mtu: 0,
251
+ speed: 0,
252
+ lldp: 0,
253
+ ingress_acl: 0,
254
+ in_lag: 0,
255
+ link: { device: 0, port: 0, through: 0 },
256
+ },
257
+ },
258
+ lag: { "*": { ...BASE, ...SWITCHED, mode: 0, id: 0 } },
259
+ vlan_interface: { "*": { ...BASE, vlan: 0, mac: 0 } },
260
+ bridge: { "*": { ...BASE, member_interfaces: 0 } },
261
+ loopback: { "*": BASE },
262
+ gre: { "*": { ...BASE, local_address: 0, remote_address: 0 } },
263
+ vxlan: { "*": { ...BASE, vni: 0, local_address: 0, remote_address: 0, port: 0, mac: 0 } },
264
+ wireguard: {
265
+ "*": {
266
+ ...BASE,
267
+ listen_port: 0,
268
+ endpoint: 0,
269
+ private_key: SECRET,
270
+ peers: [{ name: 0, public_key: 0, allowed_addresses: 0, endpoint: 0, keepalive: 0, client_allowed_addresses: 0 }],
271
+ },
272
+ },
273
+ },
274
+ switching: {
275
+ vlans: { "*": { id: 0, description: 0, untrusted: 0 } },
276
+ stp: { mode: 0, priority: 0 },
277
+ lldp: { interfaces: 0 },
278
+ acls: { "*": [ACL_RULE] },
279
+ },
280
+ services: {
281
+ dhcp_server: {
282
+ "*": {
283
+ interface: 0,
284
+ network: 0,
285
+ gateway: 0,
286
+ dns: 0,
287
+ pool: 0,
288
+ lease_time: 0,
289
+ reservations: [{ address: 0, mac: 0, description: 0 }],
290
+ },
291
+ },
292
+ dhcp_relay: { "*": { interface: 0, servers: 0 } },
293
+ dns_server: 0,
294
+ ntp_server: 0,
295
+ },
296
+ routing: {
297
+ router_id: 0,
298
+ tables: { "*": { description: 0 } },
299
+ vrfs: { "*": { description: 0 } },
300
+ policy_routing: [ROUTING_RULE],
301
+ static_routes: [STATIC_ROUTE],
302
+ bgp: {
303
+ asn: 0,
304
+ originated_prefixes: 0,
305
+ neighbor_groups: { "*": NEIGHBOR_SETTINGS },
306
+ neighbors: { "*": { ...NEIGHBOR_SETTINGS, address: 0, neighbor_group: 0 } },
307
+ },
308
+ ospf: {
309
+ address_families: 0,
310
+ areas: { "*": { interfaces: { "*": { cost: 0, passive: 0, network: 0, bfd: 0 } } } },
311
+ redistribute: 0,
312
+ },
313
+ bfd: { interval: 0, multiplier: 0 },
314
+ rpki: [{ address: 0, port: 0, description: 0 }],
315
+ policies: { "*": [POLICY_RULE] },
316
+ prefix_sets: { "*": { prefixes: { "*": 0 }, source: { registry: 0, query: 0, family: 0 } } },
317
+ },
318
+ firewall: {
319
+ address_sets: { "*": 0 },
320
+ interface_sets: { "*": 0 },
321
+ filter: { input: CHAIN, forward: CHAIN, output: CHAIN },
322
+ nat: { source: [NAT_RULE], destination: [NAT_RULE] },
323
+ conntrack_helpers: 0,
324
+ untracked: 0,
325
+ },
326
+ } as const satisfies ShapeOf<AnyDevice>;
327
+
328
+ // ---------- the network ----------
329
+
330
+ const OPTION = { code: 0, description: 0 } as const;
331
+ /** Every option of a community scheme. */
332
+ export const COMMUNITIES = {
333
+ asn: 0,
334
+ groups: { "*": { value: 0, description: 0 } },
335
+ learned_from_group: OPTION,
336
+ learned_at_site: OPTION,
337
+ learned_from_as: OPTION,
338
+ do_not_announce_to_as: OPTION,
339
+ do_not_announce_to_group: OPTION,
340
+ prepend: { once: OPTION, twice: OPTION, three_times: OPTION },
341
+ blackhole: OPTION,
342
+ custom_descriptions: { "*": 0 },
343
+ } as const satisfies ShapeOf<CommunityScheme>;
344
+
345
+ /** Circuit's built-in checks, each with its settings. Any other name in `checks` is one of the project's own. */
346
+ export const CHECKS = {
347
+ untrusted_vlans: { filtering_devices: 0, mixed_links: 0 },
348
+ exports_end_in_reject: 0,
349
+ imports_end_in_accept_or_reject: 0,
350
+ native_vlans_are_dead_ends: 0,
351
+ tunnels_outside_offered: 0,
352
+ same_vlan_ids: 0,
353
+ } as const satisfies ShapeOf<Checks<string>>;
354
+
355
+ /** Every key `circuit()` takes. `checks` is looked at apart: a project's own check has a name of its own. */
356
+ export const NETWORK = {
357
+ asn: 0,
358
+ prefixes: { "*": { "*": 0 } } satisfies ShapeOf<Prefixes>,
359
+ communities: COMMUNITIES,
360
+ sites: { "*": { description: 0, number: 0 } satisfies ShapeOf<Site> },
361
+ checks: 0,
362
+ integrations: {
363
+ peeringdb: { api_key: SECRET },
364
+ librenms: { url: 0, api_token: SECRET },
365
+ netbox: { url: 0, api_token: SECRET },
366
+ } satisfies ShapeOf<Integrations>,
367
+ secrets: { file: 0 } satisfies ShapeOf<NetworkSecrets>,
368
+ } as const satisfies { readonly [K in keyof NetworkSettings<{}, number>]-?: Shape };
369
+
370
+ // `satisfies` misses a key too many that comes from a spread or another constant. Each side assignable to the other doesn't: every key
371
+ // of a shape is required, so a shape with one more key, at any depth, is not the type's. `tsc` fails here then.
372
+ true satisfies Same<typeof DEVICE, ShapeOf<AnyDevice>> &
373
+ Same<typeof FILTER_RULE, ShapeOf<FilterRule<string, string, string>>> &
374
+ Same<typeof NAT_RULE, ShapeOf<NatRule<string, string, string>>> &
375
+ Same<typeof ACL_RULE, ShapeOf<AclRule<string>>> &
376
+ Same<typeof ROUTING_RULE, ShapeOf<RoutingRule<string, string>>> &
377
+ Same<typeof ROUTE_RULE, ShapeOf<RouteRule<string>>> &
378
+ Same<typeof CALL_RULE, ShapeOf<CallRule<string>>> &
379
+ Same<typeof COMMUNITIES_IMPORT, ShapeOf<CommunitiesImport<string>>> &
380
+ Same<typeof COMMUNITIES_EXPORT, ShapeOf<CommunitiesExport>> &
381
+ Same<typeof COMMUNITIES, ShapeOf<CommunityScheme>> &
382
+ Same<typeof CHECKS, ShapeOf<Checks<string>>> &
383
+ Same<typeof NETWORK.integrations, ShapeOf<Integrations>> &
384
+ Same<typeof NETWORK.sites, ShapeOf<Readonly<Record<string, Site>>>> &
385
+ Same<typeof NETWORK.prefixes, ShapeOf<Prefixes>> &
386
+ Same<keyof typeof NETWORK, keyof NetworkSettings<{}, number>>;
387
+
388
+ // ---------- the check ----------
389
+
390
+ /** A key Circuit doesn't take: where it is, dot by dot (`firewall.filter.input.rules[3].if`), the key, and the keys taken there. */
391
+ export type UnknownKey = { path: string; key: string; known: string[] };
392
+
393
+ /**
394
+ * Every key in `value` that `shape` doesn't take. A list met where the shape is a map is a list of such maps (a port range, or several).
395
+ * A plain value met where the shape is a map is one the type allows beside it, such as an address written alone.
396
+ */
397
+ export function unknownKeys(value: unknown, shape: Shape, path = ""): UnknownKey[] {
398
+ if (shape === 0 || !value || typeof value !== "object") return [];
399
+ if (Array.isArray(value)) {
400
+ const item: Shape = Array.isArray(shape) ? (shape as readonly [Shape])[0] : shape;
401
+ return value.flatMap((v, i) => unknownKeys(v, item, `${path}[${i}]`));
402
+ }
403
+ if (Array.isArray(shape)) return [];
404
+ const map = shape as { readonly [key: string]: Shape };
405
+ const at = (key: string) => (path ? `${path}.${key}` : key);
406
+ if (Object.hasOwn(map, "*")) return Object.entries(value).flatMap(([k, v]) => unknownKeys(v, map["*"]!, at(k)));
407
+ return Object.entries(value).flatMap(([k, v]) =>
408
+ Object.hasOwn(map, k) ? unknownKeys(v, map[k]!, at(k)) : [{ path, key: k, known: Object.keys(map) }]
409
+ );
410
+ }
411
+
412
+ /** The closest of `names`, for "Did you mean": at most two letters off, or a quarter of a long name. */
413
+ export function closest(name: string, names: Iterable<string>): string | undefined {
414
+ const distance = (a: string, b: string) => {
415
+ let row = Array.from({ length: b.length + 1 }, (_, i) => i);
416
+ for (let i = 1; i <= a.length; i++) {
417
+ const next = [i];
418
+ for (let j = 1; j <= b.length; j++)
419
+ next[j] = Math.min(row[j]! + 1, next[j - 1]! + 1, row[j - 1]! + (a[i - 1] === b[j - 1] ? 0 : 1));
420
+ row = next;
421
+ }
422
+ return row[b.length]!;
423
+ };
424
+ let best: [string, number] | undefined;
425
+ for (const n of names)
426
+ if (distance(name, n) <= Math.max(2, name.length / 4) && (!best || distance(name, n) < best[1])) best = [n, distance(name, n)];
427
+ return best?.[0];
428
+ }
429
+
430
+ /** What to say about an unknown key: "Did you mean" when one is close, else the keys taken there. */
431
+ export function unknownKeyHelp({ key, known }: UnknownKey): string {
432
+ const hint = closest(key, known);
433
+ return hint ? `Did you mean ${hint}?` : `It takes: ${known.join(", ")}.`;
434
+ }
@@ -0,0 +1,61 @@
1
+ // How people and tools log in: `management.ssh`, `.https`, `.api`, `.native_protocol`, `.console`, and the rest. SNMP polling is `monitoring.snmp`.
2
+
3
+ import type { Prefix, Secret } from "./common";
4
+
5
+ /** A way into the device. Absent means turned off. */
6
+ export type Service = {
7
+ /** Sources allowed. `management.allow` when absent. */
8
+ allow?: readonly Prefix[];
9
+ /** TCP or UDP port. The protocol's own when absent. */
10
+ port?: number;
11
+ };
12
+
13
+ /** How the device is reached and managed. `I` is the device's ports and interfaces, `C` its certificates, `AS` its address sets. */
14
+ export type Management<I extends string, C extends string, AS extends string = string> = {
15
+ /**
16
+ * Sources allowed to every service that does not set its own: a list, or one of `firewall.address_sets` by name, so a device that
17
+ * filters on those sources too writes them once. Circuit writes the set's addresses here.
18
+ */
19
+ allow?: readonly Prefix[] | AS;
20
+ /** Some of the device's ports and interfaces: the ones the services answer on; every other one answers ping alone. Every one with an address when absent. */
21
+ interfaces?: readonly I[];
22
+ /** Secure shell. */
23
+ ssh?: Service & {
24
+ /** Also offer legacy ciphers, MACs and key exchanges. Off when absent. */
25
+ weak_crypto?: boolean;
26
+ /** Seconds a login may take. */
27
+ auth_timeout?: number;
28
+ /** Failed attempts allowed per connection. */
29
+ auth_retries?: number;
30
+ };
31
+ /** Unencrypted remote login. */
32
+ telnet?: Service;
33
+ /** Web interface, unencrypted. */
34
+ http?: Service;
35
+ /** Web interface over TLS. */
36
+ https?: Service & {
37
+ /** One of `system.certificates`. */
38
+ certificate?: C;
39
+ };
40
+ /** The platform's own API, unencrypted. */
41
+ api?: Service;
42
+ /** The platform's own API over TLS. */
43
+ api_tls?: Service & {
44
+ /** One of `system.certificates`. */
45
+ certificate?: C;
46
+ };
47
+ /** File transfer, unencrypted. */
48
+ ftp?: Service;
49
+ /** The platform's own management protocol, such as Winbox. `interfaces` also allows it at layer 2 on those interfaces. */
50
+ native_protocol?: Service & {
51
+ /** Some of the device's ports and interfaces. */
52
+ interfaces?: readonly I[];
53
+ };
54
+ /** Console login uses this password. The device's users when absent. */
55
+ console?: {
56
+ /** The console password. */
57
+ password: Secret;
58
+ };
59
+ /** Password for privileged mode, on platforms that have one. */
60
+ privilege_password?: Secret;
61
+ };