diff --git a/.docker/wordpress/mu-plugins/nextcloud-dev-proxy.php b/.docker/wordpress/mu-plugins/nextcloud-dev-proxy.php new file mode 100644 index 0000000..dd693e9 --- /dev/null +++ b/.docker/wordpress/mu-plugins/nextcloud-dev-proxy.php @@ -0,0 +1,53 @@ + 30, + 'body' => array( + 'groupid' => 'admlibrecode', + 'email' => 'adm@librecode.coop', + 'displayname' => 'Adm Librecode', + ), + ) +); + +if ( ! $response->succeeded() ) { + WP_CLI::error( 'Could not provision the admin group in Nextcloud: ' . $response->failure_message() ); +} + +WP_CLI::success( 'Admin group provisioned in Nextcloud.' ); diff --git a/.github/workflows/compose-config.yml b/.github/workflows/compose-config.yml new file mode 100644 index 0000000..d27364d --- /dev/null +++ b/.github/workflows/compose-config.yml @@ -0,0 +1,18 @@ +name: Compose configuration + +on: + pull_request: + push: + branches: + - main + +jobs: + compose-config: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + with: + submodules: true + + - name: Validate Compose configuration + run: make test-config diff --git a/Makefile b/Makefile index 21273ce..8db5e40 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: up down help _help site wordpress nextcloud _up-site _up-wordpress _up-nextcloud _down-site _down-wordpress _down-nextcloud _up-integration _ensure-site-repo +.PHONY: up down test test-config test-integration help _help site wordpress nextcloud _up-site _up-wordpress _up-nextcloud _down-site _down-wordpress _down-nextcloud _up-integration _ensure-site-repo -include .env export @@ -13,18 +13,18 @@ SITE_REPO_BRANCH ?= main SITE_HTTP_PORT ?= 8081 SITE_BROWSERSYNC_PORT ?= 3000 SITE_SERVER_MODE ?= build -NEXTCLOUD_HTTP_PORT ?= 8082 SITE_BASE_URL := http://localhost:$(SITE_HTTP_PORT) -NEXTCLOUD_BASE_URL := http://localhost:$(NEXTCLOUD_HTTP_PORT) NEXTCLOUD_APPS_DIR := $(NEXTCLOUD_DIR)/volumes/nextcloud/apps-extra LOCAL_UID := $(shell id -u) LOCAL_GID := $(shell id -g) -WORDPRESS_SITE_URL ?= http://localhost +WORDPRESS_HTTP_PORT ?= 8080 +WORDPRESS_LEGACY_SITE_URL := http://localhost +WORDPRESS_SITE_URL ?= http://127.0.0.1:$(WORDPRESS_HTTP_PORT) WORDPRESS_SITE_TITLE ?= LibreSign SaaS WORDPRESS_ADMIN_USER ?= admin WORDPRESS_ADMIN_PASSWORD ?= admin WORDPRESS_ADMIN_EMAIL ?= admin@example.com -WORDPRESS_WEBHOOK_BASE_URL ?= http://host.docker.internal +WORDPRESS_WEBHOOK_BASE_URL ?= http://host.docker.internal:$(WORDPRESS_HTTP_PORT) LIBRESIGN_PUBLISH_HEADER_FRAGMENTS ?= true LIBRESIGN_HEADER_WEBHOOK_URL ?= $(WORDPRESS_WEBHOOK_BASE_URL)/wp-json/libresign/v1/header-fragment LIBRESIGN_HEADER_WEBHOOK_SECRET ?= change-me-header @@ -38,11 +38,15 @@ SELECTED_COMPONENTS := $(filter $(COMPONENT_TARGETS),$(MAKECMDGOALS)) UP_COMPONENTS := $(if $(SELECTED_COMPONENTS),$(SELECTED_COMPONENTS),$(COMPONENT_TARGETS)) # Docker Compose commands -NEXTCLOUD_COMPOSE := HTTP_PORT=$(NEXTCLOUD_HTTP_PORT) docker compose -f $(NEXTCLOUD_DIR)/docker-compose.yml +NEXTCLOUD_COMPOSE := docker compose -f $(NEXTCLOUD_DIR)/docker-compose.yml WORDPRESS_COMPOSE := docker compose -f $(WORDPRESS_DIR)/docker-compose.yml -f $(ROOT_DIR)/docker-compose.override.yml -SITE_COMPOSE := UID=$(LOCAL_UID) GID=$(LOCAL_GID) HTTP_PORT=$(SITE_HTTP_PORT) HTTP_PORT_BROWSERSYNC=$(SITE_BROWSERSYNC_PORT) SERVER_MODE=$(SITE_SERVER_MODE) URL_SITE=$(SITE_BASE_URL) LIBRESIGN_PUBLISH_HEADER_FRAGMENTS=$(LIBRESIGN_PUBLISH_HEADER_FRAGMENTS) LIBRESIGN_HEADER_WEBHOOK_URL=$(LIBRESIGN_HEADER_WEBHOOK_URL) LIBRESIGN_HEADER_WEBHOOK_SECRET=$(LIBRESIGN_HEADER_WEBHOOK_SECRET) LIBRESIGN_PUBLISH_FOOTER_FRAGMENTS=$(LIBRESIGN_PUBLISH_FOOTER_FRAGMENTS) LIBRESIGN_FOOTER_WEBHOOK_URL=$(LIBRESIGN_FOOTER_WEBHOOK_URL) LIBRESIGN_FOOTER_WEBHOOK_SECRET=$(LIBRESIGN_FOOTER_WEBHOOK_SECRET) docker compose -f $(SITE_DIR)/docker-compose.yml +SITE_COMPOSE := env UID=$(LOCAL_UID) GID=$(LOCAL_GID) HTTP_PORT=$(SITE_HTTP_PORT) HTTP_PORT_BROWSERSYNC=$(SITE_BROWSERSYNC_PORT) SERVER_MODE=$(SITE_SERVER_MODE) URL_SITE=$(SITE_BASE_URL) LIBRESIGN_PUBLISH_HEADER_FRAGMENTS=$(LIBRESIGN_PUBLISH_HEADER_FRAGMENTS) LIBRESIGN_HEADER_WEBHOOK_URL=$(LIBRESIGN_HEADER_WEBHOOK_URL) LIBRESIGN_HEADER_WEBHOOK_SECRET=$(LIBRESIGN_HEADER_WEBHOOK_SECRET) LIBRESIGN_PUBLISH_FOOTER_FRAGMENTS=$(LIBRESIGN_PUBLISH_FOOTER_FRAGMENTS) LIBRESIGN_FOOTER_WEBHOOK_URL=$(LIBRESIGN_FOOTER_WEBHOOK_URL) LIBRESIGN_FOOTER_WEBHOOK_SECRET=$(LIBRESIGN_FOOTER_WEBHOOK_SECRET) docker compose -f $(SITE_DIR)/docker-compose.yml NEXTCLOUD_OCC := $(NEXTCLOUD_COMPOSE) exec -u www-data nextcloud php occ WORDPRESS_CLI := $(WORDPRESS_COMPOSE) exec wordpress wp --allow-root +WORDPRESS_CLI_STDIN := $(WORDPRESS_COMPOSE) exec -T wordpress wp --allow-root +NEXTCLOUD_NETWORK = $$($(NEXTCLOUD_COMPOSE) config | sed -n 's/^name: //p')_default +WORDPRESS_NETWORK = $$($(WORDPRESS_COMPOSE) config | sed -n 's/^name: //p')_default +NCDD_PROXY_FILTER := label=coop.librecode.dev-proxy=true _help: @echo "LibreSign SaaS - Available commands:" @@ -57,19 +61,23 @@ _help: @echo " make down site - Stop only the static site stack" @echo " make down nextcloud - Stop only the Nextcloud stack" @echo " make down wordpress nextcloud - Stop only the selected stacks" + @echo " make test - Validate the Compose files and the running integration" + @echo " make test-config - Validate the Compose files only" @echo "" @echo "Environment variables:" @echo " SITE_HTTP_PORT - Static site port (default: 8081)" @echo " SITE_BROWSERSYNC_PORT - Static site HMR port (default: 3000)" @echo " SITE_SERVER_MODE - Static site container mode for make up (default: build)" - @echo " NEXTCLOUD_HTTP_PORT - Nextcloud port (default: 8082)" + @echo " WORDPRESS_HTTP_PORT - WordPress port (default: 8080)" + @echo " NEXTCLOUD_HOST - Nextcloud hostname behind the shared proxy (default: nextcloud-development.localhost)" + @echo " NEXTCLOUD_PROTOCOL - Nextcloud protocol behind the shared proxy (default: https)" @echo " NEXTCLOUD_ADMIN_USER - Nextcloud admin username (default: admin)" @echo " NEXTCLOUD_ADMIN_PASSWORD - Nextcloud admin password (default: admin)" - @echo " WORDPRESS_SITE_URL - WordPress site URL for first install (default: http://localhost)" + @echo " WORDPRESS_SITE_URL - WordPress site URL for first install (default: http://127.0.0.1:8080)" @echo " WORDPRESS_ADMIN_USER - WordPress admin username for first install (default: admin)" @echo " WORDPRESS_ADMIN_PASSWORD - WordPress admin password for first install (default: admin)" @echo " WORDPRESS_ADMIN_EMAIL - WordPress admin email for first install (default: admin@example.com)" - @echo " WORDPRESS_WEBHOOK_BASE_URL - Hostname used by the static site container to reach WordPress webhooks (default: http://host.docker.internal)" + @echo " WORDPRESS_WEBHOOK_BASE_URL - Hostname used by the static site container to reach WordPress webhooks (default: http://host.docker.internal:8080)" @echo " LIBRESIGN_PUBLISH_HEADER_FRAGMENTS - Publish shared header artifacts after site build (default: true)" @echo " LIBRESIGN_HEADER_WEBHOOK_URL - Target WordPress header webhook URL" @echo " LIBRESIGN_HEADER_WEBHOOK_SECRET - Shared secret for header artifact publishing" @@ -84,34 +92,44 @@ help: _help up: @for component in $(UP_COMPONENTS); do \ - $(MAKE) --no-print-directory _up-$$component; \ + $(MAKE) --no-print-directory _up-$$component || exit 1; \ done @if [ -n "$(filter wordpress,$(UP_COMPONENTS))" ] && [ -n "$(filter nextcloud,$(UP_COMPONENTS))" ]; then \ - $(MAKE) --no-print-directory _up-integration; \ + $(MAKE) --no-print-directory _up-integration || exit 1; \ fi @echo "Environment up ($(UP_COMPONENTS))." site wordpress nextcloud: @: +test: test-config test-integration + +test-config: + @echo "Validating Compose configuration..." + @$(NEXTCLOUD_COMPOSE) config --quiet + @$(WORDPRESS_COMPOSE) config --quiet + +test-integration: + @sh $(ROOT_DIR)/tests/integration.sh + down: @for component in $(UP_COMPONENTS); do \ - $(MAKE) --no-print-directory _down-$$component; \ + $(MAKE) --no-print-directory _down-$$component || exit 1; \ done @echo "Environment down ($(UP_COMPONENTS))." _down-site: $(SITE_COMPOSE) down -_down-wordpress: +_down-wordpress: _disconnect-networks $(WORDPRESS_COMPOSE) down -_down-nextcloud: +_down-nextcloud: _disconnect-networks $(NEXTCLOUD_COMPOSE) down _up-site: _ensure-site-repo _prepare-site-output-dir _refresh-site-images _start-site -_up-wordpress: _refresh-wordpress-images _start-wordpress _install-wordpress _wait-wordpress _enable-wordpress-plugin +_up-wordpress: _refresh-wordpress-images _start-wordpress _install-wordpress _wait-wordpress _migrate-wordpress-url _enable-wordpress-plugin _up-nextcloud: _refresh-nextcloud-images _start-nextcloud _wait-nextcloud _fix-nextcloud-apps-permissions @@ -147,7 +165,7 @@ _refresh-wordpress-images: _refresh-nextcloud-images: @echo "Refreshing Nextcloud images..." - @$(NEXTCLOUD_COMPOSE) pull mysql redis nextcloud nginx + @$(NEXTCLOUD_COMPOSE) pull --ignore-buildable _start-site: @echo "Starting site services..." @@ -165,11 +183,12 @@ _start-site: _start-wordpress: @echo "Starting WordPress services..." + @docker volume create librecode-dev-proxy-certs >/dev/null @$(WORDPRESS_COMPOSE) up -d mariadb wordpress nginx mailpit _start-nextcloud: @echo "Starting Nextcloud services..." - @$(NEXTCLOUD_COMPOSE) up -d mysql redis nextcloud nginx + @$(NEXTCLOUD_COMPOSE) up -d _install-wordpress: @echo "Ensuring WordPress core is installed..." @@ -208,11 +227,12 @@ _install-wordpress: _wait-wordpress: @echo "Waiting for WordPress to be ready..." @attempt=0; \ - until output=$$($(WORDPRESS_CLI) option get siteurl 2>&1); do \ + until output=$$($(WORDPRESS_CLI) option get siteurl 2>&1) && $(WORDPRESS_COMPOSE) exec -T wordpress grep -qx php-fpm /proc/1/comm; do \ attempt=$$((attempt + 1)); \ - if [ $$attempt -ge 60 ]; then \ - echo "WordPress is not ready after 120s"; \ + if [ $$attempt -ge 300 ]; then \ + echo "WordPress did not finish installing plugins and themes after 600s"; \ echo "$$output"; \ + $(WORDPRESS_COMPOSE) logs --tail 50 wordpress; \ exit 1; \ fi; \ sleep 2; \ @@ -221,10 +241,11 @@ _wait-wordpress: _wait-nextcloud: @echo "Waiting for Nextcloud to be ready..." @attempt=0; \ - until $(NEXTCLOUD_COMPOSE) exec -T nextcloud pgrep php-fpm >/dev/null 2>&1; do \ + until $(NEXTCLOUD_OCC) status 2>/dev/null | grep -q 'installed: true' && $(NEXTCLOUD_COMPOSE) exec -T nextcloud pgrep php-fpm >/dev/null 2>&1; do \ attempt=$$((attempt + 1)); \ - if [ $$attempt -ge 120 ]; then \ - echo "Nextcloud is not ready for app operations after 240s"; \ + if [ $$attempt -ge 120 ] || ! $(NEXTCLOUD_COMPOSE) ps --status running --services | grep -qx nextcloud; then \ + echo "Nextcloud is not ready for app operations"; \ + $(NEXTCLOUD_COMPOSE) logs --tail 50 nextcloud; \ exit 1; \ fi; \ sleep 2; \ @@ -234,11 +255,19 @@ _fix-nextcloud-apps-permissions: @echo "Fixing Nextcloud apps-extra permissions..." @$(NEXTCLOUD_COMPOSE) exec -u root nextcloud sh -lc 'mkdir -p /var/www/html/apps-extra && chown -R $(LOCAL_UID):$(LOCAL_GID) /var/www/html/apps-extra' >/dev/null +_migrate-wordpress-url: + @for option in home siteurl; do \ + if [ "$$($(WORDPRESS_CLI_STDIN) option get $$option)" = "$(WORDPRESS_LEGACY_SITE_URL)" ]; then \ + echo "Moving WordPress $$option from $(WORDPRESS_LEGACY_SITE_URL) to $(WORDPRESS_SITE_URL)..."; \ + $(WORDPRESS_CLI_STDIN) option update $$option "$(WORDPRESS_SITE_URL)" >/dev/null || exit 1; \ + fi; \ + done + _enable-wordpress-plugin: @echo "Enabling WordPress plugin..." - @$(WORDPRESS_CLI) plugin activate woocommerce-nextcloud-admin-group-manager >/dev/null || true + @$(WORDPRESS_CLI) plugin activate woocommerce-nextcloud-admin-group-manager -_setup-apps: _ensure-wordpress-app _ensure-nextcloud-app _enable-apps _set-wordpress-dsn +_setup-apps: _ensure-wordpress-app _ensure-nextcloud-app _enable-apps _set-wordpress-dsn _set-nextcloud-api-host _ensure-wordpress-app: @echo "Setting up wordpress_login_backend app..." @@ -260,25 +289,45 @@ _ensure-nextcloud-app: _enable-apps: @echo "Enabling apps..." - @$(NEXTCLOUD_OCC) app:enable wordpress_login_backend >/dev/null || true - @$(NEXTCLOUD_OCC) app:enable admin_group_manager --force >/dev/null || true - @$(NEXTCLOUD_OCC) app:enable groupquota --force >/dev/null || true + @$(NEXTCLOUD_OCC) app:enable wordpress_login_backend --force + @$(NEXTCLOUD_OCC) app:enable admin_group_manager --force + @$(NEXTCLOUD_OCC) app:enable groupquota --force _set-wordpress-dsn: - @$(NEXTCLOUD_OCC) config:system:set wordpress_dsn --value "mysql:host=mariadb;port=3306;dbname=wordpress;user=root;password=root" >/dev/null + @$(NEXTCLOUD_OCC) config:system:set wordpress_dsn --value "mysql:host=wordpress-mariadb;port=3306;dbname=wordpress;user=root;password=root" >/dev/null + +_set-nextcloud-api-host: + @nextcloud_url=$$($(NEXTCLOUD_COMPOSE) exec -T nextcloud sh -c 'printf "%s://%s" "$${NEXTCLOUD_PROTOCOL:?}" "$${NEXTCLOUD_HOST:?}"') && \ + $(WORDPRESS_CLI) option update nextcloud_api_host "$$nextcloud_url" >/dev/null _connect-networks: - @echo "Connecting Docker networks..." - @NEXTCLOUD_CONTAINER=$$($(NEXTCLOUD_COMPOSE) ps -q nextcloud); \ - WORDPRESS_CONTAINER=$$($(WORDPRESS_COMPOSE) ps -q mariadb); \ - WORDPRESS_NETWORK=$$(docker inspect -f '{{range $$k,$$v := .NetworkSettings.Networks}}{{println $$k}}{{end}}' $$WORDPRESS_CONTAINER | head -n1); \ - docker network connect $$WORDPRESS_NETWORK $$NEXTCLOUD_CONTAINER 2>/dev/null || true + @echo "Connecting the Nextcloud proxy and the WordPress database..." + @nextcloud_host=$$($(NEXTCLOUD_COMPOSE) exec -T nextcloud sh -c 'printf "%s" "$${NEXTCLOUD_HOST:?}"') || exit 1; \ + proxy=$$(docker ps -q --filter $(NCDD_PROXY_FILTER)); \ + mariadb=$$($(WORDPRESS_COMPOSE) ps -q mariadb); \ + if [ -z "$$proxy" ] || [ -z "$$mariadb" ]; then \ + echo "The NCDD shared proxy and the WordPress database must be running."; \ + exit 1; \ + fi; \ + connect() { \ + docker inspect -f '{{json .NetworkSettings.Networks}}' "$$3" | grep -q "\"$$1\"" || docker network connect --alias "$$2" "$$1" "$$3"; \ + }; \ + connect "$(WORDPRESS_NETWORK)" "$$nextcloud_host" "$$proxy" && \ + connect "$(NEXTCLOUD_NETWORK)" wordpress-mariadb "$$mariadb" + +_disconnect-networks: + @disconnect() { \ + if docker inspect -f '{{json .NetworkSettings.Networks}}' "$$2" | grep -q "\"$$1\""; then \ + docker network disconnect "$$1" "$$2"; \ + fi; \ + }; \ + for proxy in $$(docker ps -q --filter $(NCDD_PROXY_FILTER)); do \ + disconnect "$(WORDPRESS_NETWORK)" "$$proxy" || exit 1; \ + done; \ + for mariadb in $$($(WORDPRESS_COMPOSE) ps -q mariadb); do \ + disconnect "$(NEXTCLOUD_NETWORK)" "$$mariadb" || exit 1; \ + done _provision-user: @echo "Provisioning admin user..." - @curl -sS -u admin:admin \ - -X POST "$(NEXTCLOUD_BASE_URL)/ocs/v2.php/apps/admin_group_manager/api/v1/admin-group" \ - -H "OCS-APIREQUEST: true" \ - -d "groupid=admlibrecode" \ - -d "email=adm@librecode.coop" \ - -d "displayname=Adm Librecode" >/dev/null || true + @$(WORDPRESS_CLI_STDIN) eval-file - < $(ROOT_DIR)/.docker/wordpress/provision-admin-group.php diff --git a/README.md b/README.md index 7ecae1e..00ea8e1 100644 --- a/README.md +++ b/README.md @@ -75,9 +75,17 @@ To keep local development simple and avoid `/etc/hosts` changes, the static site is exposed on its own localhost port instead of using the production subdomain layout: -- WordPress: +- WordPress: - Static site: -- Nextcloud: +- Nextcloud: , served by the + `nextcloud-development` shared proxy, which owns host ports `80` and `443` + +WordPress reaches Nextcloud through the same HTTPS URL and validates its +certificate against the shared proxy's local certificate authority. WordPress +uses `127.0.0.1` instead of `localhost` because the shared proxy sends HSTS for +`https://localhost`, which makes browsers upgrade `http://localhost:8080` to +HTTPS. Existing installs that still use `http://localhost` are moved to the new +URL by `make up`. `make up` refreshes the remote Docker images used by the WordPress, static site, and Nextcloud services and rebuilds local buildable services before starting the @@ -89,6 +97,11 @@ On a fresh WordPress database, `make up` also performs the initial WordPress installation via WP-CLI and restarts the WordPress container once so the `wordpress-docker` entrypoint can install the configured plugins and themes. +`make test` validates the Compose configuration and checks the running +integration: required services, the WordPress → Nextcloud HTTPS connection, an +authenticated Nextcloud API request, the required apps and plugins, and that +running `make up` again keeps the existing configuration. + ## Integration Flow WordPress is the customer-facing commerce and account portal (plans, subscriptions, invoices, payments, and account reports). diff --git a/docker-compose.override.yml b/docker-compose.override.yml index de40397..ddee26e 100644 --- a/docker-compose.override.yml +++ b/docker-compose.override.yml @@ -2,8 +2,7 @@ services: wordpress: environment: PROD_SITE_URL: https://account.libresign.coop - LOCAL_SITE_URL: http://localhost - NEXTCLOUD_LOCAL_URL: ${NEXTCLOUD_LOCAL_URL:-http://host.docker.internal:8082} + LOCAL_SITE_URL: http://127.0.0.1:${WORDPRESS_HTTP_PORT:-8080} WORDPRESS_LOCAL_USERS_PASSWORD: ${WORDPRESS_LOCAL_USERS_PASSWORD:-admin} WORDPRESS_LOCAL_RESET_ALL_USERS_PASSWORDS: ${WORDPRESS_LOCAL_RESET_ALL_USERS_PASSWORDS:-1} WORDPRESS_LOCAL_RESET_PASSWORD_FOR_USER: ${WORDPRESS_LOCAL_RESET_PASSWORD_FOR_USER:-} @@ -48,7 +47,6 @@ services: - slug: woocommerce-nextcloud-admin-group-manager source: https://github.com/LibreSign/woocommerce-nextcloud-admin-group-manager.git post_install_commands: - - wp option update nextcloud_api_host $$NEXTCLOUD_LOCAL_URL - wp option update nextcloud_api_login ${NEXTCLOUD_ADMIN_USER:-admin} - wp option update nextcloud_api_password ${NEXTCLOUD_ADMIN_PASSWORD:-admin} - slug: woocommerce-restrict-switch @@ -58,10 +56,13 @@ services: wordpress_custom_themes: - slug: libresign source: https://github.com/LibreSign/wordpress-theme.git + volumes: + - ../.docker/wordpress/mu-plugins/nextcloud-dev-proxy.php:/var/www/html/wp-content/mu-plugins/nextcloud-dev-proxy.php + - librecode-dev-proxy-certs:/etc/librecode-dev-proxy-certs:ro nginx: ports: - - "80:80" + - "${WORDPRESS_HTTP_PORT:-8080}:80" volumes: - ../.docker/wordpress-nginx/default.conf:/etc/nginx/conf.d/default.conf:ro @@ -74,3 +75,8 @@ services: image: axllent/mailpit:latest ports: - "127.0.0.1:8025:8025" + +volumes: + librecode-dev-proxy-certs: + name: librecode-dev-proxy-certs + external: true diff --git a/nextcloud-development b/nextcloud-development index 56660f4..812fe2d 160000 --- a/nextcloud-development +++ b/nextcloud-development @@ -1 +1 @@ -Subproject commit 56660f41f5786a925ab85335b91ec599e02e8429 +Subproject commit 812fe2d9b1bbf3c42fe45fc26df5b3f74fa15bcc diff --git a/tests/integration.sh b/tests/integration.sh new file mode 100644 index 0000000..dc846e7 --- /dev/null +++ b/tests/integration.sh @@ -0,0 +1,122 @@ +#!/bin/sh + +set -eu + +: "${NEXTCLOUD_COMPOSE:?Run this test with make test-integration}" +: "${WORDPRESS_COMPOSE:?Run this test with make test-integration}" + +root_dir="$(cd -- "$(dirname -- "$0")/.." && pwd)" + +nextcloud_compose() { + $NEXTCLOUD_COMPOSE "$@" +} + +wordpress_compose() { + $WORDPRESS_COMPOSE "$@" +} + +occ() { + nextcloud_compose exec -T -u www-data nextcloud php occ "$@" +} + +wp() { + wordpress_compose exec -T wordpress wp --allow-root "$@" +} + +fail() { + echo "not ok - $1" >&2 + exit 1 +} + +pass() { + echo "ok - $1" +} + +assert_services_running() { + stack="$1" + shift + running="$("${stack}_compose" ps --status running --services)" || fail "list the running $stack services" + for service in "$@"; do + echo "$running" | grep -qx "$service" || fail "$stack service $service is running" + done + pass "$stack services are running" +} + +nextcloud_url() { + nextcloud_compose exec -T nextcloud sh -c 'printf "%s://%s" "${NEXTCLOUD_PROTOCOL:?}" "${NEXTCLOUD_HOST:?}"' +} + +assert_wordpress_uses_nextcloud_url() { + expected="$(nextcloud_url)" || fail "read the Nextcloud URL configured by NCDD" + actual="$(wp option get nextcloud_api_host)" || fail "read the Nextcloud URL configured in WordPress" + [ "$actual" = "$expected" ] || fail "WordPress uses $expected for Nextcloud (got $actual)" + pass "WordPress uses the Nextcloud URL configured by NCDD" +} + +assert_wordpress_uses_its_own_services() { + mailpit_ips="$(docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}} {{end}}' "$(wordpress_compose ps -q mailpit)")" || fail "inspect the WordPress mailpit" + resolved="$(wordpress_compose exec -T wordpress getent hosts mailpit | awk '{print $1}')" || fail "resolve mailpit from WordPress" + case " $mailpit_ips " in + *" $resolved "*) pass "WordPress resolves mailpit to its own service" ;; + *) fail "WordPress resolves mailpit to its own service (got $resolved, expected one of $mailpit_ips)" ;; + esac +} + +assert_wordpress_reaches_nextcloud() { + wp eval ' + $response = wp_remote_get( rtrim( get_option( "nextcloud_api_host" ), "/" ) . "/status.php", array( "sslverify" => true ) ); + if ( is_wp_error( $response ) || 200 !== wp_remote_retrieve_response_code( $response ) ) { + WP_CLI::error( is_wp_error( $response ) ? $response->get_error_message() : wp_remote_retrieve_body( $response ) ); + } + ' >/dev/null || fail "WordPress reaches Nextcloud with a verified TLS certificate" + pass "WordPress reaches Nextcloud with a verified TLS certificate" +} + +assert_authenticated_request() { + wp eval ' + $response = agm_nextcloud_request( "GET", "/ocs/v2.php/cloud/user?format=json" ); + if ( ! $response->succeeded() ) { + WP_CLI::error( $response->failure_message() ); + } + ' >/dev/null || fail "WordPress performs an authenticated Nextcloud API request" + pass "WordPress performs an authenticated Nextcloud API request" +} + +assert_integration_enabled() { + enabled_apps="$(occ app:list --enabled)" || fail "list the enabled Nextcloud apps" + for app in wordpress_login_backend admin_group_manager groupquota; do + echo "$enabled_apps" | grep -q "^ - $app:" || fail "Nextcloud app $app is enabled" + done + wp plugin is-active woocommerce-nextcloud-admin-group-manager || fail "WordPress plugin woocommerce-nextcloud-admin-group-manager is active" + occ group:info admlibrecode >/dev/null || fail "Nextcloud group admlibrecode is provisioned" + pass "Integration apps, plugin and admin group are in place" +} + +configuration_snapshot() { + occ config:system:get trusted_domains && + occ config:system:get overwritehost && + occ config:system:get overwriteprotocol && + occ config:system:get wordpress_dsn && + wp option get nextcloud_api_host && + wp option get siteurl +} + +assert_environment() { + assert_services_running nextcloud database redis nextcloud nginx proxy-coordinator + assert_services_running wordpress mariadb wordpress nginx mailpit + assert_wordpress_uses_nextcloud_url + assert_wordpress_uses_its_own_services + assert_wordpress_reaches_nextcloud + assert_authenticated_request + assert_integration_enabled +} + +assert_environment + +before="$(configuration_snapshot)" || fail "read the configuration before running make up again" +make -C "$root_dir" --no-print-directory up wordpress nextcloud >/dev/null || fail "run make up again" +after="$(configuration_snapshot)" || fail "read the configuration after running make up again" +[ "$before" = "$after" ] || fail "Running make up again keeps the existing configuration" +pass "Running make up again keeps the existing configuration" + +assert_environment