2023-06-23 06:22:34 +02:00
{ config , pkgs , lib , . . . }:
let
cfg = config . shb . nextcloud ;
2023-07-16 00:09:54 +02:00
fqdn = " ${ cfg . subdomain } . ${ cfg . domain } " ;
2024-01-06 00:26:56 +01:00
# Make sure to bump both nextcloudPkg and nextcloudApps at the same time.
nextcloudPkg = pkgs . nextcloud27 ;
nextcloudApps = pkgs . nextcloud27Packages . apps ;
2023-06-23 06:22:34 +02:00
in
{
options . shb . nextcloud = {
enable = lib . mkEnableOption " s e l f h o s t b l o c k s . n e x t c l o u d - s e r v e r " ;
2023-07-16 00:09:54 +02:00
subdomain = lib . mkOption {
type = lib . types . str ;
2023-11-21 07:29:00 +01:00
description = " S u b d o m a i n u n d e r w h i c h N e x t c l o u d w i l l b e s e r v e d . " ;
2023-07-16 00:09:54 +02:00
example = " n e x t c l o u d " ;
} ;
domain = lib . mkOption {
2023-11-30 21:06:41 +01:00
description = " D o m a i n t o s e r v e N e x t c l o u d u n d e r . " ;
2023-06-23 06:22:34 +02:00
type = lib . types . str ;
2023-07-16 00:09:54 +02:00
example = " d o m a i n . c o m " ;
2023-06-23 06:22:34 +02:00
} ;
2023-12-23 08:16:53 +01:00
externalFqdn = lib . mkOption {
description = " E x t e r n a l f q d n u s e d t o a c c e s s N e x t c l o u d . D e f a u l t s t o < s u b d o m a i n > . < d o m a i n > . T h i s s h o u l d o n l y b e s e t i f y o u i n c l u d e t h e p o r t w h e n a c c e s s i n g N e x t c l o u d . " ;
type = lib . types . nullOr lib . types . str ;
example = " n e x t c l o u d . d o m a i n . c o m : 8 0 8 0 " ;
default = null ;
} ;
2023-12-18 08:06:12 +01:00
dataDir = lib . mkOption {
description = " F o l d e r w h e r e N e x t c l o u d w i l l s t o r e a l l i t s d a t a . " ;
type = lib . types . str ;
default = " / v a r / l i b / n e x t c l o u d " ;
} ;
2023-12-30 18:51:55 +01:00
adminUser = lib . mkOption {
type = lib . types . str ;
description = " U s e r n a m e o f t h e i n i t i a l a d m i n u s e r . " ;
default = " r o o t " ;
} ;
2023-12-23 08:16:53 +01:00
adminPassFile = lib . mkOption {
2023-06-23 06:22:34 +02:00
type = lib . types . path ;
2023-12-23 08:16:53 +01:00
description = " F i l e c o n t a i n i n g t h e N e x t c l o u d a d m i n p a s s w o r d . " ;
2023-06-23 06:22:34 +02:00
} ;
2023-08-28 07:20:59 +02:00
2023-12-29 23:32:02 +01:00
maxUploadSize = lib . mkOption {
default = " 4 G " ;
type = lib . types . str ;
description = ''
The upload limit for files . This changes the relevant options
in php . ini and nginx if enabled .
'' ;
} ;
2023-12-18 08:06:12 +01:00
postgresSettings = lib . mkOption {
2023-12-23 08:16:53 +01:00
type = lib . types . nullOr ( lib . types . attrsOf lib . types . str ) ;
default = null ;
2023-12-18 08:06:12 +01:00
description = " S e t t i n g s f o r t h e P o s t g r e S Q L d a t a b a s e . G o t o h t t p s : / / p g t u n e . l e o p a r d . i n . u a / a n d c o p y t h e g e n e r a t e d c o n f i g u r a t i o n h e r e . " ;
example = lib . literalExpression ''
{
# From https://pgtune.leopard.in.ua/ with:
# DB Version: 14
# OS Type: linux
# DB Type: dw
# Total Memory (RAM): 7 GB
# CPUs num: 4
# Connections num: 100
# Data Storage: ssd
max_connections = " 1 0 0 " ;
shared_buffers = " 1 7 9 2 M B " ;
effective_cache_size = " 5 3 7 6 M B " ;
maintenance_work_mem = " 8 9 6 M B " ;
checkpoint_completion_target = " 0 . 9 " ;
wal_buffers = " 1 6 M B " ;
default_statistics_target = " 5 0 0 " ;
random_page_cost = " 1 . 1 " ;
effective_io_concurrency = " 2 0 0 " ;
work_mem = " 4 5 8 7 k B " ;
huge_pages = " o f f " ;
min_wal_size = " 4 G B " ;
max_wal_size = " 1 6 G B " ;
max_worker_processes = " 4 " ;
max_parallel_workers_per_gather = " 2 " ;
max_parallel_workers = " 4 " ;
max_parallel_maintenance_workers = " 2 " ;
}
'' ;
} ;
phpFpmPoolSettings = lib . mkOption {
2023-12-23 08:16:53 +01:00
type = lib . types . nullOr ( lib . types . attrsOf lib . types . anything ) ;
2023-12-18 08:06:12 +01:00
description = " S e t t i n g s f o r P H P F P M . " ;
2023-12-23 08:16:53 +01:00
default = null ;
example = lib . literalExpression ''
2023-12-18 08:06:12 +01:00
{
" p m " = " d y n a m i c " ;
" p m . m a x _ c h i l d r e n " = 50 ;
" p m . s t a r t _ s e r v e r s " = 25 ;
" p m . m i n _ s p a r e _ s e r v e r s " = 10 ;
" p m . m a x _ s p a r e _ s e r v e r s " = 20 ;
" p m . m a x _ s p a w n _ r a t e " = 50 ;
" p m . m a x _ r e q u e s t s " = 50 ;
" p m . p r o c e s s _ i d l e _ t i m e o u t " = " 2 0 s " ;
}
'' ;
} ;
2024-01-05 22:47:54 +01:00
apps = lib . mkOption {
description = ''
Applications to enable in Nextcloud . Enabling an application here will also configure
various services needed for this application .
2024-01-06 00:17:53 +01:00
Enabled apps will automatically be installed , enabled and configured , so no need to do that
through the UI . You can still make changes but they will be overridden on next deploy . You
can still install and configure other apps through the UI .
2024-01-05 22:47:54 +01:00
'' ;
type = lib . types . submodule {
options = {
onlyoffice = lib . mkOption {
2024-01-06 00:26:56 +01:00
description = ''
Only Office App . [ Nextcloud App Store ] ( https://apps.nextcloud.com/apps/onlyoffice )
Enabling this app will also start an OnlyOffice instance accessible at the given
subdomain from the given network range .
'' ;
2024-01-06 00:17:53 +01:00
type = lib . types . submodule {
2024-01-05 22:47:54 +01:00
options = {
2024-01-06 00:17:53 +01:00
enable = lib . mkEnableOption " N e x t c l o u d O n l y O f f i c e A p p " ;
2024-01-05 22:47:54 +01:00
subdomain = lib . mkOption {
type = lib . types . str ;
description = " S u b d o m a i n u n d e r w h i c h O n l y O f f i c e w i l l b e s e r v e d . " ;
default = " o o " ;
} ;
localNetworkIPRange = lib . mkOption {
type = lib . types . str ;
description = " L o c a l n e t w o r k r a n g e , t o r e s t r i c t a c c e s s t o O p e n O f f i c e t o o n l y t h o s e I P s . " ;
2024-01-06 00:17:53 +01:00
default = " 1 9 2 . 1 6 8 . 1 . 1 / 2 4 " ;
2024-01-05 22:47:54 +01:00
} ;
jwtSecretFile = lib . mkOption {
type = lib . types . path ;
2024-01-06 00:26:56 +01:00
description = " F i l e c o n t a i n i n g t h e J W T s e c r e t . T h i s o p t i o n i s r e q u i r e d . " ;
default = " " ;
} ;
} ;
} ;
} ;
previewgenerator = lib . mkOption {
description = ''
Preview Generator App . [ Nextcloud App Store ] ( https://apps.nextcloud.com/apps/previewgenerator )
Enabling this app will create a cron job running every minute to generate thumbnails
for new and updated files .
To generate thumbnails for already existing files , run :
` ` `
nextcloud-occ - vvv preview:generate-all
` ` `
'' ;
type = lib . types . nullOr ( lib . types . submodule {
options = {
enable = lib . mkEnableOption " N e x t c l o u d P r e v i e w G e n e r a t o r A p p " ;
debug = lib . mkOption {
type = lib . types . bool ;
description = " E n a b l e m o r e v e r b o s e l o g g i n g . " ;
default = false ;
example = true ;
2024-01-05 22:47:54 +01:00
} ;
} ;
} ) ;
} ;
} ;
} ;
} ;
2024-01-05 22:40:38 +01:00
extraApps = lib . mkOption {
type = lib . types . raw ;
description = ''
Extra apps to install . Should be a function returning an attrSet of appid to packages
generated by fetchNextcloudApp . The appid must be identical to the “ id ” value in the apps
appinfo/info.xml. You can still install apps through the appstore .
'' ;
default = apps : { } ;
example = lib . literalExpression ''
apps : {
inherit ( apps ) mail calendar contact ;
phonetrack = pkgs . fetchNextcloudApp {
name = " p h o n e t r a c k " ;
sha256 = " 0 q f 3 6 6 v b a h y l 2 7 p 9 m s h f m a 1 a s 4 n v q l 6 w 7 5 z y 2 z k 5 x w w b p 3 4 3 v s b c " ;
url = " h t t p s : / / g i t l a b . c o m / e n e i l u j / p h o n e t r a c k - o c / - / w i k i s / u p l o a d s / 9 3 1 a a a f 8 d c a 2 4 b f 3 1 a 7 e 1 6 9 a 8 3 c 1 7 2 3 5 / p h o n e t r a c k - 0 . 6 . 9 . t a r . g z " ;
version = " 0 . 6 . 9 " ;
} ;
}
'' ;
} ;
2023-09-13 05:52:24 +02:00
debug = lib . mkOption {
type = lib . types . bool ;
2023-12-18 07:54:19 +01:00
description = " E n a b l e m o r e v e r b o s e l o g g i n g . " ;
default = false ;
example = true ;
} ;
tracing = lib . mkOption {
2023-12-23 08:16:53 +01:00
type = lib . types . nullOr lib . types . str ;
2023-12-18 07:54:19 +01:00
description = " E n a b l e x d e b u g t r a c i n g . " ;
2023-12-23 08:16:53 +01:00
default = null ;
example = " d e b u g _ m e " ;
2023-09-13 05:52:24 +02:00
} ;
2023-06-23 06:22:34 +02:00
} ;
2024-01-05 22:47:54 +01:00
config = lib . mkMerge [
( lib . mkIf cfg . enable {
users . users = {
nextcloud = {
name = " n e x t c l o u d " ;
group = " n e x t c l o u d " ;
isSystemUser = true ;
} ;
2023-06-23 06:22:34 +02:00
} ;
2024-01-05 22:47:54 +01:00
users . groups = {
nextcloud = {
members = [ " b a c k u p " ] ;
} ;
2023-06-23 06:22:34 +02:00
} ;
2024-01-05 22:47:54 +01:00
# LDAP is manually configured through
# https://github.com/lldap/lldap/blob/main/example_configs/nextcloud.md, see also
# https://docs.nextcloud.com/server/latest/admin_manual/configuration_user/user_auth_ldap.html
#
# Verify setup with:
# - On admin page
# - https://scan.nextcloud.com/
# - https://www.ssllabs.com/ssltest/
# As of writing this, we got no warning on admin page and A+ on both tests.
#
# Content-Security-Policy is hard. I spent so much trying to fix lingering issues with .js files
# not loading to realize those scripts are inserted by extensions. Doh.
services . nextcloud = {
enable = true ;
2024-01-06 00:26:56 +01:00
package = nextcloudPkg ;
2024-01-05 22:47:54 +01:00
datadir = cfg . dataDir ;
hostName = fqdn ;
nginx . hstsMaxAge = 31536000 ; # Needs > 1 year for https://hstspreload.org to be happy
inherit ( cfg ) maxUploadSize ;
config = {
dbtype = " p g s q l " ;
adminuser = cfg . adminUser ;
adminpassFile = toString cfg . adminPassFile ;
# Not using dbpassFile as we're using socket authentication.
defaultPhoneRegion = " U S " ;
trustedProxies = [ " 1 2 7 . 0 . 0 . 1 " ] ;
} ;
database . createLocally = true ;
# Enable caching using redis https://nixos.wiki/wiki/Nextcloud#Caching.
configureRedis = true ;
caching . apcu = false ;
# https://docs.nextcloud.com/server/26/admin_manual/configuration_server/caching_configuration.html
caching . redis = true ;
# Adds appropriate nginx rewrite rules.
webfinger = true ;
# Very important for a bunch of scripts to load correctly. Otherwise you get Content-Security-Policy errors. See https://docs.nextcloud.com/server/13/admin_manual/configuration_server/harden_server.html#enable-http-strict-transport-security
https = config . shb . ssl . enable ;
2024-01-06 00:26:56 +01:00
extraApps = cfg . extraApps nextcloudApps ;
2024-01-05 22:47:54 +01:00
extraAppsEnable = true ;
appstoreEnable = true ;
extraOptions = let
protocol = if config . shb . ssl . enable then " h t t p s " else " h t t p " ;
in {
" o v e r w r i t e . c l i . u r l " = " ${ protocol } : / / ${ fqdn } " ;
" o v e r w r i t e h o s t " = if ( isNull cfg . externalFqdn ) then fqdn else cfg . externalFqdn ;
# 'trusted_domains' needed otherwise we get this issue https://help.nextcloud.com/t/the-polling-url-does-not-start-with-https-despite-the-login-url-started-with-https/137576/2
# TODO: could instead set extraTrustedDomains
" t r u s t e d _ d o m a i n s " = [ fqdn ] ;
# TODO: could instead set overwriteProtocol
" o v e r w r i t e p r o t o c o l " = protocol ; # Needed if behind a reverse_proxy
" o v e r w r i t e c o n d a d d r " = " " ; # We need to set it to empty otherwise overwriteprotocol does not work.
" d e b u g " = cfg . debug ;
" f i l e l o c k i n g . d e b u g " = cfg . debug ;
} ;
2023-06-23 06:22:34 +02:00
2024-01-05 22:47:54 +01:00
phpOptions = {
# The OPcache interned strings buffer is nearly full with 8, bump to 16.
catch_workers_output = " y e s " ;
display_errors = " s t d e r r " ;
error_reporting = " E _ A L L & ~ E _ D E P R E C A T E D & ~ E _ S T R I C T " ;
expose_php = " O f f " ;
" o p c a c h e . e n a b l e _ c l i " = " 1 " ;
" o p c a c h e . f a s t _ s h u t d o w n " = " 1 " ;
" o p c a c h e . i n t e r n e d _ s t r i n g s _ b u f f e r " = " 1 6 " ;
" o p c a c h e . m a x _ a c c e l e r a t e d _ f i l e s " = " 1 0 0 0 0 " ;
" o p c a c h e . m e m o r y _ c o n s u m p t i o n " = " 1 2 8 " ;
" o p c a c h e . r e v a l i d a t e _ f r e q " = " 1 " ;
" o p e n s s l . c a f i l e " = " / e t c / s s l / c e r t s / c a - c e r t i f i c a t e s . c r t " ;
short_open_tag = " O f f " ;
output_buffering = " O f f " ;
# Needed to avoid corruption per https://docs.nextcloud.com/server/21/admin_manual/configuration_server/caching_configuration.html#id2
" r e d i s . s e s s i o n . l o c k i n g _ e n a b l e d " = " 1 " ;
" r e d i s . s e s s i o n . l o c k _ r e t r i e s " = " - 1 " ;
" r e d i s . s e s s i o n . l o c k _ w a i t _ t i m e " = " 1 0 0 0 0 " ;
} // lib . optionalAttrs ( ! ( isNull cfg . tracing ) ) {
# "xdebug.remote_enable" = "on";
# "xdebug.remote_host" = "127.0.0.1";
# "xdebug.remote_port" = "9000";
# "xdebug.remote_handler" = "dbgp";
" x d e b u g . t r i g g e r _ v a l u e " = cfg . tracing ;
" x d e b u g . m o d e " = " p r o f i l e , t r a c e " ;
" x d e b u g . o u t p u t _ d i r " = " / v a r / l o g / x d e b u g " ;
" x d e b u g . s t a r t _ w i t h _ r e q u e s t " = " t r i g g e r " ;
} ;
2023-09-13 05:52:24 +02:00
2024-01-05 22:47:54 +01:00
poolSettings = lib . mkIf ( ! ( isNull cfg . phpFpmPoolSettings ) ) cfg . phpFpmPoolSettings ;
2023-12-18 08:06:12 +01:00
2024-01-05 22:47:54 +01:00
phpExtraExtensions = all : [ all . xdebug ] ;
} ;
2023-06-23 06:22:34 +02:00
2024-01-05 22:47:54 +01:00
services . nginx . virtualHosts . ${ fqdn } = {
# listen = [ { addr = "0.0.0.0"; port = 443; } ];
sslCertificate = lib . mkIf config . shb . ssl . enable " / v a r / l i b / a c m e / ${ cfg . domain } / c e r t . p e m " ;
sslCertificateKey = lib . mkIf config . shb . ssl . enable " / v a r / l i b / a c m e / ${ cfg . domain } / k e y . p e m " ;
forceSSL = lib . mkIf config . shb . ssl . enable true ;
# From [1] this should fix downloading of big files. [2] seems to indicate that buffering
# happens at multiple places anyway, so disabling one place should be okay.
# [1]: https://help.nextcloud.com/t/download-aborts-after-time-or-large-file/25044/6
# [2]: https://stackoverflow.com/a/50891625/1013628
extraConfig = ''
2023-12-29 23:32:02 +01:00
proxy_buffering off ;
'' ;
2024-01-05 22:47:54 +01:00
} ;
2023-06-23 06:22:34 +02:00
2024-01-05 22:47:54 +01:00
environment . systemPackages = [
# Needed for a few apps. Would be nice to avoid having to put that in the environment and instead override https://github.com/NixOS/nixpkgs/blob/261abe8a44a7e8392598d038d2e01f7b33cf26d0/nixos/modules/services/web-apps/nextcloud.nix#L1035
pkgs . ffmpeg
2023-10-01 00:49:55 +02:00
2024-01-05 22:47:54 +01:00
# Needed for the recognize app.
pkgs . nodejs
] ;
2023-09-27 22:28:10 +02:00
2024-01-05 22:47:54 +01:00
services . postgresql . settings = lib . mkIf ( ! ( isNull cfg . postgresSettings ) ) cfg . postgresSettings ;
2023-09-27 22:36:29 +02:00
2024-01-05 22:47:54 +01:00
systemd . services . phpfpm-nextcloud . serviceConfig = {
# Setup permissions needed for backups, as the backup user is member of the jellyfin group.
UMask = lib . mkForce " 0 0 2 7 " ;
} ;
systemd . services . phpfpm-nextcloud . preStart = ''
2023-09-13 05:52:24 +02:00
mkdir - p /var/log/xdebug ; chown - R nextcloud : /var/log/xdebug
'' ;
2023-06-23 06:22:34 +02:00
2024-01-05 22:47:54 +01:00
systemd . services . nextcloud-cron . path = [
pkgs . perl
2023-06-23 06:22:34 +02:00
] ;
2023-12-18 07:52:34 +01:00
2024-01-05 22:47:54 +01:00
# Sets up backup for Nextcloud.
shb . backup . instances . nextcloud = {
sourceDirectories = [
cfg . dataDir
] ;
excludePatterns = [ " . r n d " ] ;
} ;
} )
2023-12-18 07:52:34 +01:00
2024-01-06 00:17:53 +01:00
( lib . mkIf cfg . apps . onlyoffice . enable {
assertions = [
{
assertion = cfg . apps . onlyoffice . jwtSecretFile != " " ;
message = " M u s t s e t j w t S e c r e t F i l e . " ;
}
] ;
2024-01-05 23:03:05 +01:00
services . nextcloud . extraApps = {
inherit ( nextcloudApps ) onlyoffice ;
} ;
2024-01-05 22:47:54 +01:00
services . onlyoffice = {
enable = true ;
hostname = " ${ cfg . apps . onlyoffice . subdomain } . ${ cfg . domain } " ;
port = 13444 ;
2023-12-18 07:52:34 +01:00
2024-01-05 22:47:54 +01:00
postgresHost = " / r u n / p o s t g r e s q l " ;
jwtSecretFile = cfg . apps . onlyoffice . jwtSecretFile ;
} ;
services . nginx . virtualHosts . " ${ cfg . apps . onlyoffice . subdomain } . ${ cfg . domain } " = {
sslCertificate = lib . mkIf config . shb . ssl . enable " / v a r / l i b / a c m e / ${ cfg . domain } / c e r t . p e m " ;
sslCertificateKey = lib . mkIf config . shb . ssl . enable " / v a r / l i b / a c m e / ${ cfg . domain } / k e y . p e m " ;
forceSSL = lib . mkIf config . shb . ssl . enable true ;
locations . " / " = {
extraConfig = ''
allow $ { cfg . apps . onlyoffice . localNetworkIPRange } ;
2023-12-18 07:52:34 +01:00
'' ;
2024-01-05 22:47:54 +01:00
} ;
2023-12-18 07:52:34 +01:00
} ;
2024-01-05 22:47:54 +01:00
} )
2024-01-06 00:26:56 +01:00
( lib . mkIf cfg . apps . previewgenerator . enable {
services . nextcloud . extraApps = {
inherit ( nextcloudApps ) previewgenerator ;
} ;
# Configured as defined in https://github.com/nextcloud/previewgenerator
systemd . timers . nextcloud-cron-previewgenerator = {
wantedBy = [ " t i m e r s . t a r g e t " ] ;
after = [ " n e x t c l o u d - s e t u p . s e r v i c e " ] ;
timerConfig . OnBootSec = " 1 0 m " ;
timerConfig . OnUnitActiveSec = " 1 0 m " ;
timerConfig . Unit = " n e x t c l o u d - c r o n - p r e v i e w g e n e r a t o r . s e r v i c e " ;
} ;
systemd . services . nextcloud-cron-previewgenerator = {
environment . NEXTCLOUD_CONFIG_DIR = " ${ config . services . nextcloud . datadir } / c o n f i g " ;
serviceConfig . Type = " o n e s h o t " ;
serviceConfig . User = " n e x t c l o u d " ;
serviceConfig . ExecStart =
let
debug = if cfg . debug or cfg . apps . previewgenerator . debug then " - v v v " else " " ;
in
" ${ config . services . nextcloud . occ } / b i n / n e x t c l o u d - o c c ${ debug } p r e v i e w : p r e - g e n e r a t e " ;
} ;
} )
2024-01-05 22:47:54 +01:00
] ;
2023-06-23 06:22:34 +02:00
}