virtui-manager 1.1.5__py3-none-any.whl → 1.1.6__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {virtui_manager-1.1.5.dist-info → virtui_manager-1.1.6.dist-info}/METADATA +1 -1
- {virtui_manager-1.1.5.dist-info → virtui_manager-1.1.6.dist-info}/RECORD +14 -14
- vmanager/constants.py +1 -1
- vmanager/modals/howto_disk_modal.py +11 -71
- vmanager/modals/howto_network_modal.py +11 -38
- vmanager/modals/howto_overlay_modal.py +11 -51
- vmanager/modals/howto_ssh_modal.py +10 -66
- vmanager/modals/howto_virtiofs_modal.py +11 -63
- vmanager/modals/server_prefs_modals.py +25 -16
- vmanager/storage_manager.py +25 -3
- {virtui_manager-1.1.5.dist-info → virtui_manager-1.1.6.dist-info}/WHEEL +0 -0
- {virtui_manager-1.1.5.dist-info → virtui_manager-1.1.6.dist-info}/entry_points.txt +0 -0
- {virtui_manager-1.1.5.dist-info → virtui_manager-1.1.6.dist-info}/licenses/LICENSE +0 -0
- {virtui_manager-1.1.5.dist-info → virtui_manager-1.1.6.dist-info}/top_level.txt +0 -0
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
virtui_manager-1.1.
|
|
1
|
+
virtui_manager-1.1.6.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
|
2
2
|
vmanager/__init__.py,sha256=-gTpOgkPAOtbLwxz-U9i2uVtFW1SB9JhBguViXnQPJ8,15
|
|
3
3
|
vmanager/config.py,sha256=OBMWtXBTnmws1ITjQ7D_C0LVMxzvGaRH8nZOlW9cbqk,3102
|
|
4
4
|
vmanager/connection_manager.py,sha256=osk0EHJ0ua2wnDOu-9faK8fMd7s9TGsI6OtgxLAMTCI,12984
|
|
5
|
-
vmanager/constants.py,sha256=
|
|
5
|
+
vmanager/constants.py,sha256=q7oX6P0XKLOKEh6JxY1Jduhl5Q5aMFKk8DfKii7BvyM,5907
|
|
6
6
|
vmanager/dialog.css,sha256=iWGRlTZ-D-wc2uQQTftPoqDpysrAD6qMMENE3HXXlQg,11300
|
|
7
7
|
vmanager/events.py,sha256=yvjcYHMoDnnDKsMjfweZYnu0-U7lxQlB2uuIbtK5FeY,1556
|
|
8
8
|
vmanager/firmware_manager.py,sha256=i64S8uPKIj-Qe-xpQYAvelOLYZc0yO31ome9qMT8ObQ,3360
|
|
@@ -11,7 +11,7 @@ vmanager/libvirt_utils.py,sha256=fj74jXP2fHrn5VRWf8kwSlf57VyvWIVZ8gF0POyPUCA,160
|
|
|
11
11
|
vmanager/network_manager.py,sha256=yMU3VXBCDDBKT2H7OL5s9XAD5uknfLPLH72e4228_Do,8627
|
|
12
12
|
vmanager/remote_viewer.py,sha256=evFvp7ilKPuTf-YA4jCGMgO6dzdhbL1iea1lzETwWeU,89495
|
|
13
13
|
vmanager/remote_viewer_gtk4.py,sha256=KIyejYq_PgmSL6yAUOnnloqpaIWdkYIPdoP6CmMaTOo,45994
|
|
14
|
-
vmanager/storage_manager.py,sha256
|
|
14
|
+
vmanager/storage_manager.py,sha256=-Z-NqRklx9PjRhbipvHReSjTDkGqv5uO79vZRAZVv0g,42195
|
|
15
15
|
vmanager/utils.py,sha256=V3jOjBVD9Hf29hC6xIRkfPkjqx-_VL7eozMomaMqW9U,17254
|
|
16
16
|
vmanager/virtui_dev.py,sha256=M2pJEZRNApqezkj4RdEJR9neEdc_7HV3KInVGy9QgiQ,3118
|
|
17
17
|
vmanager/vm_actions.py,sha256=HpK7O2TtpMj-RjBGfF2hdPElwh-aIXh3HbJwjbrEs3w,117279
|
|
@@ -36,11 +36,11 @@ vmanager/modals/config_modal.py,sha256=DMNGqjej-nAvN9t1OqK-uaR_r6d1m4Vi1X5eX7D3Z
|
|
|
36
36
|
vmanager/modals/cpu_mem_pc_modals.py,sha256=puKOVeEqi36wzaXbKNyXjZQjCjXKntuIVTWKh5RsY38,9378
|
|
37
37
|
vmanager/modals/custom_migration_modal.py,sha256=-kDRI8yeBJnh9jx5_ZJFWfXIfcmOZMO0dXUht3uowC8,2289
|
|
38
38
|
vmanager/modals/disk_pool_modals.py,sha256=xme7vS52kkM4CBE3WduNfjqzJz5BAdVGxdD-HfMvw7o,28236
|
|
39
|
-
vmanager/modals/howto_disk_modal.py,sha256=
|
|
40
|
-
vmanager/modals/howto_network_modal.py,sha256=
|
|
41
|
-
vmanager/modals/howto_overlay_modal.py,sha256=
|
|
42
|
-
vmanager/modals/howto_ssh_modal.py,sha256=
|
|
43
|
-
vmanager/modals/howto_virtiofs_modal.py,sha256=
|
|
39
|
+
vmanager/modals/howto_disk_modal.py,sha256=yHMy4GFB39YXV4PVFyJe2y_1WgoeBCIlwr20gZEjF5c,1193
|
|
40
|
+
vmanager/modals/howto_network_modal.py,sha256=arSiPHlq_wW7QLzwBkH_L5O-E7UmvwMXiuaEVlAGJdk,1215
|
|
41
|
+
vmanager/modals/howto_overlay_modal.py,sha256=EJYrrW9nJsD0qSXBKabFbAEP_R1MOodsyDxEDW-ONbc,1228
|
|
42
|
+
vmanager/modals/howto_ssh_modal.py,sha256=e0I-ntbk8-DZSl9t2T3eKW_EBcQvVqiwdqQKa9HIylM,1177
|
|
43
|
+
vmanager/modals/howto_virtiofs_modal.py,sha256=GSnKjSBAutOXa16EToyuOWHycLRf94Qdlnq1XcJGHd0,1209
|
|
44
44
|
vmanager/modals/input_modals.py,sha256=PEfM2IHc8QFR4BZf5uSuJz-7uiclRq6z65cZES_GJpk,7719
|
|
45
45
|
vmanager/modals/log_modal.py,sha256=WXzha9mm5dO9jOmTSO52gJnOmgO7ZkBRrjS2_MiYMyQ,1095
|
|
46
46
|
vmanager/modals/migration_modals.py,sha256=MkuGeDBe1v-BxP7BjrpGB8KxQh4x-SZHHKD1c9mV5wE,20422
|
|
@@ -49,7 +49,7 @@ vmanager/modals/provisioning_modals.py,sha256=Yx7zO8Zvo0qVpW9ctRZg0RmzIwik-tevXA
|
|
|
49
49
|
vmanager/modals/select_server_modals.py,sha256=eAA9hmBjeK_2_AF7jRKyWcocj_LvmgP7ZFRp6H0NGi4,4994
|
|
50
50
|
vmanager/modals/selection_modals.py,sha256=Pd8ZhQ-ezp2MUNT7SV0pdk2OpAYpI5YuuizaDJQTtek,6096
|
|
51
51
|
vmanager/modals/server_modals.py,sha256=fQP8Sj7y7-i2UijS27yFvOywFAWiXdQcj27scYa2IoE,10191
|
|
52
|
-
vmanager/modals/server_prefs_modals.py,sha256
|
|
52
|
+
vmanager/modals/server_prefs_modals.py,sha256=AxRYfJm8Q-IFkKx1u3To_7yZkGhaPanabI-COdULD-k,39275
|
|
53
53
|
vmanager/modals/utils_modals.py,sha256=ijJQZF68icCWNwmKFnoeS7avufjafc7Z4egaaB_79Xg,8675
|
|
54
54
|
vmanager/modals/virsh_modals.py,sha256=t5TT8sNyb3vUJ0a7j6KpH8LdXAvjJ0U9L6NtulSB5x0,4782
|
|
55
55
|
vmanager/modals/virtiofs_modals.py,sha256=R2R6Rcaeg3QLsUgC5v9BjJfvyXtXWVO_eL3iqchanuk,2145
|
|
@@ -58,8 +58,8 @@ vmanager/modals/vmanager_modals.py,sha256=Ix4SxO_e8SfBg0B2iou6M5xk1DigO814e6iwD5
|
|
|
58
58
|
vmanager/modals/vmcard_dialog.py,sha256=EYS19Ooy6KviTPLVzBjGEkALvKRYLJG9rksMHXRO0VU,18674
|
|
59
59
|
vmanager/modals/vmdetails_modals.py,sha256=YE0u71-K-GV5n_38oIzCD2EvqTCIJmJcv3B1Ofej5Rg,149636
|
|
60
60
|
vmanager/modals/xml_modals.py,sha256=iY981cqzVnDYOUo-kw1DHpW9I7IVAU1K7rFqqvxltOk,1653
|
|
61
|
-
virtui_manager-1.1.
|
|
62
|
-
virtui_manager-1.1.
|
|
63
|
-
virtui_manager-1.1.
|
|
64
|
-
virtui_manager-1.1.
|
|
65
|
-
virtui_manager-1.1.
|
|
61
|
+
virtui_manager-1.1.6.dist-info/METADATA,sha256=rVmqF190noCHtV6z3ZsXhWjC-OoAfUmnn6pKC0-8v9c,5329
|
|
62
|
+
virtui_manager-1.1.6.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
63
|
+
virtui_manager-1.1.6.dist-info/entry_points.txt,sha256=hnJHxJyDFPSNdOaxmMYikXeoJMXG2llKG-l_Ygv7ges,163
|
|
64
|
+
virtui_manager-1.1.6.dist-info/top_level.txt,sha256=fQ1nwH-4wZrf6Rc9HEHbudVahpy3TqeuMxgaaNHevxM,9
|
|
65
|
+
virtui_manager-1.1.6.dist-info/RECORD,,
|
vmanager/constants.py
CHANGED
|
@@ -1,89 +1,29 @@
|
|
|
1
1
|
"""
|
|
2
2
|
Modal to show how to manage VM disks.
|
|
3
3
|
"""
|
|
4
|
+
import os
|
|
5
|
+
from pathlib import Path
|
|
4
6
|
from textual.app import ComposeResult
|
|
5
7
|
from textual.containers import Vertical, Horizontal, ScrollableContainer
|
|
6
8
|
from textual.widgets import Button, Markdown
|
|
7
9
|
from textual import on
|
|
8
10
|
from .base_modals import BaseModal
|
|
9
11
|
|
|
10
|
-
HOW_TO_DISK_TEXT = """
|
|
11
|
-
# Managing VM Disks
|
|
12
|
-
|
|
13
|
-
This guide explains the functions of the buttons available in the "Disks" tab.
|
|
14
|
-
|
|
15
|
-
---
|
|
16
|
-
|
|
17
|
-
### Add Disk
|
|
18
|
-
|
|
19
|
-
This button allows you to create a **brand new virtual disk** and attach it to the VM.
|
|
20
|
-
|
|
21
|
-
- You will be prompted to select a **Storage Pool** where the new disk image file will be created.
|
|
22
|
-
- You must provide a **Volume Name** for the new disk (e.g., `new-data-disk.qcow2`).
|
|
23
|
-
- You must specify its **Size** and **Format** (`qcow2` or `raw`).
|
|
24
|
-
- The VM does **not** need to be stopped to add a new disk.
|
|
25
|
-
|
|
26
|
-
---
|
|
27
|
-
|
|
28
|
-
### Attach Existing Disk
|
|
29
|
-
|
|
30
|
-
This attaches a **pre-existing disk image** to your VM. This is useful if you have an existing `.qcow2`, `.raw`, or `.iso` file you want to use.
|
|
31
|
-
|
|
32
|
-
- The system will first ask you to select a **Storage Pool** that contains the disk volume you want to attach.
|
|
33
|
-
- If the disk file is not part of a libvirt storage pool yet, you should first use the "Attach" button in the **Server Preferences -> Storage** tab to make it known to libvirt. This might involve creating a new storage pool for the directory containing your disk file.
|
|
34
|
-
|
|
35
|
-
---
|
|
36
|
-
|
|
37
|
-
### Edit Disk
|
|
38
|
-
|
|
39
|
-
*(This button is enabled only when a disk is selected in the table)*
|
|
40
|
-
|
|
41
|
-
Allows you to modify properties of an attached disk, such as:
|
|
42
|
-
- **Bus Type:** `virtio`, `sata`, `scsi`, etc.
|
|
43
|
-
- **Cache Mode:** `none`, `writeback`, etc.
|
|
44
|
-
- **Discard Mode:** `unmap`, `ignore`.
|
|
45
|
-
|
|
46
|
-
> **Important:** The VM must be **stopped** to edit disk properties.
|
|
47
|
-
|
|
48
|
-
---
|
|
49
|
-
|
|
50
|
-
### Remove Disk
|
|
51
|
-
|
|
52
|
-
*(This button is enabled only when a disk is selected)*
|
|
53
|
-
|
|
54
|
-
This **detaches** the selected disk from the virtual machine.
|
|
55
|
-
|
|
56
|
-
- **This action does NOT delete the disk image file.** The file remains in its storage pool or on your filesystem.
|
|
57
|
-
- It only removes the disk from this specific VM's configuration.
|
|
58
|
-
- The VM must be **stopped** to remove most types of disks.
|
|
59
|
-
|
|
60
|
-
---
|
|
61
|
-
|
|
62
|
-
### Disable Disk
|
|
63
|
-
|
|
64
|
-
*(This button is enabled for active disks)*
|
|
65
|
-
|
|
66
|
-
This temporarily "unplugs" the disk from the VM **without removing its configuration**.
|
|
67
|
-
|
|
68
|
-
- The disk will become invisible to the guest operating system but remains in the disk list with a `(disabled)` status.
|
|
69
|
-
- This is useful for troubleshooting or temporarily preventing access to a disk without fully removing it.
|
|
70
|
-
|
|
71
|
-
---
|
|
72
|
-
|
|
73
|
-
### Enable Disk
|
|
74
|
-
|
|
75
|
-
*(This button is enabled for disabled disks)*
|
|
76
|
-
|
|
77
|
-
This "re-plugs" a disabled disk back into the VM, making it available to the guest operating system again.
|
|
78
|
-
"""
|
|
79
|
-
|
|
80
12
|
class HowToDiskModal(BaseModal[None]):
|
|
81
13
|
"""A modal to display instructions for managing VM disks."""
|
|
82
14
|
|
|
83
15
|
def compose(self) -> ComposeResult:
|
|
16
|
+
# Load markdown from external file
|
|
17
|
+
docs_path = Path(__file__).parent.parent / "appdocs" / "howto_disk.md"
|
|
18
|
+
try:
|
|
19
|
+
with open(docs_path, "r") as f:
|
|
20
|
+
content = f.read()
|
|
21
|
+
except FileNotFoundError:
|
|
22
|
+
content = "# Error: Documentation file not found."
|
|
23
|
+
|
|
84
24
|
with Vertical(id="howto-disk-dialog"):
|
|
85
25
|
with ScrollableContainer(id="howto-disk-content"):
|
|
86
|
-
yield Markdown(
|
|
26
|
+
yield Markdown(content, id="howto-disk-markdown")
|
|
87
27
|
with Horizontal(id="dialog-buttons"):
|
|
88
28
|
yield Button("Close", id="close-btn", variant="primary")
|
|
89
29
|
|
|
@@ -1,56 +1,29 @@
|
|
|
1
1
|
"""
|
|
2
2
|
Modal to show how to configure networks.
|
|
3
3
|
"""
|
|
4
|
+
import os
|
|
5
|
+
from pathlib import Path
|
|
4
6
|
from textual.app import ComposeResult
|
|
5
7
|
from textual.containers import Vertical, Horizontal, ScrollableContainer
|
|
6
8
|
from textual.widgets import Button, Markdown
|
|
7
9
|
from textual import on
|
|
8
10
|
from .base_modals import BaseModal
|
|
9
11
|
|
|
10
|
-
HOW_TO_NETWORK_TEXT = """
|
|
11
|
-
# Understanding Network Configuration in libvirt
|
|
12
|
-
|
|
13
|
-
libvirt provides flexible networking capabilities for virtual machines, allowing them to communicate with each other, the host, and external networks.
|
|
14
|
-
|
|
15
|
-
### Types of Networks
|
|
16
|
-
|
|
17
|
-
1. **NAT (Network Address Translation) Network:**
|
|
18
|
-
* **Purpose:** Allows VMs to access the external network (internet) but prevents external machines from directly initiating connections to the VMs.
|
|
19
|
-
* **Mechanism:** libvirt creates a virtual bridge (e.g., `virbr0`) on the host, and VMs connect to this bridge. The host acts as a router, performing NAT for outgoing connections from VMs. VMs get IP addresses from a DHCP server managed by libvirt.
|
|
20
|
-
* **Use Cases:** Most common setup for general VM usage where VMs just need internet access.
|
|
21
|
-
|
|
22
|
-
2. **Routed Network:**
|
|
23
|
-
* **Purpose:** VMs can communicate with other machines on the host's physical network, and potentially external networks, with proper routing configured on the host and potentially external routers. VMs will have IP addresses on the same subnet as the host's physical network, or a dedicated routed subnet.
|
|
24
|
-
* **Mechanism:** Similar to NAT, a virtual bridge is used, but without NAT. The host needs to be configured to route traffic between the virtual bridge and the physical interface. VMs typically get IP addresses from a DHCP server on the physical network or static IPs.
|
|
25
|
-
* **Use Cases:** When VMs need to be directly accessible from the physical network, or participate as full members of an existing network.
|
|
26
|
-
|
|
27
|
-
3. **Isolated Network:**
|
|
28
|
-
* **Purpose:** VMs on this network can only communicate with each other and the host, but not with external networks.
|
|
29
|
-
* **Mechanism:** A virtual bridge is created, but no routing or NAT is configured to connect it to physical interfaces.
|
|
30
|
-
* **Use Cases:** Testing environments, isolated services, or when you need a private network segment for VMs.
|
|
31
|
-
|
|
32
|
-
### Key Concepts
|
|
33
|
-
|
|
34
|
-
* **Bridge:** A software-based network device that connects multiple network segments at the data link layer. VMs connect to a virtual bridge, which then connects to a physical interface (for NAT/routed) or remains isolated.
|
|
35
|
-
* **DHCP:** Dynamic Host Configuration Protocol. Automatically assigns IP addresses to VMs within a network.
|
|
36
|
-
* **Forward Device:** The physical network interface on the host through which the virtual network's traffic is forwarded (for NAT or routed modes).
|
|
37
|
-
* **MAC Address:** Media Access Control address. A unique identifier assigned to network interfaces. For KVM/QEMU, MAC addresses often start with `52:54:00:`.
|
|
38
|
-
|
|
39
|
-
### Common Tasks
|
|
40
|
-
|
|
41
|
-
* **Create/Edit Network:** Define network parameters like name, IP range, DHCP settings, and forward mode.
|
|
42
|
-
* **Activate/Deactivate Network:** Start or stop a virtual network.
|
|
43
|
-
* **Autostart Network:** Configure a network to start automatically when the libvirt daemon starts.
|
|
44
|
-
* **View XML:** Examine the underlying libvirt XML definition of a network, which provides full details of its configuration.
|
|
45
|
-
"""
|
|
46
|
-
|
|
47
12
|
class HowToNetworkModal(BaseModal[None]):
|
|
48
13
|
"""A modal to display instructions for network configuration."""
|
|
49
14
|
|
|
50
15
|
def compose(self) -> ComposeResult:
|
|
16
|
+
# Load markdown from external file
|
|
17
|
+
docs_path = Path(__file__).parent.parent / "appdocs" / "howto_network.md"
|
|
18
|
+
try:
|
|
19
|
+
with open(docs_path, "r") as f:
|
|
20
|
+
content = f.read()
|
|
21
|
+
except FileNotFoundError:
|
|
22
|
+
content = "# Error: Documentation file not found."
|
|
23
|
+
|
|
51
24
|
with Vertical(id="howto-network-dialog"):
|
|
52
25
|
with ScrollableContainer(id="howto-network-content"):
|
|
53
|
-
yield Markdown(
|
|
26
|
+
yield Markdown(content, id="howto-network-markdown")
|
|
54
27
|
with Horizontal(id="dialog-buttons"):
|
|
55
28
|
yield Button("Close", id="close-btn", variant="primary")
|
|
56
29
|
|
|
@@ -1,69 +1,29 @@
|
|
|
1
1
|
"""
|
|
2
2
|
Modal to show how overlay disks work.
|
|
3
3
|
"""
|
|
4
|
+
import os
|
|
5
|
+
from pathlib import Path
|
|
4
6
|
from textual.app import ComposeResult
|
|
5
7
|
from textual.containers import Vertical, Horizontal, ScrollableContainer
|
|
6
8
|
from textual.widgets import Button, Markdown
|
|
7
9
|
from textual import on
|
|
8
10
|
from .base_modals import BaseModal
|
|
9
11
|
|
|
10
|
-
HOW_TO_OVERLAY_TEXT = """
|
|
11
|
-
# Understanding Snapshots and Disk Overlays
|
|
12
|
-
|
|
13
|
-
Virtual machines in this manager support two ways to preserve states and manage changes: **Snapshots** and **Disk Overlays**. While they share similar goals, they work differently and are suited for different use cases.
|
|
14
|
-
|
|
15
|
-
---
|
|
16
|
-
|
|
17
|
-
### 1. Snapshots (Internal)
|
|
18
|
-
Snapshots are managed directly by libvirt and are typically stored **inside** the disk image (if using QCOW2 format).
|
|
19
|
-
|
|
20
|
-
* **How they work:** When you take a snapshot, libvirt records the current state of the VM's disks and, if the VM is running, its memory (RAM). You can have many snapshots for a single VM, creating a timeline you can jump back and forth in.
|
|
21
|
-
* **Operations:**
|
|
22
|
-
* **Take Snapshot:** Creates a new restore point.
|
|
23
|
-
* **Restore Snapshot:** Reverts the VM to a previous state.
|
|
24
|
-
* **Delete Snapshot:** Removes the restore point from the timeline.
|
|
25
|
-
* **Best for:** Quick restore points before risky operations, and preserving the full "live" state of a running VM.
|
|
26
|
-
|
|
27
|
-
### 2. Disk Overlays (External)
|
|
28
|
-
Overlays are **new files** created on top of a base disk image. This is also known as "External Snapshots" or "Backing Files".
|
|
29
|
-
|
|
30
|
-
* **Key Concepts:**
|
|
31
|
-
* **Base Image (Backing File):** The original disk image that becomes read-only.
|
|
32
|
-
* **Overlay Image:** A new QCOW2 file that records only the changes made *after* its creation.
|
|
33
|
-
* **Backing Chain:** The relationship between layers (e.g., Base -> Overlay 1 -> Overlay 2).
|
|
34
|
-
* **Operations:**
|
|
35
|
-
* **New Overlay:** Freezes the current disk and starts a new layer.
|
|
36
|
-
* **Discard Overlay (Revert):** Deletes the overlay file and reverts to the base image.
|
|
37
|
-
* **Commit Disk (Merge):** Merges changes from the overlay into the base image, making them permanent.
|
|
38
|
-
* **Best for:** Maintaining "Golden Images", branching multiple VMs from a single base, and isolating large changes in separate files.
|
|
39
|
-
|
|
40
|
-
---
|
|
41
|
-
|
|
42
|
-
### Comparison: Snapshot vs. Overlay
|
|
43
|
-
|
|
44
|
-
| Feature | Snapshots (Internal) | Disk Overlays (External) |
|
|
45
|
-
| :--- | :--- | :--- |
|
|
46
|
-
| **Storage** | Inside the existing disk file | In a new, separate file |
|
|
47
|
-
| **VM State** | Can include RAM (Live state) | Disk only (requires VM stop) |
|
|
48
|
-
| **Management** | Timeline (multiple points) | Layered (Base + Changes) |
|
|
49
|
-
| **Primary Use** | Quick restore points | Permanent branching / Golden images |
|
|
50
|
-
|
|
51
|
-
---
|
|
52
|
-
|
|
53
|
-
### How Overlays Work (Technical)
|
|
54
|
-
|
|
55
|
-
1. **Creation:** When you create a "New Overlay", the current disk image is set as the backing file for a newly created QCOW2 file. The VM is then updated to point to this new overlay file instead of the original disk.
|
|
56
|
-
2. **Read Operations:** When the VM needs to read data, it first checks the overlay. If the data has been modified, it reads from the overlay. If not, it transparently reads from the backing file.
|
|
57
|
-
3. **Write Operations:** All writes are directed to the overlay file. The backing file remains untouched and pristine.
|
|
58
|
-
"""
|
|
59
|
-
|
|
60
12
|
class HowToOverlayModal(BaseModal[None]):
|
|
61
13
|
"""A modal to display instructions for disk overlays."""
|
|
62
14
|
|
|
63
15
|
def compose(self) -> ComposeResult:
|
|
16
|
+
# Load markdown from external file
|
|
17
|
+
docs_path = Path(__file__).parent.parent / "appdocs" / "howto_overlay.md"
|
|
18
|
+
try:
|
|
19
|
+
with open(docs_path, "r") as f:
|
|
20
|
+
content = f.read()
|
|
21
|
+
except FileNotFoundError:
|
|
22
|
+
content = "# Error: Documentation file not found."
|
|
23
|
+
|
|
64
24
|
with Vertical(id="howto-overlay-dialog", classes="howto-dialog"):
|
|
65
25
|
with ScrollableContainer(id="howto-overlay-content"):
|
|
66
|
-
yield Markdown(
|
|
26
|
+
yield Markdown(content, id="howto-overlay-markdown")
|
|
67
27
|
with Horizontal(id="dialog-buttons"):
|
|
68
28
|
yield Button("Close", id="close-btn", variant="primary")
|
|
69
29
|
|
|
@@ -1,84 +1,28 @@
|
|
|
1
1
|
"""
|
|
2
2
|
Modal to show how to use ssh-agent.
|
|
3
3
|
"""
|
|
4
|
+
from pathlib import Path
|
|
4
5
|
from textual.app import ComposeResult
|
|
5
6
|
from textual.containers import Vertical, Horizontal, ScrollableContainer
|
|
6
7
|
from textual.widgets import Button, Markdown
|
|
7
8
|
from textual import on
|
|
8
9
|
from .base_modals import BaseModal
|
|
9
10
|
|
|
10
|
-
HOW_TO_SSH_TEXT = """
|
|
11
|
-
# Using an SSH Agent for Passwordless Connections
|
|
12
|
-
|
|
13
|
-
Using an `ssh-agent` is the standard and most secure way to handle passphrase-protected SSH keys, allowing you to connect without repeatedly entering your passphrase.
|
|
14
|
-
|
|
15
|
-
### What is an SSH Agent?
|
|
16
|
-
|
|
17
|
-
An `ssh-agent` is a background program that securely stores your private SSH keys in memory. When you try to connect to a remote server, SSH can ask the agent for the key, and the agent provides it. You only need to "unlock" your key once.
|
|
18
|
-
|
|
19
|
-
---
|
|
20
|
-
|
|
21
|
-
### Step 1: Start the `ssh-agent`
|
|
22
|
-
|
|
23
|
-
On most modern desktop environments, an agent is often started automatically. If not, run this in your terminal:
|
|
24
|
-
|
|
25
|
-
```bash
|
|
26
|
-
eval "$(ssh-agent -s)"
|
|
27
|
-
```
|
|
28
|
-
|
|
29
|
-
> To make this permanent, add the command to your shell's startup file (e.g., `~/.bashrc` or `~/.zshrc`).
|
|
30
|
-
|
|
31
|
-
---
|
|
32
|
-
|
|
33
|
-
### Step 2: Add Your SSH Key to the Agent
|
|
34
|
-
|
|
35
|
-
Use the `ssh-add` command. If your key is in a default location (`~/.ssh/id_rsa`, etc.), you can just run:
|
|
36
|
-
|
|
37
|
-
```bash
|
|
38
|
-
ssh-add
|
|
39
|
-
```
|
|
40
|
-
|
|
41
|
-
If your key is elsewhere, specify the path to the **private key**:
|
|
42
|
-
|
|
43
|
-
```bash
|
|
44
|
-
ssh-add /path/to/your/private_key
|
|
45
|
-
```
|
|
46
|
-
|
|
47
|
-
You will be prompted for your key's passphrase **one time**.
|
|
48
|
-
|
|
49
|
-
To verify the key was added, list the agent's keys:
|
|
50
|
-
```bash
|
|
51
|
-
ssh-add -l
|
|
52
|
-
```
|
|
53
|
-
---
|
|
54
|
-
|
|
55
|
-
### Step 3: Connect
|
|
56
|
-
|
|
57
|
-
That's it! `Virtui Manager` will now use the agent to authenticate for any `qemu+ssh://` connections without any more prompts.
|
|
58
|
-
|
|
59
|
-
---
|
|
60
|
-
|
|
61
|
-
### SSH Compression for Performance
|
|
62
|
-
|
|
63
|
-
For connections over slower networks, enabling SSH compression can significantly improve performance. This is configured in your SSH client's configuration file.
|
|
64
|
-
|
|
65
|
-
To enable compression for a specific host, add the following to your `~/.ssh/config` file:
|
|
66
|
-
|
|
67
|
-
```
|
|
68
|
-
Host your_remote_host_name
|
|
69
|
-
Compression yes
|
|
70
|
-
```
|
|
71
|
-
|
|
72
|
-
Replace `your_remote_host_name` with the actual hostname or IP address you use in your `qemu+ssh://` URI. If you want to enable compression for all SSH connections, you can use `Host *`.
|
|
73
|
-
"""
|
|
74
|
-
|
|
75
11
|
class HowToSSHModal(BaseModal[None]):
|
|
76
12
|
"""A modal to display instructions for using an ssh-agent."""
|
|
77
13
|
|
|
78
14
|
def compose(self) -> ComposeResult:
|
|
15
|
+
# Load markdown from external file
|
|
16
|
+
docs_path = Path(__file__).parent.parent / "appdocs" / "howto_ssh.md"
|
|
17
|
+
try:
|
|
18
|
+
with open(docs_path, "r") as f:
|
|
19
|
+
content = f.read()
|
|
20
|
+
except FileNotFoundError:
|
|
21
|
+
content = "# Error: Documentation file not found."
|
|
22
|
+
|
|
79
23
|
with Vertical(id="howto-ssh-dialog"):
|
|
80
24
|
with ScrollableContainer(id="howto-ssh-content"):
|
|
81
|
-
yield Markdown(
|
|
25
|
+
yield Markdown(content, id="howto-ssh-markdown")
|
|
82
26
|
with Horizontal(id="dialog-buttons"):
|
|
83
27
|
yield Button("Close", id="close-btn", variant="primary")
|
|
84
28
|
|
|
@@ -1,81 +1,29 @@
|
|
|
1
1
|
"""
|
|
2
2
|
Modal to show how to use VirtIO-FS.
|
|
3
3
|
"""
|
|
4
|
+
import os
|
|
5
|
+
from pathlib import Path
|
|
4
6
|
from textual.app import ComposeResult
|
|
5
7
|
from textual.containers import Vertical, Horizontal, ScrollableContainer
|
|
6
8
|
from textual.widgets import Button, Markdown
|
|
7
9
|
from textual import on
|
|
8
10
|
from .base_modals import BaseModal
|
|
9
11
|
|
|
10
|
-
HOW_TO_VIRTIOFS_TEXT = """
|
|
11
|
-
# Using VirtIO-FS for Host-Guest File Sharing
|
|
12
|
-
|
|
13
|
-
VirtIO-FS is a high-performance shared filesystem that lets you share a directory from your host machine directly with a guest VM.
|
|
14
|
-
|
|
15
|
-
---
|
|
16
|
-
|
|
17
|
-
### Host Prerequisites
|
|
18
|
-
|
|
19
|
-
1. **Shared Memory:** VirtIO-FS requires shared memory to be enabled for the VM. You can enable this in the **"Mem"** tab.
|
|
20
|
-
2. **Permissions:** The user running QEMU/libvirt on the host must have the necessary permissions to read (and write, if needed) the source directory you want to share.
|
|
21
|
-
|
|
22
|
-
---
|
|
23
|
-
|
|
24
|
-
### Adding a VirtIO-FS Mount
|
|
25
|
-
|
|
26
|
-
- **Source Path:** The absolute path to the directory on your **host machine** that you want to share.
|
|
27
|
-
- **Target Path:** This is a "mount tag" or a label that the guest VM will use to identify the shared directory. It is **not** a path inside the guest. For example, you could use `shared-data`.
|
|
28
|
-
|
|
29
|
-
---
|
|
30
|
-
|
|
31
|
-
### Mounting in a Linux Guest
|
|
32
|
-
|
|
33
|
-
Most modern Linux distributions include the necessary VirtIO-FS drivers.
|
|
34
|
-
|
|
35
|
-
**1. Create a Mount Point:**
|
|
36
|
-
This is the directory inside your VM where the shared files will appear.
|
|
37
|
-
|
|
38
|
-
```bash
|
|
39
|
-
sudo mkdir /mnt/my_host_share
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
**2. Mount the Share:**
|
|
43
|
-
Use the `mount` command with the filesystem type `virtiofs` and the **Target Path (mount tag)** you defined.
|
|
44
|
-
|
|
45
|
-
```bash
|
|
46
|
-
sudo mount -t virtiofs 'your-target-path' /mnt/my_host_share
|
|
47
|
-
```
|
|
48
|
-
*(Replace `'your-target-path'` with the actual tag you set)*
|
|
49
|
-
|
|
50
|
-
**3. Automount on Boot (Optional):**
|
|
51
|
-
To make the share available automatically every time the VM boots, add an entry to `/etc/fstab`:
|
|
52
|
-
|
|
53
|
-
```
|
|
54
|
-
your-target-path /mnt/my_host_share virtiofs defaults,nofail 0 0
|
|
55
|
-
```
|
|
56
|
-
> The `nofail` option is recommended to prevent boot issues if the share is not available.
|
|
57
|
-
|
|
58
|
-
---
|
|
59
|
-
|
|
60
|
-
### Mounting in a Windows Guest
|
|
61
|
-
|
|
62
|
-
**1. Install Drivers:**
|
|
63
|
-
You must install the VirtIO-FS drivers in the Windows guest. These are included in the **"VirtIO-Win Guest Tools"** package, which you can typically download as an ISO file.
|
|
64
|
-
- Download the latest stable `virtio-win.iso` from the [Fedora VirtIO-Win project](https://github.com/virtio-win/virtio-win-pkg-scripts/blob/master/README.md).
|
|
65
|
-
- Attach the ISO to your VM as a CD-ROM.
|
|
66
|
-
- Open the CD-ROM in Windows and run the `virtio-win-guest-tools.exe` installer, ensuring the **"VirtIO-FS"** feature is selected.
|
|
67
|
-
|
|
68
|
-
**2. Access the Share:**
|
|
69
|
-
After installation and a reboot, the VirtIO-FS service will start. The shared folder will automatically appear as a network drive in **This PC** (or My Computer). The drive will be named after the **Target Path (mount tag)** you set.
|
|
70
|
-
"""
|
|
71
|
-
|
|
72
12
|
class HowToVirtIOFSModal(BaseModal[None]):
|
|
73
13
|
"""A modal to display instructions for using VirtIO-FS."""
|
|
74
14
|
|
|
75
15
|
def compose(self) -> ComposeResult:
|
|
16
|
+
# Load markdown from external file
|
|
17
|
+
docs_path = Path(__file__).parent.parent / "appdocs" / "howto_virtiofs.md"
|
|
18
|
+
try:
|
|
19
|
+
with open(docs_path, "r") as f:
|
|
20
|
+
content = f.read()
|
|
21
|
+
except FileNotFoundError:
|
|
22
|
+
content = "# Error: Documentation file not found."
|
|
23
|
+
|
|
76
24
|
with Vertical(id="howto-virtiofs-dialog"):
|
|
77
25
|
with ScrollableContainer(id="howto-virtiofs-content"):
|
|
78
|
-
yield Markdown(
|
|
26
|
+
yield Markdown(content, id="howto-virtiofs-markdown")
|
|
79
27
|
with Horizontal(id="dialog-buttons"):
|
|
80
28
|
yield Button("Close", id="close-btn", variant="primary")
|
|
81
29
|
|
|
@@ -248,20 +248,28 @@ class ServerPrefModal(BaseModal[None]):
|
|
|
248
248
|
pools = storage_manager.list_storage_pools(self.conn)
|
|
249
249
|
node_to_select = None
|
|
250
250
|
for pool_data in pools:
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
251
|
+
try:
|
|
252
|
+
pool_name = pool_data['name']
|
|
253
|
+
status = pool_data['status']
|
|
254
|
+
autostart = "autostart" if pool_data['autostart'] else "no autostart"
|
|
255
|
+
label = f"{pool_name} [{status}, {autostart}]"
|
|
256
|
+
pool_node = tree.root.add(label, data=pool_data)
|
|
257
|
+
pool_node.data["type"] = "pool"
|
|
258
|
+
# Add a dummy node to make the pool node expandable
|
|
259
|
+
pool_node.add_leaf("Loading volumes...")
|
|
260
|
+
|
|
261
|
+
if expand_pools and pool_name in expand_pools:
|
|
262
|
+
self.app.call_later(pool_node.expand)
|
|
263
|
+
|
|
264
|
+
if select_pool and pool_name == select_pool:
|
|
265
|
+
node_to_select = pool_node
|
|
266
|
+
except libvirt.libvirtError:
|
|
267
|
+
# Handle cases where pool might be listed but inaccessible (e.g. NFS down)
|
|
268
|
+
pool_name = pool_data.get('name', 'Unknown')
|
|
269
|
+
label = f"{pool_name} [unavailable]"
|
|
270
|
+
pool_node = tree.root.add(label, data=pool_data)
|
|
271
|
+
pool_node.data["type"] = "pool"
|
|
272
|
+
pool_node.add_leaf("Pool unavailable")
|
|
265
273
|
|
|
266
274
|
if node_to_select:
|
|
267
275
|
self.app.call_later(tree.select_node, node_to_select)
|
|
@@ -308,7 +316,8 @@ class ServerPrefModal(BaseModal[None]):
|
|
|
308
316
|
if len(node.children) == 1 and node.children[0].data is None:
|
|
309
317
|
node.remove_children()
|
|
310
318
|
pool = node_data.get('pool')
|
|
311
|
-
|
|
319
|
+
# Check cached status instead of calling pool.isActive() to avoid blocking
|
|
320
|
+
if pool and node_data.get('status') == 'active':
|
|
312
321
|
volumes = storage_manager.list_storage_volumes(pool)
|
|
313
322
|
for vol_data in volumes:
|
|
314
323
|
vol_name = vol_data['name']
|
|
@@ -404,7 +413,7 @@ class ServerPrefModal(BaseModal[None]):
|
|
|
404
413
|
self.app.show_error_message("Could not find pool object to edit.")
|
|
405
414
|
return
|
|
406
415
|
|
|
407
|
-
if
|
|
416
|
+
if node_data.get('status') == 'active':
|
|
408
417
|
self.app.show_error_message("Pool must be inactive to edit its XML definition.")
|
|
409
418
|
return
|
|
410
419
|
|
vmanager/storage_manager.py
CHANGED
|
@@ -29,18 +29,40 @@ def list_storage_pools(conn: libvirt.virConnect) -> List[Dict[str, Any]]:
|
|
|
29
29
|
pools = conn.listAllStoragePools(0)
|
|
30
30
|
for pool in pools:
|
|
31
31
|
try:
|
|
32
|
+
# Try to get basic info
|
|
33
|
+
try:
|
|
34
|
+
name = pool.name()
|
|
35
|
+
except libvirt.libvirtError:
|
|
36
|
+
name = "Unknown Pool"
|
|
37
|
+
|
|
32
38
|
is_active = pool.isActive()
|
|
33
39
|
info = pool.info()
|
|
34
40
|
pools_info.append({
|
|
35
|
-
'name':
|
|
41
|
+
'name': name,
|
|
36
42
|
'pool': pool,
|
|
37
43
|
'status': 'active' if is_active else 'inactive',
|
|
38
44
|
'autostart': pool.autostart() == 1,
|
|
39
45
|
'capacity': info[1],
|
|
40
46
|
'allocation': info[2],
|
|
41
47
|
})
|
|
42
|
-
except libvirt.libvirtError:
|
|
43
|
-
|
|
48
|
+
except libvirt.libvirtError as e:
|
|
49
|
+
# If we fail to get details (e.g. NFS down), still list the pool but as unavailable
|
|
50
|
+
if 'name' not in locals():
|
|
51
|
+
try:
|
|
52
|
+
name = pool.name()
|
|
53
|
+
except:
|
|
54
|
+
name = "Unknown Pool"
|
|
55
|
+
|
|
56
|
+
logging.warning(f"Failed to get details for pool '{name}': {e}")
|
|
57
|
+
pools_info.append({
|
|
58
|
+
'name': name,
|
|
59
|
+
'pool': pool,
|
|
60
|
+
'status': 'unavailable',
|
|
61
|
+
'autostart': False,
|
|
62
|
+
'capacity': 0,
|
|
63
|
+
'allocation': 0,
|
|
64
|
+
'error': str(e)
|
|
65
|
+
})
|
|
44
66
|
except libvirt.libvirtError:
|
|
45
67
|
return []
|
|
46
68
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|