# `Hyper.Node`
[🔗](https://github.com/harmont-dev/hyper/blob/main/lib/hyper/node.ex#L1)

Per-machine supervisor. Exactly one `Hyper.Node` runs per BEAM node; it owns
every microVM scheduled onto this machine.

Children:

  * VM routing lives in `Hyper.Cluster.Routing` (a cluster-wide CRDT started by
    `Hyper.Cluster`, above this supervisor), not here - this node only owns the
    *local* processes that run its microVMs.

  * `Hyper.Node.ImageStore` - a node-local content-addressed blob cache. Started
    before the VM supervisor so VMs can pull base images on boot.

  * `Hyper.Node.VMSupervisor` - a **local** `DynamicSupervisor` that starts one
    `Hyper.Node.FireVMM` per VM. Local on purpose: a firecracker VM is pinned to
    this machine's kernel/rootfs/cgroup/tap devices and cannot migrate, so we
    deliberately avoid `Horde.DynamicSupervisor` (which would try to restart
    VMs on a surviving node - cold-booting a ghost).

  * `Hyper.Node.Users` - manages an availability pool of users. Each VM gets its own user id
    and group id.

  * `Hyper.Node.Budget.Supervisor` - the node's resource budget: hard
    memory/disk accounting (`Hyper.Node.Budget.Hard`) plus the `Sys.Mon`
    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.

# `child_spec`

Returns a specification to start this module under a supervisor.

See `Supervisor`.

# `exec`

```elixir
@spec exec(Hyper.Vm.Id.t(), [String.t()], keyword()) ::
  {:ok, %{stdout: binary(), stderr: binary(), exit_code: integer()}}
  | {:error, term()}
```

Run `argv` in a VM hosted **on this node**, identified by `vm_id`, via its
relay/agent. The node-local half of `Hyper.exec/3`, which resolves the owning
node and `:erpc`-calls this function there.

# `fork_vm_local`

```elixir
@spec fork_vm_local(Hyper.Vm.Id.t()) :: {:ok, pid()} | {:error, term()}
```

Fork the running VM `parent_vm_id` **onto this node**: budget-admitted like any
placement (`try_run/3`), erroring with the node's admission verdict
(`:mem_exhausted`, `:cpu_saturated`, ...) instead of falling back elsewhere.
The guest is asked to `sync` first (best-effort); the snapshot is otherwise
crash-consistent, like a power cut.

# `publish_fork_image`

```elixir
@spec publish_fork_image(Hyper.Vm.Id.t()) ::
  {:ok,
   %{
     img_id: Hyper.Img.id(),
     type: Hyper.Vm.Instance.t(),
     arch: Hyper.Vm.Instance.arch(),
     boot_args: String.t() | nil
   }}
  | {:error, term()}
```

Publish the running VM `vm_id`'s disk state as a new derived image and return
what a remote node needs to boot a fork of it: the new `img_id` plus the
parent's instance `type`, `arch`, and `boot_args`. The slow-fork half of
`Hyper.Vm.fork/1`; the parent keeps running throughout.

# `start_forked_vm`

```elixir
@spec start_forked_vm(Hyper.Vm.Id.t(), Hyper.Node.FireVMM.Opts.t()) ::
  {:ok, pid()} | {:error, term()}
```

Boot a fork of the running `parent` VM (described by its start `Opts`) on this
node: same image lineage, rootfs snapshotted COW-style from the parent's
mutable layer.

# `start_image_vm`

```elixir
@spec start_image_vm(Hyper.Vm.Id.t(), Hyper.Vm.Spec.t()) ::
  {:ok, pid()} | {:error, term()}
```

Boot an image-backed VM on this node: claim a uid, build the mutable rootfs
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.

# `start_link`

# `start_vm`

```elixir
@spec start_vm(Hyper.Node.FireVMM.Opts.t()) :: DynamicSupervisor.on_start_child()
```

Start a microVM on this node.

# `stop_image_vm`

```elixir
@spec stop_image_vm(pid()) :: :ok
```

Tear down an image-backed VM started by `start_image_vm/2`.

# `test_system`

```elixir
@spec test_system() :: :ok | {:error, term()}
```

# `try_run`

```elixir
@spec try_run(
  Hyper.Vm.Instance.Spec.t(),
  (-&gt; {:ok, pid()} | {:error, term()}),
  (pid() -&gt; :ok)
) ::
  {:ok, pid()} | {:error, term()}
```

Start a VM here and confirm its budget.

`start_fun` boots the VM and returns `{:ok, vm_pid}`; the reservation is held
against `vm_pid` and released when it dies. If the reserve loses a race (the
node filled up since the scheduler's snapshot) the just-started VM is torn down
via `stop_fun` and `{:error, reason}` is returned.

# `unflushed_usage`

```elixir
@spec unflushed_usage(Hyper.Vm.Id.t()) :: Unit.Time.t()
```

CPU time accrued by `vm_id`'s meter **on this node** but not yet flushed to
the usage table. The node-local half of `Hyper.usage/1`, which resolves the
owning node and `:erpc`-calls this function there.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
