1{ config, lib, pkgs, ...}: 2 3with lib; 4 5let 6 cfg = config.services.znc; 7 8 defaultUser = "znc"; # Default user to own process. 9 10 # Default user and pass: 11 # un=znc 12 # pw=nixospass 13 14 defaultUserName = "znc"; 15 defaultPassBlock = " 16 <Pass password> 17 Method = sha256 18 Hash = e2ce303c7ea75c571d80d8540a8699b46535be6a085be3414947d638e48d9e93 19 Salt = l5Xryew4g*!oa(ECfX2o 20 </Pass> 21 "; 22 23 modules = pkgs.buildEnv { 24 name = "znc-modules"; 25 paths = cfg.modulePackages; 26 }; 27 28 # Keep znc.conf in nix store, then symlink or copy into `dataDir`, depending on `mutable`. 29 notNull = a: ! isNull a; 30 mkZncConf = confOpts: '' 31 Version = 1.6.3 32 ${concatMapStrings (n: "LoadModule = ${n}\n") confOpts.modules} 33 34 <Listener l> 35 Port = ${toString confOpts.port} 36 IPv4 = true 37 IPv6 = true 38 SSL = ${boolToString confOpts.useSSL} 39 </Listener> 40 41 <User ${confOpts.userName}> 42 ${confOpts.passBlock} 43 Admin = true 44 Nick = ${confOpts.nick} 45 AltNick = ${confOpts.nick}_ 46 Ident = ${confOpts.nick} 47 RealName = ${confOpts.nick} 48 ${concatMapStrings (n: "LoadModule = ${n}\n") confOpts.userModules} 49 50 ${ lib.concatStringsSep "\n" (lib.mapAttrsToList (name: net: '' 51 <Network ${name}> 52 ${concatMapStrings (m: "LoadModule = ${m}\n") net.modules} 53 Server = ${net.server} ${lib.optionalString net.useSSL "+"}${toString net.port} ${net.password} 54 ${concatMapStrings (c: "<Chan #${c}>\n</Chan>\n") net.channels} 55 ${lib.optionalString net.hasBitlbeeControlChannel '' 56 <Chan &bitlbee> 57 </Chan> 58 ''} 59 ${net.extraConf} 60 </Network> 61 '') confOpts.networks) } 62 </User> 63 ${confOpts.extraZncConf} 64 ''; 65 66 zncConfFile = pkgs.writeTextFile { 67 name = "znc.conf"; 68 text = if cfg.zncConf != "" 69 then cfg.zncConf 70 else mkZncConf cfg.confOptions; 71 }; 72 73 networkOpts = { ... }: { 74 options = { 75 server = mkOption { 76 type = types.str; 77 example = "chat.freenode.net"; 78 description = '' 79 IRC server address. 80 ''; 81 }; 82 83 port = mkOption { 84 type = types.int; 85 default = 6697; 86 example = 6697; 87 description = '' 88 IRC server port. 89 ''; 90 }; 91 92 userName = mkOption { 93 default = ""; 94 example = "johntron"; 95 type = types.string; 96 description = '' 97 A nick identity specific to the IRC server. 98 ''; 99 }; 100 101 password = mkOption { 102 type = types.str; 103 default = ""; 104 description = '' 105 IRC server password, such as for a Slack gateway. 106 ''; 107 }; 108 109 useSSL = mkOption { 110 type = types.bool; 111 default = true; 112 description = '' 113 Whether to use SSL to connect to the IRC server. 114 ''; 115 }; 116 117 modulePackages = mkOption { 118 type = types.listOf types.package; 119 default = []; 120 example = [ "pkgs.zncModules.push" "pkgs.zncModules.fish" ]; 121 description = '' 122 External ZNC modules to build. 123 ''; 124 }; 125 126 modules = mkOption { 127 type = types.listOf types.str; 128 default = [ "simple_away" ]; 129 example = literalExample "[ simple_away sasl ]"; 130 description = '' 131 ZNC modules to load. 132 ''; 133 }; 134 135 channels = mkOption { 136 type = types.listOf types.str; 137 default = []; 138 example = [ "nixos" ]; 139 description = '' 140 IRC channels to join. 141 ''; 142 }; 143 144 hasBitlbeeControlChannel = mkOption { 145 type = types.bool; 146 default = false; 147 description = '' 148 Whether to add the special Bitlbee operations channel. 149 ''; 150 }; 151 152 extraConf = mkOption { 153 default = ""; 154 type = types.lines; 155 example = '' 156 Encoding = ^UTF-8 157 FloodBurst = 4 158 FloodRate = 1.00 159 IRCConnectEnabled = true 160 Ident = johntron 161 JoinDelay = 0 162 Nick = johntron 163 ''; 164 description = '' 165 Extra config for the network. 166 ''; 167 }; 168 }; 169 }; 170 171in 172 173{ 174 175 ###### Interface 176 177 options = { 178 services.znc = { 179 enable = mkOption { 180 default = false; 181 type = types.bool; 182 description = '' 183 Enable a ZNC service for a user. 184 ''; 185 }; 186 187 user = mkOption { 188 default = "znc"; 189 example = "john"; 190 type = types.string; 191 description = '' 192 The name of an existing user account to use to own the ZNC server process. 193 If not specified, a default user will be created to own the process. 194 ''; 195 }; 196 197 group = mkOption { 198 default = ""; 199 example = "users"; 200 type = types.string; 201 description = '' 202 Group to own the ZNCserver process. 203 ''; 204 }; 205 206 dataDir = mkOption { 207 default = "/var/lib/znc/"; 208 example = "/home/john/.znc/"; 209 type = types.path; 210 description = '' 211 The data directory. Used for configuration files and modules. 212 ''; 213 }; 214 215 zncConf = mkOption { 216 default = ""; 217 example = "See: http://wiki.znc.in/Configuration"; 218 type = types.lines; 219 description = '' 220 Config file as generated with `znc --makeconf` to use for the whole ZNC configuration. 221 If specified, `confOptions` will be ignored, and this value, as-is, will be used. 222 If left empty, a conf file with default values will be used. 223 ''; 224 }; 225 226 confOptions = { 227 modules = mkOption { 228 type = types.listOf types.str; 229 default = [ "webadmin" "adminlog" ]; 230 example = [ "partyline" "webadmin" "adminlog" "log" ]; 231 description = '' 232 A list of modules to include in the `znc.conf` file. 233 ''; 234 }; 235 236 userModules = mkOption { 237 type = types.listOf types.str; 238 default = [ "chansaver" "controlpanel" ]; 239 example = [ "chansaver" "controlpanel" "fish" "push" ]; 240 description = '' 241 A list of user modules to include in the `znc.conf` file. 242 ''; 243 }; 244 245 userName = mkOption { 246 default = defaultUserName; 247 example = "johntron"; 248 type = types.string; 249 description = '' 250 The user name used to log in to the ZNC web admin interface. 251 ''; 252 }; 253 254 networks = mkOption { 255 default = { }; 256 type = with types; attrsOf (submodule networkOpts); 257 description = '' 258 IRC networks to connect the user to. 259 ''; 260 example = { 261 "freenode" = { 262 server = "chat.freenode.net"; 263 port = 6697; 264 ssl = true; 265 modules = [ "simple_away" ]; 266 }; 267 }; 268 }; 269 270 nick = mkOption { 271 default = "znc-user"; 272 example = "john"; 273 type = types.string; 274 description = '' 275 The IRC nick. 276 ''; 277 }; 278 279 passBlock = mkOption { 280 example = defaultPassBlock; 281 type = types.string; 282 description = '' 283 Generate with `nix-shell -p znc --command "znc --makepass"`. 284 This is the password used to log in to the ZNC web admin interface. 285 ''; 286 }; 287 288 port = mkOption { 289 default = 5000; 290 example = 5000; 291 type = types.int; 292 description = '' 293 Specifies the port on which to listen. 294 ''; 295 }; 296 297 useSSL = mkOption { 298 default = true; 299 type = types.bool; 300 description = '' 301 Indicates whether the ZNC server should use SSL when listening on the specified port. A self-signed certificate will be generated. 302 ''; 303 }; 304 305 extraZncConf = mkOption { 306 default = ""; 307 type = types.lines; 308 description = '' 309 Extra config to `znc.conf` file. 310 ''; 311 }; 312 }; 313 314 modulePackages = mkOption { 315 type = types.listOf types.package; 316 default = [ ]; 317 example = literalExample "[ pkgs.zncModules.fish pkgs.zncModules.push ]"; 318 description = '' 319 A list of global znc module packages to add to znc. 320 ''; 321 }; 322 323 mutable = mkOption { 324 default = false; 325 type = types.bool; 326 description = '' 327 Indicates whether to allow the contents of the `dataDir` directory to be changed 328 by the user at run-time. 329 If true, modifications to the ZNC configuration after its initial creation are not 330 overwritten by a NixOS system rebuild. 331 If false, the ZNC configuration is rebuilt by every system rebuild. 332 If the user wants to manage the ZNC service using the web admin interface, this value 333 should be set to true. 334 ''; 335 }; 336 337 extraFlags = mkOption { 338 default = [ ]; 339 example = [ "--debug" ]; 340 type = types.listOf types.str; 341 description = '' 342 Extra flags to use when executing znc command. 343 ''; 344 }; 345 }; 346 }; 347 348 349 ###### Implementation 350 351 config = mkIf cfg.enable { 352 353 systemd.services.znc = { 354 description = "ZNC Server"; 355 wantedBy = [ "multi-user.target" ]; 356 after = [ "network.service" ]; 357 serviceConfig = { 358 User = cfg.user; 359 Group = cfg.group; 360 Restart = "always"; 361 ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID"; 362 ExecStop = "${pkgs.coreutils}/bin/kill -INT $MAINPID"; 363 }; 364 preStart = '' 365 ${pkgs.coreutils}/bin/mkdir -p ${cfg.dataDir}/configs 366 367 # If mutable, regenerate conf file every time. 368 ${optionalString (!cfg.mutable) '' 369 ${pkgs.coreutils}/bin/echo "znc is set to be system-managed. Now deleting old znc.conf file to be regenerated." 370 ${pkgs.coreutils}/bin/rm -f ${cfg.dataDir}/configs/znc.conf 371 ''} 372 373 # Ensure essential files exist. 374 if [[ ! -f ${cfg.dataDir}/configs/znc.conf ]]; then 375 ${pkgs.coreutils}/bin/echo "No znc.conf file found in ${cfg.dataDir}. Creating one now." 376 ${pkgs.coreutils}/bin/cp --no-clobber ${zncConfFile} ${cfg.dataDir}/configs/znc.conf 377 ${pkgs.coreutils}/bin/chmod u+rw ${cfg.dataDir}/configs/znc.conf 378 ${pkgs.coreutils}/bin/chown ${cfg.user} ${cfg.dataDir}/configs/znc.conf 379 fi 380 381 if [[ ! -f ${cfg.dataDir}/znc.pem ]]; then 382 ${pkgs.coreutils}/bin/echo "No znc.pem file found in ${cfg.dataDir}. Creating one now." 383 ${pkgs.znc}/bin/znc --makepem --datadir ${cfg.dataDir} 384 fi 385 386 # Symlink modules 387 rm ${cfg.dataDir}/modules || true 388 ln -fs ${modules}/lib/znc ${cfg.dataDir}/modules 389 ''; 390 script = "${pkgs.znc}/bin/znc --foreground --datadir ${cfg.dataDir} ${toString cfg.extraFlags}"; 391 }; 392 393 users.extraUsers = optional (cfg.user == defaultUser) 394 { name = defaultUser; 395 description = "ZNC server daemon owner"; 396 group = defaultUser; 397 uid = config.ids.uids.znc; 398 home = cfg.dataDir; 399 createHome = true; 400 }; 401 402 users.extraGroups = optional (cfg.user == defaultUser) 403 { name = defaultUser; 404 gid = config.ids.gids.znc; 405 members = [ defaultUser ]; 406 }; 407 408 }; 409}