air-quality-monitor/nix/module.nix

171 lines
4.6 KiB
Nix

self: {
config,
pkgs,
lib,
...
}: let
inherit (lib) mkEnableOption mkOption mkIf types optional;
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;
};
openFirewall = mkOption {
type = types.bool;
default = true;
description = "Whether to open the firewall for the service";
};
settings = {
port = mkOption {
type = types.int;
default = 8080;
description = "Port to run the service on";
};
user = mkOption {
type = types.str;
default = "pi-aqm";
description = "User to run the servvice as";
};
group = mkOption {
type = types.str;
default = "pi-aqm";
description = "Group to run the service as";
};
serialPort = mkOption {
type = types.path;
default = "/dev/ttyUSB0";
description = "Serial port device to read data from";
};
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";
};
};
};
};
config = mkIf config.services.pi-air-quality-monitor.enable {
networking.firewall.allowedTCPPorts = [cfg.settings.port];
users = {
groups."${cfg.settings.group}" = {};
users."${cfg.settings.user}" = {
isSystemUser = true;
group = "${cfg.settings.group}";
home = "${cfg.settings.dataDir}";
createHome = true;
};
};
services.redis = mkIf cfg.settings.redis.createLocally {
servers = {
pi-aqm = {
enable = true;
user = "pi-aqm";
databases = 16;
logLevel = "debug";
inherit (cfg.settings.redis) port;
};
};
};
systemd.services."pi-air-quality-monitor" = let
globalEnv = pkgs.writeText "global.env" ''
SERIAL_DEVICE=${cfg.settings.serialPort}
'';
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 {
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;
EnvironmentFile = lib.concatLists [
[globalEnv]
(optional (cfg.settings.environmentFile != null) [cfg.settings.environmentFile])
(optional cfg.settings.redis.createLocally [redisEnv])
];
WorkingDirectory = "${cfg.settings.dataDir}";
ExecStart = "${lib.getExe cfg.package}";
Restart = "always";
# 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"
];
};
};
};
}