├── .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 | [](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 |
--------------------------------------------------------------------------------