iso.py 10.1 KB
Newer Older
1 2 3 4
#!/usr/bin/env python3
"""Creates the actual ISO"""

import os
5
from os.path import join as pjoin
Fabian Kosmale's avatar
Fabian Kosmale committed
6 7
import glob
import re
8 9 10 11 12 13
import subprocess
import datetime
import shutil

inform = print  # TODO

14
class IsoBuilder:
Fabian Kosmale's avatar
Fabian Kosmale committed
15

16 17
    def __init__(self, outputdir, datadir, configdir):
        self.iso_root = pjoin(outputdir, "iso_root")
Fabian Kosmale's avatar
Fabian Kosmale committed
18

19 20 21 22 23 24
        self.outputdir = outputdir
        self.datadir = datadir
        self.configdir = configdir
        self.squashfs_files = [] # list of squashfs image files
        self.label='CHAKRA_{}'.format(datetime.datetime.now().strftime("%Y-%m-%d"))
        self.overlay_packages_dir = pjoin(outputdir, "overlay-packages")
25

26 27 28 29 30 31 32
    def make_suqash(self, directory="chakra", name=None):
        """Creates a squashfs image out of :directory:.
           :directory: the directory to be compressed
           :name: the name of the resulting squashfs image
        """
        assert os.path.isdir(directory), "mksquash only works on directories!"
        if name is None:
Fabian Kosmale's avatar
Fabian Kosmale committed
33
          name = "{}.sqfs".format(directory)
Fabian Kosmale's avatar
Fabian Kosmale committed
34
        sqimg = pjoin(self.outputdir, name)
35 36 37 38 39 40 41 42 43 44 45 46
        inform("Generating SquashFS image for {}".format(directory))
        # TODO: check if existing image is out of date
        inform("Creating SquashFS image. This may take some time...")
        command = "mksquashfs {} {} -noappend -comp xz -Xbcj x86".format(
            directory,
            sqimg
        ).split(" ")
        # only create sqimg if it doesn't already exist to save time
        # TODO: create a hash of the directory to check if it became "dirty"
        if not os.path.isfile(sqimg):
          subprocess.check_call(command)
        self.squashfs_files.append(sqimg)
Fabian Kosmale's avatar
Fabian Kosmale committed
47 48


49 50 51 52 53 54 55 56 57 58 59
    def prepare_overlay(self, locale_archive=None):
        """
        Creates missing files in the overlay.
        If no locale_archive is given, a new one is created based on the locale.conf
        in config. Furthermore, all packages specified in overlay_packages will be
        downloaded and put into overlay-packages
        :locale_archive: path to an existig locale_archive
        :datadir: files are copied for there
        :outputdir: the overlay files are put there
        """
        overlay_files = pjoin(self.datadir, "overlay-files")
Xuetian Weng's avatar
Xuetian Weng committed
60

61 62 63 64 65 66 67
        # prevent ldconfig.service to map all the libs at boot
        # https://bugzilla.redhat.com/show_bug.cgi?id=1195998
        with open(pjoin(overlay_files, "etc/.updated"),"a+") as f:
            f.write('File created by Chakra buildsystem. See systemd-update-done.service(8).')
        shutil.copy(pjoin(overlay_files, "etc/.updated"), pjoin(overlay_files, "var/.updated"))

        # copy overlay files to outputdir
Xuetian Weng's avatar
Xuetian Weng committed
68 69 70 71
        if os.path.isdir(pjoin(self.outputdir,"overlay-files")):
            inform("overlay-files exists, skip")
            return

AlmAck's avatar
AlmAck committed
72
        shutil.copytree(overlay_files, pjoin(self.outputdir,"overlay-files"), True)
73 74 75 76 77 78 79 80 81 82
        if locale_archive is None or not os.path.isfile(locale_archive):
            # create locale_archive
            pass
        else:
            # copy existing one to correct place
            shutil.copy(locale_archive, pjoin(overlay_files, "/usr/lib/locale/"))
        # download packages
        package_dir = os.path.join(self.overlay_packages_dir, "opt/chakra/pkgs")
        package_dir = os.path.abspath(package_dir)
        os.makedirs(package_dir, exist_ok=True)
83 84
        pacman_config = pjoin(self.configdir, "pacman.conf")

85
        command = "pacman -Sywdd --noconfirm --cachedir {} --config {} {}"
AlmAck's avatar
AlmAck committed
86
        # pacman -Sw doesn't check dependencies with installed system, but it will fetch required packages altogether
Xuetian Weng's avatar
Xuetian Weng committed
87
        with open(os.path.join(self.configdir, "overlay-packages.conf")) as packages:
88 89 90 91 92 93 94
           for line in packages:
             line = line.strip()
             if not line:
               continue
             subprocess.check_call(
                command.format(
                  package_dir,
95
                  pacman_config,
96 97
                  line
                ).split())
98

Xuetian Weng's avatar
Xuetian Weng committed
99
    def prepare_efi_img(self):
100 101
        prebootloader_path = "/usr/lib/efitools/efi"
        assert os.path.isdir(prebootloader_path), "efitools must be installed"
AlmAck's avatar
AlmAck committed
102 103
        systemd_path = "/usr/lib/systemd/boot/efi"
        assert os.path.isdir(systemd_path), "systemd >=220 must be installed"
Xuetian Weng's avatar
Xuetian Weng committed
104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122

        with open("log", "w") as log:
            os.makedirs(pjoin(self.iso_root, "EFI/chakraiso"))
            efi_image = pjoin(self.iso_root, "EFI/chakraiso/efiboot.img")
            subprocess.check_call(["truncate", "-s", "31M", efi_image], shell=False, stderr=log)
            subprocess.check_call(["mkfs.vfat", "-n", "CHAKRAISO_EFI", efi_image], shell=False, stderr=log)
            efi_root = pjoin(self.outputdir, "efi_root")
            if not os.path.isdir(efi_root):
                os.makedirs(efi_root)
            subprocess.check_call(["mount", efi_image, efi_root], shell=False, stderr=log)
            os.makedirs(pjoin(efi_root, "EFI/chakraiso"))
            # copy the initramfs
            shutil.copy(pjoin(self.outputdir, "chakra/boot/chakraiso.img"), pjoin(efi_root, "EFI/chakraiso/chakraiso.img"))
            # copy the kernel
            shutil.copy(pjoin(self.outputdir, "chakra/boot/vmlinuz-linux"), pjoin(efi_root, "EFI/chakraiso/chakraiso"))
            os.makedirs(pjoin(efi_root, "EFI/boot"))

            shutil.copy(pjoin(prebootloader_path, "PreLoader.efi"), pjoin(efi_root, "EFI/boot/BOOTX64.efi"))
            shutil.copy(pjoin(prebootloader_path, "HashTool.efi"), pjoin(efi_root, "EFI/boot/"))
AlmAck's avatar
AlmAck committed
123
            shutil.copy(pjoin(systemd_path, "systemd-bootx64.efi"), pjoin(efi_root, "EFI/boot/loader.efi"))
Xuetian Weng's avatar
Xuetian Weng committed
124 125 126 127 128 129 130 131 132 133 134 135 136

            shutil.copytree(pjoin(self.datadir, "efi/loader"), pjoin(efi_root, "loader"))
            for f in ["chakraiso.conf", "chakraiso-nonfree.conf"]:
                with open(pjoin(self.datadir, "efi/loader/entries", f)) as infile:
                    lines = infile.readlines()
                    with open(pjoin(efi_root, "loader/entries", f), "w") as outfile:
                        for line in lines:
                            line = re.sub("%CHAKRAISO_LABEL%", self.label, line)
                            outfile.write(line)
            shutil.copy(pjoin(self.datadir, "efi/shellx64_v1.efi"), pjoin(efi_root, "EFI/"))
            shutil.copy(pjoin(self.datadir, "efi/shellx64_v2.efi"), pjoin(efi_root, "EFI/"))

            subprocess.check_call(["umount", efi_root], shell=False, stderr=log)
137

138 139 140 141 142
    def prepare_iso(self):
        self.make_suqash(pjoin(self.outputdir,"chakra"), "root-image.sqfs")
        self.make_suqash(pjoin(self.outputdir, "overlay-files"), "overlay.sqfs")
        self.make_suqash(self.overlay_packages_dir, "overlay-pkgs.sqfs")
        # create root directory for iso image
Xuetian Weng's avatar
Xuetian Weng committed
143 144
        if os.path.isdir(self.iso_root):
            shutil.rmtree(self.iso_root)
145 146 147 148 149 150 151 152 153 154 155
        os.makedirs(self.iso_root)
        # for more information see http://www.syslinux.org/wiki/index.php/ISOLINUX
        """Copies the required files"""
        # copy all isolinux files to iso_root/isolinux
        isolinux_dir = os.path.join(self.iso_root, "isolinux")
        # this will create isolinux_dir and copy everything from <datadir>/isolinux
        shutil.copytree(pjoin(self.datadir, "isolinux"), isolinux_dir)
        assert os.path.isdir("/usr/lib/syslinux"), "syslinux must be installed"
        syslinux_path = "/usr/lib/syslinux/bios/"
        syslinux_components = (
            "isolinux.bin" , 
Xuetian Weng's avatar
Xuetian Weng committed
156
            "isohdpfx.bin" , 
157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195
            "ldlinux.c32"  , 
            "libcom32.c32" , 
            "liblua.c32"   , 
            "libgpl.c32"   , 
            "libmenu.c32"  , 
            "libutil.c32"  , 
            "mboot.c32"    , 
            "whichsys.c32" , 
            "chain.c32"    , 
            "gfxboot.c32"  , 
            "hdt.c32"      , 
        )
        for component in syslinux_components:
            shutil.copy(pjoin(syslinux_path, component), isolinux_dir)
        isolinux_cfg = os.path.join(isolinux_dir, "isolinux.cfg")
        # adjust label
        with open(isolinux_cfg) as infile:
            lines = infile.readlines()
            with open(isolinux_cfg, "w") as outfile:
                for line in lines:
                    line = re.sub("%CHAKRAISO_LABEL%", self.label, line)
                    outfile.write(line)

        # create the directory where the image will reside
        image_dir = os.path.join(self.iso_root, "chakra/boot/x86_64/")
        os.makedirs(image_dir)
        # copy the initramfs
        shutil.copy(pjoin(self.outputdir, "chakra/boot/chakraiso.img"), pjoin(image_dir, "chakraiso.img"))
        # copy the kernel
        shutil.copy(pjoin(self.outputdir, "chakra/boot/vmlinuz-linux"), pjoin(image_dir, "chakraiso"))
        # copy the isomounts file
        shutil.copy(pjoin(self.configdir, "isomounts"), pjoin(self.iso_root, "chakra"))
        # TODO: add sanity check: # isomounts entries == len(self.squashfs_files)
        # copy the squashfs images
        squashfs_dir = pjoin(self.iso_root, "chakra", "x86_64")
        os.makedirs(squashfs_dir)
        for file in self.squashfs_files:
          shutil.copy(file, squashfs_dir)

196
    def make_iso(self, publisher='Chakra_GNU/Linux_<https://chakralinux.org>',
197 198 199 200 201 202 203
                 application='Chakra_GNU/Linux_Live/Rescue_CD',
                 label=None,
                 imgname='output.iso'
                 ):
        if label is None:
            label = self.label
        inform("Building ISO")
Xuetian Weng's avatar
Xuetian Weng committed
204 205
        command = """xorriso -as mkisofs
    -iso-level 3
206
    -output {imgname}
Xuetian Weng's avatar
Xuetian Weng committed
207
    -appid {application}
208
    -publisher {publisher}
Xuetian Weng's avatar
Xuetian Weng committed
209 210 211
    -preparer prepared_by_chakra-iso
    -eltorito-boot isolinux/isolinux.bin
    -eltorito-catalog isolinux/boot.cat
212 213 214
    -no-emul-boot
    -boot-load-size 4
    -boot-info-table
Xuetian Weng's avatar
Xuetian Weng committed
215 216 217 218 219
    -isohybrid-mbr {isohdpfx_path}
    -eltorito-alt-boot
    -e EFI/chakraiso/efiboot.img
    -no-emul-boot
    -isohybrid-gpt-basdat
220 221 222 223 224 225 226 227
    -V {label}
    {iso_root}
    """.format(**{
                "publisher": publisher,
                "application": application,
                "label": label,
                "imgname": imgname,
                "iso_root": self.iso_root,
Xuetian Weng's avatar
Xuetian Weng committed
228
                "isohdpfx_path": os.path.join(self.iso_root, "isolinux/isohdpfx.bin")
229 230 231
            })
        with open("log", "w") as log:
            subprocess.check_call(command.strip().split(), shell=False, stderr=log)