···118118 <xref linkend="opt-services.samba-wsdd.enable" /> Web Services Dynamic Discovery host daemon
119119 </para>
120120 </listitem>
121121+ <listitem>
122122+ <para>
123123+ <link xlink:href="https://www.discourse.org/">Discourse</link>, a
124124+ modern and open source discussion platform.
125125+ </para>
126126+ <para>
127127+ See the <link linkend="module-services-discourse">Discourse
128128+ section of the NixOS manual</link> for more information.
129129+ </para>
130130+ </listitem>
121131 </itemizedlist>
122132123133 </section>
···5050 # List of components used in config
5151 extraComponents = filter useComponent availableComponents;
52525353- package = if (cfg.autoExtraComponents && cfg.config != null)
5353+ testedPackage = if (cfg.autoExtraComponents && cfg.config != null)
5454 then (cfg.package.override { inherit extraComponents; })
5555 else cfg.package;
5656+5757+ # overridePythonAttrs has to be applied after override
5858+ package = testedPackage.overridePythonAttrs (oldAttrs: {
5959+ doCheck = false;
6060+ });
56615762 # If you are changing this, please update the description in applyDefaultConfig
5863 defaultConfig = {
···183188 };
184189185190 package = mkOption {
186186- default = pkgs.home-assistant.overridePythonAttrs (oldAttrs: {
187187- doCheck = false;
188188- });
191191+ default = pkgs.home-assistant;
189192 defaultText = literalExample ''
190190- pkgs.home-assistant.overridePythonAttrs (oldAttrs: {
191191- doCheck = false;
192192- })
193193+ pkgs.home-assistant
193194 '';
194195 type = types.package;
195196 example = literalExample ''
···198199 }
199200 '';
200201 description = ''
201201- Home Assistant package to use. By default the tests are disabled, as they take a considerable amout of time to complete.
202202+ Home Assistant package to use. Tests are automatically disabled, as they take a considerable amout of time to complete.
202203 Override <literal>extraPackages</literal> or <literal>extraComponents</literal> in order to add additional dependencies.
203204 If you specify <option>config</option> and do not set <option>autoExtraComponents</option>
204205 to <literal>false</literal>, overriding <literal>extraComponents</literal> will have no effect.
206206+ Avoid <literal>home-assistant.overridePythonAttrs</literal> if you use
207207+ <literal>autoExtraComponents</literal>.
205208 '';
206209 };
207210
+1-1
nixos/modules/services/networking/gvpe.nix
···2727 text = ''
2828 #! /bin/sh
29293030- export PATH=$PATH:${pkgs.iproute}/sbin
3030+ export PATH=$PATH:${pkgs.iproute2}/sbin
31313232 ip link set $IFNAME up
3333 ip address add ${cfg.ipAddress} dev $IFNAME
···11+{ config, options, lib, pkgs, utils, ... }:
22+33+let
44+ json = pkgs.formats.json {};
55+66+ cfg = config.services.discourse;
77+88+ postgresqlPackage = if config.services.postgresql.enable then
99+ config.services.postgresql.package
1010+ else
1111+ pkgs.postgresql;
1212+1313+ # We only want to create a database if we're actually going to connect to it.
1414+ databaseActuallyCreateLocally = cfg.database.createLocally && cfg.database.host == null;
1515+1616+ tlsEnabled = (cfg.enableACME
1717+ || cfg.sslCertificate != null
1818+ || cfg.sslCertificateKey != null);
1919+in
2020+{
2121+ options = {
2222+ services.discourse = {
2323+ enable = lib.mkEnableOption "Discourse, an open source discussion platform";
2424+2525+ package = lib.mkOption {
2626+ type = lib.types.package;
2727+ default = pkgs.discourse;
2828+ defaultText = "pkgs.discourse";
2929+ description = ''
3030+ The discourse package to use.
3131+ '';
3232+ };
3333+3434+ hostname = lib.mkOption {
3535+ type = lib.types.str;
3636+ default = if config.networking.domain != null then
3737+ config.networking.fqdn
3838+ else
3939+ config.networking.hostName;
4040+ defaultText = "config.networking.fqdn";
4141+ example = "discourse.example.com";
4242+ description = ''
4343+ The hostname to serve Discourse on.
4444+ '';
4545+ };
4646+4747+ secretKeyBaseFile = lib.mkOption {
4848+ type = with lib.types; nullOr path;
4949+ default = null;
5050+ example = "/run/keys/secret_key_base";
5151+ description = ''
5252+ The path to a file containing the
5353+ <literal>secret_key_base</literal> secret.
5454+5555+ Discourse uses <literal>secret_key_base</literal> to encrypt
5656+ the cookie store, which contains session data, and to digest
5757+ user auth tokens.
5858+5959+ Needs to be a 64 byte long string of hexadecimal
6060+ characters. You can generate one by running
6161+6262+ <screen>
6363+ <prompt>$ </prompt>openssl rand -hex 64 >/path/to/secret_key_base_file
6464+ </screen>
6565+6666+ This should be a string, not a nix path, since nix paths are
6767+ copied into the world-readable nix store.
6868+ '';
6969+ };
7070+7171+ sslCertificate = lib.mkOption {
7272+ type = with lib.types; nullOr path;
7373+ default = null;
7474+ example = "/run/keys/ssl.cert";
7575+ description = ''
7676+ The path to the server SSL certificate. Set this to enable
7777+ SSL.
7878+ '';
7979+ };
8080+8181+ sslCertificateKey = lib.mkOption {
8282+ type = with lib.types; nullOr path;
8383+ default = null;
8484+ example = "/run/keys/ssl.key";
8585+ description = ''
8686+ The path to the server SSL certificate key. Set this to
8787+ enable SSL.
8888+ '';
8989+ };
9090+9191+ enableACME = lib.mkOption {
9292+ type = lib.types.bool;
9393+ default = cfg.sslCertificate == null && cfg.sslCertificateKey == null;
9494+ defaultText = "true, unless services.discourse.sslCertificate and services.discourse.sslCertificateKey are set.";
9595+ description = ''
9696+ Whether an ACME certificate should be used to secure
9797+ connections to the server.
9898+ '';
9999+ };
100100+101101+ backendSettings = lib.mkOption {
102102+ type = with lib.types; attrsOf (nullOr (oneOf [ str int bool float ]));
103103+ default = {};
104104+ example = lib.literalExample ''
105105+ {
106106+ max_reqs_per_ip_per_minute = 300;
107107+ max_reqs_per_ip_per_10_seconds = 60;
108108+ max_asset_reqs_per_ip_per_10_seconds = 250;
109109+ max_reqs_per_ip_mode = "warn+block";
110110+ };
111111+ '';
112112+ description = ''
113113+ Additional settings to put in the
114114+ <filename>discourse.conf</filename> file.
115115+116116+ Look in the
117117+ <link xlink:href="https://github.com/discourse/discourse/blob/master/config/discourse_defaults.conf">discourse_defaults.conf</link>
118118+ file in the upstream distribution to find available options.
119119+120120+ Setting an option to <literal>null</literal> means
121121+ <quote>define variable, but leave right-hand side
122122+ empty</quote>.
123123+ '';
124124+ };
125125+126126+ siteSettings = lib.mkOption {
127127+ type = json.type;
128128+ default = {};
129129+ example = lib.literalExample ''
130130+ {
131131+ required = {
132132+ title = "My Cats";
133133+ site_description = "Discuss My Cats (and be nice plz)";
134134+ };
135135+ login = {
136136+ enable_github_logins = true;
137137+ github_client_id = "a2f6dfe838cb3206ce20";
138138+ github_client_secret._secret = /run/keys/discourse_github_client_secret;
139139+ };
140140+ };
141141+ '';
142142+ description = ''
143143+ Discourse site settings. These are the settings that can be
144144+ changed from the UI. This only defines their default values:
145145+ they can still be overridden from the UI.
146146+147147+ Available settings can be found by looking in the
148148+ <link xlink:href="https://github.com/discourse/discourse/blob/master/config/site_settings.yml">site_settings.yml</link>
149149+ file of the upstream distribution. To find a setting's path,
150150+ you only need to care about the first two levels; i.e. its
151151+ category and name. See the example.
152152+153153+ Settings containing secret data should be set to an
154154+ attribute set containing the attribute
155155+ <literal>_secret</literal> - a string pointing to a file
156156+ containing the value the option should be set to. See the
157157+ example to get a better picture of this: in the resulting
158158+ <filename>config/nixos_site_settings.json</filename> file,
159159+ the <literal>login.github_client_secret</literal> key will
160160+ be set to the contents of the
161161+ <filename>/run/keys/discourse_github_client_secret</filename>
162162+ file.
163163+ '';
164164+ };
165165+166166+ admin = {
167167+ email = lib.mkOption {
168168+ type = lib.types.str;
169169+ example = "admin@example.com";
170170+ description = ''
171171+ The admin user email address.
172172+ '';
173173+ };
174174+175175+ username = lib.mkOption {
176176+ type = lib.types.str;
177177+ example = "admin";
178178+ description = ''
179179+ The admin user username.
180180+ '';
181181+ };
182182+183183+ fullName = lib.mkOption {
184184+ type = lib.types.str;
185185+ description = ''
186186+ The admin user's full name.
187187+ '';
188188+ };
189189+190190+ passwordFile = lib.mkOption {
191191+ type = lib.types.path;
192192+ description = ''
193193+ A path to a file containing the admin user's password.
194194+195195+ This should be a string, not a nix path, since nix paths are
196196+ copied into the world-readable nix store.
197197+ '';
198198+ };
199199+ };
200200+201201+ nginx.enable = lib.mkOption {
202202+ type = lib.types.bool;
203203+ default = true;
204204+ description = ''
205205+ Whether an <literal>nginx</literal> virtual host should be
206206+ set up to serve Discourse. Only disable if you're planning
207207+ to use a different web server, which is not recommended.
208208+ '';
209209+ };
210210+211211+ database = {
212212+ pool = lib.mkOption {
213213+ type = lib.types.int;
214214+ default = 8;
215215+ description = ''
216216+ Database connection pool size.
217217+ '';
218218+ };
219219+220220+ host = lib.mkOption {
221221+ type = with lib.types; nullOr str;
222222+ default = null;
223223+ description = ''
224224+ Discourse database hostname. <literal>null</literal> means <quote>prefer
225225+ local unix socket connection</quote>.
226226+ '';
227227+ };
228228+229229+ passwordFile = lib.mkOption {
230230+ type = with lib.types; nullOr path;
231231+ default = null;
232232+ description = ''
233233+ File containing the Discourse database user password.
234234+235235+ This should be a string, not a nix path, since nix paths are
236236+ copied into the world-readable nix store.
237237+ '';
238238+ };
239239+240240+ createLocally = lib.mkOption {
241241+ type = lib.types.bool;
242242+ default = true;
243243+ description = ''
244244+ Whether a database should be automatically created on the
245245+ local host. Set this to <literal>false</literal> if you plan
246246+ on provisioning a local database yourself. This has no effect
247247+ if <option>services.discourse.database.host</option> is customized.
248248+ '';
249249+ };
250250+251251+ name = lib.mkOption {
252252+ type = lib.types.str;
253253+ default = "discourse";
254254+ description = ''
255255+ Discourse database name.
256256+ '';
257257+ };
258258+259259+ username = lib.mkOption {
260260+ type = lib.types.str;
261261+ default = "discourse";
262262+ description = ''
263263+ Discourse database user.
264264+ '';
265265+ };
266266+ };
267267+268268+ redis = {
269269+ host = lib.mkOption {
270270+ type = lib.types.str;
271271+ default = "localhost";
272272+ description = ''
273273+ Redis server hostname.
274274+ '';
275275+ };
276276+277277+ passwordFile = lib.mkOption {
278278+ type = with lib.types; nullOr path;
279279+ default = null;
280280+ description = ''
281281+ File containing the Redis password.
282282+283283+ This should be a string, not a nix path, since nix paths are
284284+ copied into the world-readable nix store.
285285+ '';
286286+ };
287287+288288+ dbNumber = lib.mkOption {
289289+ type = lib.types.int;
290290+ default = 0;
291291+ description = ''
292292+ Redis database number.
293293+ '';
294294+ };
295295+296296+ useSSL = lib.mkOption {
297297+ type = lib.types.bool;
298298+ default = cfg.redis.host != "localhost";
299299+ description = ''
300300+ Connect to Redis with SSL.
301301+ '';
302302+ };
303303+ };
304304+305305+ mail = {
306306+ notificationEmailAddress = lib.mkOption {
307307+ type = lib.types.str;
308308+ default = "${if cfg.mail.incoming.enable then "notifications" else "noreply"}@${cfg.hostname}";
309309+ defaultText = ''
310310+ "notifications@`config.services.discourse.hostname`" if
311311+ config.services.discourse.mail.incoming.enable is "true",
312312+ otherwise "noreply`config.services.discourse.hostname`"
313313+ '';
314314+ description = ''
315315+ The <literal>from:</literal> email address used when
316316+ sending all essential system emails. The domain specified
317317+ here must have SPF, DKIM and reverse PTR records set
318318+ correctly for email to arrive.
319319+ '';
320320+ };
321321+322322+ contactEmailAddress = lib.mkOption {
323323+ type = lib.types.str;
324324+ default = "";
325325+ description = ''
326326+ Email address of key contact responsible for this
327327+ site. Used for critical notifications, as well as on the
328328+ <literal>/about</literal> contact form for urgent matters.
329329+ '';
330330+ };
331331+332332+ outgoing = {
333333+ serverAddress = lib.mkOption {
334334+ type = lib.types.str;
335335+ default = "localhost";
336336+ description = ''
337337+ The address of the SMTP server Discourse should use to
338338+ send email.
339339+ '';
340340+ };
341341+342342+ port = lib.mkOption {
343343+ type = lib.types.int;
344344+ default = 25;
345345+ description = ''
346346+ The port of the SMTP server Discourse should use to
347347+ send email.
348348+ '';
349349+ };
350350+351351+ username = lib.mkOption {
352352+ type = with lib.types; nullOr str;
353353+ default = null;
354354+ description = ''
355355+ The username of the SMTP server.
356356+ '';
357357+ };
358358+359359+ passwordFile = lib.mkOption {
360360+ type = lib.types.nullOr lib.types.path;
361361+ default = null;
362362+ description = ''
363363+ A file containing the password of the SMTP server account.
364364+365365+ This should be a string, not a nix path, since nix paths
366366+ are copied into the world-readable nix store.
367367+ '';
368368+ };
369369+370370+ domain = lib.mkOption {
371371+ type = lib.types.str;
372372+ default = cfg.hostname;
373373+ description = ''
374374+ HELO domain to use for outgoing mail.
375375+ '';
376376+ };
377377+378378+ authentication = lib.mkOption {
379379+ type = with lib.types; nullOr (enum ["plain" "login" "cram_md5"]);
380380+ default = null;
381381+ description = ''
382382+ Authentication type to use, see http://api.rubyonrails.org/classes/ActionMailer/Base.html
383383+ '';
384384+ };
385385+386386+ enableStartTLSAuto = lib.mkOption {
387387+ type = lib.types.bool;
388388+ default = true;
389389+ description = ''
390390+ Whether to try to use StartTLS.
391391+ '';
392392+ };
393393+394394+ opensslVerifyMode = lib.mkOption {
395395+ type = lib.types.str;
396396+ default = "peer";
397397+ description = ''
398398+ How OpenSSL checks the certificate, see http://api.rubyonrails.org/classes/ActionMailer/Base.html
399399+ '';
400400+ };
401401+ };
402402+403403+ incoming = {
404404+ enable = lib.mkOption {
405405+ type = lib.types.bool;
406406+ default = false;
407407+ description = ''
408408+ Whether to set up Postfix to receive incoming mail.
409409+ '';
410410+ };
411411+412412+ replyEmailAddress = lib.mkOption {
413413+ type = lib.types.str;
414414+ default = "%{reply_key}@${cfg.hostname}";
415415+ defaultText = "%{reply_key}@`config.services.discourse.hostname`";
416416+ description = ''
417417+ Template for reply by email incoming email address, for
418418+ example: %{reply_key}@reply.example.com or
419419+ replies+%{reply_key}@example.com
420420+ '';
421421+ };
422422+423423+ mailReceiverPackage = lib.mkOption {
424424+ type = lib.types.package;
425425+ default = pkgs.discourse-mail-receiver;
426426+ defaultText = "pkgs.discourse-mail-receiver";
427427+ description = ''
428428+ The discourse-mail-receiver package to use.
429429+ '';
430430+ };
431431+432432+ apiKeyFile = lib.mkOption {
433433+ type = lib.types.nullOr lib.types.path;
434434+ default = null;
435435+ description = ''
436436+ A file containing the Discourse API key used to add
437437+ posts and messages from mail. If left at its default
438438+ value <literal>null</literal>, one will be automatically
439439+ generated.
440440+441441+ This should be a string, not a nix path, since nix paths
442442+ are copied into the world-readable nix store.
443443+ '';
444444+ };
445445+ };
446446+ };
447447+448448+ plugins = lib.mkOption {
449449+ type = lib.types.listOf lib.types.package;
450450+ default = [];
451451+ example = ''
452452+ [
453453+ (pkgs.fetchFromGitHub {
454454+ owner = "discourse";
455455+ repo = "discourse-spoiler-alert";
456456+ rev = "e200cfa571d252cab63f3d30d619b370986e4cee";
457457+ sha256 = "0ya69ix5g77wz4c9x9gmng6l25ghb5xxlx3icr6jam16q14dzc33";
458458+ })
459459+ ];
460460+ '';
461461+ description = ''
462462+ <productname>Discourse</productname> plugins to install as a
463463+ list of derivations. As long as a plugin supports the
464464+ standard install method, packaging it should only require
465465+ fetching its source with an appropriate fetcher.
466466+ '';
467467+ };
468468+469469+ sidekiqProcesses = lib.mkOption {
470470+ type = lib.types.int;
471471+ default = 1;
472472+ description = ''
473473+ How many Sidekiq processes should be spawned.
474474+ '';
475475+ };
476476+477477+ unicornTimeout = lib.mkOption {
478478+ type = lib.types.int;
479479+ default = 30;
480480+ description = ''
481481+ Time in seconds before a request to Unicorn times out.
482482+483483+ This can be raised if the system Discourse is running on is
484484+ too slow to handle many requests within 30 seconds.
485485+ '';
486486+ };
487487+ };
488488+ };
489489+490490+ config = lib.mkIf cfg.enable {
491491+ assertions = [
492492+ {
493493+ assertion = (cfg.database.host != null) -> (cfg.database.passwordFile != null);
494494+ message = "When services.gitlab.database.host is customized, services.discourse.database.passwordFile must be set!";
495495+ }
496496+ {
497497+ assertion = cfg.hostname != "";
498498+ message = "Could not automatically determine hostname, set service.discourse.hostname manually.";
499499+ }
500500+ ];
501501+502502+503503+ # Default config values are from `config/discourse_defaults.conf`
504504+ # upstream.
505505+ services.discourse.backendSettings = lib.mapAttrs (_: lib.mkDefault) {
506506+ db_pool = cfg.database.pool;
507507+ db_timeout = 5000;
508508+ db_connect_timeout = 5;
509509+ db_socket = null;
510510+ db_host = cfg.database.host;
511511+ db_backup_host = null;
512512+ db_port = null;
513513+ db_backup_port = 5432;
514514+ db_name = cfg.database.name;
515515+ db_username = if databaseActuallyCreateLocally then "discourse" else cfg.database.username;
516516+ db_password = cfg.database.passwordFile;
517517+ db_prepared_statements = false;
518518+ db_replica_host = null;
519519+ db_replica_port = null;
520520+ db_advisory_locks = true;
521521+522522+ inherit (cfg) hostname;
523523+ backup_hostname = null;
524524+525525+ smtp_address = cfg.mail.outgoing.serverAddress;
526526+ smtp_port = cfg.mail.outgoing.port;
527527+ smtp_domain = cfg.mail.outgoing.domain;
528528+ smtp_user_name = cfg.mail.outgoing.username;
529529+ smtp_password = cfg.mail.outgoing.passwordFile;
530530+ smtp_authentication = cfg.mail.outgoing.authentication;
531531+ smtp_enable_start_tls = cfg.mail.outgoing.enableStartTLSAuto;
532532+ smtp_openssl_verify_mode = cfg.mail.outgoing.opensslVerifyMode;
533533+534534+ load_mini_profiler = true;
535535+ mini_profiler_snapshots_period = 0;
536536+ mini_profiler_snapshots_transport_url = null;
537537+ mini_profiler_snapshots_transport_auth_key = null;
538538+539539+ cdn_url = null;
540540+ cdn_origin_hostname = null;
541541+ developer_emails = null;
542542+543543+ redis_host = cfg.redis.host;
544544+ redis_port = 6379;
545545+ redis_slave_host = null;
546546+ redis_slave_port = 6379;
547547+ redis_db = cfg.redis.dbNumber;
548548+ redis_password = cfg.redis.passwordFile;
549549+ redis_skip_client_commands = false;
550550+ redis_use_ssl = cfg.redis.useSSL;
551551+552552+ message_bus_redis_enabled = false;
553553+ message_bus_redis_host = "localhost";
554554+ message_bus_redis_port = 6379;
555555+ message_bus_redis_slave_host = null;
556556+ message_bus_redis_slave_port = 6379;
557557+ message_bus_redis_db = 0;
558558+ message_bus_redis_password = null;
559559+ message_bus_redis_skip_client_commands = false;
560560+561561+ enable_cors = false;
562562+ cors_origin = "";
563563+ serve_static_assets = false;
564564+ sidekiq_workers = 5;
565565+ rtl_css = false;
566566+ connection_reaper_age = 30;
567567+ connection_reaper_interval = 30;
568568+ relative_url_root = null;
569569+ message_bus_max_backlog_size = 100;
570570+ secret_key_base = cfg.secretKeyBaseFile;
571571+ fallback_assets_path = null;
572572+573573+ s3_bucket = null;
574574+ s3_region = null;
575575+ s3_access_key_id = null;
576576+ s3_secret_access_key = null;
577577+ s3_use_iam_profile = null;
578578+ s3_cdn_url = null;
579579+ s3_endpoint = null;
580580+ s3_http_continue_timeout = null;
581581+ s3_install_cors_rule = null;
582582+583583+ max_user_api_reqs_per_minute = 20;
584584+ max_user_api_reqs_per_day = 2880;
585585+ max_admin_api_reqs_per_key_per_minute = 60;
586586+ max_reqs_per_ip_per_minute = 200;
587587+ max_reqs_per_ip_per_10_seconds = 50;
588588+ max_asset_reqs_per_ip_per_10_seconds = 200;
589589+ max_reqs_per_ip_mode = "block";
590590+ max_reqs_rate_limit_on_private = false;
591591+ force_anonymous_min_queue_seconds = 1;
592592+ force_anonymous_min_per_10_seconds = 3;
593593+ background_requests_max_queue_length = 0.5;
594594+ reject_message_bus_queue_seconds = 0.1;
595595+ disable_search_queue_threshold = 1;
596596+ max_old_rebakes_per_15_minutes = 300;
597597+ max_logster_logs = 1000;
598598+ refresh_maxmind_db_during_precompile_days = 2;
599599+ maxmind_backup_path = null;
600600+ maxmind_license_key = null;
601601+ enable_performance_http_headers = false;
602602+ enable_js_error_reporting = true;
603603+ mini_scheduler_workers = 5;
604604+ compress_anon_cache = false;
605605+ anon_cache_store_threshold = 2;
606606+ allowed_theme_repos = null;
607607+ enable_email_sync_demon = false;
608608+ max_digests_enqueued_per_30_mins_per_site = 10000;
609609+ };
610610+611611+ services.redis.enable = lib.mkDefault (cfg.redis.host == "localhost");
612612+613613+ services.postgresql = lib.mkIf databaseActuallyCreateLocally {
614614+ enable = true;
615615+ ensureUsers = [{ name = "discourse"; }];
616616+ };
617617+618618+ # The postgresql module doesn't currently support concepts like
619619+ # objects owners and extensions; for now we tack on what's needed
620620+ # here.
621621+ systemd.services.discourse-postgresql =
622622+ let
623623+ pgsql = config.services.postgresql;
624624+ in
625625+ lib.mkIf databaseActuallyCreateLocally {
626626+ after = [ "postgresql.service" ];
627627+ bindsTo = [ "postgresql.service" ];
628628+ wantedBy = [ "discourse.service" ];
629629+ partOf = [ "discourse.service" ];
630630+ path = [
631631+ pgsql.package
632632+ ];
633633+ script = ''
634634+ set -o errexit -o pipefail -o nounset -o errtrace
635635+ shopt -s inherit_errexit
636636+637637+ psql -tAc "SELECT 1 FROM pg_database WHERE datname = 'discourse'" | grep -q 1 || psql -tAc 'CREATE DATABASE "discourse" OWNER "discourse"'
638638+ psql '${cfg.database.name}' -tAc "CREATE EXTENSION IF NOT EXISTS pg_trgm"
639639+ psql '${cfg.database.name}' -tAc "CREATE EXTENSION IF NOT EXISTS hstore"
640640+ '';
641641+642642+ serviceConfig = {
643643+ User = pgsql.superUser;
644644+ Type = "oneshot";
645645+ RemainAfterExit = true;
646646+ };
647647+ };
648648+649649+ systemd.services.discourse = {
650650+ wantedBy = [ "multi-user.target" ];
651651+ after = [
652652+ "redis.service"
653653+ "postgresql.service"
654654+ "discourse-postgresql.service"
655655+ ];
656656+ bindsTo = [
657657+ "redis.service"
658658+ ] ++ lib.optionals (cfg.database.host == null) [
659659+ "postgresql.service"
660660+ "discourse-postgresql.service"
661661+ ];
662662+ path = cfg.package.runtimeDeps ++ [
663663+ postgresqlPackage
664664+ pkgs.replace
665665+ cfg.package.rake
666666+ ];
667667+ environment = cfg.package.runtimeEnv // {
668668+ UNICORN_TIMEOUT = builtins.toString cfg.unicornTimeout;
669669+ UNICORN_SIDEKIQS = builtins.toString cfg.sidekiqProcesses;
670670+ };
671671+672672+ preStart =
673673+ let
674674+ discourseKeyValue = lib.generators.toKeyValue {
675675+ mkKeyValue = lib.flip lib.generators.mkKeyValueDefault " = " {
676676+ mkValueString = v: with builtins;
677677+ if isInt v then toString v
678678+ else if isString v then ''"${v}"''
679679+ else if true == v then "true"
680680+ else if false == v then "false"
681681+ else if null == v then ""
682682+ else if isFloat v then lib.strings.floatToString v
683683+ else throw "unsupported type ${typeOf v}: ${(lib.generators.toPretty {}) v}";
684684+ };
685685+ };
686686+687687+ discourseConf = pkgs.writeText "discourse.conf" (discourseKeyValue cfg.backendSettings);
688688+689689+ mkSecretReplacement = file:
690690+ lib.optionalString (file != null) ''
691691+ (
692692+ password=$(<'${file}')
693693+ replace-literal -fe '${file}' "$password" /run/discourse/config/discourse.conf
694694+ )
695695+ '';
696696+ in ''
697697+ set -o errexit -o pipefail -o nounset -o errtrace
698698+ shopt -s inherit_errexit
699699+700700+ umask u=rwx,g=rx,o=
701701+702702+ cp -r ${cfg.package}/share/discourse/config.dist/* /run/discourse/config/
703703+ cp -r ${cfg.package}/share/discourse/public.dist/* /run/discourse/public/
704704+ cp -r ${cfg.package}/share/discourse/plugins.dist/* /run/discourse/plugins/
705705+ ${lib.concatMapStrings (p: "ln -sf ${p} /run/discourse/plugins/") cfg.plugins}
706706+ ln -sf /var/lib/discourse/uploads /run/discourse/public/uploads
707707+ ln -sf /var/lib/discourse/backups /run/discourse/public/backups
708708+709709+ (
710710+ umask u=rwx,g=,o=
711711+712712+ ${utils.genJqSecretsReplacementSnippet
713713+ cfg.siteSettings
714714+ "/run/discourse/config/nixos_site_settings.json"
715715+ }
716716+ install -T -m 0400 -o discourse ${discourseConf} /run/discourse/config/discourse.conf
717717+ ${mkSecretReplacement cfg.database.passwordFile}
718718+ ${mkSecretReplacement cfg.mail.outgoing.passwordFile}
719719+ ${mkSecretReplacement cfg.redis.passwordFile}
720720+ ${mkSecretReplacement cfg.secretKeyBaseFile}
721721+ )
722722+723723+ discourse-rake db:migrate >>/var/log/discourse/db_migration.log
724724+ chmod -R u+w /run/discourse/tmp/
725725+726726+ export ADMIN_EMAIL="${cfg.admin.email}"
727727+ export ADMIN_NAME="${cfg.admin.fullName}"
728728+ export ADMIN_USERNAME="${cfg.admin.username}"
729729+ export ADMIN_PASSWORD="$(<${cfg.admin.passwordFile})"
730730+ discourse-rake admin:create_noninteractively
731731+732732+ discourse-rake themes:update
733733+ discourse-rake uploads:regenerate_missing_optimized
734734+ '';
735735+736736+ serviceConfig = {
737737+ Type = "simple";
738738+ User = "discourse";
739739+ Group = "discourse";
740740+ RuntimeDirectory = map (p: "discourse/" + p) [
741741+ "config"
742742+ "home"
743743+ "tmp"
744744+ "assets/javascripts/plugins"
745745+ "public"
746746+ "plugins"
747747+ "sockets"
748748+ ];
749749+ RuntimeDirectoryMode = 0750;
750750+ StateDirectory = map (p: "discourse/" + p) [
751751+ "uploads"
752752+ "backups"
753753+ ];
754754+ StateDirectoryMode = 0750;
755755+ LogsDirectory = "discourse";
756756+ TimeoutSec = "infinity";
757757+ Restart = "on-failure";
758758+ WorkingDirectory = "${cfg.package}/share/discourse";
759759+760760+ RemoveIPC = true;
761761+ PrivateTmp = true;
762762+ NoNewPrivileges = true;
763763+ RestrictSUIDSGID = true;
764764+ ProtectSystem = "strict";
765765+ ProtectHome = "read-only";
766766+767767+ ExecStart = "${cfg.package.rubyEnv}/bin/bundle exec config/unicorn_launcher -E production -c config/unicorn.conf.rb";
768768+ };
769769+ };
770770+771771+ services.nginx = lib.mkIf cfg.nginx.enable {
772772+ enable = true;
773773+ additionalModules = [ pkgs.nginxModules.brotli ];
774774+775775+ recommendedTlsSettings = true;
776776+ recommendedOptimisation = true;
777777+ recommendedGzipSettings = true;
778778+ recommendedProxySettings = true;
779779+780780+ upstreams.discourse.servers."unix:/run/discourse/sockets/unicorn.sock" = {};
781781+782782+ appendHttpConfig = ''
783783+ # inactive means we keep stuff around for 1440m minutes regardless of last access (1 week)
784784+ # levels means it is a 2 deep heirarchy cause we can have lots of files
785785+ # max_size limits the size of the cache
786786+ proxy_cache_path /var/cache/nginx inactive=1440m levels=1:2 keys_zone=discourse:10m max_size=600m;
787787+788788+ # see: https://meta.discourse.org/t/x/74060
789789+ proxy_buffer_size 8k;
790790+ '';
791791+792792+ virtualHosts.${cfg.hostname} = {
793793+ inherit (cfg) sslCertificate sslCertificateKey enableACME;
794794+ forceSSL = lib.mkDefault tlsEnabled;
795795+796796+ root = "/run/discourse/public";
797797+798798+ locations =
799799+ let
800800+ proxy = { extraConfig ? "" }: {
801801+ proxyPass = "http://discourse";
802802+ extraConfig = extraConfig + ''
803803+ proxy_set_header X-Request-Start "t=''${msec}";
804804+ '';
805805+ };
806806+ cache = time: ''
807807+ expires ${time};
808808+ add_header Cache-Control public,immutable;
809809+ '';
810810+ cache_1y = cache "1y";
811811+ cache_1d = cache "1d";
812812+ in
813813+ {
814814+ "/".tryFiles = "$uri @discourse";
815815+ "@discourse" = proxy {};
816816+ "^~ /backups/".extraConfig = ''
817817+ internal;
818818+ '';
819819+ "/favicon.ico" = {
820820+ return = "204";
821821+ extraConfig = ''
822822+ access_log off;
823823+ log_not_found off;
824824+ '';
825825+ };
826826+ "~ ^/uploads/short-url/" = proxy {};
827827+ "~ ^/secure-media-uploads/" = proxy {};
828828+ "~* (fonts|assets|plugins|uploads)/.*\.(eot|ttf|woff|woff2|ico|otf)$".extraConfig = cache_1y + ''
829829+ add_header Access-Control-Allow-Origin *;
830830+ '';
831831+ "/srv/status" = proxy {
832832+ extraConfig = ''
833833+ access_log off;
834834+ log_not_found off;
835835+ '';
836836+ };
837837+ "~ ^/javascripts/".extraConfig = cache_1d;
838838+ "~ ^/assets/(?<asset_path>.+)$".extraConfig = cache_1y + ''
839839+ # asset pipeline enables this
840840+ brotli_static on;
841841+ gzip_static on;
842842+ '';
843843+ "~ ^/plugins/".extraConfig = cache_1y;
844844+ "~ /images/emoji/".extraConfig = cache_1y;
845845+ "~ ^/uploads/" = proxy {
846846+ extraConfig = cache_1y + ''
847847+ proxy_set_header X-Sendfile-Type X-Accel-Redirect;
848848+ proxy_set_header X-Accel-Mapping /run/discourse/public/=/downloads/;
849849+850850+ # custom CSS
851851+ location ~ /stylesheet-cache/ {
852852+ try_files $uri =404;
853853+ }
854854+ # this allows us to bypass rails
855855+ location ~* \.(gif|png|jpg|jpeg|bmp|tif|tiff|ico|webp)$ {
856856+ try_files $uri =404;
857857+ }
858858+ # SVG needs an extra header attached
859859+ location ~* \.(svg)$ {
860860+ }
861861+ # thumbnails & optimized images
862862+ location ~ /_?optimized/ {
863863+ try_files $uri =404;
864864+ }
865865+ '';
866866+ };
867867+ "~ ^/admin/backups/" = proxy {
868868+ extraConfig = ''
869869+ proxy_set_header X-Sendfile-Type X-Accel-Redirect;
870870+ proxy_set_header X-Accel-Mapping /run/discourse/public/=/downloads/;
871871+ '';
872872+ };
873873+ "~ ^/(svg-sprite/|letter_avatar/|letter_avatar_proxy/|user_avatar|highlight-js|stylesheets|theme-javascripts|favicon/proxied|service-worker)" = proxy {
874874+ extraConfig = ''
875875+ # if Set-Cookie is in the response nothing gets cached
876876+ # this is double bad cause we are not passing last modified in
877877+ proxy_ignore_headers "Set-Cookie";
878878+ proxy_hide_header "Set-Cookie";
879879+ proxy_hide_header "X-Discourse-Username";
880880+ proxy_hide_header "X-Runtime";
881881+882882+ # note x-accel-redirect can not be used with proxy_cache
883883+ proxy_cache discourse;
884884+ proxy_cache_key "$scheme,$host,$request_uri";
885885+ proxy_cache_valid 200 301 302 7d;
886886+ proxy_cache_valid any 1m;
887887+ '';
888888+ };
889889+ "/message-bus/" = proxy {
890890+ extraConfig = ''
891891+ proxy_http_version 1.1;
892892+ proxy_buffering off;
893893+ '';
894894+ };
895895+ "/downloads/".extraConfig = ''
896896+ internal;
897897+ alias /run/discourse/public/;
898898+ '';
899899+ };
900900+ };
901901+ };
902902+903903+ systemd.services.discourse-mail-receiver-setup = lib.mkIf cfg.mail.incoming.enable (
904904+ let
905905+ mail-receiver-environment = {
906906+ MAIL_DOMAIN = cfg.hostname;
907907+ DISCOURSE_BASE_URL = "http${lib.optionalString tlsEnabled "s"}://${cfg.hostname}";
908908+ DISCOURSE_API_KEY = "@api-key@";
909909+ DISCOURSE_API_USERNAME = "system";
910910+ };
911911+ mail-receiver-json = json.generate "mail-receiver.json" mail-receiver-environment;
912912+ in
913913+ {
914914+ before = [ "postfix.service" ];
915915+ after = [ "discourse.service" ];
916916+ wantedBy = [ "discourse.service" ];
917917+ partOf = [ "discourse.service" ];
918918+ path = [
919919+ cfg.package.rake
920920+ pkgs.jq
921921+ ];
922922+ preStart = lib.optionalString (cfg.mail.incoming.apiKeyFile == null) ''
923923+ set -o errexit -o pipefail -o nounset -o errtrace
924924+ shopt -s inherit_errexit
925925+926926+ if [[ ! -e /var/lib/discourse-mail-receiver/api_key ]]; then
927927+ discourse-rake api_key:create_master[email-receiver] >/var/lib/discourse-mail-receiver/api_key
928928+ fi
929929+ '';
930930+ script =
931931+ let
932932+ apiKeyPath =
933933+ if cfg.mail.incoming.apiKeyFile == null then
934934+ "/var/lib/discourse-mail-receiver/api_key"
935935+ else
936936+ cfg.mail.incoming.apiKeyFile;
937937+ in ''
938938+ set -o errexit -o pipefail -o nounset -o errtrace
939939+ shopt -s inherit_errexit
940940+941941+ export api_key=$(<'${apiKeyPath}')
942942+943943+ jq <${mail-receiver-json} \
944944+ '.DISCOURSE_API_KEY = $ENV.api_key' \
945945+ >'/run/discourse-mail-receiver/mail-receiver-environment.json'
946946+ '';
947947+948948+ serviceConfig = {
949949+ Type = "oneshot";
950950+ RemainAfterExit = true;
951951+ RuntimeDirectory = "discourse-mail-receiver";
952952+ RuntimeDirectoryMode = "0700";
953953+ StateDirectory = "discourse-mail-receiver";
954954+ User = "discourse";
955955+ Group = "discourse";
956956+ };
957957+ });
958958+959959+ services.discourse.siteSettings = {
960960+ required = {
961961+ notification_email = cfg.mail.notificationEmailAddress;
962962+ contact_email = cfg.mail.contactEmailAddress;
963963+ };
964964+ email = {
965965+ manual_polling_enabled = cfg.mail.incoming.enable;
966966+ reply_by_email_enabled = cfg.mail.incoming.enable;
967967+ reply_by_email_address = cfg.mail.incoming.replyEmailAddress;
968968+ };
969969+ };
970970+971971+ services.postfix = lib.mkIf cfg.mail.incoming.enable {
972972+ enable = true;
973973+ sslCert = if cfg.sslCertificate != null then cfg.sslCertificate else "";
974974+ sslKey = if cfg.sslCertificateKey != null then cfg.sslCertificateKey else "";
975975+976976+ origin = cfg.hostname;
977977+ relayDomains = [ cfg.hostname ];
978978+ config = {
979979+ smtpd_recipient_restrictions = "check_policy_service unix:private/discourse-policy";
980980+ append_dot_mydomain = lib.mkDefault false;
981981+ compatibility_level = "2";
982982+ smtputf8_enable = false;
983983+ smtpd_banner = lib.mkDefault "ESMTP server";
984984+ myhostname = lib.mkDefault cfg.hostname;
985985+ mydestination = lib.mkDefault "localhost";
986986+ };
987987+ transport = ''
988988+ ${cfg.hostname} discourse-mail-receiver:
989989+ '';
990990+ masterConfig = {
991991+ "discourse-mail-receiver" = {
992992+ type = "unix";
993993+ privileged = true;
994994+ chroot = false;
995995+ command = "pipe";
996996+ args = [
997997+ "user=discourse"
998998+ "argv=${cfg.mail.incoming.mailReceiverPackage}/bin/receive-mail"
999999+ "\${recipient}"
10001000+ ];
10011001+ };
10021002+ "discourse-policy" = {
10031003+ type = "unix";
10041004+ privileged = true;
10051005+ chroot = false;
10061006+ command = "spawn";
10071007+ args = [
10081008+ "user=discourse"
10091009+ "argv=${cfg.mail.incoming.mailReceiverPackage}/bin/discourse-smtp-fast-rejection"
10101010+ ];
10111011+ };
10121012+ };
10131013+ };
10141014+10151015+ users.users = {
10161016+ discourse = {
10171017+ group = "discourse";
10181018+ isSystemUser = true;
10191019+ };
10201020+ } // (lib.optionalAttrs cfg.nginx.enable {
10211021+ ${config.services.nginx.user}.extraGroups = [ "discourse" ];
10221022+ });
10231023+10241024+ users.groups = {
10251025+ discourse = {};
10261026+ };
10271027+10281028+ environment.systemPackages = [
10291029+ cfg.package.rake
10301030+ ];
10311031+ };
10321032+10331033+ meta.doc = ./discourse.xml;
10341034+ meta.maintainers = [ lib.maintainers.talyz ];
10351035+}
+323
nixos/modules/services/web-apps/discourse.xml
···11+<chapter xmlns="http://docbook.org/ns/docbook"
22+ xmlns:xlink="http://www.w3.org/1999/xlink"
33+ xmlns:xi="http://www.w3.org/2001/XInclude"
44+ version="5.0"
55+ xml:id="module-services-discourse">
66+ <title>Discourse</title>
77+ <para>
88+ <link xlink:href="https://www.discourse.org/">Discourse</link> is a
99+ modern and open source discussion platform.
1010+ </para>
1111+1212+ <section xml:id="module-services-discourse-basic-usage">
1313+ <title>Basic usage</title>
1414+ <para>
1515+ A minimal configuration using Let's Encrypt for TLS certificates looks like this:
1616+<programlisting>
1717+services.discourse = {
1818+ <link linkend="opt-services.discourse.enable">enable</link> = true;
1919+ <link linkend="opt-services.discourse.hostname">hostname</link> = "discourse.example.com";
2020+ admin = {
2121+ <link linkend="opt-services.discourse.admin.email">email</link> = "admin@example.com";
2222+ <link linkend="opt-services.discourse.admin.username">username</link> = "admin";
2323+ <link linkend="opt-services.discourse.admin.fullName">fullName</link> = "Administrator";
2424+ <link linkend="opt-services.discourse.admin.passwordFile">passwordFile</link> = "/path/to/password_file";
2525+ };
2626+ <link linkend="opt-services.discourse.secretKeyBaseFile">secretKeyBaseFile</link> = "/path/to/secret_key_base_file";
2727+};
2828+<link linkend="opt-security.acme.email">security.acme.email</link> = "me@example.com";
2929+<link linkend="opt-security.acme.acceptTerms">security.acme.acceptTerms</link> = true;
3030+</programlisting>
3131+ </para>
3232+3333+ <para>
3434+ Provided a proper DNS setup, you'll be able to connect to the
3535+ instance at <literal>discourse.example.com</literal> and log in
3636+ using the credentials provided in
3737+ <literal>services.discourse.admin</literal>.
3838+ </para>
3939+ </section>
4040+4141+ <section xml:id="module-services-discourse-tls">
4242+ <title>Using a regular TLS certificate</title>
4343+ <para>
4444+ To set up TLS using a regular certificate and key on file, use
4545+ the <xref linkend="opt-services.discourse.sslCertificate" />
4646+ and <xref linkend="opt-services.discourse.sslCertificateKey" />
4747+ options:
4848+4949+<programlisting>
5050+services.discourse = {
5151+ <link linkend="opt-services.discourse.enable">enable</link> = true;
5252+ <link linkend="opt-services.discourse.hostname">hostname</link> = "discourse.example.com";
5353+ <link linkend="opt-services.discourse.sslCertificate">sslCertificate</link> = "/path/to/ssl_certificate";
5454+ <link linkend="opt-services.discourse.sslCertificateKey">sslCertificateKey</link> = "/path/to/ssl_certificate_key";
5555+ admin = {
5656+ <link linkend="opt-services.discourse.admin.email">email</link> = "admin@example.com";
5757+ <link linkend="opt-services.discourse.admin.username">username</link> = "admin";
5858+ <link linkend="opt-services.discourse.admin.fullName">fullName</link> = "Administrator";
5959+ <link linkend="opt-services.discourse.admin.passwordFile">passwordFile</link> = "/path/to/password_file";
6060+ };
6161+ <link linkend="opt-services.discourse.secretKeyBaseFile">secretKeyBaseFile</link> = "/path/to/secret_key_base_file";
6262+};
6363+</programlisting>
6464+6565+ </para>
6666+ </section>
6767+6868+ <section xml:id="module-services-discourse-database">
6969+ <title>Database access</title>
7070+ <para>
7171+ <productname>Discourse</productname> uses
7272+ <productname>PostgreSQL</productname> to store most of its
7373+ data. A database will automatically be enabled and a database
7474+ and role created unless <xref
7575+ linkend="opt-services.discourse.database.host" /> is changed from
7676+ its default of <literal>null</literal> or <xref
7777+ linkend="opt-services.discourse.database.createLocally" /> is set
7878+ to <literal>false</literal>.
7979+ </para>
8080+8181+ <para>
8282+ External database access can also be configured by setting
8383+ <xref linkend="opt-services.discourse.database.host" />, <xref
8484+ linkend="opt-services.discourse.database.username" /> and <xref
8585+ linkend="opt-services.discourse.database.passwordFile" /> as
8686+ appropriate. Note that you need to manually create a database
8787+ called <literal>discourse</literal> (or the name you chose in
8888+ <xref linkend="opt-services.discourse.database.name" />) and
8989+ allow the configured database user full access to it.
9090+ </para>
9191+ </section>
9292+9393+ <section xml:id="module-services-discourse-mail">
9494+ <title>Email</title>
9595+ <para>
9696+ In addition to the basic setup, you'll want to configure an SMTP
9797+ server <productname>Discourse</productname> can use to send user
9898+ registration and password reset emails, among others. You can
9999+ also optionally let <productname>Discourse</productname> receive
100100+ email, which enables people to reply to threads and conversations
101101+ via email.
102102+ </para>
103103+104104+ <para>
105105+ A basic setup which assumes you want to use your configured <link
106106+ linkend="opt-services.discourse.hostname">hostname</link> as
107107+ email domain can be done like this:
108108+109109+<programlisting>
110110+services.discourse = {
111111+ <link linkend="opt-services.discourse.enable">enable</link> = true;
112112+ <link linkend="opt-services.discourse.hostname">hostname</link> = "discourse.example.com";
113113+ <link linkend="opt-services.discourse.sslCertificate">sslCertificate</link> = "/path/to/ssl_certificate";
114114+ <link linkend="opt-services.discourse.sslCertificateKey">sslCertificateKey</link> = "/path/to/ssl_certificate_key";
115115+ admin = {
116116+ <link linkend="opt-services.discourse.admin.email">email</link> = "admin@example.com";
117117+ <link linkend="opt-services.discourse.admin.username">username</link> = "admin";
118118+ <link linkend="opt-services.discourse.admin.fullName">fullName</link> = "Administrator";
119119+ <link linkend="opt-services.discourse.admin.passwordFile">passwordFile</link> = "/path/to/password_file";
120120+ };
121121+ mail.outgoing = {
122122+ <link linkend="opt-services.discourse.mail.outgoing.serverAddress">serverAddress</link> = "smtp.emailprovider.com";
123123+ <link linkend="opt-services.discourse.mail.outgoing.port">port</link> = 587;
124124+ <link linkend="opt-services.discourse.mail.outgoing.username">username</link> = "user@emailprovider.com";
125125+ <link linkend="opt-services.discourse.mail.outgoing.passwordFile">passwordFile</link> = "/path/to/smtp_password_file";
126126+ };
127127+ <link linkend="opt-services.discourse.mail.incoming.enable">mail.incoming.enable</link> = true;
128128+ <link linkend="opt-services.discourse.secretKeyBaseFile">secretKeyBaseFile</link> = "/path/to/secret_key_base_file";
129129+};
130130+</programlisting>
131131+132132+ This assumes you have set up an MX record for the address you've
133133+ set in <link linkend="opt-services.discourse.hostname">hostname</link> and
134134+ requires proper SPF, DKIM and DMARC configuration to be done for
135135+ the domain you're sending from, in order for email to be reliably delivered.
136136+ </para>
137137+138138+ <para>
139139+ If you want to use a different domain for your outgoing email
140140+ (for example <literal>example.com</literal> instead of
141141+ <literal>discourse.example.com</literal>) you should set
142142+ <xref linkend="opt-services.discourse.mail.notificationEmailAddress" /> and
143143+ <xref linkend="opt-services.discourse.mail.contactEmailAddress" /> manually.
144144+ </para>
145145+146146+ <note>
147147+ <para>
148148+ Setup of TLS for incoming email is currently only configured
149149+ automatically when a regular TLS certificate is used, i.e. when
150150+ <xref linkend="opt-services.discourse.sslCertificate" /> and
151151+ <xref linkend="opt-services.discourse.sslCertificateKey" /> are
152152+ set.
153153+ </para>
154154+ </note>
155155+156156+ </section>
157157+158158+ <section xml:id="module-services-discourse-settings">
159159+ <title>Additional settings</title>
160160+ <para>
161161+ Additional site settings and backend settings, for which no
162162+ explicit <productname>NixOS</productname> options are provided,
163163+ can be set in <xref linkend="opt-services.discourse.siteSettings" /> and
164164+ <xref linkend="opt-services.discourse.backendSettings" /> respectively.
165165+ </para>
166166+167167+ <section xml:id="module-services-discourse-site-settings">
168168+ <title>Site settings</title>
169169+ <para>
170170+ <quote>Site settings</quote> are the settings that can be
171171+ changed through the <productname>Discourse</productname>
172172+ UI. Their <emphasis>default</emphasis> values can be set using
173173+ <xref linkend="opt-services.discourse.siteSettings" />.
174174+ </para>
175175+176176+ <para>
177177+ Settings are expressed as a Nix attribute set which matches the
178178+ structure of the configuration in
179179+ <link xlink:href="https://github.com/discourse/discourse/blob/master/config/site_settings.yml">config/site_settings.yml</link>.
180180+ To find a setting's path, you only need to care about the first
181181+ two levels; i.e. its category (e.g. <literal>login</literal>)
182182+ and name (e.g. <literal>invite_only</literal>).
183183+ </para>
184184+185185+ <para>
186186+ Settings containing secret data should be set to an attribute
187187+ set containing the attribute <literal>_secret</literal> - a
188188+ string pointing to a file containing the value the option
189189+ should be set to. See the example.
190190+ </para>
191191+ </section>
192192+193193+ <section xml:id="module-services-discourse-backend-settings">
194194+ <title>Backend settings</title>
195195+ <para>
196196+ Settings are expressed as a Nix attribute set which matches the
197197+ structure of the configuration in
198198+ <link xlink:href="https://github.com/discourse/discourse/blob/stable/config/discourse_defaults.conf">config/discourse.conf</link>.
199199+ Empty parameters can be defined by setting them to
200200+ <literal>null</literal>.
201201+ </para>
202202+ </section>
203203+204204+ <section xml:id="module-services-discourse-settings-example">
205205+ <title>Example</title>
206206+ <para>
207207+ The following example sets the title and description of the
208208+ <productname>Discourse</productname> instance and enables
209209+ <productname>GitHub</productname> login in the site settings,
210210+ and changes a few request limits in the backend settings:
211211+<programlisting>
212212+services.discourse = {
213213+ <link linkend="opt-services.discourse.enable">enable</link> = true;
214214+ <link linkend="opt-services.discourse.hostname">hostname</link> = "discourse.example.com";
215215+ <link linkend="opt-services.discourse.sslCertificate">sslCertificate</link> = "/path/to/ssl_certificate";
216216+ <link linkend="opt-services.discourse.sslCertificateKey">sslCertificateKey</link> = "/path/to/ssl_certificate_key";
217217+ admin = {
218218+ <link linkend="opt-services.discourse.admin.email">email</link> = "admin@example.com";
219219+ <link linkend="opt-services.discourse.admin.username">username</link> = "admin";
220220+ <link linkend="opt-services.discourse.admin.fullName">fullName</link> = "Administrator";
221221+ <link linkend="opt-services.discourse.admin.passwordFile">passwordFile</link> = "/path/to/password_file";
222222+ };
223223+ mail.outgoing = {
224224+ <link linkend="opt-services.discourse.mail.outgoing.serverAddress">serverAddress</link> = "smtp.emailprovider.com";
225225+ <link linkend="opt-services.discourse.mail.outgoing.port">port</link> = 587;
226226+ <link linkend="opt-services.discourse.mail.outgoing.username">username</link> = "user@emailprovider.com";
227227+ <link linkend="opt-services.discourse.mail.outgoing.passwordFile">passwordFile</link> = "/path/to/smtp_password_file";
228228+ };
229229+ <link linkend="opt-services.discourse.mail.incoming.enable">mail.incoming.enable</link> = true;
230230+ <link linkend="opt-services.discourse.siteSettings">siteSettings</link> = {
231231+ required = {
232232+ title = "My Cats";
233233+ site_description = "Discuss My Cats (and be nice plz)";
234234+ };
235235+ login = {
236236+ enable_github_logins = true;
237237+ github_client_id = "a2f6dfe838cb3206ce20";
238238+ github_client_secret._secret = /run/keys/discourse_github_client_secret;
239239+ };
240240+ };
241241+ <link linkend="opt-services.discourse.backendSettings">backendSettings</link> = {
242242+ max_reqs_per_ip_per_minute = 300;
243243+ max_reqs_per_ip_per_10_seconds = 60;
244244+ max_asset_reqs_per_ip_per_10_seconds = 250;
245245+ max_reqs_per_ip_mode = "warn+block";
246246+ };
247247+ <link linkend="opt-services.discourse.secretKeyBaseFile">secretKeyBaseFile</link> = "/path/to/secret_key_base_file";
248248+};
249249+</programlisting>
250250+ </para>
251251+ <para>
252252+ In the resulting site settings file, the
253253+ <literal>login.github_client_secret</literal> key will be set
254254+ to the contents of the
255255+ <filename>/run/keys/discourse_github_client_secret</filename>
256256+ file.
257257+ </para>
258258+ </section>
259259+ </section>
260260+ <section xml:id="module-services-discourse-plugins">
261261+ <title>Plugins</title>
262262+ <para>
263263+ You can install <productname>Discourse</productname> plugins
264264+ using the <xref linkend="opt-services.discourse.plugins" />
265265+ option. As long as a plugin supports the standard install
266266+ method, packaging it should only require fetching its source
267267+ with an appropriate fetcher.
268268+ </para>
269269+270270+ <para>
271271+ Some plugins provide <link
272272+ linkend="module-services-discourse-site-settings">site
273273+ settings</link>. Their defaults can be configured using <xref
274274+ linkend="opt-services.discourse.siteSettings" />, just like
275275+ regular site settings. To find the names of these settings, look
276276+ in the <literal>config/settings.yml</literal> file of the plugin
277277+ repo.
278278+ </para>
279279+280280+ <para>
281281+ For example, to add the <link
282282+ xlink:href="https://github.com/discourse/discourse-spoiler-alert">discourse-spoiler-alert</link>
283283+ plugin and disable it by default:
284284+285285+<programlisting>
286286+services.discourse = {
287287+ <link linkend="opt-services.discourse.enable">enable</link> = true;
288288+ <link linkend="opt-services.discourse.hostname">hostname</link> = "discourse.example.com";
289289+ <link linkend="opt-services.discourse.sslCertificate">sslCertificate</link> = "/path/to/ssl_certificate";
290290+ <link linkend="opt-services.discourse.sslCertificateKey">sslCertificateKey</link> = "/path/to/ssl_certificate_key";
291291+ admin = {
292292+ <link linkend="opt-services.discourse.admin.email">email</link> = "admin@example.com";
293293+ <link linkend="opt-services.discourse.admin.username">username</link> = "admin";
294294+ <link linkend="opt-services.discourse.admin.fullName">fullName</link> = "Administrator";
295295+ <link linkend="opt-services.discourse.admin.passwordFile">passwordFile</link> = "/path/to/password_file";
296296+ };
297297+ mail.outgoing = {
298298+ <link linkend="opt-services.discourse.mail.outgoing.serverAddress">serverAddress</link> = "smtp.emailprovider.com";
299299+ <link linkend="opt-services.discourse.mail.outgoing.port">port</link> = 587;
300300+ <link linkend="opt-services.discourse.mail.outgoing.username">username</link> = "user@emailprovider.com";
301301+ <link linkend="opt-services.discourse.mail.outgoing.passwordFile">passwordFile</link> = "/path/to/smtp_password_file";
302302+ };
303303+ <link linkend="opt-services.discourse.mail.incoming.enable">mail.incoming.enable</link> = true;
304304+ <link linkend="opt-services.discourse.mail.incoming.enable">plugins</link> = [
305305+ (pkgs.fetchFromGitHub {
306306+ owner = "discourse";
307307+ repo = "discourse-spoiler-alert";
308308+ rev = "e200cfa571d252cab63f3d30d619b370986e4cee";
309309+ sha256 = "0ya69ix5g77wz4c9x9gmng6l25ghb5xxlx3icr6jam16q14dzc33";
310310+ })
311311+ ];
312312+ <link linkend="opt-services.discourse.siteSettings">siteSettings</link> = {
313313+ plugins = {
314314+ spoiler_enabled = false;
315315+ };
316316+ };
317317+ <link linkend="opt-services.discourse.secretKeyBaseFile">secretKeyBaseFile</link> = "/path/to/secret_key_base_file";
318318+};
319319+</programlisting>
320320+321321+ </para>
322322+ </section>
323323+</chapter>
···5656 lgpl2Plus # libfuse
5757 ];
5858 };
5959+6060+ passthru.warning = ''
6161+ macFUSE is required for this package to work on macOS. To install macFUSE,
6262+ use the installer from the <link xlink:href="https://osxfuse.github.io/">
6363+ project website</link>.
6464+ '';
5965}
···44 # the frontend version corresponding to a specific home-assistant version can be found here
55 # https://github.com/home-assistant/home-assistant/blob/master/homeassistant/components/frontend/manifest.json
66 pname = "home-assistant-frontend";
77- version = "20210302.6";
77+ version = "20210407.1";
8899 src = fetchPypi {
1010 inherit pname version;
1111- sha256 = "sha256-h3jCqfAPg+z6vsdLm5Pdr+7PCEWW58GCG9viIz3Mi64=";
1111+ sha256 = "sha256-7kgL6Ixlc1OZ+3sUAuvJd7vgY6FBgPFEKi6xhq7fiBc=";
1212 };
13131414 # there is nothing to strip in this package
···11+# frozen_string_literal: true
22+33+source 'https://rubygems.org'
44+# if there is a super emergency and rubygems is playing up, try
55+#source 'http://production.cf.rubygems.org'
66+77+gem 'bootsnap', require: false, platform: :mri
88+99+def rails_master?
1010+ ENV["RAILS_MASTER"] == '1'
1111+end
1212+1313+if rails_master?
1414+ gem 'arel', git: 'https://github.com/rails/arel.git'
1515+ gem 'rails', git: 'https://github.com/rails/rails.git'
1616+else
1717+ # NOTE: Until rubygems gives us optional dependencies we are stuck with this needing to be explicit
1818+ # this allows us to include the bits of rails we use without pieces we do not.
1919+ #
2020+ # To issue a rails update bump the version number here
2121+ gem 'actionmailer', '6.0.3.3'
2222+ gem 'actionpack', '6.0.3.3'
2323+ gem 'actionview', '6.0.3.3'
2424+ gem 'activemodel', '6.0.3.3'
2525+ gem 'activerecord', '6.0.3.3'
2626+ gem 'activesupport', '6.0.3.3'
2727+ gem 'railties', '6.0.3.3'
2828+ gem 'sprockets-rails'
2929+end
3030+3131+gem 'json'
3232+3333+# TODO: At the moment Discourse does not work with Sprockets 4, we would need to correct internals
3434+# This is a desired upgrade we should get to.
3535+gem 'sprockets', '3.7.2'
3636+3737+# this will eventually be added to rails,
3838+# allows us to precompile all our templates in the unicorn master
3939+gem 'actionview_precompiler', require: false
4040+4141+gem 'seed-fu'
4242+4343+gem 'mail', require: false
4444+gem 'mini_mime'
4545+gem 'mini_suffix'
4646+4747+gem 'redis'
4848+4949+# This is explicitly used by Sidekiq and is an optional dependency.
5050+# We tell Sidekiq to use the namespace "sidekiq" which triggers this
5151+# gem to be used. There is no explicit dependency in sidekiq cause
5252+# redis namespace support is optional
5353+# We already namespace stuff in DiscourseRedis, so we should consider
5454+# just using a single implementation in core vs having 2 namespace implementations
5555+gem 'redis-namespace'
5656+5757+# NOTE: AM serializer gets a lot slower with recent updates
5858+# we used an old branch which is the fastest one out there
5959+# are long term goal here is to fork this gem so we have a
6060+# better maintained living fork
6161+gem 'active_model_serializers', '~> 0.8.3'
6262+6363+gem 'onebox'
6464+6565+gem 'http_accept_language', require: false
6666+6767+# Ember related gems need to be pinned cause they control client side
6868+# behavior, we will push these versions up when upgrading ember
6969+gem 'discourse-ember-rails', '0.18.6', require: 'ember-rails'
7070+gem 'discourse-ember-source', '~> 3.12.2'
7171+gem 'ember-handlebars-template', '0.8.0'
7272+gem 'discourse-fonts'
7373+7474+gem 'barber'
7575+7676+gem 'message_bus'
7777+7878+gem 'rails_multisite'
7979+8080+gem 'fast_xs', platform: :ruby
8181+8282+gem 'xorcist'
8383+8484+gem 'fastimage'
8585+8686+gem 'aws-sdk-s3', require: false
8787+gem 'aws-sdk-sns', require: false
8888+gem 'excon', require: false
8989+gem 'unf', require: false
9090+9191+gem 'email_reply_trimmer'
9292+9393+# Forked until https://github.com/toy/image_optim/pull/162 is merged
9494+# https://github.com/discourse/image_optim
9595+gem 'discourse_image_optim', require: 'image_optim'
9696+gem 'multi_json'
9797+gem 'mustache'
9898+gem 'nokogiri'
9999+gem 'css_parser', require: false
100100+101101+gem 'omniauth'
102102+gem 'omniauth-facebook'
103103+gem 'omniauth-twitter'
104104+gem 'omniauth-github'
105105+106106+gem 'omniauth-oauth2', require: false
107107+108108+gem 'omniauth-google-oauth2'
109109+110110+gem 'oj'
111111+gem 'pg'
112112+gem 'mini_sql'
113113+gem 'pry-rails', require: false
114114+gem 'pry-byebug', require: false
115115+gem 'r2', require: false
116116+gem 'rake'
117117+118118+gem 'thor', require: false
119119+gem 'diffy', require: false
120120+gem 'rinku'
121121+gem 'sidekiq'
122122+gem 'mini_scheduler'
123123+124124+gem 'execjs', require: false
125125+gem 'mini_racer'
126126+127127+gem 'highline', require: false
128128+129129+gem 'rack'
130130+131131+gem 'rack-protection' # security
132132+gem 'cbor', require: false
133133+gem 'cose', require: false
134134+gem 'addressable'
135135+136136+# Gems used only for assets and not required in production environments by default.
137137+# Allow everywhere for now cause we are allowing asset debugging in production
138138+group :assets do
139139+ gem 'uglifier'
140140+ gem 'rtlit', require: false # for css rtling
141141+end
142142+143143+group :test do
144144+ gem 'webmock', require: false
145145+ gem 'fakeweb', require: false
146146+ gem 'minitest', require: false
147147+ gem 'simplecov', require: false
148148+ gem "test-prof"
149149+end
150150+151151+group :test, :development do
152152+ gem 'rspec'
153153+ gem 'mock_redis'
154154+ gem 'listen', require: false
155155+ gem 'certified', require: false
156156+ gem 'fabrication', require: false
157157+ gem 'mocha', require: false
158158+159159+ gem 'rb-fsevent', require: RUBY_PLATFORM =~ /darwin/i ? 'rb-fsevent' : false
160160+161161+ gem 'rspec-rails'
162162+163163+ gem 'shoulda-matchers', require: false
164164+ gem 'rspec-html-matchers'
165165+ gem 'byebug', require: ENV['RM_INFO'].nil?, platform: :mri
166166+ gem "rubocop-discourse", require: false
167167+ gem 'parallel_tests'
168168+169169+ gem 'rswag-specs'
170170+end
171171+172172+group :development do
173173+ gem 'ruby-prof', require: false, platform: :mri
174174+ gem 'bullet', require: !!ENV['BULLET']
175175+ gem 'better_errors', platform: :mri, require: !!ENV['BETTER_ERRORS']
176176+ gem 'binding_of_caller'
177177+ gem 'yaml-lint'
178178+ gem 'annotate'
179179+end
180180+181181+# this is an optional gem, it provides a high performance replacement
182182+# to String#blank? a method that is called quite frequently in current
183183+# ActiveRecord, this may change in the future
184184+gem 'fast_blank', platform: :ruby
185185+186186+# this provides a very efficient lru cache
187187+gem 'lru_redux'
188188+189189+gem 'htmlentities', require: false
190190+191191+# IMPORTANT: mini profiler monkey patches, so it better be required last
192192+# If you want to amend mini profiler to do the monkey patches in the railties
193193+# we are open to it. by deferring require to the initializer we can configure discourse installs without it
194194+195195+gem 'flamegraph', require: false
196196+gem 'rack-mini-profiler', require: ['enable_rails_patches']
197197+198198+gem 'unicorn', require: false, platform: :ruby
199199+gem 'puma', require: false
200200+gem 'rbtrace', require: false, platform: :mri
201201+gem 'gc_tracer', require: false, platform: :mri
202202+203203+# required for feed importing and embedding
204204+gem 'ruby-readability', require: false
205205+206206+gem 'stackprof', require: false, platform: :mri
207207+gem 'memory_profiler', require: false, platform: :mri
208208+209209+gem 'cppjieba_rb', require: false
210210+211211+gem 'lograge', require: false
212212+gem 'logstash-event', require: false
213213+gem 'logstash-logger', require: false
214214+gem 'logster'
215215+216216+# NOTE: later versions of sassc are causing a segfault, possibly dependent on processer architecture
217217+# and until resolved should be locked at 2.0.1
218218+gem 'sassc', '2.0.1', require: false
219219+gem "sassc-rails"
220220+221221+gem 'rotp', require: false
222222+223223+gem 'rqrcode'
224224+225225+gem 'rubyzip', require: false
226226+227227+gem 'sshkey', require: false
228228+229229+gem 'rchardet', require: false
230230+gem 'lz4-ruby', require: false, platform: :ruby
231231+232232+if ENV["IMPORT"] == "1"
233233+ gem 'mysql2'
234234+ gem 'redcarpet'
235235+236236+ # NOTE: in import mode the version of sqlite can matter a lot, so we stick it to a specific one
237237+ gem 'sqlite3', '~> 1.3', '>= 1.3.13'
238238+ gem 'ruby-bbcode-to-md', git: 'https://github.com/nlalonde/ruby-bbcode-to-md'
239239+ gem 'reverse_markdown'
240240+ gem 'tiny_tds'
241241+ gem 'csv'
242242+end
243243+244244+gem 'webpush', require: false
245245+gem 'colored2', require: false
246246+gem 'maxminddb'
247247+248248+gem 'rails_failover', require: false
···11+diff --git a/config/unicorn.conf.rb b/config/unicorn.conf.rb
22+index 373e235b3f..57d4d7a55b 100644
33+--- a/config/unicorn.conf.rb
44++++ b/config/unicorn.conf.rb
55+@@ -27,18 +27,10 @@ pid (ENV["UNICORN_PID_PATH"] || "#{discourse_path}/tmp/pids/unicorn.pid")
66+77+ if ENV["RAILS_ENV"] == "development" || !ENV["RAILS_ENV"]
88+ logger Logger.new($stdout)
99+- # we want a longer timeout in dev cause first request can be really slow
1010+- timeout (ENV["UNICORN_TIMEOUT"] && ENV["UNICORN_TIMEOUT"].to_i || 60)
1111+-else
1212+- # By default, the Unicorn logger will write to stderr.
1313+- # Additionally, some applications/frameworks log to stderr or stdout,
1414+- # so prevent them from going to /dev/null when daemonized here:
1515+- stderr_path "#{discourse_path}/log/unicorn.stderr.log"
1616+- stdout_path "#{discourse_path}/log/unicorn.stdout.log"
1717+- # nuke workers after 30 seconds instead of 60 seconds (the default)
1818+- timeout 30
1919+ end
2020+2121++timeout (ENV["UNICORN_TIMEOUT"] && ENV["UNICORN_TIMEOUT"].to_i || 60)
2222++
2323+ # important for Ruby 2.0
2424+ preload_app true
2525+
+164
pkgs/servers/web-apps/discourse/update.py
···11+#!/usr/bin/env nix-shell
22+#! nix-shell -i python3 -p bundix bundler nix-update python3 python3Packages.requests python3Packages.click python3Packages.click-log
33+44+import click
55+import click_log
66+import shutil
77+import tempfile
88+import re
99+import logging
1010+import subprocess
1111+import pathlib
1212+from distutils.version import LooseVersion
1313+from typing import Iterable
1414+1515+import requests
1616+1717+logger = logging.getLogger(__name__)
1818+1919+2020+class DiscourseRepo:
2121+ version_regex = re.compile(r'^v\d+\.\d+\.\d+$')
2222+ def __init__(self, owner: str = 'discourse', repo: str = 'discourse'):
2323+ self.owner = owner
2424+ self.repo = repo
2525+2626+ @property
2727+ def tags(self) -> Iterable[str]:
2828+ r = requests.get(f'https://api.github.com/repos/{self.owner}/{self.repo}/git/refs/tags').json()
2929+ tags = [x['ref'].replace('refs/tags/', '') for x in r]
3030+3131+ # filter out versions not matching version_regex
3232+ versions = list(filter(self.version_regex.match, tags))
3333+3434+ # sort, but ignore v for sorting comparisons
3535+ versions.sort(key=lambda x: LooseVersion(x.replace('v', '')), reverse=True)
3636+ return versions
3737+3838+ @staticmethod
3939+ def rev2version(tag: str) -> str:
4040+ """
4141+ normalize a tag to a version number.
4242+ This obviously isn't very smart if we don't pass something that looks like a tag
4343+ :param tag: the tag to normalize
4444+ :return: a normalized version number
4545+ """
4646+ # strip v prefix
4747+ return re.sub(r'^v', '', tag)
4848+4949+ def get_file(self, filepath, rev):
5050+ """returns file contents at a given rev :param filepath: the path to
5151+ the file, relative to the repo root :param rev: the rev to
5252+ fetch at :return:
5353+5454+ """
5555+ return requests.get(f'https://raw.githubusercontent.com/{self.owner}/{self.repo}/{rev}/{filepath}').text
5656+5757+5858+def _call_nix_update(pkg, version):
5959+ """calls nix-update from nixpkgs root dir"""
6060+ nixpkgs_path = pathlib.Path(__file__).parent / '../../../../'
6161+ return subprocess.check_output(['nix-update', pkg, '--version', version], cwd=nixpkgs_path)
6262+6363+6464+def _get_current_package_version(pkg: str):
6565+ nixpkgs_path = pathlib.Path(__file__).parent / '../../../../'
6666+ return subprocess.check_output(['nix', 'eval', '--raw', f'nixpkgs.{pkg}.version'], text=True)
6767+6868+6969+def _diff_file(filepath: str, old_version: str, new_version: str):
7070+ repo = DiscourseRepo()
7171+7272+ current_dir = pathlib.Path(__file__).parent
7373+7474+ old = repo.get_file(filepath, 'v' + old_version)
7575+ new = repo.get_file(filepath, 'v' + new_version)
7676+7777+ if old == new:
7878+ click.secho(f'{filepath} is unchanged', fg='green')
7979+ return
8080+8181+ with tempfile.NamedTemporaryFile(mode='w') as o, tempfile.NamedTemporaryFile(mode='w') as n:
8282+ o.write(old), n.write(new)
8383+ width = shutil.get_terminal_size((80, 20)).columns
8484+ diff_proc = subprocess.run(
8585+ ['diff', '--color=always', f'--width={width}', '-y', o.name, n.name],
8686+ stdout=subprocess.PIPE,
8787+ cwd=current_dir,
8888+ text=True
8989+ )
9090+9191+ click.secho(f'Diff for {filepath} ({old_version} -> {new_version}):', fg='bright_blue', bold=True)
9292+ click.echo(diff_proc.stdout + '\n')
9393+ return
9494+9595+9696+@click_log.simple_verbosity_option(logger)
9797+9898+9999+@click.group()
100100+def cli():
101101+ pass
102102+103103+104104+@cli.command()
105105+@click.argument('rev', default='latest')
106106+@click.option('--reverse/--no-reverse', default=False, help='Print diffs from REV to current.')
107107+def print_diffs(rev, reverse):
108108+ """Print out diffs for files used as templates for the NixOS module.
109109+110110+ The current package version found in the nixpkgs worktree the
111111+ script is run from will be used to download the "from" file and
112112+ REV used to download the "to" file for the diff, unless the
113113+ '--reverse' flag is specified.
114114+115115+ REV should be the git rev to find changes in ('vX.Y.Z') or
116116+ 'latest'; defaults to 'latest'.
117117+118118+ """
119119+ if rev == 'latest':
120120+ repo = DiscourseRepo()
121121+ rev = repo.tags[0]
122122+123123+ old_version = _get_current_package_version('discourse')
124124+ new_version = DiscourseRepo.rev2version(rev)
125125+126126+ if reverse:
127127+ old_version, new_version = new_version, old_version
128128+129129+ for f in ['config/nginx.sample.conf', 'config/discourse_defaults.conf']:
130130+ _diff_file(f, old_version, new_version)
131131+132132+133133+@cli.command()
134134+@click.argument('rev', default='latest')
135135+def update(rev):
136136+ """Update gem files and version.
137137+138138+ REV should be the git rev to update to ('vX.Y.Z') or 'latest';
139139+ defaults to 'latest'.
140140+141141+ """
142142+ repo = DiscourseRepo()
143143+144144+ if rev == 'latest':
145145+ rev = repo.tags[0]
146146+ logger.debug(f"Using rev {rev}")
147147+148148+ version = repo.rev2version(rev)
149149+ logger.debug(f"Using version {version}")
150150+151151+ rubyenv_dir = pathlib.Path(__file__).parent / "rubyEnv"
152152+153153+ for fn in ['Gemfile.lock', 'Gemfile']:
154154+ with open(rubyenv_dir / fn, 'w') as f:
155155+ f.write(repo.get_file(fn, rev))
156156+157157+ subprocess.check_output(['bundle', 'lock'], cwd=rubyenv_dir)
158158+ subprocess.check_output(['bundix'], cwd=rubyenv_dir)
159159+160160+ _call_nix_update('discourse', repo.rev2version(rev))
161161+162162+163163+if __name__ == '__main__':
164164+ cli()
+61
pkgs/tools/filesystems/sshfs-fuse/common.nix
···11+{ version, sha256, platforms, patches ? [ ] }:
22+33+{ lib, stdenv, fetchFromGitHub
44+, meson, pkg-config, ninja, docutils, makeWrapper
55+, fuse3, macfuse-stubs, glib
66+, which, python3Packages
77+, openssh
88+}:
99+1010+let
1111+ fuse = if stdenv.isDarwin then macfuse-stubs else fuse3;
1212+in stdenv.mkDerivation rec {
1313+ pname = "sshfs-fuse";
1414+ inherit version;
1515+1616+ src = fetchFromGitHub {
1717+ owner = "libfuse";
1818+ repo = "sshfs";
1919+ rev = "sshfs-${version}";
2020+ inherit sha256;
2121+ };
2222+2323+ inherit patches;
2424+2525+ nativeBuildInputs = [ meson pkg-config ninja docutils makeWrapper ];
2626+ buildInputs = [ fuse glib ];
2727+ checkInputs = [ which python3Packages.pytest ];
2828+2929+ NIX_CFLAGS_COMPILE = lib.optionalString
3030+ (stdenv.hostPlatform.system == "i686-linux")
3131+ "-D_FILE_OFFSET_BITS=64";
3232+3333+ postInstall = ''
3434+ mkdir -p $out/sbin
3535+ ln -sf $out/bin/sshfs $out/sbin/mount.sshfs
3636+ '' + lib.optionalString (!stdenv.isDarwin) ''
3737+ wrapProgram $out/bin/sshfs --prefix PATH : "${openssh}/bin"
3838+ '';
3939+4040+ # doCheck = true;
4141+ checkPhase = lib.optionalString (!stdenv.isDarwin) ''
4242+ # The tests need fusermount:
4343+ mkdir bin
4444+ cp ${fuse}/bin/fusermount3 bin/fusermount
4545+ export PATH=bin:$PATH
4646+ # Can't access /dev/fuse within the sandbox: "FUSE kernel module does not seem to be loaded"
4747+ substituteInPlace test/util.py --replace "/dev/fuse" "/dev/null"
4848+ # TODO: "fusermount executable not setuid, and we are not root"
4949+ # We should probably use a VM test instead
5050+ ${python3Packages.python.interpreter} -m pytest test/
5151+ '';
5252+5353+ meta = with lib; {
5454+ inherit platforms;
5555+ description = "FUSE-based filesystem that allows remote filesystems to be mounted over SSH";
5656+ longDescription = macfuse-stubs.warning;
5757+ homepage = "https://github.com/libfuse/sshfs";
5858+ license = licenses.gpl2Plus;
5959+ maintainers = with maintainers; [ primeos ];
6060+ };
6161+}
+25-49
pkgs/tools/filesystems/sshfs-fuse/default.nix
···11-{ lib, stdenv, fetchFromGitHub
22-, meson, pkg-config, ninja, docutils, makeWrapper
33-, fuse3, glib
44-, which, python3Packages
55-, openssh
66-}:
11+{ lib, stdenv, callPackage, fetchpatch }:
7288-stdenv.mkDerivation rec {
99- version = "3.7.1";
1010- pname = "sshfs-fuse";
33+let mkSSHFS = args: callPackage (import ./common.nix args) { };
44+in if stdenv.isDarwin then
55+ mkSSHFS {
66+ version = "2.10"; # macFUSE isn't yet compatible with libfuse 3.x
77+ sha256 = "1dmw4kx6vyawcywiv8drrajnam0m29mxfswcp4209qafzx3mjlp1";
88+ patches = [
99+ # remove reference to fuse_darwin.h which doens't exist on recent macFUSE
1010+ ./fix-fuse-darwin-h.patch
11111212- src = fetchFromGitHub {
1313- owner = "libfuse";
1414- repo = "sshfs";
1515- rev = "sshfs-${version}";
1212+ # From https://github.com/libfuse/sshfs/pull/185:
1313+ # > With this patch, setting I/O size to a reasonable large value, will
1414+ # > result in much improved performance, e.g.: -o iosize=1048576
1515+ (fetchpatch {
1616+ name = "fix-configurable-blksize.patch";
1717+ url = "https://github.com/libfuse/sshfs/commit/667cf34622e2e873db776791df275c7a582d6295.patch";
1818+ sha256 = "0d65lawd2g2aisk1rw2vl65dgxywf4vqgv765n9zj9zysyya8a54";
1919+ })
2020+ ];
2121+ platforms = lib.platforms.darwin;
2222+ }
2323+else
2424+ mkSSHFS {
2525+ version = "3.7.1";
1626 sha256 = "088mgcsqv9f2vly4xn6lvvkmqkgr9jjmjs9qp8938hl7j6rrgd17";
1717- };
1818-1919- nativeBuildInputs = [ meson pkg-config ninja docutils makeWrapper ];
2020- buildInputs = [ fuse3 glib ];
2121- checkInputs = [ which python3Packages.pytest ];
2222-2323- NIX_CFLAGS_COMPILE = lib.optionalString
2424- (stdenv.hostPlatform.system == "i686-linux")
2525- "-D_FILE_OFFSET_BITS=64";
2626-2727- postInstall = ''
2828- mkdir -p $out/sbin
2929- ln -sf $out/bin/sshfs $out/sbin/mount.sshfs
3030- wrapProgram $out/bin/sshfs --prefix PATH : "${openssh}/bin"
3131- '';
3232-3333- #doCheck = true;
3434- checkPhase = ''
3535- # The tests need fusermount:
3636- mkdir bin && cp ${fuse3}/bin/fusermount3 bin/fusermount
3737- export PATH=bin:$PATH
3838- # Can't access /dev/fuse within the sandbox: "FUSE kernel module does not seem to be loaded"
3939- substituteInPlace test/util.py --replace "/dev/fuse" "/dev/null"
4040- # TODO: "fusermount executable not setuid, and we are not root"
4141- # We should probably use a VM test instead
4242- python3 -m pytest test/
4343- '';
4444-4545- meta = with lib; {
4646- inherit (src.meta) homepage;
4747- description = "FUSE-based filesystem that allows remote filesystems to be mounted over SSH";
4848- platforms = platforms.linux;
4949- license = licenses.gpl2;
5050- maintainers = with maintainers; [ primeos ];
5151- };
5252-}
2727+ platforms = lib.platforms.linux;
2828+ }
···11+{ runCommandNoCC, openssh }:
22+33+runCommandNoCC "ssh-copy-id-${openssh.version}" {
44+ meta = openssh.meta // {
55+ description = "A tool to copy SSH public keys to a remote machine";
66+ priority = (openssh.meta.priority or 0) - 1;
77+ };
88+} ''
99+ install -Dm 755 {${openssh},$out}/bin/ssh-copy-id
1010+ install -Dm 644 {${openssh},$out}/share/man/man1/ssh-copy-id.1.gz
1111+''
+1-1
pkgs/top-level/aliases.nix
···620620 qt-3 = throw "qt-3 has been removed from nixpkgs, as it's unmaintained and insecure"; # added 2021-02-15
621621 rfkill = throw "rfkill has been removed, as it's included in util-linux"; # added 2020-08-23
622622 riak-cs = throw "riak-cs is not maintained anymore"; # added 2020-10-14
623623- radare2-cutter = cutter;
623623+ radare2-cutter = cutter; # added 2021-03-30
624624 rkt = throw "rkt was archived by upstream"; # added 2020-05-16
625625 ruby_2_0_0 = throw "ruby_2_0_0 was deprecated on 2018-02-13: use a newer version of ruby";
626626 ruby_2_1_0 = throw "ruby_2_1_0 was deprecated on 2018-02-13: use a newer version of ruby";