A web console for managing Microsoft Hyper-V clusters, hosts and virtual machines - browse your inventory, run VM lifecycle and power actions, edit hardware, manage templates and ISOs, and open a VM console straight from the browser, all in a Windows 95/98 “Explorer” interface.
OVM is a distributed system: a web frontend, a REST API + background worker, and a lightweight agent installed on every Hyper-V host. The backend and the agents never talk directly - every operation flows through RabbitMQ.
This repository is the project umbrella: the high-level overview, screenshots,
the LICENSE, and the source for the GitHub Pages site. The runnable code lives
in the component repositories listed below.
https://claudio-azevedo.github.io/Open-Virtualization-Manager/
┌─────────────────────────────────────────────┐
Browser ──────────────▶ ovm-frontend (SSR web UI) │
(OIDC login) │ React 19 · TanStack Start · Win95 kit │
└───────────────┬─────────────────────────────┘
│ REST /api/* (bearer token
│ attached server-side)
┌───────────────▼─────────────────────────────┐
│ ovm-backend │
│ ┌─────────────┐ ┌──────────────────┐ │
│ │ API │ │ Worker │ │
│ │ (FastAPI) │ │ (RabbitMQ │ │
│ │ publishes │ │ consumer) │ │
│ │ requests, │ │ applies results │ │
│ │ creates │ │ to DB + cache, │ │
│ │ tasks │ │ times out tasks │ │
│ └──────┬──────┘ └────────▲─────────┘ │
└──────────┼──────────────────────┼───────────┘
PostgreSQL ◀────────────┤ │
Valkey ◀────────────┘ │
│ RabbitMQ (per-host queues)
request ───▼──────────────────────┤ response / inventory
┌────────────────────────────────────────────┐
│ ovm-agent (one per Hyper-V host) │
│ Go · Windows service · PowerShell │
│ executes vm_* / host_* operations, │
│ publishes inventory + task results │
└────────────────────────────────────────────┘
/api/* to the backend and attaches the
OIDC bearer token.AgentRequest messages
and records a Task for every mutating operation; it never blocks on the agent.<hostid>.request queue, runs the work on the
host (mostly PowerShell / Hyper-V cmdlets), and publishes progress, results and
periodic inventory back onto per-host queues.202 { task };
the worker consumes the agent’s response and updates the row; the frontend
polls GET /tasks/:id until it finishes, then refetches inventory.| Repository | Language / stack | Role |
|---|---|---|
| ovm-frontend | React 19, TanStack Start (SSR), TypeScript, Tailwind v4 | Web console. Windows 95/98 UI. OIDC login, RBAC-aware views, VM grid, task dock, embedded VM console. Talks to the backend over REST only. |
| ovm-backend | Python 3.11+, FastAPI, SQLAlchemy 2 (async), Alembic, aio-pika | Two processes: the API (uvicorn app.main:app) serves /api/*, publishes agent requests and creates tasks; the worker (python -m app.worker) consumes every host’s response + inventory queues, writes PostgreSQL and the Valkey cache, and times out stale tasks. |
| ovm-agent-hyperv | Go 1.25+, Windows service | Installed on each Hyper-V host. Communicates exclusively via RabbitMQ. Persistent SQLite job queue, subprocess-per-job, self-upgrade. Executes vm_management / host_management functions and publishes periodic inventory. The queue protocol is hypervisor-agnostic (future libvirt/KVM agents implement the same contract). |
| ovm-webrdp | Java (Guacamole client) + guacd |
Browser RDP / Hyper-V vmconnect (port 2179) gateway. The frontend’s VM Console tab embeds a guacamole-common-js client that connects to its HTTP tunnel. |
The backing services (PostgreSQL, RabbitMQ, Valkey, an OIDC provider, and
guacd for the console) are ordinary containers you run yourself - see
Backing services.
ovm-frontend/docs/api-contract.md is the source of truth for the REST API.
ovm-backend/app/messaging/ is the source of truth for the RabbitMQ
request/response and inventory payloads that the agents implement.
Four services run alongside ovm-backend (plus guacd next to ovm-webrdp).
They are plain containers - any deployment method works (Compose, Kubernetes,
managed services). Minimum versions:
| Service | Min version | Used by | Purpose |
|---|---|---|---|
| PostgreSQL | 17 | ovm-backend | System of record: clusters, hosts, folders, VMs (extended inventory), templates, ISOs, tasks (with raw request/response), scope grants. Schema managed by Alembic migrations. |
| RabbitMQ | 4 | ovm-backend ⇄ ovm-agent | The only channel between the backend and the hosts. Per-host queues: <hostid>.request (backend → agent), <hostid>.response (task progress + result), and last-value inventory queues (agent_status, vm_inventory, host_inventory, template_inventory, iso_inventory). Last-value queues use x-max-length=1 / x-overflow=drop-head. |
| Valkey | 8.0 (or Redis 7+) | ovm-backend | Cache and coordination: agent liveness / online-offline state, per-VM mutation locks (vm:lock:<id> - one mutating agent task per VM at a time), VM delete tombstones, and cached inventory reads. |
| OIDC provider | any OIDC/OAuth2 IdP | ovm-frontend, ovm-backend | Authentication - Keycloak, Auth0, Okta, Entra ID, … The frontend runs better-auth (cookie mode); the backend verifies the JWT. Roles are read from a configurable claim; ADMINISTRATOR bypasses all scope filters. A stub / dev-bypass mode exists for local UI work. |
| guacd | 1.6.0 | ovm-webrdp | Guacamole proxy daemon; speaks RDP (3389) and Hyper-V vmconnect (2179) to the target hosts. Needs network reach to every Hyper-V host. |
services:
postgres:
image: postgres:17
environment:
POSTGRES_DB: ovm
POSTGRES_USER: ovm
POSTGRES_PASSWORD: ovm123
ports: ["5432:5432"]
volumes: ["pgdata:/var/lib/postgresql/data"]
rabbitmq:
image: rabbitmq:4-management
hostname: ovm-rabbitmq # stable Erlang node name across recreates
environment:
RABBITMQ_DEFAULT_USER: ovm
RABBITMQ_DEFAULT_PASS: ovm123
ports: ["5672:5672", "15672:15672"]
volumes: ["rabbitmq:/var/lib/rabbitmq"]
valkey:
image: valkey/valkey:8
command: ["--save", "60", "1"]
ports: ["6379:6379"]
volumes: ["valkey:/data"]
guacd: # only needed for the VM Console tab
image: guacamole/guacd:1.6.0
ports: ["4822:4822"]
volumes: { pgdata: {}, rabbitmq: {}, valkey: {} }
Then point the backend at them:
OVM_DATABASE_URL=postgresql+asyncpg://ovm:ovm123@localhost:5432/ovm
OVM_VALKEY_URL=redis://localhost:6379/0
OVM_RABBITMQ_URL=amqp://ovm:ovm123@localhost:5672/
Add a Keycloak (or any IdP) container if you don’t already have one; for local
UI work you can skip it with frontend OIDC_DEV_BYPASS=true + backend
OVM_AUTH_MODE=stub.
POST /api/vms/{id}/actions/start.Task row
(queued), publishes an AgentRequest to <hostid>.request with
correlation_id = task id, and returns 202 { task }.<hostid>.response.Task (and the VM row
live from the result payload), releases the lock, and refreshes the cache.TaskWatcher polls GET /tasks/:id until it reaches a
terminal status, then refetches inventory. The task appears in the Recent
Tasks dock with initiator, progress, status and timestamps.If a host’s agent_status goes stale (default 120 s), the backend marks it
offline: its VMs read as Unknown and mutating actions return
409 HOST_OFFLINE. Stale tasks are timed out by the worker (default 300 s).
Everything below is per-repository; see each repo’s own README.md for detail.
1. Start the backing services - PostgreSQL, RabbitMQ, Valkey (and guacd
for the console). Use the minimal Compose file above,
or any equivalent.
2. Backend (ovm-backend) - API + worker, DB starts empty:
cp .env.example .env
uvicorn app.main:app --reload --port 8000 # terminal 1
python -m app.worker # terminal 2
Or docker compose up -d (runs API + worker only; add --profile demo for a
synthetic agent so you don’t need a Windows host).
3. Frontend (ovm-frontend):
npm install
cp .env.example .env.local # set OIDC_DEV_BYPASS=true for local UI work
npm run dev # http://localhost:3000
4. Agent (ovm-agent-hyperv) - on each real Hyper-V host:
Register the host with POST /api/hosts {name}, copy its Setup Agent
config.ini onto the host next to ovm-agent.exe, then:
ovm-agent.exe install
ovm-agent.exe start
For a deployment behind one domain, a reverse proxy splits traffic:
/ → frontend, /api/ → backend, /webrdp/ → ovm-webrdp.
See docs/screenshots/.
Licensed under the Apache License 2.0 - see LICENSE.