#!/usr/bin/python # Copyright (C) 2014 Codethink Limited # # 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; version 2 of the License. # # 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, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. '''A Baserock installer.''' import morphlib import os import re import sys import json import yaml import subprocess import tempfile import errno import time import stat import traceback import contextlib import tempfile import shutil config_file = '/etc/install.conf' to_mount = ( ('/proc', 'proc', 'none'), ('/sys', 'sysfs', 'none'), ('/tmp', 'tmpfs', 'none'), ) class NotDeviceError(Exception): pass class FileNotExistsError(Exception): pass class NotBaserockRootfsError(Exception): pass @contextlib.contextmanager def _tempdir(): td = tempfile.mkdtemp() try: yield td finally: subprocess.check_call(['rmdir', td]) @contextlib.contextmanager def _mount(device, mount_type): with _tempdir() as mount_dir: try: subprocess.check_call(['mount', '-t', mount_type, device, mount_dir]) yield mount_dir finally: subprocess.check_call(['umount', mount_dir]) def validate_install_values(disk_dest, rootfs): if not os.path.exists(disk_dest): print "ERROR: The device %s doesn't exist." % disk_dest raise FileNotExistsError if not is_device(disk_dest): print "ERROR: Not deploying to a device" raise NotDeviceError if not is_baserock_rootfs(rootfs): print "ERROR: The rootfs %s is not a baserock rootfs." % rootfs raise NotBaserockRootfsError def is_baserock_rootfs(rootfs): return os.path.isdir(os.path.join(rootfs, 'baserock')) def run_install(writeext_path, deployment_config, rootfs, disk_dest, create_boot_partition): env = dict(os.environ) env.update(deployment_config) if create_boot_partition in ('yes', 'True'): subprocess.check_call([writeext_path, rootfs, disk_dest + '2'], env=env) print "Creating a boot partition" p = subprocess.Popen(['/sbin/sfdisk', '-uM', disk_dest], stdin=subprocess.PIPE) p.stdin.write(',250,83,*\n') p.stdin.write(',,83,\n') p.communicate() subprocess.check_call(['/usr/sbin/mkfs.ext4', '-L', 'boot', '-F', disk_dest + '1']) with _mount(disk_dest + '1', 'ext4') as boot_mount, \ _mount(disk_dest + '2', 'btrfs') as root_mount: boot_factory = os.path.join(boot_mount, 'systems', 'factory') root_factory = os.path.join(root_mount, 'systems', 'factory') os.makedirs(boot_factory) shutil.copy(os.path.join(root_factory, 'kernel'), os.path.join(boot_factory)) dtb_path = os.path.join(root_factory, 'dtb') if os.path.exists(dtb_path): shutil.copy(dtb_path, boot_factory) initramfs_path = os.path.join(root_factory, 'initramfs') if os.path.exists(initramfs_path): shutil.copy(initramfs_path, boot_factory) extlinuxconf_path = os.path.join(root_mount, 'extlinux.conf') if os.path.exists(extlinuxconf_path): shutil.copy(extlinuxconf_path, boot_mount) bootscr_path = os.path.join(root_factory, 'orig', 'boot', 'boot.scr') if os.path.exists(bootscr_path): shutil.copy(bootscr_path, boot_mount) os.symlink('factory', os.path.join(boot_mount, 'systems', 'default')) else: subprocess.check_call([writeext_path, rootfs, disk_dest], env=env) def finish_installation(postinstallcmd): os.system("sync") print "Executing `%s` in 5 seconds..." % postinstallcmd time.sleep(5) os.system(postinstallcmd) def mount(partition, mount_point, fstype): return subprocess.call(['mount', partition, mount_point, '-t', fstype]) def do_mounts(to_mount): mounted = [] for mount_point, mount_type, source in to_mount: print 'Mounting %s in %s' % (source, mount_point) if not os.path.exists(mount_point): os.makedirs(mount_point) if mount(source, mount_point, mount_type) == 0: mounted.append(mount_point) return mounted def do_unmounts(to_unmount): for path in reversed(to_unmount): print 'Unmounting %s' % path if subprocess.call(['umount', path]) != 0: print 'WARNING: Failed to `umount %s`' % path def check_and_read_config(config_file): print "Reading configuration from %s..." % config_file keys = ('INSTALLER_TARGET_STORAGE_DEVICE', 'INSTALLER_ROOTFS_TO_INSTALL') try: with open(config_file) as f: config = yaml.load(f) except IOError as e: if e.errno != errno.ENOENT: raise print "WARNING: Configuration file '%s' not found" % config_file config = {} device, rootfs = (read_option(config, key) for key in keys) create_boot_partition = read_option(config, 'INSTALLER_CREATE_BOOT_PARTITION') postinstallcmd = read_option(config, 'INSTALLER_POST_INSTALL_COMMAND', 'reboot -f') return device, rootfs, create_boot_partition, postinstallcmd def read_option(config, option, default_value=None): try: value = config[option] except KeyError as e: if default_value: value = default_value else: value = raw_input("Option '%s' missing, please enter a value: " % option) print "Option '%s' with value '%s'" % (option, value) return value def get_deployment_config(rootfs): print "Reading deployment.meta of the system to install..." try: meta = open(os.path.join(rootfs, 'baserock/deployment.meta')) except IOError as e: if e.errno != errno.ENOENT: raise print "Failed to read deployment.meta, it will be empty" deployment_config = {} else: deployment_config = json.load(meta).get('configuration', {}) meta.close() print "################ Environment #################" for key in deployment_config: print "# %s: %s" % (key, deployment_config[key]) print "##############################################" return deployment_config def install_system(install_script): subprocess.check_call(['sh', install_script]) def is_device(location): try: st = os.stat(location) return stat.S_ISBLK(st.st_mode) except OSError as e: if e.errno == errno.ENOENT: return False raise try: print "Baserock installation script begins..." mounted = do_mounts(to_mount) writeext_path = morphlib.extensions._get_morph_extension_filename( 'rawdisk', '.write') disk_dest, rootfs, create_boot_partition, postinstallcmd = \ check_and_read_config(config_file) validate_install_values(disk_dest, rootfs) deployment_config=get_deployment_config(rootfs) run_install(writeext_path, deployment_config, rootfs, disk_dest, create_boot_partition) do_unmounts(mounted) finish_installation(postinstallcmd) except BaseException as e: print traceback.format_exc() print "Something failed, opening shell..." print "Once you have finished, use `reboot -f`" os.execl('/bin/sh', 'sh')