Skip to content

Commit 4483c86

Browse files
authored
feat(server,driver-vm,e2e): gateway-owned readiness + VM compute driver e2e (#901)
1 parent 30ddca4 commit 4483c86

23 files changed

Lines changed: 1497 additions & 821 deletions

File tree

Cargo.lock

Lines changed: 15 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

architecture/custom-vm-runtime.md

Lines changed: 177 additions & 177 deletions
Large diffs are not rendered by default.

architecture/gateway.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -605,7 +605,7 @@ The gateway reaches the sandbox exclusively through the supervisor-initiated `Co
605605

606606
- **Create**: The VM driver process allocates a sandbox-specific rootfs from its own embedded `rootfs.tar.zst`, injects an explicitly configured guest mTLS bundle when the gateway callback endpoint is `https://`, then re-execs itself in a hidden helper mode that loads libkrun directly and boots the supervisor.
607607
- **Networking**: The helper starts an embedded `gvproxy`, wires it into libkrun as virtio-net, and gives the guest outbound connectivity. No inbound TCP listener is needed — the supervisor reaches the gateway over its outbound `ConnectSupervisor` stream.
608-
- **Gateway callback**: The guest init script configures `eth0` for gvproxy networking, prefers the configured `OPENSHELL_GRPC_ENDPOINT`, and falls back to host aliases or the gvproxy gateway IP (`192.168.127.1`) when local hostname resolution is unavailable on macOS.
608+
- **Gateway callback**: The guest init script configures `eth0` for gvproxy networking, seeds `/etc/hosts` so `host.openshell.internal` resolves to the gvproxy gateway IP (`192.168.127.1`), preserves gvproxy's legacy `host.containers.internal` / `host.docker.internal` DNS answers, prefers the configured `OPENSHELL_GRPC_ENDPOINT`, and falls back to those aliases or the raw gateway IP when local hostname resolution is unavailable on macOS.
609609
- **Guest boot**: The sandbox guest runs a minimal init script that starts `openshell-sandbox` directly as PID 1 inside the VM.
610610
- **Watch stream**: Emits provisioning, ready, error, deleting, deleted, and platform-event updates so the gateway store remains the durable source of truth.
611611

crates/openshell-driver-vm/Cargo.toml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,5 +37,13 @@ libloading = "0.8"
3737
tar = "0.4"
3838
zstd = "0.13"
3939

40+
# smol-rs/polling drives the BSD/macOS parent-death detection in
41+
# procguard via kqueue's EVFILT_PROC / NOTE_EXIT filter. We could use
42+
# it on Linux too (via epoll + pidfd) but sticking with
43+
# nix::sys::prctl::set_pdeathsig there keeps the Linux path a single
44+
# syscall with no helper thread.
45+
[target.'cfg(any(target_os = "macos", target_os = "ios", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd", target_os = "dragonfly"))'.dependencies]
46+
polling = "3.11"
47+
4048
[lints]
4149
workspace = true

crates/openshell-driver-vm/Makefile

Lines changed: 0 additions & 7 deletions
This file was deleted.

crates/openshell-driver-vm/README.md

Lines changed: 33 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -31,19 +31,15 @@ Sandbox guests execute `/opt/openshell/bin/openshell-sandbox` as PID 1 inside th
3131

3232
## Quick start (recommended)
3333

34-
`start.sh` handles runtime setup, builds, codesigning, and environment wiring. From the repo root:
3534

3635
```shell
37-
crates/openshell-driver-vm/start.sh
36+
mise run gateway:vm
3837
```
3938

40-
or equivalently:
41-
42-
```shell
43-
make -C crates/openshell-driver-vm start
44-
```
4539

46-
First run takes a few minutes while `mise run vm:setup` stages libkrun/libkrunfw/gvproxy and `mise run vm:rootfs -- --base` builds the embedded rootfs. Subsequent runs are cached. State lives under `target/openshell-vm-driver-dev/` (SQLite DB + per-sandbox rootfs + `compute-driver.sock`).
40+
First run takes a few minutes while `mise run vm:setup` stages libkrun/libkrunfw/gvproxy and `mise run vm:rootfs -- --base` builds the embedded rootfs. Subsequent runs are cached. To keep the Unix socket path under macOS `SUN_LEN`, `mise run gateway:vm` and `start.sh` default the state dir to `/tmp/openshell-vm-driver-dev-$USER-port-$PORT/` (SQLite DB + per-sandbox rootfs + `compute-driver.sock`) unless `OPENSHELL_VM_DRIVER_STATE_DIR` is set.
41+
The wrapper also prints the recommended gateway name (`vm-driver-port-$PORT` by default) plus the exact repo-local `scripts/bin/openshell gateway add` and `scripts/bin/openshell gateway select` commands to use from another terminal. This avoids accidentally hitting an older `openshell` binary elsewhere on your `PATH`.
42+
It also exports `OPENSHELL_DRIVER_DIR=$PWD/target/debug` before starting the gateway so local dev runs use the freshly built `openshell-driver-vm` instead of an older installed copy from `~/.local/libexec/openshell` or `/usr/local/libexec`.
4743

4844
Override via environment:
4945

@@ -53,10 +49,33 @@ OPENSHELL_SSH_HANDSHAKE_SECRET=$(openssl rand -hex 32) \
5349
crates/openshell-driver-vm/start.sh
5450
```
5551

52+
Run multiple dev gateways side by side by giving each one a unique port. The wrapper derives a distinct default state dir from that port automatically:
53+
54+
```shell
55+
OPENSHELL_SERVER_PORT=8080 mise run gateway:vm
56+
OPENSHELL_SERVER_PORT=8081 mise run gateway:vm
57+
```
58+
59+
If you want a custom suffix instead of `port-$PORT`, set `OPENSHELL_VM_INSTANCE`:
60+
61+
```shell
62+
OPENSHELL_SERVER_PORT=8082 \
63+
OPENSHELL_VM_INSTANCE=feature-a \
64+
mise run gateway:vm
65+
```
66+
67+
If you want a custom CLI gateway name, set `OPENSHELL_VM_GATEWAY_NAME`:
68+
69+
```shell
70+
OPENSHELL_SERVER_PORT=8082 \
71+
OPENSHELL_VM_GATEWAY_NAME=vm-feature-a \
72+
mise run gateway:vm
73+
```
74+
5675
Teardown:
5776

5877
```shell
59-
rm -rf target/openshell-vm-driver-dev
78+
rm -rf /tmp/openshell-vm-driver-dev-$USER-port-8080
6079
```
6180

6281
## Manual equivalent
@@ -78,16 +97,17 @@ codesign \
7897
--force -s - target/debug/openshell-driver-vm
7998

8099
# 4. Start the gateway with the VM driver
81-
mkdir -p target/openshell-vm-driver-dev
100+
mkdir -p /tmp/openshell-vm-driver-dev-$USER-port-8080
82101
target/debug/openshell-gateway \
83102
--drivers vm \
84103
--disable-tls \
85-
--database-url sqlite:target/openshell-vm-driver-dev/openshell.db \
104+
--database-url sqlite:/tmp/openshell-vm-driver-dev-$USER-port-8080/openshell.db \
105+
--driver-dir $PWD/target/debug \
86106
--grpc-endpoint http://host.containers.internal:8080 \
87107
--ssh-handshake-secret dev-vm-driver-secret \
88108
--ssh-gateway-host 127.0.0.1 \
89109
--ssh-gateway-port 8080 \
90-
--vm-driver-state-dir $PWD/target/openshell-vm-driver-dev
110+
--vm-driver-state-dir /tmp/openshell-vm-driver-dev-$USER-port-8080
91111
```
92112

93113
The gateway resolves `openshell-driver-vm` in this order: `--driver-dir`, conventional install locations (`~/.local/libexec/openshell`, `/usr/local/libexec/openshell`, `/usr/local/libexec`), then a sibling of the gateway binary.
@@ -97,7 +117,7 @@ The gateway resolves `openshell-driver-vm` in this order: `--driver-dir`, conven
97117
| Flag | Env var | Default | Purpose |
98118
|---|---|---|---|
99119
| `--drivers vm` | `OPENSHELL_DRIVERS` | `kubernetes` | Select the VM compute driver. |
100-
| `--grpc-endpoint URL` | `OPENSHELL_GRPC_ENDPOINT` || Required. URL the sandbox guest calls back to. Use a host alias that resolves to the gateway's host from inside the VM (gvproxy answers `host.containers.internal` and `host.openshell.internal` to `192.168.127.1`). |
120+
| `--grpc-endpoint URL` | `OPENSHELL_GRPC_ENDPOINT` || Required. URL the sandbox guest calls back to. Use a host alias that resolves to the gateway's host from inside the VM (`host.containers.internal` comes from gvproxy DNS; the guest init script also seeds `host.openshell.internal` to `192.168.127.1`). |
101121
| `--vm-driver-state-dir DIR` | `OPENSHELL_VM_DRIVER_STATE_DIR` | `target/openshell-vm-driver` | Per-sandbox rootfs, console logs, and the `compute-driver.sock` UDS. |
102122
| `--driver-dir DIR` | `OPENSHELL_DRIVER_DIR` | unset | Override the directory searched for `openshell-driver-vm`. |
103123
| `--vm-driver-vcpus N` | `OPENSHELL_VM_DRIVER_VCPUS` | `2` | vCPUs per sandbox. |

crates/openshell-driver-vm/scripts/openshell-vm-sandbox-init.sh

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
set -euo pipefail
1010

1111
BOOT_START=$(date +%s%3N 2>/dev/null || date +%s)
12+
GVPROXY_GATEWAY_IP="192.168.127.1"
1213

1314
ts() {
1415
local now
@@ -72,6 +73,20 @@ tcp_probe() {
7273
fi
7374
}
7475

76+
ensure_host_gateway_aliases() {
77+
local hosts_tmp="/tmp/openshell-hosts.$$"
78+
79+
if [ -f /etc/hosts ]; then
80+
grep -vE '(^|[[:space:]])host\.openshell\.internal([[:space:]]|$)' /etc/hosts > "$hosts_tmp" || true
81+
else
82+
: > "$hosts_tmp"
83+
fi
84+
85+
printf '%s host.openshell.internal\n' "$GVPROXY_GATEWAY_IP" >> "$hosts_tmp"
86+
cat "$hosts_tmp" > /etc/hosts
87+
rm -f "$hosts_tmp"
88+
}
89+
7590
rewrite_openshell_endpoint_if_needed() {
7691
local endpoint="${OPENSHELL_ENDPOINT:-}"
7792
[ -n "$endpoint" ] || return 0
@@ -92,7 +107,7 @@ rewrite_openshell_endpoint_if_needed() {
92107
return 0
93108
fi
94109

95-
for candidate in host.containers.internal host.docker.internal 192.168.127.1; do
110+
for candidate in host.openshell.internal host.containers.internal host.docker.internal "$GVPROXY_GATEWAY_IP"; do
96111
if [ "$candidate" = "$host" ]; then
97112
continue
98113
fi
@@ -163,18 +178,20 @@ DHCP_SCRIPT
163178
if ! udhcpc -i eth0 -f -q -n -T 1 -t 3 -A 1 -s "$UDHCPC_SCRIPT" 2>&1; then
164179
ts "WARNING: DHCP failed, falling back to static config"
165180
ip addr add 192.168.127.2/24 dev eth0 2>/dev/null || true
166-
ip route add default via 192.168.127.1 2>/dev/null || true
181+
ip route add default via "$GVPROXY_GATEWAY_IP" 2>/dev/null || true
167182
fi
168183
else
169184
ts "no DHCP client, using static config"
170185
ip addr add 192.168.127.2/24 dev eth0 2>/dev/null || true
171-
ip route add default via 192.168.127.1 2>/dev/null || true
186+
ip route add default via "$GVPROXY_GATEWAY_IP" 2>/dev/null || true
172187
fi
173188

174189
if [ ! -s /etc/resolv.conf ]; then
175190
echo "nameserver 8.8.8.8" > /etc/resolv.conf
176191
echo "nameserver 8.8.4.4" >> /etc/resolv.conf
177192
fi
193+
194+
ensure_host_gateway_aliases
178195
else
179196
ts "WARNING: eth0 not found; supervisor will start without guest egress"
180197
fi

0 commit comments

Comments
 (0)