From b7bc2c9f3ac48bb47c1875001c618d75791600b2 Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Sun, 4 Oct 2026 23:07:06 +0000 Subject: [PATCH 1/3] refactor: use one app workspace per worker Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- .docker/workspace.yml | 26 +++++ .github/workflows/worker-tests.yml | 64 ++++------- README.md | 2 +- dev-worker | 50 ++++----- docs/apps-development.md | 60 ++++++---- docs/compose-extensions.md | 103 ------------------ docs/worker-workspaces.md | 94 ++++++++++++++++ tests/worker/contract.bats | 60 +++++----- tests/worker/fixtures/compose-extension.yml | 30 ----- .../other_app}/appinfo/info.xml | 4 +- .../other_app}/marker.txt | 0 .../sample_app}/appinfo/info.xml | 4 +- .../sample_app}/marker.txt | 0 13 files changed, 232 insertions(+), 265 deletions(-) create mode 100644 .docker/workspace.yml delete mode 100644 docs/compose-extensions.md create mode 100644 docs/worker-workspaces.md delete mode 100644 tests/worker/fixtures/compose-extension.yml rename tests/worker/fixtures/{other-app => workspace/other_app}/appinfo/info.xml (75%) rename tests/worker/fixtures/{other-app => workspace/other_app}/marker.txt (100%) rename tests/worker/fixtures/{sample-app => workspace/sample_app}/appinfo/info.xml (76%) rename tests/worker/fixtures/{sample-app => workspace/sample_app}/marker.txt (100%) diff --git a/.docker/workspace.yml b/.docker/workspace.yml new file mode 100644 index 0000000..88e2e4a --- /dev/null +++ b/.docker/workspace.yml @@ -0,0 +1,26 @@ +# SPDX-FileCopyrightText: 2026 LibreCode coop and contributors +# SPDX-License-Identifier: AGPL-3.0-or-later + +services: + nextcloud: + volumes: + - type: bind + source: ${NCDD_WORKSPACE:?NCDD_WORKSPACE is required} + target: /var/www/html/apps-extra + nginx: + volumes: + - type: bind + source: ${NCDD_WORKSPACE:?NCDD_WORKSPACE is required} + target: /var/www/html/apps-extra + read_only: true + playwright: + volumes: + - type: bind + source: ${NCDD_WORKSPACE:?NCDD_WORKSPACE is required} + target: /var/www/html/apps-extra + eurooffice: + volumes: + - type: bind + source: ${NCDD_WORKSPACE:?NCDD_WORKSPACE is required}/eurooffice-plugin + target: /var/www/onlyoffice/documentserver/sdkjs-plugins/libresign + read_only: true diff --git a/.github/workflows/worker-tests.yml b/.github/workflows/worker-tests.yml index ac6b3cd..4143b1e 100644 --- a/.github/workflows/worker-tests.yml +++ b/.github/workflows/worker-tests.yml @@ -101,20 +101,6 @@ jobs: 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: @@ -122,54 +108,46 @@ jobs: 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 + - name: Destroy worker 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 - compose-extension: - name: Compose extension integration + workspace: + name: Worker workspace integration runs-on: ubuntu-latest timeout-minutes: 15 steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 - name: Build default PHP development image run: sh .docker/bin/build-nextcloud-image - - name: Start a worker with two externally mounted apps + - name: Start worker with a multi-app workspace + env: + NCDD_WORKSPACE: ${{ github.workspace }}/tests/worker/fixtures/workspace + run: DB_TYPE=sqlite sh ./dev-worker workspace up + - name: Verify all workspace apps are available env: - NCDD_COMPOSE_OVERRIDE: ${{ github.workspace }}/tests/worker/fixtures/compose-extension.yml - TEST_APP_A: ${{ github.workspace }}/tests/worker/fixtures/sample-app - TEST_APP_B: ${{ github.workspace }}/tests/worker/fixtures/other-app - run: DB_TYPE=sqlite sh ./dev-worker extension up - - name: Verify both apps use the same worker runtime + NCDD_WORKSPACE: ${{ github.workspace }}/tests/worker/fixtures/workspace + run: | + test "$(DB_TYPE=sqlite sh ./dev-worker workspace exec cat /var/www/html/apps-extra/sample_app/marker.txt)" = alpha + test "$(DB_TYPE=sqlite sh ./dev-worker workspace exec cat /var/www/html/apps-extra/other_app/marker.txt)" = other + DB_TYPE=sqlite sh ./dev-worker workspace status | grep -q 'installed: true' + - name: Verify worker state does not enter the workspace env: - NCDD_COMPOSE_OVERRIDE: ${{ github.workspace }}/tests/worker/fixtures/compose-extension.yml - TEST_APP_A: ${{ github.workspace }}/tests/worker/fixtures/sample-app - TEST_APP_B: ${{ github.workspace }}/tests/worker/fixtures/other-app + NCDD_WORKSPACE: ${{ github.workspace }}/tests/worker/fixtures/workspace run: | - test "$(DB_TYPE=sqlite sh ./dev-worker extension exec cat /var/www/html/apps-extra/sample_app/marker.txt)" = alpha - test "$(DB_TYPE=sqlite sh ./dev-worker extension exec cat /var/www/html/apps-extra/other_app/marker.txt)" = other - DB_TYPE=sqlite sh ./dev-worker extension status | grep -q 'installed: true' + DB_TYPE=sqlite sh ./dev-worker workspace exec sh -c 'printf state > /var/www/html/data/worker-probe' + test ! -e "$NCDD_WORKSPACE/worker-probe" - name: Collect worker logs if: failure() env: - NCDD_COMPOSE_OVERRIDE: ${{ github.workspace }}/tests/worker/fixtures/compose-extension.yml - TEST_APP_A: ${{ github.workspace }}/tests/worker/fixtures/sample-app - TEST_APP_B: ${{ github.workspace }}/tests/worker/fixtures/other-app - run: DB_TYPE=sqlite sh ./dev-worker extension logs || true + NCDD_WORKSPACE: ${{ github.workspace }}/tests/worker/fixtures/workspace + run: DB_TYPE=sqlite sh ./dev-worker workspace logs || true - name: Destroy worker if: always() env: - NCDD_COMPOSE_OVERRIDE: ${{ github.workspace }}/tests/worker/fixtures/compose-extension.yml - TEST_APP_A: ${{ github.workspace }}/tests/worker/fixtures/sample-app - TEST_APP_B: ${{ github.workspace }}/tests/worker/fixtures/other-app - run: DB_TYPE=sqlite sh ./dev-worker extension destroy || true + NCDD_WORKSPACE: ${{ github.workspace }}/tests/worker/fixtures/workspace + run: DB_TYPE=sqlite sh ./dev-worker workspace destroy || true diff --git a/README.md b/README.md index b9e1e66..dabeae5 100644 --- a/README.md +++ b/README.md @@ -21,5 +21,5 @@ and other advanced configuration, see the - [Advanced setup](docs/advanced-setup.md) - [App development](docs/apps-development.md) -- [Compose extensions and devcontainers](docs/compose-extensions.md) +- [Worker workspaces and devcontainers](docs/worker-workspaces.md) - [FAQ](docs/faq.md) diff --git a/dev-worker b/dev-worker index 5a3e041..2c0f2fc 100644 --- a/dev-worker +++ b/dev-worker @@ -21,11 +21,11 @@ Commands: Environment: 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) + MARIADB_VERSION= 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 - NCDD_COMPOSE_OVERRIDE= Optional Compose override appended to the base topology + NCDD_WORKSPACE= Optional directory mounted as /var/www/html/apps-extra WORKER_READY_TIMEOUT=180 Seconds to wait for a clean installation EOF } @@ -56,25 +56,15 @@ esac repo_root="$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)" db_type="${DB_TYPE:-mysql}" -compose_override="${NCDD_COMPOSE_OVERRIDE:-}" - -if [ -n "$compose_override" ]; then - case "$compose_override" in - /*) ;; - *) - override_dir="$(dirname -- "$compose_override")" - override_name="$(basename -- "$compose_override")" - [ -d "$override_dir" ] || { - echo "NCDD_COMPOSE_OVERRIDE directory does not exist: $override_dir" >&2 - exit 2 - } - compose_override="$(CDPATH= cd -- "$override_dir" && pwd -P)/$override_name" - ;; - esac - [ -f "$compose_override" ] || { - echo "NCDD_COMPOSE_OVERRIDE is not a file: $compose_override" >&2 +workspace="${NCDD_WORKSPACE:-}" + +if [ -n "$workspace" ]; then + [ -d "$workspace" ] || { + echo "NCDD_WORKSPACE is not a directory: $workspace" >&2 exit 2 } + workspace="$(CDPATH= cd -- "$workspace" && pwd -P)" + export NCDD_WORKSPACE="$workspace" fi case "$db_type" in @@ -97,24 +87,29 @@ if [ "$db_type" = "sqlite" ]; then 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 + mariadb_suffix="$(printf '%s' "$mariadb_version" | tr -d '.')" + case "$mariadb_suffix" in + *[!0-9]*|'') + echo "Invalid MARIADB_VERSION: $mariadb_version" >&2 exit 2 ;; esac + mariadb_service="mariadb$mariadb_suffix" + if ! grep -q "^ $mariadb_service:" "$repo_root/.docker/database-services.yml"; then + echo "Unsupported MARIADB_VERSION: $mariadb_version" >&2 + exit 2 + fi + export DB_SERVICE_TYPE="$mariadb_service" export DB_DRIVER=mysql else export DB_SERVICE_TYPE="${DB_SERVICE_TYPE:-$db_type}" fi compose() { - if [ -n "$compose_override" ]; then + if [ -n "$workspace" ]; then docker compose --project-directory "$repo_root" \ --file "$repo_root/docker-compose.yml" \ - --file "$compose_override" "$@" + --file "$repo_root/.docker/workspace.yml" "$@" else docker compose --project-directory "$repo_root" --file "$repo_root/docker-compose.yml" "$@" fi @@ -216,7 +211,8 @@ case "$command" in 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. + # relying on host ownership. The optional workspace is outside worker_root + # and is never removed. if [ -d "$worker_root" ]; then docker compose --project-directory "$repo_root" --file "$repo_root/docker-compose.yml" \ run --rm --no-deps -u 0 --entrypoint sh -v "$worker_root:/worker" nextcloud \ diff --git a/docs/apps-development.md b/docs/apps-development.md index 7827125..2963e1d 100644 --- a/docs/apps-development.md +++ b/docs/apps-development.md @@ -1,13 +1,12 @@ # Start development of apps -Nextcloud applications can continue to be developed directly under -`volumes/nextcloud/apps-extra`. That directory is an app space and may contain -as many applications as a development setup needs. +Nextcloud applications can be developed directly under +`volumes/nextcloud/apps-extra`. That directory may contain one application or +dozens of applications required by the development environment. -For repositories that keep application checkouts outside this repository, use a -[Compose extension](compose-extensions.md) rather than creating another -Nextcloud topology. The extension may mount one app, several apps, or add -supporting services while NCDD remains responsible for the runtime. +For isolated workers, an external directory can be used as the worker workspace. +The workspace is mounted as the complete `/var/www/html/apps-extra` directory, +so NCDD does not need to know which applications it contains. ## Local apps-extra workflow @@ -27,35 +26,48 @@ composer install npm ci ``` -## External application checkouts +## Worker workspace -A project-specific Compose override can mount any number of source trees into -the same worker. For example: +A workspace is simply a directory containing the applications needed by that +worker: -```yaml -services: - nextcloud: - volumes: - - /work/my_app:/var/www/html/apps-extra/my_app - - /work/my_dependency:/var/www/html/apps-extra/my_dependency +```text +/tmp/workspaces/issue-8985/ +├── libresign/ +├── twofactor_gateway/ +├── notifications/ +└── any_other_app/ ``` -Start the runtime with: +Start an isolated worker with that directory: ```bash -NCDD_COMPOSE_OVERRIDE=/work/project/ncdd.override.yml \ +NCDD_WORKSPACE=/tmp/workspaces/issue-8985 \ DB_TYPE=sqlite \ -sh ./dev-worker my-project up +sh ./dev-worker issue-8985 up ``` -Application-specific setup remains owned by the application repository. It can -run commands explicitly through the worker, for example: +The complete workspace is mounted at: + +```text +/var/www/html/apps-extra +``` + +NCDD does not assign a primary application, manage application repositories or +limit how many applications the workspace contains. Clones, Git worktrees and +other source layouts are the responsibility of the caller. + +Application-specific setup remains owned by the application repository or the +agent operating the worker: ```bash -NCDD_COMPOSE_OVERRIDE=/work/project/ncdd.override.yml \ +NCDD_WORKSPACE=/tmp/workspaces/issue-8985 \ DB_TYPE=sqlite \ -sh ./dev-worker my-project exec sh -lc \ - 'cd /var/www/html/apps-extra/my_app && composer install' +sh ./dev-worker issue-8985 exec sh -lc \ + 'cd /var/www/html/apps-extra/libresign && composer install' ``` +Worker runtime state remains under `.workers/`; destroying a worker +never deletes its external workspace. + ⬅️ [Back to index](../README.md) diff --git a/docs/compose-extensions.md b/docs/compose-extensions.md deleted file mode 100644 index 00e4115..0000000 --- a/docs/compose-extensions.md +++ /dev/null @@ -1,103 +0,0 @@ -# Compose extensions and devcontainers - -The base Docker Compose topology in this repository is the canonical Nextcloud -development runtime. Projects may extend that topology without teaching NCDD -which application is being developed. - -This keeps one lifecycle for Nextcloud, PHP, databases, proxy, mail and optional -services while allowing a consumer to mount zero, one or many applications and -add project-specific services. - -## Worker extension contract - -Create a normal Compose override in the consuming project. For example: - -```yaml -services: - nextcloud: - volumes: - - /work/app-a:/var/www/html/apps-extra/app_a - - /work/app-b:/var/www/html/apps-extra/app_b - nginx: - volumes: - - /work/app-a:/var/www/html/apps-extra/app_a:ro - - /work/app-b:/var/www/html/apps-extra/app_b:ro -``` - -Pass that file to the existing worker lifecycle: - -```bash -NCDD_COMPOSE_OVERRIDE=/work/project/ncdd.override.yml \ -DB_TYPE=sqlite \ -sh ./dev-worker project-a up -``` - -The same override must be supplied to worker commands that need the complete -Compose model: - -```bash -NCDD_COMPOSE_OVERRIDE=/work/project/ncdd.override.yml \ -DB_TYPE=sqlite \ -sh ./dev-worker project-a status - -NCDD_COMPOSE_OVERRIDE=/work/project/ncdd.override.yml \ -DB_TYPE=sqlite \ -sh ./dev-worker project-a destroy -``` - -The override is deliberately generic. NCDD does not assign a primary app, -persist an app id, decide where application source lives, or execute -application-specific setup hooks. - -## Runtime dimensions - -Compose extensions reuse the same worker dimensions as the base environment: - -- `PHP_VERSION` -- `VERSION_NEXTCLOUD` -- `DB_TYPE` -- `MARIADB_VERSION` where applicable -- `DB_SQL_MODE` where applicable - -Run: - -```bash -sh ./dev-worker project-a urls -``` - -to print the deterministic Nextcloud and Mailpit hostnames for a worker. - -## Application setup - -Dependency installation, app enablement and other initialization belong to the -consumer repository. Keep those steps in its scripts, Makefile, task runner or -devcontainer lifecycle and invoke the NCDD worker when container access is -needed. - -For example: - -```bash -NCDD_COMPOSE_OVERRIDE=/work/project/ncdd.override.yml \ -sh ./dev-worker project-a exec sh -lc \ - 'cd /var/www/html/apps-extra/app_a && composer install && occ app:enable app_a' -``` - -This avoids growing an NCDD API for application-specific setup. - -## Devcontainers - -A downstream devcontainer should remain a thin adapter over the same Compose -topology. It can use the `nextcloud` service as its runtime service and add a -project-owned override with its source mounts or extra services. - -Do not copy the Nextcloud, database, proxy, mail or Redis definitions into the -application repository. Those remain owned by NCDD. - -## Isolation - -Each worker owns its Compose project name and mutable state under -`.workers/`. A Compose extension changes that worker's service -model but does not create a second lifecycle. - -The worker id is an isolation namespace for development convenience, not a -security boundary between untrusted workloads sharing the same Docker daemon. diff --git a/docs/worker-workspaces.md b/docs/worker-workspaces.md new file mode 100644 index 0000000..0b0bfff --- /dev/null +++ b/docs/worker-workspaces.md @@ -0,0 +1,94 @@ +# Worker workspaces and devcontainers + +The base Docker Compose topology in this repository is the canonical Nextcloud +development runtime. A worker may optionally attach one external workspace +containing all applications needed by that environment. + +This keeps the contract intentionally small: NCDD owns the runtime and the +caller owns the source directory. + +## Workspace contract + +A workspace is a normal host directory whose direct children are Nextcloud +application checkouts: + +```text +/work/issue-8985/ +├── libresign/ +├── twofactor_gateway/ +└── notifications/ +``` + +Start a worker with: + +```bash +NCDD_WORKSPACE=/work/issue-8985 \ +DB_TYPE=sqlite \ +sh ./dev-worker issue-8985 up +``` + +NCDD mounts that directory as: + +```text +/var/www/html/apps-extra +``` + +The workspace can contain zero, one or many applications. NCDD does not know +which application is primary, how repositories were created, or which +application an agent intends to modify. + +Without `NCDD_WORKSPACE`, the existing local +`volumes/nextcloud/apps-extra` behavior is unchanged. + +## Runtime dimensions + +The workspace reuses the same worker dimensions as the base environment: + +- `PHP_VERSION` +- `VERSION_NEXTCLOUD` +- `DB_TYPE` +- `MARIADB_VERSION` where applicable +- `DB_SQL_MODE` where applicable + +For example: + +```bash +NCDD_WORKSPACE=/work/pr-123 \ +PHP_VERSION=83 \ +VERSION_NEXTCLOUD=stable35 \ +DB_TYPE=mariadb \ +sh ./dev-worker pr-123 up +``` + +## Application setup + +Dependency installation, builds and app enablement belong to the caller rather +than the NCDD lifecycle: + +```bash +NCDD_WORKSPACE=/work/pr-123 \ +sh ./dev-worker pr-123 exec sh -lc \ + 'cd /var/www/html/apps-extra/libresign && composer install' +``` + +This lets agents create whatever set of app worktrees a task requires without +expanding the NCDD API for individual applications. + +## Isolation and cleanup + +Each worker owns its Compose project name and mutable state under +`.workers/`. The workspace is external source state and is never +removed by `dev-worker destroy`. + +Workers can therefore run in parallel with different PHP, Nextcloud and +database dimensions and with completely different application directories. + +## Devcontainers + +A downstream devcontainer should use the same worker contract instead of +carrying a second Compose topology. The application repository or agent can +prepare a workspace, start the worker and then execute development commands in +the `nextcloud` service. + +The worker id is an isolation namespace for development convenience, not a +security boundary between untrusted workloads sharing the same Docker daemon. diff --git a/tests/worker/contract.bats b/tests/worker/contract.bats index 6a5ee5c..02181f0 100644 --- a/tests/worker/contract.bats +++ b/tests/worker/contract.bats @@ -49,22 +49,20 @@ setup() { [[ "$output" == *"DB_DRIVER: sqlite"* ]] } -@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 "MariaDB version selects the matching configured service" { + for version in 10.6 10.11; do + service="mariadb${version//./}" + run env DB_TYPE=mariadb MARIADB_VERSION="$version" sh "$WORKER" "maria-${version//./-}" config + [ "$status" -eq 0 ] + [[ "$output" == *"image: mariadb:$version"* ]] + [[ "$output" == *"DB_TYPE: mariadb"* ]] + [[ "$output" == *"DB_DRIVER: mysql"* ]] + grep -q "^ $service:" "$REPO_ROOT/.docker/database-services.yml" + done } @test "unsupported MariaDB versions fail before startup" { - run env DB_TYPE=mariadb MARIADB_VERSION=11.4 sh "$WORKER" maria114 config + run env DB_TYPE=mariadb MARIADB_VERSION=99.99 sh "$WORKER" maria-unsupported config [ "$status" -eq 2 ] [[ "$output" == *"Unsupported MARIADB_VERSION"* ]] } @@ -79,32 +77,28 @@ setup() { [ "$found" -eq 1 ] } -@test "worker accepts a generic Compose override with multiple app mounts" { - override="$REPO_ROOT/tests/worker/fixtures/compose-extension.yml" - app_a="$REPO_ROOT/tests/worker/fixtures/sample-app" - app_b="$REPO_ROOT/tests/worker/fixtures/other-app" - - run env \ - NCDD_COMPOSE_OVERRIDE="$override" \ - TEST_APP_A="$app_a" \ - TEST_APP_B="$app_b" \ - DB_TYPE=sqlite \ - sh "$WORKER" compose-extension config - +@test "worker mounts one workspace as the complete apps-extra directory" { + workspace="$REPO_ROOT/tests/worker/fixtures/workspace" + run env NCDD_WORKSPACE="$workspace" DB_TYPE=sqlite sh "$WORKER" workspace-config config [ "$status" -eq 0 ] - [[ "$output" == *"source: $app_a"* ]] - [[ "$output" == *"target: /var/www/html/apps-extra/sample_app"* ]] - [[ "$output" == *"source: $app_b"* ]] - [[ "$output" == *"target: /var/www/html/apps-extra/other_app"* ]] + [[ "$output" == *"source: $workspace"* ]] + [[ "$output" == *"target: /var/www/html/apps-extra"* ]] + [[ "$output" != *"target: /var/www/html/apps-extra/sample_app"* ]] + [[ "$output" != *"target: /var/www/html/apps-extra/other_app"* ]] } -@test "worker rejects a missing Compose override before startup" { - run env NCDD_COMPOSE_OVERRIDE="$REPO_ROOT/does-not-exist.yml" sh "$WORKER" missing-override config +@test "worker rejects a missing workspace before startup" { + run env NCDD_WORKSPACE="$REPO_ROOT/does-not-exist" sh "$WORKER" missing-workspace config [ "$status" -eq 2 ] - [[ "$output" == *"NCDD_COMPOSE_OVERRIDE is not a file"* ]] + [[ "$output" == *"NCDD_WORKSPACE is not a directory"* ]] } -@test "worker CI does not pin a concrete PHP series" { +@test "worker CI does not pin a concrete PHP series outside its matrix inputs" { workflow="$REPO_ROOT/.github/workflows/worker-tests.yml" ! grep -Eq 'Dockerfile\.php[0-9]+|nextcloud-dev-php[0-9]+' "$workflow" } + +@test "MariaDB matrix version is not repeated in workflow conditionals or worker names" { + workflow="$REPO_ROOT/.github/workflows/worker-tests.yml" + ! grep -Eq "matrix\.mariadb ==|MARIADB_VERSION\" = \"[0-9]|maria-[0-9]+-[0-9]+" "$workflow" +} diff --git a/tests/worker/fixtures/compose-extension.yml b/tests/worker/fixtures/compose-extension.yml deleted file mode 100644 index d6a818d..0000000 --- a/tests/worker/fixtures/compose-extension.yml +++ /dev/null @@ -1,30 +0,0 @@ -# SPDX-FileCopyrightText: 2026 LibreCode coop and contributors -# SPDX-License-Identifier: AGPL-3.0-or-later - -services: - nextcloud: - volumes: - - type: bind - source: ${TEST_APP_A:?TEST_APP_A is required} - target: /var/www/html/apps-extra/sample_app - - type: bind - source: ${TEST_APP_B:?TEST_APP_B is required} - target: /var/www/html/apps-extra/other_app - nginx: - volumes: - - type: bind - source: ${TEST_APP_A:?TEST_APP_A is required} - target: /var/www/html/apps-extra/sample_app - read_only: true - - type: bind - source: ${TEST_APP_B:?TEST_APP_B is required} - target: /var/www/html/apps-extra/other_app - read_only: true - playwright: - volumes: - - type: bind - source: ${TEST_APP_A:?TEST_APP_A is required} - target: /var/www/html/apps-extra/sample_app - - type: bind - source: ${TEST_APP_B:?TEST_APP_B is required} - target: /var/www/html/apps-extra/other_app diff --git a/tests/worker/fixtures/other-app/appinfo/info.xml b/tests/worker/fixtures/workspace/other_app/appinfo/info.xml similarity index 75% rename from tests/worker/fixtures/other-app/appinfo/info.xml rename to tests/worker/fixtures/workspace/other_app/appinfo/info.xml index 2195178..2f1da3c 100644 --- a/tests/worker/fixtures/other-app/appinfo/info.xml +++ b/tests/worker/fixtures/workspace/other_app/appinfo/info.xml @@ -6,8 +6,8 @@ other_app Worker fixture app - Fixture for the downstream worker contract - Fixture for the downstream worker contract. + Fixture for the worker workspace contract + Fixture for the worker workspace contract. 1.0.0 agpl LibreCode coop and contributors diff --git a/tests/worker/fixtures/other-app/marker.txt b/tests/worker/fixtures/workspace/other_app/marker.txt similarity index 100% rename from tests/worker/fixtures/other-app/marker.txt rename to tests/worker/fixtures/workspace/other_app/marker.txt diff --git a/tests/worker/fixtures/sample-app/appinfo/info.xml b/tests/worker/fixtures/workspace/sample_app/appinfo/info.xml similarity index 76% rename from tests/worker/fixtures/sample-app/appinfo/info.xml rename to tests/worker/fixtures/workspace/sample_app/appinfo/info.xml index abb1a44..b47db47 100644 --- a/tests/worker/fixtures/sample-app/appinfo/info.xml +++ b/tests/worker/fixtures/workspace/sample_app/appinfo/info.xml @@ -6,8 +6,8 @@ sample_app Worker fixture app - Fixture for the downstream worker contract - Fixture for the downstream worker contract. + Fixture for the worker workspace contract + Fixture for the worker workspace contract. 1.0.0 agpl LibreCode coop and contributors diff --git a/tests/worker/fixtures/sample-app/marker.txt b/tests/worker/fixtures/workspace/sample_app/marker.txt similarity index 100% rename from tests/worker/fixtures/sample-app/marker.txt rename to tests/worker/fixtures/workspace/sample_app/marker.txt From 2091046e8cbb025099367fc4739243f493d6fae8 Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Sun, 4 Oct 2026 23:11:34 +0000 Subject: [PATCH 2/3] refactor: keep workspace contract app-agnostic Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- .docker/workspace.yml | 6 ------ docs/apps-development.md | 17 ++++++++--------- docs/worker-workspaces.md | 14 +++++++------- 3 files changed, 15 insertions(+), 22 deletions(-) diff --git a/.docker/workspace.yml b/.docker/workspace.yml index 88e2e4a..b00fd48 100644 --- a/.docker/workspace.yml +++ b/.docker/workspace.yml @@ -18,9 +18,3 @@ services: - type: bind source: ${NCDD_WORKSPACE:?NCDD_WORKSPACE is required} target: /var/www/html/apps-extra - eurooffice: - volumes: - - type: bind - source: ${NCDD_WORKSPACE:?NCDD_WORKSPACE is required}/eurooffice-plugin - target: /var/www/onlyoffice/documentserver/sdkjs-plugins/libresign - read_only: true diff --git a/docs/apps-development.md b/docs/apps-development.md index 2963e1d..c02a585 100644 --- a/docs/apps-development.md +++ b/docs/apps-development.md @@ -32,19 +32,18 @@ A workspace is simply a directory containing the applications needed by that worker: ```text -/tmp/workspaces/issue-8985/ -├── libresign/ -├── twofactor_gateway/ -├── notifications/ +/tmp/workspaces/issue-123/ +├── app_a/ +├── app_b/ └── any_other_app/ ``` Start an isolated worker with that directory: ```bash -NCDD_WORKSPACE=/tmp/workspaces/issue-8985 \ +NCDD_WORKSPACE=/tmp/workspaces/issue-123 \ DB_TYPE=sqlite \ -sh ./dev-worker issue-8985 up +sh ./dev-worker issue-123 up ``` The complete workspace is mounted at: @@ -61,10 +60,10 @@ Application-specific setup remains owned by the application repository or the agent operating the worker: ```bash -NCDD_WORKSPACE=/tmp/workspaces/issue-8985 \ +NCDD_WORKSPACE=/tmp/workspaces/issue-123 \ DB_TYPE=sqlite \ -sh ./dev-worker issue-8985 exec sh -lc \ - 'cd /var/www/html/apps-extra/libresign && composer install' +sh ./dev-worker issue-123 exec sh -lc \ + 'cd /var/www/html/apps-extra/app_a && composer install' ``` Worker runtime state remains under `.workers/`; destroying a worker diff --git a/docs/worker-workspaces.md b/docs/worker-workspaces.md index 0b0bfff..9dc64f6 100644 --- a/docs/worker-workspaces.md +++ b/docs/worker-workspaces.md @@ -13,18 +13,18 @@ A workspace is a normal host directory whose direct children are Nextcloud application checkouts: ```text -/work/issue-8985/ -├── libresign/ -├── twofactor_gateway/ -└── notifications/ +/work/issue-123/ +├── app_a/ +├── app_b/ +└── app_c/ ``` Start a worker with: ```bash -NCDD_WORKSPACE=/work/issue-8985 \ +NCDD_WORKSPACE=/work/issue-123 \ DB_TYPE=sqlite \ -sh ./dev-worker issue-8985 up +sh ./dev-worker issue-123 up ``` NCDD mounts that directory as: @@ -68,7 +68,7 @@ than the NCDD lifecycle: ```bash NCDD_WORKSPACE=/work/pr-123 \ sh ./dev-worker pr-123 exec sh -lc \ - 'cd /var/www/html/apps-extra/libresign && composer install' + 'cd /var/www/html/apps-extra/app_a && composer install' ``` This lets agents create whatever set of app worktrees a task requires without From aa0debaaa399497da27ed58ccbf2b9e18a1a5ce1 Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Sun, 4 Oct 2026 23:26:56 +0000 Subject: [PATCH 3/3] fix: synchronize proxy network before backends start Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- .docker/scripts/proxy-coordinator.sh | 6 +++++- docker-compose.yml | 16 ++++++++++------ tests/proxy/fixtures/compose.yml | 11 +++++++++-- tests/proxy/services.bats | 13 +++++++++++++ 4 files changed, 37 insertions(+), 9 deletions(-) diff --git a/.docker/scripts/proxy-coordinator.sh b/.docker/scripts/proxy-coordinator.sh index 8225908..de77641 100755 --- a/.docker/scripts/proxy-coordinator.sh +++ b/.docker/scripts/proxy-coordinator.sh @@ -5,6 +5,7 @@ set -eu script_dir="$(cd -- "$(dirname -- "$0")" && pwd)" proxy_lib_dir="${PROXY_LIB_DIR:-$script_dir/proxy}" release_marker=/tmp/librecode-proxy-lease-released +ready_marker=/tmp/librecode-proxy-ready # shellcheck source=.docker/scripts/proxy/common.sh . "$proxy_lib_dir/common.sh" @@ -47,7 +48,7 @@ success() { reused) echo '✅ Existing LibreCode development proxy reused. Coordinator lease is active.' ;; - started) + started) echo '✅ Development proxy started successfully. Coordinator lease is active.' ;; esac @@ -58,6 +59,7 @@ release() { return 0 fi + rm -f "$ready_marker" echo 'Releasing shared development proxy lease.' disconnect_proxy_from_project_network if release_proxy_if_unused; then @@ -84,6 +86,7 @@ wait_for_shutdown() { } run() { + rm -f "$ready_marker" validate_environment ensure_proxy_network install_proxy_assets @@ -96,6 +99,7 @@ run() { trap shutdown INT TERM HUP connect_proxy_to_project_network + touch "$ready_marker" if ! report_environment_ready; then echo 'Could not print environment banner.' >&2 diff --git a/docker-compose.yml b/docker-compose.yml index 7a880da..99d1054 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -46,12 +46,12 @@ services: - host.docker.internal:host-gateway nginx: image: ghcr.io/librecodecoop/nextcloud-dev-nginx:latest - # build: - # context: .docker/ - # dockerfile: Dockerfile.nginx restart: unless-stopped depends_on: - - nextcloud + nextcloud: + condition: service_started + proxy-coordinator: + condition: service_healthy volumes: - ${WORKER_VOLUMES_DIR:-./volumes}/nextcloud:/var/www/html:ro - ${WORKER_VOLUMES_DIR:-./volumes}/nginx/includes:/etc/nginx/conf.d/includes/ @@ -77,9 +77,13 @@ services: - NEXTCLOUD_ADMIN_USER=${NEXTCLOUD_ADMIN_USER:-admin} - NEXTCLOUD_ADMIN_PASSWORD=${NEXTCLOUD_ADMIN_PASSWORD:-admin} - VERSION_NEXTCLOUD=${VERSION_NEXTCLOUD:-master} + healthcheck: + test: ["CMD-SHELL", "test -f /tmp/librecode-proxy-ready"] + interval: 1s + timeout: 1s + retries: 30 + start_period: 1s restart: "no" - depends_on: - - nextcloud mailpit: image: axllent/mailpit:v1.31.3@sha256:ed9b00c609e77e99c79b93f1178255ebc271868920f2c69a8d166bd5634ed10d environment: diff --git a/tests/proxy/fixtures/compose.yml b/tests/proxy/fixtures/compose.yml index d7158d6..409f0e3 100644 --- a/tests/proxy/fixtures/compose.yml +++ b/tests/proxy/fixtures/compose.yml @@ -5,6 +5,9 @@ services: - VIRTUAL_HOST=${COMPOSE_PROJECT_NAME}.localhost - VIRTUAL_PORT=80 - SELF_SIGNED_HOST=${COMPOSE_PROJECT_NAME}.localhost + depends_on: + proxy-coordinator: + condition: service_healthy proxy-coordinator: image: docker:29.8.1-cli@sha256:018edbc908e08fcc9dbf029c812c34251e9b4719e6f71ca0e5eae2a987d014ca @@ -23,6 +26,10 @@ services: - NEXTCLOUD_ADMIN_USER=admin - NEXTCLOUD_ADMIN_PASSWORD=admin - VERSION_NEXTCLOUD=master + healthcheck: + test: ["CMD-SHELL", "test -f /tmp/librecode-proxy-ready"] + interval: 1s + timeout: 1s + retries: 30 + start_period: 1s restart: "no" - depends_on: - - nginx diff --git a/tests/proxy/services.bats b/tests/proxy/services.bats index 1530630..9a02fb7 100644 --- a/tests/proxy/services.bats +++ b/tests/proxy/services.bats @@ -48,3 +48,16 @@ setup() { grep -q '^docker network disconnect current_default librecode-dev-proxy$' "$TEST_LOG" } + +@test "backend services wait for coordinator network readiness" { + main="$REPO_ROOT/docker-compose.yml" + fixture="$REPO_ROOT/tests/proxy/fixtures/compose.yml" + + grep -A8 '^ nginx:' "$main" | grep -q 'proxy-coordinator:' + grep -A10 '^ nginx:' "$main" | grep -q 'condition: service_healthy' + grep -A8 '^ nginx:' "$fixture" | grep -q 'proxy-coordinator:' + grep -A10 '^ nginx:' "$fixture" | grep -q 'condition: service_healthy' + + grep -A20 '^ proxy-coordinator:' "$main" | grep -q 'test -f /tmp/librecode-proxy-ready' + grep -A20 '^ proxy-coordinator:' "$fixture" | grep -q 'test -f /tmp/librecode-proxy-ready' +}