Merge branch 'calamares' of https://github.com/calamares/calamares into development

This commit is contained in:
Philip Müller 2022-05-24 23:06:15 +07:00
commit d12d830b4d
17 changed files with 309 additions and 111 deletions

View File

@ -8,6 +8,31 @@ contributors are listed. Note that Calamares does not have a historical
changelog -- this log starts with version 3.2.0. The release notes on the
website will have to do for older versions.
# 3.2.59 (unreleased) #
This release contains contributions from (alphabetically by first name):
- Arjen Balfoort
## Core ##
- No core changes yet
## Modules ##
- *fstab* can now be configured to put `/tmp` on a *tmpfs*, and this can
depend on it being on an SSD or not. Options applicable to `/tmp` can
be configured separately as well. #1818 (Thanks Arjen)
- *partition* now has some support for re-using LUKS partitions.
(Thanks Arjen)
- *partition* will cycle out a LUKS key if all the key slots are in use
and a new key is added, rather than crashing the installer. (Thanks Arjen)
# 3.2.58.2 (2022-05-24)
This is a extra-quick release for an issue that shows up when using a
swap **file** on a btrfs filesystem; the installation would fail with
a Python error, raised from btrfs-progs. Reported by Evan James, Erik
Dubois, TechXero.
# 3.2.58.1 (2022-05-20)
This is a hot-fix release for a regression in the *partition* module where

View File

@ -41,11 +41,11 @@
# TODO:3.3: Require CMake 3.12
cmake_minimum_required( VERSION 3.3 FATAL_ERROR )
project( CALAMARES
VERSION 3.2.58.1
VERSION 3.2.59
LANGUAGES C CXX
)
set( CALAMARES_VERSION_RC 0 ) # Set to 0 during release cycle, 1 during development
set( CALAMARES_VERSION_RC 1 ) # Set to 0 during release cycle, 1 during development
if( CALAMARES_VERSION_RC EQUAL 1 AND CMAKE_SOURCE_DIR STREQUAL CMAKE_BINARY_DIR )
message( FATAL_ERROR "Do not build development versions in the source-directory." )
endif()

View File

@ -24,6 +24,7 @@ set( OPTIONAL_PRIVATE_LIBRARIES "" )
set( OPTIONAL_PUBLIC_LIBRARIES "" )
set( libSources
CalamaresAbout.cpp
CppJob.cpp
GlobalStorage.cpp
Job.cpp

View File

@ -0,0 +1,81 @@
/* === This file is part of Calamares - <https://calamares.io> ===
*
* SPDX-FileCopyrightText: 2022 Adriaan de Groot <groot@kde.org>
* SPDX-License-Identifier: GPL-3.0-or-later
*
* Calamares is Free Software: see the License-Identifier above.
*
*/
#include "CalamaresAbout.h"
#include "CalamaresVersion.h"
#include <QCoreApplication>
static const char s_header[]
= QT_TRANSLATE_NOOP( "AboutData", "<h1>%1</h1><br/><strong>%2<br/> for %3</strong><br/><br/>" );
static const char s_footer[]
= QT_TRANSLATE_NOOP( "AboutData",
"Thanks to <a href=\"https://calamares.io/team/\">the Calamares team</a> "
"and the <a href=\"https://www.transifex.com/calamares/calamares/\">Calamares "
"translators team</a>.<br/><br/>"
"<a href=\"https://calamares.io/\">Calamares</a> "
"development is sponsored by <br/>"
"<a href=\"http://www.blue-systems.com/\">Blue Systems</a> - "
"Liberating Software." );
struct Maintainer
{
unsigned int start;
unsigned int end;
const char* name;
const char* email;
QString text() const
{
//: Copyright year-year Name <email-address>
return QCoreApplication::translate( "AboutData", "Copyright %1-%2 %3 &lt;%4&gt;<br/>" )
.arg( start )
.arg( end )
.arg( name )
.arg( email );
}
};
static constexpr const Maintainer maintainers[] = {
{ 2014, 2017, "Teo Mrnjavac", "teo@kde.org" },
{ 2017, 2022, "Adriaan de Groot", "groot@kde.org" },
};
static QString
aboutMaintainers()
{
return std::accumulate( std::cbegin( maintainers ),
std::cend( maintainers ),
QString(),
[]( QString& s, const Maintainer& m )
{
s += m.text();
return s;
} );
}
static QString
substituteVersions( const QString& s )
{
return s.arg( CALAMARES_APPLICATION_NAME ).arg( CALAMARES_VERSION );
}
const QString
Calamares::aboutString()
{
return substituteVersions( QCoreApplication::translate( "AboutData", s_header ) ) + aboutMaintainers()
+ QCoreApplication::translate( "AboutData", s_footer );
}
const QString
Calamares::aboutStringUntranslated()
{
return substituteVersions( QString( s_header ) ) + aboutMaintainers() + QString( s_footer );
}

View File

@ -0,0 +1,31 @@
/* === This file is part of Calamares - <https://calamares.io> ===
*
* SPDX-FileCopyrightText: 2022 Adriaan de Groot <groot@kde.org>
* SPDX-License-Identifier: GPL-3.0-or-later
*
* Calamares is Free Software: see the License-Identifier above.
*
*/
#ifndef CALAMARES_CALAMARESABOUT_H
#define CALAMARES_CALAMARESABOUT_H
#include "DllMacro.h"
#include <QString>
namespace Calamares
{
/** @brief Returns an about string for the application
*
* The about string includes a header-statement, a list of maintainer
* addresses, and a thank-you to Blue Systems. There is on %-substitution
* left, where you can fill in the name of the product (e.g. to say
* "Calamares for Netrunner" or ".. for Manjaro").
*/
DLLEXPORT const QString aboutStringUntranslated();
/// @brief As above, but translated in the current Calamares language
DLLEXPORT const QString aboutString();
} // namespace Calamares
#endif

View File

@ -61,3 +61,26 @@ ssdExtraMountOptions:
crypttabOptions: luks
# For Debian and Debian-based distributions, change the above line to:
# crypttabOptions: luks,keyscript=/bin/cat
# Options for handling /tmp in /etc/fstab
# Currently default (required) and ssd are supported
# The corresponding string can contain the following variables:
# tmpfs: true or tmpfs: false to either mount /tmp as tmpfs or not
# options: "<mount options>"
#
# Example:
#tmpOptions:
# default:
# tmpfs: false
# options: ""
# ssd:
# tmpfs: true
# options: "defaults,noatime,mode=1777"
#
tmpOptions:
default:
tmpfs: false
options: ""
ssd:
tmpfs: true
options: "defaults,noatime,mode=1777"

View File

@ -25,4 +25,22 @@ properties:
btrfs_swap: { type: string }
efiMountOptions: { type: string }
crypttabOptions: { type: string }
required: [ mountOptions ]
tmpOptions:
type: object
additionalProperties: false
properties:
default:
type: object
additionalProperties: false
properties:
tmpfs: { type: bool }
options: { type: string }
ssd:
type: object
additionalProperties: false
properties:
tmpfs: { type: bool }
options: { type: string }
required:
- mountOptions
- tmpOptions: default

View File

@ -14,7 +14,6 @@
import os
import re
import subprocess
import libcalamares
@ -106,14 +105,17 @@ class FstabGenerator(object):
:param root_mount_point:
:param mount_options:
:param ssd_extra_mount_options:
:param crypttab_options:
:param tmp_options:
"""
def __init__(self, partitions, root_mount_point, mount_options,
ssd_extra_mount_options, crypttab_options):
ssd_extra_mount_options, crypttab_options, tmp_options):
self.partitions = partitions
self.root_mount_point = root_mount_point
self.mount_options = mount_options
self.ssd_extra_mount_options = ssd_extra_mount_options
self.crypttab_options = crypttab_options
self.tmp_options = tmp_options
self.ssd_disks = set()
self.root_is_ssd = False
@ -162,8 +164,10 @@ class FstabGenerator(object):
crypttab_options = self.crypttab_options
# Set crypttab password for partition to none and remove crypttab options
# if crypto_keyfile.bin was not generated
if not os.path.exists(os.path.join(self.root_mount_point, "crypto_keyfile.bin")):
# if root partition was not encrypted
if any([p["mountPoint"] == "/"
and "luksMapperName" not in p
for p in self.partitions]):
password = "none"
crypttab_options = ""
# on root partition when /boot is unencrypted
@ -173,7 +177,6 @@ class FstabGenerator(object):
for p in self.partitions]):
password = "none"
crypttab_options = ""
return dict(
name=mapper_name,
@ -213,21 +216,32 @@ class FstabGenerator(object):
mount_entry["subvol"] = s["subvolume"]
dct = self.generate_fstab_line_info(mount_entry)
if dct:
self.print_fstab_line(dct, file=fstab_file)
self.print_fstab_line(dct, file=fstab_file)
elif partition["fs"] != "zfs": # zfs partitions don't need an entry in fstab
dct = self.generate_fstab_line_info(partition)
if dct:
self.print_fstab_line(dct, file=fstab_file)
if self.root_is_ssd:
# Mount /tmp on a tmpfs
dct = dict(device="tmpfs",
mount_point="/tmp",
fs="tmpfs",
options="defaults,noatime,mode=1777",
check=0,
)
self.print_fstab_line(dct, file=fstab_file)
# Old behavior was to mount /tmp as tmpfs
# New behavior is to use tmpOptions to decide
# if mounting /tmp as tmpfs and which options to use
ssd = self.tmp_options.get("ssd", {})
if not ssd:
ssd = self.tmp_options.get("default", {})
# Default to True to mimic old behavior
tmpfs = ssd.get("tmpfs", True)
if tmpfs:
options = ssd.get("options", "defaults,noatime,mode=1777")
# Mount /tmp on a tmpfs
dct = dict(device="tmpfs",
mount_point="/tmp",
fs="tmpfs",
options=options,
check=0,
)
self.print_fstab_line(dct, file=fstab_file)
def generate_fstab_line_info(self, partition):
"""
@ -342,10 +356,7 @@ def create_swapfile(root_mount_point, root_btrfs):
swapfile_path = os.path.join(root_mount_point, "swap/swapfile")
with open(swapfile_path, "wb") as f:
pass
o = subprocess.check_output(["chattr", "+C", swapfile_path])
libcalamares.utils.debug("swapfile attributes: {!s}".format(o))
o = subprocess.check_output(["btrfs", "property", "set", swapfile_path, "compression", "none"])
libcalamares.utils.debug("swapfile compression: {!s}".format(o))
libcalamares.utils.host_env_process_output(["chattr", "+C", "+m", swapfile_path]) # No Copy-on-Write, no compression
else:
swapfile_path = os.path.join(root_mount_point, "swapfile")
with open(swapfile_path, "wb") as f:
@ -363,8 +374,7 @@ def create_swapfile(root_mount_point, root_btrfs):
libcalamares.job.setprogress(0.2 + 0.3 * ( total / desired_size ) )
total += chunk
os.chmod(swapfile_path, 0o600)
o = subprocess.check_output(["mkswap", swapfile_path])
libcalamares.utils.debug("swapfile mkswap: {!s}".format(o))
libcalamares.utils.host_env_process_output(["mkswap", swapfile_path])
libcalamares.job.setprogress(0.5)
@ -410,6 +420,7 @@ def run():
mount_options = conf.get("mountOptions", {})
ssd_extra_mount_options = conf.get("ssdExtraMountOptions", {})
crypttab_options = conf.get("crypttabOptions", "luks")
tmp_options = conf.get("tmpOptions", {})
# We rely on mount_options having a default; if there wasn't one,
# bail out with a meaningful error.
@ -422,7 +433,8 @@ def run():
root_mount_point,
mount_options,
ssd_extra_mount_options,
crypttab_options)
crypttab_options,
tmp_options)
if swap_choice is not None:
libcalamares.job.setprogress(0.2)

View File

@ -17,6 +17,7 @@
#include "GlobalStorage.h"
#include "JobQueue.h"
#include <QRegularExpression>
#include <QDir>
LuksBootKeyFileJob::LuksBootKeyFileJob( QObject* parent )
@ -136,6 +137,28 @@ generateTargetKeyfile()
static bool
setupLuks( const LuksDevice& d )
{
// Sometimes this error is thrown: "All key slots full"
// luksAddKey will fail. So, remove the first slot to make room
auto luks_dump = CalamaresUtils::System::instance()->targetEnvCommand(
{ "cryptsetup", "luksDump", d.device }, QString(), QString(), std::chrono::seconds( 5 ) );
if ( luks_dump.getExitCode() == 0 )
{
QRegularExpression re( QStringLiteral( R"(\d+:\s*enabled)" ), QRegularExpression::CaseInsensitiveOption );
int count = luks_dump.getOutput().count(re);
cDebug() << "Luks Dump slot count: " << count;
if ( count >= 7 )
{
auto r = CalamaresUtils::System::instance()->targetEnvCommand(
{ "cryptsetup", "luksKillSlot", d.device, "1" }, QString(), d.passphrase, std::chrono::seconds( 60 ) );
if ( r.getExitCode() != 0 )
{
cWarning() << "Could not kill a slot to make room on" << d.device << ':' << r.getOutput() << "(exit code"
<< r.getExitCode() << ')';
return false;
}
}
}
// Adding the key can take some times, measured around 15 seconds with
// a HDD (spinning rust) and a slow-ish computer. Give it a minute.
auto r = CalamaresUtils::System::instance()->targetEnvCommand(

View File

@ -53,19 +53,19 @@ NetInstallViewStep::prettyName() const
tr( "Browser software" );
tr( "Browser package" );
tr( "Web browser" );
tr( "Kernel" );
tr( "Services" );
tr( "Login" );
tr( "Desktop" );
tr( "Kernel", "label for netinstall module, Linux kernel" );
tr( "Services", "label for netinstall module, system services" );
tr( "Login", "label for netinstall module, choose login manager" );
tr( "Desktop", "label for netinstall module, choose desktop environment" );
tr( "Applications" );
tr( "Communication" );
tr( "Development" );
tr( "Office" );
tr( "Multimedia" );
tr( "Internet" );
tr( "Theming" );
tr( "Gaming" );
tr( "Utilities" );
tr( "Communication", "label for netinstall module" );
tr( "Development", "label for netinstall module" );
tr( "Office", "label for netinstall module" );
tr( "Multimedia", "label for netinstall module" );
tr( "Internet", "label for netinstall module" );
tr( "Theming", "label for netinstall module" );
tr( "Gaming", "label for netinstall module" );
tr( "Utilities", "label for netinstall module" );
#endif
}

View File

@ -34,11 +34,15 @@ Partition*
findPartitionByMountPoint( const QList< Device* >& devices, const QString& mountPoint )
{
for ( auto device : devices )
{
for ( auto it = PartitionIterator::begin( device ); it != PartitionIterator::end( device ); ++it )
{
if ( PartitionInfo::mountPoint( *it ) == mountPoint )
{
return *it;
}
}
}
return nullptr;
}
@ -167,42 +171,35 @@ testPassphrase( FS::luks* fs, const QString& deviceNode, const QString& passphra
}
#endif
// Adapted from luks cryptOpen which always opens a dialog to ask for a passphrase
int
updateLuksDevice( Partition* partition, const QString& passphrase )
// Adapted from src/fs/luks.cpp cryptOpen which always opens a dialog to ask for a passphrase
SavePassphraseValue
savePassphrase( Partition* partition, const QString& passphrase )
{
const QString deviceNode = partition->partitionPath();
cDebug() << "Update Luks device: " << deviceNode;
if ( passphrase.isEmpty() )
{
cWarning() << Logger::SubEntry << "#1: Passphrase is empty";
return 1;
return SavePassphraseValue::EmptyPassphrase;
}
if ( partition->fileSystem().type() != FileSystem::Luks )
{
cWarning() << Logger::SubEntry << "#2: Not a luks encrypted device";
return 2;
return SavePassphraseValue::NotLuksPartition;
}
// Cast partition fs to luks fs
FS::luks* luksFs = dynamic_cast< FS::luks* >( &partition->fileSystem() );
const QString deviceNode = partition->partitionPath();
// Test the given passphrase
if ( !testPassphrase( luksFs, deviceNode, passphrase ) )
{
cWarning() << Logger::SubEntry << "#3: Passphrase incorrect";
return 3;
return SavePassphraseValue::IncorrectPassphrase;
}
if ( luksFs->isCryptOpen() )
{
if ( !luksFs->mapperName().isEmpty() )
{
cWarning() << Logger::SubEntry << "#4: Device already decrypted";
return 4;
return SavePassphraseValue::NoError;
}
else
{
@ -213,34 +210,28 @@ updateLuksDevice( Partition* partition, const QString& passphrase )
ExternalCommand openCmd( QStringLiteral( "cryptsetup" ),
{ QStringLiteral( "open" ), deviceNode, luksFs->suggestedMapperName( deviceNode ) } );
if ( !( openCmd.write( passphrase.toLocal8Bit() + '\n' ) && openCmd.start( -1 ) && openCmd.exitCode() == 0 ) )
{
cWarning() << Logger::SubEntry << openCmd.exitCode() << ": cryptsetup command failed";
return openCmd.exitCode();
return SavePassphraseValue::CryptsetupError;
}
// Save the existing passphrase
luksFs->setPassphrase( passphrase );
luksFs->scan( deviceNode );
if ( luksFs->mapperName().isEmpty() )
{
cWarning() << Logger::SubEntry << "#5: No mapper node found";
return 5;
return SavePassphraseValue::NoMapperNode;
}
luksFs->loadInnerFileSystem( luksFs->mapperName() );
luksFs->setCryptOpen( luksFs->innerFS() != nullptr );
if ( !luksFs->isCryptOpen() )
{
cWarning() << Logger::SubEntry << "#6: Device could not be decrypted";
return 6;
return SavePassphraseValue::DeviceNotDecrypted;
}
return 0;
return SavePassphraseValue::NoError;
}
Calamares::JobResult

View File

@ -43,6 +43,25 @@ class PartitionRole;
namespace KPMHelpers
{
/** @brief Return (errors) for savePassphrase()
*
* There's a handful of things that can go wrong when
* saving a passphrase for a given partition; this
* expresses clearly which ones are wrong.
*
* @c NoError is "Ok" when saving the passphrase succeeds.
*/
enum class SavePassphraseValue
{
NoError,
EmptyPassphrase,
NotLuksPartition,
IncorrectPassphrase,
CryptsetupError,
NoMapperNode,
DeviceNotDecrypted
};
/**
* Iterates on all devices and return the first partition which is associated
* with mountPoint. This uses PartitionInfo::mountPoint(), not Partition::mountPoint()
@ -74,7 +93,15 @@ Partition* createNewEncryptedPartition( PartitionNode* parent,
Partition* clonePartition( Device* device, Partition* partition );
int updateLuksDevice( Partition* partition, const QString& passphrase );
/** @brief Save an existing passphrase for a previously encrypted partition.
*
* Tries to apply the passphrase to the partition; this checks if the
* @p partition is one that can have a passphrase applied, and
* runs `cryptsetup` to check that the passphrase actually works
* for the partition. Returns `NoError` on success, or an explanatory
* other value if it fails.
*/
SavePassphraseValue savePassphrase( Partition* partition, const QString& passphrase );
/** @brief Return a result for an @p operation
*

View File

@ -254,8 +254,7 @@ EditExistingPartitionDialog::applyChanges( PartitionCoreModule* core )
const QString passphrase = m_ui->encryptWidget->passphrase();
if ( !passphrase.isEmpty() )
{
int retCode = KPMHelpers::updateLuksDevice( m_partition, passphrase );
if ( retCode != 0 )
if ( KPMHelpers::savePassphrase( m_partition, passphrase ) != KPMHelpers::SavePassphraseValue::NoError )
{
QString message = tr( "Passphrase for existing partition" );
QString description = tr( "Partition %1 could not be decrypted "

View File

@ -10,6 +10,7 @@
#include "Config.h"
#include "Branding.h"
#include "CalamaresAbout.h"
#include "GlobalStorage.h"
#include "JobQueue.h"
#include "Settings.h"
@ -247,6 +248,13 @@ Config::setSupportUrl( const QString& url )
emit supportUrlChanged();
}
QString
Config::aboutMessage() const
{
return Calamares::aboutString();
}
QString
Config::genericWelcomeMessage() const
{

View File

@ -52,6 +52,7 @@ class Config : public QObject
Q_PROPERTY( QString countryCode MEMBER m_countryCode NOTIFY countryCodeChanged FINAL )
Q_PROPERTY( int localeIndex READ localeIndex WRITE setLocaleIndex NOTIFY localeIndexChanged )
Q_PROPERTY( QString aboutMessage READ aboutMessage CONSTANT FINAL )
Q_PROPERTY( QString genericWelcomeMessage MEMBER m_genericWelcomeMessage NOTIFY genericWelcomeMessageChanged FINAL )
Q_PROPERTY( QString warningMessage READ warningMessage NOTIFY warningMessageChanged FINAL )
@ -89,6 +90,7 @@ public:
QString donateUrl() const { return m_donateUrl; }
void setDonateUrl( const QString& url );
QString aboutMessage() const;
QString genericWelcomeMessage() const;
QString warningMessage() const;

View File

@ -15,6 +15,7 @@
#include "ui_WelcomePage.h"
#include "Branding.h"
#include "CalamaresAbout.h"
#include "CalamaresVersion.h"
#include "Config.h"
#include "Settings.h"
@ -208,20 +209,7 @@ WelcomePage::setLanguageIcon( QPixmap i )
void
WelcomePage::retranslate()
{
QString message;
if ( Calamares::Settings::instance()->isSetupMode() )
{
message = Calamares::Branding::instance()->welcomeStyleCalamares()
? tr( "<h1>Welcome to the Calamares setup program for %1.</h1>" )
: tr( "<h1>Welcome to %1 setup.</h1>" );
}
else
{
message = Calamares::Branding::instance()->welcomeStyleCalamares()
? tr( "<h1>Welcome to the Calamares installer for %1.</h1>" )
: tr( "<h1>Welcome to the %1 installer.</h1>" );
}
const QString message = m_conf->genericWelcomeMessage();
ui->mainText->setText( message.arg( Calamares::Branding::instance()->versionedName() ) );
ui->retranslateUi( this );
@ -235,21 +223,7 @@ WelcomePage::showAboutBox()
= Calamares::Settings::instance()->isSetupMode() ? tr( "About %1 setup" ) : tr( "About %1 installer" );
QMessageBox mb( QMessageBox::Information,
title.arg( CALAMARES_APPLICATION_NAME ),
tr( "<h1>%1</h1><br/>"
"<strong>%2<br/>"
"for %3</strong><br/><br/>"
"Copyright 2014-2017 Teo Mrnjavac &lt;teo@kde.org&gt;<br/>"
"Copyright 2017-2020 Adriaan de Groot &lt;groot@kde.org&gt;<br/>"
"Thanks to <a href=\"https://calamares.io/team/\">the Calamares team</a> "
"and the <a href=\"https://www.transifex.com/calamares/calamares/\">Calamares "
"translators team</a>.<br/><br/>"
"<a href=\"https://calamares.io/\">Calamares</a> "
"development is sponsored by <br/>"
"<a href=\"http://www.blue-systems.com/\">Blue Systems</a> - "
"Liberating Software." )
.arg( CALAMARES_APPLICATION_NAME )
.arg( CALAMARES_VERSION )
.arg( Calamares::Branding::instance()->versionedName() ),
m_conf->aboutMessage().arg( Calamares::Branding::instance()->versionedName() ),
QMessageBox::Ok,
this );
Calamares::fixButtonLabels( &mb );

View File

@ -18,9 +18,6 @@ Item {
height: parent.height
focus: true
property var appName: "Calamares"
property var appVersion: "3.2.24"
Rectangle {
id: textArea
x: 28
@ -44,21 +41,7 @@ Item {
width: 400
height: 250
anchors.centerIn: parent
text: qsTr("<h1>%1</h1><br/>
<strong>%2<br/>
for %3</strong><br/><br/>
Copyright 2014-2017 Teo Mrnjavac &lt;teo@kde.org&gt;<br/>
Copyright 2017-2020 Adriaan de Groot &lt;groot@kde.org&gt;<br/>
Thanks to <a href='https://calamares.io/team/'>the Calamares team</a>
and the <a href='https://www.transifex.com/calamares/calamares/'>Calamares
translators team</a>.<br/><br/>
<a href='https://calamares.io/'>Calamares</a>
development is sponsored by <br/>
<a href='http://www.blue-systems.com/'>Blue Systems</a> -
Liberating Software." )
.arg(appName)
.arg(appVersion)
.arg(Branding.string(Branding.VersionedName))
text: config.aboutMessage.arg(Branding.string(Branding.VersionedName))
onLinkActivated: Qt.openUrlExternally(link)