From 25e594e6025bc0059f76d98f8553ce403b4a9512 Mon Sep 17 00:00:00 2001 From: brent s Date: Tue, 12 Jan 2021 14:33:17 -0500 Subject: [PATCH] okay. fixed up some things. --- .gitignore | 27 +++++ __init__.py | 4 + _base.py | 151 ++++++++++++++++++++++++ arch.py | 231 +++++++++++++++++++++++++++++++++++++ arch_mirror_ranking.py | 98 ++++++++++++++++ sysresccd.py | 152 ++++++++++++++++++++++++ tpl/arch_grub.conf.j2 | 26 +++++ tpl/sysresccd_grub.conf.j2 | 62 ++++++++++ 8 files changed, 751 insertions(+) create mode 100644 .gitignore create mode 100644 __init__.py create mode 100644 _base.py create mode 100755 arch.py create mode 100644 arch_mirror_ranking.py create mode 100755 sysresccd.py create mode 100644 tpl/arch_grub.conf.j2 create mode 100644 tpl/sysresccd_grub.conf.j2 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ff3b78a --- /dev/null +++ b/.gitignore @@ -0,0 +1,27 @@ +# https://git-scm.com/docs/gitignore +# https://help.github.com/articles/ignoring-files +# Example .gitignore files: https://github.com/github/gitignore +*.bak +screenlog* +*.swp +*.lck +*~ +.~lock.* +.editix +__pycache__/ +*.pyc +*.tar +*.tar.bz2 +*.tar.xz +*.tar.gz +*.tgz +*.txz +*.tbz +*.tbz2 +*.zip +*.run +*.7z +*.rar +*.sqlite3 +*.deb +.idea/ diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..1cb9cf6 --- /dev/null +++ b/__init__.py @@ -0,0 +1,4 @@ +# from . import _base +# from . import arch_mirror_ranking +# from . import arch +# from . import sysresccd diff --git a/_base.py b/_base.py new file mode 100644 index 0000000..c3f1690 --- /dev/null +++ b/_base.py @@ -0,0 +1,151 @@ +import hashlib +import os +import pathlib +import shutil +import subprocess +## +import psutil +import requests + + +class BaseUpdater(object): + _tpl_dir = os.path.join(os.path.dirname(os.path.abspath(os.path.expanduser(__file__))), 'tpl') + _tpl_file = None + _date_fmt = '%a, %d %b %Y %H:%M:%S %z' + + def __init__(self, + dest_dir, + dest_file, + ver_file, + lock_path, + do_grub_cfg, + boot_dir, + grub_cfg, + hash_type + # check_gpg = True, # TODO: GPG sig checking + ): + self.dest_dir = os.path.abspath(os.path.expanduser(dest_dir)) + self.dest_file = dest_file + self.ver_file = ver_file + self.do_grub = bool(do_grub_cfg) + self.boot_dir = os.path.abspath(os.path.expanduser(boot_dir)) + self.grub_cfg = os.path.abspath(os.path.expanduser(grub_cfg)) + self.lckfile = os.path.abspath(os.path.expanduser(lock_path)) + p_dest = pathlib.Path(self.dest_dir) + p_boot = pathlib.Path(self.boot_dir) + self.grub_iso_dir = '/{0}'.format(str(p_dest.relative_to(p_boot))) + self.old_date = None + self.old_ver = None + self.old_hash = None + self.new_date = None + self.new_ver = None + self.new_hash = None + self.do_update = False + self.force_update = False + self.iso_url = None + self.hash_type = hash_type + self.dest_iso = os.path.join(self.dest_dir, self.dest_file) + self.dest_ver = os.path.join(self.dest_dir, self.ver_file) + + def main(self): + if self.getRunning(): + return(None) + self.lock() + if self.do_update or \ + self.force_update or not \ + all((self.old_date, + self.old_ver, + self.old_hash)): + self.do_update = True + self.download() + if self.do_grub: + self.grub() + self.touchVer() + self.unlock() + return(None) + + def download(self): + if self.getRunning(): + return(None) + if not any((self.do_update, self.force_update)): + return(None) + if not self.iso_url: + raise RuntimeError('iso_url attribute must be set first') + req = requests.get(self.iso_url, stream = True, headers = {'User-Agent': 'curl/7.74.0'}) + if not req.ok: + raise RuntimeError('Received non-200/30x {0} for {1}'.format(req.status_code, self.iso_url)) + os.makedirs(os.path.dirname(self.dest_iso), exist_ok = True) + with req as uri: + with open(self.dest_iso, 'wb') as fh: + shutil.copyfileobj(uri.raw, fh) + hasher = hashlib.new(self.hash_type) + with open(self.dest_iso, 'rb') as fh: + hasher.update(fh.read()) + realhash = hasher.hexdigest().lower() + if realhash != self.new_hash: + raise RuntimeError('Hash mismatch: {0} (LOCAL), {1} (REMOTE)'.format(realhash, self.new_hash)) + self.updateVer() + return(None) + + def getCurVer(self): + raise RuntimeError('BaseUpdater should be subclassed and its updateVer, getCurVer, and getNewVer methods ' + 'should be replaced.') + + def getNewVer(self): + raise RuntimeError('BaseUpdater should be subclassed and its updateVer, getCurVer, and getNewVer methods ' + 'should be replaced.') + + def getRunning(self): + if not os.path.isfile(self.lckfile): + return(False) + my_pid = os.getpid() + with open(self.lckfile, 'r') as fh: + pid = int(fh.read().strip()) + if not psutil.pid_exists(pid): + os.remove(self.lckfile) + return(False) + if pid == my_pid: + return(False) + return(True) + + def grub(self): + import jinja2 + loader = jinja2.FileSystemLoader(searchpath = self._tpl_dir) + tplenv = jinja2.Environment(loader = loader) + tpl = tplenv.get_template(self._tpl_file) + with open(self.grub_cfg, 'w') as fh: + fh.write(tpl.render(iso_path = os.path.abspath( + os.path.expanduser( + os.path.join(self.grub_iso_dir, + self.dest_file))))) + cmd = subprocess.run(['grub-mkconfig', + '-o', '{0}/grub/grub.cfg'.format(self.boot_dir)], + stdout = subprocess.PIPE, + stderr = subprocess.PIPE) + if cmd.returncode != 0: + stderr = cmd.stderr.decode('utf-8') + if stderr.strip() != '': + print(stderr) + raise RuntimeError('grub-mkconfig returned a non-zero exit status ({0})'.format(cmd.returncode)) + return(None) + + def lock(self): + with open(self.lckfile, 'w') as fh: + fh.write(str(os.getpid())) + return(None) + + def touchVer(self): + if self.getRunning(): + return(None) + ver_path = pathlib.Path(self.dest_ver) + ver_path.touch(exist_ok = True) + return(None) + + def unlock(self): + if os.path.isfile(self.lckfile): + os.remove(self.lckfile) + return(None) + + def updateVer(self): + raise RuntimeError('BaseUpdater should be subclassed and its updateVer, getCurVer, and getNewVer methods ' + 'should be replaced.') diff --git a/arch.py b/arch.py new file mode 100755 index 0000000..d1a211e --- /dev/null +++ b/arch.py @@ -0,0 +1,231 @@ +#!/usr/bin/env python3 + +# TODO: logging +# Example .arch.json: +# { +# "date": "Fri, 01 Jan 2021 00:00:00 +0000", +# "mirror": "http://arch.mirror.constant.com/", +# "country": "US", +# "notes": "https://archlinux.org/releng/releases/2021.01.01/", +# "ver": "2021.01.01", +# "sha1": "c3082b13d3cf0a253e1322568f2fd07479f86d52" +# } + +import datetime +import json +import hashlib +import os +import pathlib +import re +import shutil +import subprocess +## +import psutil +import requests +from lxml import etree +## +import _base +import arch_mirror_ranking + + +class Updater(_base.BaseUpdater): + _fname_re = re.compile(r'^archlinux-(?P[0-9]{4}\.[0-9]{2}\.[0-9]{2})-(?P(i686|x86_64)).iso$') + _def_hash = 'sha1' + _allowed_hashes = ('md5', 'sha1') + _allowed_arches = ('x86_64', ) + _datever_fmt = '%Y.%m.%d' + _arch = 'x86_64' # Arch Linux proper only offers x86_64. + _iso_dir = 'iso/latest' + _iso_fname = 'archlinux-{ver}-{arch}.iso' + _iso_file = os.path.join(_iso_dir, _iso_fname) + _tpl_file = 'arch_grub.conf.j2' + + def __init__(self, + dest_dir = '/boot/iso', # Should be subdir of boot_dir + dest_file = 'arch.iso', + ver_file = '.arch.json', + lock_path = '/tmp/.arch.lck', + feed_url = 'https://archlinux.org/feeds/releases/', + do_grub_cfg = True, + boot_dir = '/boot', # ESP or boot partition mount; where GRUB files are installed *under* + grub_cfg = '/etc/grub.d/40_custom_arch', + # check_gpg = True, # TODO: GPG sig checking + hash_type = 'sha1'): + super().__init__(dest_dir, dest_file, ver_file, lock_path, do_grub_cfg, boot_dir, grub_cfg, hash_type) + if hash_type.lower() not in self._allowed_hashes: + raise ValueError('hash_type must be one of: {0}'.format(', '.join(self._allowed_hashes))) + else: + self.hash_type = hash_type.lower() + self.feed_url = feed_url + # From the JSON. + self.rel_notes_url = None + self.mirror_base = None + self.country = None + self.ipv4 = True + self.ipv6 = False + self._init_vars() + + def _init_vars(self): + if self.getRunning(): + return(None) + self.getCountry() + self.getNet() + self.getCurVer() + self.getNewVer() + return(None) + + def findMirror(self): + self.getCountry() + if self.mirror_base: + return(None) + mirrors = [] + for p in ('http', 'https'): + m = arch_mirror_ranking.MirrorIdx(country = self.country, + proto = p, + ipv4 = self.ipv4, + ipv6 = self.ipv6, + isos = True) + mirrors.extend(m.ranked_servers) + # Find the "best" across http/https. + mirrors = sorted(mirrors, key = lambda i: i['score']) + for s in mirrors: + try: + req = requests.get(s['url']) + if req.ok: + self.mirror_base = s['url'] + break + except (OSError, ConnectionRefusedError): + continue + return(None) + + def getCountry(self): + if self.country: # The API has limited number of accesses for free. + return(None) + url = 'https://ipinfo.io/country' + req = requests.get(url, headers = {'User-Agent': 'curl/7.74.0'}) + if not req.ok: + raise RuntimeError('Received non-200/30x {0} for {1}'.format(req.status_code, url)) + self.country = req.content.decode('utf-8').strip().upper() + return(None) + + def getCurVer(self): + if self.getRunning(): + return(None) + if not os.path.isfile(self.dest_ver): + self.getCountry() + self.getNet() + self.findMirror() + self.do_update = True + self.force_update = True + self.old_ver = 0.00 + return(None) + with open(self.dest_ver, 'rb') as fh: + ver_info = json.load(fh) + self.old_date = datetime.datetime.strptime(ver_info['date'], self._date_fmt) + self.old_ver = datetime.datetime.strptime(ver_info['ver'], self._datever_fmt) + self.old_hash = ver_info.get(self.hash_type, self._def_hash) + self.country = ver_info.get('country') + self.mirror_base = ver_info.get('mirror') + if not self.country: + self.getCountry() + if not self.mirror_base: + self.getNet() + self.findMirror() + self.new_hash = self.old_hash + self.new_ver = self.old_ver + self.new_date = self.old_date + # if ver_info.get('arch') != self._arch: + # self.do_update = True + # self.force_update = True + try: + hasher = hashlib.new(self.hash_type) + with open(self.dest_iso, 'rb') as fh: + hasher.update(fh.read()) + if self.old_hash != hasher.hexdigest().lower(): + self.do_update = True + self.force_update = True + except FileNotFoundError: + self.do_update = True + self.force_update = True + return(None) + return(None) + + def getNet(self): + for k in ('ipv4', 'ipv6'): + url = 'https://{0}.clientinfo.square-r00t.net'.format(k) + try: + req = requests.get(url) + setattr(self, k, req.json()['ip']) + except OSError: + setattr(self, k, False) + return(None) + + def getNewVer(self): + if self.getRunning(): + return(None) + if not self.mirror_base: + self.findMirror() + req = requests.get(self.feed_url, headers = {'User-Agent': 'curl/7.74.0'}) + if not req.ok: + raise RuntimeError('Received non-200/30x {0} for {1}'.format(req.status_code, self.feed_url)) + feed = etree.fromstring(req.content) + for item in feed.xpath('//item'): + date_xml = item.find('pubDate') + ver_xml = item.find('title') + notes_xml = item.find('link') + date = ver = notes = None + if date_xml is not None: + date = datetime.datetime.strptime(date_xml.text, self._date_fmt) + if ver_xml is not None: + ver = ver_xml.text + if notes_xml is not None: + notes = notes_xml.text + new_ver = datetime.datetime.strptime(ver, self._datever_fmt) + if not all((self.old_ver, self.old_date)) or \ + (new_ver > self.old_ver) or \ + (self.old_date < date): + self.do_update = True + self.new_ver = new_ver + self.new_date = date + self.rel_notes_url = notes + datever = self.new_ver.strftime(self._datever_fmt) + self.iso_url = os.path.join(self.mirror_base, + self._iso_file.lstrip('/')).format(ver = datever, arch = self._arch) + hash_url = os.path.join(self.mirror_base, + self._iso_dir, + '{0}sums.txt'.format(self.hash_type)) + req = requests.get(hash_url, headers = {'User-Agent': 'curl/7.74.0'}) + if not req.ok: + raise RuntimeError('Received non-200/30x {0} for {1}'.format(req.status_code, hash_url)) + hash_lines = req.content.decode('utf-8').strip().splitlines() + tgt_fname = os.path.basename(self.iso_url) + for line in hash_lines: + if line.strip().startswith('#'): + continue + hash_str, fname = line.split() + if fname != tgt_fname: + continue + self.new_hash = hash_str.lower() + break + break + return(None) + + def updateVer(self): + if self.getRunning(): + return(None) + d = {'date': self.new_date.strftime(self._date_fmt), + 'mirror': self.mirror_base, + 'country': self.country, + 'notes': self.rel_notes_url, + 'ver': self.new_ver.strftime(self._datever_fmt), + self.hash_type: self.new_hash} + j = json.dumps(d, indent = 4) + with open(self.dest_ver, 'w') as fh: + fh.write(j) + fh.write('\n') + return(None) + + +if __name__ == '__main__': + u = Updater() + u.main() diff --git a/arch_mirror_ranking.py b/arch_mirror_ranking.py new file mode 100644 index 0000000..f921b26 --- /dev/null +++ b/arch_mirror_ranking.py @@ -0,0 +1,98 @@ +#!/usr/bin/env python3 + +import argparse +import datetime +# import dns # TODO: replace server['ipv4'] with IPv4 address(es)? etc. +import json +import re +import sys +from urllib.request import urlopen +## +import iso3166 + + +servers_json_url = 'https://www.archlinux.org/mirrors/status/json/' +protos = ('http', 'https') + + +class MirrorIdx(object): + def __init__(self, + country = None, + proto = None, + json_url = servers_json_url, + name_re = None, + ipv4 = None, + ipv6 = None, + isos = None): + _tmpargs = locals() + del (_tmpargs['self']) + for k, v in _tmpargs.items(): + setattr(self, k, v) + self.validateParams() + self.servers_json = {} + self.servers = [] + self.servers_with_scores = [] + self.ranked_servers = [] + self.fetchJSON() + self.buildServers() + self.rankServers() + + def fetchJSON(self): + with urlopen(self.json_url) as u: + self.servers_json = json.load(u) + return() + + def buildServers(self): + _limiters = (self.proto, self.ipv4, self.ipv6, self.isos) + _filters = list(_limiters) + _filters.extend([self.name_re, self.country]) + _filters = tuple(_filters) + for s in self.servers_json['urls']: + if not s['active']: + continue + if not any(_filters): + self.servers.append(s.copy()) + if s['score']: + self.servers_with_scores.append(s) + continue + # These are based on string values. + if self.name_re: + if not self.name_re.search(s['url']): + continue + if self.country: + if self.country != s['country_code']: + continue + # These are regular True/False switches + match = False + # We want to be *very* explicit about the ordering and inclusion/exclusion of these. + # They MUST match the order of _limiters. + values = [] + for k in ('protocol', 'ipv4', 'ipv6', 'isos'): + values.append(s[k]) + valid = all([v for k, v in zip(_limiters, values) if k]) + if valid: + self.servers.append(s) + if s['score']: + self.servers_with_scores.append(s) + return() + + def rankServers(self): + self.ranked_servers = sorted(self.servers_with_scores, key = lambda i: i['score']) + return() + + def validateParams(self): + if self.proto and self.proto.lower() not in protos: + err = '{0} must be one of: {1}'.format(self.proto, ', '.join([i.upper() for i in protos])) + raise ValueError(err) + elif self.proto: + self.proto = self.proto.upper() + if self.country and self.country.upper() not in iso3166.countries: + err = ('{0} must be a valid ISO-3166-1 ALPHA-2 country code. ' + 'See https://en.wikipedia.org/wiki/List_of_ISO_3166_country_codes' + '#Current_ISO_3166_country_codes').format(self.country) + raise ValueError() + elif self.country: + self.country = self.country.upper() + if self.name_re: + self.name_re = re.compile(self.name_re) + return() diff --git a/sysresccd.py b/sysresccd.py new file mode 100755 index 0000000..c397613 --- /dev/null +++ b/sysresccd.py @@ -0,0 +1,152 @@ +#!/usr/bin/env python3 + +# TODO: logging +# Example .sysresccd.json: +# { +# "date": "Sun, 22 Nov 2020 18:03:52 +0900", +# "arch": "amd64", +# "ver": 7.01, +# "sha512": "9d8c7e6d5c5f22d42bc20a663(...)" +# } + +import datetime +import json +import hashlib +import os +import re +## +import requests +from lxml import etree +## +import _base + + +class Updater(_base.BaseUpdater): + _fname_re = re.compile(r'^systemrescue-(?P[0-9.]+)-(?P(i686|amd64)).iso$') + _def_hash = 'sha256' + _allowed_hashes = ('sha256', 'sha512') + _allowed_arches = ('i686', 'amd64') + _tpl_file = 'sysresccd_grub.conf.j2' + + def __init__(self, + arch = 'amd64', + dest_dir = '/boot/iso', # Should be subdir of boot_dir + dest_file = 'sysresccd.iso', + ver_file = '.sysresccd.json', + lock_path = '/tmp/.sysresccd.lck', + feed_url = 'https://osdn.net/projects/systemrescuecd/storage/!rss', + do_grub_cfg = True, + boot_dir = '/boot', # ESP or boot partition mount; where GRUB files are installed *under* + grub_cfg = '/etc/grub.d/40_custom_sysresccd', + # check_gpg = True, # TODO: GPG sig checking + hash_type = 'sha512'): + super().__init__(dest_dir, dest_file, ver_file, lock_path, do_grub_cfg, boot_dir, grub_cfg, hash_type) + if arch.lower() not in self._allowed_arches: + raise ValueError('arch must be one of: {0}'.format(', '.join(self._allowed_arches))) + else: + self.arch = arch.lower() + if hash_type.lower() not in self._allowed_hashes: + raise ValueError('hash_type must be one of: {0}'.format(', '.join(self._allowed_hashes))) + else: + self.hash_type = hash_type.lower() + self.feed_url = feed_url + self.dl_base = None + self._init_vars() + + def _init_vars(self): + if self.getRunning(): + return(None) + self.getCurVer() + self.getNewVer() + return(None) + + def getCurVer(self): + if self.getRunning(): + return(None) + if not os.path.isfile(self.dest_ver): + self.do_update = True + self.force_update = True + self.old_ver = 0.00 + return(None) + with open(self.dest_ver, 'rb') as fh: + ver_info = json.load(fh) + self.old_date = datetime.datetime.strptime(ver_info['date'], self._date_fmt) + self.old_ver = ver_info['ver'] + self.old_hash = ver_info.get(self.hash_type, self._def_hash) + self.new_hash = self.old_hash + self.new_ver = self.old_ver + self.new_date = self.old_date + if ver_info.get('arch') != self.arch: + self.do_update = True + self.force_update = True + try: + hasher = hashlib.new(self.hash_type) + with open(self.dest_iso, 'rb') as fh: + hasher.update(fh.read()) + if self.old_hash != hasher.hexdigest().lower(): + self.do_update = True + self.force_update = True + except FileNotFoundError: + self.do_update = True + self.force_update = True + return(None) + return (None) + + def getNewVer(self): + if self.getRunning(): + return(None) + req = requests.get(self.feed_url, headers = {'User-Agent': 'curl/7.74.0'}) + if not req.ok: + raise RuntimeError('Received non-200/30x {0} for {1}'.format(req.status_code, self.feed_url)) + feed = etree.fromstring(req.content) + self.dl_base = feed.xpath('channel/link')[0].text + for item in feed.xpath('//item'): + date_xml = item.find('pubDate') + title_xml = item.find('title') + # link_xml = item.find('link') + date = title = link = None + if date_xml is not None: + date = datetime.datetime.strptime(date_xml.text, self._date_fmt) + if title_xml is not None: + title = title_xml.text + # if link_xml is not None: + # link = link_xml.text + fname_r = self._fname_re.search(os.path.basename(title)) + if not fname_r: + continue + ver_info = fname_r.groupdict() + if ver_info['arch'] != self.arch: + continue + new_ver = float(ver_info.get('version', self.old_ver)) + if not all((self.old_ver, self.old_date)) or \ + (new_ver > self.old_ver) or \ + (self.old_date < date): + self.do_update = True + self.new_ver = new_ver + self.new_date = date + self.iso_url = os.path.join(self.dl_base, title.lstrip('/')) + hash_url = '{0}.{1}'.format(self.iso_url, self.hash_type) + req = requests.get(hash_url, headers = {'User-Agent': 'curl/7.74.0'}) + if not req.ok: + raise RuntimeError('Received non-200/30x {0} for {1}'.format(req.status_code, hash_url)) + self.new_hash = req.content.decode('utf-8').lower().split()[0] + break + return(None) + + def updateVer(self): + if self.getRunning(): + return(None) + d = {'date': self.new_date.strftime(self._date_fmt), + 'arch': self.arch, + 'ver': self.new_ver, + self.hash_type: self.new_hash} + j = json.dumps(d, indent = 4) + with open(self.dest_ver, 'w') as fh: + fh.write(j) + fh.write('\n') + return(None) + + +if __name__ == '__main__': + u = Updater() + u.main() diff --git a/tpl/arch_grub.conf.j2 b/tpl/arch_grub.conf.j2 new file mode 100644 index 0000000..634171e --- /dev/null +++ b/tpl/arch_grub.conf.j2 @@ -0,0 +1,26 @@ +#!/bin/sh +# Copy this file to /etc/grub.d/40_custom_arch with mode 0755 and run grub-mkconfig -o /boot/grub/grub.cfg +exec tail -n +3 $0 +# Arch ISO +# https://wiki.archlinux.org/index.php/Multiboot_USB_drive#Arch_Linux_monthly_release +submenu 'Arch Install ISO' { + + menuentry 'Default Options' { + set isofile='{{ iso_path }}' + probe -u $root --set=imgdevuuid + set imgdevpath="/dev/disk/by-uuid/$imgdevuuid" + loopback loop $isofile + linux (loop)/arch/boot/x86_64/vmlinuz-linux archisobasedir=arch img_dev=$imgdevpath img_loop=$isofile earlymodules=loop + initrd (loop)/arch/boot/intel-ucode.img (loop)/arch/boot/amd-ucode.img (loop)/arch/boot/x86_64/initramfs-linux.img + } + + menuentry 'Accessibility mode' { + set isofile='{{ iso_path }}' + probe -u $root --set=imgdevuuid + set imgdevpath="/dev/disk/by-uuid/$imgdevuuid" + loopback loop $isofile + linux (loop)/arch/boot/x86_64/vmlinuz-linux archisobasedir=arch img_dev=$imgdevpath img_loop=$isofile earlymodules=loop accessibility=on + initrd (loop)/arch/boot/intel-ucode.img (loop)/arch/boot/amd-ucode.img (loop)/arch/boot/x86_64/initramfs-linux.img + } + +} diff --git a/tpl/sysresccd_grub.conf.j2 b/tpl/sysresccd_grub.conf.j2 new file mode 100644 index 0000000..3e8692f --- /dev/null +++ b/tpl/sysresccd_grub.conf.j2 @@ -0,0 +1,62 @@ +#!/bin/sh +# Copy this file to /etc/grub.d/40_custom_sysresccd with mode 0755 and run grub-mkconfig -o /boot/grub/grub.cfg +exec tail -n +3 $0 +# System Rescue CD +# https://www.system-rescue.org/ +submenu 'System Rescue CD' { + + menuentry 'Default options' { + set isofile='{{ iso_path }}' + probe -u $root --set=imgdevuuid + set imgdevpath="/dev/disk/by-uuid/$imgdevuuid" + loopback loop $isofile + linux (loop)/sysresccd/boot/x86_64/vmlinuz archisobasedir=sysresccd img_dev=$imgdevpath img_loop=$isofile earlymodules=loop + initrd (loop)/sysresccd/boot/intel_ucode.img (loop)/sysresccd/boot/amd_ucode.img (loop)/sysresccd/boot/x86_64/sysresccd.img + } + + menuentry 'Run from RAM' { + set isofile='{{ iso_path }}' + probe -u $root --set=imgdevuuid + set imgdevpath="/dev/disk/by-uuid/$imgdevuuid" + loopback loop $isofile + linux (loop)/sysresccd/boot/x86_64/vmlinuz archisobasedir=sysresccd img_dev=$imgdevpath img_loop=$isofile earlymodules=loop copytoram + initrd (loop)/sysresccd/boot/intel_ucode.img (loop)/sysresccd/boot/amd_ucode.img (loop)/sysresccd/boot/x86_64/sysresccd.img + } + + menuentry 'Confirm/verify checksum' { + set isofile='{{ iso_path }}' + probe -u $root --set=imgdevuuid + set imgdevpath="/dev/disk/by-uuid/$imgdevuuid" + loopback loop $isofile + linux (loop)/sysresccd/boot/x86_64/vmlinuz archisobasedir=sysresccd img_dev=$imgdevpath img_loop=$isofile earlymodules=loop checksum + initrd (loop)/sysresccd/boot/intel_ucode.img (loop)/sysresccd/boot/amd_ucode.img (loop)/sysresccd/boot/x86_64/sysresccd.img + } + + menuentry 'Use basic display drivers' { + set isofile='{{ iso_path }}' + probe -u $root --set=imgdevuuid + set imgdevpath="/dev/disk/by-uuid/$imgdevuuid" + loopback loop $isofile + linux (loop)/sysresccd/boot/x86_64/vmlinuz archisobasedir=sysresccd img_dev=$imgdevpath img_loop=$isofile earlymodules=loop nomodeset + initrd (loop)/sysresccd/boot/intel_ucode.img (loop)/sysresccd/boot/amd_ucode.img (loop)/sysresccd/boot/x86_64/sysresccd.img + } + + menuentry 'Find and boot a locally installed Linux' { + set isofile='{{ iso_path }}' + probe -u $root --set=imgdevuuid + set imgdevpath="/dev/disk/by-uuid/$imgdevuuid" + loopback loop $isofile + linux (loop)/sysresccd/boot/x86_64/vmlinuz archisobasedir=sysresccd img_dev=$imgdevpath img_loop=$isofile earlymodules=loop findroot + initrd (loop)/sysresccd/boot/intel_ucode.img (loop)/sysresccd/boot/amd_ucode.img (loop)/sysresccd/boot/x86_64/sysresccd.img + } + + menuentry 'Stop during boot before the root filesystem is mounted' { + set isofile='{{ iso_path }}' + probe -u $root --set=imgdevuuid + set imgdevpath="/dev/disk/by-uuid/$imgdevuuid" + loopback loop $isofile + linux (loop)/sysresccd/boot/x86_64/vmlinuz archisobasedir=sysresccd img_dev=$imgdevpath img_loop=$isofile earlymodules=loop break + initrd (loop)/sysresccd/boot/intel_ucode.img (loop)/sysresccd/boot/amd_ucode.img (loop)/sysresccd/boot/x86_64/sysresccd.img + } + +}