From ebfd164015a3c4f62ffb8d2640f4ea1a9bbe9bde Mon Sep 17 00:00:00 2001 From: brent s Date: Wed, 4 Dec 2019 01:48:41 -0500 Subject: [PATCH] locales and console settings are done --- aif.xsd | 43 +++++++++++++-- aif/constants_fallback.py | 1 + aif/system/__init__.py | 3 +- aif/system/console.py | 97 ++++++++++++++++++++++++++++++++++ aif/system/locales.py | 107 ++++++++++++++++++++++++++++++++++++++ aif/system/users.py | 18 ++++++- examples/aif.xml | 10 +++- 7 files changed, 273 insertions(+), 6 deletions(-) create mode 100644 aif/system/console.py diff --git a/aif.xsd b/aif.xsd index b40187f..2417161 100644 --- a/aif.xsd +++ b/aif.xsd @@ -497,6 +497,14 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -967,11 +1007,8 @@ - - - diff --git a/aif/constants_fallback.py b/aif/constants_fallback.py index f9672b4..fc4ae20 100644 --- a/aif/constants_fallback.py +++ b/aif/constants_fallback.py @@ -1,3 +1,4 @@ +import os import re import subprocess # I wish there was a better way to get the supported LUKS ciphers. import uuid diff --git a/aif/system/__init__.py b/aif/system/__init__.py index 56b3ffb..2056488 100644 --- a/aif/system/__init__.py +++ b/aif/system/__init__.py @@ -1,2 +1,3 @@ from . import locales -from . import users \ No newline at end of file +from . import console +from . import users diff --git a/aif/system/console.py b/aif/system/console.py new file mode 100644 index 0000000..56175a2 --- /dev/null +++ b/aif/system/console.py @@ -0,0 +1,97 @@ +import configparser +import io +import os +import pathlib +import re + + +_font_re = re.compile(r'(\.(psfu?|fnt))?(\.gz)?$', re.IGNORECASE) +_kbd_re = re.compile(r'(\.map)?(\.gz)?$') + + +class Font(object): + def __init__(self, font_xml): + self.xml = font_xml + self.settings = {} + if self.xml: + chk = {'FONT': self.xml.find('font'), + 'FONT_MAP': self.xml.find('map'), + 'FONT_UNIMAP': self.xml.find('unicodeMap')} + for setting, xml in chk.items(): + if xml: + self.settings[setting] = xml.text.strip() + + def verify(self, chroot_base = '/'): + if 'FONT' not in self.settings.keys(): + return(None) + fontdir = pathlib.Path(chroot_base).joinpath('usr', 'share', 'kbd', 'consolefonts') + fontnames = [_font_re.sub('', p.stem) for p in fontdir.iterdir() if not p.stem.startswith(('README.', + 'partialfonts', + 'ERRORS'))] + if self.settings['FONT'] not in fontnames: + raise ValueError('console font is not installed on target system') + return(True) + + +class Keyboard(object): + def __init__(self, keyboard_xml): + self.xml = keyboard_xml + self.settings = {} + if self.xml: + chk = {'KEYMAP': self.xml.find('map'), + 'KEYMAP_TOGGLE': self.xml.find('toggle')} + for setting, xml in chk.items(): + if xml: + self.settings[setting] = xml.text.strip() + + def verify(self, chroot_base): + kbdnames = [] + for i in ('KEYMAP', 'KEYMAP_TOGGLE'): + if i in self.settings.keys(): + kbdnames.append(self.settings[i]) + if not kbdnames: + return(None) + keymapdir = os.path.join(chroot_base, 'usr', 'share', 'kbd', 'keymaps') + kbdmaps = [] + for root, dirs, files in os.walk(keymapdir, topdown = True): + if root.endswith('/include'): + dirs[:] = [] + files[:] = [] + continue + for f in files: + if f.endswith('.inc'): + continue + kbdmaps.append(_kbd_re.sub('', f)) + for k in kbdnames: + if k not in kbdmaps: + raise ValueError('keyboard map "{0}" is not installed on target system'.format(k)) + return(True) + + +class Console(object): + def __init__(self, console_xml): + self.xml = console_xml + self._cfg = configparser.ConfigParser() + self._cfg.optionxform(str) + self.keyboard = Keyboard(self.xml.find('keyboard')) + self.font = Font(self.xml.find('text')) + self._cfg['BASE'] = {} + for i in (self.keyboard, self.font): + self._cfg['BASE'].update(i.settings) + + def writeConf(self, chroot_base): + for x in (self.font, self.keyboard): + x.verify(chroot_base) + cfg = os.path.join(chroot_base, 'etc', 'vconsole.conf') + # We have to strip out the section from the ini. + cfgbuf = io.StringIO() + self._cfg.write(cfgbuf, space_around_delimiters = False) + cfgbuf.seek(0, 0) + with open(cfg, 'w') as fh: + for line in cfgbuf.readlines(): + if line.startswith('[BASE]') or line.strip() == '': + continue + fh.write(line) + os.chmod(cfg, 0o0644) + os.chown(cfg, 0, 0) + return() diff --git a/aif/system/locales.py b/aif/system/locales.py index e69de29..4797a59 100644 --- a/aif/system/locales.py +++ b/aif/system/locales.py @@ -0,0 +1,107 @@ +import configparser +import io +import os +import re +import shutil +import subprocess + + +_locale_re = re.compile(r'^(?!#\s|)$') +_locale_def_re = re.compile(r'([^.]*)[^@]*(.*)') + + +class Locale(object): + def __init__(self, locales_xml): + self.xml = locales_xml + self.syslocales = {} + self.userlocales = [] + self.rawlocales = None + self._localevars = configparser.ConfigParser() + self._localevars.optionxform = str + self._localevars['BASE'] = {} + self._initVars() + + def _initVars(self): + for l in self.xml.findall('locale'): + locale = l.text.strip() + self._localevars['BASE'][l.attrib['name'].strip()] = locale + if locale not in self.userlocales: + self.userlocales.append(locale) + if not self.userlocales: + self.userlocales = ['en_US', 'en_US.UTF-8'] + return() + + def _verify(self, chroot_base): + localegen = os.path.join(chroot_base, 'etc', 'locale.gen') # This *should* be brand new. + with open(localegen, 'r') as fh: + self.rawlocales = fh.read().splitlines() + for idx, line in enumerate(self.rawlocales[:]): + if _locale_re.search(line) or line.strip() == '': + continue + locale, charset = line.split() + locale = locale.replace('#', '') + self.syslocales[locale] = charset + if locale in self.userlocales: + # "Uncomment" the locale (self.writeConf() actually writes the change) + self.rawlocales[idx] = '{0} {1}'.format(locale, charset) + userl = set(self.userlocales) + sysl = set(self.syslocales.keys()) + if (userl - sysl): + raise ValueError('non-existent locale specified') + return() + + def writeConf(self, chroot_base): + # We basically recreate locale-gen in python here, more or less. + self._verify(chroot_base) + localegen = os.path.join(chroot_base, 'etc', 'locale.gen') + localedbdir = os.path.join(chroot_base, 'usr', 'lib', 'locale') + localesrcdir = os.path.join(chroot_base, 'usr', 'share', 'i18n') + with open(localegen, 'w') as fh: + fh.write('# Generated by AIF-NG.\n\n') + fh.write('\n'.join(self.rawlocales)) + fh.write('\n') + # If only the locale DB wasn't in a hopelessly binary format. + for root, dirs, files in os.walk(localedbdir): + for f in files: + fpath = os.path.join(root, f) + os.remove(fpath) + for d in dirs: + dpath = os.path.join(root, d) + shutil.rmtree(dpath) + # TODO: logging + for locale in self.userlocales: + lpath = os.path.join(localesrcdir, 'locales', locale) + charset = self.syslocales[locale] + if os.path.isfile(lpath): + ldef_name = locale + else: + ldef_name = _locale_def_re.sub(r'\g<1>\g<2>', locale) + lpath = os.path.join(localesrcdir, 'locales', ldef_name) + env = dict(os.environ).copy() + env['I18NPATH'] = localesrcdir + subprocess.run(['localedef', + '--force', + # These are overridden by a prefix env var. + # '--inputfile={0}'.format(lpath), + # '--charmap={0}'.format(os.path.join(localesrcdir, 'charmaps', charset)), + '--inputfile={0}'.format(ldef_name), + '--charmap={0}'.format(charset), + '--alias-file={0}'.format(os.path.join(chroot_base, + 'usr', 'share', 'locale', 'locale.alias')), + '--prefix={0}'.format(chroot_base), + locale], + env = env) + cfg = os.path.join(chroot_base, 'etc', 'locale.conf') + # And now we write the variables. + # We have to strip out the section from the ini. + cfgbuf = io.StringIO() + self._localevars.write(cfgbuf, space_around_delimiters = False) + cfgbuf.seek(0, 0) + with open(cfg, 'w') as fh: + for line in cfgbuf.readlines(): + if line.startswith('[BASE]') or line.strip() == '': + continue + fh.write(line) + os.chmod(cfg, 0o0644) + os.chown(cfg, 0, 0) + return() diff --git a/aif/system/users.py b/aif/system/users.py index 62c6cce..244d90d 100644 --- a/aif/system/users.py +++ b/aif/system/users.py @@ -6,4 +6,20 @@ import os ## -import passlib # validate password hash/gen hash \ No newline at end of file +import passlib # validate password hash/gen hash + + +class Password(object): + def __init__(self): + pass + + +class RootUser(object): + def __init__(self): + pass + + +class User(object): + def __init__(self): + pass + diff --git a/examples/aif.xml b/examples/aif.xml index 6bbfd30..68c1916 100644 --- a/examples/aif.xml +++ b/examples/aif.xml @@ -169,7 +169,15 @@ en_US.UTF-8 - + + + default8x16 + + + us + + +