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