├── .circleci └── config.yml ├── .editorconfig ├── .github └── dependabot.yml ├── .gitignore ├── .lando.yml ├── CHANGELOG ├── LICENSE ├── README.md ├── Vagrantfile ├── ansible.cfg ├── build.sh ├── conf ├── .yamllint ├── dev-vars.yml ├── develop.yml ├── prod-db.yml ├── prod-front.yml ├── prod-lb.yml ├── prod-vars.yml ├── production.yml ├── project.yml ├── requirements.yml ├── server.inventory ├── stage-vars.yml ├── stage.yml ├── upcloud.yml ├── vagrant.yml ├── vagrant_local.yml └── variables.yml ├── docs ├── Setup.md ├── continuous-integration.md ├── development.md ├── hacks.md ├── index.html ├── index.md ├── information_architecture.md ├── modules.md ├── navigation.md ├── testing.md ├── theme.md ├── upcloud.md └── views.md ├── drupal ├── .dockerignore ├── .gitignore ├── .lando.yml ├── build.sh ├── codeception.yml ├── composer.json ├── conf │ ├── settings.local.php │ └── site.yml ├── drush │ ├── Commands │ │ └── CheckBootstrapCommands.php │ ├── drushrc.php │ └── wundertools.aliases.drushrc.php ├── files │ └── .gitignore ├── gdpr.json ├── package-lock.json ├── package.json ├── phpcs.xml ├── scripts │ └── composer │ │ └── ScriptHandler.php ├── silta │ ├── nginx.Dockerfile │ ├── php.Dockerfile │ ├── shell.Dockerfile │ ├── silta-prod.yml │ └── silta.yml ├── sync │ ├── .gitkeep │ └── .htaccess ├── tests │ ├── _bootstrap.php │ ├── _envs │ │ ├── circleci.yml │ │ └── parameters.yml │ ├── _support │ │ ├── AcceptanceTester.php │ │ ├── FunctionalTester.php │ │ ├── Helper │ │ │ ├── Acceptance.php │ │ │ ├── Functional.php │ │ │ └── Unit.php │ │ └── UnitTester.php │ ├── acceptance.suite.yml │ ├── acceptance │ │ ├── ExampleAcceptanceCest.php │ │ └── _bootstrap.php │ ├── functional.suite.yml │ ├── functional │ │ ├── ExampleFunctionalCest.php │ │ └── _bootstrap.php │ ├── unit.suite.yml │ └── unit │ │ └── _bootstrap.php └── web │ ├── .dockerignore │ ├── modules │ └── custom │ │ └── README.txt │ ├── sites │ └── default │ │ ├── services.yml │ │ ├── settings.lando.php │ │ └── settings.php │ └── themes │ └── custom │ └── README.txt ├── drush.sh ├── local_ansible_roles └── README.md ├── provision.sh ├── sync.sh ├── syncdb.sh └── syncdb_local.sh /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2.1 2 | 3 | orbs: 4 | silta: silta/silta@1 5 | 6 | workflows: 7 | version: 2 8 | commit: 9 | jobs: 10 | - silta/drupal-validate: 11 | name: validate 12 | drupal-root: drupal 13 | post-validation: 14 | - run: echo "You can add additional validation here!" 15 | 16 | - silta/drupal-build-deploy: &build-deploy 17 | name: build-deploy 18 | drupal-root: drupal 19 | codebase-build: 20 | - silta/drupal-composer-install 21 | - silta/npm-install-build: 22 | path: . # Adjust to the location of your package.json 23 | context: silta_dev 24 | filters: 25 | branches: 26 | ignore: production 27 | 28 | - silta/drupal-build-deploy: 29 | # Extend the build-deploy configuration for the production environment. 30 | <<: *build-deploy 31 | name: build-deploy-prod 32 | silta_config: silta/silta.yml,silta/silta-prod.yml 33 | context: silta_finland 34 | filters: 35 | branches: 36 | only: production 37 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # Drupal editor configuration normalization 2 | # @see http://editorconfig.org/ 3 | 4 | # This is the top-most .editorconfig file; do not search in parent directories. 5 | root = true 6 | 7 | # All files. 8 | [*] 9 | end_of_line = LF 10 | indent_style = space 11 | indent_size = 2 12 | charset = utf-8 13 | trim_trailing_whitespace = true 14 | insert_final_newline = true 15 | 16 | [composer.json] 17 | indent_size = 4 18 | 19 | [*.py] 20 | # Four-space indentation 21 | indent_size = 4 22 | indent_style = space 23 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: composer 4 | directory: "/drupal" 5 | schedule: 6 | interval: daily 7 | time: "03:00" 8 | open-pull-requests-limit: 10 9 | - package-ecosystem: npm 10 | directory: "/drupal" 11 | schedule: 12 | interval: daily 13 | time: "03:00" 14 | open-pull-requests-limit: 10 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generic ones 2 | .DS_Store 3 | .vagrant 4 | drupal/current 5 | drupal/builds 6 | drupal/.make_cache 7 | drupal/drupal.sqlite 8 | .idea 9 | ansible 10 | ansible.vault 11 | local.* 12 | test/composer.lock 13 | test/vendor 14 | test/bin 15 | VERSION 16 | 17 | # Ignore ansible retry files 18 | conf/*.retry 19 | 20 | # Ignore secrets folder 21 | secrets 22 | 23 | # Ignore python virtualenv created by provision.sh 24 | .virtualenv 25 | -------------------------------------------------------------------------------- /.lando.yml: -------------------------------------------------------------------------------- 1 | name: provision 2 | services: 3 | python: 4 | type: python:3.6 5 | build_as_root: 6 | - "apt-get update -y" 7 | # Resolve the locale language issue. 8 | - "apt-get install locales locales-all sudo -y" 9 | - "locale-gen en_US.UTF-8" 10 | run_as_root: 11 | - rm -rf /app/ansible 12 | - git clone git@github.com:wunderio/WunderMachina.git --branch master --single-branch ansible 13 | - install -d -m 777 /var/www/.local/share 14 | - cd /app/ansible && pip install pipenv 15 | - cd /app/ansible && pipenv install --python 3.6 16 | overrides: 17 | environment: 18 | # Set the path to the Ansible vault file. Save the password to the `~/.ssh/ansible.vault` file. 19 | WT_ANSIBLE_VAULT_FILE: "/user/.ssh/ansible.vault" 20 | tooling: 21 | provision: 22 | description: Run Ansible provisioning commands 23 | cmd: 24 | - python: ./provision.sh 25 | pip: 26 | description: Run pip commands 27 | service: python 28 | pipenv: 29 | description: Run pipenv commands 30 | service: python 31 | -------------------------------------------------------------------------------- /CHANGELOG: -------------------------------------------------------------------------------- 1 | 11 2 | Local ssl cert path has been updated. For vagrant to work ssl_certificate path need to be changed into ssl/certificate.crt in conf/vagrant.yml 3 | 4 | 10 5 | Vagrant provisioning has been changed to run ansible_local so there is no need for host to have ansible installed at all. 6 | Make sure the provision.sh is updated to make it work with vagrant as there is no more vagrant provided ansible provisioner inventory. 7 | Instead we rely now on dynamic inventory extracting the data from vagrant ssh-config (see ansible/inventory.py) 8 | 9 | 9 10 | (optional) WunderTools now supports having a custom repository for WunderTools 11 | and build.sh (only on GitHub for now). To use it, create a fork of WunderTools 12 | and/or build.sh, add your repo url and branches to conf/project.yml, here is an 13 | example: 14 | 15 | buildsh: 16 | enabled: true 17 | branch: develop 18 | repository: badrange/build.sh 19 | revision: 20 | wundertools: 21 | repository: badrange/WunderTools 22 | branch: feature/custom-wundertools-repository 23 | 24 | 8 25 | varnish.control_key variable added (optional). This will set up a Varnish control key in 26 | /etc/varnish/secret and makes it a php environment variable for use in 27 | vagrant.settings.php. Check vagrant.settings.php for Varnish 4 compatible 28 | settings. 29 | 30 | 7 31 | hash_behaviour changed to merge so that you don't need to copy the whole dict object from defaults when you just want to override some of the values in it. 32 | To comply with this add "hash_behaviour=merge" to projects ansible.cfg file. 33 | 34 | 6 35 | wkv_site_env variable is now required. 36 | 37 | 5 38 | Varnish role has added parametrized probe url (probe_resource_url) that needs to be added to varnish configs or provisioning will fail. 39 | This should usually point to _ping.php e.g. : probe_resource_url: "_ping.php" 40 | See ansible/playbook/roles/varnish/defaults/main.yml for reference. 41 | 42 | 4 43 | Removed local_ansible_roles linking from wundertools. Make sure you have anisble.cfg in your repo root that defines roles_path like this: 44 | roles_path=./local_ansible_roles:./ansible/playbook/roles 45 | 46 | 3 47 | Centos7 branch is now the default (master) branch. If your project is still using centos6 update the ansible branch variable to "centos6" in the conf/project.yml or consider upgrading your project to use centos7. 48 | For centos7 projects you should update the ansible branch to "master" as the centos7 branch will be deprecated and will not receive any updates in the future. 49 | 50 | 2 51 | Possibility to use external repository for drupal. In that case you need to define following variables in conf/project.yml 52 | externaldrupal: 53 | remote: [external drupal repository url] 54 | branch: [branch to use from the external repository] 55 | Repository should have drupal installation directly under the repository root. 56 | 57 | 1 58 | Added support for managed version updates for build.sh 59 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | {description} 294 | Copyright (C) {year} {fullname} 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | {signature of Ty Coon}, 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Wundertools 2 | 3 | **Note!** This project is no longer in active development and receives only maintenance fixes. For new projects, we recommend instead. 4 | 5 | Reference setup with Ansible & Vagrant for Drupal 8 projects. For Drupal 7 support, see the [Drupal 7 branch](https://github.com/wunderio/WunderTools/tree/drupal7). 6 | 7 | [![CircleCI](https://circleci.com/gh/wunderio/WunderTools.svg?style=svg)](https://circleci.com/gh/wunderio/WunderTools) 8 | 9 | ## Requirements 10 | - Install [Vagrant](https://www.vagrantup.com/downloads.html) 1.9.2 or greater 11 | - Install [vagrant-cachier](https://github.com/fgrehm/vagrant-cachier) 12 | `vagrant plugin install vagrant-cachier` 13 | - Install [Virtualbox](https://www.virtualbox.org/wiki/Downloads) 5.1 or greater. Note version 5.1.24 has a known issue that breaks nfs, do not use it, version 5.1.22 s known to work. 14 | - Make sure you have python2.7 also installedi (For OS X should be default). 15 | 16 | ## Creating a new project 17 | 18 | If you are starting a new project, see: [Setup.md](docs/Setup.md) 19 | 20 | 21 | ## Setting up an existing project locally 22 | 23 | Find the IP-address and hostname that this local environment is configured to use from `conf/vagrant_local.yml` and add 24 | it to your own `/etc/hosts` file: 25 | 26 | `10.0.13.37 local.wundertools.com` 27 | 28 | Let Vagrant create your new machine: 29 | 30 | `vagrant up` 31 | 32 | This will create a new Virtual machine on your computer, configure it with all the nice bells & whistles that you can 33 | think of (like MariaDB, nginx, Varnish, memcached and whatnot) and start it up for you. This will also install vagrant plugin depedencies, if you encounter issues while installing the plugins then you could use: `vagrant --skip-dependency-manager up` 34 | 35 | SSH into your box and build and install Drupal: 36 | 37 | ``` 38 | vagrant ssh 39 | cd /vagrant/drupal 40 | ./build.sh new 41 | ``` 42 | 43 | If this is a project with an existing production/staging server, you should probably sync the production database now, 44 | from your local machine: 45 | 46 | `sync.sh` 47 | 48 | Drush is usable without ssh access with the drush.sh script e.g: 49 | 50 | ```bash 51 | $ ./drush.sh cr 52 | ``` 53 | 54 | To open up ssh access to the virtual machine: 55 | 56 | ```bash 57 | $ vagrant ssh 58 | ``` 59 | 60 | 61 | ## Optional additions 62 | 63 | ### WunderSecrets 64 | 65 | You can setup additional git repository for shared secrets. You need to set that in `conf/project.yml` -> `wundersecrets: remote: git@github.com:username/repo`. 66 | 67 | Only the file `ansible.yml` is loaded from that repository. 68 | 69 | ## Useful things 70 | 71 | At the moment IP is configured in 72 | Vagrantfile 73 | variable INSTANCE_IP 74 | 75 | Varnish responds to 76 | http://x.x.x.x/ 77 | 78 | Nginx responds to 79 | http://x.x.x.x:8080/ 80 | 81 | Solr responds to 82 | http://x.x.x.x:8983/solr 83 | 84 | MailHOG responds to 85 | http://x.x.x.x:8025 86 | or 87 | https://local.project.tld/mailhog/ 88 | 89 | Docs are in 90 | http://x.x.x.x:8080/index.html 91 | You can setup the dir where the docs are taken from and their URL from the 92 | variables.yml file. 93 | 94 | #Docs 95 | docs: 96 | hostname : 'docs.local.wundertools.com' 97 | dir : '/vagrant/docs' 98 | 99 | 100 | ## Vagrant + Ansible configuration 101 | 102 | Vagrant is using Ansible provision stored under the ansible subdirectory. 103 | The inventory file (which stores the hosts and their IP's) is located under 104 | ansible/inventory. Host specific configurations for Vagrant are stored in 105 | ansible/vagrant.yml and the playbooks are under ansible/playbook directory. 106 | Variable overrides are defined in ansible/variables.yml. 107 | 108 | You should only bother with the following: 109 | 110 | - Vagrant box setup `conf/vagrant.yml` 111 | - What components do you want to install? `conf/vagrant.yml` 112 | - And how are those set up? `conf/variables.yml` 113 | - You can also fix your vagrant/ansible base setup to certain branch/revision `conf/project.yml` 114 | There you can also do the same for build.sh 115 | 116 | 117 | ## Debugging tools 118 | 119 | XDebug tools are installed via the devtools role. Everything should work out 120 | of the box for PHPStorm. PHP script e.g. drush debugging should also work. 121 | 122 | ### Lando debugging 123 | 124 | XDebug can be enabled by uncommeting `xdebug: true` in the .lando.yml file. After `lando rebuild` port 9000 is used for XDebug. 125 | 126 | Note: Make sure port 9000 is not used in your OS for anything else. You can see all ports in use for example with `lsof -i -n -P`. For example php-fpm might be using port 9000 if you have it running. 127 | 128 | ## Provisioning with Lando 129 | 130 | Perform the following tasks in the project root folder to set up the Lando-based provisioning tool: 131 | 132 | 1. create the file `~/.ssh/ansible.vault` and save it with the Ansible vault password (search for `Ansible vault password` or similar in the LastPass), 133 | 2. run `lando start`, 134 | 3. use `lando provision` for help and `lando provision ` for provisioning tasks. 135 | -------------------------------------------------------------------------------- /Vagrantfile: -------------------------------------------------------------------------------- 1 | dir = File.dirname(__FILE__) + '/' 2 | system(dir + "build.sh " + ARGV[0]) 3 | load dir + "ansible/vagrantfile.rb" 4 | -------------------------------------------------------------------------------- /ansible.cfg: -------------------------------------------------------------------------------- 1 | [defaults] 2 | roles_path=./local_ansible_roles:./ansible/playbook/roles 3 | library=./ansible/playbook/library 4 | hash_behaviour=merge 5 | 6 | [ssh_connection] 7 | pipelining = True 8 | control_path = /tmp/ansible-ssh-%%h-%%p-%%r 9 | 10 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Helper function to parse yaml configs 3 | function parse_yaml { 4 | local prefix=$2 5 | local s='[[:space:]]*' w='[a-zA-Z0-9_]*' fs=$(echo @|tr @ '\034') 6 | sed -ne "s|^\($s\):|\1|" \ 7 | -e "s|^\($s\)\($w\)$s:$s[\"']\(.*\)[\"']$s\$|\1$fs\2$fs\3|p" \ 8 | -e "s|^\($s\)\($w\)$s:$s\(.*\)$s\$|\1$fs\2$fs\3|p" $1 | 9 | awk -F$fs '{ 10 | indent = length($1)/2; 11 | vname[indent] = $2; 12 | for (i in vname) {if (i > indent) {delete vname[i]}} 13 | if (length($3) > 0) { 14 | vn=""; for (i=0; i /dev/null 25 | ROOT=`pwd -P` 26 | popd > /dev/null 27 | # Parse project config 28 | PROJECTCONF=$ROOT/conf/project.yml 29 | eval $(parse_yaml $PROJECTCONF) 30 | ALIASFILE=${project_name}.aliases.drushrc.php 31 | ALIASTARGET=$HOME/.drush/$ALIASFILE 32 | 33 | if [ -z "$drush_alias_path" ]; then 34 | ALIASPATH=$ROOT/drupal/conf/$ALIASFILE 35 | else 36 | ALIASPATH=$ROOT/${drush_alias_path}/$ALIASFILE 37 | fi 38 | 39 | if [ -z "$wundertools_branch" ]; then 40 | GITBRANCH="master" 41 | else 42 | GITBRANCH=$wundertools_branch 43 | fi 44 | 45 | if [ -z "$wundertools_repository" ]; then 46 | WUNDERTOOLSREPOSITORY="wunderio/WunderTools" 47 | else 48 | WUNDERTOOLSREPOSITORY=$wundertools_repository 49 | fi 50 | 51 | 52 | if [ "$CI" = true ]; then 53 | echo "You're using CI=$CI. This means that automatic updates for this script are skipped..." 54 | else 55 | VERSIONFILE=$ROOT/VERSION 56 | CHANGELOG=$ROOT/CHANGELOG 57 | CHANGELOGURL="https://raw.githubusercontent.com/$WUNDERTOOLSREPOSITORY/$GITBRANCH/CHANGELOG" 58 | 59 | if [ -f $VERSIONFILE ]; then 60 | typeset -i CURRENT_VERSION=$(<$VERSIONFILE) 61 | else 62 | CURRENT_VERSION=0 63 | fi 64 | 65 | if [ "$CURRENT_VERSION" -ne "$VERSION" ]; then 66 | echo -e "\033[0;31mBuild.sh version has been updated.\033[0m Make sure your project complies with the changes outlined in the CHANGELOG since version $CURRENT_VERSION" 67 | while read -p "I have updated everything ([y]es / [n]o / show [c]hangelog)? " -n 1 -r && [[ $REPLY =~ ^[Cc]$ ]]; do 68 | echo $CHANGELOGURL 69 | if [ ! -f $CHANGELOG ]; then 70 | curl -s -o $CHANGELOG $CHANGELOGURL 71 | fi 72 | sed -e '/^'$CURRENT_VERSION'$/,$d' $CHANGELOG 73 | done 74 | echo 75 | if [[ $REPLY =~ ^[Yy]$ ]]; then 76 | echo $VERSION > $VERSIONFILE 77 | echo "Current version updated, make sure to commit all the changes before continuing." 78 | else 79 | echo "Please update everything to comply with the latest version before continuing!" 80 | exit 0 81 | fi 82 | fi 83 | fi 84 | 85 | if command -v md5sum >/dev/null 2>&1; then 86 | MD5COMMAND="md5sum" 87 | else 88 | MD5COMMAND="md5 -r" 89 | fi 90 | 91 | if [[ $1 == "reset" ]]; then 92 | read -p "This will reset everything! Are you sure (y/n)? " -n 1 -r 93 | echo 94 | if [[ $REPLY =~ ^[Yy]$ ]]; then 95 | cd $ROOT 96 | vagrant destroy 97 | rm -r $ROOT/ansible 98 | rm $ALIASTARGET 99 | fi 100 | # Only run when running vagrant up or provision 101 | elif [[ $1 == "up" || $1 == "provision" ]]; then 102 | # First we check if there is update for this script 103 | SELF=$(basename $0) 104 | UPDATEURL="https://raw.githubusercontent.com/$WUNDERTOOLSREPOSITORY/$GITBRANCH/build.sh" 105 | MD5SELF=$($MD5COMMAND $0 | awk '{print $1}') 106 | MD5LATEST=$(curl -s $UPDATEURL | $MD5COMMAND | awk '{print $1}') 107 | if [[ "$MD5SELF" != "$MD5LATEST" ]]; then 108 | while read -p "There is update for this script available. Update now ([y]es / [n]o / show [c]hangelog)?" -n 1 -r && [[ $REPLY =~ ^[Cc]$ ]]; do 109 | curl -s $CHANGELOGURL | sed -e '/^'$CURRENT_VERSION'$/,$d' 110 | done 111 | echo 112 | if [[ $REPLY =~ ^[Yy]$ ]]; then 113 | cd $ROOT 114 | curl -s -o $SELF $UPDATEURL 115 | curl -s -o $CHANGELOG $CHANGELOGURL 116 | echo "Update complete, please rerun any command you were running previously." 117 | echo "See CHANGELOG for more info." 118 | echo "Also remember to add updated script to git." 119 | exit 120 | fi 121 | fi 122 | # Clone and update virtual environment configurations 123 | if [ ! -d "$ROOT/ansible" ]; then 124 | git clone -b $ansible_branch $ansible_remote $ROOT/ansible 125 | if [ -n "$ansible_revision" ]; then 126 | cd $ROOT/ansible 127 | git reset --hard $ansible_revision 128 | cd $ROOT 129 | fi 130 | else 131 | if [ -z "$ansible_revision" ]; then 132 | cd $ROOT/ansible 133 | git pull 134 | git checkout $ansible_branch 135 | cd $ROOT 136 | else 137 | cd $ROOT/ansible 138 | git pull 139 | git reset --hard $ansible_revision 140 | cd $ROOT 141 | fi 142 | fi 143 | 144 | # If it is enabled in project.yml - get & update drupal/build.sh 145 | if $buildsh_enabled; then 146 | if [ -z "$buildsh_repository" ]; then 147 | BUILDSHREPOSITORY="wunderio/build.sh" 148 | else 149 | BUILDSHREPOSITORY=$buildsh_repository 150 | fi 151 | if [ -n "$buildsh_revision" ]; then 152 | curl -s -o $ROOT/drupal/build.sh https://raw.githubusercontent.com/$BUILDSHREPOSITORY/$buildsh_revision/build.sh 153 | else 154 | curl -s -o $ROOT/drupal/build.sh https://raw.githubusercontent.com/$BUILDSHREPOSITORY/$buildsh_branch/build.sh 155 | fi 156 | fi 157 | 158 | # Ensure drush aliases file is linked 159 | if [ ! -h $ALIASTARGET ] || [ ! "$(readlink $ALIASTARGET)" -ef "$ALIASPATH" ]; then 160 | rm $ALIASTARGET 161 | ln -s $ALIASPATH $ALIASTARGET 162 | fi 163 | 164 | if [ ! -z $externaldrupal_remote ]; then 165 | if [ ! -z $externaldrupal_location ]; then 166 | DRUPALLOCATION=$externaldrupal_location 167 | else 168 | DRUPALLOCATION="drupal/current" 169 | fi 170 | if [ ! -d $DRUPALLOCATION ]; then 171 | mkdir -p $ROOT/$DRUPALLOCATION 172 | if [ -z $externaldrupal_branch ]; then 173 | $externaldrupal_branch = 'master' 174 | fi 175 | git clone -b $externaldrupal_branch $externaldrupal_remote $ROOT/$DRUPALLOCATION 176 | fi 177 | fi 178 | fi 179 | -------------------------------------------------------------------------------- /conf/.yamllint: -------------------------------------------------------------------------------- 1 | # Extends the default config by adjusting some options 2 | extends: default 3 | 4 | rules: 5 | braces: disable 6 | brackets: disable 7 | comments: disable 8 | comments-indentation: disable 9 | document-start: disable 10 | empty-lines: disable 11 | key-duplicates: disable 12 | indentation: disable 13 | line-length: disable 14 | truthy: disable 15 | -------------------------------------------------------------------------------- /conf/dev-vars.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # 3 | # Note this file must be encrypted with ansible-vault! 4 | 5 | drupal_db_password: somethingrandom 6 | varnish_control_key: something-else-random 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /conf/develop.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | # Example of absolute minimum server setup. 4 | 5 | - hosts: dev 6 | become: false 7 | become_method: sudo 8 | user: root 9 | roles: 10 | - { role: base, tags: [ 'base' ] } 11 | - { role: varnish, tags: [ 'varnish' ] } 12 | - { role: dbserver, tags: [ 'dbserver' ] } 13 | - { role: drupal-db, tags: [ 'drupal-db' ] } 14 | - { role: nginx, tags: [ 'nginx' ] } 15 | - { role: php-fpm, tags: [ 'php-fpm' ] } 16 | - { role: drush, tags: [ 'drush' ] } 17 | 18 | vars_files: 19 | - dev-vars.yml 20 | 21 | tasks: 22 | # Disable email from cron 23 | - name: "Disable emails from failed cronjobs for nginx user" 24 | cron: 25 | name: "MAILTO" 26 | env: yes 27 | value: "" 28 | state: "present" 29 | user: nginx 30 | tags: ['cron'] 31 | # Run Drupal cron job 32 | - name: "Add drupal cronjob to nginx user" 33 | cron: 34 | name: "Run Drupal cronjobs with drush" 35 | minute: "*/2" 36 | job: "/usr/lib/composer/vendor/bin/drush --root={{ drupal_web_root }} cron" 37 | state: "present" 38 | user: nginx 39 | tags: ['cron'] 40 | when: drupal_web_root is defined 41 | 42 | vars: 43 | wkv_site_env: dev 44 | 45 | # You can set custom variables if the same value is used in multiple places so it can be easily changed here 46 | # You can use it anywhere after this using " {{ variable_name }}" 47 | domain_name: develop.example.com 48 | 49 | # This is used in cronjob and varnish and nginx configs 50 | drupal_web_root: "/var/www/{{ domain_name }}/current/web" 51 | 52 | # How to assign memory for each role and set the correct 53 | # memory_app and memory_db define how much total system memory is allocated to each. 54 | # On dedicated DB server memory_db should max be around 80% of total memory and would ideally fit the whole db + some more. 55 | memory_db: 1024 # In MB 56 | memory_app: 1024 # In MB 57 | 58 | # NOTE: ALWAYS leave some spare memory for the server 59 | # php memory limits etc are in variables.yml 60 | 61 | # Apps I want to run on this server 62 | apps: 63 | - server_name: "{{ domain_name }}" 64 | http_port: 8080 65 | docroot: "{{ drupal_web_root }}" 66 | 67 | deny_robots: true 68 | # This server also acts as a load balancer 69 | varnish: 70 | port: 80 71 | memory: 512M 72 | directors: 73 | - name: example 74 | host: "{{ domain_name }}" 75 | backends: 76 | - name: example_http 77 | address: 127.0.0.1 78 | port: 8080 79 | 80 | 81 | -------------------------------------------------------------------------------- /conf/prod-db.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - hosts: prod-db 4 | user: root 5 | roles: 6 | - { role: base, tags: [ 'base' ] } 7 | - { role: papertrail, tags: [ 'papertrail' ] } 8 | - { role: backups, tags: [ 'backups' ] } 9 | - { role: dbserver, tags: [ 'dbserver' ] } 10 | - { role: drupal-db, tags: [ 'drupal-db' ] } 11 | #- { role: solr, tags: [ 'solr' ] } 12 | - { role: elasticsearch, tags: [ 'elasticsearch' ] } 13 | - { role: nfs_server, tags: [ 'nfs_server' ] } 14 | - { role: monit, tags: [ 'monit' ] } 15 | - { role: newrelic-infra, tags: [ 'newrelic-infra' ] } 16 | - { role: newrelic-sysmon, tags: [ 'newrelic-sysmon' ] } 17 | 18 | tasks: 19 | 20 | vars_files: 21 | - prod-vars.yml 22 | 23 | vars: 24 | ## Base ## 25 | 26 | # On default we disable iptables but we want option to keep it on for customer-servers without external firewall 27 | #disable_iptables: True 28 | 29 | ## Backups ## 30 | 31 | backup_db_name: 32 | - drupal 33 | 34 | backup_location: /nfs-files/backups 35 | 36 | ## DB server ## 37 | 38 | memory_db: 4096 # In MB 39 | 40 | # Additional mysql configs available 41 | #max_connections: 500 42 | #connection_timeout: 5 43 | #wait_timeout: 60 44 | #max_allowed_packet: 256M 45 | #innodb_buffer_pool_instances: 6 46 | #innodb_log_buffer_size: 8M 47 | #innodb_log_file_size: 24M 48 | #innodb_additional_mem_pool_size: 10M 49 | #innodb_concurrency: 16 50 | #max_allowed_packet: 128M 51 | 52 | #innodb_buffer_pool_size configured in variables.yml based on memory_db 53 | 54 | ## Drupal DB ## 55 | # Defined in production.yml 56 | 57 | 58 | ## Solr ## 59 | # See ansible/playbook/roles/solr/defaults/main.yml for reference 60 | 61 | ## Elasticsearch ## 62 | # See ansible/playbook/roles/elasticsearch/defaults/main.yml for reference 63 | 64 | 65 | ## Nfs server ## 66 | 67 | partition_nfs_share: False 68 | nfs_share_disk: /dev/vdc 69 | 70 | nfs_exports: 71 | - "/nfs-files {{ front1_ip }}(rw,async,no_root_squash,no_subtree_check)" 72 | - "/nfs-files {{ front2_ip }}(rw,async,no_root_squash,no_subtree_check)" 73 | 74 | 75 | ## Monit ## 76 | monit_enabled: True 77 | monit_alert_email_service_name: "{{ project_name }} db {{ ansible_nodename }}" 78 | monit_alert_email_from: "{{ support_email }}" 79 | monit_alert_email_to: "{{ support_email }}" 80 | 81 | monit_check_sshd: True 82 | monit_check_remote_syslog: True 83 | monit_check_cron: True 84 | monit_check_mysql: True 85 | 86 | # Which ever you are using 87 | #monit_check_solr: True 88 | monit_check_elasticsearch: True 89 | 90 | ## Papertrail ## 91 | papertrail_enabled: True 92 | papertrail_remote_syslog2_version: "0.16" 93 | papertrail_conf: "/etc/log_files.yml" 94 | papertrail_logs: 95 | - /var/log/secure 96 | 97 | # papertrail host and port defined in prod-vars.yml 98 | 99 | ## Newrelic ## 100 | newrelic_enabled: True 101 | newrelic_infra_enabled: True 102 | # newrelic_license_key defined in prod_vars.yml 103 | 104 | 105 | -------------------------------------------------------------------------------- /conf/prod-front.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - hosts: prod-front 4 | user: root 5 | roles: 6 | - { role: base, tags: [ 'base' ] } 7 | - { role: papertrail, tags: [ 'papertrail' ] } 8 | - { role: nginx, tags: [ 'nginx' ] } 9 | - { role: php-fpm, tags: [ 'php-fpm' ] } 10 | #- { role: memcached, tags: [ 'memcached' ] } 11 | - { role: drush, tags: [ 'drush' ] } 12 | - { role: drupal-log, tags: [ 'drupal-log' ] } 13 | - { role: external-smtp, tags: [ 'external-smtp' ] } 14 | - { role: nfs_client, tags: [ 'nfs_client' ] } 15 | - { role: monit, tags: [ 'monit' ] } 16 | - { role: newrelic, tags: [ 'newrelic' ] } 17 | - { role: newrelic-sysmon, tags: [ 'newrelic-sysmon' ] } 18 | - { role: newrelic-infra, tags: [ 'newrelic-infra' ] } 19 | 20 | vars_files: 21 | - prod-vars.yml 22 | 23 | tasks: 24 | # Disable email from cron 25 | - name: "Disable emails from failed cronjobs for nginx user" 26 | cron: 27 | name: "MAILTO" 28 | env: yes 29 | value: "" 30 | state: "present" 31 | user: nginx 32 | tags: ['cron'] 33 | 34 | # Set drupal cron to be run every minute and alternate between both servers 35 | - name: "Run drupal cronjob with nginx user at even minutes" 36 | cron: 37 | name: "Run Drupal cronjobs with drush" 38 | minute: "*/2" 39 | job: "/usr/lib/composer/vendor/bin/drush --root={{ drupal_web_root }} --uri={{ domain2_name }} cron" 40 | state: "present" 41 | user: nginx 42 | tags: ['cron'] 43 | when: ansible_eth0.ipv4.address == groups['prod-front'][0] and drupal_web_root is defined 44 | - name: "Run drupal cronjob with nginx user at odd minutes" 45 | cron: 46 | name: "Run Drupal cronjobs with drush" 47 | minute: "1-59/2" 48 | job: "/usr/lib/composer/vendor/bin/drush --root={{ drupal_web_root }} --uri={{ domain2_name }} cron" 49 | state: "present" 50 | user: nginx 51 | tags: ['cron'] 52 | when: ansible_eth0.ipv4.address == groups['prod-front'][1] and drupal_web_root is defined 53 | 54 | vars: 55 | 56 | memory_app: 4096 # In MB should be atleast 4x php_memory_limit 57 | 58 | 59 | ## Nginx ## 60 | 61 | nginx_disable_content_security_policy: True 62 | nginx_real_ip: "{{ lb_ip }}" 63 | 64 | # Apps I want to run on this server 65 | apps: 66 | - server_name: "{{ domain1_name }}" 67 | http_port: 8080 68 | docroot: "{{ drupal_web_root }}" 69 | - server_name: "www.{{ domain2_name }}" 70 | server_aliases: "{{ domain2_name }}" 71 | server_forwards: "{{ domain2_name }}" # we redirect to www subdomain 72 | http_port: 8080 73 | docroot: "{{ drupal_web_root }}" 74 | 75 | # You can also define aliases with app_dir_aliases, see ansible/playbook/roles/nginx/templates/all_apps.conf.j2 76 | 77 | # Automatically create docroot folders 78 | create_docroot: True 79 | 80 | # Allow extra php scripts 81 | nginx_extra_php: 82 | - example.php 83 | 84 | 85 | ## Php-fpm ## 86 | 87 | php_ini_file: /etc/php.d/zz_wundertools.ini 88 | 89 | # You can set any php.ini variable like this: 90 | #php_ini: 91 | # - section: [section] 92 | # options: 93 | # - key: [php variable] 94 | # val: "[value]" 95 | 96 | # See ansible_playbook/roles/php-fpm/defaults/main.yml for examples 97 | 98 | ## Memcached ## 99 | 100 | #memcached_port: 11211 101 | #memcached_maxconn: 1024 102 | #memcached_cachesize: 256 103 | #memcached_options: "-I 4M" 104 | 105 | 106 | ## Drupal-log ## 107 | 108 | drupal_log_path: /var/log/drupal.log 109 | 110 | 111 | ## External-smtp ## 112 | 113 | external_smtp_tls_security_level: 'encrypt' 114 | external_smtp_relayhost: '[smtp.eu.sparkpostmail.com]:587' 115 | # external_smtp_sasl_password_maps value defined in prod-vars.yml 116 | 117 | ## Nfs-client ## 118 | 119 | nfsserver: "{{ db_ip }}" 120 | 121 | ## Monit ## 122 | monit_enabled: True 123 | monit_alert_email_service_name: "{{ project_name }} front {{ ansible_nodename }}" 124 | monit_alert_email_from: "{{ support_email }}" 125 | monit_alert_email_to: "{{ support_email }}" 126 | monit_check_sshd: True 127 | monit_check_nginx: True 128 | monit_check_memcached: True 129 | monit_check_php_fpm: True 130 | monit_check_remote_syslog: True 131 | monit_check_cron: True 132 | 133 | ## Papertrail ## 134 | papertrail_enabled: True 135 | papertrail_remote_syslog2_version: "0.16" 136 | papertrail_conf: "/etc/log_files.yml" 137 | papertrail_logs: 138 | - /var/log/secure 139 | - /var/log/maillog 140 | 141 | # papertrail host and port defined in prod-vars.yml 142 | 143 | # Define additional papertrail logs from nginx (from nginx role) 144 | nginx_papertrail_follow: 145 | - /var/log/nginx/http-*error.log 146 | 147 | php_fpm_papertrail_follow: 148 | - /var/log/php-fpm/www-error.log 149 | 150 | ## Newrelic ## 151 | newrelic_enabled: True 152 | newrelic_infra_enabled: True 153 | # newrelic_license_key defined in prod_vars.yml 154 | newrelic_appname: "{{ project_name }}" 155 | 156 | 157 | -------------------------------------------------------------------------------- /conf/prod-lb.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - hosts: prod-lb 3 | user: root 4 | roles: 5 | - { role: base, tags: [ 'base' ] } 6 | - { role: papertrail, tags: [ 'papertrail' ] } 7 | - { role: certbot, tags: [ 'certbot' ] } 8 | - { role: varnish, tags: [ 'varnish' ] } 9 | - { role: nginx, tags: [ 'nginx' ] } 10 | - { role: sslterminator, tags: [ 'sslterminator' ] } 11 | - { role: monit, tags: [ 'monit' ] } 12 | - { role: newrelic-infra, tags: [ 'newrelic-infra' ] } 13 | - { role: newrelic-sysmon, tags: [ 'newrelic-sysmon' ] } 14 | 15 | vars_files: 16 | - prod-vars.yml 17 | 18 | vars: 19 | 20 | ## Base ## 21 | 22 | # On default we disable iptables but we want option to keep it on for customer-servers without external firewall 23 | #disable_iptables: True 24 | 25 | 26 | ## Letsencrypt ## 27 | 28 | certbot_email: "{{ support_email }}" 29 | certbot_domains: 30 | - "{{ domain1_name }}" 31 | - "www.{{ domain1_name }}" 32 | - "{{ domain2_name }}" 33 | - "www.{{ domain2_name }}" 34 | certbot_renewal_docroot: /var/www/letsencrypt-auto 35 | #certbot_commands: allows yo uto customize the command to run, see ansible/playbook/roles/certbot/defaults/main.yml 36 | 37 | # We need to make sure not to leak sites ahead of launch 38 | basicauth_enabled: True 39 | basicauth_credentials: 40 | - username: wunder 41 | password: tools 42 | # Basic auth can also be disabled for certain ip's 43 | basicauth_ip: 44 | - address: 84.20.132.177 45 | - address: 87.94.15.10 46 | - address: 94.237.27.124 47 | - address: 94.237.33.88 48 | - address: 83.136.248.231 49 | - address: 194.89.156.118 50 | 51 | ## Varnish ## 52 | 53 | # This server also acts as a load balancer 54 | varnish: 55 | port: 8081 56 | memory: 1G 57 | # Basic configuration for error pages 58 | #error_header_cache_control: "no-cache, max-age: 0, must-revalidate" 59 | error_header_x_site_name: "{{ project_name }}" 60 | error_header_x_sitetitle: "{{ project_name }}" 61 | error_header_x_ua: "insert_GA_code" 62 | # It's possible to define custom error page template with 63 | #custom_error_template: 64 | # you can also set the control_key 65 | control_key: "{{ varnish_control_key }}" 66 | probe_resource_url: "_ping.php" 67 | acl_internal: 68 | - ip: 84.20.132.177 69 | - ip: 87.94.15.10 70 | - ip: 94.237.27.124 71 | - ip: 94.237.33.88 72 | - ip: 83.136.248.231 73 | acl_purge: 74 | - ip: 127.0.0.1 75 | - ip: "{{ front1_ip }}" 76 | - ip: "{{ front2_ip }}" 77 | acl_upstream_proxy: 78 | - ip: 127.0.0.1 79 | directors: 80 | - name: prod1 81 | host: "www.{{ domain1_name }}" 82 | backends: 83 | - name: prod1_http1 84 | address: "{{ front1_ip }}" 85 | port: 8080 86 | - name: prod1_http2 87 | address: "{{ front2_ip }}" 88 | port: 8080 89 | - name: prod2 90 | host: "www.{{ domain2_name }}" 91 | backends: 92 | - name: prod2_http1 93 | address: "{{ front1_ip }}" 94 | port: 8080 95 | - name: prod2_http2 96 | address: "{{ front2_ip }}" 97 | port: 8080 98 | 99 | # There is more customization options for hashes, cookies etc. see ansible/playbook/roles/varnish/templates/default.vcl.j2 for reference 100 | 101 | ## Nginx ## 102 | 103 | # You can also define aliases with app_dir_aliases, see ansible/playbook/roles/nginx/templates/all_apps.conf.j2 104 | 105 | nginx_real_ip: 127.0.0.1 106 | # If using 3rd party CDN / proxy you can define those ip's here so that the real original request ip will be passed through 107 | # For example Cloudflare: 108 | #nginx_real_ip: 109 | # - 103.21.244.0/22 110 | # - 103.22.200.0/22 111 | # - 103.31.4.0/22 112 | # - 104.16.0.0/12 113 | # - 108.162.192.0/18 114 | # - 131.0.72.0/22 115 | # - 141.101.64.0/18 116 | # - 162.158.0.0/15 117 | # - 172.64.0.0/13 118 | # - 173.245.48.0/20 119 | # - 188.114.96.0/20 120 | # - 190.93.240.0/20 121 | # - 197.234.240.0/22 122 | # - 198.41.128.0/17 123 | # - 199.27.128.0/21 124 | 125 | # Extra nginx configs 126 | nginx_conf: 127 | - client_header_timeout: 10 128 | - client_body_timeout: 120 129 | - send_timeout: 120 130 | - keepalive_timeout: "15 10" 131 | - client_max_body_size: 100M 132 | - client_body_buffer_size: 128k 133 | - proxy_read_timeout: 60 134 | 135 | # Define additional Content-Security-Policy headers 136 | #nginx_content_security_policy: "upgrade-insecure-requests; default-src https: data: 'unsafe-inline' 'unsafe-eval'; frame-ancestors 'self'; base-uri 'self'; object-src 'self'; connect-src wss: https:" 137 | 138 | # Drupal already enforces SAMEORIGIN 139 | nginx_disable_default_xframe_options: True 140 | # or disable them completely 141 | #nginx_disable_content_security_policy: True 142 | # Also affects sslterminator 143 | 144 | 145 | ## Sslterminator ## 146 | # we only use https for this host 147 | sslterminators: 148 | - server_name: "www.{{ domain1_name }}" 149 | server_forwards: "{{ domain1_name }}" # we redirect to www subdomain 150 | ssl_certificate: "/etc/letsencrypt/live/{{ domain1_name }}/fullchain.pem" 151 | ssl_certificate_key: "/etc/letsencrypt/live/{{ domain1_name}}/privkey.pem" 152 | use_dhparam: True 153 | backends: 154 | - 127.0.0.1:8081 155 | - server_name: "www.{{ domain2_name }}" 156 | server_forwards: "{{ domain2_name }}" # we redirect to www subdomain 157 | # Certbot is configured to smash all certificates into the first one 158 | # So domain2 uses domain1 certificate 159 | ssl_certificate: "/etc/letsencrypt/live/{{ domain1_name }}/fullchain.pem" 160 | ssl_certificate_key: "/etc/letsencrypt/live/{{ domain1_name}}/privkey.pem" 161 | use_dhparam: True 162 | backends: 163 | - 127.0.0.1:8081 164 | 165 | # Forward to www and https. 166 | httpforwards: 167 | - server_name: "www.{{ domain1_name }}" 168 | forwarded_domains: "{{ domain1_name}} www.{{ domain1_name }}" 169 | - server_name: "www.{{ domain2_name }}" 170 | forwarded_domains: "{{ domain2_name}} www.{{ domain2_name }}" 171 | # You can also use extra_forwards and httpextraforwards to define additional domains to listen & forward 172 | # See ansible/playbook/roles/sslterminator/templates/ssl_terminators.conf.j2 for reference. 173 | 174 | ## Monit ## 175 | monit_enabled: True 176 | monit_alert_email_service_name: "{{ project_name }} lb {{ ansible_nodename }}" 177 | monit_alert_email_from: "{{ support_email }}" 178 | monit_alert_email_to: "{{ support_email }}" 179 | monit_check_sshd: True 180 | monit_check_varnish: True 181 | monit_check_nginx: True 182 | monit_check_remote_syslog: True 183 | monit_check_cron: True 184 | 185 | 186 | ## Papertrail ## 187 | papertrail_enabled: True 188 | papertrail_remote_syslog2_version: "0.16" 189 | papertrail_conf: "/etc/log_files.yml" 190 | papertrail_logs: 191 | - /var/log/secure 192 | 193 | # papertrail host and port defined in prod-vars.yml 194 | 195 | # Define additional papertrail logs from nginx (from nginx role) 196 | nginx_papertrail_follow: 197 | - /var/log/nginx/http-*error.log 198 | 199 | # Define additional papertrail logs from sslterminator (from sslterminator role) 200 | sslterminator_papertrail_follow: 201 | - /var/log/nginx/ssl-*error.log 202 | 203 | 204 | ## Newrelic ## 205 | newrelic_enabled: True 206 | newrelic_infra_enabled: True 207 | # newrelic_license_key defined in prod_vars.yml 208 | 209 | 210 | -------------------------------------------------------------------------------- /conf/prod-vars.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | # Note this file must be encrypted with ansible-vault! 4 | 5 | 6 | drupal_db_password: "password_to_access_drupal_db1" 7 | varnish_control_key: something-random-secret 8 | 9 | external_smtp_sasl_password_maps: 'static:SMTP_Injection:whateversecretstheexternalsmtpservicerequires' 10 | 11 | -------------------------------------------------------------------------------- /conf/production.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Gather all facts about all groups: 3 | # This is currently needed to check the private ip addresses of the front machines 4 | - name: Collect facts from machines 5 | hosts: prod-lb:prod-front:prod-db 6 | user: root 7 | tasks: [ ] 8 | 9 | - name: Common 10 | hosts: prod 11 | user: root 12 | 13 | vars_files: 14 | - prod-vars.yml 15 | 16 | vars: 17 | ## Internal / helper vars ## 18 | # Production specific 19 | database: 20 | - name: drupal 21 | user: drupal 22 | # defined in prod-vars.yml 23 | pass: "{{ drupal_db_password }}" 24 | host: "{{ db_ip }}" 25 | hosts: 26 | - "127.0.0.1" 27 | - "::1" 28 | - "localhost" 29 | - "{{ front1_ip }}" 30 | - "{{ front2_ip }}" 31 | #- "{{ front3_ip }}" 32 | # ... 33 | 34 | 35 | tasks: 36 | - name: Set internal ip addresses 37 | set_fact: 38 | db_ip: "{{ groups['prod-db'] | map('extract', hostvars, ['ansible_eth1', 'ipv4', 'address']) | first }}" 39 | lb_ip: "{{ groups['prod-lb'] | map('extract', hostvars, ['ansible_eth1', 'ipv4', 'address']) | first }}" 40 | front_ips: "{{ groups['prod-front'] | map('extract', hostvars, ['ansible_eth1', 'ipv4', 'address']) | list }}" 41 | tags: ['common'] 42 | 43 | - name: Set front ips 44 | set_fact: 45 | front1_ip: "{{ front_ips[0] }}" 46 | front2_ip: "{{ front_ips[1] }}" 47 | #front3_ip: "{{ front_ips[2] }}" 48 | # ... 49 | tags: ['common'] 50 | 51 | - name: Set common facts 52 | set_fact: 53 | domain1_name: "wundertools.prod.wunder.io" 54 | domain2_name: "wunderools.com" 55 | wkv_site_env: "prod" 56 | databases: "{{ database }}" 57 | varnish_control_key: "{{ varnish_control_key }}" 58 | tags: ['common'] 59 | 60 | # this need to be separate as we are using another fact here 61 | - name: Set drupal web root 62 | set_fact: 63 | drupal_web_root: "/var/www/{{ domain1_name }}/current/web" 64 | tags: ['common'] 65 | 66 | - import_playbook: prod-lb.yml 67 | - import_playbook: prod-db.yml 68 | - import_playbook: prod-front.yml 69 | -------------------------------------------------------------------------------- /conf/project.yml: -------------------------------------------------------------------------------- 1 | project: 2 | name: wundertools 3 | file_sync_url: https://www.example1.com 4 | ansible: 5 | remote: git@github.com:wunderio/WunderMachina.git 6 | branch: master 7 | revision: 8 | buildsh: 9 | enabled: true 10 | branch: master 11 | revision: 12 | wundertools: 13 | branch: master 14 | externaldrupal: 15 | remote: 16 | branch: 17 | location: 18 | drush: 19 | alias_path: drupal/drush 20 | wundersecrets: 21 | remote: git@github.com:wunderio/WunderSecrets.git 22 | 23 | -------------------------------------------------------------------------------- /conf/requirements.yml: -------------------------------------------------------------------------------- 1 | # External Ansible roles can be defined here, they will be installed automatically when running provision.sh 2 | # Galaxy roles 3 | # - src: vkill.incron 4 | # Role from github 5 | # - src: https://github.com/wtanaka/ansible-role-jq 6 | -------------------------------------------------------------------------------- /conf/server.inventory: -------------------------------------------------------------------------------- 1 | [dev] 2 | 3 | [stage] 4 | 5 | [prod-db] 6 | 7 | [prod-lb] 8 | 9 | [prod-front] 10 | 11 | # Add all groups here which need http/https ports to be open in UpCloud firewall 12 | [firewall_web:children] 13 | dev 14 | stage 15 | prod-lb 16 | 17 | [prod:children] 18 | prod-lb 19 | prod-front 20 | prod-db 21 | 22 | -------------------------------------------------------------------------------- /conf/stage-vars.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # 3 | # Note this file must be encrypted with ansible-vault! 4 | 5 | drupal_db_password: supersecret 6 | varnish_control_key: secret-random-string 7 | -------------------------------------------------------------------------------- /conf/stage.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | # Make sure that the upcloud credentials are set as env 4 | - hosts: all 5 | connection: local 6 | become: false 7 | tasks: 8 | - set_fact: 9 | cloudflare_api_user: "{{ lookup('env', 'CLOUDFLARE_API_USER') }}" 10 | cloudflare_api_passwd: "{{ lookup('env', 'CLOUDFLARE_API_PASSWD') }}" 11 | tags: ['common', 'dns'] 12 | 13 | # Example of standard single server setup without production services (monitoring, logging etc.) 14 | - hosts: stage 15 | become: false 16 | become_method: sudo 17 | user: root 18 | roles: 19 | - { role: base, tags: [ 'base' ] } 20 | - { role: papertrail, tags: [ 'papertrail' ] } 21 | - { role: dbserver, tags: [ 'dbserver' ] } 22 | - { role: drupal-db, tags: [ 'drupal-db' ] } 23 | - { role: certbot, tags: [ 'certbot' ] } 24 | - { role: sslterminator, tags: [ 'sslterminator' ] } 25 | - { role: varnish, tags: [ 'varnish' ] } 26 | - { role: monit, tags: [ 'monit' ] } 27 | - { role: nginx, tags: [ 'nginx' ] } 28 | - { role: php-fpm, tags: [ 'php-fpm' ] } 29 | - { role: drush, tags: [ 'drush' ] } 30 | - { role: drupal-log, tags: [ 'drupal-log' ] } 31 | - { role: newrelic-infra, tags: [ 'newrelic-infra' ] } 32 | - { role: newrelic-sysmon, tags: [ 'newrelic-sysmon' ] } 33 | 34 | vars_files: 35 | - stage-vars.yml 36 | 37 | tasks: 38 | # Disable email from cron 39 | - name: "Disable emails from failed cronjobs for nginx user" 40 | cron: 41 | name: "MAILTO" 42 | env: yes 43 | value: "" 44 | state: "present" 45 | user: nginx 46 | tags: ['cron'] 47 | # Run Drupal cron job 48 | - name: "Add drupal cronjob to nginx user" 49 | cron: 50 | name: "Run Drupal cronjobs with drush" 51 | minute: "*/2" 52 | job: "/usr/lib/composer/vendor/bin/drush --root={{ drupal_web_root }} cron" 53 | state: "present" 54 | user: nginx 55 | tags: ['cron'] 56 | when: drupal_web_root is defined 57 | 58 | # Example how to add a ssh key for 3rd party developer 59 | # NOTE: Never add 3rd party developers to key.wunder.io! 60 | #- name: Add 3rd party public key 61 | # authorized_key: 62 | # user: www-admin 63 | # key: "ssh-rsa theuserspublickeycomeshere developer@somecompany.tld" 64 | 65 | 66 | - name: "Add DNS records to cloudflare" 67 | cloudflare_dns: 68 | zone: example.com 69 | record: stage 70 | type: A 71 | value: "{{ ansible_default_ipv4.address }}" 72 | account_email: "{{ cloudflare_api_user }}" 73 | account_api_token: "{{ cloudflare_api_passwd }}" 74 | register: record 75 | tags: ['dns'] 76 | 77 | vars: 78 | wkv_site_env: stage 79 | 80 | # You can set custom variables if the same value is used in multiple places so it can be easily changed here 81 | # You can use it anywhere after this using " {{ variable_name }}" 82 | domain_name: stage.example.com 83 | 84 | # This is used in cronjob and varnish and nginx configs 85 | drupal_web_root: "/var/www/{{ domain_name }}/current/web" 86 | 87 | # How to assign memory for each role and set the correct 88 | # memory_app and memory_db define how much total system memory is allocated to each. 89 | # On dedicated DB server memory_db should max be around 80% of total memory and would ideally fit the whole db + some more. 90 | memory_db: 1024 # In MB 91 | memory_app: 1024 # In MB 92 | 93 | # NOTE: memory_app needs to be at least 2x php_memory_limit 94 | # NOTE: ALWAYS leave some spare memory for the server 95 | # php memory limits etc are in variables.yml 96 | 97 | # Let's encrypt. Always use SSL if possible! 98 | certbot_email: "{{ support_email }}" 99 | certbot_domains: 100 | - "{{ domain_name }}" 101 | certbot_renewal_docroot: /var/www/letsencrypt-auto 102 | 103 | # On dev and stage it's good idea to protect the site with htauth 104 | basicauth_enabled: True 105 | basicauth_username: wunder 106 | basicauth_password: tools 107 | # Basic auth can also be disabled for certain ip's 108 | basicauth_ip: 109 | - address: 84.20.132.177 110 | - address: 87.94.15.10 111 | - address: 94.237.27.124 112 | - address: 94.237.33.88 113 | - address: 83.136.248.231 114 | - address: 194.89.156.118 115 | 116 | # Apps I want to run on this server 117 | apps: 118 | - server_name: "{{ domain_name }}" 119 | http_port: 8080 120 | docroot: "{{ drupal_web_root }}" 121 | 122 | create_docroot: True 123 | 124 | deny_robots: true 125 | 126 | # This server also acts as a load balancer 127 | varnish: 128 | port: 8081 129 | memory: 512M 130 | control_key: "{{ varnish_control_key }}" 131 | acl_internal: 132 | - ip: 127.0.0.1 133 | acl_purge: 134 | - ip: 127.0.0.1 135 | acl_upstream_proxy: 136 | - ip: 127.0.0.1 137 | directors: 138 | - name: example 139 | host: "{{ domain_name }}" 140 | backends: 141 | - name: example_http 142 | address: 127.0.0.1 143 | port: 8080 144 | 145 | # Use https for this host 146 | sslterminators: 147 | - server_name: "{{ domain_name }}" 148 | ssl_certificate: "/etc/letsencrypt/live/{{ domain_name }}/fullchain.pem" 149 | ssl_certificate_key: "/etc/letsencrypt/live/{{ domain_name }}/privkey.pem" 150 | use_dhparam: True 151 | backends: 152 | - 127.0.0.1:8081 # Pass it to local varnish 153 | 154 | # Make sure all traffic is redirected to https 155 | httpforwards: 156 | - server_name: "{{ domain_name }}" 157 | forwarded_domains: "{{ domain_name }}" 158 | http_port: 80 159 | 160 | databases: 161 | - name: drupal 162 | host: localhost 163 | user: drupal 164 | pass: "{{ drupal_db_password }}" 165 | hosts: 166 | - "127.0.0.1" 167 | - "::1" 168 | - "localhost" 169 | 170 | ## Monit ## 171 | monit_enabled: True 172 | monit_alert_email_service_name: "{{ project_name }} stage {{ ansible_nodename }}" 173 | monit_alert_email_from: "{{ support_email }}" 174 | monit_alert_email_to: "{{ support_email }}" 175 | 176 | monit_check_sshd: True 177 | monit_check_remote_syslog: True 178 | monit_check_cron: True 179 | monit_check_mysql: True 180 | monit_check_nginx: True 181 | monit_check_memcached: True 182 | monit_check_php_fpm: True 183 | monit_check_varnish: True 184 | 185 | # Which ever you are using 186 | #monit_check_solr: True 187 | monit_check_elasticsearch: True 188 | 189 | ## Papertrail ## 190 | papertrail_enabled: True 191 | papertrail_remote_syslog2_version: "0.16" 192 | papertrail_conf: "/etc/log_files.yml" 193 | papertrail_logs: 194 | - /var/log/secure 195 | - /var/log/maillog 196 | 197 | # Define additional papertrail logs from nginx (from nginx role) 198 | nginx_papertrail_follow: 199 | - /var/log/nginx/http-*error.log 200 | 201 | php_fpm_papertrail_follow: 202 | - /var/log/php-fpm/www-error.log 203 | 204 | newrelic_enabled: True 205 | newrelic_infra_enabled: True 206 | -------------------------------------------------------------------------------- /conf/upcloud.yml: -------------------------------------------------------------------------------- 1 | --- 2 | ## 3 | # This plabook configures Upcloud firewalls 4 | ## 5 | - name: Collect facts from machines 6 | hosts: prod-lb:prod-front:prod-db 7 | user: root 8 | tasks: [ ] 9 | 10 | 11 | 12 | # Make sure that the upcloud credentials are set as env 13 | - hosts: localhost 14 | connection: local 15 | become: false 16 | tags: ['upcloud'] 17 | tasks: 18 | - name: Set API credentials 19 | set_fact: 20 | upcloud_api_user: "{{ lookup('env', 'UPCLOUD_API_USER') }}" 21 | upcloud_api_passwd: "{{ lookup('env', 'UPCLOUD_API_PASSWD') }}" 22 | cloudflare_api_user: "{{ lookup('env', 'CLOUDFLARE_API_USER') }}" 23 | cloudflare_api_passwd: "{{ lookup('env', 'CLOUDFLARE_API_PASSWD') }}" 24 | 25 | - name: Ensure that UPCLOUD_API_USER is present 26 | fail: 27 | msg: "You must set UPCLOUD_API_USER env in your local machine" 28 | when: upcloud_api_user == "" 29 | 30 | - name: Ensure that UPCLOUD_API_PASSWD is present 31 | fail: 32 | msg: "You must set UPCLOUD_API_PASSWD env in your local machine" 33 | when: upcloud_api_passwd == "" 34 | 35 | # NOTE: This will create servers if upcloud_server_spec_list and upcloud_project_name variables are defined 36 | - name: Create Upcloud servers 37 | hosts: localhost 38 | connection: local 39 | become: False 40 | roles: 41 | - { role: upcloud-servers, tags: ['upcloud', 'server'] } 42 | tasks: 43 | # This task updates the inventory file 44 | - name: Update Upcloud Servers to server.inventory file 45 | ini_file: 46 | state: "{{ item['invocation']['module_args']['state'] }}" 47 | path: ./server.inventory 48 | section: "{{ item['item'] | json_query( '[*].group' ) | first }}" 49 | option: "{{ item['public_ip'] }}" 50 | allow_no_value: yes 51 | with_items: 52 | - "{{ upcloud_created_instances.results }}" 53 | when: upcloud_created_instances is defined and item['item'] | json_query( '[*].group' ) | length == 1 and item.invocation is defined 54 | tags: ['create', 'ini-config'] 55 | 56 | - name: Add DNS records to cloudflare 57 | cloudflare_dns: 58 | zone: wunder.io 59 | record: "{{ item['server']['hostname'] }}" 60 | type: A 61 | value: "{{ item['public_ip'] }}" 62 | account_email: "{{ cloudflare_api_user }}" 63 | account_api_token: "{{ cloudflare_api_passwd }}" 64 | with_items: 65 | - "{{ upcloud_created_instances.results }}" 66 | tags: ['dns', 'cloudflare'] 67 | when: cloudflare_api_user is defined 68 | 69 | 70 | - name: Update UpCloud Secondary Groups to server.inventory file 71 | ini_file: 72 | state: present 73 | path: ./server.inventory 74 | section: "{{ item.1 }}:children" 75 | option: "{{ item.0.group }}" 76 | allow_no_value: yes 77 | with_subelements: 78 | - "{{ upcloud_server_spec_list }}" 79 | - secondary_groups 80 | tags: ['create', 'ini-config'] 81 | 82 | # Creates firewalls for all project machines in UpCloud. 83 | # This means that the firewall rules will be updated even 84 | # for machines which were not provisioned by ansible originally. 85 | # NOTE: This will work for any machine in server.inventory which has IP-address from UpCloud. 86 | - hosts: localhost 87 | connection: local 88 | become: false 89 | roles: 90 | - { role: upcloud-firewall, tags: ['upcloud', 'firewall'] } 91 | 92 | # NOTE: This will setup disks to servers if upcloud_server_spec_list and upcloud_project_name variables are defined 93 | # Setups disks for all servers and disallows SSH password access 94 | - name: Setup Upcloud Servers 95 | hosts: upcloud_created_servers 96 | roles: 97 | - { role: upcloud-disks, tags: ['upcloud', 'disks'] } 98 | - { role: resize-root-disk, tags: ['disks'] } 99 | 100 | tasks: 101 | - name: Disallow SSH access with password 102 | lineinfile: 103 | dest: /etc/ssh/sshd_config 104 | regexp: "^PasswordAuthentication" 105 | line: "PasswordAuthentication no" 106 | state: present 107 | notify: restart sshd 108 | 109 | handlers: 110 | - name: restart sshd 111 | service: 112 | name: sshd 113 | state: restarted 114 | 115 | -------------------------------------------------------------------------------- /conf/vagrant.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - hosts: default 4 | become: true 5 | become_method: sudo 6 | user: vagrant 7 | roles: 8 | - { role: base, tags: [ 'base' ] } 9 | - { role: nginx, tags: [ 'nginx' ] } 10 | - { role: php-fpm, tags: [ 'php-fpm' ] } 11 | # Instead of php-fpm + nginx you can also use apache + php 12 | # - { role: httpd-php, tags: [ 'httpd-php' ] } 13 | - { role: varnish, tags: [ 'varnish' ] } 14 | - { role: memcached, tags: [ 'memcached' ] } 15 | # Drush also includes composer and prestissimo for speedier composer runs 16 | - { role: drush, tags: [ 'drush' ] } 17 | # Drupal console is also available: 18 | # - { role: drupal-console, tags: [ 'drupal-console' ] } 19 | - { role: dbserver, tags: [ 'dbserver' ] } 20 | # By default this will create database called drupal with user: drupal and password: password 21 | - { role: drupal-db, tags: [ 'drupal-db' ] } 22 | # Local replacement for letsencrypt. Uses letsencrypt settings but creates self signed certificates for local use. 23 | - { role: selfencrypt, tags: [ 'selfencrypt' ] } 24 | - { role: sslterminator, tags: [ 'sslterminator' ] } 25 | # For search. If you need project specific custom settings you need to copy this role from 26 | # ansible/playbook/roles/solr to local_ansible_roles/{project]_solr and do the modifications there (under files/) 27 | # - { role: solr, tags: [ 'solr' ] } 28 | # Of course Elasticsearch as search backend is more modern 29 | # - { role: elasticsearch, tags: [ 'elasticsearch' ] } 30 | # Devtools, uncomment to enable xdebug and blackfire 31 | # To use blackfire you need to define blackfire keys and tokens (see ansible/playbook/roles/devtools/defaults/main.yml for reference) 32 | # You can get those keys and tokens from https://blackfire.io/docs/up-and-running/installation 33 | # Otherwise you can disable blackfire by setting enable_blackfire: false 34 | - { role: devtools, tags: [ 'devtools' ] } 35 | # Mailhog, uncomment to catch outgoing mail. You can access mailhog at your local site url on port 8025 36 | - { role: mailhog, tags: [ 'mailhog' ] } 37 | # Optional HHVM role. 38 | # Requires WunderMachina hhvm branch https://github.com/wunderkraut/WunderMachina/tree/hhvm 39 | # More in-depth instructions at https://github.com/wunderkraut/WunderTools/wiki/HHVM 40 | #- { role: hhvm, tags: [ 'hhvm' ] } 41 | #- { role: selenium, tags: [ 'selenium' ] } 42 | 43 | tasks: 44 | 45 | - cron: name="check dirs" minute="0" hour="5,2" job="ls -alh > /dev/null" 46 | 47 | vars: 48 | domain_name: local.wundertools.com 49 | 50 | wkv_site_env: local 51 | base_pubkeys_enable: False 52 | 53 | varnish_control_key: something-randomly-generated 54 | 55 | nginx_disable_content_security_policy: True 56 | 57 | # How to assign memory for each role and set the correct 58 | # amount of worker processes / threads 59 | memory_db: 1024 # In MB 60 | memory_app: 1024 # In MB 61 | # NOTE: ALWAYS leave some spare memory for the server 62 | 63 | # Make sure changes to PHP files are not ignored 64 | php_ini: 65 | - section: OPCACHE 66 | options: 67 | - key: opcache.validate_timestamps 68 | val: 1 69 | - key: opcache.revalidate_freq 70 | val: 0 71 | 72 | # letsencrypt variables are also used by selfencrypt 73 | letsencrypt_email: "{{ support_email }}" 74 | letsencrypt_domains: 75 | - "{{ domain_name }}" 76 | 77 | # Apps I want to run on this server 78 | apps: 79 | - server_name: "{{ domain_name }}" 80 | http_port: 8080 81 | docroot: /vagrant/drupal/web 82 | - server_name: "docs.{{ domain_name}}" 83 | http_port: 8082 84 | docroot: /vagrant/docs 85 | # This server also acts as a load balancer 86 | varnish: 87 | port: 8081 88 | memory: 512M 89 | probe_resource_url: "_ping.php" 90 | control_key: "{{ varnish_control_key }}" 91 | acl_internal: 92 | - ip: 127.0.0.1 93 | acl_purge: 94 | - ip: 127.0.0.1 95 | acl_upstream_proxy: 96 | - ip: 127.0.0.1 97 | directors: 98 | - name: example 99 | host: "{{ domain_name }}" 100 | backends: 101 | - name: local_example_http 102 | address: 127.0.0.1 103 | port: 8080 104 | 105 | # BUT, we only use https for this host 106 | sslterminators: 107 | - server_name: "{{ domain_name }}" 108 | ssl_certificate: ssl/certificate.crt 109 | ssl_certificate_key: ssl/certificate.key 110 | backends: 111 | - 127.0.0.1:8081 112 | # Enable mailhog to be accessed from /mailhog 113 | extra_proxy_locations: 114 | - location: "/mailhog" 115 | definition: | 116 | rewrite /mailhog/(.*) /$1 break; 117 | proxy_pass http://0.0.0.0:8025; 118 | chunked_transfer_encoding on; 119 | proxy_hide_header X-Frame-Options; 120 | add_header Content-Security-Policy "default-src https: data: 'unsafe-inline' 'unsafe-eval'; connect-src ws: https:;" always; 121 | proxy_set_header X-NginX-Proxy true; 122 | proxy_set_header Upgrade $http_upgrade; 123 | proxy_set_header Connection "upgrade"; 124 | proxy_http_version 1.1; 125 | proxy_redirect off; 126 | proxy_buffering off; 127 | 128 | 129 | httpforwards: 130 | - server_name: "{{ domain_name }}" 131 | forwarded_domains: '"{{ domain_name }}"' 132 | 133 | # Set a low heap allocation for Elasticsearch on vagrant. 134 | elasticsearch_heap_size: 200m 135 | -------------------------------------------------------------------------------- /conf/vagrant_local.yml: -------------------------------------------------------------------------------- 1 | name: wundertools 2 | hostname: local.wundertools.com 3 | mem: 2000 4 | cpus: 2 5 | 6 | # By default we use DHCP to assign box ip, but you can define it explicitly here if needed 7 | ip: 192.168.10.179 8 | 9 | box: "geerlingguy/centos7" 10 | 11 | # Check for newer box versions here: https://atlas.hashicorp.com/geerlingguy/boxes/centos7 12 | # Box version is used for Virtualboxes only. VMWare does not have problems with the latest versions at this time 13 | #box_version: 1.2.0 14 | 15 | # optional host aliases 16 | aliases: local.alias.wundertools.com local.alias2.wundertools.com docs.local.wundertools.com 17 | 18 | # Uncomment to enable ssh-agent forwarding to vagrant box 19 | # Enables you to use your host keys from inside the box to access remotes 20 | #ssh_forward_agent: true 21 | -------------------------------------------------------------------------------- /conf/variables.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | project_name: Wundertools 4 | 5 | support_email: support@wundertools.com 6 | 7 | innodb_buffer_pool_size: "{{ memory_db }}" 8 | 9 | php_memory_limit: 256 # In MB "256MB ought to be enough for anyone." -Bill Gates 10 | 11 | # Estimated average memory usage per process 12 | # Use `ps --no-headers -o "rss,cmd" -C php-fpm | awk '{ sum+=$1 } END { printf ("%d%s\n", sum/NR/1024,"Mb") }'` to get the average 13 | php_app_memory_usage_average: 64 14 | 15 | # Following values are highly dependand on server resources so we just calculate suitable values for them here. 16 | php_fpm_pm_max_children: "{{ memory_app // php_app_memory_usage_average }}" 17 | php_fpm_pm_start_servers: "{{ php_fpm_pm_max_children|int // 2 }}" 18 | php_fpm_pm_min_spare_servers: "{{ php_fpm_pm_max_children|int // 2 }}" 19 | php_fpm_pm_max_spare_servers: "{{ php_fpm_pm_max_children|int // 2 }}" 20 | 21 | base_pubkeys_url: https://{{ base_pubkeys_host }}/auth?hostname={{ ansible_nodename }} 22 | base_addhost_url: https://{{ base_pubkeys_host }}/auth/add_server?hostname={{ ansible_nodename }} 23 | 24 | partition_var_log: False 25 | partition_var_lib_mysql: False 26 | 27 | # PHP version to be used. Available options: php56u, php70u, php71u, php72u 28 | php_package: "php71u" 29 | 30 | drush: { 31 | version: "9.*", 32 | } 33 | 34 | drush_use_launcher: True 35 | php_env_vars_include_db: True 36 | expose_php_vars_globally: True 37 | 38 | # Variables to create/update UpCloud Servers 39 | upcloud_project_name: "{{ project_name }}" 40 | #upcloud_server_admin_ssh_keys: 41 | 42 | upcloud_zone: de-fra1 43 | 44 | # These are the specifications for servers in this project 45 | upcloud_server_spec_list: 46 | - group: prod-front 47 | # This can be used to add these machines into multiple groups 48 | secondary_groups: 49 | - prod 50 | members: 51 | - { name: 1, state: present } 52 | - { name: 2, state: present } 53 | settings: 54 | plan: 4xCPU-8GB 55 | zone: "{{ upcloud_zone }}" 56 | # Allows ansible to reboot the machine when making changes to the disks 57 | allow_reboot_on_resize: true 58 | storage_devices: 59 | - title: root 60 | os: CentOS 7.0 61 | size: 50 62 | - title: logs 63 | size: 10 64 | mount: 65 | path: /var/log 66 | fstype: ext4 67 | opts: defaults,noatime 68 | - group: prod-lb 69 | secondary_groups: 70 | - prod 71 | - firewall_web 72 | members: 73 | - { name: 1, state: present } 74 | settings: 75 | plan: 4xCPU-8GB 76 | zone: "{{ upcloud_zone }}" 77 | # Allows ansible to reboot the machine when making changes to the disks 78 | allow_reboot_on_resize: true 79 | storage_devices: 80 | - title: root 81 | os: CentOS 7.0 82 | size: 50 83 | - title: logs 84 | size: 10 85 | mount: 86 | path: /var/log 87 | fstype: ext4 88 | opts: defaults,noatime 89 | - group: prod-db 90 | secondary_groups: 91 | - prod 92 | members: 93 | - { name: 1, state: present } 94 | settings: 95 | plan: 6xCPU-16GB 96 | zone: "{{ upcloud_zone }}" 97 | # Allows ansible to reboot the machine when making changes to the disks 98 | allow_reboot_on_resize: true 99 | storage_devices: 100 | - title: root 101 | os: CentOS 7.0 102 | size: 50 103 | - title: logs 104 | size: 10 105 | mount: 106 | path: /var/log 107 | fstype: ext4 108 | opts: defaults,noatime 109 | - title: nfs 110 | size: 100 111 | mount: 112 | path: /nfs-files 113 | fstype: ext4 114 | opts: defaults,noatime 115 | backup_rule: { interval: daily, time: '0330', retention: 7 } 116 | - title: database 117 | size: 30 118 | mount: 119 | path: /var/lib/mysql 120 | fstype: ext4 121 | # Options for mysql performance 122 | # These are the same as Mozilla is using for their mysql servers: https://bugzilla.mozilla.org/show_bug.cgi?id=874039 123 | opts: defaults,noatime,data=writeback,barrier=0,dioread_nolock 124 | - group: stage 125 | # This can be used to add these machines into multiple groups 126 | secondary_groups: 127 | - firewall_web 128 | members: 129 | - { name: web1, state: present } 130 | settings: 131 | plan: 2xCPU-4GB 132 | zone: "{{ upcloud_zone }}" 133 | # Allows ansible to reboot the machine when making changes to the disks 134 | allow_reboot_on_resize: true 135 | storage_devices: 136 | - title: root 137 | size: 50 138 | os: CentOS 7.0 139 | - title: logs 140 | size: 10 141 | mount: 142 | path: /var/log 143 | fstype: ext4 144 | opts: noatime 145 | - group: dev 146 | # This can be used to add these machines into multiple groups 147 | secondary_groups: 148 | - firewall_web 149 | members: 150 | - { name: web1, state: present } 151 | settings: 152 | plan: 2xCPU-4GB 153 | zone: "{{ upcloud_zone }}" 154 | # Allows ansible to reboot the machine when making changes to the disks 155 | allow_reboot_on_resize: true 156 | storage_devices: 157 | - title: root 158 | size: 50 159 | os: CentOS 7.0 160 | - title: logs 161 | size: 10 162 | mount: 163 | path: /var/log 164 | fstype: ext4 165 | opts: noatime 166 | -------------------------------------------------------------------------------- /docs/Setup.md: -------------------------------------------------------------------------------- 1 | ## Starting a new Drupal 8 Project with WunderTools 2 | 3 | ### Preparation 4 | 5 | Start by downloading and unarchiving a zipball of the WunderTools as a base for your new project from 6 | https://github.com/wunderkraut/WunderTools/archive/master.zip 7 | 8 | ##### a) if you already have git repo 9 | 10 | * Move the content (with dotfiles) of WunderTools-master into your git repo directory. 11 | - `mv WunderTools-master/{.[!.],}* ~/Projects/my-existing-project/` 12 | (works on OSX, but you should know how to copy if this is not a bulletproof one-liner on your system) 13 | 14 | ##### b) if you don't already have git 15 | 16 | * Just rename WunderTools-master to whatever project folder you have and init git inside it: 17 | ``` 18 | mv WunderTools-master ~/Projects/my-new-project 19 | cd ~/Projects/my-new-project 20 | git init 21 | ``` 22 | 23 | ## Configure WunderTools 24 | 25 | * Edit `conf/vagrant.yml` and change `domain_name` variable to match your local domain. 26 | * Edit `conf/vagrant_local.yml` and change: 27 | - `name` to the name of your project 28 | - `hostname` to a good hostname for your local environment (preferably replace *www* part of your production domain 29 | with *local*) 30 | - `ip` to something that no other project in your company uses (increment the value by one and commit the new ip 31 | address to WunderTools repository) 32 | 33 | * Edit `conf/project.yml` and change the variables to something that makes sense for your project. 34 | - Minimally `project:name` to the name of your project 35 | 36 | * Edit `conf/develop.yml` and change the variables to something that makes sense for your project. 37 | 38 | ## Configure Drupal build 39 | 40 | * Edit `drupal/conf/site.yml`, remove things you don't need and add stuff you want in your project. 41 | * Rename `drupal/drush/wundertools.aliases.drushrc.php` to `drupal/drush/[PROJECT_NAME].aliases.drushrc.php` and 42 | configure it to fit your setup. 43 | - this file will be automatically symlinked from `~/.drush` when running vagrant up 44 | 45 | ## Create new drupal installation 46 | 47 | * Run `./build.sh create` from inside the vagrant box from the `/vagrant/drupal/` folder 48 | * Add drupal scaffold files and relevant config export files to the repo 49 | -------------------------------------------------------------------------------- /docs/continuous-integration.md: -------------------------------------------------------------------------------- 1 | Instructions for using Travis CI 2 | ================================ 3 | 4 | See [Wundertools documentation](https://wundertools.wunder.io/#!continuous-integration.md). 5 | 6 | If your projects CI practice deviates from those, please document them here. 7 | 8 | -------------------------------------------------------------------------------- /docs/development.md: -------------------------------------------------------------------------------- 1 | Development instructions 2 | ======================== 3 | 4 | Clone the git repo and build the vagrant machine by running 5 | 6 | vagrant up 7 | 8 | Once the machine is built and provisioned you can login with 9 | 10 | vagrant ssh 11 | 12 | Git flow instructions 13 | --------------------- 14 | 15 | By default we are using WunderFlow as our development workflow. 16 | See [WunderFlow](http://wunderkraut.github.io/WunderFlow) for reference. 17 | 18 | If the project uses something else please document it here. 19 | -------------------------------------------------------------------------------- /docs/hacks.md: -------------------------------------------------------------------------------- 1 | Hacks 2 | ===== 3 | 4 | This project required no special hacks. 5 | 6 | ## Hack XXX 7 | **Except if did.** 8 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | [PROJECT] DOCS 2 | ============== 3 | 4 | This file contains the general documentation on the [project] project. It is 5 | intended for developers who are involved in the project, and to ease the 6 | handover of activities and support to colleagues. 7 | 8 | Our customer 9 | ------------ 10 | 11 | The client is [client]. They are [describe the client in 1-3 lines]. 12 | 13 | Website's goal 14 | -------------- 15 | 16 | The main objectives of the site are to [describe the main objectives]. 17 | 18 | Project management 19 | ------------------ 20 | 21 | The project is managed using the following tools: 22 | 23 | * Git repo: [url1] 24 | * Ticket system: [url2] 25 | * Project documentation: [googledocsurls] 26 | 27 | Hosting info 28 | ------------ 29 | 30 | The following environments are used in this project: 31 | 32 | * Development [dev_url] 33 | - servers: 34 | * Staging [sta_url] 35 | - servers: 36 | * Live site [live_url] 37 | - servers: 38 | 39 | 40 | Terminology 41 | ----------- 42 | 43 | Definitions of project specific terms and expressions. 44 | 45 | ### Term 1 46 | 47 | If some terms have specific meaning within the project, please document them 48 | here. 49 | -------------------------------------------------------------------------------- /docs/information_architecture.md: -------------------------------------------------------------------------------- 1 | Information architecture 2 | ============= 3 | 4 | Describe all relevant content types, vocabularies, relations, custom entities, etc. here. 5 | 6 | Content types 7 | ------------- 8 | 9 | These are the content types used in this project. 10 | 11 | ### Content type XX 12 | For each content type, describe the fields used, with their usage. 13 | Describe the technical details involved with this content type. 14 | 15 | Vocabularies 16 | ------------- 17 | 18 | ### Vocabulary XX 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /docs/modules.md: -------------------------------------------------------------------------------- 1 | Modules 2 | ======= 3 | 4 | The following custom modules are used in this project. 5 | 6 | ## Module XX 7 | Describe the purpose of any custom modules developed, and any configuration that may be necessary. 8 | If the module implementation is not trivial, please add a description of the module design reasons. Module documentation should be in the module, so this is mostly to document anything related to the module development (tradeoff against existing modules, etc.). 9 | 10 | ## Drupal.org module YYY 11 | Only list a d.o module, in case any comparison between modules was performed, and describe the conclusions here. 12 | If an existing module was extensively modified, it may be useful to describe those changes here. 13 | -------------------------------------------------------------------------------- /docs/navigation.md: -------------------------------------------------------------------------------- 1 | [Index](index.md) 2 | [Development](development.md) 3 | [Information Architecture](information_architecture.md) 4 | [Theme](theme.md) 5 | [Modules](modules.md) 6 | [Views](views.md) 7 | [Hacks](hacks.md) 8 | [Continuous integration](continuous-integration.md) 9 | [Testing](testing.md) 10 | 11 | -------------------------------------------------------------------------------- /docs/testing.md: -------------------------------------------------------------------------------- 1 | Testing instructions 2 | ======================== 3 | 4 | By default we are using Codeception for testing. 5 | See [Wundertools documentation](https://wundertools.wunder.io/#!testing.md) for more info. 6 | 7 | If your project uses something else, please document here. 8 | 9 | -------------------------------------------------------------------------------- /docs/theme.md: -------------------------------------------------------------------------------- 1 | Theme 2 | ===== 3 | 4 | Description 5 | ----------- 6 | 7 | The theme is called [theme] and is based on [base_theme]. 8 | 9 | Dependencies 10 | ------------ 11 | 12 | This theme requires the following modules to run: 13 | 14 | * base_theme 15 | * panels_nowhere 16 | 17 | Frontpage 18 | --------- 19 | 20 | Describe how the frontpage is built (panel, view, etc.) 21 | 22 | Custom templates 23 | ---------------- 24 | 25 | The theme uses the following templates: 26 | 27 | ### tpl1.tpl.php 28 | This template is used for something. 29 | 30 | Further details 31 | --------------- 32 | 33 | Describe any other theme-related special details (javascript libraries added, etc.). 34 | -------------------------------------------------------------------------------- /docs/upcloud.md: -------------------------------------------------------------------------------- 1 | # UpCloud provisioning 2 | 3 | [Wundermachina](https://github.com/wunderkraut/wundermachina) contains a ansible roles for: 4 | * Provisioning UpCloud servers using `upcloud-servers` role. 5 | * Setupping disks and mounting them to machines with `upcloud-disks` role. 6 | * Creating and Updating firewall rules using `upcloud-firewall` role. 7 | 8 | ## Requirements 9 | * UpCloud account or subaccount 10 | * Upcloud account needs to have API-access enabled (This can only be enabled by the main account) 11 | 12 | ## Provisioning servers and disks to UpCloud with ansible 13 | 14 | You need to set unique project name for UpCloud in `conf/variables.yml`: 15 | ```yml 16 | upcloud_project_name: Example-Client 17 | ``` 18 | 19 | The role uses root user ssh keys automatically from the key server when [WunderSecrets](https://github.com/wunderio/wundersecrets) are used but you can override that by providing your own: 20 | ```yml 21 | upcloud_server_admin_ssh_keys: 22 | - ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAA... 23 | - ssh-rsa AAAAB3NzaC1yc2EAAAADAQAB... 24 | ``` 25 | 26 | You can change the UpCloud region by providing following variable: 27 | ``` 28 | # Other options: de-fra1, uk-lon1 ... 29 | upcloud_default_zone: fi-hel1 30 | ``` 31 | 32 | **WARNING:** The provision works by doing hostname mapping per server to the UpCloud servers. This means that every machine needs to have unique hostname. This is achieved by declaring `upcloud_server_hostname_base_domain` variable in `conf/variables.yml`. For example: 33 | 34 | ``` 35 | upcloud_server_hostname_base_domain: upcloud.example.com 36 | ``` 37 | 38 | This example domain will create machines with following logic: 39 | ``` 40 | {members.name}.{group}.{upcloud_project_name|lowercase}.{region}.upcloud.example.com 41 | ``` 42 | 43 | So the server examples below would create machines correspondingly: 44 | ``` 45 | production-web1.example-client.de-fra1.upc.wunder.io 46 | stage-web1.example-client.de-fra1.upc.wunder.io 47 | ``` 48 | 49 | **NOTE:** If you are using [WunderSecrets](https://github.com/wunderio/wundersecrets) you don't need to add `upcloud_server_hostname_base_domain` because Wunder uses shared subdomain for all servers. 50 | 51 | ### How to modify server details 52 | 53 | You can edit the UpCloud server specifications in `conf/variables.yml`: 54 | 55 | ```yaml 56 | # These are the specifications for servers in this project 57 | upcloud_server_spec_list: 58 | - group: production 59 | members: 60 | - { name: web1, state: present } 61 | settings: 62 | plan: 6xCPU-8GB 63 | zone: de-fra1 64 | # Allows ansible to reboot the machine when making changes to the disks 65 | allow_reboot_on_resize: true 66 | storage_devices: 67 | - title: root 68 | os: CentOS 7.0 69 | size: 200 70 | backup_rule: { interval: daily, time: '0430', retention: 14 } 71 | - title: logs 72 | size: 10 73 | mount: 74 | path: /var/log 75 | fstype: ext4 76 | opts: defaults,noatime 77 | - title: database 78 | size: 30 79 | mount: 80 | path: /var/lib/mysql 81 | fstype: ext4 82 | # Options for mysql performance 83 | # These are the same as Mozilla is using for their mysql servers: https://bugzilla.mozilla.org/show_bug.cgi?id=874039 84 | opts: defaults,noatime,data=writeback,barrier=0,dioread_nolock 85 | backup_rule: { interval: daily, time: '0430', retention: 14 } 86 | # Swap is recommended for system stability and when it's a partition it can be excluded from backups 87 | # Upcloud minimum partition is 10gb 88 | - title: swap 89 | size: 10 90 | mount: 91 | path: none 92 | fstype: swap 93 | state: present 94 | opts: sw 95 | - group: stage 96 | members: 97 | - { name: web1, state: present } 98 | settings: 99 | plan: 2xCPU-2GB 100 | zone: de-fra1 101 | # Allows ansible to reboot the machine when making changes to the disks 102 | allow_reboot_on_resize: true 103 | storage_devices: 104 | - title: root 105 | size: 50 106 | os: CentOS 7.0 107 | backup_rule: { interval: daily, time: '0430', retention: 14 } 108 | - title: logs 109 | size: 10 110 | mount: 111 | path: /var/log 112 | fstype: ext4 113 | opts: noatime 114 | ``` 115 | 116 | ### How to provision UpCloud servers 117 | ```bash 118 | # Setup UpCloud credentials as envs 119 | $ export UPCLOUD_API_USER=first.last@example.com UPCLOUD_API_PASSWD=XXXXXXXXXX 120 | 121 | # Provision the servers 122 | $ ./provision.sh upcloud 123 | ``` 124 | 125 | ## Setupping the UpCloud firewall 126 | 127 | You can also automatically setup [UpCloud provided firewall](https://www.upcloud.com/support/firewall/) by using WunderTools. This will work even if you didn't provision the servers with ansible. 128 | 129 | ### Firewall configuration 130 | 131 | Default firewall rules are applied from [WunderSecrets](https://github.com/wunderio/wundersecrets). You can only see this repository if you have access to `wunderio` organization. The default rules will create list variable `firewall_ssh_allowed` which contains our prefered CI and office IP-addresses for administration. You shouldn't and can't override `firewall_ssh_allowed` variable. 132 | 133 | Variables you can modify or add to your project are listed here: 134 | ```yml 135 | # This rule set is used to allow additional servers to access ssh port 136 | project_additional_ssh_firewall_rules: 137 | - comment: Admin machine 138 | ip: 10.0.0.1 139 | - comment: Test Machine 140 | ip: 127.0.0.1 141 | 142 | # Because ansible is declarative it doesn't delete old records automatically 143 | # You should move old non-used rules over here to ensure that they are deleted. 144 | remove_ssh_firewall_rules: 145 | - comment: Admin machine 146 | ip: 10.0.0.1 147 | - comment: Test Machine 148 | ip: 127.0.0.1 149 | 150 | # These are added after ssh rules 151 | # It can be used to open ports for other services than ssh 152 | # If you open up Remember to use encryption with those services! 153 | project_additional_firewall_rules: 154 | - direction: in 155 | family: IPv4 156 | protocol: tcp 157 | source_address_start: 127.0.0.1 158 | source_address_end: 127.0.0.255 159 | destination_port_start: 3306 160 | destination_port_end: 3306 161 | action: accept 162 | ``` 163 | 164 | #### Allow web traffic for machines 165 | To allow web traffic for http (80) and https (443) ports for certain machine you need to add the machines into `firewall_web` group. This is easiest to achieve by adding the web machine group names under the `[firewall_web:children]` directive into `conf/server.inventory`: 166 | 167 | ``` 168 | [firewall_web:children] 169 | wundertools-dev 170 | wundertools-stage 171 | wundertools-prod-lb 172 | ``` 173 | 174 | All other rules are universal for all machines in the project. Web ports are only opened for machines that really need them. 175 | 176 | ### Deploy firewall settings to UpCloud 177 | 178 | When you are ready you can provision firewall rules with `provision.sh`: 179 | ```bash 180 | 181 | # Setup UpCloud credentials as envs 182 | $ export UPCLOUD_API_USER=first.last@example.com UPCLOUD_API_PASSWD=XXXXXXXXXX 183 | 184 | ./provision.sh -t firewall upcloud 185 | ``` 186 | 187 | ## Notes 188 | 189 | * Always use the maximum amount of disk space from the used plan in the root mount. For example when you're using 6xCPU-8GB plan you should use 200gb for the root mount. For 4xCPU-4GB you should use 100gb and so on. This way you can leverage all of the package discount from upcloud plans. 190 | 191 | 192 | ## Troubleshooting 193 | **ERROR! no action detected in task.** 194 | 195 | If ansible outputs this error to you it's probably because `ansible.cfg` is missing custom library path. You can fix this by adding this line into it: 196 | 197 | ```ini 198 | library=./ansible/playbook/library 199 | ``` 200 | 201 | **The conditional check 'item in groups['firewall_web']' failed.** 202 | 203 | If you have this error the `[firewall_web]` group in `conf/server.inventory` is empty. You need to add all groups that use web ports into `firewall_web` group like this: 204 | 205 | ```ini 206 | [firewall_web:children] 207 | wundertools-dev 208 | wundertools-stage 209 | wundertools-prod-lb 210 | ``` 211 | 212 | **UpCloudAPIError: AUTHENTICATION_FAILED Authentication failed using the given username and password** 213 | 214 | You are either using wrong credentials or the API-access is not enabled. Test to login with those credentials to confirm that they are correct. You can enable API-access from: https://my.upcloud.com/account. **NOTE:** API-access needs to be enabled by the main user. Subaccounts can't grant API-access to themselves. -------------------------------------------------------------------------------- /docs/views.md: -------------------------------------------------------------------------------- 1 | Views 2 | ===== 3 | 4 | The following views are used in this project. 5 | 6 | ## View XX 7 | If any view is not trivial to understand and special hooks had to be developed, document them here. 8 | -------------------------------------------------------------------------------- /drupal/.dockerignore: -------------------------------------------------------------------------------- 1 | # Ignore file for the Drupal container. 2 | **/*.md 3 | **/CHANGELOG* 4 | **/README* 5 | **/LICENSE* 6 | .circleci/* 7 | phpcs.xml 8 | **/*.sql 9 | **/*.gz 10 | **/*.zip 11 | web/core/**/tests 12 | web/modules/contrib/**/tests 13 | vendor/**/Tests 14 | **/node_modules 15 | **/.git 16 | web/sites/**/files 17 | web/.dockerignore 18 | Dockerfile 19 | silta/silta*.yml 20 | -------------------------------------------------------------------------------- /drupal/.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore directories generated by Composer 2 | /drush/contrib/ 3 | /vendor/ 4 | /web/core/ 5 | /web/modules/contrib/ 6 | /web/themes/contrib/ 7 | /web/profiles/contrib/ 8 | /web/libraries/ 9 | 10 | # Ignore wunderio/drupal-ping healthcheck module 11 | web/_ping.php 12 | 13 | # Ignore test logs 14 | /tests/_log 15 | 16 | # Ignore test generated code 17 | /tests/_support/_generated 18 | 19 | # Ignore sensitive information 20 | /web/sites/*/settings.local.php 21 | 22 | # Ignore Drupal's file directory 23 | /web/sites/*/files/ 24 | 25 | # Ignore SimpleTest multi-site environment. 26 | /web/sites/simpletest 27 | 28 | # Ignore files generated by PhpStorm 29 | /.idea/ 30 | 31 | # Ingore files generated by scaffold which are not needed 32 | web/.csslintrc 33 | web/.editorconfig 34 | web/.eslintignore 35 | web/.eslintrc.json 36 | web/.htaccess 37 | web/sites/default/default.services.yml 38 | web/sites/default/default.settings.php 39 | web/sites/example.settings.local.php 40 | web/sites/example.sites.php 41 | web/update.php 42 | web/web.config 43 | 44 | web/_ping.php 45 | -------------------------------------------------------------------------------- /drupal/.lando.yml: -------------------------------------------------------------------------------- 1 | name: wunderlando 2 | recipe: drupal8 3 | 4 | config: 5 | webroot: web 6 | via: nginx 7 | php: '7.2' 8 | database: mariadb:10.2 9 | 10 | tooling: 11 | build.sh: 12 | service: appserver 13 | description: Execute build.sh 14 | cmd: 15 | - ./build.sh 16 | codeception: 17 | service: appserver 18 | description: Run codeception 19 | cmd: 20 | - ./vendor/bin/codecept 21 | xdebug-on: 22 | service: appserver 23 | description: Enable xdebug for nginx. 24 | cmd: docker-php-ext-enable xdebug && pkill -o -USR2 php-fpm 25 | user: root 26 | xdebug-off: 27 | service: appserver 28 | description: Disable xdebug for nginx. 29 | cmd: rm /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini && pkill -o -USR2 php-fpm 30 | user: root 31 | 32 | services: 33 | mailhog: 34 | type: mailhog 35 | hogfrom: 36 | - appserver 37 | 38 | appserver: 39 | build: 40 | # Perform composer install. 41 | - composer install 42 | build_as_root: 43 | - "apt-get update -y" 44 | - "apt-get install python-yaml -y" 45 | # wkhtmltopdf setup 46 | # - "[ -f /usr/bin/wkhtmltopdf ] || ( apt-get update -y && apt-get install xvfb -y)" 47 | # - "[ -f /usr/bin/wkhtmltopdf ] || ( wget -nc https://downloads.wkhtmltopdf.org/0.12/0.12.5/wkhtmltox_0.12.5-1.jessie_amd64.deb -P /tmp )" 48 | # - "[ -e /usr/bin/wkhtmltopdf ] || ( dpkg -i /tmp/wkhtmltox_0.12.5-1.jessie_amd64.deb )" 49 | # - "[ -f /usr/bin/wkhtmltopdf ] || ( ln -s /usr/local/bin/wkhtmltopdf /usr/bin/wkhtmltopdf )" 50 | overrides: 51 | environment: 52 | WKV_SITE_ENV: lando 53 | DB_PASS_DRUPAL: drupal8 54 | DB_USER_DRUPAL: drupal8 55 | DB_HOST_DRUPAL: database 56 | DB_NAME_DRUPAL: drupal8 57 | SITE_URL: 'https://nginx' 58 | HASH_SALT: notsosecurehash 59 | # Optional. Uncomment the following lines to enable chrome service. 60 | # chrome: 61 | # type: compose 62 | # services: 63 | # image: selenium/standalone-chrome 64 | # user: root 65 | # ports: 66 | # - "4444:4444" 67 | # volumes: 68 | # - /dev/shm:/dev/shm 69 | # command: /opt/bin/entry_point.sh 70 | 71 | # Optional. Uncomment the following lines if Elasticsearch is required. 72 | # elasticsearch: 73 | # type: elasticsearch:7 74 | # portforward: "9200" 75 | # mem: 1026m 76 | # # Install `analysis-icu` plugin after ES service boots up. 77 | # run_as_root: 78 | # - elasticsearch-plugin install analysis-icu 79 | # kibana: 80 | # services: 81 | # command: /docker-entrypoint.sh kibana 82 | # depends_on: 83 | # - elasticsearch 84 | # image: blacktop/kibana:7 85 | # ports: 86 | # - "5601:5601" 87 | # type: compose 88 | proxy: 89 | mailhog: 90 | - mail.lndo.site 91 | # Uncomment the following lines if Elasticsearch & Kibana is required. 92 | # elasticsearch: 93 | # - es.lndo.site:9200 94 | # kibana: 95 | # - kibana.lndo.site:5601 96 | 97 | events: 98 | # Clear caches after a database import 99 | post-db-import: 100 | - appserver: cd $LANDO_WEBROOT && drush cr -y 101 | # Enable Drush aliases. 102 | post-start: 103 | - appserver: mkdir -p $LANDO_MOUNT/.drush/site-aliases 104 | - appserver: ln -sf /app/drush/wundertools.aliases.drushrc.php $LANDO_MOUNT/.drush/site-aliases/wundertools.aliases.drushrc.php 105 | 106 | # Lando version at the time of creation of .lando.yml. 107 | version: v3.0.0-rc.18 108 | -------------------------------------------------------------------------------- /drupal/build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # ***************************************************************************** 3 | # build.sh by the Wunderful People at Wunderkraut 4 | # 5 | # https://github.com/wunderkraut/build.sh 6 | # ***************************************************************************** 7 | from __future__ import print_function 8 | import datetime 9 | import getopt 10 | import hashlib 11 | import os 12 | import random 13 | import re 14 | import shutil 15 | import stat 16 | import string 17 | import subprocess 18 | import sys 19 | import tarfile 20 | import time 21 | import yaml 22 | from distutils.spawn import find_executable 23 | from contextlib import closing 24 | from distutils.dir_util import copy_tree 25 | 26 | try: 27 | # Python 2 28 | input = raw_input 29 | except NameError: 30 | # Python 3 doesn't have raw_input 31 | pass 32 | 33 | try: 34 | # Python 2 35 | basestring 36 | except NameError: 37 | # Python 3 doesn't have basestring 38 | basestring = (str, bytes) 39 | 40 | 41 | # Build scripts version string. 42 | build_sh_version_string = "build.sh 1.0" 43 | 44 | build_sh_skip_backup = False 45 | build_sh_disable_cache = False 46 | 47 | 48 | # Site.make item (either a project/library from the site.make) 49 | class MakeItem: 50 | 51 | def __init__(self, type, name): 52 | self.type = type 53 | self.name = name 54 | self.version = 'UNDEFINED' 55 | if self.type == 'libraries': 56 | self.project_type = 'library' 57 | else: 58 | self.project_type = 'module' 59 | 60 | self.download_args = {} 61 | 62 | # Parse a line from site.make for this project/lib 63 | def parse(self, line): 64 | 65 | # Download related items 66 | type = re.compile( 67 | "^[^\[]*\[[^\]]*\]\[download\]\[([^\]]*)\]\s*=\s*(.*)$") 68 | t = type.match(line) 69 | if t: 70 | self.download_args[t.group(1)] = t.group(2) 71 | 72 | # Version number 73 | version = re.compile("^.*\[version\]\s*=\s*(.*)$") 74 | v = version.match(line) 75 | if v: 76 | self.version = v.group(1) 77 | 78 | # Project type 79 | type = re.compile("^[^\[]*\[[^\]]*\]\[type\]\s*=\s*(.*)$") 80 | t = type.match(line) 81 | if t: 82 | self.project_type = t.group(1) 83 | 84 | # Validate site.make item, returns a string describing the issue 85 | # or False if no issues 86 | def validate(self): 87 | if 'type' in self.download_args: 88 | version = re.compile(".*[0-9]+\.[0-9]+.*") 89 | if self.download_args['type'] == 'git': 90 | if 'tag' not in self.download_args and 'revision' not in self.download_args: 91 | return "No revision or tag defined for a git download" 92 | elif self.download_args['type'] == 'file' and 'url' in self.download_args and not version.match(self.download_args['url']): 93 | return "URL does not seem to have a version number in it (" + self.download_args['url'] + ")" 94 | elif 'dev' in self.version: 95 | return "Development version in use (" + self.version + ")" 96 | return False 97 | 98 | 99 | # BuildError exception class. 100 | class BuildError(Exception): 101 | 102 | def __init__(self, value): 103 | self.value = value 104 | 105 | def __str__(self): 106 | return repr(self.value) 107 | 108 | 109 | # Maker class. 110 | class Maker: 111 | 112 | def __init__(self, settings): 113 | 114 | self.composer = settings.get('composer', 'composer') 115 | self.drush = settings.get('drush', 'drush') 116 | self.drupal_version = settings.get('drupal_version', 'd7') 117 | 118 | if self.drupal_version == 'd8': 119 | self.type = settings.get('type', 'composer') 120 | self.in_place = settings.get('build_in_place', True) 121 | self.drupal_subpath = settings.get('drupal_subpath', '/web') 122 | else: 123 | self.type = settings.get('type', 'drush make') 124 | self.in_place = settings.get('build_in_place', False) 125 | self.drupal_subpath = settings.get('drupal_subpath', '') 126 | 127 | self.temp_build_dir_name = settings.get('temporary', '.') 128 | self.temp_build_dir = os.path.abspath(self.temp_build_dir_name) 129 | self.final_build_dir_name = settings.get('final', '.') 130 | self.final_build_dir = os.path.abspath(self.final_build_dir_name) 131 | self.final_build_dir_bak = self.final_build_dir + "_bak_" + str(time.time()) 132 | self.old_build_dir = os.path.abspath(settings.get('previous', 'builds')) 133 | self.profile_name = settings.get('profile', 'standard') 134 | self.site_name = settings.get('site', 'A drupal site') 135 | self.multisite_site = settings.get('multisite_site', 'default') 136 | self.make_cache_dir = settings.get('make_cache', '.make_cache') 137 | self.site_env = settings.get('site_env', 'default') 138 | self.settings = settings 139 | self.store_old_buids = True 140 | self.linked = False 141 | 142 | if self.type == 'drush make': 143 | self.makefile = os.path.abspath(settings.get('makefile', 'conf/site.make')) 144 | self.makefile_hash = hashlib.md5(open(self.makefile, 'rb').read()).hexdigest() 145 | 146 | # See if drush is installed 147 | if not find_executable('drush'): 148 | raise BuildError('Drush missing!?') 149 | 150 | def test(self): 151 | self._validate_makefile() 152 | 153 | # Quickly validate the drush make file 154 | def _validate_makefile(self): 155 | with open(self.makefile) as f: 156 | content = f.readlines() 157 | projects = {} 158 | prog = re.compile("^([^\[]*)\[([^\]]*)\]\[([^\]]*)\].*$") 159 | for line in content: 160 | m = prog.match(line) 161 | if m: 162 | name = m.group(2) 163 | if name not in projects: 164 | projects[name] = MakeItem(m.group(1), name) 165 | projects[name].parse(line) 166 | 167 | errors = False 168 | for item in projects: 169 | error = projects[item].validate() 170 | if error: 171 | errors = True 172 | self.warning(projects[item].name + ': ' + error) 173 | if errors: 174 | raise BuildError("The make file is volatile - it is not ready for production use") 175 | else: 176 | self.notice("Everything looks good!") 177 | 178 | # Run make 179 | def make(self): 180 | if self.type == 'drush make': 181 | self._drush_make() 182 | elif self.type == 'composer': 183 | self._composer_make() 184 | 185 | def _composer_make(self): 186 | self._precheck() 187 | self.link() 188 | 189 | params = [] 190 | 191 | # Check if environment allows installing of dev packages 192 | if "allow_composer_dev" not in self.settings: 193 | allow_dev = False 194 | else: 195 | allow_dev = self.settings['allow_composer_dev'] 196 | 197 | # By default Do not install dev packages on non-development environments 198 | # Allow override to allow dev packages 199 | if self.site_env != 'default' and self.site_env != 'local' and allow_dev != True: 200 | params.append('--no-dev') 201 | params.append('--no-interaction') 202 | 203 | if self.temp_build_dir_name == ".": 204 | command_status = self._composer([ 205 | 'install' 206 | ] + params) 207 | else: 208 | command_status = self._composer([ 209 | '--working-dir=' + self.temp_build_dir, 210 | 'install' 211 | ] + params) 212 | 213 | if not command_status: 214 | raise BuildError("Composer install failed") 215 | 216 | def _drush_make(self): 217 | global build_sh_disable_cache 218 | self._precheck() 219 | self.notice("Building") 220 | 221 | packaged_build = self.make_cache_dir + '/' + self.makefile_hash + '.tgz' 222 | 223 | if not build_sh_disable_cache and os.path.exists(packaged_build): 224 | # Existing build 225 | self.notice("Make file unchanged - unpacking previous make") 226 | with closing(tarfile.open(packaged_build)) as tar: 227 | tar.extractall() 228 | 229 | else: 230 | 231 | if not self._drush(self._collect_make_args()): 232 | raise BuildError("Make failed - check your makefile") 233 | 234 | os.remove(self.temp_build_dir + "/sites/default/default.settings.php") 235 | 236 | if not os.path.isdir(self.make_cache_dir): 237 | os.makedirs(self.make_cache_dir) 238 | 239 | # Only create cache tar if cache is not disabled 240 | if not build_sh_disable_cache: 241 | with closing(tarfile.open(packaged_build, "w:gz")) as tar: 242 | tar.add(self.temp_build_dir, arcname=self.temp_build_dir_name) 243 | 244 | # with open(self.temp_build_dir + "/buildhash", "w") as f: 245 | # f.write(self.makefile_hash) 246 | # Remove default.settings.php 247 | 248 | # Existing final build? 249 | def has_existing_build(self): 250 | return os.path.isdir(self.final_build_dir) 251 | 252 | # Backup current final build 253 | def backup(self, params): 254 | global build_sh_skip_backup 255 | if build_sh_skip_backup: 256 | self.notice("Skipping backup!") 257 | return 258 | if not params: 259 | params = {} 260 | self.notice("Backing up current build") 261 | if self.has_existing_build(): 262 | self._backup(params) 263 | 264 | def cleanup(self): 265 | compare = time.time() - (60 * 60 * 24) 266 | for f in os.listdir(self.old_build_dir): 267 | fullpath = os.path.join(self.old_build_dir, f) 268 | if os.stat(fullpath).st_mtime < compare: 269 | if os.path.isdir(fullpath): 270 | self.notice("Removing old build " + f) 271 | shutil.rmtree(fullpath) 272 | elif os.path.isfile(fullpath): 273 | self.notice("Removing old build archive " + f) 274 | os.remove(fullpath) 275 | 276 | # Purge current final build 277 | def purge(self): 278 | self.notice("Purging current build") 279 | if self.has_existing_build(): 280 | self._wipe() 281 | 282 | # Link 283 | def link(self): 284 | # Link and copy required files 285 | self._link() 286 | self._copy() 287 | self.linked = True 288 | 289 | # Finalize new build to be the final build 290 | def finalize(self): 291 | self.notice("Finalizing new build") 292 | if os.path.isdir(self.final_build_dir): 293 | self._unlink() 294 | self._ensure_writable(self.final_build_dir) 295 | os.rename(self.final_build_dir, self.final_build_dir_bak) 296 | # Make sure linking has happened 297 | if not self.linked: 298 | self.link() 299 | os.rename(self.temp_build_dir, self.final_build_dir) 300 | if os.path.isdir(self.final_build_dir_bak): 301 | shutil.rmtree(self.final_build_dir_bak, True) 302 | 303 | # Print notice 304 | def notice(self, *args): 305 | print("\033[92m** BUILD NOTICE: \033[0m" + ' '.join( 306 | str(a) for a in args)) 307 | 308 | # Print errror 309 | def error(self, *args): 310 | print("\033[91m** BUILD ERROR: \033[0m" + ' '.join( 311 | str(a) for a in args)) 312 | 313 | # Print warning 314 | def warning(self, *args): 315 | print("\033[93m** BUILD WARNING: \033[0m" + ' '.join( 316 | str(a) for a in args)) 317 | 318 | # Run install 319 | def install(self): 320 | if not self._drush([ 321 | "site-install", 322 | self.profile_name, 323 | "install_configure_form.update_status_module='array(FALSE,FALSE)'" 324 | "--account-name=admin", 325 | "--account-pass=admin", 326 | "--site-name=" + self.site_name, 327 | "-y" 328 | ]): 329 | raise BuildError("Install failed.") 330 | 331 | # Update existing final build 332 | def update(self): 333 | if self._drush([ 334 | 'updatedb', 335 | '--y' 336 | ]): 337 | self.notice("Update process completed") 338 | else: 339 | self.warning("Unable to update") 340 | 341 | # Ask user for verification 342 | def verify(self, text): 343 | if text: 344 | response = input(text) 345 | else: 346 | response = input("Type yes to verify that you know what you are doing: ") 347 | if response.lower() != "yes": 348 | raise BuildError("Cancelled by user") 349 | 350 | # Execute a shell command 351 | def shell(self, command): 352 | if isinstance(command, list): 353 | for step in command: 354 | self._shell(step) 355 | else: 356 | self._shell(command) 357 | 358 | # Execute a drush command 359 | def drush_command(self, command): 360 | drush_command = command.split(' ') 361 | if not self._drush(drush_command, False): 362 | raise BuildError("Drush command: '" + command + "' failed.") 363 | 364 | def append(self, command): 365 | files = command.split(">") 366 | if len(files) > 1: 367 | with open(files[1].strip(), "a") as target: 368 | target.write(open(files[0].strip(), "rb").read()) 369 | else: 370 | raise BuildError("Append commands syntax is: source > target") 371 | 372 | # Execute given step 373 | def execute(self, step): 374 | 375 | command = False 376 | if isinstance(step, dict): 377 | step, command = step.popitem() 378 | 379 | if step == 'make': 380 | self.make() 381 | elif step == 'backup': 382 | self.backup(command) 383 | elif step == 'purge': 384 | self.purge() 385 | elif step == 'finalize': 386 | self.finalize() 387 | elif step == 'install': 388 | self.install() 389 | elif step == 'update': 390 | self.update() 391 | elif step == 'cleanup': 392 | self.cleanup() 393 | elif step == 'append': 394 | self.append(command) 395 | elif step == 'verify': 396 | self.verify(command) 397 | elif step == 'shell': 398 | self.shell(command) 399 | elif step == 'link': 400 | self.link() 401 | elif step == 'test': 402 | self.test() 403 | elif step == 'passwd': 404 | self.passwd() 405 | elif step == 'drush': 406 | self.drush_command(command) 407 | else: 408 | print("Unknown step " + step) 409 | 410 | # Collect make args 411 | def _collect_make_args(self): 412 | return [ 413 | "--strict=0", 414 | "--concurrency=20", 415 | "-y", 416 | "make", 417 | self.makefile, 418 | self.temp_build_dir 419 | ] 420 | 421 | # Handle link 422 | def _link(self): 423 | if "link" not in self.settings: 424 | return 425 | for tuple in self.settings['link']: 426 | source, target = tuple.items()[0] 427 | target = self.temp_build_dir + "/" + target 428 | if source.endswith('*'): 429 | path = source[:-1] 430 | paths = [path + name for name in os.listdir(path) if os.path.isdir(path + name)] 431 | for source in paths: 432 | if not os.path.islink(target + "/" + os.path.basename(source)): 433 | self._link_files(source, target + "/" + os.path.basename(source)) 434 | else: 435 | if not os.path.islink(target): 436 | self._link_files(source, target) 437 | 438 | # Handle unlink 439 | def _unlink(self): 440 | if "link" not in self.settings: 441 | return 442 | for tuple in self.settings['link']: 443 | source, target = tuple.items()[0] 444 | target = self.final_build_dir + "/" + target 445 | if source.endswith('*'): 446 | path = source[:-1] 447 | paths = [path + name for name in os.listdir(path) if os.path.isdir(path + name)] 448 | for source in paths: 449 | self._unlink_files(target + "/" + os.path.basename(source)) 450 | else: 451 | self._unlink_files(target) 452 | 453 | # Handle shell command 454 | def _shell(self, command): 455 | exit_code = os.system(command) 456 | if not exit_code == 0: 457 | raise BuildError("Failed executing: '" + command + "' (Exit code: " + str(exit_code) + ")") 458 | 459 | # Handle copy 460 | def _copy(self): 461 | if "copy" not in self.settings: 462 | return 463 | for tuple in self.settings['copy']: 464 | source, target = tuple.popitem() 465 | target = self.temp_build_dir + "/" + target 466 | self._copy_files(source, target) 467 | 468 | # Execute a Composer command 469 | def _composer(self, args, quiet=False): 470 | if quiet: 471 | with open(os.devnull, 'w') as fnull: 472 | return subprocess.call([self.composer] + args, 473 | stdout=fnull, 474 | stderr=fnull) == 0 475 | return subprocess.call([self.composer] + args) == 0 476 | 477 | # Execute a Drush command 478 | def _drush(self, args, quiet=False, output=False): 479 | bootstrap_args = [ 480 | "--root=" + format(self.final_build_dir + self.drupal_subpath), 481 | "-l", 482 | self.multisite_site] 483 | if quiet: 484 | with open(os.devnull, 'w') as fnull: 485 | return subprocess.call([self.drush] + bootstrap_args + args, 486 | stdout=fnull, 487 | stderr=fnull) == 0 488 | if output: 489 | return subprocess.check_output( 490 | [self.drush] + bootstrap_args + args) 491 | return subprocess.call([self.drush] + bootstrap_args + args) == 0 492 | 493 | # Ensure directories exist 494 | def _precheck(self): 495 | # Remove old build it if exists 496 | if not self.in_place: 497 | if os.path.isdir(self.temp_build_dir): 498 | shutil.rmtree(self.temp_build_dir) 499 | if not os.path.isdir(self.old_build_dir): 500 | os.mkdir(self.old_build_dir) 501 | 502 | # TarFile exclude callback for _backup function 503 | def _backup_exlude(self, file): 504 | for exclude in self._build_exclude_files: 505 | if file.endswith(exclude): 506 | return True 507 | return False 508 | 509 | # Backup existing final build 510 | def _backup(self, params): 511 | 512 | if 'skip-database' in params: 513 | self.notice("Database dump skipped as requested") 514 | else: 515 | 516 | dump_file = self.final_build_dir + '/db.sql' 517 | 518 | if self._drush([ 519 | 'sql-dump', 520 | '--result-file=' + dump_file 521 | ], True): 522 | 523 | self.notice("Database dump taken") 524 | 525 | else: 526 | 527 | self.warning("No database dump taken") 528 | 529 | name = datetime.datetime.now() 530 | name = name.isoformat() 531 | name = name.replace(":", "_") 532 | 533 | backup_file = self.old_build_dir + "/" + name + ".tgz" 534 | 535 | if 'ignore' in params: 536 | self._build_exclude_files = params['ignore'] 537 | else: 538 | self._build_exclude_files = {} 539 | 540 | with closing(tarfile.open(backup_file, "w:gz", dereference=True)) as tar: 541 | tar.add(self.final_build_dir, arcname=self.final_build_dir_name, exclude=self._backup_exlude) 542 | 543 | def passwd(self): 544 | if self.drupal_version == 'd7': 545 | query = "SELECT name from users WHERE uid=1" 546 | uid1_name = self._drush(['sqlq', 547 | query 548 | ], False, True) 549 | else: 550 | query = "print user_load(1)->getUsername();" 551 | uid1_name = self._drush(['ev', 552 | query 553 | ], False, True) 554 | char_set = string.printable 555 | password = ''.join(random.sample(char_set * 6, 16)) 556 | 557 | if self._drush(['upwd', 558 | uid1_name, 559 | '--password="' + password + '"' 560 | ], True): 561 | self.notice("UID 1 password changed") 562 | else: 563 | self.warning("UID 1 password not changed!") 564 | 565 | # Wipe existing final build 566 | def _wipe(self): 567 | if self._drush([ 568 | 'sql-drop', 569 | '--y' 570 | ], True): 571 | self.notice("Tables dropped") 572 | else: 573 | self.notice("No tables dropped") 574 | if os.path.isdir(self.final_build_dir): 575 | self._unlink() 576 | self._ensure_writable(self.final_build_dir) 577 | os.rename(self.final_build_dir, self.final_build_dir_bak) 578 | if os.path.isdir(self.final_build_dir_bak): 579 | shutil.rmtree(self.final_build_dir_bak, True) 580 | 581 | # Ensure we have write access to the given dir 582 | def _ensure_writable(self, path): 583 | for root, dirs, files in os.walk(path): 584 | for momo in dirs: 585 | file = os.path.join(root, momo) 586 | mode = os.stat(file).st_mode 587 | os.chmod(file, mode | stat.S_IWRITE) 588 | for momo in files: 589 | file = os.path.join(root, momo) 590 | mode = os.stat(file).st_mode 591 | os.chmod(file, mode | stat.S_IWRITE) 592 | 593 | def _ensure_container(self, filepath): 594 | # Ensure target directory exists 595 | target_container = os.path.dirname(filepath) 596 | if not os.path.exists(target_container): 597 | self.notice("Created directory " + target_container) 598 | os.makedirs(target_container) 599 | 600 | # Symlink file from source to target 601 | def _link_files(self, source, target): 602 | self._ensure_container(target) 603 | if not os.path.exists(target): 604 | if os.path.exists(source): 605 | if os.path.isabs(source): 606 | source = os.path.realpath(source) 607 | else: 608 | source = os.path.relpath(source, os.path.dirname(target)) 609 | os.symlink(source, target) 610 | else: 611 | raise BuildError("Can't link " + source + " to " + target + ". Make sure that the source exists.") 612 | 613 | # Unlink file from target 614 | def _unlink_files(self, target): 615 | self._ensure_container(target) 616 | if os.path.exists(target): 617 | os.unlink(target) 618 | 619 | # Copy file from source to target 620 | def _copy_files(self, source, target): 621 | self._ensure_container(target) 622 | if os.path.exists(source): 623 | if os.path.isdir(source): 624 | copy_tree(source, target) 625 | else: 626 | shutil.copyfile(source, target) 627 | else: 628 | raise BuildError("Can't copy " + source + " to " + target + ". Make sure that the source exists.") 629 | 630 | 631 | # Print help function 632 | def help(): 633 | print('build.sh [options] [command] [site]') 634 | print('[command] is one of the commands defined in the configuration file') 635 | print('[site] defines the site to build, defaults to default') 636 | print('Options:') 637 | print(' -h --help') 638 | print(' print(this help') 639 | print(' -c --config') 640 | print(' Configuration file to use, defaults to conf/site.yml') 641 | print(' -o --commands') 642 | print(' Configuration file to use, ' 643 | 'defaults to conf/commands.yml') 644 | print(' -s --skip-backup') 645 | print(' Do not take backups, ever') 646 | print(' -d --disable-cache') 647 | print(' Do not use caches') 648 | print(' -v --version') 649 | print(' Print version information') 650 | 651 | 652 | # Print version function. 653 | def version(): 654 | print(build_sh_version_string) 655 | 656 | 657 | # Program main: 658 | def main(argv): 659 | 660 | # Default configuration file to use: 661 | config_file = 'conf/site.yml' 662 | commands_file = 'conf/commands.yml' 663 | do_build = True 664 | 665 | # Parse options: 666 | try: 667 | opts, args = getopt.getopt(argv, "hdc:o:vst", ["help", "config=", "commands=", "version", "test", "skip-backup", "disable-cache"]) 668 | except getopt.GetoptError: 669 | help() 670 | return 671 | 672 | for opt, arg in opts: 673 | if opt in ('-h', "--help"): 674 | help() 675 | return 676 | elif opt in ("-c", "--config"): 677 | config_file = arg 678 | elif opt in ("-o", "--commands"): 679 | commands_file = arg 680 | elif opt in ("-s", "--skip-backup"): 681 | global build_sh_skip_backup 682 | build_sh_skip_backup = True 683 | elif opt in ("-d", "--disable-cache"): 684 | global build_sh_disable_cache 685 | build_sh_disable_cache = True 686 | elif opt in ("-v", "--version"): 687 | version() 688 | return 689 | 690 | try: 691 | 692 | # Get the settings file YAML contents. 693 | with open(config_file) as f: 694 | settings = yaml.safe_load(f) 695 | 696 | try: 697 | command = args[0] 698 | except IndexError: 699 | help() 700 | return 701 | 702 | # Default site is "default" 703 | site = 'default' 704 | try: 705 | site = args[1] 706 | except IndexError: 707 | if 'WKV_SITE_ENV' in os.environ: 708 | site = os.environ['WKV_SITE_ENV'] 709 | else: 710 | site = 'default' 711 | 712 | # Copy defaults. 713 | site_settings = settings["default"].copy() 714 | 715 | if site not in settings: 716 | new_site = False 717 | for site_name in settings: 718 | if 'aliases' in settings[site_name]: 719 | if isinstance(settings[site_name]['aliases'], basestring): 720 | site_aliases = [settings[site_name]['aliases']] 721 | else: 722 | site_aliases = settings[site_name]['aliases'] 723 | if site in site_aliases: 724 | new_site = site_name 725 | break 726 | if not new_site: 727 | raise BuildError("The site " + site + " is not defined") 728 | site = new_site 729 | 730 | # If not the default site, update it with defaults. 731 | if site != "default": 732 | site_settings.update(settings[site]) 733 | 734 | # Pass the site environment name to settings. 735 | site_settings['site_env'] = site 736 | 737 | # Create the site maker based on the settings 738 | maker = Maker(site_settings) 739 | 740 | maker.notice("Using configuration " + site) 741 | 742 | commands = {} 743 | 744 | if 'commands' in settings: 745 | commands = settings['commands'] 746 | maker.warning("There are commands defined in site.yml - please move them to commands.yml.") 747 | 748 | # Read in the commands file 749 | if os.path.isfile(commands_file): 750 | if 'commands' in settings: 751 | maker.warning("Commands defined in commands.yml override the commands defined in site.yml") 752 | with open(commands_file) as f: 753 | commands = yaml.safe_load(f) 754 | 755 | commands['test'] = {"test": "test"} 756 | 757 | # Add default overwrite commands with local_commands 758 | if 'local_commands' in settings["default"]: 759 | commands.update(settings["default"]['local_commands']) 760 | # Add and overwrite commands with local_commands 761 | if 'local_commands' in settings[site]: 762 | commands.update(settings[site]['local_commands']) 763 | 764 | if do_build: 765 | # Execute the command(s). 766 | if command in commands: 767 | command_set = commands[command] 768 | for step in command_set: 769 | maker.execute(step) 770 | else: 771 | maker.notice("No such command defined as '" + command + "'") 772 | 773 | except Exception as errtxt: 774 | print("\033[91m** BUILD ERROR: \033[0m%s" % (errtxt)) 775 | exit(1) 776 | 777 | 778 | # Entry point. 779 | if __name__ == "__main__": 780 | main(sys.argv[1:]) 781 | 782 | # vi:ft=python 783 | -------------------------------------------------------------------------------- /drupal/codeception.yml: -------------------------------------------------------------------------------- 1 | actor: Tester 2 | paths: 3 | tests: tests 4 | log: tests/_log 5 | data: tests/_data 6 | support: tests/_support 7 | envs: tests/_envs 8 | settings: 9 | bootstrap: _bootstrap.php 10 | colors: true 11 | memory_limit: 1024M 12 | extensions: 13 | enabled: 14 | - Codeception\Extension\RunFailed 15 | params: 16 | - tests/_envs/parameters.yml # load params from configuration 17 | - env # try to load params from environment vars 18 | modules: 19 | config: 20 | Db: 21 | dsn: '' 22 | user: '' 23 | password: '' 24 | dump: tests/_data/dump.sql 25 | -------------------------------------------------------------------------------- /drupal/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wunderio/wundertools", 3 | "description": "Wundertools Drupal 8 Composer Project Template", 4 | "type": "project", 5 | "license": "GPL-2.0+", 6 | "authors": [ 7 | { 8 | "name": "", 9 | "role": "" 10 | } 11 | ], 12 | "repositories": [ 13 | { 14 | "type": "composer", 15 | "url": "https://packages.drupal.org/8" 16 | } 17 | ], 18 | "require": { 19 | "composer/installers": "^1.2", 20 | "cweagans/composer-patches": "^1.6", 21 | "drupal-composer/drupal-scaffold": "^2.2", 22 | "drupal/console": "^1.0.2", 23 | "drupal/core": "~8.4", 24 | "drush/drush": "^10.1.0", 25 | "webflo/drupal-finder": "^1.0.0", 26 | "webmozart/path-util": "^2.3", 27 | "drupal/config_installer": "~1.0", 28 | "drupal/warden": "*", 29 | "drupal/simplei": "^1.0", 30 | "drupal/memcache": "^2.0@alpha", 31 | "drupal/imagemagick": "^3.1", 32 | "wunderio/drupal-ping": "^1.0", 33 | "machbarmacher/gdpr-dump": "dev-master", 34 | "zaporylie/composer-drupal-optimizations": "^1.0" 35 | }, 36 | "require-dev": { 37 | "guncha25/drupal-codeception": "^9" 38 | }, 39 | "conflict": { 40 | "drupal/drupal": "*" 41 | }, 42 | "config": { 43 | "discard-changes": true 44 | }, 45 | "minimum-stability": "dev", 46 | "prefer-stable": true, 47 | "autoload": { 48 | "classmap": [ 49 | "scripts/composer/ScriptHandler.php" 50 | ] 51 | }, 52 | "scripts": { 53 | "drupal-scaffold": "DrupalComposer\\DrupalScaffold\\Plugin::scaffold", 54 | "pre-install-cmd": [ 55 | "DrupalProject\\composer\\ScriptHandler::checkComposerVersion" 56 | ], 57 | "pre-update-cmd": [ 58 | "DrupalProject\\composer\\ScriptHandler::checkComposerVersion" 59 | ], 60 | "post-install-cmd": [ 61 | "DrupalProject\\composer\\ScriptHandler::createRequiredFiles" 62 | ], 63 | "post-update-cmd": [ 64 | "DrupalProject\\composer\\ScriptHandler::createRequiredFiles" 65 | ] 66 | }, 67 | "extra": { 68 | "installer-paths": { 69 | "web/core": ["type:drupal-core"], 70 | "web/libraries/{$name}": ["type:drupal-library"], 71 | "web/modules/contrib/{$name}": ["type:drupal-module"], 72 | "web/profiles/contrib/{$name}": ["type:drupal-profile"], 73 | "web/themes/contrib/{$name}": ["type:drupal-theme"], 74 | "drush/contrib/{$name}": ["type:drupal-drush"] 75 | }, 76 | "composer-exit-on-patch-failure": true, 77 | "dropin-paths": { 78 | "web/": ["type:web-dropin"] 79 | }, 80 | "patches": { 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /drupal/conf/settings.local.php: -------------------------------------------------------------------------------- 1 | checkBootstrapPhase(DRUSH_BOOTSTRAP_DRUPAL_FULL); 23 | } 24 | 25 | /** 26 | * This command executes successfully if Drupal's database has been bootstrapped. 27 | * 28 | * @see DRUSH_BOOTSTRAP_DRUPAL_DATABASE 29 | * 30 | * @command check-bootstrap:db 31 | * @bootstrap max 32 | */ 33 | public function isBootstrapDatabase() { 34 | $this->checkBootstrapPhase(DRUSH_BOOTSTRAP_DRUPAL_DATABASE); 35 | } 36 | 37 | /** 38 | * This command executes successfully if Drupal's settings.php have been read. 39 | * 40 | * @see DRUSH_BOOTSTRAP_DRUPAL_CONFIGURATION 41 | * 42 | * @command check-bootstrap:config 43 | * @bootstrap max 44 | */ 45 | public function isBootstrapConfiguration() { 46 | $this->checkBootstrapPhase(DRUSH_BOOTSTRAP_DRUPAL_CONFIGURATION); 47 | } 48 | 49 | /** 50 | * This command executes successfully if Drupal's settings.php was found. 51 | * 52 | * @see DRUSH_BOOTSTRAP_DRUPAL_SITE 53 | * 54 | * @command check-bootstrap:site 55 | * @bootstrap max 56 | */ 57 | public function isBootstrapSite() { 58 | $this->checkBootstrapPhase(DRUSH_BOOTSTRAP_DRUPAL_SITE); 59 | } 60 | 61 | /** 62 | * This command executes successfully if a Drupal site was found. 63 | * 64 | * @see DRUSH_BOOTSTRAP_DRUPAL_ROOT 65 | * 66 | * @command check-bootstrap:root 67 | * @bootstrap max 68 | */ 69 | public function isBootstrapRoot() { 70 | $this->checkBootstrapPhase(DRUSH_BOOTSTRAP_DRUPAL_ROOT); 71 | } 72 | 73 | protected function checkBootstrapPhase($phase) { 74 | if (Drush::bootstrapManager()->hasBootstrapped($phase)) { 75 | drush_set_context('DRUSH_EXIT_CODE', DRUSH_SUCCESS); 76 | } 77 | else { 78 | drush_set_context('DRUSH_EXIT_CODE', DRUSH_FRAMEWORK_ERROR); 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /drupal/drush/drushrc.php: -------------------------------------------------------------------------------- 1 | '@parent', 30 | 'site' => 'wundertools', 31 | 'env' => 'vagrant', 32 | 'root' => '/vagrant/drupal/web', 33 | 'remote-host' => 'local.wundertools.com', 34 | 'remote-user' => 'vagrant', 35 | 'ssh-options' => '-i ' . $key, 36 | 'path-aliases' => array( 37 | '%files' => '/vagrant/drupal/files', 38 | '%dump-dir' => '/home/vagrant', 39 | ), 40 | ); 41 | 42 | $aliases['dev'] = array( 43 | 'uri' => 'https://dev.wundertools.com', 44 | 'remote-user' => 'www-admin', 45 | 'remote-host' => 'dev.wundertools.com', 46 | 'root' => '/var/www/dev.wundertools.com/web', 47 | 'path-aliases' => array( 48 | '%dump-dir' => '/home/www-admin', 49 | ), 50 | 'command-specific' => array( 51 | 'sql-sync' => array( 52 | 'no-cache' => TRUE, 53 | ), 54 | ), 55 | ); 56 | 57 | $aliases['stage'] = array( 58 | 'uri' => 'https://stage.wundertools.com', 59 | 'remote-user' => 'www-admin', 60 | 'remote-host' => 'stage.wundertools.com', 61 | 'root' => '/var/www/stage.wundertools.com/web', 62 | 'path-aliases' => array( 63 | '%dump-dir' => '/home/www-admin', 64 | ), 65 | 'command-specific' => array( 66 | 'sql-sync' => array( 67 | 'no-cache' => TRUE, 68 | ), 69 | ), 70 | ); 71 | 72 | $aliases['prod'] = array( 73 | 'uri' => 'https://wundertools.com', 74 | 'remote-user' => 'www-admin', 75 | 'remote-host' => 'wundertools.com', 76 | 'root' => '/var/www/wundertools.com/web', 77 | 'path-aliases' => array( 78 | '%dump-dir' => '/home/www-admin', 79 | ), 80 | 'command-specific' => array( 81 | 'sql-sync' => array( 82 | 'no-cache' => TRUE, 83 | ), 84 | ), 85 | ); 86 | -------------------------------------------------------------------------------- /drupal/files/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /drupal/gdpr.json: -------------------------------------------------------------------------------- 1 | 404: Not Found 2 | -------------------------------------------------------------------------------- /drupal/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "lockfileVersion": 1 3 | } 4 | -------------------------------------------------------------------------------- /drupal/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": {}, 3 | "devDependencies": {}, 4 | "scripts": { 5 | "build": "echo 'No build command specified in package.json'" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /drupal/phpcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Drupal coding standard 6 | 7 | web/themes/custom 8 | web/modules/custom 9 | web/sites/default 10 | 11 | 12 | web/sites/default/default.settings.php 13 | 14 | 15 | web/sites/default/files 16 | 17 | 18 | *.min.* 19 | 20 | 21 | node_modules/ 22 | 23 | 24 | *.css 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /drupal/scripts/composer/ScriptHandler.php: -------------------------------------------------------------------------------- 1 | locateRoot(getcwd()); 22 | $drupalRoot = $drupalFinder->getDrupalRoot(); 23 | 24 | $dirs = [ 25 | 'modules', 26 | 'profiles', 27 | 'themes', 28 | ]; 29 | 30 | // Required for unit testing 31 | foreach ($dirs as $dir) { 32 | if (!$fs->exists($drupalRoot . '/'. $dir)) { 33 | $fs->mkdir($drupalRoot . '/'. $dir); 34 | $fs->touch($drupalRoot . '/'. $dir . '/.gitkeep'); 35 | } 36 | } 37 | 38 | // Prepare the settings file for installation 39 | if (!$fs->exists($drupalRoot . '/sites/default/settings.php') and $fs->exists($drupalRoot . '/sites/default/default.settings.php')) { 40 | $fs->copy($drupalRoot . '/sites/default/default.settings.php', $drupalRoot . '/sites/default/settings.php'); 41 | require_once $drupalRoot . '/core/includes/bootstrap.inc'; 42 | require_once $drupalRoot . '/core/includes/install.inc'; 43 | $settings['config_directories'] = [ 44 | CONFIG_SYNC_DIRECTORY => (object) [ 45 | 'value' => Path::makeRelative($drupalFinder->getComposerRoot() . '/config/sync', $drupalRoot), 46 | 'required' => TRUE, 47 | ], 48 | ]; 49 | drupal_rewrite_settings($settings, $drupalRoot . '/sites/default/settings.php'); 50 | $fs->chmod($drupalRoot . '/sites/default/settings.php', 0666); 51 | $event->getIO()->write("Create a sites/default/settings.php file with chmod 0666"); 52 | } 53 | 54 | // Create the files directory with chmod 0777 55 | if (!$fs->exists($drupalRoot . '/sites/default/files')) { 56 | $oldmask = umask(0); 57 | $fs->mkdir($drupalRoot . '/sites/default/files', 0777); 58 | umask($oldmask); 59 | $event->getIO()->write("Create a sites/default/files directory with chmod 0777"); 60 | } 61 | } 62 | 63 | /** 64 | * Checks if the installed version of Composer is compatible. 65 | * 66 | * Composer 1.0.0 and higher consider a `composer install` without having a 67 | * lock file present as equal to `composer update`. We do not ship with a lock 68 | * file to avoid merge conflicts downstream, meaning that if a project is 69 | * installed with an older version of Composer the scaffolding of Drupal will 70 | * not be triggered. We check this here instead of in drupal-scaffold to be 71 | * able to give immediate feedback to the end user, rather than failing the 72 | * installation after going through the lengthy process of compiling and 73 | * downloading the Composer dependencies. 74 | * 75 | * @see https://github.com/composer/composer/pull/5035 76 | */ 77 | public static function checkComposerVersion(Event $event) { 78 | $composer = $event->getComposer(); 79 | $io = $event->getIO(); 80 | 81 | $version = $composer::VERSION; 82 | 83 | // The dev-channel of composer uses the git revision as version number, 84 | // try to the branch alias instead. 85 | if (preg_match('/^[0-9a-f]{40}$/i', $version)) { 86 | $version = $composer::BRANCH_ALIAS_VERSION; 87 | } 88 | 89 | // If Composer is installed through git we have no easy way to determine if 90 | // it is new enough, just display a warning. 91 | if ($version === '@package_version@' || $version === '@package_branch_alias_version@') { 92 | $io->writeError('You are running a development version of Composer. If you experience problems, please update Composer to the latest stable version.'); 93 | } 94 | elseif (Comparator::lessThan($version, '1.0.0')) { 95 | $io->writeError('Drupal-project requires Composer version 1.0.0 or higher. Please update your Composer before continuing.'); 96 | exit(1); 97 | } 98 | } 99 | 100 | } 101 | -------------------------------------------------------------------------------- /drupal/silta/nginx.Dockerfile: -------------------------------------------------------------------------------- 1 | # Dockerfile for building nginx. 2 | FROM wunderio/silta-nginx:1.17-v1 3 | 4 | COPY . /app/web 5 | 6 | -------------------------------------------------------------------------------- /drupal/silta/php.Dockerfile: -------------------------------------------------------------------------------- 1 | # Dockerfile for the Drupal container. 2 | FROM wunderio/silta-php-fpm:7.3-fpm-v1 3 | 4 | COPY --chown=www-data:www-data . /app 5 | 6 | -------------------------------------------------------------------------------- /drupal/silta/shell.Dockerfile: -------------------------------------------------------------------------------- 1 | # Dockerfile for the Drupal container. 2 | FROM wunderio/silta-php-shell:php7.3-v1 3 | 4 | COPY --chown=www-data:www-data . /app 5 | -------------------------------------------------------------------------------- /drupal/silta/silta-prod.yml: -------------------------------------------------------------------------------- 1 | 2 | # Provide a high-availability, autoscaling deployment. 3 | replicas: 2 4 | autoscaling: 5 | enabled: true 6 | minReplicas: 2 7 | maxReplicas: 5 8 | 9 | # Enable daily backups. 10 | backup: 11 | enabled: true 12 | 13 | # Uncomment these lines to disable basic auth protection. 14 | #nginx: 15 | # basicauth: 16 | # enabled: false 17 | 18 | php: 19 | # Reserve more resources for our PHP containerss. 20 | resources: 21 | requests: 22 | cpu: 200m 23 | memory: 256m 24 | 25 | # Don't show errors in production. 26 | errorLevel: "hide" 27 | 28 | # Connect to an externally hosted database. 29 | # env: 30 | # DB_HOST: 'hosted.database.server.com' 31 | # DB_NAME: 'drupal-1A4G3C' 32 | # DB_USER: 'drupal' 33 | # DB_PASS: 'never store passwords' 34 | # Disable the built-in database when using an external database. 35 | #mariadb: 36 | # enabled: false -------------------------------------------------------------------------------- /drupal/silta/silta.yml: -------------------------------------------------------------------------------- 1 | 2 | # Values in this file override the default values of our helm chart. 3 | # 4 | # See https://github.com/wunderio/charts/blob/master/drupal/values.yaml 5 | # for all possible options. 6 | 7 | # Configuration for everything that runs in php containers. 8 | php: 9 | # Define the location of the Drupal config files relative to the composer root. 10 | # This variable is exposed as $DRUPAL_CONFIG_PATH in the container. 11 | drupalConfigPath: "sync" 12 | -------------------------------------------------------------------------------- /drupal/sync/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wunderio/WunderTools/5e667648c10ccb43508a1cdf8370d8dd38b2c487/drupal/sync/.gitkeep -------------------------------------------------------------------------------- /drupal/sync/.htaccess: -------------------------------------------------------------------------------- 1 | # Deny all requests from Apache 2.4+. 2 | 3 | Require all denied 4 | 5 | 6 | # Deny all requests from Apache 2.0-2.2. 7 | 8 | Deny from all 9 | 10 | # Turn off all options we don't need. 11 | Options -Indexes -ExecCGI -Includes -MultiViews 12 | 13 | # Set the catch-all handler to prevent scripts from being executed. 14 | SetHandler Drupal_Security_Do_Not_Remove_See_SA_2006_006 15 | 16 | # Override the handler again if we're run later in the evaluation list. 17 | SetHandler Drupal_Security_Do_Not_Remove_See_SA_2013_003 18 | 19 | 20 | # If we know how to do it safely, disable the PHP engine entirely. 21 | 22 | php_flag engine off 23 | -------------------------------------------------------------------------------- /drupal/tests/_bootstrap.php: -------------------------------------------------------------------------------- 1 | amOnPage('/'); 10 | $i->loginWithRole('authenticated'); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /drupal/tests/acceptance/_bootstrap.php: -------------------------------------------------------------------------------- 1 | amOnPage('/'); 10 | $I->seeResponseCodeIs(200); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /drupal/tests/functional/_bootstrap.php: -------------------------------------------------------------------------------- 1 | 'mysql', 9 | 'database' => $lando_info['database']['creds']['database'], 10 | 'username' => $lando_info['database']['creds']['user'], 11 | 'password' => $lando_info['database']['creds']['password'], 12 | 'host' => $lando_info['database']['internal_connection']['host'], 13 | 'port' => $lando_info['database']['internal_connection']['port'], 14 | ]; 15 | 16 | // Use the hash_salt setting from Lando. 17 | $settings['hash_salt'] = getenv('HASH_SALT'); 18 | 19 | // Skip file system permissions hardening when using local development with Lando. 20 | $settings['skip_permissions_hardening'] = TRUE; 21 | 22 | // Skip trusted host pattern when using Lando. 23 | $settings['trusted_host_patterns'] = ['.*']; 24 | -------------------------------------------------------------------------------- /drupal/web/sites/default/settings.php: -------------------------------------------------------------------------------- 1 | getenv('DB_NAME_DRUPAL'), 14 | 'username' => getenv('DB_USER_DRUPAL'), 15 | 'password' => getenv('DB_PASS_DRUPAL'), 16 | 'prefix' => '', 17 | 'host' => getenv('DB_HOST_DRUPAL'), 18 | 'port' => '3306', 19 | 'namespace' => 'Drupal\\Core\\Database\\Driver\\mysql', 20 | 'driver' => 'mysql', 21 | ]; 22 | 23 | // CHANGE THIS. 24 | $settings['hash_salt'] = 'some-hash-salt-please-change-this'; 25 | 26 | if ((isset($_SERVER["HTTPS"]) && strtolower($_SERVER["HTTPS"]) == "on") 27 | || (isset($_SERVER["HTTP_X_FORWARDED_PROTO"]) && $_SERVER["HTTP_X_FORWARDED_PROTO"] == "https") 28 | || (isset($_SERVER["HTTP_HTTPS"]) && $_SERVER["HTTP_HTTPS"] == "on") 29 | ) { 30 | $_SERVER["HTTPS"] = "on"; 31 | 32 | // Tell Drupal we're using HTTPS (url() for one depends on this). 33 | $settings['https'] = TRUE; 34 | } 35 | 36 | // @codingStandardsIgnoreStart 37 | if (isset($_SERVER['REMOTE_ADDR'])) { 38 | $settings['reverse_proxy'] = TRUE; 39 | $settings['reverse_proxy_addresses'] = [$_SERVER['REMOTE_ADDR']]; 40 | } 41 | // @codingStandardsIgnoreEnd 42 | 43 | if (!empty($_SERVER['SERVER_ADDR'])) { 44 | // This should return last section of IP, such as "198". (dont want/need to expose more info). 45 | //drupal_add_http_header('X-Webserver', end(explode('.', $_SERVER['SERVER_ADDR']))); 46 | $pcs = explode('.', $_SERVER['SERVER_ADDR']); 47 | header('X-Webserver: ' . end($pcs)); 48 | } 49 | 50 | $settings['memcache']['servers'] = ['127.0.0.1:11211' => 'default']; 51 | 52 | $env = getenv('WKV_SITE_ENV'); 53 | switch ($env) { 54 | case 'prod': 55 | $settings['simple_environment_indicator'] = '#d4000f Production'; 56 | $settings['memcache']['servers'] = [ 57 | '[front1_internal_ip]:11211' => 'default', 58 | '[front2_internal_ip]:11211' => 'default', 59 | ]; 60 | break; 61 | 62 | case 'dev': 63 | $settings['simple_environment_indicator'] = '#004984 Development'; 64 | break; 65 | 66 | case 'stage': 67 | $settings['simple_environment_indicator'] = '#e56716 Stage'; 68 | break; 69 | 70 | case 'local': 71 | case 'lando': 72 | $settings['simple_environment_indicator'] = '#88b700 Local'; 73 | break; 74 | } 75 | /** 76 | * Location of the site configuration files. 77 | */ 78 | $config_directories = [ 79 | CONFIG_SYNC_DIRECTORY => '../sync', 80 | ]; 81 | 82 | /** 83 | * Memcache configuration. 84 | */ 85 | if (!drupal_installation_attempted() && extension_loaded('memcached') && class_exists('Memcached')) { 86 | // Define memcache settings only if memcache module enabled. 87 | if (class_exists(MemcacheBackendFactory::class)) { 88 | $settings['memcache']['extension'] = 'Memcached'; 89 | $settings['memcache']['bins'] = ['default' => 'default']; 90 | $settings['memcache']['key_prefix'] = ''; 91 | #$settings['cache']['default'] = 'cache.backend.memcache'; 92 | $settings['cache']['bins']['render'] = 'cache.backend.memcache'; 93 | $settings['cache']['bins']['dynamic_page_cache'] = 'cache.backend.memcache'; 94 | $settings['cache']['bins']['bootstrap'] = 'cache.backend.memcache'; 95 | $settings['cache']['bins']['config'] = 'cache.backend.memcache'; 96 | $settings['cache']['bins']['discovery'] = 'cache.backend.memcache'; 97 | 98 | // Enable stampede protection. 99 | $settings['memcache']['stampede_protection'] = TRUE; 100 | 101 | // High performance - no hook_boot(), no hook_exit(), ignores Drupal IP 102 | // blacklists. 103 | $conf['page_cache_invoke_hooks'] = FALSE; 104 | $conf['page_cache_without_database'] = TRUE; 105 | 106 | // Memcached PECL Extension Support. 107 | // Adds Memcache binary protocol and no-delay features (experimental). 108 | $settings['memcache']['options'] = [ 109 | \Memcached::OPT_COMPRESSION => FALSE, 110 | \Memcached::OPT_DISTRIBUTION => \Memcached::DISTRIBUTION_CONSISTENT, 111 | \Memcached::OPT_BINARY_PROTOCOL => TRUE, 112 | \Memcached::OPT_TCP_NODELAY => TRUE, 113 | ]; 114 | } 115 | } 116 | 117 | /** 118 | * Access control for update.php script. 119 | */ 120 | $settings['update_free_access'] = FALSE; 121 | 122 | /** 123 | * Load services definition file. 124 | */ 125 | $settings['container_yamls'][] = __DIR__ . '/services.yml'; 126 | 127 | /** 128 | * The default list of directories that will be ignored by Drupal's file API. 129 | * 130 | * By default ignore node_modules and bower_components folders to avoid issues 131 | * with common frontend tools and recursive scanning of directories looking for 132 | * extensions. 133 | * 134 | * @see file_scan_directory() 135 | * @see \Drupal\Core\Extension\ExtensionDiscovery::scanDirectory() 136 | */ 137 | $settings['file_scan_ignore_directories'] = [ 138 | 'node_modules', 139 | 'bower_components', 140 | ]; 141 | 142 | /** 143 | * Environment specific override configuration, if available. 144 | */ 145 | if (file_exists(__DIR__ . '/settings.local.php')) { 146 | include __DIR__ . '/settings.local.php'; 147 | } 148 | 149 | /** 150 | * Lando configuration overrides. 151 | */ 152 | if (getenv('LANDO_INFO') && file_exists($app_root . '/' . $site_path . '/settings.lando.php')) { 153 | include $app_root . '/' . $site_path . '/settings.lando.php'; 154 | } 155 | 156 | /** 157 | * Silta cluster configuration overrides. 158 | */ 159 | if (getenv('SILTA_CLUSTER') && file_exists($app_root . '/' . $site_path . '/settings.silta.php')) { 160 | include $app_root . '/' . $site_path . '/settings.silta.php'; 161 | } 162 | -------------------------------------------------------------------------------- /drupal/web/themes/custom/README.txt: -------------------------------------------------------------------------------- 1 | See conf/site.yml for linking/copying configuration. 2 | -------------------------------------------------------------------------------- /drush.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | params='' 4 | for i in "$@";do 5 | params="$params \"${i//\"/\\\"}\"" 6 | done; 7 | 8 | 9 | if [ -z "$WKV_SITE_ENV" ]; then 10 | 11 | if ! vagrant status|grep default|grep -q running; then 12 | echo "Vagrant is not up!" 13 | exit 1 14 | fi 15 | 16 | vagrant ssh -c "cd /vagrant/drupal/web/;drush $params" 17 | 18 | else 19 | 20 | cd drupal/web 21 | drush $params 22 | 23 | fi 24 | 25 | -------------------------------------------------------------------------------- /local_ansible_roles/README.md: -------------------------------------------------------------------------------- 1 | Project specific local ansible roles can be defined here and they will take precedence over shared ansible roles. 2 | -------------------------------------------------------------------------------- /provision.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | export LC_ALL=en_US.UTF-8 3 | export LANG=en_US.UTF-8 4 | 5 | VAULT_FILE=$WT_ANSIBLE_VAULT_FILE 6 | MYSQL_ROOT_PASS= 7 | 8 | function parse_yaml { 9 | local prefix=$2 10 | local s='[[:space:]]*' w='[a-zA-Z0-9_]*' fs=$(echo @|tr @ '\034') 11 | sed -ne "s|^\($s\):|\1|" \ 12 | -e "s|^\($s\)\($w\)$s:$s[\"']\(.*\)[\"']$s\$|\1$fs\2$fs\3|p" \ 13 | -e "s|^\($s\)\($w\)$s:$s\(.*\)$s\$|\1$fs\2$fs\3|p" $1 | 14 | awk -F$fs '{ 15 | indent = length($1)/2; 16 | vname[indent] = $2; 17 | for (i in vname) {if (i > indent) {delete vname[i]}} 18 | if (length($3) > 0) { 19 | vn=""; for (i=0; i/dev/null 2>&1; then 41 | MD5COMMAND="md5sum" 42 | else 43 | MD5COMMAND="md5 -r" 44 | fi 45 | 46 | SELF=$(basename $0) 47 | UPDATEURL="https://raw.githubusercontent.com/wunderio/WunderTools/$GITBRANCH/provision.sh" 48 | MD5SELF=$($MD5COMMAND $0 | awk '{print $1}') 49 | MD5LATEST=$(curl -s $UPDATEURL | $MD5COMMAND | awk '{print $1}') 50 | if [[ "$MD5SELF" != "$MD5LATEST" ]]; then 51 | read -p "There is update for this script available. Update now ([y]es / [n]o)?" -n 1 -r; 52 | if [[ $REPLY =~ ^[Yy]$ ]]; then 53 | cd $ROOT 54 | curl -s -o $SELF $UPDATEURL 55 | echo "Update complete, please rerun any command you were running previously." 56 | echo "See CHANGELOG for more info." 57 | echo "Also remember to add updated script to git." 58 | exit 59 | fi 60 | fi 61 | # Clone and update virtual environment configurations 62 | if [ ! -d "$ROOT/ansible" ]; then 63 | git clone -b $ansible_branch $ansible_remote $ROOT/ansible 64 | if [ -n "$ansible_revision" ]; then 65 | cd $ROOT/ansible 66 | git reset --hard $ansible_revision 67 | cd $ROOT 68 | fi 69 | else 70 | if [ -z "$ansible_revision" ]; then 71 | cd $ROOT/ansible 72 | git pull 73 | git checkout $ansible_branch 74 | cd $ROOT 75 | fi 76 | fi 77 | 78 | # Use secrets if it's defined in conf/project.yml 79 | # Do this for everything else than local vagrant provisioning 80 | if [ "$ENVIRONMENT" != "vagrant" ] && [ "$wundersecrets_remote" != "" ]; then 81 | # Set defaults for WunderSecrets 82 | export wundersecrets_branch=${wundersecrets_branch-master} 83 | 84 | # Clone and update virtual environment secrets 85 | if [ ! -d "$wundersecrets_path" ]; then 86 | git clone -b $wundersecrets_branch $wundersecrets_remote $wundersecrets_path 87 | if [ -n "$wundersecrets_revision" ]; then 88 | git -C "$wundersecrets_path" reset --hard $wundersecrets_revision 89 | fi 90 | else 91 | if [ -z "$wundersecrets_revision" ]; then 92 | git -C "$wundersecrets_path" pull 93 | git -C "$wundersecrets_path" checkout $wundersecrets_branch 94 | fi 95 | fi 96 | fi 97 | } 98 | pushd `dirname $0` > /dev/null 99 | ROOT=`pwd -P` 100 | popd > /dev/null 101 | # Parse project config 102 | PROJECTCONF=$ROOT/conf/project.yml 103 | echo $PROJECTCONF 104 | eval $(parse_yaml $PROJECTCONF) 105 | 106 | if [ -z "$wundertools_branch" ]; then 107 | GITBRANCH="master" 108 | else 109 | GITBRANCH=$wundertools_branch 110 | fi 111 | 112 | export wundersecrets_path=$ROOT/secrets 113 | 114 | self_update 115 | 116 | OPTIND=1 117 | ANSIBLE_TAGS="" 118 | EXTRA_OPTS="" 119 | 120 | while getopts ":hfrp:m:t:s:" opt; do 121 | case "$opt" in 122 | h) 123 | show_help 124 | exit 0 125 | ;; 126 | p) VAULT_FILE=$OPTARG 127 | ;; 128 | f) FIRST_RUN=1 129 | ;; 130 | r) SKIP_REQUIREMENTS=1 131 | ;; 132 | m) MYSQL_ROOT_PASS=$OPTARG 133 | ;; 134 | t) ANSIBLE_TAGS=$OPTARG 135 | ;; 136 | s) ANSIBLE_SKIP_TAGS=$OPTARG 137 | ;; 138 | *) EXTRA_OPTS="$EXTRA_OPTS -$OPTARG" 139 | esac 140 | done 141 | 142 | shift "$((OPTIND-1))" 143 | ENVIRONMENT=$1 144 | 145 | if [ -z $ENVIRONMENT ]; then 146 | show_help 147 | exit 1 148 | fi 149 | 150 | if [ -z $VAULT_FILE ]; then 151 | echo -e "\e[31mVault password file missing.\e[0m" 152 | echo -e "You can provide the path to the file with -p option." 153 | echo -e "Alternatively you can set WT_ANSIBLE_VAULT_FILE environment variable." 154 | echo -e "If you don't have any ansible-vault encrypted config file this is just fine," 155 | echo -e "Otherwise your provisioning will fail horribly.\e[31mYou have been warned!\e[0m" 156 | fi 157 | 158 | pushd `dirname $0` > /dev/null 159 | ROOT=`pwd -P` 160 | popd > /dev/null 161 | 162 | PLAYBOOKPATH=$ROOT/conf/$ENVIRONMENT.yml 163 | if [ "$ENVIRONMENT" == "vagrant" ]; then 164 | INVENTORY=$ROOT/ansible/inventory.py 165 | VAGRANT_CREDENTIALS="--private-key=.vagrant/machines/default/virtualbox/private_key -u vagrant -e 'host_key_checking=False'" 166 | else 167 | INVENTORY=$ROOT/conf/server.inventory 168 | fi 169 | EXTRA_VARS=$ROOT/conf/variables.yml 170 | 171 | if [ ! $SKIP_REQUIREMENTS ] ; then 172 | # Check if pip is installed 173 | which -a pip >> /dev/null 174 | if [[ $? != 0 ]] ; then 175 | echo "ERROR: pip is not installed! Install it first." 176 | echo "OSX: $ easy_install pip" 177 | echo "Ubuntu: $ sudo apt-get install python-setuptools python-dev build-essential && sudo easy_install pip" 178 | echo "Centos: $ yum -y install python-pip" 179 | exit 1 180 | else 181 | # Install virtualenv 182 | which -a pipenv >> /dev/null 183 | if [[ $? != 0 ]] ; then 184 | sudo pip install pipenv 185 | fi 186 | cd $ROOT/ansible 187 | VENV=`pipenv --venv` 188 | 189 | # Ensure ansible & ansible library versions with pip 190 | if [ -f $ROOT/ansible/Pipfile.lock ]; then 191 | pipenv install --python 3.6 192 | else 193 | pipenv install ansible --python 3.6 194 | fi 195 | fi 196 | fi 197 | 198 | # Install ansible-galaxy roles 199 | if [ -f $ROOT/conf/requirements.yml ]; then 200 | pipenv run ansible-galaxy install -r $ROOT/conf/requirements.yml 201 | fi 202 | 203 | # Setup&Use WunderSecrets if the additional config file exists 204 | if [ -f $wundersecrets_path/ansible.yml ]; then 205 | WUNDER_SECRETS="--extra-vars=@$wundersecrets_path/ansible.yml" 206 | else 207 | WUNDER_SECRETS="" 208 | fi 209 | 210 | # Use vault encrypted file from WunderSecrets when available 211 | if [ "$VAULT_FILE" != "" ] && [ -f $wundersecrets_path/vault.yml ]; then 212 | WUNDER_SECRETS="$WUNDER_SECRETS --extra-vars=@$wundersecrets_path/vault.yml" 213 | fi 214 | 215 | if [ $FIRST_RUN ]; then 216 | if [ -z $MYSQL_ROOT_PASS ]; then 217 | pipenv run ansible-playbook $EXTRA_OPTS $PLAYBOOKPATH $WUNDER_SECRETS -c ssh -i $INVENTORY -e "@$EXTRA_VARS" -e "first_run=True" --vault-password-file=$VAULT_FILE $ANSIBLE_TAGS 218 | else 219 | pipenv run ansible-playbook $EXTRA_OPTS $PLAYBOOKPATH $WUNDER_SECRETS -c ssh -i $INVENTORY -e "@$EXTRA_VARS" -e "change_db_root_password=True mariadb_root_password=$MYSQL_ROOT_PASS first_run=True" --vault-password-file=$VAULT_FILE $ANSIBLE_TAGS 220 | fi 221 | else 222 | if [ $ANSIBLE_TAGS ]; then 223 | pipenv run ansible-playbook $EXTRA_OPTS $VAGRANT_CREDENTIALS $PLAYBOOKPATH $WUNDER_SECRETS -c ssh -i $INVENTORY -e "@$EXTRA_VARS" --vault-password-file=$VAULT_FILE --tags "common,$ANSIBLE_TAGS" 224 | elif [ $ANSIBLE_SKIP_TAGS ]; then 225 | pipenv run ansible-playbook $EXTRA_OPTS $VAGRANT_CREDENTIALS $PLAYBOOKPATH $WUNDER_SECRETS -c ssh -i $INVENTORY -e "@$EXTRA_VARS" --vault-password-file=$VAULT_FILE --skip-tags "$ANSIBLE_SKIP_TAGS" 226 | else 227 | pipenv run ansible-playbook $EXTRA_OPTS $VAGRANT_CREDENTIALS $PLAYBOOKPATH $WUNDER_SECRETS -c ssh -i $INVENTORY -e "@$EXTRA_VARS" --vault-password-file=$VAULT_FILE 228 | fi 229 | fi 230 | -------------------------------------------------------------------------------- /sync.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # This file will sync local development environment with the dev server 3 | # SQL from the server + rsync. 4 | 5 | drush sql-sync @site.stage @site.local --structure-tables-list=cache,cache_*,history,search_*,sessions,watchdog --sanitize 6 | echo 'SQL sync ready.'; 7 | 8 | drush rsync @site.stage:%files/ drupal/files/ 9 | echo 'RSync ready.'; 10 | 11 | # Set UID1 password to 'root' 12 | #drush @site.local sqlq "UPDATE users SET name = 'root' WHERE name = 'admin'" 13 | drush @site.local sqlq "UPDATE users_field_data SET mail = 'user@example.com' WHERE name != 'admin'" 14 | drush @site.local sqlq "UPDATE users_field_data SET init = '' WHERE name != 'admin'" 15 | drush @site.local sqlq "UPDATE users_field_data SET pass = '' WHERE name != 'admin'" 16 | drush @site.local upwd admin --password=admin 17 | echo 'Truncated emails and passwords from the database.'; 18 | 19 | # Download Devel 20 | # drush @site.local dl devel -y; 21 | 22 | # Download maillog to prevent emails being sent 23 | #drush @site.local dl maillog -y; 24 | 25 | # Set maillog default development environment settings 26 | #drush @site.local vset maillog_devel 1; 27 | #drush @site.local vset maillog_log 1; 28 | #drush @site.local vset maillog_send 0; 29 | 30 | # Enable Devel and UI modules 31 | # drush @site.local en field_ui devel views_ui context_ui feeds_ui rules_admin dblog --yes; 32 | # echo 'Enabled Devel and Views+Context+Feeds+Rules UI modules.'; 33 | 34 | # Disable google analytics 35 | # drush @site.local dis googleanalytics --yes; 36 | # echo 'Disabled Google Analytics.'; 37 | 38 | # Set site email address to admin@example.com 39 | #drush @site.local vset site_mail "admin@example.com" 40 | 41 | # Set imagemagick convert path 42 | # drush @site.local vset imagemagick_convert "/opt/local/bin/convert" 43 | 44 | #Enable stage file proxy 45 | drush @site.local pm-download stage_file_proxy; 46 | drush @site.local pm-enable --yes stage_file_proxy; 47 | drush @site.local cset --yes stage_file_proxy.settings origin "https://wundertools.site" 48 | echo "Enabled stage file proxy so you won't need the files locally, jeee!" 49 | 50 | 51 | # Clear caches 52 | drush @site.local cr all; 53 | 54 | # FINISH HIM 55 | #say --voice=Zarvox "Sync is now fully completed." 56 | -------------------------------------------------------------------------------- /syncdb.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Local dependencies: 4 | # - drush 5 | # - rsync 6 | # - drush aliases (automatically setup with `vagrant up`) 7 | 8 | # Function to parse the project configuration yaml. 9 | function parse_yaml { 10 | local prefix=$2 11 | local s='[[:space:]]*' w='[a-zA-Z0-9_]*' fs=$(echo @|tr @ '\034') 12 | sed -ne "s|^\($s\):|\1|" \ 13 | -e "s|^\($s\)\($w\)$s:$s[\"']\(.*\)[\"']$s\$|\1$fs\2$fs\3|p" \ 14 | -e "s|^\($s\)\($w\)$s:$s\(.*\)$s\$|\1$fs\2$fs\3|p" $1 | 15 | awk -F$fs '{ 16 | indent = length($1)/2; 17 | vname[indent] = $2; 18 | for (i in vname) {if (i > indent) {delete vname[i]}} 19 | if (length($3) > 0) { 20 | vn=""; for (i=0; i /dev/null 27 | ROOT=`pwd -P` 28 | popd > /dev/null 29 | 30 | PROJECTCONF=$ROOT/conf/project.yml 31 | eval $(parse_yaml $PROJECTCONF) 32 | 33 | # Get config from flags. We are doing this here so that flags override project 34 | # config. 35 | # 36 | # Use -gt 1 to consume two arguments per pass in the loop (e.g. each 37 | # argument has a corresponding value to go with it). 38 | # Use -gt 0 to consume one or more arguments per pass in the loop (e.g. 39 | # some arguments don't have a corresponding value to go with it such 40 | # as in the --help example). 41 | while [[ $# -gt 0 ]] 42 | do 43 | key="$1" 44 | 45 | case $key in 46 | -n|--name) 47 | project_name="$2" 48 | shift # Past argument 49 | ;; 50 | -s|--source) 51 | SOURCE="$2" 52 | shift # Past argument 53 | ;; 54 | -t|--target) 55 | TARGET="$2" 56 | shift # Past argument 57 | ;; 58 | -f|--file-sync-url) 59 | project_file_sync_url="$2" 60 | shift # Past argument 61 | ;; 62 | -h|--help) 63 | echo "Usage: ./syncdb.sh [options] 64 | Example: ./syncdb.sh -n example1 -s prod -t local -f https://www.example1.com 65 | Options: 66 | -n PROJECT_NAME, --name PROJECT_NAME Specify the project name used as Drush 67 | aliases prefix. Defaults to 68 | 'project: name' in 'conf/project.yml'. 69 | -s SOURCE, --source SOURCE Specify the source environment for 70 | the sync. Defaults to 'prod'. Needs to 71 | match a Drush alias name. 72 | -t TARGET, --target TARGET Specify the target environment for 73 | the sync. Defaults to 'local'. Needs to 74 | match a Drush alias name. 75 | -f URL, --file-sync-url URL Specify the URL of the environment used 76 | for file sync for example with 77 | Stage File Proxy module. Defaults to 78 | the SOURCE URI, but can also be set 79 | globally by 'project: file_sync_url' in 80 | 'conf/project.yml'. 81 | -h, --help Print this help. 82 | " 83 | exit 84 | ;; 85 | *) 86 | # Unknown option 87 | ;; 88 | esac 89 | shift # Past argument or value 90 | done 91 | 92 | # Make sure we have the $project_name 93 | if [ -z "$project_name" ]; then 94 | echo "Project name missing. Set in 'conf/project.yml' or use -p flag. Use -h for more help." 95 | exit 96 | fi 97 | 98 | # Default $SOURCE to "prod". 99 | if [ -z "$SOURCE" ]; then 100 | echo "Source defaults to 'prod'. Set with -s flag. Use -h to see all options." 101 | SOURCE="@$project_name.prod" 102 | else 103 | SOURCE="@$project_name.$SOURCE" 104 | fi 105 | 106 | # Default $TARGET to "local". Prevent "prod" and "production". 107 | if [ -z "$TARGET" ]; then 108 | echo "Target defaults to 'local'. Set with -t flag. Use -h to see all options." 109 | TARGET="@$project_name.local" 110 | else 111 | if [ $TARGET == 'prod' ] || [ $TARGET == 'production' ]; then 112 | echo "You tried to sync to a production environment!" 113 | echo "This is probably never the intention, so we always fail such attempts." 114 | exit 115 | else 116 | TARGET="@$project_name.$TARGET" 117 | fi 118 | fi 119 | 120 | # Get the project_file_sync_url to use from SOURCE URI. This requires the URI to 121 | # be in full format including the protocol to use like HTTPS. Only get it if not 122 | # specified in a flag or in project config. 123 | if [ -z "$project_file_sync_url" ]; then 124 | project_file_sync_url=$(drush $SOURCE status | awk 'NR==2{print $4}') 125 | fi 126 | 127 | # Set sync directory with timestamp to allow parallel syncing. 128 | SYNCDIR="/tmp/syncdb/$project_name$(date +%s)" 129 | echo "Using directory $SYNCDIR for syncing." 130 | 131 | drush $SOURCE dumpdb --structure-tables-list=cache,cache_*,history,sessions,watchdog --dump-dir=$SYNCDIR 132 | 133 | # Make sure the tmp folder exists on the machine where this script is run so that rsync will not fail. 134 | mkdir -p $SYNCDIR 135 | 136 | # Make sure the tmp folder is created in the target machine 137 | drush $TARGET ssh "mkdir -p $SYNCDIR" 138 | 139 | # --compress-level=1 is used here as testing shows on fast network it's enough compression while at default level (6) we are already bound by the cpu 140 | # on slow connections it might still be worth to use --compress-level=6 which could save around 40% of the bandwith 141 | drush -y rsync --mode=akzi --compress-level=1 $SOURCE:$SYNCDIR $SYNCDIR 142 | # Delete the exported sql files from the source for security. 143 | drush $SOURCE ssh "rm -rf $SYNCDIR" 144 | drush -y rsync --mode=akzi --compress-level=1 $SYNCDIR $TARGET:$SYNCDIR 145 | # Delete the exported sql files from the local machine for security. 146 | rm -rf $SYNCDIR 147 | 148 | # Let's not use -y here yet so that we have at least one confirmation in this 149 | # script before we destroy the $TARGET data. 150 | echo "Confirm syncing database to $TARGET" 151 | drush $TARGET sql-drop 152 | drush $TARGET importdb --dump-dir=$SYNCDIR 153 | 154 | # Delete the exported sql files from target for security. 155 | drush $TARGET ssh "rm -rf $SYNCDIR" 156 | 157 | # Include any project specific sync commands. 158 | if [ -f syncdb_local.sh ] 159 | then 160 | source syncdb_local.sh 161 | fi 162 | 163 | # Get Drupal major version to do version specific commands like cache clearing. 164 | DVER=$(drush $SOURCE status | awk 'NR==1{print substr ($4, 0, 1)}') 165 | 166 | # Clear caches after sync. 167 | if [ "$DVER" == '8' ]; then 168 | drush $TARGET cr 169 | elif [ "$DVER" == '7' ]; then 170 | drush $TARGET cc all 171 | fi 172 | -------------------------------------------------------------------------------- /syncdb_local.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Sanitise the database. 4 | 5 | ## Run the sanitation. 6 | drush $TARGET sqlsan -y 7 | 8 | # Enable Stage File Proxy. 9 | drush $TARGET pm-enable --yes stage_file_proxy 10 | # @TODO: There is no variable-set in D8. 11 | # drush $TARGET variable-set stage_file_proxy_origin "$project_file_sync_url" 12 | echo 'Enabled Stage File Proxy.' 13 | 14 | # Run any pending updates. 15 | drush $TARGET updb -y 16 | 17 | # @TODO: Add config import. 18 | --------------------------------------------------------------------------------