Clone of https://github.com/NixOS/nixpkgs.git (to stress-test knotserver)
1{ stdenv
2, pkgs
3, makeWrapper
4, runCommand
5, lib
6, writeShellScript
7, fetchFromGitHub
8, bundlerEnv
9, callPackage
10
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_16
41, dart-sass-embedded
42
43, plugins ? []
44}@args:
45
46let
47 version = "3.1.0.beta4";
48
49 src = fetchFromGitHub {
50 owner = "discourse";
51 repo = "discourse";
52 rev = "v${version}";
53 sha256 = "sha256-22GXFYPjPYL20amR4xFB4L/dCp32H4Z3uf0GLGEghUE=";
54 };
55
56 ruby = ruby_3_2;
57
58 runtimeDeps = [
59 # For backups, themes and assets
60 rubyEnv.wrappedRuby
61 rsync
62 gzip
63 gnutar
64 git
65 brotli
66
67 # Misc required system utils
68 which
69 procps # For ps and kill
70 util-linux # For renice
71 gawk
72 nettools # For hostname
73
74 # Image optimization
75 imagemagick
76 optipng
77 oxipng
78 pngquant
79 libjpeg
80 jpegoptim
81 gifsicle
82 nodePackages.svgo
83 jhead
84 ];
85
86 runtimeEnv = {
87 HOME = "/run/discourse/home";
88 RAILS_ENV = "production";
89 UNICORN_LISTENER = "/run/discourse/sockets/unicorn.sock";
90 };
91
92 mkDiscoursePlugin =
93 { name ? null
94 , pname ? null
95 , version ? null
96 , meta ? null
97 , bundlerEnvArgs ? {}
98 , preserveGemsDir ? false
99 , src
100 , ...
101 }@args:
102 let
103 rubyEnv = bundlerEnv (bundlerEnvArgs // {
104 inherit name pname version ruby;
105 });
106 in
107 stdenv.mkDerivation (builtins.removeAttrs args [ "bundlerEnvArgs" ] // {
108 pluginName = if name != null then name else "${pname}-${version}";
109 dontConfigure = true;
110 dontBuild = true;
111 installPhase = ''
112 runHook preInstall
113 mkdir -p $out
114 cp -r * $out/
115 '' + lib.optionalString (bundlerEnvArgs != {}) (
116 if preserveGemsDir then ''
117 cp -r ${rubyEnv}/lib/ruby/gems/* $out/gems/
118 ''
119 else ''
120 if [[ -e $out/gems ]]; then
121 echo "Warning: The repo contains a 'gems' directory which will be removed!"
122 echo " If you need to preserve it, set 'preserveGemsDir = true'."
123 rm -r $out/gems
124 fi
125 ln -sf ${rubyEnv}/lib/ruby/gems $out/gems
126 '' + ''
127 runHook postInstall
128 '');
129 });
130
131 rake = runCommand "discourse-rake" {
132 nativeBuildInputs = [ makeWrapper ];
133 } ''
134 mkdir -p $out/bin
135 makeWrapper ${rubyEnv}/bin/rake $out/bin/discourse-rake \
136 ${lib.concatStrings (lib.mapAttrsToList (name: value: "--set ${name} '${value}' ") runtimeEnv)} \
137 --prefix PATH : ${lib.makeBinPath runtimeDeps} \
138 --set RAKEOPT '-f ${discourse}/share/discourse/Rakefile' \
139 --chdir '${discourse}/share/discourse'
140 '';
141
142 rubyEnv = bundlerEnv {
143 name = "discourse-ruby-env-${version}";
144 inherit version ruby;
145 gemdir = ./rubyEnv;
146 gemset =
147 let
148 gems = import ./rubyEnv/gemset.nix;
149 in
150 gems // {
151 mini_racer = gems.mini_racer // {
152 buildInputs = [ icu ];
153 dontBuild = false;
154 NIX_LDFLAGS = "-licui18n";
155 };
156 libv8-node =
157 let
158 noopScript = writeShellScript "noop" "exit 0";
159 linkFiles = writeShellScript "link-files" ''
160 cd ../..
161
162 mkdir -p vendor/v8/${stdenv.hostPlatform.system}/libv8/obj/
163 ln -s "${nodejs_16.libv8}/lib/libv8.a" vendor/v8/${stdenv.hostPlatform.system}/libv8/obj/libv8_monolith.a
164
165 ln -s ${nodejs_16.libv8}/include vendor/v8/include
166
167 mkdir -p ext/libv8-node
168 echo '--- !ruby/object:Libv8::Node::Location::Vendor {}' >ext/libv8-node/.location.yml
169 '';
170 in gems.libv8-node // {
171 dontBuild = false;
172 postPatch = ''
173 cp ${noopScript} libexec/build-libv8
174 cp ${noopScript} libexec/build-monolith
175 cp ${noopScript} libexec/download-node
176 cp ${noopScript} libexec/extract-node
177 cp ${linkFiles} libexec/inject-libv8
178 '';
179 };
180 mini_suffix = gems.mini_suffix // {
181 propagatedBuildInputs = [ libpsl ];
182 dontBuild = false;
183 # Use our libpsl instead of the vendored one, which isn't
184 # available for aarch64. It has to be called
185 # libpsl.x86_64.so or it isn't found.
186 postPatch = ''
187 cp $(readlink -f ${libpsl}/lib/libpsl.so) vendor/libpsl.x86_64.so
188 '';
189 };
190 sass-embedded = gems.sass-embedded // {
191 dontBuild = false;
192 # `sass-embedded` depends on `dart-sass-embedded` and tries to
193 # fetch that as `.tar.gz` from GitHub releases. That `.tar.gz`
194 # can also be specified via `SASS_EMBEDDED`. But instead of
195 # compressing our `dart-sass-embedded` just to decompress it
196 # again, we simply patch the Rakefile to symlink that path.
197 patches = [
198 ./rubyEnv/sass-embedded-static.patch
199 ];
200 postPatch = ''
201 export SASS_EMBEDDED=${dart-sass-embedded}/bin
202 '';
203 };
204 };
205
206 groups = [
207 "default" "assets" "development" "test"
208 ];
209 };
210
211 assets = stdenv.mkDerivation {
212 pname = "discourse-assets";
213 inherit version src;
214
215 yarnOfflineCache = fetchYarnDeps {
216 yarnLock = src + "/app/assets/javascripts/yarn.lock";
217 sha256 = "0a20kns4irdpzzx2dvdjbi0m3s754gp737q08z5nlcnffxqvykrk";
218 };
219
220 nativeBuildInputs = runtimeDeps ++ [
221 postgresql
222 redis
223 nodePackages.uglify-js
224 nodePackages.terser
225 nodePackages.patch-package
226 yarn
227 nodejs_16
228 ];
229
230 outputs = [ "out" "javascripts" ];
231
232 patches = [
233 # Use the Ruby API version in the plugin gem path, to match the
234 # one constructed by bundlerEnv
235 ./plugin_gem_api_version.patch
236
237 # Change the path to the auto generated plugin assets, which
238 # defaults to the plugin's directory and isn't writable at the
239 # time of asset generation
240 ./auto_generated_path.patch
241
242 # Fix the rake command used to recursively execute itself in the
243 # assets precompilation task.
244 ./assets_rake_command.patch
245
246 # `app/assets/javascripts/discourse/package.json`'s postinstall
247 # hook tries to call `../node_modules/.bin/patch-package`, which
248 # hasn't been `patchShebangs`-ed yet. So instead we just use
249 # `patch-package` from `nativeBuildInputs`.
250 ./asserts_patch-package_from_path.patch
251 ];
252
253 # We have to set up an environment that is close enough to
254 # production ready or the assets:precompile task refuses to
255 # run. This means that Redis and PostgreSQL has to be running and
256 # database migrations performed.
257 preBuild = ''
258 # Yarn wants a real home directory to write cache, config, etc to
259 export HOME=$NIX_BUILD_TOP/fake_home
260
261 # Make yarn install packages from our offline cache, not the registry
262 yarn config --offline set yarn-offline-mirror $yarnOfflineCache
263
264 # Fixup "resolved"-entries in yarn.lock to match our offline cache
265 ${fixup_yarn_lock}/bin/fixup_yarn_lock app/assets/javascripts/yarn.lock
266
267 export SSL_CERT_FILE=${cacert}/etc/ssl/certs/ca-bundle.crt
268
269 yarn install --offline --cwd app/assets/javascripts/discourse
270
271 patchShebangs app/assets/javascripts/node_modules/
272
273 redis-server >/dev/null &
274
275 initdb -A trust $NIX_BUILD_TOP/postgres >/dev/null
276 postgres -D $NIX_BUILD_TOP/postgres -k $NIX_BUILD_TOP >/dev/null &
277 export PGHOST=$NIX_BUILD_TOP
278
279 echo "Waiting for Redis and PostgreSQL to be ready.."
280 while ! redis-cli --scan >/dev/null || ! psql -l >/dev/null; do
281 sleep 0.1
282 done
283
284 psql -d postgres -tAc 'CREATE USER "discourse"'
285 psql -d postgres -tAc 'CREATE DATABASE "discourse" OWNER "discourse"'
286 psql 'discourse' -tAc "CREATE EXTENSION IF NOT EXISTS pg_trgm"
287 psql 'discourse' -tAc "CREATE EXTENSION IF NOT EXISTS hstore"
288
289 # Create a temporary home dir to stop bundler from complaining
290 mkdir $NIX_BUILD_TOP/tmp_home
291 export HOME=$NIX_BUILD_TOP/tmp_home
292
293 ${lib.concatMapStringsSep "\n" (p: "ln -sf ${p} plugins/${p.pluginName or ""}") plugins}
294
295 export RAILS_ENV=production
296
297 bundle exec rake db:migrate >/dev/null
298 chmod -R +w tmp
299 '';
300
301 buildPhase = ''
302 runHook preBuild
303
304 bundle exec rake assets:precompile
305
306 runHook postBuild
307 '';
308
309 installPhase = ''
310 runHook preInstall
311
312 mv public/assets $out
313
314 rm -r app/assets/javascripts/plugins
315 mv app/assets/javascripts $javascripts
316 ln -sf /run/discourse/assets/javascripts/plugins $javascripts/plugins
317
318 runHook postInstall
319 '';
320 };
321
322 discourse = stdenv.mkDerivation {
323 pname = "discourse";
324 inherit version src;
325
326 buildInputs = [
327 rubyEnv rubyEnv.wrappedRuby rubyEnv.bundler
328 ];
329
330 patches = [
331 # Load a separate NixOS site settings file
332 ./nixos_defaults.patch
333
334 # Add a noninteractive admin creation task
335 ./admin_create.patch
336
337 # Add the path to the CA cert bundle to make TLS work
338 ./action_mailer_ca_cert.patch
339
340 # Log Unicorn messages to the journal and make request timeout
341 # configurable
342 ./unicorn_logging_and_timeout.patch
343
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
347
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
352
353 # Make sure the notification email setting applies
354 ./notification_email.patch
355 ];
356
357 postPatch = ''
358 # Always require lib-files and application.rb through their store
359 # path, not their relative state directory path. This gets rid of
360 # warnings and means we don't have to link back to lib from the
361 # state directory.
362 find config -type f -name "*.rb" -execdir \
363 sed -Ei "s,(\.\./)+(lib|app)/,$out/share/discourse/\2/," {} \;
364 find config -maxdepth 1 -type f -name "*.rb" -execdir \
365 sed -Ei "s,require_relative (\"|')([[:alnum:]].*)(\"|'),require_relative '$out/share/discourse/config/\2'," {} \;
366 '';
367
368 buildPhase = ''
369 runHook preBuild
370
371 mv config config.dist
372 mv public public.dist
373
374 runHook postBuild
375 '';
376
377 installPhase = ''
378 runHook preInstall
379
380 mkdir -p $out/share
381 cp -r . $out/share/discourse
382 rm -r $out/share/discourse/log
383 ln -sf /var/log/discourse $out/share/discourse/log
384 ln -sf /var/lib/discourse/tmp $out/share/discourse/tmp
385 ln -sf /run/discourse/config $out/share/discourse/config
386 ln -sf /run/discourse/public $out/share/discourse/public
387 ln -sf ${assets} $out/share/discourse/public.dist/assets
388 rm -r $out/share/discourse/app/assets/javascripts
389 ln -sf ${assets.javascripts} $out/share/discourse/app/assets/javascripts
390 ${lib.concatMapStringsSep "\n" (p: "ln -sf ${p} $out/share/discourse/plugins/${p.pluginName or ""}") plugins}
391
392 runHook postInstall
393 '';
394
395 meta = with lib; {
396 homepage = "https://www.discourse.org/";
397 platforms = platforms.linux;
398 maintainers = with maintainers; [ talyz ];
399 license = licenses.gpl2Plus;
400 description = "Discourse is an open source discussion platform";
401 };
402
403 passthru = {
404 inherit rubyEnv runtimeEnv runtimeDeps rake mkDiscoursePlugin assets;
405 enabledPlugins = plugins;
406 plugins = callPackage ./plugins/all-plugins.nix { inherit mkDiscoursePlugin; };
407 ruby = rubyEnv.wrappedRuby;
408 tests = import ../../../../nixos/tests/discourse.nix {
409 inherit (stdenv) system;
410 inherit pkgs;
411 package = pkgs.discourse.override args;
412 };
413 };
414 };
415in discourse