零配置、基于 `io_uring` 的快速 HTTPS 服务器。
Zero-config, fast, scriptable io_uring HTTPS server.
zeroserve serves a website packaged as a single tarball, runs sandboxed eBPF
request scripts JIT-compiled to native code, and can
compile and serve a Caddy config directly. It hot
reloads on SIGHUP, leaves no temporary files on disk, and hardens itself with
Linux namespaces and capability dropping.
io_uring end to end. All network and disk I/O runs on the
monoio runtime, with a per-core
worker thread and per-worker listeners (SO_REUSEPORT / SO_REUSEPORT_LB
where the kernel load-balances accepts; a shared listening socket elsewhere,
with accepted connections handed off round-robin so keep-alive traffic stays
balanced across workers)..o files,
but it also accepts raw .c files - zeroserve has a built-in
tinycc with eBPF backend patch
so it can compile C to eBPF on the fly.CADDY_COMPAT.md.--expose-filesystem opt-in for any host filesystem access.Docker:
docker run --rm -p 8080:8080 -v "$PWD/site.tar:/srv/site.tar:ro" \
ghcr.io/losfair/zeroserve:0.2.11 --addr 0.0.0.0:8080 /srv/site.tarImages are published to ghcr.io/losfair/zeroserve (multi-arch: amd64,
arm64). Published images are signed by digest with keyless Sigstore
certificates.
Prebuilt binary from GitHub releases:
curl -fsSL "https://github.com/losfair/zeroserve/releases/download/v0.2.11/zeroserve-$(uname -m)-linux" \
-o zeroserve && chmod +x zeroserveEvery release also includes SHA256SUMS and a keyless Sigstore bundle for each
binary. Verify a downloaded release with
Cosign and, optionally,
the GitHub CLI:
…From source (requires the pinned Rust toolchain, Zig 0.14.0, and
cargo-zigbuild 0.22.3; Linux only):
cargo install cargo-zigbuild --version 0.22.3 --locked
./tools/reproducible-build.sh x86_64-unknown-linux-gnu.2.31 \
target/zeroserve-x86_64-linuxThe release workflow builds each Linux target twice from independent source and
target directories and requires byte-for-byte identical results before signing
or publishing. cargo build --release --locked remains the normal development
build command.
# Pack the current directory into a site tarball
zeroserve --pack ./public > site.tar
# Serve it over HTTP on :8080
zeroserve --addr 0.0.0.0:8080 site.tar--pack walks the directory, adds every regular file, and compiles any
.zeroserve/scripts/*.c request script to an eBPF .o (the .c source is
omitted from the tarball).
…Run zeroserve --manual to print the full embedded user manual (also in
docs/user_manual.md), and zeroserve --help for the
complete flag reference.
Scripts live under .zeroserve/scripts/ in the site directory and run in
filename order. Each is a C file using the SDK header
(sdk/zeroserve.h, also available via
zeroserve --dump-sdk):
#include
ZS_ENTRY
zs_u64 entry(void) {
char path[64];
zs_req_path(path, sizeof(path));
if (zs_strcmp(path, "/health") == 0) {
zs_meta_set(ZS_STR("zs.response.header.content-type"), ZS_STR("application/json"));
zs_respond(200, ZS_STR("{\"status\":\"ok\"}\n"));
}
return 0;
}Scripts are compiled to eBPF and JIT-executed per request inside a memory- and
time-bounded sandbox. The helper API covers request/response inspection and
mutation, response templating, logging and time, crypto and encoding
(SHA-256, HMAC, base64, hex, random), AWS SigV4 signing, rate limiting,
reverse proxy, OAuth2/OIDC login with sealed cookies, and strongSwan VICI
lookups. See the "Request scripting" section of the user manual for the full
helper reference, and examples/ for runnable scripts.
Generating scripts is easiest via the
zeroserve-script-createworkflow, or by hand againstsdk/zeroserve.h.
Request scripts are compiled to eBPF and JIT-executed in-process, in the same address space as the server. The pointer cage — provided by the async-ebpf runtime — is the memory isolation boundary that makes this safe: a buggy or malicious script cannot read or write any memory outside its own sandbox, no matter how it computes a pointer.
It works by confining every guest memory access to a fixed, power-of-two-sized window of virtual memory:
PROT_NONE, carved into a read/write stack region (per-invocation) and a
read-only data region (the linked program image), separated and surrounded
by guard regions. The guard sizes are randomized per program (ASLR-style),
and the whole window is padded to a power of two with a one-page margin on each
side to absorb the maximum load/store displacement.(address & mask) + offset before the
native access, where mask = window_size - 1 and offset is the window base.
Because the window is a power of two, any pointer — however the script
arithmetic produced it — is forced back inside the cage. The transform is
branchless, so there is no mis-speculatable path for a transient out-of-bounds
read (Spectre-v1).PROT_NONE memory and faults. A SIGSEGV handler translates the faulting
native address back to a guest offset and turns it into a clean, contained
program fault instead of a server crash or an escape.mprotect), and the JIT confines all stores to the stack region
regardless of analysis hints — so a script can never modify its own code or the
shared data region.Static region analysis is layered on top as a performance optimization (it lets confidently-classified loads skip one of two region probes), but it is explicitly not a security boundary — the JIT always retains a single-region bounds check, so the cage holds even if the analysis is imprecise.
zeroserve can adapt and compile the HTTP-routing portion of a Caddy config — either a native Caddyfile or Caddy JSON, auto-detected by content:
# Adapt → compile → in-memory site → serve, all in one shot
zeroserve --caddy Caddyfile --addr 0.0.0.0:8080
# Compile a Caddy config to a zeroserve script you can pack into a site
zeroserve --caddy-compile Caddyfile > .zeroserve/scripts/50-caddy.c
# Just adapt a Caddyfile to Caddy JSON and inspect it
zeroserve --adapt-caddyfile CaddyfileSupported directives include respond/error/abort, redir,
header/request_header, rewrite/uri/method,
handle/handle_path/route/handle_errors, root/fs/vars/map,
basic_auth, request_body, file_server, and single-upstream
reverse_proxy, with matchers, route groups, response hooks, and
tls.client_auth policies. The full supported surface and known non-goals are
documented in CADDY_COMPAT.md.
# Generate an ECH keypair + ECHConfig (PEM to stdout, DNS guidance to stderr)
zeroserve --gen-ech-key --ech-public-name ech.example.com > ech.pem
# Serve with ECH enabled (TLS must be configured)
zeroserve --tls-addr 0.0.0.0:8443 --cert-dir /etc/zeroserve/certs \
--ech-key /etc/zeroserve/ech site.tar--ech-key accepts a single PEM bundle or a directory of key files for rotation.
See the "Encrypted Client Hello" section of the user manual for the rotation,
rejection, and transparent-relay-fallback behaviors.
Send SIGHUP (or point --reload-signal-file at a file whose contents change)
to reload the site tarball, TLS certificates, and scripts atomically. The last
known-good runtime state is preserved if a reload fails.
cargo fmt --all --check # verify formatting
cargo build --release --locked
cargo test --locked # Rust unit tests
cd testing && deno test -A --parallel # end-to-end tests (TypeScript/Deno)The e2e suite launches target/release/zeroserve, so build the release binary
first. Scripting tests use the built-in tinycc backend by default; pass
--ebpf-compiler clang (and have clang/llc on PATH) to exercise the
clang path. Caddy comparison tests require a caddy binary (exposed via
CADDY_BIN).
MIT — see LICENSE.
暂无开放 Issues,或尚未同步最近议题。