diff --git a/.dockerignore b/.dockerignore index 1f8ba34..d04de34 100644 --- a/.dockerignore +++ b/.dockerignore @@ -22,8 +22,6 @@ docker-compose.standalone.yml */*/temp* docker .vagrant -.data -.idea var/sessions/* var/log/* var/cache/* diff --git a/.env b/.env index 1b3d7ea..3e8b8ea 100644 --- a/.env +++ b/.env @@ -78,6 +78,7 @@ LOCK_DSN=flock ###> symfony/messenger ### # Choose one of the transports below #MESSENGER_TRANSPORT_DSN=doctrine://default +MESSENGER_FAILED_TRANSPORT_DSN=doctrine://default?queue_name=failed # MESSENGER_TRANSPORT_DSN=amqp://guest:guest@localhost:5672/%2f/messages MESSENGER_TRANSPORT_DSN=redis://redis:6379/messages # When using symfony server:start @@ -94,6 +95,7 @@ MAILER_DSN=smtp://mailer:1025?encryption=&auth_mode= APP_TITLE="Roadiz skeleton" APP_DESCRIPTION="Roadiz skeleton" APP_NAMESPACE=roadiz_skeleton +APP_ROUTER_DEFAULT_URI=http://roadiz-skeleton.test APP_CACHE=0 APP_UNSPLASH_CLIENT_ID= APP_VERSION=0.1.0 @@ -117,7 +119,8 @@ APP_USE_ACCEPT_LANGUAGE_HEADER=false OPEN_ID_DISCOVERY_URL= OPEN_ID_HOSTED_DOMAIN= OPEN_ID_CLIENT_ID= -OPEN_ID_CLIENT_SECRET= +## Define this secret value with Symfony secret and Vault +#OPEN_ID_CLIENT_SECRET= ###< roadiz/rozier-bundle ### ###> rezozero/intervention-request-bundle ### @@ -139,6 +142,10 @@ REDIS_DSN=redis://redis:6379 #REDIS_DSN=redis://127.0.0.1:6379 ###< symfony/framework-bundle ### +###> symfony/notifier ### +DEFAULT_ADMIN_NOTIFIER_RECIPIENT=admin@example.test +###< symfony/notifier ### + ###> lexik/jwt-authentication-bundle ### JWT_SECRET_KEY=%kernel.project_dir%/config/jwt/private.pem JWT_PUBLIC_KEY=%kernel.project_dir%/config/jwt/public.pem @@ -155,3 +162,10 @@ DATABASE_URL="mysql://roadiz:roadiz@db:3306/roadiz?serverVersion=8&charset=utf8m # DATABASE_URL="postgresql://app:!ChangeMe!@127.0.0.1:5432/app?serverVersion=14&charset=utf8" ###< doctrine/doctrine-bundle ### +## Restic backup configuration +#S3_ACCESS_KEY= +#S3_SECRET_KEY= +#S3_STORAGE_CLASS=STANDARD +#RESTIC_PASSWORD= +RESTIC_REPOSITORY= +MYSQL_DUMP_FILENAME=api_database_dump.sql diff --git a/.env.prod b/.env.prod index a75d825..0173be4 100644 --- a/.env.prod +++ b/.env.prod @@ -1,3 +1,5 @@ ###> sentry/sentry-symfony ### SENTRY_DSN= ###< sentry/sentry-symfony ### + +APP_ROUTER_DEFAULT_URI=https://roadiz-skeleton.test diff --git a/.env.test b/.env.test new file mode 100644 index 0000000..9e7162f --- /dev/null +++ b/.env.test @@ -0,0 +1,6 @@ +# define your env variables for the test env here +KERNEL_CLASS='App\Kernel' +APP_SECRET='$ecretf0rt3st' +SYMFONY_DEPRECATIONS_HELPER=999999 +PANTHER_APP_ENV=panther +PANTHER_ERROR_SCREENSHOT_DIR=./var/error-screenshots diff --git a/CHANGELOG.md b/CHANGELOG.md index 674cf42..dbc32ea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,31 @@ +## [v2.2.0](https://github.com/roadiz/skeleton/compare/v2.1.15...v2.2.0) (2023-12-12) + + +### Features + +* Added `APP_ROUTER_DEFAULT_URI` to configure framework.router.default_uri ([67ef138](https://github.com/roadiz/skeleton/commit/67ef1389069144257f43cc2df59da799a58902c7)) +* Added Restic services for production backup and restoration in development env ([fc28b13](https://github.com/roadiz/skeleton/commit/fc28b134f03e7bebeaec778ef673d8755f889daf)) +* Do not expose WebResponse resource directly ([a972b80](https://github.com/roadiz/skeleton/commit/a972b80a02e9e65e37f1fbc36d5f20c1c333a14c)) +* Do not use `themes:install` and `themes:migrate` command anymore as Roadiz will generate Doctrine migration at node-type changes ([09b1714](https://github.com/roadiz/skeleton/commit/09b171417d68349c0a77b0d05dd2318c73627af6)) +* Requires php 8.1 minimum ([424783d](https://github.com/roadiz/skeleton/commit/424783d01f95fa3c80536f372ccbc9e67a150217)) +* **Solr:** Added *_ps field type for multiple geolocations ([2c9f5e3](https://github.com/roadiz/skeleton/commit/2c9f5e36c6f0e81a05130c83f94d5872bc99478e)) +* **Solr:** Better Solr managed schema for French fields asciifolding ([141186c](https://github.com/roadiz/skeleton/commit/141186cd0743233e4283ebc5be0b7fd611846232)) +* Switched to php 82 ([8824e7e](https://github.com/roadiz/skeleton/commit/8824e7e9f21338c86ec6b47328fa808f03bc8835)) +* Updated docker-php-entrypoint to perform db migrations first then app:install ([4b2079a](https://github.com/roadiz/skeleton/commit/4b2079a5dbad0f17f3f91be822d5a02be663b6f4)) +* Updated README and Makefile for new `app:migrate` command, disabled default Varnish invalidation ([b34cf42](https://github.com/roadiz/skeleton/commit/b34cf42f8b03879bc3152653d8819d63e3e72ca9)) +* Upgraded configuration for Roadiz 2.2 ([8ef0d3f](https://github.com/roadiz/skeleton/commit/8ef0d3f18a74f78dd99df94c2d029ee5b1cd0a4b)) + + +### Bug Fixes + +* Configure API firewall as database-less JWT by default to ensure PreviewUser are not reloaded. Missing `user_checker` ([23d64e4](https://github.com/roadiz/skeleton/commit/23d64e49b613286f1a7b1818c8fbbea5db336dfa)) +* **Docker:** Clear caches after migrations and db ready ([91c6220](https://github.com/roadiz/skeleton/commit/91c62209a44ac4a6fc092e4cd1ffe57cf9e70a1d)) +* Fix docker compose watchtower depends-on labels ([f5afbd3](https://github.com/roadiz/skeleton/commit/f5afbd38294523b7d9ca4a6faa7ae87af49be54b)) +* Force watchtower to restart dependent containers ([a2b0357](https://github.com/roadiz/skeleton/commit/a2b0357f1ff1a8c6032b5e77b5b6d5f77c0c449e)) +* Removed deprecated `lexik_jwt_authentication.jwt_token_authenticator` ([42850a0](https://github.com/roadiz/skeleton/commit/42850a0cfe79628557c1454b65c75e2d4c78e57b)) +* Set default empty dotenv vars for OpenID and ignore large files and archives from Varnish cache ([94f3975](https://github.com/roadiz/skeleton/commit/94f3975ccdfdf5369acf1cbae7ea7a013ab5196b)) +* Use VARNISH_HOST instead of URL for reverseProxyCache host param ([5af0965](https://github.com/roadiz/skeleton/commit/5af0965020325714737c11b23c7b95c52b8fc63f)) + ## [v2.1.15](https://github.com/roadiz/skeleton/compare/v2.1.14...v2.1.15) (2023-09-20) diff --git a/Dockerfile b/Dockerfile index 1107776..67a45fd 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM roadiz/php81-fpm-alpine:latest +FROM roadiz/php82-fpm-alpine:latest MAINTAINER Ambroise Maupate ARG USER_UID=1000 ENV APP_ENV=prod diff --git a/Makefile b/Makefile index 5911f1e..6bfeaa0 100644 --- a/Makefile +++ b/Makefile @@ -10,7 +10,7 @@ test: migrate: docker compose exec -u www-data app php bin/console doctrine:migrations:migrate - docker compose exec -u www-data app php bin/console themes:migrate ./src/Resources/config.yml + docker compose exec -u www-data app php bin/console app:migrate # Stop workers to force restart them (Supervisord) docker compose exec -u www-data app php bin/console messenger:stop-workers diff --git a/README.md b/README.md index 61db4b0..cf18532 100644 --- a/README.md +++ b/README.md @@ -83,7 +83,7 @@ Or manually: # Create Roadiz database schema docker compose exec -u www-data app bin/console doctrine:migrations:migrate # Migrate any existing data types -docker compose exec -u www-data app bin/console themes:migrate ./src/Resources/config.yml +docker compose exec -u www-data app bin/console app:install # Install base Roadiz fixtures, roles and settings docker compose exec -u www-data app bin/console install # Clear cache @@ -191,9 +191,9 @@ You will need to use at least *MySQL* and *Redis* (and *Solr* if needed) service docker compose -f docker-compose.symfony.yml up -d ``` -And configure your `.env` variables to use your local MySQL and Redis services. -Replacing `db`, `redis`, `mailer` and `solr` hostnames with `127.0.0.1`. Make sure to use `127.0.0.1` and not `localhost` -on *macOS* as it will not work with Docker. +- Configure your `.env` variables to use your local MySQL and Redis services. Replacing `db`, `redis`, `mailer` and `solr` hostnames with `127.0.0.1`. Make sure to use `127.0.0.1` and not `localhost` on *macOS* as it will not work with Docker. +- Remove `docker compose exec -u www-data app ` prefix from all commands in `Makefile` to execute recipes locally. +- Remove cache invalidation Varnish configuration from `config/packages/api_platform.yaml` and `config/packages/roadiz_core.yaml` file. Then you can start your local webserver: diff --git a/composer.json b/composer.json index 1778434..d21bdaa 100644 --- a/composer.json +++ b/composer.json @@ -4,7 +4,7 @@ "license": "mit", "prefer-stable": true, "require": { - "php": ">=8.0", + "php": ">=8.1", "ext-ctype": "*", "ext-iconv": "*", "ext-json": "*", @@ -65,7 +65,8 @@ "auto-scripts": { "cache:clear": "symfony-cmd", "assets:install %PUBLIC_DIR%": "symfony-cmd", - "themes:assets:install Rozier": "symfony-cmd" + "themes:assets:install Rozier": "symfony-cmd", + "requirements-checker": "script" }, "post-install-cmd": [ "@auto-scripts" @@ -83,6 +84,11 @@ "conflict": { "symfony/symfony": "*" }, + "suggest": { + "roadiz/two-factor-bundle": "Provides a two-factor authentication system for Roadiz CMS", + "roadiz/user-bundle": "Public user management bundle for Roadiz CMS", + "roadiz/font-bundle": "Manage and expose web fonts with Roadiz CMS" + }, "extra": { "symfony": { "allow-contrib": false, diff --git a/composer.json.dist b/composer.json.dist index 1778434..d21bdaa 100644 --- a/composer.json.dist +++ b/composer.json.dist @@ -4,7 +4,7 @@ "license": "mit", "prefer-stable": true, "require": { - "php": ">=8.0", + "php": ">=8.1", "ext-ctype": "*", "ext-iconv": "*", "ext-json": "*", @@ -65,7 +65,8 @@ "auto-scripts": { "cache:clear": "symfony-cmd", "assets:install %PUBLIC_DIR%": "symfony-cmd", - "themes:assets:install Rozier": "symfony-cmd" + "themes:assets:install Rozier": "symfony-cmd", + "requirements-checker": "script" }, "post-install-cmd": [ "@auto-scripts" @@ -83,6 +84,11 @@ "conflict": { "symfony/symfony": "*" }, + "suggest": { + "roadiz/two-factor-bundle": "Provides a two-factor authentication system for Roadiz CMS", + "roadiz/user-bundle": "Public user management bundle for Roadiz CMS", + "roadiz/font-bundle": "Manage and expose web fonts with Roadiz CMS" + }, "extra": { "symfony": { "allow-contrib": false, diff --git a/config/api_resources/attribute.yml b/config/api_resources/attribute.yml new file mode 100644 index 0000000..1b48bcc --- /dev/null +++ b/config/api_resources/attribute.yml @@ -0,0 +1,4 @@ +--- +RZ\Roadiz\CoreBundle\Entity\Attribute: + operations: [] + diff --git a/config/api_resources/attribute_value.yml b/config/api_resources/attribute_value.yml index 5598418..23eee57 100644 --- a/config/api_resources/attribute_value.yml +++ b/config/api_resources/attribute_value.yml @@ -1,15 +1,14 @@ -#--- -#RZ\Roadiz\CoreBundle\Entity\AttributeValue: -# collectionOperations: -# get: -# method: "GET" -# normalization_context: -# groups: ["urls", "attribute", "document_display"] -# enable_max_depth: true -# itemOperations: -# get: -# method: 'GET' -# normalization_context: -# groups: ["urls", "attribute", "document_display"] -# enable_max_depth: true -# +--- +RZ\Roadiz\CoreBundle\Entity\AttributeValue: + operations: + ApiPlatform\Metadata\GetCollection: + method: "GET" + normalizationContext: + groups: ["urls", "attribute", "document_display", "attribute_node", "attribute_documents"] + enable_max_depth: true + ApiPlatform\Metadata\Get: + method: 'GET' + normalizationContext: + groups: ["urls", "attribute", "document_display", "attribute_node", "attribute_documents"] + enable_max_depth: true + diff --git a/config/api_resources/common_content.yml b/config/api_resources/common_content.yml index f86fc39..8f59e0f 100644 --- a/config/api_resources/common_content.yml +++ b/config/api_resources/common_content.yml @@ -1,13 +1,13 @@ App\Api\Model\CommonContent: - collectionOperations: {} - itemOperations: + operations: getCommonContent: + class: ApiPlatform\Metadata\Get method: 'GET' - path: '/common_content' + uriTemplate: '/common_content' read: false controller: App\Controller\GetCommonContentController pagination_enabled: false - normalization_context: + normalizationContext: enable_max_depth: true pagination_enabled: false groups: @@ -17,9 +17,12 @@ App\Api\Model\CommonContent: - walker - walker_level - children + - children_count - nodes_sources_base - nodes_sources_default - urls + #- blocks_urls - tag_base - translation_base - document_display + - document_folders diff --git a/config/api_resources/custom_form.yml b/config/api_resources/custom_form.yml index 6bf06ba..fbb79c5 100644 --- a/config/api_resources/custom_form.yml +++ b/config/api_resources/custom_form.yml @@ -1,25 +1,23 @@ --- RZ\Roadiz\CoreBundle\Entity\CustomForm: - collectionOperations: - get: + operations: + ApiPlatform\Metadata\GetCollection: method: "GET" - normalization_context: - groups: ['custom_form'] + normalizationContext: enable_max_depth: true - itemOperations: - get: + ApiPlatform\Metadata\Get: method: 'GET' - normalization_context: - groups: ['custom_form'] + normalizationContext: enable_max_depth: true api_custom_forms_item_post: method: 'POST' - route_name: api_custom_forms_item_post - normalization_context: + class: ApiPlatform\Metadata\Post + routeName: api_custom_forms_item_post + normalizationContext: enable_max_depth: true - openapi_context: + openapiContext: summary: Post a user custom form description: | Post a user custom form @@ -61,10 +59,11 @@ RZ\Roadiz\CoreBundle\Entity\CustomForm: api_custom_forms_item_definition: method: 'GET' - route_name: api_custom_forms_item_definition - normalization_context: + class: ApiPlatform\Metadata\Get + routeName: api_custom_forms_item_definition + normalizationContext: enable_max_depth: true - openapi_context: + openapiContext: summary: Get a custom form definition for frontend description: | Get a custom form definition for frontend @@ -109,99 +108,3 @@ RZ\Roadiz\CoreBundle\Entity\CustomForm: description: Required fields names example: - 'email' - - api_contact_form_post: - method: 'POST' - route_name: api_contact_form_post - normalization_context: - enable_max_depth: true - openapi_context: - summary: Post a user contact form - description: | - Post a user contact form - requestBody: - content: - multipart/form-data: - schema: - type: object - properties: - email: - type: string - example: test@test.test - first_name: - type: string - example: John - last_name: - type: string - example: Doe - responses: - 201: ~ - 400: - description: Posted contact form has errors - content: - application/json: - schema: - type: object - properties: - email: - type: object - example: - email: This value is not a valid email address. - 202: - description: Posted contact form was accepted - content: - application/json: - schema: - type: object - properties: { } - - api_contact_form_definition: - method: 'GET' - route_name: api_contact_form_definition - normalization_context: - enable_max_depth: true - openapi_context: - summary: Get a contact form definition for frontend - description: | - Get a contact form definition for frontend - responses: - 200: - description: Contact form definition object - content: - application/json: - schema: - type: object - properties: - title: - type: string - description: Form inputs prefix - example: reiciendis_natus_ducimus_nostrum - type: - type: string - description: Form definition type - example: object - properties: - type: object - description: Form definition fields - example: - email: - type: string - title: Email - attr: - data-group: null - placeholder: null - widget: email - propertyOrder: 1 - first_name: - type: string - title: Firstname - attr: - data-group: null - placeholder: null - widget: string - propertyOrder: 2 - required: - type: array - description: Required fields names - example: - - 'email' diff --git a/config/api_resources/document.yml b/config/api_resources/document.yml index e76e79a..d875d9f 100644 --- a/config/api_resources/document.yml +++ b/config/api_resources/document.yml @@ -1,23 +1,15 @@ --- RZ\Roadiz\CoreBundle\Entity\Document: - collectionOperations: - get: + operations: + ApiPlatform\Metadata\GetCollection: method: "GET" - normalization_context: - groups: - - urls - - document_display - - document_display_sources + normalizationContext: + groups: ["urls", "document_display", "document_folders", "document_folders_all", "document_display_sources"] enable_max_depth: true - itemOperations: - get: + + ApiPlatform\Metadata\Get: method: 'GET' - normalization_context: - groups: - - urls - - document - - document_display - - document_folders - - document_display_sources + normalizationContext: + groups: ["urls", "document", "document_display", "document_folders", "document_folders_all", "document_display_sources"] enable_max_depth: true diff --git a/config/api_resources/folder.yml b/config/api_resources/folder.yml index bb6234e..38dafc5 100644 --- a/config/api_resources/folder.yml +++ b/config/api_resources/folder.yml @@ -1,18 +1,13 @@ --- RZ\Roadiz\CoreBundle\Entity\Folder: - iri: Folder - shortName: Folder - collectionOperations: - get: + operations: + ApiPlatform\Metadata\GetCollection: method: "GET" - normalization_context: - groups: - - folder + normalizationContext: + groups: [ "folder" ] enable_max_depth: true - itemOperations: - get: + ApiPlatform\Metadata\Get: method: "GET" - normalization_context: - groups: - - folder + normalizationContext: + groups: [ "folder" ] enable_max_depth: true diff --git a/config/api_resources/node.yml b/config/api_resources/node.yml index f8d73c9..61e7b91 100644 --- a/config/api_resources/node.yml +++ b/config/api_resources/node.yml @@ -1,11 +1,9 @@ --- RZ\Roadiz\CoreBundle\Entity\Node: - shortName: Node - collectionOperations: [] - itemOperations: - get: + operations: + ApiPlatform\Metadata\Get: method: 'GET' - normalization_context: + normalizationContext: groups: - node - tag_base diff --git a/config/api_resources/nodes_sources.yml b/config/api_resources/nodes_sources.yml index 066291d..602794b 100644 --- a/config/api_resources/nodes_sources.yml +++ b/config/api_resources/nodes_sources.yml @@ -1,69 +1,66 @@ --- RZ\Roadiz\CoreBundle\Entity\NodesSources: - iri: NodesSources - shortName: NodesSources - collectionOperations: - # Get operation is needed for sitemap generation - get: + operations: + ApiPlatform\Metadata\GetCollection: method: "GET" - normalization_context: + normalizationContext: groups: - nodes_sources_base - nodes_sources_default + - user - urls - tag_base - translation_base - document_display -# - position -# archives: -# method: 'GET' -# path: '/nodes_sources/archives' -# pagination_enabled: false -# pagination_client_enabled: false -# archive_enabled: true -# archive_publication_field_name: publishedAt -# normalization_context: -# groups: -# - get -# - archives -# openapi_context: -# summary: Get available NodesSources archives -# parameters: ~ -# description: | -# Get available NodesSources archives -# search: -# method: 'GET' -# path: '/nodes_sources/search' -# controller: App\Controller\SearchNodesSourcesController -# normalization_context: -# groups: -# - nodes_sources_base -# - nodes_sources_default -# - urls -# - tag_base -# - translation_base -# - document_display -# - position -# openapi_context: -# summary: Search NodesSources resources -# description: | -# Search NodesSources resources using **Solr** full-text search engine -# parameters: -# - type: string -# name: search -# in: query -# required: true -# description: Search pattern -# schema: -# type: string - itemOperations: - get: + api_nodes_sources_archives: + class: ApiPlatform\Metadata\GetCollection method: 'GET' - normalization_context: + uriTemplate: '/nodes_sources/archives' + pagination_enabled: false + pagination_client_enabled: false + extraProperties: + archive_enabled: true + archive_publication_field_name: publishedAt + normalizationContext: groups: - - nodes_sources + - get + - archives + openapiContext: + summary: Get available NodesSources archives + parameters: ~ + description: | + Get available NodesSources archives (years and months) based on their `publishedAt` field + + api_nodes_sources_search: + class: ApiPlatform\Metadata\GetCollection + method: 'GET' + uriTemplate: '/nodes_sources/search' + controller: RZ\Roadiz\CoreBundle\Api\Controller\NodesSourcesSearchController + read: false + normalizationContext: + groups: + - get + - nodes_sources_base + - nodes_sources_default - urls - tag_base - translation_base - document_display + openapiContext: + summary: Search NodesSources resources + description: | + Search all website NodesSources resources using **Solr** full-text search engine + parameters: + - type: string + name: search + in: query + required: true + description: Search pattern + schema: + type: string + + ApiPlatform\Metadata\Get: + method: 'GET' + normalizationContext: + groups: ["nodes_sources", "urls", "tag_base", "translation_base", "document_display"] diff --git a/config/api_resources/nsmenu.yml b/config/api_resources/nsmenu.yml deleted file mode 100644 index 17b9b4d..0000000 --- a/config/api_resources/nsmenu.yml +++ /dev/null @@ -1,16 +0,0 @@ -App\GeneratedEntity\NSMenu: - iri: Menu - shortName: Menu - collectionOperations: { } - itemOperations: - get: - method: GET - normalization_context: - groups: - - nodes_sources - - urls - - tag_base - - translation_base - - document_display - - document_thumbnails - - document_display_sources diff --git a/config/api_resources/nsmenulink.yml b/config/api_resources/nsmenulink.yml deleted file mode 100644 index 85cd210..0000000 --- a/config/api_resources/nsmenulink.yml +++ /dev/null @@ -1,16 +0,0 @@ -App\GeneratedEntity\NSMenuLink: - iri: MenuLink - shortName: MenuLink - collectionOperations: { } - itemOperations: - get: - method: GET - normalization_context: - groups: - - nodes_sources - - urls - - tag_base - - translation_base - - document_display - - document_thumbnails - - document_display_sources diff --git a/config/api_resources/realm.yml b/config/api_resources/realm.yml index af5bb10..50dbdd8 100644 --- a/config/api_resources/realm.yml +++ b/config/api_resources/realm.yml @@ -1,13 +1,14 @@ --- RZ\Roadiz\CoreBundle\Entity\Realm: - iri: Realm - shortName: Realm - collectionOperations: {} - itemOperations: - get: + operations: + ApiPlatform\Metadata\GetCollection: method: "GET" - normalization_context: - groups: - - get - - realm + normalizationContext: + groups: [ "realm" ] + enable_max_depth: true + + ApiPlatform\Metadata\Get: + method: "GET" + normalizationContext: + groups: [ "realm" ] enable_max_depth: true diff --git a/config/api_resources/tag.yml b/config/api_resources/tag.yml index c136567..3f35d12 100644 --- a/config/api_resources/tag.yml +++ b/config/api_resources/tag.yml @@ -1,18 +1,16 @@ --- RZ\Roadiz\CoreBundle\Entity\Tag: - iri: Tag - shortName: Tag - collectionOperations: - get: + operations: + ApiPlatform\Metadata\GetCollection: method: "GET" - normalization_context: + normalizationContext: enable_max_depth: true groups: - tag_base - itemOperations: - get: + + ApiPlatform\Metadata\Get: method: 'GET' - normalization_context: + normalizationContext: enable_max_depth: true groups: - tag diff --git a/config/api_resources/translation.yml b/config/api_resources/translation.yml index 969a32e..ad19c01 100644 --- a/config/api_resources/translation.yml +++ b/config/api_resources/translation.yml @@ -1,17 +1,16 @@ --- RZ\Roadiz\CoreBundle\Entity\Translation: - attributes: - collectionOperations: - get: + operations: + ApiPlatform\Metadata\GetCollection: method: "GET" - normalization_context: + normalizationContext: enable_max_depth: true groups: - translation_base - itemOperations: - get: + + ApiPlatform\Metadata\Get: method: 'GET' - normalization_context: + normalizationContext: enable_max_depth: true groups: - translation_base diff --git a/config/api_resources/web_response.yml b/config/api_resources/web_response.yml index 5e4986a..865dff1 100644 --- a/config/api_resources/web_response.yml +++ b/config/api_resources/web_response.yml @@ -1,30 +1,33 @@ RZ\Roadiz\CoreBundle\Api\Model\WebResponse: - collectionOperations: {} - itemOperations: + operations: getByPath: + class: ApiPlatform\Metadata\Get method: 'GET' - path: '/web_response_by_path' + uriTemplate: '/web_response_by_path' read: false controller: RZ\Roadiz\CoreBundle\Api\Controller\GetWebResponseByPathController pagination_enabled: false - normalization_context: + normalizationContext: + enable_max_depth: true pagination_enabled: false groups: - get - web_response - #- position + - position - walker - walker_level - meta - children + - children_count - nodes_sources - urls - tag_base - translation_base - document_display - document_thumbnails + - node_attributes - document_display_sources - openapi_context: + openapiContext: summary: Get a resource by its path wrapped in a WebResponse object description: | Get a resource by its path wrapped in a WebResponse diff --git a/config/packages/api_platform.yaml b/config/packages/api_platform.yaml index 3b6cfb7..13f036b 100644 --- a/config/packages/api_platform.yaml +++ b/config/packages/api_platform.yaml @@ -2,7 +2,7 @@ api_platform: patch_formats: json: ['application/merge-patch+json'] enable_swagger_ui: false - metadata_backward_compatibility_layer: true + metadata_backward_compatibility_layer: false enable_re_doc: true graphql: graphiql: @@ -20,10 +20,6 @@ api_platform: - '%kernel.project_dir%/vendor/roadiz/core-bundle/src/Entity' - '%kernel.project_dir%/vendor/rezozero/tree-walker/src' - '%kernel.project_dir%/config/api_resources' - http_cache: - invalidation: - enabled: true - varnish_urls: ['%env(VARNISH_URL)%'] defaults: pagination_client_items_per_page: true pagination_items_per_page: 15 @@ -41,3 +37,10 @@ api_platform: collection: pagination: items_per_page_parameter_name: itemsPerPage + +when@prod: + api_platform: + http_cache: + invalidation: + enabled: true + varnish_urls: [ '%env(VARNISH_URL)%' ] diff --git a/config/packages/doctrine.yaml b/config/packages/doctrine.yaml index 5626744..72fc77d 100644 --- a/config/packages/doctrine.yaml +++ b/config/packages/doctrine.yaml @@ -2,46 +2,72 @@ doctrine: dbal: url: '%env(resolve:DATABASE_URL)%' orm: - dql: - string_functions: - JSON_CONTAINS: Scienta\DoctrineJsonFunctions\Query\AST\Functions\Mysql\JsonContains auto_generate_proxy_classes: true - naming_strategy: doctrine.orm.naming_strategy.underscore_number_aware - auto_mapping: true - mappings: - App: - is_bundle: false - type: attribute - dir: '%kernel.project_dir%/src/Entity' - prefix: 'App\Entity' - alias: App - RoadizCoreBundle: - is_bundle: true - type: attribute - dir: 'src/Entity' - prefix: 'RZ\Roadiz\CoreBundle\Entity' - alias: RoadizCoreBundle - RZ\Roadiz\Core: - is_bundle: false - type: attribute - dir: '%kernel.project_dir%/vendor/roadiz/models/src/Roadiz/Core/AbstractEntities' - prefix: 'RZ\Roadiz\Core\AbstractEntities' - alias: AbstractEntities - App\GeneratedEntity: - is_bundle: false - type: attribute - dir: '%kernel.project_dir%/src/GeneratedEntity' - prefix: 'App\GeneratedEntity' - alias: App\GeneratedEntity - gedmo_loggable: - type: attribute - prefix: Gedmo\Loggable\Entity\MappedSuperclass - dir: "%kernel.project_dir%/vendor/gedmo/doctrine-extensions/src/Loggable/Entity/MappedSuperclass" - alias: GedmoLoggableMappedSuperclass - is_bundle: false + default_entity_manager: default + entity_managers: + # Put `logger` entity manager first to select it as default for Log entity + logger: + naming_strategy: doctrine.orm.naming_strategy.underscore_number_aware + mappings: + ## Just sharding EM to avoid having Logs in default EM + ## and flushing bad entities when storing log entries. + RoadizCoreLogger: + is_bundle: false + type: attribute + dir: '%kernel.project_dir%/vendor/roadiz/core-bundle/src/Logger/Entity' + prefix: 'RZ\Roadiz\CoreBundle\Logger\Entity' + alias: RoadizCoreLogger + default: + dql: + string_functions: + JSON_CONTAINS: Scienta\DoctrineJsonFunctions\Query\AST\Functions\Mysql\JsonContains + naming_strategy: doctrine.orm.naming_strategy.underscore_number_aware + auto_mapping: true + mappings: + ## Keep RoadizCoreLogger to avoid creating different migrations since we are using + ## the same database for both entity managers. Just sharding EM to avoid + ## having Logs in default EM and flushing bad entities when storing log entries. + RoadizCoreLogger: + is_bundle: false + type: attribute + dir: '%kernel.project_dir%/vendor/roadiz/core-bundle/src/Logger/Entity' + prefix: 'RZ\Roadiz\CoreBundle\Logger\Entity' + alias: RoadizCoreLogger + App: + is_bundle: false + type: attribute + dir: '%kernel.project_dir%/src/Entity' + prefix: 'App\Entity' + alias: App + RoadizCoreBundle: + is_bundle: true + type: attribute + dir: 'src/Entity' + prefix: 'RZ\Roadiz\CoreBundle\Entity' + alias: RoadizCoreBundle + RZ\Roadiz\Core: + is_bundle: false + type: annotation + dir: '%kernel.project_dir%/vendor/roadiz/models/src/Core/AbstractEntities' + prefix: 'RZ\Roadiz\Core\AbstractEntities' + alias: AbstractEntities + App\GeneratedEntity: + is_bundle: false + type: attribute + dir: '%kernel.project_dir%/src/GeneratedEntity' + prefix: 'App\GeneratedEntity' + alias: App\GeneratedEntity + gedmo_loggable: + type: attribute + prefix: Gedmo\Loggable\Entity\MappedSuperclass + dir: "%kernel.project_dir%/vendor/gedmo/doctrine-extensions/src/Loggable/Entity/MappedSuperclass" + alias: GedmoLoggableMappedSuperclass + is_bundle: false resolve_target_entities: Symfony\Component\Security\Core\User\UserInterface: RZ\Roadiz\CoreBundle\Entity\User + + # Roadiz Core RZ\Roadiz\Documents\Models\DocumentInterface: RZ\Roadiz\CoreBundle\Entity\Document RZ\Roadiz\Documents\Models\FolderInterface: RZ\Roadiz\CoreBundle\Entity\Folder RZ\Roadiz\Contracts\NodeType\NodeTypeInterface: RZ\Roadiz\CoreBundle\Entity\NodeType @@ -52,20 +78,3 @@ doctrine: RZ\Roadiz\CoreBundle\Model\AttributeValueInterface: RZ\Roadiz\CoreBundle\Entity\AttributeValue RZ\Roadiz\CoreBundle\Model\AttributeValueTranslationInterface: RZ\Roadiz\CoreBundle\Entity\AttributeValueTranslation RZ\Roadiz\Core\AbstractEntities\TranslationInterface: RZ\Roadiz\CoreBundle\Entity\Translation - -when@prod: - doctrine: - orm: - auto_generate_proxy_classes: false - query_cache_driver: - type: pool - pool: doctrine.system_cache_pool - result_cache_driver: - type: pool - pool: doctrine.result_cache_pool - -when@test: - doctrine: - dbal: - # "TEST_TOKEN" is typically set by ParaTest - dbname_suffix: '_test%env(default::TEST_TOKEN)%' diff --git a/config/packages/http_discovery.yaml b/config/packages/http_discovery.yaml new file mode 100644 index 0000000..2a789e7 --- /dev/null +++ b/config/packages/http_discovery.yaml @@ -0,0 +1,10 @@ +services: + Psr\Http\Message\RequestFactoryInterface: '@http_discovery.psr17_factory' + Psr\Http\Message\ResponseFactoryInterface: '@http_discovery.psr17_factory' + Psr\Http\Message\ServerRequestFactoryInterface: '@http_discovery.psr17_factory' + Psr\Http\Message\StreamFactoryInterface: '@http_discovery.psr17_factory' + Psr\Http\Message\UploadedFileFactoryInterface: '@http_discovery.psr17_factory' + Psr\Http\Message\UriFactoryInterface: '@http_discovery.psr17_factory' + + http_discovery.psr17_factory: + class: Http\Discovery\Psr17Factory diff --git a/config/packages/messenger.yaml b/config/packages/messenger.yaml index 5d8feae..b97f8ba 100644 --- a/config/packages/messenger.yaml +++ b/config/packages/messenger.yaml @@ -6,7 +6,7 @@ framework: transports: # https://symfony.com/doc/current/messenger.html#transport-configuration async: '%env(MESSENGER_TRANSPORT_DSN)%' - failed: 'doctrine://default?queue_name=failed' + failed: '%env(MESSENGER_FAILED_TRANSPORT_DSN)%' sync: 'sync://' routing: diff --git a/config/packages/notifier.yaml b/config/packages/notifier.yaml index 3984a48..ed0d8e7 100644 --- a/config/packages/notifier.yaml +++ b/config/packages/notifier.yaml @@ -13,4 +13,4 @@ framework: medium: ['email'] low: ['email'] admin_recipients: - - { email: admin@example.com } + - { email: '%env(DEFAULT_ADMIN_NOTIFIER_RECIPIENT)%' } diff --git a/config/packages/roadiz_core.yaml b/config/packages/roadiz_core.yaml index c9b1d46..a735005 100644 --- a/config/packages/roadiz_core.yaml +++ b/config/packages/roadiz_core.yaml @@ -22,11 +22,7 @@ roadiz_core: ffmpeg_path: '%env(string:APP_FFMPEG_PATH)%' inheritance: type: single_table - reverseProxyCache: - frontend: - default: - host: '%env(string:VARNISH_HOST)%' - domainName: '%env(string:VARNISH_DOMAIN)%' + solr: timeout: 3 endpoints: {} @@ -37,3 +33,10 @@ roadiz_core: # path: / +when@prod: + roadiz_core: + reverseProxyCache: + frontend: + default: + host: '%env(VARNISH_HOST)%' + domainName: '%env(VARNISH_DOMAIN)%' diff --git a/config/packages/roadiz_rozier.yaml b/config/packages/roadiz_rozier.yaml index c5c9d59..a4a2acf 100644 --- a/config/packages/roadiz_rozier.yaml +++ b/config/packages/roadiz_rozier.yaml @@ -1,4 +1,9 @@ --- +parameters: + env(OPEN_ID_DISCOVERY_URL): '' + env(OPEN_ID_HOSTED_DOMAIN): '' + env(OPEN_ID_CLIENT_ID): '' + env(OPEN_ID_CLIENT_SECRET): '' roadiz_rozier: open_id: # Verify User info in JWT at each login @@ -11,6 +16,10 @@ roadiz_rozier: oauth_client_id: '%env(string:OPEN_ID_CLIENT_ID)%' # OpenID identity provider OAuth2 client secret oauth_client_secret: '%env(string:OPEN_ID_CLIENT_SECRET)%' + # A local account must exists for each OpenID user. + requires_local_user: true + # Roles granted to user logged in with OpenId authentication process. + # Only when local users are not required, creating virtual users. granted_roles: - ROLE_USER - ROLE_BACKEND_USER diff --git a/config/packages/routing.yaml b/config/packages/routing.yaml index 4b766ce..15082eb 100644 --- a/config/packages/routing.yaml +++ b/config/packages/routing.yaml @@ -1,10 +1,12 @@ +parameters: + env(APP_ROUTER_DEFAULT_URI): 'http://localhost' framework: router: utf8: true # Configure how to generate URLs in non-HTTP contexts, such as CLI commands. # See https://symfony.com/doc/current/routing.html#generating-urls-in-commands - #default_uri: http://localhost + default_uri: '%env(APP_ROUTER_DEFAULT_URI)%' when@prod: framework: diff --git a/config/packages/security.yaml b/config/packages/security.yaml index 43c7b64..e68ffda 100644 --- a/config/packages/security.yaml +++ b/config/packages/security.yaml @@ -8,6 +8,8 @@ security: # https://symfony.com/doc/current/security.html#where-do-users-come-from-user-providers providers: + jwt: + lexik_jwt: ~ openid_user_provider: id: RZ\Roadiz\OpenId\Authentication\Provider\OpenIdAccountProvider roadiz_user_provider: @@ -22,9 +24,10 @@ security: dev: pattern: ^/(_(profiler|wdt)|css|images|js)/ security: false - # JWT for API - api: - pattern: ^/api + + # https://symfony.com/bundles/LexikJWTAuthenticationBundle/current/index.html#configure-application-routing + api_login: + pattern: ^/api/token stateless: true provider: all_users login_throttling: @@ -35,7 +38,21 @@ security: password_path: password success_handler: lexik_jwt_authentication.handler.authentication_success failure_handler: lexik_jwt_authentication.handler.authentication_failure + user_checker: RZ\Roadiz\CoreBundle\Security\UserChecker + + # https://symfony.com/bundles/LexikJWTAuthenticationBundle/current/8-jwt-user-provider.html#symfony-5-3-and-higher + api: + pattern: ^/api + stateless: true + # Do not reload user from database, trust JWT roles in order to restrict PreviewUsers + # Only drawback is when you want to disable / block / expire a user, you'll have to + # wait for JWT token to expire. + provider: jwt + # If you really want to reload user from database, uncomment this line, but Preview JWT + # will be reloaded as full user and not as PreviewUser. + #provider: all_users jwt: ~ + # disables session creation for assets and healthcheck controllers assets: pattern: ^/assets @@ -63,9 +80,7 @@ security: max_attempts: 3 logout: path: logoutPage - guard: - authenticators: - - lexik_jwt_authentication.jwt_token_authenticator + user_checker: RZ\Roadiz\CoreBundle\Security\UserChecker custom_authenticator: - RZ\Roadiz\RozierBundle\Security\RozierAuthenticator # Enables OpenId on backoffice entrypoint @@ -74,11 +89,19 @@ security: # Easy way to control access for large sections of your site # Note: Only the *first* access control that matches will be used access_control: - - { path: ^/rz-admin/login, roles: IS_AUTHENTICATED_ANONYMOUSLY } + - { path: ^/rz-admin/login, roles: PUBLIC_ACCESS } - { path: ^/rz-admin, roles: ROLE_BACKEND_USER } - - { path: ^/api/token, roles: IS_AUTHENTICATED_ANONYMOUSLY } - #- { path: ^/api/newsletter_form/post, methods: [ POST ], roles: IS_AUTHENTICATED_ANONYMOUSLY } - - { path: ^/api/contact_form/post, methods: [ POST ], roles: IS_AUTHENTICATED_ANONYMOUSLY } - - { path: "^/api/custom_forms/(?:[0-9]+)/post", methods: [ POST ], roles: IS_AUTHENTICATED_ANONYMOUSLY } + - { path: ^/api/token, roles: PUBLIC_ACCESS } + #- { path: ^/api/newsletter_form/post, methods: [ POST ], roles: PUBLIC_ACCESS } + - { path: ^/api/contact_form/post, methods: [ POST ], roles: PUBLIC_ACCESS } + - { path: "^/api/custom_forms/(?:[0-9]+)/post", methods: [ POST ], roles: PUBLIC_ACCESS } - { path: ^/api, roles: ROLE_BACKEND_USER, methods: [ POST, PUT, PATCH, DELETE ] } + ## + ## roadiz/two-factor-bundle and roadiz/user-bundle config + ## + #- { path: ^/2fa, role: IS_AUTHENTICATED_2FA_IN_PROGRESS } + #- { path: "^/api/users/signup", methods: [ POST ], roles: PUBLIC_ACCESS } + #- { path: "^/api/users/password_request", methods: [ POST ], roles: PUBLIC_ACCESS } + #- { path: "^/api/users/password_reset", methods: [ PUT ], roles: PUBLIC_ACCESS } + #- { path: "^/api/users", methods: [ GET, PUT, PATCH, POST ], roles: ROLE_USER } # - { path: ^/profile, roles: ROLE_USER } diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index e3934d9..2917f85 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -1,5 +1,6 @@ # Production template # Replace “my-registry/roadiz_skeleton” with your own project registry URL +name: skeleton services: db: image: mysql:8.0 @@ -44,6 +45,7 @@ services: labels: - "traefik.enable=true" - "com.centurylinklabs.watchtower.enable=true" + - "com.centurylinklabs.watchtower.depends-on=/skeleton-nginx-1,/skeleton-worker-1,/skeleton-cron-1" # Traefik v2 ----------------------------------------------------- - "traefik.http.services.${APP_NAMESPACE}.loadbalancer.server.scheme=http" - "traefik.http.services.${APP_NAMESPACE}.loadbalancer.server.port=80" @@ -68,7 +70,7 @@ services: # # Apply middlewares # - - "traefik.http.routers.${APP_NAMESPACE}_secure.middlewares=${APP_NAMESPACE}Redirectregex" + - "traefik.http.routers.${APP_NAMESPACE}_secure.middlewares=${APP_NAMESPACE}Redirectregex,${APP_NAMESPACE}Sts" - "traefik.http.routers.${APP_NAMESPACE}.middlewares=${APP_NAMESPACE}Redirectregex,${APP_NAMESPACE}Redirectscheme" # Add domain redirection (${APP_NAMESPACE} non-www to www redir) - "traefik.http.middlewares.${APP_NAMESPACE}Redirectregex.redirectregex.regex=${REDIRECT_REGEX}" @@ -77,6 +79,12 @@ services: # Add SSL redirection - "traefik.http.middlewares.${APP_NAMESPACE}Redirectscheme.redirectscheme.scheme=https" - "traefik.http.middlewares.${APP_NAMESPACE}Redirectscheme.redirectscheme.permanent=true" + # HSTS headers + - "traefik.http.middlewares.${APP_NAMESPACE}Sts.headers.stsincludesubdomains=false" + - "traefik.http.middlewares.${APP_NAMESPACE}Sts.headers.stspreload=true" + - "traefik.http.middlewares.${APP_NAMESPACE}Sts.headers.stsseconds=31536000" + - "traefik.http.middlewares.${APP_NAMESPACE}Sts.headers.isdevelopment=false" + nginx: image: my-registry/roadiz_skeleton/nginx:latest @@ -94,8 +102,7 @@ services: - app_assets_data:/var/www/html/public/assets:ro labels: - "com.centurylinklabs.watchtower.enable=true" - # Force Watchtower to restart varnish after app update - - "com.centurylinklabs.watchtower.depends-on=skeleton-varnish-1" + - "com.centurylinklabs.watchtower.depends-on=/skeleton-app-1" app: image: my-registry/roadiz_skeleton:latest @@ -135,11 +142,11 @@ services: APP_DEBUG: 0 labels: - "com.centurylinklabs.watchtower.enable=true" - # Force Watchtower to restart nginx and varnish after app update - - "com.centurylinklabs.watchtower.depends-on=skeleton-nginx-1,skeleton-varnish-1" + - "com.centurylinklabs.watchtower.depends-on=/skeleton-redis-1" worker: - extends: app + extends: + service: app deploy: # Do not use more than 1 replica if you're using Varnish and need to purge/ban cache # from your workers. Varnish ACL hostnames won't be resolved correctly. @@ -148,15 +155,18 @@ services: restart: always labels: - "com.centurylinklabs.watchtower.enable=true" + - "com.centurylinklabs.watchtower.depends-on=/skeleton-redis-1" cron: - extends: app + extends: + service: app # https://github.com/dubiousjim/dcron/issues/13#issuecomment-1406937781 init: true entrypoint: [ "crond", "-f", "-L", "15" ] restart: always labels: - "com.centurylinklabs.watchtower.enable=true" + - "com.centurylinklabs.watchtower.depends-on=/skeleton-redis-1" #solr: # image: my-registry/roadiz_skeleton/solr:latest @@ -169,38 +179,62 @@ services: # networks: # - default - backup: - image: ambroisemaupate/s3-backup + # RESTIC incremental backup services + restic: + # Keep the same hostname for all Restic services + hostname: restic-api-backup + image: restic/restic:latest networks: - default - depends_on: - - db environment: - LOCAL_PATH: /var/www/html - DB_USER: ${MYSQL_USER} - DB_HOST: db - DB_PASS: ${MYSQL_PASSWORD} - DB_NAME: ${MYSQL_DATABASE} - S3_ACCESS_KEY: ${S3_ACCESS_KEY} - S3_SECRET_KEY: ${S3_SECRET_KEY} - S3_SIGNATURE: ${S3_SIGNATURE} - S3_BUCKET_LOCATION: ${S3_BUCKET_LOCATION} - S3_HOST_BASE: ${S3_HOST_BASE} - S3_HOST_BUCKET: ${S3_HOST_BUCKET} - S3_BUCKET_NAME: ${S3_BUCKET_NAME} - S3_FOLDER_NAME: ${S3_FOLDER_NAME} + AWS_ACCESS_KEY_ID: ${S3_ACCESS_KEY} + AWS_SECRET_ACCESS_KEY: ${S3_SECRET_KEY} S3_STORAGE_CLASS: ${S3_STORAGE_CLASS} - COMPRESS: 0 + RESTIC_REPOSITORY: ${RESTIC_REPOSITORY} + RESTIC_PASSWORD: ${RESTIC_PASSWORD} volumes: - - app_file_data:/var/www/html/public/files:ro - - app_assets_data:/var/www/html/public/assets:ro - - app_private_file_data:/var/www/html/var/files:ro - + # If no restore needed, this volume should remain read-only + - app_file_data:/srv/public/files:ro + - app_assets_data:/srv/public/assets:ro + - app_private_file_data:/srv/var/files:ro + - restic_cache:/root/.cache/restic + backup_files: + # Keep the same hostname for all Restic services + extends: + service: restic + command: 'backup -o s3.storage-class=${S3_STORAGE_CLASS} --tag files /srv' + backup_mysql: + # Keep the same hostname for all Restic services + hostname: restic-api-backup + image: ambroisemaupate/restic-database + environment: + AWS_ACCESS_KEY_ID: ${S3_ACCESS_KEY} + AWS_SECRET_ACCESS_KEY: ${S3_SECRET_KEY} + S3_STORAGE_CLASS: ${S3_STORAGE_CLASS} + RESTIC_REPOSITORY: ${RESTIC_REPOSITORY} + RESTIC_PASSWORD: ${RESTIC_PASSWORD} + # MySQL credentials + MYSQL_HOST: ${MYSQL_HOST} + MYSQL_DATABASE: ${MYSQL_DATABASE} + MYSQL_PASSWORD: ${MYSQL_PASSWORD} + MYSQL_USER: ${MYSQL_USER} + MYSQL_DUMP_FILENAME: ${MYSQL_DUMP_FILENAME} + volumes: + - restic_cache:/root/.cache/restic + depends_on: + - db + command: 'backup -o s3.storage-class=${S3_STORAGE_CLASS} --tag db ${MYSQL_DUMP_FILENAME}' + forget: + extends: + service: restic + # Forget all snapshots older than 7 days and keep 12 monthly snapshots + command: 'forget -o s3.storage-class=${S3_STORAGE_CLASS} --keep-daily 7 --keep-monthly 12 --prune' volumes: app_file_data: app_assets_data: app_private_file_data: app_secret_data: + restic_cache: ## ## Do not add volume for src/GeneratedEntity, they are versioned since Roadiz v2 ## Uncomment these if you DO want to persist and edit node-types on production env diff --git a/docker-compose.restore.yml b/docker-compose.restore.yml new file mode 100644 index 0000000..f34a8e0 --- /dev/null +++ b/docker-compose.restore.yml @@ -0,0 +1,32 @@ +# Restoring from a backup into development environment +# Separate file to avoid running the restore services when using docker-compose up +# docker compose -f docker-compose.restore.yml --env-file .env.local run --rm restore_files +version: '3.6' +services: + restore_files: + # Keep the same hostname for all Restic services + hostname: restic-api-backup + image: restic/restic:latest + environment: + AWS_ACCESS_KEY_ID: ${S3_ACCESS_KEY} + AWS_SECRET_ACCESS_KEY: ${S3_SECRET_KEY} + S3_STORAGE_CLASS: ${S3_STORAGE_CLASS} + RESTIC_REPOSITORY: ${RESTIC_REPOSITORY} + RESTIC_PASSWORD: ${RESTIC_PASSWORD} + volumes: + # If no restore needed, this volume should remain read-only + - ./public/files:/srv/public/files + - ./public/assets:/srv/public/assets + - ./var/files:/srv/var/files + - restic_cache:/root/.cache/restic + # Restore all files in place + command: 'restore latest --path /srv --target /' + + restore_mysql: + extends: + service: restore_files + command: 'restore latest --path ${MYSQL_DUMP_FILENAME} --target /srv/var/files' + +volumes: + restic_cache: + diff --git a/docker-compose.yml b/docker-compose.yml index ba8ab04..55fe755 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -89,7 +89,7 @@ services: default: # frontproxynet: # aliases: -# - ${APP_NAMESPACE}_app +# - ${APP_NAMESPACE}_nginx # labels: # - "traefik.enable=true" # - "traefik.http.services.${APP_NAMESPACE}.loadbalancer.server.scheme=http" @@ -113,7 +113,8 @@ services: # - "traefik.http.routers.${APP_NAMESPACE}_secure.service=${APP_NAMESPACE}" worker: - extends: app + extends: + service: app deploy: # Do not use more than 1 replica if you're using Varnish and need to purge/ban cache # from your workers. Varnish ACL hostnames won't be resolved correctly. @@ -122,7 +123,8 @@ services: restart: unless-stopped cron: - extends: app + extends: + service: app # https://github.com/dubiousjim/dcron/issues/13#issuecomment-1406937781 init: true entrypoint: [ "crond", "-f", "-L", "15" ] @@ -137,7 +139,7 @@ services: ## ports: ## - "${PUBLIC_SOLR_PORT}:8983/tcp" # volumes: -# - "./.data/solr:/var/solr:delegated" +# - "solr_data:/var/solr:delegated" # environment: # SOLR_UID: ${USER_UID} # SOLR_GID: ${USER_UID} @@ -246,3 +248,6 @@ networks: driver: default config: - subnet: ${DEFAULT_GATEWAY}/16 + +volumes: + solr_data: diff --git a/docker/php-fpm-alpine/Dockerfile b/docker/php-fpm-alpine/Dockerfile index b3b4eb9..185dd04 100755 --- a/docker/php-fpm-alpine/Dockerfile +++ b/docker/php-fpm-alpine/Dockerfile @@ -1,4 +1,4 @@ -FROM roadiz/php81-fpm-alpine +FROM roadiz/php82-fpm-alpine:latest MAINTAINER Ambroise Maupate ARG USER_UID=1000 diff --git a/docker/php-fpm-alpine/crontab.txt b/docker/php-fpm-alpine/crontab.txt index 4c2e596..83d7f1d 100644 --- a/docker/php-fpm-alpine/crontab.txt +++ b/docker/php-fpm-alpine/crontab.txt @@ -1,12 +1,12 @@ # Roadiz maintenance tasks ### Update Solr index -0 0 * * * cd /var/www/html && /usr/local/bin/php -d memory_limit=-1 bin/console solr:reindex --no-debug -n -q +0 0 * * * /usr/local/bin/php -d memory_limit=-1 /var/www/html/bin/console solr:reindex --no-debug -n -q ### Maintenance tasks: erase +6 months logs and keeps only 20 node-source versions -0 8 * * 1 cd /var/www/html && /usr/local/bin/php -d memory_limit=-1 bin/console documents:file:size -q -0 1 * * * cd /var/www/html && /usr/local/bin/php -d memory_limit=-1 bin/console logs:cleanup --erase -n -q -0 2 * * * cd /var/www/html && /usr/local/bin/php -d memory_limit=-1 bin/console versions:purge -c 20 -n -q -0 3 * * * cd /var/www/html && /usr/local/bin/php -d memory_limit=-1 bin/console custom-form-answer:prune -n -q +0 8 * * 1 /usr/local/bin/php -d memory_limit=-1 /var/www/html/bin/console documents:file:size -q +0 1 * * * /usr/local/bin/php -d memory_limit=-1 /var/www/html/bin/console logs:cleanup --erase -n -q +0 2 * * * /usr/local/bin/php -d memory_limit=-1 /var/www/html/bin/console versions:purge -c 20 -n -q +0 3 * * * /usr/local/bin/php -d memory_limit=-1 /var/www/html/bin/console custom-form-answer:prune -n -q ### Empty node trashcan every month -0 0 1 * * cd /var/www/html && /usr/local/bin/php -d memory_limit=-1 bin/console nodes:empty-trash -n -q +0 0 1 * * /usr/local/bin/php -d memory_limit=-1 /var/www/html/bin/console nodes:empty-trash -n -q diff --git a/docker/php-fpm-alpine/docker-php-entrypoint b/docker/php-fpm-alpine/docker-php-entrypoint index a017e84..bdd6746 100755 --- a/docker/php-fpm-alpine/docker-php-entrypoint +++ b/docker/php-fpm-alpine/docker-php-entrypoint @@ -13,9 +13,6 @@ set -e # Print local env vars to .env.xxx.php file for performances and crontab jobs /usr/bin/sudo -u www-data -- bash -c "/usr/local/bin/composer dump-env prod" -/usr/bin/sudo -u www-data -- bash -c "/var/www/html/bin/console cache:clear -n" -# Clear all cache pool on Symfony 5.4 https://symfony.com/doc/5.4/cache.html#clearing-the-cache -/usr/bin/sudo -u www-data -- bash -c "/var/www/html/bin/console cache:pool:clear cache.global_clearer -n" /usr/bin/sudo -u www-data -- bash -c "/var/www/html/bin/console assets:install -n" /usr/bin/sudo -u www-data -- bash -c "/var/www/html/bin/console themes:assets:install -n Rozier" @@ -39,10 +36,14 @@ set -e ## ## Uncomment following lines to enable automatic migration for your theme at each docker start ## -#if [ -e "./src/Resources/config.yml" ]; then -# # Perform migrations directly as database should be ready thanks to wait-for-it.sh -# /usr/bin/sudo -u www-data -- bash -c "/var/www/html/bin/console themes:migrate -n src/Resources/config.yml" -#fi +## Perform migrations directly as database should be ready thanks to wait-for-it.sh +# /usr/bin/sudo -u www-data -- bash -c "/var/www/html/bin/console doctrine:migrations:migrate -n --allow-no-migration" +# /usr/bin/sudo -u www-data -- bash -c "/var/www/html/bin/console app:install -n" + +# Clear caches after migrations +/usr/bin/sudo -u www-data -- bash -c "/var/www/html/bin/console cache:clear -n" +# Clear all cache pool on Symfony 5.4 https://symfony.com/doc/5.4/cache.html#clearing-the-cache +/usr/bin/sudo -u www-data -- bash -c "/var/www/html/bin/console cache:pool:clear cache.global_clearer -n" # first arg is `-f` or `--some-option` if [ "${1#-}" != "$1" ]; then diff --git a/docker/php-fpm-alpine/docker-php-entrypoint-dev b/docker/php-fpm-alpine/docker-php-entrypoint-dev index 3baece0..c42f154 100755 --- a/docker/php-fpm-alpine/docker-php-entrypoint-dev +++ b/docker/php-fpm-alpine/docker-php-entrypoint-dev @@ -12,9 +12,6 @@ set -e /bin/chown -R www-data:www-data /var/www/html/config || true; # Print local env vars to .env.xxx.php file for performances and crontab jobs -/usr/bin/sudo -u www-data -- bash -c "/var/www/html/bin/console cache:clear -n" -# Clear all cache pool on Symfony 5.4 https://symfony.com/doc/5.4/cache.html#clearing-the-cache -/usr/bin/sudo -u www-data -- bash -c "/var/www/html/bin/console cache:pool:clear cache.global_clearer -n" /usr/bin/sudo -u www-data -- bash -c "/var/www/html/bin/console assets:install -n" /usr/bin/sudo -u www-data -- bash -c "/var/www/html/bin/console themes:assets:install -n Rozier --relative --symlink" @@ -23,6 +20,10 @@ set -e # /wait-for-it.sh -t 0 -s ${MYSQL_HOST}:${MYSQL_PORT} +/usr/bin/sudo -u www-data -- bash -c "/var/www/html/bin/console cache:clear -n" +# Clear all cache pool on Symfony 5.4 https://symfony.com/doc/5.4/cache.html#clearing-the-cache +/usr/bin/sudo -u www-data -- bash -c "/var/www/html/bin/console cache:pool:clear cache.global_clearer -n" + # first arg is `-f` or `--some-option` if [ "${1#-}" != "$1" ]; then set -- php-fpm "$@" diff --git a/docker/php-fpm-alpine/php.ini b/docker/php-fpm-alpine/php.ini index d0b7dc2..f5831b3 100644 --- a/docker/php-fpm-alpine/php.ini +++ b/docker/php-fpm-alpine/php.ini @@ -32,6 +32,6 @@ upload_max_filesize = 128M expose_php = On display_errors = On -# Sessions +;; Sessions ;session.save_handler = redis ;session.save_path = "tcp://redis:6379" diff --git a/docker/php-fpm-alpine/php.prod.ini b/docker/php-fpm-alpine/php.prod.ini index 0a5119d..bbe4a96 100644 --- a/docker/php-fpm-alpine/php.prod.ini +++ b/docker/php-fpm-alpine/php.prod.ini @@ -33,6 +33,6 @@ zend.detect_unicode = 0 opcache.preload_user=www-data opcache.preload=/var/www/html/config/preload.php -# Sessions +;; Sessions ;session.save_handler = redis ;session.save_path = "tcp://redis:6379" diff --git a/docker/solr/managed-schema.xml b/docker/solr/managed-schema.xml index 4d4fcda..a083247 100644 --- a/docker/solr/managed-schema.xml +++ b/docker/solr/managed-schema.xml @@ -152,8 +152,12 @@ + + + + @@ -246,6 +250,8 @@ --> + + @@ -281,9 +287,9 @@ - - - + + + - - - - - - - - - - - - - + + + + + + + + + + + + + + @@ -323,17 +330,17 @@ - - - - - - - - - - - + + + + + + + + + + + - - - - - + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - + + + + + + - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - + + + + + + + + + + + + - - - - + + + + - - - - + + + + - - - - - - + + + + + + - - - - - - + + + + + + - - - - + + + + - - - - + + + + - - - - + + + + @@ -566,273 +574,269 @@ - - - - - - - - - + + + + + + + + + - - - - - - + + + + + + - - - - - - - - + + + + + + + + - - - - - - - - + + + + + + + + - - - - - - + + + + + + - - - - - - + + + + + + - - - - - - - - - - + + + + + + + + + - - - - - - - + + + + + + + - - - - - - - - + + + + + + + - - - - - - - + + + + + + - - - - - - - + + + + + + - - - - - - - - - + + + + + + + + + - - - - - - - + + + + + + + - - - - - - - - - - - + + + + + + + + + + + + + - - - - - - - - - - - + + + + + + + + + + - - - - - - - + + + + + + + - - - - - - - - - - + + + + + + + + + + - - - - - - - + + + + + + + - - - - - - + + + + + + - - - - - - - + + + + + + + - - - - - - - - - - - - + + + + + + + + + + + - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + - - - - - - - - - + + + + + + + + + - - - - - - + + + + + + - - - - - - - - + + + + + + + - - - - - - - - + + + + + + + + - - - - - - - - - - - - + + + + + + + + + + + - - - - - - - + + + + + + - - - - - - - + + + + + + + - - - - - - - + + + + + + + - - - - - + + + + + - - - - - - + + + + + +