at 23.11-pre 5.2 kB view raw
1{ config, lib, pkgs, ... }: 2 3with lib; 4 5let 6 cfg = config.services.supybot; 7 isStateDirHome = hasPrefix "/home/" cfg.stateDir; 8 isStateDirVar = cfg.stateDir == "/var/lib/supybot"; 9 pyEnv = pkgs.python3.withPackages (p: [ p.limnoria ] ++ (cfg.extraPackages p)); 10in 11{ 12 options = { 13 14 services.supybot = { 15 16 enable = mkOption { 17 type = types.bool; 18 default = false; 19 description = lib.mdDoc "Enable Supybot, an IRC bot (also known as Limnoria)."; 20 }; 21 22 stateDir = mkOption { 23 type = types.path; 24 default = if versionAtLeast config.system.stateVersion "20.09" 25 then "/var/lib/supybot" 26 else "/home/supybot"; 27 defaultText = literalExpression "/var/lib/supybot"; 28 description = lib.mdDoc "The root directory, logs and plugins are stored here"; 29 }; 30 31 configFile = mkOption { 32 type = types.path; 33 description = lib.mdDoc '' 34 Path to initial supybot config file. This can be generated by 35 running supybot-wizard. 36 37 Note: all paths should include the full path to the stateDir 38 directory (backup conf data logs logs/plugins plugins tmp web). 39 ''; 40 }; 41 42 plugins = mkOption { 43 type = types.attrsOf types.path; 44 default = {}; 45 description = lib.mdDoc '' 46 Attribute set of additional plugins that will be symlinked to the 47 {file}`plugin` subdirectory. 48 49 Please note that you still need to add the plugins to the config 50 file (or with `!load`) using their attribute name. 51 ''; 52 example = literalExpression '' 53 let 54 plugins = pkgs.fetchzip { 55 url = "https://github.com/ProgVal/Supybot-plugins/archive/57c2450c.zip"; 56 sha256 = "077snf84ibnva3sbpzdfpfma6hcdw7dflwnhg6pw7mgnf0nd84qd"; 57 }; 58 in 59 { 60 Wikipedia = "''${plugins}/Wikipedia"; 61 Decide = ./supy-decide; 62 } 63 ''; 64 }; 65 66 extraPackages = mkOption { 67 type = types.functionTo (types.listOf types.package); 68 default = p: []; 69 defaultText = literalExpression "p: []"; 70 description = lib.mdDoc '' 71 Extra Python packages available to supybot plugins. The 72 value must be a function which receives the attrset defined 73 in {var}`python3Packages` as the sole argument. 74 ''; 75 example = literalExpression "p: [ p.lxml p.requests ]"; 76 }; 77 78 }; 79 80 }; 81 82 config = mkIf cfg.enable { 83 84 environment.systemPackages = [ pkgs.python3Packages.limnoria ]; 85 86 users.users.supybot = { 87 uid = config.ids.uids.supybot; 88 group = "supybot"; 89 description = "Supybot IRC bot user"; 90 home = cfg.stateDir; 91 isSystemUser = true; 92 }; 93 94 users.groups.supybot = { 95 gid = config.ids.gids.supybot; 96 }; 97 98 systemd.services.supybot = { 99 description = "Supybot, an IRC bot"; 100 documentation = [ "https://limnoria.readthedocs.io/" ]; 101 after = [ "network.target" ]; 102 wantedBy = [ "multi-user.target" ]; 103 preStart = '' 104 # This needs to be created afresh every time 105 rm -f '${cfg.stateDir}/supybot.cfg.bak' 106 ''; 107 108 startLimitIntervalSec = 5 * 60; # 5 min 109 startLimitBurst = 1; 110 serviceConfig = { 111 ExecStart = "${pyEnv}/bin/supybot ${cfg.stateDir}/supybot.cfg"; 112 PIDFile = "/run/supybot.pid"; 113 User = "supybot"; 114 Group = "supybot"; 115 UMask = "0007"; 116 Restart = "on-abort"; 117 118 NoNewPrivileges = true; 119 PrivateDevices = true; 120 PrivateMounts = true; 121 PrivateTmp = true; 122 ProtectControlGroups = true; 123 ProtectKernelModules = true; 124 ProtectKernelTunables = true; 125 RestrictAddressFamilies = [ "AF_INET" "AF_INET6" ]; 126 RestrictSUIDSGID = true; 127 SystemCallArchitectures = "native"; 128 RestrictNamespaces = true; 129 RestrictRealtime = true; 130 LockPersonality = true; 131 MemoryDenyWriteExecute = true; 132 RemoveIPC = true; 133 ProtectHostname = true; 134 CapabilityBoundingSet = ""; 135 ProtectSystem = "full"; 136 } 137 // optionalAttrs isStateDirVar { 138 StateDirectory = "supybot"; 139 ProtectSystem = "strict"; 140 } 141 // optionalAttrs (!isStateDirHome) { 142 ProtectHome = true; 143 }; 144 }; 145 146 systemd.tmpfiles.rules = [ 147 "d '${cfg.stateDir}' 0700 supybot supybot - -" 148 "d '${cfg.stateDir}/backup' 0750 supybot supybot - -" 149 "d '${cfg.stateDir}/conf' 0750 supybot supybot - -" 150 "d '${cfg.stateDir}/data' 0750 supybot supybot - -" 151 "d '${cfg.stateDir}/plugins' 0750 supybot supybot - -" 152 "d '${cfg.stateDir}/logs' 0750 supybot supybot - -" 153 "d '${cfg.stateDir}/logs/plugins' 0750 supybot supybot - -" 154 "d '${cfg.stateDir}/tmp' 0750 supybot supybot - -" 155 "d '${cfg.stateDir}/web' 0750 supybot supybot - -" 156 "L '${cfg.stateDir}/supybot.cfg' - - - - ${cfg.configFile}" 157 ] 158 ++ (flip mapAttrsToList cfg.plugins (name: dest: 159 "L+ '${cfg.stateDir}/plugins/${name}' - - - - ${dest}" 160 )); 161 162 }; 163}