Hypervisor setup
The GigaHost hypervisor agent turns one physical Linux server into a provider host. This page covers the steps shared by home and cloud deployments.
Requirements
Section titled “Requirements”- A dedicated x86-64 server with hardware virtualization enabled in firmware.
- Ubuntu 22.04, 24.04, or 26.04, or Debian 12 or 13. Proxmox VE is not supported, because it runs its own QEMU stack beside which libvirt cannot be installed.
- A working
systemdinstallation and root access. - Persistent SSD or NVMe storage for guest disks and hypervisor state.
- Stable outbound Internet access and accurate system time.
- A GigaHost account, any account, and a new host enrollment token from it.
Confirm that KVM is available before continuing:
test -c /dev/kvm && echo 'KVM is available'Choose a network mode
Section titled “Choose a network mode”Choose the mode before enrolling the host:
- Home mode needs only outbound connectivity and is suitable for hosts behind NAT.
- Cloud mode gives each guest one of the server’s additional public IPv4 addresses, routed through a router VM that the agent runs on the host.
A host has one mode. Changing modes requires enrolling a new host identity.
Install the agent
Section titled “Install the agent”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 one command installs it together with its
dependency installer and systemd unit:
curl -fsSL https://api.gigahost.cloud/install/hypervisor.sh | sudo shThe script downloads the channel’s manifest and refuses it unless its
Ed25519 signature matches the release public key embedded in the script
(checked with OpenSSL 3, or with python3 where the OpenSSL is older),
then downloads the release archive and refuses it unless 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, the manifest URL, and the public key, so you can compare the
key the script carries with the one your account shows.
Run the same command to update. It installs a newer release, restarts a
running agent (the agent resumes its work from its journal), and reports
when the installed release is already current. Schedule updates like
reboots: pause new rentals first if you want no work queued during the
restart. The agent does not update itself. gigahost-hypervisor --version
prints the installed release, which the agent also reports to Gigahost; a
binary not installed by the script reports dev.
Update within the support window
Section titled “Update within the support window”Gigahost serves every release of the agent for at least 90 days after it
was built (a release’s name starts with its build time, such as
20261010T120000Z-…). Your host’s page and the Agent column of your
host list show the release each host runs and until when it is served;
from 14 days before the end they say “Support ends …: update it”. Update
before then with the install command above.
Once Gigahost stops serving a release, it still accepts the agent’s
heartbeats and metrics and what the work the host was already doing needs
to finish (its progress, its results, a backup’s transfers), refuses its
other requests, and names the release to install: the agent logs that once
as an error, keeps its VMs running and its heartbeats and metrics going, and
finishes the work under way; sudo gigahost-hypervisor check shows it, and
you get a critical incident naming the host and the command, which resolves
once the host runs a release that is served. Until then the host stays up
and degraded: no outage opens, but it takes no new rentals and starts no
new work, so your renters’ requests for its VMs (starting, stopping,
backing up, deleting) wait until the agent is updated. An updated agent is
served again as soon as its first heartbeat arrives. An agent of a release
that is no longer served cannot enroll a new host either; install the
current release first.
Check the dependencies later with:
sudo gigahost-install-hypervisor-deps --checkThe installer accepts only the distributions listed above on amd64. It
enables KVM/libvirt and installs the disk, image, and networking tools used by
the agent, including smartmontools for disk health, and 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. Debian 13
and Ubuntu 26.04 mount /tmp as a tmpfs that may grow to half of the RAM,
and 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 enrollment refuses a host that has one. To do
it by hand:
sudo mount -o remount,size=1G /tmpsudo systemctl mask tmp.mountA /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. A guest
gets them 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, and where the parameter
is needed it asks you on the terminal, also under curl | sh, whether to
add it to GRUB’s configuration; --check only reports. The enrollment asks
too. To do it yourself:
sudo gigahost-hypervisor shadow-stackssudo gigahost-hypervisor shadow-stacks --add-boot-parameterThe first command says what guests get and exits with 0 when they get none
or the agent turns them off, 2 when the parameter is needed and GRUB’s
configuration lacks it, and 3 when the configuration has it but the host
has not started since. --add-boot-parameter writes
/etc/default/grub.d/90-gigahost-no-shadow-stacks.cfg and runs
update-grub; the parameter takes effect when the host starts again, which
you schedule like any reboot. Linux then marks its kernel tainted, a warning
that changes nothing else.
Until the host runs with the parameter, it has the critical condition
guest_shadow_stacks and takes no new rentals.
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: the host is yours to run. To turn it off, keep the
package lists fresh but the upgrade off in /etc/apt/apt.conf.d/20auto-upgrades:
APT::Periodic::Update-Package-Lists "1";APT::Periodic::Unattended-Upgrade "0";stop the timer, and answer the package’s debconf question so that a reconfiguration does not switch it back on:
sudo systemctl disable --now apt-daily-upgrade.timerecho 'unattended-upgrades unattended-upgrades/enable_auto_updates boolean false' | sudo debconf-set-selectionsThen upgrade by hand, apt-get update && apt-get upgrade, while you watch
the host. An upgrade that leaves a new kernel waiting for a reboot is best
followed by one: pause new rentals first, as for any reboot, so that no work
is queued during the restart.
Beside the guests’ metrics, the agent reports the host’s own every minute:
CPU busy and steal time overall and per NUMA node, load, memory and
hugepages, each storage pool’s usage, disk I/O and SMART health per backing
device, traffic on the uplink, temperatures where sensors exist, and the
memory errors the kernel has counted. The readings appear under Status on
the host’s Overview tab on the website (/provider/hosts/HOST_ID),
traffic in the bit/s unit it fills (kbit/s, Mbit/s, Gbit/s) and disk rates
in binary units such as MiB/s, with the history on its
Usage tab, and through ghost provider host get, metrics, and
instance-usage.
The Overview opens with the host’s Capacity, what it offers for rent
under the capacity revision in force: its vCPUs (cpu_core_limit_per_numa_node
times its NUMA nodes), the RAM and the disk that come with them
(memory_bytes_per_cpu_core and root_storage_bytes_per_cpu_core times those
vCPUs), what its rentals hold of each, running or stopped, its VMs, the RAM
and disk per vCPU, and its prices on one line. Then come its Health (the
agent, the conditions below and the host’s open incidents), Status (the
readings, then a table each for the NUMA nodes, storage pools, uplinks,
temperatures and disks), and Capacity details: what each
NUMA node offers and its rentals hold, every size of the host with its RAM
and disk and whether it is offered, and what is reserved of each storage
pool. A cloud host’s IP addresses follow its Hardware and Network
tests: whether an address has answered through it, and the addresses of
the IP pools that serve it (the first 50 of each pool, the rest on the pool’s
page) with their gateway and state.
The agent judges these readings itself and reports what you should look at as a condition of the host, which you get as an incident for as long as it lasts. A warning leaves the host as it is. A critical condition degrades the host: it takes no new rentals until the condition is over, and its instances keep running.
| Condition | Warning | Critical |
|---|---|---|
| CPU temperature | 5 °C below the CPU’s own limit; 98 °C on an AMD CPU, which names none | the CPU’s own limit; 105 °C on an AMD CPU |
| Disk temperature (NVMe) | the drive’s warning temperature | the drive’s critical temperature |
| Memory module temperature | 75 °C | the module’s critical temperature, 85 °C |
| Disk health | its SMART self-assessment failed | |
| Disk wear | 80% of its rated endurance used | 95% |
| Disk media errors | the count rose within the last 24 hours | |
| Memory errors | corrected errors were counted within the last 24 hours | an error could not be corrected since the host started |
| Hardware RAID array | it is being rebuilt, or its controller names a state the agent does not know | it is degraded, failed or offline |
| Hardware RAID controller | the state of its arrays cannot be read | |
| Root pool | less than 5% free | |
| Backup pool | missing: its directory is gone, or on the root filesystem or another pool’s filesystem, or its volume group or thin pool cannot be read | |
| Agent filesystem | less than 10% free | less than 5% free |
| Memory | less than 2% available | |
| Accepted capacity | a NUMA node has fewer CPUs or less memory, or the root pool less room, than the capacity Gigahost accepted for the host; checked at every start of the agent too | |
| Guest shadow stacks | guests would get CET shadow stacks, and the host does not run with clearcpuid=519 |
|
| Shared IP port in use (cloud hosts) | a service listens publicly on a port from 1000 to 60999 of the host’s address, which are GigaHost’s for VMs that share it (shared IP) |
A temperature counts once three samples in a row, three minutes, were at or
over its limit. Only the sensors of the CPU (k10temp, coretemp), of NVMe
drives, and of memory modules (spd5118, jc42) are judged. The sensor
chips of mainboards are shown and not judged: an input that nothing is
connected to reads over 100 °C for good. A Ryzen 7000 or 9000 holds 95 °C
under full load by design, which is no condition.
Memory errors are counted only on a host whose memory corrects errors (ECC)
and whose kernel has the EDAC driver of its memory controller loaded. On
every other host the host’s Overview says that they are not available,
which is not the same as none. gigahost-install-hypervisor-deps --check tells
which of these sensors the host reads and which module to load for one that
it does not, without failing for it.
Prepare storage
Section titled “Prepare storage”GigaHost supports two guest root-storage layouts:
- Thin LVM identifies a pool as
volume_group/thin_pool. Size the pool so all advertised guest disks and operational headroom fit on physical storage, and its metadata 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 withthin_metadata_sizeand says how much is missing. The configured thin pool must be dedicated to GigaHost and have LVM zeroing enabled. Check withlvs -o segtype,zero VG/POOL; if needed, enable it withlvchange --zero y VG/POOLbefore enrollment. - Preallocated file storage places fallocate-reserved guest images in a dedicated filesystem. Create its directory before starting the agent and add it to the configuration. Every configured file pool must use a distinct filesystem so capacity cannot be counted twice.
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-0andghs-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.
There is no staging pool any more: a configuration that names one is refused with the advice to remove it, or to make it a backup pool.
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 refuses 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 must stay free. The size of a scratch and their number are the same on every host and no setting of yours: a renter need not 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. The backend refuses a local backup on a host without a backup pool with
local_backups_unavailable, one whose artifact no backup pool could
hold even when empty with backup_too_large_for_host, and one that fits only
once other backups leave with backup_storage_busy; a stored local backup
keeps its room until it is deleted. A restore of a local backup, or a new
instance from one, whose backup pool the host is missing is refused with
backup_pool_unavailable (see a missing backup
pool). Give the largest backup pool a little
more than the largest root disk you offer, and more to keep several backups.
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.
For a file-backed pool, create its directory:
sudo install -d -m 0711 /var/lib/gigahost/root-imagesAdd each pool to hypervisor.toml. File-backed pools name their directory:
[[file_storage_pools]]external_id = "root-images"kind = "root"directory = "/var/lib/gigahost/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"Use a separate filesystem for guest storage where possible. Do not advertise more usable capacity than the host can provide.
A file-backed backup pool needs a filesystem of its own: its directory must not be on the host’s root filesystem or on another pool’s filesystem, or the agent takes it for a pool whose drive is not mounted (see below).
Add a backup pool to an enrolled host
Section titled “Add a backup pool to an enrolled host”A backup pool can be added to a host that is enrolled and rents out instances. Its mode and location stay as they were enrolled, and its capacity as Gigahost accepted it (see Raise a host’s capacity); only the storage pools change.
-
Prepare the storage as for enrollment: a thin pool with zeroing enabled, or a dedicated filesystem, mounted, with the pool’s directory on it.
-
Add the pool to
hypervisor.tomlas a[[lvm_storage_pools]]or[[file_storage_pools]]entry withkind = "backup", as above. -
Check what the host would offer:
Terminal window sudo gigahost-hypervisor checkWithout contacting the backend, it reads the configuration as the agent does when it starts, then lists every pool the host would offer with its kind, its backend, its capacity, and what of it the host keeps for itself. A backup pool it cannot find is shown as a warning with what it found. It ends with status 1, and says why, where the agent would refuse to start, the machine lacks the capacity Gigahost accepted (the agent would start, and the host be degraded), or it could not read the pools.
-
Restart the agent with
sudo systemctl restart gigahost-hypervisor. The inventory it sends when it starts lists the new pool: from then on the backend places local backups in it, and the catalog’sbackup_limitscount it.
A pool that holds local backups stays in the configuration until they are
deleted: the agent refuses to start while a local backup’s artifact is in a
pool the configuration no longer names, with backup pool holding N local backups is not configured any more (first: PATH); put the pool back in the configuration, or delete the backups first. An empty backup pool may be
removed.
A missing backup pool
Section titled “A missing backup pool”A backup pool holds nothing a running instance needs, so the agent starts without one it cannot find, as long as the root pool is there: a file pool whose directory does not exist, or is on the host’s root filesystem or on another pool’s filesystem (a drive that is not mounted leaves its mount point there, an empty directory that must not receive backups), and a thin-LVM pool whose volume group or thin pool cannot be read. The agent leaves the pool out of its inventory and reports the condition backup pool missing with the pool’s ID and what it found, which you get as an incident. It is a warning: the host stays up and its instances run on.
While the pool is missing, the backend places no local backup in it, refuses
a restore of a backup in it, or a new instance from one, with
backup_pool_unavailable, and charges the backups’ owners nothing for
keeping them, which earns you nothing for them either. Their owners see
those backups as unavailable, and why.
Mount the drive or activate the volume group. The agent finds the pool again within a minute and sends a new inventory; restart it if it does not. Before restores and storage charges resume, the backend has the agent check every backup’s artifact in the pool: a backup whose artifact is there with its recorded size is available again, and one whose artifact is gone is marked lost. Its owner is told, it is charged for nothing after the pool went missing, and it stays listed until its owner deletes it.
A backup deleted while its pool was missing leaves its artifact behind in the pool, since the agent could not reach it then. When the pool is back, and also whenever the agent starts and once a day, the agent asks the backend which of the backups it finds on the host the backend no longer has, and removes those leftovers; it never removes a backup the backend still has.
A missing root pool still stops the agent: the instances’ disks are in it.
Hardware RAID
Section titled “Hardware RAID”Where the controller allows it, hand its drives to Linux (HBA or JBOD mode) and build the redundancy with Linux software RAID. The platform then sees every drive: its model in the catalog, its SMART health, wear, media errors and temperature, and the array’s level.
A hardware RAID controller shows Linux one logical disk for each of its
arrays and hides the drives. The agent knows such a disk by its driver
(megaraid_sas, mpt3sas, hpsa, smartpqi, aacraid, arcmsr,
3w-sas) and works with it as follows:
- The catalog shows the array under the controller’s name as a hardware
RAID array, with its level where that can be read: from the driver
(
hpsa,mpt3sas), or from the controller’s tool when every array it lists has the same level. Otherwise the level is shown as unknown. - The drives behind the controller are read through
smartctl(-d megaraid,N,-d cciss,Nand the like, assmartctl --scan-opennames them) and judged like any disk. They are listed under the host’s details as drives behind a hardware RAID controller. The array itself is shown with its health unknown, never as healthy. - The state of the arrays comes from the kernel (
mpt3sas) or from the controller’s own tool, which you install:storcliorpercclifor MegaRAID and PERC,ssaclifor Smart Array,arcconffor Adaptec. A degraded or failed array degrades the host; one that is being rebuilt is a warning. - Without the tool the agent cannot tell a drive that left its array, and you get the warning that the controller cannot be watched for as long as that lasts.
Enrollment and gigahost-install-hypervisor-deps --check tell what the
agent reads of a controller. The state of the controller’s battery and of
its write cache is not read.
Create the configuration
Section titled “Create the configuration”Create private directories for configuration and durable state:
sudo install -d -m 0700 /etc/gigahostsudo install -d -m 0711 /var/lib/gigahost /var/lib/gigahost/instancessudo install -d -m 0700 /var/lib/gigahost/imagesEnrollment needs a short-lived, single-use token from your provider account.
You create it with the ghost client when you are ready to enroll
(see below).
Start /etc/gigahost/hypervisor.toml with the following shared settings, then
add the mode-specific settings from the home or cloud guide:
backend_url = "https://api.gigahost.cloud"host_label = "my-host"location = "sgp"
cpu_core_limit_per_numa_node = 0memory_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"]The agent keeps its state, journal, and telemetry spool in
/var/lib/gigahost (state.json, journal.sqlite3, telemetry.sqlite3), and
VM state and the image cache in /var/lib/gigahost/instances and
/var/lib/gigahost/images, the directories the dependency installer creates.
Set state_file, journal_file, telemetry_spool_file,
instance_directory, or image_cache_directory only to move one elsewhere; a
relative path is resolved against the configuration file’s directory.
The agent caches the operating-system images it downloads, by default on the
filesystem that also holds its state and, on most hosts, the operating
system. image_cache_max_bytes (default "20G", at least "1G") bounds the
cache: before a download and once an hour the agent evicts the images used
the longest ago until the cache fits, and an image unused for 30 days goes
in any case, except on a host that downloads the catalog ahead of demand
(below). An image that a job is using is never evicted, and a VM’s disk
is a copy, so an evicted image only costs a download when it is next used. A
download needs the image and 5 GiB beside it free on the cache’s filesystem,
and cached images that no job uses make that room too, the least recently
used first; only when even they would not do does the creation fail, with a
message that says so, before anything is written.
A home host downloads the catalog’s images ahead of demand, so that a new or
reinstalled VM does not wait for its image over your uplink; a cloud host
does not unless you set it. Two settings of hypervisor.toml control it:
image_predownload = trueimage_predownload_rate_bps = 10000000image_predownload is true by default on a home host and false on a
cloud host. The agent asks Gigahost every 15 minutes which images to keep:
the enabled catalog images your largest VM size can take, those most VMs
were created from in the last 30 days first. It downloads them one at a
time, each only while it fits within image_cache_max_bytes beside the
images already cached and leaves the larger of 5 GiB and a fifth of the
filesystem free, and it evicts nothing for them; an image that does not fit
is left out, and a smaller one may still fit. The
whole catalog takes about 10.4 GB, so the default limit holds it. The
downloads take at most half the download bandwidth your host’s network
tests measured, or image_predownload_rate_bps (10 Mbit/s) while there is
no measurement, and they pause while a VM’s own image or a restore’s backup
downloads or a network test runs, then continue where they stopped. Each
image is verified by its checksum before it enters the cache, and one that
is withdrawn from the catalog or replaced by a newer build is removed within
about 15 minutes, once no job uses it. The cached images stay however long
they go unused, and a VM’s own download evicts the images outside the plan
first. ghost provider host get shows the cache (image_cache): what the
images take, the filesystem’s size and free space, the pace, and each image
with its state, cached, downloading, pending, left_out or failed,
and why.
The agent also watches the filesystem of its state. With less than 10% of it
free you get an incident, and with less than 5% the host is degraded and
takes no new rentals until more is free. The host’s Overview and
ghost provider host metrics show its size and free space.
libvirt’s virtlogd, which libvirt uses by default, writes what each guest
prints on its serial console to console.log in the guest’s directory under
/var/lib/gigahost/instances, with mode 0600. The agent keeps at
most 4 MiB of it per guest, and every ten seconds sends the end of each log
that changed, up to 64 KiB for each of eight guests, for the renter to
read. When it starts a guest again, it first keeps the end of what the
guest wrote since its previous start, up to 64 KiB, in console.boot
beside the log, also mode 0600, and sends that once too, so a report
takes at most about 1 MiB. A guest that ran before the agent recorded
consoles is recorded from its next start. sudo virsh console still
attaches to a running guest’s console.
backend_url defaults to https://api.gigahost.cloud when omitted. A plain
HTTP URL is accepted only when its host is a literal IPv4 or IPv6 address for
testing; http://localhost and every other hostname are rejected. Enrollment
and control requests never follow redirects. The agent does not resolve the
telemetry hostname: the backend alone resolves telemetry.gigahost.cloud and
publishes its current IPs in the signed discovery manifest used for HTTP bulk
traffic.
backup_upload_rate_bps is the aggregate hosted-backup upload limit in
bit/s: 500 Mbit/s on a cloud host and 50 Mbit/s on a home host when it is
left out. Adjust it so backups cannot crowd out guest traffic; on a home
host, to what its upload can spare. At 50 Mbit/s a backup of 30 GB takes
about 80 minutes to upload.
Local and hosted backups both stream through two reusable 64 MiB transfer
buffers; no complete raw staging disk is created. All-zero regions of a disk
are stored without data. Backups written by current agents use a container format that
older agents cannot restore, although current agents restore older backups,
so update the agent when a new release is published. A hosted backup that may
grow beyond about 625 GiB uploads in larger parts, the smallest whole number
of MiB that fits it into S3’s 10,000 parts, and its two buffers grow to that
size, so larger disks are not refused. Running guests use libvirt pull mode
and therefore need temporary copy-before-write scratch capacity in the root
pool. Restore recreates the root and writes into it directly, without a
restore staging volume.
Gigahost does not encrypt live VM root volumes. Enrollment and inventory
refresh require thin-LVM pool zeroing, which prevents newly provisioned blocks
from exposing previous guest data without writing the entire virtual disk.
Qcow2 roots are always fresh, use preallocation=falloc, and have no backing
file. Image conversion may punch sparse ranges, so the agent fallocates the
complete populated file again and verifies its physical allocation before
boot. This reserves capacity without a full-volume write. Backup
artifacts remain compressed and encrypted independently.
location is required for both home and cloud hosts. Choose a stable slug from
ghost catalog locations. The backend separately maps enabled Vultr regions to
those provider-neutral locations; a location does not need a Vultr mapping.
Each location also carries country and continent codes for catalog filtering. 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. The automatic value is worked out at enrollment; once the host
is enrolled, a 0 stands for the limit Gigahost accepted, and the agent
never lowers or raises the limit by itself, except in update-capacity,
where a CPU limit of 0 takes every CPU of the smallest allowed node (see
Raise a host’s capacity). A node holds as many
VMs as their CPU shares allow, up to four for each logical CPU; nothing else
limits their number. An instance_limit_per_numa_node that a configuration
still has is ignored with a warning: remove it.
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. 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.
Your host offers one ladder of sizes, each reserving twice the logical CPUs of
the one before, and with them twice the RAM and the disk: a quarter of a core
and half a core with 1 vCPU, one core and two cores with 2 vCPUs, and from
there as many cores as vCPUs, 4, 8, 16 and so on. Between two sizes of N and
2N vCPUs with a core each, a mid-step has 2N vCPUs on the N cores, RAM and
disk of the smaller one, where the larger one exists. The last size has
cpu_core_limit_per_numa_node vCPUs, a power of two or not; no VM has more,
and every VM remains within one NUMA node. With 4 GiB of RAM and 64 GiB of
disk for a core and a limit of 16, the sizes have 1, 2, 4, 8, 16, 32 and 64
GiB of RAM, and 16 GiB to 1 TiB of disk; the mid-steps of 4, 8 and 16 vCPUs
have 8, 16 and 32 GiB. A VM weighs on the host’s scheduler as much as the
cores it reserves: a mid-step of 4 vCPUs on 2 cores as much as the size of
2 vCPUs.
The platform offers no new VM with less than 768 MiB of memory, counted on
the size’s RAM, 64 MiB of VM overhead included, or with less than 8 GiB of
root disk, which most images need. A size of your host with less of either
is not offered, so the ladder starts higher: with
memory_bytes_per_cpu_core = "1.5G" a VM of 1 vCPU on a quarter of a core
would have 384 MiB, and the smallest VM the host offers has 1 vCPU on half a
core and 768 MiB; with root_storage_bytes_per_cpu_core = "25G" the quarter
of a core would have 6.25 GiB of disk, and the smallest VM has half a core
and 12.5 GiB. The larger sizes are not affected, and VMs that run keep their
size when a minimum changes.
The host’s Overview tab lists every size with its RAM and disk and whether
it is offered, noting a size with too little RAM, too little disk or both;
ghost provider host get HOST_UUID lists the sizes that are not offered in
sizes_below_minimum_memory and sizes_below_minimum_disk, and the host’s
Pricing tab says the same under what each size earns. Enrollment boots
the smallest and the largest size the host will offer, and refuses a host
none of whose sizes has both minimums, naming the per-core figure to raise.
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. The CPU limit and the NUMA nodes can later be
raised 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, 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; the backend chooses a physical host when a renter creates
an instance.
Protect the completed file:
sudo chmod 600 /etc/gigahost/hypervisor.tomlEnroll once
Section titled “Enroll once”After completing the home-mode or
cloud-mode configuration, 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 to run on
the server. Then enroll the
host and paste the token value when it asks; it is read like a password,
without echo:
sudo gigahost-hypervisor enrollThe 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, which other users of the host could read. Treat it as a
secret: it is accepted once, by one host, and expires three hours after it is
created by default (expires_at says when), so create it only when you are
ready to enroll. If enrollment is interrupted, run the same command with the
same token again; the host resumes with the same identity and is not enrolled
twice.
While it runs, the command shows every step on the terminal as it happens: 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,
and the network tests, each with how long it took. A step that fails says what
it was doing, and a failure before registration leaves the token unused. If
the wireguard step fails, look for a denial of /usr/bin/wg in
journalctl -k | grep -i denied (AppArmor or SELinux); the running agent
also logs an error at start when the check fails later, such as after an
upgrade. 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.
Enrollment creates the host identity in the configured state_file. Keep this
file on durable storage. Never copy it to a second running host or reuse it as
part of a machine image.
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 must reach cloud-init readiness, and libvirt must report the
guest’s virtio balloon statistics, which tell the agent that a new guest’s
kernel has started; the final pass must also 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 attempts to
publish that inventory immediately after enrollment; a network failure is
retained for 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 makes enrollment fail instead of advertising
capacity the machine cannot provide.
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 must likewise
permit search access (chmod o+x PATH); disk and key files retain restrictive
file permissions. Enrollment fails with the exact inaccessible path otherwise.
Successful first enrollment posts the configured non-refundable host-creation charge. Reusing the same enrollment identity cannot post the charge twice.
Newly enrolled hosts do not accept rentals automatically. They remain paused even after inventory and healthy heartbeats are visible. Finish pricing and network configuration, inspect the host, and then open admission explicitly:
ghost provider hostsghost provider host resume <HOST_UUID>A cloud-mode host can be resumed only once one of its registered addresses has passed its one-time check through it; see address and host checks. Home hosts need no such check: each rental’s public address is its Gigahost-run gateway.
See Pricing and SLA to set what the host earns on its
Pricing tab on the website or with ghost provider set-pricing (the
API) once the host is enrolled: per instance, per vCPU or as a total for the
whole host, and per TiB of traffic on a cloud host. Pricing is never part of
the agent’s configuration or a deployment file, and setting or changing it
needs neither a new enrollment nor a deployment. Complete the host’s
capacity, storage-pool, location, and pricing setup in your provider account
before accepting rentals. Backups are no part of your prices: the platform prices
them the same on every host. Existing VMs change prices only after 90 days
of notice, and the applicable restore and storage rates are copied to each
backup. Platform ceilings reject earnings above the configured limits.
Test in the foreground
Section titled “Test in the foreground”Run the agent once in the foreground:
sudo gigahost-hypervisor runEvery command reads /etc/gigahost/hypervisor.toml unless --config PATH
or the environment variable GIGAHOST_CONFIG_FILE names another file.
Wait for the initial checks to complete, then stop it with Ctrl+C. Resolve any
reported KVM, libvirt, storage, network, or permission errors before installing
the service.
Before it serves anything, every start of the agent checks that the machine
still has the capacity Gigahost accepted for the host: as many logical CPUs on
every allowed NUMA node as the CPU limit, in each node’s MemTotal the
guests’ RAM and 0.5 % of it, and room in the root pool for every guest disk
beside the router’s volume and the backup scratch, the disks of existing
guests counting as room. A root pool enrolled before VMs were counted by
their CPU shares may lack the room for the metadata of that many disks, a
file pool 4.5 MiB for each and a thin pool its volumes’ share of its
metadata: the agent logs it and the host keeps selling its disk as far as it
goes; extend the pool, or a thin pool’s metadata with
lvextend --poolmetadatasize, before you raise the host’s capacity. Free or
available RAM plays no part. The margin of 512 MiB that enrollment asked for
is not needed any more, so a BIOS,
firmware or kernel change that takes a little of a node’s RAM is fine. A
machine with cores or SMT turned off, RAM removed or a smaller root pool still
starts, and the agent logs the shortfall with its numbers. The host is then
degraded (the Accepted capacity condition above): it takes no new
rentals, its instances keep running, and its inventory is not updated until
the machine has the capacity again. The agent never lowers an accepted limit
to fit, so restore what is missing; it checks the whole machine again with
every inventory, every 15 minutes or sooner, and the host is healthy again
once the machine passes. sudo gigahost-hypervisor check makes the same
check without starting the agent and fails on such a machine, naming the
shortfall. A configuration that is not the host’s, with another mode,
location or capacity than Gigahost accepted, is another matter: the agent
refuses to start with it and says what to restore.
Enrollment starts real smoke VMs and rejects 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 must be enforcing; setools is required to
check the loaded policy. The agent also rejects remote
libvirt TCP/TLS listeners and any libvirt URI other than qemu:///system.
The running agent recollects inventory on startup and every 15 minutes. It publishes a new revision only when CPU, NUMA, memory, or usable storage changes, or when the parts of 100.64.0.0/10 that other software on a home host uses change (see home mode); revision state is written durably so retries after a crash are idempotent.
Run with systemd
Section titled “Run with systemd”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. Put local changes in a drop-in under
/etc/systemd/system/gigahost-hypervisor.service.d/ rather than editing the
unit, since an update replaces it. Enable and inspect it:
sudo systemctl enable --now gigahost-hypervisorsudo systemctl status gigahost-hypervisorsudo journalctl -u gigahost-hypervisor -fThe host should appear online in your provider account. Keep automatic security
updates enabled, but schedule reboots and hypervisor upgrades so active guests
are handled deliberately. ghost provider host pause <HOST_UUID> prevents new
rentals without interrupting existing guests or work already queued for the
agent.
Review ghost provider incidents as part of normal operations. The inbox
deduplicates host outages, degraded health, storage pressure, telemetry gaps,
invalid telemetry excluded from billing, failed work, and network bandwidth
drops, and links each item to the affected host or job where applicable.
Failed work is yours only when it failed on your host’s side, and each kind
of job, step, and error is an item of its own. A failure at a step the
platform runs itself, such as setting up a home VM’s gateway, or one the
renter caused, such as a restore not authorized in time, is no incident of
yours, and neither is a guest that starts no operating system again after
its renter’s reboot, reset, upgrade or restore: that system is the
renter’s. A guest that does not boot a catalog image when it is created or
reinstalled is still yours to look into.
Acknowledge an item while working on it and resolve it after the underlying
condition is fixed; a later recurrence reopens the same incident. On the
website, Provider › Incidents (/provider/incidents) lists them with
their State (Open, Acknowledged or Resolved) and
Severity (Critical, Error, Warning or Info) in columns of
their own, filtered by either, and a host’s Overview lists its open ones
the same way.
For permanent planned retirement while guests still exist, use:
ghost provider host retire <HOST_UUID> --notice-days 7 --yesSeven full days is the minimum. Admission closes immediately, affected renters
see the backend-calculated deadline, and durable deletion jobs begin when it is
reached. Keep the agent online through cleanup. Instance usage and gateway base
charges stop at the deadline, but storage capacity, public addresses, and
gateways remain fenced until their deletion is confirmed. Local-backup records
and billing close at the deadline, although their physical files may remain;
hosted backups are unaffected. Failed cleanup is reported as action_required
and is not retried without a bound.
To retire only one customer VM, keep the host online and schedule the same
minimum notice with ghost provider retire-instance <INSTANCE_UUID> --notice-days 7 --yes. If the machine is physically unrecoverable, use
ghost provider host emergency <HOST_UUID> --reason '...' --yes instead.
Emergency retirement stops billing immediately, records every affected VM as
abandoned, revokes the enrollment, and notifies renters; it cannot be undone.
External gateway deletion is still completed asynchronously by the backend.
The agent advertises independent free slots for provisioning, backup, restore,
upload, and maintenance work, along with the exact step kinds supported by its
binary. The backend leases only compatible work and atomically claims every
resource a step touches, such as its instance or backup, so two steps never
work on the same one at once. Storage pools are not claimed; the free slots
bound how much work runs on them. On every host the steps that move a whole
disk, a backup’s upload or local write and a restore’s writing, share two
slots, and the backend starts no more than two backups and restores of a
host at a time; the other backup steps, such as starting a backup or
publishing it, run beside them.
Enrollment also agrees with the backend on a version of the control
protocol. A backend that no longer speaks an agent’s version refuses its
requests until the agent is updated, and names the release to install, as it
does for a release it no longer serves
(support window). The agent logs the refusal once as an error, keeps its VMs running,
and asks again only with its heartbeat, at the heartbeat interval; update it
with the install command, which restarts it. sudo gigahost-hypervisor check shows the version the agent speaks, whether the
backend refused it, and whether the backend has the agent’s message types.
Raise a host’s capacity
Section titled “Raise a host’s capacity”A host can sell more of its machine without a new enrollment, keeping its
identity, its rentals and their history. Two settings of hypervisor.toml can
grow:
cpu_core_limit_per_numa_node, up to the logical CPUs of the smallest of the allowed NUMA nodes;0takes them all.allowed_numa_nodes, with another NUMA node that has at least as many logical CPUs as the accepted CPU limit. With a limit of0, a node with fewer would take the limit below the accepted one: the command names the node, which must stay out.
The RAM and root storage per logical CPU stay as enrolled, so every size keeps its shape and the host’s saleable RAM and disk grow with its CPUs: the machine must have them. A lower limit, a node dropped or another value of any other capacity setting is refused, naming the change, and needs a new enrollment (below), as does another mode or location.
Stop the agent, change the settings, raise the capacity, and start the agent again:
sudo systemctl stop gigahost-hypervisorsudoedit /etc/gigahost/hypervisor.tomlsudo gigahost-hypervisor update-capacity --reason "Sell all 64 threads"sudo systemctl start gigahost-hypervisorThe guests keep running while the agent is stopped, but the host sends no
heartbeats: a minute without them opens an outage, so run the steps one after
the other. update-capacity refuses to run while the agent does, since the
agent rewrites the state the command updates. It shows the capacity Gigahost
accepted and the one the configuration gives, and refuses a configuration
that raises nothing or changes anything else. It then checks the machine as
enrollment does: every allowed node needs as many logical CPUs as the new
limit, and in its MemTotal the guests’ RAM, 0.5 % of it and the margin of
512 MiB; the root pool needs room for every guest disk of the new capacity
with the metadata of the most VMs it allows, beside the router’s volume and
the backup scratch, the disks of existing guests counting as room. It boots
no VM: enrollment’s smoke tests proved the machine already. The reason, 1 to 500 bytes on one line, is kept
with the change.
The command then sends the new capacity to Gigahost, signed. The backend checks it the same way against the latest inventory the agent sent, records it as the next revision of the host’s capacity with its time and reason, and the command keeps it as the host’s own. A refusal shows the backend’s reason and changes nothing; with explicit limits, the agent then refuses to start with the larger configuration until the command succeeds or you restore the accepted values. A refusal for want of CPUs, RAM or disk names the inventory it judged by and its age: if the machine changed since, the backend does not know it yet, so start the agent, which sends a fresh inventory at once, stop it again, and run the command again. A host the backend no longer knows (retired, or enrolled again under a new identity) has nothing to raise.
If the command ends without the backend’s answer, from a lost connection, an answer it cannot verify or a failure of the backend, the backend may have accepted the new capacity. Run the same command again before you start the agent: a second run of a capacity the backend accepted answers the same revision, and the command keeps it. An agent started in between keeps the old capacity (with explicit limits it refuses to start), and the backend refuses its inventories until the command has run. The command exits with status 0 when the host keeps the new capacity, with 1 when nothing changed, and with 3 when it is to be run again.
When the agent starts, it sends its inventory at once, and the catalog offers
the larger sizes from its next refresh, under new offering IDs. Prices are
kept per vCPU, so every size the host offered keeps its price for existing
and new rentals, and the whole-host total on the Pricing tab grows with the
vCPUs; the command changes no price, which you set on the Pricing tab or
with ghost provider set-pricing as before. Existing rentals keep their
reservations, NUMA node, prices and SLA. The host’s Overview tab names the
revision in force and when it was accepted, and lists every revision with its
reason under Capacity details, as ghost provider host get
(capacity_revisions) does.
Immediately retire or re-enroll an empty host
Section titled “Immediately retire or re-enroll an empty host”A host’s mode and location, and any change of its capacity other than a higher CPU limit or more NUMA nodes (see Raise a host’s capacity), need a new enrollment. For one, the host must have no live rentals, unreleased instance resources, gateways, local backups that renters still keep, or active jobs. Stop the service and run:
sudo systemctl stop gigahost-hypervisorsudo gigahost-hypervisor reset-enrollment --yesThe command first asks the backend to retire the identity. It independently
refuses to continue if a managed libvirt domain still exists, then archives the
state file, durable job journal, telemetry spool, and SQLite sidecars with a
.retired-<HOST_UUID> suffix. Local backup artifacts are untouched; after
retirement they are outside the platform-managed lifecycle and may be retained
or removed by the provider. Their platform records and storage billing close at
retirement, while hosted backups remain available.
You can also retire an empty host remotely with
ghost provider host delete <HOST_UUID> --yes. The local reset remains required
before enrollment can be repeated. Create a fresh single-use enrollment token,
replace the consumed token file, adjust the configuration, and run enroll
again. The backend creates a new host identity and host-creation charge; the
retired historical record remains visible, and its label may be reused.
A cloud host keeps its public addresses across this. Hand the retired
identity’s pool to the new one with
ghost provider ip-pool hand-over <POOL_UUID> --host <NEW_HOST_UUID>: the new
host gets a pool with the same label and every address, and each address is
checked again through it before a rental may get it. The rentals the
addresses served stay on record with the old pool.