Compile a site directory into a MirageOS web appliance.
Vitrine serves static files from a deterministic store and keeps the HTTP details in one place: path normalization, MIME types, ETags, cache headers, custom 404 pages, precompressed assets, and a small dynamic route hook.
It is currently aimed at small static sites embedded into MirageOS unikernel images. It does not manage deployment, provision Albatross, or choose a store backend for large sites.
Install development dependencies for this checkout:
opam install . --deps-only --with-test --with-docdune build @all
dune runtest
dune fmtServe the example site locally:
dune exec -- vitrine dev examples/basic-site/site --port 8080Print the site manifest:
dune exec -- vitrine manifest examples/basic-site/siteCreate a small project:
dune exec -- vitrine init my_sitelib/contains the runtime-independent static-serving logic.lib_mirage/adapts Vitrine responses to Cohttp/Lwt.bin/contains thevitrinecommand.examples/basic-site/contains a small site and Mirage entrypoint.test/covers the core serving behavior.
vitrine embed site/ emits an OCaml module containing a Vitrine.store.
dune exec -- vitrine embed examples/basic-site/site > site_store.mlThe generated store is immutable OCaml data. It is intended for embedding into a unikernel image. Large sites may need a different store backend later; the current slice is aimed at small static sites.
The example uses Mirage's Cohttp server device:
cd examples/basic-site
mirage configure -t unix
makeFor Solo5:
mirage configure -t hvt
makeThis checkout does not vendor Mirage packages. Install Mirage, a Solo5 target, and the Cohttp Mirage stack in the switch used for unikernel builds.
Requests are normalized before lookup. Traversal attempts are rejected. / and directory paths resolve through index.html; unresolved paths use /404.html when present.
HTML uses Cache-Control: no-cache. Filenames with a hex hash segment use public, max-age=31536000, immutable. Other static assets use a short public cache lifetime.
If file.br or file.gz exists and the client advertises support, Vitrine serves the compressed file with the original file's MIME type. Brotli is preferred over gzip.
Static content is the default path. Tiny endpoints can be added at the adapter layer:
let routes =
[
{
Vitrine_mirage.meth = `GET;
path = "/health";
handler =
(fun _request _body ->
Vitrine.text "ok\n" |> Vitrine_mirage.response |> Lwt.return);
};
]Contact: funwithcthulhu29@gmail.com
MIT. See LICENSE.