Start adding some VM automation.

This commit is contained in:
James Grogan 2024-03-29 15:26:29 +00:00
parent 90b25e600b
commit c7116d32d0
10 changed files with 171 additions and 7 deletions

View file

@ -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 /

36
doc/vm_notes.md Normal file
View file

@ -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

View file

View file

@ -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()

View file

@ -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__)
@ -25,6 +26,21 @@ class AptInterface:
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:

View file

View file

@ -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")

View file

@ -0,0 +1,6 @@
import sys
from .distro import Distro
def is_linux():
return sys.platform == "linux" or sys.platform == "linux2"

View file

@ -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)

View file

@ -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}")
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)