@adviser/ovn-fabric 0.1.2 → 0.1.4

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/src/define.ts DELETED
@@ -1,187 +0,0 @@
1
- // src/define.ts — defineNetwork(): a Vite-defineConfig / Jest-describe
2
- // style builder. Lets config/topology.ts read as a declaration rather
3
- // than a set of raw object literals, and validates as it goes (e.g. a
4
- // segment can only reference an uplink that was already declared in
5
- // the same defineNetwork call).
6
-
7
- import type { UplinkBuilder } from "./factories.ts";
8
- import {
9
- FixedUplink,
10
- type Host,
11
- localHost,
12
- type Segment,
13
- sshHost,
14
- type Uplink,
15
- type UplinkSelector,
16
- } from "./types.ts";
17
-
18
- export interface NetworkDefinition {
19
- readonly name: string;
20
- readonly allUplinks: readonly Uplink[];
21
- readonly allSegments: readonly Segment[];
22
- }
23
-
24
- /**
25
- * net.uplink(name, uplinkVlan({...})) / net.uplink(name, uplinkPhysical({...})) —
26
- * the second argument is an UplinkBuilder: a function of `slot` that a
27
- * factory in factories.ts returned. NetworkBuilder assigns the next
28
- * free slot (0-4095, see addressing.ts transferNet()) and calls it —
29
- * config/topology.ts never sees or chooses a slot itself, so two
30
- * uplinks can never collide on the same transfer-link block.
31
- */
32
-
33
- type SegmentSpec = Omit<Segment, "name">;
34
-
35
- /**
36
- * The builder context passed into defineNetwork's callback. Each method
37
- * both registers the declared thing and returns a handle to it, so later
38
- * calls in the same callback can reference earlier ones directly
39
- * (`net.segment("home", { uplink: avm, ... })`), the same way Jest's
40
- * `describe`/`it` or Vite's defineConfig read top-to-bottom.
41
- */
42
- export class NetworkBuilder {
43
- private readonly uplinksByName = new Map<string, Uplink>();
44
- private readonly usedUplinkIds = new Set<number>();
45
- private nextSlot = 0;
46
- private readonly segmentsByName = new Map<string, Segment>();
47
- private readonly usedSegmentIds = new Set<number>();
48
- private readonly hostsByName = new Map<string, Host>();
49
-
50
- /** Declare a host reachable via SSH. Returns a handle for reuse. */
51
- sshHost(name: string, address: string, user: string): Host {
52
- if (this.hostsByName.has(name)) {
53
- throw new Error(`host "${name}" declared more than once`);
54
- }
55
- const host = sshHost(name, address, user);
56
- this.hostsByName.set(name, host);
57
- return host;
58
- }
59
-
60
- /** Declare the generator's own host — no SSH needed. */
61
- localHost(name: string): Host {
62
- if (this.hostsByName.has(name)) {
63
- throw new Error(`host "${name}" declared more than once`);
64
- }
65
- const host = localHost(name);
66
- this.hostsByName.set(name, host);
67
- return host;
68
- }
69
-
70
- uplink(name: string, builder: UplinkBuilder): Uplink {
71
- if (this.uplinksByName.has(name)) {
72
- throw new Error(`uplink "${name}" declared more than once`);
73
- }
74
-
75
- // Passed as an allocator, not a single slot: most uplinks call this
76
- // once (their own front-door transfer link), but one with a
77
- // `backdoor` (see Backdoor, types.ts) calls it again for the
78
- // backdoor's own, separate slot — same sequential pool either way,
79
- // so nothing declared via this builder can ever collide on a slot.
80
- const allocSlot = (): number => {
81
- if (this.nextSlot > 4095) {
82
- throw new Error(
83
- "no transfer-link slots remaining (4096 max, see addressing.ts " +
84
- "transferNet()) — this network has more uplinks (and/or " +
85
- "backdoors) than the 10.99.0.0/16 transfer-link space can " +
86
- "address",
87
- );
88
- }
89
- return this.nextSlot++;
90
- };
91
-
92
- const spec = builder(allocSlot, name);
93
-
94
- const id = spec.addresses[0]?.id();
95
- if (id !== undefined && this.usedUplinkIds.has(id)) {
96
- const existing = [...this.uplinksByName.entries()]
97
- .find(([, u]) => u.addresses[0]?.id() === id)?.[0];
98
- throw new Error(
99
- `uplink "${name}" reuses id ${id}, ` +
100
- `already used by uplink "${existing}"`,
101
- );
102
- }
103
-
104
- // Same "must already be declared via this builder" fail-fast as
105
- // segment()'s uplink check below — a backdoor's `via` has to be a
106
- // real, already-registered uplink, not an arbitrary object that
107
- // happens to match the Uplink shape (and, since it must be
108
- // declared BEFORE this one, its own emitUplinkTransfer/netns setup
109
- // is guaranteed to already exist by the time this uplink's backdoor
110
- // is emitted — see generate-ovn.ts, scriptForHost).
111
- if (
112
- spec.backdoor !== undefined &&
113
- !this.uplinksByName.has(spec.backdoor.via.name)
114
- ) {
115
- throw new Error(
116
- `uplink "${name}" has a backdoor via "${spec.backdoor.via.name}", ` +
117
- `which was not declared via net.uplink() before this uplink`,
118
- );
119
- }
120
-
121
- const uplink: Uplink = { name, ...spec };
122
-
123
- this.uplinksByName.set(name, uplink);
124
- if (id !== undefined) this.usedUplinkIds.add(id);
125
- return uplink;
126
- }
127
-
128
- segment(name: string, spec: SegmentSpec): Segment {
129
- if (this.segmentsByName.has(name)) {
130
- throw new Error(`segment "${name}" declared more than once`);
131
- }
132
- const id = spec.addresses[0]?.id();
133
- if (id !== undefined && this.usedSegmentIds.has(id)) {
134
- const existing = [...this.segmentsByName.entries()]
135
- .find(([, s]) => s.addresses[0]?.id() === id)?.[0];
136
- throw new Error(
137
- `segment "${name}" reuses id ${id}, ` +
138
- `already used by segment "${existing}"`,
139
- );
140
- }
141
-
142
- // undefined means "no uplink yet, deliberately isolated" (see
143
- // Segment.uplink, types.ts) — not every segment resolves to
144
- // something, so this must be checked before the "resolve" in
145
- // probe below, which throws on undefined.
146
- const selector: UplinkSelector | undefined = spec.uplink === undefined
147
- ? undefined
148
- : ("resolve" in spec.uplink ? spec.uplink : new FixedUplink(spec.uplink));
149
-
150
- // fail fast: if an uplink WAS given, it must have been declared via
151
- // this same builder, not an arbitrary object that happens to match
152
- // the Uplink shape.
153
- if (selector !== undefined) {
154
- const resolved = selector.resolve();
155
- if (!this.uplinksByName.has(resolved.name)) {
156
- throw new Error(
157
- `segment "${name}" references an uplink ("${resolved.name}") ` +
158
- `that was not declared via net.uplink() in this defineNetwork call`,
159
- );
160
- }
161
- }
162
-
163
- const segment: Segment = { name, ...spec, uplink: selector };
164
-
165
- this.segmentsByName.set(name, segment);
166
- if (id !== undefined) this.usedSegmentIds.add(id);
167
- return segment;
168
- }
169
-
170
- /** @internal used by defineNetwork to extract the final declarations */
171
- build(name: string): NetworkDefinition {
172
- return {
173
- name,
174
- allUplinks: [...this.uplinksByName.values()],
175
- allSegments: [...this.segmentsByName.values()],
176
- };
177
- }
178
- }
179
-
180
- export function defineNetwork(
181
- name: string,
182
- build: (net: NetworkBuilder) => void,
183
- ): NetworkDefinition {
184
- const builder = new NetworkBuilder();
185
- build(builder);
186
- return builder.build(name);
187
- }
package/src/factories.ts DELETED
@@ -1,444 +0,0 @@
1
- // src/factories.ts — sparse-input factories for Uplink/Segment.
2
- //
3
- // net.uplink(name, uplinkVlan({ ... })) reads as: "this uplink is realized as
4
- // a VLAN; here are the few facts that vary." Every factory here takes a
5
- // sparse, optional-heavy input and returns an UplinkBuilder — a function
6
- // of `slot` (the transfer-link /28 block index, see addressing.ts
7
- // transferNet()) producing a FULLY RESOLVED object with no optional
8
- // fields left unset. `slot` is deliberately NOT something the caller
9
- // supplies in config/topology.ts: NetworkBuilder.uplink() (define.ts)
10
- // assigns slots sequentially in declaration order and calls this
11
- // function itself, so a config never has to think about slot allocation
12
- // or its 0-4095 range at all — and two uplinks can never collide on the
13
- // same slot, since each is assigned exactly once by NetworkBuilder.
14
- //
15
- // Addresses are computed from `id` via addressing.ts's fold rules
16
- // unless the caller asks for "dhcp"/"slaac" discovery instead, in which
17
- // case the corresponding stack's address is left to be learned at
18
- // runtime (see ADR 0001 — the netns owns that, not this generator).
19
- //
20
- // This is the layer config/topology.ts actually calls. define.ts's
21
- // NetworkBuilder.uplink()/segment() accept the UplinkBuilder/Segment
22
- // these factories produce — they do not know about "vlan" vs "physical"
23
- // vs sparse input at all.
24
-
25
- import { segmentNet, transferNet } from "./addressing.ts";
26
- import type {
27
- Addresses,
28
- Backdoor,
29
- DhcpClient,
30
- Discovery,
31
- Host,
32
- InterfaceKind,
33
- Nat,
34
- StaticIpv4,
35
- Uplink,
36
- WireguardInterfaceConfig,
37
- } from "./types.ts";
38
-
39
- /** A factory result still waiting on its assigned transfer-link
40
- * slot(s). Takes an ALLOCATOR, not a bare slot number: most uplinks
41
- * consume exactly one slot (their own front-door transfer link), but
42
- * an uplink with a `backdoor` (see types.ts) needs a SECOND, entirely
43
- * separate slot for it — sharing the front-door's /28 was tried and is
44
- * broken (see Backdoor's doc comment). Calling allocSlot() again for
45
- * the backdoor, from the same sequential pool NetworkBuilder already
46
- * guarantees uniqueness over, is what keeps the two from ever
47
- * colliding.
48
- *
49
- * Also receives `name` — the SAME string given to net.uplink(name,
50
- * ...) — so a factory that needs a human-facing identifier of its own
51
- * (e.g. uplinkWireguard's real interface/conf-file name) can reuse it
52
- * instead of asking for a second, redundant one. Most factories ignore
53
- * it entirely (JS/TS both allow a callback to declare fewer params
54
- * than its type permits), which is why this was added as a second
55
- * parameter rather than changed in place — every existing builder
56
- * above keeps working unmodified. */
57
- export type UplinkBuilder = (
58
- allocSlot: () => number,
59
- name: string,
60
- ) => Omit<Uplink, "name">;
61
- import {
62
- FixedUplink,
63
- segmentId,
64
- type Segment,
65
- uplinkId,
66
- type UplinkSelector,
67
- } from "./types.ts";
68
-
69
- // ── shared per-stack address-or-discovery input ─────────────────────
70
- // Each stack is either omitted (defaults to "dhcp"/"slaac" — the common
71
- // case for a real WAN uplink, which has no fixed address to compute),
72
- // or explicitly "dhcp"/"slaac" (same as omitted, written out for
73
- // clarity), or a literal already-known IPAddress-parseable string for
74
- // cases like a cable/DSL uplink's historically-stable lease.
75
-
76
- interface AddressSpec {
77
- readonly ipv4?: "dhcp" | string;
78
- readonly ipv6?: "slaac" | string;
79
- }
80
-
81
- function resolveDiscovery(
82
- spec: AddressSpec | undefined,
83
- client?: DhcpClient,
84
- static4?: StaticIpv4,
85
- ): Discovery {
86
- // client === "static" implies ipv4 "static" outright — the caller
87
- // doesn't also have to remember to set addresses.ipv4: "static"
88
- // separately for it to take effect (see emitIpv4Discovery,
89
- // generate-netns.ts).
90
- const ipv4 = client === "static"
91
- ? "static"
92
- : spec?.ipv4 === undefined || spec.ipv4 === "dhcp"
93
- ? "dhcp"
94
- : "static";
95
- const ipv6 = spec?.ipv6 === undefined || spec.ipv6 === "slaac"
96
- ? "slaac"
97
- : "static";
98
- return { ipv4, ipv6, client, static4 };
99
- }
100
-
101
- // ── vlan() ───────────────────────────────────────────────────────────
102
-
103
- interface VlanUplinkInput {
104
- readonly id: string | number;
105
- readonly vlanParent: string;
106
- /** Override the VLAN tag if it should differ from `id`. Defaults to id. */
107
- readonly vlan?: number;
108
- /** Override the kernel subinterface name — see InterfaceKind
109
- * (types.ts). Only needed if `${vlanParent}.${vlanId}` is already in
110
- * use for something else. */
111
- readonly ifaceName?: string;
112
- readonly nat?: Nat;
113
- readonly host: Host;
114
- readonly addresses?: AddressSpec;
115
- /** Which program acquires the IPv4 lease when addresses.ipv4 is
116
- * "dhcp" (or omitted, same default). Defaults to "dhclient" — see
117
- * DhcpClient (types.ts). Set to "static" (with `static4` below) for
118
- * a real interface whose address is fixed/known rather than leased. */
119
- readonly client?: DhcpClient;
120
- /** Only consulted when client === "static" — see StaticIpv4 (types.ts). */
121
- readonly static4?: StaticIpv4;
122
- }
123
-
124
- export function uplinkVlan(input: VlanUplinkInput): UplinkBuilder {
125
- const id = uplinkId(
126
- typeof input.id === "string" ? Number.parseInt(input.id, 10) : input.id,
127
- );
128
-
129
- return (allocSlot: () => number) => {
130
- const slot = allocSlot();
131
- const ifc: InterfaceKind = {
132
- kind: "vlan",
133
- vlanParent: input.vlanParent,
134
- vlanId: input.vlan ?? id,
135
- ifaceName: input.ifaceName,
136
- };
137
-
138
- const addresses: Addresses = [
139
- transferNet(id, slot, 1), // OVN side
140
- transferNet(id, slot, 2), // netns side
141
- ];
142
-
143
- return {
144
- slot,
145
- addresses,
146
- if: ifc,
147
- nat: input.nat,
148
- discovery: resolveDiscovery(input.addresses, input.client, input.static4),
149
- host: input.host,
150
- };
151
- };
152
- }
153
-
154
- // ── physical() ───────────────────────────────────────────────────────
155
- // The raw case: a real, untagged interface (e.g. { name: "ens19" }).
156
- // No VLAN math, no transfer-link addressing — used for an uplink
157
- // (or segment) that's wired directly, no 802.1Q tagging involved.
158
-
159
- interface PhysicalUplinkInput {
160
- readonly id: string | number;
161
- readonly name: string;
162
- readonly nat?: Nat;
163
- readonly host: Host;
164
- readonly addresses?: AddressSpec;
165
- /** Which program acquires the IPv4 lease when addresses.ipv4 is
166
- * "dhcp" (or omitted, same default). Defaults to "dhclient" — see
167
- * DhcpClient (types.ts). Set to "static" (with `static4` below) for
168
- * a real interface whose address is fixed/known rather than leased. */
169
- readonly client?: DhcpClient;
170
- /** Only consulted when client === "static" — see StaticIpv4 (types.ts). */
171
- readonly static4?: StaticIpv4;
172
- }
173
-
174
- export function uplinkPhysical(input: PhysicalUplinkInput): UplinkBuilder {
175
- const id = uplinkId(
176
- typeof input.id === "string" ? Number.parseInt(input.id, 10) : input.id,
177
- );
178
-
179
- return (allocSlot: () => number) => {
180
- const slot = allocSlot();
181
- const ifc: InterfaceKind = { kind: "physical", name: input.name };
182
-
183
- const addresses: Addresses = [
184
- transferNet(id, slot, 1),
185
- transferNet(id, slot, 2),
186
- ];
187
-
188
- return {
189
- slot,
190
- addresses,
191
- if: ifc,
192
- nat: input.nat,
193
- discovery: resolveDiscovery(input.addresses, input.client, input.static4),
194
- host: input.host,
195
- };
196
- };
197
- }
198
-
199
- // ── dummy() ──────────────────────────────────────────────────────────
200
- // A placeholder uplink with no real backing interface — see
201
- // InterfaceKind's "dummy" variant (types.ts). Lets the OVN
202
- // router/transfer-link/backbone-join chain be built and pinged
203
- // end-to-end before the real mechanism (e.g. a WireGuard tunnel)
204
- // exists. No `name`/`vlanParent` to supply — the generator derives and
205
- // creates the actual kernel interface itself, at a slot-based name.
206
-
207
- interface DummyUplinkInput {
208
- readonly id: string | number;
209
- readonly nat?: Nat;
210
- readonly host: Host;
211
- readonly addresses?: AddressSpec;
212
- readonly client?: DhcpClient;
213
- /** Borrow real egress from another, already-working uplink — see
214
- * Backdoor (types.ts). Needed for any VPN-like uplink (WireGuard,
215
- * ZeroTier, ...): the dummy/tunnel interface itself carries no real
216
- * traffic path, so something else has to get this uplink's own netns
217
- * onto the real internet. `via` must already be declared (e.g.
218
- * isp-primary), earlier in the same defineNetwork call. */
219
- readonly backdoor?: { readonly via: Uplink };
220
- }
221
-
222
- export function uplinkDummy(input: DummyUplinkInput): UplinkBuilder {
223
- const id = uplinkId(
224
- typeof input.id === "string" ? Number.parseInt(input.id, 10) : input.id,
225
- );
226
-
227
- return (allocSlot: () => number) => {
228
- const slot = allocSlot();
229
- const ifc: InterfaceKind = { kind: "dummy" };
230
-
231
- const addresses: Addresses = [
232
- transferNet(id, slot, 1),
233
- transferNet(id, slot, 2),
234
- ];
235
-
236
- // Backdoor gets its OWN slot — allocSlot() called a second time,
237
- // from the same sequential pool as every uplink's front-door
238
- // transfer link, so its /28 (IPv4) can never overlap with this
239
- // uplink's own `addresses` above (see Backdoor's doc comment,
240
- // types.ts, for why that overlap is a real bug and not just
241
- // untidy). host=3/4 here, not 1/2: transferNet's IPv6 fold keys
242
- // only on (id, host), NOT slot, so reusing host 1/2 under the SAME
243
- // id would fold to the IDENTICAL IPv6 addresses as this uplink's
244
- // own front-door transfer link above, even though slot keeps the
245
- // IPv4 side distinct. 3/4 keeps both stacks distinct.
246
- const backdoor: Backdoor | undefined = input.backdoor === undefined
247
- ? undefined
248
- : (() => {
249
- const bdSlot = allocSlot();
250
- return {
251
- via: input.backdoor!.via,
252
- slot: bdSlot,
253
- addresses: [
254
- transferNet(id, bdSlot, 3),
255
- transferNet(id, bdSlot, 4),
256
- ],
257
- };
258
- })();
259
-
260
- return {
261
- slot,
262
- addresses,
263
- if: ifc,
264
- nat: input.nat,
265
- // Defaults to "static"/"static" (no discovery) unlike
266
- // uplinkVlan/uplinkPhysical's "dhcp" default — a dummy interface
267
- // has no real ISP behind it, so dhclient/dhcpcd would just hang
268
- // forever waiting for a lease that never comes. Still overridable
269
- // via `addresses`, in case a future test wants to exercise the
270
- // discovery path against a dummy on purpose.
271
- discovery: resolveDiscovery(
272
- input.addresses ?? { ipv4: "static", ipv6: "static" },
273
- input.client,
274
- ),
275
- backdoor,
276
- host: input.host,
277
- };
278
- };
279
- }
280
-
281
- // ── wireguard() ──────────────────────────────────────────────────────
282
- // A real WireGuard tunnel — see InterfaceKind's "wireguard" variant
283
- // (types.ts) for why this shells out to wg-quick against an
284
- // already-on-disk conf file rather than embedding key material here.
285
- // Almost always paired with `backdoor` (the tunnel's own
286
- // handshake/keepalive traffic needs a mundane path to the real
287
- // internet, same as uplinkDummy() — see Backdoor, types.ts), but not
288
- // required: an uplink whose netns can already reach the internet some
289
- // other way (e.g. it inherited a route) wouldn't need one.
290
-
291
- interface WireguardUplinkInput {
292
- readonly id: string | number;
293
- /** Override the real kernel interface name AND the .conf's basename
294
- * on disk (e.g. "mull-fra" -> /etc/wireguard/mull-fra.conf). Defaults
295
- * to this uplink's OWN name (the string given to net.uplink(name,
296
- * ...)) — redundant to repeat that in most cases, since it's already
297
- * unique per uplink. Only needed when that default doesn't fit: it
298
- * exceeds IFNAMSIZ (15 usable characters), or a real interface under
299
- * a different name already exists and renaming it would mean
300
- * bouncing a live tunnel unnecessarily. */
301
- readonly ifaceName?: string;
302
- /** The full wg-quick conf, declared directly here — see
303
- * WireguardInterfaceConfig and InterfaceKind's "wireguard" variant
304
- * (types.ts) for why the PrivateKey lives in plain sight in this
305
- * file rather than behind an env var, for this uplink specifically. */
306
- readonly config: WireguardInterfaceConfig;
307
- readonly nat?: Nat;
308
- readonly host: Host;
309
- /** Bootstrap egress for the tunnel's own setup/keepalive traffic —
310
- * see Backdoor (types.ts) and emitBackdoorNat (generate-netns.ts),
311
- * which scopes the resulting NAT rule to the backdoor's OWN transit
312
- * address (not any segment subnet) whenever the owning uplink has a
313
- * real interface of its own, as this one does. */
314
- readonly backdoor?: { readonly via: Uplink };
315
- }
316
-
317
- export function uplinkWireguard(input: WireguardUplinkInput): UplinkBuilder {
318
- const id = uplinkId(
319
- typeof input.id === "string" ? Number.parseInt(input.id, 10) : input.id,
320
- );
321
-
322
- return (allocSlot: () => number, name: string) => {
323
- const slot = allocSlot();
324
- const ifc: InterfaceKind = {
325
- kind: "wireguard",
326
- ifaceName: input.ifaceName ?? name,
327
- config: input.config,
328
- };
329
-
330
- const addresses: Addresses = [
331
- transferNet(id, slot, 1),
332
- transferNet(id, slot, 2),
333
- ];
334
-
335
- // Same separate-slot reasoning as uplinkDummy()'s backdoor above —
336
- // see Backdoor's doc comment (types.ts) for why sharing a /28 with
337
- // this uplink's own front-door transfer link is broken, not just
338
- // untidy.
339
- const backdoor: Backdoor | undefined = input.backdoor === undefined
340
- ? undefined
341
- : (() => {
342
- const bdSlot = allocSlot();
343
- return {
344
- via: input.backdoor!.via,
345
- slot: bdSlot,
346
- addresses: [
347
- transferNet(id, bdSlot, 3),
348
- transferNet(id, bdSlot, 4),
349
- ],
350
- };
351
- })();
352
-
353
- return {
354
- slot,
355
- addresses,
356
- if: ifc,
357
- nat: input.nat,
358
- // wg-quick manages the tunnel's own address (and, via its
359
- // fwmark/policy-routing dance, its own default route) entirely —
360
- // nothing for dhclient/dhcpcd/static to do on this interface.
361
- discovery: { ipv4: "static", ipv6: "static" },
362
- backdoor,
363
- host: input.host,
364
- };
365
- };
366
- }
367
-
368
- // ── segment factories ─────────────────────────────────────────────
369
- // Mirror the uplink factories above, but produce a Segment-shaped
370
- // result (adds `uplink`, uses segmentNet() instead of transferNet()
371
- // since a segment has a client-facing gateway address, not a
372
- // transfer-link pair).
373
-
374
- /** input.uplink -> UplinkSelector | undefined. Omitting uplink entirely
375
- * means "no egress yet" (see Segment.uplink, types.ts) — a deliberate,
376
- * representable state, not an oversight. */
377
- function resolveUplinkSelector(
378
- uplink: Uplink | UplinkSelector | undefined,
379
- ): UplinkSelector | undefined {
380
- if (uplink === undefined) return undefined;
381
- return "resolve" in uplink ? uplink : new FixedUplink(uplink);
382
- }
383
-
384
- interface SegmentPhysicalInput {
385
- readonly id: string | number;
386
- readonly name: string;
387
- readonly uplink?: Uplink | UplinkSelector;
388
- readonly nat?: Nat;
389
- /** Advertise RA/SLAAC for this segment's IPv6 prefix. Defaults to true. */
390
- readonly slaac?: boolean;
391
- readonly host: Host;
392
- }
393
-
394
- export function segmentPhysical(
395
- input: SegmentPhysicalInput,
396
- ): Omit<Segment, "name"> {
397
- const id = segmentId(
398
- typeof input.id === "string" ? Number.parseInt(input.id, 10) : input.id,
399
- );
400
- return {
401
- addresses: [segmentNet(id, 2)],
402
- if: { kind: "physical", name: input.name },
403
- uplink: resolveUplinkSelector(input.uplink),
404
- nat: input.nat,
405
- slaac: input.slaac ?? true,
406
- host: input.host,
407
- };
408
- }
409
-
410
- interface SegmentVlanInput {
411
- readonly id: string | number;
412
- readonly vlanParent: string;
413
- /** Override the VLAN tag if it should differ from `id`. Defaults to id. */
414
- readonly vlan?: number;
415
- /** Override the kernel subinterface name — see InterfaceKind
416
- * (types.ts). Only needed if `${vlanParent}.${vlanId}` is already in
417
- * use for something else (e.g. VLAN 129 already carries this host's
418
- * own management IP on `ens18.129` outside OVS). */
419
- readonly ifaceName?: string;
420
- readonly uplink?: Uplink | UplinkSelector;
421
- readonly nat?: Nat;
422
- /** Advertise RA/SLAAC for this segment's IPv6 prefix. Defaults to true. */
423
- readonly slaac?: boolean;
424
- readonly host: Host;
425
- }
426
-
427
- export function segmentVlan(input: SegmentVlanInput): Omit<Segment, "name"> {
428
- const id = segmentId(
429
- typeof input.id === "string" ? Number.parseInt(input.id, 10) : input.id,
430
- );
431
- return {
432
- addresses: [segmentNet(id, 2)],
433
- if: {
434
- kind: "vlan",
435
- vlanParent: input.vlanParent,
436
- vlanId: input.vlan ?? id,
437
- ifaceName: input.ifaceName,
438
- },
439
- uplink: resolveUplinkSelector(input.uplink),
440
- nat: input.nat,
441
- slaac: input.slaac ?? true,
442
- host: input.host,
443
- };
444
- }