19 Commits

Author SHA1 Message Date
Adriaan de Groot
3a525359a5 Changes: pre-release housekeeping 2021-01-04 11:35:51 +01:00
Adriaan de Groot
7716052fbb CMake: behave a little more modern
- This copies in some more settings from Calamares-proper
2021-01-04 11:29:12 +01:00
Adriaan de Groot
5e7c6ef005 Merge pull request #7 from ollieparanoid/sd-to-emmc
[mobile] Offer to install from SD to eMMC
2020-12-24 14:53:02 +01:00
Oliver Smith
83da9b2485 [mobile] Offer to install from SD to eMMC
Make it possible to install from external storage to internal storage,
if the installer was booted from the external storage medium.
2020-12-19 13:16:01 +01:00
Oliver Smith
ceea4db418 [mobile] skipDisabledFeatures: add skip by func
Make it possible to skip features by defining a skip function.
2020-12-19 13:15:59 +01:00
Oliver Smith
86823162c6 [mobile] Config: remove redundant get{Bool,Str}
Use getBool and getString from utils/Variant.h instead. I had
implemented my own versions earlier, because I didn't know about the
existing ones.
2020-12-19 13:15:58 +01:00
Oliver Smith
19ffdc47bb [mobile] *Job: 'const QString&' for args
Related: https://github.com/calamares/calamares-extensions/pull/6#discussion_r545049605
2020-12-19 13:15:57 +01:00
Oliver Smith
0bd415f83e [mobile] Don't ignore partition job errors
Stop the installation if the partition job does not finish successfully
instead of carrying on and failing later on. While at it, elaborate some
more on why it is necessary to call the partition job early.

Related: https://github.com/calamares/calamares/issues/1600
2020-12-19 13:15:55 +01:00
Oliver Smith
028042afe7 [mobile] MobileQmlViewStep: remove unused include 2020-12-19 13:15:54 +01:00
Oliver Smith
59556074cc [mobile] create Users job in jobs()
Don't create the user's job in MobileQmlViewStep::onLeave() anymore.
Instead, have Config::createJobs() and call that from
MobileQmlViewStep::jobs(), as suggested by Adriaan, and that's also how
it's done in the keyboard module in calamares.git. This allows getting
rid of m_jobs, and makes it less awkward to pass parameters to the job.

Related: https://github.com/calamares/calamares-extensions/pull/6#discussion_r545048454
2020-12-19 13:15:53 +01:00
Oliver Smith
a6a9dc6890 [mobile] qml: use generic names for id fields
Rename "enableButton" to "firstButton" and "welcomeText" to "mainText".
2020-12-19 13:15:51 +01:00
Oliver Smith
ed7d63d913 [mobile] mobile.conf: tweak line ending 2020-12-19 13:15:49 +01:00
Oliver Smith
9b1da6ff8c [mobile] Config.h: tweak signal related comments
Fix a typo and make the "booleans we don't read from QML don't need
a signal" message generic with one example, but not with a listing of
all these booleans. Then we won't need to adjust this comment with each
new one.
2020-12-19 13:15:41 +01:00
Oliver Smith
7d3c483b35 [mobile] mobile.qml: fix whitespace error 2020-12-19 13:15:33 +01:00
Adriaan de Groot
0d057478b0 Merge pull request #6 from ollieparanoid/sshd-optional
[mobile] make sshd feature optional and related navigation refactoring
2020-12-17 13:27:34 +01:00
Oliver Smith
7f750cb145 [mobile] Make sshd feature optional
Add featureSshd config key to hide related UI screens and skip related
logic in UsersJob.
2020-12-09 15:39:14 +01:00
Oliver Smith
86e77b975f [mobile] navNextFeature: support skipping features
Skip whole features if a featureSshd (example for feature "sshd") or
smilar config key exists and is set to false.
2020-12-09 15:39:10 +01:00
Oliver Smith
70fa9fa766 [mobile] Refactor screen navigation logic
Organize the screens into features and replace all navTo() calls with
hardcoded screen names with new navNext() and navNextFeature()
functions.
2020-12-09 15:39:05 +01:00
Oliver Smith
c43bd06860 [mobile] navTo: add log message for screen change 2020-12-09 15:39:01 +01:00
22 changed files with 516 additions and 126 deletions

28
CHANGES Normal file
View File

@@ -0,0 +1,28 @@
<!-- SPDX-FileCopyrightText: no
SPDX-License-Identifier: CC0-1.0
-->
This is the changelog for Calamares-Extensions. For each release, the major
changes and contributors are listed. Note that Calamares-Extensions does not
have a historical changelog -- this log starts with version 1.0.0.
# 1.1.0 (2021-01-04) #
This release contains contributions from (alphabetically by first name):
- Oliver Smith
Changes and new modules in this release:
- *mobile* module bugfixes
- *mobile* module SSH daemon can be disabled
# 1.0.0 (2020-12-05) #
This release contains contributions from (alphabetically by first name):
- Oliver Smith
- ShalokShalom
Initial release of Calamares-Extensions as such, with one new module
- *mobile*, for PostmarketOS initial configuration

View File

@@ -26,13 +26,33 @@
# #
cmake_minimum_required(VERSION 3.3 FATAL_ERROR) cmake_minimum_required(VERSION 3.3 FATAL_ERROR)
project(calamares-extensions project(calamares-extensions
VERSION 1.0.0 VERSION 1.1.0
LANGUAGES CXX LANGUAGES CXX
) )
find_package(Calamares 3.2.26 REQUIRED) find_package(Calamares 3.2.26 REQUIRED)
find_package(YAMLCPP REQUIRED) find_package(YAMLCPP REQUIRED) # Needed to untangle some dependencies before Calamares 3.2.36
### CMAKE SETUP
#
# Enable IN_LIST
if( POLICY CMP0057 )
cmake_policy( SET CMP0057 NEW )
endif()
# Let ``AUTOMOC`` and ``AUTOUIC`` process ``GENERATED`` files.
if( POLICY CMP0071 )
cmake_policy( SET CMP0071 NEW )
endif()
# Recognize more macros to trigger automoc
if(CMAKE_VERSION VERSION_GREATER_EQUAL "3.10.0")
list(APPEND CMAKE_AUTOMOC_MACRO_NAMES
"K_PLUGIN_FACTORY_WITH_JSON"
"K_EXPORT_PLASMA_DATAENGINE_WITH_JSON"
"K_EXPORT_PLASMA_RUNNER"
)
endif()
### BRANDING ### BRANDING
# #
@@ -67,6 +87,9 @@ calamares_add_module_subdirectory( modules/slowpython ) # Python job
# which builds a list of explanations; show that list. # which builds a list of explanations; show that list.
calamares_explain_skipped_modules( ${SKIPPED_MODULES} ) calamares_explain_skipped_modules( ${SKIPPED_MODULES} )
### RELEASE SUPPORT
#
#
set( CALAMARES_VERSION ${calamares-extensions_VERSION_MAJOR}.${calamares-extensions_VERSION_MINOR}.${calamares-extensions_VERSION_PATCH} ) set( CALAMARES_VERSION ${calamares-extensions_VERSION_MAJOR}.${calamares-extensions_VERSION_MINOR}.${calamares-extensions_VERSION_PATCH} )
# In rare cases we have hotfix-releases with a tweak # In rare cases we have hotfix-releases with a tweak
if( calamares-extensions_VERSION_TWEAK ) if( calamares-extensions_VERSION_TWEAK )

View File

@@ -1,6 +1,12 @@
/* SPDX-FileCopyrightText: 2020 Oliver Smith <ollieparanoid@postmarketos.org> /* SPDX-FileCopyrightText: 2020 Oliver Smith <ollieparanoid@postmarketos.org>
* SPDX-License-Identifier: GPL-3.0-or-later */ * SPDX-License-Identifier: GPL-3.0-or-later */
#include "Config.h" #include "Config.h"
#include "PartitionJob.h"
#include "UsersJob.h"
#include "ViewManager.h"
#include "utils/Variant.h"
#include <QVariant> #include <QVariant>
Config::Config( QObject* parent ) Config::Config( QObject* parent )
@@ -8,37 +14,88 @@ Config::Config( QObject* parent )
{ {
} }
QString
cfgStr( const QVariantMap& cfgMap, QString key, QString defaultStr )
{
QString ret = cfgMap.value( key ).toString();
if ( ret.isEmpty() )
{
return defaultStr;
}
return ret;
}
void void
Config::setConfigurationMap( const QVariantMap& cfgMap ) Config::setConfigurationMap( const QVariantMap& cfgMap )
{ {
m_osName = cfgStr( cfgMap, "osName", "(unknown)" ); using namespace CalamaresUtils;
m_arch = cfgStr( cfgMap, "arch", "(unknown)" );
m_device = cfgStr( cfgMap, "device", "(unknown)" );
m_userInterface = cfgStr( cfgMap, "userInterface", "(unknown)" );
m_version = cfgStr( cfgMap, "version", "(unknown)" );
m_username = cfgStr( cfgMap, "username", "user" );
m_cmdLuksFormat = cfgStr( cfgMap, "cmdLuksFormat", "cryptsetup luksFormat --use-random" ); m_osName = getString( cfgMap, "osName", "(unknown)" );
m_cmdLuksOpen = cfgStr( cfgMap, "cmdLuksOpen", "cryptsetup luksOpen" ); m_arch = getString( cfgMap, "arch", "(unknown)" );
m_cmdMkfsRoot = cfgStr( cfgMap, "cmdMkfsRoot", "mkfs.ext4 -L 'unknownOS_root'" ); m_device = getString( cfgMap, "device", "(unknown)" );
m_cmdMount = cfgStr( cfgMap, "cmdMount", "mount" ); m_userInterface = getString( cfgMap, "userInterface", "(unknown)" );
m_targetDeviceRoot = cfgStr( cfgMap, "targetDeviceRoot", "/dev/unknown" ); m_version = getString( cfgMap, "version", "(unknown)" );
m_username = getString( cfgMap, "username", "user" );
m_cmdPasswd = cfgStr( cfgMap, "cmdPasswd", "passwd" ); m_featureSshd = getBool( cfgMap, "featureSshd", true );
m_cmdSshdEnable = cfgStr( cfgMap, "cmdSshdEnable", "systemctl enable sshd.service" );
m_cmdSshdDisable = cfgStr( cfgMap, "cmdSshdDisable", "systemctl disable sshd.service" ); m_cmdLuksFormat = getString( cfgMap, "cmdLuksFormat", "cryptsetup luksFormat --use-random" );
m_cmdSshdUseradd = cfgStr( cfgMap, "cmdSshdUseradd", "useradd -G wheel -m" ); m_cmdLuksOpen = getString( cfgMap, "cmdLuksOpen", "cryptsetup luksOpen" );
m_cmdMkfsRoot = getString( cfgMap, "cmdMkfsRoot", "mkfs.ext4 -L 'unknownOS_root'" );
m_cmdMount = getString( cfgMap, "cmdMount", "mount" );
m_targetDeviceRoot = getString( cfgMap, "targetDeviceRoot", "/dev/unknown" );
m_targetDeviceRootInternal = getString( cfgMap, "targetDeviceRootInternal", "" );
m_cmdInternalStoragePrepare = getString( cfgMap, "cmdInternalStoragePrepare", "ondev-internal-storage-prepare" );
m_cmdPasswd = getString( cfgMap, "cmdPasswd", "passwd" );
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,
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 )
{
/* 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,
m_cmdMkfsRoot,
m_cmdMount,
m_targetDeviceRoot,
m_targetDeviceRootInternal,
m_installFromExternalToInternal,
m_isFdeEnabled,
m_fdePassword );
Calamares::JobResult res = j->exec();
Calamares::ViewManager* v = Calamares::ViewManager::instance();
if ( res )
{
v->next();
}
else
{
v->onInstallationFailed( res.message(), res.details() );
}
} }
void void
@@ -80,3 +137,9 @@ Config::setIsFdeEnabled( const bool isFdeEnabled )
{ {
m_isFdeEnabled = isFdeEnabled; m_isFdeEnabled = isFdeEnabled;
} }
void
Config::setInstallFromExternalToInternal( const bool val )
{
m_installFromExternalToInternal = val;
}

View File

@@ -1,6 +1,9 @@
/* SPDX-FileCopyrightText: 2020 Oliver Smith <ollieparanoid@postmarketos.org> /* SPDX-FileCopyrightText: 2020 Oliver Smith <ollieparanoid@postmarketos.org>
* SPDX-License-Identifier: GPL-3.0-or-later */ * SPDX-License-Identifier: GPL-3.0-or-later */
#pragma once #pragma once
#include "Job.h"
#include <QObject> #include <QObject>
#include <memory> #include <memory>
@@ -19,6 +22,7 @@ class Config : public QObject
Q_PROPERTY( QString userPassword READ userPassword WRITE setUserPassword NOTIFY userPasswordChanged ) Q_PROPERTY( QString userPassword READ userPassword WRITE setUserPassword NOTIFY userPasswordChanged )
/* ssh server + credentials */ /* ssh server + credentials */
Q_PROPERTY( bool featureSshd READ featureSshd CONSTANT FINAL )
Q_PROPERTY( QString sshdUsername READ sshdUsername WRITE setSshdUsername NOTIFY sshdUsernameChanged ) Q_PROPERTY( QString sshdUsername READ sshdUsername WRITE setSshdUsername NOTIFY sshdUsernameChanged )
Q_PROPERTY( QString sshdPassword READ sshdPassword WRITE setSshdPassword NOTIFY sshdPasswordChanged ) Q_PROPERTY( QString sshdPassword READ sshdPassword WRITE setSshdPassword NOTIFY sshdPasswordChanged )
Q_PROPERTY( bool isSshEnabled READ isSshEnabled WRITE setIsSshEnabled ) Q_PROPERTY( bool isSshEnabled READ isSshEnabled WRITE setIsSshEnabled )
@@ -28,11 +32,16 @@ class Config : public QObject
Q_PROPERTY( bool isFdeEnabled READ isFdeEnabled WRITE setIsFdeEnabled ) Q_PROPERTY( bool isFdeEnabled READ isFdeEnabled WRITE setIsFdeEnabled )
/* partition job */ /* 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 cmdLuksFormat READ cmdLuksFormat CONSTANT FINAL )
Q_PROPERTY( QString cmdLuksOpen READ cmdLuksOpen CONSTANT FINAL ) Q_PROPERTY( QString cmdLuksOpen READ cmdLuksOpen CONSTANT FINAL )
Q_PROPERTY( QString cmdMkfsRoot READ cmdMkfsRoot CONSTANT FINAL ) Q_PROPERTY( QString cmdMkfsRoot READ cmdMkfsRoot CONSTANT FINAL )
Q_PROPERTY( QString cmdMount READ cmdMount CONSTANT FINAL ) Q_PROPERTY( QString cmdMount READ cmdMount CONSTANT FINAL )
Q_PROPERTY( QString targetDeviceRoot READ targetDeviceRoot 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 */ /* users job */
Q_PROPERTY( QString cmdSshdEnable READ cmdSshdEnable CONSTANT FINAL ) Q_PROPERTY( QString cmdSshdEnable READ cmdSshdEnable CONSTANT FINAL )
@@ -41,6 +50,7 @@ class Config : public QObject
public: public:
Config( QObject* parent = nullptr ); Config( QObject* parent = nullptr );
void setConfigurationMap( const QVariantMap& ); void setConfigurationMap( const QVariantMap& );
Calamares::JobList createJobs();
/* welcome */ /* welcome */
QString osName() const { return m_osName; } QString osName() const { return m_osName; }
@@ -55,6 +65,7 @@ public:
void setUserPassword( const QString& userPassword ); void setUserPassword( const QString& userPassword );
/* ssh server + credetials */ /* ssh server + credetials */
bool featureSshd() { return m_featureSshd; }
QString sshdUsername() const { return m_sshdUsername; } QString sshdUsername() const { return m_sshdUsername; }
QString sshdPassword() const { return m_sshdPassword; } QString sshdPassword() const { return m_sshdPassword; }
bool isSshEnabled() { return m_isSshEnabled; } bool isSshEnabled() { return m_isSshEnabled; }
@@ -69,11 +80,17 @@ public:
void setIsFdeEnabled( bool isFdeEnabled ); void setIsFdeEnabled( bool isFdeEnabled );
/* partition job */ /* partition job */
bool runPartitionJobThenLeaveDummy() { return 0; }
void runPartitionJobThenLeave( bool b );
QString cmdInternalStoragePrepare() const { return m_cmdInternalStoragePrepare; }
QString cmdLuksFormat() const { return m_cmdLuksFormat; } QString cmdLuksFormat() const { return m_cmdLuksFormat; }
QString cmdLuksOpen() const { return m_cmdLuksOpen; } QString cmdLuksOpen() const { return m_cmdLuksOpen; }
QString cmdMkfsRoot() const { return m_cmdMkfsRoot; } QString cmdMkfsRoot() const { return m_cmdMkfsRoot; }
QString cmdMount() const { return m_cmdMount; } QString cmdMount() const { return m_cmdMount; }
QString targetDeviceRoot() const { return m_targetDeviceRoot; } QString targetDeviceRoot() const { return m_targetDeviceRoot; }
QString targetDeviceRootInternal() const { return m_targetDeviceRootInternal; }
bool installFromExternalToInternal() { return m_installFromExternalToInternal; }
void setInstallFromExternalToInternal( const bool val );
/* users job */ /* users job */
QString cmdPasswd() const { return m_cmdPasswd; } QString cmdPasswd() const { return m_cmdPasswd; }
@@ -94,6 +111,7 @@ private:
QString m_userPassword; QString m_userPassword;
/* ssh server + credetials */ /* ssh server + credetials */
bool m_featureSshd;
QString m_sshdUsername; QString m_sshdUsername;
QString m_sshdPassword; QString m_sshdPassword;
bool m_isSshEnabled; bool m_isSshEnabled;
@@ -103,11 +121,14 @@ private:
bool m_isFdeEnabled = false; bool m_isFdeEnabled = false;
/* partition job */ /* partition job */
QString m_cmdInternalStoragePrepare;
QString m_cmdLuksFormat; QString m_cmdLuksFormat;
QString m_cmdLuksOpen; QString m_cmdLuksOpen;
QString m_cmdMkfsRoot; QString m_cmdMkfsRoot;
QString m_cmdMount; QString m_cmdMount;
QString m_targetDeviceRoot; QString m_targetDeviceRoot;
QString m_targetDeviceRootInternal;
bool m_installFromExternalToInternal;
/* users job */ /* users job */
QString m_cmdPasswd; QString m_cmdPasswd;
@@ -116,13 +137,14 @@ private:
QString m_cmdSshdUseradd; QString m_cmdSshdUseradd;
signals: signals:
/* booleans we don't read from QML (like isSshEnabled) don't need a signal */
/* default user */ /* default user */
void userPasswordChanged( QString userPassword ); void userPasswordChanged( QString userPassword );
/* ssh server + credetials */ /* ssh server + credentials */
void sshdUsernameChanged( QString sshdUsername ); void sshdUsernameChanged( QString sshdUsername );
void sshdPasswordChanged( QString sshdPassword ); void sshdPasswordChanged( QString sshdPassword );
/* isSshEnabled doesn't need a signal, we don't read it from QML */
/* full disk encryption */ /* full disk encryption */
void fdePasswordChanged( QString fdePassword ); void fdePasswordChanged( QString fdePassword );

View File

@@ -1,11 +1,8 @@
/* SPDX-FileCopyrightText: 2020 Oliver Smith <ollieparanoid@postmarketos.org> /* SPDX-FileCopyrightText: 2020 Oliver Smith <ollieparanoid@postmarketos.org>
* SPDX-License-Identifier: GPL-3.0-or-later */ * SPDX-License-Identifier: GPL-3.0-or-later */
#include "MobileQmlViewStep.h" #include "MobileQmlViewStep.h"
#include "PartitionJob.h"
#include "UsersJob.h"
#include "GlobalStorage.h" #include "GlobalStorage.h"
#include "JobQueue.h"
#include "locale/LabelModel.h" #include "locale/LabelModel.h"
#include "utils/Dirs.h" #include "utils/Dirs.h"
@@ -35,34 +32,7 @@ MobileQmlViewStep::MobileQmlViewStep( QObject* parent )
void void
MobileQmlViewStep::onLeave() MobileQmlViewStep::onLeave()
{ {
Calamares::Job *partition, *users; return;
/* HACK: run partition job now */
partition = new PartitionJob( m_config->cmdLuksFormat(),
m_config->cmdLuksOpen(),
m_config->cmdMkfsRoot(),
m_config->cmdMount(),
m_config->targetDeviceRoot(),
m_config->isFdeEnabled(),
m_config->fdePassword() );
Calamares::JobResult res = partition->exec();
if ( !res )
{
cError() << "PARTITION JOB FAILED: " << res.message();
}
/* Put users job in queue (should run after unpackfs) */
m_jobs.clear();
QString cmdSshd = m_config->isSshEnabled() ? m_config->cmdSshdEnable() : m_config->cmdSshdDisable();
users = new UsersJob( m_config->cmdPasswd(),
cmdSshd,
m_config->cmdSshdUseradd(),
m_config->isSshEnabled(),
m_config->username(),
m_config->userPassword(),
m_config->sshdUsername(),
m_config->sshdPassword() );
m_jobs.append( Calamares::job_ptr( users ) );
} }
bool bool
@@ -95,7 +65,7 @@ MobileQmlViewStep::isAtEnd() const
Calamares::JobList Calamares::JobList
MobileQmlViewStep::jobs() const MobileQmlViewStep::jobs() const
{ {
return m_jobs; return m_config->createJobs();
} }
QObject* QObject*

View File

@@ -32,7 +32,6 @@ public:
private: private:
Config* m_config; Config* m_config;
QList< Calamares::job_ptr > m_jobs;
}; };
CALAMARES_PLUGIN_FACTORY_DECLARATION( MobileQmlViewStepFactory ) CALAMARES_PLUGIN_FACTORY_DECLARATION( MobileQmlViewStepFactory )

View File

@@ -12,19 +12,25 @@
#include <QFileInfo> #include <QFileInfo>
PartitionJob::PartitionJob( QString cmdLuksFormat, PartitionJob::PartitionJob( const QString& cmdInternalStoragePrepare,
QString cmdLuksOpen, const QString& cmdLuksFormat,
QString cmdMkfsRoot, const QString& cmdLuksOpen,
QString cmdMount, const QString& cmdMkfsRoot,
QString targetDeviceRoot, const QString& cmdMount,
const QString& targetDeviceRoot,
const QString& targetDeviceRootInternal,
bool installFromExternalToInternal,
bool isFdeEnabled, bool isFdeEnabled,
const QString& password ) const QString& password )
: Calamares::Job() : Calamares::Job()
, m_cmdInternalStoragePrepare( cmdInternalStoragePrepare )
, m_cmdLuksFormat( cmdLuksFormat ) , m_cmdLuksFormat( cmdLuksFormat )
, m_cmdLuksOpen( cmdLuksOpen ) , m_cmdLuksOpen( cmdLuksOpen )
, m_cmdMkfsRoot( cmdMkfsRoot ) , m_cmdMkfsRoot( cmdMkfsRoot )
, m_cmdMount( cmdMount ) , m_cmdMount( cmdMount )
, m_targetDeviceRoot( targetDeviceRoot ) , m_targetDeviceRoot( targetDeviceRoot )
, m_targetDeviceRootInternal( targetDeviceRootInternal )
, m_installFromExternalToInternal( installFromExternalToInternal )
, m_isFdeEnabled( isFdeEnabled ) , m_isFdeEnabled( isFdeEnabled )
, m_password( password ) , m_password( password )
{ {
@@ -77,10 +83,18 @@ PartitionJob::exec()
QString cryptDev = "/dev/mapper/" + cryptName; QString cryptDev = "/dev/mapper/" + cryptName;
QString passwordStdin = m_password + "\n"; QString passwordStdin = m_password + "\n";
QString dev = m_targetDeviceRoot; QString dev = m_targetDeviceRoot;
QList< QPair< QStringList, QString > > commands = {};
QList< QPair< QStringList, QString > > commands = { if ( m_installFromExternalToInternal )
{ { "mkdir", "-p", pathMount }, QString() }, {
}; dev = m_targetDeviceRootInternal;
commands.append( {
{ { "sh", "-c", m_cmdInternalStoragePrepare }, QString() },
} );
}
commands.append( { { { "mkdir", "-p", pathMount }, QString() } } );
if ( m_isFdeEnabled ) if ( m_isFdeEnabled )
{ {

View File

@@ -8,11 +8,14 @@ class PartitionJob : public Calamares::Job
{ {
Q_OBJECT Q_OBJECT
public: public:
PartitionJob( QString cmdLuksFormat, PartitionJob( const QString& cmdInternalStoragePrepare,
QString cmdLuksOpen, const QString& cmdLuksFormat,
QString cmdMkfsRoot, const QString& cmdLuksOpen,
QString cmdMount, const QString& cmdMkfsRoot,
QString targetDeviceRoot, const QString& cmdMount,
const QString& targetDeviceRoot,
const QString& targetDeviceRootInternal,
bool installFromExternalToInternal,
bool isFdeEnabled, bool isFdeEnabled,
const QString& password ); const QString& password );
@@ -22,11 +25,14 @@ public:
Calamares::JobList createJobs(); Calamares::JobList createJobs();
private: private:
QString m_cmdInternalStoragePrepare;
QString m_cmdLuksFormat; QString m_cmdLuksFormat;
QString m_cmdLuksOpen; QString m_cmdLuksOpen;
QString m_cmdMkfsRoot; QString m_cmdMkfsRoot;
QString m_cmdMount; QString m_cmdMount;
QString m_targetDeviceRoot; QString m_targetDeviceRoot;
QString m_targetDeviceRootInternal;
bool m_installFromExternalToInternal;
bool m_isFdeEnabled; bool m_isFdeEnabled;
QString m_password; QString m_password;
}; };

View File

@@ -12,15 +12,17 @@
#include <QFileInfo> #include <QFileInfo>
UsersJob::UsersJob( QString cmdPasswd, UsersJob::UsersJob( bool featureSshd,
QString cmdSshd, const QString& cmdPasswd,
QString cmdSshdUseradd, const QString& cmdSshd,
const QString& cmdSshdUseradd,
bool isSshEnabled, bool isSshEnabled,
QString username, const QString& username,
QString password, const QString& password,
QString sshdUsername, const QString& sshdUsername,
QString sshdPassword ) const QString& sshdPassword )
: Calamares::Job() : Calamares::Job()
, m_featureSshd( featureSshd )
, m_cmdPasswd( cmdPasswd ) , m_cmdPasswd( cmdPasswd )
, m_cmdSshd( cmdSshd ) , m_cmdSshd( cmdSshd )
, m_cmdSshdUseradd( cmdSshdUseradd ) , m_cmdSshdUseradd( cmdSshdUseradd )
@@ -48,14 +50,18 @@ UsersJob::exec()
QList< QPair< QStringList, QString > > commands = { QList< QPair< QStringList, QString > > commands = {
{ { "sh", "-c", m_cmdPasswd + " " + m_username }, m_password + "\n" + m_password + "\n" }, { { "sh", "-c", m_cmdPasswd + " " + m_username }, m_password + "\n" + m_password + "\n" },
{ { "sh", "-c", m_cmdSshd }, QString() },
}; };
if ( m_isSshEnabled ) if ( m_featureSshd )
{ {
commands.append( { { "sh", "-c", m_cmdSshdUseradd + " " + m_sshdUsername }, QString() } ); commands.append( { { "sh", "-c", m_cmdSshd }, QString() } );
commands.append(
{ { "sh", "-c", m_cmdPasswd + " " + m_sshdUsername }, m_sshdPassword + "\n" + m_sshdPassword + "\n" } ); 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 ) foreach ( auto command, commands )

View File

@@ -8,14 +8,15 @@ class UsersJob : public Calamares::Job
{ {
Q_OBJECT Q_OBJECT
public: public:
UsersJob( QString cmdPasswd, UsersJob( bool featureSshd,
QString cmdSshd, const QString& cmdPasswd,
QString cmdSshdUseradd, const QString& cmdSshd,
const QString& cmdSshdUseradd,
bool isSshEnabled, bool isSshEnabled,
QString username, const QString& username,
QString password, const QString& password,
QString sshdUsername, const QString& sshdUsername,
QString sshdPassword ); const QString& sshdPassword );
QString prettyName() const override; QString prettyName() const override;
Calamares::JobResult exec() override; Calamares::JobResult exec() override;
@@ -23,6 +24,7 @@ public:
Calamares::JobList createJobs(); Calamares::JobList createJobs();
private: private:
bool m_featureSshd;
QString m_cmdPasswd; QString m_cmdPasswd;
QString m_cmdSshd; QString m_cmdSshd;
QString m_cmdSshdUseradd; QString m_cmdSshdUseradd;

View File

@@ -88,7 +88,7 @@ Item {
onClicked: { onClicked: {
if (validatePin(userPin, userPinRepeat, errorText)) { if (validatePin(userPin, userPinRepeat, errorText)) {
config.userPassword = userPin.text; config.userPassword = userPin.text;
navTo("ssh_confirm"); navNext();
} }
} }
} }

View File

@@ -19,7 +19,7 @@ Item {
height: parent.height height: parent.height
Text { Text {
id: welcomeText id: mainText
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top anchors.top: parent.top
anchors.topMargin: 30 anchors.topMargin: 30
@@ -37,29 +37,29 @@ Item {
} }
Button { Button {
id: enableButton id: firstButton
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
anchors.top: welcomeText.bottom anchors.top: mainText.bottom
anchors.topMargin: 40 anchors.topMargin: 40
width: 500 width: 500
text: qsTr("Enable") text: qsTr("Enable")
onClicked: { onClicked: {
config.isFdeEnabled = true; config.isFdeEnabled = true;
navTo("fde_pass"); navNext();
} }
} }
Button { Button {
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
anchors.top: enableButton.bottom anchors.top: firstButton.bottom
anchors.topMargin: 40 anchors.topMargin: 40
width: 500 width: 500
text: qsTr("Disable") text: qsTr("Disable")
onClicked: { onClicked: {
config.isFdeEnabled = false; config.isFdeEnabled = false;
navTo("install_confirm"); navNextFeature();
} }
} }
} }

View File

@@ -73,7 +73,7 @@ Item {
onClicked: { onClicked: {
if (validatePassword(password, passwordRepeat, errorText)) { if (validatePassword(password, passwordRepeat, errorText)) {
config.fdePassword = password.text; config.fdePassword = password.text;
navTo("install_confirm"); navNext();
} }
} }
} }

View File

@@ -19,24 +19,38 @@ Item {
height: parent.height height: parent.height
Text { Text {
id: welcomeText id: mainText
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top anchors.top: parent.top
anchors.topMargin: 30 anchors.topMargin: 30
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
text: "Once you hit 'install', the installation will begin. It will" + text: (function() {
" typically take a few minutes. Do not power off the device" + var ret = "Once you hit 'install', the installation will begin." +
" until it is done. Afterwards, it will reboot into the" + " It will typically take a few minutes. Do not power off the" +
" installed system." " device until it is done.<br><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: 500 width: 500
} }
Button { Button {
id: enableButton id: firstButton
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
anchors.top: welcomeText.bottom anchors.top: mainText.bottom
anchors.topMargin: 40 anchors.topMargin: 40
width: 500 width: 500

View File

@@ -0,0 +1,64 @@
/* 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: "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: 500
}
Button {
id: firstButton
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: mainText.bottom
anchors.topMargin: 40
width: 500
text: qsTr("Internal (eMMC)")
onClicked: {
config.installFromExternalToInternal = true;
navNext();
}
}
Button {
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: firstButton.bottom
anchors.topMargin: 40
width: 500
text: qsTr("External (SD card)")
onClicked: {
config.installFromExternalToInternal = false;
navNextFeature();
}
}
}

View File

@@ -0,0 +1,58 @@
/* 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: "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: 500
}
Button {
id: firstButton
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: mainText.bottom
anchors.topMargin: 40
width: 500
text: qsTr("Yes")
onClicked: {
navNext();
}
}
Button {
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: firstButton.bottom
anchors.topMargin: 40
width: 500
text: qsTr("No")
onClicked: {
navBack();
}
}
}

View File

@@ -27,9 +27,32 @@
## Name of the device (e.g. PinePhone) ## Name of the device (e.g. PinePhone)
# device: "(unknown)" # device: "(unknown)"
## Partition that will be formatted and mounted (optionally with FDE) for the rootfs ## Partition that will be formatted and mounted (optionally with FDE) for the
## rootfs
# targetDeviceRoot: "/dev/unknown" # 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
####### #######
### Commands running in the installer OS ### Commands running in the installer OS
####### #######
@@ -52,6 +75,13 @@
## Arguments: <device> <mountpoint> ## Arguments: <device> <mountpoint>
# cmdMount: "mount" # 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) ### Commands running in the target OS (chroot)
####### #######

View File

@@ -17,6 +17,8 @@ Page
property var screenPrevious: [] property var screenPrevious: []
property var titles: { property var titles: {
"welcome": null, /* titlebar disabled */ "welcome": null, /* titlebar disabled */
"install_target": "Installation target",
"install_target_confirm": "Warning",
"default_pin": "Lockscreen PIN", "default_pin": "Lockscreen PIN",
"ssh_confirm": "SSH server", "ssh_confirm": "SSH server",
"ssh_credentials": "SSH credentials", "ssh_credentials": "SSH credentials",
@@ -25,6 +27,31 @@ Page
"install_confirm": "Ready to install", "install_confirm": "Ready to install",
"wait": null "wait": null
} }
property var features: [
{"name": "welcome",
"screens": ["welcome"]},
{"name": "installTarget",
"screens": ["install_target", "install_target_confirm"]},
{"name": "userPin",
"screens": ["default_pin"]},
{"name": "sshd",
"screens": ["ssh_confirm", "ssh_credentials"]},
{"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, "default_pin": 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 the initramfs on-screen keyboard /* Only allow characters, that can be typed in with the initramfs on-screen keyboard
* (osk-sdl: see src/keyboard.cpp). FIXME: make configurable, but keep this as default? */ * (osk-sdl: see src/keyboard.cpp). FIXME: make configurable, but keep this as default? */
property var allowed_chars: property var allowed_chars:
@@ -120,8 +147,13 @@ Page
id: timer id: timer
} }
function skipFeatureInstallTarget() {
return config.targetDeviceRootInternal == "";
}
/* Navigation related */ /* Navigation related */
function navTo(name, historyPush=true) { function navTo(name, historyPush=true) {
console.log("Navigating to screen: " + name);
if (historyPush) if (historyPush)
screenPrevious.push(screen); screenPrevious.push(screen);
screen = name; screen = name;
@@ -131,15 +163,71 @@ Page
Qt.inputMethod.hide(); Qt.inputMethod.hide();
} }
function navFinish() { function navFinish() {
/* Show a waiting screen and wait a second (so it can render), then let /* Show a waiting screen and wait a second (so it can render). The big
* MobileQmlViewStep.cpp::onLeave() create the (encrypted) partition * comment in Config.cpp::runPartitionJobThenLeave() explains why this
* and mount it. We can't have this as proper job due to ondev#18. */ * is necessary. */
navTo("wait"); navTo("wait");
timer.interval = 1000; timer.interval = 1000;
timer.repeat = false; timer.repeat = false;
timer.triggered.connect(ViewManager.next); 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(); timer.start();
} }
function navNextFeature() {
var id = featureIdByScreen[screen] + 1;
/* Skip disabled features */
do {
/* 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);
id += 1;
continue;
}
/* Check skipFeature<Name>() */
var funcName = "skipFeature" + nameUp;
if (eval("typeof " + funcName) === "function"
&& eval(funcName + "()")) {
console.log("Skipping feature (skip function): " + name);
id += 1;
continue;
}
} while(false);
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() { function navBack() {
if (screenPrevious.length) if (screenPrevious.length)
return navTo(screenPrevious.pop(), false); return navTo(screenPrevious.pop(), false);
@@ -183,7 +271,7 @@ Page
if (repeat != pin) if (repeat != pin)
return validationFailure(errorText, return validationFailure(errorText,
"The PINs don't match."); "The PINs don't match.");
return validationFailureClear(errorText); return validationFailureClear(errorText);
} }
function validateSshdUsername(username, errorText) { function validateSshdUsername(username, errorText) {

View File

@@ -4,6 +4,9 @@
<file>welcome.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>default_pin.qml</file> <!-- default user: pin --> <file>default_pin.qml</file> <!-- default user: pin -->
<file>ssh_confirm.qml</file> <!-- sshd: enable or not? --> <file>ssh_confirm.qml</file> <!-- sshd: enable or not? -->
<file>ssh_credentials.qml</file> <!-- sshd user: username, password --> <file>ssh_credentials.qml</file> <!-- sshd user: username, password -->

View File

@@ -19,7 +19,7 @@ Item {
height: parent.height height: parent.height
Text { Text {
id: welcomeText id: mainText
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top anchors.top: parent.top
anchors.topMargin: 30 anchors.topMargin: 30
@@ -40,29 +40,29 @@ Item {
} }
Button { Button {
id: enableButton id: firstButton
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
anchors.top: welcomeText.bottom anchors.top: mainText.bottom
anchors.topMargin: 40 anchors.topMargin: 40
width: 500 width: 500
text: qsTr("Enable") text: qsTr("Enable")
onClicked: { onClicked: {
config.isSshEnabled = true; config.isSshEnabled = true;
navTo("ssh_credentials"); navNext();
} }
} }
Button { Button {
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
anchors.top: enableButton.bottom anchors.top: firstButton.bottom
anchors.topMargin: 40 anchors.topMargin: 40
width: 500 width: 500
text: qsTr("Disable") text: qsTr("Disable")
onClicked: { onClicked: {
config.isSshEnabled = false; config.isSshEnabled = false;
navTo("fde_confirm"); navNextFeature();
} }
} }
} }

View File

@@ -100,7 +100,7 @@ Item {
config.sshdUsername = username.text; config.sshdUsername = username.text;
config.sshdPassword = password.text; config.sshdPassword = password.text;
navTo("fde_confirm"); navNext();
} }
} }
} }

View File

@@ -34,7 +34,7 @@ Page
source: "file:///usr/share/calamares/branding/default-mobile/logo.png" source: "file:///usr/share/calamares/branding/default-mobile/logo.png"
} }
Text { Text {
id: welcomeText id: mainText
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
anchors.top: logo.bottom anchors.top: logo.bottom
anchors.topMargin: 50 anchors.topMargin: 50
@@ -53,12 +53,12 @@ Page
Button { Button {
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
anchors.top: welcomeText.bottom anchors.top: mainText.bottom
anchors.topMargin: 50 anchors.topMargin: 50
width: 500 width: 500
text: qsTr("Continue") text: qsTr("Continue")
onClicked: navTo("default_pin") onClicked: navNext()
} }
} }
} }