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
+
+
+
+
+
+