From 60c5f3fd5ad4232a6b7e605cffa59e797ebea0f6 Mon Sep 17 00:00:00 2001 From: Peter Jung Date: Mon, 4 Mar 2024 19:44:03 +0100 Subject: [PATCH 01/47] initcpiocfg: Add microcode module Signed-off-by: Peter Jung --- CHANGES-3.3 | 2 ++ src/modules/initcpiocfg/main.py | 1 + 2 files changed, 3 insertions(+) diff --git a/CHANGES-3.3 b/CHANGES-3.3 index dc0c84633..5312c1e82 100644 --- a/CHANGES-3.3 +++ b/CHANGES-3.3 @@ -12,6 +12,7 @@ the history of the 3.2 series (2018-05 - 2022-08). This release contains contributions from (alphabetically by first name): - Adriaan de Groot - Evan James + - Peter Jung ## Core ## - Calamares logs more information about how the executable was created @@ -22,6 +23,7 @@ This release contains contributions from (alphabetically by first name): ## Modules ## - *displaymanager* module can configure an alternate SDDM configuration file. - *networkcfg* a bug affecting NetPlan + NetworkManager was fixed. + - *initcpiocfg* Add microcode hook to initcpiocfg # 3.3.4 (2024-02-27) diff --git a/src/modules/initcpiocfg/main.py b/src/modules/initcpiocfg/main.py index 0939054fb..13a281bb3 100644 --- a/src/modules/initcpiocfg/main.py +++ b/src/modules/initcpiocfg/main.py @@ -148,6 +148,7 @@ def find_initcpio_features(partitions, root_mount_point): """ hooks = [ "autodetect", + "microcode", "kms", "modconf", "block", From 408ee0338a6a323e2e08adc164cb198cd2bb43b3 Mon Sep 17 00:00:00 2001 From: Simon Quigley Date: Thu, 7 Mar 2024 15:26:32 -0600 Subject: [PATCH 02/47] Active Directory Support --- src/modules/users/ActiveDirectoryJob.cpp | 91 ++++++++++++++++++++++++ src/modules/users/ActiveDirectoryJob.h | 29 ++++++++ src/modules/users/CMakeLists.txt | 1 + src/modules/users/Config.cpp | 60 ++++++++++++++++ src/modules/users/Config.h | 20 ++++++ src/modules/users/UsersPage.cpp | 28 ++++++++ src/modules/users/page_usersetup.ui | 87 ++++++++++++++++++++++ src/modules/users/users.conf | 6 ++ src/modules/users/users.schema.yaml | 1 + 9 files changed, 323 insertions(+) create mode 100644 src/modules/users/ActiveDirectoryJob.cpp create mode 100644 src/modules/users/ActiveDirectoryJob.h diff --git a/src/modules/users/ActiveDirectoryJob.cpp b/src/modules/users/ActiveDirectoryJob.cpp new file mode 100644 index 000000000..33706c48f --- /dev/null +++ b/src/modules/users/ActiveDirectoryJob.cpp @@ -0,0 +1,91 @@ +/* === This file is part of Calamares - === + * + * SPDX-FileCopyrightText: 2024 Simon Quigley + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "ActiveDirectoryJob.h" + +#include "Config.h" + +#include "GlobalStorage.h" +#include "JobQueue.h" +#include "utils/Logger.h" +#include "utils/Permissions.h" +#include "utils/System.h" + +#include +#include +#include +#include +#include +#include + +ActiveDirectoryJob::ActiveDirectoryJob(QStringList& activeDirectoryInfo) + : Calamares::Job() + , m_activeDirectoryInfo(activeDirectoryInfo) +{ +} + +QString +ActiveDirectoryJob::prettyName() const +{ + return tr( "Enroll system in Active Directory" ); +} + +QString +ActiveDirectoryJob::prettyDescription() const +{ + return tr( "Enroll system in Active Directory" ); +} + +QString +ActiveDirectoryJob::prettyStatusMessage() const +{ + return tr( "Enrolling system in Active Directory" ); +} + +Calamares::JobResult +ActiveDirectoryJob::exec() +{ + QString username = m_activeDirectoryInfo.value(0); + QString password = m_activeDirectoryInfo.value(1); + QString domain = m_activeDirectoryInfo.value(2); + QString ip = m_activeDirectoryInfo.value(3); + + Calamares::GlobalStorage* gs = Calamares::JobQueue::instance()->globalStorage(); + QString rootMountPoint = gs ? gs->value("rootMountPoint").toString() : QString(); + + if (!ip.isEmpty()) { + QString hostsFilePath = !rootMountPoint.isEmpty() ? rootMountPoint + "/etc/hosts" : "/etc/hosts"; + QFile hostsFile(hostsFilePath); + if (hostsFile.open(QIODevice::Append | QIODevice::Text)) { + QTextStream out(&hostsFile); + out << ip << " " << domain << "\n"; + hostsFile.close(); + } else { + return Calamares::JobResult::error("Failed to open /etc/hosts for writing."); + } + } + + QString installPath = !rootMountPoint.isEmpty() ? rootMountPoint : "/"; + QStringList args = {"join", domain, "-U", username, "--install=" + installPath, "--verbose"}; + + QProcess process; + process.start("realm", args); + process.waitForStarted(); + + if (!password.isEmpty()) { + process.write((password + "\n").toUtf8()); + process.closeWriteChannel(); + } + + process.waitForFinished(-1); + + if (process.exitCode() == 0) { + return Calamares::JobResult::ok(); + } else { + QString errorOutput = process.readAllStandardError(); + return Calamares::JobResult::error(QString("Failed to join realm: %1").arg(errorOutput)); + } +} diff --git a/src/modules/users/ActiveDirectoryJob.h b/src/modules/users/ActiveDirectoryJob.h new file mode 100644 index 000000000..8877ef7e1 --- /dev/null +++ b/src/modules/users/ActiveDirectoryJob.h @@ -0,0 +1,29 @@ +/* === This file is part of Calamares - === + * + * SPDX-FileCopyrightText: 2024 Simon Quigley + * SPDX-License-Identifier: GPL-3.0-or-later + * + * Calamares is Free Software: see the License-Identifier above. + * + */ + +#ifndef ACTIVEDIRECTORYJOB_H +#define ACTIVEDIRECTORYJOB_H + +#include "Job.h" + +class ActiveDirectoryJob : public Calamares::Job +{ + Q_OBJECT +public: + ActiveDirectoryJob( QStringList& activeDirectoryInfo ); + QString prettyName() const override; + QString prettyDescription() const override; + QString prettyStatusMessage() const override; + Calamares::JobResult exec() override; + +private: + QStringList m_activeDirectoryInfo; +}; + +#endif /* ACTIVEDIRECTORYJOB_H */ diff --git a/src/modules/users/CMakeLists.txt b/src/modules/users/CMakeLists.txt index 7a8a944b7..25e011c8e 100644 --- a/src/modules/users/CMakeLists.txt +++ b/src/modules/users/CMakeLists.txt @@ -55,6 +55,7 @@ include_directories(${PROJECT_BINARY_DIR}/src/libcalamaresui) set(_users_src # Jobs + ActiveDirectoryJob.cpp CreateUserJob.cpp MiscJobs.cpp SetPasswordJob.cpp diff --git a/src/modules/users/Config.cpp b/src/modules/users/Config.cpp index 1e6db0f33..ff10131f8 100644 --- a/src/modules/users/Config.cpp +++ b/src/modules/users/Config.cpp @@ -9,6 +9,7 @@ #include "Config.h" +#include "ActiveDirectoryJob.h" #include "CreateUserJob.h" #include "MiscJobs.h" #include "SetHostNameJob.h" @@ -656,6 +657,59 @@ Config::setRootPasswordSecondary( const QString& s ) } } +void +Config::setActiveDirectoryUsed( bool used ) +{ + m_activeDirectoryUsed = used; +} + +bool +Config::getActiveDirectoryEnabled() const +{ + return m_activeDirectory; +} + +bool +Config::getActiveDirectoryUsed() const +{ + return m_activeDirectoryUsed && m_activeDirectory; +} + +void +Config::setActiveDirectoryAdminUsername( const QString & s ) +{ + m_activeDirectoryUsername = s; +} + +void +Config::setActiveDirectoryAdminPassword( const QString & s ) +{ + m_activeDirectoryPassword = s; +} + +void +Config::setActiveDirectoryDomain( const QString & s ) +{ + m_activeDirectoryDomain = s; +} + +void +Config::setActiveDirectoryIP( const QString & s ) +{ + m_activeDirectoryIP = s; +} + +QStringList& +Config::getActiveDirectory() const +{ + m_activeDirectorySettings.clear(); + m_activeDirectorySettings << m_activeDirectoryUsername + << m_activeDirectoryPassword + << m_activeDirectoryDomain + << m_activeDirectoryIP; + return m_activeDirectorySettings; +} + QString Config::rootPassword() const { @@ -913,6 +967,9 @@ Config::setConfigurationMap( const QVariantMap& configurationMap ) m_sudoStyle = Calamares::getBool( configurationMap, "sudoersConfigureWithGroup", false ) ? SudoStyle::UserAndGroup : SudoStyle::UserOnly; + // Handle Active Directory enablement + m_activeDirectory = Calamares::getBool( configurationMap, "allowActiveDirectory", false ); + // Handle *hostname* key and subkeys and legacy settings { bool ok = false; // Ignored @@ -990,6 +1047,9 @@ Config::createJobs() const jobs.append( Calamares::job_ptr( j ) ); } + j = new ActiveDirectoryJob( getActiveDirectory() ); + jobs.append( Calamares::job_ptr( j ) ); + j = new SetupGroupsJob( this ); jobs.append( Calamares::job_ptr( j ) ); diff --git a/src/modules/users/Config.h b/src/modules/users/Config.h index 599fcd6bd..afdfa8efd 100644 --- a/src/modules/users/Config.h +++ b/src/modules/users/Config.h @@ -226,6 +226,12 @@ public: bool permitWeakPasswords() const { return m_permitWeakPasswords; } /// Current setting for "require strong password"? bool requireStrongPasswords() const { return m_requireStrongPasswords; } + /// Is Active Directory enabled? + bool getActiveDirectoryEnabled() const; + /// Is it both enabled and activated? + bool getActiveDirectoryUsed() const; + /// Config for Active Directory + QStringList& getActiveDirectory() const; const QList< GroupDescription >& defaultGroups() const { return m_defaultGroups; } /** @brief the names of all the groups for the current user @@ -292,6 +298,12 @@ public Q_SLOTS: void setRootPassword( const QString& ); void setRootPasswordSecondary( const QString& ); + void setActiveDirectoryUsed( bool used ); + void setActiveDirectoryAdminUsername( const QString& ); + void setActiveDirectoryAdminPassword( const QString& ); + void setActiveDirectoryDomain( const QString& ); + void setActiveDirectoryIP( const QString& ); + signals: void userShellChanged( const QString& ); void autoLoginGroupChanged( const QString& ); @@ -343,6 +355,14 @@ private: bool m_isReady = false; ///< Used to reduce readyChanged signals + mutable QStringList m_activeDirectorySettings; + bool m_activeDirectory = false; + bool m_activeDirectoryUsed = false; + QString m_activeDirectoryUsername; + QString m_activeDirectoryPassword; + QString m_activeDirectoryDomain; + QString m_activeDirectoryIP; + HostNameAction m_hostnameAction = HostNameAction::EtcHostname; bool m_writeEtcHosts = false; QString m_hostnameTemplate; diff --git a/src/modules/users/UsersPage.cpp b/src/modules/users/UsersPage.cpp index bac30f350..269312330 100644 --- a/src/modules/users/UsersPage.cpp +++ b/src/modules/users/UsersPage.cpp @@ -162,6 +162,34 @@ UsersPage::UsersPage( Config* config, QWidget* parent ) config, &Config::requireStrongPasswordsChanged, ui->checkBoxRequireStrongPassword, &QCheckBox::setChecked ); } + // Active Directory is not checked or enabled by default + ui->useADCheckbox->setVisible(m_config->getActiveDirectoryEnabled()); + ui->domainLabel->setVisible(false); + ui->domainField->setVisible(false); + ui->domainAdminLabel->setVisible(false); + ui->domainAdminField->setVisible(false); + ui->domainPasswordField->setVisible(false); + ui->domainPasswordLabel->setVisible(false); + ui->ipAddressField->setVisible(false); + ui->ipAddressLabel->setVisible(false); + + connect(ui->useADCheckbox, &QCheckBox::toggled, [=](bool checked){ + ui->domainLabel->setVisible(checked); + ui->domainField->setVisible(checked); + ui->domainAdminLabel->setVisible(checked); + ui->domainAdminField->setVisible(checked); + ui->domainPasswordField->setVisible(checked); + ui->domainPasswordLabel->setVisible(checked); + ui->ipAddressField->setVisible(checked); + ui->ipAddressLabel->setVisible(checked); + }); + + connect( ui->domainField, &QLineEdit::textChanged, config, &Config::setActiveDirectoryDomain ); + connect( ui->domainAdminField, &QLineEdit::textChanged, config, &Config::setActiveDirectoryAdminUsername ); + connect( ui->domainPasswordField, &QLineEdit::textChanged, config, &Config::setActiveDirectoryAdminPassword ); + connect( ui->ipAddressField, &QLineEdit::textChanged, config, &Config::setActiveDirectoryIP ); + connect( ui->useADCheckbox, &QCheckBox::toggled, config, &Config::setActiveDirectoryUsed ); + CALAMARES_RETRANSLATE_SLOT( &UsersPage::retranslate ); onReuseUserPasswordChanged( m_config->reuseUserPasswordForRoot() ); diff --git a/src/modules/users/page_usersetup.ui b/src/modules/users/page_usersetup.ui index c21907415..6e6e5423e 100644 --- a/src/modules/users/page_usersetup.ui +++ b/src/modules/users/page_usersetup.ui @@ -603,6 +603,93 @@ SPDX-License-Identifier: GPL-3.0-or-later + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 6 + + + + + + + + + + Use Active Directory + + + + + + + + + + + Domain: + + + + + + + + + + + + + + Domain Administrator: + + + + + + + + + + Password: + + + + + + + QLineEdit::Password + + + + + + + + + + + IP Address (optional): + + + + + + + + + + + + diff --git a/src/modules/users/users.conf b/src/modules/users/users.conf index 669cac038..e6910bcd2 100644 --- a/src/modules/users/users.conf +++ b/src/modules/users/users.conf @@ -265,6 +265,12 @@ hostname: template: "derp-${cpu}" forbidden_names: [ localhost ] +# Enable Active Directory enrollment support (opt-in) +# +# This uses realmd to enroll the machine in an Active Directory server +# It requires realmd as a runtime dependency of Calamares, if enabled +allowActiveDirectory: false + presets: fullName: # value: "OEM User" diff --git a/src/modules/users/users.schema.yaml b/src/modules/users/users.schema.yaml index a67504321..c751a5226 100644 --- a/src/modules/users/users.schema.yaml +++ b/src/modules/users/users.schema.yaml @@ -52,6 +52,7 @@ properties: writeHostsFile: { type: boolean, default: true } template: { type: string, default: "${first}-${product}" } forbidden_names: { type: array, items: { type: string } } + allowActiveDirectory: { type: boolean, default: false } # Presets # From d33d9e8daeef7540b03fecd6f7053567f7e34321 Mon Sep 17 00:00:00 2001 From: Harald Sitter Date: Sun, 10 Mar 2024 01:33:52 +0100 Subject: [PATCH 03/47] on Qt6 we need to build with position independent code otherwise linking may fail with > copy relocation against non-copyable protected symbol `qt_resourceFeatureZstd@@Qt_6' --- lang/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/lang/CMakeLists.txt b/lang/CMakeLists.txt index dd2a47b04..efd3182f3 100644 --- a/lang/CMakeLists.txt +++ b/lang/CMakeLists.txt @@ -36,3 +36,4 @@ calamares_qrc_translations(calamares-i18n ) add_library(calamares-i18n OBJECT ${translation_outfile}) +set_property(TARGET calamares-i18n PROPERTY POSITION_INDEPENDENT_CODE ON) From e8726111f85d78615d66cb3f987a59c1e40f598d Mon Sep 17 00:00:00 2001 From: dalto Date: Sun, 17 Mar 2024 19:03:02 -0500 Subject: [PATCH 04/47] [mount] Ensure efi partition gets correct options --- src/modules/mount/main.py | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/src/modules/mount/main.py b/src/modules/mount/main.py index 4202639f3..bfeb23ea6 100644 --- a/src/modules/mount/main.py +++ b/src/modules/mount/main.py @@ -74,13 +74,14 @@ def is_ssd_disk(partition): return False -def get_mount_options(filesystem, mount_options, partition): +def get_mount_options(filesystem, mount_options, partition, efi_location = None): """ Returns the mount options for the partition object and filesystem :param filesystem: A string containing the filesystem :param mount_options: A list of dicts that descripes the mount options for each mountpoint :param partition: A dict containing information about the partition + :param efi_location: A string holding the location of the EFI partition or None :return: A comma seperated string containing the mount options suitable for passing to mount """ @@ -92,7 +93,13 @@ def get_mount_options(filesystem, mount_options, partition): if mount_options is None: return "defaults" - options = next((x for x in mount_options if x["filesystem"] == filesystem), None) + # The EFI partition uses special mounting options + if efi_location and partition["mountPoint"] == efi_location: + effective_filesystem = "efi" + else: + effective_filesystem = filesystem + + options = next((x for x in mount_options if x["filesystem"] == effective_filesystem), None) # If there is no match then check for default options if options is None: @@ -214,7 +221,7 @@ def mount_zfs(root_mount_point, partition): raise ZfsException(_("Failed to set zfs mountpoint")) -def mount_partition(root_mount_point, partition, partitions, mount_options, mount_options_list): +def mount_partition(root_mount_point, partition, partitions, mount_options, mount_options_list, efi_location): """ Do a single mount of @p partition inside @p root_mount_point. @@ -223,6 +230,7 @@ def mount_partition(root_mount_point, partition, partitions, mount_options, moun :param partitions: The full list of partitions used to filter out btrfs subvols which have duplicate mountpoints :param mount_options: The mount options from the config file :param mount_options_list: A list of options for each mountpoint to be placed in global storage for future modules + :param efi_location: A string holding the location of the EFI partition or None :return: """ # Create mount point with `+` rather than `os.path.join()` because @@ -235,7 +243,9 @@ def mount_partition(root_mount_point, partition, partitions, mount_options, moun # Ensure that the created directory has the correct SELinux context on # SELinux-enabled systems. + os.makedirs(mount_point, exist_ok=True) + try: subprocess.call(['chcon', '--reference=' + raw_mount_point, mount_point]) except FileNotFoundError as e: @@ -259,7 +269,7 @@ def mount_partition(root_mount_point, partition, partitions, mount_options, moun if fstype == "zfs": mount_zfs(root_mount_point, partition) else: # fstype == "zfs" - mount_options_string = get_mount_options(fstype, mount_options, partition) + mount_options_string = get_mount_options(fstype, mount_options, partition, efi_location) if libcalamares.utils.mount(device, mount_point, fstype, @@ -349,7 +359,10 @@ def run(): if not extra_mounts: libcalamares.utils.warning("No extra mounts defined. Does mount.conf exist?") - if libcalamares.globalstorage.value("firmwareType") != "efi": + efi_location = None + if libcalamares.globalstorage.value("firmwareType") == "efi": + efi_location = libcalamares.globalstorage.value("efiSystemPartition") + else: for mount in extra_mounts: if mount.get("efi", None) is True: extra_mounts.remove(mount) @@ -365,7 +378,7 @@ def run(): mount_options_list = [] try: for partition in mountable_partitions: - mount_partition(root_mount_point, partition, partitions, mount_options, mount_options_list) + mount_partition(root_mount_point, partition, partitions, mount_options, mount_options_list, efi_location) except ZfsException as ze: return _("zfs mounting error"), ze.message From bdd059ffa81e0cd75fc9b19a39006d0311731bfd Mon Sep 17 00:00:00 2001 From: dalto Date: Sat, 23 Mar 2024 09:43:15 -0500 Subject: [PATCH 05/47] [displaymanager] Fix sddm config file writing to the wrong location --- src/modules/displaymanager/main.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/modules/displaymanager/main.py b/src/modules/displaymanager/main.py index 7ec6af774..217cd56b0 100644 --- a/src/modules/displaymanager/main.py +++ b/src/modules/displaymanager/main.py @@ -732,13 +732,13 @@ class DMsddm(DisplayManager): name = "sddm" executable = "sddm" - configuration_file = "etc/sddm.conf" + configuration_file = "/etc/sddm.conf" 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, self.configuration_file) + sddm_conf_path = os.path.join(self.root_mount_point, self.configuration_file.lstrip('/')) sddm_config = configparser.ConfigParser(strict=False) # Make everything case sensitive From a659db1f9ce55771fa3cca2546e1eb4dfeaf3dae Mon Sep 17 00:00:00 2001 From: Mike Stemle Date: Sun, 24 Mar 2024 13:27:44 -0400 Subject: [PATCH 06/47] Update Presentation.qml The `padding` property is present in the qt5 version, but not the qt6 version. I notice a syntax error as a result of this missing propertly. --- src/qml/calamares-qt6/slideshow/Presentation.qml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/qml/calamares-qt6/slideshow/Presentation.qml b/src/qml/calamares-qt6/slideshow/Presentation.qml index aab7007e6..1eed2e842 100644 --- a/src/qml/calamares-qt6/slideshow/Presentation.qml +++ b/src/qml/calamares-qt6/slideshow/Presentation.qml @@ -196,6 +196,8 @@ Item { Text { id: notesText + property real padding: 16; + x: padding y: padding width: parent.width - 2 * padding From ec0316806fb9df2f4983e0c36ab4981ce9a4b3bb Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Fri, 29 Mar 2024 02:09:18 +0400 Subject: [PATCH 07/47] [locale] Fix build with DEBUG_TIMEZONES --- src/modules/locale/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/locale/CMakeLists.txt b/src/modules/locale/CMakeLists.txt index 990032e87..1cb7a4f84 100644 --- a/src/modules/locale/CMakeLists.txt +++ b/src/modules/locale/CMakeLists.txt @@ -33,7 +33,7 @@ calamares_add_plugin(locale SHARED_LIB ) if(DEBUG_TIMEZONES) - target_compile_definitions(${localeq_TARGET} PRIVATE DEBUG_TIMEZONES) + target_compile_definitions(${locale_TARGET} PRIVATE DEBUG_TIMEZONES) endif() calamares_add_test( From 8440a6a5ddcb3284e68312d2acba14399d24c18a Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Fri, 29 Mar 2024 02:09:51 +0400 Subject: [PATCH 08/47] CI: switch openSUSE build to Qt6 --- .github/workflows/nightly-opensuse-qt6.yml | 33 ---------------------- .github/workflows/nightly-opensuse.yml | 1 + 2 files changed, 1 insertion(+), 33 deletions(-) delete mode 100644 .github/workflows/nightly-opensuse-qt6.yml diff --git a/.github/workflows/nightly-opensuse-qt6.yml b/.github/workflows/nightly-opensuse-qt6.yml deleted file mode 100644 index cd286d5db..000000000 --- a/.github/workflows/nightly-opensuse-qt6.yml +++ /dev/null @@ -1,33 +0,0 @@ -name: nightly-opensuse-qt6 - -on: - schedule: - - cron: "32 2 * * *" - workflow_dispatch: - -env: - BUILDDIR: /build - SRCDIR: ${{ github.workspace }} - CMAKE_ARGS: | - -DKDE_INSTALL_USE_QT_SYS_PATHS=ON - -DCMAKE_BUILD_TYPE=Debug - -DWITH_QT6=ON - -jobs: - build: - runs-on: ubuntu-latest - container: - image: docker://opensuse/tumbleweed - options: --tmpfs /build:rw --user 0:0 - steps: - - name: "prepare git" - shell: bash - run: zypper --non-interactive in git-core jq curl - - name: "prepare source" - uses: calamares/actions/generic-checkout@v5 - - name: "install dependencies" - shell: bash - run: ./ci/deps-opensuse-qt6.sh - - name: "build" - shell: bash - run: ./ci/build.sh diff --git a/.github/workflows/nightly-opensuse.yml b/.github/workflows/nightly-opensuse.yml index 5b742b704..49c155a77 100644 --- a/.github/workflows/nightly-opensuse.yml +++ b/.github/workflows/nightly-opensuse.yml @@ -15,6 +15,7 @@ env: -DBUILD_TESTING=ON -DBUILD_APPSTREAM=ON -DBUILD_APPDATA=ON + -DWITH_QT6=ON jobs: build: From 2fd98c913ad1a7c10e0c6923bc92d81c1a0b3fbd Mon Sep 17 00:00:00 2001 From: Peter Jung Date: Fri, 29 Mar 2024 20:05:41 +0100 Subject: [PATCH 09/47] plymouthcfg: Use plymouth-set-default-theme instead of sed Currently we are running into an issue, when setting the default theme via "sed", since the "[DAEMON]" entry in /etc/plymouth/plymouthd.conf is commented, which results into that the theme is not correctly applied. Signed-off-by: Peter Jung --- CHANGES-3.3 | 1 + src/modules/plymouthcfg/main.py | 4 +--- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/CHANGES-3.3 b/CHANGES-3.3 index 5312c1e82..1aff89918 100644 --- a/CHANGES-3.3 +++ b/CHANGES-3.3 @@ -24,6 +24,7 @@ This release contains contributions from (alphabetically by first name): - *displaymanager* module can configure an alternate SDDM configuration file. - *networkcfg* a bug affecting NetPlan + NetworkManager was fixed. - *initcpiocfg* Add microcode hook to initcpiocfg + - *plymouthcfg* Use plymouth-set-default-theme to avoid issues with configuration # 3.3.4 (2024-02-27) diff --git a/src/modules/plymouthcfg/main.py b/src/modules/plymouthcfg/main.py index 5e66fce67..529d3426e 100644 --- a/src/modules/plymouthcfg/main.py +++ b/src/modules/plymouthcfg/main.py @@ -48,9 +48,7 @@ class PlymouthController: def setTheme(self): plymouth_theme = libcalamares.job.configuration["plymouth_theme"] - target_env_call(["sed", "-e", 's|^.*Theme=.*|Theme=' + - plymouth_theme + '|', "-i", - "/etc/plymouth/plymouthd.conf"]) + target_env_call(["plymouth-set-default-theme", plymouth_theme]) def run(self): if detect_plymouth(): From f0c2295606bb1a8efecf0bc91bda4029c498d728 Mon Sep 17 00:00:00 2001 From: demmm Date: Sat, 30 Mar 2024 20:11:48 +0100 Subject: [PATCH 10/47] [localeq]Map-qt6 needed changes for Qt6 & Wayland dragging & zooming now needs QML entries osm is the only qtlocation provided map plugin, preferred thus useless now no solution yet for warning (API key required), upstream bug reports offer no fix, example https://bugreports.qt.io/browse/QTBUG-115742 --- src/modules/localeq/Map-qt6.qml | 30 +++++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/src/modules/localeq/Map-qt6.qml b/src/modules/localeq/Map-qt6.qml index b485dcadf..1e7b489e5 100644 --- a/src/modules/localeq/Map-qt6.qml +++ b/src/modules/localeq/Map-qt6.qml @@ -1,6 +1,6 @@ /* === This file is part of Calamares - === * - * SPDX-FileCopyrightText: 2020 - 2022 Anke Boersma + * SPDX-FileCopyrightText: 2020 - 2024 Anke Boersma * SPDX-License-Identifier: GPL-3.0-or-later * * Calamares is Free Software: see the License-Identifier above. @@ -89,7 +89,7 @@ Column { console.log("Online lookup", latC, lonC) // Needs to move to localeq.conf, each distribution will need their own account - xhr.open("GET", "http://api.geonames.org/timezoneJSON?lat=" + latC + "&lng=" + lonC + "&username=SOME_USERNAME") + xhr.open("GET", "https://api.geonames.org/timezoneJSON?lat=" + latC + "&lng=" + lonC + "&username=SOME_USERNAME") xhr.send() } @@ -112,7 +112,7 @@ Column { Plugin { id: mapPlugin - preferred: ["osm", "esri"] // "esri", "here", "itemsoverlay", "mapbox", "mapboxgl", "osm" + name: ["osm"] } Map { @@ -177,6 +177,30 @@ Column { getTzOffline(); } } + + WheelHandler { + id: wheel + acceptedDevices: Qt.platform.pluginName === "cocoa" || Qt.platform.pluginName === "wayland" + ? PointerDevice.Mouse | PointerDevice.TouchPad + : PointerDevice.Mouse + rotationScale: 1/120 + property: "zoomLevel" + } + DragHandler { + id: drag + target: null + onTranslationChanged: (delta) => map.pan(-delta.x, -delta.y) + } + Shortcut { + enabled: map.zoomLevel < map.maximumZoomLevel + sequence: StandardKey.ZoomIn + onActivated: map.zoomLevel = Math.round(map.zoomLevel + 1) + } + Shortcut { + enabled: map.zoomLevel > map.minimumZoomLevel + sequence: StandardKey.ZoomOut + onActivated: map.zoomLevel = Math.round(map.zoomLevel - 1) + } } Column { From 36485c8571ecc432bb50db7857e5c6a39dd2f977 Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Tue, 2 Apr 2024 23:20:35 +0200 Subject: [PATCH 11/47] [partition] Simplify logic for checking for unencrypted /boot --- src/modules/partition/PartitionViewStep.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/modules/partition/PartitionViewStep.cpp b/src/modules/partition/PartitionViewStep.cpp index 8b7225da3..8d9aae609 100644 --- a/src/modules/partition/PartitionViewStep.cpp +++ b/src/modules/partition/PartitionViewStep.cpp @@ -497,11 +497,12 @@ shouldWarnForNotEncryptedBoot( const Config* config, const PartitionCoreModule* Partition* root_p = core->findPartitionByMountPoint( "/" ); Partition* boot_p = core->findPartitionByMountPoint( "/boot" ); - if ( root_p and boot_p ) + if ( root_p && boot_p ) { - if ( ( root_p->fileSystem().type() == FileSystem::Luks && boot_p->fileSystem().type() != FileSystem::Luks ) - || ( root_p->fileSystem().type() == FileSystem::Luks2 - && boot_p->fileSystem().type() != FileSystem::Luks2 ) ) + const auto encryptionMismatch + = [ root_t = root_p->fileSystem().type(), boot_t = boot_p->fileSystem().type() ]( FileSystem::Type t ) + { return root_t == t && boot_t != t; }; + if ( encryptionMismatch( FileSystem::Luks ) || encryptionMismatch( FileSystem::Luks2 ) ) { return true; } From b396f70d122f6cdb5d90727010df896d34ded17d Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Tue, 2 Apr 2024 23:23:56 +0200 Subject: [PATCH 12/47] [partition] Fix typo's in example config --- src/modules/partition/partition.conf | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/modules/partition/partition.conf b/src/modules/partition/partition.conf index 0f23323f8..0975179de 100644 --- a/src/modules/partition/partition.conf +++ b/src/modules/partition/partition.conf @@ -193,7 +193,7 @@ initialSwapChoice: none # Default filesystem type, used when a "new" partition is made. # -# When replacing a partition, the new filesystem type will be from the +# When replacing a partition, the new filesystem type will be from the # defaultFileSystemType value. In other cases, e.g. Erase and Alongside, # as well as when using manual partitioning and creating a new # partition, this filesystem type is pre-selected. Note that @@ -248,9 +248,9 @@ defaultFileSystemType: "ext4" # for root that uses 100% of the space and uses the filesystem defined by # defaultFileSystemType. # -# Note: the EFI system partition is prepend automatically to the layout if -# needed; the swap partition is appended to the layout if enabled (small of -# suspend). +# Note: the EFI system partition is prepended automatically to the layout if +# needed; the swap partition is appended to the layout if enabled (selections +# "small" or "suspend" in *userSwapChoices*). # # Otherwise, the partition layout is defined as follow: # From 14c8893b032093413d4040a75885a7fc458c2542 Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Tue, 2 Apr 2024 23:26:42 +0200 Subject: [PATCH 13/47] [libcalamares] Turn off Clang 17 warning for MOC The build produces a zillion warnings about unsafe buffer usage in the code that wrangles metaobjects and slot names. That is generated code and we can't fix it. --- src/libcalamares/utils/moc-warnings.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/libcalamares/utils/moc-warnings.h b/src/libcalamares/utils/moc-warnings.h index 7d54c26b5..05ba34bd6 100644 --- a/src/libcalamares/utils/moc-warnings.h +++ b/src/libcalamares/utils/moc-warnings.h @@ -25,4 +25,8 @@ #pragma clang diagnostic ignored "-Wextra-semi-stmt" #pragma clang diagnostic ignored "-Wredundant-parens" #pragma clang diagnostic ignored "-Wreserved-identifier" + +#if __clang_major__ >= 17 +#pragma clang diagnostic ignored "-Wunsafe-buffer-usage" +#endif #endif From fbde737802ae23eb663c241105d1c770205431f2 Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Tue, 2 Apr 2024 23:28:42 +0200 Subject: [PATCH 14/47] [partition] Don't redundantly specify value binding in lambda --- src/modules/partition/PartitionViewStep.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/partition/PartitionViewStep.cpp b/src/modules/partition/PartitionViewStep.cpp index 8d9aae609..119bf1baa 100644 --- a/src/modules/partition/PartitionViewStep.cpp +++ b/src/modules/partition/PartitionViewStep.cpp @@ -223,7 +223,7 @@ PartitionViewStep::prettyStatus() const const QList< PartitionCoreModule::SummaryInfo > list = m_core->createSummaryInfo(); cDebug() << "Summary for Partition" << list.length() << choice; - auto joinDiskInfo = [ choice = choice ]( QString& s, const PartitionCoreModule::SummaryInfo& i ) + auto joinDiskInfo = [ choice ]( QString& s, const PartitionCoreModule::SummaryInfo& i ) { return s + diskDescription( 1, i, choice ); }; const QString diskInfoLabel = std::accumulate( list.begin(), list.end(), QString(), joinDiskInfo ); const QString jobsLabel = jobDescriptions( jobs() ).join( QStringLiteral( "
" ) ); From b950f0f6d42f650a2f0fa386dbce86ad31ec38be Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Tue, 2 Apr 2024 23:32:53 +0200 Subject: [PATCH 15/47] [users] Remove duplicated constant (a regexp) --- src/modules/users/Config.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/modules/users/Config.cpp b/src/modules/users/Config.cpp index 1e6db0f33..87d27fd92 100644 --- a/src/modules/users/Config.cpp +++ b/src/modules/users/Config.cpp @@ -444,8 +444,6 @@ makeHostnameSuggestion( const QString& templateString, const QStringList& fullNa QString hostnameSuggestion = d.expand( templateString ); - // RegExp for valid hostnames; if the suggestion produces a valid name, return it - static const QRegularExpression HOSTNAME_RX( "^[a-zA-Z0-9][-a-zA-Z0-9_]*$" ); return hostnameSuggestion.indexOf( HOSTNAME_RX ) != -1 ? hostnameSuggestion : QString(); } From 922b8aaaec421322cd430c06d812ba2f4f0bff0a Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Sat, 6 Apr 2024 22:34:27 +0200 Subject: [PATCH 16/47] CI: install opensuse-qt6 deps before building with qt6 --- .github/workflows/nightly-opensuse.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/nightly-opensuse.yml b/.github/workflows/nightly-opensuse.yml index 49c155a77..a7c2f83ef 100644 --- a/.github/workflows/nightly-opensuse.yml +++ b/.github/workflows/nightly-opensuse.yml @@ -31,7 +31,7 @@ jobs: uses: calamares/actions/generic-checkout@v5 - name: "install dependencies" shell: bash - run: ./ci/deps-opensuse.sh + run: ./ci/deps-opensuse-qt6.sh - name: "build" shell: bash run: ./ci/build.sh From 17b72b0fd879cf0d36c12a62f341ff0c55ed6acd Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Sat, 6 Apr 2024 23:08:02 +0200 Subject: [PATCH 17/47] CI: appstream-qt6 for opensuse builds --- ci/deps-opensuse-qt6.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ci/deps-opensuse-qt6.sh b/ci/deps-opensuse-qt6.sh index c1138c1c6..1c7f0a3f0 100755 --- a/ci/deps-opensuse-qt6.sh +++ b/ci/deps-opensuse-qt6.sh @@ -8,7 +8,7 @@ zypper --non-interactive addrepo -f -G https://download.opensuse.org/repositorie zypper --non-interactive refresh zypper --non-interactive up -zypper --non-interactive in git-core jq curl ninja +zypper --non-interactive in git-core jq yq curl ninja # From deploycala.py zypper --non-interactive in bison flex git make cmake gcc-c++ zypper --non-interactive in yaml-cpp-devel libpwquality-devel parted-devel python3-devel @@ -18,5 +18,5 @@ zypper --non-interactive in kf6-extra-cmake-modules zypper --non-interactive in "qt6-declarative-devel" "cmake(Qt6Concurrent)" "cmake(Qt6Gui)" "cmake(Qt6Network)" "cmake(Qt6Svg)" "cmake(Qt6Linguist)" zypper --non-interactive in "cmake(KF6CoreAddons)" "cmake(KF6DBusAddons)" "cmake(KF6Crash)" zypper --non-interactive in "cmake(KF6Parts)" # Also installs KF5 things -zypper --non-interactive in "cmake(PolkitQt6-1)" +zypper --non-interactive in "cmake(PolkitQt6-1)" appstream-qt6-devel true From 2c52adc8e8fb7e69237eda5559634153e28bd2b9 Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Sat, 6 Apr 2024 23:11:32 +0200 Subject: [PATCH 18/47] CI: expand functionality of build.sh Make it easier to "just do the nightly build" in a Docker setting by passing the name of the workflow to the script. --- CONTRIBUTING.md | 7 ++++++- ci/build.sh | 30 ++++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 36199a5ca..a73f90589 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -154,10 +154,15 @@ dependencies for the image (in this example, for openSUSE and Qt6). - `./ci/deps-opensuse-qt6.sh` Then run CMake (add any CMake options you like at the end) and ninja. -There is a script `ci/build.sh` that does this, too (without options). - `cmake -S /src -B /build -G Ninja` - `ninja -C /build` +There is a script `ci/build.sh` that does the CMake an ninja steps. +- If you set `CMAKE_ARGS` in the environment those extra CMake options are used. +- If you add an argument to the script command which names a workflow + (e.g. "nightly-opensuse-qt6") then `CMAKE_ARGS` are extracted from that + workflow and used for the build. + ### Running in Docker To run Calamares inside the container, or e.g. `loadmodule` to test diff --git a/ci/build.sh b/ci/build.sh index 60b3bc7ca..7108f9d1d 100755 --- a/ci/build.sh +++ b/ci/build.sh @@ -5,6 +5,36 @@ # - BUILDDIR (e.g. /build) # - CMAKE_ARGS (e.g. "-DWITH_QT6=ON -DCMAKE_BUILD_TYPE=Debug") # +# If SRCDIR is not set, it is assumed to be the directory above +# wherever this script is being run from (this script is in ci/). +# +# If BUILDDIR is not set, and /build exists (e.g. in the recommended +# Docker setup) then /build is used. +# +# If CMAKE_ARGS is not set, but the script is given an argument +# that exists as a workflow (e.g. "nightly-opensuse-qt6" or +# "nightly-debian.yml") and yq is installed, then the CMAKE_ARGS +# are extracted from that workflow file. +# +# Summary, pick one: +# - set environment variables, run "build.sh" +# - set no variables, run "build.sh " + +if test -z "$SRCDIR" ; then + _d=$(dirname "$0" ) + _d=$(dirname "$_d" ) + test -f "$_d/CMakeLists.txt" && SRCDIR="$_d" +fi +if test -z "$BUILDDIR" ; then + test -d "/build" && BUILDDIR=/build +fi +if test -z "$CMAKE_ARGS" -a -n "$1" ; then + test -x "$(which yq)" || { echo "! No yq command for finding CMAKE_ARGS for workflow $1" ; exit 1 ; } + _d="$SRCDIR/.github/workflows/$1" + test -f "$_d" || _d="$SRCDIR/.github/workflows/$1.yml" + test -f "$_d" || { echo "! No workflow $1" ; exit 1 ; } + CMAKE_ARGS=$(yq ".env.CMAKE_ARGS" "$_d") +fi # Sanity check test -n "$BUILDDIR" || { echo "! \$BUILDDIR not set" ; exit 1 ; } From 4406a879bcb192e544ef5b7a8465aa6e560c4b65 Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Sat, 6 Apr 2024 23:38:20 +0200 Subject: [PATCH 19/47] CI: use python-yaml instead of yq if needed (fedora) --- ci/build.sh | 9 +++++++-- ci/deps-fedora-qt6-boost.sh | 2 +- ci/deps-fedora-qt6.sh | 2 +- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/ci/build.sh b/ci/build.sh index 7108f9d1d..56ca770f1 100755 --- a/ci/build.sh +++ b/ci/build.sh @@ -29,11 +29,16 @@ if test -z "$BUILDDIR" ; then test -d "/build" && BUILDDIR=/build fi if test -z "$CMAKE_ARGS" -a -n "$1" ; then - test -x "$(which yq)" || { echo "! No yq command for finding CMAKE_ARGS for workflow $1" ; exit 1 ; } _d="$SRCDIR/.github/workflows/$1" test -f "$_d" || _d="$SRCDIR/.github/workflows/$1.yml" test -f "$_d" || { echo "! No workflow $1" ; exit 1 ; } - CMAKE_ARGS=$(yq ".env.CMAKE_ARGS" "$_d") + + if test -x "$(which yq)" ; then + CMAKE_ARGS=$(yq ".env.CMAKE_ARGS" "$_d") + else + CMAKE_ARGS=$(python3 -c 'import yaml ; f=open("'$_d'","r"); print(yaml.safe_load(f)["env"]["CMAKE_ARGS"]);') + fi + fi # Sanity check diff --git a/ci/deps-fedora-qt6-boost.sh b/ci/deps-fedora-qt6-boost.sh index 01e3e3449..7026927a7 100755 --- a/ci/deps-fedora-qt6-boost.sh +++ b/ci/deps-fedora-qt6-boost.sh @@ -4,7 +4,7 @@ # yum install -y bison flex git make cmake gcc-c++ ninja-build -yum install -y yaml-cpp-devel libpwquality-devel parted-devel python-devel gettext gettext-devel +yum install -y yaml-cpp-devel libpwquality-devel parted-devel python-devel gettext gettext-devel python3-pyyaml yum install -y libicu-devel libatasmart-devel yum install -y boost-devel # Qt6/KF6 dependencies diff --git a/ci/deps-fedora-qt6.sh b/ci/deps-fedora-qt6.sh index 2d2e83ce5..9f91ada18 100755 --- a/ci/deps-fedora-qt6.sh +++ b/ci/deps-fedora-qt6.sh @@ -4,7 +4,7 @@ # yum install -y bison flex git make cmake gcc-c++ ninja-build -yum install -y yaml-cpp-devel libpwquality-devel parted-devel python-devel gettext gettext-devel +yum install -y yaml-cpp-devel libpwquality-devel parted-devel python-devel gettext gettext-devel python3-pyyaml yum install -y libicu-devel libatasmart-devel # Qt6/KF6 dependencies yum install -y qt6-qtbase-devel qt6-linguist qt6-qtbase-private-devel qt6-qtdeclarative-devel qt6-qtsvg-devel qt6-qttools-devel From b78721d4b9f11648ec21aacfe2596ab5e194d381 Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Tue, 9 Apr 2024 15:56:03 +0200 Subject: [PATCH 20/47] Changes: update credits --- CHANGES-3.3 | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/CHANGES-3.3 b/CHANGES-3.3 index 1aff89918..d167685ff 100644 --- a/CHANGES-3.3 +++ b/CHANGES-3.3 @@ -7,6 +7,23 @@ contributors are listed. Note that Calamares does not have a historical changelog -- this log starts with version 3.3.0. See CHANGES-3.2 for the history of the 3.2 series (2018-05 - 2022-08). +# 3.3.6 (unreleased) +This release contains contributions from (alphabetically by first name): + - Adriaan de Groot + - Anke Boersma + - Evan James + - Harald Sitter + - Mike Stemle + - Peter Jung + +## Core ## +- Various Qt6-related fixes. + +## Modules ## + - *plymouthcfg* Use plymouth-set-default-theme to avoid issues with + configuration. (thanks Peter) + + # 3.3.5 (2024-03-03) This release contains contributions from (alphabetically by first name): @@ -24,7 +41,6 @@ This release contains contributions from (alphabetically by first name): - *displaymanager* module can configure an alternate SDDM configuration file. - *networkcfg* a bug affecting NetPlan + NetworkManager was fixed. - *initcpiocfg* Add microcode hook to initcpiocfg - - *plymouthcfg* Use plymouth-set-default-theme to avoid issues with configuration # 3.3.4 (2024-02-27) From 1645874973669475954e039fbfbd5b3d126fa938 Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Tue, 9 Apr 2024 15:57:30 +0200 Subject: [PATCH 21/47] Changes: post-release housekeeping --- CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 7951b8e8f..6ef843413 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -47,8 +47,8 @@ cmake_minimum_required(VERSION 3.16 FATAL_ERROR) -set(CALAMARES_VERSION 3.3.5) -set(CALAMARES_RELEASE_MODE ON) # Set to ON during a release +set(CALAMARES_VERSION 3.3.6) +set(CALAMARES_RELEASE_MODE OFF) # Set to ON during a release if(CMAKE_SCRIPT_MODE_FILE) include(${CMAKE_CURRENT_LIST_DIR}/CMakeModules/ExtendedVersion.cmake) From 51d6dd23ea09f65168cf4c011a951c1a9a5a9a4a Mon Sep 17 00:00:00 2001 From: "Eugene San (eugenesan)" Date: Thu, 11 Apr 2024 20:00:41 -0700 Subject: [PATCH 22/47] Mirror GRUB's splash screen functionality for non-grub booloaders --- src/modules/bootloader/main.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/modules/bootloader/main.py b/src/modules/bootloader/main.py index 189902839..fe4ba2a1a 100644 --- a/src/modules/bootloader/main.py +++ b/src/modules/bootloader/main.py @@ -127,7 +127,6 @@ def is_zfs_root(partition): def get_kernel_params(uuid): kernel_params = libcalamares.job.configuration.get("kernelParams", ["quiet"]) - kernel_params.append("rw") partitions = libcalamares.globalstorage.value("partitions") swap_uuid = "" @@ -136,11 +135,17 @@ def get_kernel_params(uuid): cryptdevice_params = [] + has_plymouth = libcalamares.utils.target_env_call(["sh", "-c", "which plymouth"]) == 0 has_dracut = libcalamares.utils.target_env_call(["sh", "-c", "which dracut"]) == 0 uses_systemd_hook = libcalamares.utils.target_env_call(["sh", "-c", "grep -q \"^HOOKS.*systemd\" /etc/mkinitcpio.conf"]) == 0 use_systemd_naming = has_dracut or uses_systemd_hook + # If plymouth installed, add splash screen parameter early + if has_plymouth: + kernel_params.append("splash") + + kernel_params.append("rw") # Take over swap settings: # - unencrypted swap partition sets swap_uuid From b8049daa454d647bb2732dbb9f6f155b01a4a05b Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Fri, 12 Apr 2024 21:39:50 +0200 Subject: [PATCH 23/47] [bootloader] Factor out running "which" in target which is an actual program, drop the round-about via shell --- src/modules/bootloader/main.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/modules/bootloader/main.py b/src/modules/bootloader/main.py index fe4ba2a1a..6fe01f8bc 100644 --- a/src/modules/bootloader/main.py +++ b/src/modules/bootloader/main.py @@ -125,6 +125,11 @@ def is_zfs_root(partition): return partition["mountPoint"] == "/" and partition["fs"] == "zfs" +def have_program_in_target(program : str): + """Returns @c True if @p program is in path in the target""" + return libcalamares.utils.target_env_call(["/usr/bin/which", program]) == 0 + + def get_kernel_params(uuid): kernel_params = libcalamares.job.configuration.get("kernelParams", ["quiet"]) @@ -135,8 +140,8 @@ def get_kernel_params(uuid): cryptdevice_params = [] - has_plymouth = libcalamares.utils.target_env_call(["sh", "-c", "which plymouth"]) == 0 - has_dracut = libcalamares.utils.target_env_call(["sh", "-c", "which dracut"]) == 0 + has_plymouth = have_program_in_target("plymouth") + has_dracut = have_program_in_target("dracut") uses_systemd_hook = libcalamares.utils.target_env_call(["sh", "-c", "grep -q \"^HOOKS.*systemd\" /etc/mkinitcpio.conf"]) == 0 use_systemd_naming = has_dracut or uses_systemd_hook From b4edf5219805e48be1b703420fc2deddbf936cdd Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Fri, 12 Apr 2024 21:55:26 +0200 Subject: [PATCH 24/47] [bootloader] Avoid indirection through shell There's no need to run the shell to take apart the arguments to grep. --- src/modules/bootloader/main.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/modules/bootloader/main.py b/src/modules/bootloader/main.py index 6fe01f8bc..d09508fae 100644 --- a/src/modules/bootloader/main.py +++ b/src/modules/bootloader/main.py @@ -142,8 +142,7 @@ def get_kernel_params(uuid): has_plymouth = have_program_in_target("plymouth") has_dracut = have_program_in_target("dracut") - uses_systemd_hook = libcalamares.utils.target_env_call(["sh", "-c", - "grep -q \"^HOOKS.*systemd\" /etc/mkinitcpio.conf"]) == 0 + uses_systemd_hook = libcalamares.utils.target_env_call(["/usr/bin/grep", "-q", "^HOOKS.*systemd", "/etc/mkinitcpio.conf"]) == 0 use_systemd_naming = has_dracut or uses_systemd_hook # If plymouth installed, add splash screen parameter early From 6d242b67f943fec6ee6abb532fff1fc83ff9c1c1 Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Fri, 12 Apr 2024 22:05:47 +0200 Subject: [PATCH 25/47] [bootloader] Start building kernel parameters earlier - drop unnecessary variable - build "quiet splash rw" first --- src/modules/bootloader/main.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/modules/bootloader/main.py b/src/modules/bootloader/main.py index d09508fae..89058eaae 100644 --- a/src/modules/bootloader/main.py +++ b/src/modules/bootloader/main.py @@ -131,7 +131,12 @@ def have_program_in_target(program : str): def get_kernel_params(uuid): + # Configured kernel parameters (default "quiet"), if plymouth installed, add splash + # screen parameter and then "rw". kernel_params = libcalamares.job.configuration.get("kernelParams", ["quiet"]) + if have_program_in_target("plymouth"): + kernel_params.append("splash") + kernel_params.append("rw") partitions = libcalamares.globalstorage.value("partitions") swap_uuid = "" @@ -140,17 +145,10 @@ def get_kernel_params(uuid): cryptdevice_params = [] - has_plymouth = have_program_in_target("plymouth") has_dracut = have_program_in_target("dracut") uses_systemd_hook = libcalamares.utils.target_env_call(["/usr/bin/grep", "-q", "^HOOKS.*systemd", "/etc/mkinitcpio.conf"]) == 0 use_systemd_naming = has_dracut or uses_systemd_hook - # If plymouth installed, add splash screen parameter early - if has_plymouth: - kernel_params.append("splash") - - kernel_params.append("rw") - # Take over swap settings: # - unencrypted swap partition sets swap_uuid # - encrypted root sets cryptdevice_params From f6cd82f9a4d1c2420991e14e65680fd8c2d51379 Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Fri, 12 Apr 2024 22:08:54 +0200 Subject: [PATCH 26/47] [bootloader] Group local variables Put the constants -- do we use systemd, what are the partitions -- together, and put the things-set-by-the-loop together. --- src/modules/bootloader/main.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/modules/bootloader/main.py b/src/modules/bootloader/main.py index 89058eaae..0a9e96598 100644 --- a/src/modules/bootloader/main.py +++ b/src/modules/bootloader/main.py @@ -138,17 +138,15 @@ def get_kernel_params(uuid): kernel_params.append("splash") kernel_params.append("rw") + use_systemd_naming = have_program_in_target("dracut") or (libcalamares.utils.target_env_call(["/usr/bin/grep", "-q", "^HOOKS.*systemd", "/etc/mkinitcpio.conf"]) == 0) + partitions = libcalamares.globalstorage.value("partitions") + + cryptdevice_params = [] swap_uuid = "" swap_outer_mappername = None swap_outer_uuid = None - cryptdevice_params = [] - - has_dracut = have_program_in_target("dracut") - uses_systemd_hook = libcalamares.utils.target_env_call(["/usr/bin/grep", "-q", "^HOOKS.*systemd", "/etc/mkinitcpio.conf"]) == 0 - use_systemd_naming = has_dracut or uses_systemd_hook - # Take over swap settings: # - unencrypted swap partition sets swap_uuid # - encrypted root sets cryptdevice_params From 131b2eded169d8785aeb94911e432a25fb7941ea Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Fri, 12 Apr 2024 22:10:39 +0200 Subject: [PATCH 27/47] Changes: update credits --- CHANGES-3.3 | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES-3.3 b/CHANGES-3.3 index d167685ff..2db789bcc 100644 --- a/CHANGES-3.3 +++ b/CHANGES-3.3 @@ -11,6 +11,7 @@ the history of the 3.2 series (2018-05 - 2022-08). This release contains contributions from (alphabetically by first name): - Adriaan de Groot - Anke Boersma + - Eugene Sam - Evan James - Harald Sitter - Mike Stemle @@ -20,6 +21,8 @@ This release contains contributions from (alphabetically by first name): - Various Qt6-related fixes. ## Modules ## + - *bootloader* Adds "splash" to kernel parameters if plymouth is present. + (thanks Eugene) - *plymouthcfg* Use plymouth-set-default-theme to avoid issues with configuration. (thanks Peter) From fb83ad326bf52f8dbc4382949a93496e1026582f Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Sat, 13 Apr 2024 23:30:57 +0200 Subject: [PATCH 28/47] [libcalamares] Document recommended translation contexts Jobs have specific contexts for their different strings. Many jobs (now) misuse prettyName() as a status message, although the existing fallback mechanism means that the name gets used as if it is a status message when the job does not define a description or status. --- src/libcalamares/Job.h | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/libcalamares/Job.h b/src/libcalamares/Job.h index 241b2883c..fe678fa63 100644 --- a/src/libcalamares/Job.h +++ b/src/libcalamares/Job.h @@ -109,12 +109,23 @@ public: * which of the jobs is "heavy" and which is not. */ virtual int getJobWeight() const; + /** @brief The human-readable name of this job * * This should be a very short statement of what the job does. * For status and state information, see prettyStatusMessage(). + * + * The job's name may be similar to the status message, but this is + * a name, and should not be an active verb phrase. The translation + * should use context @c @label . + * + * The name of the job is used as a **fallback** when the status + * or descriptions are empty. If a job has no implementation of + * those methods, it is OK to use other contexts, but it may look + * strange in some places in the UI. */ virtual QString prettyName() const = 0; + /** @brief a longer human-readable description of what the job will do * * This **may** be used by view steps to fill in the summary @@ -122,15 +133,23 @@ public: * module does so. * * The default implementation returns an empty string. + * + * The translation should use context @c @title . */ virtual QString prettyDescription() const; + /** @brief A human-readable status for progress reporting * * This is called from the JobQueue when progress is made, and should * return a not-too-long description of the job's status. This * is made visible in the progress bar of the execution view step. + * + * The job's status should say **what** the job is doing. It should be in + * present active tense. Typically the translation uses tr() context + * @c @status . See prettyName() for examples. */ virtual QString prettyStatusMessage() const; + virtual JobResult exec() = 0; bool isEmergency() const { return m_emergency; } From fe68b45fc56f20ae1152bc10beb308fe06deba54 Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Sat, 13 Apr 2024 23:37:49 +0200 Subject: [PATCH 29/47] [libcalamares] Explain updating-status-text mechanism --- src/libcalamares/JobQueue.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/libcalamares/JobQueue.cpp b/src/libcalamares/JobQueue.cpp index 64cb10e88..c7548cc5c 100644 --- a/src/libcalamares/JobQueue.cpp +++ b/src/libcalamares/JobQueue.cpp @@ -188,6 +188,13 @@ private: // starts the job, or if the job itself reports 0.0) be more // accepting in what gets reported: jobs with no status fall // back to description and name, whichever is non-empty. + // + // Later calls (e.g. when percentage > 0) use the status unchanged. + // It may be empty, but the ExecutionViewStep knows about empty + // status messages and does not update the text in that case. + // + // This means that a Job can implement just prettyName() and get + // a reasonable "status" message which will update only once. if ( percentage == 0.0 && message.isEmpty() ) { message = jobitem.job->prettyDescription(); From d0420ea3ee1010cc998f3e864f4eb60f1d9daa06 Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Sun, 14 Apr 2024 00:20:57 +0200 Subject: [PATCH 30/47] [users] Add translation context to AD job --- src/modules/users/ActiveDirectoryJob.cpp | 10 ++-------- src/modules/users/ActiveDirectoryJob.h | 1 - 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/src/modules/users/ActiveDirectoryJob.cpp b/src/modules/users/ActiveDirectoryJob.cpp index 33706c48f..7f24f5931 100644 --- a/src/modules/users/ActiveDirectoryJob.cpp +++ b/src/modules/users/ActiveDirectoryJob.cpp @@ -30,19 +30,13 @@ ActiveDirectoryJob::ActiveDirectoryJob(QStringList& activeDirectoryInfo) QString ActiveDirectoryJob::prettyName() const { - return tr( "Enroll system in Active Directory" ); -} - -QString -ActiveDirectoryJob::prettyDescription() const -{ - return tr( "Enroll system in Active Directory" ); + return tr( "Enroll system in Active Directory", "@label" ); } QString ActiveDirectoryJob::prettyStatusMessage() const { - return tr( "Enrolling system in Active Directory" ); + return tr( "Enrolling system in Active Directory…", "@status" ); } Calamares::JobResult diff --git a/src/modules/users/ActiveDirectoryJob.h b/src/modules/users/ActiveDirectoryJob.h index 8877ef7e1..035c93ae2 100644 --- a/src/modules/users/ActiveDirectoryJob.h +++ b/src/modules/users/ActiveDirectoryJob.h @@ -18,7 +18,6 @@ class ActiveDirectoryJob : public Calamares::Job public: ActiveDirectoryJob( QStringList& activeDirectoryInfo ); QString prettyName() const override; - QString prettyDescription() const override; QString prettyStatusMessage() const override; Calamares::JobResult exec() override; From 2127129227cf998f1f9f11b84a20ce6e541268f3 Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Sun, 14 Apr 2024 09:25:03 +0200 Subject: [PATCH 31/47] [users] Use API to get file-paths in target --- src/modules/users/ActiveDirectoryJob.cpp | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/modules/users/ActiveDirectoryJob.cpp b/src/modules/users/ActiveDirectoryJob.cpp index 7f24f5931..5db18d2e5 100644 --- a/src/modules/users/ActiveDirectoryJob.cpp +++ b/src/modules/users/ActiveDirectoryJob.cpp @@ -47,11 +47,8 @@ ActiveDirectoryJob::exec() QString domain = m_activeDirectoryInfo.value(2); QString ip = m_activeDirectoryInfo.value(3); - Calamares::GlobalStorage* gs = Calamares::JobQueue::instance()->globalStorage(); - QString rootMountPoint = gs ? gs->value("rootMountPoint").toString() : QString(); - if (!ip.isEmpty()) { - QString hostsFilePath = !rootMountPoint.isEmpty() ? rootMountPoint + "/etc/hosts" : "/etc/hosts"; + const QString hostsFilePath = Calamares::System::instance()->targetPath(QStringLiteral("/etc/hosts"));; QFile hostsFile(hostsFilePath); if (hostsFile.open(QIODevice::Append | QIODevice::Text)) { QTextStream out(&hostsFile); @@ -62,7 +59,7 @@ ActiveDirectoryJob::exec() } } - QString installPath = !rootMountPoint.isEmpty() ? rootMountPoint : "/"; + const QString installPath = Calamares::System::instance()->targetPath(QStringLiteral("/")); QStringList args = {"join", domain, "-U", username, "--install=" + installPath, "--verbose"}; QProcess process; From 28a10cf8c3ac99fe59ffc362e83eb6f21868a8c3 Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Sun, 14 Apr 2024 09:53:27 +0200 Subject: [PATCH 32/47] [users] Use more structured data than QStringList While here, apply coding style. --- src/modules/users/ActiveDirectoryJob.cpp | 60 ++++++++++++++---------- src/modules/users/ActiveDirectoryJob.h | 10 +++- src/modules/users/Config.cpp | 22 +++------ src/modules/users/Config.h | 3 -- 4 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/modules/users/ActiveDirectoryJob.cpp b/src/modules/users/ActiveDirectoryJob.cpp index 5db18d2e5..740431cef 100644 --- a/src/modules/users/ActiveDirectoryJob.cpp +++ b/src/modules/users/ActiveDirectoryJob.cpp @@ -18,12 +18,18 @@ #include #include #include -#include #include +#include -ActiveDirectoryJob::ActiveDirectoryJob(QStringList& activeDirectoryInfo) +ActiveDirectoryJob::ActiveDirectoryJob( const QString& adminLogin, + const QString& adminPassword, + const QString& domain, + const QString& ip ) : Calamares::Job() - , m_activeDirectoryInfo(activeDirectoryInfo) + , m_adminLogin( adminLogin ) + , m_adminPassword( adminPassword ) + , m_domain( domain ) + , m_ip( ip ) { } @@ -42,41 +48,45 @@ ActiveDirectoryJob::prettyStatusMessage() const Calamares::JobResult ActiveDirectoryJob::exec() { - QString username = m_activeDirectoryInfo.value(0); - QString password = m_activeDirectoryInfo.value(1); - QString domain = m_activeDirectoryInfo.value(2); - QString ip = m_activeDirectoryInfo.value(3); - - if (!ip.isEmpty()) { - const QString hostsFilePath = Calamares::System::instance()->targetPath(QStringLiteral("/etc/hosts"));; - QFile hostsFile(hostsFilePath); - if (hostsFile.open(QIODevice::Append | QIODevice::Text)) { - QTextStream out(&hostsFile); - out << ip << " " << domain << "\n"; + if ( !m_ip.isEmpty() ) + { + const QString hostsFilePath = Calamares::System::instance()->targetPath( QStringLiteral( "/etc/hosts" ) ); + ; + QFile hostsFile( hostsFilePath ); + if ( hostsFile.open( QIODevice::Append | QIODevice::Text ) ) + { + QTextStream out( &hostsFile ); + out << m_ip << " " << m_domain << "\n"; hostsFile.close(); - } else { - return Calamares::JobResult::error("Failed to open /etc/hosts for writing."); + } + else + { + return Calamares::JobResult::error( "Failed to open /etc/hosts for writing." ); } } - const QString installPath = Calamares::System::instance()->targetPath(QStringLiteral("/")); - QStringList args = {"join", domain, "-U", username, "--install=" + installPath, "--verbose"}; + const QString installPath = Calamares::System::instance()->targetPath( QStringLiteral( "/" ) ); + QStringList args = { "join", m_domain, "-U", m_adminLogin, "--install=" + installPath, "--verbose" }; QProcess process; - process.start("realm", args); + process.start( "realm", args ); process.waitForStarted(); - if (!password.isEmpty()) { - process.write((password + "\n").toUtf8()); + if ( !m_adminPassword.isEmpty() ) + { + process.write( ( m_adminPassword + "\n" ).toUtf8() ); process.closeWriteChannel(); } - process.waitForFinished(-1); + process.waitForFinished( -1 ); - if (process.exitCode() == 0) { + if ( process.exitCode() == 0 ) + { return Calamares::JobResult::ok(); - } else { + } + else + { QString errorOutput = process.readAllStandardError(); - return Calamares::JobResult::error(QString("Failed to join realm: %1").arg(errorOutput)); + return Calamares::JobResult::error( QString( "Failed to join realm: %1" ).arg( errorOutput ) ); } } diff --git a/src/modules/users/ActiveDirectoryJob.h b/src/modules/users/ActiveDirectoryJob.h index 035c93ae2..77fd74057 100644 --- a/src/modules/users/ActiveDirectoryJob.h +++ b/src/modules/users/ActiveDirectoryJob.h @@ -16,13 +16,19 @@ class ActiveDirectoryJob : public Calamares::Job { Q_OBJECT public: - ActiveDirectoryJob( QStringList& activeDirectoryInfo ); + ActiveDirectoryJob( const QString& adminLogin, + const QString& adminPassword, + const QString& domain, + const QString& ip ); QString prettyName() const override; QString prettyStatusMessage() const override; Calamares::JobResult exec() override; private: - QStringList m_activeDirectoryInfo; + QString m_adminLogin; // Admin credentials to do the enrollment + QString m_adminPassword; + QString m_domain; + QString m_ip; }; #endif /* ACTIVEDIRECTORYJOB_H */ diff --git a/src/modules/users/Config.cpp b/src/modules/users/Config.cpp index 4a36d6d7b..2aaf3f352 100644 --- a/src/modules/users/Config.cpp +++ b/src/modules/users/Config.cpp @@ -674,40 +674,29 @@ Config::getActiveDirectoryUsed() const } void -Config::setActiveDirectoryAdminUsername( const QString & s ) +Config::setActiveDirectoryAdminUsername( const QString& s ) { m_activeDirectoryUsername = s; } void -Config::setActiveDirectoryAdminPassword( const QString & s ) +Config::setActiveDirectoryAdminPassword( const QString& s ) { m_activeDirectoryPassword = s; } void -Config::setActiveDirectoryDomain( const QString & s ) +Config::setActiveDirectoryDomain( const QString& s ) { m_activeDirectoryDomain = s; } void -Config::setActiveDirectoryIP( const QString & s ) +Config::setActiveDirectoryIP( const QString& s ) { m_activeDirectoryIP = s; } -QStringList& -Config::getActiveDirectory() const -{ - m_activeDirectorySettings.clear(); - m_activeDirectorySettings << m_activeDirectoryUsername - << m_activeDirectoryPassword - << m_activeDirectoryDomain - << m_activeDirectoryIP; - return m_activeDirectorySettings; -} - QString Config::rootPassword() const { @@ -1045,7 +1034,8 @@ Config::createJobs() const jobs.append( Calamares::job_ptr( j ) ); } - j = new ActiveDirectoryJob( getActiveDirectory() ); + j = new ActiveDirectoryJob( + m_activeDirectoryUsername, m_activeDirectoryPassword, m_activeDirectoryDomain, m_activeDirectoryIP ); jobs.append( Calamares::job_ptr( j ) ); j = new SetupGroupsJob( this ); diff --git a/src/modules/users/Config.h b/src/modules/users/Config.h index afdfa8efd..e49160f4f 100644 --- a/src/modules/users/Config.h +++ b/src/modules/users/Config.h @@ -230,8 +230,6 @@ public: bool getActiveDirectoryEnabled() const; /// Is it both enabled and activated? bool getActiveDirectoryUsed() const; - /// Config for Active Directory - QStringList& getActiveDirectory() const; const QList< GroupDescription >& defaultGroups() const { return m_defaultGroups; } /** @brief the names of all the groups for the current user @@ -355,7 +353,6 @@ private: bool m_isReady = false; ///< Used to reduce readyChanged signals - mutable QStringList m_activeDirectorySettings; bool m_activeDirectory = false; bool m_activeDirectoryUsed = false; QString m_activeDirectoryUsername; From c61399bc8a69074b88fadc0669af79b8800db186 Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Sun, 14 Apr 2024 09:53:56 +0200 Subject: [PATCH 33/47] [users] Apply coding style to unrelated files --- src/modules/users/Tests.cpp | 2 +- src/modules/users/UsersPage.cpp | 41 ++++++++++++++++++--------------- 2 files changed, 23 insertions(+), 20 deletions(-) diff --git a/src/modules/users/Tests.cpp b/src/modules/users/Tests.cpp index 1f9efc35e..ba2fd2dab 100644 --- a/src/modules/users/Tests.cpp +++ b/src/modules/users/Tests.cpp @@ -340,7 +340,7 @@ UserTests::testPasswordChecks() QCOMPARE( l.length(), 0 ); QVERIFY( !addPasswordCheck( "nonempty", QVariant( false ), l ) ); // legacy option, now ignored QCOMPARE( l.length(), 0 ); - QVERIFY( !addPasswordCheck( "nonempty", QVariant( true ), l ) ); // still ignored + QVERIFY( !addPasswordCheck( "nonempty", QVariant( true ), l ) ); // still ignored QCOMPARE( l.length(), 0 ); } } diff --git a/src/modules/users/UsersPage.cpp b/src/modules/users/UsersPage.cpp index 269312330..d31f42f5f 100644 --- a/src/modules/users/UsersPage.cpp +++ b/src/modules/users/UsersPage.cpp @@ -163,26 +163,29 @@ UsersPage::UsersPage( Config* config, QWidget* parent ) } // Active Directory is not checked or enabled by default - ui->useADCheckbox->setVisible(m_config->getActiveDirectoryEnabled()); - ui->domainLabel->setVisible(false); - ui->domainField->setVisible(false); - ui->domainAdminLabel->setVisible(false); - ui->domainAdminField->setVisible(false); - ui->domainPasswordField->setVisible(false); - ui->domainPasswordLabel->setVisible(false); - ui->ipAddressField->setVisible(false); - ui->ipAddressLabel->setVisible(false); + ui->useADCheckbox->setVisible( m_config->getActiveDirectoryEnabled() ); + ui->domainLabel->setVisible( false ); + ui->domainField->setVisible( false ); + ui->domainAdminLabel->setVisible( false ); + ui->domainAdminField->setVisible( false ); + ui->domainPasswordField->setVisible( false ); + ui->domainPasswordLabel->setVisible( false ); + ui->ipAddressField->setVisible( false ); + ui->ipAddressLabel->setVisible( false ); - connect(ui->useADCheckbox, &QCheckBox::toggled, [=](bool checked){ - ui->domainLabel->setVisible(checked); - ui->domainField->setVisible(checked); - ui->domainAdminLabel->setVisible(checked); - ui->domainAdminField->setVisible(checked); - ui->domainPasswordField->setVisible(checked); - ui->domainPasswordLabel->setVisible(checked); - ui->ipAddressField->setVisible(checked); - ui->ipAddressLabel->setVisible(checked); - }); + connect( ui->useADCheckbox, + &QCheckBox::toggled, + [ = ]( bool checked ) + { + ui->domainLabel->setVisible( checked ); + ui->domainField->setVisible( checked ); + ui->domainAdminLabel->setVisible( checked ); + ui->domainAdminField->setVisible( checked ); + ui->domainPasswordField->setVisible( checked ); + ui->domainPasswordLabel->setVisible( checked ); + ui->ipAddressField->setVisible( checked ); + ui->ipAddressLabel->setVisible( checked ); + } ); connect( ui->domainField, &QLineEdit::textChanged, config, &Config::setActiveDirectoryDomain ); connect( ui->domainAdminField, &QLineEdit::textChanged, config, &Config::setActiveDirectoryAdminUsername ); From f674214741c5bf96d43f6c629d71919b96046aac Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Sun, 14 Apr 2024 21:07:31 +0200 Subject: [PATCH 34/47] [user] Rename members to emphasize these are *admin* credentials --- src/modules/users/Config.cpp | 6 +++--- src/modules/users/Config.h | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/modules/users/Config.cpp b/src/modules/users/Config.cpp index 2aaf3f352..b98919bde 100644 --- a/src/modules/users/Config.cpp +++ b/src/modules/users/Config.cpp @@ -676,13 +676,13 @@ Config::getActiveDirectoryUsed() const void Config::setActiveDirectoryAdminUsername( const QString& s ) { - m_activeDirectoryUsername = s; + m_activeDirectoryAdminUsername = s; } void Config::setActiveDirectoryAdminPassword( const QString& s ) { - m_activeDirectoryPassword = s; + m_activeDirectoryAdminPassword = s; } void @@ -1035,7 +1035,7 @@ Config::createJobs() const } j = new ActiveDirectoryJob( - m_activeDirectoryUsername, m_activeDirectoryPassword, m_activeDirectoryDomain, m_activeDirectoryIP ); + m_activeDirectoryAdminUsername, m_activeDirectoryAdminPassword, m_activeDirectoryDomain, m_activeDirectoryIP ); jobs.append( Calamares::job_ptr( j ) ); j = new SetupGroupsJob( this ); diff --git a/src/modules/users/Config.h b/src/modules/users/Config.h index e49160f4f..fee1c3df8 100644 --- a/src/modules/users/Config.h +++ b/src/modules/users/Config.h @@ -355,8 +355,8 @@ private: bool m_activeDirectory = false; bool m_activeDirectoryUsed = false; - QString m_activeDirectoryUsername; - QString m_activeDirectoryPassword; + QString m_activeDirectoryAdminUsername; + QString m_activeDirectoryAdminPassword; QString m_activeDirectoryDomain; QString m_activeDirectoryIP; From 0ef3842da6072e0607e9f80b60d13b7b98d64278 Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Sun, 14 Apr 2024 21:33:39 +0200 Subject: [PATCH 35/47] [users] Factor out show/hide of AD controls While here, expand documentation a little. --- src/modules/users/Config.h | 4 ++-- src/modules/users/UsersPage.cpp | 40 ++++++++++++++------------------- src/modules/users/UsersPage.h | 2 ++ 3 files changed, 21 insertions(+), 25 deletions(-) diff --git a/src/modules/users/Config.h b/src/modules/users/Config.h index fee1c3df8..07fa40d1f 100644 --- a/src/modules/users/Config.h +++ b/src/modules/users/Config.h @@ -226,9 +226,9 @@ public: bool permitWeakPasswords() const { return m_permitWeakPasswords; } /// Current setting for "require strong password"? bool requireStrongPasswords() const { return m_requireStrongPasswords; } - /// Is Active Directory enabled? + /// Is Active Directory enabled in the config file? bool getActiveDirectoryEnabled() const; - /// Is it both enabled and activated? + /// Is it both enabled and activated by user choice (checkbox)? bool getActiveDirectoryUsed() const; const QList< GroupDescription >& defaultGroups() const { return m_defaultGroups; } diff --git a/src/modules/users/UsersPage.cpp b/src/modules/users/UsersPage.cpp index d31f42f5f..1ecc0ebef 100644 --- a/src/modules/users/UsersPage.cpp +++ b/src/modules/users/UsersPage.cpp @@ -164,34 +164,13 @@ UsersPage::UsersPage( Config* config, QWidget* parent ) // Active Directory is not checked or enabled by default ui->useADCheckbox->setVisible( m_config->getActiveDirectoryEnabled() ); - ui->domainLabel->setVisible( false ); - ui->domainField->setVisible( false ); - ui->domainAdminLabel->setVisible( false ); - ui->domainAdminField->setVisible( false ); - ui->domainPasswordField->setVisible( false ); - ui->domainPasswordLabel->setVisible( false ); - ui->ipAddressField->setVisible( false ); - ui->ipAddressLabel->setVisible( false ); - - connect( ui->useADCheckbox, - &QCheckBox::toggled, - [ = ]( bool checked ) - { - ui->domainLabel->setVisible( checked ); - ui->domainField->setVisible( checked ); - ui->domainAdminLabel->setVisible( checked ); - ui->domainAdminField->setVisible( checked ); - ui->domainPasswordField->setVisible( checked ); - ui->domainPasswordLabel->setVisible( checked ); - ui->ipAddressField->setVisible( checked ); - ui->ipAddressLabel->setVisible( checked ); - } ); + onActiveDirectoryToggled( false ); + connect( ui->useADCheckbox, &QCheckBox::toggled, this, &UsersPage::onActiveDirectoryToggled ); connect( ui->domainField, &QLineEdit::textChanged, config, &Config::setActiveDirectoryDomain ); connect( ui->domainAdminField, &QLineEdit::textChanged, config, &Config::setActiveDirectoryAdminUsername ); connect( ui->domainPasswordField, &QLineEdit::textChanged, config, &Config::setActiveDirectoryAdminPassword ); connect( ui->ipAddressField, &QLineEdit::textChanged, config, &Config::setActiveDirectoryIP ); - connect( ui->useADCheckbox, &QCheckBox::toggled, config, &Config::setActiveDirectoryUsed ); CALAMARES_RETRANSLATE_SLOT( &UsersPage::retranslate ); @@ -314,3 +293,18 @@ UsersPage::onReuseUserPasswordChanged( const int checked ) ui->textBoxRootPassword->setVisible( visible ); ui->textBoxVerifiedRootPassword->setVisible( visible ); } + +void +UsersPage::onActiveDirectoryToggled( bool checked ) +{ + ui->domainLabel->setVisible( checked ); + ui->domainField->setVisible( checked ); + ui->domainAdminLabel->setVisible( checked ); + ui->domainAdminField->setVisible( checked ); + ui->domainPasswordField->setVisible( checked ); + ui->domainPasswordLabel->setVisible( checked ); + ui->ipAddressField->setVisible( checked ); + ui->ipAddressLabel->setVisible( checked ); + + m_config->setActiveDirectoryUsed( checked ); +} diff --git a/src/modules/users/UsersPage.h b/src/modules/users/UsersPage.h index 2d48f1fa3..379176ab2 100644 --- a/src/modules/users/UsersPage.h +++ b/src/modules/users/UsersPage.h @@ -44,6 +44,8 @@ protected slots: void reportUserPasswordStatus( int, const QString& ); void reportRootPasswordStatus( int, const QString& ); + void onActiveDirectoryToggled( bool checked ); + private: void retranslate(); From dfe07a51a34083b0186a8f524d5e96138e44be52 Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Sun, 14 Apr 2024 21:41:57 +0200 Subject: [PATCH 36/47] [users] Only run AD job if it is enabled (user choice) --- src/modules/users/Config.cpp | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/modules/users/Config.cpp b/src/modules/users/Config.cpp index b98919bde..cd56bc3e2 100644 --- a/src/modules/users/Config.cpp +++ b/src/modules/users/Config.cpp @@ -1034,9 +1034,14 @@ Config::createJobs() const jobs.append( Calamares::job_ptr( j ) ); } - j = new ActiveDirectoryJob( - m_activeDirectoryAdminUsername, m_activeDirectoryAdminPassword, m_activeDirectoryDomain, m_activeDirectoryIP ); - jobs.append( Calamares::job_ptr( j ) ); + if ( getActiveDirectoryUsed() ) + { + j = new ActiveDirectoryJob( m_activeDirectoryAdminUsername, + m_activeDirectoryAdminPassword, + m_activeDirectoryDomain, + m_activeDirectoryIP ); + jobs.append( Calamares::job_ptr( j ) ); + } j = new SetupGroupsJob( this ); jobs.append( Calamares::job_ptr( j ) ); From 51e05241fefa683504921b97ca2d37920d4d8f20 Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Sun, 14 Apr 2024 22:15:13 +0200 Subject: [PATCH 37/47] [users] Use Calamares command-running API --- src/modules/users/ActiveDirectoryJob.cpp | 23 ++++++++--------------- 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/src/modules/users/ActiveDirectoryJob.cpp b/src/modules/users/ActiveDirectoryJob.cpp index 740431cef..deb4c82d7 100644 --- a/src/modules/users/ActiveDirectoryJob.cpp +++ b/src/modules/users/ActiveDirectoryJob.cpp @@ -66,27 +66,20 @@ ActiveDirectoryJob::exec() } const QString installPath = Calamares::System::instance()->targetPath( QStringLiteral( "/" ) ); - QStringList args = { "join", m_domain, "-U", m_adminLogin, "--install=" + installPath, "--verbose" }; + auto r = Calamares::System::instance()->runCommand( + Calamares::System::RunLocation::RunInHost, + { "realm", "join", m_domain, "-U", m_adminLogin, "--install=" + installPath, "--verbose" }, + QString(), + m_adminPassword, + std::chrono::seconds( 30 ) ); - QProcess process; - process.start( "realm", args ); - process.waitForStarted(); - if ( !m_adminPassword.isEmpty() ) - { - process.write( ( m_adminPassword + "\n" ).toUtf8() ); - process.closeWriteChannel(); - } - - process.waitForFinished( -1 ); - - if ( process.exitCode() == 0 ) + if ( r.getExitCode() == 0 ) { return Calamares::JobResult::ok(); } else { - QString errorOutput = process.readAllStandardError(); - return Calamares::JobResult::error( QString( "Failed to join realm: %1" ).arg( errorOutput ) ); + return Calamares::JobResult::error( QString( "Failed to join realm: %1" ).arg( r.getOutput() ) ); } } From 685e9db3f79611430cf6ce489a6b81cddb0acd4d Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Sun, 14 Apr 2024 22:16:40 +0200 Subject: [PATCH 38/47] Changes: credits --- CHANGES-3.3 | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES-3.3 b/CHANGES-3.3 index 2db789bcc..a427dc26e 100644 --- a/CHANGES-3.3 +++ b/CHANGES-3.3 @@ -16,6 +16,7 @@ This release contains contributions from (alphabetically by first name): - Harald Sitter - Mike Stemle - Peter Jung + - Simon Quigley ## Core ## - Various Qt6-related fixes. @@ -25,6 +26,8 @@ This release contains contributions from (alphabetically by first name): (thanks Eugene) - *plymouthcfg* Use plymouth-set-default-theme to avoid issues with configuration. (thanks Peter) + - *users* module now supports enrolling in Active Directory, if enabled. + (thanks Simon) # 3.3.5 (2024-03-03) From 30ca0bfa450e27a86dcb6868cde95fbdbf1b3743 Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Sun, 14 Apr 2024 22:47:53 +0200 Subject: [PATCH 39/47] [locale] Fix TZ assignments for various locations - Tehran (UTC+3.5) - Muscat (UTC+4) - Dubai (UTC+4) --- CHANGES-3.3 | 1 + src/modules/locale/images/timezone_3.0.png | Bin 23982 -> 21322 bytes src/modules/locale/images/timezone_3.5.png | Bin 3294 -> 3340 bytes src/modules/locale/images/timezone_4.0.png | Bin 5009 -> 5105 bytes src/modules/locale/images/timezone_5.0.png | Bin 15043 -> 15038 bytes 5 files changed, 1 insertion(+) diff --git a/CHANGES-3.3 b/CHANGES-3.3 index a427dc26e..e8d20fafe 100644 --- a/CHANGES-3.3 +++ b/CHANGES-3.3 @@ -24,6 +24,7 @@ This release contains contributions from (alphabetically by first name): ## Modules ## - *bootloader* Adds "splash" to kernel parameters if plymouth is present. (thanks Eugene) + - *locale* Now picks the correct timezone for Dubai, Muscat, Tehran. - *plymouthcfg* Use plymouth-set-default-theme to avoid issues with configuration. (thanks Peter) - *users* module now supports enrolling in Active Directory, if enabled. diff --git a/src/modules/locale/images/timezone_3.0.png b/src/modules/locale/images/timezone_3.0.png index d5c003b4e778e618a5d82b23a31bbcc1f5fe7abd..5cdf8e04e09c0b9bbb014118652368b0b485c7eb 100644 GIT binary patch literal 21322 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 diff --git a/src/modules/locale/images/timezone_3.5.png b/src/modules/locale/images/timezone_3.5.png index f803c691f8bf4eca545d5a77bff0b13c7b715a30..7900d2e753275efe926126419227d43acc9cbc56 100644 GIT binary patch delta 2292 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 O0000004R> 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

1ks`KQW{%+1;BO8g> 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 V?rWRr4BSxf->J%#yfOXv{{XZUZp;7x delta 13886 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

DZQX(i zuRaFW0$9voL^wRUkgn^c&9a;sHZN;c6Rxm?qCFwTv;BEQ{dK6xqBlaPj_?u7!D{GR z$8TnyRwfT{flOd@7zt|>@ZdlydGwtfkMFi2L_%J|$g9-*T_}}Ob!&{oP+RV)N@!T^ zWtG-uPTccx7YoS-aS`XEC|jF@b0{3iJKq9DWW9{W@(jU^*pnO0vpKYcr<;Q3gj_?z zNVf4asO+q}3;i`6eN0Y+gWvQaWE$9~>ut;>C!@&nymJKV8$^M4;-ClkG7%RXW!~*QtpRha;1O?fUDG_m1xQbZVco*U37;8+#e4 z62wBs_m}lbHx2W0I2R7zN(R2WRfJW>av0tpYUkRYij~F#E3C9LmNiaV`&#-x>h#zc zH4-H$<a~y20 zwl(Z)FsoiNdRd<1A!Etevx(S#|5@GyHqXnA?ORuQ9}XQj1osIg%K)BMedczV?P90* zHNu1G)O<}5-=pJsR8O1$FTbM%nlh3{h&xIE5w3JKM-F`fR}_#MPO=mCkwsA_!@wuv ztCxOEj`nin)#0g$M9Gom}OhVYHWBq{@h^)6>Z)w)eQ)3s$gtme=?|gEY>gg!OK5^Al=pt9qkN zc||8UKnu~D8^#?I^Zn3HM_Ashqjs@{ZZHDPs59yM&i_Hh$}W)bJ09MlEVSj{}-3+WeWk;n8O$?pc9_%ZiRmPKFp4yE@mU&&|lWy zGz#1n)I^y^xm!Pu4TnJ2sK7MLyxYI9vkSOfE^vs=V))E@)Z`nrcXJ3GaIs%X^*KOp z@^EUOrty0lu8a2rtsPS3XWO7dYFnYLvGZlfdwVvffpdo=!nwglTo@o4t|6}y;TgFz zt9@Qg8TChb^_~=A;{mQz3d$Xrfd5L$BYaxz*bp4NsMnL@@ZTry)XqN!FVMp4s`#M5KLxy8MQ+oGgDY2`G+ zdpTu(0$69dW?Hr{bb%NBT!a8X-S(KrKSA1dsS%hm=Wa+f618ssrWX{7+8&TZ-v?2J zHDV#kHC{Z=WTdS!o3;*(B_&appKQ0@w9YCJh7{Q~ zJBC}sqJY24BFQXi&+tnh9XbZJzJ2S)JoQ#c?s#a$2Id?WlIEgVl;Ae z3ek*8K$Bn~^KI_}m)O{pvE4LtBqjbyYp+`aGO9EpkBvSxVRr&6_Lk$|#uD8@j0BX` zgrEc!969&jF_T7@pJAlz)lotaa43I>b_ytkL=#&p())60B90zDS^JWhw~_(Lc81!F z##z!@HQ$?mjh&%!#-SO&Ms=z!9YU#k(=*;boNT%Ht?l1RyUfLXg)jHokd*Z=PQA`#ZNT=RTZb*4i{3-$d*-va7}x z;hHTlRwgv&|B?8=1>)V#>tJDg`}gnvJGWDKb2npLNY`R%;TU(%igK#p!q+Ar{|}3F B(8d4& From f8094bbbbb865ff1c6d6d3236c3ac582195d8d23 Mon Sep 17 00:00:00 2001 From: demmm Date: Tue, 16 Apr 2024 12:28:30 +0200 Subject: [PATCH 40/47] [localeq] tiny correction map-qt6.qml api.geonames is not https ready --- src/modules/localeq/Map-qt6.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/localeq/Map-qt6.qml b/src/modules/localeq/Map-qt6.qml index 1e7b489e5..7c8473e08 100644 --- a/src/modules/localeq/Map-qt6.qml +++ b/src/modules/localeq/Map-qt6.qml @@ -89,7 +89,7 @@ Column { console.log("Online lookup", latC, lonC) // Needs to move to localeq.conf, each distribution will need their own account - xhr.open("GET", "https://api.geonames.org/timezoneJSON?lat=" + latC + "&lng=" + lonC + "&username=SOME_USERNAME") + xhr.open("GET", "http://api.geonames.org/timezoneJSON?lat=" + latC + "&lng=" + lonC + "&username=SOME_USERNAME") xhr.send() } From bf09dfc6af67c41c1cda3d272554ddf7cd290449 Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Tue, 16 Apr 2024 13:50:38 +0200 Subject: [PATCH 41/47] [libcalamares] Suppress sleep / suspend during installation --- src/libcalamares/JobQueue.cpp | 165 ++++++++++++++++++++++++++++++++++ src/libcalamares/JobQueue.h | 9 ++ 2 files changed, 174 insertions(+) diff --git a/src/libcalamares/JobQueue.cpp b/src/libcalamares/JobQueue.cpp index c7548cc5c..80e49dce4 100644 --- a/src/libcalamares/JobQueue.cpp +++ b/src/libcalamares/JobQueue.cpp @@ -16,13 +16,174 @@ #include "compat/Mutex.h" #include "utils/Logger.h" +#include +#include +#include +#include +#include #include #include #include +namespace +{ +// This power-management code is largely cribbed from KDE Discover, +// https://invent.kde.org/plasma/discover/-/blob/master/discover/PowerManagementInterface.cpp +// +// Upstream license text says: +// +// SPDX-FileCopyrightText: 2019 (c) Matthieu Gallien +// SPDX-License-Identifier: LGPL-3.0-or-later + + +/** @brief Class to manage sleep / suspend on inactivity + * + * Create an object of this class on the heap. Call inhibitSleep() + * to (try to) stop system sleep / suspend. Call uninhibitSleep() + * when the object is no longer needed. The object self-deletes + * after uninhibitSleep() completes. + */ +class PowerManagementInterface : public QObject +{ + Q_OBJECT +public: + PowerManagementInterface( QObject* parent = nullptr ); + ~PowerManagementInterface() override; + +public Q_SLOTS: + void inhibitSleep(); + void uninhibitSleep(); + +private Q_SLOTS: + void hostSleepInhibitChanged(); + void inhibitDBusCallFinished( QDBusPendingCallWatcher* aWatcher ); + void uninhibitDBusCallFinished( QDBusPendingCallWatcher* aWatcher ); + +private: + uint m_inhibitSleepCookie = 0; + bool m_inhibitedSleep = false; +}; + +PowerManagementInterface::PowerManagementInterface( QObject* parent ) + : QObject( parent ) +{ + auto sessionBus = QDBusConnection::sessionBus(); + + sessionBus.connect( QStringLiteral( "org.freedesktop.PowerManagement.Inhibit" ), + QStringLiteral( "/org/freedesktop/PowerManagement/Inhibit" ), + QStringLiteral( "org.freedesktop.PowerManagement.Inhibit" ), + QStringLiteral( "HasInhibitChanged" ), + this, + SLOT( hostSleepInhibitChanged() ) ); +} + +PowerManagementInterface::~PowerManagementInterface() = default; + +void +PowerManagementInterface::hostSleepInhibitChanged() +{ + // We don't actually care +} + +void +PowerManagementInterface::inhibitDBusCallFinished( QDBusPendingCallWatcher* aWatcher ) +{ + QDBusPendingReply< uint > reply = *aWatcher; + if ( reply.isError() ) + { + cError() << "Could not inhibit sleep:" << reply.error(); + // m_inhibitedSleep = false; // unchanged + } + else + { + m_inhibitSleepCookie = reply.argumentAt< 0 >(); + m_inhibitedSleep = true; + cDebug() << "Sleep inhibited, cookie" << m_inhibitSleepCookie; + } + aWatcher->deleteLater(); +} + +void +PowerManagementInterface::uninhibitDBusCallFinished( QDBusPendingCallWatcher* aWatcher ) +{ + QDBusPendingReply<> reply = *aWatcher; + if ( reply.isError() ) + { + cError() << "Could not uninhibit sleep:" << reply.error(); + } + else + { + m_inhibitedSleep = false; + m_inhibitSleepCookie = 0; + cDebug() << "Sleep uninhibited."; + } + aWatcher->deleteLater(); + this->deleteLater(); +} + +void +PowerManagementInterface::inhibitSleep() +{ + if ( m_inhibitedSleep ) + { + cDebug() << "Sleep is already inhibited."; + return; + } + + auto sessionBus = QDBusConnection::sessionBus(); + auto inhibitCall = QDBusMessage::createMethodCall( QStringLiteral( "org.freedesktop.PowerManagement.Inhibit" ), + QStringLiteral( "/org/freedesktop/PowerManagement/Inhibit" ), + QStringLiteral( "org.freedesktop.PowerManagement.Inhibit" ), + QStringLiteral( "Inhibit" ) ); + inhibitCall.setArguments( + { { tr( "Calamares" ) }, { tr( "Installation in progress", "@status" ) } } ); + + auto asyncReply = sessionBus.asyncCall( inhibitCall ); + auto* replyWatcher = new QDBusPendingCallWatcher( asyncReply, this ); + QObject::connect( + replyWatcher, &QDBusPendingCallWatcher::finished, this, &PowerManagementInterface::inhibitDBusCallFinished ); +} + +void +PowerManagementInterface::uninhibitSleep() +{ + if ( !m_inhibitedSleep ) + { + cDebug() << "Sleep was never inhibited."; + this->deleteLater(); + return; + } + + auto sessionBus = QDBusConnection::sessionBus(); + auto uninhibitCall = QDBusMessage::createMethodCall( QStringLiteral( "org.freedesktop.PowerManagement.Inhibit" ), + QStringLiteral( "/org/freedesktop/PowerManagement/Inhibit" ), + QStringLiteral( "org.freedesktop.PowerManagement.Inhibit" ), + QStringLiteral( "UnInhibit" ) ); + uninhibitCall.setArguments( { { m_inhibitSleepCookie } } ); + + auto asyncReply = sessionBus.asyncCall( uninhibitCall ); + auto replyWatcher = new QDBusPendingCallWatcher( asyncReply, this ); + QObject::connect( + replyWatcher, &QDBusPendingCallWatcher::finished, this, &PowerManagementInterface::uninhibitDBusCallFinished ); +} + +} // namespace + namespace Calamares { +SleepInhibitor::SleepInhibitor() +{ + // Create a PowerManagementInterface object with intentionally no parent + // so it is not destroyed along with this. Instead, when this + // is destroyed, **start** the uninhibit-sleep call which will (later) + // destroy the PowerManagementInterface object. + auto* p = new PowerManagementInterface( nullptr ); + p->inhibitSleep(); + connect( this, &QObject::destroyed, p, &PowerManagementInterface::uninhibitSleep ); +} + +SleepInhibitor::~SleepInhibitor() = default; struct WeightedJob { @@ -274,6 +435,10 @@ JobQueue::start() m_thread->finalize(); m_finished = false; m_thread->start(); + + auto* inhibitor = new PowerManagementInterface( this ); + inhibitor->inhibitSleep(); + connect( this, &JobQueue::finished, inhibitor, &PowerManagementInterface::uninhibitSleep ); } diff --git a/src/libcalamares/JobQueue.h b/src/libcalamares/JobQueue.h index dd27f0a58..593bb06a1 100644 --- a/src/libcalamares/JobQueue.h +++ b/src/libcalamares/JobQueue.h @@ -20,6 +20,15 @@ namespace Calamares class GlobalStorage; class JobThread; +///@brief RAII class to suppress sleep / suspend during its lifetime +class DLLEXPORT SleepInhibitor : public QObject +{ + Q_OBJECT +public: + SleepInhibitor(); + ~SleepInhibitor() override; +}; + class DLLEXPORT JobQueue : public QObject { Q_OBJECT From 8ff3849820c1d5b1c2384e334c5ad34d53a0e90c Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Tue, 16 Apr 2024 14:59:56 +0200 Subject: [PATCH 42/47] [calamares] Switch test-application to use JobQueue For better realism, use JobQueue to run the jobs of a module, rather than doing it by hand in a similar-kind-of-loop. --- src/calamares/testmain.cpp | 57 +++++++++++++------------------------- 1 file changed, 19 insertions(+), 38 deletions(-) diff --git a/src/calamares/testmain.cpp b/src/calamares/testmain.cpp index 203c97936..ad40aadfc 100644 --- a/src/calamares/testmain.cpp +++ b/src/calamares/testmain.cpp @@ -52,6 +52,7 @@ #include #include #include +#include #include @@ -455,11 +456,11 @@ libcalamares.utils.debug('pre-script for testing purposes injected') int main( int argc, char* argv[] ) { - QCoreApplication* aw = createApplication( argc, argv ); + QCoreApplication* application = createApplication( argc, argv ); Logger::setupLogLevel( Logger::LOGVERBOSE ); - ModuleConfig module = handle_args( *aw ); + ModuleConfig module = handle_args( *application ); if ( module.moduleName().isEmpty() ) { return 1; @@ -469,7 +470,7 @@ main( int argc, char* argv[] ) std::unique_ptr< Calamares::JobQueue > jobqueue_p( new Calamares::JobQueue( nullptr ) ); std::unique_ptr< Calamares::System > system_p( new Calamares::System( settings_p->doChroot() ) ); - QMainWindow* mw = nullptr; + QMainWindow* mainWindow = nullptr; auto* gs = jobqueue_p->globalStorage(); if ( !module.globalConfigFile().isEmpty() ) @@ -513,21 +514,21 @@ main( int argc, char* argv[] ) // tries to create the widget **which won't be used anyway**. // // To avoid that crash, re-create the QApplication, now with GUI - if ( !qobject_cast< QApplication* >( aw ) ) + if ( !qobject_cast< QApplication* >( application ) ) { auto* replace_app = new QApplication( argc, argv ); replace_app->setQuitOnLastWindowClosed( true ); - aw = replace_app; + application = replace_app; } - mw = module.m_ui ? new QMainWindow() : nullptr; - if ( mw ) + mainWindow = module.m_ui ? new QMainWindow() : nullptr; + if ( mainWindow ) { - mw->installEventFilter( Calamares::Retranslator::instance() ); + mainWindow->installEventFilter( Calamares::Retranslator::instance() ); } (void)new Calamares::Branding( module.m_branding ); auto* modulemanager = new Calamares::ModuleManager( QStringList(), nullptr ); - (void)Calamares::ViewManager::instance( mw ); + (void)Calamares::ViewManager::instance( mainWindow ); modulemanager->addModule( m ); } @@ -542,16 +543,16 @@ main( int argc, char* argv[] ) return 1; } - if ( mw ) + if ( mainWindow ) { auto* vm = Calamares::ViewManager::instance(); vm->onInitComplete(); QWidget* w = vm->currentStep()->widget(); - w->setParent( mw ); - mw->setCentralWidget( w ); + w->setParent( mainWindow ); + mainWindow->setCentralWidget( w ); w->show(); - mw->show(); - return aw->exec(); + mainWindow->show(); + return application->exec(); } using TR = Logger::DebugRow< const char*, const QString >; @@ -559,30 +560,10 @@ main( int argc, char* argv[] ) cDebug() << Logger::SubEntry << "Module metadata" << TR( "name", m->name() ) << TR( "type", m->typeString() ) << TR( "interface", m->interfaceString() ); - Calamares::JobList jobList = m->jobs(); - unsigned int failure_count = 0; - unsigned int count = 1; - for ( const auto& p : jobList ) - { - // This doesn't get a SubEntry because the jobs may log a bunch of - // things; print the function-header to make clear that we're back in main. - cDebug() << "Job #" << count << "name" << p->prettyName(); - Calamares::JobResult r = p->exec(); - if ( !r ) - { - cError() << "Job #" << count << "failed" << TR( "summary", r.message() ) << TR( "details", r.details() ); - if ( r.errorCode() > 0 ) - { - ++failure_count; - } - } - ++count; - } + Calamares::JobQueue::instance()->enqueue(100, m->jobs()); - if ( aw ) - { - delete aw; - } + QObject::connect(Calamares::JobQueue::instance(), &Calamares::JobQueue::finished, [application]() { QTimer::singleShot(std::chrono::seconds(3), application, &QApplication::quit); }); + QTimer::singleShot(0, []() { Calamares::JobQueue::instance()->start(); }); - return failure_count ? 1 : 0; + return application->exec(); } From 0095e3084373d42163c32ec2df34506f856951ea Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Tue, 16 Apr 2024 15:09:32 +0200 Subject: [PATCH 43/47] Changes: document sleep / suspend --- CHANGES-3.3 | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGES-3.3 b/CHANGES-3.3 index e8d20fafe..2146f40a4 100644 --- a/CHANGES-3.3 +++ b/CHANGES-3.3 @@ -19,7 +19,9 @@ This release contains contributions from (alphabetically by first name): - Simon Quigley ## Core ## -- Various Qt6-related fixes. + - Various Qt6-related fixes. + - Calamares now prevents sleep and suspend while the installation is + running, so that unattended installs do not accidentally fall asleep. ## Modules ## - *bootloader* Adds "splash" to kernel parameters if plymouth is present. From bcf27aa31171c2161c903c2bf22ad2a5fcbfa78e Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Tue, 16 Apr 2024 15:31:51 +0200 Subject: [PATCH 44/47] Changes: pre-release housekeeping --- CHANGES-3.3 | 17 +++++++++-------- CMakeLists.txt | 2 +- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/CHANGES-3.3 b/CHANGES-3.3 index 2146f40a4..9a0d0107e 100644 --- a/CHANGES-3.3 +++ b/CHANGES-3.3 @@ -7,7 +7,8 @@ contributors are listed. Note that Calamares does not have a historical changelog -- this log starts with version 3.3.0. See CHANGES-3.2 for the history of the 3.2 series (2018-05 - 2022-08). -# 3.3.6 (unreleased) +# 3.3.6 (2024-04-16) + This release contains contributions from (alphabetically by first name): - Adriaan de Groot - Anke Boersma @@ -24,13 +25,13 @@ This release contains contributions from (alphabetically by first name): running, so that unattended installs do not accidentally fall asleep. ## Modules ## - - *bootloader* Adds "splash" to kernel parameters if plymouth is present. - (thanks Eugene) - - *locale* Now picks the correct timezone for Dubai, Muscat, Tehran. - - *plymouthcfg* Use plymouth-set-default-theme to avoid issues with - configuration. (thanks Peter) - - *users* module now supports enrolling in Active Directory, if enabled. - (thanks Simon) + - *bootloader* Adds "splash" to kernel parameters if plymouth is present. + (thanks Eugene) + - *locale* Now picks the correct timezone for Dubai, Muscat, Tehran. + - *plymouthcfg* Use plymouth-set-default-theme to avoid issues with + configuration. (thanks Peter) + - *users* module now supports enrolling in Active Directory, if enabled. + (thanks Simon) # 3.3.5 (2024-03-03) diff --git a/CMakeLists.txt b/CMakeLists.txt index 6ef843413..99b8d4e41 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -48,7 +48,7 @@ cmake_minimum_required(VERSION 3.16 FATAL_ERROR) set(CALAMARES_VERSION 3.3.6) -set(CALAMARES_RELEASE_MODE OFF) # Set to ON during a release +set(CALAMARES_RELEASE_MODE ON) # Set to ON during a release if(CMAKE_SCRIPT_MODE_FILE) include(${CMAKE_CURRENT_LIST_DIR}/CMakeModules/ExtendedVersion.cmake) From 370e75fc711b4b0a489d700437c29b4e9705a4d9 Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Tue, 16 Apr 2024 15:41:12 +0200 Subject: [PATCH 45/47] [libcalamares] Repair apidox for Job --- src/libcalamares/Job.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/libcalamares/Job.h b/src/libcalamares/Job.h index fe678fa63..931029a8f 100644 --- a/src/libcalamares/Job.h +++ b/src/libcalamares/Job.h @@ -117,7 +117,7 @@ public: * * The job's name may be similar to the status message, but this is * a name, and should not be an active verb phrase. The translation - * should use context @c @label . + * should use context @c \@label . * * The name of the job is used as a **fallback** when the status * or descriptions are empty. If a job has no implementation of @@ -134,7 +134,7 @@ public: * * The default implementation returns an empty string. * - * The translation should use context @c @title . + * The translation should use context @c \@title . */ virtual QString prettyDescription() const; @@ -146,7 +146,7 @@ public: * * The job's status should say **what** the job is doing. It should be in * present active tense. Typically the translation uses tr() context - * @c @status . See prettyName() for examples. + * @c \@status . See prettyName() for examples. */ virtual QString prettyStatusMessage() const; From 0a0e647be3cfef15b8b3a1e4265c0039542ab600 Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Tue, 16 Apr 2024 15:42:59 +0200 Subject: [PATCH 46/47] [hostinfo] Do not name unused parameter --- src/modules/hostinfo/HostInfoJob.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/hostinfo/HostInfoJob.cpp b/src/modules/hostinfo/HostInfoJob.cpp index d09de3ae2..51de1fadc 100644 --- a/src/modules/hostinfo/HostInfoJob.cpp +++ b/src/modules/hostinfo/HostInfoJob.cpp @@ -100,7 +100,7 @@ hostCPU_FreeBSD() #if defined( Q_OS_LINUX ) static QString -hostCPUmatchARM( const QString& s ) +hostCPUmatchARM( const QString& ) { /* The "CPU implementer" line is for ARM CPUs in general. * From 2e9618440652a1adfaf8e125e2cc2f4cca3aa5cc Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Tue, 16 Apr 2024 15:46:09 +0200 Subject: [PATCH 47/47] [partition] Simplify logic, remove dead code --- src/modules/partition/core/PartUtils.cpp | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/modules/partition/core/PartUtils.cpp b/src/modules/partition/core/PartUtils.cpp index bc3e6f5a0..e233403b4 100644 --- a/src/modules/partition/core/PartUtils.cpp +++ b/src/modules/partition/core/PartUtils.cpp @@ -581,12 +581,7 @@ efiFilesystemMinimumSize() uefisys_part_sizeB = v > 0 ? v : 0; } // There is a lower limit of what can be configured - if ( uefisys_part_sizeB < efiSpecificationHardMinimumSize ) - { - uefisys_part_sizeB = efiSpecificationHardMinimumSize; - } - return uefisys_part_sizeB; - return efiSpecificationHardMinimumSize; + return std::max( uefisys_part_sizeB, efiSpecificationHardMinimumSize ); } QString