phpVirtualBox in Go — part 3: implementation and testing roadmap

Mascote LinuxPro e cachorro caramelo cyborg em laboratório de virtualização, com PHP, Go e Vue nos monitores.

Before you start: read about the original project in the article phpVirtualBox: manage VirtualBox from your browser.

In this series: Part 1: phpVirtualBox, Echo and Vue · Part 2: AI and OpenSpec · Part 3: implementation and tests

phpVirtualBox in Go series — part 3 of 3. After analyzing phpVirtualBox and organizing the migration with AI and OpenSpec, it's time to define how to implement and validate the new panel. The goal is not to release a full rewrite all at once: it's to build a sequence of verifiable deliveries.

Delivery 1: prove distribution without touching VMs

Start with an Echo service, a health route, and a Vue page that queries that route. Build the frontend and embed its output using go:embed. At this stage, the panel does not need to know about any virtual machine: the question is whether the API and the interface work in the distributed artifact.

Echo has an official example of embedded resources, and the embed package documentation describes how files are included at compile time. Paths are relative to the package; do not plan to embed files from parent directories with ../. Generate the Vue files before compiling Go.

Código Vue → build do frontend → web/dist
                                   ↓
Código Go + Echo + go:embed → executável do painel

Acceptance criterion: copy only the executable to an empty folder, start it on the loopback and verify the page, the JavaScript files, and the health route. Stop the frontend development server before this test. This demonstrates panel distribution, but it does not yet demonstrate integration with VirtualBox.

Do not embed environment files, credentials, or databases. Those items must remain external to the binary. If the interface uses application routes, define frontend path handling without turning /api/ errors into an HTML page with a success status.

Delivery 2: security and contracts before the real host

Before releasing the panel to others, implement authentication, session, expiration, and authorization. Authentication identifies the user; authorization decides which machines and actions they can access. An authenticated user should not automatically gain access to all hosts.

The API must distinguish invalid input, missing authentication, missing permission, and external service failure. Do not send full SOAP messages or sensitive paths to the browser. Log technical details in a sanitized way on the server and return errors the interface can present.

For the initial lab, configure the service on the loopback address and use test credentials. The production design must specify protected transport, external configuration, secrets management, and access policy.

Delivery 3: real inventory in read-only mode

Implement an adapter dedicated to the integration. The HTTP layer must not know every detail of SOAP: it calls an inventory service that returns the machine representation defined in the specification. This allows testing rules without a real host and, separately, testing the adapter against VirtualBox.

Compare the results with a controlled set of machines: one powered off, one running and, when possible, additional states that your contract commits to support. Record which versions and scenarios were actually verified. Do not use the expression “compatible with all versions” for a test performed in a single environment.

The read-only test must confirm that the query does not trigger change commands. It is not enough for the interface to hide buttons: this restriction must exist in the backend. Also limit the number of queries and set a timeout so connections are not exhausted when a host is unavailable.

A test matrix to track the deliveries

Layer What to check Expected evidence
Distribution API and Vue outside the source tree Executable working without a Node server
HTTP contract Authentication, fields and errors Automated tests per scenario
Adapter Replies, failures, and SOAP timeout Sanitized fixtures and integration tests
Interface Loading, empty, and failure Navigation and visual inspection tests
Operations Initial state, result, and recovery Execution on disposable VMs
Deployment Restart and rollback Procedure reproduced in the lab

Mocks help you reproduce hard-to-trigger failures, but they don't prove compatibility with the real service. Integration tests help validate communication, but they don't replace authorization tests. Each layer answers a different question.

Delivery 4: operations with safety

With the reading validated, I would create separate changes to start machines, request shutdown, monitor operations, and finally manage more sensitive resources. Requesting shutdown from the guest system isn't the same action as cutting power to the VM; the interface and permissions should distinguish those operations.

Long-running operations deserve an explicit task model: identifier, state, start, result, and error. After a timeout, the backend needs to query the actual state before trying again. Automatically retrying an action can duplicate work or produce an unexpected effect.

  • Start and shutdown: validate current state, permissions, and monitoring.
  • Snapshots: treat creation, deletion, and restoration as distinct actions.
  • Disks: differentiate removing a link from deleting the physical file.
  • Multiple hosts: limit failures and permissions by destination.
  • Auditing: register actor, target, and result, without registering credentials.

Before allowing writes, add protection against CSRF when there is cookie-based authentication, object-level authorization, and clear confirmation for destructive operations. The lab should use disposable VMs. A snapshot is not a substitute for a tested backup and restore strategy.

Write timeout does not mean the operation was canceled

The browser may lose the connection while the hypervisor keeps working. Therefore, the panel must distinguish confirmed failure from unknown result. After a timeout, do not offer a button that simply resubmits the same operation without checking what happened.

Modelo de operações: solicitação aceita, execução, resultado confirmado ou reconciliação após perda de comunicação.
Unknown result requires reconciliation. An HTTP interruption does not undo an action already accepted by the hypervisor.

I propose internal states such as accepted, running, succeeded, failed and unknown. They belong to the panel, not an enumeration copied from VirtualBox. Record target, actor, action type, and operation identifier before reporting its acceptance; after restarting, recover the pending records and query the real state.

An idempotency key helps recognize repeated requests, provided it is persisted and tied to the same identity, target, and action. It does not create “exactly once” execution between the database and the hypervisor. If the process crashes between sending and recording the result, reconciliation remains necessary. Not every action has the same tracking mechanism: do not assume that every SOAP call returns a progress object.

Test the failure between stages

  • Request refused before touching the host: no change.
  • Response lost after acceptance: operation flagged for investigation, with no automatic retry.
  • Panel restart during execution: recoverable record and reconciled result.
  • User loses permission: new queries and actions remain subject to authorization.
  • Host unreachable: isolated failure, without indefinitely blocking other requests.

Release criteria per delivery

Delivery Only proceed when
Executable Vue and API work in an empty directory; non-existent /api/ routes do not return the SPA HTML.
Security 401/403 are tested and prevent SOAP calls; errors do not reveal internal data.
Inventory GET /api/v1/vms matches the configured host and does not change VMs.
Writing Per-object authorization, tracking, and unknown-outcome scenarios work in disposable VMs.
Deployment Backup, restart, and controlled rollback were rehearsed.

In the builds, preserve the frontend lockfiles and the Go dependencies; record the commands and environment that produced the artifact. To detail the distribution, see Vue and Go with embed and Docker.

Panel rollback is not machine rollback

Reverting to the previous executable does not undo a removed snapshot nor restore a deleted disk. Before the switch, suspend new writes, track or reconcile ongoing operations, and confirm the compatibility of the persisted data. Reactivate only one write controller. The old panel should only come back after this verification, not as a blind attempt to resolve an error.

Licensing and deployment: two steps that cannot be left for the end

The phpVirtualBox license file indicates GNU GPL version 3. Rewriting in another language is not a reason to ignore the license of the code, visual assets, or dependencies used. Record the origin of the reused material and assess the applicable obligations before distributing the new project. Original project license.

During deployment, keep the possibility of returning to the previous panel, but do not let two controllers run concurrent changes on the same machine. Start with queries, compare the results, and progressively release operations. If the need is to change hypervisor, that is a different job: the ova2xva guide discusses converting machines to XCP-ng, not replacing the VirtualBox management interface.

Series conclusion: distributing is simple; operating requires evidence

As an engineering project, the proposal makes sense if it solves a concrete problem: simpler distribution, better interface, clear API, and predictable maintenance. phpVirtualBox offers a valuable reference, but the new panel would need to earn trust with tests and compatibility, not just with a different language.

The first milestone should be small and demonstrable: an executable with Echo and Vue embedded, authentication, and a real VM inventory in read-only mode. AI and OpenSpec can speed up the path to it — as long as the tasks are verifiable and no one confuses generated code with validated integration.

In this series: Part 1: phpVirtualBox, Echo and Vue · Part 2: AI and OpenSpec · Part 3: implementation and tests