1{ config, lib, pkgs, ... }:
2
3with lib;
4
5let
6 cfg = config.services.ebusd;
7
8 package = pkgs.ebusd;
9
10 arguments = [
11 "${package}/bin/ebusd"
12 "--foreground"
13 "--updatecheck=off"
14 "--device=${cfg.device}"
15 "--port=${toString cfg.port}"
16 "--configpath=${cfg.configpath}"
17 "--scanconfig=${cfg.scanconfig}"
18 "--log=main:${cfg.logs.main}"
19 "--log=network:${cfg.logs.network}"
20 "--log=bus:${cfg.logs.bus}"
21 "--log=update:${cfg.logs.update}"
22 "--log=other:${cfg.logs.other}"
23 "--log=all:${cfg.logs.all}"
24 ] ++ lib.optionals cfg.readonly [
25 "--readonly"
26 ] ++ lib.optionals cfg.mqtt.enable [
27 "--mqtthost=${cfg.mqtt.host}"
28 "--mqttport=${toString cfg.mqtt.port}"
29 "--mqttuser=${cfg.mqtt.user}"
30 "--mqttpass=${cfg.mqtt.password}"
31 ] ++ lib.optionals cfg.mqtt.home-assistant [
32 "--mqttint=${package}/etc/ebusd/mqtt-hassio.cfg"
33 "--mqttjson"
34 ] ++ lib.optionals cfg.mqtt.retain [
35 "--mqttretain"
36 ] ++ cfg.extraArguments;
37
38 usesDev = hasPrefix "/" cfg.device;
39
40 command = concatStringsSep " " arguments;
41
42in
43{
44 meta.maintainers = with maintainers; [ nathan-gs ];
45
46 options.services.ebusd = {
47 enable = mkEnableOption (lib.mdDoc "ebusd service");
48
49 device = mkOption {
50 type = types.str;
51 default = "";
52 example = "IP:PORT";
53 description = lib.mdDoc ''
54 Use DEV as eBUS device [/dev/ttyUSB0].
55 This can be either:
56 enh:DEVICE or enh:IP:PORT for enhanced device (only adapter v3 and newer),
57 ens:DEVICE for enhanced high speed serial device (only adapter v3 and newer with firmware since 20220731),
58 DEVICE for serial device (normal speed, for all other serial adapters like adapter v2 as well as adapter v3 in non-enhanced mode), or
59 [udp:]IP:PORT for network device.
60 https://github.com/john30/ebusd/wiki/2.-Run#device-options
61 '';
62 };
63
64 port = mkOption {
65 default = 8888;
66 type = types.port;
67 description = lib.mdDoc ''
68 The port on which to listen on
69 '';
70 };
71
72 readonly = mkOption {
73 type = types.bool;
74 default = false;
75 description = lib.mdDoc ''
76 Only read from device, never write to it
77 '';
78 };
79
80 configpath = mkOption {
81 type = types.str;
82 default = "https://cfg.ebusd.eu/";
83 description = lib.mdDoc ''
84 Read CSV config files from PATH (local folder or HTTPS URL) [https://cfg.ebusd.eu/]
85 '';
86 };
87
88 scanconfig = mkOption {
89 type = types.str;
90 default = "full";
91 description = lib.mdDoc ''
92 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).
93 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,
94 see [Automatic configuration](https://github.com/john30/ebusd/wiki/4.7.-Automatic-configuration).
95 '';
96 };
97
98 logs = {
99 main = mkOption {
100 type = types.enum [ "error" "notice" "info" "debug"];
101 default = "info";
102 description = lib.mdDoc ''
103 Only write log for matching AREAs (main|network|bus|update|other|all) below or equal to LEVEL (error|notice|info|debug) [all:notice].
104 '';
105 };
106
107 network = mkOption {
108 type = types.enum [ "error" "notice" "info" "debug"];
109 default = "info";
110 description = lib.mdDoc ''
111 Only write log for matching AREAs (main|network|bus|update|other|all) below or equal to LEVEL (error|notice|info|debug) [all:notice].
112 '';
113 };
114
115 bus = mkOption {
116 type = types.enum [ "error" "notice" "info" "debug"];
117 default = "info";
118 description = lib.mdDoc ''
119 Only write log for matching AREAs (main|network|bus|update|other|all) below or equal to LEVEL (error|notice|info|debug) [all:notice].
120 '';
121 };
122
123 update = mkOption {
124 type = types.enum [ "error" "notice" "info" "debug"];
125 default = "info";
126 description = lib.mdDoc ''
127 Only write log for matching AREAs (main|network|bus|update|other|all) below or equal to LEVEL (error|notice|info|debug) [all:notice].
128 '';
129 };
130
131 other = mkOption {
132 type = types.enum [ "error" "notice" "info" "debug"];
133 default = "info";
134 description = lib.mdDoc ''
135 Only write log for matching AREAs (main|network|bus|update|other|all) below or equal to LEVEL (error|notice|info|debug) [all:notice].
136 '';
137 };
138
139 all = mkOption {
140 type = types.enum [ "error" "notice" "info" "debug"];
141 default = "info";
142 description = lib.mdDoc ''
143 Only write log for matching AREAs (main|network|bus|update|other|all) below or equal to LEVEL (error|notice|info|debug) [all:notice].
144 '';
145 };
146 };
147
148 mqtt = {
149
150 enable = mkOption {
151 type = types.bool;
152 default = false;
153 description = lib.mdDoc ''
154 Adds support for MQTT
155 '';
156 };
157
158 host = mkOption {
159 type = types.str;
160 default = "localhost";
161 description = lib.mdDoc ''
162 Connect to MQTT broker on HOST.
163 '';
164 };
165
166 port = mkOption {
167 default = 1883;
168 type = types.port;
169 description = lib.mdDoc ''
170 The port on which to connect to MQTT
171 '';
172 };
173
174 home-assistant = mkOption {
175 type = types.bool;
176 default = false;
177 description = lib.mdDoc ''
178 Adds the Home Assistant topics to MQTT, read more at [MQTT Integration](https://github.com/john30/ebusd/wiki/MQTT-integration)
179 '';
180 };
181
182 retain = mkOption {
183 type = types.bool;
184 default = false;
185 description = lib.mdDoc ''
186 Set the retain flag on all topics instead of only selected global ones
187 '';
188 };
189
190 user = mkOption {
191 type = types.str;
192 description = lib.mdDoc ''
193 The MQTT user to use
194 '';
195 };
196
197 password = mkOption {
198 type = types.str;
199 description = lib.mdDoc ''
200 The MQTT password.
201 '';
202 };
203
204 };
205
206 extraArguments = mkOption {
207 type = types.listOf types.str;
208 default = [];
209 description = lib.mdDoc ''
210 Extra arguments to the ebus daemon
211 '';
212 };
213
214 };
215
216 config = mkIf (cfg.enable) {
217
218 systemd.services.ebusd = {
219 description = "EBUSd Service";
220 wantedBy = [ "multi-user.target" ];
221 after = [ "network.target" ];
222 serviceConfig = {
223 ExecStart = command;
224 DynamicUser = true;
225 Restart = "on-failure";
226
227 # Hardening
228 CapabilityBoundingSet = "";
229 DeviceAllow = lib.optionals usesDev [
230 cfg.device
231 ] ;
232 DevicePolicy = "closed";
233 LockPersonality = true;
234 MemoryDenyWriteExecute = false;
235 NoNewPrivileges = true;
236 PrivateDevices = usesDev;
237 PrivateUsers = true;
238 PrivateTmp = true;
239 ProtectClock = true;
240 ProtectControlGroups = true;
241 ProtectHome = true;
242 ProtectHostname = true;
243 ProtectKernelLogs = true;
244 ProtectKernelModules = true;
245 ProtectKernelTunables = true;
246 ProtectProc = "invisible";
247 ProcSubset = "pid";
248 ProtectSystem = "strict";
249 RemoveIPC = true;
250 RestrictAddressFamilies = [
251 "AF_INET"
252 "AF_INET6"
253 ];
254 RestrictNamespaces = true;
255 RestrictRealtime = true;
256 RestrictSUIDSGID = true;
257 SupplementaryGroups = [
258 "dialout"
259 ];
260 SystemCallArchitectures = "native";
261 SystemCallFilter = [
262 "@system-service @pkey"
263 "~@privileged @resources"
264 ];
265 UMask = "0077";
266 };
267 };
268
269 };
270}