at master 10 kB view raw
1# GeoClue 2 daemon. 2{ 3 config, 4 lib, 5 pkgs, 6 ... 7}: 8let 9 cfg = config.services.geoclue2; 10 11 appConfigModule = lib.types.submodule ( 12 { name, ... }: 13 { 14 options = { 15 desktopID = lib.mkOption { 16 type = lib.types.str; 17 description = "Desktop ID of the application."; 18 }; 19 20 isAllowed = lib.mkOption { 21 type = lib.types.bool; 22 description = '' 23 Whether the application will be allowed access to location information. 24 ''; 25 }; 26 27 isSystem = lib.mkOption { 28 type = lib.types.bool; 29 description = '' 30 Whether the application is a system component or not. 31 ''; 32 }; 33 34 users = lib.mkOption { 35 type = lib.types.listOf lib.types.str; 36 default = [ ]; 37 description = '' 38 List of UIDs of all users for which this application is allowed location 39 info access, Defaults to an empty string to allow it for all users. 40 ''; 41 }; 42 }; 43 44 config.desktopID = lib.mkDefault name; 45 } 46 ); 47 48 appConfigToINICompatible = 49 _: 50 { 51 desktopID, 52 isAllowed, 53 isSystem, 54 users, 55 ... 56 }: 57 { 58 name = desktopID; 59 value = { 60 allowed = isAllowed; 61 system = isSystem; 62 users = lib.concatStringsSep ";" users; 63 }; 64 }; 65 66in 67{ 68 69 ###### interface 70 71 options = { 72 73 services.geoclue2 = { 74 75 enable = lib.mkOption { 76 type = lib.types.bool; 77 default = false; 78 description = '' 79 Whether to enable GeoClue 2 daemon, a DBus service 80 that provides location information for accessing. 81 ''; 82 }; 83 whitelistedAgents = lib.mkOption { 84 type = lib.types.listOf lib.types.str; 85 default = [ 86 "gnome-shell" 87 "io.elementary.desktop.agent-geoclue2" 88 ]; 89 description = '' 90 Desktop IDs (without the .desktop extension) of whitelisted agents. 91 ''; 92 }; 93 94 enableDemoAgent = lib.mkOption { 95 type = lib.types.bool; 96 default = true; 97 description = '' 98 Whether to use the GeoClue demo agent. This should be 99 overridden by desktop environments that provide their own 100 agent. 101 ''; 102 }; 103 104 enableNmea = lib.mkOption { 105 type = lib.types.bool; 106 default = true; 107 description = '' 108 Whether to fetch location from NMEA sources on local network. 109 ''; 110 }; 111 112 enable3G = lib.mkOption { 113 type = lib.types.bool; 114 default = true; 115 description = '' 116 Whether to enable 3G source. 117 ''; 118 }; 119 120 enableCDMA = lib.mkOption { 121 type = lib.types.bool; 122 default = true; 123 description = '' 124 Whether to enable CDMA source. 125 ''; 126 }; 127 128 enableModemGPS = lib.mkOption { 129 type = lib.types.bool; 130 default = true; 131 description = '' 132 Whether to enable Modem-GPS source. 133 ''; 134 }; 135 136 enableWifi = lib.mkOption { 137 type = lib.types.bool; 138 default = true; 139 description = '' 140 Whether to enable WiFi source. 141 ''; 142 }; 143 144 enableStatic = lib.mkOption { 145 type = lib.types.bool; 146 default = false; 147 description = '' 148 Whether to enable the static source. This source defines a fixed 149 location using the `staticLatitude`, `staticLongitude`, 150 `staticAltitude`, and `staticAccuracy` options. 151 152 Setting `enableStatic` to true will disable all other sources, to 153 prevent conflicts. Use `lib.mkForce true` when enabling other sources 154 if for some reason you want to override this. 155 ''; 156 }; 157 158 staticLatitude = lib.mkOption { 159 type = lib.types.numbers.between (-90) 90; 160 description = '' 161 Latitude to use for the static source. Defaults to `location.latitude`. 162 ''; 163 }; 164 165 staticLongitude = lib.mkOption { 166 type = lib.types.numbers.between (-180) 180; 167 description = '' 168 Longitude to use for the static source. Defaults to `location.longitude`. 169 ''; 170 }; 171 172 staticAltitude = lib.mkOption { 173 type = lib.types.number; 174 description = '' 175 Altitude in meters to use for the static source. 176 ''; 177 }; 178 179 staticAccuracy = lib.mkOption { 180 type = lib.types.numbers.positive; 181 description = '' 182 Accuracy radius in meters to use for the static source. 183 ''; 184 }; 185 186 geoProviderUrl = lib.mkOption { 187 type = lib.types.str; 188 default = "https://api.beacondb.net/v1/geolocate"; 189 example = "https://www.googleapis.com/geolocation/v1/geolocate?key=YOUR_KEY"; 190 description = '' 191 The url to the wifi GeoLocation Service. 192 ''; 193 }; 194 195 package = lib.mkOption { 196 type = lib.types.package; 197 default = pkgs.geoclue2; 198 defaultText = lib.literalExpression "pkgs.geoclue2"; 199 apply = 200 pkg: 201 pkg.override { 202 # the demo agent isn't built by default, but we need it here 203 withDemoAgent = cfg.enableDemoAgent; 204 }; 205 description = "The geoclue2 package to use"; 206 }; 207 208 submitData = lib.mkOption { 209 type = lib.types.bool; 210 default = false; 211 description = '' 212 Whether to submit data to a GeoLocation Service. 213 ''; 214 }; 215 216 submissionUrl = lib.mkOption { 217 type = lib.types.str; 218 default = "https://api.beacondb.net/v2/geosubmit"; 219 description = '' 220 The url to submit data to a GeoLocation Service. 221 ''; 222 }; 223 224 submissionNick = lib.mkOption { 225 type = lib.types.str; 226 default = "geoclue"; 227 description = '' 228 A nickname to submit network data with. 229 Must be 2-32 characters long. 230 ''; 231 }; 232 233 appConfig = lib.mkOption { 234 type = lib.types.attrsOf appConfigModule; 235 default = { }; 236 example = lib.literalExpression '' 237 "com.github.app" = { 238 isAllowed = true; 239 isSystem = true; 240 users = [ "300" ]; 241 }; 242 ''; 243 description = '' 244 Specify extra settings per application. 245 ''; 246 }; 247 248 }; 249 250 }; 251 252 ###### implementation 253 config = lib.mkIf cfg.enable { 254 255 environment.systemPackages = [ cfg.package ]; 256 257 services.dbus.packages = [ cfg.package ]; 258 259 systemd.packages = [ cfg.package ]; 260 261 # we cannot use DynamicUser as we need the the geoclue user to exist for the 262 # dbus policy to work 263 users = { 264 users.geoclue = { 265 isSystemUser = true; 266 home = "/var/lib/geoclue"; 267 group = "geoclue"; 268 description = "Geoinformation service"; 269 }; 270 271 groups.geoclue = { }; 272 }; 273 274 services.geoclue2 = { 275 enable3G = lib.mkIf cfg.enableStatic false; 276 enableCDMA = lib.mkIf cfg.enableStatic false; 277 enableModemGPS = lib.mkIf cfg.enableStatic false; 278 enableNmea = lib.mkIf cfg.enableStatic false; 279 enableWifi = lib.mkIf cfg.enableStatic false; 280 staticLatitude = lib.mkDefault config.location.latitude; 281 staticLongitude = lib.mkDefault config.location.longitude; 282 }; 283 284 systemd.services.geoclue = { 285 wants = lib.optionals cfg.enableWifi [ "network-online.target" ]; 286 after = lib.optionals cfg.enableWifi [ "network-online.target" ]; 287 # restart geoclue service when the configuration changes 288 restartTriggers = [ 289 config.environment.etc."geoclue/geoclue.conf".source 290 ]; 291 serviceConfig.StateDirectory = "geoclue"; 292 }; 293 294 # this needs to run as a user service, since it's associated with the 295 # user who is making the requests 296 systemd.user.services = lib.mkIf cfg.enableDemoAgent { 297 geoclue-agent = { 298 description = "Geoclue agent"; 299 # this should really be `partOf = [ "geoclue.service" ]`, but 300 # we can't be part of a system service, and the agent should 301 # be okay with the main service coming and going 302 wantedBy = [ "default.target" ]; 303 wants = lib.optionals cfg.enableWifi [ "network-online.target" ]; 304 after = lib.optionals cfg.enableWifi [ "network-online.target" ]; 305 unitConfig.ConditionUser = "!@system"; 306 serviceConfig = { 307 Type = "exec"; 308 ExecStart = "${cfg.package}/libexec/geoclue-2.0/demos/agent"; 309 Restart = "on-failure"; 310 PrivateTmp = true; 311 }; 312 }; 313 }; 314 315 services.geoclue2.appConfig.epiphany = { 316 isAllowed = true; 317 isSystem = false; 318 }; 319 320 services.geoclue2.appConfig.firefox = { 321 isAllowed = true; 322 isSystem = false; 323 }; 324 325 environment.etc."geoclue/geoclue.conf".text = lib.generators.toINI { } ( 326 { 327 agent = { 328 whitelist = lib.concatStringsSep ";" ( 329 lib.lists.unique ( 330 cfg.whitelistedAgents 331 ++ lib.optionals config.services.geoclue2.enableDemoAgent [ "geoclue-demo-agent" ] 332 ) 333 ); 334 }; 335 network-nmea = { 336 enable = cfg.enableNmea; 337 }; 338 "3g" = { 339 enable = cfg.enable3G; 340 }; 341 cdma = { 342 enable = cfg.enableCDMA; 343 }; 344 modem-gps = { 345 enable = cfg.enableModemGPS; 346 }; 347 wifi = { 348 enable = cfg.enableWifi; 349 } 350 // lib.optionalAttrs cfg.enableWifi { 351 url = cfg.geoProviderUrl; 352 submit-data = lib.boolToString cfg.submitData; 353 submission-url = cfg.submissionUrl; 354 submission-nick = cfg.submissionNick; 355 }; 356 static-source = { 357 enable = cfg.enableStatic; 358 }; 359 } 360 // lib.mapAttrs' appConfigToINICompatible cfg.appConfig 361 ); 362 363 environment.etc.geolocation = lib.mkIf cfg.enableStatic { 364 mode = "0440"; 365 group = "geoclue"; 366 text = '' 367 ${toString cfg.staticLatitude} 368 ${toString cfg.staticLongitude} 369 ${toString cfg.staticAltitude} 370 ${toString cfg.staticAccuracy} 371 ''; 372 }; 373 }; 374 375 meta = with lib; { 376 maintainers = with maintainers; [ ] ++ teams.pantheon.members; 377 }; 378}