#!/usr/bin/env bash # # Copyright © 2021,2022 IsardVDI S.L. # # This file is part of DD # # DD is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation, either version 3 of the License, or (at your # option) any later version. # # DD is distributed in the hope that it will be useful, but WITHOUT ANY # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more # details. # # You should have received a copy of the GNU Affero General Public License # along with DD. If not, see . # # SPDX-License-Identifier: AGPL-3.0-or-later OPERATION="$1" shift # We need docker-compose >= 1.28 to catch configuration variables REQUIRED_DOCKER_COMPOSE_VERSION="1.28" docker_compose_version(){ docker-compose --version --short | sed 's/^docker-compose version \([^,]\+\),.*$/\1/' } check_docker_compose_version(){ # We cannot use sort -C because is not included in BusyBox v1.33.1 of docker:dind image { echo "$REQUIRED_DOCKER_COMPOSE_VERSION" docker_compose_version } | sort -c -V 2> /dev/null } get_os(){ if grep -q ^DISTRIB_ID=Ubuntu /etc/lsb-release 2>/dev/null; then echo ubuntu elif [ -f /etc/debian_version ]; then echo debian fi } help() { cat <<-EOF Example: ./dd.ctl [operation] [arguments] For a new installation, you usually will want to run: ./dd-ctl prerequisites ./dd-ctl securize ./dd-ctl all ./dd-ctl saml Generate adminer.yml to access DBs: ./dd-ctl adminer Bring the current project up: ./dd-ctl all Branding (custom/img, custom/menu): ./dd-ctl branding Build the compose files: ./dd-ctl build Build the devel compose files: ./dd-ctl build-devel Apply customizations: ./dd-ctl customize Stop the project when started: ./dd-ctl down Show external files in repo, their license and source: ./dd-ctl listpatches Generate .orig and .patch files to compare with upstream: ./dd-ctl genpatches Rescan nextcloud data folders: ./dd-ctl nextcloud-scan Install all prerequisites for installation: ./dd-ctl prerequisites Update DD to latest version: ./dd-ctl update [branch-name] (defaults to main) Restart api if changes applied (development): ./dd-ctl restart-api Update SAML certificates: ./dd-ctl saml Set secure passwords in dd.conf: ./dd-ctl securize Set a config variable in dd.conf: ./dd-ctl setconf VARIABLE [VALUE] Start the project when stopped: ./dd-ctl up Upgrade plugins: ./dd-ctl upgrade-plugins Regenerate docker-compose.yml from conf: ./dd-ctl yml EOF } # Help messages if [ -z "$OPERATION" ] || [ "$OPERATION" = "-h" ] || [ "$OPERATION" = "--help" ]; then test -n "$OPERATION" || printf "Missing command.\n\n" help exit fi # Sanity checks if [ -z "$(get_os)" ]; then cat >&2 <<-EOF ***************************************************************** Your OS doesn't seem to be Debian or Ubuntu and is not supported! Things might still work, please report back. ***************************************************************** EOF fi if [ "$OPERATION" != "prerequisites" ]; then if [ ! -d "custom" ]; then echo "You need to copy custom.sample to custom folder and adapt it to your needs." >&2 exit 1 fi if [ ! -f "dd.conf" ]; then echo "You need to copy dd.conf.sample to dd.conf and adapt" >&2 exit 1 fi if ! check_docker_compose_version; then echo "ERROR: Please use docker-compose greather than or equal to $REQUIRED_DOCKER_COMPOSE_VERSION." >&2 exit 1 fi fi REPO_BRANCH="${1:-main}" cp dd.conf .env CUSTOM_PATH=$(pwd) . ./.env prerequisites_docker(){ # Remove uncompatible docker packages for pkg in docker docker-engine docker.io containerd runc; do if dpkg -s "${pkg}" >/dev/null; then apt-get remove -y "${pkg}" fi done # Install upstream-docker repo pre-requisites apt-get install -y \ apt-transport-https \ ca-certificates \ curl \ gnupg-agent \ software-properties-common \ git \ unzip \ libffi-dev os="$(get_os)" curl -fsSL "https://download.docker.com/linux/${os:?}/gpg" | \ apt-key add - add-apt-repository \ "deb [arch=amd64] https://download.docker.com/linux/${os:?} \ $(lsb_release -cs) \ stable" apt-get update -y # docker-ce must be used instead of the one from the distro apt-get install -y docker-ce docker-ce-cli containerd.io apt-get install -y python3-pip # docker-compose > 1.28 is required, latest will be installed pip3 install docker-compose } prerequisites_pwd(){ apt-get install -y dictionaries-common wamerican } ddupdate(){ # Bring down services ./dd-ctl down # Switch to latest version git fetch && git checkout "$REPO_BRANCH" git pull --rebase --autostash # Needed for dd-apps submodules... git submodule update --init --recursive case "${1}" in --full) # Rebuild containers ./dd-ctl build # Upgrade plugins ./dd-ctl upgrade-plugins # Reapply customisations if needed ./dd-ctl customize ;; esac } build_compose(){ DD_DEFAULT_BUILD="$(git rev-parse --short HEAD)" export DD_BUILD="${DD_BUILD:-${DD_DEFAULT_BUILD}}" setconf DD_BUILD "${DD_BUILD}" .env setconf CUSTOM_PATH "$CUSTOM_PATH" .env setconf BUILD_APPS_ROOT_PATH "$CUSTOM_PATH/dd-apps" .env setconf BUILD_SSO_ROOT_PATH "$CUSTOM_PATH/dd-sso" .env # Choose HAProxy configuration flavour if [ "${PROXY_PROTOCOL:-false}" = "true" ]; then HAPROXY_YML="haproxy.proxy.yml" HAPROXY_PROXY="proxy" else # Default HAPROXY_YML="haproxy.yml" HAPROXY_PROXY="no-proxy" fi # Enable or disable WAF if [ "${DISABLE_WAF:-true}" = "true" ]; then # Current default (might change) WAF_YML="waf-modsecurity.disabled.yml" HAPROXY_WAF="no-waf" else WAF_YML="waf-modsecurity.yml" HAPROXY_WAF="waf" fi # Persist resulting HAProxy config export HAPROXY_CFG="haproxy.${HAPROXY_WAF}.${HAPROXY_PROXY}.cfg" setconf HAPROXY_CFG "${HAPROXY_CFG}" setconf HAPROXY_CFG "${HAPROXY_CFG}" .env # Enable or disable ClamAV if [ "${DISABLE_CLAMAV:-true}" = "true" ]; then # Current default (might change) CLAMAV_YML="clamav.disabled.yml" else CLAMAV_YML="clamav.yml" fi ## Prepare apps environment ln -sf "${CUSTOM_PATH}/.env" dd-apps/.env ln -sf "${CUSTOM_PATH}/.env" dd-apps/docker/postgresql && \ ln -sf "${CUSTOM_PATH}/.env" dd-apps/docker/mariadb && \ ln -sf "${CUSTOM_PATH}/.env" dd-apps/docker/moodle && \ ln -sf "${CUSTOM_PATH}/.env" dd-apps/docker/nextcloud && \ ln -sf "${CUSTOM_PATH}/.env" dd-apps/docker/wordpress && \ ln -sf "${CUSTOM_PATH}/.env" dd-apps/docker/etherpad ## Prepare sso environment ln -sf "${CUSTOM_PATH}/.env" dd-sso/.env ln -sf "${CUSTOM_PATH}/.env" dd-sso/docker-compose-parts/.env ## Prepare waf environment ln -sf "${CUSTOM_PATH}/.env" dd-waf/.env ln -sf "${CUSTOM_PATH}/.env" dd-waf/docker-compose-parts/.env # Clean up older custom data rm -rf custom/system/keycloak-themes rmdir custom/system 2>/dev/null || true # Build compose ymls docker-compose \ -f "dd-sso/docker-compose-parts/$WAF_YML" \ -f "dd-sso/docker-compose-parts/$HAPROXY_YML"\ -f dd-sso/docker-compose-parts/api.yml \ -f dd-sso/docker-compose-parts/keycloak.yml \ -f dd-sso/docker-compose-parts/avatars.yml \ -f dd-apps/docker/postgresql/postgresql.yml \ -f dd-sso/docker-compose-parts/admin.yml \ \ -f dd-apps/docker/moodle/moodle.yml \ -f dd-apps/docker/nextcloud/nextcloud.yml \ -f dd-apps/docker/wordpress/wordpress.yml \ -f dd-apps/docker/etherpad/etherpad.yml \ -f dd-apps/docker/onlyoffice/onlyoffice.yml \ -f dd-apps/docker/redis/redis.yml \ -f dd-apps/docker/postgresql/postgresql.yml \ -f dd-apps/docker/mariadb/mariadb.yml \ -f "dd-apps/docker/clamav/${CLAMAV_YML}" \ -f dd-apps/docker/network.yml \ config > docker-compose.yml } listpatches(){ cat <<-EOM # Generate .orig and .patch files with ./dd-ctl genpatches # file license author url EOM # shellcheck disable=SC2044 # TODO: Get rid of this for X in find for patchfile in $(find . -name 'dd-patch'); do patchdir="$(dirname "${patchfile}")" grep -vE '^#' "${patchfile}" | awk "{print \"${patchdir}/\" \$0}" done } genpatches(){ CD="$(pwd)" # shellcheck disable=SC2044 # TODO: Get rid of this for X in find for patchfile in $(find . -name 'dd-patch'); do # shellcheck disable=SC2164 # We got the dir from find cd "$(dirname "${patchfile}")" echo "IN DIR $(pwd)" grep -vE '^#' dd-patch | while IFS= read -r line; do fn="$(echo "${line}" | cut -f 1)" url="$(echo "${line}" | cut -f 4)" wget "${url}" -O "${fn}.orig" diff "${fn}.orig" "${fn}" > "${fn}.patch" done # shellcheck disable=SC2164 # This was previous working dir cd "${CD}" done } build(){ build_compose docker-compose build --pull } build_compose_develop(){ build_compose ## Prepare sso environment setconf CUSTOM_PATH "$CUSTOM_PATH" .env setconf BUILD_SSO_ROOT_PATH "$CUSTOM_PATH/dd-sso" .env ln -sf "${CUSTOM_PATH}/.env" dd-sso/.env ln -sf "${CUSTOM_PATH}/.env" dd-sso/docker-compose-parts/.env docker-compose -f docker-compose.yml \ -f dd-sso/docker-compose-parts/api.devel.yml \ -f dd-sso/docker-compose-parts/admin.devel.yml \ config > devel.yml } up(){ docker-compose up -d } down(){ docker-compose down } setup_nextcloud(){ echo " --> Applying custom settings in nextcloud" # docker exec -u www-data dd-apps-nextcloud-app sh -c 'export OC_PASS=$DDADMIN_PASSWORD && php occ user:add --password-from-env --display-name="DD Admin" --group="admin" $DDADMIN_USER' # docker exec -u www-data dd-apps-nextcloud-app sh -c 'export OC_PASS=admin && php occ user:delete admin' # docker exec -u www-data dd-apps-nextcloud-app sh -c 'export OC_PASS=LostAdminGroup && php occ user:add --password-from-env --display-name="Admin" --group="admin" admin' # docker exec -u www-data dd-apps-nextcloud-app php occ --no-warnings config:app:set unaprova token --value "SuperS3cret" #cp -R $BUILD_APPS_ROOT_PATH/dd-apps/docker/nextcloud/themes/* $DATA_FOLDER/nextcloud/themes/ docker exec -u www-data dd-apps-nextcloud-app php occ --no-warnings config:system:set default_language --value="ca" docker exec -u www-data dd-apps-nextcloud-app php occ --no-warnings config:system:set skeletondirectory --value='' # Disable certain NextCloud apps for app in firstrunwizard recommendations dashboard; do docker exec -i -u www-data dd-apps-nextcloud-app sh -s <<-EOF php occ --no-warnings app:disable "${app}" EOF done # Install and enable NextCloud apps for app in bruteforcesettings polls calendar spreed bbb mail ownpad onlyoffice; do docker exec -i -u www-data dd-apps-nextcloud-app sh -s <<-EOF php occ --no-warnings app:install "${app}" php occ --no-warnings app:enable "${app}" EOF done # Install ClamAV conditionally if [ "${DISABLE_CLAMAV:-true}" = "false" ]; then docker exec -i -u www-data dd-apps-nextcloud-app sh -s <<-EOF php occ --no-warnings app:install files_antivirus php occ --no-warnings app:enable files_antivirus EOF else # Enforce disabled docker exec -i -u www-data dd-apps-nextcloud-app sh -s <<-EOF php occ --no-warnings app:install files_antivirus php occ --no-warnings app:disable files_antivirus EOF fi # Disable in Nextcloud # shellcheck disable=SC2043 # We currently only force-disable one app for app in circles; do docker exec -i -u www-data dd-apps-nextcloud-app sh -s <<-EOF php occ --no-warnings app:disable "${app}" || true EOF done # Temporary patch while upstream lands our changes # See: https://github.com/nextcloud/mail/pull/6908 for f in appinfo/info.xml lib/Command/UpdateAccount.php lib/Db/MailAccountMapper.php; do install -m 0644 -o 82 -g 82 "dd-apps/docker/nextcloud/nc_mail/$f" "${SRC_FOLDER}/nextcloud/custom_apps/mail/$f" done # Custom forms docker exec dd-apps-nextcloud-app apk add git npm composer docker exec -u www-data dd-apps-nextcloud-app rm -rf /var/www/html/custom_apps/forms docker exec -u www-data dd-apps-nextcloud-app git clone https://github.com/juanan3ip/form -b dev /var/www/html/custom_apps/forms docker exec -u www-data dd-apps-nextcloud-app npm --prefix /var/www/html/custom_apps/forms install docker exec -u www-data dd-apps-nextcloud-app composer -d/var/www/html/custom_apps/forms install --no-dev -o docker exec -u www-data dd-apps-nextcloud-app php occ app:enable forms # Disable Big Blue Button media check by default docker exec -u www-data dd-apps-nextcloud-app php occ config:app:set bbb join.mediaCheck --value="false" # Disable Big Blue Button listen only mode by default docker exec dd-apps-nextcloud-app sed -i.orig 's/^\(\s*$room->setListenOnly(\)true\();\)$/\1false\2/' /var/www/html/custom_apps/bbb/lib/Service/RoomService.php # Enable option to join muted to Big Blue Button room by default docker exec dd-apps-nextcloud-app sed -i 's/^\(\s*$room->setJoinMuted(\)false\();\)$/\1true\2/' /var/www/html/custom_apps/bbb/lib/Service/RoomService.php # Remove meeting join nextcloud bbb app dialog exclamation marks docker exec dd-apps-nextcloud-app sh -c "sed -i.orig 's/\(^\s*\"Please enter your name!\" : [^¡]*\)¡\?\([^!]*\)!\(.*\)$/\1\2\3/' /var/www/html/custom_apps/bbb/l10n/*.json" docker exec dd-apps-nextcloud-app sh -c "sed -i 's/\(^\s*\"Let\x27s go!\" : [^¡]*\)¡\?\([^!]*\)!\(.*\)$/\1\2\3/' /var/www/html/custom_apps/bbb/l10n/*.json" docker exec -u www-data dd-apps-nextcloud-app php occ --no-warnings config:system:set theme --value=dd docker exec -u www-data dd-apps-nextcloud-app php occ --no-warnings config:system:set allow_local_remote_servers --value=true docker exec -u www-data dd-apps-nextcloud-app php occ --no-warnings maintenance:theme:update #docker exec -u www-data dd-apps-nextcloud-app php occ app:install user_saml docker exec -u www-data dd-apps-nextcloud-app php occ app:enable user_saml docker exec dd-apps-nextcloud-app apk add jq docker exec dd-apps-nextcloud-app sh -c 'jq ". + {\"pad\": [\"application/x-ownpad\"], \"calc\": [\"application/x-ownpad\"]}" /var/www/html/resources/config/mimetypemapping.dist.json > /var/www/html/config/mimetypemapping.json' nextcloud_scan # Open pads in a new tab/window docker exec dd-apps-nextcloud-app sed -i.orig 's/^\(\s*\)\(var viewer = OC.generateUrl.*\)/\1\2\n\1window.open(viewer);\n\1return;/' /var/www/html/custom_apps/ownpad/js/ownpad.js # SMTP SMTP_LOCAL_PART="$(echo "${SMTP_USER}" | cut -d '@' -f 1)" SMTP_DOMAIN="$(echo "${SMTP_USER}" | cut -d '@' -f 2)" docker exec -i -u www-data dd-apps-nextcloud-app sh -s <<-EOF php occ --no-warnings config:system:set -n mail_smtpmode --value="smtp" php occ --no-warnings config:system:set -n mail_smtpsecure --value="${SMTP_PROTOCOL}" php occ --no-warnings config:system:set -n mail_sendmailmode --value="smtp" php occ --no-warnings config:system:set -n mail_from_address --value="${SMTP_LOCAL_PART}" php occ --no-warnings config:system:set -n mail_domain --value="${SMTP_DOMAIN}" php occ --no-warnings config:system:set -n mail_smtpauth --value=1 php occ --no-warnings config:system:set -n mail_smtpauthtype --value="LOGIN" php occ --no-warnings config:system:set -n mail_smtphost --value="${SMTP_HOST}" php occ --no-warnings config:system:set -n mail_smtpport --value="${SMTP_PORT}" php occ --no-warnings config:system:set -n mail_smtpname --value="${SMTP_USER}" echo 'Setting Nextcloud password' php occ --no-warnings config:system:set -n -q mail_smtppassword --value="${SMTP_PASSWORD}" EOF # Settings docker exec -i -u www-data dd-apps-nextcloud-app sh -s <<-EOF php occ --no-warnings config:app:set -n ownpad ownpad_etherpad_enable --value="yes" php occ --no-warnings config:app:set -n ownpad ownpad_etherpad_host --value="https://pad.$DOMAIN" php occ --no-warnings config:app:set -n onlyoffice DocumentServerUrl --value="https://oof.$DOMAIN" php occ --no-warnings config:app:set -n onlyoffice jwt_secret --value="secret" php occ --no-warnings config:app:set -n onlyoffice jwt_header --value="Authorization" php occ --no-warnings config:app:set -n onlyoffice sameTab --value="false" # Moodle nextcloud task needs forcesave onlyoffice option php occ --no-warnings config:app:set -n onlyoffice customizationForcesave --value="true" # Add allow list IPs php occ --no-warnings config:app:set -n bruteForce whitelist_1 --value='172.16.0.0/12' # OnlyOffice php occ --no-warnings config:app:set -n onlyoffice preview --value="true" php occ --no-warnings config:app:set -n onlyoffice defFormats --value="{\"csv\":\"false\",\"doc\":\"true\",\"docm\":\"false\",\"docx\":\"true\",\"docxf\":\"true\",\"oform\":\"true\",\"dotx\":\"false\",\"epub\":\"false\",\"html\":\"false\",\"odp\":\"true\",\"ods\":\"true\",\"odt\":\"true\",\"otp\":\"true\",\"ots\":\"true\",\"ott\":\"true\",\"pdf\":\"false\",\"potm\":\"false\",\"potx\":\"false\",\"ppsm\":\"false\",\"ppsx\":\"true\",\"ppt\":\"true\",\"pptm\":\"false\",\"pptx\":\"true\",\"rtf\":\"false\",\"txt\":\"false\",\"xls\":\"true\",\"xlsm\":\"false\",\"xlsx\":\"true\",\"xltm\":\"false\",\"xltx\":\"true\"}", php occ --no-warnings config:app:set -n onlyoffice editFormats --value="{\"csv\":\"true\",\"odp\":\"false\",\"ods\":\"false\",\"odt\":\"false\",\"rtf\":\"false\",\"txt\":\"true\"}" EOF # Configure ClamAV conditionally if [ "${DISABLE_CLAMAV:-true}" = "false" ]; then docker exec -i -u www-data dd-apps-nextcloud-app sh -s <<-EOF php occ --no-warnings config:app:set -n files_antivirus av_mode --value="daemon" php occ --no-warnings config:app:set -n files_antivirus av_host --value="dd-apps-clamav" php occ --no-warnings config:app:set -n files_antivirus av_port --value="3310" php occ --no-warnings config:app:set -n files_antivirus av_infected_action --value="only_log" php occ --no-warnings config:app:set -n files_antivirus av_stream_max_length --value="26214400" php occ --no-warnings config:app:set -n files_antivirus av_max_file_size --value="-1" EOF fi # Allow nextcloud into other apps iframes # Content-Security-Policy: frame-ancestors 'self' *.$DOMAIN; docker exec dd-apps-nextcloud-app sed -i -e "/protected \\\$allowedFrameAncestors = \[/{n;s/\('\\\\\'self\\\\\'\)\('\)/\1 *.$DOMAIN\2/}" /var/www/html/lib/public/AppFramework/Http/ContentSecurityPolicy.php # Content-Sety-Policy: connect-src 'self -' *.$DOMAIN; docker exec dd-apps-nextcloud-app sed -i -e "/protected \\\$allowedConnectDomains = \[/{n;s/\('\\\\\'self\\\\\'\)\('\)/\1 *.$DOMAIN\2/}" /var/www/html/lib/public/AppFramework/Http/ContentSecurityPolicy.php # Content-Sety-Policy: img-src 'self' *. -$DOMAIN; docker exec dd-apps-nextcloud-app sed -i -e "/protected \\\$allowedImageDomains = \[/{n;s/\('\\\\\'self\\\\\'\)\('\)/\1 *.$DOMAIN\2/}" /var/www/html/lib/public/AppFramework/Http/ContentSecurityPolicy.php # Content-Sety-Policy: style-src 'self' -*.$DOMAIN; docker exec dd-apps-nextcloud-app sed -i -e "/protected \\\$allowedStyleDomains = \[/{n;s/\('\\\\\'self\\\\\'\)\('\)/\1 *.$DOMAIN\2/}" /var/www/html/lib/public/AppFramework/Http/ContentSecurityPolicy.php # Content-Sety-Policy: font-src 'self' * -.$DOMAIN; docker exec dd-apps-nextcloud-app sed -i -e "/protected \\\$allowedFontDomains = \[/{n;s/\('\\\\\'self\\\\\'\)\('\)/\1 *.$DOMAIN\2/}" /var/www/html/lib/public/AppFramework/Http/ContentSecurityPolicy.php # Fix nextcloud files_external "segudos" typo # https://github.com/nextcloud/server/pull/28990 docker exec dd-apps-nextcloud-app sh -c 'sed -i.orig -e "s/segudos/segundos/" /var/www/html/apps/files_external/l10n/es_*.js' # Import fix from Nextcloud 22 of pdf viewer # https://github.com/nextcloud/files_pdfviewer/issues/381#issuecomment-845806364 docker exec dd-apps-nextcloud-app sed -i 's/encodeURIComponent(i\[a\])/i[a]/' /var/www/html/apps/files_pdfviewer/js/files_pdfviewer-main.js # Add default file for moodle activities if [ ! -f "$DATA_FOLDER/nextcloud/admin/files/template.docx" ]; then cp dd-apps/docker/nextcloud/template.docx "$DATA_FOLDER/nextcloud/admin/files/" nextcloud_scan fi configure_nextcloud_logo nextcloud_upgrade } nextcloud_upgrade(){ docker-compose exec -u www-data dd-apps-nextcloud-app ./occ upgrade } nextcloud_scan(){ # The folders shown as 'not writeable' are empty user folders. Not a problem. docker exec -u www-data dd-apps-nextcloud-app php occ files:scan --all } setup_moodle(){ echo " --> Applying custom settings in moodle" # TODO: check why admin/cli/cfg can't be read with -u nobody docker exec -i dd-apps-moodle sh -s <<-EOF php7 admin/cli/cfg.php --name=guestloginbutton --set=0 php7 admin/cli/cfg.php --name=enrol_plugins_enabled --set=manual php7 admin/cli/cfg.php --name=enablemobilewebservice --set=0 php7 admin/cli/cfg.php --name=enablebadges --set=0 php7 admin/cli/cfg.php --name=timezone --set="${MOODLE_TIMEZONE-Europe/Madrid}" php7 admin/cli/purge_caches.php EOF } setup_wordpress(){ echo " --> Applying custom settings in wordpress" chown -R 33:33 "${DATA_FOLDER}/wordpress" docker exec -i --user=33 dd-apps-wordpress sh -s <<-EOF wp core install --path="/var/www/html" \ --url=wp.${DOMAIN} --title="${TITLE}" \ --admin_user=${WORDPRESS_ADMIN_USER} \ --admin_password=${WORDPRESS_ADMIN_PASSWORD} \ --admin_email=${SMTP_USER} wp core multisite-convert wp plugin activate onelogin-saml-sso wp plugin install generateblocks --activate wp plugin activate generateblocks --network wp theme install generatepress --activate wp theme enable generatepress --network wp theme delete twentynineteen wp theme delete twentytwenty wp theme delete twentytwentyone wp option set WPLANG ca wp option set date_format "d/m/Y" EOF } setup_keycloak(){ echo " --> Setting up SAML: Keycloak realm and client_scopes" docker exec -i dd-sso-admin sh -s <<-EOF export PYTHONWARNINGS='ignore:Unverified HTTPS request' cd /admin/saml_scripts/ && python3 keycloak_config.py EOF } saml_generate_certificates(){ saml_dir="${DATA_FOLDER}/saml" mkdir -p "${saml_dir}/public" # Moodle generates its own certificate earlier, only NC and Wp for saml_info in nextcloud:82 wordpress:33; do saml_sp="$(echo "${saml_info}" | cut -d ':' -f 1)" sp_uid="$(echo "${saml_info}" | cut -d ':' -f 2)" sp_dir="${saml_dir}/${saml_sp}" mkdir -p "${sp_dir}" C=CA L=Barcelona O=localdomain CN_CA=$O # Generate certificate echo " --> Generating SAML certificates for SP: ${saml_sp}" openssl req -nodes -new -x509 \ -keyout "${sp_dir}/private.key" \ -out "${sp_dir}/public.crt" \ -subj "/C=$C/L=$L/O=$O/CN=$CN_CA" -days 3650 # Fix permissions chown -R "${sp_uid}:${sp_uid}" "${sp_dir}" chmod 0550 "${sp_dir}" # Propagate public part cp "${sp_dir}/public.crt" "${saml_dir}/public/${saml_sp}.crt" done # TODO: Rework wordpress_saml.py so we can get rid of this cp "${saml_dir}/wordpress/private.key" "${saml_dir}/public/wordpress.key" chmod 0555 "${saml_dir}/public" find "${saml_dir}/public" -type f -exec chmod 0444 '{}' '+' } saml_register_sps_with_idp(){ wait_for_moodle # Setup SAML for each SP for saml_sp in moodle nextcloud wordpress email; do echo " --> Registering SAML SP '${saml_sp}' in IDP" docker exec -i dd-sso-admin sh -s <<-EOF export PYTHONWARNINGS='ignore:Unverified HTTPS request' cd /admin/saml_scripts/ && python3 ${saml_sp}_saml.py EOF test $? == 0 || printf "\tError setting up SAML for %s...\n" "${saml_sp}" >&2 done # Purge cache for moodle docker exec -i dd-apps-moodle php7 admin/cli/purge_caches.php } saml_setup_idp_in_sps(){ # We need to support this for newer Nextcloud versions saml_info="dd-apps-nextcloud-app:82" saml_sp="$(echo "${saml_info}" | cut -d ':' -f 1)" sp_uid="$(echo "${saml_info}" | cut -d ':' -f 2)" echo " --> Setting up SAML IDP in ${saml_sp}" docker exec -i -u "${sp_uid}" "${saml_sp}" /saml.sh } saml(){ if [ "${1:-}" != "--no-up" ]; then up wait_for_moodle fi # Ensure realm is OK and write public IDP cert setup_keycloak # Make sure each SP has its own certs and fix permissions if needed saml_generate_certificates # Register all SPs with the IDP saml_register_sps_with_idp # Setup IDP in each SP saml_setup_idp_in_sps } wait_for_moodle(){ echo "Waiting for system to be fully up before customizing... It can take some minutes..." echo " (you can monitorize install with: docker logs dd-apps-moodle --follow" # This previously waited for the container to be 'healthy', # but that was not what is actually required here, which is: # - Is moodle installed? # - Is the web service up? # That is now checked in /is_moodle_ready.sh while ! docker exec dd-apps-moodle /is_moodle_ready.sh; do sleep 2; done } upgrade_moodle(){ docker exec -i dd-apps-moodle php7 admin/cli/maintenance.php --enable docker exec -i dd-apps-moodle php7 admin/cli/upgrade.php --non-interactive --allow-unstable docker exec -i dd-apps-moodle php7 admin/cli/maintenance.php --disable } extras_adminer(){ docker-compose -f dd-apps/docker/network.yml \ -f dd-sso/docker-compose-parts/adminer.yml config > adminer.yml echo " --> Generated adminer.yml" echo " Bring it up: docker-compose -f adminer.yml up -d" echo " Connect to: https://sso.$DOMAIN/dd-sso-adminer/" echo " Parameters:" echo " - System: PostgreSQL (or Mysql for wordpress db)" echo " Server: dd-apps-postgresql (or dd-apps-mariadb for wordpress db)" echo " User/Pass/Database from dd.conf" } extras_pgtuner(){ docker-compose -f dd-apps/docker/network.yml \ -f dd-sso/docker-compose-parts/pgtuner.yml config > pgtuner.yml echo " --> Generated pgtuner.yml" } extras_nextcloud_remove_banned_ips(){ docker-compose exec dd-apps-postgresql psql -v ON_ERROR_STOP=1 \ -U admin nextcloud -c "DELETE FROM oc_bruteforce_attempts;" } extras_nextcloud_set_admin_group(){ docker exec -u www-data dd-apps-nextcloud-app sh -c 'export OC_PASS=admin && php occ user:delete admin' docker exec -u www-data dd-apps-nextcloud-app sh -c 'export OC_PASS=N3xtcl0ud && php occ user:add --password-from-env --display-name="Admin" --group="admin" admin' } extras_dump_keycloak_client(){ docker exec -i dd-sso-keycloak sh -s <<-EOF /opt/jboss/keycloak/bin/kcadm.sh config credentials --server http://localhost:8080/auth --realm master --user admin --password keycloakkeycloak \ && /opt/jboss/keycloak/bin/kcadm.sh get clients/bef873f0-2079-4876-8657-067de27d01b7 -r master EOF } upgrade_plugins_moodle(){ wait_for_moodle rm -rf /tmp/moodle mkdir -p /tmp/moodle/mod mkdir -p /tmp/moodle/mod/assign/submission mkdir -p /tmp/moodle/auth/saml2 mkdir -p /tmp/moodle/theme/cbe mkdir -p /tmp/moodle/blocks mkdir -p /tmp/moodle/lib/editor/atto/plugins/tipnc curl --location "${MOODLE_PLUGIN_JITSI_OVERRIDE:-https://moodle.org/plugins/download.php/27002/mod_jitsi_moodle40_2022070602.zip}" > jitsi.zip unzip -q jitsi.zip -d /tmp/moodle/mod/ rm jitsi.zip curl --location "${MOODLE_PLUGIN_BBB_OVERRIDE:-https://moodle.org/plugins/download.php/26792/mod_bigbluebuttonbn_moodle311_2019101014.zip}" > bbb.zip unzip -q bbb.zip -d /tmp/moodle/mod/ rm bbb.zip # curl --location https://github.com/isard-vdi/moodle-auth_saml2/archive/refs/heads/role_map.zip > auth_saml2.zip # curl --location https://moodle.org/plugins/download.php/24556/auth_saml2_moodle311_2021062900.zip > auth_saml2.zip curl --location "${MOODLE_PLUGIN_SAML_OVERRIDE:-https://github.com/isard-vdi/moodle-auth_saml2/archive/refs/heads/role_map.zip}" > auth_saml2.zip unzip -q auth_saml2.zip -d /tmp/moodle/auth/ mv /tmp/moodle/auth/moodle-auth_saml2-role_map/* /tmp/moodle/auth/saml2/ rm -rf /tmp/moodle/auth/moodle-auth_saml2-role_map rm auth_saml2.zip M3IPSHARE="https://github.com/3iPunt/moodle_mod_tresipuntshare/archive/refs/heads/master.zip" if [[ "${MOODLE_PLUGIN_TRESIPUNTSHARE_OVERRIDE:-${M3IPSHARE}}" == *"develop"* ]]; then PLUGIN_BRANCH=develop else PLUGIN_BRANCH=master fi curl --location "${MOODLE_PLUGIN_TRESIPUNTSHARE_OVERRIDE:-${M3IPSHARE}}" > tresipuntshare.zip unzip -q tresipuntshare.zip -d /tmp/moodle/mod/ mv /tmp/moodle/mod/moodle_mod_tresipuntshare-$PLUGIN_BRANCH /tmp/moodle/mod/tresipuntshare rm tresipuntshare.zip M3IPVIDEO="https://github.com/3iPunt/moodle_mod_tresipuntvideo/archive/refs/heads/master.zip" if [[ "${MOODLE_PLUGIN_TRESIPUNTVIDEO_OVERRIDE:-${M3IPVIDEO}}" == *"develop"* ]]; then PLUGIN_BRANCH=develop else PLUGIN_BRANCH=master fi curl --location "${MOODLE_PLUGIN_TRESIPUNTVIDEO_OVERRIDE:-${M3IPVIDEO}}" > tresipuntvideo.zip unzip -q tresipuntvideo.zip -d /tmp/moodle/mod/ mv /tmp/moodle/mod/moodle_mod_tresipuntvideo-$PLUGIN_BRANCH /tmp/moodle/mod/tresipuntvideo rm tresipuntvideo.zip M3IPAUDIO="https://github.com/3iPunt/moodle_mod_tresipuntaudio/archive/refs/heads/master.zip" if [[ "${MOODLE_PLUGIN_TRESIPUNTAUDIO_OVERRIDE:-${M3IPAUDIO}}" == *"develop"* ]]; then PLUGIN_BRANCH=develop else PLUGIN_BRANCH=master fi curl --location "${MOODLE_PLUGIN_TRESIPUNTAUDIO_OVERRIDE:-${M3IPAUDIO}}" > tresipuntaudio.zip unzip -q tresipuntaudio.zip -d /tmp/moodle/mod/ mv /tmp/moodle/mod/moodle_mod_tresipuntaudio-$PLUGIN_BRANCH /tmp/moodle/mod/tresipuntaudio rm tresipuntaudio.zip M3IPSUBMISSION="https://github.com/3iPunt/moodle_assignsubmission_tipnc/archive/refs/heads/master.zip" if [[ "${MOODLE_PLUGIN_ASSIGNSUBMISSION_OVERRIDE:-${M3IPSUBMISSION}}" == *"develop"* ]]; then PLUGIN_BRANCH=develop else PLUGIN_BRANCH=master fi curl --location "${MOODLE_PLUGIN_ASSIGNSUBMISSION_OVERRIDE:-${M3IPSUBMISSION}}" > assignsubmission_tipnc.zip unzip -q assignsubmission_tipnc.zip -d /tmp/moodle/mod/assign/submission/ mv /tmp/moodle/mod/assign/submission/moodle_assignsubmission_tipnc-$PLUGIN_BRANCH /tmp/moodle/mod/assign/submission/tipnc rm assignsubmission_tipnc.zip M3IPSPEND="https://github.com/3iPunt/moodle_block_tresipuntmodspend/archive/refs/heads/master.zip" curl --location "${MOODLE_PLUGIN_TRESIPUNTMODSPEND_OVERRIDE:-${M3IPSPEND}}" > block_tresipuntmodspend.zip unzip -q block_tresipuntmodspend.zip -d /tmp/moodle/blocks/ rm block_tresipuntmodspend.zip M3IPCBE="https://github.com/3iPunt/moodle_theme_cbe/archive/refs/heads/master.zip" if [[ "${MOODLE_THEME_CBE_OVERRIDE:-${M3IPCBE}}" == *"develop"* ]]; then PLUGIN_BRANCH=develop else PLUGIN_BRANCH=master fi curl --location "${MOODLE_THEME_CBE_OVERRIDE:-${M3IPCBE}}" > tresipunt_theme_cbe.zip unzip -q tresipunt_theme_cbe.zip -d /tmp/moodle/theme/cbe/ mv /tmp/moodle/theme/cbe/moodle_theme_cbe-$PLUGIN_BRANCH/* /tmp/moodle/theme/cbe/ rm tresipunt_theme_cbe.zip # mod_tipnextcloud M3IPTN="https://github.com/3iPunt/mod_tipnextcloud/archive/refs/heads/master.zip" if [[ "${MOODLE_MOD_TN_OVERRIDE:-${M3IPTN}}" == *"develop"* ]]; then PLUGIN_BRANCH=develop else PLUGIN_BRANCH=master fi curl --location "${MOODLE_MOD_TN_OVERRIDE:-${M3IPTN}}" > tresipunt_mod_tn.zip unzip -q tresipunt_mod_tn.zip -d /tmp/moodle/mod/tipnextcloud/ mv /tmp/moodle/mod/tipnextcloud/mod_tipnextcloud-$PLUGIN_BRANCH/* /tmp/moodle/mod/tipnextcloud/ rm tresipunt_mod_tn.zip # atto_tipnc https://github.com/3iPunt/atto_tipnc M3ATTOTIPTN="https://github.com/3iPunt/atto_tipnc/archive/refs/heads/master.zip" if [[ "${MOODLE_ATTOTIPTN_OVERRIDE:-${M3ATTOTIPTN}}" == *"develop"* ]]; then PLUGIN_BRANCH=develop else PLUGIN_BRANCH=master fi curl --location "${MOODLE_ATTOTIPTN_OVERRIDE:-https://github.com/3iPunt/atto_tipnc/archive/refs/heads/master.zip}" > tresipunt_atto_tipnc.zip unzip -q tresipunt_atto_tipnc.zip -d /tmp/moodle/lib/editor/atto/plugins/tipnc/ mv /tmp/moodle/lib/editor/atto/plugins/tipnc/atto_tipnc-$PLUGIN_BRANCH/* /tmp/moodle/lib/editor/atto/plugins/tipnc/ rm tresipunt_atto_tipnc.zip # local_mail curl --location "${MOODLE_PLUGIN_MAIL_OVERRIDE:-https://moodle.org/plugins/download.php/26393/local_mail_moodle40_2017121407.zip}" > mail.zip unzip -q mail.zip -d /tmp/moodle/local/ rm mail.zip #mkdir -p /tmp/moodle/local/tresipuntimportgc #cp -R local_plugins/moodle/tresipuntimportgc/* /tmp/moodle/local/tresipuntimportgc/ cp -R /tmp/moodle/* "$SRC_FOLDER/moodle/" rm -rf /tmp/moodle docker exec -i dd-apps-moodle php7 admin/cli/purge_caches.php } upgrade_plugins_nextcloud(){ cp -R dd-apps/docker/nextcloud/src/* "$SRC_FOLDER/nextcloud/" nextcloud_upgrade } upgrade_plugins_wp(){ cp -R dd-apps/docker/wordpress/src/* "$SRC_FOLDER/wordpress/" if [ ! -d "$SRC_FOLDER/wordpress/wp-content/mu-plugins" ]; then git clone https://gitlab.com/muplugins-multiste1/muplugins-google-sites.git "$SRC_FOLDER/wordpress/wp-content/mu-plugins" fi if [ ! -d "$SRC_FOLDER/wordpress/wp-content/mu-plugins/.git" ]; then echo "WARNING: $SRC_FOLDER/wordpress/wp-content/mu-plugins is not a git repository." echo " This could be due to old installation. To bring all new mu-plugins code for WP" echo " remove that folder and it will be cloned and mantained with git from now on." else sh -c "cd $SRC_FOLDER/wordpress/wp-content/mu-plugins; git pull" fi chown -R 33:33 "${SRC_FOLDER}/wordpress/wp-content/mu-plugins" # TODO: Fix this, check https://gitlab.com/DD-workspace/DD/-/issues/16 ##install plugin gsite docker exec -i dd-apps-wordpress sh -s <<-EOF apt-get update apt-get install -y git apt install -y zlib1g-dev libjpeg-dev libpng-dev apt-get install -y python3 python3-click python3-scrapy python3-unidecode python3-pillow python3-slugify apt-get install -y curl git clone https://gitlab.com/isard/gsite2wordpress mv /var/www/html/gsite2wordpress /var/www curl -O https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar chmod +x wp-cli.phar mv wp-cli.phar /usr/local/bin/wp EOF } update_logos_and_menu(){ # docker exec -i dd-sso-keycloak sh -c "/opt/jboss/keycloak/bin/jboss-cli.sh --connect --command='/subsystem=keycloak-server/theme=defaults/:write-attribute(name=cacheThemes,value=false)'" # docker exec -i dd-sso-keycloak sh -c "/opt/jboss/keycloak/bin/jboss-cli.sh --connect --command='/subsystem=keycloak-server/theme=defaults/:write-attribute(name=cacheTemplates,value=false)'" # docker exec -i dd-sso-keycloak sh -c "/opt/jboss/keycloak/bin/jboss-cli.sh --connect --command='/subsystem=keycloak-server/theme=defaults/:write-attribute(name=staticMaxAge,value=-1)'" # docker exec -i dd-sso-keycloak sh -c "/opt/jboss/keycloak/bin/jboss-cli.sh --connect --command='reload'" docker exec -i --user root dd-sso-keycloak sh -c 'rm -rf /opt/jboss/keycloak/standalone/tmp/kc-gzip-cache/*' docker-compose build dd-sso-api && docker-compose up -d dd-sso-api configure_nextcloud_logo } configure_nextcloud_logo(){ local instance_id instance_id=$(docker exec -u www-data dd-apps-nextcloud-app php occ config:system:get instanceid) local cachebuster cachebuster=$(docker exec -u www-data dd-apps-nextcloud-app php occ config:app:get theming cachebuster) docker exec -u www-data dd-apps-nextcloud-app mkdir -p "/var/www/html/data/appdata_$instance_id/theming/images" nc_logo="${DATA_FOLDER}/nextcloud/appdata_$instance_id/theming/images/logo" nc_background="${DATA_FOLDER}/nextcloud/appdata_$instance_id/theming/images/background" cp custom/img/logo.png "${nc_logo}" cp custom/img/background.png "${nc_background}" chown 82:82 "${nc_logo}" "${nc_background}" docker exec -u www-data dd-apps-nextcloud-app php occ config:app:set theming logoMime --value="image/png" docker exec -u www-data dd-apps-nextcloud-app php occ config:app:set theming backgroundMime --value="image/png" docker exec -u www-data dd-apps-nextcloud-app php occ config:app:set theming cachebuster --value="$((cachebuster + 1 ))" } genpwd() { if [ ! -f /usr/share/dict/words ]; then prerequisites_pwd > /dev/null fi shuf -n3 /usr/share/dict/words | tr -d "\n" | tr -d "'" } securize() { for dd_var in \ ADMINAPP_PASSWORD \ DDADMIN_PASSWORD \ KEYCLOAK_PASSWORD \ KEYCLOAK_DB_PASSWORD \ POSTGRES_PASSWORD \ MARIADB_PASSWORD \ MOODLE_POSTGRES_PASSWORD \ MOODLE_ADMIN_PASSWORD \ NEXTCLOUD_POSTGRES_PASSWORD \ NEXTCLOUD_ADMIN_PASSWORD \ ETHERPAD_POSTGRES_PASSWORD \ ETHERPAD_ADMIN_PASSWORD \ WORDPRESS_MARIADB_PASSWORD \ WORDPRESS_ADMIN_PASSWORD \ IPA_ADMIN_PWD; do setconf "${dd_var}" "$(genpwd)" done } setconf() { dd_var="$(echo "$1" | tr "[:lower:]" "[:upper:]")" dd_val="$2" dd_line="$(printf '%s="%s"' "${dd_var:?}" "${dd_val}")" conf_file="${3:-dd.conf}" if grep -qE "^${dd_var:?}=" "${conf_file}"; then # Found uncommented, replace in-place sed -i'' -E "s!^${dd_var:?}=.*\$!${dd_line}!" "${conf_file}" elif grep -qE "^#[[:space:]]*${dd_var:?}=" "${conf_file}"; then # Found commented, replace in-place sed -i'' -E "s!^#[[:space:]]*${dd_var:?}=.*\$!${dd_line}!" "${conf_file}" else # Not found, append echo "${dd_line}" >> "${conf_file}" fi } special_image_tags() { # Special image tags that apply to this build head="$(git rev-parse HEAD)" # loop through BRANCH:TAG1[:TAG2:...:TAG_N] items for tag_info in develop:latest main:stable; do branch="$(echo "${tag_info}" | cut -d ':' -f 1)" upstream="$(git rev-parse "${DD_REMOTE:-origin}/${branch}" 2>/dev/null || true)" if [ "${head}" = "${upstream}" ]; then # If head and upstream match, use these tags special_tags="$(echo "${tag_info}" | cut -d ':' -f 2- | tr ':' ' ')" echo "${special_tags}" fi done } push_images() { # # Note this requires docker login on the registry # (Runs on CI) # # Get images that are using the registry images="$(docker ps --format '{{ .Names }}\t{{ .Image }}' | grep "${DD_REGISTRY:-registry.dd-work.space}")" for extra_tag in $(special_image_tags); do # And apply the special tags on them for image in $(echo "${images}" | cut -f 2); do docker tag "${image}" "${image%:*}:${extra_tag}" done done image_names="$(echo "${images}" | cut -f 1 | tr '\n' '\t')" # Finally, actually push all tags to the registry # shellcheck disable=SC2086 # We do want multiple arguments docker-compose push ${image_names:?} } # Argument handling case "$OPERATION" in build) build ;; build-devel) build_compose_develop ;; adminer) extras_adminer ;; all) build up wait_for_moodle upgrade_plugins_moodle upgrade_plugins_nextcloud upgrade_plugins_wp setup_nextcloud setup_moodle setup_wordpress saml --no-up cat <<-EOF #### After install #### - SSO in moodle should be active. You can go to: https://moodle.$DOMAIN If it fails, regenerate and lock certificate in moodle SAML2 connector as a local admin. After that run ./dd-ctl saml - SSO in nextcloud should be active. You can go to: https://nextcloud.$DOMAIN - SSO in wordpress should be active. You should go to https://wp.$DOMAIN/wp-admin//plugins.php #### Update customizations #### - ./dd-ctl customize EOF ;; branding) up wait_for_moodle update_logos_and_menu ;; customize) up wait_for_moodle setup_nextcloud setup_wordpress setup_moodle ;; down) down ;; nextcloud-scan) nextcloud_scan ;; pgtuner) extras_pgtuner ;; prerequisites) prerequisites_docker prerequisites_pwd ;; update) ddupdate --full ;; repo-update) ddupdate ;; reset-data|reset-1714) cat <<-EOF # Following commands RESET ALL DATA except for certificates # execute them only if you know what you are doing # This *will* result in DATA LOSS "$0" down rm -rf /opt/DD/data/* rm -rf /opt/DD/db/* rm -rf '$SRC_FOLDER/avatars' rm -rf '$SRC_FOLDER/moodle' rm -rf '$SRC_FOLDER/nextcloud' rm -rf '$SRC_FOLDER/wordpress' EOF ;; restart-api) up wait_for_moodle docker restart dd-sso-api ;; saml) saml "$@" ;; securize) securize ;; setconf) setconf "$@" ;; up) up ;; upgrade-plugins) up wait_for_moodle upgrade_plugins_moodle upgrade_plugins_nextcloud upgrade_plugins_wp ;; yml) cp dd.conf .env CUSTOM_PATH=$(pwd) . ./.env build_compose ;; listpatches) listpatches ;; push-images) push_images ;; failing-containers) fc="$(docker ps --format '{{.Names}}' -f 'health=unhealthy')" if [ -n "${fc}" ]; then cat >&2 <<-EOF Failing containers: ${fc} EOF exit 1 fi ;; genpatches) genpatches ;; *) printf "Unknown command '%s'\n\n" "$OPERATION" >&2 help >&2 exit 1 ;; esac