From a9c9e4665d37e28ff51706c158e35c145cbab5e2 Mon Sep 17 00:00:00 2001 From: Ian Santopietro Date: Wed, 28 Nov 2018 17:18:27 -0700 Subject: [PATCH 01/16] Get rid of any pylint warnings. --- bin/kernelstub | 180 ++++++++++++++++++----------------- debian/changelog | 7 ++ kernelstub/application.py | 192 +++++++++++++++++++++----------------- kernelstub/config.py | 68 ++++++++++---- kernelstub/drive.py | 72 ++++++++------ kernelstub/installer.py | 167 +++++++++++++++++++-------------- kernelstub/nvram.py | 55 ++++++----- kernelstub/opsys.py | 19 +++- setup.py | 26 ++++-- 9 files changed, 468 insertions(+), 318 deletions(-) diff --git a/bin/kernelstub b/bin/kernelstub index 96afdfc..7cec998 100644 --- a/bin/kernelstub +++ b/bin/kernelstub @@ -39,193 +39,202 @@ terms. kernelstub will load parameters from the /etc/default/kernelstub config file. """ -import argparse, os +import argparse +import os from kernelstub import application -def main(options=None): # Do the thing +def main(options=None): + """ Do the thing - Main Kernelstub Function""" kernelstub = application.Kernelstub() # Set up argument processing parser = argparse.ArgumentParser( - description = "Automatic Kernel EFIstub manager") + description="Automatic Kernel EFIstub manager") loader_stub = parser.add_mutually_exclusive_group() install_loader = parser.add_mutually_exclusive_group() parser.add_argument( '-c', '--dry-run', - action = 'store_true', - dest = 'dry_run', - help = 'Don\'t perform any actions, just simulate them.' + action='store_true', + dest='dry_run', + help='Don\'t perform any actions, just simulate them.' ) parser.add_argument( '-p', '--print-config', - action = 'store_true', - dest = 'print_config', - help = 'Print the current configuration and exit' + action='store_true', + dest='print_config', + help='Print the current configuration and exit' ) parser.add_argument( '-e', - dest = 'esp_path', - metavar = 'ESP,', - help = '' + dest='esp_path', + metavar='ESP,', + help='' ) parser.add_argument( '--esp-path', - dest = 'esp_path', - metavar = 'ESP', - help = 'Manually specify the path to the ESP. Default is /boot/efi' + dest='esp_path', + metavar='ESP', + help='Manually specify the path to the ESP. Default is /boot/efi' ) parser.add_argument( '-r', - dest = 'root_path', - metavar = 'ROOT', - help = '' + dest='root_path', + metavar='ROOT', + help='' ) parser.add_argument( '--root-path', - dest = 'root_path', - metavar = 'ROOT', - help = 'The path where the root filesystem to use is mounted.' + dest='root_path', + metavar='ROOT', + help='The path where the root filesystem to use is mounted.' ) parser.add_argument( '-k', - dest = 'kernel_path', - metavar= 'PATH,', - help = '' + dest='kernel_path', + metavar='PATH,', + help='' ) parser.add_argument( '--kernel-path', - dest = 'kernel_path', - metavar= 'PATH', - help = 'The path to the kernel image.' + dest='kernel_path', + metavar='PATH', + help='The path to the kernel image.' ) parser.add_argument( '-i', - dest = 'initrd_path', - metavar = 'PATH,', - help = '' + dest='initrd_path', + metavar='PATH,', + help='' ) parser.add_argument( '--initrd-path', - dest = 'initrd_path', - metavar = 'PATH', - help = 'The path to the initrd image.' + dest='initrd_path', + metavar='PATH', + help='The path to the initrd image.' ) parser.add_argument( '-o', - dest = 'k_options', - metavar = '"OPTIONS",', - help = '' + dest='k_options', + metavar='"OPTIONS",', + help='' ) parser.add_argument( '--options', - dest = 'k_options', - metavar = '"OPTIONS"', - help = 'The total boot options to be passed to the kernel' + dest='k_options', + metavar='"OPTIONS"', + help='The total boot options to be passed to the kernel' ) parser.add_argument( '-a', - dest = 'add_options', - metavar = '"OPTIONS",', - help = '' + dest='add_options', + metavar='"OPTIONS",', + help='' ) parser.add_argument( '--add-options', - dest = 'add_options', - metavar = '"OPTIONS"', - help = ('Boot options to add to the configuration ' - '(if they aren\'t already present)') - ) + dest='add_options', + metavar='"OPTIONS"', + help=( + 'Boot options to add to the configuration (if they aren\'t ' + 'already present)' + ) + ) parser.add_argument( '-d', - dest = 'remove_options', - metavar = "OPTIONS", - help = '' + dest='remove_options', + metavar="OPTIONS", + help='' ) parser.add_argument( '--delete-options', - dest = 'remove_options', - metavar = '"OPTIONS"', - help = ('Boot options to remove from the configuration ' - '(if they\'re present already)') + dest='remove_options', + metavar='"OPTIONS"', + help=( + 'Boot options to remove from the configuration (if they\'re ' + 'present already)' + ) ) parser.add_argument( '-g', - dest = 'log_file', - metavar = 'LOG', - help = '' + dest='log_file', + metavar='LOG', + help='' ) parser.add_argument( '--log-file', - dest = 'log_file', - metavar = 'LOG', - help = ('The path to the log file to use. Defaults to ' - '/var/log/kernelstub.log') + dest='log_file', + metavar='LOG', + help=( + 'The path to the log file to use. Defaults to ' + '/var/log/kernelstub.log' + ) ) install_loader.add_argument( '-l', '--loader', - action = 'store_true', - dest = 'setup_loader', - help = 'Creates a systemd-boot compatible loader configuration' + action='store_true', + dest='setup_loader', + help='Creates a systemd-boot compatible loader configuration' ) install_loader.add_argument( '-n', '--no-loader', - action = 'store_true', - dest = 'off_loader', - help = 'Turns off creating loader configuration' + action='store_true', + dest='off_loader', + help='Turns off creating loader configuration' ) loader_stub.add_argument( '-s', '--stub', - action = 'store_true', - dest = 'install_stub', - help = 'Set up NVRAM entries for the copied kernel' + action='store_true', + dest='install_stub', + help='Set up NVRAM entries for the copied kernel' ) loader_stub.add_argument( '-m', '--manage-only', - action = 'store_true', - dest = 'manage_mode', - help = 'Only copy entries, don\'t set up the NVRAM' + action='store_true', + dest='manage_mode', + help='Only copy entries, don\'t set up the NVRAM' ) parser.add_argument( '-f', '--force-update', - action = 'store_true', - dest = 'force_update', - help = ('Forcibly update any loader.conf to set the new entry as the ' - 'default') + action='store_true', + dest='force_update', + help=( + 'Forcibly update any loader.conf to set the new entry as the default' + ) ) parser.add_argument( '-v', '--verbose', - action = 'count', - dest = 'verbosity', - help = 'Increase program verbosity and display extra output.' + action='count', + dest='verbosity', + help='Increase program verbosity and display extra output.' ) parser.add_argument( '--preserve-live-mode', - action = 'store_true', - dest = 'preserve_live', - help = argparse.SUPPRESS + action='store_true', + dest='preserve_live', + help=argparse.SUPPRESS ) args = parser.parse_args() @@ -234,8 +243,9 @@ def main(options=None): # Do the thing if os.geteuid() != 0: parser.print_help() - print('kernelstub: ERROR: You need to be root or use sudo to run ' - 'kernelstub!') + print( + 'kernelstub:ERROR: You need to be root or use sudo to run kernelstub' + ) exit(176) kernelstub.main(args) diff --git a/debian/changelog b/debian/changelog index 03631fc..530cd91 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,10 @@ +kernelstub (3.2.0) cosmic; urgency=medium + + * Reformatted output + * Revisions to make code cleaner and more pythonic + + -- Ian Santopietro Wed, 28 Nov 2018 15:08:31 -0700 + kernelstub (3.1.0) bionic; urgency=medium * Add logging to systemd journald diff --git a/kernelstub/application.py b/kernelstub/application.py index d1ee448..a553ed1 100755 --- a/kernelstub/application.py +++ b/kernelstub/application.py @@ -39,17 +39,8 @@ kernelstub will load parameters from the /etc/default/kernelstub config file. """ -import logging, os - -systemd_support = False -try: - from systemd.journal import JournalHandler - systemd_support = True - -except ImportError: - pass - -import logging.handlers as handlers +import logging +import os from . import drive as Drive from . import nvram as Nvram @@ -57,32 +48,33 @@ from . import installer as Installer from . import config as Config -class CmdLineError(Exception): +SYSTEMD_SUPPORT = False +try: + from systemd.journal import JournalHandler + SYSTEMD_SUPPORT = True + +except ImportError: pass +class CmdLineError(Exception): + """ Exception raised when we can't find any kernel parameters """ + class Kernelstub(): + """ Main Kernelstub Class """ - def parse_options(self, options): - for index, option in enumerate(options): - if '"' in option: - matched = False - itr = 1 - while matched == False: - try: - next_option = options[index + itr] - option = '%s %s' % (option, next_option) - options[index + itr] = "" - if '"' in next_option: - matched = True - else: - itr = itr + 1 - except IndexError: - matched = True - options[index] = option - return options - - def main(self, args): # Do the thing + def mktable(self, data, padding): + """ + Makes a table from a dictionary. + returns: a str containing the table. + """ + table = '' + for i in data: + table += ' {0:{pad}} {1}\n'.format(i, data[i], pad=padding) + return table + + def main(self, args): + """ Do the thing """ log_file_path = '/var/log/kernelstub.log' if args.log_file: log_file_path = args.log_file @@ -115,7 +107,7 @@ def main(self, args): # Do the thing console_log.setFormatter(stream_fmt) console_log.setLevel(console_level) - file_log = handlers.RotatingFileHandler( + file_log = logging.handlers.RotatingFileHandler( log_file_path, maxBytes=(1048576*5), backupCount=5) file_log.setFormatter(file_fmt) file_log.setLevel(file_level) @@ -123,7 +115,7 @@ def main(self, args): # Do the thing log.addHandler(console_log) log.addHandler(file_log) - if systemd_support: + if SYSTEMD_SUPPORT: journald_log = JournalHandler() journald_log.setLevel(file_level) journald_log.setFormatter(stream_fmt) @@ -131,7 +123,7 @@ def main(self, args): # Do the thing log.setLevel(logging.DEBUG) - log.debug('Got command line options: %s' % args) + log.debug('Got command line options: %s', args) # Figure out runtime options no_run = False @@ -151,45 +143,44 @@ def main(self, args): # Do the thing opsys = Opsys.OS() if args.kernel_path: - log.debug( - 'Manually specified kernel path:\n ' + - ' %s' % args.kernel_path) + log.debug('Manual kernel path:\n %s', args.kernel_path) opsys.kernel_path = args.kernel_path else: opsys.kernel_path = os.path.join(root_path, opsys.kernel_name) if args.initrd_path: - log.debug( - 'Manually specified initrd path:\n ' + - ' %s' % args.initrd_path) + log.debug('Manual initrd path:\n %s', args.initrd_path) opsys.initrd_path = args.initrd_path else: opsys.initrd_path = os.path.join(root_path, opsys.initrd_name) if not os.path.exists(opsys.kernel_path): - log.exception('Can\'t find the kernel image! \n\n' - 'Please use the --kernel-path option to specify ' - 'the path to the kernel image') + log.exception( + 'Can\'t find the kernel image! \n\n Please use the ' + '--kernel-path option to specify the path to the kernel image' + ) exit(0) if not os.path.exists(opsys.initrd_path): - log.exception('Can\'t find the initrd image! \n\n' - 'Please use the --initrd-path option to specify ' - 'the path to the initrd image') + log.exception( + 'Can\'t find the initrd image! \n\n Please use the ' + '--initrd-path option to specify the path to the initrd image' + ) exit(0) # Check for kernel parameters. Without them, stop and fail if args.k_options: - configuration['kernel_options'] = self.parse_options(args.k_options.split()) + configuration['kernel_options'] = config.parse_options(args.k_options.split()) else: try: configuration['kernel_options'] except KeyError: - error = ("cmdline was 'InvalidConfig'\n\n" - "Could not find any valid configuration. This " - "probably means that the configuration file is " - "corrupt. Either remove it to regenerate it from" - "default or fix the existing one.") + error = ( + 'cmdline was "InvalidConfig"\n\n Could not find any valid ' + 'configuration. This probably means that the configuration ' + 'file is corrupt. Either remove it to regenerate it from ' + 'default or fix the existing one.' + ) log.exception(error) raise CmdLineError("No Kernel Parameters found") exit(168) @@ -228,6 +219,7 @@ def main(self, args): # Do the thing setup_loader = configuration['setup_loader'] manage_mode = configuration['manage_mode'] force = configuration['force_update'] + live_mode = configuration['live_mode'] except KeyError: log.exception( @@ -237,7 +229,7 @@ def main(self, args): # Do the thing 'If you can\'t figure it out, then deleting them should fix ' 'the errors and cause kernelstub to regenerate them from ' 'Default. \n\n You can use "-vv" to get the configuration used.') - log.debug('Configuration we got: \n\n%s' % config.print_config()) + log.debug('Configuration we got: \n\n%s', config.print_config()) exit(169) @@ -257,7 +249,7 @@ def main(self, args): # Do the thing if args.force_update: force = True - if configuration['force_update'] == True: + if configuration['force_update'] is True: force = True log.debug('Structing objects') @@ -267,35 +259,66 @@ def main(self, args): # Do the thing installer = Installer.Installer(nvram, opsys, drive) # Log some helpful information, to file and optionally console - info = ( - ' OS:..................%s %s\n' %(opsys.name_pretty,opsys.version) + - ' Root partition:......%s\n' % drive.root_fs + - ' Root FS UUID:........%s\n' % drive.root_uuid + - ' ESP Path:............%s\n' % esp_path + - ' ESP Partition:.......%s\n' % drive.esp_fs + - ' ESP Partition #:.....%s\n' % drive.esp_num + - ' NVRAM entry #:.......%s\n' % nvram.os_entry_index + - ' Boot Variable #:.....%s\n' % nvram.order_num + - ' Kernel Boot Options:.%s\n' % " ".join(kernel_opts) + - ' Kernel Image Path:...%s\n' % opsys.kernel_path + - ' Initrd Image Path:...%s\n' % opsys.initrd_path + - ' Force-overwrite:.....%s\n' % str(force)) - - log.info('System information: \n\n%s' % info) + data_system = { + 'Root:': drive.root_fs, + 'ESP:': drive.esp_fs, + 'Kernel Path:': opsys.kernel_path, + 'Initrd Path:': opsys.initrd_path, + 'Boot Options:': " ".join(kernel_opts), + } + data_debug = { + 'OS:': "{} {}".format(opsys.name_pretty, opsys.version), + 'ESP Partition #:': drive.esp_num, + 'NVRAM entry #:': nvram.os_entry_index, + 'Boot Variable #:': nvram.order_num, + 'Root FS UUID:': drive.root_uuid, + } + data_config = { + 'Kernel Options:': " ".join(kernel_opts), + 'ESP Path:': esp_path, + 'Install loader config:': setup_loader, + 'Management Mode:': manage_mode, + 'Force Overwrite:': str(force), + 'Live Disk Mode:': live_mode, + 'Config revision:': configuration['config_rev'] + } + if args.print_config: + log.info( + 'System information:\n\n%s', self.mktable(data_system, 22) + ) + log.debug( + 'Debug information:\n\n%s', self.mktable(data_debug, 22) + ) + log.info( + 'Active configuration details:\n\n%s', + self.mktable(data_config, 22) + ) + exit(0) + + log.info( + 'System information:\n\n%s', self.mktable(data_system, 16) + ) + log.debug( + 'Debug information:\n\n%s', self.mktable(data_debug, 16) + ) + log.debug( + 'Active configuration:\n\n%s', self.mktable(data_config, 22) + ) if args.print_config: - all_config = ( - ' ESP Location:..................%s\n' % configuration['esp_path'] + - ' Management Mode:...............%s\n' % configuration['manage_mode'] + - ' Install Loader configuration:..%s\n' % configuration['setup_loader'] + - ' Configuration version:.........%s\n' % configuration['config_rev']) - log.info('Configuration details: \n\n%s' % all_config) + log.info( + 'Active configuration details:\n\n%s', + self.mktable(data_config, 22) + ) exit(0) log.debug('Setting up boot...') - kopts = 'root=UUID=%s ro %s' % (drive.root_uuid, " ".join(kernel_opts)) - log.debug('kopts: %s' % kopts) + kopts = 'root=UUID={uuid} ro {options}'.format( + uuid=drive.root_uuid, + options=" ".join(kernel_opts) + ) + log.debug('kopts: %s', kopts) @@ -309,11 +332,13 @@ def main(self, args): # Do the thing kopts, setup_loader=setup_loader, simulate=no_run) - except Exception as e: - log.debug('Couldn\'t back up old kernel. \nThis might just mean ' + - 'You don\'t have an old kernel installed. If you do, try ' + - 'with -vv to see debuging information') - log.debug(e) + except Exception as e_e: + log.debug( + 'Couldn\'t back up old kernel. \nThis might just mean you ' + 'don\'t have an older kernel installed. If you do, try with -vv' + ' to see debugging information' + ) + log.debug(e_e) installer.copy_cmdline(simulate=no_run) @@ -328,4 +353,3 @@ def main(self, args): # Do the thing log.debug('Setup complete!\n\n') return 0 - diff --git a/kernelstub/config.py b/kernelstub/config.py index b2c3ed9..a7631aa 100644 --- a/kernelstub/config.py +++ b/kernelstub/config.py @@ -22,12 +22,20 @@ terms. """ -import json, os, logging +import json +import logging +import os class ConfigError(Exception): - pass + """Exception raised when we can't get a valid configuration.""" class Config(): + """ + Kernelstub Configuration Object + + Loads, parses and saves configuration files and parameters for + kernelstub. + """ config_path = "/etc/kernelstub/configuration" config = {} @@ -51,10 +59,19 @@ def __init__(self, path='/etc/kernelstub/configuration'): os.makedirs('/etc/kernelstub/', exist_ok=True) def load_config(self): + """ + Loads a configuration from a file, or loads the default. + + If the configuration is old, it should be upgraded to a newer version. + If the configuration file doesn't match expected conventions, try to + correct it and warn the user about the issue. + + Returns a valid configuration dictionary. + """ self.log.info('Looking for configuration...') if os.path.exists(self.config_path): - self.log.debug('Checking %s' % self.config_path) + self.log.debug('Checking %s', self.config_path) with open(self.config_path) as config_file: self.config = json.load(config_file) @@ -77,7 +94,7 @@ def load_config(self): self.config['user'] = self.config['default'].copy() try: - self.log.debug('Configuration version: %s' % self.config['user']['config_rev']) + self.log.debug('Configuration version: %s', self.config['user']['config_rev']) if self.config['user']['config_rev'] < self.config_default['default']['config_rev']: self.log.warning("Updating old configuration.") self.config = self.update_config(self.config) @@ -85,12 +102,16 @@ def load_config(self): elif self.config['user']['config_rev'] == self.config_default['default']['config_rev']: self.log.debug("Configuration up to date") # Double-checking in case OEMs do bad things with the config file - if type(self.config['user']['kernel_options']) is str: - self.log.warning('Invalid kernel_options format!\n\n' - 'Usually outdated or buggy maintainer packages from your hardware OEM. ' - 'Contact your hardware vendor to inform them to fix their packages.') + if isinstance(self.config['user']['kernel_options'], str): + self.log.warning( + 'Invalid kernel_options format!\n\n Usually outdated or ' + 'buggy maintainer packages from your hardware OEM. ' + 'Contact your hardware vendor to inform them to fix ' + 'their packages.' + ) try: - self.config['user']['kernel_options'] = self.parse_options(self.config['user']['kernel_options'].split()) + options = self.parse_options(self.config['user']['kernel_options']) + self.config['user']['kernel_options'] = options.split() except: raise ConfigError('Malformed configuration file found!') exit(169) @@ -103,34 +124,48 @@ def load_config(self): return self.config def save_config(self, path='/etc/kernelstub/configuration'): - self.log.debug('Saving configuration to %s' % path) + """Saves the configuration we've used to the file.""" + self.log.debug('Saving configuration to %s', path) with open(path, mode='w') as config_file: json.dump(self.config, config_file, indent=2) - + self.log.debug('Configuration saved!') return 0 def update_config(self, config): + """Updates old configuration to a new version and returns the new one""" if config['user']['config_rev'] < 2: config['user']['live_mode'] = False config['default']['live_mode'] = False if config['user']['config_rev'] < 3: - if type(config['user']['kernel_options']) is str: - config['user']['kernel_options'] = self.parse_options(config['user']['kernel_options'].split()) - if type(config['default']['kernel_options']) is str: - config['default']['kernel_options'] = self.parse_options(config['default']['kernel_options'].split()) + if isinstance(config['user']['kernel_options'], str): + options = self.parse_options(config['user']['kernel_options']) + config['user']['kernel_options'] = options.split() + if isinstance(config['default']['kernel_options'], str): + options = self.parse_options(config['default']['kernel_options']) + config['default']['kernel_options'] = options.split() config['user']['config_rev'] = 3 config['default']['config_rev'] = 3 return config def parse_options(self, options): + """ + Parse a list of kernel options + + Takes a list object and ensure that each item in the list is a single + linux kernel option. Returns the resulting list. + + Positional Argument: + options -- The list of kernel options. + + """ self.log.debug(options) for index, option in enumerate(options): if '"' in option: matched = False itr = 1 - while matched == False: + while matched is False: try: next_option = options[index + itr] option = '%s %s' % (option, next_option) @@ -145,5 +180,6 @@ def parse_options(self, options): return options def print_config(self): + """Returns a printable version of the configuration""" output_config = json.dumps(self.config, indent=2) return output_config diff --git a/kernelstub/drive.py b/kernelstub/drive.py index 0ada384..17fd114 100644 --- a/kernelstub/drive.py +++ b/kernelstub/drive.py @@ -22,15 +22,21 @@ terms. """ -import os, logging +import os +import logging class NoBlockDevError(Exception): - pass + """No Block Device Found Exception""" class UUIDNotFoundError(Exception): - pass + """No UUID for device found Exception""" class Drive(): + """ + Kernelstub Drive Object + + Stores and retrieves information related to the current drive. + """ drive_name = 'none' root_fs = '/' @@ -46,8 +52,8 @@ def __init__(self, root_path="/", esp_path="/boot/efi"): self.esp_path = esp_path self.root_path = root_path - self.log.debug('root path = %s' % self.root_path) - self.log.debug('esp_path = %s' % self.esp_path) + self.log.debug('root path = %s', self.root_path) + self.log.debug('esp_path = %s', self.esp_path) self.mtab = self.get_drives() @@ -57,25 +63,29 @@ def __init__(self, root_path="/", esp_path="/boot/efi"): self.drive_name = self.get_drive_dev(self.esp_fs) self.esp_num = self.esp_fs[-1] self.root_uuid = self.get_uuid(self.root_fs[5:]) - except NoBlockDevError as e: - self.log.exception('Could not find a block device for the a ' + - 'partition. This is a critical error and we ' + - 'cannot continue.') - self.log.debug(e) + except NoBlockDevError as e_e: + self.log.exception( + 'Could not find a block device for the a partition. This is a' + 'critical error and we cannot continue.' + ) + self.log.debug(e_e) exit(174) - except UUIDNotFoundError as e: - self.log.exception('Could not get a UUID for the a filesystem. ' + - 'This is a critical error and we cannot continue') - self.log.debug(e) + except UUIDNotFoundError as e_e: + self.log.exception( + 'Could not find a block device for the a partition. This is a ' + 'critical error and we cannot continue.' + ) + self.log.debug(e_e) exit(177) - self.log.debug('Root is on /dev/%s' % self.drive_name) - self.log.debug('root_fs = %s ' % self.root_fs) - self.log.debug('root_uuid is %s' % self.root_uuid) + self.log.debug('Root is on /dev/%s', self.drive_name) + self.log.debug('root_fs = %s ', self.root_fs) + self.log.debug('root_uuid is %s', self.root_uuid) def get_drives(self): + """Returns a list of information about mounted filesystems.""" self.log.debug('Getting a list of drives') with open('/proc/mounts', mode='r') as proc_mounts: mtab = proc_mounts.readlines() @@ -84,33 +94,37 @@ def get_drives(self): return mtab def get_part_dev(self, path): - self.log.debug('Getting the block device file for %s' % path) + """Returns a block device file for `path`.""" + self.log.debug('Getting the block device file for %s', path) for mount in self.mtab: drive = mount.split(" ") if drive[1] == path: part_dev = os.path.realpath(drive[0]) - self.log.debug('%s is on %s' % (path, part_dev)) + self.log.debug('%s is on %s', path, part_dev) return part_dev - raise NoBlockDevError('Couldn\'t find the block device for %s' % path) + raise NoBlockDevError( + 'Couldn\'t find the block device for {}'.format(path) + ) - def get_drive_dev(self, esp): + def get_drive_dev(self, blockdev): + """Returns a block device for the drive partition blockdev is on.""" # Ported from bash, out of @jackpot51's firmware updater - efi_name = os.path.basename(esp) - efi_sys = os.readlink('/sys/class/block/%s' % efi_name) + efi_name = os.path.basename(blockdev) + efi_sys = os.readlink('/sys/class/block/%s', efi_name) disk_sys = os.path.dirname(efi_sys) disk_name = os.path.basename(disk_sys) - self.log.debug('ESP is a partition on /dev/%s' % disk_name) + self.log.debug('ESP is a partition on /dev/%s', disk_name) return disk_name - def get_uuid(self, fs): + def get_uuid(self, filesystem): + """Get a UUID for a filesystem.""" all_uuids = os.listdir('/dev/disk/by-uuid') - self.log.debug('Looking for UUID for %s' % fs) - self.log.debug('List of UUIDs:\n%s' % all_uuids) + self.log.debug('Looking for UUID for %s', filesystem) + self.log.debug('List of UUIDs:\n%s', all_uuids) for uuid in all_uuids: uuid_path = os.path.join('/dev/disk/by-uuid', uuid) - if fs in os.path.realpath(uuid_path): + if filesystem in os.path.realpath(uuid_path): return uuid raise UUIDNotFoundError - diff --git a/kernelstub/installer.py b/kernelstub/installer.py index 395cce0..5d880f1 100644 --- a/kernelstub/installer.py +++ b/kernelstub/installer.py @@ -22,13 +22,20 @@ terms. """ -import os, shutil, logging +import logging +import os +import shutil class FileOpsError(Exception): - pass + """Exception thrown when a file operation fails.""" class Installer(): + """ + Installer class for Kernelstub. + Takes the information processed by kernelstub and performs the boot + configuration and setup. + """ loader_dir = '/boot/efi/loader' entry_dir = '/boot/efi/loader/entries' os_dir_name = 'linux-kernelstub' @@ -46,7 +53,7 @@ def __init__(self, nvram, opsys, drive): self.work_dir = os.path.join(self.drive.esp_path, "EFI") self.loader_dir = os.path.join(self.drive.esp_path, "loader") self.entry_dir = os.path.join(self.loader_dir, "entries") - self.os_dir_name = "%s-%s" % (self.opsys.name, self.drive.root_uuid) + self.os_dir_name = "{}-{}".format(self.opsys.name, self.drive.root_uuid) self.os_folder = os.path.join(self.work_dir, self.os_dir_name) self.kernel_dest = os.path.join(self.os_folder, self.opsys.kernel_name) self.initrd_dest = os.path.join(self.os_folder, self.opsys.initrd_name) @@ -58,56 +65,64 @@ def __init__(self, nvram, opsys, drive): def backup_old(self, kernel_opts, setup_loader=False, simulate=False): + """Copy the previous kernel (if present) into the ESP.""" self.log.info('Backing up old kernel') - kernel_name = "%s-previous.efi" % self.opsys.kernel_name + kernel_name = "{}-previous.efi".format(self.opsys.kernel_name) kernel_dest = os.path.join(self.os_folder, kernel_name) try: self.copy_files( - '%s.old' % self.opsys.kernel_path, + '{}.old'.format(self.opsys.kernel_path), kernel_dest, simulate=simulate) - except: - self.log.debug('Couldn\'t back up old kernel. There\'s ' + - 'probably only one kernel installed.') + except OSError: + self.log.debug( + 'Couldn\'t back up old kernel. There\'s probably only one ' + 'kernel installed.' + ) self.old_kernel = False - pass - initrd_name = "%s-previous" % self.opsys.initrd_name + initrd_name = "{}-previous".format(self.opsys.initrd_name) initrd_dest = os.path.join(self.os_folder, initrd_name) try: self.copy_files( - '%s.old' % self.opsys.initrd_path, + '{}.old'.format(self.opsys.initrd_path), initrd_dest, simulate=simulate) - except: - self.log.debug('Couldn\'t back up old initrd.img. There\'s ' + - 'probably only one kernel installed.') + except OSError: + self.log.debug( + 'Couldn\'t back up old kernel. There\'s probably only one ' + 'kernel installed.' + ) self.old_kernel = False - pass if setup_loader and self.old_kernel: self.ensure_dir(self.entry_dir) - linux_line = '/EFI/%s-%s/%s-previous.efi' % (self.opsys.name, - self.drive.root_uuid, - self.opsys.kernel_name) - initrd_line = '/EFI/%s-%s/%s-previous' % (self.opsys.name, - self.drive.root_uuid, - self.opsys.initrd_name) + linux_line = '/EFI/{}-{}/{}-previous.efi'.format( + self.opsys.name, + self.drive.root_uuid, + self.opsys.kernel_name + ) + initrd_line = '/EFI/{}-{}/{}-previous'.format( + self.opsys.name, + self.drive.root_uuid, + self.opsys.initrd_name + ) self.make_loader_entry( self.opsys.name_pretty, linux_line, initrd_line, kernel_opts, - os.path.join(self.entry_dir, '%s-oldkern' % self.opsys.name)) + os.path.join(self.entry_dir, '{}-oldkern'.format(self.opsys.name))) def setup_kernel(self, kernel_opts, setup_loader=False, overwrite=False, simulate=False): + """Copy the active kernel into the ESP.""" self.log.info('Copying Kernel into ESP') self.kernel_dest = os.path.join( self.os_folder, - "%s.efi" % self.opsys.kernel_name) + "{}.efi".format(self.opsys.kernel_name)) self.ensure_dir(self.os_folder, simulate=simulate) - self.log.debug('kernel being copied to %s' % self.kernel_dest) + self.log.debug('kernel being copied to %s', self.kernel_dest) try: self.copy_files( @@ -115,13 +130,13 @@ def setup_kernel(self, kernel_opts, setup_loader=False, overwrite=False, simulat self.kernel_dest, simulate=simulate) - except FileOpsError as e: + except FileOpsError as e_e: self.log.exception( - 'Couldn\'t copy the kernel onto the ESP!\n' + - 'This is a critical error and we cannot continue. Check your ' + - 'settings to see if there is a typo. Otherwise, check ' + + 'Couldn\'t copy the kernel onto the ESP!\n' + 'This is a critical error and we cannot continue. Check your ' + 'settings to see if there is a typo. Otherwise, check ' 'permissions and try again.') - self.log.debug(e) + self.log.debug(e_e) exit(170) self.log.info('Copying initrd.img into ESP') @@ -132,45 +147,52 @@ def setup_kernel(self, kernel_opts, setup_loader=False, overwrite=False, simulat self.initrd_dest, simulate=simulate) - except FileOpsError as e: - self.log.exception('Couldn\'t copy the initrd onto the ESP!\n' + - 'This is a critical error and we cannot ' + - 'continue. Check your settings to see if ' + - 'there is a typo. Otherwise, check permissions ' + - 'and try again.') - self.log.debug(e) + except FileOpsError as e_e: + self.log.exception( + 'Couldn\'t copy the initrd onto the ESP!\n This is a critical ' + 'error and we cannot continue. Check your settings to see if ' + 'there is a typo. Otherwise, check permissions and try again.' + ) + self.log.debug(e_e) exit(171) self.log.debug('Copy complete') if setup_loader: self.log.info('Setting up loader.conf configuration') - linux_line = '/EFI/%s-%s/%s.efi' % (self.opsys.name, - self.drive.root_uuid, - self.opsys.kernel_name) - initrd_line = '/EFI/%s-%s/%s' % (self.opsys.name, - self.drive.root_uuid, - self.opsys.initrd_name) + linux_line = '/EFI/{}-{}/{}-previous.efi'.format( + self.opsys.name, + self.drive.root_uuid, + self.opsys.kernel_name + ) + initrd_line = '/EFI/{}-{}/{}-previous'.format( + self.opsys.name, + self.drive.root_uuid, + self.opsys.initrd_name + ) if simulate: self.log.info("Simulate creation of entry...") - self.log.info('Loader entry: %s/%s-current\n' %(self.entry_dir, - self.opsys.name) + - 'title %s\n' % self.opsys.name_pretty + - 'linux %s\n' % linux_line + - 'initrd %s\n' % initrd_line + - 'options %s\n' % kernel_opts) + self.log.info( + 'Loader entry: %s/%s-current\n' + 'title %s\n' + 'linux %s\n' + 'initrd %s\n' + 'options %s\n', self.entry_dir, self.opsys.name, + self.opsys.name_pretty, linux_line, initrd_line, kernel_opts + ) return 0 if not overwrite: - if not os.path.exists('%s/loader.conf' % self.loader_dir): + if not os.path.exists('{}/loader.conf'.format(self.loader_dir)): overwrite = True if overwrite: self.ensure_dir(self.loader_dir) with open( - '%s/loader.conf' % self.loader_dir, mode='w') as loader: + '{}/loader.conf'.format(self.loader_dir), mode='w' + ) as loader: - default_line = 'default %s-current\n' % self.opsys.name + default_line = 'default {}-current\n'.format(self.opsys.name) loader.write(default_line) self.ensure_dir(self.entry_dir) @@ -179,13 +201,10 @@ def setup_kernel(self, kernel_opts, setup_loader=False, overwrite=False, simulat linux_line, initrd_line, kernel_opts, - os.path.join(self.entry_dir, '%s-current' % self.opsys.name)) - - - - + os.path.join(self.entry_dir, '{}-current'.format(self.opsys.name))) def setup_stub(self, kernel_opts, simulate=False): + """Set up the kernel efistub bootloader.""" self.log.info("Setting up Kernel EFISTUB loader...") self.copy_cmdline(simulate=simulate) self.nvram.update() @@ -200,45 +219,49 @@ def setup_stub(self, kernel_opts, simulate=False): self.nvram.add_entry(self.opsys, self.drive, kernel_opts, simulate) self.nvram.update() nvram_lines = "\n".join(self.nvram.nvram) - self.log.info('NVRAM configured, new values: \n\n%s\n' % nvram_lines) + self.log.info('NVRAM configured, new values: \n\n%s\n', nvram_lines) def copy_cmdline(self, simulate): + """Copy the current boot options into the ESP.""" self.copy_files( '/proc/cmdline', self.os_folder, - simulate = simulate + simulate=simulate ) def make_loader_entry(self, title, linux, initrd, options, filename): - self.log.info('Making entry file for %s' % title) - with open('%s.conf' % filename, mode='w') as entry: - entry.write('title %s\n' % title) - entry.write('linux %s\n' % linux) - entry.write('initrd %s\n' % initrd) - entry.write('options %s\n' % options) + """Create a systemd-boot loader entry file.""" + self.log.info('Making entry file for %s', title) + with open('{}.conf'.format(filename), mode='w') as entry: + entry.write('title {}\n'.format(title)) + entry.write('linux {}\n'.format(linux)) + entry.write('initrd {}\n'.format(initrd)) + entry.write('options {}\n'.format(options)) self.log.debug('Entry created!') def ensure_dir(self, directory, simulate=False): + """Ensure that a folder exists.""" if not simulate: try: os.makedirs(directory, exist_ok=True) return True - except Exception as e: - self.log.exception('Couldn\'t make sure %s exists.' % directory) - self.log.debug(e) + except Exception as e_e: + self.log.exception('Couldn\'t make sure %s exists.', directory) + self.log.debug(e_e) return False - def copy_files(self, src, dest, simulate): # Copy file src into dest + def copy_files(self, src, dest, simulate): + """Copy src into dest.""" if simulate: - self.log.info('Simulate copying: %s => %s' % (src, dest)) + self.log.info('Simulate copying: %s => %s', src, dest) return True else: try: - self.log.debug('Copying: %s => %s' % (src, dest)) + self.log.debug('Copying: %s => %s', src, dest) shutil.copy(src, dest) return True - except Exception as e: - self.log.debug(e) + except Exception as e_e: + self.log.debug(e_e) raise FileOpsError("Could not copy one or more files.") return False diff --git a/kernelstub/nvram.py b/kernelstub/nvram.py index 2e2be85..35a4457 100644 --- a/kernelstub/nvram.py +++ b/kernelstub/nvram.py @@ -22,10 +22,15 @@ terms. """ -import subprocess, logging +import logging +import subprocess class NVRAM(): + """ + Kernelstub NVRAM object. + Provides methods for interacting with the system NVRAM variables. + """ os_entry_index = -1 os_label = "" nvram = [] @@ -39,6 +44,7 @@ def __init__(self, name, version): self.update() def update(self): + """Make sure we're looking at the correct NVRAM entry.""" self.log.debug('Updating NVRAM info') self.nvram = self.get_nvram() self.find_os_entry(self.nvram, self.os_label) @@ -46,6 +52,7 @@ def update(self): self.order_num = str(self.nvram[self.os_entry_index])[4:8] def get_nvram(self): + """Retrieve NVRAM data from system.""" self.log.debug('Getting NVRAM data') command = [ '/usr/bin/sudo', @@ -53,24 +60,26 @@ def get_nvram(self): ] try: return subprocess.check_output(command).decode('UTF-8').split('\n') - except Exception as e: + except Exception as e_e: self.log.exception('Failed to retrieve NVRAM data. Are you running in a chroot?') - self.log.debug(e) + self.log.debug(e_e) return [] def find_os_entry(self, nvram, os_label): - self.log.debug('Finding NVRAM entry for %s' % os_label) + """Find an NVRAM entry for the current OS.""" + self.log.debug('Finding NVRAM entry for %s', os_label) self.os_entry_index = -1 find_index = self.os_entry_index for entry in nvram: find_index = find_index + 1 if os_label in entry: self.os_entry_index = find_index - self.log.debug('Entry found! Index: %s' % self.os_entry_index) + self.log.debug('Entry found! Index: %s', self.os_entry_index) return find_index def add_entry(self, this_os, this_drive, kernel_opts, simulate=False): + """Add an entry into the NVRAM.""" self.log.info('Creating NVRAM entry') device = '/dev/%s' % this_drive.drive_name esp_num = this_drive.esp_num @@ -88,35 +97,39 @@ def add_entry(self, this_os, this_drive, kernel_opts, simulate=False): '-u', 'initrd=%s %s' % (entry_initrd, kernel_opts) ] - self.log.debug('NVRAM command:\n%s' % command) + self.log.debug('NVRAM command:\n%s', command) if not simulate: try: subprocess.run(command) - except Exception as e: - self.log.exception('Couldn\'t create boot entry for kernel! ' + - 'This means that the system will not boot from ' + - 'the new kernel directly. Do NOT reboot without ' + - 'an alternate bootloader configured or fixing ' + - 'this problem. More information is available in ' + - 'the log or by running again with -vv') - self.log.debug(e) + except subprocess.SubprocessError as e_e: + self.log.exception( + 'Couldn\'t create boot entry for kernel! This means that ' + 'the system will not boot from the new kernel directly. Do ' + 'NOT reboot without an alternate bootloader configured or ' + 'fixing this problem. More information is available in the ' + 'log or by running again with -vv' + ) + self.log.debug(e_e) exit(172) self.update() def delete_boot_entry(self, index, simulate): - self.log.info('Deleting old boot entry: %s' % index) + """Delete an entry from the NVRAM.""" + self.log.info('Deleting old boot entry: %s', index) command = ['/usr/bin/sudo', 'efibootmgr', '-B', '-b', str(index)] - self.log.debug('NVRAM command:\n%s' % command) + self.log.debug('NVRAM command:\n%s', command) if not simulate: try: subprocess.run(command) - except Exception as e: - self.log.exception('Couldn\'t delete old boot entry %s. ' % index + - 'This could cause problems, so kernelstub will ' + - 'not continue. Check again with -vv for more info.') - self.log.debug(e) + except Exception as e_e: + self.log.exception( + 'Couldn\'t delete old boot entry %s. This could cause ' + 'problems, so kernelstub will not continue. Check again ' + 'with -vv for more info.', index + ) + self.log.debug(e_e) exit(173) self.update() diff --git a/kernelstub/opsys.py b/kernelstub/opsys.py index 28b53f5..411f662 100644 --- a/kernelstub/opsys.py +++ b/kernelstub/opsys.py @@ -25,7 +25,11 @@ import platform class OS(): + """ + Kernelstub OS object. + Provides helper functions for getting and storing OS information. + """ name_pretty = "Linux" name = "Linux" version = "1.0" @@ -43,8 +47,12 @@ def __init__(self): self.cmdline = self.get_os_cmdline() def clean_names(self, name): - # This is a list of characters we can't/don't want to have in technical - # names for the OS. name_pretty will still have them. + """ + Remove bad characters from names. + + This is a list of characters we can't/don't want to have in technical + names for the OS. name_pretty will still have them. + """ badchar = { ' ' : '_', '~' : '-', @@ -88,6 +96,7 @@ def clean_names(self, name): return name def get_os_cmdline(self): + """Gets a clean list of current OS boot options.""" with open('/proc/cmdline') as cmdline_file: cmdline_list = cmdline_file.readlines()[0].split(" ") @@ -100,6 +109,7 @@ def get_os_cmdline(self): return cmdline def get_os_name(self): + """Get the current OS name.""" os_release = self.get_os_release() for item in os_release: if item.startswith('NAME='): @@ -107,13 +117,15 @@ def get_os_name(self): return self.strip_quotes(name[:-1]) def get_os_version(self): + """Get the current OS version.""" os_release = self.get_os_release() for item in os_release: if item.startswith('VERSION_ID='): - version = item.split('=')[1] + version = item.split('=')[1] return self.strip_quotes(version[:-1]) def strip_quotes(self, value): + """Return `value` without quotation marks.""" new_value = value if value.startswith('"'): new_value = new_value[1:] @@ -122,6 +134,7 @@ def strip_quotes(self, value): return new_value def get_os_release(self): + """Return a list with the current OS release data.""" try: with open('/etc/os-release') as os_release_file: os_release = os_release_file.readlines() diff --git a/setup.py b/setup.py index 9d48aba..9c0dedb 100755 --- a/setup.py +++ b/setup.py @@ -21,21 +21,27 @@ from distutils.core import setup from distutils.cmd import Command -import os, subprocess, sys +import os +import subprocess +import sys TREE = os.path.dirname(os.path.abspath(__file__)) DIRS = [ 'kernelstub', - 'bin'] + 'bin' +] def run_under_same_interpreter(opname, script, args): + """Re-run with the same as current interpreter.""" print('\n** running: {}...'.format(script), file=sys.stderr) if not os.access(script, os.R_OK | os.X_OK): - print('ERROR: cannot read and execute: {!r}'.format(script), + print( + 'ERROR: cannot read and execute: {!r}'.format(script), file=sys.stderr ) - print('Consider running `setup.py test --skip-{}`'.format(opname), + print( + 'Consider running `setup.py test --skip-{}`'.format(opname), file=sys.stderr ) sys.exit(3) @@ -45,6 +51,7 @@ def run_under_same_interpreter(opname, script, args): print('** PASSED: {}\n'.format(script), file=sys.stderr) def run_pyflakes3(): + """Run a round of pyflakes3.""" script = '/usr/bin/pyflakes3' names = [ 'setup.py', @@ -55,6 +62,7 @@ def run_pyflakes3(): class Test(Command): + """Basic sanity checks on our code.""" description = 'run pyflakes3' user_options = [ @@ -72,8 +80,9 @@ def run(self): if not self.skip_flakes: run_pyflakes3() -setup(name='kernelstub', - version='3.1.0', +setup( + name='kernelstub', + version='3.2.0', description='Automatic kernel efistub manager for UEFI', url='https://launchpad.net/kernelstub', author='Ian Santopietro', @@ -85,5 +94,6 @@ def run(self): data_files=[ ('/etc/kernel/postinst.d', ['data/kernel/zz-kernelstub']), ('/etc/initramfs/post-update.d', ['data/initramfs/zz-kernelstub']), - ('/etc/default', ['data/config/kernelstub.SAMPLE'])] - ) + ('/etc/default', ['data/config/kernelstub.SAMPLE']) + ] +) From 596ec5b0b5b58a894a470e86ca5b22b1b22b8c1b Mon Sep 17 00:00:00 2001 From: Ian Santopietro Date: Wed, 28 Nov 2018 17:43:17 -0700 Subject: [PATCH 02/16] Fix runtime issues --- kernelstub/application.py | 3 ++- kernelstub/config.py | 8 ++++---- kernelstub/drive.py | 2 +- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/kernelstub/application.py b/kernelstub/application.py index a553ed1..c9d2d3e 100755 --- a/kernelstub/application.py +++ b/kernelstub/application.py @@ -40,6 +40,7 @@ """ import logging +import logging.handlers as handlers import os from . import drive as Drive @@ -107,7 +108,7 @@ def main(self, args): console_log.setFormatter(stream_fmt) console_log.setLevel(console_level) - file_log = logging.handlers.RotatingFileHandler( + file_log = handlers.RotatingFileHandler( log_file_path, maxBytes=(1048576*5), backupCount=5) file_log.setFormatter(file_fmt) file_log.setLevel(file_level) diff --git a/kernelstub/config.py b/kernelstub/config.py index a7631aa..bd4c1f1 100644 --- a/kernelstub/config.py +++ b/kernelstub/config.py @@ -140,11 +140,11 @@ def update_config(self, config): config['default']['live_mode'] = False if config['user']['config_rev'] < 3: if isinstance(config['user']['kernel_options'], str): - options = self.parse_options(config['user']['kernel_options']) - config['user']['kernel_options'] = options.split() + options = self.parse_options(config['user']['kernel_options'].split()) + config['user']['kernel_options'] = options if isinstance(config['default']['kernel_options'], str): - options = self.parse_options(config['default']['kernel_options']) - config['default']['kernel_options'] = options.split() + options = self.parse_options(config['default']['kernel_options'].split()) + config['default']['kernel_options'] = options config['user']['config_rev'] = 3 config['default']['config_rev'] = 3 return config diff --git a/kernelstub/drive.py b/kernelstub/drive.py index 17fd114..3d25027 100644 --- a/kernelstub/drive.py +++ b/kernelstub/drive.py @@ -110,7 +110,7 @@ def get_drive_dev(self, blockdev): """Returns a block device for the drive partition blockdev is on.""" # Ported from bash, out of @jackpot51's firmware updater efi_name = os.path.basename(blockdev) - efi_sys = os.readlink('/sys/class/block/%s', efi_name) + efi_sys = os.readlink('/sys/class/block/{}'.format(efi_name)) disk_sys = os.path.dirname(efi_sys) disk_name = os.path.basename(disk_sys) self.log.debug('ESP is a partition on /dev/%s', disk_name) From f8392894c528c87e70c8bf6678103a97aae5cdd3 Mon Sep 17 00:00:00 2001 From: Ian Santopietro Date: Thu, 29 Nov 2018 09:47:29 -0700 Subject: [PATCH 03/16] Convert to some fancy new string.format-ing --- kernelstub/application.py | 5 +++-- kernelstub/config.py | 4 ++-- kernelstub/nvram.py | 18 +++++++++--------- kernelstub/opsys.py | 2 +- 4 files changed, 15 insertions(+), 14 deletions(-) diff --git a/kernelstub/application.py b/kernelstub/application.py index c9d2d3e..91edc0e 100755 --- a/kernelstub/application.py +++ b/kernelstub/application.py @@ -225,11 +225,12 @@ def main(self, args): except KeyError: log.exception( 'Malformed configuration! \n' - 'The configuration we got is bad, and we can\'nt continue. ' + 'The configuration we got is bad, and we can\'t continue. ' 'Please check the config files and make sure they are correct. ' 'If you can\'t figure it out, then deleting them should fix ' 'the errors and cause kernelstub to regenerate them from ' - 'Default. \n\n You can use "-vv" to get the configuration used.') + 'Default. \n\n You can use "-vv" to get the configuration used.' + ) log.debug('Configuration we got: \n\n%s', config.print_config()) exit(169) diff --git a/kernelstub/config.py b/kernelstub/config.py index bd4c1f1..a8abcfd 100644 --- a/kernelstub/config.py +++ b/kernelstub/config.py @@ -168,12 +168,12 @@ def parse_options(self, options): while matched is False: try: next_option = options[index + itr] - option = '%s %s' % (option, next_option) + option = '{} {}'.format(option, next_option) options[index + itr] = "" if '"' in next_option: matched = True else: - itr = itr + 1 + itr += 1 except IndexError: matched = True options[index] = option diff --git a/kernelstub/nvram.py b/kernelstub/nvram.py index 35a4457..7039c7f 100644 --- a/kernelstub/nvram.py +++ b/kernelstub/nvram.py @@ -40,7 +40,7 @@ def __init__(self, name, version): self.log = logging.getLogger('kernelstub.NVRAM') self.log.debug('loaded kernelstub.NVRAM') - self.os_label = "%s %s" % (name, version) + self.os_label = "{} {}".format(name, version) self.update() def update(self): @@ -71,7 +71,7 @@ def find_os_entry(self, nvram, os_label): self.os_entry_index = -1 find_index = self.os_entry_index for entry in nvram: - find_index = find_index + 1 + find_index += 1 if os_label in entry: self.os_entry_index = find_index self.log.debug('Entry found! Index: %s', self.os_entry_index) @@ -81,21 +81,21 @@ def find_os_entry(self, nvram, os_label): def add_entry(self, this_os, this_drive, kernel_opts, simulate=False): """Add an entry into the NVRAM.""" self.log.info('Creating NVRAM entry') - device = '/dev/%s' % this_drive.drive_name + device = '/dev/{}'.format(this_drive.drive_name) esp_num = this_drive.esp_num - entry_label = '%s %s' % (this_os.name, this_os.version) - entry_linux = '\\EFI\\%s-%s\\vmlinuz.efi' % (this_os.name, this_drive.root_uuid) - entry_initrd = 'EFI/%s-%s/initrd.img' % (this_os.name, this_drive.root_uuid) + entry_label = '{} {}'.format(this_os.name, this_os.version) + entry_linux = '\\EFI\\{}-{}\\vmlinuz.efi'.format(this_os.name, this_drive.root_uuid) + entry_initrd = 'EFI/{}-{}/initrd.img'.format(this_os.name, this_drive.root_uuid) command = [ '/usr/bin/sudo', 'efibootmgr', '-c', '-d', device, '-p', esp_num, - '-L', '%s' % entry_label, - '-l', '%s' % entry_linux, + '-L', '{}'.format(entry_label), + '-l', '{}'.format(entry_linux), '-u', - 'initrd=%s %s' % (entry_initrd, kernel_opts) + 'initrd={} {}'.format(entry_initrd, kernel_opts) ] self.log.debug('NVRAM command:\n%s', command) if not simulate: diff --git a/kernelstub/opsys.py b/kernelstub/opsys.py index 411f662..5fa98d0 100644 --- a/kernelstub/opsys.py +++ b/kernelstub/opsys.py @@ -139,7 +139,7 @@ def get_os_release(self): with open('/etc/os-release') as os_release_file: os_release = os_release_file.readlines() except FileNotFoundError: - os_release = ['NAME="%s"\n' % self.name, + os_release = ['NAME="{}"\n'.format(self.name), 'ID=linux\n', 'ID_LIKE=linux\n', 'VERSION_ID="%s"\n' % self.version] From 65926c4a2c20e604750af59abf822f3e1bf00dea Mon Sep 17 00:00:00 2001 From: Ian Santopietro Date: Thu, 29 Nov 2018 10:23:49 -0700 Subject: [PATCH 04/16] Remove Simulate option This option wasn't really useful because kernelstub still did things when it was used. It's been official deprecated since version 2.0 and has been removed from the online and other documentation already, so this finally removes it from the program. --- kernelstub/application.py | 17 +++----- kernelstub/installer.py | 81 +++++++++++++++------------------------ kernelstub/nvram.py | 50 ++++++++++++------------ kernelstub/opsys.py | 10 +++-- 4 files changed, 65 insertions(+), 93 deletions(-) diff --git a/kernelstub/application.py b/kernelstub/application.py index 91edc0e..942e450 100755 --- a/kernelstub/application.py +++ b/kernelstub/application.py @@ -65,7 +65,7 @@ class Kernelstub(): def mktable(self, data, padding): """ - Makes a table from a dictionary. + Makes a printable table from a dictionary. returns: a str containing the table. """ @@ -126,11 +126,6 @@ def main(self, args): log.debug('Got command line options: %s', args) - # Figure out runtime options - no_run = False - if args.dry_run: - no_run = True - config = Config.Config() configuration = config.config['user'] @@ -327,13 +322,11 @@ def main(self, args): installer.setup_kernel( kopts, setup_loader=setup_loader, - overwrite=force, - simulate=no_run) + overwrite=force) try: installer.backup_old( kopts, - setup_loader=setup_loader, - simulate=no_run) + setup_loader=setup_loader) except Exception as e_e: log.debug( 'Couldn\'t back up old kernel. \nThis might just mean you ' @@ -342,10 +335,10 @@ def main(self, args): ) log.debug(e_e) - installer.copy_cmdline(simulate=no_run) + installer.copy_cmdline() if not manage_mode: - installer.setup_stub(kopts, simulate=no_run) + installer.setup_stub(kopts) log.debug('Saving configuration to file') diff --git a/kernelstub/installer.py b/kernelstub/installer.py index 5d880f1..fd64e07 100644 --- a/kernelstub/installer.py +++ b/kernelstub/installer.py @@ -64,7 +64,7 @@ def __init__(self, nvram, opsys, drive): os.makedirs(self.entry_dir) - def backup_old(self, kernel_opts, setup_loader=False, simulate=False): + def backup_old(self, kernel_opts, setup_loader=False): """Copy the previous kernel (if present) into the ESP.""" self.log.info('Backing up old kernel') @@ -73,8 +73,7 @@ def backup_old(self, kernel_opts, setup_loader=False, simulate=False): try: self.copy_files( '{}.old'.format(self.opsys.kernel_path), - kernel_dest, - simulate=simulate) + kernel_dest) except OSError: self.log.debug( 'Couldn\'t back up old kernel. There\'s probably only one ' @@ -87,8 +86,7 @@ def backup_old(self, kernel_opts, setup_loader=False, simulate=False): try: self.copy_files( '{}.old'.format(self.opsys.initrd_path), - initrd_dest, - simulate=simulate) + initrd_dest) except OSError: self.log.debug( 'Couldn\'t back up old kernel. There\'s probably only one ' @@ -115,20 +113,19 @@ def backup_old(self, kernel_opts, setup_loader=False, simulate=False): kernel_opts, os.path.join(self.entry_dir, '{}-oldkern'.format(self.opsys.name))) - def setup_kernel(self, kernel_opts, setup_loader=False, overwrite=False, simulate=False): + def setup_kernel(self, kernel_opts, setup_loader=False, overwrite=False): """Copy the active kernel into the ESP.""" self.log.info('Copying Kernel into ESP') self.kernel_dest = os.path.join( self.os_folder, "{}.efi".format(self.opsys.kernel_name)) - self.ensure_dir(self.os_folder, simulate=simulate) + self.ensure_dir(self.os_folder) self.log.debug('kernel being copied to %s', self.kernel_dest) try: self.copy_files( self.opsys.kernel_path, - self.kernel_dest, - simulate=simulate) + self.kernel_dest) except FileOpsError as e_e: self.log.exception( @@ -144,8 +141,7 @@ def setup_kernel(self, kernel_opts, setup_loader=False, overwrite=False, simulat try: self.copy_files( self.opsys.initrd_path, - self.initrd_dest, - simulate=simulate) + self.initrd_dest) except FileOpsError as e_e: self.log.exception( @@ -170,17 +166,6 @@ def setup_kernel(self, kernel_opts, setup_loader=False, overwrite=False, simulat self.drive.root_uuid, self.opsys.initrd_name ) - if simulate: - self.log.info("Simulate creation of entry...") - self.log.info( - 'Loader entry: %s/%s-current\n' - 'title %s\n' - 'linux %s\n' - 'initrd %s\n' - 'options %s\n', self.entry_dir, self.opsys.name, - self.opsys.name_pretty, linux_line, initrd_line, kernel_opts - ) - return 0 if not overwrite: if not os.path.exists('{}/loader.conf'.format(self.loader_dir)): @@ -203,30 +188,29 @@ def setup_kernel(self, kernel_opts, setup_loader=False, overwrite=False, simulat kernel_opts, os.path.join(self.entry_dir, '{}-current'.format(self.opsys.name))) - def setup_stub(self, kernel_opts, simulate=False): + def setup_stub(self, kernel_opts): """Set up the kernel efistub bootloader.""" self.log.info("Setting up Kernel EFISTUB loader...") - self.copy_cmdline(simulate=simulate) + self.copy_cmdline() self.nvram.update() if self.nvram.os_entry_index >= 0: self.log.info("Deleting old boot entry") - self.nvram.delete_boot_entry(self.nvram.order_num, simulate) + self.nvram.delete_boot_entry(self.nvram.order_num) else: self.log.debug("No old entry found, skipping removal.") - self.nvram.add_entry(self.opsys, self.drive, kernel_opts, simulate) + self.nvram.add_entry(self.opsys, self.drive, kernel_opts) self.nvram.update() nvram_lines = "\n".join(self.nvram.nvram) self.log.info('NVRAM configured, new values: \n\n%s\n', nvram_lines) - def copy_cmdline(self, simulate): + def copy_cmdline(self): """Copy the current boot options into the ESP.""" self.copy_files( '/proc/cmdline', - self.os_folder, - simulate=simulate + self.os_folder ) @@ -240,28 +224,23 @@ def make_loader_entry(self, title, linux, initrd, options, filename): entry.write('options {}\n'.format(options)) self.log.debug('Entry created!') - def ensure_dir(self, directory, simulate=False): + def ensure_dir(self, directory): """Ensure that a folder exists.""" - if not simulate: - try: - os.makedirs(directory, exist_ok=True) - return True - except Exception as e_e: - self.log.exception('Couldn\'t make sure %s exists.', directory) - self.log.debug(e_e) - return False - - def copy_files(self, src, dest, simulate): + try: + os.makedirs(directory, exist_ok=True) + return True + except Exception as e_e: + self.log.exception('Couldn\'t make sure %s exists.', directory) + self.log.debug(e_e) + return False + + def copy_files(self, src, dest): """Copy src into dest.""" - if simulate: - self.log.info('Simulate copying: %s => %s', src, dest) + try: + self.log.debug('Copying: %s => %s', src, dest) + shutil.copy(src, dest) return True - else: - try: - self.log.debug('Copying: %s => %s', src, dest) - shutil.copy(src, dest) - return True - except Exception as e_e: - self.log.debug(e_e) - raise FileOpsError("Could not copy one or more files.") - return False + except Exception as e_e: + self.log.debug(e_e) + raise FileOpsError("Could not copy one or more files.") + return False diff --git a/kernelstub/nvram.py b/kernelstub/nvram.py index 7039c7f..e1f2c5f 100644 --- a/kernelstub/nvram.py +++ b/kernelstub/nvram.py @@ -78,7 +78,7 @@ def find_os_entry(self, nvram, os_label): return find_index - def add_entry(self, this_os, this_drive, kernel_opts, simulate=False): + def add_entry(self, this_os, this_drive, kernel_opts): """Add an entry into the NVRAM.""" self.log.info('Creating NVRAM entry') device = '/dev/{}'.format(this_drive.drive_name) @@ -98,22 +98,21 @@ def add_entry(self, this_os, this_drive, kernel_opts, simulate=False): 'initrd={} {}'.format(entry_initrd, kernel_opts) ] self.log.debug('NVRAM command:\n%s', command) - if not simulate: - try: - subprocess.run(command) - except subprocess.SubprocessError as e_e: - self.log.exception( - 'Couldn\'t create boot entry for kernel! This means that ' - 'the system will not boot from the new kernel directly. Do ' - 'NOT reboot without an alternate bootloader configured or ' - 'fixing this problem. More information is available in the ' - 'log or by running again with -vv' - ) - self.log.debug(e_e) - exit(172) + try: + subprocess.run(command) + except subprocess.SubprocessError as e_e: + self.log.exception( + 'Couldn\'t create boot entry for kernel! This means that ' + 'the system will not boot from the new kernel directly. Do ' + 'NOT reboot without an alternate bootloader configured or ' + 'fixing this problem. More information is available in the ' + 'log or by running again with -vv' + ) + self.log.debug(e_e) + exit(172) self.update() - def delete_boot_entry(self, index, simulate): + def delete_boot_entry(self, index): """Delete an entry from the NVRAM.""" self.log.info('Deleting old boot entry: %s', index) command = ['/usr/bin/sudo', @@ -121,15 +120,14 @@ def delete_boot_entry(self, index, simulate): '-B', '-b', str(index)] self.log.debug('NVRAM command:\n%s', command) - if not simulate: - try: - subprocess.run(command) - except Exception as e_e: - self.log.exception( - 'Couldn\'t delete old boot entry %s. This could cause ' - 'problems, so kernelstub will not continue. Check again ' - 'with -vv for more info.', index - ) - self.log.debug(e_e) - exit(173) + try: + subprocess.run(command) + except Exception as e_e: + self.log.exception( + 'Couldn\'t delete old boot entry %s. This could cause ' + 'problems, so kernelstub will not continue. Check again ' + 'with -vv for more info.', index + ) + self.log.debug(e_e) + exit(173) self.update() diff --git a/kernelstub/opsys.py b/kernelstub/opsys.py index 5fa98d0..7e380bc 100644 --- a/kernelstub/opsys.py +++ b/kernelstub/opsys.py @@ -139,9 +139,11 @@ def get_os_release(self): with open('/etc/os-release') as os_release_file: os_release = os_release_file.readlines() except FileNotFoundError: - os_release = ['NAME="{}"\n'.format(self.name), - 'ID=linux\n', - 'ID_LIKE=linux\n', - 'VERSION_ID="%s"\n' % self.version] + os_release = [ + 'NAME="{}"\n'.format(self.name), + 'ID=linux\n', + 'ID_LIKE=linux\n', + 'VERSION_ID="{}"\n'.format(self.version) + ] return os_release From 39f76724c6bc648dd2b64c697fdc3ed9dc524e42 Mon Sep 17 00:00:00 2001 From: Ian Santopietro Date: Thu, 29 Nov 2018 10:33:54 -0700 Subject: [PATCH 05/16] Setup simulate option to silently exit. We don't want to flat-out remove the command line parameter without potentially informing scripts. For now, we will print a warning message and silently exit successfully without taking action. In the future, we will exit with non-zero status. Following that, we'll remove the option entirely. --- bin/kernelstub | 2 +- kernelstub/application.py | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/bin/kernelstub b/bin/kernelstub index 7cec998..15bfa29 100644 --- a/bin/kernelstub +++ b/bin/kernelstub @@ -58,7 +58,7 @@ def main(options=None): '--dry-run', action='store_true', dest='dry_run', - help='Don\'t perform any actions, just simulate them.' + help=argparse.SUPPRESS ) parser.add_argument( '-p', diff --git a/kernelstub/application.py b/kernelstub/application.py index 942e450..6402a6d 100755 --- a/kernelstub/application.py +++ b/kernelstub/application.py @@ -124,8 +124,19 @@ def main(self, args): log.setLevel(logging.DEBUG) + # Figure out our command line options. log.debug('Got command line options: %s', args) + if args.dry_run: + log.warning( + 'DEPRECATED!\n\n' + 'The simulate or dry-run option has been removed from ' + 'kernelstub and no longer functions. This will be removed in a ' + 'future version. Since you likely intend no action, we will now ' + 'exit.' + ) + exit() + config = Config.Config() configuration = config.config['user'] From 050409b8823b0367c6875b369764a937e554798b Mon Sep 17 00:00:00 2001 From: Ian Santopietro Date: Thu, 29 Nov 2018 10:35:37 -0700 Subject: [PATCH 06/16] Update documentation --- README.md | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 519644f..6b138d5 100644 --- a/README.md +++ b/README.md @@ -122,21 +122,22 @@ If kernelstub is going to be used in a scripted environment, it is useful to know what return codes it provides in the event of errors. The table below details these codes and their meaning: -| Exit Code | Meaning | -|-----------|--------------------------------------------------------------| -| 0 | Success | -| 166 | The kernel path supplied/detected was invalid | -| 167 | The initrd path supplied/detected was invalid | -| 168 | No kernel options found/supplied | -| 169 | Malformed configuration found | -| 170 | Couldn't copy kernel image to ESP | -| 171 | Couldn't copy initrd image to ESP | -| 172 | Couldn't create a new NVRAM entry | -| 173 | Couldn't remove an old NVRAM entry | -| 174 | Couldn't detect the block device file for the root partition | -| 175 | Coundn't detect the block device file for the ESP | -| 176 | Wasn't run as root | -| 177 | Couldn't get a required UUID | +| Exit Code | Meaning | +|-----------|---------------------------------------------------------------| +| 0 | Success | +| 166 | The kernel path supplied/detected was invalid | +| 167 | The initrd path supplied/detected was invalid | +| 168 | No kernel options found/supplied | +| 169 | Malformed configuration found | +| 170 | Couldn't copy kernel image to ESP | +| 171 | Couldn't copy initrd image to ESP | +| 172 | Couldn't create a new NVRAM entry | +| 173 | Couldn't remove an old NVRAM entry | +| 174 | Couldn't detect the block device file for the root partition | +| 175 | Coundn't detect the block device file for the ESP | +| 176 | Wasn't run as root | +| 177 | Couldn't get a required UUID | +| 178 | Simulate option used | ### Licence From 85898f3479b28babff2bfdac71c83609ca4c7dda Mon Sep 17 00:00:00 2001 From: Ian Santopietro Date: Thu, 29 Nov 2018 10:36:46 -0700 Subject: [PATCH 07/16] Missed a documentation notice. --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 6b138d5..a5d970c 100644 --- a/README.md +++ b/README.md @@ -66,7 +66,6 @@ There are other options as well, as detailed below: | Option | Action | |-------------------------------------------|--------------------------------------------------------| |`-h`, `--help` | Display the help Text | -|`-c`, `--dry-run` | Don't actually copy any files or set anything up. | |`-p`, `--print-config` | Print the current configuration and exit. | |*_Path Options_* | | |`-r `, `--root-path ` | Manually specify the root filesystem path. | From 1e959c86c17fd0062faabc9f73469bd9ca5fb7d4 Mon Sep 17 00:00:00 2001 From: Ian Santopietro Date: Thu, 29 Nov 2018 16:58:36 -0700 Subject: [PATCH 08/16] Add Manpage --- data/kernelstub.man | 333 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 333 insertions(+) create mode 100644 data/kernelstub.man diff --git a/data/kernelstub.man b/data/kernelstub.man new file mode 100644 index 0000000..d8738da --- /dev/null +++ b/data/kernelstub.man @@ -0,0 +1,333 @@ +.TH KERNELSTUB "1" + +.\" To view this file while editing, run it through groff: +.\" groff -Tascii -man python.man | less + +.SH NAME +kernelstub \- The automatic Linux kernel EFIstub manager +.SH SYNOPSIS +.B kernelstub +[ +.B \-e +.I esp-path +] +[ +.B \-r +.I root-fs-path +] +[ +.B \-k +.I kernel-image-path +] +.br + [ +.B \-i +.I initrd-image-path +] +[ +.B \-o +.I "kernel-options" +] +.br + [ +.B \-a +.I "kernel-options" +] +[ +.B \-d +.I "kernel-options" +] +.br + [ +.B \-g +.I log-file-path +] +[ +.B \-h +] +[ +.B \-p +] +[ +.B \-f +] +[ +.B \-l +| +.B \-n +] +.br + [ +.B \-s +| +.B \-m +] +[ +.B \-v... +] + +.SH DESCRIPTION +Kernelstub is a program to setup and configure booting without a traditional +bootloader. +It can configure booting through systemd-boot compatible loader files, or +through the kernel's built-in efi stub loader. +It also runs automatically when the kernel is updated to keep this +configuration up to date. +.br +It supports adding/setting different kernel command line options as well as +keeping older kernels available for use as a backup. +It can operate on an ESP and root partition which are different from the one +the system is currently booted from. +.SH COMMAND LINE OPTIONS +.TP +.BI "\-e, --esp-path " path +Manually setting the path to the EFI System Partition. +This value is saved into the configuration. +Default is /boot/efi. +.TP +.BI "\-r, --root-path " path +Manually specify the path to the root partition, in case kernelstub is being +run on a system other than the target. +Defaults to /. +.TP +.BI "\-k, --kernel-path " path +Specify the location of the Linux kernel image/vmlinuz. +Defaults to /vmlinuz. +.TP +.BI "\-i, --initrd-path " path +Specify the location of the initrd image. +Defaults to /inird.img. +.TP +.BI "\-o, --options " 'options' +Specify the kernel cmdline options/boot flags. +This will overwrite any existing options in the configuration. +Use single or double quotes to surround multiple options. +This option will save the specified changes into the configuration. +.br +If you want to add/remove options from the existing +configuration, see +.B \-a +and +.B \-d +.TP +.BI "\-a, --add-options " 'options' +Add kernel cmdliing options into the existing configuration. +This will avoid adding duplicate items if they're already present. +Use single or double quotes to surround multiple options. +This option will save the specified changes into the configuration. +.TP +.BI "\-d, --delete-options " 'options' +Remove existing kernel cmdline options from the existing configuration. +If an option specified here is not present in the list of options, kernelstub +will silently ignore it. +Use single or double quotes to surround multiple options. +This option will save the specified changes into the configuration. +.TP +.BI "\-g, --log-file " 'path' +Specify an alternative log file location. +.TP +.B \-h, --help +Prints the usage information and exits. +.TP +.B \-p, --print-config +Prints the current configuration settings and exits. +.TP +.B \-f, --force-update +Forcibly update the system loader.conf file to set the current OS as the +default. +This may change your system default boot order. +.TP +.B \-l, --loader +Creates a systemd-boot compatible loader entry in ESP/loader/entries for the +current OS. +This option is saved in the configuration. +.TP +.B \-n, --no-loader. +Don't create a loader entry for the current OS. +This option disables the behavior of +.B \-l +and is saved in the configuration. +.TP +.B \-s, --stub +Enables automatic management of the kernel efistub bootloader. +This option is saved in the configuration. +.TP +.B \-m, --manage-only +Disables automatic management of the kernel efistub bootloader. +This option disables the behavior of +.B \-s +and is saved in the configuration. +.TP +.B \-v, --verbose +Make program output more verbose. +Up to two +.B \-v +flags can be used at once (additional flags are ignored). +.TP +.SH FILES +.IP \fI/etc/kernelstub/configuration\fP +Default location of the kernelstub configuration file. +The file is a JSON format file with two main configurations in it; +.I 'default' +and +.I 'user'. +The 'esp_path' key is a string, 'config_rev' is an int, 'kernel_options' is a +list of strings, and all other keys are booleans. +.br +It is highly recommended to use the kernelstub utility to modify the +configuration rather than by editing the configuration file directly. +See the +.B CONFIGURATION +section for more details. +.PP +.IP \fI/etc/default/kernelstub\fP +This is a vendor-supplied file that can contain certain options for individual +OSs or hardware-specific values. +.SH CONFIGURATION +Specific configuration defaults may have been modified by your OS developer or +hardware vendor. +.br +Each kernelstub configuration contains the following keys: +.IP \fIkernel_options\fP +This is a list of strings, with each string being an individual kernel cmdline +option. +.br +Default: ["quiet", "splash"] +.br +Configured using the +.B \-a, -d, +and +.B -o +flags. +.PP +.IP \fIesp_path\fP +String - Points to the path where the EFI System Partition is mounted. +.br +Default: "/boot/efi". +.br +Configured using the +.B \-e +flag. +.PP +.IP \fIsetup_loader\fP +boolean - enables or disables installing the loader entry file. +.br +.I false: +(default) Skips installing a loader entry file. +.br +.I true: +Installs a loader entry file. +.br +Configured using the +.B \-l +/ +.B \-n +flags. +.PP +.IP \fImanage_mode\fP +boolean - toggles between installing the efistub bootloader or using +management-only mode. +.br +.I false: +(default) sets up the Linux kernel built-in efistub bootloader in the system +NVRAM. +.br +.I true: +Skips setting up the built-in efistub bootloader. +.br +Configured using the +.B \-s +/ +.B \-m +flags. +.PP +.IP \fIforce_update\fP +boolean - Forcibly overwrites the main systemd-boot configuration on each +update. +.br +.I false: +(default) Does not automatically modify the systemd-boot configuration to make +the current OS the default. +.br +.I true: +Overwrites the systemd-boot configuration on each update to ensure the current +OS is the default. +.br +This option cannot be enabled from the command line and must be enabled in the +configuration file directly. +This is due to its ability cause the system to lose alternate boot entries. +.PP +.IP \fIlive_mode\fP +boolean - Live mode allows updates on run on the live system without triggering +kernelstub. +When live mode is enabled, kernelstub silently exits successfully +to allow software updates to work without overwriting the current boot +configuration. +If kernelstub is run manually, live mode will be automatically disabled. +.I false: +(default) Disables live mode. +.br +.I true: +Enables live mode. +.PP +.IP \fIconfig_rev\fP +integer - Tells kernelstub what format of configuration to expect. +.br +If this value is lower than the current configuration revision supported by the +code, kernelstub will attempt to automatically migrate the configuration to the +new version. +.PP +.SH BUGS +Please report bugs to https://github.com/isantop/kernelstub/issues +.SH EXAMPLE +To set up the kernel efistub bootloader to be the default boot option +.PP +.RS +\f(CWsudo kernelstub\fP +.RE +.PP +To include some output +.PP +.RS +\f(CWsudo kernelstub \-v\fP +.RE +.PP +To use kernelstub as a manager for systemd-boot configurations +.PP +.RS +\f(CWsudo kernelstub \-vlm\fP +.RE +Note that the l and m flags are only required once; they are saved in the +configuration file. +.PP +To add the "quiet" kernel option and remove the "splash" option: +.PP +.RS +\f(CWsudo kernelstub \-a 'quiet' -d 'splash'\fP +.RE +.PP +If you have lost your boot configuration because another OS overwrote your +setup, you can recover like so +.PP +.RS +\f(CWsudo mount /dev/root_partition /mnt\fP +.br +\f(CWsudo mount /dev/esp_partition /mnt/boot/efi\fP +.br +\f(CWsudo kernelstub \\\fP +\f(CW \--root-partition /mnt \\\fP +\f(CW \--esp-path /mnt/boot/efi \\\fP +\f(CW \--kernel-path /mnt/vmlinuz \\\fP +\f(CW \--initrd-path /mnt/initrd.img \\\fP +\f(CW \--options 'quiet splash' \\\fP +\f(CW \-vslf\fP +.RE +.PP +Adjust your mount commands to correctly mount your root and ESP partitions. +.SH AUTHOR +Ian Santopietro +.SH INTERNET RESOURCES +Main website/git repository: https://github.com/isantop/kernelstub +.br + + From dfce1ebc181fc5bdbf88ecbf3990d00c71b5589e Mon Sep 17 00:00:00 2001 From: Ian Santopietro Date: Thu, 29 Nov 2018 17:13:26 -0700 Subject: [PATCH 09/16] Finish adding manpage to package --- data/{kernelstub.man => kernelstub.1} | 7 +++---- debian/kernelstub.manpages | 1 + 2 files changed, 4 insertions(+), 4 deletions(-) rename data/{kernelstub.man => kernelstub.1} (98%) create mode 100644 debian/kernelstub.manpages diff --git a/data/kernelstub.man b/data/kernelstub.1 similarity index 98% rename from data/kernelstub.man rename to data/kernelstub.1 index d8738da..18c7560 100644 --- a/data/kernelstub.man +++ b/data/kernelstub.1 @@ -1,7 +1,6 @@ .TH KERNELSTUB "1" - .\" To view this file while editing, run it through groff: -.\" groff -Tascii -man python.man | less +.\" groff -Tascii -man kernelstub.manq | less .SH NAME kernelstub \- The automatic Linux kernel EFIstub manager @@ -329,5 +328,5 @@ Ian Santopietro .SH INTERNET RESOURCES Main website/git repository: https://github.com/isantop/kernelstub .br - - +.SH SEE ALSO +efiboomgr(8), systemd-boot(7) diff --git a/debian/kernelstub.manpages b/debian/kernelstub.manpages new file mode 100644 index 0000000..99f5f71 --- /dev/null +++ b/debian/kernelstub.manpages @@ -0,0 +1 @@ +data/kernelstub.1 \ No newline at end of file From d02f2260bfdaaf6f840a3d33276d632f413e3396 Mon Sep 17 00:00:00 2001 From: Ian Santopietro Date: Thu, 29 Nov 2018 17:15:08 -0700 Subject: [PATCH 10/16] Small typo --- data/kernelstub.1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/kernelstub.1 b/data/kernelstub.1 index 18c7560..7dc9cab 100644 --- a/data/kernelstub.1 +++ b/data/kernelstub.1 @@ -1,6 +1,6 @@ .TH KERNELSTUB "1" .\" To view this file while editing, run it through groff: -.\" groff -Tascii -man kernelstub.manq | less +.\" groff -Tascii -man kernelstub.1 | less .SH NAME kernelstub \- The automatic Linux kernel EFIstub manager From 8d6ed881e0a3f0e745bd197771304658af48873c Mon Sep 17 00:00:00 2001 From: Ian Santopietro Date: Fri, 17 May 2019 11:31:52 -0600 Subject: [PATCH 11/16] Add hostname and UUID to entry. This ensures that each entry applies strictly to the installation that created it. Before, if two OSs (possibly of different versions) used kernelstub, whichever one last had kernelstub run would be the one whose entry was written. This change cleans that up a bit by writing the hostname and the UUID into the filename of the entry. This ensures that the entry files are unique to that installation, and provides a human-readable way to select which installation to boot (if the hostname was changed). --- kernelstub/drive.py | 2 ++ kernelstub/installer.py | 26 ++++++++++++++++++++------ kernelstub/opsys.py | 1 + 3 files changed, 23 insertions(+), 6 deletions(-) diff --git a/kernelstub/drive.py b/kernelstub/drive.py index 3d25027..cb7a2d5 100644 --- a/kernelstub/drive.py +++ b/kernelstub/drive.py @@ -63,6 +63,8 @@ def __init__(self, root_path="/", esp_path="/boot/efi"): self.drive_name = self.get_drive_dev(self.esp_fs) self.esp_num = self.esp_fs[-1] self.root_uuid = self.get_uuid(self.root_fs[5:]) + self.uuid_name = self.root_uuid.split('-') + self.uuid_name = self.uuid_name[0] except NoBlockDevError as e_e: self.log.exception( 'Could not find a block device for the a partition. This is a' diff --git a/kernelstub/installer.py b/kernelstub/installer.py index fd64e07..a8ba55b 100644 --- a/kernelstub/installer.py +++ b/kernelstub/installer.py @@ -107,11 +107,18 @@ def backup_old(self, kernel_opts, setup_loader=False): self.opsys.initrd_name ) self.make_loader_entry( - self.opsys.name_pretty, + '{} ({})-previous kernel'.format(self.opsys.name_pretty, self.opsys.hostname), linux_line, initrd_line, kernel_opts, - os.path.join(self.entry_dir, '{}-oldkern'.format(self.opsys.name))) + os.path.join( + self.entry_dir, '{}-{}({})-oldkern'.format( + self.opsys.name, + self.opsys.hostname, + self.drive.uuid_name + ) + ) + ) def setup_kernel(self, kernel_opts, setup_loader=False, overwrite=False): """Copy the active kernel into the ESP.""" @@ -156,12 +163,12 @@ def setup_kernel(self, kernel_opts, setup_loader=False, overwrite=False): if setup_loader: self.log.info('Setting up loader.conf configuration') - linux_line = '/EFI/{}-{}/{}-previous.efi'.format( + linux_line = '/EFI/{}-{}/{}.efi'.format( self.opsys.name, self.drive.root_uuid, self.opsys.kernel_name ) - initrd_line = '/EFI/{}-{}/{}-previous'.format( + initrd_line = '/EFI/{}-{}/{}'.format( self.opsys.name, self.drive.root_uuid, self.opsys.initrd_name @@ -182,11 +189,18 @@ def setup_kernel(self, kernel_opts, setup_loader=False, overwrite=False): self.ensure_dir(self.entry_dir) self.make_loader_entry( - self.opsys.name_pretty, + '{} ({})'.format(self.opsys.name_pretty, self.opsys.hostname), linux_line, initrd_line, kernel_opts, - os.path.join(self.entry_dir, '{}-current'.format(self.opsys.name))) + os.path.join( + self.entry_dir, '{}-{}({})-current'.format( + self.opsys.name, + self.opsys.hostname, + self.drive.uuid_name + ) + ) + ) def setup_stub(self, kernel_opts): """Set up the kernel efistub bootloader.""" diff --git a/kernelstub/opsys.py b/kernelstub/opsys.py index 7e380bc..e06f765 100644 --- a/kernelstub/opsys.py +++ b/kernelstub/opsys.py @@ -37,6 +37,7 @@ class OS(): kernel_name = 'vmlinuz' initrd_name = 'initrd.img' kernel_release = platform.release() + hostname = platform.node() kernel_path = '/vmlinuz' initrd_path = '/initrd.img' From 8bfaa4842afb67cfc161a2e5c826dad64ad05eae Mon Sep 17 00:00:00 2001 From: Ian Santopietro Date: Fri, 17 May 2019 11:42:03 -0600 Subject: [PATCH 12/16] Update changelog --- debian/changelog | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/debian/changelog b/debian/changelog index 530cd91..a199f80 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,7 +1,9 @@ -kernelstub (3.2.0) cosmic; urgency=medium +kernelstub (3.2.0) disco; urgency=medium * Reformatted output * Revisions to make code cleaner and more pythonic + * Adds fix for buggy UUID detection code (#22) + * Adds UUID and hostname to entries for better organization (#23) -- Ian Santopietro Wed, 28 Nov 2018 15:08:31 -0700 From 5f407b84e77954f6e068e010a43a8845a8259230 Mon Sep 17 00:00:00 2001 From: Ian Santopietro Date: Fri, 17 May 2019 11:43:55 -0600 Subject: [PATCH 13/16] Release with correct timestamp --- debian/changelog | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/debian/changelog b/debian/changelog index a199f80..9290e82 100644 --- a/debian/changelog +++ b/debian/changelog @@ -5,7 +5,7 @@ kernelstub (3.2.0) disco; urgency=medium * Adds fix for buggy UUID detection code (#22) * Adds UUID and hostname to entries for better organization (#23) - -- Ian Santopietro Wed, 28 Nov 2018 15:08:31 -0700 + -- Ian Santopietro Fri, 17 May 2019 11:43:49 -0600 kernelstub (3.1.0) bionic; urgency=medium From cb994969f1ec197570c2ab4aa2f38e8d9b08c195 Mon Sep 17 00:00:00 2001 From: Ian Santopietro Date: Fri, 17 May 2019 11:55:02 -0600 Subject: [PATCH 14/16] Formatting Improvements --- kernelstub/installer.py | 32 +++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/kernelstub/installer.py b/kernelstub/installer.py index a8ba55b..2044542 100644 --- a/kernelstub/installer.py +++ b/kernelstub/installer.py @@ -106,17 +106,18 @@ def backup_old(self, kernel_opts, setup_loader=False): self.drive.root_uuid, self.opsys.initrd_name ) + entry_file = '{}-{}({})-oldkern'.format( + self.opsys.name, + self.opsys.hostname, + self.drive.uuid_name + ) self.make_loader_entry( - '{} ({})-previous kernel'.format(self.opsys.name_pretty, self.opsys.hostname), + '{} ({}) - previous kernel'.format(self.opsys.name_pretty, self.opsys.hostname), linux_line, initrd_line, kernel_opts, os.path.join( - self.entry_dir, '{}-{}({})-oldkern'.format( - self.opsys.name, - self.opsys.hostname, - self.drive.uuid_name - ) + self.entry_dir, entry_file ) ) @@ -183,22 +184,27 @@ def setup_kernel(self, kernel_opts, setup_loader=False, overwrite=False): with open( '{}/loader.conf'.format(self.loader_dir), mode='w' ) as loader: - - default_line = 'default {}-current\n'.format(self.opsys.name) + default_name = '{}-{}({})-current'.format( + self.opsys.name, + self.opsys.hostname, + self.drive.uuid_name + ) + default_line = 'default {}\n'.format(default_name) loader.write(default_line) self.ensure_dir(self.entry_dir) + entry_file = '{}-{}({})-current'.format( + self.opsys.name, + self.opsys.hostname, + self.drive.uuid_name + ) self.make_loader_entry( '{} ({})'.format(self.opsys.name_pretty, self.opsys.hostname), linux_line, initrd_line, kernel_opts, os.path.join( - self.entry_dir, '{}-{}({})-current'.format( - self.opsys.name, - self.opsys.hostname, - self.drive.uuid_name - ) + self.entry_dir, entry_file ) ) From 234d34e7a22f0a43834e7def0e399555b10c3833 Mon Sep 17 00:00:00 2001 From: Ian Santopietro Date: Thu, 23 May 2019 10:10:27 -0600 Subject: [PATCH 15/16] Create SECURITY.md --- SECURITY.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 SECURITY.md diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..c3a2ba6 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,15 @@ +# Security Policy + +## Supported Versions +The following versions of Kernelstub currently receive updates for security: + +| Version | Supported | +| ------- | ------------------ | +| 3.2.0 | :white_check_mark: | +| 3.1.x | :white_check_mark: | +| < 3.1 | :x: | + +## Reporting a Vulnerability + +When Filing an issue for a potential security vulnerability, please be sure +to include a `[SECURITY]` tag in the issue title. From 07f7586a9976b77349d341891a2567cae0317a41 Mon Sep 17 00:00:00 2001 From: Andy Kitchen Date: Sat, 23 Feb 2019 11:16:30 +1100 Subject: [PATCH 16/16] Fix bug in path to UUID code with multi-device btrfs This also makes the UUID matching code more robust at the expense of using the external tool `findmnt' This bug is caused by the assumtion that there is a 1-1 correspondence between device files and UUIDs (which is almost always true). Unfortunately BTRFS multi-device filesystems may have multiple devices associated with a single UUID. The old link reading code does not handle this case and fails to work. Ultimately it's probably better to use a upstream maintained tool for computing path -> UUID as it is subtle and not a core problem this tool is trying to solve. Also, `findmnt' is already provided by the `util-linux' package so another package dependency is not added. --- kernelstub/drive.py | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/kernelstub/drive.py b/kernelstub/drive.py index cb7a2d5..623a00d 100644 --- a/kernelstub/drive.py +++ b/kernelstub/drive.py @@ -24,6 +24,7 @@ import os import logging +import subprocess class NoBlockDevError(Exception): """No Block Device Found Exception""" @@ -62,7 +63,7 @@ def __init__(self, root_path="/", esp_path="/boot/efi"): self.esp_fs = self.get_part_dev(self.esp_path) self.drive_name = self.get_drive_dev(self.esp_fs) self.esp_num = self.esp_fs[-1] - self.root_uuid = self.get_uuid(self.root_fs[5:]) + self.root_uuid = self.get_uuid(self.root_path) self.uuid_name = self.root_uuid.split('-') self.uuid_name = self.uuid_name[0] except NoBlockDevError as e_e: @@ -118,15 +119,13 @@ def get_drive_dev(self, blockdev): self.log.debug('ESP is a partition on /dev/%s', disk_name) return disk_name - def get_uuid(self, filesystem): - """Get a UUID for a filesystem.""" - all_uuids = os.listdir('/dev/disk/by-uuid') - self.log.debug('Looking for UUID for %s', filesystem) - self.log.debug('List of UUIDs:\n%s', all_uuids) - - for uuid in all_uuids: - uuid_path = os.path.join('/dev/disk/by-uuid', uuid) - if filesystem in os.path.realpath(uuid_path): - return uuid - - raise UUIDNotFoundError + def get_uuid(self, path): + self.log.debug('Looking for UUID for path %s' % path) + try: + args = ['findmnt', '-n', '-o', 'uuid', '--mountpoint', path] + result = subprocess.run(args, stdout=subprocess.PIPE) + uuid = result.stdout.decode('ASCII') + uuid = uuid.strip() + return uuid + except OSError as e: + raise UUIDNotFoundError from e