Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 66 additions & 0 deletions .github/scripts/check_image_build_inputs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
#!/usr/bin/env python3

import hashlib
import json
import sys
import urllib.error
import urllib.parse
from pathlib import Path

ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(ROOT / "src"))

from sbx.image.kernel_inputs import KERNEL_INPUTS, fetch, raw_url # noqa: E402

BRANCHES = {"CelestoAI/SmolVM": "main", "moby/moby": "master"}
LEGAL_FILES = {
"CelestoAI/SmolVM": ("LICENSE", "NOTICE"),
"moby/moby": ("LICENSE", "NOTICE"),
}


def _optional_fetch(url: str, fetcher=fetch) -> bytes | None:
try:
return fetcher(url)
except urllib.error.HTTPError as exc:
if exc.code == 404:
return None
raise


def _latest_commit(repository: str, path: str, fetcher=fetch) -> str:
query = urllib.parse.urlencode({"path": path, "per_page": 1})
data = json.loads(fetcher(f"https://api.github.com/repos/{repository}/commits?{query}"))
return data[0]["sha"]


def check_updates(fetcher=fetch) -> int:
changed = False
for name, (repository, _commit, path, expected) in KERNEL_INPUTS.items():
latest = fetcher(raw_url(repository, BRANCHES[repository], path))
digest = hashlib.sha256(latest).hexdigest()
if digest != expected:
changed = True
latest_commit = _latest_commit(repository, path, fetcher)
print(f"{name}: update available")
print(f" commit: {latest_commit}")
print(f" sha256: {digest}")

for repository, paths in LEGAL_FILES.items():
pinned_commit = next(
source[1] for source in KERNEL_INPUTS.values() if source[0] == repository
)
for path in paths:
pinned = _optional_fetch(raw_url(repository, pinned_commit, path), fetcher)
latest = _optional_fetch(raw_url(repository, BRANCHES[repository], path), fetcher)
if pinned != latest:
changed = True
print(f"{repository}/{path}: licensing review required")

if not changed:
print("Image build inputs are current.")
return int(changed)


if __name__ == "__main__":
raise SystemExit(check_updates())
42 changes: 24 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ sbx run my-sbx --agent claude
| `network forward [NAME] SPEC...` | Temporarily forward host TCP ports to a running sandbox until Ctrl-C. |
| `network auth-port [NAME]` | Expert helper: manually expose the OAuth callback port for an already-running sandbox. |
| `network close-auth-port [NAME]` | Expert helper: close the tracked OAuth callback tunnel. |
| `image build-debian` | Advanced helper: build a local Debian/Pi image, optionally with `--with-docker`. |
| `image build` | Build the curated local Pi/rootless-Docker image. |
| `image ls` | List local ready-to-run images under `~/.smolvm/images`. |
| `doctor` | Run non-sudo SmolVM diagnostics for QEMU. |
| `completion SHELL` | Generate shell completion for `bash`, `zsh`, or `fish`. |
Expand All @@ -161,42 +161,48 @@ List local ready-to-run images:
sbx image ls
```

Build the default Debian/Pi image:
Build the curated image and create project configuration while starting it:

```bash
sbx image build-debian --name debian-sbx
sbx image build
sbx run the-quest \
--image '~/.smolvm/images/sbx' \
--run-user agent \
--project-path . \
--writable-mounts \
--write-config
```

Use it from `.sbx.toml`:
The suggested project configuration is:

```toml
[sbx]
image = "~/.smolvm/images/debian-sbx"
name = "the-quest"
agent = "pi"
image = "~/.smolvm/images/sbx"
project_path = "."
run_user = "agent"
writable_mounts = true
copy_host_credentials = false
git_config = true
```

`--write-config` belongs to `run` and `create`; `image build` never changes `.sbx.toml`.

Configure durable TCP forwards applied when the VM starts:

```toml
[sbx]
port_forwards = ["3000", "8080:3000"]
```

Build with Docker support:
The default image includes rootless Docker. For a larger Docker data/build cache, increase its rootfs:

```bash
sbx image build-debian --with-docker --name debian-sbx-docker --rootfs-size-mb 81920
```

Use the Docker-capable image:

```toml
[sbx]
image = "~/.smolvm/images/debian-sbx-docker"
run_user = "agent"
sbx image build --rootfs-size-mb 81920
```

These Debian/Pi images should run as `agent`; Docker rootless also uses that user. Docker-capable images show `docker` in `sbx image ls` and start rootless Docker at boot.
These Debian/Pi images should run as `agent`; rootless Docker also uses that user. Built images show `docker` in `sbx image ls` and start rootless Docker at boot.

For details, see [`docs/build-local-debian-pi-image.md`](docs/build-local-debian-pi-image.md). For contributor setup and test commands, see [`docs/development.md`](docs/development.md).

Expand All @@ -220,7 +226,7 @@ Example `.sbx.toml`:
```toml
[sbx]
agent = "pi" # pi, claude, or codex
name = "project-sbx"
name = "the-quest"
memory = 4096
cpus = 2
disk_size = 20480
Expand Down Expand Up @@ -298,7 +304,7 @@ See [`docs/environment-forwarding.md`](docs/environment-forwarding.md) for detai

### Local ready-to-run image directories

When `[sbx].image` is set, `sbx` treats the image as already containing the selected agent. It boots the image directly with the SmolVM SDK and then attaches to run the agent command; it does not run SmolVM's preset installer. If `[sbx].disk_size` is set for a local raw ext4 image, `sbx` asks SmolVM to create an isolated disk of that size and grow the filesystem. For the end-to-end Debian/Pi workflow, including optional Docker support with `--with-docker`, see [`docs/build-local-debian-pi-image.md`](docs/build-local-debian-pi-image.md).
When `[sbx].image` is set, `sbx` treats the image as already containing the selected agent. It boots the image directly with the SmolVM SDK and then attaches to run the agent command; it does not run SmolVM's preset installer. If `[sbx].disk_size` is set for a local raw ext4 image, `sbx` asks SmolVM to create an isolated disk of that size and grow the filesystem. For the end-to-end Debian/Pi and rootless Docker workflow, see [`docs/build-local-debian-pi-image.md`](docs/build-local-debian-pi-image.md).

Example layout:

Expand Down
Loading