Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion .docker/scripts/proxy-coordinator.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -84,6 +86,7 @@ wait_for_shutdown() {
}

run() {
rm -f "$ready_marker"
validate_environment
ensure_proxy_network
install_proxy_assets
Expand All @@ -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
Expand Down
20 changes: 20 additions & 0 deletions .docker/workspace.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# 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
64 changes: 21 additions & 43 deletions .github/workflows/worker-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -101,75 +101,53 @@ 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:
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
- 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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
50 changes: 23 additions & 27 deletions dev-worker
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,11 @@ Commands:
Environment:
DB_TYPE=sqlite|mysql|mariadb|pgsql Database backend
DB_HOST=<hostname> Connection hostname (defaults to DB_TYPE)
MARIADB_VERSION=10.6|10.11 MariaDB series (default: 10.11)
MARIADB_VERSION=<version> MariaDB series (default: 10.11)
DB_SQL_MODE=<modes> Optional global SQL mode for MySQL-compatible backends
PHP_VERSION=<version> Existing PHP image selector
VERSION_NEXTCLOUD=<ref> Existing Nextcloud ref selector
NCDD_COMPOSE_OVERRIDE=<file> Optional Compose override appended to the base topology
NCDD_WORKSPACE=<path> Optional directory mounted as /var/www/html/apps-extra
WORKER_READY_TIMEOUT=180 Seconds to wait for a clean installation
EOF
}
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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 \
Expand Down
16 changes: 10 additions & 6 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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/
Expand All @@ -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:
Expand Down
59 changes: 35 additions & 24 deletions docs/apps-development.md
Original file line number Diff line number Diff line change
@@ -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

Expand All @@ -27,35 +26,47 @@ 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-123/
├── app_a/
├── app_b/
└── 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-123 \
DB_TYPE=sqlite \
sh ./dev-worker my-project up
sh ./dev-worker issue-123 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-123 \
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-123 exec sh -lc \
'cd /var/www/html/apps-extra/app_a && composer install'
```

Worker runtime state remains under `.workers/<worker-id>`; destroying a worker
never deletes its external workspace.

⬅️ [Back to index](../README.md)
Loading
Loading