We only need the filesystem with the correct fs-UUID to chroot into, there is no need to set up a whole partition table. Signed-off-by: Tom Gundersen <teg@jklm.no>
89 lines
3.3 KiB
Python
Executable file
89 lines
3.3 KiB
Python
Executable file
#!/usr/bin/python3
|
|
|
|
import contextlib
|
|
import json
|
|
import math
|
|
import os
|
|
import shutil
|
|
import subprocess
|
|
import sys
|
|
import tempfile
|
|
|
|
def tree_size(tree):
|
|
size = 0
|
|
for root, dirs, files in os.walk(tree):
|
|
for entry in files + dirs:
|
|
path = os.path.join(root, entry)
|
|
size += os.stat(path, follow_symlinks=False).st_size
|
|
return size
|
|
|
|
@contextlib.contextmanager
|
|
def mount(source, dest, *options):
|
|
os.makedirs(dest, 0o755, True)
|
|
subprocess.run(["mount", *options, source, dest], check=True)
|
|
try:
|
|
yield
|
|
finally:
|
|
subprocess.run(["umount", "-R", dest], check=True)
|
|
|
|
@contextlib.contextmanager
|
|
def mount_api(dest):
|
|
with mount("/dev", f"{dest}/dev", "-o", "rbind"), \
|
|
mount("/proc", f"{dest}/proc", "-o", "rbind"), \
|
|
mount("/sys", f"{dest}/sys", "-o", "rbind"), \
|
|
mount("none", f"{dest}/run", "-t", "tmpfs"):
|
|
yield
|
|
|
|
@contextlib.contextmanager
|
|
def loop_device(image):
|
|
r = subprocess.run(["losetup", "--show", "--find", image], stdout=subprocess.PIPE, encoding="utf-8", check=True)
|
|
loop = r.stdout.strip()
|
|
try:
|
|
yield loop
|
|
finally:
|
|
subprocess.run(["losetup", "-d", loop], check=True)
|
|
|
|
def main(tree, input_dir, options):
|
|
partition_table_id = options["partition_table_id"]
|
|
root_fs_uuid = options["root_fs_uuid"]
|
|
|
|
# Create the configuration file that determines how grub.cfg is generated.
|
|
os.makedirs(f"{tree}/etc/default", exist_ok=True)
|
|
with open(f"{tree}/etc/default/grub", "w") as default:
|
|
default.write("GRUB_TIMEOUT=0")
|
|
default.write("GRUB_DEFAULT=0")
|
|
default.write("GRUB_ENABLE_BLSCFG=true")
|
|
|
|
# Create a working directory on a tmpfs, maybe we should implicitly
|
|
# always do this.
|
|
with tempfile.TemporaryDirectory() as workdir:
|
|
image = f"{workdir}/image.raw"
|
|
mountpoint = f"{workdir}/mnt"
|
|
|
|
# Create an empty image file of the right size
|
|
size = int(math.ceil(tree_size(tree) * 1.2 / 512) * 512)
|
|
subprocess.run(["truncate", "--size", str(size), image], check=True)
|
|
|
|
# Mount the created image as a loopback device
|
|
with loop_device(image) as loop:
|
|
# Populate the first partition of the image with an ext4 fs and fill it with the contents of the
|
|
# tree we are operating on.
|
|
subprocess.run(["mkfs.ext4", "-d", tree, "-U", root_fs_uuid, loop], check=True)
|
|
|
|
# Mount the partition. The contents is now exactly the same as the input tree, with the only
|
|
# difference that when it inspects its own filesystem it will see what has been configured,
|
|
# rather than a tmpfs.
|
|
with mount(loop, mountpoint):
|
|
# Run the tool in the image we created.
|
|
with mount_api(mountpoint):
|
|
subprocess.run(["chroot", mountpoint, "grub2-mkconfig", "--output=/boot/grub2/grub.cfg"], check=True)
|
|
|
|
# Copy the entire contents of the image back on top of the input tree, capturing all the changes
|
|
# the tool performed.
|
|
shutil.rmtree(tree, ignore_errors=True)
|
|
subprocess.run(["cp", "-a", f"{mountpoint}/.", tree], check=True)
|
|
|
|
if __name__ == '__main__':
|
|
args = json.load(sys.stdin)
|
|
r = main(args["tree"], args["input_dir"], args["options"])
|
|
sys.exit(r)
|