summary refs log tree commit diff
path: root/nixos/modules/services/web-servers/apache-httpd/wordpress.nix
blob: c810b914e258afc8345ccf16a503f2074dacdbe3 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
{ config, lib, pkgs, serverInfo, ... }:
# http://codex.wordpress.org/Hardening_WordPress

with lib;

let
  # Our bare-bones wp-config.php file using the above settings
  wordpressConfig = pkgs.writeText "wp-config.php" ''
    <?php
    define('DB_NAME',     '${config.dbName}');
    define('DB_USER',     '${config.dbUser}');
    define('DB_PASSWORD', file_get_contents('${config.dbPasswordFile}'));
    define('DB_HOST',     '${config.dbHost}');
    define('DB_CHARSET',  'utf8');
    $table_prefix  = '${config.tablePrefix}';
    define('AUTOMATIC_UPDATER_DISABLED', true);
    ${config.extraConfig}
    if ( !defined('ABSPATH') )
    	define('ABSPATH', dirname(__FILE__) . '/');
    require_once(ABSPATH . 'wp-settings.php');
  '';

  # .htaccess to support pretty URLs
  htaccess = pkgs.writeText "htaccess" ''
    <IfModule mod_rewrite.c>
    RewriteEngine On
    RewriteBase /
    RewriteRule ^index\.php$ - [L]

    # add a trailing slash to /wp-admin
    RewriteRule ^wp-admin$ wp-admin/ [R=301,L]

    RewriteCond %{REQUEST_FILENAME} -f [OR]
    RewriteCond %{REQUEST_FILENAME} -d
    RewriteRule ^ - [L]
    RewriteRule ^(wp-(content|admin|includes).*) $1 [L]
    RewriteRule ^(.*\.php)$ $1 [L]
    RewriteRule . index.php [L]
    </IfModule>

    ${config.extraHtaccess}
  '';

  # WP translation can be found here:
  #   https://github.com/nixcloud/wordpress-translations
  supportedLanguages = {
    en_GB = { revision="d6c005372a5318fd758b710b77a800c86518be13"; sha256="0qbbsi87k47q4rgczxx541xz4z4f4fr49hw4lnaxkdsf5maz8p9p"; };
    de_DE = { revision="3c62955c27baaae98fd99feb35593d46562f4736"; sha256="1shndgd11dk836dakrjlg2arwv08vqx6j4xjh4jshvwmjab6ng6p"; };
    zh_ZN = { revision="12b9f811e8cae4b6ee41de343d35deb0a8fdda6d"; sha256="1339ggsxh0g6lab37jmfxicsax4h702rc3fsvv5azs7mcznvwh47"; };
    fr_FR = { revision="688c8b1543e3d38d9e8f57e0a6f2a2c3c8b588bd"; sha256="1j41iak0i6k7a4wzyav0yrllkdjjskvs45w53db8vfm8phq1n014"; };
  };

  downloadLanguagePack = language: revision: sha256s:
    pkgs.stdenv.mkDerivation rec {
      name = "wp_${language}";
      src = pkgs.fetchFromGitHub {
        owner = "nixcloud";
        repo = "wordpress-translations";
        rev = revision;
        sha256 = sha256s;
      };
      installPhase = "mkdir -p $out; cp -R * $out/";
    };

  selectedLanguages = map (lang: downloadLanguagePack lang supportedLanguages.${lang}.revision supportedLanguages.${lang}.sha256) (config.languages);

  # The wordpress package itself
  wordpressRoot = pkgs.stdenv.mkDerivation rec {
    name = "wordpress";
    src = config.package;
    installPhase = ''
      mkdir -p $out
      # copy all the wordpress files we downloaded
      cp -R * $out/

      # symlink the wordpress config
      ln -s ${wordpressConfig} $out/wp-config.php
      # symlink custom .htaccess
      ln -s ${htaccess} $out/.htaccess
      # symlink uploads directory
      ln -s ${config.wordpressUploads} $out/wp-content/uploads

      # remove bundled plugins(s) coming with wordpress
      rm -Rf $out/wp-content/plugins/*
      # remove bundled themes(s) coming with wordpress
      rm -Rf $out/wp-content/themes/*

      # symlink additional theme(s)
      ${concatMapStrings (theme: "ln -s ${theme} $out/wp-content/themes/${theme.name}\n") config.themes}
      # symlink additional plugin(s)
      ${concatMapStrings (plugin: "ln -s ${plugin} $out/wp-content/plugins/${plugin.name}\n") (config.plugins) }

      # symlink additional translation(s)
      mkdir -p $out/wp-content/languages
      ${concatMapStrings (language: "ln -s ${language}/*.mo ${language}/*.po $out/wp-content/languages/\n") (selectedLanguages) }
    '';
  };

in

{

  # And some httpd extraConfig to make things work nicely
  extraConfig = ''
    <Directory ${wordpressRoot}>
      DirectoryIndex index.php
      Allow from *
      Options FollowSymLinks
      AllowOverride All
    </Directory>
  '';

  enablePHP = true;

  options = {
    package = mkOption {
      type = types.path;
      default = pkgs.wordpress;
      description = ''
        Path to the wordpress sources.
        Upgrading? We have a test! nix-build ./nixos/tests/wordpress.nix
      '';
    };
    dbHost = mkOption {
      default = "localhost";
      description = "The location of the database server.";
      example = "localhost";
    };
    dbName = mkOption {
      default = "wordpress";
      description = "Name of the database that holds the Wordpress data.";
      example = "localhost";
    };
    dbUser = mkOption {
      default = "wordpress";
      description = "The dbUser, read: the username, for the database.";
      example = "wordpress";
    };
    dbPassword = mkOption {
      default = "wordpress";
      description = ''
        The mysql password to the respective dbUser.

        Warning: this password is stored in the world-readable Nix store. It's
        recommended to use the $dbPasswordFile option since that gives you control over
        the security of the password. $dbPasswordFile also takes precedence over $dbPassword.
      '';
      example = "wordpress";
    };
    dbPasswordFile = mkOption {
      type = types.str;
      default = toString (pkgs.writeTextFile {
        name = "wordpress-dbpassword";
        text = config.dbPassword;
      });
      example = "/run/keys/wordpress-dbpassword";
      description = ''
        Path to a file that contains the mysql password to the respective dbUser.
        The file should be readable by the user: config.services.httpd.user.

        $dbPasswordFile takes precedence over the $dbPassword option.

        This defaults to a file in the world-readable Nix store that contains the value
        of the $dbPassword option. It's recommended to override this with a path not in
        the Nix store. Tip: use nixops key management:
        <link xlink:href='https://nixos.org/nixops/manual/#idm140737318306400'/>
      '';
    };
    tablePrefix = mkOption {
      default = "wp_";
      description = ''
        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'/>.
      '';
    };
    wordpressUploads = mkOption {
    default = "/data/uploads";
      description = ''
        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.
      '';
    };
    plugins = mkOption {
      default = [];
      type = types.listOf types.path;
      description =
        ''
          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.
        '';
      example = ''
        # Wordpress plugin 'akismet' installation example
        akismetPlugin = pkgs.stdenv.mkDerivation {
          name = "akismet-plugin";
          # Download the theme from the wordpress site
          src = pkgs.fetchurl {
            url = https://downloads.wordpress.org/plugin/akismet.3.1.zip;
            sha256 = "1i4k7qyzna08822ncaz5l00wwxkwcdg4j9h3z2g0ay23q640pclg";
          };
          # We need unzip to build this package
          buildInputs = [ pkgs.unzip ];
          # Installing simply means copying all files to the output directory
          installPhase = "mkdir -p $out; cp -R * $out/";
        };

        And then pass this theme to the themes list like this:
          plugins = [ akismetPlugin ];
      '';
    };
    themes = mkOption {
      default = [];
      type = types.listOf types.path;
      description =
        ''
          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.
        '';
      example = ''
        # For shits and giggles, let's package the responsive theme
        responsiveTheme = pkgs.stdenv.mkDerivation {
          name = "responsive-theme";
          # Download the theme from the wordpress site
          src = pkgs.fetchurl {
            url = http://wordpress.org/themes/download/responsive.1.9.7.6.zip;
            sha256 = "06i26xlc5kdnx903b1gfvnysx49fb4kh4pixn89qii3a30fgd8r8";
          };
          # We need unzip to build this package
          buildInputs = [ pkgs.unzip ];
          # Installing simply means copying all files to the output directory
          installPhase = "mkdir -p $out; cp -R * $out/";
        };

        And then pass this theme to the themes list like this:
          themes = [ responsiveTheme ];
      '';
    };
    languages = mkOption {
          default = [];
          description = "Installs wordpress language packs based on the list, see wordpress.nix for possible translations.";
          example = "[ \"en_GB\" \"de_DE\" ];";
    };
    extraConfig = mkOption {
      type = types.lines;
      default = "";
      example =
        ''
          define( 'AUTOSAVE_INTERVAL', 60 ); // Seconds
        '';
      description = ''
        Any additional text to be appended to Wordpress's wp-config.php
        configuration file.  This is a PHP script.  For configuration
        settings, see <link xlink:href='http://codex.wordpress.org/Editing_wp-config.php'/>.
      '';
    };
    extraHtaccess = mkOption {
      default = "";
      example =
        ''
          php_value upload_max_filesize 20M
          php_value post_max_size 20M
        '';
      description = ''
        Any additional text to be appended to Wordpress's .htaccess file.
      '';
    };
  };

  documentRoot = wordpressRoot;

  # FIXME adding the user has to be done manually for the time being
  startupScript = pkgs.writeScript "init-wordpress.sh" ''
    #!/bin/sh
    mkdir -p ${config.wordpressUploads}
    chown ${serverInfo.serverConfig.user} ${config.wordpressUploads}

    # we should use systemd dependencies here
    if [ ! -d ${serverInfo.fullConfig.services.mysql.dataDir}/${config.dbName} ]; then
      echo "Need to create the database '${config.dbName}' and grant permissions to user named '${config.dbUser}'."
      # Wait until MySQL is up
      while [ ! -e ${serverInfo.fullConfig.services.mysql.pidDir}/mysqld.pid ]; do
        sleep 1
      done
      ${pkgs.mysql}/bin/mysql -e 'CREATE DATABASE ${config.dbName};'
      ${pkgs.mysql}/bin/mysql -e "GRANT ALL ON ${config.dbName}.* TO ${config.dbUser}@localhost IDENTIFIED BY \"$(cat ${config.dbPasswordFile})\";"
    else
      echo "Good, no need to do anything database related."
    fi
  '';
}