diff --git a/.docker/database-services.yml b/.docker/database-services.yml index a3176b5..de29147 100644 --- a/.docker/database-services.yml +++ b/.docker/database-services.yml @@ -17,6 +17,34 @@ services: - MYSQL_DATABASE=${MYSQL_DATABASE:-nextcloud} - MYSQL_USER=${MYSQL_USER:-nextcloud} - MYSQL_PASSWORD=${MYSQL_PASSWORD:-nextcloud} + mariadb106: + image: mariadb:10.6.28@sha256:23616f0bd3aff922f4dea4130f1d0a09f3571d20b7b36c8f49840672dc309e8c + volumes: + - ${WORKER_VOLUMES_DIR:-../volumes}/mariadb/data:/var/lib/mysql + ports: + - target: 3306 + published: "3306-3325" + host_ip: ${MARIADB_IP_BIND:-127.0.0.1} + environment: + - TZ=${TZ:-CET} + - MARIADB_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD:-root} + - MARIADB_DATABASE=${MYSQL_DATABASE:-nextcloud} + - MARIADB_USER=${MYSQL_USER:-nextcloud} + - MARIADB_PASSWORD=${MYSQL_PASSWORD:-nextcloud} + mariadb1011: + image: mariadb:10.11.19@sha256:07c0aaff7396b74cb7975cba78257178d188e30f531a5db2b617c48beef13c41 + volumes: + - ${WORKER_VOLUMES_DIR:-../volumes}/mariadb/data:/var/lib/mysql + ports: + - target: 3306 + published: "3306-3325" + host_ip: ${MARIADB_IP_BIND:-127.0.0.1} + environment: + - TZ=${TZ:-CET} + - MARIADB_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD:-root} + - MARIADB_DATABASE=${MYSQL_DATABASE:-nextcloud} + - MARIADB_USER=${MYSQL_USER:-nextcloud} + - MARIADB_PASSWORD=${MYSQL_PASSWORD:-nextcloud} pgsql: image: postgres:13-alpine@sha256:fb9065b6e3e213bdc07edd372a5b2a26245840b7fb65d1fd8b6700106d51805c volumes: diff --git a/.docker/scripts/nextcloud-entrypoint.sh b/.docker/scripts/nextcloud-entrypoint.sh index c566b14..29ea2d1 100644 --- a/.docker/scripts/nextcloud-entrypoint.sh +++ b/.docker/scripts/nextcloud-entrypoint.sh @@ -43,7 +43,7 @@ fi # Set configurations, if needed if [[ ! -f "config/config.php" && ${AUTOINSTALL} -eq 1 ]]; then echo "⌛️ Starting installation ..." - if [[ "${db_type}" == 'mysql' ]]; then + if [[ "${db_type}" == 'mysql' || "${db_type}" == 'mariadb' ]]; then occ maintenance:install --verbose --database="${db_driver}" --database-name="${MYSQL_DATABASE}" --database-host="${db_host}" --database-port= --database-user="${MYSQL_USER}" --database-pass="${MYSQL_PASSWORD}" --admin-user="${NEXTCLOUD_ADMIN_USER}" --admin-pass="${NEXTCLOUD_ADMIN_PASSWORD}" --admin-email="${NEXTCLOUD_ADMIN_EMAIL}" install_cmd_status=$? elif [[ "${db_type}" == 'pgsql' ]]; then @@ -61,6 +61,8 @@ if [[ ! -f "config/config.php" && ${AUTOINSTALL} -eq 1 ]]; then db_reset_hint="volumes/nextcloud/config and volumes/nextcloud/data" if [[ "${db_type}" == 'mysql' ]]; then db_reset_hint="volumes/mysql/data, ${db_reset_hint}" + elif [[ "${db_type}" == 'mariadb' ]]; then + db_reset_hint="volumes/mariadb/data, ${db_reset_hint}" elif [[ "${db_type}" == 'pgsql' ]]; then db_reset_hint="volumes/postgres/data, ${db_reset_hint}" fi diff --git a/.docker/scripts/wait-for-db.php b/.docker/scripts/wait-for-db.php index e5eebc9..372aa5a 100644 --- a/.docker/scripts/wait-for-db.php +++ b/.docker/scripts/wait-for-db.php @@ -8,7 +8,7 @@ function dbIsUp(string $dbName): bool { try { - if ($GLOBALS['dbType'] === 'mysql') { + if ($GLOBALS['dbType'] === 'mysql' || $GLOBALS['dbType'] === 'mariadb') { $dsn = 'mysql:dbname='.getenv('MYSQL_DATABASE').';host='.$GLOBALS['dbHost']; new PDO($dsn, getenv('MYSQL_USER'), getenv('MYSQL_PASSWORD')); } elseif ($GLOBALS['dbType'] === 'pgsql') { diff --git a/.github/workflows/proxy-tests.yml b/.github/workflows/proxy-tests.yml index 64b49f5..62a893a 100644 --- a/.github/workflows/proxy-tests.yml +++ b/.github/workflows/proxy-tests.yml @@ -64,10 +64,10 @@ jobs: DB_TYPE=sqlite sh ./dev-worker sqlite-b exec occ status | grep -q 'installed: true' - name: Verify SQLite worker state isolation run: | - DB_TYPE=sqlite sh ./dev-worker sqlite-a exec occ config:system:set worker_probe --value alpha - DB_TYPE=sqlite sh ./dev-worker sqlite-b exec occ config:system:set worker_probe --value beta - alpha="$(DB_TYPE=sqlite sh ./dev-worker sqlite-a exec occ config:system:get worker_probe | tail -n 1)" - beta="$(DB_TYPE=sqlite sh ./dev-worker sqlite-b exec occ config:system:get worker_probe | tail -n 1)" + DB_TYPE=sqlite sh ./dev-worker sqlite-a exec sh -c 'printf alpha > /var/www/html/data/worker-probe' + DB_TYPE=sqlite sh ./dev-worker sqlite-b exec sh -c 'printf beta > /var/www/html/data/worker-probe' + alpha="$(DB_TYPE=sqlite sh ./dev-worker sqlite-a exec cat /var/www/html/data/worker-probe)" + beta="$(DB_TYPE=sqlite sh ./dev-worker sqlite-b exec cat /var/www/html/data/worker-probe)" printf 'sqlite-a worker_probe=%s\nsqlite-b worker_probe=%s\n' "$alpha" "$beta" test "$alpha" = alpha test "$beta" = beta @@ -88,3 +88,70 @@ jobs: run: | DB_TYPE=sqlite sh ./dev-worker sqlite-a destroy || true DB_TYPE=sqlite sh ./dev-worker sqlite-b destroy || true + + mariadb-worker: + name: MariaDB ${{ matrix.mariadb }} worker integration + runs-on: ubuntu-latest + timeout-minutes: 15 + strategy: + fail-fast: false + matrix: + mariadb: ['10.6', '10.11'] + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 + - name: Build PHP 8.3 development image + run: docker build -f .docker/Dockerfile.php83 -t ghcr.io/librecodecoop/nextcloud-dev-php83:latest .docker + - name: Start MariaDB worker + env: + MARIADB_VERSION: ${{ matrix.mariadb }} + DB_SQL_MODE: ONLY_FULL_GROUP_BY + run: | + worker="maria-${MARIADB_VERSION//./-}" + DB_TYPE=mariadb sh ./dev-worker "$worker" up + - name: Verify MariaDB version and SQL mode + env: + MARIADB_VERSION: ${{ matrix.mariadb }} + DB_SQL_MODE: ONLY_FULL_GROUP_BY + run: | + worker="maria-${MARIADB_VERSION//./-}" + version="$(DB_TYPE=mariadb sh ./dev-worker "$worker" db-exec mariadb -uroot -proot -Nse 'SELECT VERSION()')" + case "$version" in + "$MARIADB_VERSION".*) ;; + *) echo "Unexpected MariaDB version: $version" >&2; exit 1 ;; + esac + DB_TYPE=mariadb sh ./dev-worker "$worker" db-exec mariadb -uroot -proot -Nse 'SELECT @@GLOBAL.sql_mode' | grep -q 'ONLY_FULL_GROUP_BY' + DB_TYPE=mariadb sh ./dev-worker "$worker" status | grep -q 'installed: true' + - name: Verify two-worker isolation + if: matrix.mariadb == '10.11' + env: + MARIADB_VERSION: ${{ matrix.mariadb }} + DB_SQL_MODE: ONLY_FULL_GROUP_BY + run: | + DB_TYPE=mariadb sh ./dev-worker maria-peer up + DB_TYPE=mariadb sh ./dev-worker maria-10-11 exec sh -c 'printf alpha > /var/www/html/data/worker-probe' + DB_TYPE=mariadb sh ./dev-worker maria-peer exec sh -c 'printf beta > /var/www/html/data/worker-probe' + alpha="$(DB_TYPE=mariadb sh ./dev-worker maria-10-11 exec cat /var/www/html/data/worker-probe)" + beta="$(DB_TYPE=mariadb sh ./dev-worker maria-peer exec cat /var/www/html/data/worker-probe)" + printf 'maria-10-11 worker_probe=%s\nmaria-peer worker_probe=%s\n' "$alpha" "$beta" + test "$alpha" = alpha + test "$beta" = beta + - name: Collect worker logs + if: failure() + env: + MARIADB_VERSION: ${{ matrix.mariadb }} + run: | + worker="maria-${MARIADB_VERSION//./-}" + DB_TYPE=mariadb sh ./dev-worker "$worker" logs || true + if [ "$MARIADB_VERSION" = 10.11 ]; then + DB_TYPE=mariadb sh ./dev-worker maria-peer logs || true + fi + - name: Destroy workers + if: always() + env: + MARIADB_VERSION: ${{ matrix.mariadb }} + run: | + worker="maria-${MARIADB_VERSION//./-}" + DB_TYPE=mariadb sh ./dev-worker "$worker" destroy || true + if [ "$MARIADB_VERSION" = 10.11 ]; then + DB_TYPE=mariadb sh ./dev-worker maria-peer destroy || true + fi diff --git a/dev-worker b/dev-worker index 5dd4504..2813185 100644 --- a/dev-worker +++ b/dev-worker @@ -14,11 +14,14 @@ Commands: logs Print worker logs config Render the resolved Compose configuration status Run occ status in the worker + db-exec Run a command in the selected database container destroy Stop the worker and remove only its mutable state Environment: - DB_TYPE=sqlite|mysql|pgsql Database backend + DB_TYPE=sqlite|mysql|mariadb|pgsql Database backend DB_HOST= Connection hostname (defaults to DB_TYPE) + MARIADB_VERSION=10.6|10.11 MariaDB series (default: 10.11) + DB_SQL_MODE= Optional global SQL mode for MySQL-compatible backends PHP_VERSION= Existing PHP image selector VERSION_NEXTCLOUD= Existing Nextcloud ref selector WORKER_READY_TIMEOUT=180 Seconds to wait for a clean installation @@ -53,11 +56,7 @@ repo_root="$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)" db_type="${DB_TYPE:-mysql}" case "$db_type" in - sqlite|mysql|pgsql) ;; - mariadb) - echo "DB_TYPE=$db_type is reserved by the worker contract but is not implemented yet." >&2 - exit 3 - ;; + sqlite|mysql|mariadb|pgsql) ;; *) echo "Unsupported DB_TYPE: $db_type" >&2 exit 2 @@ -74,6 +73,17 @@ export DB_HOST="${DB_HOST:-$db_type}" if [ "$db_type" = "sqlite" ]; then export DB_SERVICE_TYPE=mysql export DB_DRIVER=sqlite +elif [ "$db_type" = "mariadb" ]; then + mariadb_version="${MARIADB_VERSION:-10.11}" + case "$mariadb_version" in + 10.6) export DB_SERVICE_TYPE=mariadb106 ;; + 10.11) export DB_SERVICE_TYPE=mariadb1011 ;; + *) + echo "Unsupported MARIADB_VERSION: $mariadb_version (supported: 10.6, 10.11)" >&2 + exit 2 + ;; + esac + export DB_DRIVER=mysql else export DB_SERVICE_TYPE="${DB_SERVICE_TYPE:-$db_type}" fi @@ -82,6 +92,26 @@ compose() { docker compose --project-directory "$repo_root" --file "$repo_root/docker-compose.yml" "$@" } +wait_for_mariadb() { + timeout="${WORKER_READY_TIMEOUT:-180}" + started="$(date +%s)" + + while :; do + if compose exec -T database mariadb-admin ping -h 127.0.0.1 -uroot -p"${MYSQL_ROOT_PASSWORD:-root}" --silent >/dev/null 2>&1; then + return 0 + fi + + now="$(date +%s)" + if [ $((now - started)) -ge "$timeout" ]; then + echo "MariaDB for worker $worker_id did not become ready within ${timeout}s." >&2 + compose logs --no-color database >&2 || true + return 1 + fi + + sleep 1 + done +} + wait_until_ready() { timeout="${WORKER_READY_TIMEOUT:-180}" started="$(date +%s)" @@ -107,6 +137,16 @@ case "$command" in mkdir -p "$worker_volumes_dir" if [ "$db_type" = "sqlite" ]; then compose up -d nextcloud nginx proxy-coordinator mailpit redis + elif [ "$db_type" = "mariadb" ]; then + compose up -d database + wait_for_mariadb + if [ -n "${DB_SQL_MODE:-}" ]; then + case "${DB_SQL_MODE}" in + *[!A-Z0-9_,]*) echo "DB_SQL_MODE contains unsupported characters" >&2; exit 2 ;; + esac + compose exec -T database mariadb -uroot -p"${MYSQL_ROOT_PASSWORD:-root}" -e "SET GLOBAL sql_mode='${DB_SQL_MODE}'" + fi + compose up -d else compose up -d fi @@ -129,6 +169,17 @@ case "$command" in status) compose exec -T nextcloud occ status ;; + db-exec) + [ "$db_type" != "sqlite" ] || { + echo "SQLite workers do not have a database container" >&2 + exit 3 + } + [ "$#" -gt 0 ] || { + echo "db-exec requires a command" >&2 + exit 2 + } + compose exec -T database "$@" + ;; destroy) compose down --remove-orphans # Mutable bind mounts contain files created by container UIDs. Clean the diff --git a/docs/advanced-setup.md b/docs/advanced-setup.md index 2ff87dc..dbddf12 100644 --- a/docs/advanced-setup.md +++ b/docs/advanced-setup.md @@ -35,6 +35,7 @@ The currently implemented backends are: - `sqlite` - `mysql` (default) +- `mariadb` - `pgsql` To use PostgreSQL: @@ -78,8 +79,20 @@ DB_TYPE=sqlite sh ./dev-worker sqlite-a status DB_TYPE=sqlite sh ./dev-worker sqlite-a destroy ``` -MariaDB remains reserved by the `DB_TYPE` contract until its dedicated -backend implementation lands. +MariaDB workers support the 10.6 and 10.11 series without editing Compose YAML: + +```bash +DB_TYPE=mariadb MARIADB_VERSION=10.6 sh ./dev-worker maria106 up +DB_TYPE=mariadb MARIADB_VERSION=10.11 sh ./dev-worker maria1011 up +``` + +The worker maps each supported series to a pinned image. Use `DB_SQL_MODE` to +request a deterministic global SQL mode when a test suite needs one: + +```bash +DB_TYPE=mariadb MARIADB_VERSION=10.11 DB_SQL_MODE=ONLY_FULL_GROUP_BY \ + sh ./dev-worker maria-full-group-by up +``` ## Optional services diff --git a/tests/worker/contract.bats b/tests/worker/contract.bats index 74574fc..668074f 100644 --- a/tests/worker/contract.bats +++ b/tests/worker/contract.bats @@ -49,10 +49,24 @@ setup() { [[ "$output" == *"DB_DRIVER: sqlite"* ]] } -@test "MariaDB remains reserved for its dedicated implementation" { - run env DB_TYPE=mariadb sh "$WORKER" mariadb config - [ "$status" -eq 3 ] - [[ "$output" == *"not implemented yet"* ]] +@test "MariaDB 10.6 selects the pinned 10.6 service" { + run env DB_TYPE=mariadb MARIADB_VERSION=10.6 sh "$WORKER" maria106 config + [ "$status" -eq 0 ] + [[ "$output" == *"mariadb:10.6.28@sha256:23616f0bd3aff922f4dea4130f1d0a09f3571d20b7b36c8f49840672dc309e8c"* ]] + [[ "$output" == *"DB_TYPE: mariadb"* ]] + [[ "$output" == *"DB_DRIVER: mysql"* ]] +} + +@test "MariaDB 10.11 selects the pinned 10.11 service" { + run env DB_TYPE=mariadb MARIADB_VERSION=10.11 sh "$WORKER" maria1011 config + [ "$status" -eq 0 ] + [[ "$output" == *"mariadb:10.11.19@sha256:07c0aaff7396b74cb7975cba78257178d188e30f531a5db2b617c48beef13c41"* ]] +} + +@test "unsupported MariaDB versions fail before startup" { + run env DB_TYPE=mariadb MARIADB_VERSION=11.4 sh "$WORKER" maria114 config + [ "$status" -eq 2 ] + [[ "$output" == *"Unsupported MARIADB_VERSION"* ]] } @test "supported PHP images install PDO SQLite" {