modules: add initial sketch of wp for nginx

This commit is contained in:
Raito Bezarius 2021-11-22 21:39:44 +01:00
parent 2fa0b052f6
commit a37d79c31c
12 changed files with 453 additions and 0 deletions

View file

@ -2,4 +2,5 @@
acme-dns = ./servers/acme-dns.nix;
drone-server = ./servers/drone.nix;
drone-exec-runner = ./servers/drone-exec-runner.nix;
wordpress = ./web-apps/wordpress;
}

View file

@ -0,0 +1,128 @@
with import ./utils.nix;
let
writeableDefault = {
appPaths = []; # list of paths in the app to make writeable
pkgPath = "_writeable"; # path to create in read-only package that stores original content of writeable paths
sysPath = required "writeable.sysPath" ''system path to store writeable data (e.g. "/var/lib/phpfpm/my-app")'';
owner = required "writeable.owner" "user which owns the writeable files";
};
in
{ callPackage
, lib
, runCommand
, writeScript
, writeText
, appConfig
, writeable ? writeableDefault
, ...
}:
let
# Merge the given writeable settings with the defaults.
writeable_ = writeableDefault // writeable;
# We only care about writeble paths if the app is mostly frozen.
writeablePaths = lib.optionals appConfig.freezeWordPress (
["wp-content/uploads"]
++ lib.optional (!appConfig.freezePlugins) "wp-content/plugins"
++ lib.optional (!appConfig.freezeThemes) "wp-content/themes"
++ writeable_.appPaths
);
wordpress = callPackage appConfig.wordpress {};
plugins = callPackage appConfig.plugins {};
themes = callPackage appConfig.themes {};
# The wp-config.php file.
wpConfigFile = writeText "wp-config.php" appConfig.wpConfig.rendered;
# Generates a list of paths in bash that can be looped over.
listOfPaths = lib.concatMapStringsSep " " (x: "'${x}'");
# Generates the bash command to install a path from source to destination.
# If the install is `frozen`, then we simply symlink, otherwise we copy.
installPath = isFrozen: from: to:
(if isFrozen then "ln -s" else "cp -r") + " " + ''"${from}" "${to}"'';
# The build script for the app.
# This will install WordPress, the wp-config, plugins, and themes.
# If any writeble paths were configured, this script will copy them to a another folder in the
# package and set up symlinks in their place the given writeable path on the system.
buildPackageAt = out: ''
mkdir -p $(dirname "${out}") # Make parent directory.
cp -r "${wordpress}" "${out}"
chmod -R +w "${out}"
${installPath appConfig.freezeWordPress wpConfigFile "${out}/wp-config.php"}
# Install themes.
rm -r "${out}/wp-content/themes"/* # remove bundled themes
${lib.concatMapStringsSep "\n" (x: installPath appConfig.freezeThemes x "${out}/wp-content/themes/${x.name}") themes}
# Install plugins.
rm -r "${out}/wp-content/plugins"/* # remove bundled plugins
${lib.concatMapStringsSep "\n" (x: installPath appConfig.freezePlugins x "${out}/wp-content/plugins/${x.name}") plugins}
# TODO: Support translations.
${lib.optionalString (writeablePaths != []) ''
# Make symlinks to writeable directories.
writeable_orig_dir="${out}/${writeable_.pkgPath}"
mkdir -p "$writeable_orig_dir"
for thing in ${listOfPaths writeablePaths}; do
original_thing="$writeable_orig_dir/$thing"
parent=$(dirname "$original_thing")
mkdir -p "$parent"
# Move any existing data to the frozen writeable dir or create empty directory there.
mv "${out}/$thing" "$parent" || mkdir -p "$original_thing"
ln -s "${writeable_.sysPath}/$thing" "${out}/$thing"
done
''}
'';
# Copy the original writeable contents of the package to a writeable dir.
initWriteablePathsFor = package: ''
mkdir -p "$out"
writeable_orig_dir="${package}/${writeable_.pkgPath}"
for thing in $( ls "$writeable_orig_dir" ); do
cp -r "$writeable_orig_dir/$thing" "$out"
done
'';
# Takes an existing script and makes a initialization script that only runs if the output path
# has not been built yet.
mkInitScript = script: writeScript "init-writeable-paths" ''
#!/bin/sh
out="${writeable_.sysPath}"
if [ ! -d "$out" ]; then
${script}
chown -R "${writeable_.owner}" "$out"
chmod -R 744 "$out"
else
echo Output directory already exists. Not building path: "$out"
fi
'';
in if appConfig.freezeWordPress
then rec {
# For a mostly frozen app, we install it as a package and set up writeable paths on first run.
initScript = mkInitScript (initWriteablePathsFor package);
package = runCommand "wordpress-app" {
preferLocalBuild = true;
} (buildPackageAt "$out");
}
else rec {
# For fully writeable app, we skip package installation and write the app directly to the
# writeable path on first run.
initScript = mkInitScript (buildPackageAt package);
package = writeable_.sysPath;
}

View file

@ -0,0 +1,113 @@
let
lib = (import <nixpkgs> {}).lib;
in lib.makeExtensible (self: {
domain = "wordpress-site.dev";
# Simple name used for directories, etc.
# WARNING: Changing this after a deployment will change the location of data directories and will
# likely result in a partial reset of your application. You must move data from the
# previous app folders to the new ones.
name = "wordpress-app";
description = "A Wordpress Site"; # Brief, one-line description or title
tagline = "Deployed with Nixops";
host = "www.${self.domain}";
adminEmail = "admin@${self.domain}";
siteUrl = "${if self.enableHttps then "https" else "http"}://${self.host}";
# Hosts that get redirected to the primary host.
hostRedirects = [self.domain];
# Configure timezone settings (http://php.net/manual/en/timezones.php)
timezone = "UTC";
# WP-CLI settings for automatic install
autoInstall = let
adminConfig = import ./wordpress-admin.keys.nix;
in lib.makeExtensible (innerSelf: {
enable = false; # set to `true` to automatically install WordPress configuration
inherit (adminConfig) adminUser adminPassword;
});
wordpress = import ./wordpress.nix;
plugins = import ./plugins.nix;
themes = import ./themes.nix;
# Warning: Changing these after your site has been deployed will require manual
# work on the server. We don't want to do anything that would lose
# data so we leave that to you.
freezeWordPress = true; # Can admins upgrade WordPress in the CMS?
freezePlugins = true; # Can admins edit plugins in the CMS?
freezeThemes = true; # Can admins edit themes in the CMS?
dbConfig = lib.makeExtensible (innerSelf: {
isLocal = true; # if `true`, MySQL will be installed on the server.
name = "wordpress"; # database name
user = "root";
password = "";
host = "localhost";
charset = "utf8mb4";
tablePrefix = "wp_";
});
wpConfig = lib.makeExtensible (innerSelf: {
# Generate this file with `curl https://api.wordpress.org/secret-key/1.1/salt/ > wordpress-keys.php.secret`
secrets = builtins.readFile ./wordpress-keys.php.secret;
debugMode = false;
extraConfig = ''
define('WP_HOME', '${self.siteUrl}');
define('WP_SITEURL', '${self.siteUrl}');
'';
inherit (self) dbConfig;
template = import ./wp-config.nix;
rendered = innerSelf.template innerSelf;
});
# Server settings
enableHttps = true;
maxUploadMb = 50;
# --- ADVANCED CONFIGURATION ---
opcache = lib.makeExtensible (innerSelf: {
enable = true;
maxMemoryMb = 128;
# How often to invalidate timestamp cache. This is only used when the project
# has non-frozen components (see above).
# http://php.net/manual/en/opcache.configuration.php#ini.opcache.revalidate-freq
revalidateFreqSec = 60;
});
# PHP-FPM settings for the *dynamic* process manager: http://php.net/manual/en/install.fpm.configuration.php#pm
phpFpmProcessSettings = lib.makeExtensible (innerSelf: {
max_children = 10;
start_servers = innerSelf.min_spare_servers; # WARNING: min_spare_servers <= start_servers <= max_spare_servers
min_spare_servers = 2;
max_spare_servers = 5;
max_requests = 500;
});
googlePageSpeed = lib.makeExtensible (innerSelf: {
enable = true;
cachePath = "/run/nginx-pagespeed-cache"; # /run/ is tmpfs and will keep cache in memory
});
fastCgiCache = lib.makeExtensible (innerSelf: {
enable = true;
cachePath = "/run/nginx-fastcgi-cache"; # /run/ is tmpfs and will keep cache in memory
});
php = lib.makeExtensible (innerSelf: {
enableXDebug = false;
scriptMemoryLimitMb = 128;
maxExecutionTimeSec = 300;
# sendmail_path configuration for php.ini files
sendmailPath = "/run/wrappers/bin/sendmail -t -i";
});
})

View file

@ -0,0 +1,5 @@
{
imports = [
./module.nix
];
}

View file

@ -0,0 +1,37 @@
{ pkgs
, config # system configuration
, appConfig
, appPackage
, writeableDataPath
}:
pkgs.writeScript "install-wordpress.sh" ''
#!${pkgs.stdenv.shell} -eu
if ! $('${pkgs.wp-cli}/bin/wp' core is-installed --path='${appPackage}' --allow-root); then
echo 'Installing WordPress configuration for ${appConfig.host}'
'${pkgs.wp-cli}/bin/wp' core install \
--url='${appConfig.siteUrl}' \
--title='${appConfig.description}' \
--admin_user='${appConfig.autoInstall.adminUser}' \
--admin_password='${appConfig.autoInstall.adminPassword}' \
--admin_email='${appConfig.adminEmail}' \
--path='${appPackage}' \
--allow-root;
chown -R '${config.services.nginx.user}' '${writeableDataPath}';
else
echo 'WordPress configuration already installed for ${appConfig.host}'
fi
'${pkgs.wp-cli}/bin/wp' option update blogname '${appConfig.description}' \
--path='${appPackage}' \
--allow-root;
'${pkgs.wp-cli}/bin/wp' option update blogdescription '${appConfig.tagline}' \
--path='${appPackage}' \
--allow-root;
# TODO: Provide a list of plugins to be activated from plugins.nix
'${pkgs.wp-cli}/bin/wp' plugin activate nginx-helper opcache \
--path='${appPackage}' \
--allow-root;
''

View file

@ -0,0 +1,79 @@
{ config, pkgs, lib, ... }:
with lib;
let
cfg = config.services.wordpress;
appConfig = (import ./default-app-config.nix).extend (self: super: {});
writeableDataPath = "/var/lib/phpfpm/${appConfig.name}";
phpFpmListen = "/run/phpfpm/wordpress-pool.sock";
phpIni = import ./php-config.nix { inherit pkgs config appConfig; };
enablePageSpeed = pkgs.stdenv.isLinux && appConfig.googlePageSpeed.enable;
app = callPackage ./app.nix {
inherit appConfig;
writeable = {
sysPath = writeableDataPath;
owner = config.services.nginx.user;
};
};
in
{
options.services.wordpress = {
enable = mkEnableOption "Enable the WordPress module";
};
config = mkIf cfg.enable {
environment.systemPackages = [
pkgs.wp-cli
];
services.nginx = {
enable = true;
# package = pkgs.callPackage ./nginx.nix { inherit enablePageSpeed; };
# httpConfig = nginxConfig;
# TODO: ajouter les locations pour wordpress
};
systemd.services.init-writeable-paths = {
description = "Initialize writeable directories for the app";
before = [ "phpfpm.service" ];
after = [ "network.target" ];
wantedBy = [ "multi-user.target" "phpfpm.service" "nginx.service" ];
serviceConfig = {
Type = "oneshot";
ExecStart = app.initScript;
};
};
systemd.services.install-wp = let
deps = [ "init-writeable-paths.service" "mysql.service" ];
in {
enable = appConfig.autoInstall.enable;
description = "Configure WordPress installation with WP-CLI";
before = [ "nginx.service" ];
after = deps;
wants = deps;
wantedBy = [ "multi-user.target" ];
serviceConfig = {
Type = "oneshot";
ExecStart = import ./install-wp.nix {
inherit pkgs config appConfig writeableDataPath;
appPackage = app.package;
};
};
environment.PHP_INI_SCAN_DIR = let
customIni = pkgs.writeTextDir "wp-cli-custom.ini" phpIni;
in "${pkgs.php}/etc:${customIni}";
};
services.phpfpm = {
phpOptions = phpIni;
pools.wordpress-pool = import ./phpfpm-conf.nix {
inherit pkgs config phpFpmListen;
processSettings = appConfig.phpFpmProcessSettings;
};
};
services.mysql = {
enable = true;
package = pkgs.mariadb;
};
}

View file

@ -0,0 +1,34 @@
{ pkgs
, config # system configuration
, appConfig
}:
assert appConfig.php.scriptMemoryLimitMb > appConfig.maxUploadMb;
''
memory_limit ${toString appConfig.php.scriptMemoryLimitMb}M
extension = "${pkgs.phpPackages.imagick}/lib/php/extensions/imagick.so"
${pkgs.lib.optionalString appConfig.opcache.enable ''
zend_extension = "${config.services.phpfpm.phpPackage}/lib/php/extensions/opcache.so"
''}
${pkgs.lib.optionalString appConfig.php.enableXDebug ''
; WARNING: Be sure to load opcache *before* xdebug (http://us3.php.net/manual/en/opcache.installation.php).
zend_extension = "${pkgs.phpPackages.xdebug}/lib/php/extensions/xdebug.so"
''}
upload_max_filesize = ${toString appConfig.maxUploadMb}M
post_max_size = ${toString appConfig.maxUploadMb}M
max_execution_time ${toString appConfig.php.maxExecutionTimeSec}
date.timezone = "${appConfig.timezone}"
sendmail_path = ${appConfig.php.sendmailPath}
${import ./opcache-config.nix (appConfig.opcache // {
# Enable timestamp validation if the setup is not entirely frozen (managed by Nix).
validateTimestamps = ! builtins.all (x: x)
[appConfig.freezeWordPress appConfig.freezePlugins appConfig.freezeThemes];
})}
''

View file

@ -0,0 +1,12 @@
{ pkgs, config, phpFpmListen, processSettings }:
{
inherit (config.services.nginx) user group;
settings = {
"listen.owner" = config.services.nginx.user;
"listen.group" = config.services.nginx.group;
"listen.mode" = 660;
"pm" = "dynamic";
"catch_workers_output" = "yes";
} // processSettings;
listen = phpFpmListen;
}

View file

@ -0,0 +1,14 @@
# A list of your WordPress plugins.
{ callPackage, ... }:
let
utils = callPackage ./utils.nix {};
getPlugin = utils.getPlugin;
requiredPlugins = [
(getPlugin "opcache" "0.3.1" "18x6fnfc7ka4ynxv4z3rf4011ivqc0qy0dsd6i4lxa113jjyqz6d")
(getPlugin "nginx-helper" "1.9.10" "1n887qz9rzs8yj069wva6cirp6y46a49wspzja4grdj2qirr4hky")
];
in requiredPlugins ++ [
(getPlugin "akismet" "3.3" "02vsjnr7bs54a744p64rx7jwlbcall6nhh1mv6w54zbwj4ygqz68")
(getPlugin "jetpack" "4.8.2" "17bvkcb17dx969a30j0axb5kqzfxnx1sqkcdwwrski9gh7ihabqk")
]

View file

@ -0,0 +1,8 @@
# A list of your WordPress themes.
{ callPackage, ... }:
let
utils = callPackage ./utils.nix {};
getTheme = utils.getTheme;
in [
(getTheme "twentyseventeen" "1.1" "1xsdz1s68mavz9i4lhckh7rqw266jqm5mn3ql1gbz03zf6ghf982")
]

View file

@ -0,0 +1,15 @@
{
traced = x: builtins.trace x x;
required = arg: help: builtins.abort "${arg} is required: ${help}";
# Converts a set into a string
# pkgs is nixpkgs
# sep is a string separator to place between each field
# mapFn is (string -> string -> string) taking key and value for each attribute
# attrs is the set to process
setToString = pkgs: sep: mapFn: attrs: pkgs.lib.concatStringsSep sep (
pkgs.lib.mapAttrsToList
(key: val: if builtins.isInt val || builtins.isString val then mapFn key val else "")
attrs
);
}

View file

@ -0,0 +1,7 @@
{ fetchzip, runCommand, ... }:
let
version = "4.8";
in fetchzip {
url = "https://wordpress.org/wordpress-${version}.tar.gz";
sha256 = "1myflpa9pxcghnhjfd0ahqpsvgcwh3szk2k8w2x7qmvfll69n3j9";
}