Initial upload of HyprArch releng configuration
This commit is contained in:
@@ -0,0 +1,27 @@
|
||||
# SPDX-FileCopyrightText: 2020 Adriaan de Groot <groot@kde.org>
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
---
|
||||
$schema: https://json-schema.org/schema#
|
||||
$id: https://calamares.io/schemas/bootloader
|
||||
additionalProperties: false
|
||||
type: object
|
||||
properties:
|
||||
efiBootLoaderVar: { type: string }
|
||||
efiBootLoader: { type: string }
|
||||
kernelSearchPath: { type: string }
|
||||
kernelName: { type: string }
|
||||
kernelParams: { type: array, items: { type: string } }
|
||||
kernelPattern: { type: string }
|
||||
loaderEntries: { type: array, items: { type: string } }
|
||||
refindKernelList: { type: array, items: { type: string } }
|
||||
|
||||
# Programs
|
||||
grubInstall: { type: string }
|
||||
grubMkconfig: { type: string }
|
||||
grubCfg: { type: string }
|
||||
grubProbe: { type: string }
|
||||
efiBootMgr: { type: string }
|
||||
|
||||
efiBootloaderId: { type: string }
|
||||
installEFIFallback: { type: boolean }
|
||||
installHybridGRUB: { type: boolean }
|
||||
966
airootfs/usr/lib/calamares/modules/bootloader/main.py
Normal file
966
airootfs/usr/lib/calamares/modules/bootloader/main.py
Normal file
@@ -0,0 +1,966 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# === This file is part of Calamares - <https://calamares.io> ===
|
||||
#
|
||||
# SPDX-FileCopyrightText: 2014 Aurélien Gâteau <agateau@kde.org>
|
||||
# SPDX-FileCopyrightText: 2014 Anke Boersma <demm@kaosx.us>
|
||||
# SPDX-FileCopyrightText: 2014 Daniel Hillenbrand <codeworkx@bbqlinux.org>
|
||||
# SPDX-FileCopyrightText: 2014 Benjamin Vaudour <benjamin.vaudour@yahoo.fr>
|
||||
# SPDX-FileCopyrightText: 2014-2019 Kevin Kofler <kevin.kofler@chello.at>
|
||||
# SPDX-FileCopyrightText: 2015-2018 Philip Mueller <philm@manjaro.org>
|
||||
# SPDX-FileCopyrightText: 2016-2017 Teo Mrnjavac <teo@kde.org>
|
||||
# SPDX-FileCopyrightText: 2017 Alf Gaida <agaida@siduction.org>
|
||||
# SPDX-FileCopyrightText: 2017-2019 Adriaan de Groot <groot@kde.org>
|
||||
# SPDX-FileCopyrightText: 2017 Gabriel Craciunescu <crazy@frugalware.org>
|
||||
# SPDX-FileCopyrightText: 2017 Ben Green <Bezzy1999@hotmail.com>
|
||||
# SPDX-FileCopyrightText: 2021 Neal Gompa <ngompa13@gmail.com>
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#
|
||||
# Calamares is Free Software: see the License-Identifier above.
|
||||
#
|
||||
|
||||
import fileinput
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
|
||||
import libcalamares
|
||||
|
||||
from libcalamares.utils import check_target_env_call, check_target_env_output
|
||||
|
||||
import gettext
|
||||
|
||||
_ = gettext.translation("calamares-python",
|
||||
localedir=libcalamares.utils.gettext_path(),
|
||||
languages=libcalamares.utils.gettext_languages(),
|
||||
fallback=True).gettext
|
||||
|
||||
# This is the sanitizer used all over to tidy up filenames
|
||||
# to make identifiers (or to clean up names to make filenames).
|
||||
file_name_sanitizer = str.maketrans(" /()", "_-__")
|
||||
|
||||
|
||||
def pretty_name():
|
||||
return _("Install bootloader.")
|
||||
|
||||
|
||||
def get_uuid():
|
||||
"""
|
||||
Checks and passes 'uuid' to other routine.
|
||||
|
||||
:return:
|
||||
"""
|
||||
partitions = libcalamares.globalstorage.value("partitions")
|
||||
|
||||
for partition in partitions:
|
||||
if partition["mountPoint"] == "/":
|
||||
libcalamares.utils.debug("Root partition uuid: \"{!s}\"".format(partition["uuid"]))
|
||||
return partition["uuid"]
|
||||
|
||||
return ""
|
||||
|
||||
|
||||
def get_kernel_line(kernel_type):
|
||||
"""
|
||||
Passes 'kernel_line' to other routine based on configuration file.
|
||||
|
||||
:param kernel_type:
|
||||
:return:
|
||||
"""
|
||||
if kernel_type == "fallback":
|
||||
if "fallbackKernelLine" in libcalamares.job.configuration:
|
||||
return libcalamares.job.configuration["fallbackKernelLine"]
|
||||
else:
|
||||
return " (fallback)"
|
||||
else:
|
||||
if "kernelLine" in libcalamares.job.configuration:
|
||||
return libcalamares.job.configuration["kernelLine"]
|
||||
else:
|
||||
return ""
|
||||
|
||||
|
||||
def get_zfs_root():
|
||||
"""
|
||||
Looks in global storage to find the zfs root
|
||||
|
||||
:return: A string containing the path to the zfs root or None if it is not found
|
||||
"""
|
||||
|
||||
zfs = libcalamares.globalstorage.value("zfsDatasets")
|
||||
|
||||
if not zfs:
|
||||
libcalamares.utils.warning("Failed to locate zfs dataset list")
|
||||
return None
|
||||
|
||||
# Find the root dataset
|
||||
for dataset in zfs:
|
||||
try:
|
||||
if dataset["mountpoint"] == "/":
|
||||
return dataset["zpool"] + "/" + dataset["dsName"]
|
||||
except KeyError:
|
||||
# This should be impossible
|
||||
libcalamares.utils.warning("Internal error handling zfs dataset")
|
||||
raise
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def is_btrfs_root(partition):
|
||||
""" Returns True if the partition object refers to a btrfs root filesystem
|
||||
|
||||
:param partition: A partition map from global storage
|
||||
:return: True if btrfs and root, False otherwise
|
||||
"""
|
||||
return partition["mountPoint"] == "/" and partition["fs"] == "btrfs"
|
||||
|
||||
|
||||
def is_zfs_root(partition):
|
||||
""" Returns True if the partition object refers to a zfs root filesystem
|
||||
|
||||
:param partition: A partition map from global storage
|
||||
:return: True if zfs and root, False otherwise
|
||||
"""
|
||||
return partition["mountPoint"] == "/" and partition["fs"] == "zfs"
|
||||
|
||||
|
||||
def have_program_in_target(program : str):
|
||||
"""Returns @c True if @p program is in path in the target"""
|
||||
return libcalamares.utils.target_env_call(["/usr/bin/which", program]) == 0
|
||||
|
||||
|
||||
def get_kernel_params(uuid):
|
||||
# Configured kernel parameters (default "quiet"), if plymouth installed, add splash
|
||||
# screen parameter and then "rw".
|
||||
kernel_params = libcalamares.job.configuration.get("kernelParams", ["quiet"])
|
||||
if have_program_in_target("plymouth"):
|
||||
kernel_params.append("splash")
|
||||
kernel_params.append("rw")
|
||||
|
||||
use_systemd_naming = have_program_in_target("dracut") or (libcalamares.utils.target_env_call(["/usr/bin/grep", "-q", "^HOOKS.*systemd", "/etc/mkinitcpio.conf"]) == 0)
|
||||
|
||||
partitions = libcalamares.globalstorage.value("partitions")
|
||||
|
||||
cryptdevice_params = []
|
||||
swap_uuid = ""
|
||||
swap_outer_mappername = None
|
||||
swap_outer_uuid = None
|
||||
|
||||
# Take over swap settings:
|
||||
# - unencrypted swap partition sets swap_uuid
|
||||
# - encrypted root sets cryptdevice_params
|
||||
for partition in partitions:
|
||||
if partition["fs"] == "linuxswap" and not partition.get("claimed", None):
|
||||
# Skip foreign swap
|
||||
continue
|
||||
has_luks = "luksMapperName" in partition
|
||||
if partition["fs"] == "linuxswap" and not has_luks:
|
||||
swap_uuid = partition["uuid"]
|
||||
|
||||
if partition["fs"] == "linuxswap" and has_luks:
|
||||
swap_outer_mappername = partition["luksMapperName"]
|
||||
swap_outer_uuid = partition["luksUuid"]
|
||||
|
||||
if partition["mountPoint"] == "/" and has_luks:
|
||||
if use_systemd_naming:
|
||||
cryptdevice_params = [f"rd.luks.uuid={partition['luksUuid']}"]
|
||||
else:
|
||||
cryptdevice_params = [f"cryptdevice=UUID={partition['luksUuid']}:{partition['luksMapperName']}"]
|
||||
cryptdevice_params.append(f"root=/dev/mapper/{partition['luksMapperName']}")
|
||||
|
||||
# btrfs and zfs handling
|
||||
for partition in partitions:
|
||||
# If a btrfs root subvolume wasn't set, it means the root is directly on the partition
|
||||
# and this option isn't needed
|
||||
if is_btrfs_root(partition):
|
||||
btrfs_root_subvolume = libcalamares.globalstorage.value("btrfsRootSubvolume")
|
||||
if btrfs_root_subvolume:
|
||||
kernel_params.append("rootflags=subvol=" + btrfs_root_subvolume)
|
||||
|
||||
# zfs needs to be told the location of the root dataset
|
||||
if is_zfs_root(partition):
|
||||
zfs_root_path = get_zfs_root()
|
||||
if zfs_root_path is not None:
|
||||
kernel_params.append("root=ZFS=" + zfs_root_path)
|
||||
else:
|
||||
# Something is really broken if we get to this point
|
||||
libcalamares.utils.warning("Internal error handling zfs dataset")
|
||||
raise Exception("Internal zfs data missing, please contact your distribution")
|
||||
|
||||
if cryptdevice_params:
|
||||
kernel_params.extend(cryptdevice_params)
|
||||
else:
|
||||
kernel_params.append("root=UUID={!s}".format(uuid))
|
||||
|
||||
if swap_uuid:
|
||||
kernel_params.append("resume=UUID={!s}".format(swap_uuid))
|
||||
|
||||
if use_systemd_naming and swap_outer_uuid:
|
||||
kernel_params.append(f"rd.luks.uuid={swap_outer_uuid}")
|
||||
|
||||
if swap_outer_mappername:
|
||||
kernel_params.append(f"resume=/dev/mapper/{swap_outer_mappername}")
|
||||
|
||||
return kernel_params
|
||||
|
||||
|
||||
def create_systemd_boot_conf(installation_root_path, efi_dir, uuid, kernel, kernel_version):
|
||||
"""
|
||||
Creates systemd-boot configuration files based on given parameters.
|
||||
|
||||
:param installation_root_path: A string containing the absolute path to the root of the installation
|
||||
:param efi_dir: A string containing the path to the efi dir relative to the root of the installation
|
||||
:param uuid: A string containing the UUID of the root volume
|
||||
:param kernel: A string containing the path to the kernel relative to the root of the installation
|
||||
:param kernel_version: The kernel version string
|
||||
"""
|
||||
|
||||
# Get the kernel params and write them to /etc/kernel/cmdline
|
||||
# This file is used by kernel-install
|
||||
kernel_params = " ".join(get_kernel_params(uuid))
|
||||
kernel_cmdline_path = os.path.join(installation_root_path, "etc", "kernel")
|
||||
os.makedirs(kernel_cmdline_path, exist_ok=True)
|
||||
with open(os.path.join(kernel_cmdline_path, "cmdline"), "w") as cmdline_file:
|
||||
cmdline_file.write(kernel_params)
|
||||
|
||||
libcalamares.utils.debug(f"Configuring kernel version {kernel_version}")
|
||||
|
||||
# get the machine-id
|
||||
with open(os.path.join(installation_root_path, "etc", "machine-id"), 'r') as machineid_file:
|
||||
machine_id = machineid_file.read().rstrip('\n')
|
||||
|
||||
# Ensure the directory exists
|
||||
machine_dir = os.path.join(installation_root_path + efi_dir, machine_id)
|
||||
os.makedirs(machine_dir, exist_ok=True)
|
||||
|
||||
# Call kernel-install for each kernel
|
||||
libcalamares.utils.target_env_process_output(["kernel-install",
|
||||
"add",
|
||||
kernel_version,
|
||||
os.path.join("/", kernel)])
|
||||
|
||||
|
||||
def create_loader(loader_path, installation_root_path):
|
||||
"""
|
||||
Writes configuration for loader.
|
||||
|
||||
:param loader_path: The absolute path to the loader.conf file
|
||||
:param installation_root_path: The path to the root of the target installation
|
||||
"""
|
||||
|
||||
# get the machine-id
|
||||
with open(os.path.join(installation_root_path, "etc", "machine-id"), 'r') as machineid_file:
|
||||
machine_id = machineid_file.read().rstrip('\n')
|
||||
|
||||
try:
|
||||
loader_entries = libcalamares.job.configuration["loaderEntries"]
|
||||
except KeyError:
|
||||
libcalamares.utils.debug("No aditional loader entries found in config")
|
||||
loader_entries = []
|
||||
pass
|
||||
|
||||
lines = [f"default {machine_id}*"]
|
||||
|
||||
lines.extend(loader_entries)
|
||||
|
||||
with open(loader_path, 'w') as loader_file:
|
||||
for line in lines:
|
||||
loader_file.write(line + "\n")
|
||||
|
||||
|
||||
class SuffixIterator(object):
|
||||
"""
|
||||
Wrapper for one of the "generator" classes below to behave like
|
||||
a proper Python iterator. The iterator is initialized with a
|
||||
maximum number of attempts to generate a new suffix.
|
||||
"""
|
||||
|
||||
def __init__(self, attempts, generator):
|
||||
self.generator = generator
|
||||
self.attempts = attempts
|
||||
self.counter = 0
|
||||
|
||||
def __iter__(self):
|
||||
return self
|
||||
|
||||
def __next__(self):
|
||||
self.counter += 1
|
||||
if self.counter <= self.attempts:
|
||||
return self.generator.next()
|
||||
raise StopIteration
|
||||
|
||||
|
||||
class serialEfi(object):
|
||||
"""
|
||||
EFI Id generator that appends a serial number to the given name.
|
||||
"""
|
||||
|
||||
def __init__(self, name):
|
||||
self.name = name
|
||||
# So the first call to next() will bump it to 0
|
||||
self.counter = -1
|
||||
|
||||
def next(self):
|
||||
self.counter += 1
|
||||
if self.counter > 0:
|
||||
return "{!s}{!s}".format(self.name, self.counter)
|
||||
else:
|
||||
return self.name
|
||||
|
||||
|
||||
def render_in_base(value, base_values, length=-1):
|
||||
"""
|
||||
Renders @p value in base-N, where N is the number of
|
||||
items in @p base_values. When rendering, use the items
|
||||
of @p base_values (e.g. use "0123456789" to get regular decimal
|
||||
rendering, or "ABCDEFGHIJ" for letters-as-numbers 'encoding').
|
||||
|
||||
If length is positive, pads out to at least that long with
|
||||
leading "zeroes", whatever base_values[0] is.
|
||||
"""
|
||||
if value < 0:
|
||||
raise ValueError("Cannot render negative values")
|
||||
if len(base_values) < 2:
|
||||
raise ValueError("Insufficient items for base-N rendering")
|
||||
if length < 1:
|
||||
length = 1
|
||||
digits = []
|
||||
base = len(base_values)
|
||||
while value > 0:
|
||||
place = value % base
|
||||
value = value // base
|
||||
digits.append(base_values[place])
|
||||
while len(digits) < length:
|
||||
digits.append(base_values[0])
|
||||
return "".join(reversed(digits))
|
||||
|
||||
|
||||
class randomEfi(object):
|
||||
"""
|
||||
EFI Id generator that appends a random 4-digit hex number to the given name.
|
||||
"""
|
||||
|
||||
def __init__(self, name):
|
||||
self.name = name
|
||||
# So the first call to next() will bump it to 0
|
||||
self.counter = -1
|
||||
|
||||
def next(self):
|
||||
self.counter += 1
|
||||
if self.counter > 0:
|
||||
import random
|
||||
v = random.randint(0, 65535) # 16 bits
|
||||
return "{!s}{!s}".format(self.name, render_in_base(v, "0123456789ABCDEF", 4))
|
||||
else:
|
||||
return self.name
|
||||
|
||||
|
||||
class phraseEfi(object):
|
||||
"""
|
||||
EFI Id generator that appends a random phrase to the given name.
|
||||
"""
|
||||
words = ("Sun", "Moon", "Mars", "Soyuz", "Falcon", "Kuaizhou", "Gaganyaan")
|
||||
|
||||
def __init__(self, name):
|
||||
self.name = name
|
||||
# So the first call to next() will bump it to 0
|
||||
self.counter = -1
|
||||
|
||||
def next(self):
|
||||
self.counter += 1
|
||||
if self.counter > 0:
|
||||
import random
|
||||
desired_length = 1 + self.counter // 5
|
||||
v = random.randint(0, len(self.words) ** desired_length)
|
||||
return "{!s}{!s}".format(self.name, render_in_base(v, self.words))
|
||||
else:
|
||||
return self.name
|
||||
|
||||
|
||||
def get_efi_suffix_generator(name):
|
||||
"""
|
||||
Handle EFI bootloader Ids with ${<something>} for suffix-processing.
|
||||
"""
|
||||
if "${" not in name:
|
||||
raise ValueError("Misplaced call to get_efi_suffix_generator, no ${}")
|
||||
if not name.endswith("}"):
|
||||
raise ValueError("Misplaced call to get_efi_suffix_generator, no trailing ${}")
|
||||
if name.count("${") > 1:
|
||||
raise ValueError("EFI ID {!r} contains multiple generators".format(name))
|
||||
import re
|
||||
prefix, generator_name = re.match("(.*)\${([^}]*)}$", name).groups()
|
||||
if generator_name not in ("SERIAL", "RANDOM", "PHRASE"):
|
||||
raise ValueError("EFI suffix {!r} is unknown".format(generator_name))
|
||||
|
||||
generator = None
|
||||
if generator_name == "SERIAL":
|
||||
generator = serialEfi(prefix)
|
||||
elif generator_name == "RANDOM":
|
||||
generator = randomEfi(prefix)
|
||||
elif generator_name == "PHRASE":
|
||||
generator = phraseEfi(prefix)
|
||||
if generator is None:
|
||||
raise ValueError("EFI suffix {!r} is unsupported".format(generator_name))
|
||||
|
||||
return generator
|
||||
|
||||
|
||||
def change_efi_suffix(efi_directory, bootloader_id):
|
||||
"""
|
||||
Returns a label based on @p bootloader_id that is usable within
|
||||
@p efi_directory. If there is a ${<something>} suffix marker
|
||||
in the given id, tries to generate a unique label.
|
||||
"""
|
||||
if bootloader_id.endswith("}") and "${" in bootloader_id:
|
||||
# Do 10 attempts with any suffix generator
|
||||
g = SuffixIterator(10, get_efi_suffix_generator(bootloader_id))
|
||||
else:
|
||||
# Just one attempt
|
||||
g = [bootloader_id]
|
||||
|
||||
for candidate_name in g:
|
||||
if not os.path.exists(os.path.join(efi_directory, candidate_name)):
|
||||
return candidate_name
|
||||
return bootloader_id
|
||||
|
||||
|
||||
def efi_label(efi_directory):
|
||||
"""
|
||||
Returns a sanitized label, possibly unique, that can be
|
||||
used within @p efi_directory.
|
||||
"""
|
||||
if "efiBootloaderId" in libcalamares.job.configuration:
|
||||
efi_bootloader_id = change_efi_suffix(efi_directory, libcalamares.job.configuration["efiBootloaderId"])
|
||||
else:
|
||||
branding = libcalamares.globalstorage.value("branding")
|
||||
efi_bootloader_id = branding["bootloaderEntryName"]
|
||||
|
||||
return efi_bootloader_id.translate(file_name_sanitizer)
|
||||
|
||||
|
||||
def efi_word_size():
|
||||
# get bitness of the underlying UEFI
|
||||
try:
|
||||
sysfile = open("/sys/firmware/efi/fw_platform_size", "r")
|
||||
efi_bitness = sysfile.read(2)
|
||||
except Exception:
|
||||
# if the kernel is older than 4.0, the UEFI bitness likely isn't
|
||||
# exposed to the userspace so we assume a 64 bit UEFI here
|
||||
efi_bitness = "64"
|
||||
return efi_bitness
|
||||
|
||||
|
||||
def efi_boot_next():
|
||||
"""
|
||||
Tell EFI to definitely boot into the just-installed
|
||||
system next time.
|
||||
"""
|
||||
boot_mgr = libcalamares.job.configuration["efiBootMgr"]
|
||||
boot_entry = None
|
||||
efi_bootvars = subprocess.check_output([boot_mgr], universal_newlines=True)
|
||||
for line in efi_bootvars.split('\n'):
|
||||
if not line:
|
||||
continue
|
||||
words = line.split()
|
||||
if len(words) >= 2 and words[0] == "BootOrder:":
|
||||
boot_entry = words[1].split(',')[0]
|
||||
break
|
||||
if boot_entry:
|
||||
subprocess.call([boot_mgr, "-n", boot_entry])
|
||||
|
||||
|
||||
def get_kernels(installation_root_path):
|
||||
"""
|
||||
Gets a list of kernels and associated values for each kernel. This will work as is for many distros.
|
||||
If not, it should be safe to modify it to better support your distro
|
||||
|
||||
:param installation_root_path: A string with the absolute path to the root of the installation
|
||||
|
||||
Returns a list of 3-tuples
|
||||
|
||||
Each 3-tuple contains the kernel, kernel_type and kernel_version
|
||||
"""
|
||||
try:
|
||||
kernel_search_path = libcalamares.job.configuration["kernelSearchPath"]
|
||||
except KeyError:
|
||||
libcalamares.utils.warning("No kernel pattern found in configuration, using '/usr/lib/modules'")
|
||||
kernel_search_path = "/usr/lib/modules"
|
||||
pass
|
||||
|
||||
kernel_list = []
|
||||
|
||||
try:
|
||||
kernel_pattern = libcalamares.job.configuration["kernelPattern"]
|
||||
except KeyError:
|
||||
libcalamares.utils.warning("No kernel pattern found in configuration, using 'vmlinuz'")
|
||||
kernel_pattern = "vmlinuz"
|
||||
pass
|
||||
|
||||
# find all the installed kernels
|
||||
for root, dirs, files in os.walk(os.path.join(installation_root_path, kernel_search_path.lstrip('/'))):
|
||||
for file in files:
|
||||
if re.search(kernel_pattern, file):
|
||||
rel_root = os.path.relpath(root, installation_root_path)
|
||||
kernel_list.append((os.path.join(rel_root, file), "default", os.path.basename(root)))
|
||||
|
||||
return kernel_list
|
||||
|
||||
|
||||
def install_clr_boot_manager():
|
||||
"""
|
||||
Installs clr-boot-manager as the bootloader for EFI systems
|
||||
"""
|
||||
libcalamares.utils.debug("Bootloader: clr-boot-manager")
|
||||
|
||||
installation_root_path = libcalamares.globalstorage.value("rootMountPoint")
|
||||
kernel_config_path = os.path.join(installation_root_path, "etc", "kernel")
|
||||
os.makedirs(kernel_config_path, exist_ok=True)
|
||||
cmdline_path = os.path.join(kernel_config_path, "cmdline")
|
||||
|
||||
# Get the kernel params
|
||||
uuid = get_uuid()
|
||||
kernel_params = " ".join(get_kernel_params(uuid))
|
||||
|
||||
# Write out the cmdline file for clr-boot-manager
|
||||
with open(cmdline_path, "w") as cmdline_file:
|
||||
cmdline_file.write(kernel_params)
|
||||
|
||||
check_target_env_call(["clr-boot-manager", "update"])
|
||||
|
||||
|
||||
def install_systemd_boot(efi_directory):
|
||||
"""
|
||||
Installs systemd-boot as bootloader for EFI setups.
|
||||
|
||||
:param efi_directory:
|
||||
"""
|
||||
libcalamares.utils.debug("Bootloader: systemd-boot")
|
||||
installation_root_path = libcalamares.globalstorage.value("rootMountPoint")
|
||||
install_efi_directory = installation_root_path + efi_directory
|
||||
uuid = get_uuid()
|
||||
loader_path = os.path.join(install_efi_directory,
|
||||
"loader",
|
||||
"loader.conf")
|
||||
subprocess.call(["bootctl",
|
||||
"--path={!s}".format(install_efi_directory),
|
||||
"install"])
|
||||
|
||||
for (kernel, kernel_type, kernel_version) in get_kernels(installation_root_path):
|
||||
create_systemd_boot_conf(installation_root_path,
|
||||
efi_directory,
|
||||
uuid,
|
||||
kernel,
|
||||
kernel_version)
|
||||
|
||||
create_loader(loader_path, installation_root_path)
|
||||
|
||||
|
||||
def get_grub_efi_parameters():
|
||||
"""
|
||||
Returns a 3-tuple of suitable parameters for GRUB EFI installation,
|
||||
depending on the host machine architecture. The return is
|
||||
- target name
|
||||
- grub.efi name
|
||||
- boot.efi name
|
||||
all three are strings. May return None if there is no suitable
|
||||
set for the current machine. May return unsuitable values if the
|
||||
host architecture is unknown (e.g. defaults to x86_64).
|
||||
"""
|
||||
import platform
|
||||
efi_bitness = efi_word_size()
|
||||
cpu_type = platform.machine()
|
||||
|
||||
if efi_bitness == "32":
|
||||
# Assume all 32-bitters are legacy x86
|
||||
return "i386-efi", "grubia32.efi", "bootia32.efi"
|
||||
elif efi_bitness == "64" and cpu_type == "aarch64":
|
||||
return "arm64-efi", "grubaa64.efi", "bootaa64.efi"
|
||||
elif efi_bitness == "64" and cpu_type == "loongarch64":
|
||||
return "loongarch64-efi", "grubloongarch64.efi", "bootloongarch64.efi"
|
||||
elif efi_bitness == "64":
|
||||
# If it's not ARM, must by AMD64
|
||||
return "x86_64-efi", "grubx64.efi", "bootx64.efi"
|
||||
libcalamares.utils.warning(
|
||||
"Could not find GRUB parameters for bits {b} and cpu {c}".format(b=repr(efi_bitness), c=repr(cpu_type)))
|
||||
return None
|
||||
|
||||
|
||||
def run_grub_mkconfig(partitions, output_file):
|
||||
"""
|
||||
Runs grub-mkconfig in the target environment
|
||||
|
||||
:param partitions: The partitions list from global storage
|
||||
:param output_file: A string containing the path to the generating grub config file
|
||||
:return:
|
||||
"""
|
||||
|
||||
# zfs needs an environment variable set for grub-mkconfig
|
||||
if any([is_zfs_root(partition) for partition in partitions]):
|
||||
check_target_env_call(["sh", "-c", "ZPOOL_VDEV_NAME_PATH=1 " +
|
||||
libcalamares.job.configuration["grubMkconfig"] + " -o " + output_file])
|
||||
else:
|
||||
# The input file /etc/default/grub should already be filled out by the
|
||||
# grubcfg job module.
|
||||
check_target_env_call([libcalamares.job.configuration["grubMkconfig"], "-o", output_file])
|
||||
|
||||
|
||||
def run_grub_install(fw_type, partitions, efi_directory, install_hybrid_grub):
|
||||
"""
|
||||
Runs grub-install in the target environment
|
||||
|
||||
:param fw_type: A string which is "efi" for UEFI installs. Any other value results in a BIOS install
|
||||
:param partitions: The partitions list from global storage
|
||||
:param efi_directory: The path of the efi directory relative to the root of the install
|
||||
:return:
|
||||
"""
|
||||
|
||||
is_zfs = any([is_zfs_root(partition) for partition in partitions])
|
||||
|
||||
# zfs needs an environment variable set for grub
|
||||
if is_zfs:
|
||||
check_target_env_call(["sh", "-c", "echo ZPOOL_VDEV_NAME_PATH=1 >> /etc/environment"])
|
||||
|
||||
if fw_type == "efi":
|
||||
assert efi_directory is not None
|
||||
efi_bootloader_id = efi_label(efi_directory)
|
||||
efi_target, efi_grub_file, efi_boot_file = get_grub_efi_parameters()
|
||||
|
||||
if is_zfs:
|
||||
check_target_env_call(["sh", "-c", "ZPOOL_VDEV_NAME_PATH=1 " + libcalamares.job.configuration["grubInstall"]
|
||||
+ " --target=" + efi_target + " --efi-directory=" + efi_directory
|
||||
+ " --bootloader-id=" + efi_bootloader_id + " --force"])
|
||||
else:
|
||||
check_target_env_call([libcalamares.job.configuration["grubInstall"],
|
||||
"--target=" + efi_target,
|
||||
"--efi-directory=" + efi_directory,
|
||||
"--bootloader-id=" + efi_bootloader_id,
|
||||
"--force"])
|
||||
else:
|
||||
if efi_directory is not None and not install_hybrid_grub:
|
||||
libcalamares.utils.warning(_("Cannot install BIOS bootloader on UEFI installation when install_hybrid_grub is 'False'!"))
|
||||
return
|
||||
|
||||
if libcalamares.globalstorage.value("bootLoader") is None:
|
||||
efi_install_path = libcalamares.globalstorage.value("efiSystemPartition")
|
||||
if efi_install_path is None or efi_install_path == "":
|
||||
efi_install_path = "/boot/efi"
|
||||
find_esp_disk_command = f"lsblk -o PKNAME \"$(df --output=source '{efi_install_path}' | tail -n1)\""
|
||||
boot_loader_install_path = check_target_env_output(["sh", "-c", find_esp_disk_command]).strip()
|
||||
if not "\n" in boot_loader_install_path:
|
||||
libcalamares.utils.warning(_("Cannot find the drive containing the EFI system partition!"))
|
||||
return
|
||||
boot_loader_install_path = "/dev/" + boot_loader_install_path.split("\n")[1]
|
||||
else:
|
||||
boot_loader = libcalamares.globalstorage.value("bootLoader")
|
||||
boot_loader_install_path = boot_loader["installPath"]
|
||||
if boot_loader_install_path is None:
|
||||
return
|
||||
|
||||
# boot_loader_install_path points to the physical disk to install GRUB
|
||||
# to. It should start with "/dev/", and be at least as long as the
|
||||
# string "/dev/sda".
|
||||
if not boot_loader_install_path.startswith("/dev/") or len(boot_loader_install_path) < 8:
|
||||
raise ValueError(f"boot_loader_install_path contains unexpected value '{boot_loader_install_path}'")
|
||||
|
||||
if is_zfs:
|
||||
check_target_env_call(["sh", "-c", "ZPOOL_VDEV_NAME_PATH=1 "
|
||||
+ libcalamares.job.configuration["grubInstall"]
|
||||
+ " --target=i386-pc --recheck --force "
|
||||
+ boot_loader_install_path])
|
||||
else:
|
||||
check_target_env_call([libcalamares.job.configuration["grubInstall"],
|
||||
"--target=i386-pc",
|
||||
"--recheck",
|
||||
"--force",
|
||||
boot_loader_install_path])
|
||||
|
||||
|
||||
def install_grub(efi_directory, fw_type, install_hybrid_grub):
|
||||
"""
|
||||
Installs grub as bootloader, either in pc or efi mode.
|
||||
|
||||
:param efi_directory:
|
||||
:param fw_type:
|
||||
:param install_hybrid_grub:
|
||||
"""
|
||||
# get the partition from global storage
|
||||
partitions = libcalamares.globalstorage.value("partitions")
|
||||
if not partitions:
|
||||
libcalamares.utils.warning(_("Failed to install grub, no partitions defined in global storage"))
|
||||
return
|
||||
|
||||
if fw_type != "bios" and fw_type != "efi":
|
||||
raise ValueError("fw_type must be 'bios' or 'efi'")
|
||||
|
||||
if fw_type == "efi" or install_hybrid_grub:
|
||||
libcalamares.utils.debug("Bootloader: grub (efi)")
|
||||
libcalamares.utils.debug(f"install_hybrid_grub: {install_hybrid_grub}")
|
||||
installation_root_path = libcalamares.globalstorage.value("rootMountPoint")
|
||||
install_efi_directory = installation_root_path + efi_directory
|
||||
|
||||
if not os.path.isdir(install_efi_directory):
|
||||
os.makedirs(install_efi_directory)
|
||||
|
||||
efi_bootloader_id = efi_label(efi_directory)
|
||||
|
||||
efi_target, efi_grub_file, efi_boot_file = get_grub_efi_parameters()
|
||||
|
||||
run_grub_install("efi", partitions, efi_directory, install_hybrid_grub)
|
||||
|
||||
# VFAT is weird, see issue CAL-385
|
||||
install_efi_directory_firmware = (vfat_correct_case(
|
||||
install_efi_directory,
|
||||
"EFI"))
|
||||
if not os.path.exists(install_efi_directory_firmware):
|
||||
os.makedirs(install_efi_directory_firmware)
|
||||
|
||||
# there might be several values for the boot directory
|
||||
# most usual they are boot, Boot, BOOT
|
||||
|
||||
install_efi_boot_directory = (vfat_correct_case(
|
||||
install_efi_directory_firmware,
|
||||
"boot"))
|
||||
if not os.path.exists(install_efi_boot_directory):
|
||||
os.makedirs(install_efi_boot_directory)
|
||||
|
||||
# Workaround for some UEFI firmwares
|
||||
fallback = "installEFIFallback"
|
||||
libcalamares.utils.debug("UEFI Fallback: " + str(libcalamares.job.configuration.get(fallback, "<unset>")))
|
||||
if libcalamares.job.configuration.get(fallback, True):
|
||||
libcalamares.utils.debug(" .. installing '{!s}' fallback firmware".format(efi_boot_file))
|
||||
efi_file_source = os.path.join(install_efi_directory_firmware,
|
||||
efi_bootloader_id,
|
||||
efi_grub_file)
|
||||
efi_file_target = os.path.join(install_efi_boot_directory, efi_boot_file)
|
||||
|
||||
shutil.copy2(efi_file_source, efi_file_target)
|
||||
if fw_type == "bios" or install_hybrid_grub:
|
||||
libcalamares.utils.debug("Bootloader: grub (bios)")
|
||||
run_grub_install("bios", partitions, efi_directory, install_hybrid_grub)
|
||||
|
||||
run_grub_mkconfig(partitions, libcalamares.job.configuration["grubCfg"])
|
||||
|
||||
|
||||
def install_secureboot(efi_directory):
|
||||
"""
|
||||
Installs the secureboot shim in the system by calling efibootmgr.
|
||||
"""
|
||||
efi_bootloader_id = efi_label(efi_directory)
|
||||
|
||||
installation_root_path = libcalamares.globalstorage.value("rootMountPoint")
|
||||
install_efi_directory = installation_root_path + efi_directory
|
||||
|
||||
if efi_word_size() == "64":
|
||||
install_efi_bin = "shimx64.efi"
|
||||
elif efi_word_size() == "32":
|
||||
install_efi_bin = "shimia32.efi"
|
||||
else:
|
||||
libcalamares.utils.warning(f"Unknown efi word size of {efi_word_size()} found")
|
||||
return None
|
||||
|
||||
# Copied, roughly, from openSUSE's install script,
|
||||
# and pythonified. *disk* is something like /dev/sda,
|
||||
# while *drive* may return "(disk/dev/sda,gpt1)" ..
|
||||
# we're interested in the numbers in the second part
|
||||
# of that tuple.
|
||||
efi_drive = subprocess.check_output([
|
||||
libcalamares.job.configuration["grubProbe"],
|
||||
"-t", "drive", "--device-map=", install_efi_directory]).decode("ascii")
|
||||
efi_disk = subprocess.check_output([
|
||||
libcalamares.job.configuration["grubProbe"],
|
||||
"-t", "disk", "--device-map=", install_efi_directory]).decode("ascii")
|
||||
|
||||
efi_drive_partition = efi_drive.replace("(", "").replace(")", "").split(",")[1]
|
||||
# Get the first run of digits from the partition
|
||||
efi_partition_number = None
|
||||
c = 0
|
||||
start = None
|
||||
while c < len(efi_drive_partition):
|
||||
if efi_drive_partition[c].isdigit() and start is None:
|
||||
start = c
|
||||
if not efi_drive_partition[c].isdigit() and start is not None:
|
||||
efi_partition_number = efi_drive_partition[start:c]
|
||||
break
|
||||
c += 1
|
||||
if efi_partition_number is None:
|
||||
raise ValueError("No partition number found for %s" % install_efi_directory)
|
||||
|
||||
subprocess.call([
|
||||
libcalamares.job.configuration["efiBootMgr"],
|
||||
"-c",
|
||||
"-w",
|
||||
"-L", efi_bootloader_id,
|
||||
"-d", efi_disk,
|
||||
"-p", efi_partition_number,
|
||||
"-l", install_efi_directory + "/" + install_efi_bin])
|
||||
|
||||
efi_boot_next()
|
||||
|
||||
# The input file /etc/default/grub should already be filled out by the
|
||||
# grubcfg job module.
|
||||
check_target_env_call([libcalamares.job.configuration["grubMkconfig"],
|
||||
"-o", os.path.join(efi_directory, "EFI",
|
||||
efi_bootloader_id, "grub.cfg")])
|
||||
|
||||
|
||||
def vfat_correct_case(parent, name):
|
||||
for candidate in os.listdir(parent):
|
||||
if name.lower() == candidate.lower():
|
||||
return os.path.join(parent, candidate)
|
||||
return os.path.join(parent, name)
|
||||
|
||||
|
||||
def efi_partitions(efi_boot_path):
|
||||
"""
|
||||
The (one) partition mounted on @p efi_boot_path, or an empty list.
|
||||
"""
|
||||
return [p for p in libcalamares.globalstorage.value("partitions") if p["mountPoint"] == efi_boot_path]
|
||||
|
||||
|
||||
def update_refind_config(efi_directory, installation_root_path):
|
||||
"""
|
||||
:param efi_directory: The path to the efi directory relative to the root
|
||||
:param installation_root_path: The path to the root of the installation
|
||||
"""
|
||||
try:
|
||||
kernel_list = libcalamares.job.configuration["refindKernelList"]
|
||||
except KeyError:
|
||||
libcalamares.utils.warning('refindKernelList not set. Skipping updating refind.conf')
|
||||
return
|
||||
|
||||
# Update the config in the file
|
||||
for line in fileinput.input(installation_root_path + efi_directory + "/EFI/refind/refind.conf", inplace=True):
|
||||
line = line.strip()
|
||||
if line.startswith("#extra_kernel_version_strings") or line.startswith("extra_kernel_version_strings"):
|
||||
line = line.lstrip("#")
|
||||
for kernel in kernel_list:
|
||||
if kernel not in line:
|
||||
line += "," + kernel
|
||||
print(line)
|
||||
|
||||
|
||||
def install_refind(efi_directory):
|
||||
try:
|
||||
installation_root_path = libcalamares.globalstorage.value("rootMountPoint")
|
||||
except KeyError:
|
||||
libcalamares.utils.warning('Global storage value "rootMountPoint" missing')
|
||||
|
||||
install_efi_directory = installation_root_path + efi_directory
|
||||
uuid = get_uuid()
|
||||
kernel_params = " ".join(get_kernel_params(uuid))
|
||||
conf_path = os.path.join(installation_root_path, "boot/refind_linux.conf")
|
||||
|
||||
check_target_env_call(["refind-install"])
|
||||
|
||||
with open(conf_path, "r") as refind_file:
|
||||
filedata = [x.strip() for x in refind_file.readlines()]
|
||||
|
||||
with open(conf_path, 'w') as refind_file:
|
||||
for line in filedata:
|
||||
if line.startswith('"Boot with standard options"'):
|
||||
line = f'"Boot with standard options" "{kernel_params}"'
|
||||
elif line.startswith('"Boot to single-user mode"'):
|
||||
line = f'"Boot to single-user mode" "{kernel_params}" single'
|
||||
refind_file.write(line + "\n")
|
||||
|
||||
update_refind_config(efi_directory, installation_root_path)
|
||||
|
||||
|
||||
def prepare_bootloader(fw_type, install_hybrid_grub):
|
||||
"""
|
||||
Prepares bootloader.
|
||||
Based on value 'efi_boot_loader', it either calls systemd-boot
|
||||
or grub to be installed.
|
||||
|
||||
:param fw_type:
|
||||
:return:
|
||||
"""
|
||||
|
||||
# Get the boot loader selection from global storage if it is set in the config file
|
||||
try:
|
||||
gs_name = libcalamares.job.configuration["efiBootLoaderVar"]
|
||||
if libcalamares.globalstorage.contains(gs_name):
|
||||
efi_boot_loader = libcalamares.globalstorage.value(gs_name)
|
||||
else:
|
||||
libcalamares.utils.warning(
|
||||
f"Specified global storage value not found in global storage")
|
||||
return None
|
||||
except KeyError:
|
||||
# If the conf value for using global storage is not set, use the setting from the config file.
|
||||
try:
|
||||
efi_boot_loader = libcalamares.job.configuration["efiBootLoader"]
|
||||
except KeyError:
|
||||
if fw_type == "efi" or install_hybrid_grub:
|
||||
libcalamares.utils.warning("Configuration missing both efiBootLoader and efiBootLoaderVar on an EFI-enabled "
|
||||
"system, bootloader not installed")
|
||||
return
|
||||
else:
|
||||
pass
|
||||
|
||||
# If the user has selected not to install bootloader, bail out here
|
||||
if efi_boot_loader.casefold() == "none":
|
||||
libcalamares.utils.debug("Skipping bootloader installation since no bootloader was selected")
|
||||
return None
|
||||
|
||||
efi_directory = libcalamares.globalstorage.value("efiSystemPartition")
|
||||
|
||||
if efi_boot_loader == "clr-boot-manager":
|
||||
if fw_type != "efi":
|
||||
# Grub has to be installed first on non-EFI systems
|
||||
install_grub(efi_directory, fw_type)
|
||||
install_clr_boot_manager()
|
||||
elif efi_boot_loader == "systemd-boot" and fw_type == "efi":
|
||||
install_systemd_boot(efi_directory)
|
||||
elif efi_boot_loader == "sb-shim" and fw_type == "efi":
|
||||
install_secureboot(efi_directory)
|
||||
elif efi_boot_loader == "refind" and fw_type == "efi":
|
||||
install_refind(efi_directory)
|
||||
elif efi_boot_loader == "grub" or fw_type != "efi":
|
||||
install_grub(efi_directory, fw_type, install_hybrid_grub)
|
||||
else:
|
||||
libcalamares.utils.debug("WARNING: the combination of "
|
||||
"boot-loader '{!s}' and firmware '{!s}' "
|
||||
"is not supported.".format(efi_boot_loader, fw_type))
|
||||
|
||||
|
||||
def run():
|
||||
"""
|
||||
Starts procedure and passes 'fw_type' to other routine.
|
||||
|
||||
:return:
|
||||
"""
|
||||
|
||||
fw_type = libcalamares.globalstorage.value("firmwareType")
|
||||
boot_loader = libcalamares.globalstorage.value("bootLoader")
|
||||
|
||||
install_hybrid_grub = libcalamares.job.configuration.get("installHybridGRUB", False)
|
||||
efi_boot_loader = libcalamares.job.configuration.get("efiBootLoader", "")
|
||||
|
||||
if install_hybrid_grub == True and efi_boot_loader != "grub":
|
||||
raise ValueError(f"efi_boot_loader '{efi_boot_loader}' is illegal when install_hybrid_grub is 'true'!")
|
||||
|
||||
if boot_loader is None and fw_type != "efi":
|
||||
libcalamares.utils.warning("Non-EFI system, and no bootloader is set.")
|
||||
return None
|
||||
|
||||
partitions = libcalamares.globalstorage.value("partitions")
|
||||
if fw_type == "efi":
|
||||
efi_system_partition = libcalamares.globalstorage.value("efiSystemPartition")
|
||||
esp_found = [p for p in partitions if p["mountPoint"] == efi_system_partition]
|
||||
if not esp_found:
|
||||
libcalamares.utils.warning("EFI system, but nothing mounted on {!s}".format(efi_system_partition))
|
||||
return None
|
||||
|
||||
try:
|
||||
prepare_bootloader(fw_type, install_hybrid_grub)
|
||||
except subprocess.CalledProcessError as e:
|
||||
libcalamares.utils.warning(str(e))
|
||||
libcalamares.utils.debug("stdout:" + str(e.stdout))
|
||||
libcalamares.utils.debug("stderr:" + str(e.stderr))
|
||||
return (_("Bootloader installation error"),
|
||||
_("The bootloader could not be installed. The installation command <pre>{!s}</pre> returned error "
|
||||
"code {!s}.")
|
||||
.format(e.cmd, e.returncode))
|
||||
|
||||
return None
|
||||
10
airootfs/usr/lib/calamares/modules/bootloader/module.desc
Normal file
10
airootfs/usr/lib/calamares/modules/bootloader/module.desc
Normal file
@@ -0,0 +1,10 @@
|
||||
# SPDX-FileCopyrightText: no
|
||||
# SPDX-License-Identifier: CC0-1.0
|
||||
---
|
||||
type: "job"
|
||||
interface: "python"
|
||||
name: "bootloader"
|
||||
script: "main.py"
|
||||
# The partition module sets up the EFI firmware type
|
||||
# global key, which is used to decide how to install.
|
||||
requiredModules: [ "partition" ]
|
||||
7
airootfs/usr/lib/calamares/modules/bootloader/test.yaml
Normal file
7
airootfs/usr/lib/calamares/modules/bootloader/test.yaml
Normal file
@@ -0,0 +1,7 @@
|
||||
# SPDX-FileCopyrightText: no
|
||||
# SPDX-License-Identifier: CC0-1.0
|
||||
rootMountPoint: /tmp/mount
|
||||
bootLoader:
|
||||
installPath: /dev/sdb
|
||||
branding:
|
||||
shortProductName: "Generic Distro"
|
||||
Binary file not shown.
@@ -0,0 +1,7 @@
|
||||
# AUTO-GENERATED metadata file
|
||||
# Syntax is YAML 1.2
|
||||
---
|
||||
type: "job"
|
||||
name: "contextualprocess"
|
||||
interface: "qtplugin"
|
||||
load: "libcalamares_job_contextualprocess.so"
|
||||
@@ -0,0 +1,39 @@
|
||||
# SPDX-FileCopyrightText: 2020 Adriaan de Groot <groot@kde.org>
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
---
|
||||
$schema: https://json-schema.org/schema#
|
||||
$id: https://calamares.io/schemas/displaymanager
|
||||
additionalProperties: false
|
||||
type: object
|
||||
properties:
|
||||
displaymanagers:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
enum: [slim, sddm, lightdm, gdm, mdm, lxdm, greetd]
|
||||
minItems: 1 # Must be non-empty, if present at all
|
||||
defaultDesktopEnvironment:
|
||||
type: object
|
||||
properties:
|
||||
executable: { type: string }
|
||||
desktopFile: { type: string }
|
||||
required: [ executable, desktopFile ]
|
||||
basicSetup: { type: boolean, default: false }
|
||||
sysconfigSetup: { type: boolean, default: false }
|
||||
greetd:
|
||||
type: object
|
||||
properties:
|
||||
greeter_user: { type: string }
|
||||
greeter_group: { type: string }
|
||||
greeter_css_location: { type: string }
|
||||
additionalProperties: false
|
||||
lightdm:
|
||||
type: object
|
||||
properties:
|
||||
preferred_greeters: { type: array, items: { type: string } }
|
||||
additionalProperties: false
|
||||
sddm:
|
||||
type: object
|
||||
properties:
|
||||
configuration_file: { type: string }
|
||||
additionalProperties: false
|
||||
1046
airootfs/usr/lib/calamares/modules/displaymanager/main.py
Normal file
1046
airootfs/usr/lib/calamares/modules/displaymanager/main.py
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,7 @@
|
||||
# SPDX-FileCopyrightText: no
|
||||
# SPDX-License-Identifier: CC0-1.0
|
||||
---
|
||||
type: "job"
|
||||
name: "displaymanager"
|
||||
interface: "python"
|
||||
script: "main.py"
|
||||
BIN
airootfs/usr/lib/calamares/modules/finished/libcalamares_viewmodule_finished.so
Executable file
BIN
airootfs/usr/lib/calamares/modules/finished/libcalamares_viewmodule_finished.so
Executable file
Binary file not shown.
7
airootfs/usr/lib/calamares/modules/finished/module.desc
Normal file
7
airootfs/usr/lib/calamares/modules/finished/module.desc
Normal file
@@ -0,0 +1,7 @@
|
||||
# AUTO-GENERATED metadata file
|
||||
# Syntax is YAML 1.2
|
||||
---
|
||||
type: "viewmodule"
|
||||
name: "finished"
|
||||
interface: "qtplugin"
|
||||
load: "libcalamares_viewmodule_finished.so"
|
||||
Binary file not shown.
7
airootfs/usr/lib/calamares/modules/finishedq/module.desc
Normal file
7
airootfs/usr/lib/calamares/modules/finishedq/module.desc
Normal file
@@ -0,0 +1,7 @@
|
||||
# AUTO-GENERATED metadata file
|
||||
# Syntax is YAML 1.2
|
||||
---
|
||||
type: "viewmodule"
|
||||
name: "finishedq"
|
||||
interface: "qtplugin"
|
||||
load: "libcalamares_viewmodule_finishedq.so"
|
||||
@@ -0,0 +1,17 @@
|
||||
# === This file is part of Calamares - <https://calamares.io> ===
|
||||
#
|
||||
# SPDX-FileCopyrightText: 2020 Adriaan de Groot <groot@kde.org>
|
||||
# SPDX-License-Identifier: BSD-2-Clause
|
||||
#
|
||||
calamares_add_plugin(flatpakInfo
|
||||
TYPE job
|
||||
EXPORT_MACRO PLUGINDLLEXPORT_PRO
|
||||
SOURCES
|
||||
FlatpakInfoJob.h
|
||||
ItemFlatpak.h
|
||||
PackagePool.h
|
||||
FlatpakInfoJob.cpp
|
||||
ItemFlatpak.cpp
|
||||
PackagePool.cpp
|
||||
SHARED_LIB
|
||||
)
|
||||
@@ -0,0 +1,63 @@
|
||||
/* === This file is part of Calamares - <https://calamares.io> ===
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2023 Sławomir Lach <slawek@lach.art.pl>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* Calamares is Free Software: see the License-Identifier above.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "FlatpakInfoJob.h"
|
||||
|
||||
#include "utils/Runner.h"
|
||||
#include "utils/Logger.h"
|
||||
#include "utils/Variant.h"
|
||||
|
||||
#include "GlobalStorage.h"
|
||||
#include "JobQueue.h"
|
||||
#include "Settings.h"
|
||||
|
||||
#include <QProcess>
|
||||
|
||||
#include <unistd.h>
|
||||
|
||||
#include "ItemFlatpak.h"
|
||||
#include "PackagePool.h"
|
||||
|
||||
FlatpakInfoJob::FlatpakInfoJob( QObject* parent )
|
||||
: Calamares::CppJob( parent )
|
||||
{
|
||||
}
|
||||
|
||||
FlatpakInfoJob::~FlatpakInfoJob()
|
||||
{
|
||||
ItemFlatpak_freeMem();
|
||||
}
|
||||
|
||||
QString
|
||||
FlatpakInfoJob::prettyName() const
|
||||
{
|
||||
return tr( "Fill netinstall with flatpak packages" );
|
||||
}
|
||||
|
||||
|
||||
Calamares::JobResult
|
||||
FlatpakInfoJob::exec()
|
||||
{
|
||||
QVariantList partitions;
|
||||
Calamares::GlobalStorage* gs = Calamares::JobQueue::instance()->globalStorage();
|
||||
|
||||
|
||||
downloadPackagesInfo();
|
||||
serializePackagesInfo();
|
||||
|
||||
return Calamares::JobResult::ok();
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
FlatpakInfoJob::setConfigurationMap( const QVariantMap& map )
|
||||
{
|
||||
}
|
||||
|
||||
CALAMARES_PLUGIN_FACTORY_DEFINITION( FlatpakInfoJobFactory, registerPlugin< FlatpakInfoJob >(); )
|
||||
@@ -0,0 +1,43 @@
|
||||
/* === This file is part of Calamares - <https://calamares.io> ===
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2023 Sławomir Lach <slawek@lach.art.pl>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* Calamares is Free Software: see the License-Identifier above.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef FLATPAKINFOJOB_H
|
||||
#define FLATPAKINFOJOB_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QStringList>
|
||||
#include <QVariantMap>
|
||||
|
||||
#include "CppJob.h"
|
||||
|
||||
#include "utils/PluginFactory.h"
|
||||
|
||||
#include "DllMacro.h"
|
||||
|
||||
/** @brief Create zpools and zfs datasets
|
||||
*
|
||||
*/
|
||||
class PLUGINDLLEXPORT FlatpakInfoJob : public Calamares::CppJob
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit FlatpakInfoJob( QObject* parent = nullptr );
|
||||
~FlatpakInfoJob() override;
|
||||
|
||||
QString prettyName() const override;
|
||||
|
||||
Calamares::JobResult exec() override;
|
||||
|
||||
void setConfigurationMap( const QVariantMap& configurationMap ) override;
|
||||
};
|
||||
|
||||
CALAMARES_PLUGIN_FACTORY_DECLARATION( FlatpakInfoJobFactory )
|
||||
|
||||
#endif // ZFSJOB_H
|
||||
@@ -0,0 +1,66 @@
|
||||
/* === This file is part of Calamares - <https://calamares.io> ===
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2023 Sławomir Lach <slawek@lach.art.pl>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* Calamares is Free Software: see the License-Identifier above.
|
||||
*
|
||||
*/
|
||||
|
||||
/* Qt */
|
||||
#include <QVariantMap>
|
||||
|
||||
/* CPP */
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
|
||||
/* Calamares */
|
||||
#include "utils/Runner.h"
|
||||
|
||||
/* Module */
|
||||
#include "ItemFlatpak.h"
|
||||
#include "utils/Logger.h"
|
||||
#include "utils/Variant.h"
|
||||
|
||||
PackageItem
|
||||
fromFlatpak( const QVariantMap& itemMap, InstalledList &installed )
|
||||
{
|
||||
// check if it is installed
|
||||
PackageItem item( Calamares::getString( itemMap, "appstream" ) );
|
||||
item.setInstalled( false );
|
||||
|
||||
item.setInstalled( installed.contains( Calamares::getString( itemMap, "appstream" ) ) );
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
InstalledList::InstalledList()
|
||||
{
|
||||
long long int prev_pos;
|
||||
long long int pos = 0;
|
||||
QString line;
|
||||
auto process = Calamares::System::instance()->targetEnvCommand(
|
||||
QStringList { QString::fromLatin1( "flatpak" ),
|
||||
QString::fromLatin1( "list" ),
|
||||
QString::fromLatin1( "--app" ),
|
||||
QString::fromLatin1( "--columns=application" ) } );
|
||||
auto outputStr = process.second;
|
||||
|
||||
do {
|
||||
prev_pos = pos;
|
||||
|
||||
pos = outputStr.indexOf('\n', prev_pos);
|
||||
QString line = outputStr.mid(prev_pos, pos);
|
||||
installed.append(line);
|
||||
|
||||
/* Increase by 1 to not stuck on newline */
|
||||
++pos;
|
||||
|
||||
/* QString::indexOf returns -1 since no occurences. 0 = -1 + 1.*/
|
||||
} while (0 != pos);
|
||||
}
|
||||
|
||||
InstalledList::~InstalledList()
|
||||
{
|
||||
installed.clear();
|
||||
}
|
||||
110
airootfs/usr/lib/calamares/modules/flatpakinfo/PackagePool.cpp
Normal file
110
airootfs/usr/lib/calamares/modules/flatpakinfo/PackagePool.cpp
Normal file
@@ -0,0 +1,110 @@
|
||||
/* === This file is part of Calamares - <https://calamares.io> ===
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2023 Sławomir Lach <slawek@lach.art.pl>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* Calamares is Free Software: see the License-Identifier above.
|
||||
*/
|
||||
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
|
||||
#include <stdio.h>
|
||||
#include <sys/wait.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <QString>
|
||||
#include <QDesktopServices>
|
||||
#include <QVariantMap>
|
||||
|
||||
#include "GlobalStorage.h"
|
||||
#include "JobQueue.h"
|
||||
#include "utils/Logger.h"
|
||||
#include "utils/Variant.h"
|
||||
#include "ItemFlatpak.h"
|
||||
#include "PackagePool.h"
|
||||
|
||||
#include "utils/System.h"
|
||||
|
||||
void PackagePool::downloadPackagesInfo(InstalledList &list)
|
||||
{
|
||||
QHash<QString,bool> addedPackages;
|
||||
QString line;
|
||||
auto process = Calamares::System::instance()->targetEnvCommand( QStringList { QString::fromStdString( "flatpak" ), QString::fromStdString( "remotes" ), QString::fromStdString( "--columns=name" ) });
|
||||
auto outputStr = process.second;
|
||||
QTextStream output(&outputStr);
|
||||
|
||||
while (output.readLineInto(&line))
|
||||
{
|
||||
QString line2;
|
||||
auto process2 = Calamares::System::instance()->targetEnvCommand(
|
||||
QStringList { QString::fromStdString( "flatpak" ),
|
||||
QString::fromStdString( "remote-ls" ),
|
||||
QString::fromStdString( "--app" ),
|
||||
QString::fromStdString( "--columns=application" ),
|
||||
line } );
|
||||
auto output2Str = process2.second;
|
||||
QTextStream output2( &output2Str );
|
||||
|
||||
while ( output2.readLineInto( &line2 ) )
|
||||
{
|
||||
if ( line2 == "" )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
QVariantMap itemMap;
|
||||
|
||||
if ( addedPackages.contains( line2 ) )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
addedPackages.insert( line2, true );
|
||||
|
||||
itemMap.insert( "appstream", QVariant( line2 ) );
|
||||
itemMap.insert( "id", QVariant( line2 ) );
|
||||
|
||||
PackageItem item = fromFlatpak( itemMap, list );
|
||||
packages.append( item );
|
||||
}
|
||||
}
|
||||
|
||||
serializePackagesInfo();
|
||||
}
|
||||
|
||||
void PackagePool::serializePackagesInfo()
|
||||
{
|
||||
QList<QVariant> changedValue;
|
||||
auto* gs = Calamares::JobQueue::instance()->globalStorage();
|
||||
|
||||
// If an earlier packagechooser instance added this data to global storage, combine them
|
||||
if ( gs->contains( "netinstallAdd" ) )
|
||||
{
|
||||
auto selectedOrig = gs->value( "netinstallAdd" );
|
||||
|
||||
changedValue = selectedOrig.toList();
|
||||
for (auto current: packages)
|
||||
{
|
||||
QStringList selfInstall;
|
||||
QVariantMap newValue;
|
||||
newValue.insert("name", current.getAppStreamId());
|
||||
|
||||
if (current.getInstalled())
|
||||
{
|
||||
newValue.insert("selected", true);
|
||||
newValue.insert("immutable", true);
|
||||
newValue.insert("description", "[Already installed; cannot be uninstalled]");
|
||||
}
|
||||
else
|
||||
{
|
||||
newValue.insert("selected", false);
|
||||
}
|
||||
selfInstall.append(current.getAppStreamId());
|
||||
newValue.insert("packages", selfInstall);
|
||||
changedValue.append(newValue);
|
||||
}
|
||||
|
||||
gs->remove( "netinstallAdd" );
|
||||
}
|
||||
gs->insert( "netinstallAdd", changedValue );
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
# SPDX-FileCopyrightText: no
|
||||
# SPDX-License-Identifier: CC0-1.0
|
||||
#
|
||||
# The flatpakinfo module will collect package list from configured flatpak repositories
|
||||
#
|
||||
#
|
||||
#
|
||||
---
|
||||
@@ -0,0 +1,7 @@
|
||||
# SPDX-FileCopyrightText: no
|
||||
# SPDX-License-Identifier: CC0-1.0
|
||||
---
|
||||
type: "job"
|
||||
name: "flatpakinfo"
|
||||
interface: "qtplugin"
|
||||
load: "libcalamares_job_flatpakInfo.so"
|
||||
@@ -0,0 +1,17 @@
|
||||
# SPDX-FileCopyrightText: no
|
||||
# SPDX-License-Identifier: CC0-1.0
|
||||
#
|
||||
if( NOT Calamares_WITH_QML )
|
||||
calamares_skip_module( "freebsddisk (QML is not supported in this build)" )
|
||||
return()
|
||||
endif()
|
||||
|
||||
calamares_add_plugin( freebsddisk
|
||||
TYPE viewmodule
|
||||
EXPORT_MACRO PLUGINDLLEXPORT_PRO
|
||||
SOURCES
|
||||
FreeBSDDiskViewStep.cpp
|
||||
RESOURCES
|
||||
freebsddisk.qrc
|
||||
SHARED_LIB
|
||||
)
|
||||
@@ -0,0 +1,42 @@
|
||||
/* === This file is part of Calamares - <https://github.com/calamares> ===
|
||||
*
|
||||
* Calamares 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 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Calamares 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 Calamares. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2020 Adriaan de Groot <groot@kde.org>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
* License-Filename: LICENSES/GPL-3.0
|
||||
*/
|
||||
|
||||
#include "FreeBSDDiskViewStep.h"
|
||||
|
||||
FreeBSDDiskViewStep::FreeBSDDiskViewStep( QObject* parent )
|
||||
: Calamares::QmlViewStep( parent )
|
||||
{
|
||||
}
|
||||
|
||||
FreeBSDDiskViewStep::~FreeBSDDiskViewStep() {}
|
||||
|
||||
QString
|
||||
FreeBSDDiskViewStep::prettyName() const
|
||||
{
|
||||
return tr( "Disk Setup" );
|
||||
}
|
||||
|
||||
void
|
||||
FreeBSDDiskViewStep::setConfigurationMap( const QVariantMap& configurationMap )
|
||||
{
|
||||
Calamares::QmlViewStep::setConfigurationMap( configurationMap ); // call parent implementation last
|
||||
}
|
||||
|
||||
CALAMARES_PLUGIN_FACTORY_DEFINITION( FreeBSDDiskViewStepFactory, registerPlugin< FreeBSDDiskViewStep >(); )
|
||||
@@ -0,0 +1,44 @@
|
||||
/* === This file is part of Calamares - <https://github.com/calamares> ===
|
||||
*
|
||||
* Calamares 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 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Calamares 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 Calamares. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2020 Adriaan de Groot <groot@kde.org>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
* License-Filename: LICENSES/GPL-3.0
|
||||
*/
|
||||
|
||||
|
||||
#ifndef FREEBSDDISKVIEWSTEP_H
|
||||
#define FREEBSDDISKVIEWSTEP_H
|
||||
|
||||
#include "DllMacro.h"
|
||||
#include "utils/PluginFactory.h"
|
||||
#include "viewpages/QmlViewStep.h"
|
||||
|
||||
class PLUGINDLLEXPORT FreeBSDDiskViewStep : public Calamares::QmlViewStep
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
FreeBSDDiskViewStep( QObject* parent = nullptr );
|
||||
virtual ~FreeBSDDiskViewStep() override;
|
||||
|
||||
QString prettyName() const override;
|
||||
|
||||
void setConfigurationMap( const QVariantMap& configurationMap ) override;
|
||||
};
|
||||
|
||||
CALAMARES_PLUGIN_FACTORY_DECLARATION( FreeBSDDiskViewStepFactory )
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,10 @@
|
||||
# SPDX-FileCopyrightText: no
|
||||
# SPDX-License-Identifier: CC0-1.0
|
||||
#
|
||||
# The *freebsddisk* module can be used to pick a disk
|
||||
# as an installer step. This module supports ZFSroot
|
||||
# on one whole disk, and UFSroot on one whole disk.
|
||||
#
|
||||
---
|
||||
qmlSearch: both
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
/* === This file is part of Calamares - <https://github.com/calamares> ===
|
||||
*
|
||||
* Calamares 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 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Calamares 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 Calamares. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2020 Adriaan de Groot <groot@kde.org>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
* License-Filename: LICENSES/GPL-3.0
|
||||
*/
|
||||
|
||||
import io.calamares.ui 1.0
|
||||
|
||||
import QtQuick 2.7
|
||||
import QtQuick.Controls 2.2
|
||||
import QtQuick.Window 2.2
|
||||
import QtQuick.Layouts 1.3
|
||||
import QtQuick.Controls.Material 2.1
|
||||
|
||||
Item {
|
||||
Text {
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: 10
|
||||
text: "Select a disk on which to install FreeBSD."
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
<!DOCTYPE RCC><RCC version="1.0">
|
||||
<qresource>
|
||||
<file alias="freebsddisk.qml">freebsddisk.qml</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
BIN
airootfs/usr/lib/calamares/modules/fsresizer/libcalamares_job_fsresizer.so
Executable file
BIN
airootfs/usr/lib/calamares/modules/fsresizer/libcalamares_job_fsresizer.so
Executable file
Binary file not shown.
7
airootfs/usr/lib/calamares/modules/fsresizer/module.desc
Normal file
7
airootfs/usr/lib/calamares/modules/fsresizer/module.desc
Normal file
@@ -0,0 +1,7 @@
|
||||
# AUTO-GENERATED metadata file
|
||||
# Syntax is YAML 1.2
|
||||
---
|
||||
type: "job"
|
||||
name: "fsresizer"
|
||||
interface: "qtplugin"
|
||||
load: "libcalamares_job_fsresizer.so"
|
||||
28
airootfs/usr/lib/calamares/modules/fstab/fstab.schema.yaml
Normal file
28
airootfs/usr/lib/calamares/modules/fstab/fstab.schema.yaml
Normal file
@@ -0,0 +1,28 @@
|
||||
# SPDX-FileCopyrightText: 2020 Adriaan de Groot <groot@kde.org>
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
---
|
||||
$schema: https://json-schema.org/schema#
|
||||
$id: https://calamares.io/schemas/fstab
|
||||
additionalProperties: false
|
||||
type: object
|
||||
properties:
|
||||
crypttabOptions: { type: string }
|
||||
tmpOptions:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
properties:
|
||||
"default":
|
||||
type: object
|
||||
additionalProperties: false
|
||||
properties:
|
||||
tmpfs: { type: boolean }
|
||||
options: { type: string }
|
||||
ssd:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
properties:
|
||||
tmpfs: { type: boolean }
|
||||
options: { type: string }
|
||||
required: [ "default" ]
|
||||
required:
|
||||
- tmpOptions
|
||||
436
airootfs/usr/lib/calamares/modules/fstab/main.py
Normal file
436
airootfs/usr/lib/calamares/modules/fstab/main.py
Normal file
@@ -0,0 +1,436 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# === This file is part of Calamares - <https://calamares.io> ===
|
||||
#
|
||||
# SPDX-FileCopyrightText: 2014 Aurélien Gâteau <agateau@kde.org>
|
||||
# SPDX-FileCopyrightText: 2016 Teo Mrnjavac <teo@kde.org>
|
||||
# SPDX-FileCopyrightText: 2017 Alf Gaida <agaida@siduction.org>
|
||||
# SPDX-FileCopyrightText: 2019 Adriaan de Groot <groot@kde.org>
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#
|
||||
# Calamares is Free Software: see the License-Identifier above.
|
||||
#
|
||||
|
||||
import os
|
||||
import re
|
||||
import copy
|
||||
|
||||
import libcalamares
|
||||
|
||||
import gettext
|
||||
_ = gettext.translation("calamares-python",
|
||||
localedir=libcalamares.utils.gettext_path(),
|
||||
languages=libcalamares.utils.gettext_languages(),
|
||||
fallback=True).gettext
|
||||
|
||||
|
||||
def pretty_name():
|
||||
return _("Writing fstab.")
|
||||
|
||||
|
||||
FSTAB_HEADER = """# /etc/fstab: static file system information.
|
||||
#
|
||||
# Use 'blkid' to print the universally unique identifier for a device; this may
|
||||
# be used with UUID= as a more robust way to name devices that works even if
|
||||
# disks are added and removed. See fstab(5).
|
||||
#
|
||||
# <file system> <mount point> <type> <options> <dump> <pass>"""
|
||||
|
||||
CRYPTTAB_HEADER = """# /etc/crypttab: mappings for encrypted partitions.
|
||||
#
|
||||
# Each mapped device will be created in /dev/mapper, so your /etc/fstab
|
||||
# should use the /dev/mapper/<name> paths for encrypted devices.
|
||||
#
|
||||
# See crypttab(5) for the supported syntax.
|
||||
#
|
||||
# NOTE: You need not list your root (/) partition here, but it must be set up
|
||||
# beforehand by the initramfs (/etc/mkinitcpio.conf). The same applies
|
||||
# to encrypted swap, which should be set up with mkinitcpio-openswap
|
||||
# for resume support.
|
||||
#
|
||||
# <name> <device> <password> <options>"""
|
||||
|
||||
# Turn Parted filesystem names into fstab names
|
||||
FS_MAP = {
|
||||
"fat16": "vfat",
|
||||
"fat32": "vfat",
|
||||
"linuxswap": "swap",
|
||||
}
|
||||
|
||||
|
||||
def mkdir_p(path):
|
||||
""" Create directory.
|
||||
|
||||
:param path:
|
||||
"""
|
||||
if not os.path.exists(path):
|
||||
os.makedirs(path)
|
||||
|
||||
|
||||
def is_ssd_disk(disk_name):
|
||||
""" Checks if given disk is actually a ssd disk.
|
||||
|
||||
:param disk_name:
|
||||
:return:
|
||||
"""
|
||||
filename = os.path.join("/sys/block", disk_name, "queue/rotational")
|
||||
|
||||
if not os.path.exists(filename):
|
||||
# Should not happen unless sysfs changes, but better safe than sorry
|
||||
return False
|
||||
|
||||
with open(filename) as sysfile:
|
||||
return sysfile.read() == "0\n"
|
||||
|
||||
|
||||
def disk_name_for_partition(partition):
|
||||
""" Returns disk name for each found partition.
|
||||
|
||||
:param partition:
|
||||
:return:
|
||||
"""
|
||||
name = os.path.basename(partition["device"])
|
||||
|
||||
if name.startswith("mmcblk") or name.startswith("nvme"):
|
||||
# Typical mmc device is mmcblk0p1, nvme looks like nvme0n1p2
|
||||
return re.sub("p[0-9]+$", "", name)
|
||||
|
||||
return re.sub("[0-9]+$", "", name)
|
||||
|
||||
|
||||
class FstabGenerator(object):
|
||||
def __init__(self, partitions, root_mount_point, mount_options_list,
|
||||
crypttab_options, tmp_options):
|
||||
self.partitions = partitions
|
||||
self.root_mount_point = root_mount_point
|
||||
self.mount_options_list = mount_options_list
|
||||
self.crypttab_options = crypttab_options
|
||||
self.tmp_options = tmp_options
|
||||
self.ssd_disks = set()
|
||||
self.root_is_ssd = False
|
||||
|
||||
def run(self):
|
||||
""" Calls needed sub routines.
|
||||
|
||||
:return:
|
||||
"""
|
||||
self.find_ssd_disks()
|
||||
self.generate_fstab()
|
||||
self.generate_crypttab()
|
||||
self.create_mount_points()
|
||||
|
||||
return None
|
||||
|
||||
def find_ssd_disks(self):
|
||||
""" Checks for ssd disks """
|
||||
disks = {disk_name_for_partition(x) for x in self.partitions}
|
||||
self.ssd_disks = {x for x in disks if is_ssd_disk(x)}
|
||||
|
||||
def generate_crypttab(self):
|
||||
""" Create crypttab. """
|
||||
mkdir_p(os.path.join(self.root_mount_point, "etc"))
|
||||
crypttab_path = os.path.join(self.root_mount_point, "etc", "crypttab")
|
||||
|
||||
with open(crypttab_path, "w") as crypttab_file:
|
||||
print(CRYPTTAB_HEADER, file=crypttab_file)
|
||||
|
||||
for partition in self.partitions:
|
||||
dct = self.generate_crypttab_line_info(partition)
|
||||
|
||||
if dct:
|
||||
self.print_crypttab_line(dct, file=crypttab_file)
|
||||
|
||||
def generate_crypttab_line_info(self, partition):
|
||||
""" Generates information for each crypttab entry. """
|
||||
if "luksMapperName" not in partition or "luksUuid" not in partition:
|
||||
return None
|
||||
|
||||
mapper_name = partition["luksMapperName"]
|
||||
luks_uuid = partition["luksUuid"]
|
||||
if not mapper_name or not luks_uuid:
|
||||
return None
|
||||
|
||||
crypttab_options = self.crypttab_options
|
||||
# Make sure to not use missing keyfile
|
||||
if os.path.isfile(os.path.join(self.root_mount_point, "crypto_keyfile.bin")):
|
||||
password = "/crypto_keyfile.bin"
|
||||
else:
|
||||
password = "none"
|
||||
|
||||
# Set crypttab password for partition to none and remove crypttab options
|
||||
# if root partition was not encrypted
|
||||
if any([p["mountPoint"] == "/"
|
||||
and "luksMapperName" not in p
|
||||
for p in self.partitions]):
|
||||
password = "none"
|
||||
crypttab_options = ""
|
||||
# on root partition when /boot is unencrypted
|
||||
elif partition["mountPoint"] == "/":
|
||||
if any([p["mountPoint"] == "/boot"
|
||||
and "luksMapperName" not in p
|
||||
for p in self.partitions]):
|
||||
password = "none"
|
||||
crypttab_options = ""
|
||||
|
||||
return dict(
|
||||
name=mapper_name,
|
||||
device="UUID=" + luks_uuid,
|
||||
password=password,
|
||||
options=crypttab_options,
|
||||
)
|
||||
|
||||
def print_crypttab_line(self, dct, file=None):
|
||||
""" Prints line to '/etc/crypttab' file. """
|
||||
line = "{:21} {:<45} {} {}".format(dct["name"],
|
||||
dct["device"],
|
||||
dct["password"],
|
||||
dct["options"],
|
||||
)
|
||||
|
||||
print(line, file=file)
|
||||
|
||||
def generate_fstab(self):
|
||||
""" Create fstab. """
|
||||
mkdir_p(os.path.join(self.root_mount_point, "etc"))
|
||||
fstab_path = os.path.join(self.root_mount_point, "etc", "fstab")
|
||||
|
||||
with open(fstab_path, "w") as fstab_file:
|
||||
print(FSTAB_HEADER, file=fstab_file)
|
||||
|
||||
for partition in self.partitions:
|
||||
# Special treatment for a btrfs subvolumes
|
||||
if (partition["fs"] == "btrfs"
|
||||
and partition["mountPoint"] == "/"):
|
||||
# Subvolume list has been created in mount.conf and curated in mount module,
|
||||
# so all subvolumes here should be safe to add to fstab
|
||||
btrfs_subvolumes = libcalamares.globalstorage.value("btrfsSubvolumes")
|
||||
for s in btrfs_subvolumes:
|
||||
mount_entry = copy.deepcopy(partition)
|
||||
mount_entry["mountPoint"] = s["mountPoint"]
|
||||
mount_entry["subvol"] = s["subvolume"]
|
||||
dct = self.generate_fstab_line_info(mount_entry)
|
||||
if dct:
|
||||
self.print_fstab_line(dct, file=fstab_file)
|
||||
elif partition["fs"] != "zfs": # zfs partitions don't need an entry in fstab
|
||||
dct = self.generate_fstab_line_info(partition)
|
||||
if dct:
|
||||
self.print_fstab_line(dct, file=fstab_file)
|
||||
|
||||
if self.root_is_ssd:
|
||||
# Old behavior was to mount /tmp as tmpfs
|
||||
# New behavior is to use tmpOptions to decide
|
||||
# if mounting /tmp as tmpfs and which options to use
|
||||
ssd = self.tmp_options.get("ssd", {})
|
||||
if not ssd:
|
||||
ssd = self.tmp_options.get("default", {})
|
||||
# Default to True to mimic old behavior
|
||||
tmpfs = ssd.get("tmpfs", True)
|
||||
|
||||
if tmpfs:
|
||||
options = ssd.get("options", "defaults,noatime,mode=1777")
|
||||
# Mount /tmp on a tmpfs
|
||||
dct = dict(device="tmpfs",
|
||||
mount_point="/tmp",
|
||||
fs="tmpfs",
|
||||
options=options,
|
||||
check=0,
|
||||
)
|
||||
self.print_fstab_line(dct, file=fstab_file)
|
||||
|
||||
def generate_fstab_line_info(self, partition):
|
||||
"""
|
||||
Generates information (a dictionary of fstab-fields)
|
||||
for the given @p partition.
|
||||
"""
|
||||
# Some "fs" names need special handling in /etc/fstab, so remap them.
|
||||
filesystem = partition["fs"].lower()
|
||||
filesystem = FS_MAP.get(filesystem, filesystem)
|
||||
luks_mapper_name = partition.get("luksMapperName", None)
|
||||
mount_point = partition["mountPoint"]
|
||||
disk_name = disk_name_for_partition(partition)
|
||||
is_ssd = disk_name in self.ssd_disks
|
||||
|
||||
# Swap partitions are called "linuxswap" by parted.
|
||||
# That "fs" is visible in GS, but that gets mapped
|
||||
# to "swap", above, because that's the spelling needed in /etc/fstab
|
||||
if not mount_point and not filesystem == "swap":
|
||||
return None
|
||||
if not mount_point:
|
||||
mount_point = "swap"
|
||||
|
||||
if filesystem == "swap" and not partition.get("claimed", None):
|
||||
libcalamares.utils.debug("Ignoring foreign swap {!s} {!s}".format(disk_name, partition.get("uuid", None)))
|
||||
return None
|
||||
|
||||
options = self.get_mount_options(mount_point)
|
||||
|
||||
if mount_point == "/" and filesystem != "btrfs":
|
||||
check = 1
|
||||
elif mount_point and mount_point != "swap" and filesystem != "btrfs":
|
||||
check = 2
|
||||
else:
|
||||
check = 0
|
||||
|
||||
if mount_point == "/":
|
||||
self.root_is_ssd = is_ssd
|
||||
|
||||
# If there's a set-and-not-empty subvolume set, add it
|
||||
if filesystem == "btrfs" and partition.get("subvol",None):
|
||||
options = "subvol={},".format(partition["subvol"]) + options
|
||||
|
||||
device = None
|
||||
if luks_mapper_name:
|
||||
device = "/dev/mapper/" + luks_mapper_name
|
||||
elif partition["uuid"]:
|
||||
device = "UUID=" + partition["uuid"]
|
||||
else:
|
||||
device = partition["device"]
|
||||
|
||||
if not device:
|
||||
# TODO: we get here when the user mounted a previously encrypted partition
|
||||
# This should be catched early in the process
|
||||
return None
|
||||
|
||||
return dict(device=device,
|
||||
mount_point=mount_point,
|
||||
fs=filesystem,
|
||||
options=options,
|
||||
check=check,
|
||||
)
|
||||
|
||||
def print_fstab_line(self, dct, file=None):
|
||||
""" Prints line to '/etc/fstab' file. """
|
||||
line = "{:41} {:<14} {:<7} {:<10} 0 {}".format(dct["device"],
|
||||
dct["mount_point"],
|
||||
dct["fs"],
|
||||
dct["options"],
|
||||
dct["check"],
|
||||
)
|
||||
print(line, file=file)
|
||||
|
||||
def create_mount_points(self):
|
||||
""" Creates mount points """
|
||||
for partition in self.partitions:
|
||||
if partition["mountPoint"]:
|
||||
mkdir_p(self.root_mount_point + partition["mountPoint"])
|
||||
|
||||
def get_mount_options(self, mountpoint):
|
||||
"""
|
||||
Returns the mount options for a given mountpoint
|
||||
|
||||
:param mountpoint: A string containing the mountpoint for the fstab entry
|
||||
:return: A string containing the mount options for the entry or "defaults" if nothing is found
|
||||
"""
|
||||
mount_options_item = next((x for x in self.mount_options_list if x.get("mountpoint") == mountpoint), None)
|
||||
if mount_options_item:
|
||||
return mount_options_item.get("option_string", "defaults")
|
||||
else:
|
||||
return "defaults"
|
||||
|
||||
|
||||
def create_swapfile(root_mount_point, root_btrfs):
|
||||
"""
|
||||
Creates /swapfile in @p root_mount_point ; if the root filesystem
|
||||
is on btrfs, then handle some btrfs specific features as well,
|
||||
as documented in
|
||||
https://wiki.archlinux.org/index.php/Swap#Swap_file
|
||||
|
||||
The swapfile-creation covers progress from 0.2 to 0.5
|
||||
"""
|
||||
libcalamares.job.setprogress(0.2)
|
||||
if root_btrfs:
|
||||
# btrfs swapfiles must reside on a subvolume that is not snapshotted to prevent file system corruption
|
||||
swapfile_path = os.path.join(root_mount_point, "swap/swapfile")
|
||||
with open(swapfile_path, "wb") as f:
|
||||
pass
|
||||
libcalamares.utils.host_env_process_output(["chattr", "+C", "+m", swapfile_path]) # No Copy-on-Write, no compression
|
||||
else:
|
||||
swapfile_path = os.path.join(root_mount_point, "swapfile")
|
||||
with open(swapfile_path, "wb") as f:
|
||||
pass
|
||||
# Create the swapfile; swapfiles are small-ish
|
||||
zeroes = bytes(16384)
|
||||
with open(swapfile_path, "wb") as f:
|
||||
total = 0
|
||||
desired_size = 512 * 1024 * 1024 # 512MiB
|
||||
while total < desired_size:
|
||||
chunk = f.write(zeroes)
|
||||
if chunk < 1:
|
||||
libcalamares.utils.debug("Short write on {!s}, cancelling.".format(swapfile_path))
|
||||
break
|
||||
libcalamares.job.setprogress(0.2 + 0.3 * ( total / desired_size ) )
|
||||
total += chunk
|
||||
os.chmod(swapfile_path, 0o600)
|
||||
libcalamares.utils.host_env_process_output(["mkswap", swapfile_path])
|
||||
libcalamares.job.setprogress(0.5)
|
||||
|
||||
|
||||
def run():
|
||||
""" Configures fstab.
|
||||
|
||||
:return:
|
||||
"""
|
||||
global_storage = libcalamares.globalstorage
|
||||
conf = libcalamares.job.configuration
|
||||
partitions = global_storage.value("partitions")
|
||||
root_mount_point = global_storage.value("rootMountPoint")
|
||||
|
||||
if not partitions:
|
||||
libcalamares.utils.warning("partitions is empty, {!s}"
|
||||
.format(partitions))
|
||||
return (_("Configuration Error"),
|
||||
_("No partitions are defined for <pre>{!s}</pre> to use.")
|
||||
.format("fstab"))
|
||||
if not root_mount_point:
|
||||
libcalamares.utils.warning("rootMountPoint is empty, {!s}"
|
||||
.format(root_mount_point))
|
||||
return (_("Configuration Error"),
|
||||
_("No root mount point is given for <pre>{!s}</pre> to use.")
|
||||
.format("fstab"))
|
||||
|
||||
# This follows the GS settings from the partition module's Config object
|
||||
swap_choice = global_storage.value( "partitionChoices" )
|
||||
if swap_choice:
|
||||
swap_choice = swap_choice.get( "swap", None )
|
||||
if swap_choice and swap_choice == "file":
|
||||
# There's no formatted partition for it, so we'll sneak in an entry
|
||||
root_partitions = [ p["fs"].lower() for p in partitions if p["mountPoint"] == "/" ]
|
||||
root_btrfs = (root_partitions[0] == "btrfs") if root_partitions else False
|
||||
if root_btrfs:
|
||||
partitions.append( dict(fs="swap", mountPoint=None, claimed=True, device="/swap/swapfile", uuid=None) )
|
||||
else:
|
||||
partitions.append( dict(fs="swap", mountPoint=None, claimed=True, device="/swapfile", uuid=None) )
|
||||
else:
|
||||
swap_choice = None
|
||||
|
||||
libcalamares.job.setprogress(0.1)
|
||||
mount_options_list = global_storage.value("mountOptionsList")
|
||||
crypttab_options = conf.get("crypttabOptions", "luks")
|
||||
tmp_options = conf.get("tmpOptions", {})
|
||||
|
||||
# We rely on mount_options having a default; if there wasn't one,
|
||||
# bail out with a meaningful error.
|
||||
if not mount_options_list:
|
||||
libcalamares.utils.warning("No mount options defined, {!s} partitions".format(len(partitions)))
|
||||
return (_("Configuration Error"),
|
||||
_("No <pre>{!s}</pre> configuration is given for <pre>{!s}</pre> to use.")
|
||||
.format("mountOptions", "fstab"))
|
||||
|
||||
generator = FstabGenerator(partitions,
|
||||
root_mount_point,
|
||||
mount_options_list,
|
||||
crypttab_options,
|
||||
tmp_options)
|
||||
|
||||
if swap_choice is not None:
|
||||
libcalamares.job.setprogress(0.2)
|
||||
root_partitions = [ p["fs"].lower() for p in partitions if p["mountPoint"] == "/" ]
|
||||
root_btrfs = (root_partitions[0] == "btrfs") if root_partitions else False
|
||||
create_swapfile(root_mount_point, root_btrfs)
|
||||
|
||||
try:
|
||||
libcalamares.job.setprogress(0.5)
|
||||
return generator.run()
|
||||
finally:
|
||||
libcalamares.job.setprogress(1.0)
|
||||
7
airootfs/usr/lib/calamares/modules/fstab/module.desc
Normal file
7
airootfs/usr/lib/calamares/modules/fstab/module.desc
Normal file
@@ -0,0 +1,7 @@
|
||||
# SPDX-FileCopyrightText: no
|
||||
# SPDX-License-Identifier: CC0-1.0
|
||||
---
|
||||
type: "job"
|
||||
name: "fstab"
|
||||
interface: "python"
|
||||
script: "main.py"
|
||||
16
airootfs/usr/lib/calamares/modules/fstab/test.yaml
Normal file
16
airootfs/usr/lib/calamares/modules/fstab/test.yaml
Normal file
@@ -0,0 +1,16 @@
|
||||
# SPDX-FileCopyrightText: no
|
||||
# SPDX-License-Identifier: CC0-1.0
|
||||
rootMountPoint: /tmp/mount
|
||||
partitions:
|
||||
- device: /dev/sda1
|
||||
fs: ext4
|
||||
mountPoint: /
|
||||
uuid: 2a00f1d5-1217-49a7-bedd-b55c85764732
|
||||
- device: /dev/sda2
|
||||
fs: swap
|
||||
uuid: 59406569-446f-4730-a874-9f6b4b44fee3
|
||||
mountPoint:
|
||||
- device: /dev/sdb1
|
||||
fs: btrfs
|
||||
mountPoint: /home
|
||||
uuid: 59406569-abcd-1234-a874-9f6b4b44fee3
|
||||
25
airootfs/usr/lib/calamares/modules/fstab/test2.yaml
Normal file
25
airootfs/usr/lib/calamares/modules/fstab/test2.yaml
Normal file
@@ -0,0 +1,25 @@
|
||||
# SPDX-FileCopyrightText: no
|
||||
# SPDX-License-Identifier: CC0-1.0
|
||||
#
|
||||
# This test shows how btrfs root would work
|
||||
rootMountPoint: /tmp/mount
|
||||
partitions:
|
||||
- device: /dev/sda1
|
||||
fs: btrfs
|
||||
mountPoint: /
|
||||
uuid: 2a00f1d5-1217-49a7-bedd-b55c85764732
|
||||
- device: /dev/sda2
|
||||
fs: swap
|
||||
uuid: 59406569-446f-4730-a874-9f6b4b44fee3
|
||||
mountPoint:
|
||||
- device: /dev/sdb1
|
||||
fs: btrfs
|
||||
mountPoint: /home
|
||||
uuid: 59406569-abcd-1234-a874-9f6b4b44fee3
|
||||
btrfsSubvolumes:
|
||||
- mountPoint: /
|
||||
subvolume: "@ROOT"
|
||||
- mountPoint: /var
|
||||
subvolume: "@var"
|
||||
- mountPoint: /usr/local
|
||||
subvolume: "@local"
|
||||
@@ -0,0 +1,23 @@
|
||||
# SPDX-FileCopyrightText: 2020 Adriaan de Groot <groot@kde.org>
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
---
|
||||
$schema: https://json-schema.org/schema#
|
||||
$id: https://calamares.io/schemas/grubcfg
|
||||
additionalProperties: false
|
||||
type: object
|
||||
properties:
|
||||
overwrite: { type: boolean, default: false }
|
||||
keep_distributor: { type: boolean, default: false }
|
||||
prefer_grub_d: { type: boolean, default: false }
|
||||
kernel_params: { type: array, items: { type: string } }
|
||||
defaults:
|
||||
type: object
|
||||
additionalProperties: true # Other fields are acceptable
|
||||
properties:
|
||||
GRUB_TIMEOUT: { type: integer }
|
||||
GRUB_DEFAULT: { type: string }
|
||||
GRUB_DISABLE_SUBMENU: { type: boolean, default: true }
|
||||
GRUB_TERMINAL_OUTPUT: { type: string }
|
||||
GRUB_DISABLE_RECOVERY: { type: boolean, default: true }
|
||||
required: [ GRUB_TIMEOUT, GRUB_DEFAULT ]
|
||||
always_use_defaults: { type: boolean, default: false }
|
||||
329
airootfs/usr/lib/calamares/modules/grubcfg/main.py
Normal file
329
airootfs/usr/lib/calamares/modules/grubcfg/main.py
Normal file
@@ -0,0 +1,329 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# === This file is part of Calamares - <https://calamares.io> ===
|
||||
#
|
||||
# SPDX-FileCopyrightText: 2014-2015 Philip Müller <philm@manjaro.org>
|
||||
# SPDX-FileCopyrightText: 2015-2017 Teo Mrnjavac <teo@kde.org>
|
||||
# SPDX-FileCopyrightText: 2017 Alf Gaida <agaida@siduction.org>
|
||||
# SPDX-FileCopyrightText: 2017 2019, Adriaan de Groot <groot@kde.org>
|
||||
# SPDX-FileCopyrightText: 2017-2018 Gabriel Craciunescu <crazy@frugalware.org>
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#
|
||||
# Calamares is Free Software: see the License-Identifier above.
|
||||
#
|
||||
|
||||
import libcalamares
|
||||
import fileinput
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
|
||||
import gettext
|
||||
_ = gettext.translation("calamares-python",
|
||||
localedir=libcalamares.utils.gettext_path(),
|
||||
languages=libcalamares.utils.gettext_languages(),
|
||||
fallback=True).gettext
|
||||
|
||||
|
||||
def pretty_name():
|
||||
return _("Configure GRUB.")
|
||||
|
||||
|
||||
def get_grub_config_path(root_mount_point):
|
||||
"""
|
||||
Figures out where to put the grub config files. Returns
|
||||
a the full path of a file inside that
|
||||
directory, as "the config file".
|
||||
|
||||
Returns a path into @p root_mount_point.
|
||||
"""
|
||||
default_dir = os.path.join(root_mount_point, "etc/default")
|
||||
default_config_file = "grub"
|
||||
|
||||
if "prefer_grub_d" in libcalamares.job.configuration and libcalamares.job.configuration["prefer_grub_d"]:
|
||||
possible_dir = os.path.join(root_mount_point, "etc/default/grub.d")
|
||||
if os.path.exists(possible_dir) and os.path.isdir(possible_dir):
|
||||
default_dir = possible_dir
|
||||
default_config_file = "00calamares.cfg"
|
||||
|
||||
if not os.path.exists(default_dir):
|
||||
try:
|
||||
os.mkdir(default_dir)
|
||||
except Exception as error:
|
||||
# exception as error is still redundant, but it print out the error
|
||||
# identify a solution for each exception and
|
||||
# if possible and code it within.
|
||||
libcalamares.utils.debug(f"Failed to create {default_dir}")
|
||||
libcalamares.utils.debug(f"{error}")
|
||||
raise
|
||||
|
||||
return os.path.join(default_dir, default_config_file)
|
||||
|
||||
|
||||
def get_zfs_root():
|
||||
"""
|
||||
Looks in global storage to find the zfs root
|
||||
|
||||
:return: A string containing the path to the zfs root or None if it is not found
|
||||
"""
|
||||
|
||||
zfs = libcalamares.globalstorage.value("zfsDatasets")
|
||||
|
||||
if not zfs:
|
||||
libcalamares.utils.warning("Failed to locate zfs dataset list")
|
||||
return None
|
||||
|
||||
# Find the root dataset
|
||||
for dataset in zfs:
|
||||
try:
|
||||
if dataset["mountpoint"] == "/":
|
||||
return dataset["zpool"] + "/" + dataset["dsName"]
|
||||
except KeyError:
|
||||
# This should be impossible
|
||||
libcalamares.utils.warning("Internal error handling zfs dataset")
|
||||
raise
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def update_existing_config(default_grub, grub_config_items):
|
||||
"""
|
||||
Updates the existing grub configuration file with any items present in @p grub_config_items
|
||||
|
||||
Items that exist in the file will be updated and new items will be appended to the end
|
||||
|
||||
:param default_grub: The absolute path to the grub config file
|
||||
:param grub_config_items: A dict holding the key value pairs representing the items
|
||||
"""
|
||||
|
||||
default_grub_orig = default_grub + ".calamares"
|
||||
shutil.move(default_grub, default_grub_orig)
|
||||
|
||||
with open(default_grub, "w") as grub_file:
|
||||
with open(default_grub_orig, "r") as grub_orig_file:
|
||||
for line in grub_orig_file.readlines():
|
||||
line = line.strip()
|
||||
if "=" in line:
|
||||
# This may be a key, strip the leading comment if it has one
|
||||
key = line.lstrip("#").split("=")[0].strip()
|
||||
|
||||
# check if this is noe of the keys we care about
|
||||
if key in grub_config_items.keys():
|
||||
print(f"{key}={grub_config_items[key]}", file=grub_file)
|
||||
del grub_config_items[key]
|
||||
else:
|
||||
print(line, file=grub_file)
|
||||
else:
|
||||
print(line, file=grub_file)
|
||||
|
||||
if len(grub_config_items) != 0:
|
||||
for dict_key, dict_val in grub_config_items.items():
|
||||
print(f"{dict_key}={dict_val}", file=grub_file)
|
||||
|
||||
os.remove(default_grub_orig)
|
||||
|
||||
|
||||
def modify_grub_default(partitions, root_mount_point, distributor):
|
||||
"""
|
||||
Configures '/etc/default/grub' for hibernation and plymouth.
|
||||
|
||||
@see bootloader/main.py, for similar handling of kernel parameters
|
||||
|
||||
:param partitions:
|
||||
:param root_mount_point:
|
||||
:param distributor: name of the distributor to fill in for
|
||||
GRUB_DISTRIBUTOR. Must be a string. If the job setting
|
||||
*keep_distributor* is set, then this is only used if no
|
||||
GRUB_DISTRIBUTOR is found at all (otherwise, when *keep_distributor*
|
||||
is set, the GRUB_DISTRIBUTOR lines are left unchanged).
|
||||
If *keep_distributor* is unset or false, then GRUB_DISTRIBUTOR
|
||||
is always updated to set this value.
|
||||
:return:
|
||||
"""
|
||||
default_grub = get_grub_config_path(root_mount_point)
|
||||
distributor = distributor.replace("'", "'\\''")
|
||||
dracut_bin = libcalamares.utils.target_env_call(
|
||||
["sh", "-c", "which dracut"]
|
||||
)
|
||||
plymouth_bin = libcalamares.utils.target_env_call(
|
||||
["sh", "-c", "which plymouth"]
|
||||
)
|
||||
uses_systemd_hook = libcalamares.utils.target_env_call(
|
||||
["sh", "-c", "grep -q \"^HOOKS.*systemd\" /etc/mkinitcpio.conf"]
|
||||
) == 0
|
||||
# Shell exit value 0 means success
|
||||
have_plymouth = plymouth_bin == 0
|
||||
use_systemd_naming = dracut_bin == 0 or uses_systemd_hook
|
||||
|
||||
use_splash = ""
|
||||
swap_uuid = ""
|
||||
swap_outer_uuid = ""
|
||||
swap_outer_mappername = None
|
||||
no_save_default = False
|
||||
unencrypted_separate_boot = any(p["mountPoint"] == "/boot" and "luksMapperName" not in p for p in partitions)
|
||||
# If there is no dracut, and the root partition is ZFS, this gets set below
|
||||
zfs_root_path = None
|
||||
|
||||
for partition in partitions:
|
||||
if partition["mountPoint"] in ("/", "/boot") and partition["fs"] in ("btrfs", "f2fs", "zfs"):
|
||||
no_save_default = True
|
||||
break
|
||||
|
||||
if have_plymouth:
|
||||
use_splash = "splash"
|
||||
|
||||
cryptdevice_params = []
|
||||
|
||||
if use_systemd_naming:
|
||||
for partition in partitions:
|
||||
if partition["fs"] == "linuxswap" and not partition.get("claimed", None):
|
||||
# Skip foreign swap
|
||||
continue
|
||||
has_luks = "luksMapperName" in partition
|
||||
if partition["fs"] == "linuxswap" and not has_luks:
|
||||
swap_uuid = partition["uuid"]
|
||||
|
||||
if partition["fs"] == "linuxswap" and has_luks:
|
||||
swap_outer_uuid = partition["luksUuid"]
|
||||
swap_outer_mappername = partition["luksMapperName"]
|
||||
|
||||
if partition["mountPoint"] == "/" and has_luks:
|
||||
cryptdevice_params = [f"rd.luks.uuid={partition['luksUuid']}"]
|
||||
if not unencrypted_separate_boot and uses_systemd_hook:
|
||||
cryptdevice_params.append("rd.luks.key=/crypto_keyfile.bin")
|
||||
else:
|
||||
for partition in partitions:
|
||||
if partition["fs"] == "linuxswap" and not partition.get("claimed", None):
|
||||
# Skip foreign swap
|
||||
continue
|
||||
has_luks = "luksMapperName" in partition
|
||||
if partition["fs"] == "linuxswap" and not has_luks:
|
||||
swap_uuid = partition["uuid"]
|
||||
|
||||
if partition["fs"] == "linuxswap" and has_luks:
|
||||
swap_outer_mappername = partition["luksMapperName"]
|
||||
|
||||
if partition["mountPoint"] == "/" and has_luks:
|
||||
cryptdevice_params = [
|
||||
f"cryptdevice=UUID={partition['luksUuid']}:{partition['luksMapperName']}",
|
||||
f"root=/dev/mapper/{partition['luksMapperName']}"
|
||||
]
|
||||
|
||||
if partition["fs"] == "zfs" and partition["mountPoint"] == "/":
|
||||
zfs_root_path = get_zfs_root()
|
||||
|
||||
kernel_params = libcalamares.job.configuration.get("kernel_params", ["quiet"])
|
||||
|
||||
# Currently, grub doesn't detect this properly so it must be set manually
|
||||
if zfs_root_path:
|
||||
kernel_params.insert(0, "zfs=" + zfs_root_path)
|
||||
|
||||
if cryptdevice_params:
|
||||
kernel_params.extend(cryptdevice_params)
|
||||
|
||||
if use_splash:
|
||||
kernel_params.append(use_splash)
|
||||
|
||||
if swap_uuid:
|
||||
kernel_params.append(f"resume=UUID={swap_uuid}")
|
||||
|
||||
if use_systemd_naming and swap_outer_uuid:
|
||||
kernel_params.append(f"rd.luks.uuid={swap_outer_uuid}")
|
||||
if swap_outer_mappername:
|
||||
kernel_params.append(f"resume=/dev/mapper/{swap_outer_mappername}")
|
||||
|
||||
overwrite = libcalamares.job.configuration.get("overwrite", False)
|
||||
|
||||
grub_config_items = {}
|
||||
# read the lines we need from the existing config
|
||||
if os.path.exists(default_grub) and not overwrite:
|
||||
with open(default_grub, 'r') as grub_file:
|
||||
lines = [x.strip() for x in grub_file.readlines()]
|
||||
|
||||
for line in lines:
|
||||
if line.startswith("GRUB_CMDLINE_LINUX_DEFAULT"):
|
||||
existing_params = re.sub(r"^GRUB_CMDLINE_LINUX_DEFAULT\s*=\s*", "", line).strip("\"'").split()
|
||||
|
||||
for existing_param in existing_params:
|
||||
existing_param_name = existing_param.split("=")[0].strip()
|
||||
|
||||
# Ensure we aren't adding duplicated params
|
||||
param_exists = False
|
||||
for param in kernel_params:
|
||||
if param.split("=")[0].strip() == existing_param_name:
|
||||
param_exists = True
|
||||
break
|
||||
if not param_exists and existing_param_name not in ["quiet", "resume", "splash"]:
|
||||
kernel_params.append(existing_param)
|
||||
|
||||
elif line.startswith("GRUB_DISTRIBUTOR") and libcalamares.job.configuration.get("keep_distributor", False):
|
||||
distributor_parts = line.split("=")
|
||||
if len(distributor_parts) > 1:
|
||||
distributor = distributor_parts[1].strip("'\"")
|
||||
|
||||
# If a filesystem grub can't write to is used, disable save default
|
||||
if no_save_default and line.strip().startswith("GRUB_SAVEDEFAULT"):
|
||||
grub_config_items["GRUB_SAVEDEFAULT"] = "false"
|
||||
|
||||
always_use_defaults = libcalamares.job.configuration.get("always_use_defaults", False)
|
||||
|
||||
# If applicable add the items from defaults to the dict containing the grub config to wirte/modify
|
||||
if always_use_defaults or overwrite or not os.path.exists(default_grub):
|
||||
if "defaults" in libcalamares.job.configuration:
|
||||
for key, value in libcalamares.job.configuration["defaults"].items():
|
||||
if isinstance(value, bool):
|
||||
if value:
|
||||
escaped_value = "true"
|
||||
else:
|
||||
escaped_value = "false"
|
||||
else:
|
||||
escaped_value = str(value).replace("'", "'\\''")
|
||||
|
||||
grub_config_items[key] = f"'{escaped_value}'"
|
||||
|
||||
grub_config_items['GRUB_CMDLINE_LINUX_DEFAULT'] = f"'{' '.join(kernel_params)}'"
|
||||
grub_config_items["GRUB_DISTRIBUTOR"] = f"'{distributor}'"
|
||||
|
||||
if cryptdevice_params and not unencrypted_separate_boot:
|
||||
grub_config_items["GRUB_ENABLE_CRYPTODISK"] = "y"
|
||||
|
||||
if overwrite or not os.path.exists(default_grub) or libcalamares.job.configuration.get("prefer_grub_d", False):
|
||||
with open(default_grub, 'w') as grub_file:
|
||||
for key, value in grub_config_items.items():
|
||||
grub_file.write(f"{key}={value}\n")
|
||||
else:
|
||||
update_existing_config(default_grub, grub_config_items)
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def run():
|
||||
"""
|
||||
Calls routine with given parameters to modify '/etc/default/grub'.
|
||||
|
||||
:return:
|
||||
"""
|
||||
fw_type = libcalamares.globalstorage.value("firmwareType")
|
||||
partitions = libcalamares.globalstorage.value("partitions")
|
||||
root_mount_point = libcalamares.globalstorage.value("rootMountPoint")
|
||||
branding = libcalamares.globalstorage.value("branding")
|
||||
if branding is None:
|
||||
distributor = None
|
||||
else:
|
||||
distributor = branding["bootloaderEntryName"]
|
||||
|
||||
if libcalamares.globalstorage.value("bootLoader") is None and fw_type != "efi":
|
||||
return None
|
||||
|
||||
if fw_type == "efi":
|
||||
esp_found = False
|
||||
|
||||
for partition in partitions:
|
||||
if partition["mountPoint"] == libcalamares.globalstorage.value("efiSystemPartition"):
|
||||
esp_found = True
|
||||
|
||||
if not esp_found:
|
||||
return None
|
||||
|
||||
return modify_grub_default(partitions, root_mount_point, distributor)
|
||||
7
airootfs/usr/lib/calamares/modules/grubcfg/module.desc
Normal file
7
airootfs/usr/lib/calamares/modules/grubcfg/module.desc
Normal file
@@ -0,0 +1,7 @@
|
||||
# SPDX-FileCopyrightText: no
|
||||
# SPDX-License-Identifier: CC0-1.0
|
||||
---
|
||||
type: "job"
|
||||
name: "grubcfg"
|
||||
interface: "python"
|
||||
script: "main.py"
|
||||
BIN
airootfs/usr/lib/calamares/modules/hostinfo/libcalamares_job_hostinfo.so
Executable file
BIN
airootfs/usr/lib/calamares/modules/hostinfo/libcalamares_job_hostinfo.so
Executable file
Binary file not shown.
8
airootfs/usr/lib/calamares/modules/hostinfo/module.desc
Normal file
8
airootfs/usr/lib/calamares/modules/hostinfo/module.desc
Normal file
@@ -0,0 +1,8 @@
|
||||
# AUTO-GENERATED metadata file
|
||||
# Syntax is YAML 1.2
|
||||
---
|
||||
type: "job"
|
||||
name: "hostinfo"
|
||||
interface: "qtplugin"
|
||||
load: "libcalamares_job_hostinfo.so"
|
||||
noconfig: true
|
||||
56
airootfs/usr/lib/calamares/modules/hwclock/main.py
Normal file
56
airootfs/usr/lib/calamares/modules/hwclock/main.py
Normal file
@@ -0,0 +1,56 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# === This file is part of Calamares - <https://calamares.io> ===
|
||||
#
|
||||
# SPDX-FileCopyrightText: 2014-2015 Philip Müller <philm@manjaro.org>
|
||||
# SPDX-FileCopyrightText: 2014 Teo Mrnjavac <teo@kde.org>
|
||||
# SPDX-FileCopyrightText: 2017 Alf Gaida <agaida@siduction.org>
|
||||
# SPDX-FileCopyrightText: 2017-2018 Gabriel Craciunescu <crazy@frugalware.org>
|
||||
# SPDX-FileCopyrightText: 2019 Adriaan de Groot <groot@kde.org>
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#
|
||||
# Calamares is Free Software: see the License-Identifier above.
|
||||
#
|
||||
|
||||
import libcalamares
|
||||
|
||||
import gettext
|
||||
_ = gettext.translation("calamares-python",
|
||||
localedir=libcalamares.utils.gettext_path(),
|
||||
languages=libcalamares.utils.gettext_languages(),
|
||||
fallback=True).gettext
|
||||
|
||||
|
||||
def pretty_name():
|
||||
return _("Setting hardware clock.")
|
||||
|
||||
|
||||
def run():
|
||||
"""
|
||||
Set hardware clock.
|
||||
"""
|
||||
hwclock_rtc = ["hwclock", "--systohc", "--utc"]
|
||||
hwclock_isa = ["hwclock", "--systohc", "--utc", "--directisa"]
|
||||
is_broken_rtc = False
|
||||
is_broken_isa = False
|
||||
|
||||
ret = libcalamares.utils.target_env_call(hwclock_rtc)
|
||||
if ret != 0:
|
||||
is_broken_rtc = True
|
||||
libcalamares.utils.debug("Hwclock returned error code {}".format(ret))
|
||||
libcalamares.utils.debug(" .. RTC method failed, trying ISA bus method.")
|
||||
else:
|
||||
libcalamares.utils.debug("Hwclock set using RTC method.")
|
||||
if is_broken_rtc:
|
||||
ret = libcalamares.utils.target_env_call(hwclock_isa)
|
||||
if ret != 0:
|
||||
is_broken_isa = True
|
||||
libcalamares.utils.debug("Hwclock returned error code {}".format(ret))
|
||||
libcalamares.utils.debug(" .. ISA bus method failed.")
|
||||
else:
|
||||
libcalamares.utils.debug("Hwclock set using ISA bus method.")
|
||||
if is_broken_rtc and is_broken_isa:
|
||||
libcalamares.utils.debug("BIOS or Kernel BUG: Setting hwclock failed.")
|
||||
|
||||
return None
|
||||
8
airootfs/usr/lib/calamares/modules/hwclock/module.desc
Normal file
8
airootfs/usr/lib/calamares/modules/hwclock/module.desc
Normal file
@@ -0,0 +1,8 @@
|
||||
# SPDX-FileCopyrightText: no
|
||||
# SPDX-License-Identifier: CC0-1.0
|
||||
---
|
||||
type: "job"
|
||||
name: "hwclock"
|
||||
interface: "python"
|
||||
script: "main.py"
|
||||
noconfig: true
|
||||
29
airootfs/usr/lib/calamares/modules/initcpio/encrypt_hook
Executable file
29
airootfs/usr/lib/calamares/modules/initcpio/encrypt_hook
Executable file
@@ -0,0 +1,29 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# SPDX-FileCopyrightText: 2016 David McKinney <mckinney@subgraph.com>
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
PREREQ=""
|
||||
|
||||
prereqs()
|
||||
{
|
||||
echo "$PREREQ"
|
||||
}
|
||||
|
||||
case $1 in
|
||||
# get pre-requisites
|
||||
prereqs)
|
||||
prereqs
|
||||
exit 0
|
||||
;;
|
||||
esac
|
||||
|
||||
. /usr/share/initramfs-tools/hook-functions
|
||||
if [ -f /crypto_keyfile.bin ]
|
||||
then
|
||||
cp /crypto_keyfile.bin ${DESTDIR}
|
||||
fi
|
||||
if [ -f /etc/crypttab ]
|
||||
then
|
||||
cp /etc/crypttab ${DESTDIR}/etc/
|
||||
fi
|
||||
25
airootfs/usr/lib/calamares/modules/initcpio/encrypt_hook_nokey
Executable file
25
airootfs/usr/lib/calamares/modules/initcpio/encrypt_hook_nokey
Executable file
@@ -0,0 +1,25 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# SPDX-FileCopyrightText: 2016 David McKinney <mckinney@subgraph.com>
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
PREREQ=""
|
||||
|
||||
prereqs()
|
||||
{
|
||||
echo "$PREREQ"
|
||||
}
|
||||
|
||||
case $1 in
|
||||
# get pre-requisites
|
||||
prereqs)
|
||||
prereqs
|
||||
exit 0
|
||||
;;
|
||||
esac
|
||||
|
||||
. /usr/share/initramfs-tools/hook-functions
|
||||
if [ -f /etc/crypttab ]
|
||||
then
|
||||
cp /etc/crypttab ${DESTDIR}/etc/
|
||||
fi
|
||||
BIN
airootfs/usr/lib/calamares/modules/initcpio/libcalamares_job_initcpio.so
Executable file
BIN
airootfs/usr/lib/calamares/modules/initcpio/libcalamares_job_initcpio.so
Executable file
Binary file not shown.
94
airootfs/usr/lib/calamares/modules/initcpio/main.py
Normal file
94
airootfs/usr/lib/calamares/modules/initcpio/main.py
Normal file
@@ -0,0 +1,94 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# === This file is part of Calamares - <https://calamares.io> ===
|
||||
#
|
||||
# SPDX-FileCopyrightText: 2014 Rohan Garg <rohan@kde.org>
|
||||
# SPDX-FileCopyrightText: 2015 Philip Müller <philm@manjaro.org>
|
||||
# SPDX-FileCopyrightText: 2016 David McKinney <mckinney@subgraph.com>
|
||||
# SPDX-FileCopyrightText: 2016 Kevin Kofler <kevin.kofler@chello.at>
|
||||
# SPDX-FileCopyrightText: 2017 Alf Gaida <agaida@siduction.org>
|
||||
# SPDX-FileCopyrightText: 2017 2019, Adriaan de Groot <groot@kde.org>
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#
|
||||
# Calamares is Free Software: see the License-Identifier above.
|
||||
#
|
||||
|
||||
import libcalamares
|
||||
|
||||
import inspect
|
||||
import os
|
||||
import shutil
|
||||
|
||||
|
||||
import gettext
|
||||
_ = gettext.translation("calamares-python",
|
||||
localedir=libcalamares.utils.gettext_path(),
|
||||
languages=libcalamares.utils.gettext_languages(),
|
||||
fallback=True).gettext
|
||||
|
||||
|
||||
def pretty_name():
|
||||
return _("Configuring initramfs.")
|
||||
|
||||
|
||||
def copy_initramfs_hooks(partitions, root_mount_point):
|
||||
"""
|
||||
Copies initramfs hooks so they are picked up by update-initramfs
|
||||
|
||||
:param partitions:
|
||||
:param root_mount_point:
|
||||
"""
|
||||
encrypt_hook = False
|
||||
unencrypted_separate_boot = False
|
||||
|
||||
for partition in partitions:
|
||||
if partition["mountPoint"] == "/" and "luksMapperName" in partition:
|
||||
encrypt_hook = True
|
||||
|
||||
if (partition["mountPoint"] == "/boot"
|
||||
and "luksMapperName" not in partition):
|
||||
unencrypted_separate_boot = True
|
||||
|
||||
if encrypt_hook:
|
||||
target = "{!s}/usr/share/initramfs-tools/hooks/encrypt_hook".format(
|
||||
root_mount_point)
|
||||
|
||||
# Find where this module is installed
|
||||
_filename = inspect.getframeinfo(inspect.currentframe()).filename
|
||||
_path = os.path.dirname(os.path.abspath(_filename))
|
||||
|
||||
if unencrypted_separate_boot:
|
||||
shutil.copy2(
|
||||
os.path.join(_path, "encrypt_hook_nokey"),
|
||||
target
|
||||
)
|
||||
else:
|
||||
shutil.copy2(
|
||||
os.path.join(_path, "encrypt_hook"),
|
||||
target
|
||||
)
|
||||
os.chmod(target, 0o755)
|
||||
|
||||
|
||||
def run():
|
||||
"""
|
||||
Calls routine with given parameters to configure initramfs
|
||||
|
||||
:return:
|
||||
"""
|
||||
partitions = libcalamares.globalstorage.value("partitions")
|
||||
root_mount_point = libcalamares.globalstorage.value("rootMountPoint")
|
||||
|
||||
if not partitions:
|
||||
libcalamares.utils.warning("partitions is empty, {!s}".format(partitions))
|
||||
return (_("Configuration Error"),
|
||||
_("No partitions are defined for <pre>{!s}</pre> to use." ).format("initramfscfg"))
|
||||
if not root_mount_point:
|
||||
libcalamares.utils.warning("rootMountPoint is empty, {!s}".format(root_mount_point))
|
||||
return (_("Configuration Error"),
|
||||
_("No root mount point is given for <pre>{!s}</pre> to use." ).format("initramfscfg"))
|
||||
|
||||
copy_initramfs_hooks(partitions, root_mount_point)
|
||||
|
||||
return None
|
||||
7
airootfs/usr/lib/calamares/modules/initcpio/module.desc
Normal file
7
airootfs/usr/lib/calamares/modules/initcpio/module.desc
Normal file
@@ -0,0 +1,7 @@
|
||||
# AUTO-GENERATED metadata file
|
||||
# Syntax is YAML 1.2
|
||||
---
|
||||
type: "job"
|
||||
name: "initcpio"
|
||||
interface: "qtplugin"
|
||||
load: "libcalamares_job_initcpio.so"
|
||||
@@ -0,0 +1,17 @@
|
||||
# SPDX-FileCopyrightText: 2023 Evan James <dalto@fastmail.com>
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
---
|
||||
$schema: https://json-schema.org/schema#
|
||||
$id: https://calamares.io/schemas/initcpiocfg
|
||||
additionalProperties: false
|
||||
type: object
|
||||
properties:
|
||||
useSystemdHook: { type: boolean }
|
||||
hooks:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
properties:
|
||||
prepend: { type: array, items: { type: string } }
|
||||
append: { type: array, items: { type: string } }
|
||||
remove: { type: array, items: { type: string } }
|
||||
source: { type: string }
|
||||
221
airootfs/usr/lib/calamares/modules/initcpiocfg/main.py
Normal file
221
airootfs/usr/lib/calamares/modules/initcpiocfg/main.py
Normal file
@@ -0,0 +1,221 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# === This file is part of Calamares - <https://calamares.io> ===
|
||||
#
|
||||
# SPDX-FileCopyrightText: 2014 Rohan Garg <rohan@kde.org>
|
||||
# SPDX-FileCopyrightText: 2015 2019-2020, Philip Müller <philm@manjaro.org>
|
||||
# SPDX-FileCopyrightText: 2017 Alf Gaida <agaida@sidution.org>
|
||||
# SPDX-FileCopyrightText: 2019 Adriaan de Groot <groot@kde.org>
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#
|
||||
# Calamares is Free Software: see the License-Identifier above.
|
||||
#
|
||||
import libcalamares
|
||||
from libcalamares.utils import debug, target_env_call
|
||||
import os
|
||||
from collections import OrderedDict
|
||||
|
||||
import gettext
|
||||
_ = gettext.translation("calamares-python",
|
||||
localedir=libcalamares.utils.gettext_path(),
|
||||
languages=libcalamares.utils.gettext_languages(),
|
||||
fallback=True).gettext
|
||||
|
||||
|
||||
def pretty_name():
|
||||
return _("Configuring mkinitcpio.")
|
||||
|
||||
|
||||
def detect_plymouth():
|
||||
"""
|
||||
Checks existence (runnability) of plymouth in the target system.
|
||||
|
||||
@return True if plymouth exists in the target, False otherwise
|
||||
"""
|
||||
# Used to only check existence of path /usr/bin/plymouth in target
|
||||
return target_env_call(["sh", "-c", "which plymouth"]) == 0
|
||||
|
||||
|
||||
def get_host_initcpio():
|
||||
"""
|
||||
Reads the host system mkinitcpio.conf and returns all
|
||||
the lines from that file, or an empty list if it does
|
||||
not exist.
|
||||
"""
|
||||
hostfile = libcalamares.job.configuration.get("source", None) or "/etc/mkinitcpio.conf"
|
||||
try:
|
||||
with open(hostfile, "r") as mkinitcpio_file:
|
||||
mklins = [x.strip() for x in mkinitcpio_file.readlines()]
|
||||
except FileNotFoundError:
|
||||
libcalamares.utils.debug(f"Could not open host file {hostfile}")
|
||||
mklins = []
|
||||
|
||||
return mklins
|
||||
|
||||
|
||||
def write_mkinitcpio_lines(hooks, modules, files, binaries, root_mount_point):
|
||||
"""
|
||||
Set up mkinitcpio.conf.
|
||||
|
||||
:param hooks:
|
||||
:param modules:
|
||||
:param files:
|
||||
:param root_mount_point:
|
||||
"""
|
||||
mklins = get_host_initcpio()
|
||||
|
||||
target_path = os.path.join(root_mount_point, "etc/mkinitcpio.conf")
|
||||
with open(target_path, "w") as mkinitcpio_file:
|
||||
for line in mklins:
|
||||
# Replace HOOKS, MODULES, BINARIES and FILES lines with what we
|
||||
# have found via find_initcpio_features()
|
||||
if line.startswith("HOOKS"):
|
||||
line = f"HOOKS=({str.join(' ', hooks)})"
|
||||
elif line.startswith("BINARIES"):
|
||||
line = f"BINARIES=({str.join(' ', binaries)})"
|
||||
elif line.startswith("MODULES"):
|
||||
line = f"MODULES=({str.join(' ', modules)})"
|
||||
elif line.startswith("FILES"):
|
||||
line = f"FILES=({str.join(' ', files)})"
|
||||
mkinitcpio_file.write(line + "\n")
|
||||
|
||||
|
||||
def find_initcpio_features(partitions, root_mount_point):
|
||||
"""
|
||||
Returns a tuple (hooks, modules, files) needed to support
|
||||
the given @p partitions (filesystems types, encryption, etc)
|
||||
in the target.
|
||||
|
||||
:param partitions: (from GS)
|
||||
:param root_mount_point: (from GS)
|
||||
|
||||
:return 3-tuple of lists
|
||||
"""
|
||||
hooks = [
|
||||
"autodetect",
|
||||
"microcode",
|
||||
"kms",
|
||||
"modconf",
|
||||
"block",
|
||||
"keyboard",
|
||||
]
|
||||
|
||||
systemd_hook_allowed = libcalamares.job.configuration.get("useSystemdHook", False)
|
||||
|
||||
use_systemd = systemd_hook_allowed and target_env_call(["sh", "-c", "which systemd-cat"]) == 0
|
||||
|
||||
if use_systemd:
|
||||
hooks.insert(0, "systemd")
|
||||
hooks.append("sd-vconsole")
|
||||
else:
|
||||
hooks.insert(0, "udev")
|
||||
hooks.insert(0, "base")
|
||||
hooks.append("keymap")
|
||||
hooks.append("consolefont")
|
||||
|
||||
hooks_map = libcalamares.job.configuration.get("hooks", None)
|
||||
if not hooks_map:
|
||||
hooks_map = dict()
|
||||
hooks_prepend = hooks_map.get("prepend", None) or []
|
||||
hooks_append = hooks_map.get("append", None) or []
|
||||
hooks_remove = hooks_map.get("remove", None) or []
|
||||
|
||||
modules = []
|
||||
files = []
|
||||
binaries = []
|
||||
|
||||
swap_uuid = ""
|
||||
uses_btrfs = False
|
||||
uses_zfs = False
|
||||
uses_lvm2 = False
|
||||
encrypt_hook = False
|
||||
openswap_hook = False
|
||||
unencrypted_separate_boot = False
|
||||
|
||||
# It is important that the plymouth hook comes before any encrypt hook
|
||||
if detect_plymouth():
|
||||
hooks.append("plymouth")
|
||||
|
||||
for partition in partitions:
|
||||
if partition["fs"] == "linuxswap" and not partition.get("claimed", None):
|
||||
# Skip foreign swap
|
||||
continue
|
||||
|
||||
if partition["fs"] == "linuxswap":
|
||||
swap_uuid = partition["uuid"]
|
||||
if "luksMapperName" in partition:
|
||||
openswap_hook = True
|
||||
|
||||
if partition["fs"] == "btrfs":
|
||||
uses_btrfs = True
|
||||
|
||||
# In addition to checking the filesystem, check to ensure that zfs is enabled
|
||||
if partition["fs"] == "zfs" and libcalamares.globalstorage.contains("zfsPoolInfo"):
|
||||
uses_zfs = True
|
||||
|
||||
if "lvm2" in partition["fs"]:
|
||||
uses_lvm2 = True
|
||||
|
||||
if partition["mountPoint"] == "/" and "luksMapperName" in partition:
|
||||
encrypt_hook = True
|
||||
|
||||
if partition["mountPoint"] == "/boot" and "luksMapperName" not in partition:
|
||||
unencrypted_separate_boot = True
|
||||
|
||||
if partition["mountPoint"] == "/usr":
|
||||
hooks.append("usr")
|
||||
|
||||
if encrypt_hook:
|
||||
if use_systemd:
|
||||
hooks.append("sd-encrypt")
|
||||
else:
|
||||
hooks.append("encrypt")
|
||||
crypto_file = "crypto_keyfile.bin"
|
||||
if not unencrypted_separate_boot and os.path.isfile(os.path.join(root_mount_point, crypto_file)):
|
||||
files.append(f"/{crypto_file}")
|
||||
|
||||
if uses_lvm2:
|
||||
hooks.append("lvm2")
|
||||
|
||||
if uses_zfs:
|
||||
hooks.append("zfs")
|
||||
|
||||
if swap_uuid != "":
|
||||
if encrypt_hook and openswap_hook:
|
||||
hooks.extend(["openswap"])
|
||||
hooks.extend(["resume", "filesystems"])
|
||||
else:
|
||||
hooks.extend(["filesystems"])
|
||||
|
||||
if not uses_btrfs:
|
||||
hooks.append("fsck")
|
||||
|
||||
# Modify according to the keys in the configuration
|
||||
hooks = [h for h in (hooks_prepend + hooks + hooks_append) if h not in hooks_remove]
|
||||
|
||||
return hooks, modules, files, binaries
|
||||
|
||||
|
||||
def run():
|
||||
"""
|
||||
Calls routine with given parameters to modify "/etc/mkinitcpio.conf".
|
||||
|
||||
:return:
|
||||
"""
|
||||
partitions = libcalamares.globalstorage.value("partitions")
|
||||
root_mount_point = libcalamares.globalstorage.value("rootMountPoint")
|
||||
|
||||
if not partitions:
|
||||
libcalamares.utils.warning(f"partitions are empty, {partitions}")
|
||||
return (_("Configuration Error"),
|
||||
_("No partitions are defined for <pre>initcpiocfg</pre>."))
|
||||
if not root_mount_point:
|
||||
libcalamares.utils.warning(f"rootMountPoint is empty, {root_mount_point}")
|
||||
return (_("Configuration Error"),
|
||||
_("No root mount point for <pre>initcpiocfg</pre>."))
|
||||
|
||||
hooks, modules, files, binaries = find_initcpio_features(partitions, root_mount_point)
|
||||
write_mkinitcpio_lines(hooks, modules, files, binaries, root_mount_point)
|
||||
|
||||
return None
|
||||
12
airootfs/usr/lib/calamares/modules/initcpiocfg/module.desc
Normal file
12
airootfs/usr/lib/calamares/modules/initcpiocfg/module.desc
Normal file
@@ -0,0 +1,12 @@
|
||||
# SPDX-FileCopyrightText: no
|
||||
# SPDX-License-Identifier: CC0-1.0
|
||||
#
|
||||
# Writes a mkinitcpio.conf into the target system. It copies
|
||||
# the host system's /etc/mkinitcpio.conf, and replaces any
|
||||
# HOOKS, MODULES, and FILES lines with calculated values
|
||||
# based on what the installation (seems to) need.
|
||||
---
|
||||
type: "job"
|
||||
name: "initcpiocfg"
|
||||
interface: "python"
|
||||
script: "main.py"
|
||||
7
airootfs/usr/lib/calamares/modules/initcpiocfg/test.yaml
Normal file
7
airootfs/usr/lib/calamares/modules/initcpiocfg/test.yaml
Normal file
@@ -0,0 +1,7 @@
|
||||
# SPDX-FileCopyrightText: no
|
||||
# SPDX-License-Identifier: CC0-1.0
|
||||
rootMountPoint: /tmp/mount
|
||||
partitions:
|
||||
- fs: ext4
|
||||
mountPoint: "/"
|
||||
|
||||
29
airootfs/usr/lib/calamares/modules/initramfs/encrypt_hook
Executable file
29
airootfs/usr/lib/calamares/modules/initramfs/encrypt_hook
Executable file
@@ -0,0 +1,29 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# SPDX-FileCopyrightText: 2016 David McKinney <mckinney@subgraph.com>
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
PREREQ=""
|
||||
|
||||
prereqs()
|
||||
{
|
||||
echo "$PREREQ"
|
||||
}
|
||||
|
||||
case $1 in
|
||||
# get pre-requisites
|
||||
prereqs)
|
||||
prereqs
|
||||
exit 0
|
||||
;;
|
||||
esac
|
||||
|
||||
. /usr/share/initramfs-tools/hook-functions
|
||||
if [ -f /crypto_keyfile.bin ]
|
||||
then
|
||||
cp /crypto_keyfile.bin ${DESTDIR}
|
||||
fi
|
||||
if [ -f /etc/crypttab ]
|
||||
then
|
||||
cp /etc/crypttab ${DESTDIR}/etc/
|
||||
fi
|
||||
25
airootfs/usr/lib/calamares/modules/initramfs/encrypt_hook_nokey
Executable file
25
airootfs/usr/lib/calamares/modules/initramfs/encrypt_hook_nokey
Executable file
@@ -0,0 +1,25 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# SPDX-FileCopyrightText: 2016 David McKinney <mckinney@subgraph.com>
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
PREREQ=""
|
||||
|
||||
prereqs()
|
||||
{
|
||||
echo "$PREREQ"
|
||||
}
|
||||
|
||||
case $1 in
|
||||
# get pre-requisites
|
||||
prereqs)
|
||||
prereqs
|
||||
exit 0
|
||||
;;
|
||||
esac
|
||||
|
||||
. /usr/share/initramfs-tools/hook-functions
|
||||
if [ -f /etc/crypttab ]
|
||||
then
|
||||
cp /etc/crypttab ${DESTDIR}/etc/
|
||||
fi
|
||||
94
airootfs/usr/lib/calamares/modules/initramfs/main.py
Normal file
94
airootfs/usr/lib/calamares/modules/initramfs/main.py
Normal file
@@ -0,0 +1,94 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# === This file is part of Calamares - <https://calamares.io> ===
|
||||
#
|
||||
# SPDX-FileCopyrightText: 2014 Rohan Garg <rohan@kde.org>
|
||||
# SPDX-FileCopyrightText: 2015 Philip Müller <philm@manjaro.org>
|
||||
# SPDX-FileCopyrightText: 2016 David McKinney <mckinney@subgraph.com>
|
||||
# SPDX-FileCopyrightText: 2016 Kevin Kofler <kevin.kofler@chello.at>
|
||||
# SPDX-FileCopyrightText: 2017 Alf Gaida <agaida@siduction.org>
|
||||
# SPDX-FileCopyrightText: 2017 2019, Adriaan de Groot <groot@kde.org>
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#
|
||||
# Calamares is Free Software: see the License-Identifier above.
|
||||
#
|
||||
|
||||
import libcalamares
|
||||
|
||||
import inspect
|
||||
import os
|
||||
import shutil
|
||||
|
||||
|
||||
import gettext
|
||||
_ = gettext.translation("calamares-python",
|
||||
localedir=libcalamares.utils.gettext_path(),
|
||||
languages=libcalamares.utils.gettext_languages(),
|
||||
fallback=True).gettext
|
||||
|
||||
|
||||
def pretty_name():
|
||||
return _("Configuring initramfs.")
|
||||
|
||||
|
||||
def copy_initramfs_hooks(partitions, root_mount_point):
|
||||
"""
|
||||
Copies initramfs hooks so they are picked up by update-initramfs
|
||||
|
||||
:param partitions:
|
||||
:param root_mount_point:
|
||||
"""
|
||||
encrypt_hook = False
|
||||
unencrypted_separate_boot = False
|
||||
|
||||
for partition in partitions:
|
||||
if partition["mountPoint"] == "/" and "luksMapperName" in partition:
|
||||
encrypt_hook = True
|
||||
|
||||
if (partition["mountPoint"] == "/boot"
|
||||
and "luksMapperName" not in partition):
|
||||
unencrypted_separate_boot = True
|
||||
|
||||
if encrypt_hook:
|
||||
target = "{!s}/usr/share/initramfs-tools/hooks/encrypt_hook".format(
|
||||
root_mount_point)
|
||||
|
||||
# Find where this module is installed
|
||||
_filename = inspect.getframeinfo(inspect.currentframe()).filename
|
||||
_path = os.path.dirname(os.path.abspath(_filename))
|
||||
|
||||
if unencrypted_separate_boot:
|
||||
shutil.copy2(
|
||||
os.path.join(_path, "encrypt_hook_nokey"),
|
||||
target
|
||||
)
|
||||
else:
|
||||
shutil.copy2(
|
||||
os.path.join(_path, "encrypt_hook"),
|
||||
target
|
||||
)
|
||||
os.chmod(target, 0o755)
|
||||
|
||||
|
||||
def run():
|
||||
"""
|
||||
Calls routine with given parameters to configure initramfs
|
||||
|
||||
:return:
|
||||
"""
|
||||
partitions = libcalamares.globalstorage.value("partitions")
|
||||
root_mount_point = libcalamares.globalstorage.value("rootMountPoint")
|
||||
|
||||
if not partitions:
|
||||
libcalamares.utils.warning("partitions is empty, {!s}".format(partitions))
|
||||
return (_("Configuration Error"),
|
||||
_("No partitions are defined for <pre>{!s}</pre> to use." ).format("initramfscfg"))
|
||||
if not root_mount_point:
|
||||
libcalamares.utils.warning("rootMountPoint is empty, {!s}".format(root_mount_point))
|
||||
return (_("Configuration Error"),
|
||||
_("No root mount point is given for <pre>{!s}</pre> to use." ).format("initramfscfg"))
|
||||
|
||||
copy_initramfs_hooks(partitions, root_mount_point)
|
||||
|
||||
return None
|
||||
8
airootfs/usr/lib/calamares/modules/initramfs/module.desc
Normal file
8
airootfs/usr/lib/calamares/modules/initramfs/module.desc
Normal file
@@ -0,0 +1,8 @@
|
||||
# SPDX-FileCopyrightText: no
|
||||
# SPDX-License-Identifier: CC0-1.0
|
||||
---
|
||||
type: "job"
|
||||
name: "initramfs"
|
||||
interface: "python"
|
||||
script: "main.py"
|
||||
|
||||
BIN
airootfs/usr/lib/calamares/modules/keyboard/libcalamares_viewmodule_keyboard.so
Executable file
BIN
airootfs/usr/lib/calamares/modules/keyboard/libcalamares_viewmodule_keyboard.so
Executable file
Binary file not shown.
7
airootfs/usr/lib/calamares/modules/keyboard/module.desc
Normal file
7
airootfs/usr/lib/calamares/modules/keyboard/module.desc
Normal file
@@ -0,0 +1,7 @@
|
||||
# AUTO-GENERATED metadata file
|
||||
# Syntax is YAML 1.2
|
||||
---
|
||||
type: "viewmodule"
|
||||
name: "keyboard"
|
||||
interface: "qtplugin"
|
||||
load: "libcalamares_viewmodule_keyboard.so"
|
||||
Binary file not shown.
7
airootfs/usr/lib/calamares/modules/keyboardq/module.desc
Normal file
7
airootfs/usr/lib/calamares/modules/keyboardq/module.desc
Normal file
@@ -0,0 +1,7 @@
|
||||
# AUTO-GENERATED metadata file
|
||||
# Syntax is YAML 1.2
|
||||
---
|
||||
type: "viewmodule"
|
||||
name: "keyboardq"
|
||||
interface: "qtplugin"
|
||||
load: "libcalamares_viewmodule_keyboardq.so"
|
||||
BIN
airootfs/usr/lib/calamares/modules/license/libcalamares_viewmodule_license.so
Executable file
BIN
airootfs/usr/lib/calamares/modules/license/libcalamares_viewmodule_license.so
Executable file
Binary file not shown.
7
airootfs/usr/lib/calamares/modules/license/module.desc
Normal file
7
airootfs/usr/lib/calamares/modules/license/module.desc
Normal file
@@ -0,0 +1,7 @@
|
||||
# AUTO-GENERATED metadata file
|
||||
# Syntax is YAML 1.2
|
||||
---
|
||||
type: "viewmodule"
|
||||
name: "license"
|
||||
interface: "qtplugin"
|
||||
load: "libcalamares_viewmodule_license.so"
|
||||
BIN
airootfs/usr/lib/calamares/modules/locale/libcalamares_viewmodule_locale.so
Executable file
BIN
airootfs/usr/lib/calamares/modules/locale/libcalamares_viewmodule_locale.so
Executable file
Binary file not shown.
7
airootfs/usr/lib/calamares/modules/locale/module.desc
Normal file
7
airootfs/usr/lib/calamares/modules/locale/module.desc
Normal file
@@ -0,0 +1,7 @@
|
||||
# AUTO-GENERATED metadata file
|
||||
# Syntax is YAML 1.2
|
||||
---
|
||||
type: "viewmodule"
|
||||
name: "locale"
|
||||
interface: "qtplugin"
|
||||
load: "libcalamares_viewmodule_locale.so"
|
||||
175
airootfs/usr/lib/calamares/modules/localecfg/main.py
Normal file
175
airootfs/usr/lib/calamares/modules/localecfg/main.py
Normal file
@@ -0,0 +1,175 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# === This file is part of Calamares - <https://calamares.io> ===
|
||||
#
|
||||
# SPDX-FileCopyrightText: 2014 Anke Boersma <demm@kaosx.us>
|
||||
# SPDX-FileCopyrightText: 2015 Philip Müller <philm@manjaro.org>
|
||||
# SPDX-FileCopyrightText: 2016 Teo Mrnjavac <teo@kde.org>
|
||||
# SPDX-FileCopyrightText: 2018 AlmAck <gluca86@gmail.com>
|
||||
# SPDX-FileCopyrightText: 2018-2019 Adriaan de Groot <groot@kde.org>
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#
|
||||
# Calamares is Free Software: see the License-Identifier above.
|
||||
#
|
||||
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
|
||||
|
||||
import libcalamares
|
||||
|
||||
import gettext
|
||||
_ = gettext.translation("calamares-python",
|
||||
localedir=libcalamares.utils.gettext_path(),
|
||||
languages=libcalamares.utils.gettext_languages(),
|
||||
fallback=True).gettext
|
||||
|
||||
|
||||
def pretty_name():
|
||||
return _("Configuring locales.")
|
||||
|
||||
|
||||
RE_IS_COMMENT = re.compile("^ *#")
|
||||
def is_comment(line):
|
||||
"""
|
||||
Does the @p line look like a comment? Whitespace, followed by a #
|
||||
is a comment-only line.
|
||||
"""
|
||||
return bool(RE_IS_COMMENT.match(line))
|
||||
|
||||
RE_TRAILING_COMMENT = re.compile("#.*$")
|
||||
RE_REST_OF_LINE = re.compile("\\s.*$")
|
||||
def extract_locale(line):
|
||||
"""
|
||||
Extracts a locale from the @p line, and returns a pair of
|
||||
(extracted-locale, uncommented line). The locale is the
|
||||
first word of the line after uncommenting (in the human-
|
||||
readable text explanation at the top of most /etc/locale.gen
|
||||
files, the locales may be bogus -- either "" or e.g. "Configuration")
|
||||
"""
|
||||
# Remove leading spaces and comment signs
|
||||
line = RE_IS_COMMENT.sub("", line)
|
||||
uncommented = line.strip()
|
||||
fields = RE_TRAILING_COMMENT.sub("", uncommented).strip().split()
|
||||
if len(fields) != 2:
|
||||
# Not exactly two fields, can't be a proper locale line
|
||||
return "", uncommented
|
||||
else:
|
||||
# Drop all but first field
|
||||
locale = RE_REST_OF_LINE.sub("", uncommented)
|
||||
return locale, uncommented
|
||||
|
||||
|
||||
def rewrite_locale_gen(srcfilename, destfilename, locale_conf):
|
||||
"""
|
||||
Copies a locale.gen file from @p srcfilename to @p destfilename
|
||||
(this may be the same name), enabling those locales that can
|
||||
be found in the map @p locale_conf. Also always enables en_US.UTF-8.
|
||||
"""
|
||||
en_us_locale = 'en_US.UTF-8'
|
||||
|
||||
# Get entire source-file contents
|
||||
text = []
|
||||
try:
|
||||
with open(srcfilename, "r") as gen:
|
||||
text = gen.readlines()
|
||||
except FileNotFoundError:
|
||||
# That's ok, the file doesn't exist so assume empty
|
||||
pass
|
||||
|
||||
# we want unique values, so locale_values should have 1 or 2 items
|
||||
locale_values = set(locale_conf.values())
|
||||
locale_values.add(en_us_locale) # Always enable en_US as well
|
||||
|
||||
enabled_locales = {}
|
||||
seen_locales = set()
|
||||
|
||||
# Write source out again, enabling some
|
||||
with open(destfilename, "w") as gen:
|
||||
for line in text:
|
||||
c = is_comment(line)
|
||||
locale, uncommented = extract_locale(line)
|
||||
|
||||
# Non-comment lines are preserved, and comment lines
|
||||
# may be enabled if they match a desired locale
|
||||
if not c:
|
||||
seen_locales.add(locale)
|
||||
else:
|
||||
for locale_value in locale_values:
|
||||
if locale.startswith(locale_value):
|
||||
enabled_locales[locale] = uncommented
|
||||
gen.write(line)
|
||||
|
||||
gen.write("\n###\n#\n# Locales enabled by Calamares\n")
|
||||
for locale, line in enabled_locales.items():
|
||||
if locale not in seen_locales:
|
||||
gen.write(line + "\n")
|
||||
seen_locales.add(locale)
|
||||
|
||||
for locale in locale_values:
|
||||
if locale not in seen_locales:
|
||||
gen.write("# Missing: %s\n" % locale)
|
||||
|
||||
|
||||
def run():
|
||||
""" Create locale """
|
||||
import libcalamares
|
||||
|
||||
locale_conf = libcalamares.globalstorage.value("localeConf")
|
||||
|
||||
if not locale_conf:
|
||||
locale_conf = {
|
||||
'LANG': 'en_US.UTF-8',
|
||||
'LC_NUMERIC': 'en_US.UTF-8',
|
||||
'LC_TIME': 'en_US.UTF-8',
|
||||
'LC_MONETARY': 'en_US.UTF-8',
|
||||
'LC_PAPER': 'en_US.UTF-8',
|
||||
'LC_NAME': 'en_US.UTF-8',
|
||||
'LC_ADDRESS': 'en_US.UTF-8',
|
||||
'LC_TELEPHONE': 'en_US.UTF-8',
|
||||
'LC_MEASUREMENT': 'en_US.UTF-8',
|
||||
'LC_IDENTIFICATION': 'en_US.UTF-8'
|
||||
}
|
||||
|
||||
install_path = libcalamares.globalstorage.value("rootMountPoint")
|
||||
|
||||
if install_path is None:
|
||||
libcalamares.utils.warning("rootMountPoint is empty, {!s}".format(install_path))
|
||||
return (_("Configuration Error"),
|
||||
_("No root mount point is given for <pre>{!s}</pre> to use." ).format("localecfg"))
|
||||
|
||||
target_locale_gen = "{!s}/etc/locale.gen".format(install_path)
|
||||
target_locale_gen_bak = target_locale_gen + ".bak"
|
||||
target_locale_conf_path = "{!s}/etc/locale.conf".format(install_path)
|
||||
target_etc_default_path = "{!s}/etc/default".format(install_path)
|
||||
|
||||
# restore backup if available
|
||||
if os.path.exists(target_locale_gen_bak):
|
||||
shutil.copy2(target_locale_gen_bak, target_locale_gen)
|
||||
libcalamares.utils.debug("Restored backup {!s} -> {!s}"
|
||||
.format(target_locale_gen_bak, target_locale_gen))
|
||||
|
||||
# run locale-gen if detected; this *will* cause an exception
|
||||
# if the live system has locale.gen, but the target does not:
|
||||
# in that case, fix your installation filesystem.
|
||||
if os.path.exists('/etc/locale.gen'):
|
||||
rewrite_locale_gen(target_locale_gen, target_locale_gen, locale_conf)
|
||||
libcalamares.utils.target_env_call(['locale-gen'])
|
||||
libcalamares.utils.debug('{!s} done'.format(target_locale_gen))
|
||||
|
||||
# write /etc/locale.conf
|
||||
with open(target_locale_conf_path, "w") as lcf:
|
||||
for k, v in locale_conf.items():
|
||||
lcf.write("{!s}={!s}\n".format(k, v))
|
||||
libcalamares.utils.debug('{!s} done'.format(target_locale_conf_path))
|
||||
|
||||
# write /etc/default/locale if /etc/default exists and is a dir
|
||||
if os.path.isdir(target_etc_default_path):
|
||||
with open(os.path.join(target_etc_default_path, "locale"), "w") as edl:
|
||||
for k, v in locale_conf.items():
|
||||
edl.write("{!s}={!s}\n".format(k, v))
|
||||
libcalamares.utils.debug('{!s} done'.format(target_etc_default_path))
|
||||
|
||||
return None
|
||||
11
airootfs/usr/lib/calamares/modules/localecfg/module.desc
Normal file
11
airootfs/usr/lib/calamares/modules/localecfg/module.desc
Normal file
@@ -0,0 +1,11 @@
|
||||
# SPDX-FileCopyrightText: no
|
||||
# SPDX-License-Identifier: CC0-1.0
|
||||
# Enable the configured locales (those set by the user on the
|
||||
# user page) in /etc/locale.gen, if they are available in the
|
||||
# target system.
|
||||
---
|
||||
type: "job"
|
||||
name: "localecfg"
|
||||
interface: "python"
|
||||
script: "main.py"
|
||||
noconfig: true
|
||||
BIN
airootfs/usr/lib/calamares/modules/localeq/libcalamares_viewmodule_localeq.so
Executable file
BIN
airootfs/usr/lib/calamares/modules/localeq/libcalamares_viewmodule_localeq.so
Executable file
Binary file not shown.
7
airootfs/usr/lib/calamares/modules/localeq/module.desc
Normal file
7
airootfs/usr/lib/calamares/modules/localeq/module.desc
Normal file
@@ -0,0 +1,7 @@
|
||||
# AUTO-GENERATED metadata file
|
||||
# Syntax is YAML 1.2
|
||||
---
|
||||
type: "viewmodule"
|
||||
name: "localeq"
|
||||
interface: "qtplugin"
|
||||
load: "libcalamares_viewmodule_localeq.so"
|
||||
Binary file not shown.
@@ -0,0 +1,7 @@
|
||||
# AUTO-GENERATED metadata file
|
||||
# Syntax is YAML 1.2
|
||||
---
|
||||
type: "job"
|
||||
name: "luksbootkeyfile"
|
||||
interface: "qtplugin"
|
||||
load: "libcalamares_job_luksbootkeyfile.so"
|
||||
Binary file not shown.
@@ -0,0 +1,7 @@
|
||||
# AUTO-GENERATED metadata file
|
||||
# Syntax is YAML 1.2
|
||||
---
|
||||
type: "job"
|
||||
name: "luksopenswaphookcfg"
|
||||
interface: "qtplugin"
|
||||
load: "libcalamares_job_luksopenswaphookcfg.so"
|
||||
BIN
airootfs/usr/lib/calamares/modules/machineid/libcalamares_job_machineid.so
Executable file
BIN
airootfs/usr/lib/calamares/modules/machineid/libcalamares_job_machineid.so
Executable file
Binary file not shown.
7
airootfs/usr/lib/calamares/modules/machineid/module.desc
Normal file
7
airootfs/usr/lib/calamares/modules/machineid/module.desc
Normal file
@@ -0,0 +1,7 @@
|
||||
# AUTO-GENERATED metadata file
|
||||
# Syntax is YAML 1.2
|
||||
---
|
||||
type: "job"
|
||||
name: "machineid"
|
||||
interface: "qtplugin"
|
||||
load: "libcalamares_job_machineid.so"
|
||||
50
airootfs/usr/lib/calamares/modules/mkinitfs/main.py
Normal file
50
airootfs/usr/lib/calamares/modules/mkinitfs/main.py
Normal file
@@ -0,0 +1,50 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# === This file is part of Calamares - <https://calamares.io> ===
|
||||
#
|
||||
# SPDX-FileCopyrightText: 2014-2015 Philip Müller <philm@manjaro.org>
|
||||
# SPDX-FileCopyrightText: 2014 Teo Mrnjavac <teo@kde.org>
|
||||
# SPDX-FileCopyrightText: 2017 Alf Gaida <agaid@siduction.org>
|
||||
# SPDX-FileCopyrightText: 2019 Adriaan de Groot <groot@kde.org>
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#
|
||||
# Calamares is Free Software: see the License-Identifier above.
|
||||
#
|
||||
|
||||
import libcalamares
|
||||
from libcalamares.utils import target_env_call
|
||||
|
||||
|
||||
import gettext
|
||||
_ = gettext.translation("calamares-python",
|
||||
localedir=libcalamares.utils.gettext_path(),
|
||||
languages=libcalamares.utils.gettext_languages(),
|
||||
fallback=True).gettext
|
||||
|
||||
|
||||
def pretty_name():
|
||||
return _("Creating initramfs with mkinitfs.")
|
||||
|
||||
|
||||
def run_mkinitfs():
|
||||
"""
|
||||
Creates initramfs, even when initramfs already exists.
|
||||
|
||||
:return:
|
||||
"""
|
||||
return target_env_call(['mkinitfs'])
|
||||
|
||||
|
||||
def run():
|
||||
"""
|
||||
Starts routine to create initramfs. It passes back the exit code
|
||||
if it fails.
|
||||
|
||||
:return:
|
||||
"""
|
||||
return_code = run_mkinitfs()
|
||||
|
||||
if return_code != 0:
|
||||
return ( _("Failed to run mkinitfs on the target"),
|
||||
_("The exit code was {}").format(return_code) )
|
||||
7
airootfs/usr/lib/calamares/modules/mkinitfs/module.desc
Normal file
7
airootfs/usr/lib/calamares/modules/mkinitfs/module.desc
Normal file
@@ -0,0 +1,7 @@
|
||||
# SPDX-FileCopyrightText: no
|
||||
# SPDX-License-Identifier: CC0-1.0
|
||||
---
|
||||
type: "job"
|
||||
name: "mkinitfs"
|
||||
interface: "python"
|
||||
script: "main.py"
|
||||
15
airootfs/usr/lib/calamares/modules/mobile/CMakeLists.txt
Normal file
15
airootfs/usr/lib/calamares/modules/mobile/CMakeLists.txt
Normal file
@@ -0,0 +1,15 @@
|
||||
# SPDX-FileCopyrightText: 2020 Oliver Smith <ollieparanoid@postmarketos.org>
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
calamares_add_plugin( mobile
|
||||
TYPE viewmodule
|
||||
EXPORT_MACRO PLUGINDLLEXPORT_PRO
|
||||
SOURCES
|
||||
Config.cpp
|
||||
MobileQmlViewStep.cpp
|
||||
PartitionJob.cpp
|
||||
UsersJob.cpp
|
||||
RESOURCES
|
||||
mobile.qrc
|
||||
SHARED_LIB
|
||||
)
|
||||
221
airootfs/usr/lib/calamares/modules/mobile/Config.cpp
Normal file
221
airootfs/usr/lib/calamares/modules/mobile/Config.cpp
Normal file
@@ -0,0 +1,221 @@
|
||||
/* SPDX-FileCopyrightText: 2020 Oliver Smith <ollieparanoid@postmarketos.org>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later */
|
||||
#include "Config.h"
|
||||
#include "PartitionJob.h"
|
||||
#include "UsersJob.h"
|
||||
|
||||
#include "ViewManager.h"
|
||||
#include "utils/Logger.h"
|
||||
#include "utils/Variant.h"
|
||||
|
||||
#include <QVariant>
|
||||
|
||||
Config::Config( QObject* parent )
|
||||
: QObject( parent )
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
Config::setConfigurationMap( const QVariantMap& cfgMap )
|
||||
{
|
||||
using namespace Calamares;
|
||||
|
||||
if ( getBool( cfgMap, "bogus", false ) )
|
||||
{
|
||||
cWarning() << "Configuration key \"bogus\" is still set for *mobile*";
|
||||
}
|
||||
|
||||
m_osName = getString( cfgMap, "osName", "(unknown)" );
|
||||
m_arch = getString( cfgMap, "arch", "(unknown)" );
|
||||
m_device = getString( cfgMap, "device", "(unknown)" );
|
||||
m_userInterface = getString( cfgMap, "userInterface", "(unknown)" );
|
||||
m_version = getString( cfgMap, "version", "(unknown)" );
|
||||
|
||||
m_reservedUsernames = getStringList( cfgMap, "reservedUsernames", QStringList { "adm", "at ", "bin", "colord",
|
||||
"cron", "cyrus", "daemon", "ftp", "games", "geoclue", "guest", "halt", "lightdm", "lp", "mail", "man",
|
||||
"messagebus", "news", "nobody", "ntp", "operator", "polkitd", "postmaster", "pulse", "root", "shutdown",
|
||||
"smmsp", "squid", "sshd", "sync", "uucp", "vpopmail", "xfs" } );
|
||||
|
||||
// ensure m_cmdUsermod matches m_username
|
||||
m_username = getString( cfgMap, "username", "user" );
|
||||
m_userPasswordNumeric = getBool( cfgMap, "userPasswordNumeric", true );
|
||||
|
||||
m_builtinVirtualKeyboard = getBool( cfgMap, "builtinVirtualKeyboard", true );
|
||||
|
||||
m_featureSshd = getBool( cfgMap, "featureSshd", true );
|
||||
m_featureFsType = getBool( cfgMap, "featureFsType", false );
|
||||
|
||||
m_cmdLuksFormat = getString( cfgMap, "cmdLuksFormat", "cryptsetup luksFormat --use-random" );
|
||||
m_cmdLuksOpen = getString( cfgMap, "cmdLuksOpen", "cryptsetup luksOpen" );
|
||||
m_cmdMount = getString( cfgMap, "cmdMount", "mount" );
|
||||
m_targetDeviceRoot = getString( cfgMap, "targetDeviceRoot", "/dev/unknown" );
|
||||
m_targetDeviceRootInternal = getString( cfgMap, "targetDeviceRootInternal", "" );
|
||||
|
||||
m_cmdMkfsRootBtrfs = getString( cfgMap, "cmdMkfsRootBtrfs", "mkfs.btrfs -L 'unknownOS_root'" );
|
||||
m_cmdMkfsRootExt4 = getString( cfgMap, "cmdMkfsRootExt4", "mkfs.ext4 -L 'unknownOS_root'" );
|
||||
m_cmdMkfsRootF2fs = getString( cfgMap, "cmdMkfsRootF2fs", "mkfs.f2fs -l 'unknownOS_root'" );
|
||||
m_fsList = getStringList( cfgMap, "fsModel", QStringList { "ext4", "f2fs", "btrfs" } );
|
||||
m_defaultFs = getString( cfgMap, "defaultFs", "ext4" );
|
||||
m_fsIndex = m_fsList.indexOf( m_defaultFs );
|
||||
m_fsType = m_defaultFs;
|
||||
|
||||
m_cmdInternalStoragePrepare = getString( cfgMap, "cmdInternalStoragePrepare", "ondev-internal-storage-prepare" );
|
||||
m_cmdPasswd = getString( cfgMap, "cmdPasswd", "passwd" );
|
||||
m_cmdUsermod = getString( cfgMap, "cmdUsermod", "xargs -I{} -n1 usermod -m -d /home/{} -l {} -c {} user");
|
||||
|
||||
m_cmdSshdEnable = getString( cfgMap, "cmdSshdEnable", "systemctl enable sshd.service" );
|
||||
m_cmdSshdDisable = getString( cfgMap, "cmdSshdDisable", "systemctl disable sshd.service" );
|
||||
m_cmdSshdUseradd = getString( cfgMap, "cmdSshdUseradd", "useradd -G wheel -m" );
|
||||
}
|
||||
|
||||
Calamares::JobList
|
||||
Config::createJobs()
|
||||
{
|
||||
QList< Calamares::job_ptr > list;
|
||||
QString cmdSshd = m_isSshEnabled ? m_cmdSshdEnable : m_cmdSshdDisable;
|
||||
|
||||
/* Put users job in queue (should run after unpackfs) */
|
||||
Calamares::Job* j = new UsersJob( m_featureSshd,
|
||||
m_cmdPasswd,
|
||||
m_cmdUsermod,
|
||||
cmdSshd,
|
||||
m_cmdSshdUseradd,
|
||||
m_isSshEnabled,
|
||||
m_username,
|
||||
m_userPassword,
|
||||
m_sshdUsername,
|
||||
m_sshdPassword );
|
||||
list.append( Calamares::job_ptr( j ) );
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
void
|
||||
Config::runPartitionJobThenLeave( bool b )
|
||||
{
|
||||
Calamares::ViewManager* v = Calamares::ViewManager::instance();
|
||||
QString cmdMkfsRoot;
|
||||
if ( m_fsType == QStringLiteral( "btrfs" ) )
|
||||
{
|
||||
cmdMkfsRoot = m_cmdMkfsRootBtrfs;
|
||||
}
|
||||
else if ( m_fsType == QStringLiteral( "f2fs" ) )
|
||||
{
|
||||
cmdMkfsRoot = m_cmdMkfsRootF2fs;
|
||||
}
|
||||
else if ( m_fsType == QStringLiteral( "ext4" ) )
|
||||
{
|
||||
cmdMkfsRoot = m_cmdMkfsRootExt4;
|
||||
}
|
||||
else
|
||||
{
|
||||
v->onInstallationFailed( "Unknown filesystem: '" + m_fsType + "'", "" );
|
||||
}
|
||||
/* HACK: run partition job
|
||||
* The "mobile" module has two jobs, the partition job and the users job.
|
||||
* If we added both of them in Config::createJobs(), Calamares would run
|
||||
* them right after each other. But we need the "unpackfs" module to run
|
||||
* inbetween, that's why as workaround, the partition job is started here.
|
||||
* To solve this properly, we would need to place the partition job in an
|
||||
* own module and pass everything via globalstorage. But then we might as
|
||||
* well refactor everything so we can unify the mobile's partition job with
|
||||
* the proper partition job from Calamares. */
|
||||
Calamares::Job* j = new PartitionJob( m_cmdInternalStoragePrepare,
|
||||
m_cmdLuksFormat,
|
||||
m_cmdLuksOpen,
|
||||
cmdMkfsRoot,
|
||||
m_cmdMount,
|
||||
m_targetDeviceRoot,
|
||||
m_targetDeviceRootInternal,
|
||||
m_installFromExternalToInternal,
|
||||
m_isFdeEnabled,
|
||||
m_fdePassword );
|
||||
Calamares::JobResult res = j->exec();
|
||||
|
||||
if ( res )
|
||||
{
|
||||
v->next();
|
||||
}
|
||||
else
|
||||
{
|
||||
v->onInstallationFailed( res.message(), res.details() );
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
Config::setUsername( const QString& username )
|
||||
{
|
||||
m_username = username;
|
||||
emit usernameChanged( m_username );
|
||||
}
|
||||
|
||||
void
|
||||
Config::setUserPassword( const QString& userPassword )
|
||||
{
|
||||
m_userPassword = userPassword;
|
||||
emit userPasswordChanged( m_userPassword );
|
||||
}
|
||||
|
||||
void
|
||||
Config::setSshdUsername( const QString& sshdUsername )
|
||||
{
|
||||
m_sshdUsername = sshdUsername;
|
||||
emit sshdUsernameChanged( m_sshdUsername );
|
||||
}
|
||||
|
||||
void
|
||||
Config::setSshdPassword( const QString& sshdPassword )
|
||||
{
|
||||
m_sshdPassword = sshdPassword;
|
||||
emit sshdPasswordChanged( m_sshdPassword );
|
||||
}
|
||||
|
||||
void
|
||||
Config::setIsSshEnabled( const bool isSshEnabled )
|
||||
{
|
||||
m_isSshEnabled = isSshEnabled;
|
||||
}
|
||||
|
||||
void
|
||||
Config::setFdePassword( const QString& fdePassword )
|
||||
{
|
||||
m_fdePassword = fdePassword;
|
||||
emit fdePasswordChanged( m_fdePassword );
|
||||
}
|
||||
|
||||
void
|
||||
Config::setIsFdeEnabled( const bool isFdeEnabled )
|
||||
{
|
||||
m_isFdeEnabled = isFdeEnabled;
|
||||
}
|
||||
|
||||
void
|
||||
Config::setInstallFromExternalToInternal( const bool val )
|
||||
{
|
||||
m_installFromExternalToInternal = val;
|
||||
}
|
||||
|
||||
void
|
||||
Config::setFsType( int idx )
|
||||
{
|
||||
if ( idx >= 0 && idx < m_fsList.length() )
|
||||
{
|
||||
setFsType( m_fsList[ idx ] );
|
||||
}
|
||||
}
|
||||
void
|
||||
Config::setFsType( const QString& fsType )
|
||||
{
|
||||
if ( fsType != m_fsType )
|
||||
{
|
||||
m_fsType = fsType;
|
||||
emit fsTypeChanged( m_fsType );
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
Config::setFsIndex( const int fsIndex )
|
||||
{
|
||||
m_fsIndex = fsIndex;
|
||||
emit fsIndexChanged( m_fsIndex );
|
||||
}
|
||||
207
airootfs/usr/lib/calamares/modules/mobile/Config.h
Normal file
207
airootfs/usr/lib/calamares/modules/mobile/Config.h
Normal file
@@ -0,0 +1,207 @@
|
||||
/* SPDX-FileCopyrightText: 2020 Oliver Smith <ollieparanoid@postmarketos.org>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later */
|
||||
#pragma once
|
||||
|
||||
#include "Job.h"
|
||||
|
||||
#include <QObject>
|
||||
#include <memory>
|
||||
|
||||
class Config : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
/* installer UI */
|
||||
Q_PROPERTY( bool builtinVirtualKeyboard READ builtinVirtualKeyboard CONSTANT FINAL )
|
||||
|
||||
/* welcome */
|
||||
Q_PROPERTY( QString osName READ osName CONSTANT FINAL )
|
||||
Q_PROPERTY( QString arch READ arch CONSTANT FINAL )
|
||||
Q_PROPERTY( QString device READ device CONSTANT FINAL )
|
||||
Q_PROPERTY( QString userInterface READ userInterface CONSTANT FINAL )
|
||||
Q_PROPERTY( QString version READ version CONSTANT FINAL )
|
||||
|
||||
/* reserved usernames (user_pass, ssh_credentials )*/
|
||||
Q_PROPERTY( QStringList reservedUsernames READ reservedUsernames CONSTANT FINAL )
|
||||
|
||||
/* default user */
|
||||
Q_PROPERTY( QString username READ username WRITE setUsername NOTIFY usernameChanged )
|
||||
Q_PROPERTY( QString userPassword READ userPassword WRITE setUserPassword NOTIFY userPasswordChanged )
|
||||
Q_PROPERTY( bool userPasswordNumeric READ userPasswordNumeric CONSTANT FINAL )
|
||||
|
||||
/* ssh server + credentials */
|
||||
Q_PROPERTY( bool featureSshd READ featureSshd CONSTANT FINAL )
|
||||
Q_PROPERTY( QString sshdUsername READ sshdUsername WRITE setSshdUsername NOTIFY sshdUsernameChanged )
|
||||
Q_PROPERTY( QString sshdPassword READ sshdPassword WRITE setSshdPassword NOTIFY sshdPasswordChanged )
|
||||
Q_PROPERTY( bool isSshEnabled READ isSshEnabled WRITE setIsSshEnabled )
|
||||
|
||||
/* full disk encryption */
|
||||
Q_PROPERTY( QString fdePassword READ fdePassword WRITE setFdePassword NOTIFY fdePasswordChanged )
|
||||
Q_PROPERTY( bool isFdeEnabled READ isFdeEnabled WRITE setIsFdeEnabled )
|
||||
|
||||
/* filesystem selection */
|
||||
Q_PROPERTY( QString fsType READ fsType WRITE setFsType NOTIFY fsTypeChanged )
|
||||
Q_PROPERTY( bool featureFsType READ featureFsType CONSTANT FINAL )
|
||||
Q_PROPERTY( QStringList fsList READ fsList CONSTANT FINAL )
|
||||
Q_PROPERTY( QString defaultFs READ defaultFs CONSTANT FINAL )
|
||||
Q_PROPERTY( int fsIndex READ fsIndex WRITE setFsIndex NOTIFY fsIndexChanged )
|
||||
|
||||
/* partition job */
|
||||
Q_PROPERTY( bool runPartitionJobThenLeave READ runPartitionJobThenLeaveDummy WRITE runPartitionJobThenLeave )
|
||||
Q_PROPERTY( QString cmdInternalStoragePrepare READ cmdInternalStoragePrepare CONSTANT FINAL )
|
||||
Q_PROPERTY( QString cmdLuksFormat READ cmdLuksFormat CONSTANT FINAL )
|
||||
Q_PROPERTY( QString cmdLuksOpen READ cmdLuksOpen CONSTANT FINAL )
|
||||
Q_PROPERTY( QString cmdMount READ cmdMount CONSTANT FINAL )
|
||||
Q_PROPERTY( QString targetDeviceRoot READ targetDeviceRoot CONSTANT FINAL )
|
||||
Q_PROPERTY( QString targetDeviceRootInternal READ targetDeviceRootInternal CONSTANT FINAL )
|
||||
Q_PROPERTY(
|
||||
bool installFromExternalToInternal READ installFromExternalToInternal WRITE setInstallFromExternalToInternal )
|
||||
|
||||
/* users job */
|
||||
Q_PROPERTY( QString cmdSshdEnable READ cmdSshdEnable CONSTANT FINAL )
|
||||
Q_PROPERTY( QString cmdSshdDisable READ cmdSshdDisable CONSTANT FINAL )
|
||||
|
||||
public:
|
||||
Config( QObject* parent = nullptr );
|
||||
void setConfigurationMap( const QVariantMap& );
|
||||
Calamares::JobList createJobs();
|
||||
|
||||
/* installer UI */
|
||||
bool builtinVirtualKeyboard() { return m_builtinVirtualKeyboard; }
|
||||
|
||||
/* welcome */
|
||||
QString osName() const { return m_osName; }
|
||||
QString arch() const { return m_arch; }
|
||||
QString device() const { return m_device; }
|
||||
QString userInterface() const { return m_userInterface; }
|
||||
QString version() const { return m_version; }
|
||||
|
||||
/* reserved usernames (user_pass, ssh_credentials) */
|
||||
QStringList reservedUsernames() const { return m_reservedUsernames; };
|
||||
|
||||
/* user */
|
||||
QString username() const { return m_username; }
|
||||
QString userPassword() const { return m_userPassword; }
|
||||
void setUsername( const QString& username );
|
||||
void setUserPassword( const QString& userPassword );
|
||||
bool userPasswordNumeric() const { return m_userPasswordNumeric; }
|
||||
|
||||
/* ssh server + credentials */
|
||||
bool featureSshd() { return m_featureSshd; }
|
||||
QString sshdUsername() const { return m_sshdUsername; }
|
||||
QString sshdPassword() const { return m_sshdPassword; }
|
||||
bool isSshEnabled() { return m_isSshEnabled; }
|
||||
void setSshdUsername( const QString& sshdUsername );
|
||||
void setSshdPassword( const QString& sshdPassword );
|
||||
void setIsSshEnabled( bool isSshEnabled );
|
||||
|
||||
/* full disk encryption */
|
||||
QString fdePassword() const { return m_fdePassword; }
|
||||
bool isFdeEnabled() { return m_isFdeEnabled; }
|
||||
void setFdePassword( const QString& fdePassword );
|
||||
void setIsFdeEnabled( bool isFdeEnabled );
|
||||
|
||||
/* filesystem selection */
|
||||
bool featureFsType() { return m_featureFsType; };
|
||||
QString fsType() const { return m_fsType; };
|
||||
void setFsType( int idx );
|
||||
void setFsType( const QString& fsType );
|
||||
QStringList fsList() const { return m_fsList; };
|
||||
int fsIndex() const { return m_fsIndex; };
|
||||
void setFsIndex( const int fsIndex );
|
||||
QString defaultFs() const { return m_defaultFs; };
|
||||
|
||||
/* partition job */
|
||||
bool runPartitionJobThenLeaveDummy() { return 0; }
|
||||
void runPartitionJobThenLeave( bool b );
|
||||
QString cmdInternalStoragePrepare() const { return m_cmdInternalStoragePrepare; }
|
||||
QString cmdLuksFormat() const { return m_cmdLuksFormat; }
|
||||
QString cmdLuksOpen() const { return m_cmdLuksOpen; }
|
||||
QString cmdMkfsRootBtrfs() const { return m_cmdMkfsRootBtrfs; }
|
||||
QString cmdMkfsRootExt4() const { return m_cmdMkfsRootExt4; }
|
||||
QString cmdMkfsRootF2fs() const { return m_cmdMkfsRootF2fs; }
|
||||
QString cmdMount() const { return m_cmdMount; }
|
||||
QString targetDeviceRoot() const { return m_targetDeviceRoot; }
|
||||
QString targetDeviceRootInternal() const { return m_targetDeviceRootInternal; }
|
||||
bool installFromExternalToInternal() { return m_installFromExternalToInternal; }
|
||||
void setInstallFromExternalToInternal( const bool val );
|
||||
|
||||
/* users job */
|
||||
QString cmdPasswd() const { return m_cmdPasswd; }
|
||||
QString cmdUsermod() const { return m_cmdUsermod; }
|
||||
QString cmdSshdEnable() const { return m_cmdSshdEnable; }
|
||||
QString cmdSshdDisable() const { return m_cmdSshdDisable; }
|
||||
QString cmdSshdUseradd() const { return m_cmdSshdUseradd; }
|
||||
|
||||
private:
|
||||
/* installer UI */
|
||||
bool m_builtinVirtualKeyboard;
|
||||
|
||||
/* welcome */
|
||||
QString m_osName;
|
||||
QString m_arch;
|
||||
QString m_device;
|
||||
QString m_userInterface;
|
||||
QString m_version;
|
||||
|
||||
/* reserved usernames (user_pass, ssh_credentials) */
|
||||
QStringList m_reservedUsernames;
|
||||
|
||||
/* default user */
|
||||
QString m_username;
|
||||
QString m_userPassword;
|
||||
bool m_userPasswordNumeric;
|
||||
|
||||
/* ssh server + credentials */
|
||||
bool m_featureSshd = false;
|
||||
QString m_sshdUsername;
|
||||
QString m_sshdPassword;
|
||||
bool m_isSshEnabled = false;
|
||||
|
||||
/* full disk encryption */
|
||||
QString m_fdePassword;
|
||||
bool m_isFdeEnabled = false;
|
||||
|
||||
/* filesystem selection */
|
||||
bool m_featureFsType = false;
|
||||
QString m_defaultFs;
|
||||
QString m_fsType;
|
||||
// Index of the currently selected filesystem in UI.
|
||||
int m_fsIndex = -1;
|
||||
QStringList m_fsList;
|
||||
|
||||
/* partition job */
|
||||
QString m_cmdInternalStoragePrepare;
|
||||
QString m_cmdLuksFormat;
|
||||
QString m_cmdLuksOpen;
|
||||
QString m_cmdMkfsRootBtrfs;
|
||||
QString m_cmdMkfsRootExt4;
|
||||
QString m_cmdMkfsRootF2fs;
|
||||
QString m_cmdMount;
|
||||
QString m_targetDeviceRoot;
|
||||
QString m_targetDeviceRootInternal;
|
||||
bool m_installFromExternalToInternal = false;
|
||||
|
||||
/* users job */
|
||||
QString m_cmdPasswd;
|
||||
QString m_cmdUsermod;
|
||||
QString m_cmdSshdEnable;
|
||||
QString m_cmdSshdDisable;
|
||||
QString m_cmdSshdUseradd;
|
||||
|
||||
signals:
|
||||
/* booleans we don't read from QML (like isSshEnabled) don't need a signal */
|
||||
|
||||
/* default user */
|
||||
void userPasswordChanged( QString userPassword );
|
||||
void usernameChanged( QString username );
|
||||
|
||||
/* ssh server + credentials */
|
||||
void sshdUsernameChanged( QString sshdUsername );
|
||||
void sshdPasswordChanged( QString sshdPassword );
|
||||
|
||||
/* full disk encryption */
|
||||
void fdePasswordChanged( QString fdePassword );
|
||||
|
||||
void fsTypeChanged( QString fsType );
|
||||
void fsIndexChanged( int fsIndex );
|
||||
};
|
||||
@@ -0,0 +1,73 @@
|
||||
/* SPDX-FileCopyrightText: 2020 Oliver Smith <ollieparanoid@postmarketos.org>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later */
|
||||
#include "MobileQmlViewStep.h"
|
||||
|
||||
#include "Branding.h"
|
||||
#include "GlobalStorage.h"
|
||||
#include "locale/TranslationsModel.h"
|
||||
#include "modulesystem/ModuleManager.h"
|
||||
#include "utils/Dirs.h"
|
||||
#include "utils/Logger.h"
|
||||
#include "utils/Variant.h"
|
||||
|
||||
#include <QProcess>
|
||||
|
||||
CALAMARES_PLUGIN_FACTORY_DEFINITION( MobileQmlViewStepFactory, registerPlugin< MobileQmlViewStep >(); )
|
||||
|
||||
void
|
||||
MobileQmlViewStep::setConfigurationMap( const QVariantMap& configurationMap )
|
||||
{
|
||||
m_config->setConfigurationMap( configurationMap );
|
||||
Calamares::QmlViewStep::setConfigurationMap( configurationMap );
|
||||
}
|
||||
|
||||
MobileQmlViewStep::MobileQmlViewStep( QObject* parent )
|
||||
: Calamares::QmlViewStep( parent )
|
||||
, m_config( new Config( this ) )
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
MobileQmlViewStep::onLeave()
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
bool
|
||||
MobileQmlViewStep::isNextEnabled() const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool
|
||||
MobileQmlViewStep::isBackEnabled() const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
bool
|
||||
MobileQmlViewStep::isAtBeginning() const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool
|
||||
MobileQmlViewStep::isAtEnd() const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
Calamares::JobList
|
||||
MobileQmlViewStep::jobs() const
|
||||
{
|
||||
return m_config->createJobs();
|
||||
}
|
||||
|
||||
QObject*
|
||||
MobileQmlViewStep::getConfig()
|
||||
{
|
||||
return m_config;
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/* SPDX-FileCopyrightText: 2020 Oliver Smith <ollieparanoid@postmarketos.org>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later */
|
||||
#ifndef PARTITION_QMLVIEWSTEP_H
|
||||
#define PARTITION_QMLVIEWSTEP_H
|
||||
#include "Config.h"
|
||||
|
||||
#include "utils/PluginFactory.h"
|
||||
#include "viewpages/QmlViewStep.h"
|
||||
|
||||
#include <DllMacro.h>
|
||||
|
||||
#include <QObject>
|
||||
#include <QVariantMap>
|
||||
|
||||
class PLUGINDLLEXPORT MobileQmlViewStep : public Calamares::QmlViewStep
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit MobileQmlViewStep( QObject* parent = nullptr );
|
||||
|
||||
bool isNextEnabled() const override;
|
||||
bool isBackEnabled() const override;
|
||||
bool isAtBeginning() const override;
|
||||
bool isAtEnd() const override;
|
||||
|
||||
Calamares::JobList jobs() const override;
|
||||
|
||||
void setConfigurationMap( const QVariantMap& configurationMap ) override;
|
||||
void onLeave() override;
|
||||
QObject* getConfig() override;
|
||||
|
||||
private:
|
||||
Config* m_config;
|
||||
};
|
||||
|
||||
CALAMARES_PLUGIN_FACTORY_DECLARATION( MobileQmlViewStepFactory )
|
||||
|
||||
#endif // PARTITION_QMLVIEWSTEP_H
|
||||
136
airootfs/usr/lib/calamares/modules/mobile/PartitionJob.cpp
Normal file
136
airootfs/usr/lib/calamares/modules/mobile/PartitionJob.cpp
Normal file
@@ -0,0 +1,136 @@
|
||||
/* SPDX-FileCopyrightText: 2020 Oliver Smith <ollieparanoid@postmarketos.org>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later */
|
||||
#include "PartitionJob.h"
|
||||
|
||||
#include "GlobalStorage.h"
|
||||
#include "JobQueue.h"
|
||||
#include "Settings.h"
|
||||
#include "utils/System.h"
|
||||
#include "utils/Logger.h"
|
||||
|
||||
#include <QDir>
|
||||
#include <QFileInfo>
|
||||
|
||||
|
||||
PartitionJob::PartitionJob( const QString& cmdInternalStoragePrepare,
|
||||
const QString& cmdLuksFormat,
|
||||
const QString& cmdLuksOpen,
|
||||
const QString& cmdMkfsRoot,
|
||||
const QString& cmdMount,
|
||||
const QString& targetDeviceRoot,
|
||||
const QString& targetDeviceRootInternal,
|
||||
bool installFromExternalToInternal,
|
||||
bool isFdeEnabled,
|
||||
const QString& password )
|
||||
: Calamares::Job()
|
||||
, m_cmdInternalStoragePrepare( cmdInternalStoragePrepare )
|
||||
, m_cmdLuksFormat( cmdLuksFormat )
|
||||
, m_cmdLuksOpen( cmdLuksOpen )
|
||||
, m_cmdMkfsRoot( cmdMkfsRoot )
|
||||
, m_cmdMount( cmdMount )
|
||||
, m_targetDeviceRoot( targetDeviceRoot )
|
||||
, m_targetDeviceRootInternal( targetDeviceRootInternal )
|
||||
, m_installFromExternalToInternal( installFromExternalToInternal )
|
||||
, m_isFdeEnabled( isFdeEnabled )
|
||||
, m_password( password )
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
QString
|
||||
PartitionJob::prettyName() const
|
||||
{
|
||||
return "Creating and formatting installation partition";
|
||||
}
|
||||
|
||||
/* Fill the "global storage", so the following jobs (like unsquashfs) work.
|
||||
The code is similar to modules/partition/jobs/FillGlobalStorageJob.cpp in
|
||||
Calamares. */
|
||||
void
|
||||
FillGlobalStorage( const QString device, const QString pathMount )
|
||||
{
|
||||
using namespace Calamares;
|
||||
|
||||
GlobalStorage* gs = JobQueue::instance()->globalStorage();
|
||||
QVariantList partitions;
|
||||
QVariantMap partition;
|
||||
|
||||
/* See mapForPartition() in FillGlobalStorageJob.cpp */
|
||||
partition[ "device" ] = device;
|
||||
partition[ "mountPoint" ] = "/";
|
||||
partition[ "claimed" ] = true;
|
||||
|
||||
/* Ignored by calamares modules used in combination with the "mobile"
|
||||
* module, so we can get away with leaving them empty for now. */
|
||||
partition[ "uuid" ] = "";
|
||||
partition[ "fsName" ] = "";
|
||||
partition[ "fs" ] = "";
|
||||
|
||||
partitions << partition;
|
||||
gs->insert( "partitions", partitions );
|
||||
gs->insert( "rootMountPoint", pathMount );
|
||||
}
|
||||
|
||||
Calamares::JobResult
|
||||
PartitionJob::exec()
|
||||
{
|
||||
using namespace Calamares;
|
||||
using namespace Calamares;
|
||||
using namespace std;
|
||||
|
||||
const QString pathMount = "/mnt/install";
|
||||
const QString cryptName = "calamares_crypt";
|
||||
QString cryptDev = "/dev/mapper/" + cryptName;
|
||||
QString passwordStdin = m_password + "\n";
|
||||
QString dev = m_targetDeviceRoot;
|
||||
QList< QPair< QStringList, QString > > commands = {};
|
||||
|
||||
if ( m_installFromExternalToInternal )
|
||||
{
|
||||
dev = m_targetDeviceRootInternal;
|
||||
|
||||
commands.append( {
|
||||
{ { "sh", "-c", m_cmdInternalStoragePrepare }, QString() },
|
||||
} );
|
||||
}
|
||||
|
||||
commands.append( { { { "mkdir", "-p", pathMount }, QString() } } );
|
||||
|
||||
if ( m_isFdeEnabled )
|
||||
{
|
||||
commands.append( {
|
||||
{ { "sh", "-c", m_cmdLuksFormat + " " + dev }, passwordStdin },
|
||||
{ { "sh", "-c", m_cmdLuksOpen + " " + dev + " " + cryptName }, passwordStdin },
|
||||
{ { "sh", "-c", m_cmdMkfsRoot + " " + cryptDev }, QString() },
|
||||
{ { "sh", "-c", m_cmdMount + " " + cryptDev + " " + pathMount }, QString() },
|
||||
} );
|
||||
}
|
||||
else
|
||||
{
|
||||
commands.append( { { { "sh", "-c", m_cmdMkfsRoot + " " + dev }, QString() },
|
||||
{ { "sh", "-c", m_cmdMount + " " + dev + " " + pathMount }, QString() } } );
|
||||
}
|
||||
|
||||
foreach ( auto command, commands )
|
||||
{
|
||||
const QStringList args = command.first;
|
||||
const QString stdInput = command.second;
|
||||
const QString pathRoot = "/";
|
||||
|
||||
ProcessResult res
|
||||
= System::runCommand( System::RunLocation::RunInHost, args, pathRoot, stdInput, chrono::seconds( 600 ) );
|
||||
if ( res.getExitCode() )
|
||||
{
|
||||
return JobResult::error( "Command failed:<br><br>"
|
||||
"'"
|
||||
+ args.join( " " )
|
||||
+ "'<br><br>"
|
||||
" with output:<br><br>"
|
||||
"'"
|
||||
+ res.getOutput() + "'" );
|
||||
}
|
||||
}
|
||||
|
||||
FillGlobalStorage( m_isFdeEnabled ? cryptDev : dev, pathMount );
|
||||
return JobResult::ok();
|
||||
}
|
||||
38
airootfs/usr/lib/calamares/modules/mobile/PartitionJob.h
Normal file
38
airootfs/usr/lib/calamares/modules/mobile/PartitionJob.h
Normal file
@@ -0,0 +1,38 @@
|
||||
/* SPDX-FileCopyrightText: 2020 Oliver Smith <ollieparanoid@postmarketos.org>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later */
|
||||
#pragma once
|
||||
#include "Job.h"
|
||||
|
||||
|
||||
class PartitionJob : public Calamares::Job
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
PartitionJob( const QString& cmdInternalStoragePrepare,
|
||||
const QString& cmdLuksFormat,
|
||||
const QString& cmdLuksOpen,
|
||||
const QString& cmdMkfsRoot,
|
||||
const QString& cmdMount,
|
||||
const QString& targetDeviceRoot,
|
||||
const QString& targetDeviceRootInternal,
|
||||
bool installFromExternalToInternal,
|
||||
bool isFdeEnabled,
|
||||
const QString& password );
|
||||
|
||||
QString prettyName() const override;
|
||||
Calamares::JobResult exec() override;
|
||||
|
||||
Calamares::JobList createJobs();
|
||||
|
||||
private:
|
||||
QString m_cmdInternalStoragePrepare;
|
||||
QString m_cmdLuksFormat;
|
||||
QString m_cmdLuksOpen;
|
||||
QString m_cmdMkfsRoot;
|
||||
QString m_cmdMount;
|
||||
QString m_targetDeviceRoot;
|
||||
QString m_targetDeviceRootInternal;
|
||||
bool m_installFromExternalToInternal;
|
||||
bool m_isFdeEnabled;
|
||||
QString m_password;
|
||||
};
|
||||
92
airootfs/usr/lib/calamares/modules/mobile/UsersJob.cpp
Normal file
92
airootfs/usr/lib/calamares/modules/mobile/UsersJob.cpp
Normal file
@@ -0,0 +1,92 @@
|
||||
/* SPDX-FileCopyrightText: 2020 Oliver Smith <ollieparanoid@postmarketos.org>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later */
|
||||
#include "UsersJob.h"
|
||||
|
||||
#include "GlobalStorage.h"
|
||||
#include "JobQueue.h"
|
||||
#include "Settings.h"
|
||||
#include "utils/System.h"
|
||||
#include "utils/Logger.h"
|
||||
|
||||
#include <QDir>
|
||||
#include <QFileInfo>
|
||||
|
||||
|
||||
UsersJob::UsersJob( bool featureSshd,
|
||||
const QString& cmdPasswd,
|
||||
const QString& cmdUsermod,
|
||||
const QString& cmdSshd,
|
||||
const QString& cmdSshdUseradd,
|
||||
bool isSshEnabled,
|
||||
const QString& username,
|
||||
const QString& password,
|
||||
const QString& sshdUsername,
|
||||
const QString& sshdPassword )
|
||||
: Calamares::Job()
|
||||
, m_featureSshd( featureSshd )
|
||||
, m_cmdPasswd( cmdPasswd )
|
||||
, m_cmdUsermod( cmdUsermod )
|
||||
, m_cmdSshd( cmdSshd )
|
||||
, m_cmdSshdUseradd( cmdSshdUseradd )
|
||||
, m_isSshEnabled( isSshEnabled )
|
||||
, m_username( username )
|
||||
, m_password( password )
|
||||
, m_sshdUsername( sshdUsername )
|
||||
, m_sshdPassword( sshdPassword )
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
QString
|
||||
UsersJob::prettyName() const
|
||||
{
|
||||
return "Configuring users";
|
||||
}
|
||||
|
||||
Calamares::JobResult
|
||||
UsersJob::exec()
|
||||
{
|
||||
using namespace Calamares;
|
||||
using namespace Calamares;
|
||||
using namespace std;
|
||||
|
||||
QList< QPair< QStringList, QString > > commands = {
|
||||
{ { "sh", "-c", m_cmdUsermod }, m_username + "\n" }
|
||||
};
|
||||
|
||||
commands.append( { { "sh", "-c", m_cmdPasswd + " " + m_username }, m_password + "\n" + m_password + "\n" } );
|
||||
|
||||
if ( m_featureSshd )
|
||||
{
|
||||
commands.append( { { "sh", "-c", m_cmdSshd }, QString() } );
|
||||
|
||||
if ( m_isSshEnabled )
|
||||
{
|
||||
commands.append( { { "sh", "-c", m_cmdSshdUseradd + " " + m_sshdUsername }, QString() } );
|
||||
commands.append(
|
||||
{ { "sh", "-c", m_cmdPasswd + " " + m_sshdUsername }, m_sshdPassword + "\n" + m_sshdPassword + "\n" } );
|
||||
}
|
||||
}
|
||||
|
||||
foreach ( auto command, commands )
|
||||
{
|
||||
auto location = System::RunLocation::RunInTarget;
|
||||
const QString pathRoot = "/";
|
||||
const QStringList args = command.first;
|
||||
const QString stdInput = command.second;
|
||||
|
||||
ProcessResult res = System::runCommand( location, args, pathRoot, stdInput, chrono::seconds( 30 ) );
|
||||
if ( res.getExitCode() )
|
||||
{
|
||||
return JobResult::error( "Command failed:<br><br>"
|
||||
"'"
|
||||
+ args.join( " " )
|
||||
+ "'<br><br>"
|
||||
" with output:<br><br>"
|
||||
"'"
|
||||
+ res.getOutput() + "'" );
|
||||
}
|
||||
}
|
||||
|
||||
return JobResult::ok();
|
||||
}
|
||||
38
airootfs/usr/lib/calamares/modules/mobile/UsersJob.h
Normal file
38
airootfs/usr/lib/calamares/modules/mobile/UsersJob.h
Normal file
@@ -0,0 +1,38 @@
|
||||
/* SPDX-FileCopyrightText: 2020 Oliver Smith <ollieparanoid@postmarketos.org>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later */
|
||||
#pragma once
|
||||
#include "Job.h"
|
||||
|
||||
|
||||
class UsersJob : public Calamares::Job
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
UsersJob( bool featureSshd,
|
||||
const QString& cmdPasswd,
|
||||
const QString& cmdUsermod,
|
||||
const QString& cmdSshd,
|
||||
const QString& cmdSshdUseradd,
|
||||
bool isSshEnabled,
|
||||
const QString& username,
|
||||
const QString& password,
|
||||
const QString& sshdUsername,
|
||||
const QString& sshdPassword );
|
||||
|
||||
QString prettyName() const override;
|
||||
Calamares::JobResult exec() override;
|
||||
|
||||
Calamares::JobList createJobs();
|
||||
|
||||
private:
|
||||
bool m_featureSshd;
|
||||
QString m_cmdPasswd;
|
||||
QString m_cmdUsermod;
|
||||
QString m_cmdSshd;
|
||||
QString m_cmdSshdUseradd;
|
||||
bool m_isSshEnabled;
|
||||
QString m_username;
|
||||
QString m_password;
|
||||
QString m_sshdUsername;
|
||||
QString m_sshdPassword;
|
||||
};
|
||||
65
airootfs/usr/lib/calamares/modules/mobile/fde_confirm.qml
Normal file
65
airootfs/usr/lib/calamares/modules/mobile/fde_confirm.qml
Normal file
@@ -0,0 +1,65 @@
|
||||
/* SPDX-FileCopyrightText: 2020 Oliver Smith <ollieparanoid@postmarketos.org>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later */
|
||||
import io.calamares.core 1.0
|
||||
import io.calamares.ui 1.0
|
||||
|
||||
import QtQuick 2.10
|
||||
import QtQuick.Controls 2.10
|
||||
import QtQuick.Layouts 1.3
|
||||
import org.kde.kirigami 2.7 as Kirigami
|
||||
import QtGraphicalEffects 1.0
|
||||
import QtQuick.Window 2.3
|
||||
import QtQuick.VirtualKeyboard 2.1
|
||||
|
||||
Item {
|
||||
anchors.left: parent.left
|
||||
anchors.top: parent.top
|
||||
anchors.right: parent.right
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
|
||||
Text {
|
||||
id: mainText
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: 10
|
||||
wrapMode: Text.WordWrap
|
||||
|
||||
text: "To protect your data in case your device gets stolen," +
|
||||
" it is recommended to enable full disk encryption.<br>" +
|
||||
"<br>" +
|
||||
"If you enable full disk encryption, you will be asked for" +
|
||||
" a password. Without this password, it is not possible to" +
|
||||
" boot your device or access any data on it. Make sure that" +
|
||||
" you don't lose this password!"
|
||||
|
||||
width: 200
|
||||
}
|
||||
|
||||
Button {
|
||||
id: firstButton
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.top: mainText.bottom
|
||||
anchors.topMargin: 10
|
||||
width: 200
|
||||
|
||||
text: qsTr("Enable")
|
||||
onClicked: {
|
||||
config.isFdeEnabled = true;
|
||||
navNext();
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.top: firstButton.bottom
|
||||
anchors.topMargin: 10
|
||||
width: 200
|
||||
|
||||
text: qsTr("Disable")
|
||||
onClicked: {
|
||||
config.isFdeEnabled = false;
|
||||
navNextFeature();
|
||||
}
|
||||
}
|
||||
}
|
||||
80
airootfs/usr/lib/calamares/modules/mobile/fde_pass.qml
Normal file
80
airootfs/usr/lib/calamares/modules/mobile/fde_pass.qml
Normal file
@@ -0,0 +1,80 @@
|
||||
/* SPDX-FileCopyrightText: 2020 Oliver Smith <ollieparanoid@postmarketos.org>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later */
|
||||
import io.calamares.core 1.0
|
||||
import io.calamares.ui 1.0
|
||||
|
||||
import QtQuick 2.10
|
||||
import QtQuick.Controls 2.10
|
||||
import QtQuick.Layouts 1.3
|
||||
import org.kde.kirigami 2.7 as Kirigami
|
||||
import QtGraphicalEffects 1.0
|
||||
import QtQuick.Window 2.3
|
||||
import QtQuick.VirtualKeyboard 2.1
|
||||
|
||||
Item {
|
||||
anchors.left: parent.left
|
||||
anchors.top: parent.top
|
||||
anchors.right: parent.right
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
|
||||
TextField {
|
||||
id: password
|
||||
anchors.top: parent.top
|
||||
placeholderText: qsTr("Password")
|
||||
inputMethodHints: Qt.ImhPreferLowercase
|
||||
echoMode: TextInput.Password
|
||||
onTextChanged: validatePassword(password, passwordRepeat,
|
||||
errorText)
|
||||
text: config.fdePassword
|
||||
|
||||
onActiveFocusChanged: {
|
||||
if(activeFocus) {
|
||||
Qt.inputMethod.update(Qt.ImQueryInput);
|
||||
}
|
||||
}
|
||||
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.topMargin: 50
|
||||
width: 200
|
||||
}
|
||||
|
||||
TextField {
|
||||
id: passwordRepeat
|
||||
anchors.top: password.bottom
|
||||
placeholderText: qsTr("Password (repeat)")
|
||||
echoMode: TextInput.Password
|
||||
onTextChanged: validatePassword(password, passwordRepeat,
|
||||
errorText)
|
||||
text: config.fdePassword
|
||||
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.topMargin: 10
|
||||
width: 200
|
||||
}
|
||||
|
||||
Text {
|
||||
id: errorText
|
||||
anchors.top: passwordRepeat.bottom
|
||||
visible: false
|
||||
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.topMargin: 10
|
||||
width: 200
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
Button {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.top: errorText.bottom
|
||||
anchors.topMargin: 10
|
||||
width: 200
|
||||
|
||||
text: qsTr("Continue")
|
||||
onClicked: {
|
||||
if (validatePassword(password, passwordRepeat, errorText)) {
|
||||
config.fdePassword = password.text;
|
||||
navNext();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
59
airootfs/usr/lib/calamares/modules/mobile/fs_selection.qml
Normal file
59
airootfs/usr/lib/calamares/modules/mobile/fs_selection.qml
Normal file
@@ -0,0 +1,59 @@
|
||||
/* SPDX-FileCopyrightText: 2020 Undef <calamares@undef.tools>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later */
|
||||
import io.calamares.core 1.0
|
||||
import io.calamares.ui 1.0
|
||||
|
||||
import QtQuick 2.10
|
||||
import QtQuick.Controls 2.10
|
||||
import QtQuick.Layouts 1.3
|
||||
import org.kde.kirigami 2.7 as Kirigami
|
||||
import QtGraphicalEffects 1.0
|
||||
import QtQuick.Window 2.3
|
||||
import QtQuick.VirtualKeyboard 2.1
|
||||
|
||||
Item {
|
||||
anchors.left: parent.left
|
||||
anchors.top: parent.top
|
||||
anchors.right: parent.right
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
|
||||
Text {
|
||||
id: mainText
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: 10
|
||||
wrapMode: Text.WordWrap
|
||||
|
||||
text: "Select the filesystem for root partition. If unsure, leave the default."
|
||||
|
||||
width: 200
|
||||
}
|
||||
|
||||
ComboBox {
|
||||
id: fsTypeCB
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.top: mainText.bottom
|
||||
anchors.topMargin: 10
|
||||
width: 150
|
||||
height: 30
|
||||
editable: false
|
||||
model: config.fsList
|
||||
/* Save the current state on selection so it is there when the back button is pressed */
|
||||
onActivated: config.fsType = fsTypeCB.currentText;
|
||||
Component.onCompleted: fsTypeCB.currentIndex = find( config.fsType, Qt.MatchContains );
|
||||
}
|
||||
|
||||
Button {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.top: fsTypeCB.bottom
|
||||
anchors.topMargin: 10
|
||||
width: 200
|
||||
|
||||
text: qsTr("Continue")
|
||||
onClicked: {
|
||||
config.fsType = fsTypeCB.currentText;
|
||||
navNextFeature();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
/* SPDX-FileCopyrightText: 2020 Oliver Smith <ollieparanoid@postmarketos.org>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later */
|
||||
import io.calamares.core 1.0
|
||||
import io.calamares.ui 1.0
|
||||
|
||||
import QtQuick 2.10
|
||||
import QtQuick.Controls 2.10
|
||||
import QtQuick.Layouts 1.3
|
||||
import org.kde.kirigami 2.7 as Kirigami
|
||||
import QtGraphicalEffects 1.0
|
||||
import QtQuick.Window 2.3
|
||||
import QtQuick.VirtualKeyboard 2.1
|
||||
|
||||
Item {
|
||||
anchors.left: parent.left
|
||||
anchors.top: parent.top
|
||||
anchors.right: parent.right
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
|
||||
Text {
|
||||
id: mainText
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: 10
|
||||
wrapMode: Text.WordWrap
|
||||
|
||||
text: (function() {
|
||||
var ret = "Once you hit 'install', the installation will begin." +
|
||||
" It will typically take a few minutes. Do not power off the" +
|
||||
" device until it is done.<br>";
|
||||
|
||||
if (config.installFromExternalToInternal) {
|
||||
ret += "<b>After the installation, your device will shutdown" +
|
||||
" automatically. You must remove the external storage" +
|
||||
" (SD card) before booting again.</b>" +
|
||||
"<br><br>" +
|
||||
"Otherwise, your device will boot into the installer" +
|
||||
" again, and not into the installed system."
|
||||
} else {
|
||||
ret += "Afterwards, it will reboot into the installed system.";
|
||||
}
|
||||
|
||||
return ret;
|
||||
}())
|
||||
|
||||
width: 200
|
||||
}
|
||||
|
||||
Button {
|
||||
id: firstButton
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.top: mainText.bottom
|
||||
anchors.topMargin: 10
|
||||
width: 200
|
||||
|
||||
text: qsTr("Install")
|
||||
onClicked: navFinish()
|
||||
}
|
||||
}
|
||||
64
airootfs/usr/lib/calamares/modules/mobile/install_target.qml
Normal file
64
airootfs/usr/lib/calamares/modules/mobile/install_target.qml
Normal file
@@ -0,0 +1,64 @@
|
||||
/* SPDX-FileCopyrightText: 2020 Oliver Smith <ollieparanoid@postmarketos.org>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later */
|
||||
import io.calamares.core 1.0
|
||||
import io.calamares.ui 1.0
|
||||
|
||||
import QtQuick 2.10
|
||||
import QtQuick.Controls 2.10
|
||||
import QtQuick.Layouts 1.3
|
||||
import org.kde.kirigami 2.7 as Kirigami
|
||||
import QtGraphicalEffects 1.0
|
||||
import QtQuick.Window 2.3
|
||||
import QtQuick.VirtualKeyboard 2.1
|
||||
|
||||
Item {
|
||||
anchors.left: parent.left
|
||||
anchors.top: parent.top
|
||||
anchors.right: parent.right
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
|
||||
Text {
|
||||
id: mainText
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: 10
|
||||
wrapMode: Text.WordWrap
|
||||
|
||||
text: "The installation was started from an external storage medium." +
|
||||
"<br>" +
|
||||
"You can either install to the same medium and overwrite the" +
|
||||
" installer, or install to the internal storage.<br>" +
|
||||
"<br>" +
|
||||
"Where would you like to install " + config.osName + "?"
|
||||
|
||||
width: 200
|
||||
}
|
||||
|
||||
Button {
|
||||
id: firstButton
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.top: mainText.bottom
|
||||
anchors.topMargin: 10
|
||||
width: 200
|
||||
|
||||
text: qsTr("Internal (eMMC)")
|
||||
onClicked: {
|
||||
config.installFromExternalToInternal = true;
|
||||
navNext();
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.top: firstButton.bottom
|
||||
anchors.topMargin: 10
|
||||
width: 200
|
||||
|
||||
text: qsTr("External (SD card)")
|
||||
onClicked: {
|
||||
config.installFromExternalToInternal = false;
|
||||
navNextFeature();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
/* SPDX-FileCopyrightText: 2020 Oliver Smith <ollieparanoid@postmarketos.org>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later */
|
||||
import io.calamares.core 1.0
|
||||
import io.calamares.ui 1.0
|
||||
|
||||
import QtQuick 2.10
|
||||
import QtQuick.Controls 2.10
|
||||
import QtQuick.Layouts 1.3
|
||||
import org.kde.kirigami 2.7 as Kirigami
|
||||
import QtGraphicalEffects 1.0
|
||||
import QtQuick.Window 2.3
|
||||
import QtQuick.VirtualKeyboard 2.1
|
||||
|
||||
Item {
|
||||
anchors.left: parent.left
|
||||
anchors.top: parent.top
|
||||
anchors.right: parent.right
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
|
||||
Text {
|
||||
id: mainText
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: 10
|
||||
wrapMode: Text.WordWrap
|
||||
|
||||
text: "Are you sure that you want to overwrite the internal storage?" +
|
||||
"<br><br>" +
|
||||
"<b>All existing data on the device will be lost!</b>"
|
||||
width: 200
|
||||
}
|
||||
|
||||
Button {
|
||||
id: firstButton
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.top: mainText.bottom
|
||||
anchors.topMargin: 10
|
||||
width: 200
|
||||
|
||||
text: qsTr("Yes")
|
||||
onClicked: {
|
||||
navNext();
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.top: firstButton.bottom
|
||||
anchors.topMargin: 10
|
||||
width: 200
|
||||
|
||||
text: qsTr("No")
|
||||
onClicked: {
|
||||
navBack();
|
||||
}
|
||||
}
|
||||
}
|
||||
173
airootfs/usr/lib/calamares/modules/mobile/mobile.conf
Normal file
173
airootfs/usr/lib/calamares/modules/mobile/mobile.conf
Normal file
@@ -0,0 +1,173 @@
|
||||
# SPDX-FileCopyrightText: no
|
||||
# SPDX-License-Identifier: CC0-1.0
|
||||
#
|
||||
# Commented out values are defaults.
|
||||
# All commands are running with 'sh -c'.
|
||||
---
|
||||
# This entry exists only to keep the tests happy, remove it in
|
||||
# any production configuration.
|
||||
bogus: true
|
||||
|
||||
#######
|
||||
### Target OS information
|
||||
#######
|
||||
|
||||
## Operating System Name
|
||||
# osName: "(unknown)"
|
||||
|
||||
## User Interface name (e.g. Plasma Mobile)
|
||||
# userInterface: "(unknown)"
|
||||
|
||||
## User Interface assumes that the password is numeric (as of writing, this is
|
||||
## the case with Plasma Mobile and Phosh)
|
||||
# userPasswordNumeric: true
|
||||
|
||||
## OS version
|
||||
# version: "(unknown)"
|
||||
|
||||
## Default username (for which the password will be set)
|
||||
## Ensure also cmdUsermod command matches the default user, so it can be changed if desired.
|
||||
# username: "user"
|
||||
|
||||
## reserved usernames (for user_pass username prompt and ssh_credentials)
|
||||
# reservedUsernames:
|
||||
# - adm
|
||||
# - at
|
||||
# - bin
|
||||
# - colord
|
||||
# - cron
|
||||
# - cyrus
|
||||
# - daemon
|
||||
# - ftp
|
||||
# - games
|
||||
# - geoclue
|
||||
# - guest
|
||||
# - halt
|
||||
# - lightdm
|
||||
# - lp
|
||||
# - mail
|
||||
# - man
|
||||
# - messagebus
|
||||
# - news
|
||||
# - nobody
|
||||
# - ntp
|
||||
# - operator
|
||||
# - polkitd
|
||||
# - postmaster
|
||||
# - pulse
|
||||
# - root
|
||||
# - shutdown
|
||||
# - smmsp
|
||||
# - squid
|
||||
# - sshd
|
||||
# - sync
|
||||
# - uucp
|
||||
# - vpopmail
|
||||
# - xfs
|
||||
|
||||
#######
|
||||
### Target device information
|
||||
#######
|
||||
|
||||
## Architecture (e.g. aarch64)
|
||||
# arch: "(unknown)"
|
||||
|
||||
## Name of the device (e.g. PinePhone)
|
||||
# device: "(unknown)"
|
||||
|
||||
## Partition that will be formatted and mounted (optionally with FDE) for the
|
||||
## rootfs
|
||||
# targetDeviceRoot: "/dev/unknown"
|
||||
|
||||
## Partition that will be formatted and mounted (optionally with FDE) for the
|
||||
## rootfs, on internal storage. The installer OS must not set this, if it was
|
||||
## booted from the internal storage (this is not checked in the mobile
|
||||
## module!).
|
||||
## If this is set, the user gets asked whether they want to install on internal
|
||||
## or external storage. If the user chose internal storage,
|
||||
## cmdInternalStoragePrepare (see below) runs before this partition gets
|
||||
## formatted (see below). A note is displayed, that the device is powered off
|
||||
## after installation and that the user should remove the external storage
|
||||
## medium. So you need to adjust the installer OS to poweroff in that case, and
|
||||
## not reboot. See postmarketos-ondev.git for reference.
|
||||
# targetDeviceRootInternal: ""
|
||||
|
||||
######
|
||||
### Installer Features
|
||||
######
|
||||
|
||||
## Ask whether sshd should be enabled or not. If enabled, add a dedicated ssh
|
||||
## user with proper username and password and suggest to change to key-based
|
||||
## authentication after installation.
|
||||
# featureSshd: true
|
||||
|
||||
## Ask the user, which filesystem to use.
|
||||
# featureFsType: false
|
||||
## Filesystems that the user can choose from.
|
||||
#fsModel:
|
||||
# - ext4
|
||||
# - f2fs
|
||||
# - btrfs
|
||||
## Default filesystem to display in the dialog. If featureFsType is disabled,
|
||||
## this gets used without asking the user.
|
||||
# defaultFs: ext4
|
||||
|
||||
## Start Qt's virtual keyboard within the mobile module. Disable if you bring
|
||||
## your own virtual keyboard (e.g. svkbd).
|
||||
# builtinVirtualKeyboard: true
|
||||
|
||||
#######
|
||||
### Commands running in the installer OS
|
||||
#######
|
||||
|
||||
## Format the target partition with LUKS
|
||||
## Arguments: <device>
|
||||
## Stdin: password with \n
|
||||
# cmdLuksFormat: "cryptsetup luksFormat --use-random"
|
||||
|
||||
## Open the formatted partition
|
||||
## Arguments: <device> <mapping name>
|
||||
## Stdin: password with \n
|
||||
# cmdLuksOpen: "cryptsetup luksOpen"
|
||||
|
||||
## Format the rootfs with a file system
|
||||
## Arguments: <device>
|
||||
## Btrfs: to allow snapshots to work on the root subvolume, it is recommended that this
|
||||
## command be a script which will create a subvolume and make it default
|
||||
## An example can be found at:
|
||||
## https://gitlab.com/mobian1/calamares-settings-mobian/-/merge_requests/2/diffs#diff-content-dde34f5f1c89e3dea63608c553bbc452dedf428f
|
||||
# cmdMkfsRootBtrfs: "mkfs.btrfs -L 'unknownOS_root'"
|
||||
# cmdMkfsRootExt4: "mkfs.ext4 -L 'unknownOS_root'"
|
||||
# cmdMkfsRootF2fs: "mkfs.f2fs -l 'unknownOS_root'"
|
||||
|
||||
## Mount the partition after formatting with file system
|
||||
## Arguments: <device> <mountpoint>
|
||||
# cmdMount: "mount"
|
||||
|
||||
## When user selects installation from external storage to internal storage
|
||||
## (see targetDeviceRootInternal above), use this command to prepare the
|
||||
## internal storage medium. The command must create a partition table with
|
||||
## two partitions (boot, root) and fill the boot partition. See the
|
||||
## ondev-internal-storage-prepare.sh in postmarketos-ondev as example.
|
||||
# cmdInternalStoragePrepare: "ondev-internal-storage-prepare"
|
||||
|
||||
#######
|
||||
### Commands running in the target OS (chroot)
|
||||
#######
|
||||
|
||||
## Change the username for the default user
|
||||
## Stdin: username with \n
|
||||
# cmdUsermod: "xargs -I{} -n1 usermod -m -d /home/{} -l {} -c {} user"
|
||||
|
||||
## Set the password for default user and sshd user
|
||||
## Arguments: <username>
|
||||
## Stdin: password twice, each time with \n
|
||||
# cmdPasswd: "passwd"
|
||||
|
||||
## Enable or disable sshd
|
||||
# cmdSshdEnable: "systemctl enable sshd.service"
|
||||
# cmdSshdDisable: "systemctl disable sshd.service"
|
||||
|
||||
## Create the user for sshd
|
||||
## Arguments: <username>
|
||||
# cmdSshdUseradd: "useradd -G wheel -m"
|
||||
390
airootfs/usr/lib/calamares/modules/mobile/mobile.qml
Normal file
390
airootfs/usr/lib/calamares/modules/mobile/mobile.qml
Normal file
@@ -0,0 +1,390 @@
|
||||
/* SPDX-FileCopyrightText: 2020 Oliver Smith <ollieparanoid@postmarketos.org>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later */
|
||||
import io.calamares.core 1.0
|
||||
import io.calamares.ui 1.0
|
||||
|
||||
import QtQuick 2.10
|
||||
import QtQuick.Controls 2.10
|
||||
import QtQuick.Layouts 1.3
|
||||
import org.kde.kirigami 2.7 as Kirigami
|
||||
import QtGraphicalEffects 1.0
|
||||
import QtQuick.Window 2.3
|
||||
import QtQuick.VirtualKeyboard 2.1
|
||||
|
||||
Page
|
||||
{
|
||||
property var screen: "welcome"
|
||||
property var screenPrevious: []
|
||||
property var titles: {
|
||||
"welcome": null, /* titlebar disabled */
|
||||
"install_target": "Installation target",
|
||||
"install_target_confirm": "Warning",
|
||||
"user_pass": "User password",
|
||||
"ssh_confirm": "SSH server",
|
||||
"ssh_credentials": "SSH credentials",
|
||||
"fs_selection": "Root filesystem",
|
||||
"fde_confirm": "Full disk encryption",
|
||||
"fde_pass": "Full disk encryption",
|
||||
"install_confirm": "Ready to install",
|
||||
"wait": null
|
||||
}
|
||||
property var features: [
|
||||
{"name": "welcome",
|
||||
"screens": ["welcome"]},
|
||||
{"name": "installTarget",
|
||||
"screens": ["install_target", "install_target_confirm"]},
|
||||
{"name": "userPassword",
|
||||
"screens": ["user_pass"]},
|
||||
{"name": "sshd",
|
||||
"screens": ["ssh_confirm", "ssh_credentials"]},
|
||||
{"name": "fsType",
|
||||
"screens": ["fs_selection"]},
|
||||
{"name": "fde",
|
||||
"screens": ["fde_confirm", "fde_pass"]},
|
||||
{"name": "installConfirm",
|
||||
"screens": ["install_confirm", "wait"]}
|
||||
]
|
||||
property var featureIdByScreen: (function() {
|
||||
/* Put "features" above into an index of screen name -> feature id:
|
||||
* featureIdByScreen = {"welcome": 0, "user_pass": 1, ...} */
|
||||
var ret = {};
|
||||
for (var i=0; i<features.length; i++) {
|
||||
for (var j=0; j<features[i]["screens"].length; j++) {
|
||||
ret[ features[i]["screens"][j] ] = i;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}())
|
||||
/* Only allow characters, that can be typed in with osk-sdl
|
||||
* (src/keyboard.cpp). Details in big comment in validatePassword(). */
|
||||
property var allowed_chars:
|
||||
/* layer 0 */ "abcdefghijklmnopqrstuvwxyz" +
|
||||
/* layer 1 */ "ABCDEFGHIJKLMNOPQRSTUVWXYZ" +
|
||||
/* layer 2 */ "1234567890" + "@#$%&-_+()" + ",\"':;!?" +
|
||||
/* layer 3 */ "~`|·√πτ÷×¶" + "©®£€¥^°*{}" + "\\/<>=[]" +
|
||||
/* bottom row */ " ."
|
||||
|
||||
Item {
|
||||
id: appContainer
|
||||
anchors.left: parent.left
|
||||
anchors.top: parent.top
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: inputPanel.top
|
||||
Item {
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
|
||||
Rectangle {
|
||||
id: mobileNavigation
|
||||
width: parent.width
|
||||
height: 30
|
||||
color: "#e6e4e1"
|
||||
Layout.fillWidth: true
|
||||
|
||||
border.width: 1
|
||||
border.color: "#a7a7a7"
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
|
||||
RowLayout {
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
spacing: 6
|
||||
|
||||
Button {
|
||||
Layout.leftMargin: 6
|
||||
id: mobileBack
|
||||
text: "<"
|
||||
|
||||
background: Rectangle {
|
||||
implicitWidth: 10
|
||||
implicitHeight: 7
|
||||
border.color: "#c1bab5"
|
||||
border.width: 1
|
||||
radius: 4
|
||||
color: mobileBack.down ? "#dbdbdb" : "#f2f2f2"
|
||||
}
|
||||
|
||||
onClicked: navBack()
|
||||
}
|
||||
Rectangle {
|
||||
implicitHeight: 10
|
||||
Layout.fillWidth: true
|
||||
color: "#e6e4e1"
|
||||
|
||||
Text {
|
||||
id: mobileTitle
|
||||
text: ""
|
||||
color: "#303638"
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
}
|
||||
Rectangle {
|
||||
color: "#e6e4e1"
|
||||
Layout.rightMargin: 6
|
||||
implicitWidth: 32
|
||||
implicitHeight: 30
|
||||
id: filler
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: load
|
||||
anchors.left: parent.left
|
||||
anchors.top: mobileNavigation.bottom
|
||||
anchors.right: parent.right
|
||||
}
|
||||
}
|
||||
}
|
||||
InputPanel {
|
||||
id: inputPanel
|
||||
y: Qt.inputMethod.visible ? parent.height - inputPanel.height : parent.height
|
||||
visible: config.builtinVirtualKeyboard
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: timer
|
||||
}
|
||||
|
||||
function skipFeatureInstallTarget() {
|
||||
return config.targetDeviceRootInternal == "";
|
||||
}
|
||||
|
||||
/* Navigation related */
|
||||
function navTo(name, historyPush=true) {
|
||||
console.log("Navigating to screen: " + name);
|
||||
if (historyPush)
|
||||
screenPrevious.push(screen);
|
||||
screen = name;
|
||||
load.source = name + ".qml";
|
||||
mobileNavigation.visible = (titles[name] !== null);
|
||||
mobileTitle.text = "<b>" + titles[name] + "</b>";
|
||||
Qt.inputMethod.hide();
|
||||
}
|
||||
function navFinish() {
|
||||
/* Show a waiting screen and wait a second (so it can render). The big
|
||||
* comment in Config.cpp::runPartitionJobThenLeave() explains why this
|
||||
* is necessary. */
|
||||
navTo("wait");
|
||||
timer.interval = 1000;
|
||||
timer.repeat = false;
|
||||
timer.triggered.connect(function() {
|
||||
/* Trigger Config.cpp::runPartitionJobThenLeave(). (We could expose
|
||||
* the function directly with qmlRegisterSingletonType somehow, but
|
||||
* I haven't seen existing Calamares code do that with the Config
|
||||
* object, so just use the side effect of setting the variable, as
|
||||
* done in existing code of Calamares modules.) */
|
||||
config.runPartitionJobThenLeave = 1
|
||||
});
|
||||
timer.start();
|
||||
}
|
||||
function navNextFeature() {
|
||||
var id;
|
||||
|
||||
/* Skip disabled features */
|
||||
for (id = featureIdByScreen[screen] + 1; id < features.length; id++) {
|
||||
/* First letter uppercase */
|
||||
var name = features[id]["name"];
|
||||
var nameUp = name.charAt(0).toUpperCase() + name.slice(1);
|
||||
|
||||
/* Check config.Feature<Name> */
|
||||
var configOption = "feature" + nameUp;
|
||||
if (config[configOption] === false) {
|
||||
console.log("Skipping feature (disabled in config): " + name);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Check skipFeature<Name>() */
|
||||
var funcName = "skipFeature" + nameUp;
|
||||
if (eval("typeof " + funcName) === "function"
|
||||
&& eval(funcName + "()")) {
|
||||
console.log("Skipping feature (skip function): " + name);
|
||||
continue;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
console.log("Navigating to feature: " + features[id]["name"]);
|
||||
return navTo(features[id]["screens"][0]);
|
||||
}
|
||||
function navNext() {
|
||||
var featureId = featureIdByScreen[screen];
|
||||
var featureScreens = features[featureId]["screens"];
|
||||
for (var i = 0; i<featureScreens.length; i++) {
|
||||
/* Seek ahead until i is current screen */
|
||||
if (featureScreens[i] != screen)
|
||||
continue;
|
||||
|
||||
/* Navigate to next screen in same feature */
|
||||
if (i + 1 < featureScreens.length) {
|
||||
var screenNext = featureScreens[i + 1];
|
||||
return navTo(screenNext);
|
||||
}
|
||||
|
||||
/* Screen is last in feature */
|
||||
return navNextFeature();
|
||||
}
|
||||
console.log("ERROR: navNext() failed for screen: " + screen);
|
||||
}
|
||||
function navBack() {
|
||||
if (screenPrevious.length)
|
||||
return navTo(screenPrevious.pop(), false);
|
||||
ViewManager.back();
|
||||
}
|
||||
function onActivate() {
|
||||
navTo(screen, false);
|
||||
}
|
||||
|
||||
/* Input validation: show/clear failures */
|
||||
function validationFailure(errorText, message="") {
|
||||
errorText.text = message;
|
||||
errorText.visible = true;
|
||||
return false;
|
||||
}
|
||||
function validationFailureClear(errorText) {
|
||||
errorText.text = "";
|
||||
errorText.visible = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Input validation: user-screens (fde_pass, user_pass, ssh_credentials) */
|
||||
function validatePin(userPin, userPinRepeat, errorText) {
|
||||
var pin = userPin.text;
|
||||
var repeat = userPinRepeat.text;
|
||||
|
||||
if (pin == "")
|
||||
return validationFailure(errorText);
|
||||
|
||||
if (!pin.match(/^[0-9]*$/))
|
||||
return validationFailure(errorText,
|
||||
"Only digits are allowed.");
|
||||
|
||||
if (pin.length < 5)
|
||||
return validationFailure(errorText,
|
||||
"Too short: needs at least 5 digits.");
|
||||
|
||||
if (repeat == "")
|
||||
return validationFailure(errorText);
|
||||
|
||||
if (repeat != pin)
|
||||
return validationFailure(errorText,
|
||||
"The PINs don't match.");
|
||||
|
||||
return validationFailureClear(errorText);
|
||||
}
|
||||
function validateUsername(username, errorText, extraReservedUsernames = []) {
|
||||
var name = username.text;
|
||||
var reserved = config.reservedUsernames.concat(extraReservedUsernames);
|
||||
|
||||
/* Validate characters */
|
||||
for (var i = 0; i < name.length; i++) {
|
||||
if (i) {
|
||||
if (!name[i].match(/^[a-z0-9_-]$/))
|
||||
return validationFailure(errorText,
|
||||
"Characters must be lowercase" +
|
||||
" letters, numbers,<br>" +
|
||||
" underscores or minus signs.");
|
||||
} else {
|
||||
if (!name[i].match(/^[a-z_]$/))
|
||||
return validationFailure(errorText,
|
||||
"First character must be a" +
|
||||
" lowercase letter or an" +
|
||||
" underscore.");
|
||||
}
|
||||
}
|
||||
|
||||
/* Validate against reserved usernames */
|
||||
for (var i = 0; i < reserved.length; i++) {
|
||||
if (name == reserved[i])
|
||||
return validationFailure(errorText, "Username '" +
|
||||
reserved[i] +
|
||||
"' is reserved.");
|
||||
}
|
||||
|
||||
/* Passed */
|
||||
return validationFailureClear(errorText);
|
||||
}
|
||||
|
||||
function validateSshdUsername(username, errorText) {
|
||||
return validateUsername(username, errorText, [config.username]);
|
||||
}
|
||||
function validateSshdPassword(password, passwordRepeat, errorText) {
|
||||
var pass = password.text;
|
||||
var repeat = passwordRepeat.text;
|
||||
|
||||
if (pass == "")
|
||||
return validationFailure(errorText);
|
||||
|
||||
if (pass.length < 6)
|
||||
return validationFailure(errorText,
|
||||
"Too short: needs at least 6" +
|
||||
" digits/characters.");
|
||||
|
||||
if (repeat == "")
|
||||
return validationFailure(errorText);
|
||||
|
||||
if (pass != repeat)
|
||||
return validationFailure(errorText, "Passwords don't match.");
|
||||
|
||||
return validationFailureClear(errorText);
|
||||
}
|
||||
function check_chars(input) {
|
||||
for (var i = 0; i < input.length; i++) {
|
||||
if (allowed_chars.indexOf(input[i]) == -1)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
function allowed_chars_multiline() {
|
||||
/* return allowed_chars split across multiple lines */
|
||||
var step = 20;
|
||||
var ret = "";
|
||||
for (var i = 0; i < allowed_chars.length + step; i += step)
|
||||
ret += allowed_chars.slice(i, i + step) + "\n";
|
||||
return ret.trim();
|
||||
}
|
||||
function validatePassword(password, passwordRepeat, errorText) {
|
||||
var pass = password.text;
|
||||
var repeat = passwordRepeat.text;
|
||||
|
||||
if (pass == "")
|
||||
return validationFailure(errorText);
|
||||
|
||||
/* This function gets called for the FDE password and for the user
|
||||
* password. As of writing, all distributions shipping the mobile
|
||||
* module are using osk-sdl to type in the FDE password after the
|
||||
* installation, and another keyboard after booting up, to type in the
|
||||
* user password. The osk-sdl password has the same keys as
|
||||
* squeekboard's default layout, and other keyboards should be able to
|
||||
* type these characters in as well. For now, verify that the password
|
||||
* only contains characters that can be typed in by osk-sdl. If you
|
||||
* need this to be more sophisticated, feel free to submit patches to
|
||||
* make this more configurable. */
|
||||
if (!check_chars(pass))
|
||||
return validationFailure(errorText,
|
||||
"The password must only contain" +
|
||||
" these characters, others can possibly" +
|
||||
" not be typed in after installation:\n" +
|
||||
"\n" +
|
||||
allowed_chars_multiline());
|
||||
|
||||
if (pass.length < 6)
|
||||
return validationFailure(errorText,
|
||||
"Too short: needs at least 6" +
|
||||
" digits/characters.");
|
||||
|
||||
if (repeat == "")
|
||||
return validationFailure(errorText);
|
||||
|
||||
if (pass != repeat)
|
||||
return validationFailure(errorText, "Passwords don't match.");
|
||||
|
||||
return validationFailureClear(errorText);
|
||||
}
|
||||
}
|
||||
21
airootfs/usr/lib/calamares/modules/mobile/mobile.qrc
Normal file
21
airootfs/usr/lib/calamares/modules/mobile/mobile.qrc
Normal file
@@ -0,0 +1,21 @@
|
||||
<!DOCTYPE RCC><RCC version="1.0">
|
||||
<qresource>
|
||||
<file>mobile.qml</file>
|
||||
|
||||
<file>welcome.qml</file>
|
||||
|
||||
<file>install_target.qml</file> <!-- install from external to internal? -->
|
||||
<file>install_target_confirm.qml</file> <!-- overwrite internal storage? -->
|
||||
|
||||
<file>user_pass.qml</file> <!-- default user: username, password -->
|
||||
<file>ssh_confirm.qml</file> <!-- sshd: enable or not? -->
|
||||
<file>ssh_credentials.qml</file> <!-- sshd user: username, password -->
|
||||
<file>fs_selection.qml</file> <!-- filesystem selection -->
|
||||
|
||||
<file>fde_confirm.qml</file> <!-- enable FDE or not? -->
|
||||
<file>fde_pass.qml</file> <!-- FDE password (optional) -->
|
||||
<file>install_confirm.qml</file> <!-- final confirmation before install -->
|
||||
|
||||
<file>wait.qml</file> <!-- please wait while partitioning -->
|
||||
</qresource>
|
||||
</RCC>
|
||||
68
airootfs/usr/lib/calamares/modules/mobile/ssh_confirm.qml
Normal file
68
airootfs/usr/lib/calamares/modules/mobile/ssh_confirm.qml
Normal file
@@ -0,0 +1,68 @@
|
||||
/* SPDX-FileCopyrightText: 2020 Oliver Smith <ollieparanoid@postmarketos.org>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later */
|
||||
import io.calamares.core 1.0
|
||||
import io.calamares.ui 1.0
|
||||
|
||||
import QtQuick 2.10
|
||||
import QtQuick.Controls 2.10
|
||||
import QtQuick.Layouts 1.3
|
||||
import org.kde.kirigami 2.7 as Kirigami
|
||||
import QtGraphicalEffects 1.0
|
||||
import QtQuick.Window 2.3
|
||||
import QtQuick.VirtualKeyboard 2.1
|
||||
|
||||
Item {
|
||||
anchors.left: parent.left
|
||||
anchors.top: parent.top
|
||||
anchors.right: parent.right
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
|
||||
Text {
|
||||
id: mainText
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: 30
|
||||
wrapMode: Text.WordWrap
|
||||
|
||||
text: "If you don't know what SSH is, choose 'disable'.<br>" +
|
||||
"<br>" +
|
||||
"With 'enable', you will be asked for a second username and" +
|
||||
" password. You will be able to login to the SSH server with" +
|
||||
" these credentials via USB (172.16.42.1), Wi-Fi and possibly" +
|
||||
" cellular network. It is recommended to replace the password" +
|
||||
" with an SSH key after the installation.<br>" +
|
||||
"<br>" +
|
||||
"More information:<br>" +
|
||||
"https://postmarketos.org/ssh"
|
||||
|
||||
width: 200
|
||||
}
|
||||
|
||||
Button {
|
||||
id: firstButton
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.top: mainText.bottom
|
||||
anchors.topMargin: 40
|
||||
width: 200
|
||||
|
||||
text: qsTr("Enable")
|
||||
onClicked: {
|
||||
config.isSshEnabled = true;
|
||||
navNext();
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.top: firstButton.bottom
|
||||
anchors.topMargin: 40
|
||||
width: 200
|
||||
|
||||
text: qsTr("Disable")
|
||||
onClicked: {
|
||||
config.isSshEnabled = false;
|
||||
navNextFeature();
|
||||
}
|
||||
}
|
||||
}
|
||||
107
airootfs/usr/lib/calamares/modules/mobile/ssh_credentials.qml
Normal file
107
airootfs/usr/lib/calamares/modules/mobile/ssh_credentials.qml
Normal file
@@ -0,0 +1,107 @@
|
||||
/* SPDX-FileCopyrightText: 2020 Oliver Smith <ollieparanoid@postmarketos.org>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later */
|
||||
import io.calamares.core 1.0
|
||||
import io.calamares.ui 1.0
|
||||
|
||||
import QtQuick 2.10
|
||||
import QtQuick.Controls 2.10
|
||||
import QtQuick.Layouts 1.3
|
||||
import org.kde.kirigami 2.7 as Kirigami
|
||||
import QtGraphicalEffects 1.0
|
||||
import QtQuick.Window 2.3
|
||||
import QtQuick.VirtualKeyboard 2.1
|
||||
|
||||
Item {
|
||||
anchors.left: parent.left
|
||||
anchors.top: parent.top
|
||||
anchors.right: parent.right
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
|
||||
TextField {
|
||||
id: username
|
||||
anchors.top: parent.top
|
||||
placeholderText: qsTr("SSH username")
|
||||
inputMethodHints: Qt.ImhPreferLowercase
|
||||
onTextChanged: validateSshdUsername(username, errorTextUsername)
|
||||
text: config.sshdUsername
|
||||
|
||||
onActiveFocusChanged: {
|
||||
if(activeFocus) {
|
||||
Qt.inputMethod.update(Qt.ImQueryInput);
|
||||
}
|
||||
}
|
||||
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.topMargin: 50
|
||||
width: 200
|
||||
}
|
||||
|
||||
Text {
|
||||
id: errorTextUsername
|
||||
anchors.top: username.bottom
|
||||
visible: false
|
||||
wrapMode: Text.WordWrap
|
||||
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.topMargin: 50
|
||||
width: 200
|
||||
}
|
||||
|
||||
TextField {
|
||||
id: password
|
||||
anchors.top: errorTextUsername.bottom
|
||||
placeholderText: qsTr("SSH password")
|
||||
echoMode: TextInput.Password
|
||||
onTextChanged: validateSshdPassword(password, passwordRepeat,
|
||||
errorTextPassword)
|
||||
text: config.sshdPassword
|
||||
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.topMargin: 50
|
||||
width: 200
|
||||
}
|
||||
|
||||
TextField {
|
||||
id: passwordRepeat
|
||||
anchors.top: password.bottom
|
||||
placeholderText: qsTr("SSH password (repeat)")
|
||||
echoMode: TextInput.Password
|
||||
onTextChanged: validateSshdPassword(password, passwordRepeat,
|
||||
errorTextPassword)
|
||||
text: config.sshdPassword
|
||||
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.topMargin: 50
|
||||
width: 200
|
||||
}
|
||||
|
||||
Text {
|
||||
id: errorTextPassword
|
||||
anchors.top: passwordRepeat.bottom
|
||||
visible: false
|
||||
wrapMode: Text.WordWrap
|
||||
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.topMargin: 50
|
||||
width: 200
|
||||
}
|
||||
Button {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.top: errorTextPassword.bottom
|
||||
anchors.topMargin: 40
|
||||
width: 200
|
||||
|
||||
text: qsTr("Continue")
|
||||
onClicked: {
|
||||
if (validateSshdUsername(username, errorTextUsername) &&
|
||||
validateSshdPassword(password, passwordRepeat,
|
||||
errorTextPassword)) {
|
||||
config.sshdUsername = username.text;
|
||||
config.sshdPassword = password.text;
|
||||
|
||||
navNext();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
143
airootfs/usr/lib/calamares/modules/mobile/user_pass.qml
Normal file
143
airootfs/usr/lib/calamares/modules/mobile/user_pass.qml
Normal file
@@ -0,0 +1,143 @@
|
||||
/* SPDX-FileCopyrightText: 2020 Oliver Smith <ollieparanoid@postmarketos.org>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later */
|
||||
import io.calamares.core 1.0
|
||||
import io.calamares.ui 1.0
|
||||
|
||||
import QtQuick 2.10
|
||||
import QtQuick.Controls 2.10
|
||||
import QtQuick.Layouts 1.3
|
||||
import org.kde.kirigami 2.7 as Kirigami
|
||||
import QtGraphicalEffects 1.0
|
||||
import QtQuick.Window 2.3
|
||||
import QtQuick.VirtualKeyboard 2.1
|
||||
|
||||
Item {
|
||||
property var passPlaceholder: (config.userPasswordNumeric
|
||||
? "PIN"
|
||||
: "Password")
|
||||
property var hints: (config.userPasswordNumeric
|
||||
? Qt.ImhDigitsOnly
|
||||
: Qt.ImhPreferLowercase)
|
||||
property var validatePassFunc: (config.userPasswordNumeric
|
||||
? validatePin
|
||||
: validatePassword);
|
||||
|
||||
property var validateNameFunc: validateUsername;
|
||||
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.top: parent.top
|
||||
anchors.right: parent.right
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
|
||||
Text {
|
||||
id: usernameDescription
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: 10
|
||||
wrapMode: Text.WordWrap
|
||||
|
||||
text: (function() {
|
||||
return "Set the username of your user. The default" +
|
||||
" username is \"" + config.username + "\".";
|
||||
}())
|
||||
|
||||
width: 200
|
||||
}
|
||||
|
||||
TextField {
|
||||
id: username
|
||||
anchors.top: usernameDescription.bottom
|
||||
placeholderText: qsTr("Username")
|
||||
onTextChanged: validateNameFunc(username, errorText)
|
||||
text: config.username
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.topMargin: 10
|
||||
width: 200
|
||||
}
|
||||
|
||||
Text {
|
||||
id: userPassDescription
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.top: username.bottom
|
||||
anchors.topMargin: 10
|
||||
wrapMode: Text.WordWrap
|
||||
|
||||
text: (function() {
|
||||
if (config.userPasswordNumeric) {
|
||||
return "Set the numeric password of your user. The" +
|
||||
" lockscreen will ask for this PIN. This is" +
|
||||
" <i>not</i> the PIN of your SIM card. Make sure to" +
|
||||
" remember it.";
|
||||
} else {
|
||||
return "Set the password of your user. The lockscreen will" +
|
||||
" ask for this password. Make sure to remember it.";
|
||||
}
|
||||
}())
|
||||
|
||||
width: 200
|
||||
}
|
||||
|
||||
TextField {
|
||||
id: userPass
|
||||
anchors.top: userPassDescription.bottom
|
||||
placeholderText: qsTr(passPlaceholder)
|
||||
echoMode: TextInput.Password
|
||||
onTextChanged: validatePassFunc(userPass, userPassRepeat, errorText)
|
||||
text: config.userPassword
|
||||
|
||||
/* Let the virtual keyboard change to digits only */
|
||||
inputMethodHints: hints
|
||||
onActiveFocusChanged: {
|
||||
if(activeFocus) {
|
||||
Qt.inputMethod.update(Qt.ImQueryInput)
|
||||
}
|
||||
}
|
||||
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.topMargin: 10
|
||||
width: 200
|
||||
}
|
||||
|
||||
TextField {
|
||||
id: userPassRepeat
|
||||
anchors.top: userPass.bottom
|
||||
placeholderText: qsTr(passPlaceholder + " (repeat)")
|
||||
inputMethodHints: hints
|
||||
echoMode: TextInput.Password
|
||||
onTextChanged: validatePassFunc(userPass, userPassRepeat, errorText)
|
||||
text: config.userPassword
|
||||
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.topMargin: 10
|
||||
width: 200
|
||||
}
|
||||
|
||||
Text {
|
||||
anchors.top: userPassRepeat.bottom
|
||||
id: errorText
|
||||
visible: false
|
||||
wrapMode: Text.WordWrap
|
||||
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.topMargin: 10
|
||||
width: 200
|
||||
}
|
||||
|
||||
Button {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.top: errorText.bottom
|
||||
anchors.topMargin: 10
|
||||
width: 200
|
||||
|
||||
text: qsTr("Continue")
|
||||
onClicked: {
|
||||
if (validatePassFunc(userPass, userPassRepeat, errorText) && validateNameFunc(username, errorText)) {
|
||||
config.userPassword = userPass.text;
|
||||
config.username = username.text;
|
||||
navNext();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
45
airootfs/usr/lib/calamares/modules/mobile/wait.qml
Normal file
45
airootfs/usr/lib/calamares/modules/mobile/wait.qml
Normal file
@@ -0,0 +1,45 @@
|
||||
/* SPDX-FileCopyrightText: 2020 Oliver Smith <ollieparanoid@postmarketos.org>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later */
|
||||
import io.calamares.core 1.0
|
||||
import io.calamares.ui 1.0
|
||||
|
||||
import QtQuick 2.10
|
||||
import QtQuick.Controls 2.10
|
||||
import QtQuick.Layouts 1.3
|
||||
import org.kde.kirigami 2.7 as Kirigami
|
||||
import QtGraphicalEffects 1.0
|
||||
import QtQuick.Window 2.3
|
||||
import QtQuick.VirtualKeyboard 2.1
|
||||
|
||||
Page
|
||||
{
|
||||
id: fdeWait
|
||||
|
||||
Item {
|
||||
anchors.left: parent.left
|
||||
anchors.top: parent.top
|
||||
anchors.right: parent.right
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
|
||||
Image {
|
||||
id: logo
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: 10
|
||||
height: 50
|
||||
fillMode: Image.PreserveAspectFit
|
||||
source: "file:///usr/share/calamares/branding/default-mobile/logo.png"
|
||||
}
|
||||
Text {
|
||||
id: waitText
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.top: logo.bottom
|
||||
anchors.topMargin: 50
|
||||
wrapMode: Text.WordWrap
|
||||
text: "Formatting and mounting target partition. This may" +
|
||||
" take up to ten minutes, please be patient."
|
||||
width: 200
|
||||
}
|
||||
}
|
||||
}
|
||||
65
airootfs/usr/lib/calamares/modules/mobile/welcome.qml
Normal file
65
airootfs/usr/lib/calamares/modules/mobile/welcome.qml
Normal file
@@ -0,0 +1,65 @@
|
||||
/* SPDX-FileCopyrightText: 2020 Oliver Smith <ollieparanoid@postmarketos.org>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later */
|
||||
import io.calamares.core 1.0
|
||||
import io.calamares.ui 1.0
|
||||
|
||||
import QtQuick 2.10
|
||||
import QtQuick.Controls 2.10
|
||||
import QtQuick.Layouts 1.3
|
||||
import org.kde.kirigami 2.7 as Kirigami
|
||||
import QtGraphicalEffects 1.0
|
||||
import QtQuick.Window 2.3
|
||||
import QtQuick.VirtualKeyboard 2.1
|
||||
|
||||
Page
|
||||
{
|
||||
id: welcome
|
||||
|
||||
Item {
|
||||
id: appContainer
|
||||
anchors.left: parent.left
|
||||
anchors.top: parent.top
|
||||
anchors.right: parent.right
|
||||
Item {
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
|
||||
Image {
|
||||
id: logo
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: 10
|
||||
height: 50
|
||||
fillMode: Image.PreserveAspectFit
|
||||
source: "file:///usr/share/calamares/branding/default-mobile/logo.png"
|
||||
}
|
||||
Text {
|
||||
id: mainText
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.top: logo.bottom
|
||||
anchors.topMargin: 10
|
||||
horizontalAlignment: Text.AlignRight
|
||||
text: "You are about to install<br>" +
|
||||
"<b>" + config.osName +
|
||||
" " + config.version + "</b><br>" +
|
||||
"user interface " +
|
||||
"<b>" + config.userInterface + "</b><br>" +
|
||||
"architecture " +
|
||||
"<b>" + config.arch + "</b><br>" +
|
||||
"on your <br>" +
|
||||
"<b>" + config.device + "</b><br>"
|
||||
width: 200
|
||||
}
|
||||
|
||||
Button {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.top: mainText.bottom
|
||||
anchors.topMargin: 10
|
||||
width: 200
|
||||
|
||||
text: qsTr("Continue")
|
||||
onClicked: navNext()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
395
airootfs/usr/lib/calamares/modules/mount/main.py
Normal file
395
airootfs/usr/lib/calamares/modules/mount/main.py
Normal file
@@ -0,0 +1,395 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# === This file is part of Calamares - <https://calamares.io> ===
|
||||
#
|
||||
# SPDX-FileCopyrightText: 2014 Aurélien Gâteau <agateau@kde.org>
|
||||
# SPDX-FileCopyrightText: 2017 Alf Gaida <agaida@siduction.org>
|
||||
# SPDX-FileCopyrightText: 2019 Adriaan de Groot <groot@kde.org>
|
||||
# SPDX-FileCopyrightText: 2019 Kevin Kofler <kevin.kofler@chello.at>
|
||||
# SPDX-FileCopyrightText: 2019-2020 Collabora Ltd
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#
|
||||
# Calamares is Free Software: see the License-Identifier above.
|
||||
#
|
||||
|
||||
import tempfile
|
||||
import subprocess
|
||||
import os
|
||||
import re
|
||||
|
||||
import libcalamares
|
||||
|
||||
import gettext
|
||||
|
||||
_ = gettext.translation("calamares-python",
|
||||
localedir=libcalamares.utils.gettext_path(),
|
||||
languages=libcalamares.utils.gettext_languages(),
|
||||
fallback=True).gettext
|
||||
|
||||
|
||||
class ZfsException(Exception):
|
||||
"""Exception raised when there is a problem with zfs
|
||||
|
||||
Attributes:
|
||||
message -- explanation of the error
|
||||
"""
|
||||
|
||||
def __init__(self, message):
|
||||
self.message = message
|
||||
|
||||
|
||||
def pretty_name():
|
||||
return _("Mounting partitions.")
|
||||
|
||||
|
||||
def disk_name_for_partition(partition):
|
||||
""" Returns disk name for each found partition.
|
||||
|
||||
:param partition:
|
||||
:return:
|
||||
"""
|
||||
name = os.path.basename(partition["device"])
|
||||
|
||||
if name.startswith("/dev/mmcblk") or name.startswith("/dev/nvme"):
|
||||
return re.sub("p[0-9]+$", "", name)
|
||||
|
||||
return re.sub("[0-9]+$", "", name)
|
||||
|
||||
|
||||
def is_ssd_disk(partition):
|
||||
""" Checks if given partition is on an ssd disk.
|
||||
|
||||
:param partition: A dict containing the partition information
|
||||
:return: True is the partition in on an ssd, False otherwise
|
||||
"""
|
||||
|
||||
try:
|
||||
disk_name = disk_name_for_partition(partition)
|
||||
filename = os.path.join("/sys/block", disk_name, "queue/rotational")
|
||||
|
||||
with open(filename) as sysfile:
|
||||
return sysfile.read() == "0\n"
|
||||
except:
|
||||
return False
|
||||
|
||||
|
||||
def get_mount_options(filesystem, mount_options, partition, efi_location = None):
|
||||
"""
|
||||
Returns the mount options for the partition object and filesystem
|
||||
|
||||
:param filesystem: A string containing the filesystem
|
||||
:param mount_options: A list of dicts that descripes the mount options for each mountpoint
|
||||
:param partition: A dict containing information about the partition
|
||||
:param efi_location: A string holding the location of the EFI partition or None
|
||||
:return: A comma seperated string containing the mount options suitable for passing to mount
|
||||
"""
|
||||
|
||||
# Extra mounts can optionally have "options" set, in this case, they override other all other settings
|
||||
if "options" in partition:
|
||||
return ",".join(partition["options"])
|
||||
|
||||
# If there are no mount options defined then we use the defaults
|
||||
if mount_options is None:
|
||||
return "defaults"
|
||||
|
||||
# The EFI partition uses special mounting options
|
||||
if efi_location and partition["mountPoint"] == efi_location:
|
||||
effective_filesystem = "efi"
|
||||
else:
|
||||
effective_filesystem = filesystem
|
||||
|
||||
options = next((x for x in mount_options if x["filesystem"] == effective_filesystem), None)
|
||||
|
||||
# If there is no match then check for default options
|
||||
if options is None:
|
||||
options = next((x for x in mount_options if x["filesystem"] == "default"), None)
|
||||
|
||||
# If it is still None, then fallback to returning defaults
|
||||
if options is None:
|
||||
return "defaults"
|
||||
|
||||
option_items = options.get("options", []).copy()
|
||||
|
||||
# Append the appropriate options for ssd or hdd if set
|
||||
if is_ssd_disk(partition):
|
||||
option_items.extend(options.get("ssdOptions", []))
|
||||
else:
|
||||
option_items.extend(options.get("hddOptions", []))
|
||||
|
||||
if option_items:
|
||||
return ",".join(option_items)
|
||||
else:
|
||||
return "defaults"
|
||||
|
||||
|
||||
def get_btrfs_subvolumes(partitions):
|
||||
"""
|
||||
Gets the job-configuration for btrfs subvolumes, or if there is
|
||||
none given, returns a default configuration that matches
|
||||
the setup (/ and /home) from before configurability was introduced.
|
||||
|
||||
@param partitions
|
||||
The partitions (from the partitioning module) that will exist on disk.
|
||||
This is used to filter out subvolumes that don't need to be created
|
||||
because they get a dedicated partition instead.
|
||||
"""
|
||||
btrfs_subvolumes = libcalamares.job.configuration.get("btrfsSubvolumes", None)
|
||||
# Warn if there's no configuration at all, and empty configurations are
|
||||
# replaced by a simple root-only layout.
|
||||
if btrfs_subvolumes is None:
|
||||
libcalamares.utils.warning("No configuration for btrfsSubvolumes")
|
||||
if not btrfs_subvolumes:
|
||||
btrfs_subvolumes = [dict(mountPoint="/", subvolume="/@"), dict(mountPoint="/home", subvolume="/@home")]
|
||||
|
||||
# Filter out the subvolumes which have a dedicated partition
|
||||
non_root_partition_mounts = [m for m in [p.get("mountPoint", None) for p in partitions] if
|
||||
m is not None and m != '/']
|
||||
btrfs_subvolumes = list(filter(lambda s: s["mountPoint"] not in non_root_partition_mounts, btrfs_subvolumes))
|
||||
|
||||
# If we have a swap **file**, give it a separate subvolume.
|
||||
swap_choice = libcalamares.globalstorage.value("partitionChoices")
|
||||
if swap_choice and swap_choice.get("swap", None) == "file":
|
||||
swap_subvol = libcalamares.job.configuration.get("btrfsSwapSubvol", "/@swap")
|
||||
btrfs_subvolumes.append({'mountPoint': '/swap', 'subvolume': swap_subvol})
|
||||
libcalamares.globalstorage.insert("btrfsSwapSubvol", swap_subvol)
|
||||
|
||||
return btrfs_subvolumes
|
||||
|
||||
|
||||
def mount_zfs(root_mount_point, partition):
|
||||
""" Mounts a zfs partition at @p root_mount_point
|
||||
|
||||
:param root_mount_point: The absolute path to the root of the install
|
||||
:param partition: The partition map from global storage for this partition
|
||||
:return:
|
||||
"""
|
||||
# Get the list of zpools from global storage
|
||||
zfs_pool_list = libcalamares.globalstorage.value("zfsPoolInfo")
|
||||
if not zfs_pool_list:
|
||||
libcalamares.utils.warning("Failed to locate zfsPoolInfo data in global storage")
|
||||
raise ZfsException(_("Internal error mounting zfs datasets"))
|
||||
|
||||
# Find the zpool matching this partition
|
||||
for zfs_pool in zfs_pool_list:
|
||||
if zfs_pool["mountpoint"] == partition["mountPoint"]:
|
||||
pool_name = zfs_pool["poolName"]
|
||||
ds_name = zfs_pool["dsName"]
|
||||
|
||||
# import the zpool
|
||||
try:
|
||||
libcalamares.utils.host_env_process_output(["zpool", "import", "-N", "-R", root_mount_point, pool_name], None)
|
||||
except subprocess.CalledProcessError:
|
||||
raise ZfsException(_("Failed to import zpool"))
|
||||
|
||||
# Get the encrpytion information from global storage
|
||||
zfs_info_list = libcalamares.globalstorage.value("zfsInfo")
|
||||
encrypt = False
|
||||
if zfs_info_list:
|
||||
for zfs_info in zfs_info_list:
|
||||
if zfs_info["mountpoint"] == partition["mountPoint"] and zfs_info["encrypted"] is True:
|
||||
encrypt = True
|
||||
passphrase = zfs_info["passphrase"]
|
||||
|
||||
if encrypt is True:
|
||||
# The zpool is encrypted, we need to unlock it
|
||||
try:
|
||||
libcalamares.utils.host_env_process_output(["zfs", "load-key", pool_name], None, passphrase)
|
||||
except subprocess.CalledProcessError:
|
||||
raise ZfsException(_("Failed to unlock zpool"))
|
||||
|
||||
if partition["mountPoint"] == '/':
|
||||
# Get the zfs dataset list from global storage
|
||||
zfs = libcalamares.globalstorage.value("zfsDatasets")
|
||||
|
||||
if not zfs:
|
||||
libcalamares.utils.warning("Failed to locate zfs dataset list")
|
||||
raise ZfsException(_("Internal error mounting zfs datasets"))
|
||||
|
||||
zfs.sort(key=lambda x: x["mountpoint"])
|
||||
for dataset in zfs:
|
||||
try:
|
||||
if dataset["canMount"] == "noauto" or dataset["canMount"] is True:
|
||||
libcalamares.utils.host_env_process_output(["zfs", "mount",
|
||||
dataset["zpool"] + '/' + dataset["dsName"]])
|
||||
except subprocess.CalledProcessError:
|
||||
raise ZfsException(_("Failed to set zfs mountpoint"))
|
||||
else:
|
||||
try:
|
||||
libcalamares.utils.host_env_process_output(["zfs", "mount", pool_name + '/' + ds_name])
|
||||
except subprocess.CalledProcessError:
|
||||
raise ZfsException(_("Failed to set zfs mountpoint"))
|
||||
|
||||
|
||||
def mount_partition(root_mount_point, partition, partitions, mount_options, mount_options_list, efi_location):
|
||||
"""
|
||||
Do a single mount of @p partition inside @p root_mount_point.
|
||||
|
||||
:param root_mount_point: A string containing the root of the install
|
||||
:param partition: A dict containing information about the partition
|
||||
:param partitions: The full list of partitions used to filter out btrfs subvols which have duplicate mountpoints
|
||||
:param mount_options: The mount options from the config file
|
||||
:param mount_options_list: A list of options for each mountpoint to be placed in global storage for future modules
|
||||
:param efi_location: A string holding the location of the EFI partition or None
|
||||
:return:
|
||||
"""
|
||||
# Create mount point with `+` rather than `os.path.join()` because
|
||||
# `partition["mountPoint"]` starts with a '/'.
|
||||
raw_mount_point = partition["mountPoint"]
|
||||
if not raw_mount_point:
|
||||
return
|
||||
|
||||
mount_point = root_mount_point + raw_mount_point
|
||||
|
||||
# Ensure that the created directory has the correct SELinux context on
|
||||
# SELinux-enabled systems.
|
||||
|
||||
os.makedirs(mount_point, exist_ok=True)
|
||||
|
||||
try:
|
||||
subprocess.call(['chcon', '--reference=' + raw_mount_point, mount_point])
|
||||
except FileNotFoundError as e:
|
||||
libcalamares.utils.warning(str(e))
|
||||
except OSError:
|
||||
libcalamares.utils.error("Cannot run 'chcon' normally.")
|
||||
raise
|
||||
|
||||
fstype = partition.get("fs", "").lower()
|
||||
if fstype == "unformatted":
|
||||
return
|
||||
|
||||
if fstype == "fat16" or fstype == "fat32":
|
||||
fstype = "vfat"
|
||||
|
||||
device = partition["device"]
|
||||
|
||||
if "luksMapperName" in partition:
|
||||
device = os.path.join("/dev/mapper", partition["luksMapperName"])
|
||||
|
||||
if fstype == "zfs":
|
||||
mount_zfs(root_mount_point, partition)
|
||||
else: # fstype == "zfs"
|
||||
mount_options_string = get_mount_options(fstype, mount_options, partition, efi_location)
|
||||
if libcalamares.utils.mount(device,
|
||||
mount_point,
|
||||
fstype,
|
||||
mount_options_string) != 0:
|
||||
libcalamares.utils.warning("Cannot mount {}".format(device))
|
||||
mount_options_list.append({"mountpoint": raw_mount_point, "option_string": mount_options_string})
|
||||
|
||||
# Special handling for btrfs subvolumes. Create the subvolumes listed in mount.conf
|
||||
if fstype == "btrfs" and partition["mountPoint"] == '/':
|
||||
# Root has been mounted to btrfs volume -> create subvolumes from configuration
|
||||
btrfs_subvolumes = get_btrfs_subvolumes(partitions)
|
||||
|
||||
# Store created list in global storage so it can be used in the fstab module
|
||||
libcalamares.globalstorage.insert("btrfsSubvolumes", btrfs_subvolumes)
|
||||
# Create the subvolumes that are in the completed list
|
||||
for s in btrfs_subvolumes:
|
||||
if not s["subvolume"]:
|
||||
continue
|
||||
os.makedirs(root_mount_point + os.path.dirname(s["subvolume"]), exist_ok=True)
|
||||
subprocess.check_call(["btrfs", "subvolume", "create",
|
||||
root_mount_point + s["subvolume"]])
|
||||
if s["mountPoint"] == "/":
|
||||
# insert the root subvolume into global storage
|
||||
libcalamares.globalstorage.insert("btrfsRootSubvolume", s["subvolume"])
|
||||
subprocess.check_call(["umount", "-v", root_mount_point])
|
||||
|
||||
device = partition["device"]
|
||||
|
||||
if "luksMapperName" in partition:
|
||||
device = os.path.join("/dev/mapper", partition["luksMapperName"])
|
||||
|
||||
# Mount the subvolumes
|
||||
swap_subvol = libcalamares.job.configuration.get("btrfsSwapSubvol", "/@swap")
|
||||
for s in btrfs_subvolumes:
|
||||
if s['subvolume'] == swap_subvol:
|
||||
mount_option_no_subvol = get_mount_options("btrfs_swap", mount_options, partition)
|
||||
else:
|
||||
mount_option_no_subvol = get_mount_options(fstype, mount_options, partition)
|
||||
|
||||
# Only add subvol= argument if we are not mounting the entire filesystem
|
||||
if s['subvolume']:
|
||||
mount_option = f"subvol={s['subvolume']},{mount_option_no_subvol}"
|
||||
else:
|
||||
mount_option = mount_option_no_subvol
|
||||
subvolume_mountpoint = mount_point[:-1] + s['mountPoint']
|
||||
mount_options_list.append({"mountpoint": s['mountPoint'], "option_string": mount_option_no_subvol})
|
||||
if libcalamares.utils.mount(device,
|
||||
subvolume_mountpoint,
|
||||
fstype,
|
||||
mount_option) != 0:
|
||||
libcalamares.utils.warning("Cannot mount {}".format(device))
|
||||
|
||||
|
||||
def enable_swap_partition(devices):
|
||||
try:
|
||||
for d in devices:
|
||||
libcalamares.utils.host_env_process_output(["swapon", d])
|
||||
except subprocess.CalledProcessError:
|
||||
libcalamares.utils.warning(f"Failed to enable swap for devices: {devices}")
|
||||
|
||||
|
||||
def run():
|
||||
"""
|
||||
Mount all the partitions from GlobalStorage and from the job configuration.
|
||||
Partitions are mounted in-lexical-order of their mountPoint.
|
||||
"""
|
||||
|
||||
partitions = libcalamares.globalstorage.value("partitions")
|
||||
|
||||
if not partitions:
|
||||
libcalamares.utils.warning("partitions is empty, {!s}".format(partitions))
|
||||
return (_("Configuration Error"),
|
||||
_("No partitions are defined for <pre>{!s}</pre> to use.").format("mount"))
|
||||
|
||||
# Find existing swap partitions that are part of the installation and enable them now
|
||||
claimed_swap_partitions = [p for p in partitions if p["fs"] == "linuxswap" and p.get("claimed", False)]
|
||||
plain_swap = [p for p in claimed_swap_partitions if p["fsName"] == "linuxswap"]
|
||||
luks_swap = [p for p in claimed_swap_partitions if p["fsName"] == "luks" or p["fsName"] == "luks2"]
|
||||
swap_devices = [p["device"] for p in plain_swap] + ["/dev/mapper/" + p["luksMapperName"] for p in luks_swap]
|
||||
|
||||
enable_swap_partition(swap_devices)
|
||||
|
||||
root_mount_point = tempfile.mkdtemp(prefix="calamares-root-")
|
||||
|
||||
# Get the mountOptions, if this is None, that is OK and will be handled later
|
||||
mount_options = libcalamares.job.configuration.get("mountOptions")
|
||||
|
||||
# Guard against missing keys (generally a sign that the config file is bad)
|
||||
extra_mounts = libcalamares.job.configuration.get("extraMounts") or []
|
||||
if not extra_mounts:
|
||||
libcalamares.utils.warning("No extra mounts defined. Does mount.conf exist?")
|
||||
|
||||
efi_location = None
|
||||
if libcalamares.globalstorage.value("firmwareType") == "efi":
|
||||
efi_location = libcalamares.globalstorage.value("efiSystemPartition")
|
||||
else:
|
||||
for mount in extra_mounts:
|
||||
if mount.get("efi", None) is True:
|
||||
extra_mounts.remove(mount)
|
||||
|
||||
# Add extra mounts to the partitions list and sort by mount points.
|
||||
# This way, we ensure / is mounted before the rest, and every mount point
|
||||
# is created on the right partition (e.g. if a partition is to be mounted
|
||||
# under /tmp, we make sure /tmp is mounted before the partition)
|
||||
mountable_partitions = [p for p in partitions + extra_mounts if "mountPoint" in p and p["mountPoint"]]
|
||||
mountable_partitions.sort(key=lambda x: x["mountPoint"])
|
||||
|
||||
# mount_options_list will be inserted into global storage for use in fstab later
|
||||
mount_options_list = []
|
||||
try:
|
||||
for partition in mountable_partitions:
|
||||
mount_partition(root_mount_point, partition, partitions, mount_options, mount_options_list, efi_location)
|
||||
except ZfsException as ze:
|
||||
return _("zfs mounting error"), ze.message
|
||||
|
||||
if not mount_options_list:
|
||||
libcalamares.utils.warning("No mount options defined, {!s} partitions, {!s} mountable".format(len(partitions), len(mountable_partitions)))
|
||||
|
||||
libcalamares.globalstorage.insert("rootMountPoint", root_mount_point)
|
||||
libcalamares.globalstorage.insert("mountOptionsList", mount_options_list)
|
||||
|
||||
# Remember the extra mounts for the unpackfs module
|
||||
libcalamares.globalstorage.insert("extraMounts", extra_mounts)
|
||||
7
airootfs/usr/lib/calamares/modules/mount/module.desc
Normal file
7
airootfs/usr/lib/calamares/modules/mount/module.desc
Normal file
@@ -0,0 +1,7 @@
|
||||
# SPDX-FileCopyrightText: no
|
||||
# SPDX-License-Identifier: CC0-1.0
|
||||
---
|
||||
type: "job"
|
||||
name: "mount"
|
||||
interface: "python"
|
||||
script: "main.py"
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user