From 81632308e253ae30b041d616ce03dd25f70ea689 Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Sat, 3 Oct 2026 16:29:42 +0000 Subject: [PATCH 1/3] feat: add isolated worker lifecycle foundation Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- .docker/database-services.yml | 6 +- .docker/scripts/nextcloud-entrypoint.sh | 22 ++-- .docker/scripts/wait-for-db.php | 12 ++- .github/workflows/proxy-tests.yml | 2 +- .gitignore | 1 + dev-worker | 137 ++++++++++++++++++++++++ docker-compose.yml | 30 +++--- docs/advanced-setup.md | 39 ++++++- tests/worker/contract.bats | 52 +++++++++ 9 files changed, 268 insertions(+), 33 deletions(-) create mode 100644 dev-worker create mode 100644 tests/worker/contract.bats diff --git a/.docker/database-services.yml b/.docker/database-services.yml index 6f27bb85..a3176b56 100644 --- a/.docker/database-services.yml +++ b/.docker/database-services.yml @@ -4,8 +4,8 @@ services: # runs with the application user, which cannot read mysql.component there. image: mysql:8.4@sha256:b3b90af2a6552ae30c266fdb7d5dd55f3afb72404bb78d37fe8a23eb857fd3fb volumes: - - ../volumes/mysql/init:/docker-entrypoint-initdb.d - - ../volumes/mysql/data:/var/lib/mysql + - ${WORKER_VOLUMES_DIR:-../volumes}/mysql/init:/docker-entrypoint-initdb.d + - ${WORKER_VOLUMES_DIR:-../volumes}/mysql/data:/var/lib/mysql ports: - target: 3306 published: "3306-3325" @@ -20,7 +20,7 @@ services: pgsql: image: postgres:13-alpine@sha256:fb9065b6e3e213bdc07edd372a5b2a26245840b7fb65d1fd8b6700106d51805c volumes: - - ../volumes/postgres/data:/var/lib/postgresql/data + - ${WORKER_VOLUMES_DIR:-../volumes}/postgres/data:/var/lib/postgresql/data ports: - target: 5432 published: "5432-5451" diff --git a/.docker/scripts/nextcloud-entrypoint.sh b/.docker/scripts/nextcloud-entrypoint.sh index 597006d8..1e9510d8 100644 --- a/.docker/scripts/nextcloud-entrypoint.sh +++ b/.docker/scripts/nextcloud-entrypoint.sh @@ -21,6 +21,14 @@ install -d -o www-data -g www-data \ config \ apps-extra +db_type="${DB_TYPE:-${DB_HOST:-mysql}}" +db_host="${DB_HOST:-$db_type}" +db_driver="${DB_DRIVER:-$db_type}" + +export DB_TYPE="$db_type" +export DB_HOST="$db_host" +export DB_DRIVER="$db_driver" + # Wait for database php /var/www/scripts/wait-for-db.php @@ -35,22 +43,22 @@ fi # Set configurations, if needed if [[ ! -f "config/config.php" && ${AUTOINSTALL} -eq 1 ]]; then echo "⌛️ Starting installation ..." - if [[ ${DB_HOST} == 'mysql' ]]; then - occ maintenance:install --verbose --database="${DB_HOST}" --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}" + if [[ "${db_type}" == 'mysql' ]]; 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_HOST}" == 'pgsql' ]]; then - occ maintenance:install --verbose --database="${DB_HOST}" --database-name="${POSTGRES_DB}" --database-host="${DB_HOST}" --database-port= --database-user="${POSTGRES_USER}" --database-pass="${POSTGRES_PASSWORD}" --admin-user="${NEXTCLOUD_ADMIN_USER}" --admin-pass="${NEXTCLOUD_ADMIN_PASSWORD}" --admin-email="${NEXTCLOUD_ADMIN_EMAIL}" + elif [[ "${db_type}" == 'pgsql' ]]; then + occ maintenance:install --verbose --database="${db_driver}" --database-name="${POSTGRES_DB}" --database-host="${db_host}" --database-port= --database-user="${POSTGRES_USER}" --database-pass="${POSTGRES_PASSWORD}" --admin-user="${NEXTCLOUD_ADMIN_USER}" --admin-pass="${NEXTCLOUD_ADMIN_PASSWORD}" --admin-email="${NEXTCLOUD_ADMIN_EMAIL}" install_cmd_status=$? else - occ maintenance:install --verbose --admin-user="${NEXTCLOUD_ADMIN_USER}" --admin-pass="${NEXTCLOUD_ADMIN_PASSWORD}" --admin-email="${NEXTCLOUD_ADMIN_EMAIL}" + occ maintenance:install --verbose --database="${db_driver}" --admin-user="${NEXTCLOUD_ADMIN_USER}" --admin-pass="${NEXTCLOUD_ADMIN_PASSWORD}" --admin-email="${NEXTCLOUD_ADMIN_EMAIL}" install_cmd_status=$? fi if [[ ${install_cmd_status} -ne 0 ]]; then db_reset_hint="volumes/nextcloud/config and volumes/nextcloud/data" - if [[ ${DB_HOST} == 'mysql' ]]; then + if [[ "${db_type}" == 'mysql' ]]; then db_reset_hint="volumes/mysql/data, ${db_reset_hint}" - elif [[ ${DB_HOST} == 'pgsql' ]]; then + 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 0f202b25..e5eebc96 100644 --- a/.docker/scripts/wait-for-db.php +++ b/.docker/scripts/wait-for-db.php @@ -1,16 +1,18 @@ #!/usr/bin/env php [arguments...] + +Commands: + up Start the isolated worker and wait for Nextcloud installation + exec Run a command in the worker's Nextcloud container + logs Print worker logs + config Render the resolved Compose configuration + status Run occ status in the worker + destroy Stop the worker and remove only its mutable state + +Environment: + DB_TYPE=mysql|pgsql Database backend for the foundation lifecycle + DB_HOST= Connection hostname (defaults to DB_TYPE) + PHP_VERSION= Existing PHP image selector + VERSION_NEXTCLOUD= Existing Nextcloud ref selector + WORKER_READY_TIMEOUT=180 Seconds to wait for a clean installation +EOF +} + +[ "$#" -ge 2 ] || { + usage >&2 + exit 2 +} + +worker_id="$1" +command="$2" +shift 2 + +case "$worker_id" in + *[!a-z0-9_-]*|'') + echo "Invalid worker id: $worker_id" >&2 + exit 2 + ;; +esac + +case "$worker_id" in + [a-z0-9]*) ;; + *) + echo "Worker id must start with a lowercase letter or digit: $worker_id" >&2 + exit 2 + ;; +esac + +repo_root="$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)" +db_type="${DB_TYPE:-mysql}" + +case "$db_type" in + mysql|pgsql) ;; + sqlite|mariadb) + echo "DB_TYPE=$db_type is reserved by the worker contract but is not implemented by this foundation yet." >&2 + exit 3 + ;; + *) + echo "Unsupported DB_TYPE: $db_type" >&2 + exit 2 + ;; +esac + +worker_root="${WORKER_ROOT:-$repo_root/.workers/$worker_id}" +worker_volumes_dir="$worker_root/volumes" + +export COMPOSE_PROJECT_NAME="${COMPOSE_PROJECT_NAME:-ncdev-$worker_id}" +export WORKER_VOLUMES_DIR="$worker_volumes_dir" +export DB_TYPE="$db_type" +export DB_HOST="${DB_HOST:-$db_type}" + +compose() { + docker compose --project-directory "$repo_root" --file "$repo_root/docker-compose.yml" "$@" +} + +wait_until_ready() { + timeout="${WORKER_READY_TIMEOUT:-180}" + started="$(date +%s)" + + while :; do + if compose exec -T nextcloud occ status 2>/dev/null | grep -q 'installed: true'; then + return 0 + fi + + now="$(date +%s)" + if [ $((now - started)) -ge "$timeout" ]; then + echo "Worker $worker_id did not become ready within ${timeout}s." >&2 + compose logs --no-color nextcloud >&2 || true + return 1 + fi + + sleep 2 + done +} + +case "$command" in + up) + mkdir -p "$worker_volumes_dir" + compose up -d + wait_until_ready + ;; + exec) + [ "$#" -gt 0 ] || { + echo "exec requires a command" >&2 + exit 2 + } + compose exec -T nextcloud "$@" + ;; + logs) + compose logs --no-color "$@" + ;; + config) + mkdir -p "$worker_volumes_dir" + compose config "$@" + ;; + status) + compose exec -T nextcloud occ status + ;; + destroy) + compose down --remove-orphans + case "$worker_root" in + "$repo_root"/.workers/"$worker_id"|"$repo_root"/.workers/"$worker_id"/) ;; + *) + echo "Refusing to remove unexpected worker path: $worker_root" >&2 + exit 4 + ;; + esac + rm -rf -- "$worker_root" + ;; + *) + usage >&2 + exit 2 + ;; +esac diff --git a/docker-compose.yml b/docker-compose.yml index 23f6daba..56cc5ae8 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -2,11 +2,11 @@ services: database: extends: file: .docker/database-services.yml - service: ${DB_HOST:-mysql} + service: ${DB_TYPE:-${DB_HOST:-mysql}} networks: default: aliases: - - ${DB_HOST:-mysql} + - ${DB_HOST:-${DB_TYPE:-mysql}} nextcloud: image: ghcr.io/librecodecoop/nextcloud-dev-php${PHP_VERSION:-83}:latest # build: @@ -14,10 +14,10 @@ services: # dockerfile: Dockerfile.php${PHP_VERSION:-83} volumes: - ./.docker/scripts/wait-for-db.php:/var/www/scripts/wait-for-db.php - - ./volumes/php:/var/www/php-config + - ${WORKER_VOLUMES_DIR:-./volumes}/php:/var/www/php-config - ~/.composer:/var/www/.composer/ - ~/.npm:/var/www/.npm/ - - ./volumes/nextcloud:/var/www/html + - ${WORKER_VOLUMES_DIR:-./volumes}/nextcloud:/var/www/html restart: unless-stopped depends_on: - database @@ -28,7 +28,9 @@ services: - NEXTCLOUD_ADMIN_USER=${NEXTCLOUD_ADMIN_USER:-admin} - NEXTCLOUD_ADMIN_PASSWORD=${NEXTCLOUD_ADMIN_PASSWORD:-admin} - NEXTCLOUD_ADMIN_EMAIL=${NEXTCLOUD_ADMIN_EMAIL:-a@test.tld} - - DB_HOST=${DB_HOST:-mysql} + - DB_TYPE=${DB_TYPE:-${DB_HOST:-mysql}} + - DB_HOST=${DB_HOST:-${DB_TYPE:-mysql}} + - DB_DRIVER=${DB_DRIVER:-${DB_TYPE:-${DB_HOST:-mysql}}} - MYSQL_DATABASE=${MYSQL_DATABASE:-nextcloud} - MYSQL_USER=${MYSQL_USER:-nextcloud} - MYSQL_PASSWORD=${MYSQL_PASSWORD:-nextcloud} @@ -56,9 +58,9 @@ services: depends_on: - nextcloud volumes: - - ./volumes/nextcloud:/var/www/html:ro - - ./volumes/nginx/includes:/etc/nginx/conf.d/includes/ - - ./volumes/nginx/certs:/certs + - ${WORKER_VOLUMES_DIR:-./volumes}/nextcloud:/var/www/html:ro + - ${WORKER_VOLUMES_DIR:-./volumes}/nginx/includes:/etc/nginx/conf.d/includes/ + - ${WORKER_VOLUMES_DIR:-./volumes}/nginx/certs:/certs environment: - VIRTUAL_HOST=${COMPOSE_PROJECT_NAME}.localhost - VIRTUAL_PORT=80 @@ -97,9 +99,9 @@ services: - eurooffice restart: always volumes: - - ./volumes/eurooffice:/home/eurooffice/.eurooffice - - ./volumes/eurooffice/local.json:/etc/onlyoffice/documentserver/local.json - - ./volumes/nextcloud/apps-extra/eurooffice-plugin:/var/www/onlyoffice/documentserver/sdkjs-plugins/libresign:ro + - ${WORKER_VOLUMES_DIR:-./volumes}/eurooffice:/home/eurooffice/.eurooffice + - ${WORKER_VOLUMES_DIR:-./volumes}/eurooffice/local.json:/etc/onlyoffice/documentserver/local.json + - ${WORKER_VOLUMES_DIR:-./volumes}/nextcloud/apps-extra/eurooffice-plugin:/var/www/onlyoffice/documentserver/sdkjs-plugins/libresign:ro environment: - EXAMPLE_ENABLED=true - JWT_SECRET=${JWT_SECRET:-my_jwt_secret} @@ -116,7 +118,7 @@ services: - playwright user: pwuser volumes: - - ./volumes/nextcloud:/var/www/html + - ${WORKER_VOLUMES_DIR:-./volumes}/nextcloud:/var/www/html working_dir: /var/www/html environment: - PLAYWRIGHT_BASE_URL=${PLAYWRIGHT_BASE_URL:-https://${COMPOSE_PROJECT_NAME}.localhost} @@ -137,7 +139,7 @@ services: - signal restart: unless-stopped volumes: - - ./volumes/signal:/home/.local/share/signal-cli + - ${WORKER_VOLUMES_DIR:-./volumes}/signal:/home/.local/share/signal-cli environment: - LOG_LEVEL=info - VIRTUAL_HOST=${COMPOSE_PROJECT_NAME}-signal.localhost @@ -159,4 +161,4 @@ services: - WHATSAPP_WEBHOOK_EVENTS=${WHATSAPP_WEBHOOK_EVENTS:-message,message.ack,group.participants,chat_presence} - WHATSAPP_AUTO_REPLY="Conta de sistema. Para falar com a LibreCode, mande email para contato@libresign.coop" volumes: - - ./volumes/go-whatsapp-web:/usr/src/app/files + - ${WORKER_VOLUMES_DIR:-./volumes}/go-whatsapp-web:/usr/src/app/files diff --git a/docs/advanced-setup.md b/docs/advanced-setup.md index 677935f5..e9ca733d 100644 --- a/docs/advanced-setup.md +++ b/docs/advanced-setup.md @@ -28,17 +28,50 @@ MySQL is used by default: docker compose up ``` -The supported `DB_HOST` values are: +Use `DB_TYPE` to select the database backend and keep `DB_HOST` for the +connection hostname. -- `mysql` +The currently implemented backends are: + +- `mysql` (default) - `pgsql` To use PostgreSQL: ```bash -DB_HOST=pgsql docker compose up +DB_TYPE=pgsql docker compose up +``` + +Existing callers that use `DB_HOST=pgsql docker compose up` remain supported +for compatibility. New automation should use `DB_TYPE`. + +### Isolated workers + +The `dev-worker` helper gives automation a deterministic Compose project and +mutable data directory per worker: + +```bash +./dev-worker test-a up +./dev-worker test-a exec occ status +./dev-worker test-a logs +./dev-worker test-a destroy ``` +Select the existing PHP and Nextcloud dimensions in the same invocation: + +```bash +PHP_VERSION=83 VERSION_NEXTCLOUD=stable35 DB_TYPE=pgsql \ + ./dev-worker test-pg up +``` + +Each worker stores mutable state under `.workers//volumes` and uses +its own Compose project name. Destroying one worker removes only that worker's +Compose resources and mutable directory. + +The lifecycle currently enables MySQL and PostgreSQL. SQLite and MariaDB are +reserved by the `DB_TYPE` contract and are added by their database-specific +workstreams. + ## Optional services Optional services are enabled with Docker Compose profiles: diff --git a/tests/worker/contract.bats b/tests/worker/contract.bats new file mode 100644 index 00000000..f0c602e9 --- /dev/null +++ b/tests/worker/contract.bats @@ -0,0 +1,52 @@ +#!/usr/bin/env bats +# SPDX-FileCopyrightText: 2026 LibreCode coop and contributors +# SPDX-License-Identifier: AGPL-3.0-or-later + +setup() { + REPO_ROOT="$(cd "$BATS_TEST_DIRNAME/../.." && pwd)" + WORKER="$REPO_ROOT/dev-worker" +} + +@test "worker id rejects path traversal and uppercase names" { + run "$WORKER" '../bad' config + [ "$status" -eq 2 ] + + run "$WORKER" 'WorkerA' config + [ "$status" -eq 2 ] +} + +@test "worker config isolates mutable paths and compose project names" { + run "$WORKER" alpha config + [ "$status" -eq 0 ] + [[ "$output" == *"name: ncdev-alpha"* ]] + [[ "$output" == *"$REPO_ROOT/.workers/alpha/volumes/nextcloud"* ]] + + run "$WORKER" beta config + [ "$status" -eq 0 ] + [[ "$output" == *"name: ncdev-beta"* ]] + [[ "$output" == *"$REPO_ROOT/.workers/beta/volumes/nextcloud"* ]] + [[ "$output" != *"$REPO_ROOT/.workers/alpha/volumes/nextcloud"* ]] +} + +@test "DB_TYPE selects PostgreSQL while DB_HOST remains the connection host" { + run env DB_TYPE=pgsql DB_HOST=database.internal "$WORKER" pg config + [ "$status" -eq 0 ] + [[ "$output" == *"image: postgres:13-alpine"* ]] + [[ "$output" == *"DB_TYPE=pgsql"* ]] + [[ "$output" == *"DB_HOST=database.internal"* ]] +} + +@test "legacy DB_HOST PostgreSQL selection still resolves the PostgreSQL service" { + run env DB_HOST=pgsql docker compose --project-directory "$REPO_ROOT" --file "$REPO_ROOT/docker-compose.yml" config + [ "$status" -eq 0 ] + [[ "$output" == *"image: postgres:13-alpine"* ]] +} + +@test "reserved future database types fail before starting a partial worker" { + run env DB_TYPE=sqlite "$WORKER" sqlite config + [ "$status" -eq 3 ] + [[ "$output" == *"not implemented by this foundation yet"* ]] + + run env DB_TYPE=mariadb "$WORKER" mariadb config + [ "$status" -eq 3 ] +} From 7e8ba8344605b2c7d99f3d43003b8a0b46f73427 Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Sat, 3 Oct 2026 16:30:19 +0000 Subject: [PATCH 2/3] fix: invoke worker helper without executable mode Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- dev-worker | 4 ++-- docs/advanced-setup.md | 10 +++++----- tests/worker/contract.bats | 12 ++++++------ 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/dev-worker b/dev-worker index 5afcc75d..81b6536e 100644 --- a/dev-worker +++ b/dev-worker @@ -6,7 +6,7 @@ set -eu usage() { cat <<'EOF' -Usage: ./dev-worker [arguments...] +Usage: sh ./dev-worker [arguments...] Commands: up Start the isolated worker and wait for Nextcloud installation @@ -64,7 +64,7 @@ case "$db_type" in ;; esac -worker_root="${WORKER_ROOT:-$repo_root/.workers/$worker_id}" +worker_root="$repo_root/.workers/$worker_id" worker_volumes_dir="$worker_root/volumes" export COMPOSE_PROJECT_NAME="${COMPOSE_PROJECT_NAME:-ncdev-$worker_id}" diff --git a/docs/advanced-setup.md b/docs/advanced-setup.md index e9ca733d..d8283846 100644 --- a/docs/advanced-setup.md +++ b/docs/advanced-setup.md @@ -51,17 +51,17 @@ The `dev-worker` helper gives automation a deterministic Compose project and mutable data directory per worker: ```bash -./dev-worker test-a up -./dev-worker test-a exec occ status -./dev-worker test-a logs -./dev-worker test-a destroy +sh ./dev-worker test-a up +sh ./dev-worker test-a exec occ status +sh ./dev-worker test-a logs +sh ./dev-worker test-a destroy ``` Select the existing PHP and Nextcloud dimensions in the same invocation: ```bash PHP_VERSION=83 VERSION_NEXTCLOUD=stable35 DB_TYPE=pgsql \ - ./dev-worker test-pg up + sh ./dev-worker test-pg up ``` Each worker stores mutable state under `.workers//volumes` and uses diff --git a/tests/worker/contract.bats b/tests/worker/contract.bats index f0c602e9..cad102c8 100644 --- a/tests/worker/contract.bats +++ b/tests/worker/contract.bats @@ -8,20 +8,20 @@ setup() { } @test "worker id rejects path traversal and uppercase names" { - run "$WORKER" '../bad' config + run sh "$WORKER" '../bad' config [ "$status" -eq 2 ] - run "$WORKER" 'WorkerA' config + run sh "$WORKER" 'WorkerA' config [ "$status" -eq 2 ] } @test "worker config isolates mutable paths and compose project names" { - run "$WORKER" alpha config + run sh "$WORKER" alpha config [ "$status" -eq 0 ] [[ "$output" == *"name: ncdev-alpha"* ]] [[ "$output" == *"$REPO_ROOT/.workers/alpha/volumes/nextcloud"* ]] - run "$WORKER" beta config + run sh "$WORKER" beta config [ "$status" -eq 0 ] [[ "$output" == *"name: ncdev-beta"* ]] [[ "$output" == *"$REPO_ROOT/.workers/beta/volumes/nextcloud"* ]] @@ -32,8 +32,8 @@ setup() { run env DB_TYPE=pgsql DB_HOST=database.internal "$WORKER" pg config [ "$status" -eq 0 ] [[ "$output" == *"image: postgres:13-alpine"* ]] - [[ "$output" == *"DB_TYPE=pgsql"* ]] - [[ "$output" == *"DB_HOST=database.internal"* ]] + [[ "$output" == *"DB_TYPE: pgsql"* ]] + [[ "$output" == *"DB_HOST: database.internal"* ]] } @test "legacy DB_HOST PostgreSQL selection still resolves the PostgreSQL service" { From ab6d1b1e84d89ccd9e64c960406745fbc8c13e0e Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Sat, 3 Oct 2026 17:39:25 +0000 Subject: [PATCH 3/3] fix: invoke worker helper through sh in Bats Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- tests/worker/contract.bats | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/worker/contract.bats b/tests/worker/contract.bats index cad102c8..39c1d79b 100644 --- a/tests/worker/contract.bats +++ b/tests/worker/contract.bats @@ -29,7 +29,7 @@ setup() { } @test "DB_TYPE selects PostgreSQL while DB_HOST remains the connection host" { - run env DB_TYPE=pgsql DB_HOST=database.internal "$WORKER" pg config + run env DB_TYPE=pgsql DB_HOST=database.internal sh "$WORKER" pg config [ "$status" -eq 0 ] [[ "$output" == *"image: postgres:13-alpine"* ]] [[ "$output" == *"DB_TYPE: pgsql"* ]] @@ -43,10 +43,10 @@ setup() { } @test "reserved future database types fail before starting a partial worker" { - run env DB_TYPE=sqlite "$WORKER" sqlite config + run env DB_TYPE=sqlite sh "$WORKER" sqlite config [ "$status" -eq 3 ] [[ "$output" == *"not implemented by this foundation yet"* ]] - run env DB_TYPE=mariadb "$WORKER" mariadb config + run env DB_TYPE=mariadb sh "$WORKER" mariadb config [ "$status" -eq 3 ] }