1{ config, lib, pkgs, ... }:
2
3with lib;
4
5let
6 cfg = config.services.ebusd;
7in
8{
9 meta.maintainers = with maintainers; [ nathan-gs ];
10
11 options.services.ebusd = {
12 enable = mkEnableOption "ebusd, a daemon for communication with eBUS heating systems";
13
14 package = mkPackageOptionMD pkgs "ebusd" { };
15
16 device = mkOption {
17 type = types.str;
18 default = "";
19 example = "IP:PORT";
20 description = ''
21 Use DEV as eBUS device [/dev/ttyUSB0].
22 This can be either:
23 enh:DEVICE or enh:IP:PORT for enhanced device (only adapter v3 and newer),
24 ens:DEVICE for enhanced high speed serial device (only adapter v3 and newer with firmware since 20220731),
25 DEVICE for serial device (normal speed, for all other serial adapters like adapter v2 as well as adapter v3 in non-enhanced mode), or
26 [udp:]IP:PORT for network device.
27
28 Source: <https://github.com/john30/ebusd/wiki/2.-Run#device-options>
29 '';
30 };
31
32 port = mkOption {
33 default = 8888;
34 type = types.port;
35 description = ''
36 The port on which to listen on
37 '';
38 };
39
40 readonly = mkOption {
41 type = types.bool;
42 default = false;
43 description = ''
44 Only read from device, never write to it
45 '';
46 };
47
48 configpath = mkOption {
49 type = types.str;
50 default = "https://cfg.ebusd.eu/";
51 description = ''
52 Directory to read CSV config files from. This can be a local folder or a URL.
53 '';
54 };
55
56 scanconfig = mkOption {
57 type = types.str;
58 default = "full";
59 description = ''
60 Pick CSV config files matching initial scan ("none" or empty for no initial scan message, "full" for full scan, or a single hex address to scan, default is to send a broadcast ident message).
61 If combined with --checkconfig, you can add scan message data as arguments for checking a particular scan configuration, e.g. "FF08070400/0AB5454850303003277201". For further details on this option,
62 see [Automatic configuration](https://github.com/john30/ebusd/wiki/4.7.-Automatic-configuration).
63 '';
64 };
65
66 logs = let
67 # "all" must come first so it can be overridden by more specific areas
68 areas = [ "all" "main" "network" "bus" "update" "other" ];
69 levels = [ "none" "error" "notice" "info" "debug" ];
70 in listToAttrs (map (area: nameValuePair area (mkOption {
71 type = types.enum levels;
72 default = "notice";
73 example = "debug";
74 description = ''
75 Only write log for matching `AREA`s (${concatStringsSep "|" areas}) below or equal to `LEVEL` (${concatStringsSep "|" levels})
76 '';
77 })) areas);
78
79 mqtt = {
80 enable = mkEnableOption "support for MQTT";
81
82 host = mkOption {
83 type = types.str;
84 default = "localhost";
85 description = ''
86 Connect to MQTT broker on HOST.
87 '';
88 };
89
90 port = mkOption {
91 default = 1883;
92 type = types.port;
93 description = ''
94 The port on which to connect to MQTT
95 '';
96 };
97
98 home-assistant = mkOption {
99 type = types.bool;
100 default = false;
101 description = ''
102 Adds the Home Assistant topics to MQTT, read more at [MQTT Integration](https://github.com/john30/ebusd/wiki/MQTT-integration)
103 '';
104 };
105
106 retain = mkEnableOption "set the retain flag on all topics instead of only selected global ones";
107
108 user = mkOption {
109 type = types.str;
110 description = ''
111 The MQTT user to use
112 '';
113 };
114
115 password = mkOption {
116 type = types.str;
117 description = ''
118 The MQTT password.
119 '';
120 };
121 };
122
123 extraArguments = mkOption {
124 type = types.listOf types.str;
125 default = [];
126 description = ''
127 Extra arguments to the ebus daemon
128 '';
129 };
130 };
131
132 config = let
133 usesDev = hasPrefix "/" cfg.device;
134 in mkIf cfg.enable {
135 systemd.services.ebusd = {
136 description = "EBUSd Service";
137 wantedBy = [ "multi-user.target" ];
138 after = [ "network.target" ];
139 serviceConfig = {
140 ExecStart = let
141 args = cli.toGNUCommandLineShell { } (foldr (a: b: a // b) { } [
142 {
143 inherit (cfg) device port configpath scanconfig readonly;
144 foreground = true;
145 updatecheck = "off";
146 log = mapAttrsToList (name: value: "${name}:${value}") cfg.logs;
147 mqttretain = cfg.mqtt.retain;
148 }
149 (optionalAttrs cfg.mqtt.enable {
150 mqtthost = cfg.mqtt.host;
151 mqttport = cfg.mqtt.port;
152 mqttuser = cfg.mqtt.user;
153 mqttpass = cfg.mqtt.password;
154 })
155 (optionalAttrs cfg.mqtt.home-assistant {
156 mqttint = "${cfg.package}/etc/ebusd/mqtt-hassio.cfg";
157 mqttjson = true;
158 })
159 ]);
160 in "${cfg.package}/bin/ebusd ${args} ${escapeShellArgs cfg.extraArguments}";
161
162 DynamicUser = true;
163 Restart = "on-failure";
164
165 # Hardening
166 CapabilityBoundingSet = "";
167 DeviceAllow = optionals usesDev [ cfg.device ];
168 DevicePolicy = "closed";
169 LockPersonality = true;
170 MemoryDenyWriteExecute = false;
171 NoNewPrivileges = true;
172 PrivateDevices = usesDev;
173 PrivateUsers = true;
174 PrivateTmp = true;
175 ProtectClock = true;
176 ProtectControlGroups = true;
177 ProtectHome = true;
178 ProtectHostname = true;
179 ProtectKernelLogs = true;
180 ProtectKernelModules = true;
181 ProtectKernelTunables = true;
182 ProtectProc = "invisible";
183 ProcSubset = "pid";
184 ProtectSystem = "strict";
185 RemoveIPC = true;
186 RestrictAddressFamilies = [
187 "AF_INET"
188 "AF_INET6"
189 ];
190 RestrictNamespaces = true;
191 RestrictRealtime = true;
192 RestrictSUIDSGID = true;
193 SupplementaryGroups = [ "dialout" ];
194 SystemCallArchitectures = "native";
195 SystemCallFilter = [
196 "@system-service @pkey"
197 "~@privileged @resources"
198 ];
199 UMask = "0077";
200 };
201 };
202 };
203}