at 21.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 = "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 = "/var/lib/supybot"; 28 description = "The root directory, logs and plugins are stored here"; 29 }; 30 31 configFile = mkOption { 32 type = types.path; 33 description = '' 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 = '' 46 Attribute set of additional plugins that will be symlinked to the 47 <filename>plugin</filename> subdirectory. 48 49 Please note that you still need to add the plugins to the config 50 file (or with <literal>!load</literal>) using their attribute name. 51 ''; 52 example = literalExample '' 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 description = '' 70 Extra Python packages available to supybot plugins. The 71 value must be a function which receives the attrset defined 72 in <varname>python3Packages</varname> as the sole argument. 73 ''; 74 example = literalExample "p: [ p.lxml p.requests ]"; 75 }; 76 77 }; 78 79 }; 80 81 config = mkIf cfg.enable { 82 83 environment.systemPackages = [ pkgs.python3Packages.limnoria ]; 84 85 users.users.supybot = { 86 uid = config.ids.uids.supybot; 87 group = "supybot"; 88 description = "Supybot IRC bot user"; 89 home = cfg.stateDir; 90 isSystemUser = true; 91 }; 92 93 users.groups.supybot = { 94 gid = config.ids.gids.supybot; 95 }; 96 97 systemd.services.supybot = { 98 description = "Supybot, an IRC bot"; 99 documentation = [ "https://limnoria.readthedocs.io/" ]; 100 after = [ "network.target" ]; 101 wantedBy = [ "multi-user.target" ]; 102 preStart = '' 103 # This needs to be created afresh every time 104 rm -f '${cfg.stateDir}/supybot.cfg.bak' 105 ''; 106 107 startLimitIntervalSec = 5 * 60; # 5 min 108 startLimitBurst = 1; 109 serviceConfig = { 110 ExecStart = "${pyEnv}/bin/supybot ${cfg.stateDir}/supybot.cfg"; 111 PIDFile = "/run/supybot.pid"; 112 User = "supybot"; 113 Group = "supybot"; 114 UMask = "0007"; 115 Restart = "on-abort"; 116 117 NoNewPrivileges = true; 118 PrivateDevices = true; 119 PrivateMounts = true; 120 PrivateTmp = true; 121 ProtectControlGroups = true; 122 ProtectKernelModules = true; 123 ProtectKernelTunables = true; 124 RestrictAddressFamilies = [ "AF_INET" "AF_INET6" ]; 125 RestrictSUIDSGID = true; 126 SystemCallArchitectures = "native"; 127 RestrictNamespaces = true; 128 RestrictRealtime = true; 129 LockPersonality = true; 130 MemoryDenyWriteExecute = true; 131 RemoveIPC = true; 132 ProtectHostname = true; 133 CapabilityBoundingSet = ""; 134 ProtectSystem = "full"; 135 } 136 // optionalAttrs isStateDirVar { 137 StateDirectory = "supybot"; 138 ProtectSystem = "strict"; 139 } 140 // optionalAttrs (!isStateDirHome) { 141 ProtectHome = true; 142 }; 143 }; 144 145 systemd.tmpfiles.rules = [ 146 "d '${cfg.stateDir}' 0700 supybot supybot - -" 147 "d '${cfg.stateDir}/backup' 0750 supybot supybot - -" 148 "d '${cfg.stateDir}/conf' 0750 supybot supybot - -" 149 "d '${cfg.stateDir}/data' 0750 supybot supybot - -" 150 "d '${cfg.stateDir}/plugins' 0750 supybot supybot - -" 151 "d '${cfg.stateDir}/logs' 0750 supybot supybot - -" 152 "d '${cfg.stateDir}/logs/plugins' 0750 supybot supybot - -" 153 "d '${cfg.stateDir}/tmp' 0750 supybot supybot - -" 154 "d '${cfg.stateDir}/web' 0750 supybot supybot - -" 155 "L '${cfg.stateDir}/supybot.cfg' - - - - ${cfg.configFile}" 156 ] 157 ++ (flip mapAttrsToList cfg.plugins (name: dest: 158 "L+ '${cfg.stateDir}/plugins/${name}' - - - - ${dest}" 159 )); 160 161 }; 162}