From c7116d32d0dfb73106fed618ab9fe16c21f47463 Mon Sep 17 00:00:00 2001 From: jmsgrogan Date: Fri, 29 Mar 2024 15:26:29 +0000 Subject: [PATCH] Start adding some VM automation. --- Containerfile | 2 + doc/vm_notes.md | 36 ++++++++++ src/machine_admin/boot_image/__init__.py | 0 src/machine_admin/boot_image/deb_preseed.py | 74 +++++++++++++++++++++ src/machine_admin/package_manager.py | 20 +++++- src/machine_admin/platform/__init__.py | 0 src/machine_admin/platform/distro.py | 11 +++ src/machine_admin/platform/linux.py | 6 ++ src/machine_admin/user.py | 2 +- src/machine_admin/util.py | 27 ++++++-- 10 files changed, 171 insertions(+), 7 deletions(-) create mode 100644 doc/vm_notes.md create mode 100644 src/machine_admin/boot_image/__init__.py create mode 100644 src/machine_admin/boot_image/deb_preseed.py create mode 100644 src/machine_admin/platform/__init__.py create mode 100644 src/machine_admin/platform/distro.py create mode 100644 src/machine_admin/platform/linux.py diff --git a/Containerfile b/Containerfile index 8e96c64..5b86fa3 100644 --- a/Containerfile +++ b/Containerfile @@ -1,4 +1,6 @@ FROM debian +RUN apt-get update; apt-get install -y python3 + COPY src/machine_admin /machine_admin COPY src/machine_setup.py / diff --git a/doc/vm_notes.md b/doc/vm_notes.md new file mode 100644 index 0000000..f0848e1 --- /dev/null +++ b/doc/vm_notes.md @@ -0,0 +1,36 @@ +# Prepare Installation Images + +Want fully automated installation. + +## Deb + +Get `netinst` ISO: https://cdimage.debian.org/debian-cd/12.5.0/amd64/iso-cd/ + +Deb Preseeding: https://wiki.debian.org/DebianInstaller/Preseed + +ISO modification for preseeding: https://wiki.debian.org/DebianInstaller/Preseed/EditIso + + +# Set up KVM + +```sh +``` + +# Install Qemu + +```sh +sudo apt install qemu-utils qemu-system-x86 qemu-system-gui libvirt-daemon-system virtinst +``` + +Add user to libvirt group + +```sh +su -l +adduser $MY_USER libvirt +``` + +# References + +KVM and Debian: https://wiki.debian.org/KVM +Qemu and Debian: https://wiki.debian.org/QEMU +Qemu and libvrt: https://rabexc.org/posts/how-to-get-started-with-libvirt-on \ No newline at end of file diff --git a/src/machine_admin/boot_image/__init__.py b/src/machine_admin/boot_image/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/machine_admin/boot_image/deb_preseed.py b/src/machine_admin/boot_image/deb_preseed.py new file mode 100644 index 0000000..b1161d9 --- /dev/null +++ b/src/machine_admin/boot_image/deb_preseed.py @@ -0,0 +1,74 @@ +from pathlib import Path +import logging + +from machine_admin.package_manager import SystemPackage +from machine_admin.util import run_op + +logger = logging.getLogger(__name__) + +class DebPreseeder: + + def __init__(self, iso_path: Path, preseed_path: Path): + self.iso_path = iso_path + self.preseed_path = preseed_path + + self.udevil = SystemPackage("udevil") + self.gzip = SystemPackage("gzip") + self.mount_point = None + + def mount_iso(self): + self.udevil.check_available() + op = f"udevil mount {self.iso_path}" + logger.info(f"Mounting iso | {op}") + output = run_op(op, capture_output=True) + self.mount_point = Path(output.split(" ")[3]) + + def add_preseed_file(self): + + # Copy ISO content to writeable location + op = f"cp -rT {self.mount_point} {self.workdir}/isofiles/" + run_op(op) + + # Make path writeable + install_arch_path = f"{self.workdir}/isofiles/install.amd" + run_op(f"chmod +w -R {install_arch_path}") + + # Extract initrd + self.gzip.check_available() + initrd_path = f"{install_arch_path}/initrd" + run_op(f"gunzip {initrd_path}.gz") + + # Add the preseed file + op = f"echo {self.preseed_path} | cpio -H newc -o -A -F {initrd_path}" + run_op(op) + + # Recompress the initrd + run_op(f"gzip {initrd_path}") + + # Restore path permissions + run_op(f"chmod -w -R {install_arch_path}") + + def regenerate_checksum(self): + checksum_path = f"{self.workdir}/isofiles/md5sum.txt" + run_op(f"chmod +w {checksum_path}") + + def regenerate_iso(self, workdir: Path): + + self.mount_iso() + + self.add_preseed_file() + + self.regenerate_checksum() + + + + + + + + + + + + + diff --git a/src/machine_admin/package_manager.py b/src/machine_admin/package_manager.py index a9a1ec3..288518a 100644 --- a/src/machine_admin/package_manager.py +++ b/src/machine_admin/package_manager.py @@ -1,6 +1,7 @@ import logging -from .util import run_op +from .platform.distro import Distro +from .util import run_op, has_program logger = logging.getLogger(__name__) @@ -24,7 +25,22 @@ class AptInterface: op = f"apt-get install -y {packages_str}" logger.info(f"Installing packages: {op}") run_op(op) - + +class SystemPackage: + + def __init__(self, name): + self.name = name + self.package_alt_names = {} + + def check_available(self): + if has_program(self.name): + return + + msg = f"Program {self.name} not found" + if Distro.has_apt(): + msg = f"Program not found. Install with: sudo apt-get install {self.name}" + raise RuntimeError(msg) + class PackageManager: diff --git a/src/machine_admin/platform/__init__.py b/src/machine_admin/platform/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/machine_admin/platform/distro.py b/src/machine_admin/platform/distro.py new file mode 100644 index 0000000..63482bf --- /dev/null +++ b/src/machine_admin/platform/distro.py @@ -0,0 +1,11 @@ +from machine_admin.util import has_program + +class Distro: + + def __init__(self, name): + self.name = name + self.version = "" + + @staticmethod + def has_apt(self): + return has_program("apt-get") \ No newline at end of file diff --git a/src/machine_admin/platform/linux.py b/src/machine_admin/platform/linux.py new file mode 100644 index 0000000..0b3875c --- /dev/null +++ b/src/machine_admin/platform/linux.py @@ -0,0 +1,6 @@ +import sys + +from .distro import Distro + +def is_linux(): + return sys.platform == "linux" or sys.platform == "linux2" \ No newline at end of file diff --git a/src/machine_admin/user.py b/src/machine_admin/user.py index 3570f35..f51bd90 100644 --- a/src/machine_admin/user.py +++ b/src/machine_admin/user.py @@ -19,7 +19,7 @@ class UserManager: self.add_user_to_sudo(user) def add_user(self, user: User): - op = f"adduser {user.name}" + op = f'adduser {user.name} --disabled-password --comment ""' logger.info(f"Adding user: {op}") run_op(op) diff --git a/src/machine_admin/util.py b/src/machine_admin/util.py index ee3d938..5d05154 100644 --- a/src/machine_admin/util.py +++ b/src/machine_admin/util.py @@ -8,8 +8,27 @@ _DRY_RUN = False def set_is_dry_run(is_dry_run: bool): _DRY_RUN = is_dry_run -def run_op(op: str): - if _DRY_RUN: - return subprocess.run(op, shell=True) +def run_op(op: str, capture_output:bool = False, + cwd:str = None): + if not _DRY_RUN: + if capture_output: + ret = subprocess.run(op, + shell=True, + capture_output = True, + text = True, + cwd=cwd) + ret.check_returncode() + return ret.stdout + else: + ret = subprocess.run(op, shell=True, cwd=cwd) + ret.check_returncode() else: - logger.info(f"Dry Run | {op}") \ No newline at end of file + logger.info(f"Dry Run | {op}") + +def has_program(program_name: str): + op = f"which {program_name}" + ret = subprocess.run(op, + shell=True, + capture_output = True, + text = True) + return len(ret.stdout) \ No newline at end of file