From 04f38ea661be675e973d40682140cda17c8a9ac4 Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Fri, 8 Apr 2022 10:45:50 +0200 Subject: [PATCH 01/19] [users] Put hostname settings in a *hostname* key Move settings into a structured setting for *hostname*, with suitable sub-keys. Legacy settings remain supported, produce a warning. --- src/modules/users/Config.cpp | 23 +++++++++++++++++++++-- src/modules/users/Tests.cpp | 2 +- src/modules/users/users.conf | 26 ++++++++++++++++++++------ 3 files changed, 42 insertions(+), 9 deletions(-) diff --git a/src/modules/users/Config.cpp b/src/modules/users/Config.cpp index 371d98932..55377abba 100644 --- a/src/modules/users/Config.cpp +++ b/src/modules/users/Config.cpp @@ -738,7 +738,7 @@ STATICTEST HostNameActions getHostNameActions( const QVariantMap& configurationMap ) { HostNameAction setHostName = HostNameAction::EtcHostname; - QString hostnameActionString = CalamaresUtils::getString( configurationMap, "setHostname" ); + QString hostnameActionString = CalamaresUtils::getString( configurationMap, "location" ); if ( !hostnameActionString.isEmpty() ) { bool ok = false; @@ -826,6 +826,17 @@ either( T ( *f )( const QVariantMap&, const QString&, U ), } } +static void +copyLegacy( const QVariantMap& source, const QString& sourceKey, QVariantMap& target, const QString& targetKey ) +{ + if ( source.contains( sourceKey ) ) + { + const QVariant legacyValue = source.value( sourceKey ); + cWarning() << "Legacy *users* key" << sourceKey << "overrides hostname-settings."; + target.insert( targetKey, legacyValue ); + } +} + void Config::setConfigurationMap( const QVariantMap& configurationMap ) { @@ -844,7 +855,15 @@ Config::setConfigurationMap( const QVariantMap& configurationMap ) ? SudoStyle::UserAndGroup : SudoStyle::UserOnly; - m_hostNameActions = getHostNameActions( configurationMap ); + // Handle *hostname* key and subkeys and legacy settings + { + bool ok = false; // Ignored + QVariantMap hostnameSettings = CalamaresUtils::getSubMap( configurationMap, "hostname", ok ); + + copyLegacy( configurationMap, "setHostname", hostnameSettings, "location" ); + copyLegacy( configurationMap, "writeHostsFile", hostnameSettings, "writeHostsFile" ); + m_hostNameActions = getHostNameActions( hostnameSettings ); + } setConfigurationDefaultGroups( configurationMap, m_defaultGroups ); diff --git a/src/modules/users/Tests.cpp b/src/modules/users/Tests.cpp index acb0c9d6d..6bb701228 100644 --- a/src/modules/users/Tests.cpp +++ b/src/modules/users/Tests.cpp @@ -240,7 +240,7 @@ UserTests::testHostActions() QVariantMap m; if ( set ) { - m.insert( "setHostname", string ); + m.insert( "location", string ); } QCOMPARE( getHostNameActions( m ), HostNameActions( result ) | HostNameAction::WriteEtcHosts ); // write bits default to true diff --git a/src/modules/users/users.conf b/src/modules/users/users.conf index 9f932c0a1..397b888cc 100644 --- a/src/modules/users/users.conf +++ b/src/modules/users/users.conf @@ -149,19 +149,33 @@ allowWeakPasswordsDefault: false # that the shell actually exists or is executable. userShell: /bin/bash -# Hostname setting +# Hostname settings # # The user can enter a hostname; this is configured into the system -# in some way; pick one of: +# in some way. There are settings for how a hostname is guessed (as +# a default / suggestion) and where (or how) the hostname is set in +# the target system. +# +# Key *hostname* has the following sub-keys (just one): +# +# - *location* How the hostname is set in the target system: # - *None*, to not set the hostname at all # - *EtcFile*, to write to `/etc/hostname` directly # - *Hostnamed*, to use systemd hostnamed(1) over DBus -# The default is *EtcFile*. +# The default is *EtcFile*. Setting this to *None* will +# hide the hostname field. +# - *writeHostsFile* Should /etc/hosts be written with a hostname for +# this machine (also adds localhost and some ipv6 standard entries). +# Defaults to *true*. +hostname: + location: EtcFile + writeHostsFile: true + +# This is a legacy setting for hostname.location; if it is set +# at all, and there is no setting for hostname.location, it is used. setHostname: EtcFile -# Should /etc/hosts be written with a hostname for this machine -# (also adds localhost and some ipv6 standard entries). -# Defaults to *true*. +# This is a legacy setting for hostname.writeHostsFile writeHostsFile: true presets: From 4494a4b35ae4f6ee84bec5017c0f9a52a044f950 Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Fri, 8 Apr 2022 11:26:22 +0200 Subject: [PATCH 02/19] [users] Expand tests with some legacy-settings --- src/modules/users/Tests.cpp | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/modules/users/Tests.cpp b/src/modules/users/Tests.cpp index 6bb701228..230e60d4e 100644 --- a/src/modules/users/Tests.cpp +++ b/src/modules/users/Tests.cpp @@ -42,6 +42,7 @@ private Q_SLOTS: void testHostActions_data(); void testHostActions(); + void testHostActions2(); void testPasswordChecks(); void testUserPassword(); @@ -250,6 +251,22 @@ UserTests::testHostActions() QCOMPARE( getHostNameActions( m ), HostNameActions( result ) | HostNameAction::WriteEtcHosts ); } +void +UserTests::testHostActions2() +{ + Config c; + QVariantMap legacy; + + c.setConfigurationMap( legacy ); + QCOMPARE( c.hostNameActions(), HostNameAction::EtcHostname | HostNameAction::WriteEtcHosts ); + + legacy.insert( "writeHostsFile", false ); + legacy.insert( "setHostname", "Hostnamed" ); + c.setConfigurationMap( legacy ); + QCOMPARE( c.hostNameActions(), HostNameAction::SystemdHostname ); +} + + void UserTests::testPasswordChecks() { From 8a8ac4fe2b52ba69b2d1577cca8f5e7b5348f6f6 Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Fri, 8 Apr 2022 16:35:47 +0200 Subject: [PATCH 03/19] [users]: update config schema --- src/modules/users/users.schema.yaml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/modules/users/users.schema.yaml b/src/modules/users/users.schema.yaml index 3b49061bc..42ce42b44 100644 --- a/src/modules/users/users.schema.yaml +++ b/src/modules/users/users.schema.yaml @@ -40,7 +40,13 @@ properties: minLength: { type: number } maxLength: { type: number } libpwquality: { type: array, items: { type: string } } # Don't know what libpwquality supports - # Hostname setting + hostname: + additionalProperties: false + type: object + properties: + location: { type: string, enum: [ None, EtcFile, Hostnamed ] } + writeHostsFile: { type: boolean, default: true } + # Legacy Hostname setting setHostname: { type: string, enum: [ None, EtcFile, Hostnamed ] } writeHostsFile: { type: boolean, default: true } From 99bf5497cab76c029319b1c11b317ecef9ae6655 Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Mon, 11 Apr 2022 10:14:32 +0200 Subject: [PATCH 04/19] [users] Document change to config, document TODO:3.3 --- CHANGES-3.2 | 4 +++- src/modules/users/Config.cpp | 2 ++ src/modules/users/users.conf | 4 ++++ 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGES-3.2 b/CHANGES-3.2 index ea8beb764..8cec4bb78 100644 --- a/CHANGES-3.2 +++ b/CHANGES-3.2 @@ -17,7 +17,9 @@ This release contains contributions from (alphabetically by first name): - No core changes yet ## Modules ## - - No module changes yet + - *users* module has rearranged configuration for setting the hostname. + Legacy settings are preserved, but produce a warning. Please see + `users.conf` for details. # 3.2.54 (2022-03-21) # diff --git a/src/modules/users/Config.cpp b/src/modules/users/Config.cpp index 55377abba..6cc3a0cea 100644 --- a/src/modules/users/Config.cpp +++ b/src/modules/users/Config.cpp @@ -826,6 +826,7 @@ either( T ( *f )( const QVariantMap&, const QString&, U ), } } +// TODO:3.3: Remove static void copyLegacy( const QVariantMap& source, const QString& sourceKey, QVariantMap& target, const QString& targetKey ) { @@ -860,6 +861,7 @@ Config::setConfigurationMap( const QVariantMap& configurationMap ) bool ok = false; // Ignored QVariantMap hostnameSettings = CalamaresUtils::getSubMap( configurationMap, "hostname", ok ); + // TODO:3.3: Remove calls to copyLegacy copyLegacy( configurationMap, "setHostname", hostnameSettings, "location" ); copyLegacy( configurationMap, "writeHostsFile", hostnameSettings, "writeHostsFile" ); m_hostNameActions = getHostNameActions( hostnameSettings ); diff --git a/src/modules/users/users.conf b/src/modules/users/users.conf index 397b888cc..22e7e0a68 100644 --- a/src/modules/users/users.conf +++ b/src/modules/users/users.conf @@ -171,10 +171,14 @@ hostname: location: EtcFile writeHostsFile: true +# TODO:3.3: Remove this setting +# # This is a legacy setting for hostname.location; if it is set # at all, and there is no setting for hostname.location, it is used. setHostname: EtcFile +# TODO:3.3: Remove this setting +# # This is a legacy setting for hostname.writeHostsFile writeHostsFile: true From b653b130028e0a3c431a4640692d6e1642fbb14a Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Mon, 11 Apr 2022 10:42:00 +0200 Subject: [PATCH 05/19] [users] Hide hostname box when set to None If the hostname will not be written, hide the input box. While here, improve some naming in the designer file. --- src/modules/users/UsersPage.cpp | 35 ++++++++++++++++++++--------- src/modules/users/page_usersetup.ui | 16 ++++++------- 2 files changed, 32 insertions(+), 19 deletions(-) diff --git a/src/modules/users/UsersPage.cpp b/src/modules/users/UsersPage.cpp index 6ac6c5bf6..d3c61326e 100644 --- a/src/modules/users/UsersPage.cpp +++ b/src/modules/users/UsersPage.cpp @@ -105,18 +105,31 @@ UsersPage::UsersPage( Config* config, QWidget* parent ) connect( ui->textBoxFullName, &QLineEdit::textEdited, config, &Config::setFullName ); connect( config, &Config::fullNameChanged, this, &UsersPage::onFullNameTextEdited ); - ui->textBoxHostName->setText( config->hostName() ); - connect( ui->textBoxHostName, &QLineEdit::textEdited, config, &Config::setHostName ); - connect( config, - &Config::hostNameChanged, - [ this ]( const QString& name ) - { - if ( !ui->textBoxHostName->hasFocus() ) + // If the hostname is going to be written out, then show the field + if ( ( m_config->hostNameActions() & HostNameAction::EtcHostname ) + || ( m_config->hostNameActions() & HostNameAction::SystemdHostname ) ) + { + ui->textBoxHostname->setText( config->hostName() ); + connect( ui->textBoxHostname, &QLineEdit::textEdited, config, &Config::setHostName ); + connect( config, + &Config::hostNameChanged, + [ this ]( const QString& name ) { - ui->textBoxHostName->setText( name ); - } - } ); - connect( config, &Config::hostNameStatusChanged, this, &UsersPage::reportHostNameStatus ); + if ( !ui->textBoxHostname->hasFocus() ) + { + ui->textBoxHostname->setText( name ); + } + } ); + connect( config, &Config::hostNameStatusChanged, this, &UsersPage::reportHostNameStatus ); + } + else + { + // Need to hide the hostname parts individually because there's no widget-group + ui->hostnameLabel->hide(); + ui->labelHostname->hide(); + ui->textBoxHostname->hide(); + ui->labelHostnameError->hide(); + } ui->textBoxLoginName->setText( config->loginName() ); connect( ui->textBoxLoginName, &QLineEdit::textEdited, config, &Config::setLoginName ); diff --git a/src/modules/users/page_usersetup.ui b/src/modules/users/page_usersetup.ui index ba1c0bc7d..daad98174 100644 --- a/src/modules/users/page_usersetup.ui +++ b/src/modules/users/page_usersetup.ui @@ -42,7 +42,7 @@ SPDX-License-Identifier: GPL-3.0-or-later - + @@ -129,7 +129,7 @@ SPDX-License-Identifier: GPL-3.0-or-later - + @@ -218,7 +218,7 @@ SPDX-License-Identifier: GPL-3.0-or-later - + What is the name of this computer? @@ -228,9 +228,9 @@ SPDX-License-Identifier: GPL-3.0-or-later - + - + 0 @@ -304,7 +304,7 @@ SPDX-License-Identifier: GPL-3.0-or-later - + Qt::Vertical @@ -330,7 +330,7 @@ SPDX-License-Identifier: GPL-3.0-or-later - + @@ -500,7 +500,7 @@ SPDX-License-Identifier: GPL-3.0-or-later - + From 6a6aa8867bed9e14a71f00e5f61770ee0db35b94 Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Mon, 11 Apr 2022 10:49:16 +0200 Subject: [PATCH 06/19] [users] hostname.X takes precedence over legacy settings --- src/modules/users/Config.cpp | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/modules/users/Config.cpp b/src/modules/users/Config.cpp index 6cc3a0cea..829d2114f 100644 --- a/src/modules/users/Config.cpp +++ b/src/modules/users/Config.cpp @@ -832,9 +832,16 @@ copyLegacy( const QVariantMap& source, const QString& sourceKey, QVariantMap& ta { if ( source.contains( sourceKey ) ) { - const QVariant legacyValue = source.value( sourceKey ); - cWarning() << "Legacy *users* key" << sourceKey << "overrides hostname-settings."; - target.insert( targetKey, legacyValue ); + if ( target.contains( targetKey ) ) + { + cWarning() << "Legacy *users* key" << sourceKey << "ignored."; + } + else + { + const QVariant legacyValue = source.value( sourceKey ); + cWarning() << "Legacy *users* key" << sourceKey << "overrides hostname-settings."; + target.insert( targetKey, legacyValue ); + } } } From 854c711ac63442603bffed53a5306ec16c6f7ee4 Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Mon, 11 Apr 2022 11:10:40 +0200 Subject: [PATCH 07/19] [users] Untangle setting-hostname from writing-/etc/hosts Exactly one kind of setting-hostname is done, and that's entirely independent of writing /etc/hosts. Don't make it a set of flags, use an enum and a bool. --- src/modules/users/Config.cpp | 14 ++++++------ src/modules/users/Config.h | 20 ++++++++--------- src/modules/users/SetHostNameJob.cpp | 32 +++++++++++++++------------- src/modules/users/SetHostNameJob.h | 5 ++--- src/modules/users/Tests.cpp | 17 +++++++++------ src/modules/users/UsersPage.cpp | 4 ++-- 6 files changed, 47 insertions(+), 45 deletions(-) diff --git a/src/modules/users/Config.cpp b/src/modules/users/Config.cpp index 829d2114f..f6d127b26 100644 --- a/src/modules/users/Config.cpp +++ b/src/modules/users/Config.cpp @@ -734,8 +734,8 @@ setConfigurationDefaultGroups( const QVariantMap& map, QList< GroupDescription > } } -STATICTEST HostNameActions -getHostNameActions( const QVariantMap& configurationMap ) +STATICTEST HostNameAction +getHostNameAction( const QVariantMap& configurationMap ) { HostNameAction setHostName = HostNameAction::EtcHostname; QString hostnameActionString = CalamaresUtils::getString( configurationMap, "location" ); @@ -749,10 +749,7 @@ getHostNameActions( const QVariantMap& configurationMap ) } } - HostNameAction writeHosts = CalamaresUtils::getBool( configurationMap, "writeHostsFile", true ) - ? HostNameAction::WriteEtcHosts - : HostNameAction::None; - return setHostName | writeHosts; + return setHostName; } /** @brief Process entries in the passwordRequirements config entry @@ -871,7 +868,8 @@ Config::setConfigurationMap( const QVariantMap& configurationMap ) // TODO:3.3: Remove calls to copyLegacy copyLegacy( configurationMap, "setHostname", hostnameSettings, "location" ); copyLegacy( configurationMap, "writeHostsFile", hostnameSettings, "writeHostsFile" ); - m_hostNameActions = getHostNameActions( hostnameSettings ); + m_hostNameAction = getHostNameAction( hostnameSettings ); + m_writeEtcHosts = CalamaresUtils::getBool( hostnameSettings, "writeHostsFile", true ); } setConfigurationDefaultGroups( configurationMap, m_defaultGroups ); @@ -951,7 +949,7 @@ Config::createJobs() const j = new SetPasswordJob( "root", rootPassword() ); jobs.append( Calamares::job_ptr( j ) ); - j = new SetHostNameJob( hostName(), hostNameActions() ); + j = new SetHostNameJob( this ); jobs.append( Calamares::job_ptr( j ) ); return jobs; diff --git a/src/modules/users/Config.h b/src/modules/users/Config.h index e37ee77d5..7096674d3 100644 --- a/src/modules/users/Config.h +++ b/src/modules/users/Config.h @@ -20,15 +20,12 @@ #include #include -enum HostNameAction +enum class HostNameAction { - None = 0x0, - EtcHostname = 0x1, // Write to /etc/hostname directly - SystemdHostname = 0x2, // Set via hostnamed(1) - WriteEtcHosts = 0x4 // Write /etc/hosts (127.0.1.1 is this host) + None, + EtcHostname, // Write to /etc/hostname directly + SystemdHostname, // Set via hostnamed(1) }; -Q_DECLARE_FLAGS( HostNameActions, HostNameAction ) -Q_DECLARE_OPERATORS_FOR_FLAGS( HostNameActions ) const NamedEnumTable< HostNameAction >& hostNameActionNames(); @@ -103,7 +100,7 @@ class PLUGINDLLEXPORT Config : public Calamares::ModuleSystem::Config Q_PROPERTY( QString hostName READ hostName WRITE setHostName NOTIFY hostNameChanged ) Q_PROPERTY( QString hostNameStatus READ hostNameStatus NOTIFY hostNameStatusChanged ) - Q_PROPERTY( HostNameActions hostNameActions READ hostNameActions CONSTANT ) + Q_PROPERTY( HostNameAction hostNameAction READ hostNameAction CONSTANT ) Q_PROPERTY( QString userPassword READ userPassword WRITE setUserPassword NOTIFY userPasswordChanged ) Q_PROPERTY( QString userPasswordSecondary READ userPasswordSecondary WRITE setUserPasswordSecondary NOTIFY @@ -208,7 +205,9 @@ public: /// Status message about hostname -- empty for "ok" QString hostNameStatus() const; /// How to write the hostname - HostNameActions hostNameActions() const { return m_hostNameActions; } + HostNameAction hostNameAction() const { return m_hostNameAction; } + /// Write /etc/hosts ? + bool writeEtcHosts() const { return m_writeEtcHosts; } /// Should the user be automatically logged-in? bool doAutoLogin() const { return m_doAutoLogin; } @@ -337,7 +336,8 @@ private: bool m_isReady = false; ///< Used to reduce readyChanged signals - HostNameActions m_hostNameActions; + HostNameAction m_hostNameAction = HostNameAction::EtcHostname; + bool m_writeEtcHosts = false; PasswordCheckList m_passwordChecks; }; diff --git a/src/modules/users/SetHostNameJob.cpp b/src/modules/users/SetHostNameJob.cpp index 9f81ddfb5..667d993b5 100644 --- a/src/modules/users/SetHostNameJob.cpp +++ b/src/modules/users/SetHostNameJob.cpp @@ -24,31 +24,30 @@ using WriteMode = CalamaresUtils::System::WriteMode; -SetHostNameJob::SetHostNameJob( const QString& hostname, HostNameActions a ) +SetHostNameJob::SetHostNameJob( const Config* c ) : Calamares::Job() - , m_hostname( hostname ) - , m_actions( a ) + , m_config( c ) { } QString SetHostNameJob::prettyName() const { - return tr( "Set hostname %1" ).arg( m_hostname ); + return tr( "Set hostname %1" ).arg( m_config->hostName() ); } QString SetHostNameJob::prettyDescription() const { - return tr( "Set hostname %1." ).arg( m_hostname ); + return tr( "Set hostname %1." ).arg( m_config->hostName() ); } QString SetHostNameJob::prettyStatusMessage() const { - return tr( "Setting hostname %1." ).arg( m_hostname ); + return tr( "Setting hostname %1." ).arg( m_config->hostName() ); } STATICTEST bool @@ -129,29 +128,32 @@ SetHostNameJob::exec() return Calamares::JobResult::error( tr( "Internal Error" ) ); } - if ( m_actions & HostNameAction::EtcHostname ) + switch ( m_config->hostNameAction() ) { - if ( !setFileHostname( m_hostname ) ) + case HostNameAction::None: + break; + case HostNameAction::EtcHostname: + if ( !setFileHostname( m_config->hostName() ) ) { cError() << "Can't write to hostname file"; return Calamares::JobResult::error( tr( "Cannot write hostname to target system" ) ); } + break; + case HostNameAction::SystemdHostname: + // Does its own logging + setSystemdHostname( m_config->hostName() ); + break; } - if ( m_actions & HostNameAction::WriteEtcHosts ) + if ( m_config->writeEtcHosts() ) { - if ( !writeFileEtcHosts( m_hostname ) ) + if ( !writeFileEtcHosts( m_config->hostName() ) ) { cError() << "Can't write to hosts file"; return Calamares::JobResult::error( tr( "Cannot write hostname to target system" ) ); } } - if ( m_actions & HostNameAction::SystemdHostname ) - { - // Does its own logging - setSystemdHostname( m_hostname ); - } return Calamares::JobResult::ok(); } diff --git a/src/modules/users/SetHostNameJob.h b/src/modules/users/SetHostNameJob.h index 9d44579cb..b32b1d7bb 100644 --- a/src/modules/users/SetHostNameJob.h +++ b/src/modules/users/SetHostNameJob.h @@ -20,15 +20,14 @@ class SetHostNameJob : public Calamares::Job { Q_OBJECT public: - SetHostNameJob( const QString& hostname, HostNameActions a ); + SetHostNameJob( const Config* c ); QString prettyName() const override; QString prettyDescription() const override; QString prettyStatusMessage() const override; Calamares::JobResult exec() override; private: - const QString m_hostname; - const HostNameActions m_actions; + const Config* m_config; }; #endif // SETHOSTNAMEJOB_CPP_H diff --git a/src/modules/users/Tests.cpp b/src/modules/users/Tests.cpp index 230e60d4e..12f96df46 100644 --- a/src/modules/users/Tests.cpp +++ b/src/modules/users/Tests.cpp @@ -17,7 +17,7 @@ // Implementation details extern void setConfigurationDefaultGroups( const QVariantMap& map, QList< GroupDescription >& defaultGroups ); -extern HostNameActions getHostNameActions( const QVariantMap& configurationMap ); +extern HostNameAction getHostNameAction( const QVariantMap& configurationMap ); extern bool addPasswordCheck( const QString& key, const QVariant& value, PasswordCheckList& passwordChecks ); /** @brief Test Config object methods and internals @@ -243,12 +243,12 @@ UserTests::testHostActions() { m.insert( "location", string ); } - QCOMPARE( getHostNameActions( m ), - HostNameActions( result ) | HostNameAction::WriteEtcHosts ); // write bits default to true + // action is independent of writeHostsFile + QCOMPARE( getHostNameAction( m ), HostNameAction( result ) ); m.insert( "writeHostsFile", false ); - QCOMPARE( getHostNameActions( m ), HostNameActions( result ) ); + QCOMPARE( getHostNameAction( m ), HostNameAction( result ) ); m.insert( "writeHostsFile", true ); - QCOMPARE( getHostNameActions( m ), HostNameActions( result ) | HostNameAction::WriteEtcHosts ); + QCOMPARE( getHostNameAction( m ), HostNameAction( result ) ); } void @@ -257,13 +257,16 @@ UserTests::testHostActions2() Config c; QVariantMap legacy; + // Test defaults c.setConfigurationMap( legacy ); - QCOMPARE( c.hostNameActions(), HostNameAction::EtcHostname | HostNameAction::WriteEtcHosts ); + QCOMPARE( c.hostNameAction(), HostNameAction::EtcHostname ); + QCOMPARE( c.writeEtcHosts(), true ); legacy.insert( "writeHostsFile", false ); legacy.insert( "setHostname", "Hostnamed" ); c.setConfigurationMap( legacy ); - QCOMPARE( c.hostNameActions(), HostNameAction::SystemdHostname ); + QCOMPARE( c.hostNameAction(), HostNameAction::SystemdHostname ); + QCOMPARE( c.writeEtcHosts(), false ); } diff --git a/src/modules/users/UsersPage.cpp b/src/modules/users/UsersPage.cpp index d3c61326e..13ed47d68 100644 --- a/src/modules/users/UsersPage.cpp +++ b/src/modules/users/UsersPage.cpp @@ -106,8 +106,8 @@ UsersPage::UsersPage( Config* config, QWidget* parent ) connect( config, &Config::fullNameChanged, this, &UsersPage::onFullNameTextEdited ); // If the hostname is going to be written out, then show the field - if ( ( m_config->hostNameActions() & HostNameAction::EtcHostname ) - || ( m_config->hostNameActions() & HostNameAction::SystemdHostname ) ) + if ( ( m_config->hostNameAction() == HostNameAction::EtcHostname ) + || ( m_config->hostNameAction() == HostNameAction::SystemdHostname ) ) { ui->textBoxHostname->setText( config->hostName() ); connect( ui->textBoxHostname, &QLineEdit::textEdited, config, &Config::setHostName ); From c5c546d2901a40211a58320b60e6012026c51c9b Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Mon, 11 Apr 2022 11:13:45 +0200 Subject: [PATCH 08/19] [users] Warnings-- about unreachable code --- src/modules/users/MiscJobs.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/modules/users/MiscJobs.cpp b/src/modules/users/MiscJobs.cpp index 5cba202e0..fec546d96 100644 --- a/src/modules/users/MiscJobs.cpp +++ b/src/modules/users/MiscJobs.cpp @@ -41,13 +41,10 @@ designatorForStyle( Config::SudoStyle style ) { case Config::SudoStyle::UserOnly: return QStringLiteral( "(ALL)" ); - break; case Config::SudoStyle::UserAndGroup: return QStringLiteral( "(ALL:ALL)" ); - break; } __builtin_unreachable(); - return QString(); } Calamares::JobResult From a12c6de2ef1588a3cc9c8d015188dbdc7bfcb1ea Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Mon, 11 Apr 2022 11:18:30 +0200 Subject: [PATCH 09/19] [users] Don't set GS hostname if hostname.location is None --- src/modules/users/Config.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/modules/users/Config.cpp b/src/modules/users/Config.cpp index f6d127b26..1630ce9e3 100644 --- a/src/modules/users/Config.cpp +++ b/src/modules/users/Config.cpp @@ -240,6 +240,11 @@ Config::loginNameStatus() const void Config::setHostName( const QString& host ) { + if ( hostNameAction() != HostNameAction::EtcHostname && hostNameAction() != HostNameAction::SystemdHostname ) + { + cDebug() << "Ignoring hostname" << host << "No hostname will be set."; + return; + } if ( host != m_hostName ) { m_customHostName = !host.isEmpty(); From 9299bedd7e37e8f0523e27a8585ca7031bac561c Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Mon, 11 Apr 2022 11:37:56 +0200 Subject: [PATCH 10/19] [users] Hostname is empty if it will not be set --- src/modules/users/Config.h | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/modules/users/Config.h b/src/modules/users/Config.h index 7096674d3..14e29d1e6 100644 --- a/src/modules/users/Config.h +++ b/src/modules/users/Config.h @@ -201,7 +201,13 @@ public: QString loginNameStatus() const; /// The host name (name for the system) - QString hostName() const { return m_hostName; } + QString hostName() const + { + return ( ( hostNameAction() == HostNameAction::EtcHostname ) + || ( hostNameAction() == HostNameAction::SystemdHostname ) ) + ? m_hostName + : QString(); + } /// Status message about hostname -- empty for "ok" QString hostNameStatus() const; /// How to write the hostname From 3b0aa69ad3a98c0f701c61ed4732d54d902651ea Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Mon, 11 Apr 2022 11:39:14 +0200 Subject: [PATCH 11/19] [users] Write 127.0.1.1 entry only if there is a hostname --- src/modules/users/SetHostNameJob.cpp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/modules/users/SetHostNameJob.cpp b/src/modules/users/SetHostNameJob.cpp index 667d993b5..6d35245cd 100644 --- a/src/modules/users/SetHostNameJob.cpp +++ b/src/modules/users/SetHostNameJob.cpp @@ -61,16 +61,19 @@ STATICTEST bool writeFileEtcHosts( const QString& hostname ) { // The actual hostname gets substituted in at %1 - static const char etc_hosts[] = R"(# Host addresses + const QString standard_hosts = QStringLiteral( R"(# Standard host addresses 127.0.0.1 localhost -127.0.1.1 %1 ::1 localhost ip6-localhost ip6-loopback ff02::1 ip6-allnodes ff02::2 ip6-allrouters -)"; +)" ); + const QString this_host = QStringLiteral( R"(# This host address +127.0.1.1 %1 +)" ); + const QString etc_hosts = standard_hosts + ( hostname.isEmpty() ? QString() : this_host.arg( hostname ) ); return CalamaresUtils::System::instance()->createTargetFile( - QStringLiteral( "/etc/hosts" ), QString( etc_hosts ).arg( hostname ).toUtf8(), WriteMode::Overwrite ); + QStringLiteral( "/etc/hosts" ), etc_hosts.toUtf8(), WriteMode::Overwrite ); } STATICTEST bool From 3524d4a0d0efe8d18b70e9e8d298d29a35a26091 Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Mon, 11 Apr 2022 11:47:47 +0200 Subject: [PATCH 12/19] [users] Add "transient" for hostname-location --- src/modules/users/Config.cpp | 4 +++- src/modules/users/Config.h | 1 + src/modules/users/SetHostNameJob.cpp | 3 +++ src/modules/users/users.conf | 2 ++ 4 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/modules/users/Config.cpp b/src/modules/users/Config.cpp index 1630ce9e3..36a0ca1bb 100644 --- a/src/modules/users/Config.cpp +++ b/src/modules/users/Config.cpp @@ -84,7 +84,9 @@ hostNameActionNames() static const NamedEnumTable< HostNameAction > names { { QStringLiteral( "none" ), HostNameAction::None }, { QStringLiteral( "etcfile" ), HostNameAction::EtcHostname }, - { QStringLiteral( "hostnamed" ), HostNameAction::SystemdHostname } + { QStringLiteral( "etc" ), HostNameAction::EtcHostname }, + { QStringLiteral( "hostnamed" ), HostNameAction::SystemdHostname }, + { QStringLiteral( "transient" ), HostNameAction::Transient }, }; // clang-format on // *INDENT-ON* diff --git a/src/modules/users/Config.h b/src/modules/users/Config.h index 14e29d1e6..75c2d4bda 100644 --- a/src/modules/users/Config.h +++ b/src/modules/users/Config.h @@ -25,6 +25,7 @@ enum class HostNameAction None, EtcHostname, // Write to /etc/hostname directly SystemdHostname, // Set via hostnamed(1) + Transient, // Force target system transient, remove /etc/hostname }; const NamedEnumTable< HostNameAction >& hostNameActionNames(); diff --git a/src/modules/users/SetHostNameJob.cpp b/src/modules/users/SetHostNameJob.cpp index 6d35245cd..94b71f568 100644 --- a/src/modules/users/SetHostNameJob.cpp +++ b/src/modules/users/SetHostNameJob.cpp @@ -146,6 +146,9 @@ SetHostNameJob::exec() // Does its own logging setSystemdHostname( m_config->hostName() ); break; + case HostNameAction::Transient: + CalamaresUtils::System::instance()->removeTargetFile( QStringLiteral( "/etc/hostname" ) ); + break; } if ( m_config->writeEtcHosts() ) diff --git a/src/modules/users/users.conf b/src/modules/users/users.conf index 22e7e0a68..c5cc8d2af 100644 --- a/src/modules/users/users.conf +++ b/src/modules/users/users.conf @@ -161,7 +161,9 @@ userShell: /bin/bash # - *location* How the hostname is set in the target system: # - *None*, to not set the hostname at all # - *EtcFile*, to write to `/etc/hostname` directly +# - *Etc*, identical to above # - *Hostnamed*, to use systemd hostnamed(1) over DBus +# - *Transient*, to remove `/etc/hostname` from the target # The default is *EtcFile*. Setting this to *None* will # hide the hostname field. # - *writeHostsFile* Should /etc/hosts be written with a hostname for From 92b134173044971b10843cf0082de4064f20fea4 Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Mon, 11 Apr 2022 12:05:00 +0200 Subject: [PATCH 13/19] [users] Document new hostname.location setting - add to schema - add to tests - mention in CHANGES --- CHANGES-3.2 | 4 +++- src/modules/users/Tests.cpp | 9 +++++++++ src/modules/users/users.schema.yaml | 2 +- 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/CHANGES-3.2 b/CHANGES-3.2 index 8cec4bb78..578590134 100644 --- a/CHANGES-3.2 +++ b/CHANGES-3.2 @@ -20,7 +20,9 @@ This release contains contributions from (alphabetically by first name): - *users* module has rearranged configuration for setting the hostname. Legacy settings are preserved, but produce a warning. Please see `users.conf` for details. - + - *users* module has a new hostname.location setting, *Transient*, which + will force the installed system to transient-hostname-setting by removing + the file `/etc/hostname`. # 3.2.54 (2022-03-21) # diff --git a/src/modules/users/Tests.cpp b/src/modules/users/Tests.cpp index 12f96df46..e1c31721a 100644 --- a/src/modules/users/Tests.cpp +++ b/src/modules/users/Tests.cpp @@ -229,6 +229,15 @@ UserTests::testHostActions_data() QTest::newRow( "bad " ) << true << QString( "derp" ) << int( HostNameAction::EtcHostname ); QTest::newRow( "none " ) << true << QString( "none" ) << int( HostNameAction::None ); QTest::newRow( "systemd" ) << true << QString( "Hostnamed" ) << int( HostNameAction::SystemdHostname ); + QTest::newRow( "etc(1) " ) << true << QString( "etcfile" ) << int( HostNameAction::EtcHostname ); + QTest::newRow( "etc(2) " ) << true << QString( "etc" ) << int( HostNameAction::EtcHostname ); + QTest::newRow( "etc-bad" ) + << true << QString( "etchost" ) + << int( HostNameAction::EtcHostname ); // This isn't a valid name, but defaults to EtcHostname + QTest::newRow( "ci-sysd" ) << true << QString( "hOsTnaMed" ) + << int( HostNameAction::SystemdHostname ); // Case-insensitive + QTest::newRow( "trbs " ) << true << QString( "transient" ) << int( HostNameAction::Transient ); + QTest::newRow( "ci-trns" ) << true << QString( "trANSient" ) << int( HostNameAction::Transient ); } void diff --git a/src/modules/users/users.schema.yaml b/src/modules/users/users.schema.yaml index 42ce42b44..c7088253a 100644 --- a/src/modules/users/users.schema.yaml +++ b/src/modules/users/users.schema.yaml @@ -44,7 +44,7 @@ properties: additionalProperties: false type: object properties: - location: { type: string, enum: [ None, EtcFile, Hostnamed ] } + location: { type: string, enum: [ None, EtcFile, Hostnamed, Transient ] } writeHostsFile: { type: boolean, default: true } # Legacy Hostname setting setHostname: { type: string, enum: [ None, EtcFile, Hostnamed ] } From 1a8fc1feec9228d1d9d724104dbe26c2bb52cdc1 Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Mon, 11 Apr 2022 12:16:03 +0200 Subject: [PATCH 14/19] [users] Settle on 'hostname' as a single word for camel-casing --- src/modules/users/Config.cpp | 34 ++++++++++++++-------------- src/modules/users/Config.h | 28 +++++++++++------------ src/modules/users/SetHostNameJob.cpp | 14 ++++++------ src/modules/users/Tests.cpp | 4 ++-- src/modules/users/UsersPage.cpp | 14 ++++++------ src/modules/usersq/usersq.qml | 2 +- 6 files changed, 48 insertions(+), 48 deletions(-) diff --git a/src/modules/users/Config.cpp b/src/modules/users/Config.cpp index 36a0ca1bb..1acbca1a1 100644 --- a/src/modules/users/Config.cpp +++ b/src/modules/users/Config.cpp @@ -77,7 +77,7 @@ updateGSAutoLogin( bool doAutoLogin, const QString& login ) } const NamedEnumTable< HostNameAction >& -hostNameActionNames() +hostnameActionNames() { // *INDENT-OFF* // clang-format off @@ -100,7 +100,7 @@ Config::Config( QObject* parent ) emit readyChanged( m_isReady ); // false // Gang together all the changes of status to one readyChanged() signal - connect( this, &Config::hostNameStatusChanged, this, &Config::checkReady ); + connect( this, &Config::hostnameStatusChanged, this, &Config::checkReady ); connect( this, &Config::loginNameStatusChanged, this, &Config::checkReady ); connect( this, &Config::fullNameChanged, this, &Config::checkReady ); connect( this, &Config::userPasswordStatusChanged, this, &Config::checkReady ); @@ -242,15 +242,15 @@ Config::loginNameStatus() const void Config::setHostName( const QString& host ) { - if ( hostNameAction() != HostNameAction::EtcHostname && hostNameAction() != HostNameAction::SystemdHostname ) + if ( hostnameAction() != HostNameAction::EtcHostname && hostnameAction() != HostNameAction::SystemdHostname ) { cDebug() << "Ignoring hostname" << host << "No hostname will be set."; return; } - if ( host != m_hostName ) + if ( host != m_hostname ) { m_customHostName = !host.isEmpty(); - m_hostName = host; + m_hostname = host; Calamares::GlobalStorage* gs = Calamares::JobQueue::instance()->globalStorage(); if ( host.isEmpty() ) { @@ -260,8 +260,8 @@ Config::setHostName( const QString& host ) { gs->insert( "hostname", host ); } - emit hostNameChanged( host ); - emit hostNameStatusChanged( hostNameStatus() ); + emit hostnameChanged( host ); + emit hostnameStatusChanged( hostnameStatus() ); } } @@ -273,31 +273,31 @@ Config::forbiddenHostNames() } QString -Config::hostNameStatus() const +Config::hostnameStatus() const { // An empty hostname is "ok", even if it isn't really - if ( m_hostName.isEmpty() ) + if ( m_hostname.isEmpty() ) { return QString(); } - if ( m_hostName.length() < HOSTNAME_MIN_LENGTH ) + if ( m_hostname.length() < HOSTNAME_MIN_LENGTH ) { return tr( "Your hostname is too short." ); } - if ( m_hostName.length() > HOSTNAME_MAX_LENGTH ) + if ( m_hostname.length() > HOSTNAME_MAX_LENGTH ) { return tr( "Your hostname is too long." ); } for ( const QString& badName : forbiddenHostNames() ) { - if ( 0 == QString::compare( badName, m_hostName, Qt::CaseSensitive ) ) + if ( 0 == QString::compare( badName, m_hostname, Qt::CaseSensitive ) ) { return tr( "'%1' is not allowed as hostname." ).arg( badName ); } } - if ( !HOSTNAME_RX.exactMatch( m_hostName ) ) + if ( !HOSTNAME_RX.exactMatch( m_hostname ) ) { return tr( "Only letters, numbers, underscore and hyphen are allowed." ); } @@ -449,7 +449,7 @@ Config::setFullName( const QString& name ) if ( !m_customHostName ) { QString hostname = makeHostnameSuggestion( cleanParts ); - if ( !hostname.isEmpty() && hostname != m_hostName ) + if ( !hostname.isEmpty() && hostname != m_hostname ) { setHostName( hostname ); // Still not custom @@ -660,7 +660,7 @@ bool Config::isReady() const { bool readyFullName = !fullName().isEmpty(); // Needs some text - bool readyHostname = hostNameStatus().isEmpty(); // .. no warning message + bool readyHostname = hostnameStatus().isEmpty(); // .. no warning message bool readyUsername = !loginName().isEmpty() && loginNameStatus().isEmpty(); // .. no warning message bool readyUserPassword = userPasswordValidity() != Config::PasswordValidity::Invalid; bool readyRootPassword = rootPasswordValidity() != Config::PasswordValidity::Invalid; @@ -749,7 +749,7 @@ getHostNameAction( const QVariantMap& configurationMap ) if ( !hostnameActionString.isEmpty() ) { bool ok = false; - setHostName = hostNameActionNames().find( hostnameActionString, ok ); + setHostName = hostnameActionNames().find( hostnameActionString, ok ); if ( !ok ) { setHostName = HostNameAction::EtcHostname; // Rather than none @@ -875,7 +875,7 @@ Config::setConfigurationMap( const QVariantMap& configurationMap ) // TODO:3.3: Remove calls to copyLegacy copyLegacy( configurationMap, "setHostname", hostnameSettings, "location" ); copyLegacy( configurationMap, "writeHostsFile", hostnameSettings, "writeHostsFile" ); - m_hostNameAction = getHostNameAction( hostnameSettings ); + m_hostnameAction = getHostNameAction( hostnameSettings ); m_writeEtcHosts = CalamaresUtils::getBool( hostnameSettings, "writeHostsFile", true ); } diff --git a/src/modules/users/Config.h b/src/modules/users/Config.h index 75c2d4bda..6ddc72c67 100644 --- a/src/modules/users/Config.h +++ b/src/modules/users/Config.h @@ -28,7 +28,7 @@ enum class HostNameAction Transient, // Force target system transient, remove /etc/hostname }; -const NamedEnumTable< HostNameAction >& hostNameActionNames(); +const NamedEnumTable< HostNameAction >& hostnameActionNames(); /** @brief Settings for a single group * @@ -99,9 +99,9 @@ class PLUGINDLLEXPORT Config : public Calamares::ModuleSystem::Config Q_PROPERTY( QString loginName READ loginName WRITE setLoginName NOTIFY loginNameChanged ) Q_PROPERTY( QString loginNameStatus READ loginNameStatus NOTIFY loginNameStatusChanged ) - Q_PROPERTY( QString hostName READ hostName WRITE setHostName NOTIFY hostNameChanged ) - Q_PROPERTY( QString hostNameStatus READ hostNameStatus NOTIFY hostNameStatusChanged ) - Q_PROPERTY( HostNameAction hostNameAction READ hostNameAction CONSTANT ) + Q_PROPERTY( QString hostname READ hostname WRITE setHostName NOTIFY hostnameChanged ) + Q_PROPERTY( QString hostnameStatus READ hostnameStatus NOTIFY hostnameStatusChanged ) + Q_PROPERTY( HostNameAction hostnameAction READ hostnameAction CONSTANT ) Q_PROPERTY( QString userPassword READ userPassword WRITE setUserPassword NOTIFY userPasswordChanged ) Q_PROPERTY( QString userPasswordSecondary READ userPasswordSecondary WRITE setUserPasswordSecondary NOTIFY @@ -202,17 +202,17 @@ public: QString loginNameStatus() const; /// The host name (name for the system) - QString hostName() const + QString hostname() const { - return ( ( hostNameAction() == HostNameAction::EtcHostname ) - || ( hostNameAction() == HostNameAction::SystemdHostname ) ) - ? m_hostName + return ( ( hostnameAction() == HostNameAction::EtcHostname ) + || ( hostnameAction() == HostNameAction::SystemdHostname ) ) + ? m_hostname : QString(); } /// Status message about hostname -- empty for "ok" - QString hostNameStatus() const; + QString hostnameStatus() const; /// How to write the hostname - HostNameAction hostNameAction() const { return m_hostNameAction; } + HostNameAction hostnameAction() const { return m_hostnameAction; } /// Write /etc/hosts ? bool writeEtcHosts() const { return m_writeEtcHosts; } @@ -299,8 +299,8 @@ signals: void fullNameChanged( const QString& ); void loginNameChanged( const QString& ); void loginNameStatusChanged( const QString& ); - void hostNameChanged( const QString& ); - void hostNameStatusChanged( const QString& ); + void hostnameChanged( const QString& ); + void hostnameStatusChanged( const QString& ); void autoLoginChanged( bool ); void reuseUserPasswordForRootChanged( bool ); void requireStrongPasswordsChanged( bool ); @@ -323,7 +323,7 @@ private: SudoStyle m_sudoStyle = SudoStyle::UserOnly; QString m_fullName; QString m_loginName; - QString m_hostName; + QString m_hostname; QString m_userPassword; QString m_userPasswordSecondary; // enter again to be sure @@ -343,7 +343,7 @@ private: bool m_isReady = false; ///< Used to reduce readyChanged signals - HostNameAction m_hostNameAction = HostNameAction::EtcHostname; + HostNameAction m_hostnameAction = HostNameAction::EtcHostname; bool m_writeEtcHosts = false; PasswordCheckList m_passwordChecks; }; diff --git a/src/modules/users/SetHostNameJob.cpp b/src/modules/users/SetHostNameJob.cpp index 94b71f568..452f6a962 100644 --- a/src/modules/users/SetHostNameJob.cpp +++ b/src/modules/users/SetHostNameJob.cpp @@ -33,21 +33,21 @@ SetHostNameJob::SetHostNameJob( const Config* c ) QString SetHostNameJob::prettyName() const { - return tr( "Set hostname %1" ).arg( m_config->hostName() ); + return tr( "Set hostname %1" ).arg( m_config->hostname() ); } QString SetHostNameJob::prettyDescription() const { - return tr( "Set hostname %1." ).arg( m_config->hostName() ); + return tr( "Set hostname %1." ).arg( m_config->hostname() ); } QString SetHostNameJob::prettyStatusMessage() const { - return tr( "Setting hostname %1." ).arg( m_config->hostName() ); + return tr( "Setting hostname %1." ).arg( m_config->hostname() ); } STATICTEST bool @@ -131,12 +131,12 @@ SetHostNameJob::exec() return Calamares::JobResult::error( tr( "Internal Error" ) ); } - switch ( m_config->hostNameAction() ) + switch ( m_config->hostnameAction() ) { case HostNameAction::None: break; case HostNameAction::EtcHostname: - if ( !setFileHostname( m_config->hostName() ) ) + if ( !setFileHostname( m_config->hostname() ) ) { cError() << "Can't write to hostname file"; return Calamares::JobResult::error( tr( "Cannot write hostname to target system" ) ); @@ -144,7 +144,7 @@ SetHostNameJob::exec() break; case HostNameAction::SystemdHostname: // Does its own logging - setSystemdHostname( m_config->hostName() ); + setSystemdHostname( m_config->hostname() ); break; case HostNameAction::Transient: CalamaresUtils::System::instance()->removeTargetFile( QStringLiteral( "/etc/hostname" ) ); @@ -153,7 +153,7 @@ SetHostNameJob::exec() if ( m_config->writeEtcHosts() ) { - if ( !writeFileEtcHosts( m_config->hostName() ) ) + if ( !writeFileEtcHosts( m_config->hostname() ) ) { cError() << "Can't write to hosts file"; return Calamares::JobResult::error( tr( "Cannot write hostname to target system" ) ); diff --git a/src/modules/users/Tests.cpp b/src/modules/users/Tests.cpp index e1c31721a..d9ce3636d 100644 --- a/src/modules/users/Tests.cpp +++ b/src/modules/users/Tests.cpp @@ -268,13 +268,13 @@ UserTests::testHostActions2() // Test defaults c.setConfigurationMap( legacy ); - QCOMPARE( c.hostNameAction(), HostNameAction::EtcHostname ); + QCOMPARE( c.hostnameAction(), HostNameAction::EtcHostname ); QCOMPARE( c.writeEtcHosts(), true ); legacy.insert( "writeHostsFile", false ); legacy.insert( "setHostname", "Hostnamed" ); c.setConfigurationMap( legacy ); - QCOMPARE( c.hostNameAction(), HostNameAction::SystemdHostname ); + QCOMPARE( c.hostnameAction(), HostNameAction::SystemdHostname ); QCOMPARE( c.writeEtcHosts(), false ); } diff --git a/src/modules/users/UsersPage.cpp b/src/modules/users/UsersPage.cpp index 13ed47d68..7936e9fc0 100644 --- a/src/modules/users/UsersPage.cpp +++ b/src/modules/users/UsersPage.cpp @@ -106,13 +106,13 @@ UsersPage::UsersPage( Config* config, QWidget* parent ) connect( config, &Config::fullNameChanged, this, &UsersPage::onFullNameTextEdited ); // If the hostname is going to be written out, then show the field - if ( ( m_config->hostNameAction() == HostNameAction::EtcHostname ) - || ( m_config->hostNameAction() == HostNameAction::SystemdHostname ) ) + if ( ( m_config->hostnameAction() == HostNameAction::EtcHostname ) + || ( m_config->hostnameAction() == HostNameAction::SystemdHostname ) ) { - ui->textBoxHostname->setText( config->hostName() ); + ui->textBoxHostname->setText( config->hostname() ); connect( ui->textBoxHostname, &QLineEdit::textEdited, config, &Config::setHostName ); connect( config, - &Config::hostNameChanged, + &Config::hostnameChanged, [ this ]( const QString& name ) { if ( !ui->textBoxHostname->hasFocus() ) @@ -120,7 +120,7 @@ UsersPage::UsersPage( Config* config, QWidget* parent ) ui->textBoxHostname->setText( name ); } } ); - connect( config, &Config::hostNameStatusChanged, this, &UsersPage::reportHostNameStatus ); + connect( config, &Config::hostnameStatusChanged, this, &UsersPage::reportHostNameStatus ); } else { @@ -168,7 +168,7 @@ UsersPage::UsersPage( Config* config, QWidget* parent ) onReuseUserPasswordChanged( m_config->reuseUserPasswordForRoot() ); onFullNameTextEdited( m_config->fullName() ); reportLoginNameStatus( m_config->loginNameStatus() ); - reportHostNameStatus( m_config->hostNameStatus() ); + reportHostNameStatus( m_config->hostnameStatus() ); ui->textBoxLoginName->setEnabled( m_config->isEditable( "loginName" ) ); ui->textBoxFullName->setEnabled( m_config->isEditable( "fullName" ) ); @@ -231,7 +231,7 @@ UsersPage::reportLoginNameStatus( const QString& status ) void UsersPage::reportHostNameStatus( const QString& status ) { - labelStatus( ui->labelHostname, ui->labelHostnameError, m_config->hostName(), status ); + labelStatus( ui->labelHostname, ui->labelHostnameError, m_config->hostname(), status ); } static inline void diff --git a/src/modules/usersq/usersq.qml b/src/modules/usersq/usersq.qml index 71155af4b..9d49e6a7d 100644 --- a/src/modules/usersq/usersq.qml +++ b/src/modules/usersq/usersq.qml @@ -149,7 +149,7 @@ Kirigami.ScrollablePage { id: _hostName width: parent.width placeholderText: qsTr("Computer Name") - text: config.hostName + text: config.hostname validator: RegularExpressionValidator { regularExpression: /[a-zA-Z0-9][-a-zA-Z0-9_]+/ } onTextChanged: acceptableInput From b95eb559948ee7b048cd5283b4bfd760af642a58 Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Mon, 11 Apr 2022 14:12:05 +0200 Subject: [PATCH 15/19] [users] Add a template for hostname suggestion --- src/modules/users/Config.cpp | 67 +++++++++++++++++++++++++----------- src/modules/users/Config.h | 2 ++ src/modules/users/Tests.cpp | 33 ++++++++++++++++++ 3 files changed, 81 insertions(+), 21 deletions(-) diff --git a/src/modules/users/Config.cpp b/src/modules/users/Config.cpp index 1acbca1a1..cebe45452 100644 --- a/src/modules/users/Config.cpp +++ b/src/modules/users/Config.cpp @@ -20,6 +20,8 @@ #include "utils/String.h" #include "utils/Variant.h" +#include + #include #include #include @@ -305,6 +307,12 @@ Config::hostnameStatus() const return QString(); } +static QString +cleanupForHostname( const QString& s ) +{ + QRegExp dmirx( "[^a-zA-Z0-9]", Qt::CaseInsensitive ); + return s.toLower().replace( dmirx, " " ).remove( ' ' ); +} /** @brief Guess the machine's name * @@ -319,16 +327,11 @@ guessProductName() if ( !tried ) { - // yes validateHostnameText() but these files can be a mess - QRegExp dmirx( "[^a-zA-Z0-9]", Qt::CaseInsensitive ); QFile dmiFile( QStringLiteral( "/sys/devices/virtual/dmi/id/product_name" ) ); if ( dmiFile.exists() && dmiFile.open( QIODevice::ReadOnly ) ) { - dmiProduct = QString::fromLocal8Bit( dmiFile.readAll().simplified().data() ) - .toLower() - .replace( dmirx, " " ) - .remove( ' ' ); + dmiProduct = cleanupForHostname( QString::fromLocal8Bit( dmiFile.readAll().simplified().data() ) ); } if ( dmiProduct.isEmpty() ) { @@ -386,17 +389,37 @@ makeLoginNameSuggestion( const QStringList& parts ) return USERNAME_RX.indexIn( usernameSuggestion ) != -1 ? usernameSuggestion : QString(); } +/** @brief Return an invalid string for use in a hostname, if @p s is empty + * + * Maps empty to "^" (which is invalid in a hostname), everything else + * returns @p s itself. + */ static QString -makeHostnameSuggestion( const QStringList& parts ) +invalidEmpty( const QString& s ) { - static const QRegExp HOSTNAME_RX( "^[a-zA-Z0-9][-a-zA-Z0-9_]*$" ); - if ( parts.isEmpty() || parts.first().isEmpty() ) - { - return QString(); - } + return s.isEmpty() ? QStringLiteral( "^" ) : s; +} - QString productName = guessProductName(); - QString hostnameSuggestion = QStringLiteral( "%1-%2" ).arg( parts.first() ).arg( productName ); +STATICTEST QString +makeHostnameSuggestion( const QString& templateString, const QStringList& fullNameParts, const QString& loginName ) +{ + QHash< QString, QString > replace; + // User data + replace.insert( QStringLiteral( "first" ), + invalidEmpty( fullNameParts.isEmpty() ? QString() : cleanupForHostname( fullNameParts.first() ) ) ); + replace.insert( QStringLiteral( "name" ), invalidEmpty( cleanupForHostname( fullNameParts.join( QString() ) ) ) ); + replace.insert( QStringLiteral( "login" ), invalidEmpty( cleanupForHostname( loginName ) ) ); + // Hardware data + replace.insert( QStringLiteral( "product" ), guessProductName() ); + replace.insert( QStringLiteral( "product2" ), cleanupForHostname( QSysInfo::prettyProductName() ) ); + replace.insert( QStringLiteral( "cpu" ), cleanupForHostname( QSysInfo::currentCpuArchitecture() ) ); + // Hostname data + replace.insert( QStringLiteral( "host" ), invalidEmpty( cleanupForHostname( QSysInfo::machineHostName() ) ) ); + + QString hostnameSuggestion = KMacroExpander::expandMacros( templateString, replace, '$' ); + + // RegExp for valid hostnames; if the suggestion produces a valid name, return it + static const QRegExp HOSTNAME_RX( "^[a-zA-Z0-9][-a-zA-Z0-9_]*$" ); return HOSTNAME_RX.indexIn( hostnameSuggestion ) != -1 ? hostnameSuggestion : QString(); } @@ -427,18 +450,18 @@ Config::setFullName( const QString& name ) // Build login and hostname, if needed static QRegExp rx( "[^a-zA-Z0-9 ]", Qt::CaseInsensitive ); - QString cleanName = CalamaresUtils::removeDiacritics( transliterate( name ) ) - .replace( QRegExp( "[-']" ), "" ) - .replace( rx, " " ) - .toLower() - .simplified(); + const QString cleanName = CalamaresUtils::removeDiacritics( transliterate( name ) ) + .replace( QRegExp( "[-']" ), "" ) + .replace( rx, " " ) + .toLower() + .simplified(); QStringList cleanParts = cleanName.split( ' ' ); if ( !m_customLoginName ) { - QString login = makeLoginNameSuggestion( cleanParts ); + const QString login = makeLoginNameSuggestion( cleanParts ); if ( !login.isEmpty() && login != m_loginName ) { setLoginName( login ); @@ -448,7 +471,7 @@ Config::setFullName( const QString& name ) } if ( !m_customHostName ) { - QString hostname = makeHostnameSuggestion( cleanParts ); + const QString hostname = makeHostnameSuggestion( m_hostnameTemplate, cleanParts, loginName() ); if ( !hostname.isEmpty() && hostname != m_hostname ) { setHostName( hostname ); @@ -877,6 +900,8 @@ Config::setConfigurationMap( const QVariantMap& configurationMap ) copyLegacy( configurationMap, "writeHostsFile", hostnameSettings, "writeHostsFile" ); m_hostnameAction = getHostNameAction( hostnameSettings ); m_writeEtcHosts = CalamaresUtils::getBool( hostnameSettings, "writeHostsFile", true ); + m_hostnameTemplate + = CalamaresUtils::getString( hostnameSettings, "template", QStringLiteral( "${first}-${product}" ) ); } setConfigurationDefaultGroups( configurationMap, m_defaultGroups ); diff --git a/src/modules/users/Config.h b/src/modules/users/Config.h index 6ddc72c67..c395dc1d4 100644 --- a/src/modules/users/Config.h +++ b/src/modules/users/Config.h @@ -345,6 +345,8 @@ private: HostNameAction m_hostnameAction = HostNameAction::EtcHostname; bool m_writeEtcHosts = false; + QString m_hostnameTemplate; + PasswordCheckList m_passwordChecks; }; diff --git a/src/modules/users/Tests.cpp b/src/modules/users/Tests.cpp index d9ce3636d..9c410cb1f 100644 --- a/src/modules/users/Tests.cpp +++ b/src/modules/users/Tests.cpp @@ -19,6 +19,8 @@ extern void setConfigurationDefaultGroups( const QVariantMap& map, QList< GroupDescription >& defaultGroups ); extern HostNameAction getHostNameAction( const QVariantMap& configurationMap ); extern bool addPasswordCheck( const QString& key, const QVariant& value, PasswordCheckList& passwordChecks ); +extern QString +makeHostnameSuggestion( const QString& templateString, const QStringList& fullNameParts, const QString& loginName ); /** @brief Test Config object methods and internals * @@ -43,6 +45,9 @@ private Q_SLOTS: void testHostActions_data(); void testHostActions(); void testHostActions2(); + void testHostSuggestions_data(); + void testHostSuggestions(); + void testPasswordChecks(); void testUserPassword(); @@ -279,6 +284,34 @@ UserTests::testHostActions2() } +void +UserTests::testHostSuggestions_data() +{ + QTest::addColumn< QString >( "templateString" ); + QTest::addColumn< QString >( "result" ); + + QTest::newRow( "unset " ) << QString() << QString(); + QTest::newRow( "const " ) << QStringLiteral( "derp" ) << QStringLiteral( "derp" ); + QTest::newRow( "escaped" ) << QStringLiteral( "$$" ) << QString(); // Because invalid + QTest::newRow( "default" ) << QStringLiteral( "${first}-pc" ) + << QStringLiteral( "chuck-pc" ); // Avoid ${product} because it's DMI-based + QTest::newRow( "full " ) << QStringLiteral( "${name}" ) << QStringLiteral( "chuckyeager" ); + QTest::newRow( "login+ " ) << QStringLiteral( "${login}-${first}" ) << QStringLiteral( "bill-chuck" ); +} + +void +UserTests::testHostSuggestions() +{ + const QStringList fullName { "Chuck", "Yeager" }; + const QString login { "bill" }; + + QFETCH( QString, templateString ); + QFETCH( QString, result ); + + QCOMPARE( makeHostnameSuggestion( templateString, fullName, login ), result ); +} + + void UserTests::testPasswordChecks() { From 9ca6d3c71532b34af6d12ace074b1a58847ff90b Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Mon, 11 Apr 2022 14:26:29 +0200 Subject: [PATCH 16/19] [users] Document new template setting for hostname --- CHANGES-3.2 | 5 +++++ src/modules/users/users.conf | 22 ++++++++++++++++++++-- src/modules/users/users.schema.yaml | 1 + 3 files changed, 26 insertions(+), 2 deletions(-) diff --git a/CHANGES-3.2 b/CHANGES-3.2 index 578590134..a1387d99c 100644 --- a/CHANGES-3.2 +++ b/CHANGES-3.2 @@ -23,6 +23,11 @@ This release contains contributions from (alphabetically by first name): - *users* module has a new hostname.location setting, *Transient*, which will force the installed system to transient-hostname-setting by removing the file `/etc/hostname`. + - *users* module has a new hostname.template setting, which allows some + tweaking of how the hostname suggestion is constructed. In particular, + it can be configured to use the current hostname (whatever that may be). + See the example `users.conf` for details on available keys. + # 3.2.54 (2022-03-21) # diff --git a/src/modules/users/users.conf b/src/modules/users/users.conf index c5cc8d2af..60284b74e 100644 --- a/src/modules/users/users.conf +++ b/src/modules/users/users.conf @@ -156,7 +156,7 @@ userShell: /bin/bash # a default / suggestion) and where (or how) the hostname is set in # the target system. # -# Key *hostname* has the following sub-keys (just one): +# Key *hostname* has the following sub-keys: # # - *location* How the hostname is set in the target system: # - *None*, to not set the hostname at all @@ -164,14 +164,32 @@ userShell: /bin/bash # - *Etc*, identical to above # - *Hostnamed*, to use systemd hostnamed(1) over DBus # - *Transient*, to remove `/etc/hostname` from the target -# The default is *EtcFile*. Setting this to *None* will +# The default is *EtcFile*. Setting this to *None* or *Transient* will # hide the hostname field. # - *writeHostsFile* Should /etc/hosts be written with a hostname for # this machine (also adds localhost and some ipv6 standard entries). # Defaults to *true*. +# - *template* Is a simple template for making a suggestion for the +# hostname, based on user data. The default is "${first}-${product}". +# This is used only if the hostname field is shown. KMacroExpander is +# used; write `${key}` where `key` is one of the following: +# - *first* User's first name (whatever is first in the User Name field, +# which is first-in-order but not necessarily a "first name" as in +# "given name" or "name by which you call someone"; beware of western bias) +# - *name* All the text in the User Name field. +# - *login* The login name (which may be suggested based on User Name) +# - *product* The hardware product, based on DMI data +# - *product2* The product as described by Qt +# - *cpu* CPU name +# - *host* Current hostname (which may be a transient hostname) +# Literal text in the template is preserved. Calamares tries to map +# `${key}` values to something that will fit in a hostname, but does not +# apply the same to literal text in the template. Do not use invalid +# characters in the literal text, or no suggeston will be done. hostname: location: EtcFile writeHostsFile: true + template: "derp-${cpu}" # TODO:3.3: Remove this setting # diff --git a/src/modules/users/users.schema.yaml b/src/modules/users/users.schema.yaml index c7088253a..fe45d5fb2 100644 --- a/src/modules/users/users.schema.yaml +++ b/src/modules/users/users.schema.yaml @@ -46,6 +46,7 @@ properties: properties: location: { type: string, enum: [ None, EtcFile, Hostnamed, Transient ] } writeHostsFile: { type: boolean, default: true } + template: { type: string, default: "${first}-${product}" } # Legacy Hostname setting setHostname: { type: string, enum: [ None, EtcFile, Hostnamed ] } writeHostsFile: { type: boolean, default: true } From c28ba5ffb2047c3107ecbb036e23fe8b1584649d Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Mon, 11 Apr 2022 14:51:52 +0200 Subject: [PATCH 17/19] [users] Fix up test to match new strings written to /etc/hosts --- src/modules/users/TestSetHostNameJob.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/modules/users/TestSetHostNameJob.cpp b/src/modules/users/TestSetHostNameJob.cpp index 84602a053..4a22762d5 100644 --- a/src/modules/users/TestSetHostNameJob.cpp +++ b/src/modules/users/TestSetHostNameJob.cpp @@ -113,11 +113,12 @@ UsersTests::testEtcHosts() QVERIFY( QFile::exists( m_dir.path() ) ); QVERIFY( QFile::exists( m_dir.filePath( "etc" ) ) ); - QVERIFY( writeFileEtcHosts( QStringLiteral( "tubophone.calamares.io" ) ) ); + const QString testHostname = QStringLiteral( "tubophone.calamares.io" ); + QVERIFY( writeFileEtcHosts( testHostname ) ); QVERIFY( QFile::exists( m_dir.filePath( "etc/hosts" ) ) ); // The skeleton contains %1 which has the hostname substituted in, so we lose two, - // and the rest of the blabla is 150 (according to Python) - QCOMPARE( QFileInfo( m_dir.filePath( "etc/hosts" ) ).size(), 150 + 22 - 2 ); + // and the rest of the blabla is 145 (the "standard" part) and 34 (the "for this host" part) + QCOMPARE( QFileInfo( m_dir.filePath( "etc/hosts" ) ).size(), 145 + 34 + testHostname.length() - 2 ); } void From dad12a0e023ae0af659892faed0c1132b4b1d272 Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Mon, 11 Apr 2022 14:55:05 +0200 Subject: [PATCH 18/19] [users] Simplify test, fewer magic numbers --- src/modules/users/TestSetHostNameJob.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/modules/users/TestSetHostNameJob.cpp b/src/modules/users/TestSetHostNameJob.cpp index 4a22762d5..d1a556824 100644 --- a/src/modules/users/TestSetHostNameJob.cpp +++ b/src/modules/users/TestSetHostNameJob.cpp @@ -92,18 +92,19 @@ UsersTests::testEtcHostname() QVERIFY( QFile::exists( m_dir.path() ) ); QVERIFY( !QFile::exists( m_dir.filePath( "etc" ) ) ); + const QString testHostname = QStringLiteral( "tubophone.calamares.io" ); // Doesn't create intermediate directories - QVERIFY( !setFileHostname( QStringLiteral( "tubophone.calamares.io" ) ) ); + QVERIFY( !setFileHostname( testHostname ) ); QVERIFY( CalamaresUtils::System::instance()->createTargetDirs( "/etc" ) ); QVERIFY( QFile::exists( m_dir.filePath( "etc" ) ) ); // Does write the file - QVERIFY( setFileHostname( QStringLiteral( "tubophone.calamares.io" ) ) ); + QVERIFY( setFileHostname( testHostname ) ); QVERIFY( QFile::exists( m_dir.filePath( "etc/hostname" ) ) ); // 22 for the test string, above, and 1 for the newline - QCOMPARE( QFileInfo( m_dir.filePath( "etc/hostname" ) ).size(), 22 + 1 ); + QCOMPARE( QFileInfo( m_dir.filePath( "etc/hostname" ) ).size(), testHostname.length() + 1 ); } void From 3b02115f8e3cb8654ebaad69b6e228aead0d62b6 Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Mon, 11 Apr 2022 15:03:48 +0200 Subject: [PATCH 19/19] [users] Expand test to demo other template-keys --- src/modules/users/Tests.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/modules/users/Tests.cpp b/src/modules/users/Tests.cpp index 9c410cb1f..9a4005f21 100644 --- a/src/modules/users/Tests.cpp +++ b/src/modules/users/Tests.cpp @@ -297,6 +297,13 @@ UserTests::testHostSuggestions_data() << QStringLiteral( "chuck-pc" ); // Avoid ${product} because it's DMI-based QTest::newRow( "full " ) << QStringLiteral( "${name}" ) << QStringLiteral( "chuckyeager" ); QTest::newRow( "login+ " ) << QStringLiteral( "${login}-${first}" ) << QStringLiteral( "bill-chuck" ); + // This is a bit dodgy: assumes CPU architecture of the testing host + QTest::newRow( " cpu " ) << QStringLiteral( "${cpu}X" ) << QStringLiteral( "x8664X" ); // Assume we don't test on non-amd64 + // These have X X in the template to indicate that they are bogus. Mostly we want + // to see what the template engine does for these. + QTest::newRow( "@prod " ) << QStringLiteral( "X${product}X" ) << QString(); + QTest::newRow( "@prod2 " ) << QStringLiteral( "X${product2}X" ) << QString(); + QTest::newRow( "@host " ) << QStringLiteral( "X${host}X" ) << QString(); } void @@ -308,6 +315,11 @@ UserTests::testHostSuggestions() QFETCH( QString, templateString ); QFETCH( QString, result ); + if ( templateString.startsWith('X') && templateString.endsWith('X')) + { + QEXPECT_FAIL( "", "Test is too host-specific", Continue ); + cWarning() << Logger::SubEntry << "Next test" << templateString << "->" << makeHostnameSuggestion( templateString, fullName, login ); + } QCOMPARE( makeHostnameSuggestion( templateString, fullName, login ), result ); }