forked from robur/blog.robur.coop
Merge pull request 'add arguments article' (#8) from add-arguments into main
Reviewed-on: robur/blog.robur.coop#8 Reviewed-by: Reynir Björnsson <reynir@reynir.dk>
This commit is contained in:
commit
06b0b673c6
1 changed files with 538 additions and 0 deletions
538
articles/arguments.md
Normal file
538
articles/arguments.md
Normal file
|
@ -0,0 +1,538 @@
|
||||||
|
---
|
||||||
|
date: 2024-10-22
|
||||||
|
title: Runtime arguments in MirageOS
|
||||||
|
description:
|
||||||
|
The history of runtime arguments to a MirageOS unikernel
|
||||||
|
tags:
|
||||||
|
- OCaml
|
||||||
|
- MirageOS
|
||||||
|
author:
|
||||||
|
name: Hannes Mehnert
|
||||||
|
email: hannes@mehnert.org
|
||||||
|
link: https://hannes.robur.coop
|
||||||
|
---
|
||||||
|
|
||||||
|
TL;DR: Passing runtime arguments around is tricky, and prone to change every other month.
|
||||||
|
|
||||||
|
## Motivation
|
||||||
|
|
||||||
|
Sometimes, as an unikernel developer and also as operator, it's nice to have
|
||||||
|
some runtime arguments passed to an unikernel. Now, if you're into OCaml,
|
||||||
|
command-line parsing - together with error messages, man page generation, ... -
|
||||||
|
can be done by the amazing [cmdliner](https://erratique.ch/software/cmdliner)
|
||||||
|
package from Daniel Bünzli.
|
||||||
|
|
||||||
|
MirageOS uses cmdliner for command line argument passing. This also enabled
|
||||||
|
us from the early days to have nice man pages for unikernels (see
|
||||||
|
`my-unikernel-binary --help`). There are two kinds
|
||||||
|
of arguments: those at configuration time (`mirage configure`), such as the
|
||||||
|
target to compile for, and those at runtime - when the unikernel is executed.
|
||||||
|
|
||||||
|
In Mirage 4.8.1 and 4.8.0 (released October 2024) there have been some changes
|
||||||
|
to command-line arguments, which were motivated by 4.5.0 (released April 2024)
|
||||||
|
and user feedback.
|
||||||
|
|
||||||
|
First of all, our current way to pass a custom runtime argument to a unikernel
|
||||||
|
(`unikernel.ml`):
|
||||||
|
```OCaml
|
||||||
|
open Lwt.Infix
|
||||||
|
open Cmdliner
|
||||||
|
|
||||||
|
let hello =
|
||||||
|
let doc = Arg.info ~doc:"How to say hello." [ "hello" ] in
|
||||||
|
let term = Arg.(value & opt string "Hello World!" doc) in
|
||||||
|
Mirage_runtime.register_arg term
|
||||||
|
|
||||||
|
module Hello (Time : Mirage_time.S) = struct
|
||||||
|
let start _time =
|
||||||
|
let rec loop = function
|
||||||
|
| 0 -> Lwt.return_unit
|
||||||
|
| n ->
|
||||||
|
Logs.info (fun f -> f "%s" (hello ()));
|
||||||
|
Time.sleep_ns (Duration.of_sec 1) >>= fun () -> loop (n - 1)
|
||||||
|
in
|
||||||
|
loop 4
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
We define the [Cmdliner.Term.t](https://erratique.ch/software/cmdliner/doc/Cmdliner/Term/index.html#type-t)
|
||||||
|
in line 6 (`let term = ..`) - which provides documentation ("How to say hello."), the option to
|
||||||
|
use (`["hello"]` - which is then translated to `--hello=`), that it is optional,
|
||||||
|
of type `string` (cmdliner allows you to convert the incoming strings to more
|
||||||
|
complex (or more narrow) data types, with decent error handling).
|
||||||
|
|
||||||
|
The defined argument is directly passed to [`Mirage_runtime.register_arg`](https://ocaml.org/p/mirage-runtime/4.8.1/doc/Mirage_runtime/index.html#val-register_arg),
|
||||||
|
(in line 7) so our binding `hello` is of type `unit -> string`.
|
||||||
|
In line 14, the value of the runtime argument is used (`hello ()`) for printing
|
||||||
|
a log message.
|
||||||
|
|
||||||
|
The nice property is that it is all local in `unikernel.ml`, there are no other
|
||||||
|
parts involved. It is just a bunch of API calls. The downside is that `hello ()`
|
||||||
|
should only be evaluated after the function `start` was called - since the
|
||||||
|
`Mirage_runtime` needs to parse and fill in the command line arguments. If you
|
||||||
|
call `hello ()` earlier, you'll get an exception "Called too early. Please delay
|
||||||
|
this call to after the start function of the unikernel.". Also, since
|
||||||
|
Mirage_runtime needs to collect and evaluate the command line arguments, the
|
||||||
|
`Mirage_runtime.register_arg` may only be called at top-level, otherwise you'll
|
||||||
|
get another exception "The function register_arg was called to late. Please call
|
||||||
|
register_arg before the start function is executed (e.g. in a top-level binding).".
|
||||||
|
|
||||||
|
Another advantage is, having it all in unikernel.ml means adding and removing
|
||||||
|
arguments doesn't need another execution of `mirage configure`. Also, any
|
||||||
|
type can be used that the unikernel depends on - the config.ml is compiled only
|
||||||
|
with a small set of dependencies (mirage itself) - and we don't want to impose a
|
||||||
|
large dependency cone for mirage just because someone may like to use
|
||||||
|
X509.Key_type.t as argument type.
|
||||||
|
|
||||||
|
Earlier, before mirage 4.5.0, we had runtime and configure arguments mixed
|
||||||
|
together. And code was generated when `mirage configure` was executed to
|
||||||
|
deal with these arguments. The downsides included: we needed serialization for
|
||||||
|
all command-line arguments (at configure time you could fill the argument, which
|
||||||
|
was then serialized, and deserialized at runtime and used unless the argument
|
||||||
|
was provided explicitly), they had to appear in `config.ml` (which also means
|
||||||
|
changing any would need an execution of `mirage configure`), since they generated code
|
||||||
|
potential errors were in code that the developer didn't write (though we had
|
||||||
|
some `__POS__` arguments to provide error locations in the developer code).
|
||||||
|
|
||||||
|
Related recent changes are:
|
||||||
|
- in mirage 4.8.1, the runtime arguments to configure the OCaml runtime system
|
||||||
|
(such as GC settings, randomization of hashtables, recording of backtraces)
|
||||||
|
are now provided using the [cmdliner-stdlib](https://ocaml.org/p/cmdliner-stdlib)
|
||||||
|
package.
|
||||||
|
- in mirage 4.8.0, for git, dns-client, and happy-eyeballs devices the optional
|
||||||
|
arguments are generated by default - so they are always available and don't
|
||||||
|
need to be manually done by the unikernel developer.
|
||||||
|
|
||||||
|
Let's dive a bit deeper into the history.
|
||||||
|
|
||||||
|
## History
|
||||||
|
|
||||||
|
In MirageOS, since the early stages (I'll go back to 2.7.0 (February 2016) where
|
||||||
|
functoria was introduced) used an embedded fork of `cmdliner` to handle command
|
||||||
|
line arguments.
|
||||||
|
|
||||||
|
[![Animated changes to the hello world unikernel](https://asciinema.org/a/ruHoadi2oZGOzgzMKk5ZYoFgf.svg)](https://asciinema.org/a/ruHoadi2oZGOzgzMKk5ZYoFgf)
|
||||||
|
|
||||||
|
### February 2016 (Mirage 2.7.0)
|
||||||
|
|
||||||
|
When looking into the MirageOS 2.x series, here's the code for our hello world
|
||||||
|
unikernel:
|
||||||
|
|
||||||
|
`config.ml`
|
||||||
|
```OCaml
|
||||||
|
open Mirage
|
||||||
|
|
||||||
|
let hello =
|
||||||
|
let doc = Key.Arg.info ~doc:"How to say hello." ["hello"] in
|
||||||
|
Key.(create "hello" Arg.(opt string "Hello World!" doc))
|
||||||
|
|
||||||
|
let main =
|
||||||
|
foreign
|
||||||
|
~keys:[Key.abstract hello]
|
||||||
|
"Unikernel.Hello" (console @-> job)
|
||||||
|
|
||||||
|
let () = register "hello-key" [main $ default_console]
|
||||||
|
```
|
||||||
|
|
||||||
|
and `unikernel.ml`
|
||||||
|
```OCaml
|
||||||
|
open Lwt.Infix
|
||||||
|
|
||||||
|
module Hello (C: V1_LWT.CONSOLE) = struct
|
||||||
|
let start c =
|
||||||
|
let rec loop = function
|
||||||
|
| 0 -> Lwt.return_unit
|
||||||
|
| n ->
|
||||||
|
C.log c (Key_gen.hello ());
|
||||||
|
OS.Time.sleep 1.0 >>= fun () ->
|
||||||
|
loop (n-1)
|
||||||
|
in
|
||||||
|
loop 4
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
As you can see, the cmdliner term was provided in `config.ml`, and in
|
||||||
|
`unikernel.ml` the expression `Key_gen.hello ()` was used - `Key_gen` was
|
||||||
|
a module generated by the `mirage configure` invocation.
|
||||||
|
|
||||||
|
You can as well see that the term was wrapped in `Key.create "hello"` - where
|
||||||
|
this string was used as the identifier for the code generation.
|
||||||
|
|
||||||
|
As mentioned above, a change needed to be done in `config.ml` and a
|
||||||
|
`mirage configure` to take effect.
|
||||||
|
|
||||||
|
### July 2016 (Mirage 2.9.1)
|
||||||
|
|
||||||
|
The `OS.Time` was functorized with a `Time` functor:
|
||||||
|
|
||||||
|
`config.ml`
|
||||||
|
```OCaml
|
||||||
|
open Mirage
|
||||||
|
|
||||||
|
let hello =
|
||||||
|
let doc = Key.Arg.info ~doc:"How to say hello." ["hello"] in
|
||||||
|
Key.(create "hello" Arg.(opt string "Hello World!" doc))
|
||||||
|
|
||||||
|
let main =
|
||||||
|
foreign
|
||||||
|
~keys:[Key.abstract hello]
|
||||||
|
"Unikernel.Hello" (console @-> time @-> job)
|
||||||
|
|
||||||
|
let () = register "hello-key" [main $ default_console $ default_time]
|
||||||
|
```
|
||||||
|
|
||||||
|
and `unikernel.ml`
|
||||||
|
```OCaml
|
||||||
|
open Lwt.Infix
|
||||||
|
|
||||||
|
module Hello (C: V1_LWT.CONSOLE) (Time : V1_LWT.TIME) = struct
|
||||||
|
let start c _time =
|
||||||
|
let rec loop = function
|
||||||
|
| 0 -> Lwt.return_unit
|
||||||
|
| n ->
|
||||||
|
C.log c (Key_gen.hello ());
|
||||||
|
Time.sleep 1.0 >>= fun () ->
|
||||||
|
loop (n-1)
|
||||||
|
in
|
||||||
|
loop 4
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
### February 2017 (Mirage pre3)
|
||||||
|
|
||||||
|
The `Time` signature changed, now the `sleep_ns` function sleeps in nanoseconds.
|
||||||
|
This avoids floating point numbers at the core of MirageOS. The helper package
|
||||||
|
`duration` is used to avoid manual conversions.
|
||||||
|
|
||||||
|
Also, the console signature changed - and `log` is now inside the Lwt monad.
|
||||||
|
|
||||||
|
`config.ml`
|
||||||
|
```OCaml
|
||||||
|
open Mirage
|
||||||
|
|
||||||
|
let hello =
|
||||||
|
let doc = Key.Arg.info ~doc:"How to say hello." ["hello"] in
|
||||||
|
Key.(create "hello" Arg.(opt string "Hello World!" doc))
|
||||||
|
|
||||||
|
let main =
|
||||||
|
foreign
|
||||||
|
~keys:[Key.abstract hello]
|
||||||
|
~packages:[package "duration"]
|
||||||
|
"Unikernel.Hello" (console @-> time @-> job)
|
||||||
|
|
||||||
|
let () = register "hello-key" [main $ default_console $ default_time]
|
||||||
|
```
|
||||||
|
|
||||||
|
and `unikernel.ml`
|
||||||
|
```OCaml
|
||||||
|
open Lwt.Infix
|
||||||
|
|
||||||
|
module Hello (C: V1_LWT.CONSOLE) (Time : V1_LWT.TIME) = struct
|
||||||
|
let start c _time =
|
||||||
|
let rec loop = function
|
||||||
|
| 0 -> Lwt.return_unit
|
||||||
|
| n ->
|
||||||
|
C.log c (Key_gen.hello ()) >>= fun () ->
|
||||||
|
Time.sleep_ns (Duration.of_sec 1) >>= fun () ->
|
||||||
|
loop (n-1)
|
||||||
|
in
|
||||||
|
loop 4
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
### February 2017 (Mirage 3)
|
||||||
|
|
||||||
|
Another big change is that now console is not used anymore, but
|
||||||
|
[logs](https://erratique.ch/software/logs).
|
||||||
|
|
||||||
|
`config.ml`
|
||||||
|
```OCaml
|
||||||
|
open Mirage
|
||||||
|
|
||||||
|
let hello =
|
||||||
|
let doc = Key.Arg.info ~doc:"How to say hello." ["hello"] in
|
||||||
|
Key.(create "hello" Arg.(opt string "Hello World!" doc))
|
||||||
|
|
||||||
|
let main =
|
||||||
|
foreign
|
||||||
|
~keys:[Key.abstract hello]
|
||||||
|
~packages:[package "duration"]
|
||||||
|
"Unikernel.Hello" (time @-> job)
|
||||||
|
|
||||||
|
let () = register "hello-key" [main $ default_time]
|
||||||
|
```
|
||||||
|
|
||||||
|
and `unikernel.ml`
|
||||||
|
```OCaml
|
||||||
|
open Lwt.Infix
|
||||||
|
|
||||||
|
module Hello (Time : Mirage_time_lwt.S) = struct
|
||||||
|
let start _time =
|
||||||
|
let rec loop = function
|
||||||
|
| 0 -> Lwt.return_unit
|
||||||
|
| n ->
|
||||||
|
Logs.info (fun f -> f "%s" (Key_gen.hello ()));
|
||||||
|
Time.sleep_ns (Duration.of_sec 1) >>= fun () ->
|
||||||
|
loop (n-1)
|
||||||
|
in
|
||||||
|
loop 4
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
### January 2020 (Mirage 3.7.0)
|
||||||
|
|
||||||
|
The `_lwt` is dropped from the interfaces (we used to have Mirage_time and
|
||||||
|
Mirage_time_lwt - where the latter was instantiating the former with concrete
|
||||||
|
types: `type 'a io = Lwt.t` and `type buffer = Cstruct.t` -- in a cleanup
|
||||||
|
session we dropped the `_lwt` interfaces and opam packages. The reasoning was
|
||||||
|
that when we'll get around to move to another IO system, we'll move everything
|
||||||
|
at once anyways. No need to have `lwt` and something else (`async`, or nowadays
|
||||||
|
`miou` or `eio`) in a single unikernel.
|
||||||
|
|
||||||
|
`config.ml`
|
||||||
|
```OCaml
|
||||||
|
open Mirage
|
||||||
|
|
||||||
|
let hello =
|
||||||
|
let doc = Key.Arg.info ~doc:"How to say hello." ["hello"] in
|
||||||
|
Key.(create "hello" Arg.(opt string "Hello World!" doc))
|
||||||
|
|
||||||
|
let main =
|
||||||
|
foreign
|
||||||
|
~keys:[Key.abstract hello]
|
||||||
|
~packages:[package "duration"]
|
||||||
|
"Unikernel.Hello" (time @-> job)
|
||||||
|
|
||||||
|
let () = register "hello-key" [main $ default_time]
|
||||||
|
```
|
||||||
|
|
||||||
|
and `unikernel.ml`
|
||||||
|
```OCaml
|
||||||
|
open Lwt.Infix
|
||||||
|
|
||||||
|
module Hello (Time : Mirage_time.S) = struct
|
||||||
|
let start _time =
|
||||||
|
let rec loop = function
|
||||||
|
| 0 -> Lwt.return_unit
|
||||||
|
| n ->
|
||||||
|
Logs.info (fun f -> f "%s" (Key_gen.hello ()));
|
||||||
|
Time.sleep_ns (Duration.of_sec 1) >>= fun () ->
|
||||||
|
loop (n-1)
|
||||||
|
in
|
||||||
|
loop 4
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
### October 2021 (Mirage 3.10)
|
||||||
|
|
||||||
|
Some renamings to fix warnings. Only `config.ml` changed.
|
||||||
|
|
||||||
|
`config.ml`
|
||||||
|
```OCaml
|
||||||
|
open Mirage
|
||||||
|
|
||||||
|
let hello =
|
||||||
|
let doc = Key.Arg.info ~doc:"How to say hello." ["hello"] in
|
||||||
|
Key.(create "hello" Arg.(opt string "Hello World!" doc))
|
||||||
|
|
||||||
|
let main =
|
||||||
|
main
|
||||||
|
~keys:[key hello]
|
||||||
|
~packages:[package "duration"]
|
||||||
|
"Unikernel.Hello" (time @-> job)
|
||||||
|
|
||||||
|
let () = register "hello-key" [main $ default_time]
|
||||||
|
```
|
||||||
|
|
||||||
|
and `unikernel.ml`
|
||||||
|
```OCaml
|
||||||
|
open Lwt.Infix
|
||||||
|
|
||||||
|
module Hello (Time : Mirage_time.S) = struct
|
||||||
|
let start _time =
|
||||||
|
let rec loop = function
|
||||||
|
| 0 -> Lwt.return_unit
|
||||||
|
| n ->
|
||||||
|
Logs.info (fun f -> f "%s" (Key_gen.hello ()));
|
||||||
|
Time.sleep_ns (Duration.of_sec 1) >>= fun () ->
|
||||||
|
loop (n-1)
|
||||||
|
in
|
||||||
|
loop 4
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
### June 2023 (Mirage 4.4)
|
||||||
|
|
||||||
|
The argument was moved to runtime.
|
||||||
|
|
||||||
|
`config.ml`
|
||||||
|
```OCaml
|
||||||
|
open Mirage
|
||||||
|
|
||||||
|
let hello =
|
||||||
|
let doc = Key.Arg.info ~doc:"How to say hello." ["hello"] in
|
||||||
|
Key.(create "hello" Arg.(opt ~stage:`Run string "Hello World!" doc))
|
||||||
|
|
||||||
|
let main =
|
||||||
|
main
|
||||||
|
~keys:[key hello]
|
||||||
|
~packages:[package "duration"]
|
||||||
|
"Unikernel.Hello" (time @-> job)
|
||||||
|
|
||||||
|
let () = register "hello-key" [main $ default_time]
|
||||||
|
```
|
||||||
|
|
||||||
|
and `unikernel.ml`
|
||||||
|
```OCaml
|
||||||
|
open Lwt.Infix
|
||||||
|
|
||||||
|
module Hello (Time : Mirage_time.S) = struct
|
||||||
|
let start _time =
|
||||||
|
let rec loop = function
|
||||||
|
| 0 -> Lwt.return_unit
|
||||||
|
| n ->
|
||||||
|
Logs.info (fun f -> f "%s" (Key_gen.hello ());
|
||||||
|
Time.sleep_ns (Duration.of_sec 1) >>= fun () ->
|
||||||
|
loop (n-1)
|
||||||
|
in
|
||||||
|
loop 4
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
### March 2024 (Mirage 4.5)
|
||||||
|
|
||||||
|
The runtime argument is in `config.ml` refering to the argument as string
|
||||||
|
("Unikernel.hello"), and being passed to the `start` function as argument.
|
||||||
|
|
||||||
|
`config.ml`
|
||||||
|
```OCaml
|
||||||
|
open Mirage
|
||||||
|
|
||||||
|
let runtime_args = [ runtime_arg ~pos:__POS__ "Unikernel.hello" ]
|
||||||
|
|
||||||
|
let main =
|
||||||
|
main
|
||||||
|
~runtime_args
|
||||||
|
~packages:[package "duration"]
|
||||||
|
"Unikernel.Hello" (time @-> job)
|
||||||
|
|
||||||
|
let () = register "hello-key" [main $ default_time]
|
||||||
|
```
|
||||||
|
|
||||||
|
and `unikernel.ml`
|
||||||
|
```OCaml
|
||||||
|
open Lwt.Infix
|
||||||
|
open Cmdliner
|
||||||
|
|
||||||
|
let hello =
|
||||||
|
let doc = Arg.info ~doc:"How to say hello." [ "hello" ] in
|
||||||
|
Arg.(value & opt string "Hello World!" doc)
|
||||||
|
|
||||||
|
module Hello (Time : Mirage_time.S) = struct
|
||||||
|
let start _time hello =
|
||||||
|
let rec loop = function
|
||||||
|
| 0 -> Lwt.return_unit
|
||||||
|
| n ->
|
||||||
|
Logs.info (fun f -> f "%s" hello);
|
||||||
|
Time.sleep_ns (Duration.of_sec 1) >>= fun () ->
|
||||||
|
loop (n-1)
|
||||||
|
in
|
||||||
|
loop 4
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
### October 2024 (Mirage 4.8)
|
||||||
|
|
||||||
|
Again, moved out of `config.ml`.
|
||||||
|
|
||||||
|
`config.ml`
|
||||||
|
```OCaml
|
||||||
|
open Mirage
|
||||||
|
|
||||||
|
let main =
|
||||||
|
main
|
||||||
|
~packages:[package "duration"]
|
||||||
|
"Unikernel.Hello" (time @-> job)
|
||||||
|
|
||||||
|
let () = register "hello-key" [main $ default_time]
|
||||||
|
```
|
||||||
|
|
||||||
|
and `unikernel.ml`
|
||||||
|
```OCaml
|
||||||
|
open Lwt.Infix
|
||||||
|
open Cmdliner
|
||||||
|
|
||||||
|
let hello =
|
||||||
|
let doc = Arg.info ~doc:"How to say hello." [ "hello" ] in
|
||||||
|
Mirage_runtime.register_arg Arg.(value & opt string "Hello World!" doc)
|
||||||
|
|
||||||
|
module Hello (Time : Mirage_time.S) = struct
|
||||||
|
let start _time =
|
||||||
|
let rec loop = function
|
||||||
|
| 0 -> Lwt.return_unit
|
||||||
|
| n ->
|
||||||
|
Logs.info (fun f -> f "%s" (hello ()));
|
||||||
|
Time.sleep_ns (Duration.of_sec 1) >>= fun () ->
|
||||||
|
loop (n-1)
|
||||||
|
in
|
||||||
|
loop 4
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2024 (Not yet released)
|
||||||
|
|
||||||
|
This is the future with time defunctorized. Read more in the [discussion](https://github.com/mirage/mirage/issues/1513).
|
||||||
|
To delay the start function, a `dep` of `noop` is introduced.
|
||||||
|
|
||||||
|
`config.ml`
|
||||||
|
```OCaml
|
||||||
|
open Mirage
|
||||||
|
|
||||||
|
let main =
|
||||||
|
main
|
||||||
|
~packages:[package "duration"]
|
||||||
|
~dep:[dep noop]
|
||||||
|
"Unikernel" job
|
||||||
|
|
||||||
|
let () = register "hello-key" [main]
|
||||||
|
```
|
||||||
|
|
||||||
|
and `unikernel.ml`
|
||||||
|
```OCaml
|
||||||
|
open Lwt.Infix
|
||||||
|
open Cmdliner
|
||||||
|
|
||||||
|
let hello =
|
||||||
|
let doc = Arg.info ~doc:"How to say hello." [ "hello" ] in
|
||||||
|
Mirage_runtime.register_arg Arg.(value & opt string "Hello World!" doc)
|
||||||
|
|
||||||
|
let start () =
|
||||||
|
let rec loop = function
|
||||||
|
| 0 -> Lwt.return_unit
|
||||||
|
| n ->
|
||||||
|
Logs.info (fun f -> f "%s" (hello ()));
|
||||||
|
Mirage_timer.sleep_ns (Duration.of_sec 1) >>= fun () ->
|
||||||
|
loop (n-1)
|
||||||
|
in
|
||||||
|
loop 4
|
||||||
|
```
|
||||||
|
|
||||||
|
## Conclusion
|
||||||
|
|
||||||
|
The history of hello world shows that over time we slowly improve the developer
|
||||||
|
experience, and removing the boilerplate needed to get MirageOS unikernels up
|
||||||
|
and running. This is work over a decade including lots of other (here invisible)
|
||||||
|
improvements to the mirage utility.
|
||||||
|
|
||||||
|
Our current goal is to minimize the code generated by mirage, since code
|
||||||
|
generation has lots of issues (e.g. error locations, naming, binary size). It
|
||||||
|
is a long journey. At the same time, we are working on improving the performance
|
||||||
|
of MirageOS unikernels, developing unikernels that are useful in the real
|
||||||
|
world ([VPN endpoint](https://github.com/robur-coop/miragevpn), [DNSmasq replacement](https://github.com/robur-coop/dnsvizor), ...), and also [simplifying the
|
||||||
|
deployment of MirageOS unikernels](https://github.com/robur-coop/mollymawk).
|
||||||
|
|
||||||
|
If you're interested in MirageOS and using it in your domain, don't hesitate
|
||||||
|
to reach out to us (via eMail: team@robur.coop) - we're keen to deploy MirageOS
|
||||||
|
and find more domains where it is useful. If you can spare a dime, we're a
|
||||||
|
registered non-profit in Germany - and can provide tax-deductable receipts for
|
||||||
|
donations ([more information](https://robur.coop/Donate)).
|
Loading…
Reference in a new issue