From d8f9225c34268d5e730a0eda053479371edb186a Mon Sep 17 00:00:00 2001 From: "Jeffrey C. Ollie" Date: Wed, 13 Dec 2023 23:32:42 -0600 Subject: [PATCH] first --- .gitignore | 1 + feeder.nix | 161 +++++ flake.lock | 60 ++ flake.nix | 17 + modules/config/default.nix | 823 ++++++++++++++++++++++++++ modules/default.nix | 12 + modules/options/adsbexchange.nix | 96 +++ modules/options/adsbfi.nix | 144 +++++ modules/options/default.nix | 95 +++ modules/options/dump1090.nix | 293 +++++++++ modules/options/dump978.nix | 15 + modules/options/piaware.nix | 26 + modules/options/skyaware978.nix | 15 + modules/options/theairtraffic.nix | 156 +++++ packages/adsbexchange-mlat-client.nix | 22 + packages/adsbexchange-readsb.nix | 40 ++ packages/adsbfi-mlat-client.nix | 22 + packages/adsbfi-readsb.nix | 40 ++ packages/default.nix | 28 + packages/dump1090-fa.nix | 58 ++ packages/dump978-fa.nix | 52 ++ packages/mutability-mlat-client.nix | 14 + packages/piaware.nix | 91 +++ packages/tar1090.nix | 24 + packages/tcllauncher.nix | 63 ++ 25 files changed, 2368 insertions(+) create mode 100644 .gitignore create mode 100644 feeder.nix create mode 100644 flake.lock create mode 100644 flake.nix create mode 100644 modules/config/default.nix create mode 100644 modules/default.nix create mode 100644 modules/options/adsbexchange.nix create mode 100644 modules/options/adsbfi.nix create mode 100644 modules/options/default.nix create mode 100644 modules/options/dump1090.nix create mode 100644 modules/options/dump978.nix create mode 100644 modules/options/piaware.nix create mode 100644 modules/options/skyaware978.nix create mode 100644 modules/options/theairtraffic.nix create mode 100644 packages/adsbexchange-mlat-client.nix create mode 100644 packages/adsbexchange-readsb.nix create mode 100644 packages/adsbfi-mlat-client.nix create mode 100644 packages/adsbfi-readsb.nix create mode 100644 packages/default.nix create mode 100644 packages/dump1090-fa.nix create mode 100644 packages/dump978-fa.nix create mode 100644 packages/mutability-mlat-client.nix create mode 100644 packages/piaware.nix create mode 100644 packages/tar1090.nix create mode 100644 packages/tcllauncher.nix diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d6944e3 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/result* diff --git a/feeder.nix b/feeder.nix new file mode 100644 index 0000000..7ff11c6 --- /dev/null +++ b/feeder.nix @@ -0,0 +1,161 @@ +{ mainConfig, feederConfig, pkgs, self, lib }: { + + config = { + users.groups.${feederConfig.name} = { }; + users.users.${feederConfig.name} = { + isSystemUser = true; + group = "${feederConfig.name}"; + }; + + systemd.services."${feederConfig.name}-mlat" = + let + uuidFile = pkgs.writeTextFile { + name = "${feederConfig.name}-uuid"; + text = "${feederConfig.uuid}"; + }; + options = [ + "--input-type" + feederConfig.mlat.input.type + "--no-udp" + "--input-connect" + "${feederConfig.mlat.input.host}:${toString feederConfig.mlat.input.port}" + "--server" + feederConfig.mlat.server + "--user" + feederConfig.username + "--lat" + mainConfig.latitude + "--lon" + mainConfig.longitude + "--alt" + (toString mainConfig.altitude) + "--uuid-file" + "${uuidFile}" + ] ++ ( + if feederConfig.mlat.privacy + then + [ + "--privacy" + ] + else + [ ] + ) ++ ( + map (result: "--result ${result}") feederConfig.mlat.results + ); + in + { + enable = feederConfig.mlat.enable; + description = "${feederConfig.name} MLAT client."; + wants = [ "network.target" ]; + after = [ "network.target" ]; + unitConfig = { + Documentation = "https://github.com/makrsmark/mlat-client/"; + }; + serviceConfig = { + Type = "simple"; + User = "${feederConfig.name}"; + RuntimeDirectory = "${feederConfig.name}-mlat"; + RuntimeDirectoryMode = "0755"; + ExecStart = "${self.packages.${pkgs.system}.adsbfi-mlat-client}/bin/mlat-client ${lib.concatStringsSep " " options}"; + SyslogIdentifier = "${feederConfig.name}-mlat"; + Restart = "on-failure"; + RestartSec = 30; + Nice = -1; + }; + wantedBy = [ "multi-user.target" ]; + }; + + systemd.services."${feederConfig.name}-feed" = + let + uuidFile = pkgs.writeTextFile { + name = "${feederConfig.name}-uuid"; + text = "${feederConfig.uuid}"; + }; + connectors = map + ( + connector: "--net-connector ${connector}" + ) + ( + map + ( + connector: ( + lib.concatStringsSep "," ( + [ + connector.primary.host + (toString connector.primary.port) + connector.protocol + ] ++ ( + if connector.silentFail + then + [ + "silent_fail" + ] + else + [ ] + ) ++ ( + if connector.secondary != null + then + [ + connector.secondary.host + (toString connector.secondary.port) + ] + else + [ ] + ) + ) + ) + ) + feederConfig.feed.connectors + ); + options = [ + "--quiet" + "--net" + "--net-only" + "--write-json" + "%t/${feederConfig.name}-feed" + "--net-beast-reduce-interval 0.5" + "--net-heartbeat 60" + "--net-ro-size 1280" + "--net-ro-interval 0.2" + "--net-ro-port 0" + "--net-sbs-port 0" + "--net-bi-port ${toString feederConfig.feed.beastInputPort}" + "--net-bo-port 0" + "--net-ri-port 0" + "--write-json-every 1" + "--lat" + mainConfig.latitude + "--lon" + mainConfig.longitude + "--max-range 450" + "--json-location-accuracy 2" + "--range-outline-hours 24" + "--uuid-file" + "${uuidFile}" + ] ++ connectors; + in + { + enable = feederConfig.feed.enable; + description = "${feederConfig.name} feeder."; + wants = [ "network.target" ]; + after = [ "network.target" ]; + unitConfig = { + Documentation = "https://github.com/widehopf/readsb/"; + StartLimitIntervalSec = 1; + }; + serviceConfig = { + Type = "simple"; + User = "${feederConfig.name}"; + RuntimeDirectory = "${feederConfig.name}-feed"; + RuntimeDirectoryMode = "0755"; + ExecStart = "${self.packages.${pkgs.system}.adsbfi-readsb}/bin/readsb ${lib.concatStringsSep " " options}"; + SyslogIdentifier = "${feederConfig.name}-feed"; + Restart = "on-failure"; + RestartSec = 30; + StartLimitBurst = 100; + Nice = -1; + }; + wantedBy = [ "multi-user.target" ]; + }; + }; +} diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..e56509a --- /dev/null +++ b/flake.lock @@ -0,0 +1,60 @@ +{ + "nodes": { + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1694529238, + "narHash": "sha256-zsNZZGTGnMOf9YpHKJqMSsa0dXbfmxeoJ7xHlrt+xmY=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "ff7b65b44d01cf9ba6a71320833626af21126384", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1697226376, + "narHash": "sha256-cumLLb1QOUtWieUnLGqo+ylNt3+fU8Lcv5Zl+tYbRUE=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "898cb2064b6e98b8c5499f37e81adbdf2925f7c5", + "type": "github" + }, + "original": { + "id": "nixpkgs", + "ref": "nixos-23.05", + "type": "indirect" + } + }, + "root": { + "inputs": { + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..88145b0 --- /dev/null +++ b/flake.nix @@ -0,0 +1,17 @@ +{ + description = "piaware"; + + inputs = { + nixpkgs = { + url = "nixpkgs/nixos-23.05"; + }; + flake-utils = { + url = "github:numtide/flake-utils"; + }; + }; + + outputs = { self, nixpkgs, flake-utils }: { + packages = (import ./packages { inherit self flake-utils nixpkgs; }).packages; + nixosModules = (import ./modules { inherit self; }).nixosModules; + }; +} diff --git a/modules/config/default.nix b/modules/config/default.nix new file mode 100644 index 0000000..af0e8b4 --- /dev/null +++ b/modules/config/default.nix @@ -0,0 +1,823 @@ +{ self, cfg, pkgs, lib, ... }: +let + createMlatService = + (name: feedConfig: + let + uuidFile = pkgs.writeTextFile { + name = "${name}-uuid"; + text = "${feedConfig.uuid}"; + }; + options = [ + "--input-type" + feedConfig.mlat.input.type + "--no-udp" + "--input-connect" + "${feedConfig.mlat.input.host}:${toString feedConfig.mlat.input.port}" + "--server" + feedConfig.mlat.server + "--user" + feedConfig.username + "--lat" + cfg.latitude + "--lon" + cfg.longitude + "--alt" + (toString cfg.altitude) + "--uuid-file" + "${uuidFile}" + ] ++ ( + if feedConfig.mlat.privacy + then + [ + "--privacy" + ] + else + [ ] + ) ++ ( + map (result: "--result ${result}") feedConfig.mlat.results + ); + in + { + enable = feedConfig.mlat.enable; + description = "${name} MLAT client."; + wants = [ "network.target" ]; + after = [ "network.target" ]; + unitConfig = { + Documentation = "https://github.com/makrsmark/mlat-client/"; + }; + serviceConfig = { + Type = "simple"; + User = "${name}"; + RuntimeDirectory = "${name}-mlat"; + RuntimeDirectoryMode = "0755"; + ExecStart = "${self.packages.${pkgs.system}.adsbfi-mlat-client}/bin/mlat-client ${lib.concatStringsSep " " options}"; + SyslogIdentifier = "${name}-mlat"; + Restart = "on-failure"; + RestartSec = 30; + Nice = -1; + }; + wantedBy = [ "multi-user.target" ]; + } + + ); + createFeedService = (name: feedConfig: + let + uuidFile = pkgs.writeTextFile { + name = "${name}-uuid"; + text = "${feedConfig.uuid}"; + }; + connectors = map + ( + connector: "--net-connector ${connector}" + ) + ( + map + ( + connector: ( + lib.concatStringsSep "," ( + [ + connector.primary.host + (toString connector.primary.port) + connector.protocol + ] ++ ( + if connector.silentFail + then + [ + "silent_fail" + ] + else + [ ] + ) ++ ( + if connector.secondary != null + then + [ + connector.secondary.host + (toString connector.secondary.port) + ] + else + [ ] + ) + ) + ) + ) + feedConfig.feed.connectors + ); + options = [ + "--quiet" + "--net" + "--net-only" + "--write-json" + "%t/${name}-feed" + "--net-beast-reduce-interval 0.5" + "--net-heartbeat 60" + "--net-ro-size 1280" + "--net-ro-interval 0.2" + "--net-ro-port 0" + "--net-sbs-port 0" + "--net-bi-port ${toString feedConfig.feed.beastInputPort}" + "--net-bo-port 0" + "--net-ri-port 0" + "--write-json-every 1" + "--lat" + cfg.latitude + "--lon" + cfg.longitude + "--max-range 450" + "--json-location-accuracy 2" + "--range-outline-hours 24" + "--uuid-file" + "${uuidFile}" + ] ++ connectors; + in + { + enable = feedConfig.feed.enable; + description = "ADSB.fi feeder."; + wants = [ "network.target" ]; + after = [ "network.target" ]; + unitConfig = { + Documentation = "https://github.com/widehopf/readsb/"; + StartLimitIntervalSec = 1; + }; + serviceConfig = { + Type = "simple"; + User = "${name}"; + RuntimeDirectory = "${name}-feed"; + RuntimeDirectoryMode = "0755"; + ExecStart = "${self.packages.${pkgs.system}.adsbfi-readsb}/bin/readsb ${lib.concatStringsSep " " options}"; + SyslogIdentifier = "${name}-feed"; + Restart = "on-failure"; + RestartSec = 30; + StartLimitBurst = 100; + Nice = -1; + }; + wantedBy = [ "multi-user.target" ]; + } + ); +in +lib.mkIf cfg.enable { + + assertions = [ + { + assertion = cfg.dump1090.device.type == "rtlsdr"; + message = "not set up for anything but rtlsdr yet"; + } + { + assertion = !(cfg.dump1090.device.type == "rtlsdr" && cfg.dump1090.device.rtlsdr.serial != null && cfg.dump1090.device.rtlsdr.index != null); + message = "rtlsdr device cannot be selected by both serial and index"; + } + { + assertion = !(!cfg.dump978.enable && cfg.skyaware978.enable); + message = "The skyaware978 service cannot be enabled if the dump978 service is disabled."; + } + ]; + + boot.blacklistedKernelModules = [ + "dvb_usb_rtl28xxu" + ]; + + services.udev.packages = [ + pkgs.rtl-sdr + ]; + + users.groups.plugdev = { }; + + users.groups.dump1090 = { }; + users.users.dump1090 = { + isSystemUser = true; + group = "dump1090"; + extraGroups = [ "plugdev" ]; + }; + + users.groups.piaware = { }; + users.users.piaware = { + isSystemUser = true; + group = "piaware"; + }; + + systemd.services."dump1090-fa" = + let + rtlsdrOptions = + if cfg.dump1090.device.type == "rtlsdr" + then + ( + if cfg.dump1090.device.rtlsdr.serial != null + then + [ + "--device-index" + cfg.dump1090.device.rtlsdr.serial + ] + else + [ ] + ) ++ + ( + if cfg.dump1090.device.rtlsdr.index != null + then + [ + "--device-index" + (toString cfg.dump1090.device.rtlsdr.index) + ] + else + [ ] + ) ++ ( + if cfg.dump1090.device.rtlsdr.enableAgc + then + [ + "--enable-agc" + ] + else + [ ] + ) ++ ( + if cfg.dump1090.device.rtlsdr.frequencyCorrection != null + then + [ + "--ppm" + (toString cfg.dump1090.device.rtlsdr.frequencyCorrection) + ] + else + [ ] + ) ++ ( + if cfg.dump1090.device.rtlsdr.directSamplingMode != null + then + [ + "--direct" + (toString cfg.dump1090.device.rtlsdr.directSamplingMode) + ] + else + [ ] + ) + else + [ ]; + adaptiveDynamicRangeOptions = + if cfg.dump1090.adaptiveDynamicRange.enable + then + [ + "--adaptive-range" + ] ++ + ( + if cfg.dump1090.adaptiveDynamicRange.target != null + then + [ "--adaptive-range-target" (toString cfg.dump1090.adaptiveDynamicRange.target) ] + else + [ ] + ) ++ + ( + if cfg.dump1090.adaptiveDynamicRange.alpha != null + then + [ "--adaptive-range-alpha" (toString cfg.dump1090.adaptiveDynamicRange.alpha) ] + else + [ ] + ) ++ + ( + if cfg.dump1090.adaptiveDynamicRange.percentile != null + then + [ "--adaptive-range-percentile" (toString cfg.dump1090.adaptiveDynamicRange.percentile) ] + else + [ ] + ) ++ + ( + if cfg.dump1090.adaptiveDynamicRange.changeDelay != null + then + [ "--adaptive-range-change-delay" (toString cfg.dump1090.adaptiveDynamicRange.changeDelay) ] + else + [ ] + ) ++ + ( + if cfg.dump1090.adaptiveDynamicRange.scanDelay != null + then + [ "--adaptive-range-scan-delay" (toString cfg.dump1090.adaptiveDynamicRange.scanDelay) ] + else + [ ] + ) ++ + ( + if cfg.dump1090.adaptiveDynamicRange.rescanDelay != null + then + [ "--adaptive-range-rescan-delay" (toString cfg.dump1090.adaptiveDynamicRange.rescanDelay) ] + else + [ ] + ) + else + [ ]; + options = [ + "--quiet" + "--device-type" + cfg.dump1090.device.type + ] ++ rtlsdrOptions ++ adaptiveDynamicRangeOptions ++ ( + if cfg.dump1090.device.gain != null + then + [ + "--gain" + (toString cfg.dump1090.device.gain) + ] + else + [ ] + ) ++ ( + if cfg.dump1090.errorCorrection + then + [ "--fix" ] + else + [ ] + ) ++ [ + "--lat" + cfg.latitude + "--lon" + cfg.longitude + ] ++ + ( + if cfg.dump1090.maxRange != null + then + [ + "--max-range" + (toString cfg.dump1090.maxRange) + ] + else + [ ] + ) ++ ( + if cfg.dump1090.network.raw.input.enable + then + [ + "--net-ri-port" + (lib.concatStringsSep "," (map toString cfg.dump1090.network.raw.input.ports)) + ] + else + [ ] + ) ++ ( + if cfg.dump1090.network.raw.output.enable + then + [ + "--net-ro-port" + (lib.concatStringsSep "," (map toString cfg.dump1090.network.raw.output.ports)) + ] + else + [ ] + ) ++ ( + if cfg.dump1090.network.raw.output.size != null + then + [ + "--net-ro-size" + (toString cfg.dump1090.network.raw.output.size) + ] + else + [ ] + ) ++ ( + if cfg.dump1090.network.raw.output.interval != null + then + [ + "--net-ro-interval" + (toString cfg.dump1090.network.raw.output.interval) + ] + else + [ ] + ) ++ + ( + if cfg.dump1090.network.baseStation.enable + then + [ + "--net-sbs-port" + (lib.concatStringsSep "," (map toString cfg.dump1090.network.baseStation.ports)) + ] + else + [ ] + ) ++ + ( + if cfg.dump1090.network.beast.input.enable + then + [ + "--net-bi-port" + (lib.concatStringsSep "," (map toString cfg.dump1090.network.beast.input.ports)) + ] + else [ ] + ) ++ + ( + if cfg.dump1090.network.beast.output.enable + then + [ + "--net-bo-port" + (lib.concatStringsSep "," (map toString cfg.dump1090.network.beast.output.ports)) + ] + else [ ] + ) ++ [ + "--json-location-accuracy" + (toString cfg.dump1090.jsonLocationAccuracy) + "--write-json" + "%t/dump1090-fa" + ] ++ cfg.dump1090.extraOptions; + in + { + enable = cfg.dump1090.enable; + description = "dump1090 ADS-B receiver (FlightAware customization)"; + wants = [ "network.target" ]; + after = [ "network.target" ]; + unitConfig = { + Documentation = "https://flightaware.com/adsb/piaware/"; + }; + serviceConfig = { + Type = "simple"; + User = "dump1090"; + RuntimeDirectory = "dump1090-fa"; + RuntimeDirectoryMode = "0755"; + ExecStart = "${self.packages.${pkgs.system}.dump1090-fa}/bin/dump1090 ${lib.concatStringsSep " " options}"; + SyslogIdentifier = "dump1090-fa"; + Restart = "on-failure"; + RestartSec = 30; + RestartPreventExitStatus = "64"; + Nice = -5; + }; + wantedBy = [ "multi-user.target" ]; + }; + + users.groups.skyaware = { }; + users.users.skyaware = { + isSystemUser = true; + group = "skyaware"; + }; + + systemd.services."skyaware978" = { + enable = cfg.skyaware978.enable; + description = "skyaware978 ADS-B UAT web display"; + wants = [ "network.target" ]; + after = [ "network.target" "dump978-fa.service" ]; + unitConfig = { + Documentation = "https://flightaware.com/adsb/piaware/"; + }; + serviceConfig = { + Type = "simple"; + User = "skyaware"; + RuntimeDirectory = "skyaware978"; + RuntimeDirectoryMode = "0755"; + ExecStart = "${self.packages.${pkgs.system}.dump978-fa}/bin/skyaware978 --lat ${cfg.latitude} --lon ${cfg.longitude} --json-dir %t/skyaware978"; + SyslogIdentifier = "skyaware978"; + Restart = "on-failure"; + RestartSec = 30; + RestartPreventExitStatus = "64"; + }; + wantedBy = [ "dump978-fa.service" ]; + }; + + users.groups.adsbexchange = { }; + users.users.adsbexchange = { + isSystemUser = true; + group = "adsbexchange"; + }; + + systemd.services."adsbexchange-mlat" = + let + uuidFile = pkgs.writeTextFile { + name = "adsbx-uuid"; + text = "${cfg.adsbexchange.uuid}"; + }; + options = [ + "--input-type" + cfg.adsbexchange.mlat.input.type + "--no-udp" + "--input-connect" + "${cfg.adsbexchange.mlat.input.host}:${toString cfg.adsbexchange.mlat.input.port}" + "--server" + cfg.adsbexchange.mlat.server + "--user" + cfg.adsbexchange.username + "--lat" + cfg.latitude + "--lon" + cfg.longitude + "--alt" + (toString cfg.altitude) + "--uuid-file" + "${uuidFile}" + ] ++ ( + if cfg.adsbexchange.mlat.privacy + then + [ + "--privacy" + ] + else + [ ] + ) ++ ( + map (result: "--result ${result}") cfg.adsbexchange.mlat.results + ); + in + { + enable = cfg.adsbexchange.mlat.enable; + description = "ADS-B Exchange MLAT client."; + wants = [ "network.target" ]; + after = [ "network.target" ]; + unitConfig = { + Documentation = "https://github.com/makrsmark/mlat-client/"; + }; + serviceConfig = { + Type = "simple"; + User = "adsbexchange"; + RuntimeDirectory = "adsbexchange-mlat"; + RuntimeDirectoryMode = "0755"; + ExecStart = "${self.packages.${pkgs.system}.adsbfi-mlat-client}/bin/mlat-client ${lib.concatStringsSep " " options}"; + SyslogIdentifier = "adsbexchange-mlat"; + Restart = "on-failure"; + RestartSec = 30; + Nice = -1; + }; + wantedBy = [ "multi-user.target" ]; + }; + + systemd.services."adsbexchange-feed" = + let + uuidFile = pkgs.writeTextFile { + name = "adsbx-uuid"; + text = "${cfg.adsbexchange.uuid}"; + }; + options = [ + "--quiet" + "--net" + "--net-only" + "--write-json" + "%t/adsbexchange-feed" + "--net-beast-reduce-interval 0.5" + "--net-connector feed1.adsbexchange.com,30004,beast_reduce_out,feed2.adsbexchange.com,64004" + "--net-heartbeat 60" + "--net-ro-size 1280" + "--net-ro-interval 0.2" + "--net-ro-port 0" + "--net-sbs-port 0" + "--net-bi-port ${toString cfg.adsbexchange.feed.beastInputPort}" + "--net-bo-port 0" + "--net-ri-port 0" + "--write-json-every 1" + "--lat" + cfg.latitude + "--lon" + cfg.longitude + "--max-range 450" + "--json-location-accuracy 2" + "--range-outline-hours 24" + "--uuid-file" + "${uuidFile}" + "--net-connector 127.0.0.1,30978,uat_in,silent_fail" + "--net-connector 127.0.0.1,30005,beast_in,silent_fail" + ]; + in + { + enable = cfg.adsbexchange.feed.enable; + description = "ADS-B Exchange feeder."; + wants = [ "network.target" ]; + after = [ "network.target" ]; + unitConfig = { + Documentation = "https://github.com/wiedehopf/readsb/"; + StartLimitIntervalSec = 1; + }; + serviceConfig = { + Type = "simple"; + User = "adsbexchange"; + RuntimeDirectory = "adsbexchange-feed"; + RuntimeDirectoryMode = "0755"; + ExecStart = "${self.packages.${pkgs.system}.adsbfi-readsb}/bin/readsb ${lib.concatStringsSep " " options}"; + SyslogIdentifier = "adsbexchange-feed"; + Restart = "on-failure"; + RestartSec = 30; + StartLimitBurst = 100; + Nice = -1; + }; + wantedBy = [ "multi-user.target" ]; + }; + + ## + ## ADSB.fi + ## + + users.groups.adsbfi = { }; + users.users.adsbfi = { + isSystemUser = true; + group = "adsbexchange"; + }; + + systemd.services."adsbfi-mlat" = + let + uuidFile = pkgs.writeTextFile { + name = "adsbfi-uuid"; + text = "${cfg.adsbfi.uuid}"; + }; + options = [ + "--input-type" + cfg.adsbfi.mlat.input.type + "--no-udp" + "--input-connect" + "${cfg.adsbfi.mlat.input.host}:${toString cfg.adsbfi.mlat.input.port}" + "--server" + cfg.adsbfi.mlat.server + "--user" + cfg.adsbfi.username + "--lat" + cfg.latitude + "--lon" + cfg.longitude + "--alt" + (toString cfg.altitude) + "--uuid-file" + "${uuidFile}" + ] ++ ( + if cfg.adsbfi.mlat.privacy + then + [ + "--privacy" + ] + else + [ ] + ) ++ ( + map (result: "--result ${result}") cfg.adsbfi.mlat.results + ); + in + { + enable = cfg.adsbfi.mlat.enable; + description = "ADSB.fi MLAT client."; + wants = [ "network.target" ]; + after = [ "network.target" ]; + unitConfig = { + Documentation = "https://github.com/makrsmark/mlat-client/"; + }; + serviceConfig = { + Type = "simple"; + User = "adsbfi"; + RuntimeDirectory = "adsbfi-mlat"; + RuntimeDirectoryMode = "0755"; + ExecStart = "${self.packages.${pkgs.system}.adsbfi-mlat-client}/bin/mlat-client ${lib.concatStringsSep " " options}"; + SyslogIdentifier = "adsbfi-mlat"; + Restart = "on-failure"; + RestartSec = 30; + Nice = -1; + }; + wantedBy = [ "multi-user.target" ]; + }; + + systemd.services."adsbfi-feed" = + let + uuidFile = pkgs.writeTextFile { + name = "adsbfi-uuid"; + text = "${cfg.adsbfi.uuid}"; + }; + connectors = map + ( + connector: "--net-connector ${connector}" + ) + ( + map + ( + connector: ( + lib.concatStringsSep "," ( + [ + connector.primary.host + (toString connector.primary.port) + connector.protocol + ] ++ ( + if connector.silentFail + then + [ + "silent_fail" + ] + else + [ ] + ) ++ ( + if connector.secondary != null + then + [ + connector.secondary.host + (toString connector.secondary.port) + ] + else + [ ] + ) + ) + ) + ) + cfg.adsbfi.feed.connectors + ); + options = [ + "--quiet" + "--net" + "--net-only" + "--write-json" + "%t/adsbfi-feed" + "--net-beast-reduce-interval 0.5" + "--net-heartbeat 60" + "--net-ro-size 1280" + "--net-ro-interval 0.2" + "--net-ro-port 0" + "--net-sbs-port 0" + "--net-bi-port ${toString cfg.adsbfi.feed.beastInputPort}" + "--net-bo-port 0" + "--net-ri-port 0" + "--write-json-every 1" + "--lat" + cfg.latitude + "--lon" + cfg.longitude + "--max-range 450" + "--json-location-accuracy 2" + "--range-outline-hours 24" + "--uuid-file" + "${uuidFile}" + ] ++ connectors; + in + { + enable = cfg.adsbfi.feed.enable; + description = "ADSB.fi feeder."; + wants = [ "network.target" ]; + after = [ "network.target" ]; + unitConfig = { + Documentation = "https://github.com/widehopf/readsb/"; + StartLimitIntervalSec = 1; + }; + serviceConfig = { + Type = "simple"; + User = "adsbfi"; + RuntimeDirectory = "adsbfi-feed"; + RuntimeDirectoryMode = "0755"; + ExecStart = "${self.packages.${pkgs.system}.adsbfi-readsb}/bin/readsb ${lib.concatStringsSep " " options}"; + SyslogIdentifier = "adsbfi-feed"; + Restart = "on-failure"; + RestartSec = 30; + StartLimitBurst = 100; + Nice = -1; + }; + wantedBy = [ "multi-user.target" ]; + }; + + ## + ## theairtraffic.com + ## + + users.groups.theairtraffic = { }; + users.users.theairtraffic = { + isSystemUser = true; + group = "theairtraffic"; + }; + + systemd.services."theairtraffic-mlat" = createMlatService "theairtraffic" cfg.theairtraffic; + systemd.services."theairtraffic-feed" = createFeedService "theairtraffic" cfg.theairtraffic; + + systemd.tmpfiles.rules = [ + "d /var/cache/piaware 0755 piaware piaware - -" + ]; + + systemd.services."piaware" = + let + config = pkgs.writeTextFile { + name = "piaware.conf"; + text = '' + # https://flightaware.com/adsb/piaware/advanced_configuration + allow-auto-updates no + allow-manual-updates no + allow-mlat ${if cfg.piaware.allowMLAT then "yes" else "no"} + allow-modeac ${if cfg.piaware.allowModeAC then "yes" else "no"} + feeder-id ${cfg.piaware.feederId} + ''; + }; + in + { + enable = cfg.piaware.enable; + description = "FlightAware ADS-B uploader"; + wants = [ "network-online.target" ]; + after = [ "dump1090-fa.service" "network-online.target" "time-sync.target" ]; + unitConfig = { + Documentation = "https://flightaware.com/adsb/piaware/"; + }; + serviceConfig = { + Type = "simple"; + User = "piaware"; + RuntimeDirectory = "piaware"; + ExecStart = "${self.packages.${pkgs.system}.piaware}/bin/piaware -p %t/piaware/piaware.pid -plainlog -configfile ${config} -statusfile %t/piaware/status.json"; + ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID"; + Restart = "on-failure"; + RestartPreventExitStatus = "4 6"; + }; + wantedBy = [ "multi-user.target" ]; + reloadTriggers = [ + "/etc/piaware.conf" + ]; + }; + + services.nginx = { + enable = true; + virtualHosts = { + "_" = { + root = self.packages.${pkgs.system}.dump1090-fa.html; + locations = { + "=/status.json" = { + alias = "/run/piaware/status.json"; + extraConfig = '' + add_header Access-Control-Allow-Origin *; + ''; + }; + "/data/" = { + alias = "/run/dump1090-fa/"; + extraConfig = '' + add_header Access-Control-Allow-Origin *; + ''; + }; + "/data-978/" = { + root = "/run/skyaware978/"; + extraConfig = '' + add_header Access-Control-Allow-Origin *; + ''; + }; + }; + }; + }; + }; +} diff --git a/modules/default.nix b/modules/default.nix new file mode 100644 index 0000000..98360a1 --- /dev/null +++ b/modules/default.nix @@ -0,0 +1,12 @@ +{ self, ... }: { + nixosModules = { + adsb = { config, lib, pkgs, ... }: + let + cfg = config.adsb; + in + { + options = import ./options { inherit self cfg lib; }; + config = import ./config { inherit self cfg lib pkgs; }; + }; + }; +} diff --git a/modules/options/adsbexchange.nix b/modules/options/adsbexchange.nix new file mode 100644 index 0000000..1251e7a --- /dev/null +++ b/modules/options/adsbexchange.nix @@ -0,0 +1,96 @@ +{ cfg, lib, netConnector, UUID }: +lib.options.mkOption { + type = lib.types.submodule { + options = { + username = lib.options.mkOption { + type = lib.types.nullOr (lib.types.addCheck lib.types.str ( + username: (builtins.match ''^[a-zA-Z0-9_-]+$'' username) != null + )); + default = null; + example = "'william34-london' or 'william34-jersey'"; + description = "A unique name to be shown on the ADS-B Exchange MLAT map (map.adsbexchange.com/mlat-map)"; + }; + uuid = lib.options.mkOption { + type = UUID; + }; + mlat = lib.options.mkOption { + type = lib.types.submodule { + options = { + enable = lib.options.mkOption { + type = lib.types.bool; + default = false; + description = "Enable ADS-B Exchange MLAT client."; + }; + input = lib.options.mkOption { + type = lib.types.submodule { + options = { + host = lib.options.mkOption { + type = lib.types.str; + default = "127.0.0.1"; + example = "127.0.0.1"; + description = ""; + }; + port = lib.options.mkOption { + type = lib.types.port; + default = 30005; + example = 30005; + description = ""; + }; + type = lib.options.mkOption { + type = lib.types.enum [ "dump1090" "radarcape_gps" ]; + default = "dump1090"; + example = "dump1090"; + description = ""; + }; + }; + }; + default = { }; + }; + reduceInterval = lib.options.mkOption { + type = lib.types.float; + default = 0.5; + example = "0.5"; + description = ""; + }; + results = lib.options.mkOption { + type = lib.types.listOf lib.types.str; + default = [ + "beast,connect,127.0.0.1:${toString cfg.adsbexchange.feed.beastInputPort}" + ]; + description = "Where to send results of MLAT computations."; + }; + privacy = lib.options.mkOption { + type = lib.types.bool; + default = false; + description = "Sets the privacy flag for this receiver. Currently, this removes the receiver location pin from the coverage maps."; + }; + server = lib.options.mkOption { + type = lib.types.str; + default = "feed.adsbexchange.com:31090"; + example = "feed.adsbexchange.com:31090"; + description = "Server to feed MLAT data to"; + }; + }; + }; + default = { }; + }; + feed = lib.options.mkOption { + type = lib.types.submodule { + options = { + enable = lib.options.mkOption { + type = lib.types.bool; + default = false; + description = "Enable ADS-B Exchange feeder."; + }; + beastInputPort = lib.options.mkOption { + type = lib.types.port; + default = 30154; + description = "Port to accept Beast messages on."; + }; + }; + }; + }; + }; + }; + default = { }; +} diff --git a/modules/options/adsbfi.nix b/modules/options/adsbfi.nix new file mode 100644 index 0000000..62da18d --- /dev/null +++ b/modules/options/adsbfi.nix @@ -0,0 +1,144 @@ +## +## ADSB.fi configuration options +## + +{ cfg, lib, netConnector, UUID }: +lib.options.mkOption { + type = lib.types.submodule { + options = { + + username = lib.options.mkOption { + type = lib.types.nullOr (lib.types.addCheck lib.types.str ( + username: (builtins.match ''^[a-zA-Z0-9_-]+$'' username) != null + )); + default = null; + example = "'william34-london' or 'william34-jersey'"; + description = "A unique name to be shown on the ADSB.fi MLAT map"; + }; + + uuid = lib.options.mkOption { + type = UUID; + }; + + mlat = lib.options.mkOption { + type = lib.types.submodule { + options = { + + enable = lib.options.mkOption { + type = lib.types.bool; + default = false; + description = "Enable ADSD.fi MLAT client."; + }; + + input = lib.options.mkOption { + type = lib.types.submodule { + options = { + host = lib.options.mkOption { + type = lib.types.str; + default = "127.0.0.1"; + example = "127.0.0.1"; + description = ""; + }; + port = lib.options.mkOption { + type = lib.types.port; + default = 30005; + example = 30005; + description = ""; + }; + type = lib.options.mkOption { + type = lib.types.enum [ + "dump1090" + "radarcape_gps" + ]; + default = "dump1090"; + example = "dump1090"; + description = ""; + }; + }; + }; + default = { }; + }; + + reduceInterval = lib.options.mkOption { + type = lib.types.float; + default = 0.5; + example = "0.5"; + description = ""; + }; + results = lib.options.mkOption { + type = lib.types.listOf lib.types.str; + default = [ + "beast,connect,127.0.0.1:${toString cfg.adsbfi.feed.beastInputPort}" + ]; + description = ""; + }; + + privacy = lib.options.mkOption { + type = lib.types.bool; + default = false; + description = "Sets the privacy flag for this receiver. Currently, this removes the receiver location pin from the coverage maps."; + }; + + server = lib.options.mkOption { + type = lib.types.str; + default = "feed.adsb.fi:31090"; + example = "feed.adsb.fi:31090"; + description = "Server to feed MLAT data to"; + }; + }; + }; + }; + + feed = lib.options.mkOption { + type = lib.types.submodule { + options = { + enable = lib.options.mkOption { + type = lib.types.bool; + default = false; + description = "Enable ADSB.fi feeder."; + }; + + beastInputPort = lib.options.mkOption { + type = lib.types.port; + default = 30155; + description = "Port to accept Beast messages on."; + }; + + connectors = lib.options.mkOption { + type = lib.types.listOf netConnector; + default = [ + { + protocol = "uat_in"; + primary = { + host = "127.0.0.1"; + port = 30978; + }; + silentFail = true; + } + { + protocol = "beast_in"; + primary = { + host = "127.0.0.1"; + port = 30005; + }; + silentFail = true; + } + { + protocol = "beast_reduce_out"; + primary = { + host = "feed.adsb.fi"; + port = 30004; + }; + secondary = { + host = "feed.adsb.fi"; + port = 64004; + }; + } + ]; + }; + }; + }; + }; + }; + }; +} diff --git a/modules/options/default.nix b/modules/options/default.nix new file mode 100644 index 0000000..74a1e16 --- /dev/null +++ b/modules/options/default.nix @@ -0,0 +1,95 @@ +{ cfg, lib, ... }: +let + hostPort = lib.types.submodule { + options = { + + host = lib.options.mkOption { + type = lib.types.str; + }; + + port = lib.options.mkOption { + type = lib.types.port; + }; + + }; + }; + netConnector = lib.types.submodule { + options = { + protocol = lib.options.mkOption { + type = lib.types.enum [ + "beast_in" + "beast_out" + "beast_reduce_out" + "beast_reduce_plus_out" + "gpsd_in" + "json_out" + "raw_in" + "raw_out" + "sbs_in" + "sbs_in_jaero" + "sbs_in_mlat" + "sbs_out" + "sbs_out_jaero" + "sbs_out_mlat" + "sbs_out_replay" + "uat_in" + "vrs_out" + ]; + }; + primary = lib.options.mkOption { + type = hostPort; + }; + silentFail = lib.options.mkOption { + type = lib.types.bool; + default = false; + }; + secondary = lib.options.mkOption { + type = lib.types.nullOr hostPort; + default = null; + }; + }; + }; + UUID = lib.types.addCheck lib.types.str ( + uuid: (builtins.match ''^[A-F0-9a-f]{8}-[A-F0-9a-f]{4}-[A-F0-9a-f]{4}-[A-F0-9a-f]{4}-[A-F0-9a-f]{12}$'' uuid) != null + ); +in +{ + adsb = lib.options.mkOption { + type = lib.types.submodule { + options = { + + enable = lib.options.mkEnableOption "ADS-B"; + + latitude = lib.options.mkOption { + type = lib.types.addCheck lib.types.str ( + lat: + (builtins.match ''^([+-])?[0-9]+(\.[0-9]{5,})?$'' lat) != null && (builtins.fromJSON lat) >= -90.0 && (builtins.fromJSON lat) <= 90.0 + ); + description = "Latitude of the receiver expressed in degrees with at least 5 decimal places. For MLAT the precise location of your antenna is required. A small error of 15m/45ft will cause issues with MLAT!"; + }; + + longitude = lib.options.mkOption { + type = lib.types.addCheck lib.types.str ( + lon: + (builtins.match ''^([+-])?[0-9]+(\.[0-9]{5,})?$'' lon) != null && (builtins.fromJSON lon) >= -180.0 && (builtins.fromJSON lon) <= 180.0 + ); + description = "Longitude of the receiver expressed in degrees with at least 5 decimal places. For MLAT the precise location of your antenna is required. A small error of 15m/45ft will cause issues with MLAT!"; + }; + + altitude = lib.options.mkOption { + type = lib.types.float; + description = "The altitude if the receiver above sea level (in meters)."; + }; + + dump1090 = import ./dump1090.nix { inherit lib; }; + dump978 = import ./dump978.nix { inherit lib; }; + skyaware978 = import ./skyaware978.nix { inherit lib; }; + + adsbexchange = import ./adsbexchange.nix { inherit cfg lib netConnector UUID; }; + adsbfi = import ./adsbfi.nix { inherit cfg lib netConnector UUID; }; + piaware = import ./piaware.nix { inherit lib UUID; }; + theairtraffic = import ./theairtraffic.nix { inherit cfg lib netConnector UUID; }; + }; + }; + }; +} diff --git a/modules/options/dump1090.nix b/modules/options/dump1090.nix new file mode 100644 index 0000000..1bacdd0 --- /dev/null +++ b/modules/options/dump1090.nix @@ -0,0 +1,293 @@ +{ lib }: +lib.options.mkOption { + type = lib.types.submodule { + options = { + + enable = lib.options.mkEnableOption "Dump1090"; + + errorCorrection = lib.options.mkOption { + type = lib.types.bool; + default = true; + }; + + maxRange = lib.options.mkOption { + type = lib.types.addCheck lib.types.int (x: x >= 0); + default = 360; + description = "Absolute maximum range for position decoding (in NM)"; + }; + + jsonLocationAccuracy = lib.options.mkOption { + type = lib.types.enum [ 0 1 2 ]; + default = 2; + description = "Accuracy of receiver location in json metadata (0=no location, 1=approximate, 2=exact)"; + }; + + adaptiveDynamicRange = lib.options.mkOption { + type = lib.types.submodule { + options = { + + enable = lib.options.mkOption { + type = lib.types.bool; + default = true; + description = "Adjust gain for target dynamic range"; + }; + + target = lib.options.mkOption { + type = lib.types.nullOr lib.types.int; + default = null; + description = "Set target dynamic range in dB"; + }; + + alpha = lib.options.mkOption { + type = lib.types.nullOr (lib.types.addCheck lib.types.float (alpha: alpha >= 0.0 && alpha <= 1.0)); + default = null; + description = "Set dynamic range noise smoothing factor (0..1, smaller=more smoothing)"; + }; + + percentile = lib.options.mkOption { + type = lib.types.nullOr (lib.types.addCheck lib.types.int (x: x >= 0 && x <= 100)); + default = null; + description = "Set dynamic range noise percentile"; + }; + + changeDelay = lib.options.mkOption { + type = lib.types.nullOr (lib.types.addCheck lib.types.int (x: x >= 0)); + default = null; + description = "Set delay after changing gain before resuming dynamic range control (seconds)"; + }; + + scanDelay = lib.options.mkOption { + type = lib.types.nullOr (lib.types.addCheck lib.types.int (x: x >= 0)); + default = null; + description = "Set scan interval for dynamic range gain scanning following a gain decrease due to an increase in noise (seconds)"; + }; + + rescanDelay = lib.options.mkOption { + type = lib.types.nullOr (lib.types.addCheck lib.types.int (x: x >= 0)); + default = null; + description = "Set periodic rescan interval for dynamic range gain scanning (seconds)"; + }; + }; + }; + default = { }; + description = "Configuration options for adaptive dynamic range."; + }; + + device = lib.options.mkOption { + type = lib.types.submodule { + options = { + + type = lib.options.mkOption { + type = lib.types.enum [ "rtlsdr" "bladerf" "hackrf" "limesdr" ]; + default = "rtlsdr"; + }; + + rtlsdr = lib.options.mkOption { + type = lib.types.submodule { + options = { + + serial = lib.options.mkOption { + type = lib.types.nullOr lib.types.str; + default = null; + description = "select device by serial"; + }; + + index = lib.options.mkOption { + type = lib.types.nullOr lib.types.int; + default = null; + description = "select device by index"; + }; + + enableAgc = lib.options.mkOption { + type = lib.types.bool; + default = false; + description = "enable digital AGC (not tuner AGC!)"; + }; + + frequencyCorrection = lib.options.mkOption { + type = lib.types.nullOr lib.types.int; + default = null; + description = "set oscillator frequency correction in PPM"; + }; + + directSamplingMode = lib.options.mkOption { + type = lib.types.nullOr (lib.types.enum [ 0 1 2 ]); + default = null; + description = "set direct sampling mode"; + }; + }; + }; + }; + + gain = lib.options.mkOption { + type = lib.types.nullOr lib.types.int; + default = null; + description = "Set gain in dB (default: varies by SDR type)"; + }; + + frequency = lib.options.mkOption { + type = lib.types.nullOr lib.types.int; + default = null; + description = "Set frequency (default: 1090 Mhz)"; + }; + }; + }; + }; + + network = lib.options.mkOption { + type = lib.types.submodule { + options = { + + raw = lib.options.mkOption { + type = lib.types.submodule { + options = { + + input = lib.options.mkOption { + type = lib.types.submodule { + options = { + + enable = lib.options.mkOption { + type = lib.types.bool; + default = false; + description = "Enable TCP raw input"; + }; + + ports = lib.options.mkOption { + type = lib.types.listOf lib.types.port; + default = [ 30001 ]; + description = "TCP raw input listen ports (default: 30001)"; + }; + }; + }; + default = { }; + }; + + output = lib.options.mkOption { + type = lib.types.submodule { + options = { + + enable = lib.options.mkOption { + type = lib.types.bool; + default = true; + description = "Enable TCP raw output"; + }; + + ports = lib.options.mkOption { + type = lib.types.listOf lib.types.port; + default = [ 30002 ]; + description = "TCP raw output listen ports (default: 30002)"; + }; + + size = lib.options.mkOption { + type = lib.types.nullOr (lib.types.addCheck lib.types.int (x: x >= 0)); + default = null; + description = "TCP output minimum size (default: 0)"; + }; + + interval = lib.options.mkOption { + type = lib.types.nullOr (lib.types.addCheck lib.types.int (x: x >= 0)); + default = null; + description = "TCP output memory flush rate in seconds (default: 0)"; + }; + }; + }; + default = { }; + }; + }; + }; + default = { }; + }; + + baseStation = lib.options.mkOption { + type = lib.types.submodule { + options = { + + enable = lib.options.mkOption { + type = lib.types.bool; + default = true; + }; + + ports = lib.options.mkOption { + type = lib.types.listOf lib.types.port; + default = [ 30003 ]; + }; + }; + }; + default = { }; + }; + + beast = lib.options.mkOption { + type = lib.types.submodule { + options = { + + input = lib.options.mkOption { + type = lib.types.submodule { + options = { + + enable = lib.options.mkOption { + type = lib.types.bool; + default = true; + }; + + ports = lib.options.mkOption { + type = lib.types.listOf lib.types.port; + default = [ 30004 30104 ]; + }; + }; + }; + default = { }; + }; + + output = lib.options.mkOption { + type = lib.types.submodule { + options = { + + enable = lib.options.mkOption { + type = lib.types.bool; + default = true; + }; + + ports = lib.options.mkOption { + type = lib.types.listOf lib.types.port; + default = [ 30005 ]; + }; + + }; + }; + default = { }; + }; + + stratux = lib.options.mkOption { + type = lib.types.submodule { + options = { + + enable = lib.options.mkOption { + type = lib.types.bool; + default = false; + }; + + ports = lib.options.mkOption { + type = lib.types.listOf lib.types.port; + default = [ ]; + }; + + }; + }; + default = { }; + }; + }; + }; + default = { }; + }; + }; + }; + default = { }; + }; + + extraOptions = lib.options.mkOption { + type = lib.types.listOf lib.types.str; + default = [ ]; + }; + }; + }; +} diff --git a/modules/options/dump978.nix b/modules/options/dump978.nix new file mode 100644 index 0000000..c8a08d7 --- /dev/null +++ b/modules/options/dump978.nix @@ -0,0 +1,15 @@ +{ lib }: +lib.options.mkOption { + type = lib.types.submodule { + options = { + + enable = lib.options.mkOption { + type = lib.types.bool; + default = false; + description = "Enable the dump978 service."; + }; + + }; + }; + default = { }; +} diff --git a/modules/options/piaware.nix b/modules/options/piaware.nix new file mode 100644 index 0000000..1853fbc --- /dev/null +++ b/modules/options/piaware.nix @@ -0,0 +1,26 @@ +{ lib, UUID }: +lib.options.mkOption { + type = lib.types.submodule { + options = { + enable = lib.options.mkEnableOption "PiAware"; + + feederId = lib.options.mkOption { + type = lib.types.nullOr UUID; + default = null; + description = "PiAware feeder ID"; + }; + + allowMLAT = lib.options.mkOption { + type = lib.types.bool; + default = true; + description = "Should PiAware enable multilateration where possible? You may need to disable this if multilateration overloads your receiver"; + }; + + allowModeAC = lib.options.mkOption { + type = lib.types.bool; + default = true; + description = "Should PiAware enable reception of Mode A/C messages when requested? You may need to disable this if processing Mode A/C overloads your receiver."; + }; + }; + }; +} diff --git a/modules/options/skyaware978.nix b/modules/options/skyaware978.nix new file mode 100644 index 0000000..23ae561 --- /dev/null +++ b/modules/options/skyaware978.nix @@ -0,0 +1,15 @@ +{ lib }: +lib.options.mkOption { + type = lib.types.submodule { + options = { + + enable = lib.options.mkOption { + type = lib.types.bool; + default = false; + description = "Enable the skyaware978 service."; + }; + + }; + }; + default = { }; +} diff --git a/modules/options/theairtraffic.nix b/modules/options/theairtraffic.nix new file mode 100644 index 0000000..0204407 --- /dev/null +++ b/modules/options/theairtraffic.nix @@ -0,0 +1,156 @@ +## +## theairtraffic.com +## + +{ cfg, lib, netConnector, UUID }: lib.options.mkOption { + type = lib.types.submodule { + options = { + + name = lib.options.mkOption { + type = lib.types.enum [ + "adsbexchange" + "adsbfi" + "theairtraffic" + ]; + }; + + username = lib.options.mkOption { + type = lib.types.nullOr (lib.types.addCheck lib.types.str ( + username: (builtins.match ''^[a-zA-Z0-9_-]+$'' username) != null + )); + default = null; + example = "'william34-london' or 'william34-jersey'"; + description = "A unique name to be shown on the MLAT map"; + }; + + uuid = lib.options.mkOption { + type = UUID; + }; + + mlat = lib.options.mkOption { + type = lib.types.submodule { + options = { + + enable = lib.options.mkOption { + type = lib.types.bool; + default = false; + description = "Enable MLAT client."; + }; + + input = lib.options.mkOption { + type = lib.types.submodule { + options = { + + host = lib.options.mkOption { + type = lib.types.str; + default = "127.0.0.1"; + example = "127.0.0.1"; + description = ""; + }; + + port = lib.options.mkOption { + type = lib.types.port; + default = 30005; + example = 30005; + description = ""; + }; + + type = lib.options.mkOption { + type = lib.types.enum [ + "dump1090" + "radarcape_gps" + ]; + default = "dump1090"; + example = "dump1090"; + description = ""; + }; + + }; + }; + default = { }; + }; + + reduceInterval = lib.options.mkOption { + type = lib.types.float; + default = 0.5; + example = "0.5"; + description = ""; + }; + + results = lib.options.mkOption { + type = lib.types.listOf lib.types.str; + default = [ + "beast,connect,127.0.0.1:${toString cfg.theairtraffic.feed.beastInputPort}" + ]; + description = ""; + }; + + privacy = lib.options.mkOption { + type = lib.types.bool; + default = false; + description = '' + Sets the privacy flag for this receiver. Currently, this removes the receiver + location pin from the coverage maps. + ''; + }; + + server = lib.options.mkOption { + type = lib.types.str; + default = "feed.theairtraffic.com:31090"; + example = "feed.theairtraffic.com:31090"; + description = "Server to feed MLAT data to"; + }; + }; + }; + }; + + feed = lib.options.mkOption { + type = lib.types.submodule { + options = { + + enable = lib.options.mkOption { + type = lib.types.bool; + default = false; + description = "Enable theairtraffic.com feeder."; + }; + + beastInputPort = lib.options.mkOption { + type = lib.types.port; + default = 30156; + description = "Port to accept Beast messages on."; + }; + + connectors = lib.options.mkOption { + type = lib.types.listOf netConnector; + default = [ + { + protocol = "uat_in"; + primary = { + host = "127.0.0.1"; + port = 30978; + }; + silentFail = true; + } + { + protocol = "beast_in"; + primary = { + host = "127.0.0.1"; + port = 30005; + }; + silentFail = true; + } + { + protocol = "beast_reduce_out"; + primary = { + host = "feed.theairtraffic.com"; + port = 30004; + }; + } + ]; + }; + }; + }; + }; + }; + }; +} diff --git a/packages/adsbexchange-mlat-client.nix b/packages/adsbexchange-mlat-client.nix new file mode 100644 index 0000000..abf4cef --- /dev/null +++ b/packages/adsbexchange-mlat-client.nix @@ -0,0 +1,22 @@ +{ pkgs, ... }: +let + pname = "adsbexchange-mlat-client"; + version = "0.4.2"; +in +pkgs.python3.pkgs.buildPythonApplication { + inherit pname version; + src = pkgs.fetchFromGitHub { + owner = "ADSBexchange"; + repo = "mlat-client"; + rev = "faf9638fe8c2eafc2abdc45621ff879c7acb882b"; + sha256 = "sha256-V//LpYmBXtT8haX1aZ4XldzzyUY2YN7x3lTpQ2csTmw="; + }; + meta = with pkgs.lib; { + homepage = "https://github.com/adsbexchange/mlat-client"; + description = "ADS-B Exchange Mode S multilateration client"; + longDescription = '' + This is a client that selectively forwards Mode S messages to a server that resolves the transmitter position by multilateration of the same message received by multiple clients. + ''; + license = licenses.gpl3Plus; + }; +} diff --git a/packages/adsbexchange-readsb.nix b/packages/adsbexchange-readsb.nix new file mode 100644 index 0000000..4833c8a --- /dev/null +++ b/packages/adsbexchange-readsb.nix @@ -0,0 +1,40 @@ +{ pkgs, ... }: +let + pname = "adsbexchange-readsb"; + version = "3.14.1597"; +in +pkgs.stdenv.mkDerivation { + inherit pname version; + src = pkgs.fetchFromGitHub { + owner = "adsbexchange"; + repo = "readsb"; + rev = "61c6bb02ae1af04c805e2607bfec5dbfea4c6515"; + sha256 = "sha256-LXIib8mfgFMINXCYHxfnhdGI5WjJsCNq0LV+i+Kp4r8="; + }; + nativeBuildInputs = [ + pkgs.pkg-config + ]; + propagatedBuildInputs = [ + pkgs.libad9361 + pkgs.libbladeRF + pkgs.libiio + pkgs.librtlsdr + pkgs.ncurses + pkgs.zlib + pkgs.zstd + ]; + buildFlags = [ + "READSB_VERSION=v${version}" + "RTLSDR=yes" + "BLADERF=yes" + "PLUTOSDR=yes" + ]; + installPhase = '' + runHook preInstall + mkdir -p $out/bin + install -D readsb $out/bin + install -D viewadsb $out/bin + ln -s readsb $out/bin/feed-adsbx + runHook postInstall + ''; +} diff --git a/packages/adsbfi-mlat-client.nix b/packages/adsbfi-mlat-client.nix new file mode 100644 index 0000000..4ee767a --- /dev/null +++ b/packages/adsbfi-mlat-client.nix @@ -0,0 +1,22 @@ +{ pkgs, ... }: +let + pname = "adsbfi-mlat-client"; + version = "0.4.2"; +in +pkgs.python3.pkgs.buildPythonApplication { + inherit pname version; + src = pkgs.fetchFromGitHub { + owner = "makrsmark"; + repo = "mlat-client"; + rev = "0e8f9f927339e6bfa8cadaa8fff6cb734de4b60a"; + sha256 = "sha256-4dWwRBKWkxMjKJmZtAAZjaFqkGubOmKTmzmntcR92II="; + }; + meta = with pkgs.lib; { + homepage = "https://github.com/makrsmark/mlat-client"; + description = "ADSB.fi Mode S multilateration client"; + longDescription = '' + This is a client that selectively forwards Mode S messages to a server that resolves the transmitter position by multilateration of the same message received by multiple clients. + ''; + license = licenses.gpl3Plus; + }; +} diff --git a/packages/adsbfi-readsb.nix b/packages/adsbfi-readsb.nix new file mode 100644 index 0000000..0e61a65 --- /dev/null +++ b/packages/adsbfi-readsb.nix @@ -0,0 +1,40 @@ +{ pkgs, ... }: +let + pname = "adsbfi-readsb"; + version = "3.14.1606"; +in +pkgs.stdenv.mkDerivation { + inherit pname version; + src = pkgs.fetchFromGitHub { + owner = "wiedehopf"; + repo = "readsb"; + rev = "v${version}"; + sha256 = "sha256-XjvWpxCYCoNPDPwbvLKk8l23dhHL98HbVK6+Zn+36Wk="; + }; + nativeBuildInputs = [ + pkgs.pkg-config + ]; + propagatedBuildInputs = [ + pkgs.libad9361 + pkgs.libbladeRF + pkgs.libiio + pkgs.librtlsdr + pkgs.ncurses + pkgs.zlib + pkgs.zstd + ]; + buildFlags = [ + "READSB_VERSION=v${version}" + "RTLSDR=yes" + "BLADERF=yes" + "PLUTOSDR=yes" + "AIRCRAFT_HASH_BITS=12" + ]; + installPhase = '' + runHook preInstall + mkdir -p $out/bin + install -D readsb $out/bin + install -D viewadsb $out/bin + runHook postInstall + ''; +} diff --git a/packages/default.nix b/packages/default.nix new file mode 100644 index 0000000..c54d223 --- /dev/null +++ b/packages/default.nix @@ -0,0 +1,28 @@ +{ self, flake-utils, nixpkgs }: flake-utils.lib.eachDefaultSystem ( + system: + let + pkgs = import nixpkgs { + inherit system; + }; + in + { + packages = { + dump1090-fa = import ./dump1090-fa.nix { inherit pkgs; }; + dump978-fa = import ./dump978-fa.nix { inherit pkgs; }; + tcllauncher = import ./tcllauncher.nix { inherit pkgs; }; + mutability-mlat-client = import ./mutability-mlat-client.nix { inherit pkgs; }; + piaware = import ./piaware.nix { + inherit pkgs; + dump1090-fa = self.packages.${system}.dump1090-fa; + dump978-fa = self.packages.${system}.dump978-fa; + tcllauncher = self.packages.${system}.tcllauncher; + mutability-mlat-client = self.packages.${system}.mutability-mlat-client; + }; + adsbexchange-mlat-client = import ./adsbexchange-mlat-client.nix { inherit pkgs; }; + adsbexchange-readsb = import ./adsbexchange-readsb.nix { inherit pkgs; }; + adsbfi-mlat-client = import ./adsbfi-mlat-client.nix { inherit pkgs; }; + adsbfi-readsb = import ./adsbfi-readsb.nix { inherit pkgs; }; + tar1090 = import ./tar1090.nix { inherit pkgs; }; + }; + } +) diff --git a/packages/dump1090-fa.nix b/packages/dump1090-fa.nix new file mode 100644 index 0000000..683d26a --- /dev/null +++ b/packages/dump1090-fa.nix @@ -0,0 +1,58 @@ +{ pkgs, ... }: +let + pname = "dump1090-fa"; + version = "8.2"; +in +pkgs.stdenv.mkDerivation { + inherit pname version; + + src = pkgs.fetchFromGitHub { + owner = "flightaware"; + repo = "dump1090"; + rev = "v${version}"; + hash = "sha256-SUvK9XTXIDimEMEnORnp/Af/F030TZTxLI43Jzz31Js="; + }; + + outputs = [ "out" "html" ]; + + nativeBuildInputs = [ + pkgs.pkg-config + ]; + propagatedBuildInputs = [ + pkgs.ncurses + pkgs.librtlsdr + pkgs.libbladeRF + pkgs.limesuite + pkgs.hackrf + ]; + buildFlags = [ + "DUMP1090_VERSION=v${version}" + "CPUFEATURES=yes" + "showconfig" + "dump1090" + "faup1090" + "view1090" + ]; + installPhase = '' + runHook preInstall + mkdir -p $out/bin + install -D dump1090 $out/bin + install -D faup1090 $out/bin + install -D view1090 $out/bin + mkdir -p $html + cp -a public_html/* $html + runHook postInstall + ''; + meta = with pkgs.lib; { + homepage = "https://github.com/flightaware/dump1090"; + description = "A ADS-B, Mode S, and Mode 3A/3C demodulator and decoder."; + longDescription = '' + dump1090-fa is a ADS-B, Mode S, and Mode 3A/3C demodulator and decoder that will receive and decode aircraft transponder messages received via a directly connected software defined radio, or from data provided over a network connection. + + It is the successor to dump1090-mutability and is maintained by FlightAware. + + It can provide a display of locally received aircraft data in a terminal or via a browser map. Together with PiAware it can be used to contribute crowd-sourced flight tracking data to FlightAware. + ''; + license = licenses.gpl2Plus; + }; +} diff --git a/packages/dump978-fa.nix b/packages/dump978-fa.nix new file mode 100644 index 0000000..c9316fd --- /dev/null +++ b/packages/dump978-fa.nix @@ -0,0 +1,52 @@ +{ pkgs, ... }: +let + pname = "dump978-fa"; + version = "8.2"; +in +pkgs.stdenv.mkDerivation { + inherit pname version; + + src = pkgs.fetchFromGitHub { + owner = "flightaware"; + repo = "dump978"; + rev = "v${version}"; + hash = "sha256-6LPshYbI/lLcjR/xP7DHFza2xVcfiAzh66Y7w95YDjk="; + }; + + outputs = [ "out" "html" ]; + + nativeBuildInputs = [ + # pkgs.autoPatchelfHook + pkgs.pkg-config + ]; + propagatedBuildInputs = [ + pkgs.boost + pkgs.soapysdr + ]; + buildFlags = [ + "VERSION=v${version}" + "dump978-fa" + "faup978" + "skyaware978" + ]; + installPhase = '' + runHook preInstall + mkdir -p $out/bin + install -D dump978-fa $out/bin + install -D faup978 $out/bin + install -D skyaware978 $out/bin + mkdir -p $html + cp -a skyaware/* $html + runHook postInstall + ''; + meta = with pkgs.lib; { + homepage = "https://github.com/flightaware/dump978"; + description = "The FlightAware 978MHz UAT decoder.."; + longDescription = '' + This is the FlightAware 978MHz UAT decoder. + + It is a reimplementation in C++, loosely based on the demodulator from https://github.com/mutability/dump978. + ''; + license = licenses.bsd2; + }; +} diff --git a/packages/mutability-mlat-client.nix b/packages/mutability-mlat-client.nix new file mode 100644 index 0000000..b38d658 --- /dev/null +++ b/packages/mutability-mlat-client.nix @@ -0,0 +1,14 @@ +{ pkgs, ... }: +let + pname = "mlat-client"; + version = "0.2.12"; +in +pkgs.python3.pkgs.buildPythonApplication { + inherit pname version; + src = pkgs.fetchFromGitHub { + owner = "mutability"; + repo = pname; + rev = "v${version}"; + sha256 = "sha256-kU7DJ6lPwz+mVlp4g7rDz0TTSzsZvd/KxjHbYsDFLPI="; + }; +} diff --git a/packages/piaware.nix b/packages/piaware.nix new file mode 100644 index 0000000..946aeda --- /dev/null +++ b/packages/piaware.nix @@ -0,0 +1,91 @@ +{ pkgs, dump1090-fa, dump978-fa, tcllauncher, mutability-mlat-client, ... }: +let + pname = "piaware"; + version = "8.2"; +in +pkgs.stdenv.mkDerivation { + inherit pname version; + + src = pkgs.fetchFromGitHub { + owner = "flightaware"; + repo = "piaware"; + rev = "v${version}"; + sha256 = "sha256-La0J+6Y0cWr6fTr0ppzYV6Vq00GisyDxmSyGzR7nfpg="; + }; + + nativeBuildInputs = [ + pkgs.makeWrapper + pkgs.which + ]; + propagatedBuildInputs = [ + pkgs.coreutils + pkgs.iproute2 + pkgs.openssl + pkgs.tcl + pkgs.tcltls + pkgs.tclx + tcllauncher + ]; + dontPatch = true; + dontConfigure = true; + buildPhase = '' + runHook preBuild + + substituteInPlace programs/piaware/login.tcl \ + --replace /bin/uname ${pkgs.coreutils}/bin/uname + + substituteInPlace programs/piaware/helpers.tcl \ + --replace /bin/uname ${pkgs.coreutils}/bin/uname + + substituteInPlace programs/piaware/update.tcl \ + --replace /sbin/reboot ${pkgs.systemd}/bin/reboot \ + --replace /sbin/halt ${pkgs.systemd}/bin/halt + + substituteInPlace programs/piaware/faup1090.tcl \ + --replace /usr/lib/piaware/helpers/faup1090 ${dump1090-fa}/bin/faup1090 + + substituteInPlace programs/piaware/faup978.tcl \ + --replace /usr/lib/piaware/helpers/faup978 ${dump978-fa}/bin/faup978 + + substituteInPlace programs/piaware/mlat.tcl \ + --replace /usr/lib/piaware/helpers/fa-mutability-mlat-client ${mutability-mlat-client}/bin/fa-mutability-mlat-client + + substituteInPlace package/fa_sysinfo.tcl \ + --replace /sbin/ip ${pkgs.iproute2}/bin/ip \ + --replace /bin/df ${pkgs.coreutils}/bin/df + + substituteInPlace package/piaware.tcl \ + --replace "list netstat --program" "list ${pkgs.nettools}/bin/netstat --program" + + substituteInPlace package/helpers/restart-network \ + --replace /bin/systemctl ${pkgs.systemd}/bin/systemctl + + substituteInPlace package/helpers/restart-receiver \ + --replace /bin/systemctl ${pkgs.systemd}/bin/systemctl + + runHook postBuild + ''; + installFlags = [ + ''DESTDIR=''${out}'' + "PREFIX=/" + "INSTALL_SUDOERS=1" + ]; + postFixup = + let + tcllibpath = "$out/lib ${tcllauncher}/lib ${pkgs.tcl}/lib ${pkgs.tclx}/lib ${pkgs.tcltls}/lib ${pkgs.tcllib}/lib"; + in + '' + for f in piaware piaware-config piaware-status pirehose + do + ln -sf ${tcllauncher}/bin/tcllauncher $out/bin/$f + wrapProgram $out/bin/$f \ + --set TCLLIBPATH "${tcllibpath}" + done + ''; + meta = with pkgs.lib; { + homepage = "https://github.com/flightaware/piaware"; + description = ""; + longDescription = '' ''; + license = licenses.gpl2Plus; + }; +} diff --git a/packages/tar1090.nix b/packages/tar1090.nix new file mode 100644 index 0000000..bc09375 --- /dev/null +++ b/packages/tar1090.nix @@ -0,0 +1,24 @@ +{ pkgs, ... }: +let + pname = "tar1090"; + version = "0.0"; +in +pkgs.stdenvNoCC.mkDerivation { + inherit pname version; + src = pkgs.fetchFromGitHub { + owner = "wiedehopf"; + repo = pname; + rev = "3c6a15369c4da2e327d08f7f57373abdf02c6812"; + sha256 = "sha256-R4K4AxFQdy+Cc9gTVIpmU7r2ghiaDxxpFJcLUTwlhWY="; + }; + dontPatch = true; + dontConfigure = true; + dontBuild = true; + dontFixup = true; + installPhase = '' + runHook preInstall + mkdir -p $out/html + cp -av html/* $out/html + runHook postInstall + ''; +} diff --git a/packages/tcllauncher.nix b/packages/tcllauncher.nix new file mode 100644 index 0000000..49c7949 --- /dev/null +++ b/packages/tcllauncher.nix @@ -0,0 +1,63 @@ +{ pkgs, ... }: +let + pname = "tcllauncher"; + version = "1.10"; +in +pkgs.stdenv.mkDerivation { + inherit pname version; + + src = pkgs.fetchFromGitHub { + owner = "flightaware"; + repo = pname; + rev = "v${version}"; + sha256 = "sha256-BVrsoczKeBBoM1Q3v6EJY81QwsX6xbUqFkcBb482WH4="; + }; + nativeBuildInputs = [ + pkgs.autoconf + pkgs.makeBinaryWrapper + ]; + propagatedBuildInputs = [ + pkgs.tcl + pkgs.tclx + pkgs.tcllib + ]; + dontPatch = true; + preConfigure = '' + autoconf + ''; + configureFlags = [ + "--with-tcl=${pkgs.tcl}/lib" + ]; + buildFlags = [ + ''pkglibdir=''${out}/lib'' + ]; + installFlags = [ + ''DESTDIR=''${out}'' + "bindir=/bin" + "includedir=/include" + "mandir=/share/man" + "pkglibdir=/lib" + ]; + # postFixup = '' + # wrapProgram $out/bin/tcllauncher \ + # --set TCLLIBPATH "${pkgs.tcl}/lib ${pkgs.tclx}/lib" + # ''; + # buildFlags = [ + # "DUMP1090_VERSION=${version}" + # "CPUFEATURES=yes" + # ]; + # installPhase = '' + # runHook preInstall + # mkdir -p $out + # make DESTDIR=$out install + # runHook postInstall + # ''; + meta = with pkgs.lib; { + homepage = "https://flightaware.github.io/tcllauncher/"; + description = "Launcher program for Tcl applications."; + longDescription = '' + tcllauncher is a way to have Tcl programs run out of /usr/local/bin under their own name, be installed in one place with their support files, and provides commands to facilitate server-oriented application execution. + ''; + license = licenses.gpl2Plus; + }; +}