OSDN Git Service

2020.05.14 update
[rebornos/cnchi-gnome-osdn.git] / Cnchi / post_install.py
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
3 #
4 # post_install.py
5 #
6 # Copyright © 2013-2018 Antergos
7 #
8 # This file is part of Cnchi.
9 #
10 # Cnchi is free software; you can redistribute it and/or modify
11 # it under the terms of the GNU General Public License as published by
12 # the Free Software Foundation; either version 2 of the License, or
13 # (at your option) any later version.
14 #
15 # Cnchi is distributed in the hope that it will be useful,
16 # but WITHOUT ANY WARRANTY; without even the implied warranty of
17 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18 # GNU General Public License for more details.
19 #
20 # You should have received a copy of the GNU General Public License
21 # along with Cnchi; if not, write to the Free Software
22 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
23 # MA 02110-1301, USA.
24
25 """ Post-Installation process module. """
26
27 import crypt
28 import logging
29 import os
30 import shutil
31 import time
32
33 import desktop_info
34
35 from installation import mkinitcpio
36 from installation import systemd_networkd
37 from installation.boot import loader
38
39 from installation.post_fstab import PostFstab
40 from installation.post_features import PostFeatures
41 from installation import services as srv
42
43 from misc.events import Events
44 import misc.gocryptfs as gocryptfs
45 from misc.run_cmd import call, chroot_call
46
47 import parted3.fs_module as fs
48
49 from lembrame.lembrame import Lembrame
50
51 # When testing, no _() is available
52 try:
53     _("")
54 except NameError as err:
55     def _(message):
56         return message
57
58 DEST_DIR = "/install"
59
60 class PostInstallation():
61     """ Post-Installation process thread class """
62     POSTINSTALL_SCRIPT = 'postinstall.sh'
63     LOG_FOLDER = '/var/log/cnchi'
64
65     def __init__(
66             self, settings, callback_queue, mount_devices, fs_devices, ssd=None, blvm=False):
67
68         """ Initialize installation class """
69         self.settings = settings
70         self.events = Events(callback_queue)
71
72         self.pacman_conf_updated = False
73
74         self.method = self.settings.get('partition_mode')
75         self.desktop = self.settings.get('desktop').lower()
76
77         # This flag tells us if there is a lvm partition (from advanced install)
78         # If it's true we'll have to add the 'lvm2' hook to mkinitcpio
79         self.blvm = blvm
80
81         if ssd is not None:
82             self.ssd = ssd
83         else:
84             self.ssd = {}
85
86         self.mount_devices = mount_devices
87         self.fs_devices = fs_devices
88         self.virtual_box = self.settings.get('is_vbox')
89
90     def copy_logs(self):
91         """ Copy Cnchi logs to new installation """
92         log_dest_dir = os.path.join(DEST_DIR, "var/log/cnchi")
93         os.makedirs(log_dest_dir, mode=0o755, exist_ok=True)
94
95         datetime = "{0}-{1}".format(time.strftime("%Y%m%d"),
96                                     time.strftime("%H%M%S"))
97
98         file_names = [
99             'cnchi', 'cnchi-alpm', 'postinstall', 'pacman']
100
101         for name in file_names:
102             src = os.path.join(PostInstallation.LOG_FOLDER, "{0}.log".format(name))
103             dst = os.path.join(
104                 log_dest_dir, "{0}-{1}.log".format(name, datetime))
105             try:
106                 shutil.copy(src, dst)
107             except FileNotFoundError as err:
108                 logging.warning("Can't copy %s log to %s: %s", src, dst, str(err))
109             except FileExistsError:
110                 pass
111
112         # Store install id for later use by antergos-pkgstats
113         with open(os.path.join(log_dest_dir, 'install_id'), 'w') as install_record:
114             install_id = self.settings.get('install_id')
115             if not install_id:
116                 install_id = '0'
117             install_record.write(install_id)
118
119     @staticmethod
120     def copy_network_config():
121         """ Copies Network Manager configuration """
122         source_nm = "/etc/NetworkManager/system-connections/"
123         target_nm = os.path.join(
124             DEST_DIR, "etc/NetworkManager/system-connections")
125
126         # Sanity checks.  We don't want to do anything if a network
127         # configuration already exists on the target
128         if os.path.exists(source_nm) and os.path.exists(target_nm):
129             for network in os.listdir(source_nm):
130                 # Skip LTSP live
131                 if network == "LTSP":
132                     continue
133
134                 source_network = os.path.join(source_nm, network)
135                 target_network = os.path.join(target_nm, network)
136
137                 if os.path.exists(target_network):
138                     continue
139
140                 try:
141                     shutil.copy(source_network, target_network)
142                 except FileNotFoundError:
143                     logging.warning(
144                         "Can't copy network configuration files, file %s not found", source_network)
145                 except FileExistsError:
146                     pass
147
148     def set_scheduler(self):
149         """ Copies udev rule for SSDs """
150         rule_src = os.path.join(
151             self.settings.get('cnchi'),
152             'scripts/60-schedulers.rules')
153         rule_dst = os.path.join(
154             DEST_DIR,
155             "etc/udev/rules.d/60-schedulers.rules")
156         try:
157             shutil.copy2(rule_src, rule_dst)
158             os.chmod(rule_dst, 0o755)
159         except FileNotFoundError:
160             logging.warning(
161                 "Cannot copy udev rule for SSDs, file %s not found.",
162                 rule_src)
163         except FileExistsError:
164             pass
165
166     @staticmethod
167     def change_user_password(user, new_password):
168         """ Changes the user's password """
169         shadow_password = crypt.crypt(new_password, crypt.mksalt())
170         chroot_call(['usermod', '-p', shadow_password, user])
171
172     @staticmethod
173     def auto_timesetting():
174         """ Set hardware clock """
175         cmd = ["hwclock", "--systohc", "--utc"]
176         call(cmd)
177         try:
178             shutil.copy2("/etc/adjtime", os.path.join(DEST_DIR, "etc/"))
179         except FileNotFoundError:
180             logging.warning("File /etc/adjtime not found!")
181         except FileExistsError:
182             pass
183
184     @staticmethod
185     def update_pacman_conf():
186         """ Add Antergos and multilib repos """
187         """ Now, remove antergos repo, and add Reborn-OS repo (Rafael) """
188         path = os.path.join(DEST_DIR, "etc/pacman.conf")
189         if os.path.exists(path):
190             with open(path) as pacman_file:
191                 paclines = pacman_file.readlines()
192
193             mode = os.uname()[-1]
194             multilib_open = False
195
196             with open(path, 'w') as pacman_file:
197                 for pacline in paclines:
198                     if mode == "x86_64" and pacline == '#[multilib]\n':
199                         multilib_open = True
200                         pacline = '[multilib]\n'
201                     elif mode == 'x86_64' and multilib_open and pacline.startswith('#Include ='):
202                         pacline = pacline[1:]
203                         multilib_open = False
204                     elif pacline == '#[testing]\n':
205                         antlines = '\n#[Reborn-OS-staging]\n'
206                         antlines += '#SigLevel = Optional TrustAll\n'
207                         antlines += '#Server = http://repo-de.rebornos.org/Reborn-OS/\n\n'
208                         antlines += '[Reborn-OS]\n'
209                         antlines += 'SigLevel = Optional TrustAll\n'
210                         antlines += 'Include = /etc/pacman.d/reborn-mirrorlist\n\n'
211                         pacman_file.write(antlines)
212
213                     pacman_file.write(pacline)
214         else:
215             logging.warning("Can't find pacman configuration file")
216
217     @staticmethod
218     def uncomment_locale_gen(locale):
219         """ Uncomment selected locale in /etc/locale.gen """
220
221         path = os.path.join(DEST_DIR, "etc/locale.gen")
222
223         if os.path.exists(path):
224             with open(path) as gen:
225                 text = gen.readlines()
226
227             with open(path, "w") as gen:
228                 for line in text:
229                     if locale in line and line[0] == "#":
230                         # remove trailing '#'
231                         line = line[1:]
232                     gen.write(line)
233         else:
234             logging.error("Can't find locale.gen file")
235
236     @staticmethod
237     def fix_thermald_service():
238         """ Adds --ignore-cpuid-check to thermald service file """
239         path = os.path.join(DEST_DIR, "usr/lib/systemd/system/thermald.service")
240         if os.path.exists(path):
241             with open(path, 'r') as fin:
242                 lines = fin.readlines()
243             for index, line in enumerate(lines):
244                 if line.startswith("ExecStart") and "--ignore-cpuid-check" not in line:
245                     lines[index] += " --ignore-cpuid-check"
246             with open(path, 'w') as fout:
247                 fout.writelines(lines)
248
249     def setup_display_manager(self):
250         """ Configures LightDM desktop manager, including autologin. """
251         txt = _("Configuring LightDM desktop manager...")
252         self.events.add('info', txt)
253
254         if self.desktop in desktop_info.SESSIONS:
255             session = desktop_info.SESSIONS[self.desktop]
256         else:
257             session = "default"
258
259         username = self.settings.get('user_name')
260         autologin = not self.settings.get('require_password')
261
262         lightdm_greeter = "lightdm-webkit2-greeter"
263
264         lightdm_conf_path = os.path.join(DEST_DIR, "etc/lightdm/lightdm.conf")
265         try:
266             # Setup LightDM as Desktop Manager
267             with open(lightdm_conf_path) as lightdm_conf:
268                 text = lightdm_conf.readlines()
269
270             with open(lightdm_conf_path, "w") as lightdm_conf:
271                 for line in text:
272                     if autologin:
273                         # Enable automatic login
274                         if '#autologin-user=' in line:
275                             line = 'autologin-user={0}\n'.format(username)
276                         if '#autologin-user-timeout=0' in line:
277                             line = 'autologin-user-timeout=0\n'
278                     # Set correct DE session
279                     if '#user-session=default' in line:
280                         line = 'user-session={0}\n'.format(session)
281                     # Set correct greeter
282                     if '#greeter-session=example-gtk-gnome' in line:
283                         line = 'greeter-session={0}\n'.format(lightdm_greeter)
284                     if 'session-wrapper' in line:
285                         line = 'session-wrapper=/etc/lightdm/Xsession\n'
286                     lightdm_conf.write(line)
287             txt = _("LightDM display manager configuration completed.")
288             logging.debug(txt)
289         except FileNotFoundError:
290             txt = _("Error while trying to configure the LightDM display manager")
291             logging.warning(txt)
292
293     @staticmethod
294     def alsa_mixer_setup():
295         """ Sets ALSA mixer settings """
296
297         alsa_commands = [
298             "Master 70% unmute", "Front 70% unmute", "Side 70% unmute", "Surround 70% unmute",
299             "Center 70% unmute", "LFE 70% unmute", "Headphone 70% unmute", "Speaker 70% unmute",
300             "PCM 70% unmute", "Line 70% unmute", "External 70% unmute", "FM 50% unmute",
301             "Master Mono 70% unmute", "Master Digital 70% unmute", "Analog Mix 70% unmute",
302             "Aux 70% unmute", "Aux2 70% unmute", "PCM Center 70% unmute", "PCM Front 70% unmute",
303             "PCM LFE 70% unmute", "PCM Side 70% unmute", "PCM Surround 70% unmute",
304             "Playback 70% unmute", "PCM,1 70% unmute", "DAC 70% unmute", "DAC,0 70% unmute",
305             "DAC,1 70% unmute", "Synth 70% unmute", "CD 70% unmute", "Wave 70% unmute",
306             "Music 70% unmute", "AC97 70% unmute", "Analog Front 70% unmute",
307             "VIA DXS,0 70% unmute", "VIA DXS,1 70% unmute", "VIA DXS,2 70% unmute",
308             "VIA DXS,3 70% unmute", "Mic 70% mute", "IEC958 70% mute",
309             "Master Playback Switch on", "Master Surround on",
310             "SB Live Analog/Digital Output Jack off", "Audigy Analog/Digital Output Jack off"]
311
312         for alsa_command in alsa_commands:
313             cmd = ["amixer", "-q", "-c", "0", "sset"]
314             cmd.extend(alsa_command.split())
315             chroot_call(cmd)
316
317         # Save settings
318         logging.debug("Saving ALSA settings...")
319         chroot_call(['alsactl', 'store'])
320         logging.debug("ALSA settings saved.")
321
322     @staticmethod
323     def set_fluidsynth():
324         """ Sets fluidsynth configuration file """
325         fluid_path = os.path.join(DEST_DIR, "etc/conf.d/fluidsynth")
326         if os.path.exists(fluid_path):
327             audio_system = "alsa"
328             pulseaudio_path = os.path.join(DEST_DIR, "usr/bin/pulseaudio")
329             if os.path.exists(pulseaudio_path):
330                 audio_system = "pulse"
331             with open(fluid_path, "w") as fluid_conf:
332                 fluid_conf.write('# Created by Cnchi, Antergos installer\n')
333                 txt = 'SYNTHOPTS="-is -a {0} -m alsa_seq -r 48000"\n\n'
334                 txt = txt.format(audio_system)
335                 fluid_conf.write(txt)
336
337     @staticmethod
338     def patch_user_dirs_update_gtk():
339         """ Patches user-dirs-update-gtk.desktop so it is run in
340             XFCE, MATE and Cinnamon """
341         path = os.path.join(DEST_DIR, "etc/xdg/autostart/user-dirs-update-gtk.desktop")
342         if os.path.exists(path):
343             with open(path, 'r') as user_dirs:
344                 lines = user_dirs.readlines()
345             with open(path, 'w') as user_dirs:
346                 for line in lines:
347                     if "OnlyShowIn=" in line:
348                         line = "OnlyShowIn=GNOME;LXDE;Unity;XFCE;MATE;Cinnamon\n"
349                     user_dirs.write(line)
350
351     def set_keymap(self):
352         """ Set X11 and console keymap """
353         keyboard_layout = self.settings.get("keyboard_layout")
354         keyboard_variant = self.settings.get("keyboard_variant")
355         # localectl set-x11-keymap es cat
356         cmd = ['localectl', 'set-x11-keymap', keyboard_layout]
357         if keyboard_variant:
358             cmd.append(keyboard_variant)
359         # Systemd based tools like localectl do not work inside a chroot
360         # This will set correct keymap to live media, we will copy
361         # the created files to destination
362         call(cmd)
363         # Copy 00-keyboard.conf and vconsole.conf files to destination
364         path = os.path.join(DEST_DIR, "etc/X11/xorg.conf.d")
365         os.makedirs(path, mode=0o755, exist_ok=True)
366         files = ["/etc/X11/xorg.conf.d/00-keyboard.conf", "/etc/vconsole.conf"]
367         for src in files:
368             try:
369                 if os.path.exists(src):
370                     dst = os.path.join(DEST_DIR, src[1:])
371                     shutil.copy(src, dst)
372                     logging.debug("%s copied.", src)
373             except FileNotFoundError:
374                 logging.error("File %s not found in live media", src)
375             except FileExistsError:
376                 pass
377             except shutil.Error as err:
378                 logging.error(err)
379   
380     @staticmethod
381     def get_installed_zfs_version():
382         """ Get installed zfs version """
383         zfs_version = "0.6.5.4"
384         path = "/install/usr/src"
385         for file_name in os.listdir(path):
386             if file_name.startswith("zfs") and not file_name.startswith("zfs-utils"):
387                 try:
388                     zfs_version = file_name.split("-")[1]
389                     logging.info(
390                         "Installed zfs module's version: %s", zfs_version)
391                 except KeyError:
392                     logging.warning("Can't get zfs version from %s", file_name)
393         return zfs_version
394
395     @staticmethod
396     def get_installed_kernel_versions():
397         """ Get installed kernel versions """
398         kernel_versions = []
399         path = "/install/usr/lib/modules"
400         for file_name in os.listdir(path):
401             if not file_name.startswith("extramodules"):
402                 kernel_versions.append(file_name)
403         return kernel_versions
404
405     def set_desktop_settings(self):
406         """ Runs postinstall.sh that sets DE settings
407             Postinstall script uses arch-chroot, so we don't have to worry
408             about /proc, /dev, ... """
409         logging.debug("Running Cnchi post-install script")
410         keyboard_layout = self.settings.get("keyboard_layout")
411         keyboard_variant = self.settings.get("keyboard_variant")
412         # Call post-install script to fine tune our setup
413         script_path_postinstall = os.path.join(
414             self.settings.get('cnchi'),
415             "scripts",
416             PostInstallation.POSTINSTALL_SCRIPT)
417         cmd = [
418             "/usr/bin/bash",
419             script_path_postinstall,
420             self.settings.get('user_name'),
421             DEST_DIR,
422             self.desktop,
423             self.settings.get("locale"),
424             str(self.virtual_box),
425             keyboard_layout]
426
427         # Keyboard variant is optional
428         if keyboard_variant:
429             cmd.append(keyboard_variant)
430
431         call(cmd, timeout=300)
432         logging.debug("Post install script completed successfully.")
433
434     @staticmethod
435     def modify_makepkg():
436         """ Modify the makeflags to allow for threading.
437             Use threads for xz compression. """
438         makepkg_conf_path = os.path.join(DEST_DIR, 'etc/makepkg.conf')
439         if os.path.exists(makepkg_conf_path):
440             with open(makepkg_conf_path, 'r') as makepkg_conf:
441                 contents = makepkg_conf.readlines()
442             with open(makepkg_conf_path, 'w') as makepkg_conf:
443                 for line in contents:
444                     if '#MAKEFLAGS' in line:
445                         line = 'MAKEFLAGS="-j$(nproc)"\n'
446                     elif 'COMPRESSXZ' in line:
447                         line = 'COMPRESSXZ=(xz -c -z - --threads=0)\n'
448                     makepkg_conf.write(line)
449
450     def setup_user(self):
451         """ Set user parameters """
452         username = self.settings.get('user_name')
453         fullname = self.settings.get('user_fullname')
454         password = self.settings.get('user_password')
455         hostname = self.settings.get('hostname')
456
457         sudoers_dir = os.path.join(DEST_DIR, "etc/sudoers.d")
458         if not os.path.exists(sudoers_dir):
459             os.mkdir(sudoers_dir, 0o710)
460         sudoers_path = os.path.join(sudoers_dir, "10-installer")
461         try:
462             with open(sudoers_path, "w") as sudoers:
463                 sudoers.write('{0} ALL=(ALL) ALL\n'.format(username))
464             os.chmod(sudoers_path, 0o440)
465             logging.debug("Sudo configuration for user %s done.", username)
466         except IOError as io_error:
467             # Do not fail if can't write 10-installer file.
468             # Something bad must be happening, though.
469             logging.error(io_error)
470
471         # Setup user
472
473         default_groups = 'wheel'
474
475         if self.virtual_box:
476             # Why there is no vboxusers group? Add it ourselves.
477             chroot_call(['groupadd', 'vboxusers'])
478             default_groups += ',vboxusers,vboxsf'
479             srv.enable_services(["vboxservice"])
480
481         if self.settings.get('require_password') is False:
482             # Prepare system for autologin.
483             # LightDM needs the user to be in the autologin group.
484             chroot_call(['groupadd', 'autologin'])
485             default_groups += ',autologin'
486
487         cmd = [
488             'useradd', '--create-home',
489             '--shell', '/bin/bash',
490             '--groups', default_groups,
491             username]
492         chroot_call(cmd)
493         logging.debug("User %s added.", username)
494
495         self.change_user_password(username, password)
496
497         chroot_call(['chfn', '-f', fullname, username])
498         home_dir = os.path.join("/home", username)
499         cmd = ['chown', '-R', '{0}:{0}'.format(username), home_dir]
500         chroot_call(cmd)
501
502         # Set hostname
503         hostname_path = os.path.join(DEST_DIR, "etc/hostname")
504         if not os.path.exists(hostname_path):
505             with open(hostname_path, "w") as hostname_file:
506                 hostname_file.write(hostname)
507
508         logging.debug("Hostname set to %s", hostname)
509
510         # User password is the root password
511         self.change_user_password('root', password)
512         logging.debug("Set the same password to root.")
513
514         # set user's avatar
515         avatar = self.settings.get('user_avatar')
516         if avatar and os.path.exists(avatar):
517             try:
518                 dst = os.path.join(
519                     DEST_DIR,
520                     'var/lib/AccountsService/icons',
521                     username + '.png')
522                 shutil.copy(avatar, dst)
523             except FileNotFoundError:
524                 logging.warning("Can't copy %s log to %s", avatar, dst)
525             except FileExistsError:
526                 pass
527
528         ## Encrypt user's home directory if requested
529         if self.settings.get('encrypt_home'):
530             self.events.add('info', _("Encrypting user home dir..."))
531             gocryptfs.setup(username, "users", DEST_DIR, password)
532             logging.debug("User home dir encrypted")
533
534     @staticmethod
535     def nano_setup():
536         """ Enable colors and syntax highlighting in nano editor """
537         nanorc_path = os.path.join(DEST_DIR, 'etc/nanorc')
538         if os.path.exists(nanorc_path):
539             logging.debug(
540                 "Enabling colors and syntax highlighting in nano editor")
541             with open(nanorc_path, 'a') as nanorc:
542                 nanorc.write('\n')
543                 nanorc.write('# Added by Cnchi (Antergos Installer)\n')
544                 nanorc.write('set titlecolor brightwhite,blue\n')
545                 nanorc.write('set statuscolor brightwhite,green\n')
546                 nanorc.write('set numbercolor cyan\n')
547                 nanorc.write('set keycolor cyan\n')
548                 nanorc.write('set functioncolor green\n')
549                 nanorc.write('include "/usr/share/nano/*.nanorc"\n')
550
551     def rebuild_zfs_modules(self):
552         """ Sometimes dkms tries to build the zfs module before spl. """
553         self.events.add('info', _("Building zfs modules..."))
554         zfs_version = self.get_installed_zfs_version()
555         spl_module = 'spl/{}'.format(zfs_version)
556         zfs_module = 'zfs/{}'.format(zfs_version)
557         kernel_versions = self.get_installed_kernel_versions()
558         if kernel_versions:
559             for kernel_version in kernel_versions:
560                 logging.debug(
561                     "Installing zfs v%s modules for kernel %s", zfs_version, kernel_version)
562                 chroot_call(
563                     ['dkms', 'install', spl_module, '-k', kernel_version])
564                 chroot_call(
565                     ['dkms', 'install', zfs_module, '-k', kernel_version])
566         else:
567             # No kernel version found, try to install for current kernel
568             logging.debug(
569                 "Installing zfs v%s modules for current kernel.", zfs_version)
570             chroot_call(['dkms', 'install', spl_module])
571             chroot_call(['dkms', 'install', zfs_module])
572
573     def pamac_setup(self):
574         """ Enable AUR in pamac if AUR feature selected """
575         pamac_conf = os.path.join(DEST_DIR, 'etc/pamac.conf')
576         if os.path.exists(pamac_conf) and self.settings.get('feature_aur'):
577             logging.debug("Enabling AUR options in pamac")
578             with open(pamac_conf, 'r') as pamac_conf_file:
579                 file_data = pamac_conf_file.read()
580             file_data = file_data.replace("#EnableAUR", "EnableAUR")
581             file_data = file_data.replace(
582                 "#SearchInAURByDefault", "SearchInAURByDefault")
583             file_data = file_data.replace(
584                 "#CheckAURUpdates", "CheckAURUpdates")
585             with open(pamac_conf, 'w') as pamac_conf_file:
586                 pamac_conf_file.write(file_data)
587
588     @staticmethod
589     def setup_timesyncd():
590         """ Setups and enables time sync service """
591         timesyncd_path = os.path.join(DEST_DIR, "etc/systemd/timesyncd.conf")
592         try:
593             with open(timesyncd_path, 'w') as timesyncd:
594                 timesyncd.write("[Time]\n")
595                 timesyncd.write("NTP=0.arch.pool.ntp.org 1.arch.pool.ntp.org "
596                                 "2.arch.pool.ntp.org 3.arch.pool.ntp.org\n")
597                 timesyncd.write("FallbackNTP=0.pool.ntp.org 1.pool.ntp.org "
598                                 "0.fr.pool.ntp.org\n")
599         except FileNotFoundError as err:
600             logging.warning("Can't find %s file: %s", timesyncd_path, err)
601         chroot_call(['systemctl', '-fq', 'enable',
602                      'systemd-timesyncd.service'])
603
604     def check_btrfs(self):
605         """ Checks if any device will be using btrfs """
606         for mount_point in self.mount_devices:
607             partition_path = self.mount_devices[mount_point]
608             uuid = fs.get_uuid(partition_path)
609             if uuid and partition_path in self.fs_devices:
610                 myfmt = self.fs_devices[partition_path]
611                 if myfmt == 'btrfs':
612                     return True
613         return False
614
615     def configure_system(self, hardware_install):
616         """ Final install steps.
617             Set clock, language, timezone, run mkinitcpio,
618             populate pacman keyring, setup systemd services, ... """
619
620         self.events.add('pulse', 'start')
621         self.events.add('info', _("Configuring your new system"))
622
623         auto_fstab = PostFstab(
624             self.method, self.mount_devices, self.fs_devices, self.ssd, self.settings)
625         auto_fstab.run()
626         if auto_fstab.root_uuid:
627             self.settings.set('ruuid', auto_fstab.root_uuid)
628         logging.debug("fstab file generated.")
629
630         # Check if we have any btrfs device
631         if self.check_btrfs():
632             self.settings.set('btrfs', True)
633
634         # If SSD was detected copy udev rule for deadline scheduler
635         if self.ssd:
636             self.set_scheduler()
637             logging.debug("SSD udev rule copied successfully")
638
639         # Copy configured networks in Live medium to target system
640         if self.settings.get("network_manager") == "NetworkManager":
641             self.copy_network_config()
642
643         if self.desktop == "base":
644             # Setup systemd-networkd for systems that won't use the
645             # networkmanager or connman daemons (atm it's just base install)
646             # Enable systemd_networkd services
647             # https://github.com/Antergos/Cnchi/issues/332#issuecomment-108745026
648             srv.enable_services(["systemd-networkd", "systemd-resolved"])
649             # Setup systemd_networkd
650             # TODO: Ask user for SSID and passphrase if a wireless link is
651             # found (here or inside systemd_networkd.setup() ?)
652             systemd_networkd.setup()
653
654         logging.debug("Network configuration done.")
655
656         # Copy mirror list
657         mirrorlist_src_path = '/etc/pacman.d/mirrorlist'
658         mirrorlist_dst_path = os.path.join(DEST_DIR, 'etc/pacman.d/mirrorlist')
659         try:
660             shutil.copy2(mirrorlist_src_path, mirrorlist_dst_path)
661             logging.debug("Mirror list copied.")
662         except FileNotFoundError:
663             logging.error(
664                 "Can't copy mirrorlist file. File %s not found",
665                 mirrorlist_src_path)
666         except FileExistsError:
667             logging.warning("File %s already exists.", mirrorlist_dst_path)
668
669         # Add Antergos repo to /etc/pacman.conf
670         self.update_pacman_conf()
671         self.pacman_conf_updated = True
672         logging.debug("pacman.conf has been created successfully")
673
674         # Enable some useful services
675         services = []
676         if self.desktop != "base":
677             # In base there's no desktop manager ;)
678             services.append(self.settings.get("desktop_manager"))
679             # In base we use systemd-networkd (setup already done above)
680             services.append(self.settings.get("network_manager"))
681             # If bumblebee (optimus cards) is installed, enable it
682             if os.path.exists(os.path.join(DEST_DIR, "usr/lib/systemd/system/bumblebeed.service")):
683                 services.extend(["bumblebee"])
684         services.extend(["ModemManager", "haveged"])
685         if self.method == "zfs":
686             # Beginning with ZOL version 0.6.5.8 the ZFS service unit files have
687             # been changed so that you need to explicitly enable any ZFS services
688             # you want to run.
689             services.extend(["zfs.target", "zfs-import-cache", "zfs-mount"])
690         srv.enable_services(services)
691
692         # Enable timesyncd service
693         if self.settings.get("use_timesyncd"):
694             self.setup_timesyncd()
695
696         # Set timezone
697         zone = self.settings.get("timezone_zone")
698         if zone:
699             zoneinfo_path = os.path.join("/usr/share/zoneinfo", zone)
700             localtime_path = "/etc/localtime"
701             chroot_call(['ln', '-sf', zoneinfo_path, localtime_path])
702             logging.debug("Timezone set to %s", zoneinfo_path)
703         else:
704             logging.warning(
705                 "Can't read selected timezone! Will leave it to UTC.")
706
707         # Configure detected hardware
708         # NOTE: Because hardware can need extra repos, this code must run
709         # always after having called the update_pacman_conf method
710         if self.pacman_conf_updated and hardware_install:
711             try:
712                 logging.debug("Running hardware drivers post-install jobs...")
713                 hardware_install.post_install(DEST_DIR)
714             except Exception as ex:
715                 template = "Error in hardware module. " \
716                     "An exception of type {0} occured. Arguments:\n{1!r}"
717                 message = template.format(type(ex).__name__, ex.args)
718                 logging.error(message)
719
720         self.setup_user()
721
722         # Generate locales
723         locale = self.settings.get("locale")
724         self.events.add('info', _("Generating locales..."))
725         self.uncomment_locale_gen(locale)
726         chroot_call(['locale-gen'])
727         locale_conf_path = os.path.join(DEST_DIR, "etc/locale.conf")
728         with open(locale_conf_path, "w") as locale_conf:
729             locale_conf.write('LANG={0}\n'.format(locale))
730             locale_conf.write('LC_COLLATE={0}\n'.format(locale))
731
732         # environment_path = os.path.join(DEST_DIR, "etc/environment")
733         # with open(environment_path, "w") as environment:
734         #    environment.write('LANG={0}\n'.format(locale))
735
736         self.events.add('info', _("Adjusting hardware clock..."))
737         self.auto_timesetting()
738
739         self.events.add('info', _("Configuring keymap..."))
740         self.set_keymap()
741
742         # Install configs for root
743         chroot_call(['cp', '-av', '/etc/skel/.', '/root/'])
744
745         self.events.add('info', _("Configuring hardware..."))
746
747         # Copy generated xorg.conf to target
748         if os.path.exists("/etc/X11/xorg.conf"):
749             src = "/etc/X11/xorg.conf"
750             dst = os.path.join(DEST_DIR, 'etc/X11/xorg.conf')
751             shutil.copy2(src, dst)
752
753         # Configure ALSA
754         # self.alsa_mixer_setup()
755         #logging.debug("Updated Alsa mixer settings")
756
757         # Set pulse
758         # if os.path.exists(os.path.join(DEST_DIR, "usr/bin/pulseaudio-ctl")):
759         #    chroot_run(['pulseaudio-ctl', 'normal'])
760
761         # Set fluidsynth audio system (in our case, pulseaudio)
762         self.set_fluidsynth()
763         logging.debug("Updated fluidsynth configuration file")
764
765         # Workaround for pacman-key bug FS#45351
766         # https://bugs.archlinux.org/task/45351
767         # We have to kill gpg-agent because if it stays around we can't
768         # reliably unmount the target partition.
769         logging.debug("Stopping gpg agent...")
770         chroot_call(['killall', '-9', 'gpg-agent'])
771
772         # FIXME: Temporary workaround for spl and zfs packages
773         if self.method == "zfs":
774             self.rebuild_zfs_modules()
775
776         # Let's start without using hwdetect for mkinitcpio.conf.
777         # It should work out of the box most of the time.
778         # This way we don't have to fix deprecated hooks.
779         # NOTE: With LUKS or LVM maybe we'll have to fix deprecated hooks.
780         self.events.add('info', _("Configuring System Startup..."))
781         mkinitcpio.run(DEST_DIR, self.settings, self.mount_devices, self.blvm)
782
783         # Patch user-dirs-update-gtk.desktop
784         self.patch_user_dirs_update_gtk()
785         logging.debug("File user-dirs-update-gtk.desktop patched.")
786
787         # Set lightdm config including autologin if selected
788         if self.desktop != "base":
789             self.setup_display_manager()
790
791         # Configure user features (firewall, libreoffice language pack, ...)
792         #self.setup_features()
793         post_features = PostFeatures(DEST_DIR, self.settings)
794         post_features.setup()
795
796         # Install boot loader (always after running mkinitcpio)
797         if self.settings.get('bootloader_install'):
798             try:
799                 self.events.add('info', _("Installing bootloader..."))
800                 boot_loader = loader.Bootloader(
801                     DEST_DIR,
802                     self.settings,
803                     self.mount_devices)
804                 boot_loader.install()
805             except Exception as ex:
806                 template = "Cannot install bootloader. " \
807                     "An exception of type {0} occured. Arguments:\n{1!r}"
808                 message = template.format(type(ex).__name__, ex.args)
809                 logging.error(message)
810
811         # Create an initial database for mandb (slow)
812         #self.events.add('info', _("Updating man pages..."))
813         #chroot_call(["mandb", "--quiet"])
814
815         # Initialise pkgfile (pacman .files metadata explorer) database
816         logging.debug("Updating pkgfile database")
817         chroot_call(["pkgfile", "--update"])
818
819         if self.desktop != "base":
820             # avahi package seems to fail to create its user and group in some cases (¿?)
821             cmd = ["groupadd", "-r", "-g", "84", "avahi"]
822             chroot_call(cmd)
823             cmd = ["useradd", "-r", "-u", "84", "-g", "avahi", "-d", "/", "-s",
824                    "/bin/nologin", "-c", "avahi", "avahi"]
825             chroot_call(cmd)
826
827         # Install sonar (a11y) gsettings if present in the ISO (and a11y is on)
828         src = "/usr/share/glib-2.0/schemas/92_antergos_sonar.gschema.override"
829         if self.settings.get('a11y') and os.path.exists(src):
830             dst = os.path.join(DEST_DIR, 'usr/share/glib-2.0/schemas')
831             shutil.copy2(src, dst)
832
833         # Enable AUR in pamac if AUR feature selected
834         self.pamac_setup()
835
836         # Apply makepkg tweaks upon install (issue #871)
837         self.modify_makepkg()
838
839         self.nano_setup()
840
841         logging.debug("Setting .bashrc to load .bashrc.aliases")
842         bashrc_files = ["etc/skel/.bashrc"]
843         username = self.settings.get('user_name')
844         bashrc_files.append("home/{}/.bashrc".format(username))
845         for bashrc_file in bashrc_files:
846             bashrc_file = os.path.join(DEST_DIR, bashrc_file)
847             if os.path.exists(bashrc_file):
848                 with open(bashrc_file, 'a') as bashrc:
849                     bashrc.write('\n')
850                     bashrc.write('if [ -e ~/.bashrc.aliases ] ; then\n')
851                     bashrc.write('   source ~/.bashrc.aliases\n')
852                     bashrc.write('fi\n')
853
854         # Fixes thermald service file
855         self.fix_thermald_service()
856
857         # Overwrite settings with Lembrame if enabled
858         # TODO: Rethink this function because we need almost everything but some things for Lembrame
859         if self.settings.get("feature_lembrame"):
860             logging.debug("Overwriting configs from Lembrame")
861             self.events.add('info', _("Overwriting configs from Lembrame"))
862
863             lembrame = Lembrame(self.settings)
864             lembrame.overwrite_content()
865
866         # This must be done at the end of the installation when using zfs
867         if self.method == "zfs":
868             logging.debug("Installation done, exporting ZFS pool")
869             pool_name = self.settings.get("zfs_pool_name")
870             cmd = ["zpool", "export", "-f", pool_name]
871             call(cmd)