1# Discourse {#module-services-discourse}
2
3[Discourse](https://www.discourse.org/) is a
4modern and open source discussion platform.
5
6## Basic usage {#module-services-discourse-basic-usage}
7
8A minimal configuration using Let's Encrypt for TLS certificates looks like this:
9```
10services.discourse = {
11 enable = true;
12 hostname = "discourse.example.com";
13 admin = {
14 email = "admin@example.com";
15 username = "admin";
16 fullName = "Administrator";
17 passwordFile = "/path/to/password_file";
18 };
19 secretKeyBaseFile = "/path/to/secret_key_base_file";
20};
21security.acme.email = "me@example.com";
22security.acme.acceptTerms = true;
23```
24
25Provided a proper DNS setup, you'll be able to connect to the
26instance at `discourse.example.com` and log in
27using the credentials provided in
28`services.discourse.admin`.
29
30## Using a regular TLS certificate {#module-services-discourse-tls}
31
32To set up TLS using a regular certificate and key on file, use
33the [](#opt-services.discourse.sslCertificate)
34and [](#opt-services.discourse.sslCertificateKey)
35options:
36
37```
38services.discourse = {
39 enable = true;
40 hostname = "discourse.example.com";
41 sslCertificate = "/path/to/ssl_certificate";
42 sslCertificateKey = "/path/to/ssl_certificate_key";
43 admin = {
44 email = "admin@example.com";
45 username = "admin";
46 fullName = "Administrator";
47 passwordFile = "/path/to/password_file";
48 };
49 secretKeyBaseFile = "/path/to/secret_key_base_file";
50};
51```
52
53## Database access {#module-services-discourse-database}
54
55Discourse uses PostgreSQL to store most of its
56data. A database will automatically be enabled and a database
57and role created unless [](#opt-services.discourse.database.host) is changed from
58its default of `null` or [](#opt-services.discourse.database.createLocally) is set
59to `false`.
60
61External database access can also be configured by setting
62[](#opt-services.discourse.database.host),
63[](#opt-services.discourse.database.username) and
64[](#opt-services.discourse.database.passwordFile) as
65appropriate. Note that you need to manually create a database
66called `discourse` (or the name you chose in
67[](#opt-services.discourse.database.name)) and
68allow the configured database user full access to it.
69
70## Email {#module-services-discourse-mail}
71
72In addition to the basic setup, you'll want to configure an SMTP
73server Discourse can use to send user
74registration and password reset emails, among others. You can
75also optionally let Discourse receive
76email, which enables people to reply to threads and conversations
77via email.
78
79A basic setup which assumes you want to use your configured
80[hostname](#opt-services.discourse.hostname) as
81email domain can be done like this:
82
83```
84services.discourse = {
85 enable = true;
86 hostname = "discourse.example.com";
87 sslCertificate = "/path/to/ssl_certificate";
88 sslCertificateKey = "/path/to/ssl_certificate_key";
89 admin = {
90 email = "admin@example.com";
91 username = "admin";
92 fullName = "Administrator";
93 passwordFile = "/path/to/password_file";
94 };
95 mail.outgoing = {
96 serverAddress = "smtp.emailprovider.com";
97 port = 587;
98 username = "user@emailprovider.com";
99 passwordFile = "/path/to/smtp_password_file";
100 };
101 mail.incoming.enable = true;
102 secretKeyBaseFile = "/path/to/secret_key_base_file";
103};
104```
105
106This assumes you have set up an MX record for the address you've
107set in [hostname](#opt-services.discourse.hostname) and
108requires proper SPF, DKIM and DMARC configuration to be done for
109the domain you're sending from, in order for email to be reliably delivered.
110
111If you want to use a different domain for your outgoing email
112(for example `example.com` instead of
113`discourse.example.com`) you should set
114[](#opt-services.discourse.mail.notificationEmailAddress) and
115[](#opt-services.discourse.mail.contactEmailAddress) manually.
116
117::: {.note}
118Setup of TLS for incoming email is currently only configured
119automatically when a regular TLS certificate is used, i.e. when
120[](#opt-services.discourse.sslCertificate) and
121[](#opt-services.discourse.sslCertificateKey) are
122set.
123:::
124
125## Additional settings {#module-services-discourse-settings}
126
127Additional site settings and backend settings, for which no
128explicit NixOS options are provided,
129can be set in [](#opt-services.discourse.siteSettings) and
130[](#opt-services.discourse.backendSettings) respectively.
131
132### Site settings {#module-services-discourse-site-settings}
133
134"Site settings" are the settings that can be
135changed through the Discourse
136UI. Their *default* values can be set using
137[](#opt-services.discourse.siteSettings).
138
139Settings are expressed as a Nix attribute set which matches the
140structure of the configuration in
141[config/site_settings.yml](https://github.com/discourse/discourse/blob/master/config/site_settings.yml).
142To find a setting's path, you only need to care about the first
143two levels; i.e. its category (e.g. `login`)
144and name (e.g. `invite_only`).
145
146Settings containing secret data should be set to an attribute
147set containing the attribute `_secret` - a
148string pointing to a file containing the value the option
149should be set to. See the example.
150
151### Backend settings {#module-services-discourse-backend-settings}
152
153Settings are expressed as a Nix attribute set which matches the
154structure of the configuration in
155[config/discourse.conf](https://github.com/discourse/discourse/blob/stable/config/discourse_defaults.conf).
156Empty parameters can be defined by setting them to
157`null`.
158
159### Example {#module-services-discourse-settings-example}
160
161The following example sets the title and description of the
162Discourse instance and enables
163GitHub login in the site settings,
164and changes a few request limits in the backend settings:
165```
166services.discourse = {
167 enable = true;
168 hostname = "discourse.example.com";
169 sslCertificate = "/path/to/ssl_certificate";
170 sslCertificateKey = "/path/to/ssl_certificate_key";
171 admin = {
172 email = "admin@example.com";
173 username = "admin";
174 fullName = "Administrator";
175 passwordFile = "/path/to/password_file";
176 };
177 mail.outgoing = {
178 serverAddress = "smtp.emailprovider.com";
179 port = 587;
180 username = "user@emailprovider.com";
181 passwordFile = "/path/to/smtp_password_file";
182 };
183 mail.incoming.enable = true;
184 siteSettings = {
185 required = {
186 title = "My Cats";
187 site_description = "Discuss My Cats (and be nice plz)";
188 };
189 login = {
190 enable_github_logins = true;
191 github_client_id = "a2f6dfe838cb3206ce20";
192 github_client_secret._secret = /run/keys/discourse_github_client_secret;
193 };
194 };
195 backendSettings = {
196 max_reqs_per_ip_per_minute = 300;
197 max_reqs_per_ip_per_10_seconds = 60;
198 max_asset_reqs_per_ip_per_10_seconds = 250;
199 max_reqs_per_ip_mode = "warn+block";
200 };
201 secretKeyBaseFile = "/path/to/secret_key_base_file";
202};
203```
204
205In the resulting site settings file, the
206`login.github_client_secret` key will be set
207to the contents of the
208{file}`/run/keys/discourse_github_client_secret`
209file.
210
211## Plugins {#module-services-discourse-plugins}
212
213You can install Discourse plugins
214using the [](#opt-services.discourse.plugins)
215option. Pre-packaged plugins are provided in
216`<your_discourse_package_here>.plugins`. If
217you want the full suite of plugins provided through
218`nixpkgs`, you can also set the [](#opt-services.discourse.package) option to
219`pkgs.discourseAllPlugins`.
220
221Plugins can be built with the
222`<your_discourse_package_here>.mkDiscoursePlugin`
223function. Normally, it should suffice to provide a
224`name` and `src` attribute. If
225the plugin has Ruby dependencies, however, they need to be
226packaged in accordance with the [Developing with Ruby](https://nixos.org/manual/nixpkgs/stable/#developing-with-ruby)
227section of the Nixpkgs manual and the
228appropriate gem options set in `bundlerEnvArgs`
229(normally `gemdir` is sufficient). A plugin's
230Ruby dependencies are listed in its
231{file}`plugin.rb` file as function calls to
232`gem`. To construct the corresponding
233{file}`Gemfile` manually, run {command}`bundle init`, then add the `gem` lines to it
234verbatim.
235
236Much of the packaging can be done automatically by the
237{file}`nixpkgs/pkgs/servers/web-apps/discourse/update.py`
238script - just add the plugin to the `plugins`
239list in the `update_plugins` function and run
240the script:
241```bash
242./update.py update-plugins
243```
244
245Some plugins provide [site settings](#module-services-discourse-site-settings).
246Their defaults can be configured using [](#opt-services.discourse.siteSettings), just like
247regular site settings. To find the names of these settings, look
248in the `config/settings.yml` file of the plugin
249repo.
250
251For example, to add the [discourse-spoiler-alert](https://github.com/discourse/discourse-spoiler-alert)
252and [discourse-solved](https://github.com/discourse/discourse-solved)
253plugins, and disable `discourse-spoiler-alert`
254by default:
255
256```
257services.discourse = {
258 enable = true;
259 hostname = "discourse.example.com";
260 sslCertificate = "/path/to/ssl_certificate";
261 sslCertificateKey = "/path/to/ssl_certificate_key";
262 admin = {
263 email = "admin@example.com";
264 username = "admin";
265 fullName = "Administrator";
266 passwordFile = "/path/to/password_file";
267 };
268 mail.outgoing = {
269 serverAddress = "smtp.emailprovider.com";
270 port = 587;
271 username = "user@emailprovider.com";
272 passwordFile = "/path/to/smtp_password_file";
273 };
274 mail.incoming.enable = true;
275 plugins = with config.services.discourse.package.plugins; [
276 discourse-spoiler-alert
277 discourse-solved
278 ];
279 siteSettings = {
280 plugins = {
281 spoiler_enabled = false;
282 };
283 };
284 secretKeyBaseFile = "/path/to/secret_key_base_file";
285};
286```