diff --git a/nixos/doc/manual/release-notes/rl-2009.xml b/nixos/doc/manual/release-notes/rl-2009.xml
index 3bbb7d71d49..e17e8ac24d1 100644
--- a/nixos/doc/manual/release-notes/rl-2009.xml
+++ b/nixos/doc/manual/release-notes/rl-2009.xml
@@ -415,6 +415,32 @@ systemd.services.nginx.serviceConfig.ReadWritePaths = [ "/var/www" ];
continue to work through Breezy.
+
+
+ In addition to the hostname, the fully qualified domain name (FQDN),
+ which consists of ${cfg.hostName} and
+ ${cfg.domain} is now added to
+ /etc/hosts, to allow local FQDN resolution, as used by the
+ hostname --fqdn command and other applications that
+ try to determine the FQDN. These new entries take precedence over entries
+ from the DNS which could cause regressions in some very specific setups.
+ Additionally the hostname is now resolved to 127.0.0.2
+ instead of 127.0.1.1 to be consistent with what
+ nss-myhostname (from systemd) returns.
+ The old behaviour can e.g. be restored by using
+ networking.hosts = lib.mkForce { "127.0.1.1" = [ config.networking.hostName ]; };.
+
+
+
+
+ The hostname (networking.hostName) must now be a valid
+ DNS label (see RFC 1035) and as such must not contain the domain part.
+ This means that the hostname must start with a letter, end with a letter
+ or digit, and have as interior characters only letters, digits, and
+ hyphen. The maximum length is 63 characters. Additionally it is
+ recommended to only use lower-case characters.
+
+
diff --git a/nixos/modules/config/networking.nix b/nixos/modules/config/networking.nix
index 03944de8249..4cb7d81c997 100644
--- a/nixos/modules/config/networking.nix
+++ b/nixos/modules/config/networking.nix
@@ -8,9 +8,6 @@ let
cfg = config.networking;
- localhostMapped4 = cfg.hosts ? "127.0.0.1" && elem "localhost" cfg.hosts."127.0.0.1";
- localhostMapped6 = cfg.hosts ? "::1" && elem "localhost" cfg.hosts."::1";
-
localhostMultiple = any (elem "localhost") (attrValues (removeAttrs cfg.hosts [ "127.0.0.1" "::1" ]));
in
@@ -147,12 +144,6 @@ in
config = {
assertions = [{
- assertion = localhostMapped4;
- message = ''`networking.hosts` doesn't map "127.0.0.1" to "localhost"'';
- } {
- assertion = !cfg.enableIPv6 || localhostMapped6;
- message = ''`networking.hosts` doesn't map "::1" to "localhost"'';
- } {
assertion = !localhostMultiple;
message = ''
`networking.hosts` maps "localhost" to something other than "127.0.0.1"
@@ -161,22 +152,34 @@ in
'';
}];
- networking.hosts = {
- "127.0.0.1" = [ "localhost" ];
- } // optionalAttrs (cfg.hostName != "") {
- "127.0.1.1" = [ cfg.hostName ];
+ # These entries are required for "hostname -f" and to resolve both the
+ # hostname and FQDN correctly:
+ networking.hosts = let
+ hostnames = # Note: The FQDN (canonical hostname) has to come first:
+ optional (cfg.hostName != "" && cfg.domain != null) "${cfg.hostName}.${cfg.domain}"
+ ++ optional (cfg.hostName != "") cfg.hostName; # Then the hostname (without the domain)
+ in {
+ "127.0.0.2" = hostnames;
} // optionalAttrs cfg.enableIPv6 {
- "::1" = [ "localhost" ];
+ "::1" = hostnames;
};
networking.hostFiles = let
+ # Note: localhostHosts has to appear first in /etc/hosts so that 127.0.0.1
+ # resolves back to "localhost" (as some applications assume) instead of
+ # the FQDN! By default "networking.hosts" also contains entries for the
+ # FQDN so that e.g. "hostname -f" works correctly.
+ localhostHosts = pkgs.writeText "localhost-hosts" ''
+ 127.0.0.1 localhost
+ ${optionalString cfg.enableIPv6 "::1 localhost"}
+ '';
stringHosts =
let
oneToString = set: ip: ip + " " + concatStringsSep " " set.${ip} + "\n";
allToString = set: concatMapStrings (oneToString set) (attrNames set);
in pkgs.writeText "string-hosts" (allToString (filterAttrs (_: v: v != []) cfg.hosts));
extraHosts = pkgs.writeText "extra-hosts" cfg.extraHosts;
- in mkBefore [ stringHosts extraHosts ];
+ in mkBefore [ localhostHosts stringHosts extraHosts ];
environment.etc =
{ # /etc/services: TCP/UDP port assignments.
diff --git a/nixos/modules/tasks/network-interfaces.nix b/nixos/modules/tasks/network-interfaces.nix
index 44677d417ea..12cff6b038f 100644
--- a/nixos/modules/tasks/network-interfaces.nix
+++ b/nixos/modules/tasks/network-interfaces.nix
@@ -376,10 +376,20 @@ in
networking.hostName = mkOption {
default = "nixos";
- type = types.str;
+ # Only allow hostnames without the domain name part (i.e. no FQDNs, see
+ # e.g. "man 5 hostname") and require valid DNS labels (recommended
+ # syntax). Note: We also allow underscores for compatibility/legacy
+ # reasons (as undocumented feature):
+ type = types.strMatching
+ "^[[:alpha:]]([[:alnum:]_-]{0,61}[[:alnum:]])?$";
description = ''
- The name of the machine. Leave it empty if you want to obtain
- it from a DHCP server (if using DHCP).
+ The name of the machine. Leave it empty if you want to obtain it from a
+ DHCP server (if using DHCP). The hostname must be a valid DNS label (see
+ RFC 1035 section 2.3.1: "Preferred name syntax") and as such must not
+ contain the domain part. This means that the hostname must start with a
+ letter, end with a letter or digit, and have as interior characters only
+ letters, digits, and hyphen. The maximum length is 63 characters.
+ Additionally it is recommended to only use lower-case characters.
'';
};
diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix
index 58120987364..47ca015632c 100644
--- a/nixos/tests/all-tests.nix
+++ b/nixos/tests/all-tests.nix
@@ -134,6 +134,7 @@ in
hitch = handleTest ./hitch {};
hocker-fetchdocker = handleTest ./hocker-fetchdocker {};
home-assistant = handleTest ./home-assistant.nix {};
+ hostname = handleTest ./hostname.nix {};
hound = handleTest ./hound.nix {};
hydra = handleTest ./hydra {};
hydra-db-migration = handleTest ./hydra/db-migration.nix {};
diff --git a/nixos/tests/hostname.nix b/nixos/tests/hostname.nix
new file mode 100644
index 00000000000..3b87303d73e
--- /dev/null
+++ b/nixos/tests/hostname.nix
@@ -0,0 +1,66 @@
+{ system ? builtins.currentSystem,
+ config ? {},
+ pkgs ? import ../.. { inherit system config; }
+}:
+
+with import ../lib/testing-python.nix { inherit system pkgs; };
+with pkgs.lib;
+
+let
+ makeHostNameTest = hostName: domain:
+ let
+ fqdn = hostName + (optionalString (domain != null) ".${domain}");
+ in
+ makeTest {
+ name = "hostname-${fqdn}";
+ meta = with pkgs.stdenv.lib.maintainers; {
+ maintainers = [ primeos blitz ];
+ };
+
+ machine = { lib, ... }: {
+ networking.hostName = hostName;
+ networking.domain = domain;
+
+ environment.systemPackages = with pkgs; [
+ inetutils
+ ];
+ };
+
+ testScript = ''
+ start_all()
+
+ machine = ${hostName}
+
+ machine.wait_for_unit("network-online.target")
+
+ # The FQDN, domain name, and hostname detection should work as expected:
+ assert "${fqdn}" == machine.succeed("hostname --fqdn").strip()
+ assert "${optionalString (domain != null) domain}" == machine.succeed("dnsdomainname").strip()
+ assert (
+ "${hostName}"
+ == machine.succeed(
+ 'hostnamectl status | grep "Static hostname" | cut -d: -f2'
+ ).strip()
+ )
+
+ # 127.0.0.1 and ::1 should resolve back to "localhost":
+ assert (
+ "localhost" == machine.succeed("getent hosts 127.0.0.1 | awk '{print $2}'").strip()
+ )
+ assert "localhost" == machine.succeed("getent hosts ::1 | awk '{print $2}'").strip()
+
+ # 127.0.0.2 should resolve back to the FQDN and hostname:
+ fqdn_and_host_name = "${optionalString (domain != null) "${hostName}.${domain} "}${hostName}"
+ assert (
+ fqdn_and_host_name
+ == machine.succeed("getent hosts 127.0.0.2 | awk '{print $2,$3}'").strip()
+ )
+ '';
+ };
+
+in
+{
+ noExplicitDomain = makeHostNameTest "ahost" null;
+
+ explicitDomain = makeHostNameTest "ahost" "adomain";
+}