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