From 4428dec18c9b61271b3a2410ba152808db75b34a Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Sun, 4 Oct 2026 23:37:25 +0000 Subject: [PATCH 01/39] refactor: reuse NCDD for the devcontainer Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- .devcontainer/README.md | 56 ++++++++-------------- .devcontainer/devcontainer.json | 39 +++------------ .devcontainer/docker-compose.yml | 82 ++++---------------------------- .devcontainer/prepare.sh | 24 ++++++++++ .devcontainer/setup.sh | 40 +++++++++++----- .gitignore | 1 + 6 files changed, 90 insertions(+), 152 deletions(-) create mode 100644 .devcontainer/prepare.sh diff --git a/.devcontainer/README.md b/.devcontainer/README.md index 7e9d83dea1..0ff7cf8548 100644 --- a/.devcontainer/README.md +++ b/.devcontainer/README.md @@ -1,47 +1,33 @@ -## Starting devcontainer +# LibreSign dev container -- Use vscode (codium don't will work) -- Stup the extension devcontainer -- Open libresign folder at vscode -- After open you will see a message "Reopen in container", do this +The dev container is a thin adapter over +`LibreCodeCoop/nextcloud-docker-development` (NCDD). -## Stopping devcontainer +Open the LibreSign repository in VS Code and choose **Reopen in Container**. +The initialization step fetches the pinned NCDD revision, starts its canonical +Nextcloud environment, and mounts this checkout at +`/var/www/html/apps-extra/libresign`. -```bash -docker stop $(docker ps -aq) -docker rm $(docker ps -aq) -``` -## Cleaning all volumes - -This will be necessary when you want to have a new clean environment - -```bash -docker volume rm $(docker volume ls -q ) -``` -## Looking logs +The application URL is printed by `.devcontainer/setup.sh` after setup. +NCDD also exposes its Mailpit endpoint through the shared development proxy. -### Nginx +The adapter defaults to PHP 8.3, Nextcloud `master`, MariaDB 10.6. These may be +changed on the host before reopening the container: ```bash -docker logs libresign_devcontainer-nginx-1 -f --tail 100 +export LIBRESIGN_PHP_VERSION=83 +export LIBRESIGN_NEXTCLOUD_VERSION=master +export LIBRESIGN_DB_TYPE=mariadb +export LIBRESIGN_MARIADB_VERSION=10.6 ``` -### Nextcloud - -- Open the console inside vscode -- Run: - ```bash - tail -f data/nextcloud.log - ``` - -### Database +Closing or rebuilding this dev container only affects its Compose project. +Do not use global Docker cleanup commands to stop unrelated containers or +delete unrelated volumes. -- Open the console inside vscode -- Run: - ```bash - tail -f data/database.log - ``` +Canonical development documentation lives in the +[LibreSign Developer Manual](https://docs.libresign.coop/developer_manual/). diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 2f9cef86b4..7a1c8a2c6f 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,21 +1,16 @@ { "name": "LibreSign", + "initializeCommand": "sh .devcontainer/prepare.sh", "dockerComposeFile": "docker-compose.yml", "service": "nextcloud", - "forwardPorts": [ - 80, - 8025 - ], + "workspaceFolder": "/var/www/html/apps-extra/libresign", + "overrideCommand": false, + "shutdownAction": "stopCompose", "containerEnv": { "XDEBUG_MODE": "debug", - "XDEBUG_CONFIG": "client_host=127.0.0.1 client_port=9003 start_with_request=yes" + "XDEBUG_CONFIG": "client_host=host.docker.internal client_port=9003 start_with_request=yes" }, "customizations": { - "codespaces": { - "openFiles": [ - ".devcontainer/README.md" - ] - }, "vscode": { "extensions": [ "MehediDracula.php-namespace-resolver", @@ -39,32 +34,12 @@ ], "gitlens.showWelcomeOnInstall": false, "telemetry.telemetryLevel": "off", - "intelephense.telemetry.enabled": false, - "intelephense.environment.includePaths": [ - "apps-extra" - ], - "git.scanRepositories": [ - "/apps-extra/libresign" - ] + "intelephense.telemetry.enabled": false } } }, - "workspaceFolder": "/var/www/html", - "overrideCommand": true, - "postAttachCommand": "bash /var/www/html/apps-extra/libresign/.devcontainer/setup.sh", + "postCreateCommand": "bash /var/www/html/apps-extra/libresign/.devcontainer/setup.sh", "portsAttributes": { - "80": { - "label": "http", - "protocol": "http", - "onAutoForward": "openBrowser", - "elevateIfNeeded": true - }, - "8025": { - "label": "mailpit", - "protocol": "http", - "onAutoForward": "notify", - "elevateIfNeeded": true - }, "9000": { "label": "php-fpm", "onAutoForward": "ignore" diff --git a/.devcontainer/docker-compose.yml b/.devcontainer/docker-compose.yml index a896ef003f..ef264dd8b1 100644 --- a/.devcontainer/docker-compose.yml +++ b/.devcontainer/docker-compose.yml @@ -1,80 +1,18 @@ -# SPDX-FileCopyrightText: 2024 LibreCode coop and contributors +# SPDX-FileCopyrightText: 2026 LibreCode coop and contributors # SPDX-License-Identifier: AGPL-3.0-or-later + +include: + - path: ./.nextcloud-docker-development/docker-compose.yml + project_directory: ./.nextcloud-docker-development + env_file: ./.nextcloud-docker-development/.env + services: - # pgsql: - # image: postgres:13-alpine - # restart: unless-stopped - # volumes: - # - ./volumes/postgres/data:/var/lib/postgresql/data - # environment: - # - POSTGRES_DB=${POSTGRES_DB:-nextcloud} - # - POSTGRES_USER=${POSTGRES_USER:-nextcloud} - # - POSTGRES_PASSWORD=${POSTGRES_PASSWORD:-nextcloud} - # ports: - # - 127.0.0.1:5432:5432 - # command: postgres -c log_statement=all - mysql: - image: mariadb:10.6 - volumes: - - ./.docker/config/mysql:/etc/mysql/conf.d - - mysql-init:/docker-entrypoint-initdb.d - - mysql-data:/var/lib/mysql - ports: - - 127.0.0.1:3306:3306 - restart: unless-stopped - environment: - - TZ=${TZ:-CET} - - MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD:-root} - - MYSQL_DATABASE=${MYSQL_DATABASE:-nextcloud} - - MYSQL_USER=${MYSQL_USER:-nextcloud} - - MYSQL_PASSWORD=${MYSQL_PASSWORD:-nextcloud} nextcloud: - image: ghcr.io/librecodecoop/nextcloud-dev:8.3 volumes: - - ~/.composer:/var/www/.composer/ - - ~/.npm:/var/www/.npm/ - - nextcloud:/var/www/html - ../:/var/www/html/apps-extra/libresign - restart: unless-stopped - environment: - - HOST_UID=${HOST_UID:-1000} - - HOST_GID=${HOST_GID:-1000} - - DEFAULT_PHONE_REGION=${DEFAULT_PHONE_REGION:-UTC} - - 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} - - MYSQL_DATABASE=${MYSQL_DATABASE:-nextcloud} - - MYSQL_USER=${MYSQL_USER:-nextcloud} - - MYSQL_PASSWORD=${MYSQL_PASSWORD:-nextcloud} - - POSTGRES_DB=${POSTGRES_DB:-nextcloud} - - POSTGRES_USER=${POSTGRES_USER:-nextcloud} - - POSTGRES_PASSWORD=${POSTGRES_PASSWORD:-nextcloud} - - MAIL_FROM_ADDRESS=${MAIL_FROM_ADDRESS:-nextcloud} - - MAIL_DOMAIN=${MAIL_DOMAIN:-mailpit} - - MAIL_SMTPPORT=${MAIL_SMTPPORT:-1025} - - MAIL_SMTPHOST=${MAIL_SMTPHOST:-mailpit} - - VERSION_NEXTCLOUD=${VERSION_NEXTCLOUD:-master} - - AUTOINSTALL=${AUTOINSTALL:-1} - - XDEBUG_CONFIG - - HTTP_PORT=${HTTP_PORT:-80} - extra_hosts: - - host.docker.internal:host-gateway nginx: - image: ghcr.io/librecodecoop/nextcloud-dev-nginx:latest - restart: unless-stopped volumes: - - nextcloud:/var/www/html:ro - ../:/var/www/html/apps-extra/libresign:ro - ports: - - ${HTTP_PORT:-80}:80 - mailpit: - image: axllent/mailpit - ports: - - 127.0.0.1:${MAILPIT_PORT:-8025}:8025 - redis: - image: redis -volumes: - mysql-init: - mysql-data: - nextcloud: + playwright: + volumes: + - ../:/var/www/html/apps-extra/libresign diff --git a/.devcontainer/prepare.sh b/.devcontainer/prepare.sh new file mode 100644 index 0000000000..01370bb848 --- /dev/null +++ b/.devcontainer/prepare.sh @@ -0,0 +1,24 @@ +#!/bin/sh +# SPDX-FileCopyrightText: 2026 LibreCode coop and contributors +# SPDX-License-Identifier: AGPL-3.0-or-later + +set -eu + +repo_root="$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)" +ncdd_dir="$repo_root/.devcontainer/.nextcloud-docker-development" +ncdd_commit="788437e228358b505b9fa90431f1d0aae1c08727" + +if [ ! -d "$ncdd_dir/.git" ]; then + rm -rf "$ncdd_dir" + git clone --filter=blob:none --no-checkout https://github.com/LibreCodeCoop/nextcloud-docker-development.git "$ncdd_dir" +fi + +git -C "$ncdd_dir" fetch --depth=1 origin "$ncdd_commit" +git -C "$ncdd_dir" checkout --detach "$ncdd_commit" + +cat > "$ncdd_dir/.env" </dev/null | grep -q 'installed: true'; then + break + fi + sleep 1 +done + +if ! occ status 2>/dev/null | grep -q 'installed: true'; then + echo "Nextcloud did not become ready within 120 seconds." >&2 + exit 1 +fi git config --global --add safe.directory /var/www/html -git config --global --add safe.directory /var/www/html/apps-extra/libresign -cd /var/www/html/apps-extra/libresign +git config --global --add safe.directory "$app_dir" + +cd "$app_dir" git submodule update --init --recursive -if [[ ! -d "vendor" ]]; then + +if [[ ! -d vendor ]]; then composer install fi + occ app:enable libresign occ libresign:install --use-local-cert --java occ libresign:install --use-local-cert --pdftk occ libresign:install --use-local-cert --jsignpdf -occ libresign:configure:openssl --cn=CommonName --c=BR --ou=OrganizationUnit --st=RioDeJaneiro --o=LibreSign --l=RioDeJaneiro -if [[ ! -d "node_modules" ]]; then +occ libresign:configure:openssl --cn=CommonName --c=BR --ou=OrganizationUnit --st=RioDeJaneiro --o=LibreSign --l=RioDeJaneiro + +if [[ ! -d node_modules ]]; then occ theming:config name "LibreSign" occ theming:config url "https://libresign.coop" occ theming:config primary_color "#144042" @@ -31,5 +44,6 @@ if [[ ! -d "node_modules" ]]; then npm ci npm run dev fi -echo "✍️ LibreSign is up!" -echo "If you want to develop at frontend, run the command 'npm run watch'" + +echo "LibreSign is ready at https://${NEXTCLOUD_HOST}" +echo "For frontend development, run: npm run watch" diff --git a/.gitignore b/.gitignore index 47d4670308..962bc42381 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ .idea .vscode .ai-local/ +.devcontainer/.nextcloud-docker-development/ .phpactor* .env .secrets From d4b41d8cab98da95f2e9f6d171e41990e841aecc Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Sun, 4 Oct 2026 23:38:05 +0000 Subject: [PATCH 02/39] test: validate the NCDD devcontainer adapter Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- .github/workflows/devcontainer.yml | 34 ++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 .github/workflows/devcontainer.yml diff --git a/.github/workflows/devcontainer.yml b/.github/workflows/devcontainer.yml new file mode 100644 index 0000000000..19494abc4f --- /dev/null +++ b/.github/workflows/devcontainer.yml @@ -0,0 +1,34 @@ +name: Devcontainer contract + +on: + pull_request: + paths: + - '.devcontainer/**' + - '.github/workflows/devcontainer.yml' + push: + branches: + - main + paths: + - '.devcontainer/**' + - '.github/workflows/devcontainer.yml' + +permissions: + contents: read + +jobs: + validate: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 + - name: Validate adapter scripts + run: | + sh -n .devcontainer/prepare.sh + bash -n .devcontainer/setup.sh + - name: Prepare pinned NCDD + run: sh .devcontainer/prepare.sh + - name: Validate composed environment + run: docker compose --file .devcontainer/docker-compose.yml config --quiet + - name: Verify LibreSign does not duplicate NCDD services + run: | + ! grep -Eq '^[[:space:]]+(database|mysql|mariadb|mailpit|redis):$' .devcontainer/docker-compose.yml + ! grep -Eq '^[[:space:]]+image:' .devcontainer/docker-compose.yml From 73ce507763af7421c8ec47dfee5d7155cbad7900 Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Sun, 4 Oct 2026 23:39:41 +0000 Subject: [PATCH 03/39] fix: resolve devcontainer through the NCDD worker contract Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- .devcontainer/README.md | 8 ++++++-- .devcontainer/docker-compose.yml | 4 +--- .devcontainer/prepare.sh | 13 +++++++------ .gitignore | 2 ++ 4 files changed, 16 insertions(+), 11 deletions(-) diff --git a/.devcontainer/README.md b/.devcontainer/README.md index 0ff7cf8548..1aa96695c1 100644 --- a/.devcontainer/README.md +++ b/.devcontainer/README.md @@ -8,10 +8,14 @@ The dev container is a thin adapter over `LibreCodeCoop/nextcloud-docker-development` (NCDD). Open the LibreSign repository in VS Code and choose **Reopen in Container**. -The initialization step fetches the pinned NCDD revision, starts its canonical -Nextcloud environment, and mounts this checkout at +The initialization step fetches the pinned NCDD revision and asks NCDD's +`dev-worker config` command to resolve the canonical environment for this +worktree. LibreSign only adds its source mount at `/var/www/html/apps-extra/libresign`. +A deterministic worker id is derived from the local worktree path, so separate +worktrees resolve to separate NCDD mutable-state directories. + The application URL is printed by `.devcontainer/setup.sh` after setup. NCDD also exposes its Mailpit endpoint through the shared development proxy. diff --git a/.devcontainer/docker-compose.yml b/.devcontainer/docker-compose.yml index ef264dd8b1..89ac205646 100644 --- a/.devcontainer/docker-compose.yml +++ b/.devcontainer/docker-compose.yml @@ -2,9 +2,7 @@ # SPDX-License-Identifier: AGPL-3.0-or-later include: - - path: ./.nextcloud-docker-development/docker-compose.yml - project_directory: ./.nextcloud-docker-development - env_file: ./.nextcloud-docker-development/.env + - path: ./ncdd.generated.yml services: nextcloud: diff --git a/.devcontainer/prepare.sh b/.devcontainer/prepare.sh index 01370bb848..19fe5de0be 100644 --- a/.devcontainer/prepare.sh +++ b/.devcontainer/prepare.sh @@ -6,6 +6,8 @@ set -eu repo_root="$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)" ncdd_dir="$repo_root/.devcontainer/.nextcloud-docker-development" +generated_compose="$repo_root/.devcontainer/ncdd.generated.yml" +worker_id_file="$repo_root/.devcontainer/.worker-id" ncdd_commit="788437e228358b505b9fa90431f1d0aae1c08727" if [ ! -d "$ncdd_dir/.git" ]; then @@ -16,9 +18,8 @@ fi git -C "$ncdd_dir" fetch --depth=1 origin "$ncdd_commit" git -C "$ncdd_dir" checkout --detach "$ncdd_commit" -cat > "$ncdd_dir/.env" < "$worker_id_file" + +DB_TYPE="${LIBRESIGN_DB_TYPE:-mariadb}" MARIADB_VERSION="${LIBRESIGN_MARIADB_VERSION:-10.6}" PHP_VERSION="${LIBRESIGN_PHP_VERSION:-83}" VERSION_NEXTCLOUD="${LIBRESIGN_NEXTCLOUD_VERSION:-master}" sh "$ncdd_dir/dev-worker" "$worker_id" config > "$generated_compose" diff --git a/.gitignore b/.gitignore index 962bc42381..9d4460854f 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,8 @@ .vscode .ai-local/ .devcontainer/.nextcloud-docker-development/ +.devcontainer/ncdd.generated.yml +.devcontainer/.worker-id .phpactor* .env .secrets From 3794e940f1b46764c414eec8eccfecfdc118b92b Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Sun, 4 Oct 2026 23:44:42 +0000 Subject: [PATCH 04/39] fix: forward web services in the devcontainer Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- .devcontainer/README.md | 6 ++++-- .devcontainer/devcontainer.json | 14 ++++++++++++++ 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/.devcontainer/README.md b/.devcontainer/README.md index 1aa96695c1..5a0e435421 100644 --- a/.devcontainer/README.md +++ b/.devcontainer/README.md @@ -16,8 +16,10 @@ worktree. LibreSign only adds its source mount at A deterministic worker id is derived from the local worktree path, so separate worktrees resolve to separate NCDD mutable-state directories. -The application URL is printed by `.devcontainer/setup.sh` after setup. -NCDD also exposes its Mailpit endpoint through the shared development proxy. +For local Docker use, NCDD exposes the environment through its shared HTTPS +proxy at the worker hostname. The Dev Container also forwards `nginx:80` and +`mailpit:8025`, so editor-managed local/remote environments can open +Nextcloud and Mailpit without publishing fixed host ports in Compose. The adapter defaults to PHP 8.3, Nextcloud `master`, MariaDB 10.6. These may be changed on the host before reopening the container: diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 7a1c8a2c6f..7cbcdf4899 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -6,6 +6,10 @@ "workspaceFolder": "/var/www/html/apps-extra/libresign", "overrideCommand": false, "shutdownAction": "stopCompose", + "forwardPorts": [ + "nginx:80", + "mailpit:8025" + ], "containerEnv": { "XDEBUG_MODE": "debug", "XDEBUG_CONFIG": "client_host=host.docker.internal client_port=9003 start_with_request=yes" @@ -40,6 +44,16 @@ }, "postCreateCommand": "bash /var/www/html/apps-extra/libresign/.devcontainer/setup.sh", "portsAttributes": { + "80": { + "label": "Nextcloud", + "protocol": "http", + "onAutoForward": "openBrowser" + }, + "8025": { + "label": "Mailpit", + "protocol": "http", + "onAutoForward": "notify" + }, "9000": { "label": "php-fpm", "onAutoForward": "ignore" From e23af07fc548abd1aca5f7a1c44378238d58f06e Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Sun, 4 Oct 2026 23:52:30 +0000 Subject: [PATCH 05/39] fix: keep Nextcloud access behind the NCDD proxy Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- .devcontainer/README.md | 17 +++++++++++++---- .devcontainer/devcontainer.json | 6 ------ .github/workflows/devcontainer.yml | 5 +++++ 3 files changed, 18 insertions(+), 10 deletions(-) diff --git a/.devcontainer/README.md b/.devcontainer/README.md index 5a0e435421..db33e50e43 100644 --- a/.devcontainer/README.md +++ b/.devcontainer/README.md @@ -16,10 +16,19 @@ worktree. LibreSign only adds its source mount at A deterministic worker id is derived from the local worktree path, so separate worktrees resolve to separate NCDD mutable-state directories. -For local Docker use, NCDD exposes the environment through its shared HTTPS -proxy at the worker hostname. The Dev Container also forwards `nginx:80` and -`mailpit:8025`, so editor-managed local/remote environments can open -Nextcloud and Mailpit without publishing fixed host ports in Compose. +Nextcloud is accessed through NCDD's shared HTTPS proxy using the canonical +worker hostname printed by the setup script, for example: + +```text +https://ncdev-libresign-123456.localhost +``` + +Do not forward `nginx:80` as the Nextcloud web endpoint. NCDD configures +Nextcloud's trusted domain, overwrite host and HTTPS protocol for the proxy +hostname, so bypassing the proxy would use a different public URL. + +Mailpit is safe to forward directly through the Dev Container and remains +available through NCDD's proxy as well. The adapter defaults to PHP 8.3, Nextcloud `master`, MariaDB 10.6. These may be changed on the host before reopening the container: diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 7cbcdf4899..15d4301af5 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -7,7 +7,6 @@ "overrideCommand": false, "shutdownAction": "stopCompose", "forwardPorts": [ - "nginx:80", "mailpit:8025" ], "containerEnv": { @@ -44,11 +43,6 @@ }, "postCreateCommand": "bash /var/www/html/apps-extra/libresign/.devcontainer/setup.sh", "portsAttributes": { - "80": { - "label": "Nextcloud", - "protocol": "http", - "onAutoForward": "openBrowser" - }, "8025": { "label": "Mailpit", "protocol": "http", diff --git a/.github/workflows/devcontainer.yml b/.github/workflows/devcontainer.yml index 19494abc4f..ddae83f3a3 100644 --- a/.github/workflows/devcontainer.yml +++ b/.github/workflows/devcontainer.yml @@ -32,3 +32,8 @@ jobs: run: | ! grep -Eq '^[[:space:]]+(database|mysql|mariadb|mailpit|redis):$' .devcontainer/docker-compose.yml ! grep -Eq '^[[:space:]]+image:' .devcontainer/docker-compose.yml + - name: Verify Nextcloud web access stays behind the NCDD proxy + run: | + ! grep -q '"nginx:80"' .devcontainer/devcontainer.json + ! grep -q '"80":' .devcontainer/devcontainer.json + grep -q 'https://.*\.localhost' .devcontainer/setup.sh From 278f9c3e7ac0e2b03357c037e7e6c7d8aa960f20 Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Sun, 4 Oct 2026 23:52:46 +0000 Subject: [PATCH 06/39] test: assert canonical NCDD proxy access Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- .github/workflows/devcontainer.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/devcontainer.yml b/.github/workflows/devcontainer.yml index ddae83f3a3..99f5d99b9b 100644 --- a/.github/workflows/devcontainer.yml +++ b/.github/workflows/devcontainer.yml @@ -36,4 +36,5 @@ jobs: run: | ! grep -q '"nginx:80"' .devcontainer/devcontainer.json ! grep -q '"80":' .devcontainer/devcontainer.json - grep -q 'https://.*\.localhost' .devcontainer/setup.sh + grep -q 'NEXTCLOUD_HOST' .devcontainer/setup.sh + grep -Eq 'NEXTCLOUD_HOST: ncdev-.*\.localhost' .devcontainer/ncdd.generated.yml From 9fc2fb246ab9fc21241f059b1213077ea1db57a2 Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Sun, 4 Oct 2026 23:58:18 +0000 Subject: [PATCH 07/39] fix: support browser access from Codespaces Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- .devcontainer/README.md | 32 ++++++++++++++++++++++-------- .devcontainer/devcontainer.json | 8 ++++---- .devcontainer/prepare.sh | 16 +++++++++++++-- .github/workflows/devcontainer.yml | 23 ++++++++++++++------- 4 files changed, 58 insertions(+), 21 deletions(-) diff --git a/.devcontainer/README.md b/.devcontainer/README.md index db33e50e43..54a9a6c9e0 100644 --- a/.devcontainer/README.md +++ b/.devcontainer/README.md @@ -16,19 +16,29 @@ worktree. LibreSign only adds its source mount at A deterministic worker id is derived from the local worktree path, so separate worktrees resolve to separate NCDD mutable-state directories. -Nextcloud is accessed through NCDD's shared HTTPS proxy using the canonical -worker hostname printed by the setup script, for example: +## Browser access + +For local Docker/VS Code development, use NCDD's shared HTTPS proxy. The setup +script prints the canonical URL: + +```text +https://ncdev-libresign-.localhost +``` + +For GitHub Codespaces, the adapter asks NCDD to publish this worker's nginx on +port `8080` and configures Nextcloud with the corresponding Codespaces public +hostname. GitHub then forwards that port to a URL like: ```text -https://ncdev-libresign-123456.localhost +https://-8080.app.github.dev ``` -Do not forward `nginx:80` as the Nextcloud web endpoint. NCDD configures -Nextcloud's trusted domain, overwrite host and HTTPS protocol for the proxy -hostname, so bypassing the proxy would use a different public URL. +The actual forwarding domain comes from +`GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN`; it is not hard-coded. -Mailpit is safe to forward directly through the Dev Container and remains -available through NCDD's proxy as well. +The local path still uses the shared NCDD proxy. The per-worker port exists +only in Codespaces, where a concrete forwarded port is needed for browser +access. The adapter defaults to PHP 8.3, Nextcloud `master`, MariaDB 10.6. These may be changed on the host before reopening the container: @@ -40,6 +50,12 @@ export LIBRESIGN_DB_TYPE=mariadb export LIBRESIGN_MARIADB_VERSION=10.6 ``` +Codespaces uses port `8080` by default. It can be changed before rebuilding: + +```bash +export LIBRESIGN_CODESPACES_PORT=18080 +``` + Closing or rebuilding this dev container only affects its Compose project. Do not use global Docker cleanup commands to stop unrelated containers or delete unrelated volumes. diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 15d4301af5..083733b870 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -7,7 +7,7 @@ "overrideCommand": false, "shutdownAction": "stopCompose", "forwardPorts": [ - "mailpit:8025" + 8080 ], "containerEnv": { "XDEBUG_MODE": "debug", @@ -43,10 +43,10 @@ }, "postCreateCommand": "bash /var/www/html/apps-extra/libresign/.devcontainer/setup.sh", "portsAttributes": { - "8025": { - "label": "Mailpit", + "8080": { + "label": "Nextcloud (Codespaces)", "protocol": "http", - "onAutoForward": "notify" + "onAutoForward": "openBrowser" }, "9000": { "label": "php-fpm", diff --git a/.devcontainer/prepare.sh b/.devcontainer/prepare.sh index 19fe5de0be..c1d2617815 100644 --- a/.devcontainer/prepare.sh +++ b/.devcontainer/prepare.sh @@ -8,7 +8,7 @@ repo_root="$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)" ncdd_dir="$repo_root/.devcontainer/.nextcloud-docker-development" generated_compose="$repo_root/.devcontainer/ncdd.generated.yml" worker_id_file="$repo_root/.devcontainer/.worker-id" -ncdd_commit="788437e228358b505b9fa90431f1d0aae1c08727" +ncdd_commit="ef366f2f7b5e3ce3bd34b90d6d5fe25f90dff35b" if [ ! -d "$ncdd_dir/.git" ]; then rm -rf "$ncdd_dir" @@ -22,4 +22,16 @@ workspace_key="$(printf '%s' "$repo_root" | cksum | awk '{print $1}')" worker_id="libresign-$workspace_key" printf '%s\n' "$worker_id" > "$worker_id_file" -DB_TYPE="${LIBRESIGN_DB_TYPE:-mariadb}" MARIADB_VERSION="${LIBRESIGN_MARIADB_VERSION:-10.6}" PHP_VERSION="${LIBRESIGN_PHP_VERSION:-83}" VERSION_NEXTCLOUD="${LIBRESIGN_NEXTCLOUD_VERSION:-master}" sh "$ncdd_dir/dev-worker" "$worker_id" config > "$generated_compose" +set -- env DB_TYPE="${LIBRESIGN_DB_TYPE:-mariadb}" MARIADB_VERSION="${LIBRESIGN_MARIADB_VERSION:-10.6}" PHP_VERSION="${LIBRESIGN_PHP_VERSION:-83}" VERSION_NEXTCLOUD="${LIBRESIGN_NEXTCLOUD_VERSION:-master}" + +if [ "${CODESPACES:-}" = "true" ]; then + : "${CODESPACE_NAME:?CODESPACE_NAME is required in Codespaces}" + : "${GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN:?GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN is required in Codespaces}" + + codespaces_port="${LIBRESIGN_CODESPACES_PORT:-8080}" + codespaces_host="${CODESPACE_NAME}-${codespaces_port}.${GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN}" + + set -- "$@" NEXTCLOUD_HOST="$codespaces_host" NEXTCLOUD_PROTOCOL=https NEXTCLOUD_PORT="$codespaces_port" +fi + +"$@" sh "$ncdd_dir/dev-worker" "$worker_id" config > "$generated_compose" diff --git a/.github/workflows/devcontainer.yml b/.github/workflows/devcontainer.yml index 99f5d99b9b..5b4d36e6da 100644 --- a/.github/workflows/devcontainer.yml +++ b/.github/workflows/devcontainer.yml @@ -26,15 +26,24 @@ jobs: bash -n .devcontainer/setup.sh - name: Prepare pinned NCDD run: sh .devcontainer/prepare.sh - - name: Validate composed environment + - name: Validate local composed environment run: docker compose --file .devcontainer/docker-compose.yml config --quiet + - name: Verify local access uses the shared NCDD proxy + run: | + ! grep -q 'published: "8080"' .devcontainer/ncdd.generated.yml + grep -Eq 'NEXTCLOUD_HOST: ncdev-.*\.localhost' .devcontainer/ncdd.generated.yml + - name: Validate Codespaces public URL contract + env: + CODESPACES: "true" + CODESPACE_NAME: libresign-test + GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN: app.github.dev + run: | + sh .devcontainer/prepare.sh + docker compose --file .devcontainer/docker-compose.yml config --quiet + grep -q 'NEXTCLOUD_HOST: libresign-test-8080.app.github.dev' .devcontainer/ncdd.generated.yml + grep -q 'NEXTCLOUD_PROTOCOL: https' .devcontainer/ncdd.generated.yml + grep -q 'published: "8080"' .devcontainer/ncdd.generated.yml - name: Verify LibreSign does not duplicate NCDD services run: | ! grep -Eq '^[[:space:]]+(database|mysql|mariadb|mailpit|redis):$' .devcontainer/docker-compose.yml ! grep -Eq '^[[:space:]]+image:' .devcontainer/docker-compose.yml - - name: Verify Nextcloud web access stays behind the NCDD proxy - run: | - ! grep -q '"nginx:80"' .devcontainer/devcontainer.json - ! grep -q '"80":' .devcontainer/devcontainer.json - grep -q 'NEXTCLOUD_HOST' .devcontainer/setup.sh - grep -Eq 'NEXTCLOUD_HOST: ncdev-.*\.localhost' .devcontainer/ncdd.generated.yml From 04215931364faa8be51255872edbc545a74b3c4d Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Sun, 4 Oct 2026 23:59:19 +0000 Subject: [PATCH 08/39] fix: layer the LibreSign devcontainer over NCDD Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- .devcontainer/devcontainer.json | 5 ++++- .devcontainer/docker-compose.yml | 3 --- .devcontainer/prepare.sh | 2 +- .github/workflows/devcontainer.yml | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 083733b870..ac43fdda46 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,7 +1,10 @@ { "name": "LibreSign", "initializeCommand": "sh .devcontainer/prepare.sh", - "dockerComposeFile": "docker-compose.yml", + "dockerComposeFile": [ + "ncdd.generated.yml", + "docker-compose.yml" + ], "service": "nextcloud", "workspaceFolder": "/var/www/html/apps-extra/libresign", "overrideCommand": false, diff --git a/.devcontainer/docker-compose.yml b/.devcontainer/docker-compose.yml index 89ac205646..282a892365 100644 --- a/.devcontainer/docker-compose.yml +++ b/.devcontainer/docker-compose.yml @@ -1,9 +1,6 @@ # SPDX-FileCopyrightText: 2026 LibreCode coop and contributors # SPDX-License-Identifier: AGPL-3.0-or-later -include: - - path: ./ncdd.generated.yml - services: nextcloud: volumes: diff --git a/.devcontainer/prepare.sh b/.devcontainer/prepare.sh index c1d2617815..1c2b9b17bc 100644 --- a/.devcontainer/prepare.sh +++ b/.devcontainer/prepare.sh @@ -8,7 +8,7 @@ repo_root="$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)" ncdd_dir="$repo_root/.devcontainer/.nextcloud-docker-development" generated_compose="$repo_root/.devcontainer/ncdd.generated.yml" worker_id_file="$repo_root/.devcontainer/.worker-id" -ncdd_commit="ef366f2f7b5e3ce3bd34b90d6d5fe25f90dff35b" +ncdd_commit="9389f0bbf5aa9dc0597d3c81b8f53d1abb946fbf" if [ ! -d "$ncdd_dir/.git" ]; then rm -rf "$ncdd_dir" diff --git a/.github/workflows/devcontainer.yml b/.github/workflows/devcontainer.yml index 5b4d36e6da..fa1c155c0d 100644 --- a/.github/workflows/devcontainer.yml +++ b/.github/workflows/devcontainer.yml @@ -27,7 +27,7 @@ jobs: - name: Prepare pinned NCDD run: sh .devcontainer/prepare.sh - name: Validate local composed environment - run: docker compose --file .devcontainer/docker-compose.yml config --quiet + run: docker compose --file .devcontainer/ncdd.generated.yml --file .devcontainer/docker-compose.yml config --quiet - name: Verify local access uses the shared NCDD proxy run: | ! grep -q 'published: "8080"' .devcontainer/ncdd.generated.yml @@ -39,7 +39,7 @@ jobs: GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN: app.github.dev run: | sh .devcontainer/prepare.sh - docker compose --file .devcontainer/docker-compose.yml config --quiet + docker compose --file .devcontainer/ncdd.generated.yml --file .devcontainer/docker-compose.yml config --quiet grep -q 'NEXTCLOUD_HOST: libresign-test-8080.app.github.dev' .devcontainer/ncdd.generated.yml grep -q 'NEXTCLOUD_PROTOCOL: https' .devcontainer/ncdd.generated.yml grep -q 'published: "8080"' .devcontainer/ncdd.generated.yml From e352e360c8de9fa2e3f2f75b0af280fa978515f2 Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Mon, 5 Oct 2026 00:11:51 +0000 Subject: [PATCH 09/39] refactor: route Codespaces through the NCDD proxy Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- .devcontainer/README.md | 21 +++++++-------------- .devcontainer/devcontainer.json | 8 ++++---- .devcontainer/prepare.sh | 2 +- .github/workflows/devcontainer.yml | 4 ++-- 4 files changed, 14 insertions(+), 21 deletions(-) diff --git a/.devcontainer/README.md b/.devcontainer/README.md index 54a9a6c9e0..ad59f7745c 100644 --- a/.devcontainer/README.md +++ b/.devcontainer/README.md @@ -25,20 +25,19 @@ script prints the canonical URL: https://ncdev-libresign-.localhost ``` -For GitHub Codespaces, the adapter asks NCDD to publish this worker's nginx on -port `8080` and configures Nextcloud with the corresponding Codespaces public -hostname. GitHub then forwards that port to a URL like: +For GitHub Codespaces, the same shared proxy remains the only HTTP entry point. +The Dev Container forwards the proxy's HTTPS port `443`, and the adapter +configures Nextcloud with the public hostname GitHub assigns to that forwarded +port: ```text -https://-8080.app.github.dev +https://-443. ``` -The actual forwarding domain comes from +The forwarding domain comes from `GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN`; it is not hard-coded. -The local path still uses the shared NCDD proxy. The per-worker port exists -only in Codespaces, where a concrete forwarded port is needed for browser -access. +Individual workers do not publish separate nginx host ports. The adapter defaults to PHP 8.3, Nextcloud `master`, MariaDB 10.6. These may be changed on the host before reopening the container: @@ -50,12 +49,6 @@ export LIBRESIGN_DB_TYPE=mariadb export LIBRESIGN_MARIADB_VERSION=10.6 ``` -Codespaces uses port `8080` by default. It can be changed before rebuilding: - -```bash -export LIBRESIGN_CODESPACES_PORT=18080 -``` - Closing or rebuilding this dev container only affects its Compose project. Do not use global Docker cleanup commands to stop unrelated containers or delete unrelated volumes. diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index ac43fdda46..b48489ebd1 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -10,7 +10,7 @@ "overrideCommand": false, "shutdownAction": "stopCompose", "forwardPorts": [ - 8080 + 443 ], "containerEnv": { "XDEBUG_MODE": "debug", @@ -46,9 +46,9 @@ }, "postCreateCommand": "bash /var/www/html/apps-extra/libresign/.devcontainer/setup.sh", "portsAttributes": { - "8080": { - "label": "Nextcloud (Codespaces)", - "protocol": "http", + "443": { + "label": "Nextcloud via NCDD proxy", + "protocol": "https", "onAutoForward": "openBrowser" }, "9000": { diff --git a/.devcontainer/prepare.sh b/.devcontainer/prepare.sh index 1c2b9b17bc..4eba5a86ac 100644 --- a/.devcontainer/prepare.sh +++ b/.devcontainer/prepare.sh @@ -8,7 +8,7 @@ repo_root="$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)" ncdd_dir="$repo_root/.devcontainer/.nextcloud-docker-development" generated_compose="$repo_root/.devcontainer/ncdd.generated.yml" worker_id_file="$repo_root/.devcontainer/.worker-id" -ncdd_commit="9389f0bbf5aa9dc0597d3c81b8f53d1abb946fbf" +ncdd_commit="9c3acb588226e22556fb0b9dce0b48c036bdcb8a" if [ ! -d "$ncdd_dir/.git" ]; then rm -rf "$ncdd_dir" diff --git a/.github/workflows/devcontainer.yml b/.github/workflows/devcontainer.yml index fa1c155c0d..412094a674 100644 --- a/.github/workflows/devcontainer.yml +++ b/.github/workflows/devcontainer.yml @@ -40,9 +40,9 @@ jobs: run: | sh .devcontainer/prepare.sh docker compose --file .devcontainer/ncdd.generated.yml --file .devcontainer/docker-compose.yml config --quiet - grep -q 'NEXTCLOUD_HOST: libresign-test-8080.app.github.dev' .devcontainer/ncdd.generated.yml + grep -q 'NEXTCLOUD_HOST: libresign-test-443.app.github.dev' .devcontainer/ncdd.generated.yml grep -q 'NEXTCLOUD_PROTOCOL: https' .devcontainer/ncdd.generated.yml - grep -q 'published: "8080"' .devcontainer/ncdd.generated.yml + ! grep -q 'published:' .devcontainer/ncdd.generated.yml - name: Verify LibreSign does not duplicate NCDD services run: | ! grep -Eq '^[[:space:]]+(database|mysql|mariadb|mailpit|redis):$' .devcontainer/docker-compose.yml From b308dfd200da307e616ea852b5ea26d9ac2f3423 Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Mon, 5 Oct 2026 00:17:50 +0000 Subject: [PATCH 10/39] test: exercise NCDD services from the devcontainer Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- .devcontainer/devcontainer.json | 8 ++- .devcontainer/prepare.sh | 10 ++- .github/workflows/devcontainer.yml | 99 +++++++++++++++++++++++++++--- 3 files changed, 102 insertions(+), 15 deletions(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index b48489ebd1..7379d5057e 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -10,7 +10,8 @@ "overrideCommand": false, "shutdownAction": "stopCompose", "forwardPorts": [ - 443 + 443, + "mailpit:8025" ], "containerEnv": { "XDEBUG_MODE": "debug", @@ -51,6 +52,11 @@ "protocol": "https", "onAutoForward": "openBrowser" }, + "8025": { + "label": "Mailpit", + "protocol": "http", + "onAutoForward": "notify" + }, "9000": { "label": "php-fpm", "onAutoForward": "ignore" diff --git a/.devcontainer/prepare.sh b/.devcontainer/prepare.sh index 4eba5a86ac..709f7ae6c8 100644 --- a/.devcontainer/prepare.sh +++ b/.devcontainer/prepare.sh @@ -8,7 +8,7 @@ repo_root="$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)" ncdd_dir="$repo_root/.devcontainer/.nextcloud-docker-development" generated_compose="$repo_root/.devcontainer/ncdd.generated.yml" worker_id_file="$repo_root/.devcontainer/.worker-id" -ncdd_commit="9c3acb588226e22556fb0b9dce0b48c036bdcb8a" +ncdd_commit="c74b45e8fc5273c973f39179e7c1c07946eeb793" if [ ! -d "$ncdd_dir/.git" ]; then rm -rf "$ncdd_dir" @@ -22,16 +22,14 @@ workspace_key="$(printf '%s' "$repo_root" | cksum | awk '{print $1}')" worker_id="libresign-$workspace_key" printf '%s\n' "$worker_id" > "$worker_id_file" -set -- env DB_TYPE="${LIBRESIGN_DB_TYPE:-mariadb}" MARIADB_VERSION="${LIBRESIGN_MARIADB_VERSION:-10.6}" PHP_VERSION="${LIBRESIGN_PHP_VERSION:-83}" VERSION_NEXTCLOUD="${LIBRESIGN_NEXTCLOUD_VERSION:-master}" +set -- env DB_TYPE="${LIBRESIGN_DB_TYPE:-mariadb}" MARIADB_VERSION="${LIBRESIGN_MARIADB_VERSION:-10.6}" PHP_VERSION="${LIBRESIGN_PHP_VERSION:-83}" VERSION_NEXTCLOUD="${LIBRESIGN_NEXTCLOUD_VERSION:-master}" COMPOSE_PROFILES=playwright if [ "${CODESPACES:-}" = "true" ]; then : "${CODESPACE_NAME:?CODESPACE_NAME is required in Codespaces}" : "${GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN:?GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN is required in Codespaces}" - codespaces_port="${LIBRESIGN_CODESPACES_PORT:-8080}" - codespaces_host="${CODESPACE_NAME}-${codespaces_port}.${GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN}" - - set -- "$@" NEXTCLOUD_HOST="$codespaces_host" NEXTCLOUD_PROTOCOL=https NEXTCLOUD_PORT="$codespaces_port" + codespaces_host="${CODESPACE_NAME}-443.${GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN}" + set -- "$@" NEXTCLOUD_HOST="$codespaces_host" NEXTCLOUD_PROTOCOL=https fi "$@" sh "$ncdd_dir/dev-worker" "$worker_id" config > "$generated_compose" diff --git a/.github/workflows/devcontainer.yml b/.github/workflows/devcontainer.yml index 412094a674..e196f0a78e 100644 --- a/.github/workflows/devcontainer.yml +++ b/.github/workflows/devcontainer.yml @@ -1,3 +1,6 @@ +# SPDX-FileCopyrightText: 2026 LibreCode coop and contributors +# SPDX-License-Identifier: AGPL-3.0-or-later + name: Devcontainer contract on: @@ -26,24 +29,104 @@ jobs: bash -n .devcontainer/setup.sh - name: Prepare pinned NCDD run: sh .devcontainer/prepare.sh - - name: Validate local composed environment - run: docker compose --file .devcontainer/ncdd.generated.yml --file .devcontainer/docker-compose.yml config --quiet - - name: Verify local access uses the shared NCDD proxy + - name: Validate composed environment + run: | + docker compose \ + --file .devcontainer/ncdd.generated.yml \ + --file .devcontainer/docker-compose.yml \ + config --quiet + - name: Verify required NCDD services are reusable + run: | + config="$(docker compose \ + --file .devcontainer/ncdd.generated.yml \ + --file .devcontainer/docker-compose.yml \ + config)" + printf '%s\n' "$config" | grep -q '^ nextcloud:' + printf '%s\n' "$config" | grep -q '^ nginx:' + printf '%s\n' "$config" | grep -q '^ mailpit:' + printf '%s\n' "$config" | grep -q '^ playwright:' + printf '%s\n' "$config" | grep -q '/var/www/html/apps-extra/libresign' + - name: Verify local access uses NCDD hostnames run: | - ! grep -q 'published: "8080"' .devcontainer/ncdd.generated.yml grep -Eq 'NEXTCLOUD_HOST: ncdev-.*\.localhost' .devcontainer/ncdd.generated.yml - - name: Validate Codespaces public URL contract + grep -Eq 'VIRTUAL_HOST: ncdev-.*-mailpit\.localhost' .devcontainer/ncdd.generated.yml + - name: Validate Codespaces public Nextcloud URL contract env: CODESPACES: "true" CODESPACE_NAME: libresign-test GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN: app.github.dev run: | sh .devcontainer/prepare.sh - docker compose --file .devcontainer/ncdd.generated.yml --file .devcontainer/docker-compose.yml config --quiet grep -q 'NEXTCLOUD_HOST: libresign-test-443.app.github.dev' .devcontainer/ncdd.generated.yml grep -q 'NEXTCLOUD_PROTOCOL: https' .devcontainer/ncdd.generated.yml - ! grep -q 'published:' .devcontainer/ncdd.generated.yml - - name: Verify LibreSign does not duplicate NCDD services + - name: Verify LibreSign does not duplicate NCDD infrastructure run: | ! grep -Eq '^[[:space:]]+(database|mysql|mariadb|mailpit|redis):$' .devcontainer/docker-compose.yml ! grep -Eq '^[[:space:]]+image:' .devcontainer/docker-compose.yml + + smoke: + name: NCDD devcontainer smoke + runs-on: ubuntu-latest + timeout-minutes: 20 + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 + - name: Prepare pinned NCDD + run: sh .devcontainer/prepare.sh + - name: Start LibreSign development environment + run: | + docker compose \ + --file .devcontainer/ncdd.generated.yml \ + --file .devcontainer/docker-compose.yml \ + --profile playwright up -d + - name: Wait for Nextcloud + run: | + for attempt in $(seq 1 120); do + if docker compose \ + --file .devcontainer/ncdd.generated.yml \ + --file .devcontainer/docker-compose.yml \ + exec -T nextcloud occ status 2>/dev/null | grep -q 'installed: true'; then + exit 0 + fi + sleep 2 + done + exit 1 + - name: Test Nextcloud + run: | + docker compose \ + --file .devcontainer/ncdd.generated.yml \ + --file .devcontainer/docker-compose.yml \ + exec -T nextcloud occ status | grep -q 'installed: true' + curl --fail --insecure --retry 20 --retry-all-errors \ + --resolve "$(grep -m1 'NEXTCLOUD_HOST:' .devcontainer/ncdd.generated.yml | awk '{print $2}'):443:127.0.0.1" \ + "https://$(grep -m1 'NEXTCLOUD_HOST:' .devcontainer/ncdd.generated.yml | awk '{print $2}')/status.php" + - name: Test Mailpit + run: | + mailpit_host="$(grep -m1 'VIRTUAL_HOST: .*mailpit' .devcontainer/ncdd.generated.yml | awk '{print $2}')" + curl --fail --insecure --retry 20 --retry-all-errors \ + --resolve "$mailpit_host:443:127.0.0.1" \ + "https://$mailpit_host/api/v1/info" + - name: Test Playwright runner + run: | + docker compose \ + --file .devcontainer/ncdd.generated.yml \ + --file .devcontainer/docker-compose.yml \ + --profile playwright exec -T playwright \ + node -e "require('http'); console.log(process.env.PLAYWRIGHT_BASE_URL)" + - name: Collect logs + if: failure() + run: | + docker compose \ + --file .devcontainer/ncdd.generated.yml \ + --file .devcontainer/docker-compose.yml \ + --profile playwright ps + docker compose \ + --file .devcontainer/ncdd.generated.yml \ + --file .devcontainer/docker-compose.yml \ + --profile playwright logs --no-color + - name: Stop environment + if: always() + run: | + docker compose \ + --file .devcontainer/ncdd.generated.yml \ + --file .devcontainer/docker-compose.yml \ + --profile playwright down --remove-orphans || true From 17ba5f4ab71f1f037272ada6676222008afa1375 Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Mon, 5 Oct 2026 00:18:40 +0000 Subject: [PATCH 11/39] test: inspect resolved devcontainer services reliably Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- .github/workflows/devcontainer.yml | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/.github/workflows/devcontainer.yml b/.github/workflows/devcontainer.yml index e196f0a78e..2d33a4f1bd 100644 --- a/.github/workflows/devcontainer.yml +++ b/.github/workflows/devcontainer.yml @@ -37,14 +37,19 @@ jobs: config --quiet - name: Verify required NCDD services are reusable run: | - config="$(docker compose \ + services="$(COMPOSE_PROFILES=playwright docker compose \ + --file .devcontainer/ncdd.generated.yml \ + --file .devcontainer/docker-compose.yml \ + config --services)" + printf '%s\n' "$services" | grep -qx nextcloud + printf '%s\n' "$services" | grep -qx nginx + printf '%s\n' "$services" | grep -qx mailpit + printf '%s\n' "$services" | grep -qx playwright + + config="$(COMPOSE_PROFILES=playwright docker compose \ --file .devcontainer/ncdd.generated.yml \ --file .devcontainer/docker-compose.yml \ config)" - printf '%s\n' "$config" | grep -q '^ nextcloud:' - printf '%s\n' "$config" | grep -q '^ nginx:' - printf '%s\n' "$config" | grep -q '^ mailpit:' - printf '%s\n' "$config" | grep -q '^ playwright:' printf '%s\n' "$config" | grep -q '/var/www/html/apps-extra/libresign' - name: Verify local access uses NCDD hostnames run: | From 5513221eb4bd5015f89dc939d024e099f7defebc Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Mon, 5 Oct 2026 00:53:30 +0000 Subject: [PATCH 12/39] refactor: align devcontainer with the current specification Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- .devcontainer/README.md | 13 ++- .devcontainer/ci-smoke.sh | 67 ++++++++++++++++ .devcontainer/devcontainer.json | 20 +++-- .devcontainer/prepare.sh | 54 ++++++++----- .devcontainer/setup.sh | 57 ++++++++------ .github/workflows/devcontainer.yml | 122 ++++------------------------- 6 files changed, 171 insertions(+), 162 deletions(-) create mode 100644 .devcontainer/ci-smoke.sh diff --git a/.devcontainer/README.md b/.devcontainer/README.md index ad59f7745c..32f764142f 100644 --- a/.devcontainer/README.md +++ b/.devcontainer/README.md @@ -26,9 +26,9 @@ https://ncdev-libresign-.localhost ``` For GitHub Codespaces, the same shared proxy remains the only HTTP entry point. -The Dev Container forwards the proxy's HTTPS port `443`, and the adapter -configures Nextcloud with the public hostname GitHub assigns to that forwarded -port: +The Dev Container forwards `host.docker.internal:443`, which is the Docker +host port owned by the shared NCDD proxy, and the adapter configures Nextcloud +with the public hostname GitHub assigns to forwarded port `443`: ```text https://-443. @@ -37,7 +37,12 @@ https://-443. The forwarding domain comes from `GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN`; it is not hard-coded. -Individual workers do not publish separate nginx host ports. +Individual workers do not publish separate nginx host ports. Mailpit is +forwarded directly from its Compose service on port `8025`. + +The Playwright service is optional and is not started as part of normal Dev +Container use. When enabled, NCDD routes its canonical Nextcloud hostname back +through the shared proxy so browser tests exercise the same public URL. The adapter defaults to PHP 8.3, Nextcloud `master`, MariaDB 10.6. These may be changed on the host before reopening the container: diff --git a/.devcontainer/ci-smoke.sh b/.devcontainer/ci-smoke.sh new file mode 100644 index 0000000000..f029b9e2d6 --- /dev/null +++ b/.devcontainer/ci-smoke.sh @@ -0,0 +1,67 @@ +#!/bin/bash +# SPDX-FileCopyrightText: 2026 LibreCode coop and contributors +# SPDX-License-Identifier: AGPL-3.0-or-later + +set -Eeo pipefail + +repo_root="$(cd -- "$(dirname -- "$0")/.." && pwd)" +cd "$repo_root" + +compose=( + docker compose + --file .devcontainer/ncdd.generated.yml + --file .devcontainer/docker-compose.yml + --profile playwright +) + +cleanup() { + "${compose[@]}" down --remove-orphans >/dev/null 2>&1 || true +} +trap cleanup EXIT + +sh .devcontainer/prepare.sh + +CODESPACES=true CODESPACE_NAME=libresign-test GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN=app.github.dev sh .devcontainer/prepare.sh + +grep -q 'NEXTCLOUD_HOST: libresign-test-443.app.github.dev' .devcontainer/ncdd.generated.yml + +sh .devcontainer/prepare.sh + +"${compose[@]}" config --quiet +"${compose[@]}" up -d + +for _ in {1..120}; do + if "${compose[@]}" exec -T nextcloud occ status 2>/dev/null | + grep -q 'installed: true'; then + break + fi + sleep 2 +done + +"${compose[@]}" exec -T nextcloud occ status | + grep -q 'installed: true' + +nextcloud_host="$("${compose[@]}" config | + awk '/NEXTCLOUD_HOST:/ { print $2; exit }')" +mailpit_host="$("${compose[@]}" config | + awk '/VIRTUAL_HOST: .*mailpit/ { print $2; exit }')" + +curl --fail --insecure --retry 20 --retry-all-errors --resolve "$nextcloud_host:443:127.0.0.1" "https://$nextcloud_host/status.php" >/dev/null + +curl --fail --insecure --retry 20 --retry-all-errors --resolve "$mailpit_host:443:127.0.0.1" "https://$mailpit_host/api/v1/info" >/dev/null + +"${compose[@]}" exec -T playwright node -e ' + const https = require("https"); + const target = new URL(process.env.PLAYWRIGHT_BASE_URL + "/status.php"); + const request = https.get(target, { rejectUnauthorized: false }, response => { + if (response.statusCode !== 200) { + console.error("Unexpected status:", response.statusCode); + process.exitCode = 1; + } + response.resume(); + }); + request.on("error", error => { + console.error(error); + process.exitCode = 1; + }); +' diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 7379d5057e..4ccede5c69 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,16 +1,17 @@ { "name": "LibreSign", - "initializeCommand": "sh .devcontainer/prepare.sh", + "initializeCommand": [ + "sh", + ".devcontainer/prepare.sh" + ], "dockerComposeFile": [ "ncdd.generated.yml", "docker-compose.yml" ], "service": "nextcloud", "workspaceFolder": "/var/www/html/apps-extra/libresign", - "overrideCommand": false, - "shutdownAction": "stopCompose", "forwardPorts": [ - 443, + "host.docker.internal:443", "mailpit:8025" ], "containerEnv": { @@ -45,14 +46,17 @@ } } }, - "postCreateCommand": "bash /var/www/html/apps-extra/libresign/.devcontainer/setup.sh", + "postCreateCommand": [ + "bash", + "/var/www/html/apps-extra/libresign/.devcontainer/setup.sh" + ], "portsAttributes": { - "443": { + "host.docker.internal:443": { "label": "Nextcloud via NCDD proxy", "protocol": "https", - "onAutoForward": "openBrowser" + "onAutoForward": "silent" }, - "8025": { + "mailpit:8025": { "label": "Mailpit", "protocol": "http", "onAutoForward": "notify" diff --git a/.devcontainer/prepare.sh b/.devcontainer/prepare.sh index 709f7ae6c8..7d7f27eaea 100644 --- a/.devcontainer/prepare.sh +++ b/.devcontainer/prepare.sh @@ -8,28 +8,46 @@ repo_root="$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)" ncdd_dir="$repo_root/.devcontainer/.nextcloud-docker-development" generated_compose="$repo_root/.devcontainer/ncdd.generated.yml" worker_id_file="$repo_root/.devcontainer/.worker-id" -ncdd_commit="c74b45e8fc5273c973f39179e7c1c07946eeb793" +ncdd_commit="627955734907d8210a84acdb153c732c7eb11131" -if [ ! -d "$ncdd_dir/.git" ]; then - rm -rf "$ncdd_dir" - git clone --filter=blob:none --no-checkout https://github.com/LibreCodeCoop/nextcloud-docker-development.git "$ncdd_dir" -fi +prepare_ncdd() { + if [ ! -d "$ncdd_dir/.git" ]; then + rm -rf "$ncdd_dir" + git clone --filter=blob:none --no-checkout https://github.com/LibreCodeCoop/nextcloud-docker-development.git "$ncdd_dir" + fi -git -C "$ncdd_dir" fetch --depth=1 origin "$ncdd_commit" -git -C "$ncdd_dir" checkout --detach "$ncdd_commit" + if ! git -C "$ncdd_dir" cat-file -e "$ncdd_commit^{commit}" 2>/dev/null; then + git -C "$ncdd_dir" fetch --depth=1 origin "$ncdd_commit" + fi -workspace_key="$(printf '%s' "$repo_root" | cksum | awk '{print $1}')" -worker_id="libresign-$workspace_key" -printf '%s\n' "$worker_id" > "$worker_id_file" + git -C "$ncdd_dir" checkout --detach "$ncdd_commit" +} -set -- env DB_TYPE="${LIBRESIGN_DB_TYPE:-mariadb}" MARIADB_VERSION="${LIBRESIGN_MARIADB_VERSION:-10.6}" PHP_VERSION="${LIBRESIGN_PHP_VERSION:-83}" VERSION_NEXTCLOUD="${LIBRESIGN_NEXTCLOUD_VERSION:-master}" COMPOSE_PROFILES=playwright +worker_id() { + workspace_key="$(printf '%s' "$repo_root" | cksum | awk '{print $1}')" + printf 'libresign-%s\n' "$workspace_key" +} -if [ "${CODESPACES:-}" = "true" ]; then - : "${CODESPACE_NAME:?CODESPACE_NAME is required in Codespaces}" - : "${GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN:?GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN is required in Codespaces}" +render_compose() { + id="$1" + tmp="$generated_compose.tmp.$$" + trap 'rm -f "$tmp"' EXIT HUP INT TERM - codespaces_host="${CODESPACE_NAME}-443.${GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN}" - set -- "$@" NEXTCLOUD_HOST="$codespaces_host" NEXTCLOUD_PROTOCOL=https -fi + set -- env DB_TYPE="${LIBRESIGN_DB_TYPE:-mariadb}" MARIADB_VERSION="${LIBRESIGN_MARIADB_VERSION:-10.6}" PHP_VERSION="${LIBRESIGN_PHP_VERSION:-83}" VERSION_NEXTCLOUD="${LIBRESIGN_NEXTCLOUD_VERSION:-master}" COMPOSE_PROFILES=playwright -"$@" sh "$ncdd_dir/dev-worker" "$worker_id" config > "$generated_compose" + if [ "${CODESPACES:-}" = "true" ]; then + : "${CODESPACE_NAME:?CODESPACE_NAME is required in Codespaces}" + : "${GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN:?GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN is required in Codespaces}" + + set -- "$@" NEXTCLOUD_HOST="${CODESPACE_NAME}-443.${GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN}" NEXTCLOUD_PROTOCOL=https + fi + + "$@" sh "$ncdd_dir/dev-worker" "$id" config > "$tmp" + mv "$tmp" "$generated_compose" + trap - EXIT HUP INT TERM +} + +prepare_ncdd +id="$(worker_id)" +printf '%s\n' "$id" > "$worker_id_file" +render_compose "$id" diff --git a/.devcontainer/setup.sh b/.devcontainer/setup.sh index b24a021bdf..b73cc061c6 100644 --- a/.devcontainer/setup.sh +++ b/.devcontainer/setup.sh @@ -6,27 +6,34 @@ set -Eeo pipefail app_dir=/var/www/html/apps-extra/libresign -for _ in {1..120}; do - if occ status 2>/dev/null | grep -q 'installed: true'; then - break - fi - sleep 1 -done +wait_for_nextcloud() { + for _ in {1..120}; do + if occ status 2>/dev/null | grep -q 'installed: true'; then + return 0 + fi + sleep 1 + done -if ! occ status 2>/dev/null | grep -q 'installed: true'; then echo "Nextcloud did not become ready within 120 seconds." >&2 - exit 1 -fi + return 1 +} + +add_safe_directory() { + local path="$1" + if ! git config --global --get-all safe.directory | grep -Fxq "$path"; then + git config --global --add safe.directory "$path" + fi +} -git config --global --add safe.directory /var/www/html -git config --global --add safe.directory "$app_dir" +wait_for_nextcloud +add_safe_directory /var/www/html +add_safe_directory "$app_dir" cd "$app_dir" git submodule update --init --recursive -if [[ ! -d vendor ]]; then - composer install -fi +composer install --no-interaction +npm ci occ app:enable libresign occ libresign:install --use-local-cert --java @@ -34,16 +41,14 @@ occ libresign:install --use-local-cert --pdftk occ libresign:install --use-local-cert --jsignpdf occ libresign:configure:openssl --cn=CommonName --c=BR --ou=OrganizationUnit --st=RioDeJaneiro --o=LibreSign --l=RioDeJaneiro -if [[ ! -d node_modules ]]; then - occ theming:config name "LibreSign" - occ theming:config url "https://libresign.coop" - occ theming:config primary_color "#144042" - occ config:app:set libresign extra_settings --value=1 - occ config:system:set defaultapp --value libresign - occ maintenance:theme:update - npm ci - npm run dev -fi - -echo "LibreSign is ready at https://${NEXTCLOUD_HOST}" +occ theming:config name "LibreSign" +occ theming:config url "https://libresign.coop" +occ theming:config primary_color "#144042" +occ config:app:set libresign extra_settings --value=1 +occ config:system:set defaultapp --value libresign +occ maintenance:theme:update + +npm run dev + +echo "LibreSign is ready at ${NEXTCLOUD_PROTOCOL:-https}://${NEXTCLOUD_HOST}" echo "For frontend development, run: npm run watch" diff --git a/.github/workflows/devcontainer.yml b/.github/workflows/devcontainer.yml index 2d33a4f1bd..c1cce56c9d 100644 --- a/.github/workflows/devcontainer.yml +++ b/.github/workflows/devcontainer.yml @@ -1,7 +1,7 @@ # SPDX-FileCopyrightText: 2026 LibreCode coop and contributors # SPDX-License-Identifier: AGPL-3.0-or-later -name: Devcontainer contract +name: Devcontainer on: pull_request: @@ -19,119 +19,29 @@ permissions: contents: read jobs: - validate: + devcontainer: runs-on: ubuntu-latest + timeout-minutes: 25 steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 - - name: Validate adapter scripts + - name: Validate shell scripts run: | sh -n .devcontainer/prepare.sh bash -n .devcontainer/setup.sh - - name: Prepare pinned NCDD - run: sh .devcontainer/prepare.sh - - name: Validate composed environment - run: | - docker compose \ - --file .devcontainer/ncdd.generated.yml \ - --file .devcontainer/docker-compose.yml \ - config --quiet - - name: Verify required NCDD services are reusable - run: | - services="$(COMPOSE_PROFILES=playwright docker compose \ - --file .devcontainer/ncdd.generated.yml \ - --file .devcontainer/docker-compose.yml \ - config --services)" - printf '%s\n' "$services" | grep -qx nextcloud - printf '%s\n' "$services" | grep -qx nginx - printf '%s\n' "$services" | grep -qx mailpit - printf '%s\n' "$services" | grep -qx playwright + bash -n .devcontainer/ci-smoke.sh + - name: Exercise the Dev Container lifecycle + uses: devcontainers/ci@513af61f4de4f75d37e4438f184ba4358f0fc1ca # v0.3 + with: + push: never + runCmd: | + occ status | grep -q 'installed: true' + occ app:list --enabled | grep -q libresign + curl --fail --silent http://mailpit:8025/api/v1/info >/dev/null - config="$(COMPOSE_PROFILES=playwright docker compose \ - --file .devcontainer/ncdd.generated.yml \ - --file .devcontainer/docker-compose.yml \ - config)" - printf '%s\n' "$config" | grep -q '/var/www/html/apps-extra/libresign' - - name: Verify local access uses NCDD hostnames - run: | - grep -Eq 'NEXTCLOUD_HOST: ncdev-.*\.localhost' .devcontainer/ncdd.generated.yml - grep -Eq 'VIRTUAL_HOST: ncdev-.*-mailpit\.localhost' .devcontainer/ncdd.generated.yml - - name: Validate Codespaces public Nextcloud URL contract - env: - CODESPACES: "true" - CODESPACE_NAME: libresign-test - GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN: app.github.dev - run: | - sh .devcontainer/prepare.sh - grep -q 'NEXTCLOUD_HOST: libresign-test-443.app.github.dev' .devcontainer/ncdd.generated.yml - grep -q 'NEXTCLOUD_PROTOCOL: https' .devcontainer/ncdd.generated.yml - - name: Verify LibreSign does not duplicate NCDD infrastructure - run: | - ! grep -Eq '^[[:space:]]+(database|mysql|mariadb|mailpit|redis):$' .devcontainer/docker-compose.yml - ! grep -Eq '^[[:space:]]+image:' .devcontainer/docker-compose.yml - - smoke: - name: NCDD devcontainer smoke + services: + name: NCDD service smoke runs-on: ubuntu-latest timeout-minutes: 20 steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 - - name: Prepare pinned NCDD - run: sh .devcontainer/prepare.sh - - name: Start LibreSign development environment - run: | - docker compose \ - --file .devcontainer/ncdd.generated.yml \ - --file .devcontainer/docker-compose.yml \ - --profile playwright up -d - - name: Wait for Nextcloud - run: | - for attempt in $(seq 1 120); do - if docker compose \ - --file .devcontainer/ncdd.generated.yml \ - --file .devcontainer/docker-compose.yml \ - exec -T nextcloud occ status 2>/dev/null | grep -q 'installed: true'; then - exit 0 - fi - sleep 2 - done - exit 1 - - name: Test Nextcloud - run: | - docker compose \ - --file .devcontainer/ncdd.generated.yml \ - --file .devcontainer/docker-compose.yml \ - exec -T nextcloud occ status | grep -q 'installed: true' - curl --fail --insecure --retry 20 --retry-all-errors \ - --resolve "$(grep -m1 'NEXTCLOUD_HOST:' .devcontainer/ncdd.generated.yml | awk '{print $2}'):443:127.0.0.1" \ - "https://$(grep -m1 'NEXTCLOUD_HOST:' .devcontainer/ncdd.generated.yml | awk '{print $2}')/status.php" - - name: Test Mailpit - run: | - mailpit_host="$(grep -m1 'VIRTUAL_HOST: .*mailpit' .devcontainer/ncdd.generated.yml | awk '{print $2}')" - curl --fail --insecure --retry 20 --retry-all-errors \ - --resolve "$mailpit_host:443:127.0.0.1" \ - "https://$mailpit_host/api/v1/info" - - name: Test Playwright runner - run: | - docker compose \ - --file .devcontainer/ncdd.generated.yml \ - --file .devcontainer/docker-compose.yml \ - --profile playwright exec -T playwright \ - node -e "require('http'); console.log(process.env.PLAYWRIGHT_BASE_URL)" - - name: Collect logs - if: failure() - run: | - docker compose \ - --file .devcontainer/ncdd.generated.yml \ - --file .devcontainer/docker-compose.yml \ - --profile playwright ps - docker compose \ - --file .devcontainer/ncdd.generated.yml \ - --file .devcontainer/docker-compose.yml \ - --profile playwright logs --no-color - - name: Stop environment - if: always() - run: | - docker compose \ - --file .devcontainer/ncdd.generated.yml \ - --file .devcontainer/docker-compose.yml \ - --profile playwright down --remove-orphans || true + - run: bash .devcontainer/ci-smoke.sh From 0eae00c31afbc129b8cd9624bc51601b3335be37 Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Mon, 5 Oct 2026 00:55:48 +0000 Subject: [PATCH 13/39] test: exercise the Dev Container with the CLI up flow Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- .github/workflows/devcontainer.yml | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/.github/workflows/devcontainer.yml b/.github/workflows/devcontainer.yml index c1cce56c9d..64367d7c9e 100644 --- a/.github/workflows/devcontainer.yml +++ b/.github/workflows/devcontainer.yml @@ -29,14 +29,12 @@ jobs: sh -n .devcontainer/prepare.sh bash -n .devcontainer/setup.sh bash -n .devcontainer/ci-smoke.sh - - name: Exercise the Dev Container lifecycle - uses: devcontainers/ci@513af61f4de4f75d37e4438f184ba4358f0fc1ca # v0.3 - with: - push: never - runCmd: | - occ status | grep -q 'installed: true' - occ app:list --enabled | grep -q libresign - curl --fail --silent http://mailpit:8025/api/v1/info >/dev/null + - name: Start the Dev Container + run: npx --yes @devcontainers/cli@0.89.0 up --workspace-folder . + - name: Verify the created environment + run: | + npx --yes @devcontainers/cli@0.89.0 exec --workspace-folder . \ + bash -lc "occ status | grep -q 'installed: true' && occ app:list --enabled | grep -q libresign && curl --fail --silent http://mailpit:8025/api/v1/info >/dev/null" services: name: NCDD service smoke From 60fd479838a18f27ce872656c133807e8698f7c0 Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Mon, 5 Oct 2026 01:03:47 +0000 Subject: [PATCH 14/39] fix: preserve host ownership in the devcontainer Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- .devcontainer/ci-smoke.sh | 3 +++ .devcontainer/prepare.sh | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.devcontainer/ci-smoke.sh b/.devcontainer/ci-smoke.sh index f029b9e2d6..8229f479a7 100644 --- a/.devcontainer/ci-smoke.sh +++ b/.devcontainer/ci-smoke.sh @@ -21,6 +21,9 @@ trap cleanup EXIT sh .devcontainer/prepare.sh +grep -q "HOST_UID: $(id -u)" .devcontainer/ncdd.generated.yml +grep -q "HOST_GID: $(id -g)" .devcontainer/ncdd.generated.yml + CODESPACES=true CODESPACE_NAME=libresign-test GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN=app.github.dev sh .devcontainer/prepare.sh grep -q 'NEXTCLOUD_HOST: libresign-test-443.app.github.dev' .devcontainer/ncdd.generated.yml diff --git a/.devcontainer/prepare.sh b/.devcontainer/prepare.sh index 7d7f27eaea..7fa9f642c4 100644 --- a/.devcontainer/prepare.sh +++ b/.devcontainer/prepare.sh @@ -8,7 +8,7 @@ repo_root="$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)" ncdd_dir="$repo_root/.devcontainer/.nextcloud-docker-development" generated_compose="$repo_root/.devcontainer/ncdd.generated.yml" worker_id_file="$repo_root/.devcontainer/.worker-id" -ncdd_commit="627955734907d8210a84acdb153c732c7eb11131" +ncdd_commit="44a5d63669391769ff24ffd208b86c79e04746e9" prepare_ncdd() { if [ ! -d "$ncdd_dir/.git" ]; then From 25ed312e0f846c23b1193ff34660389f749c5310 Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Mon, 5 Oct 2026 01:42:26 +0000 Subject: [PATCH 15/39] test: simplify devcontainer coverage Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- .devcontainer/ci-smoke.sh | 70 ------------------------------ .github/workflows/devcontainer.yml | 51 +++++++++++++++++----- 2 files changed, 40 insertions(+), 81 deletions(-) delete mode 100644 .devcontainer/ci-smoke.sh diff --git a/.devcontainer/ci-smoke.sh b/.devcontainer/ci-smoke.sh deleted file mode 100644 index 8229f479a7..0000000000 --- a/.devcontainer/ci-smoke.sh +++ /dev/null @@ -1,70 +0,0 @@ -#!/bin/bash -# SPDX-FileCopyrightText: 2026 LibreCode coop and contributors -# SPDX-License-Identifier: AGPL-3.0-or-later - -set -Eeo pipefail - -repo_root="$(cd -- "$(dirname -- "$0")/.." && pwd)" -cd "$repo_root" - -compose=( - docker compose - --file .devcontainer/ncdd.generated.yml - --file .devcontainer/docker-compose.yml - --profile playwright -) - -cleanup() { - "${compose[@]}" down --remove-orphans >/dev/null 2>&1 || true -} -trap cleanup EXIT - -sh .devcontainer/prepare.sh - -grep -q "HOST_UID: $(id -u)" .devcontainer/ncdd.generated.yml -grep -q "HOST_GID: $(id -g)" .devcontainer/ncdd.generated.yml - -CODESPACES=true CODESPACE_NAME=libresign-test GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN=app.github.dev sh .devcontainer/prepare.sh - -grep -q 'NEXTCLOUD_HOST: libresign-test-443.app.github.dev' .devcontainer/ncdd.generated.yml - -sh .devcontainer/prepare.sh - -"${compose[@]}" config --quiet -"${compose[@]}" up -d - -for _ in {1..120}; do - if "${compose[@]}" exec -T nextcloud occ status 2>/dev/null | - grep -q 'installed: true'; then - break - fi - sleep 2 -done - -"${compose[@]}" exec -T nextcloud occ status | - grep -q 'installed: true' - -nextcloud_host="$("${compose[@]}" config | - awk '/NEXTCLOUD_HOST:/ { print $2; exit }')" -mailpit_host="$("${compose[@]}" config | - awk '/VIRTUAL_HOST: .*mailpit/ { print $2; exit }')" - -curl --fail --insecure --retry 20 --retry-all-errors --resolve "$nextcloud_host:443:127.0.0.1" "https://$nextcloud_host/status.php" >/dev/null - -curl --fail --insecure --retry 20 --retry-all-errors --resolve "$mailpit_host:443:127.0.0.1" "https://$mailpit_host/api/v1/info" >/dev/null - -"${compose[@]}" exec -T playwright node -e ' - const https = require("https"); - const target = new URL(process.env.PLAYWRIGHT_BASE_URL + "/status.php"); - const request = https.get(target, { rejectUnauthorized: false }, response => { - if (response.statusCode !== 200) { - console.error("Unexpected status:", response.statusCode); - process.exitCode = 1; - } - response.resume(); - }); - request.on("error", error => { - console.error(error); - process.exitCode = 1; - }); -' diff --git a/.github/workflows/devcontainer.yml b/.github/workflows/devcontainer.yml index 64367d7c9e..6630c6544b 100644 --- a/.github/workflows/devcontainer.yml +++ b/.github/workflows/devcontainer.yml @@ -24,22 +24,51 @@ jobs: timeout-minutes: 25 steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 - - name: Validate shell scripts + + - name: Check shell scripts run: | + shellcheck .devcontainer/prepare.sh .devcontainer/setup.sh sh -n .devcontainer/prepare.sh bash -n .devcontainer/setup.sh - bash -n .devcontainer/ci-smoke.sh + + - name: Verify local NCDD contract + run: | + sh .devcontainer/prepare.sh + docker compose --file .devcontainer/ncdd.generated.yml --file .devcontainer/docker-compose.yml --profile playwright config --quiet + grep -q "HOST_UID: $(id -u)" .devcontainer/ncdd.generated.yml + grep -q "HOST_GID: $(id -g)" .devcontainer/ncdd.generated.yml + grep -Eq 'NEXTCLOUD_HOST: ncdev-.*\.localhost' .devcontainer/ncdd.generated.yml + + - name: Verify Codespaces NCDD contract + env: + CODESPACES: "true" + CODESPACE_NAME: libresign-test + GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN: app.github.dev + run: | + sh .devcontainer/prepare.sh + grep -q 'NEXTCLOUD_HOST: libresign-test-443.app.github.dev' .devcontainer/ncdd.generated.yml + grep -q 'NEXTCLOUD_PROTOCOL: https' .devcontainer/ncdd.generated.yml + + - name: Restore local configuration + run: sh .devcontainer/prepare.sh + - name: Start the Dev Container run: npx --yes @devcontainers/cli@0.89.0 up --workspace-folder . + - name: Verify the created environment run: | - npx --yes @devcontainers/cli@0.89.0 exec --workspace-folder . \ - bash -lc "occ status | grep -q 'installed: true' && occ app:list --enabled | grep -q libresign && curl --fail --silent http://mailpit:8025/api/v1/info >/dev/null" + npx --yes @devcontainers/cli@0.89.0 exec --workspace-folder . bash -lc "occ status | grep -q 'installed: true'" + npx --yes @devcontainers/cli@0.89.0 exec --workspace-folder . bash -lc "occ app:list --enabled | grep -q libresign" + npx --yes @devcontainers/cli@0.89.0 exec --workspace-folder . bash -lc "curl --fail --silent http://mailpit:8025/api/v1/info >/dev/null" - services: - name: NCDD service smoke - runs-on: ubuntu-latest - timeout-minutes: 20 - steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 - - run: bash .devcontainer/ci-smoke.sh + - name: Verify Playwright reaches Nextcloud through the proxy + run: | + docker compose --file .devcontainer/ncdd.generated.yml --file .devcontainer/docker-compose.yml --profile playwright exec -T playwright node -e ' + const https = require("https"); + const target = new URL(process.env.PLAYWRIGHT_BASE_URL + "/status.php"); + const request = https.get(target, { rejectUnauthorized: false }, response => { + if (response.statusCode !== 200) process.exitCode = 1; + response.resume(); + }); + request.on("error", () => { process.exitCode = 1; }); + ' From 3143e73ea09d1b08082527cbc55836a8d1463e49 Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Mon, 5 Oct 2026 01:42:42 +0000 Subject: [PATCH 16/39] refactor: remove unused devcontainer worker state Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- .devcontainer/prepare.sh | 2 -- .gitignore | 1 - 2 files changed, 3 deletions(-) diff --git a/.devcontainer/prepare.sh b/.devcontainer/prepare.sh index 7fa9f642c4..c2199c8873 100644 --- a/.devcontainer/prepare.sh +++ b/.devcontainer/prepare.sh @@ -7,7 +7,6 @@ set -eu repo_root="$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)" ncdd_dir="$repo_root/.devcontainer/.nextcloud-docker-development" generated_compose="$repo_root/.devcontainer/ncdd.generated.yml" -worker_id_file="$repo_root/.devcontainer/.worker-id" ncdd_commit="44a5d63669391769ff24ffd208b86c79e04746e9" prepare_ncdd() { @@ -49,5 +48,4 @@ render_compose() { prepare_ncdd id="$(worker_id)" -printf '%s\n' "$id" > "$worker_id_file" render_compose "$id" diff --git a/.gitignore b/.gitignore index 9d4460854f..b74af9291c 100644 --- a/.gitignore +++ b/.gitignore @@ -5,7 +5,6 @@ .ai-local/ .devcontainer/.nextcloud-docker-development/ .devcontainer/ncdd.generated.yml -.devcontainer/.worker-id .phpactor* .env .secrets From 2452e208917742de9700f4d46b5c18e48fdb96fc Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Mon, 5 Oct 2026 01:43:48 +0000 Subject: [PATCH 17/39] refactor: finalize the NCDD devcontainer adapter Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- .devcontainer/prepare.sh | 24 ++++++++++++++++++------ .devcontainer/setup.sh | 9 ++++++++- .github/workflows/devcontainer.yml | 23 +++++++++++++++++------ 3 files changed, 43 insertions(+), 13 deletions(-) diff --git a/.devcontainer/prepare.sh b/.devcontainer/prepare.sh index c2199c8873..0d1b8f8ace 100644 --- a/.devcontainer/prepare.sh +++ b/.devcontainer/prepare.sh @@ -7,12 +7,16 @@ set -eu repo_root="$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)" ncdd_dir="$repo_root/.devcontainer/.nextcloud-docker-development" generated_compose="$repo_root/.devcontainer/ncdd.generated.yml" -ncdd_commit="44a5d63669391769ff24ffd208b86c79e04746e9" +ncdd_commit="b33c4dee93a4d41ce856e7480c871cf1171b3cbb" prepare_ncdd() { if [ ! -d "$ncdd_dir/.git" ]; then rm -rf "$ncdd_dir" - git clone --filter=blob:none --no-checkout https://github.com/LibreCodeCoop/nextcloud-docker-development.git "$ncdd_dir" + git clone \ + --filter=blob:none \ + --no-checkout \ + https://github.com/LibreCodeCoop/nextcloud-docker-development.git \ + "$ncdd_dir" fi if ! git -C "$ncdd_dir" cat-file -e "$ncdd_commit^{commit}" 2>/dev/null; then @@ -32,13 +36,22 @@ render_compose() { tmp="$generated_compose.tmp.$$" trap 'rm -f "$tmp"' EXIT HUP INT TERM - set -- env DB_TYPE="${LIBRESIGN_DB_TYPE:-mariadb}" MARIADB_VERSION="${LIBRESIGN_MARIADB_VERSION:-10.6}" PHP_VERSION="${LIBRESIGN_PHP_VERSION:-83}" VERSION_NEXTCLOUD="${LIBRESIGN_NEXTCLOUD_VERSION:-master}" COMPOSE_PROFILES=playwright + set -- env \ + HOST_UID="$(id -u)" \ + HOST_GID="$(id -g)" \ + DB_TYPE="${LIBRESIGN_DB_TYPE:-mariadb}" \ + MARIADB_VERSION="${LIBRESIGN_MARIADB_VERSION:-10.6}" \ + PHP_VERSION="${LIBRESIGN_PHP_VERSION:-83}" \ + VERSION_NEXTCLOUD="${LIBRESIGN_NEXTCLOUD_VERSION:-master}" \ + COMPOSE_PROFILES=playwright if [ "${CODESPACES:-}" = "true" ]; then : "${CODESPACE_NAME:?CODESPACE_NAME is required in Codespaces}" : "${GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN:?GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN is required in Codespaces}" - set -- "$@" NEXTCLOUD_HOST="${CODESPACE_NAME}-443.${GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN}" NEXTCLOUD_PROTOCOL=https + set -- "$@" \ + NEXTCLOUD_HOST="${CODESPACE_NAME}-443.${GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN}" \ + NEXTCLOUD_PROTOCOL=https fi "$@" sh "$ncdd_dir/dev-worker" "$id" config > "$tmp" @@ -47,5 +60,4 @@ render_compose() { } prepare_ncdd -id="$(worker_id)" -render_compose "$id" +render_compose "$(worker_id)" diff --git a/.devcontainer/setup.sh b/.devcontainer/setup.sh index b73cc061c6..7f659f93f7 100644 --- a/.devcontainer/setup.sh +++ b/.devcontainer/setup.sh @@ -20,6 +20,7 @@ wait_for_nextcloud() { add_safe_directory() { local path="$1" + if ! git config --global --get-all safe.directory | grep -Fxq "$path"; then git config --global --add safe.directory "$path" fi @@ -39,7 +40,13 @@ occ app:enable libresign occ libresign:install --use-local-cert --java occ libresign:install --use-local-cert --pdftk occ libresign:install --use-local-cert --jsignpdf -occ libresign:configure:openssl --cn=CommonName --c=BR --ou=OrganizationUnit --st=RioDeJaneiro --o=LibreSign --l=RioDeJaneiro +occ libresign:configure:openssl \ + --cn=CommonName \ + --c=BR \ + --ou=OrganizationUnit \ + --st=RioDeJaneiro \ + --o=LibreSign \ + --l=RioDeJaneiro occ theming:config name "LibreSign" occ theming:config url "https://libresign.coop" diff --git a/.github/workflows/devcontainer.yml b/.github/workflows/devcontainer.yml index 6630c6544b..28c9cdd1e0 100644 --- a/.github/workflows/devcontainer.yml +++ b/.github/workflows/devcontainer.yml @@ -34,10 +34,14 @@ jobs: - name: Verify local NCDD contract run: | sh .devcontainer/prepare.sh - docker compose --file .devcontainer/ncdd.generated.yml --file .devcontainer/docker-compose.yml --profile playwright config --quiet + docker compose \ + --file .devcontainer/ncdd.generated.yml \ + --file .devcontainer/docker-compose.yml \ + --profile playwright \ + config --quiet grep -q "HOST_UID: $(id -u)" .devcontainer/ncdd.generated.yml grep -q "HOST_GID: $(id -g)" .devcontainer/ncdd.generated.yml - grep -Eq 'NEXTCLOUD_HOST: ncdev-.*\.localhost' .devcontainer/ncdd.generated.yml + grep -Eq 'NEXTCLOUD_HOST: ncdev-.*\\.localhost' .devcontainer/ncdd.generated.yml - name: Verify Codespaces NCDD contract env: @@ -57,13 +61,20 @@ jobs: - name: Verify the created environment run: | - npx --yes @devcontainers/cli@0.89.0 exec --workspace-folder . bash -lc "occ status | grep -q 'installed: true'" - npx --yes @devcontainers/cli@0.89.0 exec --workspace-folder . bash -lc "occ app:list --enabled | grep -q libresign" - npx --yes @devcontainers/cli@0.89.0 exec --workspace-folder . bash -lc "curl --fail --silent http://mailpit:8025/api/v1/info >/dev/null" + npx --yes @devcontainers/cli@0.89.0 exec --workspace-folder . \ + bash -lc "occ status | grep -q 'installed: true'" + npx --yes @devcontainers/cli@0.89.0 exec --workspace-folder . \ + bash -lc "occ app:list --enabled | grep -q libresign" + npx --yes @devcontainers/cli@0.89.0 exec --workspace-folder . \ + bash -lc "curl --fail --silent http://mailpit:8025/api/v1/info >/dev/null" - name: Verify Playwright reaches Nextcloud through the proxy run: | - docker compose --file .devcontainer/ncdd.generated.yml --file .devcontainer/docker-compose.yml --profile playwright exec -T playwright node -e ' + docker compose \ + --file .devcontainer/ncdd.generated.yml \ + --file .devcontainer/docker-compose.yml \ + --profile playwright \ + exec -T playwright node -e ' const https = require("https"); const target = new URL(process.env.PLAYWRIGHT_BASE_URL + "/status.php"); const request = https.get(target, { rejectUnauthorized: false }, response => { From a8d998effd8583ca222f0f6887ef958fd8891e45 Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Mon, 5 Oct 2026 01:44:21 +0000 Subject: [PATCH 18/39] fix: satisfy ShellCheck in devcontainer bootstrap Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- .devcontainer/prepare.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.devcontainer/prepare.sh b/.devcontainer/prepare.sh index 0d1b8f8ace..1b99fb908b 100644 --- a/.devcontainer/prepare.sh +++ b/.devcontainer/prepare.sh @@ -4,7 +4,7 @@ set -eu -repo_root="$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)" +repo_root="$(CDPATH='' cd -- "$(dirname -- "$0")/.." && pwd)" ncdd_dir="$repo_root/.devcontainer/.nextcloud-docker-development" generated_compose="$repo_root/.devcontainer/ncdd.generated.yml" ncdd_commit="b33c4dee93a4d41ce856e7480c871cf1171b3cbb" From aa9a3ec6885ac1790226c3669f86ba85d9f83041 Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Mon, 5 Oct 2026 01:56:20 +0000 Subject: [PATCH 19/39] test: harden devcontainer bootstrap contract Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- .github/workflows/devcontainer.yml | 30 +++++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/.github/workflows/devcontainer.yml b/.github/workflows/devcontainer.yml index 28c9cdd1e0..a483f4ace1 100644 --- a/.github/workflows/devcontainer.yml +++ b/.github/workflows/devcontainer.yml @@ -31,19 +31,43 @@ jobs: sh -n .devcontainer/prepare.sh bash -n .devcontainer/setup.sh - - name: Verify local NCDD contract + - name: Verify prepare.sh local contract run: | sh .devcontainer/prepare.sh + cp .devcontainer/ncdd.generated.yml /tmp/ncdd.generated.first.yml + docker compose \ --file .devcontainer/ncdd.generated.yml \ --file .devcontainer/docker-compose.yml \ --profile playwright \ config --quiet + grep -q "HOST_UID: $(id -u)" .devcontainer/ncdd.generated.yml grep -q "HOST_GID: $(id -g)" .devcontainer/ncdd.generated.yml - grep -Eq 'NEXTCLOUD_HOST: ncdev-.*\\.localhost' .devcontainer/ncdd.generated.yml + grep -Eq 'NEXTCLOUD_HOST: ncdev-.*\.localhost' .devcontainer/ncdd.generated.yml + + sh .devcontainer/prepare.sh + cmp /tmp/ncdd.generated.first.yml .devcontainer/ncdd.generated.yml + + - name: Verify prepare.sh configuration overrides + env: + LIBRESIGN_DB_TYPE: sqlite + LIBRESIGN_NEXTCLOUD_VERSION: stable35 + run: | + sh .devcontainer/prepare.sh + grep -q 'DB_TYPE: sqlite' .devcontainer/ncdd.generated.yml + grep -q 'DB_DRIVER: sqlite' .devcontainer/ncdd.generated.yml + grep -q 'VERSION_NEXTCLOUD: stable35' .devcontainer/ncdd.generated.yml + + - name: Verify prepare.sh rejects incomplete Codespaces context + run: | + if CODESPACES=true sh .devcontainer/prepare.sh >/tmp/prepare.out 2>/tmp/prepare.err; then + echo "prepare.sh accepted Codespaces without the required environment" >&2 + exit 1 + fi + grep -q 'CODESPACE_NAME is required in Codespaces' /tmp/prepare.err - - name: Verify Codespaces NCDD contract + - name: Verify prepare.sh Codespaces contract env: CODESPACES: "true" CODESPACE_NAME: libresign-test From dca2ca86b653c4de14c3b86d83e9e6b5cc9b0467 Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Mon, 5 Oct 2026 02:05:38 +0000 Subject: [PATCH 20/39] ci: separate devcontainer contract and integration checks Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- .github/workflows/devcontainer.yml | 43 +++++++++++++++++++++--------- 1 file changed, 30 insertions(+), 13 deletions(-) diff --git a/.github/workflows/devcontainer.yml b/.github/workflows/devcontainer.yml index a483f4ace1..ae3268b595 100644 --- a/.github/workflows/devcontainer.yml +++ b/.github/workflows/devcontainer.yml @@ -14,14 +14,23 @@ on: paths: - '.devcontainer/**' - '.github/workflows/devcontainer.yml' + workflow_dispatch: permissions: contents: read +concurrency: + group: devcontainer-${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +env: + DEVCONTAINER_CLI_VERSION: 0.89.0 + jobs: - devcontainer: + contract: + name: Devcontainer contract runs-on: ubuntu-latest - timeout-minutes: 25 + timeout-minutes: 10 steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 @@ -31,7 +40,7 @@ jobs: sh -n .devcontainer/prepare.sh bash -n .devcontainer/setup.sh - - name: Verify prepare.sh local contract + - name: Verify local NCDD contract run: | sh .devcontainer/prepare.sh cp .devcontainer/ncdd.generated.yml /tmp/ncdd.generated.first.yml @@ -49,7 +58,7 @@ jobs: sh .devcontainer/prepare.sh cmp /tmp/ncdd.generated.first.yml .devcontainer/ncdd.generated.yml - - name: Verify prepare.sh configuration overrides + - name: Verify configuration overrides env: LIBRESIGN_DB_TYPE: sqlite LIBRESIGN_NEXTCLOUD_VERSION: stable35 @@ -59,7 +68,7 @@ jobs: grep -q 'DB_DRIVER: sqlite' .devcontainer/ncdd.generated.yml grep -q 'VERSION_NEXTCLOUD: stable35' .devcontainer/ncdd.generated.yml - - name: Verify prepare.sh rejects incomplete Codespaces context + - name: Verify incomplete Codespaces context is rejected run: | if CODESPACES=true sh .devcontainer/prepare.sh >/tmp/prepare.out 2>/tmp/prepare.err; then echo "prepare.sh accepted Codespaces without the required environment" >&2 @@ -67,7 +76,7 @@ jobs: fi grep -q 'CODESPACE_NAME is required in Codespaces' /tmp/prepare.err - - name: Verify prepare.sh Codespaces contract + - name: Verify Codespaces NCDD contract env: CODESPACES: "true" CODESPACE_NAME: libresign-test @@ -77,19 +86,27 @@ jobs: grep -q 'NEXTCLOUD_HOST: libresign-test-443.app.github.dev' .devcontainer/ncdd.generated.yml grep -q 'NEXTCLOUD_PROTOCOL: https' .devcontainer/ncdd.generated.yml - - name: Restore local configuration - run: sh .devcontainer/prepare.sh + integration: + name: Devcontainer integration + needs: contract + runs-on: ubuntu-latest + timeout-minutes: 25 + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 + + - name: Install Dev Container CLI + run: npm install --global "@devcontainers/cli@${DEVCONTAINER_CLI_VERSION}" - name: Start the Dev Container - run: npx --yes @devcontainers/cli@0.89.0 up --workspace-folder . + run: devcontainer up --workspace-folder . - - name: Verify the created environment + - name: Verify Nextcloud, LibreSign and Mailpit run: | - npx --yes @devcontainers/cli@0.89.0 exec --workspace-folder . \ + devcontainer exec --workspace-folder . \ bash -lc "occ status | grep -q 'installed: true'" - npx --yes @devcontainers/cli@0.89.0 exec --workspace-folder . \ + devcontainer exec --workspace-folder . \ bash -lc "occ app:list --enabled | grep -q libresign" - npx --yes @devcontainers/cli@0.89.0 exec --workspace-folder . \ + devcontainer exec --workspace-folder . \ bash -lc "curl --fail --silent http://mailpit:8025/api/v1/info >/dev/null" - name: Verify Playwright reaches Nextcloud through the proxy From e93acb633add29700a5888da6ea3284de0d0c1e6 Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Mon, 5 Oct 2026 02:06:02 +0000 Subject: [PATCH 21/39] ci: collect devcontainer diagnostics on failure Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- .github/workflows/devcontainer.yml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/.github/workflows/devcontainer.yml b/.github/workflows/devcontainer.yml index ae3268b595..1d4d9e2ed2 100644 --- a/.github/workflows/devcontainer.yml +++ b/.github/workflows/devcontainer.yml @@ -124,3 +124,17 @@ jobs: }); request.on("error", () => { process.exitCode = 1; }); ' + + - name: Collect container diagnostics + if: failure() + run: | + docker compose \ + --file .devcontainer/ncdd.generated.yml \ + --file .devcontainer/docker-compose.yml \ + --profile playwright \ + ps + docker compose \ + --file .devcontainer/ncdd.generated.yml \ + --file .devcontainer/docker-compose.yml \ + --profile playwright \ + logs --no-color --tail=200 From f04fb1a788024201326e17eaaab945544fda4a4d Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Mon, 5 Oct 2026 02:07:01 +0000 Subject: [PATCH 22/39] fix: keep Codespaces port configuration portable Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- .devcontainer/README.md | 16 +++++++++++----- .devcontainer/devcontainer.json | 13 ++----------- .github/workflows/devcontainer.yml | 9 ++++++++- 3 files changed, 21 insertions(+), 17 deletions(-) diff --git a/.devcontainer/README.md b/.devcontainer/README.md index 32f764142f..3863fc88e8 100644 --- a/.devcontainer/README.md +++ b/.devcontainer/README.md @@ -26,9 +26,10 @@ https://ncdev-libresign-.localhost ``` For GitHub Codespaces, the same shared proxy remains the only HTTP entry point. -The Dev Container forwards `host.docker.internal:443`, which is the Docker -host port owned by the shared NCDD proxy, and the adapter configures Nextcloud -with the public hostname GitHub assigns to forwarded port `443`: +NCDD already publishes the proxy on port `443` of the Codespaces VM. Codespaces +detects that port and exposes it using its normal port-forwarding mechanism, +while the adapter configures Nextcloud with the public hostname GitHub assigns +to forwarded port `443`: ```text https://-443. @@ -37,8 +38,13 @@ https://-443. The forwarding domain comes from `GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN`; it is not hard-coded. -Individual workers do not publish separate nginx host ports. Mailpit is -forwarded directly from its Compose service on port `8025`. +Individual workers do not publish separate nginx host ports. The Dev Container +configuration intentionally avoids service-qualified `forwardPorts` entries, +because GitHub Codespaces does not support the `host:port` form. + +Mailpit remains available locally through NCDD's shared proxy. In Codespaces it +is still reachable from the development environment and is covered by CI, but +its browser UI is not exposed as a separate forwarded URL by this adapter. The Playwright service is optional and is not started as part of normal Dev Container use. When enabled, NCDD routes its canonical Nextcloud hostname back diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 4ccede5c69..6ee959c75e 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -10,10 +10,6 @@ ], "service": "nextcloud", "workspaceFolder": "/var/www/html/apps-extra/libresign", - "forwardPorts": [ - "host.docker.internal:443", - "mailpit:8025" - ], "containerEnv": { "XDEBUG_MODE": "debug", "XDEBUG_CONFIG": "client_host=host.docker.internal client_port=9003 start_with_request=yes" @@ -51,15 +47,10 @@ "/var/www/html/apps-extra/libresign/.devcontainer/setup.sh" ], "portsAttributes": { - "host.docker.internal:443": { + "443": { "label": "Nextcloud via NCDD proxy", "protocol": "https", - "onAutoForward": "silent" - }, - "mailpit:8025": { - "label": "Mailpit", - "protocol": "http", - "onAutoForward": "notify" + "onAutoForward": "openBrowser" }, "9000": { "label": "php-fpm", diff --git a/.github/workflows/devcontainer.yml b/.github/workflows/devcontainer.yml index 1d4d9e2ed2..24da0c6083 100644 --- a/.github/workflows/devcontainer.yml +++ b/.github/workflows/devcontainer.yml @@ -34,12 +34,19 @@ jobs: steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 - - name: Check shell scripts + - name: Check Dev Container configuration run: | + jq empty .devcontainer/devcontainer.json shellcheck .devcontainer/prepare.sh .devcontainer/setup.sh sh -n .devcontainer/prepare.sh bash -n .devcontainer/setup.sh + # Codespaces does not support host:port in forwardPorts/portsAttributes. + ! jq -e ' + ((.forwardPorts // [])[]? | strings | contains(":")) or + ((.portsAttributes // {} | keys[]) | contains(":")) + ' .devcontainer/devcontainer.json + - name: Verify local NCDD contract run: | sh .devcontainer/prepare.sh From 0eb0502324dab3eb2beb7222c9bb32fb35c24e9b Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Mon, 5 Oct 2026 02:13:59 +0000 Subject: [PATCH 23/39] test: cover devcontainer bootstrap with Bats Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- .devcontainer/tests/prepare.bats | 71 ++++++++++++++++++++++++++++++ .github/workflows/devcontainer.yml | 53 +++------------------- 2 files changed, 78 insertions(+), 46 deletions(-) create mode 100644 .devcontainer/tests/prepare.bats diff --git a/.devcontainer/tests/prepare.bats b/.devcontainer/tests/prepare.bats new file mode 100644 index 0000000000..436c3b926e --- /dev/null +++ b/.devcontainer/tests/prepare.bats @@ -0,0 +1,71 @@ +#!/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)" + PREPARE="$REPO_ROOT/.devcontainer/prepare.sh" + GENERATED="$REPO_ROOT/.devcontainer/ncdd.generated.yml" + OVERRIDE="$REPO_ROOT/.devcontainer/docker-compose.yml" +} + +@test "prepare.sh renders a valid local NCDD configuration" { + run sh "$PREPARE" + [ "$status" -eq 0 ] + + run grep -q "HOST_UID: $(id -u)" "$GENERATED" + [ "$status" -eq 0 ] + + run grep -q "HOST_GID: $(id -g)" "$GENERATED" + [ "$status" -eq 0 ] + + run grep -Eq 'NEXTCLOUD_HOST: ncdev-.*\.localhost' "$GENERATED" + [ "$status" -eq 0 ] + + run docker compose --file "$GENERATED" --file "$OVERRIDE" --profile playwright config --quiet + [ "$status" -eq 0 ] +} + +@test "prepare.sh is idempotent for the same checkout and environment" { + run sh "$PREPARE" + [ "$status" -eq 0 ] + first="$(sha256sum "$GENERATED" | awk '{print $1}')" + + run sh "$PREPARE" + [ "$status" -eq 0 ] + second="$(sha256sum "$GENERATED" | awk '{print $1}')" + + [ "$first" = "$second" ] +} + +@test "prepare.sh forwards supported LibreSign overrides to NCDD" { + run env LIBRESIGN_DB_TYPE=sqlite LIBRESIGN_NEXTCLOUD_VERSION=stable35 sh "$PREPARE" + [ "$status" -eq 0 ] + + run grep -q 'DB_TYPE: sqlite' "$GENERATED" + [ "$status" -eq 0 ] + + run grep -q 'DB_DRIVER: sqlite' "$GENERATED" + [ "$status" -eq 0 ] + + run grep -q 'VERSION_NEXTCLOUD: stable35' "$GENERATED" + [ "$status" -eq 0 ] +} + +@test "prepare.sh rejects incomplete Codespaces context" { + run env CODESPACES=true sh "$PREPARE" + + [ "$status" -ne 0 ] + [[ "$output" == *"CODESPACE_NAME is required in Codespaces"* ]] +} + +@test "prepare.sh renders the Codespaces public Nextcloud URL" { + run env CODESPACES=true CODESPACE_NAME=libresign-test GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN=app.github.dev sh "$PREPARE" + [ "$status" -eq 0 ] + + run grep -q 'NEXTCLOUD_HOST: libresign-test-443.app.github.dev' "$GENERATED" + [ "$status" -eq 0 ] + + run grep -q 'NEXTCLOUD_PROTOCOL: https' "$GENERATED" + [ "$status" -eq 0 ] +} diff --git a/.github/workflows/devcontainer.yml b/.github/workflows/devcontainer.yml index 24da0c6083..ef3e1cc19a 100644 --- a/.github/workflows/devcontainer.yml +++ b/.github/workflows/devcontainer.yml @@ -34,6 +34,11 @@ jobs: steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 + - name: Install Bats + run: | + sudo apt-get update + sudo apt-get install --yes bats + - name: Check Dev Container configuration run: | jq empty .devcontainer/devcontainer.json @@ -41,57 +46,13 @@ jobs: sh -n .devcontainer/prepare.sh bash -n .devcontainer/setup.sh - # Codespaces does not support host:port in forwardPorts/portsAttributes. ! jq -e ' ((.forwardPorts // [])[]? | strings | contains(":")) or ((.portsAttributes // {} | keys[]) | contains(":")) ' .devcontainer/devcontainer.json - - name: Verify local NCDD contract - run: | - sh .devcontainer/prepare.sh - cp .devcontainer/ncdd.generated.yml /tmp/ncdd.generated.first.yml - - docker compose \ - --file .devcontainer/ncdd.generated.yml \ - --file .devcontainer/docker-compose.yml \ - --profile playwright \ - config --quiet - - grep -q "HOST_UID: $(id -u)" .devcontainer/ncdd.generated.yml - grep -q "HOST_GID: $(id -g)" .devcontainer/ncdd.generated.yml - grep -Eq 'NEXTCLOUD_HOST: ncdev-.*\.localhost' .devcontainer/ncdd.generated.yml - - sh .devcontainer/prepare.sh - cmp /tmp/ncdd.generated.first.yml .devcontainer/ncdd.generated.yml - - - name: Verify configuration overrides - env: - LIBRESIGN_DB_TYPE: sqlite - LIBRESIGN_NEXTCLOUD_VERSION: stable35 - run: | - sh .devcontainer/prepare.sh - grep -q 'DB_TYPE: sqlite' .devcontainer/ncdd.generated.yml - grep -q 'DB_DRIVER: sqlite' .devcontainer/ncdd.generated.yml - grep -q 'VERSION_NEXTCLOUD: stable35' .devcontainer/ncdd.generated.yml - - - name: Verify incomplete Codespaces context is rejected - run: | - if CODESPACES=true sh .devcontainer/prepare.sh >/tmp/prepare.out 2>/tmp/prepare.err; then - echo "prepare.sh accepted Codespaces without the required environment" >&2 - exit 1 - fi - grep -q 'CODESPACE_NAME is required in Codespaces' /tmp/prepare.err - - - name: Verify Codespaces NCDD contract - env: - CODESPACES: "true" - CODESPACE_NAME: libresign-test - GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN: app.github.dev - run: | - sh .devcontainer/prepare.sh - grep -q 'NEXTCLOUD_HOST: libresign-test-443.app.github.dev' .devcontainer/ncdd.generated.yml - grep -q 'NEXTCLOUD_PROTOCOL: https' .devcontainer/ncdd.generated.yml + - name: Test devcontainer bootstrap contract + run: bats --print-output-on-failure .devcontainer/tests/prepare.bats integration: name: Devcontainer integration From f98b74acb82145a5e51b334ceee510c1c0c4af7f Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Mon, 5 Oct 2026 02:14:55 +0000 Subject: [PATCH 24/39] test: assert devcontainer contract structurally Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- .devcontainer/tests/prepare.bats | 30 +++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/.devcontainer/tests/prepare.bats b/.devcontainer/tests/prepare.bats index 436c3b926e..71eb0a669d 100644 --- a/.devcontainer/tests/prepare.bats +++ b/.devcontainer/tests/prepare.bats @@ -9,20 +9,27 @@ setup() { OVERRIDE="$REPO_ROOT/.devcontainer/docker-compose.yml" } +compose_json() { + docker compose --file "$GENERATED" --file "$OVERRIDE" --profile playwright config --format json +} + @test "prepare.sh renders a valid local NCDD configuration" { run sh "$PREPARE" [ "$status" -eq 0 ] - run grep -q "HOST_UID: $(id -u)" "$GENERATED" + run docker compose --file "$GENERATED" --file "$OVERRIDE" --profile playwright config --quiet [ "$status" -eq 0 ] - run grep -q "HOST_GID: $(id -g)" "$GENERATED" + config="$(compose_json)" + + run jq -e --arg uid "$(id -u)" '.services.nextcloud.environment.HOST_UID == $uid' <<<"$config" [ "$status" -eq 0 ] - run grep -Eq 'NEXTCLOUD_HOST: ncdev-.*\.localhost' "$GENERATED" + run jq -e --arg gid "$(id -g)" '.services.nextcloud.environment.HOST_GID == $gid' <<<"$config" [ "$status" -eq 0 ] - run docker compose --file "$GENERATED" --file "$OVERRIDE" --profile playwright config --quiet + run jq -e '.services.nextcloud.environment.NEXTCLOUD_HOST + | test("^ncdev-.*\\.localhost$")' <<<"$config" [ "$status" -eq 0 ] } @@ -42,13 +49,15 @@ setup() { run env LIBRESIGN_DB_TYPE=sqlite LIBRESIGN_NEXTCLOUD_VERSION=stable35 sh "$PREPARE" [ "$status" -eq 0 ] - run grep -q 'DB_TYPE: sqlite' "$GENERATED" + config="$(compose_json)" + + run jq -e '.services.nextcloud.environment.DB_TYPE == "sqlite"' <<<"$config" [ "$status" -eq 0 ] - run grep -q 'DB_DRIVER: sqlite' "$GENERATED" + run jq -e '.services.nextcloud.environment.DB_DRIVER == "sqlite"' <<<"$config" [ "$status" -eq 0 ] - run grep -q 'VERSION_NEXTCLOUD: stable35' "$GENERATED" + run jq -e '.services.nextcloud.environment.VERSION_NEXTCLOUD == "stable35"' <<<"$config" [ "$status" -eq 0 ] } @@ -63,9 +72,12 @@ setup() { run env CODESPACES=true CODESPACE_NAME=libresign-test GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN=app.github.dev sh "$PREPARE" [ "$status" -eq 0 ] - run grep -q 'NEXTCLOUD_HOST: libresign-test-443.app.github.dev' "$GENERATED" + config="$(compose_json)" + + run jq -e '.services.nextcloud.environment.NEXTCLOUD_HOST + == "libresign-test-443.app.github.dev"' <<<"$config" [ "$status" -eq 0 ] - run grep -q 'NEXTCLOUD_PROTOCOL: https' "$GENERATED" + run jq -e '.services.nextcloud.environment.NEXTCLOUD_PROTOCOL == "https"' <<<"$config" [ "$status" -eq 0 ] } From b904471d91313f04e683f8b42389e8e4e33426cc Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Mon, 5 Oct 2026 02:17:48 +0000 Subject: [PATCH 25/39] test: exercise Nextcloud with a real Playwright browser Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- .github/workflows/devcontainer.yml | 40 +++++++++++++++++++++++------- 1 file changed, 31 insertions(+), 9 deletions(-) diff --git a/.github/workflows/devcontainer.yml b/.github/workflows/devcontainer.yml index ef3e1cc19a..9c2ddefb79 100644 --- a/.github/workflows/devcontainer.yml +++ b/.github/workflows/devcontainer.yml @@ -83,15 +83,37 @@ jobs: --file .devcontainer/ncdd.generated.yml \ --file .devcontainer/docker-compose.yml \ --profile playwright \ - exec -T playwright node -e ' - const https = require("https"); - const target = new URL(process.env.PLAYWRIGHT_BASE_URL + "/status.php"); - const request = https.get(target, { rejectUnauthorized: false }, response => { - if (response.statusCode !== 200) process.exitCode = 1; - response.resume(); - }); - request.on("error", () => { process.exitCode = 1; }); - ' + exec -T playwright node <<'NODE' + const dns = require('dns').promises + const { chromium } = require('/var/www/html/apps-extra/libresign/node_modules/playwright') + + ;(async () => { + const target = new URL(process.env.PLAYWRIGHT_BASE_URL + '/status.php') + const addresses = await dns.lookup(target.hostname, { all: true }) + console.log('Resolved', target.hostname, addresses) + + const browser = await chromium.launch() + try { + const context = await browser.newContext({ ignoreHTTPSErrors: true }) + const page = await context.newPage() + const response = await page.goto(target.href, { waitUntil: 'domcontentloaded' }) + + if (!response) { + throw new Error('Playwright did not receive an HTTP response') + } + + console.log('Playwright response', response.status(), response.url()) + if (response.status() !== 200) { + throw new Error(`Expected HTTP 200, got ${response.status()}`) + } + } finally { + await browser.close() + } + })().catch(error => { + console.error(error) + process.exit(1) + }) + NODE - name: Collect container diagnostics if: failure() From 578151804ab02d20854bdac8dfe83b16ec46f9cd Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Mon, 5 Oct 2026 02:20:36 +0000 Subject: [PATCH 26/39] test: run Playwright as an ephemeral smoke service Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- .github/workflows/devcontainer.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/devcontainer.yml b/.github/workflows/devcontainer.yml index 9c2ddefb79..c59bc13714 100644 --- a/.github/workflows/devcontainer.yml +++ b/.github/workflows/devcontainer.yml @@ -83,7 +83,7 @@ jobs: --file .devcontainer/ncdd.generated.yml \ --file .devcontainer/docker-compose.yml \ --profile playwright \ - exec -T playwright node <<'NODE' + run --rm --no-deps -T playwright node <<'NODE' const dns = require('dns').promises const { chromium } = require('/var/www/html/apps-extra/libresign/node_modules/playwright') From 960d7d657254e5ed5fdde5cb495824eb0e85c232 Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Mon, 5 Oct 2026 02:25:57 +0000 Subject: [PATCH 27/39] ci: pin the Bats test runtime Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- .github/workflows/devcontainer.yml | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/.github/workflows/devcontainer.yml b/.github/workflows/devcontainer.yml index c59bc13714..db512e434e 100644 --- a/.github/workflows/devcontainer.yml +++ b/.github/workflows/devcontainer.yml @@ -34,10 +34,13 @@ jobs: steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 - - name: Install Bats - run: | - sudo apt-get update - sudo apt-get install --yes bats + - name: Setup Bats-core + uses: bats-core/bats-action@77d6fb60505b4d0d1d73e48bd035b55074bbfb43 # 4.0.0 + with: + support-install: false + assert-install: false + detik-install: false + file-install: false - name: Check Dev Container configuration run: | From 311fc77cb078dbdaaf58c39f459767dc260a10a5 Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Mon, 5 Oct 2026 02:27:36 +0000 Subject: [PATCH 28/39] test: make the Playwright devcontainer smoke deterministic Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- .devcontainer/tests/playwright-smoke.cjs | 65 ++++++++++++++++++++++++ .github/workflows/devcontainer.yml | 41 ++++----------- 2 files changed, 75 insertions(+), 31 deletions(-) create mode 100644 .devcontainer/tests/playwright-smoke.cjs diff --git a/.devcontainer/tests/playwright-smoke.cjs b/.devcontainer/tests/playwright-smoke.cjs new file mode 100644 index 0000000000..544da1f978 --- /dev/null +++ b/.devcontainer/tests/playwright-smoke.cjs @@ -0,0 +1,65 @@ +/** + * SPDX-FileCopyrightText: 2026 LibreCode coop and contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +const dns = require('node:dns').promises +const { chromium } = require('playwright') + +const sleep = (milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds)) + +async function main() { + const baseUrl = process.env.PLAYWRIGHT_BASE_URL + if (!baseUrl) { + throw new Error('PLAYWRIGHT_BASE_URL is required') + } + + const target = new URL('/status.php', baseUrl) + const addresses = await dns.lookup(target.hostname, { all: true }) + console.log('Resolved', target.hostname, addresses) + + const browser = await chromium.launch() + try { + const context = await browser.newContext({ ignoreHTTPSErrors: true }) + const page = await context.newPage() + let lastError + + for (let attempt = 1; attempt <= 20; attempt++) { + try { + const response = await page.goto(target.href, { + waitUntil: 'domcontentloaded', + timeout: 5000, + }) + if (!response) { + throw new Error('Playwright did not receive an HTTP response') + } + if (response.status() !== 200) { + throw new Error(`Expected HTTP 200, got ${response.status()}`) + } + + const status = await response.json() + if (status.installed !== true) { + throw new Error('Nextcloud status endpoint does not report installed=true') + } + + console.log('Playwright reached', response.url(), status) + return + } catch (error) { + lastError = error + if (attempt === 20) { + break + } + await sleep(500) + } + } + + throw lastError + } finally { + await browser.close() + } +} + +main().catch((error) => { + console.error(error) + process.exit(1) +}) diff --git a/.github/workflows/devcontainer.yml b/.github/workflows/devcontainer.yml index db512e434e..25f6a0a095 100644 --- a/.github/workflows/devcontainer.yml +++ b/.github/workflows/devcontainer.yml @@ -80,43 +80,22 @@ jobs: devcontainer exec --workspace-folder . \ bash -lc "curl --fail --silent http://mailpit:8025/api/v1/info >/dev/null" + - name: Start Playwright runner + run: | + docker compose \ + --file .devcontainer/ncdd.generated.yml \ + --file .devcontainer/docker-compose.yml \ + --profile playwright \ + up --detach playwright + - name: Verify Playwright reaches Nextcloud through the proxy run: | docker compose \ --file .devcontainer/ncdd.generated.yml \ --file .devcontainer/docker-compose.yml \ --profile playwright \ - run --rm --no-deps -T playwright node <<'NODE' - const dns = require('dns').promises - const { chromium } = require('/var/www/html/apps-extra/libresign/node_modules/playwright') - - ;(async () => { - const target = new URL(process.env.PLAYWRIGHT_BASE_URL + '/status.php') - const addresses = await dns.lookup(target.hostname, { all: true }) - console.log('Resolved', target.hostname, addresses) - - const browser = await chromium.launch() - try { - const context = await browser.newContext({ ignoreHTTPSErrors: true }) - const page = await context.newPage() - const response = await page.goto(target.href, { waitUntil: 'domcontentloaded' }) - - if (!response) { - throw new Error('Playwright did not receive an HTTP response') - } - - console.log('Playwright response', response.status(), response.url()) - if (response.status() !== 200) { - throw new Error(`Expected HTTP 200, got ${response.status()}`) - } - } finally { - await browser.close() - } - })().catch(error => { - console.error(error) - process.exit(1) - }) - NODE + exec -T playwright \ + node /var/www/html/apps-extra/libresign/.devcontainer/tests/playwright-smoke.cjs - name: Collect container diagnostics if: failure() From 8e30e9b8cc8549e4c827b7d9adab0720955e57d2 Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Mon, 5 Oct 2026 02:27:47 +0000 Subject: [PATCH 29/39] test: validate the Playwright smoke script syntax Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- .github/workflows/devcontainer.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/devcontainer.yml b/.github/workflows/devcontainer.yml index 25f6a0a095..c882299055 100644 --- a/.github/workflows/devcontainer.yml +++ b/.github/workflows/devcontainer.yml @@ -48,6 +48,7 @@ jobs: shellcheck .devcontainer/prepare.sh .devcontainer/setup.sh sh -n .devcontainer/prepare.sh bash -n .devcontainer/setup.sh + node --check .devcontainer/tests/playwright-smoke.cjs ! jq -e ' ((.forwardPorts // [])[]? | strings | contains(":")) or From cf3e685060bce944ff7fc485335be20b76d8c281 Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Mon, 5 Oct 2026 02:29:06 +0000 Subject: [PATCH 30/39] test: cover devcontainer bootstrap recovery and repeatability Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- .devcontainer/tests/prepare.bats | 12 ++++++++++++ .github/workflows/devcontainer.yml | 9 +++++++++ 2 files changed, 21 insertions(+) diff --git a/.devcontainer/tests/prepare.bats b/.devcontainer/tests/prepare.bats index 71eb0a669d..9151015c70 100644 --- a/.devcontainer/tests/prepare.bats +++ b/.devcontainer/tests/prepare.bats @@ -61,6 +61,18 @@ compose_json() { [ "$status" -eq 0 ] } +@test "prepare.sh keeps the last valid configuration when rendering fails" { + run sh "$PREPARE" + [ "$status" -eq 0 ] + before="$(sha256sum "$GENERATED" | awk '{print $1}')" + + run env LIBRESIGN_DB_TYPE=unsupported sh "$PREPARE" + [ "$status" -ne 0 ] + + after="$(sha256sum "$GENERATED" | awk '{print $1}')" + [ "$before" = "$after" ] +} + @test "prepare.sh rejects incomplete Codespaces context" { run env CODESPACES=true sh "$PREPARE" diff --git a/.github/workflows/devcontainer.yml b/.github/workflows/devcontainer.yml index c882299055..48994d3552 100644 --- a/.github/workflows/devcontainer.yml +++ b/.github/workflows/devcontainer.yml @@ -33,6 +33,8 @@ jobs: timeout-minutes: 10 steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 + with: + persist-credentials: false - name: Setup Bats-core uses: bats-core/bats-action@77d6fb60505b4d0d1d73e48bd035b55074bbfb43 # 4.0.0 @@ -65,6 +67,8 @@ jobs: timeout-minutes: 25 steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 + with: + persist-credentials: false - name: Install Dev Container CLI run: npm install --global "@devcontainers/cli@${DEVCONTAINER_CLI_VERSION}" @@ -81,6 +85,11 @@ jobs: devcontainer exec --workspace-folder . \ bash -lc "curl --fail --silent http://mailpit:8025/api/v1/info >/dev/null" + - name: Verify LibreSign setup is repeatable + run: | + devcontainer exec --workspace-folder . \ + bash /var/www/html/apps-extra/libresign/.devcontainer/setup.sh + - name: Start Playwright runner run: | docker compose \ From a1c123bb4ee7f50b1758b2db0d6a0ba5fc106d3d Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Mon, 5 Oct 2026 02:30:54 +0000 Subject: [PATCH 31/39] style: format devcontainer contract tests Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- .devcontainer/tests/prepare.bats | 47 ++++++++++++++++++++++++-------- 1 file changed, 35 insertions(+), 12 deletions(-) diff --git a/.devcontainer/tests/prepare.bats b/.devcontainer/tests/prepare.bats index 9151015c70..b0fc2d3046 100644 --- a/.devcontainer/tests/prepare.bats +++ b/.devcontainer/tests/prepare.bats @@ -10,25 +10,36 @@ setup() { } compose_json() { - docker compose --file "$GENERATED" --file "$OVERRIDE" --profile playwright config --format json + docker compose \ + --file "$GENERATED" \ + --file "$OVERRIDE" \ + --profile playwright \ + config --format json } @test "prepare.sh renders a valid local NCDD configuration" { run sh "$PREPARE" [ "$status" -eq 0 ] - run docker compose --file "$GENERATED" --file "$OVERRIDE" --profile playwright config --quiet + run docker compose \ + --file "$GENERATED" \ + --file "$OVERRIDE" \ + --profile playwright \ + config --quiet [ "$status" -eq 0 ] config="$(compose_json)" - run jq -e --arg uid "$(id -u)" '.services.nextcloud.environment.HOST_UID == $uid' <<<"$config" + run jq -e --arg uid "$(id -u)" \ + '.services.nextcloud.environment.HOST_UID == $uid' <<<"$config" [ "$status" -eq 0 ] - run jq -e --arg gid "$(id -g)" '.services.nextcloud.environment.HOST_GID == $gid' <<<"$config" + run jq -e --arg gid "$(id -g)" \ + '.services.nextcloud.environment.HOST_GID == $gid' <<<"$config" [ "$status" -eq 0 ] - run jq -e '.services.nextcloud.environment.NEXTCLOUD_HOST + run jq -e \ + '.services.nextcloud.environment.NEXTCLOUD_HOST | test("^ncdev-.*\\.localhost$")' <<<"$config" [ "$status" -eq 0 ] } @@ -46,18 +57,24 @@ compose_json() { } @test "prepare.sh forwards supported LibreSign overrides to NCDD" { - run env LIBRESIGN_DB_TYPE=sqlite LIBRESIGN_NEXTCLOUD_VERSION=stable35 sh "$PREPARE" + run env \ + LIBRESIGN_DB_TYPE=sqlite \ + LIBRESIGN_NEXTCLOUD_VERSION=stable35 \ + sh "$PREPARE" [ "$status" -eq 0 ] config="$(compose_json)" - run jq -e '.services.nextcloud.environment.DB_TYPE == "sqlite"' <<<"$config" + run jq -e \ + '.services.nextcloud.environment.DB_TYPE == "sqlite"' <<<"$config" [ "$status" -eq 0 ] - run jq -e '.services.nextcloud.environment.DB_DRIVER == "sqlite"' <<<"$config" + run jq -e \ + '.services.nextcloud.environment.DB_DRIVER == "sqlite"' <<<"$config" [ "$status" -eq 0 ] - run jq -e '.services.nextcloud.environment.VERSION_NEXTCLOUD == "stable35"' <<<"$config" + run jq -e \ + '.services.nextcloud.environment.VERSION_NEXTCLOUD == "stable35"' <<<"$config" [ "$status" -eq 0 ] } @@ -81,15 +98,21 @@ compose_json() { } @test "prepare.sh renders the Codespaces public Nextcloud URL" { - run env CODESPACES=true CODESPACE_NAME=libresign-test GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN=app.github.dev sh "$PREPARE" + run env \ + CODESPACES=true \ + CODESPACE_NAME=libresign-test \ + GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN=app.github.dev \ + sh "$PREPARE" [ "$status" -eq 0 ] config="$(compose_json)" - run jq -e '.services.nextcloud.environment.NEXTCLOUD_HOST + run jq -e \ + '.services.nextcloud.environment.NEXTCLOUD_HOST == "libresign-test-443.app.github.dev"' <<<"$config" [ "$status" -eq 0 ] - run jq -e '.services.nextcloud.environment.NEXTCLOUD_PROTOCOL == "https"' <<<"$config" + run jq -e \ + '.services.nextcloud.environment.NEXTCLOUD_PROTOCOL == "https"' <<<"$config" [ "$status" -eq 0 ] } From 047567cf79a4145377b0b3e242927ac39f103d67 Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Mon, 5 Oct 2026 02:37:38 +0000 Subject: [PATCH 32/39] fix: separate proxy and Playwright devcontainer smoke tests Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- .devcontainer/setup.sh | 31 ++++++++++++- .devcontainer/tests/playwright-smoke.cjs | 56 +++++++----------------- .github/workflows/devcontainer.yml | 13 +++++- 3 files changed, 57 insertions(+), 43 deletions(-) diff --git a/.devcontainer/setup.sh b/.devcontainer/setup.sh index 7f659f93f7..ed449ede13 100644 --- a/.devcontainer/setup.sh +++ b/.devcontainer/setup.sh @@ -26,6 +26,34 @@ add_safe_directory() { fi } +environment_summary() { + local protocol="${NEXTCLOUD_PROTOCOL:-https}" + local nextcloud_host="${NEXTCLOUD_HOST:-localhost}" + local project="${COMPOSE_PROJECT_NAME:-}" + local mailpit_url + + if [[ -n "$project" ]]; then + mailpit_url="https://${project}-mailpit.localhost" + else + mailpit_url="http://mailpit:8025" + fi + + printf '\n' + printf 'LibreSign development environment is ready.\n' + printf '\n' + printf ' Nextcloud / LibreSign: %s://%s\n' "$protocol" "$nextcloud_host" + printf ' Mailpit: %s\n' "$mailpit_url" + printf ' Admin user: %s\n' "${NEXTCLOUD_ADMIN_USER:-admin}" + printf ' Nextcloud branch: %s\n' "${VERSION_NEXTCLOUD:-master}" + printf '\n' + printf 'Useful commands:\n' + printf ' npm run watch rebuild frontend assets while editing\n' + printf ' occ status check Nextcloud status\n' + printf ' occ app:list inspect enabled apps\n' + printf '\n' + printf 'The environment can now be opened in the browser.\n' +} + wait_for_nextcloud add_safe_directory /var/www/html add_safe_directory "$app_dir" @@ -57,5 +85,4 @@ occ maintenance:theme:update npm run dev -echo "LibreSign is ready at ${NEXTCLOUD_PROTOCOL:-https}://${NEXTCLOUD_HOST}" -echo "For frontend development, run: npm run watch" +environment_summary diff --git a/.devcontainer/tests/playwright-smoke.cjs b/.devcontainer/tests/playwright-smoke.cjs index 544da1f978..ece16e1341 100644 --- a/.devcontainer/tests/playwright-smoke.cjs +++ b/.devcontainer/tests/playwright-smoke.cjs @@ -3,57 +3,33 @@ * SPDX-License-Identifier: AGPL-3.0-or-later */ -const dns = require('node:dns').promises const { chromium } = require('playwright') -const sleep = (milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds)) - async function main() { - const baseUrl = process.env.PLAYWRIGHT_BASE_URL - if (!baseUrl) { - throw new Error('PLAYWRIGHT_BASE_URL is required') - } - - const target = new URL('/status.php', baseUrl) - const addresses = await dns.lookup(target.hostname, { all: true }) - console.log('Resolved', target.hostname, addresses) - + const target = process.env.PLAYWRIGHT_SMOKE_URL || 'http://nginx/status.php' const browser = await chromium.launch() + try { const context = await browser.newContext({ ignoreHTTPSErrors: true }) const page = await context.newPage() - let lastError - - for (let attempt = 1; attempt <= 20; attempt++) { - try { - const response = await page.goto(target.href, { - waitUntil: 'domcontentloaded', - timeout: 5000, - }) - if (!response) { - throw new Error('Playwright did not receive an HTTP response') - } - if (response.status() !== 200) { - throw new Error(`Expected HTTP 200, got ${response.status()}`) - } + const response = await page.goto(target, { + waitUntil: 'domcontentloaded', + timeout: 10000, + }) - const status = await response.json() - if (status.installed !== true) { - throw new Error('Nextcloud status endpoint does not report installed=true') - } + if (!response) { + throw new Error('Playwright did not receive an HTTP response') + } + if (response.status() !== 200) { + throw new Error(`Expected HTTP 200, got ${response.status()}`) + } - console.log('Playwright reached', response.url(), status) - return - } catch (error) { - lastError = error - if (attempt === 20) { - break - } - await sleep(500) - } + const status = await response.json() + if (status.installed !== true) { + throw new Error('Nextcloud status endpoint does not report installed=true') } - throw lastError + console.log('Playwright browser smoke passed:', response.url()) } finally { await browser.close() } diff --git a/.github/workflows/devcontainer.yml b/.github/workflows/devcontainer.yml index 48994d3552..75f8c00662 100644 --- a/.github/workflows/devcontainer.yml +++ b/.github/workflows/devcontainer.yml @@ -90,6 +90,17 @@ jobs: devcontainer exec --workspace-folder . \ bash /var/www/html/apps-extra/libresign/.devcontainer/setup.sh + - name: Verify Nextcloud through the NCDD proxy + run: | + nextcloud_host="$(docker compose \ + --file .devcontainer/ncdd.generated.yml \ + --file .devcontainer/docker-compose.yml \ + config --format json | jq -r '.services.nextcloud.environment.NEXTCLOUD_HOST')" + + curl --fail --insecure --retry 20 --retry-all-errors \ + --resolve "${nextcloud_host}:443:127.0.0.1" \ + "https://${nextcloud_host}/status.php" >/dev/null + - name: Start Playwright runner run: | docker compose \ @@ -98,7 +109,7 @@ jobs: --profile playwright \ up --detach playwright - - name: Verify Playwright reaches Nextcloud through the proxy + - name: Verify Playwright browser against Nextcloud run: | docker compose \ --file .devcontainer/ncdd.generated.yml \ From 873d8b3aa779f6b8b6d7d63c8e26e47dff39baf1 Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Mon, 5 Oct 2026 02:37:50 +0000 Subject: [PATCH 33/39] test: assert the devcontainer readiness summary Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- .github/workflows/devcontainer.yml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/workflows/devcontainer.yml b/.github/workflows/devcontainer.yml index 75f8c00662..c7f0d19549 100644 --- a/.github/workflows/devcontainer.yml +++ b/.github/workflows/devcontainer.yml @@ -88,7 +88,13 @@ jobs: - name: Verify LibreSign setup is repeatable run: | devcontainer exec --workspace-folder . \ - bash /var/www/html/apps-extra/libresign/.devcontainer/setup.sh + bash /var/www/html/apps-extra/libresign/.devcontainer/setup.sh \ + | tee /tmp/libresign-setup.out + + grep -q 'LibreSign development environment is ready.' /tmp/libresign-setup.out + grep -q 'Nextcloud / LibreSign:' /tmp/libresign-setup.out + grep -q 'Mailpit:' /tmp/libresign-setup.out + grep -q 'Useful commands:' /tmp/libresign-setup.out - name: Verify Nextcloud through the NCDD proxy run: | From 2d78f4577eac15b825b8b89d3783e93d4a38d7a6 Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Mon, 5 Oct 2026 02:45:12 +0000 Subject: [PATCH 34/39] fix: avoid reconciling the devcontainer during Playwright smoke Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- .github/workflows/devcontainer.yml | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/.github/workflows/devcontainer.yml b/.github/workflows/devcontainer.yml index c7f0d19549..0b18558d10 100644 --- a/.github/workflows/devcontainer.yml +++ b/.github/workflows/devcontainer.yml @@ -107,21 +107,13 @@ jobs: --resolve "${nextcloud_host}:443:127.0.0.1" \ "https://${nextcloud_host}/status.php" >/dev/null - - name: Start Playwright runner - run: | - docker compose \ - --file .devcontainer/ncdd.generated.yml \ - --file .devcontainer/docker-compose.yml \ - --profile playwright \ - up --detach playwright - - name: Verify Playwright browser against Nextcloud run: | docker compose \ --file .devcontainer/ncdd.generated.yml \ --file .devcontainer/docker-compose.yml \ --profile playwright \ - exec -T playwright \ + run --rm --no-deps playwright \ node /var/www/html/apps-extra/libresign/.devcontainer/tests/playwright-smoke.cjs - name: Collect container diagnostics From e68654d2fade2bf1d5ae82d5b7d94e6663f357e9 Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Mon, 5 Oct 2026 02:45:23 +0000 Subject: [PATCH 35/39] ci: keep devcontainer diagnostics non-invasive Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- .github/workflows/devcontainer.yml | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/.github/workflows/devcontainer.yml b/.github/workflows/devcontainer.yml index 0b18558d10..e1406a409c 100644 --- a/.github/workflows/devcontainer.yml +++ b/.github/workflows/devcontainer.yml @@ -119,13 +119,7 @@ jobs: - name: Collect container diagnostics if: failure() run: | - docker compose \ - --file .devcontainer/ncdd.generated.yml \ - --file .devcontainer/docker-compose.yml \ - --profile playwright \ - ps - docker compose \ - --file .devcontainer/ncdd.generated.yml \ - --file .devcontainer/docker-compose.yml \ - --profile playwright \ - logs --no-color --tail=200 + docker ps --all + docker logs --tail=200 "$(docker ps -aq --filter name=nextcloud | head -n1)" || true + docker logs --tail=200 "$(docker ps -aq --filter name=nginx | head -n1)" || true + docker logs --tail=200 librecode-dev-proxy || true From 06ac472e035ed5548836bb9f0d288672060669bd Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Mon, 5 Oct 2026 02:45:53 +0000 Subject: [PATCH 36/39] test: make LibreSign devcontainer setup directly testable Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- .devcontainer/setup.sh | 87 +++++++++++++++--------------- .devcontainer/tests/setup.bats | 30 +++++++++++ .github/workflows/devcontainer.yml | 8 +-- 3 files changed, 80 insertions(+), 45 deletions(-) create mode 100644 .devcontainer/tests/setup.bats diff --git a/.devcontainer/setup.sh b/.devcontainer/setup.sh index ed449ede13..94287a2fee 100644 --- a/.devcontainer/setup.sh +++ b/.devcontainer/setup.sh @@ -29,20 +29,17 @@ add_safe_directory() { environment_summary() { local protocol="${NEXTCLOUD_PROTOCOL:-https}" local nextcloud_host="${NEXTCLOUD_HOST:-localhost}" - local project="${COMPOSE_PROJECT_NAME:-}" - local mailpit_url - - if [[ -n "$project" ]]; then - mailpit_url="https://${project}-mailpit.localhost" - else - mailpit_url="http://mailpit:8025" - fi printf '\n' printf 'LibreSign development environment is ready.\n' printf '\n' printf ' Nextcloud / LibreSign: %s://%s\n' "$protocol" "$nextcloud_host" - printf ' Mailpit: %s\n' "$mailpit_url" + printf ' Mailpit (environment): http://mailpit:8025\n' + + if [[ "$nextcloud_host" == *.localhost ]] && [[ -n "${COMPOSE_PROJECT_NAME:-}" ]]; then + printf ' Mailpit (browser): https://%s-mailpit.localhost\n' "$COMPOSE_PROJECT_NAME" + fi + printf ' Admin user: %s\n' "${NEXTCLOUD_ADMIN_USER:-admin}" printf ' Nextcloud branch: %s\n' "${VERSION_NEXTCLOUD:-master}" printf '\n' @@ -51,38 +48,44 @@ environment_summary() { printf ' occ status check Nextcloud status\n' printf ' occ app:list inspect enabled apps\n' printf '\n' - printf 'The environment can now be opened in the browser.\n' + printf 'The environment is ready for development.\n' +} + +main() { + wait_for_nextcloud + add_safe_directory /var/www/html + add_safe_directory "$app_dir" + + cd "$app_dir" + git submodule update --init --recursive + + composer install --no-interaction + npm ci + + occ app:enable libresign + occ libresign:install --use-local-cert --java + occ libresign:install --use-local-cert --pdftk + occ libresign:install --use-local-cert --jsignpdf + occ libresign:configure:openssl \ + --cn=CommonName \ + --c=BR \ + --ou=OrganizationUnit \ + --st=RioDeJaneiro \ + --o=LibreSign \ + --l=RioDeJaneiro + + occ theming:config name "LibreSign" + occ theming:config url "https://libresign.coop" + occ theming:config primary_color "#144042" + occ config:app:set libresign extra_settings --value=1 + occ config:system:set defaultapp --value libresign + occ maintenance:theme:update + + npm run dev + + environment_summary } -wait_for_nextcloud -add_safe_directory /var/www/html -add_safe_directory "$app_dir" - -cd "$app_dir" -git submodule update --init --recursive - -composer install --no-interaction -npm ci - -occ app:enable libresign -occ libresign:install --use-local-cert --java -occ libresign:install --use-local-cert --pdftk -occ libresign:install --use-local-cert --jsignpdf -occ libresign:configure:openssl \ - --cn=CommonName \ - --c=BR \ - --ou=OrganizationUnit \ - --st=RioDeJaneiro \ - --o=LibreSign \ - --l=RioDeJaneiro - -occ theming:config name "LibreSign" -occ theming:config url "https://libresign.coop" -occ theming:config primary_color "#144042" -occ config:app:set libresign extra_settings --value=1 -occ config:system:set defaultapp --value libresign -occ maintenance:theme:update - -npm run dev - -environment_summary +if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then + main "$@" +fi diff --git a/.devcontainer/tests/setup.bats b/.devcontainer/tests/setup.bats new file mode 100644 index 0000000000..5fafc3d31a --- /dev/null +++ b/.devcontainer/tests/setup.bats @@ -0,0 +1,30 @@ +#!/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)" + # shellcheck source=../setup.sh + source "$REPO_ROOT/.devcontainer/setup.sh" +} + +@test "environment summary shows the canonical Nextcloud URL and internal Mailpit endpoint" { + run env NEXTCLOUD_PROTOCOL=https NEXTCLOUD_HOST=ncdev-libresign-test.localhost COMPOSE_PROJECT_NAME=ncdev-libresign-test NEXTCLOUD_ADMIN_USER=admin VERSION_NEXTCLOUD=master bash -c 'source "$1"; environment_summary' _ "$REPO_ROOT/.devcontainer/setup.sh" + + [ "$status" -eq 0 ] + [[ "$output" == *"LibreSign development environment is ready."* ]] + [[ "$output" == *"Nextcloud / LibreSign: https://ncdev-libresign-test.localhost"* ]] + [[ "$output" == *"Mailpit (environment): http://mailpit:8025"* ]] + [[ "$output" == *"Mailpit (browser): https://ncdev-libresign-test-mailpit.localhost"* ]] + [[ "$output" == *"Nextcloud branch: master"* ]] + [[ "$output" == *"Useful commands:"* ]] +} + +@test "environment summary does not advertise a localhost Mailpit browser URL for a remote host" { + run env NEXTCLOUD_PROTOCOL=https NEXTCLOUD_HOST=example-443.app.github.dev COMPOSE_PROJECT_NAME=ncdev-libresign-test bash -c 'source "$1"; environment_summary' _ "$REPO_ROOT/.devcontainer/setup.sh" + + [ "$status" -eq 0 ] + [[ "$output" == *"Nextcloud / LibreSign: https://example-443.app.github.dev"* ]] + [[ "$output" == *"Mailpit (environment): http://mailpit:8025"* ]] + [[ "$output" != *"Mailpit (browser):"* ]] +} diff --git a/.github/workflows/devcontainer.yml b/.github/workflows/devcontainer.yml index e1406a409c..ab039b42f4 100644 --- a/.github/workflows/devcontainer.yml +++ b/.github/workflows/devcontainer.yml @@ -57,8 +57,10 @@ jobs: ((.portsAttributes // {} | keys[]) | contains(":")) ' .devcontainer/devcontainer.json - - name: Test devcontainer bootstrap contract - run: bats --print-output-on-failure .devcontainer/tests/prepare.bats + - name: Test devcontainer shell contracts + run: | + bats --print-output-on-failure .devcontainer/tests/prepare.bats + bats --print-output-on-failure .devcontainer/tests/setup.bats integration: name: Devcontainer integration @@ -93,7 +95,7 @@ jobs: grep -q 'LibreSign development environment is ready.' /tmp/libresign-setup.out grep -q 'Nextcloud / LibreSign:' /tmp/libresign-setup.out - grep -q 'Mailpit:' /tmp/libresign-setup.out + grep -q 'Mailpit (environment):' /tmp/libresign-setup.out grep -q 'Useful commands:' /tmp/libresign-setup.out - name: Verify Nextcloud through the NCDD proxy From 35ed3f18b8f4ee5e8cdd19eb201cb40c1eb6868a Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Mon, 5 Oct 2026 02:48:57 +0000 Subject: [PATCH 37/39] style: align devcontainer readiness banner with NCDD Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- .devcontainer/setup.sh | 30 ++++++++++++++++-------------- .devcontainer/tests/setup.bats | 16 ++++++++-------- 2 files changed, 24 insertions(+), 22 deletions(-) diff --git a/.devcontainer/setup.sh b/.devcontainer/setup.sh index 94287a2fee..2d418ea1e9 100644 --- a/.devcontainer/setup.sh +++ b/.devcontainer/setup.sh @@ -31,24 +31,26 @@ environment_summary() { local nextcloud_host="${NEXTCLOUD_HOST:-localhost}" printf '\n' - printf 'LibreSign development environment is ready.\n' - printf '\n' - printf ' Nextcloud / LibreSign: %s://%s\n' "$protocol" "$nextcloud_host" - printf ' Mailpit (environment): http://mailpit:8025\n' + printf '┌─ 💙 LibreSign environment ready ─────────────────────────\n' + printf '│\n' + printf '│ %-22s %s://%s\n' 'Nextcloud / LibreSign' "$protocol" "$nextcloud_host" + printf '│ %-22s %s\n' 'Mailpit (environment)' 'http://mailpit:8025' if [[ "$nextcloud_host" == *.localhost ]] && [[ -n "${COMPOSE_PROJECT_NAME:-}" ]]; then - printf ' Mailpit (browser): https://%s-mailpit.localhost\n' "$COMPOSE_PROJECT_NAME" + printf '│ %-22s https://%s-mailpit.localhost\n' 'Mailpit (browser)' "$COMPOSE_PROJECT_NAME" fi - printf ' Admin user: %s\n' "${NEXTCLOUD_ADMIN_USER:-admin}" - printf ' Nextcloud branch: %s\n' "${VERSION_NEXTCLOUD:-master}" - printf '\n' - printf 'Useful commands:\n' - printf ' npm run watch rebuild frontend assets while editing\n' - printf ' occ status check Nextcloud status\n' - printf ' occ app:list inspect enabled apps\n' - printf '\n' - printf 'The environment is ready for development.\n' + printf '│\n' + printf '│ %-22s %s\n' 'Admin user' "${NEXTCLOUD_ADMIN_USER:-admin}" + printf '│ %-22s %s\n' 'Nextcloud branch' "${VERSION_NEXTCLOUD:-master}" + printf '│\n' + printf '│ Useful commands\n' + printf '│ npm run watch rebuild frontend assets while editing\n' + printf '│ occ status check Nextcloud status\n' + printf '│ occ app:list inspect enabled apps\n' + printf '│\n' + printf '│ ✅ LibreSign is installed and ready for development.\n' + printf '└──────────────────────────────────────────────────────────\n' } main() { diff --git a/.devcontainer/tests/setup.bats b/.devcontainer/tests/setup.bats index 5fafc3d31a..5b0855112b 100644 --- a/.devcontainer/tests/setup.bats +++ b/.devcontainer/tests/setup.bats @@ -12,19 +12,19 @@ setup() { run env NEXTCLOUD_PROTOCOL=https NEXTCLOUD_HOST=ncdev-libresign-test.localhost COMPOSE_PROJECT_NAME=ncdev-libresign-test NEXTCLOUD_ADMIN_USER=admin VERSION_NEXTCLOUD=master bash -c 'source "$1"; environment_summary' _ "$REPO_ROOT/.devcontainer/setup.sh" [ "$status" -eq 0 ] - [[ "$output" == *"LibreSign development environment is ready."* ]] - [[ "$output" == *"Nextcloud / LibreSign: https://ncdev-libresign-test.localhost"* ]] - [[ "$output" == *"Mailpit (environment): http://mailpit:8025"* ]] - [[ "$output" == *"Mailpit (browser): https://ncdev-libresign-test-mailpit.localhost"* ]] - [[ "$output" == *"Nextcloud branch: master"* ]] - [[ "$output" == *"Useful commands:"* ]] + [[ "$output" == *"LibreSign environment ready"* ]] + [[ "$output" == *"Nextcloud / LibreSign"*"https://ncdev-libresign-test.localhost"* ]] + [[ "$output" == *"Mailpit (environment)"*"http://mailpit:8025"* ]] + [[ "$output" == *"Mailpit (browser)"*"https://ncdev-libresign-test-mailpit.localhost"* ]] + [[ "$output" == *"Nextcloud branch"*"master"* ]] + [[ "$output" == *"Useful commands"* ]] } @test "environment summary does not advertise a localhost Mailpit browser URL for a remote host" { run env NEXTCLOUD_PROTOCOL=https NEXTCLOUD_HOST=example-443.app.github.dev COMPOSE_PROJECT_NAME=ncdev-libresign-test bash -c 'source "$1"; environment_summary' _ "$REPO_ROOT/.devcontainer/setup.sh" [ "$status" -eq 0 ] - [[ "$output" == *"Nextcloud / LibreSign: https://example-443.app.github.dev"* ]] - [[ "$output" == *"Mailpit (environment): http://mailpit:8025"* ]] + [[ "$output" == *"Nextcloud / LibreSign"*"https://example-443.app.github.dev"* ]] + [[ "$output" == *"Mailpit (environment)"*"http://mailpit:8025"* ]] [[ "$output" != *"Mailpit (browser):"* ]] } From 2843e65c4a3116659dd3683361a7d71c77ea376b Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Mon, 5 Oct 2026 02:53:08 +0000 Subject: [PATCH 38/39] style: use LibreSign green in readiness banner Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- .devcontainer/setup.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.devcontainer/setup.sh b/.devcontainer/setup.sh index 2d418ea1e9..2cce444040 100644 --- a/.devcontainer/setup.sh +++ b/.devcontainer/setup.sh @@ -31,7 +31,7 @@ environment_summary() { local nextcloud_host="${NEXTCLOUD_HOST:-localhost}" printf '\n' - printf '┌─ 💙 LibreSign environment ready ─────────────────────────\n' + printf '┌─ 💚 LibreSign environment ready ─────────────────────────\n' printf '│\n' printf '│ %-22s %s://%s\n' 'Nextcloud / LibreSign' "$protocol" "$nextcloud_host" printf '│ %-22s %s\n' 'Mailpit (environment)' 'http://mailpit:8025' From 1297c796fe2549450e3b16875bccd68dc5d418ae Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Mon, 5 Oct 2026 02:58:41 +0000 Subject: [PATCH 39/39] test: align readiness assertions with the banner Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- .github/workflows/devcontainer.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/devcontainer.yml b/.github/workflows/devcontainer.yml index ab039b42f4..0ae8e4f18e 100644 --- a/.github/workflows/devcontainer.yml +++ b/.github/workflows/devcontainer.yml @@ -93,10 +93,10 @@ jobs: bash /var/www/html/apps-extra/libresign/.devcontainer/setup.sh \ | tee /tmp/libresign-setup.out - grep -q 'LibreSign development environment is ready.' /tmp/libresign-setup.out - grep -q 'Nextcloud / LibreSign:' /tmp/libresign-setup.out - grep -q 'Mailpit (environment):' /tmp/libresign-setup.out - grep -q 'Useful commands:' /tmp/libresign-setup.out + grep -q 'LibreSign environment ready' /tmp/libresign-setup.out + grep -q 'Nextcloud / LibreSign' /tmp/libresign-setup.out + grep -q 'Mailpit (environment)' /tmp/libresign-setup.out + grep -q 'Useful commands' /tmp/libresign-setup.out - name: Verify Nextcloud through the NCDD proxy run: |