Kheeper

Bare Metal: OVHcloud

OVHcloud dedicated servers support custom iPXE boot, but — unlike latitude.sh — there is no field in the order form to paste a script into. You set the boot script through the OVH API instead. This page gives you a complete Go program that does the whole dance: set the script, reboot into it, then flip the server back to disk boot so it doesn't reinstall on every reboot.

See Working with Bare Metal for the general flow.

Step 1 — Create the iPXE script

Generate a one-time iPXE script for your host name. It is valid for 24 hours.

kheeper hosts ipxe create myorg/web-server

Copy the full #!ipxe … boot output — you'll paste it into the program below.

Step 2 — Get OVH API credentials

Create an application key, secret, and consumer key at OVH's token page for the region your server is in:

Grant these rights so the program can read the server, set the boot script, and reboot:

GET    /dedicated/server/*
PUT    /dedicated/server/*
POST   /dedicated/server/*/reboot

Save the credentials to ~/.ovh.conf. The endpoint must match the region where you minted the token (ovh-eu, ovh-us, or ovh-ca), and the credential section name must match that endpoint — this mismatch is the most common reason the first call fails with This application key is invalid:

[default]
endpoint=ovh-eu

[ovh-eu]
application_key=xxxxxxxxxxxxxxxx
application_secret=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
consumer_key=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Step 3 — Run the program

Set up a module and add the official OVH client:

mkdir kheeper-ovh && cd kheeper-ovh
go mod init kheeper-ovh
go get github.com/ovh/go-ovh/ovh

Save the following as main.go. Edit the two consts at the top: server is your dedicated server's name (e.g. ns1234567.ip-1-2-3.eu, shown in the OVH control panel), and bootScript is the script you copied in step 1.

package main

import (
	"fmt"
	"log"
	"time"

	"github.com/ovh/go-ovh/ovh"
)

// --- Edit these two values ---

// server is your OVH dedicated server's name from the control panel.
const server = "ns1234567.ip-1-2-3.eu"

// bootScript is the full output of `kheeper hosts ipxe create`. Keep the leading
// #!ipxe line and the trailing boot line.
const bootScript = `#!ipxe
# ... paste your kheeper iPXE script here ...
boot
`

// endpoint must match the region in your ~/.ovh.conf (ovh-eu, ovh-us, ovh-ca).
const endpoint = "ovh-eu"

func main() {
	// Reads application_key / application_secret / consumer_key from the
	// matching section of ~/.ovh.conf.
	client, err := ovh.NewEndpointClient(endpoint)
	if err != nil {
		log.Fatalf("ovh client: %v", err)
	}

	// 1. Stage the custom iPXE script. Setting bootScript alone makes the
	//    server netboot it on the next reboot; OVH leaves bootId null. (You do
	//    NOT set a bootId here — that's only used to revert to disk, below.)
	setScript := struct {
		BootScript string `json:"bootScript"`
	}{BootScript: bootScript}
	if err := client.Put("/dedicated/server/"+server, setScript, nil); err != nil {
		log.Fatalf("set bootScript: %v", err)
	}
	fmt.Println("set custom iPXE bootScript")

	// 2. Reboot so the server netboots the installer.
	var task struct {
		TaskID   int    `json:"taskId"`
		Function string `json:"function"`
		Status   string `json:"status"`
	}
	if err := client.Post("/dedicated/server/"+server+"/reboot", nil, &task); err != nil {
		log.Fatalf("reboot: %v", err)
	}
	fmt.Printf("reboot task %d: %s (%s)\n", task.TaskID, task.Function, task.Status)

	// 3. Wait for the server to PXE-boot and pull the installer into RAM, then
	//    revert to disk boot. This is the important step: once a bootScript is
	//    set, EVERY reboot netboots the installer — so when the install finishes
	//    and reboots, it would reinstall again, looping forever. Setting
	//    bootId=1 reverts to local-disk boot and clears bootScript, so the
	//    install's own post-reboot lands on the freshly installed disk.
	fmt.Println("waiting 2m for netboot before reverting to disk boot...")
	time.Sleep(2 * time.Minute)

	revert := struct {
		BootID int `json:"bootId"`
	}{BootID: 1}
	if err := client.Put("/dedicated/server/"+server, revert, nil); err != nil {
		log.Fatalf("revert to disk boot: %v", err)
	}
	fmt.Println("reverted to disk boot; the install will boot from disk when it finishes")
}

Run it:

go run .

You'll see the boot script get set, a reboot task created, a two-minute pause, and then the revert to disk boot.

Step 4 — Verify

The install takes ten to thirty minutes. When it finishes, the machine boots from disk into the Kheeper base image and auto-registers:

kheeper hosts list --org myorg

You should see web-server in the list. Deploy to it like any other host — see Working with Hosts.

Why the two-minute wait and revert? OVH serves the bootScript on every network boot until you clear it. Without the revert, the server reinstalls each time the installer reboots and never comes up. Two minutes is enough for the server to fetch the installer over the network; after that it no longer needs the script, so it is safe to set bootId=1 and let the install finish.

Further reading