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
28 changes: 24 additions & 4 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -15,24 +15,44 @@ CRATERA_KERNEL=./images/vmlinux.bin
CRATERA_ROOTFS=./images/rootfs.ext4
CRATERA_WORK_DIR=/var/tmp/cratera

# API and executor deadlines.
CRATERA_RUN_MS=2000
CRATERA_SUBMIT_MS=5000
CRATERA_MAX_TIME_MS=10000
CRATERA_COMPILE_TIMEOUT_SECS=12

# Per-microVM hardware allocation.
CRATERA_VCPU=2
CRATERA_MEM_MIB=2048

# Per-microVM host cgroup limits. When increasing concurrency, reserve at least
# CRATERA_MAX_CONCURRENT_JOBS * CRATERA_JAIL_MEM_MAX bytes of host memory.
CRATERA_JAIL_MEM_MAX=3221225472
CRATERA_JAIL_CPU_MAX=200000 100000
CRATERA_JAIL_PIDS_MAX=64

# Submission scheduler. Budget host memory and CPU for each active microVM.
CRATERA_MAX_CONCURRENT_JOBS=1
CRATERA_MAX_QUEUED_JOBS=64
CRATERA_QUEUE_TIMEOUT_MS=10000

# 0 for smoke tests; 1 on the host with jailer uid 20001
CRATERA_USE_JAILER=0
CRATERA_JAIL_UID=20001
CRATERA_JAIL_GID=20001

# Snapshot restore needs jailer (portable /vsock/job.sock paths).
# Production unit sets CRATERA_USE_SNAPSHOT=1, but this file overrides the unit
# if present at /opt/cratera/.env; keep it 1 on the judge box.
# The production systemd unit enforces its snapshot and runtime paths.
CRATERA_USE_SNAPSHOT=0
# CRATERA_SNAPSHOT_DIR=./images/snapshot
CRATERA_SNAPSHOT_DIR=./images/snapshot

# Language runtime configuration
# Built-in presets: rust (default), cpp, c, python, go, node, zig
CRATERA_LANGUAGE=rust
# CRATERA_LANGUAGES_FILE=./languages.toml
# Optional custom overrides:
# CRATERA_SOURCE_FILE=/tmp/job.rs
# CRATERA_COMPILE_CMD=rustc --edition 2024 -C panic=abort -C opt-level=2 -o /tmp/job /tmp/job.rs
# CRATERA_RUN_CMD=/tmp/job

RUST_LOG=cratera=info,cratera_api=info,cratera_executor=info,tower_http=info

6 changes: 3 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,8 @@ jobs:
if: steps.filter.outputs.vm_relevant == 'true'
run: |
sudo usermod -aG kvm $USER 2>/dev/null || true
sudo chmod 666 /dev/kvm 2>/dev/null || true
sudo chown root:kvm /dev/kvm 2>/dev/null || true
sudo chmod 660 /dev/kvm 2>/dev/null || true
if [ -e /dev/kvm ]; then
echo "KVM ready: /dev/kvm accessible"
else
Expand All @@ -143,7 +144,7 @@ jobs:

- name: Run In-Guest Smoke Test (Hardware MicroVM)
if: steps.filter.outputs.vm_relevant == 'true'
run: ./scripts/smoke.sh minimal
run: sg kvm -c './scripts/smoke.sh minimal'

- name: Skip notice (no microVM-relevant files changed)
if: steps.filter.outputs.vm_relevant != 'true'
Expand All @@ -169,4 +170,3 @@ jobs:

- name: Build release guest agent (musl static)
run: cargo build --release -p cratera-guest-agent --target x86_64-unknown-linux-musl

11 changes: 6 additions & 5 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ members = [
resolver = "3"

[workspace.package]
version = "1.1.0"
version = "1.2.0"
edition = "2024"
rust-version = "1.97"
authors = ["Rustu <contact@cratera.org>"]
Expand Down
66 changes: 58 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,27 +108,65 @@ Cratera is organized as a Cargo workspace:

## Quickstart

### Local development without KVM

You can build the workspace and run unit and contract tests on any platform
with a supported Rust toolchain. These checks do not start Firecracker
microVMs:

```bash
git clone https://github.com/cratera-project/cratera.git
cd cratera
cargo test --workspace
```

To exercise microVM execution, use x86_64 Linux with `/dev/kvm`, or a Linux VM
or WSL2 installation that exposes nested KVM. The KVM installer below exits
early on other hosts.

### Prerequisites

- Linux on x86_64 with `/dev/kvm` hardware virtualization.
- Docker or rootless Podman to build the guest root filesystem.
- Rust toolchain 1.80+ if building from source.
- Rust toolchain 1.97+ if building from source.

### Installation

**Option A: Automated setup script (Recommended)**
```bash
# Clones, downloads kernel, builds guest rootfs, and compiles cratera
# Clones, downloads the kernel, builds a minimal Rust-only guest rootfs, and compiles cratera
git clone https://github.com/cratera-project/cratera.git
cd cratera
./scripts/install.sh
./scripts/install.sh --yes
```
The installer keeps the default image small and quick to build. To install every
configured language explicitly, use `./scripts/install.sh --yes --languages=all`.
Use `--languages=<preset>` or `--languages=<language,language>` for another selection.

### Start the coordinator

From the repository root, start the verified release binary:
```bash
./target/release/cratera serve
```
The installer leaves systemd disabled by default. To opt into the production
service after installation, run `./target/release/cratera service enable`.
For the production systemd unit, host permissions, and private ingress guidance,
see [docs/deployment.md](docs/deployment.md).

**Option B: Install binary via Cargo**
```bash
cargo install cratera
```
*Note: The binary requires guest images (kernel and rootfs); run `./scripts/install.sh` or `cratera doctor` to verify environment assets.*
This installs the coordinator binary only. It does not download Firecracker,
the guest kernel, or build the rootfs it needs for execution. On an x86_64
Linux host, run `./scripts/install.sh --yes` from a checkout to provision those
runtime assets, or use `cratera doctor` to check an existing asset setup.

The default installer provisions only Rust for a small, quick first image.
Enable additional languages explicitly with `--languages=<preset>` or
`--languages=<language,language>`; use `--languages=all` only when the full
language image is required.

---

Expand Down Expand Up @@ -177,6 +215,8 @@ Send a `POST /harness` request to execute code in an isolated microVM instance.
| `400` | Invalid request payload or missing required `code` parameter. |
| `401` | Missing or invalid Bearer authentication token. |
| `500` | Internal infrastructure or microVM initialization failure. |
| `503` | The bounded queue is full (`queue_full`), its wait deadline elapsed (`queue_timeout`), or the microVM failed to boot (`boot_timeout`). |
| `504` | The end-to-end submission lifecycle exceeded its deadline (`execution_deadline`). |

### Running Examples

Expand Down Expand Up @@ -260,7 +300,7 @@ Each entry in [`languages.toml`](languages.toml) defines how a compiler is insta
To change which languages are available in the root filesystem:

1. Update [`languages.toml`](languages.toml) or run `cratera lang` to toggle runtimes.
2. Rebuild the root filesystem with `./scripts/build-rootfs.sh` (or `cratera build`).
2. Rebuild the root filesystem with `./scripts/install.sh --yes --languages=all` (or `./scripts/build-rootfs.sh`).

For recipe options and examples, read [docs/languages.md](docs/languages.md).

Expand All @@ -278,6 +318,9 @@ Cratera reads settings from environment variables or a `.env` file:
| `CRATERA_MEM_MIB` | `2048` | Guest RAM allocated per microVM in MiB. |
| `CRATERA_RUN_MS` | `2000` | Execution time limit for test runs in milliseconds. |
| `CRATERA_SUBMIT_MS` | `5000` | Execution time limit for submissions in milliseconds. |
| `CRATERA_MAX_CONCURRENT_JOBS` | `1` | Maximum number of microVM jobs executing simultaneously. |
| `CRATERA_MAX_QUEUED_JOBS` | `64` | Maximum submissions waiting for an execution slot. |
| `CRATERA_QUEUE_TIMEOUT_MS` | `10000` | Maximum queue wait in milliseconds. |
| `CRATERA_USE_JAILER` | `0` | Development default: `0` (disabled for local testing; production systemd service sets `1` for UID 20001 chroot). |

For the complete list of variables and defaults, see [docs/configuration.md](docs/configuration.md).
Expand All @@ -300,7 +343,14 @@ Type=simple
User=root
WorkingDirectory=/opt/cratera
EnvironmentFile=-/opt/cratera/.env
ExecStart=/opt/cratera/cratera
Environment=NODE_ENV=production
Environment=CRATERA_FIRECRACKER=/usr/local/bin/firecracker
Environment=CRATERA_JAILER=/usr/local/bin/jailer
Environment=CRATERA_KERNEL=/opt/cratera/images/vmlinux.bin
Environment=CRATERA_ROOTFS=/opt/cratera/images/rootfs.ext4
Environment=CRATERA_WORK_DIR=/var/lib/cratera
Environment=CRATERA_USE_JAILER=1
ExecStart=/usr/bin/env NODE_ENV=production CRATERA_FIRECRACKER=/usr/local/bin/firecracker CRATERA_JAILER=/usr/local/bin/jailer CRATERA_KERNEL=/opt/cratera/images/vmlinux.bin CRATERA_ROOTFS=/opt/cratera/images/rootfs.ext4 CRATERA_WORK_DIR=/var/lib/cratera CRATERA_USE_JAILER=1 /opt/cratera/cratera serve
Restart=on-failure
RestartSec=3
LimitNOFILE=65536
Expand Down Expand Up @@ -372,12 +422,12 @@ For setup guides and examples, see [docs/deployment.md](docs/deployment.md#ingre

### Permission denied on `/dev/kvm`

Add your user account to the `kvm` group:
Add your user account to the `kvm` group, then start a new login shell:
```bash
sudo usermod -aG kvm $USER
newgrp kvm
```
*Note: Membership in the `kvm` group grants access to host virtualization ioctls; treat it as a privileged capability.*
The host setup script keeps `/dev/kvm` owned by `root:kvm` with mode `0660`, including after device recreation. Membership in the `kvm` group grants access to host virtualization ioctls; treat it as a privileged capability.

### Language not found in manifest

Expand Down
6 changes: 3 additions & 3 deletions crates/api/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@ name = "cratera"
path = "src/main.rs"

[dependencies]
cratera-common = { path = "../common", version = "1.1.0" }
cratera-compiler = { path = "../compiler", version = "1.1.0" }
cratera-executor = { path = "../executor", version = "1.1.0" }
cratera-common = { path = "../common", version = "1.2.0" }
cratera-compiler = { path = "../compiler", version = "1.2.0" }
cratera-executor = { path = "../executor", version = "1.2.0" }
tokio = { workspace = true }
axum = { workspace = true }
tower-http = { workspace = true }
Expand Down
24 changes: 19 additions & 5 deletions crates/api/src/interactive.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const RESET: &str = "\x1b[0m";
pub async fn run_interactive() -> anyhow::Result<()> {
loop {
let server_active = server::is_server_running().await;
let local_server_active = server::get_server_pid().is_some();
let server_addr = server::get_server_addr().await;
let svc_active = service::is_service_active();
let svc_installed = service::is_service_installed();
Expand All @@ -34,11 +35,15 @@ pub async fn run_interactive() -> anyhow::Result<()> {
" {BOLD}{BOLD}[5]{RESET} {BOLD}Build / Rebuild Guest Rootfs Image{RESET} {DIM}(SquashFS / ext4){RESET}"
);

if server_active {
if local_server_active {
println!(
" {BOLD}{RED}[6]{RESET} {BOLD}Stop Interactive Local Server{RESET} {GREEN}[Active on {}]{RESET}",
server_addr
);
} else if server_active {
println!(
" {BOLD}{YELLOW}[6]{RESET} {BOLD}Interactive Local Server{RESET} {DIM}[Port is owned by systemd or another process]{RESET}"
);
} else {
println!(
" {BOLD}{GREEN}[6]{RESET} {BOLD}Start Interactive Local Server in Background{RESET} {DIM}[Stopped]{RESET}"
Expand Down Expand Up @@ -81,13 +86,18 @@ pub async fn run_interactive() -> anyhow::Result<()> {
pause();
}
"6" => {
if server_active {
if local_server_active {
let stopped = server::stop_server().await;
if stopped {
println!("\n{BOLD}{YELLOW}✓ Stopped HTTP Coordinator Server.{RESET}");
} else {
println!("\n{DIM}Server was already stopped.{RESET}");
}
} else if server_active {
println!(
"\n{DIM}Port 3100 is already served by systemd or another process; no local server was started.{RESET}"
);
println!(" {DIM}Use [7] to manage the systemd service.{RESET}");
} else {
match server::start_server_background().await {
Ok(addr) => {
Expand Down Expand Up @@ -341,14 +351,18 @@ async fn handle_tester_menu() -> anyhow::Result<()> {
let choice = prompt_input("Select an option [0-2] > ");
match choice.trim() {
"1" => {
let _ = tester::run_test(&["all".into()]).await;
if let Err(error) = tester::run_test(&["all".into()]).await {
eprintln!("{RED}Smoke test failed:{RESET} {error}");
}
pause();
}
"2" => {
let lang =
prompt_input("Enter language to test (e.g. rust, python, node, cpp, go) > ");
if !lang.trim().is_empty() {
let _ = tester::run_test(&[lang.trim().to_string()]).await;
if !lang.trim().is_empty()
&& let Err(error) = tester::run_test(&[lang.trim().to_string()]).await
{
eprintln!("{RED}Smoke test failed:{RESET} {error}");
}
pause();
}
Expand Down
Loading
Loading