diff --git a/bin/console-conf-wrapper b/bin/console-conf-wrapper index 609e9fc6..11ddb168 100755 --- a/bin/console-conf-wrapper +++ b/bin/console-conf-wrapper @@ -17,6 +17,9 @@ if [ -e /run/snapd-recovery-chooser-triggered ]; then # when recovery chooser is invoked it takes over the terminal exec /usr/lib/snapd/snap-recovery-chooser fi + if [ -e /var/lib/console-conf/complete ]; then + exit 0 + fi fi if grep -q 'snapd_recovery_mode=install' /proc/cmdline ; then diff --git a/debian/console-conf.console-conf@.service b/debian/console-conf.console-conf@.service index bec8e483..373222f4 100644 --- a/debian/console-conf.console-conf@.service +++ b/debian/console-conf.console-conf@.service @@ -10,7 +10,8 @@ After=core18.start-snapd.service core.start-snapd.service After=snapd.recovery-chooser-trigger.service IgnoreOnIsolate=yes ConditionPathExists=/dev/tty0 -ConditionPathExists=!/var/lib/console-conf/complete +ConditionPathExists=|!/var/lib/console-conf/complete +ConditionPathExists=|/run/snapd-recovery-chooser-triggered StartLimitInterval=0 [Service] diff --git a/subiquity/controllers/filesystem.py b/subiquity/controllers/filesystem.py index 8f6f38c6..9c1a8928 100644 --- a/subiquity/controllers/filesystem.py +++ b/subiquity/controllers/filesystem.py @@ -161,7 +161,8 @@ class FilesystemController(SubiquityController): "block probing failed restricted=%s", restricted) report = self.app.make_apport_report( kind, "block probing", interrupt=False) - self._crash_reports[restricted] = report + if report is not None: + self._crash_reports[restricted] = report continue break diff --git a/subiquity/controllers/installprogress.py b/subiquity/controllers/installprogress.py index 8f392037..a26e617d 100644 --- a/subiquity/controllers/installprogress.py +++ b/subiquity/controllers/installprogress.py @@ -153,7 +153,8 @@ class InstallProgressController(SubiquityController): self.progress_view.set_status(('info_error', _("An error has occurred"))) self.start_ui() - self.progress_view.show_error(crash_report) + if crash_report is not None: + self.progress_view.show_error(crash_report) def logged_command(self, cmd): return ['systemd-cat', '--level-prefix=false', diff --git a/subiquity/controllers/keyboard.py b/subiquity/controllers/keyboard.py index 4ce319cb..1a9310e5 100644 --- a/subiquity/controllers/keyboard.py +++ b/subiquity/controllers/keyboard.py @@ -35,7 +35,7 @@ class KeyboardController(SubiquityController): 'properties': { 'layout': {'type': 'string'}, 'variant': {'type': 'string'}, - 'toggle': {'type': 'string'}, + 'toggle': {'type': ['string', 'null']}, }, 'required': ['layout'], 'additionalProperties': False, diff --git a/subiquity/core.py b/subiquity/core.py index d5723e02..bb886ad0 100644 --- a/subiquity/core.py +++ b/subiquity/core.py @@ -274,7 +274,8 @@ class Subiquity(Application): report = self.make_apport_report( ErrorReportKind.UI, "Installer UI", interrupt=False, wait=True) - print("report saved to {path}".format(report.path)) + if report is not None: + print("report saved to {path}".format(report.path)) except Exception: print("report generation failed") traceback.print_exc() @@ -457,6 +458,9 @@ class Subiquity(Application): self._apport_data.append((key, value)) def make_apport_report(self, kind, thing, *, interrupt, wait=False, **kw): + if not self.opts.dry_run and not os.path.exists('/cdrom/.disk/info'): + return None + log.debug("generating crash report") try: @@ -507,7 +511,7 @@ class Subiquity(Application): self.add_global_overlay(ErrorReportStretchy(self, report)) def make_autoinstall(self): - config = {} + config = {'version': 1} for controller in self.controllers.instances: controller_conf = controller.make_autoinstall() if controller_conf: diff --git a/subiquity/models/filesystem.py b/subiquity/models/filesystem.py index bfee70b4..3ffe0535 100644 --- a/subiquity/models/filesystem.py +++ b/subiquity/models/filesystem.py @@ -24,6 +24,7 @@ import math import os import pathlib import platform +import tempfile from curtin import storage_config from curtin.util import human2bytes @@ -158,7 +159,7 @@ raidlevels = [ def _raidlevels_by_value(): - r = {l.value: l for l in raidlevels} + r = {level.value: level for level in raidlevels} for n in 0, 1, 5, 6, 10: r[str(n)] = r[n] = r["raid"+str(n)] r["stripe"] = r["raid0"] @@ -391,7 +392,7 @@ def asdict(inst): continue m = getattr(inst, 'serialize_' + field.name, None) if m: - r[field.name] = m() + r.update(m()) else: v = getattr(inst, field.name) if v is not None: @@ -968,7 +969,7 @@ class Partition(_Formattable): return self._fs._available() def serialize_number(self): - return self._number + return {'number': self._number} @property def _number(self): @@ -1021,7 +1022,7 @@ class Raid(_Device): # Surprisingly, the order of devices passed to mdadm --create # matters (see get_raid_size) so we sort devices here the same # way get_raid_size does. - return [d.id for d in raid_device_sort(self.devices)] + return {'devices': [d.id for d in raid_device_sort(self.devices)]} spare_devices = attributes.reflist( backlink="_constructed_device", default=attr.Factory(set)) @@ -1161,7 +1162,7 @@ class LVM_LogicalVolume(_Formattable): preserve = attr.ib(default=False) def serialize_size(self): - return "{}B".format(self.size) + return {'size': "{}B".format(self.size)} def available(self): if self._constructed_device is not None: @@ -1209,6 +1210,16 @@ class DM_Crypt: volume = attributes.ref(backlink="_constructed_device") # _Formattable key = attr.ib(metadata={'redact': True}) + def serialize_key(self): + if self.key: + f = tempfile.NamedTemporaryFile( + prefix='luks-key-', mode='w', delete=False) + f.write(self.key) + f.close() + return {'keyfile': f.name} + else: + return {} + dm_name = attr.ib(default=None) preserve = attr.ib(default=False) diff --git a/subiquity/models/keyboard.py b/subiquity/models/keyboard.py index 0e6649db..288c2359 100644 --- a/subiquity/models/keyboard.py +++ b/subiquity/models/keyboard.py @@ -135,7 +135,7 @@ class KeyboardSetting: # Non-latin keyboard layouts that are handled in a uniform way standard_non_latin_layouts = set( ('af', 'am', 'ara', 'ben', 'bd', 'bg', 'bt', 'by', 'et', 'ge', - 'gh', 'gr', 'guj', 'guru', 'il', ''in'', 'iq', 'ir', 'iku', 'kan', + 'gh', 'gr', 'guj', 'guru', 'il', 'in', 'iq', 'ir', 'iku', 'kan', 'kh', 'kz', 'la', 'lao', 'lk', 'kg', 'ma', 'mk', 'mm', 'mn', 'mv', 'mal', 'np', 'ori', 'pk', 'ru', 'scc', 'sy', 'syr', 'tel', 'th', 'tj', 'tam', 'tib', 'ua', 'ug', 'uz') diff --git a/subiquity/models/subiquity.py b/subiquity/models/subiquity.py index 7ed94c3f..43814794 100644 --- a/subiquity/models/subiquity.py +++ b/subiquity/models/subiquity.py @@ -149,9 +149,10 @@ class SubiquityModel: 'mode': 'off', }, 'locale': self.locale.selected_language + '.UTF-8', - 'preserve_hostname': True, 'resize_rootfs': False, } + if self.identity.hostname is not None: + config['preserve_hostname'] = True user = self.identity.user if user: users_and_groups_path = ( diff --git a/subiquity/ui/views/filesystem/guided.py b/subiquity/ui/views/filesystem/guided.py index 1457ae50..9721b2be 100644 --- a/subiquity/ui/views/filesystem/guided.py +++ b/subiquity/ui/views/filesystem/guided.py @@ -86,7 +86,7 @@ class GuidedChoiceForm(SubForm): lvm_options = SubFormField(LVMOptionsForm, "", help=NO_HELP) def __init__(self, parent): - super().__init__(parent) + super().__init__(parent, initial={'use_lvm': True}) options = [] tables = [] initial = -1 diff --git a/subiquitycore/controllers/network.py b/subiquitycore/controllers/network.py index c540856c..94524458 100644 --- a/subiquitycore/controllers/network.py +++ b/subiquitycore/controllers/network.py @@ -29,7 +29,6 @@ from subiquitycore.file_util import write_file from subiquitycore.models.network import ( BondParameters, NetDevAction, - sanitize_config, ) from subiquitycore import netplan from subiquitycore.ui.views.network import ( @@ -342,7 +341,9 @@ class NetworkController(BaseController): config = self.model.render_config() log.debug("network config: \n%s", - yaml.dump(sanitize_config(config), default_flow_style=False)) + yaml.dump( + netplan.sanitize_config(config), + default_flow_style=False)) for p in netplan.configs_in_root(self.root, masked=True): if p == self.netplan_path: diff --git a/subiquitycore/models/network.py b/subiquitycore/models/network.py index 001d85d7..6fb60556 100644 --- a/subiquitycore/models/network.py +++ b/subiquitycore/models/network.py @@ -13,7 +13,6 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . -import copy import enum import ipaddress import logging @@ -49,27 +48,6 @@ class NetDevAction(enum.Enum): return pgettext(type(self).__name__, self.value) -def _sanitize_inteface_config(iface_config): - for ap, ap_config in iface_config.get('access-points', {}).items(): - if 'password' in ap_config: - ap_config['password'] = '' - - -def sanitize_interface_config(iface_config): - iface_config = copy.deepcopy(iface_config) - _sanitize_inteface_config(iface_config) - return iface_config - - -def sanitize_config(config): - """Return a copy of config with passwords redacted.""" - config = copy.deepcopy(config) - interfaces = config.get('network', {}).get('wifis', {}).items() - for iface, iface_config in interfaces: - _sanitize_inteface_config(iface_config) - return config - - class BondParameters: # Just a place to hang various data about how bonds can be # configured. @@ -176,6 +154,21 @@ class NetworkDev(object): def supports_action(self, action): return getattr(self, "_supports_" + action.name) + @property + def configured_ssid(self): + for ssid, settings in self.config.get('access-points', {}).items(): + psk = settings.get('password') + return ssid, psk + return None, None + + def set_ssid_psk(self, ssid, psk): + aps = self.config.setdefault('access-points', {}) + aps.clear() + if ssid is not None: + aps[ssid] = {} + if psk is not None: + aps[ssid]['password'] = psk + @property def ifindex(self): if self.info is not None: @@ -298,7 +291,7 @@ class NetworkModel(object): dev.config = config log.debug("new_link %s %s with config %s", ifindex, link.name, - sanitize_interface_config(dev.config)) + netplan.sanitize_interface_config(dev.config)) self.devices_by_name[link.name] = dev return dev diff --git a/subiquitycore/netplan.py b/subiquitycore/netplan.py index aac23143..ed42a07b 100644 --- a/subiquitycore/netplan.py +++ b/subiquitycore/netplan.py @@ -8,6 +8,27 @@ import yaml log = logging.getLogger("subiquitycore.netplan") +def _sanitize_inteface_config(iface_config): + for ap, ap_config in iface_config.get('access-points', {}).items(): + if 'password' in ap_config: + ap_config['password'] = '' + + +def sanitize_interface_config(iface_config): + iface_config = copy.deepcopy(iface_config) + _sanitize_inteface_config(iface_config) + return iface_config + + +def sanitize_config(config): + """Return a copy of config with passwords redacted.""" + config = copy.deepcopy(config) + interfaces = config.get('network', {}).get('wifis', {}).items() + for iface, iface_config in interfaces: + _sanitize_inteface_config(iface_config) + return config + + class Config: """A NetplanConfig represents the network config for a system. @@ -85,7 +106,9 @@ class _PhysicalDevice: self.match_mac = match.get('macaddress') self.match_driver = match.get('driver') self.config = config - log.debug("config for %s = %s" % (name, self.config)) + log.debug( + "config for %s = %s" % ( + name, sanitize_interface_config(self.config))) def matches_link(self, link): if self.match_name is not None: @@ -107,7 +130,9 @@ class _VirtualDevice: def __init__(self, name, config): self.name = name self.config = config - log.debug("config for %s = %s" % (name, self.config)) + log.debug( + "config for %s = %s" % ( + name, sanitize_interface_config(self.config))) def configs_in_root(root, masked=False): diff --git a/subiquitycore/ssh.py b/subiquitycore/ssh.py index a312e693..fa3a5eab 100644 --- a/subiquitycore/ssh.py +++ b/subiquitycore/ssh.py @@ -84,10 +84,10 @@ def get_ips_standalone(): prober.probe_network() links = prober.get_results()['network']['links'] ips = [] - for l in sorted(links, key=lambda l: l['netlink_data']['name']): - if l['type'] in NETDEV_IGNORED_IFACE_TYPES: + for link in sorted(links, key=lambda link: link['netlink_data']['name']): + if link['type'] in NETDEV_IGNORED_IFACE_TYPES: continue - for addr in l['addresses']: + for addr in link['addresses']: if addr['scope'] == "global": ips.append(addr['address'].split('/')[0]) return ips diff --git a/subiquitycore/ui/form.py b/subiquitycore/ui/form.py index c1153ee5..0384aad4 100644 --- a/subiquitycore/ui/form.py +++ b/subiquitycore/ui/form.py @@ -555,6 +555,6 @@ class SubFormField(FormField): class SubForm(Form): - def __init__(self, parent): + def __init__(self, parent, **kw): self.parent = parent - super().__init__() + super().__init__(**kw) diff --git a/subiquitycore/ui/views/network_configure_wlan_interface.py b/subiquitycore/ui/views/network_configure_wlan_interface.py index 66b1e6ed..69a2bdbd 100644 --- a/subiquitycore/ui/views/network_configure_wlan_interface.py +++ b/subiquitycore/ui/views/network_configure_wlan_interface.py @@ -74,10 +74,11 @@ class NetworkConfigureWLANStretchy(Stretchy): connect_signal(self.form, 'submit', self.done) connect_signal(self.form, 'cancel', self.cancel) - if self.device.configured_ssid is not None: - self.form.ssid.value = self.device.configured_ssid - if self.device.configured_wifi_psk is not None: - self.form.psk.value = self.device.configured_wifi_psk + ssid, psk = self.device.configured_ssid + if ssid: + self.form.ssid.value = ssid + if psk: + self.form.psk.value = psk self.ssid_row = self.form.ssid._table self.psk_row = self.form.psk._table @@ -95,7 +96,8 @@ class NetworkConfigureWLANStretchy(Stretchy): def show_ssid_list(self, sender): self.parent.show_overlay( - NetworkList(self, self.device.actual_ssids), width=60) + NetworkList( + self, self.device.info.wlan['visible_ssids']), width=60) def start_scan(self, sender): fp = self.inputs.focus_position - 1 @@ -109,13 +111,13 @@ class NetworkConfigureWLANStretchy(Stretchy): self.error.set_text("%s" % (r,)) def _build_iface_inputs(self): - if len(self.device.actual_ssids) > 0: + if len(self.device.info.wlan['visible_ssids']) > 0: networks_btn = menu_btn("Choose a visible network", on_press=self.show_ssid_list) else: networks_btn = disabled(menu_btn("No visible networks")) - if not self.device.scan_state: + if not self.device.info.wlan['scan_state']: scan_btn = menu_btn("Scan for networks", on_press=self.start_scan) else: scan_btn = disabled(menu_btn("Scanning for networks")) @@ -146,7 +148,7 @@ class NetworkConfigureWLANStretchy(Stretchy): for obj in self._build_iface_inputs()] def done(self, sender): - if self.device.configured_ssid is None and self.form.ssid.value: + if self.device.configured_ssid[0] is None and self.form.ssid.value: # Turn DHCP4 on by default when specifying an SSID for # the first time... self.device.dhcp4 = True