OSDN Git Service

Updating info
[rebornos/cnchi-gnome-osdn.git] / Cnchi / ask.py
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
3 #
4 # ask.py
5 #
6 # Copyright © 2013-2019 RebornOS
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 3 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 # The following additional terms are in effect as per Section 7 of the license:
21 #
22 # The preservation of all legal notices and author attributions in
23 # the material or in the Appropriate Legal Notices displayed
24 # by works containing it is required.
25 #
26 # You should have received a copy of the GNU General Public License
27 # along with Cnchi; If not, see <http://www.gnu.org/licenses/>.
28
29
30 """ Asks which type of installation the user wants to perform """
31
32 import os
33 import logging
34 import subprocess
35
36 import gi
37 gi.require_version('Gtk', '3.0')
38 from gi.repository import Gtk
39
40 import bootinfo
41 from pages.gtkbasebox import GtkBaseBox
42 import misc.extra as misc
43 from browser_window import BrowserWindow
44
45 # When testing, no _() is available
46 try:
47     _("")
48 except NameError as err:
49     def _(message):
50         return message
51
52
53 def check_alongside_disk_layout():
54     """ Alongside can only work if user has followed the recommended
55         BIOS-Based Disk-Partition Configurations shown in
56         http://technet.microsoft.com/en-us/library/dd744364(v=ws.10).aspx """
57
58     # TODO: Add more scenarios where alongside could work
59
60     partitions = misc.get_partitions()
61     # logging.debug(partitions)
62     extended = False
63     for partition in partitions:
64         if misc.is_partition_extended(partition):
65             extended = True
66
67     if extended:
68         return False
69
70     # We just seek for sda partitions
71     partitions_sda = []
72     for partition in partitions:
73         if "sda" in partition:
74             partitions_sda.append(partition)
75
76     # There's no extended partition, so all partitions must be primary
77     if len(partitions_sda) < 4:
78         return True
79
80     return False
81
82
83 def load_zfs():
84     """ Load ZFS kernel module """
85     cmd = ["modprobe", "zfs"]
86     try:
87         with misc.raised_privileges():
88             subprocess.check_output(cmd, stderr=subprocess.STDOUT)
89         logging.debug("ZFS kernel module loaded successfully.")
90     except subprocess.CalledProcessError as err:
91         error_msg = err.output.decode().rstrip()
92         logging.warning("%s", error_msg)
93         return False
94     return True
95
96
97 class InstallationAsk(GtkBaseBox):
98     """ Asks user which type of installation wants to perform """
99     def __init__(self, params, prev_page="mirrors", next_page=None):
100         super().__init__(self, params, "ask", prev_page, next_page)
101
102         data_dir = self.settings.get("data")
103
104         partitioner_dir = os.path.join(
105             data_dir,
106             "images",
107             "partitioner",
108             "small")
109
110         image = self.gui.get_object("automatic_image")
111         path = os.path.join(partitioner_dir, "automatic.png")
112         image.set_from_file(path)
113
114         # image = self.gui.get_object("alongside_image")
115         # path = os.path.join(partitioner_dir, "alongside.png")
116         # image.set_from_file(path)
117
118         image = self.gui.get_object("advanced_image")
119         path = os.path.join(partitioner_dir, "advanced.png")
120         image.set_from_file(path)
121
122         self.other_oses = []
123
124         # DISABLE ALONGSIDE INSTALLATION. IT'S NOT READY YET
125         # enable_alongside = self.check_alongside()
126         enable_alongside = False
127         self.settings.set('enable_alongside', enable_alongside)
128
129         #if enable_alongside:
130         #    msg = "Cnchi will enable the 'alongside' installation mode."
131         #else:
132         #    msg = "Cnchi will NOT enable the 'alongside' installation mode."
133         #logging.debug(msg)
134
135         # By default, select automatic installation
136         self.next_page = "installation_automatic"
137         self.settings.set("partition_mode", "automatic")
138
139         self.is_zfs_available = load_zfs()
140
141         self.enable_automatic_options(True)
142
143         btn_label = _(
144             "I need help with an RebornOS / Windows(tm) dual boot setup!")
145         btn = Gtk.Button.new_with_label(btn_label)
146         btn.connect(
147             'clicked', self.alongside_wiki_button_clicked)
148         ask_box = self.gui.get_object("ask")
149         ask_box.pack_start(btn, True, False, 0)
150
151         self.browser = None
152
153     def alongside_wiki_button_clicked(self, _widget, _data=None):
154         """ Shows dual installation wiki page in a browser window  """
155         try:
156             self.browser = BrowserWindow("RebornOS Wiki - Dual Boot")
157             url = ("https://sourceforge.net/p/rebornos/wiki/Dual%20Boot%20RebornOS%20%26%20Windows%20UEFI%20%28Expanded%29%20%E2%80%93%20by%20linuxhelmet/")
158             self.browser.load_url(url)
159         except Exception as err:
160             logging.warning("Could not show RebornOS wiki: %s", err)
161
162     def check_alongside(self):
163         """ Check if alongside installation type must be enabled.
164             Alongside only works when Windows is installed on sda """
165
166         enable_alongside = False
167
168         # FIXME: Alongside does not work in UEFI systems
169         if os.path.exists("/sys/firmware/efi"):
170             msg = "The 'alongside' installation mode does not work in UEFI systems"
171             logging.debug(msg)
172             enable_alongside = False
173         else:
174             oses = bootinfo.get_os_dict()
175             self.other_oses = []
176             for key in oses:
177                 # We only check the first hard disk
178                 non_valid = ["unknown", "Swap",
179                              "Data or Swap", self.other_oses]
180                 if "sda" in key and oses[key] not in non_valid:
181                     self.other_oses.append(oses[key])
182
183             if self.other_oses:
184                 for detected_os in self.other_oses:
185                     if "windows" in detected_os.lower():
186                         logging.debug("Windows(tm) OS detected.")
187                         enable_alongside = True
188                 if not enable_alongside:
189                     logging.debug("Windows(tm) OS not detected.")
190                     enable_alongside = False
191             else:
192                 logging.debug("Can't detect any OS in device sda.")
193                 enable_alongside = False
194
195             if not check_alongside_disk_layout():
196                 msg = "Unsuported disk layout for the 'alongside' installation mode"
197                 logging.debug(msg)
198                 enable_alongside = False
199
200         return enable_alongside
201
202     def enable_automatic_options(self, status):
203         """ Enables or disables automatic installation options """
204         names = [
205             "encrypt_checkbutton", "encrypt_label",
206             "lvm_checkbutton", "lvm_label",
207             "home_checkbutton", "home_label"]
208
209         for name in names:
210             obj = self.gui.get_object(name)
211             obj.set_sensitive(status)
212
213         names = ["zfs_checkbutton", "zfs_label"]
214         for name in names:
215             obj = self.gui.get_object(name)
216             obj.set_sensitive(status and self.is_zfs_available)
217
218     def prepare(self, direction):
219         """ Prepares screen """
220         # Read options and set widgets accordingly
221         widgets_settings = {
222             ('use_luks', 'encrypt_checkbutton'), ('use_lvm', 'lvm_checkbutton'),
223             ('use_zfs', 'zfs_checkbutton'), ('use_home', 'home_checkbutton')}
224
225         for (setting_name, widget_id) in widgets_settings:
226             widget = self.gui.get_object(widget_id)
227             setting_value = self.settings.get(setting_name)
228             widget.set_active(setting_value)
229
230         self.translate_ui()
231         self.show_all()
232
233         if not self.settings.get('enable_alongside'):
234             self.hide_option("alongside")
235
236         self.forward_button.set_sensitive(True)
237
238     def hide_option(self, option):
239         """ Hides widgets """
240         widgets = []
241         if option == "alongside":
242             widgets = [
243                 "alongside_radiobutton",
244                 "alongside_description",
245                 "alongside_image"]
246
247         for name in widgets:
248             widget = self.gui.get_object(name)
249             if widget is not None:
250                 widget.hide()
251
252     def get_os_list_str(self):
253         """ Get string with the detected os names """
254         os_str = ""
255         len_other_oses = len(self.other_oses)
256         if len_other_oses > 0:
257             if len_other_oses > 1:
258                 if len_other_oses == 2:
259                     os_str = _(" and ").join(self.other_oses)
260                 else:
261                     os_str = ", ".join(self.other_oses)
262             else:
263                 os_str = self.other_oses[0]
264
265         # Truncate string if it's too large
266         if len(os_str) > 40:
267             os_str = os_str[:40] + "..."
268
269         return os_str
270
271     def translate_ui(self):
272         """ Translates screen before showing it """
273         self.header.set_subtitle(_("Installation Type"))
274
275         self.forward_button.set_always_show_image(True)
276         self.forward_button.set_sensitive(True)
277
278         # description_style = '<span style="italic">{0}</span>'
279         # bold_style = '<span weight="bold">{0}</span>'
280
281         oses_str = self.get_os_list_str()
282
283         max_width_chars = 80
284
285         # Automatic Install
286         radio = self.gui.get_object("automatic_radiobutton")
287         if oses_str:
288             txt = _("Replace {0} with RebornOS").format(oses_str)
289         else:
290             txt = _("Erase disk and install RebornOS")
291         radio.set_label(txt)
292         radio.set_name('auto_radio_btn')
293
294         label = self.gui.get_object("automatic_description")
295         txt = _("Warning: This will erase ALL data on your disk.")
296         # txt = description_style.format(txt)
297         label.set_text(txt)
298         label.set_name("automatic_desc")
299         label.set_hexpand(False)
300         label.set_line_wrap(True)
301         label.set_max_width_chars(max_width_chars)
302
303         button = self.gui.get_object("encrypt_checkbutton")
304         txt = _("Encrypt this installation for increased security.")
305         button.set_label(txt)
306         button.set_name("enc_btn")
307         button.set_hexpand(False)
308         # button.set_line_wrap(True)
309         # button.set_max_width_chars(max_width_chars)
310
311         label = self.gui.get_object("encrypt_label")
312         txt = _("You will be asked to create an encryption password in the "
313                 "next step.")
314         # txt = description_style.format(txt)
315         label.set_text(txt)
316         label.set_name("enc_label")
317         label.set_hexpand(False)
318         label.set_line_wrap(True)
319         label.set_max_width_chars(max_width_chars)
320
321         button = self.gui.get_object("lvm_checkbutton")
322         txt = _("Use LVM with this installation.")
323         button.set_label(txt)
324         button.set_name("lvm_btn")
325         button.set_hexpand(False)
326         # button.set_line_wrap(True)
327         # button.set_max_width_chars(max_width_chars)
328
329         label = self.gui.get_object("lvm_label")
330         txt = _("This will setup LVM and allow you to easily manage "
331                 "partitions and create snapshots.")
332         # txt = description_style.format(txt)
333         label.set_text(txt)
334         label.set_name("lvm_label")
335         label.set_hexpand(False)
336         label.set_line_wrap(True)
337         label.set_max_width_chars(max_width_chars)
338
339         button = self.gui.get_object("zfs_checkbutton")
340         txt = _("Use ZFS with this installation.")
341         button.set_label(txt)
342         button.set_name("zfs_btn")
343         button.set_hexpand(False)
344         # button.set_line_wrap(True)
345         # button.set_max_width_chars(max_width_chars)
346
347         label = self.gui.get_object("zfs_label")
348         txt = _("This will setup ZFS on your drive(s).")
349         # txt = description_style.format(txt)
350         label.set_text(txt)
351         label.set_name("zfs_label")
352         label.set_hexpand(False)
353         label.set_line_wrap(True)
354         label.set_max_width_chars(max_width_chars)
355
356         button = self.gui.get_object("home_checkbutton")
357         txt = _("Set your Home in a different partition/volume")
358         button.set_label(txt)
359         button.set_name("home_btn")
360         button.set_hexpand(False)
361         # button.set_line_wrap(True)
362         # button.set_max_width_chars(max_width_chars)
363
364         label = self.gui.get_object("home_label")
365         txt = _("This will setup your /home directory in a different "
366                 "partition or volume.")
367         # txt = description_style.format(txt)
368         label.set_text(txt)
369         label.set_name("home_label")
370         label.set_hexpand(False)
371         label.set_line_wrap(True)
372         label.set_max_width_chars(max_width_chars)
373
374         # Alongside Install (For now, only works with Windows)
375         # if len(oses_str) > 0:
376         #     txt = _("Install Antergos alongside {0}").format(oses_str)
377         #     radio = self.gui.get_object("alongside_radiobutton")
378         #     radio.set_label(txt)
379         #
380         #     label = self.gui.get_object("alongside_description")
381         #     txt = _("Installs Antergos without removing {0}").format(oses_str)
382         #     txt = description_style.format(txt)
383         #     label.set_markup(txt)
384         #     label.set_line_wrap(True)
385         #
386         #     intro_txt = _("This computer has {0} installed.").format(oses_str)
387         #     intro_txt = intro_txt + "\n" + _("What do you want to do?")
388         # else:
389         intro_txt = _("How would you like to proceed?")
390
391         intro_label = self.gui.get_object("introduction")
392         # intro_txt = bold_style.format(intro_txt)
393         intro_label.set_text(intro_txt)
394         intro_label.set_name("intro_label")
395         intro_label.set_hexpand(False)
396         intro_label.set_line_wrap(True)
397         intro_label.set_max_width_chars(max_width_chars)
398
399         # Advanced Install
400         radio = self.gui.get_object("advanced_radiobutton")
401         radio.set_label(
402             _("Choose exactly where RebornOS should be installed."))
403         radio.set_name("advanced_radio_btn")
404
405         label = self.gui.get_object("advanced_description")
406         txt = _("Edit partition table and choose mount points.")
407         # txt = description_style.format(txt)
408         label.set_text(txt)
409         label.set_name("adv_desc_label")
410         label.set_hexpand(False)
411         label.set_line_wrap(True)
412         label.set_max_width_chars(max_width_chars)
413
414     def store_values(self):
415         """ Store selected values """
416         check = self.gui.get_object("encrypt_checkbutton")
417         use_luks = check.get_active()
418
419         check = self.gui.get_object("lvm_checkbutton")
420         use_lvm = check.get_active()
421
422         check = self.gui.get_object("zfs_checkbutton")
423         use_zfs = check.get_active()
424
425         check = self.gui.get_object("home_checkbutton")
426         use_home = check.get_active()
427
428         self.settings.set('use_lvm', use_lvm)
429         self.settings.set('use_luks', use_luks)
430         self.settings.set('use_luks_in_root', True)
431         self.settings.set('luks_root_volume', 'cryptAntergos')
432         self.settings.set('use_zfs', use_zfs)
433         self.settings.set('use_home', use_home)
434
435         if not self.settings.get('use_zfs'):
436             if self.settings.get('use_luks'):
437                 logging.info(
438                     "RebornOS installation will be encrypted using LUKS")
439             if self.settings.get('use_lvm'):
440                 logging.info("RebornOS will be installed using LVM volumes")
441                 if self.settings.get('use_home'):
442                     logging.info(
443                         "RebornOS will be installed using a separate /home volume.")
444             elif self.settings.get('use_home'):
445                 logging.info(
446                     "RebornOS will be installed using a separate /home partition.")
447         else:
448             logging.info("RebornOS will be installed using ZFS")
449             if self.settings.get('use_luks'):
450                 logging.info("RebornOS ZFS installation will be encrypted")
451             if self.settings.get('use_home'):
452                 logging.info(
453                     "RebornOS will be installed using a separate /home volume.")
454
455         if self.next_page == "installation_alongside":
456             self.settings.set('partition_mode', 'alongside')
457         elif self.next_page == "installation_advanced":
458             self.settings.set('partition_mode', 'advanced')
459         elif self.next_page == "installation_automatic":
460             self.settings.set('partition_mode', 'automatic')
461         elif self.next_page == "installation_zfs":
462             self.settings.set('partition_mode', 'zfs')
463
464         # Get sure other modules will know if zfs is activated or not
465         self.settings.set("zfs", use_zfs)
466
467         return True
468
469     def get_next_page(self):
470         """ Returns which page should be the next one """
471         return self.next_page
472
473     def automatic_radiobutton_toggled(self, widget):
474         """ Automatic selected, enable all options """
475         if widget.get_active():
476             check = self.gui.get_object("zfs_checkbutton")
477             if check.get_active():
478                 self.next_page = "installation_zfs"
479             else:
480                 self.next_page = "installation_automatic"
481             # Enable all options
482             self.enable_automatic_options(True)
483
484     def automatic_lvm_toggled(self, widget):
485         """ Enables / disables LVM installation """
486         if widget.get_active():
487             self.next_page = "installation_automatic"
488             # Disable ZFS if using LVM
489             check = self.gui.get_object("zfs_checkbutton")
490             if check.get_active():
491                 check.set_active(False)
492
493     def automatic_zfs_toggled(self, widget):
494         """ Enables / disables ZFS installation """
495         if widget.get_active():
496             self.next_page = "installation_zfs"
497             # Disable LVM if using ZFS
498             check = self.gui.get_object("lvm_checkbutton")
499             if check.get_active():
500                 check.set_active(False)
501         else:
502             self.next_page = "installation_automatic"
503
504     def alongside_radiobutton_toggled(self, widget):
505         """ Alongside selected, disable all automatic options """
506         if widget.get_active():
507             self.next_page = "installation_alongside"
508             self.enable_automatic_options(False)
509
510     def advanced_radiobutton_toggled(self, widget):
511         """ Advanced selected, disable all automatic options """
512         if widget.get_active():
513             self.next_page = "installation_advanced"
514             self.enable_automatic_options(False)
515
516
517 if __name__ == '__main__':
518     from test_screen import _, run
519
520     run('InstallationAsk')