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