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