1{ config, lib, pkgs, serverInfo, php, ... }:
2# http://codex.wordpress.org/Hardening_WordPress
3
4with lib;
5
6let
7
8 version = "4.3.1";
9 fullversion = "${version}";
10
11 # Our bare-bones wp-config.php file using the above settings
12 wordpressConfig = pkgs.writeText "wp-config.php" ''
13 <?php
14 define('DB_NAME', '${config.dbName}');
15 define('DB_USER', '${config.dbUser}');
16 define('DB_PASSWORD', '${config.dbPassword}');
17 define('DB_HOST', '${config.dbHost}');
18 define('DB_CHARSET', 'utf8');
19 $table_prefix = '${config.tablePrefix}';
20 ${config.extraConfig}
21 if ( !defined('ABSPATH') )
22 define('ABSPATH', dirname(__FILE__) . '/');
23 require_once(ABSPATH . 'wp-settings.php');
24 '';
25
26 # .htaccess to support pretty URLs
27 htaccess = pkgs.writeText "htaccess" ''
28 <IfModule mod_rewrite.c>
29 RewriteEngine On
30 RewriteBase /
31 RewriteRule ^index\.php$ - [L]
32
33 # add a trailing slash to /wp-admin
34 RewriteRule ^wp-admin$ wp-admin/ [R=301,L]
35
36 RewriteCond %{REQUEST_FILENAME} -f [OR]
37 RewriteCond %{REQUEST_FILENAME} -d
38 RewriteRule ^ - [L]
39 RewriteRule ^(wp-(content|admin|includes).*) $1 [L]
40 RewriteRule ^(.*\.php)$ $1 [L]
41 RewriteRule . index.php [L]
42 </IfModule>
43
44 ${config.extraHtaccess}
45 '';
46
47 # WP translation can be found here:
48 # https://github.com/nixcloud/wordpress-translations
49 supportedLanguages = {
50 en_GB = { revision="d6c005372a5318fd758b710b77a800c86518be13"; sha256="0qbbsi87k47q4rgczxx541xz4z4f4fr49hw4lnaxkdsf5maz8p9p"; };
51 de_DE = { revision="3c62955c27baaae98fd99feb35593d46562f4736"; sha256="1shndgd11dk836dakrjlg2arwv08vqx6j4xjh4jshvwmjab6ng6p"; };
52 zh_ZN = { revision="12b9f811e8cae4b6ee41de343d35deb0a8fdda6d"; sha256="1339ggsxh0g6lab37jmfxicsax4h702rc3fsvv5azs7mcznvwh47"; };
53 fr_FR = { revision="688c8b1543e3d38d9e8f57e0a6f2a2c3c8b588bd"; sha256="1j41iak0i6k7a4wzyav0yrllkdjjskvs45w53db8vfm8phq1n014"; };
54 };
55
56 downloadLanguagePack = language: revision: sha256s:
57 pkgs.stdenv.mkDerivation rec {
58 name = "wp_${language}";
59 src = pkgs.fetchFromGitHub {
60 owner = "nixcloud";
61 repo = "wordpress-translations";
62 rev = revision;
63 sha256 = sha256s;
64 };
65 installPhase = "mkdir -p $out; cp -R * $out/";
66 };
67
68 selectedLanguages = map (lang: downloadLanguagePack lang supportedLanguages.${lang}.revision supportedLanguages.${lang}.sha256) (config.languages);
69
70 # The wordpress package itself
71 wordpressRoot = pkgs.stdenv.mkDerivation rec {
72 name = "wordpress";
73 src = pkgs.fetchFromGitHub {
74 owner = "WordPress";
75 repo = "WordPress";
76 rev = "${fullversion}";
77 sha256 = "1rk10vcv4z9p04hfzc0wkbilrgx7m9ssyr6c3w6vw3vl1bcgqxza";
78 };
79 installPhase = ''
80 mkdir -p $out
81 # copy all the wordpress files we downloaded
82 cp -R * $out/
83
84 # symlink the wordpress config
85 ln -s ${wordpressConfig} $out/wp-config.php
86 # symlink custom .htaccess
87 ln -s ${htaccess} $out/.htaccess
88 # symlink uploads directory
89 ln -s ${config.wordpressUploads} $out/wp-content/uploads
90
91 # remove bundled plugins(s) coming with wordpress
92 rm -Rf $out/wp-content/plugins/*
93 # remove bundled themes(s) coming with wordpress
94 rm -Rf $out/wp-content/themes/*
95
96 # symlink additional theme(s)
97 ${concatMapStrings (theme: "ln -s ${theme} $out/wp-content/themes/${theme.name}\n") config.themes}
98 # symlink additional plugin(s)
99 ${concatMapStrings (plugin: "ln -s ${plugin} $out/wp-content/plugins/${plugin.name}\n") (config.plugins) }
100
101 # symlink additional translation(s)
102 mkdir -p $out/wp-content/languages
103 ${concatMapStrings (language: "ln -s ${language}/*.mo ${language}/*.po $out/wp-content/languages/\n") (selectedLanguages) }
104 '';
105 };
106
107in
108
109{
110
111 # And some httpd extraConfig to make things work nicely
112 extraConfig = ''
113 <Directory ${wordpressRoot}>
114 DirectoryIndex index.php
115 Allow from *
116 Options FollowSymLinks
117 AllowOverride All
118 </Directory>
119 '';
120
121 enablePHP = true;
122
123 options = {
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 = "The mysql password to the respective dbUser.";
142 example = "wordpress";
143 };
144 tablePrefix = mkOption {
145 default = "wp_";
146 description = ''
147 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'/>.
148 '';
149 };
150 wordpressUploads = mkOption {
151 default = "/data/uploads";
152 description = ''
153 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.
154 '';
155 };
156 plugins = mkOption {
157 default = [];
158 type = types.listOf types.path;
159 description =
160 ''
161 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.
162 '';
163 example = ''
164 # Wordpress plugin 'akismet' installation example
165 akismetPlugin = pkgs.stdenv.mkDerivation {
166 name = "akismet-plugin";
167 # Download the theme from the wordpress site
168 src = pkgs.fetchurl {
169 url = https://downloads.wordpress.org/plugin/akismet.3.1.zip;
170 sha256 = "1i4k7qyzna08822ncaz5l00wwxkwcdg4j9h3z2g0ay23q640pclg";
171 };
172 # We need unzip to build this package
173 buildInputs = [ pkgs.unzip ];
174 # Installing simply means copying all files to the output directory
175 installPhase = "mkdir -p $out; cp -R * $out/";
176 };
177
178 And then pass this theme to the themes list like this:
179 plugins = [ akismetPlugin ];
180 '';
181 };
182 themes = mkOption {
183 default = [];
184 type = types.listOf types.path;
185 description =
186 ''
187 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.
188 '';
189 example = ''
190 # For shits and giggles, let's package the responsive theme
191 responsiveTheme = pkgs.stdenv.mkDerivation {
192 name = "responsive-theme";
193 # Download the theme from the wordpress site
194 src = pkgs.fetchurl {
195 url = http://wordpress.org/themes/download/responsive.1.9.7.6.zip;
196 sha256 = "06i26xlc5kdnx903b1gfvnysx49fb4kh4pixn89qii3a30fgd8r8";
197 };
198 # We need unzip to build this package
199 buildInputs = [ pkgs.unzip ];
200 # Installing simply means copying all files to the output directory
201 installPhase = "mkdir -p $out; cp -R * $out/";
202 };
203
204 And then pass this theme to the themes list like this:
205 themes = [ responsiveTheme ];
206 '';
207 };
208 languages = mkOption {
209 default = [];
210 description = "Installs wordpress language packs based on the list, see wordpress.nix for possible translations.";
211 example = "[ \"en_GB\" \"de_DE\" ];";
212 };
213 extraConfig = mkOption {
214 default = "";
215 example =
216 ''
217 define( 'AUTOSAVE_INTERVAL', 60 ); // Seconds
218 '';
219 description = ''
220 Any additional text to be appended to Wordpress's wp-config.php
221 configuration file. This is a PHP script. For configuration
222 settings, see <link xlink:href='http://codex.wordpress.org/Editing_wp-config.php'/>.
223 '';
224 };
225 extraHtaccess = mkOption {
226 default = "";
227 example =
228 ''
229 php_value upload_max_filesize 20M
230 php_value post_max_size 20M
231 '';
232 description = ''
233 Any additional text to be appended to Wordpress's .htaccess file.
234 '';
235 };
236 };
237
238 documentRoot = wordpressRoot;
239
240 # FIXME adding the user has to be done manually for the time being
241 startupScript = pkgs.writeScript "init-wordpress.sh" ''
242 #!/bin/sh
243 mkdir -p ${config.wordpressUploads}
244 chown ${serverInfo.serverConfig.user} ${config.wordpressUploads}
245
246 # we should use systemd dependencies here
247 #waitForUnit("network-interfaces.target");
248 if [ ! -d ${serverInfo.fullConfig.services.mysql.dataDir}/${config.dbName} ]; then
249 echo "Need to create the database '${config.dbName}' and grant permissions to user named '${config.dbUser}'."
250 # Wait until MySQL is up
251 while [ ! -e ${serverInfo.fullConfig.services.mysql.pidDir}/mysqld.pid ]; do
252 sleep 1
253 done
254 ${pkgs.mysql}/bin/mysql -e 'CREATE DATABASE ${config.dbName};'
255 ${pkgs.mysql}/bin/mysql -e 'GRANT ALL ON ${config.dbName}.* TO ${config.dbUser}@localhost IDENTIFIED BY "${config.dbPassword}";'
256 else
257 echo "Good, no need to do anything database related."
258 fi
259 '';
260}