Per-repo documentation — each repo's docs/ and README, ingested and associated with the repo. Rendered through the <<<RepoDocs>>> tag.
← gitlab__testmonkeyalpha-group__git-surface-mcp docs · docs/workstation-fabric-host.md
The git-surface fabric (fabric_up / fabric_launch / fabric_leases / fabric_instances / fabric_attach) does not run on the Mac. It lives inside a single Linux VM — the workstation libvirt domain (qemu:///session, reachable at ssh -p 2225 [email protected]). Inside that one VM, OVS is native, so every macOS limitation (no kernel OVS, no tap devices, no <virtualport>, no privilege) simply doesn't apply. Clients are Incus instances with a bridged NIC on the OVS bridge fabric; they DHCP a 10.1.0.x from the fabric's own dnsmasq.
Mac (macOS/arm64/HVF)
└── libvirt domain "workstation" (Ubuntu noble cloud image)
├── OVS bridge fabric 10.1.0.1/24 (gateway)
├── dnsmasq DHCP 10.1.0.50–200, NAT (MASQUERADE) out enp0s30 (the SLIRP uplink)
└── Incus instances eth0 = bridged nic, parent=fabric → 10.1.0.x
The host is built from the Ubuntu cloud image overlaid + cloud-init-seeded. This is the single most important lesson: earlier attempts booted the Ubuntu desktop ISO (casper / autoinstall) and never provisioned — wrong base entirely. The cloud image + NoCloud seed is the correct, fully automated path.
Artifacts (all under ~/bin/config/vm/): - noble-arm64-base.img — Ubuntu noble arm64 cloud image (the seedable base) - workstation.qcow2 — 20 GiB qcow2 overlay on the base (qemu-img create -f qcow2 -F qcow2 -b) - workstation-seed.iso — NoCloud CIDATA seed built from ~/code/vm/workstation/seed/ (hdiutil makehybrid -iso -joliet -default-volume-name CIDATA) - workstation-vars.fd — per-domain edk2 UEFI nvram (raw copy of edk2-arm-vars.fd) - workstation.xml — the domain: edk2 UEFI (raw loader + raw nvram), vda=overlay, sda=seed cdrom (virtio-scsi), a virtio-9p codeshare→~/code mount, VNC, and a user-net uplink with hostfwd=tcp::2225-:22 on an explicit high PCI slot (bus=pcie.0,addr=0x1e, to dodge the libvirt pcie-root-port slot-1 collision).
The seed (~/code/vm/workstation/seed/user-data, cloud-config): user steve (sudo, incus-admin), packages openvswitch-switch incus git python3 bridge-utils uidmap, enables openvswitch-switch, mounts the 9p codeshare at /mnt/code. meta-data carries instance-id — bump it to force cloud-init to re-run on a fresh overlay.
The MCP sshes with BatchMode=yes (no IdentityFile) — so it offers only default/agent identities and cannot fall back to a password. The original seed set a password for steve but no authorized key, so the MCP could never authenticate — the host read as unreachable. Fixed at the root, three parts:
1. A dedicated key ~/.ssh/workstation (ed25519, no passphrase) — for [email protected] only. 2. Its pubkey is authorized in the seed (steve.ssh_authorized_keys). 3. A scoped ~/.ssh/config stanza so the MCP's keyless ssh offers exactly that key:
Match user steve host 127.0.0.1
IdentityFile ~/.ssh/workstation
IdentitiesOnly yes
The key is not a secret worth guarding like the wallet seed, but it is host-scoped and passphrase-less by design (unattended MCP access to a loopback-only VM).
# 1. build (once) — overlay, nvram, seed
cd ~/bin/config/vm
qemu-img create -f qcow2 -F qcow2 -b noble-arm64-base.img workstation.qcow2 20G
cp -f /opt/homebrew/share/qemu/edk2-arm-vars.fd workstation-vars.fd
hdiutil makehybrid -quiet -iso -joliet -default-volume-name CIDATA \
-o workstation-seed.iso ~/code/vm/workstation/seed/
# 2. define + boot; cloud-init takes several minutes (installs OVS + Incus over the uplink)
virsh -c qemu:///session define ~/bin/config/vm/workstation.xml
virsh -c qemu:///session start workstation
ssh -p 2225 -i ~/.ssh/workstation [email protected] 'cloud-init status --wait'
# 3. one-time, inside the VM: incus needs a storage pool; forwarding for NAT
ssh -p 2225 -i ~/.ssh/workstation [email protected] \
'incus admin init --minimal && \
echo net.ipv4.ip_forward=1 | sudo tee /etc/sysctl.d/99-fabric.conf && \
sudo sysctl -w net.ipv4.ip_forward=1'
# 4. drive the fabric via the MCP: fabric_up -> fabric_launch <name> -> fabric_leases
Verified state after fabric_up: {bridge: fabric, gateway_10.1.0.1: true, dhcp_running: true, nat_out: true, healthy: true}.
- incus not initialized → fabric_launch's incus init fails with no storage pool. Run incus admin init --minimal once (dir pool, no incus-managed network — we bridge to OVS manually). - ip_forward off → the MCP's fabric_up sets the MASQUERADE rule but not forwarding; instances get a lease but can't route out. Set net.ipv4.ip_forward=1 (persisted above). - 9p may not mount on some qemu/macOS builds; the seed uses nofail, and the MCP works over ssh regardless — /mnt/code is a convenience (the reference fabric_up.sh), not a dependency. - fabric_launch pulls an image (default images:debian/12) over the uplink on first use — minutes, and non-deterministic. Pre-seed a local Incus image, or keep a warm base instance, for fast/repeatable spins (see network-replay.md).