diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index d87b763..8eab6d3 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -47,12 +47,14 @@ jobs: sudo cp modules/examples/simple.nix /etc/nix-darwin/configuration.nix nixConfHash=$(shasum -a 256 /etc/nix/nix.conf | cut -d ' ' -f 1) + etcHostsHash=$(shasum -a 256 /etc/hosts | cut -d ' ' -f 1) sudo /usr/bin/sed -i.bak \ "s/# programs.fish.enable = true;/ \ imports = [ \ ({ options, ... }: { \ nix.settings.access-tokens = [ \"github.com=\${{ secrets.GITHUB_TOKEN }}\" ]; \ environment.etc.\"nix\/nix.conf\".knownSha256Hashes = [ \"$nixConfHash\" ]; \ + environment.etc.hosts.knownSha256Hashes = [ \"$etcHostsHash\" ]; \ nix.nixPath = \ [ { darwin = \"${PWD////\/}\"; } ] \ ++ options.nix.nixPath.default; \ @@ -102,8 +104,9 @@ jobs: pushd /etc/nix-darwin sudo nix flake init -t $darwin nixConfHash=$(shasum -a 256 /etc/nix/nix.conf | cut -d ' ' -f 1) + etcHostsHash=$(shasum -a 256 /etc/hosts | cut -d ' ' -f 1) sudo /usr/bin/sed -i.bak \ - "s/# programs.fish.enable = true;/nix.settings.access-tokens = [ \"github.com=\${{ secrets.GITHUB_TOKEN }}\" ]; environment.etc.\"nix\/nix.conf\".knownSha256Hashes = [ \"$nixConfHash\" ];/" \ + "s/# programs.fish.enable = true;/nix.settings.access-tokens = [ \"github.com=\${{ secrets.GITHUB_TOKEN }}\" ]; environment.etc.\"nix\/nix.conf\".knownSha256Hashes = [ \"$nixConfHash\" ]; environment.etc.hosts.knownSha256Hashes = [ \"$etcHostsHash\" ];/" \ flake.nix sudo /usr/bin/sed -i.bak \ 's/darwinConfigurations."simple"/darwinConfigurations."'$(scutil --get LocalHostName)'"/g' \ diff --git a/doc/known-files/c7dd0e2ed261ce76d76f852596c5b54026b9a894fa481381ffd399b556c0e2da b/doc/known-files/c7dd0e2ed261ce76d76f852596c5b54026b9a894fa481381ffd399b556c0e2da new file mode 100644 index 0000000..34d0068 --- /dev/null +++ b/doc/known-files/c7dd0e2ed261ce76d76f852596c5b54026b9a894fa481381ffd399b556c0e2da @@ -0,0 +1,9 @@ +## +# Host Database +# +# localhost is used to configure the loopback interface +# when the system is booting. Do not change this entry. +## +127.0.0.1 localhost +255.255.255.255 broadcasthost +::1 localhost diff --git a/modules/networking/default.nix b/modules/networking/default.nix index b53a9e4..9b69759 100644 --- a/modules/networking/default.nix +++ b/modules/networking/default.nix @@ -1,4 +1,4 @@ -{ config, lib, ... }: +{ config, lib, pkgs, ... }: with lib; @@ -22,6 +22,8 @@ let esac '') cfg.knownNetworkServices} ''; + + localhostMultiple = any (elem "localhost") (attrValues (removeAttrs cfg.hosts [ "127.0.0.1" "::1" ])); in { @@ -105,9 +107,50 @@ in Battery powered devices may require being connected to power. ''; }; + + networking.hosts = lib.mkOption { + type = types.attrsOf (types.listOf types.str); + example = literalExpression '' + { + "127.0.0.1" = [ "foo.bar.baz" ]; + "192.168.0.2" = [ "fileserver.local" "nameserver.local" ]; + }; + ''; + description = '' + Locally defined maps of hostnames to IP addresses. + ''; + default = {}; + }; + + networking.hostFiles = lib.mkOption { + type = types.listOf types.path; + defaultText = literalMD "Hosts from {option}`networking.hosts` and {option}`networking.extraHosts`"; + example = literalExpression ''[ "''${pkgs.my-blocklist-package}/share/my-blocklist/hosts" ]''; + description = '' + Files that should be concatenated together to form {file}`/etc/hosts`. + ''; + }; + + networking.extraHosts = lib.mkOption { + type = types.lines; + default = ""; + example = "192.168.0.1 lanlocalhost"; + description = '' + Additional verbatim entries to be appended to {file}`/etc/hosts`. + For adding hosts from derivation results, use {option}`networking.hostFiles` instead. + ''; + }; }; config = { + assertions = [{ + assertion = !localhostMultiple; + message = '' + `networking.hosts` maps "localhost" to something other than "127.0.0.1" + or "::1". This will break some applications. Please use + `networking.extraHosts` if you really want to add such a mapping. + ''; + }]; warnings = [ (mkIf (cfg.knownNetworkServices == [] && cfg.dns != []) "networking.knownNetworkServices is empty, dns servers will not be configured.") @@ -134,5 +177,36 @@ in ''} ''; + 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! + localhostHosts = pkgs.writeText "localhost-hosts" '' + ## + # Host Database + # + # localhost is used to configure the loopback interface + # when the system is booting. Do not change this entry. + ## + 127.0.0.1 localhost + 255.255.255.255 broadcasthost + ::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 [ localhostHosts stringHosts extraHosts ]; + + environment.etc.hosts = { + knownSha256Hashes = [ + # Comes from MacOS: Darwin 24.1.0 Darwin Kernel Version 24.1.0. + "c7dd0e2ed261ce76d76f852596c5b54026b9a894fa481381ffd399b556c0e2da" + ]; + + source = pkgs.concatText "hosts" cfg.hostFiles; + }; }; } diff --git a/release.nix b/release.nix index 115025e..1085652 100644 --- a/release.nix +++ b/release.nix @@ -86,6 +86,7 @@ in { tests.launchd-daemons = makeTest ./tests/launchd-daemons.nix; tests.launchd-setenv = makeTest ./tests/launchd-setenv.nix; tests.networking-hostname = makeTest ./tests/networking-hostname.nix; + tests.networking-hosts = makeTest ./tests/networking-hosts.nix; tests.networking-networkservices = makeTest ./tests/networking-networkservices.nix; tests.nix-enable = makeTest ./tests/nix-enable.nix; tests.nixpkgs-overlays = makeTest ./tests/nixpkgs-overlays.nix; diff --git a/tests/networking-hosts.nix b/tests/networking-hosts.nix new file mode 100644 index 0000000..2d73946 --- /dev/null +++ b/tests/networking-hosts.nix @@ -0,0 +1,20 @@ +{ config, pkgs, ... }: + +{ + networking.hosts = { + "127.0.0.1" = [ "my.super.host" ]; + "10.0.0.1" = [ "my.super.host" "my.other.host" ]; + }; + + test = '' + set -v + echo checking /etc/hosts file >&2 + + file=${config.out}/etc/hosts + + grep '127.0.0.1' $file | head -n1 | grep localhost$ + grep '127.0.0.1' $file | tail -n1 | grep my.super.host$ + grep '::1' $file | grep localhost$ + grep '10.0.0.1' $file | grep my.super.host\ my.other.host$ + ''; +}