From 8a14794df5e51d6841a2166f9a2f5d1518394e9b Mon Sep 17 00:00:00 2001 From: Nicolas MASSE Date: Wed, 3 Dec 2025 17:03:32 +0100 Subject: [PATCH] add nextcloud --- nextcloud/Makefile | 21 ++++ nextcloud/config/config.env | 47 ++++++++ nextcloud/config/nginx.conf | 163 ++++++++++++++++++++++++++++ nextcloud/config/redis-session.ini | 0 nextcloud/config/redis.conf | 3 + nextcloud/config/www.conf | 9 ++ nextcloud/cookies.txt | 8 ++ nextcloud/nextcloud-app.container | 62 +++++++++++ nextcloud/nextcloud-nginx.container | 42 +++++++ nextcloud/nextcloud-redis.container | 47 ++++++++ nextcloud/nextcloud.target | 13 +++ nextcloud/tests/witness.txt | 1 + 12 files changed, 416 insertions(+) create mode 100644 nextcloud/Makefile create mode 100644 nextcloud/config/config.env create mode 100644 nextcloud/config/nginx.conf create mode 100644 nextcloud/config/redis-session.ini create mode 100644 nextcloud/config/redis.conf create mode 100644 nextcloud/config/www.conf create mode 100644 nextcloud/cookies.txt create mode 100644 nextcloud/nextcloud-app.container create mode 100644 nextcloud/nextcloud-nginx.container create mode 100644 nextcloud/nextcloud-redis.container create mode 100644 nextcloud/nextcloud.target create mode 100644 nextcloud/tests/witness.txt diff --git a/nextcloud/Makefile b/nextcloud/Makefile new file mode 100644 index 0000000..074d4a1 --- /dev/null +++ b/nextcloud/Makefile @@ -0,0 +1,21 @@ +PARENT_DIR := .. +include $(PARENT_DIR)/Makefile + +.PHONY: test + +test: uninstall clean install + @run() { echo $$*; "$$@"; }; \ + echo "Running Nextcloud tests..."; \ + set -Eeuo pipefail; \ + source config/config.env; \ + echo "Uploading file..."; \ + run curl -X PUT -sSf -u "$${NEXTCLOUD_ADMIN_USER}:$${NEXTCLOUD_ADMIN_PASSWORD}" --data-binary @tests/witness.txt "$${OVERWRITECLIURL}/remote.php/webdav/witness.txt" -b cookies.txt; \ + echo "Verifying file upload..."; \ + run curl -X GET -sSf -u "$${NEXTCLOUD_ADMIN_USER}:$${NEXTCLOUD_ADMIN_PASSWORD}" "$${OVERWRITECLIURL}/remote.php/webdav/witness.txt" -o /tmp/witness.txt -b cookies.txt; \ + if run cmp -s tests/witness.txt /tmp/witness.txt ; then \ + echo "File upload verified successfully!"; \ + else \ + echo "File upload verification failed!"; \ + exit 1; \ + fi + diff --git a/nextcloud/config/config.env b/nextcloud/config/config.env new file mode 100644 index 0000000..0ccbdd8 --- /dev/null +++ b/nextcloud/config/config.env @@ -0,0 +1,47 @@ +## +## Nextcloud Configuration Environment Variables +## + +# Nextcloud domain configuration +NEXTCLOUD_TRUSTED_DOMAINS=localhost +OVERWRITEHOST=localhost +OVERWRITEPROTOCOL=http +OVERWRITECLIURL=http://localhost + +# Nextcloud admin credentials +NEXTCLOUD_ADMIN_USER=admin +NEXTCLOUD_ADMIN_PASSWORD=nextcloud + +# Nextcloud server info token +NEXTCLOUD_SERVERINFO_TOKEN=S3cr3t! + +# SMTP configuration +#SMTP_HOST=smtp.gmail.com +#SMTP_NAME=bogus +#SMTP_PASSWORD=REDACTED +#SMTP_SECURE=tls +#SMTP_PORT=587 +#SMTP_AUTHTYPE=LOGIN +#MAIL_FROM_ADDRESS=user@itix.fr +#MAIL_DOMAIN=itix.fr + +# Database configuration +POSTGRES_HOST=localhost +POSTGRES_DB=nextcloud +POSTGRES_USER=nextcloud +POSTGRES_PASSWORD=nextcloud + +# Redis configuration +REDIS_HOST=localhost +REDIS_HOST_PORT=6379 +REDIS_HOST_PASSWORD=nextcloud + +# PHP configuration +PHP_MEMORY_LIMIT=512M +PHP_UPLOAD_LIMIT=10G + +# Nextcloud configuration +NEXTCLOUD_UPDATE=0 +NEXTCLOUD_TABLE_PREFIX= +NEXTCLOUD_DATA_DIR=/var/www/html/data +NEXTCLOUD_INIT_HTACCESS=1 diff --git a/nextcloud/config/nginx.conf b/nextcloud/config/nginx.conf new file mode 100644 index 0000000..4034c3f --- /dev/null +++ b/nextcloud/config/nginx.conf @@ -0,0 +1,163 @@ +worker_processes auto; +error_log stderr warn; + +# Running Nginx as an unprivileged container +pid /tmp/nginx.pid; + +events { + worker_connections 1024; +} + +http { + # Running Nginx as an unprivileged container + proxy_temp_path /tmp/proxy_temp; + client_body_temp_path /tmp/client_temp; + fastcgi_temp_path /tmp/fastcgi_temp; + uwsgi_temp_path /tmp/uwsgi_temp; + scgi_temp_path /tmp/scgi_temp; + + include /etc/nginx/mime.types; + default_type application/octet-stream; + + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for"'; + + access_log /var/log/nginx/access.log main; + + sendfile on; + keepalive_timeout 65; + + # Do not leak server version in HTTP headers + server_tokens off; + + set_real_ip_from 10.0.0.0/8; + set_real_ip_from 172.16.0.0/12; + set_real_ip_from 192.168.0.0/16; + real_ip_header X-Real-IP; + + upstream php-handler { + server 127.0.0.1:9000; + } + + server { + listen 80; + + # set max upload size + client_max_body_size 10G; + fastcgi_buffers 64 4K; + + # Enable gzip but do not remove ETag headers + gzip on; + gzip_vary on; + gzip_comp_level 4; + gzip_min_length 256; + gzip_proxied expired no-cache no-store private no_last_modified no_etag auth; + gzip_types application/atom+xml application/javascript application/json application/ld+json application/manifest+json application/rss+xml application/vnd.geo+json application/vnd.ms-fontobject application/x-font-ttf application/x-web-app-manifest+json application/xhtml+xml application/xml font/opentype image/bmp image/svg+xml image/x-icon text/cache-manifest text/css text/plain text/vcard text/vnd.rim.location.xloc text/vtt text/x-component text/x-cross-domain-policy; + + # HTTP response headers borrowed from Nextcloud `.htaccess` + add_header Referrer-Policy "no-referrer" always; + add_header X-Content-Type-Options "nosniff" always; + add_header X-Download-Options "noopen" always; + add_header X-Frame-Options "SAMEORIGIN" always; + add_header X-Permitted-Cross-Domain-Policies "none" always; + add_header X-Robots-Tag "none" always; + add_header X-XSS-Protection "1; mode=block" always; + + # Remove X-Powered-By, which is an information leak + fastcgi_hide_header X-Powered-By; + + # Path to the root of your installation + root /var/www/html; + + # Specify how to handle directories -- specifying `/index.php$request_uri` + # here as the fallback means that Nginx always exhibits the desired behaviour + # when a client requests a path that corresponds to a directory that exists + # on the server. In particular, if that directory contains an index.php file, + # that file is correctly served; if it doesn't, then the request is passed to + # the front-end controller. This consistent behaviour means that we don't need + # to specify custom rules for certain paths (e.g. images and other assets, + # `/updater`, `/ocm-provider`, `/ocs-provider`), and thus + # `try_files $uri $uri/ /index.php$request_uri` + # always provides the desired behaviour. + index index.php index.html /index.php$request_uri; + + # Do not include the hostname and scheme in the redirect URL since it is + # always wrong in a Kubernetes environment (request received on HTTPS by Traefik + # and transmitted on HTTP internally). + absolute_redirect off; + + # Rule borrowed from `.htaccess` to handle Microsoft DAV clients + location = / { + if ( $http_user_agent ~ ^DavClnt ) { + return 302 /remote.php/webdav/$is_args$args; + } + } + + location = /robots.txt { + allow all; + log_not_found off; + access_log off; + } + + # Make a regex exception for `/.well-known` so that clients can still + # access it despite the existence of the regex rule + # `location ~ /(\.|autotest|...)` which would otherwise handle requests + # for `/.well-known`. + location ^~ /.well-known { + # The following 6 rules are borrowed from `.htaccess` + + location = /.well-known/carddav { return 301 /remote.php/dav/; } + location = /.well-known/caldav { return 301 /remote.php/dav/; } + # Anything else is dynamically handled by Nextcloud + location ^~ /.well-known { return 301 /index.php$uri; } + + try_files $uri $uri/ =404; + } + + # Rules borrowed from `.htaccess` to hide certain paths from clients + location ~ ^/(?:build|tests|config|lib|3rdparty|templates|data)(?:$|/) { return 404; } + location ~ ^/(?:\.|autotest|occ|issue|indie|db_|console) { return 404; } + + # Ensure this block, which passes PHP files to the PHP process, is above the blocks + # which handle static assets (as seen below). If this block is not declared first, + # then Nginx will encounter an infinite rewriting loop when it prepends `/index.php` + # to the URI, resulting in a HTTP 500 error response. + location ~ \.php(?:$|/) { + fastcgi_split_path_info ^(.+?\.php)(/.*)$; + set $path_info $fastcgi_path_info; + + try_files $fastcgi_script_name =404; + + include fastcgi_params; + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + fastcgi_param PATH_INFO $path_info; + fastcgi_param HTTPS on; + + fastcgi_param modHeadersAvailable true; # Avoid sending the security headers twice + fastcgi_param front_controller_active true; # Enable pretty urls + fastcgi_pass php-handler; + + fastcgi_intercept_errors on; + fastcgi_request_buffering off; + } + + location ~ \.(?:css|js|svg|gif)$ { + try_files $uri /index.php$request_uri; + expires 6M; # Cache-Control policy borrowed from `.htaccess` + access_log off; # Optional: Don't log access to assets + } + + location ~ \.woff2?$ { + try_files $uri /index.php$request_uri; + expires 7d; # Cache-Control policy borrowed from `.htaccess` + access_log off; # Optional: Don't log access to assets + } + + location / { + try_files $uri $uri/ /index.php$request_uri; + # Optional: Don't log access to other assets + access_log off; + } + } +} \ No newline at end of file diff --git a/nextcloud/config/redis-session.ini b/nextcloud/config/redis-session.ini new file mode 100644 index 0000000..e69de29 diff --git a/nextcloud/config/redis.conf b/nextcloud/config/redis.conf new file mode 100644 index 0000000..b64d2b2 --- /dev/null +++ b/nextcloud/config/redis.conf @@ -0,0 +1,3 @@ +requirepass nextcloud +port 6379 +bind 127.0.0.1 diff --git a/nextcloud/config/www.conf b/nextcloud/config/www.conf new file mode 100644 index 0000000..49000db --- /dev/null +++ b/nextcloud/config/www.conf @@ -0,0 +1,9 @@ +[www] +listen = 127.0.0.1:9000 + +; As recommended here: https://docs.nextcloud.com/server/15/admin_manual/installation/server_tuning.html +pm = dynamic +pm.max_children = 16 +pm.start_servers = 4 +pm.min_spare_servers = 2 +pm.max_spare_servers = 8 diff --git a/nextcloud/cookies.txt b/nextcloud/cookies.txt new file mode 100644 index 0000000..f06db3d --- /dev/null +++ b/nextcloud/cookies.txt @@ -0,0 +1,8 @@ +# Netscape HTTP Cookie File +# https://curl.se/docs/http-cookies.html +# This file was generated by libcurl! Edit at your own risk. + +#HttpOnly_localhost FALSE / FALSE 0 ocbny94p8hh4 02320e51bee9bd3808c761acdb10e820 +#HttpOnly_localhost FALSE / FALSE 4133980799 nc_sameSiteCookiestrict true +#HttpOnly_localhost FALSE / FALSE 4133980799 nc_sameSiteCookielax true +#HttpOnly_localhost FALSE / FALSE 0 oc_sessionPassphrase 5xu%2F9mJaZB0HhAXmLErL8y5pNa5Rb6fr%2F%2BVJL%2FfnkKKsBVozLv2TpWQ2Khd%2FtT%2BSX0sR1VQS0pCql0CzOOd%2BTHU04gEnJ0PgYMV%2FRQVO3YJpWE5c5THOJ4eN2GLg7C0P diff --git a/nextcloud/nextcloud-app.container b/nextcloud/nextcloud-app.container new file mode 100644 index 0000000..190da84 --- /dev/null +++ b/nextcloud/nextcloud-app.container @@ -0,0 +1,62 @@ +[Unit] +Description=Nextcloud PHP-FPM Application +Documentation=https://hub.docker.com/_/nextcloud/ +After=network.target nextcloud-redis.service postgresql-server.service +Requires=nextcloud-redis.service postgresql-server.service + +# Require initialization to complete first +Requires=nextcloud-redis.service +After=nextcloud-redis.service + +# Only start if Nextcloud has been configured +ConditionPathExists=/etc/quadlets/nextcloud/config.env + +# Start/stop this unit when the target is started/stopped +PartOf=nextcloud.target + +[Container] +ContainerName=nextcloud-app +Image=docker.io/library/nextcloud:31-fpm-alpine + +# Fix the UID/GID of the PHP-FPM daemon +User=82:82 + +# Network configuration +Network=host +AddCapability=CAP_NET_BIND_SERVICE + +# Environment variables from secrets and config +EnvironmentFile=/etc/quadlets/nextcloud/config.env + +# Volume mounts +Volume=/var/lib/quadlets/nextcloud/data:/var/www/html:z +Volume=/var/lib/quadlets/nextcloud/config/www.conf:/usr/local/etc/php-fpm.d/www.conf:Z +Volume=/var/lib/quadlets/nextcloud/config/redis-session.ini:/usr/local/etc/php/conf.d/redis-session.ini:Z + +# Health check (equivalent to readiness probe) +HealthCmd=nc -z localhost 9000 +HealthInterval=30s +HealthTimeout=10s +HealthStartPeriod=60s +HealthRetries=3 + +[Service] +Restart=always +RestartSec=10 +TimeoutStartSec=600 +TimeoutStopSec=30 + +# Skaffold filesystem + fix permissions +ExecStartPre=/bin/bash -Eeuo pipefail -c 'install -m 0755 -o 0 -g 0 -d /var/lib/quadlets/nextcloud ; \ + install -m 0700 -o 82 -g 82 -d /var/lib/quadlets/nextcloud/data /var/lib/quadlets/nextcloud/config ; \ + install -m 0700 -o 82 -g 82 /etc/quadlets/nextcloud/www.conf /var/lib/quadlets/nextcloud/config/www.conf ; \ + install -m 0700 -o 82 -g 82 /etc/quadlets/nextcloud/redis-session.ini /var/lib/quadlets/nextcloud/config/redis-session.ini' + +# Wait for PostgreSQL to be ready on localhost +ExecStartPre=/bin/sh -c 'exec 2>/dev/null; for try in $(seq 0 12); do if ! /bin/true 5<> /dev/tcp/127.0.0.1/5432; then echo "Waiting for PostgreSQL to be available..."; sleep 5; else exit 0; fi; done; exit 1' + +# Wait for Redis to be ready on localhost +ExecStartPre=/bin/sh -c 'exec 2>/dev/null; for try in $(seq 0 12); do if ! /bin/true 5<> /dev/tcp/127.0.0.1/6379; then echo "Waiting for Redis to be available..."; sleep 5; else exit 0; fi; done; exit 1' + +[Install] +WantedBy=nextcloud.target diff --git a/nextcloud/nextcloud-nginx.container b/nextcloud/nextcloud-nginx.container new file mode 100644 index 0000000..1153634 --- /dev/null +++ b/nextcloud/nextcloud-nginx.container @@ -0,0 +1,42 @@ +[Unit] +Description=Nextcloud Nginx Reverse Proxy +Documentation=https://hub.docker.com/r/nginxinc/nginx-unprivileged/ +After=network.target nextcloud-app.service +Requires=nextcloud-app.service + +# Only start if Nextcloud has been configured +ConditionPathExists=/etc/quadlets/nextcloud/config.env + +# Start/stop this unit when the target is started/stopped +PartOf=nextcloud.target + +[Container] +ContainerName=nextcloud-nginx +Image=docker.io/nginxinc/nginx-unprivileged:1.20-alpine + +# Network configuration +Network=host +AddCapability=CAP_NET_BIND_SERVICE + +# Run with the same UID/GID as PHP-FPM +User=82:82 + +# Volume mounts +Volume=/var/lib/quadlets/nextcloud/data:/var/www/html:z +Volume=/etc/quadlets/nextcloud/nginx.conf:/etc/nginx/nginx.conf:ro + +# Health check (equivalent to readiness probe) +HealthCmd=curl -sSfL http://localhost/status.php +HealthInterval=30s +HealthTimeout=10s +HealthStartPeriod=30s +HealthRetries=3 + +[Service] +Restart=always +RestartSec=5 +TimeoutStartSec=300 +TimeoutStopSec=30 + +[Install] +WantedBy=nextcloud.target diff --git a/nextcloud/nextcloud-redis.container b/nextcloud/nextcloud-redis.container new file mode 100644 index 0000000..2b8fb8f --- /dev/null +++ b/nextcloud/nextcloud-redis.container @@ -0,0 +1,47 @@ +[Unit] +Description=Redis Cache for Nextcloud +Documentation=https://redis.io/ +After=network.target + +# Only start if Nextcloud has been configured +ConditionPathExists=/etc/quadlets/nextcloud/config.env + +# Start/stop this unit when the target is started/stopped +PartOf=nextcloud.target + +[Container] +ContainerName=nextcloud-redis +Image=docker.io/library/redis:8-alpine + +# Network configuration +Network=host + +# Redis configuration with authentication +Exec=redis-server /usr/local/etc/redis/redis.conf + +# Environment variables +Environment=REDISCLI_AUTH=${REDIS_HOST_PASSWORD} + +# Volume mounts for data persistence +Volume=/var/lib/quadlets/nextcloud/redis:/data:Z +Volume=/etc/quadlets/nextcloud/redis.conf:/usr/local/etc/redis/redis.conf:ro + +# Health check +HealthCmd=redis-cli ping -t 5 | grep -q PONG +HealthInterval=30s +HealthTimeout=5s +HealthStartPeriod=10s +HealthRetries=3 + +[Service] +Restart=always +RestartSec=5 +TimeoutStartSec=300 +TimeoutStopSec=30 + +# Skaffold filesystem + fix permissions +ExecStartPre=/bin/bash -Eeuo pipefail -c 'install -m 0755 -o 0 -g 0 -d /var/lib/quadlets/nextcloud ; \ + install -m 0700 -o 0 -g 0 -d /var/lib/quadlets/nextcloud/redis' + +[Install] +WantedBy=nextcloud.target \ No newline at end of file diff --git a/nextcloud/nextcloud.target b/nextcloud/nextcloud.target new file mode 100644 index 0000000..bc11488 --- /dev/null +++ b/nextcloud/nextcloud.target @@ -0,0 +1,13 @@ +[Unit] +Description=Nextcloud Service Target +Documentation=man:systemd.target(5) +Requires=postgresql.target nextcloud-redis.service nextcloud-app.service nextcloud-nginx.service +After=postgresql.target nextcloud-redis.service nextcloud-app.service nextcloud-nginx.service + +# Allow isolation - can stop/start this target independently +AllowIsolate=yes +# Only start if Nextcloud has been configured +ConditionPathExists=/etc/quadlets/nextcloud/config.env + +[Install] +WantedBy=multi-user.target diff --git a/nextcloud/tests/witness.txt b/nextcloud/tests/witness.txt new file mode 100644 index 0000000..da15b16 --- /dev/null +++ b/nextcloud/tests/witness.txt @@ -0,0 +1 @@ +I'm a test file!