diff --git a/.gitignore b/.gitignore
index 7ad5255..5a0b1ca 100644
--- a/.gitignore
+++ b/.gitignore
@@ -9,3 +9,5 @@
/backend/venv
__pycache__/
+
+.vscode/
\ No newline at end of file
diff --git a/README.md b/README.md
index c9ade7b..a55d5db 100644
--- a/README.md
+++ b/README.md
@@ -68,8 +68,13 @@ Developers
| [
Loan Riyanto](https://github.com/skl1017)
| :---: |
+<<<<<<< HEAD
+Manager
+| [
Laurent Gonzalez](https://github.com/lg-epitech)
+=======
### Manager
| [
Laurent Gonzalez](https://github.com/lg-epitech) |
+>>>>>>> main
| :---: |
diff --git a/backend/app/core/config.py b/backend/app/core/config.py
index b85d37b..1a01f29 100644
--- a/backend/app/core/config.py
+++ b/backend/app/core/config.py
@@ -1,8 +1,9 @@
import libvirt
-from app.orm.vm import VmORM
from dotenv import load_dotenv
from os import getenv
from sqlmodel import create_engine, SQLModel
+from app.telemetry.monitor import SystemMonitor
+from app.orm.vm import VmORM
load_dotenv()
@@ -22,8 +23,10 @@ class QEMUConfig:
@classmethod
def get_connection(cls):
if cls.qemu_conn is None or cls.qemu_conn.isAlive() == 0:
- try:
- qemu_conn = libvirt.open("qemu:///system")
+ try:
+ cls.qemu_conn = libvirt.open("qemu:///system")
except libvirt.libvirtError:
raise
- return qemu_conn
+ return cls.qemu_conn
+
+system_monitor = SystemMonitor(interval=3, get_connection=QEMUConfig.get_connection)
diff --git a/backend/app/core/xml_builder.py b/backend/app/core/xml_builder.py
index f1a896e..0efb0cd 100644
--- a/backend/app/core/xml_builder.py
+++ b/backend/app/core/xml_builder.py
@@ -41,11 +41,11 @@ def build_xml(vm_read: VmRead):
etree.SubElement(channel, "target", type="virtio",
name="org.qemu.guest_agent.0")
- # disk_seed = etree.SubElement(devices, "disk", type="file", device="cdrom")
- # etree.SubElement(disk_seed, "driver", name="qemu", type="raw")
- # etree.SubElement(disk_seed, "source", file="/var/lib/distribox/images/seed.iso")
- # etree.SubElement(disk_seed, "target", dev="hdb", bus="ide")
- # etree.SubElement(disk_seed, "readonly")
+ disk_seed = etree.SubElement(devices, "disk", type="file", device="cdrom")
+ etree.SubElement(disk_seed, "driver", name="qemu", type="raw")
+ etree.SubElement(disk_seed, "source", file="/var/lib/distribox/images/seed.iso")
+ etree.SubElement(disk_seed, "target", dev="hdb", bus="ide")
+ etree.SubElement(disk_seed, "readonly")
iface = etree.SubElement(devices, "interface", type="network")
etree.SubElement(iface, "source", network="default")
diff --git a/backend/app/main.py b/backend/app/main.py
index 059f6fb..7d744d3 100644
--- a/backend/app/main.py
+++ b/backend/app/main.py
@@ -1,6 +1,6 @@
from fastapi import FastAPI, HTTPException
from fastapi.responses import JSONResponse
-from app.routes import vm
+from app.routes import vm, image, host
app = FastAPI()
@@ -19,3 +19,5 @@ async def global_exception_handler(_, exc: Exception):
content={"detail": str(exc)}
)
app.include_router(vm.router, prefix="/vms")
+app.include_router(image.router, prefix="/images")
+app.include_router(host.router, prefix="/host")
diff --git a/backend/app/models/host.py b/backend/app/models/host.py
new file mode 100644
index 0000000..8267e4f
--- /dev/null
+++ b/backend/app/models/host.py
@@ -0,0 +1,20 @@
+from pydantic import BaseModel
+from app.models.resources import MemoryInfoBase, DiskInfoBase, CPUInfoBase
+
+class DiskInfoHost(DiskInfoBase):
+ distribox_used: float
+
+class MemoryInfoHost(MemoryInfoBase):
+ pass
+
+class CPUInfoHost(CPUInfoBase):
+ pass
+
+class HostInfoBase(BaseModel):
+ disk: DiskInfoHost
+ mem: MemoryInfoHost
+ cpu: CPUInfoHost
+
+
+
+
diff --git a/backend/app/models/image.py b/backend/app/models/image.py
new file mode 100644
index 0000000..65d071b
--- /dev/null
+++ b/backend/app/models/image.py
@@ -0,0 +1,9 @@
+from pydantic import BaseModel
+
+class ImageBase(BaseModel):
+ name: str
+ virtual_size: float
+ actual_size: float
+
+class ImageRead(ImageBase):
+ pass
\ No newline at end of file
diff --git a/backend/app/models/resources.py b/backend/app/models/resources.py
new file mode 100644
index 0000000..39c156f
--- /dev/null
+++ b/backend/app/models/resources.py
@@ -0,0 +1,20 @@
+from pydantic import BaseModel
+
+class ResourceStatsBase(BaseModel):
+ total: float
+ used: float
+ available: float
+ percent_used: float
+
+class DiskInfoBase(ResourceStatsBase):
+ pass
+
+class MemoryInfoBase(ResourceStatsBase):
+ pass
+
+class CPUInfoBase(BaseModel):
+ percent_used_total: float
+ percent_used_per_cpu: list[float]
+ percent_used_per_vm: list
+ percent_used_total_vms: float
+ cpu_count: int
\ No newline at end of file
diff --git a/backend/app/models/vm.py b/backend/app/models/vm.py
index 09d1662..465fae4 100644
--- a/backend/app/models/vm.py
+++ b/backend/app/models/vm.py
@@ -1,5 +1,5 @@
from pydantic import BaseModel
-from typing import Optional
+from uuid import UUID
class VmBase(BaseModel):
@@ -10,9 +10,12 @@ class VmBase(BaseModel):
class VmRead(VmBase):
- id: str
+ id: UUID
state: str
class VmCreate(VmBase):
pass
+
+class PasswordCreated(BaseModel):
+ password: str
\ No newline at end of file
diff --git a/backend/app/orm/vm.py b/backend/app/orm/vm.py
index 75bd317..3f3761b 100644
--- a/backend/app/orm/vm.py
+++ b/backend/app/orm/vm.py
@@ -11,3 +11,4 @@ class VmORM(SQLModel, table=True, ):
vcpus: int
disk_size: int
password: str | None = None
+
diff --git a/backend/app/routes/host.py b/backend/app/routes/host.py
new file mode 100644
index 0000000..8cdd3c5
--- /dev/null
+++ b/backend/app/routes/host.py
@@ -0,0 +1,10 @@
+from fastapi import APIRouter, status
+from app.services.host_service import HostService
+from app.models.host import HostInfoBase
+
+
+router = APIRouter()
+
+@router.get("/info", status_code=status.HTTP_200_OK, response_model=HostInfoBase)
+def get_host_info():
+ return HostService.get_host_info()
\ No newline at end of file
diff --git a/backend/app/routes/image.py b/backend/app/routes/image.py
new file mode 100644
index 0000000..baffa16
--- /dev/null
+++ b/backend/app/routes/image.py
@@ -0,0 +1,13 @@
+from fastapi import APIRouter, status
+from app.services.image_service import ImageService
+from app.models.image import ImageRead
+
+
+router = APIRouter()
+
+@router.get("/", status_code=status.HTTP_200_OK, response_model=list[ImageRead])
+def get_distribox_image_list():
+ try:
+ return ImageService.get_distribox_image_list()
+ except Exception:
+ raise
\ No newline at end of file
diff --git a/backend/app/routes/vm.py b/backend/app/routes/vm.py
index 0bb6ae0..73b0443 100644
--- a/backend/app/routes/vm.py
+++ b/backend/app/routes/vm.py
@@ -1,46 +1,42 @@
-from fastapi import status, APIRouter
-from app.models.vm import VmCreate
+from fastapi import status, APIRouter
+from app.models.vm import VmCreate, VmRead, PasswordCreated
from app.services.vm_service import VmService
router = APIRouter()
-
-@router.get('/', status_code=status.HTTP_200_OK)
+@router.get('/', status_code=status.HTTP_200_OK, response_model=list[VmRead])
def get_vm_list():
vm_list = VmService.get_vm_list()
return vm_list
-
-@router.get("/{vm_id}", status_code=status.HTTP_200_OK)
+@router.get("/{vm_id}", status_code=status.HTTP_200_OK, response_model=VmRead)
def get_vm(vm_id: str):
vm = VmService.get_vm(vm_id)
return vm
-
-@router.get("/{vm_id}/state", status_code=status.HTTP_200_OK)
-def get_vm(vm_id: str):
- state = VmService.get_state(vm_id)
- return state
-
-
-@router.post("/{vm_id}/start", status_code=status.HTTP_200_OK)
+@router.post("/{vm_id}/start", status_code=status.HTTP_200_OK, response_model=VmRead)
def start_vm(vm_id: str):
vm = VmService.start_vm(vm_id)
return vm
-
-@router.post("/{vm_id}/stop", status_code=status.HTTP_200_OK)
+@router.post("/{vm_id}/stop", status_code=status.HTTP_200_OK, response_model=VmRead)
def stop_vm(vm_id: str):
vm = VmService.stop_vm(vm_id)
return vm
-
-@router.post("/", status_code=status.HTTP_201_CREATED)
-def create_vm(vm: VmCreate):
+@router.post("/", status_code=status.HTTP_201_CREATED, response_model=VmRead)
+def create_vm(vm:VmCreate):
created_vm = VmService.create_vm(vm)
return created_vm
-
-@router.put("/{vm_id}/password", status_code=status.HTTP_200_OK)
+@router.put("/{vm_id}/password", status_code=status.HTTP_200_OK, response_model=PasswordCreated)
def set_vm_password(vm_id):
return VmService.set_vm_password(vm_id)
+
+@router.delete("/{vm_id}", status_code=status.HTTP_204_NO_CONTENT)
+def remove_vm (vm_id: str):
+ VmService.remove_vm(vm_id)
+
+@router.delete("/{vm_id}/password", status_code=status.HTTP_204_NO_CONTENT)
+def remove_vm_password(vm_id: str):
+ VmService.remove_vm_password(vm_id)
diff --git a/backend/app/services/host_service.py b/backend/app/services/host_service.py
new file mode 100644
index 0000000..16d7cae
--- /dev/null
+++ b/backend/app/services/host_service.py
@@ -0,0 +1,96 @@
+import shutil
+import psutil
+from app.core.config import QEMUConfig
+from threading import Thread
+from app.models.host import HostInfoBase
+from app.services.vm_service import VmService
+import libvirt
+from time import sleep
+from collections import Counter
+from app.core.config import system_monitor
+
+# cpu_total_usage = 0
+# usage_per_cpu = []
+
+# def get_cpu_usage_percent(cpu_idle_time_t2, cpu_idle_time_t1, cpu_total_time_t2, cpu_total_time_t1):
+# return (1 - (cpu_idle_time_t2 - cpu_idle_time_t1)/ (sum(cpu_total_time_t2) - sum(cpu_total_time_t1))) * 100
+
+# def get_cpu_counters():
+# per_cpus = psutil.cpu_times(percpu=True)
+# total = Counter()
+# for cpu in per_cpus:
+# total.update(cpu._asdict())
+# cpu_total = psutil.cpu_times()
+# return {
+# "per_cpus": per_cpus,
+# "cpu_total": cpu_total
+# }
+
+# def get_cpu_usage():
+# global cpu_total_usage, usage_per_cpu
+# while True:
+# cpu_usage_t1 = get_cpu_counters()
+# sleep(3)
+# cpu_usage_t2 = get_cpu_counters()
+# cpu_total_usage = get_cpu_usage_percent(cpu_usage_t2["cpu_total"].idle, cpu_usage_t1["cpu_total"].idle, cpu_usage_t2["cpu_total"], cpu_usage_t1["cpu_total"])
+# usage_per_cpu = []
+# for i in range(len(cpu_usage_t1["per_cpus"])):
+# usage_per_cpu.append(round(get_cpu_usage_percent(cpu_usage_t2["per_cpus"][i].idle, cpu_usage_t1["per_cpus"][i].idle, cpu_usage_t2["per_cpus"][i], cpu_usage_t1["per_cpus"][i]), 2))
+
+
+# Thread(target=get_cpu_usage, daemon=True).start()
+
+conn = QEMUConfig.get_connection()
+stats = conn.getAllDomainStats(stats = libvirt.VIR_DOMAIN_STATS_CPU_TOTAL | libvirt.VIR_DOMAIN_STATS_INTERFACE,
+ flags = libvirt.VIR_CONNECT_GET_ALL_DOMAINS_STATS_RUNNING)
+
+for s in stats:
+ print('lol')
+ print(s[0].name(), s[1])
+
+# cpu_overall_time_t1 = psutil.cpu_times(percpu=True)
+# cpu_total_time_t1 = psutil.cpu_times()
+# print(sum(cpu_total_time_t1))
+# cpu_idle_time_t1 = cpu_total_time_t1.idle
+
+
+# sleep(3)
+# cpu_total_time_t2 = psutil.cpu_times()
+# cpu_idle_time_t2 = cpu_total_time_t2.idle
+
+# percent_used = (1 - (cpu_idle_time_t2 - cpu_idle_time_t1)/ (sum(cpu_total_time_t2) - sum(cpu_total_time_t1))) * 100
+# print(percent_used)
+# print(psutil.cpu_percent(interval=1))
+# print(sum(psutil.cpu_times(percpu=True)))
+
+
+# print(psutil.cpu_times_percent(interval=2))
+
+
+# for dom_stats in stats:
+# data = dom_stats[1]
+# print(data.get('cpu.system'))
+
+class HostService:
+
+ @staticmethod
+ def get_host_info():
+ disk_usage = shutil.disk_usage("/")
+ mem_usage = psutil.virtual_memory()
+
+ disk = {
+ "total": round(disk_usage.total / 2**30, 2),
+ "used": round(disk_usage.used / 2**30, 2),
+ "available": round(disk_usage.free / 2**30, 2),
+ "percent_used": round((disk_usage.used / disk_usage.total) * 100, 2),
+ "distribox_used": sum([vm.disk_size for vm in VmService.get_vm_list()])
+ }
+ mem = {
+ "total": round(mem_usage.total / 2**30, 2),
+ "used": round(mem_usage.used / 2**30, 2),
+ "available": round(mem_usage.available / 2**30, 2),
+ "percent_used": mem_usage.percent
+ }
+ cpu = system_monitor.cpu
+ host_info = HostInfoBase(disk=disk, mem=mem, cpu=cpu)
+ return host_info
diff --git a/backend/app/services/image_service.py b/backend/app/services/image_service.py
new file mode 100644
index 0000000..c02f870
--- /dev/null
+++ b/backend/app/services/image_service.py
@@ -0,0 +1,23 @@
+import subprocess
+import json
+from app.models.image import ImageRead
+from pathlib import Path
+
+class ImageService():
+ @staticmethod
+ def get_distribox_image_list():
+ image_list = []
+ images_folder = Path("/var/lib/distribox/images")
+ for file in images_folder.iterdir():
+ image_info = subprocess.run(
+ ["qemu-img", "info", "--output=json", file]
+ ,capture_output=True
+ ,text=True
+ ,check=True)
+ image_info_json = json.loads(image_info.stdout)
+ image_list.append(ImageRead(
+ name=image_info_json["filename"].split("/")[-1],
+ virtual_size=round(image_info_json["virtual-size"] / (1024 ** 3), 2),
+ actual_size=round(image_info_json["actual-size"] / (1024 ** 3), 2)
+ ))
+ return image_list
diff --git a/backend/app/services/vm_service.py b/backend/app/services/vm_service.py
index 8e855bf..e08fafb 100644
--- a/backend/app/services/vm_service.py
+++ b/backend/app/services/vm_service.py
@@ -1,5 +1,6 @@
import uuid
-import shutil
+import subprocess
+from shutil import copy, rmtree
import libvirt
import hashlib
from app.utils.vm import wait_for_state
@@ -8,7 +9,7 @@
from app.models.vm import VmCreate
from app.core.xml_builder import build_xml
from app.core.config import QEMUConfig, engine
-from sqlmodel import Session, select, update
+from sqlmodel import Session, select, update, delete
from app.orm.vm import VmORM
from fastapi import status, HTTPException
@@ -30,7 +31,9 @@ def create(self):
distribox_image_dir = IMAGES_DIR / f"distribox-{self.os}.qcow2"
try:
vm_dir.mkdir(parents=True, exist_ok=True)
- shutil.copy(distribox_image_dir, vm_dir)
+ copy(distribox_image_dir, vm_dir)
+ vm_path = vm_dir / f"distribox-{self.os}.qcow2"
+ subprocess.run(["qemu-img", "resize", vm_path, f"+{self.disk_size}G"])
vm_xml = build_xml(self)
conn = QEMUConfig.get_connection()
conn.defineXML(vm_xml)
@@ -122,8 +125,23 @@ def get_state(self):
vm = conn.lookupByName(str(self.id))
state, _ = vm.state()
except Exception:
+ raise
+ return {"state" : VM_STATE_NAMES.get(state, 'None')}
+
+ def remove(self):
+ try:
+ self.stop()
+ conn = QEMUConfig.get_connection()
+ vm = conn.lookupByName(str(self.id))
+ vm.undefine()
+ vm_dir = VMS_DIR / str(self.id)
+ rmtree(vm_dir)
+ with Session(engine) as session:
+ statement = delete(VmORM).where(VmORM.id == self.id)
+ session.exec(statement)
+ session.commit()
+ except Exception:
raise
- return {"state": VM_STATE_NAMES.get(state, 'None')}
def generate_password(self):
try:
@@ -140,8 +158,18 @@ def generate_password(self):
return {"password": password}
except Exception:
raise
+
+ def remove_password(self):
+ try:
+ with Session(engine) as session:
+ statement = update(VmORM).where(VmORM.id == self.id).values(password=None)
+ session.exec(statement)
+ session.commit()
-
+ except Exception:
+ raise
+
+
class VmService:
def get_vm_list():
@@ -169,7 +197,19 @@ def start_vm(vm_id: str):
def stop_vm(vm_id: str):
vm = Vm.get(vm_id)
return vm.stop()
-
+
+ def remove_vm(vm_id: str):
+ vm = Vm.get(vm_id)
+ vm.remove()
+
def set_vm_password(vm_id: str):
vm = Vm.get(vm_id)
return vm.generate_password()
+
+ def remove_vm_password(vm_id: str):
+ vm = Vm.get(vm_id)
+ vm.remove_password()
+
+
+
+
diff --git a/backend/app/telemetry/monitor.py b/backend/app/telemetry/monitor.py
new file mode 100644
index 0000000..353493e
--- /dev/null
+++ b/backend/app/telemetry/monitor.py
@@ -0,0 +1,81 @@
+from threading import Thread
+from collections import Counter
+from time import sleep
+from libvirt import VIR_DOMAIN_STATS_CPU_TOTAL, VIR_DOMAIN_STATS_INTERFACE, VIR_CONNECT_GET_ALL_DOMAINS_STATS_RUNNING
+import psutil
+
+class CPUStateSnapshot:
+ def __init__(self, cpu_state_snapshot):
+ self.cpu_state_snapshot = cpu_state_snapshot
+ if hasattr(cpu_state_snapshot, "_asdict"):
+ data = cpu_state_snapshot._asdict().values()
+ elif hasattr(cpu_state_snapshot, "values"):
+ data = cpu_state_snapshot.values()
+ else:
+ data = cpu_state_snapshot
+ self.total_time = sum(data)
+ self.active_time = self.total_time - self.cpu_state_snapshot.idle
+
+class SystemMonitor:
+ def __init__(self, interval, get_connection):
+ self.get_connection = get_connection
+ self.interval = interval
+ self.cpu = {
+ "percent_used_total": 0,
+ "percent_used_per_cpu": [],
+ "percent_used_per_vm": [],
+ "percent_used_total_vms": 0,
+ "cpu_count": 0
+ }
+ self.cpu_counter = {}
+ self._thread = Thread(target=self._update_loop, daemon=True).start()
+
+ def _get_running_vms(self):
+ conn = self.get_connection()
+ return conn.getAllDomainStats(stats = VIR_DOMAIN_STATS_CPU_TOTAL | VIR_DOMAIN_STATS_INTERFACE, flags = VIR_CONNECT_GET_ALL_DOMAINS_STATS_RUNNING)
+
+
+ def _get_cpu_counters(self):
+ per_cpus_counter = [CPUStateSnapshot(cpu_times) for cpu_times in psutil.cpu_times(percpu=True)]
+ cpu_total_counter = Counter()
+ for cpu in per_cpus_counter:
+ cpu_total_counter.update(cpu.cpu_state_snapshot._asdict())
+
+ stats = self._get_running_vms()
+ per_vm_counter = ({"id": s[0].name(), "cpu_time": s[1]["cpu.time"] / 100000000} for s in stats)
+ return {
+ "per_cpus_counter": per_cpus_counter,
+ "cpu_total_counter": CPUStateSnapshot(psutil._pslinux.scputimes(**cpu_total_counter)),
+ "per_vm_counter": per_vm_counter
+ }
+
+ def _get_cpu_usage_percent(self, a, b, x, y):
+ return 1 * (((a - b) / (x - y)) * 100)
+
+ def _update_loop(self):
+ self.cpu["cpu_count"] = psutil.cpu_count()
+ while True:
+ cpu_usage_t1 = self._get_cpu_counters()
+ sleep(3)
+ cpu_usage_t2 = self._get_cpu_counters()
+
+ self.cpu["percent_used_total"] = self._get_cpu_usage_percent(cpu_usage_t2["cpu_total_counter"].active_time, cpu_usage_t1["cpu_total_counter"].active_time,
+ cpu_usage_t2["cpu_total_counter"].total_time, cpu_usage_t1["cpu_total_counter"].total_time)
+
+ self.cpu["percent_used_per_cpu"] = []
+ for i in range(len(cpu_usage_t1["per_cpus_counter"])):
+ self.cpu["percent_used_per_cpu"].append(round(self._get_cpu_usage_percent(cpu_usage_t2["per_cpus_counter"][i].active_time, cpu_usage_t1["per_cpus_counter"][i].active_time,
+ cpu_usage_t2["per_cpus_counter"][i].total_time, cpu_usage_t1["per_cpus_counter"][i].total_time), 2))
+
+ self.cpu["percent_used_per_vm"] = []
+ for vm_counter_t1 in cpu_usage_t1["per_vm_counter"]:
+ vm_counter_t2 = next((vm for vm in cpu_usage_t2["per_vm_counter"] if vm["id"] == vm_counter_t1["id"]), None)
+ if vm_counter_t2 is None:
+ continue
+ self.cpu["percent_used_per_vm"].append({
+ "id": vm_counter_t1["id"],
+ "cpu_percentage": round(self._get_cpu_usage_percent(vm_counter_t2["cpu_time"], vm_counter_t1["cpu_time"],
+ cpu_usage_t2["cpu_total_counter"].total_time, cpu_usage_t1["cpu_total_counter"].total_time), 2)
+ })
+
+ self.cpu["percent_used_total_vms"] = sum((vm["cpu_percentage"] for vm in self.cpu["percent_used_per_vm"]))
diff --git a/backend/requirements.txt b/backend/requirements.txt
index 79e511e..99d27bc 100644
--- a/backend/requirements.txt
+++ b/backend/requirements.txt
@@ -1,5 +1,4 @@
fastapi[standard]
libvirt-python
lxml
-sqlmodel
-psycopg2
+psutil
diff --git a/test/create-distribox-image/clear_vm.sh b/test/create-distribox-image/clear_vm.sh
deleted file mode 100644
index eb01491..0000000
--- a/test/create-distribox-image/clear_vm.sh
+++ /dev/null
@@ -1,5 +0,0 @@
-##on host machine
-virt-sysprep -a /var/lib/distribox/images/os.qcow2 --operations bash-history,logfiles,tmp-files,crash-data,utmp
-virt-sparsify --compress /var/lib/distribox/images/os.qcow2 /var/lib/distribox/images/distribox-ubuntu.qcow2
-rm -f /var/lib/distribox/images/os.qcow2
-rm -f /var/lib/distribox/images/seed.iso
\ No newline at end of file
diff --git a/test/create-distribox-image/create-almalinux.sh b/test/create-distribox-image/create-almalinux.sh
new file mode 100644
index 0000000..92d4d28
--- /dev/null
+++ b/test/create-distribox-image/create-almalinux.sh
@@ -0,0 +1,24 @@
+#!/bin/bash
+CLOUD_IMG_URL=https://repo.almalinux.org/almalinux/9/cloud/x86_64/images/AlmaLinux-9-GenericCloud-9.7-20251118.x86_64.qcow2
+DISTRIBOX_IMG_PATH="/var/lib/distribox/images/"
+CLOUD_IMG_SOURCE="${CLOUD_IMG_URL##*/}"
+
+wget -O "/tmp/${CLOUD_IMG_SOURCE}" $CLOUD_IMG_URL
+
+set -e
+
+sudo cp "/tmp/$CLOUD_IMG_SOURCE" /tmp/resized_image.qcow2
+
+sudo virt-customize -a /tmp/resized_image.qcow2 \
+ --network \
+ --update \
+ --install vim,qemu-guest-agent,cloud-init \
+ --run-command 'grub2-mkconfig -o /boot/grub2/grub.cfg' \
+ --run-command 'grub2-install /dev/sda'
+
+sudo virt-sysprep -a /tmp/resized_image.qcow2 --operations machine-id,ssh-hostkeys
+
+sudo virt-sparsify --compress /tmp/resized_image.qcow2 \
+ "/var/lib/distribox/images/distribox-almalinux.qcow2"
+
+sudo rm -f /tmp/resized_image.qcow2
\ No newline at end of file
diff --git a/test/create-distribox-image/create-alpinelinux.sh b/test/create-distribox-image/create-alpinelinux.sh
new file mode 100644
index 0000000..827b360
--- /dev/null
+++ b/test/create-distribox-image/create-alpinelinux.sh
@@ -0,0 +1,29 @@
+#!/bin/bash
+CLOUD_IMG_URL=https://dl-cdn.alpinelinux.org/alpine/v3.21/releases/cloud/generic_alpine-3.21.5-x86_64-uefi-cloudinit-r0.qcow2
+DISTRIBOX_IMG_PATH="/var/lib/distribox/images/"
+CLOUD_IMG_SOURCE="${CLOUD_IMG_URL##*/}"
+
+wget -O "/tmp/${CLOUD_IMG_SOURCE}" $CLOUD_IMG_URL
+
+set -e
+sudo qemu-img create -f qcow2 /tmp/resized_image.qcow2 9G
+sudo virt-resize --expand /dev/sda2 \
+ "/tmp/$CLOUD_IMG_SOURCE" \
+ /tmp/resized_image.qcow2
+
+sudo virt-customize -a /tmp/resized_image.qcow2 \
+ --network \
+ --run-command 'apk update' \
+ --run-command 'apk add vim qemu-guest-agent cloud-init gettext' \
+ \
+ --run-command 'apk add linux-lts linux-headers' \
+ --run-command 'mkinitfs' \
+ --run-command 'grub-mkconfig -o /boot/grub/grub.cfg' \
+ --run-command 'grub-install /dev/sda'
+
+sudo virt-sysprep -a /tmp/resized_image.qcow2 --operations machine-id,ssh-hostkeys
+
+sudo virt-sparsify --compress /tmp/resized_image.qcow2 \
+ "/var/lib/distribox/images/distribox-alpinelinux.qcow2"
+
+sudo rm -f /tmp/resized_image.qcow2
\ No newline at end of file
diff --git a/test/create-distribox-image/create-archlinux.sh b/test/create-distribox-image/create-archlinux.sh
new file mode 100644
index 0000000..488af4c
--- /dev/null
+++ b/test/create-distribox-image/create-archlinux.sh
@@ -0,0 +1,29 @@
+#!/bin/bash
+CLOUD_IMG_URL=https://fastly.mirror.pkgbuild.com/images/v20250901.414475/Arch-Linux-x86_64-cloudimg.qcow2
+DISTRIBOX_IMG_PATH="/var/lib/distribox/images/"
+CLOUD_IMG_SOURCE="${CLOUD_IMG_URL##*/}"
+
+wget -O "/tmp/${CLOUD_IMG_SOURCE}" $CLOUD_IMG_URL
+
+set -e
+sudo qemu-img create -f qcow2 /tmp/resized_image.qcow2 9G
+sudo virt-resize --expand /dev/sda3 \
+ "/tmp/$CLOUD_IMG_SOURCE" \
+ /tmp/resized_image.qcow2
+
+sudo virt-customize -a /tmp/resized_image.qcow2 \
+ --network \
+ --run-command 'pacman-key --init' \
+ --run-command 'pacman-key --populate archlinux' \
+ --run-command 'pacman -Syu --noconfirm' \
+ --run-command 'pacman -S --noconfirm vim qemu-guest-agent cloud-init grub linux intel-ucode' \
+ # --run-command 'mkinitcpio -P' \
+ # --run-command 'grub-mkconfig -o /boot/grub/grub.cfg' \
+ # --run-command 'grub-install --target=i386-pc /dev/sda'
+
+sudo virt-sysprep -a /tmp/resized_image.qcow2 --operations machine-id,ssh-hostkeys
+
+sudo virt-sparsify --compress /tmp/resized_image.qcow2 \
+ "/var/lib/distribox/images/distribox-archlinux.qcow2"
+
+sudo rm -f /tmp/resized_image.qcow2
\ No newline at end of file
diff --git a/test/create-distribox-image/create-centos.sh b/test/create-distribox-image/create-centos.sh
new file mode 100644
index 0000000..e19b012
--- /dev/null
+++ b/test/create-distribox-image/create-centos.sh
@@ -0,0 +1,25 @@
+#!/bin/bash
+CLOUD_IMG_URL=https://cloud.centos.org/centos/10-stream/x86_64/images/CentOS-Stream-GenericCloud-10-20241118.0.x86_64.qcow2
+DISTRIBOX_IMG_PATH="/var/lib/distribox/images/"
+CLOUD_IMG_SOURCE="${CLOUD_IMG_URL##*/}"
+
+wget -O "/tmp/${CLOUD_IMG_SOURCE}" $CLOUD_IMG_URL
+
+set -e
+
+sudo cp "/tmp/$CLOUD_IMG_SOURCE" /tmp/resized_image.qcow2
+
+sudo virt-customize -a /tmp/resized_image.qcow2 \
+ --network \
+ --update \
+ --install vim,qemu-guest-agent,cloud-init \
+ --run-command 'grub2-mkconfig -o /boot/grub2/grub.cfg' \
+ --run-command 'touch /.autorelabel' \
+ # --run-command 'grub2-install /dev/sda'
+
+sudo virt-sysprep -a /tmp/resized_image.qcow2 --operations machine-id,ssh-hostkeys
+
+sudo virt-sparsify --compress /tmp/resized_image.qcow2 \
+ "/var/lib/distribox/images/distribox-centos.qcow2"
+
+sudo rm -f /tmp/resized_image.qcow2
\ No newline at end of file
diff --git a/test/create-distribox-image/create-debian.sh b/test/create-distribox-image/create-debian.sh
new file mode 100644
index 0000000..7c5773b
--- /dev/null
+++ b/test/create-distribox-image/create-debian.sh
@@ -0,0 +1,28 @@
+#!/bin/bash
+CLOUD_IMG_URL=https://cdimage.debian.org/images/cloud/bookworm/20251112-2294/debian-12-generic-amd64-20251112-2294.qcow2
+DISTRIBOX_IMG_PATH="/var/lib/distribox/images/"
+CLOUD_IMG_SOURCE="${CLOUD_IMG_URL##*/}"
+
+wget -O "/tmp/${CLOUD_IMG_SOURCE}" $CLOUD_IMG_URL
+
+set -e
+sudo qemu-img create -f qcow2 /tmp/resized_image.qcow2 9G
+sudo virt-resize --expand /dev/sda1 \
+ "/tmp/$CLOUD_IMG_SOURCE" \
+ /tmp/resized_image.qcow2
+
+sudo virt-customize -a /tmp/resized_image.qcow2 \
+ --network \
+ --update \
+ --install vim,qemu-guest-agent,cloud-init \
+ --run-command 'update-initramfs -u' \
+ --run-command 'update-grub' \
+ --run-command 'grub-install /dev/sda'
+
+sudo virt-sysprep -a /tmp/resized_image.qcow2 --operations machine-id,ssh-hostkeys
+
+sudo virt-sparsify --compress /tmp/resized_image.qcow2 \
+ "/var/lib/distribox/images/distribox-debian.qcow2"
+
+sudo rm -f /tmp/resized_image.qcow2
+
diff --git a/test/create-distribox-image/create-distribox-image.sh b/test/create-distribox-image/create-distribox-image.sh
deleted file mode 100644
index 5be3845..0000000
--- a/test/create-distribox-image/create-distribox-image.sh
+++ /dev/null
@@ -1,27 +0,0 @@
-VM_NAME=ubuntu-cloud-vm
-CLOUD_IMG_URL=https://cloud-images.ubuntu.com/jammy/current/jammy-server-cloudimg-amd64.img
-CLOUD_IMG_QCOW="os.qcow2"
-LIBVIRT_IMG_PATH="/tmp/"
-DISTRIBOX_IMG_PATH=/var/lib/distribox/images/
-
-virsh destroy $VM_NAME
-virsh undefine $VM_NAME --remove-all-storage
-rm -f "${DISTRIBOX_IMG_PATH}${CLOUD_IMG_QCOW}"
-rm -f "${DISTRIBOX_IMG_PATH}seed.iso"
-
-# download cloud image
-wget -nc -P $LIBVIRT_IMG_PATH $CLOUD_IMG_URL
-
-cp "${LIBVIRT_IMG_PATH}${CLOUD_IMG_URL##*/}" "${DISTRIBOX_IMG_PATH}${CLOUD_IMG_QCOW}"
-
-qemu-img resize "${DISTRIBOX_IMG_PATH}${CLOUD_IMG_QCOW}" +5G
-
-
-#generate configuration iso for cloud image, see what's in seed-config/
-genisoimage -output "${DISTRIBOX_IMG_PATH}seed.iso" -volid cidata -joliet -rock seed-config/
-
-#defines and starts vm using vm.xml
-virsh define vm.xml
-virsh start $VM_NAME
-
-##run clear_vm.sh after the vm creation. It will shutdown when init is finished
\ No newline at end of file
diff --git a/test/create-distribox-image/create-fedora.sh b/test/create-distribox-image/create-fedora.sh
new file mode 100644
index 0000000..4241cf0
--- /dev/null
+++ b/test/create-distribox-image/create-fedora.sh
@@ -0,0 +1,24 @@
+!/bin/bash
+
+CLOUD_IMG_URL=https://download.fedoraproject.org/pub/fedora/linux/releases/43/Cloud/x86_64/images/Fedora-Cloud-Base-Generic-43-1.6.x86_64.qcow2
+DISTRIBOX_IMG_PATH="/var/lib/distribox/images/"
+CLOUD_IMG_SOURCE="${CLOUD_IMG_URL##*/}"
+
+wget -O "/tmp/${CLOUD_IMG_SOURCE}" $CLOUD_IMG_URL
+
+set -e
+sudo cp "/tmp/$CLOUD_IMG_SOURCE" /tmp/resized_image.qcow2
+
+
+sudo virt-customize -a /tmp/resized_image.qcow2 \
+ --network \
+ --update \
+ --install vim,qemu-guest-agent,cloud-init \
+ --run-command 'grub2-mkconfig -o /boot/grub2/grub.cfg' \
+ --run-command 'grub2-install /dev/sda'
+
+sudo virt-sysprep -a /tmp/resized_image.qcow2 --operations machine-id,ssh-hostkeys
+sudo virt-sparsify --compress /tmp/resized_image.qcow2 \
+ "/var/lib/distribox/images/distribox-fedora.qcow2"
+
+sudo rm -f /tmp/resized_image.qcow2
diff --git a/test/create-distribox-image/create-ubuntu.sh b/test/create-distribox-image/create-ubuntu.sh
new file mode 100644
index 0000000..b61da95
--- /dev/null
+++ b/test/create-distribox-image/create-ubuntu.sh
@@ -0,0 +1,27 @@
+#!/bin/bash
+CLOUD_IMG_URL=https://cloud-images.ubuntu.com/jammy/current/jammy-server-cloudimg-amd64.img
+DISTRIBOX_IMG_PATH="/var/lib/distribox/images/"
+CLOUD_IMG_SOURCE="${CLOUD_IMG_URL##*/}"
+
+wget -O "/tmp/${CLOUD_IMG_SOURCE}" $CLOUD_IMG_URL
+
+set -e
+sudo qemu-img create -f qcow2 /tmp/resized_image.qcow2 9G
+sudo virt-resize --expand /dev/sda1 \
+ "/tmp/$CLOUD_IMG_SOURCE" \
+ /tmp/resized_image.qcow2
+
+sudo virt-customize -a /tmp/resized_image.qcow2 \
+ --network \
+ --update \
+ --install vim,qemu-guest-agent,cloud-init,xubuntu-desktop \
+ --run-command 'update-initramfs -u' \
+ --run-command 'update-grub' \
+ --run-command 'grub-install /dev/sda'
+
+sudo virt-sysprep -a /tmp/resized_image.qcow2 --operations machine-id,ssh-hostkeys
+
+sudo virt-sparsify --compress /tmp/resized_image.qcow2 \
+ "/var/lib/distribox/images/distribox-ubuntu.qcow2"
+
+sudo rm -f /tmp/resized_image.qcow2
\ No newline at end of file
diff --git a/test/create-distribox-image/seed-config/user-data b/test/create-distribox-image/seed-config/user-data
index 3b325b3..a53a085 100644
--- a/test/create-distribox-image/seed-config/user-data
+++ b/test/create-distribox-image/seed-config/user-data
@@ -20,20 +20,8 @@ ssh_pwauth: true
package_update: true
package_upgrade: true
-packages:
- - qemu-guest-agent
- - xubuntu-desktop
- - vim
-package_reboot_if_required: false
-
-runcmd:
- - apt clean
- - apt autoremove -y
- - rm -rf /var/log/*
- - rm -rf /tmp/*
- - rm -rf /var/cache/apt/archives/*
- - rm -rf /var/lib/systemd/coredump/*
- - dd if=/dev/zero of=/EMPTY bs=1M || true
- - rm -f /EMPTY
- - sync
- - shutdown -h now
+# packages:
+# - qemu-guest-agent
+# - xubuntu-desktop
+# - vim
+# package_reboot_if_required: false
diff --git a/test/create-distribox-image/vm.xml b/test/create-distribox-image/vm.xml
deleted file mode 100644
index 4b053b3..0000000
--- a/test/create-distribox-image/vm.xml
+++ /dev/null
@@ -1,60 +0,0 @@
-
- ubuntu-cloud-vm
- 2048
- 2
-
-
- hvm
-
-
-
-
-
-
-
-
-
-
-
-
-
- destroy
- restart
- destroy
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/test/libvirt-install.sh b/test/libvirt-install.sh
index da58ed3..431ffef 100644
--- a/test/libvirt-install.sh
+++ b/test/libvirt-install.sh
@@ -1,15 +1,20 @@
-sudo groupadd -f distribox
-sudo usermod -aG distribox "$SUDO_USER"
sudo apt update
echo "Installing libvirt dependencies..."
sudo apt install -y qemu-kvm libvirt-daemon-system genisoimage libvirt-clients bridge-utils virtinst pkg-config libvirt-dev python3-dev
echo "Enabling libvirt daemon (libvirtd)..."
sudo systemctl enable --now libvirtd
+
+
sudo mkdir /var/lib/distribox/
sudo mkdir /var/lib/distribox/images
sudo mkdir /var/lib/distribox/vms
+sudo groupadd -f distribox
+sudo usermod -aG distribox,kvm "$SUDO_USER"
+
sudo chown -R root:distribox /var/lib/distribox
+sudo chown -R root:distribox /var/lib/distribox/images
+sudo chown -R libvirt-qemu:kvm /var/lib/distribox/vms
sudo chmod 2775 /var/lib/distribox
sudo chmod 2775 /var/lib/distribox/images