Start adding some VM automation.
This commit is contained in:
parent
90b25e600b
commit
c7116d32d0
10 changed files with 171 additions and 7 deletions
|
@ -1,4 +1,6 @@
|
||||||
FROM debian
|
FROM debian
|
||||||
|
|
||||||
|
RUN apt-get update; apt-get install -y python3
|
||||||
|
|
||||||
COPY src/machine_admin /machine_admin
|
COPY src/machine_admin /machine_admin
|
||||||
COPY src/machine_setup.py /
|
COPY src/machine_setup.py /
|
||||||
|
|
36
doc/vm_notes.md
Normal file
36
doc/vm_notes.md
Normal 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
|
0
src/machine_admin/boot_image/__init__.py
Normal file
0
src/machine_admin/boot_image/__init__.py
Normal file
74
src/machine_admin/boot_image/deb_preseed.py
Normal file
74
src/machine_admin/boot_image/deb_preseed.py
Normal 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()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from .util import run_op
|
from .platform.distro import Distro
|
||||||
|
from .util import run_op, has_program
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -24,7 +25,22 @@ class AptInterface:
|
||||||
op = f"apt-get install -y {packages_str}"
|
op = f"apt-get install -y {packages_str}"
|
||||||
logger.info(f"Installing packages: {op}")
|
logger.info(f"Installing packages: {op}")
|
||||||
run_op(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:
|
class PackageManager:
|
||||||
|
|
||||||
|
|
0
src/machine_admin/platform/__init__.py
Normal file
0
src/machine_admin/platform/__init__.py
Normal file
11
src/machine_admin/platform/distro.py
Normal file
11
src/machine_admin/platform/distro.py
Normal 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")
|
6
src/machine_admin/platform/linux.py
Normal file
6
src/machine_admin/platform/linux.py
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from .distro import Distro
|
||||||
|
|
||||||
|
def is_linux():
|
||||||
|
return sys.platform == "linux" or sys.platform == "linux2"
|
|
@ -19,7 +19,7 @@ class UserManager:
|
||||||
self.add_user_to_sudo(user)
|
self.add_user_to_sudo(user)
|
||||||
|
|
||||||
def add_user(self, user: 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}")
|
logger.info(f"Adding user: {op}")
|
||||||
run_op(op)
|
run_op(op)
|
||||||
|
|
||||||
|
|
|
@ -8,8 +8,27 @@ _DRY_RUN = False
|
||||||
def set_is_dry_run(is_dry_run: bool):
|
def set_is_dry_run(is_dry_run: bool):
|
||||||
_DRY_RUN = is_dry_run
|
_DRY_RUN = is_dry_run
|
||||||
|
|
||||||
def run_op(op: str):
|
def run_op(op: str, capture_output:bool = False,
|
||||||
if _DRY_RUN:
|
cwd:str = None):
|
||||||
return subprocess.run(op, shell=True)
|
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:
|
else:
|
||||||
logger.info(f"Dry Run | {op}")
|
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)
|
Loading…
Reference in a new issue