From e5f5ef0d17ca5191b5014781942eb747dbff012d Mon Sep 17 00:00:00 2001 From: Jonas Strassel Date: Wed, 27 Oct 2021 23:08:18 +0200 Subject: [PATCH 001/127] feat(greetd): add greetd to displaymanagers --- .../displaymanager/displaymanager.conf | 1 + .../displaymanager/displaymanager.schema.yaml | 2 +- src/modules/displaymanager/main.py | 70 ++++++++++++++++++- 3 files changed, 71 insertions(+), 2 deletions(-) diff --git a/src/modules/displaymanager/displaymanager.conf b/src/modules/displaymanager/displaymanager.conf index 7175c112d..0fd2af163 100644 --- a/src/modules/displaymanager/displaymanager.conf +++ b/src/modules/displaymanager/displaymanager.conf @@ -23,6 +23,7 @@ displaymanagers: - mdm - lxdm - kdm + - greetd # Enable the following settings to force a desktop environment # in your displaymanager configuration file. This will attempt diff --git a/src/modules/displaymanager/displaymanager.schema.yaml b/src/modules/displaymanager/displaymanager.schema.yaml index fc28fd66d..89d657a3b 100644 --- a/src/modules/displaymanager/displaymanager.schema.yaml +++ b/src/modules/displaymanager/displaymanager.schema.yaml @@ -10,7 +10,7 @@ properties: type: array items: type: string - enum: [slim, sddm, lightdm, gdm, mdm, lxdm, kdm] + enum: [slim, sddm, lightdm, gdm, mdm, lxdm, kdm, greetd] minItems: 1 # Must be non-empty, if present at all defaultDesktopEnvironment: type: object diff --git a/src/modules/displaymanager/main.py b/src/modules/displaymanager/main.py index 5fb228682..1fd6a26ca 100644 --- a/src/modules/displaymanager/main.py +++ b/src/modules/displaymanager/main.py @@ -17,7 +17,7 @@ import abc import os -import re +import toml import libcalamares import configparser @@ -835,6 +835,74 @@ class DMsddm(DisplayManager): pass +class DMgreetd(DisplayManager): + name = "greetd" + executable = "greetd" + config_data = {} + + def os_path(self, path): + return os.path.join(self.root_mount_point, path) + + def config_path(self): + return self.os_path("etc/greetd/config.toml") + + def environments_path(self): + return self.os_path("etc/greetd/environments") + + def config_load(self): + self.config_data = toml.loads(self.config_path()) + + def config_write(self): + toml.dump(self.config_data, self.config_path()) + + def basic_setup(self): + if libcalamares.utils.target_env_call( + ['getent', 'group', 'greetd'] + ) != 0: + libcalamares.utils.target_env_call( + ['groupadd', 'greetd'] + ) + + if libcalamares.utils.target_env_call( + ['getent', 'passwd', 'greeter'] + ) != 0: + libcalamares.utils.target_env_call( + ['useradd', + '-c', '"Greeter User"', + '-g', 'greetd', + '-s', '/bin/bash', + 'greeter' + ] + ) + self.config_load() + self.config_data['terminal']['vt'] = "next" + self.config_write() + + def desktop_environment_setup(self, default_desktop_environment): + with open(self.environments_path(), 'w') as envs_file: + envs_file.write(default_desktop_environment) + + def greeter_setup(self): + pass + + def set_autologin(self, username, do_autologin, default_desktop_environment): + self.config_load() + + if (os.path.exists(self.os_path("usr/bin/tuigreet"))): + tuigreet_base_cmd = "tuigreet --remember --time --issue --asterisks --cmd " + self.config_data['default_session']['command'] = tuigreet_base_cmd + default_desktop_environment + else: + print("no greeter detected") + + if (do_autologin == True): + self.config_data['initial_session'] = {} + self.config_data['initial_session']['command'] = default_desktop_environment + self.config_data['initial_session']['user'] = username + + self.config_write() + + + class DMsysconfig(DisplayManager): name = "sysconfig" executable = None From fbdb9e6779ecb3dff0d29e22d3c9e945fc25f41c Mon Sep 17 00:00:00 2001 From: Jonas Strassel Date: Thu, 28 Oct 2021 08:14:04 +0200 Subject: [PATCH 002/127] feat(greetd): add more greeter fallbacks --- src/modules/displaymanager/main.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/modules/displaymanager/main.py b/src/modules/displaymanager/main.py index 1fd6a26ca..cf918ce5c 100644 --- a/src/modules/displaymanager/main.py +++ b/src/modules/displaymanager/main.py @@ -844,13 +844,17 @@ class DMgreetd(DisplayManager): return os.path.join(self.root_mount_point, path) def config_path(self): - return self.os_path("etc/greetd/config.toml") + path = self.os_path("etc/greetd/config.toml") + if not os.path.exists(path): + open(path, 'a').close() + return path def environments_path(self): return self.os_path("etc/greetd/environments") def config_load(self): self.config_data = toml.loads(self.config_path()) + return self.config_data def config_write(self): toml.dump(self.config_data, self.config_path()) @@ -888,13 +892,17 @@ class DMgreetd(DisplayManager): def set_autologin(self, username, do_autologin, default_desktop_environment): self.config_load() - if (os.path.exists(self.os_path("usr/bin/tuigreet"))): + if os.path.exists(self.os_path("usr/bin/gtkgreed") and os.path.exists(self.os_path("usr/bin/cage")): + self.config_data['default_session']['command'] = "cage gtkgreet" + elif os.path.exists(self.os_path("usr/bin/tuigreet")): tuigreet_base_cmd = "tuigreet --remember --time --issue --asterisks --cmd " self.config_data['default_session']['command'] = tuigreet_base_cmd + default_desktop_environment + elif os.path.exists(self.os_path("usr/bin/ddlm")): + self.config_data['default_session']['command'] = "ddlm --target sway" else: - print("no greeter detected") + self.config_data['default_session']['command'] = "agreety --cmd " + default_desktop_environment - if (do_autologin == True): + if do_autologin == True: self.config_data['initial_session'] = {} self.config_data['initial_session']['command'] = default_desktop_environment self.config_data['initial_session']['user'] = username From 57019378838fcf570ee85a2acf23e48f0577dfad Mon Sep 17 00:00:00 2001 From: Jonas Strassel Date: Thu, 28 Oct 2021 09:06:35 +0200 Subject: [PATCH 003/127] fix(greetd): deal with no existing config --- src/modules/displaymanager/main.py | 42 ++++++++++++++++-------------- 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/src/modules/displaymanager/main.py b/src/modules/displaymanager/main.py index cf918ce5c..2fc4bead1 100644 --- a/src/modules/displaymanager/main.py +++ b/src/modules/displaymanager/main.py @@ -838,22 +838,30 @@ class DMsddm(DisplayManager): class DMgreetd(DisplayManager): name = "greetd" executable = "greetd" + greeter_user = "greeter" + greeter_group = "greetd" config_data = {} def os_path(self, path): return os.path.join(self.root_mount_point, path) def config_path(self): - path = self.os_path("etc/greetd/config.toml") - if not os.path.exists(path): - open(path, 'a').close() - return path + return self.os_path("etc/greetd/config.toml") def environments_path(self): return self.os_path("etc/greetd/environments") def config_load(self): - self.config_data = toml.loads(self.config_path()) + if (os.path.exists(self.config_path)): + self.config_data = toml.loads(self.config_path()) + + self.config_data['terminal'] = dict(vt = "next") + + if not os.path.exists(self.config_data['default_session']): + self.config_data['default_session'] = {} + + self.config_data['default_session']['user'] = self.greeter_user + return self.config_data def config_write(self): @@ -861,26 +869,23 @@ class DMgreetd(DisplayManager): def basic_setup(self): if libcalamares.utils.target_env_call( - ['getent', 'group', 'greetd'] + ['getent', 'group', self.greeter_group] ) != 0: libcalamares.utils.target_env_call( - ['groupadd', 'greetd'] + ['groupadd', self.greeter_group] ) if libcalamares.utils.target_env_call( - ['getent', 'passwd', 'greeter'] + ['getent', 'passwd', self.greeter_user] ) != 0: libcalamares.utils.target_env_call( ['useradd', '-c', '"Greeter User"', - '-g', 'greetd', + '-g', self.greeter_group, '-s', '/bin/bash', - 'greeter' + self.greeter_user ] ) - self.config_load() - self.config_data['terminal']['vt'] = "next" - self.config_write() def desktop_environment_setup(self, default_desktop_environment): with open(self.environments_path(), 'w') as envs_file: @@ -892,25 +897,22 @@ class DMgreetd(DisplayManager): def set_autologin(self, username, do_autologin, default_desktop_environment): self.config_load() - if os.path.exists(self.os_path("usr/bin/gtkgreed") and os.path.exists(self.os_path("usr/bin/cage")): - self.config_data['default_session']['command'] = "cage gtkgreet" + if os.path.exists(self.os_path("usr/bin/gtkgreed")) and os.path.exists(self.os_path("usr/bin/cage")): + self.config_data['default_session']['command'] = "cage -s -- gtkgreet" elif os.path.exists(self.os_path("usr/bin/tuigreet")): tuigreet_base_cmd = "tuigreet --remember --time --issue --asterisks --cmd " self.config_data['default_session']['command'] = tuigreet_base_cmd + default_desktop_environment elif os.path.exists(self.os_path("usr/bin/ddlm")): - self.config_data['default_session']['command'] = "ddlm --target sway" + self.config_data['default_session']['command'] = "ddlm --target " + default_desktop_environment else: self.config_data['default_session']['command'] = "agreety --cmd " + default_desktop_environment if do_autologin == True: - self.config_data['initial_session'] = {} - self.config_data['initial_session']['command'] = default_desktop_environment - self.config_data['initial_session']['user'] = username + self.config_data['initial_session'] = dict(command = default_desktop_environment, user = username) self.config_write() - class DMsysconfig(DisplayManager): name = "sysconfig" executable = None From ac44aab74a4f646515f5ed7ad89e0655fc827b8f Mon Sep 17 00:00:00 2001 From: dalto Date: Sat, 6 Nov 2021 09:16:09 -0500 Subject: [PATCH 004/127] [partition] Add zfs to the filesystem list if the zfs modules is enabled --- src/modules/partition/gui/CreatePartitionDialog.cpp | 5 ++++- src/modules/partition/gui/EditExistingPartitionDialog.cpp | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/modules/partition/gui/CreatePartitionDialog.cpp b/src/modules/partition/gui/CreatePartitionDialog.cpp index cdc9992b9..0727ee935 100644 --- a/src/modules/partition/gui/CreatePartitionDialog.cpp +++ b/src/modules/partition/gui/CreatePartitionDialog.cpp @@ -23,6 +23,7 @@ #include "GlobalStorage.h" #include "JobQueue.h" +#include "Settings.h" #include "partition/FileSystem.h" #include "partition/PartitionQuery.h" #include "utils/Logger.h" @@ -104,7 +105,9 @@ CreatePartitionDialog::CreatePartitionDialog( Device* device, QStringList fsNames; for ( auto fs : FileSystemFactory::map() ) { - if ( fs->supportCreate() != FileSystem::cmdSupportNone && fs->type() != FileSystem::Extended ) + // We need to ensure zfs is added to the list if the zfs module is enabled + if ( ( fs->type() == FileSystem::Type::Zfs && Calamares::Settings::instance()->isModuleEnabled( "zfs" ) ) + || ( fs->supportCreate() != FileSystem::cmdSupportNone && fs->type() != FileSystem::Extended ) ) { fsNames << userVisibleFS( fs ); // This is put into the combobox if ( fs->type() == defaultFSType ) diff --git a/src/modules/partition/gui/EditExistingPartitionDialog.cpp b/src/modules/partition/gui/EditExistingPartitionDialog.cpp index 411d6d0dc..04b9527f1 100644 --- a/src/modules/partition/gui/EditExistingPartitionDialog.cpp +++ b/src/modules/partition/gui/EditExistingPartitionDialog.cpp @@ -25,6 +25,7 @@ #include "GlobalStorage.h" #include "JobQueue.h" +#include "Settings.h" #include "partition/FileSystem.h" #include "utils/Logger.h" @@ -89,7 +90,9 @@ EditExistingPartitionDialog::EditExistingPartitionDialog( Device* device, QStringList fsNames; for ( auto fs : FileSystemFactory::map() ) { - if ( fs->supportCreate() != FileSystem::cmdSupportNone && fs->type() != FileSystem::Extended ) + // We need to ensure zfs is added to the list if the zfs module is enabled + if ( ( fs->type() == FileSystem::Type::Zfs && Calamares::Settings::instance()->isModuleEnabled( "zfs" ) ) + || ( fs->supportCreate() != FileSystem::cmdSupportNone && fs->type() != FileSystem::Extended ) ) { fsNames << userVisibleFS( fs ); // For the combo box } From 7faf4f30dfd71bd1939c0ff1bd2afb1f502e7338 Mon Sep 17 00:00:00 2001 From: dalto Date: Sat, 6 Nov 2021 09:42:07 -0500 Subject: [PATCH 005/127] [partition] Add support for manually creating a partition for zfs --- .../partition/jobs/CreatePartitionJob.cpp | 80 +++++++++++++++++++ 1 file changed, 80 insertions(+) diff --git a/src/modules/partition/jobs/CreatePartitionJob.cpp b/src/modules/partition/jobs/CreatePartitionJob.cpp index 241e0a451..07b816b7e 100644 --- a/src/modules/partition/jobs/CreatePartitionJob.cpp +++ b/src/modules/partition/jobs/CreatePartitionJob.cpp @@ -11,8 +11,10 @@ #include "CreatePartitionJob.h" +#include "core/PartitionInfo.h" #include "partition/FileSystem.h" #include "partition/PartitionQuery.h" +#include "utils/CalamaresUtilsSystem.h" #include "utils/Logger.h" #include "utils/Units.h" @@ -24,9 +26,80 @@ #include #include +#include +#include + using CalamaresUtils::Partition::untranslatedFS; using CalamaresUtils::Partition::userVisibleFS; +/** @brief Create + * + * Uses sfdisk to remove @p partition. This should only be used in cases + * where using kpmcore to remove the partition would not be appropriate + * + */ +static Calamares::JobResult +createZfs( Partition* partition, Device* device ) +{ + auto r = CalamaresUtils::System::instance()->runCommand( + { "sh", + "-c", + "echo start=" + QString::number( partition->firstSector() ) + " size=" + + QString::number( partition->length() ) + " | sfdisk --append --force " + partition->devicePath() }, + std::chrono::seconds( 5 ) ); + if ( r.getExitCode() != 0 ) + { + return Calamares::JobResult::error( + QCoreApplication::translate( CreatePartitionJob::staticMetaObject.className(), + "Failed to create partition" ), + QCoreApplication::translate( CreatePartitionJob::staticMetaObject.className(), + "Failed to create zfs partition with output: " + + r.getOutput().toLocal8Bit() ) ); + } + + // Now we need to do some things that would normally be done by kpmcore + + // First we get the device node from the output and set it as the partition path + QRegularExpression re( QStringLiteral( "Created a new partition (\\d+)" ) ); + QRegularExpressionMatch rem = re.match( r.getOutput() ); + + QString deviceNode; + if ( rem.hasMatch() ) + { + if ( partition->devicePath().back().isDigit() ) + { + deviceNode = partition->devicePath() + QLatin1Char( 'p' ) + rem.captured( 1 ); + } + else + { + deviceNode = partition->devicePath() + rem.captured( 1 ); + } + } + + partition->setPartitionPath( deviceNode ); + partition->setState( Partition::State::None ); + + // If it is a gpt device, set the partition UUID + if ( device->partitionTable()->type() == PartitionTable::gpt && partition->uuid().isEmpty() ) + { + r = CalamaresUtils::System::instance()->runCommand( + { "sfdisk", "--list", "--output", "Device,UUID", partition->devicePath() }, std::chrono::seconds( 5 ) ); + if ( r.getExitCode() == 0 ) + { + QRegularExpression re( deviceNode + QStringLiteral( " +(.+)" ) ); + QRegularExpressionMatch rem = re.match( r.getOutput() ); + + if ( rem.hasMatch() ) + { + partition->setUUID( rem.captured( 1 ) ); + } + } + } + + return Calamares::JobResult::ok(); +} + + CreatePartitionJob::CreatePartitionJob( Device* device, Partition* partition ) : PartitionJob( partition ) , m_device( device ) @@ -194,6 +267,13 @@ CreatePartitionJob::prettyStatusMessage() const Calamares::JobResult CreatePartitionJob::exec() { + // kpmcore doesn't currently handle this case properly so for now, we manually create the partion + // The zfs module can later deal with creating a zpool in the partition + if ( m_partition->fileSystem().type() == FileSystem::Type::Zfs ) + { + return createZfs( m_partition, m_device ); + } + Report report( nullptr ); NewOperation op( *m_device, m_partition ); op.setStatus( Operation::StatusRunning ); From e24d14c5126f50c0776c8a9da6000763bfb67453 Mon Sep 17 00:00:00 2001 From: dalto Date: Sat, 6 Nov 2021 09:44:27 -0500 Subject: [PATCH 006/127] [zfs] Initial commit for zfs module --- src/modules/zfs/CMakeLists.txt | 13 ++++ src/modules/zfs/ZfsJob.cpp | 118 ++++++++++++++++++++++++++++++++ src/modules/zfs/ZfsJob.h | 50 ++++++++++++++ src/modules/zfs/zfs.conf | 38 ++++++++++ src/modules/zfs/zfs.schema.yaml | 22 ++++++ 5 files changed, 241 insertions(+) create mode 100644 src/modules/zfs/CMakeLists.txt create mode 100644 src/modules/zfs/ZfsJob.cpp create mode 100644 src/modules/zfs/ZfsJob.h create mode 100644 src/modules/zfs/zfs.conf create mode 100644 src/modules/zfs/zfs.schema.yaml diff --git a/src/modules/zfs/CMakeLists.txt b/src/modules/zfs/CMakeLists.txt new file mode 100644 index 000000000..2feb911d0 --- /dev/null +++ b/src/modules/zfs/CMakeLists.txt @@ -0,0 +1,13 @@ +# === This file is part of Calamares - === +# +# SPDX-FileCopyrightText: 2020 Adriaan de Groot +# SPDX-License-Identifier: BSD-2-Clause +# +calamares_add_plugin( zfs + TYPE job + EXPORT_MACRO PLUGINDLLEXPORT_PRO + SOURCES + ZfsJob.cpp + SHARED_LIB +) + diff --git a/src/modules/zfs/ZfsJob.cpp b/src/modules/zfs/ZfsJob.cpp new file mode 100644 index 000000000..2602c5417 --- /dev/null +++ b/src/modules/zfs/ZfsJob.cpp @@ -0,0 +1,118 @@ +/* === This file is part of Calamares - === + * + * SPDX-FileCopyrightText: 2021 Evan James + * SPDX-License-Identifier: GPL-3.0-or-later + * + * Calamares is Free Software: see the License-Identifier above. + * + */ + +#include "ZfsJob.h" + +#include "utils/CalamaresUtilsSystem.h" +#include "utils/Logger.h" +#include "utils/Variant.h" + +#include "GlobalStorage.h" +#include "JobQueue.h" +#include "Settings.h" + +ZfsJob::ZfsJob( QObject* parent ) + : Calamares::CppJob( parent ) +{ +} + +ZfsJob::~ZfsJob() {} + +QString +ZfsJob::prettyName() const +{ + return tr( "Create ZFS pools and datasets" ); +} + +Calamares::JobResult +ZfsJob::exec() +{ + QList< QVariant > partitions; + Calamares::GlobalStorage* gs = Calamares::JobQueue::instance()->globalStorage(); + if ( gs && gs->contains( "partitions" ) && gs->value( "partitions" ).canConvert( QVariant::List ) ) + { + partitions = gs->value( "partitions" ).toList(); + } + else + { + cWarning() << "No *partitions* defined."; + return Calamares::JobResult::internalError( tr( "Configuration Error" ), + tr( "No partitions are available for Zfs." ), + Calamares::JobResult::InvalidConfiguration ); + } + + const CalamaresUtils::System* system = CalamaresUtils::System::instance(); + + for ( auto& partition : qAsConst( partitions ) ) + { + QVariantMap pMap; + if ( partition.canConvert( QVariant::Map ) ) + pMap = partition.toMap(); + + // If it isn't a zfs partition, ignore it + if ( pMap[ "fsName" ] != "zfs" ) + continue; + + // Find the best device identifier, if one isn't available, skip this partition + QString deviceName; + if ( pMap[ "partuuid" ].toString() != "" ) + deviceName = "/dev/disk/by-partuuid/" + pMap[ "partuuid" ].toString().toLower(); + else if ( pMap[ "device" ].toString() != "" ) + deviceName = pMap[ "device" ].toString().toLower(); + else + continue; + + // Create the zpool + auto r + = system->runCommand( { "sh", "-c", "zpool create " + m_poolOptions + " " + m_poolName + " " + deviceName }, + std::chrono::seconds( 10 ) ); + if ( r.getExitCode() != 0 ) + return Calamares::JobResult::error( "message", "Failed to create zpool on " + deviceName ); + + // Create the datasets + for ( const auto& dataset : qAsConst( m_datasets ) ) + { + QVariantMap dsMap = dataset.toMap(); + + // Make sure all values are valid + if ( dsMap[ "dsName" ].toString().isEmpty() || dsMap[ "mountpoint" ].toString().isEmpty() + || dsMap[ "canMount" ].toString().isEmpty() ) + { + cWarning() << "Bad dataset entry"; + continue; + } + + // Create the dataset. We set canmount=no regardless of the setting for now. + // It is modified to the correct value in the mount module to ensure mount order is maintained + r = system->runCommand( { "sh", + "-c", + "zfs create " + m_datasetOptions + + " -o canmount=off -o mountpoint=" + dsMap[ "mountpoint" ].toString() + " " + + m_poolName + "/" + dsMap[ "dsName" ].toString() }, + std::chrono::seconds( 10 ) ); + if ( r.getExitCode() != 0 ) + cWarning() << "Failed to create dataset" << dsMap[ "dsName" ].toString(); + } + } + + return Calamares::JobResult::ok(); +} + + +void +ZfsJob::setConfigurationMap( const QVariantMap& map ) +{ + m_poolName = CalamaresUtils::getString( map, "poolName" ); + m_poolOptions = CalamaresUtils::getString( map, "poolOptions" ); + m_datasetOptions = CalamaresUtils::getString( map, "datasetOptions" ); + + m_datasets = CalamaresUtils::getList( map, "datasets" ); +} + +CALAMARES_PLUGIN_FACTORY_DEFINITION( ZfsJobFactory, registerPlugin< ZfsJob >(); ) diff --git a/src/modules/zfs/ZfsJob.h b/src/modules/zfs/ZfsJob.h new file mode 100644 index 000000000..87646a227 --- /dev/null +++ b/src/modules/zfs/ZfsJob.h @@ -0,0 +1,50 @@ +/* === This file is part of Calamares - === + * + * SPDX-FileCopyrightText: 2021 Evan James + * SPDX-License-Identifier: GPL-3.0-or-later + * + * Calamares is Free Software: see the License-Identifier above. + * + */ + +#ifndef ZFSJOB_H +#define ZFSJOB_H + +#include +#include +#include + +#include "CppJob.h" + +#include "utils/PluginFactory.h" + +#include "DllMacro.h" + +/** @brief Create zpools and zfs datasets + * + */ +class PLUGINDLLEXPORT ZfsJob : public Calamares::CppJob +{ + Q_OBJECT + +public: + explicit ZfsJob( QObject* parent = nullptr ); + ~ZfsJob() override; + + QString prettyName() const override; + + Calamares::JobResult exec() override; + + void setConfigurationMap( const QVariantMap& configurationMap ) override; + +private: + QString m_poolName; + QString m_poolOptions; + QString m_datasetOptions; + + QList m_datasets; +}; + +CALAMARES_PLUGIN_FACTORY_DECLARATION( ZfsJobFactory ) + +#endif // ZFSJOB_H diff --git a/src/modules/zfs/zfs.conf b/src/modules/zfs/zfs.conf new file mode 100644 index 000000000..f2f8f52b0 --- /dev/null +++ b/src/modules/zfs/zfs.conf @@ -0,0 +1,38 @@ +# SPDX-FileCopyrightText: no +# SPDX-License-Identifier: CC0-1.0 +# +# The zfs module creates the zfs pools and datasets +# +# +# +--- +# The name to be used for the zpool +poolName: zpcala + +# A list of options that will be passed to zpool create +poolOptions: "-f -o ashift=12 -O mountpoint=none -O acltype=posixacl -O relatime=on" + +# A list of options that will be passed to zfs create when creating each dataset +# Do not include "canmount" or "mountpoint" as those are set below in the datasets array +datasetOptions: "-o compression=lz4 -o atime=off -o xattr=sa" + +# An array of datasets that will be created on the zpool mounted at / +datasets: + - dsName: ROOT + mountpoint: none + canMount: off + - dsName: ROOT/distro + mountpoint: none + canMount: off + - dsName: ROOT/distro/root + mountpoint: / + canMount: noauto + - dsName: ROOT/distro/home + mountpoint: /home + canMount: on + - dsName: ROOT/distro/varcache + mountpoint: /var/cache + canMount: on + - dsName: ROOT/distro/varlog + mountpoint: /var/log + canMount: on diff --git a/src/modules/zfs/zfs.schema.yaml b/src/modules/zfs/zfs.schema.yaml new file mode 100644 index 000000000..fb83778ad --- /dev/null +++ b/src/modules/zfs/zfs.schema.yaml @@ -0,0 +1,22 @@ +# SPDX-FileCopyrightText: 2020 Adriaan de Groot +# SPDX-License-Identifier: GPL-3.0-or-later +--- +$schema: https://json-schema.org/schema# +$id: https://calamares.io/schemas/zfs +additionalProperties: false +type: object +properties: + poolName: { type: string } + poolOptions: { type: string } + datasetOptions: { type: string } + datasets: + type: array + items: + type: object + additionalProperties: false + properties: + dsName: { type: string } + mountpoint: { type: string } + canMount: { type: string } + required: [ dsName, mountpoint, canmount ] +required: [ poolName, datasets ] From 69ef13ef0c4c84b87d7fd0cbc91860f771041679 Mon Sep 17 00:00:00 2001 From: dalto Date: Sat, 6 Nov 2021 09:48:38 -0500 Subject: [PATCH 007/127] [initcpiocfg] Add support for zfs --- src/modules/initcpiocfg/main.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/modules/initcpiocfg/main.py b/src/modules/initcpiocfg/main.py index 99168dcde..755039c0e 100644 --- a/src/modules/initcpiocfg/main.py +++ b/src/modules/initcpiocfg/main.py @@ -150,6 +150,7 @@ def find_initcpio_features(partitions, root_mount_point): swap_uuid = "" uses_btrfs = False + uses_zfs = False uses_lvm2 = False encrypt_hook = False openswap_hook = False @@ -172,6 +173,9 @@ def find_initcpio_features(partitions, root_mount_point): if partition["fs"] == "btrfs": uses_btrfs = True + if partition["fs"] == "zfs": + uses_zfs = True + if "lvm2" in partition["fs"]: uses_lvm2 = True @@ -198,6 +202,9 @@ def find_initcpio_features(partitions, root_mount_point): if uses_lvm2: hooks.append("lvm2") + if uses_zfs: + hooks.append("zfs") + if swap_uuid != "": if encrypt_hook and openswap_hook: hooks.extend(["openswap"]) From 7108d4a509782cb5be3e730dc7b2324ee52c0d5d Mon Sep 17 00:00:00 2001 From: dalto Date: Sat, 6 Nov 2021 10:30:49 -0500 Subject: [PATCH 008/127] [zfs] Update to Calamares coding standards --- src/modules/zfs/ZfsJob.cpp | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/modules/zfs/ZfsJob.cpp b/src/modules/zfs/ZfsJob.cpp index 2602c5417..0227ead07 100644 --- a/src/modules/zfs/ZfsJob.cpp +++ b/src/modules/zfs/ZfsJob.cpp @@ -53,27 +53,40 @@ ZfsJob::exec() { QVariantMap pMap; if ( partition.canConvert( QVariant::Map ) ) + { pMap = partition.toMap(); + } // If it isn't a zfs partition, ignore it if ( pMap[ "fsName" ] != "zfs" ) + { continue; + } // Find the best device identifier, if one isn't available, skip this partition QString deviceName; if ( pMap[ "partuuid" ].toString() != "" ) + { deviceName = "/dev/disk/by-partuuid/" + pMap[ "partuuid" ].toString().toLower(); + } else if ( pMap[ "device" ].toString() != "" ) + { deviceName = pMap[ "device" ].toString().toLower(); + } else + { continue; + } // Create the zpool auto r = system->runCommand( { "sh", "-c", "zpool create " + m_poolOptions + " " + m_poolName + " " + deviceName }, std::chrono::seconds( 10 ) ); if ( r.getExitCode() != 0 ) - return Calamares::JobResult::error( "message", "Failed to create zpool on " + deviceName ); + { + return Calamares::JobResult::error( tr( "zpool failure" ), + tr( "Failed to create zpool on " + deviceName.toLocal8Bit() ) ); + } // Create the datasets for ( const auto& dataset : qAsConst( m_datasets ) ) @@ -97,7 +110,9 @@ ZfsJob::exec() + m_poolName + "/" + dsMap[ "dsName" ].toString() }, std::chrono::seconds( 10 ) ); if ( r.getExitCode() != 0 ) + { cWarning() << "Failed to create dataset" << dsMap[ "dsName" ].toString(); + } } } From 51a5c4de0fe639b4fbf9682356ae3288c03386d2 Mon Sep 17 00:00:00 2001 From: dalto Date: Sat, 6 Nov 2021 13:27:03 -0500 Subject: [PATCH 009/127] [zfs] Add datasets to global storage for other modules --- src/modules/zfs/ZfsJob.cpp | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/src/modules/zfs/ZfsJob.cpp b/src/modules/zfs/ZfsJob.cpp index 0227ead07..48521a2b9 100644 --- a/src/modules/zfs/ZfsJob.cpp +++ b/src/modules/zfs/ZfsJob.cpp @@ -89,13 +89,14 @@ ZfsJob::exec() } // Create the datasets + QVariantList datasetList; for ( const auto& dataset : qAsConst( m_datasets ) ) { - QVariantMap dsMap = dataset.toMap(); + QVariantMap datasetMap = dataset.toMap(); // Make sure all values are valid - if ( dsMap[ "dsName" ].toString().isEmpty() || dsMap[ "mountpoint" ].toString().isEmpty() - || dsMap[ "canMount" ].toString().isEmpty() ) + if ( datasetMap[ "dsName" ].toString().isEmpty() || datasetMap[ "mountpoint" ].toString().isEmpty() + || datasetMap[ "canMount" ].toString().isEmpty() ) { cWarning() << "Bad dataset entry"; continue; @@ -106,13 +107,23 @@ ZfsJob::exec() r = system->runCommand( { "sh", "-c", "zfs create " + m_datasetOptions - + " -o canmount=off -o mountpoint=" + dsMap[ "mountpoint" ].toString() + " " - + m_poolName + "/" + dsMap[ "dsName" ].toString() }, + + " -o canmount=off -o mountpoint=" + datasetMap[ "mountpoint" ].toString() + + " " + m_poolName + "/" + datasetMap[ "dsName" ].toString() }, std::chrono::seconds( 10 ) ); if ( r.getExitCode() != 0 ) { - cWarning() << "Failed to create dataset" << dsMap[ "dsName" ].toString(); + cWarning() << "Failed to create dataset" << datasetMap[ "dsName" ].toString(); } + + // Add the dataset to the list for global storage + datasetMap[ "zpool" ] = m_poolName; + datasetList.append( datasetMap ); + } + + // If the list isn't empty, add it to global storage + if ( !datasetList.isEmpty() ) + { + Calamares::JobQueue::instance()->globalStorage()->insert( "zfs", datasetList ); } } From e3af4f3e2673f47122fcf09ed00b2900e120a648 Mon Sep 17 00:00:00 2001 From: dalto Date: Sat, 6 Nov 2021 14:12:40 -0500 Subject: [PATCH 010/127] [zfs] Add delay before creating the zpool --- src/modules/zfs/ZfsJob.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/modules/zfs/ZfsJob.cpp b/src/modules/zfs/ZfsJob.cpp index 48521a2b9..113f9afd8 100644 --- a/src/modules/zfs/ZfsJob.cpp +++ b/src/modules/zfs/ZfsJob.cpp @@ -79,8 +79,9 @@ ZfsJob::exec() } // Create the zpool + // zfs doesn't wait for the devices so pause for 2 seconds to ensure we give time for the device files to be created auto r - = system->runCommand( { "sh", "-c", "zpool create " + m_poolOptions + " " + m_poolName + " " + deviceName }, + = system->runCommand( { "sh", "-c", "sleep 2 ; zpool create " + m_poolOptions + " " + m_poolName + " " + deviceName }, std::chrono::seconds( 10 ) ); if ( r.getExitCode() != 0 ) { From 858e271c8ac4169f3a19f3d3d3f99bcad5bcf207 Mon Sep 17 00:00:00 2001 From: dalto Date: Sat, 6 Nov 2021 14:33:43 -0500 Subject: [PATCH 011/127] [mount] Add support for zfs datasets --- src/modules/mount/main.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/modules/mount/main.py b/src/modules/mount/main.py index 2e96b6036..1b849a433 100644 --- a/src/modules/mount/main.py +++ b/src/modules/mount/main.py @@ -131,6 +131,24 @@ def mount_partition(root_mount_point, partition, partitions): ",".join([mount_option, partition.get("options", "")])) != 0: libcalamares.utils.warning("Cannot mount {}".format(device)) + if fstype == "zfs" and partition["mountPoint"] == '/': + # Get the zfs dataset list from global storage + zfs = libcalamares.globalstorage.value("zfs") + + if not zfs: + libcalamares.utils.warning("Failed to locate zfs dataset list") + + # Set the canmount property for each dataset. This will effectively mount the dataset + for dataset in zfs: + if dataset['canmount'] == 'noauto' or dataset['canmount'] == 'on': + subprocess.check_call(['zfs', 'set', 'canmount=' + dataset['canmount'], + dataset['zpool'] + '/' + dataset['dsName']]) + + # It is common for the / mountpoint to be set to noauto since it is mounted by the initrd + # If this is the case we need to manually mount it here + if dataset['mountpoint'] == '/' and dataset['canmount'] == 'noauto': + subprocess.check_call(['zfs', 'mount', dataset['zpool'] + '/' + dataset['dsName']]) + def run(): """ From 85a216009891b2930a5ac4f02aa20a495b80d314 Mon Sep 17 00:00:00 2001 From: dalto Date: Sun, 7 Nov 2021 08:01:32 -0600 Subject: [PATCH 012/127] [mount] Improve error handling for zfs --- src/modules/mount/main.py | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/modules/mount/main.py b/src/modules/mount/main.py index 1b849a433..492525f0e 100644 --- a/src/modules/mount/main.py +++ b/src/modules/mount/main.py @@ -136,18 +136,22 @@ def mount_partition(root_mount_point, partition, partitions): zfs = libcalamares.globalstorage.value("zfs") if not zfs: - libcalamares.utils.warning("Failed to locate zfs dataset list") + libcalamares.utils.error("Failed to locate zfs dataset list") # Set the canmount property for each dataset. This will effectively mount the dataset for dataset in zfs: - if dataset['canmount'] == 'noauto' or dataset['canmount'] == 'on': - subprocess.check_call(['zfs', 'set', 'canmount=' + dataset['canmount'], - dataset['zpool'] + '/' + dataset['dsName']]) + try: + if dataset['canmount'] == 'noauto' or dataset['canmount'] == 'on': + subprocess.check_call(['zfs', 'set', 'canmount=' + dataset['canmount'], + dataset['zpool'] + '/' + dataset['dsName']]) - # It is common for the / mountpoint to be set to noauto since it is mounted by the initrd - # If this is the case we need to manually mount it here - if dataset['mountpoint'] == '/' and dataset['canmount'] == 'noauto': - subprocess.check_call(['zfs', 'mount', dataset['zpool'] + '/' + dataset['dsName']]) + # It is common for the / mountpoint to be set to noauto since it is mounted by the initrd + # If this is the case we need to manually mount it here + if dataset['mountpoint'] == '/' and dataset['canmount'] == 'noauto': + subprocess.check_call(['zfs', 'mount', dataset['zpool'] + '/' + dataset['dsName']]) + except KeyError: + # This should be impossible + libcalamares.utils.error("Internal error handling zfs dataset") def run(): From 0720d568033bf0d5545d756d9826cccceacc51fe Mon Sep 17 00:00:00 2001 From: dalto Date: Sun, 7 Nov 2021 09:32:52 -0600 Subject: [PATCH 013/127] [bootloader] Add initial support for zfs --- src/modules/bootloader/main.py | 70 +++++++++++++++++++++++++++++++++- 1 file changed, 68 insertions(+), 2 deletions(-) diff --git a/src/modules/bootloader/main.py b/src/modules/bootloader/main.py index 68cbddd0e..10e7d0f47 100644 --- a/src/modules/bootloader/main.py +++ b/src/modules/bootloader/main.py @@ -92,6 +92,32 @@ def get_kernel_line(kernel_type): 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("zfs") + + 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.error("Internal error handling zfs dataset") + raise + + return None + + def create_systemd_boot_conf(install_path, efi_dir, uuid, entry, entry_name, kernel_type): """ Creates systemd-boot configuration files based on given parameters. @@ -133,12 +159,21 @@ def create_systemd_boot_conf(install_path, efi_dir, uuid, entry, entry_name, ker "root=/dev/mapper/" + partition["luksMapperName"]] - # systemd-boot with a BTRFS root filesystem needs to be told - # about the root subvolume. for partition in partitions: + # systemd-boot with a BTRFS root filesystem needs to be told + # about the root subvolume. if partition["mountPoint"] == "/" and partition["fs"] == "btrfs": kernel_params.append("rootflags=subvol=@") + # zfs needs to be told the location of the root dataset + if partition["mountPoint"] == "/" and partition["fs"] == "zfs": + zfs_root = get_zfs_root + if zfs_root is not None: + kernel_params.append("root=ZFS=" + zfs_root) + else: + # Something is really broken if we get to this point + libcalamares.utils.error("Internal error handling zfs dataset") + if cryptdevice_params: kernel_params.extend(cryptdevice_params) else: @@ -314,6 +349,37 @@ def get_grub_efi_parameters(): return None +def run_grub_mkconfig(output_file): + """ + Runs grub-mkconfig in the target environment + + :param output_file: A string containing the path to the generating grub config file + :return: + """ + + # get the partition from global storage + partitions = libcalamares.globalstorage.value("partitions") + if not partitions: + libcalamares.utils.error("Failed to run grub-mkconfig, no partitions defined in global storage") + return + + # check for zfs + is_zfs = False + for partition in partitions: + if partition["mountPoint"] == "/" and partition["fs"] == "zfs": + is_zfs = True + + # zfs needs an environment variable set for grub-mkconfig + if is_zfs: + check_target_env_call(["sh", "-c", "echo ZPOOL_VDEV_NAME_PATH=1 >> /etc/environment"]) + 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 install_grub(efi_directory, fw_type): """ Installs grub as bootloader, either in pc or efi mode. From 24306efddb345c458e51e446f42a4f92e8e218ee Mon Sep 17 00:00:00 2001 From: Calamares CI Date: Sun, 7 Nov 2021 23:23:25 +0100 Subject: [PATCH 014/127] i18n: [calamares] Automatic merge of Transifex translations --- lang/calamares_ca.ts | 10 +++++----- lang/calamares_hr.ts | 10 +++++----- lang/calamares_pt_PT.ts | 10 +++++----- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/lang/calamares_ca.ts b/lang/calamares_ca.ts index 6facae9d1..cff953e30 100644 --- a/lang/calamares_ca.ts +++ b/lang/calamares_ca.ts @@ -689,27 +689,27 @@ L'instal·lador es tancarà i tots els canvis es perdran. Successfully unmounted %1. - + S'ha desmuntat correctament %1. Successfully disabled swap %1. - + S'ha inhabilitat correctament l'intercanvi %1. Successfully cleared swap %1. - + S'ha netejat correctament l'intercanvi %1. Successfully closed mapper device %1. - + El dispositiu de mapatge %1 s'ha tancat correctament. Successfully disabled volume group %1. - + El grup de volums %1 s'ha inhabilitat correctament. diff --git a/lang/calamares_hr.ts b/lang/calamares_hr.ts index 214d1be93..62d1d1ef1 100644 --- a/lang/calamares_hr.ts +++ b/lang/calamares_hr.ts @@ -691,27 +691,27 @@ Instalacijski program će izaći i sve promjene će biti izgubljene. Successfully unmounted %1. - + Uspješno demontiran %1. Successfully disabled swap %1. - + Uspješno onemogućen swap %1. Successfully cleared swap %1. - + Uspješno obrisan swap %1. Successfully closed mapper device %1. - + Uspješno zatvoren device mapper %1. Successfully disabled volume group %1. - + Volume grupa %1 je uspješno onemogućena. diff --git a/lang/calamares_pt_PT.ts b/lang/calamares_pt_PT.ts index 802f0810e..dbbc088a9 100644 --- a/lang/calamares_pt_PT.ts +++ b/lang/calamares_pt_PT.ts @@ -689,27 +689,27 @@ O instalador será encerrado e todas as alterações serão perdidas. Successfully unmounted %1. - + % 1 desmontado com sucesso. Successfully disabled swap %1. - + Swap %1 desativada com sucesso. Successfully cleared swap %1. - + Swap % 1 limpa com sucesso. Successfully closed mapper device %1. - + Dispositivo mapeador % 1 fechado com sucesso. Successfully disabled volume group %1. - + Grupo de volume % 1 desativado com sucesso. From 2f145fcf44b2657e21f89fcff2cc784b307718aa Mon Sep 17 00:00:00 2001 From: dalto Date: Mon, 8 Nov 2021 17:26:08 -0600 Subject: [PATCH 015/127] [partition][zfs] Add support for zfs encryption --- .../partition/gui/CreatePartitionDialog.cpp | 10 ++- src/modules/zfs/ZfsJob.cpp | 87 +++++++++++++++---- src/modules/zfs/ZfsJob.h | 19 ++++ 3 files changed, 100 insertions(+), 16 deletions(-) diff --git a/src/modules/partition/gui/CreatePartitionDialog.cpp b/src/modules/partition/gui/CreatePartitionDialog.cpp index 0727ee935..e7d7751a1 100644 --- a/src/modules/partition/gui/CreatePartitionDialog.cpp +++ b/src/modules/partition/gui/CreatePartitionDialog.cpp @@ -243,7 +243,8 @@ CreatePartitionDialog::getNewlyCreatedPartition() // does so, to set up the partition for create-and-then-set-flags. Partition* partition = nullptr; QString luksPassphrase = m_ui->encryptWidget->passphrase(); - if ( m_ui->encryptWidget->state() == EncryptWidget::Encryption::Confirmed && !luksPassphrase.isEmpty() ) + if ( m_ui->encryptWidget->state() == EncryptWidget::Encryption::Confirmed && !luksPassphrase.isEmpty() + && fsType != FileSystem::Zfs ) { partition = KPMHelpers::createNewEncryptedPartition( m_parent, *m_device, m_role, fsType, fsLabel, first, last, luksPassphrase, PartitionTable::Flags() ); @@ -254,6 +255,13 @@ CreatePartitionDialog::getNewlyCreatedPartition() m_parent, *m_device, m_role, fsType, fsLabel, first, last, PartitionTable::Flags() ); } + // For zfs, we let the zfs module handle the encryption but we need to make the passphrase available to that module + if( m_ui->encryptWidget->state() == EncryptWidget::Encryption::Confirmed && !luksPassphrase.isEmpty() + && fsType == FileSystem::Zfs ) + { + Calamares::JobQueue::instance()->globalStorage()->insert( "encryptphrase", luksPassphrase ); + } + if ( m_device->type() == Device::Type::LVM_Device ) { partition->setPartitionPath( m_device->deviceNode() + QStringLiteral( "/" ) diff --git a/src/modules/zfs/ZfsJob.cpp b/src/modules/zfs/ZfsJob.cpp index 113f9afd8..af57fa5cc 100644 --- a/src/modules/zfs/ZfsJob.cpp +++ b/src/modules/zfs/ZfsJob.cpp @@ -17,6 +17,8 @@ #include "JobQueue.h" #include "Settings.h" +#include + ZfsJob::ZfsJob( QObject* parent ) : Calamares::CppJob( parent ) { @@ -30,6 +32,56 @@ ZfsJob::prettyName() const return tr( "Create ZFS pools and datasets" ); } +ZfsResult +ZfsJob::CreateZpool( QString deviceName, QString poolName, QString poolOptions, bool encrypt, QString passphrase ) const +{ + // zfs doesn't wait for the devices so pause for 2 seconds to ensure we give time for the device files to be created + QString command; + if ( encrypt ) + { + command = "sleep 2 ; echo \"" + passphrase + "\" | zpool create " + poolOptions + + " -O encryption=aes-256-gcm -O keyformat=passphrase " + poolName + " " + deviceName; + } + else + { + command = "sleep 2 ; zpool create " + poolOptions + " " + poolName + " " + deviceName; + } + + // We use a qProcess instead of runCommand so the password will not end up in the logs + QProcess process; + + process.setProcessChannelMode( QProcess::MergedChannels ); + cDebug() << Logger::SubEntry << "Running zpool create"; + + process.start( "sh", QStringList() << "-c" << command ); + + if ( !process.waitForStarted() ) + { + return { false, tr( "zpool create process failed to start" ) }; + } + + if ( !process.waitForFinished( 5000 ) ) + { + return { false, tr( "Process for zpool create timed out" ) }; + } + + QString output = QString::fromLocal8Bit( process.readAllStandardOutput() ).trimmed(); + + if ( process.exitStatus() == QProcess::CrashExit ) + { + return { false, tr( "The output from the crash was: " ) + output }; + } + + auto exitcode = process.exitCode(); + if ( exitcode != 0 ) + { + cWarning() << "Failed to run zpool create. The output was: " + output; + return { false, tr( "Failed to create zpool on " ) + deviceName }; + } + + return { true, QString() }; +} + Calamares::JobResult ZfsJob::exec() { @@ -78,15 +130,20 @@ ZfsJob::exec() continue; } - // Create the zpool - // zfs doesn't wait for the devices so pause for 2 seconds to ensure we give time for the device files to be created - auto r - = system->runCommand( { "sh", "-c", "sleep 2 ; zpool create " + m_poolOptions + " " + m_poolName + " " + deviceName }, - std::chrono::seconds( 10 ) ); - if ( r.getExitCode() != 0 ) + ZfsResult zfsResult; + if ( gs->contains( "encryptphrase" ) ) { - return Calamares::JobResult::error( tr( "zpool failure" ), - tr( "Failed to create zpool on " + deviceName.toLocal8Bit() ) ); + zfsResult + = CreateZpool( deviceName, m_poolName, m_poolOptions, true, gs->value( "encryptphrase" ).toString() ); + } + else + { + zfsResult = CreateZpool( deviceName, m_poolName, m_poolOptions, false ); + } + + if ( !zfsResult.success ) + { + return Calamares::JobResult::error( tr( "Failed to create zpool" ), zfsResult.failureMessage ); } // Create the datasets @@ -105,12 +162,12 @@ ZfsJob::exec() // Create the dataset. We set canmount=no regardless of the setting for now. // It is modified to the correct value in the mount module to ensure mount order is maintained - r = system->runCommand( { "sh", - "-c", - "zfs create " + m_datasetOptions - + " -o canmount=off -o mountpoint=" + datasetMap[ "mountpoint" ].toString() - + " " + m_poolName + "/" + datasetMap[ "dsName" ].toString() }, - std::chrono::seconds( 10 ) ); + auto r = system->runCommand( { "sh", + "-c", + "zfs create " + m_datasetOptions + " -o canmount=off -o mountpoint=" + + datasetMap[ "mountpoint" ].toString() + " " + m_poolName + "/" + + datasetMap[ "dsName" ].toString() }, + std::chrono::seconds( 10 ) ); if ( r.getExitCode() != 0 ) { cWarning() << "Failed to create dataset" << datasetMap[ "dsName" ].toString(); @@ -124,7 +181,7 @@ ZfsJob::exec() // If the list isn't empty, add it to global storage if ( !datasetList.isEmpty() ) { - Calamares::JobQueue::instance()->globalStorage()->insert( "zfs", datasetList ); + gs->insert( "zfs", datasetList ); } } diff --git a/src/modules/zfs/ZfsJob.h b/src/modules/zfs/ZfsJob.h index 87646a227..b2feb9e87 100644 --- a/src/modules/zfs/ZfsJob.h +++ b/src/modules/zfs/ZfsJob.h @@ -20,6 +20,11 @@ #include "DllMacro.h" +struct ZfsResult { + bool success; + QString failureMessage; +}; + /** @brief Create zpools and zfs datasets * */ @@ -43,6 +48,20 @@ private: QString m_datasetOptions; QList m_datasets; + + /** @brief Creates a zpool based on the provided arguments + * + * Creates a zpool + * @p deviceName is a full path to the device the zpool should be created on + * @p poolName is a string containing the name of the pool to create + * @p poolOptions are the options to pass to zpool create + * @p encrypt is a boolean which determines if the pool should be encrypted + * @p passphrase is a string continaing the passphrase + * + */ + ZfsResult CreateZpool(QString deviceName, QString poolName, QString poolOptions, bool encrypt, QString passphrase = QString() ) const; + + }; CALAMARES_PLUGIN_FACTORY_DECLARATION( ZfsJobFactory ) From 06b6263c24894e9c12d7296a3bf580e100ef7822 Mon Sep 17 00:00:00 2001 From: dalto Date: Tue, 9 Nov 2021 07:42:39 -0600 Subject: [PATCH 016/127] [zfs] Export zpool so it can later be mounted at the correct location --- src/modules/zfs/ZfsJob.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/modules/zfs/ZfsJob.cpp b/src/modules/zfs/ZfsJob.cpp index af57fa5cc..b04dfd473 100644 --- a/src/modules/zfs/ZfsJob.cpp +++ b/src/modules/zfs/ZfsJob.cpp @@ -183,6 +183,13 @@ ZfsJob::exec() { gs->insert( "zfs", datasetList ); } + + // Export the zpool so it can be reimported at the correct local later + auto r = system->runCommand( { "zpool", "export", m_poolName }, std::chrono::seconds( 10 ) ); + if ( r.getExitCode() != 0 ) + { + cWarning() << "Failed to export pool" << m_poolName; + } } return Calamares::JobResult::ok(); From cd0785164bf166bd74b7d343433efef131bea336 Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Tue, 9 Nov 2021 15:57:20 +0100 Subject: [PATCH 017/127] [partition] Fix compatibility with Qt 5.12 --- src/modules/partition/jobs/ClearMountsJob.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/modules/partition/jobs/ClearMountsJob.cpp b/src/modules/partition/jobs/ClearMountsJob.cpp index 8b339dea4..831a1e868 100644 --- a/src/modules/partition/jobs/ClearMountsJob.cpp +++ b/src/modules/partition/jobs/ClearMountsJob.cpp @@ -215,6 +215,12 @@ getPVGroups( const QString& deviceName ) * meant **only** for debugging and is not displayed to the user, * which is why no translation is applied. * + * The MessageAndPath class stores a C-style pointer to a character + * array -- from QT_TRANSLATE_NOOP() -- and a path to substitute into it. + * + * When the tryX() functions return an "empty string", it is an + * empty MessageAndPath which acts like an empty string (in particular, + * isEmpty() is true). */ class MessageAndPath @@ -237,8 +243,14 @@ public: } private: +#if ( QT_VERSION < QT_VERSION_CHECK( 5, 15, 0 ) ) + // TODO: 3.3 remove because newer Qt does support constness + const char* m_message = nullptr; + QString m_path; +#else const char* const m_message = nullptr; QString const m_path; +#endif }; STATICTEST inline QDebug& From 7291656f19cf5955cd0950f33a0eb929545e3b81 Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Tue, 9 Nov 2021 16:24:10 +0100 Subject: [PATCH 018/127] CI: add new Python API to the linter's library, too --- ci/libcalamares/utils.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ci/libcalamares/utils.py b/ci/libcalamares/utils.py index 706e4a95a..b3676be0f 100644 --- a/ci/libcalamares/utils.py +++ b/ci/libcalamares/utils.py @@ -20,4 +20,8 @@ def target_env_call(_): return 0 def check_target_env_call(_): pass +def target_env_process_output(cmd, *args): return 0 + +def host_env_process_output(cmd, *args): return 0 + def mount(device, mountpoint, fstype, options): return 0 From 8639c9a79fa5ae4b1c8b72b281a07d8ce3b090f2 Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Tue, 9 Nov 2021 16:37:25 +0100 Subject: [PATCH 019/127] [partition] Fix up tests --- src/modules/partition/tests/ClearMountsJobTests.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/modules/partition/tests/ClearMountsJobTests.cpp b/src/modules/partition/tests/ClearMountsJobTests.cpp index e05af4897..3af400fde 100644 --- a/src/modules/partition/tests/ClearMountsJobTests.cpp +++ b/src/modules/partition/tests/ClearMountsJobTests.cpp @@ -19,6 +19,14 @@ QTEST_GUILESS_MAIN( ClearMountsJobTests ) /* Not exactly public API */ QStringList getPartitionsForDevice( const QString& deviceName ); +/* At one point, the partitions-list was read from /proc/partitions by + * running awk and grep, as below. Check that the current implementation + * matches that crufty one. + * + * Update 2021-11-02: the newer implementation prepends /dev/ to the + * names of the partitions, for simplicity elsewhere, so that needs + * to be added in to the awk(1) program, too. + */ QStringList getPartitionsForDevice_other( const QString& deviceName ) { @@ -26,7 +34,7 @@ getPartitionsForDevice_other( const QString& deviceName ) process.setProgram( "sh" ); process.setArguments( { "-c", - QString( "echo $(awk '{print $4}' /proc/partitions | sed -e '/name/d' -e '/^$/d' -e '/[1-9]/!d' | grep %1)" ) + QString( "echo $(awk '{print \"/dev/\"$4}' /proc/partitions | sed -e '/name/d' -e '/^$/d' -e '/[1-9]/!d' | grep %1)" ) .arg( deviceName ) } ); process.start(); process.waitForFinished(); From 90452147a343979c9fe7d03672e5bd5db1a08482 Mon Sep 17 00:00:00 2001 From: dalto Date: Tue, 9 Nov 2021 14:53:44 -0600 Subject: [PATCH 020/127] [mount] Fix zfs code and add support for encryption --- src/modules/mount/main.py | 62 +++++++++++++++++++++++++++++++-------- 1 file changed, 50 insertions(+), 12 deletions(-) diff --git a/src/modules/mount/main.py b/src/modules/mount/main.py index 492525f0e..813114f0a 100644 --- a/src/modules/mount/main.py +++ b/src/modules/mount/main.py @@ -61,6 +61,21 @@ def get_btrfs_subvolumes(partitions): return btrfs_subvolumes +def parse_global_storage(gs_value): + """ + Something in the chain is converting on and off to true and false. This converts it back. + + :param gs_value: The value from global storage + :return: + """ + if gs_value is True: + return "on" + elif gs_value is False: + return "off" + else: + return gs_value + + def mount_partition(root_mount_point, partition, partitions): """ Do a single mount of @p partition inside @p root_mount_point. @@ -136,22 +151,45 @@ def mount_partition(root_mount_point, partition, partitions): zfs = libcalamares.globalstorage.value("zfs") if not zfs: - libcalamares.utils.error("Failed to locate zfs dataset list") + libcalamares.utils.warning("Failed to locate zfs dataset list") + raise Exception("Internal error mounting zfs datasets") + + # import the zpool + import_result = subprocess.run(['zpool', 'import', '-R', root_mount_point, zfs[0]['zpool']]) + if import_result.returncode != 0: + raise Exception("Failed to import zpool") + + passphrase = libcalamares.globalstorage.value("encryptphrase") + if passphrase: + # The zpool is encrypted, we need to unlock it + loadkey_result = subprocess.run(['sh', '-c', 'echo "' + passphrase + '" | zfs load-key ' + zfs[0]['zpool']]) + if loadkey_result.returncode != 0: + raise Exception("Failed to unlock zpool") + + # first we handle the / dataset if there is one + for dataset in zfs: + if dataset['mountpoint'] == '/': + # Properly set the canmount field from global storage + can_mount = parse_global_storage(dataset['canMount']) + set_result = subprocess.run(['zfs', 'set', 'canmount=' + can_mount, + dataset['zpool'] + '/' + dataset['dsName']]) + if set_result.returncode != 0: + raise Exception("Failed to set zfs mountpoint") + if dataset['canMount'] == 'noauto': + mount_result = subprocess.run(['zfs', 'mount', dataset['zpool'] + '/' + dataset['dsName']]) + if mount_result.returncode != 0: + raise Exception("Failed to mount root dataset") # Set the canmount property for each dataset. This will effectively mount the dataset for dataset in zfs: - try: - if dataset['canmount'] == 'noauto' or dataset['canmount'] == 'on': - subprocess.check_call(['zfs', 'set', 'canmount=' + dataset['canmount'], - dataset['zpool'] + '/' + dataset['dsName']]) + # We already handled the / mountpoint above + if dataset['mountpoint'] != '/': + can_mount = parse_global_storage(dataset['canMount']) - # It is common for the / mountpoint to be set to noauto since it is mounted by the initrd - # If this is the case we need to manually mount it here - if dataset['mountpoint'] == '/' and dataset['canmount'] == 'noauto': - subprocess.check_call(['zfs', 'mount', dataset['zpool'] + '/' + dataset['dsName']]) - except KeyError: - # This should be impossible - libcalamares.utils.error("Internal error handling zfs dataset") + set_result = subprocess.run(['zfs', 'set', 'canmount=' + can_mount, + dataset['zpool'] + '/' + dataset['dsName']]) + if set_result.returncode != 0: + raise Exception("Failed to set zfs mountpoint") def run(): From 91762e3df4f44b4bca47b9a8ae280a96109d9e0c Mon Sep 17 00:00:00 2001 From: dalto Date: Tue, 9 Nov 2021 14:54:46 -0600 Subject: [PATCH 021/127] [zfs] Fix typo and add missing continue --- src/modules/zfs/ZfsJob.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/modules/zfs/ZfsJob.cpp b/src/modules/zfs/ZfsJob.cpp index b04dfd473..8790b1fff 100644 --- a/src/modules/zfs/ZfsJob.cpp +++ b/src/modules/zfs/ZfsJob.cpp @@ -171,6 +171,7 @@ ZfsJob::exec() if ( r.getExitCode() != 0 ) { cWarning() << "Failed to create dataset" << datasetMap[ "dsName" ].toString(); + continue; } // Add the dataset to the list for global storage @@ -184,7 +185,7 @@ ZfsJob::exec() gs->insert( "zfs", datasetList ); } - // Export the zpool so it can be reimported at the correct local later + // Export the zpool so it can be reimported at the correct location later auto r = system->runCommand( { "zpool", "export", m_poolName }, std::chrono::seconds( 10 ) ); if ( r.getExitCode() != 0 ) { From 4fb8993a3803b53cc4d7ee10e4e778cf509f5160 Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Tue, 9 Nov 2021 23:08:40 +0100 Subject: [PATCH 022/127] [finishedq] Add sample QML for mobile usage This has a countdown-timer that automatically restarts; the rest of the settings follow the finishedq.conf values. FIXES #1601 --- src/modules/finishedq/finishedq@mobile.qml | 121 +++++++++++++++++++++ 1 file changed, 121 insertions(+) create mode 100644 src/modules/finishedq/finishedq@mobile.qml diff --git a/src/modules/finishedq/finishedq@mobile.qml b/src/modules/finishedq/finishedq@mobile.qml new file mode 100644 index 000000000..d1ed7d1b0 --- /dev/null +++ b/src/modules/finishedq/finishedq@mobile.qml @@ -0,0 +1,121 @@ +/* === This file is part of Calamares - === + * + * SPDX-FileCopyrightText: 2021 Anke Boersma + * SPDX-License-Identifier: GPL-3.0-or-later + * License-Filename: LICENSE + * + * Calamares is Free Software: see the License-Identifier above. + * + */ + +import io.calamares.core 1.0 +import io.calamares.ui 1.0 + +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import QtQuick.Layouts 1.3 +import org.kde.kirigami 2.7 as Kirigami +import QtGraphicalEffects 1.0 +import QtQuick.Window 2.3 + +Page { + + id: finished + + width: parent.width + height: parent.height + + header: Kirigami.Heading { + width: parent.width + height: 100 + id: header + Layout.fillWidth: true + horizontalAlignment: Qt.AlignHCenter + color: Kirigami.Theme.textColor + level: 1 + text: qsTr("Installation Completed") + + Text { + anchors.top: header.bottom + anchors.horizontalCenter: parent.horizontalCenter + horizontalAlignment: Text.AlignHCenter + font.pointSize: 12 + text: qsTr("%1 has been installed on your computer.
+ You may now restart your device.").arg(Branding.string(Branding.ProductName)) + } + + Image { + source: "seedling.svg" + anchors.top: header.bottom + anchors.topMargin: 80 + anchors.horizontalCenter: parent.horizontalCenter + width: 64 + height: 64 + mipmap: true + } + } + + RowLayout { + Layout.alignment: Qt.AlignRight|Qt.AlignVCenter + anchors.centerIn: parent + spacing: 6 + + Button { + id: button + text: qsTr("Close") + icon.name: "application-exit" + onClicked: { ViewManager.quit(); } + } + + Button { + text: qsTr("Restart") + icon.name: "system-reboot" + onClicked: { config.doRestart(true); } + } + } + + Item { + + Layout.fillHeight: true + Layout.fillWidth: true + anchors.bottom: parent.bottom + anchors.bottomMargin : 100 + anchors.horizontalCenter: parent.horizontalCenter + + ProgressBar { + id: autoRestartBar + value: 1.0 + anchors.horizontalCenter: parent.horizontalCenter + + } + + Timer { + id: autoRestartTimer + // This is in milliseconds and should be less than 1000 (because of logic in onTriggered) + interval: 100 + repeat: true + running: false + // Whenever the timer fires (1000 / interval times a second) count the progress bar down + // by 1%. When the bar is empty, try to restart normally; as a backup, when the bar + // is empty change settings and schedule it to quit 1000 milliseconds (1s) later. + onTriggered: { + autoRestartBar.value -= 0.01; + if (autoRestartBar.value <= 0.0) { + // First time through here, set the interval to 1000 so that the + // second time (1 second later) goes to quit(). + if ( interval > 999) { ViewManager.quit(); } + else { config.doRestart(true); running = false; interval = 1000; repeat = false; start(); } + } + } + } + } + + function onActivate() + { + autoRestartTimer.running = true + } + + function onLeave() + { + } +} From 0bef2a91a1b529425bb53bedd0eba087270e1eb5 Mon Sep 17 00:00:00 2001 From: dalto Date: Wed, 10 Nov 2021 17:16:09 -0600 Subject: [PATCH 023/127] [fstab] Remove space_cache from btrfs mount options --- src/modules/fstab/fstab.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/fstab/fstab.conf b/src/modules/fstab/fstab.conf index fdf0d41ec..0e710980b 100644 --- a/src/modules/fstab/fstab.conf +++ b/src/modules/fstab/fstab.conf @@ -13,7 +13,7 @@ # options from this mapping. mountOptions: default: defaults,noatime - btrfs: defaults,noatime,space_cache,autodefrag,compress=zstd + btrfs: defaults,noatime,autodefrag,compress=zstd # Mount options to use for the EFI System Partition. If not defined, the # *mountOptions* for *vfat* are used, or if that is not set either, From 9ef520f8627ddfdcbcb060a0f7776e8593935f2f Mon Sep 17 00:00:00 2001 From: dalto Date: Fri, 12 Nov 2021 08:58:43 -0600 Subject: [PATCH 024/127] Add comment describing the situation with space_cache on btrfs --- src/modules/fstab/fstab.conf | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/modules/fstab/fstab.conf b/src/modules/fstab/fstab.conf index 0e710980b..74b1feb39 100644 --- a/src/modules/fstab/fstab.conf +++ b/src/modules/fstab/fstab.conf @@ -11,6 +11,12 @@ # Mount options to use for all filesystems. If a specific filesystem # is listed here, use those options, otherwise use the *default* # options from this mapping. +# +# With kernels 5.15 and newer be cautious of adding the option +# space_cache to the btrfs mount options. If the root volume is initially +# mounted with one version of space_cache and the mount option specifies +# a different version or implies v1 when v2 is in use, it may fail to +# remount properly. mountOptions: default: defaults,noatime btrfs: defaults,noatime,autodefrag,compress=zstd From daa5731acfecc1f7ba38ba6b2520daa28117e893 Mon Sep 17 00:00:00 2001 From: dalto Date: Fri, 12 Nov 2021 09:29:04 -0600 Subject: [PATCH 025/127] [fstab] Improve comment about space_cache --- src/modules/fstab/fstab.conf | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/modules/fstab/fstab.conf b/src/modules/fstab/fstab.conf index 74b1feb39..385161d2d 100644 --- a/src/modules/fstab/fstab.conf +++ b/src/modules/fstab/fstab.conf @@ -12,11 +12,9 @@ # is listed here, use those options, otherwise use the *default* # options from this mapping. # -# With kernels 5.15 and newer be cautious of adding the option -# space_cache to the btrfs mount options. If the root volume is initially -# mounted with one version of space_cache and the mount option specifies -# a different version or implies v1 when v2 is in use, it may fail to -# remount properly. +# With kernels 5.15 and newer be cautious of adding the option space_cache +# to the btrfs mount options. The default in 5.15 changed to space_cache=v2. +# If space_cache or space_cache=v1 are specified, it may fail to remount. mountOptions: default: defaults,noatime btrfs: defaults,noatime,autodefrag,compress=zstd From 4bed079ebf57ba1c17585cade52298bd2910f931 Mon Sep 17 00:00:00 2001 From: dalto Date: Fri, 12 Nov 2021 16:06:06 -0600 Subject: [PATCH 026/127] Add support for multiple zpools --- src/modules/mount/main.py | 123 ++++++++++------- .../partition/gui/CreatePartitionDialog.cpp | 26 +++- src/modules/zfs/ZfsJob.cpp | 124 ++++++++++++++---- 3 files changed, 192 insertions(+), 81 deletions(-) diff --git a/src/modules/mount/main.py b/src/modules/mount/main.py index 813114f0a..20815969a 100644 --- a/src/modules/mount/main.py +++ b/src/modules/mount/main.py @@ -111,11 +111,79 @@ def mount_partition(root_mount_point, partition, partitions): if "luksMapperName" in partition: device = os.path.join("/dev/mapper", partition["luksMapperName"]) - if libcalamares.utils.mount(device, - mount_point, - fstype, - partition.get("options", "")) != 0: - libcalamares.utils.warning("Cannot mount {}".format(device)) + if fstype == "zfs": + zfs_pool_list = libcalamares.globalstorage.value("zfsPoolInfo") + if not zfs_pool_list: + libcalamares.utils.warning("Failed to locate zfsPoolInfo data in global storage") + raise Exception("Internal error mounting zfs datasets") + + for zfs_pool in zfs_pool_list: + libcalamares.utils.warning("Poolname: " + zfs_pool["poolName"] + " mountpoint: " + zfs_pool["mountpoint"]) + if zfs_pool["mountpoint"] == partition["mountPoint"]: + pool_name = zfs_pool["poolName"] + ds_name = zfs_pool["dsName"]; + + # import the zpool + import_result = subprocess.run(['zpool', 'import', '-R', root_mount_point, pool_name]) + if import_result.returncode != 0: + raise Exception("Failed to import zpool") + + 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 + loadkey_result = subprocess.run(['sh', '-c', 'echo "' + passphrase + '" | zfs load-key ' + pool_name]) + if loadkey_result.returncode != 0: + raise Exception("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 Exception("Internal error mounting zfs datasets") + + # first we handle the / dataset if there is one + for dataset in zfs: + if dataset['mountpoint'] == '/': + # Properly set the canmount field from global storage + can_mount = parse_global_storage(dataset['canMount']) + set_result = subprocess.run(['zfs', 'set', 'canmount=' + can_mount, + dataset['zpool'] + '/' + dataset['dsName']]) + if set_result.returncode != 0: + raise Exception("Failed to set zfs mountpoint") + if dataset['canMount'] == 'noauto': + mount_result = subprocess.run(['zfs', 'mount', dataset['zpool'] + '/' + dataset['dsName']]) + if mount_result.returncode != 0: + raise Exception("Failed to mount root dataset") + + # Set the canmount property for each dataset. This will effectively mount the dataset + for dataset in zfs: + # We already handled the / mountpoint above + if dataset['mountpoint'] != '/': + can_mount = parse_global_storage(dataset['canMount']) + + set_result = subprocess.run(['zfs', 'set', 'canmount=' + can_mount, + dataset['zpool'] + '/' + dataset['dsName']]) + if set_result.returncode != 0: + raise Exception("Failed to set zfs mountpoint") + else: + set_result = subprocess.run(['zfs', 'set', 'canmount=on', pool_name + '/' + ds_name]) + if set_result.returncode != 0: + raise Exception("Failed to set zfs mountpoint") + else: # fstype == "zfs" + if libcalamares.utils.mount(device, + mount_point, + fstype, + partition.get("options", "")) != 0: + libcalamares.utils.warning("Cannot mount {}".format(device)) # Special handling for btrfs subvolumes. Create the subvolumes listed in mount.conf if fstype == "btrfs" and partition["mountPoint"] == '/': @@ -146,51 +214,6 @@ def mount_partition(root_mount_point, partition, partitions): ",".join([mount_option, partition.get("options", "")])) != 0: libcalamares.utils.warning("Cannot mount {}".format(device)) - if fstype == "zfs" and partition["mountPoint"] == '/': - # Get the zfs dataset list from global storage - zfs = libcalamares.globalstorage.value("zfs") - - if not zfs: - libcalamares.utils.warning("Failed to locate zfs dataset list") - raise Exception("Internal error mounting zfs datasets") - - # import the zpool - import_result = subprocess.run(['zpool', 'import', '-R', root_mount_point, zfs[0]['zpool']]) - if import_result.returncode != 0: - raise Exception("Failed to import zpool") - - passphrase = libcalamares.globalstorage.value("encryptphrase") - if passphrase: - # The zpool is encrypted, we need to unlock it - loadkey_result = subprocess.run(['sh', '-c', 'echo "' + passphrase + '" | zfs load-key ' + zfs[0]['zpool']]) - if loadkey_result.returncode != 0: - raise Exception("Failed to unlock zpool") - - # first we handle the / dataset if there is one - for dataset in zfs: - if dataset['mountpoint'] == '/': - # Properly set the canmount field from global storage - can_mount = parse_global_storage(dataset['canMount']) - set_result = subprocess.run(['zfs', 'set', 'canmount=' + can_mount, - dataset['zpool'] + '/' + dataset['dsName']]) - if set_result.returncode != 0: - raise Exception("Failed to set zfs mountpoint") - if dataset['canMount'] == 'noauto': - mount_result = subprocess.run(['zfs', 'mount', dataset['zpool'] + '/' + dataset['dsName']]) - if mount_result.returncode != 0: - raise Exception("Failed to mount root dataset") - - # Set the canmount property for each dataset. This will effectively mount the dataset - for dataset in zfs: - # We already handled the / mountpoint above - if dataset['mountpoint'] != '/': - can_mount = parse_global_storage(dataset['canMount']) - - set_result = subprocess.run(['zfs', 'set', 'canmount=' + can_mount, - dataset['zpool'] + '/' + dataset['dsName']]) - if set_result.returncode != 0: - raise Exception("Failed to set zfs mountpoint") - def run(): """ diff --git a/src/modules/partition/gui/CreatePartitionDialog.cpp b/src/modules/partition/gui/CreatePartitionDialog.cpp index e7d7751a1..6bde9a148 100644 --- a/src/modules/partition/gui/CreatePartitionDialog.cpp +++ b/src/modules/partition/gui/CreatePartitionDialog.cpp @@ -255,11 +255,29 @@ CreatePartitionDialog::getNewlyCreatedPartition() m_parent, *m_device, m_role, fsType, fsLabel, first, last, PartitionTable::Flags() ); } - // For zfs, we let the zfs module handle the encryption but we need to make the passphrase available to that module - if( m_ui->encryptWidget->state() == EncryptWidget::Encryption::Confirmed && !luksPassphrase.isEmpty() - && fsType == FileSystem::Zfs ) + // For zfs, we let the zfs module handle the encryption but we need to make the passphrase available to later modules + if ( fsType == FileSystem::Zfs ) { - Calamares::JobQueue::instance()->globalStorage()->insert( "encryptphrase", luksPassphrase ); + Calamares::GlobalStorage* storage = Calamares::JobQueue::instance()->globalStorage(); + QList< QVariant > zfsInfoList; + QVariantMap zfsInfo; + + // If this is not the first encrypted zfs partition, get the old list first + if ( storage->contains( "zfsInfo" ) ) + { + zfsInfoList = storage->value( "zfsInfo" ).toList(); + storage->remove( "zfsInfo" ); + } + + // Save the information subsequent modules will need + zfsInfo[ "encrypted" ] + = m_ui->encryptWidget->state() == EncryptWidget::Encryption::Confirmed && !luksPassphrase.isEmpty(); + zfsInfo[ "passphrase" ] = luksPassphrase; + zfsInfo[ "mountpoint" ] = selectedMountPoint( m_ui->mountPointComboBox ); + + // Add it to the list and insert it into global storage + zfsInfoList.append( zfsInfo ); + storage->insert( "zfsInfo", zfsInfoList ); } if ( m_device->type() == Device::Type::LVM_Device ) diff --git a/src/modules/zfs/ZfsJob.cpp b/src/modules/zfs/ZfsJob.cpp index 8790b1fff..26d96183c 100644 --- a/src/modules/zfs/ZfsJob.cpp +++ b/src/modules/zfs/ZfsJob.cpp @@ -101,6 +101,8 @@ ZfsJob::exec() const CalamaresUtils::System* system = CalamaresUtils::System::instance(); + QVariantList poolNames; + for ( auto& partition : qAsConst( partitions ) ) { QVariantMap pMap; @@ -130,15 +132,48 @@ ZfsJob::exec() continue; } + QString mountpoint = pMap[ "mountPoint" ].toString(); + if ( mountpoint.isEmpty() ) + { + continue; + } + + // Build a poolname off the mountpoint, this is not ideal but should work until there is UI built for zfs + QString poolName = m_poolName; + if ( mountpoint != '/' ) + { + QString suffix = mountpoint; + poolName += suffix.remove( QRegExp( "[^a-zA-Z\\d\\s]" ) ); + } + + if ( !gs->contains( "zfsInfo" ) && gs->value( "zfsInfo" ).canConvert( QVariant::List ) ) + { + return Calamares::JobResult::error( tr( "Internal data missing" ), tr( "Failed to create zpool" ) ); + } + + QVariantList zfsInfoList = gs->value( "zfsInfo" ).toList(); + + bool encrypt = false; + QString passphrase; + for ( const QVariant& zfsInfo : qAsConst( zfsInfoList ) ) + { + if ( zfsInfo.canConvert( QVariant::Map ) && zfsInfo.toMap().value( "encrypted" ).toBool() + && mountpoint == zfsInfo.toMap().value( "mountpoint" ) ) + { + encrypt = true; + passphrase = zfsInfo.toMap().value( "passphrase" ).toString(); + } + } + ZfsResult zfsResult; - if ( gs->contains( "encryptphrase" ) ) + if ( encrypt ) { zfsResult - = CreateZpool( deviceName, m_poolName, m_poolOptions, true, gs->value( "encryptphrase" ).toString() ); + = CreateZpool( deviceName, poolName, m_poolOptions, true, passphrase ); } else { - zfsResult = CreateZpool( deviceName, m_poolName, m_poolOptions, false ); + zfsResult = CreateZpool( deviceName, poolName, m_poolOptions, false ); } if ( !zfsResult.success ) @@ -146,53 +181,88 @@ ZfsJob::exec() return Calamares::JobResult::error( tr( "Failed to create zpool" ), zfsResult.failureMessage ); } - // Create the datasets - QVariantList datasetList; - for ( const auto& dataset : qAsConst( m_datasets ) ) - { - QVariantMap datasetMap = dataset.toMap(); + // Add the poolname, dataset name and mountpoint to the list + QVariantMap poolNameEntry; + poolNameEntry["poolName"] = poolName; + poolNameEntry["mountpoint"] = mountpoint; + poolNameEntry["dsName"] = "none"; - // Make sure all values are valid - if ( datasetMap[ "dsName" ].toString().isEmpty() || datasetMap[ "mountpoint" ].toString().isEmpty() - || datasetMap[ "canMount" ].toString().isEmpty() ) + + if ( mountpoint == '/' ) + { + // Create the datasets + QVariantList datasetList; + for ( const auto& dataset : qAsConst( m_datasets ) ) { - cWarning() << "Bad dataset entry"; - continue; + QVariantMap datasetMap = dataset.toMap(); + + // Make sure all values are valid + if ( datasetMap[ "dsName" ].toString().isEmpty() || datasetMap[ "mountpoint" ].toString().isEmpty() + || datasetMap[ "canMount" ].toString().isEmpty() ) + { + cWarning() << "Bad dataset entry"; + continue; + } + + // Create the dataset. We set canmount=no regardless of the setting for now. + // It is modified to the correct value in the mount module to ensure mount order is maintained + auto r = system->runCommand( { "sh", + "-c", + "zfs create " + m_datasetOptions + " -o canmount=off -o mountpoint=" + + datasetMap[ "mountpoint" ].toString() + " " + poolName + "/" + + datasetMap[ "dsName" ].toString() }, + std::chrono::seconds( 10 ) ); + if ( r.getExitCode() != 0 ) + { + cWarning() << "Failed to create dataset" << datasetMap[ "dsName" ].toString(); + continue; + } + + // Add the dataset to the list for global storage + datasetMap[ "zpool" ] = m_poolName; + datasetList.append( datasetMap ); } + // If the list isn't empty, add it to global storage + if ( !datasetList.isEmpty() ) + { + gs->insert( "zfsDatasets", datasetList ); + } + } + else + { // Create the dataset. We set canmount=no regardless of the setting for now. // It is modified to the correct value in the mount module to ensure mount order is maintained + QString dsName = mountpoint; + dsName.remove( QRegExp( "[^a-zA-Z\\d\\s]" ) ); auto r = system->runCommand( { "sh", "-c", "zfs create " + m_datasetOptions + " -o canmount=off -o mountpoint=" - + datasetMap[ "mountpoint" ].toString() + " " + m_poolName + "/" - + datasetMap[ "dsName" ].toString() }, + + mountpoint + " " + poolName + "/" + + dsName }, std::chrono::seconds( 10 ) ); if ( r.getExitCode() != 0 ) { - cWarning() << "Failed to create dataset" << datasetMap[ "dsName" ].toString(); - continue; + cWarning() << "Failed to create dataset" << dsName; } - - // Add the dataset to the list for global storage - datasetMap[ "zpool" ] = m_poolName; - datasetList.append( datasetMap ); + poolNameEntry["dsName"] = dsName; } - // If the list isn't empty, add it to global storage - if ( !datasetList.isEmpty() ) - { - gs->insert( "zfs", datasetList ); - } + poolNames.append(poolNameEntry); // Export the zpool so it can be reimported at the correct location later - auto r = system->runCommand( { "zpool", "export", m_poolName }, std::chrono::seconds( 10 ) ); + auto r = system->runCommand( { "zpool", "export", poolName }, std::chrono::seconds( 10 ) ); if ( r.getExitCode() != 0 ) { cWarning() << "Failed to export pool" << m_poolName; } } + if (!poolNames.isEmpty()) + { + gs->insert("zfsPoolInfo", poolNames); + } + return Calamares::JobResult::ok(); } From 8bdfcac0fbae62623c2cedc92439d0472ea72865 Mon Sep 17 00:00:00 2001 From: dalto Date: Sat, 13 Nov 2021 09:31:23 -0600 Subject: [PATCH 027/127] [partition] Add support for zfs encryption when erase disk is selected --- src/modules/mount/main.py | 1 - .../partition/core/PartitionLayout.cpp | 22 ++++++++++++++++++- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/src/modules/mount/main.py b/src/modules/mount/main.py index 20815969a..f58b90e9f 100644 --- a/src/modules/mount/main.py +++ b/src/modules/mount/main.py @@ -118,7 +118,6 @@ def mount_partition(root_mount_point, partition, partitions): raise Exception("Internal error mounting zfs datasets") for zfs_pool in zfs_pool_list: - libcalamares.utils.warning("Poolname: " + zfs_pool["poolName"] + " mountpoint: " + zfs_pool["mountpoint"]) if zfs_pool["mountpoint"] == partition["mountPoint"]: pool_name = zfs_pool["poolName"] ds_name = zfs_pool["dsName"]; diff --git a/src/modules/partition/core/PartitionLayout.cpp b/src/modules/partition/core/PartitionLayout.cpp index 8ae904e92..f60952643 100644 --- a/src/modules/partition/core/PartitionLayout.cpp +++ b/src/modules/partition/core/PartitionLayout.cpp @@ -296,7 +296,9 @@ PartitionLayout::createPartitions( Device* dev, } Partition* part = nullptr; - if ( luksPassphrase.isEmpty() ) + + // Encryption for zfs is handled in the zfs module + if ( luksPassphrase.isEmpty() || correctFS( entry.partFileSystem ) == FileSystem::Zfs ) { part = KPMHelpers::createNewPartition( parent, *dev, @@ -319,6 +321,24 @@ PartitionLayout::createPartitions( Device* dev, luksPassphrase, KPM_PARTITION_FLAG( None ) ); } + + // For zfs, we need to make the passphrase available to later modules + if ( correctFS( entry.partFileSystem ) == FileSystem::Zfs ) + { + Calamares::GlobalStorage* storage = Calamares::JobQueue::instance()->globalStorage(); + QList< QVariant > zfsInfoList; + QVariantMap zfsInfo; + + // Save the information subsequent modules will need + zfsInfo[ "encrypted" ] = !luksPassphrase.isEmpty(); + zfsInfo[ "passphrase" ] = luksPassphrase; + zfsInfo[ "mountpoint" ] = entry.partMountPoint; + + // Add it to the list and insert it into global storage + zfsInfoList.append( zfsInfo ); + storage->insert( "zfsInfo", zfsInfoList ); + } + PartitionInfo::setFormat( part, true ); PartitionInfo::setMountPoint( part, entry.partMountPoint ); if ( !entry.partLabel.isEmpty() ) From cf20d6495bc72578fb2ba89509fd85b867c85aa7 Mon Sep 17 00:00:00 2001 From: dalto Date: Sat, 13 Nov 2021 10:43:07 -0600 Subject: [PATCH 028/127] [partition] Ensure format is selected for existing zfs partitions --- src/modules/partition/gui/EditExistingPartitionDialog.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/modules/partition/gui/EditExistingPartitionDialog.cpp b/src/modules/partition/gui/EditExistingPartitionDialog.cpp index 04b9527f1..a3052b3b7 100644 --- a/src/modules/partition/gui/EditExistingPartitionDialog.cpp +++ b/src/modules/partition/gui/EditExistingPartitionDialog.cpp @@ -120,6 +120,12 @@ EditExistingPartitionDialog::EditExistingPartitionDialog( Device* device, m_ui->fileSystemLabel->setEnabled( m_ui->formatRadioButton->isChecked() ); m_ui->fileSystemComboBox->setEnabled( m_ui->formatRadioButton->isChecked() ); + // Force a format if the existing device is a zfs device since reusing a zpool isn't currently supported + m_ui->formatRadioButton->setChecked( m_partition->fileSystem().type() == FileSystem::Type::Zfs ); + m_ui->formatRadioButton->setEnabled( !( m_partition->fileSystem().type() == FileSystem::Type::Zfs ) ); + m_ui->keepRadioButton->setChecked( !( m_partition->fileSystem().type() == FileSystem::Type::Zfs ) ); + m_ui->keepRadioButton->setEnabled( !( m_partition->fileSystem().type() == FileSystem::Type::Zfs ) ); + setFlagList( *( m_ui->m_listFlags ), m_partition->availableFlags(), PartitionInfo::flags( m_partition ) ); } From cca38695ed8ac877537cd995c03289498abbb9cc Mon Sep 17 00:00:00 2001 From: dalto Date: Sat, 13 Nov 2021 11:13:39 -0600 Subject: [PATCH 029/127] [umount] Export zpools after unmounting --- src/modules/umount/main.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/modules/umount/main.py b/src/modules/umount/main.py index 0035a6b0f..795eb8d2b 100644 --- a/src/modules/umount/main.py +++ b/src/modules/umount/main.py @@ -49,6 +49,26 @@ def list_mounts(root_mount_point): return lst +def export_zpools(root_mount_point): + """ Exports the zpools if defined in global storage + + :param root_mount_point: The absolute path to the root of the install + :return: + """ + try: + zfs_pool_list = libcalamares.globalstorage.value("zfsPoolInfo") + zfs_pool_list.sort(reverse=True, key=lambda x: x["poolName"]) + if zfs_pool_list: + for zfs_pool in zfs_pool_list: + import_result = subprocess.run(['zpool', 'export', root_mount_point, zfs_pool["poolName"]]) + if import_result.returncode != 0: + libcalamares.utils.warning("Failed to export zpool") + except Exception as e: + # If this fails it shouldn't cause the installation to fail + libcalamares.utils.warning("Received exception while exporting zpools: " + format(e)) + pass + + def run(): """ Unmounts given mountpoints in decreasing order. @@ -94,6 +114,8 @@ def run(): # in the exception object. subprocess.check_output(["umount", "-lv", mount_point], stderr=subprocess.STDOUT) + export_zpools(root_mount_point) + os.rmdir(root_mount_point) return None From c3524c07adf9574677fdffc062dd08425dcac819 Mon Sep 17 00:00:00 2001 From: dalto Date: Sat, 13 Nov 2021 13:43:26 -0600 Subject: [PATCH 030/127] [zfs] Ensure overlapping datasets don't get created and code cleanup --- src/modules/zfs/ZfsJob.cpp | 90 ++++++++++++++++++++++++++++---------- src/modules/zfs/ZfsJob.h | 35 +++++++++++++-- 2 files changed, 99 insertions(+), 26 deletions(-) diff --git a/src/modules/zfs/ZfsJob.cpp b/src/modules/zfs/ZfsJob.cpp index 26d96183c..d0367f5f6 100644 --- a/src/modules/zfs/ZfsJob.cpp +++ b/src/modules/zfs/ZfsJob.cpp @@ -32,6 +32,43 @@ ZfsJob::prettyName() const return tr( "Create ZFS pools and datasets" ); } +QString +ZfsJob::AlphaNumeric( QString input ) const +{ + return input.remove( QRegExp( "[^a-zA-Z\\d\\s]" ) ); +} + +void +ZfsJob::CollectMountpoints( const QVariantList& partitions ) +{ + m_mountpoints.empty(); + for ( const QVariant& partition : partitions ) + { + if ( partition.canConvert( QVariant::Map ) ) + { + QString mountpoint = partition.toMap().value( "mountPoint" ).toString(); + if ( !mountpoint.isEmpty() ) + { + m_mountpoints.append( mountpoint ); + } + } + } +} + +bool +ZfsJob::IsMountpointOverlapping( const QString& targetMountpoint ) const +{ + for ( const QString& mountpoint : m_mountpoints ) + { + if ( mountpoint != '/' && targetMountpoint.startsWith( mountpoint ) ) + { + return true; + } + } + return false; +} + + ZfsResult ZfsJob::CreateZpool( QString deviceName, QString poolName, QString poolOptions, bool encrypt, QString passphrase ) const { @@ -85,7 +122,7 @@ ZfsJob::CreateZpool( QString deviceName, QString poolName, QString poolOptions, Calamares::JobResult ZfsJob::exec() { - QList< QVariant > partitions; + QVariantList partitions; Calamares::GlobalStorage* gs = Calamares::JobQueue::instance()->globalStorage(); if ( gs && gs->contains( "partitions" ) && gs->value( "partitions" ).canConvert( QVariant::List ) ) { @@ -132,27 +169,28 @@ ZfsJob::exec() continue; } + // If the partition doesn't have a mountpoint, skip it QString mountpoint = pMap[ "mountPoint" ].toString(); if ( mountpoint.isEmpty() ) { continue; } - // Build a poolname off the mountpoint, this is not ideal but should work until there is UI built for zfs + // Build a poolname off config pool name and the mountpoint, this is not ideal but should work until there is UI built for zfs QString poolName = m_poolName; if ( mountpoint != '/' ) { - QString suffix = mountpoint; - poolName += suffix.remove( QRegExp( "[^a-zA-Z\\d\\s]" ) ); + poolName += AlphaNumeric( mountpoint ); } + // Check to ensure the list of zfs info from the partition module is available and convert it to a list if ( !gs->contains( "zfsInfo" ) && gs->value( "zfsInfo" ).canConvert( QVariant::List ) ) { return Calamares::JobResult::error( tr( "Internal data missing" ), tr( "Failed to create zpool" ) ); } - QVariantList zfsInfoList = gs->value( "zfsInfo" ).toList(); + // Look in the zfs info list to see if this partition should be encrypted bool encrypt = false; QString passphrase; for ( const QVariant& zfsInfo : qAsConst( zfsInfoList ) ) @@ -165,11 +203,11 @@ ZfsJob::exec() } } + // Create the zpool ZfsResult zfsResult; if ( encrypt ) { - zfsResult - = CreateZpool( deviceName, poolName, m_poolOptions, true, passphrase ); + zfsResult = CreateZpool( deviceName, poolName, m_poolOptions, true, passphrase ); } else { @@ -181,16 +219,17 @@ ZfsJob::exec() return Calamares::JobResult::error( tr( "Failed to create zpool" ), zfsResult.failureMessage ); } - // Add the poolname, dataset name and mountpoint to the list + // Save the poolname, dataset name and mountpoint. It will later be added to a list and placed in global storage. + // This will be used by later modules including mount and umount QVariantMap poolNameEntry; - poolNameEntry["poolName"] = poolName; - poolNameEntry["mountpoint"] = mountpoint; - poolNameEntry["dsName"] = "none"; - + poolNameEntry[ "poolName" ] = poolName; + poolNameEntry[ "mountpoint" ] = mountpoint; + poolNameEntry[ "dsName" ] = "none"; + // If the mountpoint is /, create datasets per the config file. If not, create a single dataset mounted at the partitions mountpoint if ( mountpoint == '/' ) { - // Create the datasets + CollectMountpoints( partitions ); QVariantList datasetList; for ( const auto& dataset : qAsConst( m_datasets ) ) { @@ -204,6 +243,12 @@ ZfsJob::exec() continue; } + // We should skip this dataset if it conflicts with a permanent mountpoint + if ( IsMountpointOverlapping( datasetMap[ "mountpoint" ].toString() ) ) + { + continue; + } + // Create the dataset. We set canmount=no regardless of the setting for now. // It is modified to the correct value in the mount module to ensure mount order is maintained auto r = system->runCommand( { "sh", @@ -218,7 +263,8 @@ ZfsJob::exec() continue; } - // Add the dataset to the list for global storage + // Add the dataset to the list for global storage this information is used later to properly set + // the mount options on each dataset datasetMap[ "zpool" ] = m_poolName; datasetList.append( datasetMap ); } @@ -231,24 +277,23 @@ ZfsJob::exec() } else { - // Create the dataset. We set canmount=no regardless of the setting for now. + // This is a zpool with a single dataset We again set canmount=no regardless of the desired setting. // It is modified to the correct value in the mount module to ensure mount order is maintained QString dsName = mountpoint; - dsName.remove( QRegExp( "[^a-zA-Z\\d\\s]" ) ); + dsName = AlphaNumeric( mountpoint ); auto r = system->runCommand( { "sh", "-c", "zfs create " + m_datasetOptions + " -o canmount=off -o mountpoint=" - + mountpoint + " " + poolName + "/" - + dsName }, + + mountpoint + " " + poolName + "/" + dsName }, std::chrono::seconds( 10 ) ); if ( r.getExitCode() != 0 ) { cWarning() << "Failed to create dataset" << dsName; } - poolNameEntry["dsName"] = dsName; + poolNameEntry[ "dsName" ] = dsName; } - poolNames.append(poolNameEntry); + poolNames.append( poolNameEntry ); // Export the zpool so it can be reimported at the correct location later auto r = system->runCommand( { "zpool", "export", poolName }, std::chrono::seconds( 10 ) ); @@ -258,9 +303,10 @@ ZfsJob::exec() } } - if (!poolNames.isEmpty()) + // Put the list of zpools into global storage + if ( !poolNames.isEmpty() ) { - gs->insert("zfsPoolInfo", poolNames); + gs->insert( "zfsPoolInfo", poolNames ); } return Calamares::JobResult::ok(); diff --git a/src/modules/zfs/ZfsJob.h b/src/modules/zfs/ZfsJob.h index b2feb9e87..4744954c2 100644 --- a/src/modules/zfs/ZfsJob.h +++ b/src/modules/zfs/ZfsJob.h @@ -20,7 +20,8 @@ #include "DllMacro.h" -struct ZfsResult { +struct ZfsResult +{ bool success; QString failureMessage; }; @@ -46,12 +47,12 @@ private: QString m_poolName; QString m_poolOptions; QString m_datasetOptions; + QStringList m_mountpoints; - QList m_datasets; + QList< QVariant > m_datasets; /** @brief Creates a zpool based on the provided arguments * - * Creates a zpool * @p deviceName is a full path to the device the zpool should be created on * @p poolName is a string containing the name of the pool to create * @p poolOptions are the options to pass to zpool create @@ -59,9 +60,35 @@ private: * @p passphrase is a string continaing the passphrase * */ - ZfsResult CreateZpool(QString deviceName, QString poolName, QString poolOptions, bool encrypt, QString passphrase = QString() ) const; + ZfsResult CreateZpool( QString deviceName, + QString poolName, + QString poolOptions, + bool encrypt, + QString passphrase = QString() ) const; + /** @brief Returns the alphanumeric portion of a string + * + * @p input is the input string + * + */ + QString AlphaNumeric( QString input ) const; + /** @brief Collects all the mountpoints from the partitions + * + * Iterates over @p partitions to gather each mountpoint present + * in the list of maps and populates m_mountpoints + * + */ + void CollectMountpoints( const QVariantList& partitions ); + + /** @brief Check to see if a given mountpoint overlaps with one of the defined moutnpoints + * + * Iterates over m_partitions and checks if @p targetMountpoint overlaps with them by comparing + * the beginning of targetMountpoint with all the values in m_mountpoints. Of course, / is excluded + * since all the mountpoints would begin with / + * + */ + bool IsMountpointOverlapping( const QString& targetMountpoint ) const; }; CALAMARES_PLUGIN_FACTORY_DECLARATION( ZfsJobFactory ) From af4b87a4cccd14548a0cfac47aa70ce8dd56f412 Mon Sep 17 00:00:00 2001 From: dalto Date: Sat, 13 Nov 2021 14:09:16 -0600 Subject: [PATCH 031/127] [mount] Move zfs code into a seperate function to improve readability --- src/modules/mount/main.py | 148 ++++++++++++++++++++------------------ 1 file changed, 80 insertions(+), 68 deletions(-) diff --git a/src/modules/mount/main.py b/src/modules/mount/main.py index f58b90e9f..72c2015c6 100644 --- a/src/modules/mount/main.py +++ b/src/modules/mount/main.py @@ -62,10 +62,9 @@ def get_btrfs_subvolumes(partitions): def parse_global_storage(gs_value): - """ - Something in the chain is converting on and off to true and false. This converts it back. + """ Something in the chain is converting on and off to true and false. This converts it back. - :param gs_value: The value from global storage + :param gs_value: The value from global storage which needs to be fixed :return: """ if gs_value is True: @@ -76,6 +75,83 @@ def parse_global_storage(gs_value): return gs_value +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 Exception("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 + import_result = subprocess.run(['zpool', 'import', '-R', root_mount_point, pool_name]) + if import_result.returncode != 0: + raise Exception("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 + loadkey_result = subprocess.run(['sh', '-c', 'echo "' + passphrase + '" | zfs load-key ' + pool_name]) + if loadkey_result.returncode != 0: + raise Exception("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 Exception("Internal error mounting zfs datasets") + + # first we handle the / dataset if there is one + for dataset in zfs: + if dataset['mountpoint'] == '/': + # Properly set the canmount field from global storage + can_mount = parse_global_storage(dataset['canMount']) + set_result = subprocess.run(['zfs', 'set', 'canmount=' + can_mount, + dataset['zpool'] + '/' + dataset['dsName']]) + if set_result.returncode != 0: + raise Exception("Failed to set zfs mountpoint") + if dataset['canMount'] == 'noauto': + mount_result = subprocess.run(['zfs', 'mount', dataset['zpool'] + '/' + dataset['dsName']]) + if mount_result.returncode != 0: + raise Exception("Failed to mount root dataset") + + # Set the canmount property for each dataset. This will effectively mount the dataset + for dataset in zfs: + # We already handled the / mountpoint above + if dataset['mountpoint'] != '/': + can_mount = parse_global_storage(dataset['canMount']) + + set_result = subprocess.run(['zfs', 'set', 'canmount=' + can_mount, + dataset['zpool'] + '/' + dataset['dsName']]) + if set_result.returncode != 0: + raise Exception("Failed to set zfs mountpoint") + else: + set_result = subprocess.run(['zfs', 'set', 'canmount=on', pool_name + '/' + ds_name]) + if set_result.returncode != 0: + raise Exception("Failed to set zfs mountpoint") + + def mount_partition(root_mount_point, partition, partitions): """ Do a single mount of @p partition inside @p root_mount_point. @@ -112,71 +188,7 @@ def mount_partition(root_mount_point, partition, partitions): device = os.path.join("/dev/mapper", partition["luksMapperName"]) if fstype == "zfs": - zfs_pool_list = libcalamares.globalstorage.value("zfsPoolInfo") - if not zfs_pool_list: - libcalamares.utils.warning("Failed to locate zfsPoolInfo data in global storage") - raise Exception("Internal error mounting zfs datasets") - - 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 - import_result = subprocess.run(['zpool', 'import', '-R', root_mount_point, pool_name]) - if import_result.returncode != 0: - raise Exception("Failed to import zpool") - - 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 - loadkey_result = subprocess.run(['sh', '-c', 'echo "' + passphrase + '" | zfs load-key ' + pool_name]) - if loadkey_result.returncode != 0: - raise Exception("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 Exception("Internal error mounting zfs datasets") - - # first we handle the / dataset if there is one - for dataset in zfs: - if dataset['mountpoint'] == '/': - # Properly set the canmount field from global storage - can_mount = parse_global_storage(dataset['canMount']) - set_result = subprocess.run(['zfs', 'set', 'canmount=' + can_mount, - dataset['zpool'] + '/' + dataset['dsName']]) - if set_result.returncode != 0: - raise Exception("Failed to set zfs mountpoint") - if dataset['canMount'] == 'noauto': - mount_result = subprocess.run(['zfs', 'mount', dataset['zpool'] + '/' + dataset['dsName']]) - if mount_result.returncode != 0: - raise Exception("Failed to mount root dataset") - - # Set the canmount property for each dataset. This will effectively mount the dataset - for dataset in zfs: - # We already handled the / mountpoint above - if dataset['mountpoint'] != '/': - can_mount = parse_global_storage(dataset['canMount']) - - set_result = subprocess.run(['zfs', 'set', 'canmount=' + can_mount, - dataset['zpool'] + '/' + dataset['dsName']]) - if set_result.returncode != 0: - raise Exception("Failed to set zfs mountpoint") - else: - set_result = subprocess.run(['zfs', 'set', 'canmount=on', pool_name + '/' + ds_name]) - if set_result.returncode != 0: - raise Exception("Failed to set zfs mountpoint") + mount_zfs(root_mount_point, partition) else: # fstype == "zfs" if libcalamares.utils.mount(device, mount_point, From b6692341e7bc2d7444620cde0f52dffd69eba011 Mon Sep 17 00:00:00 2001 From: dalto Date: Sun, 14 Nov 2021 09:07:58 -0600 Subject: [PATCH 032/127] [fstab] Exclude zfs partitions from fstab --- src/modules/fstab/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/fstab/main.py b/src/modules/fstab/main.py index 5312e7f5b..8ca4cf557 100644 --- a/src/modules/fstab/main.py +++ b/src/modules/fstab/main.py @@ -196,7 +196,7 @@ class FstabGenerator(object): dct = self.generate_fstab_line_info(mount_entry) if dct: self.print_fstab_line(dct, file=fstab_file) - else: + 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) From b0436bf0502664479ac8c82a94f8b229f2b895b7 Mon Sep 17 00:00:00 2001 From: Calamares CI Date: Mon, 15 Nov 2021 10:58:51 +0100 Subject: [PATCH 033/127] i18n: [calamares] Automatic merge of Transifex translations --- lang/calamares_az.ts | 10 +- lang/calamares_az_AZ.ts | 10 +- lang/calamares_ko.ts | 10 +- lang/calamares_si.ts | 1028 ++++++++++++++++++++------------------- lang/calamares_tr_TR.ts | 22 +- 5 files changed, 563 insertions(+), 517 deletions(-) diff --git a/lang/calamares_az.ts b/lang/calamares_az.ts index 2cd7939e6..2e0af2c63 100644 --- a/lang/calamares_az.ts +++ b/lang/calamares_az.ts @@ -689,27 +689,27 @@ Bu proqramdan çıxılacaq və bütün dəyişikliklər itiriləcəkdir. Successfully unmounted %1. - + %1 uğurla ayrıldı.
Successfully disabled swap %1. - + %1 mübadilə bölməsi uğurla söndürüldü. Successfully cleared swap %1. - + %1 mübadilə bölməsi uğurla təmizləndi Successfully closed mapper device %1. - + Yerləşdirmə cihazı %1 uğurla bağlandı Successfully disabled volume group %1. - + Tutum qrupu %1, uğurla söndürüldü diff --git a/lang/calamares_az_AZ.ts b/lang/calamares_az_AZ.ts index 1899e20a0..d3d89768a 100644 --- a/lang/calamares_az_AZ.ts +++ b/lang/calamares_az_AZ.ts @@ -689,27 +689,27 @@ Bu proqramdan çıxılacaq və bütün dəyişikliklər itiriləcəkdir. Successfully unmounted %1. - + %1 uğurla ayrıldı. Successfully disabled swap %1. - + %1 mübadilə bölməsi uğurla söndürüldü. Successfully cleared swap %1. - + %1 mübadilə bölməsi uğurla təmizləndi Successfully closed mapper device %1. - + Yerləşdirmə cihazı %1 uğurla bağlandı Successfully disabled volume group %1. - + Tutum qrupu %1, uğurla söndürüldü diff --git a/lang/calamares_ko.ts b/lang/calamares_ko.ts index d93bde150..607f34e6c 100644 --- a/lang/calamares_ko.ts +++ b/lang/calamares_ko.ts @@ -687,27 +687,27 @@ The installer will quit and all changes will be lost. Successfully unmounted %1. - + %1을(를) 성공적으로 마운트 해제했습니다. Successfully disabled swap %1. - + 스왑% 1을(를) 성공적으로 비활성화했습니다. Successfully cleared swap %1. - + 스왑 %1을(를) 성공적으로 지웠습니다. Successfully closed mapper device %1. - + 매퍼 장치 %1을(를) 성공적으로 닫았습니다. Successfully disabled volume group %1. - + 볼륨 그룹 %1을(를) 성공적으로 비활성화했습니다. diff --git a/lang/calamares_si.ts b/lang/calamares_si.ts index 309b4a953..3d68cc26e 100644 --- a/lang/calamares_si.ts +++ b/lang/calamares_si.ts @@ -689,27 +689,27 @@ The installer will quit and all changes will be lost. Successfully unmounted %1. - + %1 සාර්ථකව ඉවත් කරන ලදී. Successfully disabled swap %1. - + swap % 1 සාර්ථකව අක්‍රීය කරන ලදී. Successfully cleared swap %1. - + swap %1 සාර්ථකව හිස් කරන ලදී. Successfully closed mapper device %1. - + සිතියම් උපාංගය %1 සාර්ථකව වසා ඇත. Successfully disabled volume group %1. - + %1 වෙළුම් සමූහය සාර්ථකව ක්‍රියා කරයි. @@ -1315,17 +1315,17 @@ The installer will quit and all changes will be lost. <br><br>This is the recommended partition table type for modern systems which start from an <strong>EFI</strong> boot environment. - + <br><br><strong>EFI</strong> ඇරඹුම් පරිසරයකින් ආරම්භ වන නවීන පද්ධති සඳහා නිර්දේශිත කොටස් වගු වර්ගය මෙයයි. <br><br>This partition table type is only advisable on older systems which start from a <strong>BIOS</strong> boot environment. GPT is recommended in most other cases.<br><br><strong>Warning:</strong> the MBR partition table is an obsolete MS-DOS era standard.<br>Only 4 <em>primary</em> partitions may be created, and of those 4, one can be an <em>extended</em> partition, which may in turn contain many <em>logical</em> partitions. - + <br><br>මෙම කොටස් වගු වර්ගය සුදුසු වන්නේ <strong>BIOS</strong> ඇරඹුම් පරිසරයකින් ආරම්භ වන පැරණි පද්ධති සඳහා පමණි. අනෙකුත් බොහෝ අවස්ථාවන්හිදී GPT නිර්දේශ කෙරේ. <br><br><strong>අවවාදයයි:</strong> MBR කොටස් වගුව යල් පැන ගිය MS-DOS යුගයේ සම්මතයකි. <br><em>ප්‍රධාන</em> කොටස් 4ක් පමණක් සෑදිය හැකි අතර, එම 4න් එකක් <strong>දීර්ඝ</strong> කළ කොටසක් විය හැක, එහි බොහෝ <strong>තාර්කික</strong> කොටස් අඩංගු විය හැක. The type of <strong>partition table</strong> on the selected storage device.<br><br>The only way to change the partition table type is to erase and recreate the partition table from scratch, which destroys all data on the storage device.<br>This installer will keep the current partition table unless you explicitly choose otherwise.<br>If unsure, on modern systems GPT is preferred. - + තෝරාගත් ගබඩා උපාංගයේ <strong>කොටස් වගුවේ</strong> වර්ගය. <br>කොටස් වගු වර්ගය වෙනස් කිරීමට ඇති එකම ක්‍රමය නම් ගබඩා උපාංගයේ ඇති සියලුම දත්ත විනාශ කරන කොටස් වගුව මුල සිට මකා ප්‍රතිනිර්මාණය කිරීමයි. <br>මෙම ස්ථාපකය ඔබ වෙනත් ආකාරයකින් තෝරා ගන්නේ නම් මිස වත්මන් කොටස් වගුව තබා ගනී. <br>විශ්වාස නැත්නම්, නවීන පද්ධති GPT මත මනාප වේ. @@ -1348,17 +1348,17 @@ The installer will quit and all changes will be lost. Write LUKS configuration for Dracut to %1 - + Dracut සඳහා LUKS වින්‍යාසය %1 වෙත ලියන්න Skip writing LUKS configuration for Dracut: "/" partition is not encrypted - + Dracut සඳහා LUKS වින්‍යාසය ලිවීම මඟ හරින්න: "/" කොටස සංකේතනය කර නොමැත Failed to open %1 - + %1 විවෘත කිරීමට අසමත් විය @@ -1366,7 +1366,7 @@ The installer will quit and all changes will be lost. Dummy C++ Job - + ව්‍යාජ C++ ක්‍රියවලියක් @@ -1374,27 +1374,27 @@ The installer will quit and all changes will be lost. Edit Existing Partition - + පවතින කොටසක් සංස්කරණය කරන්න Content: - + අන්තර්ගතය: &Keep - + තබා ගන්න (&K) Format - + මකා දමන්න Warning: Formatting the partition will erase all existing data. - + අවවාදයයි: කොටස මැකීමෙන් පවතින සියලුම දත්ත ඉවත්වනු ඇත. @@ -1447,23 +1447,23 @@ The installer will quit and all changes will be lost. En&crypt system - + පද්ධතිය සංකේතනය (&C) කරන්න Passphrase - + මුරපදය Confirm passphrase - + මුරපදය තහවුරු කරන්න Please enter the same passphrase in both boxes. - + කරුණාකර කොටු දෙකෙහිම එකම මුර-වැකිකඩ ඇතුලත් කරන්න. @@ -1471,57 +1471,57 @@ The installer will quit and all changes will be lost. Set partition information - + කොටස් තොරතුරු සකසන්න Install %1 on <strong>new</strong> %2 system partition with features <em>%3</em> - + <strong>%3</strong> විශේෂාංග සහිත <strong>නව</strong> %2 පද්ධති කොටසේ %1 ස්ථාපනය කරන්න Install %1 on <strong>new</strong> %2 system partition. - + <strong>නව</strong> %2 පද්ධති කොටසෙහි %1 ස්ථාපනය කරන්න. Set up <strong>new</strong> %2 partition with mount point <strong>%1</strong> and features <em>%3</em>. - + <strong>නව</strong> %2 කොටස සවිකිරීමේ ලක්ෂ්‍යය <strong>%1</strong> සහ විශේෂාංග <strong>%3</strong> සමඟ සකසන්න Set up <strong>new</strong> %2 partition with mount point <strong>%1</strong>%3. - + <strong>නව</strong> %2 කොටස සවිකිරීමේ ලක්ෂ්‍යය <strong>%1</strong>%3 සමඟ සකසන්න. Install %2 on %3 system partition <strong>%1</strong> with features <em>%4</em>. - + <strong>%4</strong> විශේෂාංග සහිත %3 පද්ධති කොටස <strong>%1</strong> මත %2 ස්ථාපනය කරන්න. Set up %3 partition <strong>%1</strong> with mount point <strong>%2</strong> and features <em>%4</em>. - + %3 කොටස සකසන්න <strong>%1</strong> සවිකිරීමේ ලක්ෂ්‍යය <strong>%2</strong> සහ විශේෂාංග <strong>%4</strong> සමඟින්. Set up %3 partition <strong>%1</strong> with mount point <strong>%2</strong>%4. - + %3 කොටස <strong>%1</strong> සවිකිරීමේ ලක්ෂ්‍යය <strong>%2</strong>%4 සමඟ සකසන්න. Install %2 on %3 system partition <strong>%1</strong>. - + %3 පද්ධති කොටස <strong>%1</strong> මත %2 ස්ථාපනය කරන්න. Install boot loader on <strong>%1</strong>. - + <strong>%1</strong> මත ඇරඹුම් කාරකය ස්ථාපනය කරන්න. Setting up mount points. - + සවි කිරීම් ස්ථාන සැකසීම. @@ -1534,37 +1534,37 @@ The installer will quit and all changes will be lost. &Restart now - + නැවත ආරම්භ කරන්න (&R) <h1>All done.</h1><br/>%1 has been set up on your computer.<br/>You may now start using your new system. - + <strong>සියල්ල සාර්ථකව අවසන් විය</strong>.<br>%1 ඔබගේ පරිගණකයේ පිහිටුවා ඇත.<br>ඔබට දැන් ඔබගේ නව පද්ධතිය භාවිතා කිරීමට පටන් ගත හැක. <html><head/><body><p>When this box is checked, your system will restart immediately when you click on <span style="font-style:italic;">Done</span> or close the setup program.</p></body></html> - + <html><head/><body><p>මෙම කොටුව සලකුණු කළ විට, ඔබ <strong>Done</strong> මත ක්ලික් කළ විට හෝ සැකසුම් වැඩසටහන වසා දැමූ විට ඔබේ පද්ධතිය වහාම නැවත ආරම්භ වනු ඇත.</p></body></html> <h1>All done.</h1><br/>%1 has been installed on your computer.<br/>You may now restart into your new system, or continue using the %2 Live environment. - + <strong>සියල්ල සාර්ථකව අවසන් විය</strong>.<br>%1 ඔබගේ පරිගණකයේ ස්ථාපනය කර ඇත.<br>ඔබට දැන් ඔබගේ නව පද්ධතියට නැවත ආරම්භ කළ හැක, නැතහොත් %2 සජීවී පරිසරය භාවිතා කිරීම දිගටම කරගෙනයා හැක. <html><head/><body><p>When this box is checked, your system will restart immediately when you click on <span style="font-style:italic;">Done</span> or close the installer.</p></body></html> - + <html><head/><body><p>මෙම කොටුව සලකුණු කළ විට, ඔබ <strong>Done</strong> මත ක්ලික් කළ විට හෝ ස්ථාපක වැඩසටහන වසා දැමූ විට ඔබේ පද්ධතිය වහාම නැවත ආරම්භ වනු ඇත.</p></body></html> <h1>Setup Failed</h1><br/>%1 has not been set up on your computer.<br/>The error message was: %2. - + <strong>පිහිටුවීම අසාර්ථක විය</strong><br>% 1 ඔබේ පරිගණකයේ පිහිටුවා නැත. <br>දෝෂ පණිවිඩය වූයේ: %2. <h1>Installation Failed</h1><br/>%1 has not been installed on your computer.<br/>The error message was: %2. - + <strong>ස්ථාපනය අසාර්ථක විය</strong><br>%1 ඔබේ පරිගණකයේ ස්ථාපනය කර නැත. <br>දෝෂ පණිවිඩය වූයේ: %2. @@ -1572,7 +1572,7 @@ The installer will quit and all changes will be lost. Finish - + අවසන් @@ -1580,7 +1580,7 @@ The installer will quit and all changes will be lost. Finish - + අවසන් @@ -1588,22 +1588,22 @@ The installer will quit and all changes will be lost. Format partition %1 (file system: %2, size: %3 MiB) on %4. - + %4 මත කොටස %1 (ගොනු පද්ධතිය: %2, ප්‍රමාණය: %3 MiB) ආකෘතිකරණය කරන්න. Format <strong>%3MiB</strong> partition <strong>%1</strong> with file system <strong>%2</strong>. - + <strong>%3MiB</strong> කොටස <strong>%1</strong> ගොනු පද්ධතිය <strong>%2</strong> සමඟ ආකෘති කරන්න. Formatting partition %1 with file system %2. - + %2 ගොනු පද්ධතිය සමඟ %1 කොටස හැඩතල ගැන්වීම. The installer failed to format partition %1 on disk '%2'. - + ස්ථාපකය '%2' තැටියේ %1 කොටස හැඩතල ගැන්වීමට අසමත් විය. @@ -1611,72 +1611,72 @@ The installer will quit and all changes will be lost. has at least %1 GiB available drive space - + අවම වශයෙන් %1 GiB ලබා ගත හැකි ධාවකයේ ඉඩක් ඇත There is not enough drive space. At least %1 GiB is required. - + ප්‍රමාණවත් ධාවන ඉඩක් නොමැත. අවම වශයෙන් %1 GiB අවශ්‍ය වේ. has at least %1 GiB working memory - + අවම වශයෙන් %1 GiB ක්‍රියාකාරී මතකයක් ඇත The system does not have enough working memory. At least %1 GiB is required. - + පද්ධතියට ප්රමාණවත් ක්රියාකාරී මතකයක් නොමැත. අවම වශයෙන් %1 GiB අවශ්‍ය වේ. is plugged in to a power source - + විදුලි ප්‍රභවයකට සම්බන්ධ කර ඇත The system is not plugged in to a power source. - + පද්ධතිය විදුලි ප්‍රභවයකට සම්බන්ධ කර නොමැත. is connected to the Internet - + අන්තර්ජාලයට සම්බන්ධවී ඇත The system is not connected to the Internet. - + පද්ධතිය අන්තර්ජාලයට සම්බන්ධවී නොමැත. is running the installer as an administrator (root) - + ස්ථාපකය පරිපාලකයෙකු ලෙස ධාවනය කරයි (root) The setup program is not running with administrator rights. - + සැකසුම් වැඩසටහන පරිපාලක අයිතිවාසිකම් සමඟ ක්‍රියාත්මක නොවේ. The installer is not running with administrator rights. - + ස්ථාපකය පරිපාලක අයිතිවාසිකම් සමඟ ක්‍රියාත්මක නොවේ. has a screen large enough to show the whole installer - + සම්පූර්ණ ස්ථාපකය පෙන්වීමට තරම් විශාල තිරයක් ඇත The screen is too small to display the setup program. - + සැකසුම් වැඩසටහන ප්‍රදර්ශනය කිරීමට තිරය කුඩා වැඩිය. The screen is too small to display the installer. - + ස්ථාපකය වෙත පෙන්වීමට තිරය කුඩා වැඩිය. @@ -1684,7 +1684,7 @@ The installer will quit and all changes will be lost. Collecting information about your machine. - + ඔබගේ යන්ත්‍රය පිළිබඳ තොරතුරු රැස් කරමින් සිටී. @@ -1695,22 +1695,22 @@ The installer will quit and all changes will be lost. OEM Batch Identifier - + OEM කණ්ඩායම් හැඳුනුම්කාරකය Could not create directories <code>%1</code>. - + <code>%1</code> ගොනු තැනීමට නොහැකි විය. Could not open file <code>%1</code>. - + <code>%1</code> ගොනුව විවෘත කිරීමට නොහැකි විය. Could not write to file <code>%1</code>. - + <code>%1</code> ගොනුවට ලිවීමට නොහැකි විය. @@ -1718,7 +1718,7 @@ The installer will quit and all changes will be lost. Creating initramfs with mkinitcpio. - + mkinitcpio සමඟ initramfs නිර්මාණය කිරීම. @@ -1726,7 +1726,7 @@ The installer will quit and all changes will be lost. Creating initramfs. - + initramfs නිර්මාණය කිරීම. @@ -1734,17 +1734,17 @@ The installer will quit and all changes will be lost. Konsole not installed - + කොන්සෝල් ස්ථාපනය කර නැත Please install KDE Konsole and try again! - + කරුණාකර KDE කොන්සෝල් ස්ථාපනය කර නැවත උත්සාහ කරන්න! Executing script: &nbsp;<code>%1</code> - + ස්ක්‍රිප්ට් ක්‍රියාත්මක කරමින්: &nbsp;<code>%1</code><code> @@ -1752,7 +1752,7 @@ The installer will quit and all changes will be lost. Script - + ස්ක්‍රප්ට් @@ -1776,12 +1776,12 @@ The installer will quit and all changes will be lost. System locale setting - + පද්ධති ස්ථාන සැකසීම The system locale setting affects the language and character set for some command line user interface elements.<br/>The current setting is <strong>%1</strong>. - + පද්ධති පෙදෙසි සැකසුම සමහර විධාන රේඛා පරිශීලක අතුරුමුහුණත් මූලද්‍රව්‍ය සඳහා භාෂාව සහ අක්ෂර කට්ටලයට බලපායි. <br/>වත්මන් සැකසුම <strong>%1</strong> වේ. @@ -1791,7 +1791,7 @@ The installer will quit and all changes will be lost. &OK - + හරි (&O) @@ -1804,37 +1804,37 @@ The installer will quit and all changes will be lost. <h1>License Agreement</h1> - + <h1>බලපත්ර එකගතාවය</h1> I accept the terms and conditions above. - + මම ඉහත නියමයන් සහ කොන්දේසි පිළිගනිමි. Please review the End User License Agreements (EULAs). - + කරුණාකර අවසන් පරිශීලක බලපත්‍ර ගිවිසුම් (EULAs) සමාලෝචනය කරන්න. This setup procedure will install proprietary software that is subject to licensing terms. - + මෙම සැකසුම් ක්‍රියා පටිපාටිය බලපත්‍ර කොන්දේසි වලට යටත් වන හිමිකාර මෘදුකාංග ස්ථාපනය කරනු ඇත. If you do not agree with the terms, the setup procedure cannot continue. - + ඔබ නියමයන් සමඟ එකඟ නොවන්නේ නම්, සැකසුම් ක්‍රියා පටිපාටිය දිගටම කරගෙන යා නොහැක. This setup procedure can install proprietary software that is subject to licensing terms in order to provide additional features and enhance the user experience. - + මෙම සැකසුම් ක්‍රියා පටිපාටියට අමතර විශේෂාංග සැපයීමට සහ පරිශීලක අත්දැකීම වැඩිදියුණු කිරීමට බලපත්‍ර නියමයන්ට යටත් වන හිමිකාර මෘදුකාංග ස්ථාපනය කළ හැක. If you do not agree with the terms, proprietary software will not be installed, and open source alternatives will be used instead. - + ඔබ නියමයන් සමඟ එකඟ නොවන්නේ නම්, හිමිකාර මෘදුකාංග ස්ථාපනය නොකරනු ඇති අතර, ඒ වෙනුවට විවෘත මූලාශ්‍ර විකල්ප භාවිතා කරනු ඇත. @@ -1856,33 +1856,33 @@ The installer will quit and all changes will be lost. <strong>%1 driver</strong><br/>by %2 %1 is an untranslatable product name, example: Creative Audigy driver - + <strong>%1 ධාවකය</strong><br/>%2 කින් <strong>%1 graphics driver</strong><br/><font color="Grey">by %2</font> %1 is usually a vendor name, example: Nvidia graphics driver - + <strong>%1 චිත්‍රක ධාවකය</strong><br/><font color="Grey">%2 කින්</font> <strong>%1 browser plugin</strong><br/><font color="Grey">by %2</font> - + <strong>%1 බ්‍රවුසර ප්ලගිනය</strong><br/><font color="Grey"> %2 කින්</font> <strong>%1 codec</strong><br/><font color="Grey">by %2</font> - + <strong>%1 කෝඩෙක්</strong><br/><font color="Grey">%2 කින්</font> <strong>%1 package</strong><br/><font color="Grey">by %2</font> - + <strong>%1 පැකේජය</strong><br><font color="Grey">%2 කින්</font> <strong>%1</strong><br/><font color="Grey">by %2</font> - + <strong>%1</strong><br/><font color="Grey">%2 කින්</font> @@ -1892,17 +1892,17 @@ The installer will quit and all changes will be lost. Hide license text - + බලපත්‍ර පෙළ සඟවන්න Show the license text - + බලපත්ර පාඨය පෙන්වන්න Open license agreement in browser. - + බ්‍රවුසරයේ බලපත්‍ර ගිවිසුම විවෘත කරන්න. @@ -1910,12 +1910,12 @@ The installer will quit and all changes will be lost. Region: - + කලාපයේ: Zone: - + කලාපය: @@ -1953,35 +1953,35 @@ The installer will quit and all changes will be lost. Configuring LUKS key file. - + LUKS යතුරු ගොනුව වින්‍යාස කරමින්. No partitions are defined. - + කොටස් නිර්වචනය කර නොමැත. Encrypted rootfs setup error - + සංකේතනය කරන ලද rootfs පිහිටුවීමේ දෝෂයකි Root partition %1 is LUKS but no passphrase has been set. - + මූල කොටස %1 LUKS වන නමුත් මුර-වැකිකඩක් සකසා නොමැත. Could not create LUKS key file for root partition %1. - + මූල කොටස %1 සඳහා LUKS යතුරු ගොනුව සෑදිය නොහැක. Could not configure LUKS key file on partition %1. - + %1 කොටසේ LUKS යතුරු ගොනුව වින්‍යාස කිරීමට නොහැකි විය. @@ -1989,17 +1989,17 @@ The installer will quit and all changes will be lost. Generate machine-id. - + යන්ත්‍ර හැඳුනුම්පත ජනනය කරන්න. Configuration Error - + වින්‍යාස දෝෂය No root mount point is set for MachineId. - + MachineId සඳහා root mount point එකක් සකසා නැත. @@ -2007,14 +2007,16 @@ The installer will quit and all changes will be lost. Timezone: %1 - + වේලා කලාපය:% 1 Please select your preferred location on the map so the installer can suggest the locale and timezone settings for you. You can fine-tune the suggested settings below. Search the map by dragging to move and using the +/- buttons to zoom in/out or use mouse scrolling for zooming. - + කරුණාකර ස්ථාපකයට පෙදෙසිය යෝජනා කළ හැකි වන පරිදි සිතියමේ ඔබ කැමති ස්ථානය තෝරන්න + සහ ඔබ සඳහා වේලා කලාප සැකසීම්. ඔබට පහත යෝජිත සැකසුම් මනාව සකස් කළ හැක. ඇදගෙන යාමෙන් සිතියම සොයන්න + චලනය කිරීමට සහ විශාලනය කිරීමට හෝ විශාලනය කිරීම සඳහා මූසික අනුචලනය භාවිතා කිරීමට +/- බොත්තම් භාවිතා කරන්න. @@ -2052,7 +2054,7 @@ The installer will quit and all changes will be lost. Kernel - + කර්නලය @@ -2062,12 +2064,12 @@ The installer will quit and all changes will be lost. Login - + පිවිසෙන්න Desktop - + ඩෙස්ක්ටොප් @@ -2102,17 +2104,17 @@ The installer will quit and all changes will be lost. Theming - + තේමා කිරීම Gaming - + පරිගණක ක්රීඩා Utilities - + උපයෝගිතා @@ -2128,17 +2130,17 @@ The installer will quit and all changes will be lost. Ba&tch: - + කණ්ඩායම(&t): <html><head/><body><p>Enter a batch-identifier here. This will be stored in the target system.</p></body></html> - + <html><head/><body><p>මෙහි batch-identifier එකක් ඇතුළු කරන්න. මෙය ඉලක්ක පද්ධතිය තුළ ගබඩා කරනු ලැබේ.</p></body></html> <html><head/><body><h1>OEM Configuration</h1><p>Calamares will use OEM settings while configuring the target system.</p></body></html> - + <html><head/><body><h1>OEM වින්‍යාසය</h1><p>Calamares ඉලක්ක පද්ධතිය වින්‍යාස කිරීමේදී OEM සැකසුම් භාවිතා කරනු ඇත.</p></body></html> @@ -2146,12 +2148,12 @@ The installer will quit and all changes will be lost. OEM Configuration - + OEM වින්‍යාසය Set the OEM Batch Identifier to <code>%1</code>. - + OEM Batch Identifier <code>%1</code> ලෙස සකසන්න. @@ -2159,29 +2161,29 @@ The installer will quit and all changes will be lost. Select your preferred Region, or use the default settings. - + ඔබ කැමති කලාපය තෝරන්න, නැතහොත් පෙරනිමි සැකසුම් භාවිතා කරන්න. Timezone: %1 - + වේලා කලාපය:% 1 Select your preferred Zone within your Region. - + ඔබ කැමති කලාපය තෝරන්න. Zones - + කලාපය: You can fine-tune Language and Locale settings below. - + ඔබට පහත භාෂාව සහ ස්ථාන සැකසීම් මනාව සකස් කළ හැක. @@ -2199,80 +2201,80 @@ The installer will quit and all changes will be lost. Password is too weak - + මුරපදය ඉතා දුර්වලයි Memory allocation error when setting '%1' - + '%1' සැකසීමේදී මතකය වෙන් කිරීමේ දෝෂයකි Memory allocation error - + මතකය වෙන් කිරීමේ දෝෂයකි The password is the same as the old one - + මුරපදය පැරණි එකට සමානයි The password is a palindrome - + මුරපදය palindrome වේ The password differs with case changes only - + මුරපදය වෙනස් වන්නේ සිද්ධි වෙනස් කිරීම් සමඟ පමණි The password is too similar to the old one - + මුරපදය පැරණි එකට ඉතා සමාන ය The password contains the user name in some form - + මුරපදයේ යම් ආකාරයක පරිශීලක නාමය අඩංගු වේ The password contains words from the real name of the user in some form - + මුරපදයේ යම් ආකාරයක පරිශීලකයාගේ සැබෑ නමෙන් වචන අඩංගු වේ The password contains forbidden words in some form - + මුරපදයේ යම් ආකාරයක තහනම් වචන අඩංගු වේ The password contains too few digits - + මුරපදයේ ඉතා අඩු ඉලක්කම් ඇත The password contains too few uppercase letters - + මුරපදයේ විශාල අකුරු ඉතා ස්වල්පයක් ඇත The password contains fewer than %n lowercase letters - - - + + මුරපදයේ කුඩා අකුරු %nකට වඩා අඩු ප්‍රමාණයක් ඇත + මුරපදයේ කුඩා අකුරු %n කට වඩා අඩු ප්‍රමාණයක් ඇත The password contains too few lowercase letters - + මුරපදයේ කුඩා අකුරු ඉතා ස්වල්පයක් ඇත The password contains too few non-alphanumeric characters - + මුරපදයේ අක්ෂරාංක නොවන අක්ෂර ඉතා ස්වල්පයක් අඩංගු වේ @@ -2282,181 +2284,181 @@ The installer will quit and all changes will be lost. The password does not contain enough character classes - + මුරපදයේ ප්‍රමාණවත් අක්ෂර පන්ති අඩංගු නොවේ The password contains too many same characters consecutively - + මුරපදයේ එක හා සමාන අනුලකුණු කිහිපයක් එක දිගට අඩංගු වේ The password contains too many characters of the same class consecutively - + මුරපදයේ එකම පන්තියේ අනුලකුණු වැඩි ගණනක් එක දිගට අඩංගු වේ The password contains fewer than %n digits - - - + + මුරපදයේ ඉලක්කම් %n කට වඩා අඩු ප්‍රමාණයක් අඩංගු වේ + මුරපදයේ ඉලක්කම් %n කට වඩා අඩු ප්‍රමාණයක් අඩංගු වේ The password contains fewer than %n uppercase letters - - - + + මුරපදයේ ලොකු අකුරු %n කට වඩා අඩු ප්‍රමාණයක් ඇත + මුරපදයේ ලොකු අකුරු %n කට වඩා අඩු ප්‍රමාණයක් ඇත The password contains fewer than %n non-alphanumeric characters - - - + + මුරපදයේ අක්ෂරාංක නොවන අක්ෂර %n කට වඩා අඩු ප්‍රමාණයක් අඩංගු වේ + මුරපදයේ අක්ෂරාංක නොවන අක්ෂර %n කට වඩා අඩු ප්‍රමාණයක් අඩංගු වේ The password is shorter than %n characters - - - + + මුරපදය අක්ෂර %n ට වඩා කෙටිය + මුරපදය අක්ෂර %n ට වඩා කෙටිය The password is a rotated version of the previous one - + මුරපදය පෙර එකෙහි අනුවාදයකි The password contains fewer than %n character classes - - - + + මුරපදයේ අක්ෂර පන්ති %n කට වඩා අඩු ප්‍රමාණයක් අඩංගු වේ + මුරපදයේ අක්ෂර පන්ති %n කට වඩා අඩු ප්‍රමාණයක් අඩංගු වේ The password contains more than %n same characters consecutively - - - + + මුරපදයේ එක දිගට එකම අක්ෂර %nකට වඩා අඩංගු වේ + මුරපදයේ එක දිගට එකම අක්ෂර %nකට වඩා අඩංගු වේ The password contains more than %n characters of the same class consecutively - - - + + මුරපදයේ එක පන්තියේ අනුලකුණු %n කට වඩා එක දිගට අඩංගු වේ + මුරපදයේ එක පන්තියේ අනුලකුණු %n කට වඩා එක දිගට අඩංගු වේ The password contains monotonic sequence longer than %n characters - - - + + මුරපදයේ අක්ෂර %n කට වඩා දිග ඒකාකාරී අනුපිළිවෙලක් ඇත + මුරපදයේ අක්ෂර %n කට වඩා දිග ඒකාකාරී අනුපිළිවෙලක් ඇත The password contains too long of a monotonic character sequence - + මුරපදයේ ඒකාකාරී අක්ෂර අනුපිළිවෙලක් ඉතා දිගු වේ No password supplied - + මුරපදයක් සපයා නැත Cannot obtain random numbers from the RNG device - + RNG උපාංගයෙන් අහඹු අංක ලබා ගත නොහැක Password generation failed - required entropy too low for settings - + මුරපද උත්පාදනය අසාර්ථක විය - සැකසීම් සඳහා අවශ්‍ය එන්ට්‍රොපිය ඉතා අඩුය The password fails the dictionary check - %1 - + මුරපදය ශබ්ද කෝෂ පරීක්ෂාව අසමත් වේ - %1 The password fails the dictionary check - + මුරපදය ශබ්ද කෝෂ පරීක්ෂාව අසමත් වේ Unknown setting - %1 - + නොදන්නා සැකසුම - %1 Unknown setting - + නොදන්නා සැකසුමක් Bad integer value of setting - %1 - + සැකසුමෙහි නරක පූර්ණ සංඛ්‍යා අගය - % 1 Bad integer value - + සැකසුමෙහි නරක පූර්ණ සංඛ්‍යා අගයක් Setting %1 is not of integer type - + %1 සැකසීම පූර්ණ සංඛ්‍යා වර්ගයට අයත් නොවේ Setting is not of integer type - + සැකසීම නිඛිල ආකාරයේ නොවේ Setting %1 is not of string type - + %1 සැකසීම තන්තු වර්ගයට අයත් නොවේ Setting is not of string type - + සැකසීම තන්තු ආකාරයේ නොවේ Opening the configuration file failed - + වින්‍යාස ගොනුව විවෘත කිරීම අසාර්ථක විය The configuration file is malformed - + වින්‍යාස ගොනුව විකෘති වී ඇත Fatal failure - + දරුනු අසර්ථක වීමක් Unknown error - + නොදන්නා දෝෂයකි Password is empty - + මුරපදය හිස් ය @@ -2469,17 +2471,17 @@ The installer will quit and all changes will be lost. Product Name - + නිෂ්පාදන නාමය TextLabel - + අකුරු නාමකය Long Product Description - + දිගු නිෂ්පාදන විස්තරය @@ -2497,7 +2499,7 @@ The installer will quit and all changes will be lost. Packages - + පැකේජ @@ -2505,7 +2507,7 @@ The installer will quit and all changes will be lost. Packages - + පැකේජ @@ -2513,12 +2515,12 @@ The installer will quit and all changes will be lost. Name - + නම Description - + විස්තරය @@ -2531,12 +2533,12 @@ The installer will quit and all changes will be lost. Keyboard Model: - + යතුරුපුවරු ආකෘතිය: Type here to test your keyboard - + ඔබේ යතුරු පුවරුව පරීක්ෂා කිරීමට මෙහි ටයිප් කරන්න @@ -2549,91 +2551,91 @@ The installer will quit and all changes will be lost. What is your name? - + ඔබගේ නම කුමක් ද? Your Full Name - + ඔබේ සම්පුර්ණ නම What name do you want to use to log in? - + ඔබට පුරනය වීමට භාවිතා කිරීමට අවශ්‍ය නම කුමක්ද? login - + පිවිසෙන්න What is the name of this computer? - + මෙම පරිගණකයේ නම කුමක්ද? <small>This name will be used if you make the computer visible to others on a network.</small> - + <small>ඔබ පරිගණකය ජාලයක අන් අයට පෙනෙන ලෙස සලස්වන්නේ නම් මෙම නම භාවිතා වේ.</small> Computer Name - + පරිගණක නම Choose a password to keep your account safe. - + ඔබගේ ගිණුම ආරක්ෂිතව තබා ගැනීමට මුරපදයක් තෝරන්න. <small>Enter the same password twice, so that it can be checked for typing errors. A good password will contain a mixture of letters, numbers and punctuation, should be at least eight characters long, and should be changed at regular intervals.</small> - + <small>එකම මුරපදය දෙවරක් ඇතුල් කරන්න, එවිට එය ටයිප් කිරීමේ දෝෂ සඳහා පරීක්ෂා කළ හැක. හොඳ මුරපදයක අකුරු, ඉලක්කම් සහ විරාම ලකුණු මිශ්‍රණයක් අඩංගු වන අතර, අවම වශයෙන් අක්ෂර අටක්වත් දිග විය යුතු අතර නියමිත කාල පරාසයන්හිදී වෙනස් කළ යුතුය.</small> Password - + රහස් පදය Repeat Password - + මුරපදය නැවත ඇතුල් කරන්න When this box is checked, password-strength checking is done and you will not be able to use a weak password. - + මෙම කොටුව සලකුණු කළ විට, මුරපදය-ශක්තිය පරීක්ෂා කිරීම සිදු කරනු ලබන අතර ඔබට දුර්වල මුරපදයක් භාවිතා කිරීමට නොහැකි වනු ඇත. Require strong passwords. - + ශක්තිමත් මුරපද අවශ්‍යයි. Log in automatically without asking for the password. - + මුරපදය ඉල්ලන්නේ නැතිව ස්වයංක්‍රීයව ලොග් වන්න. Use the same password for the administrator account. - + පරිපාලක ගිණුම සඳහා එකම මුරපදය භාවිතා කරන්න. Choose a password for the administrator account. - + පරිපාලක ගිණුම සඳහා මුරපදයක් තෝරන්න. <small>Enter the same password twice, so that it can be checked for typing errors.</small> - + <small>එකම මුරපදය දෙවරක් ඇතුල් කරන්න, එවිට එය ටයිප් කිරීමේ දෝෂ සඳහා පරීක්ෂා කළ හැක.</small> @@ -2641,43 +2643,43 @@ The installer will quit and all changes will be lost. Root - + රූට් Home - + හෝම් Boot - + බූට් EFI system - + EFI පද්ධතිය Swap - + ස්වප් New partition for %1 - + %1 සඳහා නව කොටස New partition - + නව කොටස %1 %2 size[number] filesystem[name] - + %1 %2 @@ -2686,38 +2688,38 @@ The installer will quit and all changes will be lost. Free Space - + නිදහස් ඉඩ New partition - + නව කොටස Name - + නම File System - + ගොනු පද්ධතිය File System Label - + ගොනු පද්ධති ලේබලය Mount Point - + මවුන්ට් පොයින්ට් Size - + ප්‍රමානය @@ -2730,72 +2732,72 @@ The installer will quit and all changes will be lost. Storage de&vice: - + ගබඩා උපාංගය (&v): &Revert All Changes - + සියලුම වෙනස්කම් ආපසු හරවන්න (&R) New Partition &Table - + නව කොටස් වගුව (&T) Cre&ate - + නිර්මාණය කරන්න (&r) &Edit - + සංස්කරණය කරන්න (&E) &Delete - + මකන්න (&D) New Volume Group - + නව වෙළුම් සමූහය Resize Volume Group - + වෙළුම් සමූහය ප්‍රතිප්‍රමාණ කරන්න Deactivate Volume Group - + පරිමා සමූහය අක්‍රිය කරන්න Remove Volume Group - + වෙළුම් කණ්ඩායම ඉවත් කරන්න I&nstall boot loader on: - + ඇරඹුම් කාරකය ස්ථාපනය කරන්න (&n): Are you sure you want to create a new partition table on %1? - + ඔබට %1 මත නව කොටස් වගුවක් සෑදීමට අවශ්‍ය බව විශ්වාසද? Can not create new partition - + නව කොටසක් සෑදිය නොහැක The partition table on %1 already has %2 primary partitions, and no more can be added. Please remove one primary partition and add an extended partition, instead. - + %1 හි කොටස් වගුවෙහි දැනටමත් ප්‍රාථමික කොටස් %2ක් ඇති අතර, තවත් එකතු කළ නොහැක. කරුණාකර එක් ප්‍රාථමික කොටසක් ඉවත් කර ඒ වෙනුවට දිගු කොටසක් එක් කරන්න. @@ -2808,7 +2810,7 @@ The installer will quit and all changes will be lost. Partitions - + කොටස් @@ -2823,72 +2825,72 @@ The installer will quit and all changes will be lost. No EFI system partition configured - + EFI පද්ධති කොටසක් වින්‍යාස කර නොමැත EFI system partition configured incorrectly - + EFI පද්ධති කොටස වැරදි ලෙස වින්‍යාස කර ඇත An EFI system partition is necessary to start %1.<br/><br/>To configure an EFI system partition, go back and select or create a suitable filesystem. - + %1 ආරම්භ කිරීමට EFI පද්ධති කොටසක් අවශ්‍ය වේ. <br/><br/>EFI පද්ධති කොටසක් වින්‍යාස කිරීමට, ආපසු ගොස් සුදුසු ගොනු පද්ධතියක් තෝරන්න හෝ සාදන්න. The filesystem must be mounted on <strong>%1</strong>. - + ගොනු පද්ධතිය %1 මත සවිකර තිබිය යුතුය. The filesystem must have type FAT32. - + ගොනු පද්ධතියට FAT32 වර්ගය තිබිය යුතුය. The filesystem must be at least %1 MiB in size. - + ගොනු පද්ධතිය අවම වශයෙන් %1 MiB විශාලත්වයකින් යුක්ත විය යුතුය. The filesystem must have flag <strong>%1</strong> set. - + ගොනු පද්ධතියට ධජය <strong>%1</strong> කට්ටලයක් තිබිය යුතුය. You can continue without setting up an EFI system partition but your system may fail to start. - + ඔබට EFI පද්ධති කොටසක් සැකසීමෙන් තොරව ඉදිරියට යා හැකි නමුත් ඔබේ පද්ධතිය ආරම්භ කිරීමට අසමත් විය හැක. Option to use GPT on BIOS - + BIOS මත GPT භාවිතා කිරීමේ විකල්පය A GPT partition table is the best option for all systems. This installer supports such a setup for BIOS systems too.<br/><br/>To configure a GPT partition table on BIOS, (if not done so already) go back and set the partition table to GPT, next create a 8 MB unformatted partition with the <strong>bios_grub</strong> flag enabled.<br/><br/>An unformatted 8 MB partition is necessary to start %1 on a BIOS system with GPT. - + GPT කොටස් වගුව සියලු පද්ධති සඳහා හොඳම විකල්පය වේ. මෙම ස්ථාපකය BIOS පද්ධති සඳහාද එවැනි සැකසුමකට සහය දක්වයි. <br/><br/>BIOS මත GPT කොටස් වගුවක් වින්‍යාස කිරීම සඳහා, (දැනටමත් එසේ කර නොමැති නම්) ආපසු ගොස් කොටස් වගුව GPT ලෙස සකසන්න, මීළඟට <strong>bios_grub</strong> ධජය සක්‍රීය කර ඇති 8 MB ආකෘතිකරණය නොකළ කොටසක් සාදන්න. <br/><br/>GPT සමඟින් BIOS පද්ධතියක %1 ආරම්භ කිරීමට හැඩතල ගැන්වීම නොකළ 8 MB කොටසක් අවශ්‍ය වේ. Boot partition not encrypted - + ඇරඹුම් කොටස සංකේතනය කර නොමැත A separate boot partition was set up together with an encrypted root partition, but the boot partition is not encrypted.<br/><br/>There are security concerns with this kind of setup, because important system files are kept on an unencrypted partition.<br/>You may continue if you wish, but filesystem unlocking will happen later during system startup.<br/>To encrypt the boot partition, go back and recreate it, selecting <strong>Encrypt</strong> in the partition creation window. - + එන්ක්‍රිප්ට් කරන ලද රූට් පාටිෂන් එකක් සමඟින් වෙනම ඇරඹුම් කොටසක් සකසා ඇත, නමුත් ඇරඹුම් කොටස සංකේතනය කර නොමැත. <br/<br/>වැදගත් පද්ධති ගොනු සංකේතනය නොකළ කොටසක තබා ඇති නිසා මෙවැනි සැකසුම සමඟ ආරක්ෂක ගැටළු ඇත. <br/>ඔබට අවශ්‍ය නම් ඔබට දිගටම කරගෙන යා හැක, නමුත් ගොනු පද්ධති අගුළු හැරීම පද්ධති ආරම්භයේදී පසුව සිදුවනු ඇත. <br/>ඇරඹුම් කොටස සංකේතනය කිරීමට, ආපසු ගොස් එය නැවත සාදන්න, කොටස් සෑදීමේ කවුළුව තුළ <strong>සංකේතනය</srong> තෝරන්න. has at least one disk device available. - + අවම වශයෙන් එක් තැටි උපාංගයක් තිබේ. There are no partitions to install on. - + ස්ථාපනය කිරීමට කොටස් නොමැත. @@ -2896,13 +2898,13 @@ The installer will quit and all changes will be lost. Plasma Look-and-Feel Job - + ප්ලාස්මා පෙනුම සහ හැඟීම රැකියාව Could not select KDE Plasma Look-and-Feel package - + KDE ප්ලාස්මා පෙනුම සහ දැනීම පැකේජය තේරීමට නොහැකි විය @@ -2915,12 +2917,12 @@ The installer will quit and all changes will be lost. Please choose a look-and-feel for the KDE Plasma Desktop. You can also skip this step and configure the look-and-feel once the system is set up. Clicking on a look-and-feel selection will give you a live preview of that look-and-feel. - + කරුණාකර KDE ප්ලාස්මා ඩෙස්ක්ටොප් එක සඳහා පෙනුම සහ හැඟීම තෝරන්න. ඔබට මෙම පියවර මඟ හැර පද්ධතිය සැකසූ පසු පෙනුම සහ හැඟීම වින්‍යාසගත කළ හැක. පෙනුම සහ හැඟීම තේරීමක් මත ක්ලික් කිරීමෙන් ඔබට එම පෙනුම සහ හැඟීම පිළිබඳ සජීවී පෙරදසුනක් ලබා දෙනු ඇත. Please choose a look-and-feel for the KDE Plasma Desktop. You can also skip this step and configure the look-and-feel once the system is installed. Clicking on a look-and-feel selection will give you a live preview of that look-and-feel. - + කරුණාකර KDE ප්ලාස්මා ඩෙස්ක්ටොප් එක සඳහා පෙනුම සහ හැඟීම තෝරන්න. ඔබට මෙම පියවර මඟ හැර පද්ධතිය ස්ථාපනය කළ පසු පෙනුම සහ හැඟීම වින්‍යාසගත කළ හැක. පෙනුම සහ හැඟීම තේරීමක් මත ක්ලික් කිරීමෙන් ඔබට එම පෙනුම සහ හැඟීම පිළිබඳ සජීවී පෙරදසුනක් ලබා දෙනු ඇත. @@ -2928,7 +2930,7 @@ The installer will quit and all changes will be lost. Look-and-Feel - + බලන්න සහ දැනෙන්න @@ -2936,17 +2938,17 @@ The installer will quit and all changes will be lost. Saving files for later ... - + පසු බාවිතට ගොනු සුරකමින් ... No files configured to save for later. - + පසුව සුරැකීමට ගොනු කිසිවක් වින්‍යාස කර නොමැත. Not all of the configured files could be preserved. - + වින්‍යාස කර ඇති සියලුම ගොනු සංරක්ෂණය කළ නොහැක. @@ -2955,64 +2957,67 @@ The installer will quit and all changes will be lost. There was no output from the command. - + +විධානයෙන් ප්‍රතිදානයක් නොතිබුණි. Output: - + +ප්‍රතිදානය: + External command crashed. - + බාහිර විධානය බිඳ වැටුණි. Command <i>%1</i> crashed. - + %1 විධානය බිඳ වැටුණි. External command failed to start. - + බාහිර විධානය ආරම්භ කිරීමට අසමත් විය. Command <i>%1</i> failed to start. - + %1 විධානය ආරම්භ කිරීමට අසමත් විය. Internal error when starting command. - + විධානය ආරම්භ කිරීමේදී අභ්යන්තර දෝෂයකි. Bad parameters for process job call. - + රැකියා ඇමතුම් ක්‍රියාවලි සඳහා නරක පරාමිතීන්. External command failed to finish. - + බාහිර විධානය අවසන් කිරීමට අසමත් විය. Command <i>%1</i> failed to finish in %2 seconds. - + <i>%1</i> විධානය තත්පර %2කින් අවසන් කිරීමට අසමත් විය. External command finished with errors. - + බාහිර විධානය දෝෂ සහිතව අවසන් විය. Command <i>%1</i> finished with exit code %2. - + <i>%1</i> විධානය පිටවීමේ කේතය %2 සමඟ අවසන් විය. @@ -3025,28 +3030,28 @@ Output: unknown - + නොදන්නා extended - + දිගුව unformatted - + ආකෘතිකරණය නොකළ swap - + ස්වප් Default - + පෙරනිමිය @@ -3054,43 +3059,43 @@ Output: File not found - + ගොනුව හමු නොවිණි Path <pre>%1</pre> must be an absolute path. - + මාර්ගය <pre>%1</pre> නිරපේක්ෂ මාර්ගයක් විය යුතුය. Directory not found - + නාමාවලිය හමු නොවීය Could not create new random file <pre>%1</pre>. - + නව අහඹු <pre>%1</pre> ගොනුවක් තැනීමට නොහැකි විය. No product - + නිෂ්පාදනයක් නැත No description provided. - + විස්තරයක් සපයා නැත. (no mount point) - + (සවිකිරීම් ස්ථානයක් නොමැත) Unpartitioned space or unknown partition table - + කොටස් නොකළ ඉඩ හෝ නොදන්නා කොටස් වගුව @@ -3099,7 +3104,8 @@ Output: <p>This computer does not satisfy some of the recommended requirements for setting up %1.<br/> Setup can continue, but some features might be disabled.</p> - + <p>මෙම පරිගණකය %1 පිහිටුවීම සඳහා නිර්දේශිත සමහර අවශ්‍යතා සපුරාලන්නේ නැත.<br/> +පිහිටුවීම දිගටම කරගෙන යා හැක, නමුත් සමහර විශේෂාංග අබල විය හැක.</p> @@ -3107,7 +3113,7 @@ Output: Remove live user from target system - + ඉලක්ක පද්ධතියෙන් සජීවී පරිශීලකයා ඉවත් කරන්න @@ -3116,17 +3122,17 @@ Output: Remove Volume Group named %1. - + %1 නම් වූ වෙළුම් සමූහය ඉවත් කරන්න. Remove Volume Group named <strong>%1</strong>. - + <strong>%1</strong> නම් වූ වෙළුම් සමූහය ඉවත් කරන්න. The installer failed to remove a volume group named '%1'. - + ස්ථාපකය '%1' නම් වෙළුම් කණ්ඩායමක් ඉවත් කිරීමට අසමත් විය. @@ -3139,59 +3145,59 @@ Output: Select where to install %1.<br/><font color="red">Warning: </font>this will delete all files on the selected partition. - + %1 ස්ථාපනය කළ යුතු ස්ථානය තෝරන්න. <br/><font color='red'>අවවාදයයි: </font>මෙය තෝරාගත් කොටසේ ඇති සියලුම ගොනු මකා දමයි. The selected item does not appear to be a valid partition. - + තෝරාගත් අයිතමය වලංගු කොටසක් ලෙස නොපෙනේ. %1 cannot be installed on empty space. Please select an existing partition. - + %1 හිස් අවකාශයේ ස්ථාපනය කල නොහැක. කරුණාකර පවතින කොටසක් තෝරන්න. %1 cannot be installed on an extended partition. Please select an existing primary or logical partition. - + %1 දිගු කළ කොටසක ස්ථාපනය කල නොහැක. කරුණාකර පවතින ප්‍රාථමික හෝ තාර්කික කොටසක් තෝරන්න. %1 cannot be installed on this partition. - + %1 මෙම කොටසෙහි ස්ථාපනය කල නොහැක. Data partition (%1) - + දත්ත කොටස (%1) Unknown system partition (%1) - + නොදන්නා පද්ධති කොටස (%1) %1 system partition (%2) - + %1 පද්ධති කොටස (%2) <strong>%4</strong><br/><br/>The partition %1 is too small for %2. Please select a partition with capacity at least %3 GiB. - + <strong>%4</strong><br/><br/>%1 කොටස %2 සඳහා ඉතා කුඩාය. කරුණාකර අවම වශයෙන් %3 GiB ධාරිතාවක් සහිත කොටසක් තෝරන්න. <strong>%2</strong><br/><br/>An EFI system partition cannot be found anywhere on this system. Please go back and use manual partitioning to set up %1. - + <strong>%2</strong><br/><br/>EFI පද්ධති කොටසක් මෙම පද්ධතියේ කොතැනකවත් සොයාගත නොහැක. කරුණාකර ආපසු ගොස් %1 පිහිටුවීමට අතින් කොටස් කිරීම භාවිතා කරන්න. <strong>%3</strong><br/><br/>%1 will be installed on %2.<br/><font color="red">Warning: </font>all data on partition %2 will be lost. - + <strong>%3</strong><br/><br/>%1 %2 මත ස්ථාපනය වනු ඇත.<br/><font color="red">අවවාදයයි:</font> %2 කොටසේ ඇති සියලුම දත්ත නැති වී යයි. @@ -3210,13 +3216,15 @@ Output: <p>This computer does not satisfy the minimum requirements for installing %1.<br/> Installation cannot continue.</p> - + <p>මෙම පරිගණකය %1 ස්ථාපනය සඳහා අවම අවශ්‍යතා සපුරාලන්නේ නැත.<br/> + ස්ථාපනය දිගටම කරගෙන යා නොහැක.</p> <p>This computer does not satisfy some of the recommended requirements for setting up %1.<br/> Setup can continue, but some features might be disabled.</p> - + <p>මෙම පරිගණකය %1 පිහිටුවීම සඳහා නිර්දේශිත සමහර අවශ්‍යතා සපුරාලන්නේ නැත.<br/> + පිහිටුවීම දිගටම කරගෙන යා හැක, නමුත් සමහර විශේෂාංග අබල විය හැක.</p> @@ -3224,27 +3232,27 @@ Output: Resize Filesystem Job - + ගොනු පද්ධති කාර්යය ප්‍රමාණය වෙනස් කරන්න Invalid configuration - + වලංගු නොවන වින්‍යාසය The file-system resize job has an invalid configuration and will not run. - + ගොනු පද්ධති ප්‍රමාණය වෙනස් කිරීමේ කාර්යයට වලංගු නොවන වින්‍යාසයක් ඇති අතර එය ක්‍රියාත්මක නොවේ. KPMCore not Available - + KPMCore නොමැත Calamares cannot start KPMCore for the file-system resize job. - + ගොනු පද්ධති ප්‍රමාණය වෙනස් කිරීමේ කාර්යය සඳහා Calamares හට KPMCore ආරම්භ කළ නොහැක. @@ -3253,39 +3261,39 @@ Output: Resize Failed - + ප්‍රමාණය වෙනස් කිරීම අසාර්ථක විය The filesystem %1 could not be found in this system, and cannot be resized. - + ගොනු පද්ධතිය %1 මෙම පද්ධතිය තුළ සොයා ගත නොහැකි අතර, ප්‍රමාණය වෙනස් කළ නොහැක. The device %1 could not be found in this system, and cannot be resized. - + %1 උපාංගය මෙම පද්ධතිය තුළ සොයාගත නොහැකි වූ අතර, ප්‍රමාණය වෙනස් කළ නොහැක. The filesystem %1 cannot be resized. - + %1 ගොනු පද්ධතිය ප්‍රතිප්‍රමාණ කළ නොහැක. The device %1 cannot be resized. - + උපාංගය %1 ප්‍රමාණය වෙනස් කළ නොහැක. The filesystem %1 must be resized, but cannot. - + ගොනු පද්ධතිය %1 ප්‍රමාණය වෙනස් කළ යුතුය, නමුත් කළ නොහැක. The device %1 must be resized, but cannot - + උපාංගය %1 ප්‍රමාණය වෙනස් කළ යුතු නමුත් කළ නොහැක @@ -3293,22 +3301,22 @@ Output: Resize partition %1. - + %1 කොටස ප්‍රතිප්‍රමාණ කරන්න. Resize <strong>%2MiB</strong> partition <strong>%1</strong> to <strong>%3MiB</strong>. - + <strong>%2MiB</strong> කොටස <strong>%1</strong> සිට <strong>%3MiB</strong> දක්වා ප්‍රමාණය වෙනස් කරන්න. Resizing %2MiB partition %1 to %3MiB. - + %2MiB කොටස %1 සිට %3MiB දක්වා ප්‍රමාණය වෙනස් කිරීම. The installer failed to resize partition %1 on disk '%2'. - + '%2' තැටියේ %1 කොටස ප්‍රතිප්‍රමාණ කිරීමට ස්ථාපකය අසමත් විය. @@ -3316,7 +3324,7 @@ Output: Resize Volume Group - + වෙළුම් සමූහය ප්‍රතිප්‍රමාණ කරන්න @@ -3325,17 +3333,17 @@ Output: Resize volume group named %1 from %2 to %3. - + %2 සිට %3 දක්වා %1 ලෙස නම් කරන ලද වෙළුම් සමූහය ප්‍රතිප්‍රමාණ කරන්න. Resize volume group named <strong>%1</strong> from <strong>%2</strong> to <strong>%3</strong>. - + <strong>%2</strong> සිට <strong>%3</strong> දක්වා <strong>%1</strong> ලෙස නම් කරන ලද වෙළුම් සමූහය ප්‍රතිප්‍රමාණ කරන්න. The installer failed to resize a volume group named '%1'. - + ස්ථාපකය '%1' නම් වූ වෙළුම් සමූහයක් ප්‍රතිප්‍රමාණ කිරීමට අසමත් විය. @@ -3343,12 +3351,12 @@ Output: For best results, please ensure that this computer: - + හොඳම ප්‍රතිඵල සඳහා, කරුණාකර මෙම පරිගණකය සහතික කර ගන්න: System requirements - + පද්ධති අවශ්යතා @@ -3356,12 +3364,12 @@ Output: Scanning storage devices... - + ගබඩා උපාංග පරිලෝකනය කරමින්... Partitioning - + කොටස් කරමින් @@ -3369,29 +3377,29 @@ Output: Set hostname %1 - + ධාරක නාමය සකසන්න %1 Set hostname <strong>%1</strong>. - + ධාරක නාමය සකසන්න <strong>%1</strong>. Setting hostname %1. - + සත්කාරක නාමය %1 සැකසීම. Internal Error - + අභ්යන්තර දෝෂයකි Cannot write hostname to target system - + ඉලක්ක පද්ධතියට සත්කාරක නාමය ලිවිය නොහැක @@ -3399,29 +3407,29 @@ Output: Set keyboard model to %1, layout to %2-%3 - + යතුරුපුවරු ආකෘතිය %1 ලෙස සකසන්න, පිරිසැලසුම %2-%3 ලෙස සකසන්න Failed to write keyboard configuration for the virtual console. - + අතථ්‍ය කොන්සෝලය සඳහා යතුරුපුවරු වින්‍යාසය ලිවීමට අසමත් විය. Failed to write to %1 - + %1 වෙත ලිවීමට අසමත් විය Failed to write keyboard configuration for X11. - + X11 සඳහා යතුරුපුවරු වින්‍යාසය ලිවීමට අසමත් විය. Failed to write keyboard configuration to existing /etc/default directory. - + පවතින /etc/default බහලුම වෙත යතුරුපුවරු වින්‍යාසය ලිවීමට අසමත් විය. @@ -3429,82 +3437,82 @@ Output: Set flags on partition %1. - + %1 කොටසේ කොඩි සකසන්න. Set flags on %1MiB %2 partition. - + %1MiB %2 කොටස මත කොඩි සකසන්න. Set flags on new partition. - + නව කොටසේ කොඩි සකසන්න. Clear flags on partition <strong>%1</strong>. - + %1 කොටසේ කොඩි හිස් කරන්න. Clear flags on %1MiB <strong>%2</strong> partition. - + %1MiB %2 කොටසේ කොඩි හිස් කරන්න. Clear flags on new partition. - + නව කොටසේ කොඩි ඉවත් කරන්න. Flag partition <strong>%1</strong> as <strong>%2</strong>. - + %1 කොටස %2 ලෙස සලකුණු කරන්න. Flag %1MiB <strong>%2</strong> partition as <strong>%3</strong>. - + %1MiB <strong>%2</strong> කොටස <strong>%3</strong> ලෙස සලකුණු කරන්න. Flag new partition as <strong>%1</strong>. - + නව කොටස <strong>%1</strong> ලෙස සලකුණු කරන්න. Clearing flags on partition <strong>%1</strong>. - + %1 කොටසේ කොඩි ඉවත් කිරීම. Clearing flags on %1MiB <strong>%2</strong> partition. - + %1MiB <strong>%2</strong> කොටසේ කොඩි ඉවත් කිරීම. Clearing flags on new partition. - + නව කොටසේ කොඩි ඉවත් කිරීම. Setting flags <strong>%2</strong> on partition <strong>%1</strong>. - + <strong>%1</strong> කොටස මත කොඩි <strong>%2</strong> සැකසීම. Setting flags <strong>%3</strong> on %1MiB <strong>%2</strong> partition. - + %1MiB <strong>%2</strong> කොටස මත කොඩි <strong>%3</strong> සැකසීම. Setting flags <strong>%1</strong> on new partition. - + නව කොටසෙහි කොඩි <strong>%1</strong> සැකසීම. The installer failed to set flags on partition %1. - + ස්ථාපකය %1 කොටසෙහි කොඩි සැකසීමට අසමත් විය. @@ -3512,42 +3520,42 @@ Output: Set password for user %1 - + පරිශීලක %1 සඳහා මුරපදය සකසන්න Setting password for user %1. - + පරිශීලක %1 සඳහා මුරපදය සැකසීම. Bad destination system path. - + නරක ගමනාන්ත පද්ධති මාර්ගය. rootMountPoint is %1 - + මූලමවුන්ට්පොයින්ට් % 1 වේ Cannot disable root account. - + මූල ගිණුම අක්‍රිය කළ නොහැක. passwd terminated with error code %1. - + මුරපදය %1 දෝෂ කේතය සමඟ අවසන් විය. Cannot set password for user %1. - + පරිශීලක %1 සඳහා මුරපදය සැකසිය නොහැක. usermod terminated with error code %1. - + පරිශීලක මොඩ් දෝෂ කේතය % 1 සමඟ අවසන් කරන ලදී. @@ -3555,37 +3563,37 @@ Output: Set timezone to %1/%2 - + වේලා කලාපය %1/%2 ලෙස සකසන්න Cannot access selected timezone path. - + තෝරාගත් වේලා කලාප මාර්ගයට ප්‍රවේශ විය නොහැක. Bad path: %1 - + නරක මාර්ගය:%1 Cannot set timezone. - + වේලා කලාපයක් සැකසිය නොහැක. Link creation failed, target: %1; link name: %2 - + සබැඳි නිර්මාණය අසාර්ථක විය, ඉලක්කය: %1; සබැඳි නම: %2 Cannot set timezone, - + වේලා කලාපය සැකසිය නොහැක, Cannot open /etc/timezone for writing - + ලිවීම සඳහා /etc/timezone විවෘත කළ නොහැක @@ -3593,18 +3601,18 @@ Output: Preparing groups. - + කණ්ඩායම් සූදානම් කිරීම. Could not create groups in target system - + ඉලක්ක පද්ධතිය තුළ කණ්ඩායම් සෑදීමට නොහැකි විය These groups are missing in the target system: %1 - + ඉලක්ක පද්ධතිය තුළ මෙම කණ්ඩායම් අතුරුදහන් වී ඇත: %1 @@ -3612,17 +3620,17 @@ Output: Configure <pre>sudo</pre> users. - + <strong>sudo</strong> භාවිතා කරන්නන් වින්‍යාස කරන්න. Cannot chmod sudoers file. - + sudoers ගොනුව chmod කළ නොහැක. Cannot create sudoers file for writing. - + ලිවීම සඳහා sudoers ගොනුව සෑදිය නොහැක. @@ -3630,7 +3638,7 @@ Output: Shell Processes Job - + ෂෙල් ක්රියාවලීන් @@ -3639,7 +3647,7 @@ Output: %L1 / %L2 slide counter, %1 of %2 (numeric) - + %L1 / %L2 @@ -3647,7 +3655,7 @@ Output: &OK - + හරි (&O) @@ -3675,22 +3683,22 @@ Output: Installation feedback - + ස්ථාපන ප්‍රතිපෝෂණය Sending installation feedback. - + ස්ථාපන ප්‍රතිපෝෂණ යැවීම. Internal error in install-tracking. - + ස්ථාපන ලුහුබැඳීමේ අභ්‍යන්තර දෝෂයකි. HTTP request timed out. - + HTTP ඉල්ලීම කල් ඉකුත් විය. @@ -3698,28 +3706,28 @@ Output: KDE user feedback - + KDE පරිශීලක ප්‍රතිපෝෂණය Configuring KDE user feedback. - + KDE පරිශීලක ප්‍රතිපෝෂණ වින්‍යාස කිරීම. Error in KDE user feedback configuration. - + KDE පරිශීලක ප්‍රතිපෝෂණ වින්‍යාසයෙහි දෝෂයකි. Could not configure KDE user feedback correctly, script error %1. - + KDE පරිශීලක ප්‍රතිපෝෂණය නිවැරදිව වින්‍යාස කිරීමට නොහැකි විය, ස්ක්‍රිප්ට් දෝෂය %1. Could not configure KDE user feedback correctly, Calamares error %1. - + KDE පරිශීලක ප්‍රතිපෝෂණය නිවැරදිව වින්‍යාස කිරීමට නොහැකි විය, Calamares දෝෂය %1. @@ -3727,28 +3735,28 @@ Output: Machine feedback - + යන්ත්‍ර ප්‍රතිපෝෂණය Configuring machine feedback. - + යන්ත්‍ර ප්‍රතිපෝෂණ වින්‍යාස කිරීම. Error in machine feedback configuration. - + යන්ත්‍ර ප්‍රතිපෝෂණ වින්‍යාසය තුළ දෝෂයකි. Could not configure machine feedback correctly, script error %1. - + යන්ත්‍ර ප්‍රතිපෝෂණය නිවැරදිව වින්‍යාස කිරීමට නොහැකි විය, ස්ක්‍රිප්ට් දෝෂය %1. Could not configure machine feedback correctly, Calamares error %1. - + යන්ත්‍ර ප්‍රතිපෝෂණය නිවැරදිව වින්‍යාස කිරීමට නොහැකි විය, Calamares දෝෂය %1. @@ -3761,37 +3769,37 @@ Output: Placeholder - + ස්ථාන දාරකය <html><head/><body><p>Click here to send <span style=" font-weight:600;">no information at all</span> about your installation.</p></body></html> - + <html><head/><body><p>ඔබගේ ස්ථාපනය පිළිබඳ <span style=" font-weight:600;">කිසිදු තොරතුරක්</span> නොයැවීමට මෙතන ක්ලික් කරන්න.</p></body></html> <html><head/><body><p><a href="placeholder"><span style=" text-decoration: underline; color:#2980b9;">Click here for more information about user feedback</span></a></p></body></html> - + <html><head/><body><p><a href="placeholder"><span style=" text-decoration: underline; color:#2980b9;">පරිශීලක ප්‍රතිපෝෂණ පිළිබඳ වැඩි විස්තර සඳහා මෙතැන ක්ලික් කරන්න</span></a></p></body></html> Tracking helps %1 to see how often it is installed, what hardware it is installed on and which applications are used. To see what will be sent, please click the help icon next to each area. - + ලුහුබැඳීම %1 හට එය කොපමණ වාරයක් ස්ථාපනය කර ඇත්ද, කුමන දෘඩාංග මත ස්ථාපනය කර ඇත්ද සහ කුමන යෙදුම් භාවිතා කරන්නේද යන්න බැලීමට උපකාරී වේ. යවන්නේ කුමක් දැයි බැලීමට, එක් එක් ප්‍රදේශයට යාබදව ඇති උදවු නිරූපකය ක්ලික් කරන්න. By selecting this you will send information about your installation and hardware. This information will only be sent <b>once</b> after the installation finishes. - + මෙය තේරීමෙන් ඔබ ඔබේ ස්ථාපනය සහ දෘඪාංග පිළිබඳ තොරතුරු එවනු ඇත. ස්ථාපනය අවසන් වූ පසු මෙම තොරතුරු <b>එක් වරක්</b> පමණක් යවනු ලැබේ. By selecting this you will periodically send information about your <b>machine</b> installation, hardware and applications, to %1. - + මෙය තේරීමෙන් ඔබ විසින් ඔබේ <b>යන්ත්‍ර</b> ස්ථාපනය, දෘඪාංග සහ යෙදුම් පිළිබඳ තොරතුරු වරින් වර %1 වෙත යවනු ලැබේ. By selecting this you will regularly send information about your <b>user</b> installation, hardware, applications and application usage patterns, to %1. - + මෙය තේරීමෙන් ඔබ ඔබේ <b>පරිශීලක</b> ස්ථාපනය, දෘඪාංග, යෙදුම්, සහ යෙදුම් භාවිත රටා, %1 වෙත නිතිපතා යවනු ලැබේ. @@ -3799,7 +3807,7 @@ Output: Feedback - + ප්‍රතිපෝෂණ @@ -3807,12 +3815,12 @@ Output: <small>If more than one person will use this computer, you can create multiple accounts after setup.</small> - + <small>එක් අයෙකුට වඩා මෙම පරිගණකය භාවිතා කරන්නේ නම්, සැකසීමෙන් පසු ඔබට ගිණුම් කිහිපයක් සෑදිය හැක.</small> <small>If more than one person will use this computer, you can create multiple accounts after installation.</small> - + <small>මෙම පරිගණකය එක් අයෙකුට වඩා භාවිතා කරන්නේ නම්, ස්ථාපනය කිරීමෙන් පසු ඔබට ගිණුම් කිහිපයක් සෑදිය හැක.</small> @@ -3820,7 +3828,7 @@ Output: Users - + පරිශීලකයන් @@ -3828,7 +3836,7 @@ Output: Users - + පරිශීලකයන් @@ -3837,13 +3845,13 @@ Output: Key Column header for key/value - + යතුර Value Column header for key/value - + අගය @@ -3856,22 +3864,22 @@ Output: List of Physical Volumes - + භෞතික වෙළුම් ලැයිස්තුව Volume Group Name: - + වෙළුම් සමූහයේ නම: Volume Group Type: - + වෙළුම් කණ්ඩායම් වර්ගය: Physical Extent Size: - + භෞතික ප්‍රමාණයේ ප්‍රමාණය: @@ -3881,22 +3889,22 @@ Output: Total Size: - + මුළු ප්‍රමාණය: Used Size: - + භාවිතා කළ ප්‍රමාණය: Total Sectors: - + මුළු අංශ: Quantity of LVs: - + LV ප්‍රමාණය: @@ -3910,92 +3918,92 @@ Output: Select application and system language - + යෙදුම් සහ පද්ධති භාෂාව තෝරන්න &About - + ගැන (&A) Open donations website - + පරිත්‍යාග වෙබ් අඩවිය විවෘත කරන්න &Donate - + පරිත්‍යාග කරන්න (&D) Open help and support website - + උදව් සහ සහාය වෙබ් අඩවිය විවෘත කරන්න &Support - + සහාය (&S) Open issues and bug-tracking website - + ගැටළු සහ දෝෂ ලුහුබැඳීමේ වෙබ් අඩවිය විවෘත කරන්න &Known issues - + දන්නා ගැටළු (&K) Open release notes website - + නිකුතු සටහන් වෙබ් අඩවිය විවෘත කරන්න &Release notes - + නිකුත් කිරීමේ සටහන් (&R) <h1>Welcome to the Calamares setup program for %1.</h1> - + <h1>%1 සඳහා Calamares සැකසුම් වැඩසටහන වෙත සාදරයෙන් පිළිගනිමු.</h1> <h1>Welcome to %1 setup.</h1> - + <h1>%1 පිහිටුවීමට සාදරයෙන් පිළිගනිමු.</h1> <h1>Welcome to the Calamares installer for %1.</h1> - + <h1>%1 සඳහා Calamares ස්ථාපකය වෙත සාදරයෙන් පිළිගනිමු.</h1> <h1>Welcome to the %1 installer.</h1> - + <h1>%1 ස්ථාපකය වෙත සාදරයෙන් පිළිගනිමු.</h1> %1 support - + %1 සහාය About %1 setup - + %1 පිහිටුවීම ගැන About %1 installer - + %1 ස්ථාපකය ගැන <h1>%1</h1><br/><strong>%2<br/>for %3</strong><br/><br/>Copyright 2014-2017 Teo Mrnjavac &lt;teo@kde.org&gt;<br/>Copyright 2017-2020 Adriaan de Groot &lt;groot@kde.org&gt;<br/>Thanks to <a href="https://calamares.io/team/">the Calamares team</a> and the <a href="https://www.transifex.com/calamares/calamares/">Calamares translators team</a>.<br/><br/><a href="https://calamares.io/">Calamares</a> development is sponsored by <br/><a href="http://www.blue-systems.com/">Blue Systems</a> - Liberating Software. - + <h1>%1</h1><br/><strong>%2<br/> සඳහා %3</strong><br/><br/>ප්‍රකාශන හිමිකම 2014-2017 Teo Mrnjavac &lt;teo@kde.org&gt;<br/>ප්‍රකාශන හිමිකම 2017-2020 Adriaan de Groot &lt;groot@kde.org&gt;<br/><a href="https://calamares.io/team/">Calamares කණ්ඩායමට</a> සහ <a href="https://www.transifex.com/calamares/calamares/">Calamares පරිවර්තකයන් සංවර්ධන කණ්ඩායමට</a> ස්තුතියි. <a href="https://calamares.io/">Calamares</a> සංවර්ධනය සඳහා අනුග්‍රහය දක්වන්නේ <br/><a href="http://www.blue-systems.com/">Blue Systems</a> - Liberating Software විසිනි. @@ -4003,7 +4011,7 @@ Output: Welcome - + සාදරයෙන් පිළිගනිමු @@ -4011,7 +4019,7 @@ Output: Welcome - + සාදරයෙන් පිළිගනිමු @@ -4030,12 +4038,23 @@ Output: development is sponsored by <br/> <a href='http://www.blue-systems.com/'>Blue Systems</a> - Liberating Software. - + <h1>%1</h1><br/> + <strong>%2<br/> + for %3</strong><br/><br/> + Copyright 2014-2017 Teo Mrnjavac &lt;teo@kde.org&gt;<br/> + Copyright 2017-2020 Adriaan de Groot &lt;groot@kde.org&gt;<br/> + <a href='https://calamares.io/team/'>Calamares කණ්ඩායමට</a> ස්තුතියි + සහ <a href='https://www.transifex.com/calamares/calamares/'>Calamares + පරිවර්තක කණ්ඩායම</a>.<br/><br/> + <a href='https://calamares.io/'>Calamares</a> + සංවර්ධනය අනුග්‍රහය දක්වනුයේ<br/> + <a href='http://www.blue-systems.com/'>Blue Systems</a> - + Liberating Software. Back - + ආපසු @@ -4043,7 +4062,7 @@ Output: Show debug information - + දෝශ නිරාකරණ තොරතුරු පෙන්වන්න @@ -4051,29 +4070,31 @@ Output: Installation Completed - + ස්ථාපනය අවසන් %1 has been installed on your computer.<br/> You may now restart into your new system, or continue using the Live environment. - + %1 ඔබේ පරිගණකයේ ස්ථාපනය කර ඇත.<br/> + ඔබට දැන් ඔබේ නව පද්ධතිය නැවත ආරම්භ කළ හැකිය, නැතහොත් සජීවී පරිසරය දිගටම භාවිතා කළ හැක. Close Installer - + ස්ථාපකය වසන්න Restart System - + පද්ධතිය නැවත ආරම්භ කරන්න <p>A full log of the install is available as installation.log in the home directory of the Live user.<br/> This log is copied to /var/log/installation.log of the target system.</p> - + <p>ස්ථාපනයේ සම්පූර්ණ ලොගයක් සජීවී පරිශීලකයාගේ මුල් නාමාවලියෙහි install.log ලෙස පවතී.<br/> + මෙම ලොගය ඉලක්ක පද්ධතියේ /var/log/installation.log වෙත පිටපත් කර ඇත.</p> @@ -4082,18 +4103,20 @@ Output: <h1>Languages</h1> </br> The system locale setting affects the language and character set for some command line user interface elements. The current setting is <strong>%1</strong>. - + <h1>භාෂා</h1> </br> + පද්ධති පෙදෙසි සැකසුම සමහර විධාන රේඛා පරිශීලක අතුරුමුහුණත් මූලද්‍රව්‍ය සඳහා භාෂාව සහ අක්ෂර කට්ටලයට බලපායි. වත්මන් සැකසුම <strong>%1</strong> වේ. <h1>Locales</h1> </br> The system locale setting affects the numbers and dates format. The current setting is <strong>%1</strong>. - + <h1>ප්‍රාදේශීය</h1> </br> + පද්ධති පෙදෙසි සැකසීම අංක සහ දින ආකෘතියට බලපායි. වත්මන් සැකසුම <strong>%1</strong> වේ. Back - + ආපසු @@ -4101,27 +4124,27 @@ Output: To activate keyboard preview, select a layout. - + යතුරුපුවරු පෙරදසුන සක්‍රිය කිරීමට, පිරිසැලසුමක් තෝරන්න. Keyboard Model: - + යතුරුපුවරු ආකෘතිය: Layouts - + පිරිසැලසුම් Type here to test your keyboard - + ඔබේ යතුරු පුවරුව පරීක්ෂා කිරීමට මෙහි ටයිප් කරන්න Variants - + ප්‍රභේද @@ -4129,7 +4152,7 @@ Output: Change - + වෙනස් කරන්න @@ -4138,7 +4161,8 @@ Output: <h3>%1</h3> <p>These are example release notes.</p> - + <h3>%1</h3> + <p>මේවා නිදසුන් නිකුත් කිරීමේ සටහන් වේ.</p> @@ -4147,37 +4171,38 @@ Output: LibreOffice is a powerful and free office suite, used by millions of people around the world. It includes several applications that make it the most versatile Free and Open Source office suite on the market.<br/> Default option. - + LibreOffice යනු ලොව පුරා සිටින මිලියන සංඛ්‍යාත ජනතාවක් විසින් භාවිතා කරන බලවත් සහ නිදහස් කාර්යාල කට්ටලයකි. වෙළඳපොලේ ඇති වඩාත්ම බහුකාර්ය නිදහස් සහ විවෘත මූලාශ්‍ර කාර්යාල කට්ටලය බවට පත් කරන යෙදුම් කිහිපයක් එයට ඇතුළත් වේ.<br/> + පෙරනිමි විකල්පය. LibreOffice - + LibreOffice If you don't want to install an office suite, just select No Office Suite. You can always add one (or more) later on your installed system as the need arrives. - + ඔබට කාර්යාල කට්ටලයක් ස්ථාපනය කිරීමට අවශ්‍ය නැතිනම්, No Office Suite තෝරන්න. ඔබගේ ස්ථාපිත පද්ධතියට අවශ්‍යතාවය අනුව ඔබට සැම විටම එකක් (හෝ කිහිපයක්) පසුව එක් කළ හැක No Office Suite - + No Office Suite Create a minimal Desktop install, remove all extra applications and decide later on what you would like to add to your system. Examples of what won't be on such an install, there will be no Office Suite, no media players, no image viewer or print support. It will be just a desktop, file browser, package manager, text editor and simple web-browser. - + අවම ඩෙස්ක්ටොප් ස්ථාපනයක් සාදන්න, සියලුම අමතර යෙදුම් ඉවත් කර ඔබ ඔබේ පද්ධතියට එකතු කිරීමට කැමති දේ පසුව තීරණය කරන්න. එවැනි ස්ථාපනයක සිදු නොවන දේ පිළිබඳ උදාහරණ, Office Suite එකක්, මාධ්‍ය වාදකයක්, රූප නරඹන්නාක් හෝ මුද්‍රණ සහායක් නොමැත. එය ඩෙස්ක්ටොප් එකක්, ගොනු බ්‍රවුසරයක්, පැකේජ කළමනාකරු, පෙළ සංස්කාරකයක් සහ සරල වෙබ් බ්‍රව්සරයක් පමණක් වනු ඇත. Minimal Install - + අවම ස්ථාපනය Please select an option for your install, or use the default: LibreOffice included. - + කරුණාකර ඔබගේ ස්ථාපනය සඳහා විකල්පයක් තෝරන්න, නැතහොත් පෙරනිමිය භාවිතා කරන්න: LibreOffice ඇතුළත්. @@ -4205,12 +4230,32 @@ Output: </ul> <p>The vertical scrollbar is adjustable, current width set to 10.</p> - + <h3>%1</h3> + <p>මෙය උදාහරණ QML ගොනුවකි, Flickable අන්තර්ගතය සහිත Rich Text විකල්ප පෙන්වයි.</p> + + <p>Rich Text සහිත QML හට HTML ටැග් භාවිතා කළ හැක, Flickable අන්තර්ගතය ස්පර්ශ තිර සඳහා ප්‍රයෝජනවත් වේ.</p> + + <p><b>මෙය තද පෙළකි</b></p> + <p><i>මෙය ඇල අකුරු වේ</i></p> + <p><u>මෙය යටින් ඉරි ඇඳ ඇති පාඨයකි</u></p> + <p><center>මෙම පාඨය මැදට පෙළගස්වනු ඇත.</center></p> + <p><s>මෙය මැදින් ඇදි ඉරකි.</s></p> + + <p>කේත උදාහරණය: + <code>ls -l /home</code></p> + + <p><b>ලැයිස්තු:</b></p> + <ul> + <li>Intel CPU පද්ධති</li> + <li>AMD CPU පද්ධති</li> + </ul> + + <p>සිරස් අනුචලන තීරුව වෙනස් කළ හැකි අතර, වත්මන් පළල 10 ලෙස සකසා ඇත.</p> Back - + ආපසු @@ -4218,32 +4263,32 @@ Output: Pick your user name and credentials to login and perform admin tasks - + පිවිසීමට සහ පරිපාලක කාර්යයන් කිරීමට ඔබගේ පරිශීලක නාමය සහ අක්තපත්‍ර තෝරන්න What is your name? - + ඔබගේ නම කුමක් ද? Your Full Name - + ඔබේ සම්පුර්ණ නම What name do you want to use to log in? - + ඔබට පුරනය වීමට භාවිතා කිරීමට අවශ්‍ය නම කුමක්ද? Login Name - + ලොගින් නම If more than one person will use this computer, you can create multiple accounts after installation. - + මෙම පරිගණකය එක් අයෙකුට වඩා භාවිතා කරන්නේ නම්, ස්ථාපනය කිරීමෙන් පසු ඔබට ගිණුම් කිහිපයක් සෑදිය හැක. @@ -4253,97 +4298,97 @@ Output: root is not allowed as username. - + root පරිශීලක නාමයක් ලෙස අවසර නැත. What is the name of this computer? - + මෙම පරිගණකයේ නම කුමක්ද? Computer Name - + පරිගණක නම This name will be used if you make the computer visible to others on a network. - + ඔබ පරිගණකය ජාලයක අන් අයට පෙනෙන ලෙස සලස්වන්නේ නම් මෙම නම භාවිතා වේ. localhost is not allowed as hostname. - + localhost සත්කාරක නාමය ලෙස භාවිතයට අවසර නැත. Choose a password to keep your account safe. - + ඔබගේ ගිණුම ආරක්ෂිතව තබා ගැනීමට මුරපදයක් තෝරන්න. Password - + රහස් පදය Repeat Password - + මුරපදය නැවත ඇතුල් කරන්න Enter the same password twice, so that it can be checked for typing errors. A good password will contain a mixture of letters, numbers and punctuation, should be at least eight characters long, and should be changed at regular intervals. - + එකම මුරපදය දෙවරක් ඇතුල් කරන්න, එවිට එය ටයිප් කිරීමේ දෝෂ සඳහා පරීක්ෂා කළ හැක. හොඳ මුරපදයක අකුරු, ඉලක්කම් සහ විරාම ලකුණු මිශ්‍රණයක් අඩංගු වන අතර, අවම වශයෙන් අක්ෂර අටක්වත් දිග විය යුතු අතර නියමිත කාල පරාසයන්හිදී වෙනස් කළ යුතුය. Validate passwords quality - + මුරපදවල ගුණාත්මකභාවය තහවුරු කරන්න When this box is checked, password-strength checking is done and you will not be able to use a weak password. - + මෙම කොටුව සලකුණු කළ විට, මුරපදය-ශක්තිය පරීක්ෂා කිරීම සිදු කරනු ලබන අතර ඔබට දුර්වල මුරපදයක් භාවිතා කිරීමට නොහැකි වනු ඇත. Log in automatically without asking for the password - + මුරපදය ඉල්ලන්නේ නැතිව ස්වයංක්‍රීයව ලොග් වන්න Only letters, numbers, underscore and hyphen are allowed, minimal of two characters. - + අකුරු, ඉලක්කම්, යටි ඉරි සහ යටි ඉරි පමණක් ඉඩ දෙනු ලැබේ, අවම වශයෙන් අක්ෂර දෙකක්. Reuse user password as root password - + පරිශීලක මුරපදය root මුරපදය ලෙස නැවත භාවිතා කරන්න Use the same password for the administrator account. - + පරිපාලක ගිණුම සඳහා එකම මුරපදය භාවිතා කරන්න. Choose a root password to keep your account safe. - + ඔබගේ ගිණුම ආරක්ෂිතව තබා ගැනීමට root මුරපදයක් තෝරන්න. Root Password - + Root මුරපදය Repeat Root Password - + Root මුරපදය නැවත ඇතුල් කරන්න Enter the same password twice, so that it can be checked for typing errors. - + එකම මුරපදය දෙවරක් ඇතුල් කරන්න, එවිට එය ටයිප් කිරීමේ දෝෂ සඳහා පරීක්ෂා කළ හැක. @@ -4352,32 +4397,33 @@ Output: <h3>Welcome to the %1 <quote>%2</quote> installer</h3> <p>This program will ask you some questions and set up %1 on your computer.</p> - + <h3>%1 <quote>%2</quote> ස්ථාපකය වෙත සාදරයෙන් පිළිගනිමු</h3> + <p>මෙම වැඩසටහන ඔබෙන් ප්‍රශ්න කිහිපයක් අසන අතර ඔබේ පරිගණකයේ %1 පිහිටුවනු ඇත.</p> About - + ගැන Support - + සහාය Known issues - + දන්නා ගැටළු Release notes - + නිකුත් කිරීමේ සටහන් Donate - + පරිත්‍යාග කරන්න diff --git a/lang/calamares_tr_TR.ts b/lang/calamares_tr_TR.ts index 859b81538..52b9c70c3 100644 --- a/lang/calamares_tr_TR.ts +++ b/lang/calamares_tr_TR.ts @@ -690,27 +690,27 @@ Yükleyiciden çıkınca tüm değişiklikler kaybedilecek. Successfully unmounted %1. - + %1 bağlantısı başarıyla kaldırıldı. Successfully disabled swap %1. - + %1 takas alanı başarıyla devre dışı bırakıldı. Successfully cleared swap %1. - + %1 takas alanı başarıyla temizlendi. Successfully closed mapper device %1. - + %1 eşleyici aygıtı başarıyla kapatıldı. Successfully disabled volume group %1. - + %1 birim grubu başarıyla devre dışı bırakıldı. @@ -862,17 +862,17 @@ Kurulum devam edebilir fakat bazı özellikler devre dışı kalabilir. <h1>Welcome to %1 setup</h1> - <h1>%1 kurulumuna hoşgeldiniz</h1> + <h1>%1 kurulumuna hoş geldiniz</h1> <h1>Welcome to the Calamares installer for %1</h1> - <h1>%1 Calamares Sistem Yükleyiciye Hoşgeldiniz</h1> + <h1>%1 Calamares Sistem Yükleyiciye Hoş Geldiniz</h1> <h1>Welcome to the %1 installer</h1> - <h1>%1 Sistem Yükleyiciye Hoşgeldiniz</h1> + <h1>%1 Sistem Yükleyiciye Hoş Geldiniz</h1> @@ -3978,7 +3978,7 @@ Output: <h1>Welcome to %1 setup.</h1> - <h1>%1 Kurulumuna Hoşgeldiniz.</h1> + <h1>%1 Kurulumuna Hoş Geldiniz.</h1> @@ -4016,7 +4016,7 @@ Output: Welcome - Hoşgeldiniz + Hoş geldiniz @@ -4024,7 +4024,7 @@ Output: Welcome - Hoşgeldiniz + Hoş geldiniz From dd2e14853c68f88aa8137591eb0b5e6f8e086db0 Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Mon, 15 Nov 2021 11:03:41 +0100 Subject: [PATCH 034/127] i18n: Update language lists --- CMakeLists.txt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index b50070f4e..9ca061f0c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -133,12 +133,12 @@ set( CALAMARES_DESCRIPTION_SUMMARY # `txstats.py -e`. See also # # Total 81 languages -set( _tx_complete az az_AZ ca cs_CZ fi_FI he hi hr ja ko lt pt_BR - pt_PT sq sv tr_TR uk zh_CN zh_TW ) -set( _tx_good as be ca@valencia da de fr fur it_IT ml nl ru sk tg - vi ) +set( _tx_complete az az_AZ ca de fi_FI he hr ja ko lt pt_PT si sq + tr_TR uk zh_TW ) +set( _tx_good as be ca@valencia cs_CZ da fr fur hi it_IT ml nl + pt_BR ru sk sv tg vi zh_CN ) set( _tx_ok ar ast bg bn el en_GB es es_MX et eu fa gl hu id is mr - nb pl ro si sl sr sr@latin th ) + nb pl ro sl sr sr@latin th ) set( _tx_incomplete en_HK en_IN eo es_PE es_PR fr_CH gu hi_IN id_ID ie kk kn ko_KR lo lv mk ne ne_NP te te_IN ur zh zh_HK ) From f0958535df082f7b394cc722c092d2862c18d052 Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Mon, 15 Nov 2021 11:14:31 +0100 Subject: [PATCH 035/127] CI: Update release instructions --- ci/RELEASE.md | 36 ++++++++++++++++++++---------------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/ci/RELEASE.md b/ci/RELEASE.md index 5289da5af..28e97e4a6 100644 --- a/ci/RELEASE.md +++ b/ci/RELEASE.md @@ -29,7 +29,8 @@ * Double-check the *CALAMARES_VERSION* value at the top of `CMakeLists.txt`. * Set *CALAMARES_RELEASE_MODE* to `ON` in `CMakeLists.txt`. -* Edit `CHANGES` and set the date of the release. +* Edit `CHANGES-*` and set the date of the release. Pick the right + file for the release-stream. * Commit both. This is usually done with commit-message *Changes: pre-release housekeeping*. @@ -81,24 +82,12 @@ Follow the instructions printed by the release script. * Bump the version number in `CMakeLists.txt` in *CALAMARES_VERSION*. * Set *CALAMARES_RELEASE_MODE* back to `OFF`. -* Add a placeholder entry for the next release in `CHANGES` with date - text *not released yet*. +* Add a placeholder entry for the next release in `CHANGES-*` with date + text *not released yet*. See the text below, "Placeholder Release". + Add the placeholder to the right file for the release-stream. * Commit and push that, usually with the message *Changes: post-release housekeeping*. -``` -# 3.2.XX (unreleased) # - -This release contains contributions from (alphabetically by first name): - - No external contributors yet - -## Core ## - - No core changes yet - -## Modules ## - - No module changes yet -``` - # Related Material > This section isn't directly related to any specific release, @@ -139,3 +128,18 @@ ssb rsa3072/0xCFDDC96F12B1915C - Upload that public key to the relevant GitHub profile. - Upload that public key to the Calamares site. +## Placeholder Release Notes + +``` +# 3.2.XX (unreleased) # + +This release contains contributions from (alphabetically by first name): + - No external contributors yet + +## Core ## + - No core changes yet + +## Modules ## + - No module changes yet +``` + From 126838fe1d55f003da55bd2c1b656302926fe5ad Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Mon, 15 Nov 2021 11:27:20 +0100 Subject: [PATCH 036/127] Changes: post-release housekeeping --- CHANGES-3.2 | 23 +++++++++++++++++++++++ CMakeLists.txt | 4 ++-- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/CHANGES-3.2 b/CHANGES-3.2 index 0ada9b89a..36c54b6f6 100644 --- a/CHANGES-3.2 +++ b/CHANGES-3.2 @@ -7,6 +7,29 @@ contributors are listed. Note that Calamares does not have a historical changelog -- this log starts with version 3.2.0. The release notes on the website will have to do for older versions. +# 3.2.47 (unreleased) # + +This release contains contributions from (alphabetically by first name): + - Evan James + +## Core ## + - The translation for Sinhala (`si`) has reached 100%. Thank you to + හෙළබස and Sandaruwan, translators for Sinhala, for special effort + in completing that translation. + +## Modules ## + - *finishedq* now has an extra example QML file that builds the UI in + a different fashion, demonstrating how a mobile-OS customization of + Calamares would present the "all done" message. + - *fstab* has an example configuration file that mentioned `space_cache` + as an option. Since 2014 there was only one possible value, so this + option matched the default-and-only value. Newer kernels with newer + btrfs versions have a `v2` option value as well. Remove the example + option, since the kernel automatically picks the right value, while + setting it to the wrong one may prevent the system from booting. + (Thanks Evan) + + # 3.2.46 (2021-11-09) # This release contains contributions from (alphabetically by first name): diff --git a/CMakeLists.txt b/CMakeLists.txt index 9ca061f0c..fd9ec6eaa 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -41,11 +41,11 @@ # TODO:3.3: Require CMake 3.12 cmake_minimum_required( VERSION 3.3 FATAL_ERROR ) project( CALAMARES - VERSION 3.2.46 + VERSION 3.2.47 LANGUAGES C CXX ) -set( CALAMARES_VERSION_RC 0 ) # Set to 0 during release cycle, 1 during development +set( CALAMARES_VERSION_RC 1 ) # Set to 0 during release cycle, 1 during development ### OPTIONS # From 85f36c77b1cf066a0a6566b114a9de2ec9c819e4 Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Mon, 15 Nov 2021 11:42:25 +0100 Subject: [PATCH 037/127] [displaymanager] Import configparser only for the DMs that actually need it --- src/modules/displaymanager/main.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/modules/displaymanager/main.py b/src/modules/displaymanager/main.py index 2fc4bead1..a85781c7b 100644 --- a/src/modules/displaymanager/main.py +++ b/src/modules/displaymanager/main.py @@ -19,7 +19,6 @@ import abc import os import toml import libcalamares -import configparser from libcalamares.utils import gettext_path, gettext_languages @@ -796,6 +795,8 @@ class DMsddm(DisplayManager): executable = "sddm" def set_autologin(self, username, do_autologin, default_desktop_environment): + import configparser + # Systems with Sddm as Desktop Manager sddm_conf_path = os.path.join(self.root_mount_point, "etc/sddm.conf") From 58cf9ffeeb6622dd8aa699d20f801f1bee42661c Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Mon, 15 Nov 2021 11:43:47 +0100 Subject: [PATCH 038/127] [displaymanager] Import toml only for the DMs that actually need it --- src/modules/displaymanager/main.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/modules/displaymanager/main.py b/src/modules/displaymanager/main.py index a85781c7b..287e4cc6b 100644 --- a/src/modules/displaymanager/main.py +++ b/src/modules/displaymanager/main.py @@ -17,7 +17,6 @@ import abc import os -import toml import libcalamares from libcalamares.utils import gettext_path, gettext_languages @@ -853,6 +852,8 @@ class DMgreetd(DisplayManager): return self.os_path("etc/greetd/environments") def config_load(self): + import toml + if (os.path.exists(self.config_path)): self.config_data = toml.loads(self.config_path()) @@ -866,6 +867,7 @@ class DMgreetd(DisplayManager): return self.config_data def config_write(self): + import toml toml.dump(self.config_data, self.config_path()) def basic_setup(self): From fad2f6ea887b669423643b43bcf85aed785ad31c Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Mon, 15 Nov 2021 11:52:24 +0100 Subject: [PATCH 039/127] [displaymanager] Add simple test --- src/modules/displaymanager/tests/1.global | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 src/modules/displaymanager/tests/1.global diff --git a/src/modules/displaymanager/tests/1.global b/src/modules/displaymanager/tests/1.global new file mode 100644 index 000000000..9b3f0ea7c --- /dev/null +++ b/src/modules/displaymanager/tests/1.global @@ -0,0 +1,3 @@ +# SPDX-FileCopyrightText: no +# SPDX-License-Identifier: CC0-1.0 +rootMountPoint: / From 11424195ef7932e62901a984f3e53cf8ba21c097 Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Mon, 15 Nov 2021 12:06:05 +0100 Subject: [PATCH 040/127] [displaymanager] Missing method call - Add `()` to call the config_path() method, because we need a path to pass to os.path.exists(). --- src/modules/displaymanager/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/displaymanager/main.py b/src/modules/displaymanager/main.py index 287e4cc6b..d9832730b 100644 --- a/src/modules/displaymanager/main.py +++ b/src/modules/displaymanager/main.py @@ -854,7 +854,7 @@ class DMgreetd(DisplayManager): def config_load(self): import toml - if (os.path.exists(self.config_path)): + if (os.path.exists(self.config_path())): self.config_data = toml.loads(self.config_path()) self.config_data['terminal'] = dict(vt = "next") From 54fd81a87e2265c1eaeeae12cacbcd4d5b065ac2 Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Mon, 15 Nov 2021 12:07:59 +0100 Subject: [PATCH 041/127] [displaymanager] Handle case where config file doesn't exist or has no key - If the config file doesn't exist, the dictionary is empty - If it **does** exist, it might not have key 'default_session' in it Either case should avoid a KeyError by using get() (or setdefault, in this context). Subsequent use of os.path.exists() is strange, since the value is a **group** (e.g. a dictionary) in the config file. Just check if it exists, and then fill something in. --- src/modules/displaymanager/main.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/modules/displaymanager/main.py b/src/modules/displaymanager/main.py index d9832730b..5b92ccf8d 100644 --- a/src/modules/displaymanager/main.py +++ b/src/modules/displaymanager/main.py @@ -859,7 +859,8 @@ class DMgreetd(DisplayManager): self.config_data['terminal'] = dict(vt = "next") - if not os.path.exists(self.config_data['default_session']): + default_session_group = self.config_data.get('default_session', None) + if not default_session_group: self.config_data['default_session'] = {} self.config_data['default_session']['user'] = self.greeter_user From ce6aec158a1da66e0e1cc5c60c6d95a2193d8eed Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Mon, 15 Nov 2021 12:11:53 +0100 Subject: [PATCH 042/127] [displaymanager] Fix config loading-and-saving - toml.dump() takes a file-like object - toml.loads() takes a whole string to parse, (e.g. the TOML data), not a pathname, so change to toml.load() which takes a file-like object. --- src/modules/displaymanager/main.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/modules/displaymanager/main.py b/src/modules/displaymanager/main.py index 5b92ccf8d..3219e803f 100644 --- a/src/modules/displaymanager/main.py +++ b/src/modules/displaymanager/main.py @@ -855,7 +855,8 @@ class DMgreetd(DisplayManager): import toml if (os.path.exists(self.config_path())): - self.config_data = toml.loads(self.config_path()) + with open(self.config_path(), "r") as f: + self.config_data = toml.load(f) self.config_data['terminal'] = dict(vt = "next") @@ -869,7 +870,8 @@ class DMgreetd(DisplayManager): def config_write(self): import toml - toml.dump(self.config_data, self.config_path()) + with open(self.config_path(), "w") as f: + toml.dump(self.config_data, f) def basic_setup(self): if libcalamares.utils.target_env_call( From f3e85efd415f6562f0e24954ab659b4d690f6728 Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Mon, 15 Nov 2021 12:49:20 +0100 Subject: [PATCH 043/127] [displaymanager] Add tests that run parts of the DM code - load and set autologin for greetd (this was used to shake out code bugs in load/save) - load and set autologin for sddm --- .../displaymanager/tests/CMakeTests.txt | 10 ++++++++++ .../displaymanager/tests/test-dm-greetd.py | 18 ++++++++++++++++++ .../displaymanager/tests/test-dm-sddm.py | 12 ++++++++++++ 3 files changed, 40 insertions(+) create mode 100644 src/modules/displaymanager/tests/CMakeTests.txt create mode 100644 src/modules/displaymanager/tests/test-dm-greetd.py create mode 100644 src/modules/displaymanager/tests/test-dm-sddm.py diff --git a/src/modules/displaymanager/tests/CMakeTests.txt b/src/modules/displaymanager/tests/CMakeTests.txt new file mode 100644 index 000000000..39c1291ca --- /dev/null +++ b/src/modules/displaymanager/tests/CMakeTests.txt @@ -0,0 +1,10 @@ +# We have tests to load (some) of the DMs specifically, to test their +# configuration code. Those tests conventionally live in Python +# files here in the tests/ directory. Add them. +foreach(_dmname greetd sddm) + add_test( + NAME configure-displaymanager-${_dmname} + COMMAND env PYTHONPATH=.: python3 ${CMAKE_CURRENT_LIST_DIR}/test-dm-${_dmname}.py + WORKING_DIRECTORY ${CMAKE_BINARY_DIR} + ) +endforeach() diff --git a/src/modules/displaymanager/tests/test-dm-greetd.py b/src/modules/displaymanager/tests/test-dm-greetd.py new file mode 100644 index 000000000..8988aaa52 --- /dev/null +++ b/src/modules/displaymanager/tests/test-dm-greetd.py @@ -0,0 +1,18 @@ +# Calamares Boilerplate +import libcalamares +libcalamares.globalstorage = libcalamares.GlobalStorage(None) +libcalamares.globalstorage.insert("testing", True) +# Module prep-work +import os +os.makedirs("/tmp/etc/greetd/", exist_ok=True) +try: + os.remove("/tmp/etc/greetd/config.toml") +except FileNotFoundError as e: + pass +# Module test +from src.modules.displaymanager import main +d = main.DMgreetd("/tmp") +d.set_autologin("d", True, "kde") +# .. and again (this time checks load/save) +d.set_autologin("d", True, "kde") +d.set_autologin("d", True, "kde") diff --git a/src/modules/displaymanager/tests/test-dm-sddm.py b/src/modules/displaymanager/tests/test-dm-sddm.py new file mode 100644 index 000000000..eb65d95f4 --- /dev/null +++ b/src/modules/displaymanager/tests/test-dm-sddm.py @@ -0,0 +1,12 @@ +# Calamares Boilerplate +import libcalamares +libcalamares.globalstorage = libcalamares.GlobalStorage(None) +libcalamares.globalstorage.insert("testing", True) +# Module prep-work +# Module test +from src.modules.displaymanager import main +d = main.DMsddm("/tmp") +d.set_autologin("d", True, "kde") +# .. and again (this time checks load/save) +d.set_autologin("d", True, "kde") +d.set_autologin("d", True, "kde") From 16a029abd29a3a09da85c13c1563258b78d6346d Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Mon, 15 Nov 2021 13:37:23 +0100 Subject: [PATCH 044/127] [displaymanager] Adjust tests to match real runtime - the default_desktop_environment isn't a string, but an object; it is unusual for it to be used in set_autologin --- src/modules/displaymanager/tests/test-dm-greetd.py | 14 +++++++++----- src/modules/displaymanager/tests/test-dm-sddm.py | 11 +++++++---- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/src/modules/displaymanager/tests/test-dm-greetd.py b/src/modules/displaymanager/tests/test-dm-greetd.py index 8988aaa52..6be0e3d47 100644 --- a/src/modules/displaymanager/tests/test-dm-greetd.py +++ b/src/modules/displaymanager/tests/test-dm-greetd.py @@ -2,17 +2,21 @@ import libcalamares libcalamares.globalstorage = libcalamares.GlobalStorage(None) libcalamares.globalstorage.insert("testing", True) + # Module prep-work +from src.modules.displaymanager import main +default_desktop_environment = main.DesktopEnvironment("startplasma-x11", "kde-plasma.desktop") + import os os.makedirs("/tmp/etc/greetd/", exist_ok=True) try: os.remove("/tmp/etc/greetd/config.toml") except FileNotFoundError as e: pass -# Module test -from src.modules.displaymanager import main + +# Specific DM test d = main.DMgreetd("/tmp") -d.set_autologin("d", True, "kde") +d.set_autologin("d", True, default_desktop_environment) # .. and again (this time checks load/save) -d.set_autologin("d", True, "kde") -d.set_autologin("d", True, "kde") +d.set_autologin("d", True, default_desktop_environment) +d.set_autologin("d", True, default_desktop_environment) diff --git a/src/modules/displaymanager/tests/test-dm-sddm.py b/src/modules/displaymanager/tests/test-dm-sddm.py index eb65d95f4..5e4b54b30 100644 --- a/src/modules/displaymanager/tests/test-dm-sddm.py +++ b/src/modules/displaymanager/tests/test-dm-sddm.py @@ -2,11 +2,14 @@ import libcalamares libcalamares.globalstorage = libcalamares.GlobalStorage(None) libcalamares.globalstorage.insert("testing", True) + # Module prep-work -# Module test from src.modules.displaymanager import main +default_desktop_environment = main.DesktopEnvironment("startplasma-x11", "kde-plasma.desktop") + +# Specific DM test d = main.DMsddm("/tmp") -d.set_autologin("d", True, "kde") +d.set_autologin("d", True, default_desktop_environment) # .. and again (this time checks load/save) -d.set_autologin("d", True, "kde") -d.set_autologin("d", True, "kde") +d.set_autologin("d", True, default_desktop_environment) +d.set_autologin("d", True, default_desktop_environment) From 5f7b221e11d758269dff21b0ad8e63b43de55fb9 Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Mon, 15 Nov 2021 13:38:46 +0100 Subject: [PATCH 045/127] [displaymanager] Fix greetd commands - since default_desktop_environment isn't a string, need to pick the string -- the command -- out of the object first. --- src/modules/displaymanager/main.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/modules/displaymanager/main.py b/src/modules/displaymanager/main.py index 3219e803f..3cfdb069a 100644 --- a/src/modules/displaymanager/main.py +++ b/src/modules/displaymanager/main.py @@ -903,18 +903,19 @@ class DMgreetd(DisplayManager): def set_autologin(self, username, do_autologin, default_desktop_environment): self.config_load() + de_command = default_desktop_environment.executable if os.path.exists(self.os_path("usr/bin/gtkgreed")) and os.path.exists(self.os_path("usr/bin/cage")): self.config_data['default_session']['command'] = "cage -s -- gtkgreet" elif os.path.exists(self.os_path("usr/bin/tuigreet")): tuigreet_base_cmd = "tuigreet --remember --time --issue --asterisks --cmd " - self.config_data['default_session']['command'] = tuigreet_base_cmd + default_desktop_environment + self.config_data['default_session']['command'] = tuigreet_base_cmd + de_command elif os.path.exists(self.os_path("usr/bin/ddlm")): - self.config_data['default_session']['command'] = "ddlm --target " + default_desktop_environment + self.config_data['default_session']['command'] = "ddlm --target " + de_command else: - self.config_data['default_session']['command'] = "agreety --cmd " + default_desktop_environment + self.config_data['default_session']['command'] = "agreety --cmd " + de_command if do_autologin == True: - self.config_data['initial_session'] = dict(command = default_desktop_environment, user = username) + self.config_data['initial_session'] = dict(command = de_command, user = username) self.config_write() From d5bef9efb514046050f6e08b70bbed7247a1a62f Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Mon, 15 Nov 2021 21:48:17 +0100 Subject: [PATCH 046/127] Python: document which exception is thrown on process failure --- src/modules/README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/modules/README.md b/src/modules/README.md index 683ca9bc3..7b4d4b3a3 100644 --- a/src/modules/README.md +++ b/src/modules/README.md @@ -398,7 +398,9 @@ target_env_process_output(["ls"]) ``` The functions return 0. If the exit code of *command* is not 0, an exception -is raised instead of returning 0. +is raised instead of returning 0. The exception is `subprocess.CalledProcessError` +(as if the *subprocess* module had been used), and the `returncode` member +of the exception object can be used to determine the exit code. Parameter *stdin* may be a string which is fed to the command as standard input. The *timeout* is in seconds, with 0 (or a negative number) treated as no-timeout. From 3dd02edc78a24e413ef7baac9d1c10089b3be034 Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Mon, 15 Nov 2021 23:16:58 +0100 Subject: [PATCH 047/127] [libcalamares] Document how to interpret percents - use 0..1 in floats for percentages (I suppose you could call that a perunage, but that would be weird). --- src/libcalamares/Job.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/libcalamares/Job.h b/src/libcalamares/Job.h index 58a9bba39..33965e15f 100644 --- a/src/libcalamares/Job.h +++ b/src/libcalamares/Job.h @@ -132,6 +132,11 @@ public: void setEmergency( bool e ) { m_emergency = e; } signals: + /** @brief Signals that the job has made progress + * + * The parameter @p percent should be between 0 (0%) and 1 (100%). + * Values outside of this range will be clamped. + */ void progress( qreal percent ); private: From abb6f73725073ef7f44c25de6b9400d84a48151b Mon Sep 17 00:00:00 2001 From: dalto Date: Mon, 15 Nov 2021 17:59:33 -0600 Subject: [PATCH 048/127] [partition] zfs changes from review feedback --- src/modules/bootloader/main.py | 43 ++++++++++++++++++++++------------ 1 file changed, 28 insertions(+), 15 deletions(-) diff --git a/src/modules/bootloader/main.py b/src/modules/bootloader/main.py index 10e7d0f47..54914beb7 100644 --- a/src/modules/bootloader/main.py +++ b/src/modules/bootloader/main.py @@ -108,16 +108,34 @@ def get_zfs_root(): # Find the root dataset for dataset in zfs: try: - if dataset['mountpoint'] == '/': + if dataset["mountpoint"] == "/": return dataset["zpool"] + "/" + dataset["dsname"] except KeyError: # This should be impossible - libcalamares.utils.error("Internal error handling zfs dataset") + 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 create_systemd_boot_conf(install_path, efi_dir, uuid, entry, entry_name, kernel_type): """ Creates systemd-boot configuration files based on given parameters. @@ -162,17 +180,18 @@ def create_systemd_boot_conf(install_path, efi_dir, uuid, entry, entry_name, ker for partition in partitions: # systemd-boot with a BTRFS root filesystem needs to be told # about the root subvolume. - if partition["mountPoint"] == "/" and partition["fs"] == "btrfs": + if is_btrfs_root(partition): kernel_params.append("rootflags=subvol=@") # zfs needs to be told the location of the root dataset - if partition["mountPoint"] == "/" and partition["fs"] == "zfs": - zfs_root = get_zfs_root - if zfs_root is not None: - kernel_params.append("root=ZFS=" + zfs_root) + 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.error("Internal error handling zfs dataset") + 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) @@ -363,14 +382,8 @@ def run_grub_mkconfig(output_file): libcalamares.utils.error("Failed to run grub-mkconfig, no partitions defined in global storage") return - # check for zfs - is_zfs = False - for partition in partitions: - if partition["mountPoint"] == "/" and partition["fs"] == "zfs": - is_zfs = True - # zfs needs an environment variable set for grub-mkconfig - if is_zfs: + if any([is_zfs_root(partition) for partition in partitions]): check_target_env_call(["sh", "-c", "echo ZPOOL_VDEV_NAME_PATH=1 >> /etc/environment"]) check_target_env_call(["sh", "-c", "ZPOOL_VDEV_NAME_PATH=1 " + libcalamares.job.configuration["grubMkconfig"] + " -o " + output_file]) From e861d8b319d1507e114c76159e32509522975f49 Mon Sep 17 00:00:00 2001 From: dalto Date: Mon, 15 Nov 2021 18:00:04 -0600 Subject: [PATCH 049/127] [mount] zfs changes from review feedback --- src/modules/mount/main.py | 76 ++++++++++++++++++++++++--------------- 1 file changed, 48 insertions(+), 28 deletions(-) diff --git a/src/modules/mount/main.py b/src/modules/mount/main.py index 72c2015c6..056b0b415 100644 --- a/src/modules/mount/main.py +++ b/src/modules/mount/main.py @@ -26,6 +26,17 @@ _ = gettext.translation("calamares-python", 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.") @@ -61,7 +72,7 @@ def get_btrfs_subvolumes(partitions): return btrfs_subvolumes -def parse_global_storage(gs_value): +def bool_to_zfs_command(gs_value): """ Something in the chain is converting on and off to true and false. This converts it back. :param gs_value: The value from global storage which needs to be fixed @@ -86,7 +97,7 @@ def mount_zfs(root_mount_point, partition): zfs_pool_list = libcalamares.globalstorage.value("zfsPoolInfo") if not zfs_pool_list: libcalamares.utils.warning("Failed to locate zfsPoolInfo data in global storage") - raise Exception("Internal error mounting zfs datasets") + raise ZfsException(_("Internal error mounting zfs datasets")) # Find the zpool matching this partition for zfs_pool in zfs_pool_list: @@ -95,9 +106,10 @@ def mount_zfs(root_mount_point, partition): ds_name = zfs_pool["dsName"] # import the zpool - import_result = subprocess.run(['zpool', 'import', '-R', root_mount_point, pool_name]) - if import_result.returncode != 0: - raise Exception("Failed to import zpool") + try: + libcalamares.utils.host_env_process_output(["zpool", "import", "-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") @@ -110,9 +122,11 @@ def mount_zfs(root_mount_point, partition): if encrypt is True: # The zpool is encrypted, we need to unlock it - loadkey_result = subprocess.run(['sh', '-c', 'echo "' + passphrase + '" | zfs load-key ' + pool_name]) - if loadkey_result.returncode != 0: - raise Exception("Failed to unlock zpool") + try: + libcalamares.utils.host_env_process_output(["sh", "-c", + "echo \"" + passphrase + "\" | zfs load-key " + pool_name], None) + except subprocess.CalledProcessError: + raise ZfsException(_("Failed to unlock zpool")) if partition["mountPoint"] == '/': # Get the zfs dataset list from global storage @@ -120,36 +134,39 @@ def mount_zfs(root_mount_point, partition): if not zfs: libcalamares.utils.warning("Failed to locate zfs dataset list") - raise Exception("Internal error mounting zfs datasets") + raise ZfsException(_("Internal error mounting zfs datasets")) # first we handle the / dataset if there is one for dataset in zfs: if dataset['mountpoint'] == '/': # Properly set the canmount field from global storage - can_mount = parse_global_storage(dataset['canMount']) - set_result = subprocess.run(['zfs', 'set', 'canmount=' + can_mount, - dataset['zpool'] + '/' + dataset['dsName']]) - if set_result.returncode != 0: - raise Exception("Failed to set zfs mountpoint") - if dataset['canMount'] == 'noauto': - mount_result = subprocess.run(['zfs', 'mount', dataset['zpool'] + '/' + dataset['dsName']]) + can_mount = bool_to_zfs_command(dataset['canMount']) + try: + libcalamares.utils.host_env_process_output(["zfs", "set", "canmount=" + can_mount, + dataset["zpool"] + "/" + dataset["dsName"]], None) + except subprocess.CalledProcessError: + raise ZfsException(_("Failed to set zfs mountpoint")) + if dataset["canMount"] == "noauto": + mount_result = subprocess.run(["zfs", "mount", dataset["zpool"] + '/' + dataset["dsName"]]) if mount_result.returncode != 0: - raise Exception("Failed to mount root dataset") + raise ZfsException(_("Failed to mount root dataset")) # Set the canmount property for each dataset. This will effectively mount the dataset for dataset in zfs: # We already handled the / mountpoint above - if dataset['mountpoint'] != '/': - can_mount = parse_global_storage(dataset['canMount']) + if dataset["mountpoint"] != '/': + can_mount = bool_to_zfs_command(dataset["canMount"]) - set_result = subprocess.run(['zfs', 'set', 'canmount=' + can_mount, - dataset['zpool'] + '/' + dataset['dsName']]) - if set_result.returncode != 0: - raise Exception("Failed to set zfs mountpoint") + try: + libcalamares.utils.host_env_process_output(["zfs", "set", "canmount=" + can_mount, + dataset["zpool"] + "/" + dataset["dsName"]], None) + except subprocess.CalledProcessError: + raise ZfsException(_("Failed to set zfs mountpoint")) else: - set_result = subprocess.run(['zfs', 'set', 'canmount=on', pool_name + '/' + ds_name]) - if set_result.returncode != 0: - raise Exception("Failed to set zfs mountpoint") + try: + libcalamares.utils.host_env_process_output(["zfs", "set", "canmount=on", pool_name + "/" + ds_name], None) + except subprocess.CalledProcessError: + raise ZfsException(_("Failed to set zfs mountpoint")) def mount_partition(root_mount_point, partition, partitions): @@ -255,8 +272,11 @@ def run(): # 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"]) - for partition in mountable_partitions: - mount_partition(root_mount_point, partition, partitions) + try: + for partition in mountable_partitions: + mount_partition(root_mount_point, partition, partitions) + except ZfsException as ze: + return _("zfs mounting error"), ze.message libcalamares.globalstorage.insert("rootMountPoint", root_mount_point) From ca3f0e289262180388bd6d4a70b2ca94f0b34daf Mon Sep 17 00:00:00 2001 From: dalto Date: Sat, 6 Nov 2021 09:16:09 -0500 Subject: [PATCH 050/127] [partition] Add zfs to the filesystem list if the zfs modules is enabled --- src/modules/partition/gui/CreatePartitionDialog.cpp | 5 ++++- src/modules/partition/gui/EditExistingPartitionDialog.cpp | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/modules/partition/gui/CreatePartitionDialog.cpp b/src/modules/partition/gui/CreatePartitionDialog.cpp index cdc9992b9..0727ee935 100644 --- a/src/modules/partition/gui/CreatePartitionDialog.cpp +++ b/src/modules/partition/gui/CreatePartitionDialog.cpp @@ -23,6 +23,7 @@ #include "GlobalStorage.h" #include "JobQueue.h" +#include "Settings.h" #include "partition/FileSystem.h" #include "partition/PartitionQuery.h" #include "utils/Logger.h" @@ -104,7 +105,9 @@ CreatePartitionDialog::CreatePartitionDialog( Device* device, QStringList fsNames; for ( auto fs : FileSystemFactory::map() ) { - if ( fs->supportCreate() != FileSystem::cmdSupportNone && fs->type() != FileSystem::Extended ) + // We need to ensure zfs is added to the list if the zfs module is enabled + if ( ( fs->type() == FileSystem::Type::Zfs && Calamares::Settings::instance()->isModuleEnabled( "zfs" ) ) + || ( fs->supportCreate() != FileSystem::cmdSupportNone && fs->type() != FileSystem::Extended ) ) { fsNames << userVisibleFS( fs ); // This is put into the combobox if ( fs->type() == defaultFSType ) diff --git a/src/modules/partition/gui/EditExistingPartitionDialog.cpp b/src/modules/partition/gui/EditExistingPartitionDialog.cpp index 411d6d0dc..04b9527f1 100644 --- a/src/modules/partition/gui/EditExistingPartitionDialog.cpp +++ b/src/modules/partition/gui/EditExistingPartitionDialog.cpp @@ -25,6 +25,7 @@ #include "GlobalStorage.h" #include "JobQueue.h" +#include "Settings.h" #include "partition/FileSystem.h" #include "utils/Logger.h" @@ -89,7 +90,9 @@ EditExistingPartitionDialog::EditExistingPartitionDialog( Device* device, QStringList fsNames; for ( auto fs : FileSystemFactory::map() ) { - if ( fs->supportCreate() != FileSystem::cmdSupportNone && fs->type() != FileSystem::Extended ) + // We need to ensure zfs is added to the list if the zfs module is enabled + if ( ( fs->type() == FileSystem::Type::Zfs && Calamares::Settings::instance()->isModuleEnabled( "zfs" ) ) + || ( fs->supportCreate() != FileSystem::cmdSupportNone && fs->type() != FileSystem::Extended ) ) { fsNames << userVisibleFS( fs ); // For the combo box } From 24a376493b5e33d59a25a2383e1534f7924e90d1 Mon Sep 17 00:00:00 2001 From: dalto Date: Sat, 6 Nov 2021 09:42:07 -0500 Subject: [PATCH 051/127] [partition] Add support for manually creating a partition for zfs --- .../partition/jobs/CreatePartitionJob.cpp | 80 +++++++++++++++++++ 1 file changed, 80 insertions(+) diff --git a/src/modules/partition/jobs/CreatePartitionJob.cpp b/src/modules/partition/jobs/CreatePartitionJob.cpp index 241e0a451..07b816b7e 100644 --- a/src/modules/partition/jobs/CreatePartitionJob.cpp +++ b/src/modules/partition/jobs/CreatePartitionJob.cpp @@ -11,8 +11,10 @@ #include "CreatePartitionJob.h" +#include "core/PartitionInfo.h" #include "partition/FileSystem.h" #include "partition/PartitionQuery.h" +#include "utils/CalamaresUtilsSystem.h" #include "utils/Logger.h" #include "utils/Units.h" @@ -24,9 +26,80 @@ #include #include +#include +#include + using CalamaresUtils::Partition::untranslatedFS; using CalamaresUtils::Partition::userVisibleFS; +/** @brief Create + * + * Uses sfdisk to remove @p partition. This should only be used in cases + * where using kpmcore to remove the partition would not be appropriate + * + */ +static Calamares::JobResult +createZfs( Partition* partition, Device* device ) +{ + auto r = CalamaresUtils::System::instance()->runCommand( + { "sh", + "-c", + "echo start=" + QString::number( partition->firstSector() ) + " size=" + + QString::number( partition->length() ) + " | sfdisk --append --force " + partition->devicePath() }, + std::chrono::seconds( 5 ) ); + if ( r.getExitCode() != 0 ) + { + return Calamares::JobResult::error( + QCoreApplication::translate( CreatePartitionJob::staticMetaObject.className(), + "Failed to create partition" ), + QCoreApplication::translate( CreatePartitionJob::staticMetaObject.className(), + "Failed to create zfs partition with output: " + + r.getOutput().toLocal8Bit() ) ); + } + + // Now we need to do some things that would normally be done by kpmcore + + // First we get the device node from the output and set it as the partition path + QRegularExpression re( QStringLiteral( "Created a new partition (\\d+)" ) ); + QRegularExpressionMatch rem = re.match( r.getOutput() ); + + QString deviceNode; + if ( rem.hasMatch() ) + { + if ( partition->devicePath().back().isDigit() ) + { + deviceNode = partition->devicePath() + QLatin1Char( 'p' ) + rem.captured( 1 ); + } + else + { + deviceNode = partition->devicePath() + rem.captured( 1 ); + } + } + + partition->setPartitionPath( deviceNode ); + partition->setState( Partition::State::None ); + + // If it is a gpt device, set the partition UUID + if ( device->partitionTable()->type() == PartitionTable::gpt && partition->uuid().isEmpty() ) + { + r = CalamaresUtils::System::instance()->runCommand( + { "sfdisk", "--list", "--output", "Device,UUID", partition->devicePath() }, std::chrono::seconds( 5 ) ); + if ( r.getExitCode() == 0 ) + { + QRegularExpression re( deviceNode + QStringLiteral( " +(.+)" ) ); + QRegularExpressionMatch rem = re.match( r.getOutput() ); + + if ( rem.hasMatch() ) + { + partition->setUUID( rem.captured( 1 ) ); + } + } + } + + return Calamares::JobResult::ok(); +} + + CreatePartitionJob::CreatePartitionJob( Device* device, Partition* partition ) : PartitionJob( partition ) , m_device( device ) @@ -194,6 +267,13 @@ CreatePartitionJob::prettyStatusMessage() const Calamares::JobResult CreatePartitionJob::exec() { + // kpmcore doesn't currently handle this case properly so for now, we manually create the partion + // The zfs module can later deal with creating a zpool in the partition + if ( m_partition->fileSystem().type() == FileSystem::Type::Zfs ) + { + return createZfs( m_partition, m_device ); + } + Report report( nullptr ); NewOperation op( *m_device, m_partition ); op.setStatus( Operation::StatusRunning ); From 11bf84bac7baddfe97ad6036edaeb7d666b2031d Mon Sep 17 00:00:00 2001 From: dalto Date: Sat, 6 Nov 2021 09:44:27 -0500 Subject: [PATCH 052/127] [zfs] Initial commit for zfs module --- src/modules/zfs/CMakeLists.txt | 13 ++++ src/modules/zfs/ZfsJob.cpp | 118 ++++++++++++++++++++++++++++++++ src/modules/zfs/ZfsJob.h | 50 ++++++++++++++ src/modules/zfs/zfs.conf | 38 ++++++++++ src/modules/zfs/zfs.schema.yaml | 22 ++++++ 5 files changed, 241 insertions(+) create mode 100644 src/modules/zfs/CMakeLists.txt create mode 100644 src/modules/zfs/ZfsJob.cpp create mode 100644 src/modules/zfs/ZfsJob.h create mode 100644 src/modules/zfs/zfs.conf create mode 100644 src/modules/zfs/zfs.schema.yaml diff --git a/src/modules/zfs/CMakeLists.txt b/src/modules/zfs/CMakeLists.txt new file mode 100644 index 000000000..2feb911d0 --- /dev/null +++ b/src/modules/zfs/CMakeLists.txt @@ -0,0 +1,13 @@ +# === This file is part of Calamares - === +# +# SPDX-FileCopyrightText: 2020 Adriaan de Groot +# SPDX-License-Identifier: BSD-2-Clause +# +calamares_add_plugin( zfs + TYPE job + EXPORT_MACRO PLUGINDLLEXPORT_PRO + SOURCES + ZfsJob.cpp + SHARED_LIB +) + diff --git a/src/modules/zfs/ZfsJob.cpp b/src/modules/zfs/ZfsJob.cpp new file mode 100644 index 000000000..2602c5417 --- /dev/null +++ b/src/modules/zfs/ZfsJob.cpp @@ -0,0 +1,118 @@ +/* === This file is part of Calamares - === + * + * SPDX-FileCopyrightText: 2021 Evan James + * SPDX-License-Identifier: GPL-3.0-or-later + * + * Calamares is Free Software: see the License-Identifier above. + * + */ + +#include "ZfsJob.h" + +#include "utils/CalamaresUtilsSystem.h" +#include "utils/Logger.h" +#include "utils/Variant.h" + +#include "GlobalStorage.h" +#include "JobQueue.h" +#include "Settings.h" + +ZfsJob::ZfsJob( QObject* parent ) + : Calamares::CppJob( parent ) +{ +} + +ZfsJob::~ZfsJob() {} + +QString +ZfsJob::prettyName() const +{ + return tr( "Create ZFS pools and datasets" ); +} + +Calamares::JobResult +ZfsJob::exec() +{ + QList< QVariant > partitions; + Calamares::GlobalStorage* gs = Calamares::JobQueue::instance()->globalStorage(); + if ( gs && gs->contains( "partitions" ) && gs->value( "partitions" ).canConvert( QVariant::List ) ) + { + partitions = gs->value( "partitions" ).toList(); + } + else + { + cWarning() << "No *partitions* defined."; + return Calamares::JobResult::internalError( tr( "Configuration Error" ), + tr( "No partitions are available for Zfs." ), + Calamares::JobResult::InvalidConfiguration ); + } + + const CalamaresUtils::System* system = CalamaresUtils::System::instance(); + + for ( auto& partition : qAsConst( partitions ) ) + { + QVariantMap pMap; + if ( partition.canConvert( QVariant::Map ) ) + pMap = partition.toMap(); + + // If it isn't a zfs partition, ignore it + if ( pMap[ "fsName" ] != "zfs" ) + continue; + + // Find the best device identifier, if one isn't available, skip this partition + QString deviceName; + if ( pMap[ "partuuid" ].toString() != "" ) + deviceName = "/dev/disk/by-partuuid/" + pMap[ "partuuid" ].toString().toLower(); + else if ( pMap[ "device" ].toString() != "" ) + deviceName = pMap[ "device" ].toString().toLower(); + else + continue; + + // Create the zpool + auto r + = system->runCommand( { "sh", "-c", "zpool create " + m_poolOptions + " " + m_poolName + " " + deviceName }, + std::chrono::seconds( 10 ) ); + if ( r.getExitCode() != 0 ) + return Calamares::JobResult::error( "message", "Failed to create zpool on " + deviceName ); + + // Create the datasets + for ( const auto& dataset : qAsConst( m_datasets ) ) + { + QVariantMap dsMap = dataset.toMap(); + + // Make sure all values are valid + if ( dsMap[ "dsName" ].toString().isEmpty() || dsMap[ "mountpoint" ].toString().isEmpty() + || dsMap[ "canMount" ].toString().isEmpty() ) + { + cWarning() << "Bad dataset entry"; + continue; + } + + // Create the dataset. We set canmount=no regardless of the setting for now. + // It is modified to the correct value in the mount module to ensure mount order is maintained + r = system->runCommand( { "sh", + "-c", + "zfs create " + m_datasetOptions + + " -o canmount=off -o mountpoint=" + dsMap[ "mountpoint" ].toString() + " " + + m_poolName + "/" + dsMap[ "dsName" ].toString() }, + std::chrono::seconds( 10 ) ); + if ( r.getExitCode() != 0 ) + cWarning() << "Failed to create dataset" << dsMap[ "dsName" ].toString(); + } + } + + return Calamares::JobResult::ok(); +} + + +void +ZfsJob::setConfigurationMap( const QVariantMap& map ) +{ + m_poolName = CalamaresUtils::getString( map, "poolName" ); + m_poolOptions = CalamaresUtils::getString( map, "poolOptions" ); + m_datasetOptions = CalamaresUtils::getString( map, "datasetOptions" ); + + m_datasets = CalamaresUtils::getList( map, "datasets" ); +} + +CALAMARES_PLUGIN_FACTORY_DEFINITION( ZfsJobFactory, registerPlugin< ZfsJob >(); ) diff --git a/src/modules/zfs/ZfsJob.h b/src/modules/zfs/ZfsJob.h new file mode 100644 index 000000000..87646a227 --- /dev/null +++ b/src/modules/zfs/ZfsJob.h @@ -0,0 +1,50 @@ +/* === This file is part of Calamares - === + * + * SPDX-FileCopyrightText: 2021 Evan James + * SPDX-License-Identifier: GPL-3.0-or-later + * + * Calamares is Free Software: see the License-Identifier above. + * + */ + +#ifndef ZFSJOB_H +#define ZFSJOB_H + +#include +#include +#include + +#include "CppJob.h" + +#include "utils/PluginFactory.h" + +#include "DllMacro.h" + +/** @brief Create zpools and zfs datasets + * + */ +class PLUGINDLLEXPORT ZfsJob : public Calamares::CppJob +{ + Q_OBJECT + +public: + explicit ZfsJob( QObject* parent = nullptr ); + ~ZfsJob() override; + + QString prettyName() const override; + + Calamares::JobResult exec() override; + + void setConfigurationMap( const QVariantMap& configurationMap ) override; + +private: + QString m_poolName; + QString m_poolOptions; + QString m_datasetOptions; + + QList m_datasets; +}; + +CALAMARES_PLUGIN_FACTORY_DECLARATION( ZfsJobFactory ) + +#endif // ZFSJOB_H diff --git a/src/modules/zfs/zfs.conf b/src/modules/zfs/zfs.conf new file mode 100644 index 000000000..f2f8f52b0 --- /dev/null +++ b/src/modules/zfs/zfs.conf @@ -0,0 +1,38 @@ +# SPDX-FileCopyrightText: no +# SPDX-License-Identifier: CC0-1.0 +# +# The zfs module creates the zfs pools and datasets +# +# +# +--- +# The name to be used for the zpool +poolName: zpcala + +# A list of options that will be passed to zpool create +poolOptions: "-f -o ashift=12 -O mountpoint=none -O acltype=posixacl -O relatime=on" + +# A list of options that will be passed to zfs create when creating each dataset +# Do not include "canmount" or "mountpoint" as those are set below in the datasets array +datasetOptions: "-o compression=lz4 -o atime=off -o xattr=sa" + +# An array of datasets that will be created on the zpool mounted at / +datasets: + - dsName: ROOT + mountpoint: none + canMount: off + - dsName: ROOT/distro + mountpoint: none + canMount: off + - dsName: ROOT/distro/root + mountpoint: / + canMount: noauto + - dsName: ROOT/distro/home + mountpoint: /home + canMount: on + - dsName: ROOT/distro/varcache + mountpoint: /var/cache + canMount: on + - dsName: ROOT/distro/varlog + mountpoint: /var/log + canMount: on diff --git a/src/modules/zfs/zfs.schema.yaml b/src/modules/zfs/zfs.schema.yaml new file mode 100644 index 000000000..fb83778ad --- /dev/null +++ b/src/modules/zfs/zfs.schema.yaml @@ -0,0 +1,22 @@ +# SPDX-FileCopyrightText: 2020 Adriaan de Groot +# SPDX-License-Identifier: GPL-3.0-or-later +--- +$schema: https://json-schema.org/schema# +$id: https://calamares.io/schemas/zfs +additionalProperties: false +type: object +properties: + poolName: { type: string } + poolOptions: { type: string } + datasetOptions: { type: string } + datasets: + type: array + items: + type: object + additionalProperties: false + properties: + dsName: { type: string } + mountpoint: { type: string } + canMount: { type: string } + required: [ dsName, mountpoint, canmount ] +required: [ poolName, datasets ] From 76892136cfe3bdaf238b0197f70e621c02dc1255 Mon Sep 17 00:00:00 2001 From: dalto Date: Sat, 6 Nov 2021 09:48:38 -0500 Subject: [PATCH 053/127] [initcpiocfg] Add support for zfs --- src/modules/initcpiocfg/main.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/modules/initcpiocfg/main.py b/src/modules/initcpiocfg/main.py index 99168dcde..755039c0e 100644 --- a/src/modules/initcpiocfg/main.py +++ b/src/modules/initcpiocfg/main.py @@ -150,6 +150,7 @@ def find_initcpio_features(partitions, root_mount_point): swap_uuid = "" uses_btrfs = False + uses_zfs = False uses_lvm2 = False encrypt_hook = False openswap_hook = False @@ -172,6 +173,9 @@ def find_initcpio_features(partitions, root_mount_point): if partition["fs"] == "btrfs": uses_btrfs = True + if partition["fs"] == "zfs": + uses_zfs = True + if "lvm2" in partition["fs"]: uses_lvm2 = True @@ -198,6 +202,9 @@ def find_initcpio_features(partitions, root_mount_point): if uses_lvm2: hooks.append("lvm2") + if uses_zfs: + hooks.append("zfs") + if swap_uuid != "": if encrypt_hook and openswap_hook: hooks.extend(["openswap"]) From b9559a9d82c4bc69524a2368fbe22e08c50f1200 Mon Sep 17 00:00:00 2001 From: dalto Date: Sat, 6 Nov 2021 10:30:49 -0500 Subject: [PATCH 054/127] [zfs] Update to Calamares coding standards --- src/modules/zfs/ZfsJob.cpp | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/modules/zfs/ZfsJob.cpp b/src/modules/zfs/ZfsJob.cpp index 2602c5417..0227ead07 100644 --- a/src/modules/zfs/ZfsJob.cpp +++ b/src/modules/zfs/ZfsJob.cpp @@ -53,27 +53,40 @@ ZfsJob::exec() { QVariantMap pMap; if ( partition.canConvert( QVariant::Map ) ) + { pMap = partition.toMap(); + } // If it isn't a zfs partition, ignore it if ( pMap[ "fsName" ] != "zfs" ) + { continue; + } // Find the best device identifier, if one isn't available, skip this partition QString deviceName; if ( pMap[ "partuuid" ].toString() != "" ) + { deviceName = "/dev/disk/by-partuuid/" + pMap[ "partuuid" ].toString().toLower(); + } else if ( pMap[ "device" ].toString() != "" ) + { deviceName = pMap[ "device" ].toString().toLower(); + } else + { continue; + } // Create the zpool auto r = system->runCommand( { "sh", "-c", "zpool create " + m_poolOptions + " " + m_poolName + " " + deviceName }, std::chrono::seconds( 10 ) ); if ( r.getExitCode() != 0 ) - return Calamares::JobResult::error( "message", "Failed to create zpool on " + deviceName ); + { + return Calamares::JobResult::error( tr( "zpool failure" ), + tr( "Failed to create zpool on " + deviceName.toLocal8Bit() ) ); + } // Create the datasets for ( const auto& dataset : qAsConst( m_datasets ) ) @@ -97,7 +110,9 @@ ZfsJob::exec() + m_poolName + "/" + dsMap[ "dsName" ].toString() }, std::chrono::seconds( 10 ) ); if ( r.getExitCode() != 0 ) + { cWarning() << "Failed to create dataset" << dsMap[ "dsName" ].toString(); + } } } From 7635b76352a6115656e2805e5619e3172764e9ea Mon Sep 17 00:00:00 2001 From: dalto Date: Sat, 6 Nov 2021 13:27:03 -0500 Subject: [PATCH 055/127] [zfs] Add datasets to global storage for other modules --- src/modules/zfs/ZfsJob.cpp | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/src/modules/zfs/ZfsJob.cpp b/src/modules/zfs/ZfsJob.cpp index 0227ead07..48521a2b9 100644 --- a/src/modules/zfs/ZfsJob.cpp +++ b/src/modules/zfs/ZfsJob.cpp @@ -89,13 +89,14 @@ ZfsJob::exec() } // Create the datasets + QVariantList datasetList; for ( const auto& dataset : qAsConst( m_datasets ) ) { - QVariantMap dsMap = dataset.toMap(); + QVariantMap datasetMap = dataset.toMap(); // Make sure all values are valid - if ( dsMap[ "dsName" ].toString().isEmpty() || dsMap[ "mountpoint" ].toString().isEmpty() - || dsMap[ "canMount" ].toString().isEmpty() ) + if ( datasetMap[ "dsName" ].toString().isEmpty() || datasetMap[ "mountpoint" ].toString().isEmpty() + || datasetMap[ "canMount" ].toString().isEmpty() ) { cWarning() << "Bad dataset entry"; continue; @@ -106,13 +107,23 @@ ZfsJob::exec() r = system->runCommand( { "sh", "-c", "zfs create " + m_datasetOptions - + " -o canmount=off -o mountpoint=" + dsMap[ "mountpoint" ].toString() + " " - + m_poolName + "/" + dsMap[ "dsName" ].toString() }, + + " -o canmount=off -o mountpoint=" + datasetMap[ "mountpoint" ].toString() + + " " + m_poolName + "/" + datasetMap[ "dsName" ].toString() }, std::chrono::seconds( 10 ) ); if ( r.getExitCode() != 0 ) { - cWarning() << "Failed to create dataset" << dsMap[ "dsName" ].toString(); + cWarning() << "Failed to create dataset" << datasetMap[ "dsName" ].toString(); } + + // Add the dataset to the list for global storage + datasetMap[ "zpool" ] = m_poolName; + datasetList.append( datasetMap ); + } + + // If the list isn't empty, add it to global storage + if ( !datasetList.isEmpty() ) + { + Calamares::JobQueue::instance()->globalStorage()->insert( "zfs", datasetList ); } } From 7f05096611cde3e3fe955685e1c665ebc6227b1a Mon Sep 17 00:00:00 2001 From: dalto Date: Sat, 6 Nov 2021 14:12:40 -0500 Subject: [PATCH 056/127] [zfs] Add delay before creating the zpool --- src/modules/zfs/ZfsJob.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/modules/zfs/ZfsJob.cpp b/src/modules/zfs/ZfsJob.cpp index 48521a2b9..113f9afd8 100644 --- a/src/modules/zfs/ZfsJob.cpp +++ b/src/modules/zfs/ZfsJob.cpp @@ -79,8 +79,9 @@ ZfsJob::exec() } // Create the zpool + // zfs doesn't wait for the devices so pause for 2 seconds to ensure we give time for the device files to be created auto r - = system->runCommand( { "sh", "-c", "zpool create " + m_poolOptions + " " + m_poolName + " " + deviceName }, + = system->runCommand( { "sh", "-c", "sleep 2 ; zpool create " + m_poolOptions + " " + m_poolName + " " + deviceName }, std::chrono::seconds( 10 ) ); if ( r.getExitCode() != 0 ) { From de0bbbe90af7e4edf3325dae7577f26de0795ec5 Mon Sep 17 00:00:00 2001 From: dalto Date: Sat, 6 Nov 2021 14:33:43 -0500 Subject: [PATCH 057/127] [mount] Add support for zfs datasets --- src/modules/mount/main.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/modules/mount/main.py b/src/modules/mount/main.py index 2e96b6036..1b849a433 100644 --- a/src/modules/mount/main.py +++ b/src/modules/mount/main.py @@ -131,6 +131,24 @@ def mount_partition(root_mount_point, partition, partitions): ",".join([mount_option, partition.get("options", "")])) != 0: libcalamares.utils.warning("Cannot mount {}".format(device)) + if fstype == "zfs" and partition["mountPoint"] == '/': + # Get the zfs dataset list from global storage + zfs = libcalamares.globalstorage.value("zfs") + + if not zfs: + libcalamares.utils.warning("Failed to locate zfs dataset list") + + # Set the canmount property for each dataset. This will effectively mount the dataset + for dataset in zfs: + if dataset['canmount'] == 'noauto' or dataset['canmount'] == 'on': + subprocess.check_call(['zfs', 'set', 'canmount=' + dataset['canmount'], + dataset['zpool'] + '/' + dataset['dsName']]) + + # It is common for the / mountpoint to be set to noauto since it is mounted by the initrd + # If this is the case we need to manually mount it here + if dataset['mountpoint'] == '/' and dataset['canmount'] == 'noauto': + subprocess.check_call(['zfs', 'mount', dataset['zpool'] + '/' + dataset['dsName']]) + def run(): """ From 5d71723aec3fc149f89c0a7476ad0391f660e89e Mon Sep 17 00:00:00 2001 From: dalto Date: Sun, 7 Nov 2021 08:01:32 -0600 Subject: [PATCH 058/127] [mount] Improve error handling for zfs --- src/modules/mount/main.py | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/modules/mount/main.py b/src/modules/mount/main.py index 1b849a433..492525f0e 100644 --- a/src/modules/mount/main.py +++ b/src/modules/mount/main.py @@ -136,18 +136,22 @@ def mount_partition(root_mount_point, partition, partitions): zfs = libcalamares.globalstorage.value("zfs") if not zfs: - libcalamares.utils.warning("Failed to locate zfs dataset list") + libcalamares.utils.error("Failed to locate zfs dataset list") # Set the canmount property for each dataset. This will effectively mount the dataset for dataset in zfs: - if dataset['canmount'] == 'noauto' or dataset['canmount'] == 'on': - subprocess.check_call(['zfs', 'set', 'canmount=' + dataset['canmount'], - dataset['zpool'] + '/' + dataset['dsName']]) + try: + if dataset['canmount'] == 'noauto' or dataset['canmount'] == 'on': + subprocess.check_call(['zfs', 'set', 'canmount=' + dataset['canmount'], + dataset['zpool'] + '/' + dataset['dsName']]) - # It is common for the / mountpoint to be set to noauto since it is mounted by the initrd - # If this is the case we need to manually mount it here - if dataset['mountpoint'] == '/' and dataset['canmount'] == 'noauto': - subprocess.check_call(['zfs', 'mount', dataset['zpool'] + '/' + dataset['dsName']]) + # It is common for the / mountpoint to be set to noauto since it is mounted by the initrd + # If this is the case we need to manually mount it here + if dataset['mountpoint'] == '/' and dataset['canmount'] == 'noauto': + subprocess.check_call(['zfs', 'mount', dataset['zpool'] + '/' + dataset['dsName']]) + except KeyError: + # This should be impossible + libcalamares.utils.error("Internal error handling zfs dataset") def run(): From 074941e2bdf2744958c37cbc4ae6922bcc6b59a1 Mon Sep 17 00:00:00 2001 From: dalto Date: Sun, 7 Nov 2021 09:32:52 -0600 Subject: [PATCH 059/127] [bootloader] Add initial support for zfs --- src/modules/bootloader/main.py | 70 +++++++++++++++++++++++++++++++++- 1 file changed, 68 insertions(+), 2 deletions(-) diff --git a/src/modules/bootloader/main.py b/src/modules/bootloader/main.py index 68cbddd0e..10e7d0f47 100644 --- a/src/modules/bootloader/main.py +++ b/src/modules/bootloader/main.py @@ -92,6 +92,32 @@ def get_kernel_line(kernel_type): 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("zfs") + + 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.error("Internal error handling zfs dataset") + raise + + return None + + def create_systemd_boot_conf(install_path, efi_dir, uuid, entry, entry_name, kernel_type): """ Creates systemd-boot configuration files based on given parameters. @@ -133,12 +159,21 @@ def create_systemd_boot_conf(install_path, efi_dir, uuid, entry, entry_name, ker "root=/dev/mapper/" + partition["luksMapperName"]] - # systemd-boot with a BTRFS root filesystem needs to be told - # about the root subvolume. for partition in partitions: + # systemd-boot with a BTRFS root filesystem needs to be told + # about the root subvolume. if partition["mountPoint"] == "/" and partition["fs"] == "btrfs": kernel_params.append("rootflags=subvol=@") + # zfs needs to be told the location of the root dataset + if partition["mountPoint"] == "/" and partition["fs"] == "zfs": + zfs_root = get_zfs_root + if zfs_root is not None: + kernel_params.append("root=ZFS=" + zfs_root) + else: + # Something is really broken if we get to this point + libcalamares.utils.error("Internal error handling zfs dataset") + if cryptdevice_params: kernel_params.extend(cryptdevice_params) else: @@ -314,6 +349,37 @@ def get_grub_efi_parameters(): return None +def run_grub_mkconfig(output_file): + """ + Runs grub-mkconfig in the target environment + + :param output_file: A string containing the path to the generating grub config file + :return: + """ + + # get the partition from global storage + partitions = libcalamares.globalstorage.value("partitions") + if not partitions: + libcalamares.utils.error("Failed to run grub-mkconfig, no partitions defined in global storage") + return + + # check for zfs + is_zfs = False + for partition in partitions: + if partition["mountPoint"] == "/" and partition["fs"] == "zfs": + is_zfs = True + + # zfs needs an environment variable set for grub-mkconfig + if is_zfs: + check_target_env_call(["sh", "-c", "echo ZPOOL_VDEV_NAME_PATH=1 >> /etc/environment"]) + 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 install_grub(efi_directory, fw_type): """ Installs grub as bootloader, either in pc or efi mode. From 6da9bad2720701142b71c405f8d998afded5f1d5 Mon Sep 17 00:00:00 2001 From: dalto Date: Mon, 8 Nov 2021 17:26:08 -0600 Subject: [PATCH 060/127] [partition][zfs] Add support for zfs encryption --- .../partition/gui/CreatePartitionDialog.cpp | 10 ++- src/modules/zfs/ZfsJob.cpp | 87 +++++++++++++++---- src/modules/zfs/ZfsJob.h | 19 ++++ 3 files changed, 100 insertions(+), 16 deletions(-) diff --git a/src/modules/partition/gui/CreatePartitionDialog.cpp b/src/modules/partition/gui/CreatePartitionDialog.cpp index 0727ee935..e7d7751a1 100644 --- a/src/modules/partition/gui/CreatePartitionDialog.cpp +++ b/src/modules/partition/gui/CreatePartitionDialog.cpp @@ -243,7 +243,8 @@ CreatePartitionDialog::getNewlyCreatedPartition() // does so, to set up the partition for create-and-then-set-flags. Partition* partition = nullptr; QString luksPassphrase = m_ui->encryptWidget->passphrase(); - if ( m_ui->encryptWidget->state() == EncryptWidget::Encryption::Confirmed && !luksPassphrase.isEmpty() ) + if ( m_ui->encryptWidget->state() == EncryptWidget::Encryption::Confirmed && !luksPassphrase.isEmpty() + && fsType != FileSystem::Zfs ) { partition = KPMHelpers::createNewEncryptedPartition( m_parent, *m_device, m_role, fsType, fsLabel, first, last, luksPassphrase, PartitionTable::Flags() ); @@ -254,6 +255,13 @@ CreatePartitionDialog::getNewlyCreatedPartition() m_parent, *m_device, m_role, fsType, fsLabel, first, last, PartitionTable::Flags() ); } + // For zfs, we let the zfs module handle the encryption but we need to make the passphrase available to that module + if( m_ui->encryptWidget->state() == EncryptWidget::Encryption::Confirmed && !luksPassphrase.isEmpty() + && fsType == FileSystem::Zfs ) + { + Calamares::JobQueue::instance()->globalStorage()->insert( "encryptphrase", luksPassphrase ); + } + if ( m_device->type() == Device::Type::LVM_Device ) { partition->setPartitionPath( m_device->deviceNode() + QStringLiteral( "/" ) diff --git a/src/modules/zfs/ZfsJob.cpp b/src/modules/zfs/ZfsJob.cpp index 113f9afd8..af57fa5cc 100644 --- a/src/modules/zfs/ZfsJob.cpp +++ b/src/modules/zfs/ZfsJob.cpp @@ -17,6 +17,8 @@ #include "JobQueue.h" #include "Settings.h" +#include + ZfsJob::ZfsJob( QObject* parent ) : Calamares::CppJob( parent ) { @@ -30,6 +32,56 @@ ZfsJob::prettyName() const return tr( "Create ZFS pools and datasets" ); } +ZfsResult +ZfsJob::CreateZpool( QString deviceName, QString poolName, QString poolOptions, bool encrypt, QString passphrase ) const +{ + // zfs doesn't wait for the devices so pause for 2 seconds to ensure we give time for the device files to be created + QString command; + if ( encrypt ) + { + command = "sleep 2 ; echo \"" + passphrase + "\" | zpool create " + poolOptions + + " -O encryption=aes-256-gcm -O keyformat=passphrase " + poolName + " " + deviceName; + } + else + { + command = "sleep 2 ; zpool create " + poolOptions + " " + poolName + " " + deviceName; + } + + // We use a qProcess instead of runCommand so the password will not end up in the logs + QProcess process; + + process.setProcessChannelMode( QProcess::MergedChannels ); + cDebug() << Logger::SubEntry << "Running zpool create"; + + process.start( "sh", QStringList() << "-c" << command ); + + if ( !process.waitForStarted() ) + { + return { false, tr( "zpool create process failed to start" ) }; + } + + if ( !process.waitForFinished( 5000 ) ) + { + return { false, tr( "Process for zpool create timed out" ) }; + } + + QString output = QString::fromLocal8Bit( process.readAllStandardOutput() ).trimmed(); + + if ( process.exitStatus() == QProcess::CrashExit ) + { + return { false, tr( "The output from the crash was: " ) + output }; + } + + auto exitcode = process.exitCode(); + if ( exitcode != 0 ) + { + cWarning() << "Failed to run zpool create. The output was: " + output; + return { false, tr( "Failed to create zpool on " ) + deviceName }; + } + + return { true, QString() }; +} + Calamares::JobResult ZfsJob::exec() { @@ -78,15 +130,20 @@ ZfsJob::exec() continue; } - // Create the zpool - // zfs doesn't wait for the devices so pause for 2 seconds to ensure we give time for the device files to be created - auto r - = system->runCommand( { "sh", "-c", "sleep 2 ; zpool create " + m_poolOptions + " " + m_poolName + " " + deviceName }, - std::chrono::seconds( 10 ) ); - if ( r.getExitCode() != 0 ) + ZfsResult zfsResult; + if ( gs->contains( "encryptphrase" ) ) { - return Calamares::JobResult::error( tr( "zpool failure" ), - tr( "Failed to create zpool on " + deviceName.toLocal8Bit() ) ); + zfsResult + = CreateZpool( deviceName, m_poolName, m_poolOptions, true, gs->value( "encryptphrase" ).toString() ); + } + else + { + zfsResult = CreateZpool( deviceName, m_poolName, m_poolOptions, false ); + } + + if ( !zfsResult.success ) + { + return Calamares::JobResult::error( tr( "Failed to create zpool" ), zfsResult.failureMessage ); } // Create the datasets @@ -105,12 +162,12 @@ ZfsJob::exec() // Create the dataset. We set canmount=no regardless of the setting for now. // It is modified to the correct value in the mount module to ensure mount order is maintained - r = system->runCommand( { "sh", - "-c", - "zfs create " + m_datasetOptions - + " -o canmount=off -o mountpoint=" + datasetMap[ "mountpoint" ].toString() - + " " + m_poolName + "/" + datasetMap[ "dsName" ].toString() }, - std::chrono::seconds( 10 ) ); + auto r = system->runCommand( { "sh", + "-c", + "zfs create " + m_datasetOptions + " -o canmount=off -o mountpoint=" + + datasetMap[ "mountpoint" ].toString() + " " + m_poolName + "/" + + datasetMap[ "dsName" ].toString() }, + std::chrono::seconds( 10 ) ); if ( r.getExitCode() != 0 ) { cWarning() << "Failed to create dataset" << datasetMap[ "dsName" ].toString(); @@ -124,7 +181,7 @@ ZfsJob::exec() // If the list isn't empty, add it to global storage if ( !datasetList.isEmpty() ) { - Calamares::JobQueue::instance()->globalStorage()->insert( "zfs", datasetList ); + gs->insert( "zfs", datasetList ); } } diff --git a/src/modules/zfs/ZfsJob.h b/src/modules/zfs/ZfsJob.h index 87646a227..b2feb9e87 100644 --- a/src/modules/zfs/ZfsJob.h +++ b/src/modules/zfs/ZfsJob.h @@ -20,6 +20,11 @@ #include "DllMacro.h" +struct ZfsResult { + bool success; + QString failureMessage; +}; + /** @brief Create zpools and zfs datasets * */ @@ -43,6 +48,20 @@ private: QString m_datasetOptions; QList m_datasets; + + /** @brief Creates a zpool based on the provided arguments + * + * Creates a zpool + * @p deviceName is a full path to the device the zpool should be created on + * @p poolName is a string containing the name of the pool to create + * @p poolOptions are the options to pass to zpool create + * @p encrypt is a boolean which determines if the pool should be encrypted + * @p passphrase is a string continaing the passphrase + * + */ + ZfsResult CreateZpool(QString deviceName, QString poolName, QString poolOptions, bool encrypt, QString passphrase = QString() ) const; + + }; CALAMARES_PLUGIN_FACTORY_DECLARATION( ZfsJobFactory ) From 1ccabf1b13657b065178ac4887e1bd54d736a539 Mon Sep 17 00:00:00 2001 From: dalto Date: Tue, 9 Nov 2021 07:42:39 -0600 Subject: [PATCH 061/127] [zfs] Export zpool so it can later be mounted at the correct location --- src/modules/zfs/ZfsJob.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/modules/zfs/ZfsJob.cpp b/src/modules/zfs/ZfsJob.cpp index af57fa5cc..b04dfd473 100644 --- a/src/modules/zfs/ZfsJob.cpp +++ b/src/modules/zfs/ZfsJob.cpp @@ -183,6 +183,13 @@ ZfsJob::exec() { gs->insert( "zfs", datasetList ); } + + // Export the zpool so it can be reimported at the correct local later + auto r = system->runCommand( { "zpool", "export", m_poolName }, std::chrono::seconds( 10 ) ); + if ( r.getExitCode() != 0 ) + { + cWarning() << "Failed to export pool" << m_poolName; + } } return Calamares::JobResult::ok(); From 75c947c5a36d7bdc9f589c8f8c5daf8c01066b54 Mon Sep 17 00:00:00 2001 From: dalto Date: Tue, 9 Nov 2021 14:53:44 -0600 Subject: [PATCH 062/127] [mount] Fix zfs code and add support for encryption --- src/modules/mount/main.py | 62 +++++++++++++++++++++++++++++++-------- 1 file changed, 50 insertions(+), 12 deletions(-) diff --git a/src/modules/mount/main.py b/src/modules/mount/main.py index 492525f0e..813114f0a 100644 --- a/src/modules/mount/main.py +++ b/src/modules/mount/main.py @@ -61,6 +61,21 @@ def get_btrfs_subvolumes(partitions): return btrfs_subvolumes +def parse_global_storage(gs_value): + """ + Something in the chain is converting on and off to true and false. This converts it back. + + :param gs_value: The value from global storage + :return: + """ + if gs_value is True: + return "on" + elif gs_value is False: + return "off" + else: + return gs_value + + def mount_partition(root_mount_point, partition, partitions): """ Do a single mount of @p partition inside @p root_mount_point. @@ -136,22 +151,45 @@ def mount_partition(root_mount_point, partition, partitions): zfs = libcalamares.globalstorage.value("zfs") if not zfs: - libcalamares.utils.error("Failed to locate zfs dataset list") + libcalamares.utils.warning("Failed to locate zfs dataset list") + raise Exception("Internal error mounting zfs datasets") + + # import the zpool + import_result = subprocess.run(['zpool', 'import', '-R', root_mount_point, zfs[0]['zpool']]) + if import_result.returncode != 0: + raise Exception("Failed to import zpool") + + passphrase = libcalamares.globalstorage.value("encryptphrase") + if passphrase: + # The zpool is encrypted, we need to unlock it + loadkey_result = subprocess.run(['sh', '-c', 'echo "' + passphrase + '" | zfs load-key ' + zfs[0]['zpool']]) + if loadkey_result.returncode != 0: + raise Exception("Failed to unlock zpool") + + # first we handle the / dataset if there is one + for dataset in zfs: + if dataset['mountpoint'] == '/': + # Properly set the canmount field from global storage + can_mount = parse_global_storage(dataset['canMount']) + set_result = subprocess.run(['zfs', 'set', 'canmount=' + can_mount, + dataset['zpool'] + '/' + dataset['dsName']]) + if set_result.returncode != 0: + raise Exception("Failed to set zfs mountpoint") + if dataset['canMount'] == 'noauto': + mount_result = subprocess.run(['zfs', 'mount', dataset['zpool'] + '/' + dataset['dsName']]) + if mount_result.returncode != 0: + raise Exception("Failed to mount root dataset") # Set the canmount property for each dataset. This will effectively mount the dataset for dataset in zfs: - try: - if dataset['canmount'] == 'noauto' or dataset['canmount'] == 'on': - subprocess.check_call(['zfs', 'set', 'canmount=' + dataset['canmount'], - dataset['zpool'] + '/' + dataset['dsName']]) + # We already handled the / mountpoint above + if dataset['mountpoint'] != '/': + can_mount = parse_global_storage(dataset['canMount']) - # It is common for the / mountpoint to be set to noauto since it is mounted by the initrd - # If this is the case we need to manually mount it here - if dataset['mountpoint'] == '/' and dataset['canmount'] == 'noauto': - subprocess.check_call(['zfs', 'mount', dataset['zpool'] + '/' + dataset['dsName']]) - except KeyError: - # This should be impossible - libcalamares.utils.error("Internal error handling zfs dataset") + set_result = subprocess.run(['zfs', 'set', 'canmount=' + can_mount, + dataset['zpool'] + '/' + dataset['dsName']]) + if set_result.returncode != 0: + raise Exception("Failed to set zfs mountpoint") def run(): From a5b21b2500182cb521a20d0eb5ce371ebb933076 Mon Sep 17 00:00:00 2001 From: dalto Date: Tue, 9 Nov 2021 14:54:46 -0600 Subject: [PATCH 063/127] [zfs] Fix typo and add missing continue --- src/modules/zfs/ZfsJob.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/modules/zfs/ZfsJob.cpp b/src/modules/zfs/ZfsJob.cpp index b04dfd473..8790b1fff 100644 --- a/src/modules/zfs/ZfsJob.cpp +++ b/src/modules/zfs/ZfsJob.cpp @@ -171,6 +171,7 @@ ZfsJob::exec() if ( r.getExitCode() != 0 ) { cWarning() << "Failed to create dataset" << datasetMap[ "dsName" ].toString(); + continue; } // Add the dataset to the list for global storage @@ -184,7 +185,7 @@ ZfsJob::exec() gs->insert( "zfs", datasetList ); } - // Export the zpool so it can be reimported at the correct local later + // Export the zpool so it can be reimported at the correct location later auto r = system->runCommand( { "zpool", "export", m_poolName }, std::chrono::seconds( 10 ) ); if ( r.getExitCode() != 0 ) { From ee99ee48f620e917fc7a96b5774b6896218a7f32 Mon Sep 17 00:00:00 2001 From: dalto Date: Fri, 12 Nov 2021 16:06:06 -0600 Subject: [PATCH 064/127] Add support for multiple zpools --- src/modules/mount/main.py | 123 ++++++++++------- .../partition/gui/CreatePartitionDialog.cpp | 26 +++- src/modules/zfs/ZfsJob.cpp | 124 ++++++++++++++---- 3 files changed, 192 insertions(+), 81 deletions(-) diff --git a/src/modules/mount/main.py b/src/modules/mount/main.py index 813114f0a..20815969a 100644 --- a/src/modules/mount/main.py +++ b/src/modules/mount/main.py @@ -111,11 +111,79 @@ def mount_partition(root_mount_point, partition, partitions): if "luksMapperName" in partition: device = os.path.join("/dev/mapper", partition["luksMapperName"]) - if libcalamares.utils.mount(device, - mount_point, - fstype, - partition.get("options", "")) != 0: - libcalamares.utils.warning("Cannot mount {}".format(device)) + if fstype == "zfs": + zfs_pool_list = libcalamares.globalstorage.value("zfsPoolInfo") + if not zfs_pool_list: + libcalamares.utils.warning("Failed to locate zfsPoolInfo data in global storage") + raise Exception("Internal error mounting zfs datasets") + + for zfs_pool in zfs_pool_list: + libcalamares.utils.warning("Poolname: " + zfs_pool["poolName"] + " mountpoint: " + zfs_pool["mountpoint"]) + if zfs_pool["mountpoint"] == partition["mountPoint"]: + pool_name = zfs_pool["poolName"] + ds_name = zfs_pool["dsName"]; + + # import the zpool + import_result = subprocess.run(['zpool', 'import', '-R', root_mount_point, pool_name]) + if import_result.returncode != 0: + raise Exception("Failed to import zpool") + + 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 + loadkey_result = subprocess.run(['sh', '-c', 'echo "' + passphrase + '" | zfs load-key ' + pool_name]) + if loadkey_result.returncode != 0: + raise Exception("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 Exception("Internal error mounting zfs datasets") + + # first we handle the / dataset if there is one + for dataset in zfs: + if dataset['mountpoint'] == '/': + # Properly set the canmount field from global storage + can_mount = parse_global_storage(dataset['canMount']) + set_result = subprocess.run(['zfs', 'set', 'canmount=' + can_mount, + dataset['zpool'] + '/' + dataset['dsName']]) + if set_result.returncode != 0: + raise Exception("Failed to set zfs mountpoint") + if dataset['canMount'] == 'noauto': + mount_result = subprocess.run(['zfs', 'mount', dataset['zpool'] + '/' + dataset['dsName']]) + if mount_result.returncode != 0: + raise Exception("Failed to mount root dataset") + + # Set the canmount property for each dataset. This will effectively mount the dataset + for dataset in zfs: + # We already handled the / mountpoint above + if dataset['mountpoint'] != '/': + can_mount = parse_global_storage(dataset['canMount']) + + set_result = subprocess.run(['zfs', 'set', 'canmount=' + can_mount, + dataset['zpool'] + '/' + dataset['dsName']]) + if set_result.returncode != 0: + raise Exception("Failed to set zfs mountpoint") + else: + set_result = subprocess.run(['zfs', 'set', 'canmount=on', pool_name + '/' + ds_name]) + if set_result.returncode != 0: + raise Exception("Failed to set zfs mountpoint") + else: # fstype == "zfs" + if libcalamares.utils.mount(device, + mount_point, + fstype, + partition.get("options", "")) != 0: + libcalamares.utils.warning("Cannot mount {}".format(device)) # Special handling for btrfs subvolumes. Create the subvolumes listed in mount.conf if fstype == "btrfs" and partition["mountPoint"] == '/': @@ -146,51 +214,6 @@ def mount_partition(root_mount_point, partition, partitions): ",".join([mount_option, partition.get("options", "")])) != 0: libcalamares.utils.warning("Cannot mount {}".format(device)) - if fstype == "zfs" and partition["mountPoint"] == '/': - # Get the zfs dataset list from global storage - zfs = libcalamares.globalstorage.value("zfs") - - if not zfs: - libcalamares.utils.warning("Failed to locate zfs dataset list") - raise Exception("Internal error mounting zfs datasets") - - # import the zpool - import_result = subprocess.run(['zpool', 'import', '-R', root_mount_point, zfs[0]['zpool']]) - if import_result.returncode != 0: - raise Exception("Failed to import zpool") - - passphrase = libcalamares.globalstorage.value("encryptphrase") - if passphrase: - # The zpool is encrypted, we need to unlock it - loadkey_result = subprocess.run(['sh', '-c', 'echo "' + passphrase + '" | zfs load-key ' + zfs[0]['zpool']]) - if loadkey_result.returncode != 0: - raise Exception("Failed to unlock zpool") - - # first we handle the / dataset if there is one - for dataset in zfs: - if dataset['mountpoint'] == '/': - # Properly set the canmount field from global storage - can_mount = parse_global_storage(dataset['canMount']) - set_result = subprocess.run(['zfs', 'set', 'canmount=' + can_mount, - dataset['zpool'] + '/' + dataset['dsName']]) - if set_result.returncode != 0: - raise Exception("Failed to set zfs mountpoint") - if dataset['canMount'] == 'noauto': - mount_result = subprocess.run(['zfs', 'mount', dataset['zpool'] + '/' + dataset['dsName']]) - if mount_result.returncode != 0: - raise Exception("Failed to mount root dataset") - - # Set the canmount property for each dataset. This will effectively mount the dataset - for dataset in zfs: - # We already handled the / mountpoint above - if dataset['mountpoint'] != '/': - can_mount = parse_global_storage(dataset['canMount']) - - set_result = subprocess.run(['zfs', 'set', 'canmount=' + can_mount, - dataset['zpool'] + '/' + dataset['dsName']]) - if set_result.returncode != 0: - raise Exception("Failed to set zfs mountpoint") - def run(): """ diff --git a/src/modules/partition/gui/CreatePartitionDialog.cpp b/src/modules/partition/gui/CreatePartitionDialog.cpp index e7d7751a1..6bde9a148 100644 --- a/src/modules/partition/gui/CreatePartitionDialog.cpp +++ b/src/modules/partition/gui/CreatePartitionDialog.cpp @@ -255,11 +255,29 @@ CreatePartitionDialog::getNewlyCreatedPartition() m_parent, *m_device, m_role, fsType, fsLabel, first, last, PartitionTable::Flags() ); } - // For zfs, we let the zfs module handle the encryption but we need to make the passphrase available to that module - if( m_ui->encryptWidget->state() == EncryptWidget::Encryption::Confirmed && !luksPassphrase.isEmpty() - && fsType == FileSystem::Zfs ) + // For zfs, we let the zfs module handle the encryption but we need to make the passphrase available to later modules + if ( fsType == FileSystem::Zfs ) { - Calamares::JobQueue::instance()->globalStorage()->insert( "encryptphrase", luksPassphrase ); + Calamares::GlobalStorage* storage = Calamares::JobQueue::instance()->globalStorage(); + QList< QVariant > zfsInfoList; + QVariantMap zfsInfo; + + // If this is not the first encrypted zfs partition, get the old list first + if ( storage->contains( "zfsInfo" ) ) + { + zfsInfoList = storage->value( "zfsInfo" ).toList(); + storage->remove( "zfsInfo" ); + } + + // Save the information subsequent modules will need + zfsInfo[ "encrypted" ] + = m_ui->encryptWidget->state() == EncryptWidget::Encryption::Confirmed && !luksPassphrase.isEmpty(); + zfsInfo[ "passphrase" ] = luksPassphrase; + zfsInfo[ "mountpoint" ] = selectedMountPoint( m_ui->mountPointComboBox ); + + // Add it to the list and insert it into global storage + zfsInfoList.append( zfsInfo ); + storage->insert( "zfsInfo", zfsInfoList ); } if ( m_device->type() == Device::Type::LVM_Device ) diff --git a/src/modules/zfs/ZfsJob.cpp b/src/modules/zfs/ZfsJob.cpp index 8790b1fff..26d96183c 100644 --- a/src/modules/zfs/ZfsJob.cpp +++ b/src/modules/zfs/ZfsJob.cpp @@ -101,6 +101,8 @@ ZfsJob::exec() const CalamaresUtils::System* system = CalamaresUtils::System::instance(); + QVariantList poolNames; + for ( auto& partition : qAsConst( partitions ) ) { QVariantMap pMap; @@ -130,15 +132,48 @@ ZfsJob::exec() continue; } + QString mountpoint = pMap[ "mountPoint" ].toString(); + if ( mountpoint.isEmpty() ) + { + continue; + } + + // Build a poolname off the mountpoint, this is not ideal but should work until there is UI built for zfs + QString poolName = m_poolName; + if ( mountpoint != '/' ) + { + QString suffix = mountpoint; + poolName += suffix.remove( QRegExp( "[^a-zA-Z\\d\\s]" ) ); + } + + if ( !gs->contains( "zfsInfo" ) && gs->value( "zfsInfo" ).canConvert( QVariant::List ) ) + { + return Calamares::JobResult::error( tr( "Internal data missing" ), tr( "Failed to create zpool" ) ); + } + + QVariantList zfsInfoList = gs->value( "zfsInfo" ).toList(); + + bool encrypt = false; + QString passphrase; + for ( const QVariant& zfsInfo : qAsConst( zfsInfoList ) ) + { + if ( zfsInfo.canConvert( QVariant::Map ) && zfsInfo.toMap().value( "encrypted" ).toBool() + && mountpoint == zfsInfo.toMap().value( "mountpoint" ) ) + { + encrypt = true; + passphrase = zfsInfo.toMap().value( "passphrase" ).toString(); + } + } + ZfsResult zfsResult; - if ( gs->contains( "encryptphrase" ) ) + if ( encrypt ) { zfsResult - = CreateZpool( deviceName, m_poolName, m_poolOptions, true, gs->value( "encryptphrase" ).toString() ); + = CreateZpool( deviceName, poolName, m_poolOptions, true, passphrase ); } else { - zfsResult = CreateZpool( deviceName, m_poolName, m_poolOptions, false ); + zfsResult = CreateZpool( deviceName, poolName, m_poolOptions, false ); } if ( !zfsResult.success ) @@ -146,53 +181,88 @@ ZfsJob::exec() return Calamares::JobResult::error( tr( "Failed to create zpool" ), zfsResult.failureMessage ); } - // Create the datasets - QVariantList datasetList; - for ( const auto& dataset : qAsConst( m_datasets ) ) - { - QVariantMap datasetMap = dataset.toMap(); + // Add the poolname, dataset name and mountpoint to the list + QVariantMap poolNameEntry; + poolNameEntry["poolName"] = poolName; + poolNameEntry["mountpoint"] = mountpoint; + poolNameEntry["dsName"] = "none"; - // Make sure all values are valid - if ( datasetMap[ "dsName" ].toString().isEmpty() || datasetMap[ "mountpoint" ].toString().isEmpty() - || datasetMap[ "canMount" ].toString().isEmpty() ) + + if ( mountpoint == '/' ) + { + // Create the datasets + QVariantList datasetList; + for ( const auto& dataset : qAsConst( m_datasets ) ) { - cWarning() << "Bad dataset entry"; - continue; + QVariantMap datasetMap = dataset.toMap(); + + // Make sure all values are valid + if ( datasetMap[ "dsName" ].toString().isEmpty() || datasetMap[ "mountpoint" ].toString().isEmpty() + || datasetMap[ "canMount" ].toString().isEmpty() ) + { + cWarning() << "Bad dataset entry"; + continue; + } + + // Create the dataset. We set canmount=no regardless of the setting for now. + // It is modified to the correct value in the mount module to ensure mount order is maintained + auto r = system->runCommand( { "sh", + "-c", + "zfs create " + m_datasetOptions + " -o canmount=off -o mountpoint=" + + datasetMap[ "mountpoint" ].toString() + " " + poolName + "/" + + datasetMap[ "dsName" ].toString() }, + std::chrono::seconds( 10 ) ); + if ( r.getExitCode() != 0 ) + { + cWarning() << "Failed to create dataset" << datasetMap[ "dsName" ].toString(); + continue; + } + + // Add the dataset to the list for global storage + datasetMap[ "zpool" ] = m_poolName; + datasetList.append( datasetMap ); } + // If the list isn't empty, add it to global storage + if ( !datasetList.isEmpty() ) + { + gs->insert( "zfsDatasets", datasetList ); + } + } + else + { // Create the dataset. We set canmount=no regardless of the setting for now. // It is modified to the correct value in the mount module to ensure mount order is maintained + QString dsName = mountpoint; + dsName.remove( QRegExp( "[^a-zA-Z\\d\\s]" ) ); auto r = system->runCommand( { "sh", "-c", "zfs create " + m_datasetOptions + " -o canmount=off -o mountpoint=" - + datasetMap[ "mountpoint" ].toString() + " " + m_poolName + "/" - + datasetMap[ "dsName" ].toString() }, + + mountpoint + " " + poolName + "/" + + dsName }, std::chrono::seconds( 10 ) ); if ( r.getExitCode() != 0 ) { - cWarning() << "Failed to create dataset" << datasetMap[ "dsName" ].toString(); - continue; + cWarning() << "Failed to create dataset" << dsName; } - - // Add the dataset to the list for global storage - datasetMap[ "zpool" ] = m_poolName; - datasetList.append( datasetMap ); + poolNameEntry["dsName"] = dsName; } - // If the list isn't empty, add it to global storage - if ( !datasetList.isEmpty() ) - { - gs->insert( "zfs", datasetList ); - } + poolNames.append(poolNameEntry); // Export the zpool so it can be reimported at the correct location later - auto r = system->runCommand( { "zpool", "export", m_poolName }, std::chrono::seconds( 10 ) ); + auto r = system->runCommand( { "zpool", "export", poolName }, std::chrono::seconds( 10 ) ); if ( r.getExitCode() != 0 ) { cWarning() << "Failed to export pool" << m_poolName; } } + if (!poolNames.isEmpty()) + { + gs->insert("zfsPoolInfo", poolNames); + } + return Calamares::JobResult::ok(); } From c48c91a5bd84f8e420fc67371bff2f769023325c Mon Sep 17 00:00:00 2001 From: dalto Date: Sat, 13 Nov 2021 09:31:23 -0600 Subject: [PATCH 065/127] [partition] Add support for zfs encryption when erase disk is selected --- src/modules/mount/main.py | 1 - .../partition/core/PartitionLayout.cpp | 22 ++++++++++++++++++- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/src/modules/mount/main.py b/src/modules/mount/main.py index 20815969a..f58b90e9f 100644 --- a/src/modules/mount/main.py +++ b/src/modules/mount/main.py @@ -118,7 +118,6 @@ def mount_partition(root_mount_point, partition, partitions): raise Exception("Internal error mounting zfs datasets") for zfs_pool in zfs_pool_list: - libcalamares.utils.warning("Poolname: " + zfs_pool["poolName"] + " mountpoint: " + zfs_pool["mountpoint"]) if zfs_pool["mountpoint"] == partition["mountPoint"]: pool_name = zfs_pool["poolName"] ds_name = zfs_pool["dsName"]; diff --git a/src/modules/partition/core/PartitionLayout.cpp b/src/modules/partition/core/PartitionLayout.cpp index 8ae904e92..f60952643 100644 --- a/src/modules/partition/core/PartitionLayout.cpp +++ b/src/modules/partition/core/PartitionLayout.cpp @@ -296,7 +296,9 @@ PartitionLayout::createPartitions( Device* dev, } Partition* part = nullptr; - if ( luksPassphrase.isEmpty() ) + + // Encryption for zfs is handled in the zfs module + if ( luksPassphrase.isEmpty() || correctFS( entry.partFileSystem ) == FileSystem::Zfs ) { part = KPMHelpers::createNewPartition( parent, *dev, @@ -319,6 +321,24 @@ PartitionLayout::createPartitions( Device* dev, luksPassphrase, KPM_PARTITION_FLAG( None ) ); } + + // For zfs, we need to make the passphrase available to later modules + if ( correctFS( entry.partFileSystem ) == FileSystem::Zfs ) + { + Calamares::GlobalStorage* storage = Calamares::JobQueue::instance()->globalStorage(); + QList< QVariant > zfsInfoList; + QVariantMap zfsInfo; + + // Save the information subsequent modules will need + zfsInfo[ "encrypted" ] = !luksPassphrase.isEmpty(); + zfsInfo[ "passphrase" ] = luksPassphrase; + zfsInfo[ "mountpoint" ] = entry.partMountPoint; + + // Add it to the list and insert it into global storage + zfsInfoList.append( zfsInfo ); + storage->insert( "zfsInfo", zfsInfoList ); + } + PartitionInfo::setFormat( part, true ); PartitionInfo::setMountPoint( part, entry.partMountPoint ); if ( !entry.partLabel.isEmpty() ) From 490ac8d086ce3f88c22c176d1aebafc533d225b0 Mon Sep 17 00:00:00 2001 From: dalto Date: Sat, 13 Nov 2021 10:43:07 -0600 Subject: [PATCH 066/127] [partition] Ensure format is selected for existing zfs partitions --- src/modules/partition/gui/EditExistingPartitionDialog.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/modules/partition/gui/EditExistingPartitionDialog.cpp b/src/modules/partition/gui/EditExistingPartitionDialog.cpp index 04b9527f1..a3052b3b7 100644 --- a/src/modules/partition/gui/EditExistingPartitionDialog.cpp +++ b/src/modules/partition/gui/EditExistingPartitionDialog.cpp @@ -120,6 +120,12 @@ EditExistingPartitionDialog::EditExistingPartitionDialog( Device* device, m_ui->fileSystemLabel->setEnabled( m_ui->formatRadioButton->isChecked() ); m_ui->fileSystemComboBox->setEnabled( m_ui->formatRadioButton->isChecked() ); + // Force a format if the existing device is a zfs device since reusing a zpool isn't currently supported + m_ui->formatRadioButton->setChecked( m_partition->fileSystem().type() == FileSystem::Type::Zfs ); + m_ui->formatRadioButton->setEnabled( !( m_partition->fileSystem().type() == FileSystem::Type::Zfs ) ); + m_ui->keepRadioButton->setChecked( !( m_partition->fileSystem().type() == FileSystem::Type::Zfs ) ); + m_ui->keepRadioButton->setEnabled( !( m_partition->fileSystem().type() == FileSystem::Type::Zfs ) ); + setFlagList( *( m_ui->m_listFlags ), m_partition->availableFlags(), PartitionInfo::flags( m_partition ) ); } From 6e440bf9bb4c6d3206ed1dc20f067cd5e6723d25 Mon Sep 17 00:00:00 2001 From: dalto Date: Sat, 13 Nov 2021 11:13:39 -0600 Subject: [PATCH 067/127] [umount] Export zpools after unmounting --- src/modules/umount/main.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/modules/umount/main.py b/src/modules/umount/main.py index 0035a6b0f..795eb8d2b 100644 --- a/src/modules/umount/main.py +++ b/src/modules/umount/main.py @@ -49,6 +49,26 @@ def list_mounts(root_mount_point): return lst +def export_zpools(root_mount_point): + """ Exports the zpools if defined in global storage + + :param root_mount_point: The absolute path to the root of the install + :return: + """ + try: + zfs_pool_list = libcalamares.globalstorage.value("zfsPoolInfo") + zfs_pool_list.sort(reverse=True, key=lambda x: x["poolName"]) + if zfs_pool_list: + for zfs_pool in zfs_pool_list: + import_result = subprocess.run(['zpool', 'export', root_mount_point, zfs_pool["poolName"]]) + if import_result.returncode != 0: + libcalamares.utils.warning("Failed to export zpool") + except Exception as e: + # If this fails it shouldn't cause the installation to fail + libcalamares.utils.warning("Received exception while exporting zpools: " + format(e)) + pass + + def run(): """ Unmounts given mountpoints in decreasing order. @@ -94,6 +114,8 @@ def run(): # in the exception object. subprocess.check_output(["umount", "-lv", mount_point], stderr=subprocess.STDOUT) + export_zpools(root_mount_point) + os.rmdir(root_mount_point) return None From 18ad188ef6a2c1406548abe78389793beeabdc48 Mon Sep 17 00:00:00 2001 From: dalto Date: Sat, 13 Nov 2021 13:43:26 -0600 Subject: [PATCH 068/127] [zfs] Ensure overlapping datasets don't get created and code cleanup --- src/modules/zfs/ZfsJob.cpp | 90 ++++++++++++++++++++++++++++---------- src/modules/zfs/ZfsJob.h | 35 +++++++++++++-- 2 files changed, 99 insertions(+), 26 deletions(-) diff --git a/src/modules/zfs/ZfsJob.cpp b/src/modules/zfs/ZfsJob.cpp index 26d96183c..d0367f5f6 100644 --- a/src/modules/zfs/ZfsJob.cpp +++ b/src/modules/zfs/ZfsJob.cpp @@ -32,6 +32,43 @@ ZfsJob::prettyName() const return tr( "Create ZFS pools and datasets" ); } +QString +ZfsJob::AlphaNumeric( QString input ) const +{ + return input.remove( QRegExp( "[^a-zA-Z\\d\\s]" ) ); +} + +void +ZfsJob::CollectMountpoints( const QVariantList& partitions ) +{ + m_mountpoints.empty(); + for ( const QVariant& partition : partitions ) + { + if ( partition.canConvert( QVariant::Map ) ) + { + QString mountpoint = partition.toMap().value( "mountPoint" ).toString(); + if ( !mountpoint.isEmpty() ) + { + m_mountpoints.append( mountpoint ); + } + } + } +} + +bool +ZfsJob::IsMountpointOverlapping( const QString& targetMountpoint ) const +{ + for ( const QString& mountpoint : m_mountpoints ) + { + if ( mountpoint != '/' && targetMountpoint.startsWith( mountpoint ) ) + { + return true; + } + } + return false; +} + + ZfsResult ZfsJob::CreateZpool( QString deviceName, QString poolName, QString poolOptions, bool encrypt, QString passphrase ) const { @@ -85,7 +122,7 @@ ZfsJob::CreateZpool( QString deviceName, QString poolName, QString poolOptions, Calamares::JobResult ZfsJob::exec() { - QList< QVariant > partitions; + QVariantList partitions; Calamares::GlobalStorage* gs = Calamares::JobQueue::instance()->globalStorage(); if ( gs && gs->contains( "partitions" ) && gs->value( "partitions" ).canConvert( QVariant::List ) ) { @@ -132,27 +169,28 @@ ZfsJob::exec() continue; } + // If the partition doesn't have a mountpoint, skip it QString mountpoint = pMap[ "mountPoint" ].toString(); if ( mountpoint.isEmpty() ) { continue; } - // Build a poolname off the mountpoint, this is not ideal but should work until there is UI built for zfs + // Build a poolname off config pool name and the mountpoint, this is not ideal but should work until there is UI built for zfs QString poolName = m_poolName; if ( mountpoint != '/' ) { - QString suffix = mountpoint; - poolName += suffix.remove( QRegExp( "[^a-zA-Z\\d\\s]" ) ); + poolName += AlphaNumeric( mountpoint ); } + // Check to ensure the list of zfs info from the partition module is available and convert it to a list if ( !gs->contains( "zfsInfo" ) && gs->value( "zfsInfo" ).canConvert( QVariant::List ) ) { return Calamares::JobResult::error( tr( "Internal data missing" ), tr( "Failed to create zpool" ) ); } - QVariantList zfsInfoList = gs->value( "zfsInfo" ).toList(); + // Look in the zfs info list to see if this partition should be encrypted bool encrypt = false; QString passphrase; for ( const QVariant& zfsInfo : qAsConst( zfsInfoList ) ) @@ -165,11 +203,11 @@ ZfsJob::exec() } } + // Create the zpool ZfsResult zfsResult; if ( encrypt ) { - zfsResult - = CreateZpool( deviceName, poolName, m_poolOptions, true, passphrase ); + zfsResult = CreateZpool( deviceName, poolName, m_poolOptions, true, passphrase ); } else { @@ -181,16 +219,17 @@ ZfsJob::exec() return Calamares::JobResult::error( tr( "Failed to create zpool" ), zfsResult.failureMessage ); } - // Add the poolname, dataset name and mountpoint to the list + // Save the poolname, dataset name and mountpoint. It will later be added to a list and placed in global storage. + // This will be used by later modules including mount and umount QVariantMap poolNameEntry; - poolNameEntry["poolName"] = poolName; - poolNameEntry["mountpoint"] = mountpoint; - poolNameEntry["dsName"] = "none"; - + poolNameEntry[ "poolName" ] = poolName; + poolNameEntry[ "mountpoint" ] = mountpoint; + poolNameEntry[ "dsName" ] = "none"; + // If the mountpoint is /, create datasets per the config file. If not, create a single dataset mounted at the partitions mountpoint if ( mountpoint == '/' ) { - // Create the datasets + CollectMountpoints( partitions ); QVariantList datasetList; for ( const auto& dataset : qAsConst( m_datasets ) ) { @@ -204,6 +243,12 @@ ZfsJob::exec() continue; } + // We should skip this dataset if it conflicts with a permanent mountpoint + if ( IsMountpointOverlapping( datasetMap[ "mountpoint" ].toString() ) ) + { + continue; + } + // Create the dataset. We set canmount=no regardless of the setting for now. // It is modified to the correct value in the mount module to ensure mount order is maintained auto r = system->runCommand( { "sh", @@ -218,7 +263,8 @@ ZfsJob::exec() continue; } - // Add the dataset to the list for global storage + // Add the dataset to the list for global storage this information is used later to properly set + // the mount options on each dataset datasetMap[ "zpool" ] = m_poolName; datasetList.append( datasetMap ); } @@ -231,24 +277,23 @@ ZfsJob::exec() } else { - // Create the dataset. We set canmount=no regardless of the setting for now. + // This is a zpool with a single dataset We again set canmount=no regardless of the desired setting. // It is modified to the correct value in the mount module to ensure mount order is maintained QString dsName = mountpoint; - dsName.remove( QRegExp( "[^a-zA-Z\\d\\s]" ) ); + dsName = AlphaNumeric( mountpoint ); auto r = system->runCommand( { "sh", "-c", "zfs create " + m_datasetOptions + " -o canmount=off -o mountpoint=" - + mountpoint + " " + poolName + "/" - + dsName }, + + mountpoint + " " + poolName + "/" + dsName }, std::chrono::seconds( 10 ) ); if ( r.getExitCode() != 0 ) { cWarning() << "Failed to create dataset" << dsName; } - poolNameEntry["dsName"] = dsName; + poolNameEntry[ "dsName" ] = dsName; } - poolNames.append(poolNameEntry); + poolNames.append( poolNameEntry ); // Export the zpool so it can be reimported at the correct location later auto r = system->runCommand( { "zpool", "export", poolName }, std::chrono::seconds( 10 ) ); @@ -258,9 +303,10 @@ ZfsJob::exec() } } - if (!poolNames.isEmpty()) + // Put the list of zpools into global storage + if ( !poolNames.isEmpty() ) { - gs->insert("zfsPoolInfo", poolNames); + gs->insert( "zfsPoolInfo", poolNames ); } return Calamares::JobResult::ok(); diff --git a/src/modules/zfs/ZfsJob.h b/src/modules/zfs/ZfsJob.h index b2feb9e87..4744954c2 100644 --- a/src/modules/zfs/ZfsJob.h +++ b/src/modules/zfs/ZfsJob.h @@ -20,7 +20,8 @@ #include "DllMacro.h" -struct ZfsResult { +struct ZfsResult +{ bool success; QString failureMessage; }; @@ -46,12 +47,12 @@ private: QString m_poolName; QString m_poolOptions; QString m_datasetOptions; + QStringList m_mountpoints; - QList m_datasets; + QList< QVariant > m_datasets; /** @brief Creates a zpool based on the provided arguments * - * Creates a zpool * @p deviceName is a full path to the device the zpool should be created on * @p poolName is a string containing the name of the pool to create * @p poolOptions are the options to pass to zpool create @@ -59,9 +60,35 @@ private: * @p passphrase is a string continaing the passphrase * */ - ZfsResult CreateZpool(QString deviceName, QString poolName, QString poolOptions, bool encrypt, QString passphrase = QString() ) const; + ZfsResult CreateZpool( QString deviceName, + QString poolName, + QString poolOptions, + bool encrypt, + QString passphrase = QString() ) const; + /** @brief Returns the alphanumeric portion of a string + * + * @p input is the input string + * + */ + QString AlphaNumeric( QString input ) const; + /** @brief Collects all the mountpoints from the partitions + * + * Iterates over @p partitions to gather each mountpoint present + * in the list of maps and populates m_mountpoints + * + */ + void CollectMountpoints( const QVariantList& partitions ); + + /** @brief Check to see if a given mountpoint overlaps with one of the defined moutnpoints + * + * Iterates over m_partitions and checks if @p targetMountpoint overlaps with them by comparing + * the beginning of targetMountpoint with all the values in m_mountpoints. Of course, / is excluded + * since all the mountpoints would begin with / + * + */ + bool IsMountpointOverlapping( const QString& targetMountpoint ) const; }; CALAMARES_PLUGIN_FACTORY_DECLARATION( ZfsJobFactory ) From ebae698a6e6598ced38367963c426257fe8381ee Mon Sep 17 00:00:00 2001 From: dalto Date: Sat, 13 Nov 2021 14:09:16 -0600 Subject: [PATCH 069/127] [mount] Move zfs code into a seperate function to improve readability --- src/modules/mount/main.py | 148 ++++++++++++++++++++------------------ 1 file changed, 80 insertions(+), 68 deletions(-) diff --git a/src/modules/mount/main.py b/src/modules/mount/main.py index f58b90e9f..72c2015c6 100644 --- a/src/modules/mount/main.py +++ b/src/modules/mount/main.py @@ -62,10 +62,9 @@ def get_btrfs_subvolumes(partitions): def parse_global_storage(gs_value): - """ - Something in the chain is converting on and off to true and false. This converts it back. + """ Something in the chain is converting on and off to true and false. This converts it back. - :param gs_value: The value from global storage + :param gs_value: The value from global storage which needs to be fixed :return: """ if gs_value is True: @@ -76,6 +75,83 @@ def parse_global_storage(gs_value): return gs_value +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 Exception("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 + import_result = subprocess.run(['zpool', 'import', '-R', root_mount_point, pool_name]) + if import_result.returncode != 0: + raise Exception("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 + loadkey_result = subprocess.run(['sh', '-c', 'echo "' + passphrase + '" | zfs load-key ' + pool_name]) + if loadkey_result.returncode != 0: + raise Exception("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 Exception("Internal error mounting zfs datasets") + + # first we handle the / dataset if there is one + for dataset in zfs: + if dataset['mountpoint'] == '/': + # Properly set the canmount field from global storage + can_mount = parse_global_storage(dataset['canMount']) + set_result = subprocess.run(['zfs', 'set', 'canmount=' + can_mount, + dataset['zpool'] + '/' + dataset['dsName']]) + if set_result.returncode != 0: + raise Exception("Failed to set zfs mountpoint") + if dataset['canMount'] == 'noauto': + mount_result = subprocess.run(['zfs', 'mount', dataset['zpool'] + '/' + dataset['dsName']]) + if mount_result.returncode != 0: + raise Exception("Failed to mount root dataset") + + # Set the canmount property for each dataset. This will effectively mount the dataset + for dataset in zfs: + # We already handled the / mountpoint above + if dataset['mountpoint'] != '/': + can_mount = parse_global_storage(dataset['canMount']) + + set_result = subprocess.run(['zfs', 'set', 'canmount=' + can_mount, + dataset['zpool'] + '/' + dataset['dsName']]) + if set_result.returncode != 0: + raise Exception("Failed to set zfs mountpoint") + else: + set_result = subprocess.run(['zfs', 'set', 'canmount=on', pool_name + '/' + ds_name]) + if set_result.returncode != 0: + raise Exception("Failed to set zfs mountpoint") + + def mount_partition(root_mount_point, partition, partitions): """ Do a single mount of @p partition inside @p root_mount_point. @@ -112,71 +188,7 @@ def mount_partition(root_mount_point, partition, partitions): device = os.path.join("/dev/mapper", partition["luksMapperName"]) if fstype == "zfs": - zfs_pool_list = libcalamares.globalstorage.value("zfsPoolInfo") - if not zfs_pool_list: - libcalamares.utils.warning("Failed to locate zfsPoolInfo data in global storage") - raise Exception("Internal error mounting zfs datasets") - - 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 - import_result = subprocess.run(['zpool', 'import', '-R', root_mount_point, pool_name]) - if import_result.returncode != 0: - raise Exception("Failed to import zpool") - - 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 - loadkey_result = subprocess.run(['sh', '-c', 'echo "' + passphrase + '" | zfs load-key ' + pool_name]) - if loadkey_result.returncode != 0: - raise Exception("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 Exception("Internal error mounting zfs datasets") - - # first we handle the / dataset if there is one - for dataset in zfs: - if dataset['mountpoint'] == '/': - # Properly set the canmount field from global storage - can_mount = parse_global_storage(dataset['canMount']) - set_result = subprocess.run(['zfs', 'set', 'canmount=' + can_mount, - dataset['zpool'] + '/' + dataset['dsName']]) - if set_result.returncode != 0: - raise Exception("Failed to set zfs mountpoint") - if dataset['canMount'] == 'noauto': - mount_result = subprocess.run(['zfs', 'mount', dataset['zpool'] + '/' + dataset['dsName']]) - if mount_result.returncode != 0: - raise Exception("Failed to mount root dataset") - - # Set the canmount property for each dataset. This will effectively mount the dataset - for dataset in zfs: - # We already handled the / mountpoint above - if dataset['mountpoint'] != '/': - can_mount = parse_global_storage(dataset['canMount']) - - set_result = subprocess.run(['zfs', 'set', 'canmount=' + can_mount, - dataset['zpool'] + '/' + dataset['dsName']]) - if set_result.returncode != 0: - raise Exception("Failed to set zfs mountpoint") - else: - set_result = subprocess.run(['zfs', 'set', 'canmount=on', pool_name + '/' + ds_name]) - if set_result.returncode != 0: - raise Exception("Failed to set zfs mountpoint") + mount_zfs(root_mount_point, partition) else: # fstype == "zfs" if libcalamares.utils.mount(device, mount_point, From 3ebe695a23b981fab1e42f34d937593f05b77e37 Mon Sep 17 00:00:00 2001 From: dalto Date: Sun, 14 Nov 2021 09:07:58 -0600 Subject: [PATCH 070/127] [fstab] Exclude zfs partitions from fstab --- src/modules/fstab/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/fstab/main.py b/src/modules/fstab/main.py index 5bc2d3344..3a2dbcf41 100644 --- a/src/modules/fstab/main.py +++ b/src/modules/fstab/main.py @@ -196,7 +196,7 @@ class FstabGenerator(object): dct = self.generate_fstab_line_info(mount_entry) if dct: self.print_fstab_line(dct, file=fstab_file) - else: + 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) From 3a9038269935b5ec8b0c88a6d9a3edbc7824b936 Mon Sep 17 00:00:00 2001 From: dalto Date: Mon, 15 Nov 2021 17:59:33 -0600 Subject: [PATCH 071/127] [partition] zfs changes from review feedback --- src/modules/bootloader/main.py | 43 ++++++++++++++++++++++------------ 1 file changed, 28 insertions(+), 15 deletions(-) diff --git a/src/modules/bootloader/main.py b/src/modules/bootloader/main.py index 10e7d0f47..54914beb7 100644 --- a/src/modules/bootloader/main.py +++ b/src/modules/bootloader/main.py @@ -108,16 +108,34 @@ def get_zfs_root(): # Find the root dataset for dataset in zfs: try: - if dataset['mountpoint'] == '/': + if dataset["mountpoint"] == "/": return dataset["zpool"] + "/" + dataset["dsname"] except KeyError: # This should be impossible - libcalamares.utils.error("Internal error handling zfs dataset") + 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 create_systemd_boot_conf(install_path, efi_dir, uuid, entry, entry_name, kernel_type): """ Creates systemd-boot configuration files based on given parameters. @@ -162,17 +180,18 @@ def create_systemd_boot_conf(install_path, efi_dir, uuid, entry, entry_name, ker for partition in partitions: # systemd-boot with a BTRFS root filesystem needs to be told # about the root subvolume. - if partition["mountPoint"] == "/" and partition["fs"] == "btrfs": + if is_btrfs_root(partition): kernel_params.append("rootflags=subvol=@") # zfs needs to be told the location of the root dataset - if partition["mountPoint"] == "/" and partition["fs"] == "zfs": - zfs_root = get_zfs_root - if zfs_root is not None: - kernel_params.append("root=ZFS=" + zfs_root) + 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.error("Internal error handling zfs dataset") + 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) @@ -363,14 +382,8 @@ def run_grub_mkconfig(output_file): libcalamares.utils.error("Failed to run grub-mkconfig, no partitions defined in global storage") return - # check for zfs - is_zfs = False - for partition in partitions: - if partition["mountPoint"] == "/" and partition["fs"] == "zfs": - is_zfs = True - # zfs needs an environment variable set for grub-mkconfig - if is_zfs: + if any([is_zfs_root(partition) for partition in partitions]): check_target_env_call(["sh", "-c", "echo ZPOOL_VDEV_NAME_PATH=1 >> /etc/environment"]) check_target_env_call(["sh", "-c", "ZPOOL_VDEV_NAME_PATH=1 " + libcalamares.job.configuration["grubMkconfig"] + " -o " + output_file]) From 4778d9b2ddd5ef319f8da920161826e153d1aee6 Mon Sep 17 00:00:00 2001 From: dalto Date: Mon, 15 Nov 2021 18:00:04 -0600 Subject: [PATCH 072/127] [mount] zfs changes from review feedback --- src/modules/mount/main.py | 76 ++++++++++++++++++++++++--------------- 1 file changed, 48 insertions(+), 28 deletions(-) diff --git a/src/modules/mount/main.py b/src/modules/mount/main.py index 72c2015c6..056b0b415 100644 --- a/src/modules/mount/main.py +++ b/src/modules/mount/main.py @@ -26,6 +26,17 @@ _ = gettext.translation("calamares-python", 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.") @@ -61,7 +72,7 @@ def get_btrfs_subvolumes(partitions): return btrfs_subvolumes -def parse_global_storage(gs_value): +def bool_to_zfs_command(gs_value): """ Something in the chain is converting on and off to true and false. This converts it back. :param gs_value: The value from global storage which needs to be fixed @@ -86,7 +97,7 @@ def mount_zfs(root_mount_point, partition): zfs_pool_list = libcalamares.globalstorage.value("zfsPoolInfo") if not zfs_pool_list: libcalamares.utils.warning("Failed to locate zfsPoolInfo data in global storage") - raise Exception("Internal error mounting zfs datasets") + raise ZfsException(_("Internal error mounting zfs datasets")) # Find the zpool matching this partition for zfs_pool in zfs_pool_list: @@ -95,9 +106,10 @@ def mount_zfs(root_mount_point, partition): ds_name = zfs_pool["dsName"] # import the zpool - import_result = subprocess.run(['zpool', 'import', '-R', root_mount_point, pool_name]) - if import_result.returncode != 0: - raise Exception("Failed to import zpool") + try: + libcalamares.utils.host_env_process_output(["zpool", "import", "-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") @@ -110,9 +122,11 @@ def mount_zfs(root_mount_point, partition): if encrypt is True: # The zpool is encrypted, we need to unlock it - loadkey_result = subprocess.run(['sh', '-c', 'echo "' + passphrase + '" | zfs load-key ' + pool_name]) - if loadkey_result.returncode != 0: - raise Exception("Failed to unlock zpool") + try: + libcalamares.utils.host_env_process_output(["sh", "-c", + "echo \"" + passphrase + "\" | zfs load-key " + pool_name], None) + except subprocess.CalledProcessError: + raise ZfsException(_("Failed to unlock zpool")) if partition["mountPoint"] == '/': # Get the zfs dataset list from global storage @@ -120,36 +134,39 @@ def mount_zfs(root_mount_point, partition): if not zfs: libcalamares.utils.warning("Failed to locate zfs dataset list") - raise Exception("Internal error mounting zfs datasets") + raise ZfsException(_("Internal error mounting zfs datasets")) # first we handle the / dataset if there is one for dataset in zfs: if dataset['mountpoint'] == '/': # Properly set the canmount field from global storage - can_mount = parse_global_storage(dataset['canMount']) - set_result = subprocess.run(['zfs', 'set', 'canmount=' + can_mount, - dataset['zpool'] + '/' + dataset['dsName']]) - if set_result.returncode != 0: - raise Exception("Failed to set zfs mountpoint") - if dataset['canMount'] == 'noauto': - mount_result = subprocess.run(['zfs', 'mount', dataset['zpool'] + '/' + dataset['dsName']]) + can_mount = bool_to_zfs_command(dataset['canMount']) + try: + libcalamares.utils.host_env_process_output(["zfs", "set", "canmount=" + can_mount, + dataset["zpool"] + "/" + dataset["dsName"]], None) + except subprocess.CalledProcessError: + raise ZfsException(_("Failed to set zfs mountpoint")) + if dataset["canMount"] == "noauto": + mount_result = subprocess.run(["zfs", "mount", dataset["zpool"] + '/' + dataset["dsName"]]) if mount_result.returncode != 0: - raise Exception("Failed to mount root dataset") + raise ZfsException(_("Failed to mount root dataset")) # Set the canmount property for each dataset. This will effectively mount the dataset for dataset in zfs: # We already handled the / mountpoint above - if dataset['mountpoint'] != '/': - can_mount = parse_global_storage(dataset['canMount']) + if dataset["mountpoint"] != '/': + can_mount = bool_to_zfs_command(dataset["canMount"]) - set_result = subprocess.run(['zfs', 'set', 'canmount=' + can_mount, - dataset['zpool'] + '/' + dataset['dsName']]) - if set_result.returncode != 0: - raise Exception("Failed to set zfs mountpoint") + try: + libcalamares.utils.host_env_process_output(["zfs", "set", "canmount=" + can_mount, + dataset["zpool"] + "/" + dataset["dsName"]], None) + except subprocess.CalledProcessError: + raise ZfsException(_("Failed to set zfs mountpoint")) else: - set_result = subprocess.run(['zfs', 'set', 'canmount=on', pool_name + '/' + ds_name]) - if set_result.returncode != 0: - raise Exception("Failed to set zfs mountpoint") + try: + libcalamares.utils.host_env_process_output(["zfs", "set", "canmount=on", pool_name + "/" + ds_name], None) + except subprocess.CalledProcessError: + raise ZfsException(_("Failed to set zfs mountpoint")) def mount_partition(root_mount_point, partition, partitions): @@ -255,8 +272,11 @@ def run(): # 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"]) - for partition in mountable_partitions: - mount_partition(root_mount_point, partition, partitions) + try: + for partition in mountable_partitions: + mount_partition(root_mount_point, partition, partitions) + except ZfsException as ze: + return _("zfs mounting error"), ze.message libcalamares.globalstorage.insert("rootMountPoint", root_mount_point) From 0a7262148ebc49798b3571b23a5fd11092028686 Mon Sep 17 00:00:00 2001 From: dalto Date: Mon, 15 Nov 2021 19:03:20 -0600 Subject: [PATCH 073/127] [umount] Convert zfs export call to use host_env_process_output --- src/modules/umount/main.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/modules/umount/main.py b/src/modules/umount/main.py index 795eb8d2b..77ea91e34 100644 --- a/src/modules/umount/main.py +++ b/src/modules/umount/main.py @@ -60,8 +60,9 @@ def export_zpools(root_mount_point): zfs_pool_list.sort(reverse=True, key=lambda x: x["poolName"]) if zfs_pool_list: for zfs_pool in zfs_pool_list: - import_result = subprocess.run(['zpool', 'export', root_mount_point, zfs_pool["poolName"]]) - if import_result.returncode != 0: + try: + libcalamares.utils.host_env_process_output(['zpool', 'export', zfs_pool["poolName"]]) + except subprocess.CalledProcessError: libcalamares.utils.warning("Failed to export zpool") except Exception as e: # If this fails it shouldn't cause the installation to fail From 3aac4dea6796ca6142dd9c368f666b5d368307ec Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Tue, 16 Nov 2021 13:52:10 +0100 Subject: [PATCH 074/127] [partition] Remove logging-of-a-pointer during device detection --- src/modules/partition/core/PartitionCoreModule.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/modules/partition/core/PartitionCoreModule.cpp b/src/modules/partition/core/PartitionCoreModule.cpp index 69a6db535..109e5182e 100644 --- a/src/modules/partition/core/PartitionCoreModule.cpp +++ b/src/modules/partition/core/PartitionCoreModule.cpp @@ -257,7 +257,6 @@ PartitionCoreModule::doInit() cDebug() << Logger::SubEntry << "node\tcapacity\tname\tprettyName"; for ( auto device : devices ) { - cDebug() << Logger::SubEntry << Logger::Pointer( device ); if ( device ) { // Gives ownership of the Device* to the DeviceInfo object From 4db4e983e3cf6049279538d8b2b47db1382bd297 Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Tue, 16 Nov 2021 14:04:00 +0100 Subject: [PATCH 075/127] [partition] Don't format tables of attributes in source --- .../partition/jobs/FillGlobalStorageJob.cpp | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/modules/partition/jobs/FillGlobalStorageJob.cpp b/src/modules/partition/jobs/FillGlobalStorageJob.cpp index 40e67d620..3af70ed05 100644 --- a/src/modules/partition/jobs/FillGlobalStorageJob.cpp +++ b/src/modules/partition/jobs/FillGlobalStorageJob.cpp @@ -105,13 +105,18 @@ mapForPartition( Partition* partition, const QString& uuid ) // so indent a bit Logger::CDebug deb; using TR = Logger::DebugRow< const char* const, const QString& >; + // clang-format off deb << Logger::SubEntry << "mapping for" << partition->partitionPath() << partition->deviceNode() - << TR( "partlabel", map[ "partlabel" ].toString() ) << TR( "partuuid", map[ "partuuid" ].toString() ) - << TR( "parttype", map[ "parttype" ].toString() ) << TR( "partattrs", map[ "partattrs" ].toString() ) - << TR( "mountPoint:", PartitionInfo::mountPoint( partition ) ) << TR( "fs:", map[ "fs" ].toString() ) - << TR( "fsName", map[ "fsName" ].toString() ) << TR( "uuid", uuid ) + << TR( "partlabel", map[ "partlabel" ].toString() ) + << TR( "partuuid", map[ "partuuid" ].toString() ) + << TR( "parttype", map[ "parttype" ].toString() ) + << TR( "partattrs", map[ "partattrs" ].toString() ) + << TR( "mountPoint:", PartitionInfo::mountPoint( partition ) ) + << TR( "fs:", map[ "fs" ].toString() ) + << TR( "fsName", map[ "fsName" ].toString() ) + << TR( "uuid", uuid ) << TR( "claimed", map[ "claimed" ].toString() ); - + // clang-format on if ( partition->roles().has( PartitionRole::Luks ) ) { const FileSystem& fsRef = partition->fileSystem(); From 7cc84b89be25c1a4c112666f9d3c9ebe9c66464d Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Tue, 16 Nov 2021 14:15:00 +0100 Subject: [PATCH 076/127] [partition] Clarify the meaning of the various UUIDs in debug-output --- src/modules/partition/jobs/FillGlobalStorageJob.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/modules/partition/jobs/FillGlobalStorageJob.cpp b/src/modules/partition/jobs/FillGlobalStorageJob.cpp index 3af70ed05..d39e7d858 100644 --- a/src/modules/partition/jobs/FillGlobalStorageJob.cpp +++ b/src/modules/partition/jobs/FillGlobalStorageJob.cpp @@ -108,13 +108,13 @@ mapForPartition( Partition* partition, const QString& uuid ) // clang-format off deb << Logger::SubEntry << "mapping for" << partition->partitionPath() << partition->deviceNode() << TR( "partlabel", map[ "partlabel" ].toString() ) - << TR( "partuuid", map[ "partuuid" ].toString() ) + << TR( "partition-uuid (partuuid)", map[ "partuuid" ].toString() ) << TR( "parttype", map[ "parttype" ].toString() ) << TR( "partattrs", map[ "partattrs" ].toString() ) << TR( "mountPoint:", PartitionInfo::mountPoint( partition ) ) << TR( "fs:", map[ "fs" ].toString() ) << TR( "fsName", map[ "fsName" ].toString() ) - << TR( "uuid", uuid ) + << TR( "filesystem-uuid (uuid)", uuid ) << TR( "claimed", map[ "claimed" ].toString() ); // clang-format on if ( partition->roles().has( PartitionRole::Luks ) ) From 7b3c4db8f00495bc58a173a11f67329ea42d6ec0 Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Tue, 16 Nov 2021 14:21:46 +0100 Subject: [PATCH 077/127] [libcalamares] Redacted -> RedactedCommand - For logging (shell) commands where a password might become visible, use RedactedCommand. Rename it to allow for other kinds of redaction, too. --- src/libcalamares/utils/Logger.cpp | 2 +- src/libcalamares/utils/Logger.h | 6 +++--- src/libcalamares/utils/Runner.cpp | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/libcalamares/utils/Logger.cpp b/src/libcalamares/utils/Logger.cpp index 79ae873db..01c3e1539 100644 --- a/src/libcalamares/utils/Logger.cpp +++ b/src/libcalamares/utils/Logger.cpp @@ -229,7 +229,7 @@ toString( const QVariant& v ) } QDebug& -operator<<( QDebug& s, const Redacted& l ) +operator<<( QDebug& s, const RedactedCommand& l ) { // Special case logging: don't log the (encrypted) password. if ( l.list.contains( "usermod" ) ) diff --git a/src/libcalamares/utils/Logger.h b/src/libcalamares/utils/Logger.h index bf6b99d00..9f0aeffea 100644 --- a/src/libcalamares/utils/Logger.h +++ b/src/libcalamares/utils/Logger.h @@ -214,9 +214,9 @@ public: * since the log may get posted to bug reports, or stored in * the target system. */ -struct Redacted +struct RedactedCommand { - Redacted( const QStringList& l ) + RedactedCommand( const QStringList& l ) : list( l ) { } @@ -224,7 +224,7 @@ struct Redacted const QStringList& list; }; -QDebug& operator<<( QDebug& s, const Redacted& l ); +QDebug& operator<<( QDebug& s, const RedactedCommand& l ); /** * @brief Formatted logging of a pointer diff --git a/src/libcalamares/utils/Runner.cpp b/src/libcalamares/utils/Runner.cpp index e138b1c68..c7146c2d7 100644 --- a/src/libcalamares/utils/Runner.cpp +++ b/src/libcalamares/utils/Runner.cpp @@ -163,7 +163,7 @@ Calamares::Utils::Runner::run() } ); } - cDebug() << Logger::SubEntry << "Running" << Logger::Redacted( m_command ); + cDebug() << Logger::SubEntry << "Running" << Logger::RedactedCommand( m_command ); process.start(); if ( !process.waitForStarted() ) { @@ -225,13 +225,13 @@ Calamares::Utils::Runner::run() { if ( !output.isEmpty() ) { - cDebug() << Logger::SubEntry << "Target cmd:" << Logger::Redacted( m_command ) << "Exit code:" << r + cDebug() << Logger::SubEntry << "Target cmd:" << Logger::RedactedCommand( m_command ) << "Exit code:" << r << "output:\n" << Logger::NoQuote << output; } else { - cDebug() << Logger::SubEntry << "Target cmd:" << Logger::Redacted( m_command ) << "Exit code:" << r + cDebug() << Logger::SubEntry << "Target cmd:" << Logger::RedactedCommand( m_command ) << "Exit code:" << r << "(no output)"; } } From 152b3c333be53cd99a3b19214bc9bf4e45b75706 Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Tue, 16 Nov 2021 14:47:13 +0100 Subject: [PATCH 078/127] [libcalamares] Introduce redaction-of-names class for logging - redacted names are stable inside of one run of Calamares - random, private displays of a given string for a context SEE #1593 --- src/libcalamares/utils/Logger.cpp | 32 +++++++++++++++++++++++++++++++ src/libcalamares/utils/Logger.h | 17 ++++++++++++++++ 2 files changed, 49 insertions(+) diff --git a/src/libcalamares/utils/Logger.cpp b/src/libcalamares/utils/Logger.cpp index 01c3e1539..2b952cc56 100644 --- a/src/libcalamares/utils/Logger.cpp +++ b/src/libcalamares/utils/Logger.cpp @@ -20,6 +20,8 @@ #include #include #include +#include +#include #include #include @@ -252,4 +254,34 @@ operator<<( QDebug& s, const RedactedCommand& l ) return s; } +/** @brief Returns a stable-but-private hash of @p context and @p s + * + * Identical strings with the same context will be hashed the same, + * so that they can be logged and still recognized as the-same. + */ +static uint insertRedactedName( const QString& context, const QString& s ) +{ + static uint salt = QRandomGenerator::global()->generate(); // Just once + + uint val = qHash(context, salt); + return qHash(s, val); +} + +RedactedName::RedactedName( const QString& context, const QString& s ) + : m_id( insertRedactedName(context, s) ), + m_context(context) +{ +} + +RedactedName::RedactedName(const char *context, const QString& s ) + : RedactedName( QString::fromLatin1(context), s ) +{ +} + +QDebug& +operator<< ( QDebug& s, const RedactedName& n ) +{ + return s << NoQuote << n.m_context << '$' << n.m_id << Quote; +} + } // namespace Logger diff --git a/src/libcalamares/utils/Logger.h b/src/libcalamares/utils/Logger.h index 9f0aeffea..70b29b98e 100644 --- a/src/libcalamares/utils/Logger.h +++ b/src/libcalamares/utils/Logger.h @@ -226,6 +226,23 @@ struct RedactedCommand QDebug& operator<<( QDebug& s, const RedactedCommand& l ); +/** @brief When logging "private" identifiers, keep them consistent but private + * + * Send a string to a logger in such a way that each time it is logged, + * it logs the same way, but without revealing the actual contents. + * This can be applied to user names, UUIDs, etc. + */ +struct RedactedName +{ + RedactedName( const char* context, const QString& s ); + RedactedName( const QString& context, const QString& s ); + + const uint m_id; + const QString m_context; +}; + +QDebug& operator<<( QDebug& s, const RedactedName& n ); + /** * @brief Formatted logging of a pointer * From 5a4e2b73ab52cfb34f9168bc3a7c0dcede4b8334 Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Tue, 16 Nov 2021 14:54:09 +0100 Subject: [PATCH 079/127] [libcalamares][partition] Give RedactedName a convert-to-QString - use hex-trailer - while here, convert DebugRow to use a copy rather than a reference, to avoid dangling references when applied to temporaries - convert *partition* module to use the RedactedNames --- src/libcalamares/utils/Logger.cpp | 7 +++---- src/libcalamares/utils/Logger.h | 12 +++++++++--- src/modules/partition/core/PartitionCoreModule.cpp | 7 +++++-- src/modules/partition/jobs/FillGlobalStorageJob.cpp | 6 +++--- 4 files changed, 20 insertions(+), 12 deletions(-) diff --git a/src/libcalamares/utils/Logger.cpp b/src/libcalamares/utils/Logger.cpp index 2b952cc56..d35d6891b 100644 --- a/src/libcalamares/utils/Logger.cpp +++ b/src/libcalamares/utils/Logger.cpp @@ -274,14 +274,13 @@ RedactedName::RedactedName( const QString& context, const QString& s ) } RedactedName::RedactedName(const char *context, const QString& s ) - : RedactedName( QString::fromLatin1(context), s ) + : RedactedName( QString::fromLatin1( context ), s ) { } -QDebug& -operator<< ( QDebug& s, const RedactedName& n ) +RedactedName::operator QString() const { - return s << NoQuote << n.m_context << '$' << n.m_id << Quote; + return QString( m_context + '$' + QString::number( m_id, 16 ) ); } } // namespace Logger diff --git a/src/libcalamares/utils/Logger.h b/src/libcalamares/utils/Logger.h index 70b29b98e..0d7d5c870 100644 --- a/src/libcalamares/utils/Logger.h +++ b/src/libcalamares/utils/Logger.h @@ -145,8 +145,8 @@ public: { } - const T& first; - const U& second; + const T first; + const U second; }; /** @@ -237,11 +237,17 @@ struct RedactedName RedactedName( const char* context, const QString& s ); RedactedName( const QString& context, const QString& s ); + operator QString() const; + +private: const uint m_id; const QString m_context; }; -QDebug& operator<<( QDebug& s, const RedactedName& n ); +inline QDebug& operator<<( QDebug& s, const RedactedName& n ) +{ + return s << NoQuote << QString( n ) << Quote; +} /** * @brief Formatted logging of a pointer diff --git a/src/modules/partition/core/PartitionCoreModule.cpp b/src/modules/partition/core/PartitionCoreModule.cpp index 109e5182e..2475937d2 100644 --- a/src/modules/partition/core/PartitionCoreModule.cpp +++ b/src/modules/partition/core/PartitionCoreModule.cpp @@ -262,8 +262,11 @@ PartitionCoreModule::doInit() // Gives ownership of the Device* to the DeviceInfo object auto deviceInfo = new DeviceInfo( device ); m_deviceInfos << deviceInfo; - cDebug() << Logger::SubEntry << device->deviceNode() << device->capacity() << device->name() - << device->prettyName(); + cDebug() << Logger::SubEntry + << device->deviceNode() + << device->capacity() + << Logger::RedactedName( "DevName", device->name() ) + << Logger::RedactedName( "DevNamePretty", device->prettyName() ); } else { diff --git a/src/modules/partition/jobs/FillGlobalStorageJob.cpp b/src/modules/partition/jobs/FillGlobalStorageJob.cpp index d39e7d858..5be353113 100644 --- a/src/modules/partition/jobs/FillGlobalStorageJob.cpp +++ b/src/modules/partition/jobs/FillGlobalStorageJob.cpp @@ -104,17 +104,17 @@ mapForPartition( Partition* partition, const QString& uuid ) // Debugging for inside the loop in createPartitionList(), // so indent a bit Logger::CDebug deb; - using TR = Logger::DebugRow< const char* const, const QString& >; + using TR = Logger::DebugRow< const char* const, const QString >; // clang-format off deb << Logger::SubEntry << "mapping for" << partition->partitionPath() << partition->deviceNode() << TR( "partlabel", map[ "partlabel" ].toString() ) - << TR( "partition-uuid (partuuid)", map[ "partuuid" ].toString() ) + << TR( "partition-uuid (partuuid)", Logger::RedactedName( "PartUUID", map[ "partuuid" ].toString() ) ) << TR( "parttype", map[ "parttype" ].toString() ) << TR( "partattrs", map[ "partattrs" ].toString() ) << TR( "mountPoint:", PartitionInfo::mountPoint( partition ) ) << TR( "fs:", map[ "fs" ].toString() ) << TR( "fsName", map[ "fsName" ].toString() ) - << TR( "filesystem-uuid (uuid)", uuid ) + << TR( "filesystem-uuid (uuid)", Logger::RedactedName( "FSUUID", uuid ) ) << TR( "claimed", map[ "claimed" ].toString() ); // clang-format on if ( partition->roles().has( PartitionRole::Luks ) ) From efe84bc6c0f1ee8f81a10617a5d55dd43f87422e Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Tue, 16 Nov 2021 15:31:35 +0100 Subject: [PATCH 080/127] [partition] Don't log private names - log device node (/dev/sdb) instead of its name - don't log job's prettyName() because that's translated, and also contains user-visible private names (introducing a non-translated, nicely redacted version of prettyName() seems like too much effort for something that can be reconstructed from bits earlier in the log) --- src/modules/partition/core/PartitionCoreModule.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/modules/partition/core/PartitionCoreModule.cpp b/src/modules/partition/core/PartitionCoreModule.cpp index 2475937d2..16e5a7ea1 100644 --- a/src/modules/partition/core/PartitionCoreModule.cpp +++ b/src/modules/partition/core/PartitionCoreModule.cpp @@ -709,10 +709,10 @@ PartitionCoreModule::dumpQueue() const cDebug() << "# Queue:"; for ( auto info : m_deviceInfos ) { - cDebug() << Logger::SubEntry << "## Device:" << info->device->name(); + cDebug() << Logger::SubEntry << "## Device:" << info->device->deviceNode(); for ( const auto& job : info->jobs() ) { - cDebug() << Logger::SubEntry << "-" << job->prettyName(); + cDebug() << Logger::SubEntry << "-" << job->metaObject()->className(); } } } From 3ee388526d329cac3eb529e8f330bd180583a359 Mon Sep 17 00:00:00 2001 From: dalto Date: Tue, 16 Nov 2021 09:06:42 -0600 Subject: [PATCH 081/127] [zfs] Cleanup code based on review feedback --- src/modules/zfs/ZfsJob.cpp | 163 ++++++++++++++++++++----------------- src/modules/zfs/ZfsJob.h | 15 +--- 2 files changed, 94 insertions(+), 84 deletions(-) diff --git a/src/modules/zfs/ZfsJob.cpp b/src/modules/zfs/ZfsJob.cpp index d0367f5f6..4b1d6eaf3 100644 --- a/src/modules/zfs/ZfsJob.cpp +++ b/src/modules/zfs/ZfsJob.cpp @@ -19,6 +19,51 @@ #include +#include + +/** @brief Returns the alphanumeric portion of a string + * + * @p input is the input string + * + */ +static QString +alphaNumeric( QString input ) +{ + return input.remove( QRegExp( "[^a-zA-Z\\d\\s]" ) ); +} + +/** @brief Returns the best available device for zpool creation + * + * zfs partitions generally don't have UUID until the zpool is created. Generally, + * they are formed using either the id or the partuuid. The id isn't stored by kpmcore + * so this function checks to see if we have a partuuid. If so, it forms a device path + * for it. As a backup, it uses the device name i.e. /dev/sdax. + * + * The function returns a fullt qualified path to the device or an empty string if no device + * is found + * + * @p pMap is the partition map from global storage + * + */ +static QString +findBestZfsDevice( QVariantMap pMap ) +{ + // Find the best device identifier, if one isn't available, skip this partition + QString deviceName; + if ( pMap[ "partuuid" ].toString() != "" ) + { + return "/dev/disk/by-partuuid/" + pMap[ "partuuid" ].toString().toLower(); + } + else if ( pMap[ "device" ].toString() != "" ) + { + return pMap[ "device" ].toString().toLower(); + } + else + { + return QString(); + } +} + ZfsJob::ZfsJob( QObject* parent ) : Calamares::CppJob( parent ) { @@ -32,14 +77,8 @@ ZfsJob::prettyName() const return tr( "Create ZFS pools and datasets" ); } -QString -ZfsJob::AlphaNumeric( QString input ) const -{ - return input.remove( QRegExp( "[^a-zA-Z\\d\\s]" ) ); -} - void -ZfsJob::CollectMountpoints( const QVariantList& partitions ) +ZfsJob::collectMountpoints( const QVariantList& partitions ) { m_mountpoints.empty(); for ( const QVariant& partition : partitions ) @@ -56,7 +95,7 @@ ZfsJob::CollectMountpoints( const QVariantList& partitions ) } bool -ZfsJob::IsMountpointOverlapping( const QString& targetMountpoint ) const +ZfsJob::isMountpointOverlapping( const QString& targetMountpoint ) const { for ( const QString& mountpoint : m_mountpoints ) { @@ -70,49 +109,32 @@ ZfsJob::IsMountpointOverlapping( const QString& targetMountpoint ) const ZfsResult -ZfsJob::CreateZpool( QString deviceName, QString poolName, QString poolOptions, bool encrypt, QString passphrase ) const +ZfsJob::createZpool( QString deviceName, QString poolName, QString poolOptions, bool encrypt, QString passphrase ) const { // zfs doesn't wait for the devices so pause for 2 seconds to ensure we give time for the device files to be created - QString command; + sleep( 2 ); + + QStringList command; if ( encrypt ) { - command = "sleep 2 ; echo \"" + passphrase + "\" | zpool create " + poolOptions - + " -O encryption=aes-256-gcm -O keyformat=passphrase " + poolName + " " + deviceName; + command = QStringList() << "zpool" + << "create" << poolOptions.split( ' ' ) << "-O" + << "encryption=aes-256-gcm" + << "-O" + << "keyformat=passphrase" << poolName << deviceName; } else { - command = "sleep 2 ; zpool create " + poolOptions + " " + poolName + " " + deviceName; + command = QStringList() << "zpool" + << "create" << poolOptions.split( ' ' ) << poolName << deviceName; } - // We use a qProcess instead of runCommand so the password will not end up in the logs - QProcess process; + auto r = CalamaresUtils::System::instance()->runCommand( + CalamaresUtils::System::RunLocation::RunInHost, command, QString(), passphrase, std::chrono::seconds( 10 ) ); - process.setProcessChannelMode( QProcess::MergedChannels ); - cDebug() << Logger::SubEntry << "Running zpool create"; - - process.start( "sh", QStringList() << "-c" << command ); - - if ( !process.waitForStarted() ) + if ( r.getExitCode() != 0 ) { - return { false, tr( "zpool create process failed to start" ) }; - } - - if ( !process.waitForFinished( 5000 ) ) - { - return { false, tr( "Process for zpool create timed out" ) }; - } - - QString output = QString::fromLocal8Bit( process.readAllStandardOutput() ).trimmed(); - - if ( process.exitStatus() == QProcess::CrashExit ) - { - return { false, tr( "The output from the crash was: " ) + output }; - } - - auto exitcode = process.exitCode(); - if ( exitcode != 0 ) - { - cWarning() << "Failed to run zpool create. The output was: " + output; + cWarning() << "Failed to run zpool create. The output was: " + r.getOutput(); return { false, tr( "Failed to create zpool on " ) + deviceName }; } @@ -140,6 +162,13 @@ ZfsJob::exec() QVariantList poolNames; + // Check to ensure the list of zfs info from the partition module is available and convert it to a list + if ( !gs->contains( "zfsInfo" ) && gs->value( "zfsInfo" ).canConvert( QVariant::List ) ) + { + return Calamares::JobResult::error( tr( "Internal data missing" ), tr( "Failed to create zpool" ) ); + } + QVariantList zfsInfoList = gs->value( "zfsInfo" ).toList(); + for ( auto& partition : qAsConst( partitions ) ) { QVariantMap pMap; @@ -155,16 +184,8 @@ ZfsJob::exec() } // Find the best device identifier, if one isn't available, skip this partition - QString deviceName; - if ( pMap[ "partuuid" ].toString() != "" ) - { - deviceName = "/dev/disk/by-partuuid/" + pMap[ "partuuid" ].toString().toLower(); - } - else if ( pMap[ "device" ].toString() != "" ) - { - deviceName = pMap[ "device" ].toString().toLower(); - } - else + QString deviceName = findBestZfsDevice( pMap ); + if ( deviceName.isEmpty() ) { continue; } @@ -180,16 +201,9 @@ ZfsJob::exec() QString poolName = m_poolName; if ( mountpoint != '/' ) { - poolName += AlphaNumeric( mountpoint ); + poolName += alphaNumeric( mountpoint ); } - // Check to ensure the list of zfs info from the partition module is available and convert it to a list - if ( !gs->contains( "zfsInfo" ) && gs->value( "zfsInfo" ).canConvert( QVariant::List ) ) - { - return Calamares::JobResult::error( tr( "Internal data missing" ), tr( "Failed to create zpool" ) ); - } - QVariantList zfsInfoList = gs->value( "zfsInfo" ).toList(); - // Look in the zfs info list to see if this partition should be encrypted bool encrypt = false; QString passphrase; @@ -207,11 +221,11 @@ ZfsJob::exec() ZfsResult zfsResult; if ( encrypt ) { - zfsResult = CreateZpool( deviceName, poolName, m_poolOptions, true, passphrase ); + zfsResult = createZpool( deviceName, poolName, m_poolOptions, true, passphrase ); } else { - zfsResult = CreateZpool( deviceName, poolName, m_poolOptions, false ); + zfsResult = createZpool( deviceName, poolName, m_poolOptions, false ); } if ( !zfsResult.success ) @@ -229,7 +243,7 @@ ZfsJob::exec() // If the mountpoint is /, create datasets per the config file. If not, create a single dataset mounted at the partitions mountpoint if ( mountpoint == '/' ) { - CollectMountpoints( partitions ); + collectMountpoints( partitions ); QVariantList datasetList; for ( const auto& dataset : qAsConst( m_datasets ) ) { @@ -244,18 +258,19 @@ ZfsJob::exec() } // We should skip this dataset if it conflicts with a permanent mountpoint - if ( IsMountpointOverlapping( datasetMap[ "mountpoint" ].toString() ) ) + if ( isMountpointOverlapping( datasetMap[ "mountpoint" ].toString() ) ) { continue; } // Create the dataset. We set canmount=no regardless of the setting for now. // It is modified to the correct value in the mount module to ensure mount order is maintained - auto r = system->runCommand( { "sh", - "-c", - "zfs create " + m_datasetOptions + " -o canmount=off -o mountpoint=" - + datasetMap[ "mountpoint" ].toString() + " " + poolName + "/" - + datasetMap[ "dsName" ].toString() }, + auto r = system->runCommand( { QStringList() << "zfs" + << "create" << m_datasetOptions.split( ' ' ) << "-o" + << "canmount=off" + << "-o" + << "mountpoint=" + datasetMap[ "mountpoint" ].toString() + << poolName + "/" + datasetMap[ "dsName" ].toString() }, std::chrono::seconds( 10 ) ); if ( r.getExitCode() != 0 ) { @@ -280,15 +295,17 @@ ZfsJob::exec() // This is a zpool with a single dataset We again set canmount=no regardless of the desired setting. // It is modified to the correct value in the mount module to ensure mount order is maintained QString dsName = mountpoint; - dsName = AlphaNumeric( mountpoint ); - auto r = system->runCommand( { "sh", - "-c", - "zfs create " + m_datasetOptions + " -o canmount=off -o mountpoint=" - + mountpoint + " " + poolName + "/" + dsName }, + dsName = alphaNumeric( mountpoint ); + auto r = system->runCommand( { QStringList() << "zfs" + << "create" << m_datasetOptions.split( ' ' ) << "-o" + << "canmount=off" + << "-o" + << "mountpoint=" + mountpoint << poolName + "/" + dsName }, std::chrono::seconds( 10 ) ); if ( r.getExitCode() != 0 ) { - cWarning() << "Failed to create dataset" << dsName; + return Calamares::JobResult::error( tr( "Failed to create dataset" ), + tr( "The output was: " ) + r.getOutput() ); } poolNameEntry[ "dsName" ] = dsName; } diff --git a/src/modules/zfs/ZfsJob.h b/src/modules/zfs/ZfsJob.h index 4744954c2..58a6450ee 100644 --- a/src/modules/zfs/ZfsJob.h +++ b/src/modules/zfs/ZfsJob.h @@ -23,7 +23,7 @@ struct ZfsResult { bool success; - QString failureMessage; + QString failureMessage; // This message is displayed to the user and should be translated at the time of population }; /** @brief Create zpools and zfs datasets @@ -60,26 +60,19 @@ private: * @p passphrase is a string continaing the passphrase * */ - ZfsResult CreateZpool( QString deviceName, + ZfsResult createZpool( QString deviceName, QString poolName, QString poolOptions, bool encrypt, QString passphrase = QString() ) const; - /** @brief Returns the alphanumeric portion of a string - * - * @p input is the input string - * - */ - QString AlphaNumeric( QString input ) const; - /** @brief Collects all the mountpoints from the partitions * * Iterates over @p partitions to gather each mountpoint present * in the list of maps and populates m_mountpoints * */ - void CollectMountpoints( const QVariantList& partitions ); + void collectMountpoints( const QVariantList& partitions ); /** @brief Check to see if a given mountpoint overlaps with one of the defined moutnpoints * @@ -88,7 +81,7 @@ private: * since all the mountpoints would begin with / * */ - bool IsMountpointOverlapping( const QString& targetMountpoint ) const; + bool isMountpointOverlapping( const QString& targetMountpoint ) const; }; CALAMARES_PLUGIN_FACTORY_DECLARATION( ZfsJobFactory ) From 18307d9f57c00bc7b8c659e47794ea6709161340 Mon Sep 17 00:00:00 2001 From: dalto Date: Tue, 16 Nov 2021 09:34:50 -0600 Subject: [PATCH 082/127] Add zfs module to settings.conf --- settings.conf | 1 + 1 file changed, 1 insertion(+) diff --git a/settings.conf b/settings.conf index bd5b06bda..2897da9b6 100644 --- a/settings.conf +++ b/settings.conf @@ -127,6 +127,7 @@ sequence: # - dummyprocess # - dummypython - partition +# - zfs - mount - unpackfs - machineid From b65321d80b466b870967cb3f378b2c135b1d835f Mon Sep 17 00:00:00 2001 From: dalto Date: Tue, 16 Nov 2021 13:48:34 -0600 Subject: [PATCH 083/127] [bootloader] Add zfs support for grub-install --- src/modules/bootloader/main.py | 104 +++++++++++++++++++++------------ 1 file changed, 68 insertions(+), 36 deletions(-) diff --git a/src/modules/bootloader/main.py b/src/modules/bootloader/main.py index 54914beb7..0d558f6dc 100644 --- a/src/modules/bootloader/main.py +++ b/src/modules/bootloader/main.py @@ -368,23 +368,17 @@ def get_grub_efi_parameters(): return None -def run_grub_mkconfig(output_file): +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: """ - # get the partition from global storage - partitions = libcalamares.globalstorage.value("partitions") - if not partitions: - libcalamares.utils.error("Failed to run grub-mkconfig, no partitions defined in global storage") - 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", "echo ZPOOL_VDEV_NAME_PATH=1 >> /etc/environment"]) check_target_env_call(["sh", "-c", "ZPOOL_VDEV_NAME_PATH=1 " + libcalamares.job.configuration["grubMkconfig"] + " -o " + output_file]) else: @@ -393,6 +387,57 @@ def run_grub_mkconfig(output_file): check_target_env_call([libcalamares.job.configuration["grubMkconfig"], "-o", output_file]) +def run_grub_install(fw_type, partitions, efi_directory=None): + """ + 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": + efi_bootloader_id = efi_label() + 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 libcalamares.globalstorage.value("bootLoader") is None: + return + + boot_loader = libcalamares.globalstorage.value("bootLoader") + if boot_loader["installPath"] is None: + return + + 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["installPath"]]) + else: + check_target_env_call([libcalamares.job.configuration["grubInstall"], + "--target=i386-pc", + "--recheck", + "--force", + boot_loader["installPath"]]) + + def install_grub(efi_directory, fw_type): """ Installs grub as bootloader, either in pc or efi mode. @@ -400,6 +445,12 @@ def install_grub(efi_directory, fw_type): :param efi_directory: :param fw_type: """ + # 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 == "efi": libcalamares.utils.debug("Bootloader: grub (efi)") install_path = libcalamares.globalstorage.value("rootMountPoint") @@ -412,11 +463,7 @@ def install_grub(efi_directory, fw_type): efi_target, efi_grub_file, efi_boot_file = get_grub_efi_parameters() - check_target_env_call([libcalamares.job.configuration["grubInstall"], - "--target=" + efi_target, - "--efi-directory=" + efi_directory, - "--bootloader-id=" + efi_bootloader_id, - "--force"]) + run_grub_install(fw_type, partitions, efi_directory) # VFAT is weird, see issue CAL-385 install_efi_directory_firmware = (vfat_correct_case( @@ -435,36 +482,21 @@ def install_grub(efi_directory, fw_type): os.makedirs(install_efi_boot_directory) # Workaround for some UEFI firmwares - FALLBACK = "installEFIFallback" - libcalamares.utils.debug("UEFI Fallback: " + str(libcalamares.job.configuration.get(FALLBACK, ""))) - if libcalamares.job.configuration.get(FALLBACK, True): + fallback = "installEFIFallback" + libcalamares.utils.debug("UEFI Fallback: " + str(libcalamares.job.configuration.get(fallback, ""))) + 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) + 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) else: libcalamares.utils.debug("Bootloader: grub (bios)") - if libcalamares.globalstorage.value("bootLoader") is None: - return + run_grub_install(fw_type, partitions) - boot_loader = libcalamares.globalstorage.value("bootLoader") - if boot_loader["installPath"] is None: - return - - check_target_env_call([libcalamares.job.configuration["grubInstall"], - "--target=i386-pc", - "--recheck", - "--force", - boot_loader["installPath"]]) - - # 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", libcalamares.job.configuration["grubCfg"]]) + run_grub_mkconfig(partitions, libcalamares.job.configuration["grubCfg"]) def install_secureboot(efi_directory): From 87cca4053fde2473435aeb1c4f854239b31b2ac6 Mon Sep 17 00:00:00 2001 From: dalto Date: Tue, 16 Nov 2021 13:59:24 -0600 Subject: [PATCH 084/127] [zfs][mount] Refactor zfs dataset mounting logic --- src/modules/mount/main.py | 53 +++++++------------------------------- src/modules/zfs/ZfsJob.cpp | 38 +++++++++++++++++++++------ 2 files changed, 40 insertions(+), 51 deletions(-) diff --git a/src/modules/mount/main.py b/src/modules/mount/main.py index 056b0b415..f1d4c0973 100644 --- a/src/modules/mount/main.py +++ b/src/modules/mount/main.py @@ -72,20 +72,6 @@ def get_btrfs_subvolumes(partitions): return btrfs_subvolumes -def bool_to_zfs_command(gs_value): - """ Something in the chain is converting on and off to true and false. This converts it back. - - :param gs_value: The value from global storage which needs to be fixed - :return: - """ - if gs_value is True: - return "on" - elif gs_value is False: - return "off" - else: - return gs_value - - def mount_zfs(root_mount_point, partition): """ Mounts a zfs partition at @p root_mount_point @@ -107,7 +93,7 @@ def mount_zfs(root_mount_point, partition): # import the zpool try: - libcalamares.utils.host_env_process_output(["zpool", "import", "-R", root_mount_point, pool_name], None) + 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")) @@ -123,8 +109,7 @@ def mount_zfs(root_mount_point, partition): if encrypt is True: # The zpool is encrypted, we need to unlock it try: - libcalamares.utils.host_env_process_output(["sh", "-c", - "echo \"" + passphrase + "\" | zfs load-key " + pool_name], None) + libcalamares.utils.host_env_process_output(["zfs", "load-key", pool_name], None, passphrase) except subprocess.CalledProcessError: raise ZfsException(_("Failed to unlock zpool")) @@ -136,35 +121,17 @@ def mount_zfs(root_mount_point, partition): libcalamares.utils.warning("Failed to locate zfs dataset list") raise ZfsException(_("Internal error mounting zfs datasets")) - # first we handle the / dataset if there is one + zfs.sort(key=lambda x: x["mountpoint"]) for dataset in zfs: - if dataset['mountpoint'] == '/': - # Properly set the canmount field from global storage - can_mount = bool_to_zfs_command(dataset['canMount']) - try: - libcalamares.utils.host_env_process_output(["zfs", "set", "canmount=" + can_mount, - dataset["zpool"] + "/" + dataset["dsName"]], None) - except subprocess.CalledProcessError: - raise ZfsException(_("Failed to set zfs mountpoint")) - if dataset["canMount"] == "noauto": - mount_result = subprocess.run(["zfs", "mount", dataset["zpool"] + '/' + dataset["dsName"]]) - if mount_result.returncode != 0: - raise ZfsException(_("Failed to mount root dataset")) - - # Set the canmount property for each dataset. This will effectively mount the dataset - for dataset in zfs: - # We already handled the / mountpoint above - if dataset["mountpoint"] != '/': - can_mount = bool_to_zfs_command(dataset["canMount"]) - - try: - libcalamares.utils.host_env_process_output(["zfs", "set", "canmount=" + can_mount, - dataset["zpool"] + "/" + dataset["dsName"]], None) - except subprocess.CalledProcessError: - raise ZfsException(_("Failed to set zfs mountpoint")) + 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", "set", "canmount=on", pool_name + "/" + ds_name], None) + libcalamares.utils.host_env_process_output(["zfs", "mount", pool_name + '/' + ds_name]) except subprocess.CalledProcessError: raise ZfsException(_("Failed to set zfs mountpoint")) diff --git a/src/modules/zfs/ZfsJob.cpp b/src/modules/zfs/ZfsJob.cpp index 4b1d6eaf3..ce2eaf183 100644 --- a/src/modules/zfs/ZfsJob.cpp +++ b/src/modules/zfs/ZfsJob.cpp @@ -39,7 +39,7 @@ alphaNumeric( QString input ) * so this function checks to see if we have a partuuid. If so, it forms a device path * for it. As a backup, it uses the device name i.e. /dev/sdax. * - * The function returns a fullt qualified path to the device or an empty string if no device + * The function returns a fully qualified path to the device or an empty string if no device * is found * * @p pMap is the partition map from global storage @@ -64,6 +64,30 @@ findBestZfsDevice( QVariantMap pMap ) } } +/** @brief Converts the value in a QVariant to a string which is a valid option for canmount + * + * Storing "on" and "off" in QVariant results in a conversion to boolean. This function takes + * the Qvariant in @p canMount and converts it to a QString holding "on", "off" or the string + * value in the QVariant. + * + */ +static QString +convertCanMount( QVariant canMount ) +{ + if ( canMount == true ) + { + return "on"; + } + else if ( canMount == false ) + { + return "off"; + } + else + { + return canMount.toString(); + } +} + ZfsJob::ZfsJob( QObject* parent ) : Calamares::CppJob( parent ) { @@ -263,12 +287,12 @@ ZfsJob::exec() continue; } - // Create the dataset. We set canmount=no regardless of the setting for now. - // It is modified to the correct value in the mount module to ensure mount order is maintained + QString canMount = convertCanMount( datasetMap[ "canMount" ].toString() ); + + // Create the dataset auto r = system->runCommand( { QStringList() << "zfs" << "create" << m_datasetOptions.split( ' ' ) << "-o" - << "canmount=off" - << "-o" + << "canmount=" + canMount << "-o" << "mountpoint=" + datasetMap[ "mountpoint" ].toString() << poolName + "/" + datasetMap[ "dsName" ].toString() }, std::chrono::seconds( 10 ) ); @@ -292,13 +316,11 @@ ZfsJob::exec() } else { - // This is a zpool with a single dataset We again set canmount=no regardless of the desired setting. - // It is modified to the correct value in the mount module to ensure mount order is maintained QString dsName = mountpoint; dsName = alphaNumeric( mountpoint ); auto r = system->runCommand( { QStringList() << "zfs" << "create" << m_datasetOptions.split( ' ' ) << "-o" - << "canmount=off" + << "canmount=on" << "-o" << "mountpoint=" + mountpoint << poolName + "/" + dsName }, std::chrono::seconds( 10 ) ); From 9603cbef144db7757b22a05cfcdc7fe1a8162a96 Mon Sep 17 00:00:00 2001 From: dalto Date: Tue, 16 Nov 2021 17:48:02 -0600 Subject: [PATCH 085/127] [grubcfg] Add zfs entry to kernel_params --- src/modules/grubcfg/main.py | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/src/modules/grubcfg/main.py b/src/modules/grubcfg/main.py index 9e9615a0c..a4985d41f 100644 --- a/src/modules/grubcfg/main.py +++ b/src/modules/grubcfg/main.py @@ -55,6 +55,32 @@ def get_grub_config_path(root_mount_point): 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 modify_grub_default(partitions, root_mount_point, distributor): """ Configures '/etc/default/grub' for hibernation and plymouth. @@ -141,8 +167,15 @@ def modify_grub_default(partitions, root_mount_point, distributor): ) ] + if partition["fs"] == "zfs" and partition["mountPoint"] == "/": + zfs_root_path = get_zfs_root() + 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) From 7e17106f341bb0a8b9c0e34ae54c1e615c2aec3a Mon Sep 17 00:00:00 2001 From: dalto Date: Tue, 16 Nov 2021 17:48:49 -0600 Subject: [PATCH 086/127] [bootloader] Cleanup zfs support from testing --- src/modules/bootloader/main.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/modules/bootloader/main.py b/src/modules/bootloader/main.py index 0d558f6dc..0121fa90a 100644 --- a/src/modules/bootloader/main.py +++ b/src/modules/bootloader/main.py @@ -99,7 +99,7 @@ def get_zfs_root(): :return: A string containing the path to the zfs root or None if it is not found """ - zfs = libcalamares.globalstorage.value("zfs") + zfs = libcalamares.globalstorage.value("zfsDatasets") if not zfs: libcalamares.utils.warning("Failed to locate zfs dataset list") @@ -109,7 +109,7 @@ def get_zfs_root(): for dataset in zfs: try: if dataset["mountpoint"] == "/": - return dataset["zpool"] + "/" + dataset["dsname"] + return dataset["zpool"] + "/" + dataset["dsName"] except KeyError: # This should be impossible libcalamares.utils.warning("Internal error handling zfs dataset") @@ -187,7 +187,7 @@ def create_systemd_boot_conf(install_path, efi_dir, uuid, entry, entry_name, ker 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) + kernel_params.append("zfs=" + zfs_root_path) else: # Something is really broken if we get to this point libcalamares.utils.warning("Internal error handling zfs dataset") From c70e31a919fe1481dae54af3bbd61030573c5d15 Mon Sep 17 00:00:00 2001 From: dalto Date: Tue, 16 Nov 2021 18:16:32 -0600 Subject: [PATCH 087/127] [zfs] Add README.md with some implementation notes --- src/modules/zfs/README.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 src/modules/zfs/README.md diff --git a/src/modules/zfs/README.md b/src/modules/zfs/README.md new file mode 100644 index 000000000..0114865a0 --- /dev/null +++ b/src/modules/zfs/README.md @@ -0,0 +1,14 @@ +## zfs Module Notes + +There are a few considerations to be aware of when enabling the zfs module +* You must provide zfs kernel modules or kernel support on the ISO for the zfs module to function +* Support for zfs in the partition module is conditional on the zfs module being enabled +* If you use grub with zfs, you must have `ZPOOL_VDEV_NAME_PATH=1` in your environment when running grub-install or grub-mkconfig. + * Calamares will ensure this happens during the bootloader module. + * It will also add it to `/etc/environment` so it will be available in the installation + * If you have an scripts or other processes that trigger grub-mkconfig during the install process, be sure to add that to the environnent +* In most cases, you will need to enable services for zfs support appropriate to your distro. For example, when testing on Arch the following services were enabled: + * zfs.target + * zfs-import-cache + * zfs-mount + * zfs-import.target From e814920bcabb3ba1fd2b6a20f98f19375676f269 Mon Sep 17 00:00:00 2001 From: dalto8 <57767042+dalto8@users.noreply.github.com> Date: Wed, 17 Nov 2021 00:17:59 +0000 Subject: [PATCH 088/127] [zfs] Fix typo in README --- src/modules/zfs/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/zfs/README.md b/src/modules/zfs/README.md index 0114865a0..666c6a5d9 100644 --- a/src/modules/zfs/README.md +++ b/src/modules/zfs/README.md @@ -6,7 +6,7 @@ There are a few considerations to be aware of when enabling the zfs module * If you use grub with zfs, you must have `ZPOOL_VDEV_NAME_PATH=1` in your environment when running grub-install or grub-mkconfig. * Calamares will ensure this happens during the bootloader module. * It will also add it to `/etc/environment` so it will be available in the installation - * If you have an scripts or other processes that trigger grub-mkconfig during the install process, be sure to add that to the environnent + * If you have an scripts or other processes that trigger grub-mkconfig during the install process, be sure to add that to the environment * In most cases, you will need to enable services for zfs support appropriate to your distro. For example, when testing on Arch the following services were enabled: * zfs.target * zfs-import-cache From b0f6530a58365108d6a53172c0fb22435fb14121 Mon Sep 17 00:00:00 2001 From: dalto Date: Thu, 18 Nov 2021 10:04:49 -0600 Subject: [PATCH 089/127] [mount][bootloader] Ensure root subvolume is set correctly for systemd-boot --- src/modules/bootloader/main.py | 9 ++++++--- src/modules/mount/main.py | 30 +++++++++++++++++------------- 2 files changed, 23 insertions(+), 16 deletions(-) diff --git a/src/modules/bootloader/main.py b/src/modules/bootloader/main.py index 0121fa90a..25c7e0d9b 100644 --- a/src/modules/bootloader/main.py +++ b/src/modules/bootloader/main.py @@ -178,10 +178,13 @@ def create_systemd_boot_conf(install_path, efi_dir, uuid, entry, entry_name, ker + partition["luksMapperName"]] for partition in partitions: - # systemd-boot with a BTRFS root filesystem needs to be told - # about the root subvolume. + # systemd-boot with a BTRFS root filesystem needs to be told abouut the root subvolume. + # 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): - kernel_params.append("rootflags=subvol=@") + 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): diff --git a/src/modules/mount/main.py b/src/modules/mount/main.py index f1d4c0973..900342e6d 100644 --- a/src/modules/mount/main.py +++ b/src/modules/mount/main.py @@ -20,6 +20,7 @@ import os import libcalamares import gettext + _ = gettext.translation("calamares-python", localedir=libcalamares.utils.gettext_path(), languages=libcalamares.utils.gettext_languages(), @@ -58,15 +59,16 @@ def get_btrfs_subvolumes(partitions): 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") ] + 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)) + 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_choice = libcalamares.globalstorage.value("partitionChoices") + if swap_choice and swap_choice.get("swap", None) == "file": btrfs_subvolumes.append({'mountPoint': '/swap', 'subvolume': '/@swap'}) return btrfs_subvolumes @@ -189,9 +191,11 @@ def mount_partition(root_mount_point, partition, partitions): libcalamares.globalstorage.insert("btrfsSubvolumes", btrfs_subvolumes) # Create the subvolumes that are in the completed list for s in btrfs_subvolumes: - subprocess.check_call(['btrfs', 'subvolume', 'create', - root_mount_point + s['subvolume']]) - + 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"] @@ -204,9 +208,9 @@ def mount_partition(root_mount_point, partition, partitions): mount_option = "subvol={}".format(s['subvolume']) subvolume_mountpoint = mount_point[:-1] + s['mountPoint'] if libcalamares.utils.mount(device, - subvolume_mountpoint, - fstype, - ",".join([mount_option, partition.get("options", "")])) != 0: + subvolume_mountpoint, + fstype, + ",".join([mount_option, partition.get("options", "")])) != 0: libcalamares.utils.warning("Cannot mount {}".format(device)) @@ -220,7 +224,7 @@ def run(): if not partitions: libcalamares.utils.warning("partitions is empty, {!s}".format(partitions)) return (_("Configuration Error"), - _("No partitions are defined for
{!s}
to use." ).format("mount")) + _("No partitions are defined for
{!s}
to use.").format("mount")) root_mount_point = tempfile.mkdtemp(prefix="calamares-root-") @@ -237,7 +241,7 @@ def run(): # 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 = [p for p in partitions + extra_mounts if "mountPoint" in p and p["mountPoint"]] mountable_partitions.sort(key=lambda x: x["mountPoint"]) try: for partition in mountable_partitions: From f5b882a07517c5d79ab316c12a10b2bc2a422c04 Mon Sep 17 00:00:00 2001 From: dalto Date: Thu, 18 Nov 2021 13:36:23 -0600 Subject: [PATCH 090/127] [partition] Remove setState call to resolve compat issue with older kpmcore --- src/modules/partition/jobs/CreatePartitionJob.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/modules/partition/jobs/CreatePartitionJob.cpp b/src/modules/partition/jobs/CreatePartitionJob.cpp index 07b816b7e..83ebc0509 100644 --- a/src/modules/partition/jobs/CreatePartitionJob.cpp +++ b/src/modules/partition/jobs/CreatePartitionJob.cpp @@ -77,7 +77,6 @@ createZfs( Partition* partition, Device* device ) } partition->setPartitionPath( deviceNode ); - partition->setState( Partition::State::None ); // If it is a gpt device, set the partition UUID if ( device->partitionTable()->type() == PartitionTable::gpt && partition->uuid().isEmpty() ) From 46c59be54165081c48af7e90c1d923d64d5ce93f Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Fri, 19 Nov 2021 11:34:35 +0100 Subject: [PATCH 091/127] Changes: document new things --- CHANGES-3.2 | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGES-3.2 b/CHANGES-3.2 index fc42033c3..9c9a638d1 100644 --- a/CHANGES-3.2 +++ b/CHANGES-3.2 @@ -23,6 +23,8 @@ This release contains contributions from (alphabetically by first name): within one run of Calamares, but differently each time. ## Modules ## + - *bootloader* with systemd-boot now handles root subvolumes better + (Thanks Evan) - *displaymanager* supports the *greetd* display manager, which is a kind of meta-DM itself, supporting multiple greeters. (Thanks Jonas) - *finishedq* now has an extra example QML file that builds the UI in @@ -37,6 +39,10 @@ This release contains contributions from (alphabetically by first name): (Thanks Evan) - The *partition* module no longer logs recognizable disk names or UUIDs. These are redacted in the logs. #1593 + - The *partition* module, together with the new *zfs* module and changes + in *mount* and *bootloader* can install to ZFS **if** the distribution + kernel supports it. ZFS tools are required, as well as the relevant + kernel modules. See the `README.md` in the *zfs* module. (Thanks Evan) # 3.2.46 (2021-11-09) # From bcd8ebd6145616ede8e566fcb1cdba3f52dcb99c Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Fri, 19 Nov 2021 12:53:42 +0100 Subject: [PATCH 092/127] [displaymanager] SPDX tags for tests --- src/modules/displaymanager/tests/CMakeTests.txt | 3 +++ src/modules/displaymanager/tests/test-dm-greetd.py | 3 +++ src/modules/displaymanager/tests/test-dm-sddm.py | 3 +++ 3 files changed, 9 insertions(+) diff --git a/src/modules/displaymanager/tests/CMakeTests.txt b/src/modules/displaymanager/tests/CMakeTests.txt index 39c1291ca..70e3d580d 100644 --- a/src/modules/displaymanager/tests/CMakeTests.txt +++ b/src/modules/displaymanager/tests/CMakeTests.txt @@ -1,3 +1,6 @@ +# SPDX-FileCopyrightText: no +# SPDX-License-Identifier: CC0-1.0 +# # We have tests to load (some) of the DMs specifically, to test their # configuration code. Those tests conventionally live in Python # files here in the tests/ directory. Add them. diff --git a/src/modules/displaymanager/tests/test-dm-greetd.py b/src/modules/displaymanager/tests/test-dm-greetd.py index 6be0e3d47..d41c2dadf 100644 --- a/src/modules/displaymanager/tests/test-dm-greetd.py +++ b/src/modules/displaymanager/tests/test-dm-greetd.py @@ -1,3 +1,6 @@ +# SPDX-FileCopyrightText: no +# SPDX-License-Identifier: CC0-1.0 +# # Calamares Boilerplate import libcalamares libcalamares.globalstorage = libcalamares.GlobalStorage(None) diff --git a/src/modules/displaymanager/tests/test-dm-sddm.py b/src/modules/displaymanager/tests/test-dm-sddm.py index 5e4b54b30..b5c334948 100644 --- a/src/modules/displaymanager/tests/test-dm-sddm.py +++ b/src/modules/displaymanager/tests/test-dm-sddm.py @@ -1,3 +1,6 @@ +# SPDX-FileCopyrightText: no +# SPDX-License-Identifier: CC0-1.0 +# # Calamares Boilerplate import libcalamares libcalamares.globalstorage = libcalamares.GlobalStorage(None) From b00177bd65cc41e68f27b72e4b44e6bb4a6e12aa Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Fri, 19 Nov 2021 12:55:23 +0100 Subject: [PATCH 093/127] [zfs] SPDX tag the documentation --- src/modules/zfs/README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/modules/zfs/README.md b/src/modules/zfs/README.md index 666c6a5d9..9138a0598 100644 --- a/src/modules/zfs/README.md +++ b/src/modules/zfs/README.md @@ -1,5 +1,9 @@ ## zfs Module Notes + + There are a few considerations to be aware of when enabling the zfs module * You must provide zfs kernel modules or kernel support on the ISO for the zfs module to function * Support for zfs in the partition module is conditional on the zfs module being enabled From 6792eb5191e02084b1cd3f3b9c7f9a56ee005cf3 Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Tue, 16 Nov 2021 15:45:44 +0100 Subject: [PATCH 094/127] Changes: pre-release housekeeping --- CHANGES-3.2 | 2 +- CMakeLists.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGES-3.2 b/CHANGES-3.2 index 9c9a638d1..521ac52fd 100644 --- a/CHANGES-3.2 +++ b/CHANGES-3.2 @@ -7,7 +7,7 @@ contributors are listed. Note that Calamares does not have a historical changelog -- this log starts with version 3.2.0. The release notes on the website will have to do for older versions. -# 3.2.47 (unreleased) # +# 3.2.47 (2021-11-19) # This release contains contributions from (alphabetically by first name): - Evan James diff --git a/CMakeLists.txt b/CMakeLists.txt index fd9ec6eaa..747c9828e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -45,7 +45,7 @@ project( CALAMARES LANGUAGES C CXX ) -set( CALAMARES_VERSION_RC 1 ) # Set to 0 during release cycle, 1 during development +set( CALAMARES_VERSION_RC 0 ) # Set to 0 during release cycle, 1 during development ### OPTIONS # From 8652fc5f6d4ff8b3fee4197bf4a161d8ac527363 Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Tue, 16 Nov 2021 17:14:12 +0100 Subject: [PATCH 095/127] [zfs] Fix schema - typo (canmount vs canMount) - the canMount property is nominally a string, but YAML is 'special' and interprets 'on' and 'off' and 'yes' and 'no' and other strings as booleans unless quoted. --- src/modules/zfs/zfs.schema.yaml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/modules/zfs/zfs.schema.yaml b/src/modules/zfs/zfs.schema.yaml index fb83778ad..ddad6d77b 100644 --- a/src/modules/zfs/zfs.schema.yaml +++ b/src/modules/zfs/zfs.schema.yaml @@ -17,6 +17,8 @@ properties: properties: dsName: { type: string } mountpoint: { type: string } - canMount: { type: string } - required: [ dsName, mountpoint, canmount ] + # Nominally a string, but "on" and "off" are valid and get + # turned into a boolean in the YAML parser. + canMount: { anyOf: [ { type: string }, { type: boolean } ] } + required: [ dsName, mountpoint, canMount ] required: [ poolName, datasets ] From 8233be93cf6ae76dd15d9bcbf2d2dc216a9af2ed Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Tue, 16 Nov 2021 17:15:37 +0100 Subject: [PATCH 096/127] CI: fix permissions on scripts --- ci/configvalidator.py | 0 ci/txcheck.sh | 0 ci/txreduce.py | 0 3 files changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 ci/configvalidator.py mode change 100644 => 100755 ci/txcheck.sh mode change 100644 => 100755 ci/txreduce.py diff --git a/ci/configvalidator.py b/ci/configvalidator.py old mode 100644 new mode 100755 diff --git a/ci/txcheck.sh b/ci/txcheck.sh old mode 100644 new mode 100755 diff --git a/ci/txreduce.py b/ci/txreduce.py old mode 100644 new mode 100755 From b0d951d7e55289201125dd487b980348cc5e86c9 Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Tue, 16 Nov 2021 17:22:43 +0100 Subject: [PATCH 097/127] [grubcfg] Avoid UnboundLocal, always set zfs_root_path to something --- src/modules/grubcfg/main.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/modules/grubcfg/main.py b/src/modules/grubcfg/main.py index a4985d41f..bc459ac67 100644 --- a/src/modules/grubcfg/main.py +++ b/src/modules/grubcfg/main.py @@ -117,6 +117,8 @@ def modify_grub_default(partitions, root_mount_point, distributor): 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"): From 8ca6a7caef7b973b789d44bc8073dfab01b38fef Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Tue, 16 Nov 2021 17:30:28 +0100 Subject: [PATCH 098/127] [displaymanager] Fix tests (don't overwrite developer host configuration) --- src/modules/displaymanager/tests/1.global | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/displaymanager/tests/1.global b/src/modules/displaymanager/tests/1.global index 9b3f0ea7c..ee06ccfe1 100644 --- a/src/modules/displaymanager/tests/1.global +++ b/src/modules/displaymanager/tests/1.global @@ -1,3 +1,3 @@ # SPDX-FileCopyrightText: no # SPDX-License-Identifier: CC0-1.0 -rootMountPoint: / +rootMountPoint: /tmp From 855391b31d1a6a48f97f6402750a2e73ae37fb49 Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Fri, 19 Nov 2021 14:55:29 +0100 Subject: [PATCH 099/127] Changes: post-release housekeeping --- CHANGES-3.2 | 12 ++++++++++++ CMakeLists.txt | 4 ++-- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/CHANGES-3.2 b/CHANGES-3.2 index 521ac52fd..6cf5a99ba 100644 --- a/CHANGES-3.2 +++ b/CHANGES-3.2 @@ -7,6 +7,18 @@ contributors are listed. Note that Calamares does not have a historical changelog -- this log starts with version 3.2.0. The release notes on the website will have to do for older versions. +# 3.2.48 (unreleased) # + +This release contains contributions from (alphabetically by first name): + - No external contributors yet + +## Core ## + - No core changes yet + +## Modules ## + - No module changes yet + + # 3.2.47 (2021-11-19) # This release contains contributions from (alphabetically by first name): diff --git a/CMakeLists.txt b/CMakeLists.txt index 747c9828e..296c88e74 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -41,11 +41,11 @@ # TODO:3.3: Require CMake 3.12 cmake_minimum_required( VERSION 3.3 FATAL_ERROR ) project( CALAMARES - VERSION 3.2.47 + VERSION 3.2.48 LANGUAGES C CXX ) -set( CALAMARES_VERSION_RC 0 ) # Set to 0 during release cycle, 1 during development +set( CALAMARES_VERSION_RC 1 ) # Set to 0 during release cycle, 1 during development ### OPTIONS # From f37a77597798bfea39853bbb3812d629412e381d Mon Sep 17 00:00:00 2001 From: dalto Date: Fri, 19 Nov 2021 10:46:23 -0600 Subject: [PATCH 100/127] [zfs] Update module documentation --- src/modules/zfs/README.md | 3 +++ src/modules/zfs/zfs.conf | 7 +++++++ 2 files changed, 10 insertions(+) diff --git a/src/modules/zfs/README.md b/src/modules/zfs/README.md index 9138a0598..1d64e3a89 100644 --- a/src/modules/zfs/README.md +++ b/src/modules/zfs/README.md @@ -6,7 +6,10 @@ There are a few considerations to be aware of when enabling the zfs module * You must provide zfs kernel modules or kernel support on the ISO for the zfs module to function + * The zfs kernel module must be loaded prior to the partition module running + * One way to acheive this is by running `modprobe zfs` * Support for zfs in the partition module is conditional on the zfs module being enabled +* The config for the default pools and datasets is configured and described in modules/zfs.conf * If you use grub with zfs, you must have `ZPOOL_VDEV_NAME_PATH=1` in your environment when running grub-install or grub-mkconfig. * Calamares will ensure this happens during the bootloader module. * It will also add it to `/etc/environment` so it will be available in the installation diff --git a/src/modules/zfs/zfs.conf b/src/modules/zfs/zfs.conf index f2f8f52b0..e5a0aa348 100644 --- a/src/modules/zfs/zfs.conf +++ b/src/modules/zfs/zfs.conf @@ -10,6 +10,9 @@ poolName: zpcala # A list of options that will be passed to zpool create +# +# Encryption options should generally not be added here since they will be added by +# selecting the encrypt disk option in the partition module poolOptions: "-f -o ashift=12 -O mountpoint=none -O acltype=posixacl -O relatime=on" # A list of options that will be passed to zfs create when creating each dataset @@ -17,6 +20,10 @@ poolOptions: "-f -o ashift=12 -O mountpoint=none -O acltype=posixacl -O relatime datasetOptions: "-o compression=lz4 -o atime=off -o xattr=sa" # An array of datasets that will be created on the zpool mounted at / +# +# This default configuration is commonly used when support for booting more than one distro +# out of a single zpool is desired. If you decide to keep this default configuration, +# you should replace "distro" with an identifier that represents your distro. datasets: - dsName: ROOT mountpoint: none From ee032b43fec3383dc6d18ceeb8602a716028fa14 Mon Sep 17 00:00:00 2001 From: dalto Date: Fri, 19 Nov 2021 14:24:36 -0600 Subject: [PATCH 101/127] [zfs] Fix spelling error in readme --- src/modules/zfs/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/zfs/README.md b/src/modules/zfs/README.md index 1d64e3a89..992fa5cb3 100644 --- a/src/modules/zfs/README.md +++ b/src/modules/zfs/README.md @@ -7,7 +7,7 @@ There are a few considerations to be aware of when enabling the zfs module * You must provide zfs kernel modules or kernel support on the ISO for the zfs module to function * The zfs kernel module must be loaded prior to the partition module running - * One way to acheive this is by running `modprobe zfs` + * One way to achieve this is by running `modprobe zfs` * Support for zfs in the partition module is conditional on the zfs module being enabled * The config for the default pools and datasets is configured and described in modules/zfs.conf * If you use grub with zfs, you must have `ZPOOL_VDEV_NAME_PATH=1` in your environment when running grub-install or grub-mkconfig. From f947c072e189cc5269a8fc54054f4a405f9003f5 Mon Sep 17 00:00:00 2001 From: Calamares CI Date: Fri, 19 Nov 2021 23:13:58 +0100 Subject: [PATCH 102/127] i18n: [calamares] Automatic merge of Transifex translations --- lang/calamares_fa.ts | 513 ++++++++++++++++++++++--------------------- 1 file changed, 268 insertions(+), 245 deletions(-) diff --git a/lang/calamares_fa.ts b/lang/calamares_fa.ts index 8f474fd2d..88e516fc8 100644 --- a/lang/calamares_fa.ts +++ b/lang/calamares_fa.ts @@ -6,7 +6,7 @@ Manage auto-mount settings - + مدیریت تنظیمات سوارشدن-خودکار
@@ -104,22 +104,22 @@ Crashes Calamares, so that Dr. Konqui can look at it. - + کلامارس کرش میکنه، تا Dr. Konqui بتونه بهش یک نگاهی بندازه. Reloads the stylesheet from the branding directory. - + استایل های مسیر branding را بارگیری مجدد می‌کند. Uploads the session log to the configured pastebin. - + گزارش نشست را به pastebin تنظیم شده بارگذاری میکند. Send Session Log - + ارسال گزارش نشست @@ -129,7 +129,7 @@ Displays the tree of widget names in the log (for stylesheet debugging). - + نمایش درخت نام های ویجت ها در گزارش (برای دیباگ استایل ها). @@ -336,7 +336,11 @@ %1 Link copied to clipboard - + گزارش نصب به پیوند زیر پست شد + +%1 + +پیوند در کلیپ برد رونگاری شد @@ -504,17 +508,17 @@ The installer will quit and all changes will be lost. Set filesystem label on %1. - + تنظیم برچسب سامانه پرونده روی %1. Set filesystem label <strong>%1</strong> to partition <strong>%2</strong>. - + تنظیم عنوان سامانه پرونده <strong>%1</strong> به افراز <strong>%2</strong>. The installer failed to update partition table on disk '%1'. - + نصب کننده برای بروز کردن جدول افراز روی دیسک '%1' شکست خورد. @@ -642,17 +646,17 @@ The installer will quit and all changes will be lost. This storage device already has an operating system on it, but the partition table <strong>%1</strong> is different from the needed <strong>%2</strong>.<br/> - + این دستگاه حافظه هم اکنون یک سیستم عامل روی خود دارد، اما جدول افراز <strong>%1</strong> با نیاز <strong>%2</strong> متفاوت است. This storage device has one of its partitions <strong>mounted</strong>. - + این دستگاه حافظه دارای یک افرازی بوده که هم اکنون <strong>سوارشده</strong> است. This storage device is a part of an <strong>inactive RAID</strong> device. - + یکی از بخش های این دستگاه حافظه عضوی از دستگاه <strong>RAID غیرفعال</strong> است. @@ -685,27 +689,27 @@ The installer will quit and all changes will be lost. Successfully unmounted %1. - + %1 باموفقیت جدا شد. Successfully disabled swap %1. - + سوآپ %1 باموفقیت غیرفعال شد. Successfully cleared swap %1. - + سوآپ %1 باموفقیت پاک شد. Successfully closed mapper device %1. - + دستگاه مپر %1 باموفقیت بسته شد. Successfully disabled volume group %1. - + گروه حجمی %1 باموفقیت غیرفعال شد. @@ -805,12 +809,12 @@ The installer will quit and all changes will be lost. Network Installation. (Disabled: Internal error) - + نصب شبکه‌ای. (از کار افتاده: خطای داخلی) Network Installation. (Disabled: No package list) - + نصب شبکه ای. (از کار افتاده: بدون فهرست بسته) @@ -875,7 +879,7 @@ The installer will quit and all changes will be lost. '%1' is not allowed as username. - + '%1' بعنوان نام کاربر مجاز نیست. @@ -900,7 +904,7 @@ The installer will quit and all changes will be lost. '%1' is not allowed as hostname. - + '%1' بعنوان نام میزبان مجاز نیست. @@ -915,7 +919,7 @@ The installer will quit and all changes will be lost. OK! - + باشه! @@ -930,12 +934,12 @@ The installer will quit and all changes will be lost. The setup of %1 did not complete successfully. - + برپایی %1 با موفقیت کامل نشد. The installation of %1 did not complete successfully. - + نصب %1 با موفقیت کامل نشد. @@ -970,12 +974,12 @@ The installer will quit and all changes will be lost. Install option: <strong>%1</strong> - + گزینه نصب: <strong>%1</strong> None - + هیچ کدام @@ -985,12 +989,12 @@ The installer will quit and all changes will be lost. This is an overview of what will happen once you start the setup procedure. - + این یک بررسی از مواردی که بعد از اینکه برپایی را شروع کنید، انجام می شوند است. This is an overview of what will happen once you start the install procedure. - + این یک بررسی از مواردی که بعد از اینکه نصب را شروع کنید، انجام می شوند است. @@ -998,7 +1002,7 @@ The installer will quit and all changes will be lost. Contextual Processes Job - + پردازه های متنی @@ -1056,12 +1060,12 @@ The installer will quit and all changes will be lost. Label for the filesystem - + برچسب برای سامانه پرونده FS Label: - + برچسب سامانه پرونده: @@ -1094,12 +1098,12 @@ The installer will quit and all changes will be lost. Create new %1MiB partition on %3 (%2) with entries %4. - + ایجاد افراز %1 می‌ب جدید روی %3 (%2) با ورودی های %4. Create new %1MiB partition on %3 (%2). - + ایجاد افراز %1 می‌ب جدید روی %3 (%2). @@ -1109,12 +1113,12 @@ The installer will quit and all changes will be lost. Create new <strong>%1MiB</strong> partition on <strong>%3</strong> (%2) with entries <em>%4</em>. - + ایجاد افراز <strong>%1 می‌ب</strong> جدید روی <strong>%3</strong> (%2) با ورودی های <em>%4</em>. Create new <strong>%1MiB</strong> partition on <strong>%3</strong> (%2). - + ایجاد افراز <strong>%1</strong> می‌ب جدید روی <strong>%3</strong> (%2). @@ -1130,7 +1134,7 @@ The installer will quit and all changes will be lost. The installer failed to create partition on disk '%1'. - + نصب کننده برای ساختن افراز روی دیسک '%1' شکست خورد. @@ -1181,7 +1185,7 @@ The installer will quit and all changes will be lost. The installer failed to create a partition table on %1. - + نصب کننده برای ساختن جدول افراز روی %1 شکست خورد. @@ -1199,23 +1203,23 @@ The installer will quit and all changes will be lost. Preserving home directory - + حفظ مسیر خانگی Creating user %1 - + درحال ایجاد کاربر %1 Configuring user %1 - + درحال تنظیم کاربر %1 Setting file permissions - + درحال تنظیم مجوزهای پرونده @@ -1246,7 +1250,7 @@ The installer will quit and all changes will be lost. The installer failed to create a volume group named '%1'. - + نصب کننده برای ساخت گروه حجمی با نام '%1' شکست خورد. @@ -1265,7 +1269,7 @@ The installer will quit and all changes will be lost. The installer failed to deactivate a volume group named %1. - + نصب کننده برای ازکارانداختن گروه حجمی با نام '%1' شکست خورد. @@ -1288,7 +1292,7 @@ The installer will quit and all changes will be lost. The installer failed to delete partition %1. - + نصب کننده برای حذف افراز %1 شکست خورد. @@ -1349,7 +1353,7 @@ The installer will quit and all changes will be lost. Skip writing LUKS configuration for Dracut: "/" partition is not encrypted - + ردشدن از نوشتن تنظیمات LUKS برای Dracut: افراز "/" رمزگذاری نشده است @@ -1420,12 +1424,12 @@ The installer will quit and all changes will be lost. Label for the filesystem - + برچسب برای سامانه پرونده FS Label: - + برچسب سامانه پرونده: @@ -1472,47 +1476,47 @@ The installer will quit and all changes will be lost. Install %1 on <strong>new</strong> %2 system partition with features <em>%3</em> - + نصب %1 روی سامانه افراز %2 <strong>جدید</strong> با امکانات <em>%3</em>. Install %1 on <strong>new</strong> %2 system partition. - + نصب %1 روی سامانه افراز %2 <strong>جدید</strong>. Set up <strong>new</strong> %2 partition with mount point <strong>%1</strong> and features <em>%3</em>. - + برپایی افراز <strong>جدید</strong> %2 با نقطه سوارشدن <strong>%1</strong> و امکانات <em>%3</em>. Set up <strong>new</strong> %2 partition with mount point <strong>%1</strong>%3. - + برپایی افراز <strong>جدید</strong> %2 با نقطه سوارشدن <strong>%1</strong> %3. Install %2 on %3 system partition <strong>%1</strong> with features <em>%4</em>. - + نصب %2 روی <strong>%1</strong> سامانه افراز %3 با امکانات <em>%4</em>. Set up %3 partition <strong>%1</strong> with mount point <strong>%2</strong> and features <em>%4</em>. - + برپایی %3 افراز <strong>%1</strong> با نقطه سوارشدن <strong>%2</strong> و امکانات <em>%4</em>. Set up %3 partition <strong>%1</strong> with mount point <strong>%2</strong>%4. - + برپایی %3 افراز <strong>%1</strong> با نقطه سوارشدن <strong>%2</strong> %4. Install %2 on %3 system partition <strong>%1</strong>. - + نصب %2 روی <strong>%1</strong> سامانه افراز %3. Install boot loader on <strong>%1</strong>. - + نصب بوت لودر روی <strong>%1</strong>. @@ -1540,7 +1544,7 @@ The installer will quit and all changes will be lost. <html><head/><body><p>When this box is checked, your system will restart immediately when you click on <span style="font-style:italic;">Done</span> or close the setup program.</p></body></html> - هنگامی که این کادر علامت گذاری شد ، هنگامی که بر روی انجام شده کلیک کنید یا برنامه نصب را ببندید ، سیستم شما بلافاصله راه اندازی می شود. + هنگامی که این کادر علامت گذاری شد ، هنگامی که بر روی انجام شده کلیک کنید یا برنامه برپاکننده را ببندید ، سیستم شما بلافاصله راه اندازی می شود. @@ -1555,12 +1559,12 @@ The installer will quit and all changes will be lost. <h1>Setup Failed</h1><br/>%1 has not been set up on your computer.<br/>The error message was: %2. - + <h1>برپایی شکست خورد</h1><br/>%1 روی رایانه شما برپا نشد.<br/>پیام خطا: %2. <h1>Installation Failed</h1><br/>%1 has not been installed on your computer.<br/>The error message was: %2. - + <h1>نصب شکست خورد</h1><br/>%1 روی رایانه شما نصب نشد.<br/>پیام خطا: %2. @@ -1584,22 +1588,22 @@ The installer will quit and all changes will be lost. Format partition %1 (file system: %2, size: %3 MiB) on %4. - + فرمت افراز %1 (سامانه پروانه: %2، اندازه: %3مبی‌بایت) روی %4. Format <strong>%3MiB</strong> partition <strong>%1</strong> with file system <strong>%2</strong>. - + فرمت افراز<strong>%1</strong> با سایز <strong>%3مبی‌بایت</strong> با سامانه پرونده <strong>%2</strong>. Formatting partition %1 with file system %2. - + فرمت افراز %1 با سامانه پروند %2. The installer failed to format partition %1 on disk '%2'. - + نصب کننده برای فرمت افراز %1 روی دیسک '%2' شکست خورد. @@ -1607,22 +1611,22 @@ The installer will quit and all changes will be lost. has at least %1 GiB available drive space - + دارای حداقل %1 گی‌ب فضای کافی There is not enough drive space. At least %1 GiB is required. - + فضای کافی موجود نیست. حداقل %1 گی‌ب نیاز است. has at least %1 GiB working memory - + دارای حداقل %1 گی‌ب مموری کارکننده The system does not have enough working memory. At least %1 GiB is required. - + سامانه مموری کارکننده کافی ندارد. حداقل %1 گی‌ب نیاز است. @@ -1691,7 +1695,7 @@ The installer will quit and all changes will be lost. OEM Batch Identifier - + شناسه Batch اوئی‌ام @@ -1858,27 +1862,27 @@ The installer will quit and all changes will be lost. <strong>%1 graphics driver</strong><br/><font color="Grey">by %2</font> %1 is usually a vendor name, example: Nvidia graphics driver - + <strong>درایور گرافیک %1</strong><br/><font color="Grey">توسط %2</font> <strong>%1 browser plugin</strong><br/><font color="Grey">by %2</font> - + <strong>افزونه مرورگر %1</strong><br/><font color="Grey">توسط %2</font> <strong>%1 codec</strong><br/><font color="Grey">by %2</font> - + <strong>کدک %1</strong><br/><font color="Grey">توسط %2</font> <strong>%1 package</strong><br/><font color="Grey">by %2</font> - + <strong>بسته %1</strong><br/><font color="Grey">توسط %2</font> <strong>%1</strong><br/><font color="Grey">by %2</font> - + <strong>%1</strong><br/><font color="Grey">توسط %2</font> @@ -1933,7 +1937,7 @@ The installer will quit and all changes will be lost. Quit - + خروج @@ -1967,17 +1971,17 @@ The installer will quit and all changes will be lost. Root partition %1 is LUKS but no passphrase has been set. - + افراز روت %1 یک LUKS است، ولی هیچ گذرواژه ای تنظیم نشده است. Could not create LUKS key file for root partition %1. - + نمیتوان پرونده کلید LUKS را برای افراز روت %1 ایجاد کرد. Could not configure LUKS key file on partition %1. - + نمیتوان پرونده کلید LUKS را برای افراز روت %1 تنظیم کرد. @@ -2003,7 +2007,7 @@ The installer will quit and all changes will be lost. Timezone: %1 - + منطقه زمانی: %1 @@ -2124,17 +2128,17 @@ The installer will quit and all changes will be lost. Ba&tch: - + Ba&tch: <html><head/><body><p>Enter a batch-identifier here. This will be stored in the target system.</p></body></html> - + <html><head/><body><p>اینجا یک شناسه batch وارد کنید. این مقدار در سامانه هدف ذخیره می‌شود.</p></body></html> <html><head/><body><h1>OEM Configuration</h1><p>Calamares will use OEM settings while configuring the target system.</p></body></html> - + <html><head/><body><h1>تنظیمات اوئی‌ام</h1><p>کلامارس از تنظیمات اوئی‌ام هنگام تنظیم سامانه هدف استفده خواهد کرد.</p></body></html> @@ -2147,7 +2151,7 @@ The installer will quit and all changes will be lost. Set the OEM Batch Identifier to <code>%1</code>. - + تنظیم شناسه Batch اوئی‌ام به <code>%1</code>. @@ -2155,14 +2159,14 @@ The installer will quit and all changes will be lost. Select your preferred Region, or use the default settings. - + منطقه موردنظر خود را انتخاب کنید یا از تنظیمات پیشفرض استفاده کنید. Timezone: %1 - + منطقه زمانی: %1 @@ -2172,12 +2176,12 @@ The installer will quit and all changes will be lost. Zones - + مناطق You can fine-tune Language and Locale settings below. - + شما میتوانید زبان و زبان محلی را در تنظیمات زیر بطوردقیق تنظیم کنید. @@ -2255,9 +2259,9 @@ The installer will quit and all changes will be lost. The password contains fewer than %n lowercase letters - - - + + گذرواژه حاوی کمتر از %n حرف کوچک است + گذرواژه حاوی کمتر از %n حرف کوچک است @@ -2293,70 +2297,70 @@ The installer will quit and all changes will be lost. The password contains fewer than %n digits - - - + + گذرواژه حاوی کمتر از %n عدد است + گذرواژه حاوی کمتر از %n عدد است The password contains fewer than %n uppercase letters - - - + + گذرواژه حاوی کمتر از %n حرف بزرگ است + گذرواژه حاوی کمتر از %n حرف بزرگ است The password contains fewer than %n non-alphanumeric characters - - - + + گذرواژه حاوی کمتر از %n نویسه غیرالفبا است + گذرواژه حاوی کمتر از %n نویسه غیرالفبا است The password is shorter than %n characters - - - + + گذرواژه کوتاه تر از %n نویسه است + گذرواژه کوتاه تر از %n نویسه است The password is a rotated version of the previous one - + گذرواژه یک نسخه برعکس شده از قبلی است The password contains fewer than %n character classes - - - + + گذرواژه حاوی کمتر از %n کلاس نویسه است + گذرواژه حاوی کمتر از %n کلاس نویسه است The password contains more than %n same characters consecutively - - - + + گذرواژه حاوی بیش از %n نویسه پی در پی است + گذرواژه حاوی بیش از %n نویسه پی در پی است The password contains more than %n characters of the same class consecutively - - - + + گذرواژه حاوی بیش از%n نویسه پی در پی از همان کلاس است + گذرواژه حاوی بیش از%n نویسه پی در پی از همان کلاس است The password contains monotonic sequence longer than %n characters - - - + + گذرواژه حاوی یک توالی کاراکتر یکنواخت بیش از %n نویسه است + گذرواژه حاوی یک توالی کاراکتر یکنواخت بیش از %n نویسه است @@ -2382,7 +2386,7 @@ The installer will quit and all changes will be lost. The password fails the dictionary check - %1 - + گذرواژه در بررسی فرهنگ لفت ناموفق است - %1 @@ -2703,7 +2707,7 @@ The installer will quit and all changes will be lost. File System Label - + برچسب سامانه پرونده @@ -2824,37 +2828,37 @@ The installer will quit and all changes will be lost. EFI system partition configured incorrectly - + افراز سامانه EFI به نادرستی تنظیم شده است An EFI system partition is necessary to start %1.<br/><br/>To configure an EFI system partition, go back and select or create a suitable filesystem. - + یک افراز سامانه EFI نیازمندست که از %1 شروع شود.<br/><br/>برای تنظیم یک افراز سامانه EFI، به عقب بازگشته و یک سامانه پرونده مناسب انتخاب یا ایجاد کنید. The filesystem must be mounted on <strong>%1</strong>. - + سامانه پرونده باید روی <strong>%1</strong> سوارشده باشد. The filesystem must have type FAT32. - + سامانه پرونده باید دارای نوع FAT32 باشد. The filesystem must be at least %1 MiB in size. - + سامانه پرونده حداقل باید دارای %1مبی‌بایت حجم باشد. The filesystem must have flag <strong>%1</strong> set. - + سامانه پرونده باید پرچم <strong>%1</strong> را دارا باشد. You can continue without setting up an EFI system partition but your system may fail to start. - + شما میتوانید بدون برپاکردن افراز سامانه EFI ادامه دهید ولی ممکن است سامانه برای شروع با مشکل مواجه شود. @@ -2968,7 +2972,7 @@ Output: Command <i>%1</i> crashed. - + دستور <i>%1</i> شکست خورد. @@ -2978,7 +2982,7 @@ Output: Command <i>%1</i> failed to start. - + دستور <i>%1</i> برای شروع شکست خورد. @@ -2998,7 +3002,7 @@ Output: Command <i>%1</i> failed to finish in %2 seconds. - + دستور <i>%1</i> برای اتمام در %2 ثانیه شکست خورد. @@ -3008,7 +3012,7 @@ Output: Command <i>%1</i> finished with exit code %2. - + دستور <i>%1</i> با کد خروج %2 به پایان رسید. @@ -3055,18 +3059,18 @@ Output: Path <pre>%1</pre> must be an absolute path. - + مسیر <pre>%1</pre> باید یک مسیر مطلق باشد. Directory not found - + مسیر یافت نشد Could not create new random file <pre>%1</pre>. - + نمی توان پرونده تصادفی <pre>%1</pre> را ساخت. @@ -3095,7 +3099,8 @@ Output: <p>This computer does not satisfy some of the recommended requirements for setting up %1.<br/> Setup can continue, but some features might be disabled.</p> - + <p>رایانه شما نیازمندی‌های برپاسازی %1 را ندارد. +برپاسازی می‌تواند ادامه یابد، ولی ممکن است برخی ویژگی‌ها از کار افتاده باشند.</p> @@ -3112,17 +3117,17 @@ Output: Remove Volume Group named %1. - + حذف گروه حجمی با نام %1.
Remove Volume Group named <strong>%1</strong>. - + حذف گروه حجمی با نام <strong>%1</strong>. The installer failed to remove a volume group named '%1'. - + نصب کننده برای حذف گروه حجمی با نام '%1' شکست خورد. @@ -3135,59 +3140,59 @@ Output: Select where to install %1.<br/><font color="red">Warning: </font>this will delete all files on the selected partition. - + انتخاب کنید که کجا %1 نصب شود.<br/><font color="red">اخطار:</font>این همه پرونده های افراز انتخاب شده را پاک خواهد کرد. The selected item does not appear to be a valid partition. - + بنظر نمی آید که گزینه انتخاب شده، افراز معتبری باشد. %1 cannot be installed on empty space. Please select an existing partition. - + %1 نمیتواند روی فضای خالی نصب شود. لطفا یک افراز موجود را برگزینید. %1 cannot be installed on an extended partition. Please select an existing primary or logical partition. - + %1 نمیتواند روی یک افراز extended نصب شود. لطفا یک افراز primary یا logical برگزینید. %1 cannot be installed on this partition. - + %1 نمیتواند روی این افراز نصب شود. Data partition (%1) - + داده افراز (%1) Unknown system partition (%1) - + سامانه افراز ناشناس (%1) %1 system partition (%2) - + سامانه افراز %1 (%2) <strong>%4</strong><br/><br/>The partition %1 is too small for %2. Please select a partition with capacity at least %3 GiB. - + <strong>%4</strong><br/><br/>افراز %1 برای %2 بسیار کوچک است. لطفا یک افراز با ظرفیت حداقل %3 گیبی‌بایت انتخاب کنید. <strong>%2</strong><br/><br/>An EFI system partition cannot be found anywhere on this system. Please go back and use manual partitioning to set up %1. - + <strong>%2</strong><br/><br/>افراز سامانه EFI نمی‌تواند در هیچ جایی از این سیستم یافت شود. لطفا برگردید و از پارتیشن بندی دستی استفاده کنید تا %1 را راه‌اندازی کنید. <strong>%3</strong><br/><br/>%1 will be installed on %2.<br/><font color="red">Warning: </font>all data on partition %2 will be lost. - + <strong>%3</strong><br/><br/>%1 روی %2 نصب خواهد شد.<br/><font color="red">اخطار:</font>همه داده های افراز %2 از دست خواهند رفت. @@ -3206,13 +3211,15 @@ Output: <p>This computer does not satisfy the minimum requirements for installing %1.<br/> Installation cannot continue.</p> - + <p>رایانه شما نیازمندی های نصب %1 را ندارد.<br/> +نصب نمیتواند ادامه یابد.</p> <p>This computer does not satisfy some of the recommended requirements for setting up %1.<br/> Setup can continue, but some features might be disabled.</p> - + <p>رایانه شما نیازمندی‌های برپاسازی %1 را ندارد. +برپاسازی می‌تواند ادامه یابد، ولی ممکن است برخی ویژگی‌ها از کار افتاده باشند.</p> @@ -3240,7 +3247,7 @@ Output: Calamares cannot start KPMCore for the file-system resize job. - + کلامارس نمیتواند KPMCore را برای کار تغییراندازه فایل سیستم شروع کند. @@ -3254,24 +3261,24 @@ Output: The filesystem %1 could not be found in this system, and cannot be resized. - + فایل سیستم %1 روی این سامانه یافت نشد و نمیتواند تغییر اندازه دهد. The device %1 could not be found in this system, and cannot be resized. - + دستگاه %1 روی این سامانه یافت نشد و نمیتواند تغییراندازه دهد. The filesystem %1 cannot be resized. - + سیستم فایل %1 نمی تواند تغییر اندازه دهد. The device %1 cannot be resized. - + دستگاه %1 نمی تواند تغییر اندازه دهد. @@ -3281,7 +3288,7 @@ Output: The device %1 must be resized, but cannot - + دستگاه %1 باید تغییر اندازه دهد، اما نمی تواند. @@ -3294,17 +3301,17 @@ Output: Resize <strong>%2MiB</strong> partition <strong>%1</strong> to <strong>%3MiB</strong>. - + تغییر اندازه افراز <strong>%1</strong> از <strong>%2مبی‌بایت</strong> به <strong>%3مبی‌بایت</strong>. Resizing %2MiB partition %1 to %3MiB. - + درحال تغییر اندازه افراز %1 از %2مبی‌بایت به %3مبی‌بایت. The installer failed to resize partition %1 on disk '%2'. - + نصب کننده برای تغییر اندازه افراز %1 روی دیسک '%2' شکست خورد. @@ -3321,17 +3328,17 @@ Output: Resize volume group named %1 from %2 to %3. - + تغییر اندازه گروه حجمی با نام %1 از %2 به %3.
Resize volume group named <strong>%1</strong> from <strong>%2</strong> to <strong>%3</strong>. - + تغییر اندازه گروه حجمی با نام <strong>%1</strong> از <strong>%2</strong> به <strong>%3</strong>. The installer failed to resize a volume group named '%1'. - + نصب کننده برای تغییر اندازه گروه حجمی با نام '%1' شکست خورد. @@ -3387,7 +3394,7 @@ Output: Cannot write hostname to target system - + عدم توانایی نوشتن نام میزبان به سامانه هدف @@ -3395,29 +3402,29 @@ Output: Set keyboard model to %1, layout to %2-%3 - + تنظیم مدل کیبورد به %1، چیدمان به %2-%3 Failed to write keyboard configuration for the virtual console. - + شکست در نوشتن تنظیمات کیبورد برای کنسول مجازی. Failed to write to %1 - + شکست در نوشتن %1 Failed to write keyboard configuration for X11. - + شکست در نوشتن تنظیمات کیبورد برای X11. Failed to write keyboard configuration to existing /etc/default directory. - + شکست در نوشتن تنظیمات کیبورد به مسیر /etc/default موجود. @@ -3425,27 +3432,27 @@ Output: Set flags on partition %1. - + تنظیم پرچم ها روی افراز %1. Set flags on %1MiB %2 partition. - + تنظیم پرچم ها روی افراز %2 با حجم %1مبی‌بایت. Set flags on new partition. - + تنظیم پرچم ها روی افراز جدید. Clear flags on partition <strong>%1</strong>. - + پاک کردن پرچم ها از افراز <strong>%1</strong>. Clear flags on %1MiB <strong>%2</strong> partition. - + پاک کردن پرچم ها از افراز <strong>%2</strong> با حجم %1مبی‌بایت. @@ -3455,27 +3462,27 @@ Output: Flag partition <strong>%1</strong> as <strong>%2</strong>. - + پرچم گذاری افراز <strong>%1</strong> بعنوان <strong>%2</strong>. Flag %1MiB <strong>%2</strong> partition as <strong>%3</strong>. - + پرچم گذاری افراز <strong>%2</strong> بعنوان <strong>%3</strong> با حجم %1 مبی‌بایت. Flag new partition as <strong>%1</strong>. - + درحال پرچم گذاری افراز جدید بعنوان <strong>%1</strong>. Clearing flags on partition <strong>%1</strong>. - + درحال پاک کردن پرچم ها از افراز <strong>%1</strong>. Clearing flags on %1MiB <strong>%2</strong> partition. - + درحال پاک کردن پرچم ها از افراز <strong>%2</strong> با حجم %1مبی‌بایت. @@ -3485,22 +3492,22 @@ Output: Setting flags <strong>%2</strong> on partition <strong>%1</strong>. - + درحال تنظیم پرچم های <strong>%2</strong> روی افراز <strong>%1</strong>. Setting flags <strong>%3</strong> on %1MiB <strong>%2</strong> partition. - + درحال تنظیم پرچم های <strong>%3</strong> روی افراز <strong>%2</strong> با حجم %1مبی‌بایت. Setting flags <strong>%1</strong> on new partition. - + درحال تنظیم پرچم های <strong>%1</strong> روی افراز جدید. The installer failed to set flags on partition %1. - + نصب کننده برای تنظیم پرچم ها روی افراز %1 شکست خورد. @@ -3513,12 +3520,12 @@ Output: Setting password for user %1. - + درحال تنظیم گذرواژه برای کاربر %1. Bad destination system path. - + مسیر مقصد سامانه بد است. @@ -3533,7 +3540,7 @@ Output: passwd terminated with error code %1. - + passwd با خطای %1 پایان یافت. @@ -3543,7 +3550,7 @@ Output: usermod terminated with error code %1. - + usermod با خطای %1 پایان یافت. @@ -3571,17 +3578,17 @@ Output: Link creation failed, target: %1; link name: %2 - + ساختن پیوند با خطا مواجه شد، هدف: %1؛ پیوند: %2 Cannot set timezone, - + نمی‌توان منطقه زمانی را تنظیم کرد، Cannot open /etc/timezone for writing - + عدم توانایی در باز کردن /etc/timezone برای نوشتن @@ -3589,18 +3596,18 @@ Output: Preparing groups. - + درحال آماده سازی گروه ها. Could not create groups in target system - + عدم توانایی در ساخت گروه ها در سامانه هدف These groups are missing in the target system: %1 - + این گروه ها در سامانه هدف یافت نشدند: %1 @@ -3608,7 +3615,7 @@ Output: Configure <pre>sudo</pre> users. - + کاربران با دسترسی <pre>sudo</pre> را تنظیم کنید. @@ -3626,7 +3633,7 @@ Output: Shell Processes Job - + پردازه های شل @@ -3635,7 +3642,7 @@ Output: %L1 / %L2 slide counter, %1 of %2 (numeric) - + %L1 از %L2 @@ -3681,7 +3688,7 @@ Output: Internal error in install-tracking. - + خطای داخلی در پیگیری نصب رخ داد. @@ -3694,28 +3701,28 @@ Output: KDE user feedback - + بازخورد کاربری KDE Configuring KDE user feedback. - + در حال تنظیم بازخورد کاربری KDE. Error in KDE user feedback configuration. - + خطایی در تنظیمات بازخورد کاربری KDE رخ داد. Could not configure KDE user feedback correctly, script error %1. - + عدم توانایی در تنظیم درست بازخورد کاربری KDE، برنامه با خطای %1 مواجه شد. Could not configure KDE user feedback correctly, Calamares error %1. - + عدم توانایی در تنظیم درست بازخورد کاربری KDE، کلامارس با خطای %1 مواجه شد. @@ -3723,28 +3730,28 @@ Output: Machine feedback - + بازخورد ماشین Configuring machine feedback. - + در حال تنظیم بازخورد ماشین. Error in machine feedback configuration. - + خطایی در تنظیمات بازخورد ماشین رخ داد. Could not configure machine feedback correctly, script error %1. - + عدم توانایی در تنظیم درست بازخورد ماشین، برنامه با خطای %1 مواجه شد. Could not configure machine feedback correctly, Calamares error %1. - + عدم توانایی در تنظیم درست بازخورد ماشین، کلامارس با خطای %1 مواجه شد. @@ -3762,12 +3769,12 @@ Output: <html><head/><body><p>Click here to send <span style=" font-weight:600;">no information at all</span> about your installation.</p></body></html> - + <html><head/><body><p> این گزینه را برای <span style=" font-weight:600;">نفرستادن هیچگونه اطلاعاتی</span> درباره نصب خودتان انتخاب کنید.</p></body></html> <html><head/><body><p><a href="placeholder"><span style=" text-decoration: underline; color:#2980b9;">Click here for more information about user feedback</span></a></p></body></html> - + <html><head/><body><p><a href="placeholder"><span style=" text-decoration: underline; color:#2980b9;">برای دریافت اطلاعات بیشتر درمورد بازخورد کاربران اینجا کلیک کنید</span></a></p></body></html> @@ -3777,17 +3784,17 @@ Output: By selecting this you will send information about your installation and hardware. This information will only be sent <b>once</b> after the installation finishes. - + با انتخاب این گزینه، شما فقط یکبار داده هایی درباره نصب و سخت افزار خود ارسال می کنید. این داده ها فقط <b>یکبار</b> بعد از به پایان رسیدن نصب فرستاده می شوند. By selecting this you will periodically send information about your <b>machine</b> installation, hardware and applications, to %1. - + با انتخاب این گزینه، شما بطور دوره ای داده هایی درباره نصب <b>ماشین</b>، سخت افزار و برنامه ها را به %1 می فرستید. By selecting this you will regularly send information about your <b>user</b> installation, hardware, applications and application usage patterns, to %1. - + با انتخاب این گزینه، شما بطور منظم داده هایی درباره نصب <b>کاربر</b>، سخت افزار، برنامه ها و الگوی مصرفی برنامه ها را به %1 می فرستید. @@ -3803,12 +3810,12 @@ Output: <small>If more than one person will use this computer, you can create multiple accounts after setup.</small> - + <small>اگر بیش از یک نفر از این کامپیوتر استفاده می کنند، میتوانید حساب های دیگری بعد نصب ایجاد کنید.</small> <small>If more than one person will use this computer, you can create multiple accounts after installation.</small> - + <small>اگر بیش از یک نفر از این کامپیوتر استفاده می کنند، میتوانید حساب های دیگری بعد نصب ایجاد کنید.</small> @@ -3991,7 +3998,7 @@ Output: <h1>%1</h1><br/><strong>%2<br/>for %3</strong><br/><br/>Copyright 2014-2017 Teo Mrnjavac &lt;teo@kde.org&gt;<br/>Copyright 2017-2020 Adriaan de Groot &lt;groot@kde.org&gt;<br/>Thanks to <a href="https://calamares.io/team/">the Calamares team</a> and the <a href="https://www.transifex.com/calamares/calamares/">Calamares translators team</a>.<br/><br/><a href="https://calamares.io/">Calamares</a> development is sponsored by <br/><a href="http://www.blue-systems.com/">Blue Systems</a> - Liberating Software. - + <h1>%1</h1><br/><strong>%2<br/> برای %3</strong><br/><br/> Copyright 2014-2017 Teo Mrnjavac &lt;teo@kde.org&gt;<br/> Copyright 2017-2020 Adriaan de Groot &lt;groot@kde.org&gt;<br/> سپاس از <a href='https://calamares.io/team/'>گروه کلامارس</a> و <a href='https://www.transifex.com/calamares/calamares/'>گروه ترجمه کلامارس</a>.<br/><br/> توسعه <a href='https://calamares.io/'>کلامارس</a> توسط <br/> <a href="http://www.blue-systems.com/"><a href='http://www.blue-systems.com/'>سیستم های آبی</a> - نرم افزار آزادی پشتیبانی شده است. @@ -4026,7 +4033,16 @@ Output: development is sponsored by <br/> <a href='http://www.blue-systems.com/'>Blue Systems</a> - Liberating Software. - + <h1>%1</h1><br/> + <strong>%2<br/> + برای %3</strong><br/><br/> + Copyright 2014-2017 Teo Mrnjavac &lt;teo@kde.org&gt;<br/> + Copyright 2017-2020 Adriaan de Groot &lt;groot@kde.org&gt;<br/> + سپاس از <a href='https://calamares.io/team/'>گروه کلامارس</a> + و <a href='https://www.transifex.com/calamares/calamares/'>گروه ترجمه کلامارس</a>.<br/><br/> + توسعه <a href='https://calamares.io/'>کلامارس</a> توسط <br/> + <a href='http://www.blue-systems.com/'>سیستم های آبی</a> - + نرم افزار آزادی پشتیبانی شده است. @@ -4047,29 +4063,31 @@ Output: Installation Completed - + نصب کامل شد %1 has been installed on your computer.<br/> You may now restart into your new system, or continue using the Live environment. - + %1 روی رایانه شما نصب شد.<br/> +میتوانید به سامانه جدیدتان وارد شوید، یا به استفاده محیط زنده ادامه دهید. Close Installer - + بستن نصب کننده Restart System - + راه اندازی مجدد سامانه <p>A full log of the install is available as installation.log in the home directory of the Live user.<br/> This log is copied to /var/log/installation.log of the target system.</p> - + <p>یک گزارش کامل از نصب در فایل installation.log درون مسیر خانه کاربر زنده موجود است.<br/> +این گزارش به مسیر /var/log/installation.log سامانه هدف نیز رونوشت شده است.</p> @@ -4078,13 +4096,15 @@ Output: <h1>Languages</h1> </br> The system locale setting affects the language and character set for some command line user interface elements. The current setting is <strong>%1</strong>. - + <h1>زبان ها</h1></br> + تنظیمات محلی سامانه روی زبان و مجموعه کارکتر برخی از عناصر رابط کاربری خط فرمان تاثیر می گذارد. تنظیمات فعلی <strong>%1</strong> است. <h1>Locales</h1> </br> The system locale setting affects the numbers and dates format. The current setting is <strong>%1</strong>. - + <h1>زبان های محلی</h1> </br> + تنظیمات زبان محلی سامانه قالب اعداد و تاریخ را تغییر می دهد. تنظیمات فعلی <strong>%1</strong> است. @@ -4097,7 +4117,7 @@ Output: To activate keyboard preview, select a layout. - + برای فعال کردن پیشنمایش صفحه کلید، یک چیدمان انتخاب کنید. @@ -4125,7 +4145,7 @@ Output: Change - + تغییر @@ -4134,7 +4154,8 @@ Output: <h3>%1</h3> <p>These are example release notes.</p> - + <h3>%1</h3> + <p>این ها یک سری یادداشت انتشار نمونه هستند.</p> @@ -4143,37 +4164,38 @@ Output: LibreOffice is a powerful and free office suite, used by millions of people around the world. It includes several applications that make it the most versatile Free and Open Source office suite on the market.<br/> Default option. - + LibreOffice یک مجموعه قدرتمند و آزاد از برنامه های اداری است، که توسط میلیون ها آدم در سراسر دنیا استفاده میشود. این مجموعه شامل برنامه های بسیاری هست که این مجموعه را یک مجموعه برنامه همه کاره آزاد و متن باز در بازار میکند.<br/> +گزینه پیشفرض LibreOffice - + LibreOffice If you don't want to install an office suite, just select No Office Suite. You can always add one (or more) later on your installed system as the need arrives. - + اگر نمیخواهید برنامه های اداری را نصب کنید، فقط گزینه بدون برنامه های اداری را انتخاب کنید. شما همیشه بعدا میتوانید یکی (یا چند تا) را اگر نیاز پیدا کردید، نصب کنید. No Office Suite - + بدون برنامه های اداری Create a minimal Desktop install, remove all extra applications and decide later on what you would like to add to your system. Examples of what won't be on such an install, there will be no Office Suite, no media players, no image viewer or print support. It will be just a desktop, file browser, package manager, text editor and simple web-browser. - + یک نصب حداقلی برای میزکار ایجاد کنید، تمام برنامه های اضافی را حذف کنید و بعدا تصمیم بگیرید که چه چیزی را میخواهید به رایانه خود اضافه کنید. مثال هایی از برنامه هایی که در این نصب جای ندارند عبارت است از نبود برنامه های اداری، هیچ پخش کننده رسانه ای، هیچ بازکننده تصویری یا پشتیبانی چاپ. این تنها یک میزکار، مدیریت فایل، مدیریت بسته، ویرایشگر متن و مرورگر ساده وب خواهد بود. Minimal Install - + نصب حداقلی Please select an option for your install, or use the default: LibreOffice included. - + لطفا گزینه ای را برای نصب انتخاب کنید، یا از پیشفرض استفاده کنید: LibreOffice @@ -4215,7 +4237,7 @@ Output: Pick your user name and credentials to login and perform admin tasks - + نام کاربری و اطلاعات مهم خود را برای ورود و انجام وظایف مدیریت برگزینید @@ -4235,12 +4257,12 @@ Output: Login Name - + نام ورود If more than one person will use this computer, you can create multiple accounts after installation. - + اگر بیش از یک نفر از این کامپیوتر استفاده می کنند، میتوانید حساب های دیگری بعد نصب ایجاد کنید. @@ -4250,7 +4272,7 @@ Output: root is not allowed as username. - + عبارت root بعنوان نام کاربر مجاز نیست. @@ -4265,12 +4287,12 @@ Output: This name will be used if you make the computer visible to others on a network. - + اگر رایانه‌تان را روی یک شبکه برای دیگران نمایان کنید، از این نام استفاده می‌شود. localhost is not allowed as hostname. - + عبارت localhost بعنوان نام میزبان مجاز نیست. @@ -4295,7 +4317,7 @@ Output: Validate passwords quality - + اعتبارسنجی کیفیت گذرواژه @@ -4305,17 +4327,17 @@ Output: Log in automatically without asking for the password - + ورود خودکار بدون پرسیدن گذرواژه Only letters, numbers, underscore and hyphen are allowed, minimal of two characters. - + حداقل دو حرف و فقط حروف، اعداد، زیرخط و خط تیره مجاز هستند. Reuse user password as root password - + استفاده گذرواژه کاربر بعنوان گذرواژه روت @@ -4325,22 +4347,22 @@ Output: Choose a root password to keep your account safe. - + برای امن نگه داشتن حسابتان، گذرواژه روت ای برگزینید. Root Password - + گذرواژه روت Repeat Root Password - + تکرار گذرواژه روت Enter the same password twice, so that it can be checked for typing errors. - + همان گذرواژه را دوباره وارد کنید تا بتواند برای خطاهای نوشتاری بررسی شود. @@ -4349,7 +4371,8 @@ Output: <h3>Welcome to the %1 <quote>%2</quote> installer</h3> <p>This program will ask you some questions and set up %1 on your computer.</p> - + <h3>به نصب کننده %1 <quote>%2</quote>خوش آمدید</h3> +<p>این برنامه از شما سوالایی میپرسد و %1 را روی رایانه شما نصب می کند.</p> From dc8901113e79415d4f37db39153fe94f7afabefb Mon Sep 17 00:00:00 2001 From: Calamares CI Date: Fri, 19 Nov 2021 23:13:58 +0100 Subject: [PATCH 103/127] i18n: [desktop] Automatic merge of Transifex translations --- calamares.desktop | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/calamares.desktop b/calamares.desktop index f7efa41a6..761a7db01 100644 --- a/calamares.desktop +++ b/calamares.desktop @@ -71,8 +71,8 @@ GenericName[eu]=Sistema instalatzailea Comment[eu]=Calamares - sistema instalatzailea Name[fa]=نصب سامانه Icon[fa]=کالامارس -GenericName[fa]=نصب‌کنندهٔ سامانه -Comment[fa]=کالامارس — نصب‌کنندهٔ سامانه +GenericName[fa]=نصب‌کننده سامانه +Comment[fa]=کالامارس — نصب‌کننده سامانه Name[es_PR]=Instalar el sistema Name[fr]=Installer le système Icon[fr]=calamares From 180b12fbafd97d058b4b55f8b8015ab29ed9e281 Mon Sep 17 00:00:00 2001 From: Calamares CI Date: Fri, 19 Nov 2021 23:13:59 +0100 Subject: [PATCH 104/127] i18n: [python] Automatic merge of Transifex translations --- lang/python/az/LC_MESSAGES/python.po | 3 +- lang/python/az_AZ/LC_MESSAGES/python.po | 3 +- lang/python/ca/LC_MESSAGES/python.po | 3 + lang/python/de/LC_MESSAGES/python.po | 4 +- lang/python/fa/LC_MESSAGES/python.po | 33 ++++-- lang/python/hr/LC_MESSAGES/python.po | 4 +- lang/python/ko/LC_MESSAGES/python.po | 4 +- lang/python/pt_PT/LC_MESSAGES/python.po | 4 +- lang/python/si/LC_MESSAGES/python.po | 148 ++++++++++++++---------- lang/python/sv/LC_MESSAGES/python.po | 4 +- lang/python/tr_TR/LC_MESSAGES/python.po | 3 +- 11 files changed, 133 insertions(+), 80 deletions(-) diff --git a/lang/python/az/LC_MESSAGES/python.po b/lang/python/az/LC_MESSAGES/python.po index 3dc3ec79a..93d08bf80 100644 --- a/lang/python/az/LC_MESSAGES/python.po +++ b/lang/python/az/LC_MESSAGES/python.po @@ -323,7 +323,7 @@ msgstr "{name!s} systemd hədəfi aktiv edilmədi" #: src/modules/services-systemd/main.py:67 msgid "Cannot enable systemd timer {name!s}." -msgstr "" +msgstr "{name!s} systemd taymeri aktiv edilə bilmir." #: src/modules/services-systemd/main.py:71 msgid "Cannot disable systemd target {name!s}." @@ -407,6 +407,7 @@ msgid "" "Failed to find unsquashfs, make sure you have the squashfs-tools package " "installed." msgstr "" +"Unsquashfs tapılmadı, squashfs-tools paketinin quraşdırıldığına əmin olun." #: src/modules/unpackfs/main.py:479 msgid "The destination \"{}\" in the target system is not a directory" diff --git a/lang/python/az_AZ/LC_MESSAGES/python.po b/lang/python/az_AZ/LC_MESSAGES/python.po index eda09d8bf..99cfb8df4 100644 --- a/lang/python/az_AZ/LC_MESSAGES/python.po +++ b/lang/python/az_AZ/LC_MESSAGES/python.po @@ -323,7 +323,7 @@ msgstr "{name!s} systemd hədəfi aktiv edilmədi" #: src/modules/services-systemd/main.py:67 msgid "Cannot enable systemd timer {name!s}." -msgstr "" +msgstr "{name!s} systemd taymeri aktiv edilə bilmir." #: src/modules/services-systemd/main.py:71 msgid "Cannot disable systemd target {name!s}." @@ -407,6 +407,7 @@ msgid "" "Failed to find unsquashfs, make sure you have the squashfs-tools package " "installed." msgstr "" +"Unsquashfs tapılmadı, squashfs-tools paketinin quraşdırıldığına əmin olun." #: src/modules/unpackfs/main.py:479 msgid "The destination \"{}\" in the target system is not a directory" diff --git a/lang/python/ca/LC_MESSAGES/python.po b/lang/python/ca/LC_MESSAGES/python.po index 236391ceb..66068c65f 100644 --- a/lang/python/ca/LC_MESSAGES/python.po +++ b/lang/python/ca/LC_MESSAGES/python.po @@ -327,6 +327,7 @@ msgstr "No es pot habilitar la destinació de systemd {name!s}." #: src/modules/services-systemd/main.py:67 msgid "Cannot enable systemd timer {name!s}." msgstr "" +"No es pot habilitar el temporitzador de systemd {name!s}." #: src/modules/services-systemd/main.py:71 msgid "Cannot disable systemd target {name!s}." @@ -406,6 +407,8 @@ msgid "" "Failed to find unsquashfs, make sure you have the squashfs-tools package " "installed." msgstr "" +"No s'ha pogut trobar unsquashfs, assegureu-vos que tingueu instal·lat el " +"paquet squashfs-tools." #: src/modules/unpackfs/main.py:479 msgid "The destination \"{}\" in the target system is not a directory" diff --git a/lang/python/de/LC_MESSAGES/python.po b/lang/python/de/LC_MESSAGES/python.po index 0baff8013..ca828a606 100644 --- a/lang/python/de/LC_MESSAGES/python.po +++ b/lang/python/de/LC_MESSAGES/python.po @@ -329,7 +329,7 @@ msgstr "Das systemd-Ziel {name!s} kann nicht aktiviert werden." #: src/modules/services-systemd/main.py:67 msgid "Cannot enable systemd timer {name!s}." -msgstr "" +msgstr "Systemd-Timer {name!s} kann nicht aktiviert werden." #: src/modules/services-systemd/main.py:71 msgid "Cannot disable systemd target {name!s}." @@ -412,6 +412,8 @@ msgid "" "Failed to find unsquashfs, make sure you have the squashfs-tools package " "installed." msgstr "" +"Unsquashfs nicht gefunden, stellen Sie sicher, dass das Paket squashfs-tools" +" installiert ist." #: src/modules/unpackfs/main.py:479 msgid "The destination \"{}\" in the target system is not a directory" diff --git a/lang/python/fa/LC_MESSAGES/python.po b/lang/python/fa/LC_MESSAGES/python.po index 914c8c70c..4c9009666 100644 --- a/lang/python/fa/LC_MESSAGES/python.po +++ b/lang/python/fa/LC_MESSAGES/python.po @@ -6,6 +6,7 @@ # Translators: # Danial Behzadi , 2020 # alireza jamshidi , 2020 +# Mahdy Mirzade , 2021 # #, fuzzy msgid "" @@ -14,7 +15,7 @@ msgstr "" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2021-11-02 15:45+0100\n" "PO-Revision-Date: 2017-08-09 10:34+0000\n" -"Last-Translator: alireza jamshidi , 2020\n" +"Last-Translator: Mahdy Mirzade , 2021\n" "Language-Team: Persian (https://www.transifex.com/calamares/teams/20061/fa/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -62,13 +63,15 @@ msgstr "نصب بارکنندهٔ راه‌اندازی." #: src/modules/bootloader/main.py:508 msgid "Bootloader installation error" -msgstr "" +msgstr "خطای نصب بوت لودر" #: src/modules/bootloader/main.py:509 msgid "" "The bootloader could not be installed. The installation command " "
{!s}
returned error code {!s}." msgstr "" +"بوت لودر نتوانست نصب شود. دستور
{!s}
برای نصب با خطای {!s} مواجه " +"شد." #: src/modules/fstab/main.py:29 msgid "Writing fstab." @@ -77,6 +80,7 @@ msgstr "در حال نوشتن fstab." #: src/modules/fstab/main.py:389 msgid "No
{!s}
configuration is given for
{!s}
to use." msgstr "" +"هیچ تنظیمات
{!s}
برای استفاده برای
{!s}
داده نشده است." #: src/modules/dracut/main.py:27 msgid "Creating initramfs with dracut." @@ -139,6 +143,8 @@ msgid "" "The displaymanagers list is empty or undefined in both globalstorage and " "displaymanager.conf." msgstr "" +"فهرست مدیریت صفحه نمایش ها خالی بوده یا در محل ذخیره داده و " +"displaymanager.conf تعریف نشده است." #: src/modules/displaymanager/main.py:989 msgid "Display manager configuration was incomplete" @@ -161,6 +167,8 @@ msgid "" "Unknown service-action {arg!s} for service {name!s} in run-" "level {level!s}." msgstr "" +"دستور سرویس {arg!s} برای سرویس {name!s} در سطح اجرای {level!s}" +" ناشناخته است." #: src/modules/services-openrc/main.py:93 #: src/modules/services-systemd/main.py:59 @@ -171,6 +179,8 @@ msgstr "نمی‌توان خدمت را دستکاری کرد" msgid "" "rc-update {arg!s} call in chroot returned error code {num!s}." msgstr "" +"فراخوانی rc-update {arg!s} در chroot کد خطای {num!s} را " +"برگرداند." #: src/modules/services-openrc/main.py:101 msgid "Target runlevel does not exist" @@ -181,6 +191,8 @@ msgid "" "The path for runlevel {level!s} is {path!s}, which does not " "exist." msgstr "" +"مسیر برای سطح اجرای {level!s} برابر {path!s} است، که وجود " +"ندارد." #: src/modules/services-openrc/main.py:110 msgid "Target service does not exist" @@ -191,6 +203,7 @@ msgid "" "The path for service {name!s} is {path!s}, which does not " "exist." msgstr "" +"مسیر برای سرویس {name!s} برابر {path!s} است، که وجود ندارد." #: src/modules/networkcfg/main.py:29 msgid "Saving network configuration." @@ -223,25 +236,31 @@ msgstr[1] "در حال برداشتن %(num)d بسته." #: src/modules/packages/main.py:638 src/modules/packages/main.py:650 #: src/modules/packages/main.py:678 msgid "Package Manager error" -msgstr "" +msgstr "خطای مدیر بسته" #: src/modules/packages/main.py:639 msgid "" "The package manager could not prepare updates. The command
{!s}
" "returned error code {!s}." msgstr "" +"مدیر بسته نتوانست برای بروزرسانی ها آماده شود، دستور
{!s}
با خطای" +" {!s} مواجه شد." #: src/modules/packages/main.py:651 msgid "" "The package manager could not update the system. The command
{!s}
" " returned error code {!s}." msgstr "" +"مدیر بسته نتوانست سامانه را بروز کند. دستور
{!s}
با خطای {!s} " +"مواجه شد." #: src/modules/packages/main.py:679 msgid "" "The package manager could not make changes to the installed system. The " "command
{!s}
returned error code {!s}." msgstr "" +"مدیر بسته نتوانست تغییرات را برای نصب سامانه انجام دهد. دستور " +"
{!s}
با خطای {!s} مواجه شد." #: src/modules/plymouthcfg/main.py:27 msgid "Configure Plymouth theme" @@ -306,7 +325,7 @@ msgstr "نمی‌توان هدف سیستم‌دی {name!s} را ب #: src/modules/services-systemd/main.py:67 msgid "Cannot enable systemd timer {name!s}." -msgstr "" +msgstr "نمی‌توان تایمر سیستم‌دی {name!s} را به کار انداخت." #: src/modules/services-systemd/main.py:71 msgid "Cannot disable systemd target {name!s}." @@ -326,11 +345,11 @@ msgstr "" #: src/modules/mkinitfs/main.py:27 msgid "Creating initramfs with mkinitfs." -msgstr "" +msgstr "درحال ایجاد initramfs با mkinitfs." #: src/modules/mkinitfs/main.py:49 msgid "Failed to run mkinitfs on the target" -msgstr "" +msgstr "شکست در اجرا mkinitfs روی هدف" #: src/modules/unpackfs/main.py:34 msgid "Filling up filesystems." @@ -385,7 +404,7 @@ msgstr "سامانهٔ پروندهٔ مبدأ {} وجود ندارد" msgid "" "Failed to find unsquashfs, make sure you have the squashfs-tools package " "installed." -msgstr "" +msgstr "شکست در یافتن unsquashfs. مطمئن شوید بسته squashfs-tools نصب است." #: src/modules/unpackfs/main.py:479 msgid "The destination \"{}\" in the target system is not a directory" diff --git a/lang/python/hr/LC_MESSAGES/python.po b/lang/python/hr/LC_MESSAGES/python.po index 796af7448..5be3da384 100644 --- a/lang/python/hr/LC_MESSAGES/python.po +++ b/lang/python/hr/LC_MESSAGES/python.po @@ -325,7 +325,7 @@ msgstr "Ne mogu omogućiti systemd cilj {name!s}." #: src/modules/services-systemd/main.py:67 msgid "Cannot enable systemd timer {name!s}." -msgstr "" +msgstr "Nije moguće omogućiti systemd timer {name!s}." #: src/modules/services-systemd/main.py:71 msgid "Cannot disable systemd target {name!s}." @@ -405,6 +405,8 @@ msgid "" "Failed to find unsquashfs, make sure you have the squashfs-tools package " "installed." msgstr "" +"Neuspješno pronalaženje unsquashfs, provjerite imate li instaliran paket " +"squashfs-tools." #: src/modules/unpackfs/main.py:479 msgid "The destination \"{}\" in the target system is not a directory" diff --git a/lang/python/ko/LC_MESSAGES/python.po b/lang/python/ko/LC_MESSAGES/python.po index 4e1aa1e1e..177ce25f7 100644 --- a/lang/python/ko/LC_MESSAGES/python.po +++ b/lang/python/ko/LC_MESSAGES/python.po @@ -307,7 +307,7 @@ msgstr "systemd 대상 {name! s}를 활성화 할 수 없습니다. #: src/modules/services-systemd/main.py:67 msgid "Cannot enable systemd timer {name!s}." -msgstr "" +msgstr "시스템 타이머 {name!s}를 활성화할 수 없습니다." #: src/modules/services-systemd/main.py:71 msgid "Cannot disable systemd target {name!s}." @@ -386,7 +386,7 @@ msgstr "\"{}\" 소스 파일시스템은 존재하지 않습니다." msgid "" "Failed to find unsquashfs, make sure you have the squashfs-tools package " "installed." -msgstr "" +msgstr "unsquashfs를 찾지 못했습니다. squashfs-tools 패키지가 설치되어 있는지 확인하십시오." #: src/modules/unpackfs/main.py:479 msgid "The destination \"{}\" in the target system is not a directory" diff --git a/lang/python/pt_PT/LC_MESSAGES/python.po b/lang/python/pt_PT/LC_MESSAGES/python.po index bbca55b84..7f10d0c95 100644 --- a/lang/python/pt_PT/LC_MESSAGES/python.po +++ b/lang/python/pt_PT/LC_MESSAGES/python.po @@ -329,7 +329,7 @@ msgstr "Não é possível ativar o destino do systemd {name!s}." #: src/modules/services-systemd/main.py:67 msgid "Cannot enable systemd timer {name!s}." -msgstr "" +msgstr "Não é possível ativar o temporizador systemd {name!s}." #: src/modules/services-systemd/main.py:71 msgid "Cannot disable systemd target {name!s}." @@ -411,6 +411,8 @@ msgid "" "Failed to find unsquashfs, make sure you have the squashfs-tools package " "installed." msgstr "" +"Falha ao localizar o unsquashfs, certifique-se de que tem o pacote squashfs-" +"tools instalado." #: src/modules/unpackfs/main.py:479 msgid "The destination \"{}\" in the target system is not a directory" diff --git a/lang/python/si/LC_MESSAGES/python.po b/lang/python/si/LC_MESSAGES/python.po index 545e074ca..76ce5f160 100644 --- a/lang/python/si/LC_MESSAGES/python.po +++ b/lang/python/si/LC_MESSAGES/python.po @@ -5,6 +5,7 @@ # # Translators: # හෙළබස, 2021 +# Sandaruwan Samaraweera, 2021 # #, fuzzy msgid "" @@ -13,7 +14,7 @@ msgstr "" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2021-11-02 15:45+0100\n" "PO-Revision-Date: 2017-08-09 10:34+0000\n" -"Last-Translator: හෙළබස, 2021\n" +"Last-Translator: Sandaruwan Samaraweera, 2021\n" "Language-Team: Sinhala (https://www.transifex.com/calamares/teams/20061/si/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -23,7 +24,7 @@ msgstr "" #: src/modules/initramfscfg/main.py:32 msgid "Configuring initramfs." -msgstr "" +msgstr "initramfs වින්‍යාස කිරීම." #: src/modules/initramfscfg/main.py:85 src/modules/initramfscfg/main.py:89 #: src/modules/fstab/main.py:355 src/modules/fstab/main.py:361 @@ -35,161 +36,169 @@ msgstr "" #: src/modules/luksopenswaphookcfg/main.py:86 #: src/modules/luksopenswaphookcfg/main.py:90 msgid "Configuration Error" -msgstr "" +msgstr "වින්‍යාස දෝෂය" #: src/modules/initramfscfg/main.py:86 src/modules/fstab/main.py:356 #: src/modules/initcpiocfg/main.py:228 src/modules/mount/main.py:145 #: src/modules/rawfs/main.py:165 src/modules/openrcdmcryptcfg/main.py:73 #: src/modules/luksopenswaphookcfg/main.py:87 msgid "No partitions are defined for
{!s}
to use." -msgstr "" +msgstr "{!s} සඳහා භාවිතා කිරීමට කිසිදු කොටස් නිර්වචනය කර නොමැත." #: src/modules/initramfscfg/main.py:90 src/modules/fstab/main.py:362 #: src/modules/networkcfg/main.py:106 src/modules/initcpiocfg/main.py:232 #: src/modules/localecfg/main.py:136 src/modules/openrcdmcryptcfg/main.py:77 #: src/modules/luksopenswaphookcfg/main.py:91 msgid "No root mount point is given for
{!s}
to use." -msgstr "" +msgstr "{!s} සඳහා භාවිතා කිරීමට root mount point ලබා දී නොමැත." #: src/modules/grubcfg/main.py:28 msgid "Configure GRUB." -msgstr "" +msgstr "GRUB වින්‍යාස කරන්න." #: src/modules/bootloader/main.py:43 msgid "Install bootloader." -msgstr "" +msgstr "bootloader ස්ථාපනය කරන්න." #: src/modules/bootloader/main.py:508 msgid "Bootloader installation error" -msgstr "" +msgstr "Bootloader ස්ථාපනය කිරීමේ දෝෂයකි" #: src/modules/bootloader/main.py:509 msgid "" "The bootloader could not be installed. The installation command " "
{!s}
returned error code {!s}." msgstr "" +"ඇරඹුම් කාරකය ස්ථාපනය කල නොහැක. ස්ථාපන විධානය
{!s}
දෝෂ කේතය {!s} " +"ලබා දුන්නේය." #: src/modules/fstab/main.py:29 msgid "Writing fstab." -msgstr "" +msgstr "fstab ලියමින්." #: src/modules/fstab/main.py:389 msgid "No
{!s}
configuration is given for
{!s}
to use." msgstr "" +"භාවිතා කිරීමට
{!s}
සඳහා
{!s}
වින්‍යාසයක් ලබා දී නොමැත." #: src/modules/dracut/main.py:27 msgid "Creating initramfs with dracut." -msgstr "" +msgstr "dracut සමඟ initramfs නිර්මාණය කිරීම." #: src/modules/dracut/main.py:49 msgid "Failed to run dracut on the target" -msgstr "" +msgstr "ඉලක්කය මත ඩ්‍රැකට් ධාවනය කිරීමට අපොහොසත් විය" #: src/modules/dracut/main.py:50 src/modules/mkinitfs/main.py:50 msgid "The exit code was {}" -msgstr "" +msgstr "පිටවීමේ කේතය වූයේ {}" #: src/modules/displaymanager/main.py:526 msgid "Cannot write KDM configuration file" -msgstr "" +msgstr "KDM වින්‍යාස ගොනුව ලිවිය නොහැක" #: src/modules/displaymanager/main.py:527 msgid "KDM config file {!s} does not exist" -msgstr "" +msgstr "KDM වින්‍යාස ගොනුව {!s} නොපවතී" #: src/modules/displaymanager/main.py:588 msgid "Cannot write LXDM configuration file" -msgstr "" +msgstr "LXDM වින්‍යාස ගොනුව ලිවිය නොහැක" #: src/modules/displaymanager/main.py:589 msgid "LXDM config file {!s} does not exist" -msgstr "" +msgstr "LXDM වින්‍යාස ගොනුව {!s} නොපවතී" #: src/modules/displaymanager/main.py:672 msgid "Cannot write LightDM configuration file" -msgstr "" +msgstr "LightDM වින්‍යාස ගොනුව ලිවිය නොහැක" #: src/modules/displaymanager/main.py:673 msgid "LightDM config file {!s} does not exist" -msgstr "" +msgstr "LightDM වින්‍යාස ගොනුව {!s} නොපවතී" #: src/modules/displaymanager/main.py:747 msgid "Cannot configure LightDM" -msgstr "" +msgstr "LightDM වින්‍යාස කළ නොහැක" #: src/modules/displaymanager/main.py:748 msgid "No LightDM greeter installed." -msgstr "" +msgstr "LightDM ග්‍රීටර් ස්ථාපනය කර නැත." #: src/modules/displaymanager/main.py:779 msgid "Cannot write SLIM configuration file" -msgstr "" +msgstr "SLIM වින්‍යාස ගොනුව ලිවිය නොහැක" #: src/modules/displaymanager/main.py:780 msgid "SLIM config file {!s} does not exist" -msgstr "" +msgstr "SLIM වින්‍යාස ගොනුව {!s} නොපවතී" #: src/modules/displaymanager/main.py:906 msgid "No display managers selected for the displaymanager module." -msgstr "" +msgstr "සංදර්ශක කළමනාකරු මොඩියුලය සඳහා සංදර්ශක කළමනාකරුවන් තෝරාගෙන නොමැත." #: src/modules/displaymanager/main.py:907 msgid "" "The displaymanagers list is empty or undefined in both globalstorage and " "displaymanager.conf." msgstr "" +"ගෝලීය ගබඩාව සහ displaymanager.conf යන දෙකෙහිම සංදර්ශක කළමනාකරු ලැයිස්තුව " +"හිස් හෝ අර්ථ දක්වා නොමැත." #: src/modules/displaymanager/main.py:989 msgid "Display manager configuration was incomplete" -msgstr "" +msgstr "සංදර්ශක කළමනාකරු වින්‍යාසය අසම්පූර්ණ විය" #: src/modules/services-openrc/main.py:29 msgid "Configure OpenRC services" -msgstr "" +msgstr "OpenRC සේවා වින්‍යාස කරන්න" #: src/modules/services-openrc/main.py:57 msgid "Cannot add service {name!s} to run-level {level!s}." -msgstr "" +msgstr "ධාවන මට්ටම {level!s} වෙත සේවාව {name!s} එක් කළ නොහැක." #: src/modules/services-openrc/main.py:59 msgid "Cannot remove service {name!s} from run-level {level!s}." -msgstr "" +msgstr "ධාවන මට්ටමේ {level!s} වෙතින් සේවාව {name!s} ඉවත් කළ නොහැක." #: src/modules/services-openrc/main.py:61 msgid "" "Unknown service-action {arg!s} for service {name!s} in run-" "level {level!s}." msgstr "" +"{name!s} සේවාව සඳහා නොදන්නා සේවා-ක්‍රියාව {arg!s} ධාවන මට්ටමේ " +"{level!s}." #: src/modules/services-openrc/main.py:93 #: src/modules/services-systemd/main.py:59 msgid "Cannot modify service" -msgstr "" +msgstr "සේවාව වෙනස් කළ නොහැක" #: src/modules/services-openrc/main.py:94 msgid "" "rc-update {arg!s} call in chroot returned error code {num!s}." msgstr "" +"rc-update {arg!s} chroot හි ඇමතුම {num!s} දෝෂ කේතය ලබා දුන්නේය." #: src/modules/services-openrc/main.py:101 msgid "Target runlevel does not exist" -msgstr "" +msgstr "ඉලක්ක ධාවන මට්ටම නොපවතී" #: src/modules/services-openrc/main.py:102 msgid "" "The path for runlevel {level!s} is {path!s}, which does not " "exist." -msgstr "" +msgstr "ධාවන මට්ටම {level!s} සඳහා මාර්ගය {path!s}, එය නොපවතී." #: src/modules/services-openrc/main.py:110 msgid "Target service does not exist" -msgstr "" +msgstr "ඉලක්ක සේවාව නොපවතී" #: src/modules/services-openrc/main.py:111 msgid "" "The path for service {name!s} is {path!s}, which does not " "exist." -msgstr "" +msgstr "සේවාව සඳහා {name!s} මාර්ගය {path!s}, එය නොපවතී." #: src/modules/networkcfg/main.py:29 msgid "Saving network configuration." @@ -203,7 +212,7 @@ msgstr "ඇසුරුම් ස්ථාපනය කරන්න." #: src/modules/packages/main.py:57 #, python-format msgid "Processing packages (%(count)d / %(total)d)" -msgstr "" +msgstr "පැකේජ සැකසීම (%(count)d / %(total)d)" #: src/modules/packages/main.py:62 #, python-format @@ -222,41 +231,47 @@ msgstr[1] "ඇසුරුම් %(num)d ක් ඉවත් වෙමින්. #: src/modules/packages/main.py:638 src/modules/packages/main.py:650 #: src/modules/packages/main.py:678 msgid "Package Manager error" -msgstr "" +msgstr "පැකේජ කළමනාකරු දෝෂයකි" #: src/modules/packages/main.py:639 msgid "" "The package manager could not prepare updates. The command
{!s}
" "returned error code {!s}." msgstr "" +"පැකේජ කළමනාකරුට යාවත්කාලීන සකස් කිරීමට නොහැකි විය. විධානය
{!s}
" +"දෝෂ කේතය {!s} ලබා දුන්නේය." #: src/modules/packages/main.py:651 msgid "" "The package manager could not update the system. The command
{!s}
" " returned error code {!s}." msgstr "" +"පැකේජ කළමනාකරුට පද්ධතිය යාවත්කාලීන කළ නොහැකි විය. විධානය
{!s}
දෝෂ" +" කේතය {!s} ලබා දුන්නේය." #: src/modules/packages/main.py:679 msgid "" "The package manager could not make changes to the installed system. The " "command
{!s}
returned error code {!s}." msgstr "" +"පැකේජ කළමනාකරුට ස්ථාපිත පද්ධතියට වෙනස්කම් සිදු කළ නොහැක. විධානය " +"
{!s}
දෝෂ කේතය {!s} ලබා දුන්නේය." #: src/modules/plymouthcfg/main.py:27 msgid "Configure Plymouth theme" -msgstr "" +msgstr "Plymouth තේමාව වින්‍යාස කරන්න" #: src/modules/initcpiocfg/main.py:28 msgid "Configuring mkinitcpio." -msgstr "" +msgstr "mkinitcpio වින්‍යාස කරමින්." #: src/modules/localecfg/main.py:30 msgid "Configuring locales." -msgstr "" +msgstr "ස්ථාන වින්‍යාස කිරීම." #: src/modules/mount/main.py:30 msgid "Mounting partitions." -msgstr "" +msgstr "කොටස් සවි කිරීම." #: src/modules/rawfs/main.py:26 msgid "Installing data." @@ -264,12 +279,12 @@ msgstr "දත්ත ස්ථාපනය වෙමින්." #: src/modules/dummypython/main.py:35 msgid "Dummy python job." -msgstr "" +msgstr "ඩමි python වැඩසටහන." #: src/modules/dummypython/main.py:37 src/modules/dummypython/main.py:93 #: src/modules/dummypython/main.py:94 msgid "Dummy python step {}" -msgstr "" +msgstr "ව්‍යාජ python පියවර {}" #: src/modules/hwclock/main.py:26 msgid "Setting hardware clock." @@ -277,115 +292,120 @@ msgstr "දෘඩාංග ඔරලෝසුව සැකසෙමින්." #: src/modules/umount/main.py:31 msgid "Unmount file systems." -msgstr "" +msgstr "ගොනු පද්ධති ඉවත් කරන්න." #: src/modules/openrcdmcryptcfg/main.py:26 msgid "Configuring OpenRC dmcrypt service." -msgstr "" +msgstr "OpenRC dmcrypt සේවාව වින්‍යාස කරමින්." #: src/modules/services-systemd/main.py:26 msgid "Configure systemd services" -msgstr "" +msgstr "systemd සේවා වින්‍යාස කරන්න" #: src/modules/services-systemd/main.py:60 msgid "" "systemctl {arg!s} call in chroot returned error code {num!s}." msgstr "" +"systemctl {arg!s} chroot වෙත ඇමතුමක් ලබා දුන් දෝෂ කේතය {num!s}." #: src/modules/services-systemd/main.py:63 #: src/modules/services-systemd/main.py:69 msgid "Cannot enable systemd service {name!s}." -msgstr "" +msgstr "systemd සේවාව {name!s} සබල කළ නොහැක." #: src/modules/services-systemd/main.py:65 msgid "Cannot enable systemd target {name!s}." -msgstr "" +msgstr "systemd ඉලක්කය {name!s} සබල කළ නොහැක." #: src/modules/services-systemd/main.py:67 msgid "Cannot enable systemd timer {name!s}." -msgstr "" +msgstr "systemd ටයිමරය {name!s} සබල කළ නොහැක." #: src/modules/services-systemd/main.py:71 msgid "Cannot disable systemd target {name!s}." -msgstr "" +msgstr "systemd ඉලක්කය {name!s} අක්‍රිය කළ නොහැක." #: src/modules/services-systemd/main.py:73 msgid "Cannot mask systemd unit {name!s}." -msgstr "" +msgstr "systemd ඒකකය {name!s} වසන් කළ නොහැක." #: src/modules/services-systemd/main.py:75 msgid "" "Unknown systemd commands {command!s} and " "{suffix!s} for unit {name!s}." msgstr "" +"{name!s} ඒකකය සඳහා නොදන්නා systemd විධාන {command!s} සහ " +"{suffix!s}." #: src/modules/mkinitfs/main.py:27 msgid "Creating initramfs with mkinitfs." -msgstr "" +msgstr "mkinitfs සමඟ initramfs නිර්මාණය කිරීම." #: src/modules/mkinitfs/main.py:49 msgid "Failed to run mkinitfs on the target" -msgstr "" +msgstr "ඉලක්කය මත mkinitfs ධාවනය කිරීමට අසමත් විය" #: src/modules/unpackfs/main.py:34 msgid "Filling up filesystems." -msgstr "" +msgstr "ගොනු පද්ධති පිරවීම." #: src/modules/unpackfs/main.py:254 msgid "rsync failed with error code {}." -msgstr "" +msgstr "දෝෂ කේතය {} සමඟ rsync අසාර්ථක විය." #: src/modules/unpackfs/main.py:299 msgid "Unpacking image {}/{}, file {}/{}" -msgstr "" +msgstr "රූපය {}/{}, ගොනුව {}/{} අසුරමින්" #: src/modules/unpackfs/main.py:314 msgid "Starting to unpack {}" -msgstr "" +msgstr "ඉවත් කිරීමට පටන් ගනියි {}" #: src/modules/unpackfs/main.py:323 src/modules/unpackfs/main.py:465 msgid "Failed to unpack image \"{}\"" -msgstr "" +msgstr "\"{}\" රූපය ඉවත් කිරීමට අසමත් විය" #: src/modules/unpackfs/main.py:430 msgid "No mount point for root partition" -msgstr "" +msgstr "root කොටස සඳහා සවි කිරීමේ ලක්ෂ්‍යයක් නොමැත" #: src/modules/unpackfs/main.py:431 msgid "globalstorage does not contain a \"rootMountPoint\" key, doing nothing" -msgstr "" +msgstr "ගෝලීය ගබඩාවේ \"rootMountPoint\" යතුරක් අඩංගු නොවේ, කිසිවක් නොකරයි" #: src/modules/unpackfs/main.py:436 msgid "Bad mount point for root partition" -msgstr "" +msgstr "මූල කොටස සඳහා නරක සවි කිරීමේ ලක්ෂ්‍යය" #: src/modules/unpackfs/main.py:437 msgid "rootMountPoint is \"{}\", which does not exist, doing nothing" -msgstr "" +msgstr "rootMountPoint යනු \"{}\", එය නොපවතින, කිසිවක් නොකරයි" #: src/modules/unpackfs/main.py:453 src/modules/unpackfs/main.py:457 #: src/modules/unpackfs/main.py:463 src/modules/unpackfs/main.py:478 msgid "Bad unsquash configuration" -msgstr "" +msgstr "නරක unsquash වින්‍යාසය" #: src/modules/unpackfs/main.py:454 msgid "The filesystem for \"{}\" ({}) is not supported by your current kernel" -msgstr "" +msgstr "\"{}\" ({}) සඳහා ගොනු පද්ධතිය ඔබගේ වත්මන් කර්නලයෙන් සහය නොදක්වයි" #: src/modules/unpackfs/main.py:458 msgid "The source filesystem \"{}\" does not exist" -msgstr "" +msgstr "මූලාශ්‍ර ගොනු පද්ධතිය \"{}\" නොපවතී" #: src/modules/unpackfs/main.py:464 msgid "" "Failed to find unsquashfs, make sure you have the squashfs-tools package " "installed." msgstr "" +"Unsquashfs සොයා ගැනීමට අපොහොසත් විය, ඔබ squashfs-tools පැකේජය ස්ථාපනය කර ඇති" +" බවට වග බලා ගන්න." #: src/modules/unpackfs/main.py:479 msgid "The destination \"{}\" in the target system is not a directory" -msgstr "" +msgstr "ඉලක්ක පද්ධතියේ \"{}\" ගමනාන්තය නාමාවලියක් නොවේ" #: src/modules/luksopenswaphookcfg/main.py:26 msgid "Configuring encrypted swap." -msgstr "" +msgstr "සංකේතාත්මක swap වින්‍යාස කිරීම." diff --git a/lang/python/sv/LC_MESSAGES/python.po b/lang/python/sv/LC_MESSAGES/python.po index e91510fb2..86832e8a3 100644 --- a/lang/python/sv/LC_MESSAGES/python.po +++ b/lang/python/sv/LC_MESSAGES/python.po @@ -327,7 +327,7 @@ msgstr "Kunde inte aktivera systemd målsystem {name!s}." #: src/modules/services-systemd/main.py:67 msgid "Cannot enable systemd timer {name!s}." -msgstr "" +msgstr "Kunde inte aktivera systemd timer {name!s}." #: src/modules/services-systemd/main.py:71 msgid "Cannot disable systemd target {name!s}." @@ -407,6 +407,8 @@ msgid "" "Failed to find unsquashfs, make sure you have the squashfs-tools package " "installed." msgstr "" +"Kunde inte hitta unsquashfs, se till att du har paketet squashfs-tools " +"installerat" #: src/modules/unpackfs/main.py:479 msgid "The destination \"{}\" in the target system is not a directory" diff --git a/lang/python/tr_TR/LC_MESSAGES/python.po b/lang/python/tr_TR/LC_MESSAGES/python.po index 90d0345e6..df566cfec 100644 --- a/lang/python/tr_TR/LC_MESSAGES/python.po +++ b/lang/python/tr_TR/LC_MESSAGES/python.po @@ -322,7 +322,7 @@ msgstr "Systemd hedefi etkinleştirilemiyor {name!s}." #: src/modules/services-systemd/main.py:67 msgid "Cannot enable systemd timer {name!s}." -msgstr "" +msgstr "{name!s} sistem zamanlayıcısı etkinleştirilemiyor." #: src/modules/services-systemd/main.py:71 msgid "Cannot disable systemd target {name!s}." @@ -403,6 +403,7 @@ msgid "" "Failed to find unsquashfs, make sure you have the squashfs-tools package " "installed." msgstr "" +"Unsquashfs bulunamadı, squashfs-tools paketinin kurulu olduğundan emin olun." #: src/modules/unpackfs/main.py:479 msgid "The destination \"{}\" in the target system is not a directory" From 01d55254f27d051c333dcfa80f069fdecdc61ccf Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Fri, 19 Nov 2021 23:17:05 +0100 Subject: [PATCH 105/127] i18n: update languages (Farsi at 100%) --- CMakeLists.txt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 296c88e74..67bb1f319 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -133,12 +133,12 @@ set( CALAMARES_DESCRIPTION_SUMMARY # `txstats.py -e`. See also # # Total 81 languages -set( _tx_complete az az_AZ ca de fi_FI he hr ja ko lt pt_PT si sq - tr_TR uk zh_TW ) +set( _tx_complete az az_AZ ca de fa fi_FI he hr ja ko lt pt_PT si + sq tr_TR uk zh_TW ) set( _tx_good as be ca@valencia cs_CZ da fr fur hi it_IT ml nl pt_BR ru sk sv tg vi zh_CN ) -set( _tx_ok ar ast bg bn el en_GB es es_MX et eu fa gl hu id is mr - nb pl ro sl sr sr@latin th ) +set( _tx_ok ar ast bg bn el en_GB es es_MX et eu gl hu id is mr nb + pl ro sl sr sr@latin th ) set( _tx_incomplete en_HK en_IN eo es_PE es_PR fr_CH gu hi_IN id_ID ie kk kn ko_KR lo lv mk ne ne_NP te te_IN ur zh zh_HK ) From fa10bb8dd35cf58d52c83b11e94e1aaeb28d0bae Mon Sep 17 00:00:00 2001 From: dalto Date: Thu, 25 Nov 2021 11:52:41 -0600 Subject: [PATCH 106/127] [packages] Add support for more pacman options --- src/modules/packages/main.py | 49 +++++++++++++++++++---- src/modules/packages/packages.conf | 16 ++++++++ src/modules/packages/packages.schema.yaml | 4 ++ 3 files changed, 62 insertions(+), 7 deletions(-) diff --git a/src/modules/packages/main.py b/src/modules/packages/main.py index 98faa9b63..3dd93576a 100644 --- a/src/modules/packages/main.py +++ b/src/modules/packages/main.py @@ -379,6 +379,7 @@ class PMPacman(PackageManager): def __init__(self): import re progress_match = re.compile("^\\((\\d+)/(\\d+)\\)") + def line_cb(line): if line.startswith(":: "): self.in_package_changes = "package changes" in line @@ -396,30 +397,64 @@ class PMPacman(PackageManager): self.in_package_changes = False self.line_cb = line_cb + self.pacman_num_retries = libcalamares.job.configuration.get("pacman_num_retries", 0) + self.pacman_disable_timeout = libcalamares.job.configuration.get("pacman_disable_download_timeout", False) + self.pacman_needed_only = libcalamares.job.configuration.get("pacman_needed_only", False) + def reset_progress(self): self.in_package_changes = False # These are globals self.progress_fraction = (completed_packages * 1.0 / total_packages) + def run_pacman(self, command): + # Call pacman in a loop until it is successful or the number of retries is exceeded + pacman_count = 0 + while pacman_count <= self.pacman_num_retries: + pacman_count += 1 + try: + libcalamares.utils.target_env_process_output(command, self.line_cb) + return + except subprocess.CalledProcessError: + if pacman_count <= self.pacman_num_retries: + pass + else: + raise + def install(self, pkgs, from_local=False): + command = ["pacman"] + if from_local: - pacman_flags = "-U" + command.append("-U") else: - pacman_flags = "-S" + command.append("-S") + + command.append("--noconfirm") + + if self.pacman_needed_only is True: + command.append("--needed") + + if self.pacman_disable_timeout is True: + command.append("--disable-download-timeout") + + command += pkgs + + self.run_pacman(command) self.reset_progress() - libcalamares.utils.target_env_process_output(["pacman", pacman_flags, - "--noconfirm"] + pkgs, self.line_cb) def remove(self, pkgs): self.reset_progress() - libcalamares.utils.target_env_process_output(["pacman", "-Rs", "--noconfirm"] + pkgs, self.line_cb) + self.run_pacman(["pacman", "-Rs", "--noconfirm"] + pkgs) def update_db(self): - check_target_env_call(["pacman", "-Sy"]) + self.run_pacman(["pacman", "-Sy"]) def update_system(self): - check_target_env_call(["pacman", "-Su", "--noconfirm"]) + command = ["pacman", "-Su", "--noconfirm"] + if self.pacman_disable_timeout is True: + command.append("--disable-download-timeout") + + self.run_pacman(command) class PMPamac(PackageManager): diff --git a/src/modules/packages/packages.conf b/src/modules/packages/packages.conf index 49fdbb6d6..8d76060a0 100644 --- a/src/modules/packages/packages.conf +++ b/src/modules/packages/packages.conf @@ -62,6 +62,22 @@ skip_if_no_internet: false update_db: true update_system: false +# pacman specific options +# +# pacman_num_retries should be a positive integer which sepcifies the +# number of times the call to pacman will be retried in the event of a +# failure. If it is missing, it will be set to 0 +# +# pacman_disable_download_timeout is a boolean that, when true, includes +# the flag --disable-download-timeout on calls to pacman. When missing, +# false is assumed +# +# pacman_needed_only is a boolean that includes the pacman argument --needed +# when set to true. If missing, false is assumed +pacman_num_retries: 0 +pacman_disable_download_timeout: false +pacman_needed_only: false + # # List of maps with package operations such as install or remove. # Distro developers can provide a list of packages to remove diff --git a/src/modules/packages/packages.schema.yaml b/src/modules/packages/packages.schema.yaml index 989bf11dd..bc0a1f9ea 100644 --- a/src/modules/packages/packages.schema.yaml +++ b/src/modules/packages/packages.schema.yaml @@ -26,6 +26,10 @@ properties: update_system: { type: boolean, default: false } skip_if_no_internet: { type: boolean, default: false } + pacman_num_retries: { type: integer, default: 0 } + pacman_disable_download_timeout: { type: boolean, default: false } + pacman_needed_only: { type: boolean, default: false } + operations: type: array items: From e1e29780f2447f30e40e2c35711d2ddf6a3cb1ea Mon Sep 17 00:00:00 2001 From: dalto Date: Thu, 25 Nov 2021 15:55:22 -0600 Subject: [PATCH 107/127] [packages] Change callbacks --- src/modules/packages/main.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/modules/packages/main.py b/src/modules/packages/main.py index 3dd93576a..a0033cf4f 100644 --- a/src/modules/packages/main.py +++ b/src/modules/packages/main.py @@ -406,13 +406,13 @@ class PMPacman(PackageManager): # These are globals self.progress_fraction = (completed_packages * 1.0 / total_packages) - def run_pacman(self, command): + def run_pacman(self, command, callback=None): # Call pacman in a loop until it is successful or the number of retries is exceeded pacman_count = 0 while pacman_count <= self.pacman_num_retries: pacman_count += 1 try: - libcalamares.utils.target_env_process_output(command, self.line_cb) + libcalamares.utils.target_env_process_output(command, callback) return except subprocess.CalledProcessError: if pacman_count <= self.pacman_num_retries: @@ -438,13 +438,13 @@ class PMPacman(PackageManager): command += pkgs - self.run_pacman(command) + self.run_pacman(command, self.line_cb) self.reset_progress() def remove(self, pkgs): self.reset_progress() - self.run_pacman(["pacman", "-Rs", "--noconfirm"] + pkgs) + self.run_pacman(["pacman", "-Rs", "--noconfirm"] + pkgs, self.line_cb) def update_db(self): self.run_pacman(["pacman", "-Sy"]) @@ -454,7 +454,7 @@ class PMPacman(PackageManager): if self.pacman_disable_timeout is True: command.append("--disable-download-timeout") - self.run_pacman(command) + self.run_pacman(command, self.line_cb) class PMPamac(PackageManager): From c80b4ff4c2f7ecae7cbf46244187c3808e70c779 Mon Sep 17 00:00:00 2001 From: dalto Date: Fri, 26 Nov 2021 08:30:18 -0600 Subject: [PATCH 108/127] [packages] Make callback logic more sensible --- src/modules/packages/main.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/modules/packages/main.py b/src/modules/packages/main.py index a0033cf4f..93f5dac94 100644 --- a/src/modules/packages/main.py +++ b/src/modules/packages/main.py @@ -406,13 +406,17 @@ class PMPacman(PackageManager): # These are globals self.progress_fraction = (completed_packages * 1.0 / total_packages) - def run_pacman(self, command, callback=None): + def run_pacman(self, command, callback=False): # Call pacman in a loop until it is successful or the number of retries is exceeded pacman_count = 0 while pacman_count <= self.pacman_num_retries: pacman_count += 1 try: - libcalamares.utils.target_env_process_output(command, callback) + if callback is True: + libcalamares.utils.target_env_process_output(command, self.line_cb) + else: + libcalamares.utils.target_env_process_output(command) + return except subprocess.CalledProcessError: if pacman_count <= self.pacman_num_retries: @@ -438,13 +442,13 @@ class PMPacman(PackageManager): command += pkgs - self.run_pacman(command, self.line_cb) + self.run_pacman(command, True) self.reset_progress() def remove(self, pkgs): self.reset_progress() - self.run_pacman(["pacman", "-Rs", "--noconfirm"] + pkgs, self.line_cb) + self.run_pacman(["pacman", "-Rs", "--noconfirm"] + pkgs, True) def update_db(self): self.run_pacman(["pacman", "-Sy"]) @@ -454,7 +458,7 @@ class PMPacman(PackageManager): if self.pacman_disable_timeout is True: command.append("--disable-download-timeout") - self.run_pacman(command, self.line_cb) + self.run_pacman(command, True) class PMPamac(PackageManager): From bb24ee1b3b5cff8ad4376d736be3553b2422872d Mon Sep 17 00:00:00 2001 From: dalto Date: Fri, 26 Nov 2021 10:56:32 -0600 Subject: [PATCH 109/127] [packages] Fix location of call to reset_progress() --- src/modules/packages/main.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/modules/packages/main.py b/src/modules/packages/main.py index 93f5dac94..688cf6340 100644 --- a/src/modules/packages/main.py +++ b/src/modules/packages/main.py @@ -442,9 +442,8 @@ class PMPacman(PackageManager): command += pkgs - self.run_pacman(command, True) - self.reset_progress() + self.run_pacman(command, True) def remove(self, pkgs): self.reset_progress() @@ -458,7 +457,7 @@ class PMPacman(PackageManager): if self.pacman_disable_timeout is True: command.append("--disable-download-timeout") - self.run_pacman(command, True) + self.run_pacman(command) class PMPamac(PackageManager): From df3e049e1c2969b5053d7098a6412b1dc838779b Mon Sep 17 00:00:00 2001 From: dalto Date: Fri, 26 Nov 2021 12:36:15 -0600 Subject: [PATCH 110/127] [fstab] Use different options for the btrfs swap subvolume --- src/modules/fstab/fstab.conf | 4 ++++ src/modules/fstab/fstab.schema.yaml | 1 + src/modules/fstab/main.py | 5 ++++- 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/modules/fstab/fstab.conf b/src/modules/fstab/fstab.conf index 385161d2d..c05f1929a 100644 --- a/src/modules/fstab/fstab.conf +++ b/src/modules/fstab/fstab.conf @@ -15,9 +15,13 @@ # With kernels 5.15 and newer be cautious of adding the option space_cache # to the btrfs mount options. The default in 5.15 changed to space_cache=v2. # If space_cache or space_cache=v1 are specified, it may fail to remount. +# +# btrfs_swap options are used when a swapfile is chosen with a btrfs root +# the options are applied to the subvolume which holds the swap partition mountOptions: default: defaults,noatime btrfs: defaults,noatime,autodefrag,compress=zstd + btrfs_swap: defaults,noatime # Mount options to use for the EFI System Partition. If not defined, the # *mountOptions* for *vfat* are used, or if that is not set either, diff --git a/src/modules/fstab/fstab.schema.yaml b/src/modules/fstab/fstab.schema.yaml index fc68fd2c5..087e82cac 100644 --- a/src/modules/fstab/fstab.schema.yaml +++ b/src/modules/fstab/fstab.schema.yaml @@ -22,6 +22,7 @@ properties: xfs: { type: string } swap: { type: string } btrfs: { type: string } + btrfs_swap: { type: string } efiMountOptions: { type: string } crypttabOptions: { type: string } required: [ mountOptions ] diff --git a/src/modules/fstab/main.py b/src/modules/fstab/main.py index 3a2dbcf41..55e9b3dc6 100644 --- a/src/modules/fstab/main.py +++ b/src/modules/fstab/main.py @@ -236,7 +236,10 @@ class FstabGenerator(object): libcalamares.utils.debug("Ignoring foreign swap {!s} {!s}".format(disk_name, partition.get("uuid", None))) return None - options = self.get_mount_options(filesystem, mount_point) + if filesystem == "btrfs" and partition["subvol"] == "/@swap": + options = self.get_mount_options("btrfs_swap", mount_point) + else: + options = self.get_mount_options(filesystem, mount_point) if is_ssd: extra = self.ssd_extra_mount_options.get(filesystem) From 6b838bbf3dfa597e5b1a2f602ca1a5203334b33b Mon Sep 17 00:00:00 2001 From: dalto Date: Fri, 26 Nov 2021 13:00:30 -0600 Subject: [PATCH 111/127] [fstab] Add comment explaining logic --- src/modules/fstab/main.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/modules/fstab/main.py b/src/modules/fstab/main.py index 55e9b3dc6..261d3cadb 100644 --- a/src/modules/fstab/main.py +++ b/src/modules/fstab/main.py @@ -236,6 +236,7 @@ class FstabGenerator(object): libcalamares.utils.debug("Ignoring foreign swap {!s} {!s}".format(disk_name, partition.get("uuid", None))) return None + # If this is btrfs subvol a dedicated to a swapfile, use different options than a normal btrfs subvol if filesystem == "btrfs" and partition["subvol"] == "/@swap": options = self.get_mount_options("btrfs_swap", mount_point) else: From bd07db544f693168cb12463804dd1fa3ccbd522f Mon Sep 17 00:00:00 2001 From: dalto Date: Sun, 28 Nov 2021 15:56:16 -0600 Subject: [PATCH 112/127] [packages] Update doumentation for run_pacman() to be more complete --- src/modules/packages/main.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/modules/packages/main.py b/src/modules/packages/main.py index 688cf6340..7be143363 100644 --- a/src/modules/packages/main.py +++ b/src/modules/packages/main.py @@ -407,7 +407,13 @@ class PMPacman(PackageManager): self.progress_fraction = (completed_packages * 1.0 / total_packages) def run_pacman(self, command, callback=False): - # Call pacman in a loop until it is successful or the number of retries is exceeded + """ + Call pacman in a loop until it is successful or the number of retries is exceeded + :param command: The pacman command to run + :param callback: An optional boolean that indicates if this pacman run should use the callback + :return: + """ + pacman_count = 0 while pacman_count <= self.pacman_num_retries: pacman_count += 1 From 8a1e5d35fa1bada125c96bc73dbea07ea25825ae Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Mon, 29 Nov 2021 11:59:09 +0100 Subject: [PATCH 113/127] [packages] Move pacman-options into their own key with subkeys --- src/modules/packages/main.py | 12 +++++++++--- src/modules/packages/packages.conf | 19 ++++++++++--------- src/modules/packages/packages.schema.yaml | 10 +++++++--- 3 files changed, 26 insertions(+), 15 deletions(-) diff --git a/src/modules/packages/main.py b/src/modules/packages/main.py index 7be143363..518ff4907 100644 --- a/src/modules/packages/main.py +++ b/src/modules/packages/main.py @@ -397,9 +397,15 @@ class PMPacman(PackageManager): self.in_package_changes = False self.line_cb = line_cb - self.pacman_num_retries = libcalamares.job.configuration.get("pacman_num_retries", 0) - self.pacman_disable_timeout = libcalamares.job.configuration.get("pacman_disable_download_timeout", False) - self.pacman_needed_only = libcalamares.job.configuration.get("pacman_needed_only", False) + pacman = libcalamares.job.configuration.get("pacman", None) + if pacman is None: + pacman = dict() + if type(pacman) is not dict: + libcalamares.utils.warning("Job configuration *pacman* will be ignored.") + pacman = dict() + self.pacman_num_retries = pacman.get("pacman_num_retries", 0) + self.pacman_disable_timeout = pacman.get("pacman_disable_download_timeout", False) + self.pacman_needed_only = pacman.get("pacman_needed_only", False) def reset_progress(self): self.in_package_changes = False diff --git a/src/modules/packages/packages.conf b/src/modules/packages/packages.conf index 8d76060a0..6e62f4b5f 100644 --- a/src/modules/packages/packages.conf +++ b/src/modules/packages/packages.conf @@ -64,19 +64,20 @@ update_system: false # pacman specific options # -# pacman_num_retries should be a positive integer which sepcifies the +# *num_retries* should be a positive integer which specifies the # number of times the call to pacman will be retried in the event of a -# failure. If it is missing, it will be set to 0 +# failure. If it is missing, it will be set to 0. # -# pacman_disable_download_timeout is a boolean that, when true, includes +# *disable_download_timeout* is a boolean that, when true, includes # the flag --disable-download-timeout on calls to pacman. When missing, -# false is assumed +# false is assumed. # -# pacman_needed_only is a boolean that includes the pacman argument --needed -# when set to true. If missing, false is assumed -pacman_num_retries: 0 -pacman_disable_download_timeout: false -pacman_needed_only: false +# *needed_only* is a boolean that includes the pacman argument --needed +# when set to true. If missing, false is assumed. +pacman: + num_retries: 0 + disable_download_timeout: false + needed_only: false # # List of maps with package operations such as install or remove. diff --git a/src/modules/packages/packages.schema.yaml b/src/modules/packages/packages.schema.yaml index bc0a1f9ea..d12f0507e 100644 --- a/src/modules/packages/packages.schema.yaml +++ b/src/modules/packages/packages.schema.yaml @@ -26,9 +26,13 @@ properties: update_system: { type: boolean, default: false } skip_if_no_internet: { type: boolean, default: false } - pacman_num_retries: { type: integer, default: 0 } - pacman_disable_download_timeout: { type: boolean, default: false } - pacman_needed_only: { type: boolean, default: false } + pacman: + additionalProperties: false + type: object + properties: + num_retries: { type: integer, default: 0 } + disable_download_timeout: { type: boolean, default: false } + needed_only: { type: boolean, default: false } operations: type: array From e9970474f5591e2c3c39531359ee4f3eac5a2721 Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Mon, 29 Nov 2021 12:22:02 +0100 Subject: [PATCH 114/127] [libcalamares] Allow Python to log an Error as well --- src/libcalamares/PythonJobApi.cpp | 15 +++++++++++++-- src/libcalamares/PythonJobApi.h | 1 + 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/libcalamares/PythonJobApi.cpp b/src/libcalamares/PythonJobApi.cpp index 1713569a4..b1ff536be 100644 --- a/src/libcalamares/PythonJobApi.cpp +++ b/src/libcalamares/PythonJobApi.cpp @@ -139,17 +139,28 @@ check_target_env_output( const bp::list& args, const std::string& stdin, int tim } static const char output_prefix[] = "[PYTHON JOB]:"; +static inline void +log_action( unsigned int level, const std::string& s ) +{ + Logger::CDebug( level ) << output_prefix << QString::fromStdString( s ); +} void debug( const std::string& s ) { - Logger::CDebug( Logger::LOGDEBUG ) << output_prefix << QString::fromStdString( s ); + log_action( Logger::LOGDEBUG, s ); } void warning( const std::string& s ) { - Logger::CDebug( Logger::LOGWARNING ) << output_prefix << QString::fromStdString( s ); + log_action( Logger::LOGWARNING, s ); +} + +void +error( const std::string& s ) +{ + log_action( Logger::LOGERROR, s ); } PythonJobInterface::PythonJobInterface( Calamares::PythonJob* parent ) diff --git a/src/libcalamares/PythonJobApi.h b/src/libcalamares/PythonJobApi.h index 48bd4f87c..0ecc43abd 100644 --- a/src/libcalamares/PythonJobApi.h +++ b/src/libcalamares/PythonJobApi.h @@ -60,6 +60,7 @@ boost::python::list gettext_languages(); void debug( const std::string& s ); void warning( const std::string& s ); +void error( const std::string& s ); class PythonJobInterface { From 7f643010b259017bc7af90186b3f25d7f6fade93 Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Mon, 29 Nov 2021 12:26:07 +0100 Subject: [PATCH 115/127] [libcalamares] Expose error() and warn() to Python --- src/libcalamares/PythonJob.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/libcalamares/PythonJob.cpp b/src/libcalamares/PythonJob.cpp index afeebbe07..6176f0312 100644 --- a/src/libcalamares/PythonJob.cpp +++ b/src/libcalamares/PythonJob.cpp @@ -85,6 +85,12 @@ BOOST_PYTHON_MODULE( libcalamares ) &CalamaresPython::warning, bp::args( "s" ), "Writes the given string to the Calamares warning stream." ); + bp::def( "warn", + &CalamaresPython::warning, + bp::args( "s" ), + "Writes the given string to the Calamares warning stream." ); + bp::def( + "error", &CalamaresPython::warning, bp::args( "s" ), "Writes the given string to the Calamares error stream." ); bp::def( "mount", &CalamaresPython::mount, From fcbe8d3a3e6dae3de9c08eaf292b5c9093aac395 Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Mon, 29 Nov 2021 12:46:12 +0100 Subject: [PATCH 116/127] [libcalamares] API for YAML-loading from Python --- src/libcalamares/PythonJob.cpp | 11 ++++++++++- src/libcalamares/PythonJobApi.cpp | 8 ++++++++ src/libcalamares/PythonJobApi.h | 5 +++++ 3 files changed, 23 insertions(+), 1 deletion(-) diff --git a/src/libcalamares/PythonJob.cpp b/src/libcalamares/PythonJob.cpp index 6176f0312..291adbc54 100644 --- a/src/libcalamares/PythonJob.cpp +++ b/src/libcalamares/PythonJob.cpp @@ -79,6 +79,7 @@ BOOST_PYTHON_MODULE( libcalamares ) bp::scope utilsScope = utilsModule; Q_UNUSED( utilsScope ) + // .. Logging functions bp::def( "debug", &CalamaresPython::debug, bp::args( "s" ), "Writes the given string to the Calamares debug stream." ); bp::def( "warning", @@ -92,6 +93,11 @@ BOOST_PYTHON_MODULE( libcalamares ) bp::def( "error", &CalamaresPython::warning, bp::args( "s" ), "Writes the given string to the Calamares error stream." ); + + // .. YAML functions + bp::def( "load_yaml", &CalamaresPython::load_yaml, bp::args( "path" ), "Loads YAML from a file." ); + + // .. Filesystem functions bp::def( "mount", &CalamaresPython::mount, mount_overloads( bp::args( "device_path", "mount_point", "filesystem_name", "options" ), @@ -100,6 +106,8 @@ BOOST_PYTHON_MODULE( libcalamares ) "-1 = QProcess crash\n" "-2 = QProcess cannot start\n" "-3 = bad arguments" ) ); + + // .. Process functions bp::def( "target_env_call", static_cast< int ( * )( const std::string&, const std::string&, int ) >( &CalamaresPython::target_env_call ), @@ -158,6 +166,7 @@ BOOST_PYTHON_MODULE( libcalamares ) host_env_process_output_overloads( bp::args( "command", "callback", "stdin", "timeout" ), "Runs the specified command in the host system." ) ); + // .. String functions bp::def( "obscure", &CalamaresPython::obscure, bp::args( "s" ), @@ -166,7 +175,7 @@ BOOST_PYTHON_MODULE( libcalamares ) "Applying the function to a string obscured by this function will result " "in the original string." ); - + // .. Translation functions bp::def( "gettext_languages", &CalamaresPython::gettext_languages, "Returns list of languages (most to least-specific) for gettext." ); diff --git a/src/libcalamares/PythonJobApi.cpp b/src/libcalamares/PythonJobApi.cpp index b1ff536be..869bda81e 100644 --- a/src/libcalamares/PythonJobApi.cpp +++ b/src/libcalamares/PythonJobApi.cpp @@ -19,6 +19,7 @@ #include "utils/RAII.h" #include "utils/Runner.h" #include "utils/String.h" +#include "utils/Yaml.h" #include #include @@ -163,6 +164,13 @@ error( const std::string& s ) log_action( Logger::LOGERROR, s ); } +boost::python::dict +load_yaml( const std::string& path ) +{ + return variantMapToPyDict( CalamaresUtils::loadYaml( QString::fromStdString( path ) ) ); +} + + PythonJobInterface::PythonJobInterface( Calamares::PythonJob* parent ) : m_parent( parent ) { diff --git a/src/libcalamares/PythonJobApi.h b/src/libcalamares/PythonJobApi.h index 0ecc43abd..62346ceda 100644 --- a/src/libcalamares/PythonJobApi.h +++ b/src/libcalamares/PythonJobApi.h @@ -62,6 +62,11 @@ void debug( const std::string& s ); void warning( const std::string& s ); void error( const std::string& s ); +/** @brief Loads YAML and returns (nested) dicts representing it + * + */ +boost::python::dict load_yaml( const std::string& path ); + class PythonJobInterface { public: From 3e0c9ba0569eef55af35437cae9712a9030f4006 Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Mon, 29 Nov 2021 13:04:44 +0100 Subject: [PATCH 117/127] [packages] Expand tests with PM-specific bits --- src/modules/packages/tests/1.global | 3 +++ .../packages/{test.yaml => tests/2.job} | 0 src/modules/packages/tests/CMakeTests.txt | 13 +++++++++++ src/modules/packages/tests/test-pm-pacman.py | 22 +++++++++++++++++++ 4 files changed, 38 insertions(+) create mode 100644 src/modules/packages/tests/1.global rename src/modules/packages/{test.yaml => tests/2.job} (100%) create mode 100644 src/modules/packages/tests/CMakeTests.txt create mode 100644 src/modules/packages/tests/test-pm-pacman.py diff --git a/src/modules/packages/tests/1.global b/src/modules/packages/tests/1.global new file mode 100644 index 000000000..ee06ccfe1 --- /dev/null +++ b/src/modules/packages/tests/1.global @@ -0,0 +1,3 @@ +# SPDX-FileCopyrightText: no +# SPDX-License-Identifier: CC0-1.0 +rootMountPoint: /tmp diff --git a/src/modules/packages/test.yaml b/src/modules/packages/tests/2.job similarity index 100% rename from src/modules/packages/test.yaml rename to src/modules/packages/tests/2.job diff --git a/src/modules/packages/tests/CMakeTests.txt b/src/modules/packages/tests/CMakeTests.txt new file mode 100644 index 000000000..6e343533c --- /dev/null +++ b/src/modules/packages/tests/CMakeTests.txt @@ -0,0 +1,13 @@ +# SPDX-FileCopyrightText: no +# SPDX-License-Identifier: CC0-1.0 +# +# We have tests to load (some) of the package-managers specifically, to +# test their configuration code and implementation. Those tests conventionally +# live in Python files here in the tests/ directory. Add them. +foreach(_pm pacman) + add_test( + NAME configure-packages-${_pm} + COMMAND env PYTHONPATH=.: python3 ${CMAKE_CURRENT_LIST_DIR}/test-pm-${_pm}.py + WORKING_DIRECTORY ${CMAKE_BINARY_DIR} + ) +endforeach() diff --git a/src/modules/packages/tests/test-pm-pacman.py b/src/modules/packages/tests/test-pm-pacman.py new file mode 100644 index 000000000..aa0df2f7e --- /dev/null +++ b/src/modules/packages/tests/test-pm-pacman.py @@ -0,0 +1,22 @@ +# SPDX-FileCopyrightText: no +# SPDX-License-Identifier: CC0-1.0 +# +# Calamares Boilerplate +import libcalamares +libcalamares.globalstorage = libcalamares.GlobalStorage(None) +libcalamares.globalstorage.insert("testing", True) + +# Module prep-work +from src.modules.packages import main + +# .. we don't have a job in this test, so fake one +class Job(object): + def __init__(self): + self.configuration = libcalamares.utils.load_yaml("pm-pacman.yaml") +libcalamares.job = Job() + +# Specific PM test +p = main.PMPacman() +assert p.pacman_num_retries == 0 +assert p.pacman_disable_timeout == False +assert p.pacman_needed_only == False From 1260d3fcb9e67c6d59707fc21d8ebb01fb4bc820 Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Mon, 29 Nov 2021 13:21:50 +0100 Subject: [PATCH 118/127] [packages] Expand tests for PM-specifics more --- src/modules/packages/tests/CMakeTests.txt | 25 +++++++++++++------ src/modules/packages/tests/pm-pacman-1.yaml | 11 +++++++++ src/modules/packages/tests/pm-pacman-2.yaml | 10 ++++++++ src/modules/packages/tests/test-pm-pacman.py | 26 +++++++++++++++----- 4 files changed, 59 insertions(+), 13 deletions(-) create mode 100644 src/modules/packages/tests/pm-pacman-1.yaml create mode 100644 src/modules/packages/tests/pm-pacman-2.yaml diff --git a/src/modules/packages/tests/CMakeTests.txt b/src/modules/packages/tests/CMakeTests.txt index 6e343533c..9ee9c6682 100644 --- a/src/modules/packages/tests/CMakeTests.txt +++ b/src/modules/packages/tests/CMakeTests.txt @@ -4,10 +4,21 @@ # We have tests to load (some) of the package-managers specifically, to # test their configuration code and implementation. Those tests conventionally # live in Python files here in the tests/ directory. Add them. -foreach(_pm pacman) - add_test( - NAME configure-packages-${_pm} - COMMAND env PYTHONPATH=.: python3 ${CMAKE_CURRENT_LIST_DIR}/test-pm-${_pm}.py - WORKING_DIRECTORY ${CMAKE_BINARY_DIR} - ) -endforeach() + +# Pacman (Arch) tests +set(_pm pacman) +add_test( + NAME configure-packages-${_pm} + COMMAND env PYTHONPATH=.: python3 ${CMAKE_CURRENT_LIST_DIR}/test-pm-${_pm}.py + WORKING_DIRECTORY ${CMAKE_BINARY_DIR} +) +add_test( + NAME configure-packages-${_pm}-ops-1 + COMMAND env PYTHONPATH=.: python3 ${CMAKE_CURRENT_LIST_DIR}/test-pm-${_pm}.py ${CMAKE_CURRENT_LIST_DIR}/pm-pacman-1.yaml 4 1 1 + WORKING_DIRECTORY ${CMAKE_BINARY_DIR} +) +add_test( + NAME configure-packages-${_pm}-ops-2 + COMMAND env PYTHONPATH=.: python3 ${CMAKE_CURRENT_LIST_DIR}/test-pm-${_pm}.py ${CMAKE_CURRENT_LIST_DIR}/pm-pacman-2.yaml 3 0 0 + WORKING_DIRECTORY ${CMAKE_BINARY_DIR} +) diff --git a/src/modules/packages/tests/pm-pacman-1.yaml b/src/modules/packages/tests/pm-pacman-1.yaml new file mode 100644 index 000000000..1ad048b61 --- /dev/null +++ b/src/modules/packages/tests/pm-pacman-1.yaml @@ -0,0 +1,11 @@ +# SPDX-FileCopyrightText: no +# SPDX-License-Identifier: CC0-1.0 +backend: pacman +rootMountPoint: /tmp/mount +operations: [] + +pacman: + num_retries: 4 + disable_timeout: yes + needed_only: true + diff --git a/src/modules/packages/tests/pm-pacman-2.yaml b/src/modules/packages/tests/pm-pacman-2.yaml new file mode 100644 index 000000000..2bbf70758 --- /dev/null +++ b/src/modules/packages/tests/pm-pacman-2.yaml @@ -0,0 +1,10 @@ +# SPDX-FileCopyrightText: no +# SPDX-License-Identifier: CC0-1.0 +backend: pacman +rootMountPoint: /tmp/mount +operations: [] + +# Leave some things unspecified +pacman: + num_retries: 3 + diff --git a/src/modules/packages/tests/test-pm-pacman.py b/src/modules/packages/tests/test-pm-pacman.py index aa0df2f7e..f57e2a761 100644 --- a/src/modules/packages/tests/test-pm-pacman.py +++ b/src/modules/packages/tests/test-pm-pacman.py @@ -11,12 +11,26 @@ from src.modules.packages import main # .. we don't have a job in this test, so fake one class Job(object): - def __init__(self): - self.configuration = libcalamares.utils.load_yaml("pm-pacman.yaml") -libcalamares.job = Job() + def __init__(self, filename): + self.configuration = libcalamares.utils.load_yaml(filename) if filename is not None else dict() + +import sys +if len(sys.argv) > 4: + filename = sys.argv[1] + retry = int(sys.argv[2]) + timeout = bool(int(sys.argv[3])) + needed = bool(int(sys.argv[4])) +else: + filename = None + retry = 0 + timeout = False + needed = False + +libcalamares.utils.warning("Expecting {!s} retry={!s} timeout={!s} needed={!s}".format(filename, retry, timeout, needed)) # Specific PM test +libcalamares.job = Job(filename) p = main.PMPacman() -assert p.pacman_num_retries == 0 -assert p.pacman_disable_timeout == False -assert p.pacman_needed_only == False +assert p.pacman_num_retries == retry, "{!r} vs {!r}".format(p.pacman_num_retries, retry) +assert p.pacman_disable_timeout == timeout +assert p.pacman_needed_only == needed From 65488ca174b3cddeb6cba1a6eaca19e871bacb50 Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Mon, 29 Nov 2021 14:19:06 +0100 Subject: [PATCH 119/127] [libcalamares] More verbose when loading YAML for Python --- src/libcalamares/PythonJobApi.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/libcalamares/PythonJobApi.cpp b/src/libcalamares/PythonJobApi.cpp index 869bda81e..bb2b8749e 100644 --- a/src/libcalamares/PythonJobApi.cpp +++ b/src/libcalamares/PythonJobApi.cpp @@ -167,7 +167,14 @@ error( const std::string& s ) boost::python::dict load_yaml( const std::string& path ) { - return variantMapToPyDict( CalamaresUtils::loadYaml( QString::fromStdString( path ) ) ); + const QString filePath = QString::fromStdString( path ); + bool ok = false; + auto map = CalamaresUtils::loadYaml( filePath, &ok ); + if ( !ok ) + { + cWarning() << "Loading YAML from" << filePath << "failed."; + } + return variantMapToPyDict( map ); } From 474aaf7603b2a3322735c6b96b71501be4cafb8e Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Mon, 29 Nov 2021 14:23:15 +0100 Subject: [PATCH 120/127] [packages] Fix loading of the subkeys for pacman --- src/modules/packages/main.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/modules/packages/main.py b/src/modules/packages/main.py index 518ff4907..10371777e 100644 --- a/src/modules/packages/main.py +++ b/src/modules/packages/main.py @@ -403,9 +403,9 @@ class PMPacman(PackageManager): if type(pacman) is not dict: libcalamares.utils.warning("Job configuration *pacman* will be ignored.") pacman = dict() - self.pacman_num_retries = pacman.get("pacman_num_retries", 0) - self.pacman_disable_timeout = pacman.get("pacman_disable_download_timeout", False) - self.pacman_needed_only = pacman.get("pacman_needed_only", False) + self.pacman_num_retries = pacman.get("num_retries", 0) + self.pacman_disable_timeout = pacman.get("disable_download_timeout", False) + self.pacman_needed_only = pacman.get("needed_only", False) def reset_progress(self): self.in_package_changes = False From 61e0d538e9f0b83f66b11f3c63d55b8495eb8ac2 Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Mon, 29 Nov 2021 14:44:12 +0100 Subject: [PATCH 121/127] [packages] Be more explicit in test failures, fix test data --- src/modules/packages/tests/pm-pacman-1.yaml | 2 +- src/modules/packages/tests/test-pm-pacman.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/modules/packages/tests/pm-pacman-1.yaml b/src/modules/packages/tests/pm-pacman-1.yaml index 1ad048b61..3fa75d481 100644 --- a/src/modules/packages/tests/pm-pacman-1.yaml +++ b/src/modules/packages/tests/pm-pacman-1.yaml @@ -6,6 +6,6 @@ operations: [] pacman: num_retries: 4 - disable_timeout: yes + disable_download_timeout: yes needed_only: true diff --git a/src/modules/packages/tests/test-pm-pacman.py b/src/modules/packages/tests/test-pm-pacman.py index f57e2a761..ee814b620 100644 --- a/src/modules/packages/tests/test-pm-pacman.py +++ b/src/modules/packages/tests/test-pm-pacman.py @@ -32,5 +32,5 @@ libcalamares.utils.warning("Expecting {!s} retry={!s} timeout={!s} needed={!s}". libcalamares.job = Job(filename) p = main.PMPacman() assert p.pacman_num_retries == retry, "{!r} vs {!r}".format(p.pacman_num_retries, retry) -assert p.pacman_disable_timeout == timeout -assert p.pacman_needed_only == needed +assert p.pacman_disable_timeout == timeout, "{!r} vs {!r}".format(p.pacman_disable_timeout, timeout) +assert p.pacman_needed_only == needed, "{!r} vs {!r}".format(p.pacman_needed_only, needed) From 864dcdb2c2c94f2d485510edb9a9384f5f624f85 Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Mon, 29 Nov 2021 14:45:30 +0100 Subject: [PATCH 122/127] FreeBSD: Calamares is in the official ports-tree already --- data/FreeBSD/Makefile | 37 ------- data/FreeBSD/distinfo | 3 - data/FreeBSD/pkg-descr | 14 --- data/FreeBSD/pkg-plist | 224 ----------------------------------------- 4 files changed, 278 deletions(-) delete mode 100644 data/FreeBSD/Makefile delete mode 100644 data/FreeBSD/distinfo delete mode 100644 data/FreeBSD/pkg-descr delete mode 100644 data/FreeBSD/pkg-plist diff --git a/data/FreeBSD/Makefile b/data/FreeBSD/Makefile deleted file mode 100644 index d9b7e5043..000000000 --- a/data/FreeBSD/Makefile +++ /dev/null @@ -1,37 +0,0 @@ -# $FreeBSD$ -# -# SPDX-FileCopyrightText: 2020 Adriaan de Groot -# SPDX-License-Identifier: BSD-2-Clause - -PORTNAME= calamares -DISTVERSION= 3.2.25 -CATEGORIES= sysutils -MASTER_SITES= https://github.com/${PORTNAME}/${PORTNAME}/releases/download/v${DISTVERSION}/ - -MAINTAINER= adridg@FreeBSD.org -COMMENT= GUI System installer and OEM configurator - -LICENSE= GPLv3 -LICENSE_FILE= ${WRKSRC}/LICENSE - -LIB_DEPENDS= libyaml-cpp.so:devel/yaml-cpp \ - libpwquality.so:security/libpwquality \ - libboost_python${PYTHON_SUFFIX}.so:devel/boost-python-libs - -USES= cmake compiler:c++17-lang gettext kde:5 pkgconfig \ - python:3.3+ qt:5 -USE_QT= concurrent core dbus declarative gui \ - network quickcontrols2 svg widgets xml \ - buildtools_build linguist_build qmake_build -USE_KDE= coreaddons dbusaddons parts service \ - ecm_build -USE_LDCONFIG= yes - -CMAKE_OFF= WITH_KF5Crash \ - INSTALL_CONFIG \ - INSTALL_COMPLETION \ - INSTALL_POLKIT -CMAKE_ON= CMAKE_DISABLE_FIND_PACKAGE_KPMcore -CMAKE_ARGS= -DSKIP_MODULES="webview" - -.include diff --git a/data/FreeBSD/distinfo b/data/FreeBSD/distinfo deleted file mode 100644 index e333963a8..000000000 --- a/data/FreeBSD/distinfo +++ /dev/null @@ -1,3 +0,0 @@ -TIMESTAMP = 1592339404 -SHA256 (calamares-3.2.25.tar.gz) = 797ce33db7d4e4c06bbccef95f6c4023f7628e91bd142896695565fed4ae8c4b -SIZE (calamares-3.2.25.tar.gz) = 3580197 diff --git a/data/FreeBSD/pkg-descr b/data/FreeBSD/pkg-descr deleted file mode 100644 index 39cb4335c..000000000 --- a/data/FreeBSD/pkg-descr +++ /dev/null @@ -1,14 +0,0 @@ -Calamares is an installer framework. By design it is very customizable, -in order to satisfy a wide variety of needs and use cases. - -Calamares aims to be easy, usable, beautiful, pragmatic, inclusive and -distribution-agnostic. - -Got a Linux distribution but no system installer? Grab Calamares, mix -and match any number of Calamares modules (or write your own in Python -or C++), throw together some branding, package it up and you are ready -to ship! - -(The above applies to FreeBSD as well) - -WWW: https://calamares.io/ diff --git a/data/FreeBSD/pkg-plist b/data/FreeBSD/pkg-plist deleted file mode 100644 index 7f588b7a9..000000000 --- a/data/FreeBSD/pkg-plist +++ /dev/null @@ -1,224 +0,0 @@ -bin/calamares -include/libcalamares/CalamaresConfig.h -include/libcalamares/CppJob.h -include/libcalamares/DllMacro.h -include/libcalamares/GlobalStorage.h -include/libcalamares/Job.h -include/libcalamares/JobExample.h -include/libcalamares/JobQueue.h -include/libcalamares/ProcessJob.h -include/libcalamares/PythonHelper.h -include/libcalamares/PythonJob.h -include/libcalamares/PythonJobApi.h -include/libcalamares/Settings.h -include/libcalamares/utils/BoostPython.h -include/libcalamares/utils/CalamaresUtilsSystem.h -include/libcalamares/utils/CommandList.h -include/libcalamares/utils/Dirs.h -include/libcalamares/utils/Entropy.h -include/libcalamares/utils/Logger.h -include/libcalamares/utils/NamedEnum.h -include/libcalamares/utils/NamedSuffix.h -include/libcalamares/utils/PluginFactory.h -include/libcalamares/utils/RAII.h -include/libcalamares/utils/Retranslator.h -include/libcalamares/utils/String.h -include/libcalamares/utils/Tests.h -include/libcalamares/utils/UMask.h -include/libcalamares/utils/Units.h -include/libcalamares/utils/Variant.h -include/libcalamares/utils/Yaml.h -include/libcalamares/utils/moc-warnings.h -lib/calamares/libcalamares.so -lib/calamares/modules/bootloader/main.py -lib/calamares/modules/bootloader/module.desc -lib/calamares/modules/bootloader/test.yaml -lib/calamares/modules/contextualprocess/libcalamares_job_contextualprocess.so -lib/calamares/modules/contextualprocess/module.desc -lib/calamares/modules/displaymanager/main.py -lib/calamares/modules/displaymanager/module.desc -lib/calamares/modules/dracut/main.py -lib/calamares/modules/dracut/module.desc -lib/calamares/modules/dracutlukscfg/libcalamares_job_dracutlukscfg.so -lib/calamares/modules/dracutlukscfg/module.desc -lib/calamares/modules/dummycpp/libcalamares_job_dummycpp.so -lib/calamares/modules/dummycpp/module.desc -lib/calamares/modules/dummyprocess/module.desc -lib/calamares/modules/dummypython/main.py -lib/calamares/modules/dummypython/module.desc -lib/calamares/modules/finished/libcalamares_viewmodule_finished.so -lib/calamares/modules/finished/module.desc -lib/calamares/modules/fstab/main.py -lib/calamares/modules/fstab/module.desc -lib/calamares/modules/fstab/test.yaml -lib/calamares/modules/grubcfg/main.py -lib/calamares/modules/grubcfg/module.desc -lib/calamares/modules/hostinfo/libcalamares_job_hostinfo.so -lib/calamares/modules/hostinfo/module.desc -lib/calamares/modules/hwclock/main.py -lib/calamares/modules/hwclock/module.desc -lib/calamares/modules/initcpio/libcalamares_job_initcpio.so -lib/calamares/modules/initcpio/module.desc -lib/calamares/modules/initcpiocfg/main.py -lib/calamares/modules/initcpiocfg/module.desc -lib/calamares/modules/initramfs/libcalamares_job_initramfs.so -lib/calamares/modules/initramfs/module.desc -lib/calamares/modules/initramfscfg/encrypt_hook -lib/calamares/modules/initramfscfg/encrypt_hook_nokey -lib/calamares/modules/initramfscfg/main.py -lib/calamares/modules/initramfscfg/module.desc -lib/calamares/modules/interactiveterminal/libcalamares_viewmodule_interactiveterminal.so -lib/calamares/modules/interactiveterminal/module.desc -lib/calamares/modules/keyboard/libcalamares_viewmodule_keyboard.so -lib/calamares/modules/keyboard/module.desc -lib/calamares/modules/keyboardq/libcalamares_viewmodule_keyboardq.so -lib/calamares/modules/keyboardq/module.desc -lib/calamares/modules/license/libcalamares_viewmodule_license.so -lib/calamares/modules/license/module.desc -lib/calamares/modules/locale/libcalamares_viewmodule_locale.so -lib/calamares/modules/locale/module.desc -lib/calamares/modules/localecfg/main.py -lib/calamares/modules/localecfg/module.desc -lib/calamares/modules/localeq/libcalamares_viewmodule_localeq.so -lib/calamares/modules/localeq/module.desc -lib/calamares/modules/luksbootkeyfile/libcalamares_job_luksbootkeyfile.so -lib/calamares/modules/luksbootkeyfile/module.desc -lib/calamares/modules/luksopenswaphookcfg/main.py -lib/calamares/modules/luksopenswaphookcfg/module.desc -lib/calamares/modules/machineid/libcalamares_job_machineid.so -lib/calamares/modules/machineid/module.desc -lib/calamares/modules/mount/main.py -lib/calamares/modules/mount/module.desc -lib/calamares/modules/mount/test.yaml -lib/calamares/modules/netinstall/libcalamares_viewmodule_netinstall.so -lib/calamares/modules/netinstall/module.desc -lib/calamares/modules/networkcfg/main.py -lib/calamares/modules/networkcfg/module.desc -lib/calamares/modules/notesqml/libcalamares_viewmodule_notesqml.so -lib/calamares/modules/notesqml/module.desc -lib/calamares/modules/oemid/libcalamares_viewmodule_oemid.so -lib/calamares/modules/oemid/module.desc -lib/calamares/modules/openrcdmcryptcfg/main.py -lib/calamares/modules/openrcdmcryptcfg/module.desc -lib/calamares/modules/packagechooser/libcalamares_viewmodule_packagechooser.so -lib/calamares/modules/packagechooser/module.desc -lib/calamares/modules/packages/main.py -lib/calamares/modules/packages/module.desc -lib/calamares/modules/packages/test.yaml -lib/calamares/modules/plymouthcfg/main.py -lib/calamares/modules/plymouthcfg/module.desc -lib/calamares/modules/preservefiles/libcalamares_job_preservefiles.so -lib/calamares/modules/preservefiles/module.desc -lib/calamares/modules/rawfs/main.py -lib/calamares/modules/rawfs/module.desc -lib/calamares/modules/removeuser/libcalamares_job_removeuser.so -lib/calamares/modules/removeuser/module.desc -lib/calamares/modules/services-openrc/main.py -lib/calamares/modules/services-openrc/module.desc -lib/calamares/modules/services-systemd/main.py -lib/calamares/modules/services-systemd/module.desc -lib/calamares/modules/shellprocess/libcalamares_job_shellprocess.so -lib/calamares/modules/shellprocess/module.desc -lib/calamares/modules/summary/libcalamares_viewmodule_summary.so -lib/calamares/modules/summary/module.desc -lib/calamares/modules/tracking/libcalamares_viewmodule_tracking.so -lib/calamares/modules/tracking/module.desc -lib/calamares/modules/umount/main.py -lib/calamares/modules/umount/module.desc -lib/calamares/modules/unpackfs/main.py -lib/calamares/modules/unpackfs/module.desc -lib/calamares/modules/unpackfs/runtests.sh -lib/calamares/modules/users/libcalamares_viewmodule_users.so -lib/calamares/modules/users/module.desc -lib/calamares/modules/welcome/libcalamares_viewmodule_welcome.so -lib/calamares/modules/welcome/module.desc -lib/calamares/modules/welcomeq/libcalamares_viewmodule_welcomeq.so -lib/calamares/modules/welcomeq/module.desc -lib/cmake/Calamares/CMakeColors.cmake -lib/cmake/Calamares/CalamaresAddBrandingSubdirectory.cmake -lib/cmake/Calamares/CalamaresAddLibrary.cmake -lib/cmake/Calamares/CalamaresAddModuleSubdirectory.cmake -lib/cmake/Calamares/CalamaresAddPlugin.cmake -lib/cmake/Calamares/CalamaresAddTest.cmake -lib/cmake/Calamares/CalamaresAddTranslations.cmake -lib/cmake/Calamares/CalamaresAutomoc.cmake -lib/cmake/Calamares/CalamaresConfig.cmake -lib/cmake/Calamares/CalamaresConfigVersion.cmake -lib/cmake/Calamares/CalamaresLibraryDepends-%%CMAKE_BUILD_TYPE%%.cmake -lib/cmake/Calamares/CalamaresLibraryDepends.cmake -lib/cmake/Calamares/CalamaresUse.cmake -lib/libcalamares.so -lib/libcalamares.so.3.2.25 -lib/libcalamaresui.so -lib/libcalamaresui.so.3.2.25 -man/man8/calamares.8.gz -share/applications/calamares.desktop -%%DATADIR%%/branding/default/banner.png -%%DATADIR%%/branding/default/branding.desc -%%DATADIR%%/branding/default/lang/calamares-default_ar.qm -%%DATADIR%%/branding/default/lang/calamares-default_en.qm -%%DATADIR%%/branding/default/lang/calamares-default_eo.qm -%%DATADIR%%/branding/default/lang/calamares-default_fr.qm -%%DATADIR%%/branding/default/lang/calamares-default_nl.qm -%%DATADIR%%/branding/default/languages.png -%%DATADIR%%/branding/default/show.qml -%%DATADIR%%/branding/default/squid.png -%%DATADIR%%/branding/default/stylesheet.qss -%%DATADIR%%/qml/calamares/slideshow/BackButton.qml -%%DATADIR%%/qml/calamares/slideshow/ForwardButton.qml -%%DATADIR%%/qml/calamares/slideshow/NavButton.qml -%%DATADIR%%/qml/calamares/slideshow/Presentation.qml -%%DATADIR%%/qml/calamares/slideshow/Slide.qml -%%DATADIR%%/qml/calamares/slideshow/SlideCounter.qml -%%DATADIR%%/qml/calamares/slideshow/qmldir -share/icons/hicolor/scalable/apps/calamares.svg -share/locale/ar/LC_MESSAGES/calamares-python.mo -share/locale/as/LC_MESSAGES/calamares-python.mo -share/locale/ast/LC_MESSAGES/calamares-python.mo -share/locale/be/LC_MESSAGES/calamares-python.mo -share/locale/bg/LC_MESSAGES/calamares-python.mo -share/locale/ca/LC_MESSAGES/calamares-python.mo -share/locale/cs_CZ/LC_MESSAGES/calamares-python.mo -share/locale/da/LC_MESSAGES/calamares-python.mo -share/locale/de/LC_MESSAGES/calamares-python.mo -share/locale/el/LC_MESSAGES/calamares-python.mo -share/locale/en_GB/LC_MESSAGES/calamares-python.mo -share/locale/eo/LC_MESSAGES/calamares-python.mo -share/locale/es/LC_MESSAGES/calamares-python.mo -share/locale/es_MX/LC_MESSAGES/calamares-python.mo -share/locale/es_PR/LC_MESSAGES/calamares-python.mo -share/locale/et/LC_MESSAGES/calamares-python.mo -share/locale/eu/LC_MESSAGES/calamares-python.mo -share/locale/fi_FI/LC_MESSAGES/calamares-python.mo -share/locale/fr/LC_MESSAGES/calamares-python.mo -share/locale/gl/LC_MESSAGES/calamares-python.mo -share/locale/he/LC_MESSAGES/calamares-python.mo -share/locale/hi/LC_MESSAGES/calamares-python.mo -share/locale/hr/LC_MESSAGES/calamares-python.mo -share/locale/hu/LC_MESSAGES/calamares-python.mo -share/locale/id/LC_MESSAGES/calamares-python.mo -share/locale/is/LC_MESSAGES/calamares-python.mo -share/locale/it_IT/LC_MESSAGES/calamares-python.mo -share/locale/ja/LC_MESSAGES/calamares-python.mo -share/locale/ko/LC_MESSAGES/calamares-python.mo -share/locale/lt/LC_MESSAGES/calamares-python.mo -share/locale/ml/LC_MESSAGES/calamares-python.mo -share/locale/mr/LC_MESSAGES/calamares-python.mo -share/locale/nb/LC_MESSAGES/calamares-python.mo -share/locale/nl/LC_MESSAGES/calamares-python.mo -share/locale/pl/LC_MESSAGES/calamares-python.mo -share/locale/pt_BR/LC_MESSAGES/calamares-python.mo -share/locale/pt_PT/LC_MESSAGES/calamares-python.mo -share/locale/ro/LC_MESSAGES/calamares-python.mo -share/locale/ru/LC_MESSAGES/calamares-python.mo -share/locale/sk/LC_MESSAGES/calamares-python.mo -share/locale/sl/LC_MESSAGES/calamares-python.mo -share/locale/sq/LC_MESSAGES/calamares-python.mo -share/locale/sr/LC_MESSAGES/calamares-python.mo -share/locale/sr@latin/LC_MESSAGES/calamares-python.mo -share/locale/sv/LC_MESSAGES/calamares-python.mo -share/locale/th/LC_MESSAGES/calamares-python.mo -share/locale/tr_TR/LC_MESSAGES/calamares-python.mo -share/locale/uk/LC_MESSAGES/calamares-python.mo -share/locale/zh_CN/LC_MESSAGES/calamares-python.mo -share/locale/zh_TW/LC_MESSAGES/calamares-python.mo From ec1b73be2bdeb230bc2bd6b2e0a87426a446d85c Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Mon, 29 Nov 2021 14:56:15 +0100 Subject: [PATCH 123/127] CMake: don't cmake in the source-directory (during development) --- CMakeLists.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 67bb1f319..bd4d3eb3a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -46,6 +46,9 @@ project( CALAMARES ) set( CALAMARES_VERSION_RC 1 ) # Set to 0 during release cycle, 1 during development +if( CALAMARES_VERSION_RC EQUAL 1 AND CMAKE_SOURCE_DIR STREQUAL CMAKE_BINARY_DIR ) + message( FATAL_ERROR "Do not build development versions in the source-directory." ) +endif() ### OPTIONS # From 28bd7370620ecf1d8ad6869df3149af63f3fa486 Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Mon, 29 Nov 2021 14:44:49 +0100 Subject: [PATCH 124/127] [packages] Validate test-configs, too - The config-files has a typo, so didn't validate, so the loaded data was wrong, leading to test-failures. See 61e0d538e9f0b83f66b11f3c63d55b8495eb8ac2 . --- src/modules/packages/tests/CMakeTests.txt | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/modules/packages/tests/CMakeTests.txt b/src/modules/packages/tests/CMakeTests.txt index 9ee9c6682..4f7d6185f 100644 --- a/src/modules/packages/tests/CMakeTests.txt +++ b/src/modules/packages/tests/CMakeTests.txt @@ -22,3 +22,21 @@ add_test( COMMAND env PYTHONPATH=.: python3 ${CMAKE_CURRENT_LIST_DIR}/test-pm-${_pm}.py ${CMAKE_CURRENT_LIST_DIR}/pm-pacman-2.yaml 3 0 0 WORKING_DIRECTORY ${CMAKE_BINARY_DIR} ) + +if ( BUILD_TESTING AND BUILD_SCHEMA_TESTING AND PYTHONINTERP_FOUND AND PYTHON_EXECUTABLE ) + set( _module packages ) + set( _schema_file "${CMAKE_CURRENT_SOURCE_DIR}/${_module}/${_module}.schema.yaml" ) + message(STATUS "Schema ${_schema_file}") + foreach( _cf pm-pacman-1.yaml pm-pacman-2.yaml ) + set( _conf_file "${CMAKE_CURRENT_SOURCE_DIR}/${_module}/tests/${_cf}" ) + if ( EXISTS "${_schema_file}" AND EXISTS "${_conf_file}" ) + add_test( + NAME validate-packages-${_cf} + COMMAND ${PYTHON_EXECUTABLE} "${CMAKE_SOURCE_DIR}/ci/configvalidator.py" "${_schema_file}" "${_conf_file}" + ) + else() + message(FATAL_ERROR "Missing ${_conf_file}") + endif() + endforeach() +endif() + From dfd13b4948596b11fd5c77827b3fb5b3a5be1072 Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Mon, 29 Nov 2021 15:02:05 +0100 Subject: [PATCH 125/127] [packages] Remove bad config-lines - rootMountPoint is a global thing, not a job-configuration item --- src/modules/packages/tests/2.job | 1 - src/modules/packages/tests/pm-pacman-1.yaml | 1 - src/modules/packages/tests/pm-pacman-2.yaml | 1 - 3 files changed, 3 deletions(-) diff --git a/src/modules/packages/tests/2.job b/src/modules/packages/tests/2.job index 130214dfd..ba205ed44 100644 --- a/src/modules/packages/tests/2.job +++ b/src/modules/packages/tests/2.job @@ -1,7 +1,6 @@ # SPDX-FileCopyrightText: no # SPDX-License-Identifier: CC0-1.0 backend: dummy -rootMountPoint: /tmp/mount operations: - install: - pre-script: touch /tmp/foo diff --git a/src/modules/packages/tests/pm-pacman-1.yaml b/src/modules/packages/tests/pm-pacman-1.yaml index 3fa75d481..e876bd0fe 100644 --- a/src/modules/packages/tests/pm-pacman-1.yaml +++ b/src/modules/packages/tests/pm-pacman-1.yaml @@ -1,7 +1,6 @@ # SPDX-FileCopyrightText: no # SPDX-License-Identifier: CC0-1.0 backend: pacman -rootMountPoint: /tmp/mount operations: [] pacman: diff --git a/src/modules/packages/tests/pm-pacman-2.yaml b/src/modules/packages/tests/pm-pacman-2.yaml index 2bbf70758..8b0bda397 100644 --- a/src/modules/packages/tests/pm-pacman-2.yaml +++ b/src/modules/packages/tests/pm-pacman-2.yaml @@ -1,7 +1,6 @@ # SPDX-FileCopyrightText: no # SPDX-License-Identifier: CC0-1.0 backend: pacman -rootMountPoint: /tmp/mount operations: [] # Leave some things unspecified From 7cea1cb56ee90baefa9b6a2518f28a7b69e245d2 Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Mon, 29 Nov 2021 17:06:10 +0100 Subject: [PATCH 126/127] Changes: document fstab --- CHANGES-3.2 | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES-3.2 b/CHANGES-3.2 index c2b0416da..9f60e0ab8 100644 --- a/CHANGES-3.2 +++ b/CHANGES-3.2 @@ -20,6 +20,9 @@ This release contains contributions from (alphabetically by first name): This may be the most useful for test-scripts. ## Modules ## + - *fstab* now has a separate, special, flags-setting for swap subvolumes + on btrfs. A swap subvolume is created if a swap **file** (not a separate + partition) is selected in the auto-partitioning page. (Thanks Evan) - The *packages* module now has some special settings for the `pacman` package manager (generally used on Arch-derivatives). This allows tweaking of the installation process, if downloads are slow or From 3845e058346c3a9cef65d8f96e726746b81aa748 Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Mon, 29 Nov 2021 22:03:51 +0100 Subject: [PATCH 127/127] [locale] Correct timezone 5.0 / 5.5 Timezones 5.0 and 5.5 have considerable overlap; clear up most of it. Since a pixel is about 55x55km on the ground, and the translation of latitude and longitude is sketchy at best, accuracy on this timezone map is not very good. FIXES #1832 --- src/modules/locale/images/timezone_5.0.png | Bin 15918 -> 15043 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/src/modules/locale/images/timezone_5.0.png b/src/modules/locale/images/timezone_5.0.png index e8ea14466a852dc81a104b69da9da34f6103c5ab..a15aaccc09ddb9fdd6491ac3aab70342d72da645 100644 GIT binary patch literal 15043 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 literal 15918 zcmch8bySqy_b>0OqLhd<5+c&w4FUqv62s6bIdnHjDj^Ee-Q69-5DH3n!_eIfUH2J$ z-+O=G-&((Q|GDcfam_sQoW0LJ`|PvN*`Krb=l6=zSm-3^C@3gcGVk81prAa!L_zsW z?FlOI%V*)HU%}H@D1Lz` zD3`!bej6w#&TJ?sTLvg70`Vv)gm%e|%0j>kR3ka*w7@e z|Gy;X*nuxXw=!?V)ZC_b=bYWt%-YKjqu&?5$*8U;I9mv;Z|d#kr==7RbmNFhcw&F{ z@+bZH63b8Kcb~qZ>~_!W)(y|q>DJY0v(B`8ab`GK#MQp1YQ1SHT~B;1&taE(&CWaE z-jDMT1?514Xf<)%q**^$38pyl-~3KAl*x%J#x(cf``cO zZ`fvqiKLoLo&oAXL1}vfj1SPvgEr*HGfcQD+v|Vc9B{XNoe!8o{{95j|1aRnK zi&6cS^N9~fwktxAJ-WTq2tVxg*&`1S6Xaced$cp@&F;8_{T6=qX@){rS((%JdsxPc z5He)bAO3B6`e~Njwh@ROqV;b#v==A4q8B0|o1T|n9DQ>KG{ACC1%ZYKzaDi+-c%n% z+zN;EZcN!`)*V;}(-Q=W80+wD0qh%pUc+<0Q8OoeqI)$$C}6+4)c^B*mq?;FIE zx3t054T@4pd4SjdC>F*4qXF=Qa-bVPIo2HV=iy0k6Q9 z`7heEJ!|YQ^MqF@*-s+5`7bj18)DuQKEwRSu>N#;KROgR4N>sGmpMoMw!xhBml@k5 z5-_&Z9czyr?ypqHMJgoQ#@5a3{R3Lcrtzrkw9YHjk#*!uGf^sp73+9{i7y*y(@OP5 z#}*G&{^|vn5eH-Q3?JNxcl!qwNeH4$U-w*y1VQLrJ6&Z_JI!fPM{bKHXstV4 zype*@b%7U7s9^%oqmU~?QI#N`q#T@x-xc#0ERN5=6bc_1Q*CuSAt zE`1`Yd!TYoIQ{YWHuf9;e){7NiIDLuIES}(kel7a1j|~ z*2U*cvYf!_>ax> ze>04$PiOWR)HBj72wUz#lWh~PW=wHZ2^&{Bq@ zAb;lLi4~hjt+W==GsHC9Dez(WFR&KxcJ#)5Oj_D&fBOg#Nj!Cl6>JqeFxH z-Hg76`AiqbLuCN#tI@Trny&j`Cbo6^JuIwrPME;5>^CoZzvQjDUZqolYNv(7PMEtO z`alp2H1_;jOw;&4@G$Av^*qLkZa@U*Dte1ii~?W($(8EbP<+~V9rO#&UC@-q9D}dc zJl&mcl|%!hLyXarf82MA+&r#;)6TSEc9>6h??%e!wZ7kaL7vBJWu>~&?p(Kpn+i8D zn5A<@G5gByMg)IT@mK`?QIv_?@jx)F;T*I%-ra$}8Elib)~wpOmwZvw!~20e45sd7 ze@h_`KOWc`Pv2f!$J74~{{m0fSdCjYS&!cs{mt7GjfV6`)6qV--b~Vb7BmB&rU`r* z32F&hgb1!I-Hi{L^zqT_Lfok-m*2I=q*F7-x)JWuv60F*XpFt`Z9X5<&SSvWjlYXs z{JQ4v0q$H3EZGckx>`!&YQo)CJ)`A{9o&76 z{i6hoWh4c3=;yMsX;|gi`)NAX$~J#OAeYx%_~@x=NJ+7l_85b=18bYXQ~%@g z({a{G9{kiFE|y|~*4JUqSEJcw({#GtlD9wb2zW)Wu1990m+m@k4-$WbBW-Dyndns= zO%j->H}0*oJS-nWQ?H_SE%bU5`cMKSH`p}|Ey%NSj^irT?Ba-W&7~-Jwu+G2v%Nxv(RLLFp}B3u!Z2 zXcV8X^$7;`38k<8b)lCMJjq$!D)p|pEY2}=BwF?RDFIXp+xkk-d26P?8Y!P!s|ObM z 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 av@>C`budfX6CyzlLPk>YZK=3{|NjC@PL