diff --git a/.docker/Dockerfile.php81 b/.docker/Dockerfile.php81 index 486d711..97799cd 100644 --- a/.docker/Dockerfile.php81 +++ b/.docker/Dockerfile.php81 @@ -43,6 +43,7 @@ RUN chmod uga+x /usr/local/bin/install-php-extensions && sync \ pdo \ pdo_mysql \ pdo_pgsql \ + pdo_sqlite \ pgsql \ redis \ sockets \ diff --git a/.docker/Dockerfile.php82 b/.docker/Dockerfile.php82 index d375b01..2a32a00 100644 --- a/.docker/Dockerfile.php82 +++ b/.docker/Dockerfile.php82 @@ -43,6 +43,7 @@ RUN chmod uga+x /usr/local/bin/install-php-extensions && sync \ pdo \ pdo_mysql \ pdo_pgsql \ + pdo_sqlite \ pgsql \ redis \ sockets \ diff --git a/.docker/Dockerfile.php83 b/.docker/Dockerfile.php83 index bdb75c4..31addf5 100644 --- a/.docker/Dockerfile.php83 +++ b/.docker/Dockerfile.php83 @@ -43,6 +43,7 @@ RUN chmod uga+x /usr/local/bin/install-php-extensions && sync \ pdo \ pdo_mysql \ pdo_pgsql \ + pdo_sqlite \ pgsql \ redis \ sockets \ diff --git a/.docker/scripts/nextcloud-entrypoint.sh b/.docker/scripts/nextcloud-entrypoint.sh index 1e9510d..c566b14 100644 --- a/.docker/scripts/nextcloud-entrypoint.sh +++ b/.docker/scripts/nextcloud-entrypoint.sh @@ -49,6 +49,9 @@ if [[ ! -f "config/config.php" && ${AUTOINSTALL} -eq 1 ]]; then 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=$? + elif [[ "${db_type}" == 'sqlite' ]]; then + occ maintenance:install --verbose --database="sqlite" --admin-user="${NEXTCLOUD_ADMIN_USER}" --admin-pass="${NEXTCLOUD_ADMIN_PASSWORD}" --admin-email="${NEXTCLOUD_ADMIN_EMAIL}" + install_cmd_status=$? else 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=$? diff --git a/.github/workflows/proxy-tests.yml b/.github/workflows/proxy-tests.yml index e611c7e..64b49f5 100644 --- a/.github/workflows/proxy-tests.yml +++ b/.github/workflows/proxy-tests.yml @@ -44,3 +44,47 @@ jobs: REPO_ROOT="$GITHUB_WORKSPACE" COMPOSE_PROJECT_NAME=proxytesta docker compose --file tests/proxy/fixtures/compose.yml config --quiet - name: Run Docker integration tests run: bats tests/proxy/integration.bats + + sqlite-worker: + name: SQLite worker integration + runs-on: ubuntu-latest + timeout-minutes: 15 + 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 isolated SQLite workers + run: | + DB_TYPE=sqlite sh ./dev-worker sqlite-a up + DB_TYPE=sqlite sh ./dev-worker sqlite-b up + - name: Verify SQLite runtime + run: | + DB_TYPE=sqlite sh ./dev-worker sqlite-a exec php -r 'exit(extension_loaded("pdo_sqlite") ? 0 : 1);' + DB_TYPE=sqlite sh ./dev-worker sqlite-a exec occ status | grep -q 'installed: true' + 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)" + printf 'sqlite-a worker_probe=%s\nsqlite-b worker_probe=%s\n' "$alpha" "$beta" + test "$alpha" = alpha + test "$beta" = beta + - name: Verify SQLite starts no database service + run: | + database_containers="$(docker ps --format '{{.Names}}' | grep -E '^ncdev-sqlite-(a|b)-database-1$' || true)" + if [ -n "$database_containers" ]; then + printf 'Unexpected database containers:\n%s\n' "$database_containers" >&2 + exit 1 + fi + - name: Collect worker logs + if: failure() + run: | + DB_TYPE=sqlite sh ./dev-worker sqlite-a logs || true + DB_TYPE=sqlite sh ./dev-worker sqlite-b logs || true + - name: Destroy workers + if: always() + run: | + DB_TYPE=sqlite sh ./dev-worker sqlite-a destroy || true + DB_TYPE=sqlite sh ./dev-worker sqlite-b destroy || true diff --git a/dev-worker b/dev-worker index 81b6536..5dd4504 100644 --- a/dev-worker +++ b/dev-worker @@ -17,7 +17,7 @@ Commands: destroy Stop the worker and remove only its mutable state Environment: - DB_TYPE=mysql|pgsql Database backend for the foundation lifecycle + DB_TYPE=sqlite|mysql|pgsql Database backend DB_HOST= Connection hostname (defaults to DB_TYPE) PHP_VERSION= Existing PHP image selector VERSION_NEXTCLOUD= Existing Nextcloud ref selector @@ -53,9 +53,9 @@ 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 + sqlite|mysql|pgsql) ;; + mariadb) + echo "DB_TYPE=$db_type is reserved by the worker contract but is not implemented yet." >&2 exit 3 ;; *) @@ -71,6 +71,12 @@ 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}" +if [ "$db_type" = "sqlite" ]; then + export DB_SERVICE_TYPE=mysql + export DB_DRIVER=sqlite +else + export DB_SERVICE_TYPE="${DB_SERVICE_TYPE:-$db_type}" +fi compose() { docker compose --project-directory "$repo_root" --file "$repo_root/docker-compose.yml" "$@" @@ -99,7 +105,11 @@ wait_until_ready() { case "$command" in up) mkdir -p "$worker_volumes_dir" - compose up -d + if [ "$db_type" = "sqlite" ]; then + compose up -d nextcloud nginx proxy-coordinator mailpit redis + else + compose up -d + fi wait_until_ready ;; exec) @@ -121,6 +131,12 @@ case "$command" in ;; destroy) compose down --remove-orphans + # Mutable bind mounts contain files created by container UIDs. Clean the + # worker root through the existing Nextcloud image as root instead of + # relying on host ownership. + if [ -d "$worker_root" ]; then + compose run --rm --no-deps -u 0 --entrypoint sh -v "$worker_root:/worker" nextcloud -c 'find /worker -mindepth 1 -exec rm -rf -- {} +' >/dev/null 2>&1 || true + fi case "$worker_root" in "$repo_root"/.workers/"$worker_id"|"$repo_root"/.workers/"$worker_id"/) ;; *) diff --git a/docker-compose.yml b/docker-compose.yml index 56cc5ae..977ef33 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -2,7 +2,7 @@ services: database: extends: file: .docker/database-services.yml - service: ${DB_TYPE:-${DB_HOST:-mysql}} + service: ${DB_SERVICE_TYPE:-${DB_TYPE:-${DB_HOST:-mysql}}} networks: default: aliases: @@ -19,8 +19,6 @@ services: - ~/.npm:/var/www/.npm/ - ${WORKER_VOLUMES_DIR:-./volumes}/nextcloud:/var/www/html restart: unless-stopped - depends_on: - - database environment: - HOST_UID=${HOST_UID:-1000} - HOST_GID=${HOST_GID:-1000} diff --git a/docs/advanced-setup.md b/docs/advanced-setup.md index d828384..2ff87dc 100644 --- a/docs/advanced-setup.md +++ b/docs/advanced-setup.md @@ -33,6 +33,7 @@ connection hostname. The currently implemented backends are: +- `sqlite` - `mysql` (default) - `pgsql` @@ -68,9 +69,17 @@ 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. +SQLite workers do not start a network database container. Their database file +lives inside the worker's isolated Nextcloud filesystem: + +```bash +DB_TYPE=sqlite sh ./dev-worker sqlite-a up +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. ## Optional services diff --git a/tests/worker/contract.bats b/tests/worker/contract.bats index 39c1d79..74574fc 100644 --- a/tests/worker/contract.bats +++ b/tests/worker/contract.bats @@ -42,11 +42,21 @@ setup() { [[ "$output" == *"image: postgres:13-alpine"* ]] } -@test "reserved future database types fail before starting a partial worker" { +@test "SQLite is a supported worker backend" { run env DB_TYPE=sqlite sh "$WORKER" sqlite config - [ "$status" -eq 3 ] - [[ "$output" == *"not implemented by this foundation yet"* ]] + [ "$status" -eq 0 ] + [[ "$output" == *"DB_TYPE: sqlite"* ]] + [[ "$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 "supported PHP images install PDO SQLite" { + for dockerfile in "$REPO_ROOT"/.docker/Dockerfile.php81 "$REPO_ROOT"/.docker/Dockerfile.php82 "$REPO_ROOT"/.docker/Dockerfile.php83; do + grep -q 'pdo_sqlite' "$dockerfile" + done }