diff --git a/stacks/storage/.env.example b/stacks/storage/.env.example index 89dca87a..ebd7414b 100644 --- a/stacks/storage/.env.example +++ b/stacks/storage/.env.example @@ -1,20 +1,44 @@ -# Storage Stack +# ============================================================================== +# Storage Stack â P Nextcloud FPM + MinIO + Filestash +# ============================================================================== + + + +# Domain configuration DOMAIN=yourdomain.com TZ=Asia/Shanghai -# Nextcloud admin + +# --------------------------------------------------------------------------- +# Nextcloud Admin +# --------------------------------------------------------------------------- + NEXTCLOUD_ADMIN_USER=admin NEXTCLOUD_ADMIN_PASSWORD=CHANGE_ME_STRONG_PASSWORD -# Database (must match databases stack) +# ---------------------------------------------------------------------------- +# Nextcloud Database (PostgreSQL) +# ---------------------------------------------------------------------------- NEXTCLOUD_DB_USER=nextcloud -NEXTCLOUD_DB_PASSWORD=CHANGE_ME -POSTGRES_PASSWORD=CHANGE_ME -REDIS_PASSWORD=CHANGE_ME +NEXTCLOUD_DB_PASSWORD=CHANGE_ME_POSTGRES_PASSWORD + + + +# ---------------------------------------------------------------------------- +# Redis Cache Password +# ---------------------------------------------------------------------------- +REDIS_PASSWORD=CHANGE_ME_REDIS_PASSWORD -# MinIO +# ---------------------------------------------------------------------------- +# MinIO S3 Storage +# ---------------------------------------------------------------------------- MINIO_ROOT_USER=minioadmin MINIO_ROOT_PASSWORD=CHANGE_ME_MINIO_PASSWORD +MINIO_BUCKET=nextcloud +MINIO_REGION=us-east-1 -# FileBrowser -FILEBROWSER_ROOT=/data +# ---------------------------------------------------------------------------- +# Network +# --------------------------------------------------------------------------- +# The storage-network should be created: docker network create storage-network +# The proxy network should exist from base stack: docker network create proxy diff --git a/stacks/storage/docker-compose.yml b/stacks/storage/docker-compose.yml index 8dfe309d..78239653 100644 --- a/stacks/storage/docker-compose.yml +++ b/stacks/storage/docker-compose.yml @@ -1,59 +1,144 @@ +version: "3.8" + +networks: + proxy: + external: true + storage-network: + external: true + services: - nextcloud: - image: nextcloud:29.0.9-apache - container_name: nextcloud + # --------------------------------------------------------------------------- + # Nextcloud FPM with Nginx Web Server + # --------------------------------------------------------------------------- + nextcloud-php: + image: nextcloud:29.0.9-fpm-alpine + container_name: nextcloud-php restart: unless-stopped networks: - - proxy - - databases + - storage-network volumes: - nextcloud-data:/var/www/html + - nextcloud-nginx-cache:/var/cache/nginx + depends_on: + - nextcloud-db + - nextcloud-redis environment: - TZ=${TZ:-Asia/Shanghai} - NEXTCLOUD_ADMIN_USER=${NEXTCLOUD_ADMIN_USER:-admin} - - NEXTCLOUD_ADMIN_PASSWORD=${NEXTCLOUD_ADMIN_PASSWORD:-changeme} + - NEXTCLOUD_ADMIN_PASSWORD=${NEXTCLOUD_ADMIN_PASSWORD} - NEXTCLOUD_TRUSTED_DOMAINS=nextcloud.${DOMAIN} - - POSTGRES_HOST=homelab-postgres + - POSTGRES_HOST=nextcloud-db - POSTGRES_DB=nextcloud - - POSTGRES_USER=${POSTGRES_USER:-homelab} - - POSTGRES_PASSWORD=${POSTGRES_PASSWORD:-changeme} - - REDIS_HOST=homelab-redis + - POSTGRES_USER=${NEXTCLOUD_DB_USER:-nextcloud} + - POSTGRES_PASSWORD=${NEXTCLOUD_DB_PASSWORD} + - REDIS_HOST=nextcloud-redis + - REDIS_HOST_PASSWORD=${REDIS_PASSWORD} + - S3_BUCKET=${MINIO_BUCKET:-nextcloud} + - S3_REGION=${MINIO_REGION:-us-east-1} + - S3_ENDPOINT=https://s3.${DOMAIN} + - S3_USE_PATH_STYLE=true + - S3_ACCESS_KEY=${MINIO_ROOT_USER} + - S3_SECRET_KEY=${MINIO_ROOT_PASSWORD} + healthcheck: + test: [CMD-SHELL, "curl -sf http://localhost:9000/status.php || exit 1"] + interval: 30s + timeout: 10s + retries: 5 + start_period: 120s + + nextcloud-web: + image: nginx:1.27-alpine + container_name: nextcloud-web + restart: unless-stopped + networks: + - proxy + - storage-network + volumes: + - nextcloud-data:/var/www/html:ro + - ./nginx.conf:/etc/nginx/nginx.conf:ro + - nextcloud-nginx-cache:/var/cache/nginx + depends_on: + - nextcloud-php labels: - traefik.enable=true - - "traefik.http.routers.nextcloud.rule=Host(`nextcloud.${DOMAIN}`)" + - traefik.http.routers.nextcloud.rule=Host(`nextcloud.${DOMAIN}`) - traefik.http.routers.nextcloud.entrypoints=websecure - traefik.http.routers.nextcloud.tls=true - traefik.http.services.nextcloud.loadbalancer.server.port=80 - - "traefik.http.middlewares.nextcloud-dav.redirectregex.regex=https://(.*)/.well-known/(card|cal)dav" - - "traefik.http.middlewares.nextcloud-dav.redirectregex.replacement=https://$${1}/remote.php/dav/" - - traefik.http.routers.nextcloud.middlewares=nextcloud-dav + - traefik.http.middlewares.nextcloud-headers.headers.customRequestHeaders.X-Forwarded-Proto=https + - traefik.http.routers.nextcloud.middlewares=nextcloud-headers + healthcheck: + test: [CMD-SHELL, "curl -sf http://localhost:80/health || exit 1"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 15s + + # --------------------------------------------------------------------------- + # Nextcloud Database (PostgreSQL) + # --------------------------------------------------------------------------- + nextcloud-db: + image: postgres:16-alpine + container_name: nextcloud-db + restart: unless-stopped + networks: + - storage-network + volumes: + - nextcloud-db-data:/var/lib/postgresql/data + environment: + - POSTGRES_DB=nextcloud + - POSTGRES_USER=${NEXTCLOUD_DB_USER:-nextcloud} + - POSTGRES_PASSWORD=${NEXTCLOUD_DB_PASSWORD} healthcheck: - test: [CMD-SHELL, "curl -sf http://localhost:80/status.php || exit 1"] + test: [CMD-SHELL, "pg_isready -U ${NEXTCLOUD_DB_USER:-nextcloud}"] interval: 30s timeout: 10s retries: 5 - start_period: 120s + # --------------------------------------------------------------------------- + # Nextcloud Redis Cache + # --------------------------------------------------------------------------- + nextcloud-redis: + image: redis:7-alpine + container_name: nextcloud-redis + restart: unless-stopped + networks: + - storage-network + volumes: + - nextcloud-redis-data:/data + command: redis-server --requirepass ${REDIS_PASSWORD} + healthcheck: + test: [CMD-SHELL, "redis-cli -a ${REDIS_PASSWORD} ping"] + interval: 30s + timeout: 10s + retries: 5 + + # --------------------------------------------------------------------------- + # MinIO - S3-Compatible Object Storage + # --------------------------------------------------------------------------- minio: image: minio/minio:RELEASE.2024-11-07T00-52-20Z container_name: minio restart: unless-stopped networks: - proxy + - storage-network volumes: - minio-data:/data environment: - - MINIO_ROOT_USER=${MINIO_ROOT_USER:-minioadmin} - - MINIO_ROOT_PASSWORD=${MINIO_ROOT_PASSWORD:-changeme-minio} - - MINIO_BROWSER_REDIRECT_URL=https://minio.${DOMAIN} + - MINIO_ROOT_USER=${MINIO_ROOT_USER} + - MINIO_ROOT_PASSWORD=${MINIO_ROOT_PASSWORD} + - MINIO_BROWSER_REDIRECT_URL=https://minio-console.${DOMAIN} command: server /data --console-address ":9001" labels: - traefik.enable=true - - "traefik.http.routers.minio.rule=Host(`minio.${DOMAIN}`)" - - traefik.http.routers.minio.entrypoints=websecure - - traefik.http.routers.minio.tls=true - - traefik.http.services.minio.loadbalancer.server.port=9001 - - "traefik.http.routers.minio-api.rule=Host(`s3.${DOMAIN}`)" + # MinIO Console + - traefik.http.routers.minio-console.rule=Host(`minio-console.${DOMAIN}`) + - traefik.http.routers.minio-console.entrypoints=websecure + - traefik.http.routers.minio-console.tls=true + - traefik.http.services.minio-console.loadbalancer.server.port=9001 + # MinIO API/S3 + - traefik.http.routers.minio-api.rule=Host(`s3.${DOMAIN}`) - traefik.http.routers.minio-api.entrypoints=websecure - traefik.http.routers.minio-api.tls=true - traefik.http.services.minio-api.loadbalancer.server.port=9000 @@ -64,37 +149,43 @@ services: retries: 3 start_period: 30s - filebrowser: - image: filebrowser/filebrowser:v2.31.2 - container_name: filebrowser + # --------------------------------------------------------------------------- + # Filestash - Web File Browser for MinIO + # --------------------------------------------------------------------------- + filestash: + image: machines/filestash:0.9.4 + container_name: filestash restart: unless-stopped networks: - proxy + - storage-network volumes: - - filebrowser-data:/database - - ${STORAGE_PATH:-/data}:/srv + - filestash-data:/app/data/state environment: - TZ=${TZ:-Asia/Shanghai} + - HOME=/app/data + # Filestash configuration for MinIO/S3 + - EOF + # Multi-backend support: S3/MinIO + env_file: + - ./filestash.env labels: - traefik.enable=true - - "traefik.http.routers.filebrowser.rule=Host(`files.${DOMAIN}`)" - - traefik.http.routers.filebrowser.entrypoints=websecure - - traefik.http.routers.filebrowser.tls=true - - traefik.http.services.filebrowser.loadbalancer.server.port=80 + - traefik.http.routers.filestash.rule=Host(`files.${DOMAIN}`) + - traefik.http.routers.filestash.entrypoints=websecure + - traefik.http.routers.filestash.tls=true + - traefik.http.services.filestash.loadbalancer.server.port=8334 healthcheck: - test: [CMD-SHELL, "curl -sf http://localhost:80/ || exit 1"] + test: [CMD-SHELL, "curl -sf http://localhost:8334/health || exit 1"] interval: 30s timeout: 10s retries: 3 start_period: 15s -networks: - proxy: - external: true - databases: - external: true - volumes: nextcloud-data: + nextcloud-nginx-cache: + nextcloud-db-data: + nextcloud-redis-data: minio-data: - filebrowser-data: + filestash-data: diff --git a/stacks/storage/filestash.env b/stacks/storage/filestash.env new file mode 100644 index 00000000..b1dbe4d2 --- /dev/null +++ b/stacks/storage/filestash.env @@ -0,0 +1,33 @@ +# Filestash Configuration +# Documentation: https://www.filestash.app/docs/ + +# Application settings +[FENOM] +allowed_env = [TZ] + +# S3/MinIO Backend +[backend] +s3 = { + endpoint = "https://s3.${DOMAIN}", + bucket = "${MINIO_BUCKET}", + region = "${MINIO_REGION}", + access_key_id = "${MINIO_ROOT_USER}", + secret_access_key = "${MINIO_ROOT_PASSWORD}", + force_path_style = true, +} + +# Disable other backends for security +[backend/dropbox] +enabled = false + +[backend/ftp] +enabled = false + +[backend/sftp] +enabled = false + +[backend/webdav] +enabled = false + +[backend/s3] +enabled = true diff --git a/stacks/storage/nginx.conf b/stacks/storage/nginx.conf new file mode 100644 index 00000000..dd78c9c5 --- /dev/null +++ b/stacks/storage/nginx.conf @@ -0,0 +1,96 @@ +worker_processes auto; +error_log /var/log/nginx/error.log warn; +pid /var/run/nginx.pid; + +events { + worker_connections 1024; +} + +http { + 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; + tcp_nopush on; + tcp_nodelay on; + keepalive_timeout 65; + types_hash_max_size 2048; + + # Nextcloud specific settings + client_max_body_size 10G; + client_body_buffer_size 512k; + proxy_buffer_size 128k; + proxy_buffers 4 256k; + proxy_busy_buffers_size 256k; + + upstream php-backend { + server nextcloud-php:9000; + } + + server { + listen 80; + server_name _; + return 301 https://$host$request_uri; + } + + server { + listen 443 ssl http2; + server_name _; + + ssl_certificate /etc/nginx/ssl/cert.pem; + ssl_certificate_key /etc/nginx/ssl/key.pem; + ssl_protocols TLSv1.2 TLSv1.3; + ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDDE-ECDSA-AES256-GCM-SHA384:ECDDE-RSA-AES256-GCM-SHA384; + ssl_prefer_server_ciphers off; + ssl_session_cache shared:SSL:10m; + + root /var/www/html; + index index.php index.html /index.php; + + location = /health { + access_log off; + return 200 "OK\n"; + add_header Content-Type text/plain; + } + + location / { + try_files $uri $uri/ /index.php?$query_string; + } + + location ~ ^/status\.php$ { + fastcgi_pass php-backend; + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + include fastcgi_params; + fastcgi_index index.php; + fastcgi_param SCRIPT_NAME /status.php; + } + + location ~ \.php$ { + try_files /$uri =404; + fastcgi_pass php-backend; + fastcgi_index index.php; + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + include fastcgi_params; + fastcgi_param PATH_INFO $fastcgi_path_info; + fastcgi_param PATH_TRANSLATED $document_root$fastcgi_path_info; + } + + location ~ /\.ht { + deny all; + } + + location /.well-known/carddav { + return 301 $scheme://$host/remote.php/dav; + } + + location /.well-known/caldav { + return 301 $scheme://$host/remote.php/dav; + } + } +}