nixpkgs/pkgs/servers/web-apps/discourse/default.nix
2021-09-23 18:46:23 +02:00

320 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.8";
src = fetchFromGitHub {
owner = "discourse";
repo = "discourse";
rev = "v${version}";
sha256 = "sha256-p4eViEvzIU6W89FZRtMBXsT7bvf2H12bTPZ/h3iD8rA=";
};
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 /run/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