diff --git a/.github/workflows/deploy_gh_page.yaml b/.github/workflows/deploy_gh_page.yaml new file mode 100644 index 0000000..5e8a837 --- /dev/null +++ b/.github/workflows/deploy_gh_page.yaml @@ -0,0 +1,48 @@ +name: Deploy Docs + +on: + # Trigger the workflow every time you push to the `gh-pages` branch + # Using a different branch name? Replace `gh-pages` with your branch's name + push: + branches: [ gh-pages ] + # Allows you to run this workflow manually from the Actions tab on GitHub. + workflow_dispatch: + inputs: + branch: + description: 'Branch to use' + required: true + default: 'gh-pages' + +# Allow this job to clone the repo and create a page deployment +permissions: + contents: read + pages: write + id-token: write + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout your repository using git + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Install, build, and upload your site + env: + SITE_URL: ${{ env.SITE_URL }} + BASE: ${{ github.event.repository.name }} + uses: withastro/action@v4 + with: + path: docs/ # The root location of your Astro project inside the repository. (optional) + + deploy: + needs: build + runs-on: ubuntu-latest + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 diff --git a/docs/404.html b/docs/404.html index f1aceb0..e6a68ca 100644 --- a/docs/404.html +++ b/docs/404.html @@ -1,4 +1,4 @@ - 404 | vJailbreak + Skip to content
Skip to content
+ Skip to content

Overview

Below is high level architecture of how vJailbreak works. vJailbreak runs +in a virtual machine in the target OpenStack environment. vJailbreak connects with VMware environment via vSphere APIs, using the VDDK library for the Standard copy method only. vJailbreak Accelerated Copy and Storage-Accelerated Copy transfer disk data without requiring VDDK. It also uses the OpenStack SDK to interact with the OpenStack environment and perform the necessary provisioning operations including creation of volumes, VMs.

+

vJailbreak Architecture

\ No newline at end of file diff --git a/docs/introduction/components/index.html b/docs/architecture/components/index.html similarity index 52% rename from docs/introduction/components/index.html rename to docs/architecture/components/index.html index bbca492..567d256 100644 --- a/docs/introduction/components/index.html +++ b/docs/architecture/components/index.html @@ -1,4 +1,4 @@ - vJailbreak Components | vJailbreak + Skip to content
Skip to content

vJailbreak Components

vJailbreak is composed of several key components that work together to facilitate the migration of virtual machines from VMware environments to OpenStack-compliant clouds. Below is an overview of each component and its role in the migration process.

+

Components

Below is an overview of each component and its role in the migration process.

v2v-helper

The v2v-helper is the main application responsible for executing the migration process. It is designed to run as a pod within the vJailbreak virtual machine (VM) in the target OpenStack environment.

UI

@@ -145,4 +159,4 @@ starlight-tabs:where(.astro-esqgolmp){display:block}.tablist-wrapper:where(.astr

The migration-controller is a Kubernetes controller that schedules and manages the migration tasks. It ensures that migrations are executed efficiently and in accordance with the defined policies.

v2v-cli

The v2v-cli is a command-line interface tool that can initiate the migration process. While it is available, it is not required in the current version of vJailbreak, as the primary interface is the UI.

-

By understanding these components, users can better appreciate the architecture and functionality of vJailbreak, enabling them to effectively manage and execute VM migrations.

\ No newline at end of file +

By understanding these components, users can better appreciate the architecture and functionality of vJailbreak, enabling them to effectively manage and execute VM migrations.

\ No newline at end of file diff --git a/docs/architecture/vjailbreak-vm/index.html b/docs/architecture/vjailbreak-vm/index.html new file mode 100644 index 0000000..bad56a1 --- /dev/null +++ b/docs/architecture/vjailbreak-vm/index.html @@ -0,0 +1,156 @@ + vJailbreak VM | vJailbreak + Skip to content

vJailbreak VM

As part of the deployment process, vJailbreak is shipped as a virtual machine (VM) that can be deployed in the target OpenStack environment. The VM is configured with the necessary resources, including sufficient memory and processing power, to handle the migration tasks efficiently.

+

The vJailbreak VM comes with a k3s deployment that runs various components as pods. The components include the v2v-helper, UI, and migration-controller as described in the components.

+

The vJailbreak VM can be scaled out to handle multiple migration tasks concurrently. This is achieved by deploying additional vJailbreak VMs in the target OpenStack environment. For more information on scaling vJailbreak, see the scaling guide.

+

vJailbreak VM

\ No newline at end of file diff --git a/docs/archives/release_notes/index.html b/docs/archives/release_notes/index.html new file mode 100644 index 0000000..e1088c8 --- /dev/null +++ b/docs/archives/release_notes/index.html @@ -0,0 +1,1099 @@ + Archived Releases | vJailbreak + Skip to content

Archived Releases

v0.1.2

+

What’s Changed

+ +

Known Issues

+
    +
  • If the VM to be migrated has an LV spanning multiple physical devices used as boot volume unless both are mounted simultaneously to the OS-VM, vJailbreak cannot detect whether it is bootable, or not. (issue link)
  • +
  • If a user turns on a VM on VCenter after migration and tries to migrate it again, the migration object will not be created. In this case, the user should delete the VM from PCD/Openstack before trying the migration again. This error will be pushed up the stack for visibility.
  • +
+

New Contributors

+ +

Full Changelog: https://github.com/platform9/vjailbreak/compare/v0.1.1…16.01

+

v0.1.3

+

What’s Changed

+ +

Full Changelog: https://github.com/platform9/vjailbreak/compare/v0.1.2…v0.1.3

+

v0.1.4

+

What’s Changed

+ +

New Contributors

+ +

Full Changelog: https://github.com/platform9/vjailbreak/compare/v0.1.3…v0.1.4

+

v0.1.5

+

What’s Changed

+ +

New Contributors

+
    +
  • @damian-pf9
  • +
  • @anmolsachan
  • +
  • @bhavin192
  • +
+

Full Changelog: https://github.com/platform9/vjailbreak/compare/v0.1.4…v0.1.5

+

v0.1.6

+

What’s Changed

+ +

New Contributors

+ +

Full Changelog: https://github.com/platform9/vjailbreak/compare/v0.1.5…v0.1.6

+

v0.1.7

+

What’s Changed

+ +

Full Changelog: https://github.com/platform9/vjailbreak/compare/v0.1.6…v0.1.7

+

v0.1.8

+

What’s Changed

+ +

New Contributors

+ +

Full Changelog: https://github.com/platform9/vjailbreak/compare/v0.1.6…v0.1.8

+

v0.1.9

+

What’s Changed

+ +

New Contributors

+ +

Full Changelog: https://github.com/platform9/vjailbreak/compare/v0.1.8…v0.1.9

+

v0.1.10

+

What’s Changed

+ +

Full Changelog: https://github.com/platform9/vjailbreak/compare/v0.1.9…v0.1.10

+

v0.1.11

+

What’s Changed

+ +

Full Changelog: https://github.com/platform9/vjailbreak/compare/v0.1.10…v0.1.11

+

v0.1.12

+

What’s Changed

+ +

Full Changelog: https://github.com/platform9/vjailbreak/compare/v0.1.11…v0.1.12

+

v0.1.13

+

What’s Changed

+ +

Usability Enhancements and Stability Improvements

+
    +
  • +

    Rolling Conversion (Beta Availability)
    +The Rolling Conversion feature is now available as a beta release.

    +
  • +
  • +

    Pre-Packaged vJailbreak Image
    +A new vJailbreak image is now available, containing all required components pre-installed. This enhancement eliminates the need for internet access during installation, making it suitable for restricted network environments.

    +
  • +
  • +

    Support for Custom VirtIO Drivers
    +Users may now upload VirtIO drivers to a designated path. If present, these drivers will be utilized during the migration process. If absent, the system will default to downloading the necessary drivers from the internet, providing flexibility based on deployment conditions. https://github.com/platform9/vjailbreak/blob/main/docs/src/content/docs/guides/virtio_doc.md

    +
  • +
  • +

    Resolution of Pod Eviction Due to Disk Pressure
    +An issue causing pod eviction under disk pressure conditions has been resolved. This fix improves the reliability and stability of workloads during extended migration operations.

    +
  • +
  • +

    Enhanced Debug Logging
    +Debug logs have been improved to provide more comprehensive and structured output

    +
  • +
+

Full Changelog: https://github.com/platform9/vjailbreak/compare/v0.1.12…v0.1.13

+

v0.1.14

+

What’s Changed

+ +

Full Changelog: https://github.com/platform9/vjailbreak/compare/v0.1.13…v0.1.14

+

v0.1.15

+

What’s Changed

+ +

Full Changelog: https://github.com/platform9/vjailbreak/compare/v0.1.14…v0.1.15

+

v0.2.0

+

What’s Changed

+ +

New Contributors

+ +

Full Changelog: https://github.com/platform9/vjailbreak/compare/v0.1.15…v0.2.0

+

v0.2.1

+

What’s Changed

+ +

Full Changelog: https://github.com/platform9/vjailbreak/compare/v0.2.0…v0.2.1

+

v0.3.0

+

What’s Changed

+ +

New Contributors

+ +

Full Changelog: https://github.com/platform9/vjailbreak/compare/v0.2.1…v0.3.0

+

v0.3.1

+

What’s Changed

+ +

Full Changelog: https://github.com/platform9/vjailbreak/compare/v0.3.0…v0.3.1

+

v0.3.2

+

What’s Changed

+ +

Full Changelog: https://github.com/platform9/vjailbreak/compare/v0.3.1…v0.3.2

+

v0.3.3

+

What’s Changed

+ +

Full Changelog: https://github.com/platform9/vjailbreak/compare/v0.3.2…v0.3.3

+

v0.3.4

+

What’s Changed

+ +

Full Changelog: https://github.com/platform9/vjailbreak/compare/v0.3.3…v0.3.4

+

v0.3.5

+

What’s Changed

+ +

New Contributors

+ +

Full Changelog: https://github.com/platform9/vjailbreak/compare/v0.3.4…v0.3.5

+

v0.3.6

+

What’s Changed

+ +

New Contributors

+ +

Full Changelog: https://github.com/platform9/vjailbreak/compare/v0.3.5…v0.3.6

+

v0.3.7

+

What’s Changed

+ +

New Contributors

+ +

Full Changelog: https://github.com/platform9/vjailbreak/compare/v0.3.6…v0.3.7

+

v0.3.8

+

What’s Changed

+ +

Full Changelog: https://github.com/platform9/vjailbreak/compare/v0.3.7…v0.3.8

+

title: v0.4.0 +description: Release Notes for v0.4.0

+

What’s Changed

+ +

New Contributors

+ +

Full Changelog: https://github.com/platform9/vjailbreak/compare/v0.3.8…v0.4.0

+
+

title: v0.4.1 +description: Release Notes for v0.4.1

+

What’s Changed

+ +

Full Changelog: https://github.com/platform9/vjailbreak/compare/v0.4.0…v0.4.1

+

Upgrade Guide: v0.4.0 → v0.4.1

+

Users upgrading from v0.4.0 to v0.4.1 need to follow the steps below:

+
Step 1: Update v0.4.0 Container Images
+

In your existing v0.4.0 setup, update the deployment images to pull the latest v0.4.0 containers and set imagePullPolicy to Always using the following command:

+
Terminal window
kubectl patch deployment migration-controller-manager -n migration-system --type='json' \
-p='[{"op":"replace","path":"/spec/template/spec/containers/0/imagePullPolicy","value":"Always"}]' && \
kubectl patch deployment migration-vpwned-sdk -n migration-system --type='json' \
-p='[{"op":"replace","path":"/spec/template/spec/containers/0/imagePullPolicy","value":"Always"}]' && \
kubectl patch deployment vjailbreak-ui -n migration-system --type='json' \
-p='[{"op":"replace","path":"/spec/template/spec/containers/0/imagePullPolicy","value":"Always"}]'
+
Step 2: Restart Deployments
+

Restart the deployments to ensure the updated images are pulled and running:

+
Terminal window
kubectl rollout restart deployment -n migration-system
+
Step 3: Follow Upgrade Steps from the public documentation
+

v0.4.2

+

What’s Changed

+ +

Full Changelog: https://github.com/platform9/vjailbreak/compare/v0.4.1…v0.4.2

+

v0.4.3

+

What’s Changed

+ +

Full Changelog: https://github.com/platform9/vjailbreak/compare/v0.4.2…v0.4.3

+

Release Notes & Known Issues

+
    +
  • Static IP and interface name preservation is not supported when the target network has DHCP disabled.
  • +
  • For Windows VMs, static IP configurations are automatically converted to DHCP. However, the original source IP is retained on the destination VM to maintain network connectivity.
  • +
  • The VMware Tools removal script may leave behind some residual artifacts. Refer to the documentation for more details.
  • +
  • Agent scaling is not supported for vJailbreak VMs operating on L2 networks.
  • +
+

v0.4.4

+

What’s Changed

+ +

New Contributors

+ +

Full Changelog: https://github.com/platform9/vjailbreak/compare/v0.4.3…v0.4.4

+

Highlights

+
    +
  • VMware Tools Removal: The removal script now removes all residual files and folders. A small number of registry entries may remain depending on the Windows version, but these are harmless and can be removed manually if needed.
  • +
  • Migration Profiles: Pre-defined key-value pairs can now be applied to a migrated VM’s root volume in OpenStack. Profiles are supported for both Windows and Linux guests.
  • +
  • L2 Network Scaling Support: Scaling of agents in L2 network (Simple Networking) is now supported.
  • +
  • Persistent Network Interface for Windows (2016+): Static network configurations including interface name, IP, MAC, and gateway are now preserved across migration for Windows Server 2016 and above.
  • +
  • FC Support for Storage Area Migration (SAM): Fibre Channel support has been added for SAM workflows, verified with NetApp FC targets.
  • +
+

Known Limitations

+
    +
  • Windows 2012 and Earlier — Persist Network: The persistent network interface option is not supported on Windows 2012 and below. IP and MAC addresses are preserved, but the interface name and gateway may get lost, and the NIC configuration becomes dynamic after migration.
  • +
  • Migration Profiles — Upgrade Path: The two default profiles (Windows and Linux) are auto-created on fresh installations only. Users upgrading from lower versions to v0.4.4 will need to create these profiles manually.
  • +
  • VMware Tools Registry Entries: Depending on the Windows version, a small number of VMware Tools registry entries (listed in public docs) may not be removed by the uninstall script. These are harmless but can be removed manually if desired.
  • +
  • FC SAM — Verified Target: FC support for SAM has been verified against NetApp FC targets. Compatibility with other FC storage vendors is not yet validated.
  • +
+

v0.4.5

+

What’s Changed

+ +

Full Changelog: https://github.com/platform9/vjailbreak/compare/v0.4.4…v0.4.5

\ No newline at end of file diff --git a/docs/concepts/cluster-conversion/index.html b/docs/concepts/cluster-conversion/index.html new file mode 100644 index 0000000..9354eac --- /dev/null +++ b/docs/concepts/cluster-conversion/index.html @@ -0,0 +1,168 @@ + Cluster Conversion | vJailbreak + Skip to content

Cluster Conversion

vJailbreak offers two primary migration options

+

VM Migration

+

The default option, where a user selects a set of VMs within VMware, across clusters and migrates them. This keeps +the source VM intact and creates a new copy of them into OpenStack/PCD.

+

Cluster Conversion

+

The second option, where a user selects a cluster within VMware and migrates it to a PCD cluster. This converts not only the VMs within the cluster but also the individual ESXi into PCD Hypervisor. +This is done through ‘evacuating’ the ESXi host and converting it into PCD hypervisor through IPMI/PXE, in the current version of vJailbreak, Canonical MAAS is used to achieve that.

+

See below the animation of the cluster conversion process.

+

Cluster conversion

+

Choose a VMware cluster and select the destination cluster in PCD. Once the clusters are selected, you will be shown the VMware ESXi hosts and the corresponding VMs. The VM portion of the wizard is similar/same as that in the VM Migration section.

+

The ESXi portion of the wizard is different from the VM portion and deals with options on how each server will be configured as a hypervisor in the PCD cluster. The most important aspect is the Host configuration, the information is pulled from the PCD cluster blueprint. The host config determines what NICs are associated with what networks. See PCD cluster blueprint docs for more details.

+

The process of converting the ESXi into PCD hypervisor is simple, each ESXi host is put into maintenance mode which migrates all the running VMs into other ESXi hosts. Then the ESXi host is converted into PCD hypervisor and the VMs are migrated into the PCD hypervisor. This process is repeated for all the ESXi hosts in the cluster.

+

The detailed configuration steps are described in cluster conversion guide.

+

See below the diagram of the cluster conversion setup.

+

Cluster conversion

+
\ No newline at end of file diff --git a/docs/concepts/credential-management/index.html b/docs/concepts/credential-management/index.html new file mode 100644 index 0000000..a59ed07 --- /dev/null +++ b/docs/concepts/credential-management/index.html @@ -0,0 +1,381 @@ + Credential Management | vJailbreak + Skip to content

Credential Management

Before you start using vJailbreak, you need to provide credentials for both the VMware vCenter and OpenStack/PCD environments.

+

VMware vCenter Credentials

+

VMware vCenter credentials are required to connect to the vCenter server and retrieve information about the virtual machines you want to migrate.

+

The credentials should have enough permissions to retrieve information about the virtual machines you want to migrate and if you are looking to the cluster conversion, the credentials should have enough permissions to retrieve information about the cluster, put host into maintenance mode, etc (see Cluster Conversion for more details).

+

The VMware credentials needs vCenter Server IP address or vCenter Server name, username and password. +The credentials also take the Datacenter name and the VMs, Hosts being worked on would be restricted to the Datacenter specified in the credentials.

+

Some VMware environments may be using self signed certificates, in such cases, you would need to “Allow insecure connection” option in the credentials.

+

OpenStack/PCD Credentials

+

OpenStack/PCD credentials are required to create VMs inside the OpenStack/PCD environment. The credentials are supplied via the openstack.rc file that is available in the PCD environment.

+

To copy the content of the openstack.rc file, you should navigate to Settings > API Access > pcdctl RC section.

+

If using PCD we recommend toggling the “Is PCD credentials” option. This will automatically indicate to vJailbreak that the credentials are for PCD and would use PCD Cluster as a destination for different migrations.

+

For non PCD environment the openstack.rc file will be available as part of various distribution and documentation. The openstack.rc file is typically used for any automation with the OpenStack CLI.

+
+

Password-Based Authentication

+

Required Variables

+

vJailbreak requires the following environment variables to be present in your admin RC file. All of these variables are mandatory and the migration will fail if any are missing:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
VariableDescriptionExample
OS_AUTH_URLOpenStack Keystone authentication URLhttps://keystone.example.com:5000/v3
OS_USERNAMEOpenStack username with admin privilegesadmin
OS_PASSWORDPassword for the OpenStack useryour-secure-password
OS_REGION_NAMEOpenStack region where VMs will be createdRegionOne
OS_PROJECT_NAMEOpenStack project name for VM deploymentservice
OS_PROJECT_DOMAIN_NAMEOpenStack project domain nameDefault
OS_AUTH_TYPEOpenStack authentication typepassword
OS_IDENTITY_API_VERSIONOpenStack identity API version3
OS_USER_DOMAIN_NAMEOpenStack user domain nameDefault
OS_INTERFACEOpenStack API interface typepublic
+

Optional Variables

+ + + + + + + + + + + + + + + +
VariableDescriptionExample
OS_INSECURESkip SSL certificate verificationtrue or false
+

User Permissions

+

The user specified in OS_USERNAME must have administrative privileges in OpenStack to:

+
    +
  • Create and manage virtual machines
  • +
  • Access network and storage resources
  • +
  • Create and manage volumes
  • +
  • Access compute, network, and storage services
  • +
+

The project specified in OS_PROJECT_NAME must exist and have sufficient quotas for the VMs being migrated.

+

Example Admin RC File (Password-Based)

+
Terminal window
export OS_USERNAME=<your-username>
export OS_PASSWORD=<your-password>
export OS_AUTH_URL=https://<fqdn of the openstack/pcd>/keystone/v3
export OS_AUTH_TYPE=password
export OS_IDENTITY_API_VERSION=3
export OS_REGION_NAME=region-1
export OS_USER_DOMAIN_NAME=Default
export OS_PROJECT_DOMAIN_NAME=Default
export OS_PROJECT_NAME=service
export OS_INTERFACE=public
export OS_INSECURE=false
+
+

Token-Based Authentication

+

In addition to password-based authentication, vJailbreak also supports token-based authentication for OpenStack/PCD environments.

+

With token-based authentication, access is provided using a Keystone authentication token instead of a username and password. The token must be generated in your OpenStack environment and supplied to vJailbreak via the openstack.rc file.

+

This authentication method is useful in environments where password-based authentication is restricted or where short-lived credentials are preferred.

+

Required Variables

+

When using token-based authentication, the following environment variables must be present in the RC file:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
VariableDescriptionExample
OS_AUTH_URLOpenStack Keystone authentication URLhttps://keystone.example.com:5000/v3
OS_IDENTITY_API_VERSIONOpenStack identity API version.3
OS_REGION_NAMEOpenStack region where VMs will be createdRegionOne
OS_PROJECT_NAMEOpenStack project name for VM deploymentservice
OS_PROJECT_DOMAIN_NAMEOpenStack project domain nameDefault
OS_INTERFACEOpenStack API interface typepublic
OS_AUTH_TOKENOpenstack authentication token<keystone-auth-token>
OS_AUTH_TYPEOpenstack authentication typetoken
+

Optional Variables

+ + + + + + + + + + + + + + + +
VariableDescriptionExample
OS_INSECURESkip SSL certificate verificationtrue or false
+

Example Admin RC File (Token-Based)

+
Terminal window
export OS_AUTH_URL=https://<fqdn of the openstack/pcd>/keystone/v3
export OS_IDENTITY_API_VERSION=3
export OS_REGION_NAME=<region-name>
export OS_PROJECT_NAME=<project-name>
export OS_PROJECT_DOMAIN_NAME=Default
export OS_INTERFACE=public
export OS_AUTH_TOKEN=<keystone-auth-token>
export OS_AUTH_TYPE=token
+

Notes on Token-Based Authentication

+
    +
  • The OS_AUTH_TOKEN must be generated in your OpenStack environment and must be valid at the time of migration.
  • +
  • Token expiration is controlled by Keystone. If the token expires, the migration will fail and a new token must be provided.
  • +
  • The openstack.rc must contain both the Domain and the Project/Tenant information. When using the OpenStack credentials, the Domain and Project/Tenant information is used as the destination domain and project/tenant for the OpenStack/PCD environment.
  • +
+

Credential Revalidation

+

Revalidation re-runs the same flow that runs on credential creation. It is triggered automatically by the controller and can also be initiated manually from the UI.

+

img1 +img1

+

What Revalidation Does

+

When a credential is revalidated, vJailbreak performs two steps:

+
    +
  1. Authentication check — verifies the credentials are still valid against the target environment (vCenter for VMware, Keystone for OpenStack/PCD). If authentication fails, the credential is marked invalid and migrations using it will be blocked.
  2. +
  3. Resource resync — if authentication succeeds, vJailbreak re-fetches the full inventory of resources tied to that credential.
  4. +
+

For VMware credentials, revalidation refreshes:

+
    +
  • Virtual Machines (CPU, memory, disks, networks, datastores, power state, guest IPs)
  • +
  • vCenter clusters and ESXi hosts
  • +
  • Stale VMs, clusters, and hosts removed from the source are also pruned from vJailbreak
  • +
+

For OpenStack/PCD credentials, revalidation refreshes:

+
    +
  • Compute flavors
  • +
  • Networks
  • +
  • Volume types
  • +
  • For PCD credentials: PCD clusters, hosts, and host configs
  • +
+

When Revalidation Runs

+
    +
  • On credential creation — initial validation + resource fetch.
  • +
  • Periodically — the controller reconciles every 1 hour by default to keep resources in sync. Default time to requeue creds can be changed in global setting page.
  • +
  • Manually — click the refresh button on the Credentials page to trigger an immediate revalidation.
  • +
+

Important: VMware IP Discovery and Credential Revalidation

+

VMware Tools and guest IP addresses can take some time to appear in vCenter after powering on a VM or making network changes.

+

Best Practice:

+
    +
  • Make all necessary configuration changes to your VMs in the vCenter UI first.
  • +
  • Wait until the IP addresses are clearly visible on the VM summary page in vCenter.
  • +
  • Only then click Add Credential or Revalidate in vJailbreak.
  • +
+

Revalidating too early may result in missing IP information, which can prevent proper IP preservation on the destination and cause migration issues.

+
+

Note: vJailbreak will show a warning for VMs where network interfaces are detected but IPs could not be discovered.

+
\ No newline at end of file diff --git a/docs/concepts/migration-options/index.html b/docs/concepts/migration-options/index.html new file mode 100644 index 0000000..df4b0c9 --- /dev/null +++ b/docs/concepts/migration-options/index.html @@ -0,0 +1,244 @@ + Migration Options | vJailbreak + Skip to content

Migration Options

vJailbreak provides a number of options to optimize and control the migration process. These options are available in the migration wizard under “Migration Options”.

+

Copy options

+

There are several options available to control how data is copied during migration.

+

Data copy method

+

Determines how the data copy is done

+
    +
  • +

    Copy live VMs, then power off - This option copies the data from the live VMs to the OpenStack/PCD volumes. vJailbreak uses CBT (Change Block Tracking) to copy the data that is dirtied. Then, the VMs are powered off and the remaining changed blocks are copied to the OpenStack/PCD volumes (see cut over options below)

    +
  • +
  • +

    Power off VMs, then copy - This option powers off the VMs and then copies the data to the OpenStack/PCD volumes. There is no CBT involved in this case, it will be the faster option, but will impact the uptime of the application. Power off VMs are supported but may need user input to provide the IP address, Operating System type during migration.

    +
  • +
+

Storage copy method

+

Determines the underlying mechanism used to transfer disk data.

+
    +
  • +

    Normal - Uses the traditional network-based copy via VMware’s NFC protocol. Data is transferred from ESXi hosts to OpenStack Cinder volumes over the network. This method is limited to approximately 1 Gbps per VMDK due to NFC protocol constraints. Requires VDDK.

    +
  • +
  • +

    Storage-Accelerated Copy - Leverages storage array-level XCOPY operations for dramatically faster migrations. Instead of copying data over the network, this method offloads the copy to the storage array itself. Does not require VDDK. Requires:

    +
      +
    • Supported storage array (Pure Storage or NetApp)
    • +
    • Both VMware datastores and OpenStack Cinder backed by the same array
    • +
    • ESXi SSH access configured
    • +
    • VMs must be powered off during copy (cold migration only)
    • +
    +
  • +
  • +

    vJailbreak Accelerated Copy (default) - Attaches frozen snapshot disks directly to a Proxy VM running in vCenter (using VMware’s hot-add mechanism) and streams data over NBD to the destination. Works with any datastore type (NFS, VMFS, vSAN) and does not require a shared storage array. Does not require VDDK. Requires:

    +
      +
    • A registered Proxy VM in Ready state (Linux VM with qemu-nbd installed)
    • +
    • SSH access from vJailbreak to the Proxy VM
    • +
    • VMs must be powered off during copy (cold migration only; hot/live copy is not supported)
    • +
    +
  • +
+ + + +

See vJailbreak Accelerated Copy for configuration instructions.

+

See Storage-Accelerated Copy for detailed configuration instructions.

+

Data copy start time

+

As the name implies, determines when the copy operation should start, typically used to start the migration during off-peak hours.

+

Cutover options

+

There are 3 options available

+
    +
  • +

    Cutover during time window - This option allows the user to specify a time window during which the VM would be powered off and the corresponding OpenStack/PCD VM would be configured and powered on. This window also involves copy of any remaining changed blocks to the OpenStack/PCD volumes since the last time the block were copied.

    +
  • +
  • +

    Cutover immediately after data copy - This option is simpler and follows the copy operation immediately after the copy is complete. This option is recommended for applications that have flexible uptime requirements and can be powered off anytime during the migration.

    +
  • +
  • +

    Admin initiated cutover - This option allows the user to manually trigger the cutover operation after the data copy is complete.

    +
  • +
+

Data Copy Method Workflows

+

1. Power off Live VM, then Copy

+

Immediately Cutover

+

The VM is powered off on the source before the initial copy begins. Once the initial copy completes, the migration immediately performs the final sync and powers on the VM at the destination.

+

Cutover During Time Window

+

The VM is powered off on the source before the initial copy begins. After the initial copy completes, the migration waits for the specified time window. When the time window arrives, it performs the final sync and powers on the VM at the destination.

+

Admin Initiated Cutover

+

The VM is powered off on the source before the initial copy begins. After the initial copy completes, the migration waits for manual intervention. When the admin initiates cutover, the migration performs the final sync and powers on the VM at the destination.

+
+

Note: When “Power off VMs then copy” is selected, cutover timing options (immediate, time window, or admin-initiated) are disabled in the UI.

+
+

2. Copy Live VM, then Power off

+

Immediately Cutover

+

The initial copy is performed while the VM remains running on the source. Once the initial copy completes, the VM is powered off on the source, and the migration immediately performs the final sync and powers on the VM at the destination.

+

Cutover During Time Window

+

The initial copy is performed while the VM remains running on the source. After the initial copy completes, the migration waits for the specified time window. When the time window arrives, the VM is powered off on the source, the final sync is performed, and the VM is powered on at the destination.

+

Admin Initiated Cutover

+

The initial copy is performed while the VM remains running on the source. After the initial copy completes, the migration waits for manual intervention. When the admin initiates cutover, the VM is powered off on the source, the final sync is performed, and the VM is powered on at the destination.

+

Post migration options

+

Post migration script

+

A script to be executed after the migration is complete. This script is optional and can be used to perform post migration tasks such as starting the application, updating the application configuration, adding VM to domain controller etc.

+

Rename VM

+

An optional parameter. Renames the source VM in VMware to have a specific suffix, good option to indicate a VM is migrated to OpenStack/PCD. The default suffix is “_migrated_to_pcd”.

+

Move to folder

+

An optional parameter. Moves the source VM in VMware to a specific folder, good option to group migrated VMs and keep it out of the hands of the user.

+

Network persistence

+

Persist source network interfaces

+

When enabled, vJailbreak preserves the source VM’s network interface names on the destination VM (for example, eth0 or ens3). This prevents breaking guest configurations—such as firewall rules or legacy scripts—that depend on specific interface names.

+

For statically configured interfaces, vJailbreak also preserves routes defined in configuration files, ensuring the guest retains its original network behavior after migration.

+

To enable this behavior, check Persist source network interfaces under Migration Options in the migration form.

+

For more information, refer to the Network Persistence documentation.

\ No newline at end of file diff --git a/docs/concepts/network-persistence/index.html b/docs/concepts/network-persistence/index.html new file mode 100644 index 0000000..be41cf1 --- /dev/null +++ b/docs/concepts/network-persistence/index.html @@ -0,0 +1,277 @@ + Network Persistence | vJailbreak + Skip to content

Network Persistence

Network Persistence Post-Migration

+

This document details the mechanism for ensuring network interface persistence following a virtual machine migration for both Linux and Windows operating systems.

+

Prerequisites

+

Network persistence is only applied when the “Persist source network interfaces” option is enabled in the migration form. This option must be selected during migration configuration to ensure that network interface names are preserved on the destination VM.

+

Linux Network Persistence

+

The Linux network persistence mechanism operates on the first boot post-migration to restore network configuration to its pre-migration state.

+

Persistence Mechanism

+
    +
  • Statically Configured Interfaces: The original names of network interfaces that were statically configured before migration are preserved.
  • +
  • DHCP Configured Interfaces: Interfaces configured via DHCP may be renamed to a consistent pattern: vjb<random_number>.
  • +
  • Interface with No Configuration: Interfaces that had no configuration (e.g., were left unconfigured) will remain untouched but the name may change.
  • +
+

Supported Distributions

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
DistributionExpectedVerified
UbuntuSupportedYes
OpenSuseSupportedYes
RHELSupportedYes
CentOSSupportedYes
RockySupportedNo
+

Windows Network Persistence

+

The Windows network persistence mechanism operates on the first boot post-migration to restore network configuration to its pre-migration state.

+

Persistence Mechanism

+

The network persistence script runs on the first boot post-migration. Its primary function is to restore the network configuration to its pre-migration state by performing the following actions:

+
    +
  • +

    Windows Server 2016 and Above:

    +
      +
    • Statically Configured Interfaces: The original interface name, IP address, and gateway from the source are persisted on the destination, ensuring continuous network connectivity.
    • +
    • DHCP Configured Interfaces: Interfaces configured via DHCP are renamed to a consistent pattern: vjb_<random_number>.
    • +
    +
  • +
  • +

    Windows Server 2012 and below:

    +
      +
    • Statically Configured Interfaces: The IP address from the source interface is preserved, but the interface configuration is converted to DHCP. The interface name and gateway are not preserved.
    • +
    +
  • +
+

Supported Versions

+

The network persistence mechanism has been validated and is supported on the following Windows Server operating systems:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
VersionExpectedVerified
Windows Server 2016SupportedYes
Windows Server 2019SupportedYes
Windows Server 2022SupportedYes
Windows Server 2025SupportedYes
Windows Server 2008UnsupportedNo
Windows Server 2012UnsupportedNo
+ +

User Guidance for Virtio Installation

+

The Windows Virtual Machine (VM) will undergo multiple reboots during the installation of necessary virtio drivers post-migration.

+ +

Important Considerations

+ + + +
\ No newline at end of file diff --git a/docs/concepts/network-storage-mapping/index.html b/docs/concepts/network-storage-mapping/index.html new file mode 100644 index 0000000..85d0bc2 --- /dev/null +++ b/docs/concepts/network-storage-mapping/index.html @@ -0,0 +1,182 @@ + Network & Storage Mapping | vJailbreak + Skip to content

Network & Storage Mapping

A large scale VMWare migration may require a large number of VMs to be migrated. In such cases, it is recommended to use network and storage mapping to optimize the migration process and keep both the environments running at the same time while migration is progressing. Network and Storage mapping are part of the migration wizard and are required for the migration to proceed.

+

Network Mapping

+

vJailbreak recognizes the different types of networks in VMware and OpenStack/PCD environments. +We recommend to create the OpenStack/PCD networks in advance such that some multi-VM applications can continue to run while the migration is in progress.

+ +

VMware Network Types

+

For VMware environment, the networks are typically of type vSphere Standard Port Group or vSphere Distributed Port Group. Currently, vJailbreak supports only these two types of networks and not NSX created networks.

+

Typical VMware networks use VLAn configuration to define the network properties.

+

OpenStack/PCD Network Types

+

For OpenStack/PCD environment, there are many more choices, refer to the PCD and OpenStack documentation on the various provider, physical and virtual networks.

+

vJailbreak expects the user to create the OpenStack/PCD networks in advance. In a typical environment for each VMware network an OpenStack/PCD physical network is created that use the corresponding VLAN of the Port Group or Distributed Port Group.

+

Storage Mapping

+

Unlike network mapping, storage mapping is different. For networking, interconnectivity is the key, for storage it is not. During migration vJailbreak ‘copies’ the data over from VMware to OpenStack/PCD and this can be used to your advantage as needed.

+

VMware Storage Types

+

For VMware environments, the storage is typically of type vSphere Datastore with either VMFS or NFS as the storage type. vJailbreak supports both of these storage types.

+

vJailbreak also supports RDM disks for cold migrations to Platform9 Private Cloud Director (PCD) when the SAN-backed LUN can be represented as a target volume and the required storage prerequisites are met. See the RDM migration guide for the step-by-step workflow. This support is not currently available for OpenStack-targeted migrations.

+

There are a few exceptions and unsupported configurations.

+
    +
  • vJailbreak does not support vCenter Storage Policies or vVols at the time of writing this document.
  • +
  • vJailbreak does not preserve snapshots during the copy.
  • +
  • For Rolling Conversion or other vMotion-based workflows, RDM disks in physical mode remain unsupported.
  • +
+

OpenStack/PCD Volume Types

+

For OpenStack/PCD environment, the volumes are created using ‘Cinder’ and can be of type NFS iSCSI, FiberChannel. vJailbreak supports all of these types of storage with the help of the corresponding OpenStack/PCD Cinder drivers. The Cinder and corresponding volume types must be precreated before the migration starts.

+

Since the migration involves copying the data from VMware to OpenStack/PCD, the storage types can be of different types, for example NFS datastore volume can be copied to iSCSI volume in OpenStack/PCD.

+

Storage-Accelerated Copy

+

For environments where both VMware and OpenStack share the same storage array (Pure Storage or NetApp), vJailbreak supports Storage-Accelerated Copy. This method leverages storage array-level XCOPY operations to dramatically improve migration performance by offloading the data copy to the storage array itself.

+

Instead of copying data over the network (limited to ~1 Gbps per VMDK), Storage-Accelerated Copy performs the copy directly on the storage array at array speeds, which can be considerably faster than normal copy.

+

See Storage-Accelerated Copy for detailed configuration and usage instructions.

+
\ No newline at end of file diff --git a/docs/concepts/storage-accelerated-copy/index.html b/docs/concepts/storage-accelerated-copy/index.html new file mode 100644 index 0000000..cd98973 --- /dev/null +++ b/docs/concepts/storage-accelerated-copy/index.html @@ -0,0 +1,755 @@ + Storage Accelerated Copy | vJailbreak + Skip to content

Storage Accelerated Copy

Storage Accelerated Copy is an advanced data copy method that leverages storage array level XCOPY operations to dramatically improve migration performance. Instead of copying data over the network via the traditional NBD/NFC protocol, this method offloads the data copy to the storage array itself, achieving significantly faster transfer speeds.

+ +

Overview

+

How It Works

+

Traditional vJailbreak migrations copy VM disk data from VMware ESXi hosts to PCD Cinder volumes over the network using the NFC (Network File Copy) protocol. This approach is limited to approximately 1 Gbps per VMDK due to VMware’s NFC protocol constraints.

+

Storage-Accelerated Copy bypasses this limitation by:

+
    +
  1. Creating a target volume directly on the storage array
  2. +
  3. Importing the volume into PCD Cinder
  4. +
  5. Mapping the volume to the ESXi host
  6. +
  7. Using ESXi’s vmkfstools to perform an XCOPY clone operation directly on the storage array
  8. +
  9. The storage array handles the data copy internally.
  10. +
+

Benefits

+
    +
  • Dramatically faster migrations: Array-level copy operations are significantly faster than network-based transfers
  • +
  • Reduced network load: Data doesn’t traverse the network between VMware and PCD.
  • +
  • Lower ESXi host CPU usage: The storage array handles the heavy lifting
  • +
  • Ideal for large VMs: Especially beneficial for VMs with large disks (hundreds of GB to TB)
  • +
+

Requirements

+
    +
  • Supported storage arrays: Pure Storage FlashArray or NetApp ONTAP
  • +
  • Shared storage: Both VMware datastores and PCD must be backed by the same storage array.
  • +
  • ESXi SSH access: SSH access to ESXi hosts with root privileges
  • +
  • Storage connectivity: ESXi hosts must be connected to the storage array via iSCSI (initiators configured) or Fibre Channel
  • +
+

Supported Storage Arrays

+ + + + + + + + + + + + + + + + + +
VendorProduct
Pure StorageFlashArray
NetAppONTAP
+ +

Prerequisites

+

Before using Storage-Accelerated Copy, ensure the following prerequisites are met:

+

1. Storage Array Configuration

+
    +
  • Storage array must be accessible from both VMware ESXi hosts and PCD compute nodes
  • +
  • VMware datastores must be VMFS volumes backed by LUNs on the supported storage array
  • +
  • PCD Cinder must be configured with a backend driver for the same storage array
  • +
  • Cinder volume types must be created and mapped to the storage array backend
  • +
+

2. ESXi SSH Access

+

Storage-Accelerated Copy requires SSH access to ESXi hosts to execute vmkfstools commands. Follow these steps to set up SSH access:

+

Step 1: Enable SSH on ESXi Hosts

+

Option A: Using vSphere Client (GUI)

+
    +
  1. Log in to vSphere Client
  2. +
  3. Navigate to the ESXi host
  4. +
  5. Click on the Configure tab
  6. +
  7. Under System, select Services
  8. +
  9. Find SSH in the list of services
  10. +
  11. Right-click on SSH and select Start
  12. +
  13. (Optional) Right-click again and select Policy → Start and stop with host to enable SSH automatically on boot
  14. +
+

Option B: Using ESXi Host Client (Direct)

+
    +
  1. Log in to the ESXi host directly via web browser: https://<esxi-host-ip>
  2. +
  3. Navigate to Host → Actions → Services → Enable Secure Shell (SSH)
  4. +
+

Option C: Using ESXi Shell (Console)

+
    +
  1. Access the ESXi host console (physical or via iLO/iDRAC)
  2. +
  3. Press F2 to customize system/view logs
  4. +
  5. Log in with root credentials
  6. +
  7. Navigate to Troubleshooting Options
  8. +
  9. Select Enable SSH
  10. +
  11. Press Enter to confirm
  12. +
+

Step 2: Generate SSH Key Pair

+

On your workstation or the vJailbreak VM, generate an SSH key pair:

+
Terminal window
# Generate RSA key pair (recommended for ESXi compatibility)
ssh-keygen -t rsa -b 4096 -f ~/.ssh/esxi_migration_key -C "vjailbreak-migration"
+
# When prompted:
# - Enter passphrase: Leave empty (press Enter) for passwordless authentication
# - Confirm passphrase: Press Enter again
+

This will create two files:

+
    +
  • ~/.ssh/esxi_migration_key - Private key (keep this secure)
  • +
  • ~/.ssh/esxi_migration_key.pub - Public key (to be copied to ESXi)
  • +
+ +

Step 3: Copy Public Key to ESXi Hosts

+

Option A: Using ssh-copy-id (Easiest)

+
Terminal window
# Copy public key to ESXi host
ssh-copy-id -i ~/.ssh/esxi_migration_key.pub root@<esxi-host-ip>
+
# Enter the root password when prompted
+

Option B: Manual Copy

+

If ssh-copy-id is not available:

+
Terminal window
# Display the public key
cat ~/.ssh/esxi_migration_key.pub
+
# SSH into the ESXi host
ssh root@<esxi-host-ip>
+
# On the ESXi host, add the public key to authorized_keys
cat >> /etc/ssh/keys-root/authorized_keys << 'EOF'
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQC... vjailbreak-migration
EOF
+
# Set correct permissions
chmod 600 /etc/ssh/keys-root/authorized_keys
+

Step 4: Test SSH Connection

+

Verify passwordless SSH access works:

+
Terminal window
# Test SSH connection (should not prompt for password)
ssh -i ~/.ssh/esxi_migration_key root@<esxi-host-ip>
+

If successful, you should be able to login to esxi.

+

Step 5: Configure SSH Key in vJailbreak

+
    +
  1. Navigate to Storage Management page
  2. +
  3. At the top, find the ESXi SSH Key section
  4. +
  5. Click Configure
  6. +
  7. Get your private key contents: +
    Terminal window
    cat ~/.ssh/esxi_migration_key
    +
  8. +
  9. Copy the entire output (including -----BEGIN OPENSSH PRIVATE KEY----- and -----END OPENSSH PRIVATE KEY-----)
  10. +
  11. Paste into the UI textarea
  12. +
  13. Click Save
  14. +
+

Step 6: Repeat for All ESXi Hosts

+

Repeat Steps 3-4 for all ESXi hosts in your vCenter cluster that host VMs you plan to migrate. The same SSH key pair can be used for all hosts.

+
Terminal window
# Example: Copy to multiple hosts
for host in esxi-host1.example.com esxi-host2.example.com esxi-host3.example.com; do
echo "Configuring $host..."
ssh-copy-id -i ~/.ssh/esxi_migration_key.pub root@$host
done
+ + +

3. Network Connectivity

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
SourceDestinationPortProtocolPurpose
vJailbreakESXi hosts22TCPSSH for vmkfstools commands
ESXi hostsStorage array3260TCPiSCSI (if using iSCSI)
ESXi hostsStorage arrayVariousFCFibre Channel (if using FC)
vJailbreakStorage array443TCPStorage array API
+

Configuration

+

Understanding Auto-Discovery

+

When you add PCD credentials to vJailbreak, the system automatically discovers all storage volume backends configured in your PCD environment. For each detected storage backend (NetApp, Pure Storage, etc.), vJailbreak creates a placeholder ArrayCreds entry with status “Auto-discovered” and credentials marked as “Pending”.

+

How Auto-Discovery Works

+
    +
  1. +

    PCD Configuration: In PCD, you configure multiple storage volume backends under “Persistent Storage Connectivity” (Cluster Blueprint → Storage). Each volume backend represents a storage array with its driver type (NetApp Data ONTAP, Pure Storage iSCSI, NFS, etc.).

    +
  2. +
  3. +

    Backend Detection: When PCD credentials are added to vJailbreak, the system queries the Cinder API to discover all configured volume backends and their properties:

    +
      +
    • Volume Type (e.g., netapp, vt-pure-iscsi)
    • +
    • Backend Name (e.g., netapp, pure-iscsi-1)
    • +
    • Driver Type (e.g., NetApp Data ONTAP, Pure Storage iSCSI)
    • +
    • Cinder Host string
    • +
    +
  4. +
  5. +

    Placeholder Creation: For each discovered backend, vJailbreak automatically creates an ArrayCreds resource with:

    +
      +
    • Name: Derived from the volume type and backend name (e.g., netapp-netapp, vt-pure-iscsi-pure-iscsi-1)
    • +
    • Vendor: Automatically identified from the driver type
    • +
    • Source: Marked as “Auto-discovered”
    • +
    • Credentials: Status shows “Pending” (requires user input)
    • +
    • PCD Mapping: Pre-populated with volume type, backend name, and Cinder host
    • +
    +
  6. +
  7. +

    User Completion: Users then update these auto-discovered entries with the actual storage array credentials (hostname, username, password) to enable Storage Accelerated Copy.

    +
  8. +
+

Storage Management Page

+

The Storage Management page displays all auto-discovered storage backends:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ColumnDescription
NameAuto-generated name based on volume type and backend
VendorStorage array vendor (NetApp Storage, Pure Storage, N/A)
Volume TypeCinder volume type name
Backend NameCinder backend name from configuration
Source”Auto-discovered” for automatically detected backends
Credentials”Pending” until user provides array credentials
ActionsEdit (to add credentials) and Delete
+

Storage Management Page

+

Example: PCD with Multiple Storage Backends

+

In PCD, storage backends are configured in the Cluster Blueprint under Persistent Storage Connectivity. Each volume backend can have multiple configurations, and each configuration represents a connection to a storage array.

+

PCD Cluster Blueprint - Storage Configuration

+

In the example above, PCD has three volume backends configured:

+
    +
  1. nfs - NFS backend with driver “NFS”
  2. +
  3. netapp - NetApp backend with driver “NetApp Data ONTAP”
  4. +
  5. vt-pure-iscsi - Pure Storage backend with driver “Pure Storage iSCSI”
  6. +
+

Each backend can have multiple configurations (shown as “Volume Backend Configurations” with the + button). For example:

+
    +
  • The nfs volume backend might have one configuration named nfs-backend
  • +
  • The netapp volume backend might have one configuration named netapp
  • +
  • The vt-pure-iscsi volume backend might have multiple configurations: pure-iscsi-1, pure-iscsi-2, etc.
  • +
+

Important: For each volume type, you can configure multiple storage arrays. This is useful when you have multiple Pure Storage or NetApp arrays in your environment, each serving different datastores.

+
Storage Volume Backend Configuration (Example):
├── nfs (Volume Type)
│ └── nfs-backend (Backend Configuration)
│ ├── Driver: NFS
│ └── Backend Name: nfs-backend
├── netapp (Volume Type)
│ └── netapp (Backend Configuration)
│ ├── Driver: NetApp Data ONTAP
│ └── Backend Name: netapp
└── vt-pure-iscsi (Volume Type)
├── pure-iscsi-1 (Backend Configuration #1)
│ ├── Driver: Pure Storage iSCSI
│ └── Backend Name: pure-iscsi-1
└── pure-iscsi-2 (Backend Configuration #2)
├── Driver: Pure Storage iSCSI
└── Backend Name: pure-iscsi-2
+

After adding PCD credentials to vJailbreak, the system automatically creates ArrayCreds placeholders for each backend configuration:

+
    +
  • nfs-nfs-backend (Vendor: N/A, Credentials: Pending) - Cannot be used for Storage Accelerated Copy
  • +
  • netapp-netapp (Vendor: NetApp Storage, Credentials: Pending)
  • +
  • vt-pure-iscsi-pure-iscsi-1 (Vendor: Pure Storage, Credentials: Pending)
  • +
  • vt-pure-iscsi-pure-iscsi-2 (Vendor: Pure Storage, Credentials: Pending)
  • +
+ +

Using the UI

+

Storage-Accelerated Copy can be configured entirely through the vJailbreak UI with automatic backend discovery:

+

Step 1: Add PCD Credentials

+

If not already done:

+
    +
  1. Navigate to Credentials → PCD/OpenStack
  2. +
  3. Add your PCD credentials
  4. +
  5. vJailbreak will automatically discover all storage volume backends configured in PCD
  6. +
+

Step 2: Configure Storage Array Credentials

+
    +
  1. +

    Navigate to Storage Management (Beta feature)

    +
  2. +
  3. +

    You’ll see auto-discovered entries for each PCD storage backend:

    +
      +
    • Name: Auto-generated (e.g., netapp-netapp, vt-pure-iscsi-pure-iscsi-1)
    • +
    • Vendor: Auto-identified from driver type
    • +
    • Volume Type: Pre-populated from PCD configuration
    • +
    • Backend Name: Pre-populated from PCD configuration
    • +
    • Source: “Auto-discovered”
    • +
    • Credentials: “Pending” (requires your input)
    • +
    +
  4. +
  5. +

    Click the Edit icon for a storage array entry

    +
  6. +
+

Storage Array Credentials Edit

+
    +
  1. Fill in the storage array credentials: +
      +
    • Hostname/IP: Storage array management IP address
    • +
    • Username: Array administrator username
    • +
    • Password: Array administrator password
    • +
    • Skip SSL Verification: Enable for testing environments (disable in production)
    • +
    +
  2. +
  3. Click Save
  4. +
  5. The system will: +
      +
    • Validate the credentials
    • +
    • Connect to the storage array
    • +
    • Auto-discover VMware datastores backed by this array
    • +
    • Update the status to show validation results
    • +
    +
  6. +
+

Step 3: Configure ESXi SSH Key

+
    +
  1. At the top of the Storage Management page, find the ESXi SSH Key section
  2. +
  3. Click Configure if not already configured
  4. +
  5. Paste your ESXi SSH private key (see ESXi SSH Access section for key generation steps)
  6. +
  7. Click Save
  8. +
+

Step 4: Create Migration with Storage-Accelerated Copy

+
    +
  1. When creating a migration plan, select Storage-Accelerated Copy as the storage copy method
  2. +
+

Storage Accelerated Copy

+
    +
  1. The UI will automatically map datastores to ArrayCreds if everything is configured correctly
  2. +
  3. Start the migration - it will use array-level XCOPY for data transfer
  4. +
+ +

Migration Workflow

+

When Storage-Accelerated Copy is enabled, the migration follows this workflow:

+
┌─────────────────────────────────────────────────────────────────┐
│ Storage-Accelerated Copy Flow │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 1. Validate Prerequisites │
│ ├── Storage provider credentials │
│ ├── ESXi SSH key │
│ └── Array connectivity │
│ │
│ 2. Connect to ESXi Host via SSH │
│ │
│ 3. Power Off Source VM (required for XCOPY) │
│ │
│ 4. For Each Disk: │
│ ├── Create target volume on storage array │
│ ├── Import volume to Cinder (manage existing) │
│ ├── Create/update initiator group with ESXi IQN │
│ ├── Map volume to ESXi host │
│ ├── Rescan ESXi storage adapters │
│ ├── Wait for target device to appear │
│ ├── Execute vmkfstools XCOPY clone │
│ └── Monitor clone progress │
│ │
│ 5. Convert Volumes (same as normal migration) │
│ │
│ 6. Create Target VM in PCD │
│ │
└─────────────────────────────────────────────────────────────────┘
+

Migration Phases

+

When using Storage-Accelerated Copy, you’ll see these additional migration phases:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PhaseDescription
ConnectingToESXiEstablishing SSH connection to ESXi host
CreatingInitiatorGroupCreating/updating initiator group on storage array
CreatingVolumeCreating target volume on storage array
ImportingToCinderImporting volume to PCD Cinder
MappingVolumeMapping volume to ESXi host
RescanningStorageRescanning ESXi storage adapters
StorageAcceleratedCopyInProgressXCOPY clone operation in progress
+

Limitations

+
    +
  • Cold migration only: VMs must be powered off during the copy operation (no live migration support)
  • +
  • Shared storage required: Source and destination must be on the same storage array
  • +
  • VMFS datastores only: NFS datastores are not supported
  • +
  • No CBT support: Change Block Tracking is not used; full disk copy is performed
  • +
  • Single array per datastore: Each datastore can only be mapped to one ArrayCreds
  • +
+

Troubleshooting

+

Common Issues

+

ESXi SSH Connection Failed

+
Error: failed to connect to ESXi via SSH
+

Resolution:

+
    +
  • Verify SSH is enabled on the ESXi host
  • +
  • Check that the SSH private key is correctly stored in the esxi-ssh-key secret
  • +
  • Ensure network connectivity between vJailbreak and ESXi host on port 22
  • +
+

Storage Array Connection Failed

+

Symptoms:

+
Error: failed to connect to storage array: authentication failed
+

Solutions:

+
    +
  1. +

    Verify credentials in the Storage Management UI:

    +
      +
    • Navigate to Storage Management
    • +
    • Check the array credentials are correct
    • +
    • Re-enter credentials if needed
    • +
    +
  2. +
  3. +

    Test connectivity from vJailbreak to storage array:

    +
      +
    • Ensure network connectivity on port 443 (HTTPS)
    • +
    • Check firewall rules between vJailbreak and storage array
    • +
    • Verify API access is enabled on the array
    • +
    +
  4. +
  5. +

    Check array-specific requirements:

    +
      +
    • Pure Storage: Ensure API token or username/password has sufficient permissions
    • +
    • NetApp: Verify ONTAP management interface is accessible
    • +
    +
  6. +
+

ESXi Device Not Found

+

Symptoms:

+
Error: timeout waiting for device naa.624a9370... to appear on ESXi host
+

Solutions:

+
    +
  1. +

    Verify iSCSI initiator is configured on ESXi:

    +
      +
    • Log into ESXi via SSH
    • +
    • Run: esxcli iscsi adapter list
    • +
    • Ensure iSCSI software adapter is enabled
    • +
    +
  2. +
  3. +

    Check network connectivity from ESXi to storage array:

    +
      +
    • Verify ESXi can reach storage array management IP
    • +
    • Check iSCSI network configuration
    • +
    • Verify VLAN/network segmentation allows iSCSI traffic
    • +
    +
  4. +
  5. +

    Manually rescan storage:

    +
    Terminal window
    esxcli storage core adapter rescan --all
    esxcli storage core device list | grep naa.624a9370
    +
  6. +
  7. +

    Verify LUN masking/mapping on the storage array:

    +
      +
    • Check initiator group includes ESXi IQN
    • +
    • Verify volume is mapped to the correct initiator group
    • +
    • Check for iSCSI authentication issues (CHAP)
    • +
    +
  8. +
+

Cinder Manage Volume Failed

+

Symptoms:

+
Error: failed to manage existing volume: volume not found on backend
+

Solutions:

+
    +
  1. +

    Verify volume exists on the storage array:

    +
      +
    • Check storage array management interface
    • +
    • Confirm volume was created successfully
    • +
    +
  2. +
  3. +

    Check Cinder backend configuration:

    +
      +
    • Verify Cinder services are running in PCD
    • +
    • Ensure volume backend name matches ArrayCreds configuration
    • +
    • Check that the Cinder host string is correct
    • +
    +
  4. +
  5. +

    Ensure volume naming matches backend expectations:

    +
      +
    • Pure Storage: Volume name must match exactly
    • +
    • NetApp: Full LUN path must be provided
    • +
    +
  6. +
  7. +

    Review Cinder logs for detailed errors:

    +
      +
    • Check PCD Cinder volume service logs
    • +
    • Look for backend connection issues
    • +
    • Verify the cinderHost field in ArrayCreds matches a running Cinder service
    • +
    +
  8. +
+

vmkfstools Clone Failed

+

Symptoms:

+
Error: vmkfstools clone failed: Unable to create raw disk
+

Solutions:

+
    +
  1. +

    Check ESXi SSH connectivity and authentication:

    +
      +
    • Verify SSH key is configured correctly in vJailbreak
    • +
    • Test SSH connection manually
    • +
    • Check ESXi SSH service is running
    • +
    +
  2. +
  3. +

    Verify vmkfstools is available:

    +
    Terminal window
    vmkfstools --version
    +
  4. +
  5. +

    Check source VMDK accessibility:

    +
    Terminal window
    ls -lh /vmfs/volumes/<datastore>/<vm-name>/<disk>.vmdk
    +
  6. +
  7. +

    Verify target device is visible:

    +
    Terminal window
    ls -lh /vmfs/devices/disks/naa.*
    +
  8. +
  9. +

    Check ESXi host logs:

    +
    Terminal window
    tail -f /var/log/vmkernel.log
    +
  10. +
  11. +

    Ensure sufficient free space on ESXi:

    +
      +
    • RDM descriptor files require space on the datastore
    • +
    • Check datastore free space
    • +
    +
  12. +
+

Clone Progress Stalled

+

Symptoms:

+
Error: clone progress stalled - no update for 5 minutes
+

Solutions:

+
    +
  1. +

    Check storage array performance and load:

    +
      +
    • Review array management interface for performance metrics
    • +
    • Check for high I/O load or resource contention
    • +
    • Verify no array-level issues or alerts
    • +
    +
  2. +
  3. +

    Verify ESXi storage adapter health:

    +
    Terminal window
    esxcli storage core adapter list
    +
  4. +
  5. +

    Review vmkfstools process on ESXi:

    +
    Terminal window
    ps | grep vmkfstools
    ps -c | grep vmkfstools
    +
      +
    • Check if process is still running
    • +
    • Look for any error indicators
    • +
    +
  6. +
  7. +

    Check for network issues:

    +
      +
    • Verify stable connectivity between ESXi and storage array
    • +
    • Check for packet loss or latency issues
    • +
    +
  8. +
  9. +

    Consider increasing timeout settings:

    +
      +
    • For very large disks, the operation may take longer than expected
    • +
    • Monitor array performance to ensure copy is progressing
    • +
    +
  10. +
+

Checking ArrayCreds Status

+

You can check the status of your storage array credentials in the UI:

+
    +
  1. Navigate to Storage Management
  2. +
  3. Check the Credentials column - it should show “Valid” after successful configuration
  4. +
  5. The system will display discovered datastores for each array
  6. +
  7. Any validation errors will be shown in the status column
  8. +
+

Best Practices

+
    +
  1. Validate prerequisites first: Ensure all connectivity and credentials are working before starting migrations
  2. +
  3. Schedule during maintenance windows: VMs must be powered off during copy
  4. +
  5. Monitor array performance: Large migrations can impact array performance
  6. +
  7. Use for large VMs: The setup overhead makes this most beneficial for VMs with large disks
  8. +
  9. Batch similar VMs: Group VMs on the same datastore for efficient migrations
  10. +
\ No newline at end of file diff --git a/docs/concepts/user-management/index.html b/docs/concepts/user-management/index.html new file mode 100644 index 0000000..00c835f --- /dev/null +++ b/docs/concepts/user-management/index.html @@ -0,0 +1,195 @@ + User Credential Management | vJailbreak + Skip to content

User Credential Management

Overview

+

Use vjbctl to manage user credentials: list users, delete users, change passwords, and refresh credentials.

+

Assumptions

+

Before you start, ensure the following prerequisites are fulfilled:

+
    +
  • vJailbreak is installed and configured properly.
  • +
+

Command Reference

+

The following commands are available for user credential management:

+
Terminal window
# List users
vjbctl user list
+
# create a new user
vjbctl user create <username>
+
# Delete a user
vjbctl user delete <username>
+
# Change a user's password
vjbctl user change-password <username>
+
# Refresh user credentials
vjbctl user refresh
+
+

Tip: Append --no-restart to vjbctl user subcommands (e.g., create, delete, change-password) to defer applying changes. Then run vjbctl user refresh once to apply all pending changes for better efficiency.

+
+

Usage

+
+

Note: After any user change (e.g., password updates or deletions), run vjbctl user refresh for the changes to be reflected.

+
+

List users

+
Terminal window
vjbctl user list
+

Delete a user

+

Delete a user and remove their credentials from the system:

+
Terminal window
vjbctl user delete <username>
+

Change a user’s password

+

Change the password for an existing user:

+
Terminal window
vjbctl user change-password <username>
+

You will be prompted to enter the new password in the terminal.

+

Batch multiple changes efficiently

+

To avoid refreshing after every change, you can batch multiple operations using --no-restart and then apply them all at once with a single refresh:

+
Terminal window
# Defer applying changes while making multiple updates
vjbctl user create <username> --no-restart
vjbctl user change-password <username> --no-restart
vjbctl user delete <username> --no-restart
+
# Apply all pending changes
vjbctl user refresh
+

Refresh user credentials

+

Refresh user credentials for active users:

+
Terminal window
vjbctl user refresh
+

Examples

+

The following sequence demonstrates a typical workflow for updating user credentials:

+
Terminal window
# Inspect existing users
vjbctl user list
+
# Create a new user
vjbctl user create john.doe
# For changes to be reflected, refresh
vjbctl user refresh
+
# Update password for a specific user
vjbctl user change-password jane.doe
# For changes to be reflected, refresh
vjbctl user refresh
+
# Remove a deprovisioned user
vjbctl user delete temp.user
# For changes to be reflected, refresh
vjbctl user refresh
\ No newline at end of file diff --git a/docs/concepts/vjailbreak-accelerated-copy/index.html b/docs/concepts/vjailbreak-accelerated-copy/index.html new file mode 100644 index 0000000..e9cee65 --- /dev/null +++ b/docs/concepts/vjailbreak-accelerated-copy/index.html @@ -0,0 +1,507 @@ + vJailbreak Accelerated Copy | vJailbreak + Skip to content

vJailbreak Accelerated Copy

vJailbreak Accelerated Copy is an advanced data copy method that attaches source VM disks directly to a dedicated Proxy VM and streams the data over NBD (Network Block Device) to the destination. Instead of copying data over the NFC protocol from ESXi, this method leverages vCenter’s disk-attach capability to transfer data at near-disk speeds without requiring shared storage arrays.

+
+

Underlying feature: vJailbreak Accelerated Copy is powered by VMware’s hot-add disk transport mechanism to attach source disks to the Proxy VM.

+
+ + +

Overview

+

How It Works

+

Traditional vJailbreak migrations copy VM disk data from VMware ESXi hosts to PCD Cinder volumes over the network using the NFC (Network File Copy) protocol, limited to approximately 1 Gbps per VMDK.

+

vJailbreak Accelerated Copy bypasses this limitation by:

+
    +
  1. Powering off the source VM and then taking a snapshot
  2. +
  3. Attaching the frozen snapshot disks directly to a Proxy VM running in vCenter
  4. +
  5. Identifying each disk as a block device inside the Proxy VM using disk UUID matching
  6. +
  7. Exposing each disk as an NBD resource on the Proxy VM via qemu-nbd
  8. +
  9. Running nbdcopy on the vJailbreak VM to pull data from the Proxy VM directly to the destination Cinder volume
  10. +
+

Benefits

+
    +
  • Faster migrations: Direct block-device access avoids NFC protocol overhead
  • +
  • No shared storage required: Works with any datastore — NFS, VMFS, vSAN
  • +
  • Lower ESXi host load: Data is streamed from the Proxy VM, not the ESXi NFC daemon
  • +
+

Requirements

+
    +
  • Proxy VM: A Linux VM running in the same vCenter with qemu-nbd and openssh-server installed
  • +
  • SSH access: vJailbreak must be able to SSH into the Proxy VM as root
  • +
  • Open ports: The Proxy VM must accept inbound TCP from the vJailbreak VM on 22 (SSH) and 10809–11808 (qemu-nbd, one port per disk copied in parallel)
  • +
  • disk.EnableUUID: Must be set to TRUE on the Proxy VM in vCenter
  • +
  • PVSCSI controller: The Proxy VM’s first SCSI controller (SCSI controller 0) must be of type VMware Paravirtual (PVSCSI)
  • +
  • Datastore accessibility: The HotAdd proxy must have access to the same datastore as the target virtual machine, and the VMFS version and data block sizes for the target VM must be the same as the datastore where the HotAdd proxy resides.
  • +
  • vCenter permissions: Sufficient permissions to snapshot VMs and attach/detach disks
  • +
+

Prerequisites

+

1. Proxy VM Requirements

+

The Proxy VM must have the following utilities installed and running:

+ + + + + + + + + + + + + + + + + +
UtilityPurpose
openssh-serverSSH connectivity for vJailbreak to control the Proxy VM
qemu-nbdExpose attached block devices as NBD resources
+

The Proxy VM must be a Linux-based OS (recommended: Ubuntu, Alpine, or Debian) with root SSH access enabled.

+

2. vCenter Requirements

+
    +
  • The Proxy VM must have disk.EnableUUID = TRUE set in vCenter VM settings
  • +
  • The Proxy VM’s SCSI controller 0 must be of type VMware Paravirtual (PVSCSI)
  • +
  • vCenter must allow disk attach/detach operations on the Proxy VM
  • +
  • The Proxy VM must be powered on and reachable over SSH
  • +
  • The Proxy VM must be on the same datastore as the source VM’s disks, with a matching VMFS version and block size (see Datastore accessibility under Requirements)
  • +
+

Setting Up the Proxy VM

+

Option A: Deploy from the vJailbreak UI (Easiest)

+

vJailbreak can deploy and register the Proxy VM in a single step directly from the UI:

+
    +
  1. Navigate to vJailbreak Accelerated Copy in the left sidebar
  2. +
  3. Click Add Proxy VM and select Deploy a new vJailbreak Proxy VM
  4. +
  5. Select your VMware credentials and fill in the deployment target (datacenter, datastore, network, and optionally a cluster or host)
  6. +
  7. Enter a unique VM name and click Deploy & Register VM
  8. +
+

vJailbreak will import the pre-configured OVA into vCenter, power the VM on, generate and inject an SSH key pair automatically, and register the Proxy VM — no manual key setup required. The VM appears in the list with status Deploying and transitions to Ready once verification completes (typically 3–5 minutes).

+ + +

Option B: Register an Existing Linux VM

+

Any Linux VM can serve as the Proxy VM provided it meets the requirements. Install the necessary utilities:

+

Ubuntu / Debian:

+
Terminal window
sudo apt update
sudo apt install -y openssh-server qemu-utils
+

Alpine:

+
Terminal window
apk update
apk add openssh qemu-nbd
+ +

Configure disk.EnableUUID on the Proxy VM

+

This setting is required for vJailbreak to match attached disks to their block devices inside the Proxy VM:

+
    +
  1. In vSphere Client, right-click the Proxy VM and select Edit Settings
  2. +
  3. Click VM Options → Advanced → Edit Configuration
  4. +
  5. Find or add the key disk.EnableUUID and set the value to TRUE
  6. +
  7. Click OK and restart the VM if it was already running
  8. +
+

Configure the SCSI Controller Type on the Proxy VM

+

Source disks are attached to the Proxy VM’s first SCSI controller, and vJailbreak can only match them to block devices when that controller is VMware Paravirtual:

+
    +
  1. Power off the Proxy VM
  2. +
  3. In vSphere Client, right-click the Proxy VM and select Edit Settings
  4. +
  5. Under Virtual Hardware, locate SCSI controller 0
  6. +
  7. Set Change Type to VMware Paravirtual
  8. +
  9. Click OK and power the VM back on
  10. +
+ +

SSH Key Configuration

+ +

When registering an existing VM, vJailbreak needs SSH access to the Proxy VM. The UI offers two ways to provide the key pair:

+

Sub-option 1: Let vJailbreak Generate the Key Pair

+
    +
  1. In the Add Proxy VM drawer, select Register an existing VM
  2. +
  3. Select your VMware credentials and the VM
  4. +
  5. Under SSH Access, choose Generate Key Pair and click Generate
  6. +
  7. The UI displays the public key — copy it and add it to the Proxy VM’s authorized_keys: +
    Terminal window
    # On the Proxy VM (as root)
    echo "<paste public key here>" >> ~/.ssh/authorized_keys
    chmod 600 ~/.ssh/authorized_keys
    +
  8. +
  9. Click Register
  10. +
+

vJailbreak stores the generated private key as a Kubernetes secret automatically.

+

Sub-option 2: Upload Your Own Private Key

+

If you have an existing key pair already configured on the VM:

+
    +
  1. Under SSH Access, choose Upload Private Key
  2. +
  3. Upload the private key file or paste its contents into the text area
  4. +
  5. Confirm the corresponding public key is already in the VM’s authorized_keys
  6. +
  7. Click Register
  8. +
+

vJailbreak stores the uploaded private key as a Kubernetes secret and uses it during verification and migration.

+

Generating a Key Pair Manually

+

If you prefer to generate the key pair yourself outside of vJailbreak:

+
Terminal window
ssh-keygen -t rsa -b 4096 -f proxy_vm_key -N ""
+

This produces two files:

+
    +
  • proxy_vm_key — private key (upload this into vJailbreak)
  • +
  • proxy_vm_key.pub — public key (add this to the Proxy VM)
  • +
+

On the Proxy VM, append the public key to root’s authorized_keys:

+
Terminal window
# On the Proxy VM (as root)
mkdir -p ~/.ssh
cat >> ~/.ssh/authorized_keys << 'EOF'
<contents of proxy_vm_key.pub>
EOF
chmod 600 ~/.ssh/authorized_keys
+

If you have temporary password SSH access, you can use ssh-copy-id from your workstation as a shortcut:

+
Terminal window
ssh-copy-id -i proxy_vm_key.pub root@<proxy-vm-ip>
+ +

Registering the Proxy VM in vJailbreak

+

Once the Proxy VM is set up and the SSH key is ready:

+
    +
  1. In the vJailbreak UI, navigate to vJailbreak Accelerated Copy in the left sidebar
  2. +
  3. Click Add Proxy VM
  4. +
  5. Fill in the form: +
      +
    • Name: A unique identifier for this Proxy VM
    • +
    • VM Name: The exact VM name as it appears in vCenter
    • +
    • VMware Credentials: Select the VMware credentials that can see this VM
    • +
    • SSH Private Key: Paste the contents of your private key file (e.g., ~/.ssh/proxy_vm_key)
    • +
    +
  6. +
  7. Click Add
  8. +
+

vJailbreak will verify the Proxy VM by:

+
    +
  • Confirming the VM exists in vCenter
  • +
  • Retrieving the guest IP via VMware Tools
  • +
  • Checking disk.EnableUUID = TRUE — if not set, vJailbreak will automatically enable it and reboot the Proxy VM, so onboarding may take longer than usual
  • +
  • Establishing an SSH connection
  • +
  • Verifying qemu-nbd is available
  • +
+

The Proxy VM status will update to Ready once all checks pass. Any failed checks are reported with a specific error message in the UI.

+ +

Using vJailbreak Accelerated Copy in a Migration

+

Step 1: Create a Migration

+
    +
  1. Navigate to the Migrations page and click New Migration
  2. +
  3. Fill out the migration form with source VM and target configuration
  4. +
  5. For the Data Copy Method, select vJailbreak Accelerated Copy
  6. +
+

Step 2: Select Proxy VM

+
    +
  1. A Proxy VM dropdown appears — select a Proxy VM in Ready state
  2. +
  3. The UI will only show Proxy VMs that are verified and ready
  4. +
+

Step 3: Start the Migration

+
    +
  1. Review Advanced Options if needed (network/storage mappings)
  2. +
  3. Click Start Migration
  4. +
+ +

Migration Workflow

+

When vJailbreak Accelerated Copy is selected, the migration follows this workflow:

+
┌─────────────────────────────────────────────────────────────────┐
│ vJailbreak Accelerated Copy Flow │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 1. Validate Prerequisites │
│ ├── Proxy VM is in Ready state │
│ ├── SSH connectivity to Proxy VM │
│ └── qemu-nbd available on Proxy VM │
│ │
│ 2. Provision Destination Resources (standard workflow) │
│ ├── Create Cinder volumes in PCD │
│ └── Attach destination disks to vJailbreak VM │
│ │
│ 3. Power Off Source VM, then Take Snapshot │
│ │
│ 4. For Each Source Disk: │
│ ├── Attach frozen snapshot disk to Proxy VM │
│ ├── Identify block device via disk UUID matching │
│ ├── Find a free port on the Proxy VM │
│ ├── Expose disk as NBD via qemu-nbd on that port │
│ ├── Run nbdcopy on vJailbreak VM to destination disk │
│ └── Detach and clean up disk from Proxy VM │
│ │
│ 5. Remove Source VM Snapshot │
│ │
│ 6. Disk Conversion (same as normal migration) │
│ │
│ 7. Create Target VM in PCD (standard post-copy flow) │
│ │
└─────────────────────────────────────────────────────────────────┘
+

Component Diagram

+

The pieces involved and how they talk to each other:

+
architecture-beta
+    service admin(internet)[Admin UI]
+
+    group vjb(cloud)[vJailbreak Appliance]
+        service ctrl(server)[Controller] in vjb
+        service pod(server)[Migration Pod] in vjb
+
+    group vmw(cloud)[VMware Environment]
+        service vc(server)[vCenter] in vmw
+        service proxy(server)[Proxy VM] in vmw
+
+    group pcd(cloud)[Platform9 PCD]
+        service os(disk)[Cinder Volume] in pcd
+
+    admin:R -- L:ctrl
+    ctrl:R -- L:vc
+    ctrl:B -- T:proxy
+    vc:B -- L:proxy
+    pod:L -- R:proxy
+    pod:B -- T:os
+
    +
  • Admin UI → Controller: manage Proxy VMs and MigrationPlans (Kubernetes API)
  • +
  • Controller → vCenter/ESXi: deploy/register the Proxy VM (OVA import), read host inventory
  • +
  • Controller → Proxy VM: push/verify SSH key, confirm qemu-nbd and disk.EnableUUID
  • +
  • vCenter/ESXi → Proxy VM: hot-add the frozen source-VM disks
  • +
  • Migration Pod → Proxy VM: SSH — identify block devices, start qemu-nbd, run nbdcopy
  • +
  • Migration Pod → Cinder Volume: write the copied disk data to the destination
  • +
+

Sequence Diagram

+

The same flow as a sequence diagram, including Proxy VM onboarding (only the operations specific to vJailbreak Accelerated Copy are shown — disk conversion and target VM creation follow the same path as every other copy method):

+
sequenceDiagram
+    autonumber
+    actor Admin
+    participant Ctrl as vJailbreak Controller
+    participant vC as vCenter / ESXi
+    participant Proxy as Proxy VM
+    participant Pod as Migration Pod (v2v-helper)
+    participant OS as OpenStack (Cinder/Nova)
+
+    Note over Admin,OS: Onboarding — Option A: Deploy new Proxy VM (OVA)
+    Admin->>Ctrl: Deploy & Register VM (OVA)
+    Ctrl->>vC: Import OVA, power on VM
+    Ctrl->>Proxy: Auto-generate & inject SSH keypair
+    Ctrl->>Proxy: Verify SSH, qemu-nbd, disk.EnableUUID
+    Ctrl->>Ctrl: Store private key as Secret "{proxyVM}-hot-add-ssh-key"
+    Ctrl-->>Admin: Proxy VM ready
+
+    Note over Admin,OS: Onboarding — Option B: Register an existing VM
+    Admin->>Ctrl: Register VM + SSH key (generate or upload)
+    Note right of Admin: If generated, admin adds public key
to the VM's authorized_keys manually + Ctrl->>Proxy: Verify SSH, qemu-nbd, disk.EnableUUID + Ctrl->>Ctrl: Store private key as Secret "{proxyVM}-hot-add-ssh-key" + Ctrl-->>Admin: Proxy VM ready + + Note over Admin,OS: Migration — vJailbreak Accelerated Copy + Admin->>Ctrl: Create MigrationPlan (StorageCopyMethod=HotAdd) + Ctrl->>Pod: Launch v2v-helper (Proxy VM IP + SSH secret) + Pod->>vC: Power off source VM, take snapshot + Pod->>vC: Hot-add frozen VMDKs to Proxy VM + Pod->>Proxy: SSH — match disk WWID to block device + Pod->>Proxy: SSH — start qemu-nbd per disk + Proxy-->>Pod: nbdcopy streams disk over NBD + Pod->>OS: Write stream into Cinder volume + Pod->>vC: Detach disks, delete snapshot + Pod-->>Ctrl: Migration succeeded
+

Limitations

+
    +
  • Cold copy only: The source VM is powered off before disk attachment — live (hot) copy of the running VM’s active disks is not supported
  • +
  • Same vCenter: Proxy VM and source VM must be managed by the same vCenter instance
  • +
  • VMware Tools required: The Proxy VM must have VMware Tools running so vJailbreak can retrieve its guest IP
  • +
  • PVSCSI controller only: The Proxy VM’s first SCSI controller (SCSI controller 0) must be VMware Paravirtual (PVSCSI). Disk UUID matching does not work on other controller types, and migrations fail with could not identify block device for disk <uuid>. See Configure the SCSI Controller Type.
  • +
  • Concurrent disk attach can fail: When several migrations reach the disk-attach step at the same time on the same Proxy VM, vCenter may reject some of the simultaneous reconfigure tasks and those migrations fail. This is a transient race — the migrations that attached first continue normally, and the failed ones succeed on retry. Stagger migration start times or spread migrations across multiple Proxy VMs to reduce the chance of it happening.
  • +
  • Maximum 60 disks per Proxy VM (including its own boot disk): vSphere allows at most 60 virtual disks per VM (4 SCSI controllers × 15 disks). The Proxy VM’s own boot disk counts toward this total, so the constraint is Proxy VM boot disk + attached source disks ≤ 60 — a Proxy VM with a single boot disk can have up to 59 source disks attached at any one time. This is a shared ceiling across all migrations using the same Proxy VM concurrently, not a per-migration limit. To migrate more disks in parallel, register additional Proxy VMs and distribute migrations across them.
  • +
+

Troubleshooting

+

Proxy VM Verification Failed

+

Symptoms: Proxy VM stuck in Pending or Failed state with a validation error.

+

Resolution by error:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ErrorResolution
VM not found in vCenterVerify the VM name exactly matches the vCenter inventory name
Guest IP not availableEnsure VMware Tools is installed and running on the Proxy VM
disk.EnableUUID not setSet disk.EnableUUID = TRUE in VM advanced settings and reboot
SSH connection refusedVerify sshd is running and port 22 is reachable from vJailbreak
qemu-nbd not foundInstall qemu-utils (Ubuntu) or qemu-nbd (Alpine) on the Proxy VM
+

NBD Connection Failed During Copy

+
Error: failed to connect to NBD endpoint on proxy VM
+

Resolution:

+
    +
  1. Verify the Proxy VM is still running and SSH is accessible
  2. +
  3. Check that qemu-nbd started successfully — review v2v helper logs
  4. +
  5. Ensure the NBD ports (TCP 10809–11808, one per disk copied in parallel) are not blocked by a firewall between the Proxy VM and vJailbreak
  6. +
  7. Confirm the Proxy VM’s guest IP is correct (VMware Tools must be running)
  8. +
+

Block Device Not Found in Proxy VM

+
Error: could not identify block device for disk <uuid>
+

Resolution:

+
    +
  1. Verify disk.EnableUUID = TRUE is set on the Proxy VM (this is the most common cause)
  2. +
  3. Verify the Proxy VM’s SCSI controller 0 is of type VMware Paravirtual — no other controller type is supported. See Configure the SCSI Controller Type
  4. +
  5. Confirm the disk was actually attached — check vCenter → Proxy VM → Edit Settings → Hard Disks
  6. +
  7. SSH into the Proxy VM and run lsblk to list visible block devices
  8. +
  9. Check vCenter events for disk attach errors on the Proxy VM
  10. +
+

Disk Attach Fails When Several Migrations Start Together

+

Symptoms: A batch of migrations is started at once and some of them fail early with a vCenter error while attaching disks to the Proxy VM. The remaining migrations proceed into the copy phase normally.

+

Cause: vCenter does not always handle simultaneous VM reconfigure (disk attach) tasks on the same Proxy VM gracefully, so some attach requests are rejected. This is a transient race condition, not a misconfiguration.

+

Resolution:

+
    +
  1. Wait until the surviving migrations have entered the copy phase
  2. +
  3. Retry the failed migrations — they normally succeed on the second attempt
  4. +
  5. To reduce the chance of the race, stagger migration start times instead of starting a large batch at once, or register additional Proxy VMs and distribute migrations across them
  6. +
+

Snapshot Creation Failed

+
Error: failed to create snapshot on source VM
+

Resolution:

+
    +
  1. Verify the VMware credentials have snapshot creation permissions
  2. +
  3. Check if a snapshot with the same name already exists on the source VM — remove stale vjailbreak-* snapshots
  4. +
  5. Ensure the source VM’s datastore has sufficient free space for the snapshot delta files
  6. +
+

Migration Stuck After Snapshot

+

If the migration is stuck after taking the snapshot and the source VM remains powered off:

+
    +
  1. Check v2v helper logs for the last successful phase
  2. +
  3. If the Proxy VM became unavailable, the migration will not auto-recover — clean up manually: +
    Terminal window
    # Remove the snapshot from vCenter
    govc snapshot.remove -vm "<source-vm>" "vjailbreak-hotadd-snap"
    +
  4. +
  5. Detach any disks vJailbreak attached to the Proxy VM before retrying
  6. +
+

Best Practices

+
    +
  1. Dedicate the Proxy VM: Avoid running other workloads on the Proxy VM during migrations to ensure stable performance
  2. +
  3. Match network placement: Place the Proxy VM on a network with low latency to the vJailbreak VM for fast NBD transfers
  4. +
  5. Verify before migrating: Always confirm the Proxy VM shows Ready status before starting a migration
  6. +
  7. Monitor disk space: Snapshot delta files consume datastore space — ensure the source VM’s datastore has at least 20% free space
  8. +
  9. Use the recommended OVA: The pre-built OVA is tested and configured correctly; custom VMs require manual validation of all prerequisites
  10. +
  11. Rotate SSH keys: Use a dedicated key pair for vJailbreak and rotate it periodically
  12. +
\ No newline at end of file diff --git a/docs/favicon.png b/docs/favicon.png new file mode 100644 index 0000000..e50ed20 Binary files /dev/null and b/docs/favicon.png differ diff --git a/docs/favicon.svg b/docs/favicon.svg index cba5ac1..e00507e 100644 --- a/docs/favicon.svg +++ b/docs/favicon.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/docs/guides/cli-api/migrating_rdm_disk_windows_cluster_machine_using_cli/index.html b/docs/guides/cli-api/migrating_rdm_disk_windows_cluster_machine_using_cli/index.html new file mode 100644 index 0000000..3291dad --- /dev/null +++ b/docs/guides/cli-api/migrating_rdm_disk_windows_cluster_machine_using_cli/index.html @@ -0,0 +1,319 @@ + Migrating RDM Disk | vJailbreak + Skip to content

Migrating RDM Disk

RDM disks are primarily used for clustered Windows machines.

+

This guide walks you through the steps required to migrate a VM with RDM (Raw Device Mapping) disks using the CLI.

+

RDM disk migration is only supported for PCD version >= July 2025 (2025.7) and is not supported for OpenStack.

+
+

Prerequisites

+

Before you begin, ensure the following:

+
    +
  1. RDM disk is attached to the Windows machine.
  2. +
  3. vjailbreak is deployed in your cluster.
  4. +
  5. PCD Requirements: +
      +
    • Minimum version: July 2025 (2025.7).
    • +
    • For multipath support (connecting to SAN array): October 2025 (2025.10) - includes patched libvirt and QEMU packages.
    • +
    • Volume type must have multi-attach support enabled in OpenStack.
    • +
    +
  6. +
  7. All required fields (like cinderBackendPool and volumeType) are available from your OpenstackCreds.
  8. +
  9. Source Details are added on RDM VMs in VMware described here
  10. +
  11. Storage array configured in PCD is same as the one configured in VMware. Usually SAN arrays have logical pools/isolation, that must be same as well.
  12. +
+

You can fetch cinderBackendPool and volumeType values using:

+

By describing the OpenStack credentials in vjailbreak:

+
Terminal window
kubectl describe openstackcreds <openstackcredsname> -n migration-system
+

After describing the OpenStack credentials, look for volumeTypes and volumeBackend. Gather the volumeTypes and volumeBackend values that need to be patched as mentioned in step 4 of Migration steps.

+

Alternatively you can also gather details using openstack cli

+
Terminal window
openstack volume backend pool list
openstack volume type list
+

Please refer to the following documents for commands to fetch volume backend pool and volume type lists:

+

opnestack cli version >= 6.2.1

+ +

RDM Validation settings

+

In vjailbreak setting configmap we have a setting called VALIDATE_RDM_OWNER_VMS whose default value is true.

+

This setting manadates all VM linked to RDM disk must be migrated in a single migration plan, to disable it set VALIDATE_RDM_OWNER_VMS to false

+

On VMware

+

Perform the following steps on each VM from the cluster you are planning to migrate.

+
    +
  • +

    Add the following annotation to the VMware Notes field for the VM:

    +
    VJB_RDM:{Name of Hardisk}:volumeRef:source-name=abac111
    +
      +
    • +

      VJB_RDM – Key prefix indicating this entry is an RDM (Raw Device Mapping) LUN reference.

      +
    • +
    • +

      {Name of Hardisk} - Name of the RDM disk attached to the VM. Replace this placeholder with the actual disk name. +Disk Name is case sensitive.

      +
    • +
    • +

      volumeRef – Denotes the reference section for the volume configuration. +source-name=abac111 – Specifies the LUN reference. +The key can be either source-id or source-name.

      +

      The value is the LUN identifier (ID or Name) used to map the disk. +To obtain the source details ie source-id, source-name, you can run the following command against the SAN Array:

      +
      Terminal window
      openstack block storage volume manageable list <Cinder backend pool name> --os-volume-api-version 3.8
      +
    • +
    +
  • +
+

Note: Not all SAN arrays are supported by the OpenStack block storage client, in such cases above command gives an empty output. If you cannot find your SAN array reference from the block storage client, contact your storage administrator to get the LUN reference by accessing the storage provider’s interface.

+

RDM disk migration has been tested with two storage arrays:

+
    +
  1. HPE Primera
  2. +
  3. NetApp ONTAP
  4. +
+

The manageable list command is only supported on HPE Primera.

+
+

Migration Steps

+

1. Verify RDM Disk Resource

+

Check if the RDM disk resource is created in Kubernetes:

+
Terminal window
kubectl get rdmdisk <vml-id> -n migration-system
+

Ensure the added annotations source-name or source-id are reflected in the vjailbreak RDM disk custom resource. Use the VML ID of the RDM disk from VMware.

+

If source details are not correct, edit the Notes section of VMware VM for correct value and wait for reconcilation ( few minutes ), to get source details updated.

+

2. Ensure RDM disk reference is correctly populated in vmwaremachine

+

For each VM’s to be migrated, list vm details on vjailbreak using below command:

+
Terminal window
kubectl describe vmwaremachine <vm-name> -n migration-system
+

Ensure vml id of all RDM disks to be migrated appear in the vmwaremachine custom resource.

+

3. Detach the RDM Disk and Power Off the VM in VMware

+

Since VMware does not allow snapshots of a VM with attached RDM disks, you must:

+
    +
  • Power off the VM to be migrated.
  • +
  • Detach the RDM disk from the VM (steps are mentioned below).
  • +
+

Optional: Once the RDM disk is detached,you can list the vmwaremachine custom resource and ensure the VML ID of all RDM disks to be migrated appear in rdmDisk section of vmwaremachine custom resource in vjailbreak.

+
Terminal window
kubectl describe vmwaremachine <vm-name> -n migration-system
+

Note: Once the RDM disk is detached, the source-name or source-id should not change, and the VMs that own the RDM disk should not change. If you need to detach the RDM disk from the VM and remove all RDM references from the VMs, you must handle it manually +

+

Delete the vmwaremachine and rdmdisk custom resources on vjailbreak. After deletion wait for the configured reconciliation time, and re ensure that deleted resources are recreated by vjailbreak.

+

Commands to delete VMware machine and RDM disk:

+
Terminal window
kubectl delete vmwaremachine <vm-name> -n migration-system
+
Terminal window
kubectl delete rdmdisk <rdm-vml-id> -n migration-system
+

Commands to verify VMware machine and RDM disk are recreated

+
Terminal window
kubectl describe vmwaremachine <vm-name> -n migration-system
+
Terminal window
kubectl describe rdmdisk <vml-id> -n migration-system
+

Steps to detach RDM disks in VMware:

+

For each VM, go to Edit Settings and perform following steps. Note down the details as you might need them in case you have to revert the migration.

+
    +
  1. Click on the cross icon near the RDM disks, and keep “Delete files from storage” unchecked.
  2. +
  3. Remove the SCSI controller used by these disks (this will be in Physical sharing mode).
  4. +
+

Note: Only remove the SCSI controller in Physical Sharing mode. Other volumes or non-RDM disks use different controllers (not in Physical Sharing mode), and those must not be deleted.

+

Detach RDM Disk in VMware

+

This ensures that the snapshot and migration can proceed without errors.

+

4. Patch RDM Disk with the Required Fields

+

Edit each RDM disk to add cinderBackendPool and volumeType. Example:

+
Terminal window
kubectl patch rdmdisk <name_of_rdmdisk_resource> -n migration-system -p '{"spec":{"openstackVolumeRef":{"cinderBackendPool":"backendpool_name","volumeType":"volume_type"}}}' --type=merge
+

The volume type specified here must match the configuration the RDM disk volume has on the SAN array. Example: if the volume has de-duplication and compression enabled, the specified volume type on OpenStack side must have these settings enabled.

+

5. Create Migration Plan

+

Create a migration plan using the CLI.
+Follow the detailed CLI steps here:

+

Migrating Using CLI and Kubectl

+

Note:

+
    +
  • While creating migration plan , make sure actual VM name is passed in spec.virtualMachines of migrationplan and not vm custom resource name.
  • +
  • Migration plan spec.migrationStrategy.type should be cold - RDM disk can only be migrated with cold migrationStrategy
  • +
+

6. Wait for Disk to Become Available

+

Confirm that the rdm disk is in Available state:

+
Terminal window
kubectl get rdmdisk <disk-id> -n migration-system -o yaml
+

Look for:

+
status:
phase: Available
+

7. Ensure All the VMs in Cluster are Migrated

+
    +
  1. +

    Check that the RDM disk is available as a volume in PCD or OpenStack.

    +
  2. +
  3. +

    Ensure all VMs in the cluster are migrated.

    +
  4. +
+

8. Retrying failed migrations

+

If VM migrations fails, but RDM disks have been successfully managed by Cinder Step 6, migration can be retried.

+

Rollback Plan - If Migration Fails

+

⚠️ Caution:

+

Once an RDM disk is managed in OpenStack or PCD, do not delete the corresponding volume from PCD/OpenStack during a rollback. +Deleting the volume will also remove the associated LUN reference from the storage array, resulting in irreversible data loss.

+

To unmanage an RDM disk safely, use the following command instead of deleting it directly:

+

openstack volume delete <volume-id> --remote

+
    +
  1. +

    Delete VMs created in PCD or OpenStack.

    +
  2. +
  3. +

    Remove the managed volume from OpenStack without deleting it from the SAN array:

    +
    Terminal window
    openstack volume delete <volume-id> --remote
    +
  4. +
+ +
    +
  1. +

    Re-attach RDM disk in VMware to powered-off VMs:

    +
      +
    • Add the reference VMDK disks.
    • +
    • Add New Device > Existing Hard Disk. This will add the disk as a new hard disk.
    • +
    • Change the controller of this hard disk to “New SCSI Controller” which was created in the first step.
    • +
    • For each VM, go to Edit Settings and add the SCSI controller for disk and select physical sharing mode.
    • +
    +

    Repeat this process for all RDM disks.

    +
  2. +
+

Re-attach RDM disk on failure

+
    +
  1. Power on all the VMs on VMware.
  2. +
\ No newline at end of file diff --git a/docs/guides/cli-api/migrating_using_cli_and_kubectl/index.html b/docs/guides/cli-api/migrating_using_cli_and_kubectl/index.html new file mode 100644 index 0000000..9861457 --- /dev/null +++ b/docs/guides/cli-api/migrating_using_cli_and_kubectl/index.html @@ -0,0 +1,352 @@ + Use CLI to Migrate | vJailbreak + Skip to content

Use CLI to Migrate

vJailbreak comprises multiple Kubernetes Controllers which work on Custom Resources (CRs). When we perform the migrations through the UI, the UI itself takes care of creating these CRs for us, hence paving the way for migration to happen. In this tutorial, we will understand how we can migrate a VM using vJailbreak via CLI.

+

Flow of information and resource creation

+

Before moving to the action, let’s understand the various resources that will be created to perform a migration, and how they are related to each other. +image

+

Note: In the diagram above, the specification/configuration flows towards the arrow, and the status travels backwards (from v2v-helper Pod up till MigrationPlan).

+

Glossary

+

Let’s get familiar with the resources mentioned in the diagram above. These resources are used by vJailbreak to perform the migration.

+
    +
  • NetworkMapping: Defines how source VMware networks map to destination OpenStack networks. Required for configuring network interfaces for VM’s during migration.
  • +
  • StorageMapping: Maps source VMware datastores to OpenStack storage backends.
  • +
  • MigrationTemplate: It defines a reusable set of configurations for migrating virtual machines (VM’s) from VMware to OpenStack.
  • +
  • MigrationPlan: MigrationPlan contains the VM’s to be migrated, provides reference to migration template which needs to be followed, and also specifies the way VM’s have to be migrated. A migration plan can be used for migrating multiple VM’s in batches.
  • +
  • Migration: For each VM, there is an individual migration custom resource, which provides reference to the migration plan to be followed and the pod which is going to execute the migration. Migration custom resource is maintained by the vJailbreak controller itself.
  • +
  • Job: With migration custom resource in place, the vJailbreak controller creates a Kubernetes Job, which will in turn create the v2v-helper pod to execute migration for the particular VM mentioned in the Migration custom resource.
  • +
  • v2v-helper pods: Based on the definition of the job created by vJailbreak controller, kubernetes creates a pod to perform the actual VM migration. These pods are temporary workloads that run the helper logic and handle all the migration steps such as image conversion, driver injection, guest customization, transfer, and creation of OpenStack resources.
  • +
+

Note: NetworkMapping, StorageMapping, MigrationTemplate, MigrationPlan, and Migration are Custom Resources.

+

Now that we have a basic understanding of the resources that will be useful during our journey, let’s start on the migration part.

+

How to migrate a VM using CLI or kubectl?

+

What are we going to do?

+

First, we will gather some configuration details. We will use those pieces of configuration details together to create some custom resources, which will be then picked by the Migration Controller, and the migration will take place accordingly. While migration takes place, we will check various resources to monitor the progress of the migration.

+ +

Assumptions

+

Before we start, ensure the following prerequisites are fulfilled:

+
    +
  • vJailbreak is installed and configured properly
  • +
  • VMware vCenter credentials and OpenStack credentials are already set up
  • +
  • User is familiar with Kubernetes and has access to the vJailbreak VM
  • +
  • All the resources will be created in the same namespace
  • +
+

Ensure that you follow the steps in sequence to gather information and create respective Kubernetes objects in sequence to initiate migration for a VM from VMware to PCD.

+

Gather Information

+

For creating the resources, we will need some values to put into the YAML manifests. Let’s gather those values first.

+

Gather the credentials to VMware vCenter and OpenStack

+

Check the resource name for the VMware vCenter credentials using the following command:

+
kubectl get vmwarecreds -n <namespace>
+

Note: The default namespace for vJailbreak ecosystem is migration-system.

+

Copy the name of the VMwareCreds resource that refers to the vCenter hosting the VM you want to migrate. This will be used when creating the MigrationTemplate. For example, if following is the output of the above command:

+
$ kubectl get vmwarecreds -n migration-system
NAME STATUS
vcenter-a Succeeded
+

Then, the name of the VMwareCreds would be vcenter-a.

+

Similarly, check the resource name for the OpenStack credentials using the following command:

+
kubectl get openstackcreds -n <namespace>
+

Copy the name of the OpenstackCreds resource that refers to the OpenStack where you want to migrate the VM. This will also be used when creating the MigrationTemplate.

+

For example, if following is the output of the above command:

+
$ kubectl get openstackcreds -n migration-system
NAME STATUS
openstack-a Succeeded
+

Then the name of the OpenStackcreds would be openstack-a.

+

Gather the details of the VM to be migrated

+

Get the details of the VM using VMwareMachine resource and details are correct:

+
kubectl get vmwaremachine -n <namespace> <vm-name> -o yaml
+

From the YAML, note down the following:

+
    +
  • VM’s name using spec.vms.name
  • +
  • Get the datastores under spec.vms.datastores. This will be used for storage mapping.
  • +
  • Get the networks under spec.vms.networks. This will be used for network mapping.
  • +
  • (Optional) Get the Operating System type under spec.vms.osFamily. The reason for keeping it optional is that vJailbreak can auto-detect the OS most of the time, if the VM is turned on in VMware. For cold migration or migration of a VM that is in powered off state, this field will be required.
  • +
  • (Optional) Note the VM’s spec.vms.cpu and spec.vms.memory. If you don’t explicitly choose a target flavor (see Explicitly select the target OpenStack flavor below), vJailbreak uses these values to auto-select the closest matching OpenStack flavor at migration time.
  • +
+

For example, if following is the output of the above command:

+
$ kubectl get vmwaremachines -n migration-system vm-1 -o yaml
+
apiVersion: vjailbreak.k8s.pf9.io/v1alpha1
kind: VmwareMachine
metadata:
name: vm-1
namespace: migration-system
creationTimestamp: "2025-05-27T09:27:34Z"
generation: 1
labels:
pcd-vm-openstack-a: d3f3d5c7-f6d8-410e-bb9f-a7afe4j8nhg
vmwarecreds.k8s.pf9.io-vcenter-a: "true"
spec:
vms:
clusterName: cluster-1
esxiName: esxi-1
ipAddress: <ip-of-the-vm>
cpu: 1
memory: 4096
name: vm-1
osFamily: linuxGuest
vmState: running
datastores:
- datastore-1
- datastore-2
disks:
- Hard disk 1
- Hard disk 2
networks:
- network-1
- network-2
+

Then:

+
    +
  • The name of the datastores would be: datastore-1 and datastore-2
  • +
  • The name of the networks would be: network-1 and network-2
  • +
  • The OS family would be: linuxGuest
  • +
+

After gathering the information about the source VM’s datastores and networks, we will need to gather the information about the networks and volume types present on the OpenStack, so that we can create proper StorageMapping and NetworkMapping configurations.

+

Gather the details of OpenStack networks and volumeTypes

+

Using the name of the OpenstackCreds resource that we recently checked, run the following command to get the configuration:

+
kubectl get openstackcreds -n <namespace> <openstackcreds-resource-name> -o yaml
+

From the YAML, note down the following:

+
    +
  • List of the networks available on OpenStack at status.openstack.networks
  • +
  • List of volume types available on OpenStack at status.openstack.volumeTypes
  • +
+

For example, if following is the output of the above command:

+
$ kubectl get openstackcreds -n migration-system openstack-a -o yaml
apiVersion: vjailbreak.k8s.pf9.io/v1alpha1
kind: OpenstackCreds
metadata:
name: openstack-a
# …
spec:
# …
status:
openstack:
networks:
- vlan1
- vlan2
- vlan3
volumeTypes:
- lvm
- lvm-class-2
- lvm-class-3
+

Then:

+
    +
  • VMware networks network-1 and network-2 can be translated to OpenStack networks vlan1/vlan2/vlan3
  • +
  • VMware datastores datastore-1 and datastore-2 can be translated to OpenStack volume types lvm/lvm-class-2/lvm-class-3
  • +
+

We will see these translations in action when we create NetworkMapping and StorageMapping resources in the next section, respectively.

+

Creation of Kubernetes resources

+

Now that we have gathered the information that we required, let’s start creating the Kubernetes resources to initiate the migration.

+

Create the StorageMapping custom resource

+

Let’s create the StorageMapping custom resource to ensure that virtual disks are placed correctly in the destination environment. Below is how its manifest would look like:

+
Manifest
+

In the manifest, we need to provide a mapping between the source datastores that we captured from the VMwareMachine (namely datastore-1 and datastore-2), and will map them with the target volume types that we captured from the OpenstackCreds.

+
$ cat storage-mapping.yaml
apiVersion: vjailbreak.k8s.pf9.io/v1alpha1
kind: StorageMapping
metadata:
name: storagemapping
namespace: migration-system
spec:
storages:
- source: datastore-1
target: lvm
- source: datastore-2
target: lvm
+
How to apply this configuration?
+
kubectl apply -f storage-mapping.yaml
+

Read more about StorageMapping in the CRDs reference document.

+

Create the NetworkMapping custom resource

+

Now, let’s create a NetworkMapping custom resource to define how networks will be translated from VMware to OpenStack. Below is how its Kubernetes manifest would look like.

+
Manifest
+

In the manifest, we need to provide target networks for both the networks that we captured from the VM (namely network-1 and network-2), and will map them with the target networks that we captured from the OpenstackCreds.

+
$ cat network-mapping.yaml
apiVersion: vjailbreak.k8s.pf9.io/v1alpha1
kind: NetworkMapping
metadata:
name: networkmapping
namespace: migration-system
spec:
networks:
- source: network-1
target: vlan1
- source: network-2
target: vlan2
+
How to apply this configuration?
+
kubectl apply -f network-mapping.yaml
+

Read more about NetworkMapping in the CRDs reference document.

+

(Optional) Explicitly select the target OpenStack flavor

+

By default, vJailbreak picks the target VM’s OpenStack flavor automatically: it looks for a flavor whose vCPU and RAM exactly match the source VM, or the next best match if there is no exact match. This “best guess” match is based on size alone, so if your OpenStack environment has multiple flavors with the same vCPU/RAM but different extra specs or tags (for example, different host aggregates, GPU passthrough, or hotplug support), vJailbreak may not pick the one you intended.

+

To avoid this, you can explicitly pin a VM to a specific flavor by setting spec.targetFlavorId on its VMwareMachine custom resource, before creating the MigrationPlan.

+

First, find the flavor ID you want to use. Using the OpenStack CLI against your target OpenStack/PCD environment, list the available flavors:

+
openstack flavor list
+

This prints each flavor’s ID, name, vCPU count, RAM, and disk size, for example:

+
+--------------------------------------+--------------+------+------+-----------+-------+-----------+
| ID | Name | RAM | Disk | Ephemeral | VCPUs | Is Public |
+--------------------------------------+--------------+------+------+-----------+-------+-----------+
| d2a1e2d0-1111-4a3e-9c3e-abc123456789 | m1.medium.gpu| 8192 | 40 | 0 | 4 | True |
| f8b2c3d1-2222-4b4f-8d4f-def456789012 | m1.medium | 8192 | 40 | 0 | 4 | True |
+--------------------------------------+--------------+------+------+-----------+-------+-----------+
+

Identify the flavor that matches what you want (by name and/or by inspecting its extra specs/tags with openstack flavor show <flavor-name-or-id>), and copy its ID column.

+

Once you’ve identified the flavor ID you want (d2a1e2d0-1111-4a3e-9c3e-abc123456789 in this example), patch the VMwareMachine resource for the VM being migrated:

+
kubectl patch vmwaremachine -n migration-system <vm-name> --type merge -p '{"spec":{"targetFlavorId":"d2a1e2d0-1111-4a3e-9c3e-abc123456789"}}'
+

You can confirm it was set correctly with:

+
kubectl get vmwaremachine -n migration-system <vm-name> -o jsonpath='{.spec.targetFlavorId}'
+

Note: targetFlavorId is read once, when vJailbreak creates the migration ConfigMap for the VM (as part of MigrationPlan processing). Set it on the VMwareMachine before creating the MigrationPlan for that VM. If it is left blank, vJailbreak falls back to the automatic best-match flavor selection described above.

+

Create the MigrationTemplate custom resource

+

Now that we have StorageMapping and NetworkMapping resources in place, let’s create a MigrationTemplate. As the name suggests, MigrationTemplate can be created once for a set of VMwareCreds, OpenstackCreds and the corresponding NetworkMapping and StorageMapping. This can be reused between various MigrationPlan resources.

+
Manifest
+

In the MigrationTemplate, we need to provide the following details:

+
    +
  • Name of the NetworkMapping resource
  • +
  • Name of the StorageMapping resource
  • +
  • VMwareCreds resource name
  • +
  • OpenstackCreds resource name
  • +
  • Type of the OS (Optional)
  • +
+
$ cat migration-template.yaml
apiVersion: vjailbreak.k8s.pf9.io/v1alpha1
kind: MigrationTemplate
metadata:
name: migrationtemplate
namespace: migration-system
spec:
networkMapping: networkmapping
storageMapping: storagemapping
osFamily: linux # valid values: linux, windows (optional — auto-detected when VM is powered on)
source:
vmwareRef: vcenter-a
destination:
openstackRef: openstack-a
+
How to apply this configuration?
+
kubectl apply -f migration-template.yaml
+

Read more about MigrationTemplate in the CRDs reference document.

+

Create the MigrationPlan custom resource

+

After creating the MigrationTemplate, we need to create the MigrationPlan to configure how migration will be executed. Whether it will be a hot migration, or cold migration? When will the cutover happen? Will the cutover happen automatically or manually? etc. Let’s create a MigrationPlan resource.

+
Manifest
+

In the manifest, we will provide the name of the MigrationTemplate to be used, and various different configurations related to migration execution.

+
$ cat migration-plan.yaml
apiVersion: vjailbreak.k8s.pf9.io/v1alpha1
kind: MigrationPlan
metadata:
name: migrationplan
namespace: migration-system
spec:
migrationTemplate: migrationtemplate
migrationStrategy:
type: hot
adminInitiatedCutOver: false
performHealthChecks: false
healthCheckPort: "443"
virtualMachines:
- - vm-1
+

To know more about all the supported fields for advanced use-cases, refer to the MigrationPlan section of the CRD reference document.

+
Optional MigrationPlan Fields
+

The example above shows the minimum required fields. The following optional fields are also supported:

+
spec:
migrationTemplate: migrationtemplate
+
# Retry the migration automatically if it fails
retry: false
+
# Shell script executed on first boot of the destination VM
firstBootScript: |
echo "Add your startup script here!"
+
# Schedule data copy and cutover windows (RFC 3339 datetime)
migrationStrategy:
type: hot
dataCopyStart: "2024-06-01T02:00:00Z" # when to start copying data
vmCutoverStart: "2024-06-01T04:00:00Z" # start of cutover window
vmCutoverEnd: "2024-06-01T05:00:00Z" # end of cutover window
adminInitiatedCutOver: false
performHealthChecks: false
healthCheckPort: "443"
+
# Post-migration actions on the source VM in VMware
postMigrationAction:
renameVm: true
suffix: "_migrated_to_pcd" # appended to source VM name
moveToFolder: true
folderName: "migrated" # VMware folder to move source VM into
+
# Advanced: override volume types, networks, or ports per VM
advancedOptions:
granularVolumeTypes:
- lvm
granularNetworks:
- vlan1
granularPorts:
- "port-uuid-1"
+
virtualMachines:
- - vm-1
+
How to apply this configuration?
+
kubectl apply -f migration-plan.yaml
+

Now that we have created all the required resources, let’s see what are the additional resources being created in the background, how do they interconnect, how to find out where the migration of a particular VM is happening, and how to check its progress.

+

Monitor the progress

+

Migration (custom resource)

+

Whenever a MigrationPlan is created, vJailbreak controller creates a Migration custom resource for individual VM’s mentioned in the MigrationPlan.

+
Check the Migration custom resource
+

Following the standard naming conventions used by vJailbreak, the Migration custom resource has a name like migration-<vm-name>. We can confirm if the MigrationPlan and VM name are correct or not. To check the specifications of a migration custom resource, run the following command:

+
kubectl get migration -n <namespace> migration-<vm-name> -o yaml
+
    +
  • MigrationPlan will be available under: metadata.labels.migrationplan or spec.migrationPlan
  • +
  • VM’s name will be placed under: spec.vmName
  • +
  • Once sure that the migration resource is related to the VM we want to track the migration of, check the pod prefix under: spec.podRef
  • +
+

Migration Job

+

Migration Job is a Kubernetes Job responsible for migrating one VM from VMware to OpenStack. It runs a pod which performs all tasks required to migrate a single VM including disk extraction, conversion, transfer, and VM creation in the target OpenStack environment. These jobs are created dynamically and are not declared by the user.

+
Check the Migration Job
+
    +
  • Following the standard naming conventions followed by vJailbreak, the job has a name like v2v-helper-<vm-name>.
  • +
  • We can check the status of the job under the status section.
  • +
+

v2v-helper pod

+

v2v-helper pod is the Kubernetes Pod created by Migration Job. It is the runtime unit that performs the actual VM migration, including disk extraction, transformation, and transfer from vSphere to OpenStack. It is the execution engine for migrating one VM. It runs the v2v-helper utility to perform these actions.

+
Check the v2v-helper pod logs
+
    +
  • After getting the pod name from the migration custom resource, you can check the logs of the pod using the following command:
  • +
+
kubectl logs -n <namespace> <pod-name>
+
    +
  • To tail the live logs, we can use the -f flag.
  • +
+

This way, we can automate the migration for n numbers of Virtual Machines, from VMware to OpenStack, using vJailbreak via kubectl.

\ No newline at end of file diff --git a/docs/guides/cluster-conversion/cluster-conversion/index.html b/docs/guides/cluster-conversion/cluster-conversion/index.html new file mode 100644 index 0000000..c533567 --- /dev/null +++ b/docs/guides/cluster-conversion/cluster-conversion/index.html @@ -0,0 +1,263 @@ + Cluster Conversion | vJailbreak + Skip to content

Cluster Conversion

The following outlines the steps to use the vJailbreak RollingConversion feature, which is exclusively available and compatible with Platform9 Private Cloud Director (PCD).

+

Pre-migration checks

+

Before starting the RollingConversion process, ensure the following checks are completed:

+
    +
  1. Ensure vCenter setup is available
  2. +
+
    +
  • vCenter credentials must possess sufficient privileges to isolate ESXi hosts, place them into maintenance mode, and subsequently remove them from the inventory. These privileges are supplementary to existing permissions. The precise set of required permissions will be communicated imminently.
  • +
+
    +
  1. Ensure PCD (Platform9 Private Cloud Director) setup is available and properly configured.
  2. +
+
    +
  • Ensure the ClusterBlueprint is configured. +** Within the Rolling Conversion form in vJailbreak UI, it is necessary to specify the desired network configuration to be implemented on the host subsequent to its conversion and onboarding to Platform Cloud Director (PCD).
  • +
  • Verify network setup on PCD aligns with the ClusterBlueprint specifications.
  • +
  • Make sure one host is already added to accommodate vJailbreak VM and its agents
  • +
+
    +
  1. Ensure Ubuntu MAAS setup is available and configured
  2. +
+
    +
  • Only Ubuntu 22.04 is currently supported for PCD hosts within the Private Cloud Director (PCD).
  • +
  • Ensure that the appropriate PXE image is configured based on requirements; a standard Ubuntu 22 image is recommended.
  • +
  • It is presumed that the ESX hosts are pre-configured as “Machines” in the “Allocated or Deployed” state within MAAS.
  • +
  • IMPORTANT: All ESXi hosts added to MAAS must have IPMI details configured. vJailbreak uses IPMI to control the power state and boot order of the ESXi hosts during the conversion process. Verify the accuracy of the IPMI configuration for all the MAAS machines before starting the rolling conversion.
  • +
+
    +
  1. Backup current configurations and data related to vCenter.
  2. +
  3. Ensure vMotion configured correctly on vCenter
  4. +
+
    +
  • I.e, If an ESX is put in maintenance mode, VMs should be able to move off that ESX
  • +
+
    +
  1. Make sure that all non-vmotion compatible VMs are migrated to the destination PCD or moved off to a single ESXI
  2. +
+
    +
  • When we put an ESXi in maintenance mode, we expect all the VMs to move off of that ESXi and wait for it to become empty.
  • +
  • Even if a single VM is present on that ESXI, we cannot really re-flash it safely.
  • +
+
    +
  1. Ensure enough additional hosts on vCenter (Number of hosts will differ on the exact setup configuration)
  2. +
+
    +
  • NOTE: We put the ESX hosts in maintenance mode one by one, so the VMs on these hosts are moved off until the host becomes empty. This host is further converted to PCD host. The extra hosts are required to accommodate the moved off VMs
  • +
  • NOTE: It is not required to have the equal number of extra hosts, ideally just one.
  • +
+
    +
  1. Check available disk space on target systems within the PCD environment.
  2. +
+

List of incompatible configurations for reference

+
    +
  • VMs with PCIe passthrough or SR-IOV devices
  • +
  • VMs using local host devices (e.g. USB, CD/DVD)
  • +
  • VMs with RDM disks in physical mode
  • +
  • VMs with Fault Tolerance (FT) enabled
  • +
  • VMs with CPU features not compatible across hosts
  • +
  • VMs without Enhanced vMotion Compatibility (EVC) in mixed-CPU clusters
  • +
  • VMs using host-only or non-shared network/storage
  • +
  • VMs with VMCI or special device interfaces
  • +
  • VMs in suspended state (for live vMotion)
  • +
  • VMs with outdated VMware Tools or hardware version
  • +
+

Configuration

+

Create VMware and OpenStack/PCD credential with “PCD” configuration only PCD is supported for rolling upgrade

+

MAAS configuration

+
    +
  • MAAS URL - the MAAS system should be reachable
  • +
  • API Key additionally the MAAS system should be configured to allow the vJailbreak VM to access it with the key
  • +
  • OS the os configuration that vJailbreak would use in MAAS to direct the MAAS to boot the ESXi into PCD hypervisor
  • +
+

Import the ESXi into MAAS

+

If you have ESXi already deployed through MAAS, you can skip this step, else you will need to import ESXis into MAAS so that MAAS can recognize them.

+

How cluster conversion works

+

After you submit the rolling conversion form, vJailbreak will take the following actions in sequence.

+
    +
  1. Verify your creds, especially openstack-creds for checking if they are PCD creds or not
  2. +
  3. Verify Cluster information submitted in the form.
  4. +
  5. Prepare a special cloud-init script that will run post conversion of this host to a ubuntu machine.
  6. +
  7. Go through the list of VMs specified in the rolling conversion form
  8. +
  9. Formulate and save a list of ESXis to be converted.
  10. +
  11. Trigger the conversion process of these ESXi sequentially.
  12. +
  13. Each conversion process includes following steps +
      +
    1. Put that ESXi in maintenance mode
    2. +
    3. Wait for all VMs on that ESXi to move off of this ESXi to other hosts (by DRS/vMotion)
    4. +
    5. Once the ESXi is empty, vJailbreak starts the process of converting it to PCD host +
        +
      1. Fetch list of all available “Machines” in MAAS
      2. +
      3. Find the correct “Machine” for the current ESXi, by checking the hardwareUUID received both from vCenter API and MAAS API
      4. +
      5. Once Machine is found, we fetch its IPMI configuration from MAAS, and use to set this machine PXE (net) boot
      6. +
      7. Release the machine in MaaS
      8. +
      9. Deploy the machine via MaaS, using the special cloud-init created in earlier steps
      10. +
      +
    6. +
    7. Now vJailbreak waits for MaaS to boot that machine to an ubuntu image and run the cloud-init that we provided.
    8. +
    9. Its polling mechanism checks the list of PCD hosts and verifies the hardwareUUID from MaaS with hostID from PCD. (HostID is forcefully set to hardwareUUID in the cloud-init)
    10. +
    11. Once vJailbreak sees the host in PCD in “unauthorised” state, vJailbreak makes API calls to PCD +
        +
      1. To apply the “Host Network Configuration” selected for this host during the “Rolling Conversion Form” submission
      2. +
      3. To provide hypervisor role to this host, with an input of the clusterName used as Target in the “Rolling Conversion Form”
      4. +
      +
    12. +
    13. Wait for the PCD host to converge.
    14. +
    15. Mark the ESXi Conversion as Successful.
    16. +
    +
  14. +
  15. If at least one ESXi conversion is successful, vJailbreak will start to migrate VMs (from the list in “Rolling Conversion Form”) to PCD (to the specified target Cluster)
  16. +
  17. RollingConversion is marked successful if all (selected) ESXi are converted and all (selected) VMs are moved to PCD.
  18. +
\ No newline at end of file diff --git a/docs/guides/cluster-conversion/maas-enablement/index.html b/docs/guides/cluster-conversion/maas-enablement/index.html new file mode 100644 index 0000000..44c4cc8 --- /dev/null +++ b/docs/guides/cluster-conversion/maas-enablement/index.html @@ -0,0 +1,171 @@ + Add ESXi to MAAS | vJailbreak + Skip to content

Add ESXi to MAAS

vJailbreak uses MAAS to manage the ESXi hosts and boot them into PCD hypervisor. +MAAS has a state machine for each machine that it manages. The machines by default are +assumed to be ‘empty’ and available for deployment. The machines are managed by MAAS by booting them with ephemeral image to discover the hardware.

+

Prerequisites

+

IMPORTANT: Before adding ESXi hosts to MAAS, ensure that IPMI (Intelligent Platform Management Interface) is configured and accessible for each ESXi host. vJailbreak requires IPMI access to control the power state and boot order of the ESXi hosts during the rolling conversion process. Without proper IPMI configuration, the conversion process will fail.

+

Verify the following:

+
    +
  • IPMI is enabled on each ESXi host
  • +
  • IPMI credentials (username/password) are available
  • +
  • IPMI interface is network accessible from both the MAAS server and the vJailbreak VM
  • +
  • IPMI power type is supported by MAAS (e.g., ipmi, LAN_2_0)
  • +
+

Adding ESXi to MAAS

+

To enlist an ESXi host into MAAS, you will need to add it to MAAS without MAAS rebooting it +inadvertently. The process in detail is described [here].

+

This requires you to have a MAAS cli. The MAAS cli can be used to add any machine in the +‘deployed’ state.

+
$ maas $profile machines create deployed=true hostname=mymachine \
architecture=amd64 mac_addresses=00:16:3e:df:35:bb power_type=ipmi \
power_parameters_power_address=<IPMI_IP> \
power_parameters_power_user=<IPMI_USER> \
power_parameters_power_pass=<IPMI_PASSWORD>
+

Note: Replace power_type=manual with appropriate IPMI power type and include IPMI credentials as shown above.

\ No newline at end of file diff --git a/docs/guides/how-to/ai_analysis/index.html b/docs/guides/how-to/ai_analysis/index.html new file mode 100644 index 0000000..172ea32 --- /dev/null +++ b/docs/guides/how-to/ai_analysis/index.html @@ -0,0 +1,324 @@ + AI-Powered Migration Failure Analysis | vJailbreak + Skip to content

AI-Powered Migration Failure Analysis

vJailbreak includes an AI analysis feature that inspects failed migration logs, Kubernetes resource conditions, and known failure patterns to identify root causes and suggest fix steps — without manual log triage.

+ +

Prerequisites

+
    +
  • vJailbreak v0.4.8 or later
  • +
  • A failed migration (phase: Failed or ValidationFailed)
  • +
  • An Anthropic API key — get one at console.anthropic.com
  • +
+

Setup

+

1. Configure the Anthropic API key

+
    +
  1. Navigate to Settings → AI in the vJailbreak UI.
  2. +
  3. Enter your Anthropic API key (sk-ant-...).
  4. +
  5. Click Save API Keys.
  6. +
+

The key is stored in a Kubernetes Secret in migration-system and never exposed after saving. The AI service restarts automatically to pick up the new key.

+

2. Verify the AI service is running

+
Terminal window
kubectl -n migration-system get pods -l app=vjailbreak-ai
+

The pod should be in Running state. If not, check logs:

+
Terminal window
kubectl -n migration-system logs -l app=vjailbreak-ai
+

Using AI Analysis

+

From the migration detail page

+
    +
  1. Open a failed migration from the Migrations list.
  2. +
  3. Click the AI Analysis tab (marked Experimental).
  4. +
  5. Click Analyse with AI.
  6. +
+

The AI collects:

+
    +
  • Migration CR conditions (primary signal)
  • +
  • v2v-helper pod exit code and logs
  • +
  • Controller logs
  • +
  • Credential validation status
  • +
  • MigrationPlan spec and template config
  • +
+

It then returns a structured result:

+ + + + + + + + + + + + + + + + + + + + + + + + + +
FieldDescription
Root CauseOne-sentence description of the failure
Fix StepsOrdered remediation steps (max 5)
Confidencehigh / medium / low / none
Doc ReferencesLinks to relevant documentation
+

From the migration logs drawer

+

In the migration logs drawer, an AI Analysis tab appears alongside the Logs tab. Click it to run the same analysis without leaving the log view.

+

Follow-up questions

+

After the initial analysis, ask follow-up questions in the text field at the bottom. The AI retains the analysis context for the conversation.

+

Example questions:

+
    +
  • “What does exit code 137 mean and how do I fix it?”
  • +
  • “How do I check if CBT is enabled?”
  • +
  • “Can I retry without changing the VDDK version?”
  • +
+

Feedback

+

Use the thumbs up / thumbs down buttons to rate the analysis. Feedback helps improve future analyses.

+

Opening a GitHub issue

+

If the root cause is identified, click Open GitHub Issue to pre-fill an issue with the migration conditions and error excerpt. If confidence is none, the AI provides a checklist of data to collect before filing the issue.

+

Confidence levels

+ + + + + + + + + + + + + + + + + + + + + + + + + +
LevelMeaning
highPattern matched exactly; fix is known and confirmed by logs
mediumPhase is clear but exact cause uncertain, or logs are partial
lowPhase identified only; logs missing or too ambiguous
noneCannot determine phase or cause from available signals
+

When confidence is low or none, the first fix step is always diagnostic (gather more data before acting).

+

Known patterns the AI detects

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
SymptomRoot causeFix
"exec: already started"nbdkit/VDDK process init race or stale socketVerify VDDK path, clean up and retry
Pod exit code 137OOM killLarger VM flavor or reduce concurrent migrations
Pod exit code 139Segfault in virt-v2v / libguestfsCheck VDDK version vs ESXi version compatibility
"Failed to connect" + VMware/ESXiDNS not resolving for ESXi hostAdd ESXi entries to /etc/hosts on vJailbreak VM
"permission denied" or "401" + OpenStackExpired or wrong credentialsRevalidate OpenStack credentials
"No space left on device"Scratch disk too small for VMDKExpand /var or configure larger scratch space
"CBT" / "Changed Block Tracking" not enabledHot migration prerequisite missingEnable CBT on source VM in vCenter
"VDDK error" + error codeVDDK library mismatch or license issueVerify VDDK version matches ESXi; check VDDK path
+

Troubleshooting the AI feature

+

”Anthropic API key not configured”

+

Navigate to Settings → AI and save a valid API key.

+

”AI service unavailable”

+
Terminal window
kubectl -n migration-system get pods -l app=vjailbreak-ai
kubectl -n migration-system logs -l app=vjailbreak-ai --tail=50
+

Check that the ANTHROPIC_API_KEY environment variable is populated in the pod — it is injected from the vjailbreak-ai-secret Secret.

+

Analysis returns confidence “none”

+

The AI could not identify the root cause from available logs. Collect the following before filing an issue:

+
    +
  • Full debug logs (use the Download button in the migration logs drawer)
  • +
  • journalctl -u libvirtd -n 200 from the vJailbreak VM
  • +
  • ESXi host version and vCenter version
  • +
  • Whether CBT is enabled on the source VM
  • +
  • VDDK library path: ls -la /home/ubuntu/vmware-vix-disklib-distrib/
  • +
  • vJailbreak version: kubectl -n migration-system get deployment migration-controller-manager -o jsonpath='{.spec.template.spec.containers[0].image}'
  • +
\ No newline at end of file diff --git a/docs/guides/building/index.html b/docs/guides/how-to/building/index.html similarity index 51% rename from docs/guides/building/index.html rename to docs/guides/how-to/building/index.html index 2904498..267caf7 100644 --- a/docs/guides/building/index.html +++ b/docs/guides/how-to/building/index.html @@ -1,4 +1,4 @@ - Building vJailbreak | vJailbreak + Skip to content
Skip to content

Building vJailbreak

vJailbreak is intended to be run in a Kubernetes environment (k3s) on the appliance VM. In order to build and deploy the Kubernetes components, follow the instructions in k8s/migration to build and deploy the custom resources in the cluster.

+

Build vJailbreak

vJailbreak is intended to be run in a Kubernetes environment (k3s) on the appliance VM. In order to build and deploy the Kubernetes components, follow the instructions in k8s/migration to build and deploy the custom resources in the cluster.

In order to build v2v-helper,

-
make v2v-helper
+
make v2v-helper

In order to build migration-controller,

make vjail-controller

In order to build the UI,

make ui
-

Change the image names in the makefile to push to another repository.

\ No newline at end of file +

Change the image names in the makefile to push to another repository.

\ No newline at end of file diff --git a/docs/guides/how-to/enable_kvm_nested_virtualization/index.html b/docs/guides/how-to/enable_kvm_nested_virtualization/index.html new file mode 100644 index 0000000..24b492a --- /dev/null +++ b/docs/guides/how-to/enable_kvm_nested_virtualization/index.html @@ -0,0 +1,245 @@ + Enable KVM for virt-v2v (Nested Virtualization) | vJailbreak + Skip to content

Enable KVM for virt-v2v (Nested Virtualization)

Overview

+

vJailbreak runs virt-v2v inside a Kubernetes pod (v2v-helper) to convert VM disks and inject drivers. By default, virt-v2v uses QEMU as its conversion backend. QEMU can run in two modes:

+ + + + + + + + + + + + + + + + + + + + +
ModeHow it worksPerformance
KVM (hardware-accelerated)Uses /dev/kvm — the host kernel’s KVM moduleFast
TCG (software emulation)Pure software fallback, no /dev/kvm required~10–20× slower
+

When /dev/kvm is not visible inside the pod, QEMU falls back to TCG and logs:

+
qemu-kvm: Could not access KVM kernel module: No such file or directory
qemu-kvm: falling back to tcg
+

Conversions still succeed but take significantly longer.

+

How vJailbreak exposes /dev/kvm

+

The v2v-helper pod already mounts the host /dev directory into the pod at /dev via a hostPath volume. This means if /dev/kvm exists on the vJailbreak VM, it is automatically visible inside the pod — no additional configuration is required.

+

The condition that must be met is that the vJailbreak VM itself has access to KVM, which requires nested virtualization to be enabled at every layer of the stack.

+

⚠️ Caution — this affects all VMs on the compute host, not just vjailbreak.

+

Enabling Nested Virtualization

+

Step 1 — Enable nested KVM on the compute host

+

On each compute host that will run vjailbreak VMs and agents:

+
cat /sys/module/kvm_intel/parameters/nested # expect Y (or 1)
# AMD: cat /sys/module/kvm_amd/parameters/nested
+

If disabled, enable it persistently and reload:

+
# Intel
echo "options kvm-intel nested=1" | sudo tee /etc/modprobe.d/kvm-nested.conf
# AMD
# echo "options kvm-amd nested=1" | sudo tee /etc/modprobe.d/kvm-nested.conf
+
sudo modprobe -r kvm_intel && sudo modprobe kvm_intel # or reboot the host
+

Step 2 — Set the CPU mode in nova_override.conf

+

[libvirt] section of /opt/pf9/etc/nova/conf.d/nova_override.conf on each +compute host.

+

Recommended — host-passthrough (passes vmx/svm through automatically):

+
[libvirt]
cpu_mode = host-passthrough
+

Alternative — host-model (must add the flag explicitly; host-model does +not expose vmx/svm by default):

+
[libvirt]
cpu_mode = host-model
cpu_model_extra_flags = vmx # use svm on AMD
+
+

Note: host-passthrough ties the VM to a host with a closely matching +CPU, model, and microcode for live migration. For vjailbreak — a +short-lived, migration-tool VM — this is rarely a concern.

+
+

Step 3 — Apply

+

Restart Nova on the compute host, then hard-reboot the vjailbreak VM so it picks +up the new CPU definition (a soft reboot is not enough):

+
sudo systemctl restart pf9-ostackhost
openstack server reboot --hard <instance-uuid>
+

Step 4 — Verify

+

Inside the vjailbreak VM, confirm the flag is present:

+
grep -E -o 'vmx|svm' /proc/cpuinfo | sort -u # expect vmx (Intel) or svm (AMD)
ls /dev/kvm # device node should exist
+

If virt-v2v still falls back to TCG (no acceleration), check that +/dev/kvm is present and that kvm-ok (if available) reports acceleration can +be used.

+

Pod (automatic)

+

No changes are needed. The pod’s /dev hostPath mount makes /dev/kvm visible to the v2v-helper container as soon as it exists on the vJailbreak VM.

+

Verifying KVM is in use

+

After enabling nested virtualization and restarting a migration, inspect the v2v-helper pod logs. You should not see the TCG fallback warning. Instead, look for QEMU initializing with KVM:

+
Terminal window
kubectl -n migration-system logs <migration-name>-v2v-helper | grep -i kvm
+

A healthy KVM-accelerated run shows no falling back to tcg messages and noticeably faster conversion times.

+

Troubleshooting

+ + + + + + + + + + + + + + + + + + + + + + + + + +
SymptomLikely causeFix
Could not access KVM kernel module in logs/dev/kvm missing on vJailbreak VMEnable nested virt on the outer hypervisor and load the KVM module
kvm_intel/kvm_amd module fails to loadVirtualization extensions not exposed by hypervisorConfigure the outer hypervisor to pass through CPU virt flags
/dev/kvm present on VM but not in podUnlikely — the hostPath mount covers all of /devConfirm the pod spec has the /dev hostPath volume (default in vJailbreak)
\ No newline at end of file diff --git a/docs/guides/how-to/firstboot_script_doc/index.html b/docs/guides/how-to/firstboot_script_doc/index.html new file mode 100644 index 0000000..9ad8940 --- /dev/null +++ b/docs/guides/how-to/firstboot_script_doc/index.html @@ -0,0 +1,368 @@ + Firstboot Script | vJailbreak + Skip to content

Firstboot Script

Overview

+

The Firstboot Script feature allows users to run custom scripts automatically on virtual machines (VMs) immediately after they are migrated to Platform9 Cloud Director (PCD) or OpenStack environments. This capability is essential for automating post-migration configurations, installations, and other setup tasks that need to be performed on the VM upon its first boot.

+

Following are some use cases for Firstboot Scripts:

+
    +
  1. Installing or updating required software
  2. +
  3. Removing VMware-specific tools or drivers
  4. +
  5. Applying system or network configuration
  6. +
  7. Running environment-specific initialization tasks
  8. +
  9. Executing multiple setup steps sequentially after migration
  10. +
+

The feature supports multiple script blocks, OS-specific targeting, and independent execution of user-provided scripts.

+

Allowed Script Formats

+

User-provided script content depends on the guest operating system.

+
    +
  1. WindowsGuests: Powershell (.ps1)
  2. +
  3. LinuxGuests: sh, bash (.sh)
  4. +
+
+

Multiple Script Blocks

+

You can include multiple script blocks in a single migration plan.
+Separate each script block using the delimiter:

+
### NEXT SCRIPT ###
+

Example:

+
// WINDOWS-SCRIPT:
Write-Host "Running Windows script part 1"
+
### NEXT SCRIPT ###
+
// WINDOWS-SCRIPT:
Write-Host "Script 2 failing intentionally"
throw "Failure"
+
### NEXT SCRIPT ###
+
// WINDOWS-SCRIPT:
Write-Host "Script 3 still runs"
+

Each block runs independently. If one script block fails, later blocks will still execute.

+

Execution Rules

+ + + + + + + + + + + + + + + + + + + + + +
Script TagExecution Behavior
WINDOWS-SCRIPT:Runs only on Windows VMs
LINUX-SCRIPT:Runs only on Linux VMs
No tagRuns on all VMs
+
+

For migration plans containing both Windows and Linux VMs, OS tags are strongly recommended.

+
+
+

Adding a Firstboot Script in the Migration Form

+

To configure a post-migration firstboot script:

+
    +
  1. Open the Migration Form
  2. +
  3. Navigate to the Migration Options section
  4. +
  5. Enable Enable Script under Post Migration Script
  6. +
  7. Paste the script content into the script field
  8. +
  9. Separate multiple scripts using ### NEXT SCRIPT ###
  10. +
  11. Use OS tags if the migration plan includes different operating systems
  12. +
  13. Start the migration
  14. +
+

img1 +img1

+
+

Note:
+Untagged script blocks run on all selected VMs.

+
+
+

How Firstboot Scripts Work

+

End-to-End Execution Flow

+
    +
  1. The user enables Post Migration Script in the migration form.
  2. +
  3. The script content is stored in the migration plan as firstBootScript.
  4. +
  5. The migration controller generates a per-VM ConfigMap containing the script.
  6. +
  7. The ConfigMap is mounted into the v2v-helper pod at /home/fedora/scripts.
  8. +
  9. During conversion, the helper reads the script and splits it into blocks using ### NEXT SCRIPT ###.
  10. +
  11. Script blocks are filtered based on OS tags.
  12. +
  13. The system prepares OS-specific execution:
  14. +
+ + + + + + + + + + + + + + + + + +
OSExecution Model
LinuxScripts are embedded into a generated wrapper
WindowsScripts are converted into PowerShell parts and executed through a scheduler
+
    +
  1. When the migrated VM boots for the first time, the prepared scripts execute automatically but needs multiple reboots to complete.
  2. +
+ +

Linux Execution Model

+

For Linux guests, applicable script blocks are combined into a generated wrapper script.

+

The wrapper:

+
    +
  • Executes each user script block using Bash
  • +
  • Continues execution even if one block fails
  • +
  • Logs warnings when failures occur
  • +
+
+

Windows Execution Model

+

For Windows guests, applicable script blocks are converted into PowerShell script parts.

+

Example generated scripts:

+
user_firstboot_part_001.ps1
user_firstboot_part_002.ps1
+

These scripts are executed using a Windows Firstboot Scheduler.

+
+

Windows Firstboot Scheduler

+

The Windows Firstboot Scheduler orchestrates execution of built-in and user-provided scripts.
+It ensures scripts run safely and continue even if reboots occur.

+

Scheduler Responsibilities

+

The scheduler:

+
    +
  • Executes scripts sequentially
  • +
  • Tracks execution progress
  • +
  • Survives system reboot
  • +
  • Retries failed scripts
  • +
  • Continues later scripts even if earlier user scripts fail
  • +
+

Scheduler Files

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FilePurpose
C:\firstboot\0-Firstboot-Scheduler.ps1Main scheduler script
C:\firstboot\Firstboot-Scheduler.logScheduler execution log
C:\firstboot\Firstboot-Scheduler_init.logScheduler initialization log
C:\firstboot\Firstboot-Scheduler.stateExecution state tracking
C:\firstboot\scripts.jsonScript metadata
+
+

Troubleshooting

+

Windows Guests

+

Primary troubleshooting locations:

+ + + + + + + + + + + + + + + + + + + + + + + + + +
FilePurpose
C:\firstboot\Firstboot-Scheduler.logScheduler execution logs
C:\firstboot\Firstboot-Scheduler_init.logScheduler initialization logs
C:\firstboot\Firstboot-Scheduler.stateScheduler execution state
C:\firstboot\scripts.jsonScript metadata
+

You may also check the guestfs log:

+
C:\Program Files\Guestfs\Firstboot\log.txt
+

This log confirms that the injected firstboot mechanism started successfully.

+

Linux Guests

+

Check the firstboot execution log with elevated privileges:

+
/root/virt-sysprep-firstboot.log
+

This log contains:

+
    +
  • Output from each script block
  • +
  • Errors encountered during execution
  • +
  • Warnings for failed script blocks
  • +
+ +
    +
  1. Windows VMware tools Removal Script - A script to remove VMware tools/Drivers from Windows VMs
  2. +
  3. VMware Residual Artifacts Documentation - Scan-based record of leftover VMware artifacts after uninstallation in v0.4.1
  4. +
\ No newline at end of file diff --git a/docs/guides/how-to/gpo_migration/index.html b/docs/guides/how-to/gpo_migration/index.html new file mode 100644 index 0000000..7305923 --- /dev/null +++ b/docs/guides/how-to/gpo_migration/index.html @@ -0,0 +1,190 @@ + Group Policy Object (GPO) VM Migration | vJailbreak + Skip to content

Group Policy Object (GPO) VM Migration

Overview

+

Some Group Policy Object (GPO) settings can interfere with driver injection, which is critical for a successful VM migration. GPO is a Windows-specific feature that manages system settings for Windows VMs. Temporarily disabling these policies ensures that driver injection can proceed reliably.

+

What is Affected by GPO

+

Driver installation cannot be done properly, leading to:

+
    +
  • Blue Screen of Death (BSOD)
  • +
  • Windows VM getting stuck in boot loop
  • +
  • Migration failures due to hardware driver conflicts
  • +
+

What to Do

+

Option 1: Disable via GUI - Step by Step

+
    +
  1. +

    Open Group Policy Editor (gpedit.msc)

    +
  2. +
  3. +

    Navigate to Driver Policies

    +

    Go to:

    +
    Computer Configuration
    → Administrative Templates
    → System
    → Device Installation
    +
  4. +
  5. +

    Disable ALL restrictive policies here

    +

    Check and set these to Not Configured (or Disabled where applicable):

    +
      +
    • 🚫 Critical ones (must fix) +
        +
      • Prevent installation of devices not described by other policy settings → Set to Not Configured
      • +
      • Prevent installation of devices that match any of these device IDs → Not Configured
      • +
      • Prevent installation of devices using drivers that match these device setup classes → Not Configured
      • +
      +
    • +
    +
  6. +
+

Option 2: Sure Shot Way - Remove GPO via PowerShell

+
Terminal window
Remove-Item -Recurse -Force "C:\Windows\System32\GroupPolicy" -ErrorAction SilentlyContinue
Remove-Item -Recurse -Force "C:\Windows\System32\GroupPolicyUsers" -ErrorAction SilentlyContinue
Remove-Item -Recurse -Force "HKLM:\Software\Policies" -ErrorAction SilentlyContinue
Remove-Item -Recurse -Force "HKCU:\Software\Policies" -ErrorAction SilentlyContinue
+
gpupdate /force
+

The PowerShell method completely removes all local Group Policy settings. Use this only when GUI methods fail or when you need to ensure complete GPO removal.

\ No newline at end of file diff --git a/docs/guides/how-to/injecting_custom_env/index.html b/docs/guides/how-to/injecting_custom_env/index.html new file mode 100644 index 0000000..be5d920 --- /dev/null +++ b/docs/guides/how-to/injecting_custom_env/index.html @@ -0,0 +1,195 @@ + Inject Environment Variables | vJailbreak + Skip to content

Inject Environment Variables

Injecting environment variables into the VJB pods is a feature that allows users to inject environment variables into the VJB pods using a Kubernetes ConfigMap.

+

Injecting Environment Variables During vJailbreak VM Provisioning

+
    +
  1. +

    Cloud-init populates environment variables

    +

    Users must provide environment variables in the /etc/pf9/env file during provisioning, typically using a cloud-init script.

    +

    Example cloud-init configuration using write_files:

    +
    write_files:
    - path: /etc/pf9/env
    content: |
    http_proxy=http://<proxy-server>:<proxy-port>
    https_proxy=http://<proxy-server>:<proxy-port>
    no_proxy=localhost,127.0.0.1
    permissions: '0644'
    +
  2. +
  3. +

    ConfigMap creation from /etc/pf9/env

    +

    A helper script or manual command reads /etc/pf9/env and creates a Kubernetes ConfigMap named pf9-env. +This is done while the vjailbreak VM is being provisioned.

    +
    Terminal window
    kubectl create configmap pf9-env --from-env-file=/etc/pf9/env -n migration-system
    +

    You can either populate the /etc/pf9/env file via cloud-init or manually.

    +

    If done manually please follow the steps mentioned in Injecting Environment Variables Post-Provisioning:

    +

    Now this will be picked up by the v2v-helper pod and the proxy variables will be available in the pod and it would be respected by the v2v-helper pod.

    +
  4. +
+

Injecting Environment Variables Post-Provisioning

+

If you would like to inject environment variables after the vjailbreak VM has been provisioned, follow these steps:

+
    +
  1. +

    Delete the existing ConfigMap

    +
    Terminal window
    kubectl delete configmap pf9-env -n migration-system
    +
  2. +
  3. +

    Populate the /etc/pf9/env file with whatever env variables needed

    +
    Terminal window
    echo "http_proxy=http://<proxy-server>:<proxy-port>" >> /etc/pf9/env
    echo "https_proxy=http://<proxy-server>:<proxy-port>" >> /etc/pf9/env
    echo "no_proxy=localhost,127.0.0.1" >> /etc/pf9/env
    +
  4. +
  5. +

    Create a new ConfigMap

    +
    Terminal window
    kubectl create configmap pf9-env --from-env-file=/etc/pf9/env -n migration-system
    +
  6. +
  7. +

    Restart the controller manager deployment

    +
    Terminal window
    kubectl rollout restart deployment migration-controller-manager -n migration-system
    +
  8. +
  9. +

    Trigger a new migration for the envs to be reflected in the pod

    +

    Trigger via UI or via api.

    +
  10. +
\ No newline at end of file diff --git a/docs/guides/how-to/migration_templates/index.html b/docs/guides/how-to/migration_templates/index.html new file mode 100644 index 0000000..35a593b --- /dev/null +++ b/docs/guides/how-to/migration_templates/index.html @@ -0,0 +1,350 @@ + Migration Templates | vJailbreak + Skip to content

Migration Templates

Operators who migrate many VMs usually reuse the same configuration: the same source vCenter, the same destination cluster, the same network and storage mappings, the same copy method and cutover policy. A Migration Template saves that configuration once so every later migration can start from it.

+

A template stores everything the migration form asks for except which VMs to migrate. You pick the VMs fresh each time.

+

Templates only pre-fill the form. They do not change how a migration runs — a migration started from a template behaves exactly like one configured by hand.

+

Where templates live

+

Open Migrations in the vJailbreak UI. It has two tabs, each with a count badge:

+
    +
  • Migrations — the existing migration list.
  • +
  • Templates — saved templates.
  • +
+

The Templates tab toolbar sits inline with the tabs and offers:

+ + + + + + + + + + + + + + + + + + + + + + + + + +
ControlBehavior
SearchMatches the template name and description
Filter (funnel icon)Filters by migration type — Hot copy, Cold copy, or Mock copy. Choose Clear filter to remove it.
SortNewest (default) or Name
Grid / list toggleCard grid (default) or a dense table
+

Prerequisites

+

At least one VMware credential and one PCD credential must exist. Until both are present, Create New Template and Save as template are disabled.

+

What a template saves

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
GroupSaved values
SourceVMware credential, source vCenter cluster
DestinationPCD credential, target PCD cluster
MappingsNetwork mappings, storage mappings, datastore-to-array-credential mappings
CopyStorage copy method (standard, vJailbreak Accelerated Copy with its proxy VM, or Storage Accelerated Copy), migration type (hot, cold, or mock), data copy start time
CutoverCutover option — immediate, time window, or admin-initiated — with its start and end times
PlacementSecurity groups, server group, DHCP fallback, disconnect source network
Post-migrationFirst boot script, post-migration actions (rename VM, move to folder)
MetadataPreserve source tags, custom metadata key-value pairs
AdvancedNetwork persistence, remove VMware Tools, periodic sync and its interval, image profiles, network conflict acknowledgement, GPU flavor, data-only mode, guest OS family
+

Not saved, by design:

+
    +
  • The VM selection, and anything attached to a specific VM — assigned IPs, MAC preservation, and per-VM flavor.
  • +
+

Create a template

+

There are three ways to create one.

+

From a migration you are configuring

+
    +
  1. Click Start Migration and fill in the form.
  2. +
  3. Click Save as template at the bottom left of the form.
  4. +
  5. Enter a name, optionally a description, and click Save template.
  6. +
+

The form stays open — saving a template does not start or cancel the migration you were configuring.

+

From the Templates tab

+

Use this when your goal is the template itself, not a migration.

+
    +
  1. Open the Templates tab and click Create New Template.
  2. +
  3. The migration form opens in Create Template mode. It differs from the normal form in three ways: +
      +
    • There is no Select VMs step.
    • +
    • The mapping step lists every network and datastore in the selected source cluster, instead of only those used by selected VMs.
    • +
    • Partial mappings are allowed — you do not have to map everything for the template to be valid.
    • +
    +
  4. +
  5. Click Create Template, name it, and save.
  6. +
+

No migration or migration plan is created by this flow.

+

By cloning

+

Click the clone icon on a template card or list row. The copy is named <original name> (copy), or <original name> (copy) 2 if that name is taken. The original is untouched.

+

Naming rules

+

Template names must be unique.

+

Use a template

+

Click Use on a template card, list row, or in its detail drawer. The Start Migration form opens with the template’s configuration applied, and every field remains editable.

+

You still choose the VMs. Because mappings are saved for the whole cluster while a migration maps only what the selected VMs use, vJailbreak keeps the template’s mappings aside and applies them as they become relevant:

+
    +
  • Before you select any VM, saved mappings are not shown — none of their sources are in play yet.
  • +
  • As you select VMs, each saved mapping whose source network or datastore appears, and whose target still exists on the destination, is applied automatically.
  • +
  • De-selecting a VM removes the mappings only that VM needed. Re-selecting it brings them back.
  • +
  • A mapping you delete by hand stays deleted, and is not re-applied when the VM list changes.
  • +
+

Submitting the form creates an ordinary migration. The template is not modified, and no link is kept between the two.

+

View template details

+

Click a card or row (anywhere except an action button) to open the detail drawer:

+
    +
  • Name, description, and created date
  • +
  • Source & Destination — source credentials, destination credentials, tenant or project, target cluster
  • +
  • Network & Storage Mappings — every mapping pair, plus the storage copy method
  • +
  • Migration Options — migration mode, cutover, guest OS
  • +
  • Advanced options — each option that is set, with its value
  • +
+

The drawer also carries Use, Edit, Clone, and Delete actions.

+

Edit a template

+

Templates are editable in place, so infrastructure changes do not force you to delete and recreate them.

+
    +
  1. Click the pencil icon on the card or list row, or Edit in the detail drawer.
  2. +
  3. The form opens in Edit Template mode, pre-filled the same way Use pre-fills it. As in Create Template mode, there is no VM step.
  4. +
  5. Change what you need and click Save Changes.
  6. +
+

The same template is updated — no duplicate is created, and the name and position in the list stay the same. You can also change the name and description in the save dialog.

+

If someone else changed the same template while your form was open, the save fails with an error instead of silently overwriting their change. Reopen the template and reapply your edits.

+

Delete a template

+

Click the trash icon on the card or list row, or Delete in the detail drawer, then confirm. Deleting a template does not affect migrations already created from it — they hold their own copy of the configuration and continue to run and display normally.

+

Templates from the command line

+

Saved templates are MigrationBlueprint custom resources in the migration-system namespace:

+
Terminal window
kubectl -n migration-system get migrationblueprints
kubectl -n migration-system get migrationblueprint <name> -o yaml
+

These are separate from the internal, per-session MigrationTemplate objects that the migration form creates and cleans up on its own. See vJailbreak CRDs for the field list.

+

Troubleshooting

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
SymptomCauseWhat to do
Create New Template and Save as template are disabledNo VMware or no PCD credential existsAdd both credentials first
Cluster dropdowns are empty after clicking UseThe saved source or target cluster no longer exists, or its name changedSelect the clusters manually.
Saved mappings do not appear after UseExpected until VMs are selectedSelect VMs. Mappings apply as their source networks and datastores come into scope.
A mapping never reappears after being removedMappings deleted by hand are not re-appliedAdd the mapping again manually
Saving reports “A template named … already exists”Names are unique, ignoring casePick a different name
Save Changes fails on an editThe template changed elsewhere since the form openedReopen the template and reapply the change
Templates tab shows “No templates match”A search term or type filter is activeClear the search box, or choose Clear filter in the filter menu
\ No newline at end of file diff --git a/docs/guides/how-to/network-traffic-separation/index.html b/docs/guides/how-to/network-traffic-separation/index.html new file mode 100644 index 0000000..3bd3c37 --- /dev/null +++ b/docs/guides/how-to/network-traffic-separation/index.html @@ -0,0 +1,250 @@ + Configuring Dedicated Data Network for Migrations | vJailbreak + Skip to content

Configuring Dedicated Data Network for Migrations

In environments where security requirements mandate separate networks for control-plane and storage traffic (e.g., a VMware underlay with a dedicated storage network that differs from the PCD host underlay), a single shared network path for both vCenter API calls and disk-copy data can cause congestion, dropped transfers, or asymmetric routing failures.

+

This guide explains the traffic separation design and the routing configuration required on the vJailbreak VM.

+

Architecture Overview

+

VMware to KVM migration dedicated data network diagram

+

The core design: keep vCenter’s control-plane traffic and vJailbreak’s disk-copy traffic on physically distinct paths rather than sharing one network.

+

ESXi Side

+

The ESXi host exposes two interfaces:

+ + + + + + + + + + + + + + + + + +
InterfaceRole
iface1Management — vCenter API calls, general connectivity
iface2NFC data copy — tagged with nfc_flag, bound to VMware’s NFC (Network File Copy) service. ESXi streams disk data through this interface during migrations.
+

This split is enforced at the ESXi interface level. Management and data movement never contend for the same physical path.

+

vJailbreak VM Side

+

The vJailbreak VM mirrors the same split with two bridges and bonds:

+ + + + + + + + + + + + + + + + + + + + + + + +
PathBridge / BondSegmentTraffic
ManagementBridge-1 / Bond0seg1 (default)vCenter API, management, internet
StorageBridge-2 / Bond1seg2Disk data copy from ESXi NFC service
+

Critical: Routing Table Configuration

+

This is the most operationally important detail.

+

When vJailbreak issues a disk-copy request, ESXi responds using iface2’s IP — because iface2 is the interface tagged for NFC traffic. If the vJailbreak VM’s outbound route for that request is not explicitly pinned to the storage interface (Bridge-2 / Bond1), the following problems occur:

+
    +
  • Asymmetric routing — request exits via the management interface, response arrives via the storage interface; the connection is dropped.
  • +
  • Silent fallback — data copy traffic falls back onto the management network, starving it of bandwidth.
  • +
  • Transfer failures — the NBD copy fails or times out due to IP mismatch on ESXi’s NFC response path.
  • +
+

Required Routing Rule

+

On the vJailbreak VM, add an explicit route so that any traffic destined for ESXi’s NFC/data-copy service (iface2’s IP subnet) is routed out through the storage interface:

+
Terminal window
# Example: route ESXi storage subnet via the storage interface
ip route add <esxi-nfc-subnet>/24 via <gateway> dev <bond1-or-bridge2-interface>
+

Replace:

+
    +
  • <esxi-nfc-subnet> — the subnet of ESXi’s iface2 (NFC-tagged interface)
  • +
  • <gateway> — the gateway on the storage network segment
  • +
  • <bond1-or-bridge2-interface> — the interface name for Bridge-2/Bond1 on the vJailbreak VM
  • +
+

To make this persistent across reboots, add the route to your network configuration (e.g., /etc/netplan/*.yaml or /etc/network/interfaces depending on the OS).

+

Verifying the Route

+

After adding the route, confirm that traffic to the ESXi NFC IP exits via the correct interface:

+
Terminal window
ip route get <esxi-iface2-ip>
+

Expected output should show the storage interface (Bond1/Bridge-2), not the management interface.

+

Summary

+ + + + + + + + + + + + + + + + + + + + + +
ConcernSolution
Management traffic contending with data copySeparate physical interfaces on both ESXi and vJailbreak VM
ESXi NFC responses routed incorrectlyExplicit ip route rule on vJailbreak VM pinning NFC subnet to storage interface
Route lost on rebootPersist route in OS network configuration
+

Without this routing configuration, bulk disk-copy traffic will bleed onto the management network regardless of the physical interface separation, because ESXi’s NFC response will always use iface2’s IP and the OS will drop the asymmetric connection.

\ No newline at end of file diff --git a/docs/guides/how-to/networking-101/index.html b/docs/guides/how-to/networking-101/index.html new file mode 100644 index 0000000..f0ea921 --- /dev/null +++ b/docs/guides/how-to/networking-101/index.html @@ -0,0 +1,785 @@ + One-Stop Migration Networking | vJailbreak + Skip to content

One-Stop Migration Networking

Network settings are the most common cause of failed or surprising migrations. This guide is organized by situation: find the scenario that matches yours, apply the settings, and check the expected outcome before you migrate.

+

Every scenario reports two separate outcomes:

+
    +
  • The OpenStack port — the address and MAC that OpenStack assigns to the network port.
  • +
  • Inside the guest — what the migrated VM’s own network configuration looks like on first boot.
  • +
+

These two can differ. A preserved IP address on the port does not always mean the guest is statically configured to match it.

+ +

Advanced options for migration

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
SettingWhat it controls
Preserve IPCarry the discovered source IP onto the OpenStack port.
IP address boxLeave as-is (discovered IP), type one IPv4 address, or leave empty.
Preserve MACCarry the source MAC, or let OpenStack generate a new one.
Fallback to DHCPOn = accept an OpenStack-assigned address rather than fail. Off = stop the migration on conflict or mismatch.
Persist source network interfacesRestore the original interface names inside the guest. Only available when Preserve IP is on.
+

Table of scenarios

+

Find the situation that matches yours, read across the row for the settings to apply, then follow the link to the detailed block — expected outcome, port and guest behavior, and the caveats.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ScenarioPreserve IPIP address boxPreserve MACFallback to DHCPPersist source network interfacesDetails
A — Same subnet: keep the exact IP and MAC, fail if the address is takenOnLeave as-isOnOffOnScenario A →
B — Specific new IPOffType one IPv4 addressEitherOff, or on if a DHCP address is acceptableUnavailableScenario B →
C — Everything on DHCPOff on every NICEmptyEitherOn — requiredUnavailableScenario C →
D — Different subnetOffEmpty, or an address valid in the new subnetEitherOn — requiredUnavailableScenario D →
E — Same subnet, bulk move: prefer the IP but accept DHCP rather than failOnLeave as-isOnOnOnScenario E →
F — L2-only networkNo effect on the portIgnoredEither — on to keep the MACGreyed outYour choiceScenario F →
G — Same IP, new MACOnLeave as-isOffYour choiceYour choiceScenario G →
H — Source VM powered offForced off (greyed out)Type an address, or empty for DHCPEither — usually onOn if the address box is emptyUnavailableScenario H →
I — Port with no addressOffEmptyEitherOffUnavailableScenario I →
J — Preserve IP and MAC, Persist Network offOnLeave as-isOnYour choiceOffScenario J →
+

Reading the table

+
    +
  • Either / Your choice — the setting does not change the outcome the scenario describes.
  • +
  • Unavailable — the UI disables Persist source network interfaces whenever Preserve IP is off.
  • +
  • Greyed out — the UI disables the setting for this scenario; you cannot change it.
  • +
  • Leave as-is — do not edit the box; it already shows the discovered IP.
  • +
+ +
+

A — Keep the same IP and the same MAC

+

Use this when

+
    +
  • The destination network carries the same subnet as the source.
  • +
  • The VM is powered on and its IP was discovered correctly.
  • +
  • DNS records, firewall rules, or MAC-locked licenses depend on the address surviving the move.
  • +
+

Settings

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
SettingValue
Preserve IPOn
IP address boxLeave as-is (shows the discovered IP)
Preserve MACOn
Fallback to DHCPOff — you want the migration to stop rather than silently change the address
Persist source network interfacesOn — keeps the original interface names too
+

What you get

+
    +
  • OpenStack port: the same IP address, provided nothing else in the network already holds it.
  • +
  • Port MAC: the same MAC address.
  • +
  • Inside the guest: unchanged. A static NIC stays static on the same address; a DHCP NIC stays on DHCP. The original interface names are restored.
  • +
+ +

B — Assign a specific new IP address

+

Use this when

+
    +
  • You are re-addressing the VM as part of the move and already know the address it should get.
  • +
+

Settings

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
SettingValue
Preserve IPOff
IP address boxType the new address — one IPv4 address only
Preserve MACEither — your choice
Fallback to DHCPOff if a wrong address should stop the migration; on if a DHCP address is an acceptable substitute
Persist source network interfacesUnavailable
+

What you get

+
    +
  • OpenStack port: the address you typed, provided it belongs to a subnet on the destination network. +
      +
    • If it does not belong to a subnet: the migration fails (Fallback off), or the port gets an OpenStack-assigned address (Fallback on).
    • +
    +
  • +
  • Inside the guest: DHCP configuration on the assigned address.
  • +
+ +

C — Force everything onto DHCP

+

Use this when

+
    +
  • You want a clean start: OpenStack allocates all addresses and no source addressing is carried over.
  • +
+

Settings

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
SettingValue
Preserve IPOff on every NIC
IP address boxEmpty
Preserve MACEither — your choice
Fallback to DHCPOn — required, see below
Persist source network interfacesUnavailable
+

What you get

+
    +
  • OpenStack port: an address allocated by OpenStack.
  • +
  • Port MAC: preserved or newly generated, per your choice.
  • +
  • Inside the guest: DHCP.
  • +
+ +

D — The destination is on a different subnet

+

Use this when

+
    +
  • The destination OpenStack network does not carry the source VM’s subnet.
  • +
+

This is the most common cause of failed migrations.

+

Settings

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
SettingValue
Preserve IPOff
IP address boxEmpty (or type an address valid in the new subnet — see Scenario B)
Preserve MACEither — your choice
Fallback to DHCPOn — required
Persist source network interfacesUnavailable — the UI greys it out automatically
+

What you get

+
    +
  • OpenStack port: an address allocated by OpenStack from the destination subnet.
  • +
  • Port MAC: the same MAC if Preserve MAC is on, otherwise a newly generated one.
  • +
  • Inside the guest: DHCP.
  • +
+ +

E — Prefer the same IP, but accept DHCP rather than fail

+

Use this when

+
    +
  • Same subnet as Scenario A, but you are migrating in bulk and would rather a few VMs come up on a different address than have the whole batch stop.
  • +
+

Settings

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
SettingValue
Preserve IPOn
IP address boxLeave as-is
Preserve MACOn
Fallback to DHCPOn
Persist source network interfacesOn
+

What you get

+
    +
  • OpenStack port: the same IP where possible; an OpenStack-assigned address where not.
  • +
  • Port MAC: the same MAC address.
  • +
  • Inside the guest: the original static configuration where the address was preserved; DHCP where it was not.
  • +
+ +

F — The destination is an L2-only network

+

Use this when

+
    +
  • The destination OpenStack network is tagged as an L2 network and has no subnets.
  • +
+

Settings

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
SettingValue
Preserve IPNo effect on the port — no fixed IPs can be assigned
IP address boxIgnored
Preserve MACWorks normally — keep it on if you need the MAC
Fallback to DHCPGreyed out
Persist source network interfacesYour choice
+

What you get

+
    +
  • OpenStack port: created with no fixed IP addresses.
  • +
  • Port MAC: preserved or newly generated, per your choice.
  • +
  • Inside the guest (Ubuntu with netplan): a wildcard configuration puts every interface on DHCP.
  • +
+ +

G — Keep the IP but let OpenStack pick a new MAC

+

Use this when

+
    +
  • The source MAC clashes with something in the destination, or you are deliberately re-issuing hardware addresses.
  • +
+

Settings

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
SettingValue
Preserve IPOn
IP address boxLeave as-is
Preserve MACOff
Fallback to DHCPYour choice — as in Scenarios A and E
Persist source network interfacesYour choice
+

What you get

+
    +
  • OpenStack port: the same IP address.
  • +
  • Port MAC: a newly generated address. The UI shows a warning triangle next to the NIC to confirm this.
  • +
  • Inside the guest: DHCP. The guest cannot match its old static settings to a hardware address it has never seen.
  • +
+ +

H — The source VM is powered off

+

Use this when

+
    +
  • The VM is not running, so VMware Tools reported no addresses.
  • +
+

Settings

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
SettingValue
Preserve IPForced off and greyed out — nothing to preserve
IP address boxType the address you want, or leave empty for DHCP
Preserve MACAvailable, and usually worth keeping on
Fallback to DHCPOn if you left the address box empty
Persist source network interfacesUnavailable
+

What you get

+
    +
  • OpenStack port: your typed address, an OpenStack-assigned one, or no address if the box was empty with Fallback off.
  • +
  • Port MAC: preserved. The MAC is read from the virtual NIC rather than the guest, so it survives a powered-off migration.
  • +
  • Inside the guest: no IP configuration is injected if no address was requested.
  • +
+ +

I — Create the port with no address at all

+

Use this when

+
    +
  • You intend to configure addressing yourself after the migration, and want the port attached but unaddressed.
  • +
+

Rarely used. If you reached this by accident, you probably wanted Scenario C.

+

Settings

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
SettingValue
Preserve IPOff
IP address boxEmpty
Preserve MACEither — your choice
Fallback to DHCPOff
Persist source network interfacesUnavailable
+

What you get

+
    +
  • OpenStack port: created and attached, with no fixed IP.
  • +
  • Port MAC: preserved or newly generated, per your choice.
  • +
  • Inside the guest: that interface is skipped entirely. No address, no DHCP client, nothing.
  • +
+ +

J — Preserve IP and MAC, but Persist Network is off

+

Use this when

+
    +
  • You want the address and MAC carried over, but do not need the original interface names restored.
  • +
  • Or Persist source network interfaces was simply left at its default (off) and you want to know what to expect.
  • +
+

This is the case Scenarios A and E refer to. The port outcome is identical to them — only the guest differs.

+

Settings

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
SettingValue
Preserve IPOn
IP address boxLeave as-is
Preserve MACOn
Fallback to DHCPYour choice — as in Scenarios A and E
Persist source network interfacesOff
+

What you get

+
    +
  • OpenStack port: exactly as in Scenario A or E — the preserved IP, or a DHCP address if Fallback rescued it.
  • +
  • Port MAC: the same MAC address.
  • +
  • Inside the guest: this is where it differs, and it depends on the guest OS rather than on the source configuration.
  • +
+

Guest outcome by OS

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Guest OSSource NIC was on DHCPSource NIC was static
Ubuntu 17.10 and newerConverted to a static address — pinned to whatever address it happened to hold when discoveredStays static on the same address
Ubuntu older than 17.10Stays on DHCP. Only interface-name rules are writtenStays static, per the guest’s own configuration
RHEL family 6 and olderLegacy interface handling runs — verify after bootLegacy interface handling runs — verify after boot
RHEL / CentOS / Rocky 7+, SUSE, other LinuxNothing is written. The guest keeps its own configurationNothing is written. The guest keeps its own configuration
WindowsNothing is written. The guest keeps its own configurationNothing is written. The guest keeps its own configuration
+

On Ubuntu 17.10 and newer, the existing /etc/netplan directory is moved aside to /etc/netplan-bkp and replaced with a single generated file. Interfaces are renamed vj0, vj1, and so on, matched by MAC address. The netmask comes from what VMware Tools reported; if it is unknown, /24 is assumed.

+ +
\ No newline at end of file diff --git a/docs/guides/how-to/ntp_timezone/index.html b/docs/guides/how-to/ntp_timezone/index.html new file mode 100644 index 0000000..fc5e87e --- /dev/null +++ b/docs/guides/how-to/ntp_timezone/index.html @@ -0,0 +1,303 @@ + Configure Time Zone and NTP Servers | vJailbreak + Skip to content

Configure Time Zone and NTP Servers

The vJailbreak appliance runs in UTC with default public NTP pools unless you change it. Two settings in the UI change that:

+
    +
  • Time zone — the appliance’s system time zone. It sets the timestamps in migration logs, controller logs, Grafana dashboards, and the schedule of the version-checker cron job.
  • +
  • NTP servers — the time sources the appliance synchronizes against. Needed in air-gapped or restricted networks where the default public pools are unreachable.
  • +
+

Keeping the appliance’s clock accurate matters beyond readable logs: clock drift makes Changed Block Tracking timestamps unreliable during hot migrations.

+

Both settings are applied to the appliance host itself, not to migrated VMs.

+

Configure the settings

+
    +
  1. Open Global Settings.
  2. +
  3. On the General tab, pick a Timezone. The dropdown is searchable and lists common IANA zones with their current UTC offset.
  4. +
  5. On the Advanced tab, enter NTP Servers — hostnames or IPv4 addresses, separated by commas or new lines. For example: ntp1.corp.local, ntp2.corp.local.
  6. +
  7. Click Save.
  8. +
+

Saving writes both values to the vjailbreak-settings ConfigMap and then applies them to the host. You will see “Applying time settings…” followed by “Time settings applied successfully.”

+

What each combination does

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Time zoneNTP serversResult
SetSetHost uses that zone, synchronizing against your servers
SetEmptyHost uses that zone, synchronizing against the default public pools
EmptySetHost stays on UTC, synchronizing against your servers
EmptyEmptyHost reverts to UTC and time synchronization is turned off
+

Clearing both fields is therefore a full reset to the appliance defaults.

+

Validation

+

The form rejects a save when an entry is malformed:

+
    +
  • A time zone must be one of the listed zones. If the appliance already holds a zone that is not in the list, it appears as (Legacy) <zone> so it is not silently discarded.
  • +
  • Each NTP entry must be a hostname or an IPv4 address. URLs, entries containing /, and malformed hostnames are rejected with “Invalid NTP server ”…”. Use hostnames or IPv4 addresses, separated by commas or new lines.”
  • +
+

Both fields lock while migrations run

+

If any migration is in a non-terminal phase — anything other than Succeeded, Failed, Validation Failed, or Unknown — the Timezone and NTP Servers fields are disabled, with the tooltip “Cannot change timezone while migrations are in progress.”

+

Applying time settings restarts the controller, SDK, and UI pods, which would disrupt a running migration. The migration list is polled every 30 seconds, so the fields unlock shortly after the last migration reaches a terminal phase.

+

Reset to Defaults respects the same rule: while a migration is running it resets every other setting but leaves the time zone and NTP servers at their current values. With no migration running, it resets them along with everything else.

+

Verify the settings

+

On the appliance host:

+
Terminal window
# Time zone, and whether the clock is synchronized
timedatectl
timedatectl show --property=NTPSynchronized
+
# The NTP servers vJailbreak wrote
cat /etc/systemd/timesyncd.conf.d/99-vjailbreak.conf
+

The conf file looks like this:

+
[Time]
NTP=ntp1.corp.local ntp2.corp.local
+

From Kubernetes:

+
Terminal window
# What was saved
kubectl -n migration-system get configmap vjailbreak-settings \
-o jsonpath='{.data.TIMEZONE}{"\n"}{.data.NTP_SERVERS}{"\n"}'
+
# What pods will inherit
kubectl -n migration-system get configmap pf9-env -o jsonpath='{.data.TZ}{"\n"}'
+
# What a running pod actually has
kubectl -n migration-system exec deploy/migration-controller-manager -- printenv TZ
+

A rolling restart takes a couple of minutes to finish, so give the pods time before concluding that TZ did not propagate.

+

Configure from the command line

+

The two values live in the vjailbreak-settings ConfigMap:

+ + + + + + + + + + + + + + + + + + + + +
KeyFormatEmpty means
TIMEZONEIANA zone, for example Asia/CalcuttaUse UTC
NTP_SERVERSHostnames or IPv4 addresses, space separatedUse the default public pools
+
Terminal window
kubectl -n migration-system patch configmap vjailbreak-settings --type merge \
-p '{"data":{"TIMEZONE":"Asia/Calcutta","NTP_SERVERS":"ntp1.corp.local ntp2.corp.local"}}'
+ +

Values written by hand skip the form’s validation. Invalid NTP entries are dropped when the settings are applied, and only the valid ones reach the conf file.

+

Troubleshooting

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
SymptomCauseWhat to do
Timezone and NTP Servers fields are greyed outA migration is in a non-terminal phaseWait for it to finish, or cancel it. The fields unlock within about 30 seconds.
Reset to Defaults left the time zone unchangedExpected while a migration is runningReset again once no migration is active
Save reports “Failed to apply time settings on the host … Settings were saved; click Save again to retry the apply.”The values were stored, but the host apply failedClick Save again. If it keeps failing, check the API response.
UI reports success, but timedatectl still shows the old zoneThe host notification is best-effort and its failure is not surfaced in the UICheck the logs for timesettings: warnings. A zone that is syntactically valid but not installed on the host fails at this step.
An NTP server you entered is missing from the conf fileIt failed validation and was droppedCheck the logs for ignoring invalid NTP server entries. Re-enter it as a plain hostname or IPv4 address.
Pods still show the old TZThe rolling restart has not finishedWait, then re-check with kubectl rollout status
The conf file is gone after a saveExpected when the NTP Servers field is emptyRe-enter your servers, or leave it empty to use the default pools
+

Logs for the apply step:

+
Terminal window
kubectl -n migration-system logs deploy/migration-vpwned-sdk | grep timesettings
+

Notes and limitations

+
    +
  • These settings change the appliance, not the VMs being migrated. Migrated guests keep their own time configuration.
  • +
  • Failures after the conf file is written — host notification, service restart, pod restarts, cron job patch — never fail the request and are not shown in the UI. The success message means the settings were saved and applied as far as possible.
  • +
  • A time zone that is not installed on the appliance is stored and reported as applied, but the host time zone does not change. Pick a zone from the dropdown to avoid this.
  • +
  • Invalid NTP entries are silently dropped at apply time. The UI blocks them first, so this affects only values written directly to the ConfigMap.
  • +
\ No newline at end of file diff --git a/docs/guides/how-to/perform_admin_cutover/index.html b/docs/guides/how-to/perform_admin_cutover/index.html new file mode 100644 index 0000000..a7e0752 --- /dev/null +++ b/docs/guides/how-to/perform_admin_cutover/index.html @@ -0,0 +1,199 @@ + Perform Admin Cutover | vJailbreak + Skip to content

Perform Admin Cutover

The Admin Cutover feature in vJailbreak is used to finalize a migration after the data transfer is complete.
+It can be triggered in two ways:

+
    +
  1. From the vJailbreak UI
  2. +
  3. Using kubectl patch on the corresponding Pod of the migration.
  4. +
+

Admin Cutover from the UI

+
    +
  1. Navigate to the migration you want to perform the admin cutover for, in the migration Options column, +Select the cutover options and select Admin Initiated Cutover as shown in the Image below. +Admin Cutover
  2. +
+

Once the migration is done with copying data, the status of the migration will change to waitforAdminCutover. +You can then click on the Admin Cutover button to trigger the cutover process. +Admin Cutover Button

+
    +
  1. +

    A confirmation dialog will appear. Click on the Confirm button to proceed with the admin cutover. +Admin Cutover Confirmation

    +
  2. +
  3. +

    After confirming, the migration will start the cutover process. +Admin Cutover In Progress

    +
  4. +
+

Admin Cutover using kubectl patch

+

You can also trigger the admin cutover using the kubectl patch command.

+
    +
  1. +

    First, identify the name of the migration Pod you want to perform the admin +cutover for. You can do that by doing the following things:

    +
      +
    • Get the name of the migration namespace. You can find it in the vJailbreak UI under the migration details.
    • +
    +
    Terminal window
    kubectl get migration -n migration-system | grep -i <migration-name>
    migration-name AwaitingAdminCutOve vjb 1h
    +
      +
    • Get the podRef of migration object
    • +
    +
    Terminal window
    kubectl get migration <migration-name> -n migration-system -o jsonpath='{.spec.podRef}'
    <pod-name>
    +
  2. +
  3. +

    Once you have identified the migration Pod, you can trigger the admin cutover by +executing the following command:

    +
  4. +
+
Terminal window
kubectl patch pod <pod-name> -n migration-system -p '{"metadata":{"labels":{"startCutover":"yes"}}}'
+

Replace pod-name with the name of your migration Pod and migration-system with the name of your migration namespace.

\ No newline at end of file diff --git a/docs/guides/how-to/profiles/index.html b/docs/guides/how-to/profiles/index.html new file mode 100644 index 0000000..a6eab07 --- /dev/null +++ b/docs/guides/how-to/profiles/index.html @@ -0,0 +1,337 @@ + Profiles | vJailbreak + Skip to content

Profiles

Profiles let us define a named set of OpenStack Cinder volume_image_metadata properties and apply them to VM boot volumes during migration. This gives us control over how the migrated VM boots and runs in OpenStack — for example, setting the firmware type, disk bus driver, video model, or guest agent settings — without having to configure each migration individually.

+

Navigate to the Profiles section in the sidebar to view and perform all CRUD operations on profiles.

+

Default Profiles

+

vJailbreak ships with two built-in profiles that are created by default as a template:

+

default-linux

+

Applies to Linux VMs (linuxGuest).

+ + + + + + + + + + + + + + + + + + + + + +
PropertyValue
hw_qemu_guest_agentyes
hw_video_modelvirtio
hw_pointer_modelusbtablet
+

default-windows

+

Applies to Windows VMs (windowsGuest).

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PropertyValue
hw_qemu_guest_agentyes
hw_video_modelvirtio
hw_pointer_modelusbtablet
hw_disk_busvirtio
os_typewindows
+

img1

+ +

Creating a Profile

+
    +
  1. +

    On the Profiles page, click Add Profile.

    +
  2. +
  3. +

    Fill in the following fields:

    +
      +
    • Profile Name — A unique name using lowercase letters, numbers, and hyphens (e.g., windows-uefi-q35). The name cannot be changed after creation.
    • +
    • OS Family — The type of VM this profile applies to: +
        +
      • Windows — Only applied to Windows VMs
      • +
      • Linux — Only applied to Linux VMs
      • +
      • Any (applies to all VMs) — Applied to every VM regardless of OS
      • +
      +
    • +
    • Description — Optional. A short note shown in the profile list.
    • +
    • Image Properties — One or more key-value pairs of OpenStack volume image metadata. Type directly into the key field or select from the list of known property keys.
    • +
    +
  4. +
  5. +

    Click Create Profile.

    +
  6. +
+

Known Image Property Keys

+

The property key field in the profile form offers autocomplete suggestions for common OpenStack volume image metadata keys. The hints shown alongside each key are example values.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
KeyExample Values
hw_firmware_typeuefi, bios
hw_machine_typeq35, pc-i440fx
hw_disk_busvirtio, scsi, ide
hw_scsi_modelvirtio-scsi, buslogic
hw_tpm_modeltpm-crb, tpm-tis
hw_tpm_version1.2, 2.0
os_secure_bootrequired, disabled, optional
os_require_quiesceyes, no
os_typewindows, linux
hw_qemu_guest_agentyes, no
hw_video_modelvirtio, qxl, vga
hw_cdrom_bussata, ide, virtio
hw_boot_menutrue, false
hw_pointer_modelusbtablet, ps2mouse
+

img1

+ +

Selecting Profiles During Migration

+

In the migration form, Step 4 (Security Groups, Server Group & Profiles) includes a Profiles dropdown. We can select one or more profiles to apply during the migration.

+

The dropdown shows only profiles that match the OS family of the VMs we have selected:

+
    +
  • If our migration includes Windows VMs, profiles with OS family Windows or Any are shown.
  • +
  • If our migration includes Linux VMs, profiles with OS family Linux or Any are shown.
  • +
+

Step 4 is optional. If we do not select any profiles, no additional image metadata is applied to the boot volumes.

+

img1

+

How Multiple Profiles Are Merged

+

When we select more than one profile, their properties are combined into a single set before being applied. The merge follows these rules:

+
    +
  • All unique keys from all selected profiles are included.
  • +
  • If two profiles set the same key to the same value, there is no conflict and the value is applied once.
  • +
  • If two profiles set the same key to different values, the UI will show a conflict error and prevent adding the second profile until the conflict is resolved.
  • +
+

This ensures that the final set of properties applied to the boot volume is predictable and unambiguous.

+ +

Using Profiles via CLI

+

A VolumeImageProfile is a standard Kubernetes custom resource in the migration-system namespace. We can create and manage profiles using kubectl.

+

View all profiles:

+
Terminal window
kubectl get volumeimageprofiles -n migration-system
+

View a profile’s details:

+
Terminal window
kubectl get volumeimageprofile default-windows -n migration-system -o yaml
+

Create a custom profile:

+
apiVersion: vjailbreak.k8s.pf9.io/v1alpha1
kind: VolumeImageProfile
metadata:
name: windows-uefi-q35
namespace: migration-system
spec:
osFamily: windowsGuest
description: Windows VMs with UEFI firmware and Q35 machine type
properties:
hw_firmware_type: uefi
hw_machine_type: q35
os_secure_boot: disabled
+
Terminal window
kubectl apply -f windows-uefi-q35.yaml
+

To reference a profile in a MigrationPlan, add its name to spec.advancedOptions.imageProfiles:

+
spec:
advancedOptions:
imageProfiles:
- default-windows
- windows-uefi-q35
\ No newline at end of file diff --git a/docs/guides/how-to/retry_failed_migration/index.html b/docs/guides/how-to/retry_failed_migration/index.html new file mode 100644 index 0000000..30de3ff --- /dev/null +++ b/docs/guides/how-to/retry_failed_migration/index.html @@ -0,0 +1,259 @@ + Retry a Failed Migration | vJailbreak + Skip to content

Retry a Failed Migration

When a migration ends in the Failed phase, you can retry it directly from the vJailbreak UI instead of building the migration again from scratch. A retry reopens the original configuration in the migration form, so you can correct whatever caused the failure — flavor, network or storage mapping, target cluster, cutover settings, or IP assignments — and start again.

+

What Retry does

+

vJailbreak offers two retry actions:

+ + + + + + + + + + + + + + + + + + + + +
ActionWhere to find itWhat it does
RetryRow action in the Migrations table, or the Retry button on a migration’s detail pageOpens the migration form pre-filled with the failed migration’s configuration. You can change settings before submitting.
Retry SelectedToolbar of the Migrations table, after selecting rowsRetries every selected failed migration with its existing configuration. No editing.
+

Use Retry when the migration failed because of a configuration problem. Use Retry Selected when several migrations failed for the same temporary reason — for example, an ESXi host or a target service was briefly unreachable — and nothing needs to change.

+

Prerequisites

+
    +
  • The migration is in the Failed phase. The Retry button appears in no other phase.
  • +
  • The migration is retryable. Migrations for VMs with RDM (Raw Device Mapping) disks cannot be retried from the UI, because the shared RDM state prevents an automatic retry. For these VMs the Retry button is visible but disabled.
  • +
  • The resources the original migration used still exist: its migration plan, migration template, VMware and OpenStack credentials, and the source VM in the inventory. If any of them is missing, the retry form opens with a banner naming the missing item, and Retry stays disabled.
  • +
+

Retry a single migration

+
    +
  1. Go to Migrations and locate the failed migration.
  2. +
  3. Click the Retry action in the row, or open the migration’s detail page and click Retry.
  4. +
  5. The migration form opens in retry mode, titled Retry Migration, and loads the original configuration.
  6. +
  7. Review the settings and change what you need. See What you can edit.
  8. +
  9. Click Retry.
  10. +
+

vJailbreak returns you to the migrations list and starts a new migration for that VM using the configuration you submitted.

+ +

What you can edit

+

Locked during a retry

+
    +
  • The VM being retried. A retry always applies to exactly one VM, and the VM list shows only that VM.
  • +
  • Source and destination credentials, and the source cluster.
  • +
+

Editable

+
    +
  • Target PCD cluster. Changing it clears the network and storage mappings, because mappings are specific to a cluster. Select new mappings before submitting.
  • +
  • Everything else — mappings, flavor, storage copy method, migration options, advanced options, and IP or MAC overrides.
  • +
+

Retry several migrations at once

+
    +
  1. Go to Migrations and select the failed rows using the checkboxes.
  2. +
  3. The Retry Selected (N) button appears in the toolbar. It appears only when every selected migration is failed and retryable. If the selection includes a migration that is not failed, or a VM with RDM disks, the button is hidden.
  4. +
  5. Click Retry Selected. A confirmation dialog appears, stating that the migrations will be retried without changing their configurations and that source VMs will not be modified.
  6. +
  7. Click Retry to confirm.
  8. +
+

Each selected migration restarts with its existing configuration. Plans, templates, and mappings are left untouched.

+

Troubleshooting

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
SymptomCauseWhat to do
Retry is disabled and the tooltip mentions RDM disksThe VM has RDM disks and cannot be retried from the UIRestart the migration manually. See Migrating an RDM disk Windows cluster machine using the CLI.
Banner: “Migration plan … no longer exists”The plan was deleted after the migration failedCreate a new migration for that VM
Banner naming a VMware or OpenStack credentialThe credential was deletedRecreate the credential, then retry
Banner: “Source VM … is no longer present in the inventory”The VM is missing from the VMware inventory, usually after a re-sync or after the VM was removedRefresh the inventory or add the VM back, then retry
Retry failed banner after submittingA step of the retry did not completeThe banner shows the underlying error. If the original plan could not be updated, nothing new was created and the original plan is intact — correct the problem and retry again.
Network and storage mappings are emptyExpected after changing the target clusterSelect mappings for the new cluster
+

Limitations

+
    +
  • A retry always creates a plan containing a single VM. You cannot retry several VMs into one shared plan with edits.
  • +
  • Retry Selected cannot change configuration. If a migration fails again after a bulk retry, retry it individually and correct its settings.
  • +
\ No newline at end of file diff --git a/docs/guides/how-to/scaling/index.html b/docs/guides/how-to/scaling/index.html new file mode 100644 index 0000000..73cd069 --- /dev/null +++ b/docs/guides/how-to/scaling/index.html @@ -0,0 +1,277 @@ + Scale vJailbreak | vJailbreak + Skip to content

Scale vJailbreak

vJailbreak can be scaled to perform multiple migrations in parallel by deploying additional agents, enabling greater efficiency and workload distribution.

+

Additional agents can be created in the Agents tab of the vJailbreak dashboard using the “Scale Up” button. You will need to choose the destination OpenStack credentials, the size of the agent VM(s), and the number of agent nodes up to a maximum of 5 per scale up. Additional agent nodes can be scaled up in batches of 5, providing the flexibility to change agent VM sizes to help with throttling network traffic.

+ +

Agent Node Sizing and Migration Capacity

+ +

Each migration running on an agent node consumes the following resources:

+ + + + + + + + + + + + + + + + + + + + + + + + + +
ResourceRequestLimit
CPU1 core2 cores
Memory1 GiB3 GiB
Ephemeral Storage3 GiB3 GiB
+

Calculating Concurrent Migrations per Agent

+

The number of concurrent migrations an agent node can handle depends on its available resources. While Kubernetes uses resource requests for scheduling decisions, the actual resource consumption during migration is closer to the limits. Therefore, consider both when planning capacity:

+

Scheduling Capacity (based on requests):

+
    +
  • Maximum Concurrent Migrations = min(Available CPU / 1 core, Available Memory / 1 GiB, Available Storage / 3 GiB)
  • +
+

Actual Runtime Capacity (based on limits):

+
    +
  • Maximum Concurrent Migrations = min(Available CPU / 2 cores, Available Memory / 3 GiB, Available Storage / 3 GiB)
  • +
+

For safe capacity planning, use the limits-based calculation to ensure migrations have sufficient resources during peak usage.

+ +

Below are recommended OpenStack flavors for agent nodes based on desired migration capacity. Reserve approximately 20-25% of resources for system overhead (OS, K3s, monitoring, etc.):

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Agent FlavorvCPUsRAMStorageConcurrent Migrations (per agent)Use Case
Small816 GiB60 GiB2-3Small-scale migrations, testing
Medium1632 GiB100 GiB5-7Standard production workloads
Large3264 GiB200 GiB10-14High-throughput migrations
X-Large4896 GiB300 GiB15-21Maximum parallel migrations
+ +

Example Calculation for Medium Flavor (16 vCPU, 32 GiB RAM):

+
    +
  • Available CPU after overhead: ~12 cores → 12 / 2 = 6 migrations
  • +
  • Available Memory after overhead: ~24 GiB → 24 / 3 = 8 migrations
  • +
  • Effective capacity: 6 concurrent migrations (limited by CPU)
  • +
+

Best Practices

+
    +
  • Network bandwidth is often the bottleneck. Monitor network utilization and adjust agent count/size accordingly.
  • +
  • Storage I/O on the agent node should be sufficient for temporary disk operations during migration.
  • +
  • Start with Medium flavors and scale up based on observed resource utilization and network capacity.
  • +
  • Distribute migrations across multiple smaller agents rather than one large agent for better fault tolerance.
  • +
  • Monitor agent resource usage via the vJailbreak dashboard or Prometheus metrics to optimize sizing.
  • +
+

Agent nodes can be scaled down by selecting the agent and using the “Scale Down” button.

+

Scaling in L2-Only Networks (PCD)

+

Agent scale-up is supported in L2-only network environments in PCD.

+

How it works

+
    +
  1. vJailbreak recognises the L2-only network and creates the agent VM on it without asking PCD to assign an IP address.
  2. +
  3. The agent VM waits for its IP address. On first boot it waits for the external DHCP server on that network to give it an IP address and a default route. It keeps retrying every minute for as long as it takes, so a slow lease is not a problem.
  4. +
  5. The agent joins vJailbreak. As soon as the guest has an IP address, the agent setup runs and the agent joins the primary vJailbreak VM. Everything it needs is already built into the image, so it does not require internet access.
  6. +
  7. The agent becomes available. While the agent is still coming up, the Agents tab shows no IP address for it. Once it reports Ready, its IP address appears and it starts picking up migrations.
  8. +
+ +

Logging into Agent VMs

+

Agent VMs use the same login process as the primary vJailbreak VM:

+
    +
  • Username: ubuntu
  • +
  • Default Password: password
  • +
  • On first login, you will be prompted to change the password immediately.
  • +
+
\ No newline at end of file diff --git a/docs/guides/how-to/stream_logs/index.html b/docs/guides/how-to/stream_logs/index.html new file mode 100644 index 0000000..edf7b20 --- /dev/null +++ b/docs/guides/how-to/stream_logs/index.html @@ -0,0 +1,318 @@ + Stream Logs | vJailbreak + Skip to content

Stream Logs

In this guide, we will cover how to enable the streaming of syslogs from vJailbreak VMs to fluentd and integrate with Loki running on the PCD-CE. +For this tutorial you will need working knowledge of K3s, fluentd and Loki.

+

Prerequisites

+
    +
  • A running vJailbreak instance with SSH enabled.
  • +
  • A running fluentd instance on a remote host (in this case PCD-CE Management host).
  • +
  • A running grafana/loki-stack instance for this we are using PCD-CE.
  • +
+

Setup vJailbreak VM for rsyslog

+
    +
  1. Enable K3s to write to syslog
  2. +
  3. Install rsyslog on the vJailbreak VM - If not already present
  4. +
  5. Configure rsyslog to forward syslogs to fluentd.
  6. +
  7. Configure loki to read logs from fluentd log directory.
  8. +
+

Enable syslog for k3s

+

To the service section add the following:

+
Terminal window
sudo vi /etc/systemd/system/k3s.service
+
Terminal window
[Service]
Type=notify
NotifyAccess=all
+

To the ExecStart section add the following:

+
Terminal window
ExecStart=/usr/local/bin/k3s \
server --log=/var/log/syslog \
'--disable' \
'traefik' \
+
Terminal window
sudo systemctl daemon-reload
sudo systemctl restart k3s
+

Configure rsyslog

+
    +
  1. Edit the rsyslog configuration file
  2. +
  3. Add the following configuration:
  4. +
+
Terminal window
sudo sh -c 'echo "*.* @<fluentd-host>:5140" >> /etc/rsyslog.d/90-fluentd.conf'
+

!note: Change <fluentd-host> to the IP address of the fluentd host

+
    +
  1. Restart rsyslog
  2. +
+
Terminal window
sudo systemctl restart rsyslog
+
    +
  1. Test rsyslog
  2. +
+
Terminal window
sudo journalctl -f
+

Install fluentd

+
    +
  1. Install fluentd on the PCD-CE Management host
  2. +
  3. Test fluentd
  4. +
+
Terminal window
$ ulimit -n
65536
+

Please add the following lines to your /etc/security/limits.conf file:

+
Terminal window
root soft nofile 65536
root hard nofile 65536
* soft nofile 65536
* hard nofile 65536
+
    +
  1. Setup sysctl conf
  2. +
+

Edit /etc/sysctl.conf and add the following

+
Terminal window
net.core.somaxconn = 1024
net.core.netdev_max_backlog = 5000
net.core.rmem_max = 16777216
net.core.wmem_max = 16777216
net.ipv4.tcp_wmem = 4096 12582912 16777216
net.ipv4.tcp_rmem = 4096 12582912 16777216
net.ipv4.tcp_max_syn_backlog = 8096
net.ipv4.tcp_slow_start_after_idle = 0
net.ipv4.tcp_tw_reuse = 1
net.ipv4.ip_local_port_range = 10240 65535
fs.inotify.max_user_instances = 1024
# If forward uses port 24224, reserve that port number for use as an ephemeral port.
# If another port, e.g., monitor_agent uses port 24220, add a comma-separated list of port numbers.
# net.ipv4.ip_local_reserved_ports = 24220,24224
net.ipv4.ip_local_reserved_ports = 24224
+

Then check if these are in effect

+
Terminal window
$ sysctl -p
net.core.somaxconn = 1024
net.core.netdev_max_backlog = 5000
net.core.rmem_max = 16777216
net.core.wmem_max = 16777216
net.ipv4.tcp_wmem = 4096 12582912 16777216
net.ipv4.tcp_rmem = 4096 12582912 16777216
net.ipv4.tcp_max_syn_backlog = 8096
net.ipv4.tcp_slow_start_after_idle = 0
net.ipv4.tcp_tw_reuse = 1
net.ipv4.ip_local_port_range = 10240 65535
fs.inotify.max_user_instances = 1024
net.ipv4.ip_local_reserved_ports = 24224
+
    +
  1. Install fluentd
  2. +
+
Terminal window
sudo curl -fsSL https://toolbelt.treasuredata.com/sh/install-ubuntu-jammy-fluent-package5.sh | bash
+
    +
  1. Restart fluentd
  2. +
+
Terminal window
sudo systemctl restart fluentd
+
    +
  1. Test fluentd
  2. +
+
Terminal window
sudo systemctl status fluentd
+

Configure fluentd

+
    +
  1. Edit the fluentd configuration file /etc/fluentd/fluentd.conf
  2. +
  3. Add the following configuration:
  4. +
+
Terminal window
<source>
@type syslog
port 5140
bind 0.0.0.0
tag system
</source>
+
<match system.**>
@type stdout
</match>
+

Ref: https://docs.fluentd.org/how-to-guides/parse-syslog

+
    +
  1. Restart fluentd
  2. +
+
Terminal window
sudo systemctl restart fluentd
+

Generally, the logs should now show up in /var/log/fluent/fluentd.log.

+

Verify

+
    +
  1. Check that rsyslog is running
  2. +
+
Terminal window
sudo systemctl status rsyslog
+
    +
  1. Check that fluentd is running
  2. +
+
Terminal window
sudo systemctl status fluentd
+
    +
  1. Check that syslogs from vJailbreak is being sent to the fluentd
  2. +
+
Terminal window
vjb$ logger -p vjailbreak.notice "This is a test message from Rsyslog - Hello Openstack!"
+
    +
  1. Check that fluentd is receiving the logs
  2. +
+
Terminal window
pcd$ tail -f /var/log/fluent/fluentd.log
+

Setup Loki on PCD-CE

+
    +
  1. Login to the PCD-CE Management Host
  2. +
  3. Then export the kubeconfig
  4. +
+
Terminal window
export KUBECONFIG=/etc/rancher/k3s/k3s.yaml
+
    +
  1. Install Loki using helm +Use the following loki-config.yaml
  2. +
+
loki:
image:
tag: 2.9.3
enabled: true
+
grafana:
enabled: false
+
promtail:
enabled: true
+
config:
server:
http_listen_port: 3101
grpc_listen_port: 0
positions:
filename: /tmp/positions.yaml
clients:
- url: http://loki:3100/loki/api/v1/push
snippets:
extraScrapeConfigs: |-
- job_name: fluentd
static_configs:
- targets:
- localhost
labels:
job: fluentd
__path__: /hostlogs/fluent/*.log
pipeline_stages:
- match:
selector: '{job="fluentd"}'
stages:
- regex:
expression: '.*'
- timestamp:
source: time
format: RFC3339
- output:
source: message
+
extraVolumes:
- name: host-logs
hostPath:
path: /var/log/fluent
type: Directory
- name: tmp
emptyDir: {}
+
extraVolumeMounts:
- name: host-logs
mountPath: /hostlogs/fluent
readOnly: true
- name: tmp
mountPath: /tmp
+
serviceAccount:
create: true
+
rbac:
create: true
+
persistence:
enabled: true
size: 10Gi
storageClassName: ""
accessModes:
- ReadWriteOnce
+
Terminal window
helm upgrade --namespace pcd-community --install loki grafana/loki-stack -f loki-config.yaml
+
    +
  1. Check if all the loki pods are running
  2. +
+
Terminal window
kubectl get pods -n pcd-community | grep loki
+
    +
  1. Add Loki as a data source in Grafana
  2. +
+
    +
  • +

    Add it manually in the grafana UI +Configuration > “Add Datasource” > Loki > “url: http://loki:3100” > “Save & Test”

    +
  • +
  • +

    Add it using a configmap

    +

    Add the configmap

    +
    Terminal window
    kubectl apply -f loki-datasource.yaml
    +

    Restart the deployment

    +
    Terminal window
    kubectl rollout restart deployment prometheus-stack-grafana
    +
  • +
+

Go to “Explore” > “Loki” to start exploring the logs.

+
    +
  1. You can use the query below to browse the logs
  2. +
+
Terminal window
{job="fluentd"} |= ``
+

Flow of Logs

+
architecture-beta
+    group vJailbreak(server)[vJailbreak VM]
+        service k3s(logos:kubernetes)[K3s] in vJailbreak
+        service syslog(disk)[Syslog] in vJailbreak
+        service rsyslogd(internet)[rsyslogd] in vJailbreak
+
+    group PCD(server)[PCD]
+        service fluentd(disk)[fluentd] in PCD
+        service loki(database)[Loki] in PCD
+        service grafana(logos:grafana)[Grafana] in PCD
+    
+    k3s:L -- R:syslog
+    syslog:B -- T:rsyslogd
+    rsyslogd:R -- R:fluentd
+    fluentd:B -- T:loki
+    loki:L -- R:grafana
+
+

Version of tools used

+
    +
  1. Fluentd
  2. +
+
Terminal window
fluentd --version
fluent-package 5.2.0 fluentd 1.18.0 (46372ddd521870f6a203baefb5a598209486d0bc)
+
    +
  1. Loki & grafana
  2. +
+
Terminal window
NAME CHART APP VERSION
grafana grafana-8.11.1 11.6.0
loki loki-stack-2.10.2 v2.9.3
\ No newline at end of file diff --git a/docs/guides/how-to/upgrade_vjailbreak/index.html b/docs/guides/how-to/upgrade_vjailbreak/index.html new file mode 100644 index 0000000..204d71a --- /dev/null +++ b/docs/guides/how-to/upgrade_vjailbreak/index.html @@ -0,0 +1,191 @@ + Upgrade vJailbreak | vJailbreak + Skip to content

Upgrade vJailbreak

vJailbreak supports an in-place upgrade feature to go from one version to other higher version. This feature is supported starting from v0.4.0 as the base to subsequent versions.

+

During the upgrade, only container images, ConfigMaps, and Custom Resource Definitions (CRDs) are modified. We currently do not support the upgrade of the base vJailbreak image or existing Custom Resources (CRs). Therefore, a pre-upgrade cleanup of these resources is required.

+

Upgrade Process

+

1. Check for Updates

+

Look for the Upgrade Available button at the bottom left of the vJailbreak navigation sidebar. Clicking this button will open the Upgrade vJailbreak modal.

+

Check for Updates

+

2. Pre-Upgrade Cleanup

+

Before upgrading, vJailbreak requires a cleanup of existing resources to ensure a smooth transition. The pre-upgrade checklist includes:

+
    +
  • Delete MigrationPlans
  • +
  • Delete RollingMigrationPlans
  • +
  • Scale down Agents
  • +
  • Delete VMware credentials
  • +
  • Delete PCD credentials
  • +
  • Delete Custom Resources
  • +
+

Click the Cleanup button to initiate this process. Wait for all items to show a green checkmark and the “Cleanup completed successfully” message to appear.

+

Pre-Upgrade Cleanup +Cleanup completed successfully

+

3. Select Version

+

Once the cleanup is successful, click the Select a version… dropdown and choose the target version you wish to upgrade to (e.g., v0.4.1).

+

Select Version

+

4. Initiate Upgrade

+

With the version selected, the Upgrade button will become enabled. Click it to start the upgrade process.

+

Initiate Upgrade

+

5. Wait for Completion

+

You will see an “Upgrading” spinner and a warning: Processing. Please do not close or refresh this page. Wait for the process to complete.

+

Wait for Completion

+

Once the upgrade is marked as successfully completed, the UI will hold on the screen for 3 seconds before automatically refreshing.

+ + +

Check for update before scheduled interval

+

If you do not want to wait for the next scheduled check, you can manually trigger the vjailbreak-version-checker CronJob to detect if a newer version (e.g., v0.4.1) is available.

+

Temporarily modify the schedule to trigger it. Example (run after 5 minutes):

+
# Change the schedule line in the cronjob to:
schedule: "*/5 * * * *"
+

After the CronJob runs, a new pod will be created. Check the pod logs to verify whether an upgrade is available:

+
Terminal window
kubectl get pods -n migration-system
kubectl logs <cronjob-pod-name> -n migration-system
+

If v0.4.1 is available, the logs will indicate the upgrade availability.

+

Trigger CronJob

\ No newline at end of file diff --git a/docs/guides/how-to/virtio_doc/index.html b/docs/guides/how-to/virtio_doc/index.html new file mode 100644 index 0000000..cd62e56 --- /dev/null +++ b/docs/guides/how-to/virtio_doc/index.html @@ -0,0 +1,184 @@ + Inject VirtIO Windows Driver | vJailbreak + Skip to content

Inject VirtIO Windows Driver

+

How to use user-provided virtio-win.iso

+

Users can upload the virtio-win.iso to the following path on vJailbreak master node:

+
Terminal window
/home/ubuntu/virtio-win/virtio-win.iso
+ +

How it works

+

If the user has scaled up vJailbreak, the ISO is propagated to all the agents. +When a Windows VM migration is initiated:

+

The migration logic checks for /home/ubuntu/virtio-win/virtio-win.iso on the source node.

+
    +
  • If found: +
      +
    • The ISO is used for injecting VirtIO drivers into the migrated disk.
    • +
    • The ISO is automatically propagated to all agent nodes if needed.
    • +
    +
  • +
  • If not found: +
      +
    • vJailbreak attempts to download the ISO from a known upstream source (e.g., fedoraproject.org).
    • +
    +
  • +
  • If both methods fail: +
      +
    • Migration fails gracefully with a clear error message.
    • +
    +
  • +
+
\ No newline at end of file diff --git a/docs/guides/how-to/vjailbreak_settings/index.html b/docs/guides/how-to/vjailbreak_settings/index.html new file mode 100644 index 0000000..5116801 --- /dev/null +++ b/docs/guides/how-to/vjailbreak_settings/index.html @@ -0,0 +1,472 @@ + Use vJailbreak Settings | vJailbreak + Skip to content

Use vJailbreak Settings

The vjailbreak-settings ConfigMap provides a centralized way to customize and override default settings in the vJailbreak system. This guide shows you how to use this ConfigMap effectively.

+

Overview

+

The vjailbreak-settings ConfigMap allows you to:

+
    +
  • Override system-wide default values
  • +
  • Enable or disable optional features
  • +
  • Configure global system behaviors
  • +
  • Set resource limits and operational parameters
  • +
+

Checking for the ConfigMap

+

In vJailbreak v0.3.0 and above, the vjailbreak-settings ConfigMap should already exist in your cluster. If it doesn’t exist, you’re likely using an older version and should upgrade to v0.3.0 or above.

+

To check if the ConfigMap exists:

+
Terminal window
kubectl get configmap vjailbreak-settings -n migration-system
+

If you receive an error that the ConfigMap doesn’t exist, please upgrade your vJailbreak installation to the latest version.

+

Available Settings

+

The vjailbreak-settings ConfigMap supports the following settings:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
SettingDescriptionDefault ValueExample Values
AUTO_FSTAB_UPDATEAutomatically update fstab during migrationfalsetrue, false
AUTO_PXE_BOOT_ON_CONVERSIONAutomatically configure PXE boot during conversionfalsetrue, false
CHANGED_BLOCKS_COPY_ITERATION_THRESHOLDNumber of iterations to copy changed blocks during hot migration20Any positive integer
CLEANUP_PORTS_AFTER_MIGRATION_FAILUREAutomatically cleanup OpenStack ports after migration failurefalsetrue, false
CLEANUP_VOLUMES_AFTER_CONVERT_FAILUREAutomatically cleanup OpenStack volumes after conversion failurefalsetrue, false
DEFAULT_MIGRATION_METHODDefault method for VM migrationcoldhot (migrate while VM is running), cold (power off VM before migration)
DEPLOYMENT_NAMEName of the vJailbreak deploymentvJailbreakAny string
NTP_SERVERSNTP servers the appliance synchronizes against.Empty (default public pools)Space-separated hostnames or IPv4 addresses (e.g., ntp1.corp.local ntp2.corp.local)
OPENSTACK_CREDS_REQUEUE_AFTER_MINUTESInterval in minutes to requeue OpenStack credentials validation60Any positive integer
PERIODIC_SYNC_INTERVALInterval for periodic sync during admin cutover1hDuration format (e.g., 30m, 1h, 2h)
PERIODIC_SYNC_MAX_RETRIESMaximum number of retries for periodic sync3Any positive integer
PERIODIC_SYNC_RETRY_CAPMaximum duration to retry periodic sync3hDuration format (e.g., 1h, 3h, 6h)
POPULATE_VMWARE_MACHINE_FLAVORSAutomatically populate flavor recommendations for VMware machinestruetrue, false
TIMEZONESystem time zone of the vJailbreak appliance.Empty (UTC)IANA time zone (e.g., Asia/Calcutta, America/New_York)
VALIDATE_RDM_OWNER_VMSValidates that all VMs linked to an RDM disk are migrated in a single migration plantruetrue, false
VCENTER_LOGIN_RETRY_LIMITNumber of retries for vCenter login attempts5Any positive integer
VCENTER_SCAN_CONCURRENCY_LIMITMaximum number of vCenter VMs to scan concurrently10Any positive integer
VM_ACTIVE_WAIT_INTERVAL_SECONDSInterval to wait for VM to become active (in seconds)20Any positive integer
VM_ACTIVE_WAIT_RETRY_LIMITNumber of retries to wait for VM to become active15Any positive integer
VMWARE_CREDS_REQUEUE_AFTER_MINUTESInterval in minutes to requeue VMware credentials validation60Any positive integer
VOLUME_AVAILABLE_WAIT_INTERVAL_SECONDSInterval to wait for volume to become available (in seconds)10Any positive integer
VOLUME_AVAILABLE_WAIT_RETRY_LIMITNumber of retries to wait for volume to become available15Any positive integer
HTTP_TIMEOUT_SECONDSTimeout in seconds for HTTP requests made by the controller30Any positive integer
V2V_HELPER_POD_CPU_REQUESTCPU request for v2v-helper migration pods1000mKubernetes CPU quantity (e.g., 500m, 2000m)
V2V_HELPER_POD_CPU_LIMITCPU limit for v2v-helper migration pods2000mKubernetes CPU quantity
V2V_HELPER_POD_MEMORY_REQUESTMemory request for v2v-helper migration pods1GiKubernetes memory quantity (e.g., 512Mi, 2Gi)
V2V_HELPER_POD_MEMORY_LIMITMemory limit for v2v-helper migration pods3GiKubernetes memory quantity
V2V_HELPER_POD_EPHEMERAL_STORAGE_REQUESTEphemeral storage request for v2v-helper migration pods3GiKubernetes storage quantity (e.g., 5Gi, 20Gi)
V2V_HELPER_POD_EPHEMERAL_STORAGE_LIMITEphemeral storage limit for v2v-helper migration pods3GiKubernetes storage quantity
+

Modifying Settings

+

You can modify the ConfigMap to change settings using one of the following methods:

+

Method 1: Edit with kubectl

+
Terminal window
kubectl edit configmap vjailbreak-settings -n migration-system
+

Then add or modify the data values as needed:

+
apiVersion: v1
kind: ConfigMap
metadata:
name: vjailbreak-settings
namespace: migration-system
data:
AUTO_FSTAB_UPDATE: "false"
AUTO_PXE_BOOT_ON_CONVERSION: "false"
CHANGED_BLOCKS_COPY_ITERATION_THRESHOLD: "30"
CLEANUP_PORTS_AFTER_MIGRATION_FAILURE: "false"
CLEANUP_VOLUMES_AFTER_CONVERT_FAILURE: "false"
DEFAULT_MIGRATION_METHOD: "cold"
DEPLOYMENT_NAME: "vJailbreak"
OPENSTACK_CREDS_REQUEUE_AFTER_MINUTES: "60"
PERIODIC_SYNC_INTERVAL: "1h"
PERIODIC_SYNC_MAX_RETRIES: "3"
PERIODIC_SYNC_RETRY_CAP: "3h"
POPULATE_VMWARE_MACHINE_FLAVORS: "true"
VALIDATE_RDM_OWNER_VMS: "true"
VCENTER_LOGIN_RETRY_LIMIT: "5"
VCENTER_SCAN_CONCURRENCY_LIMIT: "150"
VM_ACTIVE_WAIT_INTERVAL_SECONDS: "30"
VM_ACTIVE_WAIT_RETRY_LIMIT: "20"
VMWARE_CREDS_REQUEUE_AFTER_MINUTES: "60"
VOLUME_AVAILABLE_WAIT_INTERVAL_SECONDS: "10"
VOLUME_AVAILABLE_WAIT_RETRY_LIMIT: "15"
+

Method 2: Using kubectl patch

+

You can modify individual settings without editing the entire ConfigMap:

+
Terminal window
kubectl patch configmap -n migration-system vjailbreak-settings --type merge -p '{"data":{"VM_ACTIVE_WAIT_INTERVAL_SECONDS":"30","VM_ACTIVE_WAIT_RETRY_LIMIT":"20"}}'
+

Method 3: From a file

+

Create a file with your settings:

+
Terminal window
cat > vjailbreak-settings.yaml << EOF
apiVersion: v1
kind: ConfigMap
metadata:
name: vjailbreak-settings
namespace: migration-system
data:
AUTO_FSTAB_UPDATE: "false"
AUTO_PXE_BOOT_ON_CONVERSION: "false"
CHANGED_BLOCKS_COPY_ITERATION_THRESHOLD: "30"
CLEANUP_PORTS_AFTER_MIGRATION_FAILURE: "false"
CLEANUP_VOLUMES_AFTER_CONVERT_FAILURE: "false"
DEFAULT_MIGRATION_METHOD: "cold"
DEPLOYMENT_NAME: "vJailbreak"
OPENSTACK_CREDS_REQUEUE_AFTER_MINUTES: "60"
PERIODIC_SYNC_INTERVAL: "1h"
PERIODIC_SYNC_MAX_RETRIES: "3"
PERIODIC_SYNC_RETRY_CAP: "3h"
POPULATE_VMWARE_MACHINE_FLAVORS: "true"
VALIDATE_RDM_OWNER_VMS: "true"
VCENTER_LOGIN_RETRY_LIMIT: "5"
VCENTER_SCAN_CONCURRENCY_LIMIT: "150"
VM_ACTIVE_WAIT_INTERVAL_SECONDS: "30"
VM_ACTIVE_WAIT_RETRY_LIMIT: "20"
VMWARE_CREDS_REQUEUE_AFTER_MINUTES: "60"
VOLUME_AVAILABLE_WAIT_INTERVAL_SECONDS: "10"
VOLUME_AVAILABLE_WAIT_RETRY_LIMIT: "15"
EOF
+
kubectl apply -f vjailbreak-settings.yaml
+

Settings in Action

+

Optimizing Block Copy Operations

+

To increase the number of iterations for copying changed blocks during hot migrations:

+
Terminal window
kubectl patch configmap -n migration-system vjailbreak-settings --type merge -p '{"data":{"CHANGED_BLOCKS_COPY_ITERATION_THRESHOLD":"30"}}'
+

Adjusting VM Activation Parameters

+

To increase wait time and retry attempts for VM activation:

+
Terminal window
kubectl patch configmap -n migration-system vjailbreak-settings --type merge -p '{"data":{"VM_ACTIVE_WAIT_INTERVAL_SECONDS":"30","VM_ACTIVE_WAIT_RETRY_LIMIT":"20"}}'
+

Optimizing Scan Performance

+

To increase the number of concurrent vCenter scan pods:

+
Terminal window
kubectl patch configmap -n migration-system vjailbreak-settings --type merge -p '{"data":{"VCENTER_SCAN_CONCURRENCY_LIMIT":"150"}}'
+

Configuring Periodic Sync for Admin Cutover

+

To adjust the periodic sync settings for admin cutover migrations:

+
Terminal window
kubectl patch configmap -n migration-system vjailbreak-settings --type merge -p '{"data":{"PERIODIC_SYNC_INTERVAL":"30m","PERIODIC_SYNC_MAX_RETRIES":"5","PERIODIC_SYNC_RETRY_CAP":"6h"}}'
+

This configures the system to sync every 30 minutes, with a maximum of 5 retries, and a total retry window of 6 hours.

+

Enabling Automatic Cleanup After Failures

+

To automatically cleanup resources after migration failures:

+
Terminal window
kubectl patch configmap -n migration-system vjailbreak-settings --type merge -p '{"data":{"CLEANUP_PORTS_AFTER_MIGRATION_FAILURE":"true","CLEANUP_VOLUMES_AFTER_CONVERT_FAILURE":"true"}}'
+
+

Note: Enabling automatic cleanup helps prevent resource accumulation after failed migrations, but ensure you have proper logging and monitoring in place to track what gets cleaned up.

+
+

Adjusting Volume Availability Wait Parameters

+

To increase wait time and retry attempts for volumes to become available:

+
Terminal window
kubectl patch configmap -n migration-system vjailbreak-settings --type merge -p '{"data":{"VOLUME_AVAILABLE_WAIT_INTERVAL_SECONDS":"15","VOLUME_AVAILABLE_WAIT_RETRY_LIMIT":"20"}}'
+

Configuring Credentials Revalidation Intervals

+

To adjust how frequently credentials are revalidated:

+
Terminal window
kubectl patch configmap -n migration-system vjailbreak-settings --type merge -p '{"data":{"OPENSTACK_CREDS_REQUEUE_AFTER_MINUTES":"30","VMWARE_CREDS_REQUEUE_AFTER_MINUTES":"30"}}'
+

Configuring RDM Disk Validation

+

To disable the validation that requires all VMs linked to an RDM disk to be migrated in a single migration plan:

+
Terminal window
kubectl patch configmap -n migration-system vjailbreak-settings --type merge -p '{"data":{"VALIDATE_RDM_OWNER_VMS":"false"}}'
+
+

Note: When VALIDATE_RDM_OWNER_VMS is set to true (default), the system ensures that all VMs sharing an RDM disk are migrated together in the same migration plan. This prevents potential data consistency issues. Only disable this validation if you understand the implications for your RDM disk configuration.

+
+

Setting Default Migration Method

+

To set the default migration method for VMs:

+
Terminal window
kubectl patch configmap -n migration-system vjailbreak-settings --type merge -p '{"data":{"DEFAULT_MIGRATION_METHOD":"hot"}}'
+

The system supports two migration methods:

+
    +
  • Hot migration: Migrates VMs while they are running, minimizing downtime but requiring more coordination and potentially multiple sync iterations to capture changed blocks
  • +
  • Cold migration (default): Powers off the VM before migration, ensuring data consistency but causing downtime during the entire migration process
  • +
+

Verification

+

To verify your settings have been applied correctly:

+
Terminal window
kubectl get configmap -n migration-system vjailbreak-settings -o yaml
+

Applying Changes

+

After modifying settings in the ConfigMap, the behavior depends on which settings you changed:

+

Settings That Require Controller Restart

+

The following settings are loaded at controller startup and require a restart of the migration controller pod to take effect:

+
    +
  • OPENSTACK_CREDS_REQUEUE_AFTER_MINUTES
  • +
  • VMWARE_CREDS_REQUEUE_AFTER_MINUTES
  • +
+

To restart the controller after changing these settings:

+
Terminal window
kubectl rollout restart deployment migration-controller-manager -n migration-system
+

Settings That Take Effect Immediately

+

All other settings are read dynamically at runtime and do not require a restart:

+
    +
  • AUTO_FSTAB_UPDATE
  • +
  • AUTO_PXE_BOOT_ON_CONVERSION
  • +
  • CHANGED_BLOCKS_COPY_ITERATION_THRESHOLD
  • +
  • CLEANUP_PORTS_AFTER_MIGRATION_FAILURE
  • +
  • CLEANUP_VOLUMES_AFTER_CONVERT_FAILURE
  • +
  • DEFAULT_MIGRATION_METHOD
  • +
  • DEPLOYMENT_NAME
  • +
  • PERIODIC_SYNC_INTERVAL
  • +
  • PERIODIC_SYNC_MAX_RETRIES
  • +
  • PERIODIC_SYNC_RETRY_CAP
  • +
  • POPULATE_VMWARE_MACHINE_FLAVORS
  • +
  • VALIDATE_RDM_OWNER_VMS
  • +
  • VCENTER_LOGIN_RETRY_LIMIT
  • +
  • VCENTER_SCAN_CONCURRENCY_LIMIT
  • +
  • VM_ACTIVE_WAIT_INTERVAL_SECONDS
  • +
  • VM_ACTIVE_WAIT_RETRY_LIMIT
  • +
  • VOLUME_AVAILABLE_WAIT_INTERVAL_SECONDS
  • +
  • VOLUME_AVAILABLE_WAIT_RETRY_LIMIT
  • +
+

When Changes Take Effect

+

For settings that don’t require restart:

+
    +
  • On-demand access: Values are read from the ConfigMap when they are needed for an operation
  • +
  • New operations: Changes affect only new operations that start after the ConfigMap is updated
  • +
  • In-progress operations: Running operations continue using the values they initially read
  • +
  • No caching: The system does not cache these values for extended periods, ensuring relatively quick propagation of changes
  • +
+

Typically, your changes will be effective within seconds for any new operations initiated after updating the ConfigMap.

+

Best Practices and Considerations

+

Testing Recommendations

+
    +
  • Always test configuration changes in a test environment that closely matches your production setup before applying them to production.
  • +
  • Validate each setting change independently to understand its impact on system behavior.
  • +
  • Document any changes made to default settings for future reference and troubleshooting.
  • +
+

Operational Considerations

+
    +
  • Setting changes take effect for new operations and do not affect in-progress tasks.
  • +
  • The impact of settings varies based on your specific environment (hardware, network, storage configuration).
  • +
  • Performance-related settings should be adjusted based on your specific infrastructure capabilities.
  • +
+

Monitoring and Validation

+
    +
  • After changing settings, monitor system behavior to ensure the changes produce the expected results.
  • +
  • Use vJailbreak logs to verify that settings are being correctly applied.
  • +
+
+

Important: Always select configuration values appropriate for your specific environment. Incorrect settings may negatively impact system performance or stability.

+
\ No newline at end of file diff --git a/docs/guides/how-to/vtpm_migration/index.html b/docs/guides/how-to/vtpm_migration/index.html new file mode 100644 index 0000000..af77cf3 --- /dev/null +++ b/docs/guides/how-to/vtpm_migration/index.html @@ -0,0 +1,240 @@ + Virtual Trusted Platform Module (vTPM) VM Migration | vJailbreak + Skip to content

Virtual Trusted Platform Module (vTPM) VM Migration

Overview

+

While Virtualization Based Security (VBS) and Virtual Trusted Platform Module (vTPM) provide important security protections for the VM, V2V helpers and guestfs require proper read and write access to the VM’s disks. Temporarily disabling these features ensures that migration and disk operations can proceed reliably.

+ +

Prerequisites

+
    +
  • VMware Native Key Provider (default vCenter-level key provider) must be configured for enabling vTPM on the source VM.
  • +
  • On PCD, use tpm_version: 2.0 and tpm_provider: tpm-crb as extra metadata specs on the VM flavor to enable vTPM post migration.
  • +
+

vCenter Key Provider configuration

+

Enabling VBS and vTPM During Windows 11 Installation

+

When installing Windows 11, the user can enable VBS and vTPM at the OS selection stage by checking “Enable Windows Virtualization Based Security”.

+

Select guest OS with VBS enabled

+

On the next stage (Customize hardware), you can verify that the Trusted Platform Module is present under Security Devices.

+

Customize hardware showing TPM present

+

Pre-Migration Steps

+

Step 1: Power Off the Guest OS

+

Power off the guest OS on vCenter so that you can edit the VM settings and disable VBS and vTPM.

+

Step 2: Disable Virtualization Based Security

+
    +
  1. Right-click the VM in vCenter and select Edit Settings.
  2. +
  3. Navigate to the VM Options tab.
  4. +
  5. Expand Virtualization Based Security and uncheck the Enable checkbox.
  6. +
  7. Click OK to save.
  8. +
+

Disable VBS in VM Options

+

Step 3: Change VM Encryption Policies

+

Change the encryption policies of the VM to the Datastore Default policy:

+
    +
  1. Right-click the VM in vCenter.
  2. +
  3. Go to VM Policies → Edit VM Storage Policies.
  4. +
  5. Set the VM storage policy to Datastore Default.
  6. +
  7. Click OK to apply.
  8. +
+

Edit VM Storage Policies menu +Set Datastore Default policy

+

Step 4: Remove vTPM Device

+
    +
  1. Right-click the VM in vCenter and select Edit Settings.
  2. +
  3. Under Security Devices, locate the Virtual TPM device.
  4. +
  5. Remove the vTPM device and confirm the deletion when prompted.
  6. +
+ +

Remove vTPM device with data loss warning

+

Start Migration

+

With VBS disabled, encryption policies reset, and the vTPM device removed, proceed with the migration using vJailbreak as usual.

+

Migration succeeded in vJailbreak

+

Post-Migration Steps

+

After the migration completes successfully, re-enable vTPM on the VM in PCD by following the steps below.

+

Step 1: Reset PIN Using Backup Email

+

Since vTPM was removed before migration, the user will need to reset their PIN using the backup email configured earlier.

+

Step 2: Create a New Flavor with TPM Metadata

+

Create a new flavor (or update an existing one) with the same size as the migrated VM, adding the following TPM metadata:

+ + + + + + + + + + + + + + + + + +
KeyValue
hw:tpm_modeltpm-crb
hw:tpm_version2.0
+

Edit Flavor with TPM metadata in PCD

+

Step 3: Resize Migrated VM Using the TPM Flavor

+
    +
  1. Navigate to the migrated VM in PCD.
  2. +
  3. Resize the VM using the newly created flavor with TPM metadata.
  4. +
  5. Confirm the resize operation.
  6. +
+

Migrated VM details in PCD

+

Step 4: Verify TPM is Enabled

+

After the resize completes, verify that TPM is enabled on the VM:

+
    +
  • From the hypervisor: Run virsh dumpxml <instance> | grep -i tpm to confirm the <tpm model='tpm-crb'> block is present in the VM’s XML definition.
  • +
+

virsh dumpxml showing TPM configuration

+
    +
  • From inside the VM: Open TPM Management (tpm.msc) and verify that the TPM status shows “The TPM is ready for use.”
  • +
+

TPM Management console showing TPM ready

\ No newline at end of file diff --git a/docs/guides/how-to/windows-ldm-migration/index.html b/docs/guides/how-to/windows-ldm-migration/index.html new file mode 100644 index 0000000..00dc6f9 --- /dev/null +++ b/docs/guides/how-to/windows-ldm-migration/index.html @@ -0,0 +1,333 @@ + Windows Dynamic Disk (LDM) Migration | vJailbreak + Skip to content

Windows Dynamic Disk (LDM) Migration

Windows VMs whose system volume sits on a dynamic disk (LDM) follow a different +migration path. virt-v2v cannot convert these guests, so vJailbreak brings the VM +up on an emulated SATA controller first and lets you move it to virtio once you have +confirmed it boots.

+

vJailbreak detects this automatically during the migration, but you have to prepare the source VM for such VMs. There is nothing to select in the migration form. +form.

+ +

Because conversion is skipped, these do not run for LDM guests: VMware Tools +removal, network persistence and user firstboot scripts. That is why the +tasks below are manual.

+

1. Before you start

+

vJailbreak detects LDM on its own, but if you want to know in advance which VMs +will take this path, run the precheck script on the source VM as Administrator. It +is read-only, prints a plain YES or NO, and writes a transcript to +%TEMP%\vjb-ldm-check.log.

+

Download Test-VjbLdmSystemDisk.ps1

+
Terminal window
powershell -ExecutionPolicy Bypass -File .\Test-VjbLdmSystemDisk.ps1
+

It also sets an exit code — 1 for LDM, 0 for basic, 2 if inconclusive — so it +can be run across a fleet to build the list of VMs that need the steps below.

+

Take a snapshot of the source VM in vCenter before making any of the changes +below. Both steps modify the guest, and the driver installation requires a +reboot. The snapshot is your way back if either one leaves the VM in a state you +did not intend.

+

Both tasks below are performed on the source VM in vCenter, while it is still +running on ESXi, as Administrator.

+

Install the VirtIO drivers

+
    +
  1. +

    Download the ISO. Every build is published in the +virtio-win archive. +Windows Server 2016 and later can use the current build.

    + +
  2. +
  3. +

    Upload the ISO to a datastore the ESXi host can reach.

    +
  4. +
  5. +

    Attach it to the VM. In vCenter, right-click the VM → Edit Settings → +CD/DVD drive 1 → Datastore ISO File, browse to the ISO, and tick +Connected.

    +
  6. +
  7. +

    Run the installer in the guest. Open File Explorer, open the mounted CD +drive, and run virtio-win-guest-tools.exe.

    +
  8. +
  9. +

    Click through the wizard, accepting the defaults, and finish it.

    +
  10. +
  11. +

    Reboot the VM, then disconnect the ISO.

    +
  12. +
+

Nothing will look different afterwards — there is no virtio device in vCenter yet, +so the drivers sit staged until one appears at the destination.

+

Set the SAN policy

+

Windows marks migrated disks offline because the controller changed; skipping this +leaves the LDM pool broken.

+
diskpart
san policy=onlineall
exit
+

2. Trigger the migration

+

Start the migration as usual. vJailbreak skips conversion, creates the VM with +hw_disk_bus: sata, and attaches a 1 GB virtio temporary disk. Windows performs +a real driver installation against that device on first boot, which is what gets the +VirtIO storage driver installed and bound — offline injection cannot do this. The +temporary disk is removed later.

+

The status of the migration then changes to LDM Boot Verification, and the +migration waits for you to perform the cutover.

+

3. Confirm the VM booted

+

Open the console of the new VM in PCD and log in. What you should check inside the guest is that +Windows bound a VirtIO driver to the temporary disk — if it did, the root disk will work +on virtio too. Below are the commands to check that.

+

Run these before performing the cutover. The temporary disk only exists while the +migration is held at LDM Boot Verification; it is removed whichever option you +select, so the output changes afterwards.

+
Terminal window
# The 1 GB temporary disk must be present, with VirtIO in its model name.
Get-WmiObject Win32_DiskDrive | Select-Object Model, Size
+
# The controller must be healthy.
Get-WmiObject Win32_PnPEntity | Where-Object { $_.Name -like '*VirtIO*' } |
Select-Object Name, Status
+

Expect a disk of roughly 1 GB whose model names VirtIO, and a controller reporting +OK. Device Manager shows the same thing under Storage controllers. Both +commands work on every supported Windows version, including Server 2012.

+ +

If you re-run the same commands after the cutover, expect different output.

+

Once above is verified, you will have 3 cutover options:

+ + + + + + + + + + + + + + + + + + + + + +
Cutover optionWhat the checks show afterwards
Move to virtioThe temporary disk is gone and the root disk now reports a VirtIO model. This is the successful end state.
Keep on SATAThe temporary disk is gone and no VirtIO disk remains, because the root disk stayed on SATA. A VirtIO controller may linger in Device Manager as a non-present device. Expected — not a failure.
Rollback MigrationThe VM no longer exists in PCD.
+

4. Perform the cutover

+

There is no timeout. The migration remains at LDM Boot Verification until the +cutover is performed, so it can be scheduled for a maintenance window.

+

Cutover from the UI

+

Click the cutover button on the migration, either in the migrations table or on the +migration details page. A confirmation dialog appears with three options:

+ + + + + + + + + + + + + + + + + + + + + +
ChoiceResult
Move to virtioThe VM is shut down, deleted and recreated with the root disk on virtio, keeping its name, IP and MAC.
Keep on SATAThe migration completes with the VM disk left on SATA.
Rollback MigrationThe VM is deleted from PCD and the source VM in vCenter is returned to its pre-migration state.
+

Leave the VM running — the shutdown is handled for you. Expect a short outage +while it is recreated; the phase shows Moving to virtio during the rebuild.

+

If the checks in step 3 did not pass, select Keep on SATA. The VM remains fully +functional on the SATA controller; only the performance benefit of virtio is lost. +Select Rollback Migration only if the VM did not boot at all.

+

Cutover using kubectl patch

+

The cutover can also be performed by patching the migration Pod.

+
Terminal window
kubectl get migration <migration-name> -n migration-system -o jsonpath='{.spec.podRef}'
+
kubectl patch pod <pod-name> -n migration-system \
-p '{"metadata":{"labels":{"ldmBootStatus":"success"}}}'
+ + + + + + + + + + + + + + + + + + + + + +
Label valueEquivalent option
successMove to virtio
finishKeep on SATA
failedRollback Migration
+

Troubleshooting

+

Disks show “Failed Redundancy”

+

Seen on mirrored LDM volumes when the SAN policy was not set beforehand. Windows +marked a disk offline, so the mirror ran on one plex; by the time the disk returned, +the copies had diverged and LDM refuses to merge them.

+

Confirm which disk is stale with detail volume or Disk Management, then:

+
diskpart> san policy=onlineall
Set-Disk -Number 2 -IsOffline $false
diskpart> select volume 0 ; online volume
diskpart> select volume 0 ; break disk=2 nokeep
diskpart> select volume 0 ; add disk=2
+ +

No data is lost when the correct disk is named. There is no redundancy while the +mirror resyncs, but the window is bounded.

+

The VM did not boot on SATA

+

Perform the cutover with Rollback Migration, correct the prerequisites, and +migrate again.

\ No newline at end of file diff --git a/docs/guides/injecting_custom_env/index.html b/docs/guides/injecting_custom_env/index.html deleted file mode 100644 index 842615c..0000000 --- a/docs/guides/injecting_custom_env/index.html +++ /dev/null @@ -1,178 +0,0 @@ - v2v-helper Environment Variable Injection via ConfigMap | vJailbreak - Skip to content

v2v-helper Environment Variable Injection via ConfigMap

Injecting environment variables into the v2v-helper pod is a feature that allows users to inject environment variables into the v2v-helper pod using a Kubernetes ConfigMap.

-

How It Works

-
    -
  1. -

    Cloud-init populates environment variables

    -

    Users must provide environment variables in the /etc/pf9/env file during provisioning, typically using a cloud-init script.

    -
  2. -
  3. -

    ConfigMap creation from /etc/pf9/env

    -

    A helper script or manual command reads /etc/pf9/env and creates a Kubernetes ConfigMap named pf9-env. -This is done while the vjailbreak VM is being provisioned.

    -
    Terminal window
    kubectl create configmap pf9-env --from-env-file=/etc/pf9/env -n migration-system
    -
  4. -
-

Example

-

If you want proxy variables to be injected into the v2v-helper pod, you can add the following to the /etc/pf9/env file via the cloud-init script:

-
Terminal window
http_proxy=http://<proxy-server>:<proxy-port>
https_proxy=http://<proxy-server>:<proxy-port>
no_proxy=localhost,127.0.0.1
-

You can either populate the /etc/pf9/env file via cloud-init or manually.

-

If done manually please follow the steps mentioned in Injecting Environment Variables Post-Provisioning:

-

Now this will be picked up by the v2v-helper pod and the proxy variables will be available in the pod and it would be respected by the v2v-helper pod.

-

Injecting Environment Variables Post-Provisioning

-

If you would like to inject environment variables after the vjailbreak VM has been provisioned, follow these steps:

-
    -
  1. -

    Delete the existing ConfigMap

    -
    Terminal window
    kubectl delete configmap pf9-env -n migration-system
    -
  2. -
  3. -

    Populate the /etc/pf9/env file with whatever env variables needed

    -
    Terminal window
    echo "http_proxy=http://<proxy-server>:<proxy-port>" >> /etc/pf9/env
    echo "https_proxy=http://<proxy-server>:<proxy-port>" >> /etc/pf9/env
    echo "no_proxy=localhost,127.0.0.1" >> /etc/pf9/env
    -
  4. -
  5. -

    Create a new ConfigMap

    -
    Terminal window
    kubectl create configmap pf9-env --from-env-file=/etc/pf9/env -n migration-system
    -
  6. -
  7. -

    Trigger a new migration for the envs to be reflected in the pod

    -

    Trigger via UI or via api.

    -
  8. -
\ No newline at end of file diff --git a/docs/guides/scaling/index.html b/docs/guides/scaling/index.html deleted file mode 100644 index 16d430c..0000000 --- a/docs/guides/scaling/index.html +++ /dev/null @@ -1,153 +0,0 @@ - Scaling vJailbreak | vJailbreak - Skip to content

Scaling vJailbreak

vJailbreak can be scaled to perform multiple migrations in parallel by deploying additional agents, enabling greater efficiency and workload distribution.

-

Additional agents can be created in the Agents tab of the vJailbreak dashboard using the “Scale Up” button. You will need to choose the destination OpenStack credentials, the size of the agent VM(s), and the number of agent nodes up to a maximum of 5 per scale up. Additional agent nodes can be scaled up in batches of 5, providing the flexibility to change agent VM sizes to help with throttling network traffic.

- -

Agent nodes can be scaled down by selecting the agent and using the “Scale Down” button.

-

To retrieve the ubuntu user’s password for SSH’ing into an agent, follow these steps:

-
    -
  • SSH into the primary vJailbreak VM and run:
  • -
-
Terminal window
cat /var/lib/rancher/k3s/server/token | cut -c 1-12
-

The first 12 characters of this token is the password for the agent VMs.

- -

Each agent must also have a copy of the VMware VDDK libraries in their /home/ubuntu directories.

-
    -
  • Copy the latest version of the VDDK libraries for Linux into /home/ubuntu of the new agents. Untar it to a folder name vmware-vix-disklib-distrib in /home/ubuntu directory.
  • -
\ No newline at end of file diff --git a/docs/guides/troubleshooting/debug_vjailbreak_install/index.html b/docs/guides/troubleshooting/debug_vjailbreak_install/index.html new file mode 100644 index 0000000..4eaa83b --- /dev/null +++ b/docs/guides/troubleshooting/debug_vjailbreak_install/index.html @@ -0,0 +1,174 @@ + Debug vJailbreak Installation | vJailbreak + Skip to content

Debug vJailbreak Installation

1. Check Installation Logs

+

All logs related to the install process are written to:

+

/var/log/pf9-install.log

+

Look here for:

+
    +
  • Image pull errors
  • +
  • Authentication issues
  • +
  • YAML apply failures
  • +
  • Proxy or network errors
  • +
+
+

🔍 Tip: If you’re seeing errors related to pulling images, verify that the image registry URL is accessible from within the vJailbreak VM.

+
+
+

2. Test Registry Access (Image Pull Failures)

+

If the logs show image pull issues, run this on the vJailbreak VM:

+
Terminal window
curl -v <image-url>
+

🔁 What If the URL Is Accessible but Installation Still Fails? +Even if the URL is accessible, transient network issues or Kubernetes API hiccups might cause failures.

+

Recheck /var/log/pf9-install.log for intermittent or recoverable errors.

+

In such cases, you can safely re-run the installer:

+
Terminal window
sudo bash /etc/pf9/install.sh
\ No newline at end of file diff --git a/docs/guides/troubleshooting/debuglogs/index.html b/docs/guides/troubleshooting/debuglogs/index.html new file mode 100644 index 0000000..40f6887 --- /dev/null +++ b/docs/guides/troubleshooting/debuglogs/index.html @@ -0,0 +1,228 @@ + Debug Logs | vJailbreak + Skip to content

Debug Logs

This guide outlines how vJailbreak handles debug log collection for VM migrations. Traditionally, enabling debug logs required editing ConfigMaps and restarting pods. With the current setup, debug logs are automatically collected and stored without any manual intervention. In kubectl logs of the pod, normal logs will be displayed as usual.

+

How It Works

+
    +
  • +

    For every migration executed via vJailbreak, debug logs are written to the host system under /var/log/pf9.

    +
  • +
  • +

    A high-level milestone log is written for the migration at:

    +

    /var/log/pf9/<migration-name>.log

    +

    This mirrors the same key milestone messages (e.g. “Snapshot created”, “Starting NBD server”, “VM active”) that also appear in kubectl logs for the pod — it is not a full copy of the pod’s stdout/stderr.

    +
  • +
  • +

    In addition, logs are now split by category into a dedicated directory for the migration:

    +

    /var/log/pf9/<migration-name>/<category>.<timestamp>.log

    +

    Each category captures the output of a specific part of the migration, so an issue can be traced straight to the relevant subsystem instead of scanning one milestone log:

    + + + + + + + + + + + + + + + + + + + + + +
    CategoryContents
    nbdNBD/nbdcopy disk-copy commands and their output
    virtv2vvirt-v2v conversion commands and their output
    generalEverything else run during the migration
    +
  • +
  • +

    These logs are centrally accessible from the vjailbreak node, simplifying the debugging process.

    +
  • +
+

Log File Location

+ + + + + + + + + + + + + + + + + + + + +
Node TypePathDescription
vjailbreak-master/var/log/pf9/<migration>.logHigh-level milestone log for the migration
vjailbreak-master/var/log/pf9/<migration>/<category>.<timestamp>.logPer-category split logs (nbd, virtv2v, general)
+

Example

+

If a migration is named vm-migrate-001, its logs will be available at:

+
    +
  • /var/log/pf9/vm-migrate-001.log — milestone log
  • +
  • /var/log/pf9/vm-migrate-001/nbd.2026-08-25-10:15:00.log — disk-copy log
  • +
  • /var/log/pf9/vm-migrate-001/virtv2v.2026-08-25-10:20:00.log — conversion log
  • +
+

on the vjailbreak node.

+

Downloading a Debug Bundle from the UI

+

Instead of SSHing into the vjailbreak node to collect logs manually, you can download a full debug bundle directly from the migration’s Pod logs tab.

+

Download button on the Pod logs tab

+

There is a download button as shown in the image above. This downloads all the logs, debug logs, and everything related to the migration as a tar ball — no extra kubectl or SSH access is required.

\ No newline at end of file diff --git a/docs/guides/troubleshooting/index.html b/docs/guides/troubleshooting/index.html deleted file mode 100644 index ad36981..0000000 --- a/docs/guides/troubleshooting/index.html +++ /dev/null @@ -1,167 +0,0 @@ - Troubleshooting vJailbreak | vJailbreak - Skip to content

Troubleshooting vJailbreak

-

vJailbreak is deployed on Kubernetes running on Ubuntu 22.04.5, and distributed as a QCOW2 image. The Kubernetes namespace migration-system contains the vJailbreak UI and migration controller pods. Each VM migration will spawn a migration object. The status field contains a high level view of the progress of the migration of the VM. For more details about the migration, check the logs of the pod specified in the Migration object.

-

Getting logs

-

List all pods in the migration namespace

-
Terminal window
kubectl -n migration-system get pod
-

Find a specific VM migration pod

-
Terminal window
kubectl -n migration-system get pod | grep <source VM name>
-

Get details & events for a v2v-helper pod. This is helpful if a migration is stuck in a pending state, or to track the progress of a migration without the UI.

-
Terminal window
kubectl -n migration-system describe pod <v2v-helper-pod-name>
-

Get logs for a specific migration pod. This shows more detail than describe pod.

-
Terminal window
kubectl logs <pod> -n migration-system
-

Get logs for the migration-controller-manager

-
Terminal window
kubectl logs -n migration-system deploy/migration-controller-manager
-

Turn on Debug Mode

-
Terminal window
kubectl patch configmap -n migration-system migration-config-<vm-name> --type merge -p '{"data":{"DEBUG":"true"}}'
-

A migration is stuck in Pending

-

If the migration was set to Retry on Failure, then delete the v2v-helper pod for that VM and collect the logs of the pod that comes up.

-
Terminal window
kubectl delete pod -n migration-system v2v-helper-<vm-name>
-

If the v2v-helper pod doesn’t come back up, and you can’t delete the migration in the UI, then delete the associated migrationplan.

-
    -
  • First, get the migrationplan object name UUID for the associated VMs:
  • -
-
Terminal window
kubectl get migrationplans -n migration-system -o yaml
-
    -
  • Then delete the migrationplan object, which should remove it from the UI.
  • -
-
Terminal window
kubectl delete migrationplan <UUID> -n migration-system
-

Get all vJailbreak custom resource definitions (CRDs)

-
Terminal window
kubectl get migrationplans,migrations,migrationtemplates,networkmappings,openstackcreds,storagemappings,vmwarecreds,secrets -n migration-system -o yaml
\ No newline at end of file diff --git a/docs/guides/troubleshooting/nbdcopy-fails-after-vm-moved-esxi-host/index.html b/docs/guides/troubleshooting/nbdcopy-fails-after-vm-moved-esxi-host/index.html new file mode 100644 index 0000000..8c87c67 --- /dev/null +++ b/docs/guides/troubleshooting/nbdcopy-fails-after-vm-moved-esxi-host/index.html @@ -0,0 +1,182 @@ + nbdcopy fails during disk copy (often DNS resolution) | vJailbreak + Skip to content

nbdcopy fails during disk copy (often DNS resolution)

Problem

+

A migration fails during the disk copy (live replicate) phase with an error similar to:

+
Failed to migrate VM: failed to live replicate disks: failed to copy disk Hard disk 1 (DeviceKey=2000): failed to run nbdcopy: exec: already started.
+

Error signature:

+
failed to run nbdcopy: exec: already started
+

Symptoms

+
    +
  • Migration fails during the nbdcopy phase.
  • +
  • Debug logs often show DNS resolution errors when attempting to connect to an ESXi host.
  • +
+

Root Cause

+

During the disk copy phase, vJailbreak needs to communicate with ESXi hosts. If name resolution for an ESXi host is not available from the vJailbreak VM, the nbdcopy workflow can fail.

+

This is commonly caused by missing DNS records or missing /etc/hosts entries for ESXi hosts.

+

Resolution

+
    +
  1. Review the debug logs to confirm DNS/name-resolution errors.
  2. +
+

See: Debug Logs.

+
    +
  1. Ensure the vJailbreak VM can resolve ESXi host names.
  2. +
+

If you are not using DNS, add a static entry on the vJailbreak VM:

+
Terminal window
sudo sh -c 'echo "<esxi-host-ip> <esxi-host-fqdn> <esxi-host-shortname>" >> /etc/hosts'
+
    +
  1. Re-run the migration.
  2. +
+

Prevention

+
    +
  • Ensure DNS (or /etc/hosts) is configured for all ESXi hosts in the cluster, not just vCenter.
  • +
\ No newline at end of file diff --git a/docs/guides/troubleshooting/troubleshooting/index.html b/docs/guides/troubleshooting/troubleshooting/index.html new file mode 100644 index 0000000..ea4b133 --- /dev/null +++ b/docs/guides/troubleshooting/troubleshooting/index.html @@ -0,0 +1,386 @@ + Troubleshooting vJailbreak | vJailbreak + Skip to content

Troubleshooting vJailbreak

+

Common issues

+ +

vJailbreak is deployed on Kubernetes running on Ubuntu 22.04.5, and distributed as a QCOW2 image. The Kubernetes namespace migration-system contains the vJailbreak UI and migration controller pods. Each VM migration will spawn a migration object. The status field contains a high level view of the progress of the migration of the VM. For more details about the migration, check the logs of the pod specified in the Migration object.

+

Getting logs

+

List all pods in the migration namespace

+
Terminal window
kubectl -n migration-system get pod
+

Find a specific VM migration pod

+
Terminal window
kubectl -n migration-system get pod | grep <source VM name>
+

Get details & events for a v2v-helper pod. This is helpful if a migration is stuck in a pending state, or to track the progress of a migration without the UI.

+
Terminal window
kubectl -n migration-system describe pod <v2v-helper-pod-name>
+

Get logs for a specific migration pod. This shows more detail than describe pod.

+
Terminal window
kubectl logs <pod> -n migration-system
+

Get logs for the migration-controller-manager

+
Terminal window
kubectl logs -n migration-system deploy/migration-controller-manager
+

Turn on Debug Mode

+
Terminal window
kubectl patch configmap -n migration-system migration-config-<vm-name> --type merge -p '{"data":{"DEBUG":"true"}}'
+

A migration is stuck in pending

+

If the migration was set to Retry on Failure, then delete the v2v-helper pod for that VM and collect the logs of the pod that comes up.

+
Terminal window
kubectl delete pod -n migration-system v2v-helper-<vm-name>
+

If the v2v-helper pod doesn’t come back up, and you can’t delete the migration in the UI, then delete the associated migrationplan.

+
    +
  • First, get the migrationplan object name UUID for the associated VMs:
  • +
+
Terminal window
kubectl get migrationplans -n migration-system -o yaml
+
    +
  • Then delete the migrationplan object, which should remove it from the UI.
  • +
+
Terminal window
kubectl delete migrationplan <UUID> -n migration-system
+

A migration failed and I want to run it again

+

Use the Retry action on the failed migration in the vJailbreak UI. It reopens the migration form pre-filled with the original configuration, so you can correct the setting that caused the failure before starting again. To restart several failed migrations without changing anything, select them in the Migrations table and use Retry Selected.

+

See Retry a Failed Migration for the full workflow and its limitations.

+

Get all vJailbreak custom resource definitions (CRDs)

+
Terminal window
kubectl get migrationplans,migrations,migrationtemplates,networkmappings,openstackcreds,storagemappings,vmwarecreds,secrets -n migration-system -o yaml
+
+

virt-v2v fails: rename /sysroot/etc/resolv.conf Operation not permitted

+
    +
  • +

    Symptom

    +

    virt-v2v or virt-v2v-in-place fails with an error similar to:

    +
    renaming /sysroot/etc/resolv.conf to /sysroot/etc/6vvk9gzd
    guestfsd: error: rename: /sysroot/etc/resolv.conf to /sysroot/etc/6vvk9gzd: Operation not permitted
    commandrvf: stdout=n stderr=n flags=0x0
    commandrvf: umount /sysroot/sys
    virt-v2v-in-place: error: libguestfs error: sh_out: rename: /sysroot/etc/resolv.conf to /sysroot/etc/6vvk9gzd: Operation not permitted
    +
  • +
  • +

    Cause

    +

    On some Linux VMs, /etc/resolv.conf is marked immutable. When virt-v2v tries to rename or replace this file inside the guest filesystem during conversion, the immutable attribute prevents the operation and conversion fails.

    +

    You can confirm the immutable bit inside the source VM with:

    +
    Terminal window
    lsattr /etc/resolv.conf
    ----i----------------- /etc/resolv.conf
    +

    The i flag indicates the file is immutable.

    +
  • +
  • +

    Resolution

    +
      +
    1. +

      Remove the immutable attribute inside the source VM before migration:

      +
      Terminal window
      chattr -i /etc/resolv.conf
      +
    2. +
    3. +

      Verify the attribute is gone:

      +
      Terminal window
      lsattr /etc/resolv.conf
      ---------------------- /etc/resolv.conf
      +
    4. +
    5. +

      Re-run the migration.

      +
    6. +
    +
  • +
  • +

    Notes

    +
      +
    • This is a known and documented virt-v2v issue. See upstream documentation.
    • +
    • If configuration management or security hardening marks /etc/resolv.conf immutable, ensure this is unset before conversion, or adjust your automation so VMs intended for conversion do not have /etc/resolv.conf marked immutable.
    • +
    +
  • +
+
+

Disk attach fails during migration: No more available PCI slots

+
    +
  • +

    Symptom

    +

    During a migration, attaching a target volume to the vJailbreak VM (or an agent VM) fails. The nova-compute log on the OpenStack side shows an error similar to:

    +
    TRACE nova.virt.libvirt.driver [instance: <uuid>] libvirt.libvirtError: internal error: No more available PCI slots
    +
  • +
  • +

    Cause

    +

    During conversion, vJailbreak attaches the target Cinder volumes to the vJailbreak VM (or its agent VMs) to copy and convert the disk data. If the vJailbreak image was uploaded without a disk bus setting, OpenStack attaches these volumes using the default virtio-blk bus, where every attached volume is a separate PCI device and consumes its own PCI slot.

    +

    The virtual PCI bus has a limited number of slots, several of which are already used by essential devices (network interfaces, video, memory balloon, and so on). Migrating VMs with many disks — or running many migrations in parallel on one agent — exhausts the available PCI slots, and the volume attach fails with the error above.

    +
  • +
  • +

    Resolution

    +

    Configure the vJailbreak image to use the virtio-scsi disk bus. With virtio-scsi, all attached volumes share a single SCSI controller that consumes only one PCI slot and supports up to 256 devices.

    +
      +
    1. +

      Set the following properties on the vJailbreak image before creating the vJailbreak VM:

      +
      Terminal window
      openstack image set \
      --property hw_disk_bus=scsi \
      --property hw_scsi_model=virtio-scsi \
      <vjailbreak-image-name-or-ID>
      +
    2. +
    3. +

      Deploy the vJailbreak VM from the updated image, then re-run the migration.

      +
    4. +
    +
  • +
  • +

    Notes

    +
      +
    • The disk bus is fixed when the VM is created. If your vJailbreak VM is already deployed, setting the properties on the image is not enough — you must recreate the vJailbreak VM from the updated image.
    • +
    • Agent VMs created during scale up use the same image, so set these properties before scaling up agents.
    • +
    • See also: Known Limitations.
    • +
    +
  • +
+
+ +
    +
  • +

    Symptom

    +

    A RHEL 7.x migration fails during virt-v2v-in-place, after disk copy and volume attach/detach have already succeeded. The migration log only shows:

    +
    failed to run virt-v2v-in-place: exit status 1
    +

    The debug log under /var/log/pf9/ (see Debug Logs) shows the actual error:

    +
    virt-v2v-in-place: error: libguestfs error: command:
    error opening /boot/grub/grub.cfg for read:
    No such file or directory
    +
  • +
  • +

    Cause

    +

    RHEL 7’s grubby, used internally by virt-v2v-in-place, expects /boot/grub/grub.cfg to be a symlink to /boot/grub2/grub.cfg. On affected guests GRUB2 itself is configured correctly, but this compatibility symlink is missing, so grubby’s file open fails. This is unrelated to SUSE Legacy GRUB 0.97, which requires a GRUB2 upgrade instead.

    +
  • +
  • +

    Resolution

    +

    Before migrating a BIOS RHEL 7 guest, confirm:

    +
      +
    • /boot/grub2/grub.cfg exists and is non-empty (regenerate with grub2-mkconfig -o /boot/grub2/grub.cfg if not).
    • +
    • /boot/grub/grub.cfg exists as a symlink to ../grub2/grub.cfg (mkdir -p /boot/grub && ln -sfn ../grub2/grub.cfg /boot/grub/grub.cfg if missing).
    • +
    • /etc/grub2.cfg and /etc/grub.conf resolve to the same file — recreate the same way if broken.
    • +
    +

    Then re-run the migration.

    +
  • +
  • +

    Notes

    +
      +
    • Check ahead of a migration wave: test -L /boot/grub/grub.cfg && echo OK || echo MISSING.
    • +
    • Observed on RHEL 7.9 (Maipo); other RHEL 7.x releases with the same layout may be affected.
    • +
    • See also: Known Limitations.
    • +
    +
  • +
+
+

VDDK unavailable: VMware’s public download pages are down

+
    +
  • +

    Symptom

    +

    VMware’s public VDDK download pages are currently unavailable. You cannot download VDDK from +VMware’s official site.

    +
  • +
  • +

    Impact

    +

    Only the Standard copy method requires VDDK. vJailbreak Accelerated Copy and +Storage-Accelerated Copy do not need VDDK and are fully unaffected.

    +
  • +
  • +

    Resolution

    +

    Use vJailbreak Accelerated Copy or Storage-Accelerated Copy instead of Standard copy. +Both methods work without VDDK installed on the vJailbreak appliance.

    + +

    See vJailbreak Accelerated Copy and +Storage-Accelerated Copy for setup instructions.

    +
  • +
+
+

Proxy VM disk attach fails when several migrations start together

+
    +
  • +

    Symptom

    +

    A batch of vJailbreak Accelerated Copy migrations is started at once. Some of them fail early with a vCenter error while attaching the source snapshot disks to the Proxy VM, while the rest continue into the copy phase without any problem.

    +
  • +
  • +

    Cause

    +

    Each migration attaches its source disks to the Proxy VM as a vCenter VM reconfigure task. When several migrations issue these tasks against the same Proxy VM simultaneously, vCenter does not always serialize them gracefully and rejects some of the attach requests.

    +

    This is a transient race condition — the Proxy VM, its credentials, and its configuration are all fine. Only the migrations that lost the race are affected.

    +
  • +
  • +

    Resolution

    +
      +
    1. Let the surviving migrations progress past the attach step and into the copy phase.
    2. +
    3. Retry the failed migrations. They normally succeed on the second attempt.
    4. +
    +
  • +
  • +

    Notes

    +
      +
    • To reduce the chance of hitting this, stagger migration start times rather than starting a large batch at once, or register additional Proxy VMs and distribute migrations across them.
    • +
    • Use the data copy start time option to spread a wave of migrations over a window.
    • +
    • See also: Known Limitations.
    • +
    +
  • +
+
+

vJailbreak Accelerated Copy fails: could not identify block device

+
    +
  • +

    Symptom

    +

    A vJailbreak Accelerated Copy migration fails after the snapshot disk has been attached to the Proxy VM:

    +
    could not identify block device for disk <uuid>
    +
  • +
  • +

    Cause

    +

    vJailbreak locates each attached disk inside the Proxy VM by matching its disk UUID to a block device. Two conditions must be met for this to work:

    +
      +
    1. disk.EnableUUID is set to TRUE on the Proxy VM, so the UUID is visible to the guest.
    2. +
    3. The Proxy VM’s first SCSI controller (SCSI controller 0) is of type VMware Paravirtual (PVSCSI). Only PVSCSI is supported — LSI Logic SAS, LSI Logic Parallel, and BusLogic Parallel controllers do not work.
    4. +
    +
  • +
  • +

    Resolution

    +
      +
    1. Confirm disk.EnableUUID = TRUE on the Proxy VM: vSphere Client → Edit Settings → VM Options → Advanced → Edit Configuration.
    2. +
    3. Confirm SCSI controller 0 is VMware Paravirtual. If it is not, power off the Proxy VM, then in Edit Settings → Virtual Hardware set SCSI controller 0 → Change Type → VMware Paravirtual, and power it back on.
    4. +
    5. Re-verify the Proxy VM in the vJailbreak UI and re-run the migration.
    6. +
    +
  • +
  • +

    Notes

    + +
  • +
\ No newline at end of file diff --git a/docs/guides/troubleshooting/vmware_residual_artifacts/index.html b/docs/guides/troubleshooting/vmware_residual_artifacts/index.html new file mode 100644 index 0000000..defb704 --- /dev/null +++ b/docs/guides/troubleshooting/vmware_residual_artifacts/index.html @@ -0,0 +1,785 @@ + VMware Residual Artifacts | vJailbreak + Skip to content

VMware Residual Artifacts

In v0.4.4, the following artifacts remain on the Windows VMs after selecting “Remove VMware Tools” option:

+

1. VMware Driver Files

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Driver File Path20122016201920222025Win11
C:\Windows\System32\drivers\vmci.sysNot FoundNot FoundNot FoundNot FoundNot FoundNot Found
C:\Windows\System32\drivers\vm3dmp.sysNot FoundNot FoundNot FoundNot FoundNot FoundNot Found
C:\Windows\System32\drivers\vmaudio.sysNot FoundNot FoundNot FoundNot FoundNot FoundNot Found
C:\Windows\System32\drivers\vmhgfs.sysNot FoundNot FoundNot FoundNot FoundNot FoundNot Found
C:\Windows\System32\drivers\vmmemctl.sysNot FoundNot FoundNot FoundNot FoundNot FoundNot Found
C:\Windows\System32\drivers\vmmouse.sysNot FoundNot FoundNot FoundNot FoundNot FoundPresent
C:\Windows\System32\drivers\vmrawdsk.sysNot FoundNot FoundNot FoundNot FoundNot FoundNot Found
C:\Windows\System32\drivers\vmtools.sysNot FoundNot FoundNot FoundNot FoundNot FoundNot Found
C:\Windows\System32\drivers\vmusbmouse.sysNot FoundNot FoundNot FoundNot FoundNot FoundNot Found
C:\Windows\System32\drivers\vmvss.sysNot FoundNot FoundNot FoundNot FoundNot FoundNot Found
C:\Windows\System32\drivers\vsock.sysNot FoundNot FoundNot FoundNot FoundNot FoundNot Found
C:\Windows\System32\drivers\vmx_svga.sysNot FoundNot FoundNot FoundNot FoundNot FoundNot Found
C:\Windows\System32\drivers\vmxnet3.sysNot FoundNot FoundNot FoundNot FoundNot FoundNot Found
C:\Windows\System32\drivers\vm3dmp-stats.sysNot FoundNot FoundNot FoundNot FoundNot FoundNot Found
C:\Windows\System32\drivers\vm3dmp_loader.sysNot FoundNot FoundNot FoundNot FoundNot FoundNot Found
C:\Windows\System32\drivers\vm3dmp-debug.sysNot FoundNot FoundNot FoundNot FoundNot FoundNot Found
C:\Windows\System32\drivers\vm3dservice.sysNot FoundNot FoundNot FoundNot FoundNot FoundNot Found
C:\Windows\System32\drivers\vmgid.sysNot FoundNot FoundNot FoundNot FoundNot FoundNot Found
C:\Windows\System32\drivers\vmgencounter.sysNot FoundNot FoundNot FoundNot FoundNot FoundNot Found
C:\Windows\System32\drivers\vms3cap.sysNot FoundNot FoundNot FoundNot FoundNot FoundNot Found
C:\Windows\System32\drivers\vmstorfl.sysNot FoundNot FoundNot FoundNot FoundNot FoundNot Found
+

2. VMware Registry Keys

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Registry Key Path20122016201920222025Win11
HKLM:\SOFTWARE\VMware, Inc.PresentNot FoundNot FoundNot FoundNot FoundNot Found
HKLM:\SOFTWARE\VMwareNot FoundNot FoundNot FoundNot FoundNot FoundNot Found
HKLM:\SOFTWARE\WOW6432Node\VMware, Inc.Not FoundNot FoundNot FoundNot FoundNot FoundNot Found
HKLM:\SOFTWARE\WOW6432Node\VMwareNot FoundNot FoundNot FoundNot FoundNot FoundNot Found
HKLM:\SYSTEM\CurrentControlSet\Services\vmciNot FoundNot FoundNot FoundNot FoundNot FoundNot Found
HKLM:\SYSTEM\CurrentControlSet\Services\vm3dmpNot FoundNot FoundNot FoundNot FoundNot FoundNot Found
HKLM:\SYSTEM\CurrentControlSet\Services\vm3dmp_loaderNot FoundNot FoundNot FoundNot FoundNot FoundNot Found
HKLM:\SYSTEM\CurrentControlSet\Services\vm3dmp-debugNot FoundNot FoundNot FoundNot FoundNot FoundNot Found
HKLM:\SYSTEM\CurrentControlSet\Services\vm3dmp-statsNot FoundNot FoundNot FoundNot FoundNot FoundNot Found
HKLM:\SYSTEM\CurrentControlSet\Services\vm3dserviceNot FoundNot FoundNot FoundNot FoundNot FoundNot Found
HKLM:\SYSTEM\CurrentControlSet\Services\vmaudioNot FoundNot FoundNot FoundNot FoundNot FoundNot Found
HKLM:\SYSTEM\CurrentControlSet\Services\vmhgfsNot FoundNot FoundNot FoundNot FoundNot FoundNot Found
HKLM:\SYSTEM\CurrentControlSet\Services\VMMemCtlNot FoundNot FoundNot FoundNot FoundNot FoundNot Found
HKLM:\SYSTEM\CurrentControlSet\Services\vmmouseNot FoundNot FoundNot FoundNot FoundNot FoundPresent
HKLM:\SYSTEM\CurrentControlSet\Services\vmrawdskNot FoundNot FoundNot FoundNot FoundNot FoundNot Found
HKLM:\SYSTEM\CurrentControlSet\Services\VMRawDiskNot FoundNot FoundNot FoundNot FoundNot FoundNot Found
HKLM:\SYSTEM\CurrentControlSet\Services\VMToolsNot FoundNot FoundNot FoundNot FoundNot FoundNot Found
HKLM:\SYSTEM\CurrentControlSet\Services\vmusbmouseNot FoundNot FoundNot FoundNot FoundNot FoundNot Found
HKLM:\SYSTEM\CurrentControlSet\Services\vmvssNot FoundNot FoundNot FoundNot FoundNot FoundNot Found
HKLM:\SYSTEM\CurrentControlSet\Services\vmvsockNot FoundNot FoundNot FoundNot FoundNot FoundNot Found
HKLM:\SYSTEM\CurrentControlSet\Services\VMwareCAFNot FoundNot FoundNot FoundNot FoundNot FoundNot Found
HKLM:\SYSTEM\CurrentControlSet\Services\VMwareCAFCommAmqpListenerNot FoundNot FoundNot FoundNot FoundNot FoundNot Found
HKLM:\SYSTEM\CurrentControlSet\Services\VMwareCAFManagementAgentHostNot FoundNot FoundNot FoundNot FoundNot FoundNot Found
HKLM:\SYSTEM\CurrentControlSet\Services\vnetWFPPresentNot FoundNot FoundNot FoundNot FoundNot Found
+

3. VMware Folders

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Folder Path20122016201920222025Win11
C:\Program Files\VMwareNot FoundNot FoundNot FoundNot FoundNot FoundNot Found
C:\Program Files (x86)\VMwareNot FoundNot FoundNot FoundNot FoundNot FoundNot Found
C:\Program Files\Common Files\VMwareNot FoundNot FoundNot FoundNot FoundNot FoundNot Found
C:\Program Files (x86)\Common Files\VMwareNot FoundNot FoundNot FoundNot FoundNot FoundNot Found
C:\ProgramData\VMwareNot FoundNot FoundNot FoundNot FoundNot FoundNot Found
C:\Users\Administrator\AppData\Local\VMwareNot FoundNot FoundNot FoundNot FoundNot FoundNot Found
C:\Users\Administrator\AppData\Roaming\VMwareNot FoundNot FoundNot FoundNot FoundNot FoundNot Found
C:\ProgramData\Microsoft\Windows\Start Menu\Programs\VMwareNot FoundNot FoundNot FoundNot FoundNot FoundNot Found
+

4. Startup Entries

+ + + + + + + + + + + + + + + + + + + + + + + +
Startup Entry20122016201920222025Win11
vmtoolsd (HKLM:\Software\Microsoft\Windows\CurrentVersion\Run)Not FoundNot FoundNot FoundNot FoundNot FoundNot Found
+

5. VMware Devices (Device Manager)

+

Devices with Error status indicate a residual device entry whose driver was removed with VMware Tools.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Device Name20122016201920222025Win11
VMware VMCI Host DeviceErrorErrorErrorNot FoundNot FoundNot Found
VMware Pointing DeviceErrorErrorErrorNot FoundNot FoundError
Total devices found222001
+

6. Impact of Remaining Artifacts

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ArtifactVersionsImpact
vmmouse.sys + HKLM:\SYSTEM\CurrentControlSet\Services\vmmouseWin11Residual VMware mouse driver. After migration of VMware hypervisor, there’s no VMware hardware to drive, so it’s inert. The Pointing Device shows Error in Device Manager but Windows falls back to standard HID drivers — mouse input works normally.
HKLM:\SOFTWARE\VMware, Inc.2012Metadata-only registry key left by the VMware installer. No services load from it, no runtime effect. May appear in software inventory/audit tools as VMware still “installed” but it’s not.
HKLM:\SYSTEM\CurrentControlSet\Services\vnetWFP2012VMware virtual network Windows Filtering Platform driver. The service entry remains but since the driver binary is gone, Windows will fail to start it silently. No network degradation observed.
VMware Pointing Device / VMCI Host Device (Error)2012, 2016, 2019, Win11Phantom device entries in Device Manager with no loaded driver (Code 28). Cosmetic only — no runtime effect, no performance impact, no BSOD risk. Windows ignores driver-less device entries during normal operation.
+

Summary:

+
    +
  • None of these remnants are harmful to VM operation or stability post-migration.
  • +
  • The vnetWFP service key on Windows 2012 is the most noteworthy from a compliance audit standpoint, but has no observed runtime impact.
  • +
  • The Error devices in Device Manager are cosmetic — they do not affect functionality.
  • +
\ No newline at end of file diff --git a/docs/guides/troubleshooting/windows-dynamic-disk-ldm-migration-issue/index.html b/docs/guides/troubleshooting/windows-dynamic-disk-ldm-migration-issue/index.html new file mode 100644 index 0000000..67f9339 --- /dev/null +++ b/docs/guides/troubleshooting/windows-dynamic-disk-ldm-migration-issue/index.html @@ -0,0 +1,179 @@ + Windows Dynamic Disk (LDM) migration issue | vJailbreak + Skip to content

Windows Dynamic Disk (LDM) migration issue

+

Why LDM needs a different path

+

LDM (Logical Disk Manager) is Windows’ volume manager for “dynamic disks”. It +is conceptually similar to Linux LVM, but it stores its volume metadata in a +private database at the end of each disk rather than in a standard partition +table.

+

virt-v2v converts a guest by inspecting it with libguestfs, reading the +SYSTEM and SOFTWARE registry hives with Hivex, and writing VirtIO drivers and +registry changes back into the offline filesystem. When the Windows system volume +is an LDM volume, libguestfs cannot reliably assemble it, so inspection fails +before conversion can start.

+

Rather than requiring the disk to be converted to basic beforehand, vJailbreak +skips conversion for these guests and brings the VM up on an emulated SATA +controller, which Windows can boot without VirtIO drivers. A temporary virtio +temporary disk lets Windows install the viostor driver itself, and the migration +then waits at the LDM Boot Verification phase for you to move the root disk to +virtio.

+ +

Converting to basic disks is no longer required

+

Earlier versions of this guide recommended running diskpart → convert basic on +the source VM before migrating. That is no longer necessary, and convert basic +requires an empty disk in any case. Follow the +LDM migration guide instead.

\ No newline at end of file diff --git a/docs/guides/troubleshooting/windows-offline-disks/index.html b/docs/guides/troubleshooting/windows-offline-disks/index.html new file mode 100644 index 0000000..3028f16 --- /dev/null +++ b/docs/guides/troubleshooting/windows-offline-disks/index.html @@ -0,0 +1,264 @@ + Windows Offline Disks After Migration | vJailbreak + Skip to content

Windows Offline Disks After Migration

Windows Offline Disks After Migration

+

Problem Description

+

After migrating a Windows VM from VMware vCenter to PCD using vJailbreak, additional disks (beyond the primary C: drive) may not be visible inside the Windows operating system, even though they are successfully attached to the VM in PCD.

+

Symptoms

+
    +
  • The VM migrates successfully and shows as active in PCD
  • +
  • PCD shows all volumes/disks are attached (e.g., 3 disks attached)
  • +
  • Inside the Windows VM, only the primary C: drive is visible
  • +
  • Additional drives (e.g., E:, F:, G:) that existed before migration are missing
  • +
  • The disks exist but are in an “Offline” state in Windows Disk Management
  • +
+

Root Cause

+

This issue occurs due to Windows SAN Policy settings. When Windows detects disks on a SAN (Storage Area Network), it applies a policy that determines whether new disks are automatically brought online or kept offline.

+

After migration from VMware to PCD, the storage subsystem changes, and Windows may apply the “Offline Shared” SAN policy to the migrated disks. This policy keeps disks offline by default to prevent data corruption in shared storage scenarios.

+

The default SAN policies in Windows are:

+
    +
  • Offline Shared: Keeps all shared disks offline (common after migration)
  • +
  • Offline All: Keeps all new disks offline
  • +
  • Online All: Automatically brings all new disks online
  • +
+

Manual Workaround

+

Before migrating the VM, you can manually fix this issue by changing the SAN policy and bringing disks online.

+

Step 1: Check Current SAN Policy

+

Open Command Prompt as Administrator and run:

+
Terminal window
C:\> diskpart
DISKPART> SAN
+

You will likely see:

+
SAN Policy : Offline Shared
+

Step 2: Change SAN Policy to Online All

+
Terminal window
DISKPART> SAN POLICY=OnlineAll
+

Step 3: Verify the Change

+
Terminal window
DISKPART> SAN
+

You should now see:

+
SAN Policy : Online All
+

Step 4: Bring Offline Disks Online

+

While still in diskpart:

+
Terminal window
DISKPART> list disk
+

Identify offline disks (marked with an asterisk *), then for each offline disk:

+
Terminal window
DISKPART> select disk <number>
DISKPART> online disk
+

Step 5: Exit and Verify

+
Terminal window
DISKPART> exit
+

Check File Explorer - your drives (E:, F:, G:, etc.) should now be visible.

+

Automated Solution

+

vJailbreak provides automated scripts to detect and fix this issue during first boot after migration.

+

Available Scripts

+

Two BAT scripts are available in the scripts/firstboot/windows/ directory. These scripts generate PowerShell scripts that run on first boot:

+
    +
  1. check-disks.bat - Generates a diagnostic PowerShell script that only checks disk status
  2. +
  3. disk-online-fix.bat - Generates an automated fix PowerShell script that brings offline disks online
  4. +
+

Usage: Copy the contents of either BAT file and paste it into the Post Migration Script field in the migration form. The script will execute automatically on first boot after migration.

+

Script 1: Check Disks (Diagnostic Only)

+

The check-disks.bat script generates check-disks.ps1 which performs a read-only analysis:

+
    +
  • Scans all physical disks
  • +
  • Reports operational status (Online/Offline)
  • +
  • Lists partitions and drive letter assignments
  • +
  • Identifies disks without drive letters
  • +
  • Checks for read-only disks and health issues
  • +
  • Generates a detailed report at C:\DiskStatus_Report.txt
  • +
+

Script 2: Disk Online Fix (Automated Repair)

+

The disk-online-fix.bat script generates and executes check-disks-fix.ps1 which automatically fixes offline disk issues:

+
    +
  • Performs all diagnostic checks from Script 1
  • +
  • Automatically brings ALL offline disks online
  • +
  • Logs all actions to C:\DiskStatus_Report.txt
  • +
+

Note: The generated PowerShell script (check-disks-fix.ps1) is a separate file from the diagnostic-only check-disks.ps1. It includes all diagnostic functionality plus automated repair capabilities.

+

⚠️ Important Warnings

+

Blanket Online Policy

+

The automated fix script uses a blanket approach to bring ALL offline disks online without discrimination. This is necessary because:

+
    +
  • Pre-migration disk states (online/offline) are unknown
  • +
  • The script cannot determine if a disk was intentionally kept offline before migration
  • +
  • This is designed for automated firstboot scenarios after VM conversion
  • +
+

Risks and Considerations

+
    +
  1. +

    Intentionally Offline Disks: If the source VM had disks that were intentionally kept offline (for backup, security, or operational reasons), the script will bring them online. This may not align with your original configuration.

    +
  2. +
  3. +

    Shared Storage: In environments with shared storage, bringing disks online indiscriminately could potentially cause issues if the same disk is accessed by multiple systems.

    +
  4. +
  5. +

    Testing Required: Always test this script in a non-production environment first to ensure it aligns with your migration policy.

    +
  6. +
  7. +

    No Drive Letter Assignment: The script does NOT automatically assign drive letters to partitions. If a partition lacks a drive letter, you must assign it manually using Disk Management or PowerShell cmdlets.

    +
  8. +
+

Recommendations

+
    +
  • Review the diagnostic output from check-disks.bat before running the automated fix
  • +
  • Document which disks should be online in your source environment
  • +
  • Test the migration process with a non-critical VM first
  • +
  • Review the log file at C:\DiskStatus_Report.txt after running the fix script
  • +
  • Manually verify that all expected drives are accessible after the fix
  • +
+

Prevention

+

To prevent this issue in future migrations, you can:

+
    +
  1. Pre-configure SAN Policy: Before migration, set the SAN policy on the source VM to OnlineAll
  2. +
  3. Post-migration Automation: Copy the contents of disk-online-fix.bat into the Post Migration Script field in the migration form
  4. +
  5. Document Disk States: Maintain documentation of which disks should be online/offline for each VM
  6. +
+
\ No newline at end of file diff --git a/docs/guides/using_apis/index.html b/docs/guides/using_apis/index.html deleted file mode 100644 index 8d26e8e..0000000 --- a/docs/guides/using_apis/index.html +++ /dev/null @@ -1,139 +0,0 @@ - Using vJailbreak via APIs | vJailbreak - Skip to content
\ No newline at end of file diff --git a/docs/images/admin_cutover_button.png b/docs/images/admin_cutover_button.png new file mode 100644 index 0000000..55c5928 Binary files /dev/null and b/docs/images/admin_cutover_button.png differ diff --git a/docs/images/admin_cutover_confirmation.png b/docs/images/admin_cutover_confirmation.png new file mode 100644 index 0000000..700f287 Binary files /dev/null and b/docs/images/admin_cutover_confirmation.png differ diff --git a/docs/images/admin_cutover_form.png b/docs/images/admin_cutover_form.png new file mode 100644 index 0000000..6db5e0e Binary files /dev/null and b/docs/images/admin_cutover_form.png differ diff --git a/docs/images/admin_cutover_run.png b/docs/images/admin_cutover_run.png new file mode 100644 index 0000000..dc9802e Binary files /dev/null and b/docs/images/admin_cutover_run.png differ diff --git a/docs/images/cleaned_up_sucessfully.png b/docs/images/cleaned_up_sucessfully.png new file mode 100644 index 0000000..4b27491 Binary files /dev/null and b/docs/images/cleaned_up_sucessfully.png differ diff --git a/docs/images/cleaning_up_resources.png b/docs/images/cleaning_up_resources.png new file mode 100644 index 0000000..da00263 Binary files /dev/null and b/docs/images/cleaning_up_resources.png differ diff --git a/docs/images/cluster-conversion-1.png b/docs/images/cluster-conversion-1.png new file mode 100644 index 0000000..bbc3df9 Binary files /dev/null and b/docs/images/cluster-conversion-1.png differ diff --git a/docs/images/cluster-conversion-2.png b/docs/images/cluster-conversion-2.png new file mode 100644 index 0000000..dc75cd9 Binary files /dev/null and b/docs/images/cluster-conversion-2.png differ diff --git a/docs/images/create_profile.png b/docs/images/create_profile.png new file mode 100644 index 0000000..3e08dcb Binary files /dev/null and b/docs/images/create_profile.png differ diff --git a/docs/images/cronjob-logs-available.png b/docs/images/cronjob-logs-available.png new file mode 100644 index 0000000..8625e9d Binary files /dev/null and b/docs/images/cronjob-logs-available.png differ diff --git a/docs/images/debug-bundle-download-button.png b/docs/images/debug-bundle-download-button.png new file mode 100644 index 0000000..5fce5e8 Binary files /dev/null and b/docs/images/debug-bundle-download-button.png differ diff --git a/docs/images/deployment-architecture.png b/docs/images/deployment-architecture.png new file mode 100644 index 0000000..0f78e74 Binary files /dev/null and b/docs/images/deployment-architecture.png differ diff --git a/docs/images/deployment-architecture.png.old b/docs/images/deployment-architecture.png.old new file mode 100644 index 0000000..ab9f043 Binary files /dev/null and b/docs/images/deployment-architecture.png.old differ diff --git a/docs/images/firstboot-form-1.png b/docs/images/firstboot-form-1.png new file mode 100644 index 0000000..ed019df Binary files /dev/null and b/docs/images/firstboot-form-1.png differ diff --git a/docs/images/firstboot-form.png b/docs/images/firstboot-form.png new file mode 100644 index 0000000..c626f3a Binary files /dev/null and b/docs/images/firstboot-form.png differ diff --git a/docs/images/network-traffic-separation.png b/docs/images/network-traffic-separation.png new file mode 100644 index 0000000..98b3b56 Binary files /dev/null and b/docs/images/network-traffic-separation.png differ diff --git a/docs/images/profiles_page.png b/docs/images/profiles_page.png new file mode 100644 index 0000000..9b7e419 Binary files /dev/null and b/docs/images/profiles_page.png differ diff --git a/docs/images/revalidate_openstack_cred.png b/docs/images/revalidate_openstack_cred.png new file mode 100644 index 0000000..3ad752f Binary files /dev/null and b/docs/images/revalidate_openstack_cred.png differ diff --git a/docs/images/revalidate_vmware_cred.png b/docs/images/revalidate_vmware_cred.png new file mode 100644 index 0000000..c2ff30f Binary files /dev/null and b/docs/images/revalidate_vmware_cred.png differ diff --git a/docs/images/select_profile.png b/docs/images/select_profile.png new file mode 100644 index 0000000..af2b99a Binary files /dev/null and b/docs/images/select_profile.png differ diff --git a/docs/images/upgrade_available.png b/docs/images/upgrade_available.png new file mode 100644 index 0000000..61a2cb3 Binary files /dev/null and b/docs/images/upgrade_available.png differ diff --git a/docs/images/upgrade_completed.png b/docs/images/upgrade_completed.png new file mode 100644 index 0000000..2288f3a Binary files /dev/null and b/docs/images/upgrade_completed.png differ diff --git a/docs/images/upgrade_in_progress.png b/docs/images/upgrade_in_progress.png new file mode 100644 index 0000000..0a68f42 Binary files /dev/null and b/docs/images/upgrade_in_progress.png differ diff --git a/docs/images/upgrade_modal.png b/docs/images/upgrade_modal.png new file mode 100644 index 0000000..17f173b Binary files /dev/null and b/docs/images/upgrade_modal.png differ diff --git a/docs/images/vjb-cluster-conversion.gif b/docs/images/vjb-cluster-conversion.gif new file mode 100644 index 0000000..c1d0f34 Binary files /dev/null and b/docs/images/vjb-cluster-conversion.gif differ diff --git a/docs/images/vjb-internal.png b/docs/images/vjb-internal.png new file mode 100644 index 0000000..8d98145 Binary files /dev/null and b/docs/images/vjb-internal.png differ diff --git a/docs/images/vtpm-customize-hardware.png b/docs/images/vtpm-customize-hardware.png new file mode 100644 index 0000000..3b8bad5 Binary files /dev/null and b/docs/images/vtpm-customize-hardware.png differ diff --git a/docs/images/vtpm-datastore-default-policy.png b/docs/images/vtpm-datastore-default-policy.png new file mode 100644 index 0000000..66d835f Binary files /dev/null and b/docs/images/vtpm-datastore-default-policy.png differ diff --git a/docs/images/vtpm-disable-vbs.png b/docs/images/vtpm-disable-vbs.png new file mode 100644 index 0000000..baed0d8 Binary files /dev/null and b/docs/images/vtpm-disable-vbs.png differ diff --git a/docs/images/vtpm-flavor-metadata.png b/docs/images/vtpm-flavor-metadata.png new file mode 100644 index 0000000..42f26af Binary files /dev/null and b/docs/images/vtpm-flavor-metadata.png differ diff --git a/docs/images/vtpm-key-provider.png b/docs/images/vtpm-key-provider.png new file mode 100644 index 0000000..2e52356 Binary files /dev/null and b/docs/images/vtpm-key-provider.png differ diff --git a/docs/images/vtpm-migration-success.png b/docs/images/vtpm-migration-success.png new file mode 100644 index 0000000..9d02c3a Binary files /dev/null and b/docs/images/vtpm-migration-success.png differ diff --git a/docs/images/vtpm-remove-tpm-device.png b/docs/images/vtpm-remove-tpm-device.png new file mode 100644 index 0000000..ff98246 Binary files /dev/null and b/docs/images/vtpm-remove-tpm-device.png differ diff --git a/docs/images/vtpm-select-guest-os.png b/docs/images/vtpm-select-guest-os.png new file mode 100644 index 0000000..6c2af39 Binary files /dev/null and b/docs/images/vtpm-select-guest-os.png differ diff --git a/docs/images/vtpm-tpm-management-console.png b/docs/images/vtpm-tpm-management-console.png new file mode 100644 index 0000000..c562351 Binary files /dev/null and b/docs/images/vtpm-tpm-management-console.png differ diff --git a/docs/images/vtpm-virsh-tpm-verify.png b/docs/images/vtpm-virsh-tpm-verify.png new file mode 100644 index 0000000..5a22d6e Binary files /dev/null and b/docs/images/vtpm-virsh-tpm-verify.png differ diff --git a/docs/images/vtpm-vm-details-pcd.png b/docs/images/vtpm-vm-details-pcd.png new file mode 100644 index 0000000..a37fe9f Binary files /dev/null and b/docs/images/vtpm-vm-details-pcd.png differ diff --git a/docs/images/vtpm-vm-policies-menu.png b/docs/images/vtpm-vm-policies-menu.png new file mode 100644 index 0000000..7db04a4 Binary files /dev/null and b/docs/images/vtpm-vm-policies-menu.png differ diff --git a/docs/index.html b/docs/index.html index 4401cd0..8e996cd 100644 --- a/docs/index.html +++ b/docs/index.html @@ -1,5 +1,5 @@ - vJailbreak | vJailbreak; - + Skip to content
brew install oras
  • Download Image

    oras pull quay.io/platform9/vjailbreak:v0.1.9
  • Get PCD

    brew install oras
  • Download Image

    oras pull quay.io/platform9/vjailbreak:v0.4.10
  • Get PCD

    + Skip to content

    Components

    Architecture

    +

    Below is high level architecture of how vJailbreak works. vJailbreak runs +in a virtual machine in the target OpenStack environment. vJailbreak connects with VMware environment via vSphere APIs, using the VDDK library for the Standard copy method only. vJailbreak Accelerated Copy and Storage-Accelerated Copy transfer disk data without requiring VDDK. It also uses the OpenStack SDK to interact with the OpenStack environment and perform the necessary provisioning operations including creation of volumes, VMs.

    +

    vJailbreak Architecture

    +

    Components

    +

    Below is an overview of each component and its role in the migration process.

    +

    v2v-helper

    +

    The v2v-helper is the main application responsible for executing the migration process. It is designed to run as a pod within the vJailbreak virtual machine (VM) in the target OpenStack environment. It supports three storage copy methods: Standard (VDDK-based), vJailbreak Accelerated Copy (Hot-Add, VDDK-free), and Storage-Accelerated Copy (XCOPY, VDDK-free). Only Standard copy requires VDDK.

    +

    UI

    +

    The UI component provides a user-friendly interface for vJailbreak. It allows users to manage and monitor the migration process through an intuitive graphical interface.

    +

    migration-controller

    +

    The migration-controller is a Kubernetes controller that schedules and manages the migration tasks. It ensures that migrations are executed efficiently and in accordance with the defined policies.

    +

    v2v-cli

    +

    The v2v-cli is a command-line interface tool that can initiate the migration process. While it is available, it is not required in the current version of vJailbreak, as the primary interface is the UI.

    +

    By understanding these components, users can better appreciate the architecture and functionality of vJailbreak, enabling them to effectively manage and execute VM migrations.

    \ No newline at end of file diff --git a/docs/introduction/faq/index.html b/docs/introduction/faq/index.html new file mode 100644 index 0000000..205ab0a --- /dev/null +++ b/docs/introduction/faq/index.html @@ -0,0 +1,228 @@ + FAQ | vJailbreak + Skip to content

    FAQ

    What should I do if I cannot download VDDK?

    +

    VMware’s public VDDK download pages are currently unavailable. VDDK is only required for the +Standard storage copy method.

    +

    vJailbreak Accelerated Copy and Storage-Accelerated Copy do not require VDDK and can be +used immediately:

    +
      +
    • vJailbreak Accelerated Copy: works with any +datastore; cold migration only (source VM must be powered off before copy begins, live/hot +migration is not supported)
    • +
    • Storage-Accelerated Copy: requires a supported +storage array (Pure Storage or NetApp); cold migration only
    • +
    +

    Are IPs and MAC addresses persisted?

    +

    Yes, if your OpenStack network has a valid subnet range that allows the IP to be allocated, vJailbreak will create a port with the same MAC address and IP address as the source VM.

    +

    Are network interface names persisted?

    +

    Yes, vJailbreak can preserve network interface names during migration.

    +

    To enable this behavior, select Persist source network interfaces in the migration form under Migration Options.

    +

    Read more in Migration Options.

    +

    What OS versions are supported?

    +

    We internally use virt-v2v, so all operating systems supported for conversion by virt-v2v are supported by vJailbreak. You can find a detailed list of them here.

    +

    Do I need to perform any manual steps to remove VMware Tools?

    +

    No, vJailbreak will remove them for you, with the help of virt-v2v. The process that virt-v2v uses along with alternative approaches can be found here.

    +

    Do I need to perform any manual steps to install drivers for Linux and Windows VMs?

    +

    No, vJailbreak will install it for you. For Windows, we allow you to specify a URL for a specific version of virtio drivers. This is useful for older Windows versions, eg. Windows Server 2012, which specifically need v0.1.189 in order to work.

    +

    Why does the conversion step take time?

    +

    The delay is typically not because a single script is slow. The overall conversion process includes OS-level changes that take time, such as installing VirtIO drivers, removing old hypervisor drivers, and (for Windows guests) performing registry changes.

    +

    The helper scripts that apply static changes (for example, writing mount persistence entries to /etc/fstab) are simple and usually complete quickly.

    +

    Conversion time depends heavily on your infrastructure performance (especially CPU) and VM-specific factors, including the guest OS and root disk size.

    +

    Why does nbdcopy fail during disk copy?

    +

    If this issue is seen, most of the time it is a DNS/name-resolution problem. Debug logs typically show DNS resolution errors when vJailbreak tries to connect to an ESXi host.

    +

    Error signature:

    +
    failed to run nbdcopy: exec: already started
    +

    See: Debug Logs.

    +

    See the troubleshooting guide: nbdcopy fails during disk copy (often DNS resolution).

    +

    What do when virt-v2v fails with rename: /sysroot/etc/resolv.conf ... Operation not permitted?

    +

    The conversion fails because /etc/resolv.conf is marked immutable inside the source VM. virt-v2v cannot rename or replace immutable files during guest filesystem conversion.

    +

    Quick fix: Inside the source VM, remove the immutable attribute before migrating:

    +
    Terminal window
    chattr -i /etc/resolv.conf
    +

    For the full symptom description, root cause analysis, and verification steps, see: virt-v2v fails: rename /sysroot/etc/resolv.conf Operation not permitted

    +

    How does Vjailbreak handle flavors of the vm in the target openstack environment?

    +

    vJailbreak provides users the flexibility to assign desired OpenStack flavors to virtual machines during the migration setup. If the user specifies a flavor in the migration form, vJailbreak will honor that choice during provisioning on the target OpenStack environment. When migrating via CLI/kubectl, the same explicit choice can be made by setting spec.targetFlavorId on the VM’s VMwareMachine custom resource — see Explicitly select the target OpenStack flavor.

    +

    If no flavor is explicitly chosen, vJailbreak automatically selects the most appropriate flavor based on the VM’s resource requirements (We always try to find the exact match if not the next best match). In cases where no suitable flavor is found, the UI will display a warning. If the user proceeds despite the warning, the migration will fail with a clear error message indicating that a compatible flavor could not be found.

    +

    Hotplug Flavor Support

    +

    vJailbreak supports migrating VMs to OpenStack flavors that have hotplug CPU and RAM enabled. Hotplug allows live resize of vCPUs and memory without powering off the VM after migration.

    +

    To use hotplug after migration:

    +
      +
    1. +

      On Platform9 Private Cloud Director (PCD): a hotplug base flavor named hotplug is available by default. While triggering the migration, simply select the hotplug flavor for the VMs that should support live resize.

      +
    2. +
    3. +

      On other OpenStack environments: ask your administrator to create or identify a flavor with hotplug-enabled extra specs, and select it in the migration form. Example:

      +
      Terminal window
      openstack flavor set <flavor-name> \
      --property hw:cpu_policy=mixed \
      --property hw:cpu_max_vcpus=<max-vcpus>
      +
    4. +
    5. +

      After migration completes, resize the VM to add or remove vCPUs and RAM without a reboot.

      +
    6. +
    +

    When a hotplug base flavor (0 vCPU, 0 RAM — such as PCD’s default hotplug flavor) is assigned, vJailbreak sets the VM’s hotplug metadata at creation time: the current values (HOTPLUG_CPU, HOTPLUG_MEMORY) match the source VM, and the maximum values (HOTPLUG_CPU_MAX, HOTPLUG_MEMORY_MAX) are set to twice the source VM’s vCPU and memory. This gives every migrated VM room to live-resize up to 2x its original size. Hotplug metadata cannot be changed after the VM is created.

    + +

    Can vJailbreak migrate VMs running Docker Engine?

    +

    Yes, vJailbreak can migrate VMs running Docker Engine without any issues. vJailbreak performs VM-level migration and is agnostic to the workloads running inside the VM. Docker Engine is simply software running on the guest operating system, and the migration process handles it like any other application.

    +

    Can vJailbreak migrate VMs that are part of a Kubernetes cluster?

    +

    Yes, you can migrate VMs that are part of a Kubernetes cluster. However, it’s important to understand that vJailbreak operates purely as a VM migration tool and has no awareness of Kubernetes components or distributed applications running on the VM.

    +

    Users are responsible for taking necessary steps to ensure no disruption to applications and the distributed architecture of Kubernetes. This may include draining nodes, managing pod scheduling, and coordinating the migration with cluster operations. vJailbreak is not responsible for managing any workload-specific concerns.

    +

    Can vJailbreak migrate Kubernetes Persistent Volume Claims (PVCs)?

    +

    No, vJailbreak does not migrate PVCs. PVCs are Kubernetes constructs managed by CSI drivers and storage backends. vJailbreak has no visibility into these workload-level abstractions.

    +

    vJailbreak migrates virtual machines along with the disks that are currently attached to those VMs at the time of migration. Any storage managed by Kubernetes (such as PVCs) must be handled separately using Kubernetes-native tools or storage migration solutions.

    +

    Can vJailbreak migrate VMs running Docker Swarm clusters?

    +

    Yes, vJailbreak can migrate VMs that are part of a Docker Swarm cluster. However, the same principles apply as with any distributed workload: vJailbreak performs VM-level migration and does not manage workload-specific concerns.

    +

    Users must take appropriate precautions for Docker Swarm, such as draining nodes, managing service placement, and ensuring cluster quorum is maintained during migration. vJailbreak does not inspect or manage what is running inside the VM.

    +

    How do I migrate a Windows VM with Group Policy (GPO) applied?

    +

    GPO settings can block driver injection and registry changes that vJailbreak performs during conversion. The migration may succeed but produce a VM that fails to boot or has missing drivers.

    +

    See the GPO Migration Guide for steps to temporarily disable or scope down GPO before migration.

    +

    How do I migrate a VM with vTPM (Virtual Trusted Platform Module) enabled?

    +

    VMs with vTPM and Virtualization Based Security (VBS) enabled require special handling. You must temporarily disable vTPM on the source VM before migration, then re-enable it on the destination VM post-migration.

    +

    See the vTPM Migration Guide for step-by-step instructions.

    \ No newline at end of file diff --git a/docs/introduction/getting_started/index.html b/docs/introduction/getting_started/index.html index 5bf52aa..d59566e 100644 --- a/docs/introduction/getting_started/index.html +++ b/docs/introduction/getting_started/index.html @@ -1,4 +1,4 @@ - Getting Started | vJailbreak + Skip to content
    Skip to content

    Getting Started

    Network and access requirements

    +

    Getting Started

    vJailbreak works by running itself as a VM on the target OpenStack cloud. It connects remotely +to the VMware vSphere environment to perform the migration. vJailbreak supports multiple storage +copy methods: the Standard copy method uses the VMware VDDK library, while vJailbreak +Accelerated Copy and Storage-Accelerated Copy do not require VDDK. These are +the recommended methods when VDDK is unavailable.

    +

    It also uses the OpenStack SDK to interact with the OpenStack environment and perform the necessary +provisioning operations including creation of volumes, VMs.

    +

    Network and access requirements

    Ensure that your vJailbreak VM can communicate with your OpenStack and VMware environments. This includes any setup required for VPNs, etc.

    +
    Further details can be found in Prerequisites.
    -

    Install ORAS and download vJailbreak

    -

    Download and install ORAS. Then, download the latest version of the vJailbreak image with the following command:

    -
    Terminal window
    oras pull quay.io/platform9/vjailbreak:v0.1.9
    -

    This will download the vJailbreak qcow2 folder containing the image locally in the current directory named vjailbreak_qcow2/vjailbreak-image.qcow2.

    -

    Upload image and create vJailbreak VM

    - +

    Download vJailbreak Image

    +

    You can download the vJailbreak image using one of the following methods:

    +

    Option 1: Using ORAS

    +

    Download and install ORAS, a toolkit to download the qcow2 image of vJailbreak. Then, download the latest version of the vJailbreak image with the following command:

    +
    Terminal window
    oras pull quay.io/platform9/vjailbreak:v0.4.10
    +

    For older versions, download the vJailbreak image with the following command:

    +
    Terminal window
    oras pull quay.io/platform9/vjailbreak:<version>
    +

    These will download the vJailbreak qcow2 folder containing the image locally in the current directory named vjailbreak_qcow2/vjailbreak-image.qcow2.

    +

    Option 2: Direct Download from S3

    +

    For the latest version, download directly using wget:

    +
    Terminal window
    wget https://vjailbreak.s3.us-west-2.amazonaws.com/releases/latest/vjailbreak.qcow2
    +

    For older versions, download directly using wget:

    +
    Terminal window
    wget https://vjailbreak.s3.us-west-2.amazonaws.com/releases/<version>/vjailbreak.qcow2
    +

    These will download the vJailbreak qcow2 image locally in the same directory named vjailbreak.qcow2.

    +

    Note: This direct download method is supported starting from v0.3.2 and later versions.

    +

    Upload image to OpenStack

    +

    These example instructions are for any version of Private Cloud Director - Platform9 hosted, self-hosted, or Community Edition - but can be adapted for any OpenStack-compliant cloud.

    • Follow the instructions in Private Cloud Director > Images > Import with CLI to upload the image from the command line.
    • -
    • Upload vjailbreak-image.qcow2 to your image library. -
      Terminal window
      openstack image create --os-interface admin --insecure --container-format bare --disk-format qcow2 --file vjailbreak-image.qcow2 vjailbreak-image.qcow2
      -
    • -
    • Deploy a new VM from image, choosing the m1.xlarge flavor.
    • +
    • Upload the vJailbreak qcow2 image to your image library.
    • +
    +
    Terminal window
    openstack image create --os-interface admin --insecure --container-format bare --disk-format qcow2 --file <vjailbreak-image-path> vjailbreak-image.qcow2
    +
      +
    • Set the disk bus to virtio-scsi on the uploaded image. During migrations, vJailbreak attaches the target volumes to itself for conversion; with the default virtio-blk bus each attached volume consumes a PCI slot, and migrating VMs with many disks can fail with a No more available PCI slots error. +With virtio-scsi, all volumes share a single controller. See Known Limitations.
    • +
    +
    Terminal window
    openstack image set --property hw_disk_bus=scsi --property hw_scsi_model=virtio-scsi vjailbreak-image.qcow2
    +

    Create vJailbreak VM

    +
      +
    • Deploy a new VM from the uploaded image, choosing the m1.xlarge.vol flavor (use larger flavor for larger VM migration).
    • Choose a network that can reach your VMware vCenter environment.
    • -
    • Give the VM a name, optionally assign an SSH key, and set a password using cloud-init.
    • Assign a network security group that allows inbound and outbound traffic.
    -

    Copy VDDK Libraries

    +

    Network Initialization

    +

    vJailbreak requires a valid IP address and network route to initialize properly. The installation behavior differs based on your network configuration:

    +

    Standard DHCP Networks

    +

    In most PCD deployments, the VM receives an IP address via DHCP immediately at boot. The vJailbreak installation proceeds automatically and the UI becomes accessible within a few minutes at http://<vm-ip>/.

    +

    L2-Only Networks

    +

    In environments where IP addresses are assigned manually after VM deployment (L2-only networks without DHCP), the vJailbreak installation follows a specific sequence. This section provides a detailed walkthrough of what to expect and how to configure the VM.

    +

    Step 1: VM Boot and Network Wait State

    +

    When the vJailbreak VM starts in an L2-only network, the installation script detects that no IP address is available and enters a waiting state. You will see the following behavior in the console:

    +
      +
    1. The VM boots and initializes basic services
    2. +
    3. The script sets a default password for the ubuntu user
    4. +
    5. K3s master setup begins but pauses waiting for network availability
    6. +
    7. The console displays repeated messages indicating it is waiting for network configuration
    8. +
    +

    Console output during wait state:

    +
    [2026-03-17 10:07:59] IS_MASTER: true
    [2026-03-17 10:07:59] MASTER_IP:
    [2026-03-17 10:07:59] K3S_TOKEN:
    [2026-03-17 10:07:59] INSTALL_K3S_EXEC: server --secrets-encryption
    [2026-03-17 10:07:59] Setting default password for ubuntu user...
    [2026-03-17 10:07:59] Default password set for ubuntu user. User will need to change it on first login
    [2026-03-17 10:07:59] Setting up K3s Master...
    [2026-03-17 10:07:59] Waiting for network availability...
    [2026-03-17 10:07:59] Waiting for network: missing default route and global IPv4 address...
    [2026-03-17 10:08:59] Waiting for network: missing default route and global IPv4 address...
    [2026-03-17 10:09:59] Waiting for network: missing default route and global IPv4 address...
    +

    The script checks every minute for:

      -
    • Copy the latest version of the VDDK libraries for Linux into /home/ubuntu of the vJailbreak VM. Untar it to a folder named vmware-vix-disklib-distrib in the /home/ubuntu directory.
    • +
    • A non-loopback IPv4 address assigned to a network interface
    • +
    • A default route configured in the routing table
    • +
    +

    Note: The VM is fully accessible via console during this waiting period. You can log in and configure networking while the script waits.

    +

    Step 2: Assign an IP Address

    +

    You can assign an IP address using one of the following methods:

    +

    Option A: Using DHCP Client

    +

    If your network has a DHCP server available (but wasn’t configured at boot), you can request an IP:

    +
    Terminal window
    # Check current network interface status
    ip a
    +
    # Request IP via DHCP on the primary interface (commonly ens3 or enp0s3)
    dhclient ens3
    +

    After running dhclient, verify the IP assignment:

    +
    Terminal window
    ip a
    +

    You should see output similar to:

    +
    2: ens3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
    link/ether fa:16:3e:50:e9:85 brd ff:ff:ff:ff:ff:ff
    altname enp0s3
    inet 10.96.9.142/20 brd 10.96.15.255 scope global dynamic ens3
    valid_lft 8631sec preferred_lft 8631sec
    +

    Option B: Static IP Configuration

    +

    For environments without DHCP, configure a static IP address:

    +
    Terminal window
    # Assign static IP (replace with your network details)
    sudo ip addr add 192.168.1.100/24 dev ens3
    +
    # Configure default gateway
    sudo ip route add default via 192.168.1.1
    +
    # Verify configuration
    ip a
    ip route
    +

    Important: Ensure the IP address and gateway are appropriate for your network. Contact your network administrator if you’re unsure about the correct values.

    +

    Step 3: Automatic Installation Proceeds

    +

    Once the network is configured, the installation script automatically detects the change and proceeds with the setup. You can monitor progress by tailing the installation log:

    +
    Terminal window
    tail -f /var/log/pf9-install.log
    +

    You will see the script transition from waiting to active installation:

    +
    [2026-03-17 11:08:00] Waiting for network: missing default route and global IPv4 address...
    [2026-03-17 11:09:01] Network detected. Default route and global IPv4 address available.
    [2026-03-17 11:09:09] K3s is ready.
    [2026-03-17 11:09:10] Loading all the images in /etc/pf9/images...
    [2026-03-17 11:10:38] Applying kube-prometheus manifests...
    [2026-03-17 11:10:50] K3s master setup completed
    [2026-03-17 11:10:51] Rsync daemon started successfully.
    [2026-03-17 11:10:51] Config map created successfully.
    [2026-03-17 11:10:51] Installing cert-manager
    [2026-03-17 11:10:53] Waiting for cert-manager deployments to become available
    [2026-03-17 11:11:04] removing the cron job
    +

    The installation performs the following steps automatically:

    +
      +
    1. K3s Initialization - Kubernetes cluster setup completes
    2. +
    3. Image Loading - Container images are loaded from /etc/pf9/images
    4. +
    5. Prometheus Setup - Monitoring stack is deployed
    6. +
    7. Rsync Daemon - File synchronization service starts
    8. +
    9. Cert-Manager - TLS certificate management is installed
    10. +
    11. Cleanup - Temporary cron jobs are removed
    12. +
    +

    Step 4: Access vJailbreak

    +

    Once installation completes (typically 2-5 minutes after network configuration), you can access vJailbreak:

    +
      +
    • UI Access: http://<assigned-ip>/
    • +
    • SSH Access: ssh ubuntu@<assigned-ip> (default password: password)
    • +
    +

    Troubleshooting L2-Only Network Setup

    + + + + + + + + + + + + + + + + + + + + + + + + + +
    IssueSolution
    Script not detecting IPEnsure both IP address AND default route are configured
    Installation stuck after IP assignmentCheck /var/log/pf9-install.log for errors
    Cannot reach UI after installationVerify security groups allow inbound traffic on port 80
    Network interface not visibleCheck VM network attachment in OpenStack dashboard
    +

    Verifying Network Readiness:

    +
    Terminal window
    # Check IP assignment
    ip addr show | grep "inet "
    +
    # Check default route
    ip route | grep default
    +
    # Both should return valid entries for installation to proceed
    +

    Configure VM with cloud-init

    +

    Cloud-init can be used during VM creation to automate initial configuration including setting passwords and configuring /etc/hosts entries for DNS resolution.

    +
      +
    • +

      When creating the VM, use a cloud-init configuration script to:

      +
        +
      • Set a password for the ubuntu user
      • +
      • Add static DNS entries to /etc/hosts
      • +
      +
      #cloud-config
      password: your-secure-password
      chpasswd: { expire: False }
      ssh_pwauth: True
      +
      write_files:
      - path: /etc/hosts.append
      append: true
      content: |
      # VMware and OpenStack endpoints
      192.168.1.100 vcenter.example.com
      192.168.2.100 openstack.example.com
      +
      runcmd:
      - cat /etc/hosts.append >> /etc/hosts
      +
    • +
    • +

      You can provide this cloud-init configuration when creating the VM through the OpenStack dashboard or CLI.

      +
    • +
    +

    Note: If you do not set a password for the ubuntu user using cloud-init, the default password for the ubuntu user will be set to “password.” After the first login, you will be prompted to change the password and if the password is set using cloud-init you will not be prompted to change the password at first login.

    +

    Copy VDDK Libraries (Standard Copy Only)

    +

    VDDK is required only if you plan to use the Standard storage copy method. If you are using +vJailbreak Accelerated Copy or Storage-Accelerated Copy, skip this step; neither method +requires VDDK.

    + +

    If you have a VDDK 8.0.x package, copy it into /home/ubuntu of the vJailbreak VM and untar it +to a folder named vmware-vix-disklib-distrib:

    +
    Terminal window
    cd /home/ubuntu
    tar -xvf VMware-vix-disklib-8.0.3-*.x86_64.tar.gz
    # This will create the vmware-vix-disklib-distrib directory
    +

    Configure DNS Resolution

    +

    Proper DNS resolution for your VMware and OpenStack URLs is required for vJailbreak to function correctly. You can either configure this during VM creation with cloud-init (as shown above) or modify the configuration after VM deployment.

    +

    Important: DNS resolution for all ESXi hosts must be properly configured in your environment. This is specifically required during the VM copy phase of migration. Without proper DNS resolution for ESXi hosts, the migration process may fail.

    +
      +
    • +

      Static Entries: If you didn’t configure /etc/hosts entries during VM creation with cloud-init, you can add them manually later.

      +
      Terminal window
      # Example manual addition to /etc/hosts
      sudo sh -c 'echo "192.168.1.100 vcenter.example.com" >> /etc/hosts'
      sudo sh -c 'echo "192.168.2.100 openstack.example.com" >> /etc/hosts'
      +
      # ESXi hosts entries (required for VM copy phase)
      sudo sh -c 'echo "192.168.1.101 esxi01.example.com esxi01" >> /etc/hosts'
      sudo sh -c 'echo "192.168.1.102 esxi02.example.com esxi02" >> /etc/hosts'
      +

      Once the /etc/hosts file is modified execute below commands to apply the changes.

      +
      kubectl -n migration-system rollout restart deployment migration-controller-manager
      +
    • +
    • +

      DNS Configuration: If modifying /etc/resolv.conf to use DNS servers instead of static entries, restart the same migration-controller-manager deployment as shown above to apply the changes.

      +
    • +
    +

    Initial Access Steps

    +
      +
    • +

      SSH Access

      +
        +
      • Username: ubuntu
      • +
      • Password: password
      • +
      • Note: After you SSH, you will be prompted to change the password.
      • +
      • Example: +
        Terminal window
        ssh ubuntu@<vjailbreak-vm-ip>
        +
      • +
      +
    • +
    • +

      UI Access

      +
        +
      • URL: http://<vjailbreak-vm-ip>/
      • +
      • Username: admin
      • +
      • Password: password
      • +
      +

    Launch vJailbreak

    • Connect to the vJailbreak UI using the IP address assigned during VM creation.
    • -
    • Start the migration process by providing the VMware vCenter and OpenStack admin.rc credentials.
    • +
    • Add VMware and OpenStack credentials. See more here Credential Management.
    • Select the VMs you wish to migrate and complete the rest of the migration form.
    • Migrate your VMs.

    Scaling vJailbreak

    -
    Read more about scaling vJailbreak.
    \ No newline at end of file +
    Read more about scaling vJailbreak.
    \ No newline at end of file diff --git a/docs/introduction/prerequisites/index.html b/docs/introduction/prerequisites/index.html index 4f3e91b..de5f62d 100644 --- a/docs/introduction/prerequisites/index.html +++ b/docs/introduction/prerequisites/index.html @@ -1,4 +1,4 @@ - FAQ & Prerequisites | vJailbreak + Skip to content
    Skip to content

    FAQ & Prerequisites

    Are IPs and MAC addresses persisted?

    -

    Yes, if your OpenStack network has a valid subnet range that allows the IP to be allocated, vJailbreak will create a port with the same MAC address and IP address as the source VM.

    -

    What OS versions are supported?

    -

    We internally use virt-v2v, so all operating systems supported for conversion by virt-v2v are supported by vJailbreak. You can find a detailed list of them here.

    -

    Do I need to perform any manual steps to remove VMware Tools?

    -

    No, vJailbreak will remove them for you, with the help of virt-v2v. The process that virt-v2v uses along with alternative approaches can be found here.

    -

    Do I need to perform any manual steps to install drivers for Linux and Windows VMs?

    -

    No, vJailbreak will install it for you. For Windows, we allow you to specify a URL for a specific version of virtio drivers. This is useful for older Windows versions, eg. Windows Server 2012, which specifically need v0.1.189 in order to work.

    +

    Prerequisites

    For frequently asked questions, see FAQ.

    +

    VDDK requirements

    +

    VDDK is required only if you plan to use the Standard storage copy method. +vJailbreak Accelerated Copy and Storage-Accelerated Copy do not require VDDK.

    +

    What access do I need for my vCenter user to be able to perform this migration?

    -

    Please refer to the following table for the required privileges:

    +

    The required privileges depend on which features you use. The base set below is required for all migrations. Additional privileges are listed separately for vJailbreak Accelerated Copy and OVA-based Proxy VM deployment.

    +

    Base Privileges (all migrations)

    @@ -279,7 +293,125 @@ starlight-tabs:where(.astro-esqgolmp){display:block}.tablist-wrapper:where(.astr -
    PrivilegeDescription
    Virtual machine.Interaction privileges:
    Virtual machine.Interaction.Power OffAllows powering off a powered-on virtual machine. This operation powers down the guest operating system.
    Virtual machine.Interaction.Power OnAllows powering on a powered-off virtual machine and resuming a suspended virtual machine.
    Virtual machine.Guest operating system management by VIX APIAllows managing a virtual machine by the VMware VIX API.
    Virtual machine.ProvisioningNote: All Virtual machine.Provisioning privileges are required.
    Virtual machine.Provisioning.Allow disk accessAllows opening a disk on a virtual machine for random read and write access. Used mostly for remote disk mounting.
    Virtual machine.Provisioning.Allow file accessAllows operations on files associated with a virtual machine, including VMX, disks, logs, and NVRAM.
    Virtual machine.Provisioning.Allow read-only disk accessAllows opening a disk on a virtual machine for random read access. Used mostly for remote disk mounting.
    Virtual machine.Provisioning.Allow virtual machine downloadAllows read operations on files associated with a virtual machine, including VMX, disks, logs, and NVRAM.
    Virtual machine.Provisioning.Allow virtual machine files uploadAllows write operations on files associated with a virtual machine, including VMX, disks, logs, and NVRAM.
    Virtual machine.Provisioning.Clone templateAllows cloning of a template.
    Virtual machine.Provisioning.Clone virtual machineAllows cloning of an existing virtual machine and allocation of resources.
    Virtual machine.Provisioning.Create template from virtual machineAllows creation of a new template from a virtual machine.
    Virtual machine.Provisioning.Customize guestAllows customization of a virtual machine’s guest operating system without moving the virtual machine.
    Virtual machine.Provisioning.Deploy templateAllows deployment of a virtual machine from a template.
    Virtual machine.Provisioning.Mark as templateAllows marking an existing powered-off virtual machine as a template.
    Virtual machine.Provisioning.Mark as virtual machineAllows marking an existing template as a virtual machine.
    Virtual machine.Provisioning.Modify customization specificationAllows creation, modification, or deletion of customization specifications.
    Virtual machine.Provisioning.Promote disksAllows promote operations on a virtual machine’s disks.
    Virtual machine.Provisioning.Read customization specificationsAllows reading a customization specification.
    Virtual machine.Snapshot management privileges:
    Virtual machine.Snapshot management.Create snapshotAllows creation of a snapshot from the virtual machine’s current state.
    Virtual machine.Snapshot management.Remove SnapshotAllows removal of a snapshot from the snapshot history.
    Datastore privileges:
    Datastore.Browse datastoreAllows exploring the contents of a datastore.
    Datastore.Low level file operationsAllows performing low-level file operations - read, write, delete, and rename - in a datastore.
    Sessions privileges:
    Sessions.Validate sessionAllows verification of the validity of a session.
    Cryptographic privileges:
    Cryptographic.DecryptAllows decryption of an encrypted virtual machine.
    Cryptographic.Direct accessAllows access to encrypted resources.
    + + + + +
    PrivilegeDescription
    Virtual machine.Interaction privileges:
    Virtual machine.Interaction.Power OffAllows powering off a powered-on virtual machine. This operation powers down the guest operating system.
    Virtual machine.Interaction.Power OnAllows powering on a powered-off virtual machine and resuming a suspended virtual machine.
    Virtual machine.Config.ChangeTrackingAllows enabling or disabling change tracking on a virtual machine.
    Virtual machine.Guest operating system management by VIX APIAllows managing a virtual machine by the VMware VIX API.
    Virtual machine.ProvisioningNote: All Virtual machine.Provisioning privileges are required.
    Virtual machine.Provisioning.Allow disk accessAllows opening a disk on a virtual machine for random read and write access. Used mostly for remote disk mounting.
    Virtual machine.Provisioning.Allow file accessAllows operations on files associated with a virtual machine, including VMX, disks, logs, and NVRAM.
    Virtual machine.Provisioning.Allow read-only disk accessAllows opening a disk on a virtual machine for random read access. Used mostly for remote disk mounting.
    Virtual machine.Provisioning.Allow virtual machine downloadAllows read operations on files associated with a virtual machine, including VMX, disks, logs, and NVRAM.
    Virtual machine.Provisioning.Allow virtual machine files uploadAllows write operations on files associated with a virtual machine, including VMX, disks, logs, and NVRAM.
    Virtual machine.Provisioning.Clone templateAllows cloning of a template.
    Virtual machine.Provisioning.Clone virtual machineAllows cloning of an existing virtual machine and allocation of resources.
    Virtual machine.Provisioning.Create template from virtual machineAllows creation of a new template from a virtual machine.
    Virtual machine.Provisioning.Customize guestAllows customization of a virtual machine’s guest operating system without moving the virtual machine.
    Virtual machine.Provisioning.Deploy templateAllows deployment of a virtual machine from a template.
    Virtual machine.Provisioning.Mark as templateAllows marking an existing powered-off virtual machine as a template.
    Virtual machine.Provisioning.Mark as virtual machineAllows marking an existing template as a virtual machine.
    Virtual machine.Provisioning.Modify customization specificationAllows creation, modification, or deletion of customization specifications.
    Virtual machine.Provisioning.Promote disksAllows promote operations on a virtual machine’s disks.
    Virtual machine.Provisioning.Read customization specificationsAllows reading a customization specification.
    Virtual machine.Snapshot management privileges:
    Virtual machine.Snapshot management.Create snapshotAllows creation of a snapshot from the virtual machine’s current state.
    Virtual machine.Snapshot management.Remove SnapshotAllows removal of a snapshot from the snapshot history.
    Datastore privileges:
    Datastore.Browse datastoreAllows exploring the contents of a datastore.
    Datastore.Low level file operationsAllows performing low-level file operations - read, write, delete, and rename - in a datastore.
    Sessions privileges:
    Sessions.Validate sessionAllows verification of the validity of a session.
    Cryptographic privileges:
    Cryptographic.DecryptAllows decryption of an encrypted virtual machine.
    Cryptographic.Direct accessAllows access to encrypted resources.
    +

    Additional privileges required for encrypted VMs

    + + + + + + + + + + + + + + + + + +
    PrivilegePurpose
    Cryptographer.AccessBase access to cryptographic operations on the VM
    Cryptographer.DecryptDecrypt the VM’s disks for read access during migration
    +

    Troubleshooting

    +

    VixDiskLib_Open token-retrieval failure

    +

    Symptom: Migration fails during disk open with:

    +
    Error 1 (Unknown error): Unexpected error when trying to retrieve token for disk
    Unable to locate appropriate transport mode
    +

    Cause: Missing Cryptographer.Access and/or Cryptographer.Decrypt privilege on the migration service account. This is the most common encrypted-VM permissions failure and does not surface as a permissions error in the message text.

    +

    Fix: Verify the account role includes both Cryptographer.Access and Cryptographer.Decrypt. Re-check with govc permissions.ls against the target VM.

    +

    Additional Privileges: vJailbreak Accelerated Copy Migrations

    +

    Required when using the vJailbreak Accelerated Copy storage copy method (VMware hot-add). vJailbreak attaches snapshot disks from the source VM to a Proxy VM and detaches them after the NBD copy completes. The controller also automatically enables disk.EnableUUID on the Proxy VM if it is not already set.

    + + + + + + + + + + + + + + + + + + + + + +
    PrivilegeDescription
    Virtual machine.Config.AddExistingDiskAllows attaching an existing VMDK (snapshot disk) to another virtual machine — used to attach source disks to the Proxy VM.
    Virtual machine.Config.RemoveDiskAllows removing a disk from a virtual machine — used to detach source disks from the Proxy VM after copy.
    Virtual machine.Config.AdvancedConfigAllows modifying VM extra config parameters — used to set disk.EnableUUID = TRUE on the Proxy VM.
    +

    Additional Privileges: OVA-Based Proxy VM Deployment

    +

    Required only when using the Deploy a new vJailbreak Proxy VM option in the UI. These privileges are not needed if you register an existing VM as the Proxy VM.

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    PrivilegeDescription
    vApp.ImportAllows importing an OVF/OVA package into vCenter — used to deploy the pre-built Proxy VM appliance.
    Datastore.AllocateSpaceAllows allocating disk space on a datastore — used to create the Proxy VM disk files during OVA import.
    Network.AssignAllows assigning a network to a virtual machine or vApp — used to connect the Proxy VM to the selected portgroup.
    Resource.AssignVAppToPoolAllows assigning a vApp to a resource pool — used to place the deployed Proxy VM in the target compute resource.
    Virtual machine.Inventory.CreateAllows creating a virtual machine in the vCenter inventory — used to register the Proxy VM after OVA import.
    Virtual machine.Config.AdvancedConfigAllows modifying VM extra config parameters — used to set disk.EnableUUID = TRUE on the newly deployed Proxy VM.
    +

    Understanding VMware NFC Performance Limitations

    +

    vJailbreak uses nbdkit to transfer disk data from VMware ESXi hosts via the NFC (Network File Copy) protocol over port 902. It’s important to understand the inherent performance characteristics and limitations of VMware’s NFC protocol:

    +

    NFC Protocol Characteristics

    +
      +
    • Per-VMDK throughput limit: NFC is limited to approximately 1 Gbps per VMDK due to VMware’s internal implementation
    • +
    • Single-threaded: NFC operations are single-threaded, limiting performance to what a single thread can achieve
    • +
    • Encrypted by default: NFC traffic is SSL-encrypted, which adds overhead (disabling SSL can improve speed by up to 20% but reduces security)
    • +
    • Synchronous operations: NFC must complete READ/WRITE/CHECK operations sequentially before proceeding
    • +
    • Latency-aware throttling: NFC will automatically throttle when network latency increases
    • +
    +

    Impact on vJailbreak Migrations

    +
      +
    • Per-disk transfer speed: Each VMDK transfers at approximately 1 Gbps (125 MB/s), regardless of available network bandwidth
    • +
    • Network saturation: Multiple parallel VM migrations can saturate network links (e.g., a 10 Gbps link can theoretically support ~10 concurrent VM migrations)
    • +
    • Migration time estimation: Expect transfer times of approximately 8-9 minutes per 100 GB per VMDK
    • +
    +

    Recommendations

    +
      +
    • Plan migration schedules accounting for the ~1 Gbps per-VMDK limitation
    • +
    • For VMs with large single disks, migration time will be constrained by NFC throughput rather than network capacity
    • +
    • Use parallel migrations across multiple VMs to better utilize available network bandwidth
    • +
    • Monitor network utilization to optimize the number of concurrent migrations
    • +
    • Consider scheduling large VM migrations during maintenance windows
    • +
    +

    References:

    +

    What ports do I need to open for vJailbreak to work?

    Please refer the following table for the required ports:

    @@ -322,9 +454,61 @@ starlight-tabs:where(.astro-esqgolmp){display:block}.tablist-wrapper:where(.astr -
    PortProtocolSourceDestinationPurpose
    443TCPPCD nodesVMware vCenter API endpiontVMware provider inventory

    Disk transfer authentication
    443TCPPCD nodesVMware ESXi hostsDisk transfer authentication
    902TCPPCD nodesVMware ESXi hostsDisk transfer data copy
    5480TCPPCD nodesVMware vCenter API endpointVMware Site Recovery Manager Appliance Management Interface
    + + + + + + + +
    PortProtocolSourceDestinationPurpose
    443TCPPCD nodesVMware vCenter API endpointVMware provider inventory

    Disk transfer authentication
    443TCPPCD nodesVMware ESXi hostsDisk transfer authentication
    902TCPPCD nodesVMware ESXi hostsDisk transfer data copy via NFC protocol (see NFC limitations above)
    22TCPvJailbreak VMProxy VMvJailbreak Accelerated Copy only: SSH control of the Proxy VM
    10809–11808TCPvJailbreak VMProxy VMvJailbreak Accelerated Copy only: qemu-nbd disk transfer (one port per disk copied in parallel)

    What network connectivity do I need for vJailbreak?

    -

    The vJailbreak VM and any helper nodes must be able to resolve & connect to your VMware vCenter environment and all ESXi hosts, and must be able to resolve & connect to quay.io.

    +

    During normal migration operations, vJailbreak only requires connectivity to vCenter, ESXi hosts, and the OpenStack API. The additional endpoints listed below are needed only when installing or upgrading vJailbreak itself.

    +

    Required for Migration (always)

    +

    The vJailbreak VM and any helper nodes must be able to reach:

    +
      +
    • vCenter and ESXi hosts — for VM inventory, disk transfer authentication, and NFC data copy (ports 443 and 902)
    • +
    • OpenStack API endpoints — for creating volumes, networks, and VM resources at the destination
    • +
    • ICMP (ping) access to guest VM IPs — for post-migration connectivity verification (health checks)
    • +
    • Health-check endpoints on migrated guest VMs — over user-defined HTTP/HTTPS ports, if health checks are enabled
    • +
    +

    Required for Installation and Upgrades Only

    +

    The following endpoints are accessed during first-time installation or when upgrading vJailbreak. They are not required during normal migration operations:

    +

    Required Ingress Rules for Kubernetes Node with Kubelet, Metrics Server, and Prometheus

    @@ -380,4 +564,4 @@ starlight-tabs:where(.astro-esqgolmp){display:block}.tablist-wrapper:where(.astr -
    ComponentPortProtocolSourcePurpose
    Kubelet API10250TCPControl Plane / PrometheusHealth checks, logs, metrics
    Kubelet Read-Only (Optional)10255TCPInternal OnlyDeprecated but might be used in some cases
    Metrics Server4443TCPInternal ClusterK8s resource metrics (kubectl top)
    Prometheus9090TCPInternal Cluster / Monitoring ServerPrometheus UI and API
    Node Exporter (if used)9100TCPPrometheusNode-level metrics
    Cadvisor (Optional)4194TCPInternal Cluster / PrometheusContainer metrics collection
    \ No newline at end of file +
    ComponentPortProtocolSourcePurpose
    Kubelet API10250TCPControl Plane / PrometheusHealth checks, logs, metrics
    Kubelet Read-Only (Optional)10255TCPInternal OnlyDeprecated but might be used in some cases
    Metrics Server4443TCPInternal ClusterK8s resource metrics (kubectl top)
    Prometheus9090TCPInternal Cluster / Monitoring ServerPrometheus UI and API
    Node Exporter (if used)9100TCPPrometheusNode-level metrics
    Cadvisor (Optional)4194TCPInternal Cluster / PrometheusContainer metrics collection
    \ No newline at end of file diff --git a/docs/introduction/what_is_vjailbreak/index.html b/docs/introduction/what_is_vjailbreak/index.html index e85ba5b..88c61c0 100644 --- a/docs/introduction/what_is_vjailbreak/index.html +++ b/docs/introduction/what_is_vjailbreak/index.html @@ -1,4 +1,4 @@ - What is vJailbreak? | vJailbreak + Skip to content
    Skip to content

    What is vJailbreak?

    vJailbreak is an open-source tool featuring a user-friendly interface designed to simplify and accelerate the migration of virtual machines (VMs) from VMware vSphere environments to any OpenStack-compliant cloud. It eliminates the complexities of cross-platform VM migration, enabling you to modernize your infrastructure with minimal disruption and a streamlined, visual workflow.

    -

    How vJailbreak Works

    +

    What is vJailbreak?

    vJailbreak is an open-source tool featuring a user-friendly interface designed to simplify and accelerate the migration of virtual machines (VMs) from VMware vSphere environments to any Platform9 Private Cloud Director OR any OpenStack-compliant cloud. It eliminates the complexities of cross-platform VM migration, enabling you to modernize your infrastructure with minimal disruption and a streamlined, visual workflow.

    +

    How vJailbreak works

    vJailbreak’s intuitive interface leverages the OpenStack & VMware SDKs to interact directly with both your VMware vSphere environment and your target OpenStack cloud. The UI guides you through these key steps:

    1. Connection Setup: Easily configure connections to your source VMware vSphere environment and your target OpenStack cloud.
    2. @@ -146,7 +160,7 @@ starlight-tabs:where(.astro-esqgolmp){display:block}.tablist-wrapper:where(.astr
    3. Migration Execution: Initiate and monitor the migration process with real-time progress updates.
    4. Post-Migration Validation: Verify the successful migration and launch of your VMs in OpenStack.
    -

    Key Features

    +

    Key features

    • Intuitive User Interface: Manage the entire migration process through a clear, easy-to-use graphical interface – no command-line expertise required.
    • Seamless vCenter Integration: Easily connect to your VMware vCenter to manage and migrate VMs.
    • @@ -155,7 +169,7 @@ starlight-tabs:where(.astro-esqgolmp){display:block}.tablist-wrapper:where(.astr
    • Driver and Device Installation: Necessary virtual devices and drivers are installed to ensure smooth operation post-migration.
    • Post-Migration Health Checks: Comprehensive health checks are performed to verify the success of the migration and the operational status of the VMs in the new environment.
    -

    Key Benefits

    +

    Key benefits

    • Reduced Migration Time: Automate migration tasks and visualize progress, significantly reducing the time and effort compared to manual methods.
    • Minimized Downtime: vJailbreak’s efficient migration process helps minimize downtime for your critical workloads.
    • @@ -164,4 +178,4 @@ starlight-tabs:where(.astro-esqgolmp){display:block}.tablist-wrapper:where(.astr
    • Non-Disruptive Migration: Perform migrations without impacting the operation of your source VMware environment.
    • Visual Progress Tracking: Monitor the status of your migrations in real-time through the user interface.
    -
    \ No newline at end of file +
    \ No newline at end of file diff --git a/docs/pagefind/fragment/en_167d43d.pf_fragment b/docs/pagefind/fragment/en_167d43d.pf_fragment new file mode 100644 index 0000000..af921d7 Binary files /dev/null and b/docs/pagefind/fragment/en_167d43d.pf_fragment differ diff --git a/docs/pagefind/fragment/en_1690478.pf_fragment b/docs/pagefind/fragment/en_1690478.pf_fragment new file mode 100644 index 0000000..1a4bbdd Binary files /dev/null and b/docs/pagefind/fragment/en_1690478.pf_fragment differ diff --git a/docs/pagefind/fragment/en_1bef7f4.pf_fragment b/docs/pagefind/fragment/en_1bef7f4.pf_fragment new file mode 100644 index 0000000..47c3d73 Binary files /dev/null and b/docs/pagefind/fragment/en_1bef7f4.pf_fragment differ diff --git a/docs/pagefind/fragment/en_3079ddf.pf_fragment b/docs/pagefind/fragment/en_3079ddf.pf_fragment new file mode 100644 index 0000000..3946154 Binary files /dev/null and b/docs/pagefind/fragment/en_3079ddf.pf_fragment differ diff --git a/docs/pagefind/fragment/en_3545654.pf_fragment b/docs/pagefind/fragment/en_3545654.pf_fragment new file mode 100644 index 0000000..8a9f637 Binary files /dev/null and b/docs/pagefind/fragment/en_3545654.pf_fragment differ diff --git a/docs/pagefind/fragment/en_37209ad.pf_fragment b/docs/pagefind/fragment/en_37209ad.pf_fragment new file mode 100644 index 0000000..7378985 Binary files /dev/null and b/docs/pagefind/fragment/en_37209ad.pf_fragment differ diff --git a/docs/pagefind/fragment/en_3d8321f.pf_fragment b/docs/pagefind/fragment/en_3d8321f.pf_fragment new file mode 100644 index 0000000..8395d73 Binary files /dev/null and b/docs/pagefind/fragment/en_3d8321f.pf_fragment differ diff --git a/docs/pagefind/fragment/en_3f4606c.pf_fragment b/docs/pagefind/fragment/en_3f4606c.pf_fragment new file mode 100644 index 0000000..8a3c735 Binary files /dev/null and b/docs/pagefind/fragment/en_3f4606c.pf_fragment differ diff --git a/docs/pagefind/fragment/en_441751c.pf_fragment b/docs/pagefind/fragment/en_441751c.pf_fragment new file mode 100644 index 0000000..2a2e518 Binary files /dev/null and b/docs/pagefind/fragment/en_441751c.pf_fragment differ diff --git a/docs/pagefind/fragment/en_469e924.pf_fragment b/docs/pagefind/fragment/en_469e924.pf_fragment new file mode 100644 index 0000000..c119167 Binary files /dev/null and b/docs/pagefind/fragment/en_469e924.pf_fragment differ diff --git a/docs/pagefind/fragment/en_482e859.pf_fragment b/docs/pagefind/fragment/en_482e859.pf_fragment new file mode 100644 index 0000000..3ebf1f2 Binary files /dev/null and b/docs/pagefind/fragment/en_482e859.pf_fragment differ diff --git a/docs/pagefind/fragment/en_4af968f.pf_fragment b/docs/pagefind/fragment/en_4af968f.pf_fragment new file mode 100644 index 0000000..287a08d Binary files /dev/null and b/docs/pagefind/fragment/en_4af968f.pf_fragment differ diff --git a/docs/pagefind/fragment/en_5351240.pf_fragment b/docs/pagefind/fragment/en_5351240.pf_fragment new file mode 100644 index 0000000..ac1dd9a Binary files /dev/null and b/docs/pagefind/fragment/en_5351240.pf_fragment differ diff --git a/docs/pagefind/fragment/en_654f2fe.pf_fragment b/docs/pagefind/fragment/en_654f2fe.pf_fragment new file mode 100644 index 0000000..a95e319 Binary files /dev/null and b/docs/pagefind/fragment/en_654f2fe.pf_fragment differ diff --git a/docs/pagefind/fragment/en_666ee67.pf_fragment b/docs/pagefind/fragment/en_666ee67.pf_fragment new file mode 100644 index 0000000..75e7634 Binary files /dev/null and b/docs/pagefind/fragment/en_666ee67.pf_fragment differ diff --git a/docs/pagefind/fragment/en_6d72156.pf_fragment b/docs/pagefind/fragment/en_6d72156.pf_fragment new file mode 100644 index 0000000..8ddc04c Binary files /dev/null and b/docs/pagefind/fragment/en_6d72156.pf_fragment differ diff --git a/docs/pagefind/fragment/en_6e15f76.pf_fragment b/docs/pagefind/fragment/en_6e15f76.pf_fragment new file mode 100644 index 0000000..2592a26 Binary files /dev/null and b/docs/pagefind/fragment/en_6e15f76.pf_fragment differ diff --git a/docs/pagefind/fragment/en_70319fd.pf_fragment b/docs/pagefind/fragment/en_70319fd.pf_fragment new file mode 100644 index 0000000..e3b7dca Binary files /dev/null and b/docs/pagefind/fragment/en_70319fd.pf_fragment differ diff --git a/docs/pagefind/fragment/en_7266a81.pf_fragment b/docs/pagefind/fragment/en_7266a81.pf_fragment new file mode 100644 index 0000000..a277203 Binary files /dev/null and b/docs/pagefind/fragment/en_7266a81.pf_fragment differ diff --git a/docs/pagefind/fragment/en_76302ab.pf_fragment b/docs/pagefind/fragment/en_76302ab.pf_fragment new file mode 100644 index 0000000..e917869 Binary files /dev/null and b/docs/pagefind/fragment/en_76302ab.pf_fragment differ diff --git a/docs/pagefind/fragment/en_7765a9d.pf_fragment b/docs/pagefind/fragment/en_7765a9d.pf_fragment new file mode 100644 index 0000000..7b4b43e Binary files /dev/null and b/docs/pagefind/fragment/en_7765a9d.pf_fragment differ diff --git a/docs/pagefind/fragment/en_7ee78c5.pf_fragment b/docs/pagefind/fragment/en_7ee78c5.pf_fragment new file mode 100644 index 0000000..46bc775 Binary files /dev/null and b/docs/pagefind/fragment/en_7ee78c5.pf_fragment differ diff --git a/docs/pagefind/fragment/en_8052b21.pf_fragment b/docs/pagefind/fragment/en_8052b21.pf_fragment new file mode 100644 index 0000000..d4d55e7 Binary files /dev/null and b/docs/pagefind/fragment/en_8052b21.pf_fragment differ diff --git a/docs/pagefind/fragment/en_905bcdd.pf_fragment b/docs/pagefind/fragment/en_905bcdd.pf_fragment new file mode 100644 index 0000000..eefe1df Binary files /dev/null and b/docs/pagefind/fragment/en_905bcdd.pf_fragment differ diff --git a/docs/pagefind/fragment/en_929c8cc.pf_fragment b/docs/pagefind/fragment/en_929c8cc.pf_fragment new file mode 100644 index 0000000..2f441ee Binary files /dev/null and b/docs/pagefind/fragment/en_929c8cc.pf_fragment differ diff --git a/docs/pagefind/fragment/en_97d313f.pf_fragment b/docs/pagefind/fragment/en_97d313f.pf_fragment new file mode 100644 index 0000000..90daa1f Binary files /dev/null and b/docs/pagefind/fragment/en_97d313f.pf_fragment differ diff --git a/docs/pagefind/fragment/en_98cd57b.pf_fragment b/docs/pagefind/fragment/en_98cd57b.pf_fragment new file mode 100644 index 0000000..f0b2002 Binary files /dev/null and b/docs/pagefind/fragment/en_98cd57b.pf_fragment differ diff --git a/docs/pagefind/fragment/en_98d9869.pf_fragment b/docs/pagefind/fragment/en_98d9869.pf_fragment new file mode 100644 index 0000000..45971e0 Binary files /dev/null and b/docs/pagefind/fragment/en_98d9869.pf_fragment differ diff --git a/docs/pagefind/fragment/en_9eae6c3.pf_fragment b/docs/pagefind/fragment/en_9eae6c3.pf_fragment new file mode 100644 index 0000000..46ab570 Binary files /dev/null and b/docs/pagefind/fragment/en_9eae6c3.pf_fragment differ diff --git a/docs/pagefind/fragment/en_a2d3460.pf_fragment b/docs/pagefind/fragment/en_a2d3460.pf_fragment new file mode 100644 index 0000000..e78174e Binary files /dev/null and b/docs/pagefind/fragment/en_a2d3460.pf_fragment differ diff --git a/docs/pagefind/fragment/en_a3b267c.pf_fragment b/docs/pagefind/fragment/en_a3b267c.pf_fragment new file mode 100644 index 0000000..15cb32d Binary files /dev/null and b/docs/pagefind/fragment/en_a3b267c.pf_fragment differ diff --git a/docs/pagefind/fragment/en_a45f1dd.pf_fragment b/docs/pagefind/fragment/en_a45f1dd.pf_fragment new file mode 100644 index 0000000..a15d497 Binary files /dev/null and b/docs/pagefind/fragment/en_a45f1dd.pf_fragment differ diff --git a/docs/pagefind/fragment/en_a84134c.pf_fragment b/docs/pagefind/fragment/en_a84134c.pf_fragment new file mode 100644 index 0000000..4e16c90 Binary files /dev/null and b/docs/pagefind/fragment/en_a84134c.pf_fragment differ diff --git a/docs/pagefind/fragment/en_a846589.pf_fragment b/docs/pagefind/fragment/en_a846589.pf_fragment new file mode 100644 index 0000000..e8e1170 Binary files /dev/null and b/docs/pagefind/fragment/en_a846589.pf_fragment differ diff --git a/docs/pagefind/fragment/en_ac79a4a.pf_fragment b/docs/pagefind/fragment/en_ac79a4a.pf_fragment new file mode 100644 index 0000000..0f266d0 Binary files /dev/null and b/docs/pagefind/fragment/en_ac79a4a.pf_fragment differ diff --git a/docs/pagefind/fragment/en_aed33d9.pf_fragment b/docs/pagefind/fragment/en_aed33d9.pf_fragment new file mode 100644 index 0000000..b352811 Binary files /dev/null and b/docs/pagefind/fragment/en_aed33d9.pf_fragment differ diff --git a/docs/pagefind/fragment/en_b06cf3f.pf_fragment b/docs/pagefind/fragment/en_b06cf3f.pf_fragment new file mode 100644 index 0000000..6a28ffb Binary files /dev/null and b/docs/pagefind/fragment/en_b06cf3f.pf_fragment differ diff --git a/docs/pagefind/fragment/en_b574913.pf_fragment b/docs/pagefind/fragment/en_b574913.pf_fragment new file mode 100644 index 0000000..14ba426 Binary files /dev/null and b/docs/pagefind/fragment/en_b574913.pf_fragment differ diff --git a/docs/pagefind/fragment/en_b7f5944.pf_fragment b/docs/pagefind/fragment/en_b7f5944.pf_fragment new file mode 100644 index 0000000..5916ab7 Binary files /dev/null and b/docs/pagefind/fragment/en_b7f5944.pf_fragment differ diff --git a/docs/pagefind/fragment/en_b8a66f9.pf_fragment b/docs/pagefind/fragment/en_b8a66f9.pf_fragment new file mode 100644 index 0000000..b0cd99a Binary files /dev/null and b/docs/pagefind/fragment/en_b8a66f9.pf_fragment differ diff --git a/docs/pagefind/fragment/en_bca0ce1.pf_fragment b/docs/pagefind/fragment/en_bca0ce1.pf_fragment new file mode 100644 index 0000000..53a78f8 Binary files /dev/null and b/docs/pagefind/fragment/en_bca0ce1.pf_fragment differ diff --git a/docs/pagefind/fragment/en_cadf8b3.pf_fragment b/docs/pagefind/fragment/en_cadf8b3.pf_fragment new file mode 100644 index 0000000..e9be9b3 Binary files /dev/null and b/docs/pagefind/fragment/en_cadf8b3.pf_fragment differ diff --git a/docs/pagefind/fragment/en_d01cb3b.pf_fragment b/docs/pagefind/fragment/en_d01cb3b.pf_fragment new file mode 100644 index 0000000..3f855a0 Binary files /dev/null and b/docs/pagefind/fragment/en_d01cb3b.pf_fragment differ diff --git a/docs/pagefind/fragment/en_d34554f.pf_fragment b/docs/pagefind/fragment/en_d34554f.pf_fragment new file mode 100644 index 0000000..cade23f Binary files /dev/null and b/docs/pagefind/fragment/en_d34554f.pf_fragment differ diff --git a/docs/pagefind/fragment/en_d3895c9.pf_fragment b/docs/pagefind/fragment/en_d3895c9.pf_fragment new file mode 100644 index 0000000..83ad1b5 Binary files /dev/null and b/docs/pagefind/fragment/en_d3895c9.pf_fragment differ diff --git a/docs/pagefind/fragment/en_d698147.pf_fragment b/docs/pagefind/fragment/en_d698147.pf_fragment new file mode 100644 index 0000000..46082dc Binary files /dev/null and b/docs/pagefind/fragment/en_d698147.pf_fragment differ diff --git a/docs/pagefind/fragment/en_d82d838.pf_fragment b/docs/pagefind/fragment/en_d82d838.pf_fragment new file mode 100644 index 0000000..2abbe0b Binary files /dev/null and b/docs/pagefind/fragment/en_d82d838.pf_fragment differ diff --git a/docs/pagefind/fragment/en_d8aa3ed.pf_fragment b/docs/pagefind/fragment/en_d8aa3ed.pf_fragment new file mode 100644 index 0000000..7917e74 Binary files /dev/null and b/docs/pagefind/fragment/en_d8aa3ed.pf_fragment differ diff --git a/docs/pagefind/fragment/en_e39a54c.pf_fragment b/docs/pagefind/fragment/en_e39a54c.pf_fragment new file mode 100644 index 0000000..5f9170f Binary files /dev/null and b/docs/pagefind/fragment/en_e39a54c.pf_fragment differ diff --git a/docs/pagefind/fragment/en_e4c240d.pf_fragment b/docs/pagefind/fragment/en_e4c240d.pf_fragment new file mode 100644 index 0000000..a66b036 Binary files /dev/null and b/docs/pagefind/fragment/en_e4c240d.pf_fragment differ diff --git a/docs/pagefind/fragment/en_e8125df.pf_fragment b/docs/pagefind/fragment/en_e8125df.pf_fragment new file mode 100644 index 0000000..98cdaa7 Binary files /dev/null and b/docs/pagefind/fragment/en_e8125df.pf_fragment differ diff --git a/docs/pagefind/fragment/en_e88e4cc.pf_fragment b/docs/pagefind/fragment/en_e88e4cc.pf_fragment new file mode 100644 index 0000000..ba73952 Binary files /dev/null and b/docs/pagefind/fragment/en_e88e4cc.pf_fragment differ diff --git a/docs/pagefind/fragment/en_eeaae4f.pf_fragment b/docs/pagefind/fragment/en_eeaae4f.pf_fragment new file mode 100644 index 0000000..db3f91d Binary files /dev/null and b/docs/pagefind/fragment/en_eeaae4f.pf_fragment differ diff --git a/docs/pagefind/fragment/en_f43bbbe.pf_fragment b/docs/pagefind/fragment/en_f43bbbe.pf_fragment new file mode 100644 index 0000000..1a393e5 Binary files /dev/null and b/docs/pagefind/fragment/en_f43bbbe.pf_fragment differ diff --git a/docs/pagefind/fragment/en_f7126ea.pf_fragment b/docs/pagefind/fragment/en_f7126ea.pf_fragment new file mode 100644 index 0000000..f1370a2 Binary files /dev/null and b/docs/pagefind/fragment/en_f7126ea.pf_fragment differ diff --git a/docs/pagefind/fragment/en_f961823.pf_fragment b/docs/pagefind/fragment/en_f961823.pf_fragment new file mode 100644 index 0000000..b7b294f Binary files /dev/null and b/docs/pagefind/fragment/en_f961823.pf_fragment differ diff --git a/docs/pagefind/fragment/en_fa5620b.pf_fragment b/docs/pagefind/fragment/en_fa5620b.pf_fragment new file mode 100644 index 0000000..3f35a7a Binary files /dev/null and b/docs/pagefind/fragment/en_fa5620b.pf_fragment differ diff --git a/docs/pagefind/index/en_107ec7b.pf_index b/docs/pagefind/index/en_107ec7b.pf_index new file mode 100644 index 0000000..23f163b Binary files /dev/null and b/docs/pagefind/index/en_107ec7b.pf_index differ diff --git a/docs/pagefind/index/en_1b30843.pf_index b/docs/pagefind/index/en_1b30843.pf_index new file mode 100644 index 0000000..5c0f68b Binary files /dev/null and b/docs/pagefind/index/en_1b30843.pf_index differ diff --git a/docs/pagefind/index/en_3c242fe.pf_index b/docs/pagefind/index/en_3c242fe.pf_index new file mode 100644 index 0000000..187e128 Binary files /dev/null and b/docs/pagefind/index/en_3c242fe.pf_index differ diff --git a/docs/pagefind/index/en_54a25cb.pf_index b/docs/pagefind/index/en_54a25cb.pf_index new file mode 100644 index 0000000..2450982 Binary files /dev/null and b/docs/pagefind/index/en_54a25cb.pf_index differ diff --git a/docs/pagefind/index/en_553b5fb.pf_index b/docs/pagefind/index/en_553b5fb.pf_index new file mode 100644 index 0000000..4609825 Binary files /dev/null and b/docs/pagefind/index/en_553b5fb.pf_index differ diff --git a/docs/pagefind/index/en_d187c91.pf_index b/docs/pagefind/index/en_d187c91.pf_index new file mode 100644 index 0000000..7c2a238 Binary files /dev/null and b/docs/pagefind/index/en_d187c91.pf_index differ diff --git a/docs/pagefind/pagefind-entry.json b/docs/pagefind/pagefind-entry.json new file mode 100644 index 0000000..d7fe97d --- /dev/null +++ b/docs/pagefind/pagefind-entry.json @@ -0,0 +1 @@ +{"version":"1.3.0","languages":{"en":{"hash":"en_c1a75a9f75","wasm":"en","page_count":57}}} \ No newline at end of file diff --git a/docs/pagefind/pagefind-highlight.js b/docs/pagefind/pagefind-highlight.js new file mode 100644 index 0000000..c823fbf --- /dev/null +++ b/docs/pagefind/pagefind-highlight.js @@ -0,0 +1,1069 @@ +var __create = Object.create; +var __defProp = Object.defineProperty; +var __getOwnPropDesc = Object.getOwnPropertyDescriptor; +var __getOwnPropNames = Object.getOwnPropertyNames; +var __getProtoOf = Object.getPrototypeOf; +var __hasOwnProp = Object.prototype.hasOwnProperty; +var __commonJS = (cb, mod) => function __require() { + return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports; +}; +var __copyProps = (to, from, except, desc) => { + if (from && typeof from === "object" || typeof from === "function") { + for (let key of __getOwnPropNames(from)) + if (!__hasOwnProp.call(to, key) && key !== except) + __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); + } + return to; +}; +var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( + // If the importer is in node compatibility mode or this is not an ESM + // file that has been converted to a CommonJS file using a Babel- + // compatible transform (i.e. "__esModule" has not been set), then set + // "default" to the CommonJS "module.exports" for node compatibility. + isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, + mod +)); + +// node_modules/mark.js/dist/mark.js +var require_mark = __commonJS({ + "node_modules/mark.js/dist/mark.js"(exports, module) { + (function(global, factory) { + typeof exports === "object" && typeof module !== "undefined" ? module.exports = factory() : typeof define === "function" && define.amd ? define(factory) : global.Mark = factory(); + })(exports, function() { + "use strict"; + var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function(obj) { + return typeof obj; + } : function(obj) { + return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; + }; + var classCallCheck = function(instance, Constructor) { + if (!(instance instanceof Constructor)) { + throw new TypeError("Cannot call a class as a function"); + } + }; + var createClass = function() { + function defineProperties(target, props) { + for (var i = 0; i < props.length; i++) { + var descriptor = props[i]; + descriptor.enumerable = descriptor.enumerable || false; + descriptor.configurable = true; + if ("value" in descriptor) + descriptor.writable = true; + Object.defineProperty(target, descriptor.key, descriptor); + } + } + return function(Constructor, protoProps, staticProps) { + if (protoProps) + defineProperties(Constructor.prototype, protoProps); + if (staticProps) + defineProperties(Constructor, staticProps); + return Constructor; + }; + }(); + var _extends = Object.assign || function(target) { + for (var i = 1; i < arguments.length; i++) { + var source = arguments[i]; + for (var key in source) { + if (Object.prototype.hasOwnProperty.call(source, key)) { + target[key] = source[key]; + } + } + } + return target; + }; + var DOMIterator = function() { + function DOMIterator2(ctx) { + var iframes = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : true; + var exclude = arguments.length > 2 && arguments[2] !== void 0 ? arguments[2] : []; + var iframesTimeout = arguments.length > 3 && arguments[3] !== void 0 ? arguments[3] : 5e3; + classCallCheck(this, DOMIterator2); + this.ctx = ctx; + this.iframes = iframes; + this.exclude = exclude; + this.iframesTimeout = iframesTimeout; + } + createClass(DOMIterator2, [{ + key: "getContexts", + value: function getContexts() { + var ctx = void 0, filteredCtx = []; + if (typeof this.ctx === "undefined" || !this.ctx) { + ctx = []; + } else if (NodeList.prototype.isPrototypeOf(this.ctx)) { + ctx = Array.prototype.slice.call(this.ctx); + } else if (Array.isArray(this.ctx)) { + ctx = this.ctx; + } else if (typeof this.ctx === "string") { + ctx = Array.prototype.slice.call(document.querySelectorAll(this.ctx)); + } else { + ctx = [this.ctx]; + } + ctx.forEach(function(ctx2) { + var isDescendant = filteredCtx.filter(function(contexts) { + return contexts.contains(ctx2); + }).length > 0; + if (filteredCtx.indexOf(ctx2) === -1 && !isDescendant) { + filteredCtx.push(ctx2); + } + }); + return filteredCtx; + } + }, { + key: "getIframeContents", + value: function getIframeContents(ifr, successFn) { + var errorFn = arguments.length > 2 && arguments[2] !== void 0 ? arguments[2] : function() { + }; + var doc = void 0; + try { + var ifrWin = ifr.contentWindow; + doc = ifrWin.document; + if (!ifrWin || !doc) { + throw new Error("iframe inaccessible"); + } + } catch (e) { + errorFn(); + } + if (doc) { + successFn(doc); + } + } + }, { + key: "isIframeBlank", + value: function isIframeBlank(ifr) { + var bl = "about:blank", src = ifr.getAttribute("src").trim(), href = ifr.contentWindow.location.href; + return href === bl && src !== bl && src; + } + }, { + key: "observeIframeLoad", + value: function observeIframeLoad(ifr, successFn, errorFn) { + var _this = this; + var called = false, tout = null; + var listener = function listener2() { + if (called) { + return; + } + called = true; + clearTimeout(tout); + try { + if (!_this.isIframeBlank(ifr)) { + ifr.removeEventListener("load", listener2); + _this.getIframeContents(ifr, successFn, errorFn); + } + } catch (e) { + errorFn(); + } + }; + ifr.addEventListener("load", listener); + tout = setTimeout(listener, this.iframesTimeout); + } + }, { + key: "onIframeReady", + value: function onIframeReady(ifr, successFn, errorFn) { + try { + if (ifr.contentWindow.document.readyState === "complete") { + if (this.isIframeBlank(ifr)) { + this.observeIframeLoad(ifr, successFn, errorFn); + } else { + this.getIframeContents(ifr, successFn, errorFn); + } + } else { + this.observeIframeLoad(ifr, successFn, errorFn); + } + } catch (e) { + errorFn(); + } + } + }, { + key: "waitForIframes", + value: function waitForIframes(ctx, done) { + var _this2 = this; + var eachCalled = 0; + this.forEachIframe(ctx, function() { + return true; + }, function(ifr) { + eachCalled++; + _this2.waitForIframes(ifr.querySelector("html"), function() { + if (!--eachCalled) { + done(); + } + }); + }, function(handled) { + if (!handled) { + done(); + } + }); + } + }, { + key: "forEachIframe", + value: function forEachIframe(ctx, filter, each) { + var _this3 = this; + var end = arguments.length > 3 && arguments[3] !== void 0 ? arguments[3] : function() { + }; + var ifr = ctx.querySelectorAll("iframe"), open = ifr.length, handled = 0; + ifr = Array.prototype.slice.call(ifr); + var checkEnd = function checkEnd2() { + if (--open <= 0) { + end(handled); + } + }; + if (!open) { + checkEnd(); + } + ifr.forEach(function(ifr2) { + if (DOMIterator2.matches(ifr2, _this3.exclude)) { + checkEnd(); + } else { + _this3.onIframeReady(ifr2, function(con) { + if (filter(ifr2)) { + handled++; + each(con); + } + checkEnd(); + }, checkEnd); + } + }); + } + }, { + key: "createIterator", + value: function createIterator(ctx, whatToShow, filter) { + return document.createNodeIterator(ctx, whatToShow, filter, false); + } + }, { + key: "createInstanceOnIframe", + value: function createInstanceOnIframe(contents) { + return new DOMIterator2(contents.querySelector("html"), this.iframes); + } + }, { + key: "compareNodeIframe", + value: function compareNodeIframe(node, prevNode, ifr) { + var compCurr = node.compareDocumentPosition(ifr), prev = Node.DOCUMENT_POSITION_PRECEDING; + if (compCurr & prev) { + if (prevNode !== null) { + var compPrev = prevNode.compareDocumentPosition(ifr), after = Node.DOCUMENT_POSITION_FOLLOWING; + if (compPrev & after) { + return true; + } + } else { + return true; + } + } + return false; + } + }, { + key: "getIteratorNode", + value: function getIteratorNode(itr) { + var prevNode = itr.previousNode(); + var node = void 0; + if (prevNode === null) { + node = itr.nextNode(); + } else { + node = itr.nextNode() && itr.nextNode(); + } + return { + prevNode, + node + }; + } + }, { + key: "checkIframeFilter", + value: function checkIframeFilter(node, prevNode, currIfr, ifr) { + var key = false, handled = false; + ifr.forEach(function(ifrDict, i) { + if (ifrDict.val === currIfr) { + key = i; + handled = ifrDict.handled; + } + }); + if (this.compareNodeIframe(node, prevNode, currIfr)) { + if (key === false && !handled) { + ifr.push({ + val: currIfr, + handled: true + }); + } else if (key !== false && !handled) { + ifr[key].handled = true; + } + return true; + } + if (key === false) { + ifr.push({ + val: currIfr, + handled: false + }); + } + return false; + } + }, { + key: "handleOpenIframes", + value: function handleOpenIframes(ifr, whatToShow, eCb, fCb) { + var _this4 = this; + ifr.forEach(function(ifrDict) { + if (!ifrDict.handled) { + _this4.getIframeContents(ifrDict.val, function(con) { + _this4.createInstanceOnIframe(con).forEachNode(whatToShow, eCb, fCb); + }); + } + }); + } + }, { + key: "iterateThroughNodes", + value: function iterateThroughNodes(whatToShow, ctx, eachCb, filterCb, doneCb) { + var _this5 = this; + var itr = this.createIterator(ctx, whatToShow, filterCb); + var ifr = [], elements = [], node = void 0, prevNode = void 0, retrieveNodes = function retrieveNodes2() { + var _getIteratorNode = _this5.getIteratorNode(itr); + prevNode = _getIteratorNode.prevNode; + node = _getIteratorNode.node; + return node; + }; + while (retrieveNodes()) { + if (this.iframes) { + this.forEachIframe(ctx, function(currIfr) { + return _this5.checkIframeFilter(node, prevNode, currIfr, ifr); + }, function(con) { + _this5.createInstanceOnIframe(con).forEachNode(whatToShow, function(ifrNode) { + return elements.push(ifrNode); + }, filterCb); + }); + } + elements.push(node); + } + elements.forEach(function(node2) { + eachCb(node2); + }); + if (this.iframes) { + this.handleOpenIframes(ifr, whatToShow, eachCb, filterCb); + } + doneCb(); + } + }, { + key: "forEachNode", + value: function forEachNode(whatToShow, each, filter) { + var _this6 = this; + var done = arguments.length > 3 && arguments[3] !== void 0 ? arguments[3] : function() { + }; + var contexts = this.getContexts(); + var open = contexts.length; + if (!open) { + done(); + } + contexts.forEach(function(ctx) { + var ready = function ready2() { + _this6.iterateThroughNodes(whatToShow, ctx, each, filter, function() { + if (--open <= 0) { + done(); + } + }); + }; + if (_this6.iframes) { + _this6.waitForIframes(ctx, ready); + } else { + ready(); + } + }); + } + }], [{ + key: "matches", + value: function matches(element, selector) { + var selectors = typeof selector === "string" ? [selector] : selector, fn = element.matches || element.matchesSelector || element.msMatchesSelector || element.mozMatchesSelector || element.oMatchesSelector || element.webkitMatchesSelector; + if (fn) { + var match = false; + selectors.every(function(sel) { + if (fn.call(element, sel)) { + match = true; + return false; + } + return true; + }); + return match; + } else { + return false; + } + } + }]); + return DOMIterator2; + }(); + var Mark$1 = function() { + function Mark3(ctx) { + classCallCheck(this, Mark3); + this.ctx = ctx; + this.ie = false; + var ua = window.navigator.userAgent; + if (ua.indexOf("MSIE") > -1 || ua.indexOf("Trident") > -1) { + this.ie = true; + } + } + createClass(Mark3, [{ + key: "log", + value: function log(msg) { + var level = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : "debug"; + var log2 = this.opt.log; + if (!this.opt.debug) { + return; + } + if ((typeof log2 === "undefined" ? "undefined" : _typeof(log2)) === "object" && typeof log2[level] === "function") { + log2[level]("mark.js: " + msg); + } + } + }, { + key: "escapeStr", + value: function escapeStr(str) { + return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"); + } + }, { + key: "createRegExp", + value: function createRegExp(str) { + if (this.opt.wildcards !== "disabled") { + str = this.setupWildcardsRegExp(str); + } + str = this.escapeStr(str); + if (Object.keys(this.opt.synonyms).length) { + str = this.createSynonymsRegExp(str); + } + if (this.opt.ignoreJoiners || this.opt.ignorePunctuation.length) { + str = this.setupIgnoreJoinersRegExp(str); + } + if (this.opt.diacritics) { + str = this.createDiacriticsRegExp(str); + } + str = this.createMergedBlanksRegExp(str); + if (this.opt.ignoreJoiners || this.opt.ignorePunctuation.length) { + str = this.createJoinersRegExp(str); + } + if (this.opt.wildcards !== "disabled") { + str = this.createWildcardsRegExp(str); + } + str = this.createAccuracyRegExp(str); + return str; + } + }, { + key: "createSynonymsRegExp", + value: function createSynonymsRegExp(str) { + var syn = this.opt.synonyms, sens = this.opt.caseSensitive ? "" : "i", joinerPlaceholder = this.opt.ignoreJoiners || this.opt.ignorePunctuation.length ? "\0" : ""; + for (var index in syn) { + if (syn.hasOwnProperty(index)) { + var value = syn[index], k1 = this.opt.wildcards !== "disabled" ? this.setupWildcardsRegExp(index) : this.escapeStr(index), k2 = this.opt.wildcards !== "disabled" ? this.setupWildcardsRegExp(value) : this.escapeStr(value); + if (k1 !== "" && k2 !== "") { + str = str.replace(new RegExp("(" + this.escapeStr(k1) + "|" + this.escapeStr(k2) + ")", "gm" + sens), joinerPlaceholder + ("(" + this.processSynomyms(k1) + "|") + (this.processSynomyms(k2) + ")") + joinerPlaceholder); + } + } + } + return str; + } + }, { + key: "processSynomyms", + value: function processSynomyms(str) { + if (this.opt.ignoreJoiners || this.opt.ignorePunctuation.length) { + str = this.setupIgnoreJoinersRegExp(str); + } + return str; + } + }, { + key: "setupWildcardsRegExp", + value: function setupWildcardsRegExp(str) { + str = str.replace(/(?:\\)*\?/g, function(val) { + return val.charAt(0) === "\\" ? "?" : ""; + }); + return str.replace(/(?:\\)*\*/g, function(val) { + return val.charAt(0) === "\\" ? "*" : ""; + }); + } + }, { + key: "createWildcardsRegExp", + value: function createWildcardsRegExp(str) { + var spaces = this.opt.wildcards === "withSpaces"; + return str.replace(/\u0001/g, spaces ? "[\\S\\s]?" : "\\S?").replace(/\u0002/g, spaces ? "[\\S\\s]*?" : "\\S*"); + } + }, { + key: "setupIgnoreJoinersRegExp", + value: function setupIgnoreJoinersRegExp(str) { + return str.replace(/[^(|)\\]/g, function(val, indx, original) { + var nextChar = original.charAt(indx + 1); + if (/[(|)\\]/.test(nextChar) || nextChar === "") { + return val; + } else { + return val + "\0"; + } + }); + } + }, { + key: "createJoinersRegExp", + value: function createJoinersRegExp(str) { + var joiner = []; + var ignorePunctuation = this.opt.ignorePunctuation; + if (Array.isArray(ignorePunctuation) && ignorePunctuation.length) { + joiner.push(this.escapeStr(ignorePunctuation.join(""))); + } + if (this.opt.ignoreJoiners) { + joiner.push("\\u00ad\\u200b\\u200c\\u200d"); + } + return joiner.length ? str.split(/\u0000+/).join("[" + joiner.join("") + "]*") : str; + } + }, { + key: "createDiacriticsRegExp", + value: function createDiacriticsRegExp(str) { + var sens = this.opt.caseSensitive ? "" : "i", dct = this.opt.caseSensitive ? ["a\xE0\xE1\u1EA3\xE3\u1EA1\u0103\u1EB1\u1EAF\u1EB3\u1EB5\u1EB7\xE2\u1EA7\u1EA5\u1EA9\u1EAB\u1EAD\xE4\xE5\u0101\u0105", "A\xC0\xC1\u1EA2\xC3\u1EA0\u0102\u1EB0\u1EAE\u1EB2\u1EB4\u1EB6\xC2\u1EA6\u1EA4\u1EA8\u1EAA\u1EAC\xC4\xC5\u0100\u0104", "c\xE7\u0107\u010D", "C\xC7\u0106\u010C", "d\u0111\u010F", "D\u0110\u010E", "e\xE8\xE9\u1EBB\u1EBD\u1EB9\xEA\u1EC1\u1EBF\u1EC3\u1EC5\u1EC7\xEB\u011B\u0113\u0119", "E\xC8\xC9\u1EBA\u1EBC\u1EB8\xCA\u1EC0\u1EBE\u1EC2\u1EC4\u1EC6\xCB\u011A\u0112\u0118", "i\xEC\xED\u1EC9\u0129\u1ECB\xEE\xEF\u012B", "I\xCC\xCD\u1EC8\u0128\u1ECA\xCE\xCF\u012A", "l\u0142", "L\u0141", "n\xF1\u0148\u0144", "N\xD1\u0147\u0143", "o\xF2\xF3\u1ECF\xF5\u1ECD\xF4\u1ED3\u1ED1\u1ED5\u1ED7\u1ED9\u01A1\u1EDF\u1EE1\u1EDB\u1EDD\u1EE3\xF6\xF8\u014D", "O\xD2\xD3\u1ECE\xD5\u1ECC\xD4\u1ED2\u1ED0\u1ED4\u1ED6\u1ED8\u01A0\u1EDE\u1EE0\u1EDA\u1EDC\u1EE2\xD6\xD8\u014C", "r\u0159", "R\u0158", "s\u0161\u015B\u0219\u015F", "S\u0160\u015A\u0218\u015E", "t\u0165\u021B\u0163", "T\u0164\u021A\u0162", "u\xF9\xFA\u1EE7\u0169\u1EE5\u01B0\u1EEB\u1EE9\u1EED\u1EEF\u1EF1\xFB\xFC\u016F\u016B", "U\xD9\xDA\u1EE6\u0168\u1EE4\u01AF\u1EEA\u1EE8\u1EEC\u1EEE\u1EF0\xDB\xDC\u016E\u016A", "y\xFD\u1EF3\u1EF7\u1EF9\u1EF5\xFF", "Y\xDD\u1EF2\u1EF6\u1EF8\u1EF4\u0178", "z\u017E\u017C\u017A", "Z\u017D\u017B\u0179"] : ["a\xE0\xE1\u1EA3\xE3\u1EA1\u0103\u1EB1\u1EAF\u1EB3\u1EB5\u1EB7\xE2\u1EA7\u1EA5\u1EA9\u1EAB\u1EAD\xE4\xE5\u0101\u0105A\xC0\xC1\u1EA2\xC3\u1EA0\u0102\u1EB0\u1EAE\u1EB2\u1EB4\u1EB6\xC2\u1EA6\u1EA4\u1EA8\u1EAA\u1EAC\xC4\xC5\u0100\u0104", "c\xE7\u0107\u010DC\xC7\u0106\u010C", "d\u0111\u010FD\u0110\u010E", "e\xE8\xE9\u1EBB\u1EBD\u1EB9\xEA\u1EC1\u1EBF\u1EC3\u1EC5\u1EC7\xEB\u011B\u0113\u0119E\xC8\xC9\u1EBA\u1EBC\u1EB8\xCA\u1EC0\u1EBE\u1EC2\u1EC4\u1EC6\xCB\u011A\u0112\u0118", "i\xEC\xED\u1EC9\u0129\u1ECB\xEE\xEF\u012BI\xCC\xCD\u1EC8\u0128\u1ECA\xCE\xCF\u012A", "l\u0142L\u0141", "n\xF1\u0148\u0144N\xD1\u0147\u0143", "o\xF2\xF3\u1ECF\xF5\u1ECD\xF4\u1ED3\u1ED1\u1ED5\u1ED7\u1ED9\u01A1\u1EDF\u1EE1\u1EDB\u1EDD\u1EE3\xF6\xF8\u014DO\xD2\xD3\u1ECE\xD5\u1ECC\xD4\u1ED2\u1ED0\u1ED4\u1ED6\u1ED8\u01A0\u1EDE\u1EE0\u1EDA\u1EDC\u1EE2\xD6\xD8\u014C", "r\u0159R\u0158", "s\u0161\u015B\u0219\u015FS\u0160\u015A\u0218\u015E", "t\u0165\u021B\u0163T\u0164\u021A\u0162", "u\xF9\xFA\u1EE7\u0169\u1EE5\u01B0\u1EEB\u1EE9\u1EED\u1EEF\u1EF1\xFB\xFC\u016F\u016BU\xD9\xDA\u1EE6\u0168\u1EE4\u01AF\u1EEA\u1EE8\u1EEC\u1EEE\u1EF0\xDB\xDC\u016E\u016A", "y\xFD\u1EF3\u1EF7\u1EF9\u1EF5\xFFY\xDD\u1EF2\u1EF6\u1EF8\u1EF4\u0178", "z\u017E\u017C\u017AZ\u017D\u017B\u0179"]; + var handled = []; + str.split("").forEach(function(ch) { + dct.every(function(dct2) { + if (dct2.indexOf(ch) !== -1) { + if (handled.indexOf(dct2) > -1) { + return false; + } + str = str.replace(new RegExp("[" + dct2 + "]", "gm" + sens), "[" + dct2 + "]"); + handled.push(dct2); + } + return true; + }); + }); + return str; + } + }, { + key: "createMergedBlanksRegExp", + value: function createMergedBlanksRegExp(str) { + return str.replace(/[\s]+/gmi, "[\\s]+"); + } + }, { + key: "createAccuracyRegExp", + value: function createAccuracyRegExp(str) { + var _this = this; + var chars = "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~\xA1\xBF"; + var acc = this.opt.accuracy, val = typeof acc === "string" ? acc : acc.value, ls = typeof acc === "string" ? [] : acc.limiters, lsJoin = ""; + ls.forEach(function(limiter) { + lsJoin += "|" + _this.escapeStr(limiter); + }); + switch (val) { + case "partially": + default: + return "()(" + str + ")"; + case "complementary": + lsJoin = "\\s" + (lsJoin ? lsJoin : this.escapeStr(chars)); + return "()([^" + lsJoin + "]*" + str + "[^" + lsJoin + "]*)"; + case "exactly": + return "(^|\\s" + lsJoin + ")(" + str + ")(?=$|\\s" + lsJoin + ")"; + } + } + }, { + key: "getSeparatedKeywords", + value: function getSeparatedKeywords(sv) { + var _this2 = this; + var stack = []; + sv.forEach(function(kw) { + if (!_this2.opt.separateWordSearch) { + if (kw.trim() && stack.indexOf(kw) === -1) { + stack.push(kw); + } + } else { + kw.split(" ").forEach(function(kwSplitted) { + if (kwSplitted.trim() && stack.indexOf(kwSplitted) === -1) { + stack.push(kwSplitted); + } + }); + } + }); + return { + "keywords": stack.sort(function(a, b) { + return b.length - a.length; + }), + "length": stack.length + }; + } + }, { + key: "isNumeric", + value: function isNumeric(value) { + return Number(parseFloat(value)) == value; + } + }, { + key: "checkRanges", + value: function checkRanges(array) { + var _this3 = this; + if (!Array.isArray(array) || Object.prototype.toString.call(array[0]) !== "[object Object]") { + this.log("markRanges() will only accept an array of objects"); + this.opt.noMatch(array); + return []; + } + var stack = []; + var last = 0; + array.sort(function(a, b) { + return a.start - b.start; + }).forEach(function(item) { + var _callNoMatchOnInvalid = _this3.callNoMatchOnInvalidRanges(item, last), start = _callNoMatchOnInvalid.start, end = _callNoMatchOnInvalid.end, valid = _callNoMatchOnInvalid.valid; + if (valid) { + item.start = start; + item.length = end - start; + stack.push(item); + last = end; + } + }); + return stack; + } + }, { + key: "callNoMatchOnInvalidRanges", + value: function callNoMatchOnInvalidRanges(range, last) { + var start = void 0, end = void 0, valid = false; + if (range && typeof range.start !== "undefined") { + start = parseInt(range.start, 10); + end = start + parseInt(range.length, 10); + if (this.isNumeric(range.start) && this.isNumeric(range.length) && end - last > 0 && end - start > 0) { + valid = true; + } else { + this.log("Ignoring invalid or overlapping range: " + ("" + JSON.stringify(range))); + this.opt.noMatch(range); + } + } else { + this.log("Ignoring invalid range: " + JSON.stringify(range)); + this.opt.noMatch(range); + } + return { + start, + end, + valid + }; + } + }, { + key: "checkWhitespaceRanges", + value: function checkWhitespaceRanges(range, originalLength, string) { + var end = void 0, valid = true, max = string.length, offset = originalLength - max, start = parseInt(range.start, 10) - offset; + start = start > max ? max : start; + end = start + parseInt(range.length, 10); + if (end > max) { + end = max; + this.log("End range automatically set to the max value of " + max); + } + if (start < 0 || end - start < 0 || start > max || end > max) { + valid = false; + this.log("Invalid range: " + JSON.stringify(range)); + this.opt.noMatch(range); + } else if (string.substring(start, end).replace(/\s+/g, "") === "") { + valid = false; + this.log("Skipping whitespace only range: " + JSON.stringify(range)); + this.opt.noMatch(range); + } + return { + start, + end, + valid + }; + } + }, { + key: "getTextNodes", + value: function getTextNodes(cb) { + var _this4 = this; + var val = "", nodes = []; + this.iterator.forEachNode(NodeFilter.SHOW_TEXT, function(node) { + nodes.push({ + start: val.length, + end: (val += node.textContent).length, + node + }); + }, function(node) { + if (_this4.matchesExclude(node.parentNode)) { + return NodeFilter.FILTER_REJECT; + } else { + return NodeFilter.FILTER_ACCEPT; + } + }, function() { + cb({ + value: val, + nodes + }); + }); + } + }, { + key: "matchesExclude", + value: function matchesExclude(el) { + return DOMIterator.matches(el, this.opt.exclude.concat(["script", "style", "title", "head", "html"])); + } + }, { + key: "wrapRangeInTextNode", + value: function wrapRangeInTextNode(node, start, end) { + var hEl = !this.opt.element ? "mark" : this.opt.element, startNode = node.splitText(start), ret = startNode.splitText(end - start); + var repl = document.createElement(hEl); + repl.setAttribute("data-markjs", "true"); + if (this.opt.className) { + repl.setAttribute("class", this.opt.className); + } + repl.textContent = startNode.textContent; + startNode.parentNode.replaceChild(repl, startNode); + return ret; + } + }, { + key: "wrapRangeInMappedTextNode", + value: function wrapRangeInMappedTextNode(dict, start, end, filterCb, eachCb) { + var _this5 = this; + dict.nodes.every(function(n, i) { + var sibl = dict.nodes[i + 1]; + if (typeof sibl === "undefined" || sibl.start > start) { + if (!filterCb(n.node)) { + return false; + } + var s = start - n.start, e = (end > n.end ? n.end : end) - n.start, startStr = dict.value.substr(0, n.start), endStr = dict.value.substr(e + n.start); + n.node = _this5.wrapRangeInTextNode(n.node, s, e); + dict.value = startStr + endStr; + dict.nodes.forEach(function(k, j) { + if (j >= i) { + if (dict.nodes[j].start > 0 && j !== i) { + dict.nodes[j].start -= e; + } + dict.nodes[j].end -= e; + } + }); + end -= e; + eachCb(n.node.previousSibling, n.start); + if (end > n.end) { + start = n.end; + } else { + return false; + } + } + return true; + }); + } + }, { + key: "wrapMatches", + value: function wrapMatches(regex, ignoreGroups, filterCb, eachCb, endCb) { + var _this6 = this; + var matchIdx = ignoreGroups === 0 ? 0 : ignoreGroups + 1; + this.getTextNodes(function(dict) { + dict.nodes.forEach(function(node) { + node = node.node; + var match = void 0; + while ((match = regex.exec(node.textContent)) !== null && match[matchIdx] !== "") { + if (!filterCb(match[matchIdx], node)) { + continue; + } + var pos = match.index; + if (matchIdx !== 0) { + for (var i = 1; i < matchIdx; i++) { + pos += match[i].length; + } + } + node = _this6.wrapRangeInTextNode(node, pos, pos + match[matchIdx].length); + eachCb(node.previousSibling); + regex.lastIndex = 0; + } + }); + endCb(); + }); + } + }, { + key: "wrapMatchesAcrossElements", + value: function wrapMatchesAcrossElements(regex, ignoreGroups, filterCb, eachCb, endCb) { + var _this7 = this; + var matchIdx = ignoreGroups === 0 ? 0 : ignoreGroups + 1; + this.getTextNodes(function(dict) { + var match = void 0; + while ((match = regex.exec(dict.value)) !== null && match[matchIdx] !== "") { + var start = match.index; + if (matchIdx !== 0) { + for (var i = 1; i < matchIdx; i++) { + start += match[i].length; + } + } + var end = start + match[matchIdx].length; + _this7.wrapRangeInMappedTextNode(dict, start, end, function(node) { + return filterCb(match[matchIdx], node); + }, function(node, lastIndex) { + regex.lastIndex = lastIndex; + eachCb(node); + }); + } + endCb(); + }); + } + }, { + key: "wrapRangeFromIndex", + value: function wrapRangeFromIndex(ranges, filterCb, eachCb, endCb) { + var _this8 = this; + this.getTextNodes(function(dict) { + var originalLength = dict.value.length; + ranges.forEach(function(range, counter) { + var _checkWhitespaceRange = _this8.checkWhitespaceRanges(range, originalLength, dict.value), start = _checkWhitespaceRange.start, end = _checkWhitespaceRange.end, valid = _checkWhitespaceRange.valid; + if (valid) { + _this8.wrapRangeInMappedTextNode(dict, start, end, function(node) { + return filterCb(node, range, dict.value.substring(start, end), counter); + }, function(node) { + eachCb(node, range); + }); + } + }); + endCb(); + }); + } + }, { + key: "unwrapMatches", + value: function unwrapMatches(node) { + var parent = node.parentNode; + var docFrag = document.createDocumentFragment(); + while (node.firstChild) { + docFrag.appendChild(node.removeChild(node.firstChild)); + } + parent.replaceChild(docFrag, node); + if (!this.ie) { + parent.normalize(); + } else { + this.normalizeTextNode(parent); + } + } + }, { + key: "normalizeTextNode", + value: function normalizeTextNode(node) { + if (!node) { + return; + } + if (node.nodeType === 3) { + while (node.nextSibling && node.nextSibling.nodeType === 3) { + node.nodeValue += node.nextSibling.nodeValue; + node.parentNode.removeChild(node.nextSibling); + } + } else { + this.normalizeTextNode(node.firstChild); + } + this.normalizeTextNode(node.nextSibling); + } + }, { + key: "markRegExp", + value: function markRegExp(regexp, opt) { + var _this9 = this; + this.opt = opt; + this.log('Searching with expression "' + regexp + '"'); + var totalMatches = 0, fn = "wrapMatches"; + var eachCb = function eachCb2(element) { + totalMatches++; + _this9.opt.each(element); + }; + if (this.opt.acrossElements) { + fn = "wrapMatchesAcrossElements"; + } + this[fn](regexp, this.opt.ignoreGroups, function(match, node) { + return _this9.opt.filter(node, match, totalMatches); + }, eachCb, function() { + if (totalMatches === 0) { + _this9.opt.noMatch(regexp); + } + _this9.opt.done(totalMatches); + }); + } + }, { + key: "mark", + value: function mark(sv, opt) { + var _this10 = this; + this.opt = opt; + var totalMatches = 0, fn = "wrapMatches"; + var _getSeparatedKeywords = this.getSeparatedKeywords(typeof sv === "string" ? [sv] : sv), kwArr = _getSeparatedKeywords.keywords, kwArrLen = _getSeparatedKeywords.length, sens = this.opt.caseSensitive ? "" : "i", handler = function handler2(kw) { + var regex = new RegExp(_this10.createRegExp(kw), "gm" + sens), matches = 0; + _this10.log('Searching with expression "' + regex + '"'); + _this10[fn](regex, 1, function(term, node) { + return _this10.opt.filter(node, kw, totalMatches, matches); + }, function(element) { + matches++; + totalMatches++; + _this10.opt.each(element); + }, function() { + if (matches === 0) { + _this10.opt.noMatch(kw); + } + if (kwArr[kwArrLen - 1] === kw) { + _this10.opt.done(totalMatches); + } else { + handler2(kwArr[kwArr.indexOf(kw) + 1]); + } + }); + }; + if (this.opt.acrossElements) { + fn = "wrapMatchesAcrossElements"; + } + if (kwArrLen === 0) { + this.opt.done(totalMatches); + } else { + handler(kwArr[0]); + } + } + }, { + key: "markRanges", + value: function markRanges(rawRanges, opt) { + var _this11 = this; + this.opt = opt; + var totalMatches = 0, ranges = this.checkRanges(rawRanges); + if (ranges && ranges.length) { + this.log("Starting to mark with the following ranges: " + JSON.stringify(ranges)); + this.wrapRangeFromIndex(ranges, function(node, range, match, counter) { + return _this11.opt.filter(node, range, match, counter); + }, function(element, range) { + totalMatches++; + _this11.opt.each(element, range); + }, function() { + _this11.opt.done(totalMatches); + }); + } else { + this.opt.done(totalMatches); + } + } + }, { + key: "unmark", + value: function unmark(opt) { + var _this12 = this; + this.opt = opt; + var sel = this.opt.element ? this.opt.element : "*"; + sel += "[data-markjs]"; + if (this.opt.className) { + sel += "." + this.opt.className; + } + this.log('Removal selector "' + sel + '"'); + this.iterator.forEachNode(NodeFilter.SHOW_ELEMENT, function(node) { + _this12.unwrapMatches(node); + }, function(node) { + var matchesSel = DOMIterator.matches(node, sel), matchesExclude = _this12.matchesExclude(node); + if (!matchesSel || matchesExclude) { + return NodeFilter.FILTER_REJECT; + } else { + return NodeFilter.FILTER_ACCEPT; + } + }, this.opt.done); + } + }, { + key: "opt", + set: function set$$1(val) { + this._opt = _extends({}, { + "element": "", + "className": "", + "exclude": [], + "iframes": false, + "iframesTimeout": 5e3, + "separateWordSearch": true, + "diacritics": true, + "synonyms": {}, + "accuracy": "partially", + "acrossElements": false, + "caseSensitive": false, + "ignoreJoiners": false, + "ignoreGroups": 0, + "ignorePunctuation": [], + "wildcards": "disabled", + "each": function each() { + }, + "noMatch": function noMatch() { + }, + "filter": function filter() { + return true; + }, + "done": function done() { + }, + "debug": false, + "log": window.console + }, val); + }, + get: function get$$1() { + return this._opt; + } + }, { + key: "iterator", + get: function get$$1() { + return new DOMIterator(this.ctx, this.opt.iframes, this.opt.exclude, this.opt.iframesTimeout); + } + }]); + return Mark3; + }(); + function Mark2(ctx) { + var _this = this; + var instance = new Mark$1(ctx); + this.mark = function(sv, opt) { + instance.mark(sv, opt); + return _this; + }; + this.markRegExp = function(sv, opt) { + instance.markRegExp(sv, opt); + return _this; + }; + this.markRanges = function(sv, opt) { + instance.markRanges(sv, opt); + return _this; + }; + this.unmark = function(opt) { + instance.unmark(opt); + return _this; + }; + return this; + } + return Mark2; + }); + } +}); + +// lib/highlight.ts +var import_mark = __toESM(require_mark(), 1); +var PagefindHighlight = class { + constructor(options = { + markContext: null, + highlightParam: "pagefind-highlight", + markOptions: { + className: "pagefind-highlight", + exclude: ["[data-pagefind-ignore]", "[data-pagefind-ignore] *"] + }, + addStyles: true + }) { + var _a, _b; + const { highlightParam, markContext, markOptions, addStyles } = options; + this.highlightParam = highlightParam ?? "pagefind-highlight"; + this.addStyles = addStyles ?? true; + this.markContext = markContext !== void 0 ? markContext : null; + this.markOptions = markOptions !== void 0 ? markOptions : { + className: "pagefind-highlight", + exclude: ["[data-pagefind-ignore]", "[data-pagefind-ignore] *"] + }; + (_a = this.markOptions).className ?? (_a.className = "pagefind__highlight"); + (_b = this.markOptions).exclude ?? (_b.exclude = [ + "[data-pagefind-ignore]", + "[data-pagefind-ignore] *" + ]); + this.markOptions.separateWordSearch = false; + this.highlight(); + } + getHighlightParams(paramName) { + const urlParams = new URLSearchParams(window.location.search); + return urlParams.getAll(paramName); + } + // Inline styles might be too hard to override + addHighlightStyles(className) { + if (!className) + return; + const styleElement = document.createElement("style"); + styleElement.innerText = `:where(.${className}) { background-color: yellow; color: black; }`; + document.head.appendChild(styleElement); + } + createMarkInstance() { + if (this.markContext) { + return new import_mark.default(this.markContext); + } + const pagefindBody = document.querySelectorAll("[data-pagefind-body]"); + if (pagefindBody.length !== 0) { + return new import_mark.default(pagefindBody); + } else { + return new import_mark.default(document.body); + } + } + markText(instance, text) { + instance.mark(text, this.markOptions); + } + highlight() { + const params = this.getHighlightParams(this.highlightParam); + if (!params || params.length === 0) + return; + this.addStyles && this.addHighlightStyles(this.markOptions.className); + const markInstance = this.createMarkInstance(); + this.markText(markInstance, params); + } +}; +window.PagefindHighlight = PagefindHighlight; +export { + PagefindHighlight as default +}; +/*! Bundled license information: + +mark.js/dist/mark.js: + (*!*************************************************** + * mark.js v8.11.1 + * https://markjs.io/ + * Copyright (c) 2014–2018, Julian Kühnel + * Released under the MIT license https://git.io/vwTVl + *****************************************************) +*/ diff --git a/docs/pagefind/pagefind-modular-ui.css b/docs/pagefind/pagefind-modular-ui.css new file mode 100644 index 0000000..9c6793e --- /dev/null +++ b/docs/pagefind/pagefind-modular-ui.css @@ -0,0 +1,214 @@ +:root { + --pagefind-ui-scale: 0.8; + --pagefind-ui-primary: #034AD8; + --pagefind-ui-fade: #707070; + --pagefind-ui-text: #393939; + --pagefind-ui-background: #ffffff; + --pagefind-ui-border: #eeeeee; + --pagefind-ui-tag: #eeeeee; + --pagefind-ui-border-width: 2px; + --pagefind-ui-border-radius: 8px; + --pagefind-ui-image-border-radius: 8px; + --pagefind-ui-image-box-ratio: 3 / 2; + --pagefind-ui-font: system, -apple-system, ".SFNSText-Regular", + "San Francisco", "Roboto", "Segoe UI", "Helvetica Neue", + "Lucida Grande", sans-serif; +} + +[data-pfmod-hidden] { + display: none !important; +} + +[data-pfmod-suppressed] { + opacity: 0 !important; + pointer-events: none !important; +} + +[data-pfmod-sr-hidden] { + -webkit-clip: rect(0 0 0 0) !important; + clip: rect(0 0 0 0) !important; + -webkit-clip-path: inset(100%) !important; + clip-path: inset(100%) !important; + height: 1px !important; + overflow: hidden !important; + overflow: clip !important; + position: absolute !important; + white-space: nowrap !important; + width: 1px !important; +} + +[data-pfmod-loading] { + color: var(--pagefind-ui-text); + background-color: var(--pagefind-ui-text); + border-radius: var(--pagefind-ui-border-radius); + opacity: 0.1; + pointer-events: none; +} + +/* Input */ + +.pagefind-modular-input-wrapper { + position: relative; +} + +.pagefind-modular-input-wrapper::before { + background-color: var(--pagefind-ui-text); + width: calc(18px * var(--pagefind-ui-scale)); + height: calc(18px * var(--pagefind-ui-scale)); + top: calc(23px * var(--pagefind-ui-scale)); + left: calc(20px * var(--pagefind-ui-scale)); + content: ""; + position: absolute; + display: block; + opacity: 0.7; + -webkit-mask-image: url("data:image/svg+xml,%3Csvg width='18' height='18' viewBox='0 0 18 18' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M12.7549 11.255H11.9649L11.6849 10.985C12.6649 9.845 13.2549 8.365 13.2549 6.755C13.2549 3.165 10.3449 0.255005 6.75488 0.255005C3.16488 0.255005 0.254883 3.165 0.254883 6.755C0.254883 10.345 3.16488 13.255 6.75488 13.255C8.36488 13.255 9.84488 12.665 10.9849 11.685L11.2549 11.965V12.755L16.2549 17.745L17.7449 16.255L12.7549 11.255ZM6.75488 11.255C4.26488 11.255 2.25488 9.245 2.25488 6.755C2.25488 4.26501 4.26488 2.255 6.75488 2.255C9.24488 2.255 11.2549 4.26501 11.2549 6.755C11.2549 9.245 9.24488 11.255 6.75488 11.255Z' fill='%23000000'/%3E%3C/svg%3E%0A"); + mask-image: url("data:image/svg+xml,%3Csvg width='18' height='18' viewBox='0 0 18 18' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M12.7549 11.255H11.9649L11.6849 10.985C12.6649 9.845 13.2549 8.365 13.2549 6.755C13.2549 3.165 10.3449 0.255005 6.75488 0.255005C3.16488 0.255005 0.254883 3.165 0.254883 6.755C0.254883 10.345 3.16488 13.255 6.75488 13.255C8.36488 13.255 9.84488 12.665 10.9849 11.685L11.2549 11.965V12.755L16.2549 17.745L17.7449 16.255L12.7549 11.255ZM6.75488 11.255C4.26488 11.255 2.25488 9.245 2.25488 6.755C2.25488 4.26501 4.26488 2.255 6.75488 2.255C9.24488 2.255 11.2549 4.26501 11.2549 6.755C11.2549 9.245 9.24488 11.255 6.75488 11.255Z' fill='%23000000'/%3E%3C/svg%3E%0A"); + -webkit-mask-size: 100%; + mask-size: 100%; + z-index: 9; + pointer-events: none; +} + +.pagefind-modular-input { + height: calc(64px * var(--pagefind-ui-scale)); + padding: 0 calc(70px * var(--pagefind-ui-scale)) 0 calc(54px * var(--pagefind-ui-scale)); + background-color: var(--pagefind-ui-background); + border: var(--pagefind-ui-border-width) solid var(--pagefind-ui-border); + border-radius: var(--pagefind-ui-border-radius); + font-size: calc(21px * var(--pagefind-ui-scale)); + position: relative; + appearance: none; + -webkit-appearance: none; + display: flex; + width: 100%; + box-sizing: border-box; + font-weight: 700; +} + +.pagefind-modular-input::placeholder { + opacity: 0.2; +} + +.pagefind-modular-input-clear { + position: absolute; + top: calc(2px * var(--pagefind-ui-scale)); + right: calc(2px * var(--pagefind-ui-scale)); + height: calc(60px * var(--pagefind-ui-scale)); + border-radius: var(--pagefind-ui-border-radius); + padding: 0 calc(15px * var(--pagefind-ui-scale)) 0 calc(2px * var(--pagefind-ui-scale)); + color: var(--pagefind-ui-text); + font-size: calc(14px * var(--pagefind-ui-scale)); + cursor: pointer; + background-color: var(--pagefind-ui-background); + border: none; + appearance: none; +} + +/* ResultList */ + +.pagefind-modular-list-result { + list-style-type: none; + display: flex; + align-items: flex-start; + gap: min(calc(40px * var(--pagefind-ui-scale)), 3%); + padding: calc(30px * var(--pagefind-ui-scale)) 0 calc(40px * var(--pagefind-ui-scale)); + border-top: solid var(--pagefind-ui-border-width) var(--pagefind-ui-border); +} + +.pagefind-modular-list-result:last-of-type { + border-bottom: solid var(--pagefind-ui-border-width) var(--pagefind-ui-border); +} + +.pagefind-modular-list-thumb { + width: min(30%, + calc((30% - (100px * var(--pagefind-ui-scale))) * 100000)); + max-width: calc(120px * var(--pagefind-ui-scale)); + margin-top: calc(10px * var(--pagefind-ui-scale)); + aspect-ratio: var(--pagefind-ui-image-box-ratio); + position: relative; +} + +.pagefind-modular-list-image { + display: block; + position: absolute; + left: 50%; + transform: translateX(-50%); + font-size: 0; + width: auto; + height: auto; + max-width: 100%; + max-height: 100%; + border-radius: var(--pagefind-ui-image-border-radius); +} + +.pagefind-modular-list-inner { + flex: 1; + display: flex; + flex-direction: column; + align-items: flex-start; + margin-top: calc(10px * var(--pagefind-ui-scale)); +} + +.pagefind-modular-list-title { + display: inline-block; + font-weight: 700; + font-size: calc(21px * var(--pagefind-ui-scale)); + margin-top: 0; + margin-bottom: 0; +} + +.pagefind-modular-list-link { + color: var(--pagefind-ui-text); + text-decoration: none; +} + +.pagefind-modular-list-link:hover { + text-decoration: underline; +} + +.pagefind-modular-list-excerpt { + display: inline-block; + font-weight: 400; + font-size: calc(16px * var(--pagefind-ui-scale)); + margin-top: calc(4px * var(--pagefind-ui-scale)); + margin-bottom: 0; + min-width: calc(250px * var(--pagefind-ui-scale)); +} + +/* FilterPills */ + +.pagefind-modular-filter-pills-wrapper { + overflow-x: scroll; + padding: 15px 0; +} + +.pagefind-modular-filter-pills { + display: flex; + gap: 6px; +} + +.pagefind-modular-filter-pill { + display: flex; + justify-content: center; + align-items: center; + border: none; + appearance: none; + padding: 0 calc(24px * var(--pagefind-ui-scale)); + background-color: var(--pagefind-ui-background); + color: var(--pagefind-ui-fade); + border: var(--pagefind-ui-border-width) solid var(--pagefind-ui-border); + border-radius: calc(25px * var(--pagefind-ui-scale)); + font-size: calc(18px * var(--pagefind-ui-scale)); + height: calc(50px * var(--pagefind-ui-scale)); + cursor: pointer; + white-space: nowrap; +} + +.pagefind-modular-filter-pill:hover { + border-color: var(--pagefind-ui-primary); +} + +.pagefind-modular-filter-pill[aria-pressed="true"] { + border-color: var(--pagefind-ui-primary); + color: var(--pagefind-ui-primary); +} \ No newline at end of file diff --git a/docs/pagefind/pagefind-modular-ui.js b/docs/pagefind/pagefind-modular-ui.js new file mode 100644 index 0000000..43f738f --- /dev/null +++ b/docs/pagefind/pagefind-modular-ui.js @@ -0,0 +1,8 @@ +(()=>{var b=Object.defineProperty;var w=(i,e)=>{for(var t in e)b(i,t,{get:e[t],enumerable:!0})};var f={};w(f,{FilterPills:()=>h,Input:()=>l,Instance:()=>p,ResultList:()=>a,Summary:()=>o});var r=class i{constructor(e){this.element=document.createElement(e)}id(e){return this.element.id=e,this}class(e){return this.element.classList.add(e),this}attrs(e){for(let[t,s]of Object.entries(e))this.element.setAttribute(t,s);return this}text(e){return this.element.innerText=e,this}html(e){return this.element.innerHTML=e,this}handle(e,t){return this.element.addEventListener(e,t),this}addTo(e){return e instanceof i?e.element.appendChild(this.element):e.appendChild(this.element),this.element}};var T=async(i=100)=>new Promise(e=>setTimeout(e,i)),l=class{constructor(e={}){if(this.inputEl=null,this.clearEl=null,this.instance=null,this.searchID=0,this.debounceTimeoutMs=e.debounceTimeoutMs??300,e.inputElement){if(e.containerElement){console.warn("[Pagefind Input component]: inputElement and containerElement both supplied. Ignoring the container option.");return}this.initExisting(e.inputElement)}else if(e.containerElement)this.initContainer(e.containerElement);else{console.error("[Pagefind Input component]: No selector supplied for containerElement or inputElement");return}this.inputEl.addEventListener("input",async t=>{if(this.instance&&typeof t?.target?.value=="string"){this.updateState(t.target.value);let s=++this.searchID;if(await T(this.debounceTimeoutMs),s!==this.searchID)return null;this.instance?.triggerSearch(t.target.value)}}),this.inputEl.addEventListener("keydown",t=>{t.key==="Escape"&&(++this.searchID,this.inputEl.value="",this.instance?.triggerSearch(""),this.updateState("")),t.key==="Enter"&&t.preventDefault()}),this.inputEl.addEventListener("focus",()=>{this.instance?.triggerLoad()})}initContainer(e){let t=document.querySelector(e);if(!t){console.error(`[Pagefind Input component]: No container found for ${e} selector`);return}if(t.tagName==="INPUT")console.warn(`[Pagefind Input component]: Encountered input element for ${e} when a container was expected`),console.warn("[Pagefind Input component]: Treating containerElement option as inputElement and proceeding"),this.initExisting(e);else{t.innerHTML="";let s=0;for(;document.querySelector(`#pfmod-input-${s}`);)s+=1;let n=new r("form").class("pagefind-modular-input-wrapper").attrs({role:"search","aria-label":"Search this site",action:"javascript:void(0);"});new r("label").attrs({for:`pfmod-input-${s}`,"data-pfmod-sr-hidden":"true"}).text("Search this site").addTo(n),this.inputEl=new r("input").id(`pfmod-input-${s}`).class("pagefind-modular-input").attrs({autocapitalize:"none",enterkeyhint:"search"}).addTo(n),this.clearEl=new r("button").class("pagefind-modular-input-clear").attrs({"data-pfmod-suppressed":"true"}).text("Clear").handle("click",()=>{this.inputEl.value="",this.instance.triggerSearch(""),this.updateState("")}).addTo(n),n.addTo(t)}}initExisting(e){let t=document.querySelector(e);if(!t){console.error(`[Pagefind Input component]: No input element found for ${e} selector`);return}if(t.tagName!=="INPUT"){console.error(`[Pagefind Input component]: Expected ${e} to be an element`);return}this.inputEl=t}updateState(e){this.clearEl&&(e&&e?.length?this.clearEl.removeAttribute("data-pfmod-suppressed"):this.clearEl.setAttribute("data-pfmod-suppressed","true"))}register(e){this.instance=e,this.instance.on("search",(t,s)=>{this.inputEl&&document.activeElement!==this.inputEl&&(this.inputEl.value=t,this.updateState(t))})}focus(){this.inputEl&&this.inputEl.focus()}};var g=i=>{if(i instanceof Element)return[i];if(Array.isArray(i)&&i.every(e=>e instanceof Element))return i;if(typeof i=="string"||i instanceof String){let e=document.createElement("div");return e.innerHTML=i,[...e.childNodes]}else return console.error(`[Pagefind ResultList component]: Expected template function to return an HTML element or string, got ${typeof i}`),[]},v=()=>{let i=(e=30)=>". ".repeat(Math.floor(10+Math.random()*e));return`
  • +
    +
    +

    ${i(30)}

    +

    ${i(40)}

    +
    +
  • `},y=i=>{let e=new r("li").class("pagefind-modular-list-result"),t=new r("div").class("pagefind-modular-list-thumb").addTo(e);i?.meta?.image&&new r("img").class("pagefind-modular-list-image").attrs({src:i.meta.image,alt:i.meta.image_alt||i.meta.title}).addTo(t);let s=new r("div").class("pagefind-modular-list-inner").addTo(e),n=new r("p").class("pagefind-modular-list-title").addTo(s);return new r("a").class("pagefind-modular-list-link").text(i.meta?.title).attrs({href:i.meta?.url||i.url}).addTo(n),new r("p").class("pagefind-modular-list-excerpt").html(i.excerpt).addTo(s),e.element},E=i=>{if(!(i instanceof HTMLElement))return null;let e=window.getComputedStyle(i).overflowY;return e!=="visible"&&e!=="hidden"?i:E(i.parentNode)},d=class{constructor(e={}){this.rawResult=e.result,this.placeholderNodes=e.placeholderNodes,this.resultFn=e.resultFn,this.intersectionEl=e.intersectionEl,this.result=null,this.waitForIntersection()}waitForIntersection(){if(!this.placeholderNodes?.length)return;let e={root:this.intersectionEl,rootMargin:"0px",threshold:.01};new IntersectionObserver((s,n)=>{this.result===null&&s?.[0]?.isIntersecting&&(this.load(),n.disconnect())},e).observe(this.placeholderNodes[0])}async load(){if(!this.placeholderNodes?.length)return;this.result=await this.rawResult.data();let e=this.resultFn(this.result),t=g(e);for(;this.placeholderNodes.length>1;)this.placeholderNodes.pop().remove();this.placeholderNodes[0].replaceWith(...t)}},a=class{constructor(e){if(this.intersectionEl=document.body,this.containerEl=null,this.results=[],this.placeholderTemplate=e.placeholderTemplate??v,this.resultTemplate=e.resultTemplate??y,e.containerElement)this.initContainer(e.containerElement);else{console.error("[Pagefind ResultList component]: No selector supplied for containerElement");return}}initContainer(e){let t=document.querySelector(e);if(!t){console.error(`[Pagefind ResultList component]: No container found for ${e} selector`);return}this.containerEl=t}append(e){for(let t of e)this.containerEl.appendChild(t)}register(e){e.on("results",t=>{this.containerEl&&(this.containerEl.innerHTML="",this.intersectionEl=E(this.containerEl),this.results=t.results.map(s=>{let n=g(this.placeholderTemplate());return this.append(n),new d({result:s,placeholderNodes:n,resultFn:this.resultTemplate,intersectionEl:this.intersectionEl})}))}),e.on("loading",()=>{this.containerEl&&(this.containerEl.innerHTML="")})}};var o=class{constructor(e={}){if(this.containerEl=null,this.defaultMessage=e.defaultMessage??"",this.term="",e.containerElement)this.initContainer(e.containerElement);else{console.error("[Pagefind Summary component]: No selector supplied for containerElement");return}}initContainer(e){let t=document.querySelector(e);if(!t){console.error(`[Pagefind Summary component]: No container found for ${e} selector`);return}this.containerEl=t,this.containerEl.innerText=this.defaultMessage}register(e){e.on("search",(t,s)=>{this.term=t}),e.on("results",t=>{if(!this.containerEl||!t)return;if(!this.term){this.containerEl.innerText=this.defaultMessage;return}let s=t?.results?.length??0;this.containerEl.innerText=`${s} result${s===1?"":"s"} for ${this.term}`}),e.on("loading",()=>{this.containerEl&&(this.containerEl.innerText=`Searching for ${this.term}...`)})}};var h=class{constructor(e={}){if(this.instance=null,this.wrapper=null,this.pillContainer=null,this.available={},this.selected=["All"],this.total=0,this.filterMemo="",this.filter=e.filter,this.ordering=e.ordering??null,this.alwaysShow=e.alwaysShow??!1,this.selectMultiple=e.selectMultiple??!1,!this.filter?.length){console.error("[Pagefind FilterPills component]: No filter option supplied, nothing to display");return}if(e.containerElement)this.initContainer(e.containerElement);else{console.error("[Pagefind FilterPills component]: No selector supplied for containerElement");return}}initContainer(e){let t=document.querySelector(e);if(!t){console.error(`[Pagefind FilterPills component]: No container found for ${e} selector`);return}t.innerHTML="";let s=`pagefind_modular_filter_pills_${this.filter}`,n=new r("div").class("pagefind-modular-filter-pills-wrapper").attrs({role:"group","aria-labelledby":s});this.alwaysShow||n.attrs({"data-pfmod-hidden":!0}),new r("div").id(s).class("pagefind-modular-filter-pills-label").attrs({"data-pfmod-sr-hidden":!0}).text(`Filter results by ${this.filter}`).addTo(n),this.pillContainer=new r("div").class("pagefind-modular-filter-pills").addTo(n),this.wrapper=n.addTo(t)}update(){let e=this.available.map(t=>t[0]).join("~");e==this.filterMemo?this.updateExisting():(this.renderNew(),this.filterMemo=e)}pushFilters(){let e=this.selected.filter(t=>t!=="All");this.instance.triggerFilter(this.filter,e)}pillInner(e,t){return this.total?`${e} (${t})`:`${e}`}renderNew(){this.available.forEach(([e,t])=>{new r("button").class("pagefind-modular-filter-pill").html(this.pillInner(e,t)).attrs({"aria-pressed":this.selected.includes(e),type:"button"}).handle("click",()=>{e==="All"?this.selected=["All"]:this.selected.includes(e)?this.selected=this.selected.filter(s=>s!==e):this.selectMultiple?this.selected.push(e):this.selected=[e],this.selected?.length?this.selected?.length>1&&(this.selected=this.selected.filter(s=>s!=="All")):this.selected=["All"],this.update(),this.pushFilters()}).addTo(this.pillContainer)})}updateExisting(){let e=[...this.pillContainer.childNodes];this.available.forEach(([t,s],n)=>{e[n].innerHTML=this.pillInner(t,s),e[n].setAttribute("aria-pressed",this.selected.includes(t))})}register(e){this.instance=e,this.instance.on("filters",t=>{if(!this.pillContainer)return;this.selectMultiple?t=t.available:t=t.total;let s=t[this.filter];if(!s){console.warn(`[Pagefind FilterPills component]: No possible values found for the ${this.filter} filter`);return}this.available=Object.entries(s),Array.isArray(this.ordering)?this.available.sort((n,c)=>{let m=this.ordering.indexOf(n[0]),_=this.ordering.indexOf(c[0]);return(m===-1?1/0:m)-(_===-1?1/0:_)}):this.available.sort((n,c)=>n[0].localeCompare(c[0])),this.available.unshift(["All",this.total]),this.update()}),e.on("results",t=>{this.pillContainer&&(this.total=t?.unfilteredResultCount||0,this.available?.[0]?.[0]==="All"&&(this.available[0][1]=this.total),this.total||this.alwaysShow?this.wrapper.removeAttribute("data-pfmod-hidden"):this.wrapper.setAttribute("data-pfmod-hidden","true"),this.update())})}};var P=async(i=50)=>await new Promise(e=>setTimeout(e,i)),u;try{document?.currentScript&&document.currentScript.tagName.toUpperCase()==="SCRIPT"&&(u=new URL(document.currentScript.src).pathname.match(/^(.*\/)(?:pagefind-)?modular-ui.js.*$/)[1])}catch{u="/pagefind/"}var p=class{constructor(e={}){this.__pagefind__=null,this.__initializing__=null,this.__searchID__=0,this.__hooks__={search:[],filters:[],loading:[],results:[]},this.components=[],this.searchTerm="",this.searchFilters={},this.searchResult={},this.availableFilters=null,this.totalFilters=null,this.options={bundlePath:e.bundlePath??u,mergeIndex:e.mergeIndex??[]},delete e.bundlePath,delete e.resetStyles,delete e.processResult,delete e.processTerm,delete e.debounceTimeoutMs,delete e.mergeIndex,delete e.translations,this.pagefindOptions=e}add(e){e?.register?.(this),this.components.push(e)}on(e,t){if(!this.__hooks__[e]){let s=Object.keys(this.__hooks__).join(", ");console.error(`[Pagefind Composable]: Unknown event type ${e}. Supported events: [${s}]`);return}if(typeof t!="function"){console.error(`[Pagefind Composable]: Expected callback to be a function, received ${typeof t}`);return}this.__hooks__[e].push(t)}triggerLoad(){this.__load__()}triggerSearch(e){this.searchTerm=e,this.__dispatch__("search",e,this.searchFilters),this.__search__(e,this.searchFilters)}triggerSearchWithFilters(e,t){this.searchTerm=e,this.searchFilters=t,this.__dispatch__("search",e,t),this.__search__(e,t)}triggerFilters(e){this.searchFilters=e,this.__dispatch__("search",this.searchTerm,e),this.__search__(this.searchTerm,e)}triggerFilter(e,t){this.searchFilters=this.searchFilters||{},this.searchFilters[e]=t,this.__dispatch__("search",this.searchTerm,this.searchFilters),this.__search__(this.searchTerm,this.searchFilters)}__dispatch__(e,...t){this.__hooks__[e]?.forEach(s=>s?.(...t))}async __clear__(){this.__dispatch__("results",{results:[],unfilteredTotalCount:0}),this.availableFilters=await this.__pagefind__.filters(),this.totalFilters=this.availableFilters,this.__dispatch__("filters",{available:this.availableFilters,total:this.totalFilters})}async __search__(e,t){this.__dispatch__("loading"),await this.__load__();let s=++this.__searchID__;if(!e||!e.length)return this.__clear__();let n=await this.__pagefind__.search(e,{filters:t});n&&this.__searchID__===s&&(n.filters&&Object.keys(n.filters)?.length&&(this.availableFilters=n.filters,this.totalFilters=n.totalFilters,this.__dispatch__("filters",{available:this.availableFilters,total:this.totalFilters})),this.searchResult=n,this.__dispatch__("results",this.searchResult))}async __load__(){if(this.__initializing__){for(;!this.__pagefind__;)await P(50);return}if(this.__initializing__=!0,!this.__pagefind__){let e;try{e=await import(`${this.options.bundlePath}pagefind.js`)}catch(t){console.error(t),console.error([`Pagefind couldn't be loaded from ${this.options.bundlePath}pagefind.js`,"You can configure this by passing a bundlePath option to PagefindComposable Instance"].join(` +`)),document?.currentScript&&document.currentScript.tagName.toUpperCase()==="SCRIPT"?console.error(`[DEBUG: Loaded from ${document.currentScript?.src??"bad script location"}]`):console.error("no known script location")}await e.options(this.pagefindOptions||{});for(let t of this.options.mergeIndex){if(!t.bundlePath)throw new Error("mergeIndex requires a bundlePath parameter");let s=t.bundlePath;delete t.bundlePath,await e.mergeIndex(s,t)}this.__pagefind__=e}this.availableFilters=await this.__pagefind__.filters(),this.totalFilters=this.availableFilters,this.__dispatch__("filters",{available:this.availableFilters,total:this.totalFilters})}};window.PagefindModularUI=f;})(); diff --git a/docs/pagefind/pagefind-ui.css b/docs/pagefind/pagefind-ui.css new file mode 100644 index 0000000..d7984a9 --- /dev/null +++ b/docs/pagefind/pagefind-ui.css @@ -0,0 +1 @@ +.pagefind-ui__result.svelte-j9e30.svelte-j9e30{list-style-type:none;display:flex;align-items:flex-start;gap:min(calc(40px * var(--pagefind-ui-scale)),3%);padding:calc(30px * var(--pagefind-ui-scale)) 0 calc(40px * var(--pagefind-ui-scale));border-top:solid var(--pagefind-ui-border-width) var(--pagefind-ui-border)}.pagefind-ui__result.svelte-j9e30.svelte-j9e30:last-of-type{border-bottom:solid var(--pagefind-ui-border-width) var(--pagefind-ui-border)}.pagefind-ui__result-thumb.svelte-j9e30.svelte-j9e30{width:min(30%,calc((30% - (100px * var(--pagefind-ui-scale))) * 100000));max-width:calc(120px * var(--pagefind-ui-scale));margin-top:calc(10px * var(--pagefind-ui-scale));aspect-ratio:var(--pagefind-ui-image-box-ratio);position:relative}.pagefind-ui__result-image.svelte-j9e30.svelte-j9e30{display:block;position:absolute;left:50%;transform:translate(-50%);font-size:0;width:auto;height:auto;max-width:100%;max-height:100%;border-radius:var(--pagefind-ui-image-border-radius)}.pagefind-ui__result-inner.svelte-j9e30.svelte-j9e30{flex:1;display:flex;flex-direction:column;align-items:flex-start;margin-top:calc(10px * var(--pagefind-ui-scale))}.pagefind-ui__result-title.svelte-j9e30.svelte-j9e30{display:inline-block;font-weight:700;font-size:calc(21px * var(--pagefind-ui-scale));margin-top:0;margin-bottom:0}.pagefind-ui__result-title.svelte-j9e30 .pagefind-ui__result-link.svelte-j9e30{color:var(--pagefind-ui-text);text-decoration:none}.pagefind-ui__result-title.svelte-j9e30 .pagefind-ui__result-link.svelte-j9e30:hover{text-decoration:underline}.pagefind-ui__result-excerpt.svelte-j9e30.svelte-j9e30{display:inline-block;font-weight:400;font-size:calc(16px * var(--pagefind-ui-scale));margin-top:calc(4px * var(--pagefind-ui-scale));margin-bottom:0;min-width:calc(250px * var(--pagefind-ui-scale))}.pagefind-ui__loading.svelte-j9e30.svelte-j9e30{color:var(--pagefind-ui-text);background-color:var(--pagefind-ui-text);border-radius:var(--pagefind-ui-border-radius);opacity:.1;pointer-events:none}.pagefind-ui__result-tags.svelte-j9e30.svelte-j9e30{list-style-type:none;padding:0;display:flex;gap:calc(20px * var(--pagefind-ui-scale));flex-wrap:wrap;margin-top:calc(20px * var(--pagefind-ui-scale))}.pagefind-ui__result-tag.svelte-j9e30.svelte-j9e30{padding:calc(4px * var(--pagefind-ui-scale)) calc(8px * var(--pagefind-ui-scale));font-size:calc(14px * var(--pagefind-ui-scale));border-radius:var(--pagefind-ui-border-radius);background-color:var(--pagefind-ui-tag)}.pagefind-ui__result.svelte-4xnkmf.svelte-4xnkmf{list-style-type:none;display:flex;align-items:flex-start;gap:min(calc(40px * var(--pagefind-ui-scale)),3%);padding:calc(30px * var(--pagefind-ui-scale)) 0 calc(40px * var(--pagefind-ui-scale));border-top:solid var(--pagefind-ui-border-width) var(--pagefind-ui-border)}.pagefind-ui__result.svelte-4xnkmf.svelte-4xnkmf:last-of-type{border-bottom:solid var(--pagefind-ui-border-width) var(--pagefind-ui-border)}.pagefind-ui__result-nested.svelte-4xnkmf.svelte-4xnkmf{display:flex;flex-direction:column;padding-left:calc(20px * var(--pagefind-ui-scale))}.pagefind-ui__result-nested.svelte-4xnkmf.svelte-4xnkmf:first-of-type{padding-top:calc(10px * var(--pagefind-ui-scale))}.pagefind-ui__result-nested.svelte-4xnkmf .pagefind-ui__result-link.svelte-4xnkmf{font-size:.9em;position:relative}.pagefind-ui__result-nested.svelte-4xnkmf .pagefind-ui__result-link.svelte-4xnkmf:before{content:"\2937 ";position:absolute;top:0;right:calc(100% + .1em)}.pagefind-ui__result-thumb.svelte-4xnkmf.svelte-4xnkmf{width:min(30%,calc((30% - (100px * var(--pagefind-ui-scale))) * 100000));max-width:calc(120px * var(--pagefind-ui-scale));margin-top:calc(10px * var(--pagefind-ui-scale));aspect-ratio:var(--pagefind-ui-image-box-ratio);position:relative}.pagefind-ui__result-image.svelte-4xnkmf.svelte-4xnkmf{display:block;position:absolute;left:50%;transform:translate(-50%);font-size:0;width:auto;height:auto;max-width:100%;max-height:100%;border-radius:var(--pagefind-ui-image-border-radius)}.pagefind-ui__result-inner.svelte-4xnkmf.svelte-4xnkmf{flex:1;display:flex;flex-direction:column;align-items:flex-start;margin-top:calc(10px * var(--pagefind-ui-scale))}.pagefind-ui__result-title.svelte-4xnkmf.svelte-4xnkmf{display:inline-block;font-weight:700;font-size:calc(21px * var(--pagefind-ui-scale));margin-top:0;margin-bottom:0}.pagefind-ui__result-title.svelte-4xnkmf .pagefind-ui__result-link.svelte-4xnkmf{color:var(--pagefind-ui-text);text-decoration:none}.pagefind-ui__result-title.svelte-4xnkmf .pagefind-ui__result-link.svelte-4xnkmf:hover{text-decoration:underline}.pagefind-ui__result-excerpt.svelte-4xnkmf.svelte-4xnkmf{display:inline-block;font-weight:400;font-size:calc(16px * var(--pagefind-ui-scale));margin-top:calc(4px * var(--pagefind-ui-scale));margin-bottom:0;min-width:calc(250px * var(--pagefind-ui-scale))}.pagefind-ui__loading.svelte-4xnkmf.svelte-4xnkmf{color:var(--pagefind-ui-text);background-color:var(--pagefind-ui-text);border-radius:var(--pagefind-ui-border-radius);opacity:.1;pointer-events:none}.pagefind-ui__result-tags.svelte-4xnkmf.svelte-4xnkmf{list-style-type:none;padding:0;display:flex;gap:calc(20px * var(--pagefind-ui-scale));flex-wrap:wrap;margin-top:calc(20px * var(--pagefind-ui-scale))}.pagefind-ui__result-tag.svelte-4xnkmf.svelte-4xnkmf{padding:calc(4px * var(--pagefind-ui-scale)) calc(8px * var(--pagefind-ui-scale));font-size:calc(14px * var(--pagefind-ui-scale));border-radius:var(--pagefind-ui-border-radius);background-color:var(--pagefind-ui-tag)}legend.svelte-1v2r7ls.svelte-1v2r7ls{position:absolute;clip:rect(0 0 0 0)}.pagefind-ui__filter-panel.svelte-1v2r7ls.svelte-1v2r7ls{min-width:min(calc(260px * var(--pagefind-ui-scale)),100%);flex:1;display:flex;flex-direction:column;margin-top:calc(20px * var(--pagefind-ui-scale))}.pagefind-ui__filter-group.svelte-1v2r7ls.svelte-1v2r7ls{border:0;padding:0}.pagefind-ui__filter-block.svelte-1v2r7ls.svelte-1v2r7ls{padding:0;display:block;border-bottom:solid calc(2px * var(--pagefind-ui-scale)) var(--pagefind-ui-border);padding:calc(20px * var(--pagefind-ui-scale)) 0}.pagefind-ui__filter-name.svelte-1v2r7ls.svelte-1v2r7ls{font-size:calc(16px * var(--pagefind-ui-scale));position:relative;display:flex;align-items:center;list-style:none;font-weight:700;cursor:pointer;height:calc(24px * var(--pagefind-ui-scale))}.pagefind-ui__filter-name.svelte-1v2r7ls.svelte-1v2r7ls::-webkit-details-marker{display:none}.pagefind-ui__filter-name.svelte-1v2r7ls.svelte-1v2r7ls:after{position:absolute;content:"";right:calc(6px * var(--pagefind-ui-scale));top:50%;width:calc(8px * var(--pagefind-ui-scale));height:calc(8px * var(--pagefind-ui-scale));border:solid calc(2px * var(--pagefind-ui-scale)) currentColor;border-right:0;border-top:0;transform:translateY(-70%) rotate(-45deg)}.pagefind-ui__filter-block[open].svelte-1v2r7ls .pagefind-ui__filter-name.svelte-1v2r7ls:after{transform:translateY(-70%) rotate(-225deg)}.pagefind-ui__filter-group.svelte-1v2r7ls.svelte-1v2r7ls{display:flex;flex-direction:column;gap:calc(20px * var(--pagefind-ui-scale));padding-top:calc(30px * var(--pagefind-ui-scale))}.pagefind-ui__filter-value.svelte-1v2r7ls.svelte-1v2r7ls{position:relative;display:flex;align-items:center;gap:calc(8px * var(--pagefind-ui-scale))}.pagefind-ui__filter-value.svelte-1v2r7ls.svelte-1v2r7ls:before{position:absolute;content:"";top:50%;left:calc(8px * var(--pagefind-ui-scale));width:0px;height:0px;border:solid 1px #fff;opacity:0;transform:translate(calc(4.5px * var(--pagefind-ui-scale) * -1),calc(.8px * var(--pagefind-ui-scale))) skew(-5deg) rotate(-45deg);transform-origin:top left;border-top:0;border-right:0;pointer-events:none}.pagefind-ui__filter-value.pagefind-ui__filter-value--checked.svelte-1v2r7ls.svelte-1v2r7ls:before{opacity:1;width:calc(9px * var(--pagefind-ui-scale));height:calc(4px * var(--pagefind-ui-scale));transition:width .1s ease-out .1s,height .1s ease-in}.pagefind-ui__filter-checkbox.svelte-1v2r7ls.svelte-1v2r7ls{margin:0;width:calc(16px * var(--pagefind-ui-scale));height:calc(16px * var(--pagefind-ui-scale));border:solid 1px var(--pagefind-ui-border);appearance:none;-webkit-appearance:none;border-radius:calc(var(--pagefind-ui-border-radius) / 2);background-color:var(--pagefind-ui-background);cursor:pointer}.pagefind-ui__filter-checkbox.svelte-1v2r7ls.svelte-1v2r7ls:checked{background-color:var(--pagefind-ui-primary);border:solid 1px var(--pagefind-ui-primary)}.pagefind-ui__filter-label.svelte-1v2r7ls.svelte-1v2r7ls{cursor:pointer;font-size:calc(16px * var(--pagefind-ui-scale));font-weight:400}.pagefind-ui--reset *:where(:not(html,iframe,canvas,img,svg,video):not(svg *,symbol *)){all:unset;display:revert;outline:revert}.pagefind-ui--reset *,.pagefind-ui--reset *:before,.pagefind-ui--reset *:after{box-sizing:border-box}.pagefind-ui--reset a,.pagefind-ui--reset button{cursor:revert}.pagefind-ui--reset ol,.pagefind-ui--reset ul,.pagefind-ui--reset menu{list-style:none}.pagefind-ui--reset img{max-width:100%}.pagefind-ui--reset table{border-collapse:collapse}.pagefind-ui--reset input,.pagefind-ui--reset textarea{-webkit-user-select:auto}.pagefind-ui--reset textarea{white-space:revert}.pagefind-ui--reset meter{-webkit-appearance:revert;appearance:revert}.pagefind-ui--reset ::placeholder{color:unset}.pagefind-ui--reset :where([hidden]){display:none}.pagefind-ui--reset :where([contenteditable]:not([contenteditable="false"])){-moz-user-modify:read-write;-webkit-user-modify:read-write;overflow-wrap:break-word;-webkit-line-break:after-white-space;-webkit-user-select:auto}.pagefind-ui--reset :where([draggable="true"]){-webkit-user-drag:element}.pagefind-ui--reset mark{all:revert}:root{--pagefind-ui-scale:.8;--pagefind-ui-primary:#393939;--pagefind-ui-text:#393939;--pagefind-ui-background:#ffffff;--pagefind-ui-border:#eeeeee;--pagefind-ui-tag:#eeeeee;--pagefind-ui-border-width:2px;--pagefind-ui-border-radius:8px;--pagefind-ui-image-border-radius:8px;--pagefind-ui-image-box-ratio:3 / 2;--pagefind-ui-font:system, -apple-system, "BlinkMacSystemFont", ".SFNSText-Regular", "San Francisco", "Roboto", "Segoe UI", "Helvetica Neue", "Lucida Grande", "Ubuntu", "arial", sans-serif}.pagefind-ui.svelte-e9gkc3{width:100%;color:var(--pagefind-ui-text);font-family:var(--pagefind-ui-font)}.pagefind-ui__hidden.svelte-e9gkc3{display:none!important}.pagefind-ui__suppressed.svelte-e9gkc3{opacity:0;pointer-events:none}.pagefind-ui__form.svelte-e9gkc3{position:relative}.pagefind-ui__form.svelte-e9gkc3:before{background-color:var(--pagefind-ui-text);width:calc(18px * var(--pagefind-ui-scale));height:calc(18px * var(--pagefind-ui-scale));top:calc(23px * var(--pagefind-ui-scale));left:calc(20px * var(--pagefind-ui-scale));content:"";position:absolute;display:block;opacity:.7;-webkit-mask-image:url("data:image/svg+xml,%3Csvg width='18' height='18' viewBox='0 0 18 18' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M12.7549 11.255H11.9649L11.6849 10.985C12.6649 9.845 13.2549 8.365 13.2549 6.755C13.2549 3.165 10.3449 0.255005 6.75488 0.255005C3.16488 0.255005 0.254883 3.165 0.254883 6.755C0.254883 10.345 3.16488 13.255 6.75488 13.255C8.36488 13.255 9.84488 12.665 10.9849 11.685L11.2549 11.965V12.755L16.2549 17.745L17.7449 16.255L12.7549 11.255ZM6.75488 11.255C4.26488 11.255 2.25488 9.245 2.25488 6.755C2.25488 4.26501 4.26488 2.255 6.75488 2.255C9.24488 2.255 11.2549 4.26501 11.2549 6.755C11.2549 9.245 9.24488 11.255 6.75488 11.255Z' fill='%23000000'/%3E%3C/svg%3E%0A");mask-image:url("data:image/svg+xml,%3Csvg width='18' height='18' viewBox='0 0 18 18' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M12.7549 11.255H11.9649L11.6849 10.985C12.6649 9.845 13.2549 8.365 13.2549 6.755C13.2549 3.165 10.3449 0.255005 6.75488 0.255005C3.16488 0.255005 0.254883 3.165 0.254883 6.755C0.254883 10.345 3.16488 13.255 6.75488 13.255C8.36488 13.255 9.84488 12.665 10.9849 11.685L11.2549 11.965V12.755L16.2549 17.745L17.7449 16.255L12.7549 11.255ZM6.75488 11.255C4.26488 11.255 2.25488 9.245 2.25488 6.755C2.25488 4.26501 4.26488 2.255 6.75488 2.255C9.24488 2.255 11.2549 4.26501 11.2549 6.755C11.2549 9.245 9.24488 11.255 6.75488 11.255Z' fill='%23000000'/%3E%3C/svg%3E%0A");-webkit-mask-size:100%;mask-size:100%;z-index:9;pointer-events:none}.pagefind-ui__search-input.svelte-e9gkc3{height:calc(64px * var(--pagefind-ui-scale));padding:0 calc(70px * var(--pagefind-ui-scale)) 0 calc(54px * var(--pagefind-ui-scale));background-color:var(--pagefind-ui-background);border:var(--pagefind-ui-border-width) solid var(--pagefind-ui-border);border-radius:var(--pagefind-ui-border-radius);font-size:calc(21px * var(--pagefind-ui-scale));position:relative;appearance:none;-webkit-appearance:none;display:flex;width:100%;box-sizing:border-box;font-weight:700}.pagefind-ui__search-input.svelte-e9gkc3::placeholder{opacity:.2}.pagefind-ui__search-clear.svelte-e9gkc3{position:absolute;top:calc(3px * var(--pagefind-ui-scale));right:calc(3px * var(--pagefind-ui-scale));height:calc(58px * var(--pagefind-ui-scale));padding:0 calc(15px * var(--pagefind-ui-scale)) 0 calc(2px * var(--pagefind-ui-scale));color:var(--pagefind-ui-text);font-size:calc(14px * var(--pagefind-ui-scale));cursor:pointer;background-color:var(--pagefind-ui-background);border-radius:var(--pagefind-ui-border-radius)}.pagefind-ui__drawer.svelte-e9gkc3{gap:calc(60px * var(--pagefind-ui-scale));display:flex;flex-direction:row;flex-wrap:wrap}.pagefind-ui__results-area.svelte-e9gkc3{min-width:min(calc(400px * var(--pagefind-ui-scale)),100%);flex:1000;margin-top:calc(20px * var(--pagefind-ui-scale))}.pagefind-ui__results.svelte-e9gkc3{padding:0}.pagefind-ui__message.svelte-e9gkc3{box-sizing:content-box;font-size:calc(16px * var(--pagefind-ui-scale));height:calc(24px * var(--pagefind-ui-scale));padding:calc(20px * var(--pagefind-ui-scale)) 0;display:flex;align-items:center;font-weight:700;margin-top:0}.pagefind-ui__button.svelte-e9gkc3{margin-top:calc(40px * var(--pagefind-ui-scale));border:var(--pagefind-ui-border-width) solid var(--pagefind-ui-border);border-radius:var(--pagefind-ui-border-radius);height:calc(48px * var(--pagefind-ui-scale));padding:0 calc(12px * var(--pagefind-ui-scale));font-size:calc(16px * var(--pagefind-ui-scale));color:var(--pagefind-ui-primary);background:var(--pagefind-ui-background);width:100%;text-align:center;font-weight:700;cursor:pointer}.pagefind-ui__button.svelte-e9gkc3:hover{border-color:var(--pagefind-ui-primary);color:var(--pagefind-ui-primary);background:var(--pagefind-ui-background)} diff --git a/docs/pagefind/pagefind-ui.js b/docs/pagefind/pagefind-ui.js new file mode 100644 index 0000000..d88ad59 --- /dev/null +++ b/docs/pagefind/pagefind-ui.js @@ -0,0 +1,2 @@ +(()=>{var Ms=Object.defineProperty;var y=(n,e)=>{for(var t in e)Ms(n,t,{get:e[t],enumerable:!0})};function z(){}function mt(n){return n()}function gn(){return Object.create(null)}function G(n){n.forEach(mt)}function nt(n){return typeof n=="function"}function K(n,e){return n!=n?e==e:n!==e||n&&typeof n=="object"||typeof n=="function"}var et;function ie(n,e){return et||(et=document.createElement("a")),et.href=e,n===et.href}function En(n){return Object.keys(n).length===0}var Rn=typeof window<"u"?window:typeof globalThis<"u"?globalThis:global,de=class{constructor(e){this.options=e,this._listeners="WeakMap"in Rn?new WeakMap:void 0}observe(e,t){return this._listeners.set(e,t),this._getObserver().observe(e,this.options),()=>{this._listeners.delete(e),this._observer.unobserve(e)}}_getObserver(){var e;return(e=this._observer)!==null&&e!==void 0?e:this._observer=new ResizeObserver(t=>{var s;for(let r of t)de.entries.set(r.target,r),(s=this._listeners.get(r.target))===null||s===void 0||s(r)})}};de.entries="WeakMap"in Rn?new WeakMap:void 0;var bn=!1;function As(){bn=!0}function vs(){bn=!1}function b(n,e){n.appendChild(e)}function S(n,e,t){n.insertBefore(e,t||null)}function k(n){n.parentNode&&n.parentNode.removeChild(n)}function Q(n,e){for(let t=0;tn.removeEventListener(e,t,s)}function g(n,e,t){t==null?n.removeAttribute(e):n.getAttribute(e)!==t&&n.setAttribute(e,t)}function Hs(n){return Array.from(n.childNodes)}function N(n,e){e=""+e,n.data!==e&&(n.data=e)}function pt(n,e){n.value=e??""}function B(n,e,t){n.classList[t?"add":"remove"](e)}var st=class{constructor(e=!1){this.is_svg=!1,this.is_svg=e,this.e=this.n=null}c(e){this.h(e)}m(e,t,s=null){this.e||(this.is_svg?this.e=ws(t.nodeName):this.e=C(t.nodeType===11?"TEMPLATE":t.nodeName),this.t=t.tagName!=="TEMPLATE"?t:t.content,this.c(e)),this.i(s)}h(e){this.e.innerHTML=e,this.n=Array.from(this.e.nodeName==="TEMPLATE"?this.e.content.childNodes:this.e.childNodes)}i(e){for(let t=0;tn.indexOf(s)===-1?e.push(s):t.push(s)),t.forEach(s=>s()),re=e}var tt=new Set,ee;function ae(){ee={r:0,c:[],p:ee}}function oe(){ee.r||G(ee.c),ee=ee.p}function U(n,e){n&&n.i&&(tt.delete(n),n.i(e))}function P(n,e,t,s){if(n&&n.o){if(tt.has(n))return;tt.add(n),ee.c.push(()=>{tt.delete(n),s&&(t&&n.d(1),s())}),n.o(e)}else s&&s()}function Sn(n,e){P(n,1,1,()=>{e.delete(n.key)})}function yn(n,e,t,s,r,l,i,a,o,f,u,m){let p=n.length,h=l.length,_=p,c={};for(;_--;)c[n[_].key]=_;let d=[],T=new Map,R=new Map,M=[];for(_=h;_--;){let v=m(r,l,_),H=t(v),O=i.get(H);O?s&&M.push(()=>O.p(v,e)):(O=f(H,v),O.c()),T.set(H,d[_]=O),H in c&&R.set(H,Math.abs(_-c[H]))}let D=new Set,X=new Set;function V(v){U(v,1),v.m(a,u),i.set(v.key,v),u=v.first,h--}for(;p&&h;){let v=d[h-1],H=n[p-1],O=v.key,W=H.key;v===H?(u=v.first,p--,h--):T.has(W)?!i.has(O)||D.has(O)?V(v):X.has(W)?p--:R.get(O)>R.get(W)?(X.add(O),V(v)):(D.add(W),p--):(o(H,i),p--)}for(;p--;){let v=n[p];T.has(v.key)||o(v,i)}for(;h;)V(d[h-1]);return G(M),d}var zs=["allowfullscreen","allowpaymentrequest","async","autofocus","autoplay","checked","controls","default","defer","disabled","formnovalidate","hidden","inert","ismap","loop","multiple","muted","nomodule","novalidate","open","playsinline","readonly","required","reversed","selected"],Ua=new Set([...zs]);function Mn(n,e,t){let s=n.$$.props[e];s!==void 0&&(n.$$.bound[s]=t,t(n.$$.ctx[s]))}function rt(n){n&&n.c()}function me(n,e,t,s){let{fragment:r,after_update:l}=n.$$;r&&r.m(e,t),s||ht(()=>{let i=n.$$.on_mount.map(mt).filter(nt);n.$$.on_destroy?n.$$.on_destroy.push(...i):G(i),n.$$.on_mount=[]}),l.forEach(ht)}function ue(n,e){let t=n.$$;t.fragment!==null&&(js(t.after_update),G(t.on_destroy),t.fragment&&t.fragment.d(e),t.on_destroy=t.fragment=null,t.ctx=[])}function Us(n,e){n.$$.dirty[0]===-1&&(se.push(n),Ns(),n.$$.dirty.fill(0)),n.$$.dirty[e/31|0]|=1<{let _=h.length?h[0]:p;return f.ctx&&r(f.ctx[m],f.ctx[m]=_)&&(!f.skip_bound&&f.bound[m]&&f.bound[m](_),u&&Us(n,m)),p}):[],f.update(),u=!0,G(f.before_update),f.fragment=s?s(f.ctx):!1,e.target){if(e.hydrate){As();let m=Hs(e.target);f.fragment&&f.fragment.l(m),m.forEach(k)}else f.fragment&&f.fragment.c();e.intro&&U(n.$$.fragment),me(n,e.target,e.anchor,e.customElement),vs(),kn()}fe(o)}var Ds;typeof HTMLElement=="function"&&(Ds=class extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"})}connectedCallback(){let{on_mount:n}=this.$$;this.$$.on_disconnect=n.map(mt).filter(nt);for(let e in this.$$.slotted)this.appendChild(this.$$.slotted[e])}attributeChangedCallback(n,e,t){this[n]=t}disconnectedCallback(){G(this.$$.on_disconnect)}$destroy(){ue(this,1),this.$destroy=z}$on(n,e){if(!nt(e))return z;let t=this.$$.callbacks[n]||(this.$$.callbacks[n]=[]);return t.push(e),()=>{let s=t.indexOf(e);s!==-1&&t.splice(s,1)}}$set(n){this.$$set&&!En(n)&&(this.$$.skip_bound=!0,this.$$set(n),this.$$.skip_bound=!1)}});var q=class{$destroy(){ue(this,1),this.$destroy=z}$on(e,t){if(!nt(t))return z;let s=this.$$.callbacks[e]||(this.$$.callbacks[e]=[]);return s.push(t),()=>{let r=s.indexOf(t);r!==-1&&s.splice(r,1)}}$set(e){this.$$set&&!En(e)&&(this.$$.skip_bound=!0,this.$$set(e),this.$$.skip_bound=!1)}};function I(n){let e=typeof n=="string"?n.charCodeAt(0):n;return e>=97&&e<=122||e>=65&&e<=90}function $(n){let e=typeof n=="string"?n.charCodeAt(0):n;return e>=48&&e<=57}function Z(n){return I(n)||$(n)}var An=["art-lojban","cel-gaulish","no-bok","no-nyn","zh-guoyu","zh-hakka","zh-min","zh-min-nan","zh-xiang"];var Rt={"en-gb-oed":"en-GB-oxendict","i-ami":"ami","i-bnn":"bnn","i-default":null,"i-enochian":null,"i-hak":"hak","i-klingon":"tlh","i-lux":"lb","i-mingo":null,"i-navajo":"nv","i-pwn":"pwn","i-tao":"tao","i-tay":"tay","i-tsu":"tsu","sgn-be-fr":"sfb","sgn-be-nl":"vgt","sgn-ch-de":"sgg","art-lojban":"jbo","cel-gaulish":null,"no-bok":"nb","no-nyn":"nn","zh-guoyu":"cmn","zh-hakka":"hak","zh-min":null,"zh-min-nan":"nan","zh-xiang":"hsn"};var Is={}.hasOwnProperty;function lt(n,e={}){let t=vn(),s=String(n),r=s.toLowerCase(),l=0;if(n==null)throw new Error("Expected string, got `"+n+"`");if(Is.call(Rt,r)){let a=Rt[r];return(e.normalize===void 0||e.normalize===null||e.normalize)&&typeof a=="string"?lt(a):(t[An.includes(r)?"regular":"irregular"]=s,t)}for(;I(r.charCodeAt(l))&&l<9;)l++;if(l>1&&l<9){if(t.language=s.slice(0,l),l<4){let a=0;for(;r.charCodeAt(l)===45&&I(r.charCodeAt(l+1))&&I(r.charCodeAt(l+2))&&I(r.charCodeAt(l+3))&&!I(r.charCodeAt(l+4));){if(a>2)return i(l,3,"Too many extended language subtags, expected at most 3 subtags");t.extendedLanguageSubtags.push(s.slice(l+1,l+4)),l+=4,a++}}for(r.charCodeAt(l)===45&&I(r.charCodeAt(l+1))&&I(r.charCodeAt(l+2))&&I(r.charCodeAt(l+3))&&I(r.charCodeAt(l+4))&&!I(r.charCodeAt(l+5))&&(t.script=s.slice(l+1,l+5),l+=5),r.charCodeAt(l)===45&&(I(r.charCodeAt(l+1))&&I(r.charCodeAt(l+2))&&!I(r.charCodeAt(l+3))?(t.region=s.slice(l+1,l+3),l+=3):$(r.charCodeAt(l+1))&&$(r.charCodeAt(l+2))&&$(r.charCodeAt(l+3))&&!$(r.charCodeAt(l+4))&&(t.region=s.slice(l+1,l+4),l+=4));r.charCodeAt(l)===45;){let a=l+1,o=a;for(;Z(r.charCodeAt(o));){if(o-a>7)return i(o,1,"Too long variant, expected at most 8 characters");o++}if(o-a>4||o-a>3&&$(r.charCodeAt(a)))t.variants.push(s.slice(a,o)),l=o;else break}for(;r.charCodeAt(l)===45&&!(r.charCodeAt(l+1)===120||!Z(r.charCodeAt(l+1))||r.charCodeAt(l+2)!==45||!Z(r.charCodeAt(l+3)));){let a=l+2,o=0;for(;r.charCodeAt(a)===45&&Z(r.charCodeAt(a+1))&&Z(r.charCodeAt(a+2));){let f=a+1;for(a=f+2,o++;Z(r.charCodeAt(a));){if(a-f>7)return i(a,2,"Too long extension, expected at most 8 characters");a++}}if(!o)return i(a,4,"Empty extension, extensions must have at least 2 characters of content");t.extensions.push({singleton:s.charAt(l+1),extensions:s.slice(l+3,a).split("-")}),l=a}}else l=0;if(l===0&&r.charCodeAt(l)===120||r.charCodeAt(l)===45&&r.charCodeAt(l+1)===120){l=l?l+2:1;let a=l;for(;r.charCodeAt(a)===45&&Z(r.charCodeAt(a+1));){let o=l+1;for(a=o;Z(r.charCodeAt(a));){if(a-o>7)return i(a,5,"Too long private-use area, expected at most 8 characters");a++}t.privateuse.push(s.slice(l+1,a)),l=a}}if(l!==s.length)return i(l,6,"Found superfluous content after tag");return t;function i(a,o,f){return e.warning&&e.warning(f,o,a),e.forgiving?t:vn()}}function vn(){return{language:null,extendedLanguageSubtags:[],script:null,region:null,variants:[],extensions:[],privateuse:[],irregular:null,regular:null}}function wn(n,e,t){let s=n.slice();return s[8]=e[t][0],s[9]=e[t][1],s}function Ps(n){let e,t,s,r,l,i=n[0]&&Hn(n);return{c(){i&&i.c(),e=A(),t=C("div"),s=C("p"),s.textContent=`${n[3](30)}`,r=A(),l=C("p"),l.textContent=`${n[3](40)}`,g(s,"class","pagefind-ui__result-title pagefind-ui__loading svelte-j9e30"),g(l,"class","pagefind-ui__result-excerpt pagefind-ui__loading svelte-j9e30"),g(t,"class","pagefind-ui__result-inner svelte-j9e30")},m(a,o){i&&i.m(a,o),S(a,e,o),S(a,t,o),b(t,s),b(t,r),b(t,l)},p(a,o){a[0]?i||(i=Hn(a),i.c(),i.m(e.parentNode,e)):i&&(i.d(1),i=null)},d(a){i&&i.d(a),a&&k(e),a&&k(t)}}}function Ls(n){let e,t,s,r,l=n[1].meta?.title+"",i,a,o,f,u=n[1].excerpt+"",m,p=n[0]&&Fn(n),h=n[2].length&&On(n);return{c(){p&&p.c(),e=A(),t=C("div"),s=C("p"),r=C("a"),i=w(l),o=A(),f=C("p"),m=A(),h&&h.c(),g(r,"class","pagefind-ui__result-link svelte-j9e30"),g(r,"href",a=n[1].meta?.url||n[1].url),g(s,"class","pagefind-ui__result-title svelte-j9e30"),g(f,"class","pagefind-ui__result-excerpt svelte-j9e30"),g(t,"class","pagefind-ui__result-inner svelte-j9e30")},m(_,c){p&&p.m(_,c),S(_,e,c),S(_,t,c),b(t,s),b(s,r),b(r,i),b(t,o),b(t,f),f.innerHTML=u,b(t,m),h&&h.m(t,null)},p(_,c){_[0]?p?p.p(_,c):(p=Fn(_),p.c(),p.m(e.parentNode,e)):p&&(p.d(1),p=null),c&2&&l!==(l=_[1].meta?.title+"")&&N(i,l),c&2&&a!==(a=_[1].meta?.url||_[1].url)&&g(r,"href",a),c&2&&u!==(u=_[1].excerpt+"")&&(f.innerHTML=u),_[2].length?h?h.p(_,c):(h=On(_),h.c(),h.m(t,null)):h&&(h.d(1),h=null)},d(_){p&&p.d(_),_&&k(e),_&&k(t),h&&h.d()}}}function Hn(n){let e;return{c(){e=C("div"),g(e,"class","pagefind-ui__result-thumb pagefind-ui__loading svelte-j9e30")},m(t,s){S(t,e,s)},d(t){t&&k(e)}}}function Fn(n){let e,t=n[1].meta.image&&Nn(n);return{c(){e=C("div"),t&&t.c(),g(e,"class","pagefind-ui__result-thumb svelte-j9e30")},m(s,r){S(s,e,r),t&&t.m(e,null)},p(s,r){s[1].meta.image?t?t.p(s,r):(t=Nn(s),t.c(),t.m(e,null)):t&&(t.d(1),t=null)},d(s){s&&k(e),t&&t.d()}}}function Nn(n){let e,t,s;return{c(){e=C("img"),g(e,"class","pagefind-ui__result-image svelte-j9e30"),ie(e.src,t=n[1].meta?.image)||g(e,"src",t),g(e,"alt",s=n[1].meta?.image_alt||n[1].meta?.title)},m(r,l){S(r,e,l)},p(r,l){l&2&&!ie(e.src,t=r[1].meta?.image)&&g(e,"src",t),l&2&&s!==(s=r[1].meta?.image_alt||r[1].meta?.title)&&g(e,"alt",s)},d(r){r&&k(e)}}}function On(n){let e,t=n[2],s=[];for(let r=0;rn.toLocaleUpperCase();function Bs(n,e,t){let{show_images:s=!0}=e,{process_result:r=null}=e,{result:l={data:async()=>{}}}=e,i=["title","image","image_alt","url"],a,o=[],f=async m=>{t(1,a=await m.data()),t(1,a=r?.(a)??a),t(2,o=Object.entries(a.meta).filter(([p])=>!i.includes(p)))},u=(m=30)=>". ".repeat(Math.floor(10+Math.random()*m));return n.$$set=m=>{"show_images"in m&&t(0,s=m.show_images),"process_result"in m&&t(4,r=m.process_result),"result"in m&&t(5,l=m.result)},n.$$.update=()=>{if(n.$$.dirty&32)e:f(l)},[s,a,o,u,r,l]}var bt=class extends q{constructor(e){super(),Y(this,e,Bs,qs,K,{show_images:0,process_result:4,result:5})}},Un=bt;function Dn(n,e,t){let s=n.slice();return s[11]=e[t][0],s[12]=e[t][1],s}function In(n,e,t){let s=n.slice();return s[15]=e[t],s}function Vs(n){let e,t,s,r,l,i=n[0]&&Pn(n);return{c(){i&&i.c(),e=A(),t=C("div"),s=C("p"),s.textContent=`${n[5](30)}`,r=A(),l=C("p"),l.textContent=`${n[5](40)}`,g(s,"class","pagefind-ui__result-title pagefind-ui__loading svelte-4xnkmf"),g(l,"class","pagefind-ui__result-excerpt pagefind-ui__loading svelte-4xnkmf"),g(t,"class","pagefind-ui__result-inner svelte-4xnkmf")},m(a,o){i&&i.m(a,o),S(a,e,o),S(a,t,o),b(t,s),b(t,r),b(t,l)},p(a,o){a[0]?i||(i=Pn(a),i.c(),i.m(e.parentNode,e)):i&&(i.d(1),i=null)},d(a){i&&i.d(a),a&&k(e),a&&k(t)}}}function Ws(n){let e,t,s,r,l=n[1].meta?.title+"",i,a,o,f,u,m=n[0]&&Ln(n),p=n[4]&&Bn(n),h=n[3],_=[];for(let d=0;dn.toLocaleUpperCase();function Ks(n,e,t){let{show_images:s=!0}=e,{process_result:r=null}=e,{result:l={data:async()=>{}}}=e,i=["title","image","image_alt","url"],a,o=[],f=[],u=!1,m=(_,c)=>{if(_.length<=c)return _;let d=[..._].sort((T,R)=>R.locations.length-T.locations.length).slice(0,3).map(T=>T.url);return _.filter(T=>d.includes(T.url))},p=async _=>{t(1,a=await _.data()),t(1,a=r?.(a)??a),t(2,o=Object.entries(a.meta).filter(([c])=>!i.includes(c))),Array.isArray(a.sub_results)&&(t(4,u=a.sub_results?.[0]?.url===(a.meta?.url||a.url)),u?t(3,f=m(a.sub_results.slice(1),3)):t(3,f=m([...a.sub_results],3)))},h=(_=30)=>". ".repeat(Math.floor(10+Math.random()*_));return n.$$set=_=>{"show_images"in _&&t(0,s=_.show_images),"process_result"in _&&t(6,r=_.process_result),"result"in _&&t(7,l=_.result)},n.$$.update=()=>{if(n.$$.dirty&128)e:p(l)},[s,a,o,f,u,h,r,l]}var Tt=class extends q{constructor(e){super(),Y(this,e,Ks,Gs,K,{show_images:0,process_result:6,result:7})}},Jn=Tt;function Yn(n,e,t){let s=n.slice();return s[10]=e[t][0],s[11]=e[t][1],s[12]=e,s[13]=t,s}function Zn(n,e,t){let s=n.slice();return s[14]=e[t][0],s[15]=e[t][1],s[16]=e,s[17]=t,s}function Xn(n){let e,t,s=n[4]("filters_label",n[5],n[6])+"",r,l,i=Object.entries(n[1]),a=[];for(let o=0;on.toLocaleUpperCase(),ts=n=>n.toLowerCase();function Ys(n,e,t){let{available_filters:s=null}=e,{show_empty_filters:r=!0}=e,{open_filters:l=[]}=e,{translate:i=()=>""}=e,{automatic_translations:a={}}=e,{translations:o={}}=e,{selected_filters:f={}}=e,u=!1,m=!1;function p(h,_){f[`${h}:${_}`]=this.checked,t(0,f)}return n.$$set=h=>{"available_filters"in h&&t(1,s=h.available_filters),"show_empty_filters"in h&&t(2,r=h.show_empty_filters),"open_filters"in h&&t(3,l=h.open_filters),"translate"in h&&t(4,i=h.translate),"automatic_translations"in h&&t(5,a=h.automatic_translations),"translations"in h&&t(6,o=h.translations),"selected_filters"in h&&t(0,f=h.selected_filters)},n.$$.update=()=>{if(n.$$.dirty&258){e:if(s&&!u){t(8,u=!0);let h=Object.entries(s||{});h.length===1&&Object.entries(h[0][1])?.length<=6&&t(7,m=!0)}}},[f,s,r,l,i,a,o,m,u,p]}var Ct=class extends q{constructor(e){super(),Y(this,e,Ys,Js,K,{available_filters:1,show_empty_filters:2,open_filters:3,translate:4,automatic_translations:5,translations:6,selected_filters:0})}},ns=Ct;var kt={};y(kt,{comments:()=>Xs,default:()=>$s,direction:()=>Qs,strings:()=>xs,thanks_to:()=>Zs});var Zs="Jan Claasen ",Xs="",Qs="ltr",xs={placeholder:"Soek",clear_search:"Opruim",load_more:"Laai nog resultate",search_label:"Soek hierdie webwerf",filters_label:"Filters",zero_results:"Geen resultate vir [SEARCH_TERM]",many_results:"[COUNT] resultate vir [SEARCH_TERM]",one_result:"[COUNT] resultate vir [SEARCH_TERM]",alt_search:"Geen resultate vir [SEARCH_TERM]. Toon resultate vir [DIFFERENT_TERM] in plaas daarvan",search_suggestion:"Geen resultate vir [SEARCH_TERM]. Probeer eerder een van die volgende terme:",searching:"Soek vir [SEARCH_TERM]"},$s={thanks_to:Zs,comments:Xs,direction:Qs,strings:xs};var St={};y(St,{comments:()=>tr,default:()=>rr,direction:()=>nr,strings:()=>sr,thanks_to:()=>er});var er="Jermanuts",tr="",nr="rtl",sr={placeholder:"\u0628\u062D\u062B",clear_search:"\u0627\u0645\u0633\u062D",load_more:"\u062D\u0645\u0651\u0650\u0644 \u0627\u0644\u0645\u0632\u064A\u062F \u0645\u0646 \u0627\u0644\u0646\u062A\u0627\u0626\u062C",search_label:"\u0627\u0628\u062D\u062B \u0641\u064A \u0647\u0630\u0627 \u0627\u0644\u0645\u0648\u0642\u0639",filters_label:"\u062A\u0635\u0641\u064A\u0627\u062A",zero_results:"\u0644\u0627 \u062A\u0648\u062C\u062F \u0646\u062A\u0627\u0626\u062C \u0644 [SEARCH_TERM]",many_results:"[COUNT] \u0646\u062A\u0627\u0626\u062C \u0644 [SEARCH_TERM]",one_result:"[COUNT] \u0646\u062A\u064A\u062C\u0629 \u0644 [SEARCH_TERM]",alt_search:"\u0644\u0627 \u062A\u0648\u062C\u062F \u0646\u062A\u0627\u0626\u062C \u0644 [SEARCH_TERM]. \u064A\u0639\u0631\u0636 \u0627\u0644\u0646\u062A\u0627\u0626\u062C \u0644 [DIFFERENT_TERM] \u0628\u062F\u0644\u0627\u064B \u0645\u0646 \u0630\u0644\u0643",search_suggestion:"\u0644\u0627 \u062A\u0648\u062C\u062F \u0646\u062A\u0627\u0626\u062C \u0644 [SEARCH_TERM]. \u062C\u0631\u0628 \u0623\u062D\u062F \u0639\u0645\u0644\u064A\u0627\u062A \u0627\u0644\u0628\u062D\u062B \u0627\u0644\u062A\u0627\u0644\u064A\u0629:",searching:"\u064A\u0628\u062D\u062B \u0639\u0646 [SEARCH_TERM]..."},rr={thanks_to:er,comments:tr,direction:nr,strings:sr};var yt={};y(yt,{comments:()=>ir,default:()=>ur,direction:()=>ar,strings:()=>or,thanks_to:()=>lr});var lr="Maruf Alom ",ir="",ar="ltr",or={placeholder:"\u0985\u09A8\u09C1\u09B8\u09A8\u09CD\u09A7\u09BE\u09A8 \u0995\u09B0\u09C1\u09A8",clear_search:"\u09AE\u09C1\u099B\u09C7 \u09AB\u09C7\u09B2\u09C1\u09A8",load_more:"\u0986\u09B0\u09CB \u09AB\u09B2\u09BE\u09AB\u09B2 \u09A6\u09C7\u0996\u09C1\u09A8",search_label:"\u098F\u0987 \u0993\u09DF\u09C7\u09AC\u09B8\u09BE\u0987\u099F\u09C7 \u0985\u09A8\u09C1\u09B8\u09A8\u09CD\u09A7\u09BE\u09A8 \u0995\u09B0\u09C1\u09A8",filters_label:"\u09AB\u09BF\u09B2\u09CD\u099F\u09BE\u09B0",zero_results:"[SEARCH_TERM] \u098F\u09B0 \u099C\u09A8\u09CD\u09AF \u0995\u09BF\u099B\u09C1 \u0996\u09C1\u0981\u099C\u09C7 \u09AA\u09BE\u0993\u09DF\u09BE \u09AF\u09BE\u09DF\u09A8\u09BF",many_results:"[COUNT]-\u099F\u09BF \u09AB\u09B2\u09BE\u09AB\u09B2 \u09AA\u09BE\u0993\u09DF\u09BE \u0997\u09BF\u09DF\u09C7\u099B\u09C7 [SEARCH_TERM] \u098F\u09B0 \u099C\u09A8\u09CD\u09AF",one_result:"[COUNT]-\u099F\u09BF \u09AB\u09B2\u09BE\u09AB\u09B2 \u09AA\u09BE\u0993\u09DF\u09BE \u0997\u09BF\u09DF\u09C7\u099B\u09C7 [SEARCH_TERM] \u098F\u09B0 \u099C\u09A8\u09CD\u09AF",alt_search:"\u0995\u09CB\u09A8 \u0995\u09BF\u099B\u09C1 \u0996\u09C1\u0981\u099C\u09C7 \u09AA\u09BE\u0993\u09DF\u09BE \u09AF\u09BE\u09DF\u09A8\u09BF [SEARCH_TERM] \u098F\u09B0 \u099C\u09A8\u09CD\u09AF. \u09AA\u09B0\u09BF\u09AC\u09B0\u09CD\u09A4\u09C7 [DIFFERENT_TERM] \u098F\u09B0 \u099C\u09A8\u09CD\u09AF \u09A6\u09C7\u0996\u09BE\u09A8\u09CB \u09B9\u099A\u09CD\u099B\u09C7",search_suggestion:"\u0995\u09CB\u09A8 \u0995\u09BF\u099B\u09C1 \u0996\u09C1\u0981\u099C\u09C7 \u09AA\u09BE\u0993\u09DF\u09BE \u09AF\u09BE\u09DF\u09A8\u09BF [SEARCH_TERM] \u098F\u09B0 \u09AC\u09BF\u09B7\u09DF\u09C7. \u09A8\u09BF\u09A8\u09CD\u09AE\u09C7\u09B0 \u09AC\u09BF\u09B7\u09DF\u09AC\u09B8\u09CD\u09A4\u09C1 \u0996\u09C1\u0981\u099C\u09C7 \u09A6\u09C7\u0996\u09C1\u09A8:",searching:"\u0985\u09A8\u09C1\u09B8\u09A8\u09CD\u09A7\u09BE\u09A8 \u099A\u09B2\u099B\u09C7 [SEARCH_TERM]..."},ur={thanks_to:lr,comments:ir,direction:ar,strings:or};var Mt={};y(Mt,{comments:()=>_r,default:()=>hr,direction:()=>fr,strings:()=>dr,thanks_to:()=>cr});var cr="Pablo Villaverde ",_r="",fr="ltr",dr={placeholder:"Cerca",clear_search:"Netejar",load_more:"Veure m\xE9s resultats",search_label:"Cerca en aquest lloc",filters_label:"Filtres",zero_results:"No es van trobar resultats per [SEARCH_TERM]",many_results:"[COUNT] resultats trobats per [SEARCH_TERM]",one_result:"[COUNT] resultat trobat per [SEARCH_TERM]",alt_search:"No es van trobar resultats per [SEARCH_TERM]. Mostrant al seu lloc resultats per [DIFFERENT_TERM]",search_suggestion:"No es van trobar resultats per [SEARCH_TERM]. Proveu una de les cerques seg\xFCents:",searching:"Cercant [SEARCH_TERM]..."},hr={thanks_to:cr,comments:_r,direction:fr,strings:dr};var At={};y(At,{comments:()=>pr,default:()=>Rr,direction:()=>gr,strings:()=>Er,thanks_to:()=>mr});var mr="Dalibor Hon ",pr="",gr="ltr",Er={placeholder:"Hledat",clear_search:"Smazat",load_more:"Na\u010D\xEDst dal\u0161\xED v\xFDsledky",search_label:"Prohledat tuto str\xE1nku",filters_label:"Filtry",zero_results:"\u017D\xE1dn\xE9 v\xFDsledky pro [SEARCH_TERM]",many_results:"[COUNT] v\xFDsledk\u016F pro [SEARCH_TERM]",one_result:"[COUNT] v\xFDsledek pro [SEARCH_TERM]",alt_search:"\u017D\xE1dn\xE9 v\xFDsledky pro [SEARCH_TERM]. Zobrazuj\xED se v\xFDsledky pro [DIFFERENT_TERM]",search_suggestion:"\u017D\xE1dn\xE9 v\xFDsledky pro [SEARCH_TERM]. Souvisej\xEDc\xED v\xFDsledky hled\xE1n\xED:",searching:"Hled\xE1m [SEARCH_TERM]..."},Rr={thanks_to:mr,comments:pr,direction:gr,strings:Er};var vt={};y(vt,{comments:()=>Tr,default:()=>Sr,direction:()=>Cr,strings:()=>kr,thanks_to:()=>br});var br="Jonas Smedegaard ",Tr="",Cr="ltr",kr={placeholder:"S\xF8g",clear_search:"Nulstil",load_more:"Indl\xE6s flere resultater",search_label:"S\xF8g p\xE5 dette website",filters_label:"Filtre",zero_results:"Ingen resultater for [SEARCH_TERM]",many_results:"[COUNT] resultater for [SEARCH_TERM]",one_result:"[COUNT] resultat for [SEARCH_TERM]",alt_search:"Ingen resultater for [SEARCH_TERM]. Viser resultater for [DIFFERENT_TERM] i stedet",search_suggestion:"Ingen resultater for [SEARCH_TERM]. Pr\xF8v et af disse s\xF8geord i stedet:",searching:"S\xF8ger efter [SEARCH_TERM]..."},Sr={thanks_to:br,comments:Tr,direction:Cr,strings:kr};var wt={};y(wt,{comments:()=>Mr,default:()=>wr,direction:()=>Ar,strings:()=>vr,thanks_to:()=>yr});var yr="Jan Claasen ",Mr="",Ar="ltr",vr={placeholder:"Suche",clear_search:"L\xF6schen",load_more:"Mehr Ergebnisse laden",search_label:"Suche diese Seite",filters_label:"Filter",zero_results:"Keine Ergebnisse f\xFCr [SEARCH_TERM]",many_results:"[COUNT] Ergebnisse f\xFCr [SEARCH_TERM]",one_result:"[COUNT] Ergebnis f\xFCr [SEARCH_TERM]",alt_search:"Keine Ergebnisse f\xFCr [SEARCH_TERM]. Stattdessen werden Ergebnisse f\xFCr [DIFFERENT_TERM] angezeigt",search_suggestion:"Keine Ergebnisse f\xFCr [SEARCH_TERM]. Versuchen Sie eine der folgenden Suchen:",searching:"Suche f\xFCr [SEARCH_TERM]"},wr={thanks_to:yr,comments:Mr,direction:Ar,strings:vr};var Ht={};y(Ht,{comments:()=>Fr,default:()=>jr,direction:()=>Nr,strings:()=>Or,thanks_to:()=>Hr});var Hr="Liam Bigelow ",Fr="",Nr="ltr",Or={placeholder:"Search",clear_search:"Clear",load_more:"Load more results",search_label:"Search this site",filters_label:"Filters",zero_results:"No results for [SEARCH_TERM]",many_results:"[COUNT] results for [SEARCH_TERM]",one_result:"[COUNT] result for [SEARCH_TERM]",alt_search:"No results for [SEARCH_TERM]. Showing results for [DIFFERENT_TERM] instead",search_suggestion:"No results for [SEARCH_TERM]. Try one of the following searches:",searching:"Searching for [SEARCH_TERM]..."},jr={thanks_to:Hr,comments:Fr,direction:Nr,strings:Or};var Ft={};y(Ft,{comments:()=>Ur,default:()=>Pr,direction:()=>Dr,strings:()=>Ir,thanks_to:()=>zr});var zr="Pablo Villaverde ",Ur="",Dr="ltr",Ir={placeholder:"Buscar",clear_search:"Limpiar",load_more:"Ver m\xE1s resultados",search_label:"Buscar en este sitio",filters_label:"Filtros",zero_results:"No se encontraron resultados para [SEARCH_TERM]",many_results:"[COUNT] resultados encontrados para [SEARCH_TERM]",one_result:"[COUNT] resultado encontrado para [SEARCH_TERM]",alt_search:"No se encontraron resultados para [SEARCH_TERM]. Mostrando en su lugar resultados para [DIFFERENT_TERM]",search_suggestion:"No se encontraron resultados para [SEARCH_TERM]. Prueba una de las siguientes b\xFAsquedas:",searching:"Buscando [SEARCH_TERM]..."},Pr={thanks_to:zr,comments:Ur,direction:Dr,strings:Ir};var Nt={};y(Nt,{comments:()=>qr,default:()=>Wr,direction:()=>Br,strings:()=>Vr,thanks_to:()=>Lr});var Lr="Ali Khaleqi Yekta ",qr="",Br="rtl",Vr={placeholder:"\u062C\u0633\u062A\u062C\u0648",clear_search:"\u067E\u0627\u06A9\u0633\u0627\u0632\u06CC",load_more:"\u0628\u0627\u0631\u06AF\u0630\u0627\u0631\u06CC \u0646\u062A\u0627\u06CC\u062C \u0628\u06CC\u0634\u062A\u0631",search_label:"\u062C\u0633\u062A\u062C\u0648 \u062F\u0631 \u0633\u0627\u06CC\u062A",filters_label:"\u0641\u06CC\u0644\u062A\u0631\u0647\u0627",zero_results:"\u0646\u062A\u06CC\u062C\u0647\u200C\u0627\u06CC \u0628\u0631\u0627\u06CC [SEARCH_TERM] \u06CC\u0627\u0641\u062A \u0646\u0634\u062F",many_results:"[COUNT] \u0646\u062A\u06CC\u062C\u0647 \u0628\u0631\u0627\u06CC [SEARCH_TERM] \u06CC\u0627\u0641\u062A \u0634\u062F",one_result:"[COUNT] \u0646\u062A\u06CC\u062C\u0647 \u0628\u0631\u0627\u06CC [SEARCH_TERM] \u06CC\u0627\u0641\u062A \u0634\u062F",alt_search:"\u0646\u062A\u06CC\u062C\u0647\u200C\u0627\u06CC \u0628\u0631\u0627\u06CC [SEARCH_TERM] \u06CC\u0627\u0641\u062A \u0646\u0634\u062F. \u062F\u0631 \u0639\u0648\u0636 \u0646\u062A\u0627\u06CC\u062C \u0628\u0631\u0627\u06CC [DIFFERENT_TERM] \u0646\u0645\u0627\u06CC\u0634 \u062F\u0627\u062F\u0647 \u0645\u06CC\u200C\u0634\u0648\u062F",search_suggestion:"\u0646\u062A\u06CC\u062C\u0647\u200C\u0627\u06CC \u0628\u0631\u0627\u06CC [SEARCH_TERM] \u06CC\u0627\u0641\u062A \u0646\u0634\u062F. \u06CC\u06A9\u06CC \u0627\u0632 \u062C\u0633\u062A\u062C\u0648\u0647\u0627\u06CC \u0632\u06CC\u0631 \u0631\u0627 \u0627\u0645\u062A\u062D\u0627\u0646 \u06A9\u0646\u06CC\u062F:",searching:"\u062F\u0631 \u062D\u0627\u0644 \u062C\u0633\u062A\u062C\u0648\u06CC [SEARCH_TERM]..."},Wr={thanks_to:Lr,comments:qr,direction:Br,strings:Vr};var Ot={};y(Ot,{comments:()=>Kr,default:()=>Zr,direction:()=>Jr,strings:()=>Yr,thanks_to:()=>Gr});var Gr="Valtteri Laitinen ",Kr="",Jr="ltr",Yr={placeholder:"Haku",clear_search:"Tyhjenn\xE4",load_more:"Lataa lis\xE4\xE4 tuloksia",search_label:"Hae t\xE4lt\xE4 sivustolta",filters_label:"Suodattimet",zero_results:"Ei tuloksia haulle [SEARCH_TERM]",many_results:"[COUNT] tulosta haulle [SEARCH_TERM]",one_result:"[COUNT] tulos haulle [SEARCH_TERM]",alt_search:"Ei tuloksia haulle [SEARCH_TERM]. N\xE4ytet\xE4\xE4n tulokset sen sijaan haulle [DIFFERENT_TERM]",search_suggestion:"Ei tuloksia haulle [SEARCH_TERM]. Kokeile jotain seuraavista:",searching:"Haetaan [SEARCH_TERM]..."},Zr={thanks_to:Gr,comments:Kr,direction:Jr,strings:Yr};var jt={};y(jt,{comments:()=>Qr,default:()=>el,direction:()=>xr,strings:()=>$r,thanks_to:()=>Xr});var Xr="Nicolas Friedli ",Qr="",xr="ltr",$r={placeholder:"Rechercher",clear_search:"Nettoyer",load_more:"Charger plus de r\xE9sultats",search_label:"Recherche sur ce site",filters_label:"Filtres",zero_results:"Pas de r\xE9sultat pour [SEARCH_TERM]",many_results:"[COUNT] r\xE9sultats pour [SEARCH_TERM]",one_result:"[COUNT] r\xE9sultat pour [SEARCH_TERM]",alt_search:"Pas de r\xE9sultat pour [SEARCH_TERM]. Montre les r\xE9sultats pour [DIFFERENT_TERM] \xE0 la place",search_suggestion:"Pas de r\xE9sultat pour [SEARCH_TERM]. Essayer une des recherches suivantes:",searching:"Recherche [SEARCH_TERM]..."},el={thanks_to:Xr,comments:Qr,direction:xr,strings:$r};var zt={};y(zt,{comments:()=>nl,default:()=>ll,direction:()=>sl,strings:()=>rl,thanks_to:()=>tl});var tl="Pablo Villaverde ",nl="",sl="ltr",rl={placeholder:"Buscar",clear_search:"Limpar",load_more:"Ver m\xE1is resultados",search_label:"Buscar neste sitio",filters_label:"Filtros",zero_results:"Non se atoparon resultados para [SEARCH_TERM]",many_results:"[COUNT] resultados atopados para [SEARCH_TERM]",one_result:"[COUNT] resultado atopado para [SEARCH_TERM]",alt_search:"Non se atoparon resultados para [SEARCH_TERM]. Amosando no seu lugar resultados para [DIFFERENT_TERM]",search_suggestion:"Non se atoparon resultados para [SEARCH_TERM]. Probe unha das seguintes pesquisas:",searching:"Buscando [SEARCH_TERM]..."},ll={thanks_to:tl,comments:nl,direction:sl,strings:rl};var Ut={};y(Ut,{comments:()=>al,default:()=>cl,direction:()=>ol,strings:()=>ul,thanks_to:()=>il});var il="Nir Tamir ",al="",ol="rtl",ul={placeholder:"\u05D7\u05D9\u05E4\u05D5\u05E9",clear_search:"\u05E0\u05D9\u05E7\u05D5\u05D9",load_more:"\u05E2\u05D5\u05D3 \u05EA\u05D5\u05E6\u05D0\u05D5\u05EA",search_label:"\u05D7\u05D9\u05E4\u05D5\u05E9 \u05D1\u05D0\u05EA\u05E8 \u05D6\u05D4",filters_label:"\u05DE\u05E1\u05E0\u05E0\u05D9\u05DD",zero_results:"\u05DC\u05D0 \u05E0\u05DE\u05E6\u05D0\u05D5 \u05EA\u05D5\u05E6\u05D0\u05D5\u05EA \u05E2\u05D1\u05D5\u05E8 [SEARCH_TERM]",many_results:"\u05E0\u05DE\u05E6\u05D0\u05D5 [COUNT] \u05EA\u05D5\u05E6\u05D0\u05D5\u05EA \u05E2\u05D1\u05D5\u05E8 [SEARCH_TERM]",one_result:"\u05E0\u05DE\u05E6\u05D0\u05D4 \u05EA\u05D5\u05E6\u05D0\u05D4 \u05D0\u05D7\u05EA \u05E2\u05D1\u05D5\u05E8 [SEARCH_TERM]",alt_search:"\u05DC\u05D0 \u05E0\u05DE\u05E6\u05D0\u05D5 \u05EA\u05D5\u05E6\u05D0\u05D5\u05EA \u05E2\u05D1\u05D5\u05E8 [SEARCH_TERM]. \u05DE\u05D5\u05E6\u05D2\u05D5\u05EA \u05EA\u05D5\u05E6\u05D0\u05D5\u05EA \u05E2\u05D1\u05D5\u05E8 [DIFFERENT_TERM]",search_suggestion:"\u05DC\u05D0 \u05E0\u05DE\u05E6\u05D0\u05D5 \u05EA\u05D5\u05E6\u05D0\u05D5\u05EA \u05E2\u05D1\u05D5\u05E8 [SEARCH_TERM]. \u05E0\u05E1\u05D5 \u05D0\u05D7\u05D3 \u05DE\u05D4\u05D7\u05D9\u05E4\u05D5\u05E9\u05D9\u05DD \u05D4\u05D1\u05D0\u05D9\u05DD:",searching:"\u05DE\u05D7\u05E4\u05E9 \u05D0\u05EA [SEARCH_TERM]..."},cl={thanks_to:il,comments:al,direction:ol,strings:ul};var Dt={};y(Dt,{comments:()=>fl,default:()=>ml,direction:()=>dl,strings:()=>hl,thanks_to:()=>_l});var _l="Amit Yadav ",fl="",dl="ltr",hl={placeholder:"\u0916\u094B\u091C\u0947\u0902",clear_search:"\u0938\u093E\u092B \u0915\u0930\u0947\u0902",load_more:"\u0914\u0930 \u0905\u0927\u093F\u0915 \u092A\u0930\u093F\u0923\u093E\u092E \u0932\u094B\u0921 \u0915\u0930\u0947\u0902",search_label:"\u0907\u0938 \u0938\u093E\u0907\u091F \u092E\u0947\u0902 \u0916\u094B\u091C\u0947\u0902",filters_label:"\u092B\u093C\u093F\u0932\u094D\u091F\u0930",zero_results:"\u0915\u094B\u0908 \u092A\u0930\u093F\u0923\u093E\u092E [SEARCH_TERM] \u0915\u0947 \u0932\u093F\u090F \u0928\u0939\u0940\u0902 \u092E\u093F\u0932\u093E",many_results:"[COUNT] \u092A\u0930\u093F\u0923\u093E\u092E [SEARCH_TERM] \u0915\u0947 \u0932\u093F\u090F \u092E\u093F\u0932\u0947",one_result:"[COUNT] \u092A\u0930\u093F\u0923\u093E\u092E [SEARCH_TERM] \u0915\u0947 \u0932\u093F\u090F \u092E\u093F\u0932\u093E",alt_search:"[SEARCH_TERM] \u0915\u0947 \u0932\u093F\u090F \u0915\u094B\u0908 \u092A\u0930\u093F\u0923\u093E\u092E \u0928\u0939\u0940\u0902 \u092E\u093F\u0932\u093E\u0964 \u0907\u0938\u0915\u0947 \u092C\u091C\u093E\u092F [DIFFERENT_TERM] \u0915\u0947 \u0932\u093F\u090F \u092A\u0930\u093F\u0923\u093E\u092E \u0926\u093F\u0916\u093E \u0930\u0939\u093E \u0939\u0948",search_suggestion:"[SEARCH_TERM] \u0915\u0947 \u0932\u093F\u090F \u0915\u094B\u0908 \u092A\u0930\u093F\u0923\u093E\u092E \u0928\u0939\u0940\u0902 \u092E\u093F\u0932\u093E\u0964 \u0928\u093F\u092E\u094D\u0928\u0932\u093F\u0916\u093F\u0924 \u0916\u094B\u091C\u094B\u0902 \u092E\u0947\u0902 \u0938\u0947 \u0915\u094B\u0908 \u090F\u0915 \u0906\u091C\u093C\u092E\u093E\u090F\u0902:",searching:"[SEARCH_TERM] \u0915\u0940 \u0916\u094B\u091C \u0915\u0940 \u091C\u093E \u0930\u0939\u0940 \u0939\u0948..."},ml={thanks_to:_l,comments:fl,direction:dl,strings:hl};var It={};y(It,{comments:()=>gl,default:()=>bl,direction:()=>El,strings:()=>Rl,thanks_to:()=>pl});var pl="Diomed ",gl="",El="ltr",Rl={placeholder:"Tra\u017Ei",clear_search:"O\u010Disti",load_more:"U\u010Ditaj vi\u0161e rezultata",search_label:"Pretra\u017Ei ovu stranicu",filters_label:"Filteri",zero_results:"Nema rezultata za [SEARCH_TERM]",many_results:"[COUNT] rezultata za [SEARCH_TERM]",one_result:"[COUNT] rezultat za [SEARCH_TERM]",alt_search:"Nema rezultata za [SEARCH_TERM]. Prikazujem rezultate za [DIFFERENT_TERM]",search_suggestion:"Nema rezultata za [SEARCH_TERM]. Poku\u0161aj s jednom od ovih pretraga:",searching:"Pretra\u017Eujem [SEARCH_TERM]..."},bl={thanks_to:pl,comments:gl,direction:El,strings:Rl};var Pt={};y(Pt,{comments:()=>Cl,default:()=>yl,direction:()=>kl,strings:()=>Sl,thanks_to:()=>Tl});var Tl="Adam Laki ",Cl="",kl="ltr",Sl={placeholder:"Keres\xE9s",clear_search:"T\xF6rl\xE9s",load_more:"Tov\xE1bbi tal\xE1latok bet\xF6lt\xE9se",search_label:"Keres\xE9s az oldalon",filters_label:"Sz\u0171r\xE9s",zero_results:"Nincs tal\xE1lat a(z) [SEARCH_TERM] kifejez\xE9sre",many_results:"[COUNT] db tal\xE1lat a(z) [SEARCH_TERM] kifejez\xE9sre",one_result:"[COUNT] db tal\xE1lat a(z) [SEARCH_TERM] kifejez\xE9sre",alt_search:"Nincs tal\xE1lat a(z) [SEARCH_TERM] kifejez\xE9sre. Tal\xE1latok mutat\xE1sa ink\xE1bb a(z) [DIFFERENT_TERM] kifejez\xE9sre",search_suggestion:"Nincs tal\xE1lat a(z) [SEARCH_TERM] kifejez\xE9sre. Pr\xF3b\xE1ld meg a k\xF6vetkez\u0151 keres\xE9sek egyik\xE9t:",searching:"Keres\xE9s a(z) [SEARCH_TERM] kifejez\xE9sre..."},yl={thanks_to:Tl,comments:Cl,direction:kl,strings:Sl};var Lt={};y(Lt,{comments:()=>Al,default:()=>Hl,direction:()=>vl,strings:()=>wl,thanks_to:()=>Ml});var Ml="Nixentric",Al="",vl="ltr",wl={placeholder:"Cari",clear_search:"Bersihkan",load_more:"Muat lebih banyak hasil",search_label:"Telusuri situs ini",filters_label:"Filter",zero_results:"[SEARCH_TERM] tidak ditemukan",many_results:"Ditemukan [COUNT] hasil untuk [SEARCH_TERM]",one_result:"Ditemukan [COUNT] hasil untuk [SEARCH_TERM]",alt_search:"[SEARCH_TERM] tidak ditemukan. Menampilkan hasil [DIFFERENT_TERM] sebagai gantinya",search_suggestion:"[SEARCH_TERM] tidak ditemukan. Coba salah satu pencarian berikut ini:",searching:"Mencari [SEARCH_TERM]..."},Hl={thanks_to:Ml,comments:Al,direction:vl,strings:wl};var qt={};y(qt,{comments:()=>Nl,default:()=>zl,direction:()=>Ol,strings:()=>jl,thanks_to:()=>Fl});var Fl="Cosette Bruhns Alonso, Andrew Janco ",Nl="",Ol="ltr",jl={placeholder:"Cerca",clear_search:"Cancella la cronologia",load_more:"Mostra pi\xF9 risultati",search_label:"Cerca nel sito",filters_label:"Filtri di ricerca",zero_results:"Nessun risultato per [SEARCH_TERM]",many_results:"[COUNT] risultati per [SEARCH_TERM]",one_result:"[COUNT] risultato per [SEARCH_TERM]",alt_search:"Nessun risultato per [SEARCH_TERM]. Mostrando risultati per [DIFFERENT_TERM] come alternativa.",search_suggestion:"Nessun risultato per [SEARCH_TERM]. Prova una delle seguenti ricerche:",searching:"Cercando [SEARCH_TERM]..."},zl={thanks_to:Fl,comments:Nl,direction:Ol,strings:jl};var Bt={};y(Bt,{comments:()=>Dl,default:()=>Ll,direction:()=>Il,strings:()=>Pl,thanks_to:()=>Ul});var Ul="Tate",Dl="",Il="ltr",Pl={placeholder:"\u691C\u7D22",clear_search:"\u30AF\u30EA\u30A2",load_more:"\u6B21\u3092\u8AAD\u307F\u8FBC\u3080",search_label:"\u3053\u306E\u30B5\u30A4\u30C8\u3092\u691C\u7D22",filters_label:"\u30D5\u30A3\u30EB\u30BF",zero_results:"[SEARCH_TERM]\u306E\u691C\u7D22\u306B\u4E00\u81F4\u3059\u308B\u60C5\u5831\u306F\u3042\u308A\u307E\u305B\u3093\u3067\u3057\u305F",many_results:"[SEARCH_TERM]\u306E[COUNT]\u4EF6\u306E\u691C\u7D22\u7D50\u679C",one_result:"[SEARCH_TERM]\u306E[COUNT]\u4EF6\u306E\u691C\u7D22\u7D50\u679C",alt_search:"[SEARCH_TERM]\u306E\u691C\u7D22\u306B\u4E00\u81F4\u3059\u308B\u60C5\u5831\u306F\u3042\u308A\u307E\u305B\u3093\u3067\u3057\u305F\u3002[DIFFERENT_TERM]\u306E\u691C\u7D22\u7D50\u679C\u3092\u8868\u793A\u3057\u3066\u3044\u307E\u3059",search_suggestion:"[SEARCH_TERM]\u306E\u691C\u7D22\u306B\u4E00\u81F4\u3059\u308B\u60C5\u5831\u306F\u3042\u308A\u307E\u305B\u3093\u3067\u3057\u305F\u3002\u6B21\u306E\u3044\u305A\u308C\u304B\u306E\u691C\u7D22\u3092\u8A66\u3057\u3066\u304F\u3060\u3055\u3044",searching:"[SEARCH_TERM]\u3092\u691C\u7D22\u3057\u3066\u3044\u307E\u3059"},Ll={thanks_to:Ul,comments:Dl,direction:Il,strings:Pl};var Vt={};y(Vt,{comments:()=>Bl,default:()=>Gl,direction:()=>Vl,strings:()=>Wl,thanks_to:()=>ql});var ql="Seokho Son ",Bl="",Vl="ltr",Wl={placeholder:"\uAC80\uC0C9\uC5B4",clear_search:"\uBE44\uC6B0\uAE30",load_more:"\uAC80\uC0C9 \uACB0\uACFC \uB354 \uBCF4\uAE30",search_label:"\uC0AC\uC774\uD2B8 \uAC80\uC0C9",filters_label:"\uD544\uD130",zero_results:"[SEARCH_TERM]\uC5D0 \uB300\uD55C \uACB0\uACFC \uC5C6\uC74C",many_results:"[SEARCH_TERM]\uC5D0 \uB300\uD55C \uACB0\uACFC [COUNT]\uAC74",one_result:"[SEARCH_TERM]\uC5D0 \uB300\uD55C \uACB0\uACFC [COUNT]\uAC74",alt_search:"[SEARCH_TERM]\uC5D0 \uB300\uD55C \uACB0\uACFC \uC5C6\uC74C. [DIFFERENT_TERM]\uC5D0 \uB300\uD55C \uACB0\uACFC",search_suggestion:"[SEARCH_TERM]\uC5D0 \uB300\uD55C \uACB0\uACFC \uC5C6\uC74C. \uCD94\uCC9C \uAC80\uC0C9\uC5B4: ",searching:"[SEARCH_TERM] \uAC80\uC0C9 \uC911..."},Gl={thanks_to:ql,comments:Bl,direction:Vl,strings:Wl};var Wt={};y(Wt,{comments:()=>Jl,default:()=>Xl,direction:()=>Yl,strings:()=>Zl,thanks_to:()=>Kl});var Kl="",Jl="",Yl="ltr",Zl={placeholder:"Rapu",clear_search:"Whakakore",load_more:"Whakauta \u0113tahi otinga k\u0113",search_label:"Rapu",filters_label:"T\u0101tari",zero_results:"Otinga kore ki [SEARCH_TERM]",many_results:"[COUNT] otinga ki [SEARCH_TERM]",one_result:"[COUNT] otinga ki [SEARCH_TERM]",alt_search:"Otinga kore ki [SEARCH_TERM]. Otinga k\u0113 ki [DIFFERENT_TERM]",search_suggestion:"Otinga kore ki [SEARCH_TERM]. whakam\u0101tau ki ng\u0101 mea atu:",searching:"Rapu ki [SEARCH_TERM]..."},Xl={thanks_to:Kl,comments:Jl,direction:Yl,strings:Zl};var Gt={};y(Gt,{comments:()=>xl,default:()=>ti,direction:()=>$l,strings:()=>ei,thanks_to:()=>Ql});var Ql="Paul van Brouwershaven",xl="",$l="ltr",ei={placeholder:"Zoeken",clear_search:"Reset",load_more:"Meer resultaten laden",search_label:"Doorzoek deze site",filters_label:"Filters",zero_results:"Geen resultaten voor [SEARCH_TERM]",many_results:"[COUNT] resultaten voor [SEARCH_TERM]",one_result:"[COUNT] resultaat voor [SEARCH_TERM]",alt_search:"Geen resultaten voor [SEARCH_TERM]. In plaats daarvan worden resultaten voor [DIFFERENT_TERM] weergegeven",search_suggestion:"Geen resultaten voor [SEARCH_TERM]. Probeer een van de volgende zoekopdrachten:",searching:"Zoeken naar [SEARCH_TERM]..."},ti={thanks_to:Ql,comments:xl,direction:$l,strings:ei};var Kt={};y(Kt,{comments:()=>si,default:()=>ii,direction:()=>ri,strings:()=>li,thanks_to:()=>ni});var ni="Christopher Wingate",si="",ri="ltr",li={placeholder:"S\xF8k",clear_search:"Fjern",load_more:"Last flere resultater",search_label:"S\xF8k p\xE5 denne siden",filters_label:"Filtre",zero_results:"Ingen resultater for [SEARCH_TERM]",many_results:"[COUNT] resultater for [SEARCH_TERM]",one_result:"[COUNT] resultat for [SEARCH_TERM]",alt_search:"Ingen resultater for [SEARCH_TERM]. Viser resultater for [DIFFERENT_TERM] i stedet",search_suggestion:"Ingen resultater for [SEARCH_TERM]. Pr\xF8v en av disse s\xF8keordene i stedet:",searching:"S\xF8ker etter [SEARCH_TERM]"},ii={thanks_to:ni,comments:si,direction:ri,strings:li};var Jt={};y(Jt,{comments:()=>oi,default:()=>_i,direction:()=>ui,strings:()=>ci,thanks_to:()=>ai});var ai="",oi="",ui="ltr",ci={placeholder:"Szukaj",clear_search:"Wyczy\u015B\u0107",load_more:"Za\u0142aduj wi\u0119cej",search_label:"Przeszukaj t\u0119 stron\u0119",filters_label:"Filtry",zero_results:"Brak wynik\xF3w dla [SEARCH_TERM]",many_results:"[COUNT] wynik\xF3w dla [SEARCH_TERM]",one_result:"[COUNT] wynik dla [SEARCH_TERM]",alt_search:"Brak wynik\xF3w dla [SEARCH_TERM]. Wy\u015Bwietlam wyniki dla [DIFFERENT_TERM]",search_suggestion:"Brak wynik\xF3w dla [SEARCH_TERM]. Pokrewne wyniki wyszukiwania:",searching:"Szukam [SEARCH_TERM]..."},_i={thanks_to:ai,comments:oi,direction:ui,strings:ci};var Yt={};y(Yt,{comments:()=>di,default:()=>pi,direction:()=>hi,strings:()=>mi,thanks_to:()=>fi});var fi="Jonatah",di="",hi="ltr",mi={placeholder:"Pesquisar",clear_search:"Limpar",load_more:"Ver mais resultados",search_label:"Pesquisar",filters_label:"Filtros",zero_results:"Nenhum resultado encontrado para [SEARCH_TERM]",many_results:"[COUNT] resultados encontrados para [SEARCH_TERM]",one_result:"[COUNT] resultado encontrado para [SEARCH_TERM]",alt_search:"Nenhum resultado encontrado para [SEARCH_TERM]. Exibindo resultados para [DIFFERENT_TERM]",search_suggestion:"Nenhum resultado encontrado para [SEARCH_TERM]. Tente uma das seguintes pesquisas:",searching:"Pesquisando por [SEARCH_TERM]..."},pi={thanks_to:fi,comments:di,direction:hi,strings:mi};var Zt={};y(Zt,{comments:()=>Ei,default:()=>Ti,direction:()=>Ri,strings:()=>bi,thanks_to:()=>gi});var gi="Bogdan Mateescu ",Ei="",Ri="ltr",bi={placeholder:"C\u0103utare",clear_search:"\u015Eterge\u0163i",load_more:"\xCEnc\u0103rca\u021Bi mai multe rezultate",search_label:"C\u0103uta\u021Bi \xEEn acest site",filters_label:"Filtre",zero_results:"Niciun rezultat pentru [SEARCH_TERM]",many_results:"[COUNT] rezultate pentru [SEARCH_TERM]",one_result:"[COUNT] rezultat pentru [SEARCH_TERM]",alt_search:"Niciun rezultat pentru [SEARCH_TERM]. Se afi\u0219eaz\u0103 \xEEn schimb rezultatele pentru [DIFFERENT_TERM]",search_suggestion:"Niciun rezultat pentru [SEARCH_TERM]. \xCEncerca\u021Bi una dintre urm\u0103toarele c\u0103ut\u0103ri:",searching:"Se caut\u0103 dup\u0103: [SEARCH_TERM]..."},Ti={thanks_to:gi,comments:Ei,direction:Ri,strings:bi};var Xt={};y(Xt,{comments:()=>ki,default:()=>Mi,direction:()=>Si,strings:()=>yi,thanks_to:()=>Ci});var Ci="Aleksandr Gordeev",ki="",Si="ltr",yi={placeholder:"\u041F\u043E\u0438\u0441\u043A",clear_search:"\u041E\u0447\u0438\u0441\u0442\u0438\u0442\u044C \u043F\u043E\u043B\u0435",load_more:"\u0417\u0430\u0433\u0440\u0443\u0437\u0438\u0442\u044C \u0435\u0449\u0435",search_label:"\u041F\u043E\u0438\u0441\u043A \u043F\u043E \u0441\u0430\u0439\u0442\u0443",filters_label:"\u0424\u0438\u043B\u044C\u0442\u0440\u044B",zero_results:"\u041D\u0438\u0447\u0435\u0433\u043E \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D\u043E \u043F\u043E \u0437\u0430\u043F\u0440\u043E\u0441\u0443: [SEARCH_TERM]",many_results:"[COUNT] \u0440\u0435\u0437\u0443\u043B\u044C\u0442\u0430\u0442\u043E\u0432 \u043F\u043E \u0437\u0430\u043F\u0440\u043E\u0441\u0443: [SEARCH_TERM]",one_result:"[COUNT] \u0440\u0435\u0437\u0443\u043B\u044C\u0442\u0430\u0442 \u043F\u043E \u0437\u0430\u043F\u0440\u043E\u0441\u0443: [SEARCH_TERM]",alt_search:"\u041D\u0438\u0447\u0435\u0433\u043E \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D\u043E \u043F\u043E \u0437\u0430\u043F\u0440\u043E\u0441\u0443: [SEARCH_TERM]. \u041F\u043E\u043A\u0430\u0437\u0430\u043D\u044B \u0440\u0435\u0437\u0443\u043B\u044C\u0442\u0430\u0442\u044B \u043F\u043E \u0437\u0430\u043F\u0440\u043E\u0441\u0443: [DIFFERENT_TERM]",search_suggestion:"\u041D\u0438\u0447\u0435\u0433\u043E \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D\u043E \u043F\u043E \u0437\u0430\u043F\u0440\u043E\u0441\u0443: [SEARCH_TERM]. \u041F\u043E\u043F\u0440\u043E\u0431\u0443\u0439\u0442\u0435 \u043E\u0434\u0438\u043D \u0438\u0437 \u0441\u043B\u0435\u0434\u0443\u044E\u0449\u0438\u0445 \u0432\u0430\u0440\u0438\u0430\u043D\u0442\u043E\u0432",searching:"\u041F\u043E\u0438\u0441\u043A \u043F\u043E \u0437\u0430\u043F\u0440\u043E\u0441\u0443: [SEARCH_TERM]"},Mi={thanks_to:Ci,comments:ki,direction:Si,strings:yi};var Qt={};y(Qt,{comments:()=>vi,default:()=>Fi,direction:()=>wi,strings:()=>Hi,thanks_to:()=>Ai});var Ai="Andrija Sagicc",vi="",wi="ltr",Hi={placeholder:"\u041F\u0440\u0435\u0442\u0440\u0430\u0433\u0430",clear_search:"\u0411\u0440\u0438\u0441\u0430\u045A\u0435",load_more:"\u041F\u0440\u0438\u043A\u0430\u0437 \u0432\u0438\u0448\u0435 \u0440\u0435\u0437\u0443\u043B\u0442\u0430\u0442\u0430",search_label:"\u041F\u0440\u0435\u0442\u0440\u0430\u0433\u0430 \u0441\u0430\u0458\u0442\u0430",filters_label:"\u0424\u0438\u043B\u0442\u0435\u0440\u0438",zero_results:"\u041D\u0435\u043C\u0430 \u0440\u0435\u0437\u0443\u043B\u0442\u0430\u0442\u0430 \u0437\u0430 [SEARCH_TERM]",many_results:"[COUNT] \u0440\u0435\u0437\u0443\u043B\u0442\u0430\u0442\u0430 \u0437\u0430 [SEARCH_TERM]",one_result:"[COUNT] \u0440\u0435\u0437\u0443\u043B\u0442\u0430\u0442\u0430 \u0437\u0430 [SEARCH_TERM]",alt_search:"\u041D\u0435\u043C\u0430 \u0440\u0435\u0437\u0443\u043B\u0442\u0430\u0442\u0430 \u0437\u0430 [SEARCH_TERM]. \u041F\u0440\u0438\u043A\u0430\u0437 \u0434\u043E\u0434\u0430\u0442\u043D\u0438\u043A \u0440\u0435\u0437\u0443\u043B\u0442\u0430\u0442\u0430 \u0437\u0430 [DIFFERENT_TERM]",search_suggestion:"\u041D\u0435\u043C\u0430 \u0440\u0435\u0437\u0443\u043B\u0442\u0430\u0442\u0430 \u0437\u0430 [SEARCH_TERM]. \u041F\u043E\u043A\u0443\u0448\u0430\u0458\u0442\u0435 \u0441\u0430 \u043D\u0435\u043A\u043E\u043C \u043E\u0434 \u0441\u043B\u0435\u0434\u0435\u045B\u0438\u0445 \u043F\u0440\u0435\u0442\u0440\u0430\u0433\u0430:",searching:"\u041F\u0440\u0435\u0442\u0440\u0430\u0433\u0430 \u0442\u0435\u0440\u043C\u0438\u043D\u0430 [SEARCH_TERM]..."},Fi={thanks_to:Ai,comments:vi,direction:wi,strings:Hi};var xt={};y(xt,{comments:()=>Oi,default:()=>Ui,direction:()=>ji,strings:()=>zi,thanks_to:()=>Ni});var Ni="Montazar Al-Jaber ",Oi="",ji="ltr",zi={placeholder:"S\xF6k",clear_search:"Rensa",load_more:"Visa fler tr\xE4ffar",search_label:"S\xF6k p\xE5 denna sida",filters_label:"Filter",zero_results:"[SEARCH_TERM] gav inga tr\xE4ffar",many_results:"[SEARCH_TERM] gav [COUNT] tr\xE4ffar",one_result:"[SEARCH_TERM] gav [COUNT] tr\xE4ff",alt_search:"[SEARCH_TERM] gav inga tr\xE4ffar. Visar resultat f\xF6r [DIFFERENT_TERM] ist\xE4llet",search_suggestion:"[SEARCH_TERM] gav inga tr\xE4ffar. F\xF6rs\xF6k igen med en av f\xF6ljande s\xF6kord:",searching:"S\xF6ker efter [SEARCH_TERM]..."},Ui={thanks_to:Ni,comments:Oi,direction:ji,strings:zi};var $t={};y($t,{comments:()=>Ii,default:()=>qi,direction:()=>Pi,strings:()=>Li,thanks_to:()=>Di});var Di="Anonymous",Ii="",Pi="ltr",Li={placeholder:"Tafuta",clear_search:"Futa",load_more:"Pakia matokeo zaidi",search_label:"Tafuta tovuti hii",filters_label:"Vichujio",zero_results:"Hakuna matokeo ya [SEARCH_TERM]",many_results:"Matokeo [COUNT] ya [SEARCH_TERM]",one_result:"Tokeo [COUNT] la [SEARCH_TERM]",alt_search:"Hakuna mayokeo ya [SEARCH_TERM]. Badala yake, inaonyesha matokeo ya [DIFFERENT_TERM]",search_suggestion:"Hakuna matokeo ya [SEARCH_TERM]. Jaribu mojawapo ya utafutaji ufuatao:",searching:"Kutafuta [SEARCH_TERM]..."},qi={thanks_to:Di,comments:Ii,direction:Pi,strings:Li};var en={};y(en,{comments:()=>Vi,default:()=>Ki,direction:()=>Wi,strings:()=>Gi,thanks_to:()=>Bi});var Bi="",Vi="",Wi="ltr",Gi={placeholder:"\u0BA4\u0BC7\u0B9F\u0BC1\u0B95",clear_search:"\u0B85\u0BB4\u0BBF\u0B95\u0BCD\u0B95\u0BC1\u0B95",load_more:"\u0BAE\u0BC7\u0BB2\u0BC1\u0BAE\u0BCD \u0BAE\u0BC1\u0B9F\u0BBF\u0BB5\u0BC1\u0B95\u0BB3\u0BC8\u0B95\u0BCD \u0B95\u0BBE\u0B9F\u0BCD\u0B9F\u0BC1\u0B95",search_label:"\u0B87\u0BA8\u0BCD\u0BA4 \u0BA4\u0BB3\u0BA4\u0BCD\u0BA4\u0BBF\u0BB2\u0BCD \u0BA4\u0BC7\u0B9F\u0BC1\u0B95",filters_label:"\u0BB5\u0B9F\u0BBF\u0B95\u0B9F\u0BCD\u0B9F\u0BB2\u0BCD\u0B95\u0BB3\u0BCD",zero_results:"[SEARCH_TERM] \u0B95\u0BCD\u0B95\u0BBE\u0BA9 \u0BAE\u0BC1\u0B9F\u0BBF\u0BB5\u0BC1\u0B95\u0BB3\u0BCD \u0B87\u0BB2\u0BCD\u0BB2\u0BC8",many_results:"[SEARCH_TERM] \u0B95\u0BCD\u0B95\u0BBE\u0BA9 [COUNT] \u0BAE\u0BC1\u0B9F\u0BBF\u0BB5\u0BC1\u0B95\u0BB3\u0BCD",one_result:"[SEARCH_TERM] \u0B95\u0BCD\u0B95\u0BBE\u0BA9 \u0BAE\u0BC1\u0B9F\u0BBF\u0BB5\u0BC1",alt_search:"[SEARCH_TERM] \u0B87\u0BA4\u0BCD\u0BA4\u0BC7\u0B9F\u0BB2\u0BC1\u0B95\u0BCD\u0B95\u0BBE\u0BA9 \u0BAE\u0BC1\u0B9F\u0BBF\u0BB5\u0BC1\u0B95\u0BB3\u0BCD \u0B87\u0BB2\u0BCD\u0BB2\u0BC8, \u0B87\u0BA8\u0BCD\u0BA4 \u0BA4\u0BC7\u0B9F\u0BB2\u0BCD\u0B95\u0BB3\u0BC1\u0B95\u0BCD\u0B95\u0BBE\u0BA9 \u0B92\u0BA4\u0BCD\u0BA4 \u0BAE\u0BC1\u0B9F\u0BBF\u0BB5\u0BC1\u0B95\u0BB3\u0BCD [DIFFERENT_TERM]",search_suggestion:"[SEARCH_TERM] \u0B87\u0BA4\u0BCD \u0BA4\u0BC7\u0B9F\u0BB2\u0BC1\u0B95\u0BCD\u0B95\u0BBE\u0BA9 \u0BAE\u0BC1\u0B9F\u0BBF\u0BB5\u0BC1\u0B95\u0BB3\u0BCD \u0B87\u0BB2\u0BCD\u0BB2\u0BC8.\u0B87\u0BA4\u0BB1\u0BCD\u0B95\u0BC1 \u0BAA\u0BA4\u0BBF\u0BB2\u0BC0\u0B9F\u0BBE\u0BA9 \u0BA4\u0BC7\u0B9F\u0BB2\u0BCD\u0B95\u0BB3\u0BC8 \u0BA4\u0BC7\u0B9F\u0BC1\u0B95:",searching:"[SEARCH_TERM] \u0BA4\u0BC7\u0B9F\u0BAA\u0BCD\u0BAA\u0B9F\u0BC1\u0B95\u0BBF\u0BA9\u0BCD\u0BB1\u0BA4\u0BC1"},Ki={thanks_to:Bi,comments:Vi,direction:Wi,strings:Gi};var tn={};y(tn,{comments:()=>Yi,default:()=>Qi,direction:()=>Zi,strings:()=>Xi,thanks_to:()=>Ji});var Ji="Taylan \xD6zg\xFCr Bildik",Yi="",Zi="ltr",Xi={placeholder:"Ara\u015Ft\u0131r",clear_search:"Temizle",load_more:"Daha fazla sonu\xE7",search_label:"Site genelinde arama",filters_label:"Filtreler",zero_results:"[SEARCH_TERM] i\xE7in sonu\xE7 yok",many_results:"[SEARCH_TERM] i\xE7in [COUNT] sonu\xE7 bulundu",one_result:"[SEARCH_TERM] i\xE7in [COUNT] sonu\xE7 bulundu",alt_search:"[SEARCH_TERM] i\xE7in sonu\xE7 yok. Bunun yerine [DIFFERENT_TERM] i\xE7in sonu\xE7lar g\xF6steriliyor",search_suggestion:"[SEARCH_TERM] i\xE7in sonu\xE7 yok. Alternatif olarak a\u015Fa\u011F\u0131daki kelimelerden birini deneyebilirsiniz:",searching:"[SEARCH_TERM] ara\u015Ft\u0131r\u0131l\u0131yor..."},Qi={thanks_to:Ji,comments:Yi,direction:Zi,strings:Xi};var nn={};y(nn,{comments:()=>$i,default:()=>na,direction:()=>ea,strings:()=>ta,thanks_to:()=>xi});var xi="Vladyslav Lyshenko ",$i="",ea="ltr",ta={placeholder:"\u041F\u043E\u0448\u0443\u043A",clear_search:"\u041E\u0447\u0438\u0441\u0442\u0438\u0442\u0438 \u043F\u043E\u043B\u0435",load_more:"\u0417\u0430\u0432\u0430\u043D\u0442\u0430\u0436\u0438\u0442\u0438 \u0449\u0435",search_label:"\u041F\u043E\u0448\u0443\u043A \u043F\u043E \u0441\u0430\u0439\u0442\u0443",filters_label:"\u0424\u0456\u043B\u044C\u0442\u0440\u0438",zero_results:"\u041D\u0456\u0447\u043E\u0433\u043E \u043D\u0435 \u0437\u043D\u0430\u0439\u0434\u0435\u043D\u043E \u0437\u0430 \u0437\u0430\u043F\u0438\u0442\u043E\u043C: [SEARCH_TERM]",many_results:"[COUNT] \u0440\u0435\u0437\u0443\u043B\u044C\u0442\u0430\u0442\u0456\u0432 \u043D\u0430 \u0437\u0430\u043F\u0438\u0442: [SEARCH_TERM]",one_result:"[COUNT] \u0440\u0435\u0437\u0443\u043B\u044C\u0442\u0430\u0442 \u0437\u0430 \u0437\u0430\u043F\u0438\u0442\u043E\u043C: [SEARCH_TERM]",alt_search:"\u041D\u0456\u0447\u043E\u0433\u043E \u043D\u0435 \u0437\u043D\u0430\u0439\u0434\u0435\u043D\u043E \u043D\u0430 \u0437\u0430\u043F\u0438\u0442: [SEARCH_TERM]. \u041F\u043E\u043A\u0430\u0437\u0430\u043D\u043E \u0440\u0435\u0437\u0443\u043B\u044C\u0442\u0430\u0442\u0438 \u043D\u0430 \u0437\u0430\u043F\u0438\u0442: [DIFFERENT_TERM]",search_suggestion:"\u041D\u0456\u0447\u043E\u0433\u043E \u043D\u0435 \u0437\u043D\u0430\u0439\u0434\u0435\u043D\u043E \u043D\u0430 \u0437\u0430\u043F\u0438\u0442: [SEARCH_TERM]. \u0421\u043F\u0440\u043E\u0431\u0443\u0439\u0442\u0435 \u043E\u0434\u0438\u043D \u0456\u0437 \u0442\u0430\u043A\u0438\u0445 \u0432\u0430\u0440\u0456\u0430\u043D\u0442\u0456\u0432",searching:"\u041F\u043E\u0448\u0443\u043A \u0437\u0430 \u0437\u0430\u043F\u0438\u0442\u043E\u043C: [SEARCH_TERM]"},na={thanks_to:xi,comments:$i,direction:ea,strings:ta};var sn={};y(sn,{comments:()=>ra,default:()=>aa,direction:()=>la,strings:()=>ia,thanks_to:()=>sa});var sa="Long Nhat Nguyen",ra="",la="ltr",ia={placeholder:"T\xECm ki\u1EBFm",clear_search:"X\xF3a",load_more:"Nhi\u1EC1u k\u1EBFt qu\u1EA3 h\u01A1n",search_label:"T\xECm ki\u1EBFm trong trang n\xE0y",filters_label:"B\u1ED9 l\u1ECDc",zero_results:"Kh\xF4ng t\xECm th\u1EA5y k\u1EBFt qu\u1EA3 cho [SEARCH_TERM]",many_results:"[COUNT] k\u1EBFt qu\u1EA3 cho [SEARCH_TERM]",one_result:"[COUNT] k\u1EBFt qu\u1EA3 cho [SEARCH_TERM]",alt_search:"Kh\xF4ng t\xECm th\u1EA5y k\u1EBFt qu\u1EA3 cho [SEARCH_TERM]. Ki\u1EC3m th\u1ECB k\u1EBFt qu\u1EA3 thay th\u1EBF v\u1EDBi [DIFFERENT_TERM]",search_suggestion:"Kh\xF4ng t\xECm th\u1EA5y k\u1EBFt qu\u1EA3 cho [SEARCH_TERM]. Th\u1EED m\u1ED9t trong c\xE1c t\xECm ki\u1EBFm:",searching:"\u0110ang t\xECm ki\u1EBFm cho [SEARCH_TERM]..."},aa={thanks_to:sa,comments:ra,direction:la,strings:ia};var rn={};y(rn,{comments:()=>ua,default:()=>fa,direction:()=>ca,strings:()=>_a,thanks_to:()=>oa});var oa="Amber Song",ua="",ca="ltr",_a={placeholder:"\u641C\u7D22",clear_search:"\u6E05\u9664",load_more:"\u52A0\u8F7D\u66F4\u591A\u7ED3\u679C",search_label:"\u7AD9\u5185\u641C\u7D22",filters_label:"\u7B5B\u9009",zero_results:"\u672A\u627E\u5230 [SEARCH_TERM] \u7684\u76F8\u5173\u7ED3\u679C",many_results:"\u627E\u5230 [COUNT] \u4E2A [SEARCH_TERM] \u7684\u76F8\u5173\u7ED3\u679C",one_result:"\u627E\u5230 [COUNT] \u4E2A [SEARCH_TERM] \u7684\u76F8\u5173\u7ED3\u679C",alt_search:"\u672A\u627E\u5230 [SEARCH_TERM] \u7684\u76F8\u5173\u7ED3\u679C\u3002\u6539\u4E3A\u663E\u793A [DIFFERENT_TERM] \u7684\u76F8\u5173\u7ED3\u679C",search_suggestion:"\u672A\u627E\u5230 [SEARCH_TERM] \u7684\u76F8\u5173\u7ED3\u679C\u3002\u8BF7\u5C1D\u8BD5\u4EE5\u4E0B\u641C\u7D22\u3002",searching:"\u6B63\u5728\u641C\u7D22 [SEARCH_TERM]..."},fa={thanks_to:oa,comments:ua,direction:ca,strings:_a};var ln={};y(ln,{comments:()=>ha,default:()=>ga,direction:()=>ma,strings:()=>pa,thanks_to:()=>da});var da="Amber Song",ha="",ma="ltr",pa={placeholder:"\u641C\u7D22",clear_search:"\u6E05\u9664",load_more:"\u52A0\u8F09\u66F4\u591A\u7D50\u679C",search_label:"\u7AD9\u5167\u641C\u7D22",filters_label:"\u7BE9\u9078",zero_results:"\u672A\u627E\u5230 [SEARCH_TERM] \u7684\u76F8\u95DC\u7D50\u679C",many_results:"\u627E\u5230 [COUNT] \u500B [SEARCH_TERM] \u7684\u76F8\u95DC\u7D50\u679C",one_result:"\u627E\u5230 [COUNT] \u500B [SEARCH_TERM] \u7684\u76F8\u95DC\u7D50\u679C",alt_search:"\u672A\u627E\u5230 [SEARCH_TERM] \u7684\u76F8\u95DC\u7D50\u679C\u3002\u6539\u70BA\u986F\u793A [DIFFERENT_TERM] \u7684\u76F8\u95DC\u7D50\u679C",search_suggestion:"\u672A\u627E\u5230 [SEARCH_TERM] \u7684\u76F8\u95DC\u7D50\u679C\u3002\u8ACB\u5617\u8A66\u4EE5\u4E0B\u641C\u7D22\u3002",searching:"\u6B63\u5728\u641C\u7D22 [SEARCH_TERM]..."},ga={thanks_to:da,comments:ha,direction:ma,strings:pa};var an={};y(an,{comments:()=>Ra,default:()=>Ca,direction:()=>ba,strings:()=>Ta,thanks_to:()=>Ea});var Ea="Amber Song",Ra="",ba="ltr",Ta={placeholder:"\u641C\u7D22",clear_search:"\u6E05\u9664",load_more:"\u52A0\u8F7D\u66F4\u591A\u7ED3\u679C",search_label:"\u7AD9\u5185\u641C\u7D22",filters_label:"\u7B5B\u9009",zero_results:"\u672A\u627E\u5230 [SEARCH_TERM] \u7684\u76F8\u5173\u7ED3\u679C",many_results:"\u627E\u5230 [COUNT] \u4E2A [SEARCH_TERM] \u7684\u76F8\u5173\u7ED3\u679C",one_result:"\u627E\u5230 [COUNT] \u4E2A [SEARCH_TERM] \u7684\u76F8\u5173\u7ED3\u679C",alt_search:"\u672A\u627E\u5230 [SEARCH_TERM] \u7684\u76F8\u5173\u7ED3\u679C\u3002\u6539\u4E3A\u663E\u793A [DIFFERENT_TERM] \u7684\u76F8\u5173\u7ED3\u679C",search_suggestion:"\u672A\u627E\u5230 [SEARCH_TERM] \u7684\u76F8\u5173\u7ED3\u679C\u3002\u8BF7\u5C1D\u8BD5\u4EE5\u4E0B\u641C\u7D22\u3002",searching:"\u6B63\u5728\u641C\u7D22 [SEARCH_TERM]..."},Ca={thanks_to:Ea,comments:Ra,direction:ba,strings:Ta};var ka=[kt,St,yt,Mt,At,vt,wt,Ht,Ft,Nt,Ot,jt,zt,Ut,Dt,It,Pt,Lt,qt,Bt,Vt,Wt,Gt,Kt,Jt,Yt,Zt,Xt,Qt,xt,$t,en,tn,nn,sn,rn,ln,an],ss=ka,rs=["../../translations/af.json","../../translations/ar.json","../../translations/bn.json","../../translations/ca.json","../../translations/cs.json","../../translations/da.json","../../translations/de.json","../../translations/en.json","../../translations/es.json","../../translations/fa.json","../../translations/fi.json","../../translations/fr.json","../../translations/gl.json","../../translations/he.json","../../translations/hi.json","../../translations/hr.json","../../translations/hu.json","../../translations/id.json","../../translations/it.json","../../translations/ja.json","../../translations/ko.json","../../translations/mi.json","../../translations/nl.json","../../translations/no.json","../../translations/pl.json","../../translations/pt.json","../../translations/ro.json","../../translations/ru.json","../../translations/sr.json","../../translations/sv.json","../../translations/sw.json","../../translations/ta.json","../../translations/tr.json","../../translations/uk.json","../../translations/vi.json","../../translations/zh-cn.json","../../translations/zh-tw.json","../../translations/zh.json"];function ls(n,e,t){let s=n.slice();return s[51]=e[t],s}function is(n){let e,t,s;function r(i){n[37](i)}let l={show_empty_filters:n[5],open_filters:n[6],available_filters:n[18],translate:n[20],automatic_translations:n[19],translations:n[7]};return n[0]!==void 0&&(l.selected_filters=n[0]),e=new ns({props:l}),le.push(()=>Mn(e,"selected_filters",r)),{c(){rt(e.$$.fragment)},m(i,a){me(e,i,a),s=!0},p(i,a){let o={};a[0]&32&&(o.show_empty_filters=i[5]),a[0]&64&&(o.open_filters=i[6]),a[0]&262144&&(o.available_filters=i[18]),a[0]&524288&&(o.automatic_translations=i[19]),a[0]&128&&(o.translations=i[7]),!t&&a[0]&1&&(t=!0,o.selected_filters=i[0],Cn(()=>t=!1)),e.$set(o)},i(i){s||(U(e.$$.fragment,i),s=!0)},o(i){P(e.$$.fragment,i),s=!1},d(i){ue(e,i)}}}function as(n){let e,t,s,r,l=[Ma,ya],i=[];function a(o,f){return o[14]?0:1}return t=a(n,[-1,-1]),s=i[t]=l[t](n),{c(){e=C("div"),s.c(),g(e,"class","pagefind-ui__results-area svelte-e9gkc3")},m(o,f){S(o,e,f),i[t].m(e,null),r=!0},p(o,f){let u=t;t=a(o,f),t===u?i[t].p(o,f):(ae(),P(i[u],1,1,()=>{i[u]=null}),oe(),s=i[t],s?s.p(o,f):(s=i[t]=l[t](o),s.c()),U(s,1),s.m(e,null))},i(o){r||(U(s),r=!0)},o(o){P(s),r=!1},d(o){o&&k(e),i[t].d()}}}function ya(n){let e,t,s,r=[],l=new Map,i,a,o;function f(c,d){return c[13].results.length===0?wa:c[13].results.length===1?va:Aa}let u=f(n,[-1,-1]),m=u(n),p=n[13].results.slice(0,n[17]),h=c=>c[51].id;for(let c=0;cn[17]&&us(n);return{c(){e=C("p"),m.c(),t=A(),s=C("ol");for(let c=0;cc[17]?_?_.p(c,d):(_=us(c),_.c(),_.m(a.parentNode,a)):_&&(_.d(1),_=null)},i(c){if(!o){for(let d=0;d{o[p]=null}),oe(),r=o[s],r?r.p(e,m):(r=o[s]=a[s](e),r.c()),U(r,1),r.m(l.parentNode,l))},i(u){i||(U(r),i=!0)},o(u){P(r),i=!1},d(u){u&&k(t),o[s].d(u),u&&k(l)}}}function us(n){let e,t=n[20]("load_more",n[19],n[7])+"",s,r,l;return{c(){e=C("button"),s=w(t),g(e,"type","button"),g(e,"class","pagefind-ui__button svelte-e9gkc3")},m(i,a){S(i,e,a),b(e,s),r||(l=J(e,"click",n[22]),r=!0)},p(i,a){a[0]&524416&&t!==(t=i[20]("load_more",i[19],i[7])+"")&&N(s,t)},d(i){i&&k(e),r=!1,l()}}}function cs(n){let e,t=n[20]("searching",n[19],n[7]).replace(/\[SEARCH_TERM\]/,n[16])+"",s;return{c(){e=C("p"),s=w(t),g(e,"class","pagefind-ui__message svelte-e9gkc3")},m(r,l){S(r,e,l),b(e,s)},p(r,l){l[0]&589952&&t!==(t=r[20]("searching",r[19],r[7]).replace(/\[SEARCH_TERM\]/,r[16])+"")&&N(s,t)},d(r){r&&k(e)}}}function Na(n){let e,t,s,r,l,i,a=n[20]("clear_search",n[19],n[7])+"",o,f,u,m,p,h,_,c,d=n[12]&&is(n),T=n[15]&&as(n);return{c(){e=C("div"),t=C("form"),s=C("input"),l=A(),i=C("button"),o=w(a),f=A(),u=C("div"),d&&d.c(),m=A(),T&&T.c(),g(s,"class","pagefind-ui__search-input svelte-e9gkc3"),g(s,"type","text"),g(s,"placeholder",r=n[20]("placeholder",n[19],n[7])),g(s,"autocapitalize","none"),g(s,"enterkeyhint","search"),s.autofocus=n[8],g(i,"class","pagefind-ui__search-clear svelte-e9gkc3"),B(i,"pagefind-ui__suppressed",!n[9]),g(u,"class","pagefind-ui__drawer svelte-e9gkc3"),B(u,"pagefind-ui__hidden",!n[15]),g(t,"class","pagefind-ui__form svelte-e9gkc3"),g(t,"role","search"),g(t,"aria-label",p=n[20]("search_label",n[19],n[7])),g(t,"action","javascript:void(0);"),g(e,"class","pagefind-ui svelte-e9gkc3"),B(e,"pagefind-ui--reset",n[1])},m(R,M){S(R,e,M),b(e,t),b(t,s),pt(s,n[9]),n[34](s),b(t,l),b(t,i),b(i,o),n[35](i),b(t,f),b(t,u),d&&d.m(u,null),b(u,m),T&&T.m(u,null),h=!0,n[8]&&s.focus(),_||(c=[J(s,"focus",n[21]),J(s,"keydown",n[32]),J(s,"input",n[33]),J(i,"click",n[36]),J(t,"submit",Oa)],_=!0)},p(R,M){(!h||M[0]&524416&&r!==(r=R[20]("placeholder",R[19],R[7])))&&g(s,"placeholder",r),(!h||M[0]&256)&&(s.autofocus=R[8]),M[0]&512&&s.value!==R[9]&&pt(s,R[9]),(!h||M[0]&524416)&&a!==(a=R[20]("clear_search",R[19],R[7])+"")&&N(o,a),(!h||M[0]&512)&&B(i,"pagefind-ui__suppressed",!R[9]),R[12]?d?(d.p(R,M),M[0]&4096&&U(d,1)):(d=is(R),d.c(),U(d,1),d.m(u,m)):d&&(ae(),P(d,1,1,()=>{d=null}),oe()),R[15]?T?(T.p(R,M),M[0]&32768&&U(T,1)):(T=as(R),T.c(),U(T,1),T.m(u,null)):T&&(ae(),P(T,1,1,()=>{T=null}),oe()),(!h||M[0]&32768)&&B(u,"pagefind-ui__hidden",!R[15]),(!h||M[0]&524416&&p!==(p=R[20]("search_label",R[19],R[7])))&&g(t,"aria-label",p),(!h||M[0]&2)&&B(e,"pagefind-ui--reset",R[1])},i(R){h||(U(d),U(T),h=!0)},o(R){P(d),P(T),h=!1},d(R){R&&k(e),n[34](null),n[35](null),d&&d.d(),T&&T.d(),_=!1,G(c)}}}var Oa=n=>n.preventDefault();function ja(n,e,t){let s={},r=rs.map(E=>E.match(/([^\/]+)\.json$/)[1]);for(let E=0;Ej[E]??F[E]??"";gt(()=>{let E=document?.querySelector?.("html")?.getAttribute?.("lang")||"en",F=lt(E.toLocaleLowerCase());t(19,hn=s[`${F.language}-${F.script}-${F.region}`]||s[`${F.language}-${F.region}`]||s[`${F.language}`]||s.en)}),Et(()=>{H?.destroy?.(),H=null});let mn=async()=>{if(!at&&(t(12,at=!0),!H)){let E;try{E=await import(`${l}pagefind.js`)}catch(j){console.error(j),console.error([`Pagefind couldn't be loaded from ${this.options.bundlePath}pagefind.js`,"You can configure this by passing a bundlePath option to PagefindUI"].join(` +`)),document?.currentScript&&document.currentScript.tagName.toUpperCase()==="SCRIPT"?console.error(`[DEBUG: Loaded from ${document.currentScript.src??"bad script location"}]`):console.error("no known script location")}u||t(24,u=f?12:30);let F={...d||{},excerptLength:u};await E.options(F);for(let j of T){if(!j.bundlePath)throw new Error("mergeIndex requires a bundlePath parameter");let L=j.bundlePath;delete j.bundlePath,await E.mergeIndex(L,j)}H=E,hs()}},hs=async()=>{H&&(dn=await H.filters(),(!ce||!Object.keys(ce).length)&&t(18,ce=dn))},ms=E=>{let F={};return Object.entries(E).filter(([,j])=>j).forEach(([j])=>{let[L,te]=j.split(/:(.*)$/);F[L]=F[L]||[],F[L].push(te)}),F},_e,ps=async(E,F)=>{if(!E){t(15,ut=!1),_e&&clearTimeout(_e);return}let j=ms(F),L=()=>gs(E,j);c>0&&E?(_e&&clearTimeout(_e),_e=setTimeout(L,c),await pn(),H.preload(E,{filters:j})):L(),Es()},pn=async()=>{for(;!H;)mn(),await new Promise(E=>setTimeout(E,50))},gs=async(E,F)=>{t(16,fn=E||""),typeof p=="function"&&(E=p(E)),t(14,ot=!0),t(15,ut=!0),await pn();let j=++_n,L={filters:F};X&&typeof X=="object"&&(L.sort=X);let te=await H.search(E,L);_n===j&&(te.filters&&Object.keys(te.filters)?.length&&t(18,ce=te.filters),t(13,cn=te),t(14,ot=!1),t(17,ct=i))},Es=()=>{let E=W.offsetWidth;E!=fs&&t(10,O.style.paddingRight=`${E+2}px`,O)},Rs=E=>{E?.preventDefault(),t(17,ct+=i)},bs=E=>{E.key==="Escape"&&(t(9,v=""),O.blur()),E.key==="Enter"&&E.preventDefault()};function Ts(){v=this.value,t(9,v),t(23,R)}function Cs(E){le[E?"unshift":"push"](()=>{O=E,t(10,O)})}function ks(E){le[E?"unshift":"push"](()=>{W=E,t(11,W)})}let Ss=()=>{t(9,v=""),O.blur()};function ys(E){V=E,t(0,V)}return n.$$set=E=>{"base_path"in E&&t(25,l=E.base_path),"page_size"in E&&t(26,i=E.page_size),"reset_styles"in E&&t(1,a=E.reset_styles),"show_images"in E&&t(2,o=E.show_images),"show_sub_results"in E&&t(3,f=E.show_sub_results),"excerpt_length"in E&&t(24,u=E.excerpt_length),"process_result"in E&&t(4,m=E.process_result),"process_term"in E&&t(27,p=E.process_term),"show_empty_filters"in E&&t(5,h=E.show_empty_filters),"open_filters"in E&&t(6,_=E.open_filters),"debounce_timeout_ms"in E&&t(28,c=E.debounce_timeout_ms),"pagefind_options"in E&&t(29,d=E.pagefind_options),"merge_index"in E&&t(30,T=E.merge_index),"trigger_search_term"in E&&t(23,R=E.trigger_search_term),"translations"in E&&t(7,M=E.translations),"autofocus"in E&&t(8,D=E.autofocus),"sort"in E&&t(31,X=E.sort),"selected_filters"in E&&t(0,V=E.selected_filters)},n.$$.update=()=>{if(n.$$.dirty[0]&8388608)e:R&&(t(9,v=R),t(23,R=""));if(n.$$.dirty[0]&513)e:ps(v,V)},[V,a,o,f,m,h,_,M,D,v,O,W,at,cn,ot,ut,fn,ct,ce,hn,ds,mn,Rs,R,u,l,i,p,c,d,T,X,bs,Ts,Cs,ks,Ss,ys]}var on=class extends q{constructor(e){super(),Y(this,e,ja,Na,K,{base_path:25,page_size:26,reset_styles:1,show_images:2,show_sub_results:3,excerpt_length:24,process_result:4,process_term:27,show_empty_filters:5,open_filters:6,debounce_timeout_ms:28,pagefind_options:29,merge_index:30,trigger_search_term:23,translations:7,autofocus:8,sort:31,selected_filters:0},null,[-1,-1])}},_s=on;var un;try{document?.currentScript&&document.currentScript.tagName.toUpperCase()==="SCRIPT"&&(un=new URL(document.currentScript.src).pathname.match(/^(.*\/)(?:pagefind-)?ui.js.*$/)[1])}catch{un="/pagefind/"}var it=class{constructor(e){this._pfs=null;let t=e.element??"[data-pagefind-ui]",s=e.bundlePath??un,r=e.pageSize??5,l=e.resetStyles??!0,i=e.showImages??!0,a=e.showSubResults??!1,o=e.excerptLength??0,f=e.processResult??null,u=e.processTerm??null,m=e.showEmptyFilters??!0,p=e.openFilters??[],h=e.debounceTimeoutMs??300,_=e.mergeIndex??[],c=e.translations??[],d=e.autofocus??!1,T=e.sort??null;delete e.element,delete e.bundlePath,delete e.pageSize,delete e.resetStyles,delete e.showImages,delete e.showSubResults,delete e.excerptLength,delete e.processResult,delete e.processTerm,delete e.showEmptyFilters,delete e.openFilters,delete e.debounceTimeoutMs,delete e.mergeIndex,delete e.translations,delete e.autofocus,delete e.sort;let R=t instanceof HTMLElement?t:document.querySelector(t);R?this._pfs=new _s({target:R,props:{base_path:s,page_size:r,reset_styles:l,show_images:i,show_sub_results:a,excerpt_length:o,process_result:f,process_term:u,show_empty_filters:m,open_filters:p,debounce_timeout_ms:h,merge_index:_,translations:c,autofocus:d,sort:T,pagefind_options:e}}):console.error(`Pagefind UI couldn't find the selector ${t}`)}triggerSearch(e){this._pfs.$$set({trigger_search_term:e})}triggerFilters(e){let t={};for(let[s,r]of Object.entries(e))if(Array.isArray(r))for(let l of r)t[`${s}:${l}`]=!0;else t[`${s}:${r}`]=!0;this._pfs.$$set({selected_filters:t})}destroy(){this._pfs.$destroy()}};window.PagefindUI=it;})(); diff --git a/docs/pagefind/pagefind.en_c1a75a9f75.pf_meta b/docs/pagefind/pagefind.en_c1a75a9f75.pf_meta new file mode 100644 index 0000000..1fa6c07 Binary files /dev/null and b/docs/pagefind/pagefind.en_c1a75a9f75.pf_meta differ diff --git a/docs/pagefind/pagefind.js b/docs/pagefind/pagefind.js new file mode 100644 index 0000000..035a438 --- /dev/null +++ b/docs/pagefind/pagefind.js @@ -0,0 +1,9 @@ +const pagefind_version="1.3.0";let wasm_bindgen;(function(){const __exports={};let script_src;if(typeof document!=='undefined'&&document.currentScript!==null){script_src=new URL("UNHANDLED",location.href).toString()}let wasm=undefined;let cachedUint8Memory0=null;function getUint8Memory0(){if(cachedUint8Memory0===null||cachedUint8Memory0.byteLength===0){cachedUint8Memory0=new Uint8Array(wasm.memory.buffer)}return cachedUint8Memory0}let WASM_VECTOR_LEN=0;function passArray8ToWasm0(arg,malloc){const ptr=malloc(arg.length*1,1)>>>0;getUint8Memory0().set(arg,ptr/1);WASM_VECTOR_LEN=arg.length;return ptr}__exports.init_pagefind=function(metadata_bytes){const ptr0=passArray8ToWasm0(metadata_bytes,wasm.__wbindgen_malloc);const len0=WASM_VECTOR_LEN;const ret=wasm.init_pagefind(ptr0,len0);return ret>>>0};const cachedTextEncoder=(typeof TextEncoder!=='undefined'?new TextEncoder('utf-8'):{encode:()=>{throw Error('TextEncoder not available')}});const encodeString=(typeof cachedTextEncoder.encodeInto==='function'?function(arg,view){return cachedTextEncoder.encodeInto(arg,view)}:function(arg,view){const buf=cachedTextEncoder.encode(arg);view.set(buf);return{read:arg.length,written:buf.length}});function passStringToWasm0(arg,malloc,realloc){if(realloc===undefined){const buf=cachedTextEncoder.encode(arg);const ptr=malloc(buf.length,1)>>>0;getUint8Memory0().subarray(ptr,ptr+buf.length).set(buf);WASM_VECTOR_LEN=buf.length;return ptr}let len=arg.length;let ptr=malloc(len,1)>>>0;const mem=getUint8Memory0();let offset=0;for(;offset0x7F)break;mem[ptr+offset]=code}if(offset!==len){if(offset!==0){arg=arg.slice(offset)}ptr=realloc(ptr,len,len=offset+arg.length*3,1)>>>0;const view=getUint8Memory0().subarray(ptr+offset,ptr+len);const ret=encodeString(arg,view);offset+=ret.written;ptr=realloc(ptr,len,offset,1)>>>0}WASM_VECTOR_LEN=offset;return ptr}__exports.set_ranking_weights=function(ptr,weights){const ptr0=passStringToWasm0(weights,wasm.__wbindgen_malloc,wasm.__wbindgen_realloc);const len0=WASM_VECTOR_LEN;const ret=wasm.set_ranking_weights(ptr,ptr0,len0);return ret>>>0};__exports.load_index_chunk=function(ptr,chunk_bytes){const ptr0=passArray8ToWasm0(chunk_bytes,wasm.__wbindgen_malloc);const len0=WASM_VECTOR_LEN;const ret=wasm.load_index_chunk(ptr,ptr0,len0);return ret>>>0};__exports.load_filter_chunk=function(ptr,chunk_bytes){const ptr0=passArray8ToWasm0(chunk_bytes,wasm.__wbindgen_malloc);const len0=WASM_VECTOR_LEN;const ret=wasm.load_filter_chunk(ptr,ptr0,len0);return ret>>>0};__exports.add_synthetic_filter=function(ptr,filter){const ptr0=passStringToWasm0(filter,wasm.__wbindgen_malloc,wasm.__wbindgen_realloc);const len0=WASM_VECTOR_LEN;const ret=wasm.add_synthetic_filter(ptr,ptr0,len0);return ret>>>0};let cachedInt32Memory0=null;function getInt32Memory0(){if(cachedInt32Memory0===null||cachedInt32Memory0.byteLength===0){cachedInt32Memory0=new Int32Array(wasm.memory.buffer)}return cachedInt32Memory0}const cachedTextDecoder=(typeof TextDecoder!=='undefined'?new TextDecoder('utf-8',{ignoreBOM:true,fatal:true}):{decode:()=>{throw Error('TextDecoder not available')}});if(typeof TextDecoder!=='undefined'){cachedTextDecoder.decode()};function getStringFromWasm0(ptr,len){ptr=ptr>>>0;return cachedTextDecoder.decode(getUint8Memory0().subarray(ptr,ptr+len))}__exports.request_indexes=function(ptr,query){let deferred2_0;let deferred2_1;try{const retptr=wasm.__wbindgen_add_to_stack_pointer(-16);const ptr0=passStringToWasm0(query,wasm.__wbindgen_malloc,wasm.__wbindgen_realloc);const len0=WASM_VECTOR_LEN;wasm.request_indexes(retptr,ptr,ptr0,len0);var r0=getInt32Memory0()[retptr/4+0];var r1=getInt32Memory0()[retptr/4+1];deferred2_0=r0;deferred2_1=r1;return getStringFromWasm0(r0,r1)}finally{wasm.__wbindgen_add_to_stack_pointer(16);wasm.__wbindgen_free(deferred2_0,deferred2_1,1)}};__exports.request_filter_indexes=function(ptr,filters){let deferred2_0;let deferred2_1;try{const retptr=wasm.__wbindgen_add_to_stack_pointer(-16);const ptr0=passStringToWasm0(filters,wasm.__wbindgen_malloc,wasm.__wbindgen_realloc);const len0=WASM_VECTOR_LEN;wasm.request_filter_indexes(retptr,ptr,ptr0,len0);var r0=getInt32Memory0()[retptr/4+0];var r1=getInt32Memory0()[retptr/4+1];deferred2_0=r0;deferred2_1=r1;return getStringFromWasm0(r0,r1)}finally{wasm.__wbindgen_add_to_stack_pointer(16);wasm.__wbindgen_free(deferred2_0,deferred2_1,1)}};__exports.request_all_filter_indexes=function(ptr){let deferred1_0;let deferred1_1;try{const retptr=wasm.__wbindgen_add_to_stack_pointer(-16);wasm.request_all_filter_indexes(retptr,ptr);var r0=getInt32Memory0()[retptr/4+0];var r1=getInt32Memory0()[retptr/4+1];deferred1_0=r0;deferred1_1=r1;return getStringFromWasm0(r0,r1)}finally{wasm.__wbindgen_add_to_stack_pointer(16);wasm.__wbindgen_free(deferred1_0,deferred1_1,1)}};__exports.filters=function(ptr){let deferred1_0;let deferred1_1;try{const retptr=wasm.__wbindgen_add_to_stack_pointer(-16);wasm.filters(retptr,ptr);var r0=getInt32Memory0()[retptr/4+0];var r1=getInt32Memory0()[retptr/4+1];deferred1_0=r0;deferred1_1=r1;return getStringFromWasm0(r0,r1)}finally{wasm.__wbindgen_add_to_stack_pointer(16);wasm.__wbindgen_free(deferred1_0,deferred1_1,1)}};__exports.search=function(ptr,query,filter,sort,exact){let deferred4_0;let deferred4_1;try{const retptr=wasm.__wbindgen_add_to_stack_pointer(-16);const ptr0=passStringToWasm0(query,wasm.__wbindgen_malloc,wasm.__wbindgen_realloc);const len0=WASM_VECTOR_LEN;const ptr1=passStringToWasm0(filter,wasm.__wbindgen_malloc,wasm.__wbindgen_realloc);const len1=WASM_VECTOR_LEN;const ptr2=passStringToWasm0(sort,wasm.__wbindgen_malloc,wasm.__wbindgen_realloc);const len2=WASM_VECTOR_LEN;wasm.search(retptr,ptr,ptr0,len0,ptr1,len1,ptr2,len2,exact);var r0=getInt32Memory0()[retptr/4+0];var r1=getInt32Memory0()[retptr/4+1];deferred4_0=r0;deferred4_1=r1;return getStringFromWasm0(r0,r1)}finally{wasm.__wbindgen_add_to_stack_pointer(16);wasm.__wbindgen_free(deferred4_0,deferred4_1,1)}};async function __wbg_load(module,imports){if(typeof Response==='function'&&module instanceof Response){if(typeof WebAssembly.instantiateStreaming==='function'){try{return await WebAssembly.instantiateStreaming(module,imports)}catch(e){if(module.headers.get('Content-Type')!='application/wasm'){console.warn("`WebAssembly.instantiateStreaming` failed because your server does not serve wasm with `application/wasm` MIME type. Falling back to `WebAssembly.instantiate` which is slower. Original error:\n",e)}else{throw e}}}const bytes=await module.arrayBuffer();return await WebAssembly.instantiate(bytes,imports)}else{const instance=await WebAssembly.instantiate(module,imports);if(instance instanceof WebAssembly.Instance){return{instance,module}}else{return instance}}}function __wbg_get_imports(){const imports={};imports.wbg={};return imports}function __wbg_init_memory(imports,maybe_memory){}function __wbg_finalize_init(instance,module){wasm=instance.exports;__wbg_init.__wbindgen_wasm_module=module;cachedInt32Memory0=null;cachedUint8Memory0=null;return wasm}function initSync(module){if(wasm!==undefined)return wasm;const imports=__wbg_get_imports();__wbg_init_memory(imports);if(!(module instanceof WebAssembly.Module)){module=new WebAssembly.Module(module)}const instance=new WebAssembly.Instance(module,imports);return __wbg_finalize_init(instance,module)}async function __wbg_init(input){if(wasm!==undefined)return wasm;if(typeof input==='undefined'&&typeof script_src!=='undefined'){input=script_src.replace(/\.js$/,'_bg.wasm')}const imports=__wbg_get_imports();if(typeof input==='string'||(typeof Request==='function'&&input instanceof Request)||(typeof URL==='function'&&input instanceof URL)){input=fetch(input)}__wbg_init_memory(imports);const{instance,module}=await __wbg_load(await input,imports);return __wbg_finalize_init(instance,module)}wasm_bindgen=Object.assign(__wbg_init,{initSync},__exports)})();var u8=Uint8Array;var u16=Uint16Array;var u32=Uint32Array;var fleb=new u8([0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0,0,0,0]);var fdeb=new u8([0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13,0,0]);var clim=new u8([16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15]);var freb=function(eb,start){var b=new u16(31);for(var i2=0;i2<31;++i2){b[i2]=start+=1<>>1|(i&21845)<<1;x=(x&52428)>>>2|(x&13107)<<2;x=(x&61680)>>>4|(x&3855)<<4;rev[i]=((x&65280)>>>8|(x&255)<<8)>>>1}var x;var i;var hMap=function(cd,mb,r){var s=cd.length;var i2=0;var l=new u16(mb);for(;i2>>rvb]=sv}}}}else{co=new u16(s);for(i2=0;i2>>15-cd[i2]}}}return co};var flt=new u8(288);for(i=0;i<144;++i)flt[i]=8;var i;for(i=144;i<256;++i)flt[i]=9;var i;for(i=256;i<280;++i)flt[i]=7;var i;for(i=280;i<288;++i)flt[i]=8;var i;var fdt=new u8(32);for(i=0;i<32;++i)fdt[i]=5;var i;var flrm=hMap(flt,9,1);var fdrm=hMap(fdt,5,1);var max=function(a){var m=a[0];for(var i2=1;i2m)m=a[i2]}return m};var bits=function(d,p,m){var o=p/8|0;return(d[o]|d[o+1]<<8)>>(p&7)&m};var bits16=function(d,p){var o=p/8|0;return(d[o]|d[o+1]<<8|d[o+2]<<16)>>(p&7)};var shft=function(p){return(p+7)/8|0};var slc=function(v,s,e){if(s==null||s<0)s=0;if(e==null||e>v.length)e=v.length;var n=new(v.BYTES_PER_ELEMENT==2?u16:v.BYTES_PER_ELEMENT==4?u32:u8)(e-s);n.set(v.subarray(s,e));return n};var ec=["unexpected EOF","invalid block type","invalid length/literal","invalid distance","stream finished","no stream handler",,"no callback","invalid UTF-8 data","extra field too long","date not in range 1980-2099","filename too long","stream finishing","invalid zip data"];var err=function(ind,msg,nt){var e=new Error(msg||ec[ind]);e.code=ind;if(Error.captureStackTrace)Error.captureStackTrace(e,err);if(!nt)throw e;return e};var inflt=function(dat,buf,st){var sl=dat.length;if(!sl||st&&st.f&&!st.l)return buf||new u8(0);var noBuf=!buf||st;var noSt=!st||st.i;if(!st)st={};if(!buf)buf=new u8(sl*3);var cbuf=function(l2){var bl=buf.length;if(l2>bl){var nbuf=new u8(Math.max(bl*2,l2));nbuf.set(buf);buf=nbuf}};var final=st.f||0,pos=st.p||0,bt=st.b||0,lm=st.l,dm=st.d,lbt=st.m,dbt=st.n;var tbts=sl*8;do{if(!lm){final=bits(dat,pos,1);var type=bits(dat,pos+1,3);pos+=3;if(!type){var s=shft(pos)+4,l=dat[s-4]|dat[s-3]<<8,t=s+l;if(t>sl){if(noSt)err(0);break}if(noBuf)cbuf(bt+l);buf.set(dat.subarray(s,t),bt);st.b=bt+=l,st.p=pos=t*8,st.f=final;continue}else if(type==1)lm=flrm,dm=fdrm,lbt=9,dbt=5;else if(type==2){var hLit=bits(dat,pos,31)+257,hcLen=bits(dat,pos+10,15)+4;var tl=hLit+bits(dat,pos+5,31)+1;pos+=14;var ldt=new u8(tl);var clt=new u8(19);for(var i2=0;i2>>4;if(s<16){ldt[i2++]=s}else{var c=0,n=0;if(s==16)n=3+bits(dat,pos,3),pos+=2,c=ldt[i2-1];else if(s==17)n=3+bits(dat,pos,7),pos+=3;else if(s==18)n=11+bits(dat,pos,127),pos+=7;while(n--)ldt[i2++]=c}}var lt=ldt.subarray(0,hLit),dt=ldt.subarray(hLit);lbt=max(lt);dbt=max(dt);lm=hMap(lt,lbt,1);dm=hMap(dt,dbt,1)}else err(1);if(pos>tbts){if(noSt)err(0);break}}if(noBuf)cbuf(bt+131072);var lms=(1<>>4;pos+=c&15;if(pos>tbts){if(noSt)err(0);break}if(!c)err(2);if(sym<256)buf[bt++]=sym;else if(sym==256){lpos=pos,lm=null;break}else{var add=sym-254;if(sym>264){var i2=sym-257,b=fleb[i2];add=bits(dat,pos,(1<>>4;if(!d)err(3);pos+=d&15;var dt=fd[dsym];if(dsym>3){var b=fdeb[dsym];dt+=bits16(dat,pos)&(1<tbts){if(noSt)err(0);break}if(noBuf)cbuf(bt+131072);var end=bt+add;for(;bt>3&1)+(flg>>4&1);zs>0;zs-=!d[st++]);return st+(flg&2)};var gzl=function(d){var l=d.length;return(d[l-4]|d[l-3]<<8|d[l-2]<<16|d[l-1]<<24)>>>0};function gunzipSync(data,out){return inflt(data.subarray(gzs(data),-8),out||new u8(gzl(data)))}var td=typeof TextDecoder!="undefined"&&new TextDecoder();var tds=0;try{td.decode(et,{stream:true});tds=1}catch(e){}var gz_default=gunzipSync;var calculate_excerpt_region=(word_positions,excerpt_length)=>{if(word_positions.length===0){return 0}let words=[];for(const word of word_positions){words[word.location]=words[word.location]||0;words[word.location]+=word.balanced_score}if(words.length<=excerpt_length){return 0}let densest=words.slice(0,excerpt_length).reduce((partialSum,a)=>partialSum+a,0);let working_sum=densest;let densest_at=[0];for(let i2=0;i2densest){densest=working_sum;densest_at=[i2]}else if(working_sum===densest&&densest_at[densest_at.length-1]===i2-1){densest_at.push(i2)}}let midpoint=densest_at[Math.floor(densest_at.length/2)];return midpoint};var build_excerpt=(content,start,length,locations,not_before,not_from)=>{let is_zws_delimited=content.includes("\u200B");let fragment_words=[];if(is_zws_delimited){fragment_words=content.split("\u200B")}else{fragment_words=content.split(/[\r\n\s]+/g)}for(let word of locations){if(fragment_words[word]?.startsWith(``)){continue}fragment_words[word]=`${fragment_words[word]}`}let endcap=not_from??fragment_words.length;let startcap=not_before??0;if(endcap-startcapendcap){start=endcap-length}if(start{const anchors=fragment.anchors.filter((a)=>/h\d/i.test(a.element)&&a.text?.length&&/\S/.test(a.text)).sort((a,b)=>a.location-b.location);const results=[];let current_anchor_position=0;let current_anchor={title:fragment.meta["title"],url:fragment.url,weighted_locations:[],locations:[],excerpt:""};const add_result=(end_range)=>{if(current_anchor.locations.length){const relative_weighted_locations=current_anchor.weighted_locations.map((l)=>{return{weight:l.weight,balanced_score:l.balanced_score,location:l.location-current_anchor_position}});const excerpt_start=calculate_excerpt_region(relative_weighted_locations,desired_excerpt_length)+current_anchor_position;const excerpt_length=end_range?Math.min(end_range-excerpt_start,desired_excerpt_length):desired_excerpt_length;current_anchor.excerpt=build_excerpt(fragment.raw_content??"",excerpt_start,excerpt_length,current_anchor.locations,current_anchor_position,end_range);results.push(current_anchor)}};for(let word of fragment.weighted_locations){if(!anchors.length||word.location=anchors[0].location){next_anchor=anchors.shift()}let anchored_url=fragment.url;try{const url_is_fq=/^((https?:)?\/\/)/.test(anchored_url);if(url_is_fq){let fq_url=new URL(anchored_url);fq_url.hash=next_anchor.id;anchored_url=fq_url.toString()}else{if(!/^\//.test(anchored_url)){anchored_url=`/${anchored_url}`}let fq_url=new URL(`https://example.com${anchored_url}`);fq_url.hash=next_anchor.id;anchored_url=fq_url.toString().replace(/^https:\/\/example.com/,"")}}catch(e){console.error(`Pagefind: Couldn't process ${anchored_url} for a search result`)}current_anchor_position=next_anchor.location;current_anchor={title:next_anchor.text,url:anchored_url,anchor:next_anchor,weighted_locations:[word],locations:[word.location],excerpt:""}}}add_result(anchors[0]?.location);return results};var asyncSleep=async(ms=100)=>{return new Promise((r)=>setTimeout(r,ms))};var PagefindInstance=class{constructor(opts={}){this.version=pagefind_version;this.backend=wasm_bindgen;this.decoder=new TextDecoder("utf-8");this.wasm=null;this.basePath=opts.basePath||"/pagefind/";this.primary=opts.primary||false;if(this.primary&&!opts.basePath){this.initPrimary()}if(/[^\/]$/.test(this.basePath)){this.basePath=`${this.basePath}/`}if(window?.location?.origin&&this.basePath.startsWith(window.location.origin)){this.basePath=this.basePath.replace(window.location.origin,"")}this.baseUrl=opts.baseUrl||this.defaultBaseUrl();if(!/^(\/|https?:\/\/)/.test(this.baseUrl)){this.baseUrl=`/${this.baseUrl}`}this.indexWeight=opts.indexWeight??1;this.excerptLength=opts.excerptLength??30;this.mergeFilter=opts.mergeFilter??{};this.ranking=opts.ranking;this.highlightParam=opts.highlightParam??null;this.loaded_chunks={};this.loaded_filters={};this.loaded_fragments={};this.raw_ptr=null;this.searchMeta=null;this.languages=null}initPrimary(){let derivedBasePath=import.meta.url.match(/^(.*\/)pagefind.js.*$/)?.[1];if(derivedBasePath){this.basePath=derivedBasePath}else{console.warn(["Pagefind couldn't determine the base of the bundle from the import path. Falling back to the default.","Set a basePath option when initialising Pagefind to ignore this message."].join("\n"))}}defaultBaseUrl(){let default_base=this.basePath.match(/^(.*\/)_?pagefind/)?.[1];return default_base||"/"}async options(options2){const opts=["basePath","baseUrl","indexWeight","excerptLength","mergeFilter","highlightParam","ranking"];for(const[k,v]of Object.entries(options2)){if(k==="mergeFilter"){let filters2=this.stringifyFilters(v);let ptr=await this.getPtr();this.raw_ptr=this.backend.add_synthetic_filter(ptr,filters2)}else if(k==="ranking"){await this.set_ranking(options2.ranking)}else if(opts.includes(k)){if(k==="basePath"&&typeof v==="string")this.basePath=v;if(k==="baseUrl"&&typeof v==="string")this.baseUrl=v;if(k==="indexWeight"&&typeof v==="number")this.indexWeight=v;if(k==="excerptLength"&&typeof v==="number")this.excerptLength=v;if(k==="mergeFilter"&&typeof v==="object")this.mergeFilter=v;if(k==="highlightParam"&&typeof v==="string")this.highlightParam=v}else{console.warn(`Unknown Pagefind option ${k}. Allowed options: [${opts.join(", ")}]`)}}}decompress(data,file="unknown file"){if(this.decoder.decode(data.slice(0,12))==="pagefind_dcd"){return data.slice(12)}data=gz_default(data);if(this.decoder.decode(data.slice(0,12))!=="pagefind_dcd"){console.error(`Decompressing ${file} appears to have failed: Missing signature`);return data}return data.slice(12)}async set_ranking(ranking){if(!ranking)return;let rankingWeights={term_similarity:ranking.termSimilarity??null,page_length:ranking.pageLength??null,term_saturation:ranking.termSaturation??null,term_frequency:ranking.termFrequency??null};let ptr=await this.getPtr();this.raw_ptr=this.backend.set_ranking_weights(ptr,JSON.stringify(rankingWeights))}async init(language,opts){await this.loadEntry();let index=this.findIndex(language);let lang_wasm=index.wasm?index.wasm:"unknown";let resources=[this.loadMeta(index.hash)];if(opts.load_wasm===true){resources.push(this.loadWasm(lang_wasm))}await Promise.all(resources);this.raw_ptr=this.backend.init_pagefind(new Uint8Array(this.searchMeta));if(Object.keys(this.mergeFilter)?.length){let filters2=this.stringifyFilters(this.mergeFilter);let ptr=await this.getPtr();this.raw_ptr=this.backend.add_synthetic_filter(ptr,filters2)}if(this.ranking){await this.set_ranking(this.ranking)}}async loadEntry(){try{let entry_response=await fetch(`${this.basePath}pagefind-entry.json?ts=${Date.now()}`);let entry_json=await entry_response.json();this.languages=entry_json.languages;if(entry_json.version!==this.version){if(this.primary){console.warn(["Pagefind JS version doesn't match the version in your search index.",`Pagefind JS: ${this.version}. Pagefind index: ${entry_json.version}`,"If you upgraded Pagefind recently, you likely have a cached pagefind.js file.","If you encounter any search errors, try clearing your cache."].join("\n"))}else{console.warn(["Merging a Pagefind index from a different version than the main Pagefind instance.",`Main Pagefind JS: ${this.version}. Merged index (${this.basePath}): ${entry_json.version}`,"If you encounter any search errors, make sure that both sites are running the same version of Pagefind."].join("\n"))}}}catch(e){console.error(`Failed to load Pagefind metadata: +${e?.toString()}`);throw new Error("Failed to load Pagefind metadata")}}findIndex(language){if(this.languages){let index=this.languages[language];if(index)return index;index=this.languages[language.split("-")[0]];if(index)return index;let topLang=Object.values(this.languages).sort((a,b)=>b.page_count-a.page_count);if(topLang[0])return topLang[0]}throw new Error("Pagefind Error: No language indexes found.")}async loadMeta(index){try{let compressed_resp=await fetch(`${this.basePath}pagefind.${index}.pf_meta`);let compressed_meta=await compressed_resp.arrayBuffer();this.searchMeta=this.decompress(new Uint8Array(compressed_meta),"Pagefind metadata")}catch(e){console.error(`Failed to load the meta index: +${e?.toString()}`)}}async loadWasm(language){try{const wasm_url=`${this.basePath}wasm.${language}.pagefind`;let compressed_resp=await fetch(wasm_url);let compressed_wasm=await compressed_resp.arrayBuffer();const final_wasm=this.decompress(new Uint8Array(compressed_wasm),"Pagefind WebAssembly");if(!final_wasm){throw new Error("No WASM after decompression")}this.wasm=await this.backend(final_wasm)}catch(e){console.error(`Failed to load the Pagefind WASM: +${e?.toString()}`);throw new Error(`Failed to load the Pagefind WASM: +${e?.toString()}`)}}async _loadGenericChunk(url,method){try{let compressed_resp=await fetch(url);let compressed_chunk=await compressed_resp.arrayBuffer();let chunk=this.decompress(new Uint8Array(compressed_chunk),url);let ptr=await this.getPtr();this.raw_ptr=this.backend[method](ptr,chunk)}catch(e){console.error(`Failed to load the index chunk ${url}: +${e?.toString()}`)}}async loadChunk(hash){if(!this.loaded_chunks[hash]){const url=`${this.basePath}index/${hash}.pf_index`;this.loaded_chunks[hash]=this._loadGenericChunk(url,"load_index_chunk")}return await this.loaded_chunks[hash]}async loadFilterChunk(hash){if(!this.loaded_filters[hash]){const url=`${this.basePath}filter/${hash}.pf_filter`;this.loaded_filters[hash]=this._loadGenericChunk(url,"load_filter_chunk")}return await this.loaded_filters[hash]}async _loadFragment(hash){let compressed_resp=await fetch(`${this.basePath}fragment/${hash}.pf_fragment`);let compressed_fragment=await compressed_resp.arrayBuffer();let fragment=this.decompress(new Uint8Array(compressed_fragment),`Fragment ${hash}`);return JSON.parse(new TextDecoder().decode(fragment))}async loadFragment(hash,weighted_locations=[],search_term){if(!this.loaded_fragments[hash]){this.loaded_fragments[hash]=this._loadFragment(hash)}let fragment=await this.loaded_fragments[hash];fragment.weighted_locations=weighted_locations;fragment.locations=weighted_locations.map((l)=>l.location);if(!fragment.raw_content){fragment.raw_content=fragment.content.replace(//g,">");fragment.content=fragment.content.replace(/\u200B/g,"")}if(!fragment.raw_url){fragment.raw_url=fragment.url}fragment.url=this.processedUrl(fragment.raw_url,search_term);const excerpt_start=calculate_excerpt_region(weighted_locations,this.excerptLength);fragment.excerpt=build_excerpt(fragment.raw_content,excerpt_start,this.excerptLength,fragment.locations);fragment.sub_results=calculate_sub_results(fragment,this.excerptLength);return fragment}fullUrl(raw){if(/^(https?:)?\/\//.test(raw)){return raw}return`${this.baseUrl}/${raw}`.replace(/\/+/g,"/").replace(/^(https?:\/)/,"$1/")}processedUrl(url,search_term){const normalized=this.fullUrl(url);if(this.highlightParam===null){return normalized}let individual_terms=search_term.split(/\s+/);try{let processed=new URL(normalized);for(const term of individual_terms){processed.searchParams.append(this.highlightParam,term)}return processed.toString()}catch(e){try{let processed=new URL(`https://example.com${normalized}`);for(const term of individual_terms){processed.searchParams.append(this.highlightParam,term)}return processed.toString().replace(/^https:\/\/example\.com/,"")}catch(e2){return normalized}}}async getPtr(){while(this.raw_ptr===null){await asyncSleep(50)}if(!this.raw_ptr){console.error("Pagefind: WASM Error (No pointer)");throw new Error("Pagefind: WASM Error (No pointer)")}return this.raw_ptr}parseFilters(str){let output={};if(!str)return output;for(const block of str.split("__PF_FILTER_DELIM__")){let[filter,values]=block.split(/:(.*)$/);output[filter]={};if(values){for(const valueBlock of values.split("__PF_VALUE_DELIM__")){if(valueBlock){let extract=valueBlock.match(/^(.*):(\d+)$/);if(extract){let[,value,count]=extract;output[filter][value]=parseInt(count)??count}}}}}return output}stringifyFilters(obj={}){return JSON.stringify(obj)}stringifySorts(obj={}){let sorts=Object.entries(obj);for(let[sort,direction]of sorts){if(sorts.length>1){console.warn(`Pagefind was provided multiple sort options in this search, but can only operate on one. Using the ${sort} sort.`)}if(direction!=="asc"&&direction!=="desc"){console.warn(`Pagefind was provided a sort with unknown direction ${direction}. Supported: [asc, desc]`)}return`${sort}:${direction}`}return``}async filters(){let ptr=await this.getPtr();let filters2=this.backend.request_all_filter_indexes(ptr);let filter_chunks=filters2.split(" ").filter((v)=>v).map((chunk)=>this.loadFilterChunk(chunk));await Promise.all([...filter_chunks]);ptr=await this.getPtr();let results=this.backend.filters(ptr);return this.parseFilters(results)}async preload(term,options2={}){await this.search(term,{...options2,preload:true})}async search(term,options2={}){options2={verbose:false,filters:{},sort:{},...options2};const log=(str)=>{if(options2.verbose)console.log(str)};log(`Starting search on ${this.basePath}`);let start=Date.now();let ptr=await this.getPtr();let filter_only=term===null;term=term??"";let exact_search=/^\s*".+"\s*$/.test(term);if(exact_search){log(`Running an exact search`)}term=term.toLowerCase().trim().replace(/[\.`~!@#\$%\^&\*\(\)\{\}\[\]\\\|:;'",<>\/\?\-]/g,"").replace(/\s{2,}/g," ").trim();log(`Normalized search term to ${term}`);if(!term?.length&&!filter_only){return{results:[],unfilteredResultCount:0,filters:{},totalFilters:{},timings:{preload:Date.now()-start,search:Date.now()-start,total:Date.now()-start}}}let sort_list=this.stringifySorts(options2.sort);log(`Stringified sort to ${sort_list}`);const filter_list=this.stringifyFilters(options2.filters);log(`Stringified filters to ${filter_list}`);let index_resp=this.backend.request_indexes(ptr,term);let filter_resp=this.backend.request_filter_indexes(ptr,filter_list);let chunks=index_resp.split(" ").filter((v)=>v).map((chunk)=>this.loadChunk(chunk));let filter_chunks=filter_resp.split(" ").filter((v)=>v).map((chunk)=>this.loadFilterChunk(chunk));await Promise.all([...chunks,...filter_chunks]);log(`Loaded necessary chunks to run search`);if(options2.preload){log(`Preload \u2014 bailing out of search operation now.`);return null}ptr=await this.getPtr();let searchStart=Date.now();let result=this.backend.search(ptr,term,filter_list,sort_list,exact_search);log(`Got the raw search result: ${result}`);let[unfilteredResultCount,all_results,filters2,totalFilters]=result.split(/:([^:]*):(.*)__PF_UNFILTERED_DELIM__(.*)$/);let filterObj=this.parseFilters(filters2);let totalFilterObj=this.parseFilters(totalFilters);log(`Remaining filters: ${JSON.stringify(result)}`);let results=all_results.length?all_results.split(" "):[];let resultsInterface=results.map((result2)=>{let[hash,score,all_locations]=result2.split("@");log(`Processing result: + hash:${hash} + score:${score} + locations:${all_locations}`);let weighted_locations=all_locations.length?all_locations.split(",").map((l)=>{let[weight,balanced_score,location]=l.split(">");return{weight:parseInt(weight)/24,balanced_score:parseFloat(balanced_score),location:parseInt(location)}}):[];let locations=weighted_locations.map((l)=>l.location);return{id:hash,score:parseFloat(score)*this.indexWeight,words:locations,data:async()=>await this.loadFragment(hash,weighted_locations,term)}});const searchTime=Date.now()-searchStart;const realTime=Date.now()-start;log(`Found ${results.length} result${results.length == 1 ? "" : "s"} for "${term}" in ${Date.now() - searchStart}ms (${Date.now() - start}ms realtime)`);return{results:resultsInterface,unfilteredResultCount:parseInt(unfilteredResultCount),filters:filterObj,totalFilters:totalFilterObj,timings:{preload:realTime-searchTime,search:searchTime,total:realTime}}}};var Pagefind=class{constructor(options2={}){this.backend=wasm_bindgen;this.primaryLanguage="unknown";this.searchID=0;this.primary=new PagefindInstance({...options2,primary:true});this.instances=[this.primary];this.init(options2?.language)}async options(options2){await this.primary.options(options2)}async init(overrideLanguage){if(document?.querySelector){const langCode=document.querySelector("html")?.getAttribute("lang")||"unknown";this.primaryLanguage=langCode.toLocaleLowerCase()}await this.primary.init(overrideLanguage?overrideLanguage:this.primaryLanguage,{load_wasm:true})}async mergeIndex(indexPath,options2={}){if(this.primary.basePath.startsWith(indexPath)){console.warn(`Skipping mergeIndex ${indexPath} that appears to be the same as the primary index (${this.primary.basePath})`);return}let newInstance=new PagefindInstance({primary:false,basePath:indexPath});this.instances.push(newInstance);while(this.primary.wasm===null){await asyncSleep(50)}await newInstance.init(options2.language||this.primaryLanguage,{load_wasm:false});delete options2["language"];await newInstance.options(options2)}mergeFilters(filters2){const merged={};for(const searchFilter of filters2){for(const[filterKey,values]of Object.entries(searchFilter)){if(!merged[filterKey]){merged[filterKey]=values;continue}else{const filter=merged[filterKey];for(const[valueKey,count]of Object.entries(values)){filter[valueKey]=(filter[valueKey]||0)+count}}}}return merged}async filters(){let filters2=await Promise.all(this.instances.map((i2)=>i2.filters()));return this.mergeFilters(filters2)}async preload(term,options2={}){await Promise.all(this.instances.map((i2)=>i2.preload(term,options2)))}async debouncedSearch(term,options2,debounceTimeoutMs){const thisSearchID=++this.searchID;this.preload(term,options2);await asyncSleep(debounceTimeoutMs);if(thisSearchID!==this.searchID){return null}const searchResult=await this.search(term,options2);if(thisSearchID!==this.searchID){return null}return searchResult}async search(term,options2={}){let search2=await Promise.all(this.instances.map((i2)=>i2.search(term,options2)));const filters2=this.mergeFilters(search2.map((s)=>s.filters));const totalFilters=this.mergeFilters(search2.map((s)=>s.totalFilters));const results=search2.map((s)=>s.results).flat().sort((a,b)=>b.score-a.score);const timings=search2.map((s)=>s.timings);const unfilteredResultCount=search2.reduce((sum,s)=>sum+s.unfilteredResultCount,0);return{results,unfilteredResultCount,filters:filters2,totalFilters,timings}}};var pagefind=void 0;var initial_options=void 0;var init_pagefind=()=>{if(!pagefind){pagefind=new Pagefind(initial_options??{})}};var options=async(new_options)=>{if(pagefind){await pagefind.options(new_options)}else{initial_options=new_options}};var init=async()=>{init_pagefind()};var destroy=async()=>{pagefind=void 0;initial_options=void 0};var mergeIndex=async(indexPath,options2)=>{init_pagefind();return await pagefind.mergeIndex(indexPath,options2)};var search=async(term,options2)=>{init_pagefind();return await pagefind.search(term,options2)};var debouncedSearch=async(term,options2,debounceTimeoutMs=300)=>{init_pagefind();return await pagefind.debouncedSearch(term,options2,debounceTimeoutMs)};var preload=async(term,options2)=>{init_pagefind();return await pagefind.preload(term,options2)};var filters=async()=>{init_pagefind();return await pagefind.filters()};export{debouncedSearch,destroy,filters,init,mergeIndex,options,preload,search} \ No newline at end of file diff --git a/docs/pagefind/wasm.en.pagefind b/docs/pagefind/wasm.en.pagefind new file mode 100644 index 0000000..897cd6b Binary files /dev/null and b/docs/pagefind/wasm.en.pagefind differ diff --git a/docs/pagefind/wasm.unknown.pagefind b/docs/pagefind/wasm.unknown.pagefind new file mode 100644 index 0000000..3ee72c9 Binary files /dev/null and b/docs/pagefind/wasm.unknown.pagefind differ diff --git a/docs/reference/compatibility/index.html b/docs/reference/compatibility/index.html new file mode 100644 index 0000000..c33f9f0 --- /dev/null +++ b/docs/reference/compatibility/index.html @@ -0,0 +1,410 @@ + vJailbreak Compatibility | vJailbreak + Skip to content

    vJailbreak Compatibility

    Following is a list of systems vJailbreak has been validated with. Please reach out to us to report any issues or to add support for additional versions.

    +

    VMware

    +
      +
    • VMware vCenter Server 6.7
    • +
    • VMware vCenter Server 7.0
    • +
    • VMware vCenter Server 8.0
    • +
    +

    Operating System

    +

    The list of operating system is large and while we have attempted +to test some of them, there are still gaps specifically with the older +versions. This list is expected to grow over time and we will continue to +add support for additional versions. Verified implies it has been tested and converted vs expected means it has not been tested but is expected to work.

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Operating SystemPlatformVerifiedExpected
    AlmaLinuxlinux/amd64NoYes
    Amazon Linux 2linux/amd64NoYes
    CentOS 4/5linux/i386NoYes
    CentOS 4linux/amd64NoYes
    CentOS 5linux/amd64YesYes
    CentOS 6linux/amd64YesYes
    CentOS 7linux/amd64YesYes
    CentOS 8linux/amd64YesYes
    CentOS 9linux/amd64YesYes
    CentOS Stream10linux/amd64NoYes
    Debian GNU/Linux 8 (64-bit)linux/amd64NoYes
    Debian 12linux/amd64YesYes
    FreeBSD 14bsd/amd64YesYes
    Microsoft Windows 11windows/amd64YesYes
    Microsoft Windows 11 Enterprisewindows/amd64YesYes
    Microsoft Windows Server 2012windows/amd64YesYes
    Microsoft Windows Server 2016windows/amd64YesYes
    Microsoft Windows Server 2019windows/amd64YesYes
    Microsoft Windows Server 2022windows/amd64YesYes
    Microsoft Windows Server 2025windows/amd64YesYes
    Oracle Linux 7linux/amd64NoYes
    Oracle Linux 8linux/amd64YesYes
    Red Hat Enterprise Linux 10linux/amd64YesYes
    Red Hat Enterprise Linux 8linux/amd64YesYes
    Red Hat Enterprise Linux 9linux/amd64YesYes
    Red Hat Enterprise Linux 7linux/amd64NoYes
    Red Hat Enterprise Linux 5linux/amd64NoYes
    Red Hat Enterprise Linux 4linux/amd64NoYes
    Rocky 8linux/amd64YesYes
    Rocky 9linux/amd64YesYes
    Rocky 10linux/amd64YesYes
    SUSE Linux Enterprise 15linux/amd64YesYes
    Ubuntu Linux 14linux/amd64YesYes
    Ubuntu Linux 15linux/amd64YesYes
    Ubuntu Linux 16linux/amd64YesYes
    Ubuntu Linux 17linux/amd64YesYes
    Ubuntu Linux 22.04linux/amd64YesYes
    Ubuntu Linux 24.04linux/amd64YesYes
    VMware Photon OSlinux/amd64NoNo
    \ No newline at end of file diff --git a/docs/reference/example/index.html b/docs/reference/example/index.html deleted file mode 100644 index d5f7c6c..0000000 --- a/docs/reference/example/index.html +++ /dev/null @@ -1,144 +0,0 @@ - Example Reference | vJailbreak - Skip to content

    Example Reference

    Reference pages are ideal for outlining how things work in terse and clear terms. -Less concerned with telling a story or addressing a specific use case, they should give a comprehensive outline of what you’re documenting.

    -

    Further reading

    -
    \ No newline at end of file diff --git a/docs/reference/known-limitations/index.html b/docs/reference/known-limitations/index.html new file mode 100644 index 0000000..47caeef --- /dev/null +++ b/docs/reference/known-limitations/index.html @@ -0,0 +1,433 @@ + Known Limitations | vJailbreak + Skip to content

    Known Limitations

    This page documents known limitations, unsupported configurations, and important caveats in vJailbreak. Review this page before planning a migration to avoid unexpected failures.

    +

    Windows Dynamic Disk (LDM)

    +

    Windows VMs whose system volume sits on a dynamic disk (Logical Disk Manager / LDM) are supported, but they follow a dedicated migration path. virt-v2v cannot convert these guests, so vJailbreak skips conversion, brings the VM up on an emulated SATA controller, and waits at the LDM Boot Verification phase for you to move it to virtio.

    +

    vJailbreak detects this automatically — there is nothing to select in the migration form.

    + + + + + + + + + + + + + + + + + + + + + +
    ConfigurationResult
    Root: Basic, Data: LDMMigrates normally — import LDM data disks in Windows post-migration
    Root: LDM, Data: BasicSupported via the SATA-first path — manual cutover required
    Root: LDM, Data: LDMSupported via the SATA-first path — manual cutover required
    +

    The following limitations apply when the system volume is on LDM:

    + + + + + + + + + + + + + + + + + + + + + + + + + +
    LimitationDetail
    Conversion-time features do not runVMware Tools removal, network persistence and user firstboot scripts are all performed by virt-v2v during conversion. Conversion is skipped, so these must be handled manually inside the guest.
    VirtIO drivers must be pre-installedDrivers cannot be injected offline into an LDM volume. Install the VirtIO guest tools on the source VM before migrating.
    The SAN policy must be set beforehandWithout san policy=onlineall, Windows brings the migrated disks up offline and the LDM volume set is left broken.
    The migration requires manual interventionThe migration pauses at LDM Boot Verification until you confirm the VM booted. There is no timeout, so the migration will not complete unattended.
    +

    See the full guide: Windows Dynamic Disk (LDM) Migration.

    +

    Active Directory-Joined VMs

    +

    Domain Controllers

    +

    Migrating Active Directory Domain Controller VMs is strongly not recommended. The core risk is specific to how vJailbreak works: virt-v2v performs a disk-level conversion and creates a new VM on a different hypervisor. VM-GenerationID — the hypervisor metadata that Windows Server 2012+ uses to detect unsafe restores — is not stored on disk and is not preserved through this process. The migrated DC starts with a new (or absent) VMGenID, which Windows AD treats as an unsafe restore/clone.

    +

    What happens depends on the Windows version:

    +
      +
    • Windows Server 2012 and later: The lost VMGenID triggers Windows’ built-in safeguards. The DC automatically resets its invocation ID and forces a non-authoritative resync against replication partners. The domain may recover if other DCs are reachable, but this is unreliable in production and is not a supported migration path.
    • +
    • Windows Server 2008 R2 and earlier (no VMGenID support): A genuine USN rollback can occur. The domain silently stops accepting replication from the migrated DC, and the AD environment can diverge without obvious errors. This is difficult to detect and hard to recover from.
    • +
    +

    In both cases the source DC must be permanently removed from the domain before or immediately after the migrated copy is brought online. Running both simultaneously on the same domain will corrupt AD.

    +

    Recommended approach (from Microsoft guidance):

    +
      +
    1. Provision a new DC in the target OpenStack environment using standard AD promotion.
    2. +
    3. Let AD replication populate it from an existing domain controller.
    4. +
    5. Decommission the source DC via dcpromo or Server Manager once replication is verified complete.
    6. +
    +

    If you must migrate a DC (lab/test environments, single-DC setups with no alternative), take these precautions:

    +
      +
    • Cleanly shut down the source DC before migration — do not snapshot a running DC.
    • +
    • Migrate only one DC at a time.
    • +
    • After the migrated DC boots, verify replication health immediately: +
      Terminal window
      repadmin /replsummary
      dcdiag /test:replications
      +
    • +
    • Confirm time synchronization (Kerberos requires clocks within 5 minutes of each other).
    • +
    • Verify DNS is resolving correctly for all domain members.
    • +
    • Decommission the source DC immediately — never run the original and migrated DC on the same domain simultaneously.
    • +
    +

    Member Servers and Workstations

    +

    Migrating domain-joined member VMs (non-DC servers and workstations) is generally safe. The machine account password is stored in the VM’s own LSA secrets and is copied with the disk, so domain membership typically survives the migration intact.

    +

    A few edge cases can cause domain authentication to fail post-migration:

    +
      +
    • Kerberos clock skew: If the migrated VM’s clock is more than 5 minutes off from the domain controller, Kerberos authentication will fail. Sync the VM’s clock immediately after boot.
    • +
    • DNS resolution failures: The VM must be able to resolve the domain controller’s name and locate AD SRV records. Verify DNS settings after migration.
    • +
    • Pre-existing stale computer account: If the source VM had been offline for an extended period (typically 90+ days) before migration, the domain controller may have already invalidated its computer account. This is a pre-existing condition unrelated to the migration itself.
    • +
    +

    If users see The trust relationship between this workstation and the primary domain failed after migration, run the following to reset the account:

    +
    Terminal window
    # Option 1 — reset computer account password without rejoining
    netdom resetpwd /server:<domain-controller> /userd:<domain\admin> /passwordd:*
    +
    # Option 2 — rejoin the domain
    Remove-Computer -WorkgroupName WORKGROUP -Force
    Add-Computer -DomainName <domain> -Credential <domain\admin> -Restart
    +

    Persist Network: Windows Server 2012 and Below

    +

    The Persist source network interfaces option does not work for Windows Server 2012 and earlier (including Windows Server 2008 R2 and Windows Server 2008).

    +

    Network interface name persistence depends on PowerShell capabilities, the Windows registry structure for network adapters, and a compatible version of pnputil. These prerequisites are not met on Windows Server 2012 and earlier.

    +

    Workaround: Manually reconfigure network interface names and static IP settings inside the VM after migration.

    +

    Assign IP and Persist Network Cannot Be Used Together

    +

    The Assign IP and Persist Network (Persist source network interfaces) options are mutually exclusive. Enabling both simultaneously produces undefined behavior and the migration may not apply either setting correctly.

    +

    Rule: Use one or the other — not both.

    +
      +
    • Use Assign IP when you need to set a specific IP address on the destination VM.
    • +
    • Use Persist Network when you need to preserve the source VM’s interface names and static routes.
    • +
    +

    Multi-IP Assignment Not Supported

    +

    Only one IP address per network interface is supported in the Assign IPs field. The UI enforces this — the field accepts a single IP per interface. If multiple IPs are specified via CLI, the migration will fail.

    +

    Workaround: Assign additional IPs manually inside the VM after migration, or use OpenStack port configuration to attach additional floating IPs post-migration.

    +

    VMware Tools Removal: Residual Artifacts

    +

    The VMware Tools removal process performed by virt-v2v during migration may leave behind residual files and registry entries on the destination VM.

    +

    These artifacts are typically harmless but may appear in application logs or security scans.

    +

    For a full list of known residual artifacts and cleanup steps, see: VMware Residual Artifacts.

    +

    Multi-Boot VMs Not Supported

    +

    vJailbreak does not support VMs with multiple bootable operating systems (multi-boot configurations). virt-v2v inspects only a single OS installation per VM and cannot convert multi-boot disk layouts.

    +

    Workaround: Migrate each OS as a separate VM, or convert the disk to a single-boot configuration before migration.

    +

    SUSE Linux (SLES / SLED) with Legacy GRUB 0.97

    +

    Older SUSE-family VMs — SLES, SLED, and other SUSE distributions — that still boot with legacy GRUB (0.97) require special handling. These are typically BIOS VMs on a multi-disk layout, where the first boot stage sits in one disk’s MBR while its second stage and /boot live on a separate disk. After migration to KVM, the virtual disks are re-numbered and no longer match the original VMware ordering, so GRUB cannot find its second stage and the VM fails to boot with GRUB Error 21.

    +

    In such scenarios, we recommend upgrading to GRUB2.

    +

    Why we upgrade GRUB: GRUB 0.97 is too old and fragile — it hard-codes disk numbers and block offsets that break the moment the hypervisor re-orders disks. virt-v2v also can’t reconfigure GRUB 0.97 for KVM; it only manages GRUB2.

    +

    NOTE: On these older SUSE releases GRUB2 ships only as an EFI build (no legacy-BIOS version), so upgrading GRUB forces a switch to UEFI.

    + +

    Some RHEL 7.x guests are missing the /boot/grub/grub.cfg compatibility symlink that grubby (used internally by virt-v2v-in-place) expects to point at /boot/grub2/grub.cfg. GRUB2 itself is configured correctly — only this symlink is missing — and conversion fails with:

    +
    libguestfs error: command:
    error opening /boot/grub/grub.cfg for read:
    No such file or directory
    +

    Workaround: Verify and, if needed, recreate the symlink before migrating. See virt-v2v-in-place fails on RHEL 7 for details.

    +

    Hotplug Flavor Requirements

    +

    OpenStack hotplug (live CPU/RAM resize without VM reboot) is supported post-migration, but only if the VM is migrated with a hotplug-capable flavor.

    +

    To use hotplug after migration:

    +
      +
    1. +

      Platform9 Private Cloud Director (PCD): PCD provides a hotplug base flavor named hotplug by default. While triggering the migration in vJailbreak, select the hotplug flavor for the VMs that need live resize.

      +
    2. +
    3. +

      Other OpenStack environments: ask your OpenStack admin to create a flavor with hotplug-enabled extra specs, for example:

      +
      Terminal window
      openstack flavor set <flavor-name> \
      --property hw:cpu_policy=mixed \
      --property hw:cpu_max_vcpus=<max> \
      --property hw:mem_page_size=any
      +

      Then assign this flavor in the vJailbreak migration form before starting the migration.

      +
    4. +
    5. +

      After migration, resize the VM in OpenStack using the hotplug capability.

      +
    6. +
    +

    Hotplug Metadata and Resize Headroom

    +

    When a hotplug base flavor (0 vCPU, 0 RAM — such as PCD’s default hotplug flavor) is assigned, vJailbreak creates the target VM with the following server metadata:

    + + + + + + + + + + + + + + + + + + + + + + + + + +
    Metadata keyValue
    HOTPLUG_CPUSource VM’s current vCPU count
    HOTPLUG_MEMORYSource VM’s current memory (MB)
    HOTPLUG_CPU_MAX2x the source VM’s vCPU count
    HOTPLUG_MEMORY_MAX2x the source VM’s memory (MB)
    +

    The max keys define the ceiling for post-migration live resize. They are set to twice the source VM’s size so the migrated VM has hotplug headroom out of the box — for example, a VM migrated with 2 vCPUs and 4096 MB RAM can be live-resized up to 4 vCPUs and 8192 MB RAM.

    + + +

    PCI Slot Exhaustion When Attaching Disks with virtio-blk

    +

    During conversion, vJailbreak attaches the target volumes to the vJailbreak VM (or its agent VMs). If the vJailbreak image is uploaded without a disk bus setting, OpenStack uses the default virtio-blk bus, where every attached volume consumes its own PCI slot. Migrating VMs with many disks, or running many parallel migrations on one agent, Maximum 26 devices can be attached after which PCI slots will exhaust and volume attach fails with:

    +
    libvirt.libvirtError: internal error: No more available PCI slots
    +

    Workaround: Set the disk bus to virtio-scsi on the vJailbreak image before creating the vJailbreak VM. All attached volumes then share a single SCSI controller (one PCI slot, up to 256 devices):

    +
    Terminal window
    openstack image set \
    --property hw_disk_bus=scsi \
    --property hw_scsi_model=virtio-scsi \
    <vjailbreak-image-name-or-ID>
    + +

    See the full troubleshooting entry: Disk attach fails during migration: No more available PCI slots.

    +

    Low Disk Space in the Source VM

    +

    Before starting conversion, virt-v2v checks that each filesystem inside the source VM has sufficient free space. If any filesystem is too full, the conversion fails before it begins.

    +

    Minimum free space required inside the source VM (source: virt-v2v docs):

    + + + + + + + + + + + + + + + + + + + + + + + + + +
    FilesystemMinimum free space
    Linux root (/)100 MB
    Linux /boot50 MB (needed to rebuild initramfs)
    Windows C: drive100 MB (virtio drivers and guest agents are copied in)
    Any other mountable filesystem10 MB
    +

    Each filesystem must also have at least 100 free inodes.

    +

    Workaround: Before migrating, free up space inside the source VM on any full partitions. Check with df -h (Linux) or Disk Management (Windows).

    +

    Hot Migration Requires Virtual Hardware Version 7 or Newer

    +

    vJailbreak Hot migration (Copy live VMs, then power off) relies on VMware Changed Block Tracking (CBT) to copy only changed disk blocks during the live sync phase. CBT is available only on VMs running virtual hardware version 7 or newer (VMware KB 1020128).

    +

    VMs on older hardware versions (for example, version 4) do not expose the CBT property at all, so Hot migration cannot track changed blocks for them.

    +

    Symptom: A Hot migration of a legacy-hardware VM fails at the CBT step. The reported error looks similar to:

    +
    CBT is not enabled on disk <id>
    +

    What to do — choose one:

    +
      +
    1. Use cold migration (Power off VMs, then copy) for these VMs. Cold migration copies each disk once in full while the VM is powered off and does not use CBT, so it works on any hardware version. (Recommended — requires no changes to the source VM.)
    2. +
    3. Upgrade the VM’s virtual hardware version to 7 or newer in vCenter, then use Hot migration if you need minimal downtime. Upgrading the hardware version requires a VM power-off and cannot be reversed — review VMware’s documentation before proceeding.
    4. +
    + + + + + + + + + + + + + + + + + + + + +
    VM virtual hardware versionHot migrationCold migration
    7 or newerSupportedSupported
    Below 7 (e.g., version 4)Not supported — use cold migrationSupported
    + +

    vJailbreak Accelerated Copy

    +

    The limitations below are specific to vJailbreak Accelerated Copy. See its full limitations list for the remaining constraints.

    +

    Concurrent Disk Attach Can Fail

    +

    When several migrations reach the disk-attach step at the same time on the same Proxy VM, vCenter does not always handle the simultaneous reconfigure tasks gracefully and rejects some attach requests, failing those migrations. This is a transient race condition — the migrations that attached first are unaffected and continue into the copy phase.

    +

    Workaround: Retry the failed migrations once the others have moved into the copy phase. To reduce the chance of the race, stagger migration start times or distribute migrations across additional Proxy VMs.

    +

    See Proxy VM disk attach fails when several migrations start together.

    +

    Proxy VM Must Use a PVSCSI Controller

    +

    vJailbreak matches each attached snapshot disk to a block device inside the Proxy VM by disk UUID, which works only on the VMware Paravirtual (PVSCSI) controller. The Proxy VM’s SCSI controller 0 must be PVSCSI — LSI Logic SAS, LSI Logic Parallel, and BusLogic Parallel are not supported. Migrations using any other type fail with could not identify block device for disk <uuid>.

    +

    Workaround: Power off the Proxy VM and set Edit Settings → SCSI controller 0 → Change Type → VMware Paravirtual before registering it in vJailbreak.

    +

    See Configure the SCSI Controller Type on the Proxy VM and could not identify block device.

    +

    Application Reboot During Migration

    +

    Cold migration (Power off VMs, then copy) powers off the source VM before copying its disk. The destination VM boots fresh after migration completes. Applications must tolerate a reboot — any in-memory state, open transactions, or non-persistent connections will be lost.

    +

    Hot migration (Copy live VMs, then power off) minimizes downtime but still requires a brief power-off during the final cutover phase to synchronize the last changed blocks. Applications should be tested for graceful handling of this cutover reboot.

    + +

    Retrying a Failed Migration

    +

    The Retry action reopens a failed migration in the migration form so its configuration can be corrected. The following limitations apply. See Retry a Failed Migration for the full workflow.

    + + + + + + + + + + + + + + + + + + + + + + + + + +
    LimitationDetail
    VMs with RDM disks cannot be retriedShared RDM disk state prevents an automatic retry. The Retry button is disabled for these migrations and the migration must be restarted manually.
    A retry always produces a single-VM planRetrying one VM from a plan that covered several VMs moves that VM into a plan of its own. The remaining VMs stay in the original plan and are unaffected.
    Bulk retry cannot change configurationRetry Selected restarts each migration with its existing configuration. To change settings, retry the migration individually.
    Credentials and source cluster are lockedA retry cannot change the VMware or OpenStack credentials or the source cluster. Create a new migration instead.
    +
    \ No newline at end of file diff --git a/docs/reference/reference/index.html b/docs/reference/reference/index.html index 070ba1b..798c83f 100644 --- a/docs/reference/reference/index.html +++ b/docs/reference/reference/index.html @@ -1,4 +1,4 @@ - vJailbreak CRD references | vJailbreak + Skip to content
    Skip to content

    vJailbreak CRD references

    The following custom resource definitions (CRD) are deployed in the same namespace as the Migration Controller pod. By default, the namespace is migration-system.

    +

    vJailbreak CRD references

    The following custom resource definitions (CRD) are deployed in the same namespace as the Migration Controller pod. By default, the namespace is migration-system.

    Credentials

    OpenStack

    • OpenstackCreds use the variables from the openstack.rc file. All fields are required except OS_INSECURE
    -
    apiVersion: vjailbreak.k8s.pf9.io/v1alpha1
    kind: OpenstackCreds
    metadata:
    name: osc1
    namespace: migration-system
    spec:
    secretRef:
    name: osc1-openstack-secret
    ---
    apiVersion: v1
    data:
    OS_AUTH_URL:
    OS_DOMAIN_NAME:
    OS_INSECURE:
    OS_PASSWORD:
    OS_REGION_NAME:
    OS_TENANT_NAME:
    OS_USERNAME:
    kind: Secret
    metadata:
    name: osc1-openstack-secret
    namespace: migration-system
    type: Opaque
    +
    apiVersion: vjailbreak.k8s.pf9.io/v1alpha1
    kind: OpenstackCreds
    metadata:
    name: osc1
    namespace: migration-system
    spec:
    secretRef:
    name: osc1-openstack-secret
    ---
    apiVersion: v1
    data:
    OS_AUTH_URL:
    OS_DOMAIN_NAME:
    OS_INSECURE:
    OS_PASSWORD:
    OS_REGION_NAME:
    OS_TENANT_NAME:
    OS_USERNAME:
    kind: Secret
    metadata:
    name: osc1-openstack-secret
    namespace: migration-system
    type: Opaque

    VMware

    • All fields in VMwareCreds are required.
    apiVersion: vjailbreak.k8s.pf9.io/v1alpha1
    kind: VMwareCreds
    metadata:
    name: vmc1
    namespace: migration-system
    spec:
    secretRef:
    name: vmc1-vmware-secret
    ---
    apiVersion: v1
    data:
    VCENTER_HOST:
    VCENTER_INSECURE:
    VCENTER_PASSWORD:
    VCENTER_USERNAME:
    kind: Secret
    metadata:
    name: vmc1-vmware-secret
    namespace: migration-system
    type: Opaque

    Network mapping

    -
    apiVersion: vjailbreak.k8s.pf9.io/v1alpha1
    kind: NetworkMapping
    metadata:
    name: nwmap1
    namespace: migration-system
    spec:
    networks:
    - source: VM Network
    target: vlan3002
    - source: VM Network 2
    target: vlan3003
    +
    apiVersion: vjailbreak.k8s.pf9.io/v1alpha1
    kind: NetworkMapping
    metadata:
    name: nwmap1
    namespace: migration-system
    spec:
    networks:
    - source: VM Network
    target: vlan3002
    - source: VM Network 2
    target: vlan3003

    Datastore mapping

    -
    apiVersion: vjailbreak.k8s.pf9.io/v1alpha1
    kind: StorageMapping
    metadata:
    name: stmap1
    namespace: migration-system
    spec:
    storages:
    - source: vcenter-datastore-1
    target: lvm
    - source: vcenter-datastore-2
    target: ceph
    -

    MigrationTemplate

    -
    apiVersion: vjailbreak.k8s.pf9.io/v1alpha1
    kind: MigrationTemplate
    metadata:
    name: migrationtemplate-windows
    namespace: migration-system
    spec:
    networkMapping: name_of_networkMapping
    storageMapping: name_of_storageMapping
    osType: windows/linux <optional>
    source:
    datacenter: name_of_datacenter
    vmwareRef: name_of_VMwareCreds
    destination:
    openstackRef: name_of_OpenstackCreds
    +
    apiVersion: vjailbreak.k8s.pf9.io/v1alpha1
    kind: StorageMapping
    metadata:
    name: stmap1
    namespace: migration-system
    spec:
    storages:
    - source: vcenter-datastore-1
    target: lvm
    - source: vcenter-datastore-2
    target: ceph
    +

    VMwareMachine

      -
    • osType is optional. If not provided, the osType is retrieved from vCenter. If it can’t be automatically determined, migration will not proceed.
    • +
    • VMwareMachine represents a discovered VMware VM and is created automatically by vJailbreak when a VMwareCreds resource is reconciled; it is not created by hand. spec.vms is a read-only snapshot of the VM’s properties in vCenter (name, CPU, memory, datastores, networks, etc.) used to populate other resources such as StorageMapping and NetworkMapping.
    • +
    +
    apiVersion: vjailbreak.k8s.pf9.io/v1alpha1
    kind: VMwareMachine
    metadata:
    name: vm-1
    namespace: migration-system
    spec:
    vms:
    name: vm-1
    cpu: 4
    memory: 8192
    osFamily: linuxGuest
    datastores:
    - datastore-1
    networks:
    - network-1
    targetFlavorId: "" # optional
    +
      +
    • targetFlavorId: Optional. The OpenStack flavor ID to use for this VM’s target VM. Set this to explicitly pin the migration to a specific flavor instead of relying on vJailbreak’s automatic best-match selection (matched only on vCPU/RAM, so it cannot distinguish between same-sized flavors with different tags or extra specs). Must be set before the corresponding MigrationPlan is created, since it is read once when the per-VM migration ConfigMap is generated. If left empty, vJailbreak selects the closest matching flavor based on spec.vms.cpu and spec.vms.memory.
    • +
    +

    MigrationTemplate

    +
    apiVersion: vjailbreak.k8s.pf9.io/v1alpha1
    kind: MigrationTemplate
    metadata:
    name: migrationtemplate-windows
    namespace: migration-system
    spec:
    networkMapping: name_of_networkMapping
    storageMapping: name_of_storageMapping
    osFamily: windowsGuest/linuxGuest <optional>
    source:
    datacenter: name_of_datacenter
    vmwareRef: name_of_VMwareCreds
    destination:
    openstackRef: name_of_OpenstackCreds
    +
      +
    • osFamily is optional. If not provided, the osFamily is retrieved from vCenter. If it can’t be automatically determined, migration will not proceed.
    • +
    +

    MigrationBlueprint

    +

    A MigrationBlueprint is a saved, reusable migration configuration — what the UI calls a Migration Template. See Migration Templates for the workflow.

    +

    It is not the same object as the MigrationTemplate above. A MigrationTemplate is created per migration and drives an actual migration; a MigrationBlueprint is only read by the UI to pre-fill the migration form. The migration controller never reads it, and it holds no status.

    +
    apiVersion: vjailbreak.k8s.pf9.io/v1alpha1
    kind: MigrationBlueprint
    metadata:
    name: production-rhel-east
    namespace: migration-system
    spec:
    displayName: Production RHEL · East
    description: Cold copy into the east cluster <optional>
    vmwareRef: name_of_VMwareCreds
    vmwareClusterName: name_of_source_cluster
    pcdRef: name_of_OpenstackCreds
    targetPCDClusterName: name_of_target_PCD_cluster
    networkMappings:
    - source: VM Network
    target: external-network
    storageMappings:
    - source: vmware-datastore
    target: ceph
    storageCopyMethod: normal
    osFamily: windowsGuest/linuxGuest <optional>
    migrationStrategy:
    type: hot/cold
    adminInitiatedCutOver: true/false
    advancedOptions:
    networkPersistence: true/false
    securityGroups:
    - default
    serverGroup: name_of_server_group
    +
      +
    • displayName: Required. The name shown in the Templates tab. The object’s metadata.name is a sanitized form of it.
    • +
    • Every other field is optional, so a partially configured form can still be saved as a template.
    • +
    • vmwareClusterName, targetPCDClusterName: Cluster names, not IDs. The UI resolves them back to the form’s cluster selections when the template is applied.
    • +
    • networkMappings, storageMappings, arrayCredsMappings: Inline copies of the mapping pairs, not references to NetworkMapping or StorageMapping objects. This keeps a template intact when those per-migration objects are deleted.
    • +
    • storageCopyMethod: One of normal, vJailbreakAcceleratedCopy, or StorageAcceleratedCopy. Defaults to normal. proxyVMRef applies to vJailbreakAcceleratedCopy; arrayCredsMappings applies to StorageAcceleratedCopy.
    • +
    • The VM selection is deliberately absent — VMs are chosen each time the template is used.

    MigrationPlan

    -
    apiVersion: vjailbreak.k8s.pf9.io/v1alpha1
    kind: MigrationPlan
    metadata:
    name: vm-migration-app1
    namespace: migration-system
    spec:
    migrationTemplate: migrationtemplate-windows
    retry: true/false <optional>
    advancedOptions:
    granularVolumeTypes:
    - newvoltype1
    granularNetworks:
    - newnetworkname1
    - newnetworkname2
    granularPorts:
    - <port uuid 1>
    - <port uuid 2>
    migrationStrategy:
    type: hot/cold
    dataCopyStart: 2024-08-27T17:30:25.230Z
    vmCutoverStart: 2024-08-27T17:30:25.230Z
    vmCutoverEnd: 2024-08-28T17:30:25.230Z
    adminInitiatedCutOver: true/false
    performHealthChecks: true/false
    healthCheckPort: string
    virtualmachines:
    - - winserver2k12
    - winserver2k16
    - - winserver2k19
    - winserver2k22
    +
    apiVersion: vjailbreak.k8s.pf9.io/v1alpha1
    kind: MigrationPlan
    metadata:
    name: vm-migration-app1
    namespace: migration-system
    spec:
    migrationTemplate: migrationtemplate-windows
    retry: true/false <optional>
    advancedOptions:
    granularVolumeTypes:
    - newvoltype1
    granularNetworks:
    - newnetworkname1
    - newnetworkname2
    granularPorts:
    - <port uuid 1>
    - <port uuid 2>
    migrationStrategy:
    type: hot/cold
    dataCopyStart: 2024-08-27T17:30:25.230Z
    vmCutoverStart: 2024-08-27T17:30:25.230Z
    vmCutoverEnd: 2024-08-28T17:30:25.230Z
    adminInitiatedCutOver: true/false
    performHealthChecks: true/false
    healthCheckPort: string
    virtualMachines:
    - - winserver2k12
    - winserver2k16
    - - winserver2k19
    - winserver2k22
    • retry: Optional. Retries one failed migration in a migration plan once. Set to false after a migration has been retried.
    • -
    • advancedOptions: This is an optional field for granular control over migration options. MigrationTemplate with mappings must still be present. These options override the ones in the template, if set. If you use these options, you must only have 1 VM present in the virtualmachines list. +
    • advancedOptions: This is an optional field for granular control over migration options. MigrationTemplate with mappings must still be present. These options override the ones in the template, if set. If you use these options, you must only have 1 VM present in the virtualMachines list.
      • granularVolumeTypes: In case you wish to provide different volume types to disks of a VM when they are all on the same datastore, you can specify the volume type of each disk of your VM in order. You must define one volume type for one disk present on the VM
      • granularNetworks: In case you wish to override the default network mapping for a VM, you can provide a list of OpenStack network names to use in for each NIC on the VM, in order.
      • @@ -188,7 +222,7 @@ starlight-tabs:where(.astro-esqgolmp){display:block}.tablist-wrapper:where(.astr

      VjailbreakNode

      vJailbreak can be scaled to perform multiple migrations in parallel by deploying additional agents, enabling greater efficiency and workload distribution. The VjailbreakNode Custom Resource Definition (CRD) streamlines the creation and management of these agents, ensuring seamless integration into the migration workflow. Each VjailbreakNode represents a VM that functions as an independent migration agent. These agents are dynamically added to the original VjailbreakNode, forming a cohesive cluster that enhances scalability, reliability, and overall migration performance.

      -
      apiVersion: vjailbreak.k8s.pf9.io/v1alpha1
      kind: VjailbreakNode
      metadata:
      name: example-vjailbreak-node
      namespace: migration-system
      spec:
      imageid: "your-openstack-image-id" # This ID is for the first vjailbreak VMimage. It auto-populates in the UI—do not delete it.
      noderole: "migration-worker"
      openstackcreds:
      name: "name" # Reference to your OpenstackCreds
      namespace: "migration-system"
      openstackflavorid: "your-openstack-flavor-id"
      +
      apiVersion: vjailbreak.k8s.pf9.io/v1alpha1
      kind: VjailbreakNode
      metadata:
      name: example-vjailbreak-node
      namespace: migration-system
      spec:
      imageId: "your-openstack-image-id" # This ID is for the first vjailbreak VMimage. It auto-populates in the UI—do not delete it.
      nodeRole: "worker"
      openstackCreds:
      name: "name" # Reference to your OpenstackCreds
      namespace: "migration-system"
      openstackFlavorId: "your-openstack-flavor-id"

      This VjailbreakNode CRD defines a Kubernetes resource that provisions a VM in OpenStack to act as a migration agent. Below is a breakdown of each field:

      • metadata: Metadata contains identifying details about the VjailbreakNode. @@ -200,25 +234,25 @@ starlight-tabs:where(.astro-esqgolmp){display:block}.tablist-wrapper:where(.astr

      The spec section defines the desired state of the VjailbreakNode.

        -
      • imageid: "your-openstack-image-id": This is the ID of the OpenStack image used to create the VM. +
      • imageId: "your-openstack-image-id": This is the ID of the OpenStack image used to create the VM.
        • It must match the image ID used to create the initial vJailbreak VM, ensuring compatibility across all migration agents.
      • -
      • noderole: "worker": Defines the role of the node. +
      • nodeRole: "worker": Defines the role of the node.
        • It should be set to "worker" as this node functions as a migration agent within the vJailbreak cluster.
      • -
      • openstackcreds:: OpenstackCreds use the variables from the openstack.rc file. +
      • openstackCreds:: OpenstackCreds use the variables from the openstack.rc file.
        • name: "name" → Refers to a Secret or CustomResource storing OpenStack authentication details.
        • namespace: "migration-system" → The namespace where OpenStack credentials are stored.
      • -
      • openstackflavorid: "your-openstack-flavor-id": Specifies the OpenStack flavor ID, which determines the VM’s compute resources (CPU, RAM, disk size, etc.). +
      • openstackFlavorId: "your-openstack-flavor-id": Specifies the OpenStack flavor ID, which determines the VM’s compute resources (CPU, RAM, disk size, etc.).
        • The chosen flavor should align with the resource requirements for migration workloads.
      • -
    \ No newline at end of file +
    \ No newline at end of file diff --git a/docs/release_docs/v013/index.html b/docs/release_docs/v013/index.html deleted file mode 100644 index c255b5d..0000000 --- a/docs/release_docs/v013/index.html +++ /dev/null @@ -1,143 +0,0 @@ - v0.1.3 | vJailbreak - Skip to content
    \ No newline at end of file diff --git a/docs/release_docs/v014/index.html b/docs/release_docs/v014/index.html deleted file mode 100644 index 2d4fa22..0000000 --- a/docs/release_docs/v014/index.html +++ /dev/null @@ -1,160 +0,0 @@ - v0.1.4 | vJailbreak - Skip to content

    v0.1.4

    What’s Changed

    - -

    New Contributors

    - -

    Full Changelog: https://github.com/platform9/vjailbreak/compare/v0.1.3…v0.1.4

    \ No newline at end of file diff --git a/docs/release_docs/v015/index.html b/docs/release_docs/v015/index.html deleted file mode 100644 index 769c35e..0000000 --- a/docs/release_docs/v015/index.html +++ /dev/null @@ -1,160 +0,0 @@ - v0.1.5 | vJailbreak - Skip to content

    v0.1.5

    What’s Changed

    - -

    New Contributors

    -
      -
    • @damian-pf9
    • -
    • @anmolsachan
    • -
    • @bhavin192
    • -
    -

    Full Changelog: https://github.com/platform9/vjailbreak/compare/v0.1.4…v0.1.5

    \ No newline at end of file diff --git a/docs/release_docs/v016/index.html b/docs/release_docs/v016/index.html deleted file mode 100644 index 900caa4..0000000 --- a/docs/release_docs/v016/index.html +++ /dev/null @@ -1,152 +0,0 @@ - v0.1.6 | vJailbreak - Skip to content

    v0.1.6

    What’s Changed

    - -

    New Contributors

    - -

    Full Changelog: https://github.com/platform9/vjailbreak/compare/v0.1.5…v0.1.6

    \ No newline at end of file diff --git a/docs/release_docs/v017/index.html b/docs/release_docs/v017/index.html deleted file mode 100644 index 600b27d..0000000 --- a/docs/release_docs/v017/index.html +++ /dev/null @@ -1,168 +0,0 @@ - v0.1.7 | vJailbreak - Skip to content

    v0.1.7

    What’s Changed

    - -

    Full Changelog: https://github.com/platform9/vjailbreak/compare/v0.1.6…v0.1.7

    \ No newline at end of file diff --git a/docs/release_docs/v018/index.html b/docs/release_docs/v018/index.html deleted file mode 100644 index d5b5514..0000000 --- a/docs/release_docs/v018/index.html +++ /dev/null @@ -1,150 +0,0 @@ - v0.1.8 | vJailbreak - Skip to content

    v0.1.8

    What’s Changed

    - -

    New Contributors

    - -

    Full Changelog: https://github.com/platform9/vjailbreak/compare/v0.1.6…v0.1.8

    \ No newline at end of file diff --git a/docs/release_docs/v019/index.html b/docs/release_docs/v019/index.html deleted file mode 100644 index c7e8151..0000000 --- a/docs/release_docs/v019/index.html +++ /dev/null @@ -1,156 +0,0 @@ - v0.1.9 | vJailbreak - Skip to content

    v0.1.9

    What’s Changed

    - -

    New Contributors

    - -

    Full Changelog: https://github.com/platform9/vjailbreak/compare/v0.1.8…v0.1.9

    \ No newline at end of file diff --git a/docs/release_docs/v012/index.html b/docs/release_docs/v0410/index.html similarity index 50% rename from docs/release_docs/v012/index.html rename to docs/release_docs/v0410/index.html index 5022098..a64fd1f 100644 --- a/docs/release_docs/v012/index.html +++ b/docs/release_docs/v0410/index.html @@ -1,4 +1,4 @@ - v0.1.2 | vJailbreak + Skip to content
    Skip to content

    v0.1.2

    What’s Changed

    +

    v0.4.10

    What’s Changed

    -

    Known Issues

    -
      -
    • If the VM to be migrated has an LV spanning multiple physical devices used as boot volume unless both are mounted simultaneously to the OS-VM, vJailbreak cannot detect whether it is bootable, or not. (issue link)
    • -
    • If a user turns on a VM on VCenter after migration and tries to migrate it again, the migration object will not be created. In this case, the user should delete the VM from PCD/Openstack before trying the migration again. This error will be pushed up the stack for visibility.
    • -
    -

    New Contributors

    - -

    Full Changelog: https://github.com/platform9/vjailbreak/compare/v0.1.1…16.01

    \ No newline at end of file +

    Full Changelog: https://github.com/platform9/vjailbreak/compare/v0.4.9…v0.4.10

    +

    Highlights

    +

    This is a quick release in response to VDDK unavailability. More details - https://platform9.com/blog/vddk-no-longer-available/

    \ No newline at end of file diff --git a/docs/release_docs/v046/index.html b/docs/release_docs/v046/index.html new file mode 100644 index 0000000..af033f5 --- /dev/null +++ b/docs/release_docs/v046/index.html @@ -0,0 +1,212 @@ + v0.4.6 | vJailbreak + Skip to content

    v0.4.6

    What’s Changed

    + +

    Highlights

    +

    Hot Add Proxy: Hot-Add Proxy is an advanced data copy method that attaches source VM disks directly to a dedicated Proxy VM and streams the data over NBD (Network Block Device) to the destination. Instead of copying data over the NFC protocol from ESXi. This feature is currently in beta, will be stabilized in the coming releases.

    +

    Migration Reliability:

    +
      +
    • VMs with no network interfaces can now be migrated successfully.
    • +
    • Multi-attach volume types are now supported for migration.
    • +
    +

    Enhanced UX:

    +
      +
    • All IP addresses (assigned and preserved) are now shown on the Migration Details page in the UI.
    • +
    • Controller logs (migration controller manager) are visible in the UI irrespective of any migrations triggered.
    • +
    • A debug bundle collection button makes log and resource gathering easier for troubleshooting. All CRs related to that migration are now gathered for easier debugging.
    • +
    +

    Known Limitations

    +
      +
    • Migrations of VMs with grub version lower than 2 might face issues while booting post migration.
    • +
    +

    Full Changelog: https://github.com/platform9/vjailbreak/compare/v0.4.5…v0.4.6

    \ No newline at end of file diff --git a/docs/release_docs/v047/index.html b/docs/release_docs/v047/index.html new file mode 100644 index 0000000..94e8fbe --- /dev/null +++ b/docs/release_docs/v047/index.html @@ -0,0 +1,214 @@ + v0.4.7 | vJailbreak + Skip to content

    v0.4.7

    What’s Changed

    + +

    New Contributors

    + +

    Highlights

    +

    Migration details page: Added new page which shows in depth state monitoring for migration lifecycle.

    +

    Vjailbreak Proxy:

    +
      +
    • End-to-end UI for registering a Proxy VM (SSH key upload, validation status), selecting Hot-Add as the copy method in the migration form, and monitoring hot-add-specific migration phases
    • +
    • Verification now auto-installs missing dependencies on the Proxy VM during validation, eliminating the need for manual pre-configuration before registering
    • +
    +

    Advanced Configuration Management

    +
      +
    • Configure NTP servers and system timezone for vJailbreak VMs directly from the UI.
    • +
    • Introduced server group selection for agent scale-up, enabling better placement and affinity control.
    • +
    +

    Enhanced Retry Experience

    +
      +
    • Introduced a new Edit & Retry capability, allowing users to modify migration settings and retry failed migrations without recreating the migration.
    • +
    • Simplified recovery with a unified Retry action and support for Bulk Retry, reducing manual effort.
    • +
    +

    Known Limitations

    +

    Proxy VM OVA requires ESXi 8.0 U2 or higher: The bundled OVA template uses virtual hardware version vmx-21, which is incompatible with older ESXi hosts. Attempting to deploy it on ESXi 7.x will fail with an “unsupported hardware family” error. For ESXi 7.x environments, the Proxy VM must be created and configured manually instead of using the OVA deploy option.

    +

    Full Changelog: https://github.com/platform9/vjailbreak/compare/v0.4.6…v0.4.7

    \ No newline at end of file diff --git a/docs/release_docs/v048/index.html b/docs/release_docs/v048/index.html new file mode 100644 index 0000000..d97c13c --- /dev/null +++ b/docs/release_docs/v048/index.html @@ -0,0 +1,234 @@ + v0.4.8 | vJailbreak + Skip to content

    v0.4.8

    What’s Changed

    + +

    New Contributors

    + +

    Highlights

    +
      +
    • Introduced log analysis for failed migrations using Anthropic AI to help identify potential root causes and simplify troubleshooting.
    • +
    • Added ability to preserve source vCenter VM tags, attributes, and custom metadata during migration.
    • +
    • Promoted Storage Assisted Migration to General Availability (GA).
    • +
    • Added support for Migration Templates and Saved Configurations to streamline repeated migration workflows.
    • +
    • Added support for Data-Only migration mode, enabling data transfer without creating the destination VM.
    • +
    • Added support for migrating KMS-encrypted source VMs.
    • +
    +

    Known Limitations

    +
      +
    • The Data-Only migration option is not preserved when a migration is retried using the Retry button. See [Issue #2245] for details.
    • +
    • Migrations of VMs running RHEL 7.9 may intermittently encounter a disk index-related issue. Retrying the migration typically resolves the problem.
    • +
    • When using the Hotplug flavor for migration, the destination VM’s Maximum CPU and Maximum Memory values are set to twice the original VM’s configured CPU and memory in vCenter.
    • +
    • Migration of SUSE Linux Enterprise Server (SLES) and SUSE Linux Enterprise Desktop (SLED) systems using Legacy GRUB 0.97 is not supported.
    • +
    +

    Full Changelog: https://github.com/platform9/vjailbreak/compare/v0.4.7…v0.4.8

    \ No newline at end of file diff --git a/docs/release_docs/v049/index.html b/docs/release_docs/v049/index.html new file mode 100644 index 0000000..e190b40 --- /dev/null +++ b/docs/release_docs/v049/index.html @@ -0,0 +1,188 @@ + v0.4.9 | vJailbreak + Skip to content

    v0.4.9

    What’s Changed

    + +

    Highlights

    +
      +
    • Added support for Windows VMs with LDM system disk
    • +
    • UI Retry related stability fixes
    • +
    • Added support for Linux VMs with BTRFS
    • +
    +

    Known Limitations

    +
      +
    • Race condition with vjailbreak accelerated proxy - #2300
    • +
    • Multi-IP assignment limitation - #2163
    • +
    • Incorrect migration copy progress on UI - #2139
    • +
    +

    Full Changelog: https://github.com/platform9/vjailbreak/compare/v0.4.8…v0.4.9

    \ No newline at end of file diff --git a/docs/scripts/Test-VjbLdmSystemDisk.ps1 b/docs/scripts/Test-VjbLdmSystemDisk.ps1 new file mode 100644 index 0000000..acb2576 --- /dev/null +++ b/docs/scripts/Test-VjbLdmSystemDisk.ps1 @@ -0,0 +1,386 @@ +<# +.SYNOPSIS + Determines whether the Windows system disk is an LDM (dynamic) disk. + +.DESCRIPTION + Run this on the SOURCE VM in vCenter, before migrating with vJailbreak. + + Windows VMs whose system volume sits on a dynamic disk (Logical Disk Manager) + cannot be converted by virt-v2v and follow vJailbreak's dedicated SATA-first + migration path, which has extra prerequisites. Dynamic DATA disks are + irrelevant - only the disk carrying the system volume changes the outcome. + + The script is strictly READ-ONLY. It issues no diskpart command other than + 'list disk', 'select volume', 'detail volume' and 'san', none of which modify + the disk layout. + + Detection uses four independent signals, so a wrong answer requires several + of them to agree incorrectly: + + 1. GPT partition type GUID - 5808C8AA-... (LDM metadata) + AF9B60A0-... (LDM data) + 2. MBR partition type byte - 0x42 (LDM) + 3. WMI Win32_DiskPartition.Type containing "Logical Disk Manager" + 4. diskpart's Dyn column in 'list disk' / 'detail volume' + + The system volume is mapped to its physical disk(s) by three independent + routes (Storage module, WMI associators, diskpart). On a dynamic disk the + Storage-module route fails by design - the partition layer does not model LDM + volumes - and that failure is itself recorded as corroborating evidence. + +.PARAMETER LogPath + Where the transcript is written. Defaults to %TEMP%\vjb-ldm-check.log. + +.PARAMETER Quiet + Suppress the per-signal transcript on the console. The verdict banner is + always printed. + +.OUTPUTS + A verdict banner, plus a single machine-readable line: + VJB_LDM_SYSTEM_DISK=YES|NO|UNKNOWN + + Exit codes: + 0 NO system disk is basic - normal migration path + 1 YES system disk is LDM - SATA-first migration path + 2 UNKNOWN inconclusive, inspect the log + +.NOTES + Requires an elevated PowerShell session. + Tested against Windows Server 2012 and later. Written to PowerShell 2.0 + syntax so it also runs on Windows Server 2008 R2, where the Storage module + is absent and signals 1 and 2 are unavailable. +#> + +[CmdletBinding()] +param( + [string] $LogPath = (Join-Path $env:TEMP 'vjb-ldm-check.log'), + [switch] $Quiet +) + +$ErrorActionPreference = 'Continue' + +# GPT partition type GUIDs that identify an LDM disk. +$LDM_GPT_TYPES = @( + '5808c8aa-7e8f-42e0-85d2-e1e90434cfb3', # LDM metadata partition + 'af9b60a0-1431-4f62-bc68-3311714a69ad' # LDM data partition +) +# MBR partition type byte 0x42 = LDM. +$LDM_MBR_TYPE = 66 + +$script:LogLines = New-Object System.Collections.ArrayList + +# ---------------------------------------------------------------- logging ---- + +function Write-Log { + param([string] $Level, [string] $Message) + + $line = '{0} [{1,-5}] {2}' -f (Get-Date).ToString('yyyy-MM-dd HH:mm:ss'), $Level, $Message + [void] $script:LogLines.Add($line) + + if (-not $Quiet) { + $color = 'Gray' + if ($Level -eq 'WARN') { $color = 'Yellow' } + if ($Level -eq 'ERROR') { $color = 'Red' } + if ($Level -eq 'HIT') { $color = 'Magenta' } + Write-Host $line -ForegroundColor $color + } +} + +# ------------------------------------------------------------- primitives ---- + +function Test-Elevated { + try { + $identity = [Security.Principal.WindowsIdentity]::GetCurrent() + $principal = New-Object Security.Principal.WindowsPrincipal($identity) + return $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) + } catch { + return $false + } +} + +function Invoke-DiskPart { + param([string[]] $Commands) + + $scriptFile = [System.IO.Path]::GetTempFileName() + try { + Set-Content -Path $scriptFile -Value $Commands -Encoding ASCII + $output = & diskpart.exe /s $scriptFile 2>&1 + return @($output | ForEach-Object { [string] $_ }) + } catch { + Write-Log 'WARN' ("diskpart invocation failed: " + $_.Exception.Message) + return @() + } finally { + Remove-Item $scriptFile -Force -ErrorAction SilentlyContinue + } +} + +function Get-Column { + # Slice a fixed-width diskpart column, bleeding one character either side + # because diskpart does not always pad exactly to its own rule line. + param([string] $Line, $Column, [int] $Bleed = 1) + + $start = $Column.Start - $Bleed + if ($start -lt 0) { $start = 0 } + if ($Line.Length -le $start) { return '' } + + $length = $Column.Length + ($Column.Start - $start) + $Bleed + if (($start + $length) -gt $Line.Length) { $length = $Line.Length - $start } + return $Line.Substring($start, $length) +} + +function Read-DiskPartDiskTable { + # Both 'list disk' and 'detail volume' emit the same six-column table: + # + # Disk ### Status Size Free Dyn Gpt + # -------- ------------- ------- ------- --- --- + # Disk 0 Online 100 GB 0 B * + # + # Column offsets are taken from the rule line rather than from the header, + # so parsing does not depend on the console language. + param([string[]] $Lines) + + $rows = @() + $ruleIndex = -1 + $columns = @() + + for ($i = 0; $i -lt $Lines.Count; $i++) { + if ($Lines[$i] -match '^\s*-{3,}(\s+-{2,}){3,}\s*$') { + $ruleIndex = $i + foreach ($match in ([regex] '-{2,}').Matches($Lines[$i])) { + $columns += New-Object PSObject -Property @{ + Start = $match.Index + Length = $match.Length + } + } + break + } + } + + if ($ruleIndex -lt 0 -or $columns.Count -lt 5) { + Write-Log 'WARN' 'Could not locate the diskpart column rule line; skipping this signal.' + return $rows + } + + for ($i = $ruleIndex + 1; $i -lt $Lines.Count; $i++) { + if ($Lines[$i].Trim().Length -eq 0) { break } + + $number = [regex]::Match((Get-Column $Lines[$i] $columns[0] 0), '\d+') + if (-not $number.Success) { continue } + + $gpt = $false + if ($columns.Count -ge 6) { + $gpt = (Get-Column $Lines[$i] $columns[5]).Contains('*') + } + + $rows += New-Object PSObject -Property @{ + Number = [int] $number.Value + Dynamic = (Get-Column $Lines[$i] $columns[4]).Contains('*') + Gpt = $gpt + } + } + + return $rows +} + +# ------------------------------------------------------------ inspection ----- + +function Get-DynamicDiskEvidence { + # Returns the disk numbers that at least one signal reports as LDM. + param([bool] $StorageModule) + + $dynamic = @{} + + # Signal 4 - diskpart Dyn column. + foreach ($row in (Read-DiskPartDiskTable (Invoke-DiskPart @('list disk')))) { + $state = 'basic' + if ($row.Dynamic) { $state = 'DYNAMIC'; $dynamic[$row.Number] = $true } + Write-Log 'INFO' ('diskpart : disk {0} -> {1}' -f $row.Number, $state) + } + + # Signal 3 - WMI partition type string. + foreach ($partition in @(Get-WmiObject -Class Win32_DiskPartition -ErrorAction SilentlyContinue)) { + if ($partition.Type -match 'Logical Disk Manager|LDM') { + $dynamic[[int] $partition.DiskIndex] = $true + Write-Log 'HIT' ('Win32_DiskPartition: disk {0} partition type "{1}"' -f $partition.DiskIndex, $partition.Type) + } else { + Write-Log 'INFO' ('Win32_DiskPartition: disk {0} partition type "{1}"' -f $partition.DiskIndex, $partition.Type) + } + } + + # Signals 1 and 2 - GPT type GUID and MBR type byte. Storage module only. + if ($StorageModule) { + foreach ($partition in @(Get-Partition -ErrorAction SilentlyContinue)) { + $gptType = '' + if ($partition.GptType) { $gptType = ($partition.GptType -replace '[{}]', '').ToLower() } + + if ($LDM_GPT_TYPES -contains $gptType) { + $dynamic[[int] $partition.DiskNumber] = $true + Write-Log 'HIT' ('GPT type GUID : disk {0} partition {1} -> {2}' -f $partition.DiskNumber, $partition.PartitionNumber, $gptType) + } elseif ([int] $partition.MbrType -eq $LDM_MBR_TYPE) { + $dynamic[[int] $partition.DiskNumber] = $true + Write-Log 'HIT' ('MBR type byte : disk {0} partition {1} -> 0x42 (LDM)' -f $partition.DiskNumber, $partition.PartitionNumber) + } + } + } else { + Write-Log 'WARN' 'Storage module unavailable; partition-type signals skipped (expected on Server 2008 R2).' + } + + return @($dynamic.Keys | ForEach-Object { [int] $_ }) +} + +function Get-SystemVolumeDiskNumber { + # Three independent routes. Returns the union of whatever resolves. + param([string] $DriveLetter, [bool] $StorageModule) + + $found = @{} + + # Route 1 - Storage module. Fails by design when the volume is LDM. + if ($StorageModule) { + $partition = Get-Partition -DriveLetter $DriveLetter -ErrorAction SilentlyContinue + if ($partition) { + $hit = @() + foreach ($p in @($partition)) { + $found[[int] $p.DiskNumber] = $true + $hit += [string] $p.DiskNumber + } + Write-Log 'INFO' ('Get-Partition : {0}: resolves to disk {1}' -f $DriveLetter, ($hit -join ', ')) + } else { + Write-Log 'WARN' ('Get-Partition : {0}: has no partition object. The partition layer does not model LDM volumes, so this is expected on a dynamic disk.' -f $DriveLetter) + } + } + + # Route 2 - WMI associators. Also unavailable for LDM volumes. + $query = "ASSOCIATORS OF {{Win32_LogicalDisk.DeviceID='{0}:'}} WHERE AssocClass=Win32_LogicalDiskToPartition" -f $DriveLetter + $associated = @(Get-WmiObject -Query $query -ErrorAction SilentlyContinue) + if ($associated.Count -gt 0) { + $hit = @() + foreach ($p in $associated) { + $found[[int] $p.DiskIndex] = $true + $hit += [string] $p.DiskIndex + } + Write-Log 'INFO' ('WMI associator: {0}: resolves to disk {1}' -f $DriveLetter, ($hit -join ', ')) + } else { + Write-Log 'WARN' ('WMI associator: {0}: has no Win32_LogicalDiskToPartition association, which is typical for an LDM volume.' -f $DriveLetter) + } + + # Route 3 - diskpart. Works for both basic and dynamic volumes. + $detail = Invoke-DiskPart @(('select volume ' + $DriveLetter), 'detail volume') + foreach ($row in (Read-DiskPartDiskTable $detail)) { + $found[$row.Number] = $true + $state = 'basic' + if ($row.Dynamic) { $state = 'DYNAMIC' } + Write-Log 'INFO' ('detail volume : {0}: resides on disk {1} ({2})' -f $DriveLetter, $row.Number, $state) + } + + return @($found.Keys | ForEach-Object { [int] $_ }) +} + +# ------------------------------------------------------------------ main ----- + +Write-Log 'INFO' '=== vJailbreak LDM system-disk probe (read-only) ===' +Write-Log 'INFO' ('Host : {0}' -f $env:COMPUTERNAME) +Write-Log 'INFO' ('OS : {0}' -f (Get-WmiObject -Class Win32_OperatingSystem -ErrorAction SilentlyContinue).Caption) +Write-Log 'INFO' ('PowerShell : {0}' -f $PSVersionTable.PSVersion.ToString()) +Write-Log 'INFO' ('System drive : {0}' -f $env:SystemDrive) + +if (-not (Test-Elevated)) { + Write-Log 'ERROR' 'Not running elevated. diskpart and the Storage module need Administrator rights.' + Write-Log 'ERROR' 'Re-run this script from an elevated PowerShell session.' + $verdict = 'UNKNOWN' +} else { + $driveLetter = $env:SystemDrive.Substring(0, 1) + $storageModule = $null -ne (Get-Command Get-Disk -ErrorAction SilentlyContinue) + Write-Log 'INFO' ('Storage module: {0}' -f $(if ($storageModule) { 'available' } else { 'NOT available' })) + Write-Log 'INFO' '--- mapping the system volume to its physical disk(s) ---' + + $systemDisks = Get-SystemVolumeDiskNumber -DriveLetter $driveLetter -StorageModule $storageModule + + Write-Log 'INFO' '--- collecting LDM evidence for every disk ---' + $dynamicDisks = Get-DynamicDiskEvidence -StorageModule $storageModule + + Write-Log 'INFO' '--- summary ---' + Write-Log 'INFO' ('System volume resides on disk(s) : {0}' -f $(if ($systemDisks.Count) { ($systemDisks | Sort-Object) -join ', ' } else { '' })) + Write-Log 'INFO' ('Disks reported as LDM/dynamic : {0}' -f $(if ($dynamicDisks.Count) { ($dynamicDisks | Sort-Object) -join ', ' } else { 'none' })) + + if ($systemDisks.Count -gt 0) { + $hit = @($systemDisks | Where-Object { $dynamicDisks -contains $_ }) + if ($hit.Count -gt 0) { + $verdict = 'YES' + Write-Log 'HIT' ('System volume resides on dynamic disk(s): {0}' -f (($hit | Sort-Object) -join ', ')) + } else { + $verdict = 'NO' + $dataOnly = @($dynamicDisks | Where-Object { $systemDisks -notcontains $_ }) + if ($dataOnly.Count -gt 0) { + Write-Log 'INFO' ('Disk(s) {0} are dynamic but carry no system volume. Data disks do not affect the migration path.' -f (($dataOnly | Sort-Object) -join ', ')) + } + } + } elseif ($dynamicDisks.Count -gt 0) { + # No route could map the system volume to a disk, and dynamic disks + # exist. Both mapping failures are themselves LDM symptoms. + $verdict = 'YES' + Write-Log 'HIT' 'The system volume could not be mapped to any partition object while dynamic disks are present. Both facts point to an LDM system volume.' + } else { + $verdict = 'UNKNOWN' + Write-Log 'ERROR' 'The system volume could not be mapped to a disk and no LDM evidence was found. Inspect the log and check the disk layout manually.' + } + + # Informational only - not part of the verdict. The SAN policy is a + # prerequisite for the LDM migration path. + $sanOutput = Invoke-DiskPart @('san') + foreach ($line in $sanOutput) { + if ($line -match ':') { + $trimmed = $line.Trim() + if ($trimmed -match 'Online|Offline') { + Write-Log 'INFO' ('SAN policy : {0}' -f $trimmed) + } + } + } +} + +# Persist the transcript before anything else can fail. +try { + Set-Content -Path $LogPath -Value $script:LogLines -Encoding ASCII + Write-Host '' + Write-Host ('Full transcript: {0}' -f $LogPath) -ForegroundColor DarkGray +} catch { + Write-Host '' + Write-Host ('Could not write the transcript to {0}: {1}' -f $LogPath, $_.Exception.Message) -ForegroundColor Yellow +} + +# ---------------------------------------------------------------- verdict ---- +# Printed last, on its own, so it is never buried in the transcript above. + +$banner = 'Gray' +if ($verdict -eq 'YES') { $banner = 'Red' } +if ($verdict -eq 'NO') { $banner = 'Green' } +if ($verdict -eq 'UNKNOWN') { $banner = 'Yellow' } + +# Built by measurement rather than by hand-counted spaces, so the box always +# closes regardless of the verdict length. +$width = 64 +$rule = '#' * $width +$blank = '#' + (' ' * ($width - 2)) + '#' +$label = ' SYSTEM DISK IS LDM (DYNAMIC): ' + $verdict +$pad = $width - 2 - $label.Length +if ($pad -lt 0) { $pad = 0 } + +Write-Host '' +Write-Host $rule +Write-Host $blank +Write-Host ('#' + $label + (' ' * $pad) + '#') -ForegroundColor $banner +Write-Host $blank +Write-Host $rule +Write-Host '' + +if ($verdict -eq 'YES') { + Write-Host 'This VM takes the SATA-first migration path. Install the VirtIO guest' -ForegroundColor Yellow + Write-Host 'tools and set "diskpart > san policy=onlineall" on THIS VM before' -ForegroundColor Yellow + Write-Host 'migrating, and expect a manual cutover at LDM Boot Verification.' -ForegroundColor Yellow + Write-Host '' +} + +Write-Host ('VJB_LDM_SYSTEM_DISK={0}' -f $verdict) + +if ($verdict -eq 'YES') { exit 1 } +if ($verdict -eq 'NO') { exit 0 } +exit 2 diff --git a/docs/sitemap-0.xml b/docs/sitemap-0.xml index 62ff924..1d96cdc 100644 --- a/docs/sitemap-0.xml +++ b/docs/sitemap-0.xml @@ -1 +1 @@ -https://platform9.github.io/https://platform9.github.io/guides/building/https://platform9.github.io/guides/injecting_custom_env/https://platform9.github.io/guides/scaling/https://platform9.github.io/guides/troubleshooting/https://platform9.github.io/guides/using_apis/https://platform9.github.io/introduction/components/https://platform9.github.io/introduction/getting_started/https://platform9.github.io/introduction/prerequisites/https://platform9.github.io/introduction/what_is_vjailbreak/https://platform9.github.io/reference/example/https://platform9.github.io/reference/reference/https://platform9.github.io/release_docs/v012/https://platform9.github.io/release_docs/v013/https://platform9.github.io/release_docs/v014/https://platform9.github.io/release_docs/v015/https://platform9.github.io/release_docs/v016/https://platform9.github.io/release_docs/v017/https://platform9.github.io/release_docs/v018/https://platform9.github.io/release_docs/v019/ \ No newline at end of file +https://platform9.github.io/vjailbreak-docs/https://platform9.github.io/vjailbreak-docs/architecture/architecture-overview/https://platform9.github.io/vjailbreak-docs/architecture/components/https://platform9.github.io/vjailbreak-docs/architecture/vjailbreak-vm/https://platform9.github.io/vjailbreak-docs/archives/release_notes/https://platform9.github.io/vjailbreak-docs/concepts/cluster-conversion/https://platform9.github.io/vjailbreak-docs/concepts/credential-management/https://platform9.github.io/vjailbreak-docs/concepts/migration-options/https://platform9.github.io/vjailbreak-docs/concepts/network-persistence/https://platform9.github.io/vjailbreak-docs/concepts/network-storage-mapping/https://platform9.github.io/vjailbreak-docs/concepts/storage-accelerated-copy/https://platform9.github.io/vjailbreak-docs/concepts/user-management/https://platform9.github.io/vjailbreak-docs/concepts/vjailbreak-accelerated-copy/https://platform9.github.io/vjailbreak-docs/guides/cli-api/migrating_rdm_disk_windows_cluster_machine_using_cli/https://platform9.github.io/vjailbreak-docs/guides/cli-api/migrating_using_cli_and_kubectl/https://platform9.github.io/vjailbreak-docs/guides/cluster-conversion/cluster-conversion/https://platform9.github.io/vjailbreak-docs/guides/cluster-conversion/maas-enablement/https://platform9.github.io/vjailbreak-docs/guides/how-to/ai_analysis/https://platform9.github.io/vjailbreak-docs/guides/how-to/building/https://platform9.github.io/vjailbreak-docs/guides/how-to/enable_kvm_nested_virtualization/https://platform9.github.io/vjailbreak-docs/guides/how-to/firstboot_script_doc/https://platform9.github.io/vjailbreak-docs/guides/how-to/gpo_migration/https://platform9.github.io/vjailbreak-docs/guides/how-to/injecting_custom_env/https://platform9.github.io/vjailbreak-docs/guides/how-to/migration_templates/https://platform9.github.io/vjailbreak-docs/guides/how-to/network-traffic-separation/https://platform9.github.io/vjailbreak-docs/guides/how-to/networking-101/https://platform9.github.io/vjailbreak-docs/guides/how-to/ntp_timezone/https://platform9.github.io/vjailbreak-docs/guides/how-to/perform_admin_cutover/https://platform9.github.io/vjailbreak-docs/guides/how-to/profiles/https://platform9.github.io/vjailbreak-docs/guides/how-to/retry_failed_migration/https://platform9.github.io/vjailbreak-docs/guides/how-to/scaling/https://platform9.github.io/vjailbreak-docs/guides/how-to/stream_logs/https://platform9.github.io/vjailbreak-docs/guides/how-to/upgrade_vjailbreak/https://platform9.github.io/vjailbreak-docs/guides/how-to/virtio_doc/https://platform9.github.io/vjailbreak-docs/guides/how-to/vjailbreak_settings/https://platform9.github.io/vjailbreak-docs/guides/how-to/vtpm_migration/https://platform9.github.io/vjailbreak-docs/guides/how-to/windows-ldm-migration/https://platform9.github.io/vjailbreak-docs/guides/troubleshooting/debug_vjailbreak_install/https://platform9.github.io/vjailbreak-docs/guides/troubleshooting/debuglogs/https://platform9.github.io/vjailbreak-docs/guides/troubleshooting/nbdcopy-fails-after-vm-moved-esxi-host/https://platform9.github.io/vjailbreak-docs/guides/troubleshooting/troubleshooting/https://platform9.github.io/vjailbreak-docs/guides/troubleshooting/vmware_residual_artifacts/https://platform9.github.io/vjailbreak-docs/guides/troubleshooting/windows-dynamic-disk-ldm-migration-issue/https://platform9.github.io/vjailbreak-docs/guides/troubleshooting/windows-offline-disks/https://platform9.github.io/vjailbreak-docs/introduction/architecture-components/https://platform9.github.io/vjailbreak-docs/introduction/faq/https://platform9.github.io/vjailbreak-docs/introduction/getting_started/https://platform9.github.io/vjailbreak-docs/introduction/prerequisites/https://platform9.github.io/vjailbreak-docs/introduction/what_is_vjailbreak/https://platform9.github.io/vjailbreak-docs/reference/compatibility/https://platform9.github.io/vjailbreak-docs/reference/known-limitations/https://platform9.github.io/vjailbreak-docs/reference/reference/https://platform9.github.io/vjailbreak-docs/release_docs/v046/https://platform9.github.io/vjailbreak-docs/release_docs/v047/https://platform9.github.io/vjailbreak-docs/release_docs/v048/https://platform9.github.io/vjailbreak-docs/release_docs/v049/https://platform9.github.io/vjailbreak-docs/release_docs/v0410/ \ No newline at end of file diff --git a/docs/sitemap-index.xml b/docs/sitemap-index.xml index 94b8340..54e0d94 100644 --- a/docs/sitemap-index.xml +++ b/docs/sitemap-index.xml @@ -1 +1 @@ -https://platform9.github.io/sitemap-0.xml \ No newline at end of file +https://platform9.github.io/vjailbreak-docs/sitemap-0.xml \ No newline at end of file