nixpkgs-suyu/pkgs/servers/web-apps/discourse/default.nix
Ryan Mulligan f933c68374 discourse: enable restoring backups bigger than RAM
When restoring a backup, discourse decompresses the backup archive in
the /share/discourse/tmp dir. Before this change, it is linked to /run
which is typically backed by memory, so the backup will fail to
restore if you do not have enough memory on your system to contain the
backup. This has already happened to me on two small forums.

This moves tmp to the StateDirectory /var/lib/discourse/tmp which is
typically backed by disk.
2021-09-23 09:59:31 -07:00

319 lines
9.3 KiB
Nix

{ stdenv, pkgs, makeWrapper, runCommand, lib, writeShellScript
, fetchFromGitHub, bundlerEnv, callPackage
, ruby, replace, gzip, gnutar, git, cacert, util-linux, gawk
, imagemagick, optipng, pngquant, libjpeg, jpegoptim, gifsicle, libpsl
, redis, postgresql, which, brotli, procps, rsync, nodePackages, v8
, plugins ? []
}@args:
let
version = "2.7.7";
src = fetchFromGitHub {
owner = "discourse";
repo = "discourse";
rev = "v${version}";
sha256 = "sha256-rhcTQyirgPX0ITjgotJAYLLSU957GanxAYYhy9j123U=";
};
runtimeDeps = [
# For backups, themes and assets
rubyEnv.wrappedRuby
rsync
gzip
gnutar
git
brotli
# Misc required system utils
which
procps # For ps and kill
util-linux # For renice
gawk
# Image optimization
imagemagick
optipng
pngquant
libjpeg
jpegoptim
gifsicle
nodePackages.svgo
];
runtimeEnv = {
HOME = "/run/discourse/home";
RAILS_ENV = "production";
UNICORN_LISTENER = "/run/discourse/sockets/unicorn.sock";
};
mkDiscoursePlugin =
{ name ? null
, pname ? null
, version ? null
, meta ? null
, bundlerEnvArgs ? {}
, preserveGemsDir ? false
, src
, ...
}@args:
let
rubyEnv = bundlerEnv (bundlerEnvArgs // {
inherit name pname version ruby;
});
in
stdenv.mkDerivation (builtins.removeAttrs args [ "bundlerEnvArgs" ] // {
pluginName = if name != null then name else "${pname}-${version}";
dontConfigure = true;
dontBuild = true;
installPhase = ''
runHook preInstall
mkdir -p $out
cp -r * $out/
'' + lib.optionalString (bundlerEnvArgs != {}) (
if preserveGemsDir then ''
cp -r ${rubyEnv}/lib/ruby/gems/* $out/gems/
''
else ''
if [[ -e $out/gems ]]; then
echo "Warning: The repo contains a 'gems' directory which will be removed!"
echo " If you need to preserve it, set 'preserveGemsDir = true'."
rm -r $out/gems
fi
ln -sf ${rubyEnv}/lib/ruby/gems $out/gems
'' + ''
runHook postInstall
'');
});
rake = runCommand "discourse-rake" {
nativeBuildInputs = [ makeWrapper ];
} ''
mkdir -p $out/bin
makeWrapper ${rubyEnv}/bin/rake $out/bin/discourse-rake \
${lib.concatStrings (lib.mapAttrsToList (name: value: "--set ${name} '${value}' ") runtimeEnv)} \
--prefix PATH : ${lib.makeBinPath runtimeDeps} \
--set RAKEOPT '-f ${discourse}/share/discourse/Rakefile' \
--run 'cd ${discourse}/share/discourse'
'';
rubyEnv = bundlerEnv {
name = "discourse-ruby-env-${version}";
inherit version ruby;
gemdir = ./rubyEnv;
gemset =
let
gems = import ./rubyEnv/gemset.nix;
in
gems // {
libv8-node =
let
noopScript = writeShellScript "noop" "exit 0";
linkFiles = writeShellScript "link-files" ''
cd ../..
mkdir -p vendor/v8/out.gn/libv8/obj/
ln -s "${v8}/lib/libv8.a" vendor/v8/out.gn/libv8/obj/libv8_monolith.a
ln -s ${v8}/include vendor/v8/include
mkdir -p ext/libv8-node
echo '--- !ruby/object:Libv8::Node::Location::Vendor {}' >ext/libv8-node/.location.yml
'';
in gems.libv8-node // {
dontBuild = false;
postPatch = ''
cp ${noopScript} libexec/build-libv8
cp ${noopScript} libexec/build-monolith
cp ${noopScript} libexec/download-node
cp ${noopScript} libexec/extract-node
cp ${linkFiles} libexec/inject-libv8
'';
};
mini_suffix = gems.mini_suffix // {
propagatedBuildInputs = [ libpsl ];
dontBuild = false;
# Use our libpsl instead of the vendored one, which isn't
# available for aarch64. It has to be called
# libpsl.x86_64.so or it isn't found.
postPatch = ''
cp $(readlink -f ${libpsl}/lib/libpsl.so) vendor/libpsl.x86_64.so
'';
};
};
groups = [
"default" "assets" "development" "test"
];
};
assets = stdenv.mkDerivation {
pname = "discourse-assets";
inherit version src;
nativeBuildInputs = [
rubyEnv.wrappedRuby
postgresql
redis
which
brotli
procps
nodePackages.uglify-js
nodePackages.terser
];
patches = [
# Use the Ruby API version in the plugin gem path, to match the
# one constructed by bundlerEnv
./plugin_gem_api_version.patch
# Change the path to the auto generated plugin assets, which
# defaults to the plugin's directory and isn't writable at the
# time of asset generation
./auto_generated_path.patch
];
# We have to set up an environment that is close enough to
# production ready or the assets:precompile task refuses to
# run. This means that Redis and PostgreSQL has to be running and
# database migrations performed.
preBuild = ''
export SSL_CERT_FILE=${cacert}/etc/ssl/certs/ca-bundle.crt
redis-server >/dev/null &
initdb -A trust $NIX_BUILD_TOP/postgres >/dev/null
postgres -D $NIX_BUILD_TOP/postgres -k $NIX_BUILD_TOP >/dev/null &
export PGHOST=$NIX_BUILD_TOP
echo "Waiting for Redis and PostgreSQL to be ready.."
while ! redis-cli --scan >/dev/null || ! psql -l >/dev/null; do
sleep 0.1
done
psql -d postgres -tAc 'CREATE USER "discourse"'
psql -d postgres -tAc 'CREATE DATABASE "discourse" OWNER "discourse"'
psql 'discourse' -tAc "CREATE EXTENSION IF NOT EXISTS pg_trgm"
psql 'discourse' -tAc "CREATE EXTENSION IF NOT EXISTS hstore"
# Create a temporary home dir to stop bundler from complaining
mkdir $NIX_BUILD_TOP/tmp_home
export HOME=$NIX_BUILD_TOP/tmp_home
${lib.concatMapStringsSep "\n" (p: "ln -sf ${p} plugins/${p.pluginName or ""}") plugins}
export RAILS_ENV=production
bundle exec rake db:migrate >/dev/null
rm -r tmp/*
'';
buildPhase = ''
runHook preBuild
bundle exec rake assets:precompile
runHook postBuild
'';
installPhase = ''
runHook preInstall
mv public/assets $out
runHook postInstall
'';
};
discourse = stdenv.mkDerivation {
pname = "discourse";
inherit version src;
buildInputs = [
rubyEnv rubyEnv.wrappedRuby rubyEnv.bundler
];
patches = [
# Load a separate NixOS site settings file
./nixos_defaults.patch
# Add a noninteractive admin creation task
./admin_create.patch
# Disable jhead, which is currently marked as vulnerable
./disable_jhead.patch
# Add the path to the CA cert bundle to make TLS work
./action_mailer_ca_cert.patch
# Log Unicorn messages to the journal and make request timeout
# configurable
./unicorn_logging_and_timeout.patch
# Use the Ruby API version in the plugin gem path, to match the
# one constructed by bundlerEnv
./plugin_gem_api_version.patch
# Use mv instead of rename, since rename doesn't work across
# device boundaries
./use_mv_instead_of_rename.patch
# Change the path to the auto generated plugin assets, which
# defaults to the plugin's directory and isn't writable at the
# time of asset generation
./auto_generated_path.patch
];
postPatch = ''
# Always require lib-files and application.rb through their store
# path, not their relative state directory path. This gets rid of
# warnings and means we don't have to link back to lib from the
# state directory.
find config -type f -execdir sed -Ei "s,(\.\./)+(lib|app)/,$out/share/discourse/\2/," {} \;
'';
buildPhase = ''
runHook preBuild
mv config config.dist
mv public public.dist
runHook postBuild
'';
installPhase = ''
runHook preInstall
mkdir -p $out/share
cp -r . $out/share/discourse
rm -r $out/share/discourse/log
ln -sf /var/log/discourse $out/share/discourse/log
ln -sf /var/lib/discourse/tmp $out/share/discourse/tmp
ln -sf /run/discourse/config $out/share/discourse/config
ln -sf /run/discourse/assets/javascripts/plugins $out/share/discourse/app/assets/javascripts/plugins
ln -sf /run/discourse/public $out/share/discourse/public
ln -sf ${assets} $out/share/discourse/public.dist/assets
${lib.concatMapStringsSep "\n" (p: "ln -sf ${p} $out/share/discourse/plugins/${p.pluginName or ""}") plugins}
runHook postInstall
'';
meta = with lib; {
homepage = "https://www.discourse.org/";
platforms = platforms.linux;
maintainers = with maintainers; [ talyz ];
license = licenses.gpl2Plus;
description = "Discourse is an open source discussion platform";
};
passthru = {
inherit rubyEnv runtimeEnv runtimeDeps rake mkDiscoursePlugin;
enabledPlugins = plugins;
plugins = callPackage ./plugins/all-plugins.nix { inherit mkDiscoursePlugin; };
ruby = rubyEnv.wrappedRuby;
tests = import ../../../../nixos/tests/discourse.nix { package = pkgs.discourse.override args; };
};
};
in discourse