Compare commits
103 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
8ca6a7caef | ||
![]() |
b0d951d7e5 | ||
![]() |
8233be93cf | ||
![]() |
8652fc5f6d | ||
![]() |
6792eb5191 | ||
![]() |
b00177bd65 | ||
![]() |
bcd8ebd614 | ||
![]() |
46c59be541 | ||
![]() |
2f2271aad6 | ||
![]() |
feba83acdd | ||
![]() |
f5b882a075 | ||
![]() |
b0f6530a58 | ||
![]() |
ece1e338e0 | ||
![]() |
e814920bca | ||
![]() |
c70e31a919 | ||
![]() |
7e17106f34 | ||
![]() |
9603cbef14 | ||
![]() |
87cca4053f | ||
![]() |
b65321d80b | ||
![]() |
18307d9f57 | ||
![]() |
3ee388526d | ||
![]() |
13cb84aa75 | ||
![]() |
efe84bc6c0 | ||
![]() |
5a4e2b73ab | ||
![]() |
152b3c333b | ||
![]() |
7b3c4db8f0 | ||
![]() |
7cc84b89be | ||
![]() |
4db4e983e3 | ||
![]() |
3aac4dea67 | ||
![]() |
0a7262148e | ||
![]() |
ec8bab4013 | ||
![]() |
4778d9b2dd | ||
![]() |
3a90382699 | ||
![]() |
3ebe695a23 | ||
![]() |
ebae698a6e | ||
![]() |
18ad188ef6 | ||
![]() |
6e440bf9bb | ||
![]() |
490ac8d086 | ||
![]() |
c48c91a5bd | ||
![]() |
ee99ee48f6 | ||
![]() |
a5b21b2500 | ||
![]() |
75c947c5a3 | ||
![]() |
1ccabf1b13 | ||
![]() |
6da9bad272 | ||
![]() |
074941e2bd | ||
![]() |
5d71723aec | ||
![]() |
de0bbbe90a | ||
![]() |
7f05096611 | ||
![]() |
7635b76352 | ||
![]() |
b9559a9d82 | ||
![]() |
76892136cf | ||
![]() |
11bf84bac7 | ||
![]() |
24a376493b | ||
![]() |
ca3f0e2892 | ||
![]() |
e861d8b319 | ||
![]() |
abb6f73725 | ||
![]() |
3dd02edc78 | ||
![]() |
d5bef9efb5 | ||
![]() |
5f7b221e11 | ||
![]() |
16a029abd2 | ||
![]() |
f3e85efd41 | ||
![]() |
2c186132cd | ||
![]() |
ce6aec158a | ||
![]() |
54fd81a87e | ||
![]() |
11424195ef | ||
![]() |
fad2f6ea88 | ||
![]() |
58cf9ffeeb | ||
![]() |
85f36c77b1 | ||
![]() |
138db1c817 | ||
![]() |
126838fe1d | ||
![]() |
f0958535df | ||
![]() |
d7865a5bcd | ||
![]() |
dd2e14853c | ||
![]() |
b0436bf050 | ||
![]() |
b6692341e7 | ||
![]() |
af4b87a4cc | ||
![]() |
c3524c07ad | ||
![]() |
cca38695ed | ||
![]() |
cf20d6495b | ||
![]() |
8bdfcac0fb | ||
![]() |
4bed079ebf | ||
![]() |
daa5731acf | ||
![]() |
9ef520f862 | ||
![]() |
0bef2a91a1 | ||
![]() |
4fb8993a38 | ||
![]() |
91762e3df4 | ||
![]() |
90452147a3 | ||
![]() |
06b6263c24 | ||
![]() |
2f145fcf44 | ||
![]() |
0720d56803 | ||
![]() |
85a2160098 | ||
![]() |
858e271c8a | ||
![]() |
e3af4f3e26 | ||
![]() |
51a5c4de0f | ||
![]() |
7108d4a509 | ||
![]() |
69ef13ef0c | ||
![]() |
e24d14c512 | ||
![]() |
7faf4f30df | ||
![]() |
ac44aab74a | ||
![]() |
d0100afe02 | ||
![]() |
5701937883 | ||
![]() |
fbdb9e6779 | ||
![]() |
e5f5ef0d17 |
38
CHANGES-3.2
38
CHANGES-3.2
@@ -7,6 +7,44 @@ contributors are listed. Note that Calamares does not have a historical
|
||||
changelog -- this log starts with version 3.2.0. The release notes on the
|
||||
website will have to do for older versions.
|
||||
|
||||
# 3.2.47 (2021-11-19) #
|
||||
|
||||
This release contains contributions from (alphabetically by first name):
|
||||
- Evan James
|
||||
- Jonas Strassel
|
||||
|
||||
## Core ##
|
||||
- The translation for Sinhala (`si`) has reached 100%. Thank you to
|
||||
හෙළබස and Sandaruwan, translators for Sinhala, for special effort
|
||||
in completing that translation.
|
||||
- Logging now supports Redacted names. This reduces the scope for
|
||||
leaking names or other private information through the logs
|
||||
(if they are posted to a pastebin). A name is redacted consistently
|
||||
within one run of Calamares, but differently each time.
|
||||
|
||||
## Modules ##
|
||||
- *bootloader* with systemd-boot now handles root subvolumes better
|
||||
(Thanks Evan)
|
||||
- *displaymanager* supports the *greetd* display manager, which is a
|
||||
kind of meta-DM itself, supporting multiple greeters. (Thanks Jonas)
|
||||
- *finishedq* now has an extra example QML file that builds the UI in
|
||||
a different fashion, demonstrating how a mobile-OS customization of
|
||||
Calamares would present the "all done" message.
|
||||
- *fstab* has an example configuration file that mentioned `space_cache`
|
||||
as an option. Since 2014 there was only one possible value, so this
|
||||
option matched the default-and-only value. Newer kernels with newer
|
||||
btrfs versions have a `v2` option value as well. Remove the example
|
||||
option, since the kernel automatically picks the right value, while
|
||||
setting it to the wrong one may prevent the system from booting.
|
||||
(Thanks Evan)
|
||||
- The *partition* module no longer logs recognizable disk names or
|
||||
UUIDs. These are redacted in the logs. #1593
|
||||
- The *partition* module, together with the new *zfs* module and changes
|
||||
in *mount* and *bootloader* can install to ZFS **if** the distribution
|
||||
kernel supports it. ZFS tools are required, as well as the relevant
|
||||
kernel modules. See the `README.md` in the *zfs* module. (Thanks Evan)
|
||||
|
||||
|
||||
# 3.2.46 (2021-11-09) #
|
||||
|
||||
This release contains contributions from (alphabetically by first name):
|
||||
|
@@ -41,7 +41,7 @@
|
||||
# TODO:3.3: Require CMake 3.12
|
||||
cmake_minimum_required( VERSION 3.3 FATAL_ERROR )
|
||||
project( CALAMARES
|
||||
VERSION 3.2.46
|
||||
VERSION 3.2.47
|
||||
LANGUAGES C CXX
|
||||
)
|
||||
|
||||
@@ -133,12 +133,12 @@ set( CALAMARES_DESCRIPTION_SUMMARY
|
||||
# `txstats.py -e`. See also
|
||||
#
|
||||
# Total 81 languages
|
||||
set( _tx_complete az az_AZ ca cs_CZ fi_FI he hi hr ja ko lt pt_BR
|
||||
pt_PT sq sv tr_TR uk zh_CN zh_TW )
|
||||
set( _tx_good as be ca@valencia da de fr fur it_IT ml nl ru sk tg
|
||||
vi )
|
||||
set( _tx_complete az az_AZ ca de fi_FI he hr ja ko lt pt_PT si sq
|
||||
tr_TR uk zh_TW )
|
||||
set( _tx_good as be ca@valencia cs_CZ da fr fur hi it_IT ml nl
|
||||
pt_BR ru sk sv tg vi zh_CN )
|
||||
set( _tx_ok ar ast bg bn el en_GB es es_MX et eu fa gl hu id is mr
|
||||
nb pl ro si sl sr sr@latin th )
|
||||
nb pl ro sl sr sr@latin th )
|
||||
set( _tx_incomplete en_HK en_IN eo es_PE es_PR fr_CH gu hi_IN id_ID
|
||||
ie kk kn ko_KR lo lv mk ne ne_NP te te_IN ur zh zh_HK )
|
||||
|
||||
|
@@ -29,7 +29,8 @@
|
||||
|
||||
* Double-check the *CALAMARES_VERSION* value at the top of `CMakeLists.txt`.
|
||||
* Set *CALAMARES_RELEASE_MODE* to `ON` in `CMakeLists.txt`.
|
||||
* Edit `CHANGES` and set the date of the release.
|
||||
* Edit `CHANGES-*` and set the date of the release. Pick the right
|
||||
file for the release-stream.
|
||||
* Commit both. This is usually done with commit-message
|
||||
*Changes: pre-release housekeeping*.
|
||||
|
||||
@@ -81,24 +82,12 @@ Follow the instructions printed by the release script.
|
||||
|
||||
* Bump the version number in `CMakeLists.txt` in *CALAMARES_VERSION*.
|
||||
* Set *CALAMARES_RELEASE_MODE* back to `OFF`.
|
||||
* Add a placeholder entry for the next release in `CHANGES` with date
|
||||
text *not released yet*.
|
||||
* Add a placeholder entry for the next release in `CHANGES-*` with date
|
||||
text *not released yet*. See the text below, "Placeholder Release".
|
||||
Add the placeholder to the right file for the release-stream.
|
||||
* Commit and push that, usually with the message
|
||||
*Changes: post-release housekeeping*.
|
||||
|
||||
```
|
||||
# 3.2.XX (unreleased) #
|
||||
|
||||
This release contains contributions from (alphabetically by first name):
|
||||
- No external contributors yet
|
||||
|
||||
## Core ##
|
||||
- No core changes yet
|
||||
|
||||
## Modules ##
|
||||
- No module changes yet
|
||||
```
|
||||
|
||||
# Related Material
|
||||
|
||||
> This section isn't directly related to any specific release,
|
||||
@@ -139,3 +128,18 @@ ssb rsa3072/0xCFDDC96F12B1915C
|
||||
- Upload that public key to the relevant GitHub profile.
|
||||
- Upload that public key to the Calamares site.
|
||||
|
||||
## Placeholder Release Notes
|
||||
|
||||
```
|
||||
# 3.2.XX (unreleased) #
|
||||
|
||||
This release contains contributions from (alphabetically by first name):
|
||||
- No external contributors yet
|
||||
|
||||
## Core ##
|
||||
- No core changes yet
|
||||
|
||||
## Modules ##
|
||||
- No module changes yet
|
||||
```
|
||||
|
||||
|
0
ci/configvalidator.py
Normal file → Executable file
0
ci/configvalidator.py
Normal file → Executable file
0
ci/txcheck.sh
Normal file → Executable file
0
ci/txcheck.sh
Normal file → Executable file
0
ci/txreduce.py
Normal file → Executable file
0
ci/txreduce.py
Normal file → Executable file
@@ -689,27 +689,27 @@ Bu proqramdan çıxılacaq və bütün dəyişikliklər itiriləcəkdir.</transl
|
||||
<message>
|
||||
<location filename="../src/modules/partition/jobs/ClearMountsJob.cpp" line="259"/>
|
||||
<source>Successfully unmounted %1.</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>%1 uğurla ayrıldı.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../src/modules/partition/jobs/ClearMountsJob.cpp" line="266"/>
|
||||
<source>Successfully disabled swap %1.</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>%1 mübadilə bölməsi uğurla söndürüldü.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../src/modules/partition/jobs/ClearMountsJob.cpp" line="292"/>
|
||||
<source>Successfully cleared swap %1.</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>%1 mübadilə bölməsi uğurla təmizləndi</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../src/modules/partition/jobs/ClearMountsJob.cpp" line="306"/>
|
||||
<source>Successfully closed mapper device %1.</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>Yerləşdirmə cihazı %1 uğurla bağlandı</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../src/modules/partition/jobs/ClearMountsJob.cpp" line="319"/>
|
||||
<source>Successfully disabled volume group %1.</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>Tutum qrupu %1, uğurla söndürüldü</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../src/modules/partition/jobs/ClearMountsJob.cpp" line="358"/>
|
||||
|
@@ -689,27 +689,27 @@ Bu proqramdan çıxılacaq və bütün dəyişikliklər itiriləcəkdir.</transl
|
||||
<message>
|
||||
<location filename="../src/modules/partition/jobs/ClearMountsJob.cpp" line="259"/>
|
||||
<source>Successfully unmounted %1.</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>%1 uğurla ayrıldı.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../src/modules/partition/jobs/ClearMountsJob.cpp" line="266"/>
|
||||
<source>Successfully disabled swap %1.</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>%1 mübadilə bölməsi uğurla söndürüldü.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../src/modules/partition/jobs/ClearMountsJob.cpp" line="292"/>
|
||||
<source>Successfully cleared swap %1.</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>%1 mübadilə bölməsi uğurla təmizləndi</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../src/modules/partition/jobs/ClearMountsJob.cpp" line="306"/>
|
||||
<source>Successfully closed mapper device %1.</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>Yerləşdirmə cihazı %1 uğurla bağlandı</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../src/modules/partition/jobs/ClearMountsJob.cpp" line="319"/>
|
||||
<source>Successfully disabled volume group %1.</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>Tutum qrupu %1, uğurla söndürüldü</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../src/modules/partition/jobs/ClearMountsJob.cpp" line="358"/>
|
||||
|
@@ -687,27 +687,27 @@ The installer will quit and all changes will be lost.</source>
|
||||
<message>
|
||||
<location filename="../src/modules/partition/jobs/ClearMountsJob.cpp" line="259"/>
|
||||
<source>Successfully unmounted %1.</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>%1을(를) 성공적으로 마운트 해제했습니다.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../src/modules/partition/jobs/ClearMountsJob.cpp" line="266"/>
|
||||
<source>Successfully disabled swap %1.</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>스왑% 1을(를) 성공적으로 비활성화했습니다.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../src/modules/partition/jobs/ClearMountsJob.cpp" line="292"/>
|
||||
<source>Successfully cleared swap %1.</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>스왑 %1을(를) 성공적으로 지웠습니다.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../src/modules/partition/jobs/ClearMountsJob.cpp" line="306"/>
|
||||
<source>Successfully closed mapper device %1.</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>매퍼 장치 %1을(를) 성공적으로 닫았습니다.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../src/modules/partition/jobs/ClearMountsJob.cpp" line="319"/>
|
||||
<source>Successfully disabled volume group %1.</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>볼륨 그룹 %1을(를) 성공적으로 비활성화했습니다.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../src/modules/partition/jobs/ClearMountsJob.cpp" line="358"/>
|
||||
|
1028
lang/calamares_si.ts
1028
lang/calamares_si.ts
File diff suppressed because it is too large
Load Diff
@@ -690,27 +690,27 @@ Yükleyiciden çıkınca tüm değişiklikler kaybedilecek.</translation>
|
||||
<message>
|
||||
<location filename="../src/modules/partition/jobs/ClearMountsJob.cpp" line="259"/>
|
||||
<source>Successfully unmounted %1.</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>%1 bağlantısı başarıyla kaldırıldı.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../src/modules/partition/jobs/ClearMountsJob.cpp" line="266"/>
|
||||
<source>Successfully disabled swap %1.</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>%1 takas alanı başarıyla devre dışı bırakıldı.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../src/modules/partition/jobs/ClearMountsJob.cpp" line="292"/>
|
||||
<source>Successfully cleared swap %1.</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>%1 takas alanı başarıyla temizlendi.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../src/modules/partition/jobs/ClearMountsJob.cpp" line="306"/>
|
||||
<source>Successfully closed mapper device %1.</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>%1 eşleyici aygıtı başarıyla kapatıldı.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../src/modules/partition/jobs/ClearMountsJob.cpp" line="319"/>
|
||||
<source>Successfully disabled volume group %1.</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>%1 birim grubu başarıyla devre dışı bırakıldı.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../src/modules/partition/jobs/ClearMountsJob.cpp" line="358"/>
|
||||
@@ -862,17 +862,17 @@ Kurulum devam edebilir fakat bazı özellikler devre dışı kalabilir.</transla
|
||||
<message>
|
||||
<location filename="../src/modules/welcome/Config.cpp" line="251"/>
|
||||
<source><h1>Welcome to %1 setup</h1></source>
|
||||
<translation><h1>%1 kurulumuna hoşgeldiniz</h1></translation>
|
||||
<translation><h1>%1 kurulumuna hoş geldiniz</h1></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../src/modules/welcome/Config.cpp" line="255"/>
|
||||
<source><h1>Welcome to the Calamares installer for %1</h1></source>
|
||||
<translation><h1>%1 Calamares Sistem Yükleyiciye Hoşgeldiniz</h1></translation>
|
||||
<translation><h1>%1 Calamares Sistem Yükleyiciye Hoş Geldiniz</h1></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../src/modules/welcome/Config.cpp" line="256"/>
|
||||
<source><h1>Welcome to the %1 installer</h1></source>
|
||||
<translation><h1>%1 Sistem Yükleyiciye Hoşgeldiniz</h1></translation>
|
||||
<translation><h1>%1 Sistem Yükleyiciye Hoş Geldiniz</h1></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../src/modules/users/Config.cpp" line="217"/>
|
||||
@@ -3978,7 +3978,7 @@ Output:
|
||||
<message>
|
||||
<location filename="../src/modules/welcome/WelcomePage.cpp" line="217"/>
|
||||
<source><h1>Welcome to %1 setup.</h1></source>
|
||||
<translation><h1>%1 Kurulumuna Hoşgeldiniz.</h1></translation>
|
||||
<translation><h1>%1 Kurulumuna Hoş Geldiniz.</h1></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../src/modules/welcome/WelcomePage.cpp" line="222"/>
|
||||
@@ -4016,7 +4016,7 @@ Output:
|
||||
<message>
|
||||
<location filename="../src/modules/welcomeq/WelcomeQmlViewStep.cpp" line="40"/>
|
||||
<source>Welcome</source>
|
||||
<translation>Hoşgeldiniz</translation>
|
||||
<translation>Hoş geldiniz</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
@@ -4024,7 +4024,7 @@ Output:
|
||||
<message>
|
||||
<location filename="../src/modules/welcome/WelcomeViewStep.cpp" line="46"/>
|
||||
<source>Welcome</source>
|
||||
<translation>Hoşgeldiniz</translation>
|
||||
<translation>Hoş geldiniz</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
|
@@ -127,6 +127,7 @@ sequence:
|
||||
# - dummyprocess
|
||||
# - dummypython
|
||||
- partition
|
||||
# - zfs
|
||||
- mount
|
||||
- unpackfs
|
||||
- machineid
|
||||
|
@@ -132,6 +132,11 @@ public:
|
||||
void setEmergency( bool e ) { m_emergency = e; }
|
||||
|
||||
signals:
|
||||
/** @brief Signals that the job has made progress
|
||||
*
|
||||
* The parameter @p percent should be between 0 (0%) and 1 (100%).
|
||||
* Values outside of this range will be clamped.
|
||||
*/
|
||||
void progress( qreal percent );
|
||||
|
||||
private:
|
||||
|
@@ -20,6 +20,8 @@
|
||||
#include <QDir>
|
||||
#include <QFileInfo>
|
||||
#include <QMutex>
|
||||
#include <QRandomGenerator>
|
||||
#include <QTextStream>
|
||||
#include <QTime>
|
||||
#include <QVariant>
|
||||
|
||||
@@ -229,7 +231,7 @@ toString( const QVariant& v )
|
||||
}
|
||||
|
||||
QDebug&
|
||||
operator<<( QDebug& s, const Redacted& l )
|
||||
operator<<( QDebug& s, const RedactedCommand& l )
|
||||
{
|
||||
// Special case logging: don't log the (encrypted) password.
|
||||
if ( l.list.contains( "usermod" ) )
|
||||
@@ -252,4 +254,33 @@ operator<<( QDebug& s, const Redacted& l )
|
||||
return s;
|
||||
}
|
||||
|
||||
/** @brief Returns a stable-but-private hash of @p context and @p s
|
||||
*
|
||||
* Identical strings with the same context will be hashed the same,
|
||||
* so that they can be logged and still recognized as the-same.
|
||||
*/
|
||||
static uint insertRedactedName( const QString& context, const QString& s )
|
||||
{
|
||||
static uint salt = QRandomGenerator::global()->generate(); // Just once
|
||||
|
||||
uint val = qHash(context, salt);
|
||||
return qHash(s, val);
|
||||
}
|
||||
|
||||
RedactedName::RedactedName( const QString& context, const QString& s )
|
||||
: m_id( insertRedactedName(context, s) ),
|
||||
m_context(context)
|
||||
{
|
||||
}
|
||||
|
||||
RedactedName::RedactedName(const char *context, const QString& s )
|
||||
: RedactedName( QString::fromLatin1( context ), s )
|
||||
{
|
||||
}
|
||||
|
||||
RedactedName::operator QString() const
|
||||
{
|
||||
return QString( m_context + '$' + QString::number( m_id, 16 ) );
|
||||
}
|
||||
|
||||
} // namespace Logger
|
||||
|
@@ -145,8 +145,8 @@ public:
|
||||
{
|
||||
}
|
||||
|
||||
const T& first;
|
||||
const U& second;
|
||||
const T first;
|
||||
const U second;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -214,9 +214,9 @@ public:
|
||||
* since the log may get posted to bug reports, or stored in
|
||||
* the target system.
|
||||
*/
|
||||
struct Redacted
|
||||
struct RedactedCommand
|
||||
{
|
||||
Redacted( const QStringList& l )
|
||||
RedactedCommand( const QStringList& l )
|
||||
: list( l )
|
||||
{
|
||||
}
|
||||
@@ -224,7 +224,30 @@ struct Redacted
|
||||
const QStringList& list;
|
||||
};
|
||||
|
||||
QDebug& operator<<( QDebug& s, const Redacted& l );
|
||||
QDebug& operator<<( QDebug& s, const RedactedCommand& l );
|
||||
|
||||
/** @brief When logging "private" identifiers, keep them consistent but private
|
||||
*
|
||||
* Send a string to a logger in such a way that each time it is logged,
|
||||
* it logs the same way, but without revealing the actual contents.
|
||||
* This can be applied to user names, UUIDs, etc.
|
||||
*/
|
||||
struct RedactedName
|
||||
{
|
||||
RedactedName( const char* context, const QString& s );
|
||||
RedactedName( const QString& context, const QString& s );
|
||||
|
||||
operator QString() const;
|
||||
|
||||
private:
|
||||
const uint m_id;
|
||||
const QString m_context;
|
||||
};
|
||||
|
||||
inline QDebug& operator<<( QDebug& s, const RedactedName& n )
|
||||
{
|
||||
return s << NoQuote << QString( n ) << Quote;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Formatted logging of a pointer
|
||||
|
@@ -163,7 +163,7 @@ Calamares::Utils::Runner::run()
|
||||
} );
|
||||
}
|
||||
|
||||
cDebug() << Logger::SubEntry << "Running" << Logger::Redacted( m_command );
|
||||
cDebug() << Logger::SubEntry << "Running" << Logger::RedactedCommand( m_command );
|
||||
process.start();
|
||||
if ( !process.waitForStarted() )
|
||||
{
|
||||
@@ -225,13 +225,13 @@ Calamares::Utils::Runner::run()
|
||||
{
|
||||
if ( !output.isEmpty() )
|
||||
{
|
||||
cDebug() << Logger::SubEntry << "Target cmd:" << Logger::Redacted( m_command ) << "Exit code:" << r
|
||||
cDebug() << Logger::SubEntry << "Target cmd:" << Logger::RedactedCommand( m_command ) << "Exit code:" << r
|
||||
<< "output:\n"
|
||||
<< Logger::NoQuote << output;
|
||||
}
|
||||
else
|
||||
{
|
||||
cDebug() << Logger::SubEntry << "Target cmd:" << Logger::Redacted( m_command ) << "Exit code:" << r
|
||||
cDebug() << Logger::SubEntry << "Target cmd:" << Logger::RedactedCommand( m_command ) << "Exit code:" << r
|
||||
<< "(no output)";
|
||||
}
|
||||
}
|
||||
|
@@ -398,7 +398,9 @@ target_env_process_output(["ls"])
|
||||
```
|
||||
|
||||
The functions return 0. If the exit code of *command* is not 0, an exception
|
||||
is raised instead of returning 0.
|
||||
is raised instead of returning 0. The exception is `subprocess.CalledProcessError`
|
||||
(as if the *subprocess* module had been used), and the `returncode` member
|
||||
of the exception object can be used to determine the exit code.
|
||||
|
||||
Parameter *stdin* may be a string which is fed to the command as standard input.
|
||||
The *timeout* is in seconds, with 0 (or a negative number) treated as no-timeout.
|
||||
|
@@ -92,6 +92,50 @@ def get_kernel_line(kernel_type):
|
||||
return ""
|
||||
|
||||
|
||||
def get_zfs_root():
|
||||
"""
|
||||
Looks in global storage to find the zfs root
|
||||
|
||||
:return: A string containing the path to the zfs root or None if it is not found
|
||||
"""
|
||||
|
||||
zfs = libcalamares.globalstorage.value("zfsDatasets")
|
||||
|
||||
if not zfs:
|
||||
libcalamares.utils.warning("Failed to locate zfs dataset list")
|
||||
return None
|
||||
|
||||
# Find the root dataset
|
||||
for dataset in zfs:
|
||||
try:
|
||||
if dataset["mountpoint"] == "/":
|
||||
return dataset["zpool"] + "/" + dataset["dsName"]
|
||||
except KeyError:
|
||||
# This should be impossible
|
||||
libcalamares.utils.warning("Internal error handling zfs dataset")
|
||||
raise
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def is_btrfs_root(partition):
|
||||
""" Returns True if the partition object refers to a btrfs root filesystem
|
||||
|
||||
:param partition: A partition map from global storage
|
||||
:return: True if btrfs and root, False otherwise
|
||||
"""
|
||||
return partition["mountPoint"] == "/" and partition["fs"] == "btrfs"
|
||||
|
||||
|
||||
def is_zfs_root(partition):
|
||||
""" Returns True if the partition object refers to a zfs root filesystem
|
||||
|
||||
:param partition: A partition map from global storage
|
||||
:return: True if zfs and root, False otherwise
|
||||
"""
|
||||
return partition["mountPoint"] == "/" and partition["fs"] == "zfs"
|
||||
|
||||
|
||||
def create_systemd_boot_conf(install_path, efi_dir, uuid, entry, entry_name, kernel_type):
|
||||
"""
|
||||
Creates systemd-boot configuration files based on given parameters.
|
||||
@@ -133,11 +177,24 @@ def create_systemd_boot_conf(install_path, efi_dir, uuid, entry, entry_name, ker
|
||||
"root=/dev/mapper/"
|
||||
+ partition["luksMapperName"]]
|
||||
|
||||
# systemd-boot with a BTRFS root filesystem needs to be told
|
||||
# about the root subvolume.
|
||||
for partition in partitions:
|
||||
if partition["mountPoint"] == "/" and partition["fs"] == "btrfs":
|
||||
kernel_params.append("rootflags=subvol=@")
|
||||
# systemd-boot with a BTRFS root filesystem needs to be told abouut the root subvolume.
|
||||
# If a btrfs root subvolume wasn't set, it means the root is directly on the partition
|
||||
# and this option isn't needed
|
||||
if is_btrfs_root(partition):
|
||||
btrfs_root_subvolume = libcalamares.globalstorage.value("btrfsRootSubvolume")
|
||||
if btrfs_root_subvolume:
|
||||
kernel_params.append("rootflags=subvol=" + btrfs_root_subvolume)
|
||||
|
||||
# zfs needs to be told the location of the root dataset
|
||||
if is_zfs_root(partition):
|
||||
zfs_root_path = get_zfs_root()
|
||||
if zfs_root_path is not None:
|
||||
kernel_params.append("zfs=" + zfs_root_path)
|
||||
else:
|
||||
# Something is really broken if we get to this point
|
||||
libcalamares.utils.warning("Internal error handling zfs dataset")
|
||||
raise Exception("Internal zfs data missing, please contact your distribution")
|
||||
|
||||
if cryptdevice_params:
|
||||
kernel_params.extend(cryptdevice_params)
|
||||
@@ -314,6 +371,76 @@ def get_grub_efi_parameters():
|
||||
return None
|
||||
|
||||
|
||||
def run_grub_mkconfig(partitions, output_file):
|
||||
"""
|
||||
Runs grub-mkconfig in the target environment
|
||||
|
||||
:param partitions: The partitions list from global storage
|
||||
:param output_file: A string containing the path to the generating grub config file
|
||||
:return:
|
||||
"""
|
||||
|
||||
# zfs needs an environment variable set for grub-mkconfig
|
||||
if any([is_zfs_root(partition) for partition in partitions]):
|
||||
check_target_env_call(["sh", "-c", "ZPOOL_VDEV_NAME_PATH=1 " +
|
||||
libcalamares.job.configuration["grubMkconfig"] + " -o " + output_file])
|
||||
else:
|
||||
# The input file /etc/default/grub should already be filled out by the
|
||||
# grubcfg job module.
|
||||
check_target_env_call([libcalamares.job.configuration["grubMkconfig"], "-o", output_file])
|
||||
|
||||
|
||||
def run_grub_install(fw_type, partitions, efi_directory=None):
|
||||
"""
|
||||
Runs grub-install in the target environment
|
||||
|
||||
:param fw_type: A string which is "efi" for UEFI installs. Any other value results in a BIOS install
|
||||
:param partitions: The partitions list from global storage
|
||||
:param efi_directory: The path of the efi directory relative to the root of the install
|
||||
:return:
|
||||
"""
|
||||
|
||||
is_zfs = any([is_zfs_root(partition) for partition in partitions])
|
||||
|
||||
# zfs needs an environment variable set for grub
|
||||
if is_zfs:
|
||||
check_target_env_call(["sh", "-c", "echo ZPOOL_VDEV_NAME_PATH=1 >> /etc/environment"])
|
||||
|
||||
if fw_type == "efi":
|
||||
efi_bootloader_id = efi_label()
|
||||
efi_target, efi_grub_file, efi_boot_file = get_grub_efi_parameters()
|
||||
|
||||
if is_zfs:
|
||||
check_target_env_call(["sh", "-c", "ZPOOL_VDEV_NAME_PATH=1 " + libcalamares.job.configuration["grubInstall"]
|
||||
+ " --target=" + efi_target + " --efi-directory=" + efi_directory
|
||||
+ " --bootloader-id=" + efi_bootloader_id + " --force"])
|
||||
else:
|
||||
check_target_env_call([libcalamares.job.configuration["grubInstall"],
|
||||
"--target=" + efi_target,
|
||||
"--efi-directory=" + efi_directory,
|
||||
"--bootloader-id=" + efi_bootloader_id,
|
||||
"--force"])
|
||||
else:
|
||||
if libcalamares.globalstorage.value("bootLoader") is None:
|
||||
return
|
||||
|
||||
boot_loader = libcalamares.globalstorage.value("bootLoader")
|
||||
if boot_loader["installPath"] is None:
|
||||
return
|
||||
|
||||
if is_zfs:
|
||||
check_target_env_call(["sh", "-c", "ZPOOL_VDEV_NAME_PATH=1 "
|
||||
+ libcalamares.job.configuration["grubInstall"]
|
||||
+ " --target=i386-pc --recheck --force "
|
||||
+ boot_loader["installPath"]])
|
||||
else:
|
||||
check_target_env_call([libcalamares.job.configuration["grubInstall"],
|
||||
"--target=i386-pc",
|
||||
"--recheck",
|
||||
"--force",
|
||||
boot_loader["installPath"]])
|
||||
|
||||
|
||||
def install_grub(efi_directory, fw_type):
|
||||
"""
|
||||
Installs grub as bootloader, either in pc or efi mode.
|
||||
@@ -321,6 +448,12 @@ def install_grub(efi_directory, fw_type):
|
||||
:param efi_directory:
|
||||
:param fw_type:
|
||||
"""
|
||||
# get the partition from global storage
|
||||
partitions = libcalamares.globalstorage.value("partitions")
|
||||
if not partitions:
|
||||
libcalamares.utils.warning(_("Failed to install grub, no partitions defined in global storage"))
|
||||
return
|
||||
|
||||
if fw_type == "efi":
|
||||
libcalamares.utils.debug("Bootloader: grub (efi)")
|
||||
install_path = libcalamares.globalstorage.value("rootMountPoint")
|
||||
@@ -333,11 +466,7 @@ def install_grub(efi_directory, fw_type):
|
||||
|
||||
efi_target, efi_grub_file, efi_boot_file = get_grub_efi_parameters()
|
||||
|
||||
check_target_env_call([libcalamares.job.configuration["grubInstall"],
|
||||
"--target=" + efi_target,
|
||||
"--efi-directory=" + efi_directory,
|
||||
"--bootloader-id=" + efi_bootloader_id,
|
||||
"--force"])
|
||||
run_grub_install(fw_type, partitions, efi_directory)
|
||||
|
||||
# VFAT is weird, see issue CAL-385
|
||||
install_efi_directory_firmware = (vfat_correct_case(
|
||||
@@ -356,36 +485,21 @@ def install_grub(efi_directory, fw_type):
|
||||
os.makedirs(install_efi_boot_directory)
|
||||
|
||||
# Workaround for some UEFI firmwares
|
||||
FALLBACK = "installEFIFallback"
|
||||
libcalamares.utils.debug("UEFI Fallback: " + str(libcalamares.job.configuration.get(FALLBACK, "<unset>")))
|
||||
if libcalamares.job.configuration.get(FALLBACK, True):
|
||||
fallback = "installEFIFallback"
|
||||
libcalamares.utils.debug("UEFI Fallback: " + str(libcalamares.job.configuration.get(fallback, "<unset>")))
|
||||
if libcalamares.job.configuration.get(fallback, True):
|
||||
libcalamares.utils.debug(" .. installing '{!s}' fallback firmware".format(efi_boot_file))
|
||||
efi_file_source = os.path.join(install_efi_directory_firmware,
|
||||
efi_bootloader_id,
|
||||
efi_grub_file)
|
||||
efi_file_target = os.path.join(install_efi_boot_directory,
|
||||
efi_boot_file)
|
||||
efi_bootloader_id,
|
||||
efi_grub_file)
|
||||
efi_file_target = os.path.join(install_efi_boot_directory, efi_boot_file)
|
||||
|
||||
shutil.copy2(efi_file_source, efi_file_target)
|
||||
else:
|
||||
libcalamares.utils.debug("Bootloader: grub (bios)")
|
||||
if libcalamares.globalstorage.value("bootLoader") is None:
|
||||
return
|
||||
run_grub_install(fw_type, partitions)
|
||||
|
||||
boot_loader = libcalamares.globalstorage.value("bootLoader")
|
||||
if boot_loader["installPath"] is None:
|
||||
return
|
||||
|
||||
check_target_env_call([libcalamares.job.configuration["grubInstall"],
|
||||
"--target=i386-pc",
|
||||
"--recheck",
|
||||
"--force",
|
||||
boot_loader["installPath"]])
|
||||
|
||||
# The input file /etc/default/grub should already be filled out by the
|
||||
# grubcfg job module.
|
||||
check_target_env_call([libcalamares.job.configuration["grubMkconfig"],
|
||||
"-o", libcalamares.job.configuration["grubCfg"]])
|
||||
run_grub_mkconfig(partitions, libcalamares.job.configuration["grubCfg"])
|
||||
|
||||
|
||||
def install_secureboot(efi_directory):
|
||||
|
@@ -23,6 +23,7 @@ displaymanagers:
|
||||
- mdm
|
||||
- lxdm
|
||||
- kdm
|
||||
- greetd
|
||||
|
||||
# Enable the following settings to force a desktop environment
|
||||
# in your displaymanager configuration file. This will attempt
|
||||
|
@@ -10,7 +10,7 @@ properties:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
enum: [slim, sddm, lightdm, gdm, mdm, lxdm, kdm]
|
||||
enum: [slim, sddm, lightdm, gdm, mdm, lxdm, kdm, greetd]
|
||||
minItems: 1 # Must be non-empty, if present at all
|
||||
defaultDesktopEnvironment:
|
||||
type: object
|
||||
|
@@ -17,9 +17,7 @@
|
||||
|
||||
import abc
|
||||
import os
|
||||
import re
|
||||
import libcalamares
|
||||
import configparser
|
||||
|
||||
from libcalamares.utils import gettext_path, gettext_languages
|
||||
|
||||
@@ -796,6 +794,8 @@ class DMsddm(DisplayManager):
|
||||
executable = "sddm"
|
||||
|
||||
def set_autologin(self, username, do_autologin, default_desktop_environment):
|
||||
import configparser
|
||||
|
||||
# Systems with Sddm as Desktop Manager
|
||||
sddm_conf_path = os.path.join(self.root_mount_point, "etc/sddm.conf")
|
||||
|
||||
@@ -835,6 +835,91 @@ class DMsddm(DisplayManager):
|
||||
pass
|
||||
|
||||
|
||||
class DMgreetd(DisplayManager):
|
||||
name = "greetd"
|
||||
executable = "greetd"
|
||||
greeter_user = "greeter"
|
||||
greeter_group = "greetd"
|
||||
config_data = {}
|
||||
|
||||
def os_path(self, path):
|
||||
return os.path.join(self.root_mount_point, path)
|
||||
|
||||
def config_path(self):
|
||||
return self.os_path("etc/greetd/config.toml")
|
||||
|
||||
def environments_path(self):
|
||||
return self.os_path("etc/greetd/environments")
|
||||
|
||||
def config_load(self):
|
||||
import toml
|
||||
|
||||
if (os.path.exists(self.config_path())):
|
||||
with open(self.config_path(), "r") as f:
|
||||
self.config_data = toml.load(f)
|
||||
|
||||
self.config_data['terminal'] = dict(vt = "next")
|
||||
|
||||
default_session_group = self.config_data.get('default_session', None)
|
||||
if not default_session_group:
|
||||
self.config_data['default_session'] = {}
|
||||
|
||||
self.config_data['default_session']['user'] = self.greeter_user
|
||||
|
||||
return self.config_data
|
||||
|
||||
def config_write(self):
|
||||
import toml
|
||||
with open(self.config_path(), "w") as f:
|
||||
toml.dump(self.config_data, f)
|
||||
|
||||
def basic_setup(self):
|
||||
if libcalamares.utils.target_env_call(
|
||||
['getent', 'group', self.greeter_group]
|
||||
) != 0:
|
||||
libcalamares.utils.target_env_call(
|
||||
['groupadd', self.greeter_group]
|
||||
)
|
||||
|
||||
if libcalamares.utils.target_env_call(
|
||||
['getent', 'passwd', self.greeter_user]
|
||||
) != 0:
|
||||
libcalamares.utils.target_env_call(
|
||||
['useradd',
|
||||
'-c', '"Greeter User"',
|
||||
'-g', self.greeter_group,
|
||||
'-s', '/bin/bash',
|
||||
self.greeter_user
|
||||
]
|
||||
)
|
||||
|
||||
def desktop_environment_setup(self, default_desktop_environment):
|
||||
with open(self.environments_path(), 'w') as envs_file:
|
||||
envs_file.write(default_desktop_environment)
|
||||
|
||||
def greeter_setup(self):
|
||||
pass
|
||||
|
||||
def set_autologin(self, username, do_autologin, default_desktop_environment):
|
||||
self.config_load()
|
||||
|
||||
de_command = default_desktop_environment.executable
|
||||
if os.path.exists(self.os_path("usr/bin/gtkgreed")) and os.path.exists(self.os_path("usr/bin/cage")):
|
||||
self.config_data['default_session']['command'] = "cage -s -- gtkgreet"
|
||||
elif os.path.exists(self.os_path("usr/bin/tuigreet")):
|
||||
tuigreet_base_cmd = "tuigreet --remember --time --issue --asterisks --cmd "
|
||||
self.config_data['default_session']['command'] = tuigreet_base_cmd + de_command
|
||||
elif os.path.exists(self.os_path("usr/bin/ddlm")):
|
||||
self.config_data['default_session']['command'] = "ddlm --target " + de_command
|
||||
else:
|
||||
self.config_data['default_session']['command'] = "agreety --cmd " + de_command
|
||||
|
||||
if do_autologin == True:
|
||||
self.config_data['initial_session'] = dict(command = de_command, user = username)
|
||||
|
||||
self.config_write()
|
||||
|
||||
|
||||
class DMsysconfig(DisplayManager):
|
||||
name = "sysconfig"
|
||||
executable = None
|
||||
|
3
src/modules/displaymanager/tests/1.global
Normal file
3
src/modules/displaymanager/tests/1.global
Normal file
@@ -0,0 +1,3 @@
|
||||
# SPDX-FileCopyrightText: no
|
||||
# SPDX-License-Identifier: CC0-1.0
|
||||
rootMountPoint: /tmp
|
13
src/modules/displaymanager/tests/CMakeTests.txt
Normal file
13
src/modules/displaymanager/tests/CMakeTests.txt
Normal file
@@ -0,0 +1,13 @@
|
||||
# SPDX-FileCopyrightText: no
|
||||
# SPDX-License-Identifier: CC0-1.0
|
||||
#
|
||||
# We have tests to load (some) of the DMs specifically, to test their
|
||||
# configuration code. Those tests conventionally live in Python
|
||||
# files here in the tests/ directory. Add them.
|
||||
foreach(_dmname greetd sddm)
|
||||
add_test(
|
||||
NAME configure-displaymanager-${_dmname}
|
||||
COMMAND env PYTHONPATH=.: python3 ${CMAKE_CURRENT_LIST_DIR}/test-dm-${_dmname}.py
|
||||
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
|
||||
)
|
||||
endforeach()
|
25
src/modules/displaymanager/tests/test-dm-greetd.py
Normal file
25
src/modules/displaymanager/tests/test-dm-greetd.py
Normal file
@@ -0,0 +1,25 @@
|
||||
# SPDX-FileCopyrightText: no
|
||||
# SPDX-License-Identifier: CC0-1.0
|
||||
#
|
||||
# Calamares Boilerplate
|
||||
import libcalamares
|
||||
libcalamares.globalstorage = libcalamares.GlobalStorage(None)
|
||||
libcalamares.globalstorage.insert("testing", True)
|
||||
|
||||
# Module prep-work
|
||||
from src.modules.displaymanager import main
|
||||
default_desktop_environment = main.DesktopEnvironment("startplasma-x11", "kde-plasma.desktop")
|
||||
|
||||
import os
|
||||
os.makedirs("/tmp/etc/greetd/", exist_ok=True)
|
||||
try:
|
||||
os.remove("/tmp/etc/greetd/config.toml")
|
||||
except FileNotFoundError as e:
|
||||
pass
|
||||
|
||||
# Specific DM test
|
||||
d = main.DMgreetd("/tmp")
|
||||
d.set_autologin("d", True, default_desktop_environment)
|
||||
# .. and again (this time checks load/save)
|
||||
d.set_autologin("d", True, default_desktop_environment)
|
||||
d.set_autologin("d", True, default_desktop_environment)
|
18
src/modules/displaymanager/tests/test-dm-sddm.py
Normal file
18
src/modules/displaymanager/tests/test-dm-sddm.py
Normal file
@@ -0,0 +1,18 @@
|
||||
# SPDX-FileCopyrightText: no
|
||||
# SPDX-License-Identifier: CC0-1.0
|
||||
#
|
||||
# Calamares Boilerplate
|
||||
import libcalamares
|
||||
libcalamares.globalstorage = libcalamares.GlobalStorage(None)
|
||||
libcalamares.globalstorage.insert("testing", True)
|
||||
|
||||
# Module prep-work
|
||||
from src.modules.displaymanager import main
|
||||
default_desktop_environment = main.DesktopEnvironment("startplasma-x11", "kde-plasma.desktop")
|
||||
|
||||
# Specific DM test
|
||||
d = main.DMsddm("/tmp")
|
||||
d.set_autologin("d", True, default_desktop_environment)
|
||||
# .. and again (this time checks load/save)
|
||||
d.set_autologin("d", True, default_desktop_environment)
|
||||
d.set_autologin("d", True, default_desktop_environment)
|
121
src/modules/finishedq/finishedq@mobile.qml
Normal file
121
src/modules/finishedq/finishedq@mobile.qml
Normal file
@@ -0,0 +1,121 @@
|
||||
/* === This file is part of Calamares - <https://github.com/calamares> ===
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2021 Anke Boersma <demm@kaosx.us>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
* License-Filename: LICENSE
|
||||
*
|
||||
* Calamares is Free Software: see the License-Identifier above.
|
||||
*
|
||||
*/
|
||||
|
||||
import io.calamares.core 1.0
|
||||
import io.calamares.ui 1.0
|
||||
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15
|
||||
import QtQuick.Layouts 1.3
|
||||
import org.kde.kirigami 2.7 as Kirigami
|
||||
import QtGraphicalEffects 1.0
|
||||
import QtQuick.Window 2.3
|
||||
|
||||
Page {
|
||||
|
||||
id: finished
|
||||
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
|
||||
header: Kirigami.Heading {
|
||||
width: parent.width
|
||||
height: 100
|
||||
id: header
|
||||
Layout.fillWidth: true
|
||||
horizontalAlignment: Qt.AlignHCenter
|
||||
color: Kirigami.Theme.textColor
|
||||
level: 1
|
||||
text: qsTr("Installation Completed")
|
||||
|
||||
Text {
|
||||
anchors.top: header.bottom
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
font.pointSize: 12
|
||||
text: qsTr("%1 has been installed on your computer.<br/>
|
||||
You may now restart your device.").arg(Branding.string(Branding.ProductName))
|
||||
}
|
||||
|
||||
Image {
|
||||
source: "seedling.svg"
|
||||
anchors.top: header.bottom
|
||||
anchors.topMargin: 80
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
width: 64
|
||||
height: 64
|
||||
mipmap: true
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.alignment: Qt.AlignRight|Qt.AlignVCenter
|
||||
anchors.centerIn: parent
|
||||
spacing: 6
|
||||
|
||||
Button {
|
||||
id: button
|
||||
text: qsTr("Close")
|
||||
icon.name: "application-exit"
|
||||
onClicked: { ViewManager.quit(); }
|
||||
}
|
||||
|
||||
Button {
|
||||
text: qsTr("Restart")
|
||||
icon.name: "system-reboot"
|
||||
onClicked: { config.doRestart(true); }
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin : 100
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
|
||||
ProgressBar {
|
||||
id: autoRestartBar
|
||||
value: 1.0
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: autoRestartTimer
|
||||
// This is in milliseconds and should be less than 1000 (because of logic in onTriggered)
|
||||
interval: 100
|
||||
repeat: true
|
||||
running: false
|
||||
// Whenever the timer fires (1000 / interval times a second) count the progress bar down
|
||||
// by 1%. When the bar is empty, try to restart normally; as a backup, when the bar
|
||||
// is empty change settings and schedule it to quit 1000 milliseconds (1s) later.
|
||||
onTriggered: {
|
||||
autoRestartBar.value -= 0.01;
|
||||
if (autoRestartBar.value <= 0.0) {
|
||||
// First time through here, set the interval to 1000 so that the
|
||||
// second time (1 second later) goes to quit().
|
||||
if ( interval > 999) { ViewManager.quit(); }
|
||||
else { config.doRestart(true); running = false; interval = 1000; repeat = false; start(); }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function onActivate()
|
||||
{
|
||||
autoRestartTimer.running = true
|
||||
}
|
||||
|
||||
function onLeave()
|
||||
{
|
||||
}
|
||||
}
|
@@ -11,9 +11,13 @@
|
||||
# Mount options to use for all filesystems. If a specific filesystem
|
||||
# is listed here, use those options, otherwise use the *default*
|
||||
# options from this mapping.
|
||||
#
|
||||
# With kernels 5.15 and newer be cautious of adding the option space_cache
|
||||
# to the btrfs mount options. The default in 5.15 changed to space_cache=v2.
|
||||
# If space_cache or space_cache=v1 are specified, it may fail to remount.
|
||||
mountOptions:
|
||||
default: defaults,noatime
|
||||
btrfs: defaults,noatime,space_cache,autodefrag,compress=zstd
|
||||
btrfs: defaults,noatime,autodefrag,compress=zstd
|
||||
|
||||
# Mount options to use for the EFI System Partition. If not defined, the
|
||||
# *mountOptions* for *vfat* are used, or if that is not set either,
|
||||
|
@@ -196,7 +196,7 @@ class FstabGenerator(object):
|
||||
dct = self.generate_fstab_line_info(mount_entry)
|
||||
if dct:
|
||||
self.print_fstab_line(dct, file=fstab_file)
|
||||
else:
|
||||
elif partition["fs"] != "zfs": # zfs partitions don't need an entry in fstab
|
||||
dct = self.generate_fstab_line_info(partition)
|
||||
if dct:
|
||||
self.print_fstab_line(dct, file=fstab_file)
|
||||
|
@@ -55,6 +55,32 @@ def get_grub_config_path(root_mount_point):
|
||||
return os.path.join(default_dir, default_config_file)
|
||||
|
||||
|
||||
def get_zfs_root():
|
||||
"""
|
||||
Looks in global storage to find the zfs root
|
||||
|
||||
:return: A string containing the path to the zfs root or None if it is not found
|
||||
"""
|
||||
|
||||
zfs = libcalamares.globalstorage.value("zfsDatasets")
|
||||
|
||||
if not zfs:
|
||||
libcalamares.utils.warning("Failed to locate zfs dataset list")
|
||||
return None
|
||||
|
||||
# Find the root dataset
|
||||
for dataset in zfs:
|
||||
try:
|
||||
if dataset["mountpoint"] == "/":
|
||||
return dataset["zpool"] + "/" + dataset["dsName"]
|
||||
except KeyError:
|
||||
# This should be impossible
|
||||
libcalamares.utils.warning("Internal error handling zfs dataset")
|
||||
raise
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def modify_grub_default(partitions, root_mount_point, distributor):
|
||||
"""
|
||||
Configures '/etc/default/grub' for hibernation and plymouth.
|
||||
@@ -91,6 +117,8 @@ def modify_grub_default(partitions, root_mount_point, distributor):
|
||||
swap_outer_mappername = None
|
||||
no_save_default = False
|
||||
unencrypted_separate_boot = any(p["mountPoint"] == "/boot" and "luksMapperName" not in p for p in partitions)
|
||||
# If there is no dracut, and the root partition is ZFS, this gets set below
|
||||
zfs_root_path = None
|
||||
|
||||
for partition in partitions:
|
||||
if partition["mountPoint"] in ("/", "/boot") and partition["fs"] in ("btrfs", "f2fs"):
|
||||
@@ -141,8 +169,15 @@ def modify_grub_default(partitions, root_mount_point, distributor):
|
||||
)
|
||||
]
|
||||
|
||||
if partition["fs"] == "zfs" and partition["mountPoint"] == "/":
|
||||
zfs_root_path = get_zfs_root()
|
||||
|
||||
kernel_params = ["quiet"]
|
||||
|
||||
# Currently, grub doesn't detect this properly so it must be set manually
|
||||
if zfs_root_path:
|
||||
kernel_params.insert(0, "zfs=" + zfs_root_path)
|
||||
|
||||
if cryptdevice_params:
|
||||
kernel_params.extend(cryptdevice_params)
|
||||
|
||||
|
@@ -150,6 +150,7 @@ def find_initcpio_features(partitions, root_mount_point):
|
||||
|
||||
swap_uuid = ""
|
||||
uses_btrfs = False
|
||||
uses_zfs = False
|
||||
uses_lvm2 = False
|
||||
encrypt_hook = False
|
||||
openswap_hook = False
|
||||
@@ -172,6 +173,9 @@ def find_initcpio_features(partitions, root_mount_point):
|
||||
if partition["fs"] == "btrfs":
|
||||
uses_btrfs = True
|
||||
|
||||
if partition["fs"] == "zfs":
|
||||
uses_zfs = True
|
||||
|
||||
if "lvm2" in partition["fs"]:
|
||||
uses_lvm2 = True
|
||||
|
||||
@@ -198,6 +202,9 @@ def find_initcpio_features(partitions, root_mount_point):
|
||||
if uses_lvm2:
|
||||
hooks.append("lvm2")
|
||||
|
||||
if uses_zfs:
|
||||
hooks.append("zfs")
|
||||
|
||||
if swap_uuid != "":
|
||||
if encrypt_hook and openswap_hook:
|
||||
hooks.extend(["openswap"])
|
||||
|
@@ -20,12 +20,24 @@ import os
|
||||
import libcalamares
|
||||
|
||||
import gettext
|
||||
|
||||
_ = gettext.translation("calamares-python",
|
||||
localedir=libcalamares.utils.gettext_path(),
|
||||
languages=libcalamares.utils.gettext_languages(),
|
||||
fallback=True).gettext
|
||||
|
||||
|
||||
class ZfsException(Exception):
|
||||
"""Exception raised when there is a problem with zfs
|
||||
|
||||
Attributes:
|
||||
message -- explanation of the error
|
||||
"""
|
||||
|
||||
def __init__(self, message):
|
||||
self.message = message
|
||||
|
||||
|
||||
def pretty_name():
|
||||
return _("Mounting partitions.")
|
||||
|
||||
@@ -47,20 +59,85 @@ def get_btrfs_subvolumes(partitions):
|
||||
if btrfs_subvolumes is None:
|
||||
libcalamares.utils.warning("No configuration for btrfsSubvolumes")
|
||||
if not btrfs_subvolumes:
|
||||
btrfs_subvolumes = [ dict(mountPoint="/", subvolume="/@"), dict(mountPoint="/home", subvolume="/@home") ]
|
||||
btrfs_subvolumes = [dict(mountPoint="/", subvolume="/@"), dict(mountPoint="/home", subvolume="/@home")]
|
||||
|
||||
# Filter out the subvolumes which have a dedicated partition
|
||||
non_root_partition_mounts = [ m for m in [ p.get("mountPoint", None) for p in partitions ] if m is not None and m != '/' ]
|
||||
btrfs_subvolumes = list(filter(lambda s : s["mountPoint"] not in non_root_partition_mounts, btrfs_subvolumes))
|
||||
non_root_partition_mounts = [m for m in [p.get("mountPoint", None) for p in partitions] if
|
||||
m is not None and m != '/']
|
||||
btrfs_subvolumes = list(filter(lambda s: s["mountPoint"] not in non_root_partition_mounts, btrfs_subvolumes))
|
||||
|
||||
# If we have a swap **file**, give it a separate subvolume.
|
||||
swap_choice = libcalamares.globalstorage.value( "partitionChoices" )
|
||||
if swap_choice and swap_choice.get( "swap", None ) == "file":
|
||||
swap_choice = libcalamares.globalstorage.value("partitionChoices")
|
||||
if swap_choice and swap_choice.get("swap", None) == "file":
|
||||
btrfs_subvolumes.append({'mountPoint': '/swap', 'subvolume': '/@swap'})
|
||||
|
||||
return btrfs_subvolumes
|
||||
|
||||
|
||||
def mount_zfs(root_mount_point, partition):
|
||||
""" Mounts a zfs partition at @p root_mount_point
|
||||
|
||||
:param root_mount_point: The absolute path to the root of the install
|
||||
:param partition: The partition map from global storage for this partition
|
||||
:return:
|
||||
"""
|
||||
# Get the list of zpools from global storage
|
||||
zfs_pool_list = libcalamares.globalstorage.value("zfsPoolInfo")
|
||||
if not zfs_pool_list:
|
||||
libcalamares.utils.warning("Failed to locate zfsPoolInfo data in global storage")
|
||||
raise ZfsException(_("Internal error mounting zfs datasets"))
|
||||
|
||||
# Find the zpool matching this partition
|
||||
for zfs_pool in zfs_pool_list:
|
||||
if zfs_pool["mountpoint"] == partition["mountPoint"]:
|
||||
pool_name = zfs_pool["poolName"]
|
||||
ds_name = zfs_pool["dsName"]
|
||||
|
||||
# import the zpool
|
||||
try:
|
||||
libcalamares.utils.host_env_process_output(["zpool", "import", "-N", "-R", root_mount_point, pool_name], None)
|
||||
except subprocess.CalledProcessError:
|
||||
raise ZfsException(_("Failed to import zpool"))
|
||||
|
||||
# Get the encrpytion information from global storage
|
||||
zfs_info_list = libcalamares.globalstorage.value("zfsInfo")
|
||||
encrypt = False
|
||||
if zfs_info_list:
|
||||
for zfs_info in zfs_info_list:
|
||||
if zfs_info["mountpoint"] == partition["mountPoint"] and zfs_info["encrypted"] is True:
|
||||
encrypt = True
|
||||
passphrase = zfs_info["passphrase"]
|
||||
|
||||
if encrypt is True:
|
||||
# The zpool is encrypted, we need to unlock it
|
||||
try:
|
||||
libcalamares.utils.host_env_process_output(["zfs", "load-key", pool_name], None, passphrase)
|
||||
except subprocess.CalledProcessError:
|
||||
raise ZfsException(_("Failed to unlock zpool"))
|
||||
|
||||
if partition["mountPoint"] == '/':
|
||||
# Get the zfs dataset list from global storage
|
||||
zfs = libcalamares.globalstorage.value("zfsDatasets")
|
||||
|
||||
if not zfs:
|
||||
libcalamares.utils.warning("Failed to locate zfs dataset list")
|
||||
raise ZfsException(_("Internal error mounting zfs datasets"))
|
||||
|
||||
zfs.sort(key=lambda x: x["mountpoint"])
|
||||
for dataset in zfs:
|
||||
try:
|
||||
if dataset["canMount"] == "noauto" or dataset["canMount"] is True:
|
||||
libcalamares.utils.host_env_process_output(["zfs", "mount",
|
||||
dataset["zpool"] + '/' + dataset["dsName"]])
|
||||
except subprocess.CalledProcessError:
|
||||
raise ZfsException(_("Failed to set zfs mountpoint"))
|
||||
else:
|
||||
try:
|
||||
libcalamares.utils.host_env_process_output(["zfs", "mount", pool_name + '/' + ds_name])
|
||||
except subprocess.CalledProcessError:
|
||||
raise ZfsException(_("Failed to set zfs mountpoint"))
|
||||
|
||||
|
||||
def mount_partition(root_mount_point, partition, partitions):
|
||||
"""
|
||||
Do a single mount of @p partition inside @p root_mount_point.
|
||||
@@ -96,11 +173,14 @@ def mount_partition(root_mount_point, partition, partitions):
|
||||
if "luksMapperName" in partition:
|
||||
device = os.path.join("/dev/mapper", partition["luksMapperName"])
|
||||
|
||||
if libcalamares.utils.mount(device,
|
||||
mount_point,
|
||||
fstype,
|
||||
partition.get("options", "")) != 0:
|
||||
libcalamares.utils.warning("Cannot mount {}".format(device))
|
||||
if fstype == "zfs":
|
||||
mount_zfs(root_mount_point, partition)
|
||||
else: # fstype == "zfs"
|
||||
if libcalamares.utils.mount(device,
|
||||
mount_point,
|
||||
fstype,
|
||||
partition.get("options", "")) != 0:
|
||||
libcalamares.utils.warning("Cannot mount {}".format(device))
|
||||
|
||||
# Special handling for btrfs subvolumes. Create the subvolumes listed in mount.conf
|
||||
if fstype == "btrfs" and partition["mountPoint"] == '/':
|
||||
@@ -111,9 +191,11 @@ def mount_partition(root_mount_point, partition, partitions):
|
||||
libcalamares.globalstorage.insert("btrfsSubvolumes", btrfs_subvolumes)
|
||||
# Create the subvolumes that are in the completed list
|
||||
for s in btrfs_subvolumes:
|
||||
subprocess.check_call(['btrfs', 'subvolume', 'create',
|
||||
root_mount_point + s['subvolume']])
|
||||
|
||||
subprocess.check_call(["btrfs", "subvolume", "create",
|
||||
root_mount_point + s["subvolume"]])
|
||||
if s["mountPoint"] == "/":
|
||||
# insert the root subvolume into global storage
|
||||
libcalamares.globalstorage.insert("btrfsRootSubvolume", s["subvolume"])
|
||||
subprocess.check_call(["umount", "-v", root_mount_point])
|
||||
|
||||
device = partition["device"]
|
||||
@@ -126,9 +208,9 @@ def mount_partition(root_mount_point, partition, partitions):
|
||||
mount_option = "subvol={}".format(s['subvolume'])
|
||||
subvolume_mountpoint = mount_point[:-1] + s['mountPoint']
|
||||
if libcalamares.utils.mount(device,
|
||||
subvolume_mountpoint,
|
||||
fstype,
|
||||
",".join([mount_option, partition.get("options", "")])) != 0:
|
||||
subvolume_mountpoint,
|
||||
fstype,
|
||||
",".join([mount_option, partition.get("options", "")])) != 0:
|
||||
libcalamares.utils.warning("Cannot mount {}".format(device))
|
||||
|
||||
|
||||
@@ -142,7 +224,7 @@ def run():
|
||||
if not partitions:
|
||||
libcalamares.utils.warning("partitions is empty, {!s}".format(partitions))
|
||||
return (_("Configuration Error"),
|
||||
_("No partitions are defined for <pre>{!s}</pre> to use." ).format("mount"))
|
||||
_("No partitions are defined for <pre>{!s}</pre> to use.").format("mount"))
|
||||
|
||||
root_mount_point = tempfile.mkdtemp(prefix="calamares-root-")
|
||||
|
||||
@@ -159,10 +241,13 @@ def run():
|
||||
# This way, we ensure / is mounted before the rest, and every mount point
|
||||
# is created on the right partition (e.g. if a partition is to be mounted
|
||||
# under /tmp, we make sure /tmp is mounted before the partition)
|
||||
mountable_partitions = [ p for p in partitions + extra_mounts if "mountPoint" in p and p["mountPoint"] ]
|
||||
mountable_partitions = [p for p in partitions + extra_mounts if "mountPoint" in p and p["mountPoint"]]
|
||||
mountable_partitions.sort(key=lambda x: x["mountPoint"])
|
||||
for partition in mountable_partitions:
|
||||
mount_partition(root_mount_point, partition, partitions)
|
||||
try:
|
||||
for partition in mountable_partitions:
|
||||
mount_partition(root_mount_point, partition, partitions)
|
||||
except ZfsException as ze:
|
||||
return _("zfs mounting error"), ze.message
|
||||
|
||||
libcalamares.globalstorage.insert("rootMountPoint", root_mount_point)
|
||||
|
||||
|
@@ -257,14 +257,16 @@ PartitionCoreModule::doInit()
|
||||
cDebug() << Logger::SubEntry << "node\tcapacity\tname\tprettyName";
|
||||
for ( auto device : devices )
|
||||
{
|
||||
cDebug() << Logger::SubEntry << Logger::Pointer( device );
|
||||
if ( device )
|
||||
{
|
||||
// Gives ownership of the Device* to the DeviceInfo object
|
||||
auto deviceInfo = new DeviceInfo( device );
|
||||
m_deviceInfos << deviceInfo;
|
||||
cDebug() << Logger::SubEntry << device->deviceNode() << device->capacity() << device->name()
|
||||
<< device->prettyName();
|
||||
cDebug() << Logger::SubEntry
|
||||
<< device->deviceNode()
|
||||
<< device->capacity()
|
||||
<< Logger::RedactedName( "DevName", device->name() )
|
||||
<< Logger::RedactedName( "DevNamePretty", device->prettyName() );
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -707,10 +709,10 @@ PartitionCoreModule::dumpQueue() const
|
||||
cDebug() << "# Queue:";
|
||||
for ( auto info : m_deviceInfos )
|
||||
{
|
||||
cDebug() << Logger::SubEntry << "## Device:" << info->device->name();
|
||||
cDebug() << Logger::SubEntry << "## Device:" << info->device->deviceNode();
|
||||
for ( const auto& job : info->jobs() )
|
||||
{
|
||||
cDebug() << Logger::SubEntry << "-" << job->prettyName();
|
||||
cDebug() << Logger::SubEntry << "-" << job->metaObject()->className();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -296,7 +296,9 @@ PartitionLayout::createPartitions( Device* dev,
|
||||
}
|
||||
|
||||
Partition* part = nullptr;
|
||||
if ( luksPassphrase.isEmpty() )
|
||||
|
||||
// Encryption for zfs is handled in the zfs module
|
||||
if ( luksPassphrase.isEmpty() || correctFS( entry.partFileSystem ) == FileSystem::Zfs )
|
||||
{
|
||||
part = KPMHelpers::createNewPartition( parent,
|
||||
*dev,
|
||||
@@ -319,6 +321,24 @@ PartitionLayout::createPartitions( Device* dev,
|
||||
luksPassphrase,
|
||||
KPM_PARTITION_FLAG( None ) );
|
||||
}
|
||||
|
||||
// For zfs, we need to make the passphrase available to later modules
|
||||
if ( correctFS( entry.partFileSystem ) == FileSystem::Zfs )
|
||||
{
|
||||
Calamares::GlobalStorage* storage = Calamares::JobQueue::instance()->globalStorage();
|
||||
QList< QVariant > zfsInfoList;
|
||||
QVariantMap zfsInfo;
|
||||
|
||||
// Save the information subsequent modules will need
|
||||
zfsInfo[ "encrypted" ] = !luksPassphrase.isEmpty();
|
||||
zfsInfo[ "passphrase" ] = luksPassphrase;
|
||||
zfsInfo[ "mountpoint" ] = entry.partMountPoint;
|
||||
|
||||
// Add it to the list and insert it into global storage
|
||||
zfsInfoList.append( zfsInfo );
|
||||
storage->insert( "zfsInfo", zfsInfoList );
|
||||
}
|
||||
|
||||
PartitionInfo::setFormat( part, true );
|
||||
PartitionInfo::setMountPoint( part, entry.partMountPoint );
|
||||
if ( !entry.partLabel.isEmpty() )
|
||||
|
@@ -23,6 +23,7 @@
|
||||
|
||||
#include "GlobalStorage.h"
|
||||
#include "JobQueue.h"
|
||||
#include "Settings.h"
|
||||
#include "partition/FileSystem.h"
|
||||
#include "partition/PartitionQuery.h"
|
||||
#include "utils/Logger.h"
|
||||
@@ -104,7 +105,9 @@ CreatePartitionDialog::CreatePartitionDialog( Device* device,
|
||||
QStringList fsNames;
|
||||
for ( auto fs : FileSystemFactory::map() )
|
||||
{
|
||||
if ( fs->supportCreate() != FileSystem::cmdSupportNone && fs->type() != FileSystem::Extended )
|
||||
// We need to ensure zfs is added to the list if the zfs module is enabled
|
||||
if ( ( fs->type() == FileSystem::Type::Zfs && Calamares::Settings::instance()->isModuleEnabled( "zfs" ) )
|
||||
|| ( fs->supportCreate() != FileSystem::cmdSupportNone && fs->type() != FileSystem::Extended ) )
|
||||
{
|
||||
fsNames << userVisibleFS( fs ); // This is put into the combobox
|
||||
if ( fs->type() == defaultFSType )
|
||||
@@ -240,7 +243,8 @@ CreatePartitionDialog::getNewlyCreatedPartition()
|
||||
// does so, to set up the partition for create-and-then-set-flags.
|
||||
Partition* partition = nullptr;
|
||||
QString luksPassphrase = m_ui->encryptWidget->passphrase();
|
||||
if ( m_ui->encryptWidget->state() == EncryptWidget::Encryption::Confirmed && !luksPassphrase.isEmpty() )
|
||||
if ( m_ui->encryptWidget->state() == EncryptWidget::Encryption::Confirmed && !luksPassphrase.isEmpty()
|
||||
&& fsType != FileSystem::Zfs )
|
||||
{
|
||||
partition = KPMHelpers::createNewEncryptedPartition(
|
||||
m_parent, *m_device, m_role, fsType, fsLabel, first, last, luksPassphrase, PartitionTable::Flags() );
|
||||
@@ -251,6 +255,31 @@ CreatePartitionDialog::getNewlyCreatedPartition()
|
||||
m_parent, *m_device, m_role, fsType, fsLabel, first, last, PartitionTable::Flags() );
|
||||
}
|
||||
|
||||
// For zfs, we let the zfs module handle the encryption but we need to make the passphrase available to later modules
|
||||
if ( fsType == FileSystem::Zfs )
|
||||
{
|
||||
Calamares::GlobalStorage* storage = Calamares::JobQueue::instance()->globalStorage();
|
||||
QList< QVariant > zfsInfoList;
|
||||
QVariantMap zfsInfo;
|
||||
|
||||
// If this is not the first encrypted zfs partition, get the old list first
|
||||
if ( storage->contains( "zfsInfo" ) )
|
||||
{
|
||||
zfsInfoList = storage->value( "zfsInfo" ).toList();
|
||||
storage->remove( "zfsInfo" );
|
||||
}
|
||||
|
||||
// Save the information subsequent modules will need
|
||||
zfsInfo[ "encrypted" ]
|
||||
= m_ui->encryptWidget->state() == EncryptWidget::Encryption::Confirmed && !luksPassphrase.isEmpty();
|
||||
zfsInfo[ "passphrase" ] = luksPassphrase;
|
||||
zfsInfo[ "mountpoint" ] = selectedMountPoint( m_ui->mountPointComboBox );
|
||||
|
||||
// Add it to the list and insert it into global storage
|
||||
zfsInfoList.append( zfsInfo );
|
||||
storage->insert( "zfsInfo", zfsInfoList );
|
||||
}
|
||||
|
||||
if ( m_device->type() == Device::Type::LVM_Device )
|
||||
{
|
||||
partition->setPartitionPath( m_device->deviceNode() + QStringLiteral( "/" )
|
||||
|
@@ -25,6 +25,7 @@
|
||||
|
||||
#include "GlobalStorage.h"
|
||||
#include "JobQueue.h"
|
||||
#include "Settings.h"
|
||||
#include "partition/FileSystem.h"
|
||||
#include "utils/Logger.h"
|
||||
|
||||
@@ -89,7 +90,9 @@ EditExistingPartitionDialog::EditExistingPartitionDialog( Device* device,
|
||||
QStringList fsNames;
|
||||
for ( auto fs : FileSystemFactory::map() )
|
||||
{
|
||||
if ( fs->supportCreate() != FileSystem::cmdSupportNone && fs->type() != FileSystem::Extended )
|
||||
// We need to ensure zfs is added to the list if the zfs module is enabled
|
||||
if ( ( fs->type() == FileSystem::Type::Zfs && Calamares::Settings::instance()->isModuleEnabled( "zfs" ) )
|
||||
|| ( fs->supportCreate() != FileSystem::cmdSupportNone && fs->type() != FileSystem::Extended ) )
|
||||
{
|
||||
fsNames << userVisibleFS( fs ); // For the combo box
|
||||
}
|
||||
@@ -117,6 +120,12 @@ EditExistingPartitionDialog::EditExistingPartitionDialog( Device* device,
|
||||
m_ui->fileSystemLabel->setEnabled( m_ui->formatRadioButton->isChecked() );
|
||||
m_ui->fileSystemComboBox->setEnabled( m_ui->formatRadioButton->isChecked() );
|
||||
|
||||
// Force a format if the existing device is a zfs device since reusing a zpool isn't currently supported
|
||||
m_ui->formatRadioButton->setChecked( m_partition->fileSystem().type() == FileSystem::Type::Zfs );
|
||||
m_ui->formatRadioButton->setEnabled( !( m_partition->fileSystem().type() == FileSystem::Type::Zfs ) );
|
||||
m_ui->keepRadioButton->setChecked( !( m_partition->fileSystem().type() == FileSystem::Type::Zfs ) );
|
||||
m_ui->keepRadioButton->setEnabled( !( m_partition->fileSystem().type() == FileSystem::Type::Zfs ) );
|
||||
|
||||
setFlagList( *( m_ui->m_listFlags ), m_partition->availableFlags(), PartitionInfo::flags( m_partition ) );
|
||||
}
|
||||
|
||||
|
@@ -11,8 +11,10 @@
|
||||
|
||||
#include "CreatePartitionJob.h"
|
||||
|
||||
#include "core/PartitionInfo.h"
|
||||
#include "partition/FileSystem.h"
|
||||
#include "partition/PartitionQuery.h"
|
||||
#include "utils/CalamaresUtilsSystem.h"
|
||||
#include "utils/Logger.h"
|
||||
#include "utils/Units.h"
|
||||
|
||||
@@ -24,9 +26,79 @@
|
||||
#include <kpmcore/ops/newoperation.h>
|
||||
#include <kpmcore/util/report.h>
|
||||
|
||||
#include <qcoreapplication.h>
|
||||
#include <qregularexpression.h>
|
||||
|
||||
using CalamaresUtils::Partition::untranslatedFS;
|
||||
using CalamaresUtils::Partition::userVisibleFS;
|
||||
|
||||
/** @brief Create
|
||||
*
|
||||
* Uses sfdisk to remove @p partition. This should only be used in cases
|
||||
* where using kpmcore to remove the partition would not be appropriate
|
||||
*
|
||||
*/
|
||||
static Calamares::JobResult
|
||||
createZfs( Partition* partition, Device* device )
|
||||
{
|
||||
auto r = CalamaresUtils::System::instance()->runCommand(
|
||||
{ "sh",
|
||||
"-c",
|
||||
"echo start=" + QString::number( partition->firstSector() ) + " size="
|
||||
+ QString::number( partition->length() ) + " | sfdisk --append --force " + partition->devicePath() },
|
||||
std::chrono::seconds( 5 ) );
|
||||
if ( r.getExitCode() != 0 )
|
||||
{
|
||||
return Calamares::JobResult::error(
|
||||
QCoreApplication::translate( CreatePartitionJob::staticMetaObject.className(),
|
||||
"Failed to create partition" ),
|
||||
QCoreApplication::translate( CreatePartitionJob::staticMetaObject.className(),
|
||||
"Failed to create zfs partition with output: "
|
||||
+ r.getOutput().toLocal8Bit() ) );
|
||||
}
|
||||
|
||||
// Now we need to do some things that would normally be done by kpmcore
|
||||
|
||||
// First we get the device node from the output and set it as the partition path
|
||||
QRegularExpression re( QStringLiteral( "Created a new partition (\\d+)" ) );
|
||||
QRegularExpressionMatch rem = re.match( r.getOutput() );
|
||||
|
||||
QString deviceNode;
|
||||
if ( rem.hasMatch() )
|
||||
{
|
||||
if ( partition->devicePath().back().isDigit() )
|
||||
{
|
||||
deviceNode = partition->devicePath() + QLatin1Char( 'p' ) + rem.captured( 1 );
|
||||
}
|
||||
else
|
||||
{
|
||||
deviceNode = partition->devicePath() + rem.captured( 1 );
|
||||
}
|
||||
}
|
||||
|
||||
partition->setPartitionPath( deviceNode );
|
||||
|
||||
// If it is a gpt device, set the partition UUID
|
||||
if ( device->partitionTable()->type() == PartitionTable::gpt && partition->uuid().isEmpty() )
|
||||
{
|
||||
r = CalamaresUtils::System::instance()->runCommand(
|
||||
{ "sfdisk", "--list", "--output", "Device,UUID", partition->devicePath() }, std::chrono::seconds( 5 ) );
|
||||
if ( r.getExitCode() == 0 )
|
||||
{
|
||||
QRegularExpression re( deviceNode + QStringLiteral( " +(.+)" ) );
|
||||
QRegularExpressionMatch rem = re.match( r.getOutput() );
|
||||
|
||||
if ( rem.hasMatch() )
|
||||
{
|
||||
partition->setUUID( rem.captured( 1 ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Calamares::JobResult::ok();
|
||||
}
|
||||
|
||||
|
||||
CreatePartitionJob::CreatePartitionJob( Device* device, Partition* partition )
|
||||
: PartitionJob( partition )
|
||||
, m_device( device )
|
||||
@@ -194,6 +266,13 @@ CreatePartitionJob::prettyStatusMessage() const
|
||||
Calamares::JobResult
|
||||
CreatePartitionJob::exec()
|
||||
{
|
||||
// kpmcore doesn't currently handle this case properly so for now, we manually create the partion
|
||||
// The zfs module can later deal with creating a zpool in the partition
|
||||
if ( m_partition->fileSystem().type() == FileSystem::Type::Zfs )
|
||||
{
|
||||
return createZfs( m_partition, m_device );
|
||||
}
|
||||
|
||||
Report report( nullptr );
|
||||
NewOperation op( *m_device, m_partition );
|
||||
op.setStatus( Operation::StatusRunning );
|
||||
|
@@ -104,14 +104,19 @@ mapForPartition( Partition* partition, const QString& uuid )
|
||||
// Debugging for inside the loop in createPartitionList(),
|
||||
// so indent a bit
|
||||
Logger::CDebug deb;
|
||||
using TR = Logger::DebugRow< const char* const, const QString& >;
|
||||
using TR = Logger::DebugRow< const char* const, const QString >;
|
||||
// clang-format off
|
||||
deb << Logger::SubEntry << "mapping for" << partition->partitionPath() << partition->deviceNode()
|
||||
<< TR( "partlabel", map[ "partlabel" ].toString() ) << TR( "partuuid", map[ "partuuid" ].toString() )
|
||||
<< TR( "parttype", map[ "parttype" ].toString() ) << TR( "partattrs", map[ "partattrs" ].toString() )
|
||||
<< TR( "mountPoint:", PartitionInfo::mountPoint( partition ) ) << TR( "fs:", map[ "fs" ].toString() )
|
||||
<< TR( "fsName", map[ "fsName" ].toString() ) << TR( "uuid", uuid )
|
||||
<< TR( "partlabel", map[ "partlabel" ].toString() )
|
||||
<< TR( "partition-uuid (partuuid)", Logger::RedactedName( "PartUUID", map[ "partuuid" ].toString() ) )
|
||||
<< TR( "parttype", map[ "parttype" ].toString() )
|
||||
<< TR( "partattrs", map[ "partattrs" ].toString() )
|
||||
<< TR( "mountPoint:", PartitionInfo::mountPoint( partition ) )
|
||||
<< TR( "fs:", map[ "fs" ].toString() )
|
||||
<< TR( "fsName", map[ "fsName" ].toString() )
|
||||
<< TR( "filesystem-uuid (uuid)", Logger::RedactedName( "FSUUID", uuid ) )
|
||||
<< TR( "claimed", map[ "claimed" ].toString() );
|
||||
|
||||
// clang-format on
|
||||
if ( partition->roles().has( PartitionRole::Luks ) )
|
||||
{
|
||||
const FileSystem& fsRef = partition->fileSystem();
|
||||
|
@@ -49,6 +49,27 @@ def list_mounts(root_mount_point):
|
||||
return lst
|
||||
|
||||
|
||||
def export_zpools(root_mount_point):
|
||||
""" Exports the zpools if defined in global storage
|
||||
|
||||
:param root_mount_point: The absolute path to the root of the install
|
||||
:return:
|
||||
"""
|
||||
try:
|
||||
zfs_pool_list = libcalamares.globalstorage.value("zfsPoolInfo")
|
||||
zfs_pool_list.sort(reverse=True, key=lambda x: x["poolName"])
|
||||
if zfs_pool_list:
|
||||
for zfs_pool in zfs_pool_list:
|
||||
try:
|
||||
libcalamares.utils.host_env_process_output(['zpool', 'export', zfs_pool["poolName"]])
|
||||
except subprocess.CalledProcessError:
|
||||
libcalamares.utils.warning("Failed to export zpool")
|
||||
except Exception as e:
|
||||
# If this fails it shouldn't cause the installation to fail
|
||||
libcalamares.utils.warning("Received exception while exporting zpools: " + format(e))
|
||||
pass
|
||||
|
||||
|
||||
def run():
|
||||
""" Unmounts given mountpoints in decreasing order.
|
||||
|
||||
@@ -94,6 +115,8 @@ def run():
|
||||
# in the exception object.
|
||||
subprocess.check_output(["umount", "-lv", mount_point], stderr=subprocess.STDOUT)
|
||||
|
||||
export_zpools(root_mount_point)
|
||||
|
||||
os.rmdir(root_mount_point)
|
||||
|
||||
return None
|
||||
|
13
src/modules/zfs/CMakeLists.txt
Normal file
13
src/modules/zfs/CMakeLists.txt
Normal file
@@ -0,0 +1,13 @@
|
||||
# === This file is part of Calamares - <https://calamares.io> ===
|
||||
#
|
||||
# SPDX-FileCopyrightText: 2020 Adriaan de Groot <groot@kde.org>
|
||||
# SPDX-License-Identifier: BSD-2-Clause
|
||||
#
|
||||
calamares_add_plugin( zfs
|
||||
TYPE job
|
||||
EXPORT_MACRO PLUGINDLLEXPORT_PRO
|
||||
SOURCES
|
||||
ZfsJob.cpp
|
||||
SHARED_LIB
|
||||
)
|
||||
|
18
src/modules/zfs/README.md
Normal file
18
src/modules/zfs/README.md
Normal file
@@ -0,0 +1,18 @@
|
||||
## zfs Module Notes
|
||||
|
||||
<!-- SPDX-FileCopyrightText: 2021 Evan James <dalto@fastmail.com>
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
-->
|
||||
|
||||
There are a few considerations to be aware of when enabling the zfs module
|
||||
* You must provide zfs kernel modules or kernel support on the ISO for the zfs module to function
|
||||
* Support for zfs in the partition module is conditional on the zfs module being enabled
|
||||
* If you use grub with zfs, you must have `ZPOOL_VDEV_NAME_PATH=1` in your environment when running grub-install or grub-mkconfig.
|
||||
* Calamares will ensure this happens during the bootloader module.
|
||||
* It will also add it to `/etc/environment` so it will be available in the installation
|
||||
* If you have an scripts or other processes that trigger grub-mkconfig during the install process, be sure to add that to the environment
|
||||
* In most cases, you will need to enable services for zfs support appropriate to your distro. For example, when testing on Arch the following services were enabled:
|
||||
* zfs.target
|
||||
* zfs-import-cache
|
||||
* zfs-mount
|
||||
* zfs-import.target
|
365
src/modules/zfs/ZfsJob.cpp
Normal file
365
src/modules/zfs/ZfsJob.cpp
Normal file
@@ -0,0 +1,365 @@
|
||||
/* === This file is part of Calamares - <https://calamares.io> ===
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2021 Evan James <dalto@fastmail.com>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* Calamares is Free Software: see the License-Identifier above.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "ZfsJob.h"
|
||||
|
||||
#include "utils/CalamaresUtilsSystem.h"
|
||||
#include "utils/Logger.h"
|
||||
#include "utils/Variant.h"
|
||||
|
||||
#include "GlobalStorage.h"
|
||||
#include "JobQueue.h"
|
||||
#include "Settings.h"
|
||||
|
||||
#include <QProcess>
|
||||
|
||||
#include <unistd.h>
|
||||
|
||||
/** @brief Returns the alphanumeric portion of a string
|
||||
*
|
||||
* @p input is the input string
|
||||
*
|
||||
*/
|
||||
static QString
|
||||
alphaNumeric( QString input )
|
||||
{
|
||||
return input.remove( QRegExp( "[^a-zA-Z\\d\\s]" ) );
|
||||
}
|
||||
|
||||
/** @brief Returns the best available device for zpool creation
|
||||
*
|
||||
* zfs partitions generally don't have UUID until the zpool is created. Generally,
|
||||
* they are formed using either the id or the partuuid. The id isn't stored by kpmcore
|
||||
* so this function checks to see if we have a partuuid. If so, it forms a device path
|
||||
* for it. As a backup, it uses the device name i.e. /dev/sdax.
|
||||
*
|
||||
* The function returns a fully qualified path to the device or an empty string if no device
|
||||
* is found
|
||||
*
|
||||
* @p pMap is the partition map from global storage
|
||||
*
|
||||
*/
|
||||
static QString
|
||||
findBestZfsDevice( QVariantMap pMap )
|
||||
{
|
||||
// Find the best device identifier, if one isn't available, skip this partition
|
||||
QString deviceName;
|
||||
if ( pMap[ "partuuid" ].toString() != "" )
|
||||
{
|
||||
return "/dev/disk/by-partuuid/" + pMap[ "partuuid" ].toString().toLower();
|
||||
}
|
||||
else if ( pMap[ "device" ].toString() != "" )
|
||||
{
|
||||
return pMap[ "device" ].toString().toLower();
|
||||
}
|
||||
else
|
||||
{
|
||||
return QString();
|
||||
}
|
||||
}
|
||||
|
||||
/** @brief Converts the value in a QVariant to a string which is a valid option for canmount
|
||||
*
|
||||
* Storing "on" and "off" in QVariant results in a conversion to boolean. This function takes
|
||||
* the Qvariant in @p canMount and converts it to a QString holding "on", "off" or the string
|
||||
* value in the QVariant.
|
||||
*
|
||||
*/
|
||||
static QString
|
||||
convertCanMount( QVariant canMount )
|
||||
{
|
||||
if ( canMount == true )
|
||||
{
|
||||
return "on";
|
||||
}
|
||||
else if ( canMount == false )
|
||||
{
|
||||
return "off";
|
||||
}
|
||||
else
|
||||
{
|
||||
return canMount.toString();
|
||||
}
|
||||
}
|
||||
|
||||
ZfsJob::ZfsJob( QObject* parent )
|
||||
: Calamares::CppJob( parent )
|
||||
{
|
||||
}
|
||||
|
||||
ZfsJob::~ZfsJob() {}
|
||||
|
||||
QString
|
||||
ZfsJob::prettyName() const
|
||||
{
|
||||
return tr( "Create ZFS pools and datasets" );
|
||||
}
|
||||
|
||||
void
|
||||
ZfsJob::collectMountpoints( const QVariantList& partitions )
|
||||
{
|
||||
m_mountpoints.empty();
|
||||
for ( const QVariant& partition : partitions )
|
||||
{
|
||||
if ( partition.canConvert( QVariant::Map ) )
|
||||
{
|
||||
QString mountpoint = partition.toMap().value( "mountPoint" ).toString();
|
||||
if ( !mountpoint.isEmpty() )
|
||||
{
|
||||
m_mountpoints.append( mountpoint );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
ZfsJob::isMountpointOverlapping( const QString& targetMountpoint ) const
|
||||
{
|
||||
for ( const QString& mountpoint : m_mountpoints )
|
||||
{
|
||||
if ( mountpoint != '/' && targetMountpoint.startsWith( mountpoint ) )
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
ZfsResult
|
||||
ZfsJob::createZpool( QString deviceName, QString poolName, QString poolOptions, bool encrypt, QString passphrase ) const
|
||||
{
|
||||
// zfs doesn't wait for the devices so pause for 2 seconds to ensure we give time for the device files to be created
|
||||
sleep( 2 );
|
||||
|
||||
QStringList command;
|
||||
if ( encrypt )
|
||||
{
|
||||
command = QStringList() << "zpool"
|
||||
<< "create" << poolOptions.split( ' ' ) << "-O"
|
||||
<< "encryption=aes-256-gcm"
|
||||
<< "-O"
|
||||
<< "keyformat=passphrase" << poolName << deviceName;
|
||||
}
|
||||
else
|
||||
{
|
||||
command = QStringList() << "zpool"
|
||||
<< "create" << poolOptions.split( ' ' ) << poolName << deviceName;
|
||||
}
|
||||
|
||||
auto r = CalamaresUtils::System::instance()->runCommand(
|
||||
CalamaresUtils::System::RunLocation::RunInHost, command, QString(), passphrase, std::chrono::seconds( 10 ) );
|
||||
|
||||
if ( r.getExitCode() != 0 )
|
||||
{
|
||||
cWarning() << "Failed to run zpool create. The output was: " + r.getOutput();
|
||||
return { false, tr( "Failed to create zpool on " ) + deviceName };
|
||||
}
|
||||
|
||||
return { true, QString() };
|
||||
}
|
||||
|
||||
Calamares::JobResult
|
||||
ZfsJob::exec()
|
||||
{
|
||||
QVariantList partitions;
|
||||
Calamares::GlobalStorage* gs = Calamares::JobQueue::instance()->globalStorage();
|
||||
if ( gs && gs->contains( "partitions" ) && gs->value( "partitions" ).canConvert( QVariant::List ) )
|
||||
{
|
||||
partitions = gs->value( "partitions" ).toList();
|
||||
}
|
||||
else
|
||||
{
|
||||
cWarning() << "No *partitions* defined.";
|
||||
return Calamares::JobResult::internalError( tr( "Configuration Error" ),
|
||||
tr( "No partitions are available for Zfs." ),
|
||||
Calamares::JobResult::InvalidConfiguration );
|
||||
}
|
||||
|
||||
const CalamaresUtils::System* system = CalamaresUtils::System::instance();
|
||||
|
||||
QVariantList poolNames;
|
||||
|
||||
// Check to ensure the list of zfs info from the partition module is available and convert it to a list
|
||||
if ( !gs->contains( "zfsInfo" ) && gs->value( "zfsInfo" ).canConvert( QVariant::List ) )
|
||||
{
|
||||
return Calamares::JobResult::error( tr( "Internal data missing" ), tr( "Failed to create zpool" ) );
|
||||
}
|
||||
QVariantList zfsInfoList = gs->value( "zfsInfo" ).toList();
|
||||
|
||||
for ( auto& partition : qAsConst( partitions ) )
|
||||
{
|
||||
QVariantMap pMap;
|
||||
if ( partition.canConvert( QVariant::Map ) )
|
||||
{
|
||||
pMap = partition.toMap();
|
||||
}
|
||||
|
||||
// If it isn't a zfs partition, ignore it
|
||||
if ( pMap[ "fsName" ] != "zfs" )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Find the best device identifier, if one isn't available, skip this partition
|
||||
QString deviceName = findBestZfsDevice( pMap );
|
||||
if ( deviceName.isEmpty() )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// If the partition doesn't have a mountpoint, skip it
|
||||
QString mountpoint = pMap[ "mountPoint" ].toString();
|
||||
if ( mountpoint.isEmpty() )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Build a poolname off config pool name and the mountpoint, this is not ideal but should work until there is UI built for zfs
|
||||
QString poolName = m_poolName;
|
||||
if ( mountpoint != '/' )
|
||||
{
|
||||
poolName += alphaNumeric( mountpoint );
|
||||
}
|
||||
|
||||
// Look in the zfs info list to see if this partition should be encrypted
|
||||
bool encrypt = false;
|
||||
QString passphrase;
|
||||
for ( const QVariant& zfsInfo : qAsConst( zfsInfoList ) )
|
||||
{
|
||||
if ( zfsInfo.canConvert( QVariant::Map ) && zfsInfo.toMap().value( "encrypted" ).toBool()
|
||||
&& mountpoint == zfsInfo.toMap().value( "mountpoint" ) )
|
||||
{
|
||||
encrypt = true;
|
||||
passphrase = zfsInfo.toMap().value( "passphrase" ).toString();
|
||||
}
|
||||
}
|
||||
|
||||
// Create the zpool
|
||||
ZfsResult zfsResult;
|
||||
if ( encrypt )
|
||||
{
|
||||
zfsResult = createZpool( deviceName, poolName, m_poolOptions, true, passphrase );
|
||||
}
|
||||
else
|
||||
{
|
||||
zfsResult = createZpool( deviceName, poolName, m_poolOptions, false );
|
||||
}
|
||||
|
||||
if ( !zfsResult.success )
|
||||
{
|
||||
return Calamares::JobResult::error( tr( "Failed to create zpool" ), zfsResult.failureMessage );
|
||||
}
|
||||
|
||||
// Save the poolname, dataset name and mountpoint. It will later be added to a list and placed in global storage.
|
||||
// This will be used by later modules including mount and umount
|
||||
QVariantMap poolNameEntry;
|
||||
poolNameEntry[ "poolName" ] = poolName;
|
||||
poolNameEntry[ "mountpoint" ] = mountpoint;
|
||||
poolNameEntry[ "dsName" ] = "none";
|
||||
|
||||
// If the mountpoint is /, create datasets per the config file. If not, create a single dataset mounted at the partitions mountpoint
|
||||
if ( mountpoint == '/' )
|
||||
{
|
||||
collectMountpoints( partitions );
|
||||
QVariantList datasetList;
|
||||
for ( const auto& dataset : qAsConst( m_datasets ) )
|
||||
{
|
||||
QVariantMap datasetMap = dataset.toMap();
|
||||
|
||||
// Make sure all values are valid
|
||||
if ( datasetMap[ "dsName" ].toString().isEmpty() || datasetMap[ "mountpoint" ].toString().isEmpty()
|
||||
|| datasetMap[ "canMount" ].toString().isEmpty() )
|
||||
{
|
||||
cWarning() << "Bad dataset entry";
|
||||
continue;
|
||||
}
|
||||
|
||||
// We should skip this dataset if it conflicts with a permanent mountpoint
|
||||
if ( isMountpointOverlapping( datasetMap[ "mountpoint" ].toString() ) )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
QString canMount = convertCanMount( datasetMap[ "canMount" ].toString() );
|
||||
|
||||
// Create the dataset
|
||||
auto r = system->runCommand( { QStringList() << "zfs"
|
||||
<< "create" << m_datasetOptions.split( ' ' ) << "-o"
|
||||
<< "canmount=" + canMount << "-o"
|
||||
<< "mountpoint=" + datasetMap[ "mountpoint" ].toString()
|
||||
<< poolName + "/" + datasetMap[ "dsName" ].toString() },
|
||||
std::chrono::seconds( 10 ) );
|
||||
if ( r.getExitCode() != 0 )
|
||||
{
|
||||
cWarning() << "Failed to create dataset" << datasetMap[ "dsName" ].toString();
|
||||
continue;
|
||||
}
|
||||
|
||||
// Add the dataset to the list for global storage this information is used later to properly set
|
||||
// the mount options on each dataset
|
||||
datasetMap[ "zpool" ] = m_poolName;
|
||||
datasetList.append( datasetMap );
|
||||
}
|
||||
|
||||
// If the list isn't empty, add it to global storage
|
||||
if ( !datasetList.isEmpty() )
|
||||
{
|
||||
gs->insert( "zfsDatasets", datasetList );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
QString dsName = mountpoint;
|
||||
dsName = alphaNumeric( mountpoint );
|
||||
auto r = system->runCommand( { QStringList() << "zfs"
|
||||
<< "create" << m_datasetOptions.split( ' ' ) << "-o"
|
||||
<< "canmount=on"
|
||||
<< "-o"
|
||||
<< "mountpoint=" + mountpoint << poolName + "/" + dsName },
|
||||
std::chrono::seconds( 10 ) );
|
||||
if ( r.getExitCode() != 0 )
|
||||
{
|
||||
return Calamares::JobResult::error( tr( "Failed to create dataset" ),
|
||||
tr( "The output was: " ) + r.getOutput() );
|
||||
}
|
||||
poolNameEntry[ "dsName" ] = dsName;
|
||||
}
|
||||
|
||||
poolNames.append( poolNameEntry );
|
||||
|
||||
// Export the zpool so it can be reimported at the correct location later
|
||||
auto r = system->runCommand( { "zpool", "export", poolName }, std::chrono::seconds( 10 ) );
|
||||
if ( r.getExitCode() != 0 )
|
||||
{
|
||||
cWarning() << "Failed to export pool" << m_poolName;
|
||||
}
|
||||
}
|
||||
|
||||
// Put the list of zpools into global storage
|
||||
if ( !poolNames.isEmpty() )
|
||||
{
|
||||
gs->insert( "zfsPoolInfo", poolNames );
|
||||
}
|
||||
|
||||
return Calamares::JobResult::ok();
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
ZfsJob::setConfigurationMap( const QVariantMap& map )
|
||||
{
|
||||
m_poolName = CalamaresUtils::getString( map, "poolName" );
|
||||
m_poolOptions = CalamaresUtils::getString( map, "poolOptions" );
|
||||
m_datasetOptions = CalamaresUtils::getString( map, "datasetOptions" );
|
||||
|
||||
m_datasets = CalamaresUtils::getList( map, "datasets" );
|
||||
}
|
||||
|
||||
CALAMARES_PLUGIN_FACTORY_DEFINITION( ZfsJobFactory, registerPlugin< ZfsJob >(); )
|
89
src/modules/zfs/ZfsJob.h
Normal file
89
src/modules/zfs/ZfsJob.h
Normal file
@@ -0,0 +1,89 @@
|
||||
/* === This file is part of Calamares - <https://calamares.io> ===
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2021 Evan James <dalto@fastmail.com>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* Calamares is Free Software: see the License-Identifier above.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef ZFSJOB_H
|
||||
#define ZFSJOB_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QStringList>
|
||||
#include <QVariantMap>
|
||||
|
||||
#include "CppJob.h"
|
||||
|
||||
#include "utils/PluginFactory.h"
|
||||
|
||||
#include "DllMacro.h"
|
||||
|
||||
struct ZfsResult
|
||||
{
|
||||
bool success;
|
||||
QString failureMessage; // This message is displayed to the user and should be translated at the time of population
|
||||
};
|
||||
|
||||
/** @brief Create zpools and zfs datasets
|
||||
*
|
||||
*/
|
||||
class PLUGINDLLEXPORT ZfsJob : public Calamares::CppJob
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit ZfsJob( QObject* parent = nullptr );
|
||||
~ZfsJob() override;
|
||||
|
||||
QString prettyName() const override;
|
||||
|
||||
Calamares::JobResult exec() override;
|
||||
|
||||
void setConfigurationMap( const QVariantMap& configurationMap ) override;
|
||||
|
||||
private:
|
||||
QString m_poolName;
|
||||
QString m_poolOptions;
|
||||
QString m_datasetOptions;
|
||||
QStringList m_mountpoints;
|
||||
|
||||
QList< QVariant > m_datasets;
|
||||
|
||||
/** @brief Creates a zpool based on the provided arguments
|
||||
*
|
||||
* @p deviceName is a full path to the device the zpool should be created on
|
||||
* @p poolName is a string containing the name of the pool to create
|
||||
* @p poolOptions are the options to pass to zpool create
|
||||
* @p encrypt is a boolean which determines if the pool should be encrypted
|
||||
* @p passphrase is a string continaing the passphrase
|
||||
*
|
||||
*/
|
||||
ZfsResult createZpool( QString deviceName,
|
||||
QString poolName,
|
||||
QString poolOptions,
|
||||
bool encrypt,
|
||||
QString passphrase = QString() ) const;
|
||||
|
||||
/** @brief Collects all the mountpoints from the partitions
|
||||
*
|
||||
* Iterates over @p partitions to gather each mountpoint present
|
||||
* in the list of maps and populates m_mountpoints
|
||||
*
|
||||
*/
|
||||
void collectMountpoints( const QVariantList& partitions );
|
||||
|
||||
/** @brief Check to see if a given mountpoint overlaps with one of the defined moutnpoints
|
||||
*
|
||||
* Iterates over m_partitions and checks if @p targetMountpoint overlaps with them by comparing
|
||||
* the beginning of targetMountpoint with all the values in m_mountpoints. Of course, / is excluded
|
||||
* since all the mountpoints would begin with /
|
||||
*
|
||||
*/
|
||||
bool isMountpointOverlapping( const QString& targetMountpoint ) const;
|
||||
};
|
||||
|
||||
CALAMARES_PLUGIN_FACTORY_DECLARATION( ZfsJobFactory )
|
||||
|
||||
#endif // ZFSJOB_H
|
38
src/modules/zfs/zfs.conf
Normal file
38
src/modules/zfs/zfs.conf
Normal file
@@ -0,0 +1,38 @@
|
||||
# SPDX-FileCopyrightText: no
|
||||
# SPDX-License-Identifier: CC0-1.0
|
||||
#
|
||||
# The zfs module creates the zfs pools and datasets
|
||||
#
|
||||
#
|
||||
#
|
||||
---
|
||||
# The name to be used for the zpool
|
||||
poolName: zpcala
|
||||
|
||||
# A list of options that will be passed to zpool create
|
||||
poolOptions: "-f -o ashift=12 -O mountpoint=none -O acltype=posixacl -O relatime=on"
|
||||
|
||||
# A list of options that will be passed to zfs create when creating each dataset
|
||||
# Do not include "canmount" or "mountpoint" as those are set below in the datasets array
|
||||
datasetOptions: "-o compression=lz4 -o atime=off -o xattr=sa"
|
||||
|
||||
# An array of datasets that will be created on the zpool mounted at /
|
||||
datasets:
|
||||
- dsName: ROOT
|
||||
mountpoint: none
|
||||
canMount: off
|
||||
- dsName: ROOT/distro
|
||||
mountpoint: none
|
||||
canMount: off
|
||||
- dsName: ROOT/distro/root
|
||||
mountpoint: /
|
||||
canMount: noauto
|
||||
- dsName: ROOT/distro/home
|
||||
mountpoint: /home
|
||||
canMount: on
|
||||
- dsName: ROOT/distro/varcache
|
||||
mountpoint: /var/cache
|
||||
canMount: on
|
||||
- dsName: ROOT/distro/varlog
|
||||
mountpoint: /var/log
|
||||
canMount: on
|
24
src/modules/zfs/zfs.schema.yaml
Normal file
24
src/modules/zfs/zfs.schema.yaml
Normal file
@@ -0,0 +1,24 @@
|
||||
# SPDX-FileCopyrightText: 2020 Adriaan de Groot <groot@kde.org>
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
---
|
||||
$schema: https://json-schema.org/schema#
|
||||
$id: https://calamares.io/schemas/zfs
|
||||
additionalProperties: false
|
||||
type: object
|
||||
properties:
|
||||
poolName: { type: string }
|
||||
poolOptions: { type: string }
|
||||
datasetOptions: { type: string }
|
||||
datasets:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
properties:
|
||||
dsName: { type: string }
|
||||
mountpoint: { type: string }
|
||||
# Nominally a string, but "on" and "off" are valid and get
|
||||
# turned into a boolean in the YAML parser.
|
||||
canMount: { anyOf: [ { type: string }, { type: boolean } ] }
|
||||
required: [ dsName, mountpoint, canMount ]
|
||||
required: [ poolName, datasets ]
|
Reference in New Issue
Block a user