1{ config, lib, pkgs, serverInfo, ... }: 2# http://codex.wordpress.org/Hardening_WordPress 3 4with lib; 5 6let 7 # Our bare-bones wp-config.php file using the above settings 8 wordpressConfig = pkgs.writeText "wp-config.php" '' 9 <?php 10 define('DB_NAME', '${config.dbName}'); 11 define('DB_USER', '${config.dbUser}'); 12 define('DB_PASSWORD', file_get_contents('${config.dbPasswordFile}')); 13 define('DB_HOST', '${config.dbHost}'); 14 define('DB_CHARSET', 'utf8'); 15 $table_prefix = '${config.tablePrefix}'; 16 define('AUTOMATIC_UPDATER_DISABLED', true); 17 ${config.extraConfig} 18 if ( !defined('ABSPATH') ) 19 define('ABSPATH', dirname(__FILE__) . '/'); 20 require_once(ABSPATH . 'wp-settings.php'); 21 ''; 22 23 # .htaccess to support pretty URLs 24 htaccess = pkgs.writeText "htaccess" '' 25 <IfModule mod_rewrite.c> 26 RewriteEngine On 27 RewriteBase / 28 RewriteRule ^index\.php$ - [L] 29 30 # add a trailing slash to /wp-admin 31 RewriteRule ^wp-admin$ wp-admin/ [R=301,L] 32 33 RewriteCond %{REQUEST_FILENAME} -f [OR] 34 RewriteCond %{REQUEST_FILENAME} -d 35 RewriteRule ^ - [L] 36 RewriteRule ^(wp-(content|admin|includes).*) $1 [L] 37 RewriteRule ^(.*\.php)$ $1 [L] 38 RewriteRule . index.php [L] 39 </IfModule> 40 41 ${config.extraHtaccess} 42 ''; 43 44 # WP translation can be found here: 45 # https://github.com/nixcloud/wordpress-translations 46 supportedLanguages = { 47 en_GB = { revision="d6c005372a5318fd758b710b77a800c86518be13"; sha256="0qbbsi87k47q4rgczxx541xz4z4f4fr49hw4lnaxkdsf5maz8p9p"; }; 48 de_DE = { revision="3c62955c27baaae98fd99feb35593d46562f4736"; sha256="1shndgd11dk836dakrjlg2arwv08vqx6j4xjh4jshvwmjab6ng6p"; }; 49 zh_ZN = { revision="12b9f811e8cae4b6ee41de343d35deb0a8fdda6d"; sha256="1339ggsxh0g6lab37jmfxicsax4h702rc3fsvv5azs7mcznvwh47"; }; 50 fr_FR = { revision="688c8b1543e3d38d9e8f57e0a6f2a2c3c8b588bd"; sha256="1j41iak0i6k7a4wzyav0yrllkdjjskvs45w53db8vfm8phq1n014"; }; 51 }; 52 53 downloadLanguagePack = language: revision: sha256s: 54 pkgs.stdenv.mkDerivation rec { 55 name = "wp_${language}"; 56 src = pkgs.fetchFromGitHub { 57 owner = "nixcloud"; 58 repo = "wordpress-translations"; 59 rev = revision; 60 sha256 = sha256s; 61 }; 62 installPhase = "mkdir -p $out; cp -R * $out/"; 63 }; 64 65 selectedLanguages = map (lang: downloadLanguagePack lang supportedLanguages.${lang}.revision supportedLanguages.${lang}.sha256) (config.languages); 66 67 # The wordpress package itself 68 wordpressRoot = pkgs.stdenv.mkDerivation rec { 69 name = "wordpress"; 70 src = config.package; 71 installPhase = '' 72 mkdir -p $out 73 # copy all the wordpress files we downloaded 74 cp -R * $out/ 75 76 # symlink the wordpress config 77 ln -s ${wordpressConfig} $out/wp-config.php 78 # symlink custom .htaccess 79 ln -s ${htaccess} $out/.htaccess 80 # symlink uploads directory 81 ln -s ${config.wordpressUploads} $out/wp-content/uploads 82 83 # remove bundled plugins(s) coming with wordpress 84 rm -Rf $out/wp-content/plugins/* 85 # remove bundled themes(s) coming with wordpress 86 rm -Rf $out/wp-content/themes/* 87 88 # symlink additional theme(s) 89 ${concatMapStrings (theme: "ln -s ${theme} $out/wp-content/themes/${theme.name}\n") config.themes} 90 # symlink additional plugin(s) 91 ${concatMapStrings (plugin: "ln -s ${plugin} $out/wp-content/plugins/${plugin.name}\n") (config.plugins) } 92 93 # symlink additional translation(s) 94 mkdir -p $out/wp-content/languages 95 ${concatMapStrings (language: "ln -s ${language}/*.mo ${language}/*.po $out/wp-content/languages/\n") (selectedLanguages) } 96 ''; 97 }; 98 99in 100 101{ 102 103 # And some httpd extraConfig to make things work nicely 104 extraConfig = '' 105 <Directory ${wordpressRoot}> 106 DirectoryIndex index.php 107 Allow from * 108 Options FollowSymLinks 109 AllowOverride All 110 </Directory> 111 ''; 112 113 enablePHP = true; 114 115 options = { 116 package = mkOption { 117 type = types.path; 118 default = pkgs.wordpress; 119 description = '' 120 Path to the wordpress sources. 121 Upgrading? We have a test! nix-build ./nixos/tests/wordpress.nix 122 ''; 123 }; 124 dbHost = mkOption { 125 default = "localhost"; 126 description = "The location of the database server."; 127 example = "localhost"; 128 }; 129 dbName = mkOption { 130 default = "wordpress"; 131 description = "Name of the database that holds the Wordpress data."; 132 example = "localhost"; 133 }; 134 dbUser = mkOption { 135 default = "wordpress"; 136 description = "The dbUser, read: the username, for the database."; 137 example = "wordpress"; 138 }; 139 dbPassword = mkOption { 140 default = "wordpress"; 141 description = '' 142 The mysql password to the respective dbUser. 143 144 Warning: this password is stored in the world-readable Nix store. It's 145 recommended to use the $dbPasswordFile option since that gives you control over 146 the security of the password. $dbPasswordFile also takes precedence over $dbPassword. 147 ''; 148 example = "wordpress"; 149 }; 150 dbPasswordFile = mkOption { 151 type = types.str; 152 default = toString (pkgs.writeTextFile { 153 name = "wordpress-dbpassword"; 154 text = config.dbPassword; 155 }); 156 example = "/run/keys/wordpress-dbpassword"; 157 description = '' 158 Path to a file that contains the mysql password to the respective dbUser. 159 The file should be readable by the user: config.services.httpd.user. 160 161 $dbPasswordFile takes precedence over the $dbPassword option. 162 163 This defaults to a file in the world-readable Nix store that contains the value 164 of the $dbPassword option. It's recommended to override this with a path not in 165 the Nix store. Tip: use nixops key management: 166 <link xlink:href='https://nixos.org/nixops/manual/#idm140737318306400'/> 167 ''; 168 }; 169 tablePrefix = mkOption { 170 default = "wp_"; 171 description = '' 172 The $table_prefix is the value placed in the front of your database tables. Change the value if you want to use something other than wp_ for your database prefix. Typically this is changed if you are installing multiple WordPress blogs in the same database. See <link xlink:href='http://codex.wordpress.org/Editing_wp-config.php#table_prefix'/>. 173 ''; 174 }; 175 wordpressUploads = mkOption { 176 default = "/data/uploads"; 177 description = '' 178 This directory is used for uploads of pictures and must be accessible (read: owned) by the httpd running user. The directory passed here is automatically created and permissions are given to the httpd running user. 179 ''; 180 }; 181 plugins = mkOption { 182 default = []; 183 type = types.listOf types.path; 184 description = 185 '' 186 List of path(s) to respective plugin(s) which are symlinked from the 'plugins' directory. Note: These plugins need to be packaged before use, see example. 187 ''; 188 example = '' 189 # Wordpress plugin 'akismet' installation example 190 akismetPlugin = pkgs.stdenv.mkDerivation { 191 name = "akismet-plugin"; 192 # Download the theme from the wordpress site 193 src = pkgs.fetchurl { 194 url = https://downloads.wordpress.org/plugin/akismet.3.1.zip; 195 sha256 = "1i4k7qyzna08822ncaz5l00wwxkwcdg4j9h3z2g0ay23q640pclg"; 196 }; 197 # We need unzip to build this package 198 buildInputs = [ pkgs.unzip ]; 199 # Installing simply means copying all files to the output directory 200 installPhase = "mkdir -p $out; cp -R * $out/"; 201 }; 202 203 And then pass this theme to the themes list like this: 204 plugins = [ akismetPlugin ]; 205 ''; 206 }; 207 themes = mkOption { 208 default = []; 209 type = types.listOf types.path; 210 description = 211 '' 212 List of path(s) to respective theme(s) which are symlinked from the 'theme' directory. Note: These themes need to be packaged before use, see example. 213 ''; 214 example = '' 215 # For shits and giggles, let's package the responsive theme 216 responsiveTheme = pkgs.stdenv.mkDerivation { 217 name = "responsive-theme"; 218 # Download the theme from the wordpress site 219 src = pkgs.fetchurl { 220 url = http://wordpress.org/themes/download/responsive.1.9.7.6.zip; 221 sha256 = "06i26xlc5kdnx903b1gfvnysx49fb4kh4pixn89qii3a30fgd8r8"; 222 }; 223 # We need unzip to build this package 224 buildInputs = [ pkgs.unzip ]; 225 # Installing simply means copying all files to the output directory 226 installPhase = "mkdir -p $out; cp -R * $out/"; 227 }; 228 229 And then pass this theme to the themes list like this: 230 themes = [ responsiveTheme ]; 231 ''; 232 }; 233 languages = mkOption { 234 default = []; 235 description = "Installs wordpress language packs based on the list, see wordpress.nix for possible translations."; 236 example = "[ \"en_GB\" \"de_DE\" ];"; 237 }; 238 extraConfig = mkOption { 239 type = types.lines; 240 default = ""; 241 example = 242 '' 243 define( 'AUTOSAVE_INTERVAL', 60 ); // Seconds 244 ''; 245 description = '' 246 Any additional text to be appended to Wordpress's wp-config.php 247 configuration file. This is a PHP script. For configuration 248 settings, see <link xlink:href='http://codex.wordpress.org/Editing_wp-config.php'/>. 249 ''; 250 }; 251 extraHtaccess = mkOption { 252 default = ""; 253 example = 254 '' 255 php_value upload_max_filesize 20M 256 php_value post_max_size 20M 257 ''; 258 description = '' 259 Any additional text to be appended to Wordpress's .htaccess file. 260 ''; 261 }; 262 }; 263 264 documentRoot = wordpressRoot; 265 266 # FIXME adding the user has to be done manually for the time being 267 startupScript = pkgs.writeScript "init-wordpress.sh" '' 268 #!/bin/sh 269 mkdir -p ${config.wordpressUploads} 270 chown ${serverInfo.serverConfig.user} ${config.wordpressUploads} 271 272 # we should use systemd dependencies here 273 if [ ! -d ${serverInfo.fullConfig.services.mysql.dataDir}/${config.dbName} ]; then 274 echo "Need to create the database '${config.dbName}' and grant permissions to user named '${config.dbUser}'." 275 # Wait until MySQL is up 276 while [ ! -e ${serverInfo.fullConfig.services.mysql.pidDir}/mysqld.pid ]; do 277 sleep 1 278 done 279 ${pkgs.mysql}/bin/mysql -e 'CREATE DATABASE ${config.dbName};' 280 ${pkgs.mysql}/bin/mysql -e "GRANT ALL ON ${config.dbName}.* TO ${config.dbUser}@localhost IDENTIFIED BY \"$(cat ${config.dbPasswordFile})\";" 281 else 282 echo "Good, no need to do anything database related." 283 fi 284 ''; 285}