project-structure
Use when adding or moving code in envd-rs — new endpoint, Connect RPC, middleware, helper, module or test. Says which file/folder it belongs in and the code style rules (crates.io-style layout, no mod
Files1
View on GitHubSKILL.md.claude/skills/project-structure/SKILL.md
GitHubenvd-rs project structure
Layout follows rust-lang/crates.io: foo.rs declares submodules that live in foo/. Never create mod.rs.
Cargo.toml workspace only: members = ["crates/*"], release profile
crates/orchestrator/ orchestrator crate (same layout rules as envd below)
src/storage/ object_store backends (backend.rs: Storage::open per provider, signed UploadUrl;
backend/providers.rs: S3, GCS, Azure builders; backend/gcs/ ADC, credential flows, external
accounts, json.rs + credsfile.rs (credential JSON with case-insensitive keys, nulls skipped); backend/azure/ DefaultAzureCredential chain, AAD, WS-Trust, SAS; backend/aws.rs + aws/sse.rs S3 server-side encryption headers), object_path.rs (validate_object_path), spec.rs (StorageSpec, upstream storage URL
parsing), local_upload.rs (HMAC UploadSigner), build paths, header V3
src/go_url.rs Go net/url parse and String (go_url/escape.rs, go_url/query.rs url.Values)
src/command.rs Command: run a tool with stdin closed, output or combined output, CommandFailure with the exit
code; fs.rs (path_error "op path: io error", mkdir_all, temp files, parent)
src/builder/ template builder: config.rs (BuilderConfig env), template_files.rs (+ render.rs Go template
subset, layers.rs files/symlink tars, hash.rs filesHash/provision version; assets/template/),
ext4.rs (+ stats.rs debugfs parsing), oci/ (reference.rs go-containerregistry names, registry.rs
distribution client, auth.rs, ecr.rs SigV4, pull.rs GetPublicImage, untar.rs moby untar with
whiteouts, export.rs layers top-first, overlay_mount.rs new mount API, copy.rs rsync),
rootfs.rs (+ unpack.rs CreateExt4Filesystem), provision.rs (console marker scan),
phases.rs (BuildEnv) + phases/base.rs (+ build.rs), pipeline.rs (PhasePipeline)
src/go_strconv.rs strconv.ParseBool accepted values for env and URL flags; go_path.rs (path.Clean, Join, Dir, Base)
src/go_gzip.rs Go compress/gzip + flate BestSpeed port (layer digests byte for byte); go_tar.rs (Go archive/tar
writer: USTAR prefix split, PAX records); go_time.rs + go_time/parse.rs (time.ParseDuration)
src/featureflags.rs LaunchDarkly client (featureflags/client.rs: offline fallbacks, overrides), contexts.rs (sandbox,
team, service, kind contexts), flags.rs (every upstream flag with its fallback), prefetch.rs
src/telemetry.rs OpenTelemetry: metrics.rs, traces.rs, logs.rs (tracing bridge with target/level filters),
endpoint.rs (OTEL_COLLECTOR_GRPC_ENDPOINT), fc.rs (vcpu_limit counter), identity.rs (resource), grpc.rs (otelgrpc server
spans, traceparent, rpc.server.duration with sandbox.resume and team.id from metadata), report.rs (span errors), server/ (upstream instruments; network.rs slot return duration)
src/block/ sparse cache + tracker, chunker (storage-backed or local), build file (DiffStore: TTL, pins,
disk-pressure eviction), overlay
src/exposure.rs Transport (ROOTFS_EXPOSURE: nbd or ublk), the Exposure enum and expose(overlay);
block_device.rs (BlockDevice trait, device flush shared by both transports)
src/exposure/nbd/ NBD server (concurrent dispatch over the BlockDevice trait), ioctls, device.rs (attach),
pool.rs (DevicePool: process-wide ready devices on std threads, NBD_POOL_SIZE)
src/exposure/ublk/ ublk (not in upstream): device.rs (one thread per device owns the libublk control ring,
params, orphan removal), queue.rs (request to BlockDevice through tokio), wakeup.rs
(one eventfd per queue wakes the io_uring loop)
src/network/ slot.rs (slot addressing), slot/host.rs (Linux: netns, veth, tap via rtnetlink/tun,
iptables REDIRECTs; HostSlots), pool.rs (NetworkPool: 32 new, 100 reused, 3s return delay, configure/reset egress), storage.rs (SlotStorage: index allocation,
leaked namespaces), firewall.rs (nftables slot-firewall: sets, DSCP, EgressRules),
settings.rs (SANDBOX_* network env), prerequisites.rs (NETWORK_VERSION=2 checks)
src/startup_reclaim.rs startup reclaim like upstream pkg/startupreclaim: firecracker/ (kill orphaned VMMs by /proc cmdline), NBD,
ns-N slots, sbx-* cgroups, sandbox dirs; orchestrator.startup_reclaim.* counters
src/tcpfirewall/ egress TCP proxy on 5016/5017/5018: rules.rs (domain/CIDR allow-deny), sniff.rs (HTTP Host,
TLS SNI), proxy.rs (SO_ORIGINAL_DST, 30s dial)
src/hyperloop/ hyperloop server on 5010 for the guest: server.rs (/me, /logs), logs.rs (SourceSandbox)
src/proxy.rs sandbox HTTP proxy on PROXY_PORT 5007 (upstream shared/pkg/proxy + orchestrator/pkg/proxy): server.rs
(ProxyServer: h1/h2c accept loop, graceful shutdown, forced close), handler.rs (resolve, refuse with cors/pages,
connection limit, forward), forward.rs (hop-by-hop, Host mask, upgrade tunnel), host.rs (sandbox id and port
from Host or E2b-Sandbox-* headers), refusal.rs (Refusal), pages.rs + pages/template.rs (JSON/HTML error pages
from assets/proxy/*.html, Go template subset), cors.rs, limit.rs + limiter.rs (per-lifecycle connection
limit), pool.rs + pool/ (client per lifecycle, retrying dialer, TLS skip-verify), tracking.rs (counted,
abortable connections), stream_abort.rs (Connect end-stream frame), destination.rs, body.rs;
sandbox.rs (SandboxProxy: ingress token, mask_request_host, https_ports, envd internal routes in
sandbox/internal_routes.rs, metrics in sandbox/metrics.rs, node.rs registry lookup)
src/routing/ publisher.rs (RoutingPublisher: sandbox:routing:{id} on insert/stopping, tombstones),
redis.rs (fred client: Redis::connect, RoutingCatalog, EventStream, RedisSettings)
src/peer.rs peer chunk sharing between orchestrators (ChunkService): switch.rs (PeerSwitch: installed registry + resolver,
flag peer-to-peer-chunk-transfer, register/finish around a snapshot upload, uploaded builds for 1 h),
registry.rs (PeerRegistry, key peer:{build}, Redis impl in routing/redis.rs), resolver.rs (Resolver: lazy
channels, per-build "switched to storage" flag), handle.rs (PeerHandle, try_peer), stream.rs (PeerChunks,
first message decides availability), blob.rs (PeerBlob), seekable.rs (PeerSeekable, SeekableError::Transitioned),
parents.rs (PeerParents: a parent that a peer still uploads is waited for in storage), availability.rs, server.rs (ChunkServer: the four RPCs) + server/ (resolve.rs PeerCache lookups, blob.rs and
seekable.rs sources, chunk.rs 1 MiB message bound, source.rs SourceError to gRPC status); testing.rs (FakeChunks
gRPC server). storage/backend/peered.rs routes Storage reads through the peer for the six served files
src/events.rs SandboxEvent, EventsService fan-out to EventSinks (Redis stream gated by wh:{team})
src/clickhouse/ batcher.rs (1000 rows / 1000 queue / 1s), endpoint.rs (DSN, native writer with TLS),
rows.rs (sandbox_events, sandbox_host_stats), sinks.rs
src/hoststats.rs per-sandbox host stats sampler (StatsIdentity, cgroup CPU/memory)
src/oom.rs OOM kill watermark from envd /metrics
src/sandbox_observer.rs per-sandbox envd /metrics poll every 5 s: metrics.rs (SandboxMetrics), poll.rs (clock drift,
thresholds, CPU hotplug stuck warning), gauges.rs (e2b.sandbox.* gauges on the 5 s sandbox meter),
sandboxes.rs (registry adapter)
src/volumes.rs VolumeService (upstream pkg/volumes): service.rs (VolumeServer, volume and path confinement, gRPC
trait), one file per RPC (volume.rs, dir_create.rs, dir_list.rs, file_create.rs FileUpload,
file_get.rs FileDownload, path_delete.rs, path_stat.rs, path_update.rs), parents.rs (ensure_dirs),
entry.rs (EntryInfo, symlinks resolved inside the volume), failure.rs (processError mapping),
api_error.rs (UserError status details), spans.rs, test_support.rs;
chrooted.rs + chrooted/ (upstream pkg/chrooted: Chrooted confined by openat2 RESOLVE_IN_ROOT
through rustix; resolve.rs symlink walk, remove.rs, names.rs, info.rs, file.rs, metrics.rs);
the PERSISTENT_VOLUME_MOUNTS config is server/config/volume_mounts.rs
src/builder/ template-manager role: service.rs (TemplateService RPCs, TemplateBuilder trait), builds.rs
(BuildCache, 10 min TTL), logs.rs (BuildLog), phase.rs (Phase trait, run_phases, BuildError),
pipeline.rs (PhasePipeline), hash.rs (HashKeys), index.rs (LayerIndex), uploads.rs
(LayerUploads: background layer uploads, index written after landing), layer.rs
(LayerExecutor: resume or cold boot, update_envd, pause and upload), sandbox_command.rs
(logStream adapter), commands.rs + commands/ (RUN, ENV, WORKDIR, USER, COPY; script.rs
renders the embedded assets/template/commands/*.sh), phases/ (base, steps: steps and default user,
finalize: configure.sh, start/ready, CA bundle from assets/template/finalize/, optimize: prefetch,
ensure_free_disk: build-ensure-free-disk-space flag, rootfs grow over NBD, with sizing.rs, measure.rs,
resize.rs, offline.rs (NBD device guard), grow.rs; layer/filesystem.rs persists its VM-less layer)
src/template.rs Template: snapfile, headers and metadata fetched once per build (OnceCell); template/cache.rs
(TemplateCache: 25h sliding TTL, Arc pins, 1 min grace, add_snapshot), metadata.rs
(metadata.json with upstream keys, V1 fallback), optimize.rs (collect_prefetch: 2-run trace)
src/phases.rs PhaseTimer: resume phase durations and notes
src/cgroup.rs cgroup v2 per sandbox, named sbx-<id>-<random>; CgroupStats with memory.peak reset;
cgroup/cpu_limit.rs (cpu.max quota, threaded vcpu child holding the fc_vcpu threads), removal.rs
(rmdir, cgroup.kill fallback and retry)
src/firecracker/ config (VmConfig builder), kernel_args.rs (Go buildKernelArgs), console.rs (FC output to tracing with a
2000-byte tail and the FlushMetrics filter), client (API over unix socket), launch.rs (unshare -m script, /fc-vm paths),
memory_info.rs (GET /memory, /memory/mappings), mmds.rs, vcpus.rs (MAX_VCPUS, machine sizes, fc_vcpu
thread count, snapshot capacity refusal), process
src/memory/ userfaultfd: region.rs (handshake regions, address <-> memfile offset), uffd.rs (safe uffd fd),
handshake.rs (SCM_RIGHTS listener, uffd + optional memfd), server.rs (page-fault loop, installed
pages, Prefaulter), snapshot.rs (memfile export from the memfd or process_vm_readv),
trace.rs (PrefetchTracker, Trace intersection), prefetch.rs + prefetch/ (source.rs init/last-cycle/both,
last_cycle.rs, plan.rs, flags.rs worker settings)
src/envd/upgrade/ resume-time envd live upgrade: target.rs (flag target, candidate, resolve), rollout.rs (EnvdUpgrades: node-local
binary cache at <base>/envdbin, startup warm, plan: flag, old_envd gate, resolve, exec-context gate, cached copy),
gate.rs (decline reasons), phase.rs (phase labels), deliver.rs (POST /upgrade to envd); envd/report.rs
(X-Envd-Version, -Handover, -Defaults headers of /init); sandbox/upgrade.rs (Sandbox keeps the report, compares
defaults, call_envd_upgrade); server/node/upgrade.rs (Node::maybe_upgrade_envd: deliver, wait ready, confirm,
handover metrics, called by start_with); telemetry/envd_defaults.rs (orchestrator.envd.defaults.* counters)
src/envd.rs envd POST /init client (upstream retry semantics); envd/version.rs (Go semver, snapshot >= 0.5.0);
envd/process.rs (Connect process.Start client, bash -l -c as a user), body.rs (bounded reads of
guest responses), cpus.rs (X-Envd-Cpus check),
quiesce.rs + quiesce/guest.rs (GuestQuiesce: fsfreeze, fsthaw, exec fallback, guest sync before a
filesystem-only snapshot; GuestEnvd posts and shell commands)
src/storage/build.rs Build: memfile + rootfs headers of a snapshot
src/sandbox.rs Sandbox lifecycle: cold_boot, stop; sandbox/config.rs builder, resources.rs (teardown, Drop),
vcpu_limit.rs (resume VM size, boot quota, confine vCPU threads), reboot.rs (filesystem boot gate,
CPU template pick and check, VM size),
snapshot.rs (Snapshot, LocalSnapshot, snapshot_into/pause_into/pause: dirty pages via /memory/dirty or resident-empty, rootfs diff, upload;
snapshot/filesystem.rs: FilesystemSnapshot, a VM-less layer of header, metadata and optional rootfs diff;
snapshot/in_place.rs: filesystem-only checkpoint of the running VM: quiesce, pause, snapshot,
rootfs export, resume in place, thaw, with rollback and the lost-sandbox failures),
resume.rs (snapshot load over uffd),
harvest.rs + harvest/ (pause harvest: throwaway deny-egress resume recording a fresh trace)
src/server/ gRPC node: config.rs (env; config/storage.rs: TEMPLATE_/BUILD_CACHE_ storage resolution,
config/binaries.rs: kernel and Firecracker per version), vcpus.rs (Create vcpu validation, cold boot
size, filesystem boot dispatch), node/reboot.rs (Node::reboot, the filesystem boot),
upload.rs (local HMAC PUT /upload), node.rs (Node: storage, store, registry, network pool, template cache),
node/checkpoint.rs (Checkpoint: route decision, resume-fresh; checkpoint/route.rs gates and balloon mode,
checkpoint/in_place.rs filesystem-only in-place checkpoint, cache, upload, killed event), lifecycle.rs (task per sandbox: commands, end_time
expiry, death watch), service.rs (SandboxService), registry.rs (Go map semantics: reserve,
lifecycle-aware stop, stop_reserved), services.rs (ORCHESTRATOR_SERVICES roles),
scheduling.rs (SchedulingMetadata from headers), user_error.rs (UserError status details),
host_stats.rs and oom_monitor.rs (per-sandbox collectors), status.rs (NodeStatus, override rules, outstanding work), metrics.rs (host
metrics poller, /proc parsers), info.rs (InfoService), health.rs (/health on the tonic router),
admission.rs (max sandboxes from flags; admission/semaphore.rs resizable starting semaphore), pause_disk.rs (BuildDisk, PauseDiskReservation: refuse a pause the build filesystem cannot hold; node/pause_disk.rs admits it), shutdown.rs (drain), app.rs (App: one axum
router for SandboxService, InfoService, grpc health and /health), retry.rs (upload retry
policy), uploads.rs (Uploads: background snapshot uploads, parents land first)
src/proto.rs tonic::include_proto!("_") of proto/orchestrator/*.proto (no package upstream)
src/main.rs orchestrator binary: NodeConfig::from_env, App served by axum on GRPC_PORT (5008), upstream shutdown order
src/bin/resume-build.rs dev tool binary (upstream cmd/resume-build); the logic lives in src/resume_build.rs + resume_build/
(options: Go-style flags via go_flag.rs, env: setupEnv, runner: resume/cmd/pause/benchmark over an in-process
SandboxServer, guest: envd commands and pause signals, shell: PTY via envd/pty.rs, optimize, fph_bench, gdb)
examples/sandbox.rs dev runner: cold or (E2B_RESUME=1) resumed sandbox, 127.0.0.1:49983 forwarded to envd
tests/*_contract.rs Linux-only (root, KVM, nbd) integration tests, run in CI; proxy_contract.rs and sandbox_proxy_contract.rs
run anywhere (tests/proxy_contract/support.rs: backends, TLS fixtures in tests/fixtures/proxy)
docker/guest.Dockerfile + envd.service test microVM rootfs (Debian, envd under systemd)
scripts/ci/ CI helpers: vm-assets.sh, guest-rootfs.sh, vm-sdk-tests.sh, s3.sh, kvm-spike.sh
scripts/clippy-linux.sh clippy of the whole workspace in Docker (rust:1.98) to catch Linux-only compile errors on a Mac
proto/envd/ envd .proto copied from upstream E2B (never edit)
proto/orchestrator/ orchestrator, info, template-manager and volume .proto copied from upstream E2B (never edit), UPSTREAM_COMMIT
website/ Fumadocs (Next.js, static export) docs: content/docs/**/*.mdx in English, *.cs.mdx and
meta.cs.json in Czech; lib/i18n.ts (en default, cs), components/mermaid.tsx; npm run build
crates/envd/build.rs codegen from ../../proto/envd
crates/envd/reference/ upstream envd OpenAPI + pinned commit
crates/envd/src/
main.rs parse config, init tracing, bind, connection::serve. Nothing else.
lib.rs pub mod list only
config.rs CLI flags (Go `flag` compatible)
connection.rs serve: hyper with Go's HTTP/1 limits (1 MiB head, no header count limit)
app.rs AppState (shared state, Arc), Defaults
router.rs build_router: every REST route + Connect services + layers, in one place
auth.rs who is calling: basic_username, request_user, URL signing
middleware.rs pub mod list
middleware/ one file per tower middleware or Connect interceptor (access_token.rs, basic_auth.rs, connect_timeout.rs:
timeout parsing and the unary deadline checked only at handler entry)
controllers.rs pub mod list
controllers/ one file per area: health.rs, envs.rs, init.rs, filesystem.rs, ...
controllers/<area>/ helpers of a big area (filesystem/entry.rs, ops.rs, watch.rs; files/download.rs, upload.rs, compose.rs, encoding.rs; process/spawn.rs, output.rs, events.rs, registry.rs)
host.rs + host/ things done to the VM (clock, /etc/hosts, MMDS)
user.rs Linux users/groups
envvars.rs env vars for spawned processes
util.rs + util/ small shared helpers (error_response.rs; json.rs: float32 numbers without a .0; fs.rs: OS error text, eval_symlinks; paths.rs; keepalive.rs; metadata.rs: user.e2b.* xattrs)
proto.rs include_generated!() only
crates/envd/tests/ HTTP contract tests against a real server served by connection::serve (tests/common: spawn_server_with_state)
Where does new code go
| Adding | Put it in |
|---|---|
| REST endpoint | handler fn in controllers/<area>.rs, route line in router.rs |
| Connect service | controllers/<service>.rs with impl <Trait> for <Service>, register in router.rs |
| Logic of a big controller | controllers/<area>/<topic>.rs, plain pub fn |
| Middleware for every request | middleware/<name>.rs, .layer(...) in router.rs |
| Shared state | field on AppState in app.rs |
| Helper used by 2+ areas | util/<topic>.rs |
| New top-level concern (processes, cgroups) | <concern>.rs + <concern>/ at the crate src/ root |
Handler names: verb + thing (get_envs, post_init). Connect methods keep the generated names.
Code rules
- No comments at all — no
//,///,//!. Names carry the meaning. - KISS: no traits, newtypes, error enums or wrapper/delegating methods unless the compiler forces them. Free functions,
match,?, iterator chains. - Chaining: write code as method chains —
Option/Result/iterator combinators (map,and_then,filter,map_or,is_none_or,try_for_each) rather thanif let/let elsewith early returns, e.g.headers.get(..).and_then(..).filter(..).map_or(DEFAULT, ..). - Builders: build values with builder chains, e.g.
ErrorResponse::builder().status(StatusCode::NOT_FOUND).message(text).build(). A struct created in several places gets its ownbuilder(); use library builders (OpenOptions,Response::builder). - Named fields always: no tuple structs (
struct Foo(Bar)), no.0/.1field access. - Errors are
String,io::Error,ConnectErrororutil::error_response::ErrorResponse(REST JSON/plain error in Go's JSON shape) with a clear message. - API = upstream Go envd: paths, status codes, JSON and proto shapes, header names callers read and Connect framing match Go. Error wording, escaping and Go library quirks callers never see do not need to. Check the Go source before writing; internals are plain Rust.
- Unit tests live in
#[cfg(test)] mod testsat the bottom of the same file; HTTP behaviour goes incrates/<crate>/tests/*_contract.rs. rustfmt.tomlsetsmax_width = 120; runcargo fmtandcargo clippy --all-targetsbefore committing.
Request flow
CORS -> require_token (middleware/access_token.rs)
-> authenticate_user (middleware/basic_auth.rs, puts User into extensions)
-> REST route from router.rs, or fallback to Connect:
legacy_sdk -> advertise_encodings (middleware/accept_encoding.rs)
-> hold_unary_timeout -> ConnectRpcService (gzip only, ConnectTimeout interceptor in middleware/connect_timeout.rs)
-> controller fn