diff --git a/projects/miragesdk/examples/https-unikernel/.dockerignore b/projects/miragesdk/examples/https-unikernel/.dockerignore
new file mode 100644
index 000000000..4d01038d7
--- /dev/null
+++ b/projects/miragesdk/examples/https-unikernel/.dockerignore
@@ -0,0 +1,5 @@
+_build
+rpc
+Dockerfile
+tls-secrets
+www-data
diff --git a/projects/miragesdk/examples/https-unikernel/.gitignore b/projects/miragesdk/examples/https-unikernel/.gitignore
new file mode 100644
index 000000000..f8f4e9f29
--- /dev/null
+++ b/projects/miragesdk/examples/https-unikernel/.gitignore
@@ -0,0 +1,8 @@
+.merlin
+_build
+proto.ml
+proto.mli
+lib/schema.ml
+lib/schema.mli
+www-data
+tls-secrets
diff --git a/projects/miragesdk/examples/https-unikernel/Dockerfile b/projects/miragesdk/examples/https-unikernel/Dockerfile
new file mode 100644
index 000000000..141b20277
--- /dev/null
+++ b/projects/miragesdk/examples/https-unikernel/Dockerfile
@@ -0,0 +1,23 @@
+FROM ocaml/opam@sha256:a469435632d0cacbceab799d7e48201b727d025fa1805cbbe210d94233b251ad
+#FROM ocaml/opam:debian-9_ocaml-4.04.0
+RUN opam pin add -yn capnp 'https://github.com/talex5/capnp-ocaml.git#interfaces'
+RUN opam pin add -yn capnp-rpc 'https://github.com/talex5/capnp-rpc.git#demo1'
+RUN opam pin add -yn capnp-rpc-lwt 'https://github.com/talex5/capnp-rpc.git#demo1'
+RUN opam depext -i -y jbuilder lwt cohttp astring tls capnp camlzip alcotest cohttp
+RUN sudo apt-get install -y screen python-pip python-setuptools python-dev --no-install-recommends
+RUN pip install cython pycapnp
+ADD opam /home/opam/src/opam
+RUN opam pin add -ny mypkg /home/opam/src
+RUN opam install --deps-only mypkg
+ENV JBUILD_STATIC=true
+WORKDIR /home/opam/src
+ADD . /home/opam/src
+RUN sudo chown -R opam .
+RUN opam config exec -- make
+USER root
+RUN cp _build/default/src/main.exe /usr/bin/https-unikernel-single && \
+ cp _build/default/src/store_main.exe /usr/bin/https-unikernel-store && \
+ cp _build/default/src/http_main.exe /usr/bin/https-unikernel-http && \
+ cp _build/default/src/tls_main.exe /usr/bin/https-unikernel-tls
+USER opam
+ENV SHELL=bash
diff --git a/projects/miragesdk/examples/https-unikernel/Makefile b/projects/miragesdk/examples/https-unikernel/Makefile
new file mode 100644
index 000000000..eff3a1e26
--- /dev/null
+++ b/projects/miragesdk/examples/https-unikernel/Makefile
@@ -0,0 +1,31 @@
+.PHONY: all docker docker-run clean
+
+all: www-data
+ jbuilder build --dev src/main.exe src/store_main.exe src/http_main.exe src/tls_main.exe
+
+docker:
+ docker build -t https-unikernel .
+
+docker-run: tls-secrets/server.crt
+ docker run --rm -it -v ${PWD}/tls-secrets:/home/opam/src/tls-secrets -p 8443:8443 https-unikernel bash -c 'reset; screen'
+
+clean:
+ rm -rf _build
+
+tls-secrets/server.key:
+ @echo Generating server key...
+ [ -d tls-secrets ] || mkdir -p tls-secrets
+ openssl genrsa -out $@ 4096
+
+tls-secrets/server.crt: tls-secrets/server.key
+ @echo ">>> Generating server X.509 certificate."
+ @echo ">>> Enter the server's full hostname as the 'Common Name' (e.g. demo.mynet)."
+ @echo ">>> Everything else can be left blank."
+ @echo
+ @openssl req -new -x509 -key $< -out $@ -days 10000
+
+www-data:
+ git init www-data
+ echo '
It works!
Powered by Irmin.
' > www-data/index.html
+ git -C www-data add index.html
+ git -C www-data commit --author 'Test ' -m 'Initial state'
diff --git a/projects/miragesdk/examples/https-unikernel/README.md b/projects/miragesdk/examples/https-unikernel/README.md
new file mode 100644
index 000000000..5eb9f982b
--- /dev/null
+++ b/projects/miragesdk/examples/https-unikernel/README.md
@@ -0,0 +1,70 @@
+## https-unikernel example service
+
+This sample service implements an https web-server as a set of three components communicating using [Cap'n Proto RPC][].
+
+1. The `store` service accepts requests for web pages and returns the page content.
+2. The `http` service accepts HTTP connections over RPC and handles GET requests using `store`.
+3. The `tls` service accepts encrypted HTTPS connections and provides them as a plain-text stream to `http`.
+
+The protocols implemented by the services can be found in the `proto.capnp` file.
+
+The services can be run in separate processes so that they are isolated from each other.
+For example, only the `tls` component needs access to the private key, so
+a bug in the HTTP protocol decoder cannot leak the key.
+
+Although the example services are all written in OCaml, it should be possible to replace any of them with a different implementation written in any other language with Cap'n Proto RPC support.
+
+### Running the samples
+
+The easiest way to build and run is using Docker:
+
+ make docker
+ make docker-run
+
+Once inside the container, you can run all the services in a single process like this:
+
+ reset
+ https-unikernel-single
+
+You should be able to try out the service by opening in a browser.
+
+You can also run the service as three separate processes.
+The easiest way to do this is by running `screen`.
+
+First, start the store:
+
+ https-unikernel-store /tmp/store.sock
+
+Then, create a new window (`Ctrl-A c`) and start the http service:
+
+ https-unikernel-http /tmp/http.sock --store=/tmp/store.sock
+
+Finally, make another window and run the TLS terminator:
+
+ https-unikernel-tls --http /tmp/http.sock --port 8443
+
+
+### Testing with Python
+
+With the services running as separate processes (i.e. with socket files available), you can also invoke services from other languages.
+For example, using Python (make another screen split):
+
+```
+$ cd src
+$ python
+>>> import capnp
+>>> import proto_capnp
+>>> import socket
+>>> s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM, 0)
+>>> s.connect('/tmp/store.sock')
+>>> client = capnp.TwoPartyClient(s)
+>>> store = client.bootstrap().cast_as(proto_capnp.Store)
+>>> r = store.get(['index.html'])
+>>> print r.wait()
+( ok = "It works!
Powered by Irmin.
\n" )
+```
+
+(or just do `python test_store.py`)
+
+
+[Cap'n Proto RPC]: https://capnproto.org/rpc.html
diff --git a/projects/miragesdk/examples/https-unikernel/opam b/projects/miragesdk/examples/https-unikernel/opam
new file mode 100644
index 000000000..a8cf384ed
--- /dev/null
+++ b/projects/miragesdk/examples/https-unikernel/opam
@@ -0,0 +1,18 @@
+opam-version: "1.2"
+name: "https-unikernel"
+version: "0.1"
+maintainer: "Thomas Leonard "
+authors: "Thomas Leonard "
+build: [
+ [make]
+]
+depends: [
+ "lwt"
+ "cohttp"
+ "astring"
+ "tls"
+ "irmin-unix"
+ "capnp-rpc-lwt"
+ "jbuilder" {build}
+ "ocamlfind" {build}
+]
diff --git a/projects/miragesdk/examples/https-unikernel/src/api.ml b/projects/miragesdk/examples/https-unikernel/src/api.ml
new file mode 100644
index 000000000..ab4116aac
--- /dev/null
+++ b/projects/miragesdk/examples/https-unikernel/src/api.ml
@@ -0,0 +1 @@
+include Proto.MakeRPC(Capnp.BytesMessage)(Capnp_rpc_lwt)
diff --git a/projects/miragesdk/examples/https-unikernel/src/common.ml b/projects/miragesdk/examples/https-unikernel/src/common.ml
new file mode 100644
index 000000000..80721b7eb
--- /dev/null
+++ b/projects/miragesdk/examples/https-unikernel/src/common.ml
@@ -0,0 +1,77 @@
+open Lwt.Infix
+open Capnp_rpc_lwt
+
+let connect ~switch path =
+ Logs.info (fun f -> f "Connecting to %S" path);
+ let socket = Unix.(socket PF_UNIX SOCK_STREAM 0) in
+ begin
+ try Unix.connect socket (Unix.ADDR_UNIX path)
+ with Unix.Unix_error(Unix.ECONNREFUSED, "connect", "") ->
+ Logs.err (fun f -> f "Failed to connect to %S" path);
+ exit 1
+ end;
+ let endpoint = Endpoint.of_socket ~switch socket in
+ let conn = CapTP.of_endpoint ~switch endpoint in
+ CapTP.bootstrap conn
+
+let rm_socket path =
+ match Unix.lstat path with
+ | stat when stat.Unix.st_kind = Unix.S_SOCK -> Unix.unlink path
+ | _ -> failwith (Fmt.strf "%S exists and is not a socket" path)
+ | exception Unix.Unix_error(Unix.ENOENT, "lstat", _) -> ()
+
+let listen ~switch ~offer path =
+ let socket = Unix.(socket PF_UNIX SOCK_STREAM 0) in
+ Lwt_switch.add_hook (Some switch) (fun () -> Unix.close socket; Lwt.return_unit);
+ rm_socket path;
+ Unix.bind socket (Unix.ADDR_UNIX path);
+ Unix.listen socket 5;
+ let socket = Lwt_unix.of_unix_file_descr socket in
+ Logs.info (fun f -> f "Waiting for connections on %S" path);
+ let rec loop () =
+ Lwt_unix.accept socket >>= fun (c, _) ->
+ Logs.info (fun f -> f "Got connection on %S" path);
+ Lwt_switch.with_switch @@ fun switch -> (* todo: with_child_switch *)
+ let endpoint = Endpoint.of_socket ~switch (Lwt_unix.unix_file_descr c) in
+ ignore (CapTP.of_endpoint ~switch ~offer endpoint);
+ loop () in
+ loop ()
+
+module Actor = struct
+ type t = Fmt.style * string
+ let pp f (style, name) = Fmt.(styled style (const string name)) f ()
+ let tag = Logs.Tag.def "actor" pp
+end
+
+let pp_qid f = function
+ | None -> ()
+ | Some x ->
+ let s = Uint32.to_string x in
+ Fmt.(styled `Magenta (fun f x -> Fmt.pf f " (qid=%s)" x)) f s
+
+let reporter =
+ let report src level ~over k msgf =
+ let src = Logs.Src.name src in
+ msgf @@ fun ?header ?(tags=Logs.Tag.empty) fmt ->
+ let actor =
+ match Logs.Tag.find Actor.tag tags with
+ | Some x -> x
+ | None -> `Black, "------"
+ in
+ let qid = Logs.Tag.find Capnp_rpc.Debug.qid_tag tags in
+ let print _ =
+ Fmt.(pf stderr) "%a@." pp_qid qid;
+ over ();
+ k ()
+ in
+ Fmt.kpf print Fmt.stderr ("%a %a %a: @[" ^^ fmt ^^ "@]")
+ Fmt.(styled `Magenta string) (Printf.sprintf "%11s" src)
+ Logs_fmt.pp_header (level, header)
+ Actor.pp actor
+ in
+ { Logs.report = report }
+
+let init_logging () =
+ Fmt_tty.setup_std_outputs ();
+ Logs.set_reporter reporter;
+ Logs.set_level (Some Logs.Info)
diff --git a/projects/miragesdk/examples/https-unikernel/src/debug.ml b/projects/miragesdk/examples/https-unikernel/src/debug.ml
new file mode 100644
index 000000000..722fbc3d4
--- /dev/null
+++ b/projects/miragesdk/examples/https-unikernel/src/debug.ml
@@ -0,0 +1 @@
+let endpoint_tag = Logs.Tag.def "endpoint" Fmt.string
diff --git a/projects/miragesdk/examples/https-unikernel/src/http_main.ml b/projects/miragesdk/examples/https-unikernel/src/http_main.ml
new file mode 100644
index 000000000..c37c999f4
--- /dev/null
+++ b/projects/miragesdk/examples/https-unikernel/src/http_main.ml
@@ -0,0 +1,24 @@
+let () = Common.init_logging ()
+
+let main store_socket http_socket =
+ Lwt_main.run begin
+ Lwt_switch.with_switch @@ fun switch ->
+ let store = Common.connect ~switch store_socket in
+ let http = Http_server.service store in
+ Common.listen ~switch ~offer:http http_socket
+ end
+
+open Cmdliner
+
+let store =
+ let doc = "The database store to use" in
+ Arg.(required @@ opt (some string) None @@ info ~doc ~docv:"STORE" ["store"])
+
+let http =
+ let doc = "The http socket to provide" in
+ Arg.(required @@ pos 0 (some string) None @@ info ~doc ~docv:"HTTP" [])
+
+let cmd =
+ Term.(const main $ store $ http), Term.info "http"
+
+let () = Term.(exit @@ eval cmd)
diff --git a/projects/miragesdk/examples/https-unikernel/src/http_server.ml b/projects/miragesdk/examples/https-unikernel/src/http_server.ml
new file mode 100644
index 000000000..fd38bbdf3
--- /dev/null
+++ b/projects/miragesdk/examples/https-unikernel/src/http_server.ml
@@ -0,0 +1,135 @@
+let src = Logs.Src.create "web.http" ~doc:"HTTP engine for web server"
+module Log = (val Logs.src_log src: Logs.LOG)
+
+open Capnp_rpc_lwt
+open Lwt.Infix
+open Astring
+
+module Remote_flow = struct
+ type buffer = Cstruct.t
+ type flow = Api.Reader.Flow.t Capability.t
+ type error = [`Capnp of Capnp_rpc.Error.t]
+ type write_error = [Mirage_flow.write_error | `Capnp of Capnp_rpc.Error.t]
+ type 'a io = 'a Lwt.t
+
+ let create x = x
+
+ let read t =
+ let module R = Api.Reader.Flow.Read_results in
+ let req = Capability.Request.create_no_args () in
+ let proxy = new Api.Reader.Flow.client t in
+ Capability.call_for_value_exn proxy#read req >>= fun resp ->
+ let p = R.of_payload resp in
+ let data = R.data_get p in
+ Lwt.return (Ok (`Data (Cstruct.of_string data)))
+
+ let write t data =
+ let module P = Api.Builder.Flow.Write_params in
+ let req, p = Capability.Request.create P.init_pointer in
+ let proxy = new Api.Reader.Flow.client t in
+ P.data_set p (Cstruct.to_string data);
+ Capability.call_for_value_exn proxy#write req >>= fun _ ->
+ Lwt.return (Ok ())
+
+ let writev t data =
+ write t (Cstruct.concat data)
+
+ let close _ = failwith "TODO: close"
+
+ let pp_error f = function
+ | `Capnp e -> Capnp_rpc.Error.pp f e
+ | `Closed -> Fmt.string f "Closed"
+
+ let pp_write_error f = function
+ | `Capnp e -> Capnp_rpc.Error.pp f e
+ | #Mirage_flow.write_error as e -> Mirage_flow.pp_write_error f e
+end
+
+module IO = struct
+ type 'a t = 'a Lwt.t
+ let (>>=) = Lwt.bind
+ let return = Lwt.return
+
+ type ic = Lwt_io.input_channel
+ type oc = Lwt_io.output_channel
+ type conn = Remote_flow.flow
+
+ let read_line ic =
+ Lwt_io.read_line_opt ic
+
+ let read ic count =
+ let count = min count Sys.max_string_length in
+ Lwt_io.read ~count ic
+
+ let write oc buf =
+ Lwt_io.write oc buf
+
+ let flush oc =
+ Lwt_io.flush oc
+end
+
+module Server = Cohttp_lwt.Make_server(IO)
+
+type t = Api.Reader.Store.t Capability.t
+
+(* Make a Cap'n'Proto call to the store service *)
+let get t path =
+ let module P = Api.Builder.Store.Get_params in
+ let req, p = Capability.Request.create P.init_pointer in
+ ignore (P.path_set_list p path);
+ let proxy = new Api.Reader.Store.client t in
+ Capability.call_for_value_exn proxy#get req >>= fun resp ->
+ let open Api.Reader.Store in
+ match GetResults.get (GetResults.of_payload resp) with
+ | GetResults.NotFound -> Lwt.return None
+ | GetResults.Ok data -> Lwt.return (Some data)
+ | GetResults.Undefined _ -> failwith "Protocol error: bad msg type"
+
+(* Handle HTTP requests *)
+let callback t _conn req _body =
+ let open Cohttp in
+ let uri = Request.uri req in
+ Log.info (fun f -> f "HTTP request for %a" Uri.pp_hum uri);
+ match Request.meth req with
+ | `GET ->
+ let path = String.cuts ~empty:false ~sep:"/" (Uri.path uri) in
+ let path =
+ match path with
+ | [] -> ["index.html"]
+ | p -> p
+ in
+ begin get t path >>= function
+ | Some body -> Server.respond_string ~status:`OK ~body ()
+ | None -> Server.respond_not_found ~uri ()
+ end
+ | m ->
+ let body = Fmt.strf "Bad method %S" (Code.string_of_method m) in
+ Server.respond_error ~status:`Bad_request ~body ()
+
+let callback t = Server.callback (Server.make ~callback:(callback t) ())
+
+module Remote_flow_unix = Mirage_flow_unix.Make(Remote_flow)
+
+let handle_connection store c =
+ Log.info (fun f -> f "Handing new connection");
+ let flow = Remote_flow.create c in
+ callback store flow (Remote_flow_unix.ic flow) (Remote_flow_unix.oc flow) >>= fun () ->
+ Capability.dec_ref c;
+ Lwt.return_unit
+
+let service store =
+ Api.Builder.HttpServer.local @@
+ object (_ : Api.Builder.HttpServer.service)
+ method accept req =
+ Log.info (fun f -> f "Handing new connection");
+ let module P = Api.Reader.HttpServer.Accept_params in
+ let p = P.of_payload req in
+ match P.connection_get p with
+ | None -> Service.fail "No connection!"
+ | Some i ->
+ let c = Payload.import req i in
+ Service.return_lwt (fun () ->
+ handle_connection store c >|= fun () ->
+ Ok (Service.Response.create_empty ())
+ )
+ end
diff --git a/projects/miragesdk/examples/https-unikernel/src/jbuild b/projects/miragesdk/examples/https-unikernel/src/jbuild
new file mode 100644
index 000000000..0eb30fec6
--- /dev/null
+++ b/projects/miragesdk/examples/https-unikernel/src/jbuild
@@ -0,0 +1,25 @@
+(* -*- tuareg -*- *)
+open Jbuild_plugin.V1
+
+let extra_flags =
+ match Sys.getenv "JBUILD_STATIC" with
+ | "true" -> "-cclib -static"
+ | x -> failwith (Printf.sprintf "JBUILD_STATIC: unknown value %S" x)
+ | exception Not_found -> ""
+
+let sexp = Printf.sprintf "
+ (jbuild_version 1) \
+ \
+ (executables ( \
+ (names (main store_main http_main tls_main)) \
+ (libraries (capnp-rpc-lwt cohttp.lwt irmin-unix cmdliner fmt.tty)) \
+ (flags (:standard %s)) \
+ )) \
+ (rule \
+ ((targets (proto.ml proto.mli)) \
+ (deps (proto.capnp)) \
+ (action (run capnpc -o ocaml ${<})))) \
+" extra_flags
+
+let () =
+ send sexp
diff --git a/projects/miragesdk/examples/https-unikernel/src/main.ml b/projects/miragesdk/examples/https-unikernel/src/main.ml
new file mode 100644
index 000000000..46b582060
--- /dev/null
+++ b/projects/miragesdk/examples/https-unikernel/src/main.ml
@@ -0,0 +1,37 @@
+open Lwt.Infix
+open Capnp_rpc_lwt
+
+let switch = Lwt_switch.create ()
+
+let socket_pair ~switch =
+ let client, server = Unix.(socketpair PF_UNIX SOCK_STREAM 0) in
+ Lwt_switch.add_hook (Some switch) (fun () ->
+ Unix.close client;
+ Unix.close server;
+ Lwt.return ()
+ );
+ (Endpoint.of_socket ~switch client, Endpoint.of_socket ~switch server)
+
+let store_to_http, http_to_store = socket_pair ~switch
+let http_to_tls, tls_to_http = socket_pair ~switch
+
+let () =
+ Common.init_logging ();
+ Lwt_main.run begin
+ begin
+ Store.service () >>= fun service ->
+ let tags = Logs.Tag.add Common.Actor.tag (`Green, "Store ") Logs.Tag.empty in
+ let _ : CapTP.t = CapTP.of_endpoint ~offer:service ~tags ~switch store_to_http in
+ Lwt.return ()
+ end
+ >>= fun () ->
+ begin
+ let tags = Logs.Tag.add Common.Actor.tag (`Red, "HTTP ") Logs.Tag.empty in
+ let store = CapTP.bootstrap (CapTP.of_endpoint ~tags ~switch http_to_store) in
+ let service = Http_server.service store in
+ let _ : CapTP.t = CapTP.of_endpoint ~offer:service ~tags ~switch http_to_tls in
+ Lwt.return ()
+ end
+ >>= fun () ->
+ Tls_terminator.init ~switch ~to_http:tls_to_http
+ end
diff --git a/projects/miragesdk/examples/https-unikernel/src/main.mli b/projects/miragesdk/examples/https-unikernel/src/main.mli
new file mode 100644
index 000000000..e69de29bb
diff --git a/projects/miragesdk/examples/https-unikernel/src/proto.capnp b/projects/miragesdk/examples/https-unikernel/src/proto.capnp
new file mode 100644
index 000000000..49a7797ed
--- /dev/null
+++ b/projects/miragesdk/examples/https-unikernel/src/proto.capnp
@@ -0,0 +1,21 @@
+@0xe81d238ec50a0daa;
+
+interface Store {
+ struct GetResults {
+ union {
+ ok @0 :Text;
+ notFound @1 :Void;
+ }
+ }
+
+ get @0 (path :List(Text)) -> GetResults;
+}
+
+interface Flow {
+ read @0 () -> (data :Data);
+ write @1 (data :Data) -> ();
+}
+
+interface HttpServer {
+ accept @0 (connection :Flow) -> ();
+}
diff --git a/projects/miragesdk/examples/https-unikernel/src/store.ml b/projects/miragesdk/examples/https-unikernel/src/store.ml
new file mode 100644
index 000000000..bfb02b33c
--- /dev/null
+++ b/projects/miragesdk/examples/https-unikernel/src/store.ml
@@ -0,0 +1,33 @@
+let src = Logs.Src.create "web.store" ~doc:"Datastore for web server"
+module Log = (val Logs.src_log src: Logs.LOG)
+
+module Irmin_store = Irmin_unix.Git.FS.KV(Irmin.Contents.String)
+
+open Lwt.Infix
+open Capnp_rpc_lwt
+open Astring
+
+(* The Cap'n'Proto service interface we expose. *)
+let service () =
+ let config = Irmin_fs.config "www-data" in
+ Irmin_store.Repo.v config >>= fun repo ->
+ Irmin_store.of_branch repo Irmin_store.Branch.master >|= fun db ->
+ Api.Builder.Store.local @@
+ object (_ : Api.Builder.Store.service)
+ method get req =
+ let module P = Api.Reader.Store.Get_params in
+ let module R = Api.Builder.Store.GetResults in
+ let params = P.of_payload req in
+ let path = P.path_get_list params in
+ Log.info (fun f -> f "Handing request for %a" (Fmt.Dump.list String.dump) path);
+ Service.return_lwt (fun () ->
+ let resp, results = Service.Response.create R.init_pointer in
+ begin
+ Irmin_store.find db path >|= function
+ | Some data -> R.ok_set results data
+ | None -> R.not_found_set results
+ end
+ >>= fun () ->
+ Lwt.return (Ok resp)
+ )
+ end
diff --git a/projects/miragesdk/examples/https-unikernel/src/store_main.ml b/projects/miragesdk/examples/https-unikernel/src/store_main.ml
new file mode 100644
index 000000000..8ae1115d9
--- /dev/null
+++ b/projects/miragesdk/examples/https-unikernel/src/store_main.ml
@@ -0,0 +1,21 @@
+open Lwt.Infix
+
+let () = Common.init_logging ()
+
+let main store_socket =
+ Lwt_main.run begin
+ Lwt_switch.with_switch @@ fun switch ->
+ Store.service () >>= fun store ->
+ Common.listen ~switch ~offer:store store_socket
+ end
+
+open Cmdliner
+
+let store =
+ let doc = "The database store to serve" in
+ Arg.(required @@ pos 0 (some string) None @@ info ~doc ~docv:"STORE" [])
+
+let cmd =
+ Term.(const main $ store), Term.info "store"
+
+let () = Term.(exit @@ eval cmd)
diff --git a/projects/miragesdk/examples/https-unikernel/src/test_store.py b/projects/miragesdk/examples/https-unikernel/src/test_store.py
new file mode 100644
index 000000000..d44441fe6
--- /dev/null
+++ b/projects/miragesdk/examples/https-unikernel/src/test_store.py
@@ -0,0 +1,10 @@
+import capnp
+import capnp
+import proto_capnp
+import socket
+s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM, 0)
+s.connect('/tmp/store.sock')
+client = capnp.TwoPartyClient(s)
+store = client.bootstrap().cast_as(proto_capnp.Store)
+r = store.get(['index.html'])
+print r.wait()
diff --git a/projects/miragesdk/examples/https-unikernel/src/tls_main.ml b/projects/miragesdk/examples/https-unikernel/src/tls_main.ml
new file mode 100644
index 000000000..f71ed5f9d
--- /dev/null
+++ b/projects/miragesdk/examples/https-unikernel/src/tls_main.ml
@@ -0,0 +1,23 @@
+let () = Common.init_logging ()
+
+let main http_socket port =
+ Lwt_main.run begin
+ Lwt_switch.with_switch @@ fun switch ->
+ let http_service = Common.connect ~switch http_socket in
+ Tls_terminator.run ~http_service ~port
+ end
+
+open Cmdliner
+
+let http =
+ let doc = "The HTTP service to use" in
+ Arg.(required @@ opt (some string) None @@ info ~doc ~docv:"HTTP" ["http"])
+
+let tls =
+ let doc = "The TLS port on which to listen for incoming connections" in
+ Arg.(value @@ opt int 8443 @@ info ~doc ~docv:"PORT" ["port"])
+
+let cmd =
+ Term.(const main $ http $ tls), Term.info "tls"
+
+let () = Term.(exit @@ eval cmd)
diff --git a/projects/miragesdk/examples/https-unikernel/src/tls_terminator.ml b/projects/miragesdk/examples/https-unikernel/src/tls_terminator.ml
new file mode 100644
index 000000000..7d91c58e5
--- /dev/null
+++ b/projects/miragesdk/examples/https-unikernel/src/tls_terminator.ml
@@ -0,0 +1,53 @@
+open Lwt.Infix
+open Capnp_rpc_lwt
+
+let make_flow _flow ic oc =
+ Api.Builder.Flow.local @@
+ object (_ : Api.Builder.Flow.service)
+ method read _ =
+ Service.return_lwt (fun () ->
+ Lwt_io.read ~count:4096 ic >|= fun data ->
+ let module R = Api.Builder.Flow.Read_results in
+ let resp, results = Service.Response.create R.init_pointer in
+ R.data_set results data;
+ Ok resp
+ )
+
+ method write req =
+ let module R = Api.Reader.Flow.Write_params in
+ let p = R.of_payload req in
+ let data = R.data_get p in
+ Service.return_lwt (fun () ->
+ Lwt_io.write oc data >>= fun () ->
+ Lwt.return (Ok (Service.Response.create_empty ()))
+ )
+ end
+
+let handle ~http_service flow =
+ let proxy = new Api.Reader.HttpServer.client http_service in
+ let module P = Api.Builder.HttpServer.Accept_params in
+ let req, p = Capability.Request.create P.init_pointer in
+ P.connection_set p (Some (Capability.Request.export req flow));
+ Capability.call_for_value proxy#accept req >|= function
+ | Ok _ -> ()
+ | Error e -> Logs.warn (fun f -> f "Error from HTTP server: %a" Capnp_rpc.Error.pp e)
+
+let run ~port ~http_service =
+ let tls_config : Conduit_lwt_unix.server_tls_config =
+ `Crt_file_path "tls-secrets/server.crt",
+ `Key_file_path "tls-secrets/server.key",
+ `No_password,
+ `Port port
+ in
+ let mode = `TLS tls_config in
+ Logs.info (fun f -> f "Listening on https port %d" port);
+ Conduit_lwt_unix.(serve ~ctx:default_ctx) ~mode (fun flow ic oc ->
+ Logs.info (fun f -> f "Got new TLS connection");
+ let flow_obj = make_flow flow ic oc in
+ handle ~http_service flow_obj
+ )
+
+let init ~switch ~to_http =
+ let tags = Logs.Tag.add Common.Actor.tag (`Blue, "TLS ") Logs.Tag.empty in
+ let http_service = CapTP.bootstrap (CapTP.of_endpoint ~tags ~switch to_http) in
+ run ~http_service ~port:8443