Merge branch 'better-hostname'

This commit is contained in:
Adriaan de Groot 2020-02-17 17:10:21 +01:00
commit 371fe267b1
14 changed files with 368 additions and 123 deletions

View File

@ -293,19 +293,19 @@ System::targetPath( const QString& path ) const
}
}
QString
CreationResult
System::createTargetFile( const QString& path, const QByteArray& contents ) const
{
QString completePath = targetPath( path );
if ( completePath.isEmpty() )
{
return QString();
return CreationResult( CreationResult::Code::Invalid );
}
QFile f( completePath );
if ( f.exists() )
{
return QString();
return CreationResult( CreationResult::Code::AlreadyExists );
}
QIODevice::OpenMode m =
@ -317,18 +317,18 @@ System::createTargetFile( const QString& path, const QByteArray& contents ) cons
if ( !f.open( m ) )
{
return QString();
return CreationResult( CreationResult::Code::Failed );
}
if ( f.write( contents ) != contents.size() )
{
f.close();
f.remove();
return QString();
return CreationResult( CreationResult::Code::Failed );
}
f.close();
return QFileInfo( f ).canonicalFilePath();
return CreationResult( QFileInfo( f ).canonicalFilePath() );
}
void

View File

@ -84,6 +84,41 @@ public:
}
};
/** @brief The result of a create*() action, for status
*
* A CreationResult has a status field, can be converted to bool
* (true only on success) and can report the full pathname of
* the thing created if it was successful.
*/
class CreationResult : public QPair< int, QString >
{
public:
enum class Code : int
{
// These are "not failed", but only OK is a success
OK = 0,
AlreadyExists = 1,
// These are "failed"
Invalid = -1,
Failed = -2
};
CreationResult( Code r )
: QPair< int, QString >( static_cast< int >( r ), QString() )
{
}
explicit CreationResult( const QString& path )
: QPair< int, QString >( 0, path )
{
}
Code code() const { return static_cast< Code >( first ); }
QString path() const { return second; }
bool failed() const { return first < 0; }
operator bool() const { return first == 0; }
};
/**
* @brief The System class is a singleton with utility functions that perform
* system-specific operations.
@ -244,7 +279,7 @@ public:
* root of the host system, or empty on failure. (Here, it is
* possible to be canonical because the file exists).
*/
DLLEXPORT QString createTargetFile( const QString& path, const QByteArray& contents ) const;
DLLEXPORT CreationResult createTargetFile( const QString& path, const QByteArray& contents ) const;
/** @brief Remove a file from the target system.
*

View File

@ -46,6 +46,7 @@ private Q_SLOTS:
void init();
void cleanupTestCase();
void testCreationResult();
void testTargetPath();
void testCreateTarget();
void testCreateTargetBasedirs();
@ -95,6 +96,42 @@ TestPaths::init()
m_gs->insert( "rootMountPoint", "/tmp" );
}
void TestPaths::testCreationResult()
{
using Code = CalamaresUtils::CreationResult::Code;
for( auto c : { Code::OK, Code::AlreadyExists, Code::Failed, Code::Invalid } )
{
auto r = CalamaresUtils::CreationResult( c );
QVERIFY( r.path().isEmpty() );
QCOMPARE( r.path(), QString() );
// Get a warning from Clang if we're not covering everything
switch( r.code() )
{
case Code::OK:
QVERIFY( !r.failed() );
QVERIFY( r );
break;
case Code::AlreadyExists:
QVERIFY( !r.failed() );
QVERIFY( !r );
break;
case Code::Failed:
case Code::Invalid:
QVERIFY( r.failed() );
QVERIFY( !r );
break;
}
}
QString path( "/etc/os-release" );
auto r = CalamaresUtils::CreationResult( path );
QVERIFY( !r.failed() );
QVERIFY( r );
QCOMPARE( r.code(), Code::OK );
QCOMPARE( r.path(), path );
}
void
TestPaths::testTargetPath()
@ -117,7 +154,10 @@ TestPaths::testTargetPath()
void
TestPaths::testCreateTarget()
{
QCOMPARE( m_system->createTargetFile( testFile, "Hello" ), QString( absFile ) ); // Success
auto r = m_system->createTargetFile( testFile, "Hello" );
QVERIFY( !r.failed() );
QVERIFY( r );
QCOMPARE( r.path(), QString( absFile ) ); // Success
QFileInfo fi( absFile );
QVERIFY( fi.exists() );

View File

@ -54,7 +54,7 @@ InitramfsJob::exec()
// First make sure we generate a safe initramfs with suitable permissions.
static const char confFile[] = "/etc/initramfs-tools/conf.d/calamares-safe-initramfs.conf";
static const char contents[] = "UMASK=0077\n";
if ( CalamaresUtils::System::instance()->createTargetFile( confFile, QByteArray( contents ) ).isEmpty() )
if ( CalamaresUtils::System::instance()->createTargetFile( confFile, QByteArray( contents ) ).failed() )
{
cWarning() << Logger::SubEntry << "Could not configure safe UMASK for initramfs.";
// But continue anyway.

View File

@ -59,7 +59,10 @@ void InitramfsTests::testCreateHostFile()
{
CalamaresUtils::System s( false ); // don't chroot
QString path = s.createTargetFile( confFile, QByteArray( contents ) );
auto r = s.createTargetFile( confFile, QByteArray( contents ) );
QVERIFY( !r.failed() );
QVERIFY( r );
QString path = r.path();
QVERIFY( !path.isEmpty() );
QCOMPARE( path, confFile ); // don't chroot, so path create relative to /
QVERIFY( QFile::exists( confFile ) );
@ -76,7 +79,10 @@ void InitramfsTests::testCreateTargetFile()
static const char short_confFile[] = "/calamares-safe-umask";
CalamaresUtils::System s( true );
QString path = s.createTargetFile( short_confFile, QByteArray( contents ) );
auto r = s.createTargetFile( short_confFile, QByteArray( contents ) );
QVERIFY( r.failed() );
QVERIFY( !r );
QString path = r.path();
QVERIFY( path.isEmpty() ); // because no rootmountpoint is set
Calamares::JobQueue j;

View File

@ -125,8 +125,13 @@ MachineIdTests::testJob()
gs->insert( "rootMountPoint", "/tmp" );
// Prepare part of the target filesystem
{
QVERIFY( system->createTargetDirs("/etc") );
QVERIFY( !(system->createTargetFile( "/etc/machine-id", "Hello" ).isEmpty() ) );
auto r = system->createTargetFile( "/etc/machine-id", "Hello" );
QVERIFY( !r.failed() );
QVERIFY( r );
QVERIFY( !r.path().isEmpty() );
}
MachineIdJob job( nullptr );
QVERIFY( !job.prettyName().isEmpty() );

View File

@ -1,4 +1,4 @@
find_package( Qt5 COMPONENTS Core REQUIRED )
find_package( Qt5 ${QT_VERSION} CONFIG REQUIRED Core DBus Network )
find_package( Crypt REQUIRED )
# Add optional libraries here
@ -36,6 +36,7 @@ calamares_add_plugin( users
calamaresui
${CRYPT_LIBRARIES}
${USER_EXTRA_LIB}
Qt5::DBus
SHARED_LIB
)

View File

@ -22,14 +22,19 @@
#include "GlobalStorage.h"
#include "JobQueue.h"
#include "utils/CalamaresUtilsSystem.h"
#include "utils/Logger.h"
#include <QDir>
#include <QFile>
#include <QtDBus/QDBusConnection>
#include <QtDBus/QDBusInterface>
#include <QtDBus/QDBusReply>
SetHostNameJob::SetHostNameJob( const QString& hostname )
SetHostNameJob::SetHostNameJob( const QString& hostname, Actions a )
: Calamares::Job()
, m_hostname( hostname )
, m_actions( a )
{
}
@ -53,6 +58,58 @@ SetHostNameJob::prettyStatusMessage() const
return tr( "Setting hostname %1." ).arg( m_hostname );
}
static bool
setFileHostname( const QString& hostname )
{
return !( CalamaresUtils::System::instance()
->createTargetFile( QStringLiteral( "/etc/hostname" ), ( hostname + '\n' ).toUtf8() )
.failed() );
}
static bool
writeFileEtcHosts( const QString& hostname )
{
// The actual hostname gets substituted in at %1
static const char etc_hosts[] = R"(# 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
)";
return !( CalamaresUtils::System::instance()
->createTargetFile( QStringLiteral( "/etc/hosts" ), QString( etc_hosts ).arg( hostname ).toUtf8() )
.failed() );
}
static void
setSystemdHostname( const QString& hostname )
{
QDBusInterface hostnamed( "org.freedesktop.hostname1",
"/org/freedesktop/hostname1",
"org.freedesktop.hostname1",
QDBusConnection::systemBus() );
// Static, writes /etc/hostname
{
QDBusReply< uint > r = hostnamed.call( "SetStaticHostname", hostname, false );
if ( !r.isValid() )
{
cWarning() << "Could not set hostname through org.freedesktop.hostname1.SetStaticHostname." << r.error();
}
}
// Dynamic, updates kernel
{
QDBusReply< uint > r = hostnamed.call( "SetHostname", hostname, false );
if ( !r.isValid() )
{
cWarning() << "Could not set hostname through org.freedesktop.hostname1.SetHostname." << r.error();
}
}
}
Calamares::JobResult
SetHostNameJob::exec()
{
@ -71,43 +128,29 @@ SetHostNameJob::exec()
return Calamares::JobResult::error( tr( "Internal Error" ) );
}
QFile hostfile( destDir + "/etc/hostname" );
if ( !hostfile.open( QFile::WriteOnly ) )
if ( m_actions & Action::EtcHostname )
{
if ( !setFileHostname( m_hostname ) )
{
cError() << "Can't write to hostname file";
return Calamares::JobResult::error( tr( "Cannot write hostname to target system" ) );
}
}
QTextStream hostfileout( &hostfile );
hostfileout << m_hostname << "\n";
hostfile.close();
QFile hostsfile( destDir + "/etc/hosts" );
if ( !hostsfile.open( QFile::WriteOnly ) )
if ( m_actions & Action::WriteEtcHosts )
{
if ( !writeFileEtcHosts( m_hostname ) )
{
cError() << "Can't write to hosts file";
return Calamares::JobResult::error( tr( "Cannot write hostname to target system" ) );
}
}
// We also need to write the appropriate entries for /etc/hosts
QTextStream hostsfileout( &hostsfile );
// ipv4 support
hostsfileout << "127.0.0.1"
<< "\t"
<< "localhost"
<< "\n";
hostsfileout << "127.0.1.1"
<< "\t" << m_hostname << "\n";
// ipv6 support
hostsfileout << "::1"
<< "\t"
<< "localhost ip6-localhost ip6-loopback"
<< "\n";
hostsfileout << "ff02::1 ip6-allnodes"
<< "\n"
<< "ff02::2 ip6-allrouters"
<< "\n";
hostsfile.close();
if ( m_actions & Action::SystemdHostname )
{
// Does its own logging
setSystemdHostname( m_hostname );
}
return Calamares::JobResult::ok();
}

View File

@ -26,7 +26,17 @@ class SetHostNameJob : public Calamares::Job
{
Q_OBJECT
public:
SetHostNameJob( const QString& hostname );
enum Action
{
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)
};
Q_DECLARE_FLAGS( Actions, Action )
SetHostNameJob( const QString& hostname, Actions a );
QString prettyName() const override;
QString prettyDescription() const override;
QString prettyStatusMessage() const override;
@ -34,7 +44,9 @@ public:
private:
const QString m_hostname;
const Actions m_actions;
};
Q_DECLARE_OPERATORS_FOR_FLAGS( SetHostNameJob::Actions )
#endif // SETHOSTNAMEJOB_CPP_H

View File

@ -3,6 +3,7 @@
* Copyright 2014-2017, Teo Mrnjavac <teo@kde.org>
* Copyright 2017-2018, Adriaan de Groot <groot@kde.org>
* Copyright 2019, Collabora Ltd <arnaud.ferraris@collabora.com>
* Copyright 2020, Gabriel Craciunescu <crazy@frugalware.org>
*
* Portions from the Manjaro Installation Framework
* by Roland Singer <roland@manjaro.org>
@ -40,6 +41,7 @@
#include "utils/String.h"
#include <QBoxLayout>
#include <QFile>
#include <QLabel>
#include <QLineEdit>
#include <QRegExp>
@ -97,14 +99,12 @@ UsersPage::UsersPage( QWidget* parent )
connect( ui->textBoxUserVerifiedPassword, &QLineEdit::textChanged, this, &UsersPage::onPasswordTextChanged );
connect( ui->textBoxRootPassword, &QLineEdit::textChanged, this, &UsersPage::onRootPasswordTextChanged );
connect( ui->textBoxVerifiedRootPassword, &QLineEdit::textChanged, this, &UsersPage::onRootPasswordTextChanged );
connect( ui->checkBoxValidatePassword, &QCheckBox::stateChanged, this, [this]( int )
{
connect( ui->checkBoxValidatePassword, &QCheckBox::stateChanged, this, [this]( int ) {
onPasswordTextChanged( ui->textBoxUserPassword->text() );
onRootPasswordTextChanged( ui->textBoxRootPassword->text() );
checkReady( isReady() );
} );
connect( ui->checkBoxReusePassword, &QCheckBox::stateChanged, this, [this]( int checked )
{
connect( ui->checkBoxReusePassword, &QCheckBox::stateChanged, this, [this]( int checked ) {
ui->labelChooseRootPassword->setVisible( !checked );
ui->labelRootPassword->setVisible( !checked );
ui->labelRootPasswordError->setVisible( !checked );
@ -166,6 +166,37 @@ UsersPage::isReady()
return readyFields && m_readyRootPassword;
}
QString
UsersPage::getHostname() const
{
return ui->textBoxHostname->text();
}
QString
UsersPage::getRootPassword() const
{
if ( m_writeRootPassword )
{
if ( ui->checkBoxReusePassword->isChecked() )
{
return ui->textBoxUserPassword->text();
}
else
{
return ui->textBoxRootPassword->text();
}
}
else
{
return QString();
}
}
QPair< QString, QString >
UsersPage::getUserPassword() const
{
return QPair< QString, QString >( ui->textBoxUsername->text(), ui->textBoxUserPassword->text() );
}
QList< Calamares::job_ptr >
UsersPage::createJobs( const QStringList& defaultGroupsList )
@ -186,32 +217,10 @@ UsersPage::createJobs( const QStringList& defaultGroupsList )
defaultGroupsList );
list.append( Calamares::job_ptr( j ) );
j = new SetPasswordJob( ui->textBoxUsername->text(), ui->textBoxUserPassword->text() );
list.append( Calamares::job_ptr( j ) );
if ( m_writeRootPassword )
{
gs->insert( "reuseRootPassword", ui->checkBoxReusePassword->isChecked() );
if ( ui->checkBoxReusePassword->isChecked() )
{
j = new SetPasswordJob( "root", ui->textBoxUserPassword->text() );
}
else
{
j = new SetPasswordJob( "root", ui->textBoxRootPassword->text() );
}
list.append( Calamares::job_ptr( j ) );
}
else
{
j = new SetPasswordJob( "root",
"" ); //explicitly disable root password
list.append( Calamares::job_ptr( j ) );
}
j = new SetHostNameJob( ui->textBoxHostname->text() );
list.append( Calamares::job_ptr( j ) );
gs->insert( "hostname", ui->textBoxHostname->text() );
if ( ui->checkBoxAutoLogin->isChecked() )
{
@ -269,6 +278,38 @@ UsersPage::onFullNameTextEdited( const QString& textRef )
checkReady( isReady() );
}
/** @brief Guess the machine's name
*
* If there is DMI data, use that; otherwise, just call the machine "-pc".
* Reads the DMI data just once.
*/
static QString
guessProductName()
{
static bool tried = false;
static QString dmiProduct;
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( ' ' );
}
if ( dmiProduct.isEmpty() )
{
dmiProduct = QStringLiteral( "-pc" );
}
tried = true;
}
return dmiProduct;
}
void
UsersPage::fillSuggestions()
@ -303,7 +344,9 @@ UsersPage::fillSuggestions()
{
if ( !cleanParts.isEmpty() && !cleanParts.first().isEmpty() )
{
QString hostnameSuggestion = QString( "%1-pc" ).arg( cleanParts.first() );
QString hostnameSuggestion;
QString productName = guessProductName();
hostnameSuggestion = QString( "%1-%2" ).arg( cleanParts.first() ).arg( productName );
if ( HOSTNAME_RX.indexIn( hostnameSuggestion ) != -1 )
{
ui->textBoxHostname->setText( hostnameSuggestion );
@ -524,8 +567,8 @@ UsersPage::addPasswordCheck( const QString& key, const QVariant& value )
{
if ( value.toBool() )
{
m_passwordChecks.push_back( PasswordCheck(
[]() { return QCoreApplication::translate( "PWQ", "Password is empty" ); },
m_passwordChecks.push_back(
PasswordCheck( []() { return QCoreApplication::translate( "PWQ", "Password is empty" ); },
[]( const QString& s ) { return !s.isEmpty(); },
PasswordCheck::Weight( 1 ) ) );
}

View File

@ -63,6 +63,13 @@ public:
*/
void addPasswordCheck( const QString& key, const QVariant& value );
///@brief Hostname as entered / auto-filled
QString getHostname() const;
///@brief Root password, depends on settings, may be empty
QString getRootPassword() const;
///@brief User name and password
QPair< QString, QString > getUserPassword() const;
protected slots:
void onFullNameTextEdited( const QString& );
void fillSuggestions();

View File

@ -20,10 +20,12 @@
#include "UsersViewStep.h"
#include "SetHostNameJob.h"
#include "SetPasswordJob.h"
#include "UsersPage.h"
// #include "utils/CalamaresUtils.h"
#include "utils/Logger.h"
#include "utils/NamedEnum.h"
#include "utils/Variant.h"
#include "GlobalStorage.h"
@ -31,9 +33,28 @@
CALAMARES_PLUGIN_FACTORY_DEFINITION( UsersViewStepFactory, registerPlugin< UsersViewStep >(); )
static const NamedEnumTable< SetHostNameJob::Action >&
hostnameActions()
{
using Action = SetHostNameJob::Action;
// *INDENT-OFF*
// clang-format off
static const NamedEnumTable< Action > names {
{ QStringLiteral( "none" ), Action::None },
{ QStringLiteral( "etcfile" ), Action::EtcHostname },
{ QStringLiteral( "hostnamed" ), Action::SystemdHostname }
};
// clang-format on
// *INDENT-ON*
return names;
}
UsersViewStep::UsersViewStep( QObject* parent )
: Calamares::ViewStep( parent )
, m_widget( new UsersPage() )
, m_actions( SetHostNameJob::Action::None )
{
emit nextStatusChanged( true );
connect( m_widget, &UsersPage::checkReady, this, &UsersViewStep::nextStatusChanged );
@ -109,14 +130,27 @@ void
UsersViewStep::onLeave()
{
m_jobs.clear();
m_jobs.append( m_widget->createJobs( m_defaultGroups ) );
Calamares::Job* j;
auto userPW = m_widget->getUserPassword();
j = new SetPasswordJob( userPW.first, userPW.second );
m_jobs.append( Calamares::job_ptr( j ) );
j = new SetPasswordJob( "root", m_widget->getRootPassword() );
m_jobs.append( Calamares::job_ptr( j ) );
j = new SetHostNameJob( m_widget->getHostname(), m_actions );
m_jobs.append( Calamares::job_ptr( j ) );
}
void
UsersViewStep::setConfigurationMap( const QVariantMap& configurationMap )
{
using CalamaresUtils::getBool;
if ( configurationMap.contains( "defaultGroups" )
&& configurationMap.value( "defaultGroups" ).type() == QVariant::List )
{
@ -142,25 +176,12 @@ UsersViewStep::setConfigurationMap( const QVariantMap& configurationMap )
configurationMap.value( "sudoersGroup" ).toString() );
}
if ( configurationMap.contains( "setRootPassword" )
&& configurationMap.value( "setRootPassword" ).type() == QVariant::Bool )
{
Calamares::JobQueue::instance()->globalStorage()->insert(
"setRootPassword", configurationMap.value( "setRootPassword" ).toBool() );
m_widget->setWriteRootPassword( configurationMap.value( "setRootPassword" ).toBool() );
}
bool setRootPassword = getBool( configurationMap, "setRootPassword", true );
Calamares::JobQueue::instance()->globalStorage()->insert( "setRootPassword", setRootPassword );
if ( configurationMap.contains( "doAutologin" )
&& configurationMap.value( "doAutologin" ).type() == QVariant::Bool )
{
m_widget->setAutologinDefault( configurationMap.value( "doAutologin" ).toBool() );
}
if ( configurationMap.contains( "doReusePassword" )
&& configurationMap.value( "doReusePassword" ).type() == QVariant::Bool )
{
m_widget->setReusePasswordDefault( configurationMap.value( "doReusePassword" ).toBool() );
}
m_widget->setWriteRootPassword( setRootPassword );
m_widget->setAutologinDefault( getBool( configurationMap, "doAutologin", false ) );
m_widget->setReusePasswordDefault( getBool( configurationMap, "doReusePassword", false ) );
if ( configurationMap.contains( "passwordRequirements" )
&& configurationMap.value( "passwordRequirements" ).type() == QVariant::Map )
@ -173,8 +194,8 @@ UsersViewStep::setConfigurationMap( const QVariantMap& configurationMap )
}
}
m_widget->setPasswordCheckboxVisible( CalamaresUtils::getBool( configurationMap, "allowWeakPasswords", false ) );
m_widget->setValidatePasswordDefault( !CalamaresUtils::getBool( configurationMap, "allowWeakPasswordsDefault", false) );
m_widget->setPasswordCheckboxVisible( getBool( configurationMap, "allowWeakPasswords", false ) );
m_widget->setValidatePasswordDefault( !getBool( configurationMap, "allowWeakPasswordsDefault", false ) );
QString shell( QLatin1String( "/bin/bash" ) ); // as if it's not set at all
if ( configurationMap.contains( "userShell" ) )
@ -184,4 +205,21 @@ UsersViewStep::setConfigurationMap( const QVariantMap& configurationMap )
// Now it might be explicitly set to empty, which is ok
Calamares::JobQueue::instance()->globalStorage()->insert( "userShell", shell );
using Action = SetHostNameJob::Action;
QString hostnameActionString = CalamaresUtils::getString( configurationMap, "setHostname" );
if ( hostnameActionString.isEmpty() )
{
hostnameActionString = QStringLiteral( "EtcFile" );
}
bool ok = false;
auto hostnameAction = hostnameActions().find( hostnameActionString, ok );
if ( !ok )
{
hostnameAction = Action::EtcHostname;
}
Action hostsfileAction = getBool( configurationMap, "writeHostsFile", true ) ? Action::WriteEtcHosts : Action::None;
m_actions = hostsfileAction | hostnameAction;
}

View File

@ -20,13 +20,13 @@
#ifndef USERSPAGEPLUGIN_H
#define USERSPAGEPLUGIN_H
#include "SetHostNameJob.h"
#include "DllMacro.h"
#include "utils/PluginFactory.h"
#include "viewpages/ViewStep.h"
#include <QObject>
#include <utils/PluginFactory.h>
#include <viewpages/ViewStep.h>
#include <DllMacro.h>
#include <QVariant>
class UsersPage;
@ -61,6 +61,7 @@ private:
QList< Calamares::job_ptr > m_jobs;
QStringList m_defaultGroups;
SetHostNameJob::Actions m_actions;
};
CALAMARES_PLUGIN_FACTORY_DECLARATION( UsersViewStepFactory )

View File

@ -122,3 +122,17 @@ allowWeakPasswordsDefault: false
# - set, non-empty, use that path as shell. No validation is done
# that the shell actually exists or is executable.
# userShell: /bin/bash
# Hostname setting
#
# The user can enter a hostname; this is configured into the system
# in some way; pick one of:
# - *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*.
setHostname: EtcFile
# Should /etc/hosts be written with a hostname for this machine
# (also adds localhost and some ipv6 standard entries).
writeHostsFile: true