@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.
- package/README.md +115 -37
- package/package.json +21 -58
- package/src/checks/across.ts +402 -0
- package/src/checks/built-in.ts +256 -0
- package/src/checks/device.ts +436 -0
- package/src/checks/index.ts +53 -0
- package/src/cli/advisories.ts +108 -0
- package/src/cli/app.ts +71 -0
- package/src/cli/editor-types.generated.ts +30 -0
- package/src/cli/editor-types.ts +69 -0
- package/src/cli/format.ts +124 -0
- package/src/cli/git-hooks.ts +79 -0
- package/src/cli/index.ts +350 -1170
- package/src/cli/migrate.ts +554 -0
- package/src/cli/project-commands.ts +656 -0
- package/src/cli/releases.ts +374 -0
- package/src/cli/remember.ts +339 -0
- package/src/cli/rename.ts +27 -33
- package/src/cli/secrets-password.ts +60 -0
- package/src/cli/secrets.ts +258 -0
- package/src/cli/setup.ts +300 -0
- package/src/cli/signing.ts +23 -0
- package/src/cli/text-editor.ts +268 -0
- package/src/cli/ui.ts +298 -0
- package/src/cli/version.ts +2 -0
- package/src/cli/versions.ts +158 -0
- package/src/config/address.ts +102 -0
- package/src/config/circuit.d.ts +30 -0
- package/src/config/common.ts +65 -0
- package/src/config/communities.ts +479 -0
- package/src/config/define.ts +684 -0
- package/src/config/device.ts +164 -0
- package/src/config/firewall.ts +150 -0
- package/src/config/index.ts +22 -0
- package/src/{schema → config}/interfaces.ts +121 -85
- package/src/config/keys.ts +434 -0
- package/src/config/management.ts +61 -0
- package/src/config/merge.ts +335 -0
- package/src/config/models.ts +70 -0
- package/src/config/monitoring.ts +64 -0
- package/src/config/network.ts +180 -0
- package/src/config/presets.ts +149 -0
- package/src/config/routing.ts +401 -0
- package/src/config/services.ts +51 -0
- package/src/config/switching.ts +84 -0
- package/src/config/system.ts +103 -0
- package/src/{schema → config}/time-zones.ts +1 -1
- package/src/core/address.ts +1 -1
- package/src/core/cabling.ts +8 -43
- package/src/core/graph.ts +40 -0
- package/src/{integrations → core}/registries.ts +5 -3
- package/src/core/secrets-file.ts +539 -0
- package/src/core/secrets.ts +83 -64
- package/src/core/topology.ts +103 -146
- package/src/core/utils.ts +12 -1
- package/src/model.ts +83 -0
- package/src/platforms/derive.ts +64 -37
- package/src/platforms/finding.ts +9 -0
- package/src/platforms/fortios/firewall.ts +76 -64
- package/src/platforms/fortios/index.ts +189 -125
- package/src/platforms/fortios/names.ts +51 -18
- package/src/platforms/fortios/render.ts +69 -62
- package/src/platforms/fortios/routing.ts +89 -58
- package/src/platforms/fortios/vdoms.ts +47 -16
- package/src/platforms/junos/TRAPS.md +2 -2
- package/src/platforms/junos/bgp.ts +63 -42
- package/src/platforms/junos/index.ts +122 -89
- package/src/platforms/junos/render.ts +139 -104
- package/src/platforms/platform.ts +10 -8
- package/src/platforms/raisecom-ros/TRAPS.md +1 -1
- package/src/platforms/raisecom-ros/index.ts +89 -70
- package/src/platforms/raisecom-ros/render.ts +49 -45
- package/src/platforms/routeros/TRAPS.md +3 -3
- package/src/platforms/routeros/index.ts +121 -60
- package/src/platforms/routeros/render.ts +214 -195
- package/src/platforms/vrp/TRAPS.md +1 -1
- package/src/platforms/vrp/index.ts +74 -50
- package/src/platforms/vrp/render.ts +67 -56
- package/src/project/project.ts +725 -0
- package/src/studio/app/env.d.ts +9 -0
- package/src/studio/app/index.html +18 -0
- package/src/studio/app/src/App.vue +95 -0
- package/src/studio/app/src/components/AddressesView.vue +38 -0
- package/src/studio/app/src/components/CablesView.vue +29 -0
- package/src/studio/app/src/components/CompareView.vue +160 -0
- package/src/studio/app/src/components/DataTable.vue +49 -0
- package/src/studio/app/src/components/DeviceView.vue +237 -0
- package/src/studio/app/src/components/DevicesView.vue +32 -0
- package/src/studio/app/src/components/Faceplate.vue +75 -0
- package/src/studio/app/src/components/GraphView.vue +282 -0
- package/src/studio/app/src/components/LineDiff.vue +24 -0
- package/src/studio/app/src/components/ProblemsView.vue +42 -0
- package/src/studio/app/src/components/SearchView.vue +72 -0
- package/src/studio/app/src/components/SecretsView.vue +112 -0
- package/src/studio/app/src/components/SessionsView.vue +35 -0
- package/src/studio/app/src/components/Settings.vue +95 -0
- package/src/studio/app/src/components/ToolsView.vue +118 -0
- package/src/studio/app/src/components/VlansView.vue +50 -0
- package/src/studio/app/src/lib/csv.ts +19 -0
- package/src/studio/{public/diff.js → app/src/lib/diff.ts} +40 -35
- package/src/studio/app/src/lib/format.ts +38 -0
- package/src/studio/app/src/lib/net.ts +68 -0
- package/src/studio/app/src/lib/secrets-crypto.ts +35 -0
- package/src/studio/app/src/lib/store.ts +71 -0
- package/src/studio/app/src/lib/tools.ts +217 -0
- package/src/studio/app/src/main.ts +9 -0
- package/src/studio/{public → app/src}/style.css +14 -4
- package/src/studio/app/tsconfig.json +17 -0
- package/src/studio/app/vite.config.ts +16 -0
- package/src/studio/assets.generated.ts +3 -0
- package/src/studio/data.ts +96 -188
- package/src/studio/git.ts +27 -43
- package/src/studio/server.ts +196 -107
- package/src/studio/types.ts +141 -0
- package/src/transport/console.ts +3 -3
- package/src/transport/ssh.ts +1 -1
- package/dist/chunks/index-3b343xva.js +0 -177
- package/dist/chunks/index-6wb41q67.js +0 -62
- package/dist/chunks/index-n7w3tg5y.js +0 -149
- package/dist/chunks/index-yzw86rc8.js +0 -921
- package/dist/chunks/new-w3zys5y4.js +0 -172
- package/dist/chunks/server-cxr9ynxc.js +0 -259
- package/dist/cli/index.js +0 -12922
- package/dist/helpers/index.js +0 -881
- package/dist/index.js +0 -28
- package/dist/types/core/address-space.d.ts +0 -62
- package/dist/types/core/address.d.ts +0 -44
- package/dist/types/core/cabling.d.ts +0 -41
- package/dist/types/core/cross-checks.d.ts +0 -28
- package/dist/types/core/define.d.ts +0 -79
- package/dist/types/core/project-files.d.ts +0 -12
- package/dist/types/core/schema-docs.d.ts +0 -25
- package/dist/types/core/secrets.d.ts +0 -62
- package/dist/types/core/topology.d.ts +0 -96
- package/dist/types/core/utils.d.ts +0 -10
- package/dist/types/core/validator.d.ts +0 -23
- package/dist/types/helpers/bgp.d.ts +0 -195
- package/dist/types/helpers/cables.d.ts +0 -69
- package/dist/types/helpers/checks.d.ts +0 -36
- package/dist/types/helpers/exchange.d.ts +0 -55
- package/dist/types/helpers/filters.d.ts +0 -51
- package/dist/types/helpers/index.d.ts +0 -14
- package/dist/types/helpers/route-server.d.ts +0 -52
- package/dist/types/helpers/upstreams.d.ts +0 -192
- package/dist/types/index.d.ts +0 -10
- package/dist/types/integrations/librenms.d.ts +0 -60
- package/dist/types/integrations/netbox.d.ts +0 -89
- package/dist/types/integrations/peeringdb.d.ts +0 -24
- package/dist/types/integrations/prometheus.d.ts +0 -24
- package/dist/types/integrations/registries.d.ts +0 -8
- package/dist/types/platforms/bgp-wait.d.ts +0 -24
- package/dist/types/platforms/catalog.d.ts +0 -109
- package/dist/types/platforms/derive.d.ts +0 -17
- package/dist/types/platforms/fortios/firewall.d.ts +0 -47
- package/dist/types/platforms/fortios/index.d.ts +0 -140
- package/dist/types/platforms/fortios/models.d.ts +0 -14
- package/dist/types/platforms/fortios/names.d.ts +0 -52
- package/dist/types/platforms/fortios/plan.d.ts +0 -114
- package/dist/types/platforms/fortios/render.d.ts +0 -77
- package/dist/types/platforms/fortios/routing.d.ts +0 -56
- package/dist/types/platforms/fortios/tree.d.ts +0 -37
- package/dist/types/platforms/fortios/vdoms.d.ts +0 -50
- package/dist/types/platforms/index.d.ts +0 -13
- package/dist/types/platforms/junos/bgp.d.ts +0 -44
- package/dist/types/platforms/junos/index.d.ts +0 -114
- package/dist/types/platforms/junos/models.d.ts +0 -14
- package/dist/types/platforms/junos/render.d.ts +0 -154
- package/dist/types/platforms/line-diff.d.ts +0 -67
- package/dist/types/platforms/platform.d.ts +0 -135
- package/dist/types/platforms/raisecom-ros/index.d.ts +0 -71
- package/dist/types/platforms/raisecom-ros/models.d.ts +0 -14
- package/dist/types/platforms/raisecom-ros/render.d.ts +0 -41
- package/dist/types/platforms/routeros/export.d.ts +0 -59
- package/dist/types/platforms/routeros/index.d.ts +0 -116
- package/dist/types/platforms/routeros/models.d.ts +0 -18
- package/dist/types/platforms/routeros/plan.d.ts +0 -58
- package/dist/types/platforms/routeros/render.d.ts +0 -250
- package/dist/types/platforms/vrp/index.d.ts +0 -83
- package/dist/types/platforms/vrp/models.d.ts +0 -12
- package/dist/types/platforms/vrp/render.d.ts +0 -96
- package/dist/types/schema/common.d.ts +0 -30
- package/dist/types/schema/device.d.ts +0 -126
- package/dist/types/schema/firewall.d.ts +0 -131
- package/dist/types/schema/index.d.ts +0 -8
- package/dist/types/schema/interfaces.d.ts +0 -205
- package/dist/types/schema/policy.d.ts +0 -107
- package/dist/types/schema/routing.d.ts +0 -161
- package/dist/types/schema/system.d.ts +0 -198
- package/dist/types/schema/time-zones.d.ts +0 -2
- package/dist/types/transport/console.d.ts +0 -115
- package/dist/types/transport/path-check.d.ts +0 -32
- package/dist/types/transport/ssh.d.ts +0 -27
- package/src/cli/graph-format.ts +0 -91
- package/src/cli/new.ts +0 -234
- package/src/core/address-space.ts +0 -320
- package/src/core/cross-checks.ts +0 -369
- package/src/core/define.ts +0 -124
- package/src/core/project-files.ts +0 -35
- package/src/core/schema-docs.ts +0 -174
- package/src/core/validator.ts +0 -508
- package/src/helpers/bgp.ts +0 -473
- package/src/helpers/cables.ts +0 -74
- package/src/helpers/checks.ts +0 -201
- package/src/helpers/exchange.ts +0 -123
- package/src/helpers/filters.ts +0 -107
- package/src/helpers/index.ts +0 -25
- package/src/helpers/route-server.ts +0 -212
- package/src/helpers/upstreams.ts +0 -369
- package/src/index.ts +0 -13
- package/src/integrations/librenms.ts +0 -291
- package/src/integrations/netbox.ts +0 -620
- package/src/integrations/peeringdb.ts +0 -173
- package/src/integrations/prometheus.ts +0 -90
- package/src/schema/common.ts +0 -41
- package/src/schema/device.ts +0 -145
- package/src/schema/firewall.ts +0 -140
- package/src/schema/index.ts +0 -8
- package/src/schema/policy.ts +0 -115
- package/src/schema/routing.ts +0 -173
- package/src/schema/system.ts +0 -203
- package/src/studio/public/app.js +0 -186
- package/src/studio/public/compare.js +0 -250
- package/src/studio/public/dom.js +0 -138
- package/src/studio/public/evaluate.js +0 -687
- package/src/studio/public/export.js +0 -130
- package/src/studio/public/graph.js +0 -1541
- package/src/studio/public/index.html +0 -83
- package/src/studio/public/net.js +0 -134
- package/src/studio/public/tables.js +0 -1289
- package/src/studio/public/tools.js +0 -610
- package/templates/colocation/checks.ts +0 -53
- package/templates/colocation/circuit.config.ts +0 -9
- package/templates/colocation/edge-01.ts +0 -133
- package/templates/colocation/routing.ts +0 -13
- package/templates/colocation/tenancy.ts +0 -76
- package/templates/colocation/tenants/acme.ts +0 -19
- package/templates/colocation/tenants/index.ts +0 -6
- package/templates/colocation/tor-01.ts +0 -33
- package/templates/edge-router/checks.ts +0 -20
- package/templates/edge-router/circuit.config.ts +0 -8
- package/templates/edge-router/edge-01.ts +0 -110
- package/templates/edge-router/routing.ts +0 -13
- package/templates/project/AGENTS.md +0 -51
- package/templates/project/CLAUDE.md +0 -1
- package/templates/project/README.md +0 -16
- package/templates/project/gitignore +0 -5
- package/templates/project/tsconfig.json +0 -13
- package/templates/single-site/checks.ts +0 -26
- package/templates/single-site/circuit.config.ts +0 -8
- package/templates/single-site/router.ts +0 -86
- package/templates/single-site/site.ts +0 -18
- package/templates/single-site/switch.ts +0 -34
- /package/src/studio/{public → app/public}/favicon.svg +0 -0
- /package/src/studio/{public → app/public}/special-gothic-latin-ext.woff2 +0 -0
- /package/src/studio/{public → app/public}/special-gothic-latin.woff2 +0 -0
- /package/src/studio/{public → app/public}/special-gothic-license.txt +0 -0
|
@@ -1,133 +0,0 @@
|
|
|
1
|
-
// The edge router: BGP with an IP transit and an internet exchange, the gateway of every tenant, and the filter between them.
|
|
2
|
-
//
|
|
3
|
-
// Hurricane Electric and SGIX stand for your own transit and exchange. Addresses are from the documentation ranges; use the ones your providers give you.
|
|
4
|
-
|
|
5
|
-
import type { Prefix } from "@takodotid/circuit";
|
|
6
|
-
import { defineDevice, secret, Utils } from "@takodotid/circuit";
|
|
7
|
-
import { Bgp, Filters } from "@takodotid/circuit/helpers";
|
|
8
|
-
import { ASN, communities } from "./routing";
|
|
9
|
-
import { edgeRules, gateways, internetVlanNames, routerVlans } from "./tenancy";
|
|
10
|
-
import { TENANTS } from "./tenants";
|
|
11
|
-
|
|
12
|
-
/** The address space this router announces. */
|
|
13
|
-
const OURS = ["198.51.100.0/24", "2001:db8:100::/48"] as const satisfies readonly Prefix[];
|
|
14
|
-
|
|
15
|
-
/** Where the router is managed from. */
|
|
16
|
-
const MANAGEMENT = "10.0.0.0/24" satisfies Prefix;
|
|
17
|
-
|
|
18
|
-
/** Sources that never arrive from the internet: our own space, and addresses that are not on the internet at all. */
|
|
19
|
-
const SPOOFED = [OURS[0], ...Filters.MARTIANS, ...Filters.PRIVATE_RANGES];
|
|
20
|
-
|
|
21
|
-
export default defineDevice({
|
|
22
|
-
name: "edge-01",
|
|
23
|
-
platform: "routeros",
|
|
24
|
-
model: "CCR2216-1G-12XS-2XQ",
|
|
25
|
-
connection: { host: "10.0.0.1", user: "operator" },
|
|
26
|
-
users: { operator: { role: "admin", password: secret("EDGE_01_PASSWORD") } },
|
|
27
|
-
|
|
28
|
-
system: { timezone: "Asia/Jakarta", ntp: { servers: ["pool.ntp.org"] } },
|
|
29
|
-
management: { allow: [MANAGEMENT], ssh: {} },
|
|
30
|
-
|
|
31
|
-
vlans: Utils.merge(
|
|
32
|
-
{
|
|
33
|
-
mgmt: { id: 99, description: "Management of the switches" },
|
|
34
|
-
transit: { id: 300, description: "IP transit from Hurricane Electric, AS6939" },
|
|
35
|
-
ix: { id: 200, description: "Peering LAN of SGIX" },
|
|
36
|
-
},
|
|
37
|
-
routerVlans(TENANTS)
|
|
38
|
-
),
|
|
39
|
-
|
|
40
|
-
ports: {
|
|
41
|
-
"1g-1": { description: "Management", addresses: ["10.0.0.1/24"] },
|
|
42
|
-
"25g-1": { description: "Hurricane Electric", access_vlan: "transit", acl: "edge" },
|
|
43
|
-
"25g-2": { description: "SGIX", access_vlan: "ix", acl: "edge" },
|
|
44
|
-
"100g-1": {
|
|
45
|
-
description: "tor-01",
|
|
46
|
-
speed: "40g",
|
|
47
|
-
trunk_vlans: ["mgmt", ...internetVlanNames(TENANTS)],
|
|
48
|
-
link: { device: "tor-01", port: "40g-1" },
|
|
49
|
-
},
|
|
50
|
-
},
|
|
51
|
-
|
|
52
|
-
interfaces: Utils.merge(
|
|
53
|
-
{
|
|
54
|
-
mgmt: { type: "vlan", vlan: "mgmt", addresses: ["10.0.99.1/24"] },
|
|
55
|
-
transit: { type: "vlan", vlan: "transit", addresses: ["203.0.113.2/30"] },
|
|
56
|
-
ix: { type: "vlan", vlan: "ix", addresses: ["192.0.2.10/24", "2001:db8:ffff::10/64"] },
|
|
57
|
-
},
|
|
58
|
-
gateways(TENANTS)
|
|
59
|
-
),
|
|
60
|
-
|
|
61
|
-
routing: {
|
|
62
|
-
router_id: "203.0.113.2",
|
|
63
|
-
// A route for each block we announce, so it is in the table even when nothing inside it is up.
|
|
64
|
-
static: OURS.map((prefix) => ({ prefix, blackhole: true, description: "Originate" })),
|
|
65
|
-
bgp: {
|
|
66
|
-
asn: ASN,
|
|
67
|
-
networks: [...OURS],
|
|
68
|
-
neighbors: {
|
|
69
|
-
"hurricane-electric": {
|
|
70
|
-
address: "203.0.113.1",
|
|
71
|
-
remote_as: 6939,
|
|
72
|
-
role: "provider",
|
|
73
|
-
import: "TRANSIT-IN",
|
|
74
|
-
export: "OUT",
|
|
75
|
-
max_prefixes: 1_200_000,
|
|
76
|
-
},
|
|
77
|
-
"sgix-rs-v4": {
|
|
78
|
-
address: "192.0.2.1",
|
|
79
|
-
remote_as: 55518,
|
|
80
|
-
role: "rs",
|
|
81
|
-
import: "IX-IN",
|
|
82
|
-
export: "OUT",
|
|
83
|
-
max_prefixes: 100_000,
|
|
84
|
-
},
|
|
85
|
-
"sgix-rs-v6": {
|
|
86
|
-
address: "2001:db8:ffff::1",
|
|
87
|
-
remote_as: 55518,
|
|
88
|
-
role: "rs",
|
|
89
|
-
import: "IX-IN",
|
|
90
|
-
export: "OUT",
|
|
91
|
-
max_prefixes: 50_000,
|
|
92
|
-
},
|
|
93
|
-
},
|
|
94
|
-
},
|
|
95
|
-
},
|
|
96
|
-
|
|
97
|
-
policies: {
|
|
98
|
-
SANITY: Bgp.sanity(),
|
|
99
|
-
// A route from the exchange is preferred over the same route from the transit: higher local preference wins.
|
|
100
|
-
"TRANSIT-IN": [{ call: "SANITY" }, communities.tag("transit", 6939), { set: { local_pref: 100 } }, { action: "accept" }],
|
|
101
|
-
"IX-IN": [{ call: "SANITY" }, communities.tag("exchange", 55518), { set: { local_pref: 200 } }, { action: "accept" }],
|
|
102
|
-
OUT: [
|
|
103
|
-
...OURS.map((prefix) => ({ description: `Announce ${prefix}`, match: { prefix }, action: "accept" as const })),
|
|
104
|
-
{ description: "Nothing else leaves", action: "reject" },
|
|
105
|
-
],
|
|
106
|
-
},
|
|
107
|
-
|
|
108
|
-
firewall: {
|
|
109
|
-
address_sets: { "bgp-neighbors": ["203.0.113.1", "192.0.2.1", "2001:db8:ffff::1"] },
|
|
110
|
-
filter: {
|
|
111
|
-
input: {
|
|
112
|
-
default: "drop",
|
|
113
|
-
rules: [
|
|
114
|
-
{ description: "Replies", match: { state: ["established", "related"] }, action: "accept" },
|
|
115
|
-
{ description: "Ping", match: { protocol: "icmp" }, action: "accept" },
|
|
116
|
-
{ description: "Management", match: { src: MANAGEMENT }, action: "accept" },
|
|
117
|
-
{ description: "BGP", match: { protocol: "tcp", dst_port: 179, src_set: "bgp-neighbors" }, action: "accept" },
|
|
118
|
-
],
|
|
119
|
-
},
|
|
120
|
-
forward: { rules: [...Filters.badTcpFlags()] },
|
|
121
|
-
},
|
|
122
|
-
},
|
|
123
|
-
|
|
124
|
-
// In the switch chip, before a packet reaches the router's CPU: spoofed sources first, then each tenant's own rules.
|
|
125
|
-
acls: {
|
|
126
|
-
edge: [
|
|
127
|
-
...Filters.antiSpoofing("transit", SPOOFED),
|
|
128
|
-
...Filters.antiSpoofing("ix", SPOOFED),
|
|
129
|
-
...edgeRules(TENANTS, "transit"),
|
|
130
|
-
...edgeRules(TENANTS, "ix"),
|
|
131
|
-
],
|
|
132
|
-
},
|
|
133
|
-
});
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
// What your network is on the internet: its AS number and its BGP communities.
|
|
2
|
-
|
|
3
|
-
import { Bgp } from "@takodotid/circuit/helpers";
|
|
4
|
-
|
|
5
|
-
/** Your AS number. */
|
|
6
|
-
export const ASN = 64500;
|
|
7
|
-
|
|
8
|
-
/** What your BGP communities mean. The router tags each route it learns with them, and `circuit communities` publishes them. */
|
|
9
|
-
export const communities = Bgp.communityScheme({
|
|
10
|
-
asn: ASN,
|
|
11
|
-
learned_from: { function: 1, classes: { transit: 1, exchange: 2 } },
|
|
12
|
-
learned_from_as: 3,
|
|
13
|
-
});
|
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
// What a tenant is, and what each device carries for it. A tenant's file writes everything its devices carry: its VLANs, the router's addresses for it, its ports. These functions only gather them for one device.
|
|
2
|
-
|
|
3
|
-
import type { AclRule, Interface, InterfaceAddress, Port, Prefix, Vlan } from "@takodotid/circuit";
|
|
4
|
-
import { Address } from "@takodotid/circuit";
|
|
5
|
-
|
|
6
|
-
/** A VLAN with its name. */
|
|
7
|
-
export type NamedVlan = Vlan & { name: string };
|
|
8
|
-
|
|
9
|
-
export type Tenant = {
|
|
10
|
-
/** Lowercase, and never reused. */
|
|
11
|
-
name: string;
|
|
12
|
-
/** The tenant's VLAN to the internet. The router has it, with the tenant's gateways, and so does every switch, to carry it up to the router. */
|
|
13
|
-
internet: {
|
|
14
|
-
vlan: NamedVlan;
|
|
15
|
-
/** The router's addresses on it, each the gateway the tenant's servers use inside one of its blocks, such as `198.51.100.9/29`. */
|
|
16
|
-
gateways: readonly InterfaceAddress[];
|
|
17
|
-
};
|
|
18
|
-
/** A VLAN between the tenant's own servers. It is only on a switch where one of the tenant's ports uses it, and never on the router. */
|
|
19
|
-
private?: { vlan: NamedVlan };
|
|
20
|
-
/** Rules for traffic toward the tenant's IPv4 blocks from the transit and the exchange, such as a cap on UDP. Everything reaches the tenant when absent. */
|
|
21
|
-
acl?: readonly (Omit<AclRule<never>, "match"> & { match?: Omit<NonNullable<AclRule<never>["match"]>, "vlan" | "dst"> })[];
|
|
22
|
-
/** The tenant's ports, by switch, then by port, written as any port is. */
|
|
23
|
-
ports: Readonly<Record<string, Readonly<Record<string, Port<string, string, string, string>>>>>;
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
/** A VLAN as `vlans` takes it, keyed by its name. */
|
|
27
|
-
const entry = ({ name, ...vlan }: NamedVlan): [string, Vlan] => [name, vlan];
|
|
28
|
-
|
|
29
|
-
/** The tenant's blocks: the network of each gateway. */
|
|
30
|
-
export const blocksOf = (t: Tenant): Prefix[] => t.internet.gateways.map((gateway) => Address.prefixOf(gateway));
|
|
31
|
-
|
|
32
|
-
/** Every tenant's internet VLAN, for the router. */
|
|
33
|
-
export const routerVlans = (tenants: readonly Tenant[]): Record<string, Vlan> =>
|
|
34
|
-
Object.fromEntries(tenants.map((t) => entry(t.internet.vlan)));
|
|
35
|
-
|
|
36
|
-
/** The VLANs one switch carries: every tenant's internet VLAN, and the private VLAN of each tenant with a port on it that uses it. */
|
|
37
|
-
export const switchVlans = (tenants: readonly Tenant[], device: string): Record<string, Vlan> =>
|
|
38
|
-
Object.fromEntries(
|
|
39
|
-
tenants.flatMap((t) => {
|
|
40
|
-
const ports = Object.values(t.ports[device] ?? {});
|
|
41
|
-
const usesPrivate =
|
|
42
|
-
t.private &&
|
|
43
|
-
ports.some((port) => port.access_vlan === t.private!.vlan.name || port.trunk_vlans?.includes(t.private!.vlan.name));
|
|
44
|
-
return [entry(t.internet.vlan), ...(usesPrivate ? [entry(t.private!.vlan)] : [])];
|
|
45
|
-
})
|
|
46
|
-
);
|
|
47
|
-
|
|
48
|
-
/** The names of every tenant's internet VLAN, for a trunk toward the router. */
|
|
49
|
-
export const internetVlanNames = (tenants: readonly Tenant[]) => tenants.map((t) => t.internet.vlan.name);
|
|
50
|
-
|
|
51
|
-
/** The tenants' ports on one switch. */
|
|
52
|
-
export const portsOn = (tenants: readonly Tenant[], device: string): Record<string, Port<string, string, string, string>> =>
|
|
53
|
-
Object.assign({}, ...tenants.map((t) => t.ports[device] ?? {}));
|
|
54
|
-
|
|
55
|
-
/** The router's interface on each tenant's internet VLAN, holding the tenant's gateways. */
|
|
56
|
-
export const gateways = (tenants: readonly Tenant[]): Record<string, Interface<string, never>> =>
|
|
57
|
-
Object.fromEntries(
|
|
58
|
-
tenants.map((t) => [
|
|
59
|
-
t.internet.vlan.name,
|
|
60
|
-
{ type: "vlan", vlan: t.internet.vlan.name, description: `Tenant ${t.name}`, addresses: t.internet.gateways },
|
|
61
|
-
])
|
|
62
|
-
);
|
|
63
|
-
|
|
64
|
-
/** Each tenant's `acl` rules on one VLAN from outside, one per rule and IPv4 block, each limited to traffic toward that block. */
|
|
65
|
-
export const edgeRules = <V extends string>(tenants: readonly Tenant[], vlan: V): AclRule<V>[] =>
|
|
66
|
-
tenants.flatMap((t) =>
|
|
67
|
-
(t.acl ?? []).flatMap((rule) =>
|
|
68
|
-
blocksOf(t)
|
|
69
|
-
.filter((prefix) => Address.family(prefix) === "ipv4")
|
|
70
|
-
.map((prefix) => ({
|
|
71
|
-
...rule,
|
|
72
|
-
...(rule.description ? { description: `${vlan}: ${rule.description}` } : {}),
|
|
73
|
-
match: { ...rule.match, vlan, dst: prefix },
|
|
74
|
-
}))
|
|
75
|
-
)
|
|
76
|
-
);
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
// An example tenant. Copy this file for each new tenant, and add it to index.ts.
|
|
2
|
-
|
|
3
|
-
import type { Tenant } from "../tenancy";
|
|
4
|
-
|
|
5
|
-
export default {
|
|
6
|
-
name: "acme",
|
|
7
|
-
internet: {
|
|
8
|
-
vlan: { name: "acme-internet", id: 1001, description: "Tenant acme internet" },
|
|
9
|
-
gateways: ["198.51.100.9/29", "2001:db8:100:1::1/64"],
|
|
10
|
-
},
|
|
11
|
-
private: { vlan: { name: "acme-private", id: 2001, description: "Tenant acme private" } },
|
|
12
|
-
acl: [{ description: "UDP to acme above 1G", match: { protocol: "udp" }, action: "accept", rate: "1G" }],
|
|
13
|
-
ports: {
|
|
14
|
-
"tor-01": {
|
|
15
|
-
"10g-1": { description: "acme server 1", trunk_vlans: ["acme-internet", "acme-private"] },
|
|
16
|
-
"10g-2": { description: "acme server 2", trunk_vlans: ["acme-internet", "acme-private"] },
|
|
17
|
-
},
|
|
18
|
-
},
|
|
19
|
-
} satisfies Tenant;
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
// The top-of-rack switch: the tenants' servers, and one uplink to the router.
|
|
2
|
-
|
|
3
|
-
import { defineDevice, secret, Utils } from "@takodotid/circuit";
|
|
4
|
-
import { internetVlanNames, portsOn, switchVlans } from "./tenancy";
|
|
5
|
-
import { TENANTS } from "./tenants";
|
|
6
|
-
|
|
7
|
-
export default defineDevice({
|
|
8
|
-
name: "tor-01",
|
|
9
|
-
platform: "vrp",
|
|
10
|
-
model: "CE6855-48S6Q-HI",
|
|
11
|
-
connection: { host: "10.0.99.2", user: "operator" },
|
|
12
|
-
users: { operator: { role: "admin", password: secret("TOR_01_PASSWORD") } },
|
|
13
|
-
|
|
14
|
-
system: { timezone: "Asia/Jakarta", ntp: { servers: ["10.0.99.1"] } },
|
|
15
|
-
management: { allow: ["10.0.0.0/24"], ssh: {} },
|
|
16
|
-
|
|
17
|
-
lldp: true,
|
|
18
|
-
stp: {},
|
|
19
|
-
|
|
20
|
-
vlans: Utils.merge({ mgmt: { id: 99, description: "Management" } }, switchVlans(TENANTS, "tor-01")),
|
|
21
|
-
|
|
22
|
-
// Every tenant's ports, from its file in tenants/, beside the switch's own.
|
|
23
|
-
ports: Utils.merge(portsOn(TENANTS, "tor-01"), {
|
|
24
|
-
"40g-1": {
|
|
25
|
-
description: "edge-01",
|
|
26
|
-
trunk_vlans: ["mgmt", ...internetVlanNames(TENANTS)],
|
|
27
|
-
link: { device: "edge-01", port: "100g-1" },
|
|
28
|
-
},
|
|
29
|
-
}),
|
|
30
|
-
|
|
31
|
-
interfaces: { mgmt: { type: "vlan", vlan: "mgmt", addresses: ["10.0.99.2/24"] } },
|
|
32
|
-
routing: { static: [{ prefix: "0.0.0.0/0", via: "10.0.99.1" }] },
|
|
33
|
-
});
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
// Rules of your own. `circuit validate` runs them beside Circuit's own, and an error stops `diff` and `apply`.
|
|
2
|
-
|
|
3
|
-
import type { Check } from "@takodotid/circuit";
|
|
4
|
-
import { Bgp, Checks } from "@takodotid/circuit/helpers";
|
|
5
|
-
|
|
6
|
-
export default [
|
|
7
|
-
// Nothing leaves by accident: every export ends by rejecting what it did not accept.
|
|
8
|
-
Checks.exportsEndInReject,
|
|
9
|
-
|
|
10
|
-
// Traffic from the transit and the exchange reaches the rest of the network only through the router.
|
|
11
|
-
Checks.trustBoundary({ untrusted: ["transit", "ix"], routers: ["edge-01"] }),
|
|
12
|
-
|
|
13
|
-
// Every BGP session filters what it takes in.
|
|
14
|
-
(devices) =>
|
|
15
|
-
devices.flatMap((device) =>
|
|
16
|
-
Bgp.neighborPolicies(device)
|
|
17
|
-
.filter((neighbor) => !neighbor.import)
|
|
18
|
-
.map((neighbor) => ({ level: "error" as const, device: device.name, message: `${neighbor.name} has no import policy` }))
|
|
19
|
-
),
|
|
20
|
-
] satisfies Check[];
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
// The network: every device Circuit manages, your own checks, and what is published about the network.
|
|
2
|
-
|
|
3
|
-
import { defineNetwork } from "@takodotid/circuit";
|
|
4
|
-
import checks from "./checks";
|
|
5
|
-
import edge from "./edge-01";
|
|
6
|
-
import { ASN, communities } from "./routing";
|
|
7
|
-
|
|
8
|
-
export default defineNetwork({ devices: [edge], checks, asn: ASN, communities: communities.catalogue });
|
|
@@ -1,110 +0,0 @@
|
|
|
1
|
-
// The edge router: BGP with an IP transit and an internet exchange, filtered on the way in and on the way out.
|
|
2
|
-
//
|
|
3
|
-
// Hurricane Electric and SGIX stand for your own transit and exchange. Addresses are from the documentation ranges; use the ones your providers give you.
|
|
4
|
-
|
|
5
|
-
import type { Prefix } from "@takodotid/circuit";
|
|
6
|
-
import { defineDevice, secret } from "@takodotid/circuit";
|
|
7
|
-
import { Bgp, Filters } from "@takodotid/circuit/helpers";
|
|
8
|
-
import { ASN, communities } from "./routing";
|
|
9
|
-
|
|
10
|
-
/** The address space this router announces. */
|
|
11
|
-
const OURS = ["198.51.100.0/24", "2001:db8:100::/48"] as const satisfies readonly Prefix[];
|
|
12
|
-
|
|
13
|
-
/** Where the router is managed from. */
|
|
14
|
-
const MANAGEMENT = "10.0.0.0/24" satisfies Prefix;
|
|
15
|
-
|
|
16
|
-
/** Sources that never arrive from the internet: our own space, and addresses that are not on the internet at all. */
|
|
17
|
-
const SPOOFED = [OURS[0], ...Filters.MARTIANS, ...Filters.PRIVATE_RANGES];
|
|
18
|
-
|
|
19
|
-
export default defineDevice({
|
|
20
|
-
name: "edge-01",
|
|
21
|
-
platform: "routeros",
|
|
22
|
-
model: "CCR2216-1G-12XS-2XQ",
|
|
23
|
-
connection: { host: "10.0.0.1", user: "operator" },
|
|
24
|
-
users: { operator: { role: "admin", password: secret("EDGE_01_PASSWORD") } },
|
|
25
|
-
|
|
26
|
-
system: { timezone: "Asia/Jakarta", ntp: { servers: ["pool.ntp.org"] } },
|
|
27
|
-
management: { allow: [MANAGEMENT], ssh: {} },
|
|
28
|
-
|
|
29
|
-
vlans: {
|
|
30
|
-
transit: { id: 300, description: "IP transit from Hurricane Electric, AS6939" },
|
|
31
|
-
ix: { id: 200, description: "Peering LAN of SGIX" },
|
|
32
|
-
},
|
|
33
|
-
|
|
34
|
-
ports: {
|
|
35
|
-
"1g-1": { description: "Management", addresses: ["10.0.0.1/24"] },
|
|
36
|
-
"25g-1": { description: "Hurricane Electric", access_vlan: "transit", acl: "edge" },
|
|
37
|
-
"25g-2": { description: "SGIX", access_vlan: "ix", acl: "edge" },
|
|
38
|
-
},
|
|
39
|
-
|
|
40
|
-
interfaces: {
|
|
41
|
-
transit: { type: "vlan", vlan: "transit", addresses: ["203.0.113.2/30"] },
|
|
42
|
-
ix: { type: "vlan", vlan: "ix", addresses: ["192.0.2.10/24", "2001:db8:ffff::10/64"] },
|
|
43
|
-
},
|
|
44
|
-
|
|
45
|
-
routing: {
|
|
46
|
-
router_id: "203.0.113.2",
|
|
47
|
-
// A route for each block we announce, so it is in the table even when nothing inside it is up.
|
|
48
|
-
static: OURS.map((prefix) => ({ prefix, blackhole: true, description: "Originate" })),
|
|
49
|
-
bgp: {
|
|
50
|
-
asn: ASN,
|
|
51
|
-
networks: [...OURS],
|
|
52
|
-
neighbors: {
|
|
53
|
-
"hurricane-electric": {
|
|
54
|
-
address: "203.0.113.1",
|
|
55
|
-
remote_as: 6939,
|
|
56
|
-
role: "provider",
|
|
57
|
-
import: "TRANSIT-IN",
|
|
58
|
-
export: "OUT",
|
|
59
|
-
max_prefixes: 1_200_000,
|
|
60
|
-
},
|
|
61
|
-
"sgix-rs-v4": {
|
|
62
|
-
address: "192.0.2.1",
|
|
63
|
-
remote_as: 55518,
|
|
64
|
-
role: "rs",
|
|
65
|
-
import: "IX-IN",
|
|
66
|
-
export: "OUT",
|
|
67
|
-
max_prefixes: 100_000,
|
|
68
|
-
},
|
|
69
|
-
"sgix-rs-v6": {
|
|
70
|
-
address: "2001:db8:ffff::1",
|
|
71
|
-
remote_as: 55518,
|
|
72
|
-
role: "rs",
|
|
73
|
-
import: "IX-IN",
|
|
74
|
-
export: "OUT",
|
|
75
|
-
max_prefixes: 50_000,
|
|
76
|
-
},
|
|
77
|
-
},
|
|
78
|
-
},
|
|
79
|
-
},
|
|
80
|
-
|
|
81
|
-
policies: {
|
|
82
|
-
SANITY: Bgp.sanity(),
|
|
83
|
-
// A route from the exchange is preferred over the same route from the transit: higher local preference wins.
|
|
84
|
-
"TRANSIT-IN": [{ call: "SANITY" }, communities.tag("transit", 6939), { set: { local_pref: 100 } }, { action: "accept" }],
|
|
85
|
-
"IX-IN": [{ call: "SANITY" }, communities.tag("exchange", 55518), { set: { local_pref: 200 } }, { action: "accept" }],
|
|
86
|
-
OUT: [
|
|
87
|
-
...OURS.map((prefix) => ({ description: `Announce ${prefix}`, match: { prefix }, action: "accept" as const })),
|
|
88
|
-
{ description: "Nothing else leaves", action: "reject" },
|
|
89
|
-
],
|
|
90
|
-
},
|
|
91
|
-
|
|
92
|
-
firewall: {
|
|
93
|
-
address_sets: { "bgp-neighbors": ["203.0.113.1", "192.0.2.1", "2001:db8:ffff::1"] },
|
|
94
|
-
filter: {
|
|
95
|
-
input: {
|
|
96
|
-
default: "drop",
|
|
97
|
-
rules: [
|
|
98
|
-
{ description: "Replies", match: { state: ["established", "related"] }, action: "accept" },
|
|
99
|
-
{ description: "Ping", match: { protocol: "icmp" }, action: "accept" },
|
|
100
|
-
{ description: "Management", match: { src: MANAGEMENT }, action: "accept" },
|
|
101
|
-
{ description: "BGP", match: { protocol: "tcp", dst_port: 179, src_set: "bgp-neighbors" }, action: "accept" },
|
|
102
|
-
],
|
|
103
|
-
},
|
|
104
|
-
forward: { rules: [...Filters.badTcpFlags()] },
|
|
105
|
-
},
|
|
106
|
-
},
|
|
107
|
-
|
|
108
|
-
// In the switch chip, before a packet reaches the router's CPU.
|
|
109
|
-
acls: { edge: [...Filters.antiSpoofing("transit", SPOOFED), ...Filters.antiSpoofing("ix", SPOOFED)] },
|
|
110
|
-
});
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
// What your network is on the internet: its AS number and its BGP communities.
|
|
2
|
-
|
|
3
|
-
import { Bgp } from "@takodotid/circuit/helpers";
|
|
4
|
-
|
|
5
|
-
/** Your AS number. */
|
|
6
|
-
export const ASN = 64500;
|
|
7
|
-
|
|
8
|
-
/** What your BGP communities mean. The router tags each route it learns with them, and `circuit communities` publishes them. */
|
|
9
|
-
export const communities = Bgp.communityScheme({
|
|
10
|
-
asn: ASN,
|
|
11
|
-
learned_from: { function: 1, classes: { transit: 1, exchange: 2 } },
|
|
12
|
-
learned_from_as: 3,
|
|
13
|
-
});
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
# AGENTS.md
|
|
2
|
-
|
|
3
|
-
This repository describes a computer network with [Circuit](https://circuit.tako.id). Each network device, such as a router or a switch, has a TypeScript file that says how it should be configured. Circuit compares that file with what the device runs, and changes the device to match.
|
|
4
|
-
|
|
5
|
-
You are helping the owner of this network. Changes you make here can take a network down, so work carefully and let the owner decide.
|
|
6
|
-
|
|
7
|
-
## How Circuit works
|
|
8
|
-
|
|
9
|
-
1. `circuit.config.ts` lists every device and the checks to run.
|
|
10
|
-
2. Each device file calls `defineDevice`. What the file says is everything the device runs: a port, a user or a service that the file does not mention is removed or turned off when the device is applied.
|
|
11
|
-
3. A field is only written when it has a value. There is no `enabled: false`; leave a field out to turn it off.
|
|
12
|
-
4. Secrets are never written in a file. A file says `secret("NAME")`, and the value comes from `.env` or the environment. Never put a password, a key or a token in any other file, and never print one.
|
|
13
|
-
5. `.circuit/` is written by Circuit. Never edit it. `.circuit/state/` holds what each device ran when Circuit last read it.
|
|
14
|
-
|
|
15
|
-
What each field means is written as a comment on the field, in `node_modules/@takodotid/circuit/src/schema/`. Read it before using a field you have not used before. The documentation is at https://circuit.tako.id.
|
|
16
|
-
|
|
17
|
-
## Commands
|
|
18
|
-
|
|
19
|
-
| Command | What it does | Changes a device |
|
|
20
|
-
| ---------------------------------------------- | ----------------------------------------------------------------------------- | ---------------- |
|
|
21
|
-
| `npx circuit validate` | Checks every file and every rule in the checks | no |
|
|
22
|
-
| `npx circuit diff [device]` | Shows what would change, compared with the last snapshot | no |
|
|
23
|
-
| `npx circuit build <device>` | Prints the full configuration the device should run | no |
|
|
24
|
-
| `npx circuit snapshot [device]` | Reads what each device runs now into `.circuit/state/` | no, only reads |
|
|
25
|
-
| `npx circuit show <device> '<command>'` | Runs commands that only read on the device, secrets hidden | no, only reads |
|
|
26
|
-
| `npx circuit bgp [device] --neighbors <regex>` | Each BGP neighbor in the config next to its live session: up or down | no, only reads |
|
|
27
|
-
| `npx circuit console <device> '<line>'` | Sends lines to the device's serial console, unchecked | **can** |
|
|
28
|
-
| `npx circuit apply <device>` | Reads the device, then shows what would change on it | no |
|
|
29
|
-
| `npx circuit apply <device> --confirm` | Sends the change to the device | **yes** |
|
|
30
|
-
| `npx circuit secrets` | Lists every secret the config uses, and whether each one can be read | no |
|
|
31
|
-
| `npx circuit rename <device> <new-name>` | Shows every line a new device name changes; `--confirm` writes them | no |
|
|
32
|
-
| `npx circuit addresses` | Every address the config uses, by prefix, and what is still free | no |
|
|
33
|
-
| `npx circuit graph --layer=all --format=json` | The whole network as a graph: cables, VLANs, addresses, BGP, OSPF and tunnels | no |
|
|
34
|
-
|
|
35
|
-
## Making a change
|
|
36
|
-
|
|
37
|
-
1. Edit the device's file.
|
|
38
|
-
2. Run `npx circuit validate`. Fix every error.
|
|
39
|
-
3. Run `npx circuit diff <device>` and read the result.
|
|
40
|
-
4. Show the owner what will change and why, in plain words.
|
|
41
|
-
5. Run `npx circuit apply <device>` to see the plan against the live device.
|
|
42
|
-
6. **Stop.** Only run `apply <device> --confirm` after the owner says yes to that exact plan. Never confirm on your own, even if you were told to finish the task.
|
|
43
|
-
7. After the apply, commit the device file together with the updated `.circuit/state/`.
|
|
44
|
-
|
|
45
|
-
## Rules
|
|
46
|
-
|
|
47
|
-
- Never run `apply --confirm`, `refresh --confirm` or `peeringdb --confirm`, or `console` with a line that changes something, without the owner's explicit approval for that run.
|
|
48
|
-
- If a plan removes something you did not expect, stop and ask. It usually means the file is missing something the device needs.
|
|
49
|
-
- Be careful with anything that carries your own access to the device: the management address, the management VLAN, the uplink, `management.allow`, and the login user. A mistake there locks everyone out.
|
|
50
|
-
- A platform supports only the OS versions listed at https://circuit.tako.id/platforms/. Before the first apply to a device, check that it runs one of them.
|
|
51
|
-
- When you are not sure what a field does, or what the owner wants, ask instead of guessing.
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
@AGENTS.md
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
# Network
|
|
2
|
-
|
|
3
|
-
This repository describes the network with [Circuit](https://circuit.tako.id). Each device has a file; Circuit makes the device match it.
|
|
4
|
-
|
|
5
|
-
```bash
|
|
6
|
-
npm install # once
|
|
7
|
-
npx circuit secrets # check every secret can be read
|
|
8
|
-
npx circuit validate # check the files
|
|
9
|
-
npx circuit studio # see the network in the browser
|
|
10
|
-
npx circuit snapshot # read what each device runs now
|
|
11
|
-
npx circuit diff # see what would change
|
|
12
|
-
npx circuit apply <device> # see the plan against the device
|
|
13
|
-
npx circuit apply <device> --confirm
|
|
14
|
-
```
|
|
15
|
-
|
|
16
|
-
Secrets go in `.env`, which is never committed. `.circuit/` is written by Circuit; commit it, but never edit it.
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
// Rules of your own. `circuit validate` runs them beside Circuit's own, and an error stops `diff` and `apply`.
|
|
2
|
-
|
|
3
|
-
import type { Check } from "@takodotid/circuit";
|
|
4
|
-
import { Checks } from "@takodotid/circuit/helpers";
|
|
5
|
-
|
|
6
|
-
export default [
|
|
7
|
-
// A port without a description is hard to trace later. A warning, so it does not block anything.
|
|
8
|
-
(devices) =>
|
|
9
|
-
devices.flatMap((device) =>
|
|
10
|
-
Object.entries(device.ports ?? {})
|
|
11
|
-
.filter(([, port]) => port && !port.description)
|
|
12
|
-
.map(([name]) => ({ level: "warning" as const, device: device.name, message: `port ${name} has no description` }))
|
|
13
|
-
),
|
|
14
|
-
|
|
15
|
-
// Someone plugging into a wall socket must not land on the management network. Only a trunk between devices carries it.
|
|
16
|
-
(devices) =>
|
|
17
|
-
devices.flatMap((device) =>
|
|
18
|
-
Object.entries(device.ports ?? {})
|
|
19
|
-
.filter(([, port]) => port?.access_vlan && Checks.vlansOf(port).includes("mgmt"))
|
|
20
|
-
.map(([name]) => ({
|
|
21
|
-
level: "error" as const,
|
|
22
|
-
device: device.name,
|
|
23
|
-
message: `port ${name} gives management to whatever plugs in`,
|
|
24
|
-
}))
|
|
25
|
-
),
|
|
26
|
-
] satisfies Check[];
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
// The network: every device Circuit manages, and your own checks.
|
|
2
|
-
|
|
3
|
-
import { defineNetwork } from "@takodotid/circuit";
|
|
4
|
-
import checks from "./checks";
|
|
5
|
-
import router from "./router";
|
|
6
|
-
import sw from "./switch";
|
|
7
|
-
|
|
8
|
-
export default defineNetwork({ devices: [router, sw], checks });
|