OVH Cloud VPS provider backend plugin for mngr
Project description
mngr OVH Cloud Provider
OVH Cloud VPS provider backend plugin for mngr. Runs agents in Docker containers on OVH classic VPS instances (e.g. vps-2025-model1 with 1 vCPU / 8 GB RAM / 80 GB SSD at ~$7.99/mo).
See mngr_vps_docker for the base architecture and shared infrastructure.
Setup
OVH's API requires either OAuth2 service-account credentials or the legacy AK/AS/CK signed-request credentials. The plugin uses the official python-ovh SDK, so it can also read credentials from the standard ~/.ovh.conf file.
Set the env vars or add to ~/.mngr/config.toml:
[providers.ovh]
backend = "ovh"
endpoint = "ovh-us"
# Either OAuth2:
client_id = "..."
client_secret = "..."
# Or AK/AS/CK:
application_key = "..."
application_secret = "..."
consumer_key = "..."
Recognised env vars (in order of precedence after explicit config):
OVH_ENDPOINT(defaultovh-us)OVH_CLIENT_ID,OVH_CLIENT_SECRETOVH_APPLICATION_KEY/OVH_APP_KEY,OVH_APPLICATION_SECRET/OVH_APP_SECRET,OVH_CONSUMER_KEY
If none of the above are set, ~/.ovh.conf is consulted via python-ovh's normal discovery.
Usage
mngr create my-agent --provider ovh
mngr create my-agent --provider ovh -b --ovh-plan=vps-2025-model1 -b --ovh-datacenter=US-EAST-VA
mngr list
mngr exec my-agent "echo hello"
mngr stop my-agent
mngr start my-agent
mngr destroy my-agent
Operator inspection
mngr ovh list # show all mngr-tagged OVH VPSes (plan, datacenter, state, expiration, cancel?, IAM tags)
mngr ovh list --all # include untagged VPSes too -- useful for sanity-checking the account contents
OVH-specific configuration
These fields extend the base VpsDockerProviderConfig (see mngr_vps_docker):
| Field | Default | Description |
|---|---|---|
endpoint |
ovh-us |
python-ovh endpoint id (ovh-eu, ovh-us, ovh-ca, ...) |
application_key |
None (env / ~/.ovh.conf) |
AK |
application_secret |
None (env / ~/.ovh.conf) |
AS |
consumer_key |
None (env / ~/.ovh.conf) |
CK |
client_id |
None (env / ~/.ovh.conf) |
OAuth2 client id |
client_secret |
None (env / ~/.ovh.conf) |
OAuth2 client secret |
project_id |
None |
Reserved for future Public Cloud support; unused for classic VPS |
default_region |
US-EAST-VA |
Default VPS datacenter |
default_plan |
vps-2025-model1 |
Default plan code (1 vCPU / 8 GB RAM / 80 GB SSD, ~$7.99/mo as of 2025-05) |
default_image_name |
Debian 12 - Docker |
Default OS image (Docker pre-installed) |
bootstrap_ssh_user |
debian |
Non-root user the OVH image installs the rebuild key for. Only override if you change default_image_name to a non-Debian image (e.g. ubuntu for Ubuntu images, almalinux for AlmaLinux). |
pricing_mode |
default |
OVH pricing mode (default, upfront6, upfront12) |
duration |
P1M |
ISO-8601 commitment duration (monthly only) |
instance_boot_timeout |
600.0 |
Seconds to wait for the OVH order to deliver a VPS |
enable_recycle_cancelled |
True |
Whether mngr create may reuse a cancelled-but-still-alive VPS instead of ordering fresh |
recycle_safety_margin_hours |
2 |
Min hours of remaining expiration for a cancelled VPS to be recyclable |
recycle_max_candidates_considered |
10 |
Cap on the number of cancelled VPSes evaluated before falling through to a fresh order |
Implementation details
- Uses the official
python-ovhSDK for HTTP and request signing - VPSes are tagged with
mngr-provider=<name>andmngr-host-id=<id>via OVH IAM v2 tags on the VPS resource URN - Discovery:
GET /v2/iam/resource?resourceType=vpsfiltered client-side for matching tags - Provisioning: full
/order/cartflow (POST /order/cart→POST /cart/{id}/vps→ configure datacenter/OS → assign → checkout → poll/vpsuntil the newserviceNameappears) - Bootstrap (no cloud-init available): after delivery,
POST /vps/{s}/rebuildwithpublicSshKey+doNotSendPassword=truepre-installs our client key. OVH installs that key for the image's default non-root user (e.g.debianonDebian 12 - Docker), not for root, so the provider then SSHes in as that user, pins the host key on first connect (StrictHostKeyChecking=accept-newsemantics), sudo-copiesauthorized_keysinto/root/.ssh/, and verifies SSH-as-root works before handing off to the rest of the provider. After pinning, strict host-key checking is enforced on every subsequent connection. - Backups disabled: as the final bootstrap step, the provider purges all
qemu*packages (apt-get purge --auto-remove 'qemu*'). OVH automated backups drive the image'sqemu-guest-agentto freeze the guest filesystem, which causes serious runtime problems on the agent; removing qemu removes the mechanism the backups hook into. The purge runs on both the fresh-order and recycle paths (rebuilding the OS reinstalls the agent), and a failure aborts provisioning so no host is left running with backups enabled. mngr also never orders an OVH backup option in the cart in the first place.
Security caveat (first connect)
OVH's VPS API exposes no way to inject SSH host keys at install time or retrieve the freshly generated host key out-of-band (no cloud-init, no userData, no fingerprint endpoint, only graphical VNC). So the first SSH connection from mngr to a new VPS performs a one-shot TOFU on the host key.
Because the rebuild already installed our SSH client public key, key-auth is enforced from connection #1. An attacker positioned to MITM the brief first-connect window can passively read the session but cannot impersonate the VPS (they would need our SSH private key to satisfy the key-auth handshake). This is comparable to a first-time git clone git@github.com:... on a machine that hasn't cached GitHub's host key.
Pinned host keys live under <profile_dir>/providers/ovh/<provider_name>/known_hosts/<service_name> and are loaded with strict checking on every subsequent connection.
Billing caveat
OVH classic VPS is billed monthly (no hourly option). mngr stop my-agent halts the Docker container only — the VPS keeps running, and you keep being billed until the next renewal anniversary. mngr destroy my-agent cancels auto-renewal via PUT /vps/{s}/serviceInfos (renew.deleteAtExpiration=true) — no email confirmation step, no human in the loop. The VPS still keeps running until the OVH-side expiration date (OVH does not prorate classic VPS cancellations) and then auto-decommissions. For monthly subscriptions that's the rest of the current month; for upfront6 / upfront12 it can be up to 6 / 12 months of prepaid balance respectively. The cancelled-but-still-alive window is what the auto-reuse logic below exploits.
Auto-reuse of cancelled VPSes
To avoid wasting the remainder of a paid month, mngr create --provider ovh first checks for cancelled VPSes (those with renew.deleteAtExpiration=true) tagged by this provider instance. If one matches the requested plan + datacenter and has enough buffer until expiration, it is un-cancelled in place via PUT /vps/{s}/serviceInfos (no email round-trip needed for the reversal), its OS is rebuilt with our SSH key, and the mngr-host-id IAM tag is swapped to the new host. Only when no eligible cancelled VPS exists does a fresh order go out.
Eligibility filters (all required):
mngr-providerIAM tag matches this provider instance's name. A VPS that mngr ordered but never finished tagging — e.g. an order whose delivery timed out before_provision_vps's tag-immediately step ran — is invisible to the recycle path. See "Adopting slowly-delivered orphan VPSes" below.renew.deleteAtExpiration == trueandstatus == "ok"engagedUpTois null (no active engagement commitment)expirationis at leastrecycle_safety_margin_hours(default 24h) into the future — guards against the billing boundary- VPS
stateisrunningorstopped(not installing/maintenance/etc) - VPS plan/model and datacenter match the request
- No
mngr-recycling-bylock tag (anothermngr createis mid-recycle)
A cooperative lock tag (mngr-recycling-by=<uuid>) is attached before any mutating call, and re-read after attach to detect concurrent recyclers. The lock is best-effort: OVH IAM tag writes are not atomic, so under extreme contention two recyclers could both attach their UUIDs. The mitigation is that the second one's rebuild will hit OVH's per-VPS task-lock and fail, falling through to a fresh order.
This is opt-in via enable_recycle_cancelled (default True). Disable by setting enable_recycle_cancelled = false in your provider config if you'd rather see fresh VPS deliveries on every mngr create.
Intended usage is to keep a VPS pool warm (e.g., via mngr_imbue_cloud) so that destroy → create within the same billing month is essentially free.
Adopting slowly-delivered orphan VPSes
OVH's order pipeline is asynchronous: a POST /order/cart/{id}/checkout returns immediately with an orderId, but the actual VPS serviceName is only assigned during a later delivery phase. mngr create waits up to instance_boot_timeout (default 10 min) for that delivery. If OVH is slow (busy region, new-account fraud-review hold, etc.) and the VPS shows up after the timeout, the order silently produces a VPS that mngr never tags — invisible to discovery and to the recycle path. A full month of billing would leak.
The recovery mechanism is a pending-order marker pattern:
-
Marker write on timeout. When
order_and_wait_for_vpsraisesOvhOrderDeliveryTimeoutError,_provision_vpswrites a JSON marker file at<profile_dir>/providers/ovh/<instance>/pending_orders/order-<N>.jsoncarryingorder_id+plan_code+region, then re-raises the original failure. The bake exits at its normal timeout — no extra wait. -
Reconcile sweep on every subsequent bake. At the top of every
_provision_vps,_reconcile_pending_orderswalks the marker dir. For each marker, one short OVH poll (try_poll_order_for_delivered_vps):- If the order has delivered → attach
mngr-provider/mngr-host-idIAM tags, flipdeleteAtExpiration=true, delete the marker. The_maybe_claim_recycled_vpscall that runs immediately after sees the newly-tagged orphan as a candidate and claims it for the current bake. - If still pending → keep the marker for the next bake's reconcile.
- If the order has delivered → attach
Failure modes are all swallowed (logged at WARNING) so a transient OVH error or a broken marker doesn't block the current bake from proceeding to its normal order path. Worst case the orphan retries on the next bake. No operator intervention required.
Mirrors the shape of minds.envs.recover's recover-target file pattern.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file imbue_mngr_ovh-0.1.4.tar.gz.
File metadata
- Download URL: imbue_mngr_ovh-0.1.4.tar.gz
- Upload date:
- Size: 92.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4b192ef18f4e1051576042dc67eddf40b3b8951a016be095b9185f9f07188b06
|
|
| MD5 |
d14858ad570a4f3b10dd30e6eaa0dc36
|
|
| BLAKE2b-256 |
d37263c03d2ebb9711a71eed01fe46bcaeb5246e50818ad7c2f8bcc317dae96f
|
Provenance
The following attestation bundles were made for imbue_mngr_ovh-0.1.4.tar.gz:
Publisher:
publish.yml on imbue-ai/mngr
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
imbue_mngr_ovh-0.1.4.tar.gz -
Subject digest:
4b192ef18f4e1051576042dc67eddf40b3b8951a016be095b9185f9f07188b06 - Sigstore transparency entry: 1835035692
- Sigstore integration time:
-
Permalink:
imbue-ai/mngr@007398deb560be6724a62ba6692b089df0671a03 -
Branch / Tag:
refs/tags/v0.2.15 - Owner: https://github.com/imbue-ai
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@007398deb560be6724a62ba6692b089df0671a03 -
Trigger Event:
push
-
Statement type:
File details
Details for the file imbue_mngr_ovh-0.1.4-py3-none-any.whl.
File metadata
- Download URL: imbue_mngr_ovh-0.1.4-py3-none-any.whl
- Upload date:
- Size: 58.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9f1d21f5b1292bdb03bf2dcfcd1b6c876fe67f3983f91e3dbbe2f6a5cc6c9f1b
|
|
| MD5 |
f06a70713f5538e108438a231f5ed2df
|
|
| BLAKE2b-256 |
5481c4fb607b91024bf489525f0cf24cf932de10a161d548e502262db7687384
|
Provenance
The following attestation bundles were made for imbue_mngr_ovh-0.1.4-py3-none-any.whl:
Publisher:
publish.yml on imbue-ai/mngr
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
imbue_mngr_ovh-0.1.4-py3-none-any.whl -
Subject digest:
9f1d21f5b1292bdb03bf2dcfcd1b6c876fe67f3983f91e3dbbe2f6a5cc6c9f1b - Sigstore transparency entry: 1835036472
- Sigstore integration time:
-
Permalink:
imbue-ai/mngr@007398deb560be6724a62ba6692b089df0671a03 -
Branch / Tag:
refs/tags/v0.2.15 - Owner: https://github.com/imbue-ai
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@007398deb560be6724a62ba6692b089df0671a03 -
Trigger Event:
push
-
Statement type: