lib.packagesFromDirectoryRecursive: Improved documentation (#359898)
[NixPkgs.git] / nixos / modules / services / web-apps / onlyoffice.nix
blob0d0e01d4f7bc05d40bbd3aefeccb93494ade180e
1 { lib, config, pkgs, ... }:
3 let
4   cfg = config.services.onlyoffice;
5 in
7   options.services.onlyoffice = {
8     enable = lib.mkEnableOption "OnlyOffice DocumentServer";
10     enableExampleServer = lib.mkEnableOption "OnlyOffice example server";
12     hostname = lib.mkOption {
13       type = lib.types.str;
14       default = "localhost";
15       description = "FQDN for the OnlyOffice instance.";
16     };
18     jwtSecretFile = lib.mkOption {
19       type = lib.types.nullOr lib.types.str;
20       default = null;
21       description = ''
22         Path to a file that contains the secret to sign web requests using JSON Web Tokens.
23         If left at the default value null signing is disabled.
24       '';
25     };
27     package = lib.mkPackageOption pkgs "onlyoffice-documentserver" { };
29     port = lib.mkOption {
30       type = lib.types.port;
31       default = 8000;
32       description = "Port the OnlyOffice document server should listen on.";
33     };
35     examplePort = lib.mkOption {
36       type = lib.types.port;
37       default = null;
38       description = "Port the OnlyOffice example server should listen on.";
39     };
41     postgresHost = lib.mkOption {
42       type = lib.types.str;
43       default = "/run/postgresql";
44       description = "The Postgresql hostname or socket path OnlyOffice should connect to.";
45     };
47     postgresName = lib.mkOption {
48       type = lib.types.str;
49       default = "onlyoffice";
50       description = "The name of database OnlyOffice should use.";
51     };
53     postgresPasswordFile = lib.mkOption {
54       type = lib.types.nullOr lib.types.str;
55       default = null;
56       description = ''
57         Path to a file that contains the password OnlyOffice should use to connect to Postgresql.
58         Unused when using socket authentication.
59       '';
60     };
62     postgresUser = lib.mkOption {
63       type = lib.types.str;
64       default = "onlyoffice";
65       description = ''
66         The username OnlyOffice should use to connect to Postgresql.
67         Unused when using socket authentication.
68       '';
69     };
71     rabbitmqUrl = lib.mkOption {
72       type = lib.types.str;
73       default = "amqp://guest:guest@localhost:5672";
74       description = "The Rabbitmq in amqp URI style OnlyOffice should connect to.";
75     };
76   };
78   config = lib.mkIf cfg.enable {
79     services = {
80       nginx = {
81         enable = lib.mkDefault true;
82         # misses text/csv, font/ttf, application/x-font-ttf, application/rtf, application/wasm
83         recommendedGzipSettings = lib.mkDefault true;
84         recommendedProxySettings = lib.mkDefault true;
86         upstreams = {
87           # /etc/nginx/includes/http-common.conf
88           onlyoffice-docservice = {
89             servers = { "localhost:${toString cfg.port}" = { }; };
90           };
91           onlyoffice-example = lib.mkIf cfg.enableExampleServer {
92             servers = { "localhost:${toString cfg.examplePort}" = { }; };
93           };
94         };
96         virtualHosts.${cfg.hostname} = {
97           locations = {
98             # /etc/nginx/includes/ds-docservice.conf
99             "~ ^(\/[\d]+\.[\d]+\.[\d]+[\.|-][\d]+)?\/(web-apps\/apps\/api\/documents\/api\.js)$".extraConfig = ''
100               expires -1;
101               alias ${cfg.package}/var/www/onlyoffice/documentserver/$2;
102             '';
103             "~ ^(\/[\d]+\.[\d]+\.[\d]+[\.|-][\d]+)?\/(web-apps)(\/.*\.json)$".extraConfig = ''
104               expires 365d;
105               error_log /dev/null crit;
106               alias ${cfg.package}/var/www/onlyoffice/documentserver/$2$3;
107             '';
108             "~ ^(\/[\d]+\.[\d]+\.[\d]+[\.|-][\d]+)?\/(sdkjs-plugins)(\/.*\.json)$".extraConfig = ''
109               expires 365d;
110               error_log /dev/null crit;
111               alias ${cfg.package}/var/www/onlyoffice/documentserver/$2$3;
112             '';
113             "~ ^(\/[\d]+\.[\d]+\.[\d]+[\.|-][\d]+)?\/(web-apps|sdkjs|sdkjs-plugins|fonts)(\/.*)$".extraConfig = ''
114               expires 365d;
115               alias ${cfg.package}/var/www/onlyoffice/documentserver/$2$3;
116             '';
117             "~* ^(\/cache\/files.*)(\/.*)".extraConfig = ''
118               alias /var/lib/onlyoffice/documentserver/App_Data$1;
119               add_header Content-Disposition "attachment; filename*=UTF-8''$arg_filename";
121               set $secret_string verysecretstring;
122               secure_link $arg_md5,$arg_expires;
123               secure_link_md5 "$secure_link_expires$uri$secret_string";
125               if ($secure_link = "") {
126                 return 403;
127               }
129               if ($secure_link = "0") {
130                 return 410;
131               }
132             '';
133             "~* ^(\/[\d]+\.[\d]+\.[\d]+[\.|-][\d]+)?\/(internal)(\/.*)$".extraConfig = ''
134               allow 127.0.0.1;
135               deny all;
136               proxy_pass http://onlyoffice-docservice/$2$3;
137             '';
138             "~* ^(\/[\d]+\.[\d]+\.[\d]+[\.|-][\d]+)?\/(info)(\/.*)$".extraConfig = ''
139               allow 127.0.0.1;
140               deny all;
141               proxy_pass http://onlyoffice-docservice/$2$3;
142             '';
143             "/".extraConfig = ''
144               proxy_pass http://onlyoffice-docservice;
145             '';
146             "~ ^(\/[\d]+\.[\d]+\.[\d]+[\.|-][\d]+)?(\/doc\/.*)".extraConfig = ''
147               proxy_pass http://onlyoffice-docservice$2;
148               proxy_http_version 1.1;
149             '';
150             "/${cfg.package.version}/".extraConfig = ''
151               proxy_pass http://onlyoffice-docservice/;
152             '';
153             "~ ^(\/[\d]+\.[\d]+\.[\d]+[\.|-][\d]+)?\/(dictionaries)(\/.*)$".extraConfig = ''
154               expires 365d;
155               alias ${cfg.package}/var/www/onlyoffice/documentserver/$2$3;
156             '';
157             # /etc/nginx/includes/ds-example.conf
158             "~ ^(\/welcome\/.*)$".extraConfig = ''
159               expires 365d;
160               alias ${cfg.package}/var/www/onlyoffice/documentserver-example$1;
161               index docker.html;
162             '';
163             "/example/".extraConfig = lib.mkIf cfg.enableExampleServer ''
164               proxy_pass http://onlyoffice-example/;
165               proxy_set_header X-Forwarded-Path /example;
166             '';
167           };
168           extraConfig = ''
169             rewrite ^/$ /welcome/ redirect;
170             rewrite ^\/OfficeWeb(\/apps\/.*)$ /${cfg.package.version}/web-apps$1 redirect;
171             rewrite ^(\/web-apps\/apps\/(?!api\/).*)$ /${cfg.package.version}$1 redirect;
173             # based on https://github.com/ONLYOFFICE/document-server-package/blob/master/common/documentserver/nginx/includes/http-common.conf.m4#L29-L34
174             # without variable indirection and correct variable names
175             proxy_set_header Host $host;
176             proxy_set_header X-Forwarded-Host $host;
177             proxy_set_header X-Forwarded-Proto $scheme;
178             # required for CSP to take effect
179             proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
180             # required for websocket
181             proxy_set_header Upgrade $http_upgrade;
182             proxy_set_header Connection $connection_upgrade;
183           '';
184         };
185       };
187       rabbitmq.enable = lib.mkDefault true;
189       postgresql = {
190         enable = lib.mkDefault true;
191         ensureDatabases = [ "onlyoffice" ];
192         ensureUsers = [{
193           name = "onlyoffice";
194           ensureDBOwnership = true;
195         }];
196       };
197     };
199     systemd.services = {
200       onlyoffice-converter = {
201         description = "onlyoffice converter";
202         after = [ "network.target" "onlyoffice-docservice.service" "postgresql.service" ];
203         requires = [ "network.target" "onlyoffice-docservice.service" "postgresql.service" ];
204         wantedBy = [ "multi-user.target" ];
205         serviceConfig = {
206           ExecStart = "${cfg.package.fhs}/bin/onlyoffice-wrapper FileConverter/converter /run/onlyoffice/config";
207           Group = "onlyoffice";
208           Restart = "always";
209           RuntimeDirectory = "onlyoffice";
210           StateDirectory = "onlyoffice";
211           Type = "simple";
212           User = "onlyoffice";
213         };
214       };
216       onlyoffice-docservice =
217         let
218           onlyoffice-prestart = pkgs.writeShellScript "onlyoffice-prestart" ''
219             PATH=$PATH:${lib.makeBinPath (with pkgs; [ jq moreutils config.services.postgresql.package ])}
220             umask 077
221             mkdir -p /run/onlyoffice/config/ /var/lib/onlyoffice/documentserver/sdkjs/{slide/themes,common}/ /var/lib/onlyoffice/documentserver/{fonts,server/FileConverter/bin}/
222             cp -r ${cfg.package}/etc/onlyoffice/documentserver/* /run/onlyoffice/config/
223             chmod u+w /run/onlyoffice/config/default.json
225             # Allow members of the onlyoffice group to serve files under /var/lib/onlyoffice/documentserver/App_Data
226             chmod g+x /var/lib/onlyoffice/documentserver
228             cp /run/onlyoffice/config/default.json{,.orig}
230             # for a mapping of environment variables from the docker container to json options see
231             # https://github.com/ONLYOFFICE/Docker-DocumentServer/blob/master/run-document-server.sh
232             jq '
233               .services.CoAuthoring.server.port = ${toString cfg.port} |
234               .services.CoAuthoring.sql.dbHost = "${cfg.postgresHost}" |
235               .services.CoAuthoring.sql.dbName = "${cfg.postgresName}" |
236             ${lib.optionalString (cfg.postgresPasswordFile != null) ''
237               .services.CoAuthoring.sql.dbPass = "'"$(cat ${cfg.postgresPasswordFile})"'" |
238             ''}
239               .services.CoAuthoring.sql.dbUser = "${cfg.postgresUser}" |
240             ${lib.optionalString (cfg.jwtSecretFile != null) ''
241               .services.CoAuthoring.token.enable.browser = true |
242               .services.CoAuthoring.token.enable.request.inbox = true |
243               .services.CoAuthoring.token.enable.request.outbox = true |
244               .services.CoAuthoring.secret.inbox.string = "'"$(cat ${cfg.jwtSecretFile})"'" |
245               .services.CoAuthoring.secret.outbox.string = "'"$(cat ${cfg.jwtSecretFile})"'" |
246               .services.CoAuthoring.secret.session.string = "'"$(cat ${cfg.jwtSecretFile})"'" |
247             ''}
248               .rabbitmq.url = "${cfg.rabbitmqUrl}"
249               ' /run/onlyoffice/config/default.json | sponge /run/onlyoffice/config/default.json
251             if psql -d onlyoffice -c "SELECT 'task_result'::regclass;" >/dev/null; then
252               psql -f ${cfg.package}/var/www/onlyoffice/documentserver/server/schema/postgresql/removetbl.sql
253               psql -f ${cfg.package}/var/www/onlyoffice/documentserver/server/schema/postgresql/createdb.sql
254             else
255               psql -f ${cfg.package}/var/www/onlyoffice/documentserver/server/schema/postgresql/createdb.sql
256             fi
257           '';
258         in
259         {
260           description = "onlyoffice documentserver";
261           after = [ "network.target" "postgresql.service" ];
262           requires = [ "postgresql.service" ];
263           wantedBy = [ "multi-user.target" ];
264           serviceConfig = {
265             ExecStart = "${cfg.package.fhs}/bin/onlyoffice-wrapper DocService/docservice /run/onlyoffice/config";
266             ExecStartPre = [ onlyoffice-prestart ];
267             Group = "onlyoffice";
268             Restart = "always";
269             RuntimeDirectory = "onlyoffice";
270             StateDirectory = "onlyoffice";
271             Type = "simple";
272             User = "onlyoffice";
273           };
274         };
275     };
277     users.users = {
278       onlyoffice = {
279         description = "OnlyOffice Service";
280         group = "onlyoffice";
281         isSystemUser = true;
282       };
284       nginx.extraGroups = [ "onlyoffice" ];
285     };
287     users.groups.onlyoffice = { };
288   };