Repository: autopilotpattern/wordpress Branch: master Commit: 164a3d32a721 Files: 34 Total size: 82.2 KB Directory structure: gitextract_1bdlvoha/ ├── .dockerignore ├── .gitignore ├── Dockerfile ├── LICENSE ├── Makefile ├── README.md ├── bin/ │ ├── health.sh │ ├── onchange-db.sh │ ├── onchange-memcached.sh │ ├── onchange-nfs.sh │ ├── onchange-wp-config.sh │ ├── prestart.sh │ └── sensor.sh ├── docker-compose.yml ├── etc/ │ └── containerpilot.json ├── local-compose.yml ├── nginx/ │ ├── .dockerignore │ ├── Dockerfile │ └── etc/ │ ├── containerpilot.json │ └── nginx/ │ └── templates/ │ ├── conf.d/ │ │ └── site.conf │ └── nginx.conf ├── setup.sh └── var/ └── www/ └── html/ ├── .htaccess ├── content/ │ ├── mu-plugins/ │ │ ├── index.php │ │ └── register-theme-directory.php │ ├── plugins/ │ │ └── index.php │ ├── themes/ │ │ └── index.php │ └── uploads/ │ └── .empty ├── db-config.php.ctmpl ├── inactive-plugins/ │ └── no-uploads.php ├── index.php ├── memcached-config.php.ctmpl ├── wp-cli.yml └── wp-config.php.ctmpl ================================================ FILE CONTENTS ================================================ ================================================ FILE: .dockerignore ================================================ .DS_Store **/.DS_Store _env* ================================================ FILE: .gitignore ================================================ # credentials _env* manta* # macos frustration .DS_Store ================================================ FILE: Dockerfile ================================================ FROM php:5.6-apache RUN a2enmod rewrite # Install the PHP extensions we need, and other packages RUN set -ex \ && apt-get update \ && apt-get install -y --no-install-recommends \ curl \ jq \ less \ libjpeg-dev \ libmemcached-dev \ libpng12-dev \ nfs-common \ unzip \ && rm -rf /var/lib/apt/lists/* \ # Memcached 2.2.0 is the latest for PHP < 7 # see https://pecl.php.net/package/memcached && pecl install memcached-2.2.0 \ && docker-php-ext-configure gd --with-png-dir=/usr --with-jpeg-dir=/usr \ && docker-php-ext-install gd mysqli opcache \ && docker-php-ext-enable memcached \ # Set recommended PHP.ini settings # See https://secure.php.net/manual/en/opcache.installation.php && { \ echo 'opcache.memory_consumption=128'; \ echo 'opcache.interned_strings_buffer=8'; \ echo 'opcache.max_accelerated_files=4000'; \ echo 'opcache.revalidate_freq=60'; \ echo 'opcache.fast_shutdown=1'; \ echo 'opcache.enable_cli=1'; \ } > /usr/local/etc/php/conf.d/opcache-recommended.ini # The our helper/glue scripts and configuration for this specific app COPY bin /usr/local/bin COPY etc /etc # Add Containerpilot and its configuration # Releases at https://github.com/joyent/containerpilot/releases ENV CONTAINERPILOT_VER 2.7.3 ENV CONTAINERPILOT file:///etc/containerpilot.json RUN set -ex \ && export CONTAINERPILOT_CHECKSUM=2511fdfed9c6826481a9048e8d34158e1d7728bf \ && curl --retry 7 --fail -Lso /tmp/containerpilot.tar.gz \ "https://github.com/joyent/containerpilot/releases/download/${CONTAINERPILOT_VER}/containerpilot-${CONTAINERPILOT_VER}.tar.gz" \ && echo "${CONTAINERPILOT_CHECKSUM} /tmp/containerpilot.tar.gz" | sha1sum -c \ && tar zxf /tmp/containerpilot.tar.gz -C /usr/local/bin \ && rm /tmp/containerpilot.tar.gz # Install Consul # Releases at https://releases.hashicorp.com/consul RUN set -ex \ && export CONSUL_VERSION=0.7.5 \ && export CONSUL_CHECKSUM=40ce7175535551882ecdff21fdd276cef6eaab96be8a8260e0599fadb6f1f5b8 \ && curl --retry 7 --fail -vo /tmp/consul.zip "https://releases.hashicorp.com/consul/${CONSUL_VERSION}/consul_${CONSUL_VERSION}_linux_amd64.zip" \ && echo "${CONSUL_CHECKSUM} /tmp/consul.zip" | sha256sum -c \ && unzip /tmp/consul -d /usr/local/bin \ && rm /tmp/consul.zip \ && mkdir /config # Install Consul template # Releases at https://releases.hashicorp.com/consul-template/ RUN set -ex \ && export CONSUL_TEMPLATE_VERSION=0.18.3 \ && export CONSUL_TEMPLATE_CHECKSUM=caf6018d7489d97d6cc2a1ac5f1cbd574c6db4cd61ed04b22b8db7b4bde64542 \ && curl --retry 7 --fail -Lso /tmp/consul-template.zip "https://releases.hashicorp.com/consul-template/${CONSUL_TEMPLATE_VERSION}/consul-template_${CONSUL_TEMPLATE_VERSION}_linux_amd64.zip" \ && echo "${CONSUL_TEMPLATE_CHECKSUM} /tmp/consul-template.zip" | sha256sum -c \ && unzip /tmp/consul-template.zip -d /usr/local/bin \ && rm /tmp/consul-template.zip # Install wp-cli, http://wp-cli.org ENV WP_CLI_CONFIG_PATH /var/www/html/wp-cli.yml RUN set -ex \ && curl --retry 7 --fail -Ls -O https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar \ && chmod +x wp-cli.phar \ && mv wp-cli.phar /usr/local/bin/wp \ && wp --info --allow-root # Copy the WordPress skeleton from this repo into the container # This includes any themes and/or plugins we've added to the content/themes and content/plugins directories. COPY /var/www/html /var/www/html RUN chown -R www-data:www-data /var/www/html/* # Install WordPress via wp-cli & move the default themes to our content dir # Releases at https://core.svn.wordpress.org/tags/ and https://wordpress.org/news/category/releases/ ENV WORDPRESS_VERSION 4.7.5 RUN set -ex \ && wp --allow-root core download --version=${WORDPRESS_VERSION} \ && mv /var/www/html/wordpress/wp-content/themes/* /var/www/html/content/themes/ # Install HyperDB, https://wordpress.org/plugins/hyperdb # Releases at https://wordpress.org/plugins/hyperdb/developers/ , though no SHA1 fingerprints are published RUN set -ex \ && export HYPERDB_VERSION=1.1 \ && curl --retry 7 --fail -Ls -o /var/www/html/hyperdb.zip https://downloads.wordpress.org/plugin/hyperdb.${HYPERDB_VERSION}.zip \ && unzip hyperdb.zip \ && chown -R www-data:www-data /var/www/html/hyperdb \ && mv hyperdb/db.php /var/www/html/content/. \ && rm -rf /var/www/html/hyperdb.zip /var/www/html/hyperdb \ && touch /var/www/html/content/db-config.php # Install ztollman's object-cache.php or object caching to memcached RUN set -ex \ && curl --retry 7 --fail -Ls -o /var/www/html/content/object-cache.php https://raw.githubusercontent.com/tollmanz/wordpress-pecl-memcached-object-cache/master/object-cache.php # The volume is defined after we install everything VOLUME /var/www/html CMD ["/usr/local/bin/containerpilot", \ "apache2-foreground"] ================================================ FILE: LICENSE ================================================ GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Lesser General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. {description} Copyright (C) {year} {fullname} This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. {signature of Ty Coon}, 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. ================================================ FILE: Makefile ================================================ # Makefile for shipping and testing the container image. MAKEFLAGS += --warn-undefined-variables .DEFAULT_GOAL := build .PHONY: * # we get these from CI environment if available, otherwise from git GIT_COMMIT ?= $(shell git rev-parse --short HEAD) GIT_BRANCH ?= $(shell git rev-parse --abbrev-ref HEAD) WORKSPACE ?= $(shell pwd) namespace ?= autopilotpattern tag := branch-$(shell basename $(GIT_BRANCH)) imageWordpress := $(namespace)/wordpress imageNginx := $(namespace)/wordpress-nginx #dockerLocal := DOCKER_HOST= DOCKER_TLS_VERIFY= DOCKER_CERT_PATH= docker dockerLocal := docker #composeLocal := DOCKER_HOST= DOCKER_TLS_VERIFY= DOCKER_CERT_PATH= docker-compose composeLocal := docker-compose ## Display this help message help: @awk '/^##.*$$/,/[a-zA-Z_-]+:/' $(MAKEFILE_LIST) | awk '!(NR%2){print $$0p}{p=$$0}' | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}' | sort # ------------------------------------------------ # Container builds ## Builds the application container image locally build: $(dockerLocal) build -t=$(imageWordpress):$(tag) . cd nginx && $(dockerLocal) build -t=$(imageNginx):$(tag) . ## Push the current application container images to the Docker Hub push: $(dockerLocal) push $(imageWordpress):$(tag) $(dockerLocal) push $(imageNginx):$(tag) ## Tag the current images as 'latest' tag: $(dockerLocal) tag $(imageWordpress):$(tag) $(imageWordpress):latest $(dockerLocal) tag $(imageNginx):$(tag) $(imageNginx):latest ## Push latest tag(s) to the Docker Hub ship: tag $(dockerLocal) push $(imageWordpress):$(tag) $(dockerLocal) push $(imageWordpress):latest $(dockerLocal) push $(imageNginx):$(tag) $(dockerLocal) push $(imageNginx):latest # ------------------------------------------------ # Test running ## Pull the container images from the Docker Hub pull: $(dockerLocal) pull $(imageWordpress):$(tag) $(dockerLocal) pull $(imageNginx):$(tag) ## Print environment for build debugging debug: @echo WORKSPACE=$(WORKSPACE) @echo GIT_COMMIT=$(GIT_COMMIT) @echo GIT_BRANCH=$(GIT_BRANCH) @echo namespace=$(namespace) @echo tag=$(tag) @echo imageWordpress=$(imageWordpress) @echo imageNginx=$(imageNginx) # ------------------------------------------------------- # helper functions for testing if variables are defined # check_var = $(foreach 1,$1,$(__check_var)) __check_var = $(if $(value $1),,\ $(error Missing $1 $(if $(value 2),$(strip $2)))) ================================================ FILE: README.md ================================================ # Autopilot Pattern WordPress *A robust and highly-scalable implementation of WordPress in Docker using the Autopilot Pattern* [![DockerPulls](https://img.shields.io/docker/pulls/autopilotpattern/wordpress.svg)](https://registry.hub.docker.com/u/autopilotpattern/wordpress/) [![DockerStars](https://img.shields.io/docker/stars/autopilotpattern/wordpress.svg)](https://registry.hub.docker.com/u/autopilotpattern/wordpress/) --- ### Containerized and easily scalable This project uses the Autopilot Pattern to automate operations, including discovery and configuration, for easy scaling to any size. All component containers use [ContainerPilot](https://www.joyent.com/containerpilot) and [Consul](https://consul.io/) to configure themselves. This also allows each service to be scaled independently to handle incoming traffic and as more services are added, the containers that consume these services will reconfigure themselves accordingly. ### Project architecture A running cluster includes the following components: - [ContainerPilot](https://www.joyent.com/containerpilot): included in our MySQL containers to orchestrate bootstrap behavior and coordinate replication using keys and checks stored in Consul in the `preStart`, `health`, and `onChange` handlers. - [MySQL](https://github.com/autopilotpattern/mysql/): we're using the [Autopilot Pattern implementation of MySQL](https://www.joyent.com/blog/dbaas-simplicity-no-lock-in) for automatic backups and self-clustering so that we can deploy and scale easily - [HyperDB](https://wordpress.org/plugins/hyperdb/): an "advanced database class that replaces a few of the WordPress built-in database functions" to support the MySQL cluster that's necessary for scaling WordPress; everything is automatically configured so running a scalable WordPress site is no more complex than running without the scaling features - [Memcached](https://github.com/autpilotpattern/memcached/): improves performance by keeping frequently accessed data in memory so WordPress doesn't have to query the database for every request; the images include [tollmanz's Memcached plugin](https://github.com/tollmanz/wordpress-pecl-memcached-object-cache) pre-installed, and ContainerPilot automatically configures it as we scale - [Nginx](https://github.com/autopilotpattern/nginx): the front-end load balancer for the WordPress environment; passes traffic from users to the WordPress containers on the back-end - [NFS](https://github.com/autopilotpattern/nfsserver/): stores user uploaded files so these files can be shared between many WordPress containers - [Consul](https://www.consul.io/): used to coordinate replication and failover - [Manta](https://www.joyent.com/object-storage): the Joyent object store, for securely and durably storing our MySQL snapshots - [Prometheus](https://github.com/autopilotpattern/prometheus): an optional, [open source monitoring tool](https://prometheus.io) that tracks the performance of each component and demonstrates [ContainerPilot telemetry](https://www.joyent.com/blog/containerpilot-telemetry) - [WP-CLI](http://wp-cli.org/): to make managing WordPress easier ### How do I use this thing? Pick the answer that fits: 1. For the hello world experience: follow the directions below for configuration, then `docker-compose up -d` and you're done. 1. For building your own WordPress in Docker: clone this repository and place the WordPress theme you want to use into the `var/www/html/content/themes` directory. Develop locally using the `local-compose.yml` file, then build your Docker image and run those in the cloud with your own `docker-compose.yml` file that specifies your custom image. The instructions below will get you set up to run containers on [Triton](https://www.joyent.com/), or anywhere that supports the Autopilot Pattern. ### Getting started on Triton 1. [Get a Joyent account](https://my.joyent.com/landing/signup/) and [add your SSH key](https://docs.joyent.com/public-cloud/getting-started). 1. Install the [Docker Toolbox](http://docker.com/toolbox) (including `docker` and `docker-compose`) on your laptop or other environment, as well as the [Joyent Triton CLI](https://www.joyent.com/blog/introducing-the-triton-command-line-tool) 1. [Configure Docker and Docker Compose for use with Joyent](https://docs.joyent.com/public-cloud/api-access/docker): ```bash curl -O https://raw.githubusercontent.com/joyent/sdc-docker/master/tools/sdc-docker-setup.sh && chmod +x sdc-docker-setup.sh ./sdc-docker-setup.sh -k us-east-1.api.joyent.com ~/.ssh/ ``` ### Configure your environment Check that everything is configured correctly by running `./setup.sh`. You'll need an SSH key that has access to Manta, the object store where the MySQL backups are stored. Pass the path of that SSH key as `./setup.sh ~/path/to/MANTA_SSH_KEY`. The script will create an `_env` file that names the variables that you will need to run your WordPress environment. #### Manta settings The script will set defaults for almost every config variable, but the Manta config is required and must be set manually. The two most important variables there are: ``` MANTA_BUCKET=//stor/ # an existing Manta bucket MANTA_USER= # a user with access to that bucket ``` The MySQL container will take a backup during its `preStart` handler and periodically while running. Configure these Manta settings to specify how and where this backup is stored. Here you need to specify the `MANTA_USER`, and also the `MANTA_BUCKET` where the backups will be stored. #### WordPress configuration The setup script will set working defaults for the entire WordPress configuration. The defaults will work for a quick "hello world" experience, but you'll probably want to set your own values for many fields. ``` # Environment variables for for WordPress site WORDPRESS_URL=http://my-site.example.org/ WORDPRESS_SITE_TITLE=My Blog WORDPRESS_ADMIN_EMAIL=user@example.net WORDPRESS_ADMIN_USER=admin WORDPRESS_ADMIN_PASSWORD= WORDPRESS_ACTIVE_THEME=twentysixteen WORDPRESS_CACHE_KEY_SALT= #WORDPRESS_TEST_DATA=true # uncomment to import a collection of test content on start ``` This block is the typical information you must provide when installing WordPress. The URL of the site, the site title and admin user information are all straightforward. `WORDPRESS_ACTIVE_THEME` is the theme that will be activated automatically when the container starts. This will typically be theme that you are developing in this repo, or one of the default themes. `WORDPRESS_CACHE_KEY_SALT` should be set to a unique string, the object caching in WordPress will use this salt to determine the cache keys for information it sets on the Memcached container. If you are not bringing your own theme in this repo, you can choose from these default themes for the `WORDPRESS_ACTIVE_THEME` variable: - `twentyfifteen` - `twentyfourteen` - `twentysixteen` The script will set a `WORDPRESS_URL` value for Triton users using [Container Name Service](https://www.joyent.com/blog/introducing-triton-container-name-service) that will make it easy to test the containers without setting any DNS information. You can [`CNAME` your site DNS](https://www.joyent.com/blog/introducing-triton-container-name-service#example-global-dns) to that to make it easy to scale and replace the Nginx containers at the front of your site without ever needing to update the DNS configuration. Setting `WORDPRESS_TEST_DATA` will download the [manovotny/wptest](https://github.com/manovotny/wptest) content library when the WordPress container starts. #### MySQL settings The setup script will set default values for the MySQL configuration, including randomly generated passwords. ``` # Environment variables for MySQL service # WordPress database/WPDB information MYSQL_USER=wpdbuser MYSQL_PASSWORD= MYSQL_DATABASE=wp # MySQL replication user, should be different from above MYSQL_REPL_USER=repluser MYSQL_REPL_PASSWORD= ``` These values will be automatically set in the `wp-config.php`. The last two options are used by the Autopilot Pattern MySQL container to set up its replication when scaled up to more than a single container. You can keep `repluser`, but set a unique password for your environment. #### WordPress unique salts As with most of the other configuration blocks, the setup script will set reasonable defaults for these values. ``` # Wordpress security salts # These must be unique for your install to ensure the security of the site WORDPRESS_AUTH_KEY= WORDPRESS_SECURE_AUTH_KEY= WORDPRESS_LOGGED_IN_KEY= WORDPRESS_NONCE_KEY= WORDPRESS_AUTH_SALT= WORDPRESS_SECURE_AUTH_SALT= WORDPRESS_LOGGED_IN_SALT= WORDPRESS_NONCE_SALT= ``` These variables are how WordPress secures your logins and other secret info. These should be unique for your site. You can set your own values, or use [this WordPress tool](https://api.wordpress.org/secret-key/1.1/salt/) to generate a new set of random values. #### Consul Finally we need to configure an environment variable with the location of our Consul service. The setup script will pre-set this for Triton users. ``` CONSUL= ``` For local development, we use Docker links and simply set this to `CONSUL=consul`, but on Triton we use [Container Name Service](https://www.joyent.com/blog/introducing-triton-container-name-service) so that we can have a raft of Consul instances operating as a highly available service ([see example](https://www.joyent.com/blog/introducing-triton-container-name-service#example-consul-bootstrapping)). ### A note on Nginx This project also builds it's own Nginx container that is based on the [AutoPilot Pattern Nginx](https://github.com/autopilotpattern/nginx). We build a custom Nginx container to more easily inject our custom configurations. The configs located in the `/nginx` directory should work well for most use cases of this project, but they can be customized and baked into the Nginx image if the need arises. ### Start the containers! After configuring everything, we are now ready to start the containers. To do that simply execute `docker-compose up -d` to spin everything up on Triton. Open your browser to the `WORDPRESS_URL` and enjoy your new site! For local development, use `docker-compose -f local-compose.yml up -d`. ### Going big To scale, use `docker-compose scale...`. For example, the following will set the scale of the WordPress, Memcached, Nginx, and MySQL services to three instances each: ```bash docker-compose scale wordpress=3 memcached=3 nginx=3 mysql=3 ``` If there are few instances running for any of those services, more will be added to meet the specified count. As you scale, the application will automatically reconfigure itself so that everything is connected. All the Nginx instances will connect to all the WordPress instances, and those will connect to all the Memcached and MySQL instances. If an instance should unexpectedly crash, the other instances will automatically reconfigure to re-route requests around the failed instance. To scale back down, simply run `docker-compose scale...` and specify a smaller number of instances. ### Compatibility This project has been fully tested and documented to run in Docker in local development environments and on [Joyent Triton](https://www.joyent.com), however it has been demonstrated on, or is believe compatible with container environments including: - [Mantl](http://mantl.io) - [DC/OS](https://dcos.io) - [Docker Swarm](https://www.docker.com/products/docker-swarm) - [Kubernetes](http://kubernetes.io) - Others ### Contributing - Please [report bugs in Github](https://github.com/autopilotpattern/wordpress/issues) (and check the bug list for known bugs) - It's open source, [pull requests welcome](https://github.com/autopilotpattern/wordpress/pulls)! ### Sponsors Initial development of this project was sponsored by [Joyent](https://www.joyent.com) and [10up](http://10up.com). ================================================ FILE: bin/health.sh ================================================ #!/bin/bash /usr/local/bin/wp --allow-root core is-installed \ && /usr/local/bin/wp --allow-root option get siteurl \ && /usr/bin/curl --fail -s -o /dev/null http://localhost/ ================================================ FILE: bin/onchange-db.sh ================================================ #!/bin/bash /usr/local/bin/onchange-wp-config.sh consul-template \ -once \ -dedup \ -consul-addr ${CONSUL}:8500 \ -template "/var/www/html/db-config.php.ctmpl:/var/www/html/content/db-config.php" ================================================ FILE: bin/onchange-memcached.sh ================================================ #!/bin/bash consul-template \ -once \ -dedup \ -consul-addr ${CONSUL}:8500 \ -template "/var/www/html/memcached-config.php.ctmpl:/var/www/html/memcached-config.php" ================================================ FILE: bin/onchange-nfs.sh ================================================ #!/bin/bash if [[ `curl -s ${CONSUL}:8500/v1/health/state/passing | grep nfs` ]] then echo "nfs is healthy, mounting uploads directory...." NFS=$(curl -s http://${CONSUL}:8500/v1/catalog/service/nfs?passing | jq -r '.[0] | .ServiceAddress') mount -t nfs -v -o nolock,vers=3 ${NFS}:/exports /var/www/html/content/uploads echo "removing no-uploads.php mu-plugin" rm /var/www/html/content/mu-plugins/no-uploads.php # check 'wp core is-installed' here to prevent errors in the log on first run # before WP gets installed into the database if $(wp --allow-root core is-installed) then echo "adding 'upload_files' capability back to default roles" # only these roles have 'upload_files' cap by default for role in administrator editor author do if [ "$role" != 'role' ] then wp --allow-root cap add ${role} upload_files fi done fi else echo "nfs is not healthly, umounting uploads directory..." umount -f -l /var/www/html/content/uploads echo "creating mu-plugin for NFS error in wp-admin" cp /var/www/html/inactive-plugins/no-uploads.php /var/www/html/content/mu-plugins/ echo "removing 'upload_files' capability from all roles..." for role in $(wp --allow-root role list --fields=role --format=csv) do if [ "$role" != 'role' ] then wp --allow-root cap remove ${role} upload_files fi done fi ================================================ FILE: bin/onchange-wp-config.sh ================================================ #!/bin/bash # The WordPress config file consul-template \ -once \ -dedup \ -consul-addr ${CONSUL}:8500 \ -template "/var/www/html/wp-config.php.ctmpl:/var/www/html/wp-config.php" ================================================ FILE: bin/prestart.sh ================================================ #!/bin/bash # Refresh all config files in order # # This script is typically called once at the container start, but # it can be called manually if WP config details must be changed # The database and memcached config files are separate to avoid collisions # if their backends' onChange handlers are triggered simultaneously echo "******running preStart script*********" until [[ `curl -s ${CONSUL}:8500/v1/health/state/passing | grep mysql-primary` ]] do echo "mysql-primary not healthly...." sleep 5 done until [[ `curl -s ${CONSUL}:8500/v1/health/state/passing | grep nfs` ]] do echo "no healthly nfs server avaliable yet...." sleep 5 done echo "mysql-primary and nfs are now healthly, moving on..." /usr/local/bin/onchange-db.sh /usr/local/bin/onchange-memcached.sh /usr/local/bin/onchange-nfs.sh # The WordPress config file /usr/local/bin/onchange-wp-config.sh if $(wp --allow-root core is-installed) then echo "WP is installed" # run update-db to ensure the database schema is up to date in case the WP version was upgraded in the Docker image wp --allow-root core update-db else echo "WP is NOT installed" echo "installing now...." # TODO: check WORDPRESS_URL to ensure it has http:// or https:// at the beginning, if not put it in wp --allow-root core install --url=$WORDPRESS_URL --title="$WORDPRESS_SITE_TITLE" --admin_user=$WORDPRESS_ADMIN_USER --admin_password=$WORDPRESS_ADMIN_PASSWORD --admin_email=$WORDPRESS_ADMIN_EMAIL --skip-email # update siteurl to work with our directory structure # wp option update for siteurl REQUIRES http://, need to determine will we handle that here # or ask for it in the _env file or test for it above wp --allow-root option update siteurl `wp --allow-root option get siteurl`/wordpress # set a nice default permalink structure wp --allow-root option update permalink_structure '/%year%/%monthnum%/%postname%/' # set theme if [ -n "$WORDPRESS_ACTIVE_THEME" ] then wp --allow-root theme activate $WORDPRESS_ACTIVE_THEME fi if [ -n "$WORDPRESS_TEST_DATA" ] then echo "installing WP test content" wp --allow-root plugin install wordpress-importer --activate curl -OL https://raw.githubusercontent.com/manovotny/wptest/master/wptest.xml wp --allow-root import wptest.xml --authors=create wp --allow-root plugin uninstall wordpress-importer --deactivate chown -R www-data:www-data /var/www/html/content/uploads/* rm wptest.xml fi fi ================================================ FILE: bin/sensor.sh ================================================ #!/bin/bash set -e help() { echo 'Uses cli tools free and top to determine current CPU and memory usage' echo 'for the telemetry service.' } # memory usage in percent memory() { # awk oneliner to get memory usage # free -m | awk 'NR==2{printf "Memory Usage: %s/%sMB (%.2f%%)\n", $3,$2,$3*100/$2 }' # output: # Memory Usage: 15804/15959MB (99.03%) (>&2 echo "memory check fired") local memory=$(free -m | awk 'NR==2{printf "%.2f", $3*100/$2 }') echo ${memory} } # cpu load cpu() { # oneliner to display cpu load # top -bn1 | grep load | awk '{printf "CPU Load: %.2f\n", $(NF-2)}' (>&2 echo "cpu check fired") local cpuload=$(top -bn1 | grep load | awk '{printf "%.2f", $(NF-2)}') echo ${cpuload} } cmd=$1 if [ ! -z "$cmd" ]; then shift 1 $cmd "$@" exit fi help ================================================ FILE: docker-compose.yml ================================================ # An demo version of WordPress for easy scaling wordpress: image: autopilotpattern/wordpress:latest restart: always env_file: _env environment: - CONSUL_AGENT=1 ports: - 80 - 9090 labels: # Setting the CNS service name - triton.cns.services=wp-wordpress # Soft anti-affinity to avoid other WordPress instances - com.docker.swarm.affinities=["container!=~*wordpress*"] # Set the package - com.joyent.package=g4-highcpu-1G # Consul is the service catalog that helps discovery between the components # Change "-bootstrap" to "-bootstrap-expect 3", then scale to 3 or more to # turn this into an HA Consul raft. consul: image: autopilotpattern/consul:latest command: > /usr/local/bin/containerpilot /bin/consul agent -server -bootstrap -config-dir=/etc/consul -ui-dir /ui restart: always env_file: _env ports: - 8500 dns: - 127.0.0.1 labels: - triton.cns.services=wp-consul # Soft anti-affinity to avoid all other containers - com.docker.swarm.affinities=["container!=~*"] - com.joyent.package=g4-highcpu-128M # NFS is used to store user uploads nfs: image: autopilotpattern/nfsserver:latest restart: always env_file: _env environment: - CONSUL_AGENT=1 expose: - 111 - 1892 - 2049 labels: - triton.cns.services=wp-nfs - com.docker.swarm.affinities=["container!=~*"] - com.joyent.package=g4-highcpu-256M # The MySQL database will automatically cluster and scale # see https://www.joyent.com/blog/dbaas-simplicity-no-lock-in mysql: image: autopilotpattern/mysql:latest restart: always env_file: _env environment: - CONSUL_AGENT=1 expose: - 3306 labels: - triton.cns.services=wp-mysql - com.docker.swarm.affinities=["container!=~*mysql*"] - com.joyent.package=g4-highcpu-4G # Memcached is a high performance object cache memcached: image: autopilotpattern/memcached:latest restart: always env_file: _env environment: - CONSUL_AGENT=1 ports: - 11211 labels: - triton.cns.services=wp-memcached - com.docker.swarm.affinities=["container!=~*memcached*"] - com.joyent.package=g4-highcpu-512M # Nginx as a load-balancing tier and reverse proxy nginx: image: autopilotpattern/wordpress-nginx:latest restart: always ports: - 80 - 443 - 9090 env_file: _env environment: - CONSUL_AGENT=1 labels: - triton.cns.services=wp-nginx,nginx - com.docker.swarm.affinities=["container!=~*nginx*"] - com.joyent.package=g4-highcpu-512M # Prometheus is an open source performance monitoring tool # it is included here for demo purposes and is not required prometheus: image: autopilotpattern/prometheus:branch-triton-support restart: always env_file: _env ports: - 9090 labels: - triton.cns.services=wp-prometheus - com.docker.swarm.affinities=["container!=~*prometheus*"] - com.joyent.package=g4-highcpu-1G ================================================ FILE: etc/containerpilot.json ================================================ { "consul": "{{ if .CONSUL_AGENT }}localhost{{ else }}{{ .CONSUL }}{{ end }}:8500", "preStart": "/usr/local/bin/prestart.sh", "services": [ { "name": "wordpress", "port": 80, "health": "/usr/local/bin/health.sh", "poll": 10, "ttl": 25 } ], "backends": [ { "name": "mysql", "poll": 7, "onChange": "/usr/local/bin/onchange-db.sh" }, { "name": "mysql-primary", "poll": 7, "onChange": "/usr/local/bin/onchange-db.sh" }, { "name": "nfs", "poll": 11, "onChange": "/usr/local/bin/onchange-nfs.sh" }, { "name": "memcached", "poll": 5, "onChange": "/usr/local/bin/onchange-memcached.sh" } ], "coprocesses": [{{ if .CONSUL_AGENT }} { "command": ["/usr/local/bin/consul", "agent", "-data-dir=/data", "-config-dir=/config", "-rejoin", "-retry-join", "{{ .CONSUL }}", "-retry-max", "10", "-retry-interval", "10s"], "restarts": "unlimited" }{{ end }}], "telemetry": { "port": 9090, "sensors": [ { "name": "wp_memory_percent", "help": "percentage of memory used", "type": "gauge", "poll": 5, "check": ["/usr/local/bin/sensor.sh", "memory"] }, { "name": "wp_cpu_load", "help": "cpu load", "type": "gauge", "poll": 5, "check": ["/usr/local/bin/sensor.sh", "cpu"] } ] } } ================================================ FILE: local-compose.yml ================================================ # See docker-compose.yml for further descriptions and configuration details of each service wordpress: extends: file: docker-compose.yml service: wordpress build: . privileged: true restart: "no" environment: - CONSUL=consul links: - consul:consul ports: - "80" consul: extends: file: docker-compose.yml service: consul restart: "no" ports: - 8500:8500 nfs: extends: file: docker-compose.yml service: nfs privileged: true restart: "no" environment: - CONSUL=consul links: - consul:consul ports: - 111 - 1892:1892 - 2049 mysql: extends: file: docker-compose.yml service: mysql restart: "no" environment: - CONSUL=consul links: - consul:consul ports: - 3306 memcached: extends: file: docker-compose.yml service: memcached restart: "no" environment: - CONSUL=consul links: - consul:consul nginx: extends: file: docker-compose.yml service: nginx build: nginx/ restart: "no" environment: - CONSUL=consul links: - consul:consul ports: - 80:80 prometheus: extends: file: docker-compose.yml service: prometheus restart: "no" environment: - CONSUL=consul links: - consul:consul ports: - 9090:9090 ================================================ FILE: nginx/.dockerignore ================================================ .DS_Store **/.DS_Store _env* ================================================ FILE: nginx/Dockerfile ================================================ # a minimal Nginx container including containerbuddy and a simple virtulhost config FROM autopilotpattern/nginx:1.13-r7.0.1 # Add our configuration files COPY etc /etc ================================================ FILE: nginx/etc/containerpilot.json ================================================ { "consul": "{{ if .CONSUL_AGENT }}localhost{{ else }}{{ .CONSUL }}{{ end }}:8500", "preStart": "generate-config", "logging": {"level": "DEBUG"}, "services": [ { "name": "nginx", "port": 80, "health": "/usr/bin/curl --fail --silent --show-error --output /dev/null http://localhost/nginx-health", "poll": 10, "ttl": 25, "interfaces": ["eth0"] }, { "name": "nginx-public", "port": 80, "health": "/usr/bin/curl --fail --silent --show-error --output /dev/null http://localhost/nginx-health", "poll": 10, "ttl": 25, "interfaces": ["eth1", "eth0"] }{{ if .ACME_DOMAIN }}, { "name": "nginx-public-ssl", "port": 443, "health": "/usr/local/bin/acme init && /usr/bin/curl --insecure --fail --silent --show-error --output /dev/null --header \"HOST: {{ .ACME_DOMAIN }}\" https://localhost/nginx-health", "poll": 10, "ttl": 25, "interfaces": ["eth1", "eth0"] }{{ end }} ], "backends": [ { "name": "wordpress", "poll": 7, "onChange": "reload" } ], "coprocesses": [{{ if .CONSUL_AGENT }} { "command": ["/usr/local/bin/consul", "agent", "-data-dir=/data", "-config-dir=/config", "-rejoin", "-retry-join", "{{ .CONSUL }}", "-retry-max", "10", "-retry-interval", "10s"], "restarts": "unlimited" }{{ end }} {{ if and .CONSUL_AGENT .ACME_DOMAIN }},{{ end }} {{ if .ACME_DOMAIN }} { "command": ["/usr/local/bin/consul-template", "-config", "/etc/acme/watch.hcl", "-consul", "{{ if .CONSUL_AGENT }}localhost{{ else }}{{ .CONSUL }}{{ end }}:8500"], "restarts": "unlimited" }{{ end }}], "telemetry": { "port": 9090, "sensors": [ { "name": "nginx_connections_unhandled_total", "help": "Number of accepted connnections that were not handled", "type": "gauge", "poll": 5, "check": ["sensor", "unhandled"] }, { "name": "nginx_connections_load", "help": "Ratio of active connections (less waiting) to the maximum worker connections", "type": "gauge", "poll": 5, "check": ["sensor", "connections_load"] } ] }, "tasks": [{{ if .ACME_DOMAIN }} { "name": "acme-checkin", "command": [ "/usr/local/bin/acme", "checkin" ], "frequency": "5m", "timeout": "10s" }, { "name": "acme-renew-certs", "command": [ "/usr/local/bin/acme", "renew-certs" ], "frequency": "12h", "timeout": "10m" }, { "name": "clean-unused-certs", "command": ["/usr/local/bin/acme", "clean-certs" ], "frequency": "24h", "timeout": "10m" }{{ end }} ] } ================================================ FILE: nginx/etc/nginx/templates/conf.d/site.conf ================================================ {{ $acme_domain := env "ACME_DOMAIN" }} {{ $ssl_ready := env "SSL_READY" }} # If we're listening on https, define an http listener that redirects everything to https {{ if eq $ssl_ready "true" }} server { server_name _; listen 80; include /etc/nginx/health.conf; location / { return 301 https://$host$request_uri; } } {{ end }} # The main server block server { server_name _; # Listen on port 80 unless we have certificates installed, then listen on 443 listen {{ if ne $ssl_ready "true" }}80{{ else }}443 ssl{{ end }}; include /etc/nginx/health.conf; location /.well-known/acme-challenge { alias /var/www/acme/challenge; } {{ if service "wordpress" }} rewrite ^/wp-admin/?(.*) /wordpress/wp-admin/$1; location ^~ / { proxy_pass http://wordpress; proxy_redirect off; proxy_set_header Host $http_host; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; }{{ else }} # return 503 errors if we don't have our expected back-end location / { return 503; }{{ end }} } ================================================ FILE: nginx/etc/nginx/templates/nginx.conf ================================================ # This is an example Nginx configuration template file. # Adjust the values below as required for your application. user nginx; worker_processes 1; error_log /var/log/nginx/error.log warn; pid /var/run/nginx.pid; events { worker_connections 1024; } http { include /etc/nginx/mime.types; default_type application/octet-stream; map $status $isNot2xx { ~^2 0; default 1; } log_format main '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" "$http_x_forwarded_for" ' '$upstream_addr'; access_log /var/log/nginx/access.log main; sendfile on; keepalive_timeout 65; {{ $ssl_ready := env "SSL_READY" }} {{ if eq $ssl_ready "true" }} include ssl.conf; {{ end }} {{ if service "wordpress" }} upstream wordpress { # write the address:port pairs for each healthy wordpress node {{range service "wordpress"}} server {{.Address}}:{{.Port}}; {{end}} }{{ end }} include conf.d/*.conf; } ================================================ FILE: setup.sh ================================================ #!/bin/bash set -e -o pipefail help() { echo echo 'Usage ./setup.sh ~/path/to/MANTA_PRIVATE_KEY' echo echo 'Checks that your Triton and Docker environment is sane and configures' echo 'an environment file to use.' echo echo 'MANTA_PRIVATE_KEY is the filesystem path to an SSH private key' echo 'used to connect to Manta for the database backups.' echo echo 'Additional details must be configured in the _env file, but this script will properly' echo 'encode the SSH key details for use with this this project.' echo echo '-' echo echo 'Usage ./setup.sh get-cns-hostname' echo echo 'Output the CNS hostname suitable for aliasing in DNS for custom domain names.' echo } # populated by `check` function whenever we're using Triton TRITON_USER= TRITON_DC= TRITON_ACCOUNT= # --------------------------------------------------- # Top-level commands # Output aliasable CNS hostname get-cns-hostname() { TRITON_DC=$(triton profile get | awk -F"/" '/url:/{print $3}' | awk -F'.' '{print $1}') TRITON_ACCOUNT=$(triton account get | awk -F": " '/id:/{print $2}') echo "nginx.svc.${TRITON_ACCOUNT}.${TRITON_DC}.cns.joyent.com" } # Check for correct configuration and setup _env file envcheck() { if [ -z "$1" ]; then tput rev # reverse tput bold # bold echo 'Please provide a path to a SSH private key to access Manta.' tput sgr0 # clear help exit 1 fi if [ ! -f "$1" ]; then tput rev # reverse tput bold # bold echo 'SSH private key for Manta is unreadable.' tput sgr0 # clear help exit 1 fi # Assign args to named vars MANTA_PRIVATE_KEY_PATH=$1 command -v docker >/dev/null 2>&1 || { echo tput rev # reverse tput bold # bold echo 'Docker is required, but does not appear to be installed.' tput sgr0 # clear echo 'See https://docs.joyent.com/public-cloud/api-access/docker' exit 1 } command -v json >/dev/null 2>&1 || { echo tput rev # reverse tput bold # bold echo 'Error! JSON CLI tool is required, but does not appear to be installed.' tput sgr0 # clear echo 'See https://apidocs.joyent.com/cloudapi/#getting-started' exit 1 } command -v triton >/dev/null 2>&1 || { echo tput rev # reverse tput bold # bold echo 'Error! Joyent Triton CLI is required, but does not appear to be installed.' tput sgr0 # clear echo 'See https://www.joyent.com/blog/introducing-the-triton-command-line-tool' exit 1 } # make sure Docker client is pointed to the same place as the Triton client local docker_user=$(docker info 2>&1 | awk -F": " '/SDCAccount:/{print $2}') local docker_dc=$(echo $DOCKER_HOST | awk -F"/" '{print $3}' | awk -F'.' '{print $1}') TRITON_USER=$(triton profile get | awk -F": " '/account:/{print $2}') TRITON_DC=$(triton profile get | awk -F"/" '/url:/{print $3}' | awk -F'.' '{print $1}') TRITON_ACCOUNT=$(triton account get | awk -F": " '/id:/{print $2}') if [ ! "$docker_user" = "$TRITON_USER" ] || [ ! "$docker_dc" = "$TRITON_DC" ]; then echo tput rev # reverse tput bold # bold echo 'Error! The Triton CLI configuration does not match the Docker CLI configuration.' tput sgr0 # clear echo echo "Docker user: ${docker_user}" echo "Triton user: ${TRITON_USER}" echo "Docker data center: ${docker_dc}" echo "Triton data center: ${TRITON_DC}" exit 1 fi local triton_cns_enabled=$(triton account get | awk -F": " '/cns/{print $2}') if [ ! "true" == "$triton_cns_enabled" ]; then echo tput rev # reverse tput bold # bold echo 'Error! Triton CNS is required and not enabled.' tput sgr0 # clear echo echo 'Consider running:' echo ' triton account update triton_cns_enabled=true' echo exit 1 fi # setup environment file if [ ! -f "_env" ]; then echo '# Environment variables for for WordPress site' > _env echo '# please include the scheme http:// or https:// in the URL variable' >> _env echo 'WORDPRESS_URL=http://'nginx.svc.${TRITON_ACCOUNT}.${TRITON_DC}.triton.zone >> _env echo 'WORDPRESS_SITE_TITLE=Autopilot Pattern WordPress test site' >> _env echo 'WORDPRESS_ADMIN_EMAIL=user@example.net' >> _env echo 'WORDPRESS_ADMIN_USER=admin-'$(cat /dev/urandom | LC_ALL=C tr -dc 'A-Za-z0-9' | head -c 3) >> _env echo 'WORDPRESS_ADMIN_PASSWORD='$(cat /dev/urandom | LC_ALL=C tr -dc 'A-Za-z0-9' | head -c 11) >> _env echo 'WORDPRESS_ACTIVE_THEME=twentysixteen' >> _env echo 'WORDPRESS_CACHE_KEY_SALT='$(cat /dev/urandom | LC_ALL=C tr -dc 'A-Za-z0-9' | head -c 53) >> _env echo '#WORDPRESS_TEST_DATA=true # uncomment to import a collection of test content on start' >> _env echo >> _env echo '# Wordpress security salts' >> _env echo '# These must be unique for your install to ensure the security of the site' >> _env echo 'WORDPRESS_AUTH_KEY='$(cat /dev/urandom | LC_ALL=C tr -dc 'A-Za-z0-9' | head -c 53) >> _env echo 'WORDPRESS_SECURE_AUTH_KEY='$(cat /dev/urandom | LC_ALL=C tr -dc 'A-Za-z0-9' | head -c 53) >> _env echo 'WORDPRESS_LOGGED_IN_KEY='$(cat /dev/urandom | LC_ALL=C tr -dc 'A-Za-z0-9' | head -c 53) >> _env echo 'WORDPRESS_NONCE_KEY='$(cat /dev/urandom | LC_ALL=C tr -dc 'A-Za-z0-9' | head -c 53) >> _env echo 'WORDPRESS_AUTH_SALT='$(cat /dev/urandom | LC_ALL=C tr -dc 'A-Za-z0-9' | head -c 53) >> _env echo 'WORDPRESS_SECURE_AUTH_SALT='$(cat /dev/urandom | LC_ALL=C tr -dc 'A-Za-z0-9' | head -c 53) >> _env echo 'WORDPRESS_LOGGED_IN_SALT='$(cat /dev/urandom | LC_ALL=C tr -dc 'A-Za-z0-9' | head -c 53) >> _env echo 'WORDPRESS_NONCE_SALT='$(cat /dev/urandom | LC_ALL=C tr -dc 'A-Za-z0-9' | head -c 53) >> _env echo >> _env echo '# Nginx LetsEncrypt (ACME) config' >> _env echo '# be sure ACME_DOMAIN host and WORDPRESS_URL host are the same, if using automated SSL via LetsEncrypt' >> _env echo '# ACME_ENV defaults to "staging", uncomment following ACME_ENV line to switch to LetsEncrypt production endpoint' >> _env echo '#ACME_DOMAIN='nginx.svc.${TRITON_ACCOUNT}.${TRITON_DC}.triton.zone >> _env echo '#ACME_ENV=production' >> _env echo >> _env echo '# Environment variables for MySQL service' >> _env echo '# WordPress database/WPDB information' >> _env echo 'MYSQL_USER=wpdbuser' >> _env echo 'MYSQL_PASSWORD='$(cat /dev/urandom | LC_ALL=C tr -dc 'A-Za-z0-9' | head -c 7) >> _env echo 'MYSQL_DATABASE=wp' >> _env echo '# MySQL replication user, should be different from above' >> _env echo 'MYSQL_REPL_USER=repluser' >> _env echo 'MYSQL_REPL_PASSWORD='$(cat /dev/urandom | LC_ALL=C tr -dc 'A-Za-z0-9' | head -c 7) >> _env echo >> _env echo '# Environment variables for backups to Manta' >> _env echo 'MANTA_URL=https://us-east.manta.joyent.com' >> _env echo 'MANTA_BUCKET=//stor/ # an existing Manta bucket' >> _env echo 'MANTA_USER= # a user with access to that bucket' >> _env echo 'MANTA_SUBUSER=' >> _env echo 'MANTA_ROLE=' >> _env # MANTA_KEY_ID must be the md5 formatted key fingerprint. A SHA256 will result in errors. set +o pipefail # The -E option was added to ssh-keygen recently; if it doesn't work, then # assume we're using an older version of ssh-keygen that only outputs MD5 fingerprints ssh-keygen -yl -E md5 -f ${MANTA_PRIVATE_KEY_PATH} > /dev/null 2>&1 if [ $? -eq 0 ]; then echo MANTA_KEY_ID=$(ssh-keygen -yl -E md5 -f ${MANTA_PRIVATE_KEY_PATH} | awk '{print substr($2,5)}') >> _env else echo MANTA_KEY_ID=$(ssh-keygen -yl -f ${MANTA_PRIVATE_KEY_PATH} | awk '{print $2}') >> _env fi set -o pipefail # munge the private key so that we can pass it into an env var sanely # and then unmunge it in our startup script echo MANTA_PRIVATE_KEY=$(cat ${MANTA_PRIVATE_KEY_PATH} | tr '\n' '#') >> _env echo >> _env echo '# Consul discovery via Triton CNS' >> _env echo CONSUL=wp-consul.svc.${TRITON_ACCOUNT}.${TRITON_DC}.cns.joyent.com >> _env echo >> _env echo 'Edit the _env file to confirm and set your desired configuration details' else echo 'Existing _env file found, exiting' exit fi } # --------------------------------------------------- # parse arguments # Get function list funcs=($(declare -F -p | cut -d " " -f 3)) until if [ ! -z "$1" ]; then # check if the first arg is a function in this file, or use a default if [[ " ${funcs[@]} " =~ " $1 " ]]; then cmd=$1 shift 1 else cmd="envcheck" fi $cmd "$@" if [ $? == 127 ]; then help fi exit else help fi do echo done ================================================ FILE: var/www/html/.htaccess ================================================ RewriteEngine On RewriteBase / RewriteRule ^index\.php$ - [L] RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteRule . /index.php [L] ================================================ FILE: var/www/html/content/mu-plugins/index.php ================================================ ================================================ FILE: var/www/html/content/mu-plugins/register-theme-directory.php ================================================ add_database(array( 'host' => '{{.Address}}:{{.Port}}', 'user' => DB_USER, 'password' => DB_PASSWORD, 'name' => DB_NAME, 'write' => 1, 'read' => 0, 'dataset' => 'global', ));{{end}} {{end}} {{if service "mysql"}} /** * The DB replicas * * These are read-only. */ {{range service "mysql"}} $wpdb->add_database(array( 'host' => '{{.Address}}:{{.Port}}', 'user' => DB_USER, 'password' => DB_PASSWORD, 'name' => DB_NAME, 'write' => 0, 'read' => 1, 'dataset' => 'global', 'timeout' => 0.2, ));{{end}} {{else}} /** * No DB replicas are healthy, so we're adding the master in as a read-host. */ {{range service "mysql-primary"}} $wpdb->add_database(array( 'host' => '{{.Address}}:{{.Port}}', 'user' => DB_USER, 'password' => DB_PASSWORD, 'name' => DB_NAME, 'write' => 0, 'read' => 1, 'dataset' => 'global', ));{{end}} {{end}} /** HyperDB configuration documentation and examples **/ /** * Introduction to HyperDB configuration * * HyperDB can manage connections to a large number of databases. Queries are * distributed to appropriate servers by mapping table names to datasets. * * A dataset is defined as a group of tables that are located in the same * database. There may be similarly-named databases containing different * tables on different servers. There may also be many replicas of a database * on different servers. The term "dataset" removes any ambiguity. Consider a * dataset as a group of tables that can be mirrored on many servers. * * Configuring HyperDB involves defining databases and datasets. Defining a * database involves specifying the server connection details, the dataset it * contains, and its capabilities and priorities for reading and writing. * Defining a dataset involves specifying its exact table names or registering * one or more callback functions that translate table names to datasets. */ /** Variable settings **/ /** * save_queries (bool) * This is useful for debugging. Queries are saved in $wpdb->queries. It is not * a constant because you might want to use it momentarily. * Default: false */ $wpdb->save_queries = false; /** * persistent (bool) * This determines whether to use mysql_connect or mysql_pconnect. The effects * of this setting may vary and should be carefully tested. * Default: false */ $wpdb->persistent = false; /** * max_connections (int) * This is the number of mysql connections to keep open. Increase if you expect * to reuse a lot of connections to different servers. This is ignored if you * enable persistent connections. * Default: 10 */ $wpdb->max_connections = 10; /** * check_tcp_responsiveness * Enables checking TCP responsiveness by fsockopen prior to mysql_connect or * mysql_pconnect. This was added because PHP's mysql functions do not provide * a variable timeout setting. Disabling it may improve average performance by * a very tiny margin but lose protection against connections failing slowly. * Default: true */ $wpdb->check_tcp_responsiveness = true; /** Configuration Functions **/ /** * $wpdb->add_database( $database ); * * $database is an associative array with these parameters: * host (required) Hostname with optional :port. Default port is 3306. * user (required) MySQL user name. * password (required) MySQL user password. * name (required) MySQL database name. * read (optional) Whether server is readable. Default is 1 (readable). * Also used to assign preference. See "Network topology". * write (optional) Whether server is writable. Default is 1 (writable). * Also used to assign preference in multi-master mode. * dataset (optional) Name of dataset. Default is 'global'. * timeout (optional) Seconds to wait for TCP responsiveness. Default is 0.2 * lag_threshold (optional) The minimum lag on a slave in seconds before we consider it lagged. * Set null to disable. When not set, the value of $wpdb->default_lag_threshold is used. */ /** * $wpdb->add_table( $dataset, $table ); * * $dataset and $table are strings. */ /** * $wpdb->add_callback( $callback, $callback_group = 'dataset' ); * * $callback is a callable function or method. $callback_group is the * group of callbacks, this $callback belongs to. * * Callbacks are executed in the order in which they are registered until one * of them returns something other than null. * * The default $callback_group is 'dataset'. Callback in this group * will be called with two arguments and expected to compute a dataset or return null. * $dataset = $callback($table, &$wpdb); * * Anything evaluating to false will cause the query to be aborted. * * For more complex setups, the callback may be used to overwrite properties of * $wpdb or variables within hyperdb::connect_db(). If a callback returns an * array, HyperDB will extract the array. It should be an associative array and * it should include a $dataset value corresponding to a database added with * $wpdb->add_database(). It may also include $server, which will be extracted * to overwrite the parameters of each randomly selected database server prior * to connection. This allows you to dynamically vary parameters such as the * host, user, password, database name, lag_threshold and TCP check timeout. */ /** Masters and slaves * * A database definition can include 'read' and 'write' parameters. These * operate as boolean switches but they are typically specified as integers. * They allow or disallow use of the database for reading or writing. * * A master database might be configured to allow reading and writing: * 'write' => 1, * 'read' => 1, * while a slave would be allowed only to read: * 'write' => 0, * 'read' => 1, * * It might be advantageous to disallow reading from the master, such as when * there are many slaves available and the master is very busy with writes. * 'write' => 1, * 'read' => 0, * HyperDB tracks the tables that it has written since instantiation and sending * subsequent read queries to the same server that received the write query. * Thus a master set up this way will still receive read queries, but only * subsequent to writes. */ /** * Network topology / Datacenter awareness * * When your databases are located in separate physical locations there is * typically an advantage to connecting to a nearby server instead of a more * distant one. The read and write parameters can be used to place servers into * logical groups of more or less preferred connections. Lower numbers indicate * greater preference. * * This configuration instructs HyperDB to try reading from one of the local * slaves at random. If that slave is unreachable or refuses the connection, * the other slave will be tried, followed by the master, and finally the * remote slaves in random order. * Local slave 1: 'write' => 0, 'read' => 1, * Local slave 2: 'write' => 0, 'read' => 1, * Local master: 'write' => 1, 'read' => 2, * Remote slave 1: 'write' => 0, 'read' => 3, * Remote slave 2: 'write' => 0, 'read' => 3, * * In the other datacenter, the master would be remote. We would take that into * account while deciding where to send reads. Writes would always be sent to * the master, regardless of proximity. * Local slave 1: 'write' => 0, 'read' => 1, * Local slave 2: 'write' => 0, 'read' => 1, * Remote slave 1: 'write' => 0, 'read' => 2, * Remote slave 2: 'write' => 0, 'read' => 2, * Remote master: 'write' => 1, 'read' => 3, * * There are many ways to achieve different configurations in different * locations. You can deploy different config files. You can write code to * discover the web server's location, such as by inspecting $_SERVER or * php_uname(), and compute the read/write parameters accordingly. An example * appears later in this file using the legacy function add_db_server(). */ /** * Slaves lag awareness * * HyperDB accommodates slave lag by making decisions, based on the defined lag * threshold. If the lag threshold is not set, it will ignore the slave lag. * Otherwise, it will try to find a non-lagged slave, before connecting to a lagged one. * * A slave is considered lagged, if it's replication lag is bigger than the lag threshold * you have defined in $wpdb->$default_lag_threshold or in the per-database settings, using * add_database(). You can also rewrite the lag threshold, by returning * $server['lag_threshold'] variable with the 'dataset' group callbacks. * * HyperDB does not check the lag on the slaves. You have to define two callbacks * callbacks to do that: * * $wpdb->add_callback( $callback, 'get_lag_cache' ); * * and * * $wpdb->add_callback( $callback, 'get_lag' ); * * The first one is called, before connecting to a slave and should return * the replication lag in seconds or false, if unknown, based on $wpdb->lag_cache_key. * * The second callback is called after a connection to a slave is established. * It should return it's replication lag or false, if unknown, * based on the connection in $wpdb->dbhs[ $wpdb->dbhname ]. */ /** Sample Configuration 1: Using the Default Server **/ /** * This is the most basic way to add a server to HyperDB using only the * required parameters: host, user, password, name. * This adds the DB defined in wp-config.php as a read/write server for * the 'global' dataset. (Every table is in 'global' by default.) */ /* $wpdb->add_database(array( 'host' => DB_HOST, // If port is other than 3306, use host:port. 'user' => DB_USER, 'password' => DB_PASSWORD, 'name' => DB_NAME, )); */ /** * This adds the same server again, only this time it is configured as a slave. * The last three parameters are set to the defaults but are shown for clarity. */ /* $wpdb->add_database(array( 'host' => DB_HOST, // If port is other than 3306, use host:port. 'user' => DB_USER, 'password' => DB_PASSWORD, 'name' => DB_NAME, 'write' => 0, 'read' => 1, 'dataset' => 'global', 'timeout' => 0.2, )); */ /** Sample Configuration 2: Partitioning **/ /** * This example shows a setup where the multisite blog tables have been * separated from the global dataset. */ /* $wpdb->add_database(array( 'host' => 'global.db.example.com', 'user' => 'globaluser', 'password' => 'globalpassword', 'name' => 'globaldb', )); $wpdb->add_database(array( 'host' => 'blog.db.example.com', 'user' => 'bloguser', 'password' => 'blogpassword', 'name' => 'blogdb', 'dataset' => 'blog', )); $wpdb->add_callback('my_db_callback'); function my_db_callback($query, $wpdb) { // Multisite blog tables are "{$base_prefix}{$blog_id}_*" if ( preg_match("/^{$wpdb->base_prefix}\d+_/i", $wpdb->table) ) return 'blog'; } */ /** Sample helper functions from WordPress.com **/ /** * This is back-compatible with an older config style. It is for convenience. * lhost, part, and dc were removed from hyperdb because the read and write * parameters provide enough power to achieve the desired effects via config. * * @param string $dataset Datset: the name of the dataset. Just use "global" if you don't need horizontal partitioning. * @param int $part Partition: the vertical partition number (1, 2, 3, etc.). Use "0" if you don't need vertical partitioning. * @param string $dc Datacenter: where the database server is located. Airport codes are convenient. Use whatever. * @param int $read Read group: tries all servers in lowest number group before trying higher number group. Typical: 1 for slaves, 2 for master. This will cause reads to go to slaves unless all slaves are unreachable. Zero for no reads. * @param bool $write Write flag: is this server writable? Works the same as $read. Typical: 1 for master, 0 for slaves. * @param string $host Internet address: host:port of server on internet. * @param string $lhost Local address: host:port of server for use when in same datacenter. Leave empty if no local address exists. * @param string $name Database name. * @param string $user Database user. * @param string $password Database password. */ /* function add_db_server($dataset, $part, $dc, $read, $write, $host, $lhost, $name, $user, $password, $timeout = 0.2 ) { global $wpdb; // dc is not used in hyperdb. This produces the desired effect of // trying to connect to local servers before remote servers. Also // increases time allowed for TCP responsiveness check. if ( !empty($dc) && defined(DATACENTER) && $dc != DATACENTER ) { if ( $read ) $read += 10000; if ( $write ) $write += 10000; $timeout = 0.7; } // You'll need a hyperdb::add_callback() callback function to use partitioning. // $wpdb->add_callback( 'my_func' ); if ( $part ) $dataset = $dataset . '_' . $part; $database = compact('dataset', 'read', 'write', 'host', 'name', 'user', 'password', 'timeout'); $wpdb->add_database($database); // lhost is not used in hyperdb. This configures hyperdb with an // additional server to represent the local hostname so it tries to // connect over the private interface before the public one. if ( !empty( $lhost ) ) { if ( $read ) $database['read'] = $read - 0.5; if ( $write ) $database['write'] = $write - 0.5; $wpdb->add_database( $database ); } } */ /** * Sample replication lag detection configuration. * * We use mk-heartbeat (http://www.maatkit.org/doc/mk-heartbeat.html) * to detect replication lag. * * This implementation requires the database user * to have read access to the heartbeat table. * * The cache uses shared memory for portability. * Can be modified to work with Memcached, APC and etc. */ /* $wpdb->lag_cache_ttl = 30; $wpdb->shmem_key = ftok( __FILE__, "Y" ); $wpdb->shmem_size = 128 * 1024; $wpdb->add_callback( 'get_lag_cache', 'get_lag_cache' ); $wpdb->add_callback( 'get_lag', 'get_lag' ); function get_lag_cache( $wpdb ) { $segment = shm_attach( $wpdb->shmem_key, $wpdb->shmem_size, 0600 ); $lag_data = @shm_get_var( $segment, 0 ); shm_detach( $segment ); if ( !is_array( $lag_data ) || !is_array( $lag_data[ $wpdb->lag_cache_key ] ) ) return false; if ( $wpdb->lag_cache_ttl < time() - $lag_data[ $wpdb->lag_cache_key ][ 'timestamp' ] ) return false; return $lag_data[ $wpdb->lag_cache_key ][ 'lag' ]; } function get_lag( $wpdb ) { $dbh = $wpdb->dbhs[ $wpdb->dbhname ]; if ( !mysql_select_db( 'heartbeat', $dbh ) ) return false; $result = mysql_query( "SELECT UNIX_TIMESTAMP() - UNIX_TIMESTAMP(ts) AS lag FROM heartbeat LIMIT 1", $dbh ); if ( !$result || false === $row = mysql_fetch_assoc( $result ) ) return false; // Cache the result in shared memory with timestamp $sem_id = sem_get( $wpdb->shmem_key, 1, 0600, 1 ) ; sem_acquire( $sem_id ); $segment = shm_attach( $wpdb->shmem_key, $wpdb->shmem_size, 0600 ); $lag_data = @shm_get_var( $segment, 0 ); if ( !is_array( $lag_data ) ) $lag_data = array(); $lag_data[ $wpdb->lag_cache_key ] = array( 'timestamp' => time(), 'lag' => $row[ 'lag' ] ); shm_put_var( $segment, 0, $lag_data ); shm_detach( $segment ); sem_release( $sem_id ); return $row[ 'lag' ]; } */ // The ending PHP tag is omitted. This is actually safer than including it. ================================================ FILE: var/www/html/inactive-plugins/no-uploads.php ================================================

~`+=,.;:/?|' < /dev/urandom | head -c 64 * * On a Mac you'll need to use: * LC_ALL=C tr -dc 'a-zA-Z0-9!@#$%^&*()-_ []{}<>~`+=,.;:/?|' < /dev/urandom | head -c 64 * * Props to https://gist.github.com/earthgecko/3089509 for some hints there */ define( 'AUTH_KEY', '{{env "WORDPRESS_AUTH_KEY"}}' ); define( 'SECURE_AUTH_KEY', '{{env "WORDPRESS_SECURE_AUTH_KEY"}}' ); define( 'LOGGED_IN_KEY', '{{env "WORDPRESS_LOGGED_IN_KEY"}}' ); define( 'NONCE_KEY', '{{env "WORDPRESS_NONCE_KEY"}}' ); define( 'AUTH_SALT', '{{env "WORDPRESS_AUTH_SALT"}}' ); define( 'SECURE_AUTH_SALT', '{{env "WORDPRESS_SECURE_AUTH_SALT"}}' ); define( 'LOGGED_IN_SALT', '{{env "WORDPRESS_LOGGED_IN_SALT"}}' ); define( 'NONCE_SALT', '{{env "WORDPRESS_NONCE_SALT"}}' ); /** * Table prefix * Change this if you have multiple installs in the same database */ $table_prefix = 'wp_'; /** * Language * Leave blank for American English */ define( 'WPLANG', '' ); /** * Hide errors */ ini_set( 'display_errors', {{key_or_default "wordpress/config/WP_DEBUG_DISPLAY" "0" | parseInt}} ); define( 'WP_DEBUG_DISPLAY', {{key_or_default "wordpress/config/WP_DEBUG_DISPLAY" "0" | parseBool}} ); /** * Debug mode * Debugging? Enable these. */ /** * Load the Memcached config */ include( dirname( __FILE__ ) . '/memcached-config.php' ); /** * Bootstrap WordPress */ if ( ! defined( 'ABSPATH' ) ) { define( 'ABSPATH', dirname( __FILE__ ) . '/wordpress/' ); } require_once( ABSPATH . 'wp-settings.php' ); // The ending PHP tag is omitted. This is actually safer than including it.