Merge branch 'calamares' into work-3.3

This commit is contained in:
Adriaan de Groot 2021-11-30 11:39:30 +01:00
commit ce23efae99
81 changed files with 2744 additions and 1260 deletions

View File

@ -7,6 +7,67 @@ 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.48 (unreleased) #
This release contains contributions from (alphabetically by first name):
- Evan James
## Core ##
- Python modules now have `warn()` and `error()` methods they can call,
alongside the existing `debug()` and `warning()` (all live in the
*libcalamares.utils* module).
- Python modules can load YAML files via `libcalamares.utils.load_yaml()`.
This may be the most useful for test-scripts.
## Modules ##
- *fstab* now has a separate, special, flags-setting for swap subvolumes
on btrfs. A swap subvolume is created if a swap **file** (not a separate
partition) is selected in the auto-partitioning page. (Thanks Evan)
- The *packages* module now has some special settings for the `pacman`
package manager (generally used on Arch-derivatives). This allows
tweaking of the installation process, if downloads are slow or
packages may fail to install. See the `packages.conf` file for
details. (Thanks Evan)
# 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):

View File

@ -55,6 +55,10 @@ project( CALAMARES
LANGUAGES C CXX LANGUAGES C CXX
) )
if( CALAMARES_RELEASE_MODE AND CMAKE_SOURCE_DIR STREQUAL CMAKE_BINARY_DIR )
message( FATAL_ERROR "Do not build development versions in the source-directory." )
endif()
### OPTIONS ### OPTIONS
# #
option( INSTALL_CONFIG "Install configuration files" OFF ) option( INSTALL_CONFIG "Install configuration files" OFF )
@ -139,12 +143,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 fa fi_FI he hr ja ko lt pt_PT si
pt_PT sq sv tr_TR uk zh_CN zh_TW ) sq 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 gl hu id is mr nb
nb pl ro si sl sr sr@latin th ) 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 )

View File

@ -71,8 +71,8 @@ GenericName[eu]=Sistema instalatzailea
Comment[eu]=Calamares - sistema instalatzailea Comment[eu]=Calamares - sistema instalatzailea
Name[fa]=نصب سامانه Name[fa]=نصب سامانه
Icon[fa]=کالامارس Icon[fa]=کالامارس
GenericName[fa]=نصبکنندهٔ سامانه GenericName[fa]=نصبکننده سامانه
Comment[fa]=کالامارس نصبکنندهٔ سامانه Comment[fa]=کالامارس نصبکننده سامانه
Name[es_PR]=Instalar el sistema Name[es_PR]=Instalar el sistema
Name[fr]=Installer le système Name[fr]=Installer le système
Icon[fr]=calamares Icon[fr]=calamares

View File

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

View File

@ -20,4 +20,8 @@ def target_env_call(_): return 0
def check_target_env_call(_): pass def check_target_env_call(_): pass
def target_env_process_output(cmd, *args): return 0
def host_env_process_output(cmd, *args): return 0
def mount(device, mountpoint, fstype, options): return 0 def mount(device, mountpoint, fstype, options): return 0

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

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

View File

@ -1,37 +0,0 @@
# $FreeBSD$
#
# SPDX-FileCopyrightText: 2020 Adriaan de Groot <adridg@FreeBSD.org>
# SPDX-License-Identifier: BSD-2-Clause
PORTNAME= calamares
DISTVERSION= 3.2.25
CATEGORIES= sysutils
MASTER_SITES= https://github.com/${PORTNAME}/${PORTNAME}/releases/download/v${DISTVERSION}/
MAINTAINER= adridg@FreeBSD.org
COMMENT= GUI System installer and OEM configurator
LICENSE= GPLv3
LICENSE_FILE= ${WRKSRC}/LICENSE
LIB_DEPENDS= libyaml-cpp.so:devel/yaml-cpp \
libpwquality.so:security/libpwquality \
libboost_python${PYTHON_SUFFIX}.so:devel/boost-python-libs
USES= cmake compiler:c++17-lang gettext kde:5 pkgconfig \
python:3.3+ qt:5
USE_QT= concurrent core dbus declarative gui \
network quickcontrols2 svg widgets xml \
buildtools_build linguist_build qmake_build
USE_KDE= coreaddons dbusaddons parts service \
ecm_build
USE_LDCONFIG= yes
CMAKE_OFF= WITH_KF5Crash \
INSTALL_CONFIG \
INSTALL_COMPLETION \
INSTALL_POLKIT
CMAKE_ON= CMAKE_DISABLE_FIND_PACKAGE_KPMcore
CMAKE_ARGS= -DSKIP_MODULES="webview"
.include <bsd.port.mk>

View File

@ -1,3 +0,0 @@
TIMESTAMP = 1592339404
SHA256 (calamares-3.2.25.tar.gz) = 797ce33db7d4e4c06bbccef95f6c4023f7628e91bd142896695565fed4ae8c4b
SIZE (calamares-3.2.25.tar.gz) = 3580197

View File

@ -1,14 +0,0 @@
Calamares is an installer framework. By design it is very customizable,
in order to satisfy a wide variety of needs and use cases.
Calamares aims to be easy, usable, beautiful, pragmatic, inclusive and
distribution-agnostic.
Got a Linux distribution but no system installer? Grab Calamares, mix
and match any number of Calamares modules (or write your own in Python
or C++), throw together some branding, package it up and you are ready
to ship!
(The above applies to FreeBSD as well)
WWW: https://calamares.io/

View File

@ -1,224 +0,0 @@
bin/calamares
include/libcalamares/CalamaresConfig.h
include/libcalamares/CppJob.h
include/libcalamares/DllMacro.h
include/libcalamares/GlobalStorage.h
include/libcalamares/Job.h
include/libcalamares/JobExample.h
include/libcalamares/JobQueue.h
include/libcalamares/ProcessJob.h
include/libcalamares/PythonHelper.h
include/libcalamares/PythonJob.h
include/libcalamares/PythonJobApi.h
include/libcalamares/Settings.h
include/libcalamares/utils/BoostPython.h
include/libcalamares/utils/CalamaresUtilsSystem.h
include/libcalamares/utils/CommandList.h
include/libcalamares/utils/Dirs.h
include/libcalamares/utils/Entropy.h
include/libcalamares/utils/Logger.h
include/libcalamares/utils/NamedEnum.h
include/libcalamares/utils/NamedSuffix.h
include/libcalamares/utils/PluginFactory.h
include/libcalamares/utils/RAII.h
include/libcalamares/utils/Retranslator.h
include/libcalamares/utils/String.h
include/libcalamares/utils/Tests.h
include/libcalamares/utils/UMask.h
include/libcalamares/utils/Units.h
include/libcalamares/utils/Variant.h
include/libcalamares/utils/Yaml.h
include/libcalamares/utils/moc-warnings.h
lib/calamares/libcalamares.so
lib/calamares/modules/bootloader/main.py
lib/calamares/modules/bootloader/module.desc
lib/calamares/modules/bootloader/test.yaml
lib/calamares/modules/contextualprocess/libcalamares_job_contextualprocess.so
lib/calamares/modules/contextualprocess/module.desc
lib/calamares/modules/displaymanager/main.py
lib/calamares/modules/displaymanager/module.desc
lib/calamares/modules/dracut/main.py
lib/calamares/modules/dracut/module.desc
lib/calamares/modules/dracutlukscfg/libcalamares_job_dracutlukscfg.so
lib/calamares/modules/dracutlukscfg/module.desc
lib/calamares/modules/dummycpp/libcalamares_job_dummycpp.so
lib/calamares/modules/dummycpp/module.desc
lib/calamares/modules/dummyprocess/module.desc
lib/calamares/modules/dummypython/main.py
lib/calamares/modules/dummypython/module.desc
lib/calamares/modules/finished/libcalamares_viewmodule_finished.so
lib/calamares/modules/finished/module.desc
lib/calamares/modules/fstab/main.py
lib/calamares/modules/fstab/module.desc
lib/calamares/modules/fstab/test.yaml
lib/calamares/modules/grubcfg/main.py
lib/calamares/modules/grubcfg/module.desc
lib/calamares/modules/hostinfo/libcalamares_job_hostinfo.so
lib/calamares/modules/hostinfo/module.desc
lib/calamares/modules/hwclock/main.py
lib/calamares/modules/hwclock/module.desc
lib/calamares/modules/initcpio/libcalamares_job_initcpio.so
lib/calamares/modules/initcpio/module.desc
lib/calamares/modules/initcpiocfg/main.py
lib/calamares/modules/initcpiocfg/module.desc
lib/calamares/modules/initramfs/libcalamares_job_initramfs.so
lib/calamares/modules/initramfs/module.desc
lib/calamares/modules/initramfscfg/encrypt_hook
lib/calamares/modules/initramfscfg/encrypt_hook_nokey
lib/calamares/modules/initramfscfg/main.py
lib/calamares/modules/initramfscfg/module.desc
lib/calamares/modules/interactiveterminal/libcalamares_viewmodule_interactiveterminal.so
lib/calamares/modules/interactiveterminal/module.desc
lib/calamares/modules/keyboard/libcalamares_viewmodule_keyboard.so
lib/calamares/modules/keyboard/module.desc
lib/calamares/modules/keyboardq/libcalamares_viewmodule_keyboardq.so
lib/calamares/modules/keyboardq/module.desc
lib/calamares/modules/license/libcalamares_viewmodule_license.so
lib/calamares/modules/license/module.desc
lib/calamares/modules/locale/libcalamares_viewmodule_locale.so
lib/calamares/modules/locale/module.desc
lib/calamares/modules/localecfg/main.py
lib/calamares/modules/localecfg/module.desc
lib/calamares/modules/localeq/libcalamares_viewmodule_localeq.so
lib/calamares/modules/localeq/module.desc
lib/calamares/modules/luksbootkeyfile/libcalamares_job_luksbootkeyfile.so
lib/calamares/modules/luksbootkeyfile/module.desc
lib/calamares/modules/luksopenswaphookcfg/main.py
lib/calamares/modules/luksopenswaphookcfg/module.desc
lib/calamares/modules/machineid/libcalamares_job_machineid.so
lib/calamares/modules/machineid/module.desc
lib/calamares/modules/mount/main.py
lib/calamares/modules/mount/module.desc
lib/calamares/modules/mount/test.yaml
lib/calamares/modules/netinstall/libcalamares_viewmodule_netinstall.so
lib/calamares/modules/netinstall/module.desc
lib/calamares/modules/networkcfg/main.py
lib/calamares/modules/networkcfg/module.desc
lib/calamares/modules/notesqml/libcalamares_viewmodule_notesqml.so
lib/calamares/modules/notesqml/module.desc
lib/calamares/modules/oemid/libcalamares_viewmodule_oemid.so
lib/calamares/modules/oemid/module.desc
lib/calamares/modules/openrcdmcryptcfg/main.py
lib/calamares/modules/openrcdmcryptcfg/module.desc
lib/calamares/modules/packagechooser/libcalamares_viewmodule_packagechooser.so
lib/calamares/modules/packagechooser/module.desc
lib/calamares/modules/packages/main.py
lib/calamares/modules/packages/module.desc
lib/calamares/modules/packages/test.yaml
lib/calamares/modules/plymouthcfg/main.py
lib/calamares/modules/plymouthcfg/module.desc
lib/calamares/modules/preservefiles/libcalamares_job_preservefiles.so
lib/calamares/modules/preservefiles/module.desc
lib/calamares/modules/rawfs/main.py
lib/calamares/modules/rawfs/module.desc
lib/calamares/modules/removeuser/libcalamares_job_removeuser.so
lib/calamares/modules/removeuser/module.desc
lib/calamares/modules/services-openrc/main.py
lib/calamares/modules/services-openrc/module.desc
lib/calamares/modules/services-systemd/main.py
lib/calamares/modules/services-systemd/module.desc
lib/calamares/modules/shellprocess/libcalamares_job_shellprocess.so
lib/calamares/modules/shellprocess/module.desc
lib/calamares/modules/summary/libcalamares_viewmodule_summary.so
lib/calamares/modules/summary/module.desc
lib/calamares/modules/tracking/libcalamares_viewmodule_tracking.so
lib/calamares/modules/tracking/module.desc
lib/calamares/modules/umount/main.py
lib/calamares/modules/umount/module.desc
lib/calamares/modules/unpackfs/main.py
lib/calamares/modules/unpackfs/module.desc
lib/calamares/modules/unpackfs/runtests.sh
lib/calamares/modules/users/libcalamares_viewmodule_users.so
lib/calamares/modules/users/module.desc
lib/calamares/modules/welcome/libcalamares_viewmodule_welcome.so
lib/calamares/modules/welcome/module.desc
lib/calamares/modules/welcomeq/libcalamares_viewmodule_welcomeq.so
lib/calamares/modules/welcomeq/module.desc
lib/cmake/Calamares/CMakeColors.cmake
lib/cmake/Calamares/CalamaresAddBrandingSubdirectory.cmake
lib/cmake/Calamares/CalamaresAddLibrary.cmake
lib/cmake/Calamares/CalamaresAddModuleSubdirectory.cmake
lib/cmake/Calamares/CalamaresAddPlugin.cmake
lib/cmake/Calamares/CalamaresAddTest.cmake
lib/cmake/Calamares/CalamaresAddTranslations.cmake
lib/cmake/Calamares/CalamaresAutomoc.cmake
lib/cmake/Calamares/CalamaresConfig.cmake
lib/cmake/Calamares/CalamaresConfigVersion.cmake
lib/cmake/Calamares/CalamaresLibraryDepends-%%CMAKE_BUILD_TYPE%%.cmake
lib/cmake/Calamares/CalamaresLibraryDepends.cmake
lib/cmake/Calamares/CalamaresUse.cmake
lib/libcalamares.so
lib/libcalamares.so.3.2.25
lib/libcalamaresui.so
lib/libcalamaresui.so.3.2.25
man/man8/calamares.8.gz
share/applications/calamares.desktop
%%DATADIR%%/branding/default/banner.png
%%DATADIR%%/branding/default/branding.desc
%%DATADIR%%/branding/default/lang/calamares-default_ar.qm
%%DATADIR%%/branding/default/lang/calamares-default_en.qm
%%DATADIR%%/branding/default/lang/calamares-default_eo.qm
%%DATADIR%%/branding/default/lang/calamares-default_fr.qm
%%DATADIR%%/branding/default/lang/calamares-default_nl.qm
%%DATADIR%%/branding/default/languages.png
%%DATADIR%%/branding/default/show.qml
%%DATADIR%%/branding/default/squid.png
%%DATADIR%%/branding/default/stylesheet.qss
%%DATADIR%%/qml/calamares/slideshow/BackButton.qml
%%DATADIR%%/qml/calamares/slideshow/ForwardButton.qml
%%DATADIR%%/qml/calamares/slideshow/NavButton.qml
%%DATADIR%%/qml/calamares/slideshow/Presentation.qml
%%DATADIR%%/qml/calamares/slideshow/Slide.qml
%%DATADIR%%/qml/calamares/slideshow/SlideCounter.qml
%%DATADIR%%/qml/calamares/slideshow/qmldir
share/icons/hicolor/scalable/apps/calamares.svg
share/locale/ar/LC_MESSAGES/calamares-python.mo
share/locale/as/LC_MESSAGES/calamares-python.mo
share/locale/ast/LC_MESSAGES/calamares-python.mo
share/locale/be/LC_MESSAGES/calamares-python.mo
share/locale/bg/LC_MESSAGES/calamares-python.mo
share/locale/ca/LC_MESSAGES/calamares-python.mo
share/locale/cs_CZ/LC_MESSAGES/calamares-python.mo
share/locale/da/LC_MESSAGES/calamares-python.mo
share/locale/de/LC_MESSAGES/calamares-python.mo
share/locale/el/LC_MESSAGES/calamares-python.mo
share/locale/en_GB/LC_MESSAGES/calamares-python.mo
share/locale/eo/LC_MESSAGES/calamares-python.mo
share/locale/es/LC_MESSAGES/calamares-python.mo
share/locale/es_MX/LC_MESSAGES/calamares-python.mo
share/locale/es_PR/LC_MESSAGES/calamares-python.mo
share/locale/et/LC_MESSAGES/calamares-python.mo
share/locale/eu/LC_MESSAGES/calamares-python.mo
share/locale/fi_FI/LC_MESSAGES/calamares-python.mo
share/locale/fr/LC_MESSAGES/calamares-python.mo
share/locale/gl/LC_MESSAGES/calamares-python.mo
share/locale/he/LC_MESSAGES/calamares-python.mo
share/locale/hi/LC_MESSAGES/calamares-python.mo
share/locale/hr/LC_MESSAGES/calamares-python.mo
share/locale/hu/LC_MESSAGES/calamares-python.mo
share/locale/id/LC_MESSAGES/calamares-python.mo
share/locale/is/LC_MESSAGES/calamares-python.mo
share/locale/it_IT/LC_MESSAGES/calamares-python.mo
share/locale/ja/LC_MESSAGES/calamares-python.mo
share/locale/ko/LC_MESSAGES/calamares-python.mo
share/locale/lt/LC_MESSAGES/calamares-python.mo
share/locale/ml/LC_MESSAGES/calamares-python.mo
share/locale/mr/LC_MESSAGES/calamares-python.mo
share/locale/nb/LC_MESSAGES/calamares-python.mo
share/locale/nl/LC_MESSAGES/calamares-python.mo
share/locale/pl/LC_MESSAGES/calamares-python.mo
share/locale/pt_BR/LC_MESSAGES/calamares-python.mo
share/locale/pt_PT/LC_MESSAGES/calamares-python.mo
share/locale/ro/LC_MESSAGES/calamares-python.mo
share/locale/ru/LC_MESSAGES/calamares-python.mo
share/locale/sk/LC_MESSAGES/calamares-python.mo
share/locale/sl/LC_MESSAGES/calamares-python.mo
share/locale/sq/LC_MESSAGES/calamares-python.mo
share/locale/sr/LC_MESSAGES/calamares-python.mo
share/locale/sr@latin/LC_MESSAGES/calamares-python.mo
share/locale/sv/LC_MESSAGES/calamares-python.mo
share/locale/th/LC_MESSAGES/calamares-python.mo
share/locale/tr_TR/LC_MESSAGES/calamares-python.mo
share/locale/uk/LC_MESSAGES/calamares-python.mo
share/locale/zh_CN/LC_MESSAGES/calamares-python.mo
share/locale/zh_TW/LC_MESSAGES/calamares-python.mo

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

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

View File

@ -689,27 +689,27 @@ L'instal·lador es tancarà i tots els canvis es perdran.</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>S'ha desmuntat correctament %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>S'ha inhabilitat correctament l'intercanvi %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>S'ha netejat correctament l'intercanvi %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>El dispositiu de mapatge %1 s'ha tancat correctament.</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>El grup de volums %1 s'ha inhabilitat correctament.</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"/>

File diff suppressed because it is too large Load Diff

View File

@ -691,27 +691,27 @@ Instalacijski program će izaći i sve promjene će biti izgubljene.</translatio
<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>Uspješno demontiran %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>Uspješno onemogućen swap %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>Uspješno obrisan swap %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>Uspješno zatvoren device mapper %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>Volume grupa %1 je uspješno onemogućena.</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"/>

View File

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

View File

@ -689,27 +689,27 @@ O instalador será encerrado e todas as alterações serão perdidas.</translati
<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 desmontado com sucesso.</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>Swap %1 desativada com sucesso.</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>Swap % 1 limpa com sucesso.</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>Dispositivo mapeador % 1 fechado com sucesso.</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>Grupo de volume % 1 desativado com sucesso.</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"/>

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> <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"/>
@ -867,12 +867,12 @@ Kurulum devam edebilir fakat bazı özellikler devre dışı kalabilir.</transla
<message> <message>
<location filename="../src/modules/welcome/Config.cpp" line="255"/> <location filename="../src/modules/welcome/Config.cpp" line="255"/>
<source>&lt;h1&gt;Welcome to the Calamares installer for %1&lt;/h1&gt;</source> <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>
<message> <message>
<location filename="../src/modules/welcome/Config.cpp" line="256"/> <location filename="../src/modules/welcome/Config.cpp" line="256"/>
<source>&lt;h1&gt;Welcome to the %1 installer&lt;/h1&gt;</source> <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>
<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>&lt;h1&gt;Welcome to %1 setup.&lt;/h1&gt;</source> <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>
<message> <message>
<location filename="../src/modules/welcome/WelcomePage.cpp" line="222"/> <location filename="../src/modules/welcome/WelcomePage.cpp" line="222"/>

View File

@ -323,7 +323,7 @@ msgstr "<code>{name!s}</code> systemd hədəfi aktiv edilmədi"
#: src/modules/services-systemd/main.py:67 #: src/modules/services-systemd/main.py:67
msgid "Cannot enable systemd timer <code>{name!s}</code>." msgid "Cannot enable systemd timer <code>{name!s}</code>."
msgstr "" msgstr "<code>{name!s}</code> systemd taymeri aktiv edilə bilmir."
#: src/modules/services-systemd/main.py:71 #: src/modules/services-systemd/main.py:71
msgid "Cannot disable systemd target <code>{name!s}</code>." msgid "Cannot disable systemd target <code>{name!s}</code>."
@ -407,6 +407,7 @@ msgid ""
"Failed to find unsquashfs, make sure you have the squashfs-tools package " "Failed to find unsquashfs, make sure you have the squashfs-tools package "
"installed." "installed."
msgstr "" msgstr ""
"Unsquashfs tapılmadı, squashfs-tools paketinin quraşdırıldığına əmin olun."
#: src/modules/unpackfs/main.py:479 #: src/modules/unpackfs/main.py:479
msgid "The destination \"{}\" in the target system is not a directory" msgid "The destination \"{}\" in the target system is not a directory"

View File

@ -323,7 +323,7 @@ msgstr "<code>{name!s}</code> systemd hədəfi aktiv edilmədi"
#: src/modules/services-systemd/main.py:67 #: src/modules/services-systemd/main.py:67
msgid "Cannot enable systemd timer <code>{name!s}</code>." msgid "Cannot enable systemd timer <code>{name!s}</code>."
msgstr "" msgstr "<code>{name!s}</code> systemd taymeri aktiv edilə bilmir."
#: src/modules/services-systemd/main.py:71 #: src/modules/services-systemd/main.py:71
msgid "Cannot disable systemd target <code>{name!s}</code>." msgid "Cannot disable systemd target <code>{name!s}</code>."
@ -407,6 +407,7 @@ msgid ""
"Failed to find unsquashfs, make sure you have the squashfs-tools package " "Failed to find unsquashfs, make sure you have the squashfs-tools package "
"installed." "installed."
msgstr "" msgstr ""
"Unsquashfs tapılmadı, squashfs-tools paketinin quraşdırıldığına əmin olun."
#: src/modules/unpackfs/main.py:479 #: src/modules/unpackfs/main.py:479
msgid "The destination \"{}\" in the target system is not a directory" msgid "The destination \"{}\" in the target system is not a directory"

View File

@ -327,6 +327,7 @@ msgstr "No es pot habilitar la destinació de systemd <code>{name!s}</code>."
#: src/modules/services-systemd/main.py:67 #: src/modules/services-systemd/main.py:67
msgid "Cannot enable systemd timer <code>{name!s}</code>." msgid "Cannot enable systemd timer <code>{name!s}</code>."
msgstr "" msgstr ""
"No es pot habilitar el temporitzador de systemd <code>{name!s}</code>."
#: src/modules/services-systemd/main.py:71 #: src/modules/services-systemd/main.py:71
msgid "Cannot disable systemd target <code>{name!s}</code>." msgid "Cannot disable systemd target <code>{name!s}</code>."
@ -406,6 +407,8 @@ msgid ""
"Failed to find unsquashfs, make sure you have the squashfs-tools package " "Failed to find unsquashfs, make sure you have the squashfs-tools package "
"installed." "installed."
msgstr "" msgstr ""
"No s'ha pogut trobar unsquashfs, assegureu-vos que tingueu instal·lat el "
"paquet squashfs-tools."
#: src/modules/unpackfs/main.py:479 #: src/modules/unpackfs/main.py:479
msgid "The destination \"{}\" in the target system is not a directory" msgid "The destination \"{}\" in the target system is not a directory"

View File

@ -329,7 +329,7 @@ msgstr "Das systemd-Ziel <code>{name!s}</code> kann nicht aktiviert werden."
#: src/modules/services-systemd/main.py:67 #: src/modules/services-systemd/main.py:67
msgid "Cannot enable systemd timer <code>{name!s}</code>." msgid "Cannot enable systemd timer <code>{name!s}</code>."
msgstr "" msgstr "Systemd-Timer <code>{name!s}</code> kann nicht aktiviert werden."
#: src/modules/services-systemd/main.py:71 #: src/modules/services-systemd/main.py:71
msgid "Cannot disable systemd target <code>{name!s}</code>." msgid "Cannot disable systemd target <code>{name!s}</code>."
@ -412,6 +412,8 @@ msgid ""
"Failed to find unsquashfs, make sure you have the squashfs-tools package " "Failed to find unsquashfs, make sure you have the squashfs-tools package "
"installed." "installed."
msgstr "" msgstr ""
"Unsquashfs nicht gefunden, stellen Sie sicher, dass das Paket squashfs-tools"
" installiert ist."
#: src/modules/unpackfs/main.py:479 #: src/modules/unpackfs/main.py:479
msgid "The destination \"{}\" in the target system is not a directory" msgid "The destination \"{}\" in the target system is not a directory"

View File

@ -6,6 +6,7 @@
# Translators: # Translators:
# Danial Behzadi <dani.behzi@ubuntu.com>, 2020 # Danial Behzadi <dani.behzi@ubuntu.com>, 2020
# alireza jamshidi <alirezajam98@gmail.com>, 2020 # alireza jamshidi <alirezajam98@gmail.com>, 2020
# Mahdy Mirzade <me@mahdym.ir>, 2021
# #
#, fuzzy #, fuzzy
msgid "" msgid ""
@ -14,7 +15,7 @@ msgstr ""
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-11-02 15:45+0100\n" "POT-Creation-Date: 2021-11-02 15:45+0100\n"
"PO-Revision-Date: 2017-08-09 10:34+0000\n" "PO-Revision-Date: 2017-08-09 10:34+0000\n"
"Last-Translator: alireza jamshidi <alirezajam98@gmail.com>, 2020\n" "Last-Translator: Mahdy Mirzade <me@mahdym.ir>, 2021\n"
"Language-Team: Persian (https://www.transifex.com/calamares/teams/20061/fa/)\n" "Language-Team: Persian (https://www.transifex.com/calamares/teams/20061/fa/)\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
@ -62,13 +63,15 @@ msgstr "نصب بارکنندهٔ راه‌اندازی."
#: src/modules/bootloader/main.py:508 #: src/modules/bootloader/main.py:508
msgid "Bootloader installation error" msgid "Bootloader installation error"
msgstr "" msgstr "خطای نصب بوت لودر"
#: src/modules/bootloader/main.py:509 #: src/modules/bootloader/main.py:509
msgid "" msgid ""
"The bootloader could not be installed. The installation command " "The bootloader could not be installed. The installation command "
"<pre>{!s}</pre> returned error code {!s}." "<pre>{!s}</pre> returned error code {!s}."
msgstr "" msgstr ""
"بوت لودر نتوانست نصب شود. دستور <pre>{!s}</pre> برای نصب با خطای {!s} مواجه "
"شد."
#: src/modules/fstab/main.py:29 #: src/modules/fstab/main.py:29
msgid "Writing fstab." msgid "Writing fstab."
@ -77,6 +80,7 @@ msgstr "در حال نوشتن fstab."
#: src/modules/fstab/main.py:389 #: src/modules/fstab/main.py:389
msgid "No <pre>{!s}</pre> configuration is given for <pre>{!s}</pre> to use." msgid "No <pre>{!s}</pre> configuration is given for <pre>{!s}</pre> to use."
msgstr "" msgstr ""
"هیچ تنظیمات <pre>{!s}</pre> برای استفاده برای <pre>{!s}</pre> داده نشده است."
#: src/modules/dracut/main.py:27 #: src/modules/dracut/main.py:27
msgid "Creating initramfs with dracut." msgid "Creating initramfs with dracut."
@ -139,6 +143,8 @@ msgid ""
"The displaymanagers list is empty or undefined in both globalstorage and " "The displaymanagers list is empty or undefined in both globalstorage and "
"displaymanager.conf." "displaymanager.conf."
msgstr "" msgstr ""
"فهرست مدیریت صفحه نمایش ها خالی بوده یا در محل ذخیره داده و "
"displaymanager.conf تعریف نشده است."
#: src/modules/displaymanager/main.py:989 #: src/modules/displaymanager/main.py:989
msgid "Display manager configuration was incomplete" msgid "Display manager configuration was incomplete"
@ -161,6 +167,8 @@ msgid ""
"Unknown service-action <code>{arg!s}</code> for service {name!s} in run-" "Unknown service-action <code>{arg!s}</code> for service {name!s} in run-"
"level {level!s}." "level {level!s}."
msgstr "" msgstr ""
"دستور سرویس <code>{arg!s}</code> برای سرویس {name!s} در سطح اجرای {level!s}"
" ناشناخته است."
#: src/modules/services-openrc/main.py:93 #: src/modules/services-openrc/main.py:93
#: src/modules/services-systemd/main.py:59 #: src/modules/services-systemd/main.py:59
@ -171,6 +179,8 @@ msgstr "نمی‌توان خدمت را دستکاری کرد"
msgid "" msgid ""
"<code>rc-update {arg!s}</code> call in chroot returned error code {num!s}." "<code>rc-update {arg!s}</code> call in chroot returned error code {num!s}."
msgstr "" msgstr ""
"فراخوانی <code>rc-update {arg!s}</code> در chroot کد خطای {num!s} را "
"برگرداند."
#: src/modules/services-openrc/main.py:101 #: src/modules/services-openrc/main.py:101
msgid "Target runlevel does not exist" msgid "Target runlevel does not exist"
@ -181,6 +191,8 @@ msgid ""
"The path for runlevel {level!s} is <code>{path!s}</code>, which does not " "The path for runlevel {level!s} is <code>{path!s}</code>, which does not "
"exist." "exist."
msgstr "" msgstr ""
"مسیر برای سطح اجرای {level!s} برابر <code>{path!s}</code> است، که وجود "
"ندارد."
#: src/modules/services-openrc/main.py:110 #: src/modules/services-openrc/main.py:110
msgid "Target service does not exist" msgid "Target service does not exist"
@ -191,6 +203,7 @@ msgid ""
"The path for service {name!s} is <code>{path!s}</code>, which does not " "The path for service {name!s} is <code>{path!s}</code>, which does not "
"exist." "exist."
msgstr "" msgstr ""
"مسیر برای سرویس {name!s} برابر <code>{path!s}</code> است، که وجود ندارد."
#: src/modules/networkcfg/main.py:29 #: src/modules/networkcfg/main.py:29
msgid "Saving network configuration." msgid "Saving network configuration."
@ -223,25 +236,31 @@ msgstr[1] "در حال برداشتن %(num)d بسته."
#: src/modules/packages/main.py:638 src/modules/packages/main.py:650 #: src/modules/packages/main.py:638 src/modules/packages/main.py:650
#: src/modules/packages/main.py:678 #: src/modules/packages/main.py:678
msgid "Package Manager error" msgid "Package Manager error"
msgstr "" msgstr "خطای مدیر بسته"
#: src/modules/packages/main.py:639 #: src/modules/packages/main.py:639
msgid "" msgid ""
"The package manager could not prepare updates. The command <pre>{!s}</pre> " "The package manager could not prepare updates. The command <pre>{!s}</pre> "
"returned error code {!s}." "returned error code {!s}."
msgstr "" msgstr ""
"مدیر بسته نتوانست برای بروزرسانی ها آماده شود، دستور <pre>{!s}</pre> با خطای"
" {!s} مواجه شد."
#: src/modules/packages/main.py:651 #: src/modules/packages/main.py:651
msgid "" msgid ""
"The package manager could not update the system. The command <pre>{!s}</pre>" "The package manager could not update the system. The command <pre>{!s}</pre>"
" returned error code {!s}." " returned error code {!s}."
msgstr "" msgstr ""
"مدیر بسته نتوانست سامانه را بروز کند. دستور <pre>{!s}</pre> با خطای {!s} "
"مواجه شد."
#: src/modules/packages/main.py:679 #: src/modules/packages/main.py:679
msgid "" msgid ""
"The package manager could not make changes to the installed system. The " "The package manager could not make changes to the installed system. The "
"command <pre>{!s}</pre> returned error code {!s}." "command <pre>{!s}</pre> returned error code {!s}."
msgstr "" msgstr ""
"مدیر بسته نتوانست تغییرات را برای نصب سامانه انجام دهد. دستور "
"<pre>{!s}</pre> با خطای {!s} مواجه شد."
#: src/modules/plymouthcfg/main.py:27 #: src/modules/plymouthcfg/main.py:27
msgid "Configure Plymouth theme" msgid "Configure Plymouth theme"
@ -306,7 +325,7 @@ msgstr "نمی‌توان هدف سیستم‌دی <code>{name!s}</code> را ب
#: src/modules/services-systemd/main.py:67 #: src/modules/services-systemd/main.py:67
msgid "Cannot enable systemd timer <code>{name!s}</code>." msgid "Cannot enable systemd timer <code>{name!s}</code>."
msgstr "" msgstr "نمی‌توان تایمر سیستم‌دی <code>{name!s}</code> را به کار انداخت."
#: src/modules/services-systemd/main.py:71 #: src/modules/services-systemd/main.py:71
msgid "Cannot disable systemd target <code>{name!s}</code>." msgid "Cannot disable systemd target <code>{name!s}</code>."
@ -326,11 +345,11 @@ msgstr ""
#: src/modules/mkinitfs/main.py:27 #: src/modules/mkinitfs/main.py:27
msgid "Creating initramfs with mkinitfs." msgid "Creating initramfs with mkinitfs."
msgstr "" msgstr "درحال ایجاد initramfs با mkinitfs."
#: src/modules/mkinitfs/main.py:49 #: src/modules/mkinitfs/main.py:49
msgid "Failed to run mkinitfs on the target" msgid "Failed to run mkinitfs on the target"
msgstr "" msgstr "شکست در اجرا mkinitfs روی هدف"
#: src/modules/unpackfs/main.py:34 #: src/modules/unpackfs/main.py:34
msgid "Filling up filesystems." msgid "Filling up filesystems."
@ -385,7 +404,7 @@ msgstr "سامانهٔ پروندهٔ مبدأ {} وجود ندارد"
msgid "" msgid ""
"Failed to find unsquashfs, make sure you have the squashfs-tools package " "Failed to find unsquashfs, make sure you have the squashfs-tools package "
"installed." "installed."
msgstr "" msgstr "شکست در یافتن unsquashfs. مطمئن شوید بسته squashfs-tools نصب است."
#: src/modules/unpackfs/main.py:479 #: src/modules/unpackfs/main.py:479
msgid "The destination \"{}\" in the target system is not a directory" msgid "The destination \"{}\" in the target system is not a directory"

View File

@ -325,7 +325,7 @@ msgstr "Ne mogu omogućiti systemd cilj <code>{name!s}</code>."
#: src/modules/services-systemd/main.py:67 #: src/modules/services-systemd/main.py:67
msgid "Cannot enable systemd timer <code>{name!s}</code>." msgid "Cannot enable systemd timer <code>{name!s}</code>."
msgstr "" msgstr "Nije moguće omogućiti systemd timer <code>{name!s}</code>."
#: src/modules/services-systemd/main.py:71 #: src/modules/services-systemd/main.py:71
msgid "Cannot disable systemd target <code>{name!s}</code>." msgid "Cannot disable systemd target <code>{name!s}</code>."
@ -405,6 +405,8 @@ msgid ""
"Failed to find unsquashfs, make sure you have the squashfs-tools package " "Failed to find unsquashfs, make sure you have the squashfs-tools package "
"installed." "installed."
msgstr "" msgstr ""
"Neuspješno pronalaženje unsquashfs, provjerite imate li instaliran paket "
"squashfs-tools."
#: src/modules/unpackfs/main.py:479 #: src/modules/unpackfs/main.py:479
msgid "The destination \"{}\" in the target system is not a directory" msgid "The destination \"{}\" in the target system is not a directory"

View File

@ -307,7 +307,7 @@ msgstr "systemd 대상 <code>{name! s}</code>를 활성화 할 수 없습니다.
#: src/modules/services-systemd/main.py:67 #: src/modules/services-systemd/main.py:67
msgid "Cannot enable systemd timer <code>{name!s}</code>." msgid "Cannot enable systemd timer <code>{name!s}</code>."
msgstr "" msgstr "시스템 타이머 <code>{name!s}</code>를 활성화할 수 없습니다."
#: src/modules/services-systemd/main.py:71 #: src/modules/services-systemd/main.py:71
msgid "Cannot disable systemd target <code>{name!s}</code>." msgid "Cannot disable systemd target <code>{name!s}</code>."
@ -386,7 +386,7 @@ msgstr "\"{}\" 소스 파일시스템은 존재하지 않습니다."
msgid "" msgid ""
"Failed to find unsquashfs, make sure you have the squashfs-tools package " "Failed to find unsquashfs, make sure you have the squashfs-tools package "
"installed." "installed."
msgstr "" msgstr "unsquashfs를 찾지 못했습니다. squashfs-tools 패키지가 설치되어 있는지 확인하십시오."
#: src/modules/unpackfs/main.py:479 #: src/modules/unpackfs/main.py:479
msgid "The destination \"{}\" in the target system is not a directory" msgid "The destination \"{}\" in the target system is not a directory"

View File

@ -329,7 +329,7 @@ msgstr "Não é possível ativar o destino do systemd <code>{name!s}</code>."
#: src/modules/services-systemd/main.py:67 #: src/modules/services-systemd/main.py:67
msgid "Cannot enable systemd timer <code>{name!s}</code>." msgid "Cannot enable systemd timer <code>{name!s}</code>."
msgstr "" msgstr "Não é possível ativar o temporizador systemd <code>{name!s}</code>."
#: src/modules/services-systemd/main.py:71 #: src/modules/services-systemd/main.py:71
msgid "Cannot disable systemd target <code>{name!s}</code>." msgid "Cannot disable systemd target <code>{name!s}</code>."
@ -411,6 +411,8 @@ msgid ""
"Failed to find unsquashfs, make sure you have the squashfs-tools package " "Failed to find unsquashfs, make sure you have the squashfs-tools package "
"installed." "installed."
msgstr "" msgstr ""
"Falha ao localizar o unsquashfs, certifique-se de que tem o pacote squashfs-"
"tools instalado."
#: src/modules/unpackfs/main.py:479 #: src/modules/unpackfs/main.py:479
msgid "The destination \"{}\" in the target system is not a directory" msgid "The destination \"{}\" in the target system is not a directory"

View File

@ -5,6 +5,7 @@
# #
# Translators: # Translators:
# හෙළබස, 2021 # හෙළබස, 2021
# Sandaruwan Samaraweera, 2021
# #
#, fuzzy #, fuzzy
msgid "" msgid ""
@ -13,7 +14,7 @@ msgstr ""
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-11-02 15:45+0100\n" "POT-Creation-Date: 2021-11-02 15:45+0100\n"
"PO-Revision-Date: 2017-08-09 10:34+0000\n" "PO-Revision-Date: 2017-08-09 10:34+0000\n"
"Last-Translator: හෙළබස, 2021\n" "Last-Translator: Sandaruwan Samaraweera, 2021\n"
"Language-Team: Sinhala (https://www.transifex.com/calamares/teams/20061/si/)\n" "Language-Team: Sinhala (https://www.transifex.com/calamares/teams/20061/si/)\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
@ -23,7 +24,7 @@ msgstr ""
#: src/modules/initramfscfg/main.py:32 #: src/modules/initramfscfg/main.py:32
msgid "Configuring initramfs." msgid "Configuring initramfs."
msgstr "" msgstr "initramfs වින්‍යාස කිරීම."
#: src/modules/initramfscfg/main.py:85 src/modules/initramfscfg/main.py:89 #: src/modules/initramfscfg/main.py:85 src/modules/initramfscfg/main.py:89
#: src/modules/fstab/main.py:355 src/modules/fstab/main.py:361 #: src/modules/fstab/main.py:355 src/modules/fstab/main.py:361
@ -35,161 +36,169 @@ msgstr ""
#: src/modules/luksopenswaphookcfg/main.py:86 #: src/modules/luksopenswaphookcfg/main.py:86
#: src/modules/luksopenswaphookcfg/main.py:90 #: src/modules/luksopenswaphookcfg/main.py:90
msgid "Configuration Error" msgid "Configuration Error"
msgstr "" msgstr "වින්‍යාස දෝෂය"
#: src/modules/initramfscfg/main.py:86 src/modules/fstab/main.py:356 #: src/modules/initramfscfg/main.py:86 src/modules/fstab/main.py:356
#: src/modules/initcpiocfg/main.py:228 src/modules/mount/main.py:145 #: src/modules/initcpiocfg/main.py:228 src/modules/mount/main.py:145
#: src/modules/rawfs/main.py:165 src/modules/openrcdmcryptcfg/main.py:73 #: src/modules/rawfs/main.py:165 src/modules/openrcdmcryptcfg/main.py:73
#: src/modules/luksopenswaphookcfg/main.py:87 #: src/modules/luksopenswaphookcfg/main.py:87
msgid "No partitions are defined for <pre>{!s}</pre> to use." msgid "No partitions are defined for <pre>{!s}</pre> to use."
msgstr "" msgstr "{!s} සඳහා භාවිතා කිරීමට කිසිදු කොටස් නිර්වචනය කර නොමැත."
#: src/modules/initramfscfg/main.py:90 src/modules/fstab/main.py:362 #: src/modules/initramfscfg/main.py:90 src/modules/fstab/main.py:362
#: src/modules/networkcfg/main.py:106 src/modules/initcpiocfg/main.py:232 #: src/modules/networkcfg/main.py:106 src/modules/initcpiocfg/main.py:232
#: src/modules/localecfg/main.py:136 src/modules/openrcdmcryptcfg/main.py:77 #: src/modules/localecfg/main.py:136 src/modules/openrcdmcryptcfg/main.py:77
#: src/modules/luksopenswaphookcfg/main.py:91 #: src/modules/luksopenswaphookcfg/main.py:91
msgid "No root mount point is given for <pre>{!s}</pre> to use." msgid "No root mount point is given for <pre>{!s}</pre> to use."
msgstr "" msgstr "{!s} සඳහා භාවිතා කිරීමට root mount point ලබා දී නොමැත."
#: src/modules/grubcfg/main.py:28 #: src/modules/grubcfg/main.py:28
msgid "Configure GRUB." msgid "Configure GRUB."
msgstr "" msgstr "GRUB වින්‍යාස කරන්න."
#: src/modules/bootloader/main.py:43 #: src/modules/bootloader/main.py:43
msgid "Install bootloader." msgid "Install bootloader."
msgstr "" msgstr "bootloader ස්ථාපනය කරන්න."
#: src/modules/bootloader/main.py:508 #: src/modules/bootloader/main.py:508
msgid "Bootloader installation error" msgid "Bootloader installation error"
msgstr "" msgstr "Bootloader ස්ථාපනය කිරීමේ දෝෂයකි"
#: src/modules/bootloader/main.py:509 #: src/modules/bootloader/main.py:509
msgid "" msgid ""
"The bootloader could not be installed. The installation command " "The bootloader could not be installed. The installation command "
"<pre>{!s}</pre> returned error code {!s}." "<pre>{!s}</pre> returned error code {!s}."
msgstr "" msgstr ""
"ඇරඹුම් කාරකය ස්ථාපනය කල නොහැක. ස්ථාපන විධානය <pre>{!s}</pre> දෝෂ කේතය {!s} "
"ලබා දුන්නේය."
#: src/modules/fstab/main.py:29 #: src/modules/fstab/main.py:29
msgid "Writing fstab." msgid "Writing fstab."
msgstr "" msgstr "fstab ලියමින්."
#: src/modules/fstab/main.py:389 #: src/modules/fstab/main.py:389
msgid "No <pre>{!s}</pre> configuration is given for <pre>{!s}</pre> to use." msgid "No <pre>{!s}</pre> configuration is given for <pre>{!s}</pre> to use."
msgstr "" msgstr ""
"භාවිතා කිරීමට <pre>{!s}</pre> සඳහා <pre>{!s}</pre> වින්‍යාසයක් ලබා දී නොමැත."
#: src/modules/dracut/main.py:27 #: src/modules/dracut/main.py:27
msgid "Creating initramfs with dracut." msgid "Creating initramfs with dracut."
msgstr "" msgstr "dracut සමඟ initramfs නිර්මාණය කිරීම."
#: src/modules/dracut/main.py:49 #: src/modules/dracut/main.py:49
msgid "Failed to run dracut on the target" msgid "Failed to run dracut on the target"
msgstr "" msgstr "ඉලක්කය මත ඩ්‍රැකට් ධාවනය කිරීමට අපොහොසත් විය"
#: src/modules/dracut/main.py:50 src/modules/mkinitfs/main.py:50 #: src/modules/dracut/main.py:50 src/modules/mkinitfs/main.py:50
msgid "The exit code was {}" msgid "The exit code was {}"
msgstr "" msgstr "පිටවීමේ කේතය වූයේ {}"
#: src/modules/displaymanager/main.py:526 #: src/modules/displaymanager/main.py:526
msgid "Cannot write KDM configuration file" msgid "Cannot write KDM configuration file"
msgstr "" msgstr "KDM වින්‍යාස ගොනුව ලිවිය නොහැක"
#: src/modules/displaymanager/main.py:527 #: src/modules/displaymanager/main.py:527
msgid "KDM config file {!s} does not exist" msgid "KDM config file {!s} does not exist"
msgstr "" msgstr "KDM වින්‍යාස ගොනුව {!s} නොපවතී"
#: src/modules/displaymanager/main.py:588 #: src/modules/displaymanager/main.py:588
msgid "Cannot write LXDM configuration file" msgid "Cannot write LXDM configuration file"
msgstr "" msgstr "LXDM වින්‍යාස ගොනුව ලිවිය නොහැක"
#: src/modules/displaymanager/main.py:589 #: src/modules/displaymanager/main.py:589
msgid "LXDM config file {!s} does not exist" msgid "LXDM config file {!s} does not exist"
msgstr "" msgstr "LXDM වින්‍යාස ගොනුව {!s} නොපවතී"
#: src/modules/displaymanager/main.py:672 #: src/modules/displaymanager/main.py:672
msgid "Cannot write LightDM configuration file" msgid "Cannot write LightDM configuration file"
msgstr "" msgstr "LightDM වින්‍යාස ගොනුව ලිවිය නොහැක"
#: src/modules/displaymanager/main.py:673 #: src/modules/displaymanager/main.py:673
msgid "LightDM config file {!s} does not exist" msgid "LightDM config file {!s} does not exist"
msgstr "" msgstr "LightDM වින්‍යාස ගොනුව {!s} නොපවතී"
#: src/modules/displaymanager/main.py:747 #: src/modules/displaymanager/main.py:747
msgid "Cannot configure LightDM" msgid "Cannot configure LightDM"
msgstr "" msgstr "LightDM වින්‍යාස කළ නොහැක"
#: src/modules/displaymanager/main.py:748 #: src/modules/displaymanager/main.py:748
msgid "No LightDM greeter installed." msgid "No LightDM greeter installed."
msgstr "" msgstr "LightDM ග්‍රීටර් ස්ථාපනය කර නැත."
#: src/modules/displaymanager/main.py:779 #: src/modules/displaymanager/main.py:779
msgid "Cannot write SLIM configuration file" msgid "Cannot write SLIM configuration file"
msgstr "" msgstr "SLIM වින්‍යාස ගොනුව ලිවිය නොහැක"
#: src/modules/displaymanager/main.py:780 #: src/modules/displaymanager/main.py:780
msgid "SLIM config file {!s} does not exist" msgid "SLIM config file {!s} does not exist"
msgstr "" msgstr "SLIM වින්‍යාස ගොනුව {!s} නොපවතී"
#: src/modules/displaymanager/main.py:906 #: src/modules/displaymanager/main.py:906
msgid "No display managers selected for the displaymanager module." msgid "No display managers selected for the displaymanager module."
msgstr "" msgstr "සංදර්ශක කළමනාකරු මොඩියුලය සඳහා සංදර්ශක කළමනාකරුවන් තෝරාගෙන නොමැත."
#: src/modules/displaymanager/main.py:907 #: src/modules/displaymanager/main.py:907
msgid "" msgid ""
"The displaymanagers list is empty or undefined in both globalstorage and " "The displaymanagers list is empty or undefined in both globalstorage and "
"displaymanager.conf." "displaymanager.conf."
msgstr "" msgstr ""
"ගෝලීය ගබඩාව සහ displaymanager.conf යන දෙකෙහිම සංදර්ශක කළමනාකරු ලැයිස්තුව "
"හිස් හෝ අර්ථ දක්වා නොමැත."
#: src/modules/displaymanager/main.py:989 #: src/modules/displaymanager/main.py:989
msgid "Display manager configuration was incomplete" msgid "Display manager configuration was incomplete"
msgstr "" msgstr "සංදර්ශක කළමනාකරු වින්‍යාසය අසම්පූර්ණ විය"
#: src/modules/services-openrc/main.py:29 #: src/modules/services-openrc/main.py:29
msgid "Configure OpenRC services" msgid "Configure OpenRC services"
msgstr "" msgstr "OpenRC සේවා වින්‍යාස කරන්න"
#: src/modules/services-openrc/main.py:57 #: src/modules/services-openrc/main.py:57
msgid "Cannot add service {name!s} to run-level {level!s}." msgid "Cannot add service {name!s} to run-level {level!s}."
msgstr "" msgstr "ධාවන මට්ටම {level!s} වෙත සේවාව {name!s} එක් කළ නොහැක."
#: src/modules/services-openrc/main.py:59 #: src/modules/services-openrc/main.py:59
msgid "Cannot remove service {name!s} from run-level {level!s}." msgid "Cannot remove service {name!s} from run-level {level!s}."
msgstr "" msgstr "ධාවන මට්ටමේ {level!s} වෙතින් සේවාව {name!s} ඉවත් කළ නොහැක."
#: src/modules/services-openrc/main.py:61 #: src/modules/services-openrc/main.py:61
msgid "" msgid ""
"Unknown service-action <code>{arg!s}</code> for service {name!s} in run-" "Unknown service-action <code>{arg!s}</code> for service {name!s} in run-"
"level {level!s}." "level {level!s}."
msgstr "" msgstr ""
"{name!s} සේවාව සඳහා නොදන්නා සේවා-ක්‍රියාව <code>{arg!s}</code> ධාවන මට්ටමේ "
"{level!s}."
#: src/modules/services-openrc/main.py:93 #: src/modules/services-openrc/main.py:93
#: src/modules/services-systemd/main.py:59 #: src/modules/services-systemd/main.py:59
msgid "Cannot modify service" msgid "Cannot modify service"
msgstr "" msgstr "සේවාව වෙනස් කළ නොහැක"
#: src/modules/services-openrc/main.py:94 #: src/modules/services-openrc/main.py:94
msgid "" msgid ""
"<code>rc-update {arg!s}</code> call in chroot returned error code {num!s}." "<code>rc-update {arg!s}</code> call in chroot returned error code {num!s}."
msgstr "" msgstr ""
"<code>rc-update {arg!s}</code> chroot හි ඇමතුම {num!s} දෝෂ කේතය ලබා දුන්නේය."
#: src/modules/services-openrc/main.py:101 #: src/modules/services-openrc/main.py:101
msgid "Target runlevel does not exist" msgid "Target runlevel does not exist"
msgstr "" msgstr "ඉලක්ක ධාවන මට්ටම නොපවතී"
#: src/modules/services-openrc/main.py:102 #: src/modules/services-openrc/main.py:102
msgid "" msgid ""
"The path for runlevel {level!s} is <code>{path!s}</code>, which does not " "The path for runlevel {level!s} is <code>{path!s}</code>, which does not "
"exist." "exist."
msgstr "" msgstr "ධාවන මට්ටම {level!s} සඳහා මාර්ගය <code>{path!s}</code>, එය නොපවතී."
#: src/modules/services-openrc/main.py:110 #: src/modules/services-openrc/main.py:110
msgid "Target service does not exist" msgid "Target service does not exist"
msgstr "" msgstr "ඉලක්ක සේවාව නොපවතී"
#: src/modules/services-openrc/main.py:111 #: src/modules/services-openrc/main.py:111
msgid "" msgid ""
"The path for service {name!s} is <code>{path!s}</code>, which does not " "The path for service {name!s} is <code>{path!s}</code>, which does not "
"exist." "exist."
msgstr "" msgstr "සේවාව සඳහා {name!s} මාර්ගය <code>{path!s}</code>, එය නොපවතී."
#: src/modules/networkcfg/main.py:29 #: src/modules/networkcfg/main.py:29
msgid "Saving network configuration." msgid "Saving network configuration."
@ -203,7 +212,7 @@ msgstr "ඇසුරුම් ස්ථාපනය කරන්න."
#: src/modules/packages/main.py:57 #: src/modules/packages/main.py:57
#, python-format #, python-format
msgid "Processing packages (%(count)d / %(total)d)" msgid "Processing packages (%(count)d / %(total)d)"
msgstr "" msgstr "පැකේජ සැකසීම (%(count)d / %(total)d)"
#: src/modules/packages/main.py:62 #: src/modules/packages/main.py:62
#, python-format #, python-format
@ -222,41 +231,47 @@ msgstr[1] "ඇසුරුම් %(num)d ක් ඉවත් වෙමින්.
#: src/modules/packages/main.py:638 src/modules/packages/main.py:650 #: src/modules/packages/main.py:638 src/modules/packages/main.py:650
#: src/modules/packages/main.py:678 #: src/modules/packages/main.py:678
msgid "Package Manager error" msgid "Package Manager error"
msgstr "" msgstr "පැකේජ කළමනාකරු දෝෂයකි"
#: src/modules/packages/main.py:639 #: src/modules/packages/main.py:639
msgid "" msgid ""
"The package manager could not prepare updates. The command <pre>{!s}</pre> " "The package manager could not prepare updates. The command <pre>{!s}</pre> "
"returned error code {!s}." "returned error code {!s}."
msgstr "" msgstr ""
"පැකේජ කළමනාකරුට යාවත්කාලීන සකස් කිරීමට නොහැකි විය. විධානය <pre>{!s}</pre> "
"දෝෂ කේතය {!s} ලබා දුන්නේය."
#: src/modules/packages/main.py:651 #: src/modules/packages/main.py:651
msgid "" msgid ""
"The package manager could not update the system. The command <pre>{!s}</pre>" "The package manager could not update the system. The command <pre>{!s}</pre>"
" returned error code {!s}." " returned error code {!s}."
msgstr "" msgstr ""
"පැකේජ කළමනාකරුට පද්ධතිය යාවත්කාලීන කළ නොහැකි විය. විධානය <pre>{!s}</pre> දෝෂ"
" කේතය {!s} ලබා දුන්නේය."
#: src/modules/packages/main.py:679 #: src/modules/packages/main.py:679
msgid "" msgid ""
"The package manager could not make changes to the installed system. The " "The package manager could not make changes to the installed system. The "
"command <pre>{!s}</pre> returned error code {!s}." "command <pre>{!s}</pre> returned error code {!s}."
msgstr "" msgstr ""
"පැකේජ කළමනාකරුට ස්ථාපිත පද්ධතියට වෙනස්කම් සිදු කළ නොහැක. විධානය "
"<pre>{!s}</pre> දෝෂ කේතය {!s} ලබා දුන්නේය."
#: src/modules/plymouthcfg/main.py:27 #: src/modules/plymouthcfg/main.py:27
msgid "Configure Plymouth theme" msgid "Configure Plymouth theme"
msgstr "" msgstr "Plymouth තේමාව වින්‍යාස කරන්න"
#: src/modules/initcpiocfg/main.py:28 #: src/modules/initcpiocfg/main.py:28
msgid "Configuring mkinitcpio." msgid "Configuring mkinitcpio."
msgstr "" msgstr "mkinitcpio වින්‍යාස කරමින්."
#: src/modules/localecfg/main.py:30 #: src/modules/localecfg/main.py:30
msgid "Configuring locales." msgid "Configuring locales."
msgstr "" msgstr "ස්ථාන වින්‍යාස කිරීම."
#: src/modules/mount/main.py:30 #: src/modules/mount/main.py:30
msgid "Mounting partitions." msgid "Mounting partitions."
msgstr "" msgstr "කොටස් සවි කිරීම."
#: src/modules/rawfs/main.py:26 #: src/modules/rawfs/main.py:26
msgid "Installing data." msgid "Installing data."
@ -264,12 +279,12 @@ msgstr "දත්ත ස්ථාපනය වෙමින්."
#: src/modules/dummypython/main.py:35 #: src/modules/dummypython/main.py:35
msgid "Dummy python job." msgid "Dummy python job."
msgstr "" msgstr "ඩමි python වැඩසටහන."
#: src/modules/dummypython/main.py:37 src/modules/dummypython/main.py:93 #: src/modules/dummypython/main.py:37 src/modules/dummypython/main.py:93
#: src/modules/dummypython/main.py:94 #: src/modules/dummypython/main.py:94
msgid "Dummy python step {}" msgid "Dummy python step {}"
msgstr "" msgstr "ව්‍යාජ python පියවර {}"
#: src/modules/hwclock/main.py:26 #: src/modules/hwclock/main.py:26
msgid "Setting hardware clock." msgid "Setting hardware clock."
@ -277,115 +292,120 @@ msgstr "දෘඩාංග ඔරලෝසුව සැකසෙමින්."
#: src/modules/umount/main.py:31 #: src/modules/umount/main.py:31
msgid "Unmount file systems." msgid "Unmount file systems."
msgstr "" msgstr "ගොනු පද්ධති ඉවත් කරන්න."
#: src/modules/openrcdmcryptcfg/main.py:26 #: src/modules/openrcdmcryptcfg/main.py:26
msgid "Configuring OpenRC dmcrypt service." msgid "Configuring OpenRC dmcrypt service."
msgstr "" msgstr "OpenRC dmcrypt සේවාව වින්‍යාස කරමින්."
#: src/modules/services-systemd/main.py:26 #: src/modules/services-systemd/main.py:26
msgid "Configure systemd services" msgid "Configure systemd services"
msgstr "" msgstr "systemd සේවා වින්‍යාස කරන්න"
#: src/modules/services-systemd/main.py:60 #: src/modules/services-systemd/main.py:60
msgid "" msgid ""
"<code>systemctl {arg!s}</code> call in chroot returned error code {num!s}." "<code>systemctl {arg!s}</code> call in chroot returned error code {num!s}."
msgstr "" msgstr ""
"<code>systemctl {arg!s}</code> chroot වෙත ඇමතුමක් ලබා දුන් දෝෂ කේතය {num!s}."
#: src/modules/services-systemd/main.py:63 #: src/modules/services-systemd/main.py:63
#: src/modules/services-systemd/main.py:69 #: src/modules/services-systemd/main.py:69
msgid "Cannot enable systemd service <code>{name!s}</code>." msgid "Cannot enable systemd service <code>{name!s}</code>."
msgstr "" msgstr "systemd සේවාව <code>{name!s}</code> සබල කළ නොහැක."
#: src/modules/services-systemd/main.py:65 #: src/modules/services-systemd/main.py:65
msgid "Cannot enable systemd target <code>{name!s}</code>." msgid "Cannot enable systemd target <code>{name!s}</code>."
msgstr "" msgstr "systemd ඉලක්කය <code>{name!s}</code> සබල කළ නොහැක."
#: src/modules/services-systemd/main.py:67 #: src/modules/services-systemd/main.py:67
msgid "Cannot enable systemd timer <code>{name!s}</code>." msgid "Cannot enable systemd timer <code>{name!s}</code>."
msgstr "" msgstr "systemd ටයිමරය <code>{name!s}</code> සබල කළ නොහැක."
#: src/modules/services-systemd/main.py:71 #: src/modules/services-systemd/main.py:71
msgid "Cannot disable systemd target <code>{name!s}</code>." msgid "Cannot disable systemd target <code>{name!s}</code>."
msgstr "" msgstr "systemd ඉලක්කය <code>{name!s}</code> අක්‍රිය කළ නොහැක."
#: src/modules/services-systemd/main.py:73 #: src/modules/services-systemd/main.py:73
msgid "Cannot mask systemd unit <code>{name!s}</code>." msgid "Cannot mask systemd unit <code>{name!s}</code>."
msgstr "" msgstr "systemd ඒකකය <code>{name!s}</code> වසන් කළ නොහැක."
#: src/modules/services-systemd/main.py:75 #: src/modules/services-systemd/main.py:75
msgid "" msgid ""
"Unknown systemd commands <code>{command!s}</code> and " "Unknown systemd commands <code>{command!s}</code> and "
"<code>{suffix!s}</code> for unit {name!s}." "<code>{suffix!s}</code> for unit {name!s}."
msgstr "" msgstr ""
"{name!s} ඒකකය සඳහා නොදන්නා systemd විධාන <code>{command!s}</code> සහ "
"<code>{suffix!s}</code>."
#: src/modules/mkinitfs/main.py:27 #: src/modules/mkinitfs/main.py:27
msgid "Creating initramfs with mkinitfs." msgid "Creating initramfs with mkinitfs."
msgstr "" msgstr "mkinitfs සමඟ initramfs නිර්මාණය කිරීම."
#: src/modules/mkinitfs/main.py:49 #: src/modules/mkinitfs/main.py:49
msgid "Failed to run mkinitfs on the target" msgid "Failed to run mkinitfs on the target"
msgstr "" msgstr "ඉලක්කය මත mkinitfs ධාවනය කිරීමට අසමත් විය"
#: src/modules/unpackfs/main.py:34 #: src/modules/unpackfs/main.py:34
msgid "Filling up filesystems." msgid "Filling up filesystems."
msgstr "" msgstr "ගොනු පද්ධති පිරවීම."
#: src/modules/unpackfs/main.py:254 #: src/modules/unpackfs/main.py:254
msgid "rsync failed with error code {}." msgid "rsync failed with error code {}."
msgstr "" msgstr "දෝෂ කේතය {} සමඟ rsync අසාර්ථක විය."
#: src/modules/unpackfs/main.py:299 #: src/modules/unpackfs/main.py:299
msgid "Unpacking image {}/{}, file {}/{}" msgid "Unpacking image {}/{}, file {}/{}"
msgstr "" msgstr "රූපය {}/{}, ගොනුව {}/{} අසුරමින්"
#: src/modules/unpackfs/main.py:314 #: src/modules/unpackfs/main.py:314
msgid "Starting to unpack {}" msgid "Starting to unpack {}"
msgstr "" msgstr "ඉවත් කිරීමට පටන් ගනියි {}"
#: src/modules/unpackfs/main.py:323 src/modules/unpackfs/main.py:465 #: src/modules/unpackfs/main.py:323 src/modules/unpackfs/main.py:465
msgid "Failed to unpack image \"{}\"" msgid "Failed to unpack image \"{}\""
msgstr "" msgstr "\"{}\" රූපය ඉවත් කිරීමට අසමත් විය"
#: src/modules/unpackfs/main.py:430 #: src/modules/unpackfs/main.py:430
msgid "No mount point for root partition" msgid "No mount point for root partition"
msgstr "" msgstr "root කොටස සඳහා සවි කිරීමේ ලක්ෂ්‍යයක් නොමැත"
#: src/modules/unpackfs/main.py:431 #: src/modules/unpackfs/main.py:431
msgid "globalstorage does not contain a \"rootMountPoint\" key, doing nothing" msgid "globalstorage does not contain a \"rootMountPoint\" key, doing nothing"
msgstr "" msgstr "ගෝලීය ගබඩාවේ \"rootMountPoint\" යතුරක් අඩංගු නොවේ, කිසිවක් නොකරයි"
#: src/modules/unpackfs/main.py:436 #: src/modules/unpackfs/main.py:436
msgid "Bad mount point for root partition" msgid "Bad mount point for root partition"
msgstr "" msgstr "මූල කොටස සඳහා නරක සවි කිරීමේ ලක්ෂ්‍යය"
#: src/modules/unpackfs/main.py:437 #: src/modules/unpackfs/main.py:437
msgid "rootMountPoint is \"{}\", which does not exist, doing nothing" msgid "rootMountPoint is \"{}\", which does not exist, doing nothing"
msgstr "" msgstr "rootMountPoint යනු \"{}\", එය නොපවතින, කිසිවක් නොකරයි"
#: src/modules/unpackfs/main.py:453 src/modules/unpackfs/main.py:457 #: src/modules/unpackfs/main.py:453 src/modules/unpackfs/main.py:457
#: src/modules/unpackfs/main.py:463 src/modules/unpackfs/main.py:478 #: src/modules/unpackfs/main.py:463 src/modules/unpackfs/main.py:478
msgid "Bad unsquash configuration" msgid "Bad unsquash configuration"
msgstr "" msgstr "නරක unsquash වින්‍යාසය"
#: src/modules/unpackfs/main.py:454 #: src/modules/unpackfs/main.py:454
msgid "The filesystem for \"{}\" ({}) is not supported by your current kernel" msgid "The filesystem for \"{}\" ({}) is not supported by your current kernel"
msgstr "" msgstr "\"{}\" ({}) සඳහා ගොනු පද්ධතිය ඔබගේ වත්මන් කර්නලයෙන් සහය නොදක්වයි"
#: src/modules/unpackfs/main.py:458 #: src/modules/unpackfs/main.py:458
msgid "The source filesystem \"{}\" does not exist" msgid "The source filesystem \"{}\" does not exist"
msgstr "" msgstr "මූලාශ්‍ර ගොනු පද්ධතිය \"{}\" නොපවතී"
#: src/modules/unpackfs/main.py:464 #: src/modules/unpackfs/main.py:464
msgid "" msgid ""
"Failed to find unsquashfs, make sure you have the squashfs-tools package " "Failed to find unsquashfs, make sure you have the squashfs-tools package "
"installed." "installed."
msgstr "" msgstr ""
"Unsquashfs සොයා ගැනීමට අපොහොසත් විය, ඔබ squashfs-tools පැකේජය ස්ථාපනය කර ඇති"
" බවට වග බලා ගන්න."
#: src/modules/unpackfs/main.py:479 #: src/modules/unpackfs/main.py:479
msgid "The destination \"{}\" in the target system is not a directory" msgid "The destination \"{}\" in the target system is not a directory"
msgstr "" msgstr "ඉලක්ක පද්ධතියේ \"{}\" ගමනාන්තය නාමාවලියක් නොවේ"
#: src/modules/luksopenswaphookcfg/main.py:26 #: src/modules/luksopenswaphookcfg/main.py:26
msgid "Configuring encrypted swap." msgid "Configuring encrypted swap."
msgstr "" msgstr "සංකේතාත්මක swap වින්‍යාස කිරීම."

View File

@ -327,7 +327,7 @@ msgstr "Kunde inte aktivera systemd målsystem <code>{name!s}</code>."
#: src/modules/services-systemd/main.py:67 #: src/modules/services-systemd/main.py:67
msgid "Cannot enable systemd timer <code>{name!s}</code>." msgid "Cannot enable systemd timer <code>{name!s}</code>."
msgstr "" msgstr "Kunde inte aktivera systemd timer <code>{name!s}</code>."
#: src/modules/services-systemd/main.py:71 #: src/modules/services-systemd/main.py:71
msgid "Cannot disable systemd target <code>{name!s}</code>." msgid "Cannot disable systemd target <code>{name!s}</code>."
@ -407,6 +407,8 @@ msgid ""
"Failed to find unsquashfs, make sure you have the squashfs-tools package " "Failed to find unsquashfs, make sure you have the squashfs-tools package "
"installed." "installed."
msgstr "" msgstr ""
"Kunde inte hitta unsquashfs, se till att du har paketet squashfs-tools "
"installerat"
#: src/modules/unpackfs/main.py:479 #: src/modules/unpackfs/main.py:479
msgid "The destination \"{}\" in the target system is not a directory" msgid "The destination \"{}\" in the target system is not a directory"

View File

@ -322,7 +322,7 @@ msgstr "Systemd hedefi etkinleştirilemiyor <code>{name!s}</code>."
#: src/modules/services-systemd/main.py:67 #: src/modules/services-systemd/main.py:67
msgid "Cannot enable systemd timer <code>{name!s}</code>." msgid "Cannot enable systemd timer <code>{name!s}</code>."
msgstr "" msgstr "<code>{name!s}</code> sistem zamanlayıcısı etkinleştirilemiyor."
#: src/modules/services-systemd/main.py:71 #: src/modules/services-systemd/main.py:71
msgid "Cannot disable systemd target <code>{name!s}</code>." msgid "Cannot disable systemd target <code>{name!s}</code>."
@ -403,6 +403,7 @@ msgid ""
"Failed to find unsquashfs, make sure you have the squashfs-tools package " "Failed to find unsquashfs, make sure you have the squashfs-tools package "
"installed." "installed."
msgstr "" msgstr ""
"Unsquashfs bulunamadı, squashfs-tools paketinin kurulu olduğundan emin olun."
#: src/modules/unpackfs/main.py:479 #: src/modules/unpackfs/main.py:479
msgid "The destination \"{}\" in the target system is not a directory" msgid "The destination \"{}\" in the target system is not a directory"

View File

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

View File

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

View File

@ -79,13 +79,25 @@ BOOST_PYTHON_MODULE( libcalamares )
bp::scope utilsScope = utilsModule; bp::scope utilsScope = utilsModule;
Q_UNUSED( utilsScope ) Q_UNUSED( utilsScope )
// .. Logging functions
bp::def( bp::def(
"debug", &CalamaresPython::debug, bp::args( "s" ), "Writes the given string to the Calamares debug stream." ); "debug", &CalamaresPython::debug, bp::args( "s" ), "Writes the given string to the Calamares debug stream." );
bp::def( "warning", bp::def( "warning",
&CalamaresPython::warning, &CalamaresPython::warning,
bp::args( "s" ), bp::args( "s" ),
"Writes the given string to the Calamares warning stream." ); "Writes the given string to the Calamares warning stream." );
bp::def( "warn",
&CalamaresPython::warning,
bp::args( "s" ),
"Writes the given string to the Calamares warning stream." );
bp::def(
"error", &CalamaresPython::warning, bp::args( "s" ), "Writes the given string to the Calamares error stream." );
// .. YAML functions
bp::def( "load_yaml", &CalamaresPython::load_yaml, bp::args( "path" ), "Loads YAML from a file." );
// .. Filesystem functions
bp::def( "mount", bp::def( "mount",
&CalamaresPython::mount, &CalamaresPython::mount,
mount_overloads( bp::args( "device_path", "mount_point", "filesystem_name", "options" ), mount_overloads( bp::args( "device_path", "mount_point", "filesystem_name", "options" ),
@ -94,6 +106,8 @@ BOOST_PYTHON_MODULE( libcalamares )
"-1 = QProcess crash\n" "-1 = QProcess crash\n"
"-2 = QProcess cannot start\n" "-2 = QProcess cannot start\n"
"-3 = bad arguments" ) ); "-3 = bad arguments" ) );
// .. Process functions
bp::def( bp::def(
"target_env_call", "target_env_call",
static_cast< int ( * )( const std::string&, const std::string&, int ) >( &CalamaresPython::target_env_call ), static_cast< int ( * )( const std::string&, const std::string&, int ) >( &CalamaresPython::target_env_call ),
@ -152,6 +166,7 @@ BOOST_PYTHON_MODULE( libcalamares )
host_env_process_output_overloads( bp::args( "command", "callback", "stdin", "timeout" ), host_env_process_output_overloads( bp::args( "command", "callback", "stdin", "timeout" ),
"Runs the specified command in the host system." ) ); "Runs the specified command in the host system." ) );
// .. String functions
bp::def( "obscure", bp::def( "obscure",
&CalamaresPython::obscure, &CalamaresPython::obscure,
bp::args( "s" ), bp::args( "s" ),
@ -160,7 +175,7 @@ BOOST_PYTHON_MODULE( libcalamares )
"Applying the function to a string obscured by this function will result " "Applying the function to a string obscured by this function will result "
"in the original string." ); "in the original string." );
// .. Translation functions
bp::def( "gettext_languages", bp::def( "gettext_languages",
&CalamaresPython::gettext_languages, &CalamaresPython::gettext_languages,
"Returns list of languages (most to least-specific) for gettext." ); "Returns list of languages (most to least-specific) for gettext." );

View File

@ -19,6 +19,7 @@
#include "utils/RAII.h" #include "utils/RAII.h"
#include "utils/Runner.h" #include "utils/Runner.h"
#include "utils/String.h" #include "utils/String.h"
#include "utils/Yaml.h"
#include <QCoreApplication> #include <QCoreApplication>
#include <QDir> #include <QDir>
@ -139,19 +140,44 @@ check_target_env_output( const bp::list& args, const std::string& stdin, int tim
} }
static const char output_prefix[] = "[PYTHON JOB]:"; static const char output_prefix[] = "[PYTHON JOB]:";
static inline void
log_action( unsigned int level, const std::string& s )
{
Logger::CDebug( level ) << output_prefix << QString::fromStdString( s );
}
void void
debug( const std::string& s ) debug( const std::string& s )
{ {
Logger::CDebug( Logger::LOGDEBUG ) << output_prefix << QString::fromStdString( s ); log_action( Logger::LOGDEBUG, s );
} }
void void
warning( const std::string& s ) warning( const std::string& s )
{ {
Logger::CDebug( Logger::LOGWARNING ) << output_prefix << QString::fromStdString( s ); log_action( Logger::LOGWARNING, s );
} }
void
error( const std::string& s )
{
log_action( Logger::LOGERROR, s );
}
boost::python::dict
load_yaml( const std::string& path )
{
const QString filePath = QString::fromStdString( path );
bool ok = false;
auto map = CalamaresUtils::loadYaml( filePath, &ok );
if ( !ok )
{
cWarning() << "Loading YAML from" << filePath << "failed.";
}
return variantMapToPyDict( map );
}
PythonJobInterface::PythonJobInterface( Calamares::PythonJob* parent ) PythonJobInterface::PythonJobInterface( Calamares::PythonJob* parent )
: m_parent( parent ) : m_parent( parent )
{ {

View File

@ -60,6 +60,12 @@ boost::python::list gettext_languages();
void debug( const std::string& s ); void debug( const std::string& s );
void warning( const std::string& s ); void warning( const std::string& s );
void error( const std::string& s );
/** @brief Loads YAML and returns (nested) dicts representing it
*
*/
boost::python::dict load_yaml( const std::string& path );
class PythonJobInterface class PythonJobInterface
{ {

View File

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

View File

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

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

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

View File

@ -73,6 +73,69 @@ def get_bootloader_entry_name():
return branding["bootloaderEntryName"] return branding["bootloaderEntryName"]
def get_kernel_line(kernel_type):
"""
Passes 'kernel_line' to other routine based on configuration file.
:param kernel_type:
:return:
"""
if kernel_type == "fallback":
if "fallbackKernelLine" in libcalamares.job.configuration:
return libcalamares.job.configuration["fallbackKernelLine"]
else:
return " (fallback)"
else:
if "kernelLine" in libcalamares.job.configuration:
return libcalamares.job.configuration["kernelLine"]
else:
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(installation_root_path, efi_dir, uuid, entry, kernel, kernel_type, kernel_version): def create_systemd_boot_conf(installation_root_path, efi_dir, uuid, entry, kernel, kernel_type, kernel_version):
""" """
Creates systemd-boot configuration files based on given parameters. Creates systemd-boot configuration files based on given parameters.
@ -114,11 +177,24 @@ def create_systemd_boot_conf(installation_root_path, efi_dir, uuid, entry, kerne
"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)
@ -332,6 +408,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.
@ -339,6 +485,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)")
installation_root_path = libcalamares.globalstorage.value("rootMountPoint") installation_root_path = libcalamares.globalstorage.value("rootMountPoint")
@ -351,11 +503,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(
@ -374,36 +522,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):

View File

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

View File

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

View File

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

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,17 @@
# 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.
#
# btrfs_swap options are used when a swapfile is chosen with a btrfs root
# the options are applied to the subvolume which holds the swap partition
mountOptions: mountOptions:
default: defaults,noatime default: defaults,noatime
btrfs: defaults,noatime,space_cache,autodefrag,compress=zstd btrfs: defaults,noatime,autodefrag,compress=zstd
btrfs_swap: defaults,noatime
# 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,

View File

@ -22,6 +22,7 @@ properties:
xfs: { type: string } xfs: { type: string }
swap: { type: string } swap: { type: string }
btrfs: { type: string } btrfs: { type: string }
btrfs_swap: { type: string }
efiMountOptions: { type: string } efiMountOptions: { type: string }
crypttabOptions: { type: string } crypttabOptions: { type: string }
required: [ mountOptions ] required: [ mountOptions ]

View File

@ -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)
@ -236,6 +236,10 @@ class FstabGenerator(object):
libcalamares.utils.debug("Ignoring foreign swap {!s} {!s}".format(disk_name, partition.get("uuid", None))) libcalamares.utils.debug("Ignoring foreign swap {!s} {!s}".format(disk_name, partition.get("uuid", None)))
return None return None
# If this is btrfs subvol a dedicated to a swapfile, use different options than a normal btrfs subvol
if filesystem == "btrfs" and partition["subvol"] == "/@swap":
options = self.get_mount_options("btrfs_swap", mount_point)
else:
options = self.get_mount_options(filesystem, mount_point) options = self.get_mount_options(filesystem, mount_point)
if is_ssd: if is_ssd:

View File

@ -59,6 +59,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.
@ -95,6 +121,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"):
@ -139,7 +167,15 @@ def modify_grub_default(partitions, root_mount_point, distributor):
f"root=/dev/mapper/{partition['luksMapperName']}" f"root=/dev/mapper/{partition['luksMapperName']}"
] ]
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)

View File

@ -158,6 +158,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
@ -182,6 +183,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
@ -209,6 +213,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 != "":
hooks.extend(["resume"]) hooks.extend(["resume"])
if encrypt_hook and openswap_hook: if encrypt_hook and openswap_hook:

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View File

@ -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.")
@ -50,7 +62,8 @@ def get_btrfs_subvolumes(partitions):
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
m is not None and m != '/']
btrfs_subvolumes = list(filter(lambda s: s["mountPoint"] not in non_root_partition_mounts, btrfs_subvolumes)) 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.
@ -61,6 +74,70 @@ def get_btrfs_subvolumes(partitions):
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,6 +173,9 @@ 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 fstype == "zfs":
mount_zfs(root_mount_point, partition)
else: # fstype == "zfs"
if libcalamares.utils.mount(device, if libcalamares.utils.mount(device,
mount_point, mount_point,
fstype, fstype,
@ -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"]
@ -161,8 +243,11 @@ def run():
# 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"])
try:
for partition in mountable_partitions: for partition in mountable_partitions:
mount_partition(root_mount_point, partition, 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)

View File

@ -379,6 +379,7 @@ class PMPacman(PackageManager):
def __init__(self): def __init__(self):
import re import re
progress_match = re.compile("^\\((\\d+)/(\\d+)\\)") progress_match = re.compile("^\\((\\d+)/(\\d+)\\)")
def line_cb(line): def line_cb(line):
if line.startswith(":: "): if line.startswith(":: "):
self.in_package_changes = "package changes" in line self.in_package_changes = "package changes" in line
@ -396,30 +397,79 @@ class PMPacman(PackageManager):
self.in_package_changes = False self.in_package_changes = False
self.line_cb = line_cb self.line_cb = line_cb
pacman = libcalamares.job.configuration.get("pacman", None)
if pacman is None:
pacman = dict()
if type(pacman) is not dict:
libcalamares.utils.warning("Job configuration *pacman* will be ignored.")
pacman = dict()
self.pacman_num_retries = pacman.get("num_retries", 0)
self.pacman_disable_timeout = pacman.get("disable_download_timeout", False)
self.pacman_needed_only = pacman.get("needed_only", False)
def reset_progress(self): def reset_progress(self):
self.in_package_changes = False self.in_package_changes = False
# These are globals # These are globals
self.progress_fraction = (completed_packages * 1.0 / total_packages) self.progress_fraction = (completed_packages * 1.0 / total_packages)
def install(self, pkgs, from_local=False): def run_pacman(self, command, callback=False):
if from_local: """
pacman_flags = "-U" Call pacman in a loop until it is successful or the number of retries is exceeded
:param command: The pacman command to run
:param callback: An optional boolean that indicates if this pacman run should use the callback
:return:
"""
pacman_count = 0
while pacman_count <= self.pacman_num_retries:
pacman_count += 1
try:
if callback is True:
libcalamares.utils.target_env_process_output(command, self.line_cb)
else: else:
pacman_flags = "-S" libcalamares.utils.target_env_process_output(command)
return
except subprocess.CalledProcessError:
if pacman_count <= self.pacman_num_retries:
pass
else:
raise
def install(self, pkgs, from_local=False):
command = ["pacman"]
if from_local:
command.append("-U")
else:
command.append("-S")
command.append("--noconfirm")
if self.pacman_needed_only is True:
command.append("--needed")
if self.pacman_disable_timeout is True:
command.append("--disable-download-timeout")
command += pkgs
self.reset_progress() self.reset_progress()
libcalamares.utils.target_env_process_output(["pacman", pacman_flags, self.run_pacman(command, True)
"--noconfirm"] + pkgs, self.line_cb)
def remove(self, pkgs): def remove(self, pkgs):
self.reset_progress() self.reset_progress()
libcalamares.utils.target_env_process_output(["pacman", "-Rs", "--noconfirm"] + pkgs, self.line_cb) self.run_pacman(["pacman", "-Rs", "--noconfirm"] + pkgs, True)
def update_db(self): def update_db(self):
check_target_env_call(["pacman", "-Sy"]) self.run_pacman(["pacman", "-Sy"])
def update_system(self): def update_system(self):
check_target_env_call(["pacman", "-Su", "--noconfirm"]) command = ["pacman", "-Su", "--noconfirm"]
if self.pacman_disable_timeout is True:
command.append("--disable-download-timeout")
self.run_pacman(command)
class PMPamac(PackageManager): class PMPamac(PackageManager):

View File

@ -62,6 +62,23 @@ skip_if_no_internet: false
update_db: true update_db: true
update_system: false update_system: false
# pacman specific options
#
# *num_retries* should be a positive integer which specifies the
# number of times the call to pacman will be retried in the event of a
# failure. If it is missing, it will be set to 0.
#
# *disable_download_timeout* is a boolean that, when true, includes
# the flag --disable-download-timeout on calls to pacman. When missing,
# false is assumed.
#
# *needed_only* is a boolean that includes the pacman argument --needed
# when set to true. If missing, false is assumed.
pacman:
num_retries: 0
disable_download_timeout: false
needed_only: false
# #
# List of maps with package operations such as install or remove. # List of maps with package operations such as install or remove.
# Distro developers can provide a list of packages to remove # Distro developers can provide a list of packages to remove

View File

@ -26,6 +26,14 @@ properties:
update_system: { type: boolean, default: false } update_system: { type: boolean, default: false }
skip_if_no_internet: { type: boolean, default: false } skip_if_no_internet: { type: boolean, default: false }
pacman:
additionalProperties: false
type: object
properties:
num_retries: { type: integer, default: 0 }
disable_download_timeout: { type: boolean, default: false }
needed_only: { type: boolean, default: false }
operations: operations:
type: array type: array
items: items:

View File

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

View File

@ -1,7 +1,6 @@
# SPDX-FileCopyrightText: no # SPDX-FileCopyrightText: no
# SPDX-License-Identifier: CC0-1.0 # SPDX-License-Identifier: CC0-1.0
backend: dummy backend: dummy
rootMountPoint: /tmp/mount
operations: operations:
- install: - install:
- pre-script: touch /tmp/foo - pre-script: touch /tmp/foo

View File

@ -0,0 +1,42 @@
# SPDX-FileCopyrightText: no
# SPDX-License-Identifier: CC0-1.0
#
# We have tests to load (some) of the package-managers specifically, to
# test their configuration code and implementation. Those tests conventionally
# live in Python files here in the tests/ directory. Add them.
# Pacman (Arch) tests
set(_pm pacman)
add_test(
NAME configure-packages-${_pm}
COMMAND env PYTHONPATH=.: python3 ${CMAKE_CURRENT_LIST_DIR}/test-pm-${_pm}.py
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
)
add_test(
NAME configure-packages-${_pm}-ops-1
COMMAND env PYTHONPATH=.: python3 ${CMAKE_CURRENT_LIST_DIR}/test-pm-${_pm}.py ${CMAKE_CURRENT_LIST_DIR}/pm-pacman-1.yaml 4 1 1
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
)
add_test(
NAME configure-packages-${_pm}-ops-2
COMMAND env PYTHONPATH=.: python3 ${CMAKE_CURRENT_LIST_DIR}/test-pm-${_pm}.py ${CMAKE_CURRENT_LIST_DIR}/pm-pacman-2.yaml 3 0 0
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
)
if ( BUILD_TESTING AND BUILD_SCHEMA_TESTING AND PYTHONINTERP_FOUND AND PYTHON_EXECUTABLE )
set( _module packages )
set( _schema_file "${CMAKE_CURRENT_SOURCE_DIR}/${_module}/${_module}.schema.yaml" )
message(STATUS "Schema ${_schema_file}")
foreach( _cf pm-pacman-1.yaml pm-pacman-2.yaml )
set( _conf_file "${CMAKE_CURRENT_SOURCE_DIR}/${_module}/tests/${_cf}" )
if ( EXISTS "${_schema_file}" AND EXISTS "${_conf_file}" )
add_test(
NAME validate-packages-${_cf}
COMMAND ${PYTHON_EXECUTABLE} "${CMAKE_SOURCE_DIR}/ci/configvalidator.py" "${_schema_file}" "${_conf_file}"
)
else()
message(FATAL_ERROR "Missing ${_conf_file}")
endif()
endforeach()
endif()

View File

@ -0,0 +1,10 @@
# SPDX-FileCopyrightText: no
# SPDX-License-Identifier: CC0-1.0
backend: pacman
operations: []
pacman:
num_retries: 4
disable_download_timeout: yes
needed_only: true

View File

@ -0,0 +1,9 @@
# SPDX-FileCopyrightText: no
# SPDX-License-Identifier: CC0-1.0
backend: pacman
operations: []
# Leave some things unspecified
pacman:
num_retries: 3

View File

@ -0,0 +1,36 @@
# 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.packages import main
# .. we don't have a job in this test, so fake one
class Job(object):
def __init__(self, filename):
self.configuration = libcalamares.utils.load_yaml(filename) if filename is not None else dict()
import sys
if len(sys.argv) > 4:
filename = sys.argv[1]
retry = int(sys.argv[2])
timeout = bool(int(sys.argv[3]))
needed = bool(int(sys.argv[4]))
else:
filename = None
retry = 0
timeout = False
needed = False
libcalamares.utils.warning("Expecting {!s} retry={!s} timeout={!s} needed={!s}".format(filename, retry, timeout, needed))
# Specific PM test
libcalamares.job = Job(filename)
p = main.PMPacman()
assert p.pacman_num_retries == retry, "{!r} vs {!r}".format(p.pacman_num_retries, retry)
assert p.pacman_disable_timeout == timeout, "{!r} vs {!r}".format(p.pacman_disable_timeout, timeout)
assert p.pacman_needed_only == needed, "{!r} vs {!r}".format(p.pacman_needed_only, needed)

View File

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

View File

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

View File

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

View File

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

View File

@ -215,6 +215,12 @@ getPVGroups( const QString& deviceName )
* meant **only** for debugging and is not displayed to the user, * meant **only** for debugging and is not displayed to the user,
* which is why no translation is applied. * which is why no translation is applied.
* *
* The MessageAndPath class stores a C-style pointer to a character
* array -- from QT_TRANSLATE_NOOP() -- and a path to substitute into it.
*
* When the tryX() functions return an "empty string", it is an
* empty MessageAndPath which acts like an empty string (in particular,
* isEmpty() is true).
*/ */
class MessageAndPath class MessageAndPath
@ -237,8 +243,14 @@ public:
} }
private: private:
#if ( QT_VERSION < QT_VERSION_CHECK( 5, 15, 0 ) )
// TODO: 3.3 remove because newer Qt does support constness
const char* m_message = nullptr;
QString m_path;
#else
const char* const m_message = nullptr; const char* const m_message = nullptr;
QString const m_path; QString const m_path;
#endif
}; };
STATICTEST inline QDebug& STATICTEST inline QDebug&

View File

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

View File

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

View File

@ -19,6 +19,14 @@ QTEST_GUILESS_MAIN( ClearMountsJobTests )
/* Not exactly public API */ /* Not exactly public API */
QStringList getPartitionsForDevice( const QString& deviceName ); QStringList getPartitionsForDevice( const QString& deviceName );
/* At one point, the partitions-list was read from /proc/partitions by
* running awk and grep, as below. Check that the current implementation
* matches that crufty one.
*
* Update 2021-11-02: the newer implementation prepends /dev/ to the
* names of the partitions, for simplicity elsewhere, so that needs
* to be added in to the awk(1) program, too.
*/
QStringList QStringList
getPartitionsForDevice_other( const QString& deviceName ) getPartitionsForDevice_other( const QString& deviceName )
{ {
@ -26,7 +34,7 @@ getPartitionsForDevice_other( const QString& deviceName )
process.setProgram( "sh" ); process.setProgram( "sh" );
process.setArguments( process.setArguments(
{ "-c", { "-c",
QString( "echo $(awk '{print $4}' /proc/partitions | sed -e '/name/d' -e '/^$/d' -e '/[1-9]/!d' | grep %1)" ) QString( "echo $(awk '{print \"/dev/\"$4}' /proc/partitions | sed -e '/name/d' -e '/^$/d' -e '/[1-9]/!d' | grep %1)" )
.arg( deviceName ) } ); .arg( deviceName ) } );
process.start(); process.start();
process.waitForFinished(); process.waitForFinished();

View File

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

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
)

21
src/modules/zfs/README.md Normal file
View File

@ -0,0 +1,21 @@
## 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
* The zfs kernel module must be loaded prior to the partition module running
* One way to achieve this is by running `modprobe zfs`
* Support for zfs in the partition module is conditional on the zfs module being enabled
* The config for the default pools and datasets is configured and described in modules/zfs.conf
* 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

45
src/modules/zfs/zfs.conf Normal file
View File

@ -0,0 +1,45 @@
# 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
#
# Encryption options should generally not be added here since they will be added by
# selecting the encrypt disk option in the partition module
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 /
#
# This default configuration is commonly used when support for booting more than one distro
# out of a single zpool is desired. If you decide to keep this default configuration,
# you should replace "distro" with an identifier that represents your distro.
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 ]