1# GeoClue 2 daemon.
2
3{ config, lib, pkgs, ... }:
4
5with lib;
6
7let
8 # the demo agent isn't built by default, but we need it here
9 package = pkgs.geoclue2.override { withDemoAgent = config.services.geoclue2.enableDemoAgent; };
10
11 cfg = config.services.geoclue2;
12
13 defaultWhitelist = [ "gnome-shell" "io.elementary.desktop.agent-geoclue2" ];
14
15 appConfigModule = types.submodule ({ name, ... }: {
16 options = {
17 desktopID = mkOption {
18 type = types.str;
19 description = lib.mdDoc "Desktop ID of the application.";
20 };
21
22 isAllowed = mkOption {
23 type = types.bool;
24 description = lib.mdDoc ''
25 Whether the application will be allowed access to location information.
26 '';
27 };
28
29 isSystem = mkOption {
30 type = types.bool;
31 description = lib.mdDoc ''
32 Whether the application is a system component or not.
33 '';
34 };
35
36 users = mkOption {
37 type = types.listOf types.str;
38 default = [];
39 description = lib.mdDoc ''
40 List of UIDs of all users for which this application is allowed location
41 info access, Defaults to an empty string to allow it for all users.
42 '';
43 };
44 };
45
46 config.desktopID = mkDefault name;
47 });
48
49 appConfigToINICompatible = _: { desktopID, isAllowed, isSystem, users, ... }: {
50 name = desktopID;
51 value = {
52 allowed = isAllowed;
53 system = isSystem;
54 users = concatStringsSep ";" users;
55 };
56 };
57
58in
59{
60
61 ###### interface
62
63 options = {
64
65 services.geoclue2 = {
66
67 enable = mkOption {
68 type = types.bool;
69 default = false;
70 description = lib.mdDoc ''
71 Whether to enable GeoClue 2 daemon, a DBus service
72 that provides location information for accessing.
73 '';
74 };
75
76 enableDemoAgent = mkOption {
77 type = types.bool;
78 default = true;
79 description = lib.mdDoc ''
80 Whether to use the GeoClue demo agent. This should be
81 overridden by desktop environments that provide their own
82 agent.
83 '';
84 };
85
86 enableNmea = mkOption {
87 type = types.bool;
88 default = true;
89 description = lib.mdDoc ''
90 Whether to fetch location from NMEA sources on local network.
91 '';
92 };
93
94 enable3G = mkOption {
95 type = types.bool;
96 default = true;
97 description = lib.mdDoc ''
98 Whether to enable 3G source.
99 '';
100 };
101
102 enableCDMA = mkOption {
103 type = types.bool;
104 default = true;
105 description = lib.mdDoc ''
106 Whether to enable CDMA source.
107 '';
108 };
109
110 enableModemGPS = mkOption {
111 type = types.bool;
112 default = true;
113 description = lib.mdDoc ''
114 Whether to enable Modem-GPS source.
115 '';
116 };
117
118 enableWifi = mkOption {
119 type = types.bool;
120 default = true;
121 description = lib.mdDoc ''
122 Whether to enable WiFi source.
123 '';
124 };
125
126 geoProviderUrl = mkOption {
127 type = types.str;
128 default = "https://location.services.mozilla.com/v1/geolocate?key=geoclue";
129 example = "https://www.googleapis.com/geolocation/v1/geolocate?key=YOUR_KEY";
130 description = lib.mdDoc ''
131 The url to the wifi GeoLocation Service.
132 '';
133 };
134
135 submitData = mkOption {
136 type = types.bool;
137 default = false;
138 description = lib.mdDoc ''
139 Whether to submit data to a GeoLocation Service.
140 '';
141 };
142
143 submissionUrl = mkOption {
144 type = types.str;
145 default = "https://location.services.mozilla.com/v1/submit?key=geoclue";
146 description = lib.mdDoc ''
147 The url to submit data to a GeoLocation Service.
148 '';
149 };
150
151 submissionNick = mkOption {
152 type = types.str;
153 default = "geoclue";
154 description = lib.mdDoc ''
155 A nickname to submit network data with.
156 Must be 2-32 characters long.
157 '';
158 };
159
160 appConfig = mkOption {
161 type = types.attrsOf appConfigModule;
162 default = {};
163 example = literalExpression ''
164 "com.github.app" = {
165 isAllowed = true;
166 isSystem = true;
167 users = [ "300" ];
168 };
169 '';
170 description = lib.mdDoc ''
171 Specify extra settings per application.
172 '';
173 };
174
175 };
176
177 };
178
179
180 ###### implementation
181 config = mkIf cfg.enable {
182
183 environment.systemPackages = [ package ];
184
185 services.dbus.packages = [ package ];
186
187 systemd.packages = [ package ];
188
189 # we cannot use DynamicUser as we need the the geoclue user to exist for the
190 # dbus policy to work
191 users = {
192 users.geoclue = {
193 isSystemUser = true;
194 home = "/var/lib/geoclue";
195 group = "geoclue";
196 description = "Geoinformation service";
197 };
198
199 groups.geoclue = {};
200 };
201
202 systemd.services.geoclue = {
203 after = lib.optionals cfg.enableWifi [ "network-online.target" ];
204 # restart geoclue service when the configuration changes
205 restartTriggers = [
206 config.environment.etc."geoclue/geoclue.conf".source
207 ];
208 serviceConfig.StateDirectory = "geoclue";
209 };
210
211 # this needs to run as a user service, since it's associated with the
212 # user who is making the requests
213 systemd.user.services = mkIf cfg.enableDemoAgent {
214 geoclue-agent = {
215 description = "Geoclue agent";
216 # this should really be `partOf = [ "geoclue.service" ]`, but
217 # we can't be part of a system service, and the agent should
218 # be okay with the main service coming and going
219 wantedBy = [ "default.target" ];
220 after = lib.optionals cfg.enableWifi [ "network-online.target" ];
221 unitConfig.ConditionUser = "!@system";
222 serviceConfig = {
223 Type = "exec";
224 ExecStart = "${package}/libexec/geoclue-2.0/demos/agent";
225 Restart = "on-failure";
226 PrivateTmp = true;
227 };
228 };
229 };
230
231 services.geoclue2.appConfig.epiphany = {
232 isAllowed = true;
233 isSystem = false;
234 };
235
236 services.geoclue2.appConfig.firefox = {
237 isAllowed = true;
238 isSystem = false;
239 };
240
241 environment.etc."geoclue/geoclue.conf".text =
242 generators.toINI {} ({
243 agent = {
244 whitelist = concatStringsSep ";"
245 (optional cfg.enableDemoAgent "geoclue-demo-agent" ++ defaultWhitelist);
246 };
247 network-nmea = {
248 enable = cfg.enableNmea;
249 };
250 "3g" = {
251 enable = cfg.enable3G;
252 };
253 cdma = {
254 enable = cfg.enableCDMA;
255 };
256 modem-gps = {
257 enable = cfg.enableModemGPS;
258 };
259 wifi = {
260 enable = cfg.enableWifi;
261 url = cfg.geoProviderUrl;
262 submit-data = boolToString cfg.submitData;
263 submission-url = cfg.submissionUrl;
264 submission-nick = cfg.submissionNick;
265 };
266 } // mapAttrs' appConfigToINICompatible cfg.appConfig);
267 };
268
269 meta = with lib; {
270 maintainers = with maintainers; [ ] ++ teams.pantheon.members;
271 };
272}