locales and console settings are done
This commit is contained in:
parent
7bd704b284
commit
ebfd164015
43
aif.xsd
43
aif.xsd
@ -497,6 +497,14 @@
|
|||||||
</xs:restriction>
|
</xs:restriction>
|
||||||
</xs:simpleType>
|
</xs:simpleType>
|
||||||
|
|
||||||
|
<xs:simpleType name="t_pageformats">
|
||||||
|
<xs:restriction base="xs:positiveInteger">
|
||||||
|
<xs:enumeration value="8"/>
|
||||||
|
<xs:enumeration value="14"/>
|
||||||
|
<xs:enumeration value="16"/>
|
||||||
|
</xs:restriction>
|
||||||
|
</xs:simpleType>
|
||||||
|
|
||||||
<xs:simpleType name="t_bootloaders">
|
<xs:simpleType name="t_bootloaders">
|
||||||
<!-- TODO: expand?
|
<!-- TODO: expand?
|
||||||
https://wiki.archlinux.org/index.php/Category:Boot_loaders
|
https://wiki.archlinux.org/index.php/Category:Boot_loaders
|
||||||
@ -908,6 +916,38 @@
|
|||||||
</xs:sequence>
|
</xs:sequence>
|
||||||
</xs:complexType>
|
</xs:complexType>
|
||||||
</xs:element>
|
</xs:element>
|
||||||
|
<xs:element name="console" minOccurs="0" maxOccurs="1">
|
||||||
|
<!-- vconsole.conf(5) -->
|
||||||
|
<!-- timezone and kbd/xkbd are validated in-code. -->
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:all minOccurs="1">
|
||||||
|
<!-- These are validated in-code -->
|
||||||
|
<xs:element name="keyboard" minOccurs="1" maxOccurs="1">
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:choice>
|
||||||
|
<xs:element name="map" minOccurs="0" maxOccurs="1"
|
||||||
|
type="aif:t_nonempty" default="us"/>
|
||||||
|
<xs:element name="toggle" minOccurs="0" maxOccurs="1"
|
||||||
|
type="aif:t_nonempty"/>
|
||||||
|
|
||||||
|
</xs:choice>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="text" minOccurs="0" maxOccurs="1">
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:choice minOccurs="1" maxOccurs="3">
|
||||||
|
<xs:element name="font" minOccurs="0" maxOccurs="1"
|
||||||
|
type="aif:t_nonempty"/>
|
||||||
|
<xs:element name="map" minOccurs="0" maxOccurs="1"
|
||||||
|
type="aif:t_nonempty"/>
|
||||||
|
<xs:element name="unicodeMap" minOccurs="0" maxOccurs="1"
|
||||||
|
type="aif:t_nonempty"/>
|
||||||
|
</xs:choice>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
</xs:all>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
<xs:element name="users" minOccurs="0" maxOccurs="1">
|
<xs:element name="users" minOccurs="0" maxOccurs="1">
|
||||||
<xs:complexType>
|
<xs:complexType>
|
||||||
<xs:sequence>
|
<xs:sequence>
|
||||||
@ -967,11 +1007,8 @@
|
|||||||
</xs:complexType>
|
</xs:complexType>
|
||||||
</xs:element>
|
</xs:element>
|
||||||
</xs:all>
|
</xs:all>
|
||||||
<!-- timezone and kbd/xkbd are validated in-code. -->
|
|
||||||
<xs:attribute name="timezone" type="aif:t_nonempty" use="required"/>
|
<xs:attribute name="timezone" type="aif:t_nonempty" use="required"/>
|
||||||
<xs:attribute name="chrootPath" type="aif:t_filepath" use="required"/>
|
<xs:attribute name="chrootPath" type="aif:t_filepath" use="required"/>
|
||||||
<xs:attribute name="kbd" type="aif:t_nonempty" use="optional" default="us"/>
|
|
||||||
<xs:attribute name="xkbd" type="aif:t_nonempty" use="optional" default="us"/>
|
|
||||||
<xs:attribute name="reboot" type="xs:boolean" use="optional" default="0"/>
|
<xs:attribute name="reboot" type="xs:boolean" use="optional" default="0"/>
|
||||||
</xs:complexType>
|
</xs:complexType>
|
||||||
</xs:element>
|
</xs:element>
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import os
|
||||||
import re
|
import re
|
||||||
import subprocess # I wish there was a better way to get the supported LUKS ciphers.
|
import subprocess # I wish there was a better way to get the supported LUKS ciphers.
|
||||||
import uuid
|
import uuid
|
||||||
|
@ -1,2 +1,3 @@
|
|||||||
from . import locales
|
from . import locales
|
||||||
from . import users
|
from . import console
|
||||||
|
from . import users
|
||||||
|
97
aif/system/console.py
Normal file
97
aif/system/console.py
Normal file
@ -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()
|
@ -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()
|
@ -6,4 +6,20 @@
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
##
|
##
|
||||||
import passlib # validate password hash/gen hash
|
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
|
||||||
|
|
||||||
|
@ -169,7 +169,15 @@
|
|||||||
<locales>
|
<locales>
|
||||||
<locale name="LANG">en_US.UTF-8</locale>
|
<locale name="LANG">en_US.UTF-8</locale>
|
||||||
</locales>
|
</locales>
|
||||||
<!-- Note: The password hashe below is "test"; don't waste your time trying to crack. :) -->
|
<console>
|
||||||
|
<text>
|
||||||
|
<font>default8x16</font>
|
||||||
|
</text>
|
||||||
|
<keyboard>
|
||||||
|
<map>us</map>
|
||||||
|
</keyboard>
|
||||||
|
</console>
|
||||||
|
<!-- Note: The password hash below is "test"; don't waste your time trying to crack. :) -->
|
||||||
<users>
|
<users>
|
||||||
<user name="aifusr"
|
<user name="aifusr"
|
||||||
home="/opt/aifusr"
|
home="/opt/aifusr"
|
||||||
|
Loading…
Reference in New Issue
Block a user