{ 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"; }; }; }; }