Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

ovh-boot-loader

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.

Install

Requirements: Bun 1.3+.

git clone <this repo>
cd ovh-boot-loader
bun install

Or build a single static binary and drop it on your $PATH:

bun run build
sudo cp build/ovh-boot-loader /usr/local/bin/

Get an OVH API key

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.

1. Create an application

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.

2. Request a consumer key

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/credential

Important: OVH's glob /dedicated/server/* does not match the bare /dedicated/server list 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.

3. Configure

cp config.toml.example config.toml
$EDITOR config.toml

The 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"

Usage

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.

whoami

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

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 hypervisor

get

Show 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.server

After running fix-proxmox, the same command will show:

# efiBootloaderPath  \efi\systemd\systemd-bootx64.efi

set

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
#   ✓ updated

The path is sent verbatim and JSON-encoded for the API. Use the Windows-style backslashes the OVH backend expects, not Linux forward slashes.

fix-proxmox

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.

Troubleshooting

OVH API 403: NOT_GRANTED_CALL on list

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.

OVH API 403: NOT_GRANTED_CALL on set or fix-proxmox

The consumer key has GET rights but no PUT on /dedicated/server/*. Re-create the consumer key with the PUT rule.

OVH API 401

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.

Works on one server, fails on another with This server does not exist

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.

Wrong region

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.

Development

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-loader

The 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.

License

MIT

About

Tool to help update the OVH boot loader on dedicated servers.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages