aboutsummaryrefslogtreecommitdiff
blob: d24a542b93e7df704cf14725c24ff9bbf4a48ed1 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
#!/usr/bin/env python
#
#    ISOIt.py: this file is part of the GRS suite
#    Copyright (C) 2015  Anthony G. Basile
#
#    This program is free software: you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation, either version 3 of the License, or
#    (at your option) any later version.
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.
#
#    You should have received a copy of the GNU General Public License
#    along with this program.  If not, see <http://www.gnu.org/licenses/>.

import os
import shutil
from datetime import datetime
from grs.Constants import CONST
from grs.Execute import Execute
from grs.HashIt import HashIt

class ISOIt(HashIt):
    """ Create a bootable ISO of the system. """

    def __init__(
            self, name, libdir=CONST.LIBDIR, tmpdir=CONST.TMPDIR,
            portage_configroot=CONST.PORTAGE_CONFIGROOT, logfile=CONST.LOGFILE
    ):
        self.libdir = libdir
        self.tmpdir = tmpdir
        self.portage_configroot = portage_configroot
        self.logfile = logfile
        # Prepare a year, month and day for a ISO name timestamp.
        self.year = str(datetime.now().year).zfill(4)
        self.month = str(datetime.now().month).zfill(2)
        self.day = str(datetime.now().day).zfill(2)
        self.medium_name = '%s-%s%s%s.iso' % (name, self.year, self.month, self.day)
        self.digest_name = '%s.DIGESTS' % self.medium_name


    def initramfs(self, isoboot_dir):
        """ TODO """
        # Paths to where we'll build busybox and the initramfs.
        busybox_root = os.path.join(self.tmpdir, 'busybox')
        busybox_path = os.path.join(busybox_root, 'bin/busybox')
        makeprofile_path = os.path.join(busybox_root, 'etc/portage/make.profile')
        savedconfig_dir = os.path.join(busybox_root, 'etc/portage/savedconfig/sys-apps')
        savedconfig_path = os.path.join(savedconfig_dir, 'busybox')
        busybox_config = os.path.join(self.libdir, 'scripts/busybox-config')

        # Remove any old busybox build directory and prepare new one.
        shutil.rmtree(busybox_root, ignore_errors=True)
        os.makedirs(savedconfig_dir, mode=0o755, exist_ok=True)
        shutil.copy(busybox_config, savedconfig_path)

        # Emerge busybox.
        os.symlink(
            '/usr/portage/profiles/hardened/linux/amd64',
            makeprofile_path
        )
        cmd = 'emerge --nodeps -1q busybox'
        emerge_env = {
            'USE' : '-* savedconfig', 'ROOT' : busybox_root,
            'PORTAGE_CONFIGROOT' : busybox_root
        }
        Execute(cmd, timeout=600, extra_env=emerge_env, logfile=self.logfile)

        # Remove any old initramfs root and prepare a new one.
        initramfs_root = os.path.join(self.tmpdir, 'initramfs')
        shutil.rmtree(initramfs_root, ignore_errors=True)
        root_paths = [
            'bin', 'dev', 'etc', 'mnt/cdrom', 'mnt/squashfs', 'mnt/tmpfs', 'proc', 'sbin', 'sys',
            'tmp', 'usr/bin', 'usr/sbin', 'var', 'var/run'
        ]
        for _path in root_paths:
            _dir = os.path.join(initramfs_root, _path)
            os.makedirs(_dir, mode=0o755, exist_ok=True)

        # Copy the static busybox to the initramfs root.
        # TODO: we are assuming a static busybox, so we should check.
        busybox_initramfs_path = os.path.join(initramfs_root, 'bin/busybox')
        shutil.copy(busybox_path, busybox_initramfs_path)
        os.chmod(busybox_initramfs_path, 0o0755)
        cmd = 'chroot %s /bin/busybox --install -s' % initramfs_root
        Execute(cmd, timeout=60, logfile=self.logfile)

        # Copy the init script to the initramfs root.
        initscript_path = os.path.join(self.libdir, 'scripts/initramfs-init')
        init_initramfs_path = os.path.join(initramfs_root, 'init')
        shutil.copy(initscript_path, init_initramfs_path)
        os.chmod(init_initramfs_path, 0o0755)

        # TODO: we are assuming a static kernel and so not copying in
        # any modules.  This is where we should copy in modules.

        # cpio-gzip the initramfs root to the iso boot dir
        initramfs_path = os.path.join(isoboot_dir, 'initramfs')
        cwd = os.getcwd()
        os.chdir(initramfs_root)
        cmd = 'find . -print | cpio -H newc -o | gzip -9 > %s' % initramfs_path
        # Piped commands must be run in a shell.
        Execute(cmd, timeout=600, logfile=self.logfile, shell=True)
        os.chdir(cwd)


    def isoit(self, alt_name=None):
        # Create the ISO with the default name unless an alt_name is given.
        if alt_name:
            self.medium_name = '%s-%s%s%s.iso' % (alt_name, self.year, self.month, self.day)
            self.digest_name = '%s.DIGESTS' % self.medium_name
        iso_dir = os.path.join(self.tmpdir, 'iso')
        isoboot_dir = os.path.join(iso_dir, 'boot')
        isogrub_dir = os.path.join(isoboot_dir, 'grub')
        shutil.rmtree(iso_dir, ignore_errors=True)
        os.makedirs(isogrub_dir, mode=0o755, exist_ok=False)

        # 1. build initramfs image and copy it in
        self.initramfs(isoboot_dir)

        # 2. Move the kernel image into the iso/boot directory.
        # TODO: we are assuming a static kernel
        kernelimage_dir = os.path.join(self.portage_configroot, 'boot')
        kernelimage_path = os.path.join(kernelimage_dir, 'kernel')
        shutil.copy(kernelimage_path, isoboot_dir)
        # If this fails, we'll have to rebuild the kernel!
        #shutil.rmtree(kernelimage_dir, ignore_errors=True)

        # 3. Make the squashfs image and copy it into the iso/boot.
        # This can take a long time.
        squashfs_path = os.path.join(iso_dir, 'rootfs')
        cmd = 'mksquashfs %s %s -xattrs -comp xz' % (self.portage_configroot, squashfs_path)
        Execute(cmd, timeout=None, logfile=self.logfile)

        # 4. Emerge grub:0 to grab stage2_eltorito
        grub_root = os.path.join(self.tmpdir, 'grub')
        eltorito_path = os.path.join(grub_root, 'boot/grub/stage2_eltorito')
        menulst_path = os.path.join(self.libdir, 'scripts/menu.lst')
        cmd = 'emerge --nodeps -1q grub:0'
        emerge_env = {'USE' : '-* savedconfig', 'ROOT' : grub_root}
        Execute(cmd, timeout=600, extra_env=emerge_env, logfile=self.logfile)
        shutil.copy(eltorito_path, isogrub_dir)
        shutil.copy(menulst_path, isogrub_dir)

        # 5. Create the iso image.  This can take a long time.
        args = '-R '                            # Rock Ridge protocol
        args += '-b boot/grub/stage2_eltorito ' # El Torito boot image
        args += '-no-emul-boot '                # No disk emulation for El Torito
        args += '-boot-load-size 4 '            # 4x512-bit sectors for no-emulation mode
        args += '-boot-info-table '             # Create El Torito boot info table
        medium_path = os.path.join(self.tmpdir, self.medium_name)
        cmd = 'mkisofs %s -o %s %s' % (args, medium_path, iso_dir)
        Execute(cmd, timeout=None, logfile=self.logfile)