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