Compare commits

..

103 Commits

Author SHA1 Message Date
Adriaan de Groot
8ca6a7caef [displaymanager] Fix tests (don't overwrite developer host configuration) 2021-11-16 17:30:28 +01:00
Adriaan de Groot
b0d951d7e5 [grubcfg] Avoid UnboundLocal, always set zfs_root_path to something 2021-11-16 17:22:43 +01:00
Adriaan de Groot
8233be93cf CI: fix permissions on scripts 2021-11-16 17:15:37 +01:00
Adriaan de Groot
8652fc5f6d [zfs] Fix schema
- typo (canmount vs canMount)
- the canMount property is nominally a string, but YAML is 'special'
  and interprets 'on' and 'off' and 'yes' and 'no' and other strings
  as booleans unless quoted.
2021-11-16 17:14:12 +01:00
Adriaan de Groot
6792eb5191 Changes: pre-release housekeeping 2021-11-16 15:45:44 +01:00
Adriaan de Groot
b00177bd65 [zfs] SPDX tag the documentation 2021-11-19 12:55:23 +01:00
Adriaan de Groot
bcd8ebd614 [displaymanager] SPDX tags for tests 2021-11-19 12:53:42 +01:00
Adriaan de Groot
46c59be541 Changes: document new things 2021-11-19 11:34:35 +01:00
Adriaan de Groot
2f2271aad6 [mount][bootloader] Communicate btrfs root subvolume
Ensure root subvolume is set correctly for systemd-boot.

FIXES #1821
2021-11-19 11:20:09 +01:00
Adriaan de Groot
feba83acdd Merge pull request #1828 from dalto8/remove-setstate
Remove setState call to resolve compat issue with older kpmcore
2021-11-18 23:40:40 +01:00
dalto
f5b882a075 [partition] Remove setState call to resolve compat issue with older kpmcore 2021-11-18 13:36:23 -06:00
dalto
b0f6530a58 [mount][bootloader] Ensure root subvolume is set correctly for systemd-boot 2021-11-18 10:04:49 -06:00
Adriaan de Groot
ece1e338e0 Merge pull request #1822 from dalto8/zfs-wip
[zfs] Support for installing to root-on-ZFS
2021-11-17 12:20:34 +01:00
dalto8
e814920bca [zfs] Fix typo in README 2021-11-17 00:17:59 +00:00
dalto
c70e31a919 [zfs] Add README.md with some implementation notes 2021-11-16 18:16:32 -06:00
dalto
7e17106f34 [bootloader] Cleanup zfs support from testing 2021-11-16 17:48:49 -06:00
dalto
9603cbef14 [grubcfg] Add zfs entry to kernel_params 2021-11-16 17:48:02 -06:00
dalto
87cca4053f [zfs][mount] Refactor zfs dataset mounting logic 2021-11-16 13:59:24 -06:00
dalto
b65321d80b [bootloader] Add zfs support for grub-install 2021-11-16 13:48:34 -06:00
dalto
18307d9f57 Add zfs module to settings.conf 2021-11-16 09:34:50 -06:00
dalto
3ee388526d [zfs] Cleanup code based on review feedback 2021-11-16 09:06:42 -06:00
Adriaan de Groot
13cb84aa75 Merge branch 'issue-1593' into calamares
FIXES #1593
2021-11-16 15:37:41 +01:00
Adriaan de Groot
efe84bc6c0 [partition] Don't log private names
- log device node (/dev/sdb) instead of its name
- don't log job's prettyName() because that's translated, and also
  contains user-visible private names (introducing a non-translated,
  nicely redacted version of prettyName() seems like too much effort
  for something that can be reconstructed from bits earlier in the log)
2021-11-16 15:31:35 +01:00
Adriaan de Groot
5a4e2b73ab [libcalamares][partition] Give RedactedName a convert-to-QString
- use hex-trailer
- while here, convert DebugRow to use a copy rather than a reference,
  to avoid dangling references when applied to temporaries
- convert *partition* module to use the RedactedNames
2021-11-16 15:22:04 +01:00
Adriaan de Groot
152b3c333b [libcalamares] Introduce redaction-of-names class for logging
- redacted names are stable inside of one run of Calamares
- random, private displays of a given string for a context

SEE #1593
2021-11-16 14:47:13 +01:00
Adriaan de Groot
7b3c4db8f0 [libcalamares] Redacted -> RedactedCommand
- For logging (shell) commands where a password might become visible, use
  RedactedCommand. Rename it to allow for other kinds of redaction, too.
2021-11-16 14:21:46 +01:00
Adriaan de Groot
7cc84b89be [partition] Clarify the meaning of the various UUIDs in debug-output 2021-11-16 14:15:00 +01:00
Adriaan de Groot
4db4e983e3 [partition] Don't format tables of attributes in source 2021-11-16 14:04:00 +01:00
Adriaan de Groot
3aac4dea67 [partition] Remove logging-of-a-pointer during device detection 2021-11-16 13:52:10 +01:00
dalto
0a7262148e [umount] Convert zfs export call to use host_env_process_output 2021-11-15 19:03:20 -06:00
dalto
ec8bab4013 Merge branch 'zfs-wip' of github.com:dalto8/calamares into zfs-wip 2021-11-15 18:42:49 -06:00
dalto
4778d9b2dd [mount] zfs changes from review feedback 2021-11-15 18:41:35 -06:00
dalto
3a90382699 [partition] zfs changes from review feedback 2021-11-15 18:41:35 -06:00
dalto
3ebe695a23 [fstab] Exclude zfs partitions from fstab 2021-11-15 18:41:35 -06:00
dalto
ebae698a6e [mount] Move zfs code into a seperate function to improve readability 2021-11-15 18:41:35 -06:00
dalto
18ad188ef6 [zfs] Ensure overlapping datasets don't get created and code cleanup 2021-11-15 18:41:35 -06:00
dalto
6e440bf9bb [umount] Export zpools after unmounting 2021-11-15 18:41:35 -06:00
dalto
490ac8d086 [partition] Ensure format is selected for existing zfs partitions 2021-11-15 18:41:35 -06:00
dalto
c48c91a5bd [partition] Add support for zfs encryption when erase disk is selected 2021-11-15 18:41:35 -06:00
dalto
ee99ee48f6 Add support for multiple zpools 2021-11-15 18:41:35 -06:00
dalto
a5b21b2500 [zfs] Fix typo and add missing continue 2021-11-15 18:41:34 -06:00
dalto
75c947c5a3 [mount] Fix zfs code and add support for encryption 2021-11-15 18:41:34 -06:00
dalto
1ccabf1b13 [zfs] Export zpool so it can later be mounted at the correct location 2021-11-15 18:41:34 -06:00
dalto
6da9bad272 [partition][zfs] Add support for zfs encryption 2021-11-15 18:41:34 -06:00
dalto
074941e2bd [bootloader] Add initial support for zfs 2021-11-15 18:41:34 -06:00
dalto
5d71723aec [mount] Improve error handling for zfs 2021-11-15 18:41:34 -06:00
dalto
de0bbbe90a [mount] Add support for zfs datasets 2021-11-15 18:41:34 -06:00
dalto
7f05096611 [zfs] Add delay before creating the zpool 2021-11-15 18:41:34 -06:00
dalto
7635b76352 [zfs] Add datasets to global storage for other modules 2021-11-15 18:41:34 -06:00
dalto
b9559a9d82 [zfs] Update to Calamares coding standards 2021-11-15 18:41:34 -06:00
dalto
76892136cf [initcpiocfg] Add support for zfs 2021-11-15 18:41:34 -06:00
dalto
11bf84bac7 [zfs] Initial commit for zfs module 2021-11-15 18:41:34 -06:00
dalto
24a376493b [partition] Add support for manually creating a partition for zfs 2021-11-15 18:41:34 -06:00
dalto
ca3f0e2892 [partition] Add zfs to the filesystem list if the zfs modules is enabled 2021-11-15 18:41:34 -06:00
dalto
e861d8b319 [mount] zfs changes from review feedback 2021-11-15 18:00:04 -06:00
dalto
abb6f73725 [partition] zfs changes from review feedback 2021-11-15 17:59:33 -06:00
Adriaan de Groot
3dd02edc78 [libcalamares] Document how to interpret percents
- use 0..1 in floats for percentages (I suppose you could
  call that a perunage, but that would be weird).
2021-11-15 23:16:58 +01:00
Adriaan de Groot
d5bef9efb5 Python: document which exception is thrown on process failure 2021-11-15 21:48:17 +01:00
Adriaan de Groot
5f7b221e11 [displaymanager] Fix greetd commands
- since default_desktop_environment isn't a string, need
  to pick the string -- the command -- out of the object first.
2021-11-15 13:38:46 +01:00
Adriaan de Groot
16a029abd2 [displaymanager] Adjust tests to match real runtime
- the default_desktop_environment isn't a string, but an
  object; it is unusual for it to be used in set_autologin
2021-11-15 13:37:23 +01:00
Adriaan de Groot
f3e85efd41 [displaymanager] Add tests that run parts of the DM code
- load and set autologin for greetd (this was used to shake out
  code bugs in load/save)
- load and set autologin for sddm
2021-11-15 13:00:40 +01:00
Adriaan de Groot
2c186132cd [displaymanager] Add support for greetd
- Includes post-PR code-fixes

CLOSES #1814
2021-11-15 12:30:19 +01:00
Adriaan de Groot
ce6aec158a [displaymanager] Fix config loading-and-saving
- toml.dump() takes a file-like object
 - toml.loads() takes a whole string to parse, (e.g. the TOML data),
   not a pathname, so change to toml.load() which takes a file-like
   object.
2021-11-15 12:23:17 +01:00
Adriaan de Groot
54fd81a87e [displaymanager] Handle case where config file doesn't exist or has no key
- If the config file doesn't exist, the dictionary is empty
 - If it **does** exist, it might not have key 'default_session' in it

Either case should avoid a KeyError by using get() (or setdefault,
in this context). Subsequent use of os.path.exists() is strange,
since the value is a **group** (e.g. a dictionary) in the config
file. Just check if it exists, and then fill something in.
2021-11-15 12:21:27 +01:00
Adriaan de Groot
11424195ef [displaymanager] Missing method call
- Add `()` to call the config_path() method, because we need a path
  to pass to os.path.exists().
2021-11-15 12:20:54 +01:00
Adriaan de Groot
fad2f6ea88 [displaymanager] Add simple test 2021-11-15 11:52:24 +01:00
Adriaan de Groot
58cf9ffeeb [displaymanager] Import toml only for the DMs that actually need it 2021-11-15 11:43:47 +01:00
Adriaan de Groot
85f36c77b1 [displaymanager] Import configparser only for the DMs that actually need it 2021-11-15 11:42:25 +01:00
Adriaan de Groot
138db1c817 Merge branch 'feat/greetd-support' of git://github.com/boredland/calamares into boredland-feat/greetd-support 2021-11-15 11:40:18 +01:00
Adriaan de Groot
126838fe1d Changes: post-release housekeeping 2021-11-15 11:27:20 +01:00
Adriaan de Groot
f0958535df CI: Update release instructions 2021-11-15 11:14:31 +01:00
Adriaan de Groot
d7865a5bcd Merge pull request #1824 from dalto8/spacecache
[fstab] Remove space_cache from btrfs mount options
2021-11-15 11:09:05 +01:00
Adriaan de Groot
dd2e14853c i18n: Update language lists 2021-11-15 11:03:41 +01:00
Calamares CI
b0436bf050 i18n: [calamares] Automatic merge of Transifex translations 2021-11-15 10:58:51 +01:00
dalto
b6692341e7 [fstab] Exclude zfs partitions from fstab 2021-11-14 09:07:58 -06:00
dalto
af4b87a4cc [mount] Move zfs code into a seperate function to improve readability 2021-11-13 14:09:16 -06:00
dalto
c3524c07ad [zfs] Ensure overlapping datasets don't get created and code cleanup 2021-11-13 13:43:26 -06:00
dalto
cca38695ed [umount] Export zpools after unmounting 2021-11-13 11:13:39 -06:00
dalto
cf20d6495b [partition] Ensure format is selected for existing zfs partitions 2021-11-13 10:43:07 -06:00
dalto
8bdfcac0fb [partition] Add support for zfs encryption when erase disk is selected 2021-11-13 09:31:23 -06:00
dalto
4bed079ebf Add support for multiple zpools 2021-11-12 16:06:06 -06:00
dalto
daa5731acf [fstab] Improve comment about space_cache 2021-11-12 09:29:04 -06:00
dalto
9ef520f862 Add comment describing the situation with space_cache on btrfs 2021-11-12 08:58:43 -06:00
dalto
0bef2a91a1 [fstab] Remove space_cache from btrfs mount options 2021-11-10 17:16:09 -06:00
Adriaan de Groot
4fb8993a38 [finishedq] Add sample QML for mobile usage
This has a countdown-timer that automatically restarts;
the rest of the settings follow the finishedq.conf values.

FIXES #1601
2021-11-09 23:08:40 +01:00
dalto
91762e3df4 [zfs] Fix typo and add missing continue 2021-11-09 14:54:46 -06:00
dalto
90452147a3 [mount] Fix zfs code and add support for encryption 2021-11-09 14:53:44 -06:00
dalto
06b6263c24 [zfs] Export zpool so it can later be mounted at the correct location 2021-11-09 07:42:39 -06:00
dalto
2f145fcf44 [partition][zfs] Add support for zfs encryption 2021-11-08 17:26:08 -06:00
dalto
0720d56803 [bootloader] Add initial support for zfs 2021-11-07 09:32:52 -06:00
dalto
85a2160098 [mount] Improve error handling for zfs 2021-11-07 08:01:32 -06:00
dalto
858e271c8a [mount] Add support for zfs datasets 2021-11-06 14:33:43 -05:00
dalto
e3af4f3e26 [zfs] Add delay before creating the zpool 2021-11-06 14:12:40 -05:00
dalto
51a5c4de0f [zfs] Add datasets to global storage for other modules 2021-11-06 13:27:03 -05:00
dalto
7108d4a509 [zfs] Update to Calamares coding standards 2021-11-06 10:30:49 -05:00
dalto
69ef13ef0c [initcpiocfg] Add support for zfs 2021-11-06 09:48:38 -05:00
dalto
e24d14c512 [zfs] Initial commit for zfs module 2021-11-06 09:44:27 -05:00
dalto
7faf4f30df [partition] Add support for manually creating a partition for zfs 2021-11-06 09:42:07 -05:00
dalto
ac44aab74a [partition] Add zfs to the filesystem list if the zfs modules is enabled 2021-11-06 09:16:09 -05:00
dalto
d0100afe02 Merge branch 'calamares' of github.com:dalto8/calamares into calamares 2021-10-30 14:09:17 -05:00
Jonas Strassel
5701937883 fix(greetd): deal with no existing config 2021-10-29 00:02:16 +02:00
Jonas Strassel
fbdb9e6779 feat(greetd): add more greeter fallbacks 2021-10-28 08:14:04 +02:00
Jonas Strassel
e5f5ef0d17 feat(greetd): add greetd to displaymanagers 2021-10-28 08:12:45 +02:00
44 changed files with 1996 additions and 621 deletions

View File

@@ -7,6 +7,44 @@ contributors are listed. Note that Calamares does not have a historical
changelog -- this log starts with version 3.2.0. The release notes on the
website will have to do for older versions.
# 3.2.47 (2021-11-19) #
This release contains contributions from (alphabetically by first name):
- Evan James
- Jonas Strassel
## Core ##
- The translation for Sinhala (`si`) has reached 100%. Thank you to
හෙළබස and Sandaruwan, translators for Sinhala, for special effort
in completing that translation.
- Logging now supports Redacted names. This reduces the scope for
leaking names or other private information through the logs
(if they are posted to a pastebin). A name is redacted consistently
within one run of Calamares, but differently each time.
## Modules ##
- *bootloader* with systemd-boot now handles root subvolumes better
(Thanks Evan)
- *displaymanager* supports the *greetd* display manager, which is a
kind of meta-DM itself, supporting multiple greeters. (Thanks Jonas)
- *finishedq* now has an extra example QML file that builds the UI in
a different fashion, demonstrating how a mobile-OS customization of
Calamares would present the "all done" message.
- *fstab* has an example configuration file that mentioned `space_cache`
as an option. Since 2014 there was only one possible value, so this
option matched the default-and-only value. Newer kernels with newer
btrfs versions have a `v2` option value as well. Remove the example
option, since the kernel automatically picks the right value, while
setting it to the wrong one may prevent the system from booting.
(Thanks Evan)
- The *partition* module no longer logs recognizable disk names or
UUIDs. These are redacted in the logs. #1593
- The *partition* module, together with the new *zfs* module and changes
in *mount* and *bootloader* can install to ZFS **if** the distribution
kernel supports it. ZFS tools are required, as well as the relevant
kernel modules. See the `README.md` in the *zfs* module. (Thanks Evan)
# 3.2.46 (2021-11-09) #
This release contains contributions from (alphabetically by first name):

View File

@@ -41,7 +41,7 @@
# TODO:3.3: Require CMake 3.12
cmake_minimum_required( VERSION 3.3 FATAL_ERROR )
project( CALAMARES
VERSION 3.2.46
VERSION 3.2.47
LANGUAGES C CXX
)
@@ -133,12 +133,12 @@ set( CALAMARES_DESCRIPTION_SUMMARY
# `txstats.py -e`. See also
#
# Total 81 languages
set( _tx_complete az az_AZ ca cs_CZ fi_FI he hi hr ja ko lt pt_BR
pt_PT sq sv tr_TR uk zh_CN zh_TW )
set( _tx_good as be ca@valencia da de fr fur it_IT ml nl ru sk tg
vi )
set( _tx_complete az az_AZ ca de fi_FI he hr ja ko lt pt_PT si sq
tr_TR uk zh_TW )
set( _tx_good as be ca@valencia cs_CZ da fr fur hi it_IT ml nl
pt_BR ru sk sv tg vi zh_CN )
set( _tx_ok ar ast bg bn el en_GB es es_MX et eu fa gl hu id is mr
nb pl ro si sl sr sr@latin th )
nb pl ro sl sr sr@latin th )
set( _tx_incomplete en_HK en_IN eo es_PE es_PR fr_CH gu hi_IN id_ID
ie kk kn ko_KR lo lv mk ne ne_NP te te_IN ur zh zh_HK )

View File

@@ -29,7 +29,8 @@
* Double-check the *CALAMARES_VERSION* value at the top of `CMakeLists.txt`.
* Set *CALAMARES_RELEASE_MODE* to `ON` in `CMakeLists.txt`.
* Edit `CHANGES` and set the date of the release.
* Edit `CHANGES-*` and set the date of the release. Pick the right
file for the release-stream.
* Commit both. This is usually done with commit-message
*Changes: pre-release housekeeping*.
@@ -81,24 +82,12 @@ Follow the instructions printed by the release script.
* Bump the version number in `CMakeLists.txt` in *CALAMARES_VERSION*.
* Set *CALAMARES_RELEASE_MODE* back to `OFF`.
* Add a placeholder entry for the next release in `CHANGES` with date
text *not released yet*.
* Add a placeholder entry for the next release in `CHANGES-*` with date
text *not released yet*. See the text below, "Placeholder Release".
Add the placeholder to the right file for the release-stream.
* Commit and push that, usually with the message
*Changes: post-release housekeeping*.
```
# 3.2.XX (unreleased) #
This release contains contributions from (alphabetically by first name):
- No external contributors yet
## Core ##
- No core changes yet
## Modules ##
- No module changes yet
```
# Related Material
> This section isn't directly related to any specific release,
@@ -139,3 +128,18 @@ ssb rsa3072/0xCFDDC96F12B1915C
- Upload that public key to the relevant GitHub profile.
- Upload that public key to the Calamares site.
## Placeholder Release Notes
```
# 3.2.XX (unreleased) #
This release contains contributions from (alphabetically by first name):
- No external contributors yet
## Core ##
- No core changes yet
## Modules ##
- No module changes yet
```

0
ci/configvalidator.py Normal file → Executable file
View File

0
ci/txcheck.sh Normal file → Executable file
View File

0
ci/txreduce.py Normal file → Executable file
View File

View File

@@ -689,27 +689,27 @@ Bu proqramdan çıxılacaq və bütün dəyişikliklər itiriləcəkdir.</transl
<message>
<location filename="../src/modules/partition/jobs/ClearMountsJob.cpp" line="259"/>
<source>Successfully unmounted %1.</source>
<translation type="unfinished"/>
<translation>%1 uğurla ayrıldı.</translation>
</message>
<message>
<location filename="../src/modules/partition/jobs/ClearMountsJob.cpp" line="266"/>
<source>Successfully disabled swap %1.</source>
<translation type="unfinished"/>
<translation>%1 mübadilə bölməsi uğurla söndürüldü.</translation>
</message>
<message>
<location filename="../src/modules/partition/jobs/ClearMountsJob.cpp" line="292"/>
<source>Successfully cleared swap %1.</source>
<translation type="unfinished"/>
<translation>%1 mübadilə bölməsi uğurla təmizləndi</translation>
</message>
<message>
<location filename="../src/modules/partition/jobs/ClearMountsJob.cpp" line="306"/>
<source>Successfully closed mapper device %1.</source>
<translation type="unfinished"/>
<translation>Yerləşdirmə cihazı %1 uğurla bağlandı</translation>
</message>
<message>
<location filename="../src/modules/partition/jobs/ClearMountsJob.cpp" line="319"/>
<source>Successfully disabled volume group %1.</source>
<translation type="unfinished"/>
<translation>Tutum qrupu %1, uğurla söndürüldü</translation>
</message>
<message>
<location filename="../src/modules/partition/jobs/ClearMountsJob.cpp" line="358"/>

View File

@@ -689,27 +689,27 @@ Bu proqramdan çıxılacaq və bütün dəyişikliklər itiriləcəkdir.</transl
<message>
<location filename="../src/modules/partition/jobs/ClearMountsJob.cpp" line="259"/>
<source>Successfully unmounted %1.</source>
<translation type="unfinished"/>
<translation>%1 uğurla ayrıldı.</translation>
</message>
<message>
<location filename="../src/modules/partition/jobs/ClearMountsJob.cpp" line="266"/>
<source>Successfully disabled swap %1.</source>
<translation type="unfinished"/>
<translation>%1 mübadilə bölməsi uğurla söndürüldü.</translation>
</message>
<message>
<location filename="../src/modules/partition/jobs/ClearMountsJob.cpp" line="292"/>
<source>Successfully cleared swap %1.</source>
<translation type="unfinished"/>
<translation>%1 mübadilə bölməsi uğurla təmizləndi</translation>
</message>
<message>
<location filename="../src/modules/partition/jobs/ClearMountsJob.cpp" line="306"/>
<source>Successfully closed mapper device %1.</source>
<translation type="unfinished"/>
<translation>Yerləşdirmə cihazı %1 uğurla bağlandı</translation>
</message>
<message>
<location filename="../src/modules/partition/jobs/ClearMountsJob.cpp" line="319"/>
<source>Successfully disabled volume group %1.</source>
<translation type="unfinished"/>
<translation>Tutum qrupu %1, uğurla söndürüldü</translation>
</message>
<message>
<location filename="../src/modules/partition/jobs/ClearMountsJob.cpp" line="358"/>

View File

@@ -687,27 +687,27 @@ The installer will quit and all changes will be lost.</source>
<message>
<location filename="../src/modules/partition/jobs/ClearMountsJob.cpp" line="259"/>
<source>Successfully unmounted %1.</source>
<translation type="unfinished"/>
<translation>%1() .</translation>
</message>
<message>
<location filename="../src/modules/partition/jobs/ClearMountsJob.cpp" line="266"/>
<source>Successfully disabled swap %1.</source>
<translation type="unfinished"/>
<translation>% 1() .</translation>
</message>
<message>
<location filename="../src/modules/partition/jobs/ClearMountsJob.cpp" line="292"/>
<source>Successfully cleared swap %1.</source>
<translation type="unfinished"/>
<translation> %1() .</translation>
</message>
<message>
<location filename="../src/modules/partition/jobs/ClearMountsJob.cpp" line="306"/>
<source>Successfully closed mapper device %1.</source>
<translation type="unfinished"/>
<translation> %1() .</translation>
</message>
<message>
<location filename="../src/modules/partition/jobs/ClearMountsJob.cpp" line="319"/>
<source>Successfully disabled volume group %1.</source>
<translation type="unfinished"/>
<translation> %1() .</translation>
</message>
<message>
<location filename="../src/modules/partition/jobs/ClearMountsJob.cpp" line="358"/>

File diff suppressed because it is too large Load Diff

View File

@@ -690,27 +690,27 @@ Yükleyiciden çıkınca tüm değişiklikler kaybedilecek.</translation>
<message>
<location filename="../src/modules/partition/jobs/ClearMountsJob.cpp" line="259"/>
<source>Successfully unmounted %1.</source>
<translation type="unfinished"/>
<translation>%1 bağlantısı başarıyla kaldırıldı.</translation>
</message>
<message>
<location filename="../src/modules/partition/jobs/ClearMountsJob.cpp" line="266"/>
<source>Successfully disabled swap %1.</source>
<translation type="unfinished"/>
<translation>%1 takas alanı başarıyla devre dışı bırakıldı.</translation>
</message>
<message>
<location filename="../src/modules/partition/jobs/ClearMountsJob.cpp" line="292"/>
<source>Successfully cleared swap %1.</source>
<translation type="unfinished"/>
<translation>%1 takas alanı başarıyla temizlendi.</translation>
</message>
<message>
<location filename="../src/modules/partition/jobs/ClearMountsJob.cpp" line="306"/>
<source>Successfully closed mapper device %1.</source>
<translation type="unfinished"/>
<translation>%1 eşleyici aygıtı başarıyla kapatıldı.</translation>
</message>
<message>
<location filename="../src/modules/partition/jobs/ClearMountsJob.cpp" line="319"/>
<source>Successfully disabled volume group %1.</source>
<translation type="unfinished"/>
<translation>%1 birim grubu başarıyla devre dışı bırakıldı.</translation>
</message>
<message>
<location filename="../src/modules/partition/jobs/ClearMountsJob.cpp" line="358"/>
@@ -862,17 +862,17 @@ Kurulum devam edebilir fakat bazı özellikler devre dışı kalabilir.</transla
<message>
<location filename="../src/modules/welcome/Config.cpp" line="251"/>
<source>&lt;h1&gt;Welcome to %1 setup&lt;/h1&gt;</source>
<translation>&lt;h1&gt;%1 kurulumuna hoşgeldiniz&lt;/h1&gt;</translation>
<translation>&lt;h1&gt;%1 kurulumuna hoş geldiniz&lt;/h1&gt;</translation>
</message>
<message>
<location filename="../src/modules/welcome/Config.cpp" line="255"/>
<source>&lt;h1&gt;Welcome to the Calamares installer for %1&lt;/h1&gt;</source>
<translation>&lt;h1&gt;%1 Calamares Sistem Yükleyiciye Hoşgeldiniz&lt;/h1&gt;</translation>
<translation>&lt;h1&gt;%1 Calamares Sistem Yükleyiciye Hoş Geldiniz&lt;/h1&gt;</translation>
</message>
<message>
<location filename="../src/modules/welcome/Config.cpp" line="256"/>
<source>&lt;h1&gt;Welcome to the %1 installer&lt;/h1&gt;</source>
<translation>&lt;h1&gt;%1 Sistem Yükleyiciye Hoşgeldiniz&lt;/h1&gt;</translation>
<translation>&lt;h1&gt;%1 Sistem Yükleyiciye Hoş Geldiniz&lt;/h1&gt;</translation>
</message>
<message>
<location filename="../src/modules/users/Config.cpp" line="217"/>
@@ -3978,7 +3978,7 @@ Output:
<message>
<location filename="../src/modules/welcome/WelcomePage.cpp" line="217"/>
<source>&lt;h1&gt;Welcome to %1 setup.&lt;/h1&gt;</source>
<translation>&lt;h1&gt;%1 Kurulumuna Hoşgeldiniz.&lt;/h1&gt;</translation>
<translation>&lt;h1&gt;%1 Kurulumuna Hoş Geldiniz.&lt;/h1&gt;</translation>
</message>
<message>
<location filename="../src/modules/welcome/WelcomePage.cpp" line="222"/>
@@ -4016,7 +4016,7 @@ Output:
<message>
<location filename="../src/modules/welcomeq/WelcomeQmlViewStep.cpp" line="40"/>
<source>Welcome</source>
<translation>Hoşgeldiniz</translation>
<translation>Hoş geldiniz</translation>
</message>
</context>
<context>
@@ -4024,7 +4024,7 @@ Output:
<message>
<location filename="../src/modules/welcome/WelcomeViewStep.cpp" line="46"/>
<source>Welcome</source>
<translation>Hoşgeldiniz</translation>
<translation>Hoş geldiniz</translation>
</message>
</context>
<context>

View File

@@ -127,6 +127,7 @@ sequence:
# - dummyprocess
# - dummypython
- partition
# - zfs
- mount
- unpackfs
- machineid

View File

@@ -132,6 +132,11 @@ public:
void setEmergency( bool e ) { m_emergency = e; }
signals:
/** @brief Signals that the job has made progress
*
* The parameter @p percent should be between 0 (0%) and 1 (100%).
* Values outside of this range will be clamped.
*/
void progress( qreal percent );
private:

View File

@@ -20,6 +20,8 @@
#include <QDir>
#include <QFileInfo>
#include <QMutex>
#include <QRandomGenerator>
#include <QTextStream>
#include <QTime>
#include <QVariant>
@@ -229,7 +231,7 @@ toString( const QVariant& v )
}
QDebug&
operator<<( QDebug& s, const Redacted& l )
operator<<( QDebug& s, const RedactedCommand& l )
{
// Special case logging: don't log the (encrypted) password.
if ( l.list.contains( "usermod" ) )
@@ -252,4 +254,33 @@ operator<<( QDebug& s, const Redacted& l )
return s;
}
/** @brief Returns a stable-but-private hash of @p context and @p s
*
* Identical strings with the same context will be hashed the same,
* so that they can be logged and still recognized as the-same.
*/
static uint insertRedactedName( const QString& context, const QString& s )
{
static uint salt = QRandomGenerator::global()->generate(); // Just once
uint val = qHash(context, salt);
return qHash(s, val);
}
RedactedName::RedactedName( const QString& context, const QString& s )
: m_id( insertRedactedName(context, s) ),
m_context(context)
{
}
RedactedName::RedactedName(const char *context, const QString& s )
: RedactedName( QString::fromLatin1( context ), s )
{
}
RedactedName::operator QString() const
{
return QString( m_context + '$' + QString::number( m_id, 16 ) );
}
} // namespace Logger

View File

@@ -145,8 +145,8 @@ public:
{
}
const T& first;
const U& second;
const T first;
const U second;
};
/**
@@ -214,9 +214,9 @@ public:
* since the log may get posted to bug reports, or stored in
* the target system.
*/
struct Redacted
struct RedactedCommand
{
Redacted( const QStringList& l )
RedactedCommand( const QStringList& l )
: list( l )
{
}
@@ -224,7 +224,30 @@ struct Redacted
const QStringList& list;
};
QDebug& operator<<( QDebug& s, const Redacted& l );
QDebug& operator<<( QDebug& s, const RedactedCommand& l );
/** @brief When logging "private" identifiers, keep them consistent but private
*
* Send a string to a logger in such a way that each time it is logged,
* it logs the same way, but without revealing the actual contents.
* This can be applied to user names, UUIDs, etc.
*/
struct RedactedName
{
RedactedName( const char* context, const QString& s );
RedactedName( const QString& context, const QString& s );
operator QString() const;
private:
const uint m_id;
const QString m_context;
};
inline QDebug& operator<<( QDebug& s, const RedactedName& n )
{
return s << NoQuote << QString( n ) << Quote;
}
/**
* @brief Formatted logging of a pointer

View File

@@ -163,7 +163,7 @@ Calamares::Utils::Runner::run()
} );
}
cDebug() << Logger::SubEntry << "Running" << Logger::Redacted( m_command );
cDebug() << Logger::SubEntry << "Running" << Logger::RedactedCommand( m_command );
process.start();
if ( !process.waitForStarted() )
{
@@ -225,13 +225,13 @@ Calamares::Utils::Runner::run()
{
if ( !output.isEmpty() )
{
cDebug() << Logger::SubEntry << "Target cmd:" << Logger::Redacted( m_command ) << "Exit code:" << r
cDebug() << Logger::SubEntry << "Target cmd:" << Logger::RedactedCommand( m_command ) << "Exit code:" << r
<< "output:\n"
<< Logger::NoQuote << output;
}
else
{
cDebug() << Logger::SubEntry << "Target cmd:" << Logger::Redacted( m_command ) << "Exit code:" << r
cDebug() << Logger::SubEntry << "Target cmd:" << Logger::RedactedCommand( m_command ) << "Exit code:" << r
<< "(no output)";
}
}

View File

@@ -398,7 +398,9 @@ target_env_process_output(["ls"])
```
The functions return 0. If the exit code of *command* is not 0, an exception
is raised instead of returning 0.
is raised instead of returning 0. The exception is `subprocess.CalledProcessError`
(as if the *subprocess* module had been used), and the `returncode` member
of the exception object can be used to determine the exit code.
Parameter *stdin* may be a string which is fed to the command as standard input.
The *timeout* is in seconds, with 0 (or a negative number) treated as no-timeout.

View File

@@ -92,6 +92,50 @@ def get_kernel_line(kernel_type):
return ""
def get_zfs_root():
"""
Looks in global storage to find the zfs root
:return: A string containing the path to the zfs root or None if it is not found
"""
zfs = libcalamares.globalstorage.value("zfsDatasets")
if not zfs:
libcalamares.utils.warning("Failed to locate zfs dataset list")
return None
# Find the root dataset
for dataset in zfs:
try:
if dataset["mountpoint"] == "/":
return dataset["zpool"] + "/" + dataset["dsName"]
except KeyError:
# This should be impossible
libcalamares.utils.warning("Internal error handling zfs dataset")
raise
return None
def is_btrfs_root(partition):
""" Returns True if the partition object refers to a btrfs root filesystem
:param partition: A partition map from global storage
:return: True if btrfs and root, False otherwise
"""
return partition["mountPoint"] == "/" and partition["fs"] == "btrfs"
def is_zfs_root(partition):
""" Returns True if the partition object refers to a zfs root filesystem
:param partition: A partition map from global storage
:return: True if zfs and root, False otherwise
"""
return partition["mountPoint"] == "/" and partition["fs"] == "zfs"
def create_systemd_boot_conf(install_path, efi_dir, uuid, entry, entry_name, kernel_type):
"""
Creates systemd-boot configuration files based on given parameters.
@@ -133,11 +177,24 @@ def create_systemd_boot_conf(install_path, efi_dir, uuid, entry, entry_name, ker
"root=/dev/mapper/"
+ partition["luksMapperName"]]
# systemd-boot with a BTRFS root filesystem needs to be told
# about the root subvolume.
for partition in partitions:
if partition["mountPoint"] == "/" and partition["fs"] == "btrfs":
kernel_params.append("rootflags=subvol=@")
# systemd-boot with a BTRFS root filesystem needs to be told abouut the root subvolume.
# If a btrfs root subvolume wasn't set, it means the root is directly on the partition
# and this option isn't needed
if is_btrfs_root(partition):
btrfs_root_subvolume = libcalamares.globalstorage.value("btrfsRootSubvolume")
if btrfs_root_subvolume:
kernel_params.append("rootflags=subvol=" + btrfs_root_subvolume)
# zfs needs to be told the location of the root dataset
if is_zfs_root(partition):
zfs_root_path = get_zfs_root()
if zfs_root_path is not None:
kernel_params.append("zfs=" + zfs_root_path)
else:
# Something is really broken if we get to this point
libcalamares.utils.warning("Internal error handling zfs dataset")
raise Exception("Internal zfs data missing, please contact your distribution")
if cryptdevice_params:
kernel_params.extend(cryptdevice_params)
@@ -314,6 +371,76 @@ def get_grub_efi_parameters():
return None
def run_grub_mkconfig(partitions, output_file):
"""
Runs grub-mkconfig in the target environment
:param partitions: The partitions list from global storage
:param output_file: A string containing the path to the generating grub config file
:return:
"""
# zfs needs an environment variable set for grub-mkconfig
if any([is_zfs_root(partition) for partition in partitions]):
check_target_env_call(["sh", "-c", "ZPOOL_VDEV_NAME_PATH=1 " +
libcalamares.job.configuration["grubMkconfig"] + " -o " + output_file])
else:
# The input file /etc/default/grub should already be filled out by the
# grubcfg job module.
check_target_env_call([libcalamares.job.configuration["grubMkconfig"], "-o", output_file])
def run_grub_install(fw_type, partitions, efi_directory=None):
"""
Runs grub-install in the target environment
:param fw_type: A string which is "efi" for UEFI installs. Any other value results in a BIOS install
:param partitions: The partitions list from global storage
:param efi_directory: The path of the efi directory relative to the root of the install
:return:
"""
is_zfs = any([is_zfs_root(partition) for partition in partitions])
# zfs needs an environment variable set for grub
if is_zfs:
check_target_env_call(["sh", "-c", "echo ZPOOL_VDEV_NAME_PATH=1 >> /etc/environment"])
if fw_type == "efi":
efi_bootloader_id = efi_label()
efi_target, efi_grub_file, efi_boot_file = get_grub_efi_parameters()
if is_zfs:
check_target_env_call(["sh", "-c", "ZPOOL_VDEV_NAME_PATH=1 " + libcalamares.job.configuration["grubInstall"]
+ " --target=" + efi_target + " --efi-directory=" + efi_directory
+ " --bootloader-id=" + efi_bootloader_id + " --force"])
else:
check_target_env_call([libcalamares.job.configuration["grubInstall"],
"--target=" + efi_target,
"--efi-directory=" + efi_directory,
"--bootloader-id=" + efi_bootloader_id,
"--force"])
else:
if libcalamares.globalstorage.value("bootLoader") is None:
return
boot_loader = libcalamares.globalstorage.value("bootLoader")
if boot_loader["installPath"] is None:
return
if is_zfs:
check_target_env_call(["sh", "-c", "ZPOOL_VDEV_NAME_PATH=1 "
+ libcalamares.job.configuration["grubInstall"]
+ " --target=i386-pc --recheck --force "
+ boot_loader["installPath"]])
else:
check_target_env_call([libcalamares.job.configuration["grubInstall"],
"--target=i386-pc",
"--recheck",
"--force",
boot_loader["installPath"]])
def install_grub(efi_directory, fw_type):
"""
Installs grub as bootloader, either in pc or efi mode.
@@ -321,6 +448,12 @@ def install_grub(efi_directory, fw_type):
:param efi_directory:
:param fw_type:
"""
# get the partition from global storage
partitions = libcalamares.globalstorage.value("partitions")
if not partitions:
libcalamares.utils.warning(_("Failed to install grub, no partitions defined in global storage"))
return
if fw_type == "efi":
libcalamares.utils.debug("Bootloader: grub (efi)")
install_path = libcalamares.globalstorage.value("rootMountPoint")
@@ -333,11 +466,7 @@ def install_grub(efi_directory, fw_type):
efi_target, efi_grub_file, efi_boot_file = get_grub_efi_parameters()
check_target_env_call([libcalamares.job.configuration["grubInstall"],
"--target=" + efi_target,
"--efi-directory=" + efi_directory,
"--bootloader-id=" + efi_bootloader_id,
"--force"])
run_grub_install(fw_type, partitions, efi_directory)
# VFAT is weird, see issue CAL-385
install_efi_directory_firmware = (vfat_correct_case(
@@ -356,36 +485,21 @@ def install_grub(efi_directory, fw_type):
os.makedirs(install_efi_boot_directory)
# Workaround for some UEFI firmwares
FALLBACK = "installEFIFallback"
libcalamares.utils.debug("UEFI Fallback: " + str(libcalamares.job.configuration.get(FALLBACK, "<unset>")))
if libcalamares.job.configuration.get(FALLBACK, True):
fallback = "installEFIFallback"
libcalamares.utils.debug("UEFI Fallback: " + str(libcalamares.job.configuration.get(fallback, "<unset>")))
if libcalamares.job.configuration.get(fallback, True):
libcalamares.utils.debug(" .. installing '{!s}' fallback firmware".format(efi_boot_file))
efi_file_source = os.path.join(install_efi_directory_firmware,
efi_bootloader_id,
efi_grub_file)
efi_file_target = os.path.join(install_efi_boot_directory,
efi_boot_file)
efi_bootloader_id,
efi_grub_file)
efi_file_target = os.path.join(install_efi_boot_directory, efi_boot_file)
shutil.copy2(efi_file_source, efi_file_target)
else:
libcalamares.utils.debug("Bootloader: grub (bios)")
if libcalamares.globalstorage.value("bootLoader") is None:
return
run_grub_install(fw_type, partitions)
boot_loader = libcalamares.globalstorage.value("bootLoader")
if boot_loader["installPath"] is None:
return
check_target_env_call([libcalamares.job.configuration["grubInstall"],
"--target=i386-pc",
"--recheck",
"--force",
boot_loader["installPath"]])
# The input file /etc/default/grub should already be filled out by the
# grubcfg job module.
check_target_env_call([libcalamares.job.configuration["grubMkconfig"],
"-o", libcalamares.job.configuration["grubCfg"]])
run_grub_mkconfig(partitions, libcalamares.job.configuration["grubCfg"])
def install_secureboot(efi_directory):

View File

@@ -23,6 +23,7 @@ displaymanagers:
- mdm
- lxdm
- kdm
- greetd
# Enable the following settings to force a desktop environment
# in your displaymanager configuration file. This will attempt

View File

@@ -10,7 +10,7 @@ properties:
type: array
items:
type: string
enum: [slim, sddm, lightdm, gdm, mdm, lxdm, kdm]
enum: [slim, sddm, lightdm, gdm, mdm, lxdm, kdm, greetd]
minItems: 1 # Must be non-empty, if present at all
defaultDesktopEnvironment:
type: object

View File

@@ -17,9 +17,7 @@
import abc
import os
import re
import libcalamares
import configparser
from libcalamares.utils import gettext_path, gettext_languages
@@ -796,6 +794,8 @@ class DMsddm(DisplayManager):
executable = "sddm"
def set_autologin(self, username, do_autologin, default_desktop_environment):
import configparser
# Systems with Sddm as Desktop Manager
sddm_conf_path = os.path.join(self.root_mount_point, "etc/sddm.conf")
@@ -835,6 +835,91 @@ class DMsddm(DisplayManager):
pass
class DMgreetd(DisplayManager):
name = "greetd"
executable = "greetd"
greeter_user = "greeter"
greeter_group = "greetd"
config_data = {}
def os_path(self, path):
return os.path.join(self.root_mount_point, path)
def config_path(self):
return self.os_path("etc/greetd/config.toml")
def environments_path(self):
return self.os_path("etc/greetd/environments")
def config_load(self):
import toml
if (os.path.exists(self.config_path())):
with open(self.config_path(), "r") as f:
self.config_data = toml.load(f)
self.config_data['terminal'] = dict(vt = "next")
default_session_group = self.config_data.get('default_session', None)
if not default_session_group:
self.config_data['default_session'] = {}
self.config_data['default_session']['user'] = self.greeter_user
return self.config_data
def config_write(self):
import toml
with open(self.config_path(), "w") as f:
toml.dump(self.config_data, f)
def basic_setup(self):
if libcalamares.utils.target_env_call(
['getent', 'group', self.greeter_group]
) != 0:
libcalamares.utils.target_env_call(
['groupadd', self.greeter_group]
)
if libcalamares.utils.target_env_call(
['getent', 'passwd', self.greeter_user]
) != 0:
libcalamares.utils.target_env_call(
['useradd',
'-c', '"Greeter User"',
'-g', self.greeter_group,
'-s', '/bin/bash',
self.greeter_user
]
)
def desktop_environment_setup(self, default_desktop_environment):
with open(self.environments_path(), 'w') as envs_file:
envs_file.write(default_desktop_environment)
def greeter_setup(self):
pass
def set_autologin(self, username, do_autologin, default_desktop_environment):
self.config_load()
de_command = default_desktop_environment.executable
if os.path.exists(self.os_path("usr/bin/gtkgreed")) and os.path.exists(self.os_path("usr/bin/cage")):
self.config_data['default_session']['command'] = "cage -s -- gtkgreet"
elif os.path.exists(self.os_path("usr/bin/tuigreet")):
tuigreet_base_cmd = "tuigreet --remember --time --issue --asterisks --cmd "
self.config_data['default_session']['command'] = tuigreet_base_cmd + de_command
elif os.path.exists(self.os_path("usr/bin/ddlm")):
self.config_data['default_session']['command'] = "ddlm --target " + de_command
else:
self.config_data['default_session']['command'] = "agreety --cmd " + de_command
if do_autologin == True:
self.config_data['initial_session'] = dict(command = de_command, user = username)
self.config_write()
class DMsysconfig(DisplayManager):
name = "sysconfig"
executable = None

View File

@@ -0,0 +1,3 @@
# SPDX-FileCopyrightText: no
# SPDX-License-Identifier: CC0-1.0
rootMountPoint: /tmp

View 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()

View 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)

View 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)

View 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()
{
}
}

View File

@@ -11,9 +11,13 @@
# Mount options to use for all filesystems. If a specific filesystem
# is listed here, use those options, otherwise use the *default*
# options from this mapping.
#
# With kernels 5.15 and newer be cautious of adding the option space_cache
# to the btrfs mount options. The default in 5.15 changed to space_cache=v2.
# If space_cache or space_cache=v1 are specified, it may fail to remount.
mountOptions:
default: defaults,noatime
btrfs: defaults,noatime,space_cache,autodefrag,compress=zstd
btrfs: defaults,noatime,autodefrag,compress=zstd
# Mount options to use for the EFI System Partition. If not defined, the
# *mountOptions* for *vfat* are used, or if that is not set either,

View File

@@ -196,7 +196,7 @@ class FstabGenerator(object):
dct = self.generate_fstab_line_info(mount_entry)
if dct:
self.print_fstab_line(dct, file=fstab_file)
else:
elif partition["fs"] != "zfs": # zfs partitions don't need an entry in fstab
dct = self.generate_fstab_line_info(partition)
if dct:
self.print_fstab_line(dct, file=fstab_file)

View File

@@ -55,6 +55,32 @@ def get_grub_config_path(root_mount_point):
return os.path.join(default_dir, default_config_file)
def get_zfs_root():
"""
Looks in global storage to find the zfs root
:return: A string containing the path to the zfs root or None if it is not found
"""
zfs = libcalamares.globalstorage.value("zfsDatasets")
if not zfs:
libcalamares.utils.warning("Failed to locate zfs dataset list")
return None
# Find the root dataset
for dataset in zfs:
try:
if dataset["mountpoint"] == "/":
return dataset["zpool"] + "/" + dataset["dsName"]
except KeyError:
# This should be impossible
libcalamares.utils.warning("Internal error handling zfs dataset")
raise
return None
def modify_grub_default(partitions, root_mount_point, distributor):
"""
Configures '/etc/default/grub' for hibernation and plymouth.
@@ -91,6 +117,8 @@ def modify_grub_default(partitions, root_mount_point, distributor):
swap_outer_mappername = None
no_save_default = False
unencrypted_separate_boot = any(p["mountPoint"] == "/boot" and "luksMapperName" not in p for p in partitions)
# If there is no dracut, and the root partition is ZFS, this gets set below
zfs_root_path = None
for partition in partitions:
if partition["mountPoint"] in ("/", "/boot") and partition["fs"] in ("btrfs", "f2fs"):
@@ -141,8 +169,15 @@ def modify_grub_default(partitions, root_mount_point, distributor):
)
]
if partition["fs"] == "zfs" and partition["mountPoint"] == "/":
zfs_root_path = get_zfs_root()
kernel_params = ["quiet"]
# Currently, grub doesn't detect this properly so it must be set manually
if zfs_root_path:
kernel_params.insert(0, "zfs=" + zfs_root_path)
if cryptdevice_params:
kernel_params.extend(cryptdevice_params)

View File

@@ -150,6 +150,7 @@ def find_initcpio_features(partitions, root_mount_point):
swap_uuid = ""
uses_btrfs = False
uses_zfs = False
uses_lvm2 = False
encrypt_hook = False
openswap_hook = False
@@ -172,6 +173,9 @@ def find_initcpio_features(partitions, root_mount_point):
if partition["fs"] == "btrfs":
uses_btrfs = True
if partition["fs"] == "zfs":
uses_zfs = True
if "lvm2" in partition["fs"]:
uses_lvm2 = True
@@ -198,6 +202,9 @@ def find_initcpio_features(partitions, root_mount_point):
if uses_lvm2:
hooks.append("lvm2")
if uses_zfs:
hooks.append("zfs")
if swap_uuid != "":
if encrypt_hook and openswap_hook:
hooks.extend(["openswap"])

View File

@@ -20,12 +20,24 @@ import os
import libcalamares
import gettext
_ = gettext.translation("calamares-python",
localedir=libcalamares.utils.gettext_path(),
languages=libcalamares.utils.gettext_languages(),
fallback=True).gettext
class ZfsException(Exception):
"""Exception raised when there is a problem with zfs
Attributes:
message -- explanation of the error
"""
def __init__(self, message):
self.message = message
def pretty_name():
return _("Mounting partitions.")
@@ -47,20 +59,85 @@ def get_btrfs_subvolumes(partitions):
if btrfs_subvolumes is None:
libcalamares.utils.warning("No configuration for btrfsSubvolumes")
if not btrfs_subvolumes:
btrfs_subvolumes = [ dict(mountPoint="/", subvolume="/@"), dict(mountPoint="/home", subvolume="/@home") ]
btrfs_subvolumes = [dict(mountPoint="/", subvolume="/@"), dict(mountPoint="/home", subvolume="/@home")]
# Filter out the subvolumes which have a dedicated partition
non_root_partition_mounts = [ m for m in [ p.get("mountPoint", None) for p in partitions ] if m is not None and m != '/' ]
btrfs_subvolumes = list(filter(lambda s : s["mountPoint"] not in non_root_partition_mounts, btrfs_subvolumes))
non_root_partition_mounts = [m for m in [p.get("mountPoint", None) for p in partitions] if
m is not None and m != '/']
btrfs_subvolumes = list(filter(lambda s: s["mountPoint"] not in non_root_partition_mounts, btrfs_subvolumes))
# If we have a swap **file**, give it a separate subvolume.
swap_choice = libcalamares.globalstorage.value( "partitionChoices" )
if swap_choice and swap_choice.get( "swap", None ) == "file":
swap_choice = libcalamares.globalstorage.value("partitionChoices")
if swap_choice and swap_choice.get("swap", None) == "file":
btrfs_subvolumes.append({'mountPoint': '/swap', 'subvolume': '/@swap'})
return btrfs_subvolumes
def mount_zfs(root_mount_point, partition):
""" Mounts a zfs partition at @p root_mount_point
:param root_mount_point: The absolute path to the root of the install
:param partition: The partition map from global storage for this partition
:return:
"""
# Get the list of zpools from global storage
zfs_pool_list = libcalamares.globalstorage.value("zfsPoolInfo")
if not zfs_pool_list:
libcalamares.utils.warning("Failed to locate zfsPoolInfo data in global storage")
raise ZfsException(_("Internal error mounting zfs datasets"))
# Find the zpool matching this partition
for zfs_pool in zfs_pool_list:
if zfs_pool["mountpoint"] == partition["mountPoint"]:
pool_name = zfs_pool["poolName"]
ds_name = zfs_pool["dsName"]
# import the zpool
try:
libcalamares.utils.host_env_process_output(["zpool", "import", "-N", "-R", root_mount_point, pool_name], None)
except subprocess.CalledProcessError:
raise ZfsException(_("Failed to import zpool"))
# Get the encrpytion information from global storage
zfs_info_list = libcalamares.globalstorage.value("zfsInfo")
encrypt = False
if zfs_info_list:
for zfs_info in zfs_info_list:
if zfs_info["mountpoint"] == partition["mountPoint"] and zfs_info["encrypted"] is True:
encrypt = True
passphrase = zfs_info["passphrase"]
if encrypt is True:
# The zpool is encrypted, we need to unlock it
try:
libcalamares.utils.host_env_process_output(["zfs", "load-key", pool_name], None, passphrase)
except subprocess.CalledProcessError:
raise ZfsException(_("Failed to unlock zpool"))
if partition["mountPoint"] == '/':
# Get the zfs dataset list from global storage
zfs = libcalamares.globalstorage.value("zfsDatasets")
if not zfs:
libcalamares.utils.warning("Failed to locate zfs dataset list")
raise ZfsException(_("Internal error mounting zfs datasets"))
zfs.sort(key=lambda x: x["mountpoint"])
for dataset in zfs:
try:
if dataset["canMount"] == "noauto" or dataset["canMount"] is True:
libcalamares.utils.host_env_process_output(["zfs", "mount",
dataset["zpool"] + '/' + dataset["dsName"]])
except subprocess.CalledProcessError:
raise ZfsException(_("Failed to set zfs mountpoint"))
else:
try:
libcalamares.utils.host_env_process_output(["zfs", "mount", pool_name + '/' + ds_name])
except subprocess.CalledProcessError:
raise ZfsException(_("Failed to set zfs mountpoint"))
def mount_partition(root_mount_point, partition, partitions):
"""
Do a single mount of @p partition inside @p root_mount_point.
@@ -96,11 +173,14 @@ def mount_partition(root_mount_point, partition, partitions):
if "luksMapperName" in partition:
device = os.path.join("/dev/mapper", partition["luksMapperName"])
if libcalamares.utils.mount(device,
mount_point,
fstype,
partition.get("options", "")) != 0:
libcalamares.utils.warning("Cannot mount {}".format(device))
if fstype == "zfs":
mount_zfs(root_mount_point, partition)
else: # fstype == "zfs"
if libcalamares.utils.mount(device,
mount_point,
fstype,
partition.get("options", "")) != 0:
libcalamares.utils.warning("Cannot mount {}".format(device))
# Special handling for btrfs subvolumes. Create the subvolumes listed in mount.conf
if fstype == "btrfs" and partition["mountPoint"] == '/':
@@ -111,9 +191,11 @@ def mount_partition(root_mount_point, partition, partitions):
libcalamares.globalstorage.insert("btrfsSubvolumes", btrfs_subvolumes)
# Create the subvolumes that are in the completed list
for s in btrfs_subvolumes:
subprocess.check_call(['btrfs', 'subvolume', 'create',
root_mount_point + s['subvolume']])
subprocess.check_call(["btrfs", "subvolume", "create",
root_mount_point + s["subvolume"]])
if s["mountPoint"] == "/":
# insert the root subvolume into global storage
libcalamares.globalstorage.insert("btrfsRootSubvolume", s["subvolume"])
subprocess.check_call(["umount", "-v", root_mount_point])
device = partition["device"]
@@ -126,9 +208,9 @@ def mount_partition(root_mount_point, partition, partitions):
mount_option = "subvol={}".format(s['subvolume'])
subvolume_mountpoint = mount_point[:-1] + s['mountPoint']
if libcalamares.utils.mount(device,
subvolume_mountpoint,
fstype,
",".join([mount_option, partition.get("options", "")])) != 0:
subvolume_mountpoint,
fstype,
",".join([mount_option, partition.get("options", "")])) != 0:
libcalamares.utils.warning("Cannot mount {}".format(device))
@@ -142,7 +224,7 @@ def run():
if not partitions:
libcalamares.utils.warning("partitions is empty, {!s}".format(partitions))
return (_("Configuration Error"),
_("No partitions are defined for <pre>{!s}</pre> to use." ).format("mount"))
_("No partitions are defined for <pre>{!s}</pre> to use.").format("mount"))
root_mount_point = tempfile.mkdtemp(prefix="calamares-root-")
@@ -159,10 +241,13 @@ def run():
# This way, we ensure / is mounted before the rest, and every mount point
# is created on the right partition (e.g. if a partition is to be mounted
# under /tmp, we make sure /tmp is mounted before the partition)
mountable_partitions = [ p for p in partitions + extra_mounts if "mountPoint" in p and p["mountPoint"] ]
mountable_partitions = [p for p in partitions + extra_mounts if "mountPoint" in p and p["mountPoint"]]
mountable_partitions.sort(key=lambda x: x["mountPoint"])
for partition in mountable_partitions:
mount_partition(root_mount_point, partition, partitions)
try:
for partition in mountable_partitions:
mount_partition(root_mount_point, partition, partitions)
except ZfsException as ze:
return _("zfs mounting error"), ze.message
libcalamares.globalstorage.insert("rootMountPoint", root_mount_point)

View File

@@ -257,14 +257,16 @@ PartitionCoreModule::doInit()
cDebug() << Logger::SubEntry << "node\tcapacity\tname\tprettyName";
for ( auto device : devices )
{
cDebug() << Logger::SubEntry << Logger::Pointer( device );
if ( device )
{
// Gives ownership of the Device* to the DeviceInfo object
auto deviceInfo = new DeviceInfo( device );
m_deviceInfos << deviceInfo;
cDebug() << Logger::SubEntry << device->deviceNode() << device->capacity() << device->name()
<< device->prettyName();
cDebug() << Logger::SubEntry
<< device->deviceNode()
<< device->capacity()
<< Logger::RedactedName( "DevName", device->name() )
<< Logger::RedactedName( "DevNamePretty", device->prettyName() );
}
else
{
@@ -707,10 +709,10 @@ PartitionCoreModule::dumpQueue() const
cDebug() << "# Queue:";
for ( auto info : m_deviceInfos )
{
cDebug() << Logger::SubEntry << "## Device:" << info->device->name();
cDebug() << Logger::SubEntry << "## Device:" << info->device->deviceNode();
for ( const auto& job : info->jobs() )
{
cDebug() << Logger::SubEntry << "-" << job->prettyName();
cDebug() << Logger::SubEntry << "-" << job->metaObject()->className();
}
}
}

View File

@@ -296,7 +296,9 @@ PartitionLayout::createPartitions( Device* dev,
}
Partition* part = nullptr;
if ( luksPassphrase.isEmpty() )
// Encryption for zfs is handled in the zfs module
if ( luksPassphrase.isEmpty() || correctFS( entry.partFileSystem ) == FileSystem::Zfs )
{
part = KPMHelpers::createNewPartition( parent,
*dev,
@@ -319,6 +321,24 @@ PartitionLayout::createPartitions( Device* dev,
luksPassphrase,
KPM_PARTITION_FLAG( None ) );
}
// For zfs, we need to make the passphrase available to later modules
if ( correctFS( entry.partFileSystem ) == FileSystem::Zfs )
{
Calamares::GlobalStorage* storage = Calamares::JobQueue::instance()->globalStorage();
QList< QVariant > zfsInfoList;
QVariantMap zfsInfo;
// Save the information subsequent modules will need
zfsInfo[ "encrypted" ] = !luksPassphrase.isEmpty();
zfsInfo[ "passphrase" ] = luksPassphrase;
zfsInfo[ "mountpoint" ] = entry.partMountPoint;
// Add it to the list and insert it into global storage
zfsInfoList.append( zfsInfo );
storage->insert( "zfsInfo", zfsInfoList );
}
PartitionInfo::setFormat( part, true );
PartitionInfo::setMountPoint( part, entry.partMountPoint );
if ( !entry.partLabel.isEmpty() )

View File

@@ -23,6 +23,7 @@
#include "GlobalStorage.h"
#include "JobQueue.h"
#include "Settings.h"
#include "partition/FileSystem.h"
#include "partition/PartitionQuery.h"
#include "utils/Logger.h"
@@ -104,7 +105,9 @@ CreatePartitionDialog::CreatePartitionDialog( Device* device,
QStringList fsNames;
for ( auto fs : FileSystemFactory::map() )
{
if ( fs->supportCreate() != FileSystem::cmdSupportNone && fs->type() != FileSystem::Extended )
// We need to ensure zfs is added to the list if the zfs module is enabled
if ( ( fs->type() == FileSystem::Type::Zfs && Calamares::Settings::instance()->isModuleEnabled( "zfs" ) )
|| ( fs->supportCreate() != FileSystem::cmdSupportNone && fs->type() != FileSystem::Extended ) )
{
fsNames << userVisibleFS( fs ); // This is put into the combobox
if ( fs->type() == defaultFSType )
@@ -240,7 +243,8 @@ CreatePartitionDialog::getNewlyCreatedPartition()
// does so, to set up the partition for create-and-then-set-flags.
Partition* partition = nullptr;
QString luksPassphrase = m_ui->encryptWidget->passphrase();
if ( m_ui->encryptWidget->state() == EncryptWidget::Encryption::Confirmed && !luksPassphrase.isEmpty() )
if ( m_ui->encryptWidget->state() == EncryptWidget::Encryption::Confirmed && !luksPassphrase.isEmpty()
&& fsType != FileSystem::Zfs )
{
partition = KPMHelpers::createNewEncryptedPartition(
m_parent, *m_device, m_role, fsType, fsLabel, first, last, luksPassphrase, PartitionTable::Flags() );
@@ -251,6 +255,31 @@ CreatePartitionDialog::getNewlyCreatedPartition()
m_parent, *m_device, m_role, fsType, fsLabel, first, last, PartitionTable::Flags() );
}
// For zfs, we let the zfs module handle the encryption but we need to make the passphrase available to later modules
if ( fsType == FileSystem::Zfs )
{
Calamares::GlobalStorage* storage = Calamares::JobQueue::instance()->globalStorage();
QList< QVariant > zfsInfoList;
QVariantMap zfsInfo;
// If this is not the first encrypted zfs partition, get the old list first
if ( storage->contains( "zfsInfo" ) )
{
zfsInfoList = storage->value( "zfsInfo" ).toList();
storage->remove( "zfsInfo" );
}
// Save the information subsequent modules will need
zfsInfo[ "encrypted" ]
= m_ui->encryptWidget->state() == EncryptWidget::Encryption::Confirmed && !luksPassphrase.isEmpty();
zfsInfo[ "passphrase" ] = luksPassphrase;
zfsInfo[ "mountpoint" ] = selectedMountPoint( m_ui->mountPointComboBox );
// Add it to the list and insert it into global storage
zfsInfoList.append( zfsInfo );
storage->insert( "zfsInfo", zfsInfoList );
}
if ( m_device->type() == Device::Type::LVM_Device )
{
partition->setPartitionPath( m_device->deviceNode() + QStringLiteral( "/" )

View File

@@ -25,6 +25,7 @@
#include "GlobalStorage.h"
#include "JobQueue.h"
#include "Settings.h"
#include "partition/FileSystem.h"
#include "utils/Logger.h"
@@ -89,7 +90,9 @@ EditExistingPartitionDialog::EditExistingPartitionDialog( Device* device,
QStringList fsNames;
for ( auto fs : FileSystemFactory::map() )
{
if ( fs->supportCreate() != FileSystem::cmdSupportNone && fs->type() != FileSystem::Extended )
// We need to ensure zfs is added to the list if the zfs module is enabled
if ( ( fs->type() == FileSystem::Type::Zfs && Calamares::Settings::instance()->isModuleEnabled( "zfs" ) )
|| ( fs->supportCreate() != FileSystem::cmdSupportNone && fs->type() != FileSystem::Extended ) )
{
fsNames << userVisibleFS( fs ); // For the combo box
}
@@ -117,6 +120,12 @@ EditExistingPartitionDialog::EditExistingPartitionDialog( Device* device,
m_ui->fileSystemLabel->setEnabled( m_ui->formatRadioButton->isChecked() );
m_ui->fileSystemComboBox->setEnabled( m_ui->formatRadioButton->isChecked() );
// Force a format if the existing device is a zfs device since reusing a zpool isn't currently supported
m_ui->formatRadioButton->setChecked( m_partition->fileSystem().type() == FileSystem::Type::Zfs );
m_ui->formatRadioButton->setEnabled( !( m_partition->fileSystem().type() == FileSystem::Type::Zfs ) );
m_ui->keepRadioButton->setChecked( !( m_partition->fileSystem().type() == FileSystem::Type::Zfs ) );
m_ui->keepRadioButton->setEnabled( !( m_partition->fileSystem().type() == FileSystem::Type::Zfs ) );
setFlagList( *( m_ui->m_listFlags ), m_partition->availableFlags(), PartitionInfo::flags( m_partition ) );
}

View File

@@ -11,8 +11,10 @@
#include "CreatePartitionJob.h"
#include "core/PartitionInfo.h"
#include "partition/FileSystem.h"
#include "partition/PartitionQuery.h"
#include "utils/CalamaresUtilsSystem.h"
#include "utils/Logger.h"
#include "utils/Units.h"
@@ -24,9 +26,79 @@
#include <kpmcore/ops/newoperation.h>
#include <kpmcore/util/report.h>
#include <qcoreapplication.h>
#include <qregularexpression.h>
using CalamaresUtils::Partition::untranslatedFS;
using CalamaresUtils::Partition::userVisibleFS;
/** @brief Create
*
* Uses sfdisk to remove @p partition. This should only be used in cases
* where using kpmcore to remove the partition would not be appropriate
*
*/
static Calamares::JobResult
createZfs( Partition* partition, Device* device )
{
auto r = CalamaresUtils::System::instance()->runCommand(
{ "sh",
"-c",
"echo start=" + QString::number( partition->firstSector() ) + " size="
+ QString::number( partition->length() ) + " | sfdisk --append --force " + partition->devicePath() },
std::chrono::seconds( 5 ) );
if ( r.getExitCode() != 0 )
{
return Calamares::JobResult::error(
QCoreApplication::translate( CreatePartitionJob::staticMetaObject.className(),
"Failed to create partition" ),
QCoreApplication::translate( CreatePartitionJob::staticMetaObject.className(),
"Failed to create zfs partition with output: "
+ r.getOutput().toLocal8Bit() ) );
}
// Now we need to do some things that would normally be done by kpmcore
// First we get the device node from the output and set it as the partition path
QRegularExpression re( QStringLiteral( "Created a new partition (\\d+)" ) );
QRegularExpressionMatch rem = re.match( r.getOutput() );
QString deviceNode;
if ( rem.hasMatch() )
{
if ( partition->devicePath().back().isDigit() )
{
deviceNode = partition->devicePath() + QLatin1Char( 'p' ) + rem.captured( 1 );
}
else
{
deviceNode = partition->devicePath() + rem.captured( 1 );
}
}
partition->setPartitionPath( deviceNode );
// If it is a gpt device, set the partition UUID
if ( device->partitionTable()->type() == PartitionTable::gpt && partition->uuid().isEmpty() )
{
r = CalamaresUtils::System::instance()->runCommand(
{ "sfdisk", "--list", "--output", "Device,UUID", partition->devicePath() }, std::chrono::seconds( 5 ) );
if ( r.getExitCode() == 0 )
{
QRegularExpression re( deviceNode + QStringLiteral( " +(.+)" ) );
QRegularExpressionMatch rem = re.match( r.getOutput() );
if ( rem.hasMatch() )
{
partition->setUUID( rem.captured( 1 ) );
}
}
}
return Calamares::JobResult::ok();
}
CreatePartitionJob::CreatePartitionJob( Device* device, Partition* partition )
: PartitionJob( partition )
, m_device( device )
@@ -194,6 +266,13 @@ CreatePartitionJob::prettyStatusMessage() const
Calamares::JobResult
CreatePartitionJob::exec()
{
// kpmcore doesn't currently handle this case properly so for now, we manually create the partion
// The zfs module can later deal with creating a zpool in the partition
if ( m_partition->fileSystem().type() == FileSystem::Type::Zfs )
{
return createZfs( m_partition, m_device );
}
Report report( nullptr );
NewOperation op( *m_device, m_partition );
op.setStatus( Operation::StatusRunning );

View File

@@ -104,14 +104,19 @@ mapForPartition( Partition* partition, const QString& uuid )
// Debugging for inside the loop in createPartitionList(),
// so indent a bit
Logger::CDebug deb;
using TR = Logger::DebugRow< const char* const, const QString& >;
using TR = Logger::DebugRow< const char* const, const QString >;
// clang-format off
deb << Logger::SubEntry << "mapping for" << partition->partitionPath() << partition->deviceNode()
<< TR( "partlabel", map[ "partlabel" ].toString() ) << TR( "partuuid", map[ "partuuid" ].toString() )
<< TR( "parttype", map[ "parttype" ].toString() ) << TR( "partattrs", map[ "partattrs" ].toString() )
<< TR( "mountPoint:", PartitionInfo::mountPoint( partition ) ) << TR( "fs:", map[ "fs" ].toString() )
<< TR( "fsName", map[ "fsName" ].toString() ) << TR( "uuid", uuid )
<< TR( "partlabel", map[ "partlabel" ].toString() )
<< TR( "partition-uuid (partuuid)", Logger::RedactedName( "PartUUID", map[ "partuuid" ].toString() ) )
<< TR( "parttype", map[ "parttype" ].toString() )
<< TR( "partattrs", map[ "partattrs" ].toString() )
<< TR( "mountPoint:", PartitionInfo::mountPoint( partition ) )
<< TR( "fs:", map[ "fs" ].toString() )
<< TR( "fsName", map[ "fsName" ].toString() )
<< TR( "filesystem-uuid (uuid)", Logger::RedactedName( "FSUUID", uuid ) )
<< TR( "claimed", map[ "claimed" ].toString() );
// clang-format on
if ( partition->roles().has( PartitionRole::Luks ) )
{
const FileSystem& fsRef = partition->fileSystem();

View File

@@ -49,6 +49,27 @@ def list_mounts(root_mount_point):
return lst
def export_zpools(root_mount_point):
""" Exports the zpools if defined in global storage
:param root_mount_point: The absolute path to the root of the install
:return:
"""
try:
zfs_pool_list = libcalamares.globalstorage.value("zfsPoolInfo")
zfs_pool_list.sort(reverse=True, key=lambda x: x["poolName"])
if zfs_pool_list:
for zfs_pool in zfs_pool_list:
try:
libcalamares.utils.host_env_process_output(['zpool', 'export', zfs_pool["poolName"]])
except subprocess.CalledProcessError:
libcalamares.utils.warning("Failed to export zpool")
except Exception as e:
# If this fails it shouldn't cause the installation to fail
libcalamares.utils.warning("Received exception while exporting zpools: " + format(e))
pass
def run():
""" Unmounts given mountpoints in decreasing order.
@@ -94,6 +115,8 @@ def run():
# in the exception object.
subprocess.check_output(["umount", "-lv", mount_point], stderr=subprocess.STDOUT)
export_zpools(root_mount_point)
os.rmdir(root_mount_point)
return None

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

View 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 ]