2023-11-28 21:26:35 +00:00
|
|
|
self: {
|
|
|
|
config,
|
|
|
|
pkgs,
|
|
|
|
lib,
|
|
|
|
...
|
|
|
|
}: let
|
2023-11-28 23:02:15 +00:00
|
|
|
inherit (lib) mkEnableOption mkOption mkIf types optional;
|
2023-11-28 21:26:35 +00:00
|
|
|
|
|
|
|
cfg = config.services.pi-air-quality-monitor;
|
|
|
|
in {
|
|
|
|
options.services.pi-air-quality-monitor = {
|
|
|
|
enable = mkEnableOption "pi-air-quality-monitor";
|
|
|
|
package = mkOption {
|
|
|
|
type = types.package;
|
|
|
|
default = self.packages.${pkgs.system}.pi-air-quality-monitor;
|
|
|
|
};
|
2023-11-29 00:20:30 +00:00
|
|
|
|
2023-11-28 21:26:35 +00:00
|
|
|
openFirewall = mkOption {
|
|
|
|
type = types.bool;
|
|
|
|
default = true;
|
2023-11-29 00:20:30 +00:00
|
|
|
description = "Whether to open the firewall for the service";
|
2023-11-28 21:26:35 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
settings = {
|
|
|
|
port = mkOption {
|
|
|
|
type = types.int;
|
|
|
|
default = 8080;
|
2023-11-29 00:20:30 +00:00
|
|
|
description = "Port to run the service on";
|
2023-11-28 21:26:35 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
user = mkOption {
|
|
|
|
type = types.str;
|
|
|
|
default = "pi-aqm";
|
2023-11-29 00:20:30 +00:00
|
|
|
description = "User to run the servvice as";
|
2023-11-28 21:26:35 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
group = mkOption {
|
|
|
|
type = types.str;
|
|
|
|
default = "pi-aqm";
|
2023-11-29 00:20:30 +00:00
|
|
|
description = "Group to run the service as";
|
2023-11-28 21:26:35 +00:00
|
|
|
};
|
2023-11-28 23:02:15 +00:00
|
|
|
|
2023-11-29 00:20:30 +00:00
|
|
|
serialPort = mkOption {
|
2023-11-28 23:02:15 +00:00
|
|
|
type = types.path;
|
|
|
|
default = "/dev/ttyUSB0";
|
2023-11-29 00:20:30 +00:00
|
|
|
description = "Serial port device to read data from";
|
2023-11-28 23:02:15 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
environmentFile = mkOption {
|
|
|
|
type = with types; nullOr path;
|
|
|
|
default = null;
|
|
|
|
example = "/etc/pi-aqm.env";
|
|
|
|
description = "File to read environment variables from";
|
|
|
|
};
|
|
|
|
|
|
|
|
dataDir = mkOption {
|
|
|
|
type = types.path;
|
|
|
|
default = "/var/lib/pi-aqm";
|
|
|
|
description = "Directory to store data in";
|
|
|
|
};
|
|
|
|
|
|
|
|
redis = {
|
|
|
|
createLocally = mkOption {
|
|
|
|
type = types.bool;
|
|
|
|
default = true;
|
|
|
|
description = "Whether to create a Redis instance locally";
|
|
|
|
};
|
|
|
|
|
|
|
|
host = mkOption {
|
|
|
|
type = types.str;
|
|
|
|
default = "localhost";
|
|
|
|
description = "Redis host";
|
|
|
|
};
|
|
|
|
|
|
|
|
port = mkOption {
|
|
|
|
type = types.int;
|
|
|
|
default = 6379;
|
|
|
|
description = "Redis port";
|
|
|
|
};
|
|
|
|
|
|
|
|
redis_db = mkOption {
|
|
|
|
type = types.int;
|
|
|
|
default = 0;
|
|
|
|
description = "Redis database";
|
|
|
|
};
|
|
|
|
};
|
2023-11-28 21:26:35 +00:00
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
config = mkIf config.services.pi-air-quality-monitor.enable {
|
|
|
|
networking.firewall.allowedTCPPorts = [cfg.settings.port];
|
|
|
|
users = {
|
2023-11-28 23:02:15 +00:00
|
|
|
groups."${cfg.settings.group}" = {};
|
|
|
|
users."${cfg.settings.user}" = {
|
2023-11-28 21:26:35 +00:00
|
|
|
isSystemUser = true;
|
2023-11-28 23:02:15 +00:00
|
|
|
group = "${cfg.settings.group}";
|
|
|
|
home = "${cfg.settings.dataDir}";
|
2023-11-28 21:26:35 +00:00
|
|
|
createHome = true;
|
|
|
|
};
|
2023-11-28 23:02:15 +00:00
|
|
|
};
|
2023-11-28 21:26:35 +00:00
|
|
|
|
2023-11-28 23:02:15 +00:00
|
|
|
services.redis = mkIf cfg.settings.redis.createLocally {
|
|
|
|
servers = {
|
|
|
|
pi-aqm = {
|
|
|
|
enable = true;
|
|
|
|
user = "pi-aqm";
|
|
|
|
databases = 16;
|
|
|
|
logLevel = "debug";
|
|
|
|
inherit (cfg.settings.redis) port;
|
|
|
|
};
|
|
|
|
};
|
2023-11-28 21:26:35 +00:00
|
|
|
};
|
|
|
|
|
2023-11-28 23:02:15 +00:00
|
|
|
systemd.services."pi-air-quality-monitor" = let
|
|
|
|
redisEnv = pkgs.writeText "redis.env" ''
|
|
|
|
REDIS_HOST=${cfg.settings.redis.host}
|
|
|
|
REDIS_PORT=${toString cfg.settings.redis.port}
|
|
|
|
REDIS_DB=${toString cfg.settings.redis.redis_db}
|
|
|
|
'';
|
|
|
|
in {
|
2023-11-28 21:26:35 +00:00
|
|
|
description = "An air quality monitoring service with a Raspberry Pi and a SDS011 sensor";
|
|
|
|
wantedBy = ["multi-user.target"];
|
|
|
|
after = ["network.target"];
|
|
|
|
serviceConfig = {
|
|
|
|
Type = "simple";
|
|
|
|
User = cfg.settings.user;
|
|
|
|
Group = cfg.settings.group;
|
2023-11-28 23:02:15 +00:00
|
|
|
EnvironmentFile = [redisEnv] ++ optional (cfg.settings.environmentFile != null) cfg.settings.environmentFile;
|
|
|
|
WorkingDirectory = "${cfg.settings.dataDir}";
|
2023-11-28 21:26:35 +00:00
|
|
|
ExecStart = "${lib.getExe cfg.package}";
|
|
|
|
Restart = "always";
|
2023-11-29 00:20:30 +00:00
|
|
|
|
|
|
|
# Hardening
|
|
|
|
CapabilityBoundingSet = "";
|
|
|
|
DeviceAllow = [cfg.settings.serialPort];
|
|
|
|
DevicePolicy = "closed";
|
|
|
|
DynamicUser = true;
|
|
|
|
LockPersonality = true;
|
|
|
|
MemoryDenyWriteExecute = false;
|
|
|
|
NoNewPrivileges = true;
|
|
|
|
PrivateUsers = true;
|
|
|
|
PrivateTmp = true;
|
|
|
|
ProtectClock = true;
|
|
|
|
ProtectControlGroups = true;
|
|
|
|
ProtectHome = true;
|
|
|
|
ProtectHostname = true;
|
|
|
|
ProtectKernelLogs = true;
|
|
|
|
ProtectKernelModules = true;
|
|
|
|
RemoveIPC = true;
|
|
|
|
RestrictNamespaces = true;
|
|
|
|
RestrictRealtime = true;
|
|
|
|
RestrictSUIDSGID = true;
|
|
|
|
SystemCallArchitectures = "native";
|
|
|
|
UMask = "0077";
|
|
|
|
SystemCallFilter = [
|
|
|
|
"@system-service @pkey"
|
|
|
|
"~@privileged @resources"
|
|
|
|
];
|
2023-11-28 21:26:35 +00:00
|
|
|
};
|
|
|
|
};
|
|
|
|
};
|
|
|
|
}
|