Release v3.3.6

-----BEGIN PGP SIGNATURE-----
 
 iQGzBAABCAAdFiEESUdADzdGoDiQC7F4Mo10LYgHpDUFAmYegr0ACgkQMo10LYgH
 pDVL2wwAwI+efwkyadFEIZ3d8wSHQ5UVZqH+ccbBVDh+cWH4a/MBrTEXNv4NwX3s
 PKdj58otEinMPgBVMpdSZEODUMYOM6NsTBU5u8g8CR9GK7rRflqyyeHS1xUNRwOp
 i2FfUQF0NpHFNCd0kJ1b4UB1wWvqVrHhO2YR6gIJ5ud7B3BQyP8+RTxwMOBwp9M0
 nAE+p3/jDuqzq+BRbF75rNyUbMgKlqku1DI8qjqJQTehj3uYvgsAL4ze6yvgRGnH
 BLLoNmFqsH4tEQ9M7oHyO5JmMTXaOGmtuLj17RGeQuwftXjfSYbaqMRThH0q+xue
 s0cmNCDH68aFQaHYmAXonCHTdUkch+ClupIprnOuZe4JkNU9IakQ20SfP54C7OO7
 Cf2Oz+1z8psfX0PjJV4l0UoAbdLB8rj65v+JgxS/o+rFeNlBu5o3WSzuhJ1LDA8D
 H2Q6OF0ApgbY+duH43ur3ggAe+nf3Ls8nAPW/IYPaH+0SiqgFvKKqY3HNGWtqFgT
 8pRoWnmB
 =lGOn
 -----END PGP SIGNATURE-----

Merge tag 'v3.3.6' of https://github.com/calamares/calamares into 3.3.x-stable

Release v3.3.6
This commit is contained in:
Philip Mueller 2024-04-17 12:02:29 +07:00
commit 41451c9500
37 changed files with 666 additions and 110 deletions

View File

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

View File

@ -15,6 +15,7 @@ env:
-DBUILD_TESTING=ON
-DBUILD_APPSTREAM=ON
-DBUILD_APPDATA=ON
-DWITH_QT6=ON
jobs:
build:
@ -30,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

View File

@ -7,6 +7,33 @@ 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 (2024-04-16)
This release contains contributions from (alphabetically by first name):
- Adriaan de Groot
- Anke Boersma
- Eugene Sam
- Evan James
- Harald Sitter
- Mike Stemle
- Peter Jung
- Simon Quigley
## Core ##
- 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.
(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)
This release contains contributions from (alphabetically by first name):

View File

@ -47,7 +47,7 @@
cmake_minimum_required(VERSION 3.16 FATAL_ERROR)
set(CALAMARES_VERSION 3.3.5)
set(CALAMARES_VERSION 3.3.6)
set(CALAMARES_RELEASE_MODE ON) # Set to ON during a release
if(CMAKE_SCRIPT_MODE_FILE)

View File

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

View File

@ -5,6 +5,41 @@
# - 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 <workflow-name>"
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
_d="$SRCDIR/.github/workflows/$1"
test -f "$_d" || _d="$SRCDIR/.github/workflows/$1.yml"
test -f "$_d" || { echo "! No workflow $1" ; exit 1 ; }
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
test -n "$BUILDDIR" || { echo "! \$BUILDDIR not set" ; exit 1 ; }

View File

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

View File

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

View File

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

View File

@ -52,6 +52,7 @@
#include <QLabel>
#include <QMainWindow>
#include <QThread>
#include <QTimer>
#include <memory>
@ -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();
}

View File

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

View File

@ -16,13 +16,174 @@
#include "compat/Mutex.h"
#include "utils/Logger.h"
#include <QApplication>
#include <QDBusConnection>
#include <QDBusMessage>
#include <QDBusPendingCall>
#include <QDBusPendingReply>
#include <QMutex>
#include <QThread>
#include <memory>
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 <matthieu_gallien@yahoo.fr>
// 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
{
@ -188,6 +349,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();
@ -267,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 );
}

View File

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

View File

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

View File

@ -125,23 +125,28 @@ 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):
# Configured kernel parameters (default "quiet"), if plymouth installed, add splash
# screen parameter and then "rw".
kernel_params = libcalamares.job.configuration.get("kernelParams", ["quiet"])
if have_program_in_target("plymouth"):
kernel_params.append("splash")
kernel_params.append("rw")
use_systemd_naming = have_program_in_target("dracut") or (libcalamares.utils.target_env_call(["/usr/bin/grep", "-q", "^HOOKS.*systemd", "/etc/mkinitcpio.conf"]) == 0)
partitions = libcalamares.globalstorage.value("partitions")
cryptdevice_params = []
swap_uuid = ""
swap_outer_mappername = None
swap_outer_uuid = None
cryptdevice_params = []
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
# Take over swap settings:
# - unencrypted swap partition sets swap_uuid
# - encrypted root sets cryptdevice_params

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 KiB

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View File

@ -1,6 +1,6 @@
/* === This file is part of Calamares - <https://calamares.io> ===
*
* SPDX-FileCopyrightText: 2020 - 2022 Anke Boersma <demm@kaosx.us>
* SPDX-FileCopyrightText: 2020 - 2024 Anke Boersma <demm@kaosx.us>
* SPDX-License-Identifier: GPL-3.0-or-later
*
* Calamares is Free Software: see the License-Identifier above.
@ -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 {

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,85 @@
/* === This file is part of Calamares - <https://calamares.io> ===
*
* SPDX-FileCopyrightText: 2024 Simon Quigley <tsimonq2@ubuntu.com>
* 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 <QDateTime>
#include <QDir>
#include <QFile>
#include <QFileInfo>
#include <QProcess>
#include <QTextStream>
ActiveDirectoryJob::ActiveDirectoryJob( const QString& adminLogin,
const QString& adminPassword,
const QString& domain,
const QString& ip )
: Calamares::Job()
, m_adminLogin( adminLogin )
, m_adminPassword( adminPassword )
, m_domain( domain )
, m_ip( ip )
{
}
QString
ActiveDirectoryJob::prettyName() const
{
return tr( "Enroll system in Active Directory", "@label" );
}
QString
ActiveDirectoryJob::prettyStatusMessage() const
{
return tr( "Enrolling system in Active Directory…", "@status" );
}
Calamares::JobResult
ActiveDirectoryJob::exec()
{
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." );
}
}
const QString installPath = Calamares::System::instance()->targetPath( QStringLiteral( "/" ) );
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 ) );
if ( r.getExitCode() == 0 )
{
return Calamares::JobResult::ok();
}
else
{
return Calamares::JobResult::error( QString( "Failed to join realm: %1" ).arg( r.getOutput() ) );
}
}

View File

@ -0,0 +1,34 @@
/* === This file is part of Calamares - <https://calamares.io> ===
*
* SPDX-FileCopyrightText: 2024 Simon Quigley <tsimonq2@ubuntu.com>
* 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( 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:
QString m_adminLogin; // Admin credentials to do the enrollment
QString m_adminPassword;
QString m_domain;
QString m_ip;
};
#endif /* ACTIVEDIRECTORYJOB_H */

View File

@ -55,6 +55,7 @@ include_directories(${PROJECT_BINARY_DIR}/src/libcalamaresui)
set(_users_src
# Jobs
ActiveDirectoryJob.cpp
CreateUserJob.cpp
MiscJobs.cpp
SetPasswordJob.cpp

View File

@ -9,6 +9,7 @@
#include "Config.h"
#include "ActiveDirectoryJob.h"
#include "CreateUserJob.h"
#include "MiscJobs.h"
#include "SetHostNameJob.h"
@ -444,8 +445,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();
}
@ -656,6 +655,48 @@ 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_activeDirectoryAdminUsername = s;
}
void
Config::setActiveDirectoryAdminPassword( const QString& s )
{
m_activeDirectoryAdminPassword = s;
}
void
Config::setActiveDirectoryDomain( const QString& s )
{
m_activeDirectoryDomain = s;
}
void
Config::setActiveDirectoryIP( const QString& s )
{
m_activeDirectoryIP = s;
}
QString
Config::rootPassword() const
{
@ -913,6 +954,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 +1034,15 @@ Config::createJobs() const
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 ) );

View File

@ -226,6 +226,10 @@ public:
bool permitWeakPasswords() const { return m_permitWeakPasswords; }
/// Current setting for "require strong password"?
bool requireStrongPasswords() const { return m_requireStrongPasswords; }
/// Is Active Directory enabled in the config file?
bool getActiveDirectoryEnabled() const;
/// Is it both enabled and activated by user choice (checkbox)?
bool getActiveDirectoryUsed() const;
const QList< GroupDescription >& defaultGroups() const { return m_defaultGroups; }
/** @brief the names of all the groups for the current user
@ -292,6 +296,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 +353,13 @@ private:
bool m_isReady = false; ///< Used to reduce readyChanged signals
bool m_activeDirectory = false;
bool m_activeDirectoryUsed = false;
QString m_activeDirectoryAdminUsername;
QString m_activeDirectoryAdminPassword;
QString m_activeDirectoryDomain;
QString m_activeDirectoryIP;
HostNameAction m_hostnameAction = HostNameAction::EtcHostname;
bool m_writeEtcHosts = false;
QString m_hostnameTemplate;

View File

@ -162,6 +162,16 @@ 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() );
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 );
CALAMARES_RETRANSLATE_SLOT( &UsersPage::retranslate );
onReuseUserPasswordChanged( m_config->reuseUserPasswordForRoot() );
@ -283,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 );
}

View File

@ -44,6 +44,8 @@ protected slots:
void reportUserPasswordStatus( int, const QString& );
void reportRootPasswordStatus( int, const QString& );
void onActiveDirectoryToggled( bool checked );
private:
void retranslate();

View File

@ -603,6 +603,93 @@ SPDX-License-Identifier: GPL-3.0-or-later
</item>
</layout>
</item>
<item>
<spacer name="verticalSpacer_3">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>6</height>
</size>
</property>
</spacer>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QCheckBox" name="useADCheckbox">
<property name="text">
<string>Use Active Directory</string>
</property>
</widget>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QLabel" name="domainLabel">
<property name="text">
<string>Domain:</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="domainField"/>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="domainAdminLabel">
<property name="text">
<string>Domain Administrator:</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="domainAdminField"/>
</item>
<item>
<widget class="QLabel" name="domainPasswordLabel">
<property name="text">
<string>Password:</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="domainPasswordField">
<property name="echoMode">
<enum>QLineEdit::Password</enum>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<widget class="QLabel" name="ipAddressLabel">
<property name="text">
<string>IP Address (optional):</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="ipAddressField"/>
</item>
</layout>
</item>
</layout>
</item>
</layout>
</item>
<item>
<spacer name="verticalSpacer_7">
<property name="orientation">

View File

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

View File

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

View File

@ -196,6 +196,8 @@ Item {
Text {
id: notesText
property real padding: 16;
x: padding
y: padding
width: parent.width - 2 * padding