From 2d38bdfda9698d83320b2bd59bd0fcdc954c116d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Pierret=20=28fepitre=29?=
Date: Wed, 28 Nov 2018 15:11:39 +0100
Subject: [PATCH] Initial commit
---
.gitignore | 1 +
0001-anaconda-add-Qubes-installclass.patch | 116 +
0002-anaconda-add-Qubes-post-scripts.patch | 89 +
...anaconda-remove-other-installclasses.patch | 154 ++
...start-network-during-install-set-def.patch | 1820 +++++++++++++++
...ve-network-setup-from-text-interface.patch | 387 ++++
...b-config-setup-by-removing-non-xen-o.patch | 30 +
...make-encrypted-partitions-by-default.patch | 32 +
0008-anaconda-set-default-grub-theme.patch | 39 +
...ions-can_dual_boot-and-can_update-to.patch | 30 +
...efimgr-specify-root-iutil.getSysroot.patch | 31 +
...conda-generate-xen-efi-configuration.patch | 121 +
...cut-module-to-work-with-reduced-depe.patch | 65 +
...taller-kernel-parameters-as-default-.patch | 61 +
...nel-install-instead-of-grubby-to-reg.patch | 51 +
...egular-expression-determining-Releas.patch | 33 +
...fail-during-initramfs-start-up-due-t.patch | 38 +
...-Disable-the-NTP-configuration-spoke.patch | 733 ++++++
...eless-on-Qubes-dependencies-on-netwo.patch | 37 +
...p-NTP-installation-and-setup-in-dom0.patch | 35 +
...p_grub-parameter-and-allow-boot-encr.patch | 56 +
...default-partitioning-scheme-to-LVM-T.patch | 65 +
...conda-add-console-none-Xen-parameter.patch | 29 +
...0_mem-min-1024M-to-default-xen-cmdli.patch | 34 +
...om0-maxmem-to-4GB-to-limit-its-overh.patch | 36 +
0025-anaconda-disable-iommu-for-IGFX.patch | 49 +
...da-check-for-virtualization-features.patch | 121 +
...aconda-generate-proper-extlinux.conf.patch | 48 +
...rash-when-no-target-disk-is-availabl.patch | 51 +
...r-Interrupt-Remapping-as-required-fe.patch | 32 +
...naconda-lock-root-account-by-default.patch | 32 +
...onda-add-option-to-lock-root-account.patch | 161 ++
...k-add-user-to-wheel-and-qubes-groups.patch | 35 +
...user-configuration-spoke-for-QubesOS.patch | 389 ++++
...re-that-a-user-is-created-at-install.patch | 94 +
...xen.efi-upgraded-during-each-install.patch | 42 +
...re-the-latest-version-is-placed-as-x.patch | 39 +
...-message-about-unusupported-hardware.patch | 34 +
...lso-for-message-about-AMD-interrupt-.patch | 35 +
...in-memory-kickstart-representation-f.patch | 45 +
...efault-scheme-in-custom-partitioning.patch | 43 +
...da-fix-interrupt-remapping-detection.patch | 30 +
...-anaconda-Fix-macOS-EFI-Installation.patch | 276 +++
...per-subvolume-argument-when-booting-.patch | 46 +
...discard-option-for-dom0-filesystems-.patch | 86 +
...ode-scan-to-default-Xen-command-line.patch | 45 +
...oid-adding-duplicated-kernel-entries.patch | 36 +
...tem-Requirements-URL-and-typo-in-har.patch | 51 +
...naconda-save-keyboard-layout-to-udev.patch | 84 +
0049-anaconda-fix-root-password-dialog.patch | 67 +
...nda-mark-qubes-user-name-as-reserved.patch | 35 +
...t-off-xen-option-during-installation.patch | 44 +
...Qubes-specific-code-for-Fedora-21-ve.patch | 50 +
...onda-require-user-password-being-set.patch | 151 ++
...abort-installation-on-X-startup-fail.patch | 44 +
...onda-fix-encryption-passphrase-check.patch | 34 +
0056-anaconda-disable-os-prober.patch | 30 +
anaconda-25.20.9.tar.bz2 | Bin 0 -> 2585884 bytes
anaconda.spec | 1955 +++++++++++++++++
59 files changed, 8437 insertions(+)
create mode 100644 .gitignore
create mode 100644 0001-anaconda-add-Qubes-installclass.patch
create mode 100644 0002-anaconda-add-Qubes-post-scripts.patch
create mode 100644 0003-anaconda-remove-other-installclasses.patch
create mode 100644 0004-anaconda-do-not-start-network-during-install-set-def.patch
create mode 100644 0005-anaconda-remove-network-setup-from-text-interface.patch
create mode 100644 0006-anaconda-fix-grub-config-setup-by-removing-non-xen-o.patch
create mode 100644 0007-anaconda-make-encrypted-partitions-by-default.patch
create mode 100644 0008-anaconda-set-default-grub-theme.patch
create mode 100644 0009-anaconda-add-options-can_dual_boot-and-can_update-to.patch
create mode 100644 0010-anaconda-efimgr-specify-root-iutil.getSysroot.patch
create mode 100644 0011-anaconda-generate-xen-efi-configuration.patch
create mode 100644 0012-anaconda-fix-dracut-module-to-work-with-reduced-depe.patch
create mode 100644 0013-anaconda-use-installer-kernel-parameters-as-default-.patch
create mode 100644 0014-anaconda-use-kernel-install-instead-of-grubby-to-reg.patch
create mode 100644 0015-anaconda-Fix-a-regular-expression-determining-Releas.patch
create mode 100644 0016-anaconda-Do-not-fail-during-initramfs-start-up-due-t.patch
create mode 100644 0017-anaconda-Disable-the-NTP-configuration-spoke.patch
create mode 100644 0018-anaconda-drop-useless-on-Qubes-dependencies-on-netwo.patch
create mode 100644 0019-anaconda-skip-NTP-installation-and-setup-in-dom0.patch
create mode 100644 0020-anaconda-add-skip_grub-parameter-and-allow-boot-encr.patch
create mode 100644 0021-anaconda-switch-default-partitioning-scheme-to-LVM-T.patch
create mode 100644 0022-anaconda-add-console-none-Xen-parameter.patch
create mode 100644 0023-anaconda-add-dom0_mem-min-1024M-to-default-xen-cmdli.patch
create mode 100644 0024-anaconda-limit-dom0-maxmem-to-4GB-to-limit-its-overh.patch
create mode 100644 0025-anaconda-disable-iommu-for-IGFX.patch
create mode 100644 0026-anaconda-check-for-virtualization-features.patch
create mode 100644 0027-anaconda-generate-proper-extlinux.conf.patch
create mode 100644 0028-anaconda-don-t-crash-when-no-target-disk-is-availabl.patch
create mode 100644 0029-anaconda-consider-Interrupt-Remapping-as-required-fe.patch
create mode 100644 0030-anaconda-lock-root-account-by-default.patch
create mode 100644 0031-anaconda-add-option-to-lock-root-account.patch
create mode 100644 0032-anaconda-check-add-user-to-wheel-and-qubes-groups.patch
create mode 100644 0033-anaconda-Modify-user-configuration-spoke-for-QubesOS.patch
create mode 100644 0034-anaconda-Make-sure-that-a-user-is-created-at-install.patch
create mode 100644 0035-anaconda-xen.efi-upgraded-during-each-install.patch
create mode 100644 0036-anaconda-make-sure-the-latest-version-is-placed-as-x.patch
create mode 100644 0037-anaconda-update-message-about-unusupported-hardware.patch
create mode 100644 0038-anaconda-check-also-for-message-about-AMD-interrupt-.patch
create mode 100644 0039-anaconda-Remove-in-memory-kickstart-representation-f.patch
create mode 100644 0040-anaconda-fix-default-scheme-in-custom-partitioning.patch
create mode 100644 0041-anaconda-fix-interrupt-remapping-detection.patch
create mode 100644 0042-anaconda-Fix-macOS-EFI-Installation.patch
create mode 100644 0043-anaconda-use-proper-subvolume-argument-when-booting-.patch
create mode 100644 0044-anaconda-enable-discard-option-for-dom0-filesystems-.patch
create mode 100644 0045-anaconda-Add-ucode-scan-to-default-Xen-command-line.patch
create mode 100644 0046-anaconda-avoid-adding-duplicated-kernel-entries.patch
create mode 100644 0047-anaconda-Fix-System-Requirements-URL-and-typo-in-har.patch
create mode 100644 0048-anaconda-save-keyboard-layout-to-udev.patch
create mode 100644 0049-anaconda-fix-root-password-dialog.patch
create mode 100644 0050-anaconda-mark-qubes-user-name-as-reserved.patch
create mode 100644 0051-anaconda-add-smt-off-xen-option-during-installation.patch
create mode 100644 0052-anaconda-update-Qubes-specific-code-for-Fedora-21-ve.patch
create mode 100644 0053-anaconda-require-user-password-being-set.patch
create mode 100644 0054-anaconda-abort-installation-on-X-startup-fail.patch
create mode 100644 0055-anaconda-fix-encryption-passphrase-check.patch
create mode 100644 0056-anaconda-disable-os-prober.patch
create mode 100644 anaconda-25.20.9.tar.bz2
create mode 100644 anaconda.spec
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..6f91eb5
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+pkgs/
diff --git a/0001-anaconda-add-Qubes-installclass.patch b/0001-anaconda-add-Qubes-installclass.patch
new file mode 100644
index 0000000..2042bf8
--- /dev/null
+++ b/0001-anaconda-add-Qubes-installclass.patch
@@ -0,0 +1,116 @@
+From aef23d6edb86739638cdaf08e7892683681392b1 Mon Sep 17 00:00:00 2001
+From: Tomasz Sterna
+Date: Fri, 19 Oct 2018 08:02:11 +0200
+Subject: [PATCH] anaconda: add Qubes installclass
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Signed-off-by: Frédéric Pierret
+---
+ pyanaconda/installclasses/qubes.py | 93 ++++++++++++++++++++++++++++++++++++++
+ 1 file changed, 93 insertions(+)
+ create mode 100644 pyanaconda/installclasses/qubes.py
+
+diff --git a/pyanaconda/installclasses/qubes.py b/pyanaconda/installclasses/qubes.py
+new file mode 100644
+index 000000000..e98912b63
+--- /dev/null
++++ b/pyanaconda/installclasses/qubes.py
+@@ -0,0 +1,93 @@
++#
++# qubes.py
++#
++# Copyright (C) 2011 Invisible Things Lab All rights reserved.
++#
++# This program is free software; you can redistribute it and/or modify
++# it under the terms of the GNU General Public License as published by
++# the Free Software Foundation; either version 2 of the License, or
++# (at your option) any later version.
++#
++# This program is distributed in the hope that it will be useful,
++# but WITHOUT ANY WARRANTY; without even the implied warranty of
++# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++# GNU General Public License for more details.
++#
++# You should have received a copy of the GNU General Public License
++# along with this program. If not, see .
++#
++
++
++from pyanaconda.installclass import BaseInstallClass
++from pyanaconda.constants import *
++from pyanaconda.product import *
++from pyanaconda import network
++from pyanaconda.i18n import N_
++import os, types
++import blivet.platform
++
++from blivet.size import Size
++from blivet.platform import platform
++from decimal import Decimal
++
++class InstallClass(BaseInstallClass):
++ # name has underscore used for mnemonics, strip if you dont need it
++ id = "qubes"
++ name = N_("Qubes")
++ _description = N_("The default installation of %s is a minimal install. "
++ "You can optionally select a different set of software "
++ "now.")
++ _descriptionFields = (productName,)
++ sortPriority = 20000
++ hidden = 0
++ efi_dir = 'qubes'
++ _l10n_domain = "anaconda"
++ installUpdates = False
++
++ bootloaderTimeoutDefault = 5
++
++ tasks = [(N_("Minimal"), ["base", "base-x", "kde-desktop-qubes", "qubes" ]) ]
++
++ help_placeholder = "QubesPlaceholder.html"
++ help_placeholder_with_links = "QubesPlaceholderWithLinks.html"
++
++ def getPackagePaths(self, uri):
++ if not type(uri) == types.ListType:
++ uri = [uri,]
++
++ return {'Installation Repo': uri}
++
++ def configure(self, anaconda):
++ BaseInstallClass.configure(self, anaconda)
++ self.setDefaultPartitioning(anaconda.storage)
++
++ def setDefaultPartitioning(self, storage):
++ BaseInstallClass.setDefaultPartitioning(self,
++ storage)
++ for autoreq in list(storage.autopart_requests):
++ if autoreq.mountpoint == "/":
++ autoreq.max_size=None
++ autoreq.required_space=Size("10GiB")
++ if autoreq.mountpoint == "/home":
++ storage.autopart_requests.remove(autoreq)
++ if autoreq.mountpoint == "/boot/efi":
++ autoreq.max_size=Size("500MiB")
++ if autoreq.mountpoint == "/boot" and \
++ isinstance(platform, blivet.platform.EFI):
++ # xen.efi don't need /boot
++ storage.autopart_requests.remove(autoreq)
++
++ def productMatches(self, oldprod):
++ if oldprod is None:
++ return False
++
++ if oldprod.startswith(productName):
++ return True
++
++ return False
++
++ def versionMatches(self, oldver):
++ return True
++
++ def __init__(self):
++ BaseInstallClass.__init__(self)
+--
+2.14.4
+
diff --git a/0002-anaconda-add-Qubes-post-scripts.patch b/0002-anaconda-add-Qubes-post-scripts.patch
new file mode 100644
index 0000000..edce233
--- /dev/null
+++ b/0002-anaconda-add-Qubes-post-scripts.patch
@@ -0,0 +1,89 @@
+From 0cb13168feb3dfd4b9510c89ed3bc005a23795ca Mon Sep 17 00:00:00 2001
+From: Tomasz Sterna
+Date: Fri, 19 Oct 2018 08:02:11 +0200
+Subject: [PATCH] anaconda: add Qubes post-scripts
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Signed-off-by: Frédéric Pierret
+---
+ data/post-scripts/40-qubes-alt-kernels.ks | 20 ++++++++++++++++++++
+ data/post-scripts/50-qubes.ks | 5 +++++
+ data/post-scripts/60-systemd-preset.ks | 13 +++++++++++++
+ data/post-scripts/Makefile.am | 2 +-
+ 4 files changed, 39 insertions(+), 1 deletion(-)
+ create mode 100644 data/post-scripts/40-qubes-alt-kernels.ks
+ create mode 100644 data/post-scripts/50-qubes.ks
+ create mode 100644 data/post-scripts/60-systemd-preset.ks
+
+diff --git a/data/post-scripts/40-qubes-alt-kernels.ks b/data/post-scripts/40-qubes-alt-kernels.ks
+new file mode 100644
+index 000000000..4909a99ee
+--- /dev/null
++++ b/data/post-scripts/40-qubes-alt-kernels.ks
+@@ -0,0 +1,20 @@
++%post --nochroot
++
++for pkg in /run/install/repo/extrakernels/*.rpm; do
++ name=`basename $pkg .rpm`
++ rpm --root=$ANA_INSTALL_PATH -q $name > /dev/null || rpm --root=$ANA_INSTALL_PATH -i --oldpackage $pkg
++done
++
++# Set grub default to the current kernel if running not the latest one
++latest=`basename /run/install/repo/Packages/k/kernel-[0-9]*.rpm .rpm|cut -d- -f2-`
++if [ "$latest" != "`uname -r`" ]; then
++ rootdev=`grep " $ANA_INSTALL_PATH " /proc/mounts | cut -f 1 -d ' '`
++ sysid=`blkid -o value -s UUID $rootdev`
++ xenver=`dmesg | grep 'Xen version:' | sed -e 's/.*version: \([0-9.]\+\).*/\1/'`
++ grubid="gnulinux-advanced-$sysid"
++ grubid="$grubid>xen-hypervisor-$xenver-$sysid"
++ grubid="$grubid>xen-gnulinux-`uname -r`-advanced-$sysid"
++ grub2-set-default --boot-directory=$ANA_INSTALL_PATH/boot "$grubid"
++fi
++
++%end
+diff --git a/data/post-scripts/50-qubes.ks b/data/post-scripts/50-qubes.ks
+new file mode 100644
+index 000000000..1b9238b40
+--- /dev/null
++++ b/data/post-scripts/50-qubes.ks
+@@ -0,0 +1,5 @@
++%post
++
++rpm --import /etc/pki/rpm-gpg/*
++
++%end
+diff --git a/data/post-scripts/60-systemd-preset.ks b/data/post-scripts/60-systemd-preset.ks
+new file mode 100644
+index 000000000..9e6cb3f3a
+--- /dev/null
++++ b/data/post-scripts/60-systemd-preset.ks
+@@ -0,0 +1,13 @@
++%post
++
++# preset all services, to not worry about package installation order (preset
++# files vs services)
++systemctl preset-all
++
++systemctl enable initial-setup.service
++
++# systemctl preset-all disables default target
++# (https://bugzilla.redhat.com/1316387), re-enable it manually
++systemctl set-default graphical.target
++
++%end
+diff --git a/data/post-scripts/Makefile.am b/data/post-scripts/Makefile.am
+index 7d78d4bc3..ad2f6497d 100644
+--- a/data/post-scripts/Makefile.am
++++ b/data/post-scripts/Makefile.am
+@@ -16,5 +16,5 @@
+ # along with this program. If not, see .
+
+ postscriptsdir = $(datadir)/$(PACKAGE_NAME)/post-scripts
+-dist_postscripts_DATA = 80-setfilecons.ks 90-copy-screenshots.ks 99-copy-logs.ks
++dist_postscripts_DATA = 40-qubes-alt-kernels.ks 50-qubes.ks 60-systemd-preset.ks 80-setfilecons.ks 90-copy-screenshots.ks 99-copy-logs.ks
+ MAINTAINERCLEANFILES = Makefile.in
+--
+2.14.4
+
diff --git a/0003-anaconda-remove-other-installclasses.patch b/0003-anaconda-remove-other-installclasses.patch
new file mode 100644
index 0000000..8a4b6c6
--- /dev/null
+++ b/0003-anaconda-remove-other-installclasses.patch
@@ -0,0 +1,154 @@
+From 5ccbe4b9f6265ac4e07f0539da39db809fc8020a Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Pierret=20=28fepitre=29?=
+
+Date: Fri, 19 Oct 2018 08:02:11 +0200
+Subject: [PATCH] anaconda: remove other installclasses
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Signed-off-by: Frédéric Pierret
+---
+ pyanaconda/installclasses/fedora.py | 58 ---------------------------------
+ pyanaconda/installclasses/rhel.py | 64 -------------------------------------
+ 2 files changed, 122 deletions(-)
+ delete mode 100644 pyanaconda/installclasses/fedora.py
+ delete mode 100644 pyanaconda/installclasses/rhel.py
+
+diff --git a/pyanaconda/installclasses/fedora.py b/pyanaconda/installclasses/fedora.py
+deleted file mode 100644
+index c9ced65fd..000000000
+--- a/pyanaconda/installclasses/fedora.py
++++ /dev/null
+@@ -1,58 +0,0 @@
+-#
+-# fedora.py
+-#
+-# Copyright (C) 2007 Red Hat, Inc. All rights reserved.
+-#
+-# This program is free software; you can redistribute it and/or modify
+-# it under the terms of the GNU General Public License as published by
+-# the Free Software Foundation; either version 2 of the License, or
+-# (at your option) any later version.
+-#
+-# This program is distributed in the hope that it will be useful,
+-# but WITHOUT ANY WARRANTY; without even the implied warranty of
+-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+-# GNU General Public License for more details.
+-#
+-# You should have received a copy of the GNU General Public License
+-# along with this program. If not, see .
+-#
+-
+-from pyanaconda.installclass import BaseInstallClass
+-from pyanaconda.product import productName
+-from pyanaconda import network
+-from pyanaconda import nm
+-
+-class FedoraBaseInstallClass(BaseInstallClass):
+- name = "Fedora"
+- sortPriority = 10000
+- if productName.startswith("Red Hat "): # pylint: disable=no-member
+- hidden = True
+-
+- _l10n_domain = "anaconda"
+-
+- efi_dir = "fedora"
+-
+- help_placeholder = "FedoraPlaceholder.html"
+- help_placeholder_with_links = "FedoraPlaceholderWithLinks.html"
+-
+- def configure(self, anaconda):
+- BaseInstallClass.configure(self, anaconda)
+- BaseInstallClass.setDefaultPartitioning(self, anaconda.storage)
+-
+- def setNetworkOnbootDefault(self, ksdata):
+- if any(nd.onboot for nd in ksdata.network.network if nd.device):
+- return
+- # choose first wired device having link
+- for dev in nm.nm_devices():
+- if nm.nm_device_type_is_wifi(dev):
+- continue
+- try:
+- link_up = nm.nm_device_carrier(dev)
+- except (nm.UnknownDeviceError, nm.PropertyNotFoundError):
+- continue
+- if link_up:
+- network.update_onboot_value(dev, True, ksdata=ksdata)
+- break
+-
+- def __init__(self):
+- BaseInstallClass.__init__(self)
+diff --git a/pyanaconda/installclasses/rhel.py b/pyanaconda/installclasses/rhel.py
+deleted file mode 100644
+index 7e907e4bd..000000000
+--- a/pyanaconda/installclasses/rhel.py
++++ /dev/null
+@@ -1,64 +0,0 @@
+-#
+-# rhel.py
+-#
+-# Copyright (C) 2010 Red Hat, Inc. All rights reserved.
+-#
+-# This program is free software; you can redistribute it and/or modify
+-# it under the terms of the GNU General Public License as published by
+-# the Free Software Foundation; either version 2 of the License, or
+-# (at your option) any later version.
+-#
+-# This program is distributed in the hope that it will be useful,
+-# but WITHOUT ANY WARRANTY; without even the implied warranty of
+-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+-# GNU General Public License for more details.
+-#
+-# You should have received a copy of the GNU General Public License
+-# along with this program. If not, see .
+-#
+-
+-from pyanaconda.installclass import BaseInstallClass
+-from pyanaconda.product import productName
+-from pyanaconda import network
+-from pyanaconda import nm
+-
+-class RHELBaseInstallClass(BaseInstallClass):
+- name = "Red Hat Enterprise Linux"
+- sortPriority = 10000
+- if not productName.startswith("Red Hat "): # pylint: disable=no-member
+- hidden = True
+- defaultFS = "xfs"
+-
+- bootloaderTimeoutDefault = 5
+-
+- ignoredPackages = ["ntfsprogs"]
+-
+- installUpdates = False
+-
+- _l10n_domain = "comps"
+-
+- efi_dir = "redhat"
+-
+- help_placeholder = "RHEL7Placeholder.html"
+- help_placeholder_with_links = "RHEL7PlaceholderWithLinks.html"
+-
+- def configure(self, anaconda):
+- BaseInstallClass.configure(self, anaconda)
+- BaseInstallClass.setDefaultPartitioning(self, anaconda.storage)
+-
+- def setNetworkOnbootDefault(self, ksdata):
+- if any(nd.onboot for nd in ksdata.network.network if nd.device):
+- return
+- # choose the device used during installation
+- # (ie for majority of cases the one having the default route)
+- dev = network.default_route_device() \
+- or network.default_route_device(family="inet6")
+- if not dev:
+- return
+- # ignore wireless (its ifcfgs would need to be handled differently)
+- if nm.nm_device_type_is_wifi(dev):
+- return
+- network.update_onboot_value(dev, True, ksdata=ksdata)
+-
+- def __init__(self):
+- BaseInstallClass.__init__(self)
+--
+2.14.4
+
diff --git a/0004-anaconda-do-not-start-network-during-install-set-def.patch b/0004-anaconda-do-not-start-network-during-install-set-def.patch
new file mode 100644
index 0000000..e568551
--- /dev/null
+++ b/0004-anaconda-do-not-start-network-during-install-set-def.patch
@@ -0,0 +1,1820 @@
+From 7d67dbbb889a754c71a878fa69fcf2aa18fcdbb7 Mon Sep 17 00:00:00 2001
+From: Marek Marczykowski
+Date: Fri, 19 Oct 2018 08:02:11 +0200
+Subject: [PATCH] anaconda: do not start network during install, set default
+ hostname and write minimal network configuration
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Signed-off-by: Frédéric Pierret
+---
+ pyanaconda/network.py | 14 +-
+ pyanaconda/ui/gui/spokes/network.py | 1750 -----------------------------------
+ 2 files changed, 8 insertions(+), 1756 deletions(-)
+ delete mode 100644 pyanaconda/ui/gui/spokes/network.py
+
+diff --git a/pyanaconda/network.py b/pyanaconda/network.py
+index b92fb8acf..5d3dcd604 100644
+--- a/pyanaconda/network.py
++++ b/pyanaconda/network.py
+@@ -54,7 +54,7 @@ networkConfFile = "%s/network" % (sysconfigDir)
+ hostnameFile = "/etc/hostname"
+ ipv6ConfFile = "/etc/sysctl.d/anaconda.conf"
+ ifcfgLogFile = "/tmp/ifcfg.log"
+-DEFAULT_HOSTNAME = "localhost.localdomain"
++DEFAULT_HOSTNAME = "dom0"
+
+ ifcfglog = None
+
+@@ -1191,24 +1191,26 @@ def write_sysconfig_network(rootpath, overwrite=False):
+
+ with open(cfgfile, "w") as f:
+ f.write("# Created by anaconda\n")
++ f.write("NETWORKING=no\n")
+ return True
+
+ def write_network_config(storage, ksdata, instClass, rootpath):
+ # overwrite previous settings for LiveCD or liveimg installations
+ overwrite = flags.livecdInstall or ksdata.method.method == "liveimg"
+
+- write_hostname(rootpath, ksdata, overwrite=overwrite)
++ write_hostname(rootpath, ksdata, overwrite=flags.livecdInstall)
+ if ksdata.network.hostname != DEFAULT_HOSTNAME:
+ set_hostname(ksdata.network.hostname)
+- write_sysconfig_network(rootpath, overwrite=overwrite)
++ write_sysconfig_network(rootpath, overwrite=flags.livecdInstall)
+ disableIPV6(rootpath)
+ copyIfcfgFiles(rootpath)
+ copyDhclientConfFiles(rootpath)
+ copyFileToPath("/etc/resolv.conf", rootpath, overwrite=overwrite)
+- instClass.setNetworkOnbootDefault(ksdata)
+- autostartFCoEDevices(rootpath, storage, ksdata)
+
+-def update_hostname_data(ksdata, hostname):
++def update_hostname_data(ksdata, hostname=None):
++ if not hostname:
++ # Default to 'dom0' in Qubes
++ hostname = 'dom0'
+ log.debug("updating host name %s", hostname)
+ hostname_found = False
+ for nd in ksdata.network.network:
+diff --git a/pyanaconda/ui/gui/spokes/network.py b/pyanaconda/ui/gui/spokes/network.py
+deleted file mode 100644
+index a15a07ccb..000000000
+--- a/pyanaconda/ui/gui/spokes/network.py
++++ /dev/null
+@@ -1,1750 +0,0 @@
+-# Network configuration spoke classes
+-#
+-# Copyright (C) 2011 Red Hat, Inc.
+-#
+-# This copyrighted material is made available to anyone wishing to use,
+-# modify, copy, or redistribute it subject to the terms and conditions of
+-# the GNU General Public License v.2, or (at your option) any later version.
+-# This program is distributed in the hope that it will be useful, but WITHOUT
+-# ANY WARRANTY expressed or implied, including the implied warranties of
+-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
+-# Public License for more details. You should have received a copy of the
+-# GNU General Public License along with this program; if not, write to the
+-# Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+-# 02110-1301, USA. Any Red Hat trademarks that are incorporated in the
+-# source code or documentation are not subject to the GNU General Public
+-# License and may only be used or replicated with the express permission of
+-# Red Hat, Inc.
+-#
+-
+-import gi
+-gi.require_version("Gtk", "3.0")
+-gi.require_version("GLib", "2.0")
+-gi.require_version("GObject", "2.0")
+-gi.require_version("Pango", "1.0")
+-gi.require_version("Gio", "2.0")
+-gi.require_version("NM", "1.0")
+-
+-from gi.repository import Gtk
+-from gi.repository import GLib, GObject, Pango, Gio, NM
+-
+-from pyanaconda.flags import can_touch_runtime_system
+-from pyanaconda.i18n import _, N_, C_, CN_
+-from pyanaconda.flags import flags as anaconda_flags
+-from pyanaconda.ui.communication import hubQ
+-from pyanaconda.ui.gui import GUIObject
+-from pyanaconda.ui.gui.spokes import NormalSpoke, StandaloneSpoke
+-from pyanaconda.ui.categories.system import SystemCategory
+-from pyanaconda.ui.gui.hubs.summary import SummaryHub
+-from pyanaconda.ui.gui.utils import gtk_call_once, escape_markup, really_hide, really_show
+-from pyanaconda.ui.common import FirstbootSpokeMixIn
+-from pyanaconda.iutil import startProgram
+-from pyanaconda.constants import ANACONDA_ENVIRON
+-
+-from pyanaconda import network
+-from pyanaconda import nm
+-
+-import dbus
+-import dbus.service
+-# Used for ascii_letters and hexdigits constants
+-import string # pylint: disable=deprecated-module
+-from uuid import uuid4
+-
+-from dbus.mainloop.glib import DBusGMainLoop
+-DBusGMainLoop(set_as_default=True)
+-
+-import logging
+-log = logging.getLogger("anaconda")
+-
+-NM._80211ApFlags = getattr(NM, "80211ApFlags")
+-NM._80211ApSecurityFlags = getattr(NM, "80211ApSecurityFlags")
+-NM._80211Mode = getattr(NM, "80211Mode")
+-
+-NM_SERVICE = "org.freedesktop.NetworkManager"
+-NM_SECRET_AGENT_GET_SECRETS_FLAG_ALLOW_INTERACTION = 0x1
+-SECRET_AGENT_IFACE = 'org.freedesktop.NetworkManager.SecretAgent'
+-AGENT_MANAGER_IFACE = 'org.freedesktop.NetworkManager.AgentManager'
+-AGENT_MANAGER_PATH = "/org/freedesktop/NetworkManager/AgentManager"
+-
+-IPV4_CONFIG = "IPv4"
+-IPV6_CONFIG = "IPv6"
+-
+-DEVICES_COLUMN_TITLE = 2
+-DEVICES_COLUMN_OBJECT = 3
+-
+-nmclient = NM.Client.new()
+-
+-def localized_string_of_device_state(device, state):
+- s = _("Status unknown (missing)")
+-
+- if state == NM.DeviceState.UNKNOWN:
+- s = _("Status unknown")
+- elif state == NM.DeviceState.UNMANAGED:
+- s = _("Unmanaged")
+- elif state == NM.DeviceState.UNAVAILABLE:
+- if not device:
+- s = _("Unavailable")
+- elif device.get_firmware_missing():
+- s = _("Firmware missing")
+- else:
+- s = _("Unavailable")
+- elif state == NM.DeviceState.DISCONNECTED:
+- if (device and device.get_device_type() == NM.DeviceType.ETHERNET
+- and not device.get_carrier()):
+- s = _("Cable unplugged")
+- else:
+- s = _("Disconnected")
+- elif state in (NM.DeviceState.PREPARE,
+- NM.DeviceState.CONFIG,
+- NM.DeviceState.IP_CONFIG,
+- NM.DeviceState.IP_CHECK):
+- s = _("Connecting")
+- elif state == NM.DeviceState.NEED_AUTH:
+- s = _("Authentication required")
+- elif state == NM.DeviceState.ACTIVATED:
+- s = _("Connected")
+- elif state == NM.DeviceState.DEACTIVATING:
+- s = _("Disconnecting")
+- elif state == NM.DeviceState.FAILED:
+- s = _("Connection failed")
+-
+- return s
+-
+-__all__ = ["NetworkSpoke", "NetworkStandaloneSpoke"]
+-
+-class CellRendererSignal(Gtk.CellRendererPixbuf):
+-
+- __gtype_name__ = "CellRendererSignal"
+- __gproperties__ = {
+- "signal": (GObject.TYPE_UINT,
+- "Signal", "Signal",
+- 0, GObject.G_MAXUINT, 0,
+- GObject.PARAM_READWRITE),
+- }
+-
+- def __init__(self):
+- Gtk.CellRendererPixbuf.__init__(self)
+- self.signal = 0
+-
+-
+- def do_get_property(self, prop):
+- if prop.name == 'signal':
+- return self.signal
+- else:
+- raise AttributeError('unknown property %s' % prop.name)
+-
+- def do_set_property(self, prop, value):
+- if prop.name == 'signal':
+- self.signal = value
+- self._set_icon_name(value)
+- else:
+- raise AttributeError('unknown property %s' % prop.name)
+-
+- def _set_icon_name(self, value):
+-
+- if value == 0:
+- self.set_property("gicon", None)
+-
+- if value < 20:
+- icon_name = "network-wireless-signal-none-symbolic"
+- elif value < 40:
+- icon_name = "network-wireless-signal-weak-symbolic"
+- elif value < 50:
+- icon_name = "network-wireless-signal-ok-symbolic"
+- elif value < 80:
+- icon_name = "network-wireless-signal-good-symbolic"
+- else:
+- icon_name = "network-wireless-signal-excellent-symbolic"
+-
+- icon = Gio.ThemedIcon.new_with_default_fallbacks(icon_name)
+- self.set_property("gicon", icon)
+-
+-
+-NM_AP_SEC_UNKNOWN = 0
+-NM_AP_SEC_NONE = 1
+-NM_AP_SEC_WEP = 2
+-NM_AP_SEC_WPA = 3
+-NM_AP_SEC_WPA2 = 4
+-
+-class CellRendererSecurity(Gtk.CellRendererPixbuf):
+-
+- __gtype_name__ = "CellRendererSecurity"
+- __gproperties__ = {
+- "security": (GObject.TYPE_UINT,
+- "Security", "Security",
+- 0, GObject.G_MAXUINT, 0,
+- GObject.PARAM_READWRITE),
+- }
+-
+- def __init__(self):
+- Gtk.CellRendererPixbuf.__init__(self)
+- self.security = NM_AP_SEC_UNKNOWN
+- self.icon_name = ""
+-
+- def do_get_property(self, prop):
+- if prop.name == 'security':
+- return self.security
+- else:
+- raise AttributeError('unknown property %s' % prop.name)
+-
+- def do_set_property(self, prop, value):
+- if prop.name == 'security':
+- self.security = value
+- self._set_icon_name(value)
+- else:
+- raise AttributeError('unknown property %s' % prop.name)
+-
+- def _set_icon_name(self, security):
+- self.icon_name = ""
+- if security not in (NM_AP_SEC_NONE, NM_AP_SEC_UNKNOWN):
+- self.icon_name = "network-wireless-encrypted-symbolic"
+-
+- self.set_property("icon-name", self.icon_name)
+-
+-class DeviceConfiguration(object):
+-
+- setting_types = {
+- '802-11-wireless': NM.DeviceType.WIFI,
+- '802-3-ethernet': NM.DeviceType.ETHERNET,
+- 'vlan': NM.DeviceType.VLAN,
+- 'bond': NM.DeviceType.BOND,
+- 'team': NM.DeviceType.TEAM,
+- 'bridge': NM.DeviceType.BRIDGE,
+- }
+-
+- def __init__(self, device=None, con=None):
+- self.device = device
+- self.con = con
+-
+- def get_device_type(self):
+- if self.device:
+- return self.device.get_device_type()
+- elif self.con:
+- return self.setting_types.get(self.con.get_connection_type(), None)
+- else:
+- return None
+-
+- def get_iface(self):
+- iface = None
+- if self.device:
+- iface = self.device.get_iface()
+- elif self.con:
+- iface = self.con.get_setting_connection().get_interface_name()
+- wired_setting = self.con.get_setting_wired()
+- if not iface and wired_setting:
+- mac = wired_setting.get_mac_address()
+- if mac:
+- iface = nm.nm_hwaddr_to_device_name(mac)
+- return iface
+-
+- def get_uuid(self):
+- return self.con and self.con.get_uuid()
+-
+-class NetworkControlBox(GObject.GObject):
+-
+- __gsignals__ = {
+- "nm-state-changed": (GObject.SIGNAL_RUN_LAST, GObject.TYPE_NONE, []),
+- "device-state-changed": (GObject.SIGNAL_RUN_LAST, GObject.TYPE_NONE, [str, int, int, int]),
+- "apply-hostname": (GObject.SIGNAL_RUN_LAST, GObject.TYPE_NONE, []),
+- }
+-
+- supported_device_types = [
+- NM.DeviceType.ETHERNET,
+- NM.DeviceType.WIFI,
+- NM.DeviceType.TEAM,
+- NM.DeviceType.BOND,
+- NM.DeviceType.VLAN,
+- NM.DeviceType.BRIDGE,
+- ]
+-
+- wired_ui_device_types = [
+- NM.DeviceType.ETHERNET,
+- NM.DeviceType.TEAM,
+- NM.DeviceType.BOND,
+- NM.DeviceType.VLAN,
+- NM.DeviceType.BRIDGE,
+- ]
+-
+- device_type_sort_value = {
+- NM.DeviceType.ETHERNET : "1",
+- NM.DeviceType.WIFI : "2",
+- }
+-
+- device_type_name = {
+- NM.DeviceType.UNKNOWN: N_("Unknown"),
+- NM.DeviceType.ETHERNET: N_("Ethernet"),
+- NM.DeviceType.WIFI: N_("Wireless"),
+- NM.DeviceType.BOND: N_("Bond"),
+- NM.DeviceType.VLAN: N_("VLAN"),
+- NM.DeviceType.TEAM: N_("Team"),
+- NM.DeviceType.BRIDGE: N_("Bridge"),
+- }
+-
+- def __init__(self, builder, client, spoke=None):
+-
+- GObject.GObject.__init__(self)
+-
+- self.builder = builder
+- self._running_nmce = None
+- self.spoke = spoke
+- self.client = client
+-
+- # button for creating of virtual bond and vlan devices
+- self.builder.get_object("add_toolbutton").set_sensitive(True)
+- self.builder.get_object("add_toolbutton").connect("clicked",
+- self.on_add_device_clicked)
+- self.builder.get_object("remove_toolbutton").set_sensitive(False)
+- self.builder.get_object("remove_toolbutton").connect("clicked",
+- self.on_remove_device_clicked)
+-
+- not_supported = ["start_hotspot_button",
+- "stop_hotspot_button",
+- "heading_hotspot_network_name",
+- "heading_hotspot_security_key",
+- "label_hotspot_network_name",
+- "label_hotspot_security_key",
+- "hbox54",
+- ]
+-
+- do_not_show_in_refresh = ["heading_wireless_network_name",
+- "combobox_wireless_network_name"]
+- do_not_show_in_refresh += ["%s_%s_%s" % (widget, ty, value)
+- for widget in ["heading", "label"]
+- for ty in ["wired", "wireless"]
+- for value in ["ipv4", "ipv6", "dns", "route"]]
+- do_not_show_in_refresh += ["%s_wired_%s" % (widget, value)
+- for widget in ["heading", "label"]
+- for value in ["slaves", "vlanid", "parent"]]
+-
+- for ident in not_supported + do_not_show_in_refresh:
+- self.builder.get_object(ident).set_no_show_all(True)
+- self.builder.get_object(ident).hide()
+-
+- self.builder.get_object("notebook_types").set_show_tabs(False)
+-
+- # to prevent UI update signals races
+- self._updating_device = False
+-
+- # devices list
+- # limited to wired and wireless
+- treeview = self.builder.get_object("treeview_devices")
+- self._add_device_columns(treeview)
+- self.dev_cfg_store = self.builder.get_object("liststore_devices")
+- self.dev_cfg_store.set_sort_column_id(2, Gtk.SortType.ASCENDING)
+- selection = treeview.get_selection()
+- selection.set_mode(Gtk.SelectionMode.BROWSE)
+- selection.connect("changed", self.on_device_selection_changed)
+-
+- # wireless APs list
+- combobox = self.builder.get_object("combobox_wireless_network_name")
+- self._add_ap_icons(combobox)
+- model = combobox.get_model()
+- model.set_sort_column_id(2, Gtk.SortType.ASCENDING)
+- combobox.connect("changed", self.on_wireless_ap_changed_cb)
+- self.selected_ap = None
+-
+- self.builder.get_object("device_wired_off_switch").connect("notify::active",
+- self.on_device_off_toggled)
+- self.builder.get_object("device_wireless_off_switch").connect("notify::active",
+- self.on_device_off_toggled)
+- self.builder.get_object("button_wired_options").connect("clicked",
+- self.on_edit_connection)
+- self.builder.get_object("button_wireless_options").connect("clicked",
+- self.on_edit_connection)
+- self.entry_hostname = self.builder.get_object("entry_hostname")
+- self.label_current_hostname = self.builder.get_object("label_current_hostname")
+- self.button_apply_hostname = self.builder.get_object("button_apply_hostname")
+- self.button_apply_hostname.connect("clicked", self.on_apply_hostname)
+-
+- @property
+- def vbox(self):
+- return self.builder.get_object("networkControlBox_vbox")
+-
+-
+- def _add_ap_icons(self, combobox):
+- cell = CellRendererSecurity()
+- cell.set_padding(4, 0)
+- combobox.pack_start(cell, False)
+- combobox.add_attribute(cell, "security", 5)
+-
+- cell = CellRendererSignal()
+- cell.set_padding(4, 0)
+- #cell.set_property("xalign", 1.0)
+- combobox.pack_start(cell, False)
+- combobox.add_attribute(cell, "signal", 3)
+-
+- def _add_device_columns(self, treeview):
+- rnd = Gtk.CellRendererPixbuf()
+- rnd.set_property("stock-size", Gtk.IconSize.DND)
+- # TODO Gtk3 icon-name? (also at other places)
+- col = Gtk.TreeViewColumn("Icon", rnd, **{"icon-name":0})
+- treeview.append_column(col)
+-
+- rnd = Gtk.CellRendererText()
+- rnd.set_property("wrap-mode", Pango.WrapMode.WORD)
+- col = Gtk.TreeViewColumn("Text", rnd, markup=2)
+- col.set_sort_column_id(2)
+- col.set_expand(True)
+- treeview.append_column(col)
+-
+- def add_connection_to_list(self, con):
+- uuid = con.get_uuid()
+- if self.dev_cfg(uuid=uuid):
+- log.debug("network: GUI, not adding connection %s, already in list", uuid)
+- return False
+- con_setting = con.get_setting_connection()
+- if con_setting and con_setting.get_read_only():
+- log.debug("network: GUI, not adding read-only connection %s", uuid)
+- return False
+- dev_cfg = DeviceConfiguration(con=con)
+- if network.is_libvirt_device(dev_cfg.get_iface() or ""):
+- log.debug("network: GUI, not adding %s", dev_cfg.get_iface())
+- return False
+- if network.is_ibft_configured_device(dev_cfg.get_iface() or ""):
+- log.debug("network: GUI, not adding %s configured from iBFT", dev_cfg.get_iface())
+- return False
+- if dev_cfg.get_device_type() not in self.supported_device_types:
+- log.debug("network: GUI, not adding connection %s of unsupported type", uuid)
+- return False
+- if dev_cfg.get_device_type() == NM.DeviceType.ETHERNET:
+- if con_setting and con_setting.get_master():
+- log.debug("network: GUI, not adding slave connection %s", uuid)
+- return False
+- # Wireless settings are handled in scope of its device's dev_cfg
+- if dev_cfg.get_device_type() == NM.DeviceType.WIFI:
+- log.debug("network: GUI, not adding wireless connection %s", uuid)
+- return False
+- existing_dev_cfg = self.dev_cfg(iface=dev_cfg.get_iface())
+- if existing_dev_cfg:
+- if existing_dev_cfg.con:
+- log.debug("network: GUI, not adding connection %s, already have %s for device %s",
+- uuid, existing_dev_cfg.get_uuid(), existing_dev_cfg.device.get_iface())
+- return False
+- else:
+- log.debug("network: GUI, attaching connection %s to device %s",
+- uuid, existing_dev_cfg.device.get_iface())
+- existing_dev_cfg.con = con
+- else:
+- log.debug("network: GUI, adding connection %s", uuid)
+- self.add_dev_cfg(dev_cfg)
+- return True
+-
+- def initialize(self):
+- self.client.connect("device-added", self.on_device_added)
+- self.client.connect("device-removed", self.on_device_removed)
+- self.client.connect("connection-added", self.on_connection_added)
+- self.client.connect("notify::%s" % NM.CLIENT_WIRELESS_ENABLED,
+- self.on_wireless_enabled)
+- self.client.connect("notify::%s" % NM.CLIENT_STATE,
+- self.on_nm_state_changed)
+-
+- for device in self.client.get_devices():
+- self.add_device_to_list(device)
+-
+- for con in self.client.get_connections():
+- self.add_connection_to_list(con)
+-
+- # select the first device
+- treeview = self.builder.get_object("treeview_devices")
+- selection = treeview.get_selection()
+- itr = self.dev_cfg_store.get_iter_first()
+- if itr:
+- selection.select_iter(itr)
+-
+- def refresh(self):
+- self.refresh_ui()
+-
+- # Signal handlers.
+- def on_nm_state_changed(self, *args):
+- self.emit("nm-state-changed")
+-
+- def on_device_selection_changed(self, *args):
+- self.refresh_ui()
+-
+- def on_device_state_changed(self, device, new_state, *args):
+- self.emit("device-state-changed", device.get_iface(), new_state, *args)
+- if new_state == NM.DeviceState.SECONDARIES:
+- return
+- self._refresh_carrier_info()
+- dev_cfg = self.selected_dev_cfg()
+- if dev_cfg and dev_cfg.device == device:
+- self.refresh_ui(state=new_state)
+-
+- def on_device_config_changed(self, device, *args):
+- dev_cfg = self.selected_dev_cfg()
+- if dev_cfg and dev_cfg.device == device:
+- self.refresh_ui()
+-
+- def on_wireless_ap_changed_cb(self, combobox, *args):
+- if self._updating_device:
+- return
+- itr = combobox.get_active_iter()
+- if not itr:
+- return
+-
+- dev_cfg = self.selected_dev_cfg()
+- if not dev_cfg:
+- return
+-
+- device = dev_cfg.device
+-
+- ap, ssid_target = combobox.get_model().get(itr, 0, 1)
+- self.selected_ap = ap
+-
+- log.info("network: selected access point: %s", ssid_target)
+-
+- cons = ap.filter_connections(device.filter_connections(self.client.get_connections()))
+- if cons:
+- con = cons[0]
+- self.client.activate_connection_async(con, device, ap.get_path(), None)
+- else:
+- if self._ap_is_enterprise(ap):
+- # Create a connection for the ap and [Configure] it later with nm-c-e
+- con = NM.SimpleConnection.new()
+- s_con = NM.SettingConnection.new()
+- s_con.set_property('uuid', str(uuid4()))
+- s_con.set_property('id', ssid_target)
+- s_con.set_property('type', '802-11-wireless')
+- s_wireless = NM.SettingWireless.new()
+- s_wireless.set_property('ssid', ap.get_ssid())
+- s_wireless.set_property('mode', 'infrastructure')
+- log.debug("network: adding connection for WPA-Enterprise AP %s", ssid_target)
+- con.add_setting(s_con)
+- con.add_setting(s_wireless)
+- persistent = True
+- self.client.add_connection_async(con, persistent, None)
+- self.builder.get_object("button_wireless_options").set_sensitive(True)
+- else:
+- self.client.add_and_activate_connection_async(None, device, ap.get_path(), None)
+-
+- def on_connection_added(self, client, connection):
+- self.add_connection_to_list(connection)
+-
+- def on_device_added(self, client, device, *args):
+- # We need to wait for valid state before adding the device to our list
+- if device.get_state() == NM.DeviceState.UNKNOWN:
+- device.connect("state-changed", self.on_added_device_state_changed)
+- else:
+- self.add_device_to_list(device)
+-
+- def on_added_device_state_changed(self, device, new_state, *args):
+- # We need to wait for valid state before adding the device to our list
+- if new_state != NM.DeviceState.UNKNOWN:
+- device.disconnect_by_func(self.on_added_device_state_changed)
+- self.add_device_to_list(device)
+-
+- def on_device_removed(self, client, device, *args):
+- self.remove_device(device)
+-
+- def _find_first_ap_setting(self, device, ap):
+- for con in device.filter_connections(self.client.get_connections()):
+- wireless_setting = con.get_setting_wireless()
+- if not wireless_setting or not wireless_setting.get_ssid():
+- # setting is None or non-broadcast AP, we ignore these
+- return
+- if wireless_setting.get_ssid().get_data() == ap.get_ssid().get_data():
+- return con
+-
+- def on_edit_connection(self, *args):
+- dev_cfg = self.selected_dev_cfg()
+- if not dev_cfg:
+- return
+-
+- device = dev_cfg.device
+- con = dev_cfg.con
+- activate = None
+- ssid = ""
+-
+- if dev_cfg.get_device_type() == NM.DeviceType.WIFI:
+- if not self.selected_ap:
+- return
+- ssid = self.selected_ap.get_ssid().get_data()
+- con = self._find_first_ap_setting(device, self.selected_ap)
+- if not con:
+- log.debug("network: on_edit_connection: connection for ap %s not found", self.selected_ap)
+- return
+- # 871132 auto activate wireless connection after editing if it is not
+- # already activated (assume entering secrets)
+- condition = lambda: self.selected_ap != device.get_active_access_point()
+- activate = (con, device, condition)
+- else:
+- if not con:
+- log.debug("network: on_edit_connection: connection for device %s not found", device.get_iface())
+- if dev_cfg.get_device_type() == NM.DeviceType.ETHERNET:
+- # Create default connection for the device and run nm-c-e on it
+- default_con = self._default_eth_con(device.get_iface(), autoconnect=False)
+- persistent = False
+- log.info("network: creating new connection for %s device", dev_cfg.get_iface())
+- self.client.add_connection_async(default_con, persistent, None,
+- self._default_connection_added_cb, activate)
+- return
+-
+- if device and device.get_state() == NM.DeviceState.ACTIVATED:
+- # Reactivate the connection after configuring it (if it changed)
+- settings = con.to_dbus(NM.ConnectionSerializationFlags.ALL)
+- settings_changed = lambda: settings != con.to_dbus(NM.ConnectionSerializationFlags.ALL)
+- activate = (con, device, settings_changed)
+-
+- log.info("network: configuring connection %s device %s ssid %s",
+- con.get_uuid(), dev_cfg.get_iface(), ssid)
+- self._run_nmce(con.get_uuid(), activate)
+-
+- def _default_connection_added_cb(self, client, result, activate):
+- con = client.add_connection_finish(result)
+- uuid = con.get_setting_connection().get_uuid()
+- log.info("network: configuring new connection %s", uuid)
+- self._run_nmce(uuid, activate)
+-
+- def _run_nmce(self, uuid, activate):
+- self.kill_nmce(msg="Configure button clicked")
+- proc = startProgram(["nm-connection-editor", "--keep-above", "--edit", "%s" % uuid], reset_lang=False)
+- self._running_nmce = proc
+-
+- GLib.child_watch_add(proc.pid, self.on_nmce_exited, activate)
+-
+- def _default_eth_con(self, iface, autoconnect):
+- con = NM.SimpleConnection.new()
+- s_con = NM.SettingConnection.new()
+- s_con.set_property('uuid', str(uuid4()))
+- s_con.set_property('id', iface)
+- s_con.set_property('interface-name', iface)
+- s_con.set_property('autoconnect', autoconnect)
+- s_con.set_property('type', '802-3-ethernet')
+- s_wired = NM.SettingWired.new()
+- con.add_setting(s_con)
+- con.add_setting(s_wired)
+- return con
+-
+- def kill_nmce(self, msg=""):
+- if not self._running_nmce:
+- return False
+-
+- log.debug("network: killing running nm-c-e %s: %s", self._running_nmce.pid, msg)
+- self._running_nmce.kill()
+- self._running_nmce = None
+- return True
+-
+- def on_nmce_exited(self, pid, condition, activate=None):
+- # waitpid() has been called, make sure we don't do anything else with the proc
+- self._running_nmce = None
+- log.debug("nm-c-e exited with status %s", condition)
+-
+- # nm-c-e was closed normally, not killed by anaconda
+- if condition == 0:
+- if activate:
+- # The default of None confuses pylint
+- con, device, activate_condition = activate # pylint: disable=unpacking-non-sequence
+- if activate_condition():
+- gtk_call_once(self._activate_connection_cb, con, device)
+- network.logIfcfgFiles("nm-c-e run")
+-
+- def _activate_connection_cb(self, con, device):
+- self.client.activate_connection_async(con, device, None, None)
+- if self.spoke:
+- self.spoke.networking_changed = True
+-
+- def on_wireless_enabled(self, *args):
+- switch = self.builder.get_object("device_wireless_off_switch")
+- self._updating_device = True
+- switch.set_active(self.client.wireless_get_enabled())
+- self._updating_device = False
+-
+- def on_device_off_toggled(self, switch, *args):
+- if self._updating_device:
+- return
+-
+- active = switch.get_active()
+- dev_cfg = self.selected_dev_cfg()
+- if not dev_cfg:
+- return
+- device = dev_cfg.device
+- con = dev_cfg.con
+-
+- log.info("network: device %s switched %s", dev_cfg.get_iface(), "on" if active else "off")
+-
+- if dev_cfg.get_device_type() == NM.DeviceType.WIFI:
+- self.client.wireless_set_enabled(active)
+- else:
+- if active:
+- if not con:
+- log.debug("network: on_device_off_toggled: no connection for %s",
+- dev_cfg.get_iface())
+- return
+-
+- self.client.activate_connection_async(con, device, None, None)
+- else:
+- if not device:
+- log.debug("network: on_device_off_toggled: no device for %s", dev_cfg.get_iface())
+- return
+- device.disconnect(None)
+-
+- if self.spoke:
+- self.spoke.networking_changed = True
+-
+- def on_add_device_clicked(self, *args):
+- dialog = self.builder.get_object("add_device_dialog")
+- with self.spoke.main_window.enlightbox(dialog):
+- rc = dialog.run()
+- dialog.hide()
+- if rc == 1:
+- ai = self.builder.get_object("combobox_add_device").get_active_iter()
+- model = self.builder.get_object("liststore_add_device")
+- dev_type = model[ai][1]
+- self.add_device(dev_type)
+-
+- def on_remove_device_clicked(self, *args):
+- selection = self.builder.get_object("treeview_devices").get_selection()
+- model, itr = selection.get_selected()
+- if not itr:
+- return None
+- dev_cfg = model[itr][DEVICES_COLUMN_OBJECT]
+- model.remove(itr)
+- if dev_cfg.con:
+- dev_cfg.con.delete()
+-
+- def on_apply_hostname(self, *args):
+- self.emit("apply-hostname")
+-
+- def add_device(self, ty):
+- log.info("network: adding device of type %s", ty)
+- self.kill_nmce(msg="Add device button clicked")
+- proc = startProgram(["nm-connection-editor", "--keep-above", "--create", "--type=%s" % ty], reset_lang=False)
+- self._running_nmce = proc
+-
+- GLib.child_watch_add(proc.pid, self.on_nmce_exited)
+-
+- def selected_dev_cfg(self):
+- selection = self.builder.get_object("treeview_devices").get_selection()
+- model, itr = selection.get_selected()
+- if not itr:
+- return None
+- return model[itr][DEVICES_COLUMN_OBJECT]
+-
+- def add_dev_cfg(self, dev_cfg):
+- log.debug("network: GUI, device configuration added: connection %s device %s",
+- dev_cfg.get_uuid(), dev_cfg.get_iface())
+- self.dev_cfg_store.append([
+- self._dev_icon_name(dev_cfg),
+- self.device_type_sort_value.get(dev_cfg.get_device_type(), "100"),
+- self._dev_title(dev_cfg),
+- dev_cfg
+- ])
+-
+- def add_device_to_list(self, device):
+- if device.get_device_type() not in self.supported_device_types:
+- return
+- if network.is_libvirt_device(device.get_iface()):
+- log.debug("network: not adding %s", device.get_iface())
+- return
+- # ignore fcoe vlan devices
+- # (can be chopped off to IFNAMSIZ kernel limit)
+- if device.get_iface().endswith(('-fcoe', '-fco', '-fc', '-f', '-')):
+- return
+- if network.is_ibft_configured_device(device.get_iface() or ""):
+- log.debug("network: not adding connection for device %s configured from iBFT", device.get_iface())
+- return False
+-
+- # Ignore devices with active read-only connections (created by NM for iBFT VLAN)
+- ac = device.get_active_connection()
+- if ac:
+- rc = ac.get_connection()
+- # Getting of NMRemoteConnection can fail (None), isn't it a bug in NM?
+- if rc:
+- con_setting = rc.get_setting_connection()
+- if con_setting and con_setting.get_read_only():
+- log.debug("network: not adding read-only connection "
+- "(assuming iBFT) for device %s", device.get_iface())
+- return
+- else:
+- log.debug("network: can't get remote connection of active connection "
+- "of device %s", device.get_iface())
+-
+- # Find the connection for the device (assuming existence of single ifcfg actually)
+- con = None
+- # Wifi connections are stored in wifi tab combobox
+- if device.get_device_type() != NM.DeviceType.WIFI:
+- cons = device.get_available_connections()
+- for c in cons:
+- if c.get_setting_connection() and not c.get_setting_connection().get_slave_type():
+- con = c
+- if len(cons) != 1:
+- log.warning("network: %s has unexpected number of connections: %s",
+- device.get_iface(), [c.get_uuid() for c in cons])
+-
+- if con and self.dev_cfg(uuid=con.get_uuid()):
+- # If we already have a connection for the device
+- # it is a virtual device appearing
+- self.dev_cfg(uuid=con.get_uuid()).device = device
+- # it might be too late for the callbacks below so refresh now
+- self.refresh_ui()
+- else:
+- self.add_dev_cfg(DeviceConfiguration(device=device, con=con))
+-
+- device.connect("notify::ip4-config", self.on_ip_obj_changed, IPV4_CONFIG)
+- device.connect("notify::ip6-config", self.on_ip_obj_changed, IPV6_CONFIG)
+- device.connect("state-changed", self.on_device_state_changed)
+-
+- def on_ip_obj_changed(self, device, *args):
+- """Callback when ipX-config objects will be changed.
+-
+- Register callback on properties (IP address, gateway...) of these ipX-config
+- objects when they are created.
+- """
+- log.debug("network: %s object changed", args[1])
+- self.on_device_config_changed(device)
+- if args[1] == IPV4_CONFIG:
+- config = device.props.ip4_config
+- else:
+- config = device.props.ip6_config
+-
+- if config:
+- # register callback when inner NMIP[4,6]Config object changed
+- config.connect("notify::addresses", self.on_config_changed, device)
+- config.connect("notify::gateway", self.on_config_changed, device)
+- config.connect("notify::nameservers", self.on_config_changed, device)
+-
+- def on_config_changed(self, config, *args):
+- """Callback on property change of ipX-config objects.
+-
+- Call method which show changed properties (IP, gateway...) to an user.
+- """
+- self.on_device_config_changed(args[1])
+-
+- def _dev_icon_name(self, dev_cfg):
+- icon_name = ""
+- if dev_cfg.get_device_type() in self.wired_ui_device_types:
+- if dev_cfg.device:
+- if dev_cfg.device.get_state() == NM.DeviceState.UNAVAILABLE:
+- icon_name = "network-wired-disconnected"
+- else:
+- icon_name = "network-wired"
+- else:
+- icon_name = "network-wired-disconnected"
+- elif dev_cfg.get_device_type() == NM.DeviceType.WIFI:
+- icon_name = "network-wireless"
+-
+- return icon_name
+-
+- def _dev_title(self, dev_cfg):
+- unplugged = ''
+-
+- if dev_cfg.device:
+- if (dev_cfg.device.get_state() == NM.DeviceState.UNAVAILABLE
+- and dev_cfg.device.get_device_type() == NM.DeviceType.ETHERNET
+- and not dev_cfg.device.get_carrier()):
+- # TRANSLATORS: ethernet cable is unplugged
+- unplugged = ', %s' % escape_markup(_("unplugged"))
+- # pylint: disable=unescaped-markup
+- title = '%s (%s%s)' % \
+- (escape_markup(_(self.device_type_name.get(dev_cfg.get_device_type(), ""))),
+- escape_markup(dev_cfg.get_iface()),
+- unplugged)
+-
+- if dev_cfg.device:
+- title += '\n%s %s' % \
+- (escape_markup(dev_cfg.device.get_vendor() or ""),
+- escape_markup(dev_cfg.device.get_product() or ""))
+- return title
+-
+- def dev_cfg(self, uuid=None, device=None, iface=None):
+- for row in self.dev_cfg_store:
+- dev_cfg = row[DEVICES_COLUMN_OBJECT]
+- if uuid:
+- if dev_cfg.get_uuid() != uuid:
+- continue
+- if device:
+- if not dev_cfg.device \
+- or dev_cfg.device.get_udi() != device.get_udi():
+- continue
+- if iface:
+- if not dev_cfg.device \
+- or dev_cfg.device.get_iface() != iface:
+- continue
+- return dev_cfg
+- return None
+-
+- def remove_device(self, device):
+- # This should not concern wifi and ethernet devices,
+- # just virtual devices e.g. vpn probably
+- log.debug("network: GUI, device removed: %s", device.get_iface())
+- if self.spoke:
+- self.spoke.networking_changed = True
+- dev_cfg = self.dev_cfg(device=device)
+- if dev_cfg:
+- dev_cfg.device = None
+-
+- def refresh_ui(self, state=None):
+-
+- dev_cfg = self.selected_dev_cfg()
+- if not dev_cfg:
+- # the list is empty (no supported devices)
+- notebook = self.builder.get_object("notebook_types")
+- notebook.set_current_page(5)
+- return
+-
+- self._refresh_device_type_page(dev_cfg.get_device_type())
+- self._refresh_header_ui(dev_cfg, state)
+- self._refresh_slaves(dev_cfg)
+- self._refresh_parent_vlanid(dev_cfg)
+- self._refresh_speed_hwaddr(dev_cfg, state)
+- self._refresh_ap(dev_cfg, state)
+- self._refresh_device_cfg(dev_cfg)
+-
+- def _refresh_device_cfg(self, dev_cfg):
+-
+- if dev_cfg.get_device_type() in self.wired_ui_device_types:
+- dt = "wired"
+- elif dev_cfg.get_device_type() == NM.DeviceType.WIFI:
+- dt = "wireless"
+-
+- if dev_cfg.device:
+- ipv4cfg = dev_cfg.device.get_ip4_config()
+- ipv6cfg = dev_cfg.device.get_ip6_config()
+- else:
+- ipv4cfg = ipv6cfg = None
+-
+- if ipv4cfg:
+- addr_str = ",".join("%s/%d" % (a.get_address(), a.get_prefix())
+- for a in ipv4cfg.get_addresses())
+- gateway_str = ipv4cfg.get_gateway()
+- dnss_str = ",".join(ipv4cfg.get_nameservers())
+- else:
+- addr_str = dnss_str = gateway_str = None
+- self._set_device_info_value(dt, "ipv4", addr_str)
+- self._set_device_info_value(dt, "dns", dnss_str)
+- self._set_device_info_value(dt, "route", gateway_str)
+-
+- addr6_str = ""
+- if ipv6cfg:
+- addr6_str = ",".join("%s/%d" % (a.get_address(), a.get_prefix())
+- for a in ipv6cfg.get_addresses()
+- # Do not display link-local addresses
+- if not a.get_address().startswith("fe80:"))
+- self._set_device_info_value(dt, "ipv6", addr6_str.strip() or None)
+-
+- if ipv4cfg and addr6_str:
+- self.builder.get_object("heading_%s_ipv4" % dt).set_label(_("IPv4 Address"))
+- self.builder.get_object("heading_%s_ipv6" % dt).set_label(_("IPv6 Address"))
+- elif ipv4cfg:
+- self.builder.get_object("heading_%s_ipv4" % dt).set_label(_("IP Address"))
+- elif addr6_str:
+- self.builder.get_object("heading_%s_ipv6" % dt).set_label(_("IP Address"))
+-
+- return False
+-
+- def _refresh_ap(self, dev_cfg, state=None):
+- if dev_cfg.get_device_type() != NM.DeviceType.WIFI:
+- return
+-
+- if state is None:
+- state = dev_cfg.device.get_state()
+- if state == NM.DeviceState.UNAVAILABLE:
+- ap_str = None
+- else:
+- active_ap = dev_cfg.device.get_active_access_point()
+- if active_ap:
+- ap_str = self._ap_security_string(active_ap)
+- else:
+- ap_str = ""
+-
+- self._set_device_info_value("wireless", "security", ap_str)
+-
+- if state == NM.DeviceState.UNAVAILABLE:
+- self.builder.get_object("heading_wireless_network_name").hide()
+- self.builder.get_object("combobox_wireless_network_name").hide()
+- else:
+- self.builder.get_object("heading_wireless_network_name").show()
+- self.builder.get_object("combobox_wireless_network_name").show()
+-
+- store = self.builder.get_object("liststore_wireless_network")
+- self._updating_device = True
+- store.clear()
+- aps = self._get_strongest_unique_aps(dev_cfg.device.get_access_points())
+- for ap in aps:
+- self._add_ap(ap, active_ap == ap)
+- # TODO: add access point other...
+- if active_ap:
+- combobox = self.builder.get_object("combobox_wireless_network_name")
+- for i in combobox.get_model():
+- if i[0] == active_ap:
+- combobox.set_active_iter(i.iter)
+- self.selected_ap = active_ap
+- break
+- self._updating_device = False
+-
+- def _refresh_slaves(self, dev_cfg):
+- if dev_cfg.get_device_type() in [NM.DeviceType.BOND,
+- NM.DeviceType.TEAM,
+- NM.DeviceType.BRIDGE]:
+- slaves = ""
+- if dev_cfg.device:
+- slaves = ",".join(s.get_iface() for s in dev_cfg.device.get_slaves())
+- self._set_device_info_value("wired", "slaves", slaves)
+-
+- def _refresh_parent_vlanid(self, dev_cfg):
+- if dev_cfg.get_device_type() == NM.DeviceType.VLAN:
+- if dev_cfg.device:
+- vlanid = dev_cfg.device.get_vlan_id()
+- else:
+- vlanid = dev_cfg.con.get_setting_vlan().get_id()
+- parent = dev_cfg.con.get_setting_vlan().get_parent()
+- self._set_device_info_value("wired", "vlanid", str(vlanid))
+- self._set_device_info_value("wired", "parent", parent)
+-
+- def _refresh_speed_hwaddr(self, dev_cfg, state=None):
+- dev_type = dev_cfg.get_device_type()
+- if dev_type in self.wired_ui_device_types:
+- dt = "wired"
+- elif dev_type == NM.DeviceType.WIFI:
+- dt = "wireless"
+-
+- # Speed
+- speed = None
+- if dev_cfg.device:
+- if dev_type == NM.DeviceType.ETHERNET:
+- speed = dev_cfg.device.get_speed()
+- elif dev_type == NM.DeviceType.WIFI:
+- speed = dev_cfg.device.get_bitrate() / 1000
+- if state is None:
+- state = dev_cfg.device.get_state()
+-
+- if not dev_cfg.device or state == NM.DeviceState.UNAVAILABLE:
+- speed_str = None
+- elif speed:
+- speed_str = _("%d Mb/s") % speed
+- else:
+- speed_str = ""
+- self._set_device_info_value(dt, "speed", speed_str)
+- # Hardware address
+- hwaddr = dev_cfg.device and dev_cfg.device.get_hw_address()
+- self._set_device_info_value(dt, "mac", hwaddr)
+-
+- def _refresh_device_type_page(self, dev_type):
+- notebook = self.builder.get_object("notebook_types")
+- if dev_type == NM.DeviceType.ETHERNET:
+- notebook.set_current_page(0)
+- self.builder.get_object("heading_wired_slaves").hide()
+- self.builder.get_object("label_wired_slaves").hide()
+- self.builder.get_object("heading_wired_vlanid").hide()
+- self.builder.get_object("label_wired_vlanid").hide()
+- self.builder.get_object("heading_wired_parent").hide()
+- self.builder.get_object("label_wired_parent").hide()
+- self.builder.get_object("remove_toolbutton").set_sensitive(False)
+- elif dev_type in [NM.DeviceType.BOND,
+- NM.DeviceType.TEAM,
+- NM.DeviceType.BRIDGE]:
+- notebook.set_current_page(0)
+- self.builder.get_object("heading_wired_slaves").show()
+- self.builder.get_object("label_wired_slaves").show()
+- self.builder.get_object("heading_wired_vlanid").hide()
+- self.builder.get_object("label_wired_vlanid").hide()
+- self.builder.get_object("heading_wired_parent").hide()
+- self.builder.get_object("label_wired_parent").hide()
+- self.builder.get_object("remove_toolbutton").set_sensitive(True)
+- elif dev_type == NM.DeviceType.VLAN:
+- notebook.set_current_page(0)
+- self.builder.get_object("heading_wired_slaves").hide()
+- self.builder.get_object("label_wired_slaves").hide()
+- self.builder.get_object("heading_wired_vlanid").show()
+- self.builder.get_object("label_wired_vlanid").show()
+- self.builder.get_object("heading_wired_parent").show()
+- self.builder.get_object("label_wired_parent").show()
+- self.builder.get_object("remove_toolbutton").set_sensitive(True)
+- elif dev_type == NM.DeviceType.WIFI:
+- notebook.set_current_page(1)
+- self.builder.get_object("button_wireless_options").set_sensitive(self.selected_ap is not None)
+-
+- def _refresh_carrier_info(self):
+- for i in self.dev_cfg_store:
+- i[DEVICES_COLUMN_TITLE] = self._dev_title(i[DEVICES_COLUMN_OBJECT])
+-
+- def _refresh_header_ui(self, dev_cfg, state=None):
+- if dev_cfg.get_device_type() in self.wired_ui_device_types:
+- dev_type_str = "wired"
+- elif dev_cfg.get_device_type() == NM.DeviceType.WIFI:
+- dev_type_str = "wireless"
+-
+- if dev_type_str == "wired":
+- # update icon according to device status
+- img = self.builder.get_object("image_wired_device")
+- img.set_from_icon_name(self._dev_icon_name(dev_cfg), Gtk.IconSize.DIALOG)
+-
+- # TODO: is this necessary? Isn't it static from glade?
+- device_type_label = _(self.device_type_name.get(dev_cfg.get_device_type(), ""))
+- self.builder.get_object("label_%s_device" % dev_type_str).set_label(
+- "%s (%s)" % (device_type_label, dev_cfg.get_iface()))
+-
+- if state is None:
+- if not dev_cfg.device:
+- state = NM.DeviceState.DISCONNECTED
+- else:
+- state = dev_cfg.device.get_state()
+-
+- self.builder.get_object("label_%s_status" % dev_type_str).set_label(
+- localized_string_of_device_state(dev_cfg.device, state))
+-
+- switch = self.builder.get_object("device_%s_off_switch" % dev_type_str)
+- if dev_type_str == "wired":
+- switch.set_visible(state not in (NM.DeviceState.UNAVAILABLE,
+- NM.DeviceState.UNMANAGED))
+- self._updating_device = True
+- switch.set_active(state not in (NM.DeviceState.UNMANAGED,
+- NM.DeviceState.UNAVAILABLE,
+- NM.DeviceState.DISCONNECTED,
+- NM.DeviceState.DEACTIVATING,
+- NM.DeviceState.FAILED))
+- self._updating_device = False
+- elif dev_type_str == "wireless":
+- self.on_wireless_enabled()
+-
+- def _set_device_info_value(self, dev_type_str, info, value_str):
+- heading = self.builder.get_object("heading_%s_%s" % (dev_type_str, info))
+- value_label = self.builder.get_object("label_%s_%s" % (dev_type_str, info))
+- if value_str is None:
+- really_hide(heading)
+- really_hide(value_label)
+- else:
+- really_show(heading)
+- really_show(value_label)
+- value_label.set_label(value_str)
+-
+- def _add_ap(self, ap, active=False):
+- ssid = ap.get_ssid()
+- if not ssid:
+- # get_ssid can return None if AP does not broadcast.
+- return
+- ssid = ssid.get_data()
+- if not ssid:
+- return
+-
+- mode = ap.get_mode()
+- if not mode:
+- return
+-
+- security = self._ap_security(ap)
+-
+- store = self.builder.get_object("liststore_wireless_network")
+-
+- # Decode the SSID (a byte sequence) into something resembling a string
+- ssid_str = NM.utils_ssid_to_utf8(ssid)
+-
+- # the third column is for sorting
+- itr = store.append([ap,
+- ssid_str,
+- ssid_str,
+- ap.get_strength(),
+- mode,
+- security])
+- if active:
+- self.builder.get_object("combobox_wireless_network_name").set_active_iter(itr)
+-
+- def _get_strongest_unique_aps(self, access_points):
+- strongest_aps = {}
+- for ap in access_points:
+- if not ap.get_ssid():
+- # non-broadcasting AP. We don't do anything with these
+- continue
+- ssid = ap.get_ssid().get_data()
+- if ssid in strongest_aps:
+- if ap.get_strength() > strongest_aps[ssid].get_strength():
+- strongest_aps[ssid] = ap
+- else:
+- strongest_aps[ssid] = ap
+-
+- return strongest_aps.values()
+-
+- def _ap_security(self, ap):
+- ty = NM_AP_SEC_UNKNOWN
+-
+- flags = ap.get_flags()
+- wpa_flags = ap.get_wpa_flags()
+- rsn_flags = ap.get_rsn_flags()
+-
+- if (not (flags & NM._80211ApFlags.PRIVACY) and
+- wpa_flags == NM._80211ApSecurityFlags.NONE and
+- rsn_flags == NM._80211ApSecurityFlags.NONE):
+- ty = NM_AP_SEC_NONE
+- elif (flags & NM._80211ApFlags.PRIVACY and
+- wpa_flags == NM._80211ApSecurityFlags.NONE and
+- rsn_flags == NM._80211ApSecurityFlags.NONE):
+- ty = NM_AP_SEC_WEP
+- elif (not (flags & NM._80211ApFlags.PRIVACY) and
+- wpa_flags != NM._80211ApSecurityFlags.NONE and
+- rsn_flags != NM._80211ApSecurityFlags.NONE):
+- ty = NM_AP_SEC_WPA
+- else:
+- ty = NM_AP_SEC_WPA2
+-
+- return ty
+-
+- def _ap_security_string(self, ap):
+-
+- flags = ap.get_flags()
+- wpa_flags = ap.get_wpa_flags()
+- rsn_flags = ap.get_rsn_flags()
+-
+- sec_str = ""
+-
+- if ((flags & NM._80211ApFlags.PRIVACY) and
+- wpa_flags == NM._80211ApSecurityFlags.NONE and
+- rsn_flags == NM._80211ApSecurityFlags.NONE):
+- sec_str += "%s, " % _("WEP")
+-
+- if wpa_flags != NM._80211ApSecurityFlags.NONE:
+- sec_str += "%s, " % _("WPA")
+-
+- if rsn_flags != NM._80211ApSecurityFlags.NONE:
+- sec_str += "%s, " % _("WPA2")
+-
+- if ((wpa_flags & NM._80211ApSecurityFlags.KEY_MGMT_802_1X) or
+- (rsn_flags & NM._80211ApSecurityFlags.KEY_MGMT_802_1X)):
+- sec_str += "%s, " % _("Enterprise")
+-
+- if sec_str:
+- sec_str = sec_str[:-2]
+- else:
+- sec_str = _("None")
+-
+- return sec_str
+-
+- def _ap_is_enterprise(self, ap):
+- wpa_flags = ap.get_wpa_flags()
+- rsn_flags = ap.get_rsn_flags()
+- return ((wpa_flags & NM._80211ApSecurityFlags.KEY_MGMT_802_1X) or
+- (rsn_flags & NM._80211ApSecurityFlags.KEY_MGMT_802_1X))
+-
+- @property
+- def dev_cfgs(self):
+- return [row[DEVICES_COLUMN_OBJECT] for
+- row in self.dev_cfg_store]
+-
+- @property
+- def hostname(self):
+- return self.entry_hostname.get_text()
+-
+- @hostname.setter
+- def hostname(self, value):
+- if not value:
+- return
+- self.entry_hostname.set_text(value)
+-
+- @property
+- def current_hostname(self):
+- return self.label_current_hostname.get_text()
+-
+- @current_hostname.setter
+- def current_hostname(self, value):
+- if not value:
+- return
+- self.label_current_hostname.set_text(value)
+-
+- def disconnect_client_callbacks(self):
+- for cb in [self.on_device_added, self.on_device_removed,
+- self.on_connection_added, self.on_wireless_enabled,
+- self.on_nm_state_changed]:
+- _try_disconnect(self.client, cb)
+-
+- for device in self.client.get_devices():
+- _try_disconnect(device, self.on_device_config_changed)
+- _try_disconnect(device, self.on_device_state_changed)
+- _try_disconnect(device, self.on_ip_obj_changed)
+- for config in self._get_ip_configs(device):
+- _try_disconnect(config, self.on_config_changed)
+-
+- def _get_ip_configs(self, device):
+- out = []
+- try:
+- out.append(self.props.ip4_config)
+- except AttributeError:
+- pass
+- try:
+- out.append(self.props.ip6_config)
+- except AttributeError:
+- pass
+-
+- return out
+-
+-def _try_disconnect(obj, callback):
+- try:
+- obj.disconnect_by_func(callback)
+- except TypeError as e:
+- if not "nothing connected" in str(e):
+- log.debug("network: %s", e)
+-
+-class SecretAgentDialog(GUIObject):
+- builderObjects = ["secret_agent_dialog"]
+- mainWidgetName = "secret_agent_dialog"
+- uiFile = "spokes/network.glade"
+-
+- def __init__(self, *args, **kwargs):
+- self._content = kwargs.pop('content', {})
+- GUIObject.__init__(self, *args, **kwargs)
+- self.builder.get_object("label_message").set_text(self._content['message'])
+- self._connect_button = self.builder.get_object("connect_button")
+-
+- def initialize(self):
+- self._entries = {}
+- grid = Gtk.Grid()
+- grid.set_row_spacing(6)
+- grid.set_column_spacing(6)
+-
+- for row, secret in enumerate(self._content['secrets']):
+- label = Gtk.Label(label=secret['label'], halign=Gtk.Align.START)
+- entry = Gtk.Entry(hexpand=True)
+- entry.set_text(secret['value'])
+- if secret['key']:
+- self._entries[secret['key']] = entry
+- else:
+- entry.set_sensitive(False)
+- if secret['password']:
+- entry.set_visibility(False)
+- self._validate(entry, secret)
+- entry.connect("changed", self._validate, secret)
+- entry.connect("activate", self._password_entered_cb)
+- label.set_use_underline(True)
+- label.set_mnemonic_widget(entry)
+- grid.attach(label, 0, row, 1, 1)
+- grid.attach(entry, 1, row, 1, 1)
+-
+- self.builder.get_object("password_box").add(grid)
+-
+- def run(self):
+- self.initialize()
+- self.window.show_all()
+- rc = self.window.run()
+- for secret in self._content['secrets']:
+- if secret['key']:
+- secret['value'] = self._entries[secret['key']].get_text()
+- self.window.destroy()
+- return rc
+-
+- @property
+- def valid(self):
+- return all(secret.get('valid', False) for secret in self._content['secrets'])
+-
+- def _validate(self, entry, secret):
+- secret['value'] = entry.get_text()
+- if secret['validate']:
+- secret['valid'] = secret['validate'](secret)
+- else:
+- secret['valid'] = len(secret['value']) > 0
+- self._update_connect_button()
+-
+- def _password_entered_cb(self, entry):
+- if self._connect_button.get_sensitive() and self.valid:
+- self.window.response(1)
+-
+- def _update_connect_button(self):
+- self._connect_button.set_sensitive(self.valid)
+-
+-secret_agent = None
+-
+-class NotAuthorizedException(dbus.DBusException):
+- _dbus_error_name = SECRET_AGENT_IFACE + '.NotAuthorized'
+-
+-class SecretAgent(dbus.service.Object):
+- def __init__(self, spoke):
+- self._bus = dbus.SystemBus()
+- self.spoke = spoke
+- dbus.service.Object.__init__(self, self._bus, "/org/freedesktop/NetworkManager/SecretAgent")
+-
+- @dbus.service.method(SECRET_AGENT_IFACE,
+- in_signature='a{sa{sv}}osasb',
+- out_signature='a{sa{sv}}',
+- sender_keyword='sender')
+- def GetSecrets(self, connection_hash, connection_path, setting_name, hints, flags, sender=None):
+- if not sender:
+- raise NotAuthorizedException("Internal error: couldn't get sender")
+- uid = self._bus.get_unix_user(sender)
+- if uid != 0:
+- raise NotAuthorizedException("UID %d not authorized" % uid)
+-
+- log.debug("network: secrets requested path '%s' setting '%s' hints '%s' new %d",
+- connection_path, setting_name, str(hints), flags)
+- if not (flags & NM_SECRET_AGENT_GET_SECRETS_FLAG_ALLOW_INTERACTION):
+- return
+-
+- content = self._get_content(setting_name, connection_hash)
+- dialog = SecretAgentDialog(self.spoke.data, content=content)
+- with self.spoke.main_window.enlightbox(dialog.window):
+- rc = dialog.run()
+-
+- secrets = dbus.Dictionary()
+- if rc == 1:
+- for secret in content['secrets']:
+- if secret['key']:
+- secrets[secret['key']] = secret['value']
+-
+- settings = dbus.Dictionary({setting_name: secrets})
+-
+- return settings
+-
+- def _get_content(self, setting_name, connection_hash):
+- content = {}
+- connection_type = connection_hash['connection']['type']
+- if connection_type == "802-11-wireless":
+- content['title'] = _("Authentication required by wireless network")
+- content['message'] = _("Passwords or encryption keys are required to access\n"
+- "the wireless network '%(network_id)s'.") \
+- % {'network_id':str(connection_hash['connection']['id'])}
+- content['secrets'] = self._get_wireless_secrets(setting_name, connection_hash)
+- else:
+- log.info("Connection type %s not supported by secret agent", connection_type)
+-
+- return content
+-
+- def _get_wireless_secrets(self, setting_name, connection_hash):
+- key_mgmt = connection_hash['802-11-wireless-security']['key-mgmt']
+- original_secrets = connection_hash[setting_name]
+- secrets = []
+- if key_mgmt in ['wpa-none', 'wpa-psk']:
+- secrets.append({'label' : C_('GUI|Network|Secrets Dialog', '_Password:'),
+- 'key' : 'psk',
+- 'value' : original_secrets.get('psk', ''),
+- 'validate' : self._validate_wpapsk,
+- 'password' : True})
+- # static WEP
+- elif key_mgmt == 'none':
+- key_idx = str(original_secrets.get('wep_tx_keyidx', '0'))
+- secrets.append({'label' : C_('GUI|Network|Secrets Dialog', '_Key:'),
+- 'key' : 'wep-key%s' % key_idx,
+- 'value' : original_secrets.get('wep-key%s' % key_idx, ''),
+- 'wep_key_type': original_secrets.get('wep-key-type', ''),
+- 'validate' : self._validate_staticwep,
+- 'password' : True})
+- # WPA-Enterprise
+- elif key_mgmt == 'wpa-eap':
+- eap = original_secrets['eap'][0]
+- if eap in ('md5', 'leap', 'ttls', 'peap'):
+- secrets.append({'label' : _('User name: '),
+- 'key' : None,
+- 'value' : original_secrets.get('identity', ''),
+- 'validate' : None,
+- 'password' : False})
+- secrets.append({'label' : _('Password: '),
+- 'key' : 'password',
+- 'value' : original_secrets.get('password', ''),
+- 'validate' : None,
+- 'password' : True})
+- elif eap == 'tls':
+- secrets.append({'label' : _('Identity: '),
+- 'key' : None,
+- 'value' : original_secrets.get('identity', ''),
+- 'validate' : None,
+- 'password' : False})
+- secrets.append({'label' : _('Private key password: '),
+- 'key' : 'private-key-password',
+- 'value' : original_secrets.get('private-key-password', ''),
+- 'validate' : None,
+- 'password' : True})
+- else:
+- log.info("Unsupported wireless key management: %s", key_mgmt)
+-
+- return secrets
+-
+- def _validate_wpapsk(self, secret):
+- value = secret['value']
+- if len(value) == 64:
+- # must be composed of hexadecimal digits only
+- return all(c in string.hexdigits for c in value)
+- else:
+- return 8 <= len(value) <= 63
+-
+- def _validate_staticwep(self, secret):
+- value = secret['value']
+- if secret['wep_key_type'] == NM.WepKeyType.KEY:
+- if len(value) in (10, 26):
+- return all(c in string.hexdigits for c in value)
+- elif len(value) in (5, 13):
+- return all(c in string.ascii_letters for c in value)
+- else:
+- return False
+- elif secret['wep_key_type'] == NM.WepKeyType.PASSPHRASE:
+- return 0 <= len(value) <= 64
+- else:
+- return True
+-
+-def register_secret_agent(spoke):
+-
+- if not can_touch_runtime_system("register anaconda secret agent"):
+- return False
+-
+- global secret_agent
+- if not secret_agent:
+- # Ignore an error from pylint incorrectly analyzing types in dbus-python
+- secret_agent = SecretAgent(spoke) # pylint: disable=no-value-for-parameter
+- bus = dbus.SystemBus()
+- proxy = bus.get_object(NM_SERVICE, AGENT_MANAGER_PATH)
+- proxy.Register("anaconda", dbus_interface=AGENT_MANAGER_IFACE)
+- else:
+- secret_agent.spoke = spoke
+-
+- return True
+-
+-
+-class NetworkSpoke(FirstbootSpokeMixIn, NormalSpoke):
+- """
+- .. inheritance-diagram:: NetworkSpoke
+- :parts: 3
+- """
+- builderObjects = ["networkWindow", "liststore_wireless_network", "liststore_devices", "add_device_dialog", "liststore_add_device"]
+- mainWidgetName = "networkWindow"
+- uiFile = "spokes/network.glade"
+- helpFile = "NetworkSpoke.xml"
+-
+- title = CN_("GUI|Spoke", "_NETWORK & HOST NAME")
+- icon = "network-transmit-receive-symbolic"
+-
+- category = SystemCategory
+-
+- def __init__(self, *args, **kwargs):
+- NormalSpoke.__init__(self, *args, **kwargs)
+- self.networking_changed = False
+- self.network_control_box = NetworkControlBox(self.builder, nmclient, spoke=self)
+- self.network_control_box.hostname = self.data.network.hostname
+- self.network_control_box.current_hostname = network.current_hostname()
+- self.network_control_box.connect("nm-state-changed",
+- self.on_nm_state_changed)
+- self.network_control_box.connect("device-state-changed",
+- self.on_device_state_changed)
+- self.network_control_box.connect("apply-hostname",
+- self.on_apply_hostname)
+-
+- def apply(self):
+- _update_network_data(self.data, self.network_control_box)
+- log.debug("network: apply ksdata %s", self.data.network)
+-
+- # if installation media or hdd aren't used and settings have changed
+- # try if source is available
+- if self.networking_changed:
+- if ANACONDA_ENVIRON in anaconda_flags.environs and self.payload.needsNetwork:
+- log.debug("network spoke (apply) refresh payload")
+- from pyanaconda.packaging import payloadMgr
+- payloadMgr.restartThread(self.storage, self.data, self.payload, self.instclass,
+- fallback=not anaconda_flags.automatedInstall)
+- else:
+- log.debug("network spoke (apply), payload refresh skipped (running outside of installation environment)")
+- self.networking_changed = False
+- else:
+- log.debug("network spoke (apply), no changes detected")
+- self.network_control_box.kill_nmce(msg="leaving network spoke")
+-
+- @property
+- def completed(self):
+- # TODO: check also if source requires updates when implemented
+- return (not can_touch_runtime_system("require network connection")
+- or nm.nm_activated_devices())
+-
+- @property
+- def mandatory(self):
+- # the network spoke should be mandatory only if it is running
+- # during the installation and if the installation source requires network
+- return ANACONDA_ENVIRON in anaconda_flags.environs and self.payload.needsNetwork
+-
+- @property
+- def status(self):
+- """ A short string describing which devices are connected. """
+- return network.status_message()
+-
+- def initialize(self):
+- register_secret_agent(self)
+- NormalSpoke.initialize(self)
+- self.network_control_box.initialize()
+- if not can_touch_runtime_system("hide hint to use network configuration in DE"):
+- self.builder.get_object("network_config_vbox").set_no_show_all(True)
+- self.builder.get_object("network_config_vbox").hide()
+- else:
+- self.builder.get_object("live_hint_label").set_no_show_all(True)
+- self.builder.get_object("live_hint_label").hide()
+-
+- if not self.data.network.seen:
+- _update_network_data(self.data, self.network_control_box)
+-
+- def refresh(self):
+- NormalSpoke.refresh(self)
+- self.network_control_box.refresh()
+- self.network_control_box.current_hostname = network.current_hostname()
+-
+- def on_nm_state_changed(self, *args):
+- gtk_call_once(self._update_status)
+- gtk_call_once(self._update_hostname)
+-
+- def on_device_state_changed(self, source, device, new_state, *args):
+- if new_state in (NM.DeviceState.ACTIVATED,
+- NM.DeviceState.DISCONNECTED,
+- NM.DeviceState.UNAVAILABLE):
+- gtk_call_once(self._update_status)
+-
+- def on_apply_hostname(self, *args):
+- hostname = self.network_control_box.hostname
+- (valid, error) = network.sanityCheckHostname(hostname)
+- if not valid:
+- self.clear_info()
+- msg = _("Host name is not valid: %s") % error
+- self.set_warning(msg)
+- self.network_control_box.entry_hostname.grab_focus()
+- else:
+- self.clear_info()
+- network.set_hostname(hostname)
+- self._update_hostname()
+-
+- def _update_status(self):
+- hubQ.send_message(self.__class__.__name__, self.status)
+-
+- def _update_hostname(self):
+- self.network_control_box.current_hostname = network.current_hostname()
+-
+- def on_back_clicked(self, button):
+- hostname = self.network_control_box.hostname
+- (valid, error) = network.sanityCheckHostname(hostname)
+- if not valid:
+- self.clear_info()
+- msg = _("Host name is not valid: %s") % error
+- self.set_warning(msg)
+- self.network_control_box.entry_hostname.grab_focus()
+- else:
+- self.clear_info()
+- NormalSpoke.on_back_clicked(self, button)
+-
+- def finished(self):
+- """Disconnect callbacks
+-
+- Called when leaving summary hub
+- """
+- self.network_control_box.kill_nmce(msg="finished with network spoke")
+- self.network_control_box.disconnect_client_callbacks()
+-
+-class NetworkStandaloneSpoke(StandaloneSpoke):
+- """
+- .. inheritance-diagram:: NetworkStandaloneSpoke
+- :parts: 3
+- """
+- builderObjects = ["networkStandaloneWindow", "networkControlBox_vbox", "liststore_wireless_network", "liststore_devices", "add_device_dialog", "liststore_add_device"]
+- mainWidgetName = "networkStandaloneWindow"
+- uiFile = "spokes/network.glade"
+-
+- preForHub = SummaryHub
+- priority = 10
+-
+- def __init__(self, *args, **kwargs):
+- StandaloneSpoke.__init__(self, *args, **kwargs)
+- self.network_control_box = NetworkControlBox(self.builder, nmclient, spoke=self)
+- self.network_control_box.hostname = self.data.network.hostname
+- self.network_control_box.current_hostname = network.current_hostname()
+- parent = self.builder.get_object("AnacondaStandaloneWindow-action_area5")
+- parent.add(self.network_control_box.vbox)
+-
+- self.network_control_box.connect("nm-state-changed",
+- self.on_nm_state_changed)
+- self.network_control_box.connect("apply-hostname",
+- self.on_apply_hostname)
+-
+- self._initially_available = self.completed
+- log.debug("network standalone spoke (init): completed: %s", self._initially_available)
+- self._now_available = False
+-
+- def apply(self):
+- _update_network_data(self.data, self.network_control_box)
+-
+- log.debug("network: apply ksdata %s", self.data.network)
+-
+- self._now_available = self.completed
+-
+- log.debug("network standalone spoke (apply) payload: %s completed: %s", self.payload.baseRepo, self._now_available)
+- if (not self.payload.baseRepo and not self._initially_available
+- and self._now_available and self.payload.needsNetwork):
+- from pyanaconda.packaging import payloadMgr
+- payloadMgr.restartThread(self.storage, self.data, self.payload, self.instclass,
+- fallback=not anaconda_flags.automatedInstall)
+-
+- self.network_control_box.kill_nmce(msg="leaving standalone network spoke")
+- self.network_control_box.disconnect_client_callbacks()
+-
+- @property
+- def completed(self):
+- return (not can_touch_runtime_system("require network connection")
+- or nm.nm_activated_devices()
+- or self.data.method.method not in ("url", "nfs"))
+-
+- def initialize(self):
+- register_secret_agent(self)
+- StandaloneSpoke.initialize(self)
+- self.network_control_box.initialize()
+-
+- def refresh(self):
+- StandaloneSpoke.refresh(self)
+- self.network_control_box.refresh()
+- self.network_control_box.current_hostname = network.current_hostname()
+-
+- def _on_continue_clicked(self, window, user_data=None):
+- hostname = self.network_control_box.hostname
+- (valid, error) = network.sanityCheckHostname(hostname)
+- if not valid:
+- self.clear_info()
+- msg = _("Host name is not valid: %s") % error
+- self.set_warning(msg)
+- self.network_control_box.entry_hostname.grab_focus()
+- else:
+- self.clear_info()
+- StandaloneSpoke._on_continue_clicked(self, window, user_data)
+-
+- # Use case: slow dhcp has connected when on spoke
+- def on_nm_state_changed(self, *args):
+- gtk_call_once(self._update_hostname)
+-
+- def on_apply_hostname(self, *args):
+- hostname = self.network_control_box.hostname
+- (valid, error) = network.sanityCheckHostname(hostname)
+- if not valid:
+- self.clear_info()
+- msg = _("Host name is not valid: %s") % error
+- self.set_warning(msg)
+- self.network_control_box.entry_hostname.grab_focus()
+- else:
+- self.clear_info()
+- network.set_hostname(hostname)
+- self._update_hostname()
+-
+- def _update_hostname(self):
+- self.network_control_box.current_hostname = network.current_hostname()
+-
+-def _update_network_data(data, ncb):
+- data.network.network = []
+- for i, dev_cfg in enumerate(ncb.dev_cfgs):
+- devname = dev_cfg.get_iface()
+- nd = network.ksdata_from_ifcfg(devname, dev_cfg.get_uuid())
+- if not nd:
+- continue
+- if devname in nm.nm_activated_devices():
+- nd.activate = True
+- else:
+- # First network command defaults to --activate so we must
+- # use --no-activate explicitly to prevent the default
+- if i == 0:
+- nd.activate = False
+-
+- data.network.network.append(nd)
+- hostname = ncb.hostname
+- network.update_hostname_data(data, hostname)
+-
+-
+-def test():
+- win = Gtk.Window()
+- win.connect("delete-event", Gtk.main_quit)
+-
+- builder = Gtk.Builder()
+- import os
+- ui_file_path = os.environ.get('UIPATH')+'spokes/network.glade'
+- builder.add_from_file(ui_file_path)
+-
+- n = NetworkControlBox(builder, nmclient)
+- n.initialize()
+- n.refresh()
+-
+- n.vbox.reparent(win)
+-
+- win.show_all()
+- Gtk.main()
+-
+-if __name__ == "__main__":
+- test()
+--
+2.14.4
+
diff --git a/0005-anaconda-remove-network-setup-from-text-interface.patch b/0005-anaconda-remove-network-setup-from-text-interface.patch
new file mode 100644
index 0000000..9428fe4
--- /dev/null
+++ b/0005-anaconda-remove-network-setup-from-text-interface.patch
@@ -0,0 +1,387 @@
+From 9eb4146449147e8aaccc386823c8d805c276bd17 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Marek=20Marczykowski-G=C3=B3recki?=
+
+Date: Sat, 20 Oct 2018 11:23:40 +0200
+Subject: [PATCH] anaconda: remove network setup from text interface
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+We have network disabled.
+
+Signed-off-by: Frédéric Pierret
+---
+ pyanaconda/ui/tui/spokes/network.py | 361 ------------------------------------
+ 1 file changed, 361 deletions(-)
+ delete mode 100644 pyanaconda/ui/tui/spokes/network.py
+
+diff --git a/pyanaconda/ui/tui/spokes/network.py b/pyanaconda/ui/tui/spokes/network.py
+deleted file mode 100644
+index 56ab1173b..000000000
+--- a/pyanaconda/ui/tui/spokes/network.py
++++ /dev/null
+@@ -1,361 +0,0 @@
+-# Network configuration spoke classes
+-#
+-# Copyright (C) 2013 Red Hat, Inc.
+-#
+-# This copyrighted material is made available to anyone wishing to use,
+-# modify, copy, or redistribute it subject to the terms and conditions of
+-# the GNU General Public License v.2, or (at your option) any later version.
+-# This program is distributed in the hope that it will be useful, but WITHOUT
+-# ANY WARRANTY expressed or implied, including the implied warranties of
+-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
+-# Public License for more details. You should have received a copy of the
+-# GNU General Public License along with this program; if not, write to the
+-# Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+-# 02110-1301, USA. Any Red Hat trademarks that are incorporated in the
+-# source code or documentation are not subject to the GNU General Public
+-# License and may only be used or replicated with the express permission of
+-# Red Hat, Inc.
+-#
+-
+-
+-from pyanaconda.flags import can_touch_runtime_system, flags
+-from pyanaconda.ui.categories.system import SystemCategory
+-from pyanaconda.ui.tui.spokes import EditTUISpoke, OneShotEditTUIDialog
+-from pyanaconda.ui.tui.spokes import EditTUISpokeEntry as Entry
+-from pyanaconda.ui.tui.simpleline import TextWidget, ColumnWidget
+-from pyanaconda.ui.common import FirstbootSpokeMixIn
+-from pyanaconda.i18n import N_, _
+-from pyanaconda import network
+-from pyanaconda import nm
+-
+-from pyanaconda.regexes import IPV4_PATTERN_WITHOUT_ANCHORS, IPV4_NETMASK_WITHOUT_ANCHORS
+-from pyanaconda.constants_text import INPUT_PROCESSED
+-from pyanaconda.constants import ANACONDA_ENVIRON
+-
+-import logging
+-log = logging.getLogger("anaconda")
+-
+-import re
+-
+-__all__ = ["NetworkSpoke"]
+-
+-
+-class NetworkSpoke(FirstbootSpokeMixIn, EditTUISpoke):
+- """ Spoke used to configure network settings.
+-
+- .. inheritance-diagram:: NetworkSpoke
+- :parts: 3
+- """
+- title = N_("Network configuration")
+- category = SystemCategory
+-
+- def __init__(self, app, data, storage, payload, instclass):
+- EditTUISpoke.__init__(self, app, data, storage, payload, instclass)
+- self.hostname_dialog = OneShotEditTUIDialog(app, data, storage, payload, instclass)
+- self.hostname_dialog.value = self.data.network.hostname
+- self.supported_devices = []
+- self.errors = []
+- self._apply = False
+-
+- def initialize(self):
+- self._load_new_devices()
+-
+- EditTUISpoke.initialize(self)
+- if not self.data.network.seen:
+- self._update_network_data()
+-
+- def _load_new_devices(self):
+- devices = nm.nm_devices()
+- intf_dumped = network.dumpMissingDefaultIfcfgs()
+- if intf_dumped:
+- log.debug("Dumped interfaces: %s", intf_dumped)
+-
+- for name in devices:
+- if name in self.supported_devices:
+- continue
+- if network.is_ibft_configured_device(name):
+- continue
+- if nm.nm_device_type_is_ethernet(name):
+- # ignore slaves
+- if nm.nm_device_setting_value(name, "connection", "slave-type"):
+- continue
+- self.supported_devices.append(name)
+-
+- @property
+- def completed(self):
+- """ Check whether this spoke is complete or not. Do an additional
+- check if we're installing from CD/DVD, since a network connection
+- should not be required in this case.
+- """
+- return (not can_touch_runtime_system("require network connection")
+- or nm.nm_activated_devices())
+-
+- @property
+- def mandatory(self):
+- # the network spoke should be mandatory only if it is running
+- # during the installation and if the installation source requires network
+- return ANACONDA_ENVIRON in flags.environs and self.payload.needsNetwork
+-
+- @property
+- def status(self):
+- """ Short msg telling what devices are active. """
+- return network.status_message()
+-
+- def _summary_text(self):
+- """Devices cofiguration shown to user."""
+- msg = ""
+- activated_devs = nm.nm_activated_devices()
+- for name in self.supported_devices:
+- if name in activated_devs:
+- msg += self._activated_device_msg(name)
+- else:
+- msg += _("Wired (%(interface_name)s) disconnected\n") \
+- % {"interface_name": name}
+- return msg
+-
+- def _activated_device_msg(self, devname):
+- msg = _("Wired (%(interface_name)s) connected\n") \
+- % {"interface_name": devname}
+-
+- ipv4config = nm.nm_device_ip_config(devname, version=4)
+- ipv6config = nm.nm_device_ip_config(devname, version=6)
+-
+- if ipv4config and ipv4config[0]:
+- addr_str, prefix, gateway_str = ipv4config[0][0]
+- netmask_str = network.prefix2netmask(prefix)
+- dnss_str = ",".join(ipv4config[1])
+- else:
+- addr_str = dnss_str = gateway_str = netmask_str = ""
+- msg += _(" IPv4 Address: %(addr)s Netmask: %(netmask)s Gateway: %(gateway)s\n") % \
+- {"addr": addr_str, "netmask": netmask_str, "gateway": gateway_str}
+- msg += _(" DNS: %s\n") % dnss_str
+-
+- if ipv6config and ipv6config[0]:
+- for ipv6addr in ipv6config[0]:
+- addr_str, prefix, gateway_str = ipv6addr
+- # Do not display link-local addresses
+- if not addr_str.startswith("fe80:"):
+- msg += _(" IPv6 Address: %(addr)s/%(prefix)d\n") % \
+- {"addr": addr_str, "prefix": prefix}
+-
+- dnss_str = ",".join(ipv6config[1])
+-
+- return msg
+-
+- def refresh(self, args=None):
+- """ Refresh screen. """
+- self._load_new_devices()
+- EditTUISpoke.refresh(self, args)
+-
+- summary = self._summary_text()
+- self._window += [TextWidget(summary), ""]
+- hostname = _("Host Name: %s\n") % self.data.network.hostname
+- self._window += [TextWidget(hostname), ""]
+- current_hostname = _("Current host name: %s\n") % network.current_hostname()
+- self._window += [TextWidget(current_hostname), ""]
+-
+- # if we have any errors, display them
+- while len(self.errors) > 0:
+- self._window += [TextWidget(self.errors.pop()), ""]
+-
+- def _prep(i, w):
+- """ Mangle our text to make it look pretty on screen. """
+- number = TextWidget("%2d)" % (i + 1))
+- return ColumnWidget([(4, [number]), (None, [w])], 1)
+-
+- _opts = [_("Set host name")]
+- for devname in self.supported_devices:
+- _opts.append(_("Configure device %s") % devname)
+- text = [TextWidget(o) for o in _opts]
+-
+- # make everything presentable on screen
+- choices = [_prep(i, w) for i, w in enumerate(text)]
+- displayed = ColumnWidget([(78, choices)], 1)
+- self._window.append(displayed)
+-
+- return True
+-
+- def input(self, args, key):
+- """ Handle the input. """
+- try:
+- num = int(key)
+- except ValueError:
+- return key
+-
+- if num == 1:
+- # set hostname
+- self.app.switch_screen_modal(self.hostname_dialog, Entry(_("Host Name"),
+- "hostname", re.compile(".*$"), True))
+- self.apply()
+- return INPUT_PROCESSED
+- elif 2 <= num <= len(self.supported_devices) + 1:
+- # configure device
+- devname = self.supported_devices[num-2]
+- ndata = network.ksdata_from_ifcfg(devname)
+- if not ndata:
+- try:
+- nm.nm_device_setting_value(devname, "connection", "uuid")
+- except nm.SettingsNotFoundError:
+- pass
+- else:
+- log.debug("network: dumping ifcfg file for in-memory connection %s", devname)
+- nm.nm_update_settings_of_device(devname, [['connection', 'id', devname, None]])
+- ndata = network.ksdata_from_ifcfg(devname)
+-
+- if not ndata:
+- log.debug("network: can't find any connection for %s", devname)
+- self.errors.append(_("Configuration of device not found"))
+- return INPUT_PROCESSED
+-
+- newspoke = ConfigureNetworkSpoke(self.app, self.data, self.storage,
+- self.payload, self.instclass, ndata)
+- self.app.switch_screen_modal(newspoke)
+-
+- if ndata.ip == "dhcp":
+- ndata.bootProto = "dhcp"
+- ndata.ip = ""
+- else:
+- ndata.bootProto = "static"
+- if not ndata.netmask:
+- self.errors.append(_("Configuration not saved: netmask missing in static configuration"))
+- return INPUT_PROCESSED
+-
+- if ndata.ipv6 == "ignore":
+- ndata.noipv6 = True
+- ndata.ipv6 = ""
+- else:
+- ndata.noipv6 = False
+-
+- network.update_settings_with_ksdata(devname, ndata)
+- network.update_onboot_value(devname, ndata.onboot, ksdata=None, root_path="")
+-
+- if ndata._apply:
+- self._apply = True
+- uuid = nm.nm_device_setting_value(devname, "connection", "uuid")
+- try:
+- nm.nm_activate_device_connection(devname, uuid)
+- except (nm.UnmanagedDeviceError, nm.UnknownConnectionError):
+- self.errors.append(_("Can't apply configuration, device activation failed."))
+-
+- self.apply()
+- return INPUT_PROCESSED
+- else:
+- return key
+-
+- def apply(self):
+- """Apply all of our settings."""
+- self._update_network_data()
+- log.debug("network: apply ksdata %s", self.data.network)
+-
+- if self._apply:
+- self._apply = False
+- if ANACONDA_ENVIRON in flags.environs:
+- from pyanaconda.packaging import payloadMgr
+- payloadMgr.restartThread(self.storage, self.data, self.payload,
+- self.instclass, checkmount=False)
+-
+- def _update_network_data(self):
+- hostname = self.data.network.hostname
+-
+- self.data.network.network = []
+- for i, name in enumerate(nm.nm_devices()):
+- if network.is_ibft_configured_device(name):
+- continue
+- nd = network.ksdata_from_ifcfg(name)
+- if not nd:
+- continue
+- if name in nm.nm_activated_devices():
+- nd.activate = True
+- else:
+- # First network command defaults to --activate so we must
+- # use --no-activate explicitly to prevent the default
+- if i == 0:
+- nd.activate = False
+- self.data.network.network.append(nd)
+-
+- (valid, error) = network.sanityCheckHostname(self.hostname_dialog.value)
+- if valid:
+- hostname = self.hostname_dialog.value
+- else:
+- self.errors.append(_("Host name is not valid: %s") % error)
+- self.hostname_dialog.value = hostname
+- network.update_hostname_data(self.data, hostname)
+-
+-def check_ipv6_config(value):
+- if value in ["auto", "dhcp", "ignore"]:
+- return (True, None)
+- addr, _slash, prefix = value.partition("/")
+- if prefix:
+- try:
+- if not 1 <= int(prefix) <= 128:
+- return (False, None)
+- except ValueError:
+- return (False, None)
+- return check_ipv6_address(addr)
+-
+-def check_ipv6_address(value):
+- return (network.check_ip_address(value, version=6), None)
+-
+-def check_nameservers(value):
+- addresses = [str.strip(i) for i in value.split(",")]
+- if not addresses:
+- return (False, None)
+-
+- for ip in addresses:
+- if not network.check_ip_address(ip):
+- return (False, None)
+- return (True, None)
+-
+-class ConfigureNetworkSpoke(EditTUISpoke):
+- """ Spoke to set various configuration options for net devices. """
+- title = N_("Device configuration")
+- category = "network"
+-
+- edit_fields = [
+- Entry(N_('IPv4 address or %s for DHCP') % '"dhcp"', "ip",
+- re.compile("^(?:" + IPV4_PATTERN_WITHOUT_ANCHORS + "|dhcp)$"), True),
+- Entry(N_("IPv4 netmask"), "netmask", re.compile("^" + IPV4_NETMASK_WITHOUT_ANCHORS + "$"), True),
+- Entry(N_("IPv4 gateway"), "gateway", re.compile("^" + IPV4_PATTERN_WITHOUT_ANCHORS + "$"), True),
+- Entry(N_('IPv6 address[/prefix] or %(auto)s for automatic, %(dhcp)s for DHCP, %(ignore)s to turn off')
+- % {"auto": '"auto"', "dhcp": '"dhcp"', "ignore": '"ignore"'}, "ipv6",
+- check_ipv6_config, True),
+- Entry(N_("IPv6 default gateway"), "ipv6gateway", check_ipv6_address, True),
+- Entry(N_("Nameservers (comma separated)"), "nameserver", check_nameservers, True),
+- Entry(N_("Connect automatically after reboot"), "onboot", EditTUISpoke.CHECK, True),
+- Entry(N_("Apply configuration in installer"), "_apply", EditTUISpoke.CHECK, True),
+- ]
+-
+- def __init__(self, app, data, storage, payload, instclass, ndata):
+- EditTUISpoke.__init__(self, app, data, storage, payload, instclass)
+- self.args = ndata
+- if self.args.bootProto == "dhcp":
+- self.args.ip = "dhcp"
+- if self.args.noipv6:
+- self.args.ipv6 = "ignore"
+- self.args._apply = False
+-
+- def refresh(self, args=None):
+- """ Refresh window. """
+- EditTUISpoke.refresh(self, args)
+- message = _("Configuring device %s.") % self.args.device
+- self._window += [TextWidget(message), ""]
+- return True
+-
+- def input(self, args, key):
+- self.dialog.wrong_input_message = _("Bad format of the IP address")
+- try:
+- field = self.visible_fields[int(key)-1]
+- except (ValueError, IndexError):
+- pass
+- else:
+- if field.attribute == "netmask":
+- self.dialog.wrong_input_message = _("Bad format of the netmask")
+- return EditTUISpoke.input(self, args, key)
+-
+- @property
+- def indirect(self):
+- return True
+-
+- def apply(self):
+- """ Apply our changes. """
+- # this is done at upper level by updating ifcfg file
+--
+2.14.4
+
diff --git a/0006-anaconda-fix-grub-config-setup-by-removing-non-xen-o.patch b/0006-anaconda-fix-grub-config-setup-by-removing-non-xen-o.patch
new file mode 100644
index 0000000..dfae1e3
--- /dev/null
+++ b/0006-anaconda-fix-grub-config-setup-by-removing-non-xen-o.patch
@@ -0,0 +1,30 @@
+From 383217cc20f6da8030e98891e34cccd7b03d40c0 Mon Sep 17 00:00:00 2001
+From: Marek Marczykowski
+Date: Fri, 19 Oct 2018 08:02:11 +0200
+Subject: [PATCH] anaconda: fix grub config setup by removing non-xen options
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Signed-off-by: Frédéric Pierret
+---
+ pyanaconda/bootloader.py | 3 +++
+ 1 file changed, 3 insertions(+)
+
+diff --git a/pyanaconda/bootloader.py b/pyanaconda/bootloader.py
+index a37f91b9a..68fca98bf 100644
+--- a/pyanaconda/bootloader.py
++++ b/pyanaconda/bootloader.py
+@@ -1558,6 +1558,9 @@ class GRUB2(GRUB):
+ except (BootLoaderError, OSError, RuntimeError) as e:
+ log.error("boot loader password setup failed: %s", e)
+
++ # disable non-xen entries
++ os.chmod("%s/etc/grub.d/10_linux" % iutil.getSysroot(), 0o644)
++
+ # make sure the default entry is the OS we are installing
+ if self.default is not None:
+ # find the index of the default image
+--
+2.14.4
+
diff --git a/0007-anaconda-make-encrypted-partitions-by-default.patch b/0007-anaconda-make-encrypted-partitions-by-default.patch
new file mode 100644
index 0000000..c68de1c
--- /dev/null
+++ b/0007-anaconda-make-encrypted-partitions-by-default.patch
@@ -0,0 +1,32 @@
+From 2cdfe3b92b5997d03f22521a2d4edb1d42ea443f Mon Sep 17 00:00:00 2001
+From: Marek Marczykowski
+Date: Fri, 19 Oct 2018 08:02:11 +0200
+Subject: [PATCH] anaconda: make encrypted partitions by default
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Signed-off-by: Frédéric Pierret
+---
+ pyanaconda/kickstart.py | 5 +++++
+ 1 file changed, 5 insertions(+)
+
+diff --git a/pyanaconda/kickstart.py b/pyanaconda/kickstart.py
+index dc58d9b65..7d67bf1a6 100644
+--- a/pyanaconda/kickstart.py
++++ b/pyanaconda/kickstart.py
+@@ -252,6 +252,11 @@ class Authconfig(commands.authconfig.FC3_Authconfig):
+ log.error("Error running %s %s: %s", cmd, args, msg)
+
+ class AutoPart(commands.autopart.F21_AutoPart):
++ def __init__(self, writePriority=100, *args, **kwargs):
++ if 'encrypted' not in kwargs:
++ kwargs['encrypted'] = True
++ super(AutoPart, self).__init__(writePriority=writePriority, *args, **kwargs)
++
+ def parse(self, args):
+ retval = commands.autopart.F21_AutoPart.parse(self, args)
+
+--
+2.14.4
+
diff --git a/0008-anaconda-set-default-grub-theme.patch b/0008-anaconda-set-default-grub-theme.patch
new file mode 100644
index 0000000..b44643b
--- /dev/null
+++ b/0008-anaconda-set-default-grub-theme.patch
@@ -0,0 +1,39 @@
+From 0bfebcf148078e9d43cfabd13ceb92ffad6274d2 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Marek=20Marczykowski-G=C3=B3recki?=
+
+Date: Fri, 19 Oct 2018 08:02:11 +0200
+Subject: [PATCH] anaconda: set default grub theme
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Signed-off-by: Frédéric Pierret
+---
+ pyanaconda/bootloader.py | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/pyanaconda/bootloader.py b/pyanaconda/bootloader.py
+index 68fca98bf..b6488c5fd 100644
+--- a/pyanaconda/bootloader.py
++++ b/pyanaconda/bootloader.py
+@@ -1401,7 +1401,7 @@ class GRUB2(GRUB):
+ _config_file = "grub.cfg"
+ _config_dir = "grub2"
+ defaults_file = "/etc/default/grub"
+- terminal_type = "console"
++ terminal_type = "gfxterm"
+ stage2_max_end = None
+
+ # requirements for boot devices
+@@ -1503,7 +1503,7 @@ class GRUB2(GRUB):
+ log.info("bootloader.py: used boot args: %s ", self.boot_args)
+ defaults.write("GRUB_CMDLINE_LINUX=\"%s\"\n" % self.boot_args)
+ defaults.write("GRUB_DISABLE_RECOVERY=\"true\"\n")
+- #defaults.write("GRUB_THEME=\"/boot/grub2/themes/system/theme.txt\"\n")
++ defaults.write("GRUB_THEME=\"/boot/grub2/themes/system/theme.txt\"\n")
+ defaults.close()
+
+ def _encrypt_password(self):
+--
+2.14.4
+
diff --git a/0009-anaconda-add-options-can_dual_boot-and-can_update-to.patch b/0009-anaconda-add-options-can_dual_boot-and-can_update-to.patch
new file mode 100644
index 0000000..f552ce3
--- /dev/null
+++ b/0009-anaconda-add-options-can_dual_boot-and-can_update-to.patch
@@ -0,0 +1,30 @@
+From 4efdab482cd1b73f58abf4f6cf16be4965ab49ee Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Pierret=20=28fepitre=29?=
+
+Date: Fri, 19 Oct 2018 08:02:11 +0200
+Subject: [PATCH] anaconda: add options can_dual_boot and can_update to grub
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Signed-off-by: Frédéric Pierret
+---
+ pyanaconda/bootloader.py | 2 ++
+ 1 file changed, 2 insertions(+)
+
+diff --git a/pyanaconda/bootloader.py b/pyanaconda/bootloader.py
+index b6488c5fd..083b99f1a 100644
+--- a/pyanaconda/bootloader.py
++++ b/pyanaconda/bootloader.py
+@@ -1403,6 +1403,8 @@ class GRUB2(GRUB):
+ defaults_file = "/etc/default/grub"
+ terminal_type = "gfxterm"
+ stage2_max_end = None
++ can_dual_boot = True
++ can_update = True
+
+ # requirements for boot devices
+ stage2_device_types = ["partition", "mdarray", "lvmlv"]
+--
+2.14.4
+
diff --git a/0010-anaconda-efimgr-specify-root-iutil.getSysroot.patch b/0010-anaconda-efimgr-specify-root-iutil.getSysroot.patch
new file mode 100644
index 0000000..62ce720
--- /dev/null
+++ b/0010-anaconda-efimgr-specify-root-iutil.getSysroot.patch
@@ -0,0 +1,31 @@
+From b0d781737479e11c4b138ef6df4d372d8fbd2503 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Pierret=20=28fepitre=29?=
+
+Date: Fri, 19 Oct 2018 08:02:11 +0200
+Subject: [PATCH] anaconda: efimgr specify root=iutil.getSysroot()
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Signed-off-by: Frédéric Pierret
+---
+ pyanaconda/bootloader.py | 3 ++-
+ 1 file changed, 2 insertions(+), 1 deletion(-)
+
+diff --git a/pyanaconda/bootloader.py b/pyanaconda/bootloader.py
+index 083b99f1a..acdfb8322 100644
+--- a/pyanaconda/bootloader.py
++++ b/pyanaconda/bootloader.py
+@@ -1743,7 +1743,8 @@ class EFIBase(object):
+ log.warning("failed to parse efi boot slot (%s)", slot)
+ continue
+
+- rc = self.efibootmgr("-b", slot_id, "-B")
++ rc = self.efibootmgr("-b", slot_id, "-B",
++ root=iutil.getSysroot())
+ if rc:
+ raise BootLoaderError("failed to remove old efi boot entry. This is most likely a kernel or firmware bug.")
+
+--
+2.14.4
+
diff --git a/0011-anaconda-generate-xen-efi-configuration.patch b/0011-anaconda-generate-xen-efi-configuration.patch
new file mode 100644
index 0000000..65b2fca
--- /dev/null
+++ b/0011-anaconda-generate-xen-efi-configuration.patch
@@ -0,0 +1,121 @@
+From f53f5fdcaf10bdd2f64bd144b78052561ae15aa6 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Marek=20Marczykowski-G=C3=B3recki?=
+
+Date: Fri, 19 Oct 2018 08:02:12 +0200
+Subject: [PATCH] anaconda: generate xen efi configuration
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Signed-off-by: Frédéric Pierret
+---
+ pyanaconda/bootloader.py | 79 +++++++++++++++++++++++++++++++++++++++++++++++-
+ 1 file changed, 78 insertions(+), 1 deletion(-)
+
+diff --git a/pyanaconda/bootloader.py b/pyanaconda/bootloader.py
+index acdfb8322..908020ad0 100644
+--- a/pyanaconda/bootloader.py
++++ b/pyanaconda/bootloader.py
+@@ -21,6 +21,7 @@
+ import collections
+ import os
+ import re
++import shutil
+ import blivet
+ from parted import PARTITION_BIOS_GRUB
+ from glob import glob
+@@ -1828,6 +1829,82 @@ class EFIGRUB(EFIBase, GRUB2):
+ class Aarch64EFIGRUB(EFIGRUB):
+ _serial_consoles = ["ttyAMA", "ttyS"]
+
++class XenEFI(EFIGRUB):
++ packages = ["efibootmgr"]
++ _config_file = 'xen.cfg'
++
++ # stage2 not used at all, so allow any type
++ stage2_device_types = ["partition", "mdarray", "lvmlv"]
++
++ def __init__(self):
++ super(XenEFI, self).__init__()
++ self.efi_dir = 'qubes'
++
++ def add_efi_boot_target(self):
++ if self.stage1_device.type == "partition":
++ boot_disk = self.stage1_device.disk
++ boot_part_num = self.stage1_device.parted_partition.number
++ elif self.stage1_device.type == "mdarray":
++ # FIXME: I'm just guessing here. This probably needs the full
++ # treatment, ie: multiple targets for each member.
++ boot_disk = self.stage1_device.parents[0].disk
++ boot_part_num = self.stage1_device.parents[0].parted_partition.number
++ boot_part_num = str(boot_part_num)
++
++ if not os.path.exists(
++ "{}/{}".format(iutil.getSysroot() + self.config_dir, "xen.efi")):
++ xen_efi = [x for x in os.listdir(iutil.getSysroot() + self.config_dir) if
++ x.startswith('xen-') and x.endswith('.efi')][0]
++ shutil.copy("{}/{}".format(iutil.getSysroot() + self.config_dir, xen_efi),
++ "{}/{}".format(iutil.getSysroot() + self.config_dir, "xen.efi"))
++ rc = self.efibootmgr("-c", "-w", "-L", productName,
++ "-d", boot_disk.path, "-p", boot_part_num,
++ "-l",
++ self.efi_dir_as_efifs_dir + "\\xen.efi",
++ root=iutil.getSysroot())
++ if rc:
++ raise BootLoaderError("failed to set new efi boot target")
++
++ def add_image(self, image):
++ super(XenEFI, self).add_image(image)
++ shutil.copy("{}/boot/{}".format(iutil.getSysroot(), image.kernel),
++ os.path.normpath(
++ "{}/{}".format(iutil.getSysroot() + self.config_dir,
++ image.kernel)))
++ if image.initrd is not None:
++ shutil.copy("{}/boot/{}".format(iutil.getSysroot(), image.initrd),
++ os.path.normpath(
++ "{}/{}".format(iutil.getSysroot() + self.config_dir,
++ image.initrd)))
++
++ def write_config_header(self, config):
++ config.write("[global]\n")
++ config.write("default={}\n".format(self.default.version))
++
++ def write_config_images(self, config):
++ for image in self.images:
++ config.write("\n")
++ config.write("[{}]\n".format(image.version))
++ config.write("options=loglvl=all dom0_mem=min:1024M dom0_mem=max:4096M\n")
++ config.write("kernel={} root={} {}\n".format(
++ image.kernel,
++ image.device.fstab_spec,
++ self.boot_args))
++ config.write("ramdisk={}\n".format(image.initrd))
++
++ def write_config_console(self, config):
++ pass
++
++ def write_config_post(self):
++ pass
++
++ def is_valid_stage2_device(self, device, linux=True, non_linux=False):
++ """ XenEFI doesn't use stage2 at all, so allow anything here """
++ return True
++
++ write_config = BootLoader.write_config
++
++
+ class MacEFIGRUB(EFIGRUB):
+ def mactel_config(self):
+ if os.path.exists(iutil.getSysroot() + "/usr/libexec/mactel-boot-setup"):
+@@ -2342,7 +2419,7 @@ class EXTLINUX(BootLoader):
+ # every platform that wants a bootloader needs to be in this dict
+ bootloader_by_platform = {
+ platform.X86: GRUB2,
+- platform.EFI: EFIGRUB,
++ platform.EFI: XenEFI,
+ platform.MacEFI: MacEFIGRUB,
+ platform.PPC: GRUB2,
+ platform.IPSeriesPPC: IPSeriesGRUB2,
+--
+2.14.4
+
diff --git a/0012-anaconda-fix-dracut-module-to-work-with-reduced-depe.patch b/0012-anaconda-fix-dracut-module-to-work-with-reduced-depe.patch
new file mode 100644
index 0000000..16b9237
--- /dev/null
+++ b/0012-anaconda-fix-dracut-module-to-work-with-reduced-depe.patch
@@ -0,0 +1,65 @@
+From 1c37fe7581a01b93d1f46b8eb03b37f5cb09bb0e Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Marek=20Marczykowski-G=C3=B3recki?=
+
+Date: Fri, 19 Oct 2018 08:02:12 +0200
+Subject: [PATCH] anaconda: fix dracut module to work with reduced dependencies
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Do not fail because of not present url-lib. Also 'loop' module requires manual
+loading now.
+
+Signed-off-by: Frédéric Pierret
+---
+ dracut/anaconda-diskroot | 2 ++
+ dracut/module-setup.sh | 2 +-
+ dracut/parse-anaconda-options.sh | 6 +++++-
+ 3 files changed, 8 insertions(+), 2 deletions(-)
+
+diff --git a/dracut/anaconda-diskroot b/dracut/anaconda-diskroot
+index 7e52e052b..230b20418 100755
+--- a/dracut/anaconda-diskroot
++++ b/dracut/anaconda-diskroot
+@@ -43,6 +43,8 @@ kickstart="$(getarg ks= inst.ks=)"
+
+ [ -e "/dev/root" ] && exit 1 # we already have a root device!
+
++modprobe -q loop
++
+ # If we're waiting for a cdrom kickstart, the user might need to swap discs.
+ # So if this is a CDROM drive, make a note of it, but don't mount it (yet).
+ # Once we get the kickstart either the udev trigger or disk-reinsertion will
+diff --git a/dracut/module-setup.sh b/dracut/module-setup.sh
+index 184036188..9f4e9d48c 100755
+--- a/dracut/module-setup.sh
++++ b/dracut/module-setup.sh
+@@ -7,7 +7,7 @@ check() {
+ }
+
+ depends() {
+- echo livenet nfs img-lib convertfs ifcfg
++ echo img-lib dmsquash-live
+ case "$(uname -m)" in
+ s390*) echo cms ;;
+ esac
+diff --git a/dracut/parse-anaconda-options.sh b/dracut/parse-anaconda-options.sh
+index fa1455f8b..8fce64aed 100755
+--- a/dracut/parse-anaconda-options.sh
++++ b/dracut/parse-anaconda-options.sh
+@@ -2,7 +2,11 @@
+ # parse-anaconda-options.sh - parse installer-specific options
+
+ . /lib/anaconda-lib.sh
+-. /lib/url-lib.sh
++if [ -r /lib/url-lib.sh ]; then
++ . /lib/url-lib.sh
++else
++ alias set_http_header=:
++fi
+
+ # create the repodir and isodir that anaconda will look for
+ mkdir -p $repodir $isodir
+--
+2.14.4
+
diff --git a/0013-anaconda-use-installer-kernel-parameters-as-default-.patch b/0013-anaconda-use-installer-kernel-parameters-as-default-.patch
new file mode 100644
index 0000000..7e4439f
--- /dev/null
+++ b/0013-anaconda-use-installer-kernel-parameters-as-default-.patch
@@ -0,0 +1,61 @@
+From 66a87473c6360bac0f47e14fca6293cda8c15bc7 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Marek=20Marczykowski-G=C3=B3recki?=
+
+Date: Fri, 19 Oct 2018 08:02:12 +0200
+Subject: [PATCH] anaconda: use installer kernel parameters as default for
+ installed system
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+This way if any kernel parameter was need to boot Qubes on particular hardware, it will also be set to installed system
+
+Fixes QubesOS/qubes-issues#1650
+
+Signed-off-by: Frédéric Pierret
+---
+ pyanaconda/bootloader.py | 18 ++++++++----------
+ 1 file changed, 8 insertions(+), 10 deletions(-)
+
+diff --git a/pyanaconda/bootloader.py b/pyanaconda/bootloader.py
+index 908020ad0..963af46f9 100644
+--- a/pyanaconda/bootloader.py
++++ b/pyanaconda/bootloader.py
+@@ -229,14 +229,13 @@ class BootLoader(object):
+ def stage2_format_types(self):
+ return ["ext4", "ext3", "ext2"]
+
+- # this is so stupid...
+- global_preserve_args = ["speakup_synth", "apic", "noapic", "apm", "ide",
+- "noht", "acpi", "video", "pci", "nodmraid",
+- "nompath", "nomodeset", "noiswmd", "fips",
+- "selinux", "biosdevname", "ipv6.disable",
+- "net.ifnames"]
+ preserve_args = []
+
++ global_no_preserve_args = ["stage2", "root", "rescue",
++ "rd.live.check", "ip", "repo", "ks",
++ "rd.lvm", "rd.md", "rd.luks", "rd.dm",
++ "rd.lvm.lv"]
++
+ _trusted_boot = False
+
+ def __init__(self):
+@@ -870,11 +869,10 @@ class BootLoader(object):
+ self.boot_args.add("iscsi_firmware")
+
+ #
+- # preservation of some of our boot args
+- # FIXME: this is stupid.
++ # preservation of most of our boot args
+ #
+- for opt in self.global_preserve_args + self.preserve_args:
+- if opt not in flags.cmdline:
++ for opt in flags.cmdline.keys():
++ if opt in self.global_no_preserve_args:
+ continue
+
+ arg = flags.cmdline.get(opt)
+--
+2.14.4
+
diff --git a/0014-anaconda-use-kernel-install-instead-of-grubby-to-reg.patch b/0014-anaconda-use-kernel-install-instead-of-grubby-to-reg.patch
new file mode 100644
index 0000000..67bb068
--- /dev/null
+++ b/0014-anaconda-use-kernel-install-instead-of-grubby-to-reg.patch
@@ -0,0 +1,51 @@
+From 73b928fd84acfde8a8f20ddf20241da9085b87b2 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Marek=20Marczykowski-G=C3=B3recki?=
+
+Date: Fri, 19 Oct 2018 08:02:12 +0200
+Subject: [PATCH] anaconda: use kernel-install instead of grubby to regenerate
+ initrd/grub.conf
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Since we have own hook there, it properly handles Xen. This means we no longer need post scripts in kickstart for that.
+
+Signed-off-by: Frédéric Pierret
+---
+ pyanaconda/packaging/__init__.py | 11 +++--------
+ 1 file changed, 3 insertions(+), 8 deletions(-)
+
+diff --git a/pyanaconda/packaging/__init__.py b/pyanaconda/packaging/__init__.py
+index a99fbe973..8332ce0e5 100644
+--- a/pyanaconda/packaging/__init__.py
++++ b/pyanaconda/packaging/__init__.py
+@@ -608,23 +608,18 @@ class Payload(object):
+ # prevent boot on some systems
+
+ def recreateInitrds(self):
+- """ Recreate the initrds by calling new-kernel-pkg
++ """ Recreate the initrds by calling kernel-install
+
+ This needs to be done after all configuration files have been
+ written, since dracut depends on some of them.
+
+ :returns: None
+ """
+- if not os.path.exists(iutil.getSysroot() + "/usr/sbin/new-kernel-pkg"):
+- log.error("new-kernel-pkg does not exist - grubby wasn't installed? skipping")
+- return
+-
+ for kernel in self.kernelVersionList:
+ log.info("recreating initrd for %s", kernel)
+ if not flags.imageInstall:
+- iutil.execInSysroot("new-kernel-pkg",
+- ["--mkinitrd", "--dracut",
+- "--depmod", "--update", kernel])
++ iutil.execInSysroot("kernel-install",
++ ["add", kernel, "/boot/vmlinuz-%s" % kernel])
+ else:
+ # hostonly is not sensible for disk image installations
+ # using /dev/disk/by-uuid/ is necessary due to disk image naming
+--
+2.14.4
+
diff --git a/0015-anaconda-Fix-a-regular-expression-determining-Releas.patch b/0015-anaconda-Fix-a-regular-expression-determining-Releas.patch
new file mode 100644
index 0000000..4174f85
--- /dev/null
+++ b/0015-anaconda-Fix-a-regular-expression-determining-Releas.patch
@@ -0,0 +1,33 @@
+From c10dd05e7957cc188b69c302e7a9b4c2c8249ce6 Mon Sep 17 00:00:00 2001
+From: "M. Vefa Bicakci"
+Date: Fri, 19 Oct 2018 08:02:12 +0200
+Subject: [PATCH] anaconda: Fix a regular expression determining Release
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Without the start-of-line matcher, other lines are matched as well, causing invalid PACKAGE_RELEASE variable substitutions within the Makefiles.
+
+Signed-off-by: Frédéric Pierret
+---
+ configure.ac | 4 +++-
+ 1 file changed, 3 insertions(+), 1 deletion(-)
+
+diff --git a/configure.ac b/configure.ac
+index df1d206b3..b36cf53ec 100644
+--- a/configure.ac
++++ b/configure.ac
+@@ -97,7 +97,9 @@ SHUT_UP_GCC="-Wno-unused-result"
+ # Add remaining compiler flags we want to use
+ CFLAGS="$CFLAGS -Wall -Werror $SHUT_UP_GCC"
+
+-AC_SUBST(PACKAGE_RELEASE, [1])
++# Get the release number from the spec file
++rel="`awk '/^Release:/ { split($2, r, "%"); print r[[1]] }' $srcdir/anaconda.spec`"
++AC_SUBST(PACKAGE_RELEASE, [$rel])
+
+ # Perform arch related tests
+ AC_CANONICAL_BUILD
+--
+2.14.4
+
diff --git a/0016-anaconda-Do-not-fail-during-initramfs-start-up-due-t.patch b/0016-anaconda-Do-not-fail-during-initramfs-start-up-due-t.patch
new file mode 100644
index 0000000..a39ac1b
--- /dev/null
+++ b/0016-anaconda-Do-not-fail-during-initramfs-start-up-due-t.patch
@@ -0,0 +1,38 @@
+From 40992b40f344685a755f39813f0f2afd9f56e7b6 Mon Sep 17 00:00:00 2001
+From: "M. Vefa Bicakci"
+Date: Fri, 19 Oct 2018 08:02:12 +0200
+Subject: [PATCH] anaconda: Do not fail during initramfs start-up due to
+ missing url-lib
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Signed-off-by: Frédéric Pierret
+---
+ dracut/anaconda-ks-sendheaders.sh | 10 +++++++++-
+ 1 file changed, 9 insertions(+), 1 deletion(-)
+
+diff --git a/dracut/anaconda-ks-sendheaders.sh b/dracut/anaconda-ks-sendheaders.sh
+index 7bc97393b..39fa0ce0d 100755
+--- a/dracut/anaconda-ks-sendheaders.sh
++++ b/dracut/anaconda-ks-sendheaders.sh
+@@ -2,7 +2,15 @@
+ # anaconda-ks-sendheaders.sh - set various HTTP headers for kickstarting
+
+ [ -f /tmp/.ks_sendheaders ] && return
+-command -v set_http_header >/dev/null || . /lib/url-lib.sh
++
++if ! command -v set_http_header >/dev/null; then
++ if ! [ -r /lib/url-lib.sh ]; then
++ alias set_http_header=:
++ return
++ fi
++
++ . /lib/url-lib.sh
++fi
+
+ # inst.ks.sendmac: send MAC addresses in HTTP headers
+ if getargbool 0 kssendmac inst.ks.sendmac; then
+--
+2.14.4
+
diff --git a/0017-anaconda-Disable-the-NTP-configuration-spoke.patch b/0017-anaconda-Disable-the-NTP-configuration-spoke.patch
new file mode 100644
index 0000000..7740ecd
--- /dev/null
+++ b/0017-anaconda-Disable-the-NTP-configuration-spoke.patch
@@ -0,0 +1,733 @@
+From dc6d56efd644de06468ddd225c436bd3610a6527 Mon Sep 17 00:00:00 2001
+From: "M. Vefa Bicakci"
+Date: Fri, 19 Oct 2018 08:02:12 +0200
+Subject: [PATCH] anaconda: Disable the NTP configuration spoke
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Signed-off-by: Frédéric Pierret
+---
+ pyanaconda/ui/gui/spokes/datetime_spoke.glade | 300 +----------------------
+ pyanaconda/ui/gui/spokes/datetime_spoke.py | 332 --------------------------
+ 2 files changed, 2 insertions(+), 630 deletions(-)
+
+diff --git a/pyanaconda/ui/gui/spokes/datetime_spoke.glade b/pyanaconda/ui/gui/spokes/datetime_spoke.glade
+index 9179640fe..fab5ad067 100644
+--- a/pyanaconda/ui/gui/spokes/datetime_spoke.glade
++++ b/pyanaconda/ui/gui/spokes/datetime_spoke.glade
+@@ -1,14 +1,9 @@
+
+-
++
+
+
+
+
+-
+
+-
+
+-
+
+ True
+ False
+@@ -463,78 +238,7 @@
+
+
+
+-
+- True
+- False
+- 1
+- 0.20000000298023224
+- 24
+-
+-
+- True
+- False
+- end
+-
+-
+- True
+- False
+- _Network Time
+- True
+- networkTimeSwitch
+-
+-
+- False
+- True
+- 3
+- 0
+-
+-
+-
+-
+- True
+- True
+-
+-
+-
+- Use Network Time
+-
+-
+-
+-
+- False
+- True
+- 1
+- 1
+-
+-
+-
+-
+- True
+- True
+- True
+- configImage
+-
+-
+-
+- Configure NTP
+-
+-
+-
+-
+- False
+- True
+- 1
+- 2
+-
+-
+-
+-
+-
+-
+- True
+- True
+- 4
+-
++
+
+
+
+diff --git a/pyanaconda/ui/gui/spokes/datetime_spoke.py b/pyanaconda/ui/gui/spokes/datetime_spoke.py
+index 0a1b0b5a8..5fffb670a 100644
+--- a/pyanaconda/ui/gui/spokes/datetime_spoke.py
++++ b/pyanaconda/ui/gui/spokes/datetime_spoke.py
+@@ -45,7 +45,6 @@ from pyanaconda import iutil
+ from pyanaconda import isys
+ from pyanaconda import network
+ from pyanaconda import nm
+-from pyanaconda import ntp
+ from pyanaconda import flags
+ from pyanaconda import constants
+ from pyanaconda.threads import threadMgr, AnacondaThread
+@@ -149,247 +148,6 @@ def _new_date_field_box(store):
+
+ return (box, combo, suffix_label)
+
+-class NTPconfigDialog(GUIObject, GUIDialogInputCheckHandler):
+- builderObjects = ["ntpConfigDialog", "addImage", "serversStore"]
+- mainWidgetName = "ntpConfigDialog"
+- uiFile = "spokes/datetime_spoke.glade"
+-
+- def __init__(self, *args):
+- GUIObject.__init__(self, *args)
+-
+- # Use GUIDIalogInputCheckHandler to manipulate the sensitivity of the
+- # add button, and check for valid input in on_entry_activated
+- add_button = self.builder.get_object("addButton")
+- GUIDialogInputCheckHandler.__init__(self, add_button)
+-
+- #epoch is increased when serversStore is repopulated
+- self._epoch = 0
+- self._epoch_lock = threading.Lock()
+-
+- @property
+- def working_server(self):
+- for row in self._serversStore:
+- if row[SERVER_WORKING] == constants.NTP_SERVER_OK and row[SERVER_USE]:
+- #server is checked and working
+- return row[SERVER_HOSTNAME]
+-
+- return None
+-
+- @property
+- def pools_servers(self):
+- pools = list()
+- servers = list()
+-
+- for used_row in (row for row in self._serversStore if row[SERVER_USE]):
+- if used_row[SERVER_POOL]:
+- pools.append(used_row[SERVER_HOSTNAME])
+- else:
+- servers.append(used_row[SERVER_HOSTNAME])
+-
+- return (pools, servers)
+-
+- def _render_working(self, column, renderer, model, itr, user_data=None):
+- value = model[itr][SERVER_WORKING]
+-
+- if value == constants.NTP_SERVER_QUERY:
+- return "dialog-question"
+- elif value == constants.NTP_SERVER_OK:
+- return "emblem-default"
+- else:
+- return "dialog-error"
+-
+- def initialize(self):
+- self.window.set_size_request(500, 400)
+-
+- workingColumn = self.builder.get_object("workingColumn")
+- workingRenderer = self.builder.get_object("workingRenderer")
+- override_cell_property(workingColumn, workingRenderer, "icon-name",
+- self._render_working)
+-
+- self._serverEntry = self.builder.get_object("serverEntry")
+- self._serversStore = self.builder.get_object("serversStore")
+-
+- self._addButton = self.builder.get_object("addButton")
+-
+- self._poolCheckButton = self.builder.get_object("poolCheckButton")
+-
+- # Validate the server entry box
+- self._serverCheck = self.add_check(self._serverEntry, self._validateServer)
+- self._serverCheck.update_check_status()
+-
+- self._initialize_store_from_config()
+-
+- def _initialize_store_from_config(self):
+- self._serversStore.clear()
+-
+- if self.data.timezone.ntpservers:
+- pools, servers = ntp.internal_to_pools_and_servers(self.data.timezone.ntpservers)
+- else:
+- try:
+- pools, servers = ntp.get_servers_from_config()
+- except ntp.NTPconfigError:
+- log.warning("Failed to load NTP servers configuration")
+- return
+-
+- for pool in pools:
+- self._add_server(pool, True)
+- for server in servers:
+- self._add_server(server, False)
+-
+-
+- def _validateServer(self, inputcheck):
+- server = self.get_input(inputcheck.input_obj)
+-
+- # If not set, fail the check to keep the button insensitive, but don't
+- # display an error
+- if not server:
+- return InputCheck.CHECK_SILENT
+-
+- (valid, error) = network.sanityCheckHostname(server)
+- if not valid:
+- return "'%s' is not a valid hostname: %s" % (server, error)
+- else:
+- return InputCheck.CHECK_OK
+-
+- def refresh(self):
+- self._serverEntry.grab_focus()
+-
+- def refresh_servers_state(self):
+- itr = self._serversStore.get_iter_first()
+- while itr:
+- self._refresh_server_working(itr)
+- itr = self._serversStore.iter_next(itr)
+-
+- def run(self):
+- self.window.show()
+- rc = self.window.run()
+- self.window.hide()
+-
+- #OK clicked
+- if rc == 1:
+- new_pools, new_servers = self.pools_servers
+-
+- if flags.can_touch_runtime_system("save NTP servers configuration"):
+- ntp.save_servers_to_config(new_pools, new_servers)
+- iutil.restart_service(NTP_SERVICE)
+-
+- #Cancel clicked, window destroyed...
+- else:
+- self._epoch_lock.acquire()
+- self._epoch += 1
+- self._epoch_lock.release()
+-
+- self._initialize_store_from_config()
+-
+- return rc
+-
+- def _set_server_ok_nok(self, itr, epoch_started):
+- """
+- If the server is working, set its data to NTP_SERVER_OK, otherwise set its
+- data to NTP_SERVER_NOK.
+-
+- :param itr: iterator of the $server's row in the self._serversStore
+-
+- """
+-
+- @gtk_action_nowait
+- def set_store_value(arg_tuple):
+- """
+- We need a function for this, because this way it can be added to
+- the MainLoop with thread-safe GLib.idle_add (but only with one
+- argument).
+-
+- :param arg_tuple: (store, itr, column, value)
+-
+- """
+-
+- (store, itr, column, value) = arg_tuple
+- store.set_value(itr, column, value)
+-
+- orig_hostname = self._serversStore[itr][SERVER_HOSTNAME]
+- server_working = ntp.ntp_server_working(self._serversStore[itr][SERVER_HOSTNAME])
+-
+- #do not let dialog change epoch while we are modifying data
+- self._epoch_lock.acquire()
+-
+- #check if we are in the same epoch as the dialog (and the serversStore)
+- #and if the server wasn't changed meanwhile
+- if epoch_started == self._epoch:
+- actual_hostname = self._serversStore[itr][SERVER_HOSTNAME]
+-
+- if orig_hostname == actual_hostname:
+- if server_working:
+- set_store_value((self._serversStore,
+- itr, SERVER_WORKING, constants.NTP_SERVER_OK))
+- else:
+- set_store_value((self._serversStore,
+- itr, SERVER_WORKING, constants.NTP_SERVER_NOK))
+- self._epoch_lock.release()
+-
+- @gtk_action_nowait
+- def _refresh_server_working(self, itr):
+- """ Runs a new thread with _set_server_ok_nok(itr) as a taget. """
+-
+- self._serversStore.set_value(itr, SERVER_WORKING, constants.NTP_SERVER_QUERY)
+- threadMgr.add(AnacondaThread(prefix=constants.THREAD_NTP_SERVER_CHECK,
+- target=self._set_server_ok_nok,
+- args=(itr, self._epoch)))
+-
+- def _add_server(self, server, pool=False):
+- """
+- Checks if a given server is a valid hostname and if yes, adds it
+- to the list of servers.
+-
+- :param server: string containing hostname
+-
+- """
+-
+- itr = self._serversStore.append([server, pool, constants.NTP_SERVER_QUERY, True])
+-
+- #do not block UI while starting thread (may take some time)
+- self._refresh_server_working(itr)
+-
+- def on_entry_activated(self, entry, *args):
+- # Check that the input check has passed
+- if self._serverCheck.check_status == InputCheck.CHECK_OK:
+- self._add_server(entry.get_text(), self._poolCheckButton.get_active())
+- entry.set_text("")
+- self._poolCheckButton.set_active(False)
+-
+- def on_add_clicked(self, *args):
+- self._serverEntry.emit("activate")
+-
+- def on_use_server_toggled(self, renderer, path, *args):
+- itr = self._serversStore.get_iter(path)
+- old_value = self._serversStore[itr][SERVER_USE]
+-
+- self._serversStore.set_value(itr, SERVER_USE, not old_value)
+-
+- def on_pool_toggled(self, renderer, path, *args):
+- itr = self._serversStore.get_iter(path)
+- old_value = self._serversStore[itr][SERVER_POOL]
+-
+- self._serversStore.set_value(itr, SERVER_POOL, not old_value)
+-
+- def on_server_edited(self, renderer, path, new_text, *args):
+- if not path:
+- return
+-
+- (valid, error) = network.sanityCheckHostname(new_text)
+- if not valid:
+- log.error("'%s' is not a valid hostname: %s", new_text, error)
+- return
+-
+- itr = self._serversStore.get_iter(path)
+-
+- if self._serversStore[itr][SERVER_HOSTNAME] == new_text:
+- return
+-
+- self._serversStore.set_value(itr, SERVER_HOSTNAME, new_text)
+- self._serversStore.set_value(itr, SERVER_WORKING, constants.NTP_SERVER_QUERY)
+-
+- self._refresh_server_working(itr)
+-
+ class DatetimeSpoke(FirstbootSpokeMixIn, NormalSpoke):
+ """
+ .. inheritance-diagram:: DatetimeSpoke
+@@ -480,8 +238,6 @@ class DatetimeSpoke(FirstbootSpokeMixIn, NormalSpoke):
+ self._year_format, suffix = formats[widgets.index(year_box)]
+ year_label.set_text(suffix)
+
+- self._ntpSwitch = self.builder.get_object("networkTimeSwitch")
+-
+ self._regions_zones = get_all_regions_and_timezones()
+
+ # Set the initial sensitivity of the AM/PM toggle based on the time-type selected
+@@ -490,9 +246,6 @@ class DatetimeSpoke(FirstbootSpokeMixIn, NormalSpoke):
+ if not flags.can_touch_runtime_system("modify system time and date"):
+ self._set_date_time_setting_sensitive(False)
+
+- self._config_dialog = NTPconfigDialog(self.data)
+- self._config_dialog.initialize()
+-
+ threadMgr.add(AnacondaThread(name=constants.THREAD_DATE_TIME,
+ target=self._initialize))
+
+@@ -574,8 +327,6 @@ class DatetimeSpoke(FirstbootSpokeMixIn, NormalSpoke):
+ self.data.timezone.seen = False
+ self._kickstarted = False
+
+- self.data.timezone.nontp = not self._ntpSwitch.get_active()
+-
+ def execute(self):
+ if self._update_datetime_timer_id is not None:
+ GLib.source_remove(self._update_datetime_timer_id)
+@@ -610,20 +361,6 @@ class DatetimeSpoke(FirstbootSpokeMixIn, NormalSpoke):
+
+ self._update_datetime()
+
+- has_active_network = nm.nm_is_connected()
+- if not has_active_network:
+- self._show_no_network_warning()
+- else:
+- self.clear_info()
+- gtk_call_once(self._config_dialog.refresh_servers_state)
+-
+- if flags.can_touch_runtime_system("get NTP service state"):
+- ntp_working = has_active_network and iutil.service_running(NTP_SERVICE)
+- else:
+- ntp_working = not self.data.timezone.nontp
+-
+- self._ntpSwitch.set_active(ntp_working)
+-
+ @gtk_action_wait
+ def _set_timezone(self, timezone):
+ """
+@@ -1078,72 +815,3 @@ class DatetimeSpoke(FirstbootSpokeMixIn, NormalSpoke):
+ #contains all date/time setting widgets
+ footer_alignment = self.builder.get_object("footerAlignment")
+ footer_alignment.set_sensitive(sensitive)
+-
+- def _show_no_network_warning(self):
+- self.set_warning(_("You need to set up networking first if you "\
+- "want to use NTP"))
+-
+- def _show_no_ntp_server_warning(self):
+- self.set_warning(_("You have no working NTP server configured"))
+-
+- def on_ntp_switched(self, switch, *args):
+- if switch.get_active():
+- #turned ON
+- if not flags.can_touch_runtime_system("start NTP service"):
+- #cannot touch runtime system, not much to do here
+- return
+-
+- if not nm.nm_is_connected():
+- self._show_no_network_warning()
+- switch.set_active(False)
+- return
+- else:
+- self.clear_info()
+-
+- working_server = self._config_dialog.working_server
+- if working_server is None:
+- self._show_no_ntp_server_warning()
+- else:
+- #we need a one-time sync here, because chronyd would not change
+- #the time as drastically as we need
+- ntp.one_time_sync_async(working_server)
+-
+- ret = iutil.start_service(NTP_SERVICE)
+- self._set_date_time_setting_sensitive(False)
+-
+- #if starting chronyd failed and chronyd is not running,
+- #set switch back to OFF
+- if (ret != 0) and not iutil.service_running(NTP_SERVICE):
+- switch.set_active(False)
+-
+- else:
+- #turned OFF
+- if not flags.can_touch_runtime_system("stop NTP service"):
+- #cannot touch runtime system, nothing to do here
+- return
+-
+- self._set_date_time_setting_sensitive(True)
+- ret = iutil.stop_service(NTP_SERVICE)
+-
+- #if stopping chronyd failed and chronyd is running,
+- #set switch back to ON
+- if (ret != 0) and iutil.service_running(NTP_SERVICE):
+- switch.set_active(True)
+-
+- self.clear_info()
+-
+- def on_ntp_config_clicked(self, *args):
+- self._config_dialog.refresh()
+-
+- with self.main_window.enlightbox(self._config_dialog.window):
+- response = self._config_dialog.run()
+-
+- if response == 1:
+- pools, servers = self._config_dialog.pools_servers
+- self.data.timezone.ntpservers = ntp.pools_servers_to_internal(pools, servers)
+-
+- if self._config_dialog.working_server is None:
+- self._show_no_ntp_server_warning()
+- else:
+- self.clear_info()
+-
+--
+2.14.4
+
diff --git a/0018-anaconda-drop-useless-on-Qubes-dependencies-on-netwo.patch b/0018-anaconda-drop-useless-on-Qubes-dependencies-on-netwo.patch
new file mode 100644
index 0000000..2358c05
--- /dev/null
+++ b/0018-anaconda-drop-useless-on-Qubes-dependencies-on-netwo.patch
@@ -0,0 +1,37 @@
+From dfa00956589ebcb0b8c7f4faca9f5a833945b74c Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Marek=20Marczykowski-G=C3=B3recki?=
+
+Date: Fri, 19 Oct 2018 08:02:12 +0200
+Subject: [PATCH] anaconda: drop useless on Qubes dependencies on network
+ filesystems
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Installing Qubes on network drive is not supported, so drop those dependencies.
+
+Signed-off-by: Frédéric Pierret
+---
+ pyanaconda/kickstart.py | 6 ------
+ 1 file changed, 6 deletions(-)
+
+diff --git a/pyanaconda/kickstart.py b/pyanaconda/kickstart.py
+index 7d67bf1a6..1121e0928 100644
+--- a/pyanaconda/kickstart.py
++++ b/pyanaconda/kickstart.py
+@@ -2155,12 +2155,6 @@ def parseKickstart(f):
+
+ # We need this so all the /dev/disk/* stuff is set up before parsing.
+ udev.trigger(subsystem="block", action="change")
+- # So that drives onlined by these can be used in the ks file
+- blivet.iscsi.iscsi.startup()
+- blivet.fcoe.fcoe.startup()
+- blivet.zfcp.zfcp.startup()
+- # Note we do NOT call dasd.startup() here, that does not online drives, but
+- # only checks if they need formatting, which requires zerombr to be known
+
+ try:
+ ksparser.readKickstart(f)
+--
+2.14.4
+
diff --git a/0019-anaconda-skip-NTP-installation-and-setup-in-dom0.patch b/0019-anaconda-skip-NTP-installation-and-setup-in-dom0.patch
new file mode 100644
index 0000000..b40c577
--- /dev/null
+++ b/0019-anaconda-skip-NTP-installation-and-setup-in-dom0.patch
@@ -0,0 +1,35 @@
+From 29313be0db3cd1498629e9f5ff9a1b64b5fceec3 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Marek=20Marczykowski-G=C3=B3recki?=
+
+Date: Fri, 19 Oct 2018 08:02:12 +0200
+Subject: [PATCH] anaconda: skip NTP installation and setup in dom0
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Dom0 has no direct network access, to this doesn't make sense anyway.
+
+Fixes QubesOS/qubes-issues#2110
+
+Signed-off-by: Frédéric Pierret
+---
+ pyanaconda/kickstart.py | 3 +++
+ 1 file changed, 3 insertions(+)
+
+diff --git a/pyanaconda/kickstart.py b/pyanaconda/kickstart.py
+index 1121e0928..5ac54b4fd 100644
+--- a/pyanaconda/kickstart.py
++++ b/pyanaconda/kickstart.py
+@@ -1715,6 +1715,9 @@ class Timezone(commands.timezone.F25_Timezone):
+ self._disabled_chrony = False
+
+ def setup(self, ksdata):
++ ### Skip the whole NTP setup in Qubes dom0
++ return
++
+ # do not install and use NTP package
+ if self.nontp or NTP_PACKAGE in ksdata.packages.excludedList:
+ if iutil.service_running(NTP_SERVICE) and \
+--
+2.14.4
+
diff --git a/0020-anaconda-add-skip_grub-parameter-and-allow-boot-encr.patch b/0020-anaconda-add-skip_grub-parameter-and-allow-boot-encr.patch
new file mode 100644
index 0000000..3bf8474
--- /dev/null
+++ b/0020-anaconda-add-skip_grub-parameter-and-allow-boot-encr.patch
@@ -0,0 +1,56 @@
+From fd7575cbd24f9f72447755995cd9e6694e23f149 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Marek=20Marczykowski-G=C3=B3recki?=
+
+Date: Fri, 19 Oct 2018 08:02:12 +0200
+Subject: [PATCH] anaconda: add skip_grub parameter and allow boot encryption and the lvmlv format
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+On coreboot systems, as grub2 can be used a payload it is possible to have all the partitions encrypted.
+
+Based on commits bf803e7 and 62cb1ca.
+
+Signed-off-by: Frédéric Pierret
+---
+ pyanaconda/bootloader.py | 13 +++++++------
+ 1 file changed, 7 insertions(+), 6 deletions(-)
+
+diff --git a/pyanaconda/bootloader.py b/pyanaconda/bootloader.py
+index 963af46f9..fa56b532a 100644
+--- a/pyanaconda/bootloader.py
++++ b/pyanaconda/bootloader.py
+@@ -43,6 +43,8 @@ from blivet.size import Size
+ from pyanaconda.i18n import _, N_
+
+ import logging
++import subprocess
++
+ log = logging.getLogger("anaconda")
+
+ class serial_opts(object):
+@@ -1411,16 +1413,15 @@ class GRUB2(GRUB):
+ raid.RAID5, raid.RAID6, raid.RAID10]
+ stage2_raid_metadata = ["0", "0.90", "1.0", "1.2"]
+
+- @property
+- def stage2_format_types(self):
+- if productName.startswith("Red Hat "): # pylint: disable=no-member
+- return ["xfs", "ext4", "ext3", "ext2", "btrfs"]
+- else:
+- return ["ext4", "ext3", "ext2", "btrfs", "xfs"]
++ stage2_format_types = ["ext4", "ext3", "ext2", "btrfs", "xfs"]
+
+ def __init__(self):
+ super(GRUB2, self).__init__()
+
++ self.encryption_support = True
++ self.stage2_format_types += ["lvmlv"]
++ self.skip_bootloader = flags.cmdline.getbool("skip_grub", False)
++
+ # XXX we probably need special handling for raid stage1 w/ gpt disklabel
+ # since it's unlikely there'll be a bios boot partition on each disk
+
+--
+2.14.4
+
diff --git a/0021-anaconda-switch-default-partitioning-scheme-to-LVM-T.patch b/0021-anaconda-switch-default-partitioning-scheme-to-LVM-T.patch
new file mode 100644
index 0000000..a471a97
--- /dev/null
+++ b/0021-anaconda-switch-default-partitioning-scheme-to-LVM-T.patch
@@ -0,0 +1,65 @@
+From efeff13f25e049159c556d40511f953e25aa3dc5 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Marek=20Marczykowski-G=C3=B3recki?=
+
+Date: Fri, 19 Oct 2018 08:02:12 +0200
+Subject: [PATCH] anaconda: switch default partitioning scheme to LVM Thin
+ Provisioning
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+QubesOS/qubes-issues#2412
+
+Signed-off-by: Frédéric Pierret
+---
+ pyanaconda/ui/gui/spokes/storage.py | 4 ++--
+ pyanaconda/ui/tui/spokes/storage.py | 4 ++--
+ 2 files changed, 4 insertions(+), 4 deletions(-)
+
+diff --git a/pyanaconda/ui/gui/spokes/storage.py b/pyanaconda/ui/gui/spokes/storage.py
+index 87a923657..7feda7db3 100644
+--- a/pyanaconda/ui/gui/spokes/storage.py
++++ b/pyanaconda/ui/gui/spokes/storage.py
+@@ -75,7 +75,7 @@ from pyanaconda import constants, iutil, isys
+ from pyanaconda.bootloader import BootLoaderError
+ from pyanaconda.storage_utils import on_disk_storage
+
+-from pykickstart.constants import CLEARPART_TYPE_NONE, AUTOPART_TYPE_LVM
++from pykickstart.constants import CLEARPART_TYPE_NONE, AUTOPART_TYPE_LVM, AUTOPART_TYPE_LVM_THINP
+ from pykickstart.errors import KickstartParseError
+
+ import sys
+@@ -553,7 +553,7 @@ class StorageSpoke(NormalSpoke, StorageChecker):
+ self.autopart = self.data.autopart.autopart
+ self.autoPartType = self.data.autopart.type
+ if self.autoPartType is None:
+- self.autoPartType = AUTOPART_TYPE_LVM
++ self.autoPartType = AUTOPART_TYPE_LVM_THINP
+ self.encrypted = self.data.autopart.encrypted
+ self.passphrase = self.data.autopart.passphrase
+
+diff --git a/pyanaconda/ui/tui/spokes/storage.py b/pyanaconda/ui/tui/spokes/storage.py
+index fd6d7a505..d8ec992b9 100644
+--- a/pyanaconda/ui/tui/spokes/storage.py
++++ b/pyanaconda/ui/tui/spokes/storage.py
+@@ -42,7 +42,7 @@ from pyanaconda.constants_text import INPUT_PROCESSED
+ from pyanaconda.i18n import _, P_, N_, C_
+ from pyanaconda.bootloader import BootLoaderError
+
+-from pykickstart.constants import CLEARPART_TYPE_ALL, CLEARPART_TYPE_LINUX, CLEARPART_TYPE_NONE, AUTOPART_TYPE_LVM
++from pykickstart.constants import CLEARPART_TYPE_ALL, CLEARPART_TYPE_LINUX, CLEARPART_TYPE_NONE, AUTOPART_TYPE_LVM, AUTOPART_TYPE_LVM_THINP
+ from pykickstart.errors import KickstartParseError
+
+ from collections import OrderedDict
+@@ -351,7 +351,7 @@ class StorageSpoke(NormalTUISpoke):
+ self.data.clearpart.drives = self.selected_disks[:]
+
+ if self.data.autopart.type is None:
+- self.data.autopart.type = AUTOPART_TYPE_LVM
++ self.data.autopart.type = AUTOPART_TYPE_LVM_THINP
+
+ if self.autopart:
+ self.clearPartType = CLEARPART_TYPE_ALL
+--
+2.14.4
+
diff --git a/0022-anaconda-add-console-none-Xen-parameter.patch b/0022-anaconda-add-console-none-Xen-parameter.patch
new file mode 100644
index 0000000..336dbee
--- /dev/null
+++ b/0022-anaconda-add-console-none-Xen-parameter.patch
@@ -0,0 +1,29 @@
+From 9891905a9d0b3ae52fafdfeae35b062a9b94bccb Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Marek=20Marczykowski-G=C3=B3recki?=
+
+Date: Fri, 19 Oct 2018 08:02:12 +0200
+Subject: [PATCH] anaconda: add "console=none" Xen parameter
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Signed-off-by: Frédéric Pierret
+---
+ pyanaconda/bootloader.py | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/pyanaconda/bootloader.py b/pyanaconda/bootloader.py
+index fa56b532a..0e5a9a062 100644
+--- a/pyanaconda/bootloader.py
++++ b/pyanaconda/bootloader.py
+@@ -1504,6 +1504,7 @@ class GRUB2(GRUB):
+ # boot arguments
+ log.info("bootloader.py: used boot args: %s ", self.boot_args)
+ defaults.write("GRUB_CMDLINE_LINUX=\"%s\"\n" % self.boot_args)
++ defaults.write("GRUB_CMDLINE_XEN_DEFAULT=\"console=none\"\n")
+ defaults.write("GRUB_DISABLE_RECOVERY=\"true\"\n")
+ defaults.write("GRUB_THEME=\"/boot/grub2/themes/system/theme.txt\"\n")
+ defaults.close()
+--
+2.14.4
+
diff --git a/0023-anaconda-add-dom0_mem-min-1024M-to-default-xen-cmdli.patch b/0023-anaconda-add-dom0_mem-min-1024M-to-default-xen-cmdli.patch
new file mode 100644
index 0000000..fcf03d9
--- /dev/null
+++ b/0023-anaconda-add-dom0_mem-min-1024M-to-default-xen-cmdli.patch
@@ -0,0 +1,34 @@
+From dfa27101877f13bf3d0c6b7cc2715bf355c9f6e5 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Marek=20Marczykowski-G=C3=B3recki?=
+
+Date: Fri, 19 Oct 2018 08:02:12 +0200
+Subject: [PATCH] anaconda: add dom0_mem=min:1024M to default xen cmdline
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+This will solve #959 for new installations.
+
+Related to qubesos/qubes-issues#959
+
+Signed-off-by: Frédéric Pierret
+---
+ pyanaconda/bootloader.py | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/pyanaconda/bootloader.py b/pyanaconda/bootloader.py
+index 0e5a9a062..e726517fe 100644
+--- a/pyanaconda/bootloader.py
++++ b/pyanaconda/bootloader.py
+@@ -1504,7 +1504,7 @@ class GRUB2(GRUB):
+ # boot arguments
+ log.info("bootloader.py: used boot args: %s ", self.boot_args)
+ defaults.write("GRUB_CMDLINE_LINUX=\"%s\"\n" % self.boot_args)
+- defaults.write("GRUB_CMDLINE_XEN_DEFAULT=\"console=none\"\n")
++ defaults.write("GRUB_CMDLINE_XEN_DEFAULT=\"console=none dom0_mem=min:1024M\"\n")
+ defaults.write("GRUB_DISABLE_RECOVERY=\"true\"\n")
+ defaults.write("GRUB_THEME=\"/boot/grub2/themes/system/theme.txt\"\n")
+ defaults.close()
+--
+2.14.4
+
diff --git a/0024-anaconda-limit-dom0-maxmem-to-4GB-to-limit-its-overh.patch b/0024-anaconda-limit-dom0-maxmem-to-4GB-to-limit-its-overh.patch
new file mode 100644
index 0000000..15f903c
--- /dev/null
+++ b/0024-anaconda-limit-dom0-maxmem-to-4GB-to-limit-its-overh.patch
@@ -0,0 +1,36 @@
+From 8aa4e95d549833bb481fad948ff241838cc02dcf Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Marek=20Marczykowski-G=C3=B3recki?=
+
+Date: Fri, 19 Oct 2018 08:02:12 +0200
+Subject: [PATCH] anaconda: limit dom0 maxmem to 4GB to limit its overhead on
+ big systems
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Linux kernel have some memory overhead depending on maxmem. Dom0 isn't meant to use that much memory (most should be assigned to AppVMs), so on big systems this will be pure waste.
+
+QubesOS/qubes-issues#1136
+Fixes QubesOS/qubes-issues#1313
+
+Signed-off-by: Frédéric Pierret
+---
+ pyanaconda/bootloader.py | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/pyanaconda/bootloader.py b/pyanaconda/bootloader.py
+index e726517fe..26a48fc7a 100644
+--- a/pyanaconda/bootloader.py
++++ b/pyanaconda/bootloader.py
+@@ -1504,7 +1504,7 @@ class GRUB2(GRUB):
+ # boot arguments
+ log.info("bootloader.py: used boot args: %s ", self.boot_args)
+ defaults.write("GRUB_CMDLINE_LINUX=\"%s\"\n" % self.boot_args)
+- defaults.write("GRUB_CMDLINE_XEN_DEFAULT=\"console=none dom0_mem=min:1024M\"\n")
++ defaults.write("GRUB_CMDLINE_XEN_DEFAULT=\"console=none dom0_mem=min:1024M dom0_mem=max:4096M\"\n")
+ defaults.write("GRUB_DISABLE_RECOVERY=\"true\"\n")
+ defaults.write("GRUB_THEME=\"/boot/grub2/themes/system/theme.txt\"\n")
+ defaults.close()
+--
+2.14.4
+
diff --git a/0025-anaconda-disable-iommu-for-IGFX.patch b/0025-anaconda-disable-iommu-for-IGFX.patch
new file mode 100644
index 0000000..8b6e643
--- /dev/null
+++ b/0025-anaconda-disable-iommu-for-IGFX.patch
@@ -0,0 +1,49 @@
+From bc8defc9d1f4e07685d4535ae47659aae8f29aed Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Marek=20Marczykowski-G=C3=B3recki?=
+
+Date: Fri, 19 Oct 2018 08:02:12 +0200
+Subject: [PATCH] anaconda: disable iommu for IGFX
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Many Intel processors (and BIOSes) have invalid IOMMU configuration for
+IGFX, which cause multiple problems - from screen glitches, to system
+hang.
+Since IGFX currently is still in dom0 (isn't isolated from other system
+components), disabling IOMMU for it doesn't lower overall security.
+When GUI domain will be implemented, we need to re-enable IOMMU here and
+hope hardware manufacturers will fix it in the meantime.
+
+Fixes QubesOS/qubes-issues#2836
+
+Signed-off-by: Frédéric Pierret
+---
+ pyanaconda/bootloader.py | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/pyanaconda/bootloader.py b/pyanaconda/bootloader.py
+index 26a48fc7a..b0db4a087 100644
+--- a/pyanaconda/bootloader.py
++++ b/pyanaconda/bootloader.py
+@@ -1504,7 +1504,7 @@ class GRUB2(GRUB):
+ # boot arguments
+ log.info("bootloader.py: used boot args: %s ", self.boot_args)
+ defaults.write("GRUB_CMDLINE_LINUX=\"%s\"\n" % self.boot_args)
+- defaults.write("GRUB_CMDLINE_XEN_DEFAULT=\"console=none dom0_mem=min:1024M dom0_mem=max:4096M\"\n")
++ defaults.write("GRUB_CMDLINE_XEN_DEFAULT=\"console=none dom0_mem=min:1024M dom0_mem=max:4096M iommu=no-igfx\"\n")
+ defaults.write("GRUB_DISABLE_RECOVERY=\"true\"\n")
+ defaults.write("GRUB_THEME=\"/boot/grub2/themes/system/theme.txt\"\n")
+ defaults.close()
+@@ -1885,7 +1885,7 @@ class XenEFI(EFIGRUB):
+ for image in self.images:
+ config.write("\n")
+ config.write("[{}]\n".format(image.version))
+- config.write("options=loglvl=all dom0_mem=min:1024M dom0_mem=max:4096M\n")
++ config.write("options=loglvl=all dom0_mem=min:1024M dom0_mem=max:4096M iommu=no-igfx\n")
+ config.write("kernel={} root={} {}\n".format(
+ image.kernel,
+ image.device.fstab_spec,
+--
+2.14.4
+
diff --git a/0026-anaconda-check-for-virtualization-features.patch b/0026-anaconda-check-for-virtualization-features.patch
new file mode 100644
index 0000000..20947e7
--- /dev/null
+++ b/0026-anaconda-check-for-virtualization-features.patch
@@ -0,0 +1,121 @@
+From 1e8e3f8cef09f30e31f878ef4ccade45cb00a6d1 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Marek=20Marczykowski-G=C3=B3recki?=
+
+Date: Fri, 19 Oct 2018 08:02:12 +0200
+Subject: [PATCH] anaconda: check for virtualization features
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Warn if the hardware lack features required for proper Qubes OS operation.
+
+Fixes QubesOS/qubes-issues#2977
+
+Signed-off-by: Frédéric Pierret
+---
+ pyanaconda/iutil.py | 17 +++++++++++++++++
+ pyanaconda/ui/gui/spokes/welcome.glade | 2 +-
+ pyanaconda/ui/gui/spokes/welcome.py | 8 ++++++--
+ pyanaconda/ui/tui/spokes/warnings_spoke.py | 16 ++++++++--------
+ 4 files changed, 32 insertions(+), 11 deletions(-)
+
+diff --git a/pyanaconda/iutil.py b/pyanaconda/iutil.py
+index d966ca65c..20ed54302 100644
+--- a/pyanaconda/iutil.py
++++ b/pyanaconda/iutil.py
+@@ -1098,6 +1098,23 @@ def is_unsupported_hw():
+ tainted = 0
+
+ status = bool(tainted & UNSUPPORTED_HW)
++ try:
++ xl_info = subprocess.check_output(['xl', 'info'])
++ xl_dmesg = subprocess.check_output(['xl', 'dmesg'])
++ except subprocess.CalledProcessError:
++ status = 'xl call failed'
++ else:
++ missing_features = []
++ for line in xl_info.splitlines():
++ if line.startswith(b'virt_caps'):
++ if b'hvm' not in line:
++ missing_features.append('HVM/VT-x/AMD-V')
++ if b'hvm_directio' not in line:
++ missing_features.append('IOMMU/VT-d/AMD-Vi')
++ if b'HVM: Hardware Assisted Paging (HAP) detected' not in xl_dmesg:
++ missing_features.append('HAP/SLAT/EPT/RVI')
++ status = ', '.join(missing_features)
++
+ if status:
+ log.debug("Installing on Unsupported Hardware")
+ return status
+diff --git a/pyanaconda/ui/gui/spokes/welcome.glade b/pyanaconda/ui/gui/spokes/welcome.glade
+index 2373a75f1..87e5bf0e9 100644
+--- a/pyanaconda/ui/gui/spokes/welcome.glade
++++ b/pyanaconda/ui/gui/spokes/welcome.glade
+@@ -507,7 +507,7 @@
+ True
+ False
+ start
+- This hardware (or a combination thereof) is not supported by Red Hat. For more information on supported hardware, please refer to http://www.redhat.com/hardware.
++ This hardware lack features required by Qubes OS. Missing features: %(features)s. For more information on supported hardware, please refer to https://www.qubes-os.org/system-requirements/
+ True
+
+
+diff --git a/pyanaconda/ui/gui/spokes/welcome.py b/pyanaconda/ui/gui/spokes/welcome.py
+index c77e36214..802a2179b 100644
+--- a/pyanaconda/ui/gui/spokes/welcome.py
++++ b/pyanaconda/ui/gui/spokes/welcome.py
+@@ -295,9 +295,13 @@ class WelcomeLanguageSpoke(LangLocaleHandler, StandaloneSpoke):
+ sys.exit(0)
+
+ # pylint: disable=no-member
+- if productName.startswith("Red Hat ") and \
+- is_unsupported_hw() and not self.data.unsupportedhardware.unsupported_hardware:
++ unsupported_status = is_unsupported_hw()
++ if unsupported_status:
++ # Fedora kickstart do not have unsupported_hardware option:
++ # and not self.data.unsupportedhardware.unsupported_hardware:
+ dlg = self.builder.get_object("unsupportedHardwareDialog")
++ msg = self.builder.get_object("unsupportedHardwareDesc")
++ msg.set_text(_(msg.get_text()) % {'features': unsupported_status})
+ with self.main_window.enlightbox(dlg):
+ rc = dlg.run()
+ dlg.destroy()
+diff --git a/pyanaconda/ui/tui/spokes/warnings_spoke.py b/pyanaconda/ui/tui/spokes/warnings_spoke.py
+index 6334c656c..8aed09625 100644
+--- a/pyanaconda/ui/tui/spokes/warnings_spoke.py
++++ b/pyanaconda/ui/tui/spokes/warnings_spoke.py
+@@ -43,15 +43,15 @@ class WarningsSpoke(StandaloneTUISpoke):
+ def __init__(self, *args, **kwargs):
+ StandaloneTUISpoke.__init__(self, *args, **kwargs)
+
+- self._message = _("This hardware (or a combination thereof) is not "
+- "supported by Red Hat. For more information on "
+- "supported hardware, please refer to "
+- "http://www.redhat.com/hardware.")
++ self._message = _("This hardware lack features required by Qubes OS. "
++ "Missing features: %(features)s. "
++ "For more information on supported hardware, "
++ "please refer to https://www.qubes-os.org/system-requirements/")
+ # Does anything need to be displayed?
+ # pylint: disable=no-member
+- self._unsupported = productName.startswith("Red Hat ") and \
+- is_unsupported_hw() and \
+- not self.data.unsupportedhardware.unsupported_hardware
++ # self._unsupported = not self.data.unsupportedhardware.unsupported_hardware \
++ # and is_unsupported_hw()
++ self._unsupported = is_unsupported_hw()
+
+ @property
+ def completed(self):
+@@ -60,7 +60,7 @@ class WarningsSpoke(StandaloneTUISpoke):
+ def refresh(self, args=None):
+ StandaloneTUISpoke.refresh(self, args)
+
+- self._window += [TextWidget(self._message), ""]
++ self._window += [TextWidget(self._message % {'features': self._unsupported}), ""]
+
+ return True
+
+--
+2.14.4
+
diff --git a/0027-anaconda-generate-proper-extlinux.conf.patch b/0027-anaconda-generate-proper-extlinux.conf.patch
new file mode 100644
index 0000000..7b39227
--- /dev/null
+++ b/0027-anaconda-generate-proper-extlinux.conf.patch
@@ -0,0 +1,48 @@
+From 2f453961a603a30bd132d7883701dfe8320ee95b Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Marek=20Marczykowski-G=C3=B3recki?=
+
+Date: Fri, 19 Oct 2018 08:02:12 +0200
+Subject: [PATCH] anaconda: generate proper extlinux.conf
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Fixes QubesOS/qubes-issues#2902
+
+Signed-off-by: Frédéric Pierret
+---
+ pyanaconda/bootloader.py | 10 +++++++---
+ 1 file changed, 7 insertions(+), 3 deletions(-)
+
+diff --git a/pyanaconda/bootloader.py b/pyanaconda/bootloader.py
+index b0db4a087..b1e9ff421 100644
+--- a/pyanaconda/bootloader.py
++++ b/pyanaconda/bootloader.py
+@@ -2352,6 +2352,8 @@ class EXTLINUX(BootLoader):
+
+ def write_config_images(self, config):
+ self.write_config_console(config)
++ xen_gz = [x for x in os.listdir(iutil.getSysroot() + self.config_dir) if
++ x.startswith('xen-') and x.endswith('.gz')][0]
+ for image in self.images:
+ args = Arguments()
+ args.update(["root=%s" % image.device.fstab_spec, "ro"])
+@@ -2364,10 +2366,12 @@ class EXTLINUX(BootLoader):
+ label = "%s(%s)" % (self.image_label(image), image.version)
+ label = label.replace(" ", "")
+ stanza = ("label %(label)s\n"
+- "\tkernel %(boot_prefix)s/%(kernel)s\n"
+- "\tinitrd %(boot_prefix)s/%(initrd)s\n"
+- "\tappend %(args)s\n\n"
++ "\tkernel mboot.c32\n"
++ "\tappend %(boot_prefix)s/%(xen)s --- "
++ "%(boot_prefix)s/%(kernel)s %(args)s --- "
++ "%(boot_prefix)s/%(initrd)s\n"
+ % {"label": label,
++ "xen": xen_gz,
+ "kernel": image.kernel,
+ "initrd": image.initrd,
+ "args": args,
+--
+2.14.4
+
diff --git a/0028-anaconda-don-t-crash-when-no-target-disk-is-availabl.patch b/0028-anaconda-don-t-crash-when-no-target-disk-is-availabl.patch
new file mode 100644
index 0000000..1897fe1
--- /dev/null
+++ b/0028-anaconda-don-t-crash-when-no-target-disk-is-availabl.patch
@@ -0,0 +1,51 @@
+From c247cc5c37932dbe1c1db85d7d948a4b83da112e Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Marek=20Marczykowski-G=C3=B3recki?=
+
+Date: Fri, 19 Oct 2018 08:02:12 +0200
+Subject: [PATCH] anaconda: don't crash when no target disk is available
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+self.storage.root_device may be None in such case. Instead, allow proper reporting that no space is available.
+
+Signed-off-by: Frédéric Pierret
+---
+ pyanaconda/ui/lib/space.py | 14 ++++++++++----
+ 1 file changed, 10 insertions(+), 4 deletions(-)
+
+diff --git a/pyanaconda/ui/lib/space.py b/pyanaconda/ui/lib/space.py
+index 3679a33eb..9b15c474f 100644
+--- a/pyanaconda/ui/lib/space.py
++++ b/pyanaconda/ui/lib/space.py
+@@ -75,8 +75,11 @@ class FileSystemSpaceChecker(object):
+ log.info("fs space: %s needed: %s", free, needed)
+ self.success = (free > needed)
+ if not self.success:
+- dev_required_size = self.payload.requiredDeviceSize(self.storage.root_device.format)
+- self.deficit = dev_required_size - self.storage.root_device.size
++ if self.storage.root_device:
++ dev_required_size = self.payload.requiredDeviceSize(self.storage.root_device.format)
++ self.deficit = dev_required_size - self.storage.root_device.size
++ else:
++ self.deficit = needed - free
+ self.error_message = _(self.error_template) % self.deficit
+
+ return self.success
+@@ -107,8 +110,11 @@ class DirInstallSpaceChecker(FileSystemSpaceChecker):
+ log.info("fs space: %s needed: %s", free, needed)
+ self.success = (free > needed)
+ if not self.success:
+- dev_required_size = self.payload.requiredDeviceSize(self.storage.root_device.format)
+- self.deficit = dev_required_size - self.storage.root_device.size
++ if self.storage.root_device:
++ dev_required_size = self.payload.requiredDeviceSize(self.storage.root_device.format)
++ self.deficit = dev_required_size - self.storage.root_device.size
++ else:
++ self.deficit = needed - free
+ self.error_message = _(self.error_template) % self.deficit
+
+ return self.success
+--
+2.14.4
+
diff --git a/0029-anaconda-consider-Interrupt-Remapping-as-required-fe.patch b/0029-anaconda-consider-Interrupt-Remapping-as-required-fe.patch
new file mode 100644
index 0000000..bcf3317
--- /dev/null
+++ b/0029-anaconda-consider-Interrupt-Remapping-as-required-fe.patch
@@ -0,0 +1,32 @@
+From faf9c486b086a3af05047fa63a07ad2fd9de4477 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Marek=20Marczykowski-G=C3=B3recki?=
+
+Date: Fri, 19 Oct 2018 08:02:12 +0200
+Subject: [PATCH] anaconda: consider Interrupt Remapping as required feature
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+QubesOS/qubes-issues#2977
+
+Signed-off-by: Frédéric Pierret
+---
+ pyanaconda/iutil.py | 2 ++
+ 1 file changed, 2 insertions(+)
+
+diff --git a/pyanaconda/iutil.py b/pyanaconda/iutil.py
+index 20ed54302..e3fb28862 100644
+--- a/pyanaconda/iutil.py
++++ b/pyanaconda/iutil.py
+@@ -1113,6 +1113,8 @@ def is_unsupported_hw():
+ missing_features.append('IOMMU/VT-d/AMD-Vi')
+ if b'HVM: Hardware Assisted Paging (HAP) detected' not in xl_dmesg:
+ missing_features.append('HAP/SLAT/EPT/RVI')
++ if b'Intel VT-d Interrupt Remapping enabled' not in xl_dmesg:
++ missing_features.append('Interrupt Remapping')
+ status = ', '.join(missing_features)
+
+ if status:
+--
+2.14.4
+
diff --git a/0030-anaconda-lock-root-account-by-default.patch b/0030-anaconda-lock-root-account-by-default.patch
new file mode 100644
index 0000000..befdf88
--- /dev/null
+++ b/0030-anaconda-lock-root-account-by-default.patch
@@ -0,0 +1,32 @@
+From 0ac6e123eb37cec6e5f5849e861e2e6d76f3be05 Mon Sep 17 00:00:00 2001
+From: Marek Marczykowski
+Date: Fri, 19 Oct 2018 08:02:12 +0200
+Subject: [PATCH] anaconda: lock root account by default
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Signed-off-by: Frédéric Pierret
+---
+ pyanaconda/kickstart.py | 5 +++++
+ 1 file changed, 5 insertions(+)
+
+diff --git a/pyanaconda/kickstart.py b/pyanaconda/kickstart.py
+index 5ac54b4fd..c0db1b614 100644
+--- a/pyanaconda/kickstart.py
++++ b/pyanaconda/kickstart.py
+@@ -1665,6 +1665,11 @@ class ReqPart(commands.reqpart.F23_ReqPart):
+ do_reqpart(storage, reqs)
+
+ class RootPw(commands.rootpw.F18_RootPw):
++ def __init__(self, writePriority=100, *args, **kwargs):
++ if 'lock' not in kwargs:
++ kwargs['lock'] = True
++ super(RootPw, self).__init__(writePriority=writePriority, *args, **kwargs)
++
+ def execute(self, storage, ksdata, instClass, users):
+ if not self.password and not flags.automatedInstall:
+ self.lock = True
+--
+2.14.4
+
diff --git a/0031-anaconda-add-option-to-lock-root-account.patch b/0031-anaconda-add-option-to-lock-root-account.patch
new file mode 100644
index 0000000..3539e72
--- /dev/null
+++ b/0031-anaconda-add-option-to-lock-root-account.patch
@@ -0,0 +1,161 @@
+From bb3d9ee61f69488c18035d16c804af5ff079b7b7 Mon Sep 17 00:00:00 2001
+From: Marek Marczykowski
+Date: Fri, 19 Oct 2018 08:02:12 +0200
+Subject: [PATCH] anaconda: add option to lock root account
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Not only default setting, so one could leave account locked if entered
+password setting spoke.
+
+Signed-off-by: Frédéric Pierret
+---
+ pyanaconda/ui/gui/spokes/password.glade | 18 +++++++++++-
+ pyanaconda/ui/gui/spokes/password.py | 51 +++++++++++++++++++++++----------
+ 2 files changed, 53 insertions(+), 16 deletions(-)
+
+diff --git a/pyanaconda/ui/gui/spokes/password.glade b/pyanaconda/ui/gui/spokes/password.glade
+index 1a4e77720..1f1fe5837 100644
+--- a/pyanaconda/ui/gui/spokes/password.glade
++++ b/pyanaconda/ui/gui/spokes/password.glade
+@@ -40,6 +40,22 @@
+
+ False
+ vertical
++
++
++ Lock root account
++ True
++ True
++ False
++ 0
++ True
++
++
++
++ False
++ True
++ 0
++
++
+
+
+ True
+@@ -174,7 +190,7 @@
+
+ False
+ True
+- 0
++ 1
+
+
+
+diff --git a/pyanaconda/ui/gui/spokes/password.py b/pyanaconda/ui/gui/spokes/password.py
+index 92acfa8f3..3e8ada1cc 100644
+--- a/pyanaconda/ui/gui/spokes/password.py
++++ b/pyanaconda/ui/gui/spokes/password.py
+@@ -56,6 +56,7 @@ class PasswordSpoke(FirstbootSpokeMixIn, NormalSpoke, GUISpokeInputCheckHandler)
+ def __init__(self, *args):
+ NormalSpoke.__init__(self, *args)
+ GUISpokeInputCheckHandler.__init__(self)
++ self._lock = self.data.rootpw.lock
+ self._kickstarted = False
+
+ def initialize(self):
+@@ -63,6 +64,7 @@ class PasswordSpoke(FirstbootSpokeMixIn, NormalSpoke, GUISpokeInputCheckHandler)
+ # place holders for the text boxes
+ self.pw = self.builder.get_object("pw")
+ self.confirm = self.builder.get_object("confirmPW")
++ self.lock = self.builder.get_object("lock")
+
+ # Install the password checks:
+ # - Has a password been specified?
+@@ -119,17 +121,31 @@ class PasswordSpoke(FirstbootSpokeMixIn, NormalSpoke, GUISpokeInputCheckHandler)
+ # Enable the input checks in case they were disabled on the last exit
+ for check in self.checks:
+ check.enabled = True
+-
+- self.pw.grab_focus()
++ self.lock.set_active(self._lock)
++ self.on_lock_clicked(self.lock)
+ self.pw.emit("changed")
+ self.confirm.emit("changed")
+
++ def on_lock_clicked(self, lock):
++ self.pw.set_sensitive(not lock.get_active())
++ self.confirm.set_sensitive(not lock.get_active())
++ if not lock.get_active():
++ self.pw.grab_focus()
++
++# Caps lock detection isn't hooked up right now
++# def setCapsLockLabel(self):
++# if isCapsLockEnabled():
++# self.capslock.set_text("" + _("Caps Lock is on.") + "")
++# self.capslock.set_use_markup(True)
++# else:
++# self.capslock..set_text("")
++
+ @property
+ def status(self):
+- if self.data.rootpw.password:
+- return _("Root password is set")
+- elif self.data.rootpw.lock:
++ if self.data.rootpw.lock:
+ return _("Root account is disabled")
++ elif self.data.rootpw.password:
++ return _("Root password is set")
+ else:
+ return _("Root password is not set")
+
+@@ -145,15 +161,10 @@ class PasswordSpoke(FirstbootSpokeMixIn, NormalSpoke, GUISpokeInputCheckHandler)
+ self.data.rootpw.seen = False
+ self._kickstarted = False
+
+- self.data.rootpw.lock = False
+-
+- if not pw:
+- self.data.rootpw.password = ''
+- self.data.rootpw.isCrypted = False
+- return
+-
+- self.data.rootpw.password = cryptPassword(pw)
+- self.data.rootpw.isCrypted = True
++ if pw:
++ self.data.rootpw.password = cryptPassword(pw)
++ self.data.rootpw.isCrypted = True
++ self.data.rootpw.lock = self._lock
+
+ self.pw.set_placeholder_text("")
+ self.confirm.set_placeholder_text("")
+@@ -173,6 +184,8 @@ class PasswordSpoke(FirstbootSpokeMixIn, NormalSpoke, GUISpokeInputCheckHandler)
+ # If the password was set by kickstart, skip this check
+ if self._kickstarted and not self.policy.changesok:
+ return InputCheck.CHECK_OK
++ if self.lock.get_active():
++ return InputCheck.CHECK_OK
+
+ if not self.get_input(inputcheck.input_obj):
+ if inputcheck.input_obj == self.pw:
+@@ -187,9 +200,17 @@ class PasswordSpoke(FirstbootSpokeMixIn, NormalSpoke, GUISpokeInputCheckHandler)
+
+ pw = self.pw.get_text()
+ confirm = self.confirm.get_text()
++ lock = self.lock.get_active()
++
++ if lock:
++ self._lock = True
++ self._password = None
++ self.clear_info()
++ self._error = False
++ result = InputCheck.CHECK_OK
+
+ # Skip the check if no password is required
+- if (not pw and not confirm) and self._kickstarted:
++ elif (not pw and not confirm) and self._kickstarted:
+ result = InputCheck.CHECK_OK
+ elif confirm and (pw != confirm):
+ result = _(PASSWORD_CONFIRM_ERROR_GUI)
+--
+2.14.4
+
diff --git a/0032-anaconda-check-add-user-to-wheel-and-qubes-groups.patch b/0032-anaconda-check-add-user-to-wheel-and-qubes-groups.patch
new file mode 100644
index 0000000..cf2c49b
--- /dev/null
+++ b/0032-anaconda-check-add-user-to-wheel-and-qubes-groups.patch
@@ -0,0 +1,35 @@
+From 9a037670677088cf8c7b67b8b1d03e09b39932c7 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Pierret=20=28fepitre=29?=
+
+Date: Fri, 19 Oct 2018 08:02:12 +0200
+Subject: [PATCH] anaconda: check/add user to 'wheel' and 'qubes' groups
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Originally from 378cfc44dd218c61b838f4f9011dcfd790df59eb by Marek Marczykowski-Górecki
+
+Signed-off-by: Frédéric Pierret
+---
+ pyanaconda/ui/gui/spokes/user.py | 5 +++++
+ 1 file changed, 5 insertions(+)
+
+diff --git a/pyanaconda/ui/gui/spokes/user.py b/pyanaconda/ui/gui/spokes/user.py
+index bb2ec15e1..e954292c7 100644
+--- a/pyanaconda/ui/gui/spokes/user.py
++++ b/pyanaconda/ui/gui/spokes/user.py
+@@ -380,6 +380,11 @@ class UserSpoke(FirstbootSpokeMixIn, NormalSpoke, GUISpokeInputCheckHandler):
+ self._user.name = self.username.get_text()
+ self._user.gecos = self.fullname.get_text()
+
++ if "wheel" not in self._user.groups:
++ self._user.groups.append("wheel")
++ if "qubes" not in self._user.groups:
++ self._user.groups.append("qubes")
++
+ # Copy the spoke data back to kickstart
+ # If the user name is not set, no user will be created.
+ if self._user.name:
+--
+2.14.4
+
diff --git a/0033-anaconda-Modify-user-configuration-spoke-for-QubesOS.patch b/0033-anaconda-Modify-user-configuration-spoke-for-QubesOS.patch
new file mode 100644
index 0000000..b0546a5
--- /dev/null
+++ b/0033-anaconda-Modify-user-configuration-spoke-for-QubesOS.patch
@@ -0,0 +1,389 @@
+From cd70689b0642d41dcbdb826df4fc9f79b1ef899e Mon Sep 17 00:00:00 2001
+From: "M. Vefa Bicakci"
+Date: Fri, 19 Oct 2018 08:02:12 +0200
+Subject: [PATCH] anaconda: Modify user configuration spoke for QubesOS
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Signed-off-by: Frédéric Pierret
+---
+ pyanaconda/ui/gui/spokes/user.glade | 96 +++++++------------------------------
+ pyanaconda/ui/gui/spokes/user.py | 57 +++-------------------
+ pyanaconda/ui/tui/spokes/user.py | 21 ++------
+ 3 files changed, 28 insertions(+), 146 deletions(-)
+
+diff --git a/pyanaconda/ui/gui/spokes/user.glade b/pyanaconda/ui/gui/spokes/user.glade
+index 2873ff2d9..e6700657d 100644
+--- a/pyanaconda/ui/gui/spokes/user.glade
++++ b/pyanaconda/ui/gui/spokes/user.glade
+@@ -1,5 +1,5 @@
+
+-
++
+
+
+
+@@ -53,33 +53,15 @@
+ False
+ 8
+ 9
+-
+-
+- True
+- False
+- 1
+- 10
+- _Full name
+- True
+- t_fullname
+-
+-
+-
+-
+-
+- 0
+- 0
+-
+-
+
+
+ True
+ False
+- 1
+ 10
+ _User name
+ True
+ t_username
++ 1
+
+
+
+@@ -89,29 +71,12 @@
+ 1
+
+
+-
+-
+- True
+- True
+- False
+-
+-
+-
+- Full Name
+-
+-
+-
+-
+- 1
+- 0
+-
+-
+
+
+ True
+ True
+-
+
++
+
+
+ User Name
+@@ -127,11 +92,11 @@
+
+ True
+ False
+- 1
+ 10
+ _Password
+ True
+ t_password
++ 1
+
+
+
+@@ -145,11 +110,11 @@
+
+ True
+ False
+- 1
+ 10
+ _Confirm password
+ True
+ t_verifypassword
++ 1
+
+
+
+@@ -166,7 +131,7 @@
+ False
+ ●
+
+-
++
+
+
+ Password
+@@ -184,7 +149,7 @@
+ True
+ False
+ ●
+-
++
+
+
+ Confirm Password
+@@ -200,9 +165,9 @@
+
+ True
+ False
+- 0
+ <b>Tip:</b> Keep your user name shorter than 32 characters and do not use spaces.
+ True
++ 0
+
+
+ 1
+@@ -268,45 +233,16 @@
+
+
+
+-
+- _Make this user administrator
+- True
+- True
+- False
+- True
+- 0
+- True
+-
+-
+-
+- 1
+- 3
+-
++
+
+
+-
+- True
+- False
+-
+-
+- _Advanced...
+- True
+- False
+- True
+- True
+- True
+-
+-
+-
+- 0
+- 0
+-
+-
+-
+-
+- 1
+- 8
+-
++
++
++
++
++
++
++
+
+
+
+diff --git a/pyanaconda/ui/gui/spokes/user.py b/pyanaconda/ui/gui/spokes/user.py
+index e954292c7..dac6ba3e5 100644
+--- a/pyanaconda/ui/gui/spokes/user.py
++++ b/pyanaconda/ui/gui/spokes/user.py
+@@ -38,7 +38,7 @@ from pyanaconda.constants import ANACONDA_ENVIRON, FIRSTBOOT_ENVIRON,\
+ PW_ASCII_CHARS, PASSWORD_ASCII
+ from pyanaconda.regexes import GECOS_VALID, GROUPNAME_VALID, GROUPLIST_FANCY_PARSE
+
+-__all__ = ["UserSpoke", "AdvancedUserDialog"]
++__all__ = ["UserSpoke"]
+
+ class AdvancedUserDialog(GUIObject, GUIDialogInputCheckHandler):
+ """
+@@ -214,7 +214,7 @@ class UserSpoke(FirstbootSpokeMixIn, NormalSpoke, GUISpokeInputCheckHandler):
+ builderObjects = ["userCreationWindow"]
+
+ mainWidgetName = "userCreationWindow"
+- focusWidgetName = "t_fullname"
++ focusWidgetName = "t_username"
+ uiFile = "spokes/user.glade"
+ helpFile = "UserSpoke.xml"
+
+@@ -252,14 +252,16 @@ class UserSpoke(FirstbootSpokeMixIn, NormalSpoke, GUISpokeInputCheckHandler):
+ else:
+ self._user = self.data.UserData()
+
++ self._wheel = self.data.GroupData(name="wheel")
++ self._qubes = self.data.GroupData(name="qubes")
++
++ self._groupDict = {"wheel": self._wheel, "qubes": self._qubes}
++
+ # placeholders for the text boxes
+- self.fullname = self.builder.get_object("t_fullname")
+ self.username = self.builder.get_object("t_username")
+ self.pw = self.builder.get_object("t_password")
+ self.confirm = self.builder.get_object("t_verifypassword")
+- self.admin = self.builder.get_object("c_admin")
+ self.usepassword = self.builder.get_object("c_usepassword")
+- self.b_advanced = self.builder.get_object("b_advanced")
+
+ # Counters for checks that ask the user to click Done to confirm
+ self._waiveStrengthClicks = 0
+@@ -306,8 +308,6 @@ class UserSpoke(FirstbootSpokeMixIn, NormalSpoke, GUISpokeInputCheckHandler):
+
+ self.add_check(self.username, self._checkUsername)
+
+- self.add_re_check(self.fullname, GECOS_VALID, _("Full name cannot contain colon characters"))
+-
+ # Modify the GUI based on the kickstart and policy information
+ # This needs to happen after the input checks have been created, since
+ # the Gtk signal handlers use the input check variables.
+@@ -323,9 +323,6 @@ class UserSpoke(FirstbootSpokeMixIn, NormalSpoke, GUISpokeInputCheckHandler):
+ # User isn't allowed to change whether password is required or not
+ self.usepassword.set_sensitive(False)
+
+- self._advanced = AdvancedUserDialog(self._user, self.data)
+- self._advanced.initialize()
+-
+ # set the visibility of the password entries
+ set_password_visibility(self.pw, False)
+ set_password_visibility(self.confirm, False)
+@@ -336,8 +333,6 @@ class UserSpoke(FirstbootSpokeMixIn, NormalSpoke, GUISpokeInputCheckHandler):
+ check.enabled = True
+
+ self.username.set_text(self._user.name)
+- self.fullname.set_text(self._user.gecos)
+- self.admin.set_active("wheel" in self._user.groups)
+
+ self.pw.emit("changed")
+ self.confirm.emit("changed")
+@@ -378,7 +373,6 @@ class UserSpoke(FirstbootSpokeMixIn, NormalSpoke, GUISpokeInputCheckHandler):
+ self._password_kickstarted = False
+
+ self._user.name = self.username.get_text()
+- self._user.gecos = self.fullname.get_text()
+
+ if "wheel" not in self._user.groups:
+ self._user.groups.append("wheel")
+@@ -469,34 +463,10 @@ class UserSpoke(FirstbootSpokeMixIn, NormalSpoke, GUISpokeInputCheckHandler):
+ def username_changed(self, editable, data=None):
+ """Called by Gtk on all username changes."""
+
+- # Disable the advanced user dialog button when no username is set
+- if editable.get_text():
+- self.b_advanced.set_sensitive(True)
+- else:
+- self.b_advanced.set_sensitive(False)
+-
+ # Re-run the password checks against the new username
+ self.pw.emit("changed")
+ self.confirm.emit("changed")
+
+- def full_name_changed(self, editable, data=None):
+- """Called by Gtk callback when the full name field changes."""
+-
+- if self.guesser:
+- fullname = editable.get_text()
+- username = guess_username(fullname)
+-
+- with blockedHandler(self.username, self.on_username_set_by_user):
+- self.username.set_text(username)
+-
+- def on_admin_toggled(self, togglebutton, data=None):
+- # Add or remove "wheel" from the grouplist on changes to the admin checkbox
+- if togglebutton.get_active():
+- if "wheel" not in self._user.groups:
+- self._user.groups.append("wheel")
+- elif "wheel" in self._user.groups:
+- self._user.groups.remove("wheel")
+-
+ def _checkPasswordEmpty(self, inputcheck):
+ """Check whether a password has been specified at all.
+
+@@ -612,19 +582,6 @@ class UserSpoke(FirstbootSpokeMixIn, NormalSpoke, GUISpokeInputCheckHandler):
+ else:
+ return msg or _("Invalid user name")
+
+- def on_advanced_clicked(self, _button, data=None):
+- """Handler for the Advanced.. button. It starts the Advanced dialog
+- for setting homedit, uid, gid and groups.
+- """
+-
+- self._user.name = self.username.get_text()
+-
+- self._advanced.refresh()
+- with self.main_window.enlightbox(self._advanced.window):
+- self._advanced.run()
+-
+- self.admin.set_active("wheel" in self._user.groups)
+-
+ def on_back_clicked(self, button):
+ # If the failed check is for non-ASCII characters,
+ # add a click to the counter and check again
+diff --git a/pyanaconda/ui/tui/spokes/user.py b/pyanaconda/ui/tui/spokes/user.py
+index 1005852db..834b82cbf 100644
+--- a/pyanaconda/ui/tui/spokes/user.py
++++ b/pyanaconda/ui/tui/spokes/user.py
+@@ -40,11 +40,9 @@ class UserSpoke(FirstbootSpokeMixIn, EditTUISpoke):
+
+ edit_fields = [
+ Entry("Create user", "_create", EditTUISpoke.CHECK, True),
+- Entry("Fullname", "gecos", GECOS_VALID, lambda self, args: args._create),
+ Entry("Username", "name", check_username, lambda self, args: args._create),
+ Entry("Use password", "_use_password", EditTUISpoke.CHECK, lambda self, args: args._create),
+ Entry("Password", "_password", EditTUISpoke.PASSWORD, lambda self, args: args._use_password and args._create),
+- Entry("Administrator", "_admin", EditTUISpoke.CHECK, lambda self, args: args._create),
+ Entry("Groups", "_groups", GROUPLIST_SIMPLE_VALID, lambda self, args: args._create)
+ ]
+
+@@ -84,7 +82,6 @@ class UserSpoke(FirstbootSpokeMixIn, EditTUISpoke):
+ self.errors = []
+
+ def refresh(self, args=None):
+- self.args._admin = "wheel" in self.args.groups
+ self.args._groups = ", ".join(self.args.groups)
+
+ # if we have any errors, display them
+@@ -146,22 +143,14 @@ class UserSpoke(FirstbootSpokeMixIn, EditTUISpoke):
+ return EditTUISpoke.input(self, args, key)
+
+ def apply(self):
+- if self.args.gecos and not self.args.name:
+- username = guess_username(self.args.gecos)
+- valid, msg = check_username(username)
+- if not valid:
+- self.errors.append(_("Invalid user name: %(name)s.\n%(error_message)s")
+- % {"name": username, "error_message": msg})
+- else:
+- self.args.name = guess_username(self.args.gecos)
+-
+ self.args.groups = [g.strip() for g in self.args._groups.split(",") if g]
+
+- # Add or remove the user from wheel group
+- if self.args._admin and "wheel" not in self.args.groups:
++ # Add the user to the wheel and qubes groups
++ if "wheel" not in self.args.groups:
+ self.args.groups.append("wheel")
+- elif not self.args._admin and "wheel" in self.args.groups:
+- self.args.groups.remove("wheel")
++
++ if "qubes" not in self.args.groups:
++ self.args.groups.append("qubes")
+
+ # Add or remove the user from userlist as needed
+ if self.args._create and (self.args not in self.data.user.userList and self.args.name):
+--
+2.14.4
+
diff --git a/0034-anaconda-Make-sure-that-a-user-is-created-at-install.patch b/0034-anaconda-Make-sure-that-a-user-is-created-at-install.patch
new file mode 100644
index 0000000..c3c8ef3
--- /dev/null
+++ b/0034-anaconda-Make-sure-that-a-user-is-created-at-install.patch
@@ -0,0 +1,94 @@
+From b93178bff8ecb1e362c79e845c2e7ceec08c682e Mon Sep 17 00:00:00 2001
+From: "M. Vefa Bicakci"
+Date: Fri, 19 Oct 2018 08:02:12 +0200
+Subject: [PATCH] anaconda: Make sure that a user is created at installation
+ time
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Signed-off-by: Frédéric Pierret
+---
+ pyanaconda/ui/gui/spokes/user.py | 8 ++------
+ pyanaconda/ui/tui/spokes/user.py | 10 +++-------
+ 2 files changed, 5 insertions(+), 13 deletions(-)
+
+diff --git a/pyanaconda/ui/gui/spokes/user.py b/pyanaconda/ui/gui/spokes/user.py
+index dac6ba3e5..dd281f8e4 100644
+--- a/pyanaconda/ui/gui/spokes/user.py
++++ b/pyanaconda/ui/gui/spokes/user.py
+@@ -26,7 +26,6 @@ from pyanaconda.users import cryptPassword, validatePassword, guess_username, ch
+ from pyanaconda.ui.gui.spokes import NormalSpoke
+ from pyanaconda.ui.gui import GUIObject
+ from pyanaconda.ui.categories.user_settings import UserSettingsCategory
+-from pyanaconda.ui.common import FirstbootSpokeMixIn
+ from pyanaconda.ui.helpers import InputCheck
+ from pyanaconda.ui.gui.helpers import GUISpokeInputCheckHandler, GUIDialogInputCheckHandler
+ from pyanaconda.ui.gui.utils import blockedHandler, set_password_visibility
+@@ -206,7 +205,7 @@ class AdvancedUserDialog(GUIObject, GUIDialogInputCheckHandler):
+
+ return False
+
+-class UserSpoke(FirstbootSpokeMixIn, NormalSpoke, GUISpokeInputCheckHandler):
++class UserSpoke(NormalSpoke, GUISpokeInputCheckHandler):
+ """
+ .. inheritance-diagram:: UserSpoke
+ :parts: 3
+@@ -348,10 +347,7 @@ class UserSpoke(FirstbootSpokeMixIn, NormalSpoke, GUISpokeInputCheckHandler):
+
+ @property
+ def mandatory(self):
+- """ Only mandatory if the root pw hasn't been set in the UI
+- eg. not mandatory if the root account was locked in a kickstart
+- """
+- return not self.data.rootpw.password and not self.data.rootpw.lock
++ return True
+
+ def apply(self):
+ # set the password only if the user enters anything to the text entry
+diff --git a/pyanaconda/ui/tui/spokes/user.py b/pyanaconda/ui/tui/spokes/user.py
+index 834b82cbf..baa3ac203 100644
+--- a/pyanaconda/ui/tui/spokes/user.py
++++ b/pyanaconda/ui/tui/spokes/user.py
+@@ -20,7 +20,6 @@
+ from pyanaconda.ui.categories.user_settings import UserSettingsCategory
+ from pyanaconda.ui.tui.spokes import EditTUISpoke
+ from pyanaconda.ui.tui.spokes import EditTUISpokeEntry as Entry
+-from pyanaconda.ui.common import FirstbootSpokeMixIn
+ from pyanaconda.users import guess_username, check_username
+ from pyanaconda.flags import flags
+ from pyanaconda.i18n import N_, _
+@@ -30,7 +29,7 @@ from pyanaconda.regexes import GECOS_VALID, GROUPLIST_SIMPLE_VALID
+
+ __all__ = ["UserSpoke"]
+
+-class UserSpoke(FirstbootSpokeMixIn, EditTUISpoke):
++class UserSpoke(EditTUISpoke):
+ """
+ .. inheritance-diagram:: UserSpoke
+ :parts: 3
+@@ -64,8 +63,8 @@ class UserSpoke(FirstbootSpokeMixIn, EditTUISpoke):
+ return False
+
+ def __init__(self, app, data, storage, payload, instclass):
+- FirstbootSpokeMixIn.__init__(self)
+ EditTUISpoke.__init__(self, app, data, storage, payload, instclass, "user")
++
+ if self.data.user.userList:
+ self.args = self.data.user.userList[0]
+ self.args._create = True
+@@ -108,10 +107,7 @@ class UserSpoke(FirstbootSpokeMixIn, EditTUISpoke):
+
+ @property
+ def mandatory(self):
+- """ Only mandatory if the root pw hasn't been set in the UI
+- eg. not mandatory if the root account was locked in a kickstart
+- """
+- return not self.data.rootpw.password and not self.data.rootpw.lock
++ return True
+
+ @property
+ def status(self):
+--
+2.14.4
+
diff --git a/0035-anaconda-xen.efi-upgraded-during-each-install.patch b/0035-anaconda-xen.efi-upgraded-during-each-install.patch
new file mode 100644
index 0000000..6d2343f
--- /dev/null
+++ b/0035-anaconda-xen.efi-upgraded-during-each-install.patch
@@ -0,0 +1,42 @@
+From 5c3b0d8d2319f323d5437bf51356b2988262e84f Mon Sep 17 00:00:00 2001
+From: Freddie Rice
+Date: Fri, 19 Oct 2018 08:02:12 +0200
+Subject: [PATCH] anaconda: xen.efi upgraded during each install
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Signed-off-by: Frédéric Pierret
+---
+ pyanaconda/bootloader.py | 15 +++++++++------
+ 1 file changed, 9 insertions(+), 6 deletions(-)
+
+diff --git a/pyanaconda/bootloader.py b/pyanaconda/bootloader.py
+index b1e9ff421..27c7ed34a 100644
+--- a/pyanaconda/bootloader.py
++++ b/pyanaconda/bootloader.py
+@@ -1851,12 +1851,15 @@ class XenEFI(EFIGRUB):
+ boot_part_num = self.stage1_device.parents[0].parted_partition.number
+ boot_part_num = str(boot_part_num)
+
+- if not os.path.exists(
+- "{}/{}".format(iutil.getSysroot() + self.config_dir, "xen.efi")):
+- xen_efi = [x for x in os.listdir(iutil.getSysroot() + self.config_dir) if
+- x.startswith('xen-') and x.endswith('.efi')][0]
+- shutil.copy("{}/{}".format(iutil.getSysroot() + self.config_dir, xen_efi),
+- "{}/{}".format(iutil.getSysroot() + self.config_dir, "xen.efi"))
++ # could be an old version, replace in case
++ xen_efi_target = "{}/{}".format(iutil.getSysroot() + self.config_dir, "xen.efi")
++ if os.path.exists(xen_efi_target):
++ os.remove(xen_efi_target)
++
++ xen_efi = [x for x in os.listdir(iutil.getSysroot() + self.config_dir) if
++ x.startswith('xen-') and x.endswith('.efi')][0]
++ shutil.copy("{}/{}".format(iutil.getSysroot() + self.config_dir, xen_efi),
++ "{}/{}".format(iutil.getSysroot() + self.config_dir, "xen.efi"))
+ rc = self.efibootmgr("-c", "-w", "-L", productName,
+ "-d", boot_disk.path, "-p", boot_part_num,
+ "-l",
+--
+2.14.4
+
diff --git a/0036-anaconda-make-sure-the-latest-version-is-placed-as-x.patch b/0036-anaconda-make-sure-the-latest-version-is-placed-as-x.patch
new file mode 100644
index 0000000..34ebc2c
--- /dev/null
+++ b/0036-anaconda-make-sure-the-latest-version-is-placed-as-x.patch
@@ -0,0 +1,39 @@
+From 53204d90309574fddbb0576d7a153657996eef33 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Marek=20Marczykowski-G=C3=B3recki?=
+
+Date: Fri, 19 Oct 2018 08:02:12 +0200
+Subject: [PATCH] anaconda: make sure the latest version is placed as xen.efi
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+os.listdir returns files in filesystem order, not sorted.
+
+QubesOS/qubes-issues#2990
+
+Signed-off-by: Frédéric Pierret
+---
+ pyanaconda/bootloader.py | 6 +++---
+ 1 file changed, 3 insertions(+), 3 deletions(-)
+
+diff --git a/pyanaconda/bootloader.py b/pyanaconda/bootloader.py
+index 27c7ed34a..ad8e8c2e7 100644
+--- a/pyanaconda/bootloader.py
++++ b/pyanaconda/bootloader.py
+@@ -1856,10 +1856,10 @@ class XenEFI(EFIGRUB):
+ if os.path.exists(xen_efi_target):
+ os.remove(xen_efi_target)
+
+- xen_efi = [x for x in os.listdir(iutil.getSysroot() + self.config_dir) if
+- x.startswith('xen-') and x.endswith('.efi')][0]
++ xen_efi = [x for x in sorted(os.listdir(iutil.getSysroot() + self.config_dir)) if
++ x.startswith('xen-') and x.endswith('.efi')][-1]
+ shutil.copy("{}/{}".format(iutil.getSysroot() + self.config_dir, xen_efi),
+- "{}/{}".format(iutil.getSysroot() + self.config_dir, "xen.efi"))
++ xen_efi_target)
+ rc = self.efibootmgr("-c", "-w", "-L", productName,
+ "-d", boot_disk.path, "-p", boot_part_num,
+ "-l",
+--
+2.14.4
+
diff --git a/0037-anaconda-update-message-about-unusupported-hardware.patch b/0037-anaconda-update-message-about-unusupported-hardware.patch
new file mode 100644
index 0000000..74f970e
--- /dev/null
+++ b/0037-anaconda-update-message-about-unusupported-hardware.patch
@@ -0,0 +1,34 @@
+From 0a627aa765eb6fa7e9adbc0ec76ebb1e745c3941 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Marek=20Marczykowski-G=C3=B3recki?=
+
+Date: Fri, 19 Oct 2018 08:02:12 +0200
+Subject: [PATCH] anaconda: update message about unusupported hardware
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Advise to not continue.
+
+Fixes QubesOS/qubes-issues#3208
+
+Signed-off-by: Frédéric Pierret
+---
+ pyanaconda/ui/gui/spokes/welcome.glade | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/pyanaconda/ui/gui/spokes/welcome.glade b/pyanaconda/ui/gui/spokes/welcome.glade
+index 87e5bf0e9..8daed92af 100644
+--- a/pyanaconda/ui/gui/spokes/welcome.glade
++++ b/pyanaconda/ui/gui/spokes/welcome.glade
+@@ -507,7 +507,7 @@
+ True
+ False
+ start
+- This hardware lack features required by Qubes OS. Missing features: %(features)s. For more information on supported hardware, please refer to https://www.qubes-os.org/system-requirements/
++ This hardware lacks features required by Qubes OS. Missing features: %(features)s. Without these features, Qubes OS will not function normally. It is recommended that only developers and power users proceed with the installation. For more information on supported hardware, please refer to https://www.qubes-os.org/system-requirements/
+ True
+
+
+--
+2.14.4
+
diff --git a/0038-anaconda-check-also-for-message-about-AMD-interrupt-.patch b/0038-anaconda-check-also-for-message-about-AMD-interrupt-.patch
new file mode 100644
index 0000000..40928fb
--- /dev/null
+++ b/0038-anaconda-check-also-for-message-about-AMD-interrupt-.patch
@@ -0,0 +1,35 @@
+From 9d9ba78cef13cbfb00af08fddfa122488763a8e9 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Marek=20Marczykowski-G=C3=B3recki?=
+
+Date: Fri, 19 Oct 2018 08:02:12 +0200
+Subject: [PATCH] anaconda: check also for message about AMD interrupt
+ remapping
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Fixes QubesOS/qubes-issues#3208
+
+Signed-off-by: Frédéric Pierret
+---
+ pyanaconda/iutil.py | 4 +++-
+ 1 file changed, 3 insertions(+), 1 deletion(-)
+
+diff --git a/pyanaconda/iutil.py b/pyanaconda/iutil.py
+index e3fb28862..166626a67 100644
+--- a/pyanaconda/iutil.py
++++ b/pyanaconda/iutil.py
+@@ -1113,7 +1113,9 @@ def is_unsupported_hw():
+ missing_features.append('IOMMU/VT-d/AMD-Vi')
+ if b'HVM: Hardware Assisted Paging (HAP) detected' not in xl_dmesg:
+ missing_features.append('HAP/SLAT/EPT/RVI')
+- if b'Intel VT-d Interrupt Remapping enabled' not in xl_dmesg:
++ # slightly different wording for Intel and AMD
++ if b'Intel VT-d Interrupt Remapping enabled' not in xl_dmesg \
++ and 'Interrupt remapping enabled' not in xl_dmesg:
+ missing_features.append('Interrupt Remapping')
+ status = ', '.join(missing_features)
+
+--
+2.14.4
+
diff --git a/0039-anaconda-Remove-in-memory-kickstart-representation-f.patch b/0039-anaconda-Remove-in-memory-kickstart-representation-f.patch
new file mode 100644
index 0000000..83cd379
--- /dev/null
+++ b/0039-anaconda-Remove-in-memory-kickstart-representation-f.patch
@@ -0,0 +1,45 @@
+From 477f57c5661c58537e675e1d8cfa20c7430f826f Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Marek=20Marczykowski-G=C3=B3recki?=
+
+Date: Fri, 19 Oct 2018 08:02:12 +0200
+Subject: [PATCH] anaconda: Remove in-memory kickstart representation from
+ traceback file (#1519895)
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+We have been doing this filtering already, but some paths have likely
+changed and the filter was no longer effective.
+
+So add two new filter strings:
+"_intf.storage.ksdata"
+"_intf.data"
+
+After adding these two I was no longer able to find the plaintext password
+anywhere in the traceback after manually triggering a crash with:
+
+kill -USR1 `cat /var/run/anaconda.pid`
+
+Resolves: rhbz#1519895
+Signed-off-by: Frédéric Pierret
+---
+ pyanaconda/exception.py | 2 ++
+ 1 file changed, 2 insertions(+)
+
+diff --git a/pyanaconda/exception.py b/pyanaconda/exception.py
+index c283a605a..483cd51c4 100644
+--- a/pyanaconda/exception.py
++++ b/pyanaconda/exception.py
+@@ -274,7 +274,9 @@ def initExceptionHandling(anaconda):
+ "_intf._currentAction._spokes[\"UserSpoke\"]._oldweak",
+ "_intf.storage.bootloader.password",
+ "_intf.storage.data",
++ "_intf.storage.ksdata",
+ "_intf.storage.encryption_passphrase",
++ "_intf.data",
+ "_bootloader.encrypted_password",
+ "_bootloader.password",
+ "payload._groups"],
+--
+2.14.4
+
diff --git a/0040-anaconda-fix-default-scheme-in-custom-partitioning.patch b/0040-anaconda-fix-default-scheme-in-custom-partitioning.patch
new file mode 100644
index 0000000..64a502d
--- /dev/null
+++ b/0040-anaconda-fix-default-scheme-in-custom-partitioning.patch
@@ -0,0 +1,43 @@
+From 4c4c8e2f14c53e1a79edd9545e73945aba66c876 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Marek=20Marczykowski-G=C3=B3recki?=
+
+Date: Fri, 19 Oct 2018 08:02:12 +0200
+Subject: [PATCH] anaconda: fix default scheme in custom partitioning
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Update to LVM Thin Provisioning there too.
+
+Fixes QubesOS/qubes-issues#3225
+
+Signed-off-by: Frédéric Pierret
+---
+ pyanaconda/constants.py | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/pyanaconda/constants.py b/pyanaconda/constants.py
+index 3531fdca6..0e6270451 100644
+--- a/pyanaconda/constants.py
++++ b/pyanaconda/constants.py
+@@ -27,7 +27,7 @@ SELINUX_DEFAULT = -1
+ # where to look for 3rd party addons
+ ADDON_PATHS = ["/usr/share/anaconda/addons"]
+
+-from pykickstart.constants import AUTOPART_TYPE_LVM
++from pykickstart.constants import AUTOPART_TYPE_LVM_THINP
+
+ # common string needs to be easy to change
+ from pyanaconda import product
+@@ -169,7 +169,7 @@ SCREENSHOTS_TARGET_DIRECTORY = "/root/anaconda-screenshots"
+ # cmdline arguments that append instead of overwrite
+ CMDLINE_APPEND = ["modprobe.blacklist", "ifname"]
+
+-DEFAULT_AUTOPART_TYPE = AUTOPART_TYPE_LVM
++DEFAULT_AUTOPART_TYPE = AUTOPART_TYPE_LVM_THINP
+
+ # Default to these units when reading user input when no units given
+ SIZE_UNITS_DEFAULT = "MiB"
+--
+2.14.4
+
diff --git a/0041-anaconda-fix-interrupt-remapping-detection.patch b/0041-anaconda-fix-interrupt-remapping-detection.patch
new file mode 100644
index 0000000..c078c10
--- /dev/null
+++ b/0041-anaconda-fix-interrupt-remapping-detection.patch
@@ -0,0 +1,30 @@
+From 2d405103c8e9f04049dffed0af1bd03e47a8d79e Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Marek=20Marczykowski-G=C3=B3recki?=
+
+Date: Fri, 19 Oct 2018 08:02:13 +0200
+Subject: [PATCH] anaconda: fix interrupt remapping detection
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Signed-off-by: Frédéric Pierret
+---
+ pyanaconda/iutil.py | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/pyanaconda/iutil.py b/pyanaconda/iutil.py
+index 166626a67..c83bcddee 100644
+--- a/pyanaconda/iutil.py
++++ b/pyanaconda/iutil.py
+@@ -1115,7 +1115,7 @@ def is_unsupported_hw():
+ missing_features.append('HAP/SLAT/EPT/RVI')
+ # slightly different wording for Intel and AMD
+ if b'Intel VT-d Interrupt Remapping enabled' not in xl_dmesg \
+- and 'Interrupt remapping enabled' not in xl_dmesg:
++ and b'Interrupt remapping enabled' not in xl_dmesg:
+ missing_features.append('Interrupt Remapping')
+ status = ', '.join(missing_features)
+
+--
+2.14.4
+
diff --git a/0042-anaconda-Fix-macOS-EFI-Installation.patch b/0042-anaconda-Fix-macOS-EFI-Installation.patch
new file mode 100644
index 0000000..57d7b9c
--- /dev/null
+++ b/0042-anaconda-Fix-macOS-EFI-Installation.patch
@@ -0,0 +1,276 @@
+From a1980ee725fc73e367bde3706c327684ac5e35ee Mon Sep 17 00:00:00 2001
+From: Eric Duncan
+Date: Fri, 19 Oct 2018 08:02:13 +0200
+Subject: [PATCH] anaconda: Fix macOS EFI Installation
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Typical GRUB2 installations would execute the script
+located at /usr/libexec/mactel-boot-setup which would
+modify the HFS+ ESP files and bless the specified efi.
+However, we are not using GRUB at this time which would
+cause that script to exit earlier.
+
+These changes will execute the relevant commands
+to symlink the efi file in the /System directory as well
+the cfg file. Lastly, macOS requires the bootable efi
+file to be blessed.
+
+We also attempt to place some user-friendly icons
+for Qubes to show to the user.
+
+Lastly, we add a README with some instructions on how
+to get into rescue mode from macOS.
+
+Signed-off-by: Frédéric Pierret
+---
+ pyanaconda/bootloader.py | 220 +++++++++++++++++++++++++++++++++++++++++++++--
+ 1 file changed, 215 insertions(+), 5 deletions(-)
+
+diff --git a/pyanaconda/bootloader.py b/pyanaconda/bootloader.py
+index ad8e8c2e7..821eb2100 100644
+--- a/pyanaconda/bootloader.py
++++ b/pyanaconda/bootloader.py
+@@ -1908,16 +1908,208 @@ class XenEFI(EFIGRUB):
+ write_config = BootLoader.write_config
+
+
+-class MacEFIGRUB(EFIGRUB):
++class MacEFIGRUB(XenEFI):
++ """Special EFI handling for macOS HFS+ ESP partition.
++
++ Typical GRUB2 installations would execute the script
++ located at /usr/libexec/mactel-boot-setup which would
++ modify the HFS+ ESP files and bless the specified efi.
++
++ However, we are not using GRUB at this time which would
++ cause that script to exit earlier.
++
++ In this class, we will execute the relevant commands
++ to symlink the efi file in the /System directory as well
++ the cfg file. Lastly, macOS requires the bootable efi
++ file to be blessed.
++ """
++
++ def __init__(self):
++ super(MacEFIGRUB, self).__init__()
++ self._mountpoint = "/boot/efi" # fixme: extract from writeBootLoader()
++ self._system_label = "Qubes OS"
++ self._mactel_sys_dir = "{}/{}".format(self._mountpoint, "/System/Library/Coreservices")
++ self._mactel_artwork_dir = "{}/{}".format("/usr/share/pixmaps/bootloader/apple", self.efi_dir)
++
+ def mactel_config(self):
+- if os.path.exists(iutil.getSysroot() + "/usr/libexec/mactel-boot-setup"):
+- rc = iutil.execInSysroot("/usr/libexec/mactel-boot-setup", [])
+- if rc:
+- log.error("failed to configure Mac boot loader")
++ """Modifies the HFS+ ESP partition to be bootable.
++
++ Based on the /usr/libexec/mactel-boot-setup script,
++ we will create two symlinks pointing to the Qubes
++ Xen version that is installed and configured for
++ the system. We also need to run hfs-bless on
++ the specific EFI file we allow to be bootable.
++ """
++
++ log.info("mactel configure MacEFI boot partition")
++
++ not_ready = False
++ xen_efi_file = "{}/{}".format(iutil.getSysroot() + self.config_dir, "xen.efi")
++ if not os.path.exists(xen_efi_file):
++ log.waring("mactel efi file not found: %s", xen_efi_file)
++ not_ready = True
++ xen_cfg_file = "{}/{}".format(iutil.getSysroot() + self.config_dir, "xen.cfg")
++ if not os.path.exists(xen_cfg_file):
++ log.warning("mactel efi cfg not found: %s", xen_cfg_file)
++ not_ready = True
++ if not_ready:
++ log.error("mactel cannot continue with mactel_config. see log for details.")
++ return
++
++ sys_dir = iutil.getSysroot() + self._mactel_sys_dir
++ if not os.path.exists(sys_dir):
++ try:
++ os.makedirs(sys_dir)
++ except Exception as error:
++ log.warning("mactel tried to create sys_dir %s but received error: %s", sys_dir, error)
++
++ src_efi = "{}/{}".format("../../../EFI/" + self.efi_dir, "xen.efi")
++ sys_efi = "{}/{}".format(sys_dir, "boot.efi")
++ self._symlink(src_efi, sys_efi)
++
++ src_cfg = "{}/{}".format("../../../EFI/" + self.efi_dir, "xen.cfg")
++ sys_cfg = "{}/{}".format(sys_dir, "xen.cfg") # convention for Xen's cfg lookup
++ self._symlink(src_cfg, sys_cfg)
++
++ result_code = iutil.execInSysroot("touch", ["{}/{}".format(self._mountpoint, "mach_kernel")])
++ if result_code:
++ log.error("mactel failed to touch: %s", "{}/{}".format(self._mountpoint, "mach_kernel"))
++
++ text = """
++
++
++
++ ProductBuildVersion
++
++ ProductName
++ Linux
++ ProductVersion
++ {}
++
++""".format(self._system_label)
++ sys_ver_file_name = "{}/{}".format(sys_dir, "SystemVersion.plist")
++ try:
++ with open(sys_ver_file_name, "w") as sys_version_file:
++ sys_version_file.write(text)
++ except IOError as error:
++ log.error("mactel failed to open %s for write: %s", sys_ver_file_name, error)
++ cfg_ver_file_name = "{}/{}".format(iutil.getSysroot() + self.config_dir, "SystemVersion.plist")
++ self._copy_file(sys_ver_file_name, cfg_ver_file_name)
++
++ bless_file = "{}/{}".format(self.config_dir, "xen.efi")
++ result_code = iutil.execInSysroot("hfs-bless", [bless_file])
++ if result_code:
++ log.error("mactel failed to run 'hfs-bless %s'", bless_file)
++
++ # make the partition macOS friendly (e.g. to set rescue mode from macOS)
++ fseventsd = "{}/{}".format(iutil.getSysroot() + self._mountpoint, ".fseventsd")
++ try:
++ os.makedirs(fseventsd)
++ except Exception as error:
++ log.error("mactel could not make directory %s: %s", fseventsd, error)
++ touch_files = [
++ "{}/{}/{}".format(self._mountpoint, ".fseventsd", "no_log"),
++ "{}/{}".format(self._mountpoint, ".metadata_never_index"),
++ "{}/{}".format(self._mountpoint, ".Trashes")]
++ result_code = iutil.execInSysroot("touch", touch_files)
++ if result_code:
++ log.error("mactel failed to touch: %s", touch_files)
++
++ text = """Qubes OS
++
++CAUTION
++--
++This partition is used to boot Qubes OS on a macOS machine.
++Modifying the contents could render your installation unbootable.
++
++RESCUE / RECOVERY MODE
++--
++In the event that you need to boot into Rescue mode, you will need
++to change the default Xen configuration.
++
++0. Backup your xen.cfg.
++
++ cp /EFI/qubes/xen.cfg /EFI/qubes/xen.cfg~
++
++1. Open /EFI/qubes/xen.cfg
+
++2. Look at the sections with the named headers. One may already be
++ named "qubes-rescue", which we will use in Step 4 below.
++
++ If not, we will need to make one. Copy your current configuration
++ to another entry. But change the kernel line to only have "rescue"
++ at the end. As an example only:
++
++ [qubes-rescue]
++ options=loglvl=all dom0_mem=min:1024M dom0_mem=max:4096M iommu=no-igfx
++ kernel=vmlinuz-4.14.13-2.pvops.qubes.x86_64 rescue
++ ramdisk=initramfs-4.14.13-2.pvops.qubes.x86_64.img
++
++ Do not simply copy this section above. You will need to make sure
++ all of your existing parameters, vmlinuz and initramfs versions match
++ your current kernel(s) you are booting.
++
++3. At the top of the file, edit the default link to use your new entry.
++
++ [global]
++ default=qubes-rescue
++
++Now when you reboot using Boot Camp and select "Qubes OS", it will boot
++into Rescue mode.
++
++To revert, change the default entry back to the original first entry name."""
++ readme = "{}/{}".format(iutil.getSysroot() + self._mountpoint, "00-README.txt")
++ try:
++ with open(readme, "w") as readme_file:
++ readme_file.write(text)
++ except IOError as error:
++ log.error("mactel failed to open %s for write: %s", readme, error)
++
++ def mactel_install_qubes_artwork(self):
++ """Configures the Qubes logo and label for macOS boot selector.
++
++ Shows during boot selection options.
++
++ .disk_label is defined as a text representation of the volume
++ label converted to a special image. See this link for
++ more details: http://refit.sourceforge.net/info/vollabel.html
++
++ .VolumeIcon.icns is defined as an ICNS image format of 512x512.
++ """
++
++ log.info("mactel creating Qubes Artwork")
++
++ artwork_dir = self._mactel_artwork_dir
++ if not os.path.exists(artwork_dir):
++ log.debug("mactel using sysroot for artwork prefix")
++ artwork_dir = iutil.getSysroot() + artwork_dir
++ if not os.path.exists(artwork_dir):
++ log.warning("mactel artwork missing from: %s", artwork_dir)
++ return
++
++ icon_src = artwork_dir + ".icns"
++ if os.path.exists(icon_src):
++ icon_dst_fil = "{}/{}".format(iutil.getSysroot() + self._mountpoint, ".VolumeIcon.icns")
++ self._copy_file(icon_src, icon_dst_fil)
++ else:
++ log.warning("mactel volume icon not found: %s", icon_src)
++
++ src_files = os.listdir(artwork_dir)
++ for file_name in src_files:
++ full_file_name = "{}/{}".format(artwork_dir, file_name)
++ if os.path.isfile(full_file_name):
++ sys_dir_file_name = "{}/{}".format(iutil.getSysroot() + self._mactel_sys_dir, "." + file_name)
++ self._copy_file(full_file_name, sys_dir_file_name)
++ config_dir = iutil.getSysroot() + self.config_dir
++ if os.path.exists(config_dir):
++ dest = "{}/{}".format(config_dir, "." + file_name)
++ self._copy_file(full_file_name, dest)
++
+ def install(self, args=None):
+ super(MacEFIGRUB, self).install()
++ log.info("Installing mactel MacEFI")
+ self.mactel_config()
++ self.mactel_install_qubes_artwork()
+
+ def is_valid_stage1_device(self, device, early=False):
+ valid = super(MacEFIGRUB, self).is_valid_stage1_device(device, early)
+@@ -1932,6 +2124,24 @@ class MacEFIGRUB(EFIGRUB):
+ log.debug("MacEFIGRUB.is_valid_stage1_device(%s) returning %s", device.name, valid)
+ return valid
+
++ @staticmethod
++ def _symlink(source="", target=""):
++ """Creates a symlink between source and target."""
++ try:
++ os.symlink(source, target)
++ except OSError as error:
++ log.error("mactel failed to symlink %s -> %s : %s", source, target, error)
++
++ @staticmethod
++ def _copy_file(source="", target=""):
++ """Copies source to target using shutil.
++
++ Also chmod to 0644."""
++ try:
++ shutil.copy(source, target)
++ os.chmod(target, 0o644)
++ except OSError as error:
++ log.error("mactel failed to copy %s to %s: %s", source, target, error)
+
+ # Inherit abstract methods from BootLoader
+ # pylint: disable=abstract-method
+--
+2.14.4
+
diff --git a/0043-anaconda-use-proper-subvolume-argument-when-booting-.patch b/0043-anaconda-use-proper-subvolume-argument-when-booting-.patch
new file mode 100644
index 0000000..0f180fe
--- /dev/null
+++ b/0043-anaconda-use-proper-subvolume-argument-when-booting-.patch
@@ -0,0 +1,46 @@
+From bcb258acbfb3b21c44422bdd9490e5f57c1e03fe Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Marek=20Marczykowski-G=C3=B3recki?=
+
+Date: Fri, 19 Oct 2018 08:02:13 +0200
+Subject: [PATCH] anaconda: use proper subvolume argument when booting from
+ btrfs (EFI)
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Kernel command line in legacy mode is constructed by grub scripts and
+properly handle btrfs subvolumes. For EFI, it is built directly by
+anaconda and 'rootflags=subvol=...' argument need to be added manually.
+
+Fixes QubesOS/qubes-issues#1871
+
+Signed-off-by: Frédéric Pierret
+---
+ pyanaconda/bootloader.py | 7 +++++--
+ 1 file changed, 5 insertions(+), 2 deletions(-)
+
+diff --git a/pyanaconda/bootloader.py b/pyanaconda/bootloader.py
+index 821eb2100..c68b8b1e8 100644
+--- a/pyanaconda/bootloader.py
++++ b/pyanaconda/bootloader.py
+@@ -1886,12 +1886,15 @@ class XenEFI(EFIGRUB):
+
+ def write_config_images(self, config):
+ for image in self.images:
++ root_args = 'root=' + image.device.fstab_spec
++ if image.device.type == "btrfs subvolume":
++ root_args += " rootflags=subvol=%s" % image.device.name
+ config.write("\n")
+ config.write("[{}]\n".format(image.version))
+ config.write("options=loglvl=all dom0_mem=min:1024M dom0_mem=max:4096M iommu=no-igfx\n")
+- config.write("kernel={} root={} {}\n".format(
++ config.write("kernel={} {} {}\n".format(
+ image.kernel,
+- image.device.fstab_spec,
++ root_args,
+ self.boot_args))
+ config.write("ramdisk={}\n".format(image.initrd))
+
+--
+2.14.4
+
diff --git a/0044-anaconda-enable-discard-option-for-dom0-filesystems-.patch b/0044-anaconda-enable-discard-option-for-dom0-filesystems-.patch
new file mode 100644
index 0000000..81287ca
--- /dev/null
+++ b/0044-anaconda-enable-discard-option-for-dom0-filesystems-.patch
@@ -0,0 +1,86 @@
+From 99ea01b31f1733910123eff96a226f1183458839 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Marek=20Marczykowski-G=C3=B3recki?=
+
+Date: Fri, 19 Oct 2018 08:02:13 +0200
+Subject: [PATCH] anaconda: enable discard option for dom0 filesystems by
+ default
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+This may have performance impact on some older SSD, but on the other
+hand, without this option it's pretty easy to fill the whole LVM thin
+pool even if there is plenty free space in dom0.
+Note that this doesn't enable it on LUKS layer, this is still disabled
+by default.
+
+Fixes QubesOS/qubes-issues#3226
+
+Signed-off-by: Frédéric Pierret
+---
+ pyanaconda/install.py | 11 +++++++++++
+ pyanaconda/kickstart.py | 12 ++++++++++++
+ 2 files changed, 23 insertions(+)
+
+diff --git a/pyanaconda/install.py b/pyanaconda/install.py
+index a11b6b43b..324e136c7 100644
+--- a/pyanaconda/install.py
++++ b/pyanaconda/install.py
+@@ -211,6 +211,17 @@ def doInstall(storage, payload, ksdata, instClass):
+ wait_for_entropy=entropy_wait_clbk)
+
+ turn_on_filesystems(storage, mount_only=flags.flags.dirInstall, callbacks=callbacks_reg)
++
++ # For autopart, actual partition related objects (especially
++ # blivet.format.FS objects) are created by the above call. And autopart
++ # does not provide any way to specify default mount options (unlike manual
++ # partitioning). Because of this, patch it now to add 'discard' option.
++ if storage.root_device.format.options and \
++ 'discard' not in storage.root_device.format.options:
++ storage.root_device.format.options += ',discard'
++ else:
++ storage.root_device.format.options = 'defaults,discard'
++
+ payload.writeStorageEarly()
+
+ # Run %pre-install scripts with the filesystem mounted and no packages
+diff --git a/pyanaconda/kickstart.py b/pyanaconda/kickstart.py
+index c0db1b614..5cd86d5fb 100644
+--- a/pyanaconda/kickstart.py
++++ b/pyanaconda/kickstart.py
+@@ -885,6 +885,10 @@ class LogVolData(commands.logvol.F23_LogVolData):
+ self.mountpoint = ""
+ ty = None
+
++ if self.mountpoint.startswith('/') and not self.fsopts:
++ # enable discard for normal filesystems in dom0
++ self.fsopts = "defaults,discard"
++
+ # Sanity check mountpoint
+ if self.mountpoint != "" and self.mountpoint[0] != '/':
+ raise KickstartParseError(formatErrorMsg(self.lineno,
+@@ -1245,6 +1249,10 @@ class PartitionData(commands.partition.F23_PartData):
+ else:
+ ty = storage.default_fstype
+
++ if self.mountpoint.startswith('/') and not self.fsopts:
++ # enable discard for normal filesystems in dom0
++ self.fsopts = "defaults,discard"
++
+ if not size and self.size:
+ try:
+ size = Size("%d MiB" % self.size)
+@@ -1490,6 +1498,10 @@ class RaidData(commands.raid.F25_RaidData):
+ else:
+ ty = storage.default_fstype
+
++ if self.mountpoint.startswith('/') and not self.fsopts:
++ # enable discard for normal filesystems in dom0
++ self.fsopts = "defaults,discard"
++
+ # Sanity check mountpoint
+ if self.mountpoint != "" and self.mountpoint[0] != '/':
+ raise KickstartParseError(formatErrorMsg(self.lineno,
+--
+2.14.4
+
diff --git a/0045-anaconda-Add-ucode-scan-to-default-Xen-command-line.patch b/0045-anaconda-Add-ucode-scan-to-default-Xen-command-line.patch
new file mode 100644
index 0000000..1e10abd
--- /dev/null
+++ b/0045-anaconda-Add-ucode-scan-to-default-Xen-command-line.patch
@@ -0,0 +1,45 @@
+From 93611849f490f249b7ecf5c5cdce8852c526c73d Mon Sep 17 00:00:00 2001
+From: Marek Marczykowski
+Date: Fri, 19 Oct 2018 08:02:13 +0200
+Subject: [PATCH] anaconda: Add ucode=scan to default Xen command line
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Try to update microcode as early as possible if provided.
+This option will scan all multiboot modules besides dom0 kernel. In our
+case this is perfect - there is only one other module and it is
+initramfs which have microcode early cpio prepended.
+
+QubesOS/qubes-issues#3703
+
+Signed-off-by: Frédéric Pierret
+---
+ pyanaconda/bootloader.py | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/pyanaconda/bootloader.py b/pyanaconda/bootloader.py
+index c68b8b1e8..27cc480b5 100644
+--- a/pyanaconda/bootloader.py
++++ b/pyanaconda/bootloader.py
+@@ -1504,7 +1504,7 @@ class GRUB2(GRUB):
+ # boot arguments
+ log.info("bootloader.py: used boot args: %s ", self.boot_args)
+ defaults.write("GRUB_CMDLINE_LINUX=\"%s\"\n" % self.boot_args)
+- defaults.write("GRUB_CMDLINE_XEN_DEFAULT=\"console=none dom0_mem=min:1024M dom0_mem=max:4096M iommu=no-igfx\"\n")
++ defaults.write("GRUB_CMDLINE_XEN_DEFAULT=\"console=none dom0_mem=min:1024M dom0_mem=max:4096M iommu=no-igfx ucode=scan\"\n")
+ defaults.write("GRUB_DISABLE_RECOVERY=\"true\"\n")
+ defaults.write("GRUB_THEME=\"/boot/grub2/themes/system/theme.txt\"\n")
+ defaults.close()
+@@ -1891,7 +1891,7 @@ class XenEFI(EFIGRUB):
+ root_args += " rootflags=subvol=%s" % image.device.name
+ config.write("\n")
+ config.write("[{}]\n".format(image.version))
+- config.write("options=loglvl=all dom0_mem=min:1024M dom0_mem=max:4096M iommu=no-igfx\n")
++ config.write("options=loglvl=all dom0_mem=min:1024M dom0_mem=max:4096M iommu=no-igfx ucode=scan\n")
+ config.write("kernel={} {} {}\n".format(
+ image.kernel,
+ root_args,
+--
+2.14.4
+
diff --git a/0046-anaconda-avoid-adding-duplicated-kernel-entries.patch b/0046-anaconda-avoid-adding-duplicated-kernel-entries.patch
new file mode 100644
index 0000000..7f199c2
--- /dev/null
+++ b/0046-anaconda-avoid-adding-duplicated-kernel-entries.patch
@@ -0,0 +1,36 @@
+From 5136f7c2142166360108c104fdf9244cdca91820 Mon Sep 17 00:00:00 2001
+From: Marek Marczykowski
+Date: Fri, 19 Oct 2018 08:02:13 +0200
+Subject: [PATCH] anaconda: avoid adding duplicated kernel entries
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+List kernel versions without duplicates, even when there are multiple
+files related to the same kernel version.
+Duplicated kernel versions here caused regenerating initramfs multiple
+times and duplicated entries in xen.cfg.
+
+QubesOS/qubes-issues#3624
+
+Signed-off-by: Frédéric Pierret
+---
+ pyanaconda/packaging/__init__.py | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/pyanaconda/packaging/__init__.py b/pyanaconda/packaging/__init__.py
+index 8332ce0e5..26eb953d4 100644
+--- a/pyanaconda/packaging/__init__.py
++++ b/pyanaconda/packaging/__init__.py
+@@ -829,7 +829,7 @@ class PackagePayload(Payload):
+ if fnmatch(f, "/boot/vmlinuz-*") or
+ fnmatch(f, "/boot/efi/EFI/%s/vmlinuz-*" % self.instclass.efi_dir)))
+
+- return sorted(files, key=functools.cmp_to_key(versionCmp))
++ return sorted(set(files), key=functools.cmp_to_key(versionCmp))
+
+ @property
+ def rpmMacros(self):
+--
+2.14.4
+
diff --git a/0047-anaconda-Fix-System-Requirements-URL-and-typo-in-har.patch b/0047-anaconda-Fix-System-Requirements-URL-and-typo-in-har.patch
new file mode 100644
index 0000000..3448988
--- /dev/null
+++ b/0047-anaconda-Fix-System-Requirements-URL-and-typo-in-har.patch
@@ -0,0 +1,51 @@
+From fb3d98cebb67a468e37c036b1dd2c1707cc731bd Mon Sep 17 00:00:00 2001
+From: Andrew David Wong
+Date: Fri, 19 Oct 2018 08:02:13 +0200
+Subject: [PATCH] anaconda: Fix System Requirements URL and typo in hardware
+ warnings
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Fixes QubesOS/qubes-issues#3932
+Related to QubesOS/qubes-issues#3208
+
+Signed-off-by: Frédéric Pierret
+---
+ pyanaconda/ui/gui/spokes/welcome.glade | 2 +-
+ pyanaconda/ui/tui/spokes/warnings_spoke.py | 4 ++--
+ 2 files changed, 3 insertions(+), 3 deletions(-)
+
+diff --git a/pyanaconda/ui/gui/spokes/welcome.glade b/pyanaconda/ui/gui/spokes/welcome.glade
+index 8daed92af..3bd04fe08 100644
+--- a/pyanaconda/ui/gui/spokes/welcome.glade
++++ b/pyanaconda/ui/gui/spokes/welcome.glade
+@@ -507,7 +507,7 @@
+ True
+ False
+ start
+- This hardware lacks features required by Qubes OS. Missing features: %(features)s. Without these features, Qubes OS will not function normally. It is recommended that only developers and power users proceed with the installation. For more information on supported hardware, please refer to https://www.qubes-os.org/system-requirements/
++ This hardware lacks features required by Qubes OS. Missing features: %(features)s. Without these features, Qubes OS will not function normally. It is recommended that only developers and power users proceed with the installation. For more information on supported hardware, please refer to https://www.qubes-os.org/doc/system-requirements/
+ True
+
+
+diff --git a/pyanaconda/ui/tui/spokes/warnings_spoke.py b/pyanaconda/ui/tui/spokes/warnings_spoke.py
+index 8aed09625..a0e750914 100644
+--- a/pyanaconda/ui/tui/spokes/warnings_spoke.py
++++ b/pyanaconda/ui/tui/spokes/warnings_spoke.py
+@@ -43,10 +43,10 @@ class WarningsSpoke(StandaloneTUISpoke):
+ def __init__(self, *args, **kwargs):
+ StandaloneTUISpoke.__init__(self, *args, **kwargs)
+
+- self._message = _("This hardware lack features required by Qubes OS. "
++ self._message = _("This hardware lacks features required by Qubes OS. "
+ "Missing features: %(features)s. "
+ "For more information on supported hardware, "
+- "please refer to https://www.qubes-os.org/system-requirements/")
++ "please refer to https://www.qubes-os.org/doc/system-requirements/")
+ # Does anything need to be displayed?
+ # pylint: disable=no-member
+ # self._unsupported = not self.data.unsupportedhardware.unsupported_hardware \
+--
+2.14.4
+
diff --git a/0048-anaconda-save-keyboard-layout-to-udev.patch b/0048-anaconda-save-keyboard-layout-to-udev.patch
new file mode 100644
index 0000000..1bb1493
--- /dev/null
+++ b/0048-anaconda-save-keyboard-layout-to-udev.patch
@@ -0,0 +1,84 @@
+From 9c52b9b77378282cf25c87744ff86935f529e345 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Marek=20Marczykowski-G=C3=B3recki?=
+
+Date: Fri, 19 Oct 2018 08:02:13 +0200
+Subject: [PATCH] anaconda: save keyboard layout to udev
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Xorg loads keyboard layout for new devices (or existing one re-detected)
+only from its config, ignoring runtime changes done in the meantime
+(setxkbmap etc). Since installation process calls udevadm trigger
+somewhere, all input devices are re-discovered and reverted to default
+keyboard layout (us). Avoid this by configuring current keyboard layout
+also as udev rules, which are loaded by Xorg while discovering device.
+
+Fixes QubesOS/qubes-issues#3352
+
+Signed-off-by: Frédéric Pierret
+---
+ pyanaconda/ui/gui/xkl_wrapper.py | 29 +++++++++++++++++++++++++++++
+ 1 file changed, 29 insertions(+)
+
+diff --git a/pyanaconda/ui/gui/xkl_wrapper.py b/pyanaconda/ui/gui/xkl_wrapper.py
+index a2f14ce75..73178310b 100644
+--- a/pyanaconda/ui/gui/xkl_wrapper.py
++++ b/pyanaconda/ui/gui/xkl_wrapper.py
+@@ -307,6 +307,8 @@ class XklWrapper(object):
+ raise XklWrapperError("Failed to add layout '%s (%s)'" % (layout,
+ variant))
+
++ self.save_layouts_to_udev(self._rec.layouts, self._rec.variants)
++
+ @gtk_action_wait
+ def remove_layout(self, layout):
+ """
+@@ -341,6 +343,8 @@ class XklWrapper(object):
+ raise XklWrapperError("Failed to remove layout '%s (%s)'" % (layout,
+ variant))
+
++ self.save_layouts_to_udev(new_layouts, new_variants)
++
+ @gtk_action_wait
+ def replace_layouts(self, layouts_list):
+ """
+@@ -368,6 +372,8 @@ class XklWrapper(object):
+ msg = "Failed to replace layouts with: %s" % ",".join(layouts_list)
+ raise XklWrapperError(msg)
+
++ self.save_layouts_to_udev(new_layouts, new_variants)
++
+ @gtk_action_wait
+ def set_switching_options(self, options):
+ """
+@@ -391,3 +397,26 @@ class XklWrapper(object):
+ msg = "Failed to set switching options to: %s" % ",".join(options)
+ raise XklWrapperError(msg)
+
++ def save_layouts_to_udev(self, layouts, variants):
++ """
++ Sets layouts to udev, so it will also apply to newly connected
++ keyboards (or existing after udevadm trigger). Otherwise Xorg setup
++ them based on xorg.conf with a fallback to hardcoded values.
++
++ :param layouts: list of layouts
++ :param variants: list of layout variants, matching *layouts*
++ """
++
++ udev_rules_dir = '/run/udev/rules.d'
++ udev_rules_path = udev_rules_dir + '/90-keyboard-layout.rules'
++ try:
++ iutil.mkdirChain(udev_rules_dir)
++ except FileExistsError:
++ pass
++ with open(udev_rules_path, 'w') as rules:
++ rules.write('ENV{{ID_INPUT_KEYBOARD}}=="1", '
++ 'ENV{{xkblayout}}="{layouts}", '
++ 'ENV{{xkbvariant}}="{variants}"\n'.format(
++ layouts=','.join(layouts), variants=','.join(variants)))
++
++ iutil.startProgram(['udevadm', 'control', '-R']).communicate()
+--
+2.14.4
+
diff --git a/0049-anaconda-fix-root-password-dialog.patch b/0049-anaconda-fix-root-password-dialog.patch
new file mode 100644
index 0000000..1f2b5a3
--- /dev/null
+++ b/0049-anaconda-fix-root-password-dialog.patch
@@ -0,0 +1,67 @@
+From ee52e579dee7e8b33c932a61d27151826e47a97e Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Marek=20Marczykowski-G=C3=B3recki?=
+
+Date: Fri, 19 Oct 2018 08:02:13 +0200
+Subject: [PATCH] anaconda: fix root password dialog
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Properly save 'lock' state. Previously if it was unchecked, new password
+was saved, but remained locked.
+
+Fixes QubesOS/qubes-issues#3327
+
+Signed-off-by: Frédéric Pierret
+---
+ pyanaconda/ui/gui/spokes/password.py | 11 ++++++++---
+ 1 file changed, 8 insertions(+), 3 deletions(-)
+
+diff --git a/pyanaconda/ui/gui/spokes/password.py b/pyanaconda/ui/gui/spokes/password.py
+index 3e8ada1cc..646e7f7c1 100644
+--- a/pyanaconda/ui/gui/spokes/password.py
++++ b/pyanaconda/ui/gui/spokes/password.py
+@@ -99,6 +99,8 @@ class PasswordSpoke(FirstbootSpokeMixIn, NormalSpoke, GUISpokeInputCheckHandler)
+ self.pw.set_placeholder_text(_("The password is set."))
+ self.confirm.set_placeholder_text(_("The password is set."))
+
++ self._lock = self.data.rootpw.lock
++
+ self.pw_bar = self.builder.get_object("password_bar")
+ self.pw_label = self.builder.get_object("password_label")
+
+@@ -131,6 +133,7 @@ class PasswordSpoke(FirstbootSpokeMixIn, NormalSpoke, GUISpokeInputCheckHandler)
+ self.confirm.set_sensitive(not lock.get_active())
+ if not lock.get_active():
+ self.pw.grab_focus()
++ self._lock = lock.get_active()
+
+ # Caps lock detection isn't hooked up right now
+ # def setCapsLockLabel(self):
+@@ -161,10 +164,12 @@ class PasswordSpoke(FirstbootSpokeMixIn, NormalSpoke, GUISpokeInputCheckHandler)
+ self.data.rootpw.seen = False
+ self._kickstarted = False
+
+- if pw:
++ if self._lock:
++ self.data.rootpw.lock = True
++ elif pw:
++ self.data.rootpw.lock = False
+ self.data.rootpw.password = cryptPassword(pw)
+ self.data.rootpw.isCrypted = True
+- self.data.rootpw.lock = self._lock
+
+ self.pw.set_placeholder_text("")
+ self.confirm.set_placeholder_text("")
+@@ -265,7 +270,7 @@ class PasswordSpoke(FirstbootSpokeMixIn, NormalSpoke, GUISpokeInputCheckHandler)
+ return InputCheck.CHECK_OK
+
+ # If the password is empty, clear the strength bar and skip this check
+- if not pw and not confirm:
++ if self.lock.get_active() or (not pw and not confirm):
+ self._updatePwQuality(True, 0)
+ return InputCheck.CHECK_OK
+
+--
+2.14.4
+
diff --git a/0050-anaconda-mark-qubes-user-name-as-reserved.patch b/0050-anaconda-mark-qubes-user-name-as-reserved.patch
new file mode 100644
index 0000000..7b7c8e2
--- /dev/null
+++ b/0050-anaconda-mark-qubes-user-name-as-reserved.patch
@@ -0,0 +1,35 @@
+From 2522cc68bb55ca668ca5a71abc9497fdd0114d21 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Marek=20Marczykowski-G=C3=B3recki?=
+
+Date: Fri, 19 Oct 2018 08:02:13 +0200
+Subject: [PATCH] anaconda: mark 'qubes' user name as reserved
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+'qubes' group is used internally, but useradd want to create a new group
+named as new user, so 'qubes' user name should also be avoided.
+
+Fixes QubesOS/qubes-issues#3777
+
+Signed-off-by: Frédéric Pierret
+---
+ pyanaconda/users.py | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/pyanaconda/users.py b/pyanaconda/users.py
+index 366d6226f..1b4424be8 100644
+--- a/pyanaconda/users.py
++++ b/pyanaconda/users.py
+@@ -123,7 +123,7 @@ def validatePassword(pw, user="root", settings=None, minlen=None):
+ return (valid, strength, message)
+
+ def check_username(name):
+- if name in os.listdir("/") + ["root", "home", "daemon", "system"]:
++ if name in os.listdir("/") + ["root", "home", "daemon", "system", "qubes"]:
+ return (False, _("User name is reserved for system: %s") % name)
+
+ if name.startswith("-"):
+--
+2.14.4
+
diff --git a/0051-anaconda-add-smt-off-xen-option-during-installation.patch b/0051-anaconda-add-smt-off-xen-option-during-installation.patch
new file mode 100644
index 0000000..bc79689
--- /dev/null
+++ b/0051-anaconda-add-smt-off-xen-option-during-installation.patch
@@ -0,0 +1,44 @@
+From 3bf0a8b70e34c5019adf39b1a7439efa93e28ac6 Mon Sep 17 00:00:00 2001
+From: Marek Marczykowski
+Date: Fri, 19 Oct 2018 08:02:13 +0200
+Subject: [PATCH] anaconda: add smt=off xen option during installation
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Defaults set during package installation do not apply, as booloader
+configuration doesn't exist at that stage yet.
+
+Reported by @rustybird
+QubesOS/qubes-issues#4252
+
+Signed-off-by: Frédéric Pierret
+---
+ pyanaconda/bootloader.py | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/pyanaconda/bootloader.py b/pyanaconda/bootloader.py
+index 27cc480b5..deab41cf1 100644
+--- a/pyanaconda/bootloader.py
++++ b/pyanaconda/bootloader.py
+@@ -1504,7 +1504,7 @@ class GRUB2(GRUB):
+ # boot arguments
+ log.info("bootloader.py: used boot args: %s ", self.boot_args)
+ defaults.write("GRUB_CMDLINE_LINUX=\"%s\"\n" % self.boot_args)
+- defaults.write("GRUB_CMDLINE_XEN_DEFAULT=\"console=none dom0_mem=min:1024M dom0_mem=max:4096M iommu=no-igfx ucode=scan\"\n")
++ defaults.write("GRUB_CMDLINE_XEN_DEFAULT=\"console=none dom0_mem=min:1024M dom0_mem=max:4096M iommu=no-igfx ucode=scan smt=off\"\n")
+ defaults.write("GRUB_DISABLE_RECOVERY=\"true\"\n")
+ defaults.write("GRUB_THEME=\"/boot/grub2/themes/system/theme.txt\"\n")
+ defaults.close()
+@@ -1891,7 +1891,7 @@ class XenEFI(EFIGRUB):
+ root_args += " rootflags=subvol=%s" % image.device.name
+ config.write("\n")
+ config.write("[{}]\n".format(image.version))
+- config.write("options=loglvl=all dom0_mem=min:1024M dom0_mem=max:4096M iommu=no-igfx ucode=scan\n")
++ config.write("options=loglvl=all dom0_mem=min:1024M dom0_mem=max:4096M iommu=no-igfx ucode=scan smt=off\n")
+ config.write("kernel={} {} {}\n".format(
+ image.kernel,
+ root_args,
+--
+2.14.4
+
diff --git a/0052-anaconda-update-Qubes-specific-code-for-Fedora-21-ve.patch b/0052-anaconda-update-Qubes-specific-code-for-Fedora-21-ve.patch
new file mode 100644
index 0000000..3e3db48
--- /dev/null
+++ b/0052-anaconda-update-Qubes-specific-code-for-Fedora-21-ve.patch
@@ -0,0 +1,50 @@
+From 8914364af4e9724852ad91ef583d425e5b0b53fa Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Marek=20Marczykowski-G=C3=B3recki?=
+
+Date: Fri, 19 Oct 2018 08:02:13 +0200
+Subject: [PATCH] anaconda: update Qubes-specific code for Fedora 21 version
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Signed-off-by: Frédéric Pierret
+---
+ data/help/en-US/QubesPlaceholder.html | 5 +++++
+ data/help/en-US/QubesPlaceholderWithLinks.html | 13 +++++++++++++
+ 2 files changed, 18 insertions(+)
+ create mode 100644 data/help/en-US/QubesPlaceholder.html
+ create mode 100644 data/help/en-US/QubesPlaceholderWithLinks.html
+
+diff --git a/data/help/en-US/QubesPlaceholder.html b/data/help/en-US/QubesPlaceholder.html
+new file mode 100644
+index 000000000..6811a3871
+--- /dev/null
++++ b/data/help/en-US/QubesPlaceholder.html
+@@ -0,0 +1,5 @@
++
++The Anaconda built-in help
++...is not yet available for this screen.
++You can check the Anaconda wiki page, the Qubes Installation Guide or other online help resources instead.
++
+diff --git a/data/help/en-US/QubesPlaceholderWithLinks.html b/data/help/en-US/QubesPlaceholderWithLinks.html
+new file mode 100644
+index 000000000..0f7d7dcd5
+--- /dev/null
++++ b/data/help/en-US/QubesPlaceholderWithLinks.html
+@@ -0,0 +1,13 @@
++
++The Anaconda built-in help
++...is not yet available for this screen.
++You can check the Anaconda wiki page, the Qubes Installation Guide or other online help resources instead:
++
++
++
++
+--
+2.14.4
+
diff --git a/0053-anaconda-require-user-password-being-set.patch b/0053-anaconda-require-user-password-being-set.patch
new file mode 100644
index 0000000..784a2c4
--- /dev/null
+++ b/0053-anaconda-require-user-password-being-set.patch
@@ -0,0 +1,151 @@
+From 757f6c7095362f2b71321bc94ed67f290a6ff8db Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Marek=20Marczykowski-G=C3=B3recki?=
+
+Date: Fri, 19 Oct 2018 08:02:13 +0200
+Subject: [PATCH] anaconda: require user password being set
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Drop selectable option 'Require a password to use this account'. Make it
+required.
+
+QubesOS/qubes-issues#2574
+
+Signed-off-by: Frédéric Pierret
+---
+ pyanaconda/ui/gui/spokes/user.glade | 17 ---------------
+ pyanaconda/ui/gui/spokes/user.py | 43 ++++++-------------------------------
+ 2 files changed, 7 insertions(+), 53 deletions(-)
+
+diff --git a/pyanaconda/ui/gui/spokes/user.glade b/pyanaconda/ui/gui/spokes/user.glade
+index e6700657d..79283a948 100644
+--- a/pyanaconda/ui/gui/spokes/user.glade
++++ b/pyanaconda/ui/gui/spokes/user.glade
+@@ -174,23 +174,6 @@
+ 2
+
+
+-
+-
+- _Require a password to use this account
+- True
+- True
+- False
+- True
+- 0
+- True
+- True
+-
+-
+-
+- 1
+- 4
+-
+-
+
+
+ True
+diff --git a/pyanaconda/ui/gui/spokes/user.py b/pyanaconda/ui/gui/spokes/user.py
+index dd281f8e4..7db7e44d4 100644
+--- a/pyanaconda/ui/gui/spokes/user.py
++++ b/pyanaconda/ui/gui/spokes/user.py
+@@ -260,7 +260,6 @@ class UserSpoke(NormalSpoke, GUISpokeInputCheckHandler):
+ self.username = self.builder.get_object("t_username")
+ self.pw = self.builder.get_object("t_password")
+ self.confirm = self.builder.get_object("t_verifypassword")
+- self.usepassword = self.builder.get_object("c_usepassword")
+
+ # Counters for checks that ask the user to click Done to confirm
+ self._waiveStrengthClicks = 0
+@@ -311,16 +310,8 @@ class UserSpoke(NormalSpoke, GUISpokeInputCheckHandler):
+ # This needs to happen after the input checks have been created, since
+ # the Gtk signal handlers use the input check variables.
+ if self._password_kickstarted:
+- self.usepassword.set_active(True)
+ self.pw.set_placeholder_text(_("The password was set by kickstart."))
+ self.confirm.set_placeholder_text(_("The password was set by kickstart."))
+- elif not self.policy.emptyok:
+- # Policy is that a non-empty password is required
+- self.usepassword.set_active(True)
+-
+- if not self.policy.emptyok:
+- # User isn't allowed to change whether password is required or not
+- self.usepassword.set_sensitive(False)
+
+ # set the visibility of the password entries
+ set_password_visibility(self.pw, False)
+@@ -352,21 +343,12 @@ class UserSpoke(NormalSpoke, GUISpokeInputCheckHandler):
+ def apply(self):
+ # set the password only if the user enters anything to the text entry
+ # this should preserve the kickstart based password
+- if self.usepassword.get_active():
+- if self.pw.get_text():
+- self._password_kickstarted = False
+- self._user.password = cryptPassword(self.pw.get_text())
+- self._user.isCrypted = True
+- self.pw.set_placeholder_text("")
+- self.confirm.set_placeholder_text("")
+-
+- # reset the password when the user unselects it
+- else:
++ if self.pw.get_text():
++ self._password_kickstarted = False
++ self._user.password = cryptPassword(self.pw.get_text())
++ self._user.isCrypted = True
+ self.pw.set_placeholder_text("")
+ self.confirm.set_placeholder_text("")
+- self._user.password = ""
+- self._user.isCrypted = False
+- self._password_kickstarted = False
+
+ self._user.name = self.username.get_text()
+
+@@ -419,17 +401,6 @@ class UserSpoke(NormalSpoke, GUISpokeInputCheckHandler):
+ self.pw_bar.set_value(val)
+ self.pw_label.set_text(text)
+
+- def usepassword_toggled(self, togglebutton=None, data=None):
+- """Called by Gtk callback when the "Use password" check
+- button is toggled. It will make password entries in/sensitive."""
+-
+- self.pw.set_sensitive(togglebutton.get_active())
+- self.confirm.set_sensitive(togglebutton.get_active())
+-
+- # Re-check the password
+- self.pw.emit("changed")
+- self.confirm.emit("changed")
+-
+ def password_changed(self, editable=None, data=None):
+ """Update the password strength level bar"""
+ # Reset the counters used for the "press Done twice" logic
+@@ -474,7 +445,7 @@ class UserSpoke(NormalSpoke, GUISpokeInputCheckHandler):
+ return InputCheck.CHECK_OK
+
+ # Skip the check if no password is required
+- if (not self.usepassword.get_active()) or self._password_kickstarted:
++ if self._password_kickstarted:
+ return InputCheck.CHECK_OK
+ elif not self.get_input(inputcheck.input_obj):
+ if inputcheck.input_obj == self.pw:
+@@ -488,7 +459,7 @@ class UserSpoke(NormalSpoke, GUISpokeInputCheckHandler):
+ """If the user has entered confirmation data, check whether it matches the password."""
+
+ # Skip the check if no password is required
+- if (not self.usepassword.get_active()) or self._password_kickstarted:
++ if self._password_kickstarted:
+ result = InputCheck.CHECK_OK
+ elif self.confirm.get_text() and (self.pw.get_text() != self.confirm.get_text()):
+ result = _(PASSWORD_CONFIRM_ERROR_GUI)
+@@ -506,7 +477,7 @@ class UserSpoke(NormalSpoke, GUISpokeInputCheckHandler):
+ """
+
+ # Skip the check if no password is required
+- if not self.usepassword.get_active or self._password_kickstarted:
++ if self._password_kickstarted:
+ return InputCheck.CHECK_OK
+
+ # If the password is empty, clear the strength bar and skip this check
+--
+2.14.4
+
diff --git a/0054-anaconda-abort-installation-on-X-startup-fail.patch b/0054-anaconda-abort-installation-on-X-startup-fail.patch
new file mode 100644
index 0000000..847fb8a
--- /dev/null
+++ b/0054-anaconda-abort-installation-on-X-startup-fail.patch
@@ -0,0 +1,44 @@
+From 6b1e2472eb4b495ac6e65ff5b39c3f42cfde50b1 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Marek=20Marczykowski-G=C3=B3recki?=
+
+Date: Fri, 19 Oct 2018 08:02:13 +0200
+Subject: [PATCH] anaconda: abort installation on X startup fail
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Do not fallback to text mode, which cannot property install the system
+without kickstart file (missing LUKS passphrase prompt).
+
+Fixes QubesOS/qubes-issues#2996
+
+Signed-off-by: Frédéric Pierret
+---
+ anaconda.py | 11 +++++++----
+ 1 file changed, 7 insertions(+), 4 deletions(-)
+
+diff --git a/anaconda.py b/anaconda.py
+index 25eba4b0a..f5c7fdb7f 100755
+--- a/anaconda.py
++++ b/anaconda.py
+@@ -543,10 +543,13 @@ def setupDisplay(anaconda, options, addons=None):
+ doStartupX11Actions()
+ except (OSError, RuntimeError) as e:
+ log.warning("X startup failed: %s", e)
+- stdoutLog.warning("X startup failed, falling back to text mode")
+- anaconda.displayMode = 't'
+- graphical_failed = 1
+- time.sleep(2)
++ stdoutLog.warning("X startup failed, aborting installation")
++ stdoutLog.error("X startup failed, aborting installation")
++ print(_("The installation cannot continue and the system will be rebooted"))
++ print(_("Press ENTER to continue"))
++ input()
++ iutil.ipmi_report(constants.IPMI_ABORTED)
++ sys.exit(1)
+
+ if not graphical_failed:
+ doExtraX11Actions(options.runres)
+--
+2.14.4
+
diff --git a/0055-anaconda-fix-encryption-passphrase-check.patch b/0055-anaconda-fix-encryption-passphrase-check.patch
new file mode 100644
index 0000000..b9d1f62
--- /dev/null
+++ b/0055-anaconda-fix-encryption-passphrase-check.patch
@@ -0,0 +1,34 @@
+From 94957794abf433c8a1ab48cace77fa6b4998eaa8 Mon Sep 17 00:00:00 2001
+From: Wojtek Porczyk
+Date: Fri, 19 Oct 2018 08:02:13 +0200
+Subject: [PATCH] anaconda: fix encryption passphrase check
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+The installer checks if there is password for autopart. It should check
+that only if autopart is actually in use.
+
+QubesOS/qubes-issues#2180
+
+Signed-off-by: Frédéric Pierret
+---
+ pyanaconda/ui/gui/spokes/storage.py | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/pyanaconda/ui/gui/spokes/storage.py b/pyanaconda/ui/gui/spokes/storage.py
+index 7feda7db3..b812dc543 100644
+--- a/pyanaconda/ui/gui/spokes/storage.py
++++ b/pyanaconda/ui/gui/spokes/storage.py
+@@ -372,7 +372,7 @@ class StorageSpoke(NormalSpoke, StorageChecker):
+ # on the off-chance dasdfmt is running, we can't proceed further
+ threadMgr.wait(constants.THREAD_DASDFMT)
+ hubQ.send_message(self.__class__.__name__, _("Saving storage configuration..."))
+- if flags.automatedInstall and self.data.autopart.encrypted and not self.data.autopart.passphrase:
++ if flags.automatedInstall and self.data.autopart.autopart and self.data.autopart.encrypted and not self.data.autopart.passphrase:
+ self.autopart_missing_passphrase = True
+ StorageChecker.errors = [_("Passphrase for autopart encryption not specified.")]
+ self._ready = True
+--
+2.14.4
+
diff --git a/0056-anaconda-disable-os-prober.patch b/0056-anaconda-disable-os-prober.patch
new file mode 100644
index 0000000..d2069ba
--- /dev/null
+++ b/0056-anaconda-disable-os-prober.patch
@@ -0,0 +1,30 @@
+From e1e51562a8a25892f8c6027b428ce06b0465ac0b Mon Sep 17 00:00:00 2001
+From: Marek Marczykowski
+Date: Sat, 20 Oct 2018 11:16:05 +0200
+Subject: [PATCH] anaconda: disable os prober
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+It tries to mount every existing block device, including VM images.
+
+Signed-off-by: Frédéric Pierret
+---
+ pyanaconda/bootloader.py | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/pyanaconda/bootloader.py b/pyanaconda/bootloader.py
+index deab41cf1..9a4defb9f 100644
+--- a/pyanaconda/bootloader.py
++++ b/pyanaconda/bootloader.py
+@@ -1507,6 +1507,7 @@ class GRUB2(GRUB):
+ defaults.write("GRUB_CMDLINE_XEN_DEFAULT=\"console=none dom0_mem=min:1024M dom0_mem=max:4096M iommu=no-igfx ucode=scan smt=off\"\n")
+ defaults.write("GRUB_DISABLE_RECOVERY=\"true\"\n")
+ defaults.write("GRUB_THEME=\"/boot/grub2/themes/system/theme.txt\"\n")
++ defaults.write("GRUB_DISABLE_OS_PROBER=\"true\"\n")
+ defaults.close()
+
+ def _encrypt_password(self):
+--
+2.14.4
+
diff --git a/anaconda-25.20.9.tar.bz2 b/anaconda-25.20.9.tar.bz2
new file mode 100644
index 0000000000000000000000000000000000000000..7293fc822af56333ad916082e0fbdb405ceea718
GIT binary patch
literal 2585884
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