Open-vCenter

Open Virtualization Manager (OVM)

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/


Architecture

                          ┌─────────────────────────────────────────────┐
   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        │
                          └────────────────────────────────────────────┘

Components

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.

The contract

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.


Backing services

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.

Minimal Docker Compose

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.


How a request flows

  1. An operator clicks Start on a VM in the frontend.
  2. The frontend optimistically flips the VM to a transitional state and calls POST /api/vms/{id}/actions/start.
  3. The backend API takes the per-VM lock in Valkey, writes a Task row (queued), publishes an AgentRequest to <hostid>.request with correlation_id = task id, and returns 202 { task }.
  4. The agent on that host consumes the request, runs the Hyper-V operation, and publishes progress messages and a final result to <hostid>.response.
  5. The backend worker consumes the response, updates the Task (and the VM row live from the result payload), releases the lock, and refreshes the cache.
  6. The frontend’s 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).


Running it

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.


Screenshots

See docs/screenshots/.


License

Licensed under the Apache License 2.0 - see LICENSE.