├── .github └── CODEOWNERS ├── .gitignore ├── .lando.yml.ejs ├── README.md ├── docker ├── dev-tools │ ├── Dockerfile │ ├── add-site.sh │ ├── build.sh │ ├── setup.sh │ ├── wp-config-defaults.php │ ├── wp-config-multisite.php.tpl │ └── wp-config.php.tpl ├── nginx │ ├── Dockerfile │ ├── build.sh │ └── default.conf ├── php-fpm │ ├── Dockerfile │ ├── build.sh │ ├── php.ini │ ├── wp-cli.yaml │ └── xdebug.ini └── statsd │ ├── Dockerfile │ ├── build.sh │ └── statsd-config.js ├── package-lock.json ├── package.json └── vipdev.js /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/en/github/creating-cloning-and-archiving-repositories/about-code-owners 2 | * @Automattic/vip-platform-cantina 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /dev-* 2 | node_modules 3 | .idea 4 | -------------------------------------------------------------------------------- /.lando.yml.ejs: -------------------------------------------------------------------------------- 1 | name: vipdev<%= siteSlug %> 2 | env_file: 3 | - .env 4 | proxy: 5 | nginx: 6 | - <%= siteSlug %>.vipdev.lndo.site 7 | <% if ( multisite ) { %> 8 | - '*.<%= siteSlug %>.vipdev.lndo.site' 9 | <% } %> 10 | services: 11 | 12 | devtools: 13 | type: compose 14 | services: 15 | image: wpvipdev/dev-tools:0.4 16 | command: sleep infinity 17 | volumes: 18 | - devtools:/dev-tools 19 | volumes: 20 | devtools: {} 21 | 22 | nginx: 23 | type: compose 24 | services: 25 | image: wpvipdev/nginx:1.19.2-2 26 | command: nginx -g "daemon off;" 27 | volumes: 28 | <% wpVolumes() %> 29 | 30 | php: 31 | type: compose 32 | services: 33 | image: wpvipdev/php-fpm:<%= phpVersion %> 34 | command: php-fpm 35 | working_dir: /wp 36 | volumes: 37 | - type: volume 38 | source: devtools 39 | target: /dev-tools 40 | volume: 41 | nocopy: true 42 | <% wpVolumes() %> 43 | 44 | run_as_root: 45 | - sh /dev-tools/setup.sh database root "http://<%= siteSlug %>.vipdev.lndo.site/" "<%= wpTitle %>" <% if ( multisite ) { %> <%= siteSlug %>.vipdev.lndo.site <% } %> 46 | 47 | database: 48 | type: mariadb 49 | #portforward: 13306 50 | 51 | memcached: 52 | type: memcached:1.5.12 53 | 54 | #mailhog: 55 | # type: mailhog 56 | # hogfrom: 57 | # - php 58 | 59 | phpmyadmin: 60 | type: phpmyadmin 61 | hosts: 62 | - database 63 | 64 | vip-search: 65 | type: elasticsearch:custom 66 | portforward: true 67 | overrides: 68 | image: bitnami/elasticsearch:7.8.0 69 | 70 | statsd: 71 | type: compose 72 | services: 73 | image: wpvipdev/statsd:v0.8.6-1 74 | command: node stats.js /config/statsd-config.js 75 | 76 | # Code containers 77 | 78 | <% if ( wordpress.mode == 'image' ) { %> 79 | wordpress: 80 | type: compose 81 | services: 82 | image: <%= wordpress.image %>:<%= wordpress.tag %> 83 | command: sh -c "rsync -a /wp/ /shared/; sleep infinity" 84 | volumes: 85 | - wordpress:/shared 86 | volumes: 87 | wordpress: {} 88 | <% } %> 89 | 90 | <% if ( muplugins.mode == 'image' ) { %> 91 | mu-plugins: 92 | type: compose 93 | services: 94 | image: <%= muplugins.image %>:<%= muplugins.tag %> 95 | command: sh -c 'rsync -a /mu-plugins/ /shared/; sh /autoupdate.sh /shared' 96 | volumes: 97 | - mu-plugins:/shared 98 | volumes: 99 | mu-plugins: {} 100 | <% } %> 101 | 102 | <% if ( jetpack.mode == 'image' ) { %> 103 | jetpack: 104 | type: compose 105 | services: 106 | image: <%= jetpack.image %>:<%= jetpack.tag %> 107 | command: sh /run.sh 108 | volumes: 109 | - jetpack:/shared 110 | volumes: 111 | jetpack: {} 112 | <% } %> 113 | 114 | <% if ( clientcode.mode == 'image' ) { %> 115 | client-code: 116 | type: compose 117 | services: 118 | image: <%= clientcode.image %>:<%= clientcode.tag %> 119 | command: sleep infinity 120 | volumes: 121 | - clientcode_clientmuplugins:/clientcode/client-mu-plugins 122 | - clientcode_images:/clientcode/images 123 | - clientcode_languages:/clientcode/languages 124 | - clientcode_plugins:/clientcode/plugins 125 | - clientcode_private:/clientcode/private 126 | - clientcode_themes:/clientcode/themes 127 | - clientcode_vipconfig:/clientcode/vip-config 128 | volumes: 129 | clientcode_clientmuplugins: {} 130 | clientcode_images: {} 131 | clientcode_languages: {} 132 | clientcode_plugins: {} 133 | clientcode_private: {} 134 | clientcode_themes: {} 135 | clientcode_vipconfig: {} 136 | <% } %> 137 | 138 | tooling: 139 | wp: 140 | service: php 141 | description: "Run WP-CLI command" 142 | cmd: 143 | - wp 144 | 145 | test: 146 | service: php 147 | description: "Run all tests: lando test" 148 | cmd: 149 | - cd /wp/wp-content/mu-plugins && phpunit 150 | 151 | add-fake-data: 152 | service: php 153 | description: "Add fake data described in '/configs/fixtures/test_fixtures.yml'. You can also use 'wp fixtures' directly to aim it at other files within lando." 154 | cmd: 155 | - wp fixtures load --file=/app/configs/fixtures/test_fixtures.yml 156 | 157 | delete-fake-data: 158 | service: php 159 | description: "Delete all fake data generated by 'wp fixtures'" 160 | cmd: 161 | - wp fixtures delete 162 | 163 | add-site: 164 | service: php 165 | description: "Add site to a multisite installation" 166 | cmd: 167 | - bash /dev-tools/add-site.sh 168 | 169 | db: 170 | service: php 171 | description: "Connect to the DB using mysql client (e.g. allow to run imports)" 172 | cmd: 173 | - mysql -hdatabase -uwordpress -pwordpress wordpress 174 | 175 | <% function wpVolumes() { %> 176 | - ./config:/wp/config 177 | - ./log:/wp/log 178 | <% if ( wordpress.mode == 'image' ) { %> 179 | - type: volume 180 | source: wordpress 181 | target: /wp 182 | volume: 183 | nocopy: true 184 | <% } else { %> 185 | - <%= wordpress.dir %>:/wp 186 | <% } %> 187 | <% if ( muplugins.mode == 'image' ) { %> 188 | - type: volume 189 | source: mu-plugins 190 | target: /wp/wp-content/mu-plugins 191 | volume: 192 | nocopy: true 193 | <% } else { %> 194 | - <%= muplugins.dir %>:/wp/wp-content/mu-plugins 195 | <% } %> 196 | <% if ( jetpack.mode == 'image' ) { %> 197 | - type: volume 198 | source: jetpack 199 | target: /wp/wp-content/mu-plugins/jetpack 200 | volume: 201 | nocopy: true 202 | <% } else if ( jetpack.mode == 'local' ) { %> 203 | - <%= jetpack.dir %>:/wp/wp-content/mu-plugins/jetpack 204 | <% } %> 205 | <% if ( clientcode.mode == 'image' ) { %> 206 | - type: volume 207 | source: clientcode_clientmuplugins 208 | target: /wp/wp-content/client-mu-plugins 209 | volume: 210 | nocopy: true 211 | - type: volume 212 | source: clientcode_images 213 | target: /wp/wp-content/images 214 | volume: 215 | nocopy: true 216 | - type: volume 217 | source: clientcode_languages 218 | target: /wp/wp-content/languages 219 | volume: 220 | nocopy: true 221 | - type: volume 222 | source: clientcode_plugins 223 | target: /wp/wp-content/plugins 224 | volume: 225 | nocopy: true 226 | - type: volume 227 | source: clientcode_private 228 | # FIXME: Do we need this to be out of /wp for local development? 229 | target: /wp/wp-content/private 230 | volume: 231 | nocopy: true 232 | - type: volume 233 | source: clientcode_themes 234 | target: /wp/wp-content/themes 235 | volume: 236 | nocopy: true 237 | - type: volume 238 | source: clientcode_vipconfig 239 | target: /wp/wp-content/vip-config 240 | volume: 241 | nocopy: true 242 | <% } else { %> 243 | - <%= clientcode.dir %>/client-mu-plugins:/wp/wp-content/client-mu-plugins 244 | - <%= clientcode.dir %>/images:/wp/wp-content/images 245 | - <%= clientcode.dir %>/languages:/wp/wp-content/languages 246 | - <%= clientcode.dir %>/plugins:/wp/wp-content/plugins 247 | - <%= clientcode.dir %>/private:/wp/wp-content/private 248 | - <%= clientcode.dir %>/themes:/wp/wp-content/themes 249 | - <%= clientcode.dir %>/vip-config:/wp/wp-content/vip-config 250 | <% } %> 251 | <% } %> 252 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Dev environment with containers 2 | 3 | > ============================== 4 | > 5 | > THIS REPO IS BEING DEPRECATED. 6 | > 7 | > Please use https://docs.wpvip.com/technical-references/vip-local-development-environment/ instead. 8 | > 9 | > ============================== 10 | 11 | This is a PoC of a new model for the dev environment based almost completely on containers. 12 | 13 | It also integrates the ideas of multi-instance support from https://github.com/Automattic/vip-go-mu-dev/pull/32 14 | 15 | ### Creating an instance 16 | 17 | After installing dependencies with `npm install`, you can create a new instance with: 18 | 19 | ``` 20 | ./vipdev.js create 21 | ``` 22 | 23 | This will create the directory `site-` where the final `.lando.yml` file will be located. 24 | 25 | You can set several parameters to tune the instance, e.g: 26 | 27 | ``` 28 | ./vipdev.js create testing \ 29 | --title "Site Title" \ 30 | --php 7.3 \ 31 | --wordpress 5.5.1 \ 32 | --mu-plugins /home/code/vip-go-mu-plugins \ 33 | --client-code /home/code/vip-wordpress-com 34 | ``` 35 | 36 | As you can see, you can choose to use your local clone of `mu-plugins` in case you want to develop on it (by default it will use a container that auto updates the repo). You can also choose the local path to the client code (by default it will use the `vip-go-skeleton` container). 37 | 38 | You can also fetch all required data using the site id: 39 | 40 | ``` 41 | ./vipdev.js create wpvip --site 1513 42 | ``` 43 | 44 | This will obtain the PHP and WordPress version from GOOP, and it will clone the git repo with the client code, putting it in `site-wpvip/clientcode` 45 | 46 | 47 | ### Upgrading an instance 48 | 49 | After an instance has been created, you can upgrade some of its components. E.g: 50 | 51 | ``` 52 | ./vipdev.js upgrade testing \ 53 | --php 7.4 \ 54 | --wordpress 5.5.1 \ 55 | --mu-plugins auto 56 | ``` 57 | 58 | This will rebuild the app containers but without losing any data. 59 | 60 | 61 | ## Common use cases 62 | 63 | NOTE: Default `wp-admin` credentials are `vipgo:password` 64 | 65 | ### MU-plugins DEV 66 | 67 | When you want to have basic site that is using your local mu-plugins code checkout similar to what previous `vip-go-mu-dev` environment used to do: 68 | 69 | ``` 70 | ./vipdev.js create --mu-plugins 71 | ``` 72 | 73 | ### MU-plugins DEV multisite (-m) 74 | 75 | ``` 76 | ./vipdev.js create -m --mu-plugins 77 | ``` 78 | 79 | To add sites: 80 | 81 | ``` 82 | cd dev- 83 | lando add-site --slug=test --title="Test" 84 | ``` 85 | 86 | ### To-Do 87 | 88 | There are a few things that are needed before matching the functionalities of the current lando environment in `mu-dev`: 89 | 90 | - Multisite support [DONE] 91 | - Cron control 92 | - Mu-plugins tests 93 | 94 | 95 | ### Container definitions 96 | 97 | In the directory `docker` you can find the Dockerfiles for all containers used in this environment. They are pushed to the organization `wpvipdev` in dockerhub (all of them are based on open source projects, so they can be public). 98 | 99 | If you need to change anything on the docker images: 100 | 1. Adapt the `Dockerfile` and/or the scripts or config files 101 | 1. Bump the tag version in the corresponding `build.sh` script 102 | 1. Run `sh build.sh`, it will build the new docker image locally 103 | 1. Bump the required version in `.lando.yml.ejs` (but don't commit yet) 104 | 1. Test it in your local dev environment 105 | 1. Once you are happy with it, you can push the image to dockerhub using the script `tools/push-public.sh` in the [vip-docker repo](https://github.com/Automattic/vip-docker/), so it can be made available to any other user 106 | 1. Commit/Push/PR your changes to `.lando.yml.ejs` so the new docker images requirements are distributed 107 | -------------------------------------------------------------------------------- /docker/dev-tools/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM wpvipdev/alpine:3.12.0-2 2 | 3 | COPY setup.sh add-site.sh wp-config* /dev-tools/ 4 | 5 | CMD ["sleep", "infinity"] 6 | -------------------------------------------------------------------------------- /docker/dev-tools/add-site.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | syntax() { 4 | echo "Syntax: add-site.sh --slug= --title=\"\"" 5 | exit 1 6 | } 7 | 8 | # Parse title and slug arguments 9 | arguments=`getopt -o '' -l slug:,title: -- "$@"` 10 | eval set -- "$arguments" 11 | while true; do 12 | case "$1" in 13 | --slug) slug=$2; shift 2;; 14 | --title) title=$2; shift 2;; 15 | --) shift; break;; 16 | esac 17 | done 18 | [ -z "$slug" ] && echo "ERROR: Missing or empty slug argument" && syntax 19 | [ -z "$title" ] && echo "ERROR: Missing or empty title argument" && syntax 20 | 21 | network_domain=`wp --allow-root site list --field=domain | head -n1` 22 | 23 | site_domain=$slug.$network_domain 24 | 25 | echo "Checking if this is a multisite installation..." 26 | wp --allow-root core is-installed --network 27 | [ $? -ne 0 ] && echo "ERROR: Not a multisite' first" && exit 1 28 | 29 | echo "Checking if $site_domain already belongs to another site..." 30 | wp --allow-root --path=/wp site list --field=domain | grep -q "^$site_domain$" 31 | [ $? -eq 0 ] && echo "ERROR: site with domain $site_domain already exists" && exit 1 32 | 33 | echo "Creating the new site..." 34 | wp --allow-root --path=/wp site create --title="$title" --slug="$slug" 35 | 36 | echo 37 | echo "======================================================================" 38 | echo "Site '$title' added correctly" 39 | echo 40 | echo "You can access it using these URLs:" 41 | echo " http://$site_domain/" 42 | echo " http://$site_domain/wp-admin/" 43 | echo "======================================================================" 44 | -------------------------------------------------------------------------------- /docker/dev-tools/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | tag=0.5 4 | 5 | docker build -t wpvipdev/dev-tools:$tag . 6 | -------------------------------------------------------------------------------- /docker/dev-tools/setup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | if [ $# -lt 4 ]; then 4 | echo: "Syntax: setup.sh <db_host> <db_admin_user> <wp_domain> <wp_title> [<multisite_domain>]" 5 | exit 1 6 | fi 7 | 8 | db_host=$1 9 | db_admin_user=$2 10 | wp_url=$3 11 | wp_title=$4 12 | multisite_domain=$5 13 | 14 | if [ -r /wp/config/wp-config.php ]; then 15 | echo "Already existing wp-config.php file" 16 | else 17 | cp /dev-tools/wp-config-defaults.php /wp/config/ 18 | cat /dev-tools/wp-config.php.tpl | sed -e "s/%DB_HOST%/$db_host/" > /wp/config/wp-config.php 19 | if [ -n "$multisite_domain" ]; then 20 | cat /dev-tools/wp-config-multisite.php.tpl | sed -e "s/%DOMAIN%/$multisite_domain/" >> /wp/config/wp-config.php 21 | fi 22 | curl -s https://api.wordpress.org/secret-key/1.1/salt/ >> /wp/config/wp-config.php 23 | fi 24 | 25 | echo "Checking for database connectivity..." 26 | echo "SELECT 'testing_db'" | mysql -h $db_host -u wordpress -pwordpress wordpress 27 | if [ $? -ne 0 ]; then 28 | echo "No WordPress database exists, provisioning..." 29 | echo "GRANT ALL ON *.* TO 'wordpress'@'localhost' IDENTIFIED BY 'wordpress' WITH GRANT OPTION;" | mysql -h $db_host -u root 30 | echo "GRANT ALL ON *.* TO 'wordpress'@'%' IDENTIFIED BY 'wordpress' WITH GRANT OPTION;" | mysql -h $db_host -u $db_admin_user 31 | echo "CREATE DATABASE wordpress;" | mysql -h $db_host -u $db_admin_user 32 | fi 33 | 34 | echo "Checking for WordPress installation..." 35 | wp --allow-root option get siteurl 36 | if [ $? -ne 0 ]; then 37 | echo "No installation found, installing WordPress..." 38 | if [ -n "$multisite_domain" ]; then 39 | wp core multisite-install \ 40 | --path=/wp \ 41 | --allow-root \ 42 | --url="$wp_url" \ 43 | --title="$wp_title" \ 44 | --admin_user="vipgo" \ 45 | --admin_email="vip@localhost.local" \ 46 | --admin_password="password" \ 47 | --skip-email \ 48 | --skip-plugins \ 49 | --subdomains \ 50 | --skip-config #2>/dev/null 51 | else 52 | wp core install \ 53 | --path=/wp \ 54 | --allow-root \ 55 | --url="$wp_url" \ 56 | --title="$wp_title" \ 57 | --admin_user="vipgo" \ 58 | --admin_email="vip@localhost.local" \ 59 | --admin_password="password" \ 60 | --skip-email \ 61 | --skip-plugins #2>/dev/null 62 | fi 63 | 64 | wp --allow-root elasticpress delete-index 65 | wp --allow-root elasticpress index --setup 66 | 67 | wp --allow-root user add-cap 1 view_query_monitor 68 | fi 69 | -------------------------------------------------------------------------------- /docker/dev-tools/wp-config-defaults.php: -------------------------------------------------------------------------------- 1 | <?php 2 | 3 | /** 4 | * These are baseline configs that are identical across all Go environments. 5 | */ 6 | 7 | /** 8 | * Read-only filesystem 9 | */ 10 | if ( ! defined( 'DISALLOW_FILE_EDIT' ) ) { 11 | define( 'DISALLOW_FILE_EDIT', true ); 12 | } 13 | 14 | if ( ! defined( 'DISALLOW_FILE_MODS' ) ) { 15 | define( 'DISALLOW_FILE_MODS', true ); 16 | } 17 | 18 | if ( ! defined( 'AUTOMATIC_UPDATER_DISABLED' ) ) { 19 | define( 'AUTOMATIC_UPDATER_DISABLED', true ); 20 | } 21 | 22 | // Server limits 23 | if ( ! defined( 'WP_MAX_MEMORY_LIMIT' ) ) { 24 | define( 'WP_MAX_MEMORY_LIMIT', '512M' ); 25 | } 26 | 27 | /** 28 | * Error Handler 29 | * 30 | * Load custom error logging functions, if available. 31 | */ 32 | if ( file_exists( ABSPATH . '/wp-content/mu-plugins/lib/wpcom-error-handler/wpcom-error-handler.php' ) ) { 33 | require_once ABSPATH . '/wp-content/mu-plugins/lib/wpcom-error-handler/wpcom-error-handler.php'; 34 | } 35 | 36 | /** 37 | * Cron Control 38 | */ 39 | if ( ! defined( 'WPCOM_VIP_LOAD_CRON_CONTROL_LOCALLY' ) ) { 40 | define( 'WPCOM_VIP_LOAD_CRON_CONTROL_LOCALLY', true ); 41 | } 42 | 43 | if ( ! defined( 'WP_CRON_CONTROL_SECRET' ) ) { 44 | define( 'WP_CRON_CONTROL_SECRET', 'this-is-a-secret' ); 45 | } 46 | 47 | /** 48 | * VIP Env variables 49 | */ 50 | if ( ! defined( 'WPCOM_IS_VIP_ENV' ) ) { 51 | define( 'WPCOM_IS_VIP_ENV', true ); 52 | } 53 | 54 | if ( ! defined( 'A8C_PROXIED_REQUEST' ) ) { 55 | define( 'A8C_PROXIED_REQUEST', true ); 56 | } 57 | 58 | if ( ! defined( 'FILES_CLIENT_SITE_ID' ) ) { 59 | define( 'FILES_CLIENT_SITE_ID', 200508 ); 60 | } 61 | 62 | if ( ! defined( 'VIP_GO_APP_ENVIRONMENT' ) ) { 63 | define( 'VIP_GO_APP_ENVIRONMENT', 'local' ); 64 | } 65 | 66 | /** 67 | * VIP Config 68 | */ 69 | if ( file_exists( ABSPATH . '/wp-content/vip-config/vip-config.php' ) ) { 70 | require_once( ABSPATH . '/wp-content/vip-config/vip-config.php' ); 71 | } 72 | 73 | /** 74 | * VIP Search 75 | */ 76 | if ( ! defined( 'USE_VIP_ELASTICSEARCH' ) ) { 77 | define( 'USE_VIP_ELASTICSEARCH', true ); 78 | } 79 | 80 | if ( ! defined( 'VIP_ENABLE_ELASTICSEARCH_QUERY_INTEGRATION' ) ) { 81 | define( 'VIP_ENABLE_ELASTICSEARCH_QUERY_INTEGRATION', true ); 82 | } 83 | 84 | if ( ! defined( 'VIP_ELASTICSEARCH_ENDPOINTS' ) ) { 85 | define( 'VIP_ELASTICSEARCH_ENDPOINTS', [ 86 | 'http://vip-search:9200', 87 | ] ); 88 | } 89 | 90 | if ( ! defined( 'VIP_ELASTICSEARCH_USERNAME' ) ) { 91 | define( 'VIP_ELASTICSEARCH_USERNAME', 'test_user' ); 92 | } 93 | 94 | if ( ! defined( 'VIP_ELASTICSEARCH_PASSWORD' ) ) { 95 | define( 'VIP_ELASTICSEARCH_PASSWORD', 'test_password' ); 96 | } 97 | 98 | /** 99 | * StatsD 100 | */ 101 | if ( ! defined( 'VIP_STATSD_HOST' ) ) { 102 | define( 'VIP_STATSD_HOST', 'statsd' ); 103 | } 104 | if ( ! defined( 'VIP_STATSD_PORT' ) ) { 105 | define( 'VIP_STATSD_PORT', 8126 ); 106 | } 107 | -------------------------------------------------------------------------------- /docker/dev-tools/wp-config-multisite.php.tpl: -------------------------------------------------------------------------------- 1 | define( 'WP_ALLOW_MULTISITE', true ); 2 | define( 'MULTISITE', true ); 3 | define( 'SUBDOMAIN_INSTALL', true ); 4 | define( 'DOMAIN_CURRENT_SITE', '%DOMAIN%' ); 5 | define( 'PATH_CURRENT_SITE', '/' ); 6 | define( 'SITE_ID_CURRENT_SITE', 1 ); 7 | define( 'BLOG_ID_CURRENT_SITE', 1 ); 8 | 9 | -------------------------------------------------------------------------------- /docker/dev-tools/wp-config.php.tpl: -------------------------------------------------------------------------------- 1 | <?php 2 | 3 | define( 'DB_NAME', 'wordpress' ); 4 | define( 'DB_USER', 'wordpress' ); 5 | define( 'DB_PASSWORD', 'wordpress' ); 6 | define( 'DB_HOST', '%DB_HOST%' ); 7 | 8 | define( 'DB_CHARSET', 'utf8' ); 9 | define( 'DB_COLLATE', '' ); 10 | 11 | $table_prefix = 'wp_'; 12 | 13 | $memcached_servers = array ( 14 | 'default' => 15 | array ( 16 | 0 => 'memcached:11211', 17 | ), 18 | ); 19 | 20 | if ( ! defined( 'WP_DEBUG' ) ) { 21 | define( 'WP_DEBUG', true ); 22 | } 23 | 24 | if ( ! defined( 'WP_DEBUG_LOG' ) ) { 25 | define( 'WP_DEBUG_LOG', '/wp/log/debug.log' ); 26 | } 27 | 28 | if ( ! defined( 'WP_DEBUG_DISPLAY' ) ) { 29 | define( 'WP_DEBUG_DISPLAY', false ); 30 | } 31 | 32 | require( __DIR__ . '/wp-config-defaults.php' ); 33 | -------------------------------------------------------------------------------- /docker/nginx/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM nginx:1.19.2-alpine 2 | 3 | COPY default.conf /etc/nginx/conf.d/default.conf 4 | 5 | CMD ["nginx", "-g", "daemon off;"] 6 | -------------------------------------------------------------------------------- /docker/nginx/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Please, keep the same version as the original nginx image, 4 | # increasing the suffix if needed 5 | tag=1.19.2-2 6 | 7 | docker build -t wpvipdev/nginx:$tag . 8 | -------------------------------------------------------------------------------- /docker/nginx/default.conf: -------------------------------------------------------------------------------- 1 | server { 2 | 3 | listen 80 default_server; 4 | 5 | server_name localhost; 6 | 7 | #ssl_certificate /certs/cert.crt; 8 | #ssl_certificate_key /certs/cert.key; 9 | #ssl_verify_client off; 10 | 11 | port_in_redirect off; 12 | client_max_body_size 100M; 13 | 14 | ## Your only path reference. 15 | root /wp; 16 | 17 | ## This should be in your http block and if it is, it's not needed here. 18 | index index.php; 19 | 20 | location = /favicon.ico { 21 | log_not_found off; 22 | access_log off; 23 | } 24 | 25 | location = /robots.txt { 26 | allow all; 27 | log_not_found off; 28 | access_log off; 29 | } 30 | 31 | location / { 32 | # This is cool because no php is touched for static content. 33 | # include the "?$args" part so non-default permalinks doesn't break when using query string 34 | try_files $uri $uri/ /index.php?$args; 35 | } 36 | 37 | location ~ \.php$ { 38 | #NOTE: You should have "cgi.fix_pathinfo = 0;" in php.ini 39 | fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; 40 | fastcgi_param PATH_TRANSLATED $document_root$fastcgi_script_name; 41 | include fastcgi_params; 42 | fastcgi_intercept_errors on; 43 | fastcgi_pass php:9000; 44 | fastcgi_buffers 16 16k; 45 | fastcgi_buffer_size 32k; 46 | fastcgi_connect_timeout 3000s; 47 | fastcgi_send_timeout 3000s; 48 | fastcgi_read_timeout 3000s; 49 | } 50 | 51 | location ~ ^/_static/ { 52 | include fastcgi_params; 53 | fastcgi_param SCRIPT_FILENAME $document_root/wp-content/mu-plugins/http-concat/ngx-http-concat.php; 54 | fastcgi_pass php:9000; 55 | } 56 | 57 | location ~* \.(png|jpg|jpeg|gif|ico)$ { 58 | expires max; 59 | log_not_found off; 60 | } 61 | 62 | location ~* \.(js|css)$ { 63 | expires -1; 64 | log_not_found off; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /docker/php-fpm/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM php:7.4-fpm-alpine 2 | 3 | RUN apk update && \ 4 | apk add --no-cache less bash mariadb-client && \ 5 | apk add --no-cache --virtual build-deps $PHPIZE_DEPS build-base zlib-dev libffi-dev openssl-dev && \ 6 | yes '' | pecl install -f memcache && \ 7 | docker-php-ext-install mysqli && \ 8 | docker-php-ext-enable memcache && \ 9 | wget -O /usr/local/bin/phpunit https://phar.phpunit.de/phpunit-7.phar && \ 10 | chmod a+x /usr/local/bin/phpunit && \ 11 | wget -O /usr/local/bin/wp https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar && \ 12 | chmod a+x /usr/local/bin/wp && \ 13 | pecl install xdebug && \ 14 | docker-php-ext-enable xdebug && \ 15 | apk del build-deps 16 | 17 | ENV WP_CLI_CONFIG_PATH=/config/wp-cli.yaml 18 | 19 | COPY wp-cli.yaml /config/wp-cli.yaml 20 | 21 | COPY php.ini /usr/local/etc/php/php.ini 22 | 23 | COPY xdebug.ini /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini 24 | 25 | CMD ["php-fpm"] 26 | -------------------------------------------------------------------------------- /docker/php-fpm/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | tag=7.4.1 4 | 5 | docker build -t wpvipdev/php-fpm:$tag . 6 | -------------------------------------------------------------------------------- /docker/php-fpm/php.ini: -------------------------------------------------------------------------------- 1 | [PHP] 2 | 3 | ;;;;;;;;;;;;;;; 4 | ; PHP Globals ; 5 | ;;;;;;;;;;;;;;; 6 | 7 | short_open_tag = Off 8 | output_buffering = 4096 9 | allow_call_time_pass_reference = Off 10 | request_order = "GP" 11 | register_long_arrays = Off 12 | register_argc_argv = Off 13 | magic_quotes_gpc = Off 14 | enable_dl = Off 15 | allow_url_fopen = On 16 | realpath_cache_size = "800K" 17 | realpath_cache_ttl = "86400" 18 | disable_functions = 19 | sendmail_path=/bin/true 20 | ;sendmail_path = "/usr/local/bin/mhsendmail --smtp-addr='sendmailhog:1025'" 21 | 22 | [Date] 23 | date.timezone = "UTC" 24 | 25 | ;;;;;;;;;;;;;;;;;;;;;; 26 | ;; PACKAGE SETTINGS ;; 27 | ;;;;;;;;;;;;;;;;;;;;;; 28 | 29 | ; Xdebug 30 | xdebug.max_nesting_level = 512 31 | xdebug.remote_autostart = 1 32 | xdebug.show_exception_trace = 0 33 | xdebug.collect_params = 0 34 | 35 | ; Extra config to allow enabling profiler 36 | xdebug.profiler_output_dir = /wp 37 | xdebug.profiler_output_name = xdebug.%t.out 38 | xdebug.profiler_enable_trigger = 1 39 | 40 | ; Globals 41 | expose_php = on 42 | max_execution_time = 90 43 | max_input_time = 900 44 | max_input_vars = 10000 45 | memory_limit = 512M 46 | upload_max_filesize = 100M 47 | post_max_size = 100M 48 | error_reporting = E_ALL & ~E_DEPRECATED 49 | ignore_repeated_errors = on 50 | html_errors = off 51 | display_errors = on 52 | log_errors = on 53 | -------------------------------------------------------------------------------- /docker/php-fpm/wp-cli.yaml: -------------------------------------------------------------------------------- 1 | path: /wp 2 | -------------------------------------------------------------------------------- /docker/php-fpm/xdebug.ini: -------------------------------------------------------------------------------- 1 | [XDebug] 2 | zend_extension=xdebug.so 3 | 4 | xdebug.mode = debug 5 | xdebug.start_with_request=yes 6 | 7 | #Replace host.docker.internal to your computers IP address if linux 8 | xdebug.client_host=host.docker.internal 9 | 10 | -------------------------------------------------------------------------------- /docker/statsd/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM statsd/statsd:v0.8.6 2 | 3 | COPY statsd-config.js /config/statsd-config.js 4 | 5 | CMD ["node", "/usr/src/app/stats.js", "/config/statsd-config.js"] 6 | -------------------------------------------------------------------------------- /docker/statsd/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Please, keep the same version as the original statsd image, 4 | # increasing the suffix if needed 5 | tag=v0.8.6-1 6 | 7 | docker build -t wpvipdev/statsd:$tag . 8 | -------------------------------------------------------------------------------- /docker/statsd/statsd-config.js: -------------------------------------------------------------------------------- 1 | /* 2 | Graphite Required Variable: 3 | 4 | (Leave this unset to avoid sending stats to Graphite. 5 | Set debug flag and leave this unset to run in 'dry' debug mode - 6 | useful for testing statsd clients without a Graphite server.) 7 | 8 | graphiteHost: hostname or IP of Graphite server 9 | 10 | Optional Variables: 11 | 12 | graphitePort: port for the graphite text collector [default: 2003] 13 | graphitePicklePort: port for the graphite pickle collector [default: 2004] 14 | graphiteProtocol: either 'text' or 'pickle' [default: 'text'] 15 | backends: an array of backends to load. Each backend must exist 16 | by name in the directory backends/. If not specified, 17 | the default graphite backend will be loaded. 18 | * example for console and graphite: 19 | [ "./backends/console", "./backends/graphite" ] 20 | 21 | servers: an array of server configurations. 22 | If not specified, the server, address, 23 | address_ipv6, and port top-level configuration 24 | options are used to configure a single server for 25 | backwards-compatibility 26 | Each server configuration supports the following keys: 27 | server: the server to load. The server must exist by name in the directory 28 | servers/. If not specified, the default udp server will be loaded. 29 | * example for tcp server: 30 | "./servers/tcp" 31 | address: address to listen on [default: 0.0.0.0] 32 | address_ipv6: defines if the address is an IPv4 or IPv6 address [true or false, default: false] 33 | port: port to listen for messages on [default: 8125] 34 | socket: (only for tcp servers) path to unix domain socket which will be used to receive 35 | metrics [default: undefinded] 36 | socket_mod: (only for tcp servers) file mode which should be applied to unix domain socket, relevant 37 | only if socket option is used [default: undefined] 38 | 39 | debug: debug flag [default: false] 40 | mgmt_address: address to run the management TCP interface on 41 | [default: 0.0.0.0] 42 | mgmt_port: port to run the management TCP interface on [default: 8126] 43 | title: Allows for overriding the process title. [default: statsd] 44 | if set to false, will not override the process title and let the OS set it. 45 | The length of the title has to be less than or equal to the binary name + cli arguments 46 | NOTE: This does not work on Mac's with node versions prior to v0.10 47 | 48 | healthStatus: default health status to be returned and statsd process starts ['up' or 'down', default: 'up'] 49 | dumpMessages: log all incoming messages 50 | flushInterval: interval (in ms) to flush metrics to each backend 51 | percentThreshold: for time information, calculate the Nth percentile(s) 52 | (can be a single value or list of floating-point values) 53 | negative values mean to use "top" Nth percentile(s) values 54 | [%, default: 90] 55 | flush_counts: send stats_counts metrics [default: true] 56 | 57 | keyFlush: log the most frequently sent keys [object, default: undefined] 58 | interval: how often to log frequent keys [ms, default: 0] 59 | percent: percentage of frequent keys to log [%, default: 100] 60 | log: location of log file for frequent keys [default: STDOUT] 61 | deleteIdleStats: don't send values to graphite for inactive counters, sets, gauges, or timers 62 | as opposed to sending 0. For gauges, this unsets the gauge (instead of sending 63 | the previous value). Can be individually overridden. [default: false] 64 | deleteGauges: don't send values to graphite for inactive gauges, as opposed to sending the previous value [default: false] 65 | gaugesMaxTTL: number of flush cycles to wait before the gauge is marked as inactive, to use in combination with deleteGauges [default: 1] 66 | deleteTimers: don't send values to graphite for inactive timers, as opposed to sending 0 [default: false] 67 | deleteSets: don't send values to graphite for inactive sets, as opposed to sending 0 [default: false] 68 | deleteCounters: don't send values to graphite for inactive counters, as opposed to sending 0 [default: false] 69 | prefixStats: prefix to use for the statsd statistics data for this running instance of statsd [default: statsd] 70 | applies to both legacy and new namespacing 71 | keyNameSanitize: sanitize all stat names on ingress [default: true] 72 | If disabled, it is up to the backends to sanitize keynames 73 | as appropriate per their storage requirements. 74 | 75 | calculatedTimerMetrics: List of timer metrics that will be sent. Default will send all metrics. 76 | To filter on percents and top percents: append '_percent' to the metric name. 77 | Example: calculatedTimerMetrics: ['count', 'median', 'upper_percent', 'histogram'] 78 | 79 | console: 80 | prettyprint: whether to prettyprint the console backend 81 | output [true or false, default: true] 82 | 83 | log: log settings [object, default: undefined] 84 | backend: where to log: stdout or syslog [string, default: stdout] 85 | application: name of the application for syslog [string, default: statsd] 86 | level: log level for [node-]syslog [string, default: LOG_INFO] 87 | 88 | graphite: 89 | legacyNamespace: use the legacy namespace [default: true] 90 | globalPrefix: global prefix to use for sending stats to graphite [default: "stats"] 91 | prefixCounter: graphite prefix for counter metrics [default: "counters"] 92 | prefixTimer: graphite prefix for timer metrics [default: "timers"] 93 | prefixGauge: graphite prefix for gauge metrics [default: "gauges"] 94 | prefixSet: graphite prefix for set metrics [default: "sets"] 95 | globalSuffix: global suffix to use for sending stats to graphite [default: ""] 96 | This is particularly useful for sending per host stats by 97 | settings this value to: require('os').hostname().split('.')[0] 98 | 99 | repeater: an array of hashes of the for host: and port: 100 | that details other statsd servers to which the received 101 | packets should be "repeated" (duplicated to). 102 | e.g. [ { host: '10.10.10.10', port: 8125 }, 103 | { host: 'observer', port: 88125 } ] 104 | 105 | repeaterProtocol: whether to use udp4, udp6, or tcp for repeaters. 106 | ["udp4," "udp6", or "tcp" default: "udp4"] 107 | 108 | histogram: for timers, an array of mappings of strings (to match metrics) and 109 | corresponding ordered non-inclusive upper limits of bins. 110 | For all matching metrics, histograms are maintained over 111 | time by writing the frequencies for all bins. 112 | 'inf' means infinity. A lower limit of 0 is assumed. 113 | default: [], meaning no histograms for any timer. 114 | First match wins. examples: 115 | * histogram to only track render durations, with unequal 116 | class intervals and catchall for outliers: 117 | [ { metric: 'render', bins: [ 0.01, 0.1, 1, 10, 'inf'] } ] 118 | * histogram for all timers except 'foo' related, 119 | equal class interval and catchall for outliers: 120 | [ { metric: 'foo', bins: [] }, 121 | { metric: '', bins: [ 50, 100, 150, 200, 'inf'] } ] 122 | 123 | automaticConfigReload: whether to watch the config file and reload it when it 124 | changes. The default is true. Set this to false to disable. 125 | */ 126 | { 127 | port: 8126 128 | , backends: [ "./backends/console" ] 129 | // Set this to true if you want to show all received messages in the console 130 | , dumpMessages: false 131 | } 132 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vipdev-cli", 3 | "version": "0.0.1", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "ansi-styles": { 8 | "version": "3.2.1", 9 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", 10 | "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", 11 | "requires": { 12 | "color-convert": "^1.9.0" 13 | } 14 | }, 15 | "async": { 16 | "version": "0.9.2", 17 | "resolved": "https://registry.npmjs.org/async/-/async-0.9.2.tgz", 18 | "integrity": "sha1-rqdNXmHB+JlhO/ZL2mbUx48v0X0=" 19 | }, 20 | "balanced-match": { 21 | "version": "1.0.0", 22 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", 23 | "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" 24 | }, 25 | "brace-expansion": { 26 | "version": "1.1.11", 27 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 28 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 29 | "requires": { 30 | "balanced-match": "^1.0.0", 31 | "concat-map": "0.0.1" 32 | } 33 | }, 34 | "chalk": { 35 | "version": "2.4.2", 36 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", 37 | "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", 38 | "requires": { 39 | "ansi-styles": "^3.2.1", 40 | "escape-string-regexp": "^1.0.5", 41 | "supports-color": "^5.3.0" 42 | } 43 | }, 44 | "color-convert": { 45 | "version": "1.9.3", 46 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", 47 | "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", 48 | "requires": { 49 | "color-name": "1.1.3" 50 | } 51 | }, 52 | "color-name": { 53 | "version": "1.1.3", 54 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", 55 | "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" 56 | }, 57 | "commander": { 58 | "version": "6.1.0", 59 | "resolved": "https://registry.npmjs.org/commander/-/commander-6.1.0.tgz", 60 | "integrity": "sha512-wl7PNrYWd2y5mp1OK/LhTlv8Ff4kQJQRXXAvF+uU/TPNiVJUxZLRYGj/B0y/lPGAVcSbJqH2Za/cvHmrPMC8mA==" 61 | }, 62 | "concat-map": { 63 | "version": "0.0.1", 64 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 65 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" 66 | }, 67 | "ejs": { 68 | "version": "3.1.5", 69 | "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.5.tgz", 70 | "integrity": "sha512-dldq3ZfFtgVTJMLjOe+/3sROTzALlL9E34V4/sDtUd/KlBSS0s6U1/+WPE1B4sj9CXHJpL1M6rhNJnc9Wbal9w==", 71 | "requires": { 72 | "jake": "^10.6.1" 73 | } 74 | }, 75 | "escape-string-regexp": { 76 | "version": "1.0.5", 77 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", 78 | "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" 79 | }, 80 | "filelist": { 81 | "version": "1.0.1", 82 | "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.1.tgz", 83 | "integrity": "sha512-8zSK6Nu0DQIC08mUC46sWGXi+q3GGpKydAG36k+JDba6VRpkevvOWUW5a/PhShij4+vHT9M+ghgG7eM+a9JDUQ==", 84 | "requires": { 85 | "minimatch": "^3.0.4" 86 | } 87 | }, 88 | "has-flag": { 89 | "version": "3.0.0", 90 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", 91 | "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" 92 | }, 93 | "jake": { 94 | "version": "10.8.2", 95 | "resolved": "https://registry.npmjs.org/jake/-/jake-10.8.2.tgz", 96 | "integrity": "sha512-eLpKyrfG3mzvGE2Du8VoPbeSkRry093+tyNjdYaBbJS9v17knImYGNXQCUV0gLxQtF82m3E8iRb/wdSQZLoq7A==", 97 | "requires": { 98 | "async": "0.9.x", 99 | "chalk": "^2.4.2", 100 | "filelist": "^1.0.1", 101 | "minimatch": "^3.0.4" 102 | } 103 | }, 104 | "minimatch": { 105 | "version": "3.0.4", 106 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", 107 | "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", 108 | "requires": { 109 | "brace-expansion": "^1.1.7" 110 | } 111 | }, 112 | "supports-color": { 113 | "version": "5.5.0", 114 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", 115 | "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", 116 | "requires": { 117 | "has-flag": "^3.0.0" 118 | } 119 | } 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vipdev-cli", 3 | "description": "Tool to manage VIP development sites", 4 | "version": "0.0.1", 5 | "dependencies": { 6 | "commander": "^6.1.0", 7 | "ejs": "^3.1.5" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /vipdev.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const program = require( 'commander' ); 4 | const fs = require( 'fs' ); 5 | const ejs = require( 'ejs' ); 6 | const cp = require( 'child_process' ); 7 | 8 | const defaultPHP = '7.3'; 9 | 10 | const containerImages = { 11 | 'wordpress': { 12 | image: 'wpvipdev/wordpress', 13 | tag: '5.6', 14 | }, 15 | 'jetpack': { 16 | image: 'wpvipdev/jetpack', 17 | }, 18 | 'muplugins': { 19 | image: 'wpvipdev/mu-plugins', 20 | tag: 'auto', 21 | }, 22 | 'skeleton': { 23 | image: 'wpvipdev/skeleton', 24 | tag: '181a17d9aedf7da73730d65ccef3d8dbf172a5c5', 25 | } 26 | } 27 | 28 | program 29 | .command( 'create <slug>' ) 30 | .description( 'Create a new local development instance' ) 31 | .arguments( 'slug', 'Short name to be used for the lando project and the internal domain' ) 32 | .option( '-t, --title <title>', 'Title for the WordPress site (default: "VIP Dev")' ) 33 | .option( '-m, --multisite', 'Enable multisite install' ) 34 | .option( '-s, --site <site_id>', 'Get all options below for a specific site' ) 35 | .option( '-p, --php <php-version>', 'Use a specific PHP version (default: ' + defaultPHP + ')' ) 36 | .option( '-w, --wordpress <wordpress>', 'Use a specific WordPress version or local directory (default: last stable)' ) 37 | .option( '-u, --mu-plugins <mu-plugins>', 'Use a specific mu-plugins changeset or local directory (default: "auto": last commit in master)' ) 38 | .option( '-j, --jetpack <jetpack>', 'Use a specific Jetpack from a local directory (default: "mu": use the version in mu-plugins)' ) 39 | .option( '-c, --client-code <clientcode>', 'Use the client code from github or a local directory (default: use the VIP skeleton)' ) 40 | .option( '--no-start', 'If provided, don\'t start the Lando environment, just create it' ) 41 | .action( createAction ); 42 | 43 | program 44 | .command( 'upgrade <slug>' ) 45 | .description( 'Upgrade versions for one or more components of a development instance' ) 46 | .arguments( 'slug', 'Name of the development instance' ) 47 | .option( '-p, --php <php-version>', 'Use a specific PHP version (default: ' + defaultPHP + ')' ) 48 | .option( '-w, --wordpress <wordpress>', 'Use a specific WordPress version or local directory' ) 49 | .option( '-u, --mu-plugins <mu-plugins>', 'Use a specific mu-plugins changeset or local directory ("auto" for auto updates)' ) 50 | .option( '-j, --jetpack <jetpack>', 'Use a specific Jetpack from a local directory ("mu" for the version in mu-plugins)' ) 51 | .option( '-c, --client-code <clientcode>', 'Use the client code from github or a local directory' ) 52 | .action( upgradeAction ); 53 | 54 | program.parse( process.argv ); 55 | 56 | async function createAction( slug, options ) { 57 | const instancePath = 'dev-' + slug; 58 | if ( fs.existsSync( instancePath ) ) { 59 | return console.error( 'Instance ' + slug + ' already exists' ); 60 | } 61 | fs.mkdirSync( instancePath ); 62 | 63 | // Fill options if a site is provided 64 | // TODO: Detect incompatible options 65 | if ( options.site ) { 66 | setOptionsForSiteId( options, options.site ); 67 | } 68 | 69 | let instanceData = { 70 | siteSlug: slug, 71 | wpTitle: options.title || 'VIP Dev', 72 | multisite: options.multisite || false, 73 | wordpress: {}, 74 | muplugins: {}, 75 | jetpack: {}, 76 | clientcode: {}, 77 | }; 78 | 79 | updateSiteDataWithOptions( instanceData, options ); 80 | 81 | await prepareLandoEnv( instanceData, instancePath ); 82 | 83 | console.log( instanceData ); 84 | fs.writeFileSync( instancePath + '/instanceData.json', JSON.stringify( instanceData ) ); 85 | 86 | if ( options.start ) { 87 | landoStart( instancePath ); 88 | console.log( 'Lando environment created on directory "' + instancePath + '" and started.' ); 89 | } else { 90 | console.log( 'Lando environment created on directory "' + instancePath + '".' ); 91 | console.log( 'You can cd into that directory and run "lando start"' ); 92 | } 93 | } 94 | 95 | async function upgradeAction( slug, options ) { 96 | const instancePath = 'dev-' + slug; 97 | const instanceData = JSON.parse( fs.readFileSync( instancePath + '/instanceData.json' ) ); 98 | 99 | updateSiteDataWithOptions( instanceData, options ); 100 | 101 | fs.writeFileSync( instancePath + '/instanceData.json', JSON.stringify( instanceData ) ); 102 | 103 | await prepareLandoEnv( instanceData, instancePath ); 104 | 105 | landoRebuild( instancePath ); 106 | } 107 | 108 | function setOptionsForSiteId( options, siteId ) { 109 | let response = cp.execSync( 'vipgo api GET /sites/' + siteId ).toString(); 110 | const siteInfo = JSON.parse( response ); 111 | 112 | options.title = siteInfo.data[0].name + ' (' + siteId + ')'; 113 | 114 | if ( ! options.clientCode ) { 115 | const repo = siteInfo.data[0].source_repo; 116 | const branch = siteInfo.data[0].source_repo_branch; 117 | options.clientCode = 'git@github.com:' + repo + '#' + branch 118 | } 119 | 120 | options.multisite = siteInfo.data[0].is_multisite; 121 | 122 | response = cp.execSync( 'vipgo api GET /sites/' + siteId + '/allocations' ).toString(); 123 | const siteAllocations = JSON.parse( response ); 124 | 125 | siteAllocations.data.forEach( ( allocation ) => { 126 | if ( allocation.container_type_id == 1 ) { 127 | options.wp = allocation.software_stack_name.split( ' ' ).slice( -1 )[0]; 128 | options.php = allocation.container_image_name.split( ' ' ).slice( -1 )[0]; 129 | } 130 | } ); 131 | } 132 | 133 | function updateSiteDataWithOptions( instanceData, options ) { 134 | updatePhpData( instanceData, options.php ); 135 | updateWordPressData( instanceData, options.wordpress ); 136 | updateMuPluginsData( instanceData, options.muPlugins ); 137 | updateJetpackData( instanceData, options.jetpack ); 138 | updateClientCodeData( instanceData, options.clientCode ); 139 | } 140 | 141 | function updatePhpData( instanceData, phpParam ) { 142 | if ( phpParam ) { 143 | instanceData.phpVersion = phpParam; 144 | } else if ( ! instanceData.phpVersion ) { 145 | instanceData.phpVersion = defaultPHP; 146 | } 147 | } 148 | 149 | function updateWordPressData( instanceData, wpParam ) { 150 | if ( wpParam ) { 151 | if ( wpParam.includes( '/' ) ) { 152 | instanceData.wordpress = { 153 | mode: 'local', 154 | dir: wpParam, 155 | } 156 | } else { 157 | instanceData.wordpress = { 158 | mode: 'image', 159 | image: containerImages['wordpress'].image, 160 | tag: wpParam, 161 | } 162 | } 163 | } else if ( ! instanceData.wordpress.mode ) { 164 | instanceData.wordpress = { 165 | mode: 'image', 166 | image: containerImages['wordpress'].image, 167 | tag: containerImages['wordpress'].tag, 168 | } 169 | } 170 | } 171 | 172 | function updateMuPluginsData( instanceData, muParam ) { 173 | if ( muParam ) { 174 | if ( muParam.includes( '/' ) ) { 175 | instanceData.muplugins = { 176 | mode: 'local', 177 | dir: muParam, 178 | } 179 | } else { 180 | instanceData.muplugins = { 181 | mode: 'image', 182 | image: containerImages['muplugins'].image, 183 | tag: muParam, 184 | } 185 | } 186 | } else if ( ! instanceData.muplugins.mode ) { 187 | instanceData.muplugins = { 188 | mode: 'image', 189 | image: containerImages['muplugins'].image, 190 | tag: containerImages['muplugins'].tag, 191 | } 192 | } 193 | } 194 | 195 | function updateJetpackData( instanceData, jpParam ) { 196 | if ( jpParam ) { 197 | if ( jpParam.includes( '/' ) ) { 198 | instanceData.jetpack = { 199 | mode: 'local', 200 | dir: jpParam, 201 | } 202 | } else if ( jpParam === 'mu' ) { 203 | instanceData.jetpack = { 204 | mode: 'inherit', 205 | } 206 | } else { 207 | instanceData.jetpack = { 208 | mode: 'image', 209 | image: containerImages['jetpack'].image, 210 | tag: jpParam, 211 | } 212 | } 213 | } else if ( ! instanceData.jetpack.mode ) { 214 | instanceData.jetpack = { 215 | mode: 'inherit', 216 | } 217 | } 218 | } 219 | 220 | function updateClientCodeData( instanceData, codeParam ) { 221 | if ( codeParam ) { 222 | if ( codeParam.includes( 'github' ) ) { 223 | instanceData.clientcode = { 224 | mode: 'git', 225 | repo: codeParam, 226 | fetched: false, 227 | } 228 | } else { 229 | instanceData.clientcode = { 230 | mode: 'local', 231 | dir: codeParam, 232 | } 233 | } 234 | } else if ( ! instanceData.clientcode.mode ) { 235 | instanceData.clientcode = { 236 | mode: 'image', 237 | image: containerImages['skeleton'].image, 238 | tag: containerImages['skeleton'].tag, 239 | } 240 | } 241 | } 242 | 243 | async function prepareLandoEnv( instanceData, instancePath ) { 244 | if ( instanceData.clientcode.mode == 'git' && ! instanceData.clientcode.fetched ) { 245 | const clonePath = instancePath + '/clientcode'; 246 | 247 | try { 248 | fs.rmdirSync(clonePath, { recursive: true }) 249 | } catch (err) { 250 | const { 251 | code = '' 252 | } = err; 253 | 254 | // rmdir throws an error if the directory does not exist. 255 | // it's not worth wasting a call to fileExists beforehand since the goal is to delete the directory. 256 | if ( -1 === ['ENOENT', 'ENOTDIR'].indexOf(code) ) { 257 | // something else happened, throw the error. 258 | throw err; 259 | } 260 | } 261 | 262 | console.log( 'Cloning client code repo: ' + instanceData.clientcode.repo ); 263 | 264 | let [ repo, branch ] = instanceData.clientcode.repo.split( '#' ); 265 | 266 | let cmd = 'git clone --recurse-submodules ' + repo + ' ' + clonePath; 267 | if ( branch ) { 268 | cmd += ' --branch ' + branch; 269 | } 270 | 271 | cp.execSync( cmd ); 272 | instanceData.clientcode.fetched = true; 273 | instanceData.clientcode.dir = './clientcode'; 274 | } 275 | const landoFile = await ejs.renderFile( '.lando.yml.ejs', instanceData ); 276 | fs.writeFileSync( instancePath + '/.lando.yml', landoFile ); 277 | } 278 | 279 | function landoStart( instancePath ) { 280 | cp.execSync( 'lando start', { cwd: instancePath, stdio: 'inherit' } ); 281 | } 282 | 283 | function landoRebuild( instancePath ) { 284 | cp.execSync( 'lando rebuild -y', { cwd: instancePath, stdio: 'inherit' } ); 285 | } 286 | --------------------------------------------------------------------------------