From 4bf8832d1bafa10e4db0930ef4f2b365ad722d3c Mon Sep 17 00:00:00 2001 From: Winzlieb Date: Fri, 17 Jun 2022 13:49:18 +0200 Subject: [PATCH] working postgreql import --- mediawiki.nix | 7 + module/location-options.nix | 54 ++++ module/mediawiki.nix | 475 ++++++++++++++++++++++++++++++++++++ module/vhost-options.nix | 295 ++++++++++++++++++++++ 4 files changed, 831 insertions(+) create mode 100644 mediawiki.nix create mode 100644 module/location-options.nix create mode 100644 module/mediawiki.nix create mode 100644 module/vhost-options.nix diff --git a/mediawiki.nix b/mediawiki.nix new file mode 100644 index 0000000..ddf84b9 --- /dev/null +++ b/mediawiki.nix @@ -0,0 +1,7 @@ +{ config, pkgs, lib, ... }: { + version = "1.27.0"; + src = with lib; pkgs.fetchurl { + url = "https://releases.wikimedia.org/mediawiki/${versions.majorMinor version}/mediawiki-${version}.tar.gz"; + sha256 = "sha256-x50AMSpLdJkn5PP5YAs7z5/pFKiYt/5PhRjp9Zro0Sg="; + }; +} diff --git a/module/location-options.nix b/module/location-options.nix new file mode 100644 index 0000000..8ea88f9 --- /dev/null +++ b/module/location-options.nix @@ -0,0 +1,54 @@ +{ config, lib, name, ... }: +let + inherit (lib) mkOption types; +in +{ + options = { + + proxyPass = mkOption { + type = with types; nullOr str; + default = null; + example = "http://www.example.org/"; + description = '' + Sets up a simple reverse proxy as described by . + ''; + }; + + index = mkOption { + type = with types; nullOr str; + default = null; + example = "index.php index.html"; + description = '' + Adds DirectoryIndex directive. See . + ''; + }; + + alias = mkOption { + type = with types; nullOr path; + default = null; + example = "/your/alias/directory"; + description = '' + Alias directory for requests. See . + ''; + }; + + extraConfig = mkOption { + type = types.lines; + default = ""; + description = '' + These lines go to the end of the location verbatim. + ''; + }; + + priority = mkOption { + type = types.int; + default = 1000; + description = '' + Order of this location block in relation to the others in the vhost. + The semantics are the same as with `lib.mkOrder`. Smaller values have + a greater priority. + ''; + }; + + }; +} diff --git a/module/mediawiki.nix b/module/mediawiki.nix new file mode 100644 index 0000000..40c715b --- /dev/null +++ b/module/mediawiki.nix @@ -0,0 +1,475 @@ +{ config, pkgs, lib, ... }: + +let + + inherit (lib) mkDefault mkEnableOption mkForce mkIf mkMerge mkOption; + inherit (lib) concatStringsSep literalExpression mapAttrsToList optional optionals optionalString types; + + cfg = config.services.mymediawiki; + fpm = config.services.phpfpm.pools.mediawiki; + user = "mediawiki"; + group = config.services.httpd.group; + cacheDir = "/var/cache/mediawiki"; + stateDir = "/var/lib/mediawiki"; + + pkg = pkgs.stdenv.mkDerivation rec { + pname = "mediawiki-full"; + version = src.version; + src = cfg.package; + + installPhase = '' + mkdir -p $out + cp -r * $out/ + + rm -rf $out/share/mediawiki/skins/* + rm -rf $out/share/mediawiki/extensions/* + + ${concatStringsSep "\n" (mapAttrsToList (k: v: '' + ln -s ${v} $out/share/mediawiki/skins/${k} + '') cfg.skins)} + + ${concatStringsSep "\n" (mapAttrsToList (k: v: '' + ln -s ${if v != null then v else "$src/share/mediawiki/extensions/${k}"} $out/share/mediawiki/extensions/${k} + '') cfg.extensions)} + ''; + }; + + mediawikiScripts = pkgs.runCommand "mediawiki-scripts" { + buildInputs = [ pkgs.makeWrapper ]; + preferLocalBuild = true; + } '' + mkdir -p $out/bin + for i in changePassword.php createAndPromote.php userOptions.php edit.php nukePage.php update.php; do + makeWrapper ${pkgs.php}/bin/php $out/bin/mediawiki-$(basename $i .php) \ + --set MEDIAWIKI_CONFIG ${mediawikiConfig} \ + --add-flags ${pkg}/share/mediawiki/maintenance/$i + done + ''; + + mediawikiConfig = pkgs.writeText "LocalSettings.php" '' + skins + subdirectory of the MediaWiki installation in addition to the default skins. + ''; + }; + + extensions = mkOption { + default = {}; + type = types.attrsOf (types.nullOr types.path); + description = '' + Attribute set of paths whose content is copied to the extensions + subdirectory of the MediaWiki installation and enabled in configuration. + + Use null instead of path to enable extensions that are part of MediaWiki. + ''; + example = literalExpression '' + { + Matomo = pkgs.fetchzip { + url = "https://github.com/DaSchTour/matomo-mediawiki-extension/archive/v4.0.1.tar.gz"; + sha256 = "0g5rd3zp0avwlmqagc59cg9bbkn3r7wx7p6yr80s644mj6dlvs1b"; + }; + ParserFunctions = null; + } + ''; + }; + + database = { + type = mkOption { + type = types.enum [ "mysql" "postgres" "sqlite" "mssql" "oracle" ]; + default = "mysql"; + description = "Database engine to use. MySQL/MariaDB is the database of choice by MediaWiki developers."; + }; + + host = mkOption { + type = types.str; + default = "localhost"; + description = "Database host address."; + }; + + port = mkOption { + type = types.port; + default = 3306; + description = "Database host port."; + }; + + name = mkOption { + type = types.str; + default = "mediawiki"; + description = "Database name."; + }; + + user = mkOption { + type = types.str; + default = "mediawiki"; + description = "Database user."; + }; + + passwordFile = mkOption { + type = types.nullOr types.path; + default = null; + example = "/run/keys/mediawiki-dbpassword"; + description = '' + A file containing the password corresponding to + . + ''; + }; + + tablePrefix = mkOption { + type = types.nullOr types.str; + default = null; + description = '' + If you only have access to a single database and wish to install more than + one version of MediaWiki, or have other applications that also use the + database, you can give the table names a unique prefix to stop any naming + conflicts or confusion. + See . + ''; + }; + + socket = mkOption { + type = types.nullOr types.path; + default = if cfg.database.createLocally then "/run/mysqld/mysqld.sock" else null; + defaultText = literalExpression "/run/mysqld/mysqld.sock"; + description = "Path to the unix socket file to use for authentication."; + }; + + createLocally = mkOption { + type = types.bool; + default = cfg.database.type == "mysql"; + defaultText = literalExpression "true"; + description = '' + Create the database and database user locally. + This currently only applies if database type "mysql" is selected. + ''; + }; + }; + + virtualHost = mkOption { + type = types.submodule (import ./vhost-options.nix); + example = literalExpression '' + { + hostName = "mediawiki.example.org"; + adminAddr = "webmaster@example.org"; + forceSSL = true; + enableACME = true; + } + ''; + description = '' + Apache configuration can be done by adapting . + See for further information. + ''; + }; + + poolConfig = mkOption { + type = with types; attrsOf (oneOf [ str int bool ]); + default = { + "pm" = "dynamic"; + "pm.max_children" = 32; + "pm.start_servers" = 2; + "pm.min_spare_servers" = 2; + "pm.max_spare_servers" = 4; + "pm.max_requests" = 500; + }; + description = '' + Options for the MediaWiki PHP pool. See the documentation on php-fpm.conf + for details on configuration directives. + ''; + }; + + extraConfig = mkOption { + type = types.lines; + description = '' + Any additional text to be appended to MediaWiki's + LocalSettings.php configuration file. For configuration + settings, see . + ''; + default = ""; + example = '' + $wgEnableEmail = false; + ''; + }; + + }; + }; + + # implementation + config = mkIf cfg.enable { + + assertions = [ + { assertion = cfg.database.createLocally -> cfg.database.type == "mysql"; + message = "services.mediawiki.createLocally is currently only supported for database type 'mysql'"; + } + { assertion = cfg.database.createLocally -> cfg.database.user == user; + message = "services.mediawiki.database.user must be set to ${user} if services.mediawiki.database.createLocally is set true"; + } + { assertion = cfg.database.createLocally -> cfg.database.socket != null; + message = "services.mediawiki.database.socket must be set if services.mediawiki.database.createLocally is set to true"; + } + { assertion = cfg.database.createLocally -> cfg.database.passwordFile == null; + message = "a password cannot be specified if services.mediawiki.database.createLocally is set to true"; + } + ]; + + services.mymediawiki.skins = { + Vector = "${cfg.package}/share/mediawiki/skins/Vector"; + }; + + services.mysql = mkIf cfg.database.createLocally { + enable = true; + package = mkDefault pkgs.mariadb; + ensureDatabases = [ cfg.database.name ]; + ensureUsers = [ + { name = cfg.database.user; + ensurePermissions = { "${cfg.database.name}.*" = "ALL PRIVILEGES"; }; + } + ]; + }; + + services.phpfpm.pools.mediawiki = { + inherit user group; + phpEnv.MEDIAWIKI_CONFIG = "${mediawikiConfig}"; + settings = { + "listen.owner" = config.services.httpd.user; + "listen.group" = config.services.httpd.group; + } // cfg.poolConfig; + }; + + services.httpd = { + enable = true; + extraModules = [ "proxy_fcgi" ]; + virtualHosts.${cfg.virtualHost.hostName} = mkMerge [ cfg.virtualHost { + documentRoot = mkForce "${pkg}/share/mediawiki"; + extraConfig = '' + + + + SetHandler "proxy:unix:${fpm.socket}|fcgi://localhost/" + + + + Require all granted + DirectoryIndex index.php + AllowOverride All + + '' + optionalString (cfg.uploadsDir != null) '' + Alias "/images" "${cfg.uploadsDir}" + + Require all granted + + ''; + } ]; + }; + + systemd.tmpfiles.rules = [ + "d '${stateDir}' 0750 ${user} ${group} - -" + "d '${cacheDir}' 0750 ${user} ${group} - -" + ] ++ optionals (cfg.uploadsDir != null) [ + "d '${cfg.uploadsDir}' 0750 ${user} ${group} - -" + "Z '${cfg.uploadsDir}' 0750 ${user} ${group} - -" + ]; + + systemd.services.mediawiki-init = { + wantedBy = [ "multi-user.target" ]; + before = [ "phpfpm-mediawiki.service" ]; + after = optional cfg.database.createLocally "mysql.service"; + script = '' + if ! test -e "${stateDir}/secret.key"; then + tr -dc A-Za-z0-9 /dev/null | head -c 64 > ${stateDir}/secret.key + fi + + echo "exit( wfGetDB( DB_MASTER )->tableExists( 'user' ) ? 1 : 0 );" | \ + ${pkgs.php}/bin/php ${pkg}/share/mediawiki/maintenance/eval.php --conf ${mediawikiConfig} && \ + ${pkgs.php}/bin/php ${pkg}/share/mediawiki/maintenance/install.php \ + --confpath /tmp \ + --scriptpath / \ + --dbserver ${cfg.database.host}${optionalString (cfg.database.socket != null) ":${cfg.database.socket}"} \ + --dbport ${toString cfg.database.port} \ + --dbname ${cfg.database.name} \ + ${optionalString (cfg.database.tablePrefix != null) "--dbprefix ${cfg.database.tablePrefix}"} \ + --dbuser ${cfg.database.user} \ + ${optionalString (cfg.database.passwordFile != null) "--dbpassfile ${cfg.database.passwordFile}"} \ + --passfile ${cfg.passwordFile} \ + ${cfg.name} \ + admin + + ${pkgs.php}/bin/php ${pkg}/share/mediawiki/maintenance/update.php --conf ${mediawikiConfig} --quick + ''; + + serviceConfig = { + Type = "oneshot"; + User = user; + Group = group; + PrivateTmp = true; + }; + }; + + systemd.services.httpd.after = optional (cfg.database.createLocally && cfg.database.type == "mysql") "mysql.service"; + + users.users.${user} = { + group = group; + isSystemUser = true; + }; + + environment.systemPackages = [ mediawikiScripts ]; + }; +} diff --git a/module/vhost-options.nix b/module/vhost-options.nix new file mode 100644 index 0000000..c52ab2c --- /dev/null +++ b/module/vhost-options.nix @@ -0,0 +1,295 @@ +{ config, lib, name, ... }: +let + inherit (lib) literalExpression mkOption nameValuePair types; +in +{ + options = { + + hostName = mkOption { + type = types.str; + default = name; + description = "Canonical hostname for the server."; + }; + + serverAliases = mkOption { + type = types.listOf types.str; + default = []; + example = ["www.example.org" "www.example.org:8080" "example.org"]; + description = '' + Additional names of virtual hosts served by this virtual host configuration. + ''; + }; + + listen = mkOption { + type = with types; listOf (submodule ({ + options = { + port = mkOption { + type = types.port; + description = "Port to listen on"; + }; + ip = mkOption { + type = types.str; + default = "*"; + description = "IP to listen on. 0.0.0.0 for IPv4 only, * for all."; + }; + ssl = mkOption { + type = types.bool; + default = false; + description = "Whether to enable SSL (https) support."; + }; + }; + })); + default = []; + example = [ + { ip = "195.154.1.1"; port = 443; ssl = true;} + { ip = "192.154.1.1"; port = 80; } + { ip = "*"; port = 8080; } + ]; + description = '' + Listen addresses and ports for this virtual host. + + + This option overrides addSSL, forceSSL and onlySSL. + + + If you only want to set the addresses manually and not the ports, take a look at listenAddresses. + + + ''; + }; + + listenAddresses = mkOption { + type = with types; nonEmptyListOf str; + + description = '' + Listen addresses for this virtual host. + Compared to listen this only sets the addreses + and the ports are chosen automatically. + ''; + default = [ "*" ]; + example = [ "127.0.0.1" ]; + }; + + enableSSL = mkOption { + type = types.bool; + visible = false; + default = false; + }; + + addSSL = mkOption { + type = types.bool; + default = false; + description = '' + Whether to enable HTTPS in addition to plain HTTP. This will set defaults for + listen to listen on all interfaces on the respective default + ports (80, 443). + ''; + }; + + onlySSL = mkOption { + type = types.bool; + default = false; + description = '' + Whether to enable HTTPS and reject plain HTTP connections. This will set + defaults for listen to listen on all interfaces on port 443. + ''; + }; + + forceSSL = mkOption { + type = types.bool; + default = false; + description = '' + Whether to add a separate nginx server block that permanently redirects (301) + all plain HTTP traffic to HTTPS. This will set defaults for + listen to listen on all interfaces on the respective default + ports (80, 443), where the non-SSL listens are used for the redirect vhosts. + ''; + }; + + enableACME = mkOption { + type = types.bool; + default = false; + description = '' + Whether to ask Let's Encrypt to sign a certificate for this vhost. + Alternately, you can use an existing certificate through . + ''; + }; + + useACMEHost = mkOption { + type = types.nullOr types.str; + default = null; + description = '' + A host of an existing Let's Encrypt certificate to use. + This is useful if you have many subdomains and want to avoid hitting the + rate limit. + Alternately, you can generate a certificate through . + Note that this option does not create any certificates, nor it does add subdomains to existing ones – you will need to create them manually using . + ''; + }; + + acmeRoot = mkOption { + type = types.nullOr types.str; + default = "/var/lib/acme/acme-challenge"; + description = '' + Directory for the acme challenge which is PUBLIC, don't put certs or keys in here. + Set to null to inherit from config.security.acme. + ''; + }; + + sslServerCert = mkOption { + type = types.path; + example = "/var/host.cert"; + description = "Path to server SSL certificate."; + }; + + sslServerKey = mkOption { + type = types.path; + example = "/var/host.key"; + description = "Path to server SSL certificate key."; + }; + + sslServerChain = mkOption { + type = types.nullOr types.path; + default = null; + example = "/var/ca.pem"; + description = "Path to server SSL chain file."; + }; + + http2 = mkOption { + type = types.bool; + default = true; + description = '' + Whether to enable HTTP 2. HTTP/2 is supported in all multi-processing modules that come with httpd. However, if you use the prefork mpm, there will + be severe restrictions. Refer to for details. + ''; + }; + + adminAddr = mkOption { + type = types.nullOr types.str; + default = null; + example = "admin@example.org"; + description = "E-mail address of the server administrator."; + }; + + documentRoot = mkOption { + type = types.nullOr types.path; + default = null; + example = "/data/webserver/docs"; + description = '' + The path of Apache's document root directory. If left undefined, + an empty directory in the Nix store will be used as root. + ''; + }; + + servedDirs = mkOption { + type = types.listOf types.attrs; + default = []; + example = [ + { urlPath = "/nix"; + dir = "/home/eelco/Dev/nix-homepage"; + } + ]; + description = '' + This option provides a simple way to serve static directories. + ''; + }; + + servedFiles = mkOption { + type = types.listOf types.attrs; + default = []; + example = [ + { urlPath = "/foo/bar.png"; + file = "/home/eelco/some-file.png"; + } + ]; + description = '' + This option provides a simple way to serve individual, static files. + + + This option has been deprecated and will be removed in a future + version of NixOS. You can achieve the same result by making use of + the locations.<name>.alias option. + + ''; + }; + + extraConfig = mkOption { + type = types.lines; + default = ""; + example = '' + + Options FollowSymlinks + AllowOverride All + + ''; + description = '' + These lines go to httpd.conf verbatim. They will go after + directories and directory aliases defined by default. + ''; + }; + + enableUserDir = mkOption { + type = types.bool; + default = false; + description = '' + Whether to enable serving ~/public_html as + /~username. + ''; + }; + + globalRedirect = mkOption { + type = types.nullOr types.str; + default = null; + example = "http://newserver.example.org/"; + description = '' + If set, all requests for this host are redirected permanently to + the given URL. + ''; + }; + + logFormat = mkOption { + type = types.str; + default = "common"; + example = "combined"; + description = '' + Log format for Apache's log files. Possible values are: combined, common, referer, agent. + ''; + }; + + robotsEntries = mkOption { + type = types.lines; + default = ""; + example = "Disallow: /foo/"; + description = '' + Specification of pages to be ignored by web crawlers. See for details. + ''; + }; + + locations = mkOption { + type = with types; attrsOf (submodule (import ./location-options.nix)); + default = {}; + example = literalExpression '' + { + "/" = { + proxyPass = "http://localhost:3000"; + }; + "/foo/bar.png" = { + alias = "/home/eelco/some-file.png"; + }; + }; + ''; + description = '' + Declarative location config. See for details. + ''; + }; + + }; + + config = { + + locations = builtins.listToAttrs (map (elem: nameValuePair elem.urlPath { alias = elem.file; }) config.servedFiles); + + }; +}