23 Commits

Author SHA1 Message Date
379f38bfe9 basestrap: add copy_pacconf property 2024-01-02 18:47:43 +01:00
25acdeceba remove old service modules from build 2024-01-02 16:51:02 +01:00
5462411d8a basestrap: create dirs sequentially 2024-01-02 13:38:36 +01:00
b7f606cba1 services-artix: add debug info 2024-01-01 22:49:09 +01:00
d51ba1e8df services-artix: fix alias 2024-01-01 21:43:16 +01:00
10dc285ed2 services-artixc: add alias and target service properties 2024-01-01 21:40:03 +01:00
916f1c2fae services-artix: small refactor 2024-01-01 21:18:53 +01:00
0d9923ed0b services-artix: simplify greatly 2024-01-01 20:55:00 +01:00
8947b568a7 rm 2024-01-01 03:07:22 +01:00
3dc5e111bb add packagechooserxq; experimental module since packagechooserq somehow doesn't work in non legacy mode and no GS written 2024-01-01 02:30:46 +01:00
c6e2cacd51 packagechooserq: fix qt5 qml 2024-01-01 01:37:19 +01:00
3d3e73f015 cleaning 2023-12-31 21:27:43 +01:00
fe012233e3 fix it 2023-12-31 18:29:27 +01:00
e55d467f12 cmake: include packagechooserq 2023-12-31 18:28:15 +01:00
c99c53714f update packagechooserq 2023-12-31 18:23:06 +01:00
9e10e21381 no test 2023-12-31 17:06:02 +01:00
c6884e1044 remove packagechooser tests for now 2023-12-31 17:03:03 +01:00
fc2b4c54a5 pkgch: reenable test 2023-12-31 03:04:41 +01:00
c04875fb6a try fix build 2023-12-31 02:42:08 +01:00
bac0c002d8 packagechooser: take out test 2023-12-31 00:59:48 +01:00
999e45c64a add packagechooser 2023-12-31 00:41:15 +01:00
a1012b9a82 fix 2023-12-30 23:33:07 +01:00
39427ffc4c add artix modules 2023-12-30 16:21:50 +01:00
129 changed files with 5048 additions and 3695 deletions

View File

@@ -56,6 +56,29 @@ project(calamares-extensions
set( CMAKE_CXX_STANDARD 17 )
set( CMAKE_CXX_STANDARD_REQUIRED ON )
if(WITH_QT6)
set(kfname "KF6")
set(KF_VERSION 5.240) # KDE Neon weirdness
else()
message(STATUS "Building Calamares with Qt5")
set(kfname "KF5")
set(KF_VERSION 5.78)
# API that was deprecated before Qt 5.15 causes a compile error
add_compile_definitions(QT_DISABLE_DEPRECATED_BEFORE=0x050f00)
endif()
include( FeatureSummary )
find_package(${kfname}CoreAddons ${KF_VERSION} QUIET)
set_package_properties(
${kfname}CoreAddons
PROPERTIES
TYPE REQUIRED
DESCRIPTION "KDE Framework CoreAddons"
URL "https://api.kde.org/frameworks/"
PURPOSE "Essential Framework for AboutData and Macros"
)
# On developer's machine, the user package registry breaks
# consumers by loading the developer's config from a build
# directory (which doesn't have the rest of the config
@@ -96,18 +119,19 @@ include( CTest )
# Typically you would use only one branding, since that's
# the (single) branding for your distro.
#
calamares_add_branding_subdirectory( branding/default NAME default )
calamares_add_branding_subdirectory( branding/default-mobile NAME default-mobile )
calamares_add_branding_subdirectory( branding/fancy NAME fancy )
# calamares_add_branding_subdirectory( branding/default NAME default )
# calamares_add_branding_subdirectory( branding/default-mobile NAME default-mobile )
# calamares_add_branding_subdirectory( branding/fancy NAME fancy )
# This one has files in subdirectories
calamares_add_branding_subdirectory( branding/samegame NAME samegame SUBDIRECTORIES img )
# calamares_add_branding_subdirectory( branding/samegame NAME samegame SUBDIRECTORIES img )
# KaOS branding, with translations, note we can *NAME* something
# different from the source directory it lives in; this will be installed
# to a directory called *NAME* though -- and the `branding.desc` must
# have a *componentName* that matches this *NAME*.
calamares_add_branding_subdirectory( branding/kaos_branding NAME kaos )
# calamares_add_branding_subdirectory( branding/kaos_branding NAME kaos )
calamares_add_branding_subdirectory( branding/artix NAME artix )
### MODULES
#
@@ -115,13 +139,22 @@ calamares_add_branding_subdirectory( branding/kaos_branding NAME kaos )
#
set(LIST_SKIPPED_MODULES "")
calamares_add_module_subdirectory( modules/freebsddisk LIST_SKIPPED_MODULES ) # C++ viewmodule
calamares_add_module_subdirectory( modules/mobile LIST_SKIPPED_MODULES )
calamares_add_module_subdirectory( modules/os-freebsd LIST_SKIPPED_MODULES )
calamares_add_module_subdirectory( modules/os-nixos LIST_SKIPPED_MODULES )
calamares_add_module_subdirectory( modules/refind LIST_SKIPPED_MODULES )
calamares_add_module_subdirectory( modules/slowpython LIST_SKIPPED_MODULES ) # Python job
calamares_add_module_subdirectory( modules/unpackfsc LIST_SKIPPED_MODULES )
# calamares_add_module_subdirectory( modules/freebsddisk LIST_SKIPPED_MODULES ) # C++ viewmodule
# calamares_add_module_subdirectory( modules/mobile LIST_SKIPPED_MODULES )
# calamares_add_module_subdirectory( modules/os-freebsd LIST_SKIPPED_MODULES )
# calamares_add_module_subdirectory( modules/os-nixos LIST_SKIPPED_MODULES )
# calamares_add_module_subdirectory( modules/refind LIST_SKIPPED_MODULES )
# calamares_add_module_subdirectory( modules/slowpython LIST_SKIPPED_MODULES ) # Python job
# calamares_add_module_subdirectory( modules/unpackfsc LIST_SKIPPED_MODULES )
calamares_add_module_subdirectory( modules/basestrap LIST_SKIPPED_MODULES )
calamares_add_module_subdirectory( modules/services-artix LIST_SKIPPED_MODULES )
# calamares_add_module_subdirectory( modules/services-runit LIST_SKIPPED_MODULES )
# calamares_add_module_subdirectory( modules/services-dinit LIST_SKIPPED_MODULES )
# calamares_add_module_subdirectory( modules/services-s6 LIST_SKIPPED_MODULES )
calamares_add_module_subdirectory( modules/postcfg LIST_SKIPPED_MODULES )
calamares_add_module_subdirectory( modules/packagechooser LIST_SKIPPED_MODULES )
calamares_add_module_subdirectory( modules/packagechooserq LIST_SKIPPED_MODULES )
message(STATUS "Calamares extensions ${CALAMARES_EXTENSIONS_VERSION} for Calamares version ${Calamares_VERSION}")

BIN
branding/artix/artix.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

BIN
branding/artix/banner.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

View File

@@ -0,0 +1,239 @@
# SPDX-FileCopyrightText: no
# SPDX-License-Identifier: CC0-1.0
#
# Product branding information. This influences some global
# user-visible aspects of Calamares, such as the product
# name, window behavior, and the slideshow during installation.
#
# Additional styling can be done using the stylesheet.qss
# file, also in the branding directory.
---
componentName: artix
### WELCOME / OVERALL WORDING
#
# These settings affect some overall phrasing and looks,
# which are most visible in the welcome page.
# This selects between different welcome texts. When false, uses
# the traditional "Welcome to the %1 installer.", and when true,
# uses "Welcome to the Calamares installer for %1." This allows
# to distinguish this installer from other installers for the
# same distribution.
welcomeStyleCalamares: false
# Should the welcome image (productWelcome, below) be scaled
# up beyond its natural size? If false, the image does not grow
# with the window but remains the same size throughout (this
# may have surprising effects on HiDPI monitors).
welcomeExpandingLogo: true
### WINDOW CONFIGURATION
#
# The settings here affect the placement of the Calamares
# window through hints to the window manager and initial
# sizing of the Calamares window.
# Size and expansion policy for Calamares.
# - "normal" or unset, expand as needed, use *windowSize*
# - "fullscreen", start as large as possible, ignore *windowSize*
# - "noexpand", don't expand automatically, use *windowSize*
windowExpanding: normal
# Size of Calamares window, expressed as w,h. Both w and h
# may be either pixels (suffix px) or font-units (suffix em).
# e.g. "800px,600px"
# "60em,480px"
# This setting is ignored if "fullscreen" is selected for
# *windowExpanding*, above. If not set, use constants defined
# in CalamaresUtilsGui, 800x520.
windowSize: 800px,520px
# Placement of Calamares window. Either "center" or "free".
# Whether "center" actually works does depend on the window
# manager in use (and only makes sense if you're not using
# *windowExpanding* set to "fullscreen").
windowPlacement: center
### PANELS CONFIGURATION
#
# Calamares has a main content area, and two panels (navigation
# and progress / sidebar). The panels can be controlled individually,
# or switched off. If both panels are switched off, the layout of
# the main content area loses its margins, on the assumption that
# you're doing something special.
# Kind of sidebar (panel on the left, showing progress).
# - "widget" or unset, use traditional sidebar (logo, items)
# - "none", hide it entirely
# - "qml", use calamares-sidebar.qml from branding folder
# In addition, you **may** specify a side, separated by a comma,
# from the kind. Valid sides are:
# - "left" (if not specified, uses this)
# - "right"
# - "top"
# - "bottom"
# For instance, "widget,right" is valid; so is "qml", which defaults
# to putting the sidebar on the left. Also valid is "qml,top".
# While "widget,top" is valid, the widgets code is **not** flexible
# and results will be terrible.
sidebar: widget
# Kind of navigation (button panel on the bottom).
# - "widget" or unset, use traditional navigation
# - "none", hide it entirely
# - "qml", use calamares-navigation.qml from branding folder
# In addition, you **may** specify a side, separated by a comma,
# from the kind. The same sides are valid as for *sidebar*,
# except the default is *bottom*.
navigation: widget
### STRINGS, IMAGES AND COLORS
#
# This section contains the "branding proper" of names
# and images, rather than global-look settings.
# These are strings shown to the user in the user interface.
# There is no provision for translating them -- since they
# are names, the string is included as-is.
#
# The four Url strings are the Urls used by the buttons in
# the welcome screen, and are not shown to the user. Clicking
# on the "Support" button, for instance, opens the link supportUrl.
# If a Url is empty, the corresponding button is not shown.
#
# bootloaderEntryName is how this installation / distro is named
# in the boot loader (e.g. in the GRUB menu).
#
# These strings support substitution from /etc/os-release
# if KDE Frameworks 5.58 are available at build-time. When
# enabled, ${varname} is replaced by the equivalent value
# from os-release. All the supported var-names are in all-caps,
# and are listed on the FreeDesktop.org site,
# https://www.freedesktop.org/software/systemd/man/os-release.html
# Note that ANSI_COLOR and CPE_NAME don't make sense here, and
# are not supported (the rest are). Remember to quote the string
# if it contains substitutions, or you'll get YAML exceptions.
#
# The *Url* entries are used on the welcome page, and they
# are visible as buttons there if the corresponding *show* keys
# are set to "true" (they can also be overridden).
strings:
productName: Artix Linux
shortProductName: Artix
version: rolling
shortVersion: rolling
versionedName: Artix Linux "rolling"
shortVersionedName: Artix rolling
bootloaderEntryName: Artix
productUrl: https://www.artixlinux.org/
supportUrl: https://github.com/calamares/calamares/issues
knownIssuesUrl: https://calamares.io/about/
releaseNotesUrl: https://calamares.io/about/
# donateUrl: https://kde.org/community/donations/index.php
# These images are loaded from the branding module directory.
#
# productBanner is an optional image, which if present, will be shown
# on the welcome page of the application, above the welcome text.
# It is intended to have a width much greater than height.
# It is displayed at 64px height (also on HiDPI).
# Recommended size is 64px tall, and up to 460px wide.
# productIcon is used as the window icon, and will (usually) be used
# by the window manager to represent the application. This image
# should be square, and may be displayed by the window manager
# as small as 16x16 (but possibly larger).
# productLogo is used as the logo at the top of the left-hand column
# which shows the steps to be taken. The image should be square,
# and is displayed at 80x80 pixels (also on HiDPI).
# productWallpaper is an optional image, which if present, will replace
# the normal solid background on every page of the application.
# It can be any size and proportion,
# and will be tiled to fit the entire window.
# For a non-tiled wallpaper, the size should be the same as
# the overall window, see *windowSize* above (800x520).
# productWelcome is shown on the welcome page of the application in
# the middle of the window, below the welcome text. It can be
# any size and proportion, and will be scaled to fit inside
# the window. Use `welcomeExpandingLogo` to make it non-scaled.
# Recommended size is 320x150.
#
# These filenames can also use substitutions from os-release (see above).
images:
productBanner: "banner.png"
productIcon: "logo.png"
productLogo: "logo.png"
# productWallpaper: "wallpaper.png"
productWelcome: "languages.png"
# Colors for text and background components.
#
# - SidebarBackground is the background of the sidebar
# - SidebarText is the (foreground) text color
# - SidebarBackgroundCurrent sets the background of the current step.
# Optional, and defaults to the application palette.
# - SidebarTextCurrent is the text color of the current step.
#
# These colors can **also** be set through the stylesheet, if the
# branding component also ships a stylesheet.qss. Then they are
# the corresponding CSS attributes of #sidebarApp.
style:
SidebarBackground: "#292F34"
SidebarText: "#FFFFFF"
SidebarTextCurrent: "#292F34"
SidebarBackgroundCurrent: "#16a3f5"
### SLIDESHOW
#
# The slideshow is displayed during execution steps (e.g. when the
# installer is actually writing to disk and doing other slow things).
# The slideshow can be a QML file (recommended) which can display
# arbitrary things -- text, images, animations, or even play a game --
# during the execution step. The QML **is** abruptly stopped when the
# execution step is done, though, so maybe a game isn't a great idea.
#
# The slideshow can also be a sequence of images (not recommended unless
# you don't want QML at all in your Calamares). The images are displayed
# at a rate of 1 every 2 seconds during the execution step.
#
# To configure a QML file, list a single filename:
# slideshow: "show.qml"
# To configure images, like the filenames (here, as an inline list):
# slideshow: [ "/etc/calamares/slideshow/0.png", "/etc/logo.png" ]
slideshow: "show.qml"
# There are two available APIs for a QML slideshow:
# - 1 (the default) loads the entire slideshow when the installation-
# slideshow page is shown and starts the QML then. The QML
# is never stopped (after installation is done, times etc.
# continue to fire).
# - 2 loads the slideshow on startup and calls onActivate() and
# onLeave() in the root object. After the installation is done,
# the show is stopped (first by calling onLeave(), then destroying
# the QML components).
#
# An image slideshow does not need to have the API defined.
slideshowAPI: 2
# These options are to customize online uploading of logs to pastebins:
# - type : Defines the kind of pastebin service to be used. Currently
# it accepts two values:
# - none : disables the pastebin functionality
# - fiche : use fiche pastebin server
# - url : Defines the address of pastebin service to be used.
# Takes string as input. Important bits are the host and port,
# the scheme is not used.
# - sizeLimit : Defines maximum size limit (in KiB) of log file to be pasted.
# The option must be set, to have the log option work.
# Takes integer as input. If < 0, no limit will be forced,
# else only last (approximately) 'n' KiB of log file will be pasted.
# Please note that upload size may be slightly over the limit (due
# to last minute logging), so provide a suitable value.
uploadServer :
type : "fiche"
url : "http://termbin.com:9999"
sizeLimit : -1

BIN
branding/artix/browsers.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 399 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 818 KiB

BIN
branding/artix/desktops.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 151 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

View File

@@ -0,0 +1,2 @@
SPDX-FileCopyrightText: 2015 Teo Mrnjavac <teo@kde.org>
SPDX-License-Identifier: GPL-3.0-or-later

BIN
branding/artix/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 363 KiB

BIN
branding/artix/office.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 359 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 873 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 502 KiB

BIN
branding/artix/rolling.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 121 KiB

178
branding/artix/show.qml Normal file
View File

@@ -0,0 +1,178 @@
/* === This file is part of Calamares - <http://github.com/calamares> ===
*
* Copyright 2015, Teo Mrnjavac <teo@kde.org>
* Copyright 2016, Luca Giambonini <almack@chakralinux.org>
*
* Calamares is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Calamares is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Calamares. If not, see <http://www.gnu.org/licenses/>.
*/
import QtQuick 2.0;
import calamares.slideshow 1.0;
/* Tested with slide images of 1800x1200 and 1632x1248 pixels */
Presentation
{
id: presentation
Timer {
interval: 10000
running: true
repeat: true
onTriggered: presentation.goToNextSlide()
}
Slide {
Image {
id: pic0
source: "thanks.png"
width: parent.width * 1.12; height: parent.height * 1.5
fillMode: Image.Stretch
anchors.centerIn: parent
anchors.verticalCenterOffset: -20
}
}
Slide {
Image {
id: pic00
source: "artix.png"
width: parent.width * 1.12; height: parent.height * 1.5
fillMode: Image.Stretch
anchors.centerIn: parent
anchors.verticalCenterOffset: -20
}
}
Slide {
Image {
id: pic02
source: "tools.png"
width: parent.width * 1.12; height: parent.height * 1.5
fillMode: Image.Stretch
anchors.centerIn: parent
anchors.verticalCenterOffset: -20
}
}
Slide {
Image {
id: pic03
source: "desktops.png"
width: parent.width * 1.12; height: parent.height * 1.5
fillMode: Image.Stretch
anchors.centerIn: parent
anchors.verticalCenterOffset: -20
}
}
Slide {
Image {
id: pic04
source: "productivity.png"
width: parent.width * 1.12; height: parent.height * 1.5
fillMode: Image.Stretch
anchors.centerIn: parent
anchors.verticalCenterOffset: -20
}
}
Slide {
Image {
id: pic06
source: "browsers.png"
width: parent.width * 1.12; height: parent.height * 1.5
fillMode: Image.Stretch
anchors.centerIn: parent
anchors.verticalCenterOffset: -20
}
}
Slide {
Image {
id: pic07
source: "office.png"
width: parent.width * 1.12; height: parent.height * 1.5
fillMode: Image.Stretch
anchors.centerIn: parent
anchors.verticalCenterOffset: -20
}
}
Slide {
Image {
id: pic08
source: "multimedia.png"
width: parent.width * 1.12; height: parent.height * 1.5
fillMode: Image.Stretch
anchors.centerIn: parent
anchors.verticalCenterOffset: -20
}
}
Slide {
Image {
id: pic09
source: "web.png"
width: parent.width * 1.12; height: parent.height * 1.5
fillMode: Image.Stretch
anchors.centerIn: parent
anchors.verticalCenterOffset: -20
}
}
Slide {
Image {
id: pic11
source: "packages1.png"
width: parent.width * 1.12; height: parent.height * 1.5
fillMode: Image.Stretch
anchors.centerIn: parent
anchors.verticalCenterOffset: -20
}
}
Slide {
Image {
id: pic13
source: "packages2.png"
width: parent.width * 1.12; height: parent.height * 1.5
fillMode: Image.Stretch
anchors.centerIn: parent
anchors.verticalCenterOffset: -20
}
}
Slide {
Image {
id: pic15
source: "customise.png"
width: parent.width * 1.12; height: parent.height * 1.5
fillMode: Image.Stretch
anchors.centerIn: parent
anchors.verticalCenterOffset: -20
}
}
Slide {
Image {
id: pic16
source: "rolling.png"
width: parent.width * 1.12; height: parent.height * 1.5
fillMode: Image.Stretch
anchors.centerIn: parent
anchors.verticalCenterOffset: -20
}
}
}

View File

@@ -0,0 +1,96 @@
/*
* SPDX-FileCopyrightText: no
* SPDX-License-Identifier: CC0-1.0
*/
/*
A branding component can ship a stylesheet (like this one)
which is applied to parts of the Calamares user-interface.
In principle, all parts can be styled through CSS.
Missing parts should be filed as issues.
The IDs are based on the object names in the C++ code.
You can use the Debug Dialog to find out object names:
- Open the debug dialog
- Choose tab *Tools*
- Click *Widget Tree* button
The list of object names is printed in the log.
Documentation for styling Qt Widgets through a stylesheet
can be found at
https://doc.qt.io/qt-5/stylesheet-examples.html
https://doc.qt.io/qt-5/stylesheet-reference.html
In Calamares, styling widget classes is supported (e.g.
using `QComboBox` as a selector).
This example stylesheet has all the actual styling commented out.
The examples are not exhaustive.
*/
/*** Generic Widgets.
*
* You can style **all** widgets of a given class by selecting
* the class name. Some widgets have specialized sub-selectors.
*/
/*
QPushButton { background-color: green; }
*/
/*** Main application window.
*
* The main application window has the sidebar, which in turn
* contains a logo and a list of items -- note that the list
* can **not** be styled, since it has its own custom C++
* delegate code.
*/
/*
#mainApp { }
#sidebarApp { }
#logoApp { }
*/
/*** Welcome module.
*
* There are plenty of parts, but the buttons are the most interesting
* ones (donate, release notes, ...). The little icon image can be
* styled through *qproperty-icon*, which is a little obscure.
* URLs can reference the QRC paths of the Calamares application
* or loaded via plugins or within the filesystem. There is no
* comprehensive list of available icons, though.
*/
/*
QPushButton#aboutButton { qproperty-icon: url(:/data/images/release.svg); }
#donateButton,
#supportButton,
#releaseNotesButton,
#knownIssuesButton { qproperty-icon: url(:/data/images/help.svg); }
*/
/*** Partitioning module.
*
* Many moving parts, which you will need to experiment with.
*/
/*
#bootInfoIcon { }
#bootInfoLable { }
#deviceInfoIcon { }
#defineInfoLabel { }
#scrollAreaWidgetContents { }
#partitionBarView { }
*/
/*** Licensing module.
*
* The licensing module paints individual widgets for each of
* the licenses. The item can be collapsed or expanded.
*/
/*
#licenseItem { }
#licenseItemFullText { }
*/

BIN
branding/artix/thanks.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

BIN
branding/artix/tools.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 160 KiB

BIN
branding/artix/web.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 136 KiB

View File

@@ -0,0 +1,75 @@
# SPDX-FileCopyrightText: no
# SPDX-License-Identifier: CC0-1.0
#
# The configuration for the package manager starts with the
# *backend* key, which picks one of the backends to use.
# In `main.py` there is a base class `PackageManager`.
# Implementations must subclass that and set a (class-level)
# property *backend* to the name of the backend (e.g. "dummy").
# That property is used to match against the *backend* key here.
#
# You will have to add such a class for your package manager.
# It is fairly simple Python code. The API is described in the
# abstract methods in class `PackageManager`. Mostly, the only
# trick is to figure out the correct commands to use, and in particular,
# whether additional switches are required or not. Some package managers
# have more installer-friendly defaults than others, e.g., DNF requires
# passing --disablerepo=* -C to allow removing packages without Internet
# connectivity, and it also returns an error exit code if the package did
# not exist to begin with.
---
#
# Which package manager to use, options are:
# - pacman - Pacman
#
# Not actually a package manager, but suitable for testing:
# - dummy - Dummy manager, only logs
#
backend: dummy
# 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.
# *handle_keyrings* is a boolean that includes initializing and populating keyrings
# when set to true. If missing, false is assumed.
pacman:
num_retries: 0
disable_download_timeout: false
needed_only: false
handle_keyrings: false
copy_pacconf: false
requirements:
- name: /etc
mode: "0o755"
- name: /var
mode: "0o755"
- name: /var/cache
mode: "0o755"
- name: /var/cache/pacman
mode: "0o755"
- name: /var/cache/pacman/pkg
mode: "0o755"
- name: /var/lib
mode: "0o755"
- name: /var/lib/pacman
mode: "0o755"
keyrings:
- artix
# the artix base package allows selection of the init system tied to elogind
# this option is artix specific
# base_init: elogind
operations:
- install:
- base

552
modules/basestrap/main.py Normal file
View File

@@ -0,0 +1,552 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# === This file is part of Calamares - <https://calamares.io> ===
#
# SPDX-FileCopyrightText: 2014 Pier Luigi Fiorini <pierluigi.fiorini@gmail.com>
# SPDX-FileCopyrightText: 2015-2017 Teo Mrnjavac <teo@kde.org>
# SPDX-FileCopyrightText: 2016-2017 Kyle Robbertze <kyle@aims.ac.za>
# SPDX-FileCopyrightText: 2017 Alf Gaida <agaida@siduction.org>
# SPDX-FileCopyrightText: 2018 Adriaan de Groot <groot@kde.org>
# SPDX-FileCopyrightText: 2018 Philip Müller <philm@manjaro.org>
# SPDX-FileCopyrightText: 2023 Artoo <artoo@artixlinux.org>
# SPDX-License-Identifier: GPL-3.0-or-later
#
# Calamares is Free Software: see the License-Identifier above.
#
import abc
from string import Template
import os, shutil, subprocess, sys
import libcalamares
from libcalamares.utils import host_env_process_output, target_env_process_output
from libcalamares.utils import gettext_path, gettext_languages
from os.path import join
import gettext
_translation = gettext.translation("calamares-python",
localedir=gettext_path(),
languages=gettext_languages(),
fallback=True)
_ = _translation.gettext
_n = _translation.ngettext
total_packages = 0 # For the entire job
completed_packages = 0 # Done so far for this job
group_packages = 0 # One group of packages from an -install or -remove entry
# A PM object may set this to a string (take care of translations!)
# to override the string produced by pretty_status_message()
custom_status_message = None
INSTALL = object()
REMOVE = object()
mode_packages = None # Changes to INSTALL or REMOVE
def _change_mode(mode):
global mode_packages
mode_packages = mode
libcalamares.job.setprogress(completed_packages * 1.0 / total_packages)
def pretty_name():
return _("Install packages.")
def pretty_status_message():
if custom_status_message is not None:
return custom_status_message
if not group_packages:
if (total_packages > 0):
# Outside the context of an operation
s = _("Processing packages (%(count)d / %(total)d)")
else:
s = _("Install packages.")
elif mode_packages is INSTALL:
s = _n("Installing one package.",
"Installing %(num)d packages.", group_packages)
elif mode_packages is REMOVE:
s = _n("Removing one package.",
"Removing %(num)d packages.", group_packages)
else:
# No mode, generic description
s = _("Install packages.")
return s % {"num": group_packages,
"count": completed_packages,
"total": total_packages}
class PackageManager(metaclass=abc.ABCMeta):
"""
Package manager base class. A subclass implements package management
for a specific backend, and must have a class property `backend`
with the string identifier for that backend.
Subclasses are collected below to populate the list of possible
backends.
"""
backend = None
@abc.abstractmethod
def install(self, pkgs, from_local=False):
"""
Install a list of packages (named) into the system.
Although this handles lists, in practice it is called
with one package at a time.
@param pkgs: list[str]
list of package names
@param from_local: bool
if True, then these are local packages (on disk) and the
pkgs names are paths.
"""
pass
@abc.abstractmethod
def remove(self, pkgs):
"""
Removes packages.
@param pkgs: list[str]
list of package names
"""
pass
def run(self, script):
if script != "":
host_env_process_output(script.split(" "))
def install_package(self, packagedata, from_local=False):
"""
Install a package from a single entry in the install list.
This can be either a single package name, or an object
with pre- and post-scripts. If @p packagedata is a dict,
it is assumed to follow the documented structure.
@param packagedata: str|dict
@param from_local: bool
see install.from_local
"""
if isinstance(packagedata, str):
self.install([packagedata], from_local=from_local)
else:
self.run(packagedata["pre-script"])
self.install([packagedata["package"]], from_local=from_local)
self.run(packagedata["post-script"])
def remove_package(self, packagedata):
"""
Remove a package from a single entry in the remove list.
This can be either a single package name, or an object
with pre- and post-scripts. If @p packagedata is a dict,
it is assumed to follow the documented structure.
@param packagedata: str|dict
"""
if isinstance(packagedata, str):
self.remove([packagedata])
else:
self.run(packagedata["pre-script"])
self.remove([packagedata["package"]])
self.run(packagedata["post-script"])
def operation_install(self, package_list, from_local=False):
"""
Installs the list of packages named in @p package_list .
These can be strings -- plain package names -- or
structures (with a pre- and post-install step).
This operation is called for "critical" packages,
which are expected to succeed, or fail, all together.
However, if there are packages with pre- or post-scripts,
then packages are installed one-by-one instead.
NOTE: package managers may reimplement this method
NOTE: exceptions are expected to leave this method, to indicate
failure of the installation.
"""
if all([isinstance(x, str) for x in package_list]):
self.install(package_list, from_local=from_local)
else:
for package in package_list:
self.install_package(package, from_local=from_local)
def operation_try_install(self, package_list):
"""
Installs the list of packages named in @p package_list .
These can be strings -- plain package names -- or
structures (with a pre- and post-install step).
This operation is called for "non-critical" packages,
which can succeed or fail without affecting the overall installation.
Packages are installed one-by-one to support package managers
that do not have a "install as much as you can" mode.
NOTE: package managers may reimplement this method
NOTE: no package-installation exceptions should be raised
"""
# we make a separate package manager call for each package so a
# single failing package won't stop all of them
for package in package_list:
try:
self.install_package(package)
except subprocess.CalledProcessError:
libcalamares.utils.warning("Could not install package %s" % package)
def operation_remove(self, package_list):
"""
Removes the list of packages named in @p package_list .
These can be strings -- plain package names -- or
structures (with a pre- and post-install step).
This operation is called for "critical" packages, which are
expected to succeed or fail all together.
However, if there are packages with pre- or post-scripts,
then packages are removed one-by-one instead.
NOTE: package managers may reimplement this method
NOTE: exceptions should be raised to indicate failure
"""
if all([isinstance(x, str) for x in package_list]):
self.remove(package_list)
else:
for package in package_list:
self.remove_package(package)
def operation_try_remove(self, package_list):
"""
Same relation as try_install has to install, except it removes
packages instead. Packages are removed one-by-one.
NOTE: package managers may reimplement this method
NOTE: no package-installation exceptions should be raised
"""
for package in package_list:
try:
self.remove_package(package)
except subprocess.CalledProcessError:
libcalamares.utils.warning("Could not remove package %s" % package)
### PACKAGE MANAGER IMPLEMENTATIONS
#
# Keep these alphabetical (presumably both by class name and backend name),
# even the Dummy implementation.
#
class PMPacman(PackageManager):
backend = "pacman"
def __init__(self):
import re
progress_match = re.compile("^\\((\\d+)/(\\d+)\\)")
def line_cb(line):
if line.startswith(":: "):
self.in_package_changes = "package" in line or "hooks" in line
else:
if self.in_package_changes and line.endswith("...\n"):
# Update the message, untranslated; do not change the
# progress percentage, since there may be more "installing..."
# lines in the output for the group, than packages listed
# explicitly. We don't know how to calculate proper progress.
global custom_status_message
custom_status_message = "pacman: " + line.strip()
libcalamares.job.setprogress(self.progress_fraction)
libcalamares.utils.debug(line)
self.in_package_changes = False
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)
self.pacman_key = pacman.get("handle_keyrings", False)
self.pacman_pacconf = pacman.get("copy_pacconf", False)
self.pacman_requirements = pacman.get("requirements", [])
self.pacman_keyrings = pacman.get("keyrings", [])
def reset_progress(self):
self.in_package_changes = False
# These are globals
self.progress_fraction = (completed_packages * 1.0 / total_packages)
def run_pacman(self, command, callback=False):
"""
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 False: # callback:
host_env_process_output(command, self.line_cb)
else:
host_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):
install_root = libcalamares.globalstorage.value("rootMountPoint")
cal_umask = os.umask(0)
for target in self.pacman_requirements:
dest = install_root + target["name"]
if not os.path.exists(dest):
mod = int(target["mode"],8)
os.mkdir(dest, mode=mod)
libcalamares.utils.debug("Mode: {!s}".format(oct(mod)))
libcalamares.utils.debug("Created: {!s}".format(dest))
path = join(install_root, "run")
os.chmod(path, 0o755)
os.umask(cal_umask)
f = "etc/resolv.conf"
if os.path.exists(join("/",f)):
shutil.copy2(join("/",f), join(install_root, f))
command = ["pacman"]
cachedir = join(install_root, "var/cache/pacman/pkg")
dbdir = join(install_root, "var/lib/pacman")
pacman_args = ["--root", install_root, "--dbpath", dbdir, "--cachedir", cachedir]
command.extend(pacman_args)
# Don't ask for user intervention, take the default action
command.append("--noconfirm")
# Don't report download progress for each file
command.append("--noprogressbar")
if self.pacman_needed_only is True:
command.append("--needed")
if self.pacman_disable_timeout is True:
command.append("--disable-download-timeout")
if from_local:
command.append("-U")
else:
command.append("-Sy")
command += pkgs
libcalamares.utils.debug("Command: {!s}".format(command))
self.reset_progress()
self.run_pacman(command, True)
if self.pacman_key:
self.init_keyring()
self.populate_keyring()
if self.pacman_pacconf:
f = "etc/pacman.conf"
if os.path.exists(join("/",f)):
shutil.copy2(join("/",f), join(install_root, f))
def remove(self, pkgs):
self.reset_progress()
self.run_pacman(["pacman", "-Rs", "--noconfirm"] + pkgs, True)
def init_keyring(self):
target_env_process_output(["pacman-key", "--init"])
def populate_keyring(self):
target_env_process_output(["pacman-key", "--populate"] + self.pacman_keyrings)
# Collect all the subclasses of PackageManager defined above,
# and index them based on the backend property of each class.
backend_managers = [
(c.backend, c)
for c in globals().values()
if type(c) is abc.ABCMeta and issubclass(c, PackageManager) and c.backend]
def subst_locale(plist):
"""
Returns a locale-aware list of packages, based on @p plist.
Package names that contain LOCALE are localized with the
BCP47 name of the chosen system locale; if the system
locale is 'en' (e.g. English, US) then these localized
packages are dropped from the list.
@param plist: list[str|dict]
Candidate packages to install.
@return: list[str|dict]
"""
locale = libcalamares.globalstorage.value("locale")
if not locale:
# It is possible to skip the locale-setting entirely.
# Then pretend it is "en", so that {LOCALE}-decorated
# package names are removed from the list.
locale = "en"
ret = []
for packagedata in plist:
if isinstance(packagedata, str):
packagename = packagedata
else:
packagename = packagedata["package"]
# Update packagename: substitute LOCALE, and drop packages
# if locale is en and LOCALE is in the package name.
if locale != "en":
packagename = Template(packagename).safe_substitute(LOCALE=locale)
elif 'LOCALE' in packagename:
packagename = None
if packagename is not None:
# Put it back in packagedata
if isinstance(packagedata, str):
packagedata = packagename
else:
packagedata["package"] = packagename
ret.append(packagedata)
return ret
def run_operations(pkgman, entry):
"""
Call package manager with suitable parameters for the given
package actions.
:param pkgman: PackageManager
This is the manager that does the actual work.
:param entry: dict
Keys are the actions -- e.g. "install" -- to take, and the values
are the (list of) packages to apply the action to. The actions are
not iterated in a specific order, so it is recommended to use only
one action per dictionary. The list of packages may be package
names (strings) or package information dictionaries with pre-
and post-scripts.
"""
global group_packages, completed_packages, mode_packages
for key in entry.keys():
package_list = subst_locale(entry[key])
group_packages = len(package_list)
if key == "install":
_change_mode(INSTALL)
pkgman.operation_install(package_list)
elif key == "try_install":
_change_mode(INSTALL)
pkgman.operation_try_install(package_list)
elif key == "remove":
_change_mode(REMOVE)
pkgman.operation_remove(package_list)
elif key == "try_remove":
_change_mode(REMOVE)
pkgman.operation_try_remove(package_list)
elif key == "localInstall":
_change_mode(INSTALL)
pkgman.operation_install(package_list, from_local=True)
elif key == "source":
libcalamares.utils.debug("Package-list from {!s}".format(entry[key]))
else:
libcalamares.utils.warning("Unknown package-operation key {!s}".format(key))
completed_packages += len(package_list)
libcalamares.job.setprogress(completed_packages * 1.0 / total_packages)
libcalamares.utils.debug("Pretty name: {!s}, setting progress..".format(pretty_name()))
group_packages = 0
_change_mode(None)
def run():
"""
Calls routine with detected package manager to install locale packages
or remove drivers not needed on the installed system.
:return:
"""
global mode_packages, total_packages, completed_packages, group_packages
backend = libcalamares.job.configuration.get("backend")
for identifier, impl in backend_managers:
if identifier == backend:
pkgman = impl()
break
else:
return "Bad backend", "backend=\"{}\"".format(backend)
if not libcalamares.globalstorage.value("hasInternet"):
libcalamares.utils.warning( "Package installation has been skipped: no internet" )
return None
operations = libcalamares.job.configuration.get("operations", [])
# if libcalamares.globalstorage.contains("packageOperations"):
# operations += libcalamares.globalstorage.value("packageOperations")
if libcalamares.job.configuration.get("base_init"):
base_init = libcalamares.job.configuration.get("base_init", None)
if libcalamares.globalstorage.contains("netinstallAdd"):
data = libcalamares.globalstorage.value("netinstallAdd")
init_provider = data[0]["name"]
libcalamares.utils.debug("Init provider: {!s}".format(init_provider))
if base_init is not None:
init_pkg = "-".join([base_init, init_provider])
libcalamares.utils.debug("Package added: {!s}".format(init_pkg))
operations[0]["install"].append(init_pkg)
if init_provider is not None:
libcalamares.globalstorage.insert("initProvider", init_provider)
libcalamares.globalstorage.insert("packageOperationsBasestrap", operations)
mode_packages = None
total_packages = 0
completed_packages = 0
for op in operations:
for packagelist in op.values():
total_packages += len(subst_locale(packagelist))
if not total_packages:
# Avoids potential divide-by-zero in progress reporting
return None
for entry in operations:
group_packages = 0
libcalamares.utils.debug(pretty_name())
try:
run_operations(pkgman, entry)
except subprocess.CalledProcessError as e:
libcalamares.utils.warning(str(e))
libcalamares.utils.debug("stdout:" + str(e.stdout))
libcalamares.utils.debug("stderr:" + str(e.stderr))
return (_("Package Manager error"),
_("The package manager could not make changes to the installed system. The command <pre>{!s}</pre> returned error code {!s}.")
.format(e.cmd, e.returncode))
mode_packages = None
libcalamares.job.setprogress(1.0)
return None

View File

@@ -0,0 +1,7 @@
# SPDX-FileCopyrightText: no
# SPDX-License-Identifier: CC0-1.0
---
type: "job"
name: "basestrap"
interface: "python"
script: "main.py"

View File

@@ -1,17 +0,0 @@
# SPDX-FileCopyrightText: no
# SPDX-License-Identifier: CC0-1.0
#
if( NOT Calamares_WITH_QML )
calamares_skip_module( "freebsddisk (QML is not supported in this build)" )
return()
endif()
calamares_add_plugin( freebsddisk
TYPE viewmodule
EXPORT_MACRO PLUGINDLLEXPORT_PRO
SOURCES
FreeBSDDiskViewStep.cpp
RESOURCES
freebsddisk.qrc
SHARED_LIB
)

View File

@@ -1,42 +0,0 @@
/* === This file is part of Calamares - <https://github.com/calamares> ===
*
* Calamares is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Calamares is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Calamares. If not, see <http://www.gnu.org/licenses/>.
*
* SPDX-FileCopyrightText: 2020 Adriaan de Groot <groot@kde.org>
* SPDX-License-Identifier: GPL-3.0-or-later
* License-Filename: LICENSES/GPL-3.0
*/
#include "FreeBSDDiskViewStep.h"
FreeBSDDiskViewStep::FreeBSDDiskViewStep( QObject* parent )
: Calamares::QmlViewStep( parent )
{
}
FreeBSDDiskViewStep::~FreeBSDDiskViewStep() {}
QString
FreeBSDDiskViewStep::prettyName() const
{
return tr( "Disk Setup" );
}
void
FreeBSDDiskViewStep::setConfigurationMap( const QVariantMap& configurationMap )
{
Calamares::QmlViewStep::setConfigurationMap( configurationMap ); // call parent implementation last
}
CALAMARES_PLUGIN_FACTORY_DEFINITION( FreeBSDDiskViewStepFactory, registerPlugin< FreeBSDDiskViewStep >(); )

View File

@@ -1,44 +0,0 @@
/* === This file is part of Calamares - <https://github.com/calamares> ===
*
* Calamares is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Calamares is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Calamares. If not, see <http://www.gnu.org/licenses/>.
*
* SPDX-FileCopyrightText: 2020 Adriaan de Groot <groot@kde.org>
* SPDX-License-Identifier: GPL-3.0-or-later
* License-Filename: LICENSES/GPL-3.0
*/
#ifndef FREEBSDDISKVIEWSTEP_H
#define FREEBSDDISKVIEWSTEP_H
#include "DllMacro.h"
#include "utils/PluginFactory.h"
#include "viewpages/QmlViewStep.h"
class PLUGINDLLEXPORT FreeBSDDiskViewStep : public Calamares::QmlViewStep
{
Q_OBJECT
public:
FreeBSDDiskViewStep( QObject* parent = nullptr );
virtual ~FreeBSDDiskViewStep() override;
QString prettyName() const override;
void setConfigurationMap( const QVariantMap& configurationMap ) override;
};
CALAMARES_PLUGIN_FACTORY_DECLARATION( FreeBSDDiskViewStepFactory )
#endif

View File

@@ -1,10 +0,0 @@
# SPDX-FileCopyrightText: no
# SPDX-License-Identifier: CC0-1.0
#
# The *freebsddisk* module can be used to pick a disk
# as an installer step. This module supports ZFSroot
# on one whole disk, and UFSroot on one whole disk.
#
---
qmlSearch: both

View File

@@ -1,35 +0,0 @@
/* === This file is part of Calamares - <https://github.com/calamares> ===
*
* Calamares is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Calamares is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Calamares. If not, see <http://www.gnu.org/licenses/>.
*
* SPDX-FileCopyrightText: 2020 Adriaan de Groot <groot@kde.org>
* SPDX-License-Identifier: GPL-3.0-or-later
* License-Filename: LICENSES/GPL-3.0
*/
import io.calamares.ui 1.0
import QtQuick 2.7
import QtQuick.Controls 2.2
import QtQuick.Window 2.2
import QtQuick.Layouts 1.3
import QtQuick.Controls.Material 2.1
Item {
Text {
anchors.top: parent.top
anchors.topMargin: 10
text: "Select a disk on which to install FreeBSD."
}
}

View File

@@ -1,5 +0,0 @@
<!DOCTYPE RCC><RCC version="1.0">
<qresource>
<file alias="freebsddisk.qml">freebsddisk.qml</file>
</qresource>
</RCC>

View File

@@ -1,15 +0,0 @@
# SPDX-FileCopyrightText: 2020 Oliver Smith <ollieparanoid@postmarketos.org>
# SPDX-License-Identifier: GPL-3.0-or-later
calamares_add_plugin( mobile
TYPE viewmodule
EXPORT_MACRO PLUGINDLLEXPORT_PRO
SOURCES
Config.cpp
MobileQmlViewStep.cpp
PartitionJob.cpp
UsersJob.cpp
RESOURCES
mobile.qrc
SHARED_LIB
)

View File

@@ -1,221 +0,0 @@
/* SPDX-FileCopyrightText: 2020 Oliver Smith <ollieparanoid@postmarketos.org>
* SPDX-License-Identifier: GPL-3.0-or-later */
#include "Config.h"
#include "PartitionJob.h"
#include "UsersJob.h"
#include "ViewManager.h"
#include "utils/Logger.h"
#include "utils/Variant.h"
#include <QVariant>
Config::Config( QObject* parent )
: QObject( parent )
{
}
void
Config::setConfigurationMap( const QVariantMap& cfgMap )
{
using namespace CalamaresUtils;
if ( getBool( cfgMap, "bogus", false ) )
{
cWarning() << "Configuration key \"bogus\" is still set for *mobile*";
}
m_osName = getString( cfgMap, "osName", "(unknown)" );
m_arch = getString( cfgMap, "arch", "(unknown)" );
m_device = getString( cfgMap, "device", "(unknown)" );
m_userInterface = getString( cfgMap, "userInterface", "(unknown)" );
m_version = getString( cfgMap, "version", "(unknown)" );
m_reservedUsernames = getStringList( cfgMap, "reservedUsernames", QStringList { "adm", "at ", "bin", "colord",
"cron", "cyrus", "daemon", "ftp", "games", "geoclue", "guest", "halt", "lightdm", "lp", "mail", "man",
"messagebus", "news", "nobody", "ntp", "operator", "polkitd", "postmaster", "pulse", "root", "shutdown",
"smmsp", "squid", "sshd", "sync", "uucp", "vpopmail", "xfs" } );
// ensure m_cmdUsermod matches m_username
m_username = getString( cfgMap, "username", "user" );
m_userPasswordNumeric = getBool( cfgMap, "userPasswordNumeric", true );
m_builtinVirtualKeyboard = getBool( cfgMap, "builtinVirtualKeyboard", true );
m_featureSshd = getBool( cfgMap, "featureSshd", true );
m_featureFsType = getBool( cfgMap, "featureFsType", false );
m_cmdLuksFormat = getString( cfgMap, "cmdLuksFormat", "cryptsetup luksFormat --use-random" );
m_cmdLuksOpen = getString( cfgMap, "cmdLuksOpen", "cryptsetup luksOpen" );
m_cmdMount = getString( cfgMap, "cmdMount", "mount" );
m_targetDeviceRoot = getString( cfgMap, "targetDeviceRoot", "/dev/unknown" );
m_targetDeviceRootInternal = getString( cfgMap, "targetDeviceRootInternal", "" );
m_cmdMkfsRootBtrfs = getString( cfgMap, "cmdMkfsRootBtrfs", "mkfs.btrfs -L 'unknownOS_root'" );
m_cmdMkfsRootExt4 = getString( cfgMap, "cmdMkfsRootExt4", "mkfs.ext4 -L 'unknownOS_root'" );
m_cmdMkfsRootF2fs = getString( cfgMap, "cmdMkfsRootF2fs", "mkfs.f2fs -l 'unknownOS_root'" );
m_fsList = getStringList( cfgMap, "fsModel", QStringList { "ext4", "f2fs", "btrfs" } );
m_defaultFs = getString( cfgMap, "defaultFs", "ext4" );
m_fsIndex = m_fsList.indexOf( m_defaultFs );
m_fsType = m_defaultFs;
m_cmdInternalStoragePrepare = getString( cfgMap, "cmdInternalStoragePrepare", "ondev-internal-storage-prepare" );
m_cmdPasswd = getString( cfgMap, "cmdPasswd", "passwd" );
m_cmdUsermod = getString( cfgMap, "cmdUsermod", "xargs -I{} -n1 usermod -m -d /home/{} -l {} -c {} user");
m_cmdSshdEnable = getString( cfgMap, "cmdSshdEnable", "systemctl enable sshd.service" );
m_cmdSshdDisable = getString( cfgMap, "cmdSshdDisable", "systemctl disable sshd.service" );
m_cmdSshdUseradd = getString( cfgMap, "cmdSshdUseradd", "useradd -G wheel -m" );
}
Calamares::JobList
Config::createJobs()
{
QList< Calamares::job_ptr > list;
QString cmdSshd = m_isSshEnabled ? m_cmdSshdEnable : m_cmdSshdDisable;
/* Put users job in queue (should run after unpackfs) */
Calamares::Job* j = new UsersJob( m_featureSshd,
m_cmdPasswd,
m_cmdUsermod,
cmdSshd,
m_cmdSshdUseradd,
m_isSshEnabled,
m_username,
m_userPassword,
m_sshdUsername,
m_sshdPassword );
list.append( Calamares::job_ptr( j ) );
return list;
}
void
Config::runPartitionJobThenLeave( bool b )
{
Calamares::ViewManager* v = Calamares::ViewManager::instance();
QString cmdMkfsRoot;
if ( m_fsType == QStringLiteral( "btrfs" ) )
{
cmdMkfsRoot = m_cmdMkfsRootBtrfs;
}
else if ( m_fsType == QStringLiteral( "f2fs" ) )
{
cmdMkfsRoot = m_cmdMkfsRootF2fs;
}
else if ( m_fsType == QStringLiteral( "ext4" ) )
{
cmdMkfsRoot = m_cmdMkfsRootExt4;
}
else
{
v->onInstallationFailed( "Unknown filesystem: '" + m_fsType + "'", "" );
}
/* HACK: run partition job
* The "mobile" module has two jobs, the partition job and the users job.
* If we added both of them in Config::createJobs(), Calamares would run
* them right after each other. But we need the "unpackfs" module to run
* inbetween, that's why as workaround, the partition job is started here.
* To solve this properly, we would need to place the partition job in an
* own module and pass everything via globalstorage. But then we might as
* well refactor everything so we can unify the mobile's partition job with
* the proper partition job from Calamares. */
Calamares::Job* j = new PartitionJob( m_cmdInternalStoragePrepare,
m_cmdLuksFormat,
m_cmdLuksOpen,
cmdMkfsRoot,
m_cmdMount,
m_targetDeviceRoot,
m_targetDeviceRootInternal,
m_installFromExternalToInternal,
m_isFdeEnabled,
m_fdePassword );
Calamares::JobResult res = j->exec();
if ( res )
{
v->next();
}
else
{
v->onInstallationFailed( res.message(), res.details() );
}
}
void
Config::setUsername( const QString& username )
{
m_username = username;
emit usernameChanged( m_username );
}
void
Config::setUserPassword( const QString& userPassword )
{
m_userPassword = userPassword;
emit userPasswordChanged( m_userPassword );
}
void
Config::setSshdUsername( const QString& sshdUsername )
{
m_sshdUsername = sshdUsername;
emit sshdUsernameChanged( m_sshdUsername );
}
void
Config::setSshdPassword( const QString& sshdPassword )
{
m_sshdPassword = sshdPassword;
emit sshdPasswordChanged( m_sshdPassword );
}
void
Config::setIsSshEnabled( const bool isSshEnabled )
{
m_isSshEnabled = isSshEnabled;
}
void
Config::setFdePassword( const QString& fdePassword )
{
m_fdePassword = fdePassword;
emit fdePasswordChanged( m_fdePassword );
}
void
Config::setIsFdeEnabled( const bool isFdeEnabled )
{
m_isFdeEnabled = isFdeEnabled;
}
void
Config::setInstallFromExternalToInternal( const bool val )
{
m_installFromExternalToInternal = val;
}
void
Config::setFsType( int idx )
{
if ( idx >= 0 && idx < m_fsList.length() )
{
setFsType( m_fsList[ idx ] );
}
}
void
Config::setFsType( const QString& fsType )
{
if ( fsType != m_fsType )
{
m_fsType = fsType;
emit fsTypeChanged( m_fsType );
}
}
void
Config::setFsIndex( const int fsIndex )
{
m_fsIndex = fsIndex;
emit fsIndexChanged( m_fsIndex );
}

View File

@@ -1,207 +0,0 @@
/* SPDX-FileCopyrightText: 2020 Oliver Smith <ollieparanoid@postmarketos.org>
* SPDX-License-Identifier: GPL-3.0-or-later */
#pragma once
#include "Job.h"
#include <QObject>
#include <memory>
class Config : public QObject
{
Q_OBJECT
/* installer UI */
Q_PROPERTY( bool builtinVirtualKeyboard READ builtinVirtualKeyboard CONSTANT FINAL )
/* welcome */
Q_PROPERTY( QString osName READ osName CONSTANT FINAL )
Q_PROPERTY( QString arch READ arch CONSTANT FINAL )
Q_PROPERTY( QString device READ device CONSTANT FINAL )
Q_PROPERTY( QString userInterface READ userInterface CONSTANT FINAL )
Q_PROPERTY( QString version READ version CONSTANT FINAL )
/* reserved usernames (user_pass, ssh_credentials )*/
Q_PROPERTY( QStringList reservedUsernames READ reservedUsernames CONSTANT FINAL )
/* default user */
Q_PROPERTY( QString username READ username WRITE setUsername NOTIFY usernameChanged )
Q_PROPERTY( QString userPassword READ userPassword WRITE setUserPassword NOTIFY userPasswordChanged )
Q_PROPERTY( bool userPasswordNumeric READ userPasswordNumeric CONSTANT FINAL )
/* ssh server + credentials */
Q_PROPERTY( bool featureSshd READ featureSshd CONSTANT FINAL )
Q_PROPERTY( QString sshdUsername READ sshdUsername WRITE setSshdUsername NOTIFY sshdUsernameChanged )
Q_PROPERTY( QString sshdPassword READ sshdPassword WRITE setSshdPassword NOTIFY sshdPasswordChanged )
Q_PROPERTY( bool isSshEnabled READ isSshEnabled WRITE setIsSshEnabled )
/* full disk encryption */
Q_PROPERTY( QString fdePassword READ fdePassword WRITE setFdePassword NOTIFY fdePasswordChanged )
Q_PROPERTY( bool isFdeEnabled READ isFdeEnabled WRITE setIsFdeEnabled )
/* filesystem selection */
Q_PROPERTY( QString fsType READ fsType WRITE setFsType NOTIFY fsTypeChanged )
Q_PROPERTY( bool featureFsType READ featureFsType CONSTANT FINAL )
Q_PROPERTY( QStringList fsList READ fsList CONSTANT FINAL )
Q_PROPERTY( QString defaultFs READ defaultFs CONSTANT FINAL )
Q_PROPERTY( int fsIndex READ fsIndex WRITE setFsIndex NOTIFY fsIndexChanged )
/* partition job */
Q_PROPERTY( bool runPartitionJobThenLeave READ runPartitionJobThenLeaveDummy WRITE runPartitionJobThenLeave )
Q_PROPERTY( QString cmdInternalStoragePrepare READ cmdInternalStoragePrepare CONSTANT FINAL )
Q_PROPERTY( QString cmdLuksFormat READ cmdLuksFormat CONSTANT FINAL )
Q_PROPERTY( QString cmdLuksOpen READ cmdLuksOpen CONSTANT FINAL )
Q_PROPERTY( QString cmdMount READ cmdMount CONSTANT FINAL )
Q_PROPERTY( QString targetDeviceRoot READ targetDeviceRoot CONSTANT FINAL )
Q_PROPERTY( QString targetDeviceRootInternal READ targetDeviceRootInternal CONSTANT FINAL )
Q_PROPERTY(
bool installFromExternalToInternal READ installFromExternalToInternal WRITE setInstallFromExternalToInternal )
/* users job */
Q_PROPERTY( QString cmdSshdEnable READ cmdSshdEnable CONSTANT FINAL )
Q_PROPERTY( QString cmdSshdDisable READ cmdSshdDisable CONSTANT FINAL )
public:
Config( QObject* parent = nullptr );
void setConfigurationMap( const QVariantMap& );
Calamares::JobList createJobs();
/* installer UI */
bool builtinVirtualKeyboard() { return m_builtinVirtualKeyboard; }
/* welcome */
QString osName() const { return m_osName; }
QString arch() const { return m_arch; }
QString device() const { return m_device; }
QString userInterface() const { return m_userInterface; }
QString version() const { return m_version; }
/* reserved usernames (user_pass, ssh_credentials) */
QStringList reservedUsernames() const { return m_reservedUsernames; };
/* user */
QString username() const { return m_username; }
QString userPassword() const { return m_userPassword; }
void setUsername( const QString& username );
void setUserPassword( const QString& userPassword );
bool userPasswordNumeric() const { return m_userPasswordNumeric; }
/* ssh server + credentials */
bool featureSshd() { return m_featureSshd; }
QString sshdUsername() const { return m_sshdUsername; }
QString sshdPassword() const { return m_sshdPassword; }
bool isSshEnabled() { return m_isSshEnabled; }
void setSshdUsername( const QString& sshdUsername );
void setSshdPassword( const QString& sshdPassword );
void setIsSshEnabled( bool isSshEnabled );
/* full disk encryption */
QString fdePassword() const { return m_fdePassword; }
bool isFdeEnabled() { return m_isFdeEnabled; }
void setFdePassword( const QString& fdePassword );
void setIsFdeEnabled( bool isFdeEnabled );
/* filesystem selection */
bool featureFsType() { return m_featureFsType; };
QString fsType() const { return m_fsType; };
void setFsType( int idx );
void setFsType( const QString& fsType );
QStringList fsList() const { return m_fsList; };
int fsIndex() const { return m_fsIndex; };
void setFsIndex( const int fsIndex );
QString defaultFs() const { return m_defaultFs; };
/* partition job */
bool runPartitionJobThenLeaveDummy() { return 0; }
void runPartitionJobThenLeave( bool b );
QString cmdInternalStoragePrepare() const { return m_cmdInternalStoragePrepare; }
QString cmdLuksFormat() const { return m_cmdLuksFormat; }
QString cmdLuksOpen() const { return m_cmdLuksOpen; }
QString cmdMkfsRootBtrfs() const { return m_cmdMkfsRootBtrfs; }
QString cmdMkfsRootExt4() const { return m_cmdMkfsRootExt4; }
QString cmdMkfsRootF2fs() const { return m_cmdMkfsRootF2fs; }
QString cmdMount() const { return m_cmdMount; }
QString targetDeviceRoot() const { return m_targetDeviceRoot; }
QString targetDeviceRootInternal() const { return m_targetDeviceRootInternal; }
bool installFromExternalToInternal() { return m_installFromExternalToInternal; }
void setInstallFromExternalToInternal( const bool val );
/* users job */
QString cmdPasswd() const { return m_cmdPasswd; }
QString cmdUsermod() const { return m_cmdUsermod; }
QString cmdSshdEnable() const { return m_cmdSshdEnable; }
QString cmdSshdDisable() const { return m_cmdSshdDisable; }
QString cmdSshdUseradd() const { return m_cmdSshdUseradd; }
private:
/* installer UI */
bool m_builtinVirtualKeyboard;
/* welcome */
QString m_osName;
QString m_arch;
QString m_device;
QString m_userInterface;
QString m_version;
/* reserved usernames (user_pass, ssh_credentials) */
QStringList m_reservedUsernames;
/* default user */
QString m_username;
QString m_userPassword;
bool m_userPasswordNumeric;
/* ssh server + credentials */
bool m_featureSshd = false;
QString m_sshdUsername;
QString m_sshdPassword;
bool m_isSshEnabled = false;
/* full disk encryption */
QString m_fdePassword;
bool m_isFdeEnabled = false;
/* filesystem selection */
bool m_featureFsType = false;
QString m_defaultFs;
QString m_fsType;
// Index of the currently selected filesystem in UI.
int m_fsIndex = -1;
QStringList m_fsList;
/* partition job */
QString m_cmdInternalStoragePrepare;
QString m_cmdLuksFormat;
QString m_cmdLuksOpen;
QString m_cmdMkfsRootBtrfs;
QString m_cmdMkfsRootExt4;
QString m_cmdMkfsRootF2fs;
QString m_cmdMount;
QString m_targetDeviceRoot;
QString m_targetDeviceRootInternal;
bool m_installFromExternalToInternal = false;
/* users job */
QString m_cmdPasswd;
QString m_cmdUsermod;
QString m_cmdSshdEnable;
QString m_cmdSshdDisable;
QString m_cmdSshdUseradd;
signals:
/* booleans we don't read from QML (like isSshEnabled) don't need a signal */
/* default user */
void userPasswordChanged( QString userPassword );
void usernameChanged( QString username );
/* ssh server + credentials */
void sshdUsernameChanged( QString sshdUsername );
void sshdPasswordChanged( QString sshdPassword );
/* full disk encryption */
void fdePasswordChanged( QString fdePassword );
void fsTypeChanged( QString fsType );
void fsIndexChanged( int fsIndex );
};

View File

@@ -1,73 +0,0 @@
/* SPDX-FileCopyrightText: 2020 Oliver Smith <ollieparanoid@postmarketos.org>
* SPDX-License-Identifier: GPL-3.0-or-later */
#include "MobileQmlViewStep.h"
#include "Branding.h"
#include "GlobalStorage.h"
#include "locale/TranslationsModel.h"
#include "modulesystem/ModuleManager.h"
#include "utils/Dirs.h"
#include "utils/Logger.h"
#include "utils/Variant.h"
#include <QProcess>
CALAMARES_PLUGIN_FACTORY_DEFINITION( MobileQmlViewStepFactory, registerPlugin< MobileQmlViewStep >(); )
void
MobileQmlViewStep::setConfigurationMap( const QVariantMap& configurationMap )
{
m_config->setConfigurationMap( configurationMap );
Calamares::QmlViewStep::setConfigurationMap( configurationMap );
}
MobileQmlViewStep::MobileQmlViewStep( QObject* parent )
: Calamares::QmlViewStep( parent )
, m_config( new Config( this ) )
{
}
void
MobileQmlViewStep::onLeave()
{
return;
}
bool
MobileQmlViewStep::isNextEnabled() const
{
return false;
}
bool
MobileQmlViewStep::isBackEnabled() const
{
return false;
}
bool
MobileQmlViewStep::isAtBeginning() const
{
return true;
}
bool
MobileQmlViewStep::isAtEnd() const
{
return true;
}
Calamares::JobList
MobileQmlViewStep::jobs() const
{
return m_config->createJobs();
}
QObject*
MobileQmlViewStep::getConfig()
{
return m_config;
}

View File

@@ -1,39 +0,0 @@
/* SPDX-FileCopyrightText: 2020 Oliver Smith <ollieparanoid@postmarketos.org>
* SPDX-License-Identifier: GPL-3.0-or-later */
#ifndef PARTITION_QMLVIEWSTEP_H
#define PARTITION_QMLVIEWSTEP_H
#include "Config.h"
#include "utils/PluginFactory.h"
#include "viewpages/QmlViewStep.h"
#include <DllMacro.h>
#include <QObject>
#include <QVariantMap>
class PLUGINDLLEXPORT MobileQmlViewStep : public Calamares::QmlViewStep
{
Q_OBJECT
public:
explicit MobileQmlViewStep( QObject* parent = nullptr );
bool isNextEnabled() const override;
bool isBackEnabled() const override;
bool isAtBeginning() const override;
bool isAtEnd() const override;
Calamares::JobList jobs() const override;
void setConfigurationMap( const QVariantMap& configurationMap ) override;
void onLeave() override;
QObject* getConfig() override;
private:
Config* m_config;
};
CALAMARES_PLUGIN_FACTORY_DECLARATION( MobileQmlViewStepFactory )
#endif // PARTITION_QMLVIEWSTEP_H

View File

@@ -1,136 +0,0 @@
/* SPDX-FileCopyrightText: 2020 Oliver Smith <ollieparanoid@postmarketos.org>
* SPDX-License-Identifier: GPL-3.0-or-later */
#include "PartitionJob.h"
#include "GlobalStorage.h"
#include "JobQueue.h"
#include "Settings.h"
#include "utils/CalamaresUtilsSystem.h"
#include "utils/Logger.h"
#include <QDir>
#include <QFileInfo>
PartitionJob::PartitionJob( const QString& cmdInternalStoragePrepare,
const QString& cmdLuksFormat,
const QString& cmdLuksOpen,
const QString& cmdMkfsRoot,
const QString& cmdMount,
const QString& targetDeviceRoot,
const QString& targetDeviceRootInternal,
bool installFromExternalToInternal,
bool isFdeEnabled,
const QString& password )
: Calamares::Job()
, m_cmdInternalStoragePrepare( cmdInternalStoragePrepare )
, m_cmdLuksFormat( cmdLuksFormat )
, m_cmdLuksOpen( cmdLuksOpen )
, m_cmdMkfsRoot( cmdMkfsRoot )
, m_cmdMount( cmdMount )
, m_targetDeviceRoot( targetDeviceRoot )
, m_targetDeviceRootInternal( targetDeviceRootInternal )
, m_installFromExternalToInternal( installFromExternalToInternal )
, m_isFdeEnabled( isFdeEnabled )
, m_password( password )
{
}
QString
PartitionJob::prettyName() const
{
return "Creating and formatting installation partition";
}
/* Fill the "global storage", so the following jobs (like unsquashfs) work.
The code is similar to modules/partition/jobs/FillGlobalStorageJob.cpp in
Calamares. */
void
FillGlobalStorage( const QString device, const QString pathMount )
{
using namespace Calamares;
GlobalStorage* gs = JobQueue::instance()->globalStorage();
QVariantList partitions;
QVariantMap partition;
/* See mapForPartition() in FillGlobalStorageJob.cpp */
partition[ "device" ] = device;
partition[ "mountPoint" ] = "/";
partition[ "claimed" ] = true;
/* Ignored by calamares modules used in combination with the "mobile"
* module, so we can get away with leaving them empty for now. */
partition[ "uuid" ] = "";
partition[ "fsName" ] = "";
partition[ "fs" ] = "";
partitions << partition;
gs->insert( "partitions", partitions );
gs->insert( "rootMountPoint", pathMount );
}
Calamares::JobResult
PartitionJob::exec()
{
using namespace Calamares;
using namespace CalamaresUtils;
using namespace std;
const QString pathMount = "/mnt/install";
const QString cryptName = "calamares_crypt";
QString cryptDev = "/dev/mapper/" + cryptName;
QString passwordStdin = m_password + "\n";
QString dev = m_targetDeviceRoot;
QList< QPair< QStringList, QString > > commands = {};
if ( m_installFromExternalToInternal )
{
dev = m_targetDeviceRootInternal;
commands.append( {
{ { "sh", "-c", m_cmdInternalStoragePrepare }, QString() },
} );
}
commands.append( { { { "mkdir", "-p", pathMount }, QString() } } );
if ( m_isFdeEnabled )
{
commands.append( {
{ { "sh", "-c", m_cmdLuksFormat + " " + dev }, passwordStdin },
{ { "sh", "-c", m_cmdLuksOpen + " " + dev + " " + cryptName }, passwordStdin },
{ { "sh", "-c", m_cmdMkfsRoot + " " + cryptDev }, QString() },
{ { "sh", "-c", m_cmdMount + " " + cryptDev + " " + pathMount }, QString() },
} );
}
else
{
commands.append( { { { "sh", "-c", m_cmdMkfsRoot + " " + dev }, QString() },
{ { "sh", "-c", m_cmdMount + " " + dev + " " + pathMount }, QString() } } );
}
foreach ( auto command, commands )
{
const QStringList args = command.first;
const QString stdInput = command.second;
const QString pathRoot = "/";
ProcessResult res
= System::runCommand( System::RunLocation::RunInHost, args, pathRoot, stdInput, chrono::seconds( 600 ) );
if ( res.getExitCode() )
{
return JobResult::error( "Command failed:<br><br>"
"'"
+ args.join( " " )
+ "'<br><br>"
" with output:<br><br>"
"'"
+ res.getOutput() + "'" );
}
}
FillGlobalStorage( m_isFdeEnabled ? cryptDev : dev, pathMount );
return JobResult::ok();
}

View File

@@ -1,38 +0,0 @@
/* SPDX-FileCopyrightText: 2020 Oliver Smith <ollieparanoid@postmarketos.org>
* SPDX-License-Identifier: GPL-3.0-or-later */
#pragma once
#include "Job.h"
class PartitionJob : public Calamares::Job
{
Q_OBJECT
public:
PartitionJob( const QString& cmdInternalStoragePrepare,
const QString& cmdLuksFormat,
const QString& cmdLuksOpen,
const QString& cmdMkfsRoot,
const QString& cmdMount,
const QString& targetDeviceRoot,
const QString& targetDeviceRootInternal,
bool installFromExternalToInternal,
bool isFdeEnabled,
const QString& password );
QString prettyName() const override;
Calamares::JobResult exec() override;
Calamares::JobList createJobs();
private:
QString m_cmdInternalStoragePrepare;
QString m_cmdLuksFormat;
QString m_cmdLuksOpen;
QString m_cmdMkfsRoot;
QString m_cmdMount;
QString m_targetDeviceRoot;
QString m_targetDeviceRootInternal;
bool m_installFromExternalToInternal;
bool m_isFdeEnabled;
QString m_password;
};

View File

@@ -1,92 +0,0 @@
/* SPDX-FileCopyrightText: 2020 Oliver Smith <ollieparanoid@postmarketos.org>
* SPDX-License-Identifier: GPL-3.0-or-later */
#include "UsersJob.h"
#include "GlobalStorage.h"
#include "JobQueue.h"
#include "Settings.h"
#include "utils/CalamaresUtilsSystem.h"
#include "utils/Logger.h"
#include <QDir>
#include <QFileInfo>
UsersJob::UsersJob( bool featureSshd,
const QString& cmdPasswd,
const QString& cmdUsermod,
const QString& cmdSshd,
const QString& cmdSshdUseradd,
bool isSshEnabled,
const QString& username,
const QString& password,
const QString& sshdUsername,
const QString& sshdPassword )
: Calamares::Job()
, m_featureSshd( featureSshd )
, m_cmdPasswd( cmdPasswd )
, m_cmdUsermod( cmdUsermod )
, m_cmdSshd( cmdSshd )
, m_cmdSshdUseradd( cmdSshdUseradd )
, m_isSshEnabled( isSshEnabled )
, m_username( username )
, m_password( password )
, m_sshdUsername( sshdUsername )
, m_sshdPassword( sshdPassword )
{
}
QString
UsersJob::prettyName() const
{
return "Configuring users";
}
Calamares::JobResult
UsersJob::exec()
{
using namespace Calamares;
using namespace CalamaresUtils;
using namespace std;
QList< QPair< QStringList, QString > > commands = {
{ { "sh", "-c", m_cmdUsermod }, m_username + "\n" }
};
commands.append( { { "sh", "-c", m_cmdPasswd + " " + m_username }, m_password + "\n" + m_password + "\n" } );
if ( m_featureSshd )
{
commands.append( { { "sh", "-c", m_cmdSshd }, QString() } );
if ( m_isSshEnabled )
{
commands.append( { { "sh", "-c", m_cmdSshdUseradd + " " + m_sshdUsername }, QString() } );
commands.append(
{ { "sh", "-c", m_cmdPasswd + " " + m_sshdUsername }, m_sshdPassword + "\n" + m_sshdPassword + "\n" } );
}
}
foreach ( auto command, commands )
{
auto location = System::RunLocation::RunInTarget;
const QString pathRoot = "/";
const QStringList args = command.first;
const QString stdInput = command.second;
ProcessResult res = System::runCommand( location, args, pathRoot, stdInput, chrono::seconds( 30 ) );
if ( res.getExitCode() )
{
return JobResult::error( "Command failed:<br><br>"
"'"
+ args.join( " " )
+ "'<br><br>"
" with output:<br><br>"
"'"
+ res.getOutput() + "'" );
}
}
return JobResult::ok();
}

View File

@@ -1,38 +0,0 @@
/* SPDX-FileCopyrightText: 2020 Oliver Smith <ollieparanoid@postmarketos.org>
* SPDX-License-Identifier: GPL-3.0-or-later */
#pragma once
#include "Job.h"
class UsersJob : public Calamares::Job
{
Q_OBJECT
public:
UsersJob( bool featureSshd,
const QString& cmdPasswd,
const QString& cmdUsermod,
const QString& cmdSshd,
const QString& cmdSshdUseradd,
bool isSshEnabled,
const QString& username,
const QString& password,
const QString& sshdUsername,
const QString& sshdPassword );
QString prettyName() const override;
Calamares::JobResult exec() override;
Calamares::JobList createJobs();
private:
bool m_featureSshd;
QString m_cmdPasswd;
QString m_cmdUsermod;
QString m_cmdSshd;
QString m_cmdSshdUseradd;
bool m_isSshEnabled;
QString m_username;
QString m_password;
QString m_sshdUsername;
QString m_sshdPassword;
};

View File

@@ -1,65 +0,0 @@
/* SPDX-FileCopyrightText: 2020 Oliver Smith <ollieparanoid@postmarketos.org>
* SPDX-License-Identifier: GPL-3.0-or-later */
import io.calamares.core 1.0
import io.calamares.ui 1.0
import QtQuick 2.10
import QtQuick.Controls 2.10
import QtQuick.Layouts 1.3
import org.kde.kirigami 2.7 as Kirigami
import QtGraphicalEffects 1.0
import QtQuick.Window 2.3
import QtQuick.VirtualKeyboard 2.1
Item {
anchors.left: parent.left
anchors.top: parent.top
anchors.right: parent.right
width: parent.width
height: parent.height
Text {
id: mainText
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
anchors.topMargin: 10
wrapMode: Text.WordWrap
text: "To protect your data in case your device gets stolen," +
" it is recommended to enable full disk encryption.<br>" +
"<br>" +
"If you enable full disk encryption, you will be asked for" +
" a password. Without this password, it is not possible to" +
" boot your device or access any data on it. Make sure that" +
" you don't lose this password!"
width: 200
}
Button {
id: firstButton
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: mainText.bottom
anchors.topMargin: 10
width: 200
text: qsTr("Enable")
onClicked: {
config.isFdeEnabled = true;
navNext();
}
}
Button {
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: firstButton.bottom
anchors.topMargin: 10
width: 200
text: qsTr("Disable")
onClicked: {
config.isFdeEnabled = false;
navNextFeature();
}
}
}

View File

@@ -1,80 +0,0 @@
/* SPDX-FileCopyrightText: 2020 Oliver Smith <ollieparanoid@postmarketos.org>
* SPDX-License-Identifier: GPL-3.0-or-later */
import io.calamares.core 1.0
import io.calamares.ui 1.0
import QtQuick 2.10
import QtQuick.Controls 2.10
import QtQuick.Layouts 1.3
import org.kde.kirigami 2.7 as Kirigami
import QtGraphicalEffects 1.0
import QtQuick.Window 2.3
import QtQuick.VirtualKeyboard 2.1
Item {
anchors.left: parent.left
anchors.top: parent.top
anchors.right: parent.right
width: parent.width
height: parent.height
TextField {
id: password
anchors.top: parent.top
placeholderText: qsTr("Password")
inputMethodHints: Qt.ImhPreferLowercase
echoMode: TextInput.Password
onTextChanged: validatePassword(password, passwordRepeat,
errorText)
text: config.fdePassword
onActiveFocusChanged: {
if(activeFocus) {
Qt.inputMethod.update(Qt.ImQueryInput);
}
}
anchors.horizontalCenter: parent.horizontalCenter
anchors.topMargin: 50
width: 200
}
TextField {
id: passwordRepeat
anchors.top: password.bottom
placeholderText: qsTr("Password (repeat)")
echoMode: TextInput.Password
onTextChanged: validatePassword(password, passwordRepeat,
errorText)
text: config.fdePassword
anchors.horizontalCenter: parent.horizontalCenter
anchors.topMargin: 10
width: 200
}
Text {
id: errorText
anchors.top: passwordRepeat.bottom
visible: false
anchors.horizontalCenter: parent.horizontalCenter
anchors.topMargin: 10
width: 200
wrapMode: Text.WordWrap
}
Button {
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: errorText.bottom
anchors.topMargin: 10
width: 200
text: qsTr("Continue")
onClicked: {
if (validatePassword(password, passwordRepeat, errorText)) {
config.fdePassword = password.text;
navNext();
}
}
}
}

View File

@@ -1,59 +0,0 @@
/* SPDX-FileCopyrightText: 2020 Undef <calamares@undef.tools>
* SPDX-License-Identifier: GPL-3.0-or-later */
import io.calamares.core 1.0
import io.calamares.ui 1.0
import QtQuick 2.10
import QtQuick.Controls 2.10
import QtQuick.Layouts 1.3
import org.kde.kirigami 2.7 as Kirigami
import QtGraphicalEffects 1.0
import QtQuick.Window 2.3
import QtQuick.VirtualKeyboard 2.1
Item {
anchors.left: parent.left
anchors.top: parent.top
anchors.right: parent.right
width: parent.width
height: parent.height
Text {
id: mainText
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
anchors.topMargin: 10
wrapMode: Text.WordWrap
text: "Select the filesystem for root partition. If unsure, leave the default."
width: 200
}
ComboBox {
id: fsTypeCB
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: mainText.bottom
anchors.topMargin: 10
width: 150
height: 30
editable: false
model: config.fsList
/* Save the current state on selection so it is there when the back button is pressed */
onActivated: config.fsType = fsTypeCB.currentText;
Component.onCompleted: fsTypeCB.currentIndex = find( config.fsType, Qt.MatchContains );
}
Button {
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: fsTypeCB.bottom
anchors.topMargin: 10
width: 200
text: qsTr("Continue")
onClicked: {
config.fsType = fsTypeCB.currentText;
navNextFeature();
}
}
}

View File

@@ -1,60 +0,0 @@
/* SPDX-FileCopyrightText: 2020 Oliver Smith <ollieparanoid@postmarketos.org>
* SPDX-License-Identifier: GPL-3.0-or-later */
import io.calamares.core 1.0
import io.calamares.ui 1.0
import QtQuick 2.10
import QtQuick.Controls 2.10
import QtQuick.Layouts 1.3
import org.kde.kirigami 2.7 as Kirigami
import QtGraphicalEffects 1.0
import QtQuick.Window 2.3
import QtQuick.VirtualKeyboard 2.1
Item {
anchors.left: parent.left
anchors.top: parent.top
anchors.right: parent.right
width: parent.width
height: parent.height
Text {
id: mainText
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
anchors.topMargin: 10
wrapMode: Text.WordWrap
text: (function() {
var ret = "Once you hit 'install', the installation will begin." +
" It will typically take a few minutes. Do not power off the" +
" device until it is done.<br>";
if (config.installFromExternalToInternal) {
ret += "<b>After the installation, your device will shutdown" +
" automatically. You must remove the external storage" +
" (SD card) before booting again.</b>" +
"<br><br>" +
"Otherwise, your device will boot into the installer" +
" again, and not into the installed system."
} else {
ret += "Afterwards, it will reboot into the installed system.";
}
return ret;
}())
width: 200
}
Button {
id: firstButton
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: mainText.bottom
anchors.topMargin: 10
width: 200
text: qsTr("Install")
onClicked: navFinish()
}
}

View File

@@ -1,64 +0,0 @@
/* SPDX-FileCopyrightText: 2020 Oliver Smith <ollieparanoid@postmarketos.org>
* SPDX-License-Identifier: GPL-3.0-or-later */
import io.calamares.core 1.0
import io.calamares.ui 1.0
import QtQuick 2.10
import QtQuick.Controls 2.10
import QtQuick.Layouts 1.3
import org.kde.kirigami 2.7 as Kirigami
import QtGraphicalEffects 1.0
import QtQuick.Window 2.3
import QtQuick.VirtualKeyboard 2.1
Item {
anchors.left: parent.left
anchors.top: parent.top
anchors.right: parent.right
width: parent.width
height: parent.height
Text {
id: mainText
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
anchors.topMargin: 10
wrapMode: Text.WordWrap
text: "The installation was started from an external storage medium." +
"<br>" +
"You can either install to the same medium and overwrite the" +
" installer, or install to the internal storage.<br>" +
"<br>" +
"Where would you like to install " + config.osName + "?"
width: 200
}
Button {
id: firstButton
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: mainText.bottom
anchors.topMargin: 10
width: 200
text: qsTr("Internal (eMMC)")
onClicked: {
config.installFromExternalToInternal = true;
navNext();
}
}
Button {
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: firstButton.bottom
anchors.topMargin: 10
width: 200
text: qsTr("External (SD card)")
onClicked: {
config.installFromExternalToInternal = false;
navNextFeature();
}
}
}

View File

@@ -1,58 +0,0 @@
/* SPDX-FileCopyrightText: 2020 Oliver Smith <ollieparanoid@postmarketos.org>
* SPDX-License-Identifier: GPL-3.0-or-later */
import io.calamares.core 1.0
import io.calamares.ui 1.0
import QtQuick 2.10
import QtQuick.Controls 2.10
import QtQuick.Layouts 1.3
import org.kde.kirigami 2.7 as Kirigami
import QtGraphicalEffects 1.0
import QtQuick.Window 2.3
import QtQuick.VirtualKeyboard 2.1
Item {
anchors.left: parent.left
anchors.top: parent.top
anchors.right: parent.right
width: parent.width
height: parent.height
Text {
id: mainText
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
anchors.topMargin: 10
wrapMode: Text.WordWrap
text: "Are you sure that you want to overwrite the internal storage?" +
"<br><br>" +
"<b>All existing data on the device will be lost!</b>"
width: 200
}
Button {
id: firstButton
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: mainText.bottom
anchors.topMargin: 10
width: 200
text: qsTr("Yes")
onClicked: {
navNext();
}
}
Button {
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: firstButton.bottom
anchors.topMargin: 10
width: 200
text: qsTr("No")
onClicked: {
navBack();
}
}
}

View File

@@ -1,173 +0,0 @@
# SPDX-FileCopyrightText: no
# SPDX-License-Identifier: CC0-1.0
#
# Commented out values are defaults.
# All commands are running with 'sh -c'.
---
# This entry exists only to keep the tests happy, remove it in
# any production configuration.
bogus: true
#######
### Target OS information
#######
## Operating System Name
# osName: "(unknown)"
## User Interface name (e.g. Plasma Mobile)
# userInterface: "(unknown)"
## User Interface assumes that the password is numeric (as of writing, this is
## the case with Plasma Mobile and Phosh)
# userPasswordNumeric: true
## OS version
# version: "(unknown)"
## Default username (for which the password will be set)
## Ensure also cmdUsermod command matches the default user, so it can be changed if desired.
# username: "user"
## reserved usernames (for user_pass username prompt and ssh_credentials)
# reservedUsernames:
# - adm
# - at
# - bin
# - colord
# - cron
# - cyrus
# - daemon
# - ftp
# - games
# - geoclue
# - guest
# - halt
# - lightdm
# - lp
# - mail
# - man
# - messagebus
# - news
# - nobody
# - ntp
# - operator
# - polkitd
# - postmaster
# - pulse
# - root
# - shutdown
# - smmsp
# - squid
# - sshd
# - sync
# - uucp
# - vpopmail
# - xfs
#######
### Target device information
#######
## Architecture (e.g. aarch64)
# arch: "(unknown)"
## Name of the device (e.g. PinePhone)
# device: "(unknown)"
## Partition that will be formatted and mounted (optionally with FDE) for the
## rootfs
# targetDeviceRoot: "/dev/unknown"
## Partition that will be formatted and mounted (optionally with FDE) for the
## rootfs, on internal storage. The installer OS must not set this, if it was
## booted from the internal storage (this is not checked in the mobile
## module!).
## If this is set, the user gets asked whether they want to install on internal
## or external storage. If the user chose internal storage,
## cmdInternalStoragePrepare (see below) runs before this partition gets
## formatted (see below). A note is displayed, that the device is powered off
## after installation and that the user should remove the external storage
## medium. So you need to adjust the installer OS to poweroff in that case, and
## not reboot. See postmarketos-ondev.git for reference.
# targetDeviceRootInternal: ""
######
### Installer Features
######
## Ask whether sshd should be enabled or not. If enabled, add a dedicated ssh
## user with proper username and password and suggest to change to key-based
## authentication after installation.
# featureSshd: true
## Ask the user, which filesystem to use.
# featureFsType: false
## Filesystems that the user can choose from.
#fsModel:
# - ext4
# - f2fs
# - btrfs
## Default filesystem to display in the dialog. If featureFsType is disabled,
## this gets used without asking the user.
# defaultFs: ext4
## Start Qt's virtual keyboard within the mobile module. Disable if you bring
## your own virtual keyboard (e.g. svkbd).
# builtinVirtualKeyboard: true
#######
### Commands running in the installer OS
#######
## Format the target partition with LUKS
## Arguments: <device>
## Stdin: password with \n
# cmdLuksFormat: "cryptsetup luksFormat --use-random"
## Open the formatted partition
## Arguments: <device> <mapping name>
## Stdin: password with \n
# cmdLuksOpen: "cryptsetup luksOpen"
## Format the rootfs with a file system
## Arguments: <device>
## Btrfs: to allow snapshots to work on the root subvolume, it is recommended that this
## command be a script which will create a subvolume and make it default
## An example can be found at:
## https://gitlab.com/mobian1/calamares-settings-mobian/-/merge_requests/2/diffs#diff-content-dde34f5f1c89e3dea63608c553bbc452dedf428f
# cmdMkfsRootBtrfs: "mkfs.btrfs -L 'unknownOS_root'"
# cmdMkfsRootExt4: "mkfs.ext4 -L 'unknownOS_root'"
# cmdMkfsRootF2fs: "mkfs.f2fs -l 'unknownOS_root'"
## Mount the partition after formatting with file system
## Arguments: <device> <mountpoint>
# cmdMount: "mount"
## When user selects installation from external storage to internal storage
## (see targetDeviceRootInternal above), use this command to prepare the
## internal storage medium. The command must create a partition table with
## two partitions (boot, root) and fill the boot partition. See the
## ondev-internal-storage-prepare.sh in postmarketos-ondev as example.
# cmdInternalStoragePrepare: "ondev-internal-storage-prepare"
#######
### Commands running in the target OS (chroot)
#######
## Change the username for the default user
## Stdin: username with \n
# cmdUsermod: "xargs -I{} -n1 usermod -m -d /home/{} -l {} -c {} user"
## Set the password for default user and sshd user
## Arguments: <username>
## Stdin: password twice, each time with \n
# cmdPasswd: "passwd"
## Enable or disable sshd
# cmdSshdEnable: "systemctl enable sshd.service"
# cmdSshdDisable: "systemctl disable sshd.service"
## Create the user for sshd
## Arguments: <username>
# cmdSshdUseradd: "useradd -G wheel -m"

View File

@@ -1,390 +0,0 @@
/* SPDX-FileCopyrightText: 2020 Oliver Smith <ollieparanoid@postmarketos.org>
* SPDX-License-Identifier: GPL-3.0-or-later */
import io.calamares.core 1.0
import io.calamares.ui 1.0
import QtQuick 2.10
import QtQuick.Controls 2.10
import QtQuick.Layouts 1.3
import org.kde.kirigami 2.7 as Kirigami
import QtGraphicalEffects 1.0
import QtQuick.Window 2.3
import QtQuick.VirtualKeyboard 2.1
Page
{
property var screen: "welcome"
property var screenPrevious: []
property var titles: {
"welcome": null, /* titlebar disabled */
"install_target": "Installation target",
"install_target_confirm": "Warning",
"user_pass": "User password",
"ssh_confirm": "SSH server",
"ssh_credentials": "SSH credentials",
"fs_selection": "Root filesystem",
"fde_confirm": "Full disk encryption",
"fde_pass": "Full disk encryption",
"install_confirm": "Ready to install",
"wait": null
}
property var features: [
{"name": "welcome",
"screens": ["welcome"]},
{"name": "installTarget",
"screens": ["install_target", "install_target_confirm"]},
{"name": "userPassword",
"screens": ["user_pass"]},
{"name": "sshd",
"screens": ["ssh_confirm", "ssh_credentials"]},
{"name": "fsType",
"screens": ["fs_selection"]},
{"name": "fde",
"screens": ["fde_confirm", "fde_pass"]},
{"name": "installConfirm",
"screens": ["install_confirm", "wait"]}
]
property var featureIdByScreen: (function() {
/* Put "features" above into an index of screen name -> feature id:
* featureIdByScreen = {"welcome": 0, "user_pass": 1, ...} */
var ret = {};
for (var i=0; i<features.length; i++) {
for (var j=0; j<features[i]["screens"].length; j++) {
ret[ features[i]["screens"][j] ] = i;
}
}
return ret;
}())
/* Only allow characters, that can be typed in with osk-sdl
* (src/keyboard.cpp). Details in big comment in validatePassword(). */
property var allowed_chars:
/* layer 0 */ "abcdefghijklmnopqrstuvwxyz" +
/* layer 1 */ "ABCDEFGHIJKLMNOPQRSTUVWXYZ" +
/* layer 2 */ "1234567890" + "@#$%&-_+()" + ",\"':;!?" +
/* layer 3 */ "~`|·√πτ÷×¶" + "©®£€¥^°*{}" + "\\/<>=[]" +
/* bottom row */ " ."
Item {
id: appContainer
anchors.left: parent.left
anchors.top: parent.top
anchors.right: parent.right
anchors.bottom: inputPanel.top
Item {
width: parent.width
height: parent.height
Rectangle {
id: mobileNavigation
width: parent.width
height: 30
color: "#e6e4e1"
Layout.fillWidth: true
border.width: 1
border.color: "#a7a7a7"
anchors.left: parent.left
anchors.right: parent.right
RowLayout {
width: parent.width
height: parent.height
spacing: 6
Button {
Layout.leftMargin: 6
id: mobileBack
text: "<"
background: Rectangle {
implicitWidth: 10
implicitHeight: 7
border.color: "#c1bab5"
border.width: 1
radius: 4
color: mobileBack.down ? "#dbdbdb" : "#f2f2f2"
}
onClicked: navBack()
}
Rectangle {
implicitHeight: 10
Layout.fillWidth: true
color: "#e6e4e1"
Text {
id: mobileTitle
text: ""
color: "#303638"
anchors.verticalCenter: parent.verticalCenter
anchors.horizontalCenter: parent.horizontalCenter
}
}
Rectangle {
color: "#e6e4e1"
Layout.rightMargin: 6
implicitWidth: 32
implicitHeight: 30
id: filler
}
}
}
Loader {
id: load
anchors.left: parent.left
anchors.top: mobileNavigation.bottom
anchors.right: parent.right
}
}
}
InputPanel {
id: inputPanel
y: Qt.inputMethod.visible ? parent.height - inputPanel.height : parent.height
visible: config.builtinVirtualKeyboard
anchors.left: parent.left
anchors.right: parent.right
}
Timer {
id: timer
}
function skipFeatureInstallTarget() {
return config.targetDeviceRootInternal == "";
}
/* Navigation related */
function navTo(name, historyPush=true) {
console.log("Navigating to screen: " + name);
if (historyPush)
screenPrevious.push(screen);
screen = name;
load.source = name + ".qml";
mobileNavigation.visible = (titles[name] !== null);
mobileTitle.text = "<b>" + titles[name] + "</b>";
Qt.inputMethod.hide();
}
function navFinish() {
/* Show a waiting screen and wait a second (so it can render). The big
* comment in Config.cpp::runPartitionJobThenLeave() explains why this
* is necessary. */
navTo("wait");
timer.interval = 1000;
timer.repeat = false;
timer.triggered.connect(function() {
/* Trigger Config.cpp::runPartitionJobThenLeave(). (We could expose
* the function directly with qmlRegisterSingletonType somehow, but
* I haven't seen existing Calamares code do that with the Config
* object, so just use the side effect of setting the variable, as
* done in existing code of Calamares modules.) */
config.runPartitionJobThenLeave = 1
});
timer.start();
}
function navNextFeature() {
var id;
/* Skip disabled features */
for (id = featureIdByScreen[screen] + 1; id < features.length; id++) {
/* First letter uppercase */
var name = features[id]["name"];
var nameUp = name.charAt(0).toUpperCase() + name.slice(1);
/* Check config.Feature<Name> */
var configOption = "feature" + nameUp;
if (config[configOption] === false) {
console.log("Skipping feature (disabled in config): " + name);
continue;
}
/* Check skipFeature<Name>() */
var funcName = "skipFeature" + nameUp;
if (eval("typeof " + funcName) === "function"
&& eval(funcName + "()")) {
console.log("Skipping feature (skip function): " + name);
continue;
}
break;
}
console.log("Navigating to feature: " + features[id]["name"]);
return navTo(features[id]["screens"][0]);
}
function navNext() {
var featureId = featureIdByScreen[screen];
var featureScreens = features[featureId]["screens"];
for (var i = 0; i<featureScreens.length; i++) {
/* Seek ahead until i is current screen */
if (featureScreens[i] != screen)
continue;
/* Navigate to next screen in same feature */
if (i + 1 < featureScreens.length) {
var screenNext = featureScreens[i + 1];
return navTo(screenNext);
}
/* Screen is last in feature */
return navNextFeature();
}
console.log("ERROR: navNext() failed for screen: " + screen);
}
function navBack() {
if (screenPrevious.length)
return navTo(screenPrevious.pop(), false);
ViewManager.back();
}
function onActivate() {
navTo(screen, false);
}
/* Input validation: show/clear failures */
function validationFailure(errorText, message="") {
errorText.text = message;
errorText.visible = true;
return false;
}
function validationFailureClear(errorText) {
errorText.text = "";
errorText.visible = false;
return true;
}
/* Input validation: user-screens (fde_pass, user_pass, ssh_credentials) */
function validatePin(userPin, userPinRepeat, errorText) {
var pin = userPin.text;
var repeat = userPinRepeat.text;
if (pin == "")
return validationFailure(errorText);
if (!pin.match(/^[0-9]*$/))
return validationFailure(errorText,
"Only digits are allowed.");
if (pin.length < 5)
return validationFailure(errorText,
"Too short: needs at least 5 digits.");
if (repeat == "")
return validationFailure(errorText);
if (repeat != pin)
return validationFailure(errorText,
"The PINs don't match.");
return validationFailureClear(errorText);
}
function validateUsername(username, errorText, extraReservedUsernames = []) {
var name = username.text;
var reserved = config.reservedUsernames.concat(extraReservedUsernames);
/* Validate characters */
for (var i = 0; i < name.length; i++) {
if (i) {
if (!name[i].match(/^[a-z0-9_-]$/))
return validationFailure(errorText,
"Characters must be lowercase" +
" letters, numbers,<br>" +
" underscores or minus signs.");
} else {
if (!name[i].match(/^[a-z_]$/))
return validationFailure(errorText,
"First character must be a" +
" lowercase letter or an" +
" underscore.");
}
}
/* Validate against reserved usernames */
for (var i = 0; i < reserved.length; i++) {
if (name == reserved[i])
return validationFailure(errorText, "Username '" +
reserved[i] +
"' is reserved.");
}
/* Passed */
return validationFailureClear(errorText);
}
function validateSshdUsername(username, errorText) {
return validateUsername(username, errorText, [config.username]);
}
function validateSshdPassword(password, passwordRepeat, errorText) {
var pass = password.text;
var repeat = passwordRepeat.text;
if (pass == "")
return validationFailure(errorText);
if (pass.length < 6)
return validationFailure(errorText,
"Too short: needs at least 6" +
" digits/characters.");
if (repeat == "")
return validationFailure(errorText);
if (pass != repeat)
return validationFailure(errorText, "Passwords don't match.");
return validationFailureClear(errorText);
}
function check_chars(input) {
for (var i = 0; i < input.length; i++) {
if (allowed_chars.indexOf(input[i]) == -1)
return false;
}
return true;
}
function allowed_chars_multiline() {
/* return allowed_chars split across multiple lines */
var step = 20;
var ret = "";
for (var i = 0; i < allowed_chars.length + step; i += step)
ret += allowed_chars.slice(i, i + step) + "\n";
return ret.trim();
}
function validatePassword(password, passwordRepeat, errorText) {
var pass = password.text;
var repeat = passwordRepeat.text;
if (pass == "")
return validationFailure(errorText);
/* This function gets called for the FDE password and for the user
* password. As of writing, all distributions shipping the mobile
* module are using osk-sdl to type in the FDE password after the
* installation, and another keyboard after booting up, to type in the
* user password. The osk-sdl password has the same keys as
* squeekboard's default layout, and other keyboards should be able to
* type these characters in as well. For now, verify that the password
* only contains characters that can be typed in by osk-sdl. If you
* need this to be more sophisticated, feel free to submit patches to
* make this more configurable. */
if (!check_chars(pass))
return validationFailure(errorText,
"The password must only contain" +
" these characters, others can possibly" +
" not be typed in after installation:\n" +
"\n" +
allowed_chars_multiline());
if (pass.length < 6)
return validationFailure(errorText,
"Too short: needs at least 6" +
" digits/characters.");
if (repeat == "")
return validationFailure(errorText);
if (pass != repeat)
return validationFailure(errorText, "Passwords don't match.");
return validationFailureClear(errorText);
}
}

View File

@@ -1,21 +0,0 @@
<!DOCTYPE RCC><RCC version="1.0">
<qresource>
<file>mobile.qml</file>
<file>welcome.qml</file>
<file>install_target.qml</file> <!-- install from external to internal? -->
<file>install_target_confirm.qml</file> <!-- overwrite internal storage? -->
<file>user_pass.qml</file> <!-- default user: username, password -->
<file>ssh_confirm.qml</file> <!-- sshd: enable or not? -->
<file>ssh_credentials.qml</file> <!-- sshd user: username, password -->
<file>fs_selection.qml</file> <!-- filesystem selection -->
<file>fde_confirm.qml</file> <!-- enable FDE or not? -->
<file>fde_pass.qml</file> <!-- FDE password (optional) -->
<file>install_confirm.qml</file> <!-- final confirmation before install -->
<file>wait.qml</file> <!-- please wait while partitioning -->
</qresource>
</RCC>

View File

@@ -1,68 +0,0 @@
/* SPDX-FileCopyrightText: 2020 Oliver Smith <ollieparanoid@postmarketos.org>
* SPDX-License-Identifier: GPL-3.0-or-later */
import io.calamares.core 1.0
import io.calamares.ui 1.0
import QtQuick 2.10
import QtQuick.Controls 2.10
import QtQuick.Layouts 1.3
import org.kde.kirigami 2.7 as Kirigami
import QtGraphicalEffects 1.0
import QtQuick.Window 2.3
import QtQuick.VirtualKeyboard 2.1
Item {
anchors.left: parent.left
anchors.top: parent.top
anchors.right: parent.right
width: parent.width
height: parent.height
Text {
id: mainText
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
anchors.topMargin: 30
wrapMode: Text.WordWrap
text: "If you don't know what SSH is, choose 'disable'.<br>" +
"<br>" +
"With 'enable', you will be asked for a second username and" +
" password. You will be able to login to the SSH server with" +
" these credentials via USB (172.16.42.1), Wi-Fi and possibly" +
" cellular network. It is recommended to replace the password" +
" with an SSH key after the installation.<br>" +
"<br>" +
"More information:<br>" +
"https://postmarketos.org/ssh"
width: 200
}
Button {
id: firstButton
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: mainText.bottom
anchors.topMargin: 40
width: 200
text: qsTr("Enable")
onClicked: {
config.isSshEnabled = true;
navNext();
}
}
Button {
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: firstButton.bottom
anchors.topMargin: 40
width: 200
text: qsTr("Disable")
onClicked: {
config.isSshEnabled = false;
navNextFeature();
}
}
}

View File

@@ -1,107 +0,0 @@
/* SPDX-FileCopyrightText: 2020 Oliver Smith <ollieparanoid@postmarketos.org>
* SPDX-License-Identifier: GPL-3.0-or-later */
import io.calamares.core 1.0
import io.calamares.ui 1.0
import QtQuick 2.10
import QtQuick.Controls 2.10
import QtQuick.Layouts 1.3
import org.kde.kirigami 2.7 as Kirigami
import QtGraphicalEffects 1.0
import QtQuick.Window 2.3
import QtQuick.VirtualKeyboard 2.1
Item {
anchors.left: parent.left
anchors.top: parent.top
anchors.right: parent.right
width: parent.width
height: parent.height
TextField {
id: username
anchors.top: parent.top
placeholderText: qsTr("SSH username")
inputMethodHints: Qt.ImhPreferLowercase
onTextChanged: validateSshdUsername(username, errorTextUsername)
text: config.sshdUsername
onActiveFocusChanged: {
if(activeFocus) {
Qt.inputMethod.update(Qt.ImQueryInput);
}
}
anchors.horizontalCenter: parent.horizontalCenter
anchors.topMargin: 50
width: 200
}
Text {
id: errorTextUsername
anchors.top: username.bottom
visible: false
wrapMode: Text.WordWrap
anchors.horizontalCenter: parent.horizontalCenter
anchors.topMargin: 50
width: 200
}
TextField {
id: password
anchors.top: errorTextUsername.bottom
placeholderText: qsTr("SSH password")
echoMode: TextInput.Password
onTextChanged: validateSshdPassword(password, passwordRepeat,
errorTextPassword)
text: config.sshdPassword
anchors.horizontalCenter: parent.horizontalCenter
anchors.topMargin: 50
width: 200
}
TextField {
id: passwordRepeat
anchors.top: password.bottom
placeholderText: qsTr("SSH password (repeat)")
echoMode: TextInput.Password
onTextChanged: validateSshdPassword(password, passwordRepeat,
errorTextPassword)
text: config.sshdPassword
anchors.horizontalCenter: parent.horizontalCenter
anchors.topMargin: 50
width: 200
}
Text {
id: errorTextPassword
anchors.top: passwordRepeat.bottom
visible: false
wrapMode: Text.WordWrap
anchors.horizontalCenter: parent.horizontalCenter
anchors.topMargin: 50
width: 200
}
Button {
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: errorTextPassword.bottom
anchors.topMargin: 40
width: 200
text: qsTr("Continue")
onClicked: {
if (validateSshdUsername(username, errorTextUsername) &&
validateSshdPassword(password, passwordRepeat,
errorTextPassword)) {
config.sshdUsername = username.text;
config.sshdPassword = password.text;
navNext();
}
}
}
}

View File

@@ -1,143 +0,0 @@
/* SPDX-FileCopyrightText: 2020 Oliver Smith <ollieparanoid@postmarketos.org>
* SPDX-License-Identifier: GPL-3.0-or-later */
import io.calamares.core 1.0
import io.calamares.ui 1.0
import QtQuick 2.10
import QtQuick.Controls 2.10
import QtQuick.Layouts 1.3
import org.kde.kirigami 2.7 as Kirigami
import QtGraphicalEffects 1.0
import QtQuick.Window 2.3
import QtQuick.VirtualKeyboard 2.1
Item {
property var passPlaceholder: (config.userPasswordNumeric
? "PIN"
: "Password")
property var hints: (config.userPasswordNumeric
? Qt.ImhDigitsOnly
: Qt.ImhPreferLowercase)
property var validatePassFunc: (config.userPasswordNumeric
? validatePin
: validatePassword);
property var validateNameFunc: validateUsername;
anchors.left: parent.left
anchors.top: parent.top
anchors.right: parent.right
width: parent.width
height: parent.height
Text {
id: usernameDescription
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
anchors.topMargin: 10
wrapMode: Text.WordWrap
text: (function() {
return "Set the username of your user. The default" +
" username is \"" + config.username + "\".";
}())
width: 200
}
TextField {
id: username
anchors.top: usernameDescription.bottom
placeholderText: qsTr("Username")
onTextChanged: validateNameFunc(username, errorText)
text: config.username
anchors.horizontalCenter: parent.horizontalCenter
anchors.topMargin: 10
width: 200
}
Text {
id: userPassDescription
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: username.bottom
anchors.topMargin: 10
wrapMode: Text.WordWrap
text: (function() {
if (config.userPasswordNumeric) {
return "Set the numeric password of your user. The" +
" lockscreen will ask for this PIN. This is" +
" <i>not</i> the PIN of your SIM card. Make sure to" +
" remember it.";
} else {
return "Set the password of your user. The lockscreen will" +
" ask for this password. Make sure to remember it.";
}
}())
width: 200
}
TextField {
id: userPass
anchors.top: userPassDescription.bottom
placeholderText: qsTr(passPlaceholder)
echoMode: TextInput.Password
onTextChanged: validatePassFunc(userPass, userPassRepeat, errorText)
text: config.userPassword
/* Let the virtual keyboard change to digits only */
inputMethodHints: hints
onActiveFocusChanged: {
if(activeFocus) {
Qt.inputMethod.update(Qt.ImQueryInput)
}
}
anchors.horizontalCenter: parent.horizontalCenter
anchors.topMargin: 10
width: 200
}
TextField {
id: userPassRepeat
anchors.top: userPass.bottom
placeholderText: qsTr(passPlaceholder + " (repeat)")
inputMethodHints: hints
echoMode: TextInput.Password
onTextChanged: validatePassFunc(userPass, userPassRepeat, errorText)
text: config.userPassword
anchors.horizontalCenter: parent.horizontalCenter
anchors.topMargin: 10
width: 200
}
Text {
anchors.top: userPassRepeat.bottom
id: errorText
visible: false
wrapMode: Text.WordWrap
anchors.horizontalCenter: parent.horizontalCenter
anchors.topMargin: 10
width: 200
}
Button {
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: errorText.bottom
anchors.topMargin: 10
width: 200
text: qsTr("Continue")
onClicked: {
if (validatePassFunc(userPass, userPassRepeat, errorText) && validateNameFunc(username, errorText)) {
config.userPassword = userPass.text;
config.username = username.text;
navNext();
}
}
}
}

View File

@@ -1,45 +0,0 @@
/* SPDX-FileCopyrightText: 2020 Oliver Smith <ollieparanoid@postmarketos.org>
* SPDX-License-Identifier: GPL-3.0-or-later */
import io.calamares.core 1.0
import io.calamares.ui 1.0
import QtQuick 2.10
import QtQuick.Controls 2.10
import QtQuick.Layouts 1.3
import org.kde.kirigami 2.7 as Kirigami
import QtGraphicalEffects 1.0
import QtQuick.Window 2.3
import QtQuick.VirtualKeyboard 2.1
Page
{
id: fdeWait
Item {
anchors.left: parent.left
anchors.top: parent.top
anchors.right: parent.right
width: parent.width
height: parent.height
Image {
id: logo
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
anchors.topMargin: 10
height: 50
fillMode: Image.PreserveAspectFit
source: "file:///usr/share/calamares/branding/default-mobile/logo.png"
}
Text {
id: waitText
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: logo.bottom
anchors.topMargin: 50
wrapMode: Text.WordWrap
text: "Formatting and mounting target partition. This may" +
" take up to ten minutes, please be patient."
width: 200
}
}
}

View File

@@ -1,65 +0,0 @@
/* SPDX-FileCopyrightText: 2020 Oliver Smith <ollieparanoid@postmarketos.org>
* SPDX-License-Identifier: GPL-3.0-or-later */
import io.calamares.core 1.0
import io.calamares.ui 1.0
import QtQuick 2.10
import QtQuick.Controls 2.10
import QtQuick.Layouts 1.3
import org.kde.kirigami 2.7 as Kirigami
import QtGraphicalEffects 1.0
import QtQuick.Window 2.3
import QtQuick.VirtualKeyboard 2.1
Page
{
id: welcome
Item {
id: appContainer
anchors.left: parent.left
anchors.top: parent.top
anchors.right: parent.right
Item {
width: parent.width
height: parent.height
Image {
id: logo
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
anchors.topMargin: 10
height: 50
fillMode: Image.PreserveAspectFit
source: "file:///usr/share/calamares/branding/default-mobile/logo.png"
}
Text {
id: mainText
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: logo.bottom
anchors.topMargin: 10
horizontalAlignment: Text.AlignRight
text: "You are about to install<br>" +
"<b>" + config.osName +
" " + config.version + "</b><br>" +
"user interface " +
"<b>" + config.userInterface + "</b><br>" +
"architecture " +
"<b>" + config.arch + "</b><br>" +
"on your <br>" +
"<b>" + config.device + "</b><br>"
width: 200
}
Button {
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: mainText.bottom
anchors.topMargin: 10
width: 200
text: qsTr("Continue")
onClicked: navNext()
}
}
}
}

View File

@@ -1,20 +0,0 @@
# The OS-FreeBSD module does "all the things" in a FreeBSD installation.
# Since the other modules -- users, fstab, grub, pretty much all of them
# -- are Linux-specific, it doesn't make much sense to fork each of them
# or provide alternatives, so instead we have one module that completes
# a FreeBSD installation based on the GlobalStorage values set by
# Calamares viewmodules.
#
# SPDX-FileCopyrightText: 2019 Adriaan de Groot <groot@kde.org>
# SPDX-License-Identifier: GPL-3.0-or-later
# License-Filename: LICENSE
#
calamares_add_plugin( os-freebsd
TYPE job
EXPORT_MACRO PLUGINDLLEXPORT_PRO
SOURCES
FreeBSDJob.cpp
SHARED_LIB
NO_CONFIG
)

View File

@@ -1,58 +0,0 @@
/* === This file is part of Calamares - <https://github.com/calamares> ===
*
* SPDX-FileCopyrightText: 2019 Adriaan de Groot <groot@kde.org>
* SPDX-License-Identifier: GPL-3.0-or-later
* License-Filename: LICENSE
*/
#include "FreeBSDJob.h"
#include "CalamaresVersion.h"
#include "GlobalStorage.h"
#include "JobQueue.h"
#include "utils/Logger.h"
#include <QDateTime>
#include <QProcess>
#include <QThread>
FreeBSDJob::FreeBSDJob( QObject* parent )
: Calamares::CppJob( parent )
{
}
FreeBSDJob::~FreeBSDJob() {}
QString
FreeBSDJob::prettyName() const
{
return tr( "FreeBSD Installation Job" );
}
Calamares::JobResult
FreeBSDJob::exec()
{
emit progress( 0.1 );
cDebug() << "[FREEBSD]";
Calamares::JobQueue::instance()->globalStorage()->debugDump();
emit progress( 0.5 );
QThread::sleep( 3 );
emit progress( 1.0 );
return Calamares::JobResult::ok();
}
void
FreeBSDJob::setConfigurationMap( const QVariantMap& configurationMap )
{
// TODO: actually fetch something from that configuration
m_configurationMap = configurationMap;
}
CALAMARES_PLUGIN_FACTORY_DEFINITION( FreeBSDJobFactory, registerPlugin< FreeBSDJob >(); )

View File

@@ -1,39 +0,0 @@
/* === This file is part of Calamares - <https://github.com/calamares> ===
*
* SPDX-FileCopyrightText: 2019 Adriaan de Groot <groot@kde.org>
* SPDX-License-Identifier: GPL-3.0-or-later
* License-Filename: LICENSE
*/
#ifndef FREEBSDJOB_H
#define FREEBSDJOB_H
#include "CppJob.h"
#include "DllMacro.h"
#include "utils/PluginFactory.h"
#include <QObject>
#include <QVariantMap>
class PLUGINDLLEXPORT FreeBSDJob : public Calamares::CppJob
{
Q_OBJECT
public:
explicit FreeBSDJob( QObject* parent = nullptr );
virtual ~FreeBSDJob() override;
QString prettyName() const override;
Calamares::JobResult exec() override;
void setConfigurationMap( const QVariantMap& configurationMap ) override;
private:
QVariantMap m_configurationMap;
};
CALAMARES_PLUGIN_FACTORY_DECLARATION( FreeBSDJobFactory )
#endif // FREEBSDJOB_H

View File

@@ -1,276 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# === This file is part of Calamares - <https://github.com/calamares> ===
#
# Calamares is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Calamares is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Calamares. If not, see <http://www.gnu.org/licenses/>.
#
# SPDX-FileCopyrightText: 2019 Adriaan de Groot <groot@kde.org>
# SPDX-License-Identifier: GPL-3.0-or-later
#
"""
=== NixOS Configuration
NixOS has its own "do all the things" configuration file which
declaratively handles what things need to be done in the target
system, and it has an existing tool to "execute" that declarative
specification. This module takes configuration values set by
Calamares viewmodules (e.g. the users module) and puts
them into the configuration file in the target system,
and then runs the necessary NixOS specific tools.
"""
import libcalamares
import os
from time import gmtime, strftime, sleep
import gettext
_ = gettext.translation("calamares-python",
localedir=libcalamares.utils.gettext_path(),
languages=libcalamares.utils.gettext_languages(),
fallback=True).gettext
# The following long **long** string is the entire text of
# a nix-configuration file. It is cribbed from, and adapted from,
# the sample file in https://github.com/itamar567/dotnix .
#
# We are going to substitute values into this text. However,
# Python's .format() function wants parens { } around variable
# names, and Nix's config file wants to use parens { } for block
# structure. So we have a compromise format here:
#
# - Write the config file as you would normally,
# - Write @@variable@@ instead of {variable}
#
# Some minor trickery later will massage this and substitute variables.
#
configuration_nix_sample = """# Nix configuration file
{ config, pkgs, ... }:
{
imports =
[ # Include the results of the hardware scan.
./hardware-configuration.nix
./command-not-found/command-not-found.nix
];
# Use the systemd-boot EFI boot loader.
boot.loader.systemd-boot.enable = true;
boot.loader.efi.canTouchEfiVariables = true;
# Use Zen Kernel
boot.kernelPackages = pkgs.linuxPackages_zen;
networking.hostName = "@@hostname@@"; # Define your hostname.
# networking.wireless.enable = true; # Enables wireless support via wpa_supplicant.
# Set your time zone.
time.timeZone = "@@timezone@@";
# The global useDHCP flag is deprecated, therefore explicitly set to false here.
networking.useDHCP = false;
networking.interfaces.enp42s0.useDHCP = true;
# Select internationalisation properties.
i18n.defaultLocale = "en_US.UTF-8";
# Configure X11
services.xserver = {
enable = true;
windowManager.i3 = {
enable = true;
package = pkgs.i3-gaps;
};
# Set i3 to the default session in the display manager
displayManager.defaultSession = "none+i3";
};
# SSH fix
programs.ssh.askPassword = pkgs.lib.mkForce "";
# Enable CUPS to print documents.
services.printing.enable = true;
# Enable sound.
sound.enable = true;
hardware.pulseaudio.enable = true;
# Define a user account. Don't forget to set a password with passwd.
users.users.username = {
isNormalUser = true;
extraGroups = [ "wheel" "libvirtd" ];
};
# Disable password for sudo
security.sudo.extraRules= [{
groups = [ "wheel" ];
commands = [{
command = "ALL" ;
options= [ "NOPASSWD" ];
}];
}];
# Set ZSH as the default shell
users.defaultUserShell = pkgs.zsh;
# clean /tmp on boot
boot.cleanTmpDir=true;
# Config packages
nixpkgs.config = {
allowUnfree = true;
chromium = {
enableWideVine = true;
};
};
# Automatically upgrade the system
# This service is a modified version of https://github.com/NixOS/nixpkgs/blob/nixos-21.05/nixos/modules/tasks/auto-upgrade.nix#L122
systemd = {
services.nixos-upgrade = {
description = "NixOS Upgrade";
# We use --upgrade, so we need internet access
wants = [ "network-online.target" ];
restartIfChanged = false;
unitConfig.X-StopOnRemoval = false;
serviceConfig.Type = "oneshot";
environment = config.nix.envVars // {
inherit (config.environment.sessionVariables) NIX_PATH;
HOME = "/root";
} // config.networking.proxy.envVars;
path = with pkgs; [
coreutils
gnutar
xz.bin
gzip
gitMinimal
config.nix.package.out
];
script = let
nixos-rebuild =
"${config.system.build.nixos-rebuild}/bin/nixos-rebuild";
in ''
${nixos-rebuild} boot --upgrade
booted="$(readlink /run/booted-system/{initrd,kernel,kernel-modules})"
built="$(readlink /nix/var/nix/profiles/system/{initrd,kernel,kernel-modules})"
if [ "$booted" = "$built" ]; then
${nixos-rebuild} switch
fi
'';
};
# To start the service at boot, we will use a systemd timer
timers.nixos-upgrade = {
wantedBy = [ "timers.target" ];
partOf = [ "nixos-upgrade.service" ];
timerConfig.OnBootSec = "5s";
};
};
# Some programs need SUID wrappers, can be configured further or are
# started in user sessions.
programs.mtr.enable = true;
programs.gnupg.agent = {
enable = true;
enableSSHSupport = true;
};
# Enable the OpenSSH daemon.
services.openssh.enable = true;
# Open ports in the firewall.
networking.firewall.enable = false;
# This value determines the NixOS release from which the default
# settings for stateful data, like file locations and database versions
# on your system were taken. Its perfectly fine and recommended to leave
# this value at the release version of the first install of this system.
# Before changing this value read the documentation for this option
# (e.g. man configuration.nix or on https://nixos.org/nixos/options.html).
system.stateVersion = "20.09"; # Did you read the comment?
}
"""
def pretty_name():
return _("NixOS Configuration.")
def catenate(d, key, *values):
"""
Sets @p d[key] to the string-concatenation of @p values
if none of the values are None.
This can be used to set keys conditionally based on
the values being found.
"""
if [v for v in values if v is None]:
return
d[key] = "".join(values)
def run():
"""NixOS Configuration."""
gs = libcalamares.globalstorage
text = configuration_nix_sample
# Collect variables to substitute into the main text
variables = dict()
catenate(variables, "hostname", gs.value("hostname"))
catenate(variables, "timezone", gs.value("locationRegion"), "/", gs.value("locationZone"))
# Check that all variables are used
for key in variables.keys():
pattern = "@@{key}@@".format(key=key)
if not pattern in text:
libcalamares.utils.warning("Variable '{key}' is not used.".format(key=key))
# Check that all patterns exist
import re
variable_pattern = re.compile("@@\w+@@")
for match in variable_pattern.finditer(text):
variable_name = text[match.start()+2:match.end()-2]
if not variable_name in variables:
libcalamares.utils.warning("Variable '{key}' is used but not defined.".format(key=variable_name))
# Do the substitutions
for key in variables.keys():
pattern = "@@{key}@@".format(key=key)
text = text.replace(pattern, str(variables[key]))
# Write the result to a temp-file, then run the main tool.
# There is no progress reporting from the tool, so it's going
# to seem like the module is hanging (see issue #1740).
configuration_filename = "/tmp/configuration.nix"
with open(configuration_filename, "w") as f:
f.write(text)
libcalamares.job.setprogress(0.1)
libcalamares.utils.check_target_env_call(["nix", configuration_filename])
# To indicate an error, return a tuple of:
# (message, detailed-error-message)
return None

View File

@@ -1,11 +0,0 @@
# SPDX-FileCopyrightText: no
# SPDX-License-Identifier: CC0-1.0
#
# The NixOS module writes a nix-configuration file and then calls
# the Nix configuration tool to do the actual work of building
# the target system.
---
type: "job"
name: "os-nixos"
interface: "python"
script: "main.py"

View File

@@ -1,6 +0,0 @@
# SPDX-FileCopyrightText: no
# SPDX-License-Identifier: CC0-1.0
---
hostname: my-nix-host
locationRegion: Asia
locationZone: Kolkata

View File

@@ -0,0 +1,68 @@
# === This file is part of Calamares - <https://calamares.io> ===
#
# SPDX-FileCopyrightText: 2020 Adriaan de Groot <groot@kde.org>
# SPDX-License-Identifier: BSD-2-Clause
#
find_package(${qtname} COMPONENTS Core Gui Widgets REQUIRED)
set(_extra_libraries "")
set(_extra_src "")
### OPTIONAL AppData XML support in PackageModel
#
#
option(BUILD_APPDATA "Support appdata: items in PackageChooser (requires QtXml)" ON)
if(BUILD_APPDATA)
find_package(${qtname} COMPONENTS Xml)
if(TARGET ${qtname}::Xml)
add_definitions(-DHAVE_APPDATA)
list(APPEND _extra_libraries ${qtname}::Xml)
list(APPEND _extra_src ItemAppData.cpp)
endif()
endif()
### OPTIONAL AppStream support in PackageModel
#
#
option(BUILD_APPSTREAM "Support appstream: items in PackageChooser (requires libappstream-qt)" ON)
if(BUILD_APPSTREAM)
find_package(AppStreamQt)
set_package_properties(
AppStreamQt
PROPERTIES
DESCRIPTION "Support for AppStream (cache) data"
URL "https://github.com/ximion/appstream"
PURPOSE "AppStream provides package data"
TYPE OPTIONAL
)
if(AppStreamQt_FOUND)
add_definitions(-DHAVE_APPSTREAM_VERSION=${AppStreamQt_VERSION_MAJOR})
list(APPEND _extra_libraries AppStreamQt)
list(APPEND _extra_src ItemAppStream.cpp)
endif()
endif()
calamares_add_plugin(packagechooser
TYPE viewmodule
EXPORT_MACRO PLUGINDLLEXPORT_PRO
SOURCES
Config.cpp
PackageChooserPage.cpp
PackageChooserViewStep.cpp
PackageModel.cpp
${_extra_src}
RESOURCES
packagechooser.qrc
UI
page_package.ui
LINK_PRIVATE_LIBRARIES
${_extra_libraries}
SHARED_LIB
)
# include(CalamaresAddTest)
# calamares_add_test(
# packagechoosertest
# GUI
# SOURCES Tests.cpp
# LIBRARIES calamares_viewmodule_packagechooser ${_extra_libraries}
# )

View File

@@ -0,0 +1,361 @@
/* === This file is part of Calamares - <https://calamares.io> ===
*
* SPDX-FileCopyrightText: 2021 Adriaan de Groot <groot@kde.org>
* SPDX-FileCopyrightText: 2021 Anke Boersma <demm@kaosx.us>
* SPDX-License-Identifier: GPL-3.0-or-later
*
* Calamares is Free Software: see the License-Identifier above.
*
*/
#include "Config.h"
#ifdef HAVE_APPDATA
#include "ItemAppData.h"
#endif
#ifdef HAVE_APPSTREAM_VERSION
#include "ItemAppStream.h"
#include <AppStreamQt/pool.h>
#include <memory>
#endif
#include "GlobalStorage.h"
#include "JobQueue.h"
#include "compat/Variant.h"
#include "packages/Globals.h"
#include "utils/Logger.h"
#include "utils/Variant.h"
/** @brief This removes any values from @p groups that match @p source
*
* This is used to remove duplicates from the netinstallAdd structure
* It iterates over @p groups and for each map in the list, if the
* "source" element matches @p source, it is removed from the returned
* list.
*/
static QVariantList
pruneNetinstallAdd( const QString& source, const QVariant& groups )
{
QVariantList newGroupList;
const QVariantList groupList = groups.toList();
for ( const QVariant& group : groupList )
{
QVariantMap groupMap = group.toMap();
if ( groupMap.value( "source", "" ).toString() != source )
{
newGroupList.append( groupMap );
}
}
return newGroupList;
}
const NamedEnumTable< PackageChooserMode >&
packageChooserModeNames()
{
static const NamedEnumTable< PackageChooserMode > names {
{ "optional", PackageChooserMode::Optional },
{ "required", PackageChooserMode::Required },
{ "optionalmultiple", PackageChooserMode::OptionalMultiple },
{ "requiredmultiple", PackageChooserMode::RequiredMultiple },
// and a bunch of aliases
{ "zero-or-one", PackageChooserMode::Optional },
{ "radio", PackageChooserMode::Required },
{ "one", PackageChooserMode::Required },
{ "set", PackageChooserMode::OptionalMultiple },
{ "zero-or-more", PackageChooserMode::OptionalMultiple },
{ "multiple", PackageChooserMode::RequiredMultiple },
{ "one-or-more", PackageChooserMode::RequiredMultiple }
};
return names;
}
const NamedEnumTable< PackageChooserMethod >&
PackageChooserMethodNames()
{
static const NamedEnumTable< PackageChooserMethod > names {
{ "legacy", PackageChooserMethod::Legacy },
{ "custom", PackageChooserMethod::Legacy },
{ "contextualprocess", PackageChooserMethod::Legacy },
{ "packages", PackageChooserMethod::Packages },
{ "netinstall-add", PackageChooserMethod::NetAdd },
{ "netinstall-select", PackageChooserMethod::NetSelect },
};
return names;
}
Config::Config( QObject* parent )
: Calamares::ModuleSystem::Config( parent )
, m_model( new PackageListModel( this ) )
, m_mode( PackageChooserMode::Required )
{
}
Config::~Config() {}
const PackageItem&
Config::introductionPackage() const
{
for ( int i = 0; i < m_model->packageCount(); ++i )
{
const auto& package = m_model->packageData( i );
if ( package.isNonePackage() )
{
return package;
}
}
static PackageItem* defaultIntroduction = nullptr;
if ( !defaultIntroduction )
{
const auto name = QT_TR_NOOP( "Package Selection" );
const auto description
= QT_TR_NOOP( "Please pick a product from the list. The selected product will be installed." );
defaultIntroduction = new PackageItem( QString(), name, description );
defaultIntroduction->screenshot = QPixmap( QStringLiteral( ":/images/no-selection.png" ) );
defaultIntroduction->name = Calamares::Locale::TranslatedString( name, metaObject()->className() );
defaultIntroduction->description
= Calamares::Locale::TranslatedString( description, metaObject()->className() );
}
return *defaultIntroduction;
}
static inline QString
make_gs_key( const Calamares::ModuleSystem::InstanceKey& key )
{
return QStringLiteral( "packagechooser_" ) + key.id();
}
void
Config::updateGlobalStorage( const QStringList& selected ) const
{
if ( m_packageChoice.has_value() )
{
cWarning() << "Inconsistent package choices -- both model and single-selection QML";
}
if ( m_method == PackageChooserMethod::Legacy )
{
QString value = selected.join( ',' );
Calamares::JobQueue::instance()->globalStorage()->insert( make_gs_key( m_defaultId ), value );
cDebug() << m_defaultId << "selected" << value;
}
else if ( m_method == PackageChooserMethod::Packages )
{
QStringList packageNames = m_model->getInstallPackagesForNames( selected );
cDebug() << m_defaultId << "packages to install" << packageNames;
Calamares::Packages::setGSPackageAdditions(
Calamares::JobQueue::instance()->globalStorage(), m_defaultId, packageNames );
}
else if ( m_method == PackageChooserMethod::NetAdd )
{
QVariantList netinstallDataList = m_model->getNetinstallDataForNames( selected );
if ( netinstallDataList.isEmpty() )
{
cWarning() << "No netinstall information found for " << selected;
}
else
{
// If an earlier packagechooser instance added this data to global storage, combine them
auto* gs = Calamares::JobQueue::instance()->globalStorage();
if ( gs->contains( "netinstallAdd" ) )
{
netinstallDataList
+= pruneNetinstallAdd( QStringLiteral( "packageChooser" ), gs->value( "netinstallAdd" ) );
}
gs->insert( "netinstallAdd", netinstallDataList );
}
}
else if ( m_method == PackageChooserMethod::NetSelect )
{
cDebug() << m_defaultId << "groups to select in netinstall" << selected;
QStringList newSelected = selected;
auto* gs = Calamares::JobQueue::instance()->globalStorage();
// If an earlier packagechooser instance added this data to global storage, combine them
if ( gs->contains( "netinstallSelect" ) )
{
auto selectedOrig = gs->value( "netinstallSelect" );
if ( selectedOrig.canConvert< QStringList >() )
{
newSelected += selectedOrig.toStringList();
}
else
{
cWarning() << "Invalid NetinstallSelect data in global storage. Earlier selections purged";
}
gs->remove( "netinstallSelect" );
}
gs->insert( "netinstallSelect", newSelected );
}
else
{
cWarning() << "Unknown packagechooser method" << smash( m_method );
}
}
void
Config::updateGlobalStorage() const
{
if ( m_model->packageCount() > 0 )
{
cWarning() << "Inconsistent package choices -- both model and single-selection QML";
}
if ( m_method == PackageChooserMethod::Legacy )
{
auto* gs = Calamares::JobQueue::instance()->globalStorage();
if ( m_packageChoice.has_value() )
{
gs->insert( make_gs_key( m_defaultId ), m_packageChoice.value() );
}
else
{
gs->remove( make_gs_key( m_defaultId ) );
}
}
else if ( m_method == PackageChooserMethod::Packages )
{
cWarning() << "Unsupported single-selection packagechooser method 'Packages'";
}
else
{
cWarning() << "Unknown packagechooser method" << smash( m_method );
}
}
void
Config::setPackageChoice( const QString& packageChoice )
{
if ( packageChoice.isEmpty() )
{
m_packageChoice.reset();
}
else
{
m_packageChoice = packageChoice;
}
emit packageChoiceChanged( m_packageChoice.value_or( QString() ) );
}
QString
Config::prettyName() const
{
return m_stepName ? m_stepName->get() : tr( "Packages" );
}
QString
Config::prettyStatus() const
{
return tr( "Install option: <strong>%1</strong>" ).arg( m_packageChoice.value_or( tr( "None" ) ) );
}
static void
fillModel( PackageListModel* model, const QVariantList& items )
{
if ( items.isEmpty() )
{
cWarning() << "No *items* for PackageChooser module.";
return;
}
#ifdef HAVE_APPSTREAM_VERSION
std::unique_ptr< AppStream::Pool > pool;
bool poolOk = false;
#endif
cDebug() << "Loading PackageChooser model items from config";
int item_index = 0;
for ( const auto& item_it : items )
{
++item_index;
QVariantMap item_map = item_it.toMap();
if ( item_map.isEmpty() )
{
cWarning() << "PackageChooser entry" << item_index << "is not valid.";
continue;
}
if ( item_map.contains( "appdata" ) )
{
#ifdef HAVE_XML
model->addPackage( fromAppData( item_map ) );
#else
cWarning() << "Loading AppData XML is not supported.";
#endif
}
else if ( item_map.contains( "appstream" ) )
{
#ifdef HAVE_APPSTREAM_VERSION
if ( !pool )
{
pool = std::make_unique< AppStream::Pool >();
pool->setLocale( QStringLiteral( "ALL" ) );
poolOk = pool->load();
}
if ( pool && poolOk )
{
model->addPackage( fromAppStream( *pool, item_map ) );
}
#else
cWarning() << "Loading AppStream data is not supported.";
#endif
}
else
{
model->addPackage( PackageItem( item_map ) );
}
}
cDebug() << Logger::SubEntry << "Loaded PackageChooser with" << model->packageCount() << "entries.";
}
void
Config::setConfigurationMap( const QVariantMap& configurationMap )
{
m_mode = packageChooserModeNames().find( Calamares::getString( configurationMap, "mode" ),
PackageChooserMode::Required );
m_method = PackageChooserMethodNames().find( Calamares::getString( configurationMap, "method" ),
PackageChooserMethod::Legacy );
if ( m_method == PackageChooserMethod::Legacy )
{
cDebug() << "Using module ID" << m_defaultId;
}
if ( configurationMap.contains( "items" ) )
{
fillModel( m_model, configurationMap.value( "items" ).toList() );
QString default_item_id = Calamares::getString( configurationMap, "default" );
if ( !default_item_id.isEmpty() )
{
for ( int item_n = 0; item_n < m_model->packageCount(); ++item_n )
{
QModelIndex item_idx = m_model->index( item_n, 0 );
QVariant item_id = m_model->data( item_idx, PackageListModel::IdRole );
if ( item_id.toString() == default_item_id )
{
m_defaultModelIndex = item_idx;
break;
}
}
}
}
else
{
setPackageChoice( Calamares::getString( configurationMap, "packageChoice" ) );
if ( m_method != PackageChooserMethod::Legacy )
{
cWarning() << "Single-selection QML module must use 'Legacy' method.";
}
}
bool labels_ok = false;
auto labels = Calamares::getSubMap( configurationMap, "labels", labels_ok );
if ( labels_ok )
{
if ( labels.contains( "step" ) )
{
m_stepName = new Calamares::Locale::TranslatedString( labels, "step" );
}
}
}

View File

@@ -0,0 +1,128 @@
/* === This file is part of Calamares - <https://calamares.io> ===
*
* SPDX-FileCopyrightText: 2021 Adriaan de Groot <groot@kde.org>
* SPDX-FileCopyrightText: 2021 Anke Boersma <demm@kaosx.us>
* SPDX-License-Identifier: GPL-3.0-or-later
*
* Calamares is Free Software: see the License-Identifier above.
*
*/
#ifndef PACKAGECHOOSER_CONFIG_H
#define PACKAGECHOOSER_CONFIG_H
#include "PackageModel.h"
#include "modulesystem/Config.h"
#include "modulesystem/InstanceKey.h"
#include <memory>
#include <optional>
enum class PackageChooserMode
{
Optional, // zero or one
Required, // exactly one
OptionalMultiple, // zero or more
RequiredMultiple // one or more
};
const NamedEnumTable< PackageChooserMode >& packageChooserModeNames();
enum class PackageChooserMethod
{
Legacy, // use contextualprocess or other custom
Packages, // use the packages module
NetAdd, // adds packages to the netinstall module
NetSelect, // makes selections in the netinstall module
};
const NamedEnumTable< PackageChooserMethod >& PackageChooserMethodNames();
class Config : public Calamares::ModuleSystem::Config
{
Q_OBJECT
/** @brief This is the single-select package-choice
*
* For (QML) modules that support only a single selection and
* just want to do things in a straightforward pick-this-one
* way, the packageChoice property is a (the) way to go.
*
* Writing to this property means that any other form of package-
* choice or selection is ignored.
*/
Q_PROPERTY( QString packageChoice READ packageChoice WRITE setPackageChoice NOTIFY packageChoiceChanged )
Q_PROPERTY( QString prettyStatus READ prettyStatus NOTIFY prettyStatusChanged FINAL )
public:
Config( QObject* parent = nullptr );
~Config() override;
/** @brief Sets the default Id for this Config
*
* The default Id is the (owning) module identifier for the config,
* and it is used when the Id read from the config file is empty.
* The **usual** configuration when using method *packages* is
* to rely on the default Id.
*/
void setDefaultId( const Calamares::ModuleSystem::InstanceKey& defaultId ) { m_defaultId = defaultId; }
void setConfigurationMap( const QVariantMap& ) override;
PackageChooserMode mode() const { return m_mode; }
PackageListModel* model() const { return m_model; }
QModelIndex defaultSelectionIndex() const { return m_defaultModelIndex; }
/** @brief Returns an "introductory package" which describes packagechooser
*
* If the model contains a "none" package, returns that one on
* the assumption that it is one to describe the whole; otherwise
* returns a totally generic description.
*/
const PackageItem& introductionPackage() const;
/** @brief Write selection to global storage
*
* Updates the GS keys for this packagechooser, marking all
* (and only) the packages in @p selected as selected.
*/
void updateGlobalStorage( const QStringList& selected ) const;
/** @brief Write selection to global storage
*
* Updates the GS keys for this packagechooser, marking **only**
* the package choice as selected. This assumes that the single-
* selection QML code is in use.
*/
void updateGlobalStorage() const;
QString packageChoice() const { return m_packageChoice.value_or( QString() ); }
void setPackageChoice( const QString& packageChoice );
QString prettyName() const;
QString prettyStatus() const;
signals:
void packageChoiceChanged( QString packageChoice );
void prettyStatusChanged();
private:
PackageListModel* m_model = nullptr;
QModelIndex m_defaultModelIndex;
/// Selection mode for this module
PackageChooserMode m_mode = PackageChooserMode::Optional;
/// How this module stores to GS
PackageChooserMethod m_method = PackageChooserMethod::Legacy;
/// Value to use for id if none is set in the config file
Calamares::ModuleSystem::InstanceKey m_defaultId;
/** @brief QML selection (for single-selection approaches)
*
* If there is no value, then there has been no selection.
* Reading the property will return an empty QString.
*/
std::optional< QString > m_packageChoice;
Calamares::Locale::TranslatedString* m_stepName; // As it appears in the sidebar
};
#endif

View File

@@ -0,0 +1,225 @@
/* === This file is part of Calamares - <https://calamares.io> ===
*
* SPDX-FileCopyrightText: 2019 Adriaan de Groot <groot@kde.org>
* SPDX-License-Identifier: GPL-3.0-or-later
*
* Calamares is Free Software: see the License-Identifier above.
*
*/
/** @brief Loading items from AppData XML files.
*
* Only used if QtXML is found, implements PackageItem::fromAppData().
*/
#include "PackageModel.h"
#include "utils/Logger.h"
#include "utils/Variant.h"
#include <QDomDocument>
#include <QDomNodeList>
#include <QFile>
/** @brief try to load the given @p fileName XML document
*
* Returns a QDomDocument, which will be valid iff the file can
* be read and contains valid XML data.
*/
static inline QDomDocument
loadAppData( const QString& fileName )
{
QFile file( fileName );
if ( !file.open( QIODevice::ReadOnly ) )
{
return QDomDocument();
}
QDomDocument doc( "AppData" );
if ( !doc.setContent( &file ) )
{
file.close();
return QDomDocument();
}
file.close();
return doc;
}
/** @brief gets the text of child element @p tagName
*/
static inline QString
getChildText( const QDomNode& n, const QString& tagName )
{
QDomElement e = n.firstChildElement( tagName );
return e.isNull() ? QString() : e.text();
}
/** @brief Gets a suitable screenshot path
*
* The <screenshots> element contains zero or more <screenshot>
* elements, which can have a *type* associated with them.
* Scan the screenshot elements, return the <image> path
* for the one labeled with type=default or, if there is no
* default, the first element.
*/
static inline QString
getScreenshotPath( const QDomNode& n )
{
QDomElement shotsNode = n.firstChildElement( "screenshots" );
if ( shotsNode.isNull() )
{
return QString();
}
const QDomNodeList shotList = shotsNode.childNodes();
int firstScreenshot = -1; // Use which screenshot node?
for ( int i = 0; i < shotList.count(); ++i )
{
if ( !shotList.at( i ).isElement() )
{
continue;
}
QDomElement e = shotList.at( i ).toElement();
if ( e.tagName() != "screenshot" )
{
continue;
}
// If none has the "type=default" attribute, use the first one
if ( firstScreenshot < 0 )
{
firstScreenshot = i;
}
// But type=default takes precedence.
if ( e.hasAttribute( "type" ) && e.attribute( "type" ) == "default" )
{
firstScreenshot = i;
break;
}
}
if ( firstScreenshot >= 0 )
{
return shotList.at( firstScreenshot ).firstChildElement( "image" ).text();
}
return QString();
}
/** @brief Returns language of the given element @p e
*
* Transforms the attribute value for xml:lang to something
* suitable for TranslatedString (e.g. [lang]).
*/
static inline QString
getLanguage( const QDomElement& e )
{
QString language = e.attribute( "xml:lang" );
if ( !language.isEmpty() )
{
language.replace( '-', '_' );
language.prepend( '[' );
language.append( ']' );
}
return language;
}
/** @brief Scan the list of @p children for @p tagname elements and add them to the map
*
* Uses @p mapname instead of @p tagname for the entries in map @p m
* to allow renaming from XML to map keys (in particular for
* TranslatedString). Also transforms xml:lang attributes to suitable
* key-decorations on @p mapname.
*/
static inline void
fillMap( QVariantMap& m, const QDomNodeList& children, const QString& tagname, const QString& mapname )
{
for ( int i = 0; i < children.count(); ++i )
{
if ( !children.at( i ).isElement() )
{
continue;
}
QDomElement e = children.at( i ).toElement();
if ( e.tagName() != tagname )
{
continue;
}
m[ mapname + getLanguage( e ) ] = e.text();
}
}
/** @brief gets the <name> and <description> elements
*
* Builds up a map of the <name> elements (which may have a *lang*
* attribute to indicate translations and paragraphs of the
* <description> element (also with lang). Uses the <summary>
* elements to supplement the description if no description
* is available for a given language.
*
* Returns a map with keys suitable for use by TranslatedString.
*/
static inline QVariantMap
getNameAndSummary( const QDomNode& n )
{
QVariantMap m;
const QDomNodeList children = n.childNodes();
fillMap( m, children, "name", "name" );
fillMap( m, children, "summary", "description" );
const QDomElement description = n.firstChildElement( "description" );
if ( !description.isNull() )
{
fillMap( m, description.childNodes(), "p", "description" );
}
return m;
}
PackageItem
fromAppData( const QVariantMap& item_map )
{
QString fileName = Calamares::getString( item_map, "appdata" );
if ( fileName.isEmpty() )
{
cWarning() << "Can't load AppData without a suitable key.";
return PackageItem();
}
cDebug() << "Loading AppData XML from" << fileName;
QDomDocument doc = loadAppData( fileName );
if ( doc.isNull() )
{
return PackageItem();
}
QDomElement componentNode = doc.documentElement();
if ( !componentNode.isNull() && componentNode.tagName() == "component" )
{
// An "id" entry in the Calamares config overrides ID in the AppData
QString id = Calamares::getString( item_map, "id" );
if ( id.isEmpty() )
{
id = getChildText( componentNode, "id" );
}
if ( id.isEmpty() )
{
return PackageItem();
}
// A "screenshot" entry in the Calamares config overrides AppData
QString screenshotPath = Calamares::getString( item_map, "screenshot" );
if ( screenshotPath.isEmpty() )
{
screenshotPath = getScreenshotPath( componentNode );
}
QVariantMap map = getNameAndSummary( componentNode );
map.insert( "id", id );
map.insert( "screenshot", screenshotPath );
return PackageItem( map );
}
return PackageItem();
}

View File

@@ -0,0 +1,28 @@
/* === This file is part of Calamares - <https://calamares.io> ===
*
* SPDX-FileCopyrightText: 2019 Adriaan de Groot <groot@kde.org>
* SPDX-License-Identifier: GPL-3.0-or-later
*
* Calamares is Free Software: see the License-Identifier above.
*
*/
#ifndef ITEMAPPDATA_H
#define ITEMAPPDATA_H
#include "PackageModel.h"
/** @brief Loads an AppData XML file and returns a PackageItem
*
* The @p map must have a key *appdata*. That is used as the
* primary source of information, but keys *id* and *screenshotPath*
* may be used to override parts of the AppData -- so that the
* ID is under the control of Calamares, and the screenshot can be
* forced to a local path available on the installation medium.
*
* Requires XML support in libcalamares, if not present will
* return invalid PackageItems.
*/
PackageItem fromAppData( const QVariantMap& map );
#endif

View File

@@ -0,0 +1,169 @@
/* === This file is part of Calamares - <https://calamares.io> ===
*
* SPDX-FileCopyrightText: 2019 Adriaan de Groot <groot@kde.org>
* SPDX-License-Identifier: GPL-3.0-or-later
*
* Calamares is Free Software: see the License-Identifier above.
*
*/
/** @brief Loading items from AppData XML files.
*
* Only used if QtXML is found, implements PackageItem::fromAppData().
*/
#include "PackageModel.h"
#include "locale/TranslationsModel.h"
#include "utils/Logger.h"
#include "utils/Variant.h"
#include <AppStreamQt/image.h>
#include <AppStreamQt/pool.h>
#include <AppStreamQt/screenshot.h>
/// @brief Return number of pixels in a size, for < ordering purposes
static inline quint64
sizeOrder( const QSize& size )
{
return static_cast<quint64>(size.width()) * static_cast<quint64>(size.height());
}
/// @brief Sets a screenshot in @p map from @p screenshot, if a usable one is found
static void
setScreenshot( QVariantMap& map, const AppStream::Screenshot& screenshot )
{
if ( screenshot.images().count() < 1 )
{
return;
}
// Pick the smallest
QUrl url;
quint64 size = sizeOrder( screenshot.images().first().size() );
for ( const auto& img : screenshot.images() )
{
if ( sizeOrder( img.size() ) <= size )
{
url = img.url();
}
}
if ( url.isValid() )
{
map.insert( "screenshot", url.toString() );
}
}
/// @brief Interpret an AppStream Component
static PackageItem
fromComponent( AppStream::Pool& pool, AppStream::Component& component )
{
#if HAVE_APPSTREAM_VERSION == 0
auto setActiveLocale = [&component](const QString & locale)
{
component.setActiveLocale( locale );
};
#else
auto setActiveLocale = [&pool](const QString & locale)
{
pool.setLocale( locale );
};
#endif
QVariantMap map;
map.insert( "id", component.id() );
map.insert( "package", component.packageNames().join( "," ) );
// Assume that the pool has loaded "ALL" locales, but it might be set
// to any of them; get the en_US locale as "untranslated" and then
// loop over Calamares locales (since there is no way to query for
// available locales in the Component) to see if there's anything else.
setActiveLocale( QStringLiteral( "en_US" ) );
QString en_name = component.name();
QString en_description = component.description();
map.insert( "name", en_name );
map.insert( "description", en_description );
for ( const QString& locale : Calamares::Locale::availableTranslations()->localeIds() )
{
setActiveLocale( locale );
QString name = component.name();
if ( name != en_name )
{
map.insert( QStringLiteral( "name[%1]" ).arg( locale ), name );
}
QString description = component.description();
if ( description != en_description )
{
map.insert( QStringLiteral( "description[%1]" ).arg( locale ), description );
}
}
#if HAVE_APPSTREAM_VERSION == 0
auto screenshots = component.screenshots();
#else
auto screenshots = component.screenshotsAll();
#endif
if ( screenshots.count() > 0 )
{
bool done = false;
for ( const auto& s : screenshots )
{
if ( s.isDefault() )
{
setScreenshot( map, s );
done = true;
break;
}
}
if ( !done )
{
setScreenshot( map, screenshots.first() );
}
}
return PackageItem( map );
}
PackageItem
fromAppStream( AppStream::Pool& pool, const QVariantMap& item_map )
{
QString appstreamId = Calamares::getString( item_map, "appstream" );
if ( appstreamId.isEmpty() )
{
cWarning() << "Can't load AppStream without a suitable appstreamId.";
return PackageItem();
}
cDebug() << "Loading AppStream data for" << appstreamId;
#if HAVE_APPSTREAM_VERSION == 0
auto itemList = pool.componentsById( appstreamId );
#else
auto itemList = pool.componentsById( appstreamId ).toList();
#endif
if ( itemList.count() < 1 )
{
cWarning() << "No AppStream data for" << appstreamId;
return PackageItem();
}
if ( itemList.count() > 1 )
{
cDebug() << "Multiple AppStream data for" << appstreamId << "using first.";
}
auto r = fromComponent( pool, itemList.first() );
if ( r.isValid() )
{
QString id = Calamares::getString( item_map, "id" );
QString screenshotPath = Calamares::getString( item_map, "screenshot" );
if ( !id.isEmpty() )
{
r.id = id;
}
if ( !screenshotPath.isEmpty() )
{
r.screenshot = screenshotPath;
}
}
return r;
}

View File

@@ -0,0 +1,34 @@
/* === This file is part of Calamares - <https://calamares.io> ===
*
* SPDX-FileCopyrightText: 2019 Adriaan de Groot <groot@kde.org>
* SPDX-License-Identifier: GPL-3.0-or-later
*
* Calamares is Free Software: see the License-Identifier above.
*
*/
#ifndef ITEMAPPSTREAM_H
#define ITEMAPPSTREAM_H
#include "PackageModel.h"
namespace AppStream
{
class Pool;
} // namespace AppStream
/** @brief Loads an item from AppStream data.
*
* The @p map must have a key *appstream*. That is used as the
* primary source of information from the AppStream cache, but
* keys *id* and *screenshotPath* may be used to override parts
* of the AppStream data -- so that the ID is under the control
* of Calamares, and the screenshot can be forced to a local path
* available on the installation medium.
*
* Requires AppStreamQt, if not present will return invalid
* PackageItems.
*/
PackageItem fromAppStream( AppStream::Pool& pool, const QVariantMap& map );
#endif

View File

@@ -0,0 +1,145 @@
/* === This file is part of Calamares - <https://calamares.io> ===
*
* SPDX-FileCopyrightText: 2019 Adriaan de Groot <groot@kde.org>
* SPDX-License-Identifier: GPL-3.0-or-later
*
* Calamares is Free Software: see the License-Identifier above.
*
*/
#include "PackageChooserPage.h"
#include "ui_page_package.h"
#include "utils/Gui.h"
#include "utils/Logger.h"
#include "utils/Retranslator.h"
#include <QLabel>
PackageChooserPage::PackageChooserPage( PackageChooserMode mode, QWidget* parent )
: QWidget( parent )
, ui( new Ui::PackageChooserPage )
, m_introduction( QString(),
QString(),
tr( "Package Selection" ),
tr( "Please pick a product from the list. The selected product will be installed." ) )
{
m_introduction.screenshot = QPixmap( QStringLiteral( ":/images/no-selection.png" ) );
ui->setupUi( this );
CALAMARES_RETRANSLATE( updateLabels(); );
switch ( mode )
{
case PackageChooserMode::Optional:
[[fallthrough]];
case PackageChooserMode::Required:
ui->products->setSelectionMode( QAbstractItemView::SingleSelection );
break;
case PackageChooserMode::OptionalMultiple:
[[fallthrough]];
case PackageChooserMode::RequiredMultiple:
ui->products->setSelectionMode( QAbstractItemView::ExtendedSelection );
}
ui->products->setMinimumWidth( 10 * Calamares::defaultFontHeight() );
}
void
PackageChooserPage::currentChanged( const QModelIndex& index )
{
if ( !index.isValid() || !ui->products->selectionModel()->hasSelection() )
{
ui->productName->setText( m_introduction.name.get() );
ui->productScreenshot->setPixmap( m_introduction.screenshot );
ui->productDescription->setText( m_introduction.description.get() );
}
else
{
const auto* model = ui->products->model();
ui->productName->setText( model->data( index, PackageListModel::NameRole ).toString() );
ui->productDescription->setText( model->data( index, PackageListModel::DescriptionRole ).toString() );
QPixmap currentScreenshot = model->data( index, PackageListModel::ScreenshotRole ).value< QPixmap >();
if ( currentScreenshot.isNull() )
{
ui->productScreenshot->setPixmap( m_introduction.screenshot );
}
else
{
ui->productScreenshot->setPixmap( currentScreenshot );
}
}
}
void
PackageChooserPage::updateLabels()
{
if ( ui && ui->products && ui->products->selectionModel() )
{
currentChanged( ui->products->selectionModel()->currentIndex() );
}
else
{
currentChanged( QModelIndex() );
}
emit selectionChanged();
}
void
PackageChooserPage::setModel( QAbstractItemModel* model )
{
ui->products->setModel( model );
currentChanged( QModelIndex() );
connect( ui->products->selectionModel(),
&QItemSelectionModel::selectionChanged,
this,
&PackageChooserPage::updateLabels );
}
void
PackageChooserPage::setSelection( const QModelIndex& index )
{
if ( index.isValid() )
{
ui->products->selectionModel()->select( index, QItemSelectionModel::Select );
}
currentChanged( index );
}
bool
PackageChooserPage::hasSelection() const
{
return ui && ui->products && ui->products->selectionModel() && ui->products->selectionModel()->hasSelection();
}
QStringList
PackageChooserPage::selectedPackageIds() const
{
if ( !( ui && ui->products && ui->products->selectionModel() ) )
{
return QStringList();
}
const auto* model = ui->products->model();
QStringList ids;
for ( const auto& index : ui->products->selectionModel()->selectedIndexes() )
{
QString pid = model->data( index, PackageListModel::IdRole ).toString();
if ( !pid.isEmpty() )
{
ids.append( pid );
}
}
return ids;
}
void
PackageChooserPage::setIntroduction( const PackageItem& item )
{
m_introduction.name = item.name;
m_introduction.description = item.description;
m_introduction.screenshot = item.screenshot;
}

View File

@@ -0,0 +1,57 @@
/* === This file is part of Calamares - <https://calamares.io> ===
*
* SPDX-FileCopyrightText: 2019 Adriaan de Groot <groot@kde.org>
* SPDX-License-Identifier: GPL-3.0-or-later
*
* Calamares is Free Software: see the License-Identifier above.
*
*/
#ifndef PACKAGECHOOSERPAGE_H
#define PACKAGECHOOSERPAGE_H
#include "Config.h"
#include "PackageModel.h"
#include <QAbstractItemModel>
#include <QWidget>
namespace Ui
{
class PackageChooserPage;
} // namespace Ui
class PackageChooserPage : public QWidget
{
Q_OBJECT
public:
explicit PackageChooserPage( PackageChooserMode mode, QWidget* parent = nullptr );
/// @brief Sets the data model for the listview
void setModel( QAbstractItemModel* model );
/// @brief Sets the introductory (no-package-selected) texts
void setIntroduction( const PackageItem& item );
/// @brief Selects a listview item
void setSelection( const QModelIndex& index );
/// @brief Is anything selected?
bool hasSelection() const;
/** @brief Get the list of selected ids
*
* This list may be empty (if none is selected).
*/
QStringList selectedPackageIds() const;
public slots:
void currentChanged( const QModelIndex& index );
void updateLabels();
signals:
void selectionChanged();
private:
Ui::PackageChooserPage* ui;
PackageItem m_introduction;
};
#endif // PACKAGECHOOSERPAGE_H

View File

@@ -0,0 +1,158 @@
/* === This file is part of Calamares - <https://calamares.io> ===
*
* SPDX-FileCopyrightText: 2019 Adriaan de Groot <groot@kde.org>
* SPDX-License-Identifier: GPL-3.0-or-later
*
* Calamares is Free Software: see the License-Identifier above.
*
*/
#include "PackageChooserViewStep.h"
#include "Config.h"
#include "PackageChooserPage.h"
#include "PackageModel.h"
#include "GlobalStorage.h"
#include "JobQueue.h"
#include "locale/TranslatableConfiguration.h"
#include "utils/Logger.h"
#include "utils/System.h"
#include "utils/Variant.h"
#include <QDesktopServices>
#include <QVariantMap>
CALAMARES_PLUGIN_FACTORY_DEFINITION( PackageChooserViewStepFactory, registerPlugin< PackageChooserViewStep >(); )
PackageChooserViewStep::PackageChooserViewStep( QObject* parent )
: Calamares::ViewStep( parent )
, m_config( new Config( this ) )
, m_widget( nullptr )
{
emit nextStatusChanged( false );
}
PackageChooserViewStep::~PackageChooserViewStep()
{
if ( m_widget && m_widget->parent() == nullptr )
{
m_widget->deleteLater();
}
}
QString
PackageChooserViewStep::prettyName() const
{
return m_config->prettyName();
}
QWidget*
PackageChooserViewStep::widget()
{
if ( !m_widget )
{
m_widget = new PackageChooserPage( m_config->mode(), nullptr );
connect( m_widget,
&PackageChooserPage::selectionChanged,
[ = ]() { emit nextStatusChanged( this->isNextEnabled() ); } );
hookupModel();
}
return m_widget;
}
bool
PackageChooserViewStep::isNextEnabled() const
{
if ( !m_widget )
{
// No way to have changed anything
return true;
}
switch ( m_config->mode() )
{
case PackageChooserMode::Optional:
case PackageChooserMode::OptionalMultiple:
// zero or one OR zero or more
return true;
case PackageChooserMode::Required:
case PackageChooserMode::RequiredMultiple:
// exactly one OR one or more
return m_widget->hasSelection();
}
__builtin_unreachable();
}
bool
PackageChooserViewStep::isBackEnabled() const
{
return true;
}
bool
PackageChooserViewStep::isAtBeginning() const
{
return true;
}
bool
PackageChooserViewStep::isAtEnd() const
{
return true;
}
void
PackageChooserViewStep::onActivate()
{
if ( !m_widget->hasSelection() )
{
m_widget->setSelection( m_config->defaultSelectionIndex() );
}
}
void
PackageChooserViewStep::onLeave()
{
m_config->updateGlobalStorage( m_widget->selectedPackageIds() );
}
Calamares::JobList
PackageChooserViewStep::jobs() const
{
Calamares::JobList l;
return l;
}
void
PackageChooserViewStep::setConfigurationMap( const QVariantMap& configurationMap )
{
m_config->setDefaultId( moduleInstanceKey() );
m_config->setConfigurationMap( configurationMap );
if ( m_widget )
{
hookupModel();
}
}
void
PackageChooserViewStep::hookupModel()
{
if ( !m_config->model() || !m_widget )
{
cError() << "Can't hook up model until widget and model both exist.";
return;
}
m_widget->setModel( m_config->model() );
m_widget->setIntroduction( m_config->introductionPackage() );
}

View File

@@ -0,0 +1,57 @@
/* === This file is part of Calamares - <https://calamares.io> ===
*
* SPDX-FileCopyrightText: 2019 Adriaan de Groot <groot@kde.org>
* SPDX-License-Identifier: GPL-3.0-or-later
*
* Calamares is Free Software: see the License-Identifier above.
*
*/
#ifndef PACKAGECHOOSERVIEWSTEP_H
#define PACKAGECHOOSERVIEWSTEP_H
#include "DllMacro.h"
#include "locale/TranslatableConfiguration.h"
#include "utils/PluginFactory.h"
#include "viewpages/ViewStep.h"
#include <QVariantMap>
class Config;
class PackageChooserPage;
class PLUGINDLLEXPORT PackageChooserViewStep : public Calamares::ViewStep
{
Q_OBJECT
public:
explicit PackageChooserViewStep( QObject* parent = nullptr );
~PackageChooserViewStep() override;
QString prettyName() const override;
QWidget* widget() override;
bool isNextEnabled() const override;
bool isBackEnabled() const override;
bool isAtBeginning() const override;
bool isAtEnd() const override;
void onActivate() override;
void onLeave() override;
Calamares::JobList jobs() const override;
void setConfigurationMap( const QVariantMap& configurationMap ) override;
private:
void hookupModel();
Config* m_config;
PackageChooserPage* m_widget;
};
CALAMARES_PLUGIN_FACTORY_DECLARATION( PackageChooserViewStepFactory )
#endif // PACKAGECHOOSERVIEWSTEP_H

View File

@@ -0,0 +1,196 @@
/* === This file is part of Calamares - <https://calamares.io> ===
*
* SPDX-FileCopyrightText: 2019 Adriaan de Groot <groot@kde.org>
* SPDX-License-Identifier: GPL-3.0-or-later
*
* Calamares is Free Software: see the License-Identifier above.
*
*/
#include "PackageModel.h"
#include "Branding.h"
#include "utils/Logger.h"
#include "utils/Variant.h"
#include <QFileInfo>
/** @brief A wrapper for Calamares::getSubMap that excludes the success param
*/
static QVariantMap
getSubMap( const QVariantMap& map, const QString& key )
{
bool success;
return Calamares::getSubMap( map, key, success );
}
static QPixmap
loadScreenshot( const QString& path )
{
if ( QFileInfo::exists( path ) )
{
return QPixmap( path );
}
const auto* branding = Calamares::Branding::instance();
if ( !branding )
{
return QPixmap();
}
return QPixmap( branding->componentDirectory() + QStringLiteral( "/" ) + path );
}
PackageItem::PackageItem() {}
PackageItem::PackageItem( const QString& a_id, const QString& a_name, const QString& a_description )
: id( a_id )
, name( a_name )
, description( a_description )
{
}
PackageItem::PackageItem( const QString& a_id,
const QString& a_name,
const QString& a_description,
const QString& screenshotPath )
: id( a_id )
, name( a_name )
, description( a_description )
, screenshot( screenshotPath )
{
}
PackageItem::PackageItem( const QVariantMap& item_map )
: id( Calamares::getString( item_map, "id" ) )
, name( Calamares::Locale::TranslatedString( item_map, "name" ) )
, description( Calamares::Locale::TranslatedString( item_map, "description" ) )
, screenshot( loadScreenshot( Calamares::getString( item_map, "screenshot" ) ) )
, packageNames( Calamares::getStringList( item_map, "packages" ) )
, netinstallData( getSubMap( item_map, "netinstall" ) )
{
if ( name.isEmpty() && id.isEmpty() )
{
name = QObject::tr( "No product" );
}
else if ( name.isEmpty() )
{
cWarning() << "PackageChooser item" << id << "has an empty name.";
}
if ( description.isEmpty() )
{
description = QObject::tr( "No description provided." );
}
}
PackageListModel::PackageListModel( QObject* parent )
: QAbstractListModel( parent )
{
}
PackageListModel::PackageListModel( PackageList&& items, QObject* parent )
: QAbstractListModel( parent )
, m_packages( std::move( items ) )
{
}
PackageListModel::~PackageListModel() {}
void
PackageListModel::addPackage( PackageItem&& p )
{
// Only add valid packages
if ( p.isValid() )
{
int c = m_packages.count();
beginInsertRows( QModelIndex(), c, c );
m_packages.append( p );
endInsertRows();
}
}
QStringList
PackageListModel::getInstallPackagesForName( const QString& id ) const
{
for ( const auto& p : qAsConst( m_packages ) )
{
if ( p.id == id )
{
return p.packageNames;
}
}
return QStringList();
}
QStringList
PackageListModel::getInstallPackagesForNames( const QStringList& ids ) const
{
QStringList l;
for ( const auto& p : qAsConst( m_packages ) )
{
if ( ids.contains( p.id ) )
{
l.append( p.packageNames );
}
}
return l;
}
QVariantList
PackageListModel::getNetinstallDataForNames( const QStringList& ids ) const
{
QVariantList l;
for ( auto& p : m_packages )
{
if ( ids.contains( p.id ) )
{
if ( !p.netinstallData.isEmpty() )
{
QVariantMap newData = p.netinstallData;
newData[ "source" ] = QStringLiteral( "packageChooser" );
l.append( newData );
}
}
}
return l;
}
int
PackageListModel::rowCount( const QModelIndex& index ) const
{
// For lists, valid indexes have zero children; only the root index has them
return index.isValid() ? 0 : m_packages.count();
}
QVariant
PackageListModel::data( const QModelIndex& index, int role ) const
{
if ( !index.isValid() )
{
return QVariant();
}
int row = index.row();
if ( row >= m_packages.count() || row < 0 )
{
return QVariant();
}
if ( role == Qt::DisplayRole /* Also PackageNameRole */ )
{
return m_packages[ row ].name.get();
}
else if ( role == DescriptionRole )
{
return m_packages[ row ].description.get();
}
else if ( role == ScreenshotRole )
{
return m_packages[ row ].screenshot;
}
else if ( role == IdRole )
{
return m_packages[ row ].id;
}
return QVariant();
}

View File

@@ -0,0 +1,135 @@
/* === This file is part of Calamares - <https://calamares.io> ===
*
* SPDX-FileCopyrightText: 2019 Adriaan de Groot <groot@kde.org>
* SPDX-License-Identifier: GPL-3.0-or-later
*
* Calamares is Free Software: see the License-Identifier above.
*
*/
#ifndef PACKAGEMODEL_H
#define PACKAGEMODEL_H
#include "locale/TranslatableConfiguration.h"
#include "utils/NamedEnum.h"
#include <QAbstractListModel>
#include <QObject>
#include <QPixmap>
#include <QVector>
struct PackageItem
{
QString id;
Calamares::Locale::TranslatedString name;
Calamares::Locale::TranslatedString description;
QPixmap screenshot;
QStringList packageNames;
QVariantMap netinstallData;
/// @brief Create blank PackageItem
PackageItem();
/** @brief Creates a PackageItem from given strings
*
* This constructor sets all the text members,
* but leaves the screenshot blank. Set that separately.
*/
PackageItem( const QString& id, const QString& name, const QString& description );
/** @brief Creates a PackageItem from given strings.
*
* Set all the text members and load the screenshot from the given
* @p screenshotPath, which may be a QRC path (:/path/in/qrc) or
* a filesystem path, whatever QPixmap understands.
*/
PackageItem( const QString& id, const QString& name, const QString& description, const QString& screenshotPath );
/** @brief Creates a PackageItem from a QVariantMap
*
* This is intended for use when loading PackageItems from a
* configuration map. It will look up the various keys in the map
* and handle translation strings as well.
*
* The following keys are used:
* - *id*: the identifier for this item; if it is the empty string
* then this is the special "no-package".
* - *name* (and *name[lang]*): for the name and its translations
* - *description* (and *description[lang]*)
* - *screenshot*: a path to a screenshot for this package
* - *packages*: a list of package names
*/
PackageItem( const QVariantMap& map );
/** @brief Is this item valid?
*
* A valid item has an untranslated name available.
*/
bool isValid() const { return !name.isEmpty(); }
/** @brief Is this a (the) No-Package package?
*
* There should be at most one No-Package item in a collection
* of PackageItems. That one will be used to describe a
* "no package" situation.
*/
bool isNonePackage() const { return id.isEmpty(); }
};
using PackageList = QVector< PackageItem >;
class PackageListModel : public QAbstractListModel
{
public:
PackageListModel( PackageList&& items, QObject* parent );
PackageListModel( QObject* parent );
~PackageListModel() override;
/** @brief Add a package @p to the model
*
* Only valid packages are added -- that is, they must have a name.
*/
void addPackage( PackageItem&& p );
int rowCount( const QModelIndex& index ) const override;
QVariant data( const QModelIndex& index, int role ) const override;
/// @brief Direct (non-abstract) access to package data
const PackageItem& packageData( int r ) const { return m_packages[ r ]; }
/// @brief Direct (non-abstract) count of package data
int packageCount() const { return m_packages.count(); }
/** @brief Does a name lookup (based on id) and returns the packages member
*
* If there is a package with the given @p id, returns its packages
* (e.g. the names of underlying packages to install for it); returns
* an empty list if the id is not found.
*/
QStringList getInstallPackagesForName( const QString& id ) const;
/** @brief Name-lookup all the @p ids and returns the packages members
*
* Concatenates installPackagesForName() for each id in @p ids.
*/
QStringList getInstallPackagesForNames( const QStringList& ids ) const;
/** @brief Does a name lookup (based on id) and returns the netinstall data
*
* If there is a package with an id in @p ids, returns their netinstall data
*
* returns a list of netinstall data or an emply list if none is found
*/
QVariantList getNetinstallDataForNames( const QStringList& ids ) const;
enum Roles : int
{
NameRole = Qt::DisplayRole,
DescriptionRole = Qt::UserRole,
ScreenshotRole,
IdRole
};
private:
PackageList m_packages;
};
#endif

View File

@@ -0,0 +1,84 @@
/* === This file is part of Calamares - <https://calamares.io> ===
*
* SPDX-FileCopyrightText: 2019 Adriaan de Groot <groot@kde.org>
* SPDX-License-Identifier: GPL-3.0-or-later
*
* Calamares is Free Software: see the License-Identifier above.
*
*/
#include "Tests.h"
#ifdef HAVE_APPDATA
#include "ItemAppData.h"
#endif
#ifdef HAVE_APPSTREAM_VERSION
#include "ItemAppStream.h"
#endif
#include "PackageModel.h"
#include "utils/Logger.h"
#include <QtTest/QtTest>
QTEST_MAIN( PackageChooserTests )
PackageChooserTests::PackageChooserTests() {}
PackageChooserTests::~PackageChooserTests() {}
void
PackageChooserTests::initTestCase()
{
Logger::setupLogLevel( Logger::LOGDEBUG );
}
void
PackageChooserTests::testBogus()
{
QVERIFY( true );
}
void
PackageChooserTests::testAppData()
{
// Path from the build-dir and from the running-the-test varies,
// for in-source build, for build/, and for tests-in-build/,
// so look in multiple places.
QString appdataName( "io.calamares.calamares.appdata.xml" );
for ( const auto& prefix : QStringList { "", "../", "../../../", "../../../../" } )
{
if ( QFile::exists( prefix + appdataName ) )
{
appdataName = prefix + appdataName;
break;
}
}
QVERIFY( QFile::exists( appdataName ) );
QVariantMap m;
m.insert( "appdata", appdataName );
#ifdef HAVE_XML
PackageItem p1 = fromAppData( m );
QVERIFY( p1.isValid() );
QCOMPARE( p1.id, QStringLiteral( "io.calamares.calamares.desktop" ) );
QCOMPARE( p1.name.get(), QStringLiteral( "Calamares" ) );
// The <description> entry has precedence
QCOMPARE( p1.description.get(), QStringLiteral( "Calamares is an installer program for Linux distributions." ) );
// .. but en_GB doesn't have an entry in description, so uses <summary>
QCOMPARE( p1.description.get( QLocale( "en_GB" ) ), QStringLiteral( "Calamares Linux Installer" ) );
QCOMPARE( p1.description.get( QLocale( "nl" ) ),
QStringLiteral( "Calamares is een installatieprogramma voor Linux distributies." ) );
QVERIFY( p1.screenshot.isNull() );
m.insert( "id", "calamares" );
m.insert( "screenshot", ":/images/calamares.png" );
PackageItem p2 = fromAppData( m );
QVERIFY( p2.isValid() );
QCOMPARE( p2.id, QStringLiteral( "calamares" ) );
QCOMPARE( p2.description.get( QLocale( "nl" ) ),
QStringLiteral( "Calamares is een installatieprogramma voor Linux distributies." ) );
QVERIFY( !p2.screenshot.isNull() );
#endif
}

View File

@@ -0,0 +1,28 @@
/* === This file is part of Calamares - <https://calamares.io> ===
*
* SPDX-FileCopyrightText: 2019 Adriaan de Groot <groot@kde.org>
* SPDX-License-Identifier: GPL-3.0-or-later
*
* Calamares is Free Software: see the License-Identifier above.
*
*/
#ifndef PACKAGECHOOSERTESTS_H
#define PACKAGECHOOSERTESTS_H
#include <QObject>
class PackageChooserTests : public QObject
{
Q_OBJECT
public:
PackageChooserTests();
~PackageChooserTests() override;
private Q_SLOTS:
void initTestCase();
void testBogus();
void testAppData();
};
#endif

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

View File

@@ -0,0 +1,2 @@
SPDX-FileCopyrightText: 2014 Teo Mrnjavac <teo@kde.org>
SPDX-License-Identifier: GPL-3.0-or-later

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@@ -0,0 +1,2 @@
SPDX-FileCopyrightText: 2014 Uri Herrera <uri_herrera@nitrux.in> and others
SPDX-License-Identifier: LGPL-3.0-or-later

View File

@@ -0,0 +1,172 @@
# SPDX-FileCopyrightText: no
# SPDX-License-Identifier: CC0-1.0
#
# Configuration for the low-density software chooser
---
# Software selection mode, to set whether the software packages
# can be chosen singly, or multiply.
#
# Possible modes are "optional", "required" (for zero-or-one or exactly-one)
# or "optionalmultiple", "requiredmultiple" (for zero-or-more
# or one-or-more).
mode: required
# Software installation method:
#
# - "legacy" or "custom" or "contextualprocess"
# When set to "legacy", writes a GlobalStorage value for the choice that
# has been made. The key is *packagechooser_<id>*. The module's
# instance name is used; see the *instances* section of `settings.conf`.
# If there is just one packagechooser module, and no special instance is set,
# resulting GS key is probably *packagechooser_packagechooser*.
#
# The GS value is a comma-separated list of the IDs of the selected
# packages, or an empty string if none is selected.
#
# With "legacy" installation, you should have a contextualprocess or similar
# module somewhere in the `exec` phase to process the GlobalStorage key
# and actually **do** something for the packages.
#
# - "packages"
# When set to "packages", writes GlobalStorage values suitable for
# consumption by the *packages* module (which should appear later
# in the `exec` section. These package settings will then be handed
# off to whatever package manager is configured there.
#
# - "netinstall-select"
# When this is set, the id(s) selected are passed to the netinstall module.
# Any id that matches a group name in that module is set to checked
#
# - "netinstall-add"
# With this method, the packagechooser module is used to add groups to the
# netinstall module. For this to have any effect. You must set netinstall,
# which is described below.
#
# There is no need to put this module in the `exec` section. There
# are no jobs that this module provides. You should put **other**
# modules, either *contextualprocess* or *packages* or some custom
# module, in the `exec` section to do the actual work.
method: legacy
# Human-visible strings in this module. These are all optional.
# The following translated keys are used:
# - *step*, used in the overall progress view (left-hand pane)
#
# Each key can have a [locale] added to it, which is used as
# the translated string for that locale. For the strings
# associated with the "no-selection" item, see *items*, below
# with the explicit item-*id* "".
#
labels:
step: "Packages"
step[nl]: "Pakketten"
# (Optional) item-*id* of pre-selected list-view item.
# Pre-selects one of the items below.
# default: kde
# Items to display in the chooser. In general, this should be a
# pretty short list to avoid overwhelming the UI. This is a list
# of objects, and the items are displayed in list order.
#
# Either provide the data for an item in the list (using the keys
# below), or use existing AppData XML files, or use AppStream cache
# as a source for the data.
#
# For data provided by the list: the item has an id, which is used in
# setting the value of *packagechooser_<module-id>*. The following field
# is mandatory:
#
# - *id*
# ID for the product. The ID "" is special, and is used for
# "no package selected". Only include this if the mode allows
# selecting none. The name and description given for the "no package
# selected" item are displayed when the module starts.
#
# Each item must adhere to one of three "styles" of item. Which styles
# are supported depends on compile-time dependencies of Calamares.
# Both AppData and AppStream may **optionally** be available.
#
# # Generic Items #
#
# These items are always supported. They require the most configuration
# **in this file** and duplicate information that may be available elsewhere
# (e.g. in AppData or AppStream), but do not require any additional
# dependencies. These items have the following **mandatory** fields:
#
# - *name*
# Human-readable name of the product. To provide translations,
# add a *[lang]* decoration as part of the key name, e.g. `name[nl]`
# for Dutch. The list of usable languages can be found in
# `CMakeLists.txt` or as part of the debug output of Calamares.
# - *description*
# Human-readable description. These can be translated as well.
# - *screenshot*
# Path to a single screenshot of the product. May be a filesystem
# path or a QRC path, e.g. ":/images/no-selection.png". If the path
# is not found (e.g. is a non-existent absolute path, or is a relative
# path that does not exist in the current working directory) then
# an additional attempt is made to load the image from the **branding**
# directory.
#
# The following fields are **optional** for an item:
#
# - *packages* :
# List of package names for the product. If using the *method*
# "packages", consider this item mandatory (because otherwise
# selecting the item would install no packages).
#
# - *netinstall* :
# The data in this field should follow the format of a group
# from the netinstall module documented in
# src/modules/netinstall/netinstall.conf. This is only used
# when method is set to "netinstall-add"
#
# # AppData Items #
#
# For data provided by AppData XML: the item has an *appdata*
# key which points to an AppData XML file in the local filesystem.
# This file is parsed to provide the id (from AppData id), name
# (from AppData name), description (from AppData description paragraphs
# or the summary entries), and a screenshot (the default screenshot
# from AppData). No package is set (but that is unused anyway).
#
# AppData may contain IDs that are not useful inside Calamares,
# and the screenshot URL may be remote -- a remote URL will not
# be loaded and the screenshot will be missing. An item with *appdata*
# **may** specify an ID or screenshot path, as above. This will override
# the settings from AppData.
#
# # AppStream Items #
#
# For data provided by AppStream cache: the item has an *appstream*
# key which matches the AppStream identifier in the cache (e.g.
# *org.kde.kwrite.desktop*). Data is retrieved from the AppStream
# cache for that ID. The package name is set from the AppStream data.
#
# An item for AppStream may also contain an *id* and a *screenshot*
# key which will override the data from AppStream.
items:
- id: ""
# packages: [] # This item installs no packages
name: "No Desktop"
name[nl]: "Geen desktop"
description: "Please pick a desktop environment from the list. If you don't want to install a desktop, that's fine, your system will start up in text-only mode and you can install a desktop environment later."
description[nl]: "Kies eventueel een desktop-omgeving uit deze lijst. Als u geen desktop-omgeving wenst te gebruiken, kies er dan geen. In dat geval start het systeem straks op in tekst-modus en kunt u later alsnog een desktop-omgeving installeren."
screenshot: ":/images/no-selection.png"
- id: kde
packages: [ kde-frameworks, kde-plasma, kde-gear ]
name: Plasma Desktop
description: "KDE Plasma Desktop, simple by default, a clean work area for real-world usage which intends to stay out of your way. Plasma is powerful when needed, enabling the user to create the workflow that makes them more effective to complete their tasks."
screenshot: ":/images/kde.png"
- id: gnome
packages: [ gnome-all ]
name: GNOME
description: GNU Networked Object Modeling Environment Desktop
screenshot: ":/images/gnome.png"
- id: calamares
appdata: ../io.calamares.calamares.appdata.xml
screenshot: ":/images/calamares.png"
- id: kate
appstream: org.kde.kwrite.desktop

View File

@@ -0,0 +1,7 @@
<RCC>
<qresource prefix="/">
<file>images/no-selection.png</file>
<file>images/calamares.png</file>
<file>images/if.png</file>
</qresource>
</RCC>

View File

@@ -0,0 +1,104 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<author>
SPDX-FileCopyrightText: 2019 Adriaan de Groot &lt;groot@kde.org&gt;
SPDX-License-Identifier: GPL-3.0-or-later
</author>
<class>PackageChooserPage</class>
<widget class="QWidget" name="PackageChooserPage">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>500</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
<horstretch>0</horstretch>
<verstretch>1</verstretch>
</sizepolicy>
</property>
<property name="windowTitle">
<string notr="true">Form</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<layout class="QHBoxLayout" name="horizontalLayout" stretch="1,4">
<item>
<widget class="QListView" name="products">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>1</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout" stretch="1,30,1">
<item>
<widget class="QLabel" name="productName">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="MinimumExpanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Product Name</string>
</property>
</widget>
</item>
<item>
<widget class="FixedAspectRatioLabel" name="productScreenshot">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>1</horstretch>
<verstretch>1</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>TextLabel</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="productDescription">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="MinimumExpanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Long Product Description</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
<property name="openExternalLinks">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>FixedAspectRatioLabel</class>
<extends>QLabel</extends>
<header>widgets/FixedAspectRatioLabel.h</header>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>

View File

@@ -0,0 +1,69 @@
# === This file is part of Calamares - <https://calamares.io> ===
#
# SPDX-FileCopyrightText: 2020 Adriaan de Groot <groot@kde.org>
# SPDX-FileCopyrightText: 2021 Anke Boersma <demm@kaosx.us>
# SPDX-License-Identifier: BSD-2-Clause
#
if( NOT Calamares_WITH_QML )
calamares_skip_module( "packagechooserq (QML is not supported in this build)" )
return()
endif()
find_package(${qtname} ${QT_VERSION} CONFIG REQUIRED Core)
# Add optional libraries here
set(USER_EXTRA_LIB)
# include_directories( ${CMAKE_CURRENT_SOURCE_DIR}/../packagechooser )
set(_packagechooser ${CMAKE_CURRENT_SOURCE_DIR}/../packagechooser)
include_directories(${_packagechooser})
### OPTIONAL AppData XML support in PackageModel
#
#
option(BUILD_APPDATA "Support appdata: items in PackageChooser (requires QtXml)" ON)
if(BUILD_APPDATA)
find_package(${qtname} COMPONENTS Xml)
if(TARGET ${qtname}::Xml)
add_definitions(-DHAVE_APPDATA)
list(APPEND _extra_libraries ${qtname}::Xml)
list(APPEND _extra_src ${_packagechooser}/ItemAppData.cpp)
endif()
endif()
### OPTIONAL AppStream support in PackageModel
#
#
option(BUILD_APPSTREAM "Support appstream: items in PackageChooser (requires libappstream-qt)" ON)
if(BUILD_APPSTREAM)
find_package(AppStreamQt)
set_package_properties(
AppStreamQt
PROPERTIES
DESCRIPTION "Support for AppStream (cache) data"
URL "https://github.com/ximion/appstream"
PURPOSE "AppStream provides package data"
TYPE OPTIONAL
)
if(AppStreamQt_FOUND)
add_definitions(-DHAVE_APPSTREAM_VERSION=${AppStreamQt_VERSION_MAJOR})
list(APPEND _extra_libraries AppStreamQt)
list(APPEND _extra_src ${_packagechooser}/ItemAppStream.cpp)
endif()
endif()
calamares_add_plugin(packagechooserq
TYPE viewmodule
EXPORT_MACRO PLUGINDLLEXPORT_PRO
SOURCES
PackageChooserQmlViewStep.cpp
${_packagechooser}/Config.cpp
${_packagechooser}/PackageModel.cpp
${_extra_src}
RESOURCES
packagechooserq${QT_VERSION_SUFFIX}.qrc
LINK_PRIVATE_LIBRARIES
calamaresui
${_extra_libraries}
SHARED_LIB
)

View File

@@ -0,0 +1,86 @@
/* === This file is part of Calamares - <https://calamares.io> ===
*
* SPDX-FileCopyrightText: 2019 Adriaan de Groot <groot@kde.org>
* SPDX-FileCopyrightText: 2021 Anke Boersma <demm@kaosx.us>
* SPDX-License-Identifier: GPL-3.0-or-later
*
* Calamares is Free Software: see the License-Identifier above.
*
*/
#include "PackageChooserQmlViewStep.h"
#include "GlobalStorage.h"
#include "JobQueue.h"
#include "locale/TranslatableConfiguration.h"
#include "utils/Logger.h"
#include "utils/System.h"
#include "utils/Variant.h"
CALAMARES_PLUGIN_FACTORY_DEFINITION( PackageChooserQmlViewStepFactory, registerPlugin< PackageChooserQmlViewStep >(); )
PackageChooserQmlViewStep::PackageChooserQmlViewStep( QObject* parent )
: Calamares::QmlViewStep( parent )
, m_config( new Config( this ) )
{
emit nextStatusChanged( true );
}
QString
PackageChooserQmlViewStep::prettyName() const
{
return m_config->prettyName();
}
QString
PackageChooserQmlViewStep::prettyStatus() const
{
//QString option = m_pkgc;
//return tr( "Install option: %1" ).arg( option );
return m_config->prettyStatus();
}
bool
PackageChooserQmlViewStep::isNextEnabled() const
{
return true;
}
bool
PackageChooserQmlViewStep::isBackEnabled() const
{
return true;
}
bool
PackageChooserQmlViewStep::isAtBeginning() const
{
return true;
}
bool
PackageChooserQmlViewStep::isAtEnd() const
{
return true;
}
Calamares::JobList
PackageChooserQmlViewStep::jobs() const
{
Calamares::JobList l;
return l;
}
void
PackageChooserQmlViewStep::onLeave()
{
m_config->updateGlobalStorage();
}
void
PackageChooserQmlViewStep::setConfigurationMap( const QVariantMap& configurationMap )
{
m_config->setDefaultId( moduleInstanceKey() );
m_config->setConfigurationMap( configurationMap );
Calamares::QmlViewStep::setConfigurationMap( configurationMap ); // call parent implementation last
}

View File

@@ -0,0 +1,58 @@
/* === This file is part of Calamares - <https://calamares.io> ===
*
* SPDX-FileCopyrightText: 2019 Adriaan de Groot <groot@kde.org>
* SPDX-FileCopyrightText: 2021 Anke Boersma <demm@kaosx.us>
* SPDX-License-Identifier: GPL-3.0-or-later
*
* Calamares is Free Software: see the License-Identifier above.
*
*/
#ifndef PACKAGECHOOSERQMLVIEWSTEP_H
#define PACKAGECHOOSERQMLVIEWSTEP_H
// Config from packagechooser module
#include "Config.h"
#include "DllMacro.h"
#include "locale/TranslatableConfiguration.h"
#include "utils/PluginFactory.h"
#include "viewpages/QmlViewStep.h"
#include <QVariantMap>
class Config;
class PackageChooserPage;
class PLUGINDLLEXPORT PackageChooserQmlViewStep : public Calamares::QmlViewStep
{
Q_OBJECT
public:
explicit PackageChooserQmlViewStep( QObject* parent = nullptr );
QString prettyName() const override;
QString prettyStatus() const override;
bool isNextEnabled() const override;
bool isBackEnabled() const override;
bool isAtBeginning() const override;
bool isAtEnd() const override;
//void onActivate() override;
void onLeave() override;
Calamares::JobList jobs() const override;
void setConfigurationMap( const QVariantMap& configurationMap ) override;
QObject* getConfig() override { return m_config; }
private:
Config* m_config;
};
CALAMARES_PLUGIN_FACTORY_DECLARATION( PackageChooserQmlViewStepFactory )
#endif // PACKAGECHOOSERQMLVIEWSTEP_H

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

View File

@@ -0,0 +1,2 @@
SPDX-FileCopyrightText: 2020 demmm <anke62@gmail.com>
SPDX-License-Identifier: GPL-3.0-or-later

Binary file not shown.

After

Width:  |  Height:  |  Size: 184 KiB

View File

@@ -0,0 +1,2 @@
SPDX-FileCopyrightText: 2020 demmm <anke62@gmail.com>
SPDX-License-Identifier: GPL-3.0-or-later

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

View File

@@ -0,0 +1,2 @@
SPDX-FileCopyrightText: 2021 pngegg <https://www.pngegg.com/>
SPDX-License-Identifier: GPL-3.0-or-later

View File

@@ -0,0 +1,304 @@
/* === This file is part of Calamares - <https://calamares.io> ===
*
* SPDX-FileCopyrightText: 2021 Anke Boersma <demm@kaosx.us>
* SPDX-License-Identifier: GPL-3.0-or-later
*
* Calamares is Free Software: see the License-Identifier above.
*
*/
import io.calamares.core 1.0
import io.calamares.ui 1.0
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
Item {
width: parent.width
height: parent.height
Rectangle {
anchors.fill: parent
color: "#f2f2f2"
ButtonGroup {
id: switchGroup
}
Column {
id: column
anchors.centerIn: parent
spacing: 5
Rectangle {
//id: rectangle
width: 700
height: 150
color: "#ffffff"
radius: 10
border.width: 0
Text {
width: 450
height: 104
anchors.centerIn: parent
text: qsTr("OpenRC base system.<br/>
Default option.")
font.pointSize: 10
anchors.verticalCenterOffset: -10
anchors.horizontalCenterOffset: 100
wrapMode: Text.WordWrap
}
Switch {
id: element1
x: 500
y: 110
width: 187
height: 14
text: qsTr("OpenRC")
checked: true
hoverEnabled: true
ButtonGroup.group: switchGroup
indicator: Rectangle {
implicitWidth: 40
implicitHeight: 14
radius: 10
color: element1.checked ? "#3498db" : "#B9B9B9"
border.color: element1.checked ? "#3498db" : "#cccccc"
Rectangle {
x: element1.checked ? parent.width - width : 0
y: (parent.height - height) / 2
width: 20
height: 20
radius: 10
color: element1.down ? "#cccccc" : "#ffffff"
border.color: element1.checked ? (element1.down ? "#3498db" : "#3498db") : "#999999"
}
}
onCheckedChanged: {
if ( checked ) {
config.packageChoice = "openrc"
}
}
}
Image {
id: image1
x: 8
y: 25
height: 100
fillMode: Image.PreserveAspectFit
source: "images/if.png"
}
}
Rectangle {
width: 700
height: 150
radius: 10
border.width: 0
Text {
width: 450
height: 104
anchors.centerIn: parent
text: qsTr("Dinit base system.")
font.pointSize: 10
anchors.verticalCenterOffset: -10
anchors.horizontalCenterOffset: 100
wrapMode: Text.WordWrap
}
Switch {
id: element2
x: 500
y: 110
width: 187
height: 14
text: qsTr("Dinit")
checked: false
hoverEnabled: true
ButtonGroup.group: switchGroup
indicator: Rectangle {
implicitWidth: 40
implicitHeight: 14
radius: 10
color: element2.checked ? "#3498db" : "#B9B9B9"
border.color: element2.checked ? "#3498db" : "#cccccc"
Rectangle {
x: element2.checked ? parent.width - width : 0
y: (parent.height - height) / 2
width: 20
height: 20
radius: 10
color: element2.down ? "#cccccc" : "#ffffff"
border.color: element2.checked ? (element2.down ? "#3498db" : "#3498db") : "#999999"
}
}
onCheckedChanged: {
if ( checked ) {
config.packageChoice = "dinit"
}
}
}
Image {
id: image2
x: 8
y: 25
height: 100
fillMode: Image.PreserveAspectFit
source: "images/if.png"
}
}
Rectangle {
width: 700
height: 150
color: "#ffffff"
radius: 10
border.width: 0
Text {
width: 450
height: 104
anchors.centerIn: parent
text: qsTr("Runit base system.")
font.pointSize: 10
anchors.verticalCenterOffset: -10
anchors.horizontalCenterOffset: 100
wrapMode: Text.WordWrap
}
Switch {
id: element3
x: 500
y: 110
width: 187
height: 14
text: qsTr("Runit")
checked: false
hoverEnabled: true
ButtonGroup.group: switchGroup
indicator: Rectangle {
implicitWidth: 40
implicitHeight: 14
radius: 10
color: element3.checked ? "#3498db" : "#B9B9B9"
border.color: element3.checked ? "#3498db" : "#cccccc"
Rectangle {
x: element3.checked ? parent.width - width : 0
y: (parent.height - height) / 2
width: 20
height: 20
radius: 10
color: element3.down ? "#cccccc" : "#ffffff"
border.color: element3.checked ? (element3.down ? "#3498db" : "#3498db") : "#999999"
}
}
onCheckedChanged: {
if ( checked ) {
config.packageChoice = "runit"
}
}
}
Image {
id: image3
x: 8
y: 25
height: 100
fillMode: Image.PreserveAspectFit
source: "images/if.png"
}
}
Rectangle {
width: 700
height: 150
color: "#ffffff"
radius: 10
border.width: 0
Text {
width: 450
height: 104
anchors.centerIn: parent
text: qsTr("S6 base system.")
font.pointSize: 10
anchors.verticalCenterOffset: -10
anchors.horizontalCenterOffset: 100
wrapMode: Text.WordWrap
}
Switch {
id: element4
x: 500
y: 110
width: 187
height: 14
text: qsTr("S6")
checked: false
hoverEnabled: true
ButtonGroup.group: switchGroup
indicator: Rectangle {
implicitWidth: 40
implicitHeight: 14
radius: 10
color: element4.checked ? "#3498db" : "#B9B9B9"
border.color: element4.checked ? "#3498db" : "#cccccc"
Rectangle {
x: element4.checked ? parent.width - width : 0
y: (parent.height - height) / 2
width: 20
height: 20
radius: 10
color: element4.down ? "#cccccc" : "#ffffff"
border.color: element4.checked ? (element4.down ? "#3498db" : "#3498db") : "#999999"
}
}
onCheckedChanged: {
if ( checked ) {
config.packageChoice = "s6"
}
}
}
Image {
id: image4
x: 8
y: 25
height: 100
fillMode: Image.PreserveAspectFit
source: "images/if.png"
}
}
Rectangle {
width: 700
height: 25
color: "#f2f2f2"
border.width: 0
Text {
height: 25
anchors.centerIn: parent
text: qsTr("Please select an option for your install, or use the default: OpenRC.")
font.pointSize: 10
wrapMode: Text.WordWrap
}
}
}
}
}

View File

@@ -0,0 +1,9 @@
<RCC>
<qresource>
<file alias="packagechooserq.qml">packagechooserq-qt6.qml</file>
<file>images/libreoffice.jpg</file>
<file>images/no-selection.png</file>
<file>images/plasma.png</file>
<file>images/if.png</file>
</qresource>
</RCC>

View File

@@ -0,0 +1,66 @@
# SPDX-FileCopyrightText: no
# SPDX-License-Identifier: CC0-1.0
#
# Configuration for the low-density software chooser, QML implementation
#
# The example QML implementation uses single-selection, rather than
# a model for the available packages. That makes it simpler: the
# QML itself codes the available options, descriptions and images
# -- after all, this is **low density** selection, so a custom UI
# can make sense for the few choices that need to be made.
#
#
---
# Software installation method:
#
# - "legacy" or "custom" or "contextualprocess"
# When set to "legacy", writes a GlobalStorage value for the choice that
# has been made. The key is *packagechooser_<id>*. The module's
# instance name is used; see the *instances* section of `settings.conf`.
# If there is just one packagechooserq module, and no special instance is set,
# resulting GS key is probably *packagechooser_packagechooserq*.
# (Do note that the prefix of the GS key remains "packagechooser_")
#
# The GS value is a comma-separated list of the IDs of the selected
# packages, or an empty string if none is selected.
#
# With "legacy" installation, you should have a contextualprocess or similar
# module somewhere in the `exec` phase to process the GlobalStorage key
# and actually **do** something for the packages.
#
# - "packages"
# When set to "packages", writes GlobalStorage values suitable for
# consumption by the *packages* module (which should appear later
# in the `exec` section. These package settings will then be handed
# off to whatever package manager is configured there.
#
# There is no need to put this module in the `exec` section. There
# are no jobs that this module provides. You should put **other**
# modules, either *contextualprocess* or *packages* or some custom
# module, in the `exec` section to do the actual work.
#
method: legacy
# Human-visible strings in this module. These are all optional.
# The following translated keys are used:
# - *step*, used in the overall progress view (left-hand pane)
#
# Each key can have a [locale] added to it, which is used as
# the translated string for that locale. For the strings
# associated with the "no-selection" item, see *items*, below
# with the explicit item-*id* "".
#
labels:
step: "Packages"
step[nl]: "Pakketten"
# The *packageChoice* value is used for setting the default selection
# in the QML view; this should match one of the keys used in the QML
# module for package names.
#
# (e.g. the sample QML uses "no_office_suite", "minimal_install" and
# "libreoffice" as possible choices).
#
packageChoice: libreoffice

View File

@@ -0,0 +1,304 @@
/* === This file is part of Calamares - <https://calamares.io> ===
*
* SPDX-FileCopyrightText: 2021 Anke Boersma <demm@kaosx.us>
* SPDX-License-Identifier: GPL-3.0-or-later
*
* 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
Item {
width: parent.width
height: parent.height
Rectangle {
anchors.fill: parent
color: "#f2f2f2"
ButtonGroup {
id: switchGroup
}
Column {
id: column
anchors.centerIn: parent
spacing: 5
Rectangle {
//id: rectangle
width: 700
height: 150
color: "#ffffff"
radius: 10
border.width: 0
Text {
width: 450
height: 104
anchors.centerIn: parent
text: qsTr("OpenRC base system.<br/>
Default option.")
font.pointSize: 10
anchors.verticalCenterOffset: -10
anchors.horizontalCenterOffset: 100
wrapMode: Text.WordWrap
}
Switch {
id: element1
x: 500
y: 110
width: 187
height: 14
text: qsTr("OpenRC")
checked: true
hoverEnabled: true
ButtonGroup.group: switchGroup
indicator: Rectangle {
implicitWidth: 40
implicitHeight: 14
radius: 10
color: element1.checked ? "#3498db" : "#B9B9B9"
border.color: element1.checked ? "#3498db" : "#cccccc"
Rectangle {
x: element1.checked ? parent.width - width : 0
y: (parent.height - height) / 2
width: 20
height: 20
radius: 10
color: element1.down ? "#cccccc" : "#ffffff"
border.color: element1.checked ? (element1.down ? "#3498db" : "#3498db") : "#999999"
}
}
onCheckedChanged: {
if ( checked ) {
config.packageChoice = "openrc"
}
}
}
Image {
id: image1
x: 8
y: 25
height: 100
fillMode: Image.PreserveAspectFit
source: "images/if.png"
}
}
Rectangle {
width: 700
height: 150
radius: 10
border.width: 0
Text {
width: 450
height: 104
anchors.centerIn: parent
text: qsTr("Dinit base system.")
font.pointSize: 10
anchors.verticalCenterOffset: -10
anchors.horizontalCenterOffset: 100
wrapMode: Text.WordWrap
}
Switch {
id: element2
x: 500
y: 110
width: 187
height: 14
text: qsTr("Dinit")
checked: false
hoverEnabled: true
ButtonGroup.group: switchGroup
indicator: Rectangle {
implicitWidth: 40
implicitHeight: 14
radius: 10
color: element2.checked ? "#3498db" : "#B9B9B9"
border.color: element2.checked ? "#3498db" : "#cccccc"
Rectangle {
x: element2.checked ? parent.width - width : 0
y: (parent.height - height) / 2
width: 20
height: 20
radius: 10
color: element2.down ? "#cccccc" : "#ffffff"
border.color: element2.checked ? (element2.down ? "#3498db" : "#3498db") : "#999999"
}
}
onCheckedChanged: {
if ( checked ) {
config.packageChoice = "dinit"
}
}
}
Image {
id: image2
x: 8
y: 25
height: 100
fillMode: Image.PreserveAspectFit
source: "images/if.png"
}
}
Rectangle {
width: 700
height: 150
color: "#ffffff"
radius: 10
border.width: 0
Text {
width: 450
height: 104
anchors.centerIn: parent
text: qsTr("Runit base system.")
font.pointSize: 10
anchors.verticalCenterOffset: -10
anchors.horizontalCenterOffset: 100
wrapMode: Text.WordWrap
}
Switch {
id: element3
x: 500
y: 110
width: 187
height: 14
text: qsTr("Runit")
checked: false
hoverEnabled: true
ButtonGroup.group: switchGroup
indicator: Rectangle {
implicitWidth: 40
implicitHeight: 14
radius: 10
color: element3.checked ? "#3498db" : "#B9B9B9"
border.color: element3.checked ? "#3498db" : "#cccccc"
Rectangle {
x: element3.checked ? parent.width - width : 0
y: (parent.height - height) / 2
width: 20
height: 20
radius: 10
color: element3.down ? "#cccccc" : "#ffffff"
border.color: element3.checked ? (element3.down ? "#3498db" : "#3498db") : "#999999"
}
}
onCheckedChanged: {
if ( checked ) {
config.packageChoice = "runit"
}
}
}
Image {
id: image3
x: 8
y: 25
height: 100
fillMode: Image.PreserveAspectFit
source: "images/if.png"
}
}
Rectangle {
width: 700
height: 150
color: "#ffffff"
radius: 10
border.width: 0
Text {
width: 450
height: 104
anchors.centerIn: parent
text: qsTr("S6 base system.")
font.pointSize: 10
anchors.verticalCenterOffset: -10
anchors.horizontalCenterOffset: 100
wrapMode: Text.WordWrap
}
Switch {
id: element4
x: 500
y: 110
width: 187
height: 14
text: qsTr("S6")
checked: false
hoverEnabled: true
ButtonGroup.group: switchGroup
indicator: Rectangle {
implicitWidth: 40
implicitHeight: 14
radius: 10
color: element4.checked ? "#3498db" : "#B9B9B9"
border.color: element4.checked ? "#3498db" : "#cccccc"
Rectangle {
x: element4.checked ? parent.width - width : 0
y: (parent.height - height) / 2
width: 20
height: 20
radius: 10
color: element4.down ? "#cccccc" : "#ffffff"
border.color: element4.checked ? (element4.down ? "#3498db" : "#3498db") : "#999999"
}
}
onCheckedChanged: {
if ( checked ) {
config.packageChoice = "s6"
}
}
}
Image {
id: image4
x: 8
y: 25
height: 100
fillMode: Image.PreserveAspectFit
source: "images/if.png"
}
}
Rectangle {
width: 700
height: 25
color: "#f2f2f2"
border.width: 0
Text {
height: 25
anchors.centerIn: parent
text: qsTr("Please select an option for your install, or use the default: OpenRC.")
font.pointSize: 10
wrapMode: Text.WordWrap
}
}
}
}
}

View File

@@ -0,0 +1,9 @@
<RCC>
<qresource>
<file>packagechooserq.qml</file>
<file>images/libreoffice.jpg</file>
<file>images/no-selection.png</file>
<file>images/plasma.png</file>
<file>images/if.png</file>
</qresource>
</RCC>

80
modules/postcfg/main.py Normal file
View File

@@ -0,0 +1,80 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# === This file is part of Calamares - <http://github.com/calamares> ===
#
# Copyright 2014 - 2016, Philip Müller <philm@manjaro.org>
# Copyright 2016, Artoo <artoo@manjaro.org>
# Copyright 2018, Artoo <artoo@artixlinux.org>
#
# Calamares is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Calamares is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Calamares. If not, see <http://www.gnu.org/licenses/>.
from os.path import join, exists
import libcalamares
from libcalamares.utils import target_env_call
class ConfigController:
"""Configuration controller
"""
def __init__(self):
self.root = libcalamares.globalstorage.value("rootMountPoint")
def terminate(self, proc):
"""Send SIGKILL to the given proccess
"""
target_env_call(['killall', '-9', proc])
def sedFile(self, pattern, file):
"""Sed the given file with the given pattern
"""
target_env_call(["sed", "-e", pattern, "-i", file])
def configure(self):
"""Configure the services
"""
if exists(join(self.root, "/etc/conf.d/keymaps")):
exp = 's|^.*keymap=.*|keymap="{}"|'.format(
libcalamares.globalstorage.value("keyboardLayout")
)
self.sedFile(exp, "/etc/conf.d/keymaps")
if exists(join(self.root, "/etc/conf.d/xdm")):
for dm in libcalamares.globalstorage.value("displayManagers"):
exp = 's|^.*DISPLAYMANAGER=.*|DISPLAYMANAGER="{}"|'.format(dm)
self.sedFile(exp, "/etc/conf.d/xdm")
def run(self):
"""Run the controller
Workaround for pacman-key bug
FS#45351 https://bugs.archlinux.org/task/45351
We have to kill gpg-agent because if it stays
around we can't reliably unmount
the target partition.
"""
self.configure()
self.terminate('gpg-agent')
def run():
""" Misc postinstall configurations """
config = ConfigController()
return config.run()

View File

@@ -0,0 +1,7 @@
# Syntax is YAML 1.2
---
type: "job"
name: "postcfg"
interface: "python"
script: "main.py" #assumed relative to the current directory
noconfig: true

View File

@@ -0,0 +1,104 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# === This file is part of Calamares - <https://calamares.io> ===
#
# SPDX-FileCopyrightText: 2016 Artoo <artoo@manjaro.org>
# SPDX-FileCopyrightText: 2017 Philip Müller <philm@manjaro.org>
# SPDX-FileCopyrightText: 2018 Artoo <artoo@artixlinux.org>
# SPDX-FileCopyrightText: 2018-2019 Adriaan de Groot <groot@kde.org>
# SPDX-FileCopyrightText: 2023-2024 Artoo <artoo@artixlinux.org>
# SPDX-License-Identifier: GPL-3.0-or-later
#
# Calamares is Free Software: see the License-Identifier above.
#
import libcalamares
import gettext
_ = gettext.translation("calamares-python",
localedir=libcalamares.utils.gettext_path(),
languages=libcalamares.utils.gettext_languages(),
fallback=True).gettext
def pretty_name():
return _("Configure services")
def manage_services(services, manager, cmd):
"""
For each entry in @p services, run "<command> <action> <name>",
where each service is a mapping of service name, action, and a flag.
Returns a failure message, or None if this was successful.
Services that are not mandatory have their failures suppressed
silently.
"""
for sv in services:
if isinstance(sv, str):
name = sv
action = "enable"
mandatory = False
alias = "add"
target = "default"
else:
if "name" not in sv:
libcalamares.utils.error("The key 'name' is missing from the mapping {_sv!s}. Continuing to the next service.".format(_sv=str(sv)))
continue
name = sv["name"]
action = sv.get("action", "enable")
mandatory = sv.get("mandatory", False)
alias = sv.get("alias", "add")
target = sv.get("target", "default")
command = [ cmd ]
match manager:
case "openrc":
args = [alias, name, target]
case "s6":
args = [alias, target, name]
case _:
args = [action, name]
command.extend(args)
libcalamares.utils.debug("Manager command: {!s}".format(command))
ec = libcalamares.utils.target_env_call(command)
if ec != 0:
libcalamares.utils.warning(
"Cannot {} service {}".format(action, name)
)
libcalamares.utils.warning(
"service {} call in chroot returned error code {}".format(action, ec)
)
if mandatory:
title = _("Cannot modify service")
diagnostic = _("<code>service {_action!s}</code> call in chroot returned error code {_ec!s}.").format(_action=action, _ec=ec)
description = _("Cannot {_action!s} service <code>{_name!s}</code>.").format(_action=action, _name=name)
return (
title,
description + " " + diagnostic
)
def run():
"""
Setup services
"""
manager = libcalamares.job.configuration.get("manager")
if libcalamares.globalstorage.contains("initProvider"):
manager = libcalamares.globalstorage.value("initProvider")
commands = libcalamares.job.configuration.get("commands")
cmd = commands.get(manager)
services = libcalamares.job.configuration.get("services", [])
r = manage_services(services, manager, cmd)
if r is not None:
return r

Some files were not shown because too many files have changed in this diff Show More