From 5f83042317fdd7c95d275d0c8f4b4aceae9905c0 Mon Sep 17 00:00:00 2001 From: Jeppe Bundgaard Date: Tue, 21 Apr 2026 20:05:10 +0200 Subject: [PATCH 1/7] Use self-hosted runner for backend tests --- .github/workflows/tests.yml | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 7c44c003..dc16e91e 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -7,7 +7,8 @@ on: jobs: unit: name: Unit (required) - runs-on: ubuntu-latest + # Match the labels exposed by the Coolify-managed GitHub runner. + runs-on: [self-hosted, Linux, X64, default] steps: - name: Checkout @@ -53,7 +54,7 @@ jobs: edge-agent: name: Edge Agent (required) - runs-on: ubuntu-latest + runs-on: [self-hosted, Linux, X64, default] steps: - name: Checkout @@ -76,7 +77,7 @@ jobs: edge-broker: name: Edge Broker (required) - runs-on: ubuntu-latest + runs-on: [self-hosted, Linux, X64, default] steps: - name: Checkout @@ -121,7 +122,7 @@ jobs: integration: name: Integration (advisory) - runs-on: ubuntu-latest + runs-on: [self-hosted, Linux, X64, default] continue-on-error: true services: @@ -189,7 +190,7 @@ jobs: api: name: API (advisory) - runs-on: ubuntu-latest + runs-on: [self-hosted, Linux, X64, default] continue-on-error: true services: From 98b39439196e2c4f530b7460a307f821c727cb76 Mon Sep 17 00:00:00 2001 From: Jeppe Bundgaard Date: Tue, 21 Apr 2026 21:23:49 +0200 Subject: [PATCH 2/7] Restore edge broker compose defaults --- README.md | 4 ++-- docker-compose.yml | 31 +++++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index db36d3d2..9db033df 100644 --- a/README.md +++ b/README.md @@ -16,9 +16,9 @@ Backend API for Copenhagen Truck Wash services. - PHP 8.2 CLI (optional, for host-side testing) ### Local Development -To bring up the minimal development stack (Traefik, Redis, MySQL debug DB, Caddy, and one PHP worker): +To bring up the minimal development stack (Traefik, Redis, MySQL debug DB, Edge Broker, Caddy, and one PHP worker): ```powershell -docker compose up -d traefik redis mysql-debug php1 caddy +docker compose up -d traefik redis mysql-debug edge-broker php1 caddy ``` The API is accessible at: diff --git a/docker-compose.yml b/docker-compose.yml index 52a27f92..3f53f96c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -80,6 +80,16 @@ services: retries: 10 start_period: 20s + edge-broker: + build: + context: . + dockerfile: services/edge-broker/Dockerfile + container_name: edge-broker + environment: + EDGE_BROKER_SHARED_SECRET: ${EDGE_BROKER_SHARED_SECRET:-truckwash-edge-dev} + ports: + - "4300:4300" + caddy: image: caddy:2.7.6-alpine container_name: caddy @@ -196,11 +206,14 @@ services: container_name: php1 depends_on: - redis + - edge-broker command: ["php-fpm"] env_file: - .env environment: AUTO_COMPOSER_INSTALL: "true" + EDGE_BROKER_URL: ${EDGE_BROKER_URL:-http://edge-broker:4300} + EDGE_BROKER_SHARED_SECRET: ${EDGE_BROKER_SHARED_SECRET:-truckwash-edge-dev} volumes: - ./services/nginx/app:/var/www/html - ./services/php/php.ini:/usr/local/etc/php/conf.d/zz-custom.ini:ro @@ -213,11 +226,14 @@ services: container_name: php2 depends_on: - redis + - edge-broker command: ["php-fpm"] env_file: - .env environment: AUTO_COMPOSER_INSTALL: "false" + EDGE_BROKER_URL: ${EDGE_BROKER_URL:-http://edge-broker:4300} + EDGE_BROKER_SHARED_SECRET: ${EDGE_BROKER_SHARED_SECRET:-truckwash-edge-dev} volumes: - ./services/nginx/app:/var/www/html - ./services/php/php.ini:/usr/local/etc/php/conf.d/zz-custom.ini:ro @@ -230,11 +246,14 @@ services: container_name: php3 depends_on: - redis + - edge-broker command: ["php-fpm"] env_file: - .env environment: AUTO_COMPOSER_INSTALL: "false" + EDGE_BROKER_URL: ${EDGE_BROKER_URL:-http://edge-broker:4300} + EDGE_BROKER_SHARED_SECRET: ${EDGE_BROKER_SHARED_SECRET:-truckwash-edge-dev} volumes: - ./services/nginx/app:/var/www/html - ./services/php/php.ini:/usr/local/etc/php/conf.d/zz-custom.ini:ro @@ -247,11 +266,14 @@ services: container_name: php4 depends_on: - redis + - edge-broker command: ["php-fpm"] env_file: - .env environment: AUTO_COMPOSER_INSTALL: "false" + EDGE_BROKER_URL: ${EDGE_BROKER_URL:-http://edge-broker:4300} + EDGE_BROKER_SHARED_SECRET: ${EDGE_BROKER_SHARED_SECRET:-truckwash-edge-dev} volumes: - ./services/nginx/app:/var/www/html - ./services/php/php.ini:/usr/local/etc/php/conf.d/zz-custom.ini:ro @@ -264,11 +286,14 @@ services: container_name: php5 depends_on: - redis + - edge-broker command: ["php-fpm"] env_file: - .env environment: AUTO_COMPOSER_INSTALL: "false" + EDGE_BROKER_URL: ${EDGE_BROKER_URL:-http://edge-broker:4300} + EDGE_BROKER_SHARED_SECRET: ${EDGE_BROKER_SHARED_SECRET:-truckwash-edge-dev} volumes: - ./services/nginx/app:/var/www/html - ./services/php/php.ini:/usr/local/etc/php/conf.d/zz-custom.ini:ro @@ -281,11 +306,14 @@ services: container_name: php-staging depends_on: - redis-staging + - edge-broker command: ["php-fpm"] env_file: - .env.staging environment: AUTO_COMPOSER_INSTALL: "false" + EDGE_BROKER_URL: ${EDGE_BROKER_URL:-http://edge-broker:4300} + EDGE_BROKER_SHARED_SECRET: ${EDGE_BROKER_SHARED_SECRET:-truckwash-edge-dev} volumes: - ./services/nginx/app:/var/www/html - ./services/php/php.ini:/usr/local/etc/php/conf.d/zz-custom.ini:ro @@ -298,11 +326,14 @@ services: container_name: php-cron depends_on: - redis + - edge-broker command: ["sh", "-c", "while true; do php index.php run cron; sleep 60; done"] env_file: - .env environment: AUTO_COMPOSER_INSTALL: "false" + EDGE_BROKER_URL: ${EDGE_BROKER_URL:-http://edge-broker:4300} + EDGE_BROKER_SHARED_SECRET: ${EDGE_BROKER_SHARED_SECRET:-truckwash-edge-dev} volumes: - ./services/nginx/app:/var/www/html - ./services/php/php.ini:/usr/local/etc/php/conf.d/zz-custom.ini:ro From 837c49a5895549d6ab232c273a1a791d52c8e911 Mon Sep 17 00:00:00 2001 From: Jeppe Bundgaard Date: Tue, 21 Apr 2026 21:31:18 +0200 Subject: [PATCH 3/7] Configure setup-php for self-hosted runner --- .github/workflows/tests.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index dc16e91e..8a4d4074 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -29,6 +29,8 @@ jobs: extensions: mysqli, curl, openssl, json, redis, xdebug coverage: xdebug ini-values: variables_order=EGPCS,xdebug.mode=coverage + env: + runner: self-hosted - name: Resolve dependencies working-directory: services/nginx/app @@ -153,6 +155,8 @@ jobs: php-version: '8.2' extensions: mysqli, curl, openssl, json, redis ini-values: variables_order=EGPCS + env: + runner: self-hosted - name: Resolve dependencies working-directory: services/nginx/app @@ -221,6 +225,8 @@ jobs: php-version: '8.2' extensions: mysqli, curl, openssl, json, redis ini-values: variables_order=EGPCS + env: + runner: self-hosted - name: Resolve dependencies working-directory: services/nginx/app From e4fefab576699e3a8439ec745b87e86ae56a9ce2 Mon Sep 17 00:00:00 2001 From: Jeppe Bundgaard Date: Tue, 21 Apr 2026 21:36:06 +0200 Subject: [PATCH 4/7] Run PHP jobs in setup-php container image --- .github/workflows/tests.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 8a4d4074..cf8f5361 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -9,6 +9,7 @@ jobs: name: Unit (required) # Match the labels exposed by the Coolify-managed GitHub runner. runs-on: [self-hosted, Linux, X64, default] + container: shivammathur/node:php-8.2-24.04-amd64 steps: - name: Checkout @@ -125,6 +126,7 @@ jobs: integration: name: Integration (advisory) runs-on: [self-hosted, Linux, X64, default] + container: shivammathur/node:php-8.2-24.04-amd64 continue-on-error: true services: @@ -195,6 +197,7 @@ jobs: api: name: API (advisory) runs-on: [self-hosted, Linux, X64, default] + container: shivammathur/node:php-8.2-24.04-amd64 continue-on-error: true services: From 7c7895f776a2c7666fd38a2f51cc3ed61ff5e3e4 Mon Sep 17 00:00:00 2001 From: Jeppe Bundgaard Date: Tue, 21 Apr 2026 21:46:39 +0200 Subject: [PATCH 5/7] Restore edge agent shell polling --- services/edge-agent/dist/agent.mjs | 51 ++++++++++++++++++++++++------ 1 file changed, 41 insertions(+), 10 deletions(-) diff --git a/services/edge-agent/dist/agent.mjs b/services/edge-agent/dist/agent.mjs index cb3cd5cf..317c3059 100644 --- a/services/edge-agent/dist/agent.mjs +++ b/services/edge-agent/dist/agent.mjs @@ -137,6 +137,7 @@ function buildTransportHeartbeatState(brokerState = {}) { status: "ONLINE", metadata: { command_transport: brokerConnected ? "BROKER_FAST_PATH" : "API_POLLING", + shell_transport: brokerConnected ? "BROKER_FAST_PATH" : "API_POLLING", broker_connected: brokerConnected, broker_url: brokerState.url || null, broker_last_error: brokerState.lastError || null, @@ -1761,22 +1762,22 @@ export async function startAgent({ }); config.configPath = configPath; const intervalMs = Number(config.heartbeatIntervalSeconds || 15) * 1000; - const commandPollTimeoutSeconds = Number(config.commandPollTimeoutSeconds || 20); - const commandPollRetryDelayMs = Number(config.commandPollRetryDelayMs || 1000); + const commandPollTimeoutSeconds = Number(config.commandPollTimeoutSeconds ?? 20); + const commandPollRetryDelayMs = Number(config.commandPollRetryDelayMs ?? 1000); + const shellActionPollTimeoutSeconds = Number(config.shellActionPollTimeoutSeconds ?? 20); + const shellActionPollRetryDelayMs = Number(config.shellActionPollRetryDelayMs ?? 1000); const brokerReconnectDelayMs = Number(config.brokerReconnectDelayMs || DEFAULT_BROKER_RECONNECT_DELAY_MS); let stopped = false; let cpuSnapshot = null; let lastHeartbeatLatencyMs = null; - void createShellBridgeImpl; let brokerBridge = null; - const shell = { - open: async () => {}, - input: () => {}, - resize: () => {}, - close: () => {}, - dispose: () => {}, + const shellEventPublisher = createShellEventPublisher(config, fetchImpl); + const sendShellMessage = (message) => { + shellEventPublisher.publish(message); + brokerBridge?.send(message); }; + const shell = createShellBridgeImpl(sendShellMessage); brokerBridge = createBrokerBridge({ config, shell, @@ -1841,8 +1842,37 @@ export async function startAgent({ } }; + const runShellActionPollLoop = async () => { + while (!stopped) { + try { + if (brokerBridge?.state?.connected) { + await new Promise((resolve) => setTimeout(resolve, shellActionPollRetryDelayMs)); + continue; + } + + const action = await pollShellActionJob(config, fetchImpl, shellActionPollTimeoutSeconds); + if (stopped) { + break; + } + + if (!action) { + continue; + } + + await processPolledShellAction(config, action, shell, fetchImpl); + } catch { + if (stopped) { + break; + } + + await new Promise((resolve) => setTimeout(resolve, shellActionPollRetryDelayMs)); + } + } + }; + await sendTransportHeartbeat(); const commandPollPromise = runCommandPollLoop(); + const shellActionPollPromise = runShellActionPollLoop(); const timer = setInterval(() => { sendTransportHeartbeat().catch(() => {}); @@ -1854,7 +1884,8 @@ export async function startAgent({ clearInterval(timer); brokerBridge?.stop(); shell.dispose(); - await Promise.allSettled([commandPollPromise]); + await Promise.allSettled([commandPollPromise, shellActionPollPromise]); + await shellEventPublisher.drain(); }; return { From d30393b6099e6f9824b6ade8296922d04bfa5508 Mon Sep 17 00:00:00 2001 From: Jeppe Bundgaard Date: Tue, 21 Apr 2026 22:00:03 +0200 Subject: [PATCH 6/7] Run PHP suites in php1 on CI --- .github/workflows/tests.yml | 193 ++++++++++-------------------------- scripts/ci/run-php-suite.sh | 163 ++++++++++++++++++++++++++++++ services/php/Dockerfile | 4 +- 3 files changed, 219 insertions(+), 141 deletions(-) create mode 100755 scripts/ci/run-php-suite.sh diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index cf8f5361..71286101 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -9,7 +9,6 @@ jobs: name: Unit (required) # Match the labels exposed by the Coolify-managed GitHub runner. runs-on: [self-hosted, Linux, X64, default] - container: shivammathur/node:php-8.2-24.04-amd64 steps: - name: Checkout @@ -23,27 +22,26 @@ jobs: - name: Check AI workflow sync run: node scripts/sync-ai-workflow.mjs --check - - name: Setup PHP - uses: shivammathur/setup-php@v2 - with: - php-version: '8.2' - extensions: mysqli, curl, openssl, json, redis, xdebug - coverage: xdebug - ini-values: variables_order=EGPCS,xdebug.mode=coverage + - name: Materialize compose env files env: - runner: self-hosted + COMPOSE_ENV: ${{ secrets.COMPOSE_ENV }} + COMPOSE_ENV_STAGING: ${{ secrets.COMPOSE_ENV_STAGING }} + run: | + set -euo pipefail + if [ -z "${COMPOSE_ENV}" ]; then + echo "Required GitHub secret COMPOSE_ENV is not configured." >&2 + exit 1 + fi + if [ -z "${COMPOSE_ENV_STAGING}" ]; then + echo "Required GitHub secret COMPOSE_ENV_STAGING is not configured." >&2 + exit 1 + fi + printf '%s\n' "$COMPOSE_ENV" > .env + printf '%s\n' "$COMPOSE_ENV_STAGING" > .env.staging + chmod +x scripts/ci/run-php-suite.sh - - name: Resolve dependencies - working-directory: services/nginx/app - run: composer update --no-interaction --prefer-dist - - - name: Run unit tests - working-directory: services/nginx/app - run: composer test:unit - - - name: Generate coverage report - working-directory: services/nginx/app - run: composer test:coverage + - name: Run unit suite in php1 + run: scripts/ci/run-php-suite.sh unit - name: Upload coverage artifact if: ${{ github.event_name == 'pull_request' }} @@ -126,142 +124,59 @@ jobs: integration: name: Integration (advisory) runs-on: [self-hosted, Linux, X64, default] - container: shivammathur/node:php-8.2-24.04-amd64 continue-on-error: true - services: - redis: - image: redis:7 - ports: - - 6379:6379 - mysql: - image: mysql:8 - env: - MYSQL_DATABASE: app_test - MYSQL_ROOT_PASSWORD: root - ports: - - 3306:3306 - options: >- - --health-cmd="mysqladmin ping -h 127.0.0.1 -proot" - --health-interval=10s - --health-timeout=5s - --health-retries=10 - steps: - name: Checkout uses: actions/checkout@v4 - - name: Setup PHP - uses: shivammathur/setup-php@v2 - with: - php-version: '8.2' - extensions: mysqli, curl, openssl, json, redis - ini-values: variables_order=EGPCS + - name: Materialize compose env files env: - runner: self-hosted + COMPOSE_ENV: ${{ secrets.COMPOSE_ENV }} + COMPOSE_ENV_STAGING: ${{ secrets.COMPOSE_ENV_STAGING }} + run: | + set -euo pipefail + if [ -z "${COMPOSE_ENV}" ]; then + echo "Required GitHub secret COMPOSE_ENV is not configured." >&2 + exit 1 + fi + if [ -z "${COMPOSE_ENV_STAGING}" ]; then + echo "Required GitHub secret COMPOSE_ENV_STAGING is not configured." >&2 + exit 1 + fi + printf '%s\n' "$COMPOSE_ENV" > .env + printf '%s\n' "$COMPOSE_ENV_STAGING" > .env.staging + chmod +x scripts/ci/run-php-suite.sh - - name: Resolve dependencies - working-directory: services/nginx/app - run: composer update --no-interaction --prefer-dist - - - name: Run integration tests - working-directory: services/nginx/app - env: - RUN_INTEGRATION_TESTS: '1' - USE_ENV: 'true' - DEBUG: '0' - ENCRYPTION_KEY: test-key - CORS: '*' - CONFIG_TIMEZONE: Europe/Copenhagen - ECONOMIC_API_APP_ACCESS_GRANT: test - ECONOMIC_API_APP_ACCESS_GRANT2: test - ECONOMIC_API_APP_SECRET_TOKEN: test - WORDPRESS_STATIC_TOKEN: '' - EMAIL_WASH_CERTIFICATE_TOKEN: '' - WORDPRESS_API_URL: http://localhost - MINIO_ENDPOINT: '' - MINIO_ACCESS_KEY: '' - MINIO_SECRET_KEY: '' - SLACK_DEFAULT_WEBHOOK: '' - REDIS_CONFIG_HOST: 127.0.0.1 - REDIS_CONFIG_DATABASE: '0' - REDIS_CONFIG_PASSWORD: '' - REDIS_CONFIG_PORT: '6379' - CONFIG_DB_HOST: 127.0.0.1 - CONFIG_DB_USER: root - CONFIG_DB_PASSWORD: root - CONFIG_DB_DATABASE: app_test - CONFIG_DB_PORT: '3306' - run: composer test:integration + - name: Run integration suite in php1 + run: scripts/ci/run-php-suite.sh integration api: name: API (advisory) runs-on: [self-hosted, Linux, X64, default] - container: shivammathur/node:php-8.2-24.04-amd64 continue-on-error: true - services: - redis: - image: redis:7 - ports: - - 6379:6379 - mysql: - image: mysql:8 - env: - MYSQL_DATABASE: app_test - MYSQL_ROOT_PASSWORD: root - ports: - - 3306:3306 - options: >- - --health-cmd="mysqladmin ping -h 127.0.0.1 -proot" - --health-interval=10s - --health-timeout=5s - --health-retries=10 - steps: - name: Checkout uses: actions/checkout@v4 - - name: Setup PHP - uses: shivammathur/setup-php@v2 - with: - php-version: '8.2' - extensions: mysqli, curl, openssl, json, redis - ini-values: variables_order=EGPCS + - name: Materialize compose env files env: - runner: self-hosted + COMPOSE_ENV: ${{ secrets.COMPOSE_ENV }} + COMPOSE_ENV_STAGING: ${{ secrets.COMPOSE_ENV_STAGING }} + run: | + set -euo pipefail + if [ -z "${COMPOSE_ENV}" ]; then + echo "Required GitHub secret COMPOSE_ENV is not configured." >&2 + exit 1 + fi + if [ -z "${COMPOSE_ENV_STAGING}" ]; then + echo "Required GitHub secret COMPOSE_ENV_STAGING is not configured." >&2 + exit 1 + fi + printf '%s\n' "$COMPOSE_ENV" > .env + printf '%s\n' "$COMPOSE_ENV_STAGING" > .env.staging + chmod +x scripts/ci/run-php-suite.sh - - name: Resolve dependencies - working-directory: services/nginx/app - run: composer update --no-interaction --prefer-dist - - - name: Run API tests - working-directory: services/nginx/app - env: - RUN_API_TESTS: '1' - API_TEST_BOOTSTRAP_SCHEMA: '1' - USE_ENV: 'true' - DEBUG: '0' - ENCRYPTION_KEY: test-key - CORS: '*' - CONFIG_TIMEZONE: Europe/Copenhagen - ECONOMIC_API_APP_ACCESS_GRANT: test - ECONOMIC_API_APP_ACCESS_GRANT2: test - ECONOMIC_API_APP_SECRET_TOKEN: test - WORDPRESS_STATIC_TOKEN: '' - EMAIL_WASH_CERTIFICATE_TOKEN: '' - WORDPRESS_API_URL: http://localhost - MINIO_ENDPOINT: '' - MINIO_ACCESS_KEY: '' - MINIO_SECRET_KEY: '' - SLACK_DEFAULT_WEBHOOK: '' - REDIS_CONFIG_HOST: 127.0.0.1 - REDIS_CONFIG_DATABASE: '0' - REDIS_CONFIG_PASSWORD: '' - REDIS_CONFIG_PORT: '6379' - CONFIG_DB_HOST: 127.0.0.1 - CONFIG_DB_USER: root - CONFIG_DB_PASSWORD: root - CONFIG_DB_DATABASE: app_test - CONFIG_DB_PORT: '3306' - run: composer test:api + - name: Run API suite in php1 + run: scripts/ci/run-php-suite.sh api diff --git a/scripts/ci/run-php-suite.sh b/scripts/ci/run-php-suite.sh new file mode 100755 index 00000000..2015f790 --- /dev/null +++ b/scripts/ci/run-php-suite.sh @@ -0,0 +1,163 @@ +#!/usr/bin/env bash +set -euo pipefail + +suite="${1:-}" + +if [[ -z "$suite" ]]; then + echo "Usage: $0 " >&2 + exit 1 +fi + +repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +cd "$repo_root" + +if [[ -f .env ]]; then + set -a + # shellcheck disable=SC1091 + . ./.env + set +a +fi + +cleanup() { + docker compose down -v --remove-orphans || true +} + +wait_for_container_running() { + local name="$1" + local status="" + + for _ in $(seq 1 90); do + status="$(docker inspect -f '{{.State.Status}}' "$name" 2>/dev/null || true)" + if [[ "$status" == "running" ]]; then + return 0 + fi + sleep 1 + done + + docker logs "$name" || true + echo "Container $name did not reach running state." >&2 + exit 1 +} + +wait_for_container_healthy() { + local name="$1" + local status="" + + for _ in $(seq 1 90); do + status="$(docker inspect -f '{{if .State.Health}}{{.State.Health.Status}}{{else}}{{.State.Status}}{{end}}' "$name" 2>/dev/null || true)" + if [[ "$status" == "healthy" || "$status" == "running" ]]; then + return 0 + fi + sleep 2 + done + + docker logs "$name" || true + echo "Container $name did not reach a healthy state." >&2 + exit 1 +} + +docker_exec_php1() { + docker exec "$@" php1 sh -lc "cd /var/www/html && ${PHP_COMMAND}" +} + +run_php_command() { + local -a env_args=() + while [[ $# -gt 0 ]]; do + env_args+=(-e "$1") + shift + done + + docker exec "${env_args[@]}" php1 sh -lc "cd /var/www/html && ${PHP_COMMAND}" +} + +docker compose down -v --remove-orphans || true +trap cleanup EXIT + +services=(redis edge-broker php1) +if [[ "$suite" != "unit" ]]; then + services+=(mysql-debug) +fi + +docker compose up -d --build "${services[@]}" +wait_for_container_running php1 +wait_for_container_healthy redis +if [[ "$suite" != "unit" ]]; then + wait_for_container_healthy mysql-debug +fi + +PHP_COMMAND="composer install --no-interaction --prefer-dist" +docker_exec_php1 + +case "$suite" in + unit) + PHP_COMMAND="composer test:unit" + docker_exec_php1 + PHP_COMMAND="composer test:coverage" + docker_exec_php1 + ;; + integration) + PHP_COMMAND="composer test:integration" + run_php_command \ + "RUN_INTEGRATION_TESTS=1" \ + "USE_ENV=true" \ + "DEBUG=0" \ + "ENCRYPTION_KEY=test-key" \ + "CORS=*" \ + "CONFIG_TIMEZONE=Europe/Copenhagen" \ + "ECONOMIC_API_APP_ACCESS_GRANT=test" \ + "ECONOMIC_API_APP_ACCESS_GRANT2=test" \ + "ECONOMIC_API_APP_SECRET_TOKEN=test" \ + "WORDPRESS_STATIC_TOKEN=" \ + "EMAIL_WASH_CERTIFICATE_TOKEN=" \ + "WORDPRESS_API_URL=http://localhost" \ + "MINIO_ENDPOINT=" \ + "MINIO_ACCESS_KEY=" \ + "MINIO_SECRET_KEY=" \ + "SLACK_DEFAULT_WEBHOOK=" \ + "REDIS_CONFIG_HOST=redis" \ + "REDIS_CONFIG_DATABASE=0" \ + "REDIS_CONFIG_PASSWORD=" \ + "REDIS_CONFIG_PORT=6379" \ + "CONFIG_DB_TARGET=debug" \ + "CONFIG_DB_DEBUG_HOST=mysql-debug" \ + "CONFIG_DB_DEBUG_USER=root" \ + "CONFIG_DB_DEBUG_PASSWORD=${CONFIG_DB_DEBUG_PASSWORD:-debug_root_password}" \ + "CONFIG_DB_DEBUG_DATABASE=${CONFIG_DB_DEBUG_DATABASE:-nnks_db_debug}" \ + "CONFIG_DB_DEBUG_PORT=3306" + ;; + api) + PHP_COMMAND="composer test:api" + run_php_command \ + "RUN_API_TESTS=1" \ + "API_TEST_BOOTSTRAP_SCHEMA=1" \ + "USE_ENV=true" \ + "DEBUG=0" \ + "ENCRYPTION_KEY=test-key" \ + "CORS=*" \ + "CONFIG_TIMEZONE=Europe/Copenhagen" \ + "ECONOMIC_API_APP_ACCESS_GRANT=test" \ + "ECONOMIC_API_APP_ACCESS_GRANT2=test" \ + "ECONOMIC_API_APP_SECRET_TOKEN=test" \ + "WORDPRESS_STATIC_TOKEN=" \ + "EMAIL_WASH_CERTIFICATE_TOKEN=" \ + "WORDPRESS_API_URL=http://localhost" \ + "MINIO_ENDPOINT=" \ + "MINIO_ACCESS_KEY=" \ + "MINIO_SECRET_KEY=" \ + "SLACK_DEFAULT_WEBHOOK=" \ + "REDIS_CONFIG_HOST=redis" \ + "REDIS_CONFIG_DATABASE=0" \ + "REDIS_CONFIG_PASSWORD=" \ + "REDIS_CONFIG_PORT=6379" \ + "CONFIG_DB_TARGET=debug" \ + "CONFIG_DB_DEBUG_HOST=mysql-debug" \ + "CONFIG_DB_DEBUG_USER=root" \ + "CONFIG_DB_DEBUG_PASSWORD=${CONFIG_DB_DEBUG_PASSWORD:-debug_root_password}" \ + "CONFIG_DB_DEBUG_DATABASE=${CONFIG_DB_DEBUG_DATABASE:-nnks_db_debug}" \ + "CONFIG_DB_DEBUG_PORT=3306" + ;; + *) + echo "Unsupported suite: $suite" >&2 + exit 1 + ;; +esac diff --git a/services/php/Dockerfile b/services/php/Dockerfile index 2ad44be7..9d02d90c 100644 --- a/services/php/Dockerfile +++ b/services/php/Dockerfile @@ -43,8 +43,8 @@ RUN set -eux; \ pdo_mysql \ mysqli \ zip; \ - pecl install imagick-3.7.0; \ - docker-php-ext-enable imagick; \ + pecl install imagick-3.7.0 redis xdebug; \ + docker-php-ext-enable imagick redis xdebug; \ apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false $PHPIZE_DEPS; \ rm -rf /var/lib/apt/lists/* From a842edea97b30848c39894a268ea01edd9adafb3 Mon Sep 17 00:00:00 2001 From: Jeppe Bundgaard Date: Tue, 21 Apr 2026 22:11:06 +0200 Subject: [PATCH 7/7] Sync PHP app into php1 on CI runner --- docker-compose.yml | 2 +- scripts/ci/run-php-suite.sh | 25 +++++++++++++++++++++++-- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 3f53f96c..5b220835 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -211,7 +211,7 @@ services: env_file: - .env environment: - AUTO_COMPOSER_INSTALL: "true" + AUTO_COMPOSER_INSTALL: "${AUTO_COMPOSER_INSTALL:-true}" EDGE_BROKER_URL: ${EDGE_BROKER_URL:-http://edge-broker:4300} EDGE_BROKER_SHARED_SECRET: ${EDGE_BROKER_SHARED_SECRET:-truckwash-edge-dev} volumes: diff --git a/scripts/ci/run-php-suite.sh b/scripts/ci/run-php-suite.sh index 2015f790..a0793cb0 100755 --- a/scripts/ci/run-php-suite.sh +++ b/scripts/ci/run-php-suite.sh @@ -18,6 +18,8 @@ if [[ -f .env ]]; then set +a fi +export AUTO_COMPOSER_INSTALL=false + cleanup() { docker compose down -v --remove-orphans || true } @@ -70,6 +72,23 @@ run_php_command() { docker exec "${env_args[@]}" php1 sh -lc "cd /var/www/html && ${PHP_COMMAND}" } +sync_php_app_into_container() { + docker exec php1 sh -lc 'mkdir -p /var/www/html && find /var/www/html -mindepth 1 -maxdepth 1 -exec rm -rf {} +' + docker cp "${repo_root}/services/nginx/app/." php1:/var/www/html/ +} + +install_php_dependencies() { + PHP_COMMAND="composer install --no-interaction --prefer-dist" + docker_exec_php1 + docker exec php1 sh -lc ' + module_dir="/var/www/html/modules/washcertificates" + if [ -f "$module_dir/composer.json" ]; then + cd "$module_dir" + composer install --no-interaction --prefer-dist + fi + ' +} + docker compose down -v --remove-orphans || true trap cleanup EXIT @@ -85,8 +104,8 @@ if [[ "$suite" != "unit" ]]; then wait_for_container_healthy mysql-debug fi -PHP_COMMAND="composer install --no-interaction --prefer-dist" -docker_exec_php1 +sync_php_app_into_container +install_php_dependencies case "$suite" in unit) @@ -94,6 +113,8 @@ case "$suite" in docker_exec_php1 PHP_COMMAND="composer test:coverage" docker_exec_php1 + mkdir -p "${repo_root}/services/nginx/app/build/logs" + docker cp php1:/var/www/html/build/logs/clover.xml "${repo_root}/services/nginx/app/build/logs/clover.xml" 2>/dev/null || true ;; integration) PHP_COMMAND="composer test:integration"