├── .gitignore ├── .vscode └── launch.json ├── Makefile ├── README.md ├── bin ├── drush ├── phpcbf ├── phpcs └── phpunit ├── composer.json ├── config └── sync │ └── .gitkeep ├── docker-compose.macos.yml ├── docker-compose.yml ├── docker ├── 000-default.conf ├── Dockerfile ├── entrypoint.sh └── run-entrypoint.sh ├── package.json ├── phpcs.xml ├── phpunit.xml ├── scripts ├── acquia_hooks │ ├── README.md │ ├── common │ │ ├── post-code-deploy │ │ │ └── update.sh │ │ ├── post-code-update │ │ │ └── update.sh │ │ ├── post-db-copy │ │ │ └── .gitkeep │ │ ├── post-files-copy │ │ │ └── .gitkeep │ │ └── pre-web-activate │ │ │ └── .gitkeep │ └── samples │ │ ├── db-scrub.sh │ │ ├── domain_fix.sh │ │ ├── drupal-tests.sh │ │ ├── drush-cache-clear.sh │ │ ├── hello-world.sh │ │ ├── newrelic │ │ ├── README.md │ │ ├── newrelic.sh │ │ └── newrelic_settings │ │ ├── post-code-deploy.tmpl │ │ ├── post-db-copy.tmpl │ │ ├── post-files-copy.tmpl │ │ ├── rollback_demo │ │ ├── README.md │ │ ├── rollback.sh │ │ └── rollback_settings │ │ ├── slack │ │ ├── README.md │ │ ├── slack.sh │ │ └── slack_settings │ │ └── update-db.sh ├── backup-local-database.sh ├── beautify.sh ├── ci-check.sh ├── ci-deploy.sh ├── ci-install-prod.sh ├── ci-install.sh ├── ci-setup.sh ├── ci-tests.sh ├── composer │ └── ScriptHandler.php ├── git-hooks │ └── pre-push.sh ├── restore-local-database.sh ├── sniff.sh ├── toggle-xdebug.sh └── update.sh ├── temp └── .gitkeep └── web ├── modules ├── custom │ └── README.md └── sandbox │ └── README.md ├── sites ├── default │ └── settings.php └── template.settings.local.php └── themes └── custom └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | # Temporary files. 2 | temp/* 3 | !temp/.gitkeep 4 | 5 | # Composer dependencies. 6 | /vendor/ 7 | 8 | # Node modules. 9 | node_modules 10 | 11 | # Drupal core and contrib files. 12 | /web/core 13 | /web/profiles/contrib 14 | /web/modules/contrib 15 | /web/themes/contrib 16 | 17 | # Drupal files folder. 18 | /web/sites/default/files 19 | 20 | #Drupal config files 21 | /web/sites/**/settings.local.php 22 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Listen for XDebug", 6 | "type": "php", 7 | "request": "launch", 8 | "port": 9000, 9 | "log": false, 10 | "pathMappings": { 11 | "/taller/app/web": "${workspaceFolder}/web" 12 | } 13 | } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | SHELL := /bin/bash # Use bash syntax 2 | # Configure environment. 3 | # ---------------------- 4 | 5 | export TZ=America/Sao_Paulo 6 | export USER_ID=$(shell id -u) 7 | 8 | # @TODO Hack for MacOSX or other OS which has the same group id 9 | # than the containers user. 10 | export GROUP_ID=$(shell if [ `id -g` == '20' ]; then echo '1000'; else echo `id -g`; fi) 11 | 12 | APP_CONTAINER=$(shell docker-compose ps | grep _app_run_ | cut -d" " -f 1) 13 | 14 | run: 15 | touch temp/.bash_history 16 | docker-compose run --service-ports --rm app 17 | 18 | in: 19 | docker exec -it ${APP_CONTAINER} /bin/bash 20 | 21 | stop: 22 | docker-compose stop 23 | 24 | clean: 25 | docker-compose down 26 | docker rmi APP_IMAGE_NAME 27 | 28 | ci-clean: 29 | docker-compose down 30 | 31 | build: 32 | docker-compose build app 33 | 34 | ci-setup: 35 | ./scripts/ci-setup.sh 36 | 37 | ci-check: 38 | ./scripts/ci-check.sh 39 | 40 | ci-install: 41 | ./scripts/ci-install.sh 42 | 43 | ci-tests: sniff 44 | ./scripts/ci-tests.sh 45 | 46 | ci-install-prod: 47 | ./scripts/ci-install-prod.sh 48 | 49 | ci-deploy: 50 | ./scripts/ci-deploy.sh 51 | 52 | sniff: 53 | ./scripts/sniff.sh 54 | 55 | beautify: 56 | ./scripts/beautify.sh 57 | 58 | xdebug: 59 | ./scripts/toggle-xdebug.sh 60 | 61 | backup-local-database: 62 | docker-compose run --rm app sh ./scripts/backup-local-database.sh 63 | 64 | restore-local-database: 65 | docker-compose run --rm app sh ./scripts/restore-local-database.sh 66 | 67 | default: run 68 | 69 | 70 | ## MacOSx only 71 | 72 | run-macos: 73 | touch temp/.bash_history 74 | docker-compose -f docker-compose.yml -f docker-compose.macos.yml run --service-ports --rm app 75 | 76 | sniff-macos: 77 | docker exec -it ${APP_CONTAINER} ./bin/phpcs ./web/modules/custom ./web/modules/sandbox 78 | 79 | beautify-macos: 80 | docker exec -it ${APP_CONTAINER} ./bin/phpcbf ./web/modules/custom ./web/modules/sandbox 81 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Drupal Enterprise Boilerplate 2 | This boilerplate is based on how we are currently working with Drupal at Taller. It may not be suitable for all kinds of projects and still needs lots of decoupling. 3 | 4 | ## Dependencies 5 | - Docker 6 | - Docker-compose 7 | - Make 8 | 9 | - MacOS users, [follow this post to configure NFS first.](https://www.jeffgeerling.com/blog/2020/revisiting-docker-macs-performance-nfs-volumes) 10 | 11 | 12 | ## How to Use 13 | 14 | ### Installing locally 15 | - Clone this repository 16 | - Remove .git folder 17 | - Now is the moment I suggest you to create your own Git repo and commit the boilerplate code, but it is really up to you 18 | - Find/replace the following variables (be carefull not to replace in this Readme): 19 | 20 | ||| 21 | |---|---| 22 | | APP_IMAGE_NAME | The name of the Docker image for your site | 23 | | DB_CONTAINER_NAME | The name of the Docker container with the database | 24 | | CI_ACQUIA_DIR | The name of the folder to use in CI when deploying to Acquia. Should be __different__ from the repository's name | 25 | | ACQUIA_GIT_DOMAIN | The domain of Acquia's __Git__ repository, __not the client domain__ | 26 | | ACQUIA_GIT_REPO | The Acquia's Git repository of your project | 27 | | GIT_USER_EMAIL | The email to be in the Git config while building in Semaphore and deploying to Acquia | 28 | | GIT_USER_NAME | The name to be in the Git config while building in Semaphore and deploying to Acquia | 29 | | DRUPAL_SITE_NAME | The name of your Drupal site | 30 | | SERVER_ADMIN | Apache server admin | 31 | 32 | - Run `make` (`make run-macos` to MacOS users) 33 | - If everything went right, you should be able to access your Drupal site in your localhost 34 | - You can use the default login and password: `admin` and `123456` 35 | - Commit Drupal generated files 36 | 37 | ### Coding Standards 38 | 39 | #### PHP Code Sniffer 40 | To mantain the [Drupal Coding Standards](https://www.drupal.org/docs/develop/standards) in our custom code, we use the [PHPCS](https://github.com/squizlabs/PHP_CodeSniffer) lib with the [Coder module](https://www.drupal.org/project/coder). 41 | 42 | > MacOS users, use the following commands with the sufix `-macos`, eg: `make sniff-macos` 43 | 44 | To run it against your project, use the following command from Makefile: 45 | ``` 46 | make sniff 47 | ``` 48 | Some errors in the sniffer can be automatically corrected by running: 49 | ``` 50 | make beautify 51 | ``` 52 | The remaining errors you'll have to fix by yourself. 53 | 54 | ### Deploying 55 | Currently, the integration and delivery features of this boilerplate assume you are using Semaphore CI for building and hosting the environments in Acquia Cloud. Add more generic scripts that can be used to build in other CI systems or deploy in other servers is one of the many improvements we need to do. 56 | 57 | - In the setup of the Semaphore project, place the following command: 58 | ``` 59 | make ci-setup 60 | ``` 61 | - In a job of this project, place the following commands: 62 | ``` 63 | git config --global core.autocrlf true 64 | make ci-check 65 | make ci-install 66 | make ci-tests 67 | make ci-install-prod 68 | make ci-deploy 69 | make ci-clean 70 | docker volume ls -qf dangling=true | xargs -r docker volume rm 71 | docker-cache snapshot 72 | ``` 73 | ### Backing up and Restoring local databases 74 | Once your Drupal application has started you can generate a backup of your database anytime you want. You just need to run the following command: 75 | 76 | ``` 77 | make backup-local-database 78 | ``` 79 | 80 | The backup will be generated inside of the **temp** folder with the name **backup_local_database_{date}.sql**. 81 | 82 | And, if you need to restore any database, just place the SQL dump inside of the **temp** folder with the name **local_database.sql** and then run the following command: 83 | 84 | ``` 85 | make restore-local-database 86 | ``` 87 | 88 | ### Debugging 89 | If you want to use xdebug for debugging your code you just need to use the command below to enable it (or run the command again to disable): 90 | 91 | ``` 92 | make xdebug 93 | ``` 94 | 95 | **Note:** The command above must be executed within the container, in the folder where the Makefile is located. 96 | 97 | You'll receive a message indicating if xdebug is ON or OFF. 98 | 99 | Once xdebug is enabled, add breakpoints in the file you want to debug, go to the section Debug in your VSCode (the [PHP Debug extension](https://marketplace.visualstudio.com/items?itemName=felixfbecker.php-debug "PHP Debug Adapter for Visual Studio Code") is required) and clicks on the button **Start Debugging** (this button it's located on the top, beside a **Listen for XDebug** text). Now, you just have to access the application in your favorite browser and you'll see your VSCode stopping in the breakpoints you added. 100 | 101 | #### To run in Acquia 102 | - Clone the Acquia repository and add a symlink to docroot named web. This way, the autoload generated by composer will work properly in Acquia servers. 103 | 104 | ## Features 105 | - Ready to use Drupal installation 106 | - Drush 107 | - Drupal Console 108 | - Node (with NVM) 109 | - Pre configured Acquia deploy scripts 110 | - Configured Git Hook to execute the code sniffer on pre-push. 111 | - [cweagans/composer-patches](https://github.com/cweagans/composer-patches) as a dependency, so patch support is already in place 112 | 113 | ## FYI 114 | - This boilerplate works best with Drupal ^8.5 115 | - By default, the latest Drupal 8 version is installed 116 | - You should always commit your composer.lock file 117 | -------------------------------------------------------------------------------- /bin/drush: -------------------------------------------------------------------------------- 1 | ../vendor/drush/drush/drush -------------------------------------------------------------------------------- /bin/phpcbf: -------------------------------------------------------------------------------- 1 | ../vendor/squizlabs/php_codesniffer/bin/phpcbf -------------------------------------------------------------------------------- /bin/phpcs: -------------------------------------------------------------------------------- 1 | ../vendor/squizlabs/php_codesniffer/bin/phpcs -------------------------------------------------------------------------------- /bin/phpunit: -------------------------------------------------------------------------------- 1 | ../vendor/phpunit/phpunit/phpunit -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "taller/drupal-enterprise-boilerplate", 3 | "description": "Boilerplate for creating projects with Drupal.", 4 | "repositories": [ 5 | { 6 | "type": "composer", 7 | "url": "https://packages.drupal.org/8" 8 | } 9 | ], 10 | "require": { 11 | "drupal/core": "8.*", 12 | "drush/drush": "^9.7", 13 | "cweagans/composer-patches": "^1.6" 14 | }, 15 | "require-dev": { 16 | "drupal/coder": "8.*", 17 | "drupal-composer/drupal-scaffold": "^2.2", 18 | "drupal/console": "1.*", 19 | "squizlabs/php_codesniffer": "*", 20 | "phpunit/phpunit": "6.*", 21 | "drupal/devel": "^2.1" 22 | }, 23 | "scripts": { 24 | "drupal-scaffold": "DrupalComposer\\DrupalScaffold\\Plugin::scaffold", 25 | "pre-install-cmd": [ 26 | "DrupalProject\\composer\\ScriptHandler::checkComposerVersion" 27 | ], 28 | "pre-update-cmd": [ 29 | "DrupalProject\\composer\\ScriptHandler::checkComposerVersion" 30 | ], 31 | "post-install-cmd": [ 32 | "DrupalProject\\composer\\ScriptHandler::createRequiredFiles" 33 | ], 34 | "post-update-cmd": [ 35 | "DrupalProject\\composer\\ScriptHandler::createRequiredFiles" 36 | ] 37 | }, 38 | "autoload": { 39 | "classmap": [ 40 | "scripts/composer/ScriptHandler.php" 41 | ] 42 | }, 43 | "extra": { 44 | "installer-paths": { 45 | "web/core": [ 46 | "type:drupal-core" 47 | ], 48 | "web/modules/contrib/{$name}": [ 49 | "type:drupal-module" 50 | ], 51 | "web/profiles/contrib/{$name}": [ 52 | "type:drupal-profile" 53 | ], 54 | "web/themes/contrib/{$name}": [ 55 | "type:drupal-theme" 56 | ], 57 | "drush/contrib/{$name}": [ 58 | "type:drupal-drush" 59 | ] 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /config/sync/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TallerWebSolutions/drupal-enterprise-boilerplate/765fc8a7791aed5b4496fa27d9a5accc9981e420/config/sync/.gitkeep -------------------------------------------------------------------------------- /docker-compose.macos.yml: -------------------------------------------------------------------------------- 1 | version: '2.1' 2 | 3 | services: 4 | app: 5 | volumes: 6 | - $HOME/.ssh:/taller/.ssh 7 | - $HOME/.gitconfig:/taller/.gitconfig 8 | - $PWD/temp/.bash_history:/taller/.bash_history 9 | - "code_nfsmount:/taller/app" 10 | 11 | devtools: 12 | container_name: devtools 13 | volumes: 14 | - "code_nfsmount:/taller/app" 15 | 16 | volumes: 17 | mysql: 18 | code_nfsmount: 19 | driver: local 20 | driver_opts: 21 | type: nfs 22 | o: addr=host.docker.internal,rw,nolock,hard,nointr,nfsvers=3 23 | device: ":/System/Volumes/Data/${PWD}" -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '2.1' 2 | 3 | services: 4 | meta: 5 | image: busybox # Necessary to make this container valid. 6 | environment: 7 | - MYSQL_USER=drupal 8 | - MYSQL_DATABASE=drupal 9 | - MYSQL_PASSWORD=password 10 | - MYSQL_ROOT_PASSWORD=password 11 | 12 | app: 13 | extends: 14 | service: meta 15 | image: APP_IMAGE_NAME 16 | build: 17 | context: ./docker/ 18 | args: 19 | USER_ID: '$USER_ID' 20 | GROUP_ID: '$GROUP_ID' 21 | hostname: app 22 | ports: 23 | - 80:80 24 | cap_add: 25 | - ALL 26 | links: 27 | - database:database-host 28 | volumes: 29 | - $HOME/.ssh:/taller/.ssh 30 | - $HOME/.gitconfig:/taller/.gitconfig 31 | - $PWD:/taller/app 32 | - $PWD/temp/.bash_history:/taller/.bash_history 33 | 34 | database: 35 | extends: 36 | service: meta 37 | image: mysql:5.6 38 | container_name: DB_CONTAINER_NAME 39 | volumes: 40 | - mysql:/var/lib/mysql 41 | 42 | devtools: 43 | image: taller/php7-node 44 | entrypoint: ./docker/run-entrypoint.sh 45 | volumes: 46 | - $PWD:/taller/app 47 | 48 | volumes: 49 | mysql: 50 | -------------------------------------------------------------------------------- /docker/000-default.conf: -------------------------------------------------------------------------------- 1 | 2 | # The ServerName directive sets the request scheme, hostname and port that 3 | # the server uses to identify itself. This is used when creating 4 | # redirection URLs. In the context of virtual hosts, the ServerName 5 | # specifies what hostname must appear in the request's Host: header to 6 | # match this virtual host. For the default virtual host (this file) this 7 | # value is not decisive as it is used as a last resort host regardless. 8 | # However, you must set it for any further virtual host explicitly. 9 | #ServerName www.example.com 10 | 11 | ServerAdmin SERVER_ADMIN 12 | DocumentRoot /taller/app/web 13 | 14 | 15 | Options Indexes FollowSymLinks MultiViews 16 | AllowOverride All 17 | Require all granted 18 | 19 | 20 | # Available loglevels: trace8, ..., trace1, debug, info, notice, warn, 21 | # error, crit, alert, emerg. 22 | # It is also possible to configure the loglevel for particular 23 | # modules, e.g. 24 | #LogLevel info ssl:warn 25 | 26 | ErrorLog ${APACHE_LOG_DIR}/error.log 27 | CustomLog ${APACHE_LOG_DIR}/access.log combined 28 | 29 | # For most configuration files from conf-available/, which are 30 | # enabled or disabled at a global level, it is possible to 31 | # include a line for only one particular virtual host. For example the 32 | # following line enables the CGI configuration for this host only 33 | # after it has been globally disabled with "a2disconf". 34 | #Include conf-available/serve-cgi-bin.conf 35 | 36 | 37 | Require all denied 38 | 39 | 40 | 41 | # vim: syntax=apache ts=4 sw=4 sts=4 sr noet 42 | -------------------------------------------------------------------------------- /docker/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM taller/php7-node 2 | 3 | COPY ./000-default.conf /etc/apache2/sites-available/ 4 | 5 | ### IMPORT FILES TO ENVIRONMENT 6 | COPY ./entrypoint.sh /etc/entrypoint.sh 7 | RUN sudo chmod +x /etc/entrypoint.sh 8 | 9 | CMD ["/bin/bash"] 10 | ENTRYPOINT ["/etc/entrypoint.sh"] 11 | -------------------------------------------------------------------------------- /docker/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | # Source NVM scripts 6 | 7 | source /taller/.nvm/nvm.sh 8 | 9 | # Start services and loggers. 10 | # --------------------------- 11 | 12 | sudo service apache2 restart > /tmp/apache2.log 13 | 14 | # Await database. 15 | # ---------------. 16 | 17 | while ! nc -q 1 database-host 3306 > /taller/.bashrc 66 | 67 | # Ensure permissions are correct. 68 | # --------------------------------------- 69 | sudo chmod -R 777 /taller/app/web/sites/default/files 70 | sudo chmod 777 /taller/app/web/sites/default/settings.local.php 71 | sudo chmod +w -R /taller/app/web/sites/default 72 | 73 | echo "" 74 | echo "------------------------------------" 75 | echo "-- Virtual Marchine ready to work --" 76 | echo "------------------------------------" 77 | echo "" 78 | echo "Access your Drupal site at http://$(hostname -i)" 79 | echo "" 80 | 81 | exec "$@" 82 | 83 | -------------------------------------------------------------------------------- /docker/run-entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | source /taller/.nvm/nvm.sh 6 | 7 | echo -e '\nPATH="/taller/app/web/themes/custom/webapp/node_modules/.bin:$PATH"' >> /taller/.bashrc 8 | echo -e '\nPATH="/taller/app/vendor/bin:$PATH"' >> /taller/.bashrc 9 | 10 | sudo chown -R taller:taller . 11 | sudo chown -R taller:taller ./* 12 | 13 | echo "" 14 | echo "------------------------------------------------" 15 | echo "--------- Running on a stale container ---------" 16 | echo "------------------------------------------------" 17 | echo "" 18 | 19 | exec "$@" 20 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "drupal-enterprise-boilerplate", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "repository": { 6 | "type": "git", 7 | "url": "git+https://github.com/TallerWebSolutions/drupal-enterprise-boilerplate.git" 8 | }, 9 | "license": "MIT", 10 | "bugs": { 11 | "url": "https://github.com/TallerWebSolutions/drupal-enterprise-boilerplate/issues" 12 | }, 13 | "homepage": "https://github.com/TallerWebSolutions/drupal-enterprise-boilerplate#readme", 14 | "dependencies": { 15 | "husky": "^3.0.9" 16 | }, 17 | "husky": { 18 | "hooks": { 19 | "pre-push": "./scripts/git-hooks/pre-push.sh" 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /phpcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Make this project use the Drupal coding standard. 4 | 5 | 6 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | ./web/modules/custom/* 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | ./web/modules 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /scripts/acquia_hooks/README.md: -------------------------------------------------------------------------------- 1 | ## What are Cloud Hooks? 2 | 3 | Cloud Hooks is a feature of Acquia Cloud, the Drupal cloud hosting platform. For more information, see https://www.acquia.com/products-services/acquia-dev-cloud. 4 | 5 | The Acquia Cloud Workflow page automates the most common tasks involved in developing a Drupal site: deploying code from a version control system, and migrating code, databases, and files across your Development, Staging, and Production environments. Cloud Hooks allow you to automate other tasks as part of these migrations. 6 | 7 | A Cloud Hook is simply a script in your code repository that Acquia Cloud executes on your behalf when a triggering action occurs. Examples of tasks that you can automate with Cloud Hooks include: 8 | 9 | * Perform Drupal database updates each time new code is deployed. 10 | * "Scrub" your Production database when it is copied to Dev or Staging by removing customer emails or disabling production-only modules. 11 | * Run your test suite or a site performance test each time new code is deployed. 12 | 13 | ## Installing Cloud Hooks 14 | 15 | Cloud hook scripts live in your Acquia Cloud code repository. In each branch of your repo, there is a directory named docroot that contains your site's source code. Cloud hooks live in the directory hooks NEXT TO docroot (not inside of docroot). 16 | 17 | To install the correct directory structure and sample hook scripts, simply copy this repo into your Acquia Cloud repo. 18 | 19 | If you are using Git: 20 | 21 | cd /my/repo 22 | curl -L -o hooks.tar.gz https://github.com/acquia/cloud-hooks/tarball/master 23 | tar xzf hooks.tar.gz 24 | mv acquia-cloud-hooks-* hooks 25 | git add hooks 26 | git commit -m 'Import Cloud hooks directory and sample scripts.' 27 | git push 28 | 29 | If you are using SVN: 30 | 31 | cd /my/repo 32 | curl -L -o hooks.tar.gz https://github.com/acquia/cloud-hooks/tarball/master 33 | tar xzf hooks.tar.gz 34 | mv acquia-cloud-hooks-* hooks 35 | svn add hooks 36 | svn commit -m 'Import Cloud hooks directory and sample scripts.' 37 | 38 | ## Quick Start 39 | 40 | To get an idea of the power of Cloud Hooks, let's run the "Hello, Cloud!" script when new code is deployed in to your Dev environment. 41 | 42 | 1. Install the hello-world.sh script to run on code deployments to Dev. *This example assumes your Dev environment is running the 'master' branch*. 43 | 44 | cd /my/repo 45 | git checkout master 46 | cp hooks/samples/hello-world.sh hooks/dev/post-code-deploy 47 | git commit -a 'Run the hello-world script on post-code-deploy to Dev.' 48 | git push 49 | 50 | 2. Visit the Workflow page in the Acquia Cloud UI. In the Dev environment, select the 'master' branch (if your Dev environment is already running master, select any other tag and then select master again), then press Deploy. 51 | 52 | 3. Scroll down on the Workflow page. When the code deployment task is done, click its "Show" link to see the hook's output. It will look like this: 53 | 54 | Started 55 | Updating s1.dev to deploy master 56 | Deploying master on s1.dev 57 | [05:28:33] Starting hook: post-code-deploy 58 | Executing: /var/www/html/s1.dev/hooks/dev/post-code-deploy/hello-world.sh s1 dev master master s1@svn-3.bjaspan.hosting.acquia.com:s1.git git (as s1@srv-4) 59 | Hello, Cloud! 60 | [05:28:34] Finished hook: post-code-deploy 61 | 62 | You can use the Code drop-down list to put your Dev enviroment back to whatever it was previously deploying. 63 | 64 | ## The Cloud Hooks directory 65 | 66 | The hooks directory in your repo has a directory structure like this: 67 | 68 | /hooks / [env] / [hook] / [script] 69 | 70 | * [env] is a directory whose name is an environment name: 'dev' for Development, 'test' for Staging, and 'prod' for Production, as well as 'common' for all environments. 71 | 72 | * [hook] is a directory whose name is a Cloud Hook name: see below for supported hooks. 73 | 74 | * [script] is a program or shell script within the [env]/[hook] directory. 75 | 76 | Each time a hookable action occurs, Acquia Cloud runs scripts from the directory common/[hook] and [target-env]/[hook]. All scripts in the hook directory are run, in lexicographical (shell glob) order. If one of the hook scripts exits with non-zero status, the remaining hook scripts are skipped, and the task is marked "failed" on the Workflow page so you know to check it. All stdout and stderr output from all the hooks that ran are displayed in the task log on the Workflow page. 77 | 78 | Note that hook scripts must have the Unix "executable" bit in order to run. If your script has the execute bit set when you first add it to Git or SVN, you're all set. Otherwise, to set the execute bit to a file already in your Git repo: 79 | 80 | chmod a+x ./my-hook.sh 81 | git add ./my-hook.sh 82 | git commit -m 'Add execute bit to my-hook.sh' 83 | git push 84 | 85 | If you are using SVN: 86 | 87 | chmod a+x ./my-hook.sh 88 | svn propset svn:executable ON ./my-hook.sh 89 | svn commit -m 'Add execute bit to my-hook.sh' 90 | 91 | ## Sample scripts 92 | 93 | The samples directory contains bare-bones example scripts for each of the supported hooks, plus a variety of useful user-contributed scripts. Each script starts with comments explaining what it is for and how it works. 94 | 95 | Sample scripts currently include: 96 | 97 | * post-code-deploy.tmpl: Template for post-code-deploy hook scripts. 98 | * post-db-copy.tmpl: Template for post-db-copy hook scripts. 99 | * post-files-copy.tmpl: Template for post-files-copy hook scripts. 100 | * update-db.sh: Run drush updatedb to perform database updates. 101 | * db-scrub.sh: Scrub important information from a Drupal database. 102 | * drupal-tests.sh: Run Drupal simpletests. 103 | * rollback.sh: Run designated simpletest testing againsts a branch/tag and rollback on failure. 104 | * newrelic.sh: Example of Acquia Hosting Cloud Hook to notify New Relic API of code version deployments. 105 | 106 | 107 | ## Supported hooks 108 | 109 | This section defines the currently supported Cloud Hooks and the command-line arguments they receive. 110 | 111 | ### post-code-deploy 112 | 113 | The post-code-deploy hook is run whenever you use the Workflow page to deploy new code to an environment, either via drag-drop or by selecting an existing branch or tag from the Code drop-down list. (The post-code-update hook runs after every code commit.) 114 | 115 | Usage: post-code-deploy site target-env source-branch deployed-tag repo-url repo-type 116 | 117 | * site: The site name. This is the same as the Acquia Cloud username for the site. 118 | * target-env: The environment to which code was just deployed. 119 | * source-branch: The code branch or tag being deployed. See below. 120 | * deployed-tag: The code branch or tag being deployed. See below. 121 | * repo-url: The URL of your code repository. 122 | * repo-type: The version control system your site is uing; "git" or "svn". 123 | 124 | The meaning of source-branch and deployed-tag depends on whether you use drag-drop to move code from one environment to another or whether you select a new branch or tag for an environment from the Code drop-down list: 125 | 126 | * With drag-drop, the "source branch" is the branch or tag that the environment you dragged from is set to, and the "deployed tag" is the tag just deployed in the target environment. If source-branch is a branch (does not start with "tags/"), deployed-tag will be a newly created tag pointing at the tip of source-branch. If source-branch is a tag, deployed-tag will be the same tag. 127 | 128 | * With the Code drop-down list, source-branch and deployed-tag will both be the name of branch or tag selected from the drop-down list. 129 | 130 | Example: If the Dev environment is deploying the master branch and you drag Dev code to Stage, the code-deploy arguments will be like: 131 | 132 | post-code-deploy mysite test master tags/2011-11-05 mysite@svn-3.devcloud.hosting.acquia.com:mysite.git git 133 | 134 | ### post-code-update 135 | 136 | The post-code-update hook runs in response to code commits. When you push commits to a Git branch, the post-code-update hooks runs for each environment that is currently running that branch. 137 | 138 | The arguments for post-code-update are the same as for post-code-deploy, with the source-branch and deployed-tag arguments both set to the name of the environment receiving the new code. 139 | 140 | post-code-update only runs if your site is using a Git repository. It does not support SVN. 141 | 142 | ### post-db-copy 143 | 144 | The post-db-copy hook is run whenever you use the Workflow page to copy a database from one environment to another. 145 | 146 | Usage: post-db-copy site target-env db-name source-env 147 | 148 | * site: The site name. This is the same as the Acquia Cloud username for the site. 149 | * target-env: The environment to which the database was copied. 150 | * db-name: The name of the database that was copied. See below. 151 | * source-env: The environment from which the database was copied. 152 | 153 | db-name is not the actual MySQL database name but rather the common name for the database in all environments. Use the drush ah-sql-cli to connect to the actual MySQL database, or use th drush ah-sql-connect command to convert the site name and target environment into the specific MySQL database name and credentials. (The drush sql-cli and sql-connect commands work too, but only if your Drupal installation is set up correctly.) 154 | 155 | Example: To "scrub" your production database by removing all user accounts every time it is copied into your Stage environment, put this script into /hooks/test/post-db-copy/delete-users.sh: 156 | 157 | #!/bin/bash 158 | site=$1 159 | env=$2 160 | db=$3 161 | echo "DELETE FROM users" | drush @$site.$env ah-sql-cli --db=$db 162 | 163 | ### post-files-copy 164 | 165 | The post-files-copy hook is run whenever you use the Workflow page to copy the user-uploaded files directory from one environment to another. 166 | 167 | Usage: post-files-copy site target-env source-env 168 | 169 | * site: The site name. This is the same as the Acquia Cloud username for the site. 170 | * target-env: The environment to which files were copied. 171 | * source-env: The environment from which the files were copied. 172 | 173 | Example: When you use the Workflow page to drag files from Prod to Dev, the files-copy hook will be run like: 174 | 175 | post-files-copy mysite prod dev 176 | -------------------------------------------------------------------------------- /scripts/acquia_hooks/common/post-code-deploy/update.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # Cloud Hook: post-code-deploy 4 | # 5 | # The post-code-deploy hook is run whenever you use the Workflow page to 6 | # deploy new code to an environment, either via drag-drop or by selecting 7 | # an existing branch or tag from the Code drop-down list. See 8 | # ../README.md for details. 9 | # 10 | # Usage: post-code-deploy site target-env source-branch deployed-tag repo-url 11 | # repo-type 12 | 13 | set -e 14 | 15 | # Map the script inputs to convenient names. 16 | site="$1" 17 | target_env="$2" 18 | source_branch="$3" 19 | deployed_tag="$4" 20 | repo_url="$5" 21 | repo_type="$6" 22 | 23 | drush_alias=$site'.'$target_env 24 | 25 | ( 26 | cd /var/www/html/$drush_alias/docroot 27 | 28 | ../bin/drush @$drush_alias updb -y -vdd 29 | ../bin/drush @$drush_alias config-import --partial sync -y -vdd 30 | ../bin/drush @$drush_alias cr -vdd 31 | ) 32 | 33 | if [ "$source_branch" != "$deployed_tag" ]; then 34 | echo "[$(date)] $site.$target_env: Deploying branch $source_branch as $deployed_tag." 35 | else 36 | echo "[$(date)] $site.$target_env: Deployed $deployed_tag." 37 | fi 38 | -------------------------------------------------------------------------------- /scripts/acquia_hooks/common/post-code-update/update.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # Cloud Hook: post-code-deploy 4 | # 5 | # The post-code-deploy hook is run whenever you use the Workflow page to 6 | # deploy new code to an environment, either via drag-drop or by selecting 7 | # an existing branch or tag from the Code drop-down list. See 8 | # ../README.md for details. 9 | # 10 | # Usage: post-code-deploy site target-env source-branch deployed-tag repo-url 11 | # repo-type 12 | 13 | set -e 14 | 15 | # Map the script inputs to convenient names. 16 | site="$1" 17 | target_env="$2" 18 | source_branch="$3" 19 | deployed_tag="$4" 20 | repo_url="$5" 21 | repo_type="$6" 22 | 23 | drush_alias=$site'.'$target_env 24 | 25 | ( 26 | cd /var/www/html/$drush_alias/docroot 27 | 28 | ../bin/drush @$drush_alias updb -y -vdd 29 | ../bin/drush @$drush_alias config-import --partial sync -y -vdd 30 | ../bin/drush @$drush_alias cr -vdd 31 | ) 32 | 33 | if [ "$source_branch" != "$deployed_tag" ]; then 34 | echo "[$(date)] $site.$target_env: Deploying branch $source_branch as $deployed_tag." 35 | else 36 | echo "[$(date)] $site.$target_env: Deployed $deployed_tag." 37 | fi 38 | -------------------------------------------------------------------------------- /scripts/acquia_hooks/common/post-db-copy/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TallerWebSolutions/drupal-enterprise-boilerplate/765fc8a7791aed5b4496fa27d9a5accc9981e420/scripts/acquia_hooks/common/post-db-copy/.gitkeep -------------------------------------------------------------------------------- /scripts/acquia_hooks/common/post-files-copy/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TallerWebSolutions/drupal-enterprise-boilerplate/765fc8a7791aed5b4496fa27d9a5accc9981e420/scripts/acquia_hooks/common/post-files-copy/.gitkeep -------------------------------------------------------------------------------- /scripts/acquia_hooks/common/pre-web-activate/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TallerWebSolutions/drupal-enterprise-boilerplate/765fc8a7791aed5b4496fa27d9a5accc9981e420/scripts/acquia_hooks/common/pre-web-activate/.gitkeep -------------------------------------------------------------------------------- /scripts/acquia_hooks/samples/db-scrub.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # db-copy Cloud hook: db-scrub 4 | # 5 | # Scrub important information from a Drupal database. 6 | # 7 | # Usage: db-scrub.sh site target-env db-name source-env 8 | 9 | site="$1" 10 | target_env="$2" 11 | db_name="$3" 12 | source_env="$4" 13 | 14 | echo "$site.$target_env: Scrubbing database $db_name" 15 | 16 | (cat < 0); 39 | 40 | -- don't leave e-mail addresses, etc in comments table. 41 | -- UPDATE comments SET name='Anonymous', mail='', homepage='http://example.com' WHERE uid=0; 42 | 43 | -- Scrub webform submissions. 44 | -- UPDATE webform_submitted_data set data='*scrubbed*'; 45 | 46 | -- remove sensitive customer data from custom module 47 | -- TRUNCATE custom_customer_lead_data; 48 | 49 | -- USER PASSWORDS 50 | -- These statements assume you want to preserve real passwords for developers. Change 'rid=3' to the 51 | -- developer or test role you want to preserve. 52 | 53 | -- DRUPAL 6 54 | -- Remove passwords unless users have 'developer role' 55 | -- UPDATE users SET pass=md5('devpassword') WHERE uid IN (SELECT uid FROM users_roles WHERE rid=3) AND uid > 0; 56 | 57 | -- Admin user should not be same but not really well known 58 | -- UPDATE users SET pass = MD5('supersecret!') WHERE uid = 1; 59 | 60 | -- DRUPAL 7 61 | -- Drupal 7 requires sites to generate a hashed password specific to their site. A script in the 62 | -- docroot/scripts directory is provided for doing this. From your docroot run the following: 63 | -- 64 | -- scripts/password-hash.sh password 65 | -- 66 | -- this will generate a hash for the password "password". In the following statements replace 67 | -- $REPLACE THIS$ with your generated hash. 68 | 69 | -- Remove passwords unless users have 'developer role' 70 | -- UPDATE users SET pass='$REPLACE THIS$' WHERE uid IN (SELECT uid FROM users_roles WHERE rid=3) AND uid > 0; 71 | 72 | -- Admin user should not be same but not really well known 73 | -- UPDATE users SET pass='$REPLACE THIS$' WHERE uid = 1; 74 | 75 | -- TRUNCATE accesslog; 76 | -- TRUNCATE access; 77 | -- TRUNCATE cache; 78 | -- TRUNCATE cache_filter; 79 | -- TRUNCATE cache_menu; 80 | -- TRUNCATE cache_page; 81 | -- TRUNCATE cache_views; 82 | -- TRUNCATE cache_views_data; 83 | -- TRUNCATE devel_queries; 84 | -- TRUNCATE devel_times; 85 | -- TRUNCATE flood; 86 | -- TRUNCATE history; 87 | -- TRUNCATE search_dataset; 88 | -- TRUNCATE search_index; 89 | -- TRUNCATE search_total; 90 | -- TRUNCATE sessions; 91 | -- TRUNCATE watchdog; 92 | 93 | 94 | EOF 95 | ) | drush @$site.$target_env ah-sql-cli --db=$db_name 96 | -------------------------------------------------------------------------------- /scripts/acquia_hooks/samples/domain_fix.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | 7 | * 8 | * This file should be placed in /hooks/common/post-db-copy 9 | * and will allow domains in domain_access module to be updated 10 | * following database copy. This ensures no manual updates to 11 | * the domains configuration are necessary after copying a db 12 | * between environments. 13 | */ 14 | 15 | $site = $argv[1]; 16 | $env = $argv[2]; 17 | $db = $argv[3]; 18 | $source = $argv[4]; 19 | 20 | // First ensure the domain module is installed. We do this by checking 21 | // existence of the domain table in the appropriate database. 22 | $domain=`echo "SHOW TABLES LIKE 'domain'" | drush @$site.$env ah-sql-cli --db=$db"`; 23 | 24 | if (!$domain) { 25 | $returns[] = "Domain module not installed, aborting"; 26 | } 27 | else { 28 | 29 | // Build a list of domains that require changing after the db has copied. 30 | // Each element of the $domains array should be keyed the machine name 31 | // and have key/value pairs of environment => URL. 32 | $domains = array( 33 | 'foo_com' => array( 34 | 'dev' => 'dev.foo.com', 35 | 'test' => 'stg.foo.com', 36 | 'prod' => 'foo.com', 37 | ), 38 | 'bar_com' => array( 39 | 'dev' => 'dev.bar.com', 40 | 'test' => 'stg.bar.com', 41 | 'prod' => 'bar.com', 42 | ), 43 | 'example_com' => array( 44 | 'dev' => 'dev.example.com', 45 | 'test' => 'stg.example.com', 46 | 'prod' => 'example.com', 47 | ), 48 | ); 49 | 50 | // Iterate through the domains and update the record to the URL specified. 51 | // If the domain machine name does not exist, the record will be skipped. 52 | foreach ($domains as $domain => $info) { 53 | if (isset($info[$env])) { 54 | $to = $info[$env]; 55 | $returns[] = "Updating domain table to update $domain to $to"; 56 | echo "UPDATE domain SET subdomain = "$to" where machine_name = "$domain" | drush @$site.$env ah-sql-cli --db=$db"; 57 | } 58 | } 59 | } 60 | 61 | // This output will be visible from the insights dashboard to reveal 62 | // which domains have been updated. 63 | foreach ($returns as $output) { 64 | print "$output\n"; 65 | } 66 | 67 | -------------------------------------------------------------------------------- /scripts/acquia_hooks/samples/drupal-tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Cloud Hook: drupal-tests 4 | # 5 | # Run Drupal simpletests in the target environment using drush test-run. 6 | 7 | site="$1" 8 | target_env="$2" 9 | 10 | # Select the tests to run. Run "drush help test-run" for options. 11 | TESTS="UserRegistrationTestCase" 12 | # To run all tests (very slow!), uncomment this line. 13 | TESTS="--all" 14 | 15 | # Enable the simpletest module if it is not already enabled. 16 | simpletest=`drush @$site.$target_env pm-info simpletest | perl -F'/[\s:]+/' -lane '/Status/ && print $F[2]'` 17 | if [ "$simpletest" = "disabled" ]; then 18 | echo "Temporarily enabling simpletest module." 19 | drush @$site.$target_env pm-enable simpletest --yes 20 | fi 21 | 22 | # Run the tests. 23 | drush @$site.$target_env test-run $TESTS 24 | status=$? 25 | 26 | # If we enabled simpletest, disable it. 27 | if [ "$simpletest" = "disabled" ]; then 28 | echo "Disabling simpletest module." 29 | drush @$site.$target_env pm-disable simpletest --yes 30 | fi 31 | 32 | exit $status 33 | -------------------------------------------------------------------------------- /scripts/acquia_hooks/samples/drush-cache-clear.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # Cloud Hook: drush-cache-clear 4 | # 5 | # Run drush cache-clear all in the target environment. This script works as 6 | # any Cloud hook. 7 | 8 | 9 | # Map the script inputs to convenient names. 10 | site=$1 11 | target_env=$2 12 | drush_alias=$site'.'$target_env 13 | 14 | # Execute a standard drush command. 15 | drush @$drush_alias cc all 16 | -------------------------------------------------------------------------------- /scripts/acquia_hooks/samples/hello-world.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # This sample Cloud Hook script just echos "Hello, Cloud!" to standard 4 | # output. It will work in any hook directory. 5 | 6 | echo "Hello, Cloud!" 7 | 8 | -------------------------------------------------------------------------------- /scripts/acquia_hooks/samples/newrelic/README.md: -------------------------------------------------------------------------------- 1 | # Example of Acquia Cloud Hook to notify New Relic API of code version deployments 2 | 3 | Installation Steps (assumes New Relic subscription setup and Acquia Cloud Hooks installed in repo): 4 | 5 | * Login to New Relic and goto https://rpm.newrelic.com/accounts/(UserID)/applications/(ApplicationID)/deployments/instructions 6 | * From the instrtuctions get your application_id and your x-api-key. Store these variables and a username you wish to send to New Relic in $HOME/newrelic_settings file on your Acquia Cloud Server (see example file). 7 | * Set the execution bit to on i.e. chmod a+x newrelic_settings 8 | * Add newrelic.sh to dev, test, prod or common post-code-deploy hook. 9 | 10 | 11 | -------------------------------------------------------------------------------- /scripts/acquia_hooks/samples/newrelic/newrelic.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # This sample a Cloud Hook script to update New Relic whenever there is a new code deployment 4 | 5 | site=$1 # The site name. This is the same as the Acquia Cloud username for the site. 6 | targetenv=$2 # The environment to which code was just deployed. 7 | sourcebranch=$3 # The code branch or tag being deployed. 8 | deployedtag=$4 # The code branch or tag being deployed. 9 | repourl=$5 # The URL of your code repository. 10 | repotype=$6 # The version control system your site is using; "git" or "svn". 11 | 12 | 13 | #Load the New Relic APPID and APPKEY variables. 14 | . $HOME/newrelic_settings 15 | 16 | curl -s -H "x-api-key:$APIKEY" -d "deployment[application_id]=$APPID" -d "deployment[host]=localhost" -d "deployment[description]=$deployedtag deployed to $site:$targetenv" -d "deployment[revision]=$deployedtag" -d "deployment[changelog]=$deployedtag deployed to $site:$targetenv" -d "deployment[user]=$username" https://api.newrelic.com/deployments.xml 17 | -------------------------------------------------------------------------------- /scripts/acquia_hooks/samples/newrelic/newrelic_settings: -------------------------------------------------------------------------------- 1 | APIKEY='123456abcdefgh1234567890abcdefgh1234567890abc1234567890' 2 | APPID='123456' 3 | username='myname@mydomain.com' 4 | -------------------------------------------------------------------------------- /scripts/acquia_hooks/samples/post-code-deploy.tmpl: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # Cloud Hook: post-code-deploy 4 | # 5 | # The post-code-deploy hook is run whenever you use the Workflow page to 6 | # deploy new code to an environment, either via drag-drop or by selecting 7 | # an existing branch or tag from the Code drop-down list. See 8 | # ../README.md for details. 9 | # 10 | # Usage: post-code-deploy site target-env source-branch deployed-tag repo-url 11 | # repo-type 12 | 13 | site="$1" 14 | target_env="$2" 15 | source_branch="$3" 16 | deployed_tag="$4" 17 | repo_url="$5" 18 | repo_type="$6" 19 | 20 | if [ "$source_branch" != "$deployed_tag" ]; then 21 | echo "$site.$target_env: Deployed branch $source_branch as $deployed_tag." 22 | else 23 | echo "$site.$target_env: Deployed $deployed_tag." 24 | fi 25 | -------------------------------------------------------------------------------- /scripts/acquia_hooks/samples/post-db-copy.tmpl: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # Cloud Hook: post-db-copy 4 | # 5 | # The post-db-copy hook is run whenever you use the Workflow page to copy a 6 | # database from one environment to another. See ../README.md for 7 | # details. 8 | # 9 | # Usage: post-db-copy site target-env db-name source-env 10 | 11 | site="$1" 12 | target_env="$2" 13 | db_name="$3" 14 | source_env="$4" 15 | 16 | echo "$site.$target_env: Received copy of database $db_name from $source_env." 17 | -------------------------------------------------------------------------------- /scripts/acquia_hooks/samples/post-files-copy.tmpl: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # Cloud Hook: post-files-copy 4 | # 5 | # The post-files-copy hook is run whenever you use the Workflow page to 6 | # copy the files directory from one environment to another. See 7 | # ../README.md for details. 8 | # 9 | # Usage: post-files-copy site target-env source-env 10 | 11 | site="$1" 12 | target_env="$2" 13 | source_env="$3" 14 | 15 | echo "$site.$target_env: Received copy of files from $source_env." 16 | -------------------------------------------------------------------------------- /scripts/acquia_hooks/samples/rollback_demo/README.md: -------------------------------------------------------------------------------- 1 | #Sample cloud hook to rollback code deploy on testing failure 2 | 3 | This hook utilizes the simpletest module to test code base during deployment and automatically 4 | rollback to the last deployed set of code on test failure. Since pre-code-deploy hooks don't exist 5 | yet we store original code source in the origsource variable in the rollback settings file stored in 6 | the $HOME dir. This file also lists drush test-run tests to be run and the number of attempts to make 7 | before giving up. 8 | 9 | Installation Steps (assumes ah cloud hooks installed in Version Control Software) 10 | 11 | * Copy rollback.sh into your dev, stage, prod, or common hooks directory. 12 | * SCP or SFTP rollback_settings to your $HOME dir on your Acquia Host Server. 13 | * $TEST settings are available SimpleTests (or core Testing module in D7+). You may use '--all' for all tests (very slow). See http://drupal.org/simpletest for details. 14 | * Edit rollback_settings to your existing code base ($ORIGSOURCE), test setting ($TEST) and number of attempts ($ATTEMPTS). Ensure execute bits are set on both files. (i.e. chmod a+x rollback_settings and chmod a+x rollback.sh) 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /scripts/acquia_hooks/samples/rollback_demo/rollback.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Cloud Hook: tests_rollback 4 | # 5 | # Run Drupal simpletests in the target environment using drush test-run. On failure, 6 | # rollback to last deployed code set 7 | # 8 | # implements Cloud_hook post_code_deploy 9 | # @todo needs to have pre_code_deploy for proper handling of files. 10 | # 11 | 12 | 13 | site="$1" 14 | target_env="$2" 15 | sourcebranch=$3 # The code branch or tag being deployed. 16 | deployedtag=$4 # The code branch or tag being deployed. 17 | repourl=$5 # The URL of your code repository. 18 | repotype=$6 # The version control system your site is using; "git" or "svn". 19 | 20 | #load variable settings from $HOME/rollback_settings 21 | #Check rollback_settings exists; if not alert and exit 22 | if [ -x "$HOME/rollback_settings" ]; then 23 | . $HOME/rollback_settings 24 | else 25 | echo "rollback_settings file not found in $HOME or not able to include (check execution bit)" 26 | exit 1 27 | fi 28 | 29 | #check attempts variable has any number of tries left. To prevent infinite loops. 30 | if [ "$ATTEMPTS" -le 0 ]; then 31 | echo "Maximum Number of attempts exceeded! Exiting." 32 | exit 1 33 | fi 34 | 35 | #now set the variable and append it into the settings file. 36 | ORIGATTEMPTS=$ATTEMPTS 37 | let "ATTEMPTS-=1" 38 | sed -i "s/ATTEMPTS=$ORIGATTEMPTS/ATTEMPTS=$ATTEMPTS/" $HOME/rollback_settings 39 | 40 | #initialize exit code so we can exit with 0 after rollback 41 | extcode=0 42 | 43 | # Enable the simpletest module if it is not already enabled. 44 | simpletest=`drush @$site.$target_env pm-info simpletest | perl -F'/[\s:]+/' -lane '/Status/ && print $F[2]'` 45 | if [ "$simpletest" = "disabled" ]; then 46 | echo "Temporarily enabling simpletest module." 47 | drush @$site.$target_env pm-enable simpletest --yes 48 | fi 49 | 50 | # Run the tests. 51 | CMD=`drush @$site.$target_env test-run $TESTS` 52 | 53 | #test output from drush 54 | if [ $? -ne 0 ]; then 55 | 56 | #sanity check to make sure we have a $origsource to fall back to. 57 | if [ $ORIGSOURCE ]; then 58 | #if simpletests fail tell the user and launch a new job rolling back to the original source 59 | echo "Testing failed on deploy rolling back to $ORIGSOURCE" 60 | echo "Executing: drush @$site.$target_env ac-code-path-deploy $ORIGSOURCE" 61 | drush @$site.$target_env ac-code-path-deploy $ORIGSOURCE 62 | else #something is very wrong should never get here, if we do notify and quit. 63 | echo "Cannot rollback. No fallback source identified." 64 | exit 1 65 | fi 66 | #set exitcode to fail so this code base does not deploy 67 | extcode=1 68 | 69 | else 70 | 71 | #simpletests passed! Inform user then clear and set rollback_settings to new code base 72 | echo "Testing passed on deploy of $deployedtag" 73 | sed -i "s/ORIGSOURCE=$ORIGSOURCE/ORIGSOURCE=$deployedtag/" $HOME/rollback_settings 74 | extcode=0 75 | 76 | fi 77 | 78 | # If we enabled simpletest, disable it. 79 | if [ "$simpletest" = "disabled" ]; then 80 | echo "Disabling simpletest module." 81 | drush @$site.$target_env pm-disable simpletest --yes 82 | fi 83 | 84 | #cleanly exit 85 | exit $extcode 86 | 87 | 88 | 89 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /scripts/acquia_hooks/samples/rollback_demo/rollback_settings: -------------------------------------------------------------------------------- 1 | ORIGSOURCE="master" 2 | TESTS="UserRegistrationTestCase" 3 | ATTEMPTS=3 4 | -------------------------------------------------------------------------------- /scripts/acquia_hooks/samples/slack/README.md: -------------------------------------------------------------------------------- 1 | #Example of Acquia Cloud Hook to notify Slack of deployments 2 | 3 | Installation Steps (assumes Slack subscription setup and Acquia Cloud Hooks installed in repo): 4 | 5 | * Login to Slack and click the "Integrations" link in the header 6 | * Follow the instructions and make note of "Your Unique Webhook URL." Store this URL in $HOME/slack_settings file on your Acquia Cloud Server (see example file). 7 | * Set the execution bit to on i.e. chmod a+x slack_settings 8 | * Add slack.sh to dev, test, prod or common post-cody-deploy hook. 9 | 10 | -------------------------------------------------------------------------------- /scripts/acquia_hooks/samples/slack/slack.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # Cloud Hook: post-code-deploy 4 | # 5 | # The post-code-deploy hook is run whenever you use the Workflow page to 6 | # deploy new code to an environment, either via drag-drop or by selecting 7 | # an existing branch or tag from the Code drop-down list. See 8 | # ../README.md for details. 9 | # 10 | # Usage: post-code-deploy site target-env source-branch deployed-tag repo-url 11 | # repo-type 12 | 13 | site="$1" 14 | target_env="$2" 15 | source_branch="$3" 16 | deployed_tag="$4" 17 | repo_url="$5" 18 | repo_type="$6" 19 | 20 | # Load the Slack webhook URL (which is not stored in this repo). 21 | . $HOME/slack_settings 22 | 23 | # Post deployment notice to Slack 24 | curl -X POST --data-urlencode "payload={\"text\": \"A new deployment has been made to *$target_env* using tag *$deployed_tag* on branch *$source_branch*.\", \"icon_emoji\": \":information_source:\"}" $SLACK_WEBHOOK_URL 25 | -------------------------------------------------------------------------------- /scripts/acquia_hooks/samples/slack/slack_settings: -------------------------------------------------------------------------------- 1 | SLACK_WEBHOOK_URL=https://example.slack.com/services/hooks/incoming-webhook?token=TOKEN 2 | -------------------------------------------------------------------------------- /scripts/acquia_hooks/samples/update-db.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # Cloud Hook: update-db 4 | # 5 | # Run drush updatedb in the target environment. This script works as 6 | # any Cloud hook. 7 | 8 | site="$1" 9 | target_env="$2" 10 | 11 | drush @$site.$target_env updatedb --yes 12 | -------------------------------------------------------------------------------- /scripts/backup-local-database.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | BACKUP_NAME="backup_local_database_$(date +"%Y%m%d%H%M%S").sql" 6 | mysqldump --host=database-host --user=root --password=$MYSQL_ROOT_PASSWORD $MYSQL_DATABASE > /taller/app/temp/$BACKUP_NAME -------------------------------------------------------------------------------- /scripts/beautify.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | docker-compose run --rm devtools \ 6 | ./bin/phpcbf --ignore=node_modules ./web/modules/custom ./web/modules/sandbox ./web/themes/custom 7 | -------------------------------------------------------------------------------- /scripts/ci-check.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | COMPOSER_LOCK_OUTDATED=$(docker-compose run --rm devtools \ 4 | composer validate --no-check-all --no-check-publish | grep 'The lock file is not up to date with the latest changes in composer.json') 5 | 6 | set -e 7 | 8 | if [[ ! -z "$COMPOSER_LOCK_OUTDATED" ]]; then 9 | echo '[ERROR] The composer.lock file is not up to date with composer.json, execute "composer update nothing".' 10 | exit 1 11 | fi 12 | -------------------------------------------------------------------------------- /scripts/ci-deploy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | ACQUIA_DIR=/home/runner/CI_ACQUIA_DIR 6 | 7 | ssh-keyscan ACQUIA_GIT_DOMAIN >> /home/runner/.ssh/known_hosts 8 | 9 | git clone --branch master ACQUIA_GIT_REPO $ACQUIA_DIR 10 | 11 | # Checkout to the branch, if doesn't exit create and checkout to it. 12 | ( 13 | cd $ACQUIA_DIR 14 | 15 | if [[ `git branch -a | grep -w $BRANCH_NAME` ]]; then 16 | git checkout ${BRANCH_NAME} 17 | else 18 | git checkout -b ${BRANCH_NAME} 19 | fi 20 | ) 21 | 22 | # Remove git submodules. 23 | find $SEMAPHORE_PROJECT_DIR -type d -name ".git" | xargs sudo rm -rf 24 | 25 | # Remove old directories. 26 | sudo rm -Rf $ACQUIA_DIR/docroot 27 | sudo rm -Rf $ACQUIA_DIR/config 28 | sudo rm -Rf $ACQUIA_DIR/hooks 29 | sudo rm -Rf $ACQUIA_DIR/vendor 30 | sudo rm -Rf $ACQUIA_DIR/bin 31 | sudo rm -Rf $ACQUIA_DIR/composer.json 32 | sudo rm -Rf $ACQUIA_DIR/composer.lock 33 | sudo rm -Rf $ACQUIA_DIR/README.md 34 | 35 | # Copy new directories. 36 | cp -r ./web $ACQUIA_DIR/docroot 37 | cp -r ./config $ACQUIA_DIR/config 38 | cp -r ./scripts/acquia_hooks $ACQUIA_DIR/hooks 39 | cp -r ./vendor $ACQUIA_DIR/vendor 40 | cp -r ./bin $ACQUIA_DIR/bin 41 | cp ./composer.json $ACQUIA_DIR/composer.json 42 | cp ./composer.lock $ACQUIA_DIR/composer.lock 43 | cp ./README.md $ACQUIA_DIR/README.md 44 | 45 | # Make Acquia hooks executable. 46 | sudo chmod -R +x $ACQUIA_DIR/hooks 47 | 48 | # Configure GIT. 49 | git config --global core.autocrlf true 50 | git config --global user.email "GIT_USER_EMAIL" 51 | git config --global user.name "GIT_USER_NAME" 52 | 53 | ( 54 | cd $ACQUIA_DIR 55 | 56 | # Add all the things. 57 | git add --all . 58 | 59 | # Commit only if there's something new. 60 | if [ ! "$(git status | grep 'nothing to commit')" ]; then 61 | echo 'Has things to commit.' 62 | 63 | git commit -m "Deploy by Taller's SemaphoreCI: $REVISION." 64 | git push origin $BRANCH_NAME 65 | else 66 | echo 'Nothing to commit.' 67 | fi 68 | ) 69 | -------------------------------------------------------------------------------- /scripts/ci-install-prod.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | docker-compose run --rm devtools \ 6 | composer install --no-dev --prefer-dist --optimize-autoloader 7 | -------------------------------------------------------------------------------- /scripts/ci-install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | docker-compose run --rm devtools \ 6 | composer install --prefer-dist 7 | -------------------------------------------------------------------------------- /scripts/ci-setup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | export GROUP_ID=$(id -g) 4 | docker-cache restore 5 | export USER_ID=$(id -u) 6 | git config --global user.email "GIT_USER_EMAIL" 7 | git config --global user.name "GIT_USER_NAME" 8 | -------------------------------------------------------------------------------- /scripts/ci-tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | docker-compose run --rm devtools \ 6 | ./bin/phpunit --testsuite=custom --debug --verbose --colors 7 | -------------------------------------------------------------------------------- /scripts/composer/ScriptHandler.php: -------------------------------------------------------------------------------- 1 | exists(static::getDrupalRoot(getcwd()) . '/autoload.php')) { 23 | \DrupalComposer\DrupalScaffold\Plugin::scaffold($event); 24 | } 25 | } 26 | 27 | public static function createRequiredFiles(Event $event) { 28 | $fs = new Filesystem(); 29 | $root = static::getDrupalRoot(getcwd()); 30 | 31 | $dirs = [ 32 | 'modules', 33 | 'profiles', 34 | 'themes', 35 | ]; 36 | 37 | // Required for unit testing 38 | foreach ($dirs as $dir) { 39 | if (!$fs->exists($root . '/'. $dir)) { 40 | $fs->mkdir($root . '/'. $dir); 41 | $fs->touch($root . '/'. $dir . '/.gitkeep'); 42 | } 43 | } 44 | 45 | 46 | // Prepare the settings file for installation 47 | if (!$fs->exists($root . '/sites/default/settings.php')) { 48 | $fs->chmod($root . '/sites/default/', 0755); 49 | $fs->copy($root . '/sites/default/default.settings.php', $root . '/sites/default/settings.php'); 50 | $fs->chmod($root . '/sites/default/settings.php', 0666); 51 | $event->getIO()->write("Create a sites/default/settings.php file with chmod 0666"); 52 | } 53 | 54 | // Prepare the services file for installation 55 | if (!$fs->exists($root . '/sites/default/services.yml')) { 56 | $fs->chmod($root . '/sites/default/', 0755); 57 | $fs->copy($root . '/sites/default/default.services.yml', $root . '/sites/default/services.yml'); 58 | $fs->chmod($root . '/sites/default/services.yml', 0666); 59 | $event->getIO()->write("Create a sites/default/services.yml file with chmod 0666"); 60 | } 61 | 62 | // Create the files directory with chmod 0777 63 | if (!$fs->exists($root . '/sites/default/files')) { 64 | $oldmask = umask(0); 65 | $fs->mkdir($root . '/sites/default/files', 0777); 66 | umask($oldmask); 67 | $event->getIO()->write("Create a sites/default/files directory with chmod 0777"); 68 | } 69 | } 70 | 71 | public static function dependencyCleanup() { 72 | $fs = new Filesystem(); 73 | $root = getcwd(); 74 | 75 | $directories = array( 76 | "bin", 77 | "web/core", 78 | "web/libraries", 79 | "web/modules/contrib", 80 | "web/profiles/contrib", 81 | "web/themes/contrib", 82 | "drush/contrib", 83 | "vendor", 84 | ); 85 | 86 | $directories = array_map(function ($directory) use ($root) { 87 | return $root.'/'.$directory; 88 | }, $directories); 89 | 90 | $fs->remove($directories); 91 | 92 | echo "(!) Now you can run 'composer install' to get the latest dependencies."; 93 | 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /scripts/git-hooks/pre-push.sh: -------------------------------------------------------------------------------- 1 | 2 | #!/bin/bash 3 | 4 | set -e 5 | 6 | make sniff -------------------------------------------------------------------------------- /scripts/restore-local-database.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | DATABASE_DUMP="/taller/app/temp/local_database.sql" 6 | [ ! -f $DATABASE_DUMP ] && { echo "$DATABASE_DUMP not found."; exit 1; } 7 | 8 | mysql --host=database-host --user=root --password=$MYSQL_ROOT_PASSWORD $MYSQL_DATABASE < $DATABASE_DUMP -------------------------------------------------------------------------------- /scripts/sniff.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | docker-compose run --rm devtools \ 6 | ./bin/phpcs --ignore=node_modules ./web/modules/custom ./web/modules/sandbox ./web/themes/custom 7 | -------------------------------------------------------------------------------- /scripts/toggle-xdebug.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | restartApache() { 6 | sudo service apache2 restart 7 | } 8 | 9 | if [[ -z `ls -l /etc/php/7.2/apache2/conf.d/ | grep xdebug` ]]; then 10 | sudo phpenmod xdebug 11 | echo -e '\n\nXDebug: ON.\n' 12 | restartApache 13 | else 14 | sudo phpdismod xdebug 15 | echo -e '\nXDebug: OFF.\n' 16 | restartApache 17 | fi 18 | -------------------------------------------------------------------------------- /scripts/update.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | drush updb -y 6 | drush config-import --partial -y 7 | drush cr 8 | -------------------------------------------------------------------------------- /temp/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TallerWebSolutions/drupal-enterprise-boilerplate/765fc8a7791aed5b4496fa27d9a5accc9981e420/temp/.gitkeep -------------------------------------------------------------------------------- /web/modules/custom/README.md: -------------------------------------------------------------------------------- 1 | # Custom Modules Folder 2 | This folder should contains all custom modules created to support a given 3 | feature or functionality. All modules placed under this folder should be 4 | specific for the project. For each new module, a new folder should be created 5 | under `custom/` folder and place all the related files for that 6 | module inside it. 7 | 8 | ## Standards 9 | 10 | * Naming Convention: `_feature` 11 | 12 | ## Custom Module Folder Structure 13 | 14 | Please, follow instructions on Drupal docs [here](https://www.drupal.org/docs/8/creating-custom-modules) 15 | -------------------------------------------------------------------------------- /web/modules/sandbox/README.md: -------------------------------------------------------------------------------- 1 | # Sandbox Modules Folder 2 | This folder should contains all custom modules created to support a generic 3 | feature or functionality. All modules placed under this folder should be 4 | project agnostic. It should support a feature that could be used among 5 | several projects. For each new module, a new folder should be created 6 | under `sandbox/` folder and place all the related files for that 7 | module inside it. 8 | 9 | ## Standards 10 | 11 | * Naming Convention: `` 12 | 13 | ## Custom Module Folder Structure 14 | 15 | Please, follow instructions on Drupal docs [here](https://www.drupal.org/docs/8/creating-custom-modules) 16 | -------------------------------------------------------------------------------- /web/sites/default/settings.php: -------------------------------------------------------------------------------- 1 | 'databasename', 81 | * 'username' => 'sqlusername', 82 | * 'password' => 'sqlpassword', 83 | * 'host' => 'localhost', 84 | * 'port' => '3306', 85 | * 'driver' => 'mysql', 86 | * 'prefix' => '', 87 | * 'collation' => 'utf8mb4_general_ci', 88 | * ); 89 | * @endcode 90 | */ 91 | $databases = []; 92 | 93 | /** 94 | * Customizing database settings. 95 | * 96 | * Many of the values of the $databases array can be customized for your 97 | * particular database system. Refer to the sample in the section above as a 98 | * starting point. 99 | * 100 | * The "driver" property indicates what Drupal database driver the 101 | * connection should use. This is usually the same as the name of the 102 | * database type, such as mysql or sqlite, but not always. The other 103 | * properties will vary depending on the driver. For SQLite, you must 104 | * specify a database file name in a directory that is writable by the 105 | * webserver. For most other drivers, you must specify a 106 | * username, password, host, and database name. 107 | * 108 | * Transaction support is enabled by default for all drivers that support it, 109 | * including MySQL. To explicitly disable it, set the 'transactions' key to 110 | * FALSE. 111 | * Note that some configurations of MySQL, such as the MyISAM engine, don't 112 | * support it and will proceed silently even if enabled. If you experience 113 | * transaction related crashes with such configuration, set the 'transactions' 114 | * key to FALSE. 115 | * 116 | * For each database, you may optionally specify multiple "target" databases. 117 | * A target database allows Drupal to try to send certain queries to a 118 | * different database if it can but fall back to the default connection if not. 119 | * That is useful for primary/replica replication, as Drupal may try to connect 120 | * to a replica server when appropriate and if one is not available will simply 121 | * fall back to the single primary server (The terms primary/replica are 122 | * traditionally referred to as master/slave in database server documentation). 123 | * 124 | * The general format for the $databases array is as follows: 125 | * @code 126 | * $databases['default']['default'] = $info_array; 127 | * $databases['default']['replica'][] = $info_array; 128 | * $databases['default']['replica'][] = $info_array; 129 | * $databases['extra']['default'] = $info_array; 130 | * @endcode 131 | * 132 | * In the above example, $info_array is an array of settings described above. 133 | * The first line sets a "default" database that has one primary database 134 | * (the second level default). The second and third lines create an array 135 | * of potential replica databases. Drupal will select one at random for a given 136 | * request as needed. The fourth line creates a new database with a name of 137 | * "extra". 138 | * 139 | * You can optionally set prefixes for some or all database table names 140 | * by using the 'prefix' setting. If a prefix is specified, the table 141 | * name will be prepended with its value. Be sure to use valid database 142 | * characters only, usually alphanumeric and underscore. If no prefixes 143 | * are desired, leave it as an empty string ''. 144 | * 145 | * To have all database names prefixed, set 'prefix' as a string: 146 | * @code 147 | * 'prefix' => 'main_', 148 | * @endcode 149 | * 150 | * Per-table prefixes are deprecated as of Drupal 8.2, and will be removed in 151 | * Drupal 9.0. After that, only a single prefix for all tables will be 152 | * supported. 153 | * 154 | * To provide prefixes for specific tables, set 'prefix' as an array. 155 | * The array's keys are the table names and the values are the prefixes. 156 | * The 'default' element is mandatory and holds the prefix for any tables 157 | * not specified elsewhere in the array. Example: 158 | * @code 159 | * 'prefix' => array( 160 | * 'default' => 'main_', 161 | * 'users' => 'shared_', 162 | * 'sessions' => 'shared_', 163 | * 'role' => 'shared_', 164 | * 'authmap' => 'shared_', 165 | * ), 166 | * @endcode 167 | * You can also use a reference to a schema/database as a prefix. This may be 168 | * useful if your Drupal installation exists in a schema that is not the default 169 | * or you want to access several databases from the same code base at the same 170 | * time. 171 | * Example: 172 | * @code 173 | * 'prefix' => array( 174 | * 'default' => 'main.', 175 | * 'users' => 'shared.', 176 | * 'sessions' => 'shared.', 177 | * 'role' => 'shared.', 178 | * 'authmap' => 'shared.', 179 | * ); 180 | * @endcode 181 | * NOTE: MySQL and SQLite's definition of a schema is a database. 182 | * 183 | * Advanced users can add or override initial commands to execute when 184 | * connecting to the database server, as well as PDO connection settings. For 185 | * example, to enable MySQL SELECT queries to exceed the max_join_size system 186 | * variable, and to reduce the database connection timeout to 5 seconds: 187 | * @code 188 | * $databases['default']['default'] = array( 189 | * 'init_commands' => array( 190 | * 'big_selects' => 'SET SQL_BIG_SELECTS=1', 191 | * ), 192 | * 'pdo' => array( 193 | * PDO::ATTR_TIMEOUT => 5, 194 | * ), 195 | * ); 196 | * @endcode 197 | * 198 | * WARNING: The above defaults are designed for database portability. Changing 199 | * them may cause unexpected behavior, including potential data loss. See 200 | * https://www.drupal.org/developing/api/database/configuration for more 201 | * information on these defaults and the potential issues. 202 | * 203 | * More details can be found in the constructor methods for each driver: 204 | * - \Drupal\Core\Database\Driver\mysql\Connection::__construct() 205 | * - \Drupal\Core\Database\Driver\pgsql\Connection::__construct() 206 | * - \Drupal\Core\Database\Driver\sqlite\Connection::__construct() 207 | * 208 | * Sample Database configuration format for PostgreSQL (pgsql): 209 | * @code 210 | * $databases['default']['default'] = array( 211 | * 'driver' => 'pgsql', 212 | * 'database' => 'databasename', 213 | * 'username' => 'sqlusername', 214 | * 'password' => 'sqlpassword', 215 | * 'host' => 'localhost', 216 | * 'prefix' => '', 217 | * ); 218 | * @endcode 219 | * 220 | * Sample Database configuration format for SQLite (sqlite): 221 | * @code 222 | * $databases['default']['default'] = array( 223 | * 'driver' => 'sqlite', 224 | * 'database' => '/path/to/databasefilename', 225 | * ); 226 | * @endcode 227 | */ 228 | 229 | /** 230 | * Location of the site configuration files. 231 | * 232 | * The $config_directories array specifies the location of file system 233 | * directories used for configuration data. On install, the "sync" directory is 234 | * created. This is used for configuration imports. The "active" directory is 235 | * not created by default since the default storage for active configuration is 236 | * the database rather than the file system. (This can be changed. See "Active 237 | * configuration settings" below). 238 | * 239 | * The default location for the "sync" directory is inside a randomly-named 240 | * directory in the public files path. The setting below allows you to override 241 | * the "sync" location. 242 | * 243 | * If you use files for the "active" configuration, you can tell the 244 | * Configuration system where this directory is located by adding an entry with 245 | * array key CONFIG_ACTIVE_DIRECTORY. 246 | * 247 | * Example: 248 | * @code 249 | * $config_directories = array( 250 | * CONFIG_SYNC_DIRECTORY => '/directory/outside/webroot', 251 | * ); 252 | * @endcode 253 | */ 254 | $config_directories = []; 255 | 256 | /** 257 | * Settings: 258 | * 259 | * $settings contains environment-specific configuration, such as the files 260 | * directory and reverse proxy address, and temporary configuration, such as 261 | * security overrides. 262 | * 263 | * @see \Drupal\Core\Site\Settings::get() 264 | */ 265 | 266 | /** 267 | * Salt for one-time login links, cancel links, form tokens, etc. 268 | * 269 | * This variable will be set to a random value by the installer. All one-time 270 | * login links will be invalidated if the value is changed. Note that if your 271 | * site is deployed on a cluster of web servers, you must ensure that this 272 | * variable has the same value on each server. 273 | * 274 | * For enhanced security, you may set this variable to the contents of a file 275 | * outside your document root; you should also ensure that this file is not 276 | * stored with backups of your database. 277 | * 278 | * Example: 279 | * @code 280 | * $settings['hash_salt'] = file_get_contents('/home/example/salt.txt'); 281 | * @endcode 282 | */ 283 | $settings['hash_salt'] = ''; 284 | 285 | /** 286 | * Deployment identifier. 287 | * 288 | * Drupal's dependency injection container will be automatically invalidated and 289 | * rebuilt when the Drupal core version changes. When updating contributed or 290 | * custom code that changes the container, changing this identifier will also 291 | * allow the container to be invalidated as soon as code is deployed. 292 | */ 293 | # $settings['deployment_identifier'] = \Drupal::VERSION; 294 | 295 | /** 296 | * Access control for update.php script. 297 | * 298 | * If you are updating your Drupal installation using the update.php script but 299 | * are not logged in using either an account with the "Administer software 300 | * updates" permission or the site maintenance account (the account that was 301 | * created during installation), you will need to modify the access check 302 | * statement below. Change the FALSE to a TRUE to disable the access check. 303 | * After finishing the upgrade, be sure to open this file again and change the 304 | * TRUE back to a FALSE! 305 | */ 306 | $settings['update_free_access'] = FALSE; 307 | 308 | /** 309 | * External access proxy settings: 310 | * 311 | * If your site must access the Internet via a web proxy then you can enter the 312 | * proxy settings here. Set the full URL of the proxy, including the port, in 313 | * variables: 314 | * - $settings['http_client_config']['proxy']['http']: The proxy URL for HTTP 315 | * requests. 316 | * - $settings['http_client_config']['proxy']['https']: The proxy URL for HTTPS 317 | * requests. 318 | * You can pass in the user name and password for basic authentication in the 319 | * URLs in these settings. 320 | * 321 | * You can also define an array of host names that can be accessed directly, 322 | * bypassing the proxy, in $settings['http_client_config']['proxy']['no']. 323 | */ 324 | # $settings['http_client_config']['proxy']['http'] = 'http://proxy_user:proxy_pass@example.com:8080'; 325 | # $settings['http_client_config']['proxy']['https'] = 'http://proxy_user:proxy_pass@example.com:8080'; 326 | # $settings['http_client_config']['proxy']['no'] = ['127.0.0.1', 'localhost']; 327 | 328 | /** 329 | * Reverse Proxy Configuration: 330 | * 331 | * Reverse proxy servers are often used to enhance the performance 332 | * of heavily visited sites and may also provide other site caching, 333 | * security, or encryption benefits. In an environment where Drupal 334 | * is behind a reverse proxy, the real IP address of the client should 335 | * be determined such that the correct client IP address is available 336 | * to Drupal's logging, statistics, and access management systems. In 337 | * the most simple scenario, the proxy server will add an 338 | * X-Forwarded-For header to the request that contains the client IP 339 | * address. However, HTTP headers are vulnerable to spoofing, where a 340 | * malicious client could bypass restrictions by setting the 341 | * X-Forwarded-For header directly. Therefore, Drupal's proxy 342 | * configuration requires the IP addresses of all remote proxies to be 343 | * specified in $settings['reverse_proxy_addresses'] to work correctly. 344 | * 345 | * Enable this setting to get Drupal to determine the client IP from 346 | * the X-Forwarded-For header (or $settings['reverse_proxy_header'] if set). 347 | * If you are unsure about this setting, do not have a reverse proxy, 348 | * or Drupal operates in a shared hosting environment, this setting 349 | * should remain commented out. 350 | * 351 | * In order for this setting to be used you must specify every possible 352 | * reverse proxy IP address in $settings['reverse_proxy_addresses']. 353 | * If a complete list of reverse proxies is not available in your 354 | * environment (for example, if you use a CDN) you may set the 355 | * $_SERVER['REMOTE_ADDR'] variable directly in settings.php. 356 | * Be aware, however, that it is likely that this would allow IP 357 | * address spoofing unless more advanced precautions are taken. 358 | */ 359 | # $settings['reverse_proxy'] = TRUE; 360 | 361 | /** 362 | * Specify every reverse proxy IP address in your environment. 363 | * This setting is required if $settings['reverse_proxy'] is TRUE. 364 | */ 365 | # $settings['reverse_proxy_addresses'] = ['a.b.c.d', ...]; 366 | 367 | /** 368 | * Set this value if your proxy server sends the client IP in a header 369 | * other than X-Forwarded-For. 370 | */ 371 | # $settings['reverse_proxy_header'] = 'X_CLUSTER_CLIENT_IP'; 372 | 373 | /** 374 | * Set this value if your proxy server sends the client protocol in a header 375 | * other than X-Forwarded-Proto. 376 | */ 377 | # $settings['reverse_proxy_proto_header'] = 'X_FORWARDED_PROTO'; 378 | 379 | /** 380 | * Set this value if your proxy server sends the client protocol in a header 381 | * other than X-Forwarded-Host. 382 | */ 383 | # $settings['reverse_proxy_host_header'] = 'X_FORWARDED_HOST'; 384 | 385 | /** 386 | * Set this value if your proxy server sends the client protocol in a header 387 | * other than X-Forwarded-Port. 388 | */ 389 | # $settings['reverse_proxy_port_header'] = 'X_FORWARDED_PORT'; 390 | 391 | /** 392 | * Set this value if your proxy server sends the client protocol in a header 393 | * other than Forwarded. 394 | */ 395 | # $settings['reverse_proxy_forwarded_header'] = 'FORWARDED'; 396 | 397 | /** 398 | * Page caching: 399 | * 400 | * By default, Drupal sends a "Vary: Cookie" HTTP header for anonymous page 401 | * views. This tells a HTTP proxy that it may return a page from its local 402 | * cache without contacting the web server, if the user sends the same Cookie 403 | * header as the user who originally requested the cached page. Without "Vary: 404 | * Cookie", authenticated users would also be served the anonymous page from 405 | * the cache. If the site has mostly anonymous users except a few known 406 | * editors/administrators, the Vary header can be omitted. This allows for 407 | * better caching in HTTP proxies (including reverse proxies), i.e. even if 408 | * clients send different cookies, they still get content served from the cache. 409 | * However, authenticated users should access the site directly (i.e. not use an 410 | * HTTP proxy, and bypass the reverse proxy if one is used) in order to avoid 411 | * getting cached pages from the proxy. 412 | */ 413 | # $settings['omit_vary_cookie'] = TRUE; 414 | 415 | 416 | /** 417 | * Cache TTL for client error (4xx) responses. 418 | * 419 | * Items cached per-URL tend to result in a large number of cache items, and 420 | * this can be problematic on 404 pages which by their nature are unbounded. A 421 | * fixed TTL can be set for these items, defaulting to one hour, so that cache 422 | * backends which do not support LRU can purge older entries. To disable caching 423 | * of client error responses set the value to 0. Currently applies only to 424 | * page_cache module. 425 | */ 426 | # $settings['cache_ttl_4xx'] = 3600; 427 | 428 | /** 429 | * Expiration of cached forms. 430 | * 431 | * Drupal's Form API stores details of forms in a cache and these entries are 432 | * kept for at least 6 hours by default. Expired entries are cleared by cron. 433 | * 434 | * @see \Drupal\Core\Form\FormCache::setCache() 435 | */ 436 | # $settings['form_cache_expiration'] = 21600; 437 | 438 | /** 439 | * Class Loader. 440 | * 441 | * If the APC extension is detected, the Symfony APC class loader is used for 442 | * performance reasons. Detection can be prevented by setting 443 | * class_loader_auto_detect to false, as in the example below. 444 | */ 445 | # $settings['class_loader_auto_detect'] = FALSE; 446 | 447 | /* 448 | * If the APC extension is not detected, either because APC is missing or 449 | * because auto-detection has been disabled, auto-loading falls back to 450 | * Composer's ClassLoader, which is good for development as it does not break 451 | * when code is moved in the file system. You can also decorate the base class 452 | * loader with another cached solution than the Symfony APC class loader, as 453 | * all production sites should have a cached class loader of some sort enabled. 454 | * 455 | * To do so, you may decorate and replace the local $class_loader variable. For 456 | * example, to use Symfony's APC class loader without automatic detection, 457 | * uncomment the code below. 458 | */ 459 | /* 460 | if ($settings['hash_salt']) { 461 | $prefix = 'drupal.' . hash('sha256', 'drupal.' . $settings['hash_salt']); 462 | $apc_loader = new \Symfony\Component\ClassLoader\ApcClassLoader($prefix, $class_loader); 463 | unset($prefix); 464 | $class_loader->unregister(); 465 | $apc_loader->register(); 466 | $class_loader = $apc_loader; 467 | } 468 | */ 469 | 470 | /** 471 | * Authorized file system operations: 472 | * 473 | * The Update Manager module included with Drupal provides a mechanism for 474 | * site administrators to securely install missing updates for the site 475 | * directly through the web user interface. On securely-configured servers, 476 | * the Update manager will require the administrator to provide SSH or FTP 477 | * credentials before allowing the installation to proceed; this allows the 478 | * site to update the new files as the user who owns all the Drupal files, 479 | * instead of as the user the webserver is running as. On servers where the 480 | * webserver user is itself the owner of the Drupal files, the administrator 481 | * will not be prompted for SSH or FTP credentials (note that these server 482 | * setups are common on shared hosting, but are inherently insecure). 483 | * 484 | * Some sites might wish to disable the above functionality, and only update 485 | * the code directly via SSH or FTP themselves. This setting completely 486 | * disables all functionality related to these authorized file operations. 487 | * 488 | * @see https://www.drupal.org/node/244924 489 | * 490 | * Remove the leading hash signs to disable. 491 | */ 492 | # $settings['allow_authorize_operations'] = FALSE; 493 | 494 | /** 495 | * Default mode for directories and files written by Drupal. 496 | * 497 | * Value should be in PHP Octal Notation, with leading zero. 498 | */ 499 | # $settings['file_chmod_directory'] = 0775; 500 | # $settings['file_chmod_file'] = 0664; 501 | 502 | /** 503 | * Public file base URL: 504 | * 505 | * An alternative base URL to be used for serving public files. This must 506 | * include any leading directory path. 507 | * 508 | * A different value from the domain used by Drupal to be used for accessing 509 | * public files. This can be used for a simple CDN integration, or to improve 510 | * security by serving user-uploaded files from a different domain or subdomain 511 | * pointing to the same server. Do not include a trailing slash. 512 | */ 513 | # $settings['file_public_base_url'] = 'http://downloads.example.com/files'; 514 | 515 | /** 516 | * Public file path: 517 | * 518 | * A local file system path where public files will be stored. This directory 519 | * must exist and be writable by Drupal. This directory must be relative to 520 | * the Drupal installation directory and be accessible over the web. 521 | */ 522 | # $settings['file_public_path'] = 'sites/default/files'; 523 | 524 | /** 525 | * Private file path: 526 | * 527 | * A local file system path where private files will be stored. This directory 528 | * must be absolute, outside of the Drupal installation directory and not 529 | * accessible over the web. 530 | * 531 | * Note: Caches need to be cleared when this value is changed to make the 532 | * private:// stream wrapper available to the system. 533 | * 534 | * See https://www.drupal.org/documentation/modules/file for more information 535 | * about securing private files. 536 | */ 537 | # $settings['file_private_path'] = ''; 538 | 539 | /** 540 | * Session write interval: 541 | * 542 | * Set the minimum interval between each session write to database. 543 | * For performance reasons it defaults to 180. 544 | */ 545 | # $settings['session_write_interval'] = 180; 546 | 547 | /** 548 | * String overrides: 549 | * 550 | * To override specific strings on your site with or without enabling the Locale 551 | * module, add an entry to this list. This functionality allows you to change 552 | * a small number of your site's default English language interface strings. 553 | * 554 | * Remove the leading hash signs to enable. 555 | * 556 | * The "en" part of the variable name, is dynamic and can be any langcode of 557 | * any added language. (eg locale_custom_strings_de for german). 558 | */ 559 | # $settings['locale_custom_strings_en'][''] = [ 560 | # 'forum' => 'Discussion board', 561 | # '@count min' => '@count minutes', 562 | # ]; 563 | 564 | /** 565 | * A custom theme for the offline page: 566 | * 567 | * This applies when the site is explicitly set to maintenance mode through the 568 | * administration page or when the database is inactive due to an error. 569 | * The template file should also be copied into the theme. It is located inside 570 | * 'core/modules/system/templates/maintenance-page.html.twig'. 571 | * 572 | * Note: This setting does not apply to installation and update pages. 573 | */ 574 | # $settings['maintenance_theme'] = 'bartik'; 575 | 576 | /** 577 | * PHP settings: 578 | * 579 | * To see what PHP settings are possible, including whether they can be set at 580 | * runtime (by using ini_set()), read the PHP documentation: 581 | * http://php.net/manual/ini.list.php 582 | * See \Drupal\Core\DrupalKernel::bootEnvironment() for required runtime 583 | * settings and the .htaccess file for non-runtime settings. 584 | * Settings defined there should not be duplicated here so as to avoid conflict 585 | * issues. 586 | */ 587 | 588 | /** 589 | * If you encounter a situation where users post a large amount of text, and 590 | * the result is stripped out upon viewing but can still be edited, Drupal's 591 | * output filter may not have sufficient memory to process it. If you 592 | * experience this issue, you may wish to uncomment the following two lines 593 | * and increase the limits of these variables. For more information, see 594 | * http://php.net/manual/pcre.configuration.php. 595 | */ 596 | # ini_set('pcre.backtrack_limit', 200000); 597 | # ini_set('pcre.recursion_limit', 200000); 598 | 599 | /** 600 | * Active configuration settings. 601 | * 602 | * By default, the active configuration is stored in the database in the 603 | * {config} table. To use a different storage mechanism for the active 604 | * configuration, do the following prior to installing: 605 | * - Create an "active" directory and declare its path in $config_directories 606 | * as explained under the 'Location of the site configuration files' section 607 | * above in this file. To enhance security, you can declare a path that is 608 | * outside your document root. 609 | * - Override the 'bootstrap_config_storage' setting here. It must be set to a 610 | * callable that returns an object that implements 611 | * \Drupal\Core\Config\StorageInterface. 612 | * - Override the service definition 'config.storage.active'. Put this 613 | * override in a services.yml file in the same directory as settings.php 614 | * (definitions in this file will override service definition defaults). 615 | */ 616 | # $settings['bootstrap_config_storage'] = ['Drupal\Core\Config\BootstrapConfigStorageFactory', 'getFileStorage']; 617 | 618 | /** 619 | * Configuration overrides. 620 | * 621 | * To globally override specific configuration values for this site, 622 | * set them here. You usually don't need to use this feature. This is 623 | * useful in a configuration file for a vhost or directory, rather than 624 | * the default settings.php. 625 | * 626 | * Note that any values you provide in these variable overrides will not be 627 | * viewable from the Drupal administration interface. The administration 628 | * interface displays the values stored in configuration so that you can stage 629 | * changes to other environments that don't have the overrides. 630 | * 631 | * There are particular configuration values that are risky to override. For 632 | * example, overriding the list of installed modules in 'core.extension' is not 633 | * supported as module install or uninstall has not occurred. Other examples 634 | * include field storage configuration, because it has effects on database 635 | * structure, and 'core.menu.static_menu_link_overrides' since this is cached in 636 | * a way that is not config override aware. Also, note that changing 637 | * configuration values in settings.php will not fire any of the configuration 638 | * change events. 639 | */ 640 | # $config['system.file']['path']['temporary'] = '/tmp'; 641 | # $config['system.site']['name'] = 'My Drupal site'; 642 | # $config['system.theme']['default'] = 'stark'; 643 | # $config['user.settings']['anonymous'] = 'Visitor'; 644 | 645 | /** 646 | * Fast 404 pages: 647 | * 648 | * Drupal can generate fully themed 404 pages. However, some of these responses 649 | * are for images or other resource files that are not displayed to the user. 650 | * This can waste bandwidth, and also generate server load. 651 | * 652 | * The options below return a simple, fast 404 page for URLs matching a 653 | * specific pattern: 654 | * - $config['system.performance']['fast_404']['exclude_paths']: A regular 655 | * expression to match paths to exclude, such as images generated by image 656 | * styles, or dynamically-resized images. The default pattern provided below 657 | * also excludes the private file system. If you need to add more paths, you 658 | * can add '|path' to the expression. 659 | * - $config['system.performance']['fast_404']['paths']: A regular expression to 660 | * match paths that should return a simple 404 page, rather than the fully 661 | * themed 404 page. If you don't have any aliases ending in htm or html you 662 | * can add '|s?html?' to the expression. 663 | * - $config['system.performance']['fast_404']['html']: The html to return for 664 | * simple 404 pages. 665 | * 666 | * Remove the leading hash signs if you would like to alter this functionality. 667 | */ 668 | # $config['system.performance']['fast_404']['exclude_paths'] = '/\/(?:styles)|(?:system\/files)\//'; 669 | # $config['system.performance']['fast_404']['paths'] = '/\.(?:txt|png|gif|jpe?g|css|js|ico|swf|flv|cgi|bat|pl|dll|exe|asp)$/i'; 670 | # $config['system.performance']['fast_404']['html'] = '404 Not Found

Not Found

The requested URL "@path" was not found on this server.

'; 671 | 672 | /** 673 | * Load services definition file. 674 | */ 675 | $settings['container_yamls'][] = $app_root . '/' . $site_path . '/services.yml'; 676 | 677 | /** 678 | * Override the default service container class. 679 | * 680 | * This is useful for example to trace the service container for performance 681 | * tracking purposes, for testing a service container with an error condition or 682 | * to test a service container that throws an exception. 683 | */ 684 | # $settings['container_base_class'] = '\Drupal\Core\DependencyInjection\Container'; 685 | 686 | /** 687 | * Override the default yaml parser class. 688 | * 689 | * Provide a fully qualified class name here if you would like to provide an 690 | * alternate implementation YAML parser. The class must implement the 691 | * \Drupal\Component\Serialization\SerializationInterface interface. 692 | */ 693 | # $settings['yaml_parser_class'] = NULL; 694 | 695 | /** 696 | * Trusted host configuration. 697 | * 698 | * Drupal core can use the Symfony trusted host mechanism to prevent HTTP Host 699 | * header spoofing. 700 | * 701 | * To enable the trusted host mechanism, you enable your allowable hosts 702 | * in $settings['trusted_host_patterns']. This should be an array of regular 703 | * expression patterns, without delimiters, representing the hosts you would 704 | * like to allow. 705 | * 706 | * For example: 707 | * @code 708 | * $settings['trusted_host_patterns'] = array( 709 | * '^www\.example\.com$', 710 | * ); 711 | * @endcode 712 | * will allow the site to only run from www.example.com. 713 | * 714 | * If you are running multisite, or if you are running your site from 715 | * different domain names (eg, you don't redirect http://www.example.com to 716 | * http://example.com), you should specify all of the host patterns that are 717 | * allowed by your site. 718 | * 719 | * For example: 720 | * @code 721 | * $settings['trusted_host_patterns'] = array( 722 | * '^example\.com$', 723 | * '^.+\.example\.com$', 724 | * '^example\.org$', 725 | * '^.+\.example\.org$', 726 | * ); 727 | * @endcode 728 | * will allow the site to run off of all variants of example.com and 729 | * example.org, with all subdomains included. 730 | */ 731 | 732 | /** 733 | * The default list of directories that will be ignored by Drupal's file API. 734 | * 735 | * By default ignore node_modules and bower_components folders to avoid issues 736 | * with common frontend tools and recursive scanning of directories looking for 737 | * extensions. 738 | * 739 | * @see file_scan_directory() 740 | * @see \Drupal\Core\Extension\ExtensionDiscovery::scanDirectory() 741 | */ 742 | $settings['file_scan_ignore_directories'] = [ 743 | 'node_modules', 744 | 'bower_components', 745 | ]; 746 | 747 | /** 748 | * The default number of entities to update in a batch process. 749 | * 750 | * This is used by update and post-update functions that need to go through and 751 | * change all the entities on a site, so it is useful to increase this number 752 | * if your hosting configuration (i.e. RAM allocation, CPU speed) allows for a 753 | * larger number of entities to be processed in a single batch run. 754 | */ 755 | $settings['entity_update_batch_size'] = 50; 756 | 757 | /** 758 | * Connect to db in Acquia. 759 | * 760 | * Checks if the script is running on ACQUIA and require custom configurations for database connection. 761 | */ 762 | if (isset($_ENV['AH_SITE_ENVIRONMENT']) && file_exists('/var/www/site-php')) { 763 | require '/var/www/site-php/' . $_ENV['AH_SITE_GROUP'] . '/' . $_ENV['AH_SITE_GROUP'] . '-settings.inc'; 764 | } 765 | 766 | /** 767 | * Load local development override configuration, if available. 768 | * 769 | * Use settings.local.php to override variables on secondary (staging, 770 | * development, etc) installations of this site. Typically used to disable 771 | * caching, JavaScript/CSS compression, re-routing of outgoing emails, and 772 | * other things that should not happen on development and testing sites. 773 | * 774 | * Keep this code block at the end of this file to take full effect. 775 | */ 776 | if (file_exists($app_root . '/' . $site_path . '/settings.local.php')) { 777 | include $app_root . '/' . $site_path . '/settings.local.php'; 778 | } 779 | 780 | $config_directories['sync'] = './../config/sync'; 781 | -------------------------------------------------------------------------------- /web/sites/template.settings.local.php: -------------------------------------------------------------------------------- 1 | '{MYSQL_DATABASE}', 138 | 'username' => '{MYSQL_USER}', 139 | 'password' => '{MYSQL_PASSWORD}', 140 | 'prefix' => '', 141 | 'host' => 'database-host', 142 | 'port' => '', 143 | 'namespace' => 'Drupal\\Core\\Database\\Driver\\mysql', 144 | 'driver' => 'mysql', 145 | ); 146 | -------------------------------------------------------------------------------- /web/themes/custom/README.md: -------------------------------------------------------------------------------- 1 | # Custom Themes Folder 2 | This folder should contains all custom themes created to define the 3 | presentation layer. All themes placed under this folder should be 4 | specific for the project. For each new theme, a new folder should be created 5 | under `custom/` folder and place all the related files for that 6 | theme inside it. 7 | 8 | ## Standards 9 | 10 | * Naming Convention: `_feature` 11 | 12 | ## Custom Theme Folder Structure 13 | 14 | Please, follow instructions on Drupal docs [here](https://www.drupal.org/docs/theming-drupal/drupal-theme-folder-structure) 15 | --------------------------------------------------------------------------------