From 4239177563755719cb0f99da65cad4e163a72b23 Mon Sep 17 00:00:00 2001 From: Lang <7system7@gmail.com> Date: Mon, 13 Apr 2026 11:56:50 +0200 Subject: [PATCH] chg: pkg: make compatible the whole project with bare metal AND with docker #4 --- .dockerignore | 12 ++++++ .env.dist | 3 +- Caddyfile | 25 ++++++++++++ Dockerfile | 52 ++++++++++++++++++++++++ Makefile | 48 ++++++++++++++++++++++ compose.yaml | 69 ++++++++++++++++++++++++++++++++ config/packages/doctrine.yaml | 9 +---- docker/entrypoint.sh | 18 +++++++++ src/Form/RecaptchaType.php | 5 +++ src/Service/RecaptchaService.php | 8 ++++ 10 files changed, 240 insertions(+), 9 deletions(-) create mode 100644 .dockerignore create mode 100644 Caddyfile create mode 100644 Dockerfile create mode 100644 Makefile create mode 100644 compose.yaml create mode 100644 docker/entrypoint.sh diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..ba9df07 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,12 @@ +.git +.gitignore +node_modules +var/cache +var/log +var/sessions +public/build +*.md +docker-compose*.yml +compose.override.yaml +.env.local +.env.*.local diff --git a/.env.dist b/.env.dist index 5acf959..6eee4c2 100644 --- a/.env.dist +++ b/.env.dist @@ -16,7 +16,8 @@ APP_SECRET=c1c278747d952ea66326352b72bb8ec6 DATABASE_URL=mysql://db_user:db_password@127.0.0.1:3306/db_name ###< doctrine/doctrine-bundle ### ###> symfony/mailer ### -# MAILER_DSN=smtp://localhost +MAILER_DSN=smtp://localhost:1025 +MAIL_DOMAIN=localhost ###< symfony/mailer ### ###> symfony/mercure-bundle ### diff --git a/Caddyfile b/Caddyfile new file mode 100644 index 0000000..34cab8f --- /dev/null +++ b/Caddyfile @@ -0,0 +1,25 @@ +{ + {$CADDY_GLOBAL_OPTIONS} + + frankenphp { + {$FRANKENPHP_CONFIG} + } +} + +{$SERVER_NAME:localhost} { + log + + root * /app/public + + encode zstd br gzip + + mercure { + transport_url {$MERCURE_TRANSPORT_URL:bolt:///data/mercure.db} + publisher_jwt {$MERCURE_JWT_SECRET} HS256 + subscriber_jwt {$MERCURE_JWT_SECRET} HS256 + anonymous + subscriptions + } + + php_server +} diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..4a141e0 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,52 @@ +FROM node:22-alpine AS assets + +WORKDIR /app + +COPY package*.json ./ +RUN npm ci --ignore-scripts + +COPY vite.config.js ./ +COPY assets/ assets/ +COPY public/ public/ + +RUN npm run build + +FROM dunglas/frankenphp:latest + +RUN install-php-extensions \ + pdo_pgsql \ + gd \ + intl \ + zip \ + opcache \ + apcu \ + sodium + +RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini" +RUN printf '[opcache]\nopcache.enable=1\nopcache.memory_consumption=256\nopcache.max_accelerated_files=20000\nopcache.validate_timestamps=0\n' \ + > "$PHP_INI_DIR/conf.d/opcache.ini" + +COPY --from=composer:2 /usr/bin/composer /usr/bin/composer + +ENV APP_ENV=prod + +WORKDIR /app + +COPY . . + +RUN composer install \ + --no-dev \ + --no-interaction \ + --no-scripts \ + --optimize-autoloader + +COPY --from=assets /app/public/build ./public/build + +RUN mkdir -p var/cache var/log var/sessions && \ + chown -R www-data:www-data var/ + +COPY Caddyfile /etc/caddy/Caddyfile +COPY docker/entrypoint.sh /entrypoint.sh +RUN chmod +x /entrypoint.sh + +ENTRYPOINT ["/entrypoint.sh"] diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..e51a3de --- /dev/null +++ b/Makefile @@ -0,0 +1,48 @@ +.PHONY: help start start-build stop build down ps logs prune-everything db-reset + +.DEFAULT_GOAL := help + +help: + @echo "Available commands:" + @echo " make start - Start services without building (uses existing images)" + @echo " make start-build - Start services and build images if needed" + @echo " make stop - Stop running services" + @echo " make build - Build Docker images only" + @echo " make down - Stop and remove containers/networks" + @echo " make prune-everything - Prune volumes, networks and images (DANGEROUS!)" + @echo " make db-reset - Reset the database (drop, create, migrate) (DANGEROUS!)" + +start: + docker compose up -d + +start-build: + docker compose up -d --build + +stop: + docker compose stop + +build: + docker compose build + +down: + docker compose down + +prune-everything: + @echo "WARNING: This will remove ALL containers, volumes, networks and images!" + @read -p "Type 'yes' to confirm: " confirm; \ + if [ "$$confirm" != "yes" ]; then \ + echo "Aborted."; \ + exit 1; \ + fi + docker compose down -v --rmi all --remove-orphans + +db-reset: + @echo "WARNING: This will DROP and RECREATE the database!" + @read -p "Type 'yes' to confirm: " confirm; \ + if [ "$$confirm" != "yes" ]; then \ + echo "Aborted."; \ + exit 1; \ + fi + bin/console doctrine:database:drop --force --if-exists --no-interaction + bin/console doctrine:database:create --if-not-exists --no-interaction + bin/console doctrine:migrations:migrate --no-interaction diff --git a/compose.yaml b/compose.yaml new file mode 100644 index 0000000..fc08fab --- /dev/null +++ b/compose.yaml @@ -0,0 +1,69 @@ +services: + app: + build: + context: . + dockerfile: Dockerfile + restart: unless-stopped + ports: + - "10080:80" + environment: + SERVER_NAME: ${SERVER_NAME:-:80} + APP_ENV: prod + APP_SECRET: ${APP_SECRET} + DATABASE_URL: >- + postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB}?serverVersion=${POSTGRES_VERSION}&charset=utf8 + POSTGRES_URL: db + POSTGRES_USER: ${POSTGRES_USER} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} + POSTGRES_DB: ${POSTGRES_DB} + MERCURE_URL: http://localhost/.well-known/mercure + MERCURE_PUBLIC_URL: https://${PUBLIC_HOSTNAME:-localhost}/.well-known/mercure + MERCURE_JWT_SECRET: ${MERCURE_JWT_SECRET} + MERCURE_JWT_TOKEN: ${MERCURE_JWT_TOKEN} + MERCURE_SUBSCRIBER_JWT: ${MERCURE_SUBSCRIBER_JWT} + MAILER_DSN: smtp://mail:25?verify_peer=0 + RECAPTCHA_SITE_KEY: ${RECAPTCHA_SITE_KEY} + RECAPTCHA_SECRET_KEY: ${RECAPTCHA_SECRET_KEY} + WEBAUTHN_RP_ID: ${WEBAUTHN_RP_ID:-localhost} + WEBAUTHN_ORIGIN: ${WEBAUTHN_ORIGIN:-https://localhost} + volumes: + - app_var:/app/var + - caddy_data:/data + - caddy_config:/config + depends_on: + db: + condition: service_healthy + mail: + condition: service_started + mail: + image: boky/postfix:latest + restart: unless-stopped + environment: + ALLOWED_SENDER_DOMAINS: ${MAIL_DOMAIN:-localhost} + # Optional: set a relay host if you need to forward to an external SMTP + # RELAYHOST: "[smtp.example.com]:587" + volumes: + - postfix_spool:/var/spool/postfix + db: + image: postgres:${POSTGRES_VERSION:-latest}-alpine + restart: unless-stopped + environment: + POSTGRES_USER: ${POSTGRES_USER} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} + POSTGRES_DB: ${POSTGRES_DB} + volumes: + - postgres_data:/var/lib/postgresql/data + healthcheck: + test: [ "CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}" ] + interval: 5s + timeout: 3s + retries: 10 + start_period: 10s + ports: + - "15432:5432" +volumes: + app_var: + postgres_data: + caddy_data: + caddy_config: + postfix_spool: diff --git a/config/packages/doctrine.yaml b/config/packages/doctrine.yaml index c4fa8bb..4b5b61f 100644 --- a/config/packages/doctrine.yaml +++ b/config/packages/doctrine.yaml @@ -7,14 +7,7 @@ parameters: doctrine: dbal: - # configure these for your database server - driver: 'pdo_mysql' - server_version: '5.7' - charset: utf8mb4 - default_table_options: - charset: utf8mb4 - collate: utf8mb4_unicode_ci - + driver: 'pdo_pgsql' url: '%env(resolve:DATABASE_URL)%' orm: diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh new file mode 100644 index 0000000..5bd2389 --- /dev/null +++ b/docker/entrypoint.sh @@ -0,0 +1,18 @@ +#!/bin/sh +set -e + +echo "[entrypoint] Waiting for database..." +until php -r "new PDO('pgsql:host=db;port=5432;dbname=${POSTGRES_DB}', '${POSTGRES_USER}', '${POSTGRES_PASSWORD}');" 2>/dev/null; do + echo "[entrypoint] Database not ready, retrying in 2s..." + sleep 2 +done +echo "[entrypoint] Database is ready." + +echo "[entrypoint] Warming up Symfony cache..." +php bin/console cache:warmup + +echo "[entrypoint] Running database migrations..." +php bin/console doctrine:migrations:migrate --no-interaction --allow-no-migration + +echo "[entrypoint] Starting FrankenPHP..." +exec frankenphp run --config /etc/caddy/Caddyfile "$@" diff --git a/src/Form/RecaptchaType.php b/src/Form/RecaptchaType.php index 59d8107..0b1bf7b 100644 --- a/src/Form/RecaptchaType.php +++ b/src/Form/RecaptchaType.php @@ -42,6 +42,11 @@ class RecaptchaType extends AbstractType $builder->addEventListener(FormEvents::PRE_SUBMIT, function (PreSubmitEvent $event): void { $request = $this->requestStack->getCurrentRequest(); $token = $request?->request->getString('g-recaptcha-response') ?? ''; + // For forms that set the token directly on the field (e.g. registration_form[recaptcha]) + // rather than via a standalone g-recaptcha-response input, fall back to the submitted value. + if ($token === '') { + $token = (string) ($event->getData() ?? ''); + } $event->setData($token); }); } diff --git a/src/Service/RecaptchaService.php b/src/Service/RecaptchaService.php index 7a7f442..4184f97 100644 --- a/src/Service/RecaptchaService.php +++ b/src/Service/RecaptchaService.php @@ -58,6 +58,14 @@ readonly class RecaptchaService ->request('POST', self::SITEVERIFY_URL, ['body' => $body]) ->toArray(); + $this->logger->info('reCAPTCHA verify response', [ + 'success' => $data['success'] ?? null, + 'score' => $data['score'] ?? null, + 'hostname' => $data['hostname'] ?? null, + 'error-codes' => $data['error-codes'] ?? [], + 'token_length' => strlen($token), + ]); + return ($data['success'] ?? false) === true && ($data['score'] ?? 0.0) >= self::SCORE_THRESHOLD; } catch (\Throwable $e) {