CLI to manage the EFI boot loader path (efiBootloaderPath) on OVH dedicated
servers via the OVH API.
This was built to fix a Proxmox 9.2 regression
where Proxmox's update drops a memtest86+.efi binary into the EFI System
Partition, which then becomes the most recent ESP entry. rEFInd (the OVH boot
loader) picks the most recently-modified .efi file, so the server boots into
memtest instead of the actual Proxmox installer. The fix is to set
efiBootloaderPath = \efi\systemd\systemd-bootx64.efi on the OVH backend, which
forces rEFInd to launch systemd-boot regardless of ESP timestamps.
Requirements: Bun 1.3+.
git clone <this repo>
cd ovh-boot-loader
bun installOr build a single static binary and drop it on your $PATH:
bun run build
sudo cp build/ovh-boot-loader /usr/local/bin/You need three values: an appKey, an appSecret, and a consumerKey. The
app identifies your tool, the secret signs every request, and the consumer key
is a per-user token that carries the granted permissions.
In the OVH API console, create a new application. Pick whichever region your
account uses (CA or EU). Note the Application Key and Application
Secret — these go into the [ovh] section of your config.
A consumer key is a per-user token that you request on the user's behalf. The request returns a validation URL the user must click to authorize the rights; after authorization, the same response gives you the Consumer Key.
Request one with curl (replace the placeholder appKey/secret with your own):
curl -X POST \
-H "X-Ovh-Application: YOUR_APP_KEY" \
-H "Content-Type: application/json" \
-d '{
"accessRules": [
{"method": "GET", "path": "/dedicated/server"},
{"method": "GET", "path": "/dedicated/server/*"},
{"method": "PUT", "path": "/dedicated/server/*"}
],
"redirection": "https://example.com"
}' \
https://ca.api.ovh.com/1.0/auth/credentialImportant: OVH's glob
/dedicated/server/*does not match the bare/dedicated/serverlist endpoint. You need all three rules above, not just the wildcard ones.
The response looks like:
{
"validationUrl": "https://ca.api.ovh.com/auth/?credentialToken=...",
"consumerKey": "abc123...",
"state": "pendingValidation"
}Open validationUrl in a browser, log in as the OVH account holder, and click
Authorize. After that, state becomes validated and the consumerKey is
ready to use.
cp config.toml.example config.toml
$EDITOR config.tomlThe config format is:
[ovh]
appKey = "..."
appSecret = "..."
consumerKey = "..."
# Optional. Defaults to "ca" (ca.api.ovh.com). Use "eu" for the European
# endpoint. The consumer key must be created on the same region.
# region = "eu"ovh-boot-loader [--config <path>] [--debug] [--env production|development] <command>Default config path: /etc/ovh-boot-loader/config.toml. Override with
--config ./config.toml for local development.
Verify the configured credentials are valid and reachable. Hits
GET /auth/currentCredential (works with any validated consumer key) and
prints the appKey, status, and creation/expiration dates. Use this first if
anything else 403s.
ovh-boot-loader whoami
# endpoint: https://ca.api.ovh.com/1.0
# appKey: yourAppKey
# status: validated
# applicationId: 12345
# scope: ...
# creation: 2026-06-24T...
# lastLogin: 2026-06-24T...
# expiration: 2027-06-24T...List all OVH dedicated servers on the account, with the IAM displayName next
to the service name when set. Fans out one GET /dedicated/server/{name} per
server in parallel.
ovh-boot-loader list
# ns1234.ip-1-2-3.eu
# vmh01.server vmh01 - Proxmox hypervisor
# vmh02.server vmh02 - Proxmox hypervisorShow the current boot configuration for a single server. Always prints
efiBootloaderPath — (unset) if the OVH API returns null (which is the
default state for Proxmox 9.2 servers before this fix), the actual path
otherwise. Also shows iam.urn for cross-referencing with IAM policies.
ovh-boot-loader get vmh01.server
# serviceName vmh01.server
# name vmh01 - Proxmox hypervisor
# state ok
# efiBootloaderPath (unset) ← this is the bug state
# bootId (unset)
# bootScript (unset)
# iam.urn urn:v1:ca:resource:dedicatedServer:vmh01.serverAfter running fix-proxmox, the same command will show:
# efiBootloaderPath \efi\systemd\systemd-bootx64.efi
Set efiBootloaderPath to an arbitrary path on a single server. Use
fix-proxmox for the Proxmox 9.2 known-good value; set is for everything
else.
ovh-boot-loader set <serviceName> <efiBootloaderPath>
ovh-boot-loader set vmh01.server '\efi\proxmox\grubx64.efi'
# [vmh01.server]
# current efiBootloaderPath: \efi\systemd\systemd-bootx64.efi
# new efiBootloaderPath: \efi\proxmox\grubx64.efi
# ✓ updatedThe path is sent verbatim and JSON-encoded for the API. Use the Windows-style backslashes the OVH backend expects, not Linux forward slashes.
Convenience command for the Proxmox 9.2 rEFInd/memtest bug. Sets
efiBootloaderPath to \efi\systemd\systemd-bootx64.efi on one or more
servers.
ovh-boot-loader fix-proxmox vmh01.server vmh02.server vmh03.server
# [vmh01.server]
# current efiBootloaderPath: (unset)
# new efiBootloaderPath: \efi\systemd\systemd-bootx64.efi
# ✓ updated
#
# [vmh02.server]
# current efiBootloaderPath: (unset)
# new efiBootloaderPath: \efi\systemd\systemd-bootx64.efi
# ✓ updated
#
# [vmh03.server]
# ...Use GRUB installations instead? Override with the explicit set command and
\efi\proxmox\grubx64.efi.
The consumer key doesn't have GET /dedicated/server (the bare list path).
Re-create the consumer key with all three rights — see step 2 above. The
wildcard /dedicated/server/* does not match the bare path.
The consumer key has GET rights but no PUT on /dedicated/server/*. Re-create
the consumer key with the PUT rule.
Either the appKey/appSecret/consumerKey triple is wrong, or the consumer key
hasn't been validated yet. Run ovh-boot-loader whoami to check the status
— it should print status: validated. If it's pendingValidation, the user
hasn't clicked the validation URL yet.
The service name is wrong. ovh-boot-loader list prints the actual service
names on the account. The naming format is typically ns1234.ip-1-2-3.eu or
abc123.server depending on the OVH legacy vs. new range.
The default endpoint is ca.api.ovh.com. If your account is on the European
endpoint, set region = "eu" in the config. Consumer keys are region-scoped
— a CA consumer key won't work on the EU endpoint and vice versa.
bun install
bun test # run the OVH lib test suite
bun run type-check # tsc --noEmit
bun run build # produce a single binary in build/ovh-boot-loaderThe OVH client lives in src/ovh-lib.ts; the CLI subcommands are in the
src/ovh-boot-loader-*.ts files. There's no global state — createOvhClient
captures credentials in its closure and returns a stateless request function.
MIT