Skip to content

Setting up a host

This page sets up one physical Linux server as a provider host, with the steps that home and cloud hosts share. Becoming a provider says what a host needs and which mode to choose. Every setting of hypervisor.toml is listed in Hypervisor configuration.

  1. Confirming that KVM is available (Requirements)
  2. Installing the agent (Installing the agent)
  3. Preparing storage (Preparing storage)
  4. Creating the configuration, then adding the settings of home mode or cloud mode, and keeping the host’s state on its root pool’s drive (Creating the configuration)
  5. Enrolling the host, creating its enrollment token at this step (Enrolling once)
  6. Testing the agent in the foreground (Testing in the foreground)
  7. Enabling the systemd service (Running with systemd)
  8. Setting what the host earns (Pricing and SLA)
  9. Cloud host: registering its public addresses (Registering public addresses)
  10. Cloud host: making the checks that need no rental, while it is still paused (Checking a new host)
  11. Resuming new rentals (Controlling rental admission)
  12. Making the checks with a test VM, and pausing the host again if one fails (Checking a new host)

The requirements are listed under Requirements at a glance. Before continuing, you can confirm that KVM is available:

test -c /dev/kvm && echo 'KVM is available'

You choose home mode or cloud mode before enrolling the host. The two are compared under Home host or cloud host.

curl -fsSL https://api.gigahost.cloud/install/hypervisor.sh | sudo sh

The agent is one binary, gigahost-hypervisor, built on Ubuntu 22.04 against its glibc 2.35, so it runs on every supported distribution. It is published on a signed release channel, and this one command installs it together with its dependency installer and systemd unit.

  • The script downloads the channel’s manifest and accepts it only if its Ed25519 signature matches the release public key embedded in the script (checked with OpenSSL 3, or with python3 where the OpenSSL is older).
  • It then downloads the release archive and accepts it only if its SHA-256 matches the signed manifest.
  • Only then does it run the dependency installer and put gigahost-hypervisor in /usr/local/sbin, the dependency installer beside it as gigahost-install-hypervisor-deps, and the unit in /usr/local/lib/systemd/system.

The website’s Provider page (/provider) and its Add a host page (/provider/hosts/new) show the same command, with the requirements listed apart (CPU: x86-64 with hardware virtualization; System: Ubuntu 22.04, 24.04 or 26.04, or Debian 12 or 13). The script does the signature check itself; the website no longer shows the release key or manifest. Updating the agent later is under Updating the agent.

sudo gigahost-install-hypervisor-deps --check

The installer accepts only the distributions listed in the requirements, on amd64.

  • It enables KVM/libvirt and installs the disk, image, and networking tools used by the agent, including smartmontools for disk health.
  • It creates the state directories under /var/lib/gigahost.
  • --check verifies an existing installation without changing the host.
  • The installer does not repartition disks, create storage pools, or change the management uplink.

The installer makes one change to the system beside its packages. To make it by hand, run:

sudo mount -o remount,size=1G /tmp
sudo systemctl mask tmp.mount

Debian 13 and Ubuntu 26.04 mount /tmp as a tmpfs that may grow to half of the RAM. What is written to a tmpfs is held in memory, the memory your host sells to its guests. Whatever filled /tmp would take it from them, up to the host running out of memory.

  • The installer therefore caps a tmpfs /tmp of more than 1 GiB at 1 GiB and masks tmp.mount, so that /tmp is on the root filesystem from the next boot, and says so.
  • --check reports a larger tmpfs /tmp without changing it, and Gigahost does not accept the enrollment of a host that has one.
  • A /tmp on disk, or a tmpfs of 1 GiB or less, is left as it is.

Gigahost keeps CET shadow stacks, a CPU feature, from every guest. Where the host needs a kernel parameter for that, the dependency installer asks you on the terminal, also under curl | sh, whether to add it to GRUB’s configuration, and --check only reports. The enrollment asks too. To do it yourself, run:

sudo gigahost-hypervisor shadow-stacks
sudo gigahost-hypervisor shadow-stacks --add-boot-parameter
  • A guest gets shadow stacks where the host’s Linux, 6.18 or later, virtualizes them for a CPU that has them.
  • The agent turns them off in every guest’s CPU where the host’s libvirt and QEMU know them by name.
  • Elsewhere only the kernel parameter clearcpuid=519 keeps them off, from the host’s next boot. The dependency installer says which in its cpu line.

What the commands answer, and what the parameter does, is under CET shadow stacks.

Ubuntu’s images, and some providers’ Debian images, install unattended-upgrades and run it daily. An upgrade may restart libvirt, replace QEMU’s libraries under running guests or leave a new kernel waiting for a reboot, on its own at any hour. The dependency installer warns in its upgrades line where it runs, when installing and with --check, and changes nothing about it, because the host is yours to run.

  1. To keep the package lists fresh but the upgrade off, set /etc/apt/apt.conf.d/20auto-upgrades to:

    APT::Periodic::Update-Package-Lists "1";
    APT::Periodic::Unattended-Upgrade "0";
  2. To stop the timer, and to answer the package’s debconf question so that a reconfiguration does not switch it back on, run:

    sudo systemctl disable --now apt-daily-upgrade.timer
    echo 'unattended-upgrades unattended-upgrades/enable_auto_updates boolean false' | sudo debconf-set-selections

Applying updates yourself is described under Applying system updates.

  1. You choose a layout for each pool, thin LVM or preallocated file storage, on a separate filesystem for guest storage where possible.

  2. For a thin-LVM pool, LVM zeroing needs to be enabled before enrollment. To check it, and to enable it if needed, run:

    lvs -o segtype,zero VG/POOL
    lvchange --zero y VG/POOL
  3. For a file-backed pool, you create its directory on the pool’s own filesystem, here one mounted at /srv/gigahost-root:

    sudo install -d -m 0711 /srv/gigahost-root/root-images
  4. You add each pool to hypervisor.toml. File-backed pools name their directory:

    [[file_storage_pools]]
    external_id = "root-images"
    kind = "root"
    directory = "/srv/gigahost-root/root-images"
    [[file_storage_pools]]
    external_id = "backups"
    kind = "backup"
    directory = "/srv/gigahost-backups"

    A thin-LVM pool is named by its volume group and thin pool, here as the root pool in place of the file-backed one above:

    [[lvm_storage_pools]]
    external_id = "vg0/guest-root"
    kind = "root"

It is best not to advertise more usable capacity than the host can provide. Adding a backup pool to a host that is already enrolled is under Adding a backup pool to an enrolled host.

Gigahost supports two guest root-storage layouts.

Thin LVM identifies a pool as volume_group/thin_pool.

  • The pool needs to be sized so that all advertised guest disks and operational headroom fit on physical storage.
  • Its metadata needs room for a thin volume for each VM the host can hold, four for each logical CPU it sells, and a few of the host’s own: enrollment works the need out with thin_metadata_size and says how much is missing.
  • The configured thin pool needs to be dedicated to Gigahost and have LVM zeroing enabled.

Preallocated file storage places fallocate-reserved guest images in a dedicated filesystem.

  • Its directory is created, and added to the configuration, before the agent starts.
  • Every configured file pool needs a distinct filesystem, so capacity cannot be counted twice.
  • A file-backed backup pool needs a filesystem of its own. When its directory is on the host’s root filesystem or on another pool’s filesystem, the agent takes it for a pool whose drive is not mounted (see A missing backup pool).

Each pool has a kind:

  • Exactly one root pool holds the guests’ root disks. On a cloud host it also holds the 4 GiB root volume of the router VM. On every host it holds 32 GiB of scratch space for the backups of running guests: two scratches of 16 GiB, ghs-slot-0 and ghs-slot-1, where the blocks a guest overwrites during its backup are kept.
  • A backup pool holds the encrypted artifacts of local backups, each about the size of the guest’s root disk at most. Backup pools can use thin LVM or file storage.

The root pool needs the 32 GiB of scratch space beside the root storage your capacity profile offers and the router’s volume. Enrollment checks that, and Gigahost does not accept a host whose root pool is too small, so that a running guest can be backed up whatever you have rented out.

  • The agent makes the two scratches when it starts: in a file-backed root pool two files with all of their 16 GiB allocated, which it attaches as loop devices, and in a thin-LVM root pool two thin volumes with all of their blocks provisioned.
  • They stay for the next backup, are no part of what the pool offers, and do not count toward the five percent of the root pool that has to stay free.
  • The size of a scratch and their number are the same on every host and no setting of yours, so a renter does not need to know the host to know what a backup does.

A host makes two backups or restores at a time, of running guests or stopped ones, and further ones wait in the queue until one of the two is done.

A guest that overwrites more than 16 GiB of its disk while it is backed up fills its scratch. QEMU then pauses the guest, the backup reads the rest of the disk, and the agent resumes the guest when the backup ends. The guest’s writes are not lost, and it sees no error. A stopped guest is read without a scratch.

A hosted backup needs no pool of its own, and a local backup needs a backup pool. Give the largest backup pool a little more than the largest root disk you offer, and more to keep several backups. A stored local backup keeps its room until it is deleted. Gigahost does not accept these backup requests, and answers each with the code shown:

Answer When
local_backups_unavailable A local backup on a host without a backup pool
backup_too_large_for_host A local backup whose artifact no backup pool could hold even when empty
backup_storage_busy A local backup that fits only once other backups leave
backup_pool_unavailable A restore of a local backup, or a new instance from one, whose backup pool the host is missing (see A missing backup pool)

Each offering in the catalog shows renters, as backup_limits, which backups of its root every host behind it has room for when nothing else holds its backup pools.

A backup pool on hard disks works on one local backup or restore at a time: two at once would make the disks’ heads seek between them, and both would run far slower than one after the other. The agent tells from the disks below the pool, through LVM, software RAID and partitions, whether any of them rotates, and the host’s page shows such a pool as On hard disks. Hosted backups keep nothing in a backup pool, and a pool on SSD or NVMe works on two at a time, as the host does.

  1. To create private directories for configuration and durable state, run:

    sudo install -d -m 0700 /etc/gigahost
    sudo install -d -m 0711 /var/lib/gigahost /var/lib/gigahost/instances
    sudo install -d -m 0700 /var/lib/gigahost/images
  2. You start /etc/gigahost/hypervisor.toml with the following shared settings and the storage pools above:

    backend_url = "https://api.gigahost.cloud"
    host_label = "my-host"
    location = "sgp"
    cpu_core_limit_per_numa_node = 0
    memory_bytes_per_cpu_core = "4G"
    root_storage_bytes_per_cpu_core = "64G"
    allowed_numa_nodes = [0]
    libvirt_uri = "qemu:///system"
    cloud_uplink = "auto"
    dns_servers = ["8.8.8.8", "1.1.1.1"]
  3. You add the mode-specific settings from home mode or cloud mode.

  4. To protect the completed file, run:

    sudo chmod 600 /etc/gigahost/hypervisor.toml

The host’s state on the root pool’s drive

Section titled “The host’s state on the root pool’s drive”

The agent keeps what Gigahost cannot give a host again: its identity and keys (state.json), the job journal, the readings it has not sent yet, each rental’s files (its definition, seed and console log) and the container kernels. By default they are in /var/lib/gigahost on the system drive, and a host whose system drive fails cannot come back, even with its rentals’ disks intact.

We recommend keeping them on the root pool’s drive instead, before the host is enrolled. Then the root pool’s drive is the one to protect, with RAID1 for example, and the system drive can be replaced: the host comes back with its own identity and rentals (see Recovering from a lost system drive). With the configuration and its pools in place, this command makes the state directory:

sudo gigahost-hypervisor prepare-state
  • For a file-backed root pool, the state directory is .ghostrack in the pool’s directory.
  • For a thin-LVM root pool VG/POOL, it is a thin volume of that pool, VG/ghostrack-state, with an ext4 that a mount unit of its own mounts at /var/lib/ghostrack. A drop-in in /etc/systemd/system/gigahost-hypervisor.service.d/ has the agent start only once it is mounted, and both stay through reboots and agent updates.
  • /var/lib/ghostrack is that mount point or, for a file-backed root pool, a link to the state directory, and /var/lib/gigahost becomes a link to /var/lib/ghostrack, so every path on these pages stays the same.
  • The command refuses a host that has state already, and a pool directory inside /var/lib/gigahost or /var/lib/ghostrack.

The command prints three lines for hypervisor.toml: state_directory, and the image and container caches in /var/cache/ghostrack, which stay on the system drive since they are downloaded again. The layers disk a container’s creation unpacks its image from, as large as the image, is written beside the container cache too. From then on the agent refuses to start when the state directory is not on the root pool, as when the pool’s drive is not mounted, or when a cache is inside it.

The root pool keeps room for the state beside what it offers: 1 GiB, and 9 MiB for each rental the capacity profile allows, room for a container’s two logs and its small files. A host without state_directory keeps its state on the system drive as before.

Both home and cloud hosts need a location. You choose a stable slug from ghost catalog locations. Each location also carries country and continent codes for catalog filtering.

cpu_core_limit_per_numa_node, memory_bytes_per_cpu_core, root_storage_bytes_per_cpu_core and allowed_numa_nodes set the host’s saleable capacity.

  • A CPU limit of 0 auto-detects the smallest logical-CPU count among the listed NUMA nodes; a nonzero value sets a lower manual limit independently on every listed node. How 0 is read after enrollment is under cpu_core_limit_per_numa_node.
  • A node holds as many VMs as their CPU shares allow, up to four for each logical CPU; nothing else limits their number.
  • RAM and root storage use binary K, M, or G suffixes and describe saleable capacity per logical CPU. A size may have decimals, such as 1.6G. One that is no whole number of KiB is rounded down to one, so 1.6G is 1,677,721 KiB, and enrollment shows what it resolved to.

The sizes these settings give your host, and the platform’s minimums, are under Sizes.

A VM’s own overhead is counted in its RAM. 64 MiB of each size’s RAM is the VM’s, for QEMU and its devices, and the guest gets the rest. A VM of 4 GiB boots with 4 GiB less 64 MiB.

  • Beside its guests’ RAM, every listed node keeps 0.5 % of it for the VMs’ overhead that grows with their memory, such as page tables.
  • What the node has beyond that is your host’s, for its own services, the router VM of a cloud host and its page cache.
  • With 4 GiB for a logical CPU and a limit of 16, a node sells 64 GiB to guests and needs 64.3 GiB of MemTotal for them.
  • Enrollment needs that and a margin of 512 MiB besides on every listed node.

The enrollment command verifies the selected nodes, guest memory plus VM overhead and the margin in each node’s MemTotal, and configured root pools before registering the host.

  • You can raise the CPU limit and the NUMA nodes later in place (see Raise a host’s capacity).
  • The mode, the location and every other capacity setting stay as enrolled for that host identity. To change one of them, you drain and retire the host and enroll a new identity.

Identically configured hosts with matching CPU, location, price, and SLA appear as one catalog group. Gigahost chooses a physical host when a renter creates an instance.

  1. After you complete the home-mode or cloud-mode configuration, you create an enrollment token on the machine where you use ghost:

    ghost account get-host-enrollment-token --label "my-host"

    The command prints the token and when it expires (expires_at). The website creates one too: Provider › Add a host (/provider/hosts/new) shows it once, inside the enroll command for the server.

  2. To enroll the host, run the command below and paste the token value when it asks. The token is read like a password, without echo:

    sudo gigahost-hypervisor enroll

A newly enrolled host starts paused; see Controlling rental admission.

While it runs, the command shows every step on the terminal as it happens, each with how long it took:

  • the host checks;
  • on a home host, a wireguard step that installs a key on a throwaway tunnel;
  • the smoke-test image download with its size and speed;
  • each smoke-test VM as it is created, booted, and removed again;
  • the registration;
  • the network tests.

A step that fails says what it was doing, and a failure before registration leaves the token unused. A non-empty NO_COLOR turns the colors off, and output that is not a terminal, such as a log file, gets one plain line per step instead of a live line.

If the wireguard step fails, journalctl -k | grep -i denied shows whether AppArmor or SELinux denied /usr/bin/wg. The running agent also logs an error at start when the check fails later, such as after an upgrade.

Successful first enrollment posts the configured non-refundable host-creation charge. Reusing the same enrollment identity cannot post the charge twice. The charge is paid from your available balance, not a trial credit, and must leave your protected balance; otherwise the enrollment is refused with insufficient_balance and nothing is created.

The command asks for the token first, before its checks, and reads it from standard input instead when that is a pipe. To read it from a file, pass --token-file PATH. The token is never a configuration setting or a command-line argument, because other users of the host could read those.

If enrollment is interrupted, running the same command with the same token again resumes it. The host keeps the same identity and is not enrolled twice.

Enrollment creates the host identity in the configured state_file. This file belongs on durable storage. Copying it to a second running host, or reusing it as part of a machine image, would give two machines one identity, so both are best avoided.

Before registering the host, the agent reads Linux CPU topology, NUMA memory, the usable capacity of every configured pool, and the root pool’s Linux-visible physical devices and RAID levels. Of a hardware RAID controller it reads the logical disk Linux exposes and the array’s level where that is told; see Hardware RAID.

The agent then boots the smallest and largest Ubuntu 24.04 configuration on every selected NUMA node.

  • Each pass needs to reach cloud-init readiness, and libvirt needs to report the guest’s virtio balloon statistics, which tell the agent that a new guest’s kernel has started.
  • The final pass also needs to accept a real key-only root SSH login using a newly generated temporary key and link-local bridge addresses.
  • Every mode creates an isolated temporary bridge. Temporary domains, volumes, bridges, addresses, and keys are removed before registration.

The agent tries to publish that inventory right after enrollment, and a network failure is kept for a retry when the persistent agent starts. Invalid CPU topology, undersized memory, a non-thin LVM target, disabled thin-pool zeroing, or overlapping file-pool filesystems make enrollment fail instead of advertising capacity the machine cannot provide.

Enrollment starts real smoke VMs and does not accept a host unless QEMU runs as a non-root user without effective capabilities, has seccomp filtering, and has an enforcing libvirt AppArmor or SELinux policy.

  • On SELinux systems, both global enforcement and the QEMU domain type need to be enforcing, and the setools package needs to be installed to check the loaded policy.
  • The agent also does not accept remote libvirt TCP/TLS listeners or any libvirt URI other than qemu:///system.

Libvirt runs QEMU as an unprivileged account. The agent makes each VM state directory searchable but not listable by other users. Every ancestor of instance_directory and each file-backed root-pool directory need to permit search access likewise (chmod o+x PATH), while disk and key files keep restrictive file permissions. Otherwise enrollment fails and names the exact inaccessible path.

  1. To test the agent once in the foreground, run:

    sudo gigahost-hypervisor run
  2. When the initial checks have completed, Ctrl+C stops it.

  3. Any reported KVM, libvirt, storage, network, or permission errors are best resolved before enabling the service.

Every command reads /etc/gigahost/hypervisor.toml unless --config PATH or the environment variable GIGAHOST_CONFIG_FILE names another file. What every start of the agent checks is under The capacity check at every start.

To enable and inspect the service, run:

sudo systemctl enable --now gigahost-hypervisor
sudo systemctl status gigahost-hypervisor
sudo journalctl -u gigahost-hypervisor -f

The host then appears online in your provider account.

The installer placed gigahost-hypervisor.service in /usr/local/lib/systemd/system. It runs /usr/local/sbin/gigahost-hypervisor run --config /etc/gigahost/hypervisor.toml as root with a private /tmp, no new privileges, and the kernel-facing protections systemd offers, and restarts the agent five seconds after any exit.

Local changes belong in a drop-in under /etc/systemd/system/gigahost-hypervisor.service.d/ rather than in the unit, since an update replaces the unit.

The host is enrolled and running, and still paused. What remains:

  1. Setting what it earns: Pricing and SLA.
  2. On a cloud host, registering its addresses: Registering public addresses.
  3. On a cloud host, making the checks that need no rental: Checking a new host.
  4. Resuming new rentals: Controlling rental admission.
  5. Making the checks with a small test VM: Checking a new host.

A host whose state is on its root pool’s drive (The host’s state on the root pool’s drive) comes back after its system drive is lost, with its own identity, so it needs no new enrollment. Gigahost counts the time it was away as an outage, as for any host that goes offline.

  1. You install the operating system and the agent on a new system drive, as in Installing the agent.

  2. You attach the root pool’s drive. A file-backed pool’s filesystem needs to be mounted where it was before.

  3. This command brings the host back, with --lvm only for a thin-LVM root pool:

    sudo gigahost-hypervisor recover \
    --state-directory /var/lib/ghostrack \
    --lvm VG/ghostrack-state

    For a file-backed root pool, --state-directory names the .ghostrack directory in the pool’s directory.

  4. The agent can then start again:

    sudo systemctl enable --now gigahost-hypervisor

The command mounts the state volume of a thin-LVM pool, restores /etc/gigahost/hypervisor.toml from the copy the agent keeps in the state directory, checks that the configuration’s root pool is the drive’s, and points /var/lib/ghostrack and /var/lib/gigahost at the state directory. It refuses a drive of another pool, and a configuration that differs from the kept copy unless you pass --force (the present one is kept beside it as hypervisor.toml.before-recover).

When the agent starts, it defines each rental from the definition it kept and starts the ones that were running, as after a reboot. A new mainboard or other changed hardware is reported to Gigahost as a new inventory, as any change of the machine is.

Raising a host’s capacity has moved to Operating a host.

Retiring or re-enrolling an empty host has moved to Operating a host.