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-22 08:38:57 +01:00
fqdnWithPort = if isNull cfg . port then fqdn else " ${ fqdn } : ${ toString cfg . port } " ;
protocol = if ! ( isNull cfg . ssl ) then " h t t p s " else " h t t p " ;
ssoFqdnWithPort = if isNull cfg . apps . sso . port then cfg . apps . sso . endpoint else " ${ cfg . apps . sso . endpoint } : ${ toString cfg . apps . sso . port } " ;
2024-01-06 00:26:56 +01:00
2024-01-12 08:22:46 +01:00
contracts = pkgs . callPackage ../contracts { } ;
2024-01-06 00:26:56 +01:00
# Make sure to bump both nextcloudPkg and nextcloudApps at the same time.
2024-01-29 07:54:49 +01:00
nextcloudPkg = version : builtins . getAttr ( " n e x t c l o u d " + builtins . toString version ) pkgs ;
nextcloudApps = version : ( builtins . getAttr ( " n e x t c l o u d " + builtins . toString version + " P a c k a g e s " ) pkgs ) . apps ;
2024-01-06 09:03:56 +01:00
occ = " ${ config . services . nextcloud . occ } / b i n / n e x t c l o u d - o c c " ;
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 ;
2024-01-22 08:38:57 +01:00
description = ''
Subdomain under which Nextcloud will be served .
` ` `
<subdomain> . <domain> [ : <port> ]
` ` `
'' ;
2023-07-16 00:09:54 +02:00
example = " n e x t c l o u d " ;
} ;
domain = lib . mkOption {
2024-01-22 08:38:57 +01:00
description = ''
Domain under which Nextcloud is served .
` ` `
<subdomain> . <domain> [ : <port> ]
` ` `
'' ;
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
} ;
2024-01-22 08:38:57 +01:00
port = lib . mkOption {
description = ''
Port under which Nextcloud will be served . If null is given , then the port is omitted .
` ` `
<subdomain> . <domain> [ : <port> ]
` ` `
'' ;
type = lib . types . nullOr lib . types . port ;
default = null ;
} ;
2024-01-12 08:22:46 +01:00
ssl = lib . mkOption {
description = " P a t h t o S S L f i l e s " ;
type = lib . types . nullOr contracts . ssl . certs ;
default = null ;
} ;
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 ;
} ;
2024-01-29 07:54:49 +01:00
version = lib . mkOption {
description = " N e x t c l o u d v e r s i o n t o c h o o s e f r o m . " ;
type = lib . types . enum [ 27 28 ] ;
default = 27 ;
} ;
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 {
2024-01-06 01:15:39 +01:00
type = lib . types . nullOr lib . types . path ;
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 . R e q u i r e d . " ;
default = null ;
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 .
'' ;
} ;
2024-02-07 05:42:52 +01:00
defaultPhoneRegion = lib . mkOption {
type = lib . types . str ;
description = ''
Two letters region defining default region .
'' ;
example = " U S " ;
} ;
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
'' ;
2024-01-06 00:34:13 +01:00
default = { } ;
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 01:15:39 +01:00
default = { } ;
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 " ;
} ;
2024-01-12 08:22:46 +01:00
ssl = lib . mkOption {
description = " P a t h t o S S L f i l e s " ;
type = lib . types . nullOr contracts . ssl . certs ;
default = null ;
} ;
2024-01-05 22:47:54 +01:00
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 {
2024-01-06 01:15:39 +01:00
type = lib . types . nullOr lib . types . path ;
2024-01-22 08:38:57 +01:00
description = ''
File containing the JWT secret . This option is required .
Must be readable by the nextcloud system user .
'' ;
2024-01-06 01:15:39 +01:00
default = null ;
2024-01-06 00:26:56 +01:00
} ;
} ;
} ;
} ;
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
` ` `
'' ;
2024-01-06 01:15:39 +01:00
default = { } ;
type = lib . types . submodule {
2024-01-06 00:26:56 +01:00
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-06 01:15:39 +01:00
} ;
2024-01-05 22:47:54 +01:00
} ;
2024-01-06 09:03:56 +01:00
ldap = lib . mkOption {
description = ''
LDAP Integration App . [ Manual ] ( https://docs.nextcloud.com/server/latest/admin_manual/configuration_user/user_auth_ldap.html )
Enabling this app will create a new LDAP configuration or update one that exists with
the given host .
'' ;
default = { } ;
type = lib . types . nullOr ( lib . types . submodule {
options = {
enable = lib . mkEnableOption " L D A P a p p . " ;
host = lib . mkOption {
2024-01-22 08:38:57 +01:00
type = lib . types . str ;
2024-01-06 09:03:56 +01:00
description = ''
Host serving the LDAP server .
'' ;
default = " 1 2 7 . 0 . 0 . 1 " ;
} ;
port = lib . mkOption {
2024-01-22 08:38:57 +01:00
type = lib . types . port ;
2024-01-06 09:03:56 +01:00
description = ''
Port of the service serving the LDAP server .
'' ;
default = 389 ;
} ;
dcdomain = lib . mkOption {
type = lib . types . str ;
description = " d c d o m a i n f o r l d a p . " ;
example = " d c = m y d o m a i n , d c = c o m " ;
} ;
adminName = lib . mkOption {
type = lib . types . str ;
description = " A d m i n u s e r o f t h e L D A P s e r v e r . " ;
default = " a d m i n " ;
} ;
adminPasswordFile = lib . mkOption {
type = lib . types . path ;
2024-01-22 08:38:57 +01:00
description = ''
File containing the admin password of the LDAP server .
Must be readable by the nextcloud system user .
'' ;
2024-01-06 09:03:56 +01:00
default = " " ;
} ;
userGroup = lib . mkOption {
type = lib . types . str ;
description = " G r o u p u s e r s m u s t b e l o n g t o t o b e a b l e t o l o g i n t o N e x t c l o u d . " ;
default = " n e x t c l o u d _ u s e r " ;
} ;
} ;
} ) ;
} ;
2024-01-22 08:38:57 +01:00
sso = lib . mkOption {
description = ''
SSO Integration App . [ Manual ] ( https://docs.nextcloud.com/server/latest/admin_manual/configuration_user/oidc_auth.html )
Enabling this app will create a new LDAP configuration or update one that exists with
the given host .
'' ;
default = { } ;
type = lib . types . submodule {
options = {
enable = lib . mkEnableOption " S S O a p p . " ;
endpoint = lib . mkOption {
type = lib . types . str ;
description = " O I D C e n d p o i n t f o r S S O . " ;
example = " h t t p s : / / a u t h e l i a . e x a m p l e . c o m " ;
} ;
port = lib . mkOption {
description = " I f g i v e n , a d d s a p o r t t o t h e e n d p o i n t . " ;
type = lib . types . nullOr lib . types . port ;
default = null ;
} ;
provider = lib . mkOption {
type = lib . types . enum [ " A u t h e l i a " ] ;
description = " O I D C p r o v i d e r n a m e , u s e d f o r d i s p l a y . " ;
default = " A u t h e l i a " ;
} ;
clientID = lib . mkOption {
type = lib . types . str ;
description = " C l i e n t I D f o r t h e O I D C e n d p o i n t . " ;
default = " n e x t c l o u d " ;
} ;
authorization_policy = lib . mkOption {
type = lib . types . enum [ " o n e _ f a c t o r " " t w o _ f a c t o r " ] ;
description = " R e q u i r e o n e f a c t o r ( p a s s w o r d ) o r t w o f a c t o r ( d e v i c e ) a u t h e n t i c a t i o n . " ;
default = " o n e _ f a c t o r " ;
} ;
secretFile = lib . mkOption {
type = lib . types . path ;
description = ''
File containing the secret for the OIDC endpoint .
Must be readable by the nextcloud system user .
'' ;
default = " " ;
} ;
secretFileForAuthelia = lib . mkOption {
type = lib . types . path ;
description = ''
File containing the secret for the OIDC endpoint , must be readable by the Authelia user .
Must be readable by the authelia system user .
'' ;
default = " " ;
} ;
fallbackDefaultAuth = lib . mkOption {
type = lib . types . bool ;
description = ''
Fallback to normal Nextcloud auth if something goes wrong with the SSO app .
Usually , you want to enable this to transfer existing users to LDAP and then you
can disabled it .
'' ;
default = false ;
} ;
} ;
} ;
} ;
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 .
'' ;
2024-01-06 01:15:39 +01:00
default = null ;
2024-01-05 22:40:38 +01:00
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 ;
2024-01-06 18:43:30 +01:00
description = ''
Enable xdebug tracing .
To trigger writing a trace to ` /var/log/xdebug ` , add a the following header :
` ` `
XDEBUG_TRACE < shb . nextcloud . tracing value >
` ` `
The response will contain the following header :
` ` `
x-xdebug-profile-filename /var/log/xdebug/cachegrind.out.63484
` ` `
'' ;
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 {
2024-01-06 01:15:39 +01:00
assertions = [
{
assertion = ! ( isNull cfg . adminPassFile ) ;
message = " M u s t s e t s h b . n e x t c l o u d . a d m i n P a s s F i l e . " ;
}
] ;
2024-01-05 22:47:54 +01:00
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-29 07:54:49 +01:00
package = nextcloudPkg cfg . version ;
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 ;
} ;
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
2024-01-12 08:22:46 +01:00
https = ! ( isNull cfg . ssl ) ;
2024-01-05 22:47:54 +01:00
2024-01-29 07:54:49 +01:00
extraApps = if isNull cfg . extraApps then { } else cfg . extraApps ( nextcloudApps cfg . version ) ;
2024-01-05 22:47:54 +01:00
extraAppsEnable = true ;
appstoreEnable = true ;
2024-02-07 05:42:52 +01:00
settings = let
2024-01-12 08:22:46 +01:00
protocol = if ! ( isNull cfg . ssl ) then " h t t p s " else " h t t p " ;
2024-01-05 22:47:54 +01:00
in {
2024-02-07 05:42:52 +01:00
" d e f a u l t _ p h o n e _ r e g i o n " = cfg . defaultPhoneRegion ;
2024-01-05 22:47:54 +01:00
" o v e r w r i t e . c l i . u r l " = " ${ protocol } : / / ${ fqdn } " ;
2024-01-22 08:38:57 +01:00
" o v e r w r i t e h o s t " = fqdnWithPort ;
2024-01-05 22:47:54 +01:00
# '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 ] ;
2024-02-07 05:42:52 +01:00
" t r u s t e d _ p r o x i e s " = [ " 1 2 7 . 0 . 0 . 1 " ] ;
2024-01-05 22:47:54 +01:00
# 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 ;
2024-01-12 08:22:46 +01:00
# Use persistent SQL connections.
" d b p e r s i s t e n t " = " t r u e " ;
2024-01-05 22:47:54 +01:00
} ;
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 " ;
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; } ];
2024-01-12 08:22:46 +01:00
forceSSL = ! ( isNull cfg . ssl ) ;
sslCertificate = lib . mkIf ( ! ( isNull cfg . ssl ) ) cfg . ssl . paths . cert ;
sslCertificateKey = lib . mkIf ( ! ( isNull cfg . ssl ) ) cfg . ssl . paths . key ;
2024-01-05 22:47:54 +01:00
# 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 = ''
2024-01-22 08:38:57 +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
2024-01-22 08:38:57 +01:00
'' ;
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 = [
{
2024-01-06 01:15:39 +01:00
assertion = ! ( isNull cfg . apps . onlyoffice . jwtSecretFile ) ;
message = " M u s t s e t s h b . n e x t c l o u d . a p p s . o n l y o f f i c e . j w t S e c r e t F i l e . " ;
2024-01-06 00:17:53 +01:00
}
] ;
2024-01-05 23:03:05 +01:00
services . nextcloud . extraApps = {
2024-01-29 07:54:49 +01:00
inherit ( ( nextcloudApps cfg . version ) ) onlyoffice ;
2024-01-05 23:03:05 +01:00
} ;
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 } " = {
2024-01-12 08:22:46 +01:00
forceSSL = ! ( isNull cfg . ssl ) ;
sslCertificate = lib . mkIf ( ! ( isNull cfg . ssl ) ) cfg . ssl . paths . cert ;
sslCertificateKey = lib . mkIf ( ! ( isNull cfg . ssl ) ) cfg . ssl . paths . key ;
2024-01-05 22:47:54 +01:00
locations . " / " = {
extraConfig = ''
2024-01-22 08:38:57 +01:00
allow $ { cfg . apps . onlyoffice . localNetworkIPRange } ;
'' ;
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 = {
2024-01-29 07:54:49 +01:00
inherit ( ( nextcloudApps cfg . version ) ) previewgenerator ;
2024-01-06 00:26:56 +01:00
} ;
# 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
2024-01-06 09:03:56 +01:00
" ${ occ } ${ debug } p r e v i e w : p r e - g e n e r a t e " ;
2024-01-06 00:26:56 +01:00
} ;
} )
2024-01-06 09:03:56 +01:00
( lib . mkIf cfg . apps . ldap . enable {
systemd . services . nextcloud-setup . path = [ pkgs . jq ] ;
systemd . services . nextcloud-setup . script = ''
$ { occ } app:install user_ldap || :
$ { occ } app:enable user_ldap
# The following code tries to match an existing config or creates a new one.
# The criteria for matching is the ldapHost value.
ALL_CONFIG = " $ ( ${ occ } l d a p : s h o w - c o n f i g - - o u t p u t = j s o n - - s h o w - p a s s w o r d ) "
MATCHING_CONFIG_IDs = " $ ( e c h o " $ ALL_CONFIG " | j q ' [ t o _ e n t r i e s [ ] | s e l e c t ( . v a l u e . l d a p H o s t = = " 127 .0 .0 .1 " ) | . k e y ] ' ) "
if [ [ $ ( echo " $ M A T C H I N G _ C O N F I G _ I D s " | jq ' length' ) > 0 ] ] ; then
CONFIG_ID = " $ ( e c h o " $ MATCHING_CONFIG_IDs " | j q - - r a w - o u t p u t ' . [ 0 ] ' ) "
else
CONFIG_ID = " $ ( ${ occ } l d a p : c r e a t e - e m p t y - c o n f i g - - o n l y - p r i n t - p r e f i x ) "
fi
echo " U s i n g c o n f i g I d $ C O N F I G _ I D "
# The following CLI commands follow
# https://github.com/lldap/lldap/blob/main/example_configs/nextcloud.md#nextcloud-config--the-cli-way
$ { occ } ldap:set-config " $ C O N F I G _ I D " ' ldapHost' \
' $ { cfg . apps . ldap . host } '
$ { occ } ldap:set-config " $ C O N F I G _ I D " ' ldapPort' \
' $ { toString cfg . apps . ldap . port } '
$ { occ } ldap:set-config " $ C O N F I G _ I D " ' ldapAgentName' \
' uid = $ { cfg . apps . ldap . adminName } , ou = people , $ { cfg . apps . ldap . dcdomain } '
$ { occ } ldap:set-config " $ C O N F I G _ I D " ' ldapAgentPassword' \
" $ ( c a t ${ cfg . apps . ldap . adminPasswordFile } ) "
$ { occ } ldap:set-config " $ C O N F I G _ I D " ' ldapBase' \
' $ { cfg . apps . ldap . dcdomain } '
$ { occ } ldap:set-config " $ C O N F I G _ I D " ' ldapBaseGroups' \
' $ { cfg . apps . ldap . dcdomain } '
$ { occ } ldap:set-config " $ C O N F I G _ I D " ' ldapBaseUsers' \
' $ { cfg . apps . ldap . dcdomain } '
$ { occ } ldap:set-config " $ C O N F I G _ I D " ' ldapEmailAttribute' \
' mail'
$ { occ } ldap:set-config " $ C O N F I G _ I D " ' ldapGroupFilter' \
' ( & ( | ( objectclass = groupOfUniqueNames ) ) ( | ( cn = $ { cfg . apps . ldap . userGroup } ) ) ) '
$ { occ } ldap:set-config " $ C O N F I G _ I D " ' ldapGroupFilterGroups' \
' $ { cfg . apps . ldap . userGroup } '
$ { occ } ldap:set-config " $ C O N F I G _ I D " ' ldapGroupFilterObjectclass' \
' groupOfUniqueNames'
$ { occ } ldap:set-config " $ C O N F I G _ I D " ' ldapGroupMemberAssocAttr' \
' uniqueMember'
$ { occ } ldap:set-config " $ C O N F I G _ I D " ' ldapLoginFilter' \
' ( & ( & ( objectclass = person ) ( memberOf = cn = $ { cfg . apps . ldap . userGroup } , ou = groups , $ { cfg . apps . ldap . dcdomain } ) ) ( | ( uid = % uid ) ( | ( mail = % uid ) ( objectclass = % uid ) ) ) ) '
$ { occ } ldap:set-config " $ C O N F I G _ I D " ' ldapLoginFilterAttributes' \
' mail ; objectclass'
$ { occ } ldap:set-config " $ C O N F I G _ I D " ' ldapUserDisplayName' \
' displayname'
$ { occ } ldap:set-config " $ C O N F I G _ I D " ' ldapUserFilter' \
' ( & ( objectclass = person ) ( memberOf = cn = $ { cfg . apps . ldap . userGroup } , ou = groups , $ { cfg . apps . ldap . dcdomain } ) ) '
$ { occ } ldap:set-config " $ C O N F I G _ I D " ' ldapUserFilterMode' \
' 1 '
$ { occ } ldap:set-config " $ C O N F I G _ I D " ' ldapUserFilterObjectclass' \
' person'
$ { occ } ldap:test-config - - " $ C O N F I G _ I D "
# Only one active at the same time
for configid in $ ( echo " $ A L L _ C O N F I G " | jq - - raw-output " k e y s [ ] " ) ; do
echo " D e a c t i v a t i n g $ c o n f i g i d "
$ { occ } ldap:set-config " $ c o n f i g i d " ' ldapConfigurationActive' \
' 0 '
done
$ { occ } ldap:set-config " $ C O N F I G _ I D " ' ldapConfigurationActive' \
' 1 '
'' ;
} )
2024-01-22 08:38:57 +01:00
( lib . mkIf cfg . apps . sso . enable {
assertions = [
{
assertion = cfg . apps . sso . enable -> cfg . apps . ldap . enable ;
message = " S S O a p p r e q u i r e s L D A P a p p t o w o r k c o r r e c t l y . " ;
}
] ;
systemd . services . nextcloud-setup . script =
''
$ { occ } app:install oidc_login || :
$ { occ } app:enable oidc_login
'' ;
systemd . services . nextcloud-setup . preStart =
''
mkdir - p $ { cfg . dataDir } /config
cat < < EOF > " ${ cfg . dataDir } / c o n f i g / s e c r e t F i l e "
{
" o i d c _ l o g i n _ c l i e n t _ s e c r e t " : " $ ( c a t ${ cfg . apps . sso . secretFile } ) "
}
EOF
'' ;
services . nextcloud = {
secretFile = " ${ cfg . dataDir } / c o n f i g / s e c r e t F i l e " ;
# See all options at https://github.com/pulsejet/nextcloud-oidc-login
2024-02-07 05:42:52 +01:00
settings = {
2024-01-22 08:38:57 +01:00
allow_user_to_change_display_name = false ;
lost_password_link = " d i s a b l e d " ;
oidc_login_provider_url = ssoFqdnWithPort ;
oidc_login_client_id = cfg . apps . sso . clientID ;
# Automatically redirect the login page to the provider.
oidc_login_auto_redirect = ! cfg . apps . sso . fallbackDefaultAuth ;
# Authelia at least does not support this.
oidc_login_end_session_redirect = false ;
# Redirect to this page after logging out the user
oidc_login_logout_url = ssoFqdnWithPort ;
oidc_login_button_text = " L o g i n w i t h ${ cfg . apps . sso . provider } " ;
oidc_login_hide_password_form = false ;
oidc_login_use_id_token = true ;
oidc_login_attributes = {
id = " p r e f e r r e d _ u s e r n a m e " ;
name = " n a m e " ;
mail = " e m a i l " ;
groups = " g r o u p s " ;
} ;
oidc_login_default_group = " o i d c " ;
oidc_login_use_external_storage = false ;
oidc_login_scope = " o p e n i d p r o f i l e e m a i l g r o u p s " ;
oidc_login_proxy_ldap = false ;
# Enable creation of users new to Nextcloud from OIDC login. A user may be known to the
# IdP but not (yet) known to Nextcloud. This setting controls what to do in this case.
# * 'true' (default): if the user authenticates to the IdP but is not known to Nextcloud,
# then they will be returned to the login screen and not allowed entry;
# * 'false': if the user authenticates but is not yet known to Nextcloud, then the user
# will be automatically created; note that with this setting, you will be allowing (or
# relying on) a third-party (the IdP) to create new users
oidc_login_disable_registration = false ;
oidc_login_redir_fallback = cfg . apps . sso . fallbackDefaultAuth ;
# oidc_login_alt_login_page = "assets/login.php";
oidc_login_tls_verify = true ;
# If you get your groups from the oidc_login_attributes, you might want to create them if
2024-01-24 06:51:52 +01:00
# they are not already existing, Default is `false`. This creates groups for all groups
# the user is associated with in LDAP. It's too much.
oidc_create_groups = false ;
2024-01-22 08:38:57 +01:00
# Enable use of WebDAV via OIDC bearer token.
oidc_login_webdav_enabled = true ;
oidc_login_password_authentication = false ;
oidc_login_public_key_caching_time = 86400 ;
oidc_login_min_time_between_jwks_requests = 10 ;
oidc_login_well_known_caching_time = 86400 ;
# If true, nextcloud will download user avatars on login. This may lead to security issues
# as the server does not control which URLs will be requested. Use with care.
oidc_login_update_avatar = false ;
} ;
} ;
shb . authelia . oidcClients = lib . mkIf ( cfg . apps . sso . provider == " A u t h e l i a " ) [
{
id = cfg . apps . sso . clientID ;
description = " N e x t c l o u d " ;
secretFile = cfg . apps . sso . secretFileForAuthelia ;
public = " f a l s e " ;
authorization_policy = cfg . apps . sso . authorization_policy ;
redirect_uris = [ " ${ protocol } : / / ${ fqdnWithPort } / a p p s / o i d c _ l o g i n / o i d c " ] ;
scopes = [
" o p e n i d "
" p r o f i l e "
" e m a i l "
" g r o u p s "
] ;
userinfo_signing_algorithm = " n o n e " ;
}
] ;
} )
2024-01-05 22:47:54 +01:00
] ;
2023-06-23 06:22:34 +02:00
}