at master 12 kB view raw
1{ 2 stdenv, 3 pkgs, 4 makeWrapper, 5 runCommand, 6 lib, 7 writeShellScript, 8 fetchFromGitHub, 9 bundlerEnv, 10 callPackage, 11 nixosTests, 12 13 ruby_3_3, 14 gzip, 15 gnutar, 16 git, 17 cacert, 18 util-linux, 19 gawk, 20 net-tools, 21 imagemagick, 22 optipng, 23 pngquant, 24 libjpeg, 25 jpegoptim, 26 gifsicle, 27 jhead, 28 oxipng, 29 libpsl, 30 redis, 31 postgresql, 32 which, 33 brotli, 34 procps, 35 rsync, 36 icu, 37 pnpm_9, 38 svgo, 39 nodejs, 40 jq, 41 moreutils, 42 terser, 43 uglify-js, 44 45 plugins ? [ ], 46}: 47 48let 49 version = "3.4.7"; 50 51 src = fetchFromGitHub { 52 owner = "discourse"; 53 repo = "discourse"; 54 rev = "v${version}"; 55 sha256 = "sha256-vidv5aa2r1YOcnvkqrk7ttuIk1bN5Ct7kMANl8kpEm0="; 56 }; 57 58 ruby = ruby_3_3; 59 60 runtimeDeps = [ 61 # For backups, themes and assets 62 rubyEnv.wrappedRuby 63 rsync 64 gzip 65 gnutar 66 git 67 brotli 68 nodejs 69 70 # Misc required system utils 71 which 72 procps # For ps and kill 73 util-linux # For renice 74 gawk 75 net-tools # For hostname 76 77 # Image optimization 78 imagemagick 79 optipng 80 oxipng 81 pngquant 82 libjpeg 83 jpegoptim 84 gifsicle 85 svgo 86 jhead 87 ]; 88 89 runtimeEnv = { 90 HOME = "/run/discourse/home"; 91 RAILS_ENV = "production"; 92 UNICORN_LISTENER = "/run/discourse/sockets/unicorn.sock"; 93 }; 94 95 mkDiscoursePlugin = 96 { 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 ( 108 bundlerEnvArgs 109 // { 110 inherit 111 name 112 pname 113 version 114 ruby 115 ; 116 } 117 ); 118 in 119 stdenv.mkDerivation ( 120 builtins.removeAttrs args [ "bundlerEnvArgs" ] 121 // { 122 pluginName = if name != null then name else "${pname}-${version}"; 123 dontConfigure = true; 124 dontBuild = true; 125 installPhase = '' 126 runHook preInstall 127 mkdir -p $out 128 cp -r * $out/ 129 '' 130 + lib.optionalString (bundlerEnvArgs != { }) ( 131 if preserveGemsDir then 132 '' 133 cp -r ${rubyEnv}/lib/ruby/gems/* $out/gems/ 134 '' 135 else 136 '' 137 if [[ -e $out/gems ]]; then 138 echo "Warning: The repo contains a 'gems' directory which will be removed!" 139 echo " If you need to preserve it, set 'preserveGemsDir = true'." 140 rm -r $out/gems 141 fi 142 ln -sf ${rubyEnv}/lib/ruby/gems $out/gems 143 '' 144 + '' 145 runHook postInstall 146 '' 147 ); 148 } 149 ); 150 151 rake = 152 runCommand "discourse-rake" 153 { 154 nativeBuildInputs = [ makeWrapper ]; 155 } 156 '' 157 mkdir -p $out/bin 158 makeWrapper ${rubyEnv}/bin/rake $out/bin/discourse-rake \ 159 ${ 160 lib.concatStrings (lib.mapAttrsToList (name: value: "--set ${name} '${value}' ") runtimeEnv) 161 } \ 162 --prefix PATH : ${lib.makeBinPath runtimeDeps} \ 163 --set RAKEOPT '-f ${discourse}/share/discourse/Rakefile' \ 164 --chdir '${discourse}/share/discourse' 165 ''; 166 167 rubyEnv = bundlerEnv { 168 name = "discourse-ruby-env-${version}"; 169 inherit version ruby; 170 gemdir = ./rubyEnv; 171 gemset = 172 let 173 gems = import ./rubyEnv/gemset.nix; 174 in 175 gems 176 // { 177 mini_racer = gems.mini_racer // { 178 buildInputs = [ icu ]; 179 dontBuild = false; 180 NIX_LDFLAGS = "-licui18n"; 181 }; 182 libv8-node = 183 let 184 noopScript = writeShellScript "noop" "exit 0"; 185 linkFiles = writeShellScript "link-files" '' 186 cd ../.. 187 188 mkdir -p vendor/v8/${stdenv.hostPlatform.system}/libv8/obj/ 189 ln -s "${nodejs.libv8}/lib/libv8.a" vendor/v8/${stdenv.hostPlatform.system}/libv8/obj/libv8_monolith.a 190 191 ln -s ${nodejs.libv8}/include vendor/v8/include 192 193 mkdir -p ext/libv8-node 194 echo '--- !ruby/object:Libv8::Node::Location::Vendor {}' >ext/libv8-node/.location.yml 195 ''; 196 in 197 gems.libv8-node 198 // { 199 dontBuild = false; 200 postPatch = '' 201 cp ${noopScript} libexec/build-libv8 202 cp ${noopScript} libexec/build-monolith 203 cp ${noopScript} libexec/download-node 204 cp ${noopScript} libexec/extract-node 205 cp ${linkFiles} libexec/inject-libv8 206 ''; 207 }; 208 mini_suffix = gems.mini_suffix // { 209 propagatedBuildInputs = [ libpsl ]; 210 dontBuild = false; 211 # Use our libpsl instead of the vendored one, which isn't 212 # available for aarch64. It has to be called 213 # libpsl.x86_64.so or it isn't found. 214 postPatch = '' 215 cp $(readlink -f ${lib.getLib libpsl}/lib/libpsl.so) vendor/libpsl.x86_64.so 216 ''; 217 }; 218 }; 219 220 groups = [ 221 "default" 222 "assets" 223 "development" 224 "test" 225 ]; 226 }; 227 228 assets = stdenv.mkDerivation { 229 pname = "discourse-assets"; 230 inherit version src; 231 232 pnpmDeps = pnpm_9.fetchDeps { 233 pname = "discourse-assets"; 234 inherit version src; 235 fetcherVersion = 1; 236 hash = "sha256-WyRBnuKCl5NJLtqy3HK/sJcrpMkh0PjbasGPNDV6+7Y="; 237 }; 238 239 nativeBuildInputs = runtimeDeps ++ [ 240 postgresql 241 redis 242 uglify-js 243 terser 244 jq 245 moreutils 246 nodejs 247 pnpm_9.configHook 248 ]; 249 250 outputs = [ 251 "out" 252 "javascripts" 253 "node_modules" 254 ]; 255 256 patches = [ 257 # Use the Ruby API version in the plugin gem path, to match the 258 # one constructed by bundlerEnv 259 ./plugin_gem_api_version.patch 260 261 # Change the path to the auto generated plugin assets, which 262 # defaults to the plugin's directory and isn't writable at the 263 # time of asset generation 264 ./auto_generated_path.patch 265 266 # Fix the rake command used to recursively execute itself in the 267 # assets precompilation task. 268 ./assets_rake_command.patch 269 270 # Little does he know, so he decided there is no need to generate the 271 # theme-transpiler over and over again. Which at the same time allows the removal 272 # of javascript devDependencies from the runtime environment. 273 ./prebuild-theme-transpiler.patch 274 ]; 275 276 env.RAILS_ENV = "production"; 277 278 # We have to set up an environment that is close enough to 279 # production ready or the assets:precompile task refuses to 280 # run. This means that Redis and PostgreSQL has to be running and 281 # database migrations performed. 282 preBuild = '' 283 # Patch before running postinstall hook script 284 patchShebangs node_modules/ 285 patchShebangs --build app/assets/javascripts 286 export SSL_CERT_FILE=${cacert}/etc/ssl/certs/ca-bundle.crt 287 288 redis-server >/dev/null & 289 290 initdb -A trust $NIX_BUILD_TOP/postgres >/dev/null 291 postgres -D $NIX_BUILD_TOP/postgres -k $NIX_BUILD_TOP >/dev/null & 292 export PGHOST=$NIX_BUILD_TOP 293 294 echo "Waiting for Redis and PostgreSQL to be ready.." 295 while ! redis-cli --scan >/dev/null || ! psql -l >/dev/null; do 296 sleep 0.1 297 done 298 299 psql -d postgres -tAc 'CREATE USER "discourse"' 300 psql -d postgres -tAc 'CREATE DATABASE "discourse" OWNER "discourse"' 301 psql 'discourse' -tAc "CREATE EXTENSION IF NOT EXISTS pg_trgm" 302 psql 'discourse' -tAc "CREATE EXTENSION IF NOT EXISTS hstore" 303 304 ${lib.concatMapStringsSep "\n" (p: "ln -sf ${p} plugins/${p.pluginName or ""}") plugins} 305 306 bundle exec rake db:migrate >/dev/null 307 chmod -R +w tmp 308 ''; 309 310 buildPhase = '' 311 runHook preBuild 312 313 bundle exec rake assets:precompile 314 315 runHook postBuild 316 ''; 317 318 installPhase = '' 319 runHook preInstall 320 321 mv public/assets $out 322 323 mv node_modules $node_modules 324 325 rm -r app/assets/javascripts/plugins 326 mv app/assets/javascripts $javascripts 327 ln -sf /run/discourse/assets/javascripts/plugins $javascripts/plugins 328 329 runHook postInstall 330 ''; 331 332 # The node_modules output by design has broken symlinks, as it refers to the source code. 333 # They are resolved in the primary discourse derivation. 334 dontCheckForBrokenSymlinks = true; 335 }; 336 337 discourse = stdenv.mkDerivation { 338 pname = "discourse"; 339 inherit version src; 340 341 buildInputs = [ 342 rubyEnv 343 rubyEnv.wrappedRuby 344 rubyEnv.bundler 345 ]; 346 347 patches = [ 348 # Load a separate NixOS site settings file 349 ./nixos_defaults.patch 350 351 # Add a noninteractive admin creation task 352 ./admin_create.patch 353 354 # Add the path to the CA cert bundle to make TLS work 355 ./action_mailer_ca_cert.patch 356 357 # Log Unicorn messages to the journal and make request timeout 358 # configurable 359 ./unicorn_logging_and_timeout.patch 360 361 # Use the Ruby API version in the plugin gem path, to match the 362 # one constructed by bundlerEnv 363 ./plugin_gem_api_version.patch 364 365 # Change the path to the auto generated plugin assets, which 366 # defaults to the plugin's directory and isn't writable at the 367 # time of asset generation 368 ./auto_generated_path.patch 369 370 # Make sure the notification email setting applies 371 ./notification_email.patch 372 373 # Little does he know, so he decided there is no need to generate the 374 # theme-transpiler over and over again. Which at the same time allows the removal 375 # of javascript devDependencies from the runtime environment. 376 ./prebuild-theme-transpiler.patch 377 ]; 378 379 postPatch = '' 380 # Always require lib-files and application.rb through their store 381 # path, not their relative state directory path. This gets rid of 382 # warnings and means we don't have to link back to lib from the 383 # state directory. 384 find config -type f -name "*.rb" -execdir \ 385 sed -Ei "s,(\.\./)+(lib|app)/,$out/share/discourse/\2/," {} \; 386 find config -maxdepth 1 -type f -name "*.rb" -execdir \ 387 sed -Ei "s,require_relative (\"|')([[:alnum:]].*)(\"|'),require_relative '$out/share/discourse/config/\2'," {} \; 388 ''; 389 390 buildPhase = '' 391 runHook preBuild 392 393 mv config config.dist 394 mv public public.dist 395 396 runHook postBuild 397 ''; 398 399 installPhase = '' 400 runHook preInstall 401 402 mkdir -p $out/share 403 cp -r . $out/share/discourse 404 rm -r $out/share/discourse/log 405 ln -sf /var/log/discourse $out/share/discourse/log 406 ln -sf /var/lib/discourse/tmp $out/share/discourse/tmp 407 ln -sf /run/discourse/config $out/share/discourse/config 408 ln -sf /run/discourse/public $out/share/discourse/public 409 ln -sf ${assets.node_modules} $out/share/discourse/node_modules 410 ln -sf ${assets} $out/share/discourse/public.dist/assets 411 rm -r $out/share/discourse/app/assets/javascripts 412 # This needs to be copied because it contains symlinks to node_modules 413 cp -r ${assets.javascripts} $out/share/discourse/app/assets/javascripts 414 ${lib.concatMapStringsSep "\n" ( 415 p: "ln -sf ${p} $out/share/discourse/plugins/${p.pluginName or ""}" 416 ) plugins} 417 418 runHook postInstall 419 ''; 420 421 passthru = { 422 inherit 423 rubyEnv 424 runtimeEnv 425 runtimeDeps 426 rake 427 mkDiscoursePlugin 428 assets 429 ; 430 inherit (pkgs) 431 discourseAllPlugins 432 ; 433 enabledPlugins = plugins; 434 plugins = callPackage ./plugins/all-plugins.nix { inherit mkDiscoursePlugin; }; 435 ruby = rubyEnv.wrappedRuby; 436 tests = { 437 inherit (nixosTests) 438 discourse 439 discourseAllPlugins 440 ; 441 }; 442 }; 443 meta = with lib; { 444 homepage = "https://www.discourse.org/"; 445 platforms = platforms.linux; 446 maintainers = with maintainers; [ talyz ]; 447 license = licenses.gpl2Plus; 448 description = "Open source discussion platform"; 449 }; 450 }; 451in 452discourse