class IntegrationForm(Form): def __init__(self, initial): super().__init__(initial=initial) custom_path = MountField(_("Mount Location"), help=_("Location for the automount")) custom_mount_opt = StringField( _("Mount Option"), help=_("Mount option passed for the automount")) gen_host = BooleanField( _("Enable Host Generation"), help=_("Selecting enables /etc/host re-generation at every start")) gen_resolvconf = BooleanField( _("Enable resolv.conf Generation"), help=_( "Selecting enables /etc/resolv.conf re-generation at every start")) gui_integration = BooleanField( _("GUI Integration"), help= _("Selecting enables automatic DISPLAY environment set-up. Third-party X server required." )) def validate_custom_path(self): p = self.custom_path.value if p != "" and (re.fullmatch(r"(/[^/ ]*)+/?", p) is None): return _( "Mount location must be a absolute UNIX path without space.") def validate_custom_mount_opt(self): o = self.custom_mount_opt.value # filesystem independent mount option fsimo = [ r"async", r"(no)?atime", r"(no)?auto", r"(fs|def|root)?context=\w+", r"(no)?dev", r"(no)?diratime", r"dirsync", r"(no)?exec", r"group", r"(no)?iversion", r"(no)?mand", r"_netdev", r"nofail", r"(no)?relatime", r"(no)?strictatime", r"(no)?suid", r"owner", r"remount", r"ro", r"rw", r"_rnetdev", r"sync", r"(no)?user", r"users" ] # DrvFs filesystem mount option drvfsmo = r"case=(dir|force|off)|metadata|(u|g)id=\d+|(u|f|d)mask=\d+|" fso = "{0}{1}".format(drvfsmo, '|'.join(fsimo)) if o != "": e_t = "" p = o.split(',') x = True for i in p: if i == "": e_t += _("an empty entry detected; ") x = x and False elif re.fullmatch(fso, i) is not None: x = x and True else: e_t += _("{} is not a valid mount option; ").format(i) x = x and False if not x: return _( "Invalid Input: {}Please check " "https://docs.microsoft.com/en-us/windows/wsl/wsl-config#mount-options " "for correct valid input").format(e_t)
class GuidedChoiceForm(SubForm): disk = ChoiceField(caption=NO_CAPTION, help=NO_HELP, choices=["x"]) use_lvm = BooleanField(_("Set up this disk as an LVM group"), help=NO_HELP) lvm_options = SubFormField(LVMOptionsForm, "", help=NO_HELP) def __init__(self, parent): super().__init__(parent) options = [] tables = [] initial = -1 for disk in parent.model.all_disks(): for obj, cells in summarize_device(disk): table = TablePile([TableRow(cells)]) tables.append(table) enabled = False if obj is disk and disk.size > 6 * (2**30): enabled = True if initial < 0: initial = len(options) options.append(Option((table, enabled, obj))) t0 = tables[0] for t in tables[1:]: t0.bind(t) self.disk.widget.options = options self.disk.widget.index = initial connect_signal(self.use_lvm.widget, 'change', self._toggle) self.lvm_options.enabled = self.use_lvm.value def _toggle(self, sender, val): self.lvm_options.enabled = val
class LVMOptionsForm(SubForm): def __init__(self, parent): super().__init__(parent) connect_signal(self.encrypt.widget, 'change', self._toggle) self.luks_options.enabled = self.encrypt.value def _toggle(self, sender, val): self.luks_options.enabled = val encrypt = BooleanField(_("Encrypt the LVM group with LUKS"), help=NO_HELP) luks_options = SubFormField(LUKSOptionsForm, "", help=NO_HELP)
class VolGroupForm(CompoundDiskForm): def __init__(self, model, possible_components, initial, vg_names, deleted_vg_names): self.vg_names = vg_names self.deleted_vg_names = deleted_vg_names super().__init__(model, possible_components, initial) connect_signal(self.encrypt.widget, 'change', self._change_encrypt) setup_password_validation(self, _("passphrases")) self._change_encrypt(None, self.encrypt.value) name = VGNameField(_("Name:")) devices = MultiDeviceField(_("Devices:")) size = ReadOnlyField(_("Size:")) encrypt = BooleanField(_("Create encrypted volume")) password = PasswordField(_("Passphrase:")) confirm_password = PasswordField(_("Confirm passphrase:")) def _change_encrypt(self, sender, new_value): self.password.enabled = new_value self.confirm_password.enabled = new_value if not new_value: self.password.validate() self.confirm_password.validate() def validate_devices(self): if len(self.devices.value) < 1: return _("Select at least one device to be part of the volume " "group.") def validate_name(self): v = self.name.value if not v: return _("The name of a volume group cannot be empty") if v.startswith('-'): return _("The name of a volume group cannot start with a hyphen") if v in self.vg_names: return _("There is already a volume group named '{name}'").format( name=self.name.value) if v in ('.', '..', 'md') or os.path.exists('/dev/' + v): if v not in self.deleted_vg_names: return _("{name} is not a valid name for a volume " "group").format(name=v) def validate_password(self): if self.encrypt.value and len(self.password.value) < 1: return _("Passphrase must be set") def validate_confirm_password(self): if self.encrypt.value and \ self.password.value != self.confirm_password.value: return _("Passphrases do not match")
class IdentityForm(Form): def __init__(self, reserved_usernames, initial): self.reserved_usernames = reserved_usernames super().__init__(initial=initial) username = UsernameField( _("Pick a username:"******"The username does not need to match your Windows username")) password = PasswordField(_("Choose a password:"******"Confirm your password:"******"Show Advanced Options Next Page")) def validate_username(self): username = self.username.value if len(username) < 1: return _("Username missing") if len(username) > USERNAME_MAXLEN: return _("Username too long, must be less than {limit}").format( limit=USERNAME_MAXLEN) if not re.match(r'[a-z_][a-z0-9_-]*', username): return _("Username must match NAME_REGEX, i.e. [a-z_][a-z0-9_-]*") if username in self.reserved_usernames: return _( 'The username "{username}" is reserved for use by the system.' ).format(username=username) def validate_password(self): if len(self.password.value) < 1: return _("Password must be set") def validate_confirm_password(self): if self.password.value != self.confirm_password.value: return _("Passwords do not match") def validate_show_advanced(self): pass
class PartitionForm(Form): def __init__(self, model, max_size, initial, lvm_names, device): self.model = model self.device = device self.existing_fs_type = None if device: ofstype = device.original_fstype() if ofstype: self.existing_fs_type = ofstype initial_path = initial.get('mount') self.mountpoints = { m.path: m.device.volume for m in self.model.all_mounts() if m.path != initial_path} self.max_size = max_size if max_size is not None: self.size_str = humanize_size(max_size) self.size.caption = _("Size (max {}):").format(self.size_str) self.lvm_names = lvm_names super().__init__(initial) if max_size is None: self.remove_field('size') connect_signal(self.fstype.widget, 'select', self.select_fstype) self.form_pile = None self.select_fstype(None, self.fstype.widget.value) def select_fstype(self, sender, fstype): show_use = False if fstype is None and self.existing_fs_type is not None: self.mount.widget.disable_unsuitable_mountpoints_for_existing_fs() self.mount.value = self.mount.value else: self.mount.widget.enable_common_mountpoints() self.mount.value = self.mount.value if fstype is None: if self.existing_fs_type == "swap": show_use = True if self.form_pile is not None: for i, (w, o) in enumerate(self.form_pile.contents): if w is self.mount._table and show_use: self.form_pile.contents[i] = (self.use_swap._table, o) elif w is self.use_swap._table and not show_use: self.form_pile.contents[i] = (self.mount._table, o) if getattr(self.device, 'flag', None) != "boot": fstype_for_check = fstype if fstype_for_check is None: fstype_for_check = self.existing_fs_type self.mount.enabled = self.model.is_mounted_filesystem( fstype_for_check) self.fstype.value = fstype self.mount.showing_extra = False self.mount.validate() name = LVNameField(_("Name: ")) size = SizeField() fstype = FSTypeField(_("Format:")) mount = MountField(_("Mount:")) use_swap = BooleanField( _("Use as swap"), help=_("Use this swap partition in the installed system.")) def clean_size(self, val): if not val: return self.max_size suffixes = ''.join(HUMAN_UNITS) + ''.join(HUMAN_UNITS).lower() if val[-1] not in suffixes: val += self.size_str[-1] if val == self.size_str: return self.max_size else: return dehumanize_size(val) def clean_mount(self, val): if self.model.is_mounted_filesystem(self.fstype): return val else: return None def validate_name(self): if self.lvm_names is None: return None v = self.name.value if not v: return _("The name of a logical volume cannot be empty") if v.startswith('-'): return _("The name of a logical volume cannot start with a hyphen") if v in ('.', '..', 'snapshot', 'pvmove'): return _("A logical volume may not be called {}").format(v) for substring in ['_cdata', '_cmeta', '_corig', '_mlog', '_mimage', '_pmspare', '_rimage', '_rmeta', '_tdata', '_tmeta', '_vorigin']: if substring in v: return _('The name of a logical volume may not contain ' '"{}"').format(substring) if v in self.lvm_names: return _("There is already a logical volume named {}.").format( self.name.value) def validate_mount(self): mount = self.mount.value if mount is None: return # /usr/include/linux/limits.h:PATH_MAX if len(mount) > 4095: return _('Path exceeds PATH_MAX') dev = self.mountpoints.get(mount) if dev is not None: return _("{} is already mounted at {}.").format( dev.label.title(), mount) if self.existing_fs_type is not None: if self.fstype.value is None: if mount in common_mountpoints: if mount not in suitable_mountpoints_for_existing_fs: self.mount.show_extra( ('info_error', _("Mounting an existing filesystem at {} is " "usually a bad idea, proceed only with " "caution.").format(mount))) def as_rows(self): r = super().as_rows() if self.existing_fs_type == "swap": exclude = self.mount._table else: exclude = self.use_swap._table i = r.index(exclude) del r[i-1:i+1] return r
class SSHForm(Form): install_server = BooleanField(_("Install OpenSSH server")) ssh_import_id = ChoiceField( _("Import SSH identity:"), choices=[ (_("No"), True, None), (_("from GitHub"), True, "gh"), (_("from Launchpad"), True, "lp"), ], help=_("You can import your SSH keys from GitHub or Launchpad.")) import_username = UsernameField(_ssh_import_data[None]['caption']) pwauth = BooleanField(_("Allow password authentication over SSH")) cancel_label = _("Back") def __init__(self, initial): super().__init__(initial=initial) connect_signal( self.install_server.widget, 'change', self._toggle_server) self._toggle_server(None, self.install_server.value) def _toggle_server(self, sender, new_value): if new_value: self.ssh_import_id.enabled = True self.import_username.enabled = self.ssh_import_id.value is not None self.pwauth.enabled = self.ssh_import_id.value is not None else: self.ssh_import_id.enabled = False self.import_username.enabled = False self.pwauth.enabled = False # validation of the import username does not read from # ssh_import_id.value because it is sometimes done from the # 'select' signal of the import id selector, which is called # before the import id selector's value has actually changed. so # the signal handler stuffs the value here before doing # validation (yes, this is a hack). ssh_import_id_value = None def validate_import_username(self): if self.ssh_import_id_value is None: return username = self.import_username.value if len(username) == 0: return _("This field must not be blank.") if len(username) > SSH_IMPORT_MAXLEN: return _("SSH id too long, must be < ") + str(SSH_IMPORT_MAXLEN) if self.ssh_import_id_value == 'lp': lp_regex = r"^[a-z0-9][a-z0-9\+\.\-]*$" if not re.match(lp_regex, self.import_username.value): return _("A Launchpad username must start with a letter or " "number. All letters must be lower-case. The " "characters +, - and . are also allowed after " "the first character.""") elif self.ssh_import_id_value == 'gh': if not re.match(r'^[a-zA-Z0-9\-]+$', username): return _("A GitHub username may only contain alphanumeric " "characters or single hyphens, and cannot begin or " "end with a hyphen.")