nixos-piaware/modules/config/default.nix
2023-12-13 23:32:42 -06:00

824 lines
23 KiB
Nix

{ 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 *;
'';
};
};
};
};
};
}