From 32b3a210e0aedd73ed3d8bec93216160b17f7716 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Marczykowski-G=C3=B3recki?= Date: Tue, 11 Jun 2019 05:38:48 +0200 Subject: [PATCH] Make proper text-based initial setup UI Re-use the same kickstart code to configure the system in text mode too. --- qubes-anaconda-addon/firstboot-qubes-text | 160 ------------------ .../org_qubes_os_initial_setup/ks/qubes.py | 2 + .../tui/spokes/qubes_os.py | 112 +++++++----- .../qubes-anaconda-addon.spec | 4 - 4 files changed, 71 insertions(+), 207 deletions(-) delete mode 100755 qubes-anaconda-addon/firstboot-qubes-text diff --git a/qubes-anaconda-addon/firstboot-qubes-text b/qubes-anaconda-addon/firstboot-qubes-text deleted file mode 100755 index 7155267..0000000 --- a/qubes-anaconda-addon/firstboot-qubes-text +++ /dev/null @@ -1,160 +0,0 @@ -#!/bin/bash - -# Failsafe minimal text-mode firstboot - -# Welcome - -if [ "x$1" = "x--help" ]; then - echo "Failsafe minimal text-mode firstboot" - echo "For unattended mode use: $0 " - exit 0 -fi - -echo "########################################################" -echo " Welcome to `cat /etc/qubes-release`" -echo "########################################################" -echo -echo "This is failsafe text-mode firstboot. If you see this message, you have" -echo "some problem with Xorg (most probably video driver)" -echo -echo "Anyway, some basic setup is needed to continue:" - -# User creation - -echo -echo "1. Setup user account" -exists=0 -user=$1 -while [ -z "$user" ]; do - echo -n "Enter desired username (may already exist): " - read user - if echo "$user" | grep -q "[^a-z0-9]"; then - echo "ERROR: Invalid characters in username, try again" - user= - elif id $user > /dev/null 2>&1; then - if [ $(id -u ${user}) -ge 1000 ] && id -n -G ${user} | grep -q qubes; then - echo "OK: Using an existing user: \"${user}\"" - exists=1 - break - fi - echo "ERROR: This user already exists or is not suitable. Please try again" - user= - else - break - fi -done - -if [ ${exists} -eq 0 ]; then - useradd -G qubes -m "$user" || exit 1 - if [ -n "$2" ]; then - echo -e "$2\n$2" | passwd --stdin "$user" - else - while ! passwd "$user"; do true; done - fi -fi - -# Create default VMs - -echo -echo "2. Create default VMs" -echo -echo "Choose one option:" -echo " 1. Create default service VMs, and pre-defined AppVMs (personal, work, untrusted, vault)" -echo " 2. Just create default service VMs" -echo " 3. Do not create any VMs right now, but configure template(s)" -echo " 4. Do not do anything (not recommended, for advanced users only)" -vms_option=$3 -while true; do - if [ -z "$vms_option" ]; then - echo -n "Enter your choice (1/2/3/4): " - read vms_option - fi - if [ "$vms_option" == "1" ]; then - vms_template=yes - vms_service=yes - vms_app=yes - break - elif [ "$vms_option" == "2" ]; then - vms_template=yes - vms_service=yes - break - elif [ "$vms_option" == "3" ]; then - vms_template=yes - break - elif [ "$vms_option" == "4" ]; then - break - else - echo "ERROR: Invalid choice, try again" - vms_option= - fi -done - -set -e - -for service in rdisc kdump libvirt-guests salt-minion; do - systemctl disable ${service}.service || : - systemctl stop ${service}.service || : -done - -if [ "$vms_template" == "yes" ]; then - for template in `ls /var/lib/qubes/vm-templates`; do - echo "-> Configuring template $template..." - qvm-start --no-guid $template - su -g "qubes" -c "qvm-sync-appmenus $template" - $user - qvm-shutdown --wait $template - done - - qubes-prefs --set default-template 'fedora-29' -fi - -if [ "$vms_service" == "yes" -o "$vms_app" == "yes" ]; then - echo "-> Configuring Qubes OS management framework..." - - if test -e /var/log/salt/minion; then - mv /var/log/salt/minion /var/log/salt/minion.install || : - fi - - qubesctl saltutil.clear_cache -l quiet --out quiet - qubesctl saltutil.sync_all -l quiet --out quiet -fi - -states=() - -if [ "$vms_service" == "yes" ]; then - states=("${states[@]}" qvm.sys-net qvm.sys-firewall) -fi - -if [ "$vms_app" == "yes" ]; then - states=("${states[@]}" qvm.personal qvm.work qvm.untrusted qvm.vault) -fi - -if [ "$vms_service" == "yes" -o "$vms_app" == "yes" ]; then - for state in "${states[@]}"; do - echo "-> Requesting creation of VM: ${state#qvm.}" - qubesctl top.enable "${state}" -l quiet --out quiet - done - - echo "-> Creating VMs" - qubesctl "state.highstate" -fi - -if [ "$vms_service" == "yes" ]; then - echo "--> Configuring service VMs" - default_netvm="sys-net" - default_firewallvm="sys-firewall" - - su -g "qubes" -c "qvm-prefs --set ${default_firewallvm} netvm ${default_netvm}" - $user - su -g "qubes" -c "qubes-prefs --set default-netvm ${default_firewallvm}" - $user - su -g "qubes" -c "qubes-prefs --set updatevm ${default_firewallvm}" - $user - su -g "qubes" -c "qubes-prefs --set clockvm ${default_netvm}" - $user - - echo "-> Starting network..." - service qubes-netvm start - - # DispVM creation fails with the following error message, most likely due to missing $DISPLAY: - # "Cannot start qubes-guid!" - #echo "-> Creating DispVM savefile (can take long time)..." - #su -g "qubes" -c "/usr/bin/qvm-create-default-dvm --default-template --default-script" - $user || : -fi - -echo "-> Done." diff --git a/qubes-anaconda-addon/org_qubes_os_initial_setup/ks/qubes.py b/qubes-anaconda-addon/org_qubes_os_initial_setup/ks/qubes.py index b6d1d44..27e36a9 100644 --- a/qubes-anaconda-addon/org_qubes_os_initial_setup/ks/qubes.py +++ b/qubes-anaconda-addon/org_qubes_os_initial_setup/ks/qubes.py @@ -178,6 +178,8 @@ class QubesData(AddonData): def set_stage(self, stage): if self.thread_dialog is not None: self.thread_dialog.set_text(stage) + else: + print(stage) def do_setup(self): qubes_gid = grp.getgrnam('qubes').gr_gid diff --git a/qubes-anaconda-addon/org_qubes_os_initial_setup/tui/spokes/qubes_os.py b/qubes-anaconda-addon/org_qubes_os_initial_setup/tui/spokes/qubes_os.py index 9aaf857..086c44f 100644 --- a/qubes-anaconda-addon/org_qubes_os_initial_setup/tui/spokes/qubes_os.py +++ b/qubes-anaconda-addon/org_qubes_os_initial_setup/tui/spokes/qubes_os.py @@ -27,11 +27,11 @@ _ = lambda x: x N_ = lambda x: x -import subprocess - from pyanaconda.ui.categories.system import SystemCategory from pyanaconda.ui.tui.spokes import NormalTUISpoke -from pyanaconda.constants_text import INPUT_PROCESSED, INPUT_DISCARDED +from simpleline.render.containers import ListColumnContainer +from simpleline.render.widgets import CheckboxWidget +from simpleline.render.screen import InputState from pyanaconda.ui.common import FirstbootOnlySpokeMixIn # export only the HelloWorldSpoke and HelloWorldEditSpoke classes @@ -51,21 +51,14 @@ class QubesOsSpoke(FirstbootOnlySpokeMixIn, NormalTUISpoke): ### class attributes defined by API ### - # title of the spoke - title = N_("Qubes OS") # category this spoke belongs to category = SystemCategory - def __init__(self, app, data, storage, payload, instclass): + def __init__(self, data, storage, payload, instclass): """ :see: pyanaconda.ui.tui.base.UIScreen :see: pyanaconda.ui.tui.base.App - :param app: reference to application which is a main class for TUI - screen handling, it is responsible for mainloop control - and keeping track of the stack where all TUI screens are - scheduled - :type app: instance of pyanaconda.ui.tui.base.App :param data: data object passed to every spoke to load/store data from/to it :type data: pykickstart.base.BaseHandler @@ -79,9 +72,22 @@ class QubesOsSpoke(FirstbootOnlySpokeMixIn, NormalTUISpoke): """ - NormalTUISpoke.__init__(self, app, data, storage, payload, instclass) + NormalTUISpoke.__init__(self, data, storage, payload, instclass) + + self.initialize_start() + + # title of the spoke + self.title = N_("Qubes OS") + + self._container = None + + self.qubes_data = self.data.addons.org_qubes_os_initial_setup + + for attr in self.qubes_data.bool_options: + setattr(self, '_' + attr, getattr(self.qubes_data, attr)) + + self.initialize_done() - self.done = False def initialize(self): """ @@ -95,6 +101,10 @@ class QubesOsSpoke(FirstbootOnlySpokeMixIn, NormalTUISpoke): NormalTUISpoke.initialize(self) + def _add_checkbox(self, name, title): + w = CheckboxWidget(title=title, completed=getattr(self, name)) + self._container.add(w, self._set_checkbox, name) + def refresh(self, args=None): """ The refresh method that is called every time the spoke is displayed. @@ -110,8 +120,44 @@ class QubesOsSpoke(FirstbootOnlySpokeMixIn, NormalTUISpoke): :rtype: bool """ - - return True + super(QubesOsSpoke, self).refresh() + self._container = ListColumnContainer(1) + + w = CheckboxWidget(title=_('Create default system qubes ' + '(sys-net, sys-firewall, default DispVM)'), + completed=self._system_vms) + self._container.add(w, self._set_checkbox, '_system_vms') + w = CheckboxWidget(title=_('Create default application qubes ' + '(personal, work, untrusted, vault)'), + completed=self._default_vms) + self._container.add(w, self._set_checkbox, '_default_vms') + if self.qubes_data.whonix_available: + w = CheckboxWidget( + title=_('Create Whonix Gateway and Workstation qubes ' + '(sys-whonix, anon-whonix)'), + completed=self._whonix_vms) + self._container.add(w, self._set_checkbox, '_whonix_vms') + if self._whonix_vms: + w = CheckboxWidget( + title=_('Enable system and template updates over the Tor anonymity ' + 'network using Whonix'), + completed=self._whonix_default) + self._container.add(w, self._set_checkbox, '_whonix_default') + if self.qubes_data.usbvm_available: + w = CheckboxWidget( + title=_('Create USB qube holding all USB controllers (sys-usb)'), + completed=self._usbvm) + self._container.add(w, self._set_checkbox, '_usbvm') + if self._usbvm: + w = CheckboxWidget( + title=_('Use sys-net qube for both networking and USB devices'), + completed=self._usbvm_with_netvm) + self._container.add(w, self._set_checkbox, '_usbvm_with_netvm') + + self.window.add_with_separator(self._container) + + def _set_checkbox(self, name): + setattr(self, name, not getattr(self, name)) def apply(self): """ @@ -120,10 +166,10 @@ class QubesOsSpoke(FirstbootOnlySpokeMixIn, NormalTUISpoke): """ - proc = subprocess.Popen(["/usr/share/qubes/firstboot-qubes-text"]) - proc.wait() + for attr in self.qubes_data.bool_options: + setattr(self.qubes_data, attr, getattr(self, '_' + attr)) - self.done = True + self.qubes_data.seen = True def execute(self): """ @@ -147,7 +193,7 @@ class QubesOsSpoke(FirstbootOnlySpokeMixIn, NormalTUISpoke): """ - return self.done + return self.qubes_data.seen @property def status(self): @@ -179,28 +225,8 @@ class QubesOsSpoke(FirstbootOnlySpokeMixIn, NormalTUISpoke): """ - if key: - # Do something with the user's input. - pass - - # no other actions scheduled, apply changes - self.apply() - - # close the current screen (remove it from the stack) - self.close() - return INPUT_PROCESSED - - def prompt(self, args=None): - """ - The prompt method that is called by the main loop to get the prompt - for this screen. - - :param args: optional argument that can be passed to App.switch_screen* - methods - :type args: anything - :return: text that should be used in the prompt for the input - :rtype: unicode|None - - """ + if self._container.process_user_input(key): + self.apply() + return InputState.PROCESSED_AND_REDRAW - return _("Please press enter to start Qubes OS configuration.") + return super().input(args, key) diff --git a/qubes-anaconda-addon/qubes-anaconda-addon.spec b/qubes-anaconda-addon/qubes-anaconda-addon.spec index 3a500f6..7ddb574 100644 --- a/qubes-anaconda-addon/qubes-anaconda-addon.spec +++ b/qubes-anaconda-addon/qubes-anaconda-addon.spec @@ -25,16 +25,12 @@ at first boot time. %install rm -rf $RPM_BUILD_ROOT -install -d $RPM_BUILD_ROOT/%{_datadir}/qubes -install --mode 0755 firstboot-qubes-text $RPM_BUILD_ROOT/%{_datadir}/qubes/firstboot-qubes-text - install -d $RPM_BUILD_ROOT/%{_datadir}/anaconda/addons cp -a org_qubes_os_initial_setup $RPM_BUILD_ROOT/%{_datadir}/anaconda/addons/ %files %defattr(-,root,root,-) %doc LICENSE README -%{_datadir}/qubes/firstboot-qubes-text %dir %{_datadir}/anaconda/addons/org_qubes_os_initial_setup %{_datadir}/anaconda/addons/org_qubes_os_initial_setup/*