Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
d5bffcc
feat(suidhelper): mix suidhelper.install wrapping cargo xtask install
markovejnovic Jun 25, 2026
9201730
docs: document full node requirements (postgres, dm targets, kvm, cgr…
markovejnovic Jun 25, 2026
b68dd5e
docs: replace em-dashes with ASCII hyphens in intro.md
markovejnovic Jun 25, 2026
e2253f6
fix(suidhelper): make mix suidhelper.install tty-safe
markovejnovic Jun 25, 2026
4ddcd4d
feat(suidhelper): source device binaries from config, drop caller --bin
markovejnovic Jun 25, 2026
f340571
docs: Postgres quickstart, optional helper config, install caveats
markovejnovic Jun 25, 2026
23a5883
docs: how to load device-mapper modules for the dm targets
markovejnovic Jun 25, 2026
b0c0b5f
docs: how to create the parent cgroup + delegate controllers
markovejnovic Jun 25, 2026
5761a4e
fix(node): start Layer before Budget.Supervisor
markovejnovic Jun 25, 2026
8dc61d3
fix: quiet benign ThinPool port exits and keyless OTLP export
markovejnovic Jun 25, 2026
1320700
fix(thin_pool): reclaim a stale dm pool on init
markovejnovic Jun 25, 2026
6ef56d2
fix(node): validate OCI loader tools at boot
markovejnovic Jun 25, 2026
06cc05b
deslop
markovejnovic Jun 25, 2026
c0685ae
fix: ignore benign port exits in the remaining trap_exit servers
markovejnovic Jun 25, 2026
7fa0480
fix(fire_vmm): child_spec key must be :id, not :vm_id
markovejnovic Jun 25, 2026
2ac546a
fix(scheduler): log why a candidate refused placement
markovejnovic Jun 25, 2026
68c8d4b
feat(node): reclaim orphaned dm/loop devices at boot
markovejnovic Jun 25, 2026
b6ce604
feat(suidhelper): add firecracker/jailer/uid_gid_range to config
markovejnovic Jun 25, 2026
8f671a3
feat(suidhelper): add jailer subcommand that execs the jailer as root
markovejnovic Jun 25, 2026
a5e098c
fix(suidhelper): fail closed on close_range error; _exit on jailer ex…
markovejnovic Jun 25, 2026
5f9f36b
refactor(fire_vmm): drive jailer through suidhelper; drop Provider
markovejnovic Jun 25, 2026
3a58b77
feat(mix): add firecracker.install task to fetch+configure firecracke…
markovejnovic Jun 25, 2026
a8743fc
docs: firecracker/jailer are operator-installed via mix firecracker.i…
markovejnovic Jun 26, 2026
5770f57
fix(config): node and helper read the same firecracker/jailer TOML keys
markovejnovic Jun 26, 2026
ffd46a4
feat(mix): firecracker.install prints the chown/chmod root commands
markovejnovic Jun 26, 2026
202141b
feat(fire_vmm): log jailer/firecracker output and real exit status
markovejnovic Jun 26, 2026
0ecccf5
feat(fire_vmm): surface the API readiness-probe failure reason
markovejnovic Jun 26, 2026
2d48872
feat(suidhelper): add chroot-jail grant-api to hand the API socket to…
markovejnovic Jun 26, 2026
85bc4a4
feat(fire_vmm): grant the API socket before probing so the controller…
markovejnovic Jun 26, 2026
71d6440
fix(hyper): generate alphanumeric vm ids (firecracker rejects _)
markovejnovic Jun 26, 2026
b3c2315
fix(fire_vmm): self-register per-VM names in init, not via a start name
markovejnovic Jun 26, 2026
4196153
fix(suidhelper): grant-api opens the jail root dir for node traversal
markovejnovic Jun 26, 2026
5284878
fix(suidhelper): resolve dm symlink to real node before rootfs open
markovejnovic Jun 26, 2026
f2dc209
feat(fire_vmm): log boot failures in the Configuring state
markovejnovic Jun 26, 2026
725e1ca
fix(suidhelper): cgroup.kill the leaf before removing it
markovejnovic Jun 26, 2026
8fd3817
fix(fire_vmm): guarantee firecracker death on teardown
markovejnovic Jun 26, 2026
09c1ea8
feat(node): periodic Reaper to GC orphaned VM resources
markovejnovic Jun 26, 2026
988b026
refactor(vm): extract Hyper.Vm.Id (type + generator)
markovejnovic Jun 26, 2026
7811ed6
test(suidhelper): tolerate shell-set PWD in jailer empty-env e2e
markovejnovic Jun 26, 2026
b5a0c16
style(node): wrap with_image_lease spec after Vm.Id.t() rename
markovejnovic Jun 26, 2026
dfb6078
refactor(config): make config.toml the single source of truth
markovejnovic Jun 26, 2026
727d08e
refactor(config): nest cgroup + uid_gid_range under [jails] table
markovejnovic Jun 26, 2026
32d84cc
feat(config): read node tool paths from [tools]
markovejnovic Jun 27, 2026
9b72969
feat(config): merge optional /etc/hyper/config.exs at runtime
markovejnovic Jun 27, 2026
46dfd96
docs: highlight cookbook code blocks via makeup_syntect
markovejnovic Jun 27, 2026
ed6ad5d
docs(cookbook): document [tools]/[jails], node tools, user config
markovejnovic Jun 27, 2026
293293b
Merge origin/main into chore/get-a-vm-running
markovejnovic Jun 27, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 21 additions & 31 deletions docs/cookbook/intro.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,51 +13,41 @@ of the aforementioned systems.
The absolute best way to understand `Hyper` and how it works is to play around
with it.

## Getting Started
#### Auto-redistributed

The absolute best way to get started with `Hyper` is to play with it.
`umoci` and the guest `vmlinux` kernels are downloaded, checksum-verified, and
managed by Hyper itself; you do not install them.

### Requirements

Hyper requires the following software be installed on each node running it:

- [`skopeo`](https://github.com/containers/skopeo)
- [`e2fsprogs`](https://github.com/tytso/e2fsprogs)

Hyper has more runtime dependencies, but they are automatically redistributed
by Hyper.
`firecracker` and `jailer` are not auto-downloaded. Install them with
`mix firecracker.install [--prefix <dir>]` (default prefix `/opt/firecracker`),
which downloads the pinned v1.16.0 release, places the binaries at
`<prefix>/firecracker` and `<prefix>/jailer`, and prints the `/etc/hyper/config.toml`
snippet to paste in.

### Installation

<!-- TODO(markovejnovic): Write this out. -->

### Configuration

Running `Hyper` is involved and requires a large number of pre-requisites. The
configuration of `:hyper` can be done by creating a `config :hyper` entry in
your `config.exs`. Refer to the given snippet for details on each
configuration.
Almost all host configuration — `work_dir`, the `[tools]` binary paths
(`firecracker`, `jailer`, `dmsetup`, ...), and the `[jails]` table (`cgroup`,
`uid_gid_range`) — lives in `/etc/hyper/config.toml` (the single source of
truth shared with the setuid helper, shown above), and every node-local path
(`jails`, `socks`, `scratch`, `layers`) is derived from `work_dir`. None of it is
repeated in `config :hyper`.

The node's own tool paths (`skopeo`, `mke2fs`, `umoci`, `suidhelper`) now live in
the `[tools]` table of `/etc/hyper/config.toml` alongside the privileged binaries,
so `config :hyper` holds only the per-architecture guest kernels (each with a
default, so the block may be omitted):

```elixir
config :hyper,
# TODO(markovejnovic): Remove this after it gets auto-downloaded.
jailer_bin: "/opt/firecracker/jailer-v1.16.0-x86_64",
# TODO(markovejnovic): Remove this after it gets auto-downloaded.
firecracker_bin: "/opt/firecracker/firecracker-v1.16.0-x86_64",
# You must create a parent cgroup on your system. Continue reading for
# further details.
cgroup_parent: "hyper",
# TODO(markovejnovic): Merge these directories into one.
jailer_chroot_base: "/srv/hyper/jails",
socket_dir: "/srv/hyper/socks",
scratch_dir: "/srv/hyper/scratch",
# Hyper requires that each VM you pass
uid_gid_range: {900_000, 999_999},
layer_dir: "/srv/hyper/layers"
# Per-architecture guest kernel images placed on the host.
vmlinux: %{x86_64: "/srv/hyper/redist/vmlinux/vmlinux-x86_64"}
```

<!-- TODO(markovejnovic): Update the config section. -->

### Usage

<!-- TODO(markovejnovic): Write out how to boot hyper etc -->
Expand Down
10 changes: 3 additions & 7 deletions lib/hyper.ex
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ defmodule Hyper do
@spec create_vm(Hyper.Vm.Spec.t()) :: {:ok, Hyper.Vm.t()} | {:error, term()}
def create_vm(%Hyper.Vm.Spec{} = spec) do
with {:ok, arch} <- resolve_arch(spec.arch) do
vm_id = gen_vm_id()
vm_id = Hyper.Vm.Id.generate()
spec = %{spec | arch: arch}
instance_spec = Hyper.Vm.Instance.spec(spec.type)

Expand All @@ -34,17 +34,13 @@ defmodule Hyper do
end
end

@doc "Generate a fresh VM id (url-safe base64, dm-name compatible)."
@spec gen_vm_id() :: Hyper.Vm.id()
def gen_vm_id, do: Base.url_encode64(:crypto.strong_rand_bytes(9), padding: false)

@spec resolve_arch(Hyper.Vm.Instance.arch() | nil) ::
{:ok, Hyper.Vm.Instance.arch()} | {:error, term()}
defp resolve_arch(nil), do: Sys.Arch.current()
defp resolve_arch(arch), do: {:ok, arch}

@doc "Cluster-wide: which node currently runs `vm_id`? `nil` if unknown."
@spec whereis(Hyper.Vm.id()) :: node() | nil
@spec whereis(Hyper.Vm.Id.t()) :: node() | nil
def whereis(vm_id), do: Hyper.Cluster.Routing.whereis(vm_id)

@doc """
Expand All @@ -57,7 +53,7 @@ defmodule Hyper do
died with its host, so "unknown" is the truthful answer. Only `:erpc`'s own
transport failures are swallowed; a genuine fault in the lookup still raises.
"""
@spec id(Hyper.Vm.t()) :: Hyper.Vm.id() | nil
@spec id(Hyper.Vm.t()) :: Hyper.Vm.Id.t() | nil
def id(pid) when is_pid(pid) do
:erpc.call(node(pid), Hyper.Cluster.Routing, :id_for, [pid])
catch
Expand Down
26 changes: 23 additions & 3 deletions lib/hyper/cluster/routing.ex
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,28 @@ defmodule Hyper.Cluster.Routing do
@spec via(term()) :: {:via, module(), {atom(), term()}}
def via(key), do: {:via, Horde.Registry, {@name, key}}

@doc """
Register the calling process under `key` from inside its own `init`.

Prefer this over starting a process with a `{:via, Horde.Registry, _}` name.
OTP's post-start name check (`gen:get_proc_name`) calls `whereis_name`
immediately after the synchronous `register`, but Horde materialises the name
into its local ETS only asynchronously, via the DeltaCRDT diff loop. Under
registry churn that read loses the race and OTP aborts startup with
`{:process_not_registered_via, Horde.Registry}`. Registering from within
`init` carries no such self-check, while leaving the name cluster-resolvable
through `via/1` once the diff propagates (callers already tolerate that lag).
"""
@spec register_self(term()) :: :ok | {:error, {:already_registered, pid()}}
def register_self(key) do
case Horde.Registry.register(@name, key, nil) do
{:ok, _pid} -> :ok
{:error, {:already_registered, _pid}} = err -> err
end
end

@doc "Which node currently runs `vm_id`? `nil` if unknown."
@spec whereis(Hyper.Vm.id()) :: node() | nil
@spec whereis(Hyper.Vm.Id.t()) :: node() | nil
@decorate with_span("Hyper.Cluster.Routing.whereis", include: [:vm_id])
def whereis(vm_id) do
case Horde.Registry.lookup(@name, {vm_id, :supervisor}) do
Expand All @@ -43,7 +63,7 @@ defmodule Hyper.Cluster.Routing do
replica via a registry match spec; intended to be called on the node that owns
`pid` (see `Hyper.id/1`).
"""
@spec id_for(pid()) :: Hyper.Vm.id() | nil
@spec id_for(pid()) :: Hyper.Vm.Id.t() | nil
@decorate with_span("Hyper.Cluster.Routing.id_for")
def id_for(pid) when is_pid(pid) do
case Horde.Registry.select(@name, [
Expand All @@ -55,7 +75,7 @@ defmodule Hyper.Cluster.Routing do
end

@doc "Every VM the cluster currently knows about, paired with the node it runs on."
@spec all() :: [{Hyper.Vm.id(), node()}]
@spec all() :: [{Hyper.Vm.Id.t(), node()}]
@decorate with_span("Hyper.Cluster.Routing.all")
def all do
@name
Expand Down
13 changes: 11 additions & 2 deletions lib/hyper/cluster/scheduler.ex
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ defmodule Hyper.Cluster.Scheduler do
alias Hyper.Vm.Instance.Spec
alias Unit.Information

require Logger

use OpenTelemetryDecorator

@type layer_sizes :: [{Hyper.Layer.id(), Unit.Information.t()}]
Expand Down Expand Up @@ -45,8 +47,15 @@ defmodule Hyper.Cluster.Scheduler do
|> candidates(layers)
|> Enum.reduce_while({:error, :no_capacity}, fn node, acc ->
case attempt.(node) do
{:ok, result} -> {:halt, {:ok, {node, result}}}
{:error, _reason} -> {:cont, acc}
{:ok, result} ->
{:halt, {:ok, {node, result}}}

{:error, reason} ->
# The candidate fit the snapshot but refused at confirmation time.
# Log the real reason: otherwise an actual boot failure on the only
# candidate is indistinguishable from genuine `:no_capacity`.
Logger.warning("scheduler: #{inspect(node)} refused placement: #{inspect(reason)}")
{:cont, acc}
end
end)
end
Expand Down
8 changes: 4 additions & 4 deletions lib/hyper/grpc/codec.ex
Original file line number Diff line number Diff line change
Expand Up @@ -85,15 +85,15 @@ defmodule Hyper.Grpc.Codec do
end

@doc "Convert a domain result to an outbound response message, or an error to `GRPC.RPCError`."
@spec to_grpc({:created, Hyper.Vm.id(), node()}) :: CreateVmResponse.t()
@spec to_grpc({:created, Hyper.Vm.Id.t(), node()}) :: CreateVmResponse.t()
def to_grpc({:created, vm_id, node}) when is_binary(vm_id),
do: %CreateVmResponse{vm_id: vm_id, node: to_string(node)}

@spec to_grpc({:located, Hyper.Vm.id(), node()}) :: GetVmResponse.t()
@spec to_grpc({:located, Hyper.Vm.Id.t(), node()}) :: GetVmResponse.t()
def to_grpc({:located, vm_id, node}),
do: %GetVmResponse{vm_id: vm_id, node: to_string(node)}

@spec to_grpc({:vms, [{Hyper.Vm.id(), node()}]}) :: ListVmsResponse.t()
@spec to_grpc({:vms, [{Hyper.Vm.Id.t(), node()}]}) :: ListVmsResponse.t()
def to_grpc({:vms, vms}),
do: %ListVmsResponse{vms: Enum.map(vms, &vm/1)}

Expand All @@ -117,7 +117,7 @@ defmodule Hyper.Grpc.Codec do
def to_grpc({:exit, {:nodedown, _}}), do: rpc_error(:machine_unreachable)
def to_grpc({:exit, reason}), do: rpc_error({:stop_failed, reason})

@spec vm({Hyper.Vm.id(), node()}) :: Vm.t()
@spec vm({Hyper.Vm.Id.t(), node()}) :: Vm.t()
defp vm({vm_id, node}), do: %Vm{vm_id: vm_id, node: to_string(node)}

@spec instance_type(instance_enum()) :: {:ok, Hyper.Vm.Instance.t()}
Expand Down
4 changes: 2 additions & 2 deletions lib/hyper/img/db/lease.ex
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ defmodule Hyper.Img.Db.Lease do
Upserts on `(node_id, vm_id)` - the same call both takes a fresh lease and
heartbeats a live one.
"""
@spec bump(Hyper.Img.id(), Hyper.Vm.id(), Unit.Time.t()) ::
@spec bump(Hyper.Img.id(), Hyper.Vm.Id.t(), Unit.Time.t()) ::
{:ok, %__MODULE__{}} | {:error, Ecto.Changeset.t()}
@decorate with_span("Hyper.Img.Db.Lease.bump", include: [:image_id, :vm_id])
def bump(image_id, vm_id, ttl) do
Expand All @@ -72,7 +72,7 @@ defmodule Hyper.Img.Db.Lease do
Release the lease issued to the given node_id and the given vm_id. Since each VM only ever uses
one image, it is not necessary to specify the image id.
"""
@spec release(Hyper.Vm.id()) :: :ok
@spec release(Hyper.Vm.Id.t()) :: :ok
@decorate with_span("Hyper.Img.Db.Lease.release", include: [:vm_id])
def release(vm_id) do
query = from(l in __MODULE__, where: l.node_id == ^to_string(node()) and l.vm_id == ^vm_id)
Expand Down
50 changes: 43 additions & 7 deletions lib/hyper/node.ex
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@ defmodule Hyper.Node do
real-time monitors backing the soft budget (`Hyper.Node.Budget.Soft`).
Lives here, not at the application root, because both are per-node and only
meaningful while this node hosts VMs.

* `Hyper.Node.Reaper` - a periodic, liveness-aware GC for per-VM host
resources (orphaned firecracker cgroups and `hyper-rw-*` dm volumes) stranded
by an unclean death whose vm_id never reboots. Started last so the VM
supervisor it consults for liveness is already up.
"""

use Supervisor
Expand All @@ -40,19 +45,31 @@ defmodule Hyper.Node do

def start_link(opts \\ []) do
case test_system() do
:ok -> Supervisor.start_link(__MODULE__, opts, name: __MODULE__)
{:error, reason} -> {:error, reason}
:ok ->
# Clear any dm/loop devices a previous unclean shutdown left behind,
# before the device-owning children start and collide with them.
:ok = Hyper.Node.Reclaim.run()
Supervisor.start_link(__MODULE__, opts, name: __MODULE__)

{:error, reason} ->
{:error, reason}
end
end

@impl true
def init(_opts) do
children = [
Hyper.Node.Users,
# Layer owns Hyper.Node.Layer.Registry, which Budget.Advertiser queries
# (via Hyper.Node.Layer.active/0) as it advertises on init - so Layer must
# be up first.
Hyper.Node.Layer,
Hyper.Node.Budget.Supervisor,
{DynamicSupervisor, name: @vm_sup, strategy: :one_for_one},
Hyper.Node.Layer,
Hyper.Node.Img
Hyper.Node.Img,
# Last child: :one_for_one starts in order, so the VM supervisor and Img are
# up before the reaper's first tick can read their liveness.
Hyper.Node.Reaper
]

Supervisor.init(children, strategy: :one_for_one)
Expand All @@ -63,7 +80,7 @@ defmodule Hyper.Node do
layer, resolve the kernel, and start the VM supervisor. The uid is freed and
the mutable layer torn down automatically when the VM supervisor dies.
"""
@spec start_image_vm(Hyper.Vm.id(), Hyper.Vm.Spec.t()) :: {:ok, pid()} | {:error, term()}
@spec start_image_vm(Hyper.Vm.Id.t(), Hyper.Vm.Spec.t()) :: {:ok, pid()} | {:error, term()}
@decorate with_span("Hyper.Node.start_image_vm", include: [:vm_id, :spec])
def start_image_vm(vm_id, %Hyper.Vm.Spec{} = spec) do
with {:ok, uid} <- Users.claim(),
Expand All @@ -89,7 +106,7 @@ defmodule Hyper.Node do
end

@doc false
@spec build_opts(Hyper.Vm.id(), Hyper.Vm.Spec.t(), Users.id(), pid(), Path.t()) ::
@spec build_opts(Hyper.Vm.Id.t(), Hyper.Vm.Spec.t(), Users.id(), pid(), Path.t()) ::
FireVMM.Opts.t()
def build_opts(vm_id, %Hyper.Vm.Spec{} = spec, uid, mutable, kernel) do
%FireVMM.Opts{
Expand Down Expand Up @@ -144,10 +161,11 @@ defmodule Hyper.Node do
@spec test_system :: :ok | {:error, term()}
def test_system do
with {:ok, _} <- Hyper.Cfg.Budget.load(),
:ok <- Hyper.Node.FireVMM.Provider.ensure_installed(),
:ok <- check_firecracker_bins(),
:ok <- Hyper.Node.FireVMM.VmLinux.Provider.ensure_installed(),
:ok <- Hyper.Node.Vmlinux.test_system(),
:ok <- Hyper.Img.OciLoader.Umoci.ensure_installed(),
:ok <- Hyper.Img.OciLoader.test_system(),
:ok <- Hyper.Node.Users.test_system(),
:ok <- Hyper.Node.Layer.Repo.test_system(),
:ok <- Hyper.SuidHelper.test_system(),
Expand All @@ -157,6 +175,24 @@ defmodule Hyper.Node do
end
end

@spec check_firecracker_bins ::
:ok
| {:error, {:firecracker_bin_missing | :jailer_bin_missing, Path.t()}}
| {:error, :firecracker_not_configured | :jailer_not_configured}
defp check_firecracker_bins do
with {:fc, {:ok, fc}} <- {:fc, Hyper.Cfg.Tools.firecracker_configured()},
{:jail, {:ok, jail}} <- {:jail, Hyper.Cfg.Tools.jailer_configured()} do
cond do
not Sys.Posix.executable?(fc) -> {:error, {:firecracker_bin_missing, fc}}
not Sys.Posix.executable?(jail) -> {:error, {:jailer_bin_missing, jail}}
true -> :ok
end
else
{:fc, :error} -> {:error, :firecracker_not_configured}
{:jail, :error} -> {:error, :jailer_not_configured}
end
end

@spec check_helper_base(Path.t()) ::
:ok | {:error, {:suid_helper_base_mismatch, Path.t(), Path.t()}}
defp check_helper_base(base) do
Expand Down
35 changes: 22 additions & 13 deletions lib/hyper/node/fire_vmm.ex
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ defmodule Hyper.Node.FireVMM do
defstruct [:vm_id, :uid, :gid, :type, :arch, :mutable, :kernel, :boot_args]

@type t :: %__MODULE__{
vm_id: Hyper.Vm.id(),
vm_id: Hyper.Vm.Id.t(),
uid: Hyper.Node.Users.id(),
gid: Hyper.Node.Users.id(),
type: Hyper.Vm.Instance.t(),
Expand All @@ -47,14 +47,15 @@ defmodule Hyper.Node.FireVMM do

@spec start_link(Opts.t()) :: Supervisor.on_start()
def start_link(opts) do
Supervisor.start_link(__MODULE__, opts, name: via(opts.vm_id))
Supervisor.start_link(__MODULE__, opts)
end

@spec child_spec(Opts.t()) :: Supervisor.child_spec()
def child_spec(opts) do
# Keyed by VM id and :transient so a cleanly-stopped VM is not rebooted by
# the node-level DynamicSupervisor.
%{
vm_id: {__MODULE__, opts.vm_id},
id: {__MODULE__, opts.vm_id},
start: {__MODULE__, :start_link, [opts]},
type: :supervisor,
restart: :transient
Expand All @@ -63,18 +64,26 @@ defmodule Hyper.Node.FireVMM do

@impl true
def init(opts) do
children = [
# Client must be registered before Core: Core starts the State machine,
# which calls Client.run while waiting for the daemon's API. Client depends
# only on vm_id (an independent peer), so it has no reverse dependency.
{Client, %Client.Opts{vm_id: opts.vm_id}},
{Core, opts}
]
# Self-register the cluster routing entry here rather than via a start name;
# see `Hyper.Cluster.Routing.register_self/1`. A fresh random vm_id never
# collides, so `:already_registered` only happens against a stale dead
# incarnation - decline the start and let the supervisor retry clean.
case Hyper.Cluster.Routing.register_self({opts.vm_id, :supervisor}) do
:ok ->
children = [
# Client must be registered before Core: Core starts the State machine,
# which calls Client.run while waiting for the daemon's API. Client
# depends only on vm_id (an independent peer), so no reverse dependency.
{Client, %Client.Opts{vm_id: opts.vm_id}},
{Core, opts}
]

Supervisor.init(children, strategy: :one_for_one)
end
Supervisor.init(children, strategy: :one_for_one)

defp via(vm_id), do: Hyper.Cluster.Routing.via({vm_id, :supervisor})
{:error, _} ->
:ignore
end
end

@doc "Test whether the system can run firecracker VMMs."
@spec test_system() :: :ok | {:error, term()}
Expand Down
Loading
Loading