{ config, lib, pkgs, ... }: with lib; let cfg = config.services.supybot; isStateDirHome = hasPrefix "/home/" cfg.stateDir; isStateDirVar = cfg.stateDir == "/var/lib/supybot"; pyEnv = pkgs.python3.withPackages (p: [ p.limnoria ] ++ (cfg.extraPackages p)); in { options = { services.supybot = { enable = mkOption { type = types.bool; default = false; description = "Enable Supybot, an IRC bot (also known as Limnoria)."; }; stateDir = mkOption { type = types.path; default = if versionAtLeast config.system.stateVersion "20.09" then "/var/lib/supybot" else "/home/supybot"; defaultText = literalExpression "/var/lib/supybot"; description = "The root directory, logs and plugins are stored here"; }; configFile = mkOption { type = types.path; description = '' Path to initial supybot config file. This can be generated by running supybot-wizard. Note: all paths should include the full path to the stateDir directory (backup conf data logs logs/plugins plugins tmp web). ''; }; plugins = mkOption { type = types.attrsOf types.path; default = {}; description = '' Attribute set of additional plugins that will be symlinked to the {file}`plugin` subdirectory. Please note that you still need to add the plugins to the config file (or with `!load`) using their attribute name. ''; example = literalExpression '' let plugins = pkgs.fetchzip { url = "https://github.com/ProgVal/Supybot-plugins/archive/57c2450c.zip"; sha256 = "077snf84ibnva3sbpzdfpfma6hcdw7dflwnhg6pw7mgnf0nd84qd"; }; in { Wikipedia = "''${plugins}/Wikipedia"; Decide = ./supy-decide; } ''; }; extraPackages = mkOption { type = types.functionTo (types.listOf types.package); default = p: []; defaultText = literalExpression "p: []"; description = '' Extra Python packages available to supybot plugins. The value must be a function which receives the attrset defined in {var}`python3Packages` as the sole argument. ''; example = literalExpression "p: [ p.lxml p.requests ]"; }; }; }; config = mkIf cfg.enable { environment.systemPackages = [ pkgs.python3Packages.limnoria ]; users.users.supybot = { uid = config.ids.uids.supybot; group = "supybot"; description = "Supybot IRC bot user"; home = cfg.stateDir; isSystemUser = true; }; users.groups.supybot = { gid = config.ids.gids.supybot; }; systemd.services.supybot = { description = "Supybot, an IRC bot"; documentation = [ "https://limnoria.readthedocs.io/" ]; after = [ "network.target" ]; wantedBy = [ "multi-user.target" ]; preStart = '' # This needs to be created afresh every time rm -f '${cfg.stateDir}/supybot.cfg.bak' ''; startLimitIntervalSec = 5 * 60; # 5 min startLimitBurst = 1; serviceConfig = { ExecStart = "${pyEnv}/bin/supybot ${cfg.stateDir}/supybot.cfg"; PIDFile = "/run/supybot.pid"; User = "supybot"; Group = "supybot"; UMask = "0007"; Restart = "on-abort"; NoNewPrivileges = true; PrivateDevices = true; PrivateMounts = true; PrivateTmp = true; ProtectControlGroups = true; ProtectKernelModules = true; ProtectKernelTunables = true; RestrictAddressFamilies = [ "AF_INET" "AF_INET6" ]; RestrictSUIDSGID = true; SystemCallArchitectures = "native"; RestrictNamespaces = true; RestrictRealtime = true; LockPersonality = true; MemoryDenyWriteExecute = true; RemoveIPC = true; ProtectHostname = true; CapabilityBoundingSet = ""; ProtectSystem = "full"; } // optionalAttrs isStateDirVar { StateDirectory = "supybot"; ProtectSystem = "strict"; } // optionalAttrs (!isStateDirHome) { ProtectHome = true; }; }; systemd.tmpfiles.rules = [ "d '${cfg.stateDir}' 0700 supybot supybot - -" "d '${cfg.stateDir}/backup' 0750 supybot supybot - -" "d '${cfg.stateDir}/conf' 0750 supybot supybot - -" "d '${cfg.stateDir}/data' 0750 supybot supybot - -" "d '${cfg.stateDir}/plugins' 0750 supybot supybot - -" "d '${cfg.stateDir}/logs' 0750 supybot supybot - -" "d '${cfg.stateDir}/logs/plugins' 0750 supybot supybot - -" "d '${cfg.stateDir}/tmp' 0750 supybot supybot - -" "d '${cfg.stateDir}/web' 0750 supybot supybot - -" "L '${cfg.stateDir}/supybot.cfg' - - - - ${cfg.configFile}" ] ++ (flip mapAttrsToList cfg.plugins (name: dest: "L+ '${cfg.stateDir}/plugins/${name}' - - - - ${dest}" )); }; }