Upgrade to drive v0.25.3, replace MinIO with RustFS

- use rustfs instead of minio and provide migration script
- Schedule clean_pending_items/purge_deleted_items with celery beat
- Give celery workers the S3 access key secret
- Collabora admin access needs setting a password

Following the [C4DT AI Policy](https://git.coopcloud.tech/c4dt/ai-policy/src/branch/main/AI-POLICY.md):

 #LLM_vibe
 #LLM_model Claude Opus 5.5

- [x] I take full responsibility for this code
- [x] I understand every line of this code
- [x] I tested this code thoroughly on my own computer / servers
- [x] None of the commit messages were created using LLMs
- [x] I will participate in any discussion on my own and will not use an LLM for the discussion

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Linus Gasser
2026-11-12 16:07:05 +00:00
committed by Linus Gasser
co-authored by Claude Opus 5.5
parent a09d912312
commit e7aaf18d2d
12 changed files with 399 additions and 125 deletions
+65 -66
View File
@@ -34,10 +34,10 @@ x-common-env: &common-env
DRIVE_BASE_URL: "https://${DOMAIN}"
# Media
STORAGES_STATICFILES_BACKEND: django.contrib.staticfiles.storage.StaticFilesStorage
AWS_S3_ENDPOINT_URL: http://minio:9000
AWS_S3_DOMAIN_REPLACE: https://${MINIO_DOMAIN}
# AWS_S3_ACCESS_KEY_ID supplied via secret (this is same MINIO_ROOT_USER)
# AWS_S3_SECRET_ACCESS_KEY supplied via secret (this is same as MINIO_ROOT_PASSWORD)
AWS_S3_ENDPOINT_URL: http://rustfs:9000
AWS_S3_DOMAIN_REPLACE: https://${RUSTFS_DOMAIN}
# AWS_S3_ACCESS_KEY_ID supplied via secret (this is same as RUSTFS_ACCESS_KEY)
# AWS_S3_SECRET_ACCESS_KEY supplied via secret (this is same as RUSTFS_SECRET_KEY)
MEDIA_BASE_URL: https://${DOMAIN}
AWS_STORAGE_BUCKET_NAME: drive-media-storage
# OIDC - settings from .env, see .env.sample
@@ -63,6 +63,8 @@ x-common-env: &common-env
AI_MODEL: llama
# Collaboration
COLLABORATION_API_URL: https://$DOMAIN/collaboration/api/
# SDK picker pages (/sdk) and SDK relay API, used by other La Suite apps
SDK_ENABLED: ${SDK_ENABLED:-false}
# WOPI
WOPI_CLIENTS: "collabora,onlyoffice"
@@ -87,9 +89,10 @@ x-postgres-env: &postgres-env
DB_PORT: 5432
# DB_PASSWORD supplied via secrets (this is same as POSTGRES_PASSWORD)
x-minio-env: &minio-env
MINIO_ROOT_USER_FILE: /run/secrets/minio_ru
MINIO_ROOT_PASSWORD_FILE: /run/secrets/minio_rp
x-rustfs-env: &rustfs-env
RUSTFS_ACCESS_KEY_FILE: /run/secrets/rustfs_ru
RUSTFS_SECRET_KEY_FILE: /run/secrets/rustfs_rp
RUSTFS_CONSOLE_ENABLE: "false"
services:
@@ -119,7 +122,8 @@ services:
image: lasuite/drive-backend:v0.25.3
command: [ "gunicorn", "-c", "/usr/local/etc/gunicorn/drive.py", "drive.wsgi:application" ]
entrypoint: >
sh -c "if [ \"$$AUTO_MIGRATIONS\" = \"true\" ]; then /migrate.sh; fi && exec /abra-entrypoint.sh /usr/local/bin/entrypoint \"$$@\"" --
sh -c "/s3-initialize.sh || echo '⚠ s3-initialize failed, run: abra app cmd <app> backend s3_initialize' ;
if [ \"$$AUTO_MIGRATIONS\" = \"true\" ]; then /migrate.sh; fi && exec /abra-entrypoint.sh /usr/local/bin/entrypoint \"$$@\"" --
environment:
<<: [ *common-env, *postgres-env ]
AUTO_MIGRATIONS: "${AUTO_MIGRATIONS:-true}"
@@ -134,12 +138,15 @@ services:
- source: migrate
target: /migrate.sh
mode: 0555
- source: s3_initialize
target: /s3-initialize.sh
mode: 0555
secrets:
- django_sk
- django_sp
- oidc_rpcs
- minio_rp
- minio_ru
- rustfs_rp
- rustfs_ru
- postgres_p
- email_pass
@@ -161,7 +168,8 @@ services:
- django_sk
- django_sp
- oidc_rpcs
- minio_rp
- rustfs_rp
- rustfs_ru
- postgres_p
- email_pass
@@ -174,6 +182,9 @@ services:
entrypoint: ["/abra-entrypoint.sh", "/usr/local/bin/entrypoint"]
environment:
<<: [*common-env, *postgres-env]
# Maintenance commands scheduled by celery beat: crontab expression followed by command arguments
CLEAN_PENDING_ITEMS_SCHEDULE: ${CLEAN_PENDING_ITEMS_SCHEDULE:-30 0 * * *}
PURGE_DELETED_ITEMS_SCHEDULE: ${PURGE_DELETED_ITEMS_SCHEDULE:-45 0 * * *}
configs:
- source: abra_entrypoint
target: /abra-entrypoint.sh
@@ -182,7 +193,8 @@ services:
- django_sk
- django_sp
- oidc_rpcs
- minio_rp
- rustfs_rp
- rustfs_ru
- postgres_p
- email_pass
@@ -228,59 +240,48 @@ services:
networks:
- backend
minio:
image: bitnamilegacy/minio:2025.7.23-debian-12-r5
user: root
environment: *minio-env
rustfs:
image: rustfs/rustfs:1.0.3
environment: *rustfs-env
healthcheck:
test: ["CMD", "mc", "ready", "local"]
interval: 1s
test: ["CMD", "curl", "-sf", "http://localhost:9000/health"]
interval: 5s
timeout: 20s
retries: 300
retries: 60
networks:
- backend
- proxy
command: /opt/bitnami/scripts/minio/run.sh
entrypoint: >
sh -c "/minio-initialize.sh & exec /opt/bitnami/scripts/minio/entrypoint.sh \"$$@\"" --
volumes:
- minio:/bitnami/minio/data
configs:
- source: abra_entrypoint
target: /abra-entrypoint.sh
mode: 0555
- source: minio_initialize
target: /minio-initialize.sh
mode: 0555
- rustfs:/data
secrets:
- minio_rp
- minio_ru
- rustfs_rp
- rustfs_ru
deploy:
labels:
- "traefik.enable=true"
- "traefik.docker.network=proxy"
- "traefik.http.routers.${STACK_NAME}_minio.rule=Host(`${MINIO_DOMAIN}`)"
- "traefik.http.routers.${STACK_NAME}_minio.entrypoints=web-secure"
- "traefik.http.routers.${STACK_NAME}_minio.tls=true"
- "traefik.http.routers.${STACK_NAME}_minio.tls.certresolver=${LETS_ENCRYPT_ENV}"
- "traefik.http.services.${STACK_NAME}_minio.loadbalancer.server.port=9000"
- "traefik.http.middlewares.${STACK_NAME}_minio-cors.headers.accessControlAllowOriginList=https://${DOMAIN}"
- "traefik.http.middlewares.${STACK_NAME}_minio-cors.headers.accessControlAllowMethods=GET,POST,PUT,DELETE,OPTIONS"
- "traefik.http.middlewares.${STACK_NAME}_minio-cors.headers.accessControlAllowHeaders=*"
- "traefik.http.middlewares.${STACK_NAME}_minio-cors.headers.accessControlExposeHeaders=ETag,Content-Length"
- "traefik.http.middlewares.${STACK_NAME}_minio-cors.headers.accessControlMaxAge=600"
- "traefik.http.middlewares.${STACK_NAME}_minio-cors.headers.addVaryHeader=true"
- "traefik.http.routers.${STACK_NAME}_minio.middlewares=${STACK_NAME}_minio-cors"
- "traefik.http.routers.${STACK_NAME}_rustfs.rule=Host(`${RUSTFS_DOMAIN}`)"
- "traefik.http.routers.${STACK_NAME}_rustfs.entrypoints=web-secure"
- "traefik.http.routers.${STACK_NAME}_rustfs.tls=true"
- "traefik.http.routers.${STACK_NAME}_rustfs.tls.certresolver=${LETS_ENCRYPT_ENV}"
- "traefik.http.services.${STACK_NAME}_rustfs.loadbalancer.server.port=9000"
- "traefik.http.middlewares.${STACK_NAME}_rustfs-cors.headers.accessControlAllowOriginList=https://${DOMAIN}"
- "traefik.http.middlewares.${STACK_NAME}_rustfs-cors.headers.accessControlAllowMethods=GET,POST,PUT,DELETE,OPTIONS"
- "traefik.http.middlewares.${STACK_NAME}_rustfs-cors.headers.accessControlAllowHeaders=*"
- "traefik.http.middlewares.${STACK_NAME}_rustfs-cors.headers.accessControlExposeHeaders=ETag,Content-Length"
- "traefik.http.middlewares.${STACK_NAME}_rustfs-cors.headers.accessControlMaxAge=600"
- "traefik.http.middlewares.${STACK_NAME}_rustfs-cors.headers.addVaryHeader=true"
- "traefik.http.routers.${STACK_NAME}_rustfs.middlewares=${STACK_NAME}_rustfs-cors"
- "backupbot.backup=${ENABLE_BACKUPS:-true}"
collabora:
image: collabora/code:26.04.5.1.1
entrypoint: >
sh -c "
export password=\"$$(cat /run/secrets/collabora_p)\" &&
exec /start-collabora-online.sh"
image: collabora/code:26.04.5.2.1
# Since 26.04 the image is distroless (no sh, no curl, no
# start-collabora-online.sh): keep the image entrypoint
# (coolwsd --use-env-vars), which reads the env vars below directly.
healthcheck:
test: [ "CMD", "curl", "-f", "http://localhost:9980/hosting/discovery" ]
# --use-env-vars so the probe honours ssl.enable=false from extra_params
test: [ "CMD", "/usr/bin/coolwsd", "--probe", "--use-env-vars" ]
interval: 30s
retries: 5
start_period: 60s
@@ -291,9 +292,8 @@ services:
environment:
- extra_params=--o:ssl.enable=false --o:ssl.termination=true
- username=${COLLABORA_ADMIN_USERNAME:-admin}
- password=${COLLABORA_ADMIN_PASSWORD}
- server_name=${COLLABORA_DOMAIN}
secrets:
- collabora_p
deploy:
labels:
- "traefik.enable=true"
@@ -312,7 +312,7 @@ services:
- "traefik.http.routers.${STACK_NAME}_collabora.middlewares=${STACK_NAME}_collabora-cors"
onlyoffice:
image: onlyoffice/documentserver-de:10.0.0.1
image: onlyoffice/documentserver-de:10.0.2.1
healthcheck:
test: [ "CMD", "curl", "-f", "http://localhost/hosting/discovery" ]
interval: 30s
@@ -323,6 +323,9 @@ services:
TZ: "Europe/Berlin"
USE_UNAUTHORIZED_STORAGE: "true"
ONLY_OFFICE_DOMAIN: ${ONLY_OFFICE_DOMAIN} # need to make variable available for golang template
# onlyoffice >= 10.0.2 only sends WOPI requests to hosts named in an
# ipfilter rule (wopi.requireIpFilterRule), see onlyoffice-config.json.tmpl
DOMAIN: ${DOMAIN}
networks:
- backend
- proxy
@@ -345,9 +348,8 @@ services:
- "traefik.http.middlewares.${STACK_NAME}_onlyoffice-cors.headers.accessControlExposeHeaders=ETag,Content-Length"
- "traefik.http.middlewares.${STACK_NAME}_onlyoffice-cors.headers.accessControlMaxAge=600"
- "traefik.http.middlewares.${STACK_NAME}_onlyoffice-cors.headers.addVaryHeader=true"
- "traefik.http.routers.${STACK_NAME}_onlyoffice.middlewares=${STACK_NAME}_onlyoffice-cors"
- "traefik.http.middlewares.${STACK_NAME}_onlyoffice-fwdproto.headers.customRequestHeaders.X-Forwarded-Proto=https"
- "traefik.http.routers.${STACK_NAME}_onlyoffice.middlewares=${STACK_NAME}_onlyoffice-fwdproto"
- "traefik.http.routers.${STACK_NAME}_onlyoffice.middlewares=${STACK_NAME}_onlyoffice-cors,${STACK_NAME}_onlyoffice-fwdproto"
web:
@@ -380,7 +382,7 @@ networks:
volumes:
postgres:
minio:
rustfs:
configs:
nginx_conf:
@@ -396,9 +398,9 @@ configs:
migrate:
name: ${STACK_NAME}_migrate_${MIGRATE_VERSION}
file: migrate.sh
minio_initialize:
name: ${STACK_NAME}_minio_initialize_${MINIO_INITIALIZE_VERSION}
file: minio-initialize.sh
s3_initialize:
name: ${STACK_NAME}_s3_initialize_${S3_INITIALIZE_VERSION}
file: s3-initialize.sh
onlyoffice_conf:
name: ${STACK_NAME}_onlyoffice_conf_${ONLYOFFICE_CONF_VERSION}
file: onlyoffice-config.json.tmpl
@@ -417,15 +419,12 @@ secrets:
postgres_p:
external: true
name: ${STACK_NAME}_postgres_p_${SECRET_POSTGRES_P_VERSION}
minio_rp:
rustfs_rp:
external: true
name: ${STACK_NAME}_minio_rp_${SECRET_MINIO_RP_VERSION}
minio_ru:
name: ${STACK_NAME}_rustfs_rp_${SECRET_RUSTFS_RP_VERSION}
rustfs_ru:
external: true
name: ${STACK_NAME}_minio_ru_${SECRET_MINIO_RU_VERSION}
collabora_p:
external: true
name: ${STACK_NAME}_collabora_p_${SECRET_COLLABORA_P_VERSION}
name: ${STACK_NAME}_rustfs_ru_${SECRET_RUSTFS_RU_VERSION}
email_pass:
external: true
name: ${STACK_NAME}_email_pass_${SECRET_EMAIL_PASS_VERSION}