at 21.11-pre 11 kB view raw
1{ config, lib, pkgs, ... }: 2with lib; 3 4let 5 cfg = config.services.moinmoin; 6 python = pkgs.python27; 7 pkg = python.pkgs.moinmoin; 8 dataDir = "/var/lib/moin"; 9 usingGunicorn = cfg.webServer == "nginx-gunicorn" || cfg.webServer == "gunicorn"; 10 usingNginx = cfg.webServer == "nginx-gunicorn"; 11 user = "moin"; 12 group = "moin"; 13 14 uLit = s: ''u"${s}"''; 15 indentLines = n: str: concatMapStrings (line: "${fixedWidthString n " " " "}${line}\n") (splitString "\n" str); 16 17 moinCliWrapper = wikiIdent: pkgs.writeShellScriptBin "moin-${wikiIdent}" '' 18 ${pkgs.su}/bin/su -s ${pkgs.runtimeShell} -c "${pkg}/bin/moin --config-dir=/var/lib/moin/${wikiIdent}/config $*" ${user} 19 ''; 20 21 wikiConfig = wikiIdent: w: '' 22 # -*- coding: utf-8 -*- 23 24 from MoinMoin.config import multiconfig, url_prefix_static 25 26 class Config(multiconfig.DefaultConfig): 27 ${optionalString (w.webLocation != "/") '' 28 url_prefix_static = '${w.webLocation}' + url_prefix_static 29 ''} 30 31 sitename = u'${w.siteName}' 32 page_front_page = u'${w.frontPage}' 33 34 data_dir = '${dataDir}/${wikiIdent}/data' 35 data_underlay_dir = '${dataDir}/${wikiIdent}/underlay' 36 37 language_default = u'${w.languageDefault}' 38 ${optionalString (w.superUsers != []) '' 39 superuser = [${concatMapStringsSep ", " uLit w.superUsers}] 40 ''} 41 42 ${indentLines 4 w.extraConfig} 43 ''; 44 wikiConfigFile = name: wiki: pkgs.writeText "${name}.py" (wikiConfig name wiki); 45 46in 47{ 48 options.services.moinmoin = with types; { 49 enable = mkEnableOption "MoinMoin Wiki Engine"; 50 51 webServer = mkOption { 52 type = enum [ "nginx-gunicorn" "gunicorn" "none" ]; 53 default = "nginx-gunicorn"; 54 example = "none"; 55 description = '' 56 Which web server to use to serve the wiki. 57 Use <literal>none</literal> if you want to configure this yourself. 58 ''; 59 }; 60 61 gunicorn.workers = mkOption { 62 type = ints.positive; 63 default = 3; 64 example = 10; 65 description = '' 66 The number of worker processes for handling requests. 67 ''; 68 }; 69 70 wikis = mkOption { 71 type = attrsOf (submodule ({ name, ... }: { 72 options = { 73 siteName = mkOption { 74 type = str; 75 default = "Untitled Wiki"; 76 example = "ExampleWiki"; 77 description = '' 78 Short description of your wiki site, displayed below the logo on each page, and 79 used in RSS documents as the channel title. 80 ''; 81 }; 82 83 webHost = mkOption { 84 type = str; 85 description = "Host part of the wiki URL. If undefined, the name of the attribute set will be used."; 86 example = "wiki.example.org"; 87 }; 88 89 webLocation = mkOption { 90 type = str; 91 default = "/"; 92 example = "/moin"; 93 description = "Location part of the wiki URL."; 94 }; 95 96 frontPage = mkOption { 97 type = str; 98 default = "LanguageSetup"; 99 example = "FrontPage"; 100 description = '' 101 Front page name. Set this to something like <literal>FrontPage</literal> once languages are 102 configured. 103 ''; 104 }; 105 106 superUsers = mkOption { 107 type = listOf str; 108 default = []; 109 example = [ "elvis" ]; 110 description = '' 111 List of trusted user names with wiki system administration super powers. 112 113 Please note that accounts for these users need to be created using the <command>moin</command> command-line utility, e.g.: 114 <command>moin-<replaceable>WIKINAME</replaceable> account create --name=<replaceable>NAME</replaceable> --email=<replaceable>EMAIL</replaceable> --password=<replaceable>PASSWORD</replaceable></command>. 115 ''; 116 }; 117 118 languageDefault = mkOption { 119 type = str; 120 default = "en"; 121 example = "de"; 122 description = "The ISO-639-1 name of the main wiki language. Languages that MoinMoin does not support are ignored."; 123 }; 124 125 extraConfig = mkOption { 126 type = lines; 127 default = ""; 128 example = '' 129 show_hosts = True 130 search_results_per_page = 100 131 acl_rights_default = u"Known:read,write,delete,revert All:read" 132 logo_string = u"<h2>\U0001f639</h2>" 133 theme_default = u"modernized" 134 135 user_checkbox_defaults = {'show_page_trail': 0, 'edit_on_doubleclick': 0} 136 navi_bar = [u'SomePage'] + multiconfig.DefaultConfig.navi_bar 137 actions_excluded = multiconfig.DefaultConfig.actions_excluded + ['newaccount'] 138 139 mail_smarthost = "mail.example.org" 140 mail_from = u"Example.Org Wiki <wiki@example.org>" 141 ''; 142 description = '' 143 Additional configuration to be appended verbatim to this wiki's config. 144 145 See <link xlink:href='http://moinmo.in/HelpOnConfiguration' /> for documentation. 146 ''; 147 }; 148 149 }; 150 config = { 151 webHost = mkDefault name; 152 }; 153 })); 154 example = literalExample '' 155 { 156 "mywiki" = { 157 siteName = "Example Wiki"; 158 webHost = "wiki.example.org"; 159 superUsers = [ "admin" ]; 160 frontPage = "Index"; 161 extraConfig = "page_category_regex = ur'(?P<all>(Category|Kategorie)(?P<key>(?!Template)\S+))'" 162 }; 163 } 164 ''; 165 description = '' 166 Configurations of the individual wikis. Attribute names must be valid Python 167 identifiers of the form <literal>[A-Za-z_][A-Za-z0-9_]*</literal>. 168 169 For every attribute <replaceable>WIKINAME</replaceable>, a helper script 170 moin-<replaceable>WIKINAME</replaceable> is created which runs the 171 <command>moin</command> command under the <literal>moin</literal> user (to avoid 172 file ownership issues) and with the right configuration directory passed to it. 173 ''; 174 }; 175 }; 176 177 config = mkIf cfg.enable { 178 assertions = forEach (attrNames cfg.wikis) (wname: 179 { assertion = builtins.match "[A-Za-z_][A-Za-z0-9_]*" wname != null; 180 message = "${wname} is not valid Python identifier"; 181 } 182 ); 183 184 users.users = { 185 moin = { 186 description = "MoinMoin wiki"; 187 home = dataDir; 188 group = group; 189 isSystemUser = true; 190 }; 191 }; 192 193 users.groups = { 194 moin = { 195 members = mkIf usingNginx [ config.services.nginx.user ]; 196 }; 197 }; 198 199 environment.systemPackages = [ pkg ] ++ map moinCliWrapper (attrNames cfg.wikis); 200 201 systemd.services = mkIf usingGunicorn 202 (flip mapAttrs' cfg.wikis (wikiIdent: wiki: 203 nameValuePair "moin-${wikiIdent}" 204 { 205 description = "MoinMoin wiki ${wikiIdent} - gunicorn process"; 206 wantedBy = [ "multi-user.target" ]; 207 after = [ "network.target" ]; 208 restartIfChanged = true; 209 restartTriggers = [ (wikiConfigFile wikiIdent wiki) ]; 210 211 environment = let 212 penv = python.buildEnv.override { 213 # setuptools: https://github.com/benoitc/gunicorn/issues/1716 214 extraLibs = [ python.pkgs.eventlet python.pkgs.setuptools pkg ]; 215 }; 216 in { 217 PYTHONPATH = "${dataDir}/${wikiIdent}/config:${penv}/${python.sitePackages}"; 218 }; 219 220 preStart = '' 221 umask 0007 222 rm -rf ${dataDir}/${wikiIdent}/underlay 223 cp -r ${pkg}/share/moin/underlay ${dataDir}/${wikiIdent}/ 224 chmod -R u+w ${dataDir}/${wikiIdent}/underlay 225 ''; 226 227 startLimitIntervalSec = 30; 228 229 serviceConfig = { 230 User = user; 231 Group = group; 232 WorkingDirectory = "${dataDir}/${wikiIdent}"; 233 ExecStart = ''${python.pkgs.gunicorn}/bin/gunicorn moin_wsgi \ 234 --name gunicorn-${wikiIdent} \ 235 --workers ${toString cfg.gunicorn.workers} \ 236 --worker-class eventlet \ 237 --bind unix:/run/moin/${wikiIdent}/gunicorn.sock 238 ''; 239 240 Restart = "on-failure"; 241 RestartSec = "2s"; 242 243 StateDirectory = "moin/${wikiIdent}"; 244 StateDirectoryMode = "0750"; 245 RuntimeDirectory = "moin/${wikiIdent}"; 246 RuntimeDirectoryMode = "0750"; 247 248 NoNewPrivileges = true; 249 ProtectSystem = "strict"; 250 ProtectHome = true; 251 PrivateTmp = true; 252 PrivateDevices = true; 253 PrivateNetwork = true; 254 ProtectKernelTunables = true; 255 ProtectKernelModules = true; 256 ProtectControlGroups = true; 257 RestrictAddressFamilies = [ "AF_UNIX" "AF_INET" "AF_INET6" ]; 258 RestrictNamespaces = true; 259 LockPersonality = true; 260 MemoryDenyWriteExecute = true; 261 RestrictRealtime = true; 262 }; 263 } 264 )); 265 266 services.nginx = mkIf usingNginx { 267 enable = true; 268 virtualHosts = flip mapAttrs' cfg.wikis (name: w: nameValuePair w.webHost { 269 forceSSL = mkDefault true; 270 enableACME = mkDefault true; 271 locations."${w.webLocation}" = { 272 extraConfig = '' 273 proxy_set_header Host $host; 274 proxy_set_header X-Real-IP $remote_addr; 275 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 276 proxy_set_header X-Forwarded-Proto $scheme; 277 proxy_set_header X-Forwarded-Host $host; 278 proxy_set_header X-Forwarded-Server $host; 279 280 proxy_pass http://unix:/run/moin/${name}/gunicorn.sock; 281 ''; 282 }; 283 }); 284 }; 285 286 systemd.tmpfiles.rules = [ 287 "d /run/moin 0750 ${user} ${group} - -" 288 "d ${dataDir} 0550 ${user} ${group} - -" 289 ] 290 ++ (concatLists (flip mapAttrsToList cfg.wikis (wikiIdent: wiki: [ 291 "d ${dataDir}/${wikiIdent} 0750 ${user} ${group} - -" 292 "d ${dataDir}/${wikiIdent}/config 0550 ${user} ${group} - -" 293 "L+ ${dataDir}/${wikiIdent}/config/wikiconfig.py - - - - ${wikiConfigFile wikiIdent wiki}" 294 # needed in order to pass module name to gunicorn 295 "L+ ${dataDir}/${wikiIdent}/config/moin_wsgi.py - - - - ${pkg}/share/moin/server/moin.wsgi" 296 # seed data files 297 "C ${dataDir}/${wikiIdent}/data 0770 ${user} ${group} - ${pkg}/share/moin/data" 298 # fix nix store permissions 299 "Z ${dataDir}/${wikiIdent}/data 0770 ${user} ${group} - -" 300 ]))); 301 }; 302 303 meta.maintainers = with lib.maintainers; [ mmilata ]; 304}