forgejo-lts: 7.0.10 -> 7.0.11
[NixPkgs.git] / pkgs / servers / web-apps / discourse / default.nix
blob1f702d13502b9682d2c5b7758c3649f6bcae38ce
1 { stdenv
2 , pkgs
3 , makeWrapper
4 , runCommand
5 , lib
6 , writeShellScript
7 , fetchFromGitHub
8 , bundlerEnv
9 , callPackage
11 , ruby_3_2
12 , replace
13 , gzip
14 , gnutar
15 , git
16 , cacert
17 , util-linux
18 , gawk
19 , nettools
20 , imagemagick
21 , optipng
22 , pngquant
23 , libjpeg
24 , jpegoptim
25 , gifsicle
26 , jhead
27 , oxipng
28 , libpsl
29 , redis
30 , postgresql
31 , which
32 , brotli
33 , procps
34 , rsync
35 , icu
36 , fetchYarnDeps
37 , yarn
38 , fixup-yarn-lock
39 , nodePackages
40 , nodejs_18
41 , jq
42 , moreutils
43 , terser
44 , uglify-js
46 , plugins ? []
47 }@args:
49 let
50   version = "3.3.2";
52   src = fetchFromGitHub {
53     owner = "discourse";
54     repo = "discourse";
55     rev = "v${version}";
56     sha256 = "sha256-FaPcUta5z/8oasw+9zGBRZnUVYD8eCo1t/XwwsFoSM8=";
57   };
59   ruby = ruby_3_2;
61   runtimeDeps = [
62     # For backups, themes and assets
63     rubyEnv.wrappedRuby
64     rsync
65     gzip
66     gnutar
67     git
68     brotli
69     nodejs_18
71     # Misc required system utils
72     which
73     procps       # For ps and kill
74     util-linux   # For renice
75     gawk
76     nettools     # For hostname
78     # Image optimization
79     imagemagick
80     optipng
81     oxipng
82     pngquant
83     libjpeg
84     jpegoptim
85     gifsicle
86     nodePackages.svgo
87     jhead
88   ];
90   runtimeEnv = {
91     HOME = "/run/discourse/home";
92     RAILS_ENV = "production";
93     UNICORN_LISTENER = "/run/discourse/sockets/unicorn.sock";
94   };
96   mkDiscoursePlugin =
97     { name ? null
98     , pname ? null
99     , version ? null
100     , meta ? null
101     , bundlerEnvArgs ? {}
102     , preserveGemsDir ? false
103     , src
104     , ...
105     }@args:
106     let
107       rubyEnv = bundlerEnv (bundlerEnvArgs // {
108         inherit name pname version ruby;
109       });
110     in
111       stdenv.mkDerivation (builtins.removeAttrs args [ "bundlerEnvArgs" ] // {
112         pluginName = if name != null then name else "${pname}-${version}";
113         dontConfigure = true;
114         dontBuild = true;
115         installPhase = ''
116           runHook preInstall
117           mkdir -p $out
118           cp -r * $out/
119         '' + lib.optionalString (bundlerEnvArgs != {}) (
120           if preserveGemsDir then ''
121             cp -r ${rubyEnv}/lib/ruby/gems/* $out/gems/
122           ''
123           else ''
124             if [[ -e $out/gems ]]; then
125               echo "Warning: The repo contains a 'gems' directory which will be removed!"
126               echo "         If you need to preserve it, set 'preserveGemsDir = true'."
127               rm -r $out/gems
128             fi
129             ln -sf ${rubyEnv}/lib/ruby/gems $out/gems
130           '' + ''
131           runHook postInstall
132         '');
133       });
135   rake = runCommand "discourse-rake" {
136     nativeBuildInputs = [ makeWrapper ];
137   } ''
138     mkdir -p $out/bin
139     makeWrapper ${rubyEnv}/bin/rake $out/bin/discourse-rake \
140         ${lib.concatStrings (lib.mapAttrsToList (name: value: "--set ${name} '${value}' ") runtimeEnv)} \
141         --prefix PATH : ${lib.makeBinPath runtimeDeps} \
142         --set RAKEOPT '-f ${discourse}/share/discourse/Rakefile' \
143         --chdir '${discourse}/share/discourse'
144   '';
146   rubyEnv = bundlerEnv {
147     name = "discourse-ruby-env-${version}";
148     inherit version ruby;
149     gemdir = ./rubyEnv;
150     gemset =
151       let
152         gems = import ./rubyEnv/gemset.nix;
153       in
154         gems // {
155           mini_racer = gems.mini_racer // {
156             buildInputs = [ icu ];
157             dontBuild = false;
158             NIX_LDFLAGS = "-licui18n";
159           };
160           libv8-node =
161             let
162               noopScript = writeShellScript "noop" "exit 0";
163               linkFiles = writeShellScript "link-files" ''
164                 cd ../..
166                 mkdir -p vendor/v8/${stdenv.hostPlatform.system}/libv8/obj/
167                 ln -s "${nodejs_18.libv8}/lib/libv8.a" vendor/v8/${stdenv.hostPlatform.system}/libv8/obj/libv8_monolith.a
169                 ln -s ${nodejs_18.libv8}/include vendor/v8/include
171                 mkdir -p ext/libv8-node
172                 echo '--- !ruby/object:Libv8::Node::Location::Vendor {}' >ext/libv8-node/.location.yml
173               '';
174             in gems.libv8-node // {
175               dontBuild = false;
176               postPatch = ''
177                 cp ${noopScript} libexec/build-libv8
178                 cp ${noopScript} libexec/build-monolith
179                 cp ${noopScript} libexec/download-node
180                 cp ${noopScript} libexec/extract-node
181                 cp ${linkFiles} libexec/inject-libv8
182               '';
183             };
184           mini_suffix = gems.mini_suffix // {
185             propagatedBuildInputs = [ libpsl ];
186             dontBuild = false;
187             # Use our libpsl instead of the vendored one, which isn't
188             # available for aarch64. It has to be called
189             # libpsl.x86_64.so or it isn't found.
190             postPatch = ''
191               cp $(readlink -f ${lib.getLib libpsl}/lib/libpsl.so) vendor/libpsl.x86_64.so
192             '';
193           };
194         };
196     groups = [
197       "default" "assets" "development" "test"
198     ];
199   };
201   assets = stdenv.mkDerivation {
202     pname = "discourse-assets";
203     inherit version src;
205     yarnOfflineCache = fetchYarnDeps {
206       yarnLock = src + "/yarn.lock";
207       hash = "sha256-cSQofaULCmPuWGxS+hK4KlRq9lSkCPiYvhax9X6Dor8=";
208     };
210     nativeBuildInputs = runtimeDeps ++ [
211       postgresql
212       redis
213       uglify-js
214       terser
215       yarn
216       jq
217       moreutils
218       fixup-yarn-lock
219     ];
221     outputs = [ "out" "javascripts" "node_modules" ];
223     patches = [
224       # Use the Ruby API version in the plugin gem path, to match the
225       # one constructed by bundlerEnv
226       ./plugin_gem_api_version.patch
228       # Change the path to the auto generated plugin assets, which
229       # defaults to the plugin's directory and isn't writable at the
230       # time of asset generation
231       ./auto_generated_path.patch
233       # Fix the rake command used to recursively execute itself in the
234       # assets precompilation task.
235       ./assets_rake_command.patch
237       # Little does he know, so he decided there is no need to generate the
238       # theme-transpiler over and over again. Which at the same time allows the removal
239       # of javascript devDependencies from the runtime environment.
240       ./prebuild-theme-transpiler.patch
241     ];
243     env.RAILS_ENV = "production";
245     # We have to set up an environment that is close enough to
246     # production ready or the assets:precompile task refuses to
247     # run. This means that Redis and PostgreSQL has to be running and
248     # database migrations performed.
249     preBuild = ''
250       # Yarn wants a real home directory to write cache, config, etc to
251       export HOME=$NIX_BUILD_TOP/fake_home
253       yarn_install() {
254         local offlineCache=$1 yarnLock=$2
256         # Make yarn install packages from our offline cache, not the registry
257         yarn config --offline set yarn-offline-mirror $offlineCache
259         # Fixup "resolved"-entries in yarn.lock to match our offline cache
260         fixup-yarn-lock $yarnLock
262         # Install while ignoring hook scripts
263         yarn --offline --ignore-scripts --cwd $(dirname $yarnLock) install
264       }
266       # Install runtime and devDependencies.
267       # The dev deps are necessary for generating the theme-transpiler executed as dependent task
268       # assets:precompile:theme_transpiler before db:migrate and unfortunately also in the runtime
269       yarn_install $yarnOfflineCache yarn.lock
271       # Patch before running postinstall hook script
272       patchShebangs node_modules/
273       patchShebangs --build app/assets/javascripts
274       yarn --offline --cwd app/assets/javascripts run postinstall
275       export SSL_CERT_FILE=${cacert}/etc/ssl/certs/ca-bundle.crt
277       redis-server >/dev/null &
279       initdb -A trust $NIX_BUILD_TOP/postgres >/dev/null
280       postgres -D $NIX_BUILD_TOP/postgres -k $NIX_BUILD_TOP >/dev/null &
281       export PGHOST=$NIX_BUILD_TOP
283       echo "Waiting for Redis and PostgreSQL to be ready.."
284       while ! redis-cli --scan >/dev/null || ! psql -l >/dev/null; do
285         sleep 0.1
286       done
288       psql -d postgres -tAc 'CREATE USER "discourse"'
289       psql -d postgres -tAc 'CREATE DATABASE "discourse" OWNER "discourse"'
290       psql 'discourse' -tAc "CREATE EXTENSION IF NOT EXISTS pg_trgm"
291       psql 'discourse' -tAc "CREATE EXTENSION IF NOT EXISTS hstore"
293       ${lib.concatMapStringsSep "\n" (p: "ln -sf ${p} plugins/${p.pluginName or ""}") plugins}
295       bundle exec rake db:migrate >/dev/null
296       chmod -R +w tmp
297     '';
299     buildPhase = ''
300       runHook preBuild
302       bundle exec rake assets:precompile
304       runHook postBuild
305     '';
307     installPhase = ''
308       runHook preInstall
310       mv public/assets $out
312       mv node_modules $node_modules
314       rm -r app/assets/javascripts/plugins
315       mv app/assets/javascripts $javascripts
316       ln -sf /run/discourse/assets/javascripts/plugins $javascripts/plugins
318       runHook postInstall
319     '';
320   };
322   discourse = stdenv.mkDerivation {
323     pname = "discourse";
324     inherit version src;
326     buildInputs = [
327       rubyEnv rubyEnv.wrappedRuby rubyEnv.bundler
328     ];
330     patches = [
331       # Load a separate NixOS site settings file
332       ./nixos_defaults.patch
334       # Add a noninteractive admin creation task
335       ./admin_create.patch
337       # Add the path to the CA cert bundle to make TLS work
338       ./action_mailer_ca_cert.patch
340       # Log Unicorn messages to the journal and make request timeout
341       # configurable
342       ./unicorn_logging_and_timeout.patch
344       # Use the Ruby API version in the plugin gem path, to match the
345       # one constructed by bundlerEnv
346       ./plugin_gem_api_version.patch
348       # Change the path to the auto generated plugin assets, which
349       # defaults to the plugin's directory and isn't writable at the
350       # time of asset generation
351       ./auto_generated_path.patch
353       # Make sure the notification email setting applies
354       ./notification_email.patch
356       # Little does he know, so he decided there is no need to generate the
357       # theme-transpiler over and over again. Which at the same time allows the removal
358       # of javascript devDependencies from the runtime environment.
359       ./prebuild-theme-transpiler.patch
360     ];
362     postPatch = ''
363       # Always require lib-files and application.rb through their store
364       # path, not their relative state directory path. This gets rid of
365       # warnings and means we don't have to link back to lib from the
366       # state directory.
367       find config -type f -name "*.rb" -execdir \
368         sed -Ei "s,(\.\./)+(lib|app)/,$out/share/discourse/\2/," {} \;
369       find config -maxdepth 1 -type f -name "*.rb" -execdir \
370         sed -Ei "s,require_relative (\"|')([[:alnum:]].*)(\"|'),require_relative '$out/share/discourse/config/\2'," {} \;
371     '';
373     buildPhase = ''
374       runHook preBuild
376       mv config config.dist
377       mv public public.dist
379       runHook postBuild
380     '';
382     installPhase = ''
383       runHook preInstall
385       mkdir -p $out/share
386       cp -r . $out/share/discourse
387       rm -r $out/share/discourse/log
388       ln -sf /var/log/discourse $out/share/discourse/log
389       ln -sf /var/lib/discourse/tmp $out/share/discourse/tmp
390       ln -sf /run/discourse/config $out/share/discourse/config
391       ln -sf /run/discourse/public $out/share/discourse/public
392       # This needs to be copied because of symlinks in node_modules
393       # Also this needs to be full node_modules (including dev deps) because without loader.js it just throws 500
394       cp -r ${assets.node_modules} $out/share/discourse/node_modules
395       ln -sf ${assets} $out/share/discourse/public.dist/assets
396       rm -r $out/share/discourse/app/assets/javascripts
397       ln -sf ${assets.javascripts} $out/share/discourse/app/assets/javascripts
398       ${lib.concatMapStringsSep "\n" (p: "ln -sf ${p} $out/share/discourse/plugins/${p.pluginName or ""}") plugins}
400       runHook postInstall
401     '';
403     meta = with lib; {
404       homepage = "https://www.discourse.org/";
405       platforms = platforms.linux;
406       maintainers = with maintainers; [ talyz ];
407       license = licenses.gpl2Plus;
408       description = "Discourse is an open source discussion platform";
409     };
411     passthru = {
412       inherit rubyEnv runtimeEnv runtimeDeps rake mkDiscoursePlugin assets;
413       inherit (pkgs)
414         discourseAllPlugins
415       ;
416       enabledPlugins = plugins;
417       plugins = callPackage ./plugins/all-plugins.nix { inherit mkDiscoursePlugin; };
418       ruby = rubyEnv.wrappedRuby;
419       tests = import ../../../../nixos/tests/discourse.nix {
420         inherit (stdenv) system;
421         inherit pkgs;
422         package = pkgs.discourse.override args;
423       };
424     };
425   };
426 in discourse