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 } " ;
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-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-18 07:52:34 +01:00
onlyoffice = lib . mkOption {
description = " I f n o n n u l l , s e t u p a n O n l y O f f i c e s e r v i c e . " ;
default = null ;
type = lib . types . nullOr ( lib . types . submodule {
options = {
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 . " ;
example = " 1 9 2 . 1 6 8 . 1 . 1 / 2 4 " ;
} ;
2023-12-23 08:16:53 +01:00
jwtSecretFile = lib . mkOption {
type = lib . types . path ;
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 . " ;
} ;
2023-12-18 07:52:34 +01:00
} ;
} ) ;
2023-08-28 07:20:59 +02:00
} ;
2023-09-13 05:52:24 +02:00
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 " ;
}
'' ;
} ;
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
} ;
2023-12-18 07:52:34 +01:00
config = lib . mkMerge [ ( lib . mkIf cfg . enable {
2023-06-23 06:22:34 +02: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 ;
} ;
} ;
users . groups = {
nextcloud = {
members = [ " b a c k u p " ] ;
} ;
} ;
2023-08-10 05:40:38 +02: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
2023-08-12 08:30:04 +02:00
#
# 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.
2023-06-23 06:22:34 +02:00
services . nextcloud = {
enable = true ;
2023-09-27 08:34:24 +02:00
package = pkgs . nextcloud27 ;
2023-06-23 06:22:34 +02:00
2023-12-18 08:06:12 +01:00
datadir = cfg . dataDir ;
2023-07-16 00:09:54 +02:00
hostName = fqdn ;
2023-08-12 06:35:51 +02:00
nginx . hstsMaxAge = 31536000 ; # Needs > 1 year for https://hstspreload.org to be happy
2023-06-23 06:22:34 +02:00
config = {
dbtype = " p g s q l " ;
adminuser = " r o o t " ;
2023-12-23 08:16:53 +01:00
adminpassFile = cfg . adminPassFile ;
2023-06-23 06:22:34 +02:00
# 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 ;
2023-08-12 06:35:27 +02:00
# 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
2023-12-23 08:16:53 +01:00
https = config . shb . ssl . enable ;
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
2023-08-12 01:38:13 +02:00
" t r u s t e d _ d o m a i n s " = [ fqdn ] ;
2023-12-23 08:16:53 +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
2023-08-12 01:38:13 +02:00
" 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.
2023-11-24 11:16:36 +01:00
" 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
} ;
phpOptions = {
# The OPcache interned strings buffer is nearly full with 8, bump to 16.
2023-07-30 07:13:09 +02:00
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 " ;
2023-06-23 06:22:34 +02:00
" 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 " ;
2023-07-30 07:13:09 +02:00
" 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 " ;
# 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 " ;
2023-12-23 08:16:53 +01:00
} // lib . optionalAttrs ( ! ( isNull cfg . tracing ) ) {
2023-09-13 05:52:24 +02:00
# "xdebug.remote_enable" = "on";
# "xdebug.remote_host" = "127.0.0.1";
# "xdebug.remote_port" = "9000";
# "xdebug.remote_handler" = "dbgp";
2023-12-23 08:16:53 +01:00
" x d e b u g . t r i g g e r _ v a l u e " = cfg . tracing ;
2023-09-13 05:52:24 +02:00
2023-12-18 07:54:19 +01:00
" x d e b u g . m o d e " = " p r o f i l e , t r a c e " ;
2023-09-13 05:52:24 +02:00
" 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-06-23 06:22:34 +02:00
} ;
2023-09-13 05:52:24 +02:00
2023-12-23 08:16:53 +01:00
poolSettings = lib . mkIf ( ! ( isNull cfg . phpFpmPoolSettings ) ) cfg . phpFpmPoolSettings ;
2023-12-18 08:06:12 +01:00
2023-09-13 05:52:24 +02:00
phpExtraExtensions = all : [ all . xdebug ] ;
2023-06-23 06:22:34 +02:00
} ;
2023-07-16 00:09:54 +02:00
services . nginx . virtualHosts . ${ fqdn } = {
# listen = [ { addr = "0.0.0.0"; port = 443; } ];
2023-12-23 08:16:53 +01:00
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 ;
2023-06-23 06:22:34 +02:00
} ;
2023-10-01 00:49:55 +02: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
# Needed for the recognize app.
pkgs . nodejs
] ;
2023-09-27 22:28:10 +02:00
2023-12-23 08:16:53 +01:00
services . postgresql . settings = lib . mkIf ( ! ( isNull cfg . postgresSettings ) ) cfg . postgresSettings ;
2023-09-27 22:36:29 +02:00
2023-06-23 06:22:34 +02: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 " ;
} ;
2023-09-13 05:52:24 +02:00
systemd . services . phpfpm-nextcloud . preStart = ''
mkdir - p /var/log/xdebug ; chown - R nextcloud : /var/log/xdebug
'' ;
2023-06-23 06:22:34 +02:00
2023-10-28 09:10:50 +02:00
systemd . services . nextcloud-cron . path = [
pkgs . perl
] ;
2023-06-23 06:22:34 +02:00
# Sets up backup for Nextcloud.
shb . backup . instances . nextcloud = {
sourceDirectories = [
2023-12-18 08:06:12 +01:00
cfg . dataDir
2023-06-23 06:22:34 +02:00
] ;
2023-07-22 18:54:19 +02:00
excludePatterns = [ " . r n d " ] ;
2023-06-23 06:22:34 +02:00
} ;
2023-12-18 07:52:34 +01:00
} ) ( lib . mkIf ( ! ( isNull cfg . onlyoffice ) ) {
services . onlyoffice = {
enable = true ;
hostname = " ${ cfg . onlyoffice . subdomain } . ${ cfg . domain } " ;
port = 13444 ;
postgresHost = " / r u n / p o s t g r e s q l " ;
2023-12-23 08:16:53 +01:00
jwtSecretFile = cfg . onlyoffice . jwtSecretFile ;
2023-12-18 07:52:34 +01:00
} ;
services . nginx . virtualHosts . " ${ cfg . onlyoffice . subdomain } . ${ cfg . domain } " = {
2023-12-23 08:16:53 +01:00
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 ;
2023-12-18 07:52:34 +01:00
locations . " / " = {
extraConfig = ''
allow $ { cfg . onlyoffice . localNetworkIPRange } ;
'' ;
} ;
} ;
} ) ] ;
2023-06-23 06:22:34 +02:00
}