Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
121 changes: 77 additions & 44 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,27 +37,20 @@ running.
$ vmctl -c CONFIG <command>
```

4. Prepare a boot image. The base configruation `*-base.conf` will look for a
base image in `img/base.qcow2`. You can use [archbase][archbase] to build a
lean Arch Linux base image or grab a QCOW2-based [Ubuntu cloud
image][ubuntu-cloud-image] if that's your vice.
4. Pre-run prep:
`vmctl` supports two methods:
* Imageless: Requires nix infrastructure and is based on creating the
system through the `nix build` command. Goto [Prep nix](#Prep-nix) before
running.
* Image-Backed: This is the "usual" way of running QEMU where the OS is in
images (qcow) on your filesystem. Goto [Prep boot img](#Prep-boot-img)
before running.

In the case of a standard "cloud image", you probably want to resize it
since it is usually shrunk to be as small as possible by default.

$ qemu-img resize img/base.qcow2 8G

**Note** The example `nvme.conf` will define `GUEST_BOOT="img/nvme.qcow2"`.
You do not need to provide that image - if it is not there `$GUEST_BOOT`
will be a differential image backed by `img/base.qcow2`. So, if you ever
need to reset to the "base" state, just remove the `img/nvme.qcow2` image.

5. Start from an example and edit it as you see fit.

$ edit $HOME/vms/nvme.conf

[archbase]: https://github.com/OpenMPDK/archbase
[ubuntu-cloud-image]: https://cloud-images.ubuntu.com

## Virtual Machine Configurations

Expand All @@ -74,35 +67,6 @@ the `CONFIG` config file in interactive mode such that the VM serial output is
sent to standard out. The QEMU monitor is multiplexed to standard out, so you
can access it by issuing `Ctrl-a c`.

### cloud-init

If your chosen base image is meant to be configured through [cloud-init][cloud-init],
you can use the included cloud-config helper script to generate a basic
cloud-init seed image:

$ ./contrib/generate-cloud-config-seed.sh ~/.ssh/id_rsa.pub

If the image is running freebsd, use the script with `-freebsd` suffix:

$ ./contrib/generate-cloud-config-seed-freebsd.sh ~/.ssh/id_rsa.pub

This will generate a simple cloud-init seed image that will set up the image
with a default `vmuser` account that can be logged into using the given public
key. Place the output image (`seed.img`) in `img/` and pass the `--cloud-init`
(short: `'-c'`) option to `vmctl run` to initialize the image on first boot:

$ vmctl -c CONFIG run -c

cloud-init will automatically power off the virtual machine when it has been
configured.

NOTE: For the cloud-config helper script to work `cloud-utils` is required.

[cloud-init]: https://cloudinit.readthedocs.io/en/latest/


### SSH, Serial console and QEMU monitor

By default, `vmctl` will launch the guest such that the serial console and the
QEMU monitor is multiplexed to standard out. This means that you will see the
serial console output directly on the screen.
Expand All @@ -117,6 +81,7 @@ the console and monitor using
$ vmctl -c CONFIG console
$ vmctl -c CONFIG monitor


### Tracing

The `--trace` (short: `-t`) option can be used to enable tracing inside QEMU.
Expand All @@ -130,6 +95,7 @@ to IRQs, use
`vmctl` inserts an implicit `*`-suffix such that all traces with the given
prefix is traced.


### Custom kernel

Finally, the `--kernel-dir` (short: `-k`) can be used to point to a custom
Expand All @@ -142,6 +108,73 @@ that configures the image to support this. In non-cloud-init settings, see
`contrib/systemd` for a systemd service that should be usable on most
distributions.

## Prep nix

Imageless mode depends on nix commands and flake infrastrcture. If you are on
nixos, add the following text to your configuration file (usually located at
/etc/nixos/configuration.nix):

```
nix = {
package = pkgs.nix;
extraOptions = ''
experimental-features = nix-command flakes
'';
};
```

On the other hand, if you have installed nix on another distribution (like Debian),
add the following text to your configruation file (usually /etc/nix/nix.conf):

experimental-features = nix-command flakes


## Prep boot img

The base configruation `*-base.conf` will look for a base image in
`img/base.qcow2`. You can use [archbase][archbase] to build a lean Arch Linux
base image or grab a QCOW2-based [Ubuntu cloud image][ubuntu-cloud-image] if
that's your vice.

In the case of a standard "cloud image", you probably want to resize it since
it is usually shrunk to be as small as possible by default.

$ qemu-img resize img/base.qcow2 8G

**Note** The example `nvme.conf` will define `GUEST_BOOT="img/nvme.qcow2"`.
You do not need to provide that image - if it is not there `$GUEST_BOOT`
will be a differential image backed by `img/base.qcow2`. So, if you ever
need to reset to the "base" state, just remove the `img/nvme.qcow2` image.

[archbase]: https://github.com/OpenMPDK/archbase
[ubuntu-cloud-image]: https://cloud-images.ubuntu.com

### cloud-init

If your chosen base image is meant to be configured through [cloud-init][cloud-init],
you can use the included cloud-config helper script to generate a basic
cloud-init seed image:

$ ./contrib/generate-cloud-config-seed.sh ~/.ssh/id_rsa.pub

If the image is running freebsd, use the script with `-freebsd` suffix:

$ ./contrib/generate-cloud-config-seed-freebsd.sh ~/.ssh/id_rsa.pub

This will generate a simple cloud-init seed image that will set up the image
with a default `vmuser` account that can be logged into using the given public
key. Place the output image (`seed.img`) in `img/` and pass the `--cloud-init`
(short: `'-c'`) option to `vmctl run` to initialize the image on first boot:

$ vmctl -c CONFIG run -c

cloud-init will automatically power off the virtual machine when it has been
configured.

**Note**: For the cloud-config helper script to work `cloud-utils` is required.

[cloud-init]: https://cloudinit.readthedocs.io/en/latest/


## License

Expand Down
18 changes: 1 addition & 17 deletions cmd/run
Original file line number Diff line number Diff line change
Expand Up @@ -102,16 +102,6 @@ _run() {

_load_vm

if [[ ! -f "${VMROOT}/$GUEST_BOOT" && ! -v do_print ]]; then
if [[ ! -f "${VMROOT}/$GUEST_BOOT_BASE" ]]; then
_fatal 1 "base image '${VMROOT}/${GUEST_BOOT_BASE}' does not exist"
fi

_log "creating boot image ('$GUEST_BOOT') from base ('$GUEST_BOOT_BASE')"
_log_named "qemu-img create" "$QEMU_IMG" create -f qcow2 -b "$(basename "$GUEST_BOOT_BASE")" \
-F qcow2 "${VMIMG}/$(basename "$GUEST_BOOT")" "$GUEST_BOOT_SIZE"
fi

if [[ -v do_gdb && -v do_background ]]; then
_fatal 1 "--gdb cannot be used with --background"
fi
Expand Down Expand Up @@ -155,13 +145,7 @@ _run() {
QEMU_PARAMS+=("-pidfile" "${VMROOT}/run/${VMNAME}/pidfile")

if [[ -v kernel_dir ]]; then
local cmdline_extra=""
if [[ -v cloud_init_seed ]]; then
cmdline_extra="ci.datasource=NoCloud"
fi

qemu_kern_add_custom --kernel-dir "${kernel_dir}" \
--cmdline-extra "${cmdline_extra}"
qemu_kern_add_custom --kernel-dir "${kernel_dir}"
fi

if [[ -v do_background ]]; then
Expand Down
8 changes: 8 additions & 0 deletions common/rc
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,14 @@ _require_program() {
fi
}

_require_path() {
local rpath="$1"; shift

if [[ ! -d "${rpath}" ]]; then
_fatal 1 "Path not found (${rpath}). $*"
fi
}

_is_running() {
if [[ -f "${VMRUN}/pidfile" ]]; then
return 0
Expand Down
21 changes: 21 additions & 0 deletions contrib/nix/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
CC := $(shell command -v musl-clang 2>/dev/null || echo gcc)
CFLAGS := -Os -static -ffunction-sections -fdata-sections
LDFLAGS := -Wl,--gc-sections
BUILDDIR ?= .
SRC := vmctl_init.c
OUT := $(BUILDDIR)/init
IMG := $(BUILDDIR)/init.img

.PHONY: clean

$(OUT): $(SRC)
@echo "Compiler: $(CC)"
$(CC) $(CFLAGS) $(LDFLAGS) -o $@ $<

$(IMG): $(OUT)
find . -type f -name init -printf '%P\n' | cpio -ov -H newc > $(IMG)

all: $(IMG)

clean:
rm -f $(OUT) $(IMG)
107 changes: 107 additions & 0 deletions contrib/nix/vmctl_init.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
#include <unistd.h>
#include <sys/mount.h>
#include <sys/stat.h>
#include <errno.h>
#include <string.h>

#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))

#define VMCTL_INIT_VERBOSITY 0
#if VMCTL_INIT_VERBOSITY
#include <stdio.h>
#include <stdarg.h>

char const * const indent_str = " ";
static void info(unsigned indent, char const *fmt, ...) {
while(0 != indent--)
fprintf(stdout, "%s", indent_str);

va_list args;
va_start(args, fmt);
vfprintf(stdout, fmt, args);
va_end(args);

fprintf(stdout, "\n");
}
static void prnt_args(int argc, char *argv[]) {
info(1, "Arguments :\n");
info(2, "argc = %d\n", argc);
info(2, "argv:\n");
for (int i = 0; i < argc; i++)
info(3, "argv[%d] = %s\n", i, argv[i]);
}

#else
static inline void info(unsigned indent, char const *fmt, ...) {}
static inline void prnt_args(int argc, char *argv[]) {}
#endif

struct ArgOpt{
const char *arg_str;
char **arg_val;
};

void parse_args(int const argc, char *argv[], struct ArgOpt *opts, int const opts_count) {
for (int i = 1; i < argc; i++) {
for (int j = 0; j < opts_count; j++) {
if (strcmp(argv[i], opts[j].arg_str) == 0 && i + 1 < argc) {
*opts[j].arg_val = argv[i + 1];
i++;
break;
}
}
}
}

int ensure_dir(const char *path) {
if (mkdir(path, 0755) == -1 && errno != EEXIST) {
info(1, "Error: creating the path (%s)", path);
return -1;
}
return 0;
}

int mount_nix_store() {
int ret;
// This must match the tag used in virtiofsd
const char *source = "nixstore";
const char *target = "/nix/store";

if (ensure_dir("/nix") || ensure_dir("/nix/store"))
return 1;

if (mount(source, target, "virtiofs", 0, "") == -1) {
info(1, "Error: mounting source (%s) at target (%s)", source, target);
return 1;
}

info(1, "Mounted %s at %s\n", source, target);

return 0;
}

int main(int argc, char *argv[]) {
int ret;
char *init_path = NULL;
struct ArgOpt opts[] = {
{"--init-path", &init_path},
};

info(1, "VMCTL image-less INIT started ... \n");
prnt_args(argc, argv);

parse_args(argc, argv, opts, ARRAY_SIZE(opts));
if (init_path == NULL) {
info(1, "Error: missing --init-path arg\n");
return -1;
}

if(mount_nix_store())
return 1;

info(1, "Attempting to exec: %s\n", init_path);
execv(init_path, argv);
info(1, "Error: execv failed");

return -1;
}
Loading