Files
Numbus/modules/server/services/applications/nextcloud.nix
T
2026-05-02 12:52:08 +02:00

384 lines
14 KiB
Nix

{ config, pkgs, lib, ... }:
with lib;
let
# Version tagging
nextcloudVersion = "33.0.0";
redisVersion = "8.6-alpine";
databaseVersion = "11.8";
onlyofficeVersion = "9.2";
whiteboardVersion = "v1.5.6";
# Helper
helper = import ../service-helper.nix { inherit config pkgs lib; };
cfg = config.numbus-server.services.nextcloud;
# Container config
name = "nextcloud";
in
helper.mkPodmanService {
inherit name;
description = "Nextcloud, your own online office suite";
defaultPort = "1100";
middlewares = [
"nextcloudSecureHeaders"
];
secrets = [
"nextcloud/db_name"
"nextcloud/db_username"
"nextcloud/db_password"
"nextcloud/redis_password"
"nextcloud/onlyoffice_secret"
"nextcloud/whiteboard_secret"
];
dirPermissions = [
"100032:100 ${cfg.dataDir}"
"100032:100 ${cfg.configDir}"
"100032:100 ${cfg.configDir}/web"
"100999:100 ${cfg.configDir}/redis"
"100999:100 ${cfg.configDir}/database"
"1000:100 ${cfg.configDir}/onlyoffice"
"1000:100 ${cfg.configDir}/onlyoffice/log"
"1000:100 ${cfg.configDir}/onlyoffice/cache"
"1000:100 ${cfg.configDir}/onlyoffice/data"
"1000:100 ${cfg.configDir}/onlyoffice/database"
];
# Compose file good
composeText = ''
services:
nextcloud-server:
image: docker.io/library/nextcloud:${nextcloudVersion}
container_name: nextcloud-server
hostname: nextcloud-server
networks:
nextcloud:
ipv4_address: 10.89.160.253
ports:
- "${cfg.port}:80/tcp"
volumes:
- ${cfg.configDir}/web:/var/www/html
- ${cfg.dataDir}:/mnt/ncdata
environment:
MYSQL_HOST: nextcloud-database:3306
MYSQL_DATABASE: ${config.sops.placeholder."nextcloud/db_name"}
MYSQL_USER: ${config.sops.placeholder."nextcloud/db_username"}
MYSQL_PASSWORD: ${config.sops.placeholder."nextcloud/db_password"}
REDIS_HOST_PASSWORD: ${config.sops.placeholder."nextcloud/redis_password"}
REDIS_HOST: nextcloud-redis
NEXTCLOUD_TRUSTED_DOMAINS: ${cfg.subdomain}.${config.numbus-server.services.domain}
NEXTCLOUD_DATA_DIR: /mnt/ncdata
SMTP_SECURE: tls
SMTP_HOST: ${config.numbus-server.mail.smtpServer}
SMTP_PORT: ${toString config.numbus-server.mail.smtpPort}
SMTP_NAME: ${config.numbus-server.mail.smtpUsername}
SMTP_PASSWORD: ${config.sops.placeholder.smtpPassword}
MAIL_FROM_ADDRESS: no-reply
MAIL_DOMAIN: ${config.numbus-server.services.domain}
APACHE_DISABLE_REWRITE_IP: 1
OVERWRITEPROTOCOL: https
TRUSTED_PROXIES: 10.89.160.1
NC_default_phone_region: "${config.numbus-server.language}"
NC_default_language: "${config.numbus-server.language}"
NC_default_locale: "${config.numbus-server.locale}"
NC_default_timezone: "${config.time.timeZone}"
NC_maintenance_window_start: "1"
depends_on:
- nextcloud-database
security_opt:
- no-new-privileges:true
cap_drop:
- NET_RAW
restart: unless-stopped
nextcloud-redis:
image: docker.io/library/redis:${redisVersion}
container_name: nextcloud-redis
hostname: nextcloud-redis
user: '1000:1000'
networks:
nextcloud:
ipv4_address: 10.89.160.252
volumes:
- ${cfg.configDir}/redis:/data
command: redis-server --requirepass ${config.sops.placeholder."nextcloud/redis_password"} --save 60 1 --loglevel warning
security_opt:
- no-new-privileges:true
cap_drop:
- NET_RAW
restart: unless-stopped
nextcloud-database:
image: docker.io/library/mariadb:${databaseVersion}
container_name: nextcloud-database
hostname: nextcloud-database
user: '1000:1000'
networks:
nextcloud:
ipv4_address: 10.89.160.251
volumes:
- ${cfg.configDir}/database:/var/lib/mysql
environment:
MARIADB_DATABASE: ${config.sops.placeholder."nextcloud/db_name"}
MARIADB_USER: ${config.sops.placeholder."nextcloud/db_username"}
MARIADB_PASSWORD: ${config.sops.placeholder."nextcloud/db_password"}
MARIADB_RANDOM_ROOT_PASSWORD: true
security_opt:
- no-new-privileges:true
cap_drop:
- NET_RAW
command:
- "--transaction-isolation=READ-COMMITTED"
- "--binlog-format=ROW"
restart: unless-stopped
nextcloud-onlyoffice:
image: docker.io/onlyoffice/documentserver:${onlyofficeVersion}
container_name: nextcloud-onlyoffice
hostname: nextcloud-onlyoffice
networks:
nextcloud:
ipv4_address: 10.89.160.250
ports:
- "9980:80/tcp"
volumes:
- ${cfg.configDir}/onlyoffice/log:/var/log/onlyoffice
- ${cfg.configDir}/onlyoffice/cache:/var/lib/onlyoffice
- ${cfg.configDir}/onlyoffice/data:/var/www/onlyoffice/Data
- ${cfg.configDir}/onlyoffice/database:/var/lib/postgresql
environment:
- JWT_SECRET=${config.sops.placeholder."nextcloud/onlyoffice_secret"}
- REDIS_SERVER_PASS=${config.sops.placeholder."nextcloud/redis_password"}
- REDIS_SERVER_HOST=nextcloud-redis
- REDIS_SERVER_PORT=6379
- ADMINPANEL_ENABLED=false
- EXAMPLE_ENABLED=false
- METRICS_ENABLED=false
cap_drop:
- NET_RAW
restart: unless-stopped
nextcloud-whiteboard:
image: ghcr.io/nextcloud-releases/whiteboard:${whiteboardVersion}
container_name: nextcloud-whiteboard
hostname: nextcloud-whiteboard
user: '1000:1000'
ports:
- "3002:3002/tcp"
environment:
NEXTCLOUD_URL: https://${cfg.subdomain}.${config.numbus-server.services.domain}
JWT_SECRET_KEY: ${config.sops.placeholder."nextcloud/whiteboard_secret"}
security_opt:
- no-new-privileges:true
cap_drop:
- NET_RAW
restart: unless-stopped
networks:
nextcloud:
driver: bridge
name: nextcloud
ipam:
config:
- subnet: "10.89.160.0/24"
gateway: "10.89.160.254"
'';
extraOptions = {
onlyoffice = {
subdomain = mkOption {
type = types.str;
default = "onlyoffice";
example = "onlyoffice";
description = "The subdomain that onlyoffice for nextcloud will use";
};
};
whiteboard = {
subdomain = mkOption {
type = types.str;
default = "whiteboard";
example = "whiteboard";
description = "The subdomain that whiteboard for nextcloud will use";
};
};
};
extraConfig = {
sops.templates."traefik/rules/nextcloud-onlyoffice" = {
gid = "100";
uid = "100999";
mode = "0400";
content = ''
http:
routers:
nextcloud-onlyoffice:
rule: "Host(`${cfg.onlyoffice.subdomain}.${config.numbus-server.services.domain}`)"
entrypoints:
- "websecure"
service: nextcloud-onlyoffice
tls:
certresolver: "cloudflare"
options: "secureTLS"
services:
nextcloud-onlyoffice:
loadBalancer:
servers:
- url: "http://host.containers.internal:9980"
'';
path = "/etc/traefik/rules/nextcloud-onlyoffice.yaml";
};
sops.templates."traefik/rules/nextcloud-whiteboard" = {
gid = "100";
uid = "100999";
mode = "0400";
content = ''
http:
routers:
nextcloud-whiteboard:
rule: "Host(`${cfg.whiteboard.subdomain}.${config.numbus-server.services.domain}`)"
entrypoints:
- "websecure"
service: nextcloud-whiteboard
middlewares:
- "secureHeaders"
tls:
certresolver: "cloudflare"
options: "secureTLS"
services:
nextcloud-whiteboard:
loadBalancer:
servers:
- url: "http://host.containers.internal:3002"
'';
path = "/etc/traefik/rules/nextcloud-whiteboard.yaml";
};
sops.templates."traefik/rules/nextcloud-secureHeaders" = {
gid = "100";
uid = "100999";
mode = "0400";
content = ''
http:
middlewares:
nextcloudSecureHeaders:
headers:
FrameDeny: false
CustomFrameOptionsValue: "SAMEORIGIN"
AddVaryHeader: true
BrowserXssFilter: true
ContentTypeNosniff: true
ForceSTSHeader: true
STSSeconds: 315360000
STSIncludeSubdomains: true
STSPreload: true
AccessControlAllowMethods: "GET,OPTIONS,PUT"
AccessControlAllowOriginList:
- origin-list-or-null
AccessControlMaxAge: 100
ReferrerPolicy: same-origin
PermissionsPolicy: "vibrate=()"
ContentSecurityPolicy: >-
default-src https://${cfg.onlyoffice.subdomain}.${config.numbus-server.services.domain} 'self';
script-src https://${cfg.onlyoffice.subdomain}.${config.numbus-server.services.domain} 'self' 'unsafe-inline';
style-src 'self' 'unsafe-inline';
connect-src 'self';
img-src 'self' data:;
font-src 'self' data:;
frame-src https://${cfg.onlyoffice.subdomain}.${config.numbus-server.services.domain} 'self';
frame-ancestors https://${cfg.onlyoffice.subdomain}.${config.numbus-server.services.domain} 'self';
object-src 'none';
base-uri 'self';
'';
path = "/etc/traefik/rules/nextcloud-secureHeaders";
};
systemd.services."${name}-quirk" = {
description = "Podman container quirk : ${name}";
wantedBy = [ "multi-user.target" ];
after = [ "${name}.service" "${name}-secrets.service" ];
onFailure = [ "service-failure-notify@%n.service" ];
startLimitBurst = 5;
startLimitIntervalSec = 600;
path = [ pkgs.coreutils pkgs.sudo pkgs.podman pkgs.systemd pkgs.gnugrep ];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
};
script = ''
OCC="sudo -u numbus-admin podman exec --user www-data nextcloud-server php occ"
[[ ! -e /var/lib/numbus-server/${name}/.env ]] && systemctl start ${name}-secrets.service
until [[ -e /var/lib/numbus-server/${name}/.env ]]; do
echo "Waiting for secrets generation..."
sleep 5
done
source /var/lib/numbus-server/${name}/.env
until $OCC status | grep -iq "installed: true" >/dev/null 2>&1; do
echo "Waiting for Nextcloud to be up and running..."
sleep 60
done
$OCC db:add-missing-indices
$OCC maintenance:repair --include-expensive
INSTALL_APPS_LIST=( "calendar" "contacts" "mail" "notes" "onlyoffice" "cookbook" "whiteboard" )
DISABLE_APPS_LIST=( "activity" "federation" "webhook_listeners" "photos" "recommendations" "sharebymail" "teams" "support" "richdocumentscode" )
for app in ''${INSTALL_APPS_LIST[@]}; do
if ! $OCC --no-warnings app:list | grep -iq "$app:"; then
$OCC --no-warnings app:install "$app"
fi
if $OCC --no-warnings app:list --disabled | grep -iq "$app:"; then
$OCC --no-warnings app:enable "$app"
fi
done
for app in ''${DISABLE_APPS_LIST[@]}; do
if $OCC --no-warnings app:list --enabled | grep -iq "$app:"; then
$OCC --no-warnings app:disable "$app"
fi
done
$OCC --no-warnings config:system:set onlyoffice DocumentServerInternalUrl --value="https://${cfg.onlyoffice.subdomain}.${config.numbus-server.services.domain}/"
$OCC --no-warnings config:system:set onlyoffice DocumentServerUrl --value="https://${cfg.onlyoffice.subdomain}.${config.numbus-server.services.domain}/"
$OCC --no-warnings config:system:set onlyoffice jwt_secret --value="$ONLYOFFICE_PASSWORD"
$OCC --no-warnings config:app:set whiteboard collabBackendUrl --value="https://${cfg.whiteboard.subdomain}.${config.numbus-server.services.domain}"
$OCC --no-warnings config:app:set whiteboard jwt_secret_key --value="$WHITEBOARD_PASSWORD"
if [[ ! -f /var/lib/numbus-server/${name}/croned.true ]]; then
$OCC background:cron
sudo -u numbus-admin podman exec --user www-data nextcloud-server php -f /var/www/html/cron.php
touch /var/lib/numbus-server/${name}/croned.true
fi
if [[ ! -f /var/lib/numbus-server/${name}/scanned.true ]]; then
$OCC files:scan --all
$OCC files:repair-tree
touch /var/lib/numbus-server/${name}/scanned.true
fi
'';
};
systemd.services."${name}-cron" = {
description = "Podman container crontab : ${name}";
after = [ "${name}.service" "${name}-quirk.service" ];
onFailure = [ "service-failure-notify@%n.service" ];
path = [ pkgs.sudo pkgs.podman ];
serviceConfig = {
Type = "oneshot";
ExecCondition = ''${pkgs.sudo}/bin/sudo -u numbus-admin podman exec --user www-data nextcloud-server php occ status'';
ExecStart = "${pkgs.sudo}/bin/sudo -u numbus-admin podman exec --user www-data nextcloud-server php -f /var/www/html/cron.php";
};
};
systemd.timers."${name}-cron" = {
description = "Timer for Nextcloud cron";
wantedBy = [ "timers.target" ];
timerConfig = {
OnBootSec = "5m";
OnUnitActiveSec = "5m";
Unit = "${name}-cron.service";
};
};
};
}