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
|
changelog -- this log starts with version 3.2.0. The release notes on the
|
||||||
website will have to do for older versions.
|
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) #
|
# 3.2.46 (2021-11-09) #
|
||||||
|
|
||||||
This release contains contributions from (alphabetically by first name):
|
This release contains contributions from (alphabetically by first name):
|
||||||
|
@@ -41,7 +41,7 @@
|
|||||||
# TODO:3.3: Require CMake 3.12
|
# TODO:3.3: Require CMake 3.12
|
||||||
cmake_minimum_required( VERSION 3.3 FATAL_ERROR )
|
cmake_minimum_required( VERSION 3.3 FATAL_ERROR )
|
||||||
project( CALAMARES
|
project( CALAMARES
|
||||||
VERSION 3.2.46
|
VERSION 3.2.47
|
||||||
LANGUAGES C CXX
|
LANGUAGES C CXX
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -133,12 +133,12 @@ set( CALAMARES_DESCRIPTION_SUMMARY
|
|||||||
# `txstats.py -e`. See also
|
# `txstats.py -e`. See also
|
||||||
#
|
#
|
||||||
# Total 81 languages
|
# Total 81 languages
|
||||||
set( _tx_complete az az_AZ ca cs_CZ fi_FI he hi hr ja ko lt pt_BR
|
set( _tx_complete az az_AZ ca de fi_FI he hr ja ko lt pt_PT si sq
|
||||||
pt_PT sq sv tr_TR uk zh_CN zh_TW )
|
tr_TR uk zh_TW )
|
||||||
set( _tx_good as be ca@valencia da de fr fur it_IT ml nl ru sk tg
|
set( _tx_good as be ca@valencia cs_CZ da fr fur hi it_IT ml nl
|
||||||
vi )
|
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
|
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
|
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 )
|
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`.
|
* Double-check the *CALAMARES_VERSION* value at the top of `CMakeLists.txt`.
|
||||||
* Set *CALAMARES_RELEASE_MODE* to `ON` in `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
|
* Commit both. This is usually done with commit-message
|
||||||
*Changes: pre-release housekeeping*.
|
*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*.
|
* Bump the version number in `CMakeLists.txt` in *CALAMARES_VERSION*.
|
||||||
* Set *CALAMARES_RELEASE_MODE* back to `OFF`.
|
* Set *CALAMARES_RELEASE_MODE* back to `OFF`.
|
||||||
* Add a placeholder entry for the next release in `CHANGES` with date
|
* Add a placeholder entry for the next release in `CHANGES-*` with date
|
||||||
text *not released yet*.
|
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
|
* Commit and push that, usually with the message
|
||||||
*Changes: post-release housekeeping*.
|
*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
|
# Related Material
|
||||||
|
|
||||||
> This section isn't directly related to any specific release,
|
> 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 relevant GitHub profile.
|
||||||
- Upload that public key to the Calamares site.
|
- 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>
|
<message>
|
||||||
<location filename="../src/modules/partition/jobs/ClearMountsJob.cpp" line="259"/>
|
<location filename="../src/modules/partition/jobs/ClearMountsJob.cpp" line="259"/>
|
||||||
<source>Successfully unmounted %1.</source>
|
<source>Successfully unmounted %1.</source>
|
||||||
<translation type="unfinished"/>
|
<translation>%1 uğurla ayrıldı.</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../src/modules/partition/jobs/ClearMountsJob.cpp" line="266"/>
|
<location filename="../src/modules/partition/jobs/ClearMountsJob.cpp" line="266"/>
|
||||||
<source>Successfully disabled swap %1.</source>
|
<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>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../src/modules/partition/jobs/ClearMountsJob.cpp" line="292"/>
|
<location filename="../src/modules/partition/jobs/ClearMountsJob.cpp" line="292"/>
|
||||||
<source>Successfully cleared swap %1.</source>
|
<source>Successfully cleared swap %1.</source>
|
||||||
<translation type="unfinished"/>
|
<translation>%1 mübadilə bölməsi uğurla təmizləndi</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../src/modules/partition/jobs/ClearMountsJob.cpp" line="306"/>
|
<location filename="../src/modules/partition/jobs/ClearMountsJob.cpp" line="306"/>
|
||||||
<source>Successfully closed mapper device %1.</source>
|
<source>Successfully closed mapper device %1.</source>
|
||||||
<translation type="unfinished"/>
|
<translation>Yerləşdirmə cihazı %1 uğurla bağlandı</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../src/modules/partition/jobs/ClearMountsJob.cpp" line="319"/>
|
<location filename="../src/modules/partition/jobs/ClearMountsJob.cpp" line="319"/>
|
||||||
<source>Successfully disabled volume group %1.</source>
|
<source>Successfully disabled volume group %1.</source>
|
||||||
<translation type="unfinished"/>
|
<translation>Tutum qrupu %1, uğurla söndürüldü</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../src/modules/partition/jobs/ClearMountsJob.cpp" line="358"/>
|
<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>
|
<message>
|
||||||
<location filename="../src/modules/partition/jobs/ClearMountsJob.cpp" line="259"/>
|
<location filename="../src/modules/partition/jobs/ClearMountsJob.cpp" line="259"/>
|
||||||
<source>Successfully unmounted %1.</source>
|
<source>Successfully unmounted %1.</source>
|
||||||
<translation type="unfinished"/>
|
<translation>%1 uğurla ayrıldı.</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../src/modules/partition/jobs/ClearMountsJob.cpp" line="266"/>
|
<location filename="../src/modules/partition/jobs/ClearMountsJob.cpp" line="266"/>
|
||||||
<source>Successfully disabled swap %1.</source>
|
<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>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../src/modules/partition/jobs/ClearMountsJob.cpp" line="292"/>
|
<location filename="../src/modules/partition/jobs/ClearMountsJob.cpp" line="292"/>
|
||||||
<source>Successfully cleared swap %1.</source>
|
<source>Successfully cleared swap %1.</source>
|
||||||
<translation type="unfinished"/>
|
<translation>%1 mübadilə bölməsi uğurla təmizləndi</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../src/modules/partition/jobs/ClearMountsJob.cpp" line="306"/>
|
<location filename="../src/modules/partition/jobs/ClearMountsJob.cpp" line="306"/>
|
||||||
<source>Successfully closed mapper device %1.</source>
|
<source>Successfully closed mapper device %1.</source>
|
||||||
<translation type="unfinished"/>
|
<translation>Yerləşdirmə cihazı %1 uğurla bağlandı</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../src/modules/partition/jobs/ClearMountsJob.cpp" line="319"/>
|
<location filename="../src/modules/partition/jobs/ClearMountsJob.cpp" line="319"/>
|
||||||
<source>Successfully disabled volume group %1.</source>
|
<source>Successfully disabled volume group %1.</source>
|
||||||
<translation type="unfinished"/>
|
<translation>Tutum qrupu %1, uğurla söndürüldü</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../src/modules/partition/jobs/ClearMountsJob.cpp" line="358"/>
|
<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>
|
<message>
|
||||||
<location filename="../src/modules/partition/jobs/ClearMountsJob.cpp" line="259"/>
|
<location filename="../src/modules/partition/jobs/ClearMountsJob.cpp" line="259"/>
|
||||||
<source>Successfully unmounted %1.</source>
|
<source>Successfully unmounted %1.</source>
|
||||||
<translation type="unfinished"/>
|
<translation>%1을(를) 성공적으로 마운트 해제했습니다.</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../src/modules/partition/jobs/ClearMountsJob.cpp" line="266"/>
|
<location filename="../src/modules/partition/jobs/ClearMountsJob.cpp" line="266"/>
|
||||||
<source>Successfully disabled swap %1.</source>
|
<source>Successfully disabled swap %1.</source>
|
||||||
<translation type="unfinished"/>
|
<translation>스왑% 1을(를) 성공적으로 비활성화했습니다.</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../src/modules/partition/jobs/ClearMountsJob.cpp" line="292"/>
|
<location filename="../src/modules/partition/jobs/ClearMountsJob.cpp" line="292"/>
|
||||||
<source>Successfully cleared swap %1.</source>
|
<source>Successfully cleared swap %1.</source>
|
||||||
<translation type="unfinished"/>
|
<translation>스왑 %1을(를) 성공적으로 지웠습니다.</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../src/modules/partition/jobs/ClearMountsJob.cpp" line="306"/>
|
<location filename="../src/modules/partition/jobs/ClearMountsJob.cpp" line="306"/>
|
||||||
<source>Successfully closed mapper device %1.</source>
|
<source>Successfully closed mapper device %1.</source>
|
||||||
<translation type="unfinished"/>
|
<translation>매퍼 장치 %1을(를) 성공적으로 닫았습니다.</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../src/modules/partition/jobs/ClearMountsJob.cpp" line="319"/>
|
<location filename="../src/modules/partition/jobs/ClearMountsJob.cpp" line="319"/>
|
||||||
<source>Successfully disabled volume group %1.</source>
|
<source>Successfully disabled volume group %1.</source>
|
||||||
<translation type="unfinished"/>
|
<translation>볼륨 그룹 %1을(를) 성공적으로 비활성화했습니다.</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../src/modules/partition/jobs/ClearMountsJob.cpp" line="358"/>
|
<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>
|
<message>
|
||||||
<location filename="../src/modules/partition/jobs/ClearMountsJob.cpp" line="259"/>
|
<location filename="../src/modules/partition/jobs/ClearMountsJob.cpp" line="259"/>
|
||||||
<source>Successfully unmounted %1.</source>
|
<source>Successfully unmounted %1.</source>
|
||||||
<translation type="unfinished"/>
|
<translation>%1 bağlantısı başarıyla kaldırıldı.</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../src/modules/partition/jobs/ClearMountsJob.cpp" line="266"/>
|
<location filename="../src/modules/partition/jobs/ClearMountsJob.cpp" line="266"/>
|
||||||
<source>Successfully disabled swap %1.</source>
|
<source>Successfully disabled swap %1.</source>
|
||||||
<translation type="unfinished"/>
|
<translation>%1 takas alanı başarıyla devre dışı bırakıldı.</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../src/modules/partition/jobs/ClearMountsJob.cpp" line="292"/>
|
<location filename="../src/modules/partition/jobs/ClearMountsJob.cpp" line="292"/>
|
||||||
<source>Successfully cleared swap %1.</source>
|
<source>Successfully cleared swap %1.</source>
|
||||||
<translation type="unfinished"/>
|
<translation>%1 takas alanı başarıyla temizlendi.</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../src/modules/partition/jobs/ClearMountsJob.cpp" line="306"/>
|
<location filename="../src/modules/partition/jobs/ClearMountsJob.cpp" line="306"/>
|
||||||
<source>Successfully closed mapper device %1.</source>
|
<source>Successfully closed mapper device %1.</source>
|
||||||
<translation type="unfinished"/>
|
<translation>%1 eşleyici aygıtı başarıyla kapatıldı.</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../src/modules/partition/jobs/ClearMountsJob.cpp" line="319"/>
|
<location filename="../src/modules/partition/jobs/ClearMountsJob.cpp" line="319"/>
|
||||||
<source>Successfully disabled volume group %1.</source>
|
<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>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../src/modules/partition/jobs/ClearMountsJob.cpp" line="358"/>
|
<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>
|
<message>
|
||||||
<location filename="../src/modules/welcome/Config.cpp" line="251"/>
|
<location filename="../src/modules/welcome/Config.cpp" line="251"/>
|
||||||
<source><h1>Welcome to %1 setup</h1></source>
|
<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>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../src/modules/welcome/Config.cpp" line="255"/>
|
<location filename="../src/modules/welcome/Config.cpp" line="255"/>
|
||||||
<source><h1>Welcome to the Calamares installer for %1</h1></source>
|
<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>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../src/modules/welcome/Config.cpp" line="256"/>
|
<location filename="../src/modules/welcome/Config.cpp" line="256"/>
|
||||||
<source><h1>Welcome to the %1 installer</h1></source>
|
<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>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../src/modules/users/Config.cpp" line="217"/>
|
<location filename="../src/modules/users/Config.cpp" line="217"/>
|
||||||
@@ -3978,7 +3978,7 @@ Output:
|
|||||||
<message>
|
<message>
|
||||||
<location filename="../src/modules/welcome/WelcomePage.cpp" line="217"/>
|
<location filename="../src/modules/welcome/WelcomePage.cpp" line="217"/>
|
||||||
<source><h1>Welcome to %1 setup.</h1></source>
|
<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>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../src/modules/welcome/WelcomePage.cpp" line="222"/>
|
<location filename="../src/modules/welcome/WelcomePage.cpp" line="222"/>
|
||||||
@@ -4016,7 +4016,7 @@ Output:
|
|||||||
<message>
|
<message>
|
||||||
<location filename="../src/modules/welcomeq/WelcomeQmlViewStep.cpp" line="40"/>
|
<location filename="../src/modules/welcomeq/WelcomeQmlViewStep.cpp" line="40"/>
|
||||||
<source>Welcome</source>
|
<source>Welcome</source>
|
||||||
<translation>Hoşgeldiniz</translation>
|
<translation>Hoş geldiniz</translation>
|
||||||
</message>
|
</message>
|
||||||
</context>
|
</context>
|
||||||
<context>
|
<context>
|
||||||
@@ -4024,7 +4024,7 @@ Output:
|
|||||||
<message>
|
<message>
|
||||||
<location filename="../src/modules/welcome/WelcomeViewStep.cpp" line="46"/>
|
<location filename="../src/modules/welcome/WelcomeViewStep.cpp" line="46"/>
|
||||||
<source>Welcome</source>
|
<source>Welcome</source>
|
||||||
<translation>Hoşgeldiniz</translation>
|
<translation>Hoş geldiniz</translation>
|
||||||
</message>
|
</message>
|
||||||
</context>
|
</context>
|
||||||
<context>
|
<context>
|
||||||
|
@@ -127,6 +127,7 @@ sequence:
|
|||||||
# - dummyprocess
|
# - dummyprocess
|
||||||
# - dummypython
|
# - dummypython
|
||||||
- partition
|
- partition
|
||||||
|
# - zfs
|
||||||
- mount
|
- mount
|
||||||
- unpackfs
|
- unpackfs
|
||||||
- machineid
|
- machineid
|
||||||
|
@@ -132,6 +132,11 @@ public:
|
|||||||
void setEmergency( bool e ) { m_emergency = e; }
|
void setEmergency( bool e ) { m_emergency = e; }
|
||||||
|
|
||||||
signals:
|
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 );
|
void progress( qreal percent );
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
@@ -20,6 +20,8 @@
|
|||||||
#include <QDir>
|
#include <QDir>
|
||||||
#include <QFileInfo>
|
#include <QFileInfo>
|
||||||
#include <QMutex>
|
#include <QMutex>
|
||||||
|
#include <QRandomGenerator>
|
||||||
|
#include <QTextStream>
|
||||||
#include <QTime>
|
#include <QTime>
|
||||||
#include <QVariant>
|
#include <QVariant>
|
||||||
|
|
||||||
@@ -229,7 +231,7 @@ toString( const QVariant& v )
|
|||||||
}
|
}
|
||||||
|
|
||||||
QDebug&
|
QDebug&
|
||||||
operator<<( QDebug& s, const Redacted& l )
|
operator<<( QDebug& s, const RedactedCommand& l )
|
||||||
{
|
{
|
||||||
// Special case logging: don't log the (encrypted) password.
|
// Special case logging: don't log the (encrypted) password.
|
||||||
if ( l.list.contains( "usermod" ) )
|
if ( l.list.contains( "usermod" ) )
|
||||||
@@ -252,4 +254,33 @@ operator<<( QDebug& s, const Redacted& l )
|
|||||||
return s;
|
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
|
} // namespace Logger
|
||||||
|
@@ -145,8 +145,8 @@ public:
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
const T& first;
|
const T first;
|
||||||
const U& second;
|
const U second;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -214,9 +214,9 @@ public:
|
|||||||
* since the log may get posted to bug reports, or stored in
|
* since the log may get posted to bug reports, or stored in
|
||||||
* the target system.
|
* the target system.
|
||||||
*/
|
*/
|
||||||
struct Redacted
|
struct RedactedCommand
|
||||||
{
|
{
|
||||||
Redacted( const QStringList& l )
|
RedactedCommand( const QStringList& l )
|
||||||
: list( l )
|
: list( l )
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
@@ -224,7 +224,30 @@ struct Redacted
|
|||||||
const QStringList& list;
|
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
|
* @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();
|
process.start();
|
||||||
if ( !process.waitForStarted() )
|
if ( !process.waitForStarted() )
|
||||||
{
|
{
|
||||||
@@ -225,13 +225,13 @@ Calamares::Utils::Runner::run()
|
|||||||
{
|
{
|
||||||
if ( !output.isEmpty() )
|
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"
|
<< "output:\n"
|
||||||
<< Logger::NoQuote << output;
|
<< Logger::NoQuote << output;
|
||||||
}
|
}
|
||||||
else
|
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)";
|
<< "(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
|
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.
|
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.
|
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 ""
|
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):
|
def create_systemd_boot_conf(install_path, efi_dir, uuid, entry, entry_name, kernel_type):
|
||||||
"""
|
"""
|
||||||
Creates systemd-boot configuration files based on given parameters.
|
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/"
|
"root=/dev/mapper/"
|
||||||
+ partition["luksMapperName"]]
|
+ partition["luksMapperName"]]
|
||||||
|
|
||||||
# systemd-boot with a BTRFS root filesystem needs to be told
|
|
||||||
# about the root subvolume.
|
|
||||||
for partition in partitions:
|
for partition in partitions:
|
||||||
if partition["mountPoint"] == "/" and partition["fs"] == "btrfs":
|
# systemd-boot with a BTRFS root filesystem needs to be told abouut the root subvolume.
|
||||||
kernel_params.append("rootflags=subvol=@")
|
# 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:
|
if cryptdevice_params:
|
||||||
kernel_params.extend(cryptdevice_params)
|
kernel_params.extend(cryptdevice_params)
|
||||||
@@ -314,6 +371,76 @@ def get_grub_efi_parameters():
|
|||||||
return None
|
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):
|
def install_grub(efi_directory, fw_type):
|
||||||
"""
|
"""
|
||||||
Installs grub as bootloader, either in pc or efi mode.
|
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 efi_directory:
|
||||||
:param fw_type:
|
: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":
|
if fw_type == "efi":
|
||||||
libcalamares.utils.debug("Bootloader: grub (efi)")
|
libcalamares.utils.debug("Bootloader: grub (efi)")
|
||||||
install_path = libcalamares.globalstorage.value("rootMountPoint")
|
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()
|
efi_target, efi_grub_file, efi_boot_file = get_grub_efi_parameters()
|
||||||
|
|
||||||
check_target_env_call([libcalamares.job.configuration["grubInstall"],
|
run_grub_install(fw_type, partitions, efi_directory)
|
||||||
"--target=" + efi_target,
|
|
||||||
"--efi-directory=" + efi_directory,
|
|
||||||
"--bootloader-id=" + efi_bootloader_id,
|
|
||||||
"--force"])
|
|
||||||
|
|
||||||
# VFAT is weird, see issue CAL-385
|
# VFAT is weird, see issue CAL-385
|
||||||
install_efi_directory_firmware = (vfat_correct_case(
|
install_efi_directory_firmware = (vfat_correct_case(
|
||||||
@@ -356,36 +485,21 @@ def install_grub(efi_directory, fw_type):
|
|||||||
os.makedirs(install_efi_boot_directory)
|
os.makedirs(install_efi_boot_directory)
|
||||||
|
|
||||||
# Workaround for some UEFI firmwares
|
# Workaround for some UEFI firmwares
|
||||||
FALLBACK = "installEFIFallback"
|
fallback = "installEFIFallback"
|
||||||
libcalamares.utils.debug("UEFI Fallback: " + str(libcalamares.job.configuration.get(FALLBACK, "<unset>")))
|
libcalamares.utils.debug("UEFI Fallback: " + str(libcalamares.job.configuration.get(fallback, "<unset>")))
|
||||||
if libcalamares.job.configuration.get(FALLBACK, True):
|
if libcalamares.job.configuration.get(fallback, True):
|
||||||
libcalamares.utils.debug(" .. installing '{!s}' fallback firmware".format(efi_boot_file))
|
libcalamares.utils.debug(" .. installing '{!s}' fallback firmware".format(efi_boot_file))
|
||||||
efi_file_source = os.path.join(install_efi_directory_firmware,
|
efi_file_source = os.path.join(install_efi_directory_firmware,
|
||||||
efi_bootloader_id,
|
efi_bootloader_id,
|
||||||
efi_grub_file)
|
efi_grub_file)
|
||||||
efi_file_target = os.path.join(install_efi_boot_directory,
|
efi_file_target = os.path.join(install_efi_boot_directory, efi_boot_file)
|
||||||
efi_boot_file)
|
|
||||||
|
|
||||||
shutil.copy2(efi_file_source, efi_file_target)
|
shutil.copy2(efi_file_source, efi_file_target)
|
||||||
else:
|
else:
|
||||||
libcalamares.utils.debug("Bootloader: grub (bios)")
|
libcalamares.utils.debug("Bootloader: grub (bios)")
|
||||||
if libcalamares.globalstorage.value("bootLoader") is None:
|
run_grub_install(fw_type, partitions)
|
||||||
return
|
|
||||||
|
|
||||||
boot_loader = libcalamares.globalstorage.value("bootLoader")
|
run_grub_mkconfig(partitions, libcalamares.job.configuration["grubCfg"])
|
||||||
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"]])
|
|
||||||
|
|
||||||
|
|
||||||
def install_secureboot(efi_directory):
|
def install_secureboot(efi_directory):
|
||||||
|
@@ -23,6 +23,7 @@ displaymanagers:
|
|||||||
- mdm
|
- mdm
|
||||||
- lxdm
|
- lxdm
|
||||||
- kdm
|
- kdm
|
||||||
|
- greetd
|
||||||
|
|
||||||
# Enable the following settings to force a desktop environment
|
# Enable the following settings to force a desktop environment
|
||||||
# in your displaymanager configuration file. This will attempt
|
# in your displaymanager configuration file. This will attempt
|
||||||
|
@@ -10,7 +10,7 @@ properties:
|
|||||||
type: array
|
type: array
|
||||||
items:
|
items:
|
||||||
type: string
|
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
|
minItems: 1 # Must be non-empty, if present at all
|
||||||
defaultDesktopEnvironment:
|
defaultDesktopEnvironment:
|
||||||
type: object
|
type: object
|
||||||
|
@@ -17,9 +17,7 @@
|
|||||||
|
|
||||||
import abc
|
import abc
|
||||||
import os
|
import os
|
||||||
import re
|
|
||||||
import libcalamares
|
import libcalamares
|
||||||
import configparser
|
|
||||||
|
|
||||||
from libcalamares.utils import gettext_path, gettext_languages
|
from libcalamares.utils import gettext_path, gettext_languages
|
||||||
|
|
||||||
@@ -796,6 +794,8 @@ class DMsddm(DisplayManager):
|
|||||||
executable = "sddm"
|
executable = "sddm"
|
||||||
|
|
||||||
def set_autologin(self, username, do_autologin, default_desktop_environment):
|
def set_autologin(self, username, do_autologin, default_desktop_environment):
|
||||||
|
import configparser
|
||||||
|
|
||||||
# Systems with Sddm as Desktop Manager
|
# Systems with Sddm as Desktop Manager
|
||||||
sddm_conf_path = os.path.join(self.root_mount_point, "etc/sddm.conf")
|
sddm_conf_path = os.path.join(self.root_mount_point, "etc/sddm.conf")
|
||||||
|
|
||||||
@@ -835,6 +835,91 @@ class DMsddm(DisplayManager):
|
|||||||
pass
|
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):
|
class DMsysconfig(DisplayManager):
|
||||||
name = "sysconfig"
|
name = "sysconfig"
|
||||||
executable = None
|
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
|
# Mount options to use for all filesystems. If a specific filesystem
|
||||||
# is listed here, use those options, otherwise use the *default*
|
# is listed here, use those options, otherwise use the *default*
|
||||||
# options from this mapping.
|
# 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:
|
mountOptions:
|
||||||
default: defaults,noatime
|
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
|
# 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,
|
# *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)
|
dct = self.generate_fstab_line_info(mount_entry)
|
||||||
if dct:
|
if dct:
|
||||||
self.print_fstab_line(dct, file=fstab_file)
|
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)
|
dct = self.generate_fstab_line_info(partition)
|
||||||
if dct:
|
if dct:
|
||||||
self.print_fstab_line(dct, file=fstab_file)
|
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)
|
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):
|
def modify_grub_default(partitions, root_mount_point, distributor):
|
||||||
"""
|
"""
|
||||||
Configures '/etc/default/grub' for hibernation and plymouth.
|
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
|
swap_outer_mappername = None
|
||||||
no_save_default = False
|
no_save_default = False
|
||||||
unencrypted_separate_boot = any(p["mountPoint"] == "/boot" and "luksMapperName" not in p for p in partitions)
|
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:
|
for partition in partitions:
|
||||||
if partition["mountPoint"] in ("/", "/boot") and partition["fs"] in ("btrfs", "f2fs"):
|
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"]
|
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:
|
if cryptdevice_params:
|
||||||
kernel_params.extend(cryptdevice_params)
|
kernel_params.extend(cryptdevice_params)
|
||||||
|
|
||||||
|
@@ -150,6 +150,7 @@ def find_initcpio_features(partitions, root_mount_point):
|
|||||||
|
|
||||||
swap_uuid = ""
|
swap_uuid = ""
|
||||||
uses_btrfs = False
|
uses_btrfs = False
|
||||||
|
uses_zfs = False
|
||||||
uses_lvm2 = False
|
uses_lvm2 = False
|
||||||
encrypt_hook = False
|
encrypt_hook = False
|
||||||
openswap_hook = False
|
openswap_hook = False
|
||||||
@@ -172,6 +173,9 @@ def find_initcpio_features(partitions, root_mount_point):
|
|||||||
if partition["fs"] == "btrfs":
|
if partition["fs"] == "btrfs":
|
||||||
uses_btrfs = True
|
uses_btrfs = True
|
||||||
|
|
||||||
|
if partition["fs"] == "zfs":
|
||||||
|
uses_zfs = True
|
||||||
|
|
||||||
if "lvm2" in partition["fs"]:
|
if "lvm2" in partition["fs"]:
|
||||||
uses_lvm2 = True
|
uses_lvm2 = True
|
||||||
|
|
||||||
@@ -198,6 +202,9 @@ def find_initcpio_features(partitions, root_mount_point):
|
|||||||
if uses_lvm2:
|
if uses_lvm2:
|
||||||
hooks.append("lvm2")
|
hooks.append("lvm2")
|
||||||
|
|
||||||
|
if uses_zfs:
|
||||||
|
hooks.append("zfs")
|
||||||
|
|
||||||
if swap_uuid != "":
|
if swap_uuid != "":
|
||||||
if encrypt_hook and openswap_hook:
|
if encrypt_hook and openswap_hook:
|
||||||
hooks.extend(["openswap"])
|
hooks.extend(["openswap"])
|
||||||
|
@@ -20,12 +20,24 @@ import os
|
|||||||
import libcalamares
|
import libcalamares
|
||||||
|
|
||||||
import gettext
|
import gettext
|
||||||
|
|
||||||
_ = gettext.translation("calamares-python",
|
_ = gettext.translation("calamares-python",
|
||||||
localedir=libcalamares.utils.gettext_path(),
|
localedir=libcalamares.utils.gettext_path(),
|
||||||
languages=libcalamares.utils.gettext_languages(),
|
languages=libcalamares.utils.gettext_languages(),
|
||||||
fallback=True).gettext
|
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():
|
def pretty_name():
|
||||||
return _("Mounting partitions.")
|
return _("Mounting partitions.")
|
||||||
|
|
||||||
@@ -47,20 +59,85 @@ def get_btrfs_subvolumes(partitions):
|
|||||||
if btrfs_subvolumes is None:
|
if btrfs_subvolumes is None:
|
||||||
libcalamares.utils.warning("No configuration for btrfsSubvolumes")
|
libcalamares.utils.warning("No configuration for btrfsSubvolumes")
|
||||||
if not btrfs_subvolumes:
|
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
|
# 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 != '/' ]
|
non_root_partition_mounts = [m for m in [p.get("mountPoint", None) for p in partitions] if
|
||||||
btrfs_subvolumes = list(filter(lambda s : s["mountPoint"] not in non_root_partition_mounts, btrfs_subvolumes))
|
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.
|
# If we have a swap **file**, give it a separate subvolume.
|
||||||
swap_choice = libcalamares.globalstorage.value( "partitionChoices" )
|
swap_choice = libcalamares.globalstorage.value("partitionChoices")
|
||||||
if swap_choice and swap_choice.get( "swap", None ) == "file":
|
if swap_choice and swap_choice.get("swap", None) == "file":
|
||||||
btrfs_subvolumes.append({'mountPoint': '/swap', 'subvolume': '/@swap'})
|
btrfs_subvolumes.append({'mountPoint': '/swap', 'subvolume': '/@swap'})
|
||||||
|
|
||||||
return btrfs_subvolumes
|
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):
|
def mount_partition(root_mount_point, partition, partitions):
|
||||||
"""
|
"""
|
||||||
Do a single mount of @p partition inside @p root_mount_point.
|
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:
|
if "luksMapperName" in partition:
|
||||||
device = os.path.join("/dev/mapper", partition["luksMapperName"])
|
device = os.path.join("/dev/mapper", partition["luksMapperName"])
|
||||||
|
|
||||||
if libcalamares.utils.mount(device,
|
if fstype == "zfs":
|
||||||
mount_point,
|
mount_zfs(root_mount_point, partition)
|
||||||
fstype,
|
else: # fstype == "zfs"
|
||||||
partition.get("options", "")) != 0:
|
if libcalamares.utils.mount(device,
|
||||||
libcalamares.utils.warning("Cannot mount {}".format(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
|
# Special handling for btrfs subvolumes. Create the subvolumes listed in mount.conf
|
||||||
if fstype == "btrfs" and partition["mountPoint"] == '/':
|
if fstype == "btrfs" and partition["mountPoint"] == '/':
|
||||||
@@ -111,9 +191,11 @@ def mount_partition(root_mount_point, partition, partitions):
|
|||||||
libcalamares.globalstorage.insert("btrfsSubvolumes", btrfs_subvolumes)
|
libcalamares.globalstorage.insert("btrfsSubvolumes", btrfs_subvolumes)
|
||||||
# Create the subvolumes that are in the completed list
|
# Create the subvolumes that are in the completed list
|
||||||
for s in btrfs_subvolumes:
|
for s in btrfs_subvolumes:
|
||||||
subprocess.check_call(['btrfs', 'subvolume', 'create',
|
subprocess.check_call(["btrfs", "subvolume", "create",
|
||||||
root_mount_point + s['subvolume']])
|
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])
|
subprocess.check_call(["umount", "-v", root_mount_point])
|
||||||
|
|
||||||
device = partition["device"]
|
device = partition["device"]
|
||||||
@@ -126,9 +208,9 @@ def mount_partition(root_mount_point, partition, partitions):
|
|||||||
mount_option = "subvol={}".format(s['subvolume'])
|
mount_option = "subvol={}".format(s['subvolume'])
|
||||||
subvolume_mountpoint = mount_point[:-1] + s['mountPoint']
|
subvolume_mountpoint = mount_point[:-1] + s['mountPoint']
|
||||||
if libcalamares.utils.mount(device,
|
if libcalamares.utils.mount(device,
|
||||||
subvolume_mountpoint,
|
subvolume_mountpoint,
|
||||||
fstype,
|
fstype,
|
||||||
",".join([mount_option, partition.get("options", "")])) != 0:
|
",".join([mount_option, partition.get("options", "")])) != 0:
|
||||||
libcalamares.utils.warning("Cannot mount {}".format(device))
|
libcalamares.utils.warning("Cannot mount {}".format(device))
|
||||||
|
|
||||||
|
|
||||||
@@ -142,7 +224,7 @@ def run():
|
|||||||
if not partitions:
|
if not partitions:
|
||||||
libcalamares.utils.warning("partitions is empty, {!s}".format(partitions))
|
libcalamares.utils.warning("partitions is empty, {!s}".format(partitions))
|
||||||
return (_("Configuration Error"),
|
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-")
|
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
|
# 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
|
# 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)
|
# 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"])
|
mountable_partitions.sort(key=lambda x: x["mountPoint"])
|
||||||
for partition in mountable_partitions:
|
try:
|
||||||
mount_partition(root_mount_point, partition, partitions)
|
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)
|
libcalamares.globalstorage.insert("rootMountPoint", root_mount_point)
|
||||||
|
|
||||||
|
@@ -257,14 +257,16 @@ PartitionCoreModule::doInit()
|
|||||||
cDebug() << Logger::SubEntry << "node\tcapacity\tname\tprettyName";
|
cDebug() << Logger::SubEntry << "node\tcapacity\tname\tprettyName";
|
||||||
for ( auto device : devices )
|
for ( auto device : devices )
|
||||||
{
|
{
|
||||||
cDebug() << Logger::SubEntry << Logger::Pointer( device );
|
|
||||||
if ( device )
|
if ( device )
|
||||||
{
|
{
|
||||||
// Gives ownership of the Device* to the DeviceInfo object
|
// Gives ownership of the Device* to the DeviceInfo object
|
||||||
auto deviceInfo = new DeviceInfo( device );
|
auto deviceInfo = new DeviceInfo( device );
|
||||||
m_deviceInfos << deviceInfo;
|
m_deviceInfos << deviceInfo;
|
||||||
cDebug() << Logger::SubEntry << device->deviceNode() << device->capacity() << device->name()
|
cDebug() << Logger::SubEntry
|
||||||
<< device->prettyName();
|
<< device->deviceNode()
|
||||||
|
<< device->capacity()
|
||||||
|
<< Logger::RedactedName( "DevName", device->name() )
|
||||||
|
<< Logger::RedactedName( "DevNamePretty", device->prettyName() );
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -707,10 +709,10 @@ PartitionCoreModule::dumpQueue() const
|
|||||||
cDebug() << "# Queue:";
|
cDebug() << "# Queue:";
|
||||||
for ( auto info : m_deviceInfos )
|
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() )
|
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;
|
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,
|
part = KPMHelpers::createNewPartition( parent,
|
||||||
*dev,
|
*dev,
|
||||||
@@ -319,6 +321,24 @@ PartitionLayout::createPartitions( Device* dev,
|
|||||||
luksPassphrase,
|
luksPassphrase,
|
||||||
KPM_PARTITION_FLAG( None ) );
|
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::setFormat( part, true );
|
||||||
PartitionInfo::setMountPoint( part, entry.partMountPoint );
|
PartitionInfo::setMountPoint( part, entry.partMountPoint );
|
||||||
if ( !entry.partLabel.isEmpty() )
|
if ( !entry.partLabel.isEmpty() )
|
||||||
|
@@ -23,6 +23,7 @@
|
|||||||
|
|
||||||
#include "GlobalStorage.h"
|
#include "GlobalStorage.h"
|
||||||
#include "JobQueue.h"
|
#include "JobQueue.h"
|
||||||
|
#include "Settings.h"
|
||||||
#include "partition/FileSystem.h"
|
#include "partition/FileSystem.h"
|
||||||
#include "partition/PartitionQuery.h"
|
#include "partition/PartitionQuery.h"
|
||||||
#include "utils/Logger.h"
|
#include "utils/Logger.h"
|
||||||
@@ -104,7 +105,9 @@ CreatePartitionDialog::CreatePartitionDialog( Device* device,
|
|||||||
QStringList fsNames;
|
QStringList fsNames;
|
||||||
for ( auto fs : FileSystemFactory::map() )
|
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
|
fsNames << userVisibleFS( fs ); // This is put into the combobox
|
||||||
if ( fs->type() == defaultFSType )
|
if ( fs->type() == defaultFSType )
|
||||||
@@ -240,7 +243,8 @@ CreatePartitionDialog::getNewlyCreatedPartition()
|
|||||||
// does so, to set up the partition for create-and-then-set-flags.
|
// does so, to set up the partition for create-and-then-set-flags.
|
||||||
Partition* partition = nullptr;
|
Partition* partition = nullptr;
|
||||||
QString luksPassphrase = m_ui->encryptWidget->passphrase();
|
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(
|
partition = KPMHelpers::createNewEncryptedPartition(
|
||||||
m_parent, *m_device, m_role, fsType, fsLabel, first, last, luksPassphrase, PartitionTable::Flags() );
|
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() );
|
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 )
|
if ( m_device->type() == Device::Type::LVM_Device )
|
||||||
{
|
{
|
||||||
partition->setPartitionPath( m_device->deviceNode() + QStringLiteral( "/" )
|
partition->setPartitionPath( m_device->deviceNode() + QStringLiteral( "/" )
|
||||||
|
@@ -25,6 +25,7 @@
|
|||||||
|
|
||||||
#include "GlobalStorage.h"
|
#include "GlobalStorage.h"
|
||||||
#include "JobQueue.h"
|
#include "JobQueue.h"
|
||||||
|
#include "Settings.h"
|
||||||
#include "partition/FileSystem.h"
|
#include "partition/FileSystem.h"
|
||||||
#include "utils/Logger.h"
|
#include "utils/Logger.h"
|
||||||
|
|
||||||
@@ -89,7 +90,9 @@ EditExistingPartitionDialog::EditExistingPartitionDialog( Device* device,
|
|||||||
QStringList fsNames;
|
QStringList fsNames;
|
||||||
for ( auto fs : FileSystemFactory::map() )
|
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
|
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->fileSystemLabel->setEnabled( m_ui->formatRadioButton->isChecked() );
|
||||||
m_ui->fileSystemComboBox->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 ) );
|
setFlagList( *( m_ui->m_listFlags ), m_partition->availableFlags(), PartitionInfo::flags( m_partition ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -11,8 +11,10 @@
|
|||||||
|
|
||||||
#include "CreatePartitionJob.h"
|
#include "CreatePartitionJob.h"
|
||||||
|
|
||||||
|
#include "core/PartitionInfo.h"
|
||||||
#include "partition/FileSystem.h"
|
#include "partition/FileSystem.h"
|
||||||
#include "partition/PartitionQuery.h"
|
#include "partition/PartitionQuery.h"
|
||||||
|
#include "utils/CalamaresUtilsSystem.h"
|
||||||
#include "utils/Logger.h"
|
#include "utils/Logger.h"
|
||||||
#include "utils/Units.h"
|
#include "utils/Units.h"
|
||||||
|
|
||||||
@@ -24,9 +26,79 @@
|
|||||||
#include <kpmcore/ops/newoperation.h>
|
#include <kpmcore/ops/newoperation.h>
|
||||||
#include <kpmcore/util/report.h>
|
#include <kpmcore/util/report.h>
|
||||||
|
|
||||||
|
#include <qcoreapplication.h>
|
||||||
|
#include <qregularexpression.h>
|
||||||
|
|
||||||
using CalamaresUtils::Partition::untranslatedFS;
|
using CalamaresUtils::Partition::untranslatedFS;
|
||||||
using CalamaresUtils::Partition::userVisibleFS;
|
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 )
|
CreatePartitionJob::CreatePartitionJob( Device* device, Partition* partition )
|
||||||
: PartitionJob( partition )
|
: PartitionJob( partition )
|
||||||
, m_device( device )
|
, m_device( device )
|
||||||
@@ -194,6 +266,13 @@ CreatePartitionJob::prettyStatusMessage() const
|
|||||||
Calamares::JobResult
|
Calamares::JobResult
|
||||||
CreatePartitionJob::exec()
|
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 );
|
Report report( nullptr );
|
||||||
NewOperation op( *m_device, m_partition );
|
NewOperation op( *m_device, m_partition );
|
||||||
op.setStatus( Operation::StatusRunning );
|
op.setStatus( Operation::StatusRunning );
|
||||||
|
@@ -104,14 +104,19 @@ mapForPartition( Partition* partition, const QString& uuid )
|
|||||||
// Debugging for inside the loop in createPartitionList(),
|
// Debugging for inside the loop in createPartitionList(),
|
||||||
// so indent a bit
|
// so indent a bit
|
||||||
Logger::CDebug deb;
|
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()
|
deb << Logger::SubEntry << "mapping for" << partition->partitionPath() << partition->deviceNode()
|
||||||
<< TR( "partlabel", map[ "partlabel" ].toString() ) << TR( "partuuid", map[ "partuuid" ].toString() )
|
<< TR( "partlabel", map[ "partlabel" ].toString() )
|
||||||
<< TR( "parttype", map[ "parttype" ].toString() ) << TR( "partattrs", map[ "partattrs" ].toString() )
|
<< TR( "partition-uuid (partuuid)", Logger::RedactedName( "PartUUID", map[ "partuuid" ].toString() ) )
|
||||||
<< TR( "mountPoint:", PartitionInfo::mountPoint( partition ) ) << TR( "fs:", map[ "fs" ].toString() )
|
<< TR( "parttype", map[ "parttype" ].toString() )
|
||||||
<< TR( "fsName", map[ "fsName" ].toString() ) << TR( "uuid", uuid )
|
<< 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() );
|
<< TR( "claimed", map[ "claimed" ].toString() );
|
||||||
|
// clang-format on
|
||||||
if ( partition->roles().has( PartitionRole::Luks ) )
|
if ( partition->roles().has( PartitionRole::Luks ) )
|
||||||
{
|
{
|
||||||
const FileSystem& fsRef = partition->fileSystem();
|
const FileSystem& fsRef = partition->fileSystem();
|
||||||
|
@@ -49,6 +49,27 @@ def list_mounts(root_mount_point):
|
|||||||
return lst
|
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():
|
def run():
|
||||||
""" Unmounts given mountpoints in decreasing order.
|
""" Unmounts given mountpoints in decreasing order.
|
||||||
|
|
||||||
@@ -94,6 +115,8 @@ def run():
|
|||||||
# in the exception object.
|
# in the exception object.
|
||||||
subprocess.check_output(["umount", "-lv", mount_point], stderr=subprocess.STDOUT)
|
subprocess.check_output(["umount", "-lv", mount_point], stderr=subprocess.STDOUT)
|
||||||
|
|
||||||
|
export_zpools(root_mount_point)
|
||||||
|
|
||||||
os.rmdir(root_mount_point)
|
os.rmdir(root_mount_point)
|
||||||
|
|
||||||
return None
|
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