Skip to content

Commit c7bf74e

Browse files
committed
Support configuring network locations.
This adds support for configuring DNS servers and search domains for each location. This is exposed through the networking.location option. The networking.knownNetworkServices option now applies globally across all locations. The networking.dns and networking.search options are now aliases under networking.location.Automatic, since the "Automatic" network location is the default location on an unconfigured system.
1 parent 9d7aebb commit c7bf74e

File tree

2 files changed

+117
-29
lines changed

2 files changed

+117
-29
lines changed

modules/networking/default.nix

+93-25
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
{ config, lib, pkgs, ... }:
1+
{ config, lib, ... }:
22

33
with lib;
44

@@ -10,20 +10,64 @@ let
1010
emptyList = lst: if lst != [] then lst else ["empty"];
1111
quoteStrings = concatMapStringsSep " " (str: "'${str}'");
1212

13-
setNetworkServices = optionalString (cfg.knownNetworkServices != []) ''
14-
networkservices=$(networksetup -listallnetworkservices)
15-
${concatMapStringsSep "\n" (srv: ''
16-
case "$networkservices" in
17-
*'${srv}'*)
18-
networksetup -setdnsservers '${srv}' ${quoteStrings (emptyList cfg.dns)}
19-
networksetup -setsearchdomains '${srv}' ${quoteStrings (emptyList cfg.search)}
20-
;;
21-
esac
22-
'') cfg.knownNetworkServices}
13+
setLocations = optionalString (cfg.knownNetworkServices != [] && cfg.location != {}) ''
14+
curr_location=$(networksetup -getcurrentlocation)
15+
16+
readarray -t curr_locations_array < <(networksetup -listlocations)
17+
18+
declare -A curr_locations
19+
for location in "''${curr_locations_array[@]}"; do
20+
curr_locations[$location]=1
21+
done
22+
23+
declare -A goal_locations
24+
for location in ${strings.escapeShellArgs (builtins.attrNames cfg.location)}; do
25+
goal_locations[$location]=1
26+
done
27+
28+
for location in "''${!goal_locations[@]}"; do
29+
if [[ ! -v curr_locations[$location] ]]; then
30+
networksetup -createlocation "$location" populate > /dev/null
31+
fi
32+
done
33+
34+
# switch to a location that surely does not need to be deleted
35+
networksetup -switchtolocation ${strings.escapeShellArg (
36+
builtins.head (builtins.attrNames cfg.location)
37+
)} > /dev/null
38+
39+
for location in "''${!curr_locations[@]}"; do
40+
if [[ ! -v goal_locations[$location] ]]; then
41+
networksetup -deletelocation "$location" > /dev/null
42+
fi
43+
done
44+
45+
${concatMapStringsSep "\n" (location: ''
46+
networksetup -switchtolocation ${strings.escapeShellArg location} > /dev/null
47+
48+
networkservices=$(networksetup -listallnetworkservices)
49+
${concatMapStringsSep "\n" (srv: ''
50+
case "$networkservices" in
51+
*'${srv}'*)
52+
networksetup -setdnsservers '${srv}' ${quoteStrings (emptyList cfg.location.${location}.dns)}
53+
networksetup -setsearchdomains '${srv}' ${quoteStrings (emptyList cfg.location.${location}.search)}
54+
;;
55+
esac
56+
'') cfg.knownNetworkServices}
57+
'') (builtins.attrNames cfg.location)}
58+
59+
if [[ -v goal_locations[$curr_location] ]]; then
60+
networksetup -switchtolocation "$curr_location" > /dev/null
61+
fi
2362
'';
2463
in
2564

2665
{
66+
imports = [
67+
(mkAliasOptionModule ["networking" "dns"] ["networking" "location" "Automatic" "dns"])
68+
(mkAliasOptionModule ["networking" "search"] ["networking" "location" "Automatic" "search"])
69+
];
70+
2771
options = {
2872
networking.computerName = mkOption {
2973
type = types.nullOr types.str;
@@ -75,32 +119,56 @@ in
75119
default = [];
76120
example = [ "Wi-Fi" "Ethernet Adaptor" "Thunderbolt Ethernet" ];
77121
description = ''
78-
List of networkservices that should be configured.
122+
List of network services that should be configured.
79123
80124
To display a list of all the network services on the server's
81125
hardware ports, use {command}`networksetup -listallnetworkservices`.
82126
'';
83127
};
84128

85-
networking.dns = mkOption {
86-
type = types.listOf types.str;
87-
default = [];
88-
example = [ "8.8.8.8" "8.8.4.4" "2001:4860:4860::8888" "2001:4860:4860::8844" ];
89-
description = "The list of dns servers used when resolving domain names.";
90-
};
129+
networking.location = mkOption {
130+
type = types.attrsOf (types.submodule {
131+
options = {
132+
dns = mkOption {
133+
type = types.listOf types.str;
134+
default = [];
135+
example = [ "8.8.8.8" "8.8.4.4" "2001:4860:4860::8888" "2001:4860:4860::8844" ];
136+
description = "The list of DNS servers used when resolving domain names.";
137+
};
138+
139+
search = mkOption {
140+
type = types.listOf types.str;
141+
default = [];
142+
description = "The list of search paths used when resolving domain names.";
143+
};
144+
};
145+
});
146+
default = {};
147+
description = ''
148+
Set of network locations to configure.
91149
92-
networking.search = mkOption {
93-
type = types.listOf types.str;
94-
default = [];
95-
description = "The list of search paths used when resolving domain names.";
150+
By default, a system comes with a single location called "Automatic", but you can
151+
define additional locations to switch between different network configurations.
152+
153+
If you define any locations here, you must also explicitly define the "Automatic"
154+
location if you want it to exist.
155+
'';
96156
};
97157
};
98158

99159
config = {
100160

101161
warnings = [
102-
(mkIf (cfg.knownNetworkServices == [] && cfg.dns != []) "networking.knownNetworkServices is empty, dns servers will not be configured.")
103-
(mkIf (cfg.knownNetworkServices == [] && cfg.search != []) "networking.knownNetworkServices is empty, dns searchdomains will not be configured.")
162+
(
163+
mkIf (cfg.knownNetworkServices == [] && (
164+
builtins.any (l: l.dns != []) (builtins.attrValues cfg.location)
165+
)) "networking.knownNetworkServices is empty, DNS servers will not be configured."
166+
)
167+
(
168+
mkIf (cfg.knownNetworkServices == [] && (
169+
builtins.any (l: l.search != []) (builtins.attrValues cfg.location)
170+
)) "networking.knownNetworkServices is empty, DNS search domains will not be configured."
171+
)
104172
];
105173

106174
system.activationScripts.networking.text = ''
@@ -116,7 +184,7 @@ in
116184
scutil --set LocalHostName ${escapeShellArg cfg.localHostName}
117185
''}
118186
119-
${setNetworkServices}
187+
${setLocations}
120188
'';
121189

122190
};

tests/networking-networkservices.nix

+24-4
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,35 @@
1-
{ config, pkgs, ... }:
1+
{ lib, config, ... }:
22

33
{
4-
networking.knownNetworkServices = [ "Wi-Fi" "Thunderbolt Ethernet" ];
5-
networking.dns = [ "8.8.8.8" "8.8.4.4" ];
4+
networking.knownNetworkServices = [
5+
"Wi-Fi"
6+
"Thunderbolt Ethernet"
7+
];
8+
networking.dns = [
9+
"8.8.8.8"
10+
"8.8.4.4"
11+
];
12+
networking.location."Home Lab" = {
13+
search = [ "home.lab" ];
14+
};
615

716
test = ''
817
echo checking dns settings in /activate >&2
18+
19+
grep "networksetup -switchtolocation ${lib.strings.escapeShellArg "Automatic"}" ${config.out}/activate
920
grep "networksetup -setdnsservers 'Wi-Fi' '8.8.8.8' '8.8.4.4'" ${config.out}/activate
1021
grep "networksetup -setdnsservers 'Thunderbolt Ethernet' '8.8.8.8' '8.8.4.4'" ${config.out}/activate
11-
echo checking empty searchdomain settings in /activate >&2
22+
23+
grep "networksetup -switchtolocation 'Home Lab'" ${config.out}/activate
24+
grep "networksetup -setdnsservers 'Wi-Fi' 'empty'" ${config.out}/activate
25+
grep "networksetup -setdnsservers 'Thunderbolt Ethernet' 'empty'" ${config.out}/activate
26+
27+
echo checking searchdomain settings in /activate >&2
28+
1229
grep "networksetup -setsearchdomains 'Wi-Fi' 'empty'" ${config.out}/activate
1330
grep "networksetup -setsearchdomains 'Thunderbolt Ethernet' 'empty'" ${config.out}/activate
31+
32+
grep "networksetup -setsearchdomains 'Wi-Fi' 'home.lab'" ${config.out}/activate
33+
grep "networksetup -setsearchdomains 'Thunderbolt Ethernet' 'home.lab'" ${config.out}/activate
1434
'';
1535
}

0 commit comments

Comments
 (0)