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
        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)
82 83
        pacman_config = pjoin(self.configdir, "pacman.conf")

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

Xuetian Weng's avatar
Xuetian Weng committed
98
    def prepare_efi_img(self):
99
        prebootloader_path = "/usr/share/efitools/efi"
100
        assert os.path.isdir(prebootloader_path), "efitools must be installed"
AlmAck's avatar
AlmAck committed
101 102
        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
103 104 105 106 107 108 109 110 111 112 113 114 115 116

        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
117
            shutil.copy(pjoin(self.outputdir, "chakra/boot/vmlinuz-linux"), pjoin(efi_root, "EFI/chakraiso/"))
Xuetian Weng's avatar
Xuetian Weng committed
118 119
            os.makedirs(pjoin(efi_root, "EFI/boot"))

120
            shutil.copy(pjoin(prebootloader_path, "PreLoader.efi"), pjoin(efi_root, "EFI/boot/"))
121
            shutil.copy(pjoin(prebootloader_path, "HashTool.efi"), pjoin(efi_root, "EFI/boot/"))
122 123 124 125 126 127 128 129 130
            shutil.copy(pjoin(systemd_path, "systemd-bootx64.efi"), pjoin(efi_root, "EFI/boot/BOOTX64.efi"))
            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)
Xuetian Weng's avatar
Xuetian Weng committed
131 132 133 134
            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)
135

136 137 138 139 140
    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
141 142
        if os.path.isdir(self.iso_root):
            shutil.rmtree(self.iso_root)
143 144 145 146 147 148 149 150 151 152 153
        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
154
            "isohdpfx.bin" , 
155 156 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
            "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
184
        shutil.copy(pjoin(self.outputdir, "chakra/boot/vmlinuz-linux"), pjoin(image_dir, "vmlinuz-linux"))
185 186 187 188 189 190 191 192 193
        # 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)

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