commit
9c58f49c49
11
CHANGES-3.2
11
CHANGES-3.2
@ -17,7 +17,16 @@ 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.
|
||||
- *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) #
|
||||
|
@ -20,6 +20,8 @@
|
||||
#include "utils/String.h"
|
||||
#include "utils/Variant.h"
|
||||
|
||||
#include <KMacroExpander>
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QFile>
|
||||
#include <QMetaProperty>
|
||||
@ -77,14 +79,16 @@ updateGSAutoLogin( bool doAutoLogin, const QString& login )
|
||||
}
|
||||
|
||||
const NamedEnumTable< HostNameAction >&
|
||||
hostNameActionNames()
|
||||
hostnameActionNames()
|
||||
{
|
||||
// *INDENT-OFF*
|
||||
// clang-format off
|
||||
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*
|
||||
@ -98,7 +102,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 );
|
||||
@ -240,10 +244,15 @@ Config::loginNameStatus() const
|
||||
void
|
||||
Config::setHostName( const QString& host )
|
||||
{
|
||||
if ( host != m_hostName )
|
||||
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();
|
||||
m_hostName = host;
|
||||
m_hostname = host;
|
||||
Calamares::GlobalStorage* gs = Calamares::JobQueue::instance()->globalStorage();
|
||||
if ( host.isEmpty() )
|
||||
{
|
||||
@ -253,8 +262,8 @@ Config::setHostName( const QString& host )
|
||||
{
|
||||
gs->insert( "hostname", host );
|
||||
}
|
||||
emit hostNameChanged( host );
|
||||
emit hostNameStatusChanged( hostNameStatus() );
|
||||
emit hostnameChanged( host );
|
||||
emit hostnameStatusChanged( hostnameStatus() );
|
||||
}
|
||||
}
|
||||
|
||||
@ -266,31 +275,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." );
|
||||
}
|
||||
@ -298,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
|
||||
*
|
||||
@ -312,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() )
|
||||
{
|
||||
@ -379,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();
|
||||
}
|
||||
|
||||
@ -420,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 );
|
||||
@ -441,8 +471,8 @@ Config::setFullName( const QString& name )
|
||||
}
|
||||
if ( !m_customHostName )
|
||||
{
|
||||
QString hostname = makeHostnameSuggestion( cleanParts );
|
||||
if ( !hostname.isEmpty() && hostname != m_hostName )
|
||||
const QString hostname = makeHostnameSuggestion( m_hostnameTemplate, cleanParts, loginName() );
|
||||
if ( !hostname.isEmpty() && hostname != m_hostname )
|
||||
{
|
||||
setHostName( hostname );
|
||||
// Still not custom
|
||||
@ -653,7 +683,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;
|
||||
@ -734,25 +764,22 @@ 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, "setHostname" );
|
||||
QString hostnameActionString = CalamaresUtils::getString( configurationMap, "location" );
|
||||
if ( !hostnameActionString.isEmpty() )
|
||||
{
|
||||
bool ok = false;
|
||||
setHostName = hostNameActionNames().find( hostnameActionString, ok );
|
||||
setHostName = hostnameActionNames().find( hostnameActionString, ok );
|
||||
if ( !ok )
|
||||
{
|
||||
setHostName = HostNameAction::EtcHostname; // Rather than none
|
||||
}
|
||||
}
|
||||
|
||||
HostNameAction writeHosts = CalamaresUtils::getBool( configurationMap, "writeHostsFile", true )
|
||||
? HostNameAction::WriteEtcHosts
|
||||
: HostNameAction::None;
|
||||
return setHostName | writeHosts;
|
||||
return setHostName;
|
||||
}
|
||||
|
||||
/** @brief Process entries in the passwordRequirements config entry
|
||||
@ -826,6 +853,25 @@ 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 )
|
||||
{
|
||||
if ( source.contains( sourceKey ) )
|
||||
{
|
||||
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 );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
Config::setConfigurationMap( const QVariantMap& configurationMap )
|
||||
{
|
||||
@ -844,7 +890,19 @@ 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 );
|
||||
|
||||
// TODO:3.3: Remove calls to copyLegacy
|
||||
copyLegacy( configurationMap, "setHostname", hostnameSettings, "location" );
|
||||
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 );
|
||||
|
||||
@ -923,7 +981,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;
|
||||
|
@ -20,17 +20,15 @@
|
||||
#include <QObject>
|
||||
#include <QVariantMap>
|
||||
|
||||
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)
|
||||
Transient, // Force target system transient, remove /etc/hostname
|
||||
};
|
||||
Q_DECLARE_FLAGS( HostNameActions, HostNameAction )
|
||||
Q_DECLARE_OPERATORS_FOR_FLAGS( HostNameActions )
|
||||
|
||||
const NamedEnumTable< HostNameAction >& hostNameActionNames();
|
||||
const NamedEnumTable< HostNameAction >& hostnameActionNames();
|
||||
|
||||
/** @brief Settings for a single group
|
||||
*
|
||||
@ -101,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( HostNameActions hostNameActions READ hostNameActions 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
|
||||
@ -204,11 +202,19 @@ 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;
|
||||
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; }
|
||||
@ -293,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 );
|
||||
@ -317,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
|
||||
@ -337,7 +343,10 @@ private:
|
||||
|
||||
bool m_isReady = false; ///< Used to reduce readyChanged signals
|
||||
|
||||
HostNameActions m_hostNameActions;
|
||||
HostNameAction m_hostnameAction = HostNameAction::EtcHostname;
|
||||
bool m_writeEtcHosts = false;
|
||||
QString m_hostnameTemplate;
|
||||
|
||||
PasswordCheckList m_passwordChecks;
|
||||
};
|
||||
|
||||
|
@ -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
|
||||
|
@ -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 <strong>%1</strong>." ).arg( m_hostname );
|
||||
return tr( "Set hostname <strong>%1</strong>." ).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
|
||||
@ -62,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
|
||||
@ -129,29 +131,35 @@ 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;
|
||||
case HostNameAction::Transient:
|
||||
CalamaresUtils::System::instance()->removeTargetFile( QStringLiteral( "/etc/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();
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
@ -113,11 +114,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
|
||||
|
@ -17,8 +17,10 @@
|
||||
|
||||
// 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 );
|
||||
extern QString
|
||||
makeHostnameSuggestion( const QString& templateString, const QStringList& fullNameParts, const QString& loginName );
|
||||
|
||||
/** @brief Test Config object methods and internals
|
||||
*
|
||||
@ -42,6 +44,10 @@ private Q_SLOTS:
|
||||
|
||||
void testHostActions_data();
|
||||
void testHostActions();
|
||||
void testHostActions2();
|
||||
void testHostSuggestions_data();
|
||||
void testHostSuggestions();
|
||||
|
||||
void testPasswordChecks();
|
||||
void testUserPassword();
|
||||
|
||||
@ -228,6 +234,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
|
||||
@ -240,16 +255,75 @@ 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
|
||||
// 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
|
||||
UserTests::testHostActions2()
|
||||
{
|
||||
Config c;
|
||||
QVariantMap legacy;
|
||||
|
||||
// Test defaults
|
||||
c.setConfigurationMap( legacy );
|
||||
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.writeEtcHosts(), false );
|
||||
}
|
||||
|
||||
|
||||
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" );
|
||||
// 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
|
||||
UserTests::testHostSuggestions()
|
||||
{
|
||||
const QStringList fullName { "Chuck", "Yeager" };
|
||||
const QString login { "bill" };
|
||||
|
||||
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 );
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
UserTests::testPasswordChecks()
|
||||
{
|
||||
|
@ -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->hostnameAction() == HostNameAction::EtcHostname )
|
||||
|| ( m_config->hostnameAction() == 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 );
|
||||
@ -155,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" ) );
|
||||
@ -218,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
|
||||
|
@ -42,7 +42,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<layout class="QHBoxLayout" name="fullNameLayout">
|
||||
<item>
|
||||
<widget class="QLineEdit" name="textBoxFullName">
|
||||
<property name="minimumSize">
|
||||
@ -129,7 +129,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<layout class="QHBoxLayout" name="usernameLayout">
|
||||
<item>
|
||||
<widget class="QLineEdit" name="textBoxLoginName">
|
||||
<property name="sizePolicy">
|
||||
@ -218,7 +218,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="hostname_label_2">
|
||||
<widget class="QLabel" name="hostnameLabel">
|
||||
<property name="text">
|
||||
<string>What is the name of this computer?</string>
|
||||
</property>
|
||||
@ -228,9 +228,9 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_4">
|
||||
<layout class="QHBoxLayout" name="hostnameLayout">
|
||||
<item>
|
||||
<widget class="QLineEdit" name="textBoxHostName">
|
||||
<widget class="QLineEdit" name="textBoxHostname">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
@ -304,7 +304,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer_3">
|
||||
<spacer name="hostnameVSpace">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
@ -330,7 +330,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_3">
|
||||
<layout class="QHBoxLayout" name="userPasswordLayout">
|
||||
<item>
|
||||
<widget class="QLineEdit" name="textBoxUserPassword">
|
||||
<property name="sizePolicy">
|
||||
@ -500,7 +500,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_5">
|
||||
<layout class="QHBoxLayout" name="rootPasswordLayout">
|
||||
<item>
|
||||
<widget class="QLineEdit" name="textBoxRootPassword">
|
||||
<property name="sizePolicy">
|
||||
|
@ -149,19 +149,57 @@ 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:
|
||||
#
|
||||
# - *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
|
||||
# The default is *EtcFile*.
|
||||
# - *Transient*, to remove `/etc/hostname` from the target
|
||||
# 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
|
||||
#
|
||||
# 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*.
|
||||
# TODO:3.3: Remove this setting
|
||||
#
|
||||
# This is a legacy setting for hostname.writeHostsFile
|
||||
writeHostsFile: true
|
||||
|
||||
presets:
|
||||
|
@ -40,7 +40,14 @@ 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, 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 }
|
||||
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user