Skip to content

Commit 52bd002

Browse files
chore: setup.sh one-time provisioning + quickstart guide (#67)
1 parent 7912e9c commit 52bd002

7 files changed

Lines changed: 263 additions & 36 deletions

File tree

.github/scripts/provision-kvm-host.sh

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
#!/usr/bin/env bash
22
# Provision a GitHub-hosted ubuntu runner as a single-node Firecracker host
33
# for the `:integration` E2E suite. Mirrors docs/cookbook/install.md; keep
4-
# the two in sync. Assumes: passwordless sudo, repo compiled (the firecracker
4+
# the two (and setup.sh, the dev-host equivalent) in sync. Assumes:
5+
# passwordless sudo, repo compiled (the firecracker
56
# and suidhelper install tasks are mix tasks), MIX_ENV matching the test run.
67
#
7-
# Deliberate CI-only deltas from install.md do NOT remove these in a future
8+
# Deliberate CI-only deltas from install.md -- do NOT remove these in a future
89
# sync pass:
910
# - the 0666 udev kvm rule (install.md assumes a real host with a `kvm`
1011
# group an operator is added to; the ephemeral runner has neither)
@@ -115,7 +116,7 @@ sudo chmod 0644 /etc/hyper/config.toml
115116
sudo mkdir -p /sys/fs/cgroup/hyper
116117
echo '+cpu +memory' | sudo tee /sys/fs/cgroup/hyper/cgroup.subtree_control >/dev/null
117118

118-
# The BEAM (the `runner` user Hyper refuses to run as root) owns work_dir.
119+
# The BEAM (the `runner` user -- Hyper refuses to run as root) owns work_dir.
119120
# layers/ must pre-exist: boot validation (Layer.Repo.test_system) checks it,
120121
# and the node only creates it lazily on first image load.
121122
sudo mkdir -p /srv/hyper

.github/workflows/ci.yml

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,18 @@ jobs:
152152
- name: Dialyzer
153153
run: mix dialyzer
154154

155+
shellcheck:
156+
name: ShellCheck
157+
runs-on: ubuntu-latest
158+
steps:
159+
- uses: actions/checkout@v7
160+
161+
# shellcheck ships preinstalled on GitHub-hosted ubuntu runners. Lint
162+
# every tracked shell script; `git ls-files` keeps new scripts covered
163+
# without a hand-maintained list and skips untracked _build/deps.
164+
- name: ShellCheck
165+
run: git ls-files -z '*.sh' | xargs -0 -r shellcheck
166+
155167
rust:
156168
name: Rust (suidhelper)
157169
runs-on: ubuntu-latest
@@ -271,14 +283,14 @@ jobs:
271283
integration:
272284
name: Integration (KVM E2E)
273285
# ubuntu-latest x64 exposes /dev/kvm (all Linux runners with 2+ vCPUs,
274-
# per GitHub's Apr 2024 changelog); arm64 runners do NOT keep this x64.
286+
# per GitHub's Apr 2024 changelog); arm64 runners do NOT -- keep this x64.
275287
runs-on: ubuntu-latest
276288
# Worst case: three 10-minute tests (vm_lifecycle + crash_recovery + fork),
277289
# plus the gRPC contract wrapper's own 25-minute cap (20-min lifecycle
278290
# vitest test + errors suite + npm ci/gen), plus ~5 minutes of
279291
# provisioning/compile/npm setup. Budget with headroom above that sum: a
280292
# tight budget could cancel the job right as it finishes, and
281-
# `!cancelled()` steps then skip result uploads exactly when flake
293+
# `!cancelled()` steps then skip result uploads -- exactly when flake
282294
# history matters most.
283295
timeout-minutes: 75
284296
env:
@@ -383,7 +395,7 @@ jobs:
383395
# Multiple --only flags OR together: this runs ONLY the gated tags.
384396
# coveralls.json wraps `mix test`, so the tag filters and
385397
# --warnings-as-errors pass through; it writes cover/excoveralls.json,
386-
# which is this job's Codecov upload without it the integration-only
398+
# which is this job's Codecov upload -- without it the integration-only
387399
# modules (e.g. Hyper.Metering.Usage) report zero coverage.
388400
# LLVM_PROFILE_FILE reaches the helper through the BEAM's System.cmd
389401
# (inherited env). %p keeps concurrent helper invocations' profiles

README.md

Lines changed: 24 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -26,29 +26,40 @@ same niche as [Daytona](https://github.com/daytonaio/daytona),
2626

2727
## Quick Start
2828

29-
Add Hyper to your Mix project:
29+
On an Ubuntu/Debian machine with KVM (bare metal, or a cloud instance with
30+
nested virtualization):
3031

31-
```elixir
32-
def deps do
33-
[
34-
{:hypervm, "~> 0.1"}
35-
]
36-
end
32+
```sh
33+
git clone https://github.com/harmont-dev/hyper && cd hyper
34+
./setup.sh # one-time host provisioning (asks for sudo)
35+
iex -S mix # boot a Hyper node
3736
```
3837

39-
Prepare the host (KVM, device-mapper, PostgreSQL, the Firecracker binaries and
40-
the setuid helper — the [installation
41-
guide](https://hexdocs.pm/hypervm/install.html) walks through each step), then
42-
load an OCI image and boot it:
38+
Then load an OCI image and boot it:
4339

4440
```elixir
4541
{:ok, img_id} = Hyper.Img.OciLoader.load("docker.io/library/alpine:3.19")
4642
{:ok, vm} = Hyper.create_vm(%Hyper.Vm.Spec{img_id: img_id})
4743
{:ok, %{stdout: "hello\n"}} = Hyper.exec(vm, ["/bin/echo", "hello"])
4844
```
4945

50-
Please read the [Hexdocs](https://hexdocs.pm/hypervm/) for guides on using,
51-
deploying and integrating Hyper.
46+
That's a real Firecracker microVM with a copy-on-write rootfs -- the
47+
[quickstart guide](https://hexdocs.pm/hypervm/quickstart.html) has the
48+
details. To embed Hyper in your own application instead, add it to your Mix
49+
project:
50+
51+
```elixir
52+
def deps do
53+
[
54+
{:hypervm, "~> 0.1"}
55+
]
56+
end
57+
```
58+
59+
and follow the [installation
60+
guide](https://hexdocs.pm/hypervm/install.html). The
61+
[Hexdocs](https://hexdocs.pm/hypervm/) cover using, deploying and
62+
integrating Hyper.
5263

5364
## Features
5465

docs/cookbook/install.md

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1-
# Quick Start
1+
# Installation
22

3-
This document provides the quickest start available to get Hyper running.
3+
This document walks through a full installation of Hyper on a node. For the
4+
fastest dev/eval path, see the [quickstart](quickstart.md), which automates
5+
this guide via `setup.sh`.
46

57
## Configuration
68

@@ -67,8 +69,8 @@ sudo dnf install -y \
6769

6870
### Build Toolchain
6971

70-
Hyper compiles two Rust components as part of `mix compile` the setuid
71-
helper and the in-guest agent and generates its Firecracker/gRPC bindings
72+
Hyper compiles two Rust components as part of `mix compile` -- the setuid
73+
helper and the in-guest agent -- and generates its Firecracker/gRPC bindings
7274
from the shipped specs. Every machine that **compiles** Hyper (including as a
7375
Mix dependency) therefore needs, besides Elixir `~> 1.20` on OTP 28+:
7476

@@ -84,7 +86,7 @@ sudo apt install -y protobuf-compiler # dnf install protobuf-compiler
8486
mix escript.install hex protobuf 0.17.0
8587
```
8688

87-
`~/.mix/escripts` does not need to be on your `PATH` the build finds
89+
`~/.mix/escripts` does not need to be on your `PATH` -- the build finds
8890
`protoc-gen-elixir` there itself.
8991

9092
### Device Mapper Config
@@ -299,14 +301,14 @@ as this user; every operation that genuinely needs root is routed through the
299301
setuid helper (see [SUID Helper](#suid-helper)), so the node itself never holds
300302
privilege.
301303
302-
Create the user system account, no login shell:
304+
Create the user -- system account, no login shell:
303305
304306
```sh
305307
sudo useradd --system --shell /usr/sbin/nologin --home-dir /srv/hyper hyper
306308
```
307309
308310
Start Hyper as this user (for example `sudo -u hyper ...`, or `User=hyper` in a
309-
systemd unit). The rest of this section covers the few permissions it needs
311+
systemd unit). The rest of this section covers the few permissions it needs --
310312
and the ones it deliberately does **not**.
311313
312314
#### Working directory
@@ -315,7 +317,7 @@ The node builds its entire on-disk tree (`jails`, `socks`, `scratch`, `layers`,
315317
`redist`) under `work_dir` (from `/etc/hyper/config.toml`, default `/srv/hyper`)
316318
**as this user**. It must therefore own that directory. Boot validation
317319
(`Hyper.Node.Layer.Repo.test_system/0`) refuses to start unless the `layers`
318-
subdirectory already exists the node only creates it lazily on first image
320+
subdirectory already exists -- the node only creates it lazily on first image
319321
load, so pre-create it now:
320322
321323
```sh
@@ -340,7 +342,7 @@ end
340342
```
341343
342344
Then `mix deps.get && mix compile`. Alternatively, work from a source
343-
checkout of the [repository](https://github.com/harmont-dev/hyper) every
345+
checkout of the [repository](https://github.com/harmont-dev/hyper) -- every
344346
step below is identical.
345347
346348
### Firecracker
@@ -354,7 +356,7 @@ mix firecracker.install # installs to /opt/firecracker
354356
```
355357
356358
The task installs the binaries under their bare basenames (`firecracker`,
357-
`jailer` the setuid helper rejects version-stamped names), marks them
359+
`jailer` -- the setuid helper rejects version-stamped names), marks them
358360
executable, and prints the `[tools]` snippet for `/etc/hyper/config.toml`.
359361
After installing, make both binaries root-owned and not group- or
360362
world-writable (the task prints the exact `chown` command); the helper refuses
@@ -373,7 +375,7 @@ operations. This is achieved through a side-car binary called
373375
> build produced**: `mix compile` stamps the helper with a BLAKE3 checksum and
374376
> bakes that identity into the release, and a deployed helper whose version or
375377
> checksum differs is refused. A binary from another machine or build will not
376-
> pass always install the helper from the same tree you compiled.
378+
> pass -- always install the helper from the same tree you compiled.
377379
378380
Build and install it with:
379381
@@ -394,7 +396,7 @@ sudo install -o root -g root -m 4755 \
394396
395397
With PostgreSQL reachable (see above) and your database credentials configured
396398
(see the [configuration guide](config.md)), create and migrate the image
397-
database once per cluster, from any node:
399+
database -- once per cluster, from any node:
398400
399401
```sh
400402
mix ecto.create -r Hyper.Img.Db.Repo
@@ -403,7 +405,7 @@ mix ecto.migrate -r Hyper.Img.Db.Repo
403405
404406
## Booting
405407
406-
Start your application as the `hyper` user Hyper's supervision tree boots
408+
Start your application as the `hyper` user -- Hyper's supervision tree boots
407409
with it and the node becomes a VM runner. From a source checkout, an
408410
interactive session is the quickest smoke test:
409411
@@ -414,7 +416,7 @@ sudo -u hyper iex -S mix
414416
At boot Hyper validates the node: config file ownership, the setuid helper's
415417
build identity, and the device-mapper targets. A misconfigured node refuses to
416418
start with a specific error rather than limping along. Once up, load an image
417-
and boot a VM see the [intro](intro.md#usage) for the walkthrough.
419+
and boot a VM -- see the [intro](intro.md#usage) for the walkthrough.
418420

419421
For production, run it under a supervisor of your choice (e.g. a systemd unit
420422
with `User=hyper`). Nodes that join the BEAM cluster become additional VM

docs/cookbook/quickstart.md

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
# Quick Start
2+
3+
Boot a real Firecracker microVM on your own Linux machine in about five
4+
minutes.
5+
6+
This is the fastest path to a running Hyper node. It is a dev/eval setup -- for
7+
a production install (dedicated system user, managed PostgreSQL, systemd)
8+
follow the [installation guide](install.md).
9+
10+
## What you need
11+
12+
- An Ubuntu/Debian host (x86_64 or aarch64) with KVM -- `stat /dev/kvm` must
13+
succeed. (Other distros work too, via the manual [installation
14+
guide](install.md).)
15+
- cgroups v2 (the default on any modern distro).
16+
- `sudo` -- host provisioning (device-mapper, the setuid helper) needs root.
17+
Hyper itself never runs as root.
18+
- [Docker](https://docs.docker.com/engine/install/) -- for the side-car
19+
Postgres.
20+
- Elixir `~> 1.20` OTP 28+ ([install](https://elixir-lang.org/install.html)).
21+
- Rust via [rustup](https://rustup.rs) -- the build compiles the setuid helper
22+
and the in-guest agent.
23+
24+
`setup.sh` checks every requirement up front and stops with an actionable
25+
error before touching the host.
26+
27+
## Run it
28+
29+
```sh
30+
git clone https://github.com/harmont-dev/hyper && cd hyper
31+
./setup.sh # one-time host provisioning (asks for sudo)
32+
# NOTE: please read this script and ensure you're comfortable
33+
# with it
34+
35+
iex -S mix # boot a Hyper node
36+
```
37+
38+
## Boot a VM
39+
40+
In the `iex` shell:
41+
42+
```elixir
43+
{:ok, img_id} = Hyper.Img.OciLoader.load("docker.io/library/alpine:3.19")
44+
{:ok, vm} = Hyper.create_vm(%Hyper.Vm.Spec{img_id: img_id, type: :micro})
45+
{:ok, %{stdout: "hello\n"}} = Hyper.exec(vm, ["/bin/echo", "hello"])
46+
```
47+
48+
That is a real Firecracker microVM with a copy-on-write rootfs. The
49+
[intro](intro.md#usage) walks through loading images, booting VMs, and
50+
running commands; for other languages there is a
51+
[gRPC interface](../grpc.md).
52+
53+
## Where next
54+
55+
- [Intro](intro.md) -- concepts and the usage walkthrough.
56+
- [Installation guide](install.md) -- the manual, production-grade setup.
57+
- [Configuration guide](config.md) -- every `/etc/hyper/config.toml` knob.

mix.exs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ defmodule Hyper.MixProject do
118118
# Narrative/guide pages rendered alongside the API reference.
119119
extras: [
120120
"README.md",
121+
"docs/cookbook/quickstart.md",
121122
"docs/cookbook/intro.md",
122123
"docs/cookbook/install.md",
123124
"docs/cookbook/config.md",
@@ -195,7 +196,7 @@ defmodule Hyper.MixProject do
195196
# the bindings in a consumer's build (they are gitignored, not in `lib`).
196197
# priv/repo ships the migrations so a consumer can `mix ecto.migrate`.
197198
# native/ ships the Rust crate *sources* because the `:suidhelper_stamp` and
198-
# `:guest_agent_build` compilers build them wherever hyper compiles the
199+
# `:guest_agent_build` compilers build them wherever hyper compiles -- the
199200
# suidhelper must be built locally anyway (`Hyper.SuidHelper.verify_version/0`
200201
# checks the deployed helper's BLAKE3 against this build's stamp, so a
201202
# prebuilt binary can never match). Crate subpaths are listed explicitly to
@@ -216,7 +217,7 @@ defmodule Hyper.MixProject do
216217
),
217218
# `lib` is included wholesale, so on a dev box the gitignored generated
218219
# outputs (bindings, expected.ex) exist on disk and would leak into the
219-
# tarball with a build identity from the packaging machine. Keep the
220+
# tarball -- with a build identity from the packaging machine. Keep the
220221
# package deterministic: consumers regenerate all of these at compile.
221222
exclude_patterns: [
222223
~r{^lib/hyper/firecracker/api/(operations|schemas)/},
@@ -239,7 +240,7 @@ defmodule Hyper.MixProject do
239240
# zsh (it only de-selects the `$ ` prompt; everything else is plain text)
240241
# from `ExDoc.Application.start`, which runs during the `docs` task.
241242
#
242-
# So we start :ex_doc and :makeup_syntect here first (idempotent the later
243+
# So we start :ex_doc and :makeup_syntect here first (idempotent -- the later
243244
# `docs` task won't re-run their `start/2`), then register our shell aliases
244245
# LAST so they win. Dev-only; runs as the `docs` alias's first step.
245246
defp register_doc_lexers(_args) do

0 commit comments

Comments
 (0)