Initial upload of HyprArch releng configuration

This commit is contained in:
2026-03-03 20:31:33 +00:00
commit 7df61351c0
634 changed files with 36355 additions and 0 deletions

View File

@@ -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 }

View 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

View 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" ]

View 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"

View File

@@ -0,0 +1,7 @@
# AUTO-GENERATED metadata file
# Syntax is YAML 1.2
---
type: "job"
name: "contextualprocess"
interface: "qtplugin"
load: "libcalamares_job_contextualprocess.so"

View File

@@ -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

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,7 @@
# SPDX-FileCopyrightText: no
# SPDX-License-Identifier: CC0-1.0
---
type: "job"
name: "displaymanager"
interface: "python"
script: "main.py"

View 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"

View 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"

View File

@@ -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
)

View File

@@ -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 >(); )

View File

@@ -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

View File

@@ -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();
}

View 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 );
}

View File

@@ -0,0 +1,8 @@
# SPDX-FileCopyrightText: no
# SPDX-License-Identifier: CC0-1.0
#
# The flatpakinfo module will collect package list from configured flatpak repositories
#
#
#
---

View File

@@ -0,0 +1,7 @@
# SPDX-FileCopyrightText: no
# SPDX-License-Identifier: CC0-1.0
---
type: "job"
name: "flatpakinfo"
interface: "qtplugin"
load: "libcalamares_job_flatpakInfo.so"

View File

@@ -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
)

View File

@@ -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 >(); )

View File

@@ -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

View File

@@ -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

View File

@@ -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."
}
}

View File

@@ -0,0 +1,5 @@
<!DOCTYPE RCC><RCC version="1.0">
<qresource>
<file alias="freebsddisk.qml">freebsddisk.qml</file>
</qresource>
</RCC>

View 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"

View 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

View 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)

View File

@@ -0,0 +1,7 @@
# SPDX-FileCopyrightText: no
# SPDX-License-Identifier: CC0-1.0
---
type: "job"
name: "fstab"
interface: "python"
script: "main.py"

View 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

View 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"

View File

@@ -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 }

View 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)

View File

@@ -0,0 +1,7 @@
# SPDX-FileCopyrightText: no
# SPDX-License-Identifier: CC0-1.0
---
type: "job"
name: "grubcfg"
interface: "python"
script: "main.py"

View 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

View 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

View 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

View 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

View 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

View 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

View 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"

View File

@@ -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 }

View 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

View 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"

View File

@@ -0,0 +1,7 @@
# SPDX-FileCopyrightText: no
# SPDX-License-Identifier: CC0-1.0
rootMountPoint: /tmp/mount
partitions:
- fs: ext4
mountPoint: "/"

View 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

View 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

View 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

View File

@@ -0,0 +1,8 @@
# SPDX-FileCopyrightText: no
# SPDX-License-Identifier: CC0-1.0
---
type: "job"
name: "initramfs"
interface: "python"
script: "main.py"

View 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"

View 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"

View 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"

View 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"

View 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

View 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

View 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"

View File

@@ -0,0 +1,7 @@
# AUTO-GENERATED metadata file
# Syntax is YAML 1.2
---
type: "job"
name: "luksbootkeyfile"
interface: "qtplugin"
load: "libcalamares_job_luksbootkeyfile.so"

View File

@@ -0,0 +1,7 @@
# AUTO-GENERATED metadata file
# Syntax is YAML 1.2
---
type: "job"
name: "luksopenswaphookcfg"
interface: "qtplugin"
load: "libcalamares_job_luksopenswaphookcfg.so"

View 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"

View 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) )

View File

@@ -0,0 +1,7 @@
# SPDX-FileCopyrightText: no
# SPDX-License-Identifier: CC0-1.0
---
type: "job"
name: "mkinitfs"
interface: "python"
script: "main.py"

View 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
)

View 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 );
}

View 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 );
};

View File

@@ -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;
}

View File

@@ -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

View 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();
}

View 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;
};

View 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();
}

View 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;
};

View 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();
}
}
}

View 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();
}
}
}
}

View 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();
}
}
}

View File

@@ -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()
}
}

View 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();
}
}
}

View File

@@ -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();
}
}
}

View 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"

View 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);
}
}

View 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>

View 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();
}
}
}

View 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();
}
}
}
}

View 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();
}
}
}
}

View 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
}
}
}

View 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()
}
}
}
}

View 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)

View 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