diff --git a/README.md b/README.md index 43a336de1..8037612aa 100644 --- a/README.md +++ b/README.md @@ -17,16 +17,16 @@ > Calamares is a distribution-independent system installer, with an advanced partitioning > feature for both manual and automated partitioning operations. Calamares is designed to -> be customizable by distribution maintainers without need for cumbersome patching, -> thanks to third party branding and external modules support. +> be customizable by distribution maintainers without the need for cumbersome patching, +> thanks to third-party branding and external modules support. ## Target Audience Calamares is a Linux installer; users who install Linux on a computer will hopefully use it just **once**, to install their Linux distribution. Calamares is not -a "ready to use" application: distributions apply a huge amount of customisation +a "ready to use" application: distributions apply a huge amount of customization and configuration to Calamares, and the target audience for this repository -is those distributions, and the people who make those Linux distro's. +is those distributions, and the people who make those Linux distros. Calamares has some [generic user documentation](https://calamares.io/docs/users-guide/) for end-users, but most of what we have is for distro developers. @@ -45,10 +45,10 @@ The dependencies are explained in [CONTRIBUTING.md](CONTRIBUTING.md). ## Contributing to Calamares Calamares welcomes PRs. New issues are welcome, too. -There are both the Calamares **core** repository (this one), +There are both the Calamares **core** repository (this one) and an **extensions** repository ([Calamares extensions](https://github.com/calamares/calamares-extensions)). -Contributions to code, modules, documentation, the wiki and the website are all welcome. +Contributions to code, modules, documentation, the wiki, and the website are all welcome. There is more information in the [CONTRIBUTING.md](CONTRIBUTING.md) file. ## Join the Conversation diff --git a/src/modules/displaymanager/main.py b/src/modules/displaymanager/main.py index d7533e122..2a1448e51 100644 --- a/src/modules/displaymanager/main.py +++ b/src/modules/displaymanager/main.py @@ -197,6 +197,8 @@ desktop_environments = [ DesktopEnvironment('/usr/bin/sway', 'sway'), DesktopEnvironment('/usr/bin/ukui-session', 'ukui'), DesktopEnvironment('/usr/bin/cutefish-session', 'cutefish-xsession'), + DesktopEnvironment('/usr/bin/river', 'river'), + DesktopEnvironment('/usr/bin/Hyprland', 'hyprland'), ] diff --git a/src/modules/initcpiocfg/main.py b/src/modules/initcpiocfg/main.py index 5fd9d93ae..57dc5e432 100644 --- a/src/modules/initcpiocfg/main.py +++ b/src/modules/initcpiocfg/main.py @@ -54,8 +54,9 @@ class cpuinfo(object): self.number_of_cores = 0 cpu = self._cpuinfo() - self.is_intel = cpu['proc0']['vendor_id'].lower() == "genuineintel" - self.is_amd = cpu['proc0']['vendor_id'].lower() == "authenticamd" + if 'vendor_id' in cpu['proc0']: + self.is_intel = cpu['proc0']['vendor_id'].lower() == "genuineintel" + self.is_amd = cpu['proc0']['vendor_id'].lower() == "authenticamd" self.number_of_cores = len(cpu) @staticmethod diff --git a/src/modules/luksbootkeyfile/CMakeLists.txt b/src/modules/luksbootkeyfile/CMakeLists.txt index a2b52ad94..735d3174f 100644 --- a/src/modules/luksbootkeyfile/CMakeLists.txt +++ b/src/modules/luksbootkeyfile/CMakeLists.txt @@ -9,7 +9,6 @@ calamares_add_plugin(luksbootkeyfile SOURCES LuksBootKeyFileJob.cpp SHARED_LIB - NO_CONFIG ) calamares_add_test(luksbootkeyfiletest SOURCES Tests.cpp LuksBootKeyFileJob.cpp) diff --git a/src/modules/luksbootkeyfile/LuksBootKeyFileJob.cpp b/src/modules/luksbootkeyfile/LuksBootKeyFileJob.cpp index 5b0914595..c9c0e1298 100644 --- a/src/modules/luksbootkeyfile/LuksBootKeyFileJob.cpp +++ b/src/modules/luksbootkeyfile/LuksBootKeyFileJob.cpp @@ -135,34 +135,64 @@ generateTargetKeyfile() } static bool -setupLuks( const LuksDevice& d ) +setupLuks( const LuksDevice& d, const QString& luks2Hash ) { - // Sometimes this error is thrown: "All key slots full" - // luksAddKey will fail. So, remove the first slot to make room + // Get luksDump for this device auto luks_dump = CalamaresUtils::System::instance()->targetEnvCommand( - { "cryptsetup", "luksDump", d.device }, QString(), QString(), std::chrono::seconds( 5 ) ); - if ( luks_dump.getExitCode() == 0 ) + { QStringLiteral( "cryptsetup" ), QStringLiteral( "luksDump" ), d.device }, + QString(), + QString(), + std::chrono::seconds( 5 ) ); + if ( luks_dump.getExitCode() != 0 ) { - QRegularExpression re( QStringLiteral( R"(\d+:\s*enabled)" ), QRegularExpression::CaseInsensitiveOption ); - int count = luks_dump.getOutput().count( re ); - cDebug() << "Luks Dump slot count: " << count; - if ( count >= 7 ) + cWarning() << "Could not get LUKS information on " << d.device << ':' << luks_dump.getOutput() << "(exit code" + << luks_dump.getExitCode() << ')'; + return false; + } + + // Check LUKS version + int luks_version = 0; + QRegularExpression version_re( QStringLiteral( R"(version:\s*([0-9]))" ), + QRegularExpression::CaseInsensitiveOption ); + QRegularExpressionMatch match = version_re.match( luks_dump.getOutput() ); + if ( ! match.hasMatch() ) + { + cWarning() << "Could not get LUKS version on device: " << d.device; + return false; + } + bool ok; + luks_version = match.captured(1).toInt(&ok); + if( ! ok ) + { + cWarning() << "Could not get LUKS version on device: " << d.device; + return false; + } + cDebug() << "LUKS" << luks_version << " found on device: " << d.device; + + // Check the number of slots used for LUKS1 devices + if ( luks_version == 1 ) + { + QRegularExpression slots_re( QStringLiteral( R"(\d+:\s*enabled)" ), + QRegularExpression::CaseInsensitiveOption ); + if ( luks_dump.getOutput().count( slots_re ) == 8 ) { - auto r = CalamaresUtils::System::instance()->targetEnvCommand( - { "cryptsetup", "luksKillSlot", d.device, "1" }, QString(), d.passphrase, std::chrono::seconds( 60 ) ); - if ( r.getExitCode() != 0 ) - { - cWarning() << "Could not kill a slot to make room on" << d.device << ':' << r.getOutput() - << "(exit code" << r.getExitCode() << ')'; - return false; - } + cWarning() << "No key slots left on LUKS1 device: " << d.device; + return false; } } - // Adding the key can take some times, measured around 15 seconds with - // a HDD (spinning rust) and a slow-ish computer. Give it a minute. + // Add the key to the keyfile + QStringList args = { QStringLiteral( "cryptsetup" ), QStringLiteral( "luksAddKey" ), d.device, keyfile }; + if ( luks_version == 2 && luks2Hash != QString() ) + { + args.insert(2, "--pbkdf"); + args.insert(3, luks2Hash); + } auto r = CalamaresUtils::System::instance()->targetEnvCommand( - { "cryptsetup", "luksAddKey", d.device, keyfile }, QString(), d.passphrase, std::chrono::seconds( 60 ) ); + args, + QString(), + d.passphrase, + std::chrono::seconds( 60 ) ); if ( r.getExitCode() != 0 ) { cWarning() << "Could not configure LUKS keyfile on" << d.device << ':' << r.getOutput() << "(exit code" @@ -259,15 +289,15 @@ LuksBootKeyFileJob::exec() if ( it == s.devices.begin() ) { - // Then there was no root partition - cDebug() << Logger::SubEntry << "No root partition."; + // User has configured non-root partition for encryption + cDebug() << Logger::SubEntry << "No root partition, skipping keyfile creation."; return Calamares::JobResult::ok(); } - // /boot partition is not encrypted, keyfile must not be used - // But only if root partition is not encrypted if ( hasUnencryptedSeparateBoot() && !hasEncryptedRoot() ) { + // /boot partition is not encrypted, keyfile must not be used + // But only if root partition is not encrypted cDebug() << Logger::SubEntry << "/boot partition is not encrypted, skipping keyfile creation."; return Calamares::JobResult::ok(); } @@ -295,13 +325,22 @@ LuksBootKeyFileJob::exec() continue; } - if ( !setupLuks( d ) ) - return Calamares::JobResult::error( - tr( "Encrypted rootfs setup error" ), - tr( "Could not configure LUKS key file on partition %1." ).arg( d.device ) ); + if ( !setupLuks( d, m_luks2Hash ) ) + { + // Could not configure the LUKS partition + // This should not stop the installation: do not return Calamares::JobResult::error. + cError() << "Encrypted rootfs setup error: could not configure LUKS key file on partition " << d.device; + } } return Calamares::JobResult::ok(); } +void +LuksBootKeyFileJob::setConfigurationMap( const QVariantMap& configurationMap ) +{ + m_luks2Hash = CalamaresUtils::getString( + configurationMap, QStringLiteral( "luks2Hash" ), QString() ); +} + CALAMARES_PLUGIN_FACTORY_DEFINITION( LuksBootKeyFileJobFactory, registerPlugin< LuksBootKeyFileJob >(); ) diff --git a/src/modules/luksbootkeyfile/LuksBootKeyFileJob.h b/src/modules/luksbootkeyfile/LuksBootKeyFileJob.h index 9681228bd..05288a152 100644 --- a/src/modules/luksbootkeyfile/LuksBootKeyFileJob.h +++ b/src/modules/luksbootkeyfile/LuksBootKeyFileJob.h @@ -30,6 +30,11 @@ public: QString prettyName() const override; Calamares::JobResult exec() override; + + void setConfigurationMap( const QVariantMap& configurationMap ) override; + +private: + QString m_luks2Hash; }; CALAMARES_PLUGIN_FACTORY_DECLARATION( LuksBootKeyFileJobFactory ) diff --git a/src/modules/luksbootkeyfile/luksbootkeyfile.conf b/src/modules/luksbootkeyfile/luksbootkeyfile.conf new file mode 100644 index 000000000..04ffbac6b --- /dev/null +++ b/src/modules/luksbootkeyfile/luksbootkeyfile.conf @@ -0,0 +1,13 @@ +# SPDX-FileCopyrightText: no +# SPDX-License-Identifier: CC0-1.0 +# +# Luksbootkeyfile configuration. A key file is created for the +# LUKS encrypted devices. +--- +# Set Password-Based Key Derivation Function (PBKDF) algorithm +# for LUKS keyslot. +# +# There are three usable values: pbkdf2, argon2i or argon2id. +# +# When not set, the cryptsetup default is used +#luks2Hash: argon2id diff --git a/src/modules/luksbootkeyfile/luksbootkeyfile.schema.yaml b/src/modules/luksbootkeyfile/luksbootkeyfile.schema.yaml new file mode 100644 index 000000000..6c0917646 --- /dev/null +++ b/src/modules/luksbootkeyfile/luksbootkeyfile.schema.yaml @@ -0,0 +1,9 @@ +# SPDX-FileCopyrightText: 2023 Arjen Balfoort +# SPDX-License-Identifier: GPL-3.0-or-later +--- +$schema: https://json-schema.org/schema# +$id: https://calamares.io/schemas/luksbootkeyfile +additionalProperties: false +type: object +properties: + luks2Hash: { type: string, enum: [ pbkdf2, argon2i, argon2id ] } diff --git a/src/modules/packages/main.py b/src/modules/packages/main.py index e373a3443..26ed30a19 100644 --- a/src/modules/packages/main.py +++ b/src/modules/packages/main.py @@ -282,12 +282,12 @@ class PMDnf(PackageManager): backend = "dnf" def install(self, pkgs, from_local=False): - check_target_env_call(["dnf", "-y", "install"] + pkgs) + check_target_env_call(["dnf-3", "-y", "install"] + pkgs) def remove(self, pkgs): # ignore the error code for now because dnf thinks removing a # nonexistent package is an error - target_env_call(["dnf", "--disablerepo=*", "-C", "-y", + target_env_call(["dnf-3", "--disablerepo=*", "-C", "-y", "remove"] + pkgs) def update_db(self): @@ -295,7 +295,7 @@ class PMDnf(PackageManager): pass def update_system(self): - check_target_env_call(["dnf", "-y", "upgrade"]) + check_target_env_call(["dnf-3", "-y", "upgrade"]) class PMDummy(PackageManager): diff --git a/src/modules/partition/Config.cpp b/src/modules/partition/Config.cpp index 4519bd278..0a0514c72 100644 --- a/src/modules/partition/Config.cpp +++ b/src/modules/partition/Config.cpp @@ -415,6 +415,7 @@ Config::setConfigurationMap( const QVariantMap& configurationMap ) m_requiredPartitionTableType = CalamaresUtils::getStringList( configurationMap, "requiredPartitionTableType" ); Calamares::GlobalStorage* gs = Calamares::JobQueue::instance()->globalStorage(); + gs->insert( "armInstall", CalamaresUtils::getBool( configurationMap, "armInstall", false ) ); fillGSConfigurationEFI( gs, configurationMap ); fillConfigurationFSTypes( configurationMap ); } diff --git a/src/modules/partition/core/PartUtils.cpp b/src/modules/partition/core/PartUtils.cpp index 3d5e1e762..60f966f27 100644 --- a/src/modules/partition/core/PartUtils.cpp +++ b/src/modules/partition/core/PartUtils.cpp @@ -443,7 +443,15 @@ runOsprober( DeviceModel* dm ) bool isEfiSystem() { - return QDir( "/sys/firmware/efi/efivars" ).exists(); + Calamares::GlobalStorage* gs = Calamares::JobQueue::instance()->globalStorage(); + if ( gs->contains( "armInstall" ) && gs->value( "armInstall" ).toBool() ) + { + return true; + } + else + { + return QDir( "/sys/firmware/efi/efivars" ).exists(); + } } bool diff --git a/src/modules/partition/core/PartitionActions.cpp b/src/modules/partition/core/PartitionActions.cpp index 2287c57bd..631d4ca64 100644 --- a/src/modules/partition/core/PartitionActions.cpp +++ b/src/modules/partition/core/PartitionActions.cpp @@ -95,8 +95,16 @@ doAutopartition( PartitionCoreModule* core, Device* dev, Choices::AutoPartitionO // the logical sector size (usually 512B). EFI starts with 2MiB // empty and a EFI boot partition, while BIOS starts at // the 1MiB boundary (usually sector 2048). - int empty_space_sizeB = isEfi ? 2_MiB : 1_MiB; - + // ARM empty sectors are 16 MiB in size. + int empty_space_sizeB; + if ( gs->contains( "armInstall" ) && gs->value( "armInstall" ).toBool() ) + { + empty_space_sizeB = 16_MiB; + } + else + { + empty_space_sizeB = isEfi ? 2_MiB : 1_MiB; + } // Since sectors count from 0, if the space is 2048 sectors in size, // the first free sector has number 2048 (and there are 2048 sectors // before that one, numbered 0..2047). diff --git a/src/modules/partition/partition.conf b/src/modules/partition/partition.conf index ecd183ca1..b6364d3c0 100644 --- a/src/modules/partition/partition.conf +++ b/src/modules/partition/partition.conf @@ -124,6 +124,13 @@ initialPartitioningChoice: none # one of the items from the options. initialSwapChoice: none +# armInstall +# +# Leaves 16MB empty at the start of a drive when partitioning +# where usually the u-boot loader goes +# +# armInstall: false + # Default partition table type, used when a "erase" disk is made. # # When erasing a disk, a new partition table is created on disk. diff --git a/src/modules/partition/partition.schema.yaml b/src/modules/partition/partition.schema.yaml index dafdc5851..50a51cd9e 100644 --- a/src/modules/partition/partition.schema.yaml +++ b/src/modules/partition/partition.schema.yaml @@ -14,6 +14,7 @@ properties: userSwapChoices: { type: array, items: { type: string, enum: [ none, reuse, small, suspend, file ] } } # ensureSuspendToDisk: { type: boolean, default: true } # Legacy # neverCreateSwap: { type: boolean, default: false } # Legacy + armInstall: { type: boolean, default: false } allowZfsEncryption: { type: boolean, default: true } drawNestedPartitions: { type: boolean, default: false } diff --git a/src/modules/services-systemd/services-systemd.conf b/src/modules/services-systemd/services-systemd.conf index 6e7f34451..330a94c65 100644 --- a/src/modules/services-systemd/services-systemd.conf +++ b/src/modules/services-systemd/services-systemd.conf @@ -31,7 +31,7 @@ # # finally masks pacman-init (an ArchLinux-only service). # # # units: -# - name: "NetworkManager" +# - name: "NetworkManager.service" # action: "enable" # mandatory: true # @@ -40,12 +40,12 @@ # # The property "mandatory" is taken to be false by default here # # because it is not specified # -# - name: "graphical" +# - name: "graphical.target" # action: "enable" # # The property "mandatory" is taken to be false by default here # # because it is not specified # -# - name: "pacman-init" +# - name: "pacman-init.service" # action: "mask" # # The property "mandatory" is taken to be false by default here # # because it is not specified