diff --git a/.env.example b/.env.example index 716a186bdb24885c850ab391954426e0d447c215..3a4146beb658635bcc8310a19b38a2f83b20d6bd 100644 --- a/.env.example +++ b/.env.example @@ -1,10 +1,14 @@ # docker compose COMPOSE_BAKE=true +COMPOSE_FILE=docker-compose.yml:docker-compose.local.yml # Server DOMAIN=localhost +SHARED_STORAGE_PATH=./nextcloud-shared-storage # mail +SMTP_SECURE=tls +SMTP_PORT=587 SMTP_NAME=username SMTP_PASSWORD=123456 SMTP_HOST=smtp.domain.com @@ -22,10 +26,16 @@ REDIS_HOST=redis REDIS_HOST_PASSWORD=12456 # nextcloud -NEXTCLOUD_DOCKERFILE=slim.Dockerfile -NEXTCLOUD_DOCKER_IMG=registry.gitlab.e.foundation/e/infra/ecloud/nextcloud/slim +NEXTCLOUD_DOCKER_IMG=registry.gitlab.e.foundation/e/infra/ecloud/nextcloud/slim:latest NEXTCLOUD_ADMIN_USER=admin NEXTCLOUD_ADMIN_PASSWORD=@dm1n NEXTCLOUD_TRUSTED_DOMAINS=nginx SENTRY_DSN= SENTRY_PUBLIC_DSN= + +# nginx +NGINX_DOCKER_IMG=registry.gitlab.e.foundation/e/infra/ecloud/nextcloud/nginx:latest + +# syslog +SYSLOG_HOST=syslog + diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index fcdae6f6ecb76fa9180ef84ae56318d17d7a451f..2d1f39a7d2f7946dbdd92ff7a670ca07c67e02cd 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -31,23 +31,47 @@ build-workspace: build-slim-workspace: extends: .build variables: - DOCKER_BUILD_ARGS: "-f slim.Dockerfile" + DOCKER_BUILD_ARGS: "-f slim.Dockerfile --target nextcloud" REGISTRY_SUBPATH: "/slim" +build-nginx-workspace: + extends: .build + variables: + DOCKER_BUILD_ARGS: "-f slim.Dockerfile --target nginx" + REGISTRY_SUBPATH: "/nginx" + publish-slim-latest: extends: .deploy variables: - DOCKER_BUILD_ARGS: "-f slim.Dockerfile" + DOCKER_BUILD_ARGS: "-f slim.Dockerfile --target nextcloud" REGISTRY_SUBPATH: "/slim" MW_DOCKER_VERSION: "latest" rules: - if: '$CI_COMMIT_REF_NAME == "slim"' +publish-nginx-latest: + extends: .deploy + variables: + DOCKER_BUILD_ARGS: "-f slim.Dockerfile --target nginx" + REGISTRY_SUBPATH: "/nginx" + MW_DOCKER_VERSION: "latest" + rules: + - if: '$CI_COMMIT_REF_NAME == "slim"' + publish-slim-tag: extends: .deploy variables: - DOCKER_BUILD_ARGS: "-f slim.Dockerfile" + DOCKER_BUILD_ARGS: "-f slim.Dockerfile --target nextcloud" REGISTRY_SUBPATH: "/slim" MW_DOCKER_VERSION: "${CI_COMMIT_TAG/v/}" rules: - if: '$CI_COMMIT_TAG' + +publish-nginx-tag: + extends: .deploy + variables: + DOCKER_BUILD_ARGS: "-f slim.Dockerfile --target nginx" + REGISTRY_SUBPATH: "/nginx" + MW_DOCKER_VERSION: "${CI_COMMIT_TAG/v/}" + rules: + - if: '$CI_COMMIT_TAG' diff --git a/README.md b/README.md index 642bf397916e42129c8e530833c278c77da18242..327e5bc726aa9ec3b97ebc393b400f925940b8d4 100644 --- a/README.md +++ b/README.md @@ -6,13 +6,33 @@ This project builds a custom docker image from the official [Nextcloud](https:// ## Getting started +### Local environment + You can configure default values from the `.env` file. See [.env.example](./.env.example). By default, the `slim` Murena Workspace is configured. `slim` Murena Workspace ``` cp .env.example .env +mkdir -p nextcloud-shared-storage/{config,data} docker compose up --build -d ``` Go to http://localhost:8000 then use admin credentials provided into `.env` file. + +### Swarm environment with managed storage and services + + +First create the context: + +``` +docker context create dev --description "Development environment" --docker "host=ssh://root@dev.domain.app" +docker --context dev service ls +``` + +Secondly, once the .env configuration file initiated create the deployments stack: + +``` +set -a && source .env && set +a +docker --context dev stack deploy -c docker-compose.yml instance1 +``` diff --git a/config/nextcloud/murena.config.php b/config/nextcloud/murena.config.php new file mode 100644 index 0000000000000000000000000000000000000000..ccf639b360baffe4477bf0c11120a5c26a752df0 --- /dev/null +++ b/config/nextcloud/murena.config.php @@ -0,0 +1,34 @@ + true, + 'profile.enabled' => false, + 'defaultapp' => 'murena-dashboard,files', + 'theme' => 'eCloud', + 'filelocking.enabled' => true, + 'log_type' => 'syslog', + 'loglevel' => 2, + 'syslog_tag' => 'nextcloud', + 'cron_log' => true, + 'enabledPreviewProviders' => array( + 'OC\\Preview\\PNG', + 'OC\\Preview\\JPEG', + 'OC\\Preview\\GIF', + 'OC\\Preview\\BMP', + 'OC\\Preview\\XBitmap', + 'OC\\Preview\\MP3', + 'OC\\Preview\\TXT', + 'OC\\Preview\\MarkDown', + 'OC\\Preview\\OpenDocument', + 'OC\\Preview\\Krita', + 'OC\\Preview\\Movie', + ), + 'preview_max_x' => 1024, + 'preview_max_y' => 1024, + 'default_phone_region' => getenv('NEXTCLOUD_DEFAULT_PHONE_REGION_CODE') ?: 'FR', + 'maintenance_window_start' => 1, +); + +if (getenv('SENTRY_DSN') && getenv('SENTRY_PUBLIC_DSN')) { + $CONFIG['sentry.dsn'] = getenv('SENTRY_DSN'); + $CONFIG['sentry.public-dsn'] = getenv('SENTRY_PUBLIC_DSN'); +} diff --git a/config/nextcloud/pgsql_ssl.config.php b/config/nextcloud/pgsql_ssl.config.php new file mode 100644 index 0000000000000000000000000000000000000000..6e1a11a3a76a064bd35646ca424d6a3b4e4f3912 --- /dev/null +++ b/config/nextcloud/pgsql_ssl.config.php @@ -0,0 +1,9 @@ + array( + 'mode' => getenv('POSTGRES_SSL_MODE'), // see https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNECT-SSLMODE + 'rootcert' => getenv('POSTGRES_SSL_ROOTCERT') ?: null, + ), + ); +} diff --git a/config/nginx/templates/default.conf.template b/config/nginx/templates/default.conf.template index 669f56b3f4775df391dbcdfa8d210a44abdaa484..a48e4ae696fe727f8fcbf4c087117612513c30ba 100644 --- a/config/nginx/templates/default.conf.template +++ b/config/nginx/templates/default.conf.template @@ -5,7 +5,7 @@ map $arg_v $asset_immutable { } upstream php-handler { - server nextcloud:9000; + server ${NEXTCLOUD_ADDR}; } server { diff --git a/docker-compose.local.yml b/docker-compose.local.yml new file mode 100644 index 0000000000000000000000000000000000000000..1b121a54390ed7e1ddf899acbb74499324d949bb --- /dev/null +++ b/docker-compose.local.yml @@ -0,0 +1,57 @@ +services: + db: + image: postgres:16.10-alpine + restart: unless-stopped + environment: + - POSTGRES_DB=${DB_NAME} + - POSTGRES_USER=${DB_USER} + - POSTGRES_PASSWORD=${DB_PASSWORD} + volumes: + - db:/var/lib/postgresql/data + healthcheck: + test: ["CMD-SHELL", "pg_isready -U ${DB_USER} -d ${DB_NAME}"] + interval: 10s + timeout: 5s + retries: 5 + + redis: + image: redis:7.4-alpine + restart: unless-stopped + healthcheck: + test: [ "CMD", "redis-cli", "--raw", "incr", "ping" ] + interval: 10s + timeout: 5s + retries: 5 + + syslog: + image: jumanjiman/rsyslog + restart: unless-stopped + + nextcloud: + build: + context: . + dockerfile: slim.Dockerfile + target: nextcloud + depends_on: + syslog: + condition: service_started + required: false + db: + condition: service_healthy + required: false + redis: + condition: service_healthy + required: false + + nginx: + build: + context: . + dockerfile: slim.Dockerfile + target: nginx + ports: + - "8000:80" + depends_on: + - nextcloud + +volumes: + db: diff --git a/docker-compose.yml b/docker-compose.yml index 24af74daaf359057a74cae0d0abea45e27107220..8fc391527c5fa4d162203b915de4e2ef7c6e933b 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,34 +1,6 @@ services: - db: - image: postgres:17.4-alpine - restart: unless-stopped - environment: - - POSTGRES_DB=${DB_NAME} - - POSTGRES_USER=${DB_USER} - - POSTGRES_PASSWORD=${DB_PASSWORD} - volumes: - - db:/var/lib/postgresql/data - healthcheck: - test: ["CMD-SHELL", "pg_isready -U ${DB_USER} -d ${DB_NAME}"] - interval: 10s - timeout: 5s - retries: 5 - - redis: - image: redis:7.4-alpine - restart: unless-stopped - healthcheck: - test: [ "CMD", "redis-cli", "--raw", "incr", "ping" ] - interval: 10s - timeout: 5s - retries: 5 - nextcloud: image: ${NEXTCLOUD_DOCKER_IMG} - build: - context: . - dockerfile: ${NEXTCLOUD_DOCKERFILE} - restart: unless-stopped environment: - POSTGRES_HOST=${DB_HOST} - POSTGRES_USER=${DB_USER} @@ -38,51 +10,67 @@ services: - NEXTCLOUD_ADMIN_USER=${NEXTCLOUD_ADMIN_USER} - NEXTCLOUD_ADMIN_PASSWORD=${NEXTCLOUD_ADMIN_PASSWORD} - NEXTCLOUD_TRUSTED_DOMAINS=${NEXTCLOUD_TRUSTED_DOMAINS} - - SMTP_SECURE=tls - - SMTP_PORT=587 + - SMTP_SECURE=${SMTP_SECURE} + - SMTP_PORT=${SMTP_PORT} - SMTP_NAME=${SMTP_NAME} - SMTP_PASSWORD=${SMTP_PASSWORD} - SMTP_HOST=${SMTP_HOST} - MAIL_FROM_ADDRESS=${MAIL_FROM_ADDRESS} - MAIL_DOMAIN=${MAIL_DOMAIN} - - SYSLOG_HOST=syslog + - SYSLOG_HOST=${SYSLOG_HOST} - SENTRY_DSN=${SENTRY_DSN} - SENTRY_PUBLIC_DSN=${SENTRY_PUBLIC_DSN} volumes: - - nextcloud:/var/www/html - depends_on: - syslog: - condition: service_started - db: - condition: service_healthy - redis: - condition: service_healthy - - syslog: - image: jumanjiman/rsyslog + - nextcloud-config:/var/www/html/config + - nextcloud-data:/var/www/html/data + deploy: + placement: + constraints: + - node.role == worker nextcloud-cron: image: ${NEXTCLOUD_DOCKER_IMG} - restart: unless-stopped entrypoint: /cron.sh volumes: - - nextcloud:/var/www/html - depends_on: - - nextcloud + - nextcloud-config:/var/www/html/config + - nextcloud-data:/var/www/html/data + deploy: + placement: + constraints: + - node.role == worker nginx: - image: nginx:stable-alpine - restart: unless-stopped + image: ${NGINX_DOCKER_IMG} environment: + NEXTCLOUD_ADDR: ${NEXTCLOUD_ADDR:-nextcloud:9000} DOMAIN: ${DOMAIN} - ports: - - "8000:80" volumes: - - ${DEPLOYMENT_PATH:-.}/config/nginx/templates:/etc/nginx/templates - - nextcloud:/var/www/html - depends_on: - - nextcloud + - nextcloud-data:/var/www/html/data + deploy: + placement: + constraints: + - node.role == worker + labels: + - traefik.enable=true + - traefik.http.routers.${COMPOSE_PROJECT_NAME:-nextcloud}.rule=Host(`${DOMAIN}`) + - traefik.http.routers.${COMPOSE_PROJECT_NAME:-nextcloud}.entrypoints=websecure + - traefik.http.routers.${COMPOSE_PROJECT_NAME:-nextcloud}.tls.certresolver=letsencrypt + - traefik.http.routers.${COMPOSE_PROJECT_NAME:-nextcloud}-http.rule=Host(`${DOMAIN}`) + - traefik.http.routers.${COMPOSE_PROJECT_NAME:-nextcloud}-http.entrypoints=web + - traefik.http.routers.${COMPOSE_PROJECT_NAME:-nextcloud}-http.middlewares=https-redirect + - traefik.http.middlewares.https-redirect.redirectscheme.scheme=https + - traefik.http.services.${COMPOSE_PROJECT_NAME:-nextcloud}.loadbalancer.server.port=80 volumes: - db: - nextcloud: + nextcloud-config: + driver: local + driver_opts: + type: none + o: bind + device: "${SHARED_STORAGE_PATH}/config" + nextcloud-data: + driver: local + driver_opts: + type: none + o: bind + device: "${SHARED_STORAGE_PATH}/data" diff --git a/hooks.d/post-installation/murena-config.json b/hooks.d/post-installation/murena-config.json deleted file mode 100644 index 01f37613ddc9dd8a1e1e239dcfd1abc8de55ea48..0000000000000000000000000000000000000000 --- a/hooks.d/post-installation/murena-config.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "system": { - "profile.enabled": false, - "defaultapp": "murena-dashboard,files", - "theme": "eCloud", - "filelocking.enabled": true, - "log_type": "syslog", - "loglevel": 2, - "syslog_tag": "nextcloud", - "cron_log": true, - "enabledPreviewProviders": [ - "OC\\Preview\\PNG", - "OC\\Preview\\JPEG", - "OC\\Preview\\GIF", - "OC\\Preview\\BMP", - "OC\\Preview\\XBitmap", - "OC\\Preview\\MP3", - "OC\\Preview\\TXT", - "OC\\Preview\\MarkDown", - "OC\\Preview\\OpenDocument", - "OC\\Preview\\Krita", - "OC\\Preview\\Movie" - ], - "preview_max_x": 1024, - "preview_max_y": 1024, - "default_phone_region": "FR", - "maintenance_window_start": 1, - "sentry.dsn": "${SENTRY_DSN}", - "sentry.public-dsn": "${SENTRY_PUBLIC_DSN}" - } -} diff --git a/hooks.d/post-installation/murena-theme.sh b/hooks.d/post-installation/murena-config.sh similarity index 57% rename from hooks.d/post-installation/murena-theme.sh rename to hooks.d/post-installation/murena-config.sh index bd1987a916fb15962a692254fe3504d7aa97aae9..c828e7b7ecce964e95cfd0dc30245102e33195c4 100755 --- a/hooks.d/post-installation/murena-theme.sh +++ b/hooks.d/post-installation/murena-config.sh @@ -3,15 +3,10 @@ SCRIPT_DIR=$(dirname "$0") PATH=${PATH}:/var/www/html -# Apply configuration -sed -e "s|\${SENTRY_DSN}|${SENTRY_DSN}|g" \ - -e "s|\${SENTRY_PUBLIC_DSN}|${SENTRY_PUBLIC_DSN}|g" \ - "${SCRIPT_DIR}/murena-config.json" | occ config:import - # Update theme occ maintenance:theme:update -echo "Enabling nextcloud apps" +# Manage apps occ app:enable contacts occ app:enable calendar occ app:enable ecloud-theme-helper @@ -27,11 +22,11 @@ occ app:enable sentry occ app:disable firstrunwizard occ app:disable logreader -echo "Performing some Nextcloud administrative tasks" -if [ -n "${MYSQL_DATABASE+x}" ]; then - occ db:convert-mysql-charset -fi -occ db:convert-filecache-bigint --no-interaction +# database occ db:add-missing-indices + +# mimetype migration +occ maintenance:repair --include-expensive + # Set background jobs to use system cron occ background:cron diff --git a/patches/037-remove-rsync-on-init-about-static-files.patch b/patches/037-remove-rsync-on-init-about-static-files.patch new file mode 100644 index 0000000000000000000000000000000000000000..f3a5c261a5cc3b12f6cae8aa323ed18da3ebf53b --- /dev/null +++ b/patches/037-remove-rsync-on-init-about-static-files.patch @@ -0,0 +1,50 @@ +diff --git ./entrypoint.sh ./entrypoint.sh +--- ./entrypoint.sh ++++ ./entrypoint.sh +@@ -157,23 +157,21 @@ if expr "$1" : "apache" 1>/dev/null || [ "$1" = "php-fpm" ] || [ "${NEXTCLOUD_UP + fi + + installed_version="0.0.0.0" +- if [ -f /var/www/html/version.php ]; then ++ if [ -f /var/www/html/config/config.php ]; then + # shellcheck disable=SC2016 +- installed_version="$(php -r 'require "/var/www/html/version.php"; echo implode(".", $OC_Version);')" ++ installed_version=$(php -r 'require "/var/www/html/config/config.php"; echo $CONFIG["version"];') + fi +- # shellcheck disable=SC2016 +- image_version="$(php -r 'require "/usr/src/nextcloud/version.php"; echo implode(".", $OC_Version);')" + +- if version_greater "$installed_version" "$image_version"; then +- echo "Can't start Nextcloud because the version of the data ($installed_version) is higher than the docker image version ($image_version) and downgrading is not supported. Are you sure you have pulled the newest image version?" ++ if version_greater "$installed_version" "$NEXTCLOUD_VERSION_LONG"; then ++ echo "Can't start Nextcloud because the version of the data ($installed_version) is higher than the docker image version ($NEXTCLOUD_VERSION_LONG) and downgrading is not supported. Are you sure you have pulled the newest image version?" + exit 1 + fi + +- if version_greater "$image_version" "$installed_version"; then +- echo "Initializing nextcloud $image_version ..." ++ if version_greater "$NEXTCLOUD_VERSION_LONG" "$installed_version"; then ++ echo "Initializing nextcloud $NEXTCLOUD_VERSION_LONG ..." + if [ "$installed_version" != "0.0.0.0" ]; then +- if [ "${image_version%%.*}" -gt "$((${installed_version%%.*} + 1))" ]; then +- echo "Can't start Nextcloud because upgrading from $installed_version to $image_version is not supported." ++ if [ "${NEXTCLOUD_VERSION_LONG%%.*}" -gt "$((${installed_version%%.*} + 1))" ]; then ++ echo "Can't start Nextcloud because upgrading from $installed_version to $NEXTCLOUD_VERSION_LONG is not supported." + echo "It is only possible to upgrade one major version at a time. For example, if you want to upgrade from version 14 to 16, you will have to upgrade from version 14 to 15, then from 15 to 16." + exit 1 + fi +@@ -186,13 +184,11 @@ if expr "$1" : "apache" 1>/dev/null || [ "$1" = "php-fpm" ] || [ "${NEXTCLOUD_UP + rsync_options="-rlD" + fi + +- rsync $rsync_options --delete --exclude-from=/upgrade.exclude /usr/src/nextcloud/ /var/www/html/ +- for dir in config data custom_apps themes; do ++ for dir in config data; do + if [ ! -d "/var/www/html/$dir" ] || directory_empty "/var/www/html/$dir"; then + rsync $rsync_options --include "/$dir/" --exclude '/*' /usr/src/nextcloud/ /var/www/html/ + fi + done +- rsync $rsync_options --include '/version.php' --exclude '/*' /usr/src/nextcloud/ /var/www/html/ + + # Install + if [ "$installed_version" = "0.0.0.0" ]; then diff --git a/slim.Dockerfile b/slim.Dockerfile index 8fc693d839bc63e377226006db609f6fb71a42be..4aeca707d44f73689138ba66857caa4380e9233d 100644 --- a/slim.Dockerfile +++ b/slim.Dockerfile @@ -1,6 +1,10 @@ -FROM nextcloud:30.0.14-fpm +FROM nextcloud:30.0.16-fpm AS nextcloud + +# see $OC_Version from /usr/src/nextcloud/version.php +ENV NEXTCLOUD_VERSION_LONG 30.0.16.1 ARG BASE_DIR="/usr/src/nextcloud" +ARG TMP_PATCH_DIR="/tmp/build_patches" ARG CONTACTS_URL="https://gitlab.e.foundation/api/v4/projects/1238/packages/generic/contacts/v7.2.0+murena-20250902/contacts-v7.2.0+murena-20250902.tar.gz" ARG CALENDAR_URL="https://gitlab.e.foundation/api/v4/projects/1199/packages/generic/calendar/v5.3.5+murena-20250902/calendar-v5.3.5+murena-20250902.tar.gz" @@ -18,7 +22,7 @@ ARG THEME_VERSION="https://gitlab.e.foundation/api/v4/projects/315/packages/gene ARG SNAPPY_THEME_VERSION="https://gitlab.e.foundation/api/v4/projects/1377/packages/generic/snappymail/v4.0.5/snappymail-v4.0.5.tar.gz" COPY custom_entrypoint-slim.sh / -COPY hooks.d/post-installation/ /docker-entrypoint-hooks.d/post-installation/ +COPY hooks.d/ /docker-entrypoint-hooks.d/ RUN rm -rf ${BASE_DIR}/core/skeleton/* ${BASE_DIR}/themes/example \ && mkdir -p ${BASE_DIR}/core/skeleton/Documents \ @@ -46,9 +50,25 @@ RUN curl -sL ${SENTRY_URL} | tar xzf - -C ${BASE_DIR}/custom_apps RUN curl -sL ${THEME_VERSION} | tar xzf - -C ${BASE_DIR}/themes RUN curl -sL ${SNAPPY_THEME_VERSION} | tar xzf - -C ${BASE_DIR}/themes/Murena/ +COPY config/nextcloud/ /usr/src/nextcloud/config/ + +# Apply patches +COPY patches/ ${TMP_PATCH_DIR}/ +RUN cd / && patch -p0 < ${TMP_PATCH_DIR}/037-remove-rsync-on-init-about-static-files.patch \ + && rm -rf ${TMP_PATCH_DIR} + +# Initialize nextcloud /var/www/html and patch the default entrypoint.sh accordingly +RUN rsync -rLDog --chown www-data:www-data --delete --exclude-from=/upgrade.exclude /usr/src/nextcloud/ /var/www/html/ \ + && rsync -rLDog --chown www-data:www-data --include "version.php" --include "/custom_apps/" --include "/themes/" --exclude '/*' /usr/src/nextcloud/ /var/www/html/ + COPY config/syslog-ng/syslog-ng.conf /etc/syslog-ng/syslog-ng.conf ENTRYPOINT ["/custom_entrypoint-slim.sh"] CMD ["php-fpm"] # only for dev purpose STOPSIGNAL SIGKILL + +FROM nginx:1.29-alpine AS nginx + +COPY ./config/nginx/templates /etc/nginx/templates +COPY --from=nextcloud /var/www/html /var/www/html