diff --git a/.devcontainer/README.md b/.devcontainer/README.md index 7e9d83dea1..3863fc88e8 100644 --- a/.devcontainer/README.md +++ b/.devcontainer/README.md @@ -1,47 +1,68 @@ -## 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 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`. -```bash -docker stop $(docker ps -aq) -docker rm $(docker ps -aq) -``` -## Cleaning all volumes +A deterministic worker id is derived from the local worktree path, so separate +worktrees resolve to separate NCDD mutable-state directories. -This will be necessary when you want to have a new clean environment +## Browser access -```bash -docker volume rm $(docker volume ls -q ) +For local Docker/VS Code development, use NCDD's shared HTTPS proxy. The setup +script prints the canonical URL: + +```text +https://ncdev-libresign-.localhost ``` -## Looking logs -### Nginx +For GitHub Codespaces, the same shared proxy remains the only HTTP entry point. +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`: -```bash -docker logs libresign_devcontainer-nginx-1 -f --tail 100 +```text +https://-443. ``` -### Nextcloud +The forwarding domain comes from +`GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN`; it is not hard-coded. + +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. -- Open the console inside vscode -- Run: - ```bash - tail -f data/nextcloud.log - ``` +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 +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: + +```bash +export LIBRESIGN_PHP_VERSION=83 +export LIBRESIGN_NEXTCLOUD_VERSION=master +export LIBRESIGN_DB_TYPE=mariadb +export LIBRESIGN_MARIADB_VERSION=10.6 +``` -### 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..6ee959c75e 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,21 +1,20 @@ { "name": "LibreSign", - "dockerComposeFile": "docker-compose.yml", - "service": "nextcloud", - "forwardPorts": [ - 80, - 8025 + "initializeCommand": [ + "sh", + ".devcontainer/prepare.sh" + ], + "dockerComposeFile": [ + "ncdd.generated.yml", + "docker-compose.yml" ], + "service": "nextcloud", + "workspaceFolder": "/var/www/html/apps-extra/libresign", "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,31 +38,19 @@ ], "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 + "443": { + "label": "Nextcloud via NCDD proxy", + "protocol": "https", + "onAutoForward": "openBrowser" }, "9000": { "label": "php-fpm", diff --git a/.devcontainer/docker-compose.yml b/.devcontainer/docker-compose.yml index a896ef003f..282a892365 100644 --- a/.devcontainer/docker-compose.yml +++ b/.devcontainer/docker-compose.yml @@ -1,80 +1,13 @@ -# SPDX-FileCopyrightText: 2024 LibreCode coop and contributors +# SPDX-FileCopyrightText: 2026 LibreCode coop and contributors # SPDX-License-Identifier: AGPL-3.0-or-later + 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..1b99fb908b --- /dev/null +++ b/.devcontainer/prepare.sh @@ -0,0 +1,63 @@ +#!/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" +generated_compose="$repo_root/.devcontainer/ncdd.generated.yml" +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" + fi + + 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 + + git -C "$ncdd_dir" checkout --detach "$ncdd_commit" +} + +worker_id() { + workspace_key="$(printf '%s' "$repo_root" | cksum | awk '{print $1}')" + printf 'libresign-%s\n' "$workspace_key" +} + +render_compose() { + id="$1" + tmp="$generated_compose.tmp.$$" + trap 'rm -f "$tmp"' EXIT HUP INT TERM + + 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 + fi + + "$@" sh "$ncdd_dir/dev-worker" "$id" config > "$tmp" + mv "$tmp" "$generated_compose" + trap - EXIT HUP INT TERM +} + +prepare_ncdd +render_compose "$(worker_id)" diff --git a/.devcontainer/setup.sh b/.devcontainer/setup.sh index 8edf8e833f..2cce444040 100644 --- a/.devcontainer/setup.sh +++ b/.devcontainer/setup.sh @@ -1,35 +1,93 @@ #!/bin/bash -# -# SPDX-FileCopyrightText: 2024 LibreCode coop and contributors +# SPDX-FileCopyrightText: 2024-2026 LibreCode coop and contributors # SPDX-License-Identifier: AGPL-3.0-or-later -# -( - . /var/www/scripts/entrypoint.sh && php-fpm --daemonize +set -Eeo pipefail -) +app_dir=/var/www/html/apps-extra/libresign + +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 + + echo "Nextcloud did not become ready within 120 seconds." >&2 + 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 +} + +environment_summary() { + local protocol="${NEXTCLOUD_PROTOCOL:-https}" + local nextcloud_host="${NEXTCLOUD_HOST:-localhost}" + + printf '\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 '│ %-22s https://%s-mailpit.localhost\n' 'Mailpit (browser)' "$COMPOSE_PROJECT_NAME" + fi + + 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() { + 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 -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 submodule update --init --recursive -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 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 + + environment_summary +} + +if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then + main "$@" fi -echo "✍️ LibreSign is up!" -echo "If you want to develop at frontend, run the command 'npm run watch'" diff --git a/.devcontainer/tests/playwright-smoke.cjs b/.devcontainer/tests/playwright-smoke.cjs new file mode 100644 index 0000000000..ece16e1341 --- /dev/null +++ b/.devcontainer/tests/playwright-smoke.cjs @@ -0,0 +1,41 @@ +/** + * SPDX-FileCopyrightText: 2026 LibreCode coop and contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +const { chromium } = require('playwright') + +async function main() { + 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() + const response = await page.goto(target, { + waitUntil: 'domcontentloaded', + timeout: 10000, + }) + + 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 browser smoke passed:', response.url()) + } finally { + await browser.close() + } +} + +main().catch((error) => { + console.error(error) + process.exit(1) +}) diff --git a/.devcontainer/tests/prepare.bats b/.devcontainer/tests/prepare.bats new file mode 100644 index 0000000000..b0fc2d3046 --- /dev/null +++ b/.devcontainer/tests/prepare.bats @@ -0,0 +1,118 @@ +#!/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" +} + +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 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" + [ "$status" -eq 0 ] + + 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 + | test("^ncdev-.*\\.localhost$")' <<<"$config" + [ "$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 ] + + config="$(compose_json)" + + run jq -e \ + '.services.nextcloud.environment.DB_TYPE == "sqlite"' <<<"$config" + [ "$status" -eq 0 ] + + run jq -e \ + '.services.nextcloud.environment.DB_DRIVER == "sqlite"' <<<"$config" + [ "$status" -eq 0 ] + + run jq -e \ + '.services.nextcloud.environment.VERSION_NEXTCLOUD == "stable35"' <<<"$config" + [ "$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" + + [ "$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 ] + + config="$(compose_json)" + + 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" + [ "$status" -eq 0 ] +} diff --git a/.devcontainer/tests/setup.bats b/.devcontainer/tests/setup.bats new file mode 100644 index 0000000000..5b0855112b --- /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 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" != *"Mailpit (browser):"* ]] +} diff --git a/.github/workflows/devcontainer.yml b/.github/workflows/devcontainer.yml new file mode 100644 index 0000000000..0ae8e4f18e --- /dev/null +++ b/.github/workflows/devcontainer.yml @@ -0,0 +1,127 @@ +# SPDX-FileCopyrightText: 2026 LibreCode coop and contributors +# SPDX-License-Identifier: AGPL-3.0-or-later + +name: Devcontainer + +on: + pull_request: + paths: + - '.devcontainer/**' + - '.github/workflows/devcontainer.yml' + push: + branches: + - main + 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: + contract: + name: Devcontainer contract + runs-on: ubuntu-latest + 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 + with: + support-install: false + assert-install: false + detik-install: false + file-install: false + + - 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 + node --check .devcontainer/tests/playwright-smoke.cjs + + ! jq -e ' + ((.forwardPorts // [])[]? | strings | contains(":")) or + ((.portsAttributes // {} | keys[]) | contains(":")) + ' .devcontainer/devcontainer.json + + - 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 + needs: contract + runs-on: ubuntu-latest + 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}" + + - name: Start the Dev Container + run: devcontainer up --workspace-folder . + + - name: Verify Nextcloud, LibreSign and Mailpit + run: | + devcontainer exec --workspace-folder . \ + bash -lc "occ status | grep -q 'installed: true'" + devcontainer exec --workspace-folder . \ + bash -lc "occ app:list --enabled | grep -q libresign" + 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 \ + | tee /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: | + 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: Verify Playwright browser against Nextcloud + run: | + docker compose \ + --file .devcontainer/ncdd.generated.yml \ + --file .devcontainer/docker-compose.yml \ + --profile playwright \ + run --rm --no-deps playwright \ + node /var/www/html/apps-extra/libresign/.devcontainer/tests/playwright-smoke.cjs + + - name: Collect container diagnostics + if: failure() + run: | + 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 diff --git a/.gitignore b/.gitignore index 47d4670308..b74af9291c 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,8 @@ .idea .vscode .ai-local/ +.devcontainer/.nextcloud-docker-development/ +.devcontainer/ncdd.generated.yml .phpactor* .env .secrets