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}