commit b36df4384a7360199aacbb93412349c43baa4906 Author: jeroenops Date: Tue Nov 13 21:55:59 2018 +0100 Refactored diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..41b775b --- /dev/null +++ b/.gitignore @@ -0,0 +1,66 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +.idea/ + +# C extensions +*.so + +# Distribution / packaging +.Python +env/ +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +*.egg-info/ +.installed.cfg +*.egg + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.cache +nosetests.xml +coverage.xml + +# Translations +*.mo +*.pot + +# Django stuff: +*.log + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + + +# Vim .swp file +*.swp + +# Folder created for Nose testing +bin/ +coverage_report/ +include/ +local/ +man/ diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..a3ec624 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,100 @@ +language: bash + +before_install: + - rm -rf ~/.gnupg + +before_script: + - sudo rm -rf /etc/mysql/ + - sudo bash -c 'echo example.com > /etc/hostname' + - sudo service hostname restart + - sudo apt-get -qq purge mysql* graphviz* + - sudo apt-get -qq autoremove + - sudo apt-get update + +script: + - lsb_release -a + - unset LANG + - sudo bash -c 'echo -e "[user]\n\tname = abc\n\temail = root@localhost.com" > /home/travis/.gitconfig' + - sudo echo "Travis Banch = $TRAVIS_BRANCH" + - sudo apt-get install -y --force-yes git python3-setuptools python3-dev python3-apt + - sudo bash install $TRAVIS_BRANCH + - sudo wo --help + - sudo wo stack install || sudo tail -n50 /var/log/wo/wordops.log + - sudo wo stack install --web || sudo tail -n50 /var/log/wo/wordops.log + - sudo wo stack install --admin || sudo tail -n50 /var/log/wo/wordops.log + + - sudo wo site create html.net --html || sudo tail -n50 /var/log/wo/wordops.log + - sudo wo site create php.com --php || sudo tail -n50 /var/log/wo/wordops.log + - sudo wo site create mysql.com --mysql || sudo tail -n50 /var/log/wo/wordops.log + - sudo wo site create site1.com --wp || sudo tail -n50 /var/log/wo/wordops.log + + - sudo wo site create site2.net --wp --wpsc || sudo tail -n50 /var/log/wo/wordops.log + - sudo wo site create site4.com --wpfc || sudo tail -n50 /var/log/wo/wordops.log + - sudo wo site create site4.net --wp --wpfc || sudo tail -n50 /var/log/wo/wordops.log + - sudo wo site create site4.org --wpfc --wp || sudo tail -n50 /var/log/wo/wordops.log + - sudo wo site create site5.com --wpsubdir || sudo tail -n50 /var/log/wo/wordops.log + + - sudo wo site create site6.com --wpsubdir --wpsc || sudo tail -n50 /var/log/wo/wordops.log + - sudo wo site create site8.com --wpsubdir --wpfc || sudo tail -n50 /var/log/wo/wordops.log + - sudo wo site create site8.net --wpfc --wpsubdir || sudo tail -n50 /var/log/wo/wordops.log + - sudo wo site create site9.com --wpsubdomain || sudo tail -n50 /var/log/wo/wordops.log + + - sudo wo site create site10.org --wpsubdomain --wpsc || sudo tail -n50 /var/log/wo/wordops.log + - sudo wo site create site12.org --wpsubdomain --wpfc || sudo tail -n50 /var/log/wo/wordops.log + - sudo wo site create site12.in --wpfc --wpsubdomain || sudo tail -n50 /var/log/wo/wordops.log + + - yes | sudo wo site create site.hhvm.pagespeed2.com --wpsc --hhvm || sudo tail -n50 /var/log/wo/wordops.log + - yes | sudo wo site create site.hhvm.pagespeed4.com --wpfc --hhvm || sudo tail -n50 /var/log/wo/wordops.log + - yes | sudo wo site create site.hhvm.pagespeed5.com --wpsubdir --hhvm || sudo tail -n50 /var/log/wo/wordops.log + - yes | sudo wo site create site.hhvm.pagespeed6.com --wpsubdir --wpsc --hhvm || sudo tail -n50 /var/log/wo/wordops.log + - yes | sudo wo site create site.hhvm.pagespeed8.com --wpsubdir --wpfc --hhvm || sudo tail -n50 /var/log/wo/wordops.log + - yes | sudo wo site create site.hhvm.pagespeed9.com --wpsubdomain --hhvm || sudo tail -n50 /var/log/wo/wordops.log + - yes | sudo wo site create site.hhvm.pagespeed10.org --wpsubdomain --wpsc --hhvm || sudo tail -n50 /var/log/wo/wordops.log + - yes | sudo wo site create site.hhvm.pagespeed12.in --wpfc --wpsubdomain --hhvm || sudo tail -n50 /var/log/wo/wordops.log + + - sudo wo site create site1.localtest.me --php --mysql || sudo tail -n50 /var/log/wo/wordops.log + - sudo wo site create site2.localtest.me --mysql --html || sudo tail -n50 /var/log/wo/wordops.log + - sudo wo site create site3.localtest.me --php --html || sudo tail -n50 /var/log/wo/wordops.log + - sudo wo site create site4.localtest.me --wp --wpsubdomain || sudo tail -n50 /var/log/wo/wordops.log + - sudo wo site create site5.localtest.me --wp --wpsubdir --wpfc || sudo tail -n50 /var/log/wo/wordops.log + - yes | sudo wo site create site6.localtest.me --wpredis || sudo tail -n50 /var/log/wo/wordops.log + - yes | sudo wo site create site7.localtest.me --wpsubdomain --wpredis || sudo tail -n50 /var/log/wo/wordops.log + - yes | sudo wo site create site8.localtest.me --wpsubdir --wpredis || sudo tail -n50 /var/log/wo/wordops.log + + + - sudo wo debug --all || sudo tail -n50 /var/log/wo/wordops.log + - sudo wo debug --all=off || sudo tail -n50 /var/log/wo/wordops.log + - sudo wo debug site12.net || sudo tail -n50 /var/log/wo/wordops.log + - sudo wo debug site12.net --all=off || sudo tail -n50 /var/log/wo/wordops.log + - sudo wo site create 1.com --html || sudo tail -n50 /var/log/wo/wordops.log + - sudo wo site create 2.com --php || sudo tail -n50 /var/log/wo/wordops.log + - sudo wo site create 3.com --mysql || sudo tail -n50 /var/log/wo/wordops.log + + - sudo wo site update 1.com --wp || sudo tail -n50 /var/log/wo/wordops.log + - sudo wo site update 2.com --wpsubdir || sudo tail -n50 /var/log/wo/wordops.log + - sudo wo site update 3.com --wpsubdomain || sudo tail -n50 /var/log/wo/wordops.log + + - sudo wo site update site1.com --wp --wpfc || sudo tail -n50 /var/log/wo/wordops.log + - sudo wo site update site1.com --wp --wpsc || sudo tail -n50 /var/log/wo/wordops.log + - yes | sudo wo site update site1.com --wpredis || sudo tail -n50 /var/log/wo/wordops.log + + - sudo wo site update site5.com --wpsubdir --wpfc || sudo tail -n50 /var/log/wo/wordops.log + - sudo wo site update site5.com --wpsubdir --wpsc || sudo tail -n50 /var/log/wo/wordops.log + + - sudo wo site update site9.com --wpsubdomain --wpfc || sudo tail -n50 /var/log/wo/wordops.log + - sudo wo site update site9.com --wpsubdomain --wpsc || sudo tail -n50 /var/log/wo/wordops.log + - yes | sudo wo site update site.hhvm.pagespeed12.in --hhvm=off || sudo tail -n50 /var/log/wo/wordops.log + - yes | sudo wo site update site9.com --hhvm || sudo tail -n50 /var/log/wo/wordops.log + - sudo wo site info site.hhvm.pagespeed12.in || sudo tail -n50 /var/log/wo/wordops.log + - sudo wo site info site9.com || sudo tail -n50 /var/log/wo/wordops.log + + - sudo wo site create www.site-1.com --wp || sudo tail -n50 /var/log/wo/wordops.log|| sudo tail -n50 /var/log/wo/wordops.log + - sudo wo site create www.subsite.site-1.com --wpfc || sudo tail -n50 /var/log/wo/wordops.log + - sudo wo site update www.subsite.site-1.com --wp || sudo tail -n50 /var/log/wo/wordops.log + - sudo wo site delete www.subsite.site-1.com --all --no-prompt || sudo tail -n50 /var/log/wo/wordops.log + + - sudo wo site delete site12.in --all --no-prompt || sudo tail -n50 /var/log/wo/wordops.log + + - sudo ls /var/www/ + - sudo wp --allow-root --info + - sudo bash -c 'cat /var/log/wo/wordops.log' diff --git a/CHANGELOG.txt b/CHANGELOG.txt new file mode 100644 index 0000000..19a6789 --- /dev/null +++ b/CHANGELOG.txt @@ -0,0 +1,8 @@ +v3.9.0 - November 11, 2018 + +[+] Rebranded the fork to WordOps +[+] Codebase cleanup +[+] Included support for newer OS releases +[+] Reworked the HTTPS configuration +[-] Dropped mail services +[-] Dropped w3tc support \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..9a71675 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2018 WordOps + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..018839a --- /dev/null +++ b/README.md @@ -0,0 +1,93 @@ +# [WordOps](https://wordops.org/) + +WordOps (wo) is the essential toolset that eases WordPress site and server administration. + +**WordOps currently supports:** + +- Ubuntu 14.04, 16.04 & 18.04 +- Debian 7, 8 & 9 + +**Port requirements:** + +| Name | Port Number | Inbound | Outbound | +|:-----:|:-----------:|:-------:|:---------:| +|SSH |22 | ✓ |✓ | +|HTTP |80 | ✓ |✓ | +|HTTPS/SSL |443 | ✓ |✓ | +|WO Admin |22222 | ✓ | | +|GPG Key Server |11371 | |✓ | + +## Quick start + +```bash +wget -qO wo wordops.se/tup && sudo bash wo # Install WordOps +sudo wo site create example.com --wp # Install required packages & setup WordPress on example.com +``` +## Must read! + +WordOps made some fundamental changes: + +* We've deprecated the mail stack. Less is more. As an alternative, take a look at [iRedMail](https://www.iredmail.org/) or [Caesonia](https://github.com/vedetta-com/caesonia). And an alternative for Roundcube is [Rainloop](https://www.rainloop.net/). +* Support for w3tc is dropped as a security precaution. +* We are currently migrating the PHP 5.x series to PHP 7.1 and PHP 7.2. We'll offer an easy to use migration script once we are confident enough to unleash this daemon. + +## Update WordOps + +#### With one simple command +``` +wo update +``` + +## More site creation commands + +### Standard WordPress sites + +```bash +wo site create example.com --wp # install wordpress without any page caching +wo site create example.com --wpsc # install wordpress with wp-super-cache plugin +wo site create example.com --wpfc # install wordpress + nginx fastcgi_cache +wo site create example.com --wpredis # install wordpress + nginx redis_cache +``` + +### WordPress multsite with subdirectory + +```bash +wo site create example.com --wpsubdir # install wpmu-subdirectory without any page caching +wo site create example.com --wpsubdir --wpsc # install wpmu-subdirectory with wp-super-cache plugin +wo site create example.com --wpsubdir --wpfc # install wpmu-subdirectory + nginx fastcgi_cache +wo site create example.com --wpsubdir --wpredis # install wpmu-subdirectory + nginx redis_cache +``` + +### WordPress multsite with subdomain + +```bash +wo site create example.com --wpsubdomain # install wpmu-subdomain without any page caching +wo site create example.com --wpsubdomain --wpsc # install wpmu-subdomain with wp-super-cache plugin +wo site create example.com --wpsubdomain --wpfc # install wpmu-subdomain + nginx fastcgi_cache +wo site create example.com --wpsubdomain --wpredis # install wpmu-subdomain + nginx redis_cache +``` + +### Non-WordPress sites +```bash +wo site create example.com --html # create example.com for static/html sites +wo site create example.com --php # create example.com with php support +wo site create example.com --mysql # create example.com with php & mysql support +``` + +### HHVM enabled sites +```bash +wo site create example.com --wp --hhvm # create example.com WordPress site with HHVM support +wo site create example.com --php --hhvm # create example.com php site with HHVM support +``` + +## Cheatsheet - site creation + +| | single site | multisite w/ subdir | multisite w/ subdom | +|--------------------|---------------|-----------------------|--------------------------| +| **NO Cache** | --wp | --wpsubdir | --wpsubdomain | +| **WP Super Cache** | --wpsc | --wpsubdir --wpsc | --wpsubdomain --wpsc | +| **Nginx cache** | --wpfc | --wpsubdir --wpfc | --wpsubdomain --wpfc | +| **Redis cache** | --wpredis | --wpsubdir --wpredis | --wpsubdomain --wpredis | + +## License +[MIT](http://opensource.org/licenses/MIT) \ No newline at end of file diff --git a/config/bash_completion.d/wo_auto.rc b/config/bash_completion.d/wo_auto.rc new file mode 100644 index 0000000..17fb655 --- /dev/null +++ b/config/bash_completion.d/wo_auto.rc @@ -0,0 +1,378 @@ +_wo_complete() +{ + local cur prev BASE_LEVEL + + COMPREPLY=() + cur=${COMP_WORDS[COMP_CWORD]} + prev=${COMP_WORDS[COMP_CWORD-1]} + mprev=${COMP_WORDS[COMP_CWORD-2]} + + + # SETUP THE BASE LEVEL (everything after "wo") + if [ $COMP_CWORD -eq 1 ]; then + COMPREPLY=( $(compgen \ + -W "stack site debug clean secure import-slow-log log update sync info --version --help --quiet" \ + -- $cur) ) + + + # SETUP THE SECOND LEVEL (EVERYTHING AFTER "wo second") + elif [ $COMP_CWORD -eq 2 ]; then + case "$prev" in + + # HANDLE EVERYTHING AFTER THE SECOND LEVEL NAMESPACE + "clean") + COMPREPLY=( $(compgen \ + -W "--memcache --opcache --fastcgi --redis --all" \ + -- $cur) ) + ;; + + # IF YOU HAD ANOTHER CONTROLLER, YOU'D HANDLE THAT HERE + "debug") + COMPREPLY=( $(compgen \ + -W "$(command find /etc/nginx/sites-enabled/ -type l -printf "%P " 2> /dev/null) --nginx --php --php7 --fpm --fpm7 --mysql -i --interactive --all --import-slow-log --import-slow-log-interval= --nginx=off --php=off --php7=off --fpm=off --fpm7=off --mysql=off --all=off " \ + -- $cur) ) + ;; + + "stack") + COMPREPLY=( $(compgen \ + -W "upgrade install purge reload remove restart start status stop migrate" \ + -- $cur) ) + ;; + + "site") + COMPREPLY=( $(compgen \ + -W "cd create delete disable edit enable info list log show update" \ + -- $cur) ) + ;; + + "secure") + COMPREPLY=( $(compgen \ + -W "--auth --port --ip" \ + -- $cur) ) + ;; + + "info") + COMPREPLY=( $(compgen \ + -W "--mysql --php --php7 --nginx" \ + -- $cur) ) + ;; + + "log") + COMPREPLY=( $(compgen \ + -W "show reset gzip mail" \ + -- $cur) ) + ;; + + # EVERYTHING ELSE + *) + ;; + esac + + # SETUP THE THIRD LEVEL (EVERYTHING AFTER "wo second third") + elif [ $COMP_CWORD -eq 3 ]; then + case "$prev" in + # HANDLE EVERYTHING AFTER THE THIRD LEVEL NAMESPACE + "install" | "purge" | "remove" ) + COMPREPLY=( $(compgen \ + -W "--web --admin --nginx --php --php7 --mysql --wpcli --phpmyadmin --adminer --utils --all --hhvm --redis --phpredisadmin" \ + -- $cur) ) + ;; + "upgrade" ) + COMPREPLY=( $(compgen \ + -W "--web --nginx --php --php7 --mysql --all --hhvm --php56 --no-prompt --wpcli" \ + -- $cur) ) + ;; + "start" | "stop" | "reload" | "restart" | "status") + COMPREPLY=( $(compgen \ + -W "--nginx --php --php7 --mysql --memcache --redis" \ + -- $cur) ) + ;; + "migrate") + COMPREPLY=( $(compgen \ + -W "--mariadb" \ + -- $cur) ) + ;; + "list") + COMPREPLY=( $(compgen \ + -W "--enabled --disabled" \ + -- $cur) ) + ;; + + "edit" | "enable" | "info" | "log" | "show" | "cd" | "delete") + if [ ${COMP_WORDS[1]} == "log" ]; then + COMPREPLY=( $(compgen \ + -W "$(find /etc/nginx/sites-available/ -type f -printf "%P " 2> /dev/null) --nginx --php --fpm --mysql --access" \ + -- $cur) ) + else + COMPREPLY=( $(compgen \ + -W "$(find /etc/nginx/sites-available/ -type f -printf "%P " 2> /dev/null)" \ + -- $cur) ) + fi + ;; + "update") + COMPREPLY=( $(compgen \ + -W "$(find /etc/nginx/sites-available/ -type f -printf "%P " 2> /dev/null) --all" \ + -- $cur) ) + ;; + "gzip") + COMPREPLY=( $(compgen \ + -W "$(find /etc/nginx/sites-available/ -type f -printf "%P " 2> /dev/null) --nginx --php --fpm --mysql --access" \ + -- $cur) ) + ;; + + "reset") + COMPREPLY=( $(compgen \ + -W "$(find /etc/nginx/sites-available/ -type f -printf "%P " 2> /dev/null) --nginx --php --fpm --mysql --wp --access --slow-log-db" \ + -- $cur) ) + ;; + + "disable") + COMPREPLY=( $(compgen \ + -W "$(command find /etc/nginx/sites-enabled/ -type l -printf "%P " 2> /dev/null)" \ + -- $cur) ) + ;; + + *) + ;; + esac + + if [ ${COMP_WORDS[1]} == "debug" ] && ([ "$prev" != "--start" ] && [ "$prev" != "--nginx" ] && [ "$prev" != "--php" ] && [ "$prev" != "--php7" ] && [ "$prev" != "--fpm" ] && [ "$prev" != "--fpm7" ] && [ "$prev" != "--mysql" ] && [ "$prev" != "-i" ] && [ "$prev" != "--interactive" ] && [ "$prev" != "--import-slow-log" ] && [ "$prev" != "--stop" ]); then + retlist="--all --wp --rewrite -i --all=off --wp=off --rewrite=off" + ret="${retlist[@]/$prev}" + COMPREPLY=( $(compgen \ + -W "$(echo $ret)" \ + -- $cur) ) + fi + + #if [ ${COMP_WORDS[1]} == "log" ] && ([ "$prev" != "--access" ] || [ "$prev" != "--nginx" ] || [ "$prev" != "--php" ] || [ "$prev" != "--fpm" ] || [ "$prev" != "--mysql" ] || [ "$prev" != "-i" ] || ["$prev" != "--interactive" ] || ["$prev" != "--stop" ]); then + # retlist="--all --wp --rewrite -i --all=off --wp=off --rewrite=off" + # ret="${retlist[@]/$prev}" + # COMPREPLY=( $(compgen \ + # -W "$(echo $ret)" \ + # -- $cur) ) + #fi + + + elif [ $COMP_CWORD -eq 4 ]; then + case "$mprev" in + # HANDLE EVERYTHING AFTER THE THIRD LEVEL NAMESPACE + + "create") + COMPREPLY=( $(compgen \ + -W "--user --pass --email --html --php --php7 --mysql --wp --wpsubdir --wpsubdomain --wpfc --wpsc --hhvm --proxy= --wpredis --letsencrypt -le" \ + -- $cur) ) + ;; + + "update") + COMPREPLY=( $(compgen \ + -W "--password --php --php7 --mysql --wp --wpsubdir --wpsubdomain --wpfc --wpsc --hhvm --hhvm=off --wpredis --letsencrypt --letsencrypt=off --letsencrypt=renew" \ + -- $cur) ) + ;; + "delete") + COMPREPLY=( $(compgen \ + -W "--db --files --all --no-prompt --force -f" \ + -- $cur) ) + ;; + "show") + COMPREPLY=( $(compgen \ + -W "--wp --nginx --php --fpm --mysql --access" \ + -- $cur) ) + ;; + + "gzip") + COMPREPLY=( $(compgen \ + -W "--wp --nginx --php --fpm --mysql --access" \ + -- $cur) ) + ;; + + "mail") + COMPREPLY=( $(compgen \ + -W "--wp --nginx --php --fpm --mysql --access --to=" \ + -- $cur) ) + ;; + + "reset") + COMPREPLY=( $(compgen \ + -W "--wp --nginx --php --fpm --mysql --wp --access --slow-log-db" \ + -- $cur) ) + ;; + edit) + COMPREPLY=( $(compgen \ + + -- $cur) ) + ;; + *) + ;; + esac + + fi + + case "$prev" in + "--wp") + if [ ${COMP_WORDS[1]} != "debug" ]; then + if [ ${COMP_WORDS[2]} == "create" ]; then + retlist="--wp --wpsc --wpfc --hhvm --user --email --pass --wpredis --letsencrypt --php7" + elif [ ${COMP_WORDS[2]} == "update" ]; then + retlist="--wp --wpfc --wpsc --php7 --php7=off --hhvm --hhvm=off --wpredis --letsencrypt --letsencrypt=off --letsencrypt=renew" + else + retlist="" + fi + else + retlist="--wp --wp=off --rewrite --rewrite=off -i --interactive" + fi + + ret="${retlist[@]/$prev}" + COMPREPLY=( $(compgen \ + -W "$(echo $ret)" \ + -- $cur) ) + ;; + + "--wpsubdir" | "--wpsubdomain") + if [ ${COMP_WORDS[1]} != "debug" ]; then + if [ ${COMP_WORDS[2]} == "create" ]; then + retlist="--wpsc --wpfc --hhvm --user --email --pass --wpredis --letsencrypt --php7" + elif [ ${COMP_WORDS[2]} == "update" ]; then + retlist="--wpfc --wpsc --php7 --php7=off --hhvm --hhvm=off --wpredis --letsencrypt --letsencrypt=off --letsencrypt=renew" + else + retlist="" + fi + else + retlist="--wp=off --rewrite --rewrite=off -i --interactive" + fi + + ret="${retlist[@]/$prev}" + COMPREPLY=( $(compgen \ + -W "$(echo $ret)" \ + -- $cur) ) + ;; + + "--hhvm" | "--wpredis" | "--wpfc" | "--wpsc" | "--wpsubdir" | "--wpsubdomain" | "--user" | "--pass" | "--email" | "--wp") + if [ ${COMP_WORDS[2]} == "create" ]; then + retlist="--user --pass --email --wp --wpsubdir --wpsubdomain --wpfc --wpsc --hhvm --experimenal --wpredis --php7 --letsencrypt " + else + retlist="" + fi + + ret="${retlist[@]/$prev}" + COMPREPLY=( $(compgen \ + -W "$(echo $ret)" \ + -- $cur) ) + ;; + + "--hhvm" | "--wpredis" | "--wpfc") + if [ ${COMP_WORDS[2]} == "update" ]; then + retlist="--password --php --php7 --mysql --wp --wpsubdir --wpsubdomain --wpfc --wpsc --hhvm --hhvm=off --experimenal --wpredis --letsencrypt --letsencrypt=off --letsencrypt=renew" + else + retlist="" + fi + + ret="${retlist[@]/$prev}" + COMPREPLY=( $(compgen \ + -W "$(echo $ret)" \ + -- $cur) ) + ;; + + "--web" | "--admin" | "--nginx" | "--php" | "--php7" | "--mysql" | "--wpcli" | "--phpmyadmin" | "--adminer" | "--utils" | "--memcache" | "--redis | --phpredisadmin") + if [[ ${COMP_WORDS[2]} == "install" || ${COMP_WORDS[2]} == "purge" || ${COMP_WORDS[2]} == "remove" ]]; then + retlist="--web --admin --nginx --php --php7 --mysql--wpcli --phpmyadmin --adminer --utils --memcache --redis --phpredisadmin" + elif [[ ${COMP_WORDS[2]} == "start" || ${COMP_WORDS[2]} == "reload" || ${COMP_WORDS[2]} == "restart" || ${COMP_WORDS[2]} == "stop" ]]; then + retlist="--nginx --php --php7 --mysql --memcache --redis" + elif [[ ${COMP_WORDS[1]} == "debug" ]]; then + retlist="--start --nginx --php --php7 --fpm --fpm7 --mysql -i --interactive -stop --import-slow-log --import-slow-log-interval= -" + if [[ $prev == '--mysql' ]]; then + retlist="--start --nginx --php --php7 --fpm --fpm7 --mysql -i --interactive --stop --import-slow-log" + fi + elif [[ ${COMP_WORDS[1]} == "log" ]]; then + if [ ${COMP_WORDS[2]} == "show" ]; then + retlist="--access --nginx --php --mysql --fpm --wp" + + elif [ ${COMP_WORDS[2]} == "reset" ]; then + retlist="--access --nginx --php --mysql --fpm --wp --slow-log-db" + + elif [ ${COMP_WORDS[2]} == "mail" ]; then + retlist="--access --nginx --php --mysql --fpm --wp --to=" + + fi + fi + ret="${retlist[@]/$prev}" + COMPREPLY=( $(compgen \ + -W "$(echo $ret)" \ + -- $cur) ) + ;; + + "--db" | "--files" | "--force") + retlist="--db --files --all --force" + ret="${retlist[@]/$prev}" + COMPREPLY=( $(compgen \ + -W "$(echo $ret)" \ + -- $cur) ) + ;; + + "--all") + if [ ${COMP_WORDS[1]} == "clean" ]; then + retlist="--memcache --opcache --fastcgi --redis" + elif [ ${COMP_WORDS[2]} == "delete" ]; then + retlist="--db --files --force" + elif [ ${COMP_WORDS[2]} == "update" ]; then + retlist="--password --php --mysql --wp --wpsubdir --wpsubdomain --wpfc --wpsc --hhvm --hhvm=off --wpredis --letsencrypt --letsencrypt=off --letsencrypt=renew" + else + retlist="" + fi + ret="${retlist[@]/$prev}" + COMPREPLY=( $(compgen \ + -W "$(echo $ret)" \ + -- $cur) ) + ;; + + "--memcache" | "--opcache" | "--fastcgi" | "--all" | "--redis") + retlist="--memcache --opcache --fastcgi --redis --all" + ret="${retlist[@]/$prev}" + COMPREPLY=( $(compgen \ + -W "$(echo $ret)" \ + -- $cur) ) + ;; + "--auth" | "--port" | "--ip") + retlist="--auth --port --ip" + ret="${retlist[@]/$prev}" + COMPREPLY=( $(compgen \ + -W "$(echo $ret)" \ + -- $cur) ) + ;; + "--access" | "--fpm" | "--wp" | "--slow-log-db") + if [[ ${COMP_WORDS[1]} == "log" ]]; then + if [ ${COMP_WORDS[2]} == "show" ]; then + retlist="--access --nginx --php --mysql --fpm --wp" + + elif [ ${COMP_WORDS[2]} == "reset" ]; then + retlist="--access --nginx --php --mysql --fpm --wp --slow-log-db" + + + elif [ ${COMP_WORDS[2]} == "mail" ]; then + retlist="--access --nginx --php --mysql --fpm --wp --to=" + + fi + fi + ret="${retlist[@]/$prev}" + COMPREPLY=( $(compgen \ + -W "$(echo $ret)" \ + -- $cur) ) + ;; + *) + ;; + esac + case "$mprev" in + "--user" | "--email" | "--pass") + if [ ${COMP_WORDS[2]} == "create" ]; then + retlist="--user --pass --email --html --php --php7 --mysql --wp --wpsubdir --wpsubdomain --wpfc --wpsc --hhvm --wpredis --letsencrypt" + fi + ret="${retlist[@]/$prev}" + COMPREPLY=( $(compgen \ + -W "$(echo $ret)" \ + -- $cur) ) + ;; + esac + + return 0 + +} && +complete -F _wo_complete wo diff --git a/config/plugins.d/clean.conf b/config/plugins.d/clean.conf new file mode 100644 index 0000000..f324f55 --- /dev/null +++ b/config/plugins.d/clean.conf @@ -0,0 +1,8 @@ +### Example Plugin Configuration for WordOps + +[clean] + +### If enabled, load a plugin named `example` either from the Python module +### `wo.cli.plugins.example` or from the file path +### `/var/lib/wo/plugins/example.py` +enable_plugin = true diff --git a/config/plugins.d/debug.conf b/config/plugins.d/debug.conf new file mode 100644 index 0000000..c1a0ab1 --- /dev/null +++ b/config/plugins.d/debug.conf @@ -0,0 +1,8 @@ +### Example Plugin Configuration for WordOps + +[debug] + +### If enabled, load a plugin named `example` either from the Python module +### `wo.cli.plugins.example` or from the file path +### `/var/lib/wo/plugins/example.py` +enable_plugin = true diff --git a/config/plugins.d/import_slow_log.conf b/config/plugins.d/import_slow_log.conf new file mode 100644 index 0000000..ad82899 --- /dev/null +++ b/config/plugins.d/import_slow_log.conf @@ -0,0 +1,8 @@ +### Example Plugin Configuration for WordOps + +[import_slow_log] + +### If enabled, load a plugin named `example` either from the Python module +### `wo.cli.plugins.example` or from the file path +### `/var/lib/wo/plugins/example.py` +enable_plugin = true diff --git a/config/plugins.d/info.conf b/config/plugins.d/info.conf new file mode 100644 index 0000000..97d8328 --- /dev/null +++ b/config/plugins.d/info.conf @@ -0,0 +1,11 @@ +### Example Plugin Configuration for WordOps + +[info] + +### If enabled, load a plugin named `example` either from the Python module +### `wo.cli.plugins.example` or from the file path +### `/var/lib/wo/plugins/example.py` +enable_plugin = true + +### Additional plugin configuration settings +foo = bar diff --git a/config/plugins.d/log.conf b/config/plugins.d/log.conf new file mode 100644 index 0000000..8adcf57 --- /dev/null +++ b/config/plugins.d/log.conf @@ -0,0 +1,8 @@ +### Example Plugin Configuration for WordOps + +[log] + +### If enabled, load a plugin named `example` either from the Python module +### `wo.cli.plugins.example` or from the file path +### `/var/lib/wo/plugins/example.py` +enable_plugin = true diff --git a/config/plugins.d/secure.conf b/config/plugins.d/secure.conf new file mode 100644 index 0000000..05017c2 --- /dev/null +++ b/config/plugins.d/secure.conf @@ -0,0 +1,8 @@ +### Example Plugin Configuration for WordOps + +[secure] + +### If enabled, load a plugin named `example` either from the Python module +### `wo.cli.plugins.example` or from the file path +### `/var/lib/wo/plugins/example.py` +enable_plugin = true diff --git a/config/plugins.d/site.conf b/config/plugins.d/site.conf new file mode 100644 index 0000000..f3f2bf7 --- /dev/null +++ b/config/plugins.d/site.conf @@ -0,0 +1,8 @@ +### Example Plugin Configuration for WordOps + +[site] + +### If enabled, load a plugin named `example` either from the Python module +### `wo.cli.plugins.example` or from the file path +### `/var/lib/wo/plugins/example.py` +enable_plugin = true diff --git a/config/plugins.d/stack.conf b/config/plugins.d/stack.conf new file mode 100644 index 0000000..2817439 --- /dev/null +++ b/config/plugins.d/stack.conf @@ -0,0 +1,8 @@ +### Example Plugin Configuration for WordOps + +[stack] + +### If enabled, load a plugin named `example` either from the Python module +### `wo.cli.plugins.example` or from the file path +### `/var/lib/wo/plugins/example.py` +enable_plugin = true diff --git a/config/plugins.d/update.conf b/config/plugins.d/update.conf new file mode 100644 index 0000000..21aa436 --- /dev/null +++ b/config/plugins.d/update.conf @@ -0,0 +1,8 @@ +### Example Plugin Configuration for WordOps + +[update] + +### If enabled, load a plugin named `example` either from the Python module +### `wo.cli.plugins.example` or from the file path +### `/var/lib/wo/plugins/example.py` +enable_plugin = true diff --git a/config/wo.conf b/config/wo.conf new file mode 100644 index 0000000..4390e4d --- /dev/null +++ b/config/wo.conf @@ -0,0 +1,82 @@ +# WordOps Configuration +# +# All commented values are the application default +# + +[wo] + +### Toggle application level debug (does not toggle framework debugging) +# debug = false + +### Where external (third-party) plugins are loaded from +# plugin_dir = /var/lib/wo/plugins/ + +### Where all plugin configurations are loaded from +# plugin_config_dir = /etc/wo/plugins.d/ + +### Where external templates are loaded from +# template_dir = /var/lib/wo/templates/ + + +[log.logging] + +### Where the log file lives (no log file by default) +file = /var/log/wo/wordops.log + +### The level for which to log. One of: info, warn, error, fatal, debug +level = debug + +### Whether or not to log to console +to_console = false + +### Whether or not to rotate the log file when it reaches `max_bytes` +rotate = true + +### Max size in bytes that a log file can grow until it is rotated. +max_bytes = 512000 + +### The maximun number of log files to maintain when rotating +max_files = 7 + +[stack] + +### IP address that will be used in Nginx configurations while installing +ip-address = 127.0.0.1 + +[mysql] + +### MySQL database grant host name +grant-host = localhost + +### Ask for MySQL db name while site creation +db-name = False + +### Ask for MySQL user name while site creation +db-user = False + +[wordpress] + +### Ask for WordPress prefix while site creation +prefix = False + +### User name for WordPress sites +user = + +### Password for WordPress sites +password = + +### EMail for WordPress sites +email = + +[update] + +### If enabled, load a plugin named `update` either from the Python module +### `wo.cli.plugins.example` or from the file path +### `/var/lib/wo/plugins/example.py` +enable_plugin = true + +[sync] +### If enabled, load a plugin named `update` either from the Python module +### `wo.cli.plugins.example` or from the file path +### `/var/lib/wo/plugins/example.py` +enable_plugin = true diff --git a/docs/wo.8 b/docs/wo.8 new file mode 100644 index 0000000..11dccb4 --- /dev/null +++ b/docs/wo.8 @@ -0,0 +1,318 @@ +.TH wo 8 "WordOps (wo) version: 3.3.8" "Sep 10,2015" "WordOps" +.SH NAME +.B WordOps (wo) +\- Manage Nginx Based Websites. +.SH SYNOPSIS +wo [ --version | --help | info | stack | site | debug | update | clean | import_slow_log | log | secure | sync] +.TP +wo stack [ install | remove | purge | migrate | upgrade] [ --web | --mail | --all | --nginx | --php | --php7 | --mysql | --admin | --postfix | --mailscanner | --adminer | --redis | --hhvm | --phpmyadmin | --phpredisadmin | --wpcli | --utils ] +.TP +wo stack [ status | start | stop | reload | restart ] [--all | --nginx | --php | --php7 |--mysql | --devcot | --web | --postfix | --memcache | --redis] +.TP +wo site [ list | info | show | enable | disable | edit | cd | show ] [ example.com ] +.TP +wo site create example.com [ --html | --php | --php7 | --mysql] [[--wp | --wpsubdir | --wpsubdomain ] [--wpsc | --w3tc | --wpfc | --wpredis | --hhvm | --letsencrypt/-le]] +.TP +wo site update example.com [ --php | --php7 |--mysql] [[--wp | --wpsubdir | --wpsubdomain ] [--wpsc | --w3tc | --wpfc | --wpredis | --hhvm ] [--password] [--letsencrypt=on/off/renew]] +.TP +wo site delete example.com [--db | --files | --all | --no-prompt | --force/-f ] +.TP +wo debug [ -i | --all=on/off |--nginx=on/off | --rewrite=on/off | --php=on/off | --fpm=on/off | --mysql=on/off ] +.TP +wo debug example.com [ -i | --all=on/off | --nginx=on/off | --rewrite=on/off | --wp=on/off ] +.TP +wo secure [ --auth | --port | --ip ] +.SH DESCRIPTION +WordOps aka wo is the opensource project developed with the purpose to automate web-server configuration. +.br +WordOps is the collection of python script that provides automation for the web-server +.br +installation, site creation, services debugging & monitoring. +.SH OPTIONS +.TP +.B --version +.br +Display WordOps (wo) version information. +.TP +.B info +.br +wo info - Display Nginx, PHP, MySQL and wo common location information +.br +wo site info - Diplay given website details like enable, disable. weboot and log files. +.TP +.B --help +.br +Display WordOps (wo) help. +.TP +.B stack +.TP +.B install [ --all | --web | --mail | --nginx | --php | --php7 |--mysql | --redis | --postfix | --adminer | --phpmyadmin | --phpredismyadmin | --wpcli | --utils ] +.br +Install Nginx PHP5 MySQL Postfix stack Packages if not used with +.br +any options.Installs specific package if used with option. +.TP +.B remove [ --all | --web | --mail | --nginx | --php | --php7 |--mysql | --redis | --postfix | --adminer | --phpmyadmin | --phpredismyadmin | --wpcli | --utils ] +.br +Remove Nginx PHP5 MySQL Postfix stack Packages if not used with +.br +any options. Remove specific package if used with option. +.TP +.B purge [ --all | --web | --mail | --nginx | --php | --php7 |--mysql | --redis | --postfix | --adminer | --phpmyadmin | --phpredismyadmin | --wpcli | --utils ] +.br +Purge Nginx PHP5 MySQL Postfix stack Packages if not used with any +.br +options.Purge specific package if used with option. +.TP +.B status +.br +Display status of NGINX, PHP5-FPM, MySQL, Postfix, Redis-Server services. +.TP +.B start +.br +Start services NGINX, PHP5-FPM, MySQL, Postfix, Redis-Server. +.TP +.B stop +.br +Stop services NGINX, PHP5-FPM, MySQL, Postfix, Redis-Server. +.TP +.B reload +.br +Reload services NGINX, PHP5-FPM, MySQL, Postfix, Redis-Server. +.TP +.B restart +.br +Restart services NGINX, PHP5-FPM, MySQL, Postfix, Redis-Server. +.TP +.B site +.br +.TP +.B cd [ example.com ] +.br +Change directory to webroot of specified site in subshell. +.TP +.B log [ example.com ] +.br +monitor access and error logs for site specified. +.TP +.B list [ --enabled | --disabled ] +.br +Lists all available sites from /etc/nginx/sites-enabled/ +.br +by default & enable argument. Display sites list from +.br +/etc/nginx/sites-available/ if used with available option. +.TP +.B info [ example.com ] +.br +prints information about site such as access log, error log +.br +location and type of site. +.TP +.B show [ example.com ] +.br +Display NGINX configuration of site. +.TP +.B enable [ example.com ] +.br +Enable site by creating softlink with site file in +.br +/etc/nginx/sites-available to /etc/nginx/sites-enabled/. +.TP +.B disable [ example.com ] +.br +Disable site by Destroying softlink with site file in +.br +/etc/nginx/sites-available to /etc/nginx/sites-enabled/. +.TP +.B edit [ example.com ] +.br +Edit NGINX configuration of site. +.TP +.B create [ example.com ] [ --html | --php | --php7 |--mysql] [[--wp | --wpsubdir | --wpsubdomain ] [--wpsc | --w3tc | --wpfc | --wpredis | --hhvm ]] +.br +Create new site according to given options. If no options provided +.br +create static site with html only. +.TP +.B update [ example.com ] [ --html | --php | --php7 |--mysql] [[--wp | --wpsubdir | --wpsubdomain ] [ --wpsc | --w3tc | --wpfc | --wpredis | --hhvm ] [--password]] +.br +Update site configuration according to specified options. +.TP +.B delete [ example.com ] [--no-prompt ] [--force/-f] [ --db | --files | --all ] +.br +Delete site i.e webroot, database, ad configuration permanently. +.TP +.B debug [ -i | --nginx=on/off | --php=on/off | --php7=on/off | --mysql=on/off | --rewrite=on/off | --fpm=on/off | --fpm7=on/off ] +.br +Starts server level debugging. If this is used without arguments it will start debugging +.br +all services.Else it will debug only service provided with argument.This will Stop +.br +Debugging if used with --all=off argument. +.TP +.B debug example.com [ -i | --nginx=on/off | --rewrite=on/off | --wp=on/off | --all=on/off ] +.br +Starts site level debugging. If this is used without arguments it will start debugging all +.br +services.Else it will debug only service provided with argument.This will Stop Debugging +.br +if used with --all=off argument. +.TP +.B secure [ --auth | --port | --ip ] +.br +Update security settings. +.TP +.B clean [ --fastcgi | --opcache | --memcache | --redis | --all ] +.br +Clean NGINX fastCGI cache, Opcache, Memcache, Redis cache. +.br +Clean NGINX fastCGI cache if no option specified. +.SH ARGUMENTS +.TP +.B -i +.br +setup intractive mode while used with debug. +.TP +.B --nginx=on/off +.br +used with wo debug command. used to start or stop nginx debugging. +.TP +.B --php=on/off +.br +used with wo debug command. used to start or stop php debugging. +.TP +.B --php7=on/off +.br +used with wo debug command. used to start or stop php7 debugging. +.TP +.B --mysql=on/off +.br +used with wo debug command. used to start or stop mysql debugging. +.TP +.B --rewrite=on/off +.br +used with wo debug command. used to start or stop nginx rewrite rules debugging. +.TP +.B --fpm=on/off +.br +used with wo debug command. used to start or stop fpm debugging. +.TP +.B --wp=on/off +.br +used with wo debug command. used to start or stop wordpress site debugging. +.TP +.B --all=on/off +.br +used with wo debug command. used to stop debugging. +.TP +.B --all=off +.br +used with wo debug command. used to stop debugging. +.TP +.B --html +.br +Create a HTML website. +.TP +.B --php +.br +Create a PHP website. +.TP +.B --mysql +.br +Create a PHP+MySQL website. +.TP +.B --wp +.br +Create a WordPress Website. +.TP +.B --wpsubdir +.br +Create a Wordpress Multisite with Sub Directories Setup. +.TP +.B --wpsubdomain +.br +Create a Wordpress Multisite with Sub Domains Setup. +.br +.TP +.B --db +.br +Delete website database. +.br +.TP +.B --files +.br +Delete website webroot. +.br +.TP +.B --no-prompt +.br +Does not prompt for confirmation when delete command used. +.br +.TP +.B --force/-f +.br +Delete website webroot and database forcefully.Remove nginx configuration for site. +.br +.TP +.B --auth +.br +used with wo secure command. Update credential of HTTP authentication +.TP +.B --port +.br +used with wo secure command. Change WordOps admin port 22222. +.TP +.B --ip +.br +used with wo secure command. Update whitelist IP address +.SH WORDPRESS CACHING OPTIONS +.TP +.B --wpsc +.br +Install and activate Nginx-helper and WP Super Cache plugin. +.TP +.B --wpfc +.br +Install and activate Nginx-helper and W3 Total Cache plugin with +.br +Nginx FastCGI cache. +.TP +.B --wpredis +.br +Install, activate, configure Nginx-helper and Redis Object Cache Plugin, Configure NGINX for Redis Page Caching. +.TP +.B --hhvm +.br +Install, activate Nginx-helper and configure NGINX for HHVM. +.SH FILES +.br +/etc/wo/wo.conf +.SH BUGS +Report bugs at +.SH AUTHOR +.br +.B rtCamp Team +.I \ +.br +.B Mitesh Shah +.I \ +.br +.B Manish +.I \ +.br +.B Gaurav +.I \ +.br +.B Harshad +.I \ +.br +.B Prabuddha +.I \ +.br +.B Shital +.I \ +.br +.B Rajdeep Sharma +.I \ +.br \ No newline at end of file diff --git a/install b/install new file mode 100644 index 0000000..5415d64 --- /dev/null +++ b/install @@ -0,0 +1,744 @@ +#!/bin/bash + +# WordOps update script. +# This script is designed to install latest WordOps or +# to update current WordOps from the v3 branch of EasyEngine. + +# Define echo function + +# Blue color +function wo_lib_echo() +{ + echo $(tput setaf 4)$@$(tput sgr0) +} +# White color +function wo_lib_echo_info() +{ + echo $(tput setaf 7)$@$(tput sgr0) +} +# Red color +function wo_lib_echo_fail() +{ + echo $(tput setaf 1)$@$(tput sgr0) +} + +# Checking permissions +if [[ $EUID -ne 0 ]]; then + wo_lib_echo_fail "Sudo privilege required..." + wo_lib_echo_fail "Uses: wget -qO wo wordops.se/tup && sudo bash wo" + exit 100 +fi + +# Capture errors +function wo_lib_error() +{ + echo "[ `date` ] $(tput setaf 1)$@$(tput sgr0)" + exit $2 +} + +# Execute: apt-get update +wo_lib_echo "Executing apt-get update, please wait..." +apt-get update &>> /dev/null + +# Checking lsb_release package +if [ ! -x /usr/bin/lsb_release ]; then + wo_lib_echo "Installing lsb-release, please wait..." + apt-get -y install lsb-release &>> /dev/null +fi + +# Define variables for later use +wo_branch=$1 +readonly wo_version_old="2.2.3" +readonly wo_version_new="3.8.1" +readonly wo_log_dir=/var/log/wo/ +readonly wo_install_log=/var/log/wo/install.log +readonly wo_linux_distro=$(lsb_release -i | awk '{print $3}') +readonly wo_distro_version=$(lsb_release -sc) + +# Checking linux distro +if [ "$wo_linux_distro" != "Ubuntu" ] && [ "$wo_linux_distro" != "Debian" ]; then + wo_lib_echo_fail "WordOps (wo) only supports Ubuntu and Debian at the moment." + wo_lib_echo_fail "If you are feeling adventurous, you are free to fork WordOps to support" + wo_lib_echo_fail "other Linux distributions and perhaps even Unix deratives." + wo_lib_echo_fail "WordOps (wo) only supports Ubuntu 14.04/16.04/18.04, Debian 8.x and Debian 9.x" + exit 100 +fi + +# WordOps (wo) only supports Ubuntu/Debian versions that are eligible for support +lsb_release -d | egrep -e "14.04|16.04|18.04|jessie|stretch" &>> /dev/null +if [ "$?" -ne "0" ]; then + wo_lib_echo_fail "WordOps (wo) only supports Ubuntu 14.04/16.04/18.04, Debian 8.x and Debian 9.x" + exit 100 +fi + +# To prevent errors or unexpected behaviour, create +# the log directory and properly set the ACL. +if [ ! -d $wo_log_dir ]; then + + wo_lib_echo "Creating WordOps log directory, just a second..." + mkdir -p $wo_log_dir || wo_lib_error "Whoops - seems we are unable to create the log directory $wo_log_dir, exit status " $? + + # Touch/create two empty log files within the wo_log_dir + touch /var/log/wo/{wordops.log,install.log} + + # Set the ACL to only allow access by the root user and block others + chmod -R 700 /var/log/wo || wo_lib_error "Whoops, there was an error setting the permissions on the WordOps log folder, exit status " $? +fi + +# Install a few dependencies (python v3, git, tar and python-software-properties), +# plus generate the locale afterwards. +function wo_install_dep() +{ + if [ "$wo_linux_distro" == "Ubuntu" ]; then + apt-get -y install build-essential curl gzip python3 python3-apt python3-setuptools python3-dev sqlite3 git tar software-properties-common || wo_lib_error "There was an error during dependency installation, exit status " 1 + elif [ "$wo_linux_distro" == "Debian" ]; then + apt-get -y install build-essential curl gzip dirmngr python3 python3-apt python3-setuptools python3-dev sqlite3 git tar python-software-properties || wo_lib_error "There was an error during dependency installation, exit status " 1 + fi + + # Generate the locale, output to the blackhole rather than STDOUT + locale-gen en &>> /dev/null +} + +# Create the WordOps database and intialize it +function wo_sync_db() +{ + if [ ! -f /var/lib/wo/dbase.db ]; then + mkdir -p /var/lib/wo + + echo "CREATE TABLE sites ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + sitename UNIQUE, + site_type CHAR, + cache_type CHAR, + site_path CHAR, + created_on TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + is_enabled INT, + is_ssl INT, + storage_fs CHAR, + storage_db CHAR, + db_name VARCHAR, + db_user VARCHAR, + db_password VARCHAR, + db_host VARCHAR, + is_hhvm INT INT DEFAULT '0', + php_version VARCHAR + );" | sqlite3 /var/lib/wo/dbase.db + + # Check site is enable/live or disable + for site in $(ls /etc/nginx/sites-available/ | grep -v default); + do + if [ -f /etc/nginx/sites-enabled/$site ]; then + wo_site_status='1' + else + wo_site_status='0' + fi + + # Acquire information about the current nginx configuration + wo_site_current_type=$(head -n1 /etc/nginx/sites-available/$site | grep "NGINX CONFIGURATION" | rev | cut -d' ' -f3,4,5,6,7 | rev | cut -d ' ' -f2,3,4,5) + + # Sniff out the vhost type and cache configuration + if [ "$wo_site_current_type" = "HTML" ]; then + wo_site_current="html" + wo_site_current_cache="basic" + elif [ "$wo_site_current_type" = "PHP" ]; then + wo_site_current="php" + wo_site_current_cache="basic" + elif [ "$wo_site_current_type" = "MYSQL" ]; then + wo_site_current="mysql" + wo_site_current_cache="basic" + + # Caching types on a single WordPress installation + elif [ "$wo_site_current_type" = "WPSINGLE BASIC" ]; then + wo_site_current="wp" + wo_site_current_cache="basic" + + elif [ "$wo_site_current_type" = "WPSINGLE WP SUPER CACHE" ]; then + wo_site_current="wp" + wo_site_current_cache="wpsc" + + elif [ "$wo_site_current_type" = "WPSINGLE FAST CGI" ] || [ "$wo_site_current_type" = "WPSINGLE FASTCGI" ]; then + wo_site_current="wp" + wo_site_current_cache="wpfc" + + # Caching types on a single, subdirectory WordPress installation + elif [ "$wo_site_current_type" = "WPSUBDIR BASIC" ]; then + wo_site_current="wpsubdir" + wo_site_current_cache="basic" + + elif [ "$wo_site_current_type" = "WPSUBDIR WP SUPER CACHE" ]; then + wo_site_current="wpsubdir" + wo_site_current_cache="wpsc" + + elif [ "$wo_site_current_type" = "WPSUBDIR FAST CGI" ] || [ "$wo_site_current_type" = "WPSUBDIR FASTCGI" ]; then + wo_site_current="wpsubdir" + wo_site_current_cache="wpfc" + + # Caching types on a single, subdomain WordPress installation + elif [ "$wo_site_current_type" = "WPSUBDOMAIN BASIC" ]; then + wo_site_current="wpsubdomain" + wo_site_current_cache="basic" + + elif [ "$wo_site_current_type" = "WPSUBDOMAIN WP SUPER CACHE" ]; then + wo_site_current="wpsubdomain" + wo_site_current_cache="wpsc" + + elif [ "$wo_site_current_type" = "WPSUBDOMAIN FAST CGI" ] || [ "$wo_site_current_type" = "WPSUBDOMAIN FASTCGI" ]; then + wo_site_current="wpsubdomain" + wo_site_current_cache="wpfc" + fi + + wo_webroot="/var/www/$site" + + # Import the configuration into the WordOps SQLite database + echo "INSERT INTO sites (sitename, site_type, cache_type, site_path, is_enabled, is_ssl, storage_fs, storage_db) + VALUES (\"$site\", \"$wo_site_current\", \"$wo_site_current_cache\", \"$wo_webroot\", \"$wo_site_status\", 0, 'ext4', 'mysql');" | sqlite3 /var/lib/wo/dbase.db + + done + else + wo_php_version=$(php -v | head -n1 | cut -d' ' -f2 |cut -c1-3) + wo_lib_echo "Updating WordOps Database" + echo "ALTER TABLE sites ADD COLUMN db_name varchar;" | sqlite3 /var/lib/wo/dbase.db + echo "ALTER TABLE sites ADD COLUMN db_user varchar; " | sqlite3 /var/lib/wo/dbase.db + echo "ALTER TABLE sites ADD COLUMN db_password varchar;" | sqlite3 /var/lib/wo/dbase.db + echo "ALTER TABLE sites ADD COLUMN db_host varchar;" | sqlite3 /var/lib/wo/dbase.db + echo "ALTER TABLE sites ADD COLUMN is_hhvm INT DEFAULT '0';" | sqlite3 /var/lib/wo/dbase.db + echo "ALTER TABLE sites ADD COLUMN php_version varchar DEFAULT \"$wo_php_version\";" | sqlite3 /var/lib/wo/dbase.db + fi +} + +# Once again, set the proper ACL on the WordOps configuration directory +function secure_wo_db() +{ + # The owner is root + chown -R root:root /var/lib/wo/ + # Only allow access by root, block others + chmod -R 600 /var/lib/wo/ +} + +# Update the WP-CLI version +function wo_update_wp_cli() +{ + wo_lib_echo "Updating WP-CLI version to resolve compatibility issue." + PHP_PATH=$(which php) + WP_CLI_PATH=$(which wp) + if [ "${WP_CLI_PATH}" != "" ]; then + # Obtain the current WP-CLI version + WP_CLI_VERSION=$(${PHP_PATH} ${WP_CLI_PATH} --allow-root cli version | awk '{ print $2 }') + dpkg --compare-versions ${WP_CLI_VERSION} lt 1.4.1 + # Update WP-CLI to the most recent version + if [ "$?" == "0" ]; then + wget -qO ${WP_CLI_PATH} https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar + chmod +x ${WP_CLI_PATH} + fi + fi +} + +# Now, finally, let's install WordOps +function wo_install() +{ + # Remove old versions of EasyEngine/WordOps + rm -rf /tmp/easyengine &>> /dev/null + rm -rf /tmp/wordops &>> /dev/null + + # Clone the latest version directly from GitHub + wo_lib_echo "Downloading WordOps straight from GitHub - fresh and brewed with love. Hold your horses..." + if [ "$wo_branch" = "" ]; then + wo_branch=master + fi + + git clone -b $wo_branch https://github.com/WordOps/WordOps.git /tmp/wordops --quiet > /dev/null \ + || wo_lib_error "An error was encountered during the download, exit status" $? + + cd /tmp/wordops + wo_lib_echo "The moment you've all been waiting for, time to install WordOps!" + python3 setup.py install || wo_lib_error "An error was encountered during the installation, exit status " $? +} + +# Update the WordOps installation, +# remove old versions +function wo_update() +{ + wo_lib_echo "Importing the EasyEngine configuration and magically importing it to WordOps." + wo_lib_echo "No worries, we'll make sure the configuration is duly backupped..." + wo_grant_host=$(grep grant-host /etc/easyengine/ee.conf | awk '{ print $3 }' | head -1 ) + wo_db_name=$(grep db-name /etc/easyengine/ee.conf | awk '{ print $3 }') + wo_db_user=$(grep db-name /etc/easyengine/ee.conf | awk '{ print $3 }') + wo_wp_prefix=$(grep prefix /etc/easyengine/ee.conf | awk '{ print $3 }') + wo_wp_user=$(grep 'user ' /etc/easyengine/ee.conf | grep -v db-user |awk '{ print $3 }') + wo_wp_pass=$(grep password /etc/easyengine/ee.conf | awk '{ print $3 }') + wo_wp_email=$(grep email /etc/easyengine/ee.conf | awk '{ print $3 }') + wo_ip_addr=$(grep ip-address /etc/easyengine/ee.conf |awk -F'=' '{ print $2 }') + + sed -i "s/ip-address.*/ip-address = ${wo_ip_addr}/" /etc/wo/wo.conf && \ + sed -i "s/grant-host.*/grant-host = ${wo_grant_host}/" /etc/ee/wo.conf && \ + sed -i "s/db-name.*/db-name = ${db-name}/" /etc/ee/wo.conf && \ + sed -i "s/db-user.*/db-user = ${wo_db_user}/" /etc/wo/wo.conf && \ + sed -i "s/prefix.*/prefix = ${wo_wp_prefix}/" /etc/wo/wo.conf && \ + sed -i "s/^user.*/user = ${wo_wp_user}/" /etc/wo/wo.conf && \ + sed -i "s/password.*/password = ${wo_wp_password}/" /etc/wo/wo.conf && \ + sed -i "s/email.*/email = ${wo_wp_email}/" /etc/wo/wo.conf || wo_lib_error "An error was encountered during the configuration update, exit status " $? + + wo_lib_echo "Removing EasyEngine 2.x" + rm -rf /etc/bash_completion.d/ee /etc/easyengine/ /usr/share/easyengine/ /usr/local/lib/easyengine /usr/local/sbin/easyengine /usr/local/sbin/ee /var/log/easyengine + + # Softlink to fix command not found error + ln -s /usr/local/bin/wo /usr/local/sbin/wo || wo_lib_error "Unable to softlink the WordOps binary, exit status " $? +} + +function wo_upgrade_php(){ + # Change the PHP repository for PHP 7.x support + if [ "$wo_distro_version" == "trusty" ]; then + if [ -f /etc/apt/sources.list.d/ondrej-php5-5_6-trusty.list ]; then + # add-apt-repository -y --remove 'ppa:ondrej/php5-5.6' + add-apt-repository -y 'ppa:ondrej/php' + wo_lib_echo "Updating the PHP repository for some neat PHP 7 support" + apt-get update &>> /dev/null + apt-get -y install php5.6-fpm php5.6-curl php5.6-gd php5.6-imap php5.6-mcrypt php5.6-readline php5.6-mysql php5.6-cli php5.6-common php5.6-curl php5.6-mbstring php5.6-bcmath php5.6-recode php5.6-mysql php5.6-opcache php-memcached php-imagick memcached php-pear php-xdebug php-msgpack php5.6-zip php5.6-xml php5.6-soap php-memcache || wo_lib_error "Unable to install PHP 5.6 packages, exit status " 1 + if [ -e /etc/php5/fpm/pool.d/www.conf -a -e /etc/php5/fpm/pool.d/debug.conf -a -e /etc/php5/fpm/php.ini -a -e /etc/php5/fpm/php-fpm.conf ]; then + cp -f /etc/php5/fpm/pool.d/www.conf /etc/php/5.6/fpm/pool.d/www.conf &>> /dev/null + cp -f /etc/php5/fpm/pool.d/debug.conf /etc/php/5.6/fpm/pool.d/debug.conf &>> /dev/null + cp -f /etc/php5/fpm/php.ini /etc/php/5.6/fpm/php.ini &>> /dev/null + cp -f /etc/php5/fpm/php-fpm.conf /etc/php/5.6/fpm/php-fpm.conf &>> /dev/null + else + echo "Some expected files are missing." || wo_lib_error "Unable to configure PHP 5.6 packages, exit status " 1 + fi + sed -i "s/pid.*/pid = \/run\/php\/php5.6-fpm.pid/" /etc/php/5.6/fpm/php-fpm.conf && \ + sed -i "s/error_log.*/error_log = \/var\/log\/php\/5.6\/fpm.log/" /etc/php/5.6/fpm/php-fpm.conf && \ + sed -i "s/log_level.*/log_level = notice/" /etc/php/5.6/fpm/php-fpm.conf && \ + sed -i "s/include.*/include = \/etc\/php\/5.6\/fpm\/pool.d\/*.conf/" /etc/php/5.6/fpm/php-fpm.conf && \ + sed -i "s/slowlog =.*/slowlog = \/var\/log\/php\/5.6\/slow.log/" /etc/php/5.6/fpm/pool.d/debug.conf || wo_lib_error "An error was encountered during the configuration update, exit status " $? + mkdir -p /var/log/php/5.6/ + touch /var/log/php/5.6/slow.log /var/log/php/5.6/fpm.log + service php5-fpm stop &>> /dev/null + service php5.6-fpm restart &>> /dev/null + rm -f /etc/apt/sources.list.d/ondrej-php5-5_6-trusty.list &>> /dev/null + apt-get remove -y php5-fpm php5-curl php5-gd php5-imap php5-mcrypt php5-common php5-readline php5-mysql php5-cli php5-memcache php5-imagick memcached graphviz php-pear + + # Fix for PHP 5.6 + 7.0 missed packages + elif [ -f /etc/php/mods-available/readline.ini ]; then + mkdir -p /tmp/php-conf/5.6 + mkdir -p /tmp/php-conf/7.0 + cp -f /etc/php/5.6/fpm/pool.d/www.conf /tmp/php-conf/5.6 &>> /dev/null + cp -f /etc/php/5.6/fpm/pool.d/debug.conf /tmp/php-conf/5.6 &>> /dev/null + cp -f /etc/php/5.6/fpm/php.ini /tmp/php-conf/5.6 &>> /dev/null + cp -f /etc/php/5.6/fpm/php-fpm.conf /tmp/php-conf/5.6 &>> /dev/null + + cp -f /etc/php/7.0/fpm/pool.d/www.conf /tmp/php-conf/7.0 &>> /dev/null + cp -f /etc/php/7.0/fpm/pool.d/debug.conf /tmp/php-conf/7.0 &>> /dev/null + cp -f /etc/php/7.0/fpm/php.ini /tmp/php-conf/7.0 &>> /dev/null + cp -f /etc/php/7.0/fpm/php-fpm.conf /tmp/php-conf/7.0 &>> /dev/null + + + apt-get -y install php5.6-fpm php5.6-curl php5.6-gd php5.6-imap php5.6-mcrypt php5.6-readline php5.6-mysql php5.6-cli php5.6-common php5.6-curl php5.6-mbstring php5.6-bcmath php5.6-recode php5.6-mysql php5.6-opcache php-memcached php-imagick memcached php-pear php-xdebug php-msgpack php5.6-zip php5.6-xml php-memcache || wo_lib_error "Unable to install PHP 5.6 packages, exit status " 1 + dpkg-query -W -f='${Status} ${Version}\n' php7.0-fpm 2>/dev/null | grep installed + if [ "$?" -eq "0" ]; then + apt-get -y install php7.0-fpm php7.0-curl php7.0-gd php7.0-imap php7.0-mcrypt php7.0-readline php7.0-common php7.0-recode php7.0-mysql php7.0-cli php7.0-curl php7.0-mbstring php7.0-bcmath php7.0-mysql php7.0-opcache php7.0-zip php7.0-xml php-memcached php-imagick php-memcache memcached php-pear php-xdebug php-msgpack php7.0-soap || wo_lib_error "Unable to install PHP 7.0 packages, exit status " 1 + mv -f /tmp/php-conf/7.0/www.conf /etc/php/7.0/fpm/pool.d/www.conf &>> /dev/null + mv -f /tmp/php-conf/7.0/debug.conf /etc/php/7.0/fpm/pool.d/debug.conf &>> /dev/null + mv -f /tmp/php-conf/7.0/php.ini /etc/php/7.0/fpm/php.ini &>> /dev/null + mv -f /tmp/php-conf/7.0/php-fpm.conf /etc/php/7.0/fpm/php-fpm.conf &>> /dev/null + service php7.0-fpm restart &>> /dev/null + fi + + mv -f /tmp/php-conf/5.6/www.conf /etc/php/5.6/fpm/pool.d/www.conf &>> /dev/null + mv -f /tmp/php-conf/5.6/debug.conf /etc/php/5.6/fpm/pool.d/debug.conf &>> /dev/null + mv -f /tmp/php-conf/5.6/php.ini /etc/php/5.6/fpm/php.ini &>> /dev/null + mv -f /tmp/php-conf/5.6/php-fpm.conf /etc/php/5.6/fpm/php-fpm.conf &>> /dev/null + + service php5.6-fpm restart &>> /dev/null + rm -rf /tmp/php-conf + fi + fi + +} + +function wo_update_latest() +{ + +if [ -f /etc/nginx/fastcgi_params ] +then + cat /etc/nginx/fastcgi_params| grep -q 'HTTP_PROXY' + if [[ $? -ne 0 ]]; then + echo "fastcgi_param HTTP_PROXY \"\";" >> /etc/nginx/fastcgi_params + echo "fastcgi_param HTTP_PROXY \"\";" >> /etc/nginx/fastcgi.conf + service nginx restart &>> /dev/null + fi +fi + +if [ -f /etc/ImageMagick/policy.xml ] + then + if [ ! -f /etc/ImageMagick/patch.txt ] + then + echo -e "\t\n\t\n\t\n\t\n\t" >> /etc/ImageMagick/patch.txt + sed -i '//r /etc/ImageMagick/patch.txt' /etc/ImageMagick/policy.xml + fi + fi + + #Move ~/.my.cnf to /etc/mysql/conf.d/my.cnf + if [ ! -f /etc/mysql/conf.d/my.cnf ] + then + #create conf.d folder if not exist + if [ ! -d /etc/mysql/conf.d ]; then + mkdir -p /etc/mysql/conf.d + chmod 755 /etc/mysql/conf.d + fi + if [ -d /etc/mysql/conf.d ] + then + if [ -f ~/.my.cnf ] + then + cp ~/.my.cnf /etc/mysql/conf.d/my.cnf &>> /dev/null + chmod 600 /etc/mysql/conf.d/my.cnf + else + if [ -f /root/.my.cnf ] + then + cp /root/.my.cnf /etc/mysql/conf.d/my.cnf &>> /dev/null + chmod 600 /etc/mysql/conf.d/my.cnf + else + wo_lib_echo_fail ".my.cnf cannot be located in your current user or root folder..." + fi + fi + fi + fi + + + if [ -f /etc/nginx/nginx.conf ]; then + wo_lib_echo "Updating Nginx configuration, please wait..." + # From version 3.1.10 we are using Suse builder for repository + if [ "$wo_distro_version" == "precise" ]; then + + wo_lib_echo_fail ".my.cnf cannot be located in your current user or root folder..." + + elif [ "$wo_distro_version" == "trusty" ]; then + grep -Hr 'http://download.opensuse.org/repositories/home:/rtCamp:/EasyEngine/xUbuntu_14.04/ /' /etc/apt/sources.list.d/ &>> /dev/null + if [[ $? -ne 0 ]]; then + if [ -f /etc/apt/sources.list.d/rtcamp-nginx-trusty.list ]; then + rm -rf /etc/apt/sources.list.d/rtcamp-nginx-trusty.list + fi + echo -e "\ndeb http://download.opensuse.org/repositories/home:/rtCamp:/EasyEngine/xUbuntu_14.04/ /" >> /etc/apt/sources.list.d/wo-repo.list + gpg --keyserver "hkp://pgp.mit.edu" --recv-keys '3050AC3CD2AE6F03' + gpg -a --export --armor '3050AC3CD2AE6F03' | apt-key add - + if [ -f /etc/nginx/conf.d/ee-nginx.conf ]; then + mv /etc/nginx/conf.d/ee-nginx.conf /etc/nginx/conf.d/ee-nginx.conf.old &>> /dev/null + fi + mv /etc/nginx/nginx.conf /etc/nginx/nginx.conf.old &>> /dev/null + apt-get update + service nginx stop &>> /dev/null + DEBIAN_FRONTEND=noninteractive apt-get -o Dpkg::Options::="--force-confmiss" -o Dpkg::Options::="--force-confold" -y install nginx-custom nginx-ee + service nginx restart &>> /dev/null + fi + dpkg --get-selections | grep -v deinstall | grep nginx-common + if [ $? -eq 0 ]; then + apt-get update + dpkg --get-selections | grep -v deinstall | grep nginx-mainline + if [ $? -eq 0 ]; then + apt-get remove -y nginx-mainline + fi + service nginx stop &>> /dev/null + DEBIAN_FRONTEND=noninteractive apt-get -o Dpkg::Options::="--force-confmiss" -o Dpkg::Options::="--force-confold" -y --allow-unauthenticated install nginx-ee nginx-custom + service nginx restart &>> /dev/null + fi + + elif [ "$wo_distro_version" == "wheezy" ]; then + grep -Hr 'http://download.opensuse.org/repositories/home:/rtCamp:/EasyEngine/Debian_7.0/ /' /etc/apt/sources.list.d/ &>> /dev/null + #grep -Hr "deb http://packages.dotdeb.org wheezy all" /etc/apt/sources.list.d/wo-repo.list &>> /dev/null + if [[ $? -ne 0 ]]; then + # if [ -f /etc/apt/sources.list.d/dotdeb-wheezy.list ]; then + # rm -rf /etc/apt/sources.list.d/dotdeb-wheezy.list + # else + # sed -i "/deb http:\/\/packages.dotdeb.org wheezy all/d" /etc/apt/sources.list.d/wo-repo.list &>> /dev/null + # fi + echo -e "deb http://download.opensuse.org/repositories/home:/rtCamp:/EasyEngine/Debian_7.0/ /" >> /etc/apt/sources.list.d/wo-repo.list + gpg --keyserver "hkp://pgp.mit.edu" --recv-keys '3050AC3CD2AE6F03' + gpg -a --export --armor '3050AC3CD2AE6F03' | apt-key add - + if [ -f /etc/nginx/conf.d/wo-nginx.conf ]; then + mv /etc/nginx/conf.d/wo-nginx.conf /etc/nginx/conf.d/wo-nginx.conf.old &>> /dev/null + fi + mv /etc/nginx/nginx.conf /etc/nginx/nginx.conf.old &>> /dev/null + mv /etc/nginx/fastcgi_params /etc/nginx/fastcgi_params.old &>> /dev/null + apt-get update + service nginx stop &>> /dev/null + DEBIAN_FRONTEND=noninteractive apt-get -o Dpkg::Options::="--force-confmiss" -o Dpkg::Options::="--force-confold" -y install nginx-custom + service nginx restart &>> /dev/null + fi + dpkg --get-selections | grep -v deinstall | grep nginx-common + if [ $? -eq 0 ]; then + apt-get update + service nginx stop &>> /dev/null + DEBIAN_FRONTEND=noninteractive apt-get -o Dpkg::Options::="--force-confmiss" -o Dpkg::Options::="--force-confold" -y install nginx-ee nginx-custom + service nginx restart &>> /dev/null + fi + elif [ "$wo_distro_version" == "jessie" ]; then + + grep -Hr 'http://download.opensuse.org/repositories/home:/rtCamp:/EasyEngine/Debian_8.0/ /' /etc/apt/sources.list.d/ &>> /dev/null + #grep -Hr "deb http://packages.dotdeb.org jessie all" /etc/apt/sources.list.d/wo-repo.list &>> /dev/null + if [[ $? -ne 0 ]]; then + #sed -i "/deb http:\/\/packages.dotdeb.org jessie all/d" /etc/apt/sources.list.d/wo-repo.list &>> /dev/null + echo -e "deb http://download.opensuse.org/repositories/home:/rtCamp:/EasyEngine/Debian_8.0/ /" >> /etc/apt/sources.list.d/wo-repo.list + gpg --keyserver "hkp://pgp.mit.edu" --recv-keys '3050AC3CD2AE6F03' + gpg -a --export --armor '3050AC3CD2AE6F03' | apt-key add - + if [ -f /etc/nginx/conf.d/ee-nginx.conf ]; then + mv /etc/nginx/conf.d/ee-nginx.conf /etc/nginx/conf.d/ee-nginx.conf.old &>> /dev/null + fi + mv /etc/nginx/nginx.conf /etc/nginx/nginx.conf.old &>> /dev/null + mv /etc/nginx/fastcgi_params /etc/nginx/fastcgi_params.old &>> /dev/null + apt-get update + service nginx stop &>> /dev/null + apt-get -o Dpkg::Options::="--force-confmiss" -o Dpkg::Options::="--force-confold" -y install nginx-custom + service nginx restart &>> /dev/null + fi + dpkg --get-selections | grep -v deinstall | grep nginx-common + if [ $? -eq 0 ]; then + apt-get update + dpkg --get-selections | grep -v deinstall | grep nginx-mainline + if [ $? -eq 0 ]; then + apt-get remove -y nginx-mainline + fi + service nginx stop &>> /dev/null + DEBIAN_FRONTEND=noninteractive apt-get -o Dpkg::Options::="--force-confmiss" -o Dpkg::Options::="--force-confold" -y --allow-unauthenticated install nginx-ee nginx-custom + service nginx restart &>> /dev/null + fi + fi + fi + + if [ -f /etc/nginx/nginx.conf ]; then + sed -i "s/.*X-Powered-By.*/\tadd_header X-Powered-By \"WordOps $wo_version_new\";/" /etc/nginx/nginx.conf &>> /dev/null + fi + + if [ -f /etc/nginx/conf.d/wo-plus.conf ]; then + sed -i "s/.*X-Powered-By.*/\tadd_header X-Powered-By \"WordOps $wo_version_new\";/" /etc/nginx/conf.d/wo-plus.conf &>> /dev/null + fi + + # Disable Xdebug on old systems if and only if wo debug is off + if [ -f /etc/php5/mods-available/xdebug.ini ]; then + wo_debug_value=$(grep -Hr 9001 /etc/nginx/conf.d/upstream.conf | wc -l ) + if [ $wo_debug_value -eq 1 ]; then + grep -Hr ";zend_extension" /etc/php5/mods-available/xdebug.ini &>> /dev/null + if [ $? -ne 0 ]; then + sed -i "s/zend_extension/;zend_extension/" /etc/php5/mods-available/xdebug.ini + fi + fi + fi + + # Fix HHVM autostart on reboot + dpkg --get-selections | grep -v deinstall | grep hhvm &>> /dev/null + if [ $? -eq 0 ]; then + update-rc.d hhvm defaults &>> /dev/null + fi + + # Fix WordPress example.html issue + # Ref: http://wptavern.com/xss-vulnerability-in-jetpack-and-the-twenty-fifteen-default-theme-affects-millions-of-wordpress-users + dpkg --get-selections | grep -v deinstall | grep nginx &>> /dev/null + if [ $? -eq 0 ]; then + cp /usr/lib/wo/templates/locations.mustache /etc/nginx/common/locations.conf &>> /dev/null + fi + + # Fix HHVM upstream issue that was preventing from using EasyEngine for site operations + if [ -f /etc/nginx/conf.d/upstream.conf ]; then + grep -Hr hhvm /etc/nginx/conf.d/upstream.conf &>> /dev/null + if [ $? -ne 0 ]; then + echo -e "upstream hhvm {\n# HHVM Pool\nserver 127.0.0.1:8000;\nserver 127.0.0.1:9000 backup;\n}\n" >> /etc/nginx/conf.d/upstream.conf + fi + fi + + # Fix HHVM server IP + if [ -f /etc/hhvm/server.ini ]; then + grep -Hr "hhvm.server.ip" /etc/hhvm/server.ini &>> /dev/null + if [ $? -ne 0 ]; then + echo -e "hhvm.server.ip = 127.0.0.1\n" >> /etc/hhvm/server.ini + fi + fi + + + # Rename Redis Header + if [ -f /etc/nginx/common/redis-hhvm.conf ]; then + sed -i "s/X-Cache /X-SRCache-Fetch-Status /g" /etc/nginx/common/redis-hhvm.conf &>> /dev/null + sed -i "s/X-Cache-2 /X-SRCache-Store-Status /g" /etc/nginx/common/redis-hhvm.conf &>> /dev/null + fi + + if [ -f /etc/nginx/common/redis.conf ]; then + sed -i "s/X-Cache /X-SRCache-Fetch-Status /g" /etc/nginx/common/redis.conf &>> /dev/null + sed -i "s/X-Cache-2 /X-SRCache-Store-Status /g" /etc/nginx/common/redis.conf &>> /dev/null + fi + + + if [ -f /etc/nginx/common/redis-hhvm.conf ]; then + # Update Timeout redis-hhvm.conf + grep -0 'redis2_query expire $key 6h' /etc/nginx/common/redis-hhvm.conf &>> /dev/null + if [ $? -eq 0 ]; then + sed -i 's/redis2_query expire $key 6h/redis2_query expire $key 14400/g' /etc/nginx/common/redis-hhvm.conf &>> /dev/null + fi + + #Fix for 3.3.4 redis-hhvm issue + grep -0 'HTTP_ACCEPT_ENCODING' /etc/nginx/common/redis-hhvm.conf &>> /dev/null + if [ $? -ne 0 ]; then + sed -i 's/fastcgi_params;/fastcgi_params;\n fastcgi_param HTTP_ACCEPT_ENCODING "";/g' /etc/nginx/common/redis-hhvm.conf &>> /dev/null + fi + fi + + #Fix Security Issue. commit #c64f28e + if [ -f /etc/nginx/common/locations.conf ]; then + grep -0 '$request_uri ~\* \"^.+(readme|license|example)\\.(txt|html)$\"' /etc/nginx/common/locations.conf &>> /dev/null + if [ $? -eq 0 ]; then + sed -i 's/$request_uri ~\* \"^.+(readme|license|example)\\.(txt|html)$\"/$uri ~\* \"^.+(readme|license|example)\\.(txt|html)$\"/g' /etc/nginx/common/locations.conf &>> /dev/null + fi + fi + + #Fix Redis-server security issue + #http://redis.io/topics/security + if [ -f /etc/redis/redis.conf ]; then + grep -0 -v "#" /etc/redis/redis.conf | grep 'bind' &>> /dev/null + if [ $? -ne 0 ]; then + sed -i '$ a bind 127.0.0.1' /etc/redis/redis.conf &>> /dev/null + service redis-server restart &>> /dev/null + fi + fi + + #Fix For --letsencrypt + if [ -f /etc/nginx/common/locations.conf ]; then + grep -0 'location ~ \/\\.well-known' /etc/nginx/common/locations.conf &>> /dev/null + if [ $? -ne 0 ]; then + sed -i 's/# Deny hidden files/# Deny hidden files\nlocation ~ \/\\.well-known {\n allow all;\n}\n /g' /etc/nginx/common/locations.conf &>> /dev/null + fi + fi + + # Fix for 3.3.2 renamed nginx.conf + nginx -V 2>&1 &>>/dev/null + if [[ $? -eq 0 ]]; then + nginx -t 2>&1 | grep 'open() "/etc/nginx/nginx.conf" failed' &>>/dev/null + if [[ $? -eq 0 ]]; then + if [ -f /etc/nginx/nginx.conf.old ]; then + if [ ! -f /etc/nginx/nginx.conf ]; then + cp /etc/nginx/nginx.conf.old /etc/nginx/nginx.conf + fi + fi + fi + # Fix for 3.3.2 renamed fastcgi_param + nginx -t 2>&1 | grep 'open() "/etc/nginx/fastcgi_params" failed' &>>/dev/null + if [[ $? -eq 0 ]]; then + if [ -f /etc/nginx/fastcgi_params.old ]; then + if [ ! -f /etc/nginx/fastcgi_params ]; then + cp /etc/nginx/fastcgi_params.old /etc/nginx/fastcgi_params + fi + fi + fi + fi + + #Fix For ssl_ciphers + if [ -f /etc/nginx/nginx.conf ]; then + sed -i 's/HIGH:!aNULL:!MD5:!kEDH;/ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:ECDHE-RSA-DES-CBC3-SHA:ECDHE-ECDSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA;/' /etc/nginx/nginx.conf + fi + + #Fix for SSL cert --all + crontab -l | grep -q '\-\-min_expiry_limit' + if [[ $? -eq 0 ]]; then + crontab -l > /var/spool/cron/cron-backup.txt #backup cron before editing + crontab -l | sed '/--min_expiry_limit/d' | crontab - + /bin/bash -c "crontab -l 2> /dev/null | { cat; echo -e \"\n0 0 * * 0 wo site update --le=renew --all 2> /dev/null # Renew all letsencrypt SSL cert. Set by EasyEngine\"; } | crontab -" + fi + + + +} + +# Do git intialisation +function wo_git_init() +{ + # Nginx under git version control + if [ -d /etc/nginx ];then + cd /etc/nginx + if [ ! -d /etc/nginx/.git ]; then + git init &>> /dev/null + fi + git add -A . + git commit -am "Updated Nginx" > /dev/null + fi + # EasyEngine under git version control + cd /etc/wo + if [ ! -d /etc/wo/.git ]; then + git init > /dev/null + fi + git add -A . + git commit -am "Installed/Updated to WordOps" &>> /dev/null + + #PHP under git version control + if [ -d /etc/php ];then + cd /etc/php + if [ ! -d /etc/php/.git ]; then + git init &>> /dev/null + fi + git add -A . + git commit -am "Updated PHP" > /dev/null + fi + +} + +# Update WordOps +if [ -f /usr/local/sbin/easyengine ]; then + # Check old EasyEngine version + ee version | grep ${wo_version_old} &>> /dev/null + if [[ $? -ne 0 ]]; then + wo_lib_echo "WordOps/EasyEngine $wo_version_old not found on your system" | tee -ai $wo_install_log + wo_lib_echo "Updating your EasyEngine to $wo_version_old for compability" | tee -ai $wo_install_log + wget -q https://raw.githubusercontent.com/EasyEngine/easyengine/old-stable/bin/update && bash update + if [[ $? -ne 0 ]]; then + wo_lib_echo_fail "Unable to update EasyEngine to $wo_version_old, exit status = " $? + exit 100 + fi + fi + read -p "Update WordOps to $wo_version_new (y/n): " wo_ans + if [ "$wo_ans" = "y" ] || [ "$wo_ans" = "Y" ]; then + wo_install_dep | tee -ai $wo_install_log + wo_sync_db 2&>>1 $WO_INSTALL_LOG + secure_wo_db | tee -ai $WO_INSTALL_LOG + wo_upgrade_php | tee -ai $wo_install_log + wo_install | tee -ai $wo_install_log + wo_update | tee -ai $wo_install_log + wo_update_latest | tee -ai $wo_install_log + wo_git_init | tee -ai $wo_install_log + else + wo_lib_error "Not updating WordOps to $wo_version_new, exit status = " 1 + fi +elif [ ! -f /usr/local/bin/wo ]; then + wo_lib_echo "Installing depedencies" | tee -ai $wo_install_log + wo_install_dep | tee -ai $wo_install_log + wo_lib_echo "Installing WordOps $wo_branch" | tee -ai $wo_install_log + wo_install | tee -ai $wo_install_log + wo_lib_echo "Running post-install steps" | tee -ai $wo_install_log + secure_wo_db | tee -ai $WO_INSTALL_LOG + wo_git_init | tee -ai $wo_install_log + +else + wo -v 2>&1 | grep $wo_version_new &>> /dev/null + if [[ $? -ne 0 ]];then + read -p "Update WordOps to $wo_version_new (y/n): " wo_ans + if [ "$wo_ans" = "y" ] || [ "$wo_ans" = "Y" ]; then + wo_install_dep | tee -ai $wo_install_log + wo_sync_db 2&>>1 $WO_INSTALL_LOG + secure_wo_db | tee -ai $WO_INSTALL_LOG + wo_upgrade_php | tee -ai $wo_install_log + wo_install | tee -ai $wo_install_log + wo_update_latest | tee -ai $wo_install_log + wo_git_init | tee -ai $wo_install_log + service nginx reload &>> /dev/null + if [ "$wo_distro_version" == "trusty" ]; then + service php5.6-fpm restart &>> /dev/null + else + service php5-fpm restart &>> /dev/null + fi + wo_update_wp_cli | tee -ai $wo_install_log + else + wo_lib_error "Not updating WordOps to $wo_version_new, exit status = " 1 + fi + else + wo_lib_error "You already have WordOps $wo_version_new, exit status = " 1 + fi +fi +wo sync | tee -ai $WO_INSTALL_LOG + +echo +wo_lib_echo "For WordOps (wo) auto completion, run the following command" +echo +wo_lib_echo_info "source /etc/bash_completion.d/wo_auto.rc" +echo +wo_lib_echo "Yay! WordOps (wo) installed/updated successfully" +wo_lib_echo "WordOps (wo) help: https://wordops.org/docs" diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..24fb221 --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +cement>=2.4.0 diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..2313b24 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,9 @@ +[nosetests] +verbosity=3 +debug=0 +detailed-errors=1 +with-coverage=1 +cover-package=wo +cover-erase=1 +cover-html=1 +cover-html-dir=coverage_report/ diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..d48d1bd --- /dev/null +++ b/setup.py @@ -0,0 +1,103 @@ + +from setuptools import setup, find_packages +import sys +import os +import glob +import configparser +import re +import shutil + +conf = [] +templates = [] + +long_description = '''WordOps is the commandline tool to manage your + Websites based on WordPress and Nginx with easy to use + commands''' + +for name in glob.glob('config/plugins.d/*.conf'): + conf.insert(1, name) + +for name in glob.glob('wo/cli/templates/*.mustache'): + templates.insert(1, name) + +if not os.path.exists('/var/log/wo/'): + os.makedirs('/var/log/wo/') + +if not os.path.exists('/var/lib/wo/'): + os.makedirs('/var/lib/wo/') + +# WordOps git configuration management +config = configparser.ConfigParser() +config.read(os.path.expanduser("~")+'/.gitconfig') +try: + wo_user = config['user']['name'] + wo_email = config['user']['email'] +except Exception as e: + print("WordOps (wo) required your name & email address to track" + " changes you made under the Git version control") + print("WordOps (wo) will be able to send you daily reports & alerts in " + "upcoming version") + print("WordOps (wo) will NEVER share your information with other parties") + + wo_user = input("Enter your name: ") + while wo_user is "": + print("Unfortunately, this can't be left blank") + wo_user = input("Enter your name: ") + + wo_email = input("Enter your email: ") + + while not re.match(r"^[A-Za-z0-9\.\+_-]+@[A-Za-z0-9\._-]+\.[a-zA-Z]*$", + wo_email): + print("Whoops, seems like you made a typo - the e-mailaddress is invalid...") + wo_email = input("Enter your email: ") + + os.system("git config --global user.name {0}".format(wo_user)) + os.system("git config --global user.email {0}".format(wo_email)) + +if not os.path.isfile('/root/.gitconfig'): + shutil.copy2(os.path.expanduser("~")+'/.gitconfig', '/root/.gitconfig') + +setup(name='wo', + version='3.8.1', + description=long_description, + long_description=long_description, + classifiers=[], + keywords='', + author='WordOps', + author_email='core@wordops.org', + url='https://wordops.io', + license='MIT', + packages=find_packages(exclude=['ez_setup', 'examples', 'tests', + 'templates']), + include_package_data=True, + zip_safe=False, + test_suite='nose.collector', + install_requires=[ + # Required to build documentation + # "Sphinx >= 1.0", + # Required for testing + # "nose", + # "coverage", + # Required to function + 'cement == 2.4', + 'pystache', + 'python-apt', + 'pynginxconfig', + 'PyMySQL == 0.8.0', + 'psutil == 3.1.1', + 'sh', + 'SQLAlchemy', + ], + data_files=[('/etc/wo', ['config/wo.conf']), + ('/etc/wo/plugins.d', conf), + ('/usr/lib/wo/templates', templates), + ('/etc/bash_completion.d/', + ['config/bash_completion.d/wo_auto.rc']), + ('/usr/share/man/man8/', ['docs/wo.8'])], + setup_requires=[], + entry_points=""" + [console_scripts] + wo = wo.cli.main:main + """, + namespace_packages=[], + ) diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/cli/13_test_stack.py b/tests/cli/13_test_stack.py new file mode 100644 index 0000000..107adc7 --- /dev/null +++ b/tests/cli/13_test_stack.py @@ -0,0 +1,64 @@ +from wo.utils import test +from wo.cli.main import get_test_app + + +class CliTestCaseStack(test.WOTestCase): + + def test_wo_cli(self): + self.app.setup() + self.app.run() + self.app.close() + + def test_wo_cli_stack_install_web(self): + self.app = get_test_app(argv=['stack', 'install', '--web']) + self.app.setup() + self.app.run() + self.app.close() + + def test_wo_cli_stack_install_admin(self): + self.app = get_test_app(argv=['stack', 'install', '--admin']) + self.app.setup() + self.app.run() + self.app.close() + + def test_wo_cli_stack_install_nginx(self): + self.app = get_test_app(argv=['stack', 'install', '--nginx']) + self.app.setup() + self.app.run() + self.app.close() + + def test_wo_cli_stack_install_php(self): + self.app = get_test_app(argv=['stack', 'install', '--php']) + self.app.setup() + self.app.run() + self.app.close() + + def test_wo_cli_stack_install_mysql(self): + self.app = get_test_app(argv=['stack', 'install', '--mysql']) + self.app.setup() + self.app.run() + self.app.close() + + def test_wo_cli_stack_install_wpcli(self): + self.app = get_test_app(argv=['stack', 'install', '--wpcli']) + self.app.setup() + self.app.run() + self.app.close() + + def test_wo_cli_stack_install_phpmyadmin(self): + self.app = get_test_app(argv=['stack', 'install', '--phpmyadmin']) + self.app.setup() + self.app.run() + self.app.close() + + def test_wo_cli_stack_install_adminer(self): + self.app = get_test_app(argv=['stack', 'install', '--adminer']) + self.app.setup() + self.app.run() + self.app.close() + + def test_wo_cli_stack_install_utils(self): + self.app = get_test_app(argv=['stack', 'install', '--utils']) + self.app.setup() + self.app.run() + self.app.close() diff --git a/tests/cli/2_test_stack_services_start.py b/tests/cli/2_test_stack_services_start.py new file mode 100644 index 0000000..4b7323d --- /dev/null +++ b/tests/cli/2_test_stack_services_start.py @@ -0,0 +1,40 @@ +from wo.utils import test +from wo.cli.main import get_test_app + + +class CliTestCaseStack(test.WOTestCase): + + def test_wo_cli(self): + self.app.setup() + self.app.run() + self.app.close() + + def test_wo_cli_stack_services_start_nginx(self): + self.app = get_test_app(argv=['stack', 'start', '--nginx']) + self.app.setup() + self.app.run() + self.app.close() + + def test_wo_cli_stack_services_start_php5_fpm(self): + self.app = get_test_app(argv=['stack', 'start', '--php']) + self.app.setup() + self.app.run() + self.app.close() + + def test_wo_cli_stack_services_start_mysql(self): + self.app = get_test_app(argv=['stack', 'start', '--mysql']) + self.app.setup() + self.app.run() + self.app.close() + + def test_wo_cli_stack_services_start_memcached(self): + self.app = get_test_app(argv=['stack', 'start', '--memcache']) + self.app.setup() + self.app.run() + self.app.close() + + def test_wo_cli_stack_services_start_all(self): + self.app = get_test_app(argv=['stack', 'start']) + self.app.setup() + self.app.run() + self.app.close() diff --git a/tests/cli/3_test_stack_services_status.py b/tests/cli/3_test_stack_services_status.py new file mode 100644 index 0000000..9527779 --- /dev/null +++ b/tests/cli/3_test_stack_services_status.py @@ -0,0 +1,40 @@ +from wo.utils import test +from wo.cli.main import get_test_app + + +class CliTestCaseStack(test.WOTestCase): + + def test_wo_cli(self): + self.app.setup() + self.app.run() + self.app.close() + + def test_wo_cli_stack_services_status_nginx(self): + self.app = get_test_app(argv=['stack', 'status', '--nginx']) + self.app.setup() + self.app.run() + self.app.close() + + def test_wo_cli_stack_services_status_php5_fpm(self): + self.app = get_test_app(argv=['stack', 'status', '--php']) + self.app.setup() + self.app.run() + self.app.close() + + def test_wo_cli_stack_services_status_mysql(self): + self.app = get_test_app(argv=['stack', 'status', '--mysql']) + self.app.setup() + self.app.run() + self.app.close() + + def test_wo_cli_stack_services_status_memcached(self): + self.app = get_test_app(argv=['stack', 'status', '--memcache']) + self.app.setup() + self.app.run() + self.app.close() + + def test_wo_cli_stack_services_status_all(self): + self.app = get_test_app(argv=['stack', 'status']) + self.app.setup() + self.app.run() + self.app.close() diff --git a/tests/cli/4_test_stack_services_stop.py b/tests/cli/4_test_stack_services_stop.py new file mode 100644 index 0000000..17da27c --- /dev/null +++ b/tests/cli/4_test_stack_services_stop.py @@ -0,0 +1,40 @@ +from wo.utils import test +from wo.cli.main import get_test_app + + +class CliTestCaseStack(test.WOTestCase): + + def test_wo_cli(self): + self.app.setup() + self.app.run() + self.app.close() + + def test_wo_cli_stack_services_stop_nginx(self): + self.app = get_test_app(argv=['stack', 'stop', '--nginx']) + self.app.setup() + self.app.run() + self.app.close() + + def test_wo_cli_stack_services_stop_php5_fpm(self): + self.app = get_test_app(argv=['stack', 'stop', '--php']) + self.app.setup() + self.app.run() + self.app.close() + + def test_wo_cli_stack_services_stop_mysql(self): + self.app = get_test_app(argv=['stack', 'stop', '--mysql']) + self.app.setup() + self.app.run() + self.app.close() + + def test_wo_cli_stack_services_stop_memcached(self): + self.app = get_test_app(argv=['stack', 'stop', '--memcache']) + self.app.setup() + self.app.run() + self.app.close() + + def test_wo_cli_stack_services_stop_all(self): + self.app = get_test_app(argv=['stack', 'stop']) + self.app.setup() + self.app.run() + self.app.close() diff --git a/tests/cli/5_test_stack_services_restart.py b/tests/cli/5_test_stack_services_restart.py new file mode 100644 index 0000000..20e51d8 --- /dev/null +++ b/tests/cli/5_test_stack_services_restart.py @@ -0,0 +1,40 @@ +from wo.utils import test +from wo.cli.main import get_test_app + + +class CliTestCaseStack(test.WOTestCase): + + def test_wo_cli(self): + self.app.setup() + self.app.run() + self.app.close() + + def test_wo_cli_stack_services_restart_nginx(self): + self.app = get_test_app(argv=['stack', 'restart', '--nginx']) + self.app.setup() + self.app.run() + self.app.close() + + def test_wo_cli_stack_services_restart_php5_fpm(self): + self.app = get_test_app(argv=['stack', 'restart', '--php']) + self.app.setup() + self.app.run() + self.app.close() + + def test_wo_cli_stack_services_restart_mysql(self): + self.app = get_test_app(argv=['stack', 'restart', '--mysql']) + self.app.setup() + self.app.run() + self.app.close() + + def test_wo_cli_stack_services_restart_memcached(self): + self.app = get_test_app(argv=['stack', 'restart', '--memcache']) + self.app.setup() + self.app.run() + self.app.close() + + def test_wo_cli_stack_services_restart_all(self): + self.app = get_test_app(argv=['stack', 'restart']) + self.app.setup() + self.app.run() + self.app.close() diff --git a/tests/cli/8_test_site_create.py b/tests/cli/8_test_site_create.py new file mode 100644 index 0000000..2465cc6 --- /dev/null +++ b/tests/cli/8_test_site_create.py @@ -0,0 +1,66 @@ +from wo.utils import test +from wo.cli.main import get_test_app + + +class CliTestCaseSite(test.WOTestCase): + + def test_wo_cli(self): + self.app.setup() + self.app.run() + self.app.close() + + def test_wo_cli_site_create_html(self): + self.app = get_test_app(argv=['site', 'create', 'example1.com', + '--html']) + self.app.setup() + self.app.run() + self.app.close() + + def test_wo_cli_site_create_php(self): + self.app = get_test_app(argv=['site', 'create', 'example2.com', + '--php']) + self.app.setup() + self.app.run() + self.app.close() + + def test_wo_cli_site_create_mysql(self): + self.app = get_test_app(argv=['site', 'create', 'example3.com', + '--mysql']) + self.app.setup() + self.app.run() + self.app.close() + + def test_wo_cli_site_create_wp(self): + self.app = get_test_app(argv=['site', 'create', 'example4.com', + '--wp']) + self.app.setup() + self.app.run() + self.app.close() + + def test_wo_cli_site_create_wpsubdir(self): + self.app = get_test_app(argv=['site', 'create', 'example5.com', + '--wpsubdir']) + self.app.setup() + self.app.run() + self.app.close() + + def test_wo_cli_site_create_wpsubdomain(self): + self.app = get_test_app(argv=['site', 'create', 'example6.com', + '--wpsubdomain']) + self.app.setup() + self.app.run() + self.app.close() + + def test_wo_cli_site_create_wpfc(self): + self.app = get_test_app(argv=['site', 'create', 'example8.com', + '--wpfc']) + self.app.setup() + self.app.run() + self.app.close() + + def test_wo_cli_site_create_wpsc(self): + self.app = get_test_app(argv=['site', 'create', 'example9.com', + '--wpsc']) + self.app.setup() + self.app.run() + self.app.close() diff --git a/tests/cli/91_test_site_info.py b/tests/cli/91_test_site_info.py new file mode 100644 index 0000000..38caeff --- /dev/null +++ b/tests/cli/91_test_site_info.py @@ -0,0 +1,16 @@ +from wo.utils import test +from wo.cli.main import get_test_app + + +class CliTestCaseSite(test.WOTestCase): + + def test_wo_cli(self): + self.app.setup() + self.app.run() + self.app.close() + + def test_wo_cli_site_info(self): + self.app = get_test_app(argv=['site', 'info', 'example1.com']) + self.app.setup() + self.app.run() + self.app.close() diff --git a/tests/cli/93_test_site_list.py b/tests/cli/93_test_site_list.py new file mode 100644 index 0000000..da3ef8d --- /dev/null +++ b/tests/cli/93_test_site_list.py @@ -0,0 +1,22 @@ +from wo.utils import test +from wo.cli.main import get_test_app + + +class CliTestCaseSite(test.WOTestCase): + + def test_wo_cli(self): + self.app.setup() + self.app.run() + self.app.close() + + def test_wo_cli_site_list_enable(self): + self.app = get_test_app(argv=['site', 'list', '--enabled']) + self.app.setup() + self.app.run() + self.app.close() + + def test_wo_cli_site_list_disable(self): + self.app = get_test_app(argv=['site', 'list', '--disabled']) + self.app.setup() + self.app.run() + self.app.close() diff --git a/tests/cli/95_test_site_show.py b/tests/cli/95_test_site_show.py new file mode 100644 index 0000000..bbf81e2 --- /dev/null +++ b/tests/cli/95_test_site_show.py @@ -0,0 +1,16 @@ +from wo.utils import test +from wo.cli.main import get_test_app + + +class CliTestCaseSite(test.WOTestCase): + + def test_wo_cli(self): + self.app.setup() + self.app.run() + self.app.close() + + def test_wo_cli_show_edit(self): + self.app = get_test_app(argv=['site', 'show', 'example1.com']) + self.app.setup() + self.app.run() + self.app.close() diff --git a/tests/cli/97_test_site_update.py b/tests/cli/97_test_site_update.py new file mode 100644 index 0000000..3d111c7 --- /dev/null +++ b/tests/cli/97_test_site_update.py @@ -0,0 +1,66 @@ +from wo.utils import test +from wo.cli.main import get_test_app + + +class CliTestCaseSite(test.WOTestCase): + + def test_wo_cli(self): + self.app.setup() + self.app.run() + self.app.close() + + def test_wo_cli_site_update_html(self): + self.app = get_test_app(argv=['site', 'update', 'example2.com', + '--html']) + self.app.setup() + self.app.run() + self.app.close() + + def test_wo_cli_site_update_php(self): + self.app = get_test_app(argv=['site', 'update', 'example1.com', + '--php']) + self.app.setup() + self.app.run() + self.app.close() + + def test_wo_cli_site_update_mysql(self): + self.app = get_test_app(argv=['site', 'update', 'example1.com', + '--html']) + self.app.setup() + self.app.run() + self.app.close() + + def test_wo_cli_site_update_wp(self): + self.app = get_test_app(argv=['site', 'update', 'example5.com', + '--wp']) + self.app.setup() + self.app.run() + self.app.close() + + def test_wo_cli_site_update_wpsubdir(self): + self.app = get_test_app(argv=['site', 'update', 'example4.com', + '--wpsubdir']) + self.app.setup() + self.app.run() + self.app.close() + + def test_wo_cli_site_update_wpsubdomain(self): + self.app = get_test_app(argv=['site', 'update', 'example7.com', + '--wpsubdomain']) + self.app.setup() + self.app.run() + self.app.close() + + def test_wo_cli_site_update_wpfc(self): + self.app = get_test_app(argv=['site', 'update', 'example9.com', + '--wpfc']) + self.app.setup() + self.app.run() + self.app.close() + + def test_wo_cli_site_update_wpsc(self): + self.app = get_test_app(argv=['site', 'update', 'example6.com', + '--wpsc']) + self.app.setup() + self.app.run() + self.app.close() diff --git a/tests/cli/9_test_site_enable.py b/tests/cli/9_test_site_enable.py new file mode 100644 index 0000000..08b15c9 --- /dev/null +++ b/tests/cli/9_test_site_enable.py @@ -0,0 +1,16 @@ +from wo.utils import test +from wo.cli.main import get_test_app + + +class CliTestCaseSite(test.WOTestCase): + + def test_wo_cli(self): + self.app.setup() + self.app.run() + self.app.close() + + def test_wo_cli_site_enable(self): + self.app = get_test_app(argv=['site', 'enable', 'example2.com']) + self.app.setup() + self.app.run() + self.app.close() diff --git a/tests/cli/__init__.py b/tests/cli/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/cli/a_test_site_disable.py b/tests/cli/a_test_site_disable.py new file mode 100644 index 0000000..058313b --- /dev/null +++ b/tests/cli/a_test_site_disable.py @@ -0,0 +1,16 @@ +from wo.utils import test +from wo.cli.main import get_test_app + + +class CliTestCaseSite(test.WOTestCase): + + def test_wo_cli(self): + self.app.setup() + self.app.run() + self.app.close() + + def test_wo_cli_site_disable(self): + self.app = get_test_app(argv=['site', 'disable', 'example2.com']) + self.app.setup() + self.app.run() + self.app.close() diff --git a/tests/cli/ext/__init__.py b/tests/cli/ext/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/cli/plugins/__init__.py b/tests/cli/plugins/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/cli/plugins/test_example.py b/tests/cli/plugins/test_example.py new file mode 100644 index 0000000..d3745b4 --- /dev/null +++ b/tests/cli/plugins/test_example.py @@ -0,0 +1,8 @@ +"""Tests for Example Plugin.""" + +from wo.utils import test + +class ExamplePluginTestCase(test.WOTestCase): + def test_load_example_plugin(self): + self.app.setup() + self.app.plugin.load_plugin('example') diff --git a/tests/cli/test_clean.py b/tests/cli/test_clean.py new file mode 100644 index 0000000..a21a7d2 --- /dev/null +++ b/tests/cli/test_clean.py @@ -0,0 +1,40 @@ +from wo.utils import test +from wo.cli.main import get_test_app + + +class CliTestCaseClean(test.WOTestCase): + + def test_wo_cli(self): + self.app.setup() + self.app.run() + self.app.close() + + def test_wo_cli_clean(self): + self.app = get_test_app(argv=['clean']) + self.app.setup() + self.app.run() + self.app.close() + + def test_wo_cli_clean_fastcgi(self): + self.app = get_test_app(argv=['clean', '--fastcgi']) + self.app.setup() + self.app.run() + self.app.close() + + def test_wo_cli_clean_all(self): + self.app = get_test_app(argv=['clean', '--all']) + self.app.setup() + self.app.run() + self.app.close() + + def test_wo_cli_clean_memcache(self): + self.app = get_test_app(argv=['clean', '--memcache']) + self.app.setup() + self.app.run() + self.app.close() + + def test_wo_cli_clean_opcache(self): + self.app = get_test_app(argv=['clean', '--opcache']) + self.app.setup() + self.app.run() + self.app.close() diff --git a/tests/cli/test_debug.py b/tests/cli/test_debug.py new file mode 100644 index 0000000..026711b --- /dev/null +++ b/tests/cli/test_debug.py @@ -0,0 +1,95 @@ +from wo.utils import test +from wo.cli.main import get_test_app + + +class CliTestCaseDebug(test.WOTestCase): + + def test_wo_cli(self): + self.app.setup() + self.app.run() + self.app.close() + + def test_wo_cli_debug_stop(self): + self.app = get_test_app(argv=['debug', '--stop']) + self.app.setup() + self.app.run() + self.app.close() + + def test_wo_cli_debug_start(self): + self.app = get_test_app(argv=['debug', '--start']) + self.app.setup() + self.app.run() + self.app.close() + + def test_wo_cli_debug_php(self): + self.app = get_test_app(argv=['debug', '--php']) + self.app.setup() + self.app.run() + self.app.close() + + def test_wo_cli_debug_nginx(self): + self.app = get_test_app(argv=['debug', '--nginx']) + self.app.setup() + self.app.run() + self.app.close() + + def test_wo_cli_debug_rewrite(self): + self.app = get_test_app(argv=['debug', '--rewrite']) + self.app.setup() + self.app.run() + self.app.close() + + def test_wo_cli_debug_fpm(self): + self.app = get_test_app(argv=['debug', '--fpm']) + self.app.setup() + self.app.run() + self.app.close() + + def test_wo_cli_debug_mysql(self): + self.app = get_test_app(argv=['debug', '--mysql']) + self.app.setup() + self.app.run() + self.app.close() + + def test_wo_cli_debug_import_slow_log_interval(self): + self.app = get_test_app(argv=['debug', '--mysql', + '--import-slow-log-interval']) + self.app.setup() + self.app.run() + self.app.close() + + def test_wo_cli_debug_site_name_mysql(self): + self.app = get_test_app(argv=['debug', 'example3.com', '--mysql']) + self.app.setup() + self.app.run() + self.app.close() + + def test_wo_cli_debug_site_name_wp(self): + self.app = get_test_app(argv=['debug', 'example4.com', '--wp']) + self.app.setup() + self.app.run() + self.app.close() + + def test_wo_cli_debug_site_name_nginx(self): + self.app = get_test_app(argv=['debug', 'example4.com', '--nginx']) + self.app.setup() + self.app.run() + self.app.close() + + def test_wo_cli_debug_site_name_start(self): + self.app = get_test_app(argv=['debug', 'example1.com', '--start']) + self.app.setup() + self.app.run() + self.app.close() + + def test_wo_cli_debug_site_name_stop(self): + self.app = get_test_app(argv=['debug', 'example1.com', '--stop']) + self.app.setup() + self.app.run() + self.app.close() + + def test_wo_cli_debug_site_name_rewrite(self): + self.app = get_test_app(argv=['debug', 'example1.com', '--rewrite']) + self.app.setup() + self.app.run() + self.app.close() diff --git a/tests/cli/test_info.py b/tests/cli/test_info.py new file mode 100644 index 0000000..0d87c45 --- /dev/null +++ b/tests/cli/test_info.py @@ -0,0 +1,28 @@ +from wo.utils import test +from wo.cli.main import get_test_app + + +class CliTestCaseInfo(test.WOTestCase): + + def test_wo_cli(self): + self.app.setup() + self.app.run() + self.app.close() + + def test_wo_cli_info_mysql(self): + self.app = get_test_app(argv=['info', '--mysql']) + self.app.setup() + self.app.run() + self.app.close() + + def test_wo_cli_info_php(self): + self.app = get_test_app(argv=['info', '--php']) + self.app.setup() + self.app.run() + self.app.close() + + def test_wo_cli_info_nginx(self): + self.app = get_test_app(argv=['info', '--nginx']) + self.app.setup() + self.app.run() + self.app.close() diff --git a/tests/cli/test_secure.py b/tests/cli/test_secure.py new file mode 100644 index 0000000..4b6c527 --- /dev/null +++ b/tests/cli/test_secure.py @@ -0,0 +1,28 @@ +from wo.utils import test +from wo.cli.main import get_test_app + + +class CliTestCaseSecure(test.WOTestCase): + + def test_wo_cli(self): + self.app.setup() + self.app.run() + self.app.close() + + def test_wo_cli_secure_auth(self): + self.app = get_test_app(argv=['secure', '--auth']) + self.app.setup() + self.app.run() + self.app.close() + + def test_wo_cli_secure_port(self): + self.app = get_test_app(argv=['secure', '--port']) + self.app.setup() + self.app.run() + self.app.close() + + def test_wo_cli_secure_ip(self): + self.app = get_test_app(argv=['secure', '--ip']) + self.app.setup() + self.app.run() + self.app.close() diff --git a/tests/cli/test_site_delete.py b/tests/cli/test_site_delete.py new file mode 100644 index 0000000..7bebf44 --- /dev/null +++ b/tests/cli/test_site_delete.py @@ -0,0 +1,38 @@ +from wo.utils import test +from wo.cli.main import get_test_app + + +class CliTestCaseSite(test.WOTestCase): + + def test_wo_cli(self): + self.app.setup() + self.app.run() + self.app.close() + + def test_wo_cli_site_detele(self): + self.app = get_test_app(argv=['site', 'delete', 'example1.com', + '--no-prompt']) + self.app.setup() + self.app.run() + self.app.close() + + def test_wo_cli_site_detele_all(self): + self.app = get_test_app(argv=['site', 'delete', 'example2.com', + '--all', '--no-prompt']) + self.app.setup() + self.app.run() + self.app.close() + + def test_wo_cli_site_detele_db(self): + self.app = get_test_app(argv=['site', 'delete', 'example3.com', + '--db', '--no-prompt']) + self.app.setup() + self.app.run() + self.app.close() + + def test_wo_cli_site_detele_files(self): + self.app = get_test_app(argv=['site', 'delete', 'example4.com', + '--files', '--no-prompt']) + self.app.setup() + self.app.run() + self.app.close() diff --git a/tests/cli/test_stack_purge.py b/tests/cli/test_stack_purge.py new file mode 100644 index 0000000..3362360 --- /dev/null +++ b/tests/cli/test_stack_purge.py @@ -0,0 +1,64 @@ +from wo.utils import test +from wo.cli.main import get_test_app + + +class CliTestCaseStack(test.WOTestCase): + + def test_wo_cli(self): + self.app.setup() + self.app.run() + self.app.close() + + def test_wo_cli_stack_purge_web(self): + self.app = get_test_app(argv=['stack', 'purge', '--web']) + self.app.setup() + self.app.run() + self.app.close() + + def test_wo_cli_stack_purge_admin(self): + self.app = get_test_app(argv=['stack', 'purge', '--admin']) + self.app.setup() + self.app.run() + self.app.close() + + def test_wo_cli_stack_purge_nginx(self): + self.app = get_test_app(argv=['stack', 'purge', '--nginx']) + self.app.setup() + self.app.run() + self.app.close() + + def test_wo_cli_stack_purge_php(self): + self.app = get_test_app(argv=['stack', 'purge', '--php']) + self.app.setup() + self.app.run() + self.app.close() + + def test_wo_cli_stack_purge_mysql(self): + self.app = get_test_app(argv=['stack', 'purge', '--mysql']) + self.app.setup() + self.app.run() + self.app.close() + + def test_wo_cli_stack_purge_wpcli(self): + self.app = get_test_app(argv=['stack', 'purge', '--wpcli']) + self.app.setup() + self.app.run() + self.app.close() + + def test_wo_cli_stack_purge_phpmyadmin(self): + self.app = get_test_app(argv=['stack', 'purge', '--phpmyadmin']) + self.app.setup() + self.app.run() + self.app.close() + + def test_wo_cli_stack_purge_adminer(self): + self.app = get_test_app(argv=['stack', 'purge', '--adminer']) + self.app.setup() + self.app.run() + self.app.close() + + def test_wo_cli_stack_purge_utils(self): + self.app = get_test_app(argv=['stack', 'purge', '--utils']) + self.app.setup() + self.app.run() + self.app.close() diff --git a/tests/cli/test_stack_remove.py b/tests/cli/test_stack_remove.py new file mode 100644 index 0000000..7380e87 --- /dev/null +++ b/tests/cli/test_stack_remove.py @@ -0,0 +1,64 @@ +from wo.utils import test +from wo.cli.main import get_test_app + + +class CliTestCaseStack(test.WOTestCase): + + def test_wo_cli(self): + self.app.setup() + self.app.run() + self.app.close() + + def test_wo_cli_stack_remove_web(self): + self.app = get_test_app(argv=['stack', 'remove', '--web']) + self.app.setup() + self.app.run() + self.app.close() + + def test_wo_cli_stack_install_admin(self): + self.app = get_test_app(argv=['stack', 'remove', '--admin']) + self.app.setup() + self.app.run() + self.app.close() + + def test_wo_cli_stack_install_nginx(self): + self.app = get_test_app(argv=['stack', 'remove', '--nginx']) + self.app.setup() + self.app.run() + self.app.close() + + def test_wo_cli_stack_install_php(self): + self.app = get_test_app(argv=['stack', 'remove', '--php']) + self.app.setup() + self.app.run() + self.app.close() + + def test_wo_cli_stack_install_mysql(self): + self.app = get_test_app(argv=['stack', 'remove', '--mysql']) + self.app.setup() + self.app.run() + self.app.close() + + def test_wo_cli_stack_install_wpcli(self): + self.app = get_test_app(argv=['stack', 'remove', '--wpcli']) + self.app.setup() + self.app.run() + self.app.close() + + def test_wo_cli_stack_install_phpmyadmin(self): + self.app = get_test_app(argv=['stack', 'remove', '--phpmyadmin']) + self.app.setup() + self.app.run() + self.app.close() + + def test_wo_cli_stack_install_adminer(self): + self.app = get_test_app(argv=['stack', 'remove', '--adminer']) + self.app.setup() + self.app.run() + self.app.close() + + def test_wo_cli_stack_install_utils(self): + self.app = get_test_app(argv=['stack', 'remove', '--utils']) + self.app.setup() + self.app.run() + self.app.close() diff --git a/tests/core/__init__.py b/tests/core/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/wo/__init__.py b/wo/__init__.py new file mode 100644 index 0000000..de40ea7 --- /dev/null +++ b/wo/__init__.py @@ -0,0 +1 @@ +__import__('pkg_resources').declare_namespace(__name__) diff --git a/wo/cli/__init__.py b/wo/cli/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/wo/cli/bootstrap.py b/wo/cli/bootstrap.py new file mode 100644 index 0000000..c982bc0 --- /dev/null +++ b/wo/cli/bootstrap.py @@ -0,0 +1,11 @@ +"""WordOps bootstrapping.""" + +# All built-in application controllers should be imported, and registered +# in this file in the same way as WOBaseController. + +from cement.core import handler +from wo.cli.controllers.base import WOBaseController + + +def load(app): + handler.register(WOBaseController) diff --git a/wo/cli/controllers/__init__.py b/wo/cli/controllers/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/wo/cli/controllers/base.py b/wo/cli/controllers/base.py new file mode 100644 index 0000000..ad49a25 --- /dev/null +++ b/wo/cli/controllers/base.py @@ -0,0 +1,25 @@ +"""WordOps base controller.""" + +from cement.core.controller import CementBaseController, expose +from wo.core.variables import WOVariables +VERSION = WOVariables.wo_version + +BANNER = """ +WordOps v%s +Copyright (c) 2018 WordOps. +""" % VERSION + + +class WOBaseController(CementBaseController): + class Meta: + label = 'base' + description = ("WordOps is the commandline tool to manage your" + " websites based on WordPress and Nginx with easy to" + " use commands") + arguments = [ + (['-v', '--version'], dict(action='version', version=BANNER)), + ] + + @expose(hide=True) + def default(self): + self.app.args.print_help() diff --git a/wo/cli/ext/__init__.py b/wo/cli/ext/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/wo/cli/ext/wo_outputhandler.py b/wo/cli/ext/wo_outputhandler.py new file mode 100644 index 0000000..a39b5f9 --- /dev/null +++ b/wo/cli/ext/wo_outputhandler.py @@ -0,0 +1,20 @@ +# Based on https://github.com/datafolklabs/cement/issues/295 +# To avoid encoding releated error,we defined our custom output handler +# I hope we will remove this when we upgarde to Cement 2.6 (Not released yet) +import os +from cement.utils import fs +from cement.ext.ext_mustache import MustacheOutputHandler + + +class WOOutputHandler(MustacheOutputHandler): + class Meta: + label = 'wo_output_handler' + + def _load_template_from_file(self, path): + for templ_dir in self.app._meta.template_dirs: + full_path = fs.abspath(os.path.join(templ_dir, path)) + if os.path.exists(full_path): + self.app.log.debug('loading template file %s' % full_path) + return open(full_path, encoding='utf-8', mode='r').read() + else: + continue diff --git a/wo/cli/main.py b/wo/cli/main.py new file mode 100644 index 0000000..ccfcefe --- /dev/null +++ b/wo/cli/main.py @@ -0,0 +1,135 @@ +"""WordOps main application entry point.""" +import sys +import os + +# this has to happen after you import sys, but before you import anything +# from Cement "source: https://github.com/datafolklabs/cement/issues/290" +if '--debug' in sys.argv: + sys.argv.remove('--debug') + TOGGLE_DEBUG = True +else: + TOGGLE_DEBUG = False + +from cement.core import foundation +from cement.utils.misc import init_defaults +from cement.core.exc import FrameworkError, CaughtSignal +from cement.ext.ext_argparse import ArgParseArgumentHandler +from wo.core import exc +from wo.cli.ext.wo_outputhandler import WOOutputHandler + +# Application default. Should update config/wo.conf to reflect any +# changes, or additions here. +defaults = init_defaults('wo') + +# All internal/external plugin configurations are loaded from here +defaults['wo']['plugin_config_dir'] = '/etc/wo/plugins.d' + +# External plugins (generally, do not ship with application code) +defaults['wo']['plugin_dir'] = '/var/lib/wo/plugins' + +# External templates (generally, do not ship with application code) +defaults['wo']['template_dir'] = '/var/lib/wo/templates' + + +class WOArgHandler(ArgParseArgumentHandler): + class Meta: + label = 'wo_args_handler' + + def error(self, message): + super(WOArgHandler, self).error("unknown args") + + +class WOApp(foundation.CementApp): + class Meta: + label = 'wo' + + config_defaults = defaults + + # All built-in application bootstrapping (always run) + bootstrap = 'wo.cli.bootstrap' + + # Optional plugin bootstrapping (only run if plugin is enabled) + plugin_bootstrap = 'wo.cli.plugins' + + # Internal templates (ship with application code) + template_module = 'wo.cli.templates' + + # Internal plugins (ship with application code) + plugin_bootstrap = 'wo.cli.plugins' + + extensions = ['mustache'] + + # default output handler + output_handler = WOOutputHandler + + arg_handler = WOArgHandler + + debug = TOGGLE_DEBUG + + +class WOTestApp(WOApp): + """A test app that is better suited for testing.""" + class Meta: + argv = [] + config_files = [] + + +# Define the applicaiton object outside of main, as some libraries might wish +# to import it as a global (rather than passing it into another class/func) +app = WOApp() + + +def main(): + try: + global sys + # Default our exit status to 0 (non-error) + code = 0 + + # if not root...kick out + if not os.geteuid() == 0: + print("\nNon-privileged users cant use WordOps. Switch to root or invoke sudo.\n") + app.close(1) + + # Setup the application + app.setup() + + # Dump all arguments into wo log + app.log.debug(sys.argv) + + # Run the application + app.run() + except exc.WOError as e: + # Catch our application errors and exit 1 (error) + code = 1 + print(e) + except FrameworkError as e: + # Catch framework errors and exit 1 (error) + code = 1 + print(e) + except CaughtSignal as e: + # Default Cement signals are SIGINT and SIGTERM, exit 0 (non-error) + code = 0 + print(e) + except Exception as e: + code = 1 + print(e) + finally: + # Print an exception (if it occurred) and --debug was passed + if app.debug: + import sys + import traceback + + exc_type, exc_value, exc_traceback = sys.exc_info() + if exc_traceback is not None: + traceback.print_exc() + + # # Close the application + app.close(code) + + +def get_test_app(**kw): + app = WOApp(**kw) + return app + +if __name__ == '__main__': + main() diff --git a/wo/cli/plugins/__init__.py b/wo/cli/plugins/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/wo/cli/plugins/clean.py b/wo/cli/plugins/clean.py new file mode 100644 index 0000000..38ddf61 --- /dev/null +++ b/wo/cli/plugins/clean.py @@ -0,0 +1,104 @@ +"""Clean Plugin for WordOps.""" + +from wo.core.shellexec import WOShellExec +from wo.core.aptget import WOAptGet +from wo.core.services import WOService +from wo.core.logging import Log +from cement.core.controller import CementBaseController, expose +from cement.core import handler, hook +import os +import urllib.request + + +def wo_clean_hook(app): + pass + + +class WOCleanController(CementBaseController): + class Meta: + label = 'clean' + stacked_on = 'base' + stacked_type = 'nested' + description = ('Clean NGINX FastCGI cache, Opcache, Memcache, Redis Cache') + arguments = [ + (['--all'], + dict(help='Clean all cache', action='store_true')), + (['--fastcgi'], + dict(help='Clean FastCGI cache', action='store_true')), + (['--memcache'], + dict(help='Clean MemCache', action='store_true')), + (['--opcache'], + dict(help='Clean OpCache', action='store_true')), + (['--redis'], + dict(help='Clean Redis Cache', action='store_true')), + ] + usage = "wo clean [options]" + + @expose(hide=True) + def default(self): + if (not (self.app.pargs.all or self.app.pargs.fastcgi or + self.app.pargs.memcache or self.app.pargs.opcache or + self.app.pargs.redis)): + self.clean_fastcgi() + if self.app.pargs.all: + self.clean_memcache() + self.clean_fastcgi() + self.clean_opcache() + self.clean_redis() + if self.app.pargs.fastcgi: + self.clean_fastcgi() + if self.app.pargs.memcache: + self.clean_memcache() + if self.app.pargs.opcache: + self.clean_opcache() + if self.app.pargs.redis: + self.clean_redis() + @expose(hide=True) + def clean_redis(self): + """This function clears Redis cache""" + if(WOAptGet.is_installed(self, "redis-server")): + Log.info(self, "Cleaning Redis cache") + WOShellExec.cmd_exec(self, "redis-cli flushall") + else: + Log.info(self, "Redis is not installed") + + @expose(hide=True) + def clean_memcache(self): + try: + if(WOAptGet.is_installed(self, "memcached")): + WOService.restart_service(self, "memcached") + Log.info(self, "Cleaning MemCache") + else: + Log.info(self, "Memcache not installed") + except Exception as e: + Log.debug(self, "{0}".format(e)) + Log.error(self, "Unable to restart Memcached", False) + + @expose(hide=True) + def clean_fastcgi(self): + if(os.path.isdir("/var/run/nginx-cache")): + Log.info(self, "Cleaning NGINX FastCGI cache") + WOShellExec.cmd_exec(self, "rm -rf /var/run/nginx-cache/*") + else: + Log.error(self, "Unable to clean FastCGI cache", False) + + @expose(hide=True) + def clean_opcache(self): + try: + Log.info(self, "Cleaning opcache") + wp = urllib.request.urlopen(" https://127.0.0.1:22222/cache" + "/opcache/opgui.php?page=reset").read() + except Exception as e: + Log.debug(self, "{0}".format(e)) + Log.debug(self, "Unable hit url, " + " https://127.0.0.1:22222/cache/opcache/opgui.php?page=reset," + " please check you have admin tools installed") + Log.debug(self, "please check you have admin tools installed," + " or install them with `wo stack install --admin`") + Log.error(self, "Unable to clean opcache", False) + +def load(app): + # register the plugin class.. this only happens if the plugin is enabled + handler.register(WOCleanController) + # register a hook (function) to run after arguments are parsed. + hook.register('post_argument_parsing', wo_clean_hook) diff --git a/wo/cli/plugins/debug.py b/wo/cli/plugins/debug.py new file mode 100644 index 0000000..e2697bd --- /dev/null +++ b/wo/cli/plugins/debug.py @@ -0,0 +1,856 @@ +"""Debug Plugin for WordOps""" + +from cement.core.controller import CementBaseController, expose +from cement.core import handler, hook +from wo.core.aptget import WOAptGet +from wo.core.shellexec import * +from wo.core.mysql import WOMysql +from wo.core.services import WOService +from wo.core.logging import Log +from wo.cli.plugins.site_functions import logwatch +from wo.core.variables import WOVariables +from wo.core.fileutils import WOFileUtils +from pynginxconfig import NginxConfig +import os +import configparser +import glob +import signal +import subprocess + + +def wo_debug_hook(app): + pass + + +class WODebugController(CementBaseController): + class Meta: + label = 'debug' + description = 'Used for server level debugging' + stacked_on = 'base' + stacked_type = 'nested' + arguments = [ + (['--stop'], + dict(help='Stop debug', action='store_true')), + (['--start'], + dict(help='Start debug', action='store_true')), + (['--import-slow-log'], + dict(help='Import MySQL slow log to Anemometer database', + action='store_true')), + (['--nginx'], + dict(help='start/stop debugging nginx server ' + 'configuration for site', + action='store' or 'store_const', + choices=('on', 'off'), const='on', nargs='?')), + (['--php'], + dict(help='start/stop debugging server PHP configuration', + action='store' or 'store_const', + choices=('on', 'off'), const='on', nargs='?')), + (['--fpm'], + dict(help='start/stop debugging fastcgi configuration', + action='store' or 'store_const', + choices=('on', 'off'), const='on', nargs='?')), + (['--php7'], + dict(help='start/stop debugging server PHP 7 configuration', + action='store' or 'store_const', + choices=('on', 'off'), const='on', nargs='?')), + (['--fpm7'], + dict(help='start/stop debugging fastcgi 7.0 configuration', + action='store' or 'store_const', + choices=('on', 'off'), const='on', nargs='?')), + (['--mysql'], + dict(help='start/stop debugging MySQL server', + action='store' or 'store_const', + choices=('on', 'off'), const='on', nargs='?')), + (['--wp'], + dict(help='start/stop wordpress debugging for site', + action='store' or 'store_const', choices=('on', 'off'), + const='on', nargs='?')), + (['--rewrite'], + dict(help='start/stop debugging nginx rewrite rules for site', + action='store' or 'store_const', choices=('on', 'off'), + const='on', nargs='?')), + (['--all'], + dict(help='start/stop debugging all server parameters', + action='store' or 'store_const', choices=('on', 'off'), + const='on', nargs='?')), + (['-i', '--interactive'], + dict(help='Interactive debug', action='store_true')), + (['--import-slow-log-interval'], + dict(help='Import MySQL slow log to Anemometer', + action='store', dest='interval')), + (['site_name'], + dict(help='Website Name', nargs='?', default=None)) + ] + usage = "wo debug [] [options] " + + @expose(hide=True) + def debug_nginx(self): + """Start/Stop Nginx debug""" + # start global debug + if (self.app.pargs.nginx == 'on' and not self.app.pargs.site_name): + try: + debug_address = (self.app.config.get('stack', 'ip-address') + .split()) + except Exception as e: + debug_address = ['0.0.0.0/0'] + + # Check if IP address is 127.0.0.1 then enable debug globally + if debug_address == ['127.0.0.1'] or debug_address == []: + debug_address = ['0.0.0.0/0'] + + for ip_addr in debug_address: + if not ("debug_connection "+ip_addr in open('/etc/nginx/' + 'nginx.conf', encoding='utf-8').read()): + Log.info(self, "Setting up Nginx debug connection" + " for "+ip_addr) + WOShellExec.cmd_exec(self, "sed -i \"/events {{/a\\ \\ \\ " + "\\ $(echo debug_connection " + "{ip}\;)\" /etc/nginx/" + "nginx.conf".format(ip=ip_addr)) + self.trigger_nginx = True + + if not self.trigger_nginx: + Log.info(self, "Nginx debug connection already enabled") + + self.msg = self.msg + ["/var/log/nginx/*.error.log"] + + # stop global debug + elif (self.app.pargs.nginx == 'off' and not self.app.pargs.site_name): + if "debug_connection " in open('/etc/nginx/nginx.conf', + encoding='utf-8').read(): + Log.info(self, "Disabling Nginx debug connections") + WOShellExec.cmd_exec(self, "sed -i \"/debug_connection.*/d\"" + " /etc/nginx/nginx.conf") + self.trigger_nginx = True + else: + Log.info(self, "Nginx debug connection already disabled") + + # start site specific debug + elif (self.app.pargs.nginx == 'on' and self.app.pargs.site_name): + config_path = ("/etc/nginx/sites-available/{0}" + .format(self.app.pargs.site_name)) + if os.path.isfile(config_path): + if not WOShellExec.cmd_exec(self, "grep \"error.log debug\" " + "{0}".format(config_path)): + Log.info(self, "Starting NGINX debug connection for " + "{0}".format(self.app.pargs.site_name)) + WOShellExec.cmd_exec(self, "sed -i \"s/error.log;/" + "error.log " + "debug;/\" {0}".format(config_path)) + self.trigger_nginx = True + + else: + Log.info(self, "Nginx debug for site already enabled") + + self.msg = self.msg + ['{0}{1}/logs/error.log' + .format(WOVariables.wo_webroot, + self.app.pargs.site_name)] + + else: + Log.info(self, "{0} domain not valid" + .format(self.app.pargs.site_name)) + + # stop site specific debug + elif (self.app.pargs.nginx == 'off' and self.app.pargs.site_name): + config_path = ("/etc/nginx/sites-available/{0}" + .format(self.app.pargs.site_name)) + if os.path.isfile(config_path): + if WOShellExec.cmd_exec(self, "grep \"error.log debug\" {0}" + .format(config_path)): + Log.info(self, "Stoping NGINX debug connection for {0}" + .format(self.app.pargs.site_name)) + WOShellExec.cmd_exec(self, "sed -i \"s/error.log debug;/" + "error.log;/\" {0}" + .format(config_path)) + self.trigger_nginx = True + + else: + + Log.info(self, "Nginx debug for site already disabled") + else: + Log.info(self, "{0} domain not valid" + .format(self.app.pargs.site_name)) + + @expose(hide=True) + def debug_php(self): + """Start/Stop PHP debug""" + # PHP global debug start + + if (self.app.pargs.php == 'on' and not self.app.pargs.site_name): + if not (WOShellExec.cmd_exec(self, "sed -n \"/upstream php" + "{/,/}/p \" /etc/nginx/" + "conf.d/upstream.conf " + "| grep 9001")): + + Log.info(self, "Enabling PHP debug") + + # Change upstream.conf + nc = NginxConfig() + nc.loadf('/etc/nginx/conf.d/upstream.conf') + nc.set([('upstream','php',), 'server'], '127.0.0.1:9001') + if os.path.isfile("/etc/nginx/common/wpfc-hhvm.conf"): + nc.set([('upstream','hhvm',), 'server'], '127.0.0.1:9001') + nc.savef('/etc/nginx/conf.d/upstream.conf') + + # Enable xdebug + WOFileUtils.searchreplace(self, "/etc/{0}/mods-available/".format("php/5.6" if (WOVariables.wo_platform_codename == 'trusty' or WOVariables.wo_platform_codename == 'xenial' or WOVariables.wo_platform_codename == 'bionic') else "php5") + + "xdebug.ini", + ";zend_extension", + "zend_extension") + + # Fix slow log is not enabled default in PHP5.6 + config = configparser.ConfigParser() + config.read('/etc/{0}/fpm/pool.d/debug.conf'.format("php/5.6" if (WOVariables.wo_platform_codename == 'trusty' or WOVariables.wo_platform_codename == 'xenial' or WOVariables.wo_platform_codename == 'bionic') else "php5")) + config['debug']['slowlog'] = '/var/log/{0}/slow.log'.format("php/5.6" if (WOVariables.wo_platform_codename == 'trusty' or WOVariables.wo_platform_codename == 'xenial' or WOVariables.wo_platform_codename == 'bionic') else "php5") + config['debug']['request_slowlog_timeout'] = '10s' + with open('/etc/{0}/fpm/pool.d/debug.conf'.format("php/5.6" if (WOVariables.wo_platform_codename == 'trusty' or WOVariables.wo_platform_codename == 'xenial' or WOVariables.wo_platform_codename == 'bionic') else "php5"), + encoding='utf-8', mode='w') as confifile: + Log.debug(self, "Writting debug.conf configuration into " + "/etc/{0}/fpm/pool.d/debug.conf".format("php/5.6" if (WOVariables.wo_platform_codename == 'trusty' or WOVariables.wo_platform_codename == 'xenial' or WOVariables.wo_platform_codename == 'bionic') else "php5")) + config.write(confifile) + + self.trigger_php = True + self.trigger_nginx = True + else: + Log.info(self, "PHP debug is already enabled") + + self.msg = self.msg + ['/var/log/{0}/slow.log'.format("php/5.6" if (WOVariables.wo_platform_codename == 'trusty' or WOVariables.wo_platform_codename == 'xenial' or WOVariables.wo_platform_codename == 'bionic') else "php5")] + + # PHP global debug stop + elif (self.app.pargs.php == 'off' and not self.app.pargs.site_name): + if WOShellExec.cmd_exec(self, " sed -n \"/upstream php {/,/}/p\" " + "/etc/nginx/conf.d/upstream.conf " + "| grep 9001"): + Log.info(self, "Disabling PHP debug") + + # Change upstream.conf + nc = NginxConfig() + nc.loadf('/etc/nginx/conf.d/upstream.conf') + nc.set([('upstream','php',), 'server'], '127.0.0.1:9000') + if os.path.isfile("/etc/nginx/common/wpfc-hhvm.conf"): + nc.set([('upstream','hhvm',), 'server'], '127.0.0.1:8000') + nc.savef('/etc/nginx/conf.d/upstream.conf') + + # Disable xdebug + WOFileUtils.searchreplace(self, "/etc/{0}/mods-available/".format("php/5.6" if (WOVariables.wo_platform_codename == 'trusty' or WOVariables.wo_platform_codename == 'xenial' or WOVariables.wo_platform_codename == 'bionic') else "php5") + + "xdebug.ini", + "zend_extension", + ";zend_extension") + + self.trigger_php = True + self.trigger_nginx = True + else: + Log.info(self, "PHP debug is already disabled") + + @expose(hide=True) + def debug_fpm(self): + """Start/Stop PHP5-FPM debug""" + # PHP5-FPM start global debug + if (self.app.pargs.fpm == 'on' and not self.app.pargs.site_name): + if not WOShellExec.cmd_exec(self, "grep \"log_level = debug\" " + "/etc/{0}/fpm/php-fpm.conf".format("php/5.6" if (WOVariables.wo_platform_codename == 'trusty' or WOVariables.wo_platform_codename == 'xenial' or WOVariables.wo_platform_codename == 'bionic') else "php5")): + Log.info(self, "Setting up PHP5-FPM log_level = debug") + config = configparser.ConfigParser() + config.read('/etc/{0}/fpm/php-fpm.conf'.format("php/5.6" if (WOVariables.wo_platform_codename == 'trusty' or WOVariables.wo_platform_codename == 'xenial' or WOVariables.wo_platform_codename == 'bionic') else "php5")) + config.remove_option('global', 'include') + config['global']['log_level'] = 'debug' + config['global']['include'] = '/etc/{0}/fpm/pool.d/*.conf'.format("php/5.6" if (WOVariables.wo_platform_codename == 'trusty' or WOVariables.wo_platform_codename == 'xenial' or WOVariables.wo_platform_codename == 'bionic') else "php5") + with open('/etc/{0}/fpm/php-fpm.conf'.format("php/5.6" if (WOVariables.wo_platform_codename == 'trusty' or WOVariables.wo_platform_codename == 'xenial' or WOVariables.wo_platform_codename == 'bionic') else "php5"), + encoding='utf-8', mode='w') as configfile: + Log.debug(self, "Writting php5-FPM configuration into " + "/etc/{0}/fpm/php-fpm.conf".format("php/5.6" if (WOVariables.wo_platform_codename == 'trusty' or WOVariables.wo_platform_codename == 'xenial' or WOVariables.wo_platform_codename == 'bionic') else "php5")) + config.write(configfile) + self.trigger_php = True + else: + Log.info(self, "PHP5-FPM log_level = debug already setup") + + self.msg = self.msg + ['/var/log/{0}/fpm.log'.format("php/5.6" if (WOVariables.wo_platform_codename == 'trusty' or WOVariables.wo_platform_codename == 'xenial' or WOVariables.wo_platform_codename == 'bionic') else "php5")] + + # PHP5-FPM stop global debug + elif (self.app.pargs.fpm == 'off' and not self.app.pargs.site_name): + if WOShellExec.cmd_exec(self, "grep \"log_level = debug\" " + "/etc/{0}/fpm/php-fpm.conf".format("php/5.6" if (WOVariables.wo_platform_codename == 'trusty' or WOVariables.wo_platform_codename == 'xenial' or WOVariables.wo_platform_codename == 'bionic') else "php5")): + Log.info(self, "Disabling PHP5-FPM log_level = debug") + config = configparser.ConfigParser() + config.read('/etc/{0}/fpm/php-fpm.conf'.format("php/5.6" if (WOVariables.wo_platform_codename == 'trusty' or WOVariables.wo_platform_codename == 'xenial' or WOVariables.wo_platform_codename == 'bionic') else "php5")) + config.remove_option('global', 'include') + config['global']['log_level'] = 'notice' + config['global']['include'] = '/etc/{0}/fpm/pool.d/*.conf'.format("php/5.6" if (WOVariables.wo_platform_codename == 'trusty' or WOVariables.wo_platform_codename == 'xenial' or WOVariables.wo_platform_codename == 'bionic') else "php5") + with open('/etc/{0}/fpm/php-fpm.conf'.format("php/5.6" if (WOVariables.wo_platform_codename == 'trusty' or WOVariables.wo_platform_codename == 'xenial' or WOVariables.wo_platform_codename == 'bionic') else "php5"), + encoding='utf-8', mode='w') as configfile: + Log.debug(self, "writting php5 configuration into " + "/etc/{0}/fpm/php-fpm.conf".format("php/5.6" if (WOVariables.wo_platform_codename == 'trusty' or WOVariables.wo_platform_codename == 'xenial' or WOVariables.wo_platform_codename == 'bionic') else "php5")) + config.write(configfile) + + self.trigger_php = True + else: + Log.info(self, "PHP5-FPM log_level = debug already disabled") + + @expose(hide=True) + def debug_php7(self): + """Start/Stop PHP debug""" + # PHP global debug start + + if (self.app.pargs.php7 == 'on' and not self.app.pargs.site_name): + if (WOVariables.wo_platform_codename == 'wheezy' or WOVariables.wo_platform_codename == 'precise'): + Log.error(self,"PHP 7.0 not supported.") + if not (WOShellExec.cmd_exec(self, "sed -n \"/upstream php7" + "{/,/}/p \" /etc/nginx/" + "conf.d/upstream.conf " + "| grep 9170")): + + Log.info(self, "Enabling PHP 7.0 debug") + + # Change upstream.conf + nc = NginxConfig() + nc.loadf('/etc/nginx/conf.d/upstream.conf') + nc.set([('upstream','php7',), 'server'], '127.0.0.1:9170') + if os.path.isfile("/etc/nginx/common/wpfc-hhvm.conf"): + nc.set([('upstream','hhvm',), 'server'], '127.0.0.1:9170') + nc.savef('/etc/nginx/conf.d/upstream.conf') + + # Enable xdebug + WOFileUtils.searchreplace(self, "/etc/php/7.0/mods-available/" + "xdebug.ini", + ";zend_extension", + "zend_extension") + + # Fix slow log is not enabled default in PHP5.6 + config = configparser.ConfigParser() + config.read('/etc/php/7.0/fpm/pool.d/debug.conf') + config['debug']['slowlog'] = '/var/log/php/7.0/slow.log' + config['debug']['request_slowlog_timeout'] = '10s' + with open('/etc/php/7.0/fpm/pool.d/debug.conf', + encoding='utf-8', mode='w') as confifile: + Log.debug(self, "Writting debug.conf configuration into " + "/etc/php/7.0/fpm/pool.d/debug.conf") + config.write(confifile) + + self.trigger_php = True + self.trigger_nginx = True + else: + Log.info(self, "PHP debug is already enabled") + + self.msg = self.msg + ['/var/log/php/7.0/slow.log'] + + # PHP global debug stop + elif (self.app.pargs.php7 == 'off' and not self.app.pargs.site_name): + if WOShellExec.cmd_exec(self, " sed -n \"/upstream php7 {/,/}/p\" " + "/etc/nginx/conf.d/upstream.conf " + "| grep 9170"): + Log.info(self, "Disabling PHP 7.0 debug") + + # Change upstream.conf + nc = NginxConfig() + nc.loadf('/etc/nginx/conf.d/upstream.conf') + nc.set([('upstream','php7',), 'server'], '127.0.0.1:9070') + if os.path.isfile("/etc/nginx/common/wpfc-hhvm.conf"): + nc.set([('upstream','hhvm',), 'server'], '127.0.0.1:8000') + nc.savef('/etc/nginx/conf.d/upstream.conf') + + # Disable xdebug + WOFileUtils.searchreplace(self, "/etc/php/7.0/mods-available/" + "xdebug.ini", + "zend_extension", + ";zend_extension") + + self.trigger_php = True + self.trigger_nginx = True + else: + Log.info(self, "PHP 7.0 debug is already disabled") + + @expose(hide=True) + def debug_fpm7(self): + """Start/Stop PHP5-FPM debug""" + # PHP5-FPM start global debug + if (self.app.pargs.fpm7 == 'on' and not self.app.pargs.site_name): + if not WOShellExec.cmd_exec(self, "grep \"log_level = debug\" " + "/etc/php/7.0/fpm/php-fpm.conf"): + Log.info(self, "Setting up PHP7.0-FPM log_level = debug") + config = configparser.ConfigParser() + config.read('/etc/php/7.0/fpm/php-fpm.conf') + config.remove_option('global', 'include') + config['global']['log_level'] = 'debug' + config['global']['include'] = '/etc/php/7.0/fpm/pool.d/*.conf' + with open('/etc/php/7.0/fpm/php-fpm.conf', + encoding='utf-8', mode='w') as configfile: + Log.debug(self, "Writting php7.0-FPM configuration into " + "/etc/php/7.0/fpm/php-fpm.conf") + config.write(configfile) + self.trigger_php = True + else: + Log.info(self, "PHP7.0-FPM log_level = debug already setup") + + self.msg = self.msg + ['/var/log/php/7.0/fpm.log'] + + # PHP5-FPM stop global debug + elif (self.app.pargs.fpm7 == 'off' and not self.app.pargs.site_name): + if WOShellExec.cmd_exec(self, "grep \"log_level = debug\" " + "/etc/php/7.0/fpm/php-fpm.conf"): + Log.info(self, "Disabling PHP7.0-FPM log_level = debug") + config = configparser.ConfigParser() + config.read('/etc/php/7.0/fpm/php-fpm.conf') + config.remove_option('global', 'include') + config['global']['log_level'] = 'notice' + config['global']['include'] = '/etc/php/7.0/fpm/pool.d/*.conf' + with open('/etc/php/7.0/fpm/php-fpm.conf', + encoding='utf-8', mode='w') as configfile: + Log.debug(self, "writting php7.0 configuration into " + "/etc/php/7.0/fpm/php-fpm.conf") + config.write(configfile) + self.trigger_php = True + else: + Log.info(self, "PHP7.0-FPM log_level = debug already disabled") + + @expose(hide=True) + def debug_mysql(self): + """Start/Stop MySQL debug""" + # MySQL start global debug + if (self.app.pargs.mysql == 'on' and not self.app.pargs.site_name): + if not WOShellExec.cmd_exec(self, "mysql -e \"show variables like" + " \'slow_query_log\';\" | " + "grep ON"): + Log.info(self, "Setting up MySQL slow log") + WOMysql.execute(self, "set global slow_query_log = " + "\'ON\';") + WOMysql.execute(self, "set global slow_query_log_file = " + "\'/var/log/mysql/mysql-slow.log\';") + WOMysql.execute(self, "set global long_query_time = 2;") + WOMysql.execute(self, "set global log_queries_not_using" + "_indexes = \'ON\';") + else: + Log.info(self, "MySQL slow log is already enabled") + + self.msg = self.msg + ['/var/log/mysql/mysql-slow.log'] + + # MySQL stop global debug + elif (self.app.pargs.mysql == 'off' and not self.app.pargs.site_name): + if WOShellExec.cmd_exec(self, "mysql -e \"show variables like \'" + "slow_query_log\';\" | grep ON"): + Log.info(self, "Disabling MySQL slow log") + WOMysql.execute(self, "set global slow_query_log = \'OFF\';") + WOMysql.execute(self, "set global slow_query_log_file = \'" + "/var/log/mysql/mysql-slow.log\';") + WOMysql.execute(self, "set global long_query_time = 10;") + WOMysql.execute(self, "set global log_queries_not_using_index" + "es = \'OFF\';") + WOShellExec.cmd_exec(self, "crontab -l | sed \'/#WordOps " + "start/,/#WordOps end/d\' | crontab -") + else: + Log.info(self, "MySQL slow log already disabled") + + @expose(hide=True) + def debug_wp(self): + """Start/Stop WordPress debug""" + if (self.app.pargs.wp == 'on' and self.app.pargs.site_name): + wp_config = ("{0}/{1}/wp-config.php" + .format(WOVariables.wo_webroot, + self.app.pargs.site_name)) + webroot = "{0}{1}".format(WOVariables.wo_webroot, + self.app.pargs.site_name) + # Check wp-config.php file into htdocs folder + if not os.path.isfile(wp_config): + wp_config = ("{0}/{1}/htdocs/wp-config.php" + .format(WOVariables.wo_webroot, + self.app.pargs.site_name)) + if os.path.isfile(wp_config): + if not WOShellExec.cmd_exec(self, "grep \"\'WP_DEBUG\'\" {0} |" + " grep true".format(wp_config)): + Log.info(self, "Starting WordPress debug") + open("{0}/htdocs/wp-content/debug.log".format(webroot), + encoding='utf-8', mode='a').close() + WOShellExec.cmd_exec(self, "chown {1}: {0}/htdocs/wp-" + "content/debug.log" + "".format(webroot, + WOVariables.wo_php_user)) + WOShellExec.cmd_exec(self, "sed -i \"s/define(\'WP_DEBUG\'" + ".*/define(\'WP_DEBUG\', true);\\n" + "define(\'WP_DEBUG_DISPLAY\', false);" + "\\ndefine(\'WP_DEBUG_LOG\', true);" + "\\ndefine(\'SAVEQUERIES\', true);/\"" + " {0}".format(wp_config)) + WOShellExec.cmd_exec(self, "cd {0}/htdocs/ && wp" + " plugin --allow-root install " + "developer query-monitor" + .format(webroot)) + WOShellExec.cmd_exec(self, "chown -R {1}: {0}/htdocs/" + "wp-content/plugins" + .format(webroot, + WOVariables.wo_php_user)) + + self.msg = self.msg + ['{0}{1}/htdocs/wp-content' + '/debug.log' + .format(WOVariables.wo_webroot, + self.app.pargs.site_name)] + + else: + Log.info(self, "Unable to find wp-config.php for site: {0}" + .format(self.app.pargs.site_name)) + + elif (self.app.pargs.wp == 'off' and self.app.pargs.site_name): + wp_config = ("{0}{1}/wp-config.php" + .format(WOVariables.wo_webroot, + self.app.pargs.site_name)) + webroot = "{0}{1}".format(WOVariables.wo_webroot, + self.app.pargs.site_name) + # Check wp-config.php file into htdocs folder + if not os.path.isfile(wp_config): + wp_config = ("{0}/{1}/htdocs/wp-config.php" + .format(WOVariables.wo_webroot, + self.app.pargs.site_name)) + if os.path.isfile(wp_config): + if WOShellExec.cmd_exec(self, "grep \"\'WP_DEBUG\'\" {0} | " + "grep true".format(wp_config)): + Log.info(self, "Disabling WordPress debug") + WOShellExec.cmd_exec(self, "sed -i \"s/define(\'WP_DEBUG\'" + ", true);/define(\'WP_DEBUG\', " + "false);/\" {0}".format(wp_config)) + WOShellExec.cmd_exec(self, "sed -i \"/define(\'" + "WP_DEBUG_DISPLAY\', false);/d\" {0}" + .format(wp_config)) + WOShellExec.cmd_exec(self, "sed -i \"/define(\'" + "WP_DEBUG_LOG\', true);/d\" {0}" + .format(wp_config)) + WOShellExec.cmd_exec(self, "sed -i \"/define(\'" + "SAVEQUERIES\', " + "true);/d\" {0}".format(wp_config)) + else: + Log.info(self, "WordPress debug all already disabled") + else: + Log.error(self, "Missing argument site name") + + @expose(hide=True) + def debug_rewrite(self): + """Start/Stop Nginx rewrite rules debug""" + # Start Nginx rewrite debug globally + if (self.app.pargs.rewrite == 'on' and not self.app.pargs.site_name): + if not WOShellExec.cmd_exec(self, "grep \"rewrite_log on;\" " + "/etc/nginx/nginx.conf"): + Log.info(self, "Setting up Nginx rewrite logs") + WOShellExec.cmd_exec(self, "sed -i \'/http {/a \\\\t" + "rewrite_log on;\' /etc/nginx/nginx.conf") + self.trigger_nginx = True + else: + Log.info(self, "Nginx rewrite logs already enabled") + + if '/var/log/nginx/*.error.log' not in self.msg: + self.msg = self.msg + ['/var/log/nginx/*.error.log'] + + # Stop Nginx rewrite debug globally + elif (self.app.pargs.rewrite == 'off' + and not self.app.pargs.site_name): + if WOShellExec.cmd_exec(self, "grep \"rewrite_log on;\" " + "/etc/nginx/nginx.conf"): + Log.info(self, "Disabling Nginx rewrite logs") + WOShellExec.cmd_exec(self, "sed -i \"/rewrite_log.*/d\"" + " /etc/nginx/nginx.conf") + self.trigger_nginx = True + else: + Log.info(self, "Nginx rewrite logs already disabled") + # Start Nginx rewrite for site + elif (self.app.pargs.rewrite == 'on' and self.app.pargs.site_name): + config_path = ("/etc/nginx/sites-available/{0}" + .format(self.app.pargs.site_name)) + if not WOShellExec.cmd_exec(self, "grep \"rewrite_log on;\" {0}" + .format(config_path)): + Log.info(self, "Setting up Nginx rewrite logs for {0}" + .format(self.app.pargs.site_name)) + WOShellExec.cmd_exec(self, "sed -i \"/access_log/i \\\\\\t" + "rewrite_log on;\" {0}" + .format(config_path)) + self.trigger_nginx = True + else: + Log.info(self, "Nginx rewrite logs for {0} already setup" + .format(self.app.pargs.site_name)) + + if ('{0}{1}/logs/error.log'.format(WOVariables.wo_webroot, + self.app.pargs.site_name) + not in self.msg): + self.msg = self.msg + ['{0}{1}/logs/error.log' + .format(WOVariables.wo_webroot, + self.app.pargs.site_name)] + + # Stop Nginx rewrite for site + elif (self.app.pargs.rewrite == 'off' and self.app.pargs.site_name): + config_path = ("/etc/nginx/sites-available/{0}" + .format(self.app.pargs.site_name)) + if WOShellExec.cmd_exec(self, "grep \"rewrite_log on;\" {0}" + .format(config_path)): + Log.info(self, "Disabling Nginx rewrite logs for {0}" + .format(self.app.pargs.site_name)) + WOShellExec.cmd_exec(self, "sed -i \"/rewrite_log.*/d\" {0}" + .format(config_path)) + self.trigger_nginx = True + else: + Log.info(self, "Nginx rewrite logs for {0} already " + " disabled".format(self.app.pargs.site_name)) + + @expose(hide=True) + def signal_handler(self, signal, frame): + """Handle Ctrl+c hevent for -i option of debug""" + self.start = False + if self.app.pargs.nginx: + self.app.pargs.nginx = 'off' + self.debug_nginx() + if self.app.pargs.php: + self.app.pargs.php = 'off' + self.debug_php() + if self.app.pargs.php7: + self.app.pargs.php7 = 'off' + self.debug_php7() + if self.app.pargs.fpm: + self.app.pargs.fpm = 'off' + self.debug_fpm() + if self.app.pargs.fpm7: + self.app.pargs.fpm7 = 'off' + self.debug_fpm7() + if self.app.pargs.mysql: + # MySQL debug will not work for remote MySQL + if WOVariables.wo_mysql_host is "localhost": + self.app.pargs.mysql = 'off' + self.debug_mysql() + else: + Log.warn(self, "Remote MySQL found, WordOps does not support " + "debugging remote servers") + if self.app.pargs.wp: + self.app.pargs.wp = 'off' + self.debug_wp() + if self.app.pargs.rewrite: + self.app.pargs.rewrite = 'off' + self.debug_rewrite() + + # Reload Nginx + if self.trigger_nginx: + WOService.reload_service(self, 'nginx') + + # Reload PHP + if self.trigger_php: + if WOVariables.wo_platform_codename == 'trusty' or WOVariables.wo_platform_codename == 'xenial' or WOVariables.wo_platform_codename == 'bionic': + if WOAptGet.is_installed(self,'php5.6-fpm'): + WOService.reload_service(self, 'php5.6-fpm') + if WOAptGet.is_installed(self,'php7.0-fpm'): + WOService.reload_service(self, 'php7.0-fpm') + else: + WOService.reload_service(self, 'php5-fpm') + self.app.close(0) + + @expose(hide=True) + def default(self): + """Default function of debug""" + # self.start = True + self.interactive = False + self.msg = [] + self.trigger_nginx = False + self.trigger_php = False + + if ((not self.app.pargs.nginx) and (not self.app.pargs.php) and (not self.app.pargs.php7) + and (not self.app.pargs.fpm) and (not self.app.pargs.fpm7) and (not self.app.pargs.mysql) + and (not self.app.pargs.wp) and (not self.app.pargs.rewrite) + and (not self.app.pargs.all) + and (not self.app.pargs.site_name) + and (not self.app.pargs.import_slow_log) + and (not self.app.pargs.interval)): + if self.app.pargs.stop or self.app.pargs.start: + print("--start/stop option is deprecated since ee v3.0.5") + self.app.args.print_help() + else: + self.app.args.print_help() + + if self.app.pargs.import_slow_log: + self.import_slow_log() + + if self.app.pargs.interval: + try: + cron_time = int(self.app.pargs.interval) + except Exception as e: + cron_time = 5 + + try: + if not WOShellExec.cmd_exec(self, "crontab -l | grep " + "'wo debug --import-slow-log'"): + if not cron_time == 0: + Log.info(self, "setting up crontab entry," + " please wait...") + WOShellExec.cmd_exec(self, "/bin/bash -c \"crontab -l " + "2> /dev/null | {{ cat; echo -e" + " \\\"#WordOps start MySQL " + "slow log \\n*/{0} * * * * " + "/usr/local/bin/wo debug" + " --import-slow-log\\n" + "#WordOps end MySQL slow log" + "\\\"; }} | crontab -\"" + .format(cron_time)) + else: + if not cron_time == 0: + Log.info(self, "updating crontab entry," + " please wait...") + if not WOShellExec.cmd_exec(self, "/bin/bash -c " + "\"crontab " + "-l | sed '/WordOps " + "start MySQL slow " + "log/!b;n;c\*\/{0} " + "\* \* \* " + "\* \/usr" + "\/local\/bin\/wo debug " + "--import\-slow\-log' " + "| crontab -\"" + .format(cron_time)): + Log.error(self, "failed to update crontab entry") + else: + Log.info(self, "removing crontab entry," + " please wait...") + if not WOShellExec.cmd_exec(self, "/bin/bash -c " + "\"crontab " + "-l | sed '/WordOps " + "start MySQL slow " + "log/,+2d'" + "| crontab -\"" + .format(cron_time)): + Log.error(self, "failed to remove crontab entry") + except CommandExecutionError as e: + Log.debug(self, str(e)) + + if self.app.pargs.all == 'on': + if self.app.pargs.site_name: + self.app.pargs.wp = 'on' + self.app.pargs.nginx = 'on' + self.app.pargs.php = 'on' + self.app.pargs.fpm = 'on' + if (WOVariables.wo_platform_codename == 'trusty' or WOVariables.wo_platform_codename == 'xenial' or WOVariables.wo_platform_codename == 'bionic') and WOAptGet.is_installed(self,'php7.0-fpm'): + self.app.pargs.php7 = 'on' + self.app.pargs.fpm7 = 'on' + self.app.pargs.mysql = 'on' + self.app.pargs.rewrite = 'on' + + if self.app.pargs.all == 'off': + if self.app.pargs.site_name: + self.app.pargs.wp = 'off' + self.app.pargs.nginx = 'off' + self.app.pargs.php = 'off' + self.app.pargs.fpm = 'off' + if (WOVariables.wo_platform_codename == 'trusty' or WOVariables.wo_platform_codename == 'xenial' or WOVariables.wo_platform_codename == 'bionic') and WOAptGet.is_installed(self,'php7.0-fpm'): + self.app.pargs.php7 = 'off' + self.app.pargs.fpm7 = 'off' + self.app.pargs.mysql = 'off' + self.app.pargs.rewrite = 'off' + + if ((not self.app.pargs.nginx) and (not self.app.pargs.php) and (not self.app.pargs.php7) + and (not self.app.pargs.fpm) and (not self.app.pargs.fpm7) and (not self.app.pargs.mysql) + and (not self.app.pargs.wp) and (not self.app.pargs.rewrite) + and self.app.pargs.site_name): + self.app.args.print_help() + # self.app.pargs.nginx = 'on' + # self.app.pargs.wp = 'on' + # self.app.pargs.rewrite = 'on' + + if self.app.pargs.nginx: + self.debug_nginx() + if self.app.pargs.php: + self.debug_php() + if self.app.pargs.fpm: + self.debug_fpm() + if self.app.pargs.php7: + self.debug_php7() + if self.app.pargs.fpm7: + self.debug_fpm7() + if self.app.pargs.mysql: + # MySQL debug will not work for remote MySQL + if WOVariables.wo_mysql_host is "localhost": + self.debug_mysql() + else: + Log.warn(self, "Remote MySQL found, WordOps does not support " + "debugging remote servers") + if self.app.pargs.wp: + self.debug_wp() + if self.app.pargs.rewrite: + self.debug_rewrite() + + if self.app.pargs.interactive: + self.interactive = True + + # Reload Nginx + if self.trigger_nginx: + WOService.reload_service(self, 'nginx') + # Reload PHP + if self.trigger_php: + if (WOVariables.wo_platform_codename == 'trusty' or WOVariables.wo_platform_codename == 'xenial' or WOVariables.wo_platform_codename == 'bionic'): + if WOAptGet.is_installed(self,'php5.6-fpm'): + WOService.restart_service(self, 'php5.6-fpm') + if WOAptGet.is_installed(self,'php7.0-fpm'): + WOService.restart_service(self, 'php7.0-fpm') + else: + WOService.restart_service(self, 'php5-fpm') + if WOVariables.wo_platform_codename == 'jessie': + WOService.restart_service(self, 'php7.0-fpm') + + if len(self.msg) > 0: + if not self.app.pargs.interactive: + disp_msg = ' '.join(self.msg) + Log.info(self, "Use following command to check debug logs:\n" + + Log.ENDC + "tail -f {0}".format(disp_msg)) + else: + signal.signal(signal.SIGINT, self.signal_handler) + watch_list = [] + for w_list in self.msg: + watch_list = watch_list + glob.glob(w_list) + + logwatch(self, watch_list) + + @expose(hide=True) + def import_slow_log(self): + """Default function for import slow log""" + if os.path.isdir("{0}22222/htdocs/db/anemometer" + .format(WOVariables.wo_webroot)): + if os.path.isfile("/var/log/mysql/mysql-slow.log"): + # Get Anemometer user name and password + Log.info(self, "Importing MySQL slow log to Anemometer") + host = os.popen("grep -e \"\'host\'\" {0}22222/htdocs/" + .format(WOVariables.wo_webroot) + + "db/anemometer/conf/config.inc.php " + "| head -1 | cut -d\\\' -f4 | " + "tr -d '\n'").read() + user = os.popen("grep -e \"\'user\'\" {0}22222/htdocs/" + .format(WOVariables.wo_webroot) + + "db/anemometer/conf/config.inc.php " + "| head -1 | cut -d\\\' -f4 | " + "tr -d '\n'").read() + password = os.popen("grep -e \"\'password\'\" {0}22222/" + .format(WOVariables.wo_webroot) + + "htdocs/db/anemometer/conf" + "/config.inc.php " + "| head -1 | cut -d\\\' -f4 | " + "tr -d '\n'").read() + + # Import slow log Anemometer using pt-query-digest + try: + WOShellExec.cmd_exec(self, "pt-query-digest --user={0} " + "--password={1} " + "--review D=slow_query_log," + "t=global_query_review " + "--history D=slow_query_log,t=" + "global_query_review_history " + "--no-report --limit=0% " + "--filter=\" \\$event->{{Bytes}} = " + "length(\\$event->{{arg}}) " + "and \\$event->{{hostname}}=\\\"" + "{2}\\\"\" " + "/var/log/mysql/mysql-slow.log" + .format(user, password, host)) + except CommandExecutionError as e: + Log.debug(self, str(e)) + Log.error(self, "MySQL slow log import failed.") + else: + Log.error(self, "MySQL slow log file not found," + " so not imported slow logs") + else: + Log.error(self, "Anemometer is not installed." + + Log.ENDC + "\n Install Anemometer with:" + + Log.BOLD + "\n `wo stack install --utils`" + + Log.ENDC) + + +def load(app): + # register the plugin class.. this only happens if the plugin is enabled + handler.register(WODebugController) + # register a hook (function) to run after arguments are parsed. + hook.register('post_argument_parsing', wo_debug_hook) diff --git a/wo/cli/plugins/import_slow_log.py b/wo/cli/plugins/import_slow_log.py new file mode 100644 index 0000000..4716cad --- /dev/null +++ b/wo/cli/plugins/import_slow_log.py @@ -0,0 +1,34 @@ +from cement.core.controller import CementBaseController, expose +from cement.core import handler, hook +from wo.core.shellexec import WOShellExec +from wo.core.logging import Log +from wo.core.variables import WOVariables +import os + + +def wo_import_slow_log_hook(app): + pass + + +class WOImportslowlogController(CementBaseController): + class Meta: + label = 'import_slow_log' + stacked_on = 'base' + stacked_type = 'nested' + description = 'Import MySQL slow log to Anemometer database' + usage = "wo import-slow-log" + + @expose(hide=True) + def default(self): + Log.info(self, "This command is deprecated." + " You can use this command instead, " + + Log.ENDC + Log.BOLD + "\n`wo debug --import-slow-log`" + + Log.ENDC) + + +def load(app): + # register the plugin class.. this only happens if the plugin is enabled + handler.register(WOImportslowlogController) + + # register a hook (function) to run after arguments are parsed. + hook.register('post_argument_parsing', wo_import_slow_log_hook) diff --git a/wo/cli/plugins/info.py b/wo/cli/plugins/info.py new file mode 100644 index 0000000..6a2b996 --- /dev/null +++ b/wo/cli/plugins/info.py @@ -0,0 +1,289 @@ +"""WOInfo Plugin for WordOps""" + +from cement.core.controller import CementBaseController, expose +from cement.core import handler, hook +from wo.core.variables import WOVariables +from pynginxconfig import NginxConfig +from wo.core.aptget import WOAptGet +from wo.core.shellexec import WOShellExec +from wo.core.logging import Log +import os +import configparser + + +def wo_info_hook(app): + pass + + +class WOInfoController(CementBaseController): + class Meta: + label = 'info' + stacked_on = 'base' + stacked_type = 'nested' + description = ('Display configuration information related to Nginx,' + ' PHP and MySQL') + arguments = [ + (['--mysql'], + dict(help='Get MySQL configuration information', + action='store_true')), + (['--php'], + dict(help='Get PHP configuration information', + action='store_true')), + (['--php7'], + dict(help='Get PHP 7.0 configuration information', + action='store_true')), + (['--nginx'], + dict(help='Get Nginx configuration information', + action='store_true')), + ] + usage = "wo info [options]" + + @expose(hide=True) + def info_nginx(self): + """Display Nginx information""" + version = os.popen("nginx -v 2>&1 | cut -d':' -f2 | cut -d' ' -f2 | " + "cut -d'/' -f2 | tr -d '\n'").read() + allow = os.popen("grep ^allow /etc/nginx/common/acl.conf | " + "cut -d' ' -f2 | cut -d';' -f1 | tr '\n' ' '").read() + nc = NginxConfig() + nc.loadf('/etc/nginx/nginx.conf') + user = nc.get('user')[1] + worker_processes = nc.get('worker_processes')[1] + worker_connections = nc.get([('events',), 'worker_connections'])[1] + keepalive_timeout = nc.get([('http',), 'keepalive_timeout'])[1] + fastcgi_read_timeout = nc.get([('http',), + 'fastcgi_read_timeout'])[1] + client_max_body_size = nc.get([('http',), + 'client_max_body_size'])[1] + data = dict(version=version, allow=allow, user=user, + worker_processes=worker_processes, + keepalive_timeout=keepalive_timeout, + worker_connections=worker_connections, + fastcgi_read_timeout=fastcgi_read_timeout, + client_max_body_size=client_max_body_size) + self.app.render((data), 'info_nginx.mustache') + + @expose(hide=True) + def info_php(self): + """Display PHP information""" + version = os.popen("{0} -v 2>/dev/null | head -n1 | cut -d' ' -f2 |".format("php5.6" if (WOVariables.wo_platform_codename == 'trusty' or WOVariables.wo_platform_codename == 'xenial' or WOVariables.wo_platform_codename == 'bionic') else "php") + + " cut -d'+' -f1 | tr -d '\n'").read + config = configparser.ConfigParser() + config.read('/etc/{0}/fpm/php.ini'.format("php/5.6" if (WOVariables.wo_platform_codename == 'trusty' or WOVariables.wo_platform_codename == 'xenial' or WOVariables.wo_platform_codename == 'bionic') else "php5")) + expose_php = config['PHP']['expose_php'] + memory_limit = config['PHP']['memory_limit'] + post_max_size = config['PHP']['post_max_size'] + upload_max_filesize = config['PHP']['upload_max_filesize'] + max_execution_time = config['PHP']['max_execution_time'] + + config.read('/etc/{0}/fpm/pool.d/www.conf'.format("php/5.6" if (WOVariables.wo_platform_codename == 'trusty' or WOVariables.wo_platform_codename == 'xenial' or WOVariables.wo_platform_codename == 'bionic') else "php5")) + www_listen = config['www']['listen'] + www_ping_path = config['www']['ping.path'] + www_pm_status_path = config['www']['pm.status_path'] + www_pm = config['www']['pm'] + www_pm_max_requests = config['www']['pm.max_requests'] + www_pm_max_children = config['www']['pm.max_children'] + www_pm_start_servers = config['www']['pm.start_servers'] + www_pm_min_spare_servers = config['www']['pm.min_spare_servers'] + www_pm_max_spare_servers = config['www']['pm.max_spare_servers'] + www_request_terminate_time = (config['www'] + ['request_terminate_timeout']) + try: + www_xdebug = (config['www']['php_admin_flag[xdebug.profiler_enable' + '_trigger]']) + except Exception as e: + www_xdebug = 'off' + + config.read('/etc/{0}/fpm/pool.d/debug.conf'.format("php/5.6" if (WOVariables.wo_platform_codename == 'trusty' or WOVariables.wo_platform_codename == 'xenial' or WOVariables.wo_platform_codename == 'bionic') else "php5")) + debug_listen = config['debug']['listen'] + debug_ping_path = config['debug']['ping.path'] + debug_pm_status_path = config['debug']['pm.status_path'] + debug_pm = config['debug']['pm'] + debug_pm_max_requests = config['debug']['pm.max_requests'] + debug_pm_max_children = config['debug']['pm.max_children'] + debug_pm_start_servers = config['debug']['pm.start_servers'] + debug_pm_min_spare_servers = config['debug']['pm.min_spare_servers'] + debug_pm_max_spare_servers = config['debug']['pm.max_spare_servers'] + debug_request_terminate = (config['debug'] + ['request_terminate_timeout']) + try: + debug_xdebug = (config['debug']['php_admin_flag[xdebug.profiler_' + 'enable_trigger]']) + except Exception as e: + debug_xdebug = 'off' + + data = dict(version=version, expose_php=expose_php, + memory_limit=memory_limit, post_max_size=post_max_size, + upload_max_filesize=upload_max_filesize, + max_execution_time=max_execution_time, + www_listen=www_listen, www_ping_path=www_ping_path, + www_pm_status_path=www_pm_status_path, www_pm=www_pm, + www_pm_max_requests=www_pm_max_requests, + www_pm_max_children=www_pm_max_children, + www_pm_start_servers=www_pm_start_servers, + www_pm_min_spare_servers=www_pm_min_spare_servers, + www_pm_max_spare_servers=www_pm_max_spare_servers, + www_request_terminate_timeout=www_request_terminate_time, + www_xdebug_profiler_enable_trigger=www_xdebug, + debug_listen=debug_listen, debug_ping_path=debug_ping_path, + debug_pm_status_path=debug_pm_status_path, + debug_pm=debug_pm, + debug_pm_max_requests=debug_pm_max_requests, + debug_pm_max_children=debug_pm_max_children, + debug_pm_start_servers=debug_pm_start_servers, + debug_pm_min_spare_servers=debug_pm_min_spare_servers, + debug_pm_max_spare_servers=debug_pm_max_spare_servers, + debug_request_terminate_timeout=debug_request_terminate, + debug_xdebug_profiler_enable_trigger=debug_xdebug) + self.app.render((data), 'info_php.mustache') + + @expose(hide=True) + def info_php7(self): + """Display PHP information""" + version = os.popen("php7.0 -v 2>/dev/null | head -n1 | cut -d' ' -f2 |" + " cut -d'+' -f1 | tr -d '\n'").read + config = configparser.ConfigParser() + config.read('/etc/php/7.0/fpm/php.ini') + expose_php = config['PHP']['expose_php'] + memory_limit = config['PHP']['memory_limit'] + post_max_size = config['PHP']['post_max_size'] + upload_max_filesize = config['PHP']['upload_max_filesize'] + max_execution_time = config['PHP']['max_execution_time'] + + config.read('/etc/php/7.0/fpm/pool.d/www.conf') + www_listen = config['www']['listen'] + www_ping_path = config['www']['ping.path'] + www_pm_status_path = config['www']['pm.status_path'] + www_pm = config['www']['pm'] + www_pm_max_requests = config['www']['pm.max_requests'] + www_pm_max_children = config['www']['pm.max_children'] + www_pm_start_servers = config['www']['pm.start_servers'] + www_pm_min_spare_servers = config['www']['pm.min_spare_servers'] + www_pm_max_spare_servers = config['www']['pm.max_spare_servers'] + www_request_terminate_time = (config['www'] + ['request_terminate_timeout']) + try: + www_xdebug = (config['www']['php_admin_flag[xdebug.profiler_enable' + '_trigger]']) + except Exception as e: + www_xdebug = 'off' + + config.read('/etc/php/7.0/fpm/pool.d/debug.conf') + debug_listen = config['debug']['listen'] + debug_ping_path = config['debug']['ping.path'] + debug_pm_status_path = config['debug']['pm.status_path'] + debug_pm = config['debug']['pm'] + debug_pm_max_requests = config['debug']['pm.max_requests'] + debug_pm_max_children = config['debug']['pm.max_children'] + debug_pm_start_servers = config['debug']['pm.start_servers'] + debug_pm_min_spare_servers = config['debug']['pm.min_spare_servers'] + debug_pm_max_spare_servers = config['debug']['pm.max_spare_servers'] + debug_request_terminate = (config['debug'] + ['request_terminate_timeout']) + try: + debug_xdebug = (config['debug']['php_admin_flag[xdebug.profiler_' + 'enable_trigger]']) + except Exception as e: + debug_xdebug = 'off' + + data = dict(version=version, expose_php=expose_php, + memory_limit=memory_limit, post_max_size=post_max_size, + upload_max_filesize=upload_max_filesize, + max_execution_time=max_execution_time, + www_listen=www_listen, www_ping_path=www_ping_path, + www_pm_status_path=www_pm_status_path, www_pm=www_pm, + www_pm_max_requests=www_pm_max_requests, + www_pm_max_children=www_pm_max_children, + www_pm_start_servers=www_pm_start_servers, + www_pm_min_spare_servers=www_pm_min_spare_servers, + www_pm_max_spare_servers=www_pm_max_spare_servers, + www_request_terminate_timeout=www_request_terminate_time, + www_xdebug_profiler_enable_trigger=www_xdebug, + debug_listen=debug_listen, debug_ping_path=debug_ping_path, + debug_pm_status_path=debug_pm_status_path, + debug_pm=debug_pm, + debug_pm_max_requests=debug_pm_max_requests, + debug_pm_max_children=debug_pm_max_children, + debug_pm_start_servers=debug_pm_start_servers, + debug_pm_min_spare_servers=debug_pm_min_spare_servers, + debug_pm_max_spare_servers=debug_pm_max_spare_servers, + debug_request_terminate_timeout=debug_request_terminate, + debug_xdebug_profiler_enable_trigger=debug_xdebug) + self.app.render((data), 'info_php.mustache') + + @expose(hide=True) + def info_mysql(self): + """Display MySQL information""" + version = os.popen("mysql -V | awk '{print($5)}' | cut -d ',' " + "-f1 | tr -d '\n'").read() + host = "localhost" + port = os.popen("mysql -e \"show variables\" | grep ^port | awk " + "'{print($2)}' | tr -d '\n'").read() + wait_timeout = os.popen("mysql -e \"show variables\" | grep " + "^wait_timeout | awk '{print($2)}' | " + "tr -d '\n'").read() + interactive_timeout = os.popen("mysql -e \"show variables\" | grep " + "^interactive_timeout | awk " + "'{print($2)}' | tr -d '\n'").read() + max_used_connections = os.popen("mysql -e \"show global status\" | " + "grep Max_used_connections | awk " + "'{print($2)}' | tr -d '\n'").read() + datadir = os.popen("mysql -e \"show variables\" | grep datadir | awk" + " '{print($2)}' | tr -d '\n'").read() + socket = os.popen("mysql -e \"show variables\" | grep \"^socket\" | " + "awk '{print($2)}' | tr -d '\n'").read() + data = dict(version=version, host=host, port=port, + wait_timeout=wait_timeout, + interactive_timeout=interactive_timeout, + max_used_connections=max_used_connections, + datadir=datadir, socket=socket) + self.app.render((data), 'info_mysql.mustache') + + @expose(hide=True) + def default(self): + """default function for info""" + if (not self.app.pargs.nginx and not self.app.pargs.php + and not self.app.pargs.mysql and not self.app.pargs.php7): + self.app.pargs.nginx = True + self.app.pargs.php = True + self.app.pargs.mysql = True + if WOAptGet.is_installed(self, 'php7.0-fpm'): + self.app.pargs.php = True + + if self.app.pargs.nginx: + if WOAptGet.is_installed(self, 'nginx-custom') or WOAptGet.is_installed(self, 'nginx-common'): + self.info_nginx() + else: + Log.error(self, "Nginx is not installed") + + if self.app.pargs.php: + if (WOVariables.wo_platform_distro == 'debian' or WOVariables.wo_platform_codename == 'precise'): + if WOAptGet.is_installed(self, 'php5-fpm'): + self.info_php() + else: + Log.error(self, "PHP5 is not installed") + else: + if WOAptGet.is_installed(self, 'php5.6-fpm'): + self.info_php() + else: + Log.error(self, "PHP5.6 is not installed") + + if self.app.pargs.php7: + if WOAptGet.is_installed(self, 'php7.0-fpm'): + self.info_php7() + else: + Log.error(self, "PHP 7.0 is not installed") + + if self.app.pargs.mysql: + if WOShellExec.cmd_exec(self, "mysqladmin ping"): + self.info_mysql() + else: + Log.error(self, "MySQL is not installed") + + +def load(app): + # register the plugin class.. this only happens if the plugin is enabled + handler.register(WOInfoController) + + # register a hook (function) to run after arguments are parsed. + hook.register('post_argument_parsing', wo_info_hook) diff --git a/wo/cli/plugins/log.py b/wo/cli/plugins/log.py new file mode 100644 index 0000000..f7ef3d8 --- /dev/null +++ b/wo/cli/plugins/log.py @@ -0,0 +1,577 @@ +"""Logfile Plugin for WordOps""" + +from cement.core.controller import CementBaseController, expose +from cement.core import handler, hook +from wo.core.logging import Log +from wo.cli.plugins.site_functions import logwatch +from wo.core.variables import WOVariables +from wo.core.fileutils import WOFileUtils +from wo.core.shellexec import WOShellExec +from wo.core.sendmail import WOSendMail +from wo.core.mysql import WOMysql +import os +import glob +import gzip + + +def wo_log_hook(app): + pass + + +class WOLogController(CementBaseController): + class Meta: + label = 'log' + description = 'Perform operations on Nginx, PHP and MySQL log files' + stacked_on = 'base' + stacked_type = 'nested' + usage = "wo log [] [options]" + + @expose(hide=True) + def default(self): + self.app.args.print_help() + + +class WOLogShowController(CementBaseController): + class Meta: + label = 'show' + description = 'Show Nginx, PHP, MySQL log file' + stacked_on = 'log' + stacked_type = 'nested' + arguments = [ + (['--all'], + dict(help='Show All logs file', action='store_true')), + (['--nginx'], + dict(help='Show Nginx Error logs file', action='store_true')), + (['--php'], + dict(help='Show PHP Error logs file', action='store_true')), + (['--fpm'], + dict(help='Show PHP5-fpm slow logs file', + action='store_true')), + (['--mysql'], + dict(help='Show MySQL logs file', action='store_true')), + (['--wp'], + dict(help='Show Site specific WordPress logs file', + action='store_true')), + (['--access'], + dict(help='Show Nginx access log file', + action='store_true')), + (['site_name'], + dict(help='Website Name', nargs='?', default=None)) + ] + usage = "wo log show [] [options]" + + @expose(hide=True) + def default(self): + """Default function of log show""" + self.msg = [] + + if self.app.pargs.php: + self.app.pargs.nginx = True + + if ((not self.app.pargs.nginx) and (not self.app.pargs.fpm) + and (not self.app.pargs.mysql) and (not self.app.pargs.access) + and (not self.app.pargs.wp) and (not self.app.pargs.site_name)): + self.app.pargs.nginx = True + self.app.pargs.fpm = True + self.app.pargs.mysql = True + self.app.pargs.access = True + + if ((not self.app.pargs.nginx) and (not self.app.pargs.fpm) + and (not self.app.pargs.mysql) and (not self.app.pargs.access) + and (not self.app.pargs.wp) and (self.app.pargs.site_name)): + self.app.pargs.nginx = True + self.app.pargs.wp = True + self.app.pargs.access = True + self.app.pargs.mysql = True + + if self.app.pargs.nginx and (not self.app.pargs.site_name): + self.msg = self.msg + ["/var/log/nginx/*error.log"] + + if self.app.pargs.access and (not self.app.pargs.site_name): + self.msg = self.msg + ["/var/log/nginx/*access.log"] + + if self.app.pargs.fpm: + open('/var/log/php5/slow.log', 'a').close() + open('/var/log/php5/fpm.log', 'a').close() + self.msg = self.msg + ['/var/log/php5/slow.log', + '/var/log/php5/fpm.log'] + if self.app.pargs.mysql: + # MySQL debug will not work for remote MySQL + if WOVariables.wo_mysql_host is "localhost": + if os.path.isfile('/var/log/mysql/mysql-slow.log'): + self.msg = self.msg + ['/var/log/mysql/mysql-slow.log'] + else: + Log.info(self, "MySQL slow-log not found, skipped") + else: + Log.warn(self, "Remote MySQL found, WordOps does not support" + "remote MySQL servers or log files") + + if self.app.pargs.site_name: + webroot = "{0}{1}".format(WOVariables.wo_webroot, + self.app.pargs.site_name) + + if not os.path.isdir(webroot): + Log.error(self, "Site not present, quitting") + if self.app.pargs.access: + self.msg = self.msg + ["{0}/{1}/logs/access.log" + .format(WOVariables.wo_webroot, + self.app.pargs.site_name)] + if self.app.pargs.nginx: + self.msg = self.msg + ["{0}/{1}/logs/error.log" + .format(WOVariables.wo_webroot, + self.app.pargs.site_name)] + if self.app.pargs.wp: + if os.path.isdir('{0}/htdocs/wp-content'.format(webroot)): + if not os.path.isfile('{0}/logs/debug.log' + .format(webroot)): + if not os.path.isfile('{0}/htdocs/wp-content/debug.log' + .format(webroot)): + open("{0}/htdocs/wp-content/debug.log" + .format(webroot), + encoding='utf-8', mode='a').close() + WOShellExec.cmd_exec(self, "chown {1}: {0}/htdocs/" + "wp-content/debug.log" + "".format(webroot, + WOVariables + .wo_php_user) + ) + # create symbolic link for debug log + WOFileUtils.create_symlink(self, ["{0}/htdocs/wp-content/" + "debug.log" + .format(webroot), + '{0}/logs/debug.log' + .format(webroot)]) + + self.msg = self.msg + ["{0}/{1}/logs/debug.log" + .format(WOVariables.wo_webroot, + self.app.pargs.site_name)] + else: + Log.info(self, "Site is not WordPress site, skipping " + "WordPress logs") + + watch_list = [] + for w_list in self.msg: + watch_list = watch_list + glob.glob(w_list) + + logwatch(self, watch_list) + + +class WOLogResetController(CementBaseController): + class Meta: + label = 'reset' + description = 'Reset Nginx, PHP, MySQL log file' + stacked_on = 'log' + stacked_type = 'nested' + arguments = [ + (['--all'], + dict(help='Reset All logs file', action='store_true')), + (['--nginx'], + dict(help='Reset Nginx Error logs file', action='store_true')), + (['--php'], + dict(help='Reset PHP Error logs file', action='store_true')), + (['--fpm'], + dict(help='Reset PHP5-fpm slow logs file', + action='store_true')), + (['--mysql'], + dict(help='Reset MySQL logs file', action='store_true')), + (['--wp'], + dict(help='Reset Site specific WordPress logs file', + action='store_true')), + (['--access'], + dict(help='Reset Nginx access log file', + action='store_true')), + (['--slow-log-db'], + dict(help='Drop all rows from slowlog table in database', + action='store_true')), + (['site_name'], + dict(help='Website Name', nargs='?', default=None)) + ] + usage = "wo log reset [] [options]" + + @expose(hide=True) + def default(self): + """Default function of log reset""" + self.msg = [] + + if self.app.pargs.php: + self.app.pargs.nginx = True + + if ((not self.app.pargs.nginx) and (not self.app.pargs.fpm) + and (not self.app.pargs.mysql) and (not self.app.pargs.access) + and (not self.app.pargs.wp) and (not self.app.pargs.site_name) + and (not self.app.pargs.slow_log_db)): + self.app.pargs.nginx = True + self.app.pargs.fpm = True + self.app.pargs.mysql = True + self.app.pargs.access = True + self.app.pargs.slow_log_db = True + + if ((not self.app.pargs.nginx) and (not self.app.pargs.fpm) + and (not self.app.pargs.mysql) and (not self.app.pargs.access) + and (not self.app.pargs.wp) and (self.app.pargs.site_name) + and (not self.app.pargs.slow-log-db)): + self.app.pargs.nginx = True + self.app.pargs.wp = True + self.app.pargs.access = True + self.app.pargs.mysql = True + + if self.app.pargs.slow_log_db: + if os.path.isdir("/var/www/22222/htdocs/db/anemometer"): + Log.info(self, "Resetting MySQL slow_query_log database table") + WOMysql.execute(self, "TRUNCATE TABLE " + "slow_query_log.global_query_review_history") + WOMysql.execute(self, "TRUNCATE TABLE " + "slow_query_log.global_query_review") + + if self.app.pargs.nginx and (not self.app.pargs.site_name): + self.msg = self.msg + ["/var/log/nginx/*error.log"] + + if self.app.pargs.access and (not self.app.pargs.site_name): + self.msg = self.msg + ["/var/log/nginx/*access.log"] + + if self.app.pargs.fpm: + open('/var/log/php5/slow.log', 'a').close() + open('/var/log/php5/fpm.log', 'a').close() + self.msg = self.msg + ['/var/log/php5/slow.log', + '/var/log/php5/fpm.log'] + if self.app.pargs.mysql: + # MySQL debug will not work for remote MySQL + if WOVariables.wo_mysql_host is "localhost": + if os.path.isfile('/var/log/mysql/mysql-slow.log'): + self.msg = self.msg + ['/var/log/mysql/mysql-slow.log'] + else: + Log.info(self, "MySQL slow-log not found, skipped") + else: + Log.warn(self, "Remote MySQL found, WordOps does not support" + "remote MySQL servers or log files") + + if self.app.pargs.site_name: + webroot = "{0}{1}".format(WOVariables.wo_webroot, + self.app.pargs.site_name) + + if not os.path.isdir(webroot): + Log.error(self, "Site not present, quitting") + if self.app.pargs.access: + self.msg = self.msg + ["{0}/{1}/logs/access.log" + .format(WOVariables.wo_webroot, + self.app.pargs.site_name)] + if self.app.pargs.nginx: + self.msg = self.msg + ["{0}/{1}/logs/error.log" + .format(WOVariables.wo_webroot, + self.app.pargs.site_name)] + if self.app.pargs.wp: + if os.path.isdir('{0}/htdocs/wp-content'.format(webroot)): + if not os.path.isfile('{0}/logs/debug.log' + .format(webroot)): + if not os.path.isfile('{0}/htdocs/wp-content/debug.log' + .format(webroot)): + open("{0}/htdocs/wp-content/debug.log" + .format(webroot), + encoding='utf-8', mode='a').close() + WOShellExec.cmd_exec(self, "chown {1}: {0}/htdocs/" + "wp-content/debug.log" + "".format(webroot, + WOVariables + .wo_php_user) + ) + # create symbolic link for debug log + WOFileUtils.create_symlink(self, ["{0}/htdocs/wp-content/" + "debug.log" + .format(webroot), + '{0}/logs/debug.log' + .format(webroot)]) + + self.msg = self.msg + ["{0}/{1}/logs/debug.log" + .format(WOVariables.wo_webroot, + self.app.pargs.site_name)] + else: + Log.info(self, "Site is not WordPress site, skipping " + "WordPress logs") + + reset_list = [] + for r_list in self.msg: + reset_list = reset_list + glob.glob(r_list) + + # Clearing content of file + for r_list in reset_list: + Log.info(self, "Resetting file {file}".format(file=r_list)) + open(r_list, 'w').close() + + +class WOLogGzipController(CementBaseController): + class Meta: + label = 'gzip' + description = 'GZip Nginx, PHP, MySQL log file' + stacked_on = 'log' + stacked_type = 'nested' + arguments = [ + (['--all'], + dict(help='GZip All logs file', action='store_true')), + (['--nginx'], + dict(help='GZip Nginx Error logs file', action='store_true')), + (['--php'], + dict(help='GZip PHP Error logs file', action='store_true')), + (['--fpm'], + dict(help='GZip PHP5-fpm slow logs file', + action='store_true')), + (['--mysql'], + dict(help='GZip MySQL logs file', action='store_true')), + (['--wp'], + dict(help='GZip Site specific WordPress logs file', + action='store_true')), + (['--access'], + dict(help='GZip Nginx access log file', + action='store_true')), + (['site_name'], + dict(help='Website Name', nargs='?', default=None)) + ] + usage = "wo log gzip [] [options]" + + @expose(hide=True) + def default(self): + """Default function of log GZip""" + self.msg = [] + + if self.app.pargs.php: + self.app.pargs.nginx = True + + if ((not self.app.pargs.nginx) and (not self.app.pargs.fpm) + and (not self.app.pargs.mysql) and (not self.app.pargs.access) + and (not self.app.pargs.wp) and (not self.app.pargs.site_name)): + self.app.pargs.nginx = True + self.app.pargs.fpm = True + self.app.pargs.mysql = True + self.app.pargs.access = True + + if ((not self.app.pargs.nginx) and (not self.app.pargs.fpm) + and (not self.app.pargs.mysql) and (not self.app.pargs.access) + and (not self.app.pargs.wp) and (self.app.pargs.site_name)): + self.app.pargs.nginx = True + self.app.pargs.wp = True + self.app.pargs.access = True + self.app.pargs.mysql = True + + if self.app.pargs.nginx and (not self.app.pargs.site_name): + self.msg = self.msg + ["/var/log/nginx/*error.log"] + + if self.app.pargs.access and (not self.app.pargs.site_name): + self.msg = self.msg + ["/var/log/nginx/*access.log"] + + if self.app.pargs.fpm: + open('/var/log/php5/slow.log', 'a').close() + open('/var/log/php5/fpm.log', 'a').close() + self.msg = self.msg + ['/var/log/php5/slow.log', + '/var/log/php5/fpm.log'] + if self.app.pargs.mysql: + # MySQL debug will not work for remote MySQL + if WOVariables.wo_mysql_host is "localhost": + if os.path.isfile('/var/log/mysql/mysql-slow.log'): + self.msg = self.msg + ['/var/log/mysql/mysql-slow.log'] + else: + Log.info(self, "MySQL slow-log not found, skipped") + + else: + Log.warn(self, "Remote MySQL found, WordOps does not support" + "remote MySQL servers or log files") + + if self.app.pargs.site_name: + webroot = "{0}{1}".format(WOVariables.wo_webroot, + self.app.pargs.site_name) + + if not os.path.isdir(webroot): + Log.error(self, "Site not present, quitting") + if self.app.pargs.access: + self.msg = self.msg + ["{0}/{1}/logs/access.log" + .format(WOVariables.wo_webroot, + self.app.pargs.site_name)] + if self.app.pargs.nginx: + self.msg = self.msg + ["{0}/{1}/logs/error.log" + .format(WOVariables.wo_webroot, + self.app.pargs.site_name)] + if self.app.pargs.wp: + if os.path.isdir('{0}/htdocs/wp-content'.format(webroot)): + if not os.path.isfile('{0}/logs/debug.log' + .format(webroot)): + if not os.path.isfile('{0}/htdocs/wp-content/debug.log' + .format(webroot)): + open("{0}/htdocs/wp-content/debug.log" + .format(webroot), + encoding='utf-8', mode='a').close() + WOShellExec.cmd_exec(self, "chown {1}: {0}/htdocs/" + "wp-content/debug.log" + "".format(webroot, + WOVariables + .wo_php_user) + ) + # create symbolic link for debug log + WOFileUtils.create_symlink(self, ["{0}/htdocs/wp-content/" + "debug.log" + .format(webroot), + '{0}/logs/debug.log' + .format(webroot)]) + + self.msg = self.msg + ["{0}/{1}/logs/debug.log" + .format(WOVariables.wo_webroot, + self.app.pargs.site_name)] + else: + Log.info(self, "Site is not WordPress site, skipping " + "WordPress logs") + + gzip_list = [] + for g_list in self.msg: + gzip_list = gzip_list + glob.glob(g_list) + + # Gzip content of file + for g_list in gzip_list: + Log.info(self, "Gzipping file {file}".format(file=g_list)) + in_file = g_list + in_data = open(in_file, "rb").read() + out_gz = g_list + ".gz" + gzf = gzip.open(out_gz, "wb") + gzf.write(in_data) + gzf.close() + + +class WOLogMailController(CementBaseController): + class Meta: + label = 'mail' + description = 'Mail Nginx, PHP, MySQL log file' + stacked_on = 'log' + stacked_type = 'nested' + arguments = [ + (['--all'], + dict(help='Mail All logs file', action='store_true')), + (['--nginx'], + dict(help='Mail Nginx Error logs file', action='store_true')), + (['--php'], + dict(help='Mail PHP Error logs file', action='store_true')), + (['--fpm'], + dict(help='Mail PHP5-fpm slow logs file', + action='store_true')), + (['--mysql'], + dict(help='Mail MySQL logs file', action='store_true')), + (['--wp'], + dict(help='Mail Site specific WordPress logs file', + action='store_true')), + (['--access'], + dict(help='Mail Nginx access log file', + action='store_true')), + (['site_name'], + dict(help='Website Name', nargs='?', default=None)), + (['--to'], + dict(help='Email addresses to send log files', action='append', + dest='to', nargs=1, required=True)), + ] + usage = "wo log mail [] [options]" + + @expose(hide=True) + def default(self): + """Default function of log Mail""" + self.msg = [] + + if self.app.pargs.php: + self.app.pargs.nginx = True + + if ((not self.app.pargs.nginx) and (not self.app.pargs.fpm) + and (not self.app.pargs.mysql) and (not self.app.pargs.access) + and (not self.app.pargs.wp) and (not self.app.pargs.site_name)): + self.app.pargs.nginx = True + self.app.pargs.fpm = True + self.app.pargs.mysql = True + self.app.pargs.access = True + + if ((not self.app.pargs.nginx) and (not self.app.pargs.fpm) + and (not self.app.pargs.mysql) and (not self.app.pargs.access) + and (not self.app.pargs.wp) and (self.app.pargs.site_name)): + self.app.pargs.nginx = True + self.app.pargs.wp = True + self.app.pargs.access = True + self.app.pargs.mysql = True + + if self.app.pargs.nginx and (not self.app.pargs.site_name): + self.msg = self.msg + ["/var/log/nginx/*error.log"] + + if self.app.pargs.access and (not self.app.pargs.site_name): + self.msg = self.msg + ["/var/log/nginx/*access.log"] + + if self.app.pargs.fpm: + open('/var/log/php5/slow.log', 'a').close() + open('/var/log/php5/fpm.log', 'a').close() + self.msg = self.msg + ['/var/log/php5/slow.log', + '/var/log/php5/fpm.log'] + if self.app.pargs.mysql: + # MySQL debug will not work for remote MySQL + if WOVariables.wo_mysql_host is "localhost": + if os.path.isfile('/var/log/mysql/mysql-slow.log'): + self.msg = self.msg + ['/var/log/mysql/mysql-slow.log'] + else: + Log.info(self, "MySQL slow-log not found, skipped") + else: + Log.warn(self, "Remote MySQL found, WordOps does not support" + "remote MySQL servers or log files") + + if self.app.pargs.site_name: + webroot = "{0}{1}".format(WOVariables.wo_webroot, + self.app.pargs.site_name) + + if not os.path.isdir(webroot): + Log.error(self, "Site not present, quitting") + if self.app.pargs.access: + self.msg = self.msg + ["{0}/{1}/logs/access.log" + .format(WOVariables.wo_webroot, + self.app.pargs.site_name)] + if self.app.pargs.nginx: + self.msg = self.msg + ["{0}/{1}/logs/error.log" + .format(WOVariables.wo_webroot, + self.app.pargs.site_name)] + if self.app.pargs.wp: + if os.path.isdir('{0}/htdocs/wp-content'.format(webroot)): + if not os.path.isfile('{0}/logs/debug.log' + .format(webroot)): + if not os.path.isfile('{0}/htdocs/wp-content/debug.log' + .format(webroot)): + open("{0}/htdocs/wp-content/debug.log" + .format(webroot), + encoding='utf-8', mode='a').close() + WOShellExec.cmd_exec(self, "chown {1}: {0}/htdocs/" + "wp-content/debug.log" + "".format(webroot, + WOVariables + .wo_php_user) + ) + # create symbolic link for debug log + WOFileUtils.create_symlink(self, ["{0}/htdocs/wp-content/" + "debug.log" + .format(webroot), + '{0}/logs/debug.log' + .format(webroot)]) + + self.msg = self.msg + ["{0}/{1}/logs/debug.log" + .format(WOVariables.wo_webroot, + self.app.pargs.site_name)] + else: + Log.info(self, "Site is not WordPress site, skipping " + "WordPress logs") + + mail_list = [] + for m_list in self.msg: + mail_list = mail_list + glob.glob(m_list) + + for tomail in self.app.pargs.to: + Log.info(self, "Sending mail to {0}".format(tomail[0])) + WOSendMail("wordops", tomail[0], "{0} Log Files" + .format(WOVariables.wo_fqdn), + "Hi,\n The requested logfiles are attached." + "\n\nBest regards,\nYour WordOps worker", + files=mail_list, port=25, isTls=False) + + +def load(app): + # register the plugin class.. this only happens if the plugin is enabled + handler.register(WOLogController) + handler.register(WOLogShowController) + handler.register(WOLogResetController) + handler.register(WOLogGzipController) + handler.register(WOLogMailController) + # register a hook (function) to run after arguments are parsed. + hook.register('post_argument_parsing', wo_log_hook) diff --git a/wo/cli/plugins/models.py b/wo/cli/plugins/models.py new file mode 100644 index 0000000..a609c67 --- /dev/null +++ b/wo/cli/plugins/models.py @@ -0,0 +1,57 @@ +from sqlalchemy import Column, DateTime, String, Integer, Boolean, func +from wo.core.database import Base + + +class SiteDB(Base): + """ + Database model for site table + """ + __tablename__ = 'sites' + __table_args__ = {'extend_existing': True} + id = Column(Integer, primary_key=True) + sitename = Column(String, unique=True) + + site_type = Column(String) + cache_type = Column(String) + site_path = Column(String) + + # Use default=func.now() to set the default created time + # of a site to be the current time when a + # Site record was created + + created_on = Column(DateTime, default=func.now()) + is_enabled = Column(Boolean, unique=False, default=True, nullable=False) + is_ssl = Column(Boolean, unique=False, default=False) + storage_fs = Column(String) + storage_db = Column(String) + db_name = Column(String) + db_user = Column(String) + db_password = Column(String) + db_host = Column(String) + is_hhvm = Column(Boolean, unique=False, default=False) + php_version = Column(String) + + def __init__(self, sitename=None, site_type=None, cache_type=None, + site_path=None, site_enabled=None, + is_ssl=None, storage_fs=None, storage_db=None, db_name=None, + db_user=None, db_password=None, db_host='localhost', + hhvm=None, php_version=None): + self.sitename = sitename + self.site_type = site_type + self.cache_type = cache_type + self.site_path = site_path + self.is_enabled = site_enabled + self.is_ssl = is_ssl + self.storage_fs = storage_fs + self.storage_db = storage_db + self.db_name = db_name + self.db_user = db_user + self.db_password = db_password + self.db_host = db_host + self.is_hhvm = hhvm + self.php_version = php_version + # def __repr__(self): + # return '' % (self.site_type) + # + # def getType(self): + # return '%r>' % (self.site_type) diff --git a/wo/cli/plugins/secure.py b/wo/cli/plugins/secure.py new file mode 100644 index 0000000..8050ade --- /dev/null +++ b/wo/cli/plugins/secure.py @@ -0,0 +1,141 @@ +from cement.core.controller import CementBaseController, expose +from cement.core import handler, hook +from wo.core.aptget import WOAptGet +from wo.core.shellexec import WOShellExec +from wo.core.variables import WOVariables +from wo.core.logging import Log +from wo.core.git import WOGit +from wo.core.services import WOService +import string +import random +import sys +import hashlib +import getpass + + +def wo_secure_hook(app): + pass + + +class WOSecureController(CementBaseController): + class Meta: + label = 'secure' + stacked_on = 'base' + stacked_type = 'nested' + description = ('Secure command secure auth, ip and port') + arguments = [ + (['--auth'], + dict(help='secure auth', action='store_true')), + (['--port'], + dict(help='secure port', action='store_true')), + (['--ip'], + dict(help='secure ip', action='store_true')), + (['user_input'], + dict(help='user input', nargs='?', default=None)), + (['user_pass'], + dict(help='user pass', nargs='?', default=None))] + usage = "wo secure [options]" + + @expose(hide=True) + def default(self): + if self.app.pargs.auth: + self.secure_auth() + if self.app.pargs.port: + self.secure_port() + if self.app.pargs.ip: + self.secure_ip() + + @expose(hide=True) + def secure_auth(self): + """This function secures authentication""" + passwd = ''.join([random.choice + (string.ascii_letters + string.digits) + for n in range(6)]) + if not self.app.pargs.user_input: + username = input("Provide HTTP authentication user " + "name [{0}] :".format(WOVariables.wo_user)) + self.app.pargs.user_input = username + if username == "": + self.app.pargs.user_input = WOVariables.wo_user + if not self.app.pargs.user_pass: + password = getpass.getpass("Provide HTTP authentication " + "password [{0}] :".format(passwd)) + self.app.pargs.user_pass = password + if password == "": + self.app.pargs.user_pass = passwd + Log.debug(self, "printf username:" + "$(openssl passwd -crypt " + "password 2> /dev/null)\n\"" + "> /etc/nginx/htpasswd-wo 2>/dev/null") + WOShellExec.cmd_exec(self, "printf \"{username}:" + "$(openssl passwd -crypt " + "{password} 2> /dev/null)\n\"" + "> /etc/nginx/htpasswd-wo 2>/dev/null" + .format(username=self.app.pargs.user_input, + password=self.app.pargs.user_pass), + log=False) + WOGit.add(self, ["/etc/nginx"], + msg="Adding changed secure auth into Git") + + @expose(hide=True) + def secure_port(self): + """This function Secures port""" + if self.app.pargs.user_input: + while not self.app.pargs.user_input.isdigit(): + Log.info(self, "Please enter a valid port number ") + self.app.pargs.user_input = input("WordOps " + "admin port [22222]:") + if not self.app.pargs.user_input: + port = input("WordOps admin port [22222]:") + if port == "": + self.app.pargs.user_input = 22222 + while not port.isdigit() and port != "": + Log.info(self, "Please Enter valid port number :") + port = input("WordOps admin port [22222]:") + self.app.pargs.user_input = port + if WOVariables.wo_platform_distro == 'ubuntu': + WOShellExec.cmd_exec(self, "sed -i \"s/listen.*/listen " + "{port} default_server ssl http2;/\" " + "/etc/nginx/sites-available/22222" + .format(port=self.app.pargs.user_input)) + if WOVariables.wo_platform_distro == 'debian': + WOShellExec.cmd_exec(self, "sed -i \"s/listen.*/listen " + "{port} default_server ssl http2;/\" " + "/etc/nginx/sites-available/22222" + .format(port=self.app.pargs.user_input)) + WOGit.add(self, ["/etc/nginx"], + msg="Adding changed secure port into Git") + if not WOService.reload_service(self, 'nginx'): + Log.error(self, "service nginx reload failed. " + "check issues with `nginx -t` command") + Log.info(self, "Successfully port changed {port}" + .format(port=self.app.pargs.user_input)) + + @expose(hide=True) + def secure_ip(self): + """IP whitelisting""" + newlist = [] + if not self.app.pargs.user_input: + ip = input("Enter the comma separated IP addresses " + "to white list [127.0.0.1]:") + self.app.pargs.user_input = ip + try: + user_ip = self.app.pargs.user_input.split(',') + except Exception as e: + user_ip = ['127.0.0.1'] + for ip_addr in user_ip: + if not ("exist_ip_address "+ip_addr in open('/etc/nginx/common/' + 'acl.conf').read()): + WOShellExec.cmd_exec(self, "sed -i " + "\"/deny/i allow {whitelist_address}\;\"" + " /etc/nginx/common/acl.conf" + .format(whitelist_address=ip_addr)) + WOGit.add(self, ["/etc/nginx"], + msg="Adding changed secure ip into Git") + + Log.info(self, "Successfully added IP address in acl.conf file") + + +def load(app): + handler.register(WOSecureController) + hook.register('post_argument_parsing', wo_secure_hook) diff --git a/wo/cli/plugins/site.py b/wo/cli/plugins/site.py new file mode 100644 index 0000000..e961711 --- /dev/null +++ b/wo/cli/plugins/site.py @@ -0,0 +1,1735 @@ +# """WordOps site controller.""" +from cement.core.controller import CementBaseController, expose +from cement.core import handler, hook +from wo.core.cron import WOCron +from wo.core.sslutils import SSL +from wo.core.variables import WOVariables +from wo.core.domainvalidate import ValidateDomain +from wo.core.fileutils import WOFileUtils +from wo.cli.plugins.site_functions import * +from wo.core.services import WOService +from wo.cli.plugins.sitedb import * +from wo.core.git import WOGit +from subprocess import Popen +from wo.core.nginxhashbucket import hashbucket +import sys +import os +import glob +import subprocess + + +def wo_site_hook(app): + from wo.core.database import init_db + import wo.cli.plugins.models + init_db(app) + + +class WOSiteController(CementBaseController): + class Meta: + label = 'site' + stacked_on = 'base' + stacked_type = 'nested' + description = ('Performs website specific operations') + arguments = [ + (['site_name'], + dict(help='Website name', nargs='?')), + ] + usage = "wo site (command) [options]" + + @expose(hide=True) + def default(self): + self.app.args.print_help() + + @expose(help="Enable site example.com") + def enable(self): + if not self.app.pargs.site_name: + try: + while not self.app.pargs.site_name: + self.app.pargs.site_name = (input('Enter site name : ') + .strip()) + except IOError as e: + Log.error(self, 'could not input site name') + + self.app.pargs.site_name = self.app.pargs.site_name.strip() + # validate domain name + (wo_domain, wo_www_domain) = ValidateDomain(self.app.pargs.site_name) + + # check if site exists + if not check_domain_exists(self, wo_domain): + Log.error(self, "site {0} does not exist".format(wo_domain)) + if os.path.isfile('/etc/nginx/sites-available/{0}' + .format(wo_domain)): + Log.info(self, "Enable domain {0:10} \t".format(wo_domain), end='') + WOFileUtils.create_symlink(self, + ['/etc/nginx/sites-available/{0}' + .format(wo_domain), + '/etc/nginx/sites-enabled/{0}' + .format(wo_domain)]) + WOGit.add(self, ["/etc/nginx"], + msg="Enabled {0} " + .format(wo_domain)) + updateSiteInfo(self, wo_domain, enabled=True) + Log.info(self, "[" + Log.ENDC + "OK" + Log.OKBLUE + "]") + if not WOService.reload_service(self, 'nginx'): + Log.error(self, "service nginx reload failed. " + "check issues with `nginx -t` command") + else: + Log.error(self, "nginx configuration file does not exist" + .format(wo_domain)) + + @expose(help="Disable site example.com") + def disable(self): + if not self.app.pargs.site_name: + try: + while not self.app.pargs.site_name: + self.app.pargs.site_name = (input('Enter site name : ') + .strip()) + + except IOError as e: + Log.error(self, 'could not input site name') + self.app.pargs.site_name = self.app.pargs.site_name.strip() + (wo_domain, wo_www_domain) = ValidateDomain(self.app.pargs.site_name) + # check if site exists + if not check_domain_exists(self, wo_domain): + Log.error(self, "site {0} does not exist".format(wo_domain)) + + if os.path.isfile('/etc/nginx/sites-available/{0}' + .format(wo_domain)): + Log.info(self, "Disable domain {0:10} \t" + .format(wo_domain), end='') + if not os.path.isfile('/etc/nginx/sites-enabled/{0}' + .format(wo_domain)): + Log.debug(self, "Site {0} already disabled".format(wo_domain)) + Log.info(self, "[" + Log.FAIL + "Failed" + Log.OKBLUE+"]") + else: + WOFileUtils.remove_symlink(self, + '/etc/nginx/sites-enabled/{0}' + .format(wo_domain)) + WOGit.add(self, ["/etc/nginx"], + msg="Disabled {0} " + .format(wo_domain)) + updateSiteInfo(self, wo_domain, enabled=False) + Log.info(self, "[" + Log.ENDC + "OK" + Log.OKBLUE + "]") + if not WOService.reload_service(self, 'nginx'): + Log.error(self, "service nginx reload failed. " + "check issues with `nginx -t` command") + else: + Log.error(self, "nginx configuration file does not exist" + .format(wo_domain)) + + @expose(help="Get example.com information") + def info(self): + if not self.app.pargs.site_name: + try: + while not self.app.pargs.site_name: + self.app.pargs.site_name = (input('Enter site name : ') + .strip()) + except IOError as e: + Log.error(self, 'could not input site name') + self.app.pargs.site_name = self.app.pargs.site_name.strip() + (wo_domain, wo_www_domain) = ValidateDomain(self.app.pargs.site_name) + wo_db_name = '' + wo_db_user = '' + wo_db_pass = '' + hhvm = '' + + if not check_domain_exists(self, wo_domain): + Log.error(self, "site {0} does not exist".format(wo_domain)) + if os.path.isfile('/etc/nginx/sites-available/{0}' + .format(wo_domain)): + siteinfo = getSiteInfo(self, wo_domain) + + sitetype = siteinfo.site_type + cachetype = siteinfo.cache_type + wo_site_webroot = siteinfo.site_path + access_log = (wo_site_webroot + '/logs/access.log') + error_log = (wo_site_webroot + '/logs/error.log') + wo_db_name = siteinfo.db_name + wo_db_user = siteinfo.db_user + wo_db_pass = siteinfo.db_password + wo_db_host = siteinfo.db_host + if sitetype != "html": + hhvm = ("enabled" if siteinfo.is_hhvm else "disabled") + if sitetype == "proxy": + access_log = "/var/log/nginx/{0}.access.log".format(wo_domain) + error_log = "/var/log/nginx/{0}.error.log".format(wo_domain) + wo_site_webroot = '' + + php_version = siteinfo.php_version + + ssl = ("enabled" if siteinfo.is_ssl else "disabled") + if (ssl == "enabled"): + sslprovider = "Lets Encrypt" + sslexpiry = str(SSL.getExpirationDate(self,wo_domain)) + else: + sslprovider = '' + sslexpiry = '' + data = dict(domain=wo_domain, webroot=wo_site_webroot, + accesslog=access_log, errorlog=error_log, + dbname=wo_db_name, dbuser=wo_db_user,php_version=php_version, + dbpass=wo_db_pass, hhvm=hhvm, + ssl=ssl, sslprovider=sslprovider, sslexpiry= sslexpiry, + type=sitetype + " " + cachetype + " ({0})" + .format("enabled" if siteinfo.is_enabled else + "disabled")) + self.app.render((data), 'siteinfo.mustache') + else: + Log.error(self, "nginx configuration file does not exist" + .format(wo_domain)) + + @expose(help="Monitor example.com logs") + def log(self): + self.app.pargs.site_name = self.app.pargs.site_name.strip() + (wo_domain, wo_www_domain) = ValidateDomain(self.app.pargs.site_name) + wo_site_webroot = getSiteInfo(self, wo_domain).site_path + + if not check_domain_exists(self, wo_domain): + Log.error(self, "site {0} does not exist".format(wo_domain)) + logfiles = glob.glob(wo_site_webroot + '/logs/*.log') + if logfiles: + logwatch(self, logfiles) + + @expose(help="Display Nginx configuration of example.com") + def show(self): + if not self.app.pargs.site_name: + try: + while not self.app.pargs.site_name: + self.app.pargs.site_name = (input('Enter site name : ') + .strip()) + except IOError as e: + Log.error(self, 'could not input site name') + # TODO Write code for wo site edit command here + self.app.pargs.site_name = self.app.pargs.site_name.strip() + (wo_domain, wo_www_domain) = ValidateDomain(self.app.pargs.site_name) + + if not check_domain_exists(self, wo_domain): + Log.error(self, "site {0} does not exist".format(wo_domain)) + + if os.path.isfile('/etc/nginx/sites-available/{0}' + .format(wo_domain)): + Log.info(self, "Display NGINX configuration for {0}" + .format(wo_domain)) + f = open('/etc/nginx/sites-available/{0}'.format(wo_domain), + encoding='utf-8', mode='r') + text = f.read() + Log.info(self, Log.ENDC + text) + f.close() + else: + Log.error(self, "nginx configuration file does not exists" + .format(wo_domain)) + + @expose(help="Change directory to site webroot") + def cd(self): + if not self.app.pargs.site_name: + try: + while not self.app.pargs.site_name: + self.app.pargs.site_name = (input('Enter site name : ') + .strip()) + except IOError as e: + Log.error(self, 'Unable to read input, please try again') + + self.app.pargs.site_name = self.app.pargs.site_name.strip() + (wo_domain, wo_www_domain) = ValidateDomain(self.app.pargs.site_name) + + if not check_domain_exists(self, wo_domain): + Log.error(self, "site {0} does not exist".format(wo_domain)) + + wo_site_webroot = getSiteInfo(self, wo_domain).site_path + WOFileUtils.chdir(self, wo_site_webroot) + + try: + subprocess.call(['bash']) + except OSError as e: + Log.debug(self, "{0}{1}".format(e.errno, e.strerror)) + Log.error(self, "unable to change directory") + + +class WOSiteEditController(CementBaseController): + class Meta: + label = 'edit' + stacked_on = 'site' + stacked_type = 'nested' + description = ('Edit Nginx configuration of site') + arguments = [ + (['site_name'], + dict(help='domain name for the site', + nargs='?')), + ] + + @expose(hide=True) + def default(self): + if not self.app.pargs.site_name: + try: + while not self.app.pargs.site_name: + self.app.pargs.site_name = (input('Enter site name : ') + .strip()) + except IOError as e: + Log.error(self, 'Unable to read input, Please try again') + + self.app.pargs.site_name = self.app.pargs.site_name.strip() + (wo_domain, wo_www_domain) = ValidateDomain(self.app.pargs.site_name) + + if not check_domain_exists(self, wo_domain): + Log.error(self, "site {0} does not exist".format(wo_domain)) + + wo_site_webroot = WOVariables.wo_webroot + wo_domain + + if os.path.isfile('/etc/nginx/sites-available/{0}' + .format(wo_domain)): + try: + WOShellExec.invoke_editor(self, '/etc/nginx/sites-availa' + 'ble/{0}'.format(wo_domain)) + except CommandExecutionError as e: + Log.error(self, "Failed invoke editor") + if (WOGit.checkfilestatus(self, "/etc/nginx", + '/etc/nginx/sites-available/{0}'.format(wo_domain))): + WOGit.add(self, ["/etc/nginx"], msg="Edit website: {0}" + .format(wo_domain)) + # Reload NGINX + if not WOService.reload_service(self, 'nginx'): + Log.error(self, "service nginx reload failed. " + "check issues with `nginx -t` command") + else: + Log.error(self, "nginx configuration file does not exists" + .format(wo_domain)) + + +class WOSiteCreateController(CementBaseController): + class Meta: + label = 'create' + stacked_on = 'site' + stacked_type = 'nested' + description = ('this commands set up configuration and installs ' + 'required files as options are provided') + arguments = [ + (['site_name'], + dict(help='domain name for the site to be created.', + nargs='?')), + (['--html'], + dict(help="create html site", action='store_true')), + (['--php'], + dict(help="create php site", action='store_true')), + (['--php7'], + dict(help="create php 7.0 site", action='store_true')), + (['--mysql'], + dict(help="create mysql site", action='store_true')), + (['--wp'], + dict(help="create wordpress single site", + action='store_true')), + (['--wpsubdir'], + dict(help="create wordpress multisite with subdirectory setup", + action='store_true')), + (['--wpsubdomain'], + dict(help="create wordpress multisite with subdomain setup", + action='store_true')), + (['--wpfc'], + dict(help="create wordpress single/multi site with wpfc cache", + action='store_true')), + (['--wpsc'], + dict(help="create wordpress single/multi site with wpsc cache", + action='store_true')), + (['--wpredis'], + dict(help="create wordpress single/multi site with redis cache", + action='store_true')), + (['--hhvm'], + dict(help="create HHVM site", action='store_true')), + (['-le','--letsencrypt'], + dict(help="configure letsencrypt ssl for the site", action='store_true')), + (['--user'], + dict(help="provide user for wordpress site")), + (['--email'], + dict(help="provide email address for wordpress site")), + (['--pass'], + dict(help="provide password for wordpress user", + dest='wppass')), + (['--proxy'], + dict(help="create proxy for site", nargs='+')), + (['--experimental'], + dict(help="Enable Experimenal packages without prompt", + action='store_true')), + ] + + @expose(hide=True) + def default(self): + # self.app.render((data), 'default.mustache') + # Check domain name validation + data = dict() + host, port = None, None + try: + stype, cache = detSitePar(vars(self.app.pargs)) + except RuntimeError as e: + Log.debug(self, str(e)) + Log.error(self, "Please provide valid options to creating site") + + if stype is None and self.app.pargs.proxy: + stype, cache = 'proxy', '' + proxyinfo = self.app.pargs.proxy[0].strip() + if not proxyinfo: + Log.error(self, "Please provide proxy server host information") + proxyinfo = proxyinfo.split(':') + host = proxyinfo[0].strip() + port = '80' if len(proxyinfo) < 2 else proxyinfo[1].strip() + elif stype is None and not self.app.pargs.proxy: + stype, cache = 'html', 'basic' + elif stype and self.app.pargs.proxy: + Log.error(self, "proxy should not be used with other site types") + if (self.app.pargs.proxy and self.app.pargs.hhvm): + Log.error(self, "Proxy site can not run on hhvm") + + if not self.app.pargs.site_name: + try: + while not self.app.pargs.site_name: + # preprocessing before finalize site name + self.app.pargs.site_name = (input('Enter site name : ') + .strip()) + except IOError as e: + Log.debug(self, str(e)) + Log.error(self, "Unable to input site name, Please try again!") + + self.app.pargs.site_name = self.app.pargs.site_name.strip() + (wo_domain, wo_www_domain) = ValidateDomain(self.app.pargs.site_name) + + if not wo_domain.strip(): + Log.error("Invalid domain name, " + "Provide valid domain name") + + wo_site_webroot = WOVariables.wo_webroot + wo_domain + + if check_domain_exists(self, wo_domain): + Log.error(self, "site {0} already exists".format(wo_domain)) + elif os.path.isfile('/etc/nginx/sites-available/{0}' + .format(wo_domain)): + Log.error(self, "Nginx configuration /etc/nginx/sites-available/" + "{0} already exists".format(wo_domain)) + + if stype == 'proxy': + data['site_name'] = wo_domain + data['www_domain'] = wo_www_domain + data['proxy'] = True + data['host'] = host + data['port'] = port + wo_site_webroot = "" + + if self.app.pargs.php7: + data = dict(site_name=wo_domain, www_domain=wo_www_domain, + static=False, basic=False, php7=True, wp=False, + wpfc=False, wpsc=False, multisite=False, + wpsubdir=False, webroot=wo_site_webroot) + data['basic'] = True + + if stype in ['html', 'php' ]: + data = dict(site_name=wo_domain, www_domain=wo_www_domain, + static=True, basic=False, php7=False, wp=False, + wpfc=False, wpsc=False, multisite=False, + wpsubdir=False, webroot=wo_site_webroot) + + if stype == 'php': + data['static'] = False + data['basic'] = True + + elif stype in ['mysql', 'wp', 'wpsubdir', 'wpsubdomain']: + + data = dict(site_name=wo_domain, www_domain=wo_www_domain, + static=False, basic=True, wp=False, wpfc=False, + wpsc=False, wpredis=False, multisite=False, + wpsubdir=False, webroot=wo_site_webroot, + wo_db_name='', wo_db_user='', wo_db_pass='', + wo_db_host='') + + if stype in ['wp', 'wpsubdir', 'wpsubdomain']: + data['wp'] = True + data['basic'] = False + data[cache] = True + data['wp-user'] = self.app.pargs.user + data['wp-email'] = self.app.pargs.email + data['wp-pass'] = self.app.pargs.wppass + if stype in ['wpsubdir', 'wpsubdomain']: + data['multisite'] = True + if stype == 'wpsubdir': + data['wpsubdir'] = True + else: + pass + + if stype == "html" and self.app.pargs.hhvm: + Log.error(self, "Can not create HTML site with HHVM") + + if data and self.app.pargs.php7: + if (not self.app.pargs.experimental): + Log.info(self, "PHP7.0 is experimental feature and it may not " + "work with all CSS/JS/Cache of your site.\nDo you wish" + " to install PHP 7.0 now for {0}?".format(wo_domain)) + + # Check prompt + check_prompt = input("Type \"y\" to continue [n]:") + if check_prompt != "Y" and check_prompt != "y": + Log.info(self, "Not using PHP 7.0 for site.") + data['php7'] = False + data['basic'] = True + php7 = 0 + self.app.pargs.php7 = False + else: + data['php7'] = True + php7 = 1 + else: + data['php7'] = True + php7 = 1 + elif data: + data['php7'] = False + php7 = 0 + + if (not self.app.pargs.wpfc) and (not self.app.pargs.wpsc) and (not self.app.pargs.wpredis) and (not self.app.pargs.hhvm): + data['basic'] = True + + if data and self.app.pargs.hhvm: + if (not self.app.pargs.experimental): + Log.info(self, "HHVM is experimental feature and it may not " + "work with all plugins of your site.\nYou can " + "disable it by passing --hhvm=off later.\nDo you wish" + " to enable HHVM now for {0}?".format(wo_domain)) + + # Check prompt + check_prompt = input("Type \"y\" to continue [n]:") + if check_prompt != "Y" and check_prompt != "y": + Log.info(self, "Not using HHVM for site.") + data['hhvm'] = False + hhvm = 0 + self.app.pargs.hhvm = False + else: + data['hhvm'] = True + hhvm = 1 + else: + data['hhvm'] = True + hhvm = 1 + + elif data: + data['hhvm'] = False + hhvm = 0 + + if (cache == 'wpredis' and (not self.app.pargs.experimental)): + Log.info(self, "Redis is experimental feature and it may not " + "work with all CSS/JS/Cache of your site.\nYou can " + "disable it by changing cache later.\nDo you wish" + " to enable Redis now for {0}?".format(wo_domain)) + + # Check prompt + check_prompt = input("Type \"y\" to continue [n]:") + if check_prompt != "Y" and check_prompt != "y": + Log.error(self, "Not using Redis for site") + cache = 'basic' + data['wpredis'] = False + data['basic'] = True + self.app.pargs.wpredis = False + + # Check rerequired packages are installed or not + wo_auth = site_package_check(self, stype) + + try: + pre_run_checks(self) + except SiteError as e: + Log.debug(self, str(e)) + Log.error(self, "NGINX configuration check failed.") + + try: + try: + # setup NGINX configuration, and webroot + setupdomain(self, data) + + # Fix Nginx Hashbucket size error + hashbucket(self) + except SiteError as e: + # call cleanup actions on failure + Log.info(self, Log.FAIL + "There was a serious error encountered...") + Log.info(self, Log.FAIL + "Cleaning up afterwards...") + doCleanupAction(self, domain=wo_domain, + webroot=data['webroot']) + Log.debug(self, str(e)) + Log.error(self, "Check the log for details: " + "`tail /var/log/wo/wordops.log` and please try again") + + if 'proxy' in data.keys() and data['proxy']: + addNewSite(self, wo_domain, stype, cache, wo_site_webroot) + # Service Nginx Reload + if not WOService.reload_service(self, 'nginx'): + Log.info(self, Log.FAIL + "There was a serious error encountered...") + Log.info(self, Log.FAIL + "Cleaning up afterwards...") + doCleanupAction(self, domain=wo_domain) + deleteSiteInfo(self, wo_domain) + Log.error(self, "service nginx reload failed. " + "check issues with `nginx -t` command") + Log.error(self, "Check the log for details: " + "`tail /var/log/wo/wordops.log` and please try again") + if wo_auth and len(wo_auth): + for msg in wo_auth: + Log.info(self, Log.ENDC + msg, log=False) + Log.info(self, "Successfully created site" + " http://{0}".format(wo_domain)) + return + + if data['php7']: + php_version = "7.0" + else: + php_version = "5.6" + + + addNewSite(self, wo_domain, stype, cache, wo_site_webroot, + hhvm=hhvm, php_version=php_version) + + # Setup database for MySQL site + if 'wo_db_name' in data.keys() and not data['wp']: + try: + data = setupdatabase(self, data) + # Add database information for site into database + updateSiteInfo(self, wo_domain, db_name=data['wo_db_name'], + db_user=data['wo_db_user'], + db_password=data['wo_db_pass'], + db_host=data['wo_db_host']) + except SiteError as e: + # call cleanup actions on failure + Log.debug(self, str(e)) + Log.info(self, Log.FAIL + "There was a serious error encountered...") + Log.info(self, Log.FAIL + "Cleaning up afterwards...") + doCleanupAction(self, domain=wo_domain, + webroot=data['webroot'], + dbname=data['wo_db_name'], + dbuser=data['wo_db_user'], + dbhost=data['wo_db_host']) + deleteSiteInfo(self, wo_domain) + Log.error(self, "Check the log for details: " + "`tail /var/log/wo/wordops.log` and please try again") + + try: + wodbconfig = open("{0}/wo-config.php" + .format(wo_site_webroot), + encoding='utf-8', mode='w') + wodbconfig.write("" + .format(data['wo_db_name'], + data['wo_db_user'], + data['wo_db_pass'], + data['wo_db_host'])) + wodbconfig.close() + stype = 'mysql' + except IOError as e: + Log.debug(self, str(e)) + Log.debug(self, "Error occured while generating " + "wo-config.php") + Log.info(self, Log.FAIL + "There was a serious error encountered...") + Log.info(self, Log.FAIL + "Cleaning up afterwards...") + doCleanupAction(self, domain=wo_domain, + webroot=data['webroot'], + dbname=data['wo_db_name'], + dbuser=data['wo_db_user'], + dbhost=data['wo_db_host']) + deleteSiteInfo(self, wo_domain) + Log.error(self, "Check the log for details: " + "`tail /var/log/wo/wordops.log` and please try again") + + # Setup WordPress if Wordpress site + if data['wp']: + try: + wo_wp_creds = setupwordpress(self, data) + # Add database information for site into database + updateSiteInfo(self, wo_domain, db_name=data['wo_db_name'], + db_user=data['wo_db_user'], + db_password=data['wo_db_pass'], + db_host=data['wo_db_host']) + except SiteError as e: + # call cleanup actions on failure + Log.debug(self, str(e)) + Log.info(self, Log.FAIL + "There was a serious error encountered...") + Log.info(self, Log.FAIL + "Cleaning up afterwards...") + doCleanupAction(self, domain=wo_domain, + webroot=data['webroot'], + dbname=data['wo_db_name'], + dbuser=data['wo_db_user'], + dbhost=data['wo_mysql_grant_host']) + deleteSiteInfo(self, wo_domain) + Log.error(self, "Check the log for details: " + "`tail /var/log/wo/wordops.log` and please try again") + + # Service Nginx Reload call cleanup if failed to reload nginx + if not WOService.reload_service(self, 'nginx'): + Log.info(self, Log.FAIL + "There was a serious error encountered...") + Log.info(self, Log.FAIL + "Cleaning up afterwards...") + doCleanupAction(self, domain=wo_domain, + webroot=data['webroot']) + if 'wo_db_name' in data.keys(): + doCleanupAction(self, domain=wo_domain, + dbname=data['wo_db_name'], + dbuser=data['wo_db_user'], + dbhost=data['wo_mysql_grant_host']) + deleteSiteInfo(self, wo_domain) + Log.info(self, Log.FAIL + "service nginx reload failed." + " check issues with `nginx -t` command.") + Log.error(self, "Check the log for details: " + "`tail /var/log/wo/wordops.log` and please try again") + + WOGit.add(self, ["/etc/nginx"], + msg="{0} created with {1} {2}" + .format(wo_www_domain, stype, cache)) + # Setup Permissions for webroot + try: + setwebrootpermissions(self, data['webroot']) + except SiteError as e: + Log.debug(self, str(e)) + Log.info(self, Log.FAIL + "There was a serious error encountered...") + Log.info(self, Log.FAIL + "Cleaning up afterwards...") + doCleanupAction(self, domain=wo_domain, + webroot=data['webroot']) + if 'wo_db_name' in data.keys(): + print("Inside db cleanup") + doCleanupAction(self, domain=wo_domain, + dbname=data['wo_db_name'], + dbuser=data['wo_db_user'], + dbhost=data['wo_mysql_grant_host']) + deleteSiteInfo(self, wo_domain) + Log.error(self, "Check the log for details: " + "`tail /var/log/wo/wordops.log` and please try again") + + if wo_auth and len(wo_auth): + for msg in wo_auth: + Log.info(self, Log.ENDC + msg, log=False) + + if data['wp']: + Log.info(self, Log.ENDC + "WordPress admin user :" + " {0}".format(wo_wp_creds['wp_user']), log=False) + Log.info(self, Log.ENDC + "WordPress admin user password : {0}" + .format(wo_wp_creds['wp_pass']), log=False) + + display_cache_settings(self, data) + + Log.info(self, "Successfully created site" + " http://{0}".format(wo_domain)) + except SiteError as e: + Log.error(self, "Check the log for details: " + "`tail /var/log/wo/wordops.log` and please try again") + + if self.app.pargs.letsencrypt : + if (not self.app.pargs.experimental): + if stype in ['wpsubdomain']: + Log.warn(self, "Wildcard domains are not supported in Lets Encrypt.\nWP SUBDOMAIN site will get SSL for primary site only.") + + Log.info(self, "Letsencrypt is currently in beta phase." + " \nDo you wish" + " to enable SSl now for {0}?".format(wo_domain)) + + # Check prompt + check_prompt = input("Type \"y\" to continue [n]:") + if check_prompt != "Y" and check_prompt != "y": + data['letsencrypt'] = False + letsencrypt = False + else: + data['letsencrypt'] = True + letsencrypt = True + else: + data['letsencrypt'] = True + letsencrypt = True + + if data['letsencrypt'] is True: + setupLetsEncrypt(self, wo_domain) + httpsRedirect(self,wo_domain) + Log.info(self,"Creating Cron Job for cert auto-renewal") + WOCron.setcron_weekly(self,'wo site update --le=renew --all 2> /dev/null'.format(wo_domain),'Renew all' + ' letsencrypt SSL cert. Set by WordOps') + + if not WOService.reload_service(self, 'nginx'): + Log.error(self, "service nginx reload failed. " + "check issues with `nginx -t` command") + + Log.info(self, "Congratulations! Successfully Configured SSl for Site " + " https://{0}".format(wo_domain)) + + if (SSL.getExpirationDays(self,wo_domain)>0): + Log.info(self, "Your cert will expire within " + str(SSL.getExpirationDays(self,wo_domain)) + " days.") + else: + Log.warn(self, "Your cert already EXPIRED ! .PLEASE renew soon . ") + + # Add nginx conf folder into GIT + WOGit.add(self, ["{0}/conf/nginx".format(wo_site_webroot)], + msg="Adding letsencrypts config of site: {0}" + .format(wo_domain)) + updateSiteInfo(self, wo_domain, ssl=letsencrypt) + + elif data['letsencrypt'] is False: + Log.info(self, "Not using Let\'s encrypt for Site " + " http://{0}".format(wo_domain)) + + + + +class WOSiteUpdateController(CementBaseController): + class Meta: + label = 'update' + stacked_on = 'site' + stacked_type = 'nested' + description = ('This command updates websites configuration to ' + 'another as per the options are provided') + arguments = [ + (['site_name'], + dict(help='domain name for the site to be updated', + nargs='?')), + (['--password'], + dict(help="update to password for wordpress site user", + action='store_true')), + (['--html'], + dict(help="update to html site", action='store_true')), + (['--php'], + dict(help="update to php site", action='store_true')), + (['--php7'], + dict(help="update to php7 site", + action='store' or 'store_const', + choices=('on', 'off'), const='on', nargs='?')), + (['--mysql'], + dict(help="update to mysql site", action='store_true')), + (['--wp'], + dict(help="update to wordpress single site", + action='store_true')), + (['--wpsubdir'], + dict(help="update to wpsubdir site", action='store_true')), + (['--wpsubdomain'], + dict(help="update to wpsubdomain site", action='store_true')), + (['--wpfc'], + dict(help="update to wpfc cache", action='store_true')), + (['--wpsc'], + dict(help="update to wpsc cache", action='store_true')), + (['--wpredis'], + dict(help="update to redis cache", action='store_true')), + (['--hhvm'], + dict(help='Use HHVM for site', + action='store' or 'store_const', + choices=('on', 'off'), const='on', nargs='?')), + (['-le','--letsencrypt'], + dict(help="configure letsencrypt ssl for the site", + action='store' or 'store_const', + choices=('on', 'off', 'renew'), const='on', nargs='?')), + (['--proxy'], + dict(help="update to proxy site", nargs='+')), + (['--experimental'], + dict(help="Enable Experimenal packages without prompt", + action='store_true')), + (['--all'], + dict(help="update all sites", action='store_true')), + ] + + @expose(help="Update site type or cache") + def default(self): + pargs = self.app.pargs + + if pargs.all: + if pargs.site_name: + Log.error(self, "`--all` option cannot be used with site name" + " provided") + if pargs.html: + Log.error(self, "No site can be updated to html") + + if not (pargs.php or pargs.php7 or + pargs.mysql or pargs.wp or pargs.wpsubdir or + pargs.wpsubdomain or pargs.wpfc or pargs.wpsc or + pargs.hhvm or pargs.wpredis or pargs.letsencrypt): + Log.error(self, "Please provide options to update sites.") + + if pargs.all: + if pargs.site_name: + Log.error(self, "`--all` option cannot be used with site name" + " provided") + + sites = getAllsites(self) + if not sites: + pass + else: + for site in sites: + pargs.site_name = site.sitename + Log.info(self, Log.ENDC + Log.BOLD + "Updating site {0}," + " please wait..." + .format(pargs.site_name)) + self.doupdatesite(pargs) + print("\n") + else: + self.doupdatesite(pargs) + + def doupdatesite(self, pargs): + hhvm = None + letsencrypt = False + php7 = None + + + data = dict() + try: + stype, cache = detSitePar(vars(pargs)) + except RuntimeError as e: + Log.debug(self, str(e)) + Log.error(self, "Please provide valid options combination for" + " site update") + + if stype is None and pargs.proxy: + stype, cache = 'proxy', '' + proxyinfo = pargs.proxy[0].strip() + if not proxyinfo: + Log.error(self, "Please provide proxy server host information") + proxyinfo = proxyinfo.split(':') + host = proxyinfo[0].strip() + port = '80' if len(proxyinfo) < 2 else proxyinfo[1].strip() + elif stype is None and not (pargs.proxy or pargs.letsencrypt): + stype, cache = 'html', 'basic' + elif stype and pargs.proxy: + Log.error(self, "--proxy can not be used with other site types") + if (pargs.proxy and pargs.hhvm): + Log.error(self, "Proxy site can not run on hhvm") + + if not pargs.site_name: + try: + while not pargs.site_name: + pargs.site_name = (input('Enter site name : ').strip()) + except IOError as e: + Log.error(self, 'Unable to input site name, Please try again!') + + pargs.site_name = pargs.site_name.strip() + (wo_domain, + wo_www_domain, ) = ValidateDomain(pargs.site_name) + wo_site_webroot = WOVariables.wo_webroot + wo_domain + + check_site = getSiteInfo(self, wo_domain) + + if check_site is None: + Log.error(self, " Site {0} does not exist.".format(wo_domain)) + else: + oldsitetype = check_site.site_type + oldcachetype = check_site.cache_type + old_hhvm = check_site.is_hhvm + check_ssl = check_site.is_ssl + check_php_version = check_site.php_version + + if check_php_version == "7.0": + old_php7 = True + else: + old_php7 = False + + if (pargs.password and not (pargs.html or + pargs.php or pargs.php7 or pargs.mysql or + pargs.wp or pargs.wpfc or pargs.wpsc or + pargs.wpsubdir or pargs.wpsubdomain)): + try: + updatewpuserpassword(self, wo_domain, wo_site_webroot) + except SiteError as e: + Log.debug(self, str(e)) + Log.info(self, "\nPassword Unchanged.") + return 0 + + if ((stype == "proxy" and stype == oldsitetype and self.app.pargs.hhvm) + or (stype == "proxy" and + stype == oldsitetype )): + Log.info(self, Log.FAIL + + "Can not update proxy site to HHVM") + return 1 + if stype == "html" and stype == oldsitetype and self.app.pargs.hhvm: + Log.info(self, Log.FAIL + "Can not update HTML site to HHVM") + return 1 + + if ((stype == 'php' and oldsitetype not in ['html', 'proxy', 'php7']) or + # (stype == 'php7' and oldsitetype not in ['html', 'mysql', 'php', 'php7', 'wp', 'wpsubdir', 'wpsubdomain', ]) or + (stype == 'mysql' and oldsitetype not in ['html', 'php', + 'proxy','php7']) or + (stype == 'wp' and oldsitetype not in ['html', 'php', 'mysql', + 'proxy', 'wp', 'php7']) or + (stype == 'wpsubdir' and oldsitetype in ['wpsubdomain']) or + (stype == 'wpsubdomain' and oldsitetype in ['wpsubdir']) or + (stype == oldsitetype and cache == oldcachetype) and + not pargs.php7): + Log.info(self, Log.FAIL + "can not update {0} {1} to {2} {3}". + format(oldsitetype, oldcachetype, stype, cache)) + return 1 + + if stype == 'proxy': + data['site_name'] = wo_domain + data['www_domain'] = wo_www_domain + data['proxy'] = True + data['host'] = host + data['port'] = port + hhvm = False + data['webroot'] = wo_site_webroot + data['currsitetype'] = oldsitetype + data['currcachetype'] = oldcachetype + + if stype == 'php': + data = dict(site_name=wo_domain, www_domain=wo_www_domain, + static=False, basic=True, wp=False, wpfc=False, + wpsc=False, wpredis=False, multisite=False, + wpsubdir=False, webroot=wo_site_webroot, + currsitetype=oldsitetype, currcachetype=oldcachetype) + + elif stype in ['mysql', 'wp', 'wpsubdir', 'wpsubdomain']: + + data = dict(site_name=wo_domain, www_domain=wo_www_domain, + static=False, basic=True, wp=False, wpfc=False, + wpsc=False, wpredis=False, multisite=False, + wpsubdir=False, webroot=wo_site_webroot, + wo_db_name='', wo_db_user='', wo_db_pass='', + wo_db_host='', + currsitetype=oldsitetype, currcachetype=oldcachetype) + + if stype in ['wp', 'wpsubdir', 'wpsubdomain']: + data['wp'] = True + data['basic'] = False + data[cache] = True + if stype in ['wpsubdir', 'wpsubdomain']: + data['multisite'] = True + if stype == 'wpsubdir': + data['wpsubdir'] = True + + if pargs.hhvm or pargs.php7: + if not data: + data = dict(site_name=wo_domain, www_domain=wo_www_domain, + currsitetype=oldsitetype, + currcachetype=oldcachetype, + webroot=wo_site_webroot) + stype = oldsitetype + cache = oldcachetype + if oldsitetype == 'html' or oldsitetype == 'proxy': + data['static'] = True + data['wp'] = False + data['multisite'] = False + data['wpsubdir'] = False + elif oldsitetype == 'php' or oldsitetype == 'mysql': + data['static'] = False + data['wp'] = False + data['multisite'] = False + data['wpsubdir'] = False + elif oldsitetype == 'wp': + data['static'] = False + data['wp'] = True + data['multisite'] = False + data['wpsubdir'] = False + elif oldsitetype == 'wpsubdir': + data['static'] = False + data['wp'] = True + data['multisite'] = True + data['wpsubdir'] = True + elif oldsitetype == 'wpsubdomain': + data['static'] = False + data['wp'] = True + data['multisite'] = True + data['wpsubdir'] = False + + if oldcachetype == 'basic': + data['basic'] = True + data['wpfc'] = False + data['wpsc'] = False + data['wpredis'] = False + elif oldcachetype == 'wpfc': + data['basic'] = False + data['wpfc'] = True + data['wpsc'] = False + data['wpredis'] = False + elif oldcachetype == 'wpsc': + data['basic'] = False + data['wpfc'] = False + data['wpsc'] = True + data['wpredis'] = False + elif oldcachetype == 'wpredis': + data['basic'] = False + data['wpfc'] = False + data['wpsc'] = False + data['wpredis'] = True + + if pargs.hhvm != 'off': + data['hhvm'] = True + hhvm = True + elif pargs.hhvm == 'off': + data['hhvm'] = False + hhvm = False + + if pargs.php7 == 'on' : + data['php7'] = True + php7 = True + check_php_version= '7.0' + elif pargs.php7 == 'off': + data['php7'] = False + php7 = False + check_php_version = '5.6' + + if pargs.php7: + if php7 is old_php7: + if php7 is False: + Log.info(self, "PHP 7.0 is already disabled for given " + "site") + elif php7 is True: + Log.info(self, "PHP 7.0 is already enabled for given " + "site") + pargs.php7 = False + + #--letsencrypt=renew code goes here + if pargs.letsencrypt == "renew" and not pargs.all: + expiry_days = SSL.getExpirationDays(self,wo_domain) + min_expiry_days = 30 + if check_ssl: + if (expiry_days <= min_expiry_days): + renewLetsEncrypt(self,wo_domain) + else: + Log.error(self,"More than 30 days left for certificate Expiry. Not renewing now.") + + else: + Log.error(self,"Cannot RENEW ! SSL is not configured for given site .") + + if not WOService.reload_service(self, 'nginx'): + Log.error(self, "service nginx reload failed. " + "check issues with `nginx -t` command") + Log.info(self, "SUCCESS: Certificate was successfully renewed For" + " https://{0}".format(wo_domain)) + if (SSL.getExpirationDays(self,wo_domain)>0): + Log.info(self, "Your cert will expire within " + str(SSL.getExpirationDays(self,wo_domain)) + " days.") + Log.info(self, "Expiration DATE: " + str(SSL.getExpirationDate(self,wo_domain))) + + else: + Log.warn(self, "Your cert already EXPIRED !. PLEASE renew soon . ") + return 0 + + if pargs.all and pargs.letsencrypt == "renew": + + if check_ssl: + expiry_days = SSL.getExpirationDays(self,wo_domain,True) + if expiry_days < 0: + return 0 + min_expiry_days = 30 + if (expiry_days <= min_expiry_days): + renewLetsEncrypt(self,wo_domain) + if not WOService.reload_service(self, 'nginx'): + Log.error(self, "service nginx reload failed. " + "check issues with `nginx -t` command") + Log.info(self, "SUCCESS: Certificate was successfully renewed For" + " https://{0}".format(wo_domain)) + else: + Log.info(self,"More than 30 days left for certificate Expiry. Not renewing now.\n") + + if (SSL.getExpirationDays(self,wo_domain)>0): + Log.info(self, "Your cert will expire within " + str(SSL.getExpirationDays(self,wo_domain)) + " days.") + Log.info(self, "Expiration DATE: \n\n" + str(SSL.getExpirationDate(self,wo_domain))) + return 0 + #else: + # Log.warn(self, "Your cert already EXPIRED ! .PLEASE renew soon . ") + else: + Log.info(self,"SSL not configured for site http://{0}".format(wo_domain)) + return 0 + + if pargs.all and pargs.letsencrypt == "off": + if letsencrypt is check_ssl: + if letsencrypt is False: + Log.error(self, "SSl is not configured for given " + "site",False) + return 0 + pass + + if pargs.letsencrypt: + if pargs.letsencrypt == 'on': + data['letsencrypt'] = True + letsencrypt = True + elif pargs.letsencrypt == 'off': + data['letsencrypt'] = False + letsencrypt = False + + if letsencrypt is check_ssl: + if letsencrypt is False: + Log.error(self, "SSl is not configured for given " + "site") + elif letsencrypt is True: + Log.error(self, "SSl is already configured for given " + "site") + pargs.letsencrypt = False + + if pargs.hhvm: + if hhvm is old_hhvm: + if hhvm is False: + Log.info(self, "HHVM is allready disabled for given " + "site") + elif hhvm is True: + Log.info(self, "HHVM is allready enabled for given " + "site") + + pargs.hhvm = False + + if data and (not pargs.hhvm): + if old_hhvm is True: + data['hhvm'] = True + hhvm = True + else: + data['hhvm'] = False + hhvm = False + + if data and (not pargs.php7): + if old_php7 is True: + data['php7'] = True + php7 = True + else: + data['php7'] = False + php7 = False + + if pargs.hhvm=="on" or pargs.letsencrypt=="on" or pargs.php7=="on": + if pargs.php7 == "on": + if (not pargs.experimental): + Log.info(self, "PHP7.0 is experimental feature and it may not" + " work with all plugins of your site.\nYou can " + "disable it by passing --php7=off later.\nDo you wish" + " to enable PHP now for {0}?".format(wo_domain)) + + check_prompt = input("Type \"y\" to continue [n]:") + if check_prompt != "Y" and check_prompt != "y": + Log.info(self, "Not using PHP 7.0 for site") + data['php7'] = False + php7 = False + else: + data['php7'] = True + php7 = True + else: + data['php7'] = True + php7 = True + + if pargs.hhvm == "on": + if (not pargs.experimental): + Log.info(self, "HHVM is experimental feature and it may not" + " work with all plugins of your site.\nYou can " + "disable it by passing --hhvm=off later.\nDo you wish" + " to enable HHVM now for {0}?".format(wo_domain)) + + # Check prompt + check_prompt = input("Type \"y\" to continue [n]:") + if check_prompt != "Y" and check_prompt != "y": + Log.info(self, "Not using HHVM for site") + data['hhvm'] = False + hhvm = False + else: + data['hhvm'] = True + hhvm = True + else: + data['hhvm'] = True + hhvm = True + + if pargs.letsencrypt == "on": + + if (not pargs.experimental): + + if oldsitetype in ['wpsubdomain']: + Log.warn(self, "Wildcard domains are not supported in Lets Encrypt.\nWP SUBDOMAIN site will get SSL for primary site only.") + + Log.info(self, "Letsencrypt is currently in beta phase." + " \nDo you wish" + " to enable SSl now for {0}?".format(wo_domain)) + + check_prompt = input("Type \"y\" to continue [n]:") + if check_prompt != "Y" and check_prompt != "y": + Log.info(self, "Not using letsencrypt for site") + data['letsencrypt'] = False + letsencrypt = False + else: + data['letsencrypt'] = True + letsencrypt = True + else: + data['letsencrypt'] = True + letsencrypt = True + + + + if pargs.wpredis and data['currcachetype'] != 'wpredis': + if (not pargs.experimental): + Log.info(self, "Redis is experimental feature and it may not" + " work with all plugins of your site.\nYou can " + "disable it by changing cache type later.\nDo you wish" + " to enable Redis now for {0}?".format(wo_domain)) + + # Check prompt + check_prompt = input("Type \"y\" to continue [n]: ") + if check_prompt != "Y" and check_prompt != "y": + Log.error(self, "Not using Redis for site") + data['wpredis'] = False + data['basic'] = True + cache = 'basic' + + if ((hhvm is old_hhvm) and (php7 is old_php7) and + (stype == oldsitetype and cache == oldcachetype)): + return 1 + + if not data: + Log.error(self, "Cannot update {0}, Invalid Options" + .format(wo_domain)) + + wo_auth = site_package_check(self, stype) + data['wo_db_name'] = check_site.db_name + data['wo_db_user'] = check_site.db_user + data['wo_db_pass'] = check_site.db_password + data['wo_db_host'] = check_site.db_host + + if not pargs.letsencrypt: + try: + pre_run_checks(self) + except SiteError as e: + Log.debug(self, str(e)) + Log.error(self, "NGINX configuration check failed.") + + try: + sitebackup(self, data) + except Exception as e: + Log.debug(self, str(e)) + Log.info(self, Log.FAIL + "Check the log for details: " + "`tail /var/log/wo/wordops.log` and please try again") + return 1 + + # setup NGINX configuration, and webroot + try: + setupdomain(self, data) + except SiteError as e: + Log.debug(self, str(e)) + Log.info(self, Log.FAIL + "Update site failed." + "Check the log for details:" + "`tail /var/log/wo/wordops.log` and please try again") + return 1 + + if 'proxy' in data.keys() and data['proxy']: + updateSiteInfo(self, wo_domain, stype=stype, cache=cache, + hhvm=hhvm,ssl=True if check_site.is_ssl else False) + Log.info(self, "Successfully updated site" + " http://{0}".format(wo_domain)) + return 0 + + if pargs.letsencrypt: + if data['letsencrypt'] is True: + if not os.path.isfile("{0}/conf/nginx/ssl.conf.disabled" + .format(wo_site_webroot)): + setupLetsEncrypt(self, wo_domain) + + else: + WOFileUtils.mvfile(self, "{0}/conf/nginx/ssl.conf.disabled" + .format(wo_site_webroot), + '{0}/conf/nginx/ssl.conf' + .format(wo_site_webroot)) + + httpsRedirect(self,wo_domain) + Log.info(self,"Creating Cron Job for cert auto-renewal") + WOCron.setcron_weekly(self,'wo site update --le=renew --all 2> /dev/null'.format(wo_domain),'Renew all' + ' letsencrypt SSL cert. Set by WordOps') + + if not WOService.reload_service(self, 'nginx'): + Log.error(self, "service nginx reload failed. " + "check issues with `nginx -t` command") + + Log.info(self, "Congratulations! Successfully Configured SSl for Site " + " https://{0}".format(wo_domain)) + + if (SSL.getExpirationDays(self,wo_domain)>0): + Log.info(self, "Your cert will expire within " + str(SSL.getExpirationDays(self,wo_domain)) + " days.") + else: + Log.warn(self, "Your cert already EXPIRED ! .PLEASE renew soon . ") + + elif data['letsencrypt'] is False: + if os.path.isfile("{0}/conf/nginx/ssl.conf" + .format(wo_site_webroot)): + Log.info(self,'Setting Nginx configuration') + WOFileUtils.mvfile(self, "{0}/conf/nginx/ssl.conf" + .format(wo_site_webroot), + '{0}/conf/nginx/ssl.conf.disabled' + .format(wo_site_webroot)) + httpsRedirect(self,wo_domain,False) + if not WOService.reload_service(self, 'nginx'): + Log.error(self, "service nginx reload failed. " + "check issues with `nginx -t` command") + #Log.info(self,"Removing Cron Job set for cert auto-renewal") + #WOCron.remove_cron(self,'wo site update {0} --le=renew --min_expiry_limit 30 2> \/dev\/null'.format(wo_domain)) + Log.info(self, "Successfully Disabled SSl for Site " + " http://{0}".format(wo_domain)) + + + # Add nginx conf folder into GIT + WOGit.add(self, ["{0}/conf/nginx".format(wo_site_webroot)], + msg="Adding letsencrypts config of site: {0}" + .format(wo_domain)) + updateSiteInfo(self, wo_domain, ssl=letsencrypt) + return 0 + + if stype == oldsitetype and cache == oldcachetype: + + # Service Nginx Reload + if not WOService.reload_service(self, 'nginx'): + Log.error(self, "service nginx reload failed. " + "check issues with `nginx -t` command") + + updateSiteInfo(self, wo_domain, stype=stype, cache=cache, + hhvm=hhvm, ssl=True if check_site.is_ssl else False, php_version=check_php_version) + + Log.info(self, "Successfully updated site" + " http://{0}".format(wo_domain)) + return 0 + + #if data['wo_db_name'] and not data['wp']: + if 'wo_db_name' in data.keys() and not data['wp']: + try: + data = setupdatabase(self, data) + except SiteError as e: + Log.debug(self, str(e)) + Log.info(self, Log.FAIL + "Update site failed." + "Check the log for details:" + "`tail /var/log/wo/wordops.log` and please try again") + return 1 + try: + wodbconfig = open("{0}/wo-config.php".format(wo_site_webroot), + encoding='utf-8', mode='w') + wodbconfig.write("" + .format(data['wo_db_name'], + data['wo_db_user'], + data['wo_db_pass'], + data['wo_db_host'])) + wodbconfig.close() + except IOError as e: + Log.debug(self, str(e)) + Log.debug(self, "creating wo-config.php failed.") + Log.info(self, Log.FAIL + "Update site failed. " + "Check the log for details: " + "`tail /var/log/wo/wordops.log` and please try again") + return 1 + + # Setup WordPress if old sites are html/php/mysql sites + if data['wp'] and oldsitetype in ['html', 'proxy', 'php', 'mysql']: + try: + wo_wp_creds = setupwordpress(self, data) + except SiteError as e: + Log.debug(self, str(e)) + Log.info(self, Log.FAIL + "Update site failed." + "Check the log for details: " + "`tail /var/log/wo/wordops.log` and please try again") + return 1 + + # Uninstall unnecessary plugins + if oldsitetype in ['wp', 'wpsubdir', 'wpsubdomain']: + # Setup WordPress Network if update option is multisite + # and oldsite is WordPress single site + if data['multisite'] and oldsitetype == 'wp': + try: + setupwordpressnetwork(self, data) + except SiteError as e: + Log.debug(self, str(e)) + Log.info(self, Log.FAIL + "Update site failed. " + "Check the log for details:" + " `tail /var/log/wo/wordops.log` and please try again") + return 1 + + if ((oldcachetype in ['wpsc', 'basic', 'wpredis'] and + (data['wpfc'])) or (oldsitetype == 'wp' and data['multisite'] and data['wpfc'])): + try: + plugin_data = '{"log_level":"INFO","log_filesize":5,"enable_purge":1,"enable_map":0,"enable_log":0,"enable_stamp":0,"purge_homepage_on_new":1,"purge_homepage_on_edit":1,"purge_homepage_on_del":1,"purge_archive_on_new":1,"purge_archive_on_edit":0,"purge_archive_on_del":0,"purge_archive_on_new_comment":0,"purge_archive_on_deleted_comment":0,"purge_page_on_mod":1,"purge_page_on_new_comment":1,"purge_page_on_deleted_comment":1,"cache_method":"enable_fastcgi","purge_method":"get_request","redis_hostname":"127.0.0.1","redis_port":"6379","redis_prefix":"nginx-cache:"}' + setupwp_plugin(self, 'nginx-helper', 'rt_wp_nginx_helper_options', plugin_data, data) + except SiteError as e: + Log.debug(self, str(e)) + Log.info(self, Log.FAIL + "Update nginx-helper settings failed. " + "Check the log for details:" + " `tail /var/log/wo/wordops.log` and please try again") + return 1 + + elif ((oldcachetype in ['wpsc', 'basic', 'wpfc'] and + (data['wpredis'])) or (oldsitetype == 'wp' and data['multisite'] and data['wpredis'])): + try: + plugin_data = '{"log_level":"INFO","log_filesize":5,"enable_purge":1,"enable_map":0,"enable_log":0,"enable_stamp":0,"purge_homepage_on_new":1,"purge_homepage_on_edit":1,"purge_homepage_on_del":1,"purge_archive_on_new":1,"purge_archive_on_edit":0,"purge_archive_on_del":0,"purge_archive_on_new_comment":0,"purge_archive_on_deleted_comment":0,"purge_page_on_mod":1,"purge_page_on_new_comment":1,"purge_page_on_deleted_comment":1,"cache_method":"enable_redis","purge_method":"get_request","redis_hostname":"127.0.0.1","redis_port":"6379","redis_prefix":"nginx-cache:"}' + setupwp_plugin(self, 'nginx-helper', 'rt_wp_nginx_helper_options', plugin_data, data) + except SiteError as e: + Log.debug(self, str(e)) + Log.info(self, Log.FAIL + "Update nginx-helper settings failed. " + "Check the log for details:" + " `tail /var/log/wo/wordops.log` and please try again") + return 1 + else: + try: + plugin_data = '{"log_level":"INFO","log_filesize":5,"enable_purge":0,"enable_map":0,"enable_log":0,"enable_stamp":0,"purge_homepage_on_new":1,"purge_homepage_on_edit":1,"purge_homepage_on_del":1,"purge_archive_on_new":1,"purge_archive_on_edit":0,"purge_archive_on_del":0,"purge_archive_on_new_comment":0,"purge_archive_on_deleted_comment":0,"purge_page_on_mod":1,"purge_page_on_new_comment":1,"purge_page_on_deleted_comment":1,"cache_method":"enable_redis","purge_method":"get_request","redis_hostname":"127.0.0.1","redis_port":"6379","redis_prefix":"nginx-cache:"}' + setupwp_plugin(self, 'nginx-helper', 'rt_wp_nginx_helper_options', plugin_data, data) + except SiteError as e: + Log.debug(self, str(e)) + Log.info(self, Log.FAIL + "Update nginx-helper settings failed. " + "Check the log for details:" + " `tail /var/log/wo/wordops.log` and please try again") + return 1 + + if oldcachetype == 'wpsc' and not data['wpsc']: + try: + uninstallwp_plugin(self, 'wp-super-cache', data) + except SiteError as e: + Log.debug(self, str(e)) + Log.info(self, Log.FAIL + "Update site failed." + "Check the log for details:" + " `tail /var/log/wo/wordops.log` and please try again") + return 1 + + if oldcachetype == 'wpredis' and not data['wpredis']: + try: + uninstallwp_plugin(self, 'redis-cache', data) + except SiteError as e: + Log.debug(self, str(e)) + Log.info(self, Log.FAIL + "Update site failed." + "Check the log for details:" + " `tail /var/log/wo/wordops.log` and please try again") + return 1 + + if oldcachetype != 'wpsc' and data['wpsc']: + try: + installwp_plugin(self, 'wp-super-cache', data) + except SiteError as e: + Log.debug(self, str(e)) + Log.info(self, Log.FAIL + "Update site failed." + "Check the log for details: " + "`tail /var/log/wo/wordops.log` and please try again") + return 1 + + if oldcachetype != 'wpredis' and data['wpredis']: + try: + if installwp_plugin(self, 'redis-cache', data): + #search for wp-config.php + if WOFileUtils.isexist(self,"{0}/wp-config.php".format(wo_site_webroot)): + config_path = '{0}/wp-config.php'.format(wo_site_webroot) + elif WOFileUtils.isexist(self,"{0}/htdocs/wp-config.php".format(wo_site_webroot)): + config_path = '{0}/htdocs/wp-config.php'.format(wo_site_webroot) + else: + Log.debug(self, "Updating wp-config.php failed. File could not be located.") + Log.error(self,"wp-config.php could not be located !!") + raise SiteError + + if WOShellExec.cmd_exec(self, "grep -q \"WP_CACHE_KEY_SALT\" {0}" + .format(config_path)): + pass + else: + try: + wpconfig = open("{0}".format(config_path), + encoding='utf-8', mode='a') + wpconfig.write("\n\ndefine( \'WP_CACHE_KEY_SALT\', \'{0}:\' );" + .format(wo_domain)) + wpconfig.close() + except IOError as e: + Log.debug(self, str(e)) + Log.debug(self, "Updating wp-config.php failed.") + Log.warn(self, "Updating wp-config.php failed. " + "Could not append:" + "\ndefine( \'WP_CACHE_KEY_SALT\', \'{0}:\' );".format(wo_domain) + + "\nPlease add manually") + except SiteError as e: + Log.debug(self, str(e)) + Log.info(self, Log.FAIL + "Update site failed." + "Check the log for details: " + "`tail /var/log/wo/wordops.log` and please try again") + return 1 + + # Service Nginx Reload + if not WOService.reload_service(self, 'nginx'): + Log.error(self, "service nginx reload failed. " + "check issues with `nginx -t` command") + + WOGit.add(self, ["/etc/nginx"], + msg="{0} updated with {1} {2}" + .format(wo_www_domain, stype, cache)) + # Setup Permissions for webroot + try: + setwebrootpermissions(self, data['webroot']) + except SiteError as e: + Log.debug(self, str(e)) + Log.info(self, Log.FAIL + "Update site failed." + "Check the log for details: " + "`tail /var/log/wo/wordops.log` and please try again") + return 1 + + if wo_auth and len(wo_auth): + for msg in wo_auth: + Log.info(self, Log.ENDC + msg) + + display_cache_settings(self, data) + if data['wp'] and oldsitetype in ['html', 'php', 'mysql']: + Log.info(self, "\n\n" + Log.ENDC + "WordPress admin user :" + " {0}".format(wo_wp_creds['wp_user'])) + Log.info(self, Log.ENDC + "WordPress admin password : {0}" + .format(wo_wp_creds['wp_pass']) + "\n\n") + if oldsitetype in ['html', 'php'] and stype != 'php': + updateSiteInfo(self, wo_domain, stype=stype, cache=cache, + db_name=data['wo_db_name'], + db_user=data['wo_db_user'], + db_password=data['wo_db_pass'], + db_host=data['wo_db_host'], hhvm=hhvm, + ssl=True if check_site.is_ssl else False,php_version=check_php_version) + else: + updateSiteInfo(self, wo_domain, stype=stype, cache=cache, + hhvm=hhvm, ssl=True if check_site.is_ssl else False,php_version=check_php_version) + Log.info(self, "Successfully updated site" + " http://{0}".format(wo_domain)) + return 0 + + +class WOSiteDeleteController(CementBaseController): + class Meta: + label = 'delete' + stacked_on = 'site' + stacked_type = 'nested' + description = 'delete an existing website' + arguments = [ + (['site_name'], + dict(help='domain name to be deleted', nargs='?')), + (['--no-prompt'], + dict(help="doesnt ask permission for delete", + action='store_true')), + (['-f','--force'], + dict(help="forcefully delete site and configuration", + action='store_true')), + (['--all'], + dict(help="delete all", action='store_true')), + (['--db'], + dict(help="delete db only", action='store_true')), + (['--files'], + dict(help="delete webroot only", action='store_true')), + ] + + @expose(help="Delete website configuration and files") + @expose(hide=True) + def default(self): + if not self.app.pargs.site_name: + try: + while not self.app.pargs.site_name: + self.app.pargs.site_name = (input('Enter site name : ') + .strip()) + except IOError as e: + Log.error(self, 'could not input site name') + + self.app.pargs.site_name = self.app.pargs.site_name.strip() + (wo_domain, wo_www_domain) = ValidateDomain(self.app.pargs.site_name) + wo_db_name = '' + wo_prompt = '' + wo_nginx_prompt = '' + mark_db_delete_prompt = False + mark_webroot_delete_prompt = False + mark_db_deleted = False + mark_webroot_deleted = False + if not check_domain_exists(self, wo_domain): + Log.error(self, "site {0} does not exist".format(wo_domain)) + + if ((not self.app.pargs.db) and (not self.app.pargs.files) and + (not self.app.pargs.all)): + self.app.pargs.all = True + + # Gather information from wo-db for wo_domain + check_site = getSiteInfo(self, wo_domain) + wo_site_type = check_site.site_type + wo_site_webroot = check_site.site_path + if wo_site_webroot == 'deleted': + mark_webroot_deleted = True + if wo_site_type in ['mysql', 'wp', 'wpsubdir', 'wpsubdomain']: + wo_db_name = check_site.db_name + wo_db_user = check_site.db_user + wo_mysql_grant_host = self.app.config.get('mysql', 'grant-host') + if wo_db_name == 'deleted': + mark_db_deleted = True + if self.app.pargs.all: + self.app.pargs.db = True + self.app.pargs.files = True + else: + if self.app.pargs.all: + mark_db_deleted = True + self.app.pargs.files = True + + # Delete website database + if self.app.pargs.db: + if wo_db_name != 'deleted' and wo_db_name != '': + if not self.app.pargs.no_prompt: + wo_db_prompt = input('Are you sure, you want to delete' + ' database [y/N]: ') + else: + wo_db_prompt = 'Y' + mark_db_delete_prompt = True + + if wo_db_prompt == 'Y' or wo_db_prompt == 'y': + mark_db_delete_prompt = True + Log.info(self, "Deleting Database, {0}, user {1}" + .format(wo_db_name, wo_db_user)) + deleteDB(self, wo_db_name, wo_db_user, wo_mysql_grant_host, False) + updateSiteInfo(self, wo_domain, + db_name='deleted', + db_user='deleted', + db_password='deleted') + mark_db_deleted = True + Log.info(self, "Deleted Database successfully.") + else: + mark_db_deleted = True + Log.info(self, "Does not seems to have database for this site." + ) + + # Delete webroot + if self.app.pargs.files: + if wo_site_webroot != 'deleted': + if not self.app.pargs.no_prompt: + wo_web_prompt = input('Are you sure, you want to delete ' + 'webroot [y/N]: ') + else: + wo_web_prompt = 'Y' + mark_webroot_delete_prompt = True + + if wo_web_prompt == 'Y' or wo_web_prompt == 'y': + mark_webroot_delete_prompt = True + Log.info(self, "Deleting Webroot, {0}" + .format(wo_site_webroot)) + deleteWebRoot(self, wo_site_webroot) + updateSiteInfo(self, wo_domain, webroot='deleted') + mark_webroot_deleted = True + Log.info(self, "Deleted webroot successfully") + else: + mark_webroot_deleted = True + Log.info(self, "Webroot seems to be already deleted") + + if not self.app.pargs.force: + if (mark_webroot_deleted and mark_db_deleted): + # TODO Delete nginx conf + removeNginxConf(self, wo_domain) + deleteSiteInfo(self, wo_domain) + Log.info(self, "Deleted site {0}".format(wo_domain)) + # else: + # Log.error(self, " site {0} does not exists".format(wo_domain)) + else: + if (mark_db_delete_prompt or mark_webroot_delete_prompt or (mark_webroot_deleted and mark_db_deleted)): + # TODO Delete nginx conf + removeNginxConf(self, wo_domain) + deleteSiteInfo(self, wo_domain) + Log.info(self, "Deleted site {0}".format(wo_domain)) + + +class WOSiteListController(CementBaseController): + class Meta: + label = 'list' + stacked_on = 'site' + stacked_type = 'nested' + description = 'List websites' + arguments = [ + (['--enabled'], + dict(help='List enabled websites', action='store_true')), + (['--disabled'], + dict(help="List disabled websites", action='store_true')), + ] + + @expose(help="Lists websites") + def default(self): + sites = getAllsites(self) + if not sites: + pass + + if self.app.pargs.enabled: + for site in sites: + if site.is_enabled: + Log.info(self, "{0}".format(site.sitename)) + elif self.app.pargs.disabled: + for site in sites: + if not site.is_enabled: + Log.info(self, "{0}".format(site.sitename)) + else: + for site in sites: + Log.info(self, "{0}".format(site.sitename)) + + +def load(app): + # register the plugin class.. this only happens if the plugin is enabled + handler.register(WOSiteController) + handler.register(WOSiteCreateController) + handler.register(WOSiteUpdateController) + handler.register(WOSiteDeleteController) + handler.register(WOSiteListController) + handler.register(WOSiteEditController) + # register a hook (function) to run after arguments are parsed. + hook.register('post_argument_parsing', wo_site_hook) diff --git a/wo/cli/plugins/site_functions.py b/wo/cli/plugins/site_functions.py new file mode 100644 index 0000000..361918c --- /dev/null +++ b/wo/cli/plugins/site_functions.py @@ -0,0 +1,1439 @@ +from wo.cli.plugins.stack import WOStackController +from wo.core.fileutils import WOFileUtils +from wo.core.mysql import * +from wo.core.shellexec import * +from wo.core.sslutils import SSL +from wo.core.variables import WOVariables +from wo.cli.plugins.sitedb import * +from wo.core.aptget import WOAptGet +from wo.core.git import WOGit +from wo.core.logging import Log +from wo.core.sendmail import WOSendMail +from wo.core.services import WOService +import subprocess +from subprocess import CalledProcessError +import os +import random +import string +import sys +import getpass +import glob +import re +import platform + + +class SiteError(Exception): + """Custom Exception Occured when setting up site""" + def __init__(self, message): + self.message = message + + def __str__(self): + return repr(self.message) + + +def pre_run_checks(self): + + # Check nginx configuration + Log.info(self, "Running pre-update checks, please wait...") + try: + Log.debug(self, "checking NGINX configuration ...") + FNULL = open('/dev/null', 'w') + ret = subprocess.check_call(["nginx", "-t"], stdout=FNULL, + stderr=subprocess.STDOUT) + except CalledProcessError as e: + Log.debug(self, "{0}".format(str(e))) + raise SiteError("nginx configuration check failed.") + + +def check_domain_exists(self, domain): + if getSiteInfo(self, domain): + return True + else: + return False + + +def setupdomain(self, data): + + #for debug purpose + # for key, value in data.items() : + # print (key, value) + + wo_domain_name = data['site_name'] + wo_site_webroot = data['webroot'] if 'webroot' in data.keys() else '' + + # Check if nginx configuration already exists + # if os.path.isfile('/etc/nginx/sites-available/{0}' + # .format(wo_domain_name)): + # raise SiteError("nginx configuration already exists for site") + + Log.info(self, "Setting up NGINX configuration \t", end='') + # write nginx config for file + try: + wo_site_nginx_conf = open('/etc/nginx/sites-available/{0}' + .format(wo_domain_name), encoding='utf-8', + mode='w') + if not data['php7']: + self.app.render((data), 'virtualconf.mustache', + out=wo_site_nginx_conf) + else: + self.app.render((data), 'virtualconf-php7.mustache', + out=wo_site_nginx_conf) + wo_site_nginx_conf.close() + except IOError as e: + Log.debug(self, "{0}".format(e)) + raise SiteError("create nginx configuration failed for site") + except Exception as e: + Log.debug(self, "{0}".format(e)) + raise SiteError("create nginx configuration failed for site") + finally: + # Check nginx -t and return status over it + try: + Log.debug(self, "Checking generated nginx conf, please wait...") + FNULL = open('/dev/null', 'w') + ret = subprocess.check_call(["nginx", "-t"], stdout=FNULL, + stderr=subprocess.STDOUT) + Log.info(self, "[" + Log.ENDC + "Done" + Log.OKBLUE + "]") + except CalledProcessError as e: + Log.debug(self, "{0}".format(str(e))) + Log.info(self, "[" + Log.ENDC + Log.FAIL + "Fail" + + Log.OKBLUE + "]") + raise SiteError("created nginx configuration failed for site." + " check with `nginx -t`") + + + # create symbolic link for + WOFileUtils.create_symlink(self, ['/etc/nginx/sites-available/{0}' + .format(wo_domain_name), + '/etc/nginx/sites-enabled/{0}' + .format(wo_domain_name)]) + + if 'proxy' in data.keys() and data['proxy']: + return + + # Creating htdocs & logs directory + Log.info(self, "Setting up webroot \t\t", end='') + try: + if not os.path.exists('{0}/htdocs'.format(wo_site_webroot)): + os.makedirs('{0}/htdocs'.format(wo_site_webroot)) + if not os.path.exists('{0}/logs'.format(wo_site_webroot)): + os.makedirs('{0}/logs'.format(wo_site_webroot)) + if not os.path.exists('{0}/conf/nginx'.format(wo_site_webroot)): + os.makedirs('{0}/conf/nginx'.format(wo_site_webroot)) + + WOFileUtils.create_symlink(self, ['/var/log/nginx/{0}.access.log' + .format(wo_domain_name), + '{0}/logs/access.log' + .format(wo_site_webroot)]) + WOFileUtils.create_symlink(self, ['/var/log/nginx/{0}.error.log' + .format(wo_domain_name), + '{0}/logs/error.log' + .format(wo_site_webroot)]) + except Exception as e: + Log.debug(self, "{0}".format(e)) + raise SiteError("setup webroot failed for site") + finally: + # TODO Check if directories are setup + if (os.path.exists('{0}/htdocs'.format(wo_site_webroot)) and + os.path.exists('{0}/logs'.format(wo_site_webroot))): + Log.info(self, "[" + Log.ENDC + "Done" + Log.OKBLUE + "]") + else: + Log.info(self, "[" + Log.ENDC + "Fail" + Log.OKBLUE + "]") + raise SiteError("setup webroot failed for site") + + +def setupdatabase(self, data): + wo_domain_name = data['site_name'] + wo_random = (''.join(random.sample(string.ascii_uppercase + + string.ascii_lowercase + string.digits, 15))) + wo_replace_dot = wo_domain_name.replace('.', '_') + prompt_dbname = self.app.config.get('mysql', 'db-name') + prompt_dbuser = self.app.config.get('mysql', 'db-user') + wo_mysql_grant_host = self.app.config.get('mysql', 'grant-host') + wo_db_name = '' + wo_db_username = '' + wo_db_password = '' + + if prompt_dbname == 'True' or prompt_dbname == 'true': + try: + wo_db_name = input('Enter the MySQL database name [{0}]: ' + .format(wo_replace_dot)) + except EOFError as e: + Log.debug(self, "{0}".format(e)) + raise SiteError("Unable to input database name") + + if not wo_db_name: + wo_db_name = wo_replace_dot + + if prompt_dbuser == 'True' or prompt_dbuser == 'true': + try: + wo_db_username = input('Enter the MySQL database user name [{0}]: ' + .format(wo_replace_dot)) + wo_db_password = getpass.getpass(prompt='Enter the MySQL database' + ' password [{0}]: ' + .format(wo_random)) + except EOFError as e: + Log.debug(self, "{0}".format(e)) + raise SiteError("Unable to input database credentials") + + if not wo_db_username: + wo_db_username = wo_replace_dot + if not wo_db_password: + wo_db_password = wo_random + + if len(wo_db_username) > 16: + Log.debug(self, 'Autofix MySQL username (ERROR 1470 (HY000)),' + ' please wait') + wo_db_username = (wo_db_name[0:6] + generate_random()) + + # create MySQL database + Log.info(self, "Setting up database\t\t", end='') + Log.debug(self, "Creating database {0}".format(wo_db_name)) + try: + if WOMysql.check_db_exists(self, wo_db_name): + Log.debug(self, "Database already exists, Updating DB_NAME .. ") + wo_db_name = (wo_db_name[0:6] + generate_random()) + wo_db_username = (wo_db_name[0:6] + generate_random()) + except MySQLConnectionError as e: + raise SiteError("MySQL Connectivity problem occured") + + try: + WOMysql.execute(self, "create database `{0}`" + .format(wo_db_name)) + except StatementExcecutionError as e: + Log.info(self, "[" + Log.ENDC + Log.FAIL + "Failed" + Log.OKBLUE + "]") + raise SiteError("create database execution failed") + # Create MySQL User + Log.debug(self, "Creating user {0}".format(wo_db_username)) + Log.debug(self, "create user `{0}`@`{1}` identified by ''" + .format(wo_db_username, wo_mysql_grant_host)) + try: + WOMysql.execute(self, + "create user `{0}`@`{1}` identified by '{2}'" + .format(wo_db_username, wo_mysql_grant_host, + wo_db_password), log=False) + except StatementExcecutionError as e: + Log.info(self, "[" + Log.ENDC + Log.FAIL + "Failed" + Log.OKBLUE + "]") + raise SiteError("creating user failed for database") + + # Grant permission + Log.debug(self, "Setting up user privileges") + try: + WOMysql.execute(self, + "grant all privileges on `{0}`.* to `{1}`@`{2}`" + .format(wo_db_name, + wo_db_username, wo_mysql_grant_host)) + except StatementExcecutionError as e: + Log.info(self, "[" + Log.ENDC + Log.FAIL + "Failed" + Log.OKBLUE + "]") + SiteError("grant privileges to user failed for database ") + + Log.info(self, "[" + Log.ENDC + "Done" + Log.OKBLUE + "]") + + data['wo_db_name'] = wo_db_name + data['wo_db_user'] = wo_db_username + data['wo_db_pass'] = wo_db_password + data['wo_db_host'] = WOVariables.wo_mysql_host + data['wo_mysql_grant_host'] = wo_mysql_grant_host + return(data) + + +def setupwordpress(self, data): + wo_domain_name = data['site_name'] + wo_site_webroot = data['webroot'] + prompt_wpprefix = self.app.config.get('wordpress', 'prefix') + wo_wp_user = self.app.config.get('wordpress', 'user') + wo_wp_pass = self.app.config.get('wordpress', 'password') + wo_wp_email = self.app.config.get('wordpress', 'email') + # Random characters + wo_random = (''.join(random.sample(string.ascii_uppercase + + string.ascii_lowercase + string.digits, 15))) + wo_wp_prefix = '' + # wo_wp_user = '' + # wo_wp_pass = '' + + if 'wp-user' in data.keys() and data['wp-user']: + wo_wp_user = data['wp-user'] + if 'wp-email' in data.keys() and data['wp-email']: + wo_wp_email = data['wp-email'] + if 'wp-pass' in data.keys() and data['wp-pass']: + wo_wp_pass = data['wp-pass'] + + Log.info(self, "Downloading WordPress \t\t", end='') + WOFileUtils.chdir(self, '{0}/htdocs/'.format(wo_site_webroot)) + try: + if WOShellExec.cmd_exec(self, "wp --allow-root core" + " download"): + pass + else: + Log.info(self, "[" + Log.ENDC + Log.FAIL + "Fail" + Log.OKBLUE + "]") + raise SiteError("download WordPress core failed") + except CommandExecutionError as e: + Log.info(self, "[" + Log.ENDC + Log.FAIL + "Fail" + Log.OKBLUE + "]") + raise SiteError(self, "download WordPress core failed") + + Log.info(self, "[" + Log.ENDC + "Done" + Log.OKBLUE + "]") + + if not (data['wo_db_name'] and data['wo_db_user'] and data['wo_db_pass']): + data = setupdatabase(self, data) + if prompt_wpprefix == 'True' or prompt_wpprefix == 'true': + try: + wo_wp_prefix = input('Enter the WordPress table prefix [wp_]: ') + while not re.match('^[A-Za-z0-9_]*$', wo_wp_prefix): + Log.warn(self, "table prefix can only " + "contain numbers, letters, and underscores") + wo_wp_prefix = input('Enter the WordPress table prefix [wp_]: ' + ) + except EOFError as e: + Log.debug(self, "{0}".format(e)) + raise SiteError("input table prefix failed") + + if not wo_wp_prefix: + wo_wp_prefix = 'wp_' + + # Modify wp-config.php & move outside the webroot + + WOFileUtils.chdir(self, '{0}/htdocs/'.format(wo_site_webroot)) + Log.debug(self, "Setting up wp-config file") + if not data['multisite']: + Log.debug(self, "Generating wp-config for WordPress Single site") + Log.debug(self, "bash -c \"php {0} --allow-root " + .format(WOVariables.wo_wpcli_path) + + "core config " + + "--dbname=\'{0}\' --dbprefix=\'{1}\' --dbuser=\'{2}\' " + "--dbhost=\'{3}\' " + .format(data['wo_db_name'], wo_wp_prefix, + data['wo_db_user'], data['wo_db_host']) + + "--dbpass= " + "--extra-php< {1}/{0}.sql" + .format(data['wo_db_name'], + backup_path)): + Log.info(self, + "[" + Log.ENDC + Log.FAIL + "Fail" + Log.OKBLUE + "]") + raise SiteError("mysqldump failed to backup database") + except CommandExecutionError as e: + Log.info(self, "[" + Log.ENDC + "Fail" + Log.OKBLUE + "]") + raise SiteError("mysqldump failed to backup database") + Log.info(self, "[" + Log.ENDC + "Done" + Log.OKBLUE + "]") + # move wp-config.php/wo-config.php to backup + if data['currsitetype'] in ['mysql', 'proxy']: + if data['php7'] is True and not data['wp']: + WOFileUtils.copyfile(self, configfiles[0], backup_path) + else: + WOFileUtils.mvfile(self, configfiles[0], backup_path) + else: + WOFileUtils.copyfile(self, configfiles[0], backup_path) + + +def site_package_check(self, stype): + apt_packages = [] + packages = [] + stack = WOStackController() + stack.app = self.app + if stype in ['html', 'proxy', 'php', 'mysql', 'wp', 'wpsubdir', + 'wpsubdomain', 'php7']: + Log.debug(self, "Setting apt_packages variable for Nginx") + + # Check if server has nginx-custom package + if not (WOAptGet.is_installed(self, 'nginx-custom') or WOAptGet.is_installed(self, 'nginx-mainline')): + # check if Server has nginx-plus installed + if WOAptGet.is_installed(self, 'nginx-plus'): + # do something + # do post nginx installation configuration + Log.info(self, "NGINX PLUS Detected ...") + apt = ["nginx-plus"] + WOVariables.wo_nginx + #apt_packages = apt_packages + WOVariables.wo_nginx + stack.post_pref(apt, packages) + elif WOAptGet.is_installed(self, 'nginx'): + Log.info(self, "WordOps detected a previously installed Nginx package. " + "It may or may not have required modules. " + "\nIf you need help, please create an issue at https://github.com/WordOps/WordOps/issues/ \n") + apt = ["nginx"] + WOVariables.wo_nginx + #apt_packages = apt_packages + WOVariables.wo_nginx + stack.post_pref(apt, packages) + else: + apt_packages = apt_packages + WOVariables.wo_nginx + else: + # Fix for Nginx white screen death + if not WOFileUtils.grep(self, '/etc/nginx/fastcgi_params', + 'SCRIPT_FILENAME'): + with open('/etc/nginx/fastcgi_params', encoding='utf-8', + mode='a') as wo_nginx: + wo_nginx.write('fastcgi_param \tSCRIPT_FILENAME ' + '\t$request_filename;\n') + + if self.app.pargs.php and self.app.pargs.php7: + Log.error(self,"Error: two different PHP versions cannot be combined within the same WordOps site") + + if not self.app.pargs.php7 and stype in ['php', 'mysql', 'wp', 'wpsubdir', 'wpsubdomain']: + Log.debug(self, "Setting apt_packages variable for PHP") + if (WOVariables.wo_platform_codename == 'trusty' or WOVariables.wo_platform_codename == 'xenial' or WOVariables.wo_platform_codename == 'bionic'): + if not WOAptGet.is_installed(self, 'php7.2-fpm'): + apt_packages = apt_packages + WOVariables.wo_php7_2 + WOVariables.wo_php_extra + else: + if not WOAptGet.is_installed(self, 'php5-fpm'): + apt_packages = apt_packages + WOVariables.wo_php + + if self.app.pargs.php7 and stype in [ 'mysql', 'wp', 'wpsubdir', 'wpsubdomain']: + if (WOVariables.wo_platform_codename == 'trusty' or WOVariables.wo_platform_codename == 'xenial' or WOVariables.wo_platform_codename == 'bionic'): + Log.debug(self, "Setting apt_packages variable for PHP 5.6") + if not WOAptGet.is_installed(self, 'php5.6-fpm'): + apt_packages = apt_packages + WOVariables.wo_php5_6 + Log.debug(self, "Setting apt_packages variable for PHP 7.0") + if not WOAptGet.is_installed(self, 'php7.0-fpm'): + apt_packages = apt_packages + WOVariables.wo_php7_0 + WOVariables.wo_php_extra + else: + if WOVariables.wo_platform_codename == 'wheezy': + Log.warn(self, "PHP 7.0 not available for your system.") + Log.info(self,"Creating site with PHP 5.6") + if not WOAptGet.is_installed(self, 'php5-fpm'): + Log.info(self, "Setting apt_packages variable for PHP") + Log.debug(self, "Setting apt_packages variable for PHP") + apt_packages = apt_packages + WOVariables.wo_php + else: + Log.debug(self, "Setting apt_packages variable for PHP 7.0") + if not WOAptGet.is_installed(self, 'php7.0-fpm'): + apt_packages = apt_packages + WOVariables.wo_php7_0 + + if stype in ['mysql', 'wp', 'wpsubdir', 'wpsubdomain']: + Log.debug(self, "Setting apt_packages variable for MySQL") + if not WOShellExec.cmd_exec(self, "mysqladmin ping"): + apt_packages = apt_packages + WOVariables.wo_mysql + packages = packages + [["https://raw.githubusercontent.com/" + "major/MySQLTuner-perl/master/" + "mysqltuner.pl", "/usr/bin/mysqltuner", + "MySQLTuner"]] + + if stype in ['wp', 'wpsubdir', 'wpsubdomain']: + Log.debug(self, "Setting packages variable for WP-CLI") + if not WOShellExec.cmd_exec(self, "which wp"): + packages = packages + [["https://github.com/wp-cli/wp-cli/" + "releases/download/v{0}/" + "wp-cli-{0}.phar" + .format(WOVariables.wo_wp_cli), + "/usr/bin/wp", "WP-CLI"]] + if self.app.pargs.wpredis: + Log.debug(self, "Setting apt_packages variable for redis") + if not WOAptGet.is_installed(self, 'redis-server'): + apt_packages = apt_packages + WOVariables.wo_redis + + if os.path.isfile("/etc/nginx/nginx.conf") and (not + os.path.isfile("/etc/nginx/common/redis.conf")): + + data = dict() + Log.debug(self, 'Writting the nginx configuration to ' + 'file /etc/nginx/common/redis.conf') + wo_nginx = open('/etc/nginx/common/redis.conf', + encoding='utf-8', mode='w') + self.app.render((data), 'redis.mustache', + out=wo_nginx) + wo_nginx.close() + + if os.path.isfile("/etc/nginx/nginx.conf") and (not + os.path.isfile("/etc/nginx/common/redis-hhvm.conf")): + + data = dict() + Log.debug(self, 'Writting the nginx configuration to ' + 'file /etc/nginx/common/redis-hhvm.conf') + wo_nginx = open('/etc/nginx/common/redis-hhvm.conf', + encoding='utf-8', mode='w') + self.app.render((data), 'redis-hhvm.mustache', + out=wo_nginx) + wo_nginx.close() + + if os.path.isfile("/etc/nginx/conf.d/upstream.conf"): + if not WOFileUtils.grep(self, "/etc/nginx/conf.d/" + "upstream.conf", + "redis"): + with open("/etc/nginx/conf.d/upstream.conf", + "a") as redis_file: + redis_file.write("upstream redis {\n" + " server 127.0.0.1:6379;\n" + " keepalive 10;\n}") + + if os.path.isfile("/etc/nginx/nginx.conf") and (not + os.path.isfile("/etc/nginx/conf.d/redis.conf")): + with open("/etc/nginx/conf.d/redis.conf", "a") as redis_file: + redis_file.write("# Log format Settings\n" + "log_format rt_cache_redis '$remote_addr $upstream_response_time $srcache_fetch_status [$time_local] '\n" + "'$http_host \"$request\" $status $body_bytes_sent '\n" + "'\"$http_referer\" \"$http_user_agent\"';\n") + + if self.app.pargs.hhvm: + if platform.architecture()[0] is '32bit': + Log.error(self, "HHVM is not supported by 32bit system") + Log.debug(self, "Setting apt_packages variable for HHVM") + if not WOAptGet.is_installed(self, 'hhvm'): + apt_packages = apt_packages + WOVariables.wo_hhvm + + if os.path.isdir("/etc/nginx/common") and (not + os.path.isfile("/etc/nginx/common/php-hhvm.conf")): + data = dict() + Log.debug(self, 'Writting the nginx configuration to ' + 'file /etc/nginx/common/php-hhvm.conf') + wo_nginx = open('/etc/nginx/common/php-hhvm.conf', + encoding='utf-8', mode='w') + self.app.render((data), 'php-hhvm.mustache', + out=wo_nginx) + wo_nginx.close() + + Log.debug(self, 'Writting the nginx configuration to ' + 'file /etc/nginx/common/wpfc-hhvm.conf') + wo_nginx = open('/etc/nginx/common/wpfc-hhvm.conf', + encoding='utf-8', mode='w') + self.app.render((data), 'wpfc-hhvm.mustache', + out=wo_nginx) + wo_nginx.close() + + Log.debug(self, 'Writting the nginx configuration to ' + 'file /etc/nginx/common/wpsc-hhvm.conf') + wo_nginx = open('/etc/nginx/common/wpsc-hhvm.conf', + encoding='utf-8', mode='w') + self.app.render((data), 'wpsc-hhvm.mustache', + out=wo_nginx) + wo_nginx.close() + + if os.path.isfile("/etc/nginx/conf.d/upstream.conf"): + if not WOFileUtils.grep(self, "/etc/nginx/conf.d/upstream.conf", + "hhvm"): + with open("/etc/nginx/conf.d/upstream.conf", "a") as hhvm_file: + hhvm_file.write("upstream hhvm {\nserver 127.0.0.1:8000;\n" + "server 127.0.0.1:9000 backup;\n}\n") + + if self.app.pargs.php7: + if (WOVariables.wo_platform_codename == 'wheezy' or WOVariables.wo_platform_codename == 'precise'): + Log.error(self,"PHP 7.0 is not supported in your Platform") + + Log.debug(self, "Setting apt_packages variable for PHP 7.0") + if not WOAptGet.is_installed(self, 'php7.0-fpm'): + apt_packages = apt_packages + WOVariables.wo_php7_0 + WOVariables.wo_php_extra + + if os.path.isdir("/etc/nginx/common") and (not + os.path.isfile("/etc/nginx/common/php7.conf")): + data = dict() + Log.debug(self, 'Writting the nginx configuration to ' + 'file /etc/nginx/common/locations-php7.conf') + wo_nginx = open('/etc/nginx/common/locations-php7.conf', + encoding='utf-8', mode='w') + self.app.render((data), 'locations-php7.mustache', + out=wo_nginx) + wo_nginx.close() + + Log.debug(self, 'Writting the nginx configuration to ' + 'file /etc/nginx/common/php7.conf') + wo_nginx = open('/etc/nginx/common/php7.conf', + encoding='utf-8', mode='w') + self.app.render((data), 'php7.mustache', + out=wo_nginx) + wo_nginx.close() + + Log.debug(self, 'Writting the nginx configuration to ' + 'file /etc/nginx/common/wpcommon-php7.conf') + wo_nginx = open('/etc/nginx/common/wpcommon-php7.conf', + encoding='utf-8', mode='w') + self.app.render((data), 'wpcommon-php7.mustache', + out=wo_nginx) + wo_nginx.close() + + Log.debug(self, 'Writting the nginx configuration to ' + 'file /etc/nginx/common/wpfc-php7.conf') + wo_nginx = open('/etc/nginx/common/wpfc-php7.conf', + encoding='utf-8', mode='w') + self.app.render((data), 'wpfc-php7.mustache', + out=wo_nginx) + wo_nginx.close() + + Log.debug(self, 'Writting the nginx configuration to ' + 'file /etc/nginx/common/wpsc-php7.conf') + wo_nginx = open('/etc/nginx/common/wpsc-php7.conf', + encoding='utf-8', mode='w') + self.app.render((data), 'wpsc-php7.mustache', + out=wo_nginx) + wo_nginx.close() + + if os.path.isfile("/etc/nginx/nginx.conf") and (not + os.path.isfile("/etc/nginx/common/redis-php7.conf")): + data = dict() + Log.debug(self, 'Writting the nginx configuration to ' + 'file /etc/nginx/common/redis-php7.conf') + wo_nginx = open('/etc/nginx/common/redis-php7.conf', + encoding='utf-8', mode='w') + self.app.render((data), 'redis-php7.mustache', + out=wo_nginx) + wo_nginx.close() + + if os.path.isfile("/etc/nginx/conf.d/upstream.conf"): + if not WOFileUtils.grep(self, "/etc/nginx/conf.d/upstream.conf", + "php7"): + with open("/etc/nginx/conf.d/upstream.conf", "a") as php_file: + php_file.write("upstream php7 {\nserver 127.0.0.1:9070;\n}\n" + "upstream debug7 {\nserver 127.0.0.1:9170;\n}\n") + + return(stack.install(apt_packages=apt_packages, packages=packages, + disp_msg=False)) + + +def updatewpuserpassword(self, wo_domain, wo_site_webroot): + + wo_wp_user = '' + wo_wp_pass = '' + WOFileUtils.chdir(self, '{0}/htdocs/'.format(wo_site_webroot)) + + # Check if wo_domain is wordpress install + try: + is_wp = WOShellExec.cmd_exec(self, "wp --allow-root core" + " version") + except CommandExecutionError as e: + raise SiteError("is WordPress site? check command failed ") + + # Exit if wo_domain is not wordpress install + if not is_wp: + Log.error(self, "{0} does not seem to be a WordPress site" + .format(wo_domain)) + + try: + wo_wp_user = input("Provide WordPress user name [admin]: ") + except Exception as e: + Log.debug(self, "{0}".format(e)) + Log.error(self, "\nCould not update password") + + if wo_wp_user == "?": + Log.info(self, "Fetching WordPress user list") + try: + WOShellExec.cmd_exec(self, "wp --allow-root user list " + "--fields=user_login | grep -v user_login") + except CommandExecutionError as e: + raise SiteError("fetch wp userlist command failed") + + if not wo_wp_user: + wo_wp_user = 'admin' + + try: + is_user_exist = WOShellExec.cmd_exec(self, "wp --allow-root user list " + "--fields=user_login | grep {0}$ " + .format(wo_wp_user)) + except CommandExecutionError as e: + raise SiteError("if wp user exists check command failed") + + if is_user_exist: + try: + wo_wp_pass = getpass.getpass(prompt="Provide password for " + "{0} user: " + .format(wo_wp_user)) + + while not wo_wp_pass: + wo_wp_pass = getpass.getpass(prompt="Provide password for " + "{0} user: " + .format(wo_wp_user)) + except Exception as e: + Log.debug(self, "{0}".format(e)) + raise SiteError("failed to read password input ") + + try: + WOShellExec.cmd_exec(self, "wp --allow-root user update {0}" + " --user_pass={1}" + .format(wo_wp_user, wo_wp_pass)) + except CommandExecutionError as e: + raise SiteError("wp user password update command failed") + Log.info(self, "Password updated successfully") + + else: + Log.error(self, "Invalid WordPress user {0} for {1}." + .format(wo_wp_user, wo_domain)) + + +def display_cache_settings(self, data): + if data['wpsc']: + if data['multisite']: + Log.info(self, "Configure WPSC:" + "\t\thttp://{0}/wp-admin/network/settings.php?" + "page=wpsupercache" + .format(data['site_name'])) + else: + Log.info(self, "Configure WPSC:" + "\t\thttp://{0}/wp-admin/options-general.php?" + "page=wpsupercache" + .format(data['site_name'])) + + if data['wpredis']: + if data['multisite']: + Log.info(self, "Configure redis-cache:" + "\thttp://{0}/wp-admin/network/settings.php?" + "page=redis-cache".format(data['site_name'])) + else: + Log.info(self, "Configure redis-cache:" + "\thttp://{0}/wp-admin/options-general.php?" + "page=redis-cache".format(data['site_name'])) + Log.info(self, "Object Cache:\t\tEnable") + +def logwatch(self, logfiles): + import zlib + import base64 + import time + from wo.core import logwatch + + def callback(filename, lines): + for line in lines: + if line.find(':::') == -1: + print(line) + else: + data = line.split(':::') + try: + print(data[0], data[1], + zlib.decompress(base64.decodestring(data[2]))) + except Exception as e: + Log.info(time.time(), + 'caught exception rendering a new log line in %s' + % filename) + + l = logwatch.LogWatcher(logfiles, callback) + l.loop() + + +def detSitePar(opts): + """ + Takes dictionary of parsed arguments + 1.returns sitetype and cachetype + 2. raises RuntimeError when wrong combination is used like + "--wp --wpsubdir" or "--html --wp" + """ + sitetype, cachetype = '', '' + typelist = list() + cachelist = list() + for key, val in opts.items(): + if val and key in ['html', 'php', 'mysql', 'wp', + 'wpsubdir', 'wpsubdomain','php7']: + typelist.append(key) + elif val and key in ['wpfc', 'wpsc', 'wpredis']: + cachelist.append(key) + + if len(typelist) > 1 or len(cachelist) > 1: + if len(cachelist) > 1: + raise RuntimeError("Could not determine cache type.Multiple cache parameter entered") + elif False not in [x in ('php','mysql','html') for x in typelist]: + sitetype = 'mysql' + if not cachelist: + cachetype = 'basic' + else: + cachetype = cachelist[0] + elif False not in [x in ('php7','mysql','html') for x in typelist]: + sitetype = 'mysql' + if not cachelist: + cachetype = 'basic' + else: + cachetype = cachelist[0] + elif False not in [x in ('php','mysql') for x in typelist]: + sitetype = 'mysql' + if not cachelist: + cachetype = 'basic' + else: + cachetype = cachelist[0] + elif False not in [x in ('php7','mysql') for x in typelist]: + sitetype = 'mysql' + if not cachelist: + cachetype = 'basic' + else: + cachetype = cachelist[0] + elif False not in [x in ('html','mysql') for x in typelist]: + sitetype = 'mysql' + if not cachelist: + cachetype = 'basic' + else: + cachetype = cachelist[0] + elif False not in [x in ('php','html') for x in typelist]: + sitetype = 'php' + if not cachelist: + cachetype = 'basic' + else: + cachetype = cachelist[0] + elif False not in [x in ('php7','html') for x in typelist]: + sitetype = 'php7' + if not cachelist: + cachetype = 'basic' + else: + cachetype = cachelist[0] + elif False not in [x in ('wp','wpsubdir') for x in typelist]: + sitetype = 'wpsubdir' + if not cachelist: + cachetype = 'basic' + else: + cachetype = cachelist[0] + elif False not in [x in ('wp','wpsubdomain') for x in typelist]: + sitetype = 'wpsubdomain' + if not cachelist: + cachetype = 'basic' + else: + cachetype = cachelist[0] + elif False not in [x in ('wp','php7') for x in typelist]: + sitetype = 'wp' + if not cachelist: + cachetype = 'basic' + else: + cachetype = cachelist[0] + elif False not in [x in ('wpsubdir','php7') for x in typelist]: + sitetype = 'wpsubdir' + if not cachelist: + cachetype = 'basic' + else: + cachetype = cachelist[0] + elif False not in [x in ('wpsubdomain','php7') for x in typelist]: + sitetype = 'wpsubdomain' + if not cachelist: + cachetype = 'basic' + else: + cachetype = cachelist[0] + else: + raise RuntimeError("could not determine site and cache type") + else: + if not typelist and not cachelist: + sitetype = None + cachetype = None + elif (not typelist or "php7" in typelist) and cachelist: + sitetype = 'wp' + cachetype = cachelist[0] + elif typelist and (not cachelist): + sitetype = typelist[0] + cachetype = 'basic' + else: + sitetype = typelist[0] + cachetype = cachelist[0] + + return (sitetype, cachetype) + + +def generate_random(): + wo_random10 = (''.join(random.sample(string.ascii_uppercase + + string.ascii_lowercase + string.digits, 10))) + return wo_random10 + + +def deleteDB(self, dbname, dbuser, dbhost, exit=True): + try: + # Check if Database exists + try: + if WOMysql.check_db_exists(self, dbname): + # Drop database if exists + Log.debug(self, "dropping database `{0}`".format(dbname)) + WOMysql.execute(self, + "drop database `{0}`".format(dbname), + errormsg='Unable to drop database {0}' + .format(dbname)) + except StatementExcecutionError as e: + Log.debug(self, "drop database failed") + Log.info(self, "Database {0} not dropped".format(dbname)) + + except MySQLConnectionError as e: + Log.debug(self, "Mysql Connection problem occured") + + if dbuser != 'root': + Log.debug(self, "dropping user `{0}`".format(dbuser)) + try: + WOMysql.execute(self, + "drop user `{0}`@`{1}`" + .format(dbuser, dbhost)) + except StatementExcecutionError as e: + Log.debug(self, "drop database user failed") + Log.info(self, "Database {0} not dropped".format(dbuser)) + try: + WOMysql.execute(self, "flush privileges") + except StatementExcecutionError as e: + Log.debug(self, "drop database failed") + Log.info(self, "Database {0} not dropped".format(dbname)) + except Exception as e: + Log.error(self, "Error occured while deleting database", exit) + + +def deleteWebRoot(self, webroot): + # do some preprocessing before proceeding + webroot = webroot.strip() + if (webroot == "/var/www/" or webroot == "/var/www" + or webroot == "/var/www/.." or webroot == "/var/www/."): + Log.debug(self, "Tried to remove {0}, but didn't remove it" + .format(webroot)) + return False + + if os.path.isdir(webroot): + Log.debug(self, "Removing {0}".format(webroot)) + WOFileUtils.rm(self, webroot) + return True + else: + Log.debug(self, "{0} does not exist".format(webroot)) + return False + + +def removeNginxConf(self, domain): + if os.path.isfile('/etc/nginx/sites-available/{0}' + .format(domain)): + Log.debug(self, "Removing Nginx configuration") + WOFileUtils.rm(self, '/etc/nginx/sites-enabled/{0}' + .format(domain)) + WOFileUtils.rm(self, '/etc/nginx/sites-available/{0}' + .format(domain)) + WOService.reload_service(self, 'nginx') + WOGit.add(self, ["/etc/nginx"], + msg="Deleted {0} " + .format(domain)) + + +def doCleanupAction(self, domain='', webroot='', dbname='', dbuser='', + dbhost=''): + """ + Removes the nginx configuration and database for the domain provided. + doCleanupAction(self, domain='sitename', webroot='', + dbname='', dbuser='', dbhost='') + """ + if domain: + if os.path.isfile('/etc/nginx/sites-available/{0}' + .format(domain)): + removeNginxConf(self, domain) + if webroot: + deleteWebRoot(self, webroot) + + if dbname: + if not dbuser: + raise SiteError("dbuser not provided") + if not dbhost: + raise SiteError("dbhost not provided") + deleteDB(self, dbname, dbuser, dbhost) + +def cloneLetsEncrypt(self): + letsencrypt_repo = "https://github.com/letsencrypt/letsencrypt" + if not os.path.isdir("/opt"): + WOFileUtils.mkdir(self,"/opt") + try: + Log.info(self, "Downloading {0:20}".format("LetsEncrypt"), end=' ') + WOFileUtils.chdir(self, '/opt/') + WOShellExec.cmd_exec(self, "git clone {0}".format(letsencrypt_repo)) + Log.info(self, "{0}".format("[" + Log.ENDC + "Done" + + Log.OKBLUE + "]")) + return True + except Exception as e: + Log.debug(self, "[{err}]".format(err=str(e.reason))) + Log.error(self, "Unable to download file, LetsEncrypt") + return False + +def setupLetsEncrypt(self, wo_domain_name): + wo_wp_email = WOVariables.wo_email + while not wo_wp_email: + try: + wo_wp_email = input('Enter WordPress email: ') + except EOFError as e: + Log.debug(self, "{0}".format(e)) + raise SiteError("input WordPress username failed") + + if not os.path.isdir("/opt/letsencrypt"): + cloneLetsEncrypt(self) + WOFileUtils.chdir(self, '/opt/letsencrypt') + WOShellExec.cmd_exec(self, "git pull") + + if os.path.isfile("/etc/letsencrypt/renewal/{0}.conf".format(wo_domain_name)): + Log.debug(self, "Let's Encrypt certificate found for the domain: {0}" + .format(wo_domain_name)) + ssl= archivedCertificateHandle(self,wo_domain_name,wo_wp_email) + else: + Log.warn(self,"Please wait while we fetch the new HTTPS certificate for your site.\nIt may take a few minutes depending on the network.") + ssl = WOShellExec.cmd_exec(self, "./letsencrypt-auto --rsa-key-size 4096 certonly --webroot -w /var/www/{0}/htdocs/ -d {0} -d www.{0} " + .format(wo_domain_name) + + "--email {0} --text --agree-tos".format(wo_wp_email)) + if ssl: + Log.info(self, "The HTTPS setup for your website is successfully completed!") + Log.info(self, "Your certificate and chain have been saved in " + "/etc/letsencrypt/live/{0}/fullchain.pem".format(wo_domain_name)) + Log.info(self, "Configuring nginx HTTPS configuration") + + try: + Log.info(self, "Adding /var/www/{0}/conf/nginx/ssl.conf".format(wo_domain_name)) + + sslconf = open("/var/www/{0}/conf/nginx/ssl.conf" + .format(wo_domain_name), + encoding='utf-8', mode='w') + sslconf.write("listen 443 ssl http2;\n" + "ssl on;\n" + "ssl_certificate /etc/letsencrypt/live/{0}/fullchain.pem;\n" + "ssl_certificate_key /etc/letsencrypt/live/{0}/privkey.pem;\n" + .format(wo_domain_name)) + sslconf.close() + # updateSiteInfo(self, wo_domain_name, ssl=True) + + WOGit.add(self, ["/etc/letsencrypt"], + msg="Adding letsencrypt folder") + + except IOError as e: + Log.debug(self, str(e)) + Log.debug(self, "Error occured while generating " + "ssl.conf") + else: + Log.error(self, "Unable to setup, Let\'s Encrypt", False) + Log.error(self, "Please make sure that your site is pointed to \n" + "same server on which you are running Let\'s Encrypt Client " + "\n to allow it to verify the site automatically.") + +def renewLetsEncrypt(self, wo_domain_name): + + wo_wp_email = WOVariables.wo_email + while not wo_wp_email: + try: + wo_wp_email = input('Enter email address: ') + except EOFError as e: + Log.debug(self, "{0}".format(e)) + raise SiteError("Input WordPress email failed") + + if not os.path.isdir("/opt/letsencrypt"): + cloneLetsEncrypt(self) + WOFileUtils.chdir(self, '/opt/letsencrypt') + WOShellExec.cmd_exec(self, "git pull") + + Log.info(self, "Renewing SSl cert for https://{0}".format(wo_domain_name)) + + ssl = WOShellExec.cmd_exec(self, "./letsencrypt-auto --renew-by-default --rsa-key-size 4096 certonly --webroot -w /var/www/{0}/htdocs/ -d {0} -d www.{0} " + .format(wo_domain_name) + + "--email {0} --text --agree-tos".format(wo_wp_email)) + mail_list = '' + if not ssl: + Log.error(self,"ERROR : Let's Encrypt certificate renewal FAILED!",False) + if (SSL.getExpirationDays(self,wo_domain_name)>0): + Log.error(self, "Your current certificate will expire within " + str(SSL.getExpirationDays(self,wo_domain_name)) + " days.",False) + else: + Log.error(self, "Your current certificate already expired!",False) + + WOSendMail("wordops@{0}".format(wo_domain_name), wo_wp_email, "[FAIL] HTTPS cert renewal {0}".format(wo_domain_name), + "Hi,\n\nHTTPS certificate renewal for https://{0} was unsuccessful.".format(wo_domain_name) + + "\nPlease check the WordOps log for reason. The current expiry date is : " + + str(SSL.getExpirationDate(self,wo_domain_name)) + + "\n\nFor support visit https://wordops.org/support .\n\nBest regards,\nYour WordOps Worker",files=mail_list, + port=25, isTls=False) + Log.error(self, "Check the WO log for more details " + "`tail /var/log/wo/wordops.log`") + + WOGit.add(self, ["/etc/letsencrypt"], + msg="Adding letsencrypt folder") + WOSendMail("wordops@{0}".format(wo_domain_name), wo_wp_email, "[SUCCESS] Let's Encrypt certificate renewal {0}".format(wo_domain_name), + "Hi,\n\nYour Let's Encrypt certificate has been renewed for https://{0} .".format(wo_domain_name) + + "\nYour new certificate will expire on : " + + str(SSL.getExpirationDate(self,wo_domain_name)) + + "\n\nBest regards,\nYour WordOps Worker",files=mail_list, + port=25, isTls=False) + +#redirect= False to disable https redirection +def httpsRedirect(self,wo_domain_name,redirect=True): + if redirect: + if os.path.isfile("/etc/nginx/conf.d/force-ssl-{0}.conf.disabled".format(wo_domain_name)): + WOFileUtils.mvfile(self, "/etc/nginx/conf.d/force-ssl-{0}.conf.disabled".format(wo_domain_name), + "/etc/nginx/conf.d/force-ssl-{0}.conf".format(wo_domain_name)) + else: + try: + Log.info(self, "Adding /etc/nginx/conf.d/force-ssl-{0}.conf".format(wo_domain_name)) + + sslconf = open("/etc/nginx/conf.d/force-ssl-{0}.conf" + .format(wo_domain_name), + encoding='utf-8', mode='w') + sslconf.write("server {\n" + "\tlisten 80;\n" + + "\tserver_name www.{0} {0};\n".format(wo_domain_name) + + "\treturn 301 https://{0}".format(wo_domain_name)+"$request_uri;\n}" ) + sslconf.close() + # Nginx Configation into GIT + except IOError as e: + Log.debug(self, str(e)) + Log.debug(self, "Error occured while generating " + "/etc/nginx/conf.d/force-ssl-{0}.conf".format(wo_domain_name)) + + Log.info(self, "Added HTTPS Force Redirection for Site " + " http://{0}".format(wo_domain_name)) + WOGit.add(self, + ["/etc/nginx"], msg="Adding /etc/nginx/conf.d/force-ssl-{0}.conf".format(wo_domain_name)) + else: + if os.path.isfile("/etc/nginx/conf.d/force-ssl-{0}.conf".format(wo_domain_name)): + WOFileUtils.mvfile(self, "/etc/nginx/conf.d/force-ssl-{0}.conf".format(wo_domain_name), + "/etc/nginx/conf.d/force-ssl-{0}.conf.disabled".format(wo_domain_name)) + Log.info(self, "Disabled HTTPS Force Redirection for Site " + " http://{0}".format(wo_domain_name)) + +def archivedCertificateHandle(self,domain,wo_wp_email): + Log.warn(self,"You already have an existing certificate for the domain requested.\n" + "(ref: /etc/letsencrypt/renewal/{0}.conf)".format(domain) + + "\nPlease select an option from below?" + "\n\t1: Reinstall existing certificate" + "\n\t2: Keep the existing certificate for now" + "\n\t3: Renew & replace the certificate (limit ~5 per 7 days)" + "") + check_prompt = input("\nType the appropriate number [1-3] or any other key to cancel: ") + if not os.path.isfile("/etc/letsencrypt/live/{0}/cert.pem".format(domain)): + Log.error(self,"/etc/letsencrypt/live/{0}/cert.pem file is missing.".format(domain)) + if check_prompt == "1": + Log.info(self,"Please wait while we reinstall the Let's Encrypt certificate for your site.\nIt may take a few minutes depending on your network.") + ssl = WOShellExec.cmd_exec(self, "./letsencrypt-auto certonly --rsa-key-size 4096 --reinstall --webroot -w /var/www/{0}/htdocs/ -d {0} -d www.{0} " + .format(domain) + + "--email {0} --text --agree-tos".format(wo_wp_email)) + elif check_prompt == "2" : + Log.info(self,"Using Existing Certificate files") + if not (os.path.isfile("/etc/letsencrypt/live/{0}/fullchain.pem".format(domain)) or + os.path.isfile("/etc/letsencrypt/live/{0}/privkey.pem".format(domain))): + Log.error(self,"Certificate files not found. Skipping.\n" + "Please check if following file exist\n\t/etc/letsencrypt/live/{0}/fullchain.pem\n\t" + "/etc/letsencrypt/live/{0}/privkey.pem".format(domain)) + ssl = True + + elif check_prompt == "3": + Log.info(self,"Please wait while we renew the Let's Encrypt certificate for your site.\nIt may take a few minutes depending on your network.") + ssl = WOShellExec.cmd_exec(self, "./letsencrypt-auto --renew-by-default --rsa-key-size 4096 certonly --webroot -w /var/www/{0}/htdocs/ -d {0} -d www.{0} " + .format(domain) + + "--email {0} --text --agree-tos".format(wo_wp_email)) + else: + Log.error(self,"Operation cancelled by user.") + + if os.path.isfile("{0}/conf/nginx/ssl.conf" + .format(domain)): + Log.info(self, "Existing ssl.conf . Backing it up ..") + WOFileUtils.mvfile(self, "/var/www/{0}/conf/nginx/ssl.conf" + .format(domain), + '/var/www/{0}/conf/nginx/ssl.conf.bak' + .format(domain)) + + return ssl diff --git a/wo/cli/plugins/sitedb.py b/wo/cli/plugins/sitedb.py new file mode 100644 index 0000000..e006157 --- /dev/null +++ b/wo/cli/plugins/sitedb.py @@ -0,0 +1,126 @@ +from sqlalchemy import Column, DateTime, String, Integer, Boolean +from sqlalchemy import ForeignKey, func +from sqlalchemy.orm import relationship, backref +from sqlalchemy.ext.declarative import declarative_base +from wo.core.logging import Log +from wo.core.database import db_session +from wo.cli.plugins.models import SiteDB +import sys +import glob + + +def addNewSite(self, site, stype, cache, path, + enabled=True, ssl=False, fs='ext4', db='mysql', + db_name=None, db_user=None, db_password=None, + db_host='localhost', hhvm=0, php_version='5.5'): + """ + Add New Site record information into the wo database. + """ + try: + newRec = SiteDB(site, stype, cache, path, enabled, ssl, fs, db, + db_name, db_user, db_password, db_host, hhvm, + php_version) + db_session.add(newRec) + db_session.commit() + except Exception as e: + Log.debug(self, "{0}".format(e)) + Log.error(self, "Unable to add site to database") + + +def getSiteInfo(self, site): + """ + Retrieves site record from ee databse + """ + try: + q = SiteDB.query.filter(SiteDB.sitename == site).first() + return q + except Exception as e: + Log.debug(self, "{0}".format(e)) + Log.error(self, "Unable to query database for site info") + + +def updateSiteInfo(self, site, stype='', cache='', webroot='', + enabled=True, ssl=False, fs='', db='', db_name=None, + db_user=None, db_password=None, db_host=None, hhvm=None, + php_version=''): + """updates site record in database""" + try: + q = SiteDB.query.filter(SiteDB.sitename == site).first() + except Exception as e: + Log.debug(self, "{0}".format(e)) + Log.error(self, "Unable to query database for site info") + + if not q: + Log.error(self, "{0} does not exist in database".format(site)) + + # Check if new record matches old if not then only update database + if stype and q.site_type != stype: + q.site_type = stype + + if cache and q.cache_type != cache: + q.cache_type = cache + + if q.is_enabled != enabled: + q.is_enabled = enabled + + if q.is_ssl != ssl: + q.is_ssl = ssl + + if db_name and q.db_name != db_name: + q.db_name = db_name + + if db_user and q.db_user != db_user: + q.db_user = db_user + + if db_user and q.db_password != db_password: + q.db_password = db_password + + if db_host and q.db_host != db_host: + q.db_host = db_host + + if webroot and q.site_path != webroot: + q.site_path = webroot + + if (hhvm is not None) and (q.is_hhvm is not hhvm): + q.is_hhvm = hhvm + + if php_version and q.php_version != php_version: + q.php_version = php_version + + try: + q.created_on = func.now() + db_session.commit() + except Exception as e: + Log.debug(self, "{0}".format(e)) + Log.error(self, "Unable to update site info in application database.") + + +def deleteSiteInfo(self, site): + """Delete site record in database""" + try: + q = SiteDB.query.filter(SiteDB.sitename == site).first() + except Exception as e: + Log.debug(self, "{0}".format(e)) + Log.error(self, "Unable to query database") + + if not q: + Log.error(self, "{0} does not exist in database".format(site)) + + try: + db_session.delete(q) + db_session.commit() + except Exception as e: + Log.debug(self, "{0}".format(e)) + Log.error(self, "Unable to delete site from application database.") + + +def getAllsites(self): + """ + 1. returns all records from ee database + """ + try: + q = SiteDB.query.all() + return q + except Exception as e: + Log.debug(self, "{0}".format(e)) + Log.error(self, "Unable to query database") diff --git a/wo/cli/plugins/stack.py b/wo/cli/plugins/stack.py new file mode 100644 index 0000000..94cc811 --- /dev/null +++ b/wo/cli/plugins/stack.py @@ -0,0 +1,2047 @@ +"""Stack Plugin for WordOps""" + +from cement.core.controller import CementBaseController, expose +from cement.core import handler, hook +from wo.cli.plugins.site_functions import * +from wo.core.variables import WOVariables +from wo.core.aptget import WOAptGet +from wo.core.download import WODownload +from wo.core.shellexec import WOShellExec, CommandExecutionError +from wo.core.fileutils import WOFileUtils +from wo.core.apt_repo import WORepo +from wo.core.extract import WOExtract +from wo.core.mysql import WOMysql +from wo.core.addswap import WOSwap +from wo.core.git import WOGit +from wo.core.checkfqdn import check_fqdn +from pynginxconfig import NginxConfig +from wo.core.services import WOService +from wo.core.variables import WOVariables +import random +import string +import configparser +import time +import shutil +import os +import pwd +import grp +import codecs +import platform +from wo.cli.plugins.stack_services import WOStackStatusController +from wo.cli.plugins.stack_migrate import WOStackMigrateController +from wo.cli.plugins.stack_upgrade import WOStackUpgradeController +from wo.core.logging import Log +from wo.cli.plugins.sitedb import * + + +def wo_stack_hook(app): + pass + + +class WOStackController(CementBaseController): + class Meta: + label = 'stack' + stacked_on = 'base' + stacked_type = 'nested' + description = 'Stack command manages stack operations' + arguments = [ + (['--all'], + dict(help='Install all stacks at once', action='store_true')), + (['--web'], + dict(help='Install web stack', action='store_true')), + (['--admin'], + dict(help='Install admin tools stack', action='store_true')), + (['--nginx'], + dict(help='Install Nginx stack', action='store_true')), +# (['--nginxmainline'], +# dict(help='Install Nginx mainline stack', action='store_true')), + (['--php'], + dict(help='Install PHP stack', action='store_true')), + (['--php7'], + dict(help='Install PHP 7.0 stack', action='store_true')), + (['--php71'], + dict(help='Install PHP 7.1 stack', action='store_true')), + (['--php72'], + dict(help='Install PHP 7.2 stack', action='store_true')), + (['--mysql'], + dict(help='Install MySQL stack', action='store_true')), + (['--hhvm'], + dict(help='Install HHVM stack', action='store_true')), + (['--wpcli'], + dict(help='Install WPCLI stack', action='store_true')), + (['--phpmyadmin'], + dict(help='Install PHPMyAdmin stack', action='store_true')), + (['--adminer'], + dict(help='Install Adminer stack', action='store_true')), + (['--utils'], + dict(help='Install Utils stack', action='store_true')), + (['--redis'], + dict(help='Install Redis', action='store_true')), + (['--phpredisadmin'], + dict(help='Install phpRedisAdmin', action='store_true')), + ] + usage = "ee stack (command) [options]" + + @expose(hide=True) + def default(self): + """default action of wo stack command""" + self.app.args.print_help() + + @expose(hide=True) + def pre_pref(self, apt_packages): + """Pre settings to do before installation packages""" + + if set(WOVariables.wo_mysql).issubset(set(apt_packages)): + Log.info(self, "Adding repository for MySQL, please wait...") + mysql_pref = ("Package: *\nPin: origin sfo1.mirrors.digitalocean.com" + "\nPin-Priority: 1000\n") + with open('/etc/apt/preferences.d/' + 'MariaDB.pref', 'w') as mysql_pref_file: + mysql_pref_file.write(mysql_pref) + WORepo.add(self, repo_url=WOVariables.wo_mysql_repo) + Log.debug(self, 'Adding key for {0}' + .format(WOVariables.wo_mysql_repo)) + WORepo.add_key(self, '0xcbcb082a1bb943db', + keyserver="keyserver.ubuntu.com") + WORepo.add_key(self, '0xF1656F24C74CD1D8', + keyserver="keyserver.ubuntu.com") + chars = ''.join(random.sample(string.ascii_letters, 8)) + Log.debug(self, "Pre-seeding MySQL") + Log.debug(self, "echo \"mariadb-server-10.1 " + "mysql-server/root_password " + "password \" | " + "debconf-set-selections") + try: + WOShellExec.cmd_exec(self, "echo \"mariadb-server-10.1 " + "mysql-server/root_password " + "password {chars}\" | " + "debconf-set-selections" + .format(chars=chars), + log=False) + except CommandExecutionError as e: + Log.error("Failed to initialize MySQL package") + + Log.debug(self, "echo \"mariadb-server-10.1 " + "mysql-server/root_password_again " + "password \" | " + "debconf-set-selections") + try: + WOShellExec.cmd_exec(self, "echo \"mariadb-server-10.1 " + "mysql-server/root_password_again " + "password {chars}\" | " + "debconf-set-selections" + .format(chars=chars), + log=False) + except CommandExecutionError as e: + Log.error("Failed to initialize MySQL package") + + mysql_config = """ + [client] + user = root + password = {chars} + """.format(chars=chars) + config = configparser.ConfigParser() + config.read_string(mysql_config) + Log.debug(self, 'Writting configuration into MySQL file') + conf_path = "/etc/mysql/conf.d/my.cnf" + os.makedirs(os.path.dirname(conf_path), exist_ok=True) + with open(conf_path, encoding='utf-8', + mode='w') as configfile: + config.write(configfile) + Log.debug(self, 'Setting my.cnf permission') + WOFileUtils.chmod(self, "/etc/mysql/conf.d/my.cnf", 0o600) + + if set(WOVariables.wo_nginx).issubset(set(apt_packages)): + Log.info(self, "Adding repository for NGINX, please wait...") + WORepo.add(self, repo_url=WOVariables.wo_nginx_repo) + Log.debug(self, 'Adding ppa of Nginx') + WORepo.add_key(self, WOVariables.wo_nginx_key) + + if (WOVariables.wo_platform_codename == 'trusty' or WOVariables.wo_platform_codename == 'xenial' or WOVariables.wo_platform_codename == 'bionic'): + if set(WOVariables.wo_php7_0).issubset(set(apt_packages)) \ + or set(WOVariables.wo_php5_6).issubset(set(apt_packages)): + Log.info(self, "Adding repository for PHP, please wait...") + Log.debug(self, 'Adding ppa for PHP') + WORepo.add(self, ppa=WOVariables.wo_php_repo) + else: + if set(WOVariables.wo_php).issubset(set(apt_packages)): + Log.info(self, "Adding repository for PHP, please wait...") + # Add repository for php + if WOVariables.wo_platform_distro == 'debian': + if WOVariables.wo_platform_codename != 'jessie': + Log.debug(self, 'Adding repo_url of php for debian') + WORepo.add(self, repo_url=WOVariables.wo_php_repo) + Log.debug(self, 'Adding Dotdeb/php GPG key') + WORepo.add_key(self, '89DF5277') + else: + Log.debug(self, 'Adding ppa for PHP') + WORepo.add(self, ppa=WOVariables.wo_php_repo) + + if WOVariables.wo_platform_codename == 'jessie': + if set(WOVariables.wo_php7_0).issubset(set(apt_packages)): + Log.debug(self, 'Adding repo_url of php 7.0 for debian') + WORepo.add(self, repo_url=WOVariables.wo_php_repo) + Log.debug(self, 'Adding Dotdeb/php GPG key') + WORepo.add_key(self, '89DF5277') + + if set(WOVariables.wo_hhvm).issubset(set(apt_packages)): + if (WOVariables.wo_platform_codename != 'xenial' or WOVariables.wo_platform_codename != 'bionic'): + Log.info(self, "Adding repository for HHVM, please wait...") + if WOVariables.wo_platform_codename == 'precise': + Log.debug(self, 'Adding PPA for Boost') + WORepo.add(self, ppa=WOVariables.wo_boost_repo) + Log.debug(self, 'Adding ppa repo for HHVM') + WORepo.add(self, repo_url=WOVariables.wo_hhvm_repo) + Log.debug(self, 'Adding HHVM GPG Key') + WORepo.add_key(self, '0x5a16e7281be7a449') + else: + Log.info(self, "Using default Ubuntu repository for HHVM") + + if set(WOVariables.wo_redis).issubset(set(apt_packages)): + Log.info(self, "Adding repository for Redis, please wait...") + if WOVariables.wo_platform_distro == 'debian': + Log.debug(self, 'Adding repo_url of redis for debian') + WORepo.add(self, repo_url=WOVariables.wo_redis_repo) + Log.debug(self, 'Adding Dotdeb GPG key') + WORepo.add_key(self, '89DF5277') + else: + Log.debug(self, 'Adding ppa for redis') + WORepo.add(self, ppa=WOVariables.wo_redis_repo) + + @expose(hide=True) + def post_pref(self, apt_packages, packages): + """Post activity after installation of packages""" + if len(apt_packages): + + if set(WOVariables.wo_nginx).issubset(set(apt_packages)): + if set(["nginx-plus"]).issubset(set(apt_packages)) or set(["nginx"]).issubset(set(apt_packages)): + # Fix for white screen death with NGINX PLUS + if not WOFileUtils.grep(self, '/etc/nginx/fastcgi_params', + 'SCRIPT_FILENAME'): + with open('/etc/nginx/fastcgi_params', encoding='utf-8', + mode='a') as wo_nginx: + wo_nginx.write('fastcgi_param \tSCRIPT_FILENAME ' + '\t$request_filename;\n') + + if not (os.path.isfile('/etc/nginx/common/wpfc.conf')): + # Change WordOpsVersion in nginx.conf file + WOFileUtils.searchreplace(self, "/etc/nginx/nginx.conf", + "# add_header", + "add_header") + + WOFileUtils.searchreplace(self, "/etc/nginx/nginx.conf", + "\"WordOps\"", + "\"WordOps{0}\"" + .format(WOVariables.wo_version)) + data = dict() + Log.debug(self, 'Writting the nginx configuration to ' + 'file /etc/nginx/conf.d/blockips.conf') + wo_nginx = open('/etc/nginx/conf.d/blockips.conf', + encoding='utf-8', mode='w') + self.app.render((data), 'blockips.mustache', out=wo_nginx) + wo_nginx.close() + + Log.debug(self, 'Writting the nginx configuration to ' + 'file /etc/nginx/conf.d/fastcgi.conf') + wo_nginx = open('/etc/nginx/conf.d/fastcgi.conf', + encoding='utf-8', mode='w') + self.app.render((data), 'fastcgi.mustache', out=wo_nginx) + wo_nginx.close() + + data = dict(php="9000", debug="9001", hhvm="8000",php7="9070",debug7="9170", + hhvmconf=False, php7conf= True if WOAptGet.is_installed(self,'php7.0-fpm') else False ) + Log.debug(self, 'Writting the nginx configuration to ' + 'file /etc/nginx/conf.d/upstream.conf') + wo_nginx = open('/etc/nginx/conf.d/upstream.conf', + encoding='utf-8', mode='w') + self.app.render((data), 'upstream.mustache', out=wo_nginx) + wo_nginx.close() + + # Setup Nginx common directory + if not os.path.exists('/etc/nginx/common'): + Log.debug(self, 'Creating directory' + '/etc/nginx/common') + os.makedirs('/etc/nginx/common') + + # http2 = ("http2" if set(["nginx-mainline"]).issubset(set(apt_packages)) else "spdy") + data = dict(webroot=WOVariables.wo_webroot) + Log.debug(self, 'Writting the nginx configuration to ' + 'file /etc/nginx/common/acl.conf') + wo_nginx = open('/etc/nginx/common/acl.conf', + encoding='utf-8', mode='w') + self.app.render((data), 'acl.mustache', + out=wo_nginx) + wo_nginx.close() + + Log.debug(self, 'Writting the nginx configuration to ' + 'file /etc/nginx/common/locations.conf') + wo_nginx = open('/etc/nginx/common/locations.conf', + encoding='utf-8', mode='w') + self.app.render((data), 'locations.mustache', + out=wo_nginx) + wo_nginx.close() + + Log.debug(self, 'Writting the nginx configuration to ' + 'file /etc/nginx/common/php.conf') + wo_nginx = open('/etc/nginx/common/php.conf', + encoding='utf-8', mode='w') + self.app.render((data), 'php.mustache', + out=wo_nginx) + wo_nginx.close() + + Log.debug(self, 'Writting the nginx configuration to ' + 'file /etc/nginx/common/wpcommon.conf') + wo_nginx = open('/etc/nginx/common/wpcommon.conf', + encoding='utf-8', mode='w') + self.app.render((data), 'wpcommon.mustache', + out=wo_nginx) + wo_nginx.close() + + Log.debug(self, 'Writting the nginx configuration to ' + 'file /etc/nginx/common/wpfc.conf') + wo_nginx = open('/etc/nginx/common/wpfc.conf', + encoding='utf-8', mode='w') + self.app.render((data), 'wpfc.mustache', + out=wo_nginx) + wo_nginx.close() + + Log.debug(self, 'Writting the nginx configuration to ' + 'file /etc/nginx/common/wpsc.conf') + wo_nginx = open('/etc/nginx/common/wpsc.conf', + encoding='utf-8', mode='w') + self.app.render((data), 'wpsc.mustache', + out=wo_nginx) + wo_nginx.close() + + Log.debug(self, 'Writting the nginx configuration to ' + 'file /etc/nginx/common/wpsubdir.conf') + wo_nginx = open('/etc/nginx/common/wpsubdir.conf', + encoding='utf-8', mode='w') + self.app.render((data), 'wpsubdir.mustache', + out=wo_nginx) + wo_nginx.close() + + #php7 conf + if (WOVariables.wo_platform_codename == 'stretch' or WOVariables.wo_platform_codename == 'jessie' or WOVariables.wo_platform_codename == 'trusty' or WOVariables.wo_platform_codename == 'xenial' or WOVariables.wo_platform_codename == 'bionic') and (not + os.path.isfile("/etc/nginx/common/php7.conf")): + #data = dict() + Log.debug(self, 'Writting the nginx configuration to ' + 'file /etc/nginx/common/locations-php7.conf') + wo_nginx = open('/etc/nginx/common/locations-php7.conf', + encoding='utf-8', mode='w') + self.app.render((data), 'locations-php7.mustache', + out=wo_nginx) + wo_nginx.close() + + Log.debug(self, 'Writting the nginx configuration to ' + 'file /etc/nginx/common/php7.conf') + wo_nginx = open('/etc/nginx/common/php7.conf', + encoding='utf-8', mode='w') + self.app.render((data), 'php7.mustache', + out=wo_nginx) + wo_nginx.close() + + Log.debug(self, 'Writting the nginx configuration to ' + 'file /etc/nginx/common/wpcommon-php7.conf') + wo_nginx = open('/etc/nginx/common/wpcommon-php7.conf', + encoding='utf-8', mode='w') + self.app.render((data), 'wpcommon-php7.mustache', + out=wo_nginx) + wo_nginx.close() + + Log.debug(self, 'Writting the nginx configuration to ' + 'file /etc/nginx/common/wpfc-php7.conf') + wo_nginx = open('/etc/nginx/common/wpfc-php7.conf', + encoding='utf-8', mode='w') + self.app.render((data), 'wpfc-php7.mustache', + out=wo_nginx) + wo_nginx.close() + + Log.debug(self, 'Writting the nginx configuration to ' + 'file /etc/nginx/common/wpsc-php7.conf') + wo_nginx = open('/etc/nginx/common/wpsc-php7.conf', + encoding='utf-8', mode='w') + self.app.render((data), 'wpsc-php7.mustache', + out=wo_nginx) + wo_nginx.close() + + Log.debug(self, 'Writting the nginx configuration to ' + 'file /etc/nginx/common/redis-php7.conf') + wo_nginx = open('/etc/nginx/common/redis-php7.conf', + encoding='utf-8', mode='w') + self.app.render((data), 'redis-php7.mustache', + out=wo_nginx) + wo_nginx.close() + + # Nginx-Plus does not have nginx package structure like this + # So creating directories + if set(["nginx-plus"]).issubset(set(apt_packages)) or set(["nginx"]).issubset(set(apt_packages)): + Log.info(self, + "Installing WordOpsConfigurations for" "NGINX") + if not os.path.exists('/etc/nginx/sites-available'): + Log.debug(self, 'Creating directory' + '/etc/nginx/sites-available') + os.makedirs('/etc/nginx/sites-available') + + if not os.path.exists('/etc/nginx/sites-enabled'): + Log.debug(self, 'Creating directory' + '/etc/nginx/sites-available') + os.makedirs('/etc/nginx/sites-enabled') + + # 22222 port settings + Log.debug(self, 'Writting the nginx configuration to ' + 'file /etc/nginx/sites-available/' + '22222') + wo_nginx = open('/etc/nginx/sites-available/22222', + encoding='utf-8', mode='w') + self.app.render((data), '22222.mustache', + out=wo_nginx) + wo_nginx.close() + + passwd = ''.join([random.choice + (string.ascii_letters + string.digits) + for n in range(6)]) + try: + WOShellExec.cmd_exec(self, "printf \"WordOps:" + "$(openssl passwd -crypt " + "{password} 2> /dev/null)\n\"" + "> /etc/nginx/htpasswd-wo " + "2>/dev/null" + .format(password=passwd)) + except CommandExecutionError as e: + Log.error(self, "Failed to save HTTP Auth") + + # Create Symbolic link for 22222 + WOFileUtils.create_symlink(self, ['/etc/nginx/' + 'sites-available/' + '22222', + '/etc/nginx/' + 'sites-enabled/' + '22222']) + # Create log and cert folder and softlinks + if not os.path.exists('{0}22222/logs' + .format(WOVariables.wo_webroot)): + Log.debug(self, "Creating directory " + "{0}22222/logs " + .format(WOVariables.wo_webroot)) + os.makedirs('{0}22222/logs' + .format(WOVariables.wo_webroot)) + + if not os.path.exists('{0}22222/cert' + .format(WOVariables.wo_webroot)): + Log.debug(self, "Creating directory " + "{0}22222/cert" + .format(WOVariables.wo_webroot)) + os.makedirs('{0}22222/cert' + .format(WOVariables.wo_webroot)) + + WOFileUtils.create_symlink(self, ['/var/log/nginx/' + '22222.access.log', + '{0}22222/' + 'logs/access.log' + .format(WOVariables.wo_webroot)] + ) + + WOFileUtils.create_symlink(self, ['/var/log/nginx/' + '22222.error.log', + '{0}22222/' + 'logs/error.log' + .format(WOVariables.wo_webroot)] + ) + + try: + WOShellExec.cmd_exec(self, "openssl genrsa -out " + "{0}22222/cert/22222.key 2048" + .format(WOVariables.wo_webroot)) + WOShellExec.cmd_exec(self, "openssl req -new -batch " + "-subj /commonName=127.0.0.1/ " + "-key {0}22222/cert/22222.key " + "-out {0}22222/cert/" + "22222.csr" + .format(WOVariables.wo_webroot)) + + WOFileUtils.mvfile(self, "{0}22222/cert/22222.key" + .format(WOVariables.wo_webroot), + "{0}22222/cert/" + "22222.key.org" + .format(WOVariables.wo_webroot)) + + WOShellExec.cmd_exec(self, "openssl rsa -in " + "{0}22222/cert/" + "22222.key.org -out " + "{0}22222/cert/22222.key" + .format(WOVariables.wo_webroot)) + + WOShellExec.cmd_exec(self, "openssl x509 -req -days " + "3652 -in {0}22222/cert/" + "22222.csr -signkey {0}" + "22222/cert/22222.key -out " + "{0}22222/cert/22222.crt" + .format(WOVariables.wo_webroot)) + + except CommandExecutionError as e: + Log.error(self, "Failed to generate HTTPS certificate for 22222") + + # Nginx Configation into GIT + WOGit.add(self, + ["/etc/nginx"], msg="Adding Nginx into Git") + WOService.reload_service(self, 'nginx') + if set(["nginx-plus"]).issubset(set(apt_packages)) or set(["nginx"]).issubset(set(apt_packages)): + WOShellExec.cmd_exec(self, "sed -i -e 's/^user/#user/'" + " -e '/^#user/a user" + "\ www-data\;'" + " /etc/nginx/nginx.conf") + if not WOShellExec.cmd_exec(self, "cat /etc/nginx/" + "nginx.conf | grep -q " + "'/etc/nginx/sites-enabled'"): + WOShellExec.cmd_exec(self, "sed -i '/\/etc\/" + "nginx\/conf\.d\/\*" + "\.conf/a \ include" + "\ \/etc\/nginx\/sites-enabled" + "\/*;' /etc/nginx/nginx.conf") + + # WordOpsconfig for NGINX plus + data['version'] = WOVariables.wo_version + Log.debug(self, 'Writting for nginx plus configuration' + ' to file /etc/nginx/conf.d/wo-plus.conf') + wo_nginx = open('/etc/nginx/conf.d/wo-plus.conf', + encoding='utf-8', mode='w') + self.app.render((data), 'wo-plus.mustache', + out=wo_nginx) + wo_nginx.close() + + print("HTTP Auth User Name: WordOps" + + "\nHTTP Auth Password : {0}".format(passwd)) + WOService.reload_service(self, 'nginx') + else: + self.msg = (self.msg + ["HTTP Auth User Name: WordOps"] + + ["HTTP Auth Password : {0}".format(passwd)]) + else: + WOService.restart_service(self, 'nginx') + + if WOAptGet.is_installed(self,'redis-server'): + if os.path.isfile("/etc/nginx/nginx.conf") and (not + os.path.isfile("/etc/nginx/common/redis.conf")): + + data = dict() + Log.debug(self, 'Writting the nginx configuration to ' + 'file /etc/nginx/common/redis.conf') + wo_nginx = open('/etc/nginx/common/redis.conf', + encoding='utf-8', mode='w') + self.app.render((data), 'redis.mustache', + out=wo_nginx) + wo_nginx.close() + + if os.path.isfile("/etc/nginx/nginx.conf") and (not + os.path.isfile("/etc/nginx/common/redis-hhvm.conf")): + + data = dict() + Log.debug(self, 'Writting the nginx configuration to ' + 'file /etc/nginx/common/redis-hhvm.conf') + wo_nginx = open('/etc/nginx/common/redis-hhvm.conf', + encoding='utf-8', mode='w') + self.app.render((data), 'redis-hhvm.mustache', + out=wo_nginx) + wo_nginx.close() + + if (WOVariables.wo_platform_codename == 'trusty' or WOVariables.wo_platform_codename == 'xenial' or WOVariables.wo_platform_codename == 'bionic'): + if os.path.isfile("/etc/nginx/nginx.conf") and (not + os.path.isfile("/etc/nginx/common/redis-php7.conf")): + data = dict() + Log.debug(self, 'Writting the nginx configuration to ' + 'file /etc/nginx/common/redis-php7.conf') + wo_nginx = open('/etc/nginx/common/redis-php7.conf', + encoding='utf-8', mode='w') + self.app.render((data), 'redis-php7.mustache', + out=wo_nginx) + wo_nginx.close() + + if os.path.isfile("/etc/nginx/conf.d/upstream.conf"): + if not WOFileUtils.grep(self, "/etc/nginx/conf.d/" + "upstream.conf", + "redis"): + with open("/etc/nginx/conf.d/upstream.conf", + "a") as redis_file: + redis_file.write("upstream redis {\n" + " server 127.0.0.1:6379;\n" + " keepalive 10;\n}\n") + + if os.path.isfile("/etc/nginx/nginx.conf") and (not + os.path.isfile("/etc/nginx/conf.d/redis.conf")): + with open("/etc/nginx/conf.d/redis.conf", "a") as redis_file: + redis_file.write("# Log format Settings\n" + "log_format rt_cache_redis '$remote_addr $upstream_response_time $srcache_fetch_status [$time_local] '\n" + "'$http_host \"$request\" $status $body_bytes_sent '\n" + "'\"$http_referer\" \"$http_user_agent\"';\n") + #setup nginx common folder for php7 + if self.app.pargs.php7: + if os.path.isdir("/etc/nginx/common") and (not + os.path.isfile("/etc/nginx/common/php7.conf")): + data = dict() + Log.debug(self, 'Writting the nginx configuration to ' + 'file /etc/nginx/common/locations-php7.conf') + wo_nginx = open('/etc/nginx/common/locations-php7.conf', + encoding='utf-8', mode='w') + self.app.render((data), 'locations-php7.mustache', + out=wo_nginx) + wo_nginx.close() + + Log.debug(self, 'Writting the nginx configuration to ' + 'file /etc/nginx/common/php7.conf') + wo_nginx = open('/etc/nginx/common/php7.conf', + encoding='utf-8', mode='w') + self.app.render((data), 'php7.mustache', + out=wo_nginx) + wo_nginx.close() + + Log.debug(self, 'Writting the nginx configuration to ' + 'file /etc/nginx/common/wpcommon-php7.conf') + wo_nginx = open('/etc/nginx/common/wpcommon-php7.conf', + encoding='utf-8', mode='w') + self.app.render((data), 'wpcommon-php7.mustache', + out=wo_nginx) + wo_nginx.close() + + Log.debug(self, 'Writting the nginx configuration to ' + 'file /etc/nginx/common/wpfc-php7.conf') + wo_nginx = open('/etc/nginx/common/wpfc-php7.conf', + encoding='utf-8', mode='w') + self.app.render((data), 'wpfc-php7.mustache', + out=wo_nginx) + wo_nginx.close() + + Log.debug(self, 'Writting the nginx configuration to ' + 'file /etc/nginx/common/wpsc-php7.conf') + wo_nginx = open('/etc/nginx/common/wpsc-php7.conf', + encoding='utf-8', mode='w') + self.app.render((data), 'wpsc-php7.mustache', + out=wo_nginx) + wo_nginx.close() + + if os.path.isdir("/etc/nginx/common") and (not os.path.isfile("/etc/nginx/common/redis-php7.conf")): + data = dict() + Log.debug(self, 'Writting the nginx configuration to ' + 'file /etc/nginx/common/redis-php7.conf') + wo_nginx = open('/etc/nginx/common/redis-php7.conf', + encoding='utf-8', mode='w') + self.app.render((data), 'redis-php7.mustache', + out=wo_nginx) + wo_nginx.close() + + if os.path.isfile("/etc/nginx/conf.d/upstream.conf"): + if not WOFileUtils.grep(self, "/etc/nginx/conf.d/upstream.conf", + "php7"): + with open("/etc/nginx/conf.d/upstream.conf", "a") as php_file: + php_file.write("upstream php7 {\nserver 127.0.0.1:9070;\n}\n" + "upstream debug7 {\nserver 127.0.0.1:9170;\n}\n") + + if set(WOVariables.wo_hhvm).issubset(set(apt_packages)): + + WOShellExec.cmd_exec(self, "update-rc.d hhvm defaults") + + WOFileUtils.searchreplace(self, "/etc/hhvm/server.ini", + "9000", "8000") + if (WOVariables.wo_platform_codename != 'xenial' or WOVariables.wo_platform_codename != 'bionic'): + WOFileUtils.searchreplace(self, "/etc/nginx/hhvm.conf", + "9000", "8000") + + with open("/etc/hhvm/php.ini", "a") as hhvm_file: + hhvm_file.write("hhvm.log.header = true\n" + "hhvm.log.natives_stack_trace = true\n" + "hhvm.mysql.socket = " + "/var/run/mysqld/mysqld.sock\n" + "hhvm.pdo_mysql.socket = " + "/var/run/mysqld/mysqld.sock\n" + "hhvm.mysqli.socket = " + "/var/run/mysqld/mysqld.sock\n") + + with open("/etc/hhvm/server.ini", "a") as hhvm_file: + hhvm_file.write("hhvm.server.ip = 127.0.0.1\n") + + if os.path.isfile("/etc/nginx/conf.d/fastcgi.conf"): + if not WOFileUtils.grep(self, "/etc/nginx/conf.d/" + "fastcgi.conf", + "fastcgi_keep_conn"): + with open("/etc/nginx/conf.d/fastcgi.conf", + "a") as hhvm_file: + hhvm_file.write("fastcgi_keep_conn on;\n") + + if os.path.isfile("/etc/nginx/conf.d/upstream.conf"): + if not WOFileUtils.grep(self, "/etc/nginx/conf.d/" + "upstream.conf", + "hhvm"): + with open("/etc/nginx/conf.d/upstream.conf", + "a") as hhvm_file: + hhvm_file.write("upstream hhvm {\nserver " + "127.0.0.1:8000;\n" + "server 127.0.0.1:9000 backup;\n}" + "\n") + + WOGit.add(self, ["/etc/hhvm"], msg="Adding HHVM into Git") + WOService.restart_service(self, 'hhvm') + + if os.path.isfile("/etc/nginx/nginx.conf") and (not + os.path.isfile("/etc/nginx/common/php-hhvm.conf")): + + data = dict() + Log.debug(self, 'Writting the nginx configuration to ' + 'file /etc/nginx/common/php-hhvm.conf') + wo_nginx = open('/etc/nginx/common/php-hhvm.conf', + encoding='utf-8', mode='w') + self.app.render((data), 'php-hhvm.mustache', + out=wo_nginx) + wo_nginx.close() + + Log.debug(self, 'Writting the nginx configuration to ' + 'file /etc/nginx/common/wpfc-hhvm.conf') + wo_nginx = open('/etc/nginx/common/wpfc-hhvm.conf', + encoding='utf-8', mode='w') + self.app.render((data), 'wpfc-hhvm.mustache', + out=wo_nginx) + wo_nginx.close() + + Log.debug(self, 'Writting the nginx configuration to ' + 'file /etc/nginx/common/wpsc-hhvm.conf') + wo_nginx = open('/etc/nginx/common/wpsc-hhvm.conf', + encoding='utf-8', mode='w') + self.app.render((data), 'wpsc-hhvm.mustache', + out=wo_nginx) + wo_nginx.close() + + if not WOService.reload_service(self, 'nginx'): + Log.error(self, "Failed to reload Nginx, please check " + "output of `nginx -t`") + + if set(WOVariables.wo_redis).issubset(set(apt_packages)): + if os.path.isfile("/etc/nginx/nginx.conf") and (not + os.path.isfile("/etc/nginx/common/redis.conf")): + + data = dict() + Log.debug(self, 'Writting the nginx configuration to ' + 'file /etc/nginx/common/redis.conf') + wo_nginx = open('/etc/nginx/common/redis.conf', + encoding='utf-8', mode='w') + self.app.render((data), 'redis.mustache', + out=wo_nginx) + wo_nginx.close() + + if os.path.isfile("/etc/nginx/nginx.conf") and (not + os.path.isfile("/etc/nginx/common/redis-hhvm.conf")): + + data = dict() + Log.debug(self, 'Writting the nginx configuration to ' + 'file /etc/nginx/common/redis-hhvm.conf') + wo_nginx = open('/etc/nginx/common/redis-hhvm.conf', + encoding='utf-8', mode='w') + self.app.render((data), 'redis-hhvm.mustache', + out=wo_nginx) + wo_nginx.close() + + if os.path.isfile("/etc/nginx/conf.d/upstream.conf"): + if not WOFileUtils.grep(self, "/etc/nginx/conf.d/" + "upstream.conf", + "redis"): + with open("/etc/nginx/conf.d/upstream.conf", + "a") as redis_file: + redis_file.write("upstream redis {\n" + " server 127.0.0.1:6379;\n" + " keepalive 10;\n}\n") + + if os.path.isfile("/etc/nginx/nginx.conf") and (not + os.path.isfile("/etc/nginx/conf.d/redis.conf")): + with open("/etc/nginx/conf.d/redis.conf", "a") as redis_file: + redis_file.write("# Log format Settings\n" + "log_format rt_cache_redis '$remote_addr $upstream_response_time $srcache_fetch_status [$time_local] '\n" + "'$http_host \"$request\" $status $body_bytes_sent '\n" + "'\"$http_referer\" \"$http_user_agent\"';\n") + + if (WOVariables.wo_platform_distro == 'debian' or WOVariables.wo_platform_codename == 'precise') and set(WOVariables.wo_php).issubset(set(apt_packages)): + # Create log directories + if not os.path.exists('/var/log/php5/'): + Log.debug(self, 'Creating directory /var/log/php5/') + os.makedirs('/var/log/php5/') + + # For debian install xdebug + + if (WOVariables.wo_platform_distro == "debian" and + WOVariables.wo_platform_codename == 'wheezy'): + WOShellExec.cmd_exec(self, "pecl install xdebug") + + with open("/etc/php5/mods-available/xdebug.ini", + encoding='utf-8', mode='a') as myfile: + myfile.write("zend_extension=/usr/lib/php5/20131226/" + "xdebug.so\n") + + WOFileUtils.create_symlink(self, ["/etc/php5/" + "mods-available/xdebug.ini", + "/etc/php5/fpm/conf.d" + "/20-xedbug.ini"]) + + # Parse etc/php5/fpm/php.ini + config = configparser.ConfigParser() + Log.debug(self, "configuring php file /etc/php5/fpm/php.ini") + config.read('/etc/php5/fpm/php.ini') + config['PHP']['expose_php'] = 'Off' + config['PHP']['post_max_size'] = '100M' + config['PHP']['upload_max_filesize'] = '100M' + config['PHP']['max_execution_time'] = '300' + config['PHP']['date.timezone'] = WOVariables.wo_timezone + with open('/etc/php5/fpm/php.ini', + encoding='utf-8', mode='w') as configfile: + Log.debug(self, "Writting php configuration into " + "/etc/php5/fpm/php.ini") + config.write(configfile) + + #configure /etc/php5/fpm/php-fpm.conf + data = dict(pid="/run/php5-fpm.pid", error_log="/var/log/php5/fpm.log", + include="/etc/php5/fpm/pool.d/*.conf") + Log.debug(self, "writting php configuration into " + "/etc/php5/fpm/php-fpm.conf") + wo_php_fpm = open('/etc/php5/fpm/php-fpm.conf', + encoding='utf-8', mode='w') + self.app.render((data), 'php-fpm.mustache', out=wo_php_fpm) + wo_php_fpm.close() + + + # Parse /etc/php5/fpm/pool.d/www.conf + config = configparser.ConfigParser() + config.read_file(codecs.open('/etc/php5/fpm/pool.d/www.conf', + "r", "utf8")) + config['www']['ping.path'] = '/ping' + config['www']['pm.status_path'] = '/status' + config['www']['pm.max_requests'] = '500' + config['www']['pm.max_children'] = '100' + config['www']['pm.start_servers'] = '20' + config['www']['pm.min_spare_servers'] = '10' + config['www']['pm.max_spare_servers'] = '30' + config['www']['request_terminate_timeout'] = '300' + config['www']['pm'] = 'ondemand' + config['www']['listen'] = '127.0.0.1:9000' + with codecs.open('/etc/php5/fpm/pool.d/www.conf', + encoding='utf-8', mode='w') as configfile: + Log.debug(self, "writting PHP5 configuration into " + "/etc/php5/fpm/pool.d/www.conf") + config.write(configfile) + + # Generate /etc/php5/fpm/pool.d/debug.conf + WOFileUtils.copyfile(self, "/etc/php5/fpm/pool.d/www.conf", + "/etc/php5/fpm/pool.d/debug.conf") + WOFileUtils.searchreplace(self, "/etc/php5/fpm/pool.d/" + "debug.conf", "[www]", "[debug]") + config = configparser.ConfigParser() + config.read('/etc/php5/fpm/pool.d/debug.conf') + config['debug']['listen'] = '127.0.0.1:9001' + config['debug']['rlimit_core'] = 'unlimited' + config['debug']['slowlog'] = '/var/log/php5/slow.log' + config['debug']['request_slowlog_timeout'] = '10s' + with open('/etc/php5/fpm/pool.d/debug.conf', + encoding='utf-8', mode='w') as confifile: + Log.debug(self, "writting PHP5 configuration into " + "/etc/php5/fpm/pool.d/debug.conf") + config.write(confifile) + + with open("/etc/php5/fpm/pool.d/debug.conf", + encoding='utf-8', mode='a') as myfile: + myfile.write("php_admin_value[xdebug.profiler_output_dir] " + "= /tmp/ \nphp_admin_value[xdebug.profiler_" + "output_name] = cachegrind.out.%p-%H-%R " + "\nphp_admin_flag[xdebug.profiler_enable" + "_trigger] = on \nphp_admin_flag[xdebug." + "profiler_enable] = off\n") + + # Disable xdebug + WOFileUtils.searchreplace(self, "/etc/php5/mods-available/" + "xdebug.ini", + "zend_extension", + ";zend_extension") + + # PHP and Debug pull configuration + if not os.path.exists('{0}22222/htdocs/fpm/status/' + .format(WOVariables.wo_webroot)): + Log.debug(self, 'Creating directory ' + '{0}22222/htdocs/fpm/status/ ' + .format(WOVariables.wo_webroot)) + os.makedirs('{0}22222/htdocs/fpm/status/' + .format(WOVariables.wo_webroot)) + open('{0}22222/htdocs/fpm/status/debug' + .format(WOVariables.wo_webroot), + encoding='utf-8', mode='a').close() + open('{0}22222/htdocs/fpm/status/php' + .format(WOVariables.wo_webroot), + encoding='utf-8', mode='a').close() + + # Write info.php + if not os.path.exists('{0}22222/htdocs/php/' + .format(WOVariables.wo_webroot)): + Log.debug(self, 'Creating directory ' + '{0}22222/htdocs/php/ ' + .format(WOVariables.wo_webroot)) + os.makedirs('{0}22222/htdocs/php' + .format(WOVariables.wo_webroot)) + + with open("{0}22222/htdocs/php/info.php" + .format(WOVariables.wo_webroot), + encoding='utf-8', mode='w') as myfile: + myfile.write("") + + WOFileUtils.chown(self, "{0}22222" + .format(WOVariables.wo_webroot), + WOVariables.wo_php_user, + WOVariables.wo_php_user, recursive=True) + + WOGit.add(self, ["/etc/php5"], msg="Adding PHP into Git") + WOService.restart_service(self, 'php5-fpm') + + + if (WOVariables.wo_platform_codename == 'trusty' or WOVariables.wo_platform_codename == 'xenial' or WOVariables.wo_platform_codename == 'bionic') and set(WOVariables.wo_php5_6).issubset(set(apt_packages)): + # Create log directories + if not os.path.exists('/var/log/php/5.6/'): + Log.debug(self, 'Creating directory /var/log/php/5.6/') + os.makedirs('/var/log/php/5.6/') + + # Parse etc/php/5.6/fpm/php.ini + config = configparser.ConfigParser() + Log.debug(self, "configuring php file /etc/php/5.6/fpm/php.ini") + config.read('/etc/php/5.6/fpm/php.ini') + config['PHP']['expose_php'] = 'Off' + config['PHP']['post_max_size'] = '100M' + config['PHP']['upload_max_filesize'] = '100M' + config['PHP']['max_execution_time'] = '300' + config['PHP']['date.timezone'] = WOVariables.wo_timezone + with open('/etc/php/5.6/fpm/php.ini', + encoding='utf-8', mode='w') as configfile: + Log.debug(self, "Writting php configuration into " + "/etc/php/5.6/fpm/php.ini") + config.write(configfile) + + # Parse /etc/php/5.6/fpm/php-fpm.conf + data = dict(pid="/run/php/php5.6-fpm.pid", error_log="/var/log/php/5.6/fpm.log", + include="/etc/php/5.6/fpm/pool.d/*.conf") + Log.debug(self, "writting php5 configuration into " + "/etc/php/5.6/fpm/php-fpm.conf") + wo_php_fpm = open('/etc/php/5.6/fpm/php-fpm.conf', + encoding='utf-8', mode='w') + self.app.render((data), 'php-fpm.mustache', out=wo_php_fpm) + wo_php_fpm.close() + + # Parse /etc/php/5.6/fpm/pool.d/www.conf + config = configparser.ConfigParser() + config.read_file(codecs.open('/etc/php/5.6/fpm/pool.d/www.conf', + "r", "utf8")) + config['www']['ping.path'] = '/ping' + config['www']['pm.status_path'] = '/status' + config['www']['pm.max_requests'] = '500' + config['www']['pm.max_children'] = '100' + config['www']['pm.start_servers'] = '20' + config['www']['pm.min_spare_servers'] = '10' + config['www']['pm.max_spare_servers'] = '30' + config['www']['request_terminate_timeout'] = '300' + config['www']['pm'] = 'ondemand' + config['www']['listen'] = '127.0.0.1:9000' + with codecs.open('/etc/php/5.6/fpm/pool.d/www.conf', + encoding='utf-8', mode='w') as configfile: + Log.debug(self, "writting PHP5 configuration into " + "/etc/php/5.6/fpm/pool.d/www.conf") + config.write(configfile) + + # Generate /etc/php/5.6/fpm/pool.d/debug.conf + WOFileUtils.copyfile(self, "/etc/php/5.6/fpm/pool.d/www.conf", + "/etc/php/5.6/fpm/pool.d/debug.conf") + WOFileUtils.searchreplace(self, "/etc/php/5.6/fpm/pool.d/" + "debug.conf", "[www]", "[debug]") + config = configparser.ConfigParser() + config.read('/etc/php/5.6/fpm/pool.d/debug.conf') + config['debug']['listen'] = '127.0.0.1:9001' + config['debug']['rlimit_core'] = 'unlimited' + config['debug']['slowlog'] = '/var/log/php/5.6/slow.log' + config['debug']['request_slowlog_timeout'] = '10s' + with open('/etc/php/5.6/fpm/pool.d/debug.conf', + encoding='utf-8', mode='w') as confifile: + Log.debug(self, "writting PHP5 configuration into " + "/etc/php/5.6/fpm/pool.d/debug.conf") + config.write(confifile) + + with open("/etc/php/5.6/fpm/pool.d/debug.conf", + encoding='utf-8', mode='a') as myfile: + myfile.write("php_admin_value[xdebug.profiler_output_dir] " + "= /tmp/ \nphp_admin_value[xdebug.profiler_" + "output_name] = cachegrind.out.%p-%H-%R " + "\nphp_admin_flag[xdebug.profiler_enable" + "_trigger] = on \nphp_admin_flag[xdebug." + "profiler_enable] = off\n") + + # Disable xdebug + if not WOShellExec.cmd_exec(self, "grep -q \';zend_extension\' /etc/php/5.6/mods-available/xdebug.ini"): + WOFileUtils.searchreplace(self, "/etc/php/5.6/mods-available/" + "xdebug.ini", + "zend_extension", + ";zend_extension") + + # PHP and Debug pull configuration + if not os.path.exists('{0}22222/htdocs/fpm/status/' + .format(WOVariables.wo_webroot)): + Log.debug(self, 'Creating directory ' + '{0}22222/htdocs/fpm/status/ ' + .format(WOVariables.wo_webroot)) + os.makedirs('{0}22222/htdocs/fpm/status/' + .format(WOVariables.wo_webroot)) + open('{0}22222/htdocs/fpm/status/debug' + .format(WOVariables.wo_webroot), + encoding='utf-8', mode='a').close() + open('{0}22222/htdocs/fpm/status/php' + .format(WOVariables.wo_webroot), + encoding='utf-8', mode='a').close() + + # Write info.php + if not os.path.exists('{0}22222/htdocs/php/' + .format(WOVariables.wo_webroot)): + Log.debug(self, 'Creating directory ' + '{0}22222/htdocs/php/ ' + .format(WOVariables.wo_webroot)) + os.makedirs('{0}22222/htdocs/php' + .format(WOVariables.wo_webroot)) + + with open("{0}22222/htdocs/php/info.php" + .format(WOVariables.wo_webroot), + encoding='utf-8', mode='w') as myfile: + myfile.write("") + + WOFileUtils.chown(self, "{0}22222" + .format(WOVariables.wo_webroot), + WOVariables.wo_php_user, + WOVariables.wo_php_user, recursive=True) + + WOGit.add(self, ["/etc/php"], msg="Adding PHP into Git") + WOService.restart_service(self, 'php5.6-fpm') + + #PHP7.0 configuration for debian + if (WOVariables.wo_platform_codename == 'jessie' ) and set(WOVariables.wo_php7_0).issubset(set(apt_packages)): + # Create log directories + if not os.path.exists('/var/log/php/7.0/'): + Log.debug(self, 'Creating directory /var/log/php/7.0/') + os.makedirs('/var/log/php/7.0/') + + # Parse etc/php/7.0/fpm/php.ini + config = configparser.ConfigParser() + Log.debug(self, "configuring php file /etc/php/7.0/fpm/php.ini") + config.read('/etc/php/7.0/fpm/php.ini') + config['PHP']['expose_php'] = 'Off' + config['PHP']['post_max_size'] = '100M' + config['PHP']['upload_max_filesize'] = '100M' + config['PHP']['max_execution_time'] = '300' + config['PHP']['date.timezone'] = WOVariables.wo_timezone + with open('/etc/php/7.0/fpm/php.ini', + encoding='utf-8', mode='w') as configfile: + Log.debug(self, "Writting php configuration into " + "/etc/php/7.0/fpm/php.ini") + config.write(configfile) + + # Parse /etc/php/7.0/fpm/php-fpm.conf + data = dict(pid="/run/php/php7.0-fpm.pid", error_log="/var/log/php7.0-fpm.log", + include="/etc/php/7.0/fpm/pool.d/*.conf") + Log.debug(self, "writting php 7.0 configuration into " + "/etc/php/7.0/fpm/php-fpm.conf") + wo_php_fpm = open('/etc/php/7.0/fpm/php-fpm.conf', + encoding='utf-8', mode='w') + self.app.render((data), 'php-fpm.mustache', out=wo_php_fpm) + wo_php_fpm.close() + + # Parse /etc/php/7.0/fpm/pool.d/www.conf + config = configparser.ConfigParser() + config.read_file(codecs.open('/etc/php/7.0/fpm/pool.d/www.conf', + "r", "utf8")) + config['www']['ping.path'] = '/ping' + config['www']['pm.status_path'] = '/status' + config['www']['pm.max_requests'] = '500' + config['www']['pm.max_children'] = '100' + config['www']['pm.start_servers'] = '20' + config['www']['pm.min_spare_servers'] = '10' + config['www']['pm.max_spare_servers'] = '30' + config['www']['request_terminate_timeout'] = '300' + config['www']['pm'] = 'ondemand' + config['www']['listen'] = '127.0.0.1:9070' + with codecs.open('/etc/php/7.0/fpm/pool.d/www.conf', + encoding='utf-8', mode='w') as configfile: + Log.debug(self, "writting PHP5 configuration into " + "/etc/php/7.0/fpm/pool.d/www.conf") + config.write(configfile) + + # Generate /etc/php/7.0/fpm/pool.d/debug.conf + WOFileUtils.copyfile(self, "/etc/php/7.0/fpm/pool.d/www.conf", + "/etc/php/7.0/fpm/pool.d/debug.conf") + WOFileUtils.searchreplace(self, "/etc/php/7.0/fpm/pool.d/" + "debug.conf", "[www]", "[debug]") + config = configparser.ConfigParser() + config.read('/etc/php/7.0/fpm/pool.d/debug.conf') + config['debug']['listen'] = '127.0.0.1:9170' + config['debug']['rlimit_core'] = 'unlimited' + config['debug']['slowlog'] = '/var/log/php/7.0/slow.log' + config['debug']['request_slowlog_timeout'] = '10s' + with open('/etc/php/7.0/fpm/pool.d/debug.conf', + encoding='utf-8', mode='w') as confifile: + Log.debug(self, "writting PHP5 configuration into " + "/etc/php/7.0/fpm/pool.d/debug.conf") + config.write(confifile) + + with open("/etc/php/7.0/fpm/pool.d/debug.conf", + encoding='utf-8', mode='a') as myfile: + myfile.write("php_admin_value[xdebug.profiler_output_dir] " + "= /tmp/ \nphp_admin_value[xdebug.profiler_" + "output_name] = cachegrind.out.%p-%H-%R " + "\nphp_admin_flag[xdebug.profiler_enable" + "_trigger] = on \nphp_admin_flag[xdebug." + "profiler_enable] = off\n") + + # Disable xdebug + if not WOShellExec.cmd_exec(self, "grep -q \';zend_extension\' /etc/php/7.0/mods-available/xdebug.ini"): + WOFileUtils.searchreplace(self, "/etc/php/7.0/mods-available/" + "xdebug.ini", + "zend_extension", + ";zend_extension") + + # PHP and Debug pull configuration + if not os.path.exists('{0}22222/htdocs/fpm/status/' + .format(WOVariables.wo_webroot)): + Log.debug(self, 'Creating directory ' + '{0}22222/htdocs/fpm/status/ ' + .format(WOVariables.wo_webroot)) + os.makedirs('{0}22222/htdocs/fpm/status/' + .format(WOVariables.wo_webroot)) + open('{0}22222/htdocs/fpm/status/debug' + .format(WOVariables.wo_webroot), + encoding='utf-8', mode='a').close() + open('{0}22222/htdocs/fpm/status/php' + .format(WOVariables.wo_webroot), + encoding='utf-8', mode='a').close() + + # Write info.php + if not os.path.exists('{0}22222/htdocs/php/' + .format(WOVariables.wo_webroot)): + Log.debug(self, 'Creating directory ' + '{0}22222/htdocs/php/ ' + .format(WOVariables.wo_webroot)) + os.makedirs('{0}22222/htdocs/php' + .format(WOVariables.wo_webroot)) + + with open("{0}22222/htdocs/php/info.php" + .format(WOVariables.wo_webroot), + encoding='utf-8', mode='w') as myfile: + myfile.write("") + + WOFileUtils.chown(self, "{0}22222" + .format(WOVariables.wo_webroot), + WOVariables.wo_php_user, + WOVariables.wo_php_user, recursive=True) + + WOGit.add(self, ["/etc/php"], msg="Adding PHP into Git") + WOService.restart_service(self, 'php7.0-fpm') + + #preconfiguration for php7.0 + if (WOVariables.wo_platform_codename == 'trusty' or WOVariables.wo_platform_codename == 'xenial' or WOVariables.wo_platform_codename == 'bionic') and set(WOVariables.wo_php7_0).issubset(set(apt_packages)): + # Create log directories + if not os.path.exists('/var/log/php/7.0/'): + Log.debug(self, 'Creating directory /var/log/php/7.0/') + os.makedirs('/var/log/php/7.0/') + + # Parse etc/php/7.0/fpm/php.ini + config = configparser.ConfigParser() + Log.debug(self, "configuring php file /etc/php/7.0/fpm/php.ini") + config.read('/etc/php/7.0/fpm/php.ini') + config['PHP']['expose_php'] = 'Off' + config['PHP']['post_max_size'] = '100M' + config['PHP']['upload_max_filesize'] = '100M' + config['PHP']['max_execution_time'] = '300' + config['PHP']['date.timezone'] = WOVariables.wo_timezone + with open('/etc/php/7.0/fpm/php.ini', + encoding='utf-8', mode='w') as configfile: + Log.debug(self, "Writting php configuration into " + "/etc/php/7.0/fpm/php.ini") + config.write(configfile) + + # Parse /etc/php/7.0/fpm/php-fpm.conf + data = dict(pid="/run/php/php7.0-fpm.pid", error_log="/var/log/php/7.0/fpm.log", + include="/etc/php/7.0/fpm/pool.d/*.conf") + Log.debug(self, "writting php 7.0 configuration into " + "/etc/php/7.0/fpm/php-fpm.conf") + wo_php_fpm = open('/etc/php/7.0/fpm/php-fpm.conf', + encoding='utf-8', mode='w') + self.app.render((data), 'php-fpm.mustache', out=wo_php_fpm) + wo_php_fpm.close() + + # Parse /etc/php/7.0/fpm/pool.d/www.conf + config = configparser.ConfigParser() + config.read_file(codecs.open('/etc/php/7.0/fpm/pool.d/www.conf', + "r", "utf8")) + config['www']['ping.path'] = '/ping' + config['www']['pm.status_path'] = '/status' + config['www']['pm.max_requests'] = '500' + config['www']['pm.max_children'] = '100' + config['www']['pm.start_servers'] = '20' + config['www']['pm.min_spare_servers'] = '10' + config['www']['pm.max_spare_servers'] = '30' + config['www']['request_terminate_timeout'] = '300' + config['www']['pm'] = 'ondemand' + config['www']['listen'] = '127.0.0.1:9070' + with codecs.open('/etc/php/7.0/fpm/pool.d/www.conf', + encoding='utf-8', mode='w') as configfile: + Log.debug(self, "writting PHP5 configuration into " + "/etc/php/7.0/fpm/pool.d/www.conf") + config.write(configfile) + + # Generate /etc/php/7.0/fpm/pool.d/debug.conf + WOFileUtils.copyfile(self, "/etc/php/7.0/fpm/pool.d/www.conf", + "/etc/php/7.0/fpm/pool.d/debug.conf") + WOFileUtils.searchreplace(self, "/etc/php/7.0/fpm/pool.d/" + "debug.conf", "[www]", "[debug]") + config = configparser.ConfigParser() + config.read('/etc/php/7.0/fpm/pool.d/debug.conf') + config['debug']['listen'] = '127.0.0.1:9170' + config['debug']['rlimit_core'] = 'unlimited' + config['debug']['slowlog'] = '/var/log/php/7.0/slow.log' + config['debug']['request_slowlog_timeout'] = '10s' + with open('/etc/php/7.0/fpm/pool.d/debug.conf', + encoding='utf-8', mode='w') as confifile: + Log.debug(self, "writting PHP5 configuration into " + "/etc/php/7.0/fpm/pool.d/debug.conf") + config.write(confifile) + + with open("/etc/php/7.0/fpm/pool.d/debug.conf", + encoding='utf-8', mode='a') as myfile: + myfile.write("php_admin_value[xdebug.profiler_output_dir] " + "= /tmp/ \nphp_admin_value[xdebug.profiler_" + "output_name] = cachegrind.out.%p-%H-%R " + "\nphp_admin_flag[xdebug.profiler_enable" + "_trigger] = on \nphp_admin_flag[xdebug." + "profiler_enable] = off\n") + + # Disable xdebug + if not WOShellExec.cmd_exec(self, "grep -q \';zend_extension\' /etc/php/7.0/mods-available/xdebug.ini"): + WOFileUtils.searchreplace(self, "/etc/php/7.0/mods-available/" + "xdebug.ini", + "zend_extension", + ";zend_extension") + + # PHP and Debug pull configuration + if not os.path.exists('{0}22222/htdocs/fpm/status/' + .format(WOVariables.wo_webroot)): + Log.debug(self, 'Creating directory ' + '{0}22222/htdocs/fpm/status/ ' + .format(WOVariables.wo_webroot)) + os.makedirs('{0}22222/htdocs/fpm/status/' + .format(WOVariables.wo_webroot)) + open('{0}22222/htdocs/fpm/status/debug' + .format(WOVariables.wo_webroot), + encoding='utf-8', mode='a').close() + open('{0}22222/htdocs/fpm/status/php' + .format(WOVariables.wo_webroot), + encoding='utf-8', mode='a').close() + + # Write info.php + if not os.path.exists('{0}22222/htdocs/php/' + .format(WOVariables.wo_webroot)): + Log.debug(self, 'Creating directory ' + '{0}22222/htdocs/php/ ' + .format(WOVariables.wo_webroot)) + os.makedirs('{0}22222/htdocs/php' + .format(WOVariables.wo_webroot)) + + with open("{0}22222/htdocs/php/info.php" + .format(WOVariables.wo_webroot), + encoding='utf-8', mode='w') as myfile: + myfile.write("") + + WOFileUtils.chown(self, "{0}22222" + .format(WOVariables.wo_webroot), + WOVariables.wo_php_user, + WOVariables.wo_php_user, recursive=True) + + WOGit.add(self, ["/etc/php"], msg="Adding PHP into Git") + WOService.restart_service(self, 'php7.0-fpm') + + + + if set(WOVariables.wo_mysql).issubset(set(apt_packages)): + # TODO: Currently we are using, we need to remove it in future + # config = configparser.ConfigParser() + # config.read('/etc/mysql/my.cnf') + # config['mysqld']['wait_timeout'] = 30 + # config['mysqld']['interactive_timeout'] = 60 + # config['mysqld']['performance_schema'] = 0 + # with open('/etc/mysql/my.cnf', 'w') as configfile: + # config.write(configfile) + if not os.path.isfile("/etc/mysql/my.cnf"): + config = ("[mysqld]\nwait_timeout = 30\n" + "interactive_timeout=60\nperformance_schema = 0" + "\nquery_cache_type = 1") + config_file = open("/etc/mysql/my.cnf", + encoding='utf-8', mode='w') + config_file.write(config) + config_file.close() + else: + try: + WOShellExec.cmd_exec(self, "sed -i \"/#max_conn" + "ections/a wait_timeout = 30 \\n" + "interactive_timeout = 60 \\n" + "performance_schema = 0\\n" + "query_cache_type = 1 \" " + "/etc/mysql/my.cnf") + except CommandExecutionError as e: + Log.error(self, "Unable to update MySQL file") + + # Set MySQLTuner permission + WOFileUtils.chmod(self, "/usr/bin/mysqltuner", 0o775) + + WOGit.add(self, ["/etc/mysql"], msg="Adding MySQL into Git") + WOService.reload_service(self, 'mysql') + + if len(packages): + if any('/usr/bin/wp' == x[1] for x in packages): + Log.debug(self, "Setting Privileges to /usr/bin/wp file ") + WOFileUtils.chmod(self, "/usr/bin/wp", 0o775) + + if any('/tmp/pma.tar.gz' == x[1] + for x in packages): + WOExtract.extract(self, '/tmp/pma.tar.gz', '/tmp/') + Log.debug(self, 'Extracting file /tmp/pma.tar.gz to ' + 'location /tmp/') + if not os.path.exists('{0}22222/htdocs/db' + .format(WOVariables.wo_webroot)): + Log.debug(self, "Creating new directory " + "{0}22222/htdocs/db" + .format(WOVariables.wo_webroot)) + os.makedirs('{0}22222/htdocs/db' + .format(WOVariables.wo_webroot)) + shutil.move('/tmp/phpmyadmin-STABLE/', + '{0}22222/htdocs/db/pma/' + .format(WOVariables.wo_webroot)) + shutil.copyfile('{0}22222/htdocs/db/pma/config.sample.inc.php' + .format(WOVariables.wo_webroot), + '{0}22222/htdocs/db/pma/config.inc.php' + .format(WOVariables.wo_webroot)) + Log.debug(self, 'Setting Blowfish Secret Key FOR COOKIE AUTH to ' + '{0}22222/htdocs/db/pma/config.inc.php file ' + .format(WOVariables.wo_webroot)) + blowfish_key = ''.join([random.choice + (string.ascii_letters + string.digits) + for n in range(10)]) + WOFileUtils.searchreplace(self, + '{0}22222/htdocs/db/pma/config.inc.php' + .format(WOVariables.wo_webroot), + "$cfg[\'blowfish_secret\'] = \'\';","$cfg[\'blowfish_secret\'] = \'{0}\';" + .format(blowfish_key)) + Log.debug(self, 'Setting HOST Server For Mysql to ' + '{0}22222/htdocs/db/pma/config.inc.php file ' + .format(WOVariables.wo_webroot)) + WOFileUtils.searchreplace(self, + '{0}22222/htdocs/db/pma/config.inc.php' + .format(WOVariables.wo_webroot), + "$cfg[\'Servers\'][$i][\'host\'] = \'localhost\';","$cfg[\'Servers\'][$i][\'host\'] = \'{0}\';" + .format(WOVariables.wo_mysql_host)) + Log.debug(self, 'Setting Privileges of webroot permission to ' + '{0}22222/htdocs/db/pma file ' + .format(WOVariables.wo_webroot)) + WOFileUtils.chown(self, '{0}22222' + .format(WOVariables.wo_webroot), + WOVariables.wo_php_user, + WOVariables.wo_php_user, + recursive=True) + if any('/tmp/memcache.tar.gz' == x[1] + for x in packages): + Log.debug(self, "Extracting memcache.tar.gz to location" + " {0}22222/htdocs/cache/memcache " + .format(WOVariables.wo_webroot)) + WOExtract.extract(self, '/tmp/memcache.tar.gz', + '{0}22222/htdocs/cache/memcache' + .format(WOVariables.wo_webroot)) + Log.debug(self, "Setting Privileges to " + "{0}22222/htdocs/cache/memcache file" + .format(WOVariables.wo_webroot)) + WOFileUtils.chown(self, '{0}22222' + .format(WOVariables.wo_webroot), + WOVariables.wo_php_user, + WOVariables.wo_php_user, + recursive=True) + + if any('/tmp/webgrind.tar.gz' == x[1] + for x in packages): + Log.debug(self, "Extracting file webgrind.tar.gz to " + "location /tmp/ ") + WOExtract.extract(self, '/tmp/webgrind.tar.gz', '/tmp/') + if not os.path.exists('{0}22222/htdocs/php' + .format(WOVariables.wo_webroot)): + Log.debug(self, "Creating directroy " + "{0}22222/htdocs/php" + .format(WOVariables.wo_webroot)) + os.makedirs('{0}22222/htdocs/php' + .format(WOVariables.wo_webroot)) + shutil.move('/tmp/webgrind-master/', + '{0}22222/htdocs/php/webgrind' + .format(WOVariables.wo_webroot)) + + WOFileUtils.searchreplace(self, "{0}22222/htdocs/php/webgrind/" + "config.php" + .format(WOVariables.wo_webroot), + "/usr/local/bin/dot", "/usr/bin/dot") + WOFileUtils.searchreplace(self, "{0}22222/htdocs/php/webgrind/" + "config.php" + .format(WOVariables.wo_webroot), + "Europe/Copenhagen", + WOVariables.wo_timezone) + + WOFileUtils.searchreplace(self, "{0}22222/htdocs/php/webgrind/" + "config.php" + .format(WOVariables.wo_webroot), + "90", "100") + + Log.debug(self, "Setting Privileges of webroot permission to " + "{0}22222/htdocs/php/webgrind/ file " + .format(WOVariables.wo_webroot)) + WOFileUtils.chown(self, '{0}22222' + .format(WOVariables.wo_webroot), + WOVariables.wo_php_user, + WOVariables.wo_php_user, + recursive=True) + + if any('/tmp/anemometer.tar.gz' == x[1] + for x in packages): + Log.debug(self, "Extracting file anemometer.tar.gz to " + "location /tmp/ ") + WOExtract.extract(self, '/tmp/anemometer.tar.gz', '/tmp/') + if not os.path.exists('{0}22222/htdocs/db/' + .format(WOVariables.wo_webroot)): + Log.debug(self, "Creating directory") + os.makedirs('{0}22222/htdocs/db/' + .format(WOVariables.wo_webroot)) + shutil.move('/tmp/Anemometer-master', + '{0}22222/htdocs/db/anemometer' + .format(WOVariables.wo_webroot)) + chars = ''.join(random.sample(string.ascii_letters, 8)) + try: + WOShellExec.cmd_exec(self, 'mysql < {0}22222/htdocs/db' + '/anemometer/install.sql' + .format(WOVariables.wo_webroot)) + except CommandExecutionError as e: + raise SiteError("Unable to import Anemometer database") + + WOMysql.execute(self, 'grant select on *.* to \'anemometer\'' + '@\'{0}\' IDENTIFIED' + ' BY \'{1}\''.format(self.app.config.get('mysql', + 'grant-host'),chars)) + Log.debug(self, "grant all on slow-query-log.*" + " to anemometer@root_user IDENTIFIED BY password ") + WOMysql.execute(self, 'grant all on slow_query_log.* to' + '\'anemometer\'@\'{0}\' IDENTIFIED' + ' BY \'{1}\''.format(self.app.config.get( + 'mysql', 'grant-host'), + chars), + errormsg="cannot grant priviledges", log=False) + + # Custom Anemometer configuration + Log.debug(self, "configration Anemometer") + data = dict(host=WOVariables.wo_mysql_host, port='3306', + user='anemometer', password=chars) + wo_anemometer = open('{0}22222/htdocs/db/anemometer' + '/conf/config.inc.php' + .format(WOVariables.wo_webroot), + encoding='utf-8', mode='w') + self.app.render((data), 'anemometer.mustache', + out=wo_anemometer) + wo_anemometer.close() + + if any('/usr/bin/pt-query-advisor' == x[1] + for x in packages): + WOFileUtils.chmod(self, "/usr/bin/pt-query-advisor", 0o775) + + if any('/tmp/pra.tar.gz' == x[1] + for x in packages): + Log.debug(self, 'Extracting file /tmp/pra.tar.gz to ' + 'loaction /tmp/') + WOExtract.extract(self, '/tmp/pra.tar.gz', '/tmp/') + if not os.path.exists('{0}22222/htdocs/cache/redis' + .format(WOVariables.wo_webroot)): + Log.debug(self, "Creating new directory " + "{0}22222/htdocs/cache/redis" + .format(WOVariables.wo_webroot)) + os.makedirs('{0}22222/htdocs/cache/redis' + .format(WOVariables.wo_webroot)) + shutil.move('/tmp/phpRedisAdmin-master/', + '{0}22222/htdocs/cache/redis/phpRedisAdmin' + .format(WOVariables.wo_webroot)) + + Log.debug(self, 'Extracting file /tmp/predis.tar.gz to ' + 'loaction /tmp/') + WOExtract.extract(self, '/tmp/predis.tar.gz', '/tmp/') + shutil.move('/tmp/predis-1.0.1/', + '{0}22222/htdocs/cache/redis/phpRedisAdmin/vendor' + .format(WOVariables.wo_webroot)) + + Log.debug(self, 'Setting Privileges of webroot permission to ' + '{0}22222/htdocs/cache/ file ' + .format(WOVariables.wo_webroot)) + WOFileUtils.chown(self, '{0}22222' + .format(WOVariables.wo_webroot), + WOVariables.wo_php_user, + WOVariables.wo_php_user, + recursive=True) + + @expose(help="Install packages") + def install(self, packages=[], apt_packages=[], disp_msg=True): + """Start installation of packages""" + self.msg = [] + try: + # Default action for stack installation + if ((not self.app.pargs.web) and (not self.app.pargs.admin) and + (not self.app.pargs.nginx) and (not self.app.pargs.php) and + (not self.app.pargs.mysql) and (not self.app.pargs.wpcli) and + (not self.app.pargs.phpmyadmin) and (not self.app.pargs.hhvm) and + (not self.app.pargs.adminer) and (not self.app.pargs.utils) and + (not self.app.pargs.redis) and (not self.app.pargs.phpredisadmin) and + (not self.app.pargs.php7)): + self.app.pargs.web = True + self.app.pargs.admin = True + + if self.app.pargs.all: + self.app.pargs.web = True + self.app.pargs.admin = True + + if self.app.pargs.web: + self.app.pargs.nginx = True + self.app.pargs.php = True + self.app.pargs.mysql = True + self.app.pargs.wpcli = True + + if self.app.pargs.admin: + self.app.pargs.nginx = True + self.app.pargs.php = True + self.app.pargs.mysql = True + self.app.pargs.adminer = True + self.app.pargs.phpmyadmin = True + self.app.pargs.utils = True + + if self.app.pargs.redis: + if not WOAptGet.is_installed(self, 'redis-server'): + apt_packages = apt_packages + WOVariables.wo_redis + self.app.pargs.php = True + else: + Log.info(self, "Redis already installed") + + if self.app.pargs.nginx: + Log.debug(self, "Setting apt_packages variable for Nginx") + + if not (WOAptGet.is_installed(self, 'nginx-custom')): + if not (WOAptGet.is_installed(self, 'nginx-plus') or WOAptGet.is_installed(self, 'nginx')): + apt_packages = apt_packages + WOVariables.wo_nginx + else: + if WOAptGet.is_installed(self, 'nginx-plus'): + Log.info(self, "NGINX PLUS Detected ...") + apt = ["nginx-plus"] + WOVariables.wo_nginx + self.post_pref(apt, packages) + elif WOAptGet.is_installed(self, 'nginx'): + Log.info(self, "WordOps detected an already installed nginx package." + "It may or may not have required modules.\n") + apt = ["nginx"] + WOVariables.wo_nginx + self.post_pref(apt, packages) + else: + Log.debug(self, "Nginx Stable already installed") + + if self.app.pargs.php: + Log.debug(self, "Setting apt_packages variable for PHP") + if not (WOAptGet.is_installed(self, 'php5-fpm') or WOAptGet.is_installed(self, 'php5.6-fpm')): + if (WOVariables.wo_platform_codename == 'trusty' or WOVariables.wo_platform_codename == 'xenial' or WOVariables.wo_platform_codename == 'bionic'): + apt_packages = apt_packages + WOVariables.wo_php7_2 + WOVariables.wo_php_extra + else: + apt_packages = apt_packages + WOVariables.wo_php + else: + Log.debug(self, "PHP already installed") + Log.info(self, "PHP already installed") + + #PHP 7.0 for Debian (jessie+) + if self.app.pargs.php7 and WOVariables.wo_platform_distro == 'debian': + if (WOVariables.wo_platform_codename == 'jessie'): + Log.debug(self, "Setting apt_packages variable for PHP 7.0") + if not WOAptGet.is_installed(self, 'php7.0-fpm') : + apt_packages = apt_packages + WOVariables.wo_php7_0 + if not WOAptGet.is_installed(self, 'php5-fpm'): + apt_packages = apt_packages + WOVariables.wo_php + else: + Log.debug(self, "PHP 7.0 already installed") + Log.info(self, "PHP 7.0 already installed") + else: + Log.debug(self, "PHP 7.0 Not Available for your Distribution") + Log.info(self, "PHP 7.0 Not Available for your Distribution") + + #PHP 7.0 for Ubuntu + if self.app.pargs.php7 and not WOVariables.wo_platform_distro == 'debian': + if (WOVariables.wo_platform_codename == 'trusty' or WOVariables.wo_platform_codename == 'xenial' or WOVariables.wo_platform_codename == 'bionic'): + Log.debug(self, "Setting apt_packages variable for PHP 7.2") + if not WOAptGet.is_installed(self, 'php7.2-fpm') : + apt_packages = apt_packages + WOVariables.wo_php7_2 + WOVariables.wo_php_extra + if not WOAptGet.is_installed(self, 'php5.6-fpm'): + apt_packages = apt_packages + WOVariables.wo_php5_6 + WOVariables.wo_php_extra + else: + Log.debug(self, "PHP 7.2 already installed") + Log.info(self, "PHP 7.2 already installed") + else: + Log.debug(self, "Unfortunately PHP 7.2 is not available for your Ubuntu or Debian version.") + Log.info(self, "Unfortunately PHP 7.2 is not available for your Ubuntu or Debian version.") + + + if self.app.pargs.hhvm: + Log.debug(self, "Setting apt packages variable for HHVM") + if platform.architecture()[0] is '32bit': + Log.error(self, "HHVM is not supported by 32bit system") + if not WOAptGet.is_installed(self, 'hhvm'): + apt_packages = apt_packages + WOVariables.wo_hhvm + else: + Log.debug(self, "HHVM already installed") + Log.info(self, "HHVM already installed") + + if self.app.pargs.mysql: + Log.debug(self, "Setting apt_packages variable for MySQL") + if not WOShellExec.cmd_exec(self, "mysqladmin ping"): + apt_packages = apt_packages + WOVariables.wo_mysql + packages = packages + [["https://raw." + "githubusercontent.com/" + "major/MySQLTuner-perl" + "/master/mysqltuner.pl", + "/usr/bin/mysqltuner", + "MySQLTuner"]] + + else: + Log.debug(self, "MySQL connection is already alive") + Log.info(self, "MySQL connection is already alive") + + if self.app.pargs.wpcli: + Log.debug(self, "Setting packages variable for WP-CLI") + if not WOShellExec.cmd_exec(self, "which wp"): + packages = packages + [["https://github.com/wp-cli/wp-cli/" + "releases/download/v{0}/" + "wp-cli-{0}.phar" + "".format(WOVariables.wo_wp_cli), + "/usr/bin/wp", + "WP-CLI"]] + else: + Log.debug(self, "WP-CLI is already installed") + Log.info(self, "WP-CLI is already installed") + if self.app.pargs.phpmyadmin: + Log.debug(self, "Setting packages varible for phpMyAdmin ") + packages = packages + [["https://github.com/phpmyadmin/" + "phpmyadmin/archive/STABLE.tar.gz", + "/tmp/pma.tar.gz", "phpMyAdmin"]] + + if self.app.pargs.phpredisadmin: + Log.debug(self, "Setting packages varible for phpRedisAdmin") + packages = packages + [["https://github.com/ErikDubbelboer/" + "phpRedisAdmin/archive/master.tar.gz", + "/tmp/pra.tar.gz","phpRedisAdmin"], + ["https://github.com/nrk/predis/" + "archive/v1.0.1.tar.gz", + "/tmp/predis.tar.gz", "Predis"]] + + if self.app.pargs.adminer: + Log.debug(self, "Setting packages variable for Adminer ") + packages = packages + [["https://www.adminer.org/static/download/" + "{0}/adminer-{0}.php" + "".format(WOVariables.wo_adminer), + "{0}22222/" + "htdocs/db/adminer/index.php" + .format(WOVariables.wo_webroot), + "Adminer"]] + + if self.app.pargs.utils: + Log.debug(self, "Setting packages variable for utils") + packages = packages + [["https://storage.googleapis.com/google-code-archive-downloads/" + "v2/code.google.com/phpmemcacheadmin/" + "phpMemcachedAdmin-1.2.2-r262.tar.gz", '/tmp/memcache.tar.gz', + 'phpMemcachedAdmin'], + ["https://raw.githubusercontent.com" + "/rtCamp/eeadmin/master/cache/nginx/" + "clean.php", + "{0}22222/htdocs/cache/" + "nginx/clean.php" + .format(WOVariables.wo_webroot), + "clean.php"], + ["https://raw.github.com/rlerdorf/" + "opcache-status/master/opcache.php", + "{0}22222/htdocs/cache/" + "opcache/opcache.php" + .format(WOVariables.wo_webroot), + "opcache.php"], + ["https://raw.github.com/amnuts/" + "opcache-gui/master/index.php", + "{0}22222/htdocs/" + "cache/opcache/opgui.php" + .format(WOVariables.wo_webroot), + "Opgui"], + ["https://gist.github.com/ck-on/4959032" + "/raw/0b871b345fd6cfcd6d2be030c1f33d1" + "ad6a475cb/ocp.php", + "{0}22222/htdocs/cache/" + "opcache/ocp.php" + .format(WOVariables.wo_webroot), + "OCP.php"], + ["https://github.com/jokkedk/webgrind/" + "archive/master.tar.gz", + '/tmp/webgrind.tar.gz', 'Webgrind'], + ["http://bazaar.launchpad.net/~" + "percona-toolkit-dev/percona-toolkit/" + "2.1/download/head:/ptquerydigest-" + "20110624220137-or26tn4" + "expb9ul2a-16/pt-query-digest", + "/usr/bin/pt-query-advisor", + "pt-query-advisor"], + ["https://github.com/box/Anemometer/" + "archive/master.tar.gz", + '/tmp/anemometer.tar.gz', 'Anemometer'] + ] + except Exception as e: + pass + + if len(apt_packages) or len(packages): + Log.debug(self, "Calling pre_pref") + self.pre_pref(apt_packages) + if len(apt_packages): + WOSwap.add(self) + Log.info(self, "Updating apt-cache, please wait...") + WOAptGet.update(self) + Log.info(self, "Installing packages, please wait...") + WOAptGet.install(self, apt_packages) + if len(packages): + Log.debug(self, "Downloading following: {0}".format(packages)) + WODownload.download(self, packages) + Log.debug(self, "Calling post_pref") + self.post_pref(apt_packages, packages) + if 'redis-server' in apt_packages: + # set redis.conf parameter + # set maxmemory 10% for ram below 512MB and 20% for others + # set maxmemory-policy allkeys-lru + if os.path.isfile("/etc/redis/redis.conf"): + if WOVariables.wo_ram < 512: + Log.debug(self, "Setting maxmemory variable to {0} in redis.conf" + .format(int(WOVariables.wo_ram*1024*1024*0.1))) + WOShellExec.cmd_exec(self, "sed -i 's/# maxmemory /maxmemory {0}/' /etc/redis/redis.conf" + .format(int(WOVariables.wo_ram*1024*1024*0.1))) + Log.debug(self, "Setting maxmemory-policy variable to allkeys-lru in redis.conf") + WOShellExec.cmd_exec(self, "sed -i 's/# maxmemory-policy.*/maxmemory-policy allkeys-lru/' " + "/etc/redis/redis.conf") + WOService.restart_service(self, 'redis-server') + else: + Log.debug(self, "Setting maxmemory variable to {0} in redis.conf" + .format(int(WOVariables.wo_ram*1024*1024*0.2))) + WOShellExec.cmd_exec(self, "sed -i 's/# maxmemory /maxmemory {0}/' /etc/redis/redis.conf" + .format(int(WOVariables.wo_ram*1024*1024*0.2))) + Log.debug(self, "Setting maxmemory-policy variable to allkeys-lru in redis.conf") + WOShellExec.cmd_exec(self, "sed -i 's/# maxmemory-policy.*/maxmemory-policy allkeys-lru/' " + "/etc/redis/redis.conf") + WOService.restart_service(self, 'redis-server') + if disp_msg: + if len(self.msg): + for msg in self.msg: + Log.info(self, Log.ENDC + msg) + Log.info(self, "Successfully installed packages") + else: + return self.msg + + @expose(help="Remove packages") + def remove(self): + """Start removal of packages""" + apt_packages = [] + packages = [] + + if ((not self.app.pargs.web) and (not self.app.pargs.admin) and + (not self.app.pargs.nginx) and (not self.app.pargs.php) and + (not self.app.pargs.php7) and (not self.app.pargs.mysql) and + (not self.app.pargs.wpcli) and (not self.app.pargs.phpmyadmin) and + (not self.app.pargs.hhvm) and (not self.app.pargs.adminer) and + (not self.app.pargs.utils) and (not self.app.pargs.all) and + (not self.app.pargs.redis) and (not self.app.pargs.phpredisadmin)): + self.app.pargs.web = True + self.app.pargs.admin = True + + if self.app.pargs.all: + self.app.pargs.web = True + self.app.pargs.admin = True + if (WOVariables.wo_platform_codename == 'trusty' or WOVariables.wo_platform_codename == 'xenial' or WOVariables.wo_platform_codename == 'bionic'): + self.app.pargs.php7 = True + + if self.app.pargs.web: + self.app.pargs.nginx = True + self.app.pargs.php = True + self.app.pargs.mysql = True + self.app.pargs.wpcli = True + + if self.app.pargs.admin: + self.app.pargs.adminer = True + self.app.pargs.phpmyadmin = True + self.app.pargs.utils = True + + if self.app.pargs.nginx: + if WOAptGet.is_installed(self, 'nginx-custom'): + Log.debug(self, "Removing apt_packages variable of Nginx") + apt_packages = apt_packages + WOVariables.wo_nginx + else: + Log.error(self,"Cannot Remove! Nginx Stable version not found.") + + if self.app.pargs.php: + Log.debug(self, "Removing apt_packages variable of PHP") + if (WOVariables.wo_platform_codename == 'trusty' or WOVariables.wo_platform_codename == 'xenial' or WOVariables.wo_platform_codename == 'bionic'): + apt_packages = apt_packages + WOVariables.wo_php7_2 + if not WOAptGet.is_installed(self, 'php7.2-fpm'): + apt_packages = apt_packages + WOVariables.wo_php_extra + else: + apt_packages = apt_packages + WOVariables.wo_php + + #PHP7.0 for debian(jessie+) + if self.app.pargs.php7: + if (WOVariables.wo_platform_codename == 'jessie'): + Log.debug(self, "Removing apt_packages variable of PHP 7.0") + apt_packages = apt_packages + WOVariables.wo_php7_0 + if not WOAptGet.is_installed(self, 'php5-fpm'): + apt_packages = apt_packages + WOVariables.wo_php_extra + else: + Log.info(self,"PHP 7.0 not supported.") + + if self.app.pargs.php7: + if (WOVariables.wo_platform_codename == 'trusty' or WOVariables.wo_platform_codename == 'xenial' or WOVariables.wo_platform_codename == 'bionic'): + Log.debug(self, "Removing apt_packages variable of PHP 7.0") + apt_packages = apt_packages + WOVariables.wo_php7_0 + if not WOAptGet.is_installed(self, 'php5.6-fpm'): + apt_packages = apt_packages + WOVariables.wo_php_extra + else: + Log.info(self,"PHP 7.0 not supported.") + + if self.app.pargs.hhvm: + if WOAptGet.is_installed(self, 'hhvm'): + Log.debug(self, "Removing apt_packages variable of HHVM") + apt_packages = apt_packages + WOVariables.wo_hhvm + + if self.app.pargs.redis: + Log.debug(self, "Remove apt_packages variable of Redis") + apt_packages = apt_packages + WOVariables.wo_redis + + if self.app.pargs.mysql: + Log.debug(self, "Removing apt_packages variable of MySQL") + apt_packages = apt_packages + WOVariables.wo_mysql + packages = packages + ['/usr/bin/mysqltuner'] + + if self.app.pargs.wpcli: + Log.debug(self, "Removing package variable of WPCLI ") + if os.path.isfile('/usr/bin/wp'): + packages = packages + ['/usr/bin/wp'] + else: + Log.warn(self, "WP-CLI is not installed with WordOps") + if self.app.pargs.phpmyadmin: + Log.debug(self, "Removing package variable of phpMyAdmin ") + packages = packages + ['{0}22222/htdocs/db/pma' + .format(WOVariables.wo_webroot)] + if self.app.pargs.phpredisadmin: + Log.debug(self, "Removing package variable of phpRedisAdmin ") + packages = packages + ['{0}22222/htdocs/cache/redis/phpRedisAdmin' + .format(WOVariables.wo_webroot)] + if self.app.pargs.adminer: + Log.debug(self, "Removing package variable of Adminer ") + packages = packages + ['{0}22222/htdocs/db/adminer' + .format(WOVariables.wo_webroot)] + if self.app.pargs.utils: + Log.debug(self, "Removing package variable of utils ") + packages = packages + ['{0}22222/htdocs/php/webgrind/' + .format(WOVariables.wo_webroot), + '{0}22222/htdocs/cache/opcache' + .format(WOVariables.wo_webroot), + '{0}22222/htdocs/cache/nginx/' + 'clean.php'.format(WOVariables.wo_webroot), + '{0}22222/htdocs/cache/memcache' + .format(WOVariables.wo_webroot), + '/usr/bin/pt-query-advisor', + '{0}22222/htdocs/db/anemometer' + .format(WOVariables.wo_webroot)] + + if len(packages) or len(apt_packages): + wo_prompt = input('Are you sure you to want to' + ' remove from server.' + '\nPackage configuration will remain' + ' on server after this operation.\n' + 'Any answer other than ' + '"yes" will be stop this' + ' operation : ') + + if wo_prompt == 'YES' or wo_prompt == 'yes': + + if (set(["nginx-custom"]).issubset(set(apt_packages))) : + WOService.stop_service(self, 'nginx') + + if len(packages): + WOFileUtils.remove(self, packages) + WOAptGet.auto_remove(self) + + if len(apt_packages): + Log.debug(self, "Removing apt_packages") + Log.info(self, "Removing packages, please wait...") + WOAptGet.remove(self, apt_packages) + WOAptGet.auto_remove(self) + + + Log.info(self, "Successfully removed packages") + + #Added for Ondrej Repo missing package Fix + if self.app.pargs.php7: + if WOAptGet.is_installed(self, 'php5.6-fpm'): + Log.info(self, "PHP5.6-fpm found on system.") + Log.info(self, "Verifying and installing missing packages,") + WOShellExec.cmd_exec(self, "apt-get install -y php-memcached php-igbinary") + + + @expose(help="Purge packages") + def purge(self): + """Start purging of packages""" + apt_packages = [] + packages = [] + + # Default action for stack purge + if ((not self.app.pargs.web) and (not self.app.pargs.admin) and + (not self.app.pargs.nginx) and (not self.app.pargs.php) and + (not self.app.pargs.php7) and (not self.app.pargs.mysql) and + (not self.app.pargs.wpcli) and (not self.app.pargs.phpmyadmin) and + (not self.app.pargs.hhvm) and (not self.app.pargs.adminer) and + (not self.app.pargs.utils) and (not self.app.pargs.all) and + (not self.app.pargs.redis) and (not self.app.pargs.phpredisadmin)): + self.app.pargs.web = True + self.app.pargs.admin = True + + if self.app.pargs.all: + self.app.pargs.web = True + self.app.pargs.admin = True + if (WOVariables.wo_platform_codename == 'trusty' or WOVariables.wo_platform_codename == 'xenial' or WOVariables.wo_platform_codename == 'bionic'): + self.app.pargs.php7 = True + + if self.app.pargs.web: + self.app.pargs.nginx = True + self.app.pargs.php = True + self.app.pargs.mysql = True + self.app.pargs.wpcli = True + + if self.app.pargs.admin: + self.app.pargs.adminer = True + self.app.pargs.phpmyadmin = True + self.app.pargs.utils = True + + if self.app.pargs.nginx: + if WOAptGet.is_installed(self, 'nginx-custom'): + Log.debug(self, "Purge apt_packages variable of Nginx") + apt_packages = apt_packages + WOVariables.wo_nginx + else: + Log.error(self,"Cannot Purge! Nginx Stable version not found.") + if self.app.pargs.php: + Log.debug(self, "Purge apt_packages variable PHP") + if (WOVariables.wo_platform_codename == 'trusty' or WOVariables.wo_platform_codename == 'xenial' or WOVariables.wo_platform_codename == 'bionic'): + apt_packages = apt_packages + WOVariables.wo_php5_6 + if not WOAptGet.is_installed(self, 'php7.0-fpm'): + apt_packages = apt_packages + WOVariables.wo_php_extra + else: + apt_packages = apt_packages + WOVariables.wo_php + + #For debian --php7 + if self.app.pargs.php7: + if (WOVariables.wo_platform_codename == 'jessie'): + Log.debug(self, "Removing apt_packages variable of PHP 7.0") + apt_packages = apt_packages + WOVariables.wo_php7_0 + if not WOAptGet.is_installed(self, 'php5-fpm'): + apt_packages = apt_packages + WOVariables.wo_php_extra + else: + Log.info(self,"PHP 7.0 not supported.") + + if self.app.pargs.php7: + if (WOVariables.wo_platform_codename == 'trusty' or WOVariables.wo_platform_codename == 'xenial' or WOVariables.wo_platform_codename == 'bionic'): + Log.debug(self, "Removing apt_packages variable of PHP 7.0") + apt_packages = apt_packages + WOVariables.wo_php7_0 + if not WOAptGet.is_installed(self, 'php5.6-fpm'): + apt_packages = apt_packages + WOVariables.wo_php_extra + else: + Log.info(self,"PHP 7.0 not supported.") + if self.app.pargs.hhvm: + if WOAptGet.is_installed(self, 'hhvm'): + Log.debug(self, "Purge apt_packages varible of HHVM") + apt_packages = apt_packages + WOVariables.wo_hhvm + if self.app.pargs.redis: + Log.debug(self, "Purge apt_packages variable of Redis") + apt_packages = apt_packages + WOVariables.wo_redis + if self.app.pargs.mysql: + Log.debug(self, "Purge apt_packages variable MySQL") + apt_packages = apt_packages + WOVariables.wo_mysql + packages = packages + ['/usr/bin/mysqltuner'] + if self.app.pargs.wpcli: + Log.debug(self, "Purge package variable WPCLI") + if os.path.isfile('/usr/bin/wp'): + packages = packages + ['/usr/bin/wp'] + else: + Log.warn(self, "WP-CLI is not installed with WordOps") + if self.app.pargs.phpmyadmin: + packages = packages + ['{0}22222/htdocs/db/pma'. + format(WOVariables.wo_webroot)] + Log.debug(self, "Purge package variable phpMyAdmin") + if self.app.pargs.phpredisadmin: + Log.debug(self, "Removing package variable of phpRedisAdmin ") + packages = packages + ['{0}22222/htdocs/cache/redis/phpRedisAdmin' + .format(WOVariables.wo_webroot)] + if self.app.pargs.adminer: + Log.debug(self, "Purge package variable Adminer") + packages = packages + ['{0}22222/htdocs/db/adminer' + .format(WOVariables.wo_webroot)] + if self.app.pargs.utils: + Log.debug(self, "Purge package variable utils") + packages = packages + ['{0}22222/htdocs/php/webgrind/' + .format(WOVariables.wo_webroot), + '{0}22222/htdocs/cache/opcache' + .format(WOVariables.wo_webroot), + '{0}22222/htdocs/cache/nginx/' + 'clean.php'.format(WOVariables.wo_webroot), + '{0}22222/htdocs/cache/memcache' + .format(WOVariables.wo_webroot), + '/usr/bin/pt-query-advisor', + '{0}22222/htdocs/db/anemometer' + .format(WOVariables.wo_webroot) + ] + + if len(packages) or len(apt_packages): + wo_prompt = input('Are you sure you to want to purge ' + 'from server ' + 'along with their configuration' + ' packages,\nAny answer other than ' + '"yes" will be stop this ' + 'operation :') + + if wo_prompt == 'YES' or wo_prompt == 'yes': + + if (set(["nginx-custom"]).issubset(set(apt_packages))) : + WOService.stop_service(self, 'nginx') + + if len(apt_packages): + Log.info(self, "Purging packages, please wait...") + WOAptGet.remove(self, apt_packages, purge=True) + WOAptGet.auto_remove(self) + + if len(packages): + WOFileUtils.remove(self, packages) + WOAptGet.auto_remove(self) + + + Log.info(self, "Successfully purged packages") + + #Added for php Ondrej repo missing package fix + if self.app.pargs.php7: + if WOAptGet.is_installed(self, 'php5.6-fpm'): + Log.info(self, "PHP5.6-fpm found on system.") + Log.info(self, "Verifying and installing missing packages,") + WOShellExec.cmd_exec(self, "apt-get install -y php-memcached php-igbinary") + + + +def load(app): + # register the plugin class.. this only happens if the plugin is enabled + handler.register(WOStackController) + handler.register(WOStackStatusController) + handler.register(WOStackMigrateController) + handler.register(WOStackUpgradeController) + + # register a hook (function) to run after arguments are parsed. + hook.register('post_argument_parsing', wo_stack_hook) diff --git a/wo/cli/plugins/stack_migrate.py b/wo/cli/plugins/stack_migrate.py new file mode 100644 index 0000000..46f070c --- /dev/null +++ b/wo/cli/plugins/stack_migrate.py @@ -0,0 +1,115 @@ +from cement.core.controller import CementBaseController, expose +from cement.core import handler, hook +from wo.core.mysql import WOMysql +from wo.core.logging import Log +from wo.core.variables import WOVariables +from wo.core.aptget import WOAptGet +from wo.core.shellexec import WOShellExec +from wo.core.apt_repo import WORepo +from wo.core.services import WOService +import configparser +import os + + +class WOStackMigrateController(CementBaseController): + class Meta: + label = 'migrate' + stacked_on = 'stack' + stacked_type = 'nested' + description = ('Migrate stack safely') + arguments = [ + (['--mariadb'], + dict(help="Migrate database to MariaDB", + action='store_true')), + ] + + @expose(hide=True) + def migrate_mariadb(self): + # Backup all database + WOMysql.backupAll(self) + + # Add MariaDB repo + Log.info(self, "Adding repository for MariaDB, please wait...") + + mysql_pref = ("Package: *\nPin: origin sfo1.mirrors.digitalocean.com" + "\nPin-Priority: 1000\n") + with open('/etc/apt/preferences.d/' + 'MariaDB.pref', 'w') as mysql_pref_file: + mysql_pref_file.write(mysql_pref) + + WORepo.add(self, repo_url=WOVariables.wo_mysql_repo) + Log.debug(self, 'Adding key for {0}' + .format(WOVariables.wo_mysql_repo)) + WORepo.add_key(self, '0xcbcb082a1bb943db', + keyserver="keyserver.ubuntu.com") + + config = configparser.ConfigParser() + if os.path.exists('/etc/mysql/conf.d/my.cnf'): + config.read('/etc/mysql/conf.d/my.cnf') + else: + config.read(os.path.expanduser("~")+'/.my.cnf') + + try: + chars = config['client']['password'] + except Exception as e: + Log.error(self, "Error: process exited with error %s" + % e) + + Log.debug(self, "Pre-seeding MariaDB") + Log.debug(self, "echo \"mariadb-server-10.0 " + "mysql-server/root_password " + "password \" | " + "debconf-set-selections") + WOShellExec.cmd_exec(self, "echo \"mariadb-server-10.0 " + "mysql-server/root_password " + "password {chars}\" | " + "debconf-set-selections" + .format(chars=chars), + log=False) + Log.debug(self, "echo \"mariadb-server-10.0 " + "mysql-server/root_password_again " + "password \" | " + "debconf-set-selections") + WOShellExec.cmd_exec(self, "echo \"mariadb-server-10.0 " + "mysql-server/root_password_again " + "password {chars}\" | " + "debconf-set-selections" + .format(chars=chars), + log=False) + + # Install MariaDB + apt_packages = WOVariables.wo_mysql + + # If PHP is installed then install php5-mysql + if WOAptGet.is_installed(self, "php5-fpm"): + apt_packages = apt_packages + ["php5-mysql"] + + Log.info(self, "Updating apt-cache, hang on...") + WOAptGet.update(self) + Log.info(self, "Installing MariaDB, hang on...") + WOAptGet.remove(self, ["mysql-common", "libmysqlclient18"]) + WOAptGet.auto_remove(self) + WOAptGet.install(self, apt_packages) + + @expose(hide=True) + def default(self): + if ((not self.app.pargs.mariadb)): + self.app.args.print_help() + if self.app.pargs.mariadb: + if WOVariables.wo_mysql_host is not "localhost": + Log.error(self, "Remote MySQL server in use, skipping local install") + + if WOShellExec.cmd_exec(self, "mysqladmin ping") and (not + WOAptGet.is_installed(self, 'mariadb-server')): + + Log.info(self, "If your database size is big, " + "migration may take some time.") + Log.info(self, "During migration non nginx-cached parts of " + "your site may remain down") + start_migrate = input("Type \"mariadb\" to continue:") + if start_migrate != "mariadb": + Log.error(self, "Not starting migration") + self.migrate_mariadb() + else: + Log.error(self, "Your current MySQL is not alive or " + "you allready installed MariaDB") diff --git a/wo/cli/plugins/stack_services.py b/wo/cli/plugins/stack_services.py new file mode 100644 index 0000000..df88b06 --- /dev/null +++ b/wo/cli/plugins/stack_services.py @@ -0,0 +1,403 @@ +from cement.core.controller import CementBaseController, expose +from cement.core import handler, hook +from wo.core.services import WOService +from wo.core.logging import Log +from wo.core.variables import WOVariables +from wo.core.aptget import WOAptGet + + +class WOStackStatusController(CementBaseController): + class Meta: + label = 'stack_services' + stacked_on = 'stack' + stacked_type = 'embedded' + description = 'Check the stack status' + arguments = [ + (['--memcache'], + dict(help='start/stop/restart memcache', action='store_true')), + ] + + @expose(help="Start stack services") + def start(self): + """Start services""" + services = [] + if not (self.app.pargs.nginx or self.app.pargs.php or self.app.pargs.php7 + or self.app.pargs.mysql or self.app.pargs.hhvm or self.app.pargs.memcache + or self.app.pargs.redis): + self.app.pargs.nginx = True + self.app.pargs.php = True + self.app.pargs.mysql = True + + if self.app.pargs.nginx: + if WOAptGet.is_installed(self, 'nginx-custom') or WOAptGet.is_installed(self,'nginx-mainline'): + services = services + ['nginx'] + else: + Log.info(self, "Nginx is not installed") + + if self.app.pargs.php: + if (WOVariables.wo_platform_distro == 'debian' or WOVariables.wo_platform_codename == 'precise'): + if WOAptGet.is_installed(self, 'php5-fpm'): + services = services + ['php5-fpm'] + else: + Log.info(self, "PHP5-FPM is not installed") + else: + if WOAptGet.is_installed(self, 'php5.6-fpm'): + services = services + ['php5.6-fpm'] + else: + Log.info(self, "PHP5.6-FPM is not installed") + + if WOAptGet.is_installed(self, 'php7.0-fpm'): + services = services + ['php7.0-fpm'] + else: + Log.info(self, "PHP7.0-FPM is not installed") + + if self.app.pargs.php7: + if (WOVariables.wo_platform_codename == 'trusty' or WOVariables.wo_platform_codename == 'xenial' or WOVariables.wo_platform_codename == 'bionic'): + if WOAptGet.is_installed(self, 'php7.0-fpm'): + services = services + ['php7.0-fpm'] + else: + Log.info(self, "PHP7.0-FPM is not installed") + else: + Log.info(self, "Your platform does not support PHP 7") + + if self.app.pargs.mysql: + if ((WOVariables.wo_mysql_host is "localhost") or + (WOVariables.wo_mysql_host is "127.0.0.1")): + if (WOAptGet.is_installed(self, 'mysql-server') or + WOAptGet.is_installed(self, 'percona-server-server-5.6') or + WOAptGet.is_installed(self, 'mariadb-server')): + services = services + ['mysql'] + else: + Log.info(self, "MySQL is not installed") + else: + Log.warn(self, "Remote MySQL found, " + "Unable to check MySQL service status") + + if self.app.pargs.hhvm: + if WOAptGet.is_installed(self, 'hhvm'): + services = services + ['hhvm'] + else: + Log.info(self, "HHVM is not installed") + if self.app.pargs.memcache: + if WOAptGet.is_installed(self, 'memcached'): + services = services + ['memcached'] + else: + Log.info(self, "Memcache is not installed") + + if self.app.pargs.redis: + if WOAptGet.is_installed(self, 'redis-server'): + services = services + ['redis-server'] + else: + Log.info(self, "Redis server is not installed") + + for service in services: + Log.debug(self, "Starting service: {0}".format(service)) + WOService.start_service(self, service) + + @expose(help="Stop stack services") + def stop(self): + """Stop services""" + services = [] + if not (self.app.pargs.nginx or self.app.pargs.php or self.app.pargs.php7 + or self.app.pargs.mysql or self.app.pargs.hhvm or self.app.pargs.memcache + or self.app.pargs.redis): + self.app.pargs.nginx = True + self.app.pargs.php = True + self.app.pargs.mysql = True + + if self.app.pargs.nginx: + if WOAptGet.is_installed(self, 'nginx-custom') or WOAptGet.is_installed(self,'nginx-mainline'): + services = services + ['nginx'] + else: + Log.info(self, "Nginx is not installed") + + if self.app.pargs.php: + if (WOVariables.wo_platform_distro == 'debian' or WOVariables.wo_platform_codename == 'precise'): + if WOAptGet.is_installed(self, 'php5-fpm'): + services = services + ['php5-fpm'] + else: + Log.info(self, "PHP5-FPM is not installed") + else: + if WOAptGet.is_installed(self, 'php5.6-fpm'): + services = services + ['php5.6-fpm'] + else: + Log.info(self, "PHP5.6-FPM is not installed") + + if WOAptGet.is_installed(self, 'php7.0-fpm'): + services = services + ['php7.0-fpm'] + else: + Log.info(self, "PHP7.0-FPM is not installed") + + if self.app.pargs.php7: + if (WOVariables.wo_platform_codename == 'trusty' or WOVariables.wo_platform_codename == 'xenial' or WOVariables.wo_platform_codename == 'bionic'): + if WOAptGet.is_installed(self, 'php7.0-fpm'): + services = services + ['php7.0-fpm'] + else: + Log.info(self, "PHP7.0-FPM is not installed") + else: + Log.info(self, "Your platform does not support PHP 7") + + if self.app.pargs.mysql: + if ((WOVariables.wo_mysql_host is "localhost") or + (WOVariables.wo_mysql_host is "127.0.0.1")): + if (WOAptGet.is_installed(self, 'mysql-server') or + WOAptGet.is_installed(self, 'percona-server-server-5.6') or + WOAptGet.is_installed(self, 'mariadb-server')): + services = services + ['mysql'] + else: + Log.info(self, "MySQL is not installed") + else: + Log.warn(self, "Remote MySQL found, " + "Unable to check MySQL service status") + + if self.app.pargs.hhvm: + if WOAptGet.is_installed(self, 'hhvm'): + services = services + ['hhvm'] + else: + Log.info(self, "HHVM is not installed") + if self.app.pargs.memcache: + if WOAptGet.is_installed(self, 'memcached'): + services = services + ['memcached'] + else: + Log.info(self, "Memcache is not installed") + + if self.app.pargs.redis: + if WOAptGet.is_installed(self, 'redis-server'): + services = services + ['redis-server'] + else: + Log.info(self, "Redis server is not installed") + + for service in services: + Log.debug(self, "Stopping service: {0}".format(service)) + WOService.stop_service(self, service) + + @expose(help="Restart stack services") + def restart(self): + """Restart services""" + services = [] + if not (self.app.pargs.nginx or self.app.pargs.php or self.app.pargs.php7 + or self.app.pargs.mysql or self.app.pargs.hhvm or self.app.pargs.memcache + or self.app.pargs.redis): + self.app.pargs.nginx = True + self.app.pargs.php = True + self.app.pargs.mysql = True + + if self.app.pargs.nginx: + if WOAptGet.is_installed(self, 'nginx-custom') or WOAptGet.is_installed(self,'nginx-mainline'): + services = services + ['nginx'] + else: + Log.info(self, "Nginx is not installed") + + if self.app.pargs.php: + if (WOVariables.wo_platform_distro == 'debian' or WOVariables.wo_platform_codename == 'precise'): + if WOAptGet.is_installed(self, 'php5-fpm'): + services = services + ['php5-fpm'] + else: + Log.info(self, "PHP5-FPM is not installed") + else: + if WOAptGet.is_installed(self, 'php5.6-fpm'): + services = services + ['php5.6-fpm'] + else: + Log.info(self, "PHP5.6-FPM is not installed") + + if WOAptGet.is_installed(self, 'php7.0-fpm'): + services = services + ['php7.0-fpm'] + else: + Log.info(self, "PHP7.0-FPM is not installed") + + if self.app.pargs.php7: + if (WOVariables.wo_platform_codename == 'trusty' or WOVariables.wo_platform_codename == 'xenial' or WOVariables.wo_platform_codename == 'bionic'): + if WOAptGet.is_installed(self, 'php7.0-fpm'): + services = services + ['php7.0-fpm'] + else: + Log.info(self, "PHP7.0-FPM is not installed") + else: + Log.info(self, "Your platform does not support PHP 7") + + + if self.app.pargs.mysql: + if ((WOVariables.wo_mysql_host is "localhost") or + (WOVariables.wo_mysql_host is "127.0.0.1")): + if (WOAptGet.is_installed(self, 'mysql-server') or + WOAptGet.is_installed(self, 'percona-server-server-5.6') or + WOAptGet.is_installed(self, 'mariadb-server')): + services = services + ['mysql'] + else: + Log.info(self, "MySQL is not installed") + else: + Log.warn(self, "Remote MySQL found, " + "Unable to check MySQL service status") + + if self.app.pargs.hhvm: + if WOAptGet.is_installed(self, 'hhvm'): + services = services + ['hhvm'] + else: + Log.info(self, "HHVM is not installed") + if self.app.pargs.memcache: + if WOAptGet.is_installed(self, 'memcached'): + services = services + ['memcached'] + else: + Log.info(self, "Memcache is not installed") + + if self.app.pargs.redis: + if WOAptGet.is_installed(self, 'redis-server'): + services = services + ['redis-server'] + else: + Log.info(self, "Redis server is not installed") + + for service in services: + Log.debug(self, "Restarting service: {0}".format(service)) + WOService.restart_service(self, service) + + @expose(help="Get stack status") + def status(self): + """Status of services""" + services = [] + if not (self.app.pargs.nginx or self.app.pargs.php or self.app.pargs.php7 + or self.app.pargs.mysql or self.app.pargs.hhvm or self.app.pargs.memcache + or self.app.pargs.redis): + self.app.pargs.nginx = True + self.app.pargs.php = True + self.app.pargs.mysql = True + self.app.pargs.hhvm = True + + if self.app.pargs.nginx: + if WOAptGet.is_installed(self, 'nginx-custom') or WOAptGet.is_installed(self,'nginx-mainline'): + services = services + ['nginx'] + else: + Log.info(self, "Nginx is not installed") + + if self.app.pargs.php: + if (WOVariables.wo_platform_distro == 'debian' or WOVariables.wo_platform_codename == 'precise'): + if WOAptGet.is_installed(self, 'php5-fpm'): + services = services + ['php5-fpm'] + else: + Log.info(self, "PHP5-FPM is not installed") + else: + if WOAptGet.is_installed(self, 'php5.6-fpm'): + services = services + ['php5.6-fpm'] + else: + Log.info(self, "PHP5.6-FPM is not installed") + + if WOAptGet.is_installed(self, 'php7.0-fpm'): + services = services + ['php7.0-fpm'] + else: + Log.info(self, "PHP7.0-FPM is not installed") + + if self.app.pargs.php7: + if (WOVariables.wo_platform_codename == 'trusty' or WOVariables.wo_platform_codename == 'xenial' or WOVariables.wo_platform_codename == 'bionic'): + if WOAptGet.is_installed(self, 'php7.0-fpm'): + services = services + ['php7.0-fpm'] + else: + Log.info(self, "PHP7.0-FPM is not installed") + else: + Log.info(self, "Your platform does not support PHP 7") + + if self.app.pargs.mysql: + if ((WOVariables.wo_mysql_host is "localhost") or + (WOVariables.wo_mysql_host is "127.0.0.1")): + if (WOAptGet.is_installed(self, 'mysql-server') or + WOAptGet.is_installed(self, 'percona-server-server-5.6') or + WOAptGet.is_installed(self, 'mariadb-server')): + services = services + ['mysql'] + else: + Log.info(self, "MySQL is not installed") + else: + Log.warn(self, "Remote MySQL found, " + "Unable to check MySQL service status") + + if self.app.pargs.hhvm: + if WOAptGet.is_installed(self, 'hhvm'): + services = services + ['hhvm'] + else: + Log.info(self, "HHVM is not installed") + if self.app.pargs.memcache: + if WOAptGet.is_installed(self, 'memcached'): + services = services + ['memcached'] + else: + Log.info(self, "Memcache is not installed") + + if self.app.pargs.redis: + if WOAptGet.is_installed(self, 'redis-server'): + services = services + ['redis-server'] + else: + Log.info(self, "Redis server is not installed") + + for service in services: + if WOService.get_service_status(self, service): + Log.info(self, "{0:10}: {1}".format(service, "Running")) + + @expose(help="Reload stack services") + def reload(self): + """Reload service""" + services = [] + if not (self.app.pargs.nginx or self.app.pargs.php or self.app.pargs.php7 + or self.app.pargs.mysql or self.app.pargs.hhvm or self.app.pargs.memcache + or self.app.pargs.redis): + self.app.pargs.nginx = True + self.app.pargs.php = True + self.app.pargs.mysql = True + + if self.app.pargs.nginx: + if WOAptGet.is_installed(self, 'nginx-custom') or WOAptGet.is_installed(self,'nginx-mainline'): + services = services + ['nginx'] + else: + Log.info(self, "Nginx is not installed") + + if self.app.pargs.php: + if (WOVariables.wo_platform_distro == 'debian' or WOVariables.wo_platform_codename == 'precise'): + if WOAptGet.is_installed(self, 'php5-fpm'): + services = services + ['php5-fpm'] + else: + Log.info(self, "PHP5-FPM is not installed") + else: + if WOAptGet.is_installed(self, 'php5.6-fpm'): + services = services + ['php5.6-fpm'] + else: + Log.info(self, "PHP5.6-FPM is not installed") + + if WOAptGet.is_installed(self, 'php7.0-fpm'): + services = services + ['php7.0-fpm'] + else: + Log.info(self, "PHP7.0-FPM is not installed") + + if self.app.pargs.php7: + if (WOVariables.wo_platform_codename == 'trusty' or WOVariables.wo_platform_codename == 'xenial' or WOVariables.wo_platform_codename == 'bionic'): + if WOAptGet.is_installed(self, 'php7.0-fpm'): + services = services + ['php7.0-fpm'] + else: + Log.info(self, "PHP7.0-FPM is not installed") + else: + Log.info(self, "Your platform does not support PHP 7") + + if self.app.pargs.mysql: + if ((WOVariables.wo_mysql_host is "localhost") or + (WOVariables.wo_mysql_host is "127.0.0.1")): + if (WOAptGet.is_installed(self, 'mysql-server') or + WOAptGet.is_installed(self, 'percona-server-server-5.6') or + WOAptGet.is_installed(self, 'mariadb-server')): + services = services + ['mysql'] + else: + Log.info(self, "MySQL is not installed") + else: + Log.warn(self, "Remote MySQL found, " + "Unable to check MySQL service status") + + if self.app.pargs.hhvm: + Log.info(self, "HHVM does not support to reload") + + if self.app.pargs.memcache: + if WOAptGet.is_installed(self, 'memcached'): + services = services + ['memcached'] + else: + Log.info(self, "Memcache is not installed") + + if self.app.pargs.redis: + if WOAptGet.is_installed(self, 'redis-server'): + services = services + ['redis-server'] + else: + Log.info(self, "Redis server is not installed") + + for service in services: + Log.debug(self, "Reloading service: {0}".format(service)) + WOService.reload_service(self, service) diff --git a/wo/cli/plugins/stack_upgrade.py b/wo/cli/plugins/stack_upgrade.py new file mode 100644 index 0000000..4ee96e9 --- /dev/null +++ b/wo/cli/plugins/stack_upgrade.py @@ -0,0 +1,233 @@ +from cement.core.controller import CementBaseController, expose +from cement.core import handler, hook +from wo.core.logging import Log +from wo.core.variables import WOVariables +from wo.core.aptget import WOAptGet +from wo.core.apt_repo import WORepo +from wo.core.services import WOService +from wo.core.fileutils import WOFileUtils +from wo.core.shellexec import WOShellExec +from wo.core.git import WOGit +from wo.core.download import WODownload +import configparser +import os + + +class WOStackUpgradeController(CementBaseController): + class Meta: + label = 'upgrade' + stacked_on = 'stack' + stacked_type = 'nested' + description = ('Upgrade stack safely') + arguments = [ + (['--all'], + dict(help='Upgrade all stack', action='store_true')), + (['--web'], + dict(help='Upgrade web stack', action='store_true')), + (['--admin'], + dict(help='Upgrade admin tools stack', action='store_true')), + (['--nginx'], + dict(help='Upgrade Nginx stack', action='store_true')), + (['--nginxmainline'], + dict(help='Upgrade Nginx Mainline stack', action='store_true')), + (['--php'], + dict(help='Upgrade PHP stack', action='store_true')), + (['--mysql'], + dict(help='Upgrade MySQL stack', action='store_true')), + (['--hhvm'], + dict(help='Upgrade HHVM stack', action='store_true')), + (['--wpcli'], + dict(help='Upgrade WPCLI', action='store_true')), + (['--redis'], + dict(help='Upgrade Redis', action='store_true')), + (['--php56'], + dict(help="Upgrade to PHP5.6 from PHP5.5", + action='store_true')), + (['--no-prompt'], + dict(help="Upgrade Packages without any prompt", + action='store_true')), + ] + + @expose(hide=True) + def upgrade_php56(self): + if WOVariables.wo_platform_distro == "ubuntu": + if os.path.isfile("/etc/apt/sources.list.d/ondrej-php5-5_6-{0}." + "list".format(WOVariables.wo_platform_codename)): + Log.error(self, "Unable to find PHP 5.5") + else: + if not(os.path.isfile(WOVariables.wo_repo_file_path) and + WOFileUtils.grep(self, WOVariables.wo_repo_file_path, + "php55")): + Log.error(self, "Unable to find PHP 5.5") + + Log.info(self, "During PHP update process non nginx-cached" + " parts of your site may remain down.") + + # Check prompt + if (not self.app.pargs.no_prompt): + start_upgrade = input("Do you want to continue:[y/N]") + if start_upgrade != "Y" and start_upgrade != "y": + Log.error(self, "Not starting PHP package update") + + if WOVariables.wo_platform_distro == "ubuntu": + WORepo.remove(self, ppa="ppa:ondrej/php5") + WORepo.add(self, ppa=WOVariables.wo_php_repo) + else: + WOAptGet.remove(self, ["php5-xdebug"]) + WOFileUtils.searchreplace(self, WOVariables.wo_repo_file_path, + "php55", "php56") + + Log.info(self, "Updating apt-cache, please wait...") + WOAptGet.update(self) + Log.info(self, "Installing packages, please wait ...") + if (WOVariables.wo_platform_codename == 'trusty' or WOVariables.wo_platform_codename == 'xenial' or WOVariables.wo_platform_codename == 'bionic'): + WOAptGet.install(self, WOVariables.wo_php5_6 + WOVariables.wo_php_extra) + else: + WOAptGet.install(self, WOVariables.wo_php) + + if WOVariables.wo_platform_distro == "debian": + WOShellExec.cmd_exec(self, "pecl install xdebug") + + with open("/etc/php5/mods-available/xdebug.ini", + encoding='utf-8', mode='a') as myfile: + myfile.write(";zend_extension=/usr/lib/php5/20131226/" + "xdebug.so\n") + + WOFileUtils.create_symlink(self, ["/etc/php5/mods-available/" + "xdebug.ini", "/etc/php5/fpm/conf.d" + "/20-xedbug.ini"]) + + Log.info(self, "Successfully upgraded from PHP 5.5 to PHP 5.6") + + @expose(hide=True) + def default(self): + # All package update + if ((not self.app.pargs.php56)): + + apt_packages = [] + packages = [] + + if ((not self.app.pargs.web) and (not self.app.pargs.nginx) and + (not self.app.pargs.php) and (not self.app.pargs.mysql) and + (not self.app.pargs.hhvm) and (not self.app.pargs.all) and + (not self.app.pargs.wpcli) and (not self.app.pargs.redis) and + (not self.app.pargs.nginxmainline)): + self.app.pargs.web = True + + if self.app.pargs.all: + self.app.pargs.web = True + + if self.app.pargs.web: + if WOAptGet.is_installed(self, 'nginx-custom'): + self.app.pargs.nginx = True + else: + Log.info(self, "Nginx is not already installed") + self.app.pargs.php = True + self.app.pargs.mysql = True + self.app.pargs.wpcli = True + + if self.app.pargs.nginx : + if WOAptGet.is_installed(self, 'nginx-custom'): + apt_packages = apt_packages + WOVariables.wo_nginx + else: + Log.info(self, "Nginx Stable is not already installed") + + if self.app.pargs.php: + if (WOVariables.wo_platform_distro == 'debian' or WOVariables.wo_platform_codename == 'precise'): + if WOAptGet.is_installed(self, 'php5-fpm'): + apt_packages = apt_packages + WOVariables.wo_php + else: + Log.info(self, "PHP is not installed") + if WOAptGet.is_installed(self, 'php7.0-fpm'): + apt_packages = apt_packages + WOVariables.wo_php7_0 + else: + if WOAptGet.is_installed(self, 'php5.6-fpm'): + apt_packages = apt_packages + WOVariables.wo_php5_6 + WOVariables.wo_php_extra + else: + Log.info(self, "PHP 5.6 is not installed") + if WOAptGet.is_installed(self, 'php7.0-fpm'): + apt_packages = apt_packages + WOVariables.wo_php7_0 + WOVariables.wo_php_extra + else: + Log.info(self, "PHP 7.0 is not installed") + + if self.app.pargs.hhvm: + if WOAptGet.is_installed(self, 'hhvm'): + apt_packages = apt_packages + WOVariables.wo_hhvm + else: + Log.info(self, "HHVM is not installed") + + if self.app.pargs.mysql: + if WOAptGet.is_installed(self, 'mariadb-server'): + apt_packages = apt_packages + WOVariables.wo_mysql + else: + Log.info(self, "MariaDB is not installed") + + if self.app.pargs.redis: + if WOAptGet.is_installed(self, 'redis-server'): + apt_packages = apt_packages + WOVariables.wo_redis + else: + Log.info(self, "Redis is not installed") + + if self.app.pargs.wpcli: + if os.path.isfile('/usr/bin/wp'): + packages = packages + [["https://github.com/wp-cli/wp-cli/" + "releases/download/v{0}/" + "wp-cli-{0}.phar" + "".format(WOVariables.wo_wp_cli), + "/usr/bin/wp", + "WP-CLI"]] + else: + Log.info(self, "WPCLI is not installed with WordOps") + + if len(packages) or len(apt_packages): + + Log.info(self, "During package update process non nginx-cached" + " parts of your site may remain down") + # Check prompt + if (not self.app.pargs.no_prompt): + start_upgrade = input("Do you want to continue:[y/N]") + if start_upgrade != "Y" and start_upgrade != "y": + Log.error(self, "Not starting package update") + + Log.info(self, "Updating packages, please wait...") + if len(apt_packages): + # apt-get update + WOAptGet.update(self) + # Update packages + WOAptGet.install(self, apt_packages) + + # Post Actions after package updates + if (set(WOVariables.wo_nginx).issubset(set(apt_packages))): + WOService.restart_service(self, 'nginx') + if (WOVariables.wo_platform_distro == 'debian' or WOVariables.wo_platform_codename == 'precise'): + if set(WOVariables.wo_php).issubset(set(apt_packages)): + WOService.restart_service(self, 'php5-fpm') + else: + if set(WOVariables.wo_php5_6).issubset(set(apt_packages)): + WOService.restart_service(self, 'php5.6-fpm') + if set(WOVariables.wo_php7_0).issubset(set(apt_packages)): + WOService.restart_service(self, 'php7.0-fpm') + if set(WOVariables.wo_hhvm).issubset(set(apt_packages)): + WOService.restart_service(self, 'hhvm') + if set(WOVariables.wo_mysql).issubset(set(apt_packages)): + WOService.restart_service(self, 'mysql') + if set(WOVariables.wo_redis).issubset(set(apt_packages)): + WOService.restart_service(self, 'redis-server') + + if len(packages): + if self.app.pargs.wpcli: + WOFileUtils.remove(self,['/usr/bin/wp']) + + Log.debug(self, "Downloading following: {0}".format(packages)) + WODownload.download(self, packages) + + if self.app.pargs.wpcli: + WOFileUtils.chmod(self, "/usr/bin/wp", 0o775) + + Log.info(self, "Successfully updated packages") + + # PHP 5.6 to 5.6 + elif (self.app.pargs.php56): + self.upgrade_php56() + else: + self.app.args.print_help() diff --git a/wo/cli/plugins/sync.py b/wo/cli/plugins/sync.py new file mode 100644 index 0000000..dfa12fe --- /dev/null +++ b/wo/cli/plugins/sync.py @@ -0,0 +1,94 @@ +from cement.core.controller import CementBaseController, expose +from cement.core import handler, hook +from wo.core.fileutils import WOFileUtils +from wo.cli.plugins.sitedb import * +from wo.core.mysql import * +from wo.core.logging import Log + + +def wo_sync_hook(app): + pass + + +class WOSyncController(CementBaseController): + class Meta: + label = 'sync' + stacked_on = 'base' + stacked_type = 'nested' + description = 'synchronize the WordOps database' + + @expose(hide=True) + def default(self): + self.sync() + + @expose(hide=True) + def sync(self): + """ + 1. reads database information from wp/wo-config.php + 2. updates records into wo database accordingly. + """ + Log.info(self, "Synchronizing wo database, please wait...") + sites = getAllsites(self) + if not sites: + pass + for site in sites: + if site.site_type in ['mysql', 'wp', 'wpsubdir', 'wpsubdomain']: + wo_site_webroot = site.site_path + # Read config files + configfiles = glob.glob(wo_site_webroot + '/*-config.php') + + #search for wp-config.php inside htdocs/ + if not configfiles: + Log.debug(self, "Config files not found in {0}/ " + .format(wo_site_webroot)) + if site.site_type != 'mysql': + Log.debug(self, "Searching wp-config.php in {0}/htdocs/ " + .format(wo_site_webroot)) + configfiles = glob.glob(wo_site_webroot + '/htdocs/wp-config.php') + + if configfiles: + if WOFileUtils.isexist(self, configfiles[0]): + wo_db_name = (WOFileUtils.grep(self, configfiles[0], + 'DB_NAME').split(',')[1] + .split(')')[0].strip().replace('\'', '')) + wo_db_user = (WOFileUtils.grep(self, configfiles[0], + 'DB_USER').split(',')[1] + .split(')')[0].strip().replace('\'', '')) + wo_db_pass = (WOFileUtils.grep(self, configfiles[0], + 'DB_PASSWORD').split(',')[1] + .split(')')[0].strip().replace('\'', '')) + wo_db_host = (WOFileUtils.grep(self, configfiles[0], + 'DB_HOST').split(',')[1] + .split(')')[0].strip().replace('\'', '')) + + # Check if database really exist + try: + if not WOMysql.check_db_exists(self, wo_db_name): + # Mark it as deleted if not exist + wo_db_name = 'deleted' + wo_db_user = 'deleted' + wo_db_pass = 'deleted' + except StatementExcecutionError as e: + Log.debug(self, str(e)) + except Exception as e: + Log.debug(self, str(e)) + + if site.db_name != wo_db_name: + # update records if any mismatch found + Log.debug(self, "Updating wo db record for {0}" + .format(site.sitename)) + updateSiteInfo(self, site.sitename, + db_name=wo_db_name, + db_user=wo_db_user, + db_password=wo_db_pass, + db_host=wo_db_host) + else: + Log.debug(self, "Config files not found for {0} " + .format(site.sitename)) + + +def load(app): + # register the plugin class.. this only happens if the plugin is enabled + handler.register(WOSyncController) + # register a hook (function) to run after arguments are parsed. + hook.register('post_argument_parsing', wo_sync_hook) diff --git a/wo/cli/plugins/update.py b/wo/cli/plugins/update.py new file mode 100644 index 0000000..171e748 --- /dev/null +++ b/wo/cli/plugins/update.py @@ -0,0 +1,44 @@ +from cement.core.controller import CementBaseController, expose +from cement.core import handler, hook +from wo.core.download import WODownload +from wo.core.logging import Log +import time +import os + + +def wo_update_hook(app): + pass + + +class WOUpdateController(CementBaseController): + class Meta: + label = 'wo_update' + stacked_on = 'base' + aliases = ['update'] + aliases_only = True + stacked_type = 'nested' + description = ('update WordOps to latest version') + usage = "wo update" + + @expose(hide=True) + def default(self): + filename = "woupdate" + time.strftime("%Y%m%d-%H%M%S") + WODownload.download(self, [["https://wrdps.nl/woup", + "/tmp/{0}".format(filename), + "update script"]]) + try: + Log.info(self, "updating WordOps, please wait...") + os.system("bash /tmp/{0}".format(filename)) + except OSError as e: + Log.debug(self, str(e)) + Log.error(self, "WordOps update failed !") + except Exception as e: + Log.debug(self, str(e)) + Log.error(self, "WordOps update failed !") + + +def load(app): + # register the plugin class.. this only happens if the plugin is enabled + handler.register(WOUpdateController) + # register a hook (function) to run after arguments are parsed. + hook.register('post_argument_parsing', wo_update_hook) diff --git a/wo/cli/templates/15-content_filter_mode.mustache b/wo/cli/templates/15-content_filter_mode.mustache new file mode 100644 index 0000000..6fd8f21 --- /dev/null +++ b/wo/cli/templates/15-content_filter_mode.mustache @@ -0,0 +1,27 @@ +use strict; + +# You can modify this file to re-enable SPAM checking through spamassassin +# and to re-enable antivirus checking. + +# +# Default antivirus checking mode +# Please note, that anti-virus checking is DISABLED by +# default. +# If You wish to enable it, please uncomment the following lines: + + +@bypass_virus_checks_maps = ( + \%bypass_virus_checks, \@bypass_virus_checks_acl, \$bypass_virus_checks_re); + + +# +# Default SPAM checking mode +# Please note, that anti-spam checking is DISABLED by +# default. +# If You wish to enable it, please uncomment the following lines: + + +@bypass_spam_checks_maps = ( + \%bypass_spam_checks, \@bypass_spam_checks_acl, \$bypass_spam_checks_re); + +1; # ensure a defined return diff --git a/wo/cli/templates/22222.mustache b/wo/cli/templates/22222.mustache new file mode 100644 index 0000000..ecd934b --- /dev/null +++ b/wo/cli/templates/22222.mustache @@ -0,0 +1,44 @@ +# WordOps admin NGINX CONFIGURATION + +server { + + listen 22222 default_server ssl http2; + + access_log /var/log/nginx/22222.access.log rt_cache; + error_log /var/log/nginx/22222.error.log; + + ssl_certificate {{webroot}}22222/cert/22222.crt; + ssl_certificate_key {{webroot}}22222/cert/22222.key; + + # Force HTTP to HTTPS + error_page 497 =200 https://$host:22222$request_uri; + + root {{webroot}}22222/htdocs; + index index.php index.htm index.html; + + # Turn on directory listing + autoindex on; + + # HTTP Authentication on port 22222 + include common/acl.conf; + + location / { + try_files $uri $uri/ /index.php?$args; + } + + # Display menu at location /fpm/status/ + location = /fpm/status/ {} + + location ~ /fpm/status/(.*) { + try_files $uri =404; + include fastcgi_params; + fastcgi_param SCRIPT_NAME /status; + fastcgi_pass $1; + } + + location ~ \.php$ { + try_files $uri =404; + include fastcgi_params; + fastcgi_pass php; + } +} diff --git a/wo/cli/templates/__init__.py b/wo/cli/templates/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/wo/cli/templates/acl.mustache b/wo/cli/templates/acl.mustache new file mode 100644 index 0000000..c8ff43b --- /dev/null +++ b/wo/cli/templates/acl.mustache @@ -0,0 +1,8 @@ +# WordOps (wo) protect locations using +# HTTP authentication || IP address +satisfy any; +auth_basic "Restricted Area"; +auth_basic_user_file htpasswd-wo; +# Allowed IP Address List +allow 127.0.0.1; +deny all; diff --git a/wo/cli/templates/anemometer.mustache b/wo/cli/templates/anemometer.mustache new file mode 100644 index 0000000..7b85a6a --- /dev/null +++ b/wo/cli/templates/anemometer.mustache @@ -0,0 +1,255 @@ + '{{host}}', + 'port' => '{{port}}', + 'db' => 'slow_query_log', + 'user' => '{{user}}', + 'password' => '{{password}}', + 'tables' => array( + 'global_query_review' => 'fact', + 'global_query_review_history' => 'dimension' + ), + 'source_type' => 'slow_query_log' +); + +$conf['default_report_action'] = 'report'; + +$conf['reviewers'] = array( 'dba1','dba2'); +$conf['review_types'] = array( 'good', 'bad', 'ticket-created', 'needs-fix', 'fixed', 'needs-analysis', 'review-again'); + +$conf['history_defaults'] = array( + 'output' => 'table', + 'fact-group' => 'date', + 'fact-order' => 'date DESC', + 'fact-limit' => '90', + 'dimension-ts_min_start' => date("Y-m-d H:i:s", strtotime( '-90 day')), + 'dimension-ts_min_end' => date("Y-m-d H:i:s"), + 'table_fields' => array('date', 'index_ratio','query_time_avg','rows_sent_avg','ts_cnt','Query_time_sum','Lock_time_sum','Rows_sent_sum','Rows_examined_sum','Tmp_table_sum','Filesort_sum','Full_scan_sum') +); + +$conf['report_defaults'] = array( + 'fact-group' => 'checksum', + 'fact-order' => 'Query_time_sum DESC', + 'fact-limit' => '20', + 'dimension-ts_min_start' => date("Y-m-d H:i:s", strtotime( '-1 day')), + 'dimension-ts_min_end' => date("Y-m-d H:i:s"), + 'table_fields' => array('checksum','snippet', 'index_ratio','query_time_avg','rows_sent_avg','ts_cnt','Query_time_sum','Lock_time_sum','Rows_sent_sum','Rows_examined_sum','Tmp_table_sum','Filesort_sum','Full_scan_sum'), + 'dimension-pivot-hostname_max' => null +); + +$conf['graph_defaults'] = array( + 'fact-group' => 'minute_ts', + 'fact-order' => 'minute_ts', + 'fact-limit' => '', + 'dimension-ts_min_start' => date("Y-m-d H:i:s", strtotime( '-7 day')), + 'dimension-ts_min_end' => date("Y-m-d H:i:s"), + 'table_fields' => array('minute_ts'), + 'plot_field' => 'Query_time_sum', +); + +$conf['report_defaults']['performance_schema'] = array( + 'fact-order' => 'SUM_TIMER_WAIT DESC', + 'fact-limit' => '20', + 'fact-group' => 'DIGEST', + 'table_fields' => array( 'DIGEST', 'snippet', 'index_ratio', 'COUNT_STAR', 'SUM_TIMER_WAIT', 'SUM_LOCK_TIME','SUM_ROWS_AFFECTED','SUM_ROWS_SENT','SUM_ROWS_EXAMINED','SUM_CREATED_TMP_TABLES','SUM_SORT_SCAN','SUM_NO_INDEX_USED' ) +); + +$conf['history_defaults']['performance_schema'] = array( + 'fact-order' => 'SUM_TIMER_WAIT DESC', + 'fact-limit' => '20', + 'fact-group' => 'DIGEST', + 'table_fields' => array( 'DIGEST', 'index_ratio', 'COUNT_STAR', 'SUM_LOCK_TIME','SUM_ROWS_AFFECTED','SUM_ROWS_SENT','SUM_ROWS_EXAMINED','SUM_CREATED_TMP_TABLES','SUM_SORT_SCAN','SUM_NO_INDEX_USED' ) +); + +$conf['report_defaults']['performance_schema_history'] = array( + 'fact-group' => 'DIGEST', + 'fact-order' => 'SUM_TIMER_WAIT DESC', + 'fact-limit' => '20', + 'dimension-FIRST_SEEN_start' => date("Y-m-d H:i:s", strtotime( '-1 day')), + 'dimension-FIRST_SEEN_end' => date("Y-m-d H:i:s"), + 'table_fields' => array( 'DIGEST', 'snippet', 'index_ratio', 'COUNT_STAR', 'SUM_LOCK_TIME','SUM_ROWS_AFFECTED','SUM_ROWS_SENT','SUM_ROWS_EXAMINED','SUM_CREATED_TMP_TABLES','SUM_SORT_SCAN','SUM_NO_INDEX_USED' ) +); + +$conf['graph_defaults']['performance_schema_history'] = array( + 'fact-group' => 'minute_ts', + 'fact-order' => 'minute_ts', + 'fact-limit' => '', + 'dimension-FIRST_SEEN_start' => date("Y-m-d H:i:s", strtotime( '-7 day')), + 'dimension-FIRST_SEEN_end' => date("Y-m-d H:i:s"), + 'table_fields' => array('minute_ts'), + 'plot_field' => 'SUM_TIMER_WAIT', + 'dimension-pivot-hostname_max' => null +); + +$conf['history_defaults']['performance_schema_history'] = array( + 'output' => 'table', + 'fact-group' => 'date', + 'fact-order' => 'date DESC', + 'fact-limit' => '90', + 'dimension-FIRST_SEEN_start' => date("Y-m-d H:i:s", strtotime( '-90 day')), + 'dimension-FIRST_SEEN_end' => date("Y-m-d H:i:s"), + 'table_fields' => array( 'date', 'snippet', 'index_ratio', 'COUNT_STAR', 'SUM_LOCK_TIME','SUM_ROWS_AFFECTED','SUM_ROWS_SENT','SUM_ROWS_EXAMINED','SUM_CREATED_TMP_TABLES','SUM_SORT_SCAN','SUM_NO_INDEX_USED' ) +); +$conf['plugins'] = array( + + 'visual_explain' => '/usr/bin/pt-visual-explain', + 'query_advisor' => '/usr/bin/pt-query-advisor', + + 'show_create' => true, + 'show_status' => true, + + 'explain' => function ($sample) { + $conn = array(); + + if (!array_key_exists('hostname_max',$sample) or strlen($sample['hostname_max']) < 5) + { + return; + } + + $pos = strpos($sample['hostname_max'], ':'); + if ($pos === false) + { + $conn['port'] = 3306; + $conn['host'] = $sample['hostname_max']; + } + else + { + $parts = preg_split("/:/", $sample['hostname_max']); + $conn['host'] = $parts[0]; + $conn['port'] = $parts[1]; + } + + $conn['db'] = 'mysql'; + if ($sample['db_max'] != '') + { + $conn['db'] = $sample['db_max']; + } + + $conn['user'] = '{{user}}'; + $conn['password'] = '{{password}}'; + + return $conn; + }, +); + +$conf['reports']['slow_query_log'] = array( + 'join' => array ( + 'dimension' => 'USING (`checksum`)' + ), + 'fields' => array( + 'fact' => array( + 'group' => 'group', + 'order' => 'order', + 'having' => 'having', + 'limit' => 'limit', + 'first_seen'=> 'clear|reldate|ge|where', + 'where' => 'raw_where', + 'sample' => 'clear|like|where', + 'checksum' => 'clear|where', + 'reviewed_status' => 'clear|where', + + ), + + 'dimension' => array( + 'extra_fields' => 'where', + 'hostname_max' => 'clear|where', + 'ts_min' => 'date_range|reldate|clear|where', + 'pivot-hostname_max' => 'clear|pivot|select', + 'pivot-checksum' => 'clear|pivot|select', + ), + ), + 'custom_fields' => array( + 'checksum' => 'checksum', + 'date' => 'DATE(ts_min)', + 'hour' => 'substring(ts_min,1,13)', + 'hour_ts' => 'round(unix_timestamp(substring(ts_min,1,13)))', + 'minute_ts' => 'round(unix_timestamp(substring(ts_min,1,16)))', + 'minute' => 'substring(ts_min,1,16)', + 'snippet' => 'LEFT(dimension.sample,20)', + 'index_ratio' =>'ROUND(SUM(Rows_examined_sum)/SUM(rows_sent_sum),2)', + 'query_time_avg' => 'SUM(Query_time_sum) / SUM(ts_cnt)', + 'rows_sent_avg' => 'ROUND(SUM(Rows_sent_sum)/SUM(ts_cnt),0)', + ), + + 'callbacks' => array( + 'table' => array( + 'date' => function ($x) { $type=''; if ( date('N',strtotime($x)) >= 6) { $type = 'weekend'; } return array($x,$type); }, + 'checksum' => function ($x) { return array(dec2hex($x), ''); } + ) + ) + +); + +$conf['reports']['performance_schema'] = array( + 'fields' => array( + 'fact' => array( + 'order' => 'order', + 'having' => 'having', + 'limit' => 'limit', + 'first_seen' => 'date_range|reldate|clear|where', + 'where' => 'raw_where', + 'DIGEST' => 'clear|where', + 'DIGEST_TEXT' => 'clear|like|where', + 'group' => 'group', + ), + ), + 'custom_fields' => array( + 'snippet' => 'LEFT(fact.DIGEST_TEXT,20)', + 'index_ratio' =>'ROUND(SUM_ROWS_EXAMINED/SUM_ROWS_SENT,2)', + 'rows_sent_avg' => 'ROUND(SUM_ROWS_SENT/COUNT_STAR,0)', + + ), + + 'special_field_names' => array( + 'time' => 'FIRST_SEEN', + 'checksum' => 'DIGEST', + 'sample' => 'DIGEST_TEXT', + 'fingerprint' => 'DIGEST_TEXT', + ), +); + +$conf['reports']['performance_schema_history'] = array( + 'join' => array ( + 'dimension' => 'USING (`DIGEST`)' + ), + 'fields' => array( + 'fact' => array( + 'group' => 'group', + 'order' => 'order', + 'having' => 'having', + 'limit' => 'limit', + 'first_seen'=> 'clear|reldate|ge|where', + 'where' => 'raw_where', + 'DIGEST_TEXT' => 'clear|like|where', + 'DIGEST' => 'clear|where', + 'reviewed_status' => 'clear|where', + + ), + + 'dimension' => array( + 'extra_fields' => 'where', + 'hostname' => 'clear|where', + 'FIRST_SEEN' => 'date_range|reldate|clear|where', + 'pivot-hostname' => 'clear|pivot|select', + ), + ), + 'custom_fields' => array( + 'date' => 'DATE(fact.FIRST_SEEN)', + 'snippet' => 'LEFT(fact.DIGEST_TEXT,20)', + 'index_ratio' =>'ROUND(SUM_ROWS_EXAMINED/SUM_ROWS_SENT,2)', + 'rows_sent_avg' => 'ROUND(SUM_ROWS_SENT/COUNT_STAR,0)', + 'hour' => 'substring(dimension.FIRST_SEEN,1,13)', + 'hour_ts' => 'round(unix_timestamp(substring(dimension.FIRST_SEEN,1,13)))', + 'minute_ts' => 'round(unix_timestamp(substring(dimension.FIRST_SEEN,1,16)))', + 'minute' => 'substring(dimension.FIRST_SEEN,1,16)', + ), + + 'special_field_names' => array( + 'time' => 'FIRST_SEEN', + 'checksum' => 'DIGEST', + 'hostname' => 'hostname', + 'sample' => 'DIGEST_TEXT' + ), +); + +?> diff --git a/wo/cli/templates/blockips.mustache b/wo/cli/templates/blockips.mustache new file mode 100644 index 0000000..8228bed --- /dev/null +++ b/wo/cli/templates/blockips.mustache @@ -0,0 +1,2 @@ +# Block IP Address +# deny 1.1.1.1; diff --git a/wo/cli/templates/fastcgi.mustache b/wo/cli/templates/fastcgi.mustache new file mode 100644 index 0000000..6d4bc4b --- /dev/null +++ b/wo/cli/templates/fastcgi.mustache @@ -0,0 +1,10 @@ +# FastCGI cache settings +fastcgi_cache_path /var/run/nginx-cache levels=1:2 keys_zone=WORDPRESS:50m inactive=60m; +fastcgi_cache_key "$scheme$request_method$host$request_uri"; +fastcgi_cache_use_stale error timeout invalid_header updating http_500 http_503; +fastcgi_cache_valid 200 301 302 404 1h; +fastcgi_buffers 16 16k; +fastcgi_buffer_size 32k; +fastcgi_param SERVER_NAME $http_host; +fastcgi_ignore_headers Cache-Control Expires Set-Cookie; +fastcgi_keep_conn on; diff --git a/wo/cli/templates/info_mysql.mustache b/wo/cli/templates/info_mysql.mustache new file mode 100644 index 0000000..8d7ac95 --- /dev/null +++ b/wo/cli/templates/info_mysql.mustache @@ -0,0 +1,10 @@ + +MySQL ({{version}}) on {{host}}: + +port {{port}} +wait_timeout {{wait_timeout}} +interactive_timeout {{interactive_timeout}} +max_used_connections {{max_used_connections}} +datadir {{datadir}} +socket {{socket}} +my.cnf [PATH] /etc/mysql/conf.d/my.cnf diff --git a/wo/cli/templates/info_nginx.mustache b/wo/cli/templates/info_nginx.mustache new file mode 100644 index 0000000..6420405 --- /dev/null +++ b/wo/cli/templates/info_nginx.mustache @@ -0,0 +1,10 @@ + +NGINX ({{version}}): + +user {{user}} +worker_processes {{worker_processes}} +worker_connections {{worker_connections}} +keepalive_timeout {{keepalive_timeout}} +fastcgi_read_timeout {{fastcgi_read_timeout}} +client_max_body_size {{client_max_body_size}} +allow {{allow}} diff --git a/wo/cli/templates/info_php.mustache b/wo/cli/templates/info_php.mustache new file mode 100644 index 0000000..1638cf8 --- /dev/null +++ b/wo/cli/templates/info_php.mustache @@ -0,0 +1,35 @@ + +PHP ({{version}}): + +user {{user}} +expose_php {{expose_php}} +memory_limit {{memory_limit}} +post_max_size {{post_max_size}} +upload_max_filesize {{upload_max_filesize}} +max_execution_time {{max_execution_time}} + +Information about www.conf +ping.path {{www_ping_path}} +pm.status_path {{www_pm_status_path}} +process_manager {{www_pm}} +pm.max_requests {{www_pm_max_requests}} +pm.max_children {{www_pm_max_children}} +pm.start_servers {{www_pm_start_servers}} +pm.min_spare_servers {{www_pm_min_spare_servers}} +pm.max_spare_servers {{www_pm_max_spare_servers}} +request_terminate_timeout {{www_request_terminate_timeout}} +xdebug.profiler_enable_trigger {{www_xdebug_profiler_enable_trigger}} +listen {{www_listen}} + +Information about debug.conf +ping.path {{debug_ping_path}} +pm.status_path {{debug_pm_status_path}} +process_manager {{debug_pm}} +pm.max_requests {{debug_pm_max_requests}} +pm.max_children {{debug_pm_max_children}} +pm.start_servers {{debug_pm_start_servers}} +pm.min_spare_servers {{debug_pm_min_spare_servers}} +pm.max_spare_servers {{debug_pm_max_spare_servers}} +request_terminate_timeout {{debug_request_terminate_timeout}} +xdebug.profiler_enable_trigger {{debug_xdebug_profiler_enable_trigger}} +listen {{debug_listen}} diff --git a/wo/cli/templates/locations-php7.mustache b/wo/cli/templates/locations-php7.mustache new file mode 100644 index 0000000..f7de530 --- /dev/null +++ b/wo/cli/templates/locations-php7.mustache @@ -0,0 +1,68 @@ +# NGINX CONFIGURATION FOR COMMON LOCATION +# DO NOT MODIFY, ALL CHANGES WILL BE LOST AFTER AN WordOps (wo) UPDATE +# Basic locations files +location = /favicon.ico { + access_log off; + log_not_found off; + expires max; +} +location = /robots.txt { + # Some WordPress plugin gererate robots.txt file + # Refer #340 issue + try_files $uri $uri/ /index.php?$args; + access_log off; + log_not_found off; +} +# Cache static files +location ~* \.(ogg|ogv|svg|svgz|eot|otf|woff|mp4|ttf|css|rss|atom|js|jpg|jpeg|gif|png|ico|zip|tgz|gz|rar|bz2|doc|xls|exe|ppt|tar|mid|midi|wav|bmp|rtf|swf)$ { + add_header "Access-Control-Allow-Origin" "*"; + access_log off; + log_not_found off; + expires max; +} +# Security settings for better privacy +# Deny hidden files +location ~ /\.well-known { + allow all; +} +location ~ /\. { + deny all; + access_log off; + log_not_found off; +} +# Deny backup extensions & log files +location ~* ^.+\.(bak|log|old|orig|original|php#|php~|php_bak|save|swo|swp|sql)$ { + deny all; + access_log off; + log_not_found off; +} +# Return 403 forbidden for readme.(txt|html) or license.(txt|html) or example.(txt|html) +if ($uri ~* "^.+(readme|license|example)\.(txt|html)$") { + return 403; +} +# Status pages +location = /nginx_status { + stub_status on; + access_log off; + include common/acl.conf; +} +location ~ ^/(status|ping)$ { + include fastcgi_params; + fastcgi_pass php7; + include common/acl.conf; +} +# WordOps (wo) utilities +# phpMyAdmin settings +location = /pma { + return 301 https://$host:22222/db/pma; +} +location = /phpMyAdmin { + return 301 https://$host:22222/db/pma; +} +location = /phpmyadmin { + return 301 https://$host:22222/db/pma; +} +# Adminer settings +location = /adminer { + return 301 https://$host:22222/db/adminer; +} diff --git a/wo/cli/templates/locations.mustache b/wo/cli/templates/locations.mustache new file mode 100644 index 0000000..1acfb17 --- /dev/null +++ b/wo/cli/templates/locations.mustache @@ -0,0 +1,68 @@ +# NGINX CONFIGURATION FOR COMMON LOCATION +# DO NOT MODIFY, ALL CHANGES WILL BE LOST AFTER AN WordOps (wo) UPDATE +# Basic locations files +location = /favicon.ico { + access_log off; + log_not_found off; + expires max; +} +location = /robots.txt { + # Some WordPress plugin gererate robots.txt file + # Refer #340 issue + try_files $uri $uri/ /index.php?$args; + access_log off; + log_not_found off; +} +# Cache static files +location ~* \.(ogg|ogv|svg|svgz|eot|otf|woff|mp4|ttf|css|rss|atom|js|jpg|jpeg|gif|png|ico|zip|tgz|gz|rar|bz2|doc|xls|exe|ppt|tar|mid|midi|wav|bmp|rtf|swf)$ { + add_header "Access-Control-Allow-Origin" "*"; + access_log off; + log_not_found off; + expires max; +} +# Security settings for better privacy +# Deny hidden files +location ~ /\.well-known { + allow all; +} +location ~ /\. { + deny all; + access_log off; + log_not_found off; +} +# Deny backup extensions & log files +location ~* ^.+\.(bak|log|old|orig|original|php#|php~|php_bak|save|swo|swp|sql)$ { + deny all; + access_log off; + log_not_found off; +} +# Return 403 forbidden for readme.(txt|html) or license.(txt|html) or example.(txt|html) +if ($uri ~* "^.+(readme|license|example)\.(txt|html)$") { + return 403; +} +# Status pages +location = /nginx_status { + stub_status on; + access_log off; + include common/acl.conf; +} +location ~ ^/(status|ping)$ { + include fastcgi_params; + fastcgi_pass php; + include common/acl.conf; +} +# WordOps (wo) utilities +# phpMyAdmin settings +location = /pma { + return 301 https://$host:22222/db/pma; +} +location = /phpMyAdmin { + return 301 https://$host:22222/db/pma; +} +location = /phpmyadmin { + return 301 https://$host:22222/db/pma; +} +# Adminer settings +location = /adminer { + return 301 https://$host:22222/db/adminer; +} diff --git a/wo/cli/templates/nginx-core.mustache b/wo/cli/templates/nginx-core.mustache new file mode 100644 index 0000000..cef16cc --- /dev/null +++ b/wo/cli/templates/nginx-core.mustache @@ -0,0 +1,60 @@ +## +# WordOps Settings +## + +server_tokens off; +reset_timedout_connection on; +add_header X-Powered-By "WordOps {{version}}"; + +# Limit Request +limit_req_status 403; +limit_req_zone $binary_remote_addr zone=one:10m rate=1r/s; + +# Proxy Settings +# set_real_ip_from proxy-server-ip; +# real_ip_header X-Forwarded-For; + +fastcgi_read_timeout 300; +client_max_body_size 100m; + +# SSL Settings +ssl_protocols TLSv1.1 TLSv1.2; +ssl_session_cache shared:SSL:10m; +ssl_session_timeout 10m; +{{#Ubuntu}} +ssl_prefer_server_ciphers on; +{{/Ubuntu}} +ssl_ciphers "ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-CHACHA20-POLY1305:EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH"; + +# Log format Settings +log_format rt_cache '$remote_addr $upstream_response_time $upstream_cache_status [$time_local] ' +'$http_host "$request" $status $body_bytes_sent ' +'"$http_referer" "$http_user_agent"'; + + +# GZip settings +gzip_vary on; +gzip_proxied any; +gzip_comp_level 6; +gzip_buffers 16 8k; +gzip_http_version 1.1; +# Compress all output labeled with one of the following MIME-types. +gzip_types + application/atom+xml + application/javascript + application/json + application/rss+xml + application/vnd.ms-fontobject + application/x-font-ttf + application/x-web-app-manifest+json + application/xhtml+xml + application/xml + font/opentype + image/svg+xml + image/x-icon + text/css + text/plain + text/x-component + text/xml + text/javascript; + # text/html is always compressed by HttpGzipModule diff --git a/wo/cli/templates/php-fpm.mustache b/wo/cli/templates/php-fpm.mustache new file mode 100644 index 0000000..eae67a4 --- /dev/null +++ b/wo/cli/templates/php-fpm.mustache @@ -0,0 +1,5 @@ +[global] +pid = {{pid}} +error_log = {{error_log}} +log_level = notice +include = {{include}} \ No newline at end of file diff --git a/wo/cli/templates/php-hhvm.mustache b/wo/cli/templates/php-hhvm.mustache new file mode 100644 index 0000000..3c7a7bb --- /dev/null +++ b/wo/cli/templates/php-hhvm.mustache @@ -0,0 +1,10 @@ +# PHP NGINX CONFIGURATION +# DO NOT MODIFY, ALL CHANGES WILL BE LOST AFTER AN WordOps (wo) UPDATE +location / { + try_files $uri $uri/ /index.php?$args; +} +location ~ \.php$ { + try_files $uri =404; + include fastcgi_params; + fastcgi_pass hhvm; +} diff --git a/wo/cli/templates/php.mustache b/wo/cli/templates/php.mustache new file mode 100644 index 0000000..de0f096 --- /dev/null +++ b/wo/cli/templates/php.mustache @@ -0,0 +1,10 @@ +# PHP NGINX CONFIGURATION +# DO NOT MODIFY, ALL CHANGES WILL BE LOST AFTER AN WordOps (wo) UPDATE +location / { + try_files $uri $uri/ /index.php?$args; +} +location ~ \.php$ { + try_files $uri =404; + include fastcgi_params; + fastcgi_pass php; +} diff --git a/wo/cli/templates/php7.mustache b/wo/cli/templates/php7.mustache new file mode 100644 index 0000000..d9f9d9a --- /dev/null +++ b/wo/cli/templates/php7.mustache @@ -0,0 +1,10 @@ +# PHP NGINX CONFIGURATION +# DO NOT MODIFY, ALL CHANGES WILL BE LOST AFTER AN WordOps (wo) UPDATE +location / { + try_files $uri $uri/ /index.php?$args; +} +location ~ \.php$ { + try_files $uri =404; + include fastcgi_params; + fastcgi_pass php7; +} diff --git a/wo/cli/templates/redis-hhvm.mustache b/wo/cli/templates/redis-hhvm.mustache new file mode 100644 index 0000000..8ebeaf8 --- /dev/null +++ b/wo/cli/templates/redis-hhvm.mustache @@ -0,0 +1,58 @@ +# Redis NGINX CONFIGURATION +# DO NOT MODIFY, ALL CHANGES WILL BE LOST AFTER AN WordOps (wo) UPDATE +set $skip_cache 0; +# POST requests and URL with a query string should always go to php +if ($request_method = POST) { + set $skip_cache 1; +} +if ($query_string != "") { + set $skip_cache 1; +} +# Don't cache URL containing the following segments +if ($request_uri ~* "(/wp-admin/|/xmlrpc.php|wp-.*\.php|index.php|/feed/|.*sitemap.*\.xml)") { + set $skip_cache 1; +} +# Don't use the cache for logged in users or recent commenter or customer with items in cart +if ($http_cookie ~* "comment_author|wordpress_[a-f0-9]+|wp-postpass|wordpress_no_cache|wordpress_logged_in|[a-z0-9]+_items_in_cart") { + set $skip_cache 1; +} +# Use cached or actual file if they exists, Otherwise pass request to WordPress +location / { + try_files $uri $uri/ /index.php?$args; +} + +location /redis-fetch { + internal ; + set $redis_key $args; + redis_pass redis; +} +location /redis-store { + internal ; + set_unescape_uri $key $arg_key ; + redis2_query set $key $echo_request_body; + redis2_query expire $key 14400; + redis2_pass redis; + +} + +location ~ \.php$ { + set $key "nginx-cache:$scheme$request_method$host$request_uri"; + try_files $uri =404; + + srcache_fetch_skip $skip_cache; + srcache_store_skip $skip_cache; + + srcache_response_cache_control off; + + set_escape_uri $escaped_key $key; + + srcache_fetch GET /redis-fetch $key; + srcache_store PUT /redis-store key=$escaped_key; + + more_set_headers 'X-SRCache-Fetch-Status $srcache_fetch_status'; + more_set_headers 'X-SRCache-Store-Status $srcache_store_status'; + + include fastcgi_params; + fastcgi_param HTTP_ACCEPT_ENCODING ""; + fastcgi_pass hhvm; +} diff --git a/wo/cli/templates/redis-php7.mustache b/wo/cli/templates/redis-php7.mustache new file mode 100644 index 0000000..9134b84 --- /dev/null +++ b/wo/cli/templates/redis-php7.mustache @@ -0,0 +1,56 @@ +# Redis NGINX CONFIGURATION +# DO NOT MODIFY, ALL CHANGES WILL BE LOST AFTER AN WordOps (wo) UPDATE +set $skip_cache 0; +# POST requests and URL with a query string should always go to php +if ($request_method = POST) { + set $skip_cache 1; +} +if ($query_string != "") { + set $skip_cache 1; +} +# Don't cache URL containing the following segments +if ($request_uri ~* "(/wp-admin/|/xmlrpc.php|wp-.*\.php|index.php|/feed/|.*sitemap.*\.xml)") { + set $skip_cache 1; +} +# Don't use the cache for logged in users or recent commenter or customer with items in cart +if ($http_cookie ~* "comment_author|wordpress_[a-f0-9]+|wp-postpass|wordpress_no_cache|wordpress_logged_in|[a-z0-9]+_items_in_cart") { + set $skip_cache 1; +} +# Use cached or actual file if they exists, Otherwise pass request to WordPress +location / { + try_files $uri $uri/ /index.php?$args; +} + +location /redis-fetch { + internal ; + set $redis_key $args; + redis_pass redis; +} +location /redis-store { + internal ; + set_unescape_uri $key $arg_key ; + redis2_query set $key $echo_request_body; + redis2_query expire $key 14400; + redis2_pass redis; +} + +location ~ \.php$ { + set $key "nginx-cache:$scheme$request_method$host$request_uri"; + try_files $uri =404; + + srcache_fetch_skip $skip_cache; + srcache_store_skip $skip_cache; + + srcache_response_cache_control off; + + set_escape_uri $escaped_key $key; + + srcache_fetch GET /redis-fetch $key; + srcache_store PUT /redis-store key=$escaped_key; + + more_set_headers 'X-SRCache-Fetch-Status $srcache_fetch_status'; + more_set_headers 'X-SRCache-Store-Status $srcache_store_status'; + + include fastcgi_params; + fastcgi_pass php7; +} diff --git a/wo/cli/templates/redis.mustache b/wo/cli/templates/redis.mustache new file mode 100644 index 0000000..8335ea8 --- /dev/null +++ b/wo/cli/templates/redis.mustache @@ -0,0 +1,57 @@ +# Redis NGINX CONFIGURATION +# DO NOT MODIFY, ALL CHANGES WILL BE LOST AFTER AN WordOps (wo) UPDATE +set $skip_cache 0; +# POST requests and URL with a query string should always go to php +if ($request_method = POST) { + set $skip_cache 1; +} +if ($query_string != "") { + set $skip_cache 1; +} +# Don't cache URL containing the following segments +if ($request_uri ~* "(/wp-admin/|/xmlrpc.php|wp-.*\.php|index.php|/feed/|.*sitemap.*\.xml)") { + set $skip_cache 1; +} +# Don't use the cache for logged in users or recent commenter or customer with items in cart +if ($http_cookie ~* "comment_author|wordpress_[a-f0-9]+|wp-postpass|wordpress_no_cache|wordpress_logged_in|[a-z0-9]+_items_in_cart") { + set $skip_cache 1; +} +# Use cached or actual file if they exists, Otherwise pass request to WordPress +location / { + try_files $uri $uri/ /index.php?$args; +} + +location /redis-fetch { + internal ; + set $redis_key $args; + redis_pass redis; +} +location /redis-store { + internal ; + set_unescape_uri $key $arg_key ; + redis2_query set $key $echo_request_body; + redis2_query expire $key 14400; + redis2_pass redis; + +} + +location ~ \.php$ { + set $key "nginx-cache:$scheme$request_method$host$request_uri"; + try_files $uri =404; + + srcache_fetch_skip $skip_cache; + srcache_store_skip $skip_cache; + + srcache_response_cache_control off; + + set_escape_uri $escaped_key $key; + + srcache_fetch GET /redis-fetch $key; + srcache_store PUT /redis-store key=$escaped_key; + + more_set_headers 'X-SRCache-Fetch-Status $srcache_fetch_status'; + more_set_headers 'X-SRCache-Store-Status $srcache_store_status'; + + include fastcgi_params; + fastcgi_pass php; +} diff --git a/wo/cli/templates/siteinfo.mustache b/wo/cli/templates/siteinfo.mustache new file mode 100644 index 0000000..3d0acbb --- /dev/null +++ b/wo/cli/templates/siteinfo.mustache @@ -0,0 +1,15 @@ +Information about {{domain}}: + +Nginx configuration {{type}} {{enable}} +{{#php_version}}PHP Version {{php_version}}{{/php_version}} +{{#hhvm}}HHVM {{hhvm}}{{/hhvm}} +{{#ssl}}SSL {{ssl}}{{/ssl}} +{{#sslprovider}}SSL PROVIDER {{sslprovider}}{{/sslprovider}} +{{#sslexpiry}}SSL EXPIRY DATE {{sslexpiry}}{{/sslexpiry}} +access_log {{accesslog}} +error_log {{errorlog}} +{{#webroot}}Webroot {{webroot}}{{/webroot}} +{{#dbname}}DB_NAME {{dbname}}{{/dbname}} +{{#dbname}}DB_USER {{dbuser}}{{/dbname}} +{{#dbname}}DB_PASS {{dbpass}}{{/dbname}} +{{#tablepref}}table_prefix {{tableprefix}}{{/tablepref}} diff --git a/wo/cli/templates/upstream.mustache b/wo/cli/templates/upstream.mustache new file mode 100644 index 0000000..473fd1b --- /dev/null +++ b/wo/cli/templates/upstream.mustache @@ -0,0 +1,26 @@ +# Common upstream settings +upstream php { +# server unix:/run/php5-fpm.sock; +server 127.0.0.1:{{php}}; +} +upstream debug { +# Debug Pool +server 127.0.0.1:{{debug}}; +} +{{#php7conf}} +upstream php7 { +server 127.0.0.1:{{php7}}; +} +upstream debug7 { +# Debug Pool +server 127.0.0.1:{{debug7}}; +} +{{/php7conf}} + +{{#hhvmconf}} +upstream hhvm { +# HHVM Pool +server 127.0.0.1:{{hhvm}}; +server 127.0.0.1:{{php}} backup; +} +{{/hhvmconf}} diff --git a/wo/cli/templates/virtualconf-php7.mustache b/wo/cli/templates/virtualconf-php7.mustache new file mode 100644 index 0000000..13b663a --- /dev/null +++ b/wo/cli/templates/virtualconf-php7.mustache @@ -0,0 +1,47 @@ + +server { + + {{#multisite}} + # Uncomment the following line for domain mapping + # listen 80 default_server; + {{/multisite}} + + server_name {{^vma}}{{^rc}}{{site_name}}{{/rc}}{{/vma}} {{#vma}}vma.*{{/vma}} {{#rc}}webmail.*{{/rc}} {{^vma}}{{^rc}}{{#multisite}}*{{/multisite}}{{^multisite}}www{{/multisite}}.{{site_name}}{{/rc}}{{/vma}}; + + {{#multisite}} + # Uncomment the following line for domain mapping + #server_name_in_redirect off; + {{/multisite}} + + access_log /var/log/nginx/{{site_name}}.access.log {{^wpredis}}{{^static}}rt_cache{{/static}}{{/wpredis}}{{#wpredis}}rt_cache_redis{{/wpredis}}; + error_log /var/log/nginx/{{site_name}}.error.log; + + {{#proxy}} + add_header X-Proxy-Cache $upstream_cache_status; + location / { + proxy_pass http://{{host}}:{{port}}; + proxy_redirect off; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + {{/proxy}} + + {{^proxy}} + {{^vma}}{{^rc}}root {{webroot}}/htdocs;{{/rc}}{{/vma}} + + {{^proxy}}index {{^static}}index.php{{/static}} index.html index.htm;{{/proxy}} + + {{#static}} + location / { + try_files $uri $uri/ =404; + } + {{/static}} + + {{^static}}include {{^hhvm}}{{#basic}}common/php7.conf;{{/basic}}{{#wpfc}}common/wpfc-php7.conf;{{/wpfc}} {{#wpsc}}common/wpsc-php7.conf;{{/wpsc}}{{#wpredis}}common/redis-php7.conf;{{/wpredis}} {{/hhvm}}{{#hhvm}}{{#basic}}common/php-hhvm.conf;{{/basic}}{{#wpfc}}common/wpfc-hhvm.conf;{{/wpfc}} {{#wpsc}}common/wpsc-hhvm.conf;{{/wpsc}}{{#wpredis}}common/redis-hhvm.conf;{{/wpredis}} {{/hhvm}} + {{#wpsubdir}}include common/wpsubdir.conf;{{/wpsubdir}}{{/static}} + {{#wp}}include common/wpcommon-php7.conf;{{/wp}} + {{^proxy}}include common/locations-php7.conf;{{/proxy}} + {{^vma}}{{^rc}}include {{webroot}}/conf/nginx/*.conf;{{/rc}}{{/vma}} + {{/proxy}} +} diff --git a/wo/cli/templates/virtualconf.mustache b/wo/cli/templates/virtualconf.mustache new file mode 100644 index 0000000..47702e3 --- /dev/null +++ b/wo/cli/templates/virtualconf.mustache @@ -0,0 +1,46 @@ + +server { + + {{#multisite}} + # Uncomment the following line for domain mapping + # listen 80 default_server; + {{/multisite}} + + server_name {{^vma}}{{^rc}}{{site_name}}{{/rc}}{{/vma}} {{#vma}}vma.*{{/vma}} {{#rc}}webmail.*{{/rc}} {{^vma}}{{^rc}}{{#multisite}}*{{/multisite}}{{^multisite}}www{{/multisite}}.{{site_name}}{{/rc}}{{/vma}}; + + {{#multisite}} + # Uncomment the following line for domain mapping + #server_name_in_redirect off; + {{/multisite}} + + access_log /var/log/nginx/{{site_name}}.access.log {{^wpredis}}{{^static}}rt_cache{{/static}}{{/wpredis}}{{#wpredis}}rt_cache_redis{{/wpredis}}; + error_log /var/log/nginx/{{site_name}}.error.log; + + {{#proxy}} + add_header X-Proxy-Cache $upstream_cache_status; + location / { + proxy_pass http://{{host}}:{{port}}; + proxy_redirect off; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + {{/proxy}} + + {{^proxy}} + {{^vma}}{{^rc}}root {{webroot}}/htdocs;{{/rc}}{{/vma}} + + {{^proxy}}index {{^static}}index.php{{/static}} index.html index.htm;{{/proxy}} + + {{#static}} + location / { + try_files $uri $uri/ =404; + } + {{/static}} + + {{^static}}include {{^hhvm}}{{#basic}}common/php.conf;{{/basic}}{{#wpfc}}common/wpfc.conf;{{/wpfc}} {{#wpsc}}common/wpsc.conf;{{/wpsc}}{{#wpredis}}common/redis.conf;{{/wpredis}} {{/hhvm}}{{#hhvm}}{{#basic}}common/php-hhvm.conf;{{/basic}}{{#wpfc}}common/wpfc-hhvm.conf;{{/wpfc}} {{#wpsc}}common/wpsc-hhvm.conf;{{/wpsc}}{{#wpredis}}common/redis-hhvm.conf;{{/wpredis}} {{/hhvm}} {{#wpsubdir}}include common/wpsubdir.conf;{{/wpsubdir}}{{/static}} + {{#wp}}include common/wpcommon.conf;{{/wp}} + {{^proxy}}include common/locations.conf;{{/proxy}} + {{^vma}}{{^rc}}include {{webroot}}/conf/nginx/*.conf;{{/rc}}{{/vma}} + {{/proxy}} +} diff --git a/wo/cli/templates/wo-plus.mustache b/wo/cli/templates/wo-plus.mustache new file mode 100644 index 0000000..7dfbb9a --- /dev/null +++ b/wo/cli/templates/wo-plus.mustache @@ -0,0 +1,83 @@ +## +# WordOps Settings +## + + +tcp_nopush on; +tcp_nodelay on; +types_hash_max_size 2048; + +server_tokens off; +reset_timedout_connection on; +add_header X-Powered-By "WordOps {{ version }}"; + +# Limit Request +limit_req_status 403; +limit_req_zone $binary_remote_addr zone=one:10m rate=1r/s; + +# Proxy Settings +# set_real_ip_from proxy-server-ip; +# real_ip_header X-Forwarded-For; + +fastcgi_read_timeout 300; +client_max_body_size 100m; + +## +# SSL Settings +## + +ssl_session_cache shared:SSL:5m; +ssl_session_timeout 10m; +ssl_prefer_server_ciphers on; +ssl_ciphers "ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-CHACHA20-POLY1305:EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH"; +ssl_protocols TLSv1.1 TLSv1.2; + +## +# Basic Settings +## +server_names_hash_bucket_size 16384; +# server_name_in_redirect off; + + +## +# Logging Settings +## + +access_log /var/log/nginx/access.log; +error_log /var/log/nginx/error.log; + +# Log format Settings +log_format rt_cache '$remote_addr $upstream_response_time $upstream_cache_status [$time_local] ' +'$http_host "$request" $status $body_bytes_sent ' +'"$http_referer" "$http_user_agent" "$request_body"'; + +## +# Gzip Settings +## + +gzip on; +gzip_disable "msie6"; + +gzip_vary on; +gzip_proxied any; +gzip_comp_level 6; +gzip_buffers 16 8k; +gzip_http_version 1.1; +gzip_types + application/atom+xml + application/javascript + application/json + application/rss+xml + application/vnd.ms-fontobject + application/x-font-ttf + application/x-web-app-manifest+json + application/xhtml+xml + application/xml + font/opentype + image/svg+xml + image/x-icon + text/css + text/plain + text/x-component + text/xml + text/javascript; diff --git a/wo/cli/templates/wpcommon-php7.mustache b/wo/cli/templates/wpcommon-php7.mustache new file mode 100644 index 0000000..f43f426 --- /dev/null +++ b/wo/cli/templates/wpcommon-php7.mustache @@ -0,0 +1,21 @@ +# WordPress COMMON SETTINGS +# DO NOT MODIFY, ALL CHANGES WILL BE LOST AFTER AN WordOps (wo) UPDATE +# Limit access to avoid brute force attack +location = /wp-login.php { + limit_req zone=one burst=1 nodelay; + include fastcgi_params; + fastcgi_pass php7; +} +# Disable wp-config.txt +location = /wp-config.txt { + deny all; + access_log off; + log_not_found off; +} +# Disallow php in upload folder +location /wp-content/uploads/ { + location ~ \.php$ { + #Prevent Direct Access Of PHP Files From Web Browsers + deny all; + } +} diff --git a/wo/cli/templates/wpcommon.mustache b/wo/cli/templates/wpcommon.mustache new file mode 100644 index 0000000..e866a99 --- /dev/null +++ b/wo/cli/templates/wpcommon.mustache @@ -0,0 +1,21 @@ +# WordPress COMMON SETTINGS +# DO NOT MODIFY, ALL CHANGES WILL BE LOST AFTER AN WordOps (wo) UPDATE +# Limit access to avoid brute force attack +location = /wp-login.php { + limit_req zone=one burst=1 nodelay; + include fastcgi_params; + fastcgi_pass php; +} +# Disable wp-config.txt +location = /wp-config.txt { + deny all; + access_log off; + log_not_found off; +} +# Disallow php in upload folder +location /wp-content/uploads/ { + location ~ \.php$ { + #Prevent Direct Access Of PHP Files From Web Browsers + deny all; + } +} diff --git a/wo/cli/templates/wpfc-hhvm.mustache b/wo/cli/templates/wpfc-hhvm.mustache new file mode 100644 index 0000000..424e95b --- /dev/null +++ b/wo/cli/templates/wpfc-hhvm.mustache @@ -0,0 +1,37 @@ +# WPFC NGINX CONFIGURATION +# DO NOT MODIFY, ALL CHANGES WILL BE LOST AFTER AN WordOps (wo) UPDATE +set $skip_cache 0; +# POST requests and URL with a query string should always go to php +if ($request_method = POST) { + set $skip_cache 1; +} +if ($query_string != "") { + set $skip_cache 1; +} +# Don't cache URL containing the following segments +if ($request_uri ~* "(/wp-admin/|/xmlrpc.php|wp-.*\.php|index.php|/feed/|.*sitemap.*\.xml)") { + set $skip_cache 1; +} +# Don't use the cache for logged in users or recent commenter or customer with items in cart +if ($http_cookie ~* "comment_author|wordpress_[a-f0-9]+|wp-postpass|wordpress_no_cache|wordpress_logged_in|[a-z0-9]+_items_in_cart") { + set $skip_cache 1; +} +# Use cached or actual file if they exists, Otherwise pass request to WordPress +location / { + try_files $uri $uri/ /index.php?$args; +} +location ~ ^/wp-content/cache/minify/(.+\.(css|js))$ { + try_files $uri /wp-content/plugins/w3-total-cache/pub/minify.php?file=$1; +} +location ~ \.php$ { + try_files $uri =404; + include fastcgi_params; + fastcgi_pass hhvm; + fastcgi_cache_bypass $skip_cache; + fastcgi_no_cache $skip_cache; + fastcgi_cache WORDPRESS; +} +location ~ /purge(/.*) { + fastcgi_cache_purge WORDPRESS "$scheme$request_method$host$1"; + access_log off; +} diff --git a/wo/cli/templates/wpfc-php7.mustache b/wo/cli/templates/wpfc-php7.mustache new file mode 100644 index 0000000..108a3e8 --- /dev/null +++ b/wo/cli/templates/wpfc-php7.mustache @@ -0,0 +1,37 @@ +# WPFC NGINX CONFIGURATION +# DO NOT MODIFY, ALL CHANGES WILL BE LOST AFTER AN WordOps (wo) UPDATE +set $skip_cache 0; +# POST requests and URL with a query string should always go to php +if ($request_method = POST) { + set $skip_cache 1; +} +if ($query_string != "") { + set $skip_cache 1; +} +# Don't cache URL containing the following segments +if ($request_uri ~* "(/wp-admin/|/xmlrpc.php|wp-.*\.php|index.php|/feed/|.*sitemap.*\.xml)") { + set $skip_cache 1; +} +# Don't use the cache for logged in users or recent commenter or customer with items in cart +if ($http_cookie ~* "comment_author|wordpress_[a-f0-9]+|wp-postpass|wordpress_no_cache|wordpress_logged_in|[a-z0-9]+_items_in_cart") { + set $skip_cache 1; +} +# Use cached or actual file if they exists, Otherwise pass request to WordPress +location / { + try_files $uri $uri/ /index.php?$args; +} +location ~ ^/wp-content/cache/minify/(.+\.(css|js))$ { + try_files $uri /wp-content/plugins/w3-total-cache/pub/minify.php?file=$1; +} +location ~ \.php$ { + try_files $uri =404; + include fastcgi_params; + fastcgi_pass php7; + fastcgi_cache_bypass $skip_cache; + fastcgi_no_cache $skip_cache; + fastcgi_cache WORDPRESS; +} +location ~ /purge(/.*) { + fastcgi_cache_purge WORDPRESS "$scheme$request_method$host$1"; + access_log off; +} diff --git a/wo/cli/templates/wpfc.mustache b/wo/cli/templates/wpfc.mustache new file mode 100644 index 0000000..6406b71 --- /dev/null +++ b/wo/cli/templates/wpfc.mustache @@ -0,0 +1,37 @@ +# WPFC NGINX CONFIGURATION +# DO NOT MODIFY, ALL CHANGES WILL BE LOST AFTER AN WordOps (wo) UPDATE +set $skip_cache 0; +# POST requests and URL with a query string should always go to php +if ($request_method = POST) { + set $skip_cache 1; +} +if ($query_string != "") { + set $skip_cache 1; +} +# Don't cache URL containing the following segments +if ($request_uri ~* "(/wp-admin/|/xmlrpc.php|wp-.*\.php|index.php|/feed/|.*sitemap.*\.xml)") { + set $skip_cache 1; +} +# Don't use the cache for logged in users or recent commenter or customer with items in cart +if ($http_cookie ~* "comment_author|wordpress_[a-f0-9]+|wp-postpass|wordpress_no_cache|wordpress_logged_in|[a-z0-9]+_items_in_cart") { + set $skip_cache 1; +} +# Use cached or actual file if they exists, Otherwise pass request to WordPress +location / { + try_files $uri $uri/ /index.php?$args; +} +location ~ ^/wp-content/cache/minify/(.+\.(css|js))$ { + try_files $uri /wp-content/plugins/w3-total-cache/pub/minify.php?file=$1; +} +location ~ \.php$ { + try_files $uri =404; + include fastcgi_params; + fastcgi_pass php; + fastcgi_cache_bypass $skip_cache; + fastcgi_no_cache $skip_cache; + fastcgi_cache WORDPRESS; +} +location ~ /purge(/.*) { + fastcgi_cache_purge WORDPRESS "$scheme$request_method$host$1"; + access_log off; +} diff --git a/wo/cli/templates/wpsc-hhvm.mustache b/wo/cli/templates/wpsc-hhvm.mustache new file mode 100644 index 0000000..8006d52 --- /dev/null +++ b/wo/cli/templates/wpsc-hhvm.mustache @@ -0,0 +1,31 @@ +# WPSC NGINX CONFIGURATION +# DO NOT MODIFY, ALL CHANGES WILL BE LOST AFTER AN WordOps (wo) UPDATE +set $cache_uri $request_uri; +# POST requests and URL with a query string should always go to php +if ($request_method = POST) { + set $cache_uri 'null cache'; +} +if ($query_string != "") { + set $cache_uri 'null cache'; +} +# Don't cache URL containing the following segments +if ($request_uri ~* "(/wp-admin/|/xmlrpc.php|wp-.*\.php|index.php|/feed/|.*sitemap.*\.xml)") { + set $cache_uri 'null cache'; +} +# Don't use the cache for logged in users or recent commenter or customer with items in cart +if ($http_cookie ~* "comment_author|wordpress_[a-f0-9]+|wp-postpass|wordpress_no_cache|wordpress_logged_in|[a-z0-9]+_items_in_cart") { + set $cache_uri 'null cache'; +} +# Use cached or actual file if they exists, Otherwise pass request to WordPress +location / { + # If we add index.php?$args its break WooCommerce like plugins + # Ref: #330 + try_files /wp-content/cache/supercache/$http_host/$cache_uri/index.html $uri $uri/ /index.php; +} +location ~ \.php$ { + try_files $uri =404; + include fastcgi_params; + fastcgi_pass hhvm; + # Following line is needed by WP Super Cache plugin + fastcgi_param SERVER_NAME $http_host; +} diff --git a/wo/cli/templates/wpsc-php7.mustache b/wo/cli/templates/wpsc-php7.mustache new file mode 100644 index 0000000..7cca3f8 --- /dev/null +++ b/wo/cli/templates/wpsc-php7.mustache @@ -0,0 +1,31 @@ +# WPSC NGINX CONFIGURATION +# DO NOT MODIFY, ALL CHANGES WILL BE LOST AFTER AN WordOps (wo) UPDATE +set $cache_uri $request_uri; +# POST requests and URL with a query string should always go to php +if ($request_method = POST) { + set $cache_uri 'null cache'; +} +if ($query_string != "") { + set $cache_uri 'null cache'; +} +# Don't cache URL containing the following segments +if ($request_uri ~* "(/wp-admin/|/xmlrpc.php|wp-.*\.php|index.php|/feed/|.*sitemap.*\.xml)") { + set $cache_uri 'null cache'; +} +# Don't use the cache for logged in users or recent commenter or customer with items in cart +if ($http_cookie ~* "comment_author|wordpress_[a-f0-9]+|wp-postpass|wordpress_no_cache|wordpress_logged_in|[a-z0-9]+_items_in_cart") { + set $cache_uri 'null cache'; +} +# Use cached or actual file if they exists, Otherwise pass request to WordPress +location / { + # If we add index.php?$args its break WooCommerce like plugins + # Ref: #330 + try_files /wp-content/cache/supercache/$http_host/$cache_uri/index.html $uri $uri/ /index.php; +} +location ~ \.php$ { + try_files $uri =404; + include fastcgi_params; + fastcgi_pass php7; + # Following line is needed by WP Super Cache plugin + fastcgi_param SERVER_NAME $http_host; +} diff --git a/wo/cli/templates/wpsc.mustache b/wo/cli/templates/wpsc.mustache new file mode 100644 index 0000000..e5b1648 --- /dev/null +++ b/wo/cli/templates/wpsc.mustache @@ -0,0 +1,31 @@ +# WPSC NGINX CONFIGURATION +# DO NOT MODIFY, ALL CHANGES WILL BE LOST AFTER AN WordOps (wo) UPDATE +set $cache_uri $request_uri; +# POST requests and URL with a query string should always go to php +if ($request_method = POST) { + set $cache_uri 'null cache'; +} +if ($query_string != "") { + set $cache_uri 'null cache'; +} +# Don't cache URL containing the following segments +if ($request_uri ~* "(/wp-admin/|/xmlrpc.php|wp-.*\.php|index.php|/feed/|.*sitemap.*\.xml)") { + set $cache_uri 'null cache'; +} +# Don't use the cache for logged in users or recent commenter or customer with items in cart +if ($http_cookie ~* "comment_author|wordpress_[a-f0-9]+|wp-postpass|wordpress_no_cache|wordpress_logged_in|[a-z0-9]+_items_in_cart") { + set $cache_uri 'null cache'; +} +# Use cached or actual file if they exists, Otherwise pass request to WordPress +location / { + # If we add index.php?$args its break WooCommerce like plugins + # Ref: #330 + try_files /wp-content/cache/supercache/$http_host/$cache_uri/index.html $uri $uri/ /index.php; +} +location ~ \.php$ { + try_files $uri =404; + include fastcgi_params; + fastcgi_pass php; + # Following line is needed by WP Super Cache plugin + fastcgi_param SERVER_NAME $http_host; +} diff --git a/wo/cli/templates/wpsubdir.mustache b/wo/cli/templates/wpsubdir.mustache new file mode 100644 index 0000000..5f0eebf --- /dev/null +++ b/wo/cli/templates/wpsubdir.mustache @@ -0,0 +1,10 @@ +# WPSUBDIRECTORY NGINX CONFIGURATION +# DO NOT MODIFY, ALL CHANGES WILL BE LOST AFTER AN WordOps (wo) UPDATE +if (!-e $request_filename) { + # Redirect wp-admin to wp-admin/ + rewrite /wp-admin$ $scheme://$host$uri/ permanent; + # Redirect wp-* files/folders + rewrite ^(/[^/]+)?(/wp-.*) $2 last; + # Redirect other php files + rewrite ^(/[^/]+)?(/.*\.php) $2 last; +} diff --git a/wo/core/__init__.py b/wo/core/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/wo/core/addswap.py b/wo/core/addswap.py new file mode 100644 index 0000000..5feb1e5 --- /dev/null +++ b/wo/core/addswap.py @@ -0,0 +1,48 @@ +"""WordOps Swap Creation""" +from wo.core.variables import WOVariables +from wo.core.shellexec import WOShellExec +from wo.core.fileutils import WOFileUtils +from wo.core.aptget import WOAptGet +from wo.core.logging import Log +import os + + +class WOSwap(): + """Manage Swap""" + + def __init__(): + """Initialize """ + pass + + def add(self): + """Swap addition with WordOps""" + if WOVariables.wo_ram < 512: + if WOVariables.wo_swap < 1000: + Log.info(self, "Adding SWAP file, please wait...") + + # Install dphys-swapfile + WOAptGet.update(self) + WOAptGet.install(self, ["dphys-swapfile"]) + # Stop service + WOShellExec.cmd_exec(self, "service dphys-swapfile stop") + # Remove Default swap created + WOShellExec.cmd_exec(self, "/sbin/dphys-swapfile uninstall") + + # Modify Swap configuration + if os.path.isfile("/etc/dphys-swapfile"): + WOFileUtils.searchreplace(self, "/etc/dphys-swapfile", + "#CONF_SWAPFILE=/var/swap", + "CONF_SWAPFILE=/wo-swapfile") + WOFileUtils.searchreplace(self, "/etc/dphys-swapfile", + "#CONF_MAXSWAP=2048", + "CONF_MAXSWAP=1024") + WOFileUtils.searchreplace(self, "/etc/dphys-swapfile", + "#CONF_SWAPSIZE=", + "CONF_SWAPSIZE=1024") + else: + with open("/etc/dphys-swapfile", 'w') as conffile: + conffile.write("CONF_SWAPFILE=/wo-swapfile\n" + "CONF_SWAPSIZE=1024\n" + "CONF_MAXSWAP=1024\n") + # Create swap file + WOShellExec.cmd_exec(self, "service dphys-swapfile start") diff --git a/wo/core/apt_repo.py b/wo/core/apt_repo.py new file mode 100644 index 0000000..478bbbe --- /dev/null +++ b/wo/core/apt_repo.py @@ -0,0 +1,91 @@ +"""WordOps packages repository operations""" +from wo.core.shellexec import WOShellExec +from wo.core.variables import WOVariables +from wo.core.logging import Log +import os + + +class WORepo(): + """Manage Repositories""" + + def __init__(self): + """Initialize """ + pass + + def add(self, repo_url=None, ppa=None): + """ + This function used to add apt repositories and or ppa's + If repo_url is provided adds repo file to + /etc/apt/sources.list.d/ + If ppa is provided add apt-repository using + add-apt-repository + command. + """ + + if repo_url is not None: + repo_file_path = ("/etc/apt/sources.list.d/" + + WOVariables().wo_repo_file) + try: + if not os.path.isfile(repo_file_path): + with open(repo_file_path, + encoding='utf-8', mode='a') as repofile: + repofile.write(repo_url) + repofile.write('\n') + repofile.close() + elif repo_url not in open(repo_file_path, + encoding='utf-8').read(): + with open(repo_file_path, + encoding='utf-8', mode='a') as repofile: + repofile.write(repo_url) + repofile.write('\n') + repofile.close() + return True + except IOError as e: + Log.debug(self, "{0}".format(e)) + Log.error(self, "File I/O error.") + except Exception as e: + Log.debug(self, "{0}".format(e)) + Log.error(self, "Unable to add repo") + if ppa is not None: + WOShellExec.cmd_exec(self, "add-apt-repository -y '{ppa_name}'" + .format(ppa_name=ppa)) + + def remove(self, ppa=None, repo_url=None): + """ + This function used to remove ppa's + If ppa is provided adds repo file to + /etc/apt/sources.list.d/ + command. + """ + if ppa: + WOShellExec.cmd_exec(self, "add-apt-repository -y " + "--remove '{ppa_name}'" + .format(ppa_name=ppa)) + elif repo_url: + repo_file_path = ("/etc/apt/sources.list.d/" + + WOVariables().wo_repo_file) + + try: + repofile = open(repo_file_path, "w+") + repofile.write(repofile.read().replace(repo_url, "")) + repofile.close() + except IOError as e: + Log.debug(self, "{0}".format(e)) + Log.error(self, "File I/O error.") + except Exception as e: + Log.debug(self, "{0}".format(e)) + Log.error(self, "Unable to remove repo") + + def add_key(self, keyids, keyserver=None): + """ + This function adds imports repository keys from keyserver. + default keyserver is hkp://keys.gnupg.net + user can provide other keyserver with keyserver="hkp://xyz" + """ + WOShellExec.cmd_exec(self, "gpg --keyserver {serv}" + .format(serv=(keyserver or + "hkp://keys.gnupg.net")) + + " --recv-keys {key}".format(key=keyids)) + WOShellExec.cmd_exec(self, "gpg -a --export --armor {0}" + .format(keyids) + + " | apt-key add - ") diff --git a/wo/core/aptget.py b/wo/core/aptget.py new file mode 100644 index 0000000..7c274cf --- /dev/null +++ b/wo/core/aptget.py @@ -0,0 +1,231 @@ +"""WordOps package installation using apt-get module.""" +import apt +import apt_pkg +import sys +import subprocess +from wo.core.logging import Log +from wo.core.apt_repo import WORepo +from sh import apt_get +from sh import ErrorReturnCode + + +class WOAptGet(): + """Generic apt-get intialisation""" + + def update(self): + """ + Similar to `apt-get update` + """ + try: + with open('/var/log/wo/wordops.log', 'a') as f: + proc = subprocess.Popen('apt-get update', + shell=True, + stdin=None, stdout=f, + stderr=subprocess.PIPE, + executable="/bin/bash") + proc.wait() + output, error_output = proc.communicate() + + # Check what is error in error_output + if "NO_PUBKEY" in str(error_output): + # Split the output + Log.info(self, "Fixing missing GPG keys, please wait...") + error_list = str(error_output).split("\\n") + + # Use a loop to add misising keys + for single_error in error_list: + if "NO_PUBKEY" in single_error: + key = single_error.rsplit(None, 1)[-1] + WORepo.add_key(self, key, keyserver="hkp://pgp.mit.edu") + + proc = subprocess.Popen('apt-get update', + shell=True, + stdin=None, stdout=f, stderr=f, + executable="/bin/bash") + proc.wait() + + if proc.returncode == 0: + return True + else: + Log.info(self, Log.FAIL + "Whoops, something went wrong...") + Log.error(self, "Check the WordOps log for more details " + "`tail /var/log/wo/wordops.log` and please try again...") + + except Exception as e: + Log.error(self, "apt-get update exited with error") + + def check_upgrade(self): + """ + Similar to `apt-get upgrade` + """ + try: + check_update = subprocess.Popen(['apt-get upgrade -s | grep ' + '\"^Inst\" | wc -l'], + stdout=subprocess.PIPE, + shell=True).communicate()[0] + if check_update == b'0\n': + Log.error(self, "No package updates available") + Log.info(self, "Following package updates are available:") + subprocess.Popen("apt-get -s dist-upgrade | grep \"^Inst\"", + shell=True, executable="/bin/bash", + stdout=sys.stdout).communicate() + + except Exception as e: + Log.error(self, "Unable to check for packages upgrades") + + def dist_upgrade(self): + """ + Similar to `apt-get upgrade` + """ + try: + with open('/var/log/wo/wordops.log', 'a') as f: + proc = subprocess.Popen("DEBIAN_FRONTEND=noninteractive " + "apt-get dist-upgrade -o " + "Dpkg::Options::=\"--force-confdef\"" + " -o " + "Dpkg::Options::=\"--force-confold\"" + " -y ", + shell=True, stdin=None, + stdout=f, stderr=f, + executable="/bin/bash") + proc.wait() + + if proc.returncode == 0: + return True + else: + Log.info(self, Log.FAIL + "Oops Something went " + "wrong!!") + Log.error(self, "Check the WordOps log for more details " + "`tail /var/log/wo/wordops.log` and please try again...") + except Exception as e: + Log.error(self, "Error while installing packages, " + "apt-get exited with error") + + def install(self, packages): + all_packages = ' '.join(packages) + try: + with open('/var/log/wo/wordops.log', 'a') as f: + proc = subprocess.Popen("DEBIAN_FRONTEND=noninteractive " + "apt-get install -o " + "Dpkg::Options::=\"--force-confdef\"" + " -o " + "Dpkg::Options::=\"--force-confold\"" + " -y --allow-unauthenticated {0}" + .format(all_packages), shell=True, + stdin=None, stdout=f, stderr=f, + executable="/bin/bash") + proc.wait() + + if proc.returncode == 0: + return True + else: + Log.info(self, Log.FAIL + "Oops Something went " + "wrong!!") + Log.error(self, "Check the WordOps log for more details " + "`tail /var/log/wo/wordops.log` and please try again...") + + except Exception as e: + Log.info(self, Log.FAIL + "Oops Something went " + "wrong!!") + Log.error(self, "Check the WordOps log for more details " + "`tail /var/log/wo/wordops.log` and please try again...") + + def remove(self, packages, auto=False, purge=False): + all_packages = ' '.join(packages) + try: + with open('/var/log/wo/wordops.log', 'a') as f: + if purge: + proc = subprocess.Popen('apt-get purge -y {0}' + .format(all_packages), shell=True, + stdin=None, stdout=f, stderr=f, + executable="/bin/bash") + else: + proc = subprocess.Popen('apt-get remove -y {0}' + .format(all_packages), shell=True, + stdin=None, stdout=f, stderr=f, + executable="/bin/bash") + proc.wait() + if proc.returncode == 0: + return True + else: + Log.info(self, Log.FAIL + "Oops Something went " + "wrong!!") + Log.error(self, "Check the WordOps log for more details " + "`tail /var/log/wo/wordops.log` and please try again...") + + except Exception as e: + Log.error(self, "Error while installing packages, " + "apt-get exited with error") + + def auto_clean(self): + """ + Similar to `apt-get autoclean` + """ + try: + orig_out = sys.stdout + sys.stdout = open(self.app.config.get('log.logging', 'file'), + encoding='utf-8', mode='a') + apt_get.autoclean("-y") + sys.stdout = orig_out + except ErrorReturnCode as e: + Log.debug(self, "{0}".format(e)) + Log.error(self, "Unable to apt-get autoclean") + + def auto_remove(self): + """ + Similar to `apt-get autoremove` + """ + try: + Log.debug(self, "Running apt-get autoremove") + apt_get.autoremove("-y") + except ErrorReturnCode as e: + Log.debug(self, "{0}".format(e)) + Log.error(self, "Unable to apt-get autoremove") + + def is_installed(self, package_name): + """ + Checks if package is available in cache and is installed or not + returns True if installed otherwise returns False + """ + apt_cache = apt.cache.Cache() + apt_cache.open() + if (package_name.strip() in apt_cache and + apt_cache[package_name.strip()].is_installed): + # apt_cache.close() + return True + # apt_cache.close() + return False + + def download_only(self,package_name,repo_url=None,repo_key=None): + """ + Similar to `apt-get install --download-only PACKAGE_NAME` + """ + packages = ' '.join(package_name) + try: + with open('/var/log/wo/wordops.log', 'a') as f: + if repo_url is not None: + WORepo.add(self, repo_url=repo_url) + if repo_key is not None: + WORepo.add_key(self, repo_key) + proc = subprocess.Popen("apt-get update && DEBIAN_FRONTEND=noninteractive " + "apt-get install -o " + "Dpkg::Options::=\"--force-confdef\"" + " -o " + "Dpkg::Options::=\"--force-confold\"" + " -y --download-only {0}" + .format(packages), shell=True, + stdin=None, stdout=f, stderr=f, + executable="/bin/bash") + proc.wait() + + if proc.returncode == 0: + return True + else: + Log.error(self,"Error in fetching dpkg package.\nReverting changes ..",False) + if repo_url is not None: + WORepo.remove(self, repo_url=repo_url) + return False + except Exception as e: + Log.error(self, "Error while downloading packages, " + "apt-get exited with error") + diff --git a/wo/core/checkfqdn.py b/wo/core/checkfqdn.py new file mode 100644 index 0000000..9add740 --- /dev/null +++ b/wo/core/checkfqdn.py @@ -0,0 +1,23 @@ +from wo.core.shellexec import WOShellExec +from wo.core.variables import WOVariables +import os + + +def check_fqdn(self, wo_host): + """FQDN check with WordOps, for mail server hostname must be FQDN""" + # wo_host=os.popen("hostname -f | tr -d '\n'").read() + if '.' in wo_host: + WOVariables.wo_fqdn = wo_host + with open('/etc/hostname', encoding='utf-8', mode='w') as hostfile: + hostfile.write(wo_host) + + WOShellExec.cmd_exec(self, "sed -i \"1i\\127.0.0.1 {0}\" /etc/hosts" + .format(wo_host)) + if WOVariables.wo_platform_distro == 'debian': + WOShellExec.cmd_exec(self, "/etc/init.d/hostname.sh start") + else: + WOShellExec.cmd_exec(self, "service hostname restart") + + else: + wo_host = input("Enter hostname [fqdn]:") + check_fqdn(self, wo_host) diff --git a/wo/core/cron.py b/wo/core/cron.py new file mode 100644 index 0000000..2392ad4 --- /dev/null +++ b/wo/core/cron.py @@ -0,0 +1,32 @@ +from wo.core.shellexec import WOShellExec +from wo.core.logging import Log + +""" +Set CRON on LINUX system. +""" + +class WOCron(): + def setcron_weekly(self,cmd,comment='Cron set by WordOps',user='root',min=0,hour=12): + if not WOShellExec.cmd_exec(self, "crontab -l | grep -q \'{0}\'".format(cmd)): + + WOShellExec.cmd_exec(self, "/bin/bash -c \"crontab -l " + "2> /dev/null | {{ cat; echo -e" + " \\\"" + "\\n0 0 * * 0 " + "{0}".format(cmd) + + " # {0}".format(comment)+ + "\\\"; } | crontab -\"") + Log.debug(self, "Cron set") + + + + def remove_cron(self,cmd): + if WOShellExec.cmd_exec(self, "crontab -l | grep -q \'{0}\'".format(cmd)): + if not WOShellExec.cmd_exec(self, "/bin/bash -c " + "\"crontab " + "-l | sed '/{0}/d'" + "| crontab -\"" + .format(cmd)): + Log.error(self, "Failed to remove crontab entry",False) + else: + Log.debug(self, "Cron not found") diff --git a/wo/core/database.py b/wo/core/database.py new file mode 100644 index 0000000..2fe0631 --- /dev/null +++ b/wo/core/database.py @@ -0,0 +1,28 @@ +"""WordOps generic database creation module""" +from sqlalchemy import create_engine +from sqlalchemy.orm import scoped_session, sessionmaker +from sqlalchemy.ext.declarative import declarative_base +from wo.core.variables import WOVariables + +# db_path = self.app.config.get('site', 'db_path') +engine = create_engine(WOVariables.wo_db_uri, convert_unicode=True) +db_session = scoped_session(sessionmaker(autocommit=False, + autoflush=False, + bind=engine)) +Base = declarative_base() +Base.query = db_session.query_property() + + +def init_db(app): + """ + Initializes and creates all tables from models into the database + """ + # import all modules here that might define models so that + # they will be registered properly on the metadata. Otherwise + # # you will have to import them first before calling init_db() + # import wo.core.models + try: + app.log.info("Initializing WordOps Database") + Base.metadata.create_all(bind=engine) + except Exception as e: + app.log.debug("{0}".format(e)) diff --git a/wo/core/domainvalidate.py b/wo/core/domainvalidate.py new file mode 100644 index 0000000..f0c5985 --- /dev/null +++ b/wo/core/domainvalidate.py @@ -0,0 +1,24 @@ +"""WordOps domain validation module.""" +from urllib.parse import urlparse + + +def ValidateDomain(url): + """ + This function returns domain name removing http:// and https:// + returns domain name only with or without www as user provided. + """ + + # Check if http:// or https:// present remove it if present + domain_name = url.split('/') + if 'http:' in domain_name or 'https:' in domain_name: + domain_name = domain_name[2] + else: + domain_name = domain_name[0] + www_domain_name = domain_name.split('.') + final_domain = '' + if www_domain_name[0] == 'www': + final_domain = '.'.join(www_domain_name[1:]) + else: + final_domain = domain_name + + return (final_domain, domain_name) diff --git a/wo/core/download.py b/wo/core/download.py new file mode 100644 index 0000000..ef94d4f --- /dev/null +++ b/wo/core/download.py @@ -0,0 +1,45 @@ +"""WordOps download core classes.""" +import urllib.request +import urllib.error +import os +from wo.core.logging import Log + + +class WODownload(): + """Method to download using urllib""" + def __init__(): + pass + + def download(self, packages): + """Download packages, packges must be list in format of + [url, path, package name]""" + for package in packages: + url = package[0] + filename = package[1] + pkg_name = package[2] + try: + directory = os.path.dirname(filename) + if not os.path.exists(directory): + os.makedirs(directory) + Log.info(self, "Downloading {0:20}".format(pkg_name), end=' ') + req = urllib.request.Request(url, headers={'User-Agent': 'Mozilla/5.0'}) + with urllib.request.urlopen(req) as response, open(filename, 'wb') as out_file: + out_file.write(response.read()) + Log.info(self, "{0}".format("[" + Log.ENDC + "Done" + + Log.OKBLUE + "]")) + except urllib.error.URLError as e: + Log.debug(self, "[{err}]".format(err=str(e.reason))) + Log.error(self, "Unable to download file, {0}" + .format(filename)) + return False + except urllib.error.HTTPError as e: + Log.error(self, "Package download failed. {0}" + .format(pkg_name)) + Log.debug(self, "[{err}]".format(err=str(e.reason))) + return False + except urllib.error.ContentTooShortError as e: + Log.debug(self, "{0}{1}".format(e.errno, e.strerror)) + Log.error(self, "Package download failed. The amount of the" + " downloaded data is less than " + "the expected amount \{0} ".format(pkg_name)) + return False diff --git a/wo/core/exc.py b/wo/core/exc.py new file mode 100644 index 0000000..67e968f --- /dev/null +++ b/wo/core/exc.py @@ -0,0 +1,26 @@ +"""WordOps exception classes.""" + + +class WOError(Exception): + """Generic errors.""" + def __init__(self, msg): + Exception.__init__(self) + self.msg = msg + + def __str__(self): + return self.msg + + +class WOConfigError(WOError): + """Config related errors.""" + pass + + +class WORuntimeError(WOError): + """Generic runtime errors.""" + pass + + +class WOArgumentError(WOError): + """Argument related errors.""" + pass diff --git a/wo/core/extract.py b/wo/core/extract.py new file mode 100644 index 0000000..0564b9b --- /dev/null +++ b/wo/core/extract.py @@ -0,0 +1,21 @@ +"""WordOps Extract Core """ +import tarfile +import os +from wo.core.logging import Log + + +class WOExtract(): + """Method to extract from tar.gz file""" + + def extract(self, file, path): + """Function to extract tar.gz file""" + try: + tar = tarfile.open(file) + tar.extractall(path=path) + tar.close() + os.remove(file) + return True + except tarfile.TarError as e: + Log.debug(self, "{0}".format(e)) + Log.error(self, 'Unable to extract file \{0}'.format(file)) + return False diff --git a/wo/core/fileutils.py b/wo/core/fileutils.py new file mode 100644 index 0000000..a6032b8 --- /dev/null +++ b/wo/core/fileutils.py @@ -0,0 +1,268 @@ +"""WordOps file utils core classes.""" +import shutil +import os +import sys +import glob +import shutil +import pwd +import fileinput +from wo.core.logging import Log + + +class WOFileUtils(): + """Utilities to operate on files""" + def __init__(): + pass + + def remove(self, filelist): + """remove files from given path""" + for file in filelist: + if os.path.isfile(file): + Log.info(self, "Removing {0:65}".format(file), end=' ') + os.remove(file) + Log.info(self, "{0}".format("[" + Log.ENDC + "Done" + + Log.OKBLUE + "]")) + Log.debug(self, 'file Removed') + if os.path.isdir(file): + try: + Log.info(self, "Removing {0:65}".format(file), end=' ') + shutil.rmtree(file) + Log.info(self, "{0}".format("[" + Log.ENDC + "Done" + + Log.OKBLUE + "]")) + except shutil.Error as e: + Log.debug(self, "{err}".format(err=str(e.reason))) + Log.error(self, 'Unable to Remove file ') + + def create_symlink(self, paths, errormsg=''): + """ + Create symbolic links provided in list with first as source + and second as destination + """ + src = paths[0] + dst = paths[1] + if not os.path.islink(dst): + try: + Log.debug(self, "Creating Symbolic link, Source:{0}, Dest:{1}" + .format(src, dst)) + os.symlink(src, dst) + except Exception as e: + Log.debug(self, "{0}{1}".format(e.errno, e.strerror)) + Log.error(self, "Unable to create symbolic link ...\n ") + else: + Log.debug(self, "Destination: {0} exists".format(dst)) + + def remove_symlink(self, filepath): + """ + Removes symbolic link for the path provided with filepath + """ + try: + Log.debug(self, "Removing symbolic link: {0}".format(filepath)) + os.unlink(filepath) + except Exception as e: + Log.debug(self, "{0}".format(e)) + Log.error(self, "Unable to reomove symbolic link ...\n") + + def copyfiles(self, src, dest): + """ + Copies files: + src : source path + dest : destination path + + Recursively copy an entire directory tree rooted at src. + The destination directory, named by dst, must not already exist; + it will be created as well as missing parent directories. + """ + try: + Log.debug(self, "Copying files, Source:{0}, Dest:{1}" + .format(src, dest)) + shutil.copytree(src, dest) + except shutil.Error as e: + Log.debug(self, "{0}".format(e)) + Log.error(self, 'Unable to copy files from {0} to {1}' + .format(src, dest)) + except IOError as e: + Log.debug(self, "{0}".format(e.strerror)) + Log.error(self, "Unable to copy files from {0} to {1}" + .format(src, dest)) + + def copyfile(self, src, dest): + """ + Copy file: + src : source path + dest : destination path + """ + try: + Log.debug(self, "Copying file, Source:{0}, Dest:{1}" + .format(src, dest)) + shutil.copy2(src, dest) + except shutil.Error as e: + Log.debug(self, "{0}".format(e)) + Log.error(self, 'Unable to copy file from {0} to {1}' + .format(src, dest)) + except IOError as e: + Log.debug(self, "{0}".format(e.strerror)) + Log.error(self, "Unable to copy file from {0} to {1}" + .format(src, dest)) + + def searchreplace(self, fnm, sstr, rstr): + """ + Search replace strings in file + fnm : filename + sstr: search string + rstr: replace string + """ + try: + Log.debug(self, "Doning search and replace, File:{0}," + "Source string:{1}, Dest String:{2}" + .format(fnm, sstr, rstr)) + for line in fileinput.input(fnm, inplace=True): + print(line.replace(sstr, rstr), end='') + fileinput.close() + except Exception as e: + Log.debug(self, "{0}".format(e)) + Log.error(self, "Unable to search {0} and replace {1} {2}" + .format(fnm, sstr, rstr)) + + def mvfile(self, src, dst): + """ + Moves file from source path to destination path + src : source path + dst : Destination path + """ + try: + Log.debug(self, "Moving file from {0} to {1}".format(src, dst)) + shutil.move(src, dst) + except Exception as e: + Log.debug(self, "{err}".format(err=e)) + Log.error(self, 'Unable to move file from {0} to {1}' + .format(src, dst)) + + def chdir(self, path): + """ + Change Directory to path specified + Path : path for destination directory + """ + try: + Log.debug(self, "Changing directory to {0}" + .format(path)) + os.chdir(path) + except OSError as e: + Log.debug(self, "{err}".format(err=e.strerror)) + Log.error(self, 'Unable to Change Directory {0}'.format(path)) + + def chown(self, path, user, group, recursive=False): + """ + Change Owner for files + change owner for file with path specified + user: username of owner + group: group of owner + recursive: if recursive is True change owner for all + files in directory + """ + userid = pwd.getpwnam(user)[2] + groupid = pwd.getpwnam(user)[3] + try: + Log.debug(self, "Changing ownership of {0}, Userid:{1},Groupid:{2}" + .format(path, userid, groupid)) + # Change inside files/directory permissions only if recursive flag + # is set + if recursive: + for root, dirs, files in os.walk(path): + for d in dirs: + os.chown(os.path.join(root, d), userid, + groupid) + for f in files: + os.chown(os.path.join(root, f), userid, + groupid) + os.chown(path, userid, groupid) + except shutil.Error as e: + Log.debug(self, "{0}".format(e)) + Log.error(self, "Unable to change owner : {0}".format(path)) + except Exception as e: + Log.debug(self, "{0}".format(e)) + Log.error(self, "Unable to change owner : {0} ".format(path)) + + def chmod(self, path, perm, recursive=False): + """ + Changes Permission for files + path : file path permission to be changed + perm : permissions to be given + recursive: change permission recursively for all files + """ + try: + Log.debug(self, "Changing permission of {0}, Perm:{1}" + .format(path, perm)) + if recursive: + for root, dirs, files in os.walk(path): + for d in dirs: + os.chmod(os.path.join(root, d), perm) + for f in files: + os.chmod(os.path.join(root, f), perm) + else: + os.chmod(path, perm) + except OSError as e: + Log.debug(self, "{0}".format(e.strerror)) + Log.error(self, "Unable to change owner : {0}".format(path)) + + def mkdir(self, path): + """ + create directories. + path : path for directory to be created + Similar to `mkdir -p` + """ + try: + Log.debug(self, "Creating directories: {0}" + .format(path)) + os.makedirs(path) + except OSError as e: + Log.debug(self, "{0}".format(e.strerror)) + Log.error(self, "Unable to create directory {0} ".format(path)) + + def isexist(self, path): + """ + Check if file exist on given path + """ + try: + if os.path.exists(path): + return (True) + else: + return (False) + except OSError as e: + Log.debug(self, "{0}".format(e.strerror)) + Log.error(self, "Unable to check path {0}".format(path)) + + def grep(self, fnm, sstr): + """ + Searches for string in file and returns the matched line. + """ + try: + Log.debug(self, "Finding string {0} to file {1}" + .format(sstr, fnm)) + for line in open(fnm, encoding='utf-8'): + if sstr in line: + return line + return False + except OSError as e: + Log.debug(self, "{0}".format(e.strerror)) + Log.error(self, "Unable to Search string {0} in {1}" + .format(sstr, fnm)) + + def rm(self, path): + """ + Remove files + """ + Log.debug(self, "Removing {0}".format(path)) + if WOFileUtils.isexist(self, path): + try: + if os.path.isdir(path): + shutil.rmtree(path) + else: + os.remove(path) + except shutil.Error as e: + Log.debug(self, "{0}".format(e)) + Log.error(self, "Unable to remove directory : {0} " + .format(path)) + except OSError as e: + Log.debug(self, "{0}".format(e)) + Log.error(self, "Unable to remove file : {0} " + .format(path)) diff --git a/wo/core/git.py b/wo/core/git.py new file mode 100644 index 0000000..d01eee6 --- /dev/null +++ b/wo/core/git.py @@ -0,0 +1,57 @@ +"""WordOps GIT module""" +from sh import git, ErrorReturnCode +from wo.core.logging import Log +import os + + +class WOGit: + """Intialization of core variables""" + def ___init__(): + # TODO method for core variables + pass + + def add(self, paths, msg="Intializating"): + """ + Initializes Directory as repository if not already git repo. + and adds uncommited changes automatically + """ + for path in paths: + global git + git = git.bake("--git-dir={0}/.git".format(path), + "--work-tree={0}".format(path)) + if os.path.isdir(path): + if not os.path.isdir(path+"/.git"): + try: + Log.debug(self, "WOGit: git init at {0}" + .format(path)) + git.init(path) + except ErrorReturnCode as e: + Log.debug(self, "{0}".format(e)) + Log.error(self, "Unable to git init at {0}" + .format(path)) + status = git.status("-s") + if len(status.splitlines()) > 0: + try: + Log.debug(self, "WOGit: git commit at {0}" + .format(path)) + git.add("--all") + git.commit("-am {0}".format(msg)) + except ErrorReturnCode as e: + Log.debug(self, "{0}".format(e)) + Log.error(self, "Unable to git commit at {0} " + .format(path)) + else: + Log.debug(self, "WOGit: Path {0} not present".format(path)) + + def checkfilestatus(self, repo, filepath): + """ + Checks status of file, If its tracked or untracked. + """ + global git + git = git.bake("--git-dir={0}/.git".format(repo), + "--work-tree={0}".format(repo)) + status = git.status("-s", "{0}".format(filepath)) + if len(status.splitlines()) > 0: + return True + else: + return False diff --git a/wo/core/logging.py b/wo/core/logging.py new file mode 100644 index 0000000..84a3508 --- /dev/null +++ b/wo/core/logging.py @@ -0,0 +1,47 @@ +"""WordOps log module""" + + +class Log: + """ + Logs messages with colors for different messages + according to functions + """ + HEADER = '\033[95m' + OKBLUE = '\033[94m' + OKGREEN = '\033[92m' + WARNING = '\033[93m' + FAIL = '\033[91m' + ENDC = '\033[0m' + BOLD = '\033[1m' + UNDERLINE = '\033[4m' + + def error(self, msg, exit=True): + """ + Logs error into log file + """ + print(Log.FAIL + msg + Log.ENDC) + self.app.log.error(Log.FAIL + msg + Log.ENDC) + if exit: + self.app.close(1) + + def info(self, msg, end='\n', log=True): + """ + Logs info messages into log file + """ + + print(Log.OKBLUE + msg + Log.ENDC, end=end) + if log: + self.app.log.info(Log.OKBLUE + msg + Log.ENDC) + + def warn(self, msg): + """ + Logs warning into log file + """ + print(Log.WARNING + msg + Log.ENDC) + self.app.log.warn(Log.BOLD + msg + Log.ENDC) + + def debug(self, msg): + """ + Logs debug messages into log file + """ + self.app.log.debug(Log.HEADER + msg + Log.ENDC) diff --git a/wo/core/logwatch.py b/wo/core/logwatch.py new file mode 100644 index 0000000..36baf16 --- /dev/null +++ b/wo/core/logwatch.py @@ -0,0 +1,195 @@ + +""" +Real time log files watcher supporting log rotation. +""" + +import os +import time +import errno +import stat +from wo.core.logging import Log + + +class LogWatcher(object): + """Looks for changes in all files of a directory. + This is useful for watching log file changes in real-time. + It also supports files rotation. + + Example: + + >>> def callback(filename, lines): + ... print filename, lines + ... + >>> l = LogWatcher("/var/www/example.com/logs", callback) + >>> l.loop() + """ + + def __init__(self, filelist, callback, extensions=["log"], tail_lines=0): + """Arguments: + + (str) @folder: + the folder to watch + + (callable) @callback: + a function which is called every time a new line in a + file being watched is found; + this is called with "filename" and "lines" arguments. + + (list) @extensions: + only watch files with these extensions + + (int) @tail_lines: + read last N lines from files being watched before starting + """ + self.files_map = {} + self.filelist = filelist + self.callback = callback + # self.folder = os.path.realpath(folder) + self.extensions = extensions + # assert (os.path.isdir(self.folder), "%s does not exists" + # % self.folder) + for file in self.filelist: + assert (os.path.isfile(file)) + assert callable(callback) + self.update_files() + # The first time we run the script we move all file markers at EOF. + # In case of files created afterwards we don't do this. + for id, file in list(iter(self.files_map.items())): + file.seek(os.path.getsize(file.name)) # EOF + if tail_lines: + lines = self.tail(file.name, tail_lines) + if lines: + self.callback(file.name, lines) + + def __del__(self): + self.close() + + def loop(self, interval=0.1, async=False): + """Start the loop. + If async is True make one loop then return. + """ + while 1: + self.update_files() + for fid, file in list(iter(self.files_map.items())): + self.readfile(file) + if async: + return + time.sleep(interval) + + def log(self, line): + """Log when a file is un/watched""" + print(line) + + # def listdir(self): + # """List directory and filter files by extension. + # You may want to override this to add extra logic or + # globbling support. + # """ + # ls = os.listdir(self.folder) + # if self.extensions: + # return ([x for x in ls if os.path.splitext(x)[1][1:] + # in self.extensions]) + # else: + # return ls + + @staticmethod + def tail(fname, window): + """Read last N lines from file fname.""" + try: + f = open(fname, encoding='utf-8', mode='r') + except IOError as err: + if err.errno == errno.ENOENT: + return [] + else: + raise + else: + BUFSIZ = 1024 + f.seek(0, os.SEEK_END) + fsize = f.tell() + block = -1 + data = "" + exit = False + while not exit: + step = (block * BUFSIZ) + if abs(step) >= fsize: + f.seek(0) + exit = True + else: + f.seek(step, os.SEEK_END) + data = f.read().strip() + if data.count('\n') >= window: + break + else: + block -= 1 + return data.splitlines()[-window:] + + def update_files(self): + ls = [] + for name in self.filelist: + absname = os.path.realpath(os.path.join(name)) + try: + st = os.stat(absname) + except EnvironmentError as err: + if err.errno != errno.ENOENT: + raise + else: + if not stat.S_ISREG(st.st_mode): + continue + fid = self.get_file_id(st) + ls.append((fid, absname)) + + # check existent files + for fid, file in list(iter(self.files_map.items())): + # next(iter(graph.items())) + try: + st = os.stat(file.name) + except EnvironmentError as err: + if err.errno == errno.ENOENT: + self.unwatch(file, fid) + else: + raise + else: + if fid != self.get_file_id(st): + # same name but different file (rotation); reload it. + self.unwatch(file, fid) + self.watch(file.name) + + # add new ones + for fid, fname in ls: + if fid not in self.files_map: + self.watch(fname) + + def readfile(self, file): + lines = file.readlines() + if lines: + self.callback(file.name, lines) + + def watch(self, fname): + try: + file = open(fname, encoding='utf-8', mode='r') + fid = self.get_file_id(os.stat(fname)) + except EnvironmentError as err: + if err.errno != errno.ENOENT: + raise + else: + self.log("watching logfile %s" % fname) + self.files_map[fid] = file + + def unwatch(self, file, fid): + # file no longer exists; if it has been renamed + # try to read it for the last time in case the + # log rotator has written something in it. + lines = self.readfile(file) + self.log("un-watching logfile %s" % file.name) + del self.files_map[fid] + if lines: + self.callback(file.name, lines) + + @staticmethod + def get_file_id(st): + return "%xg%x" % (st.st_dev, st.st_ino) + + def close(self): + for id, file in list(iter(self.files_map.items())): + file.close() + self.files_map.clear() diff --git a/wo/core/mysql.py b/wo/core/mysql.py new file mode 100644 index 0000000..92a1c5e --- /dev/null +++ b/wo/core/mysql.py @@ -0,0 +1,136 @@ +"""WordOps MySQL core classes.""" +import pymysql +from pymysql import connections, DatabaseError, Error +import configparser +from os.path import expanduser +import sys +import os +from wo.core.logging import Log +from wo.core.variables import WOVariables + + +class MySQLConnectionError(Exception): + """Custom Exception when MySQL server Not Connected""" + pass + + +class StatementExcecutionError(Exception): + """Custom Exception when any Query Fails to execute""" + pass + + +class DatabaseNotExistsError(Exception): + """Custom Exception when Database not Exist""" + pass + + +class WOMysql(): + """Method for MySQL connection""" + + def connect(self): + """Makes connection with MySQL server""" + try: + if os.path.exists('/etc/mysql/conf.d/my.cnf'): + connection = pymysql.connect(read_default_file='/etc/mysql/conf.d/my.cnf') + else: + connection = pymysql.connect(read_default_file='~/.my.cnf') + return connection + except ValueError as e: + Log.debug(self, str(e)) + raise MySQLConnectionError + except pymysql.err.InternalError as e: + Log.debug(self, str(e)) + raise MySQLConnectionError + + def dbConnection(self, db_name): + try: + if os.path.exists('/etc/mysql/conf.d/my.cnf'): + connection = pymysql.connect(db=db_name,read_default_file='/etc/mysql/conf.d/my.cnf') + else: + connection = pymysql.connect(db=db_name,read_default_file='~/.my.cnf') + + return connection + except DatabaseError as e: + if e.args[1] == '#42000Unknown database \'{0}\''.format(db_name): + raise DatabaseNotExistsError + else: + raise MySQLConnectionError + except pymysql.err.InternalError as e: + Log.debug(self, str(e)) + raise MySQLConnectionError + except Exception as e : + Log.debug(self, "[Error]Setting up database: \'" + str(e) + "\'") + raise MySQLConnectionError + + def execute(self, statement, errormsg='', log=True): + """Get login details from /etc/mysql/conf.d/my.cnf & Execute MySQL query""" + connection = WOMysql.connect(self) + log and Log.debug(self, "Exceuting MySQL Statement : {0}" + .format(statement)) + try: + cursor = connection.cursor() + sql = statement + cursor.execute(sql) + + # connection is not autocommit by default. + # So you must commit to save your changes. + connection.commit() + except AttributeError as e: + Log.debug(self, str(e)) + raise StatementExcecutionError + except Error as e: + Log.debug(self, str(e)) + raise StatementExcecutionError + finally: + connection.close() + + def backupAll(self): + import subprocess + try: + Log.info(self, "Backing up database at location: " + "/var/wo-mysqlbackup") + # Setup Nginx common directory + if not os.path.exists('/var/wo-mysqlbackup'): + Log.debug(self, 'Creating directory' + '/var/wo-mysqlbackup') + os.makedirs('/var/wo-mysqlbackup') + + db = subprocess.check_output(["mysql -Bse \'show databases\'"], + universal_newlines=True, + shell=True).split('\n') + for dbs in db: + if dbs == "": + continue + Log.info(self, "Backing up {0} database".format(dbs)) + p1 = subprocess.Popen("mysqldump {0}" + " --max_allowed_packet=1024M" + " --single-transaction".format(dbs), + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, shell=True) + p2 = subprocess.Popen("gzip -c > /var/wo-mysqlbackup/{0}{1}.s" + "ql.gz".format(dbs, WOVariables.wo_date), + stdin=p1.stdout, + shell=True) + + # Allow p1 to receive a SIGPIPE if p2 exits + p1.stdout.close() + output = p1.stderr.read() + p1.wait() + if p1.returncode == 0: + Log.debug(self, "done") + else: + Log.error(self, output.decode("utf-8")) + except Exception as e: + Log.error(self, "Error: process exited with status %s" + % e) + + def check_db_exists(self, db_name): + try: + if WOMysql.dbConnection(self, db_name): + return True + except DatabaseNotExistsError as e: + Log.debug(self, str(e)) + return False + except MySQLConnectionError as e: + Log.debug(self, str(e)) + return False diff --git a/wo/core/nginxhashbucket.py b/wo/core/nginxhashbucket.py new file mode 100644 index 0000000..a4ec755 --- /dev/null +++ b/wo/core/nginxhashbucket.py @@ -0,0 +1,44 @@ +"""WordOps Hash Bucket Calculator""" +from wo.core.fileutils import WOFileUtils +import math +import os +import fileinput +import re +import subprocess + + +def hashbucket(self): + # Check Nginx Hashbucket error + sub = subprocess.Popen('nginx -t', stdout=subprocess.PIPE, + stderr=subprocess.PIPE, shell=True) + output, error_output = sub.communicate() + if 'server_names_hash_bucket_size' not in str(error_output): + return True + + count = 0 + # Get the list of sites-availble + sites_list = os.listdir("/etc/nginx/sites-enabled/") + + # Count the number of characters in site names + for site in sites_list: + count = sum([count, len(site)]) + + # Calculate Nginx hash bucket size + ngx_calc = math.trunc(sum([math.log(count, 2), 2])) + ngx_hash = math.trunc(math.pow(2, ngx_calc)) + + # Replace hashbucket in Nginx.conf file + if WOFileUtils.grep(self, "/etc/nginx/nginx.conf", + "server_names_hash_bucket_size"): + for line in fileinput.FileInput("/etc/nginx/nginx.conf", inplace=1): + if "server_names_hash_bucket_size" in line: + print("\tserver_names_hash_bucket_size {0};".format(ngx_hash)) + else: + print(line, end='') + + else: + WOFileUtils.searchreplace(self, '/etc/nginx/nginx.conf', + "gzip_disable \"msie6\";", + "gzip_disable \"msie6\";\n" + "\tserver_names_hash_bucket_size {0};\n" + .format(ngx_hash)) diff --git a/wo/core/sendmail.py b/wo/core/sendmail.py new file mode 100644 index 0000000..59c330f --- /dev/null +++ b/wo/core/sendmail.py @@ -0,0 +1,33 @@ +import smtplib +import os +from email.mime.multipart import MIMEMultipart +from email.mime.base import MIMEBase +from email.mime.text import MIMEText +from email.utils import COMMASPACE, formatdate +from email import encoders + + +def WOSendMail(send_from, send_to, subject, text, files, server="localhost", + port=587, username='', password='', isTls=True): + msg = MIMEMultipart() + msg['From'] = send_from + msg['To'] = send_to + msg['Date'] = formatdate(localtime=True) + msg['Subject'] = subject + + msg.attach(MIMEText(text)) + + for f in files: + part = MIMEBase('application', "octet-stream") + part.set_payload(open(f, "rb").read()) + encoders.encode_base64(part) + part.add_header('Content-Disposition', 'attachment; filename="{0}"' + .format(os.path.basename(f))) + msg.attach(part) + + smtp = smtplib.SMTP(server, port) + if isTls: + smtp.starttls() + + smtp.sendmail(send_from, send_to, msg.as_string()) + smtp.quit() diff --git a/wo/core/services.py b/wo/core/services.py new file mode 100644 index 0000000..ad5d8b8 --- /dev/null +++ b/wo/core/services.py @@ -0,0 +1,134 @@ +"""WordOps Service Manager""" +import os +import sys +import subprocess +from subprocess import Popen +from wo.core.logging import Log +import pystache + + +class WOService(): + """Intialization for service""" + def ___init__(): + pass + + def start_service(self, service_name): + """ + start service + Similar to `service xyz start` + """ + try: + if service_name in ['nginx', 'php5-fpm']: + service_cmd = ('{0} -t && service {0} start' + .format(service_name)) + else: + service_cmd = ('service {0} start'.format(service_name)) + + Log.info(self, "Start : {0:10}" .format(service_name), end='') + retcode = subprocess.getstatusoutput(service_cmd) + if retcode[0] == 0: + Log.info(self, "[" + Log.ENDC + "OK" + Log.OKBLUE + "]") + return True + else: + Log.debug(self, "{0}".format(retcode[1])) + Log.info(self, "[" + Log.FAIL + "Failed" + Log.OKBLUE+"]") + return False + except OSError as e: + Log.debug(self, "{0}".format(e)) + Log.error(self, "\nFailed to start service {0}" + .format(service_name)) + + def stop_service(self, service_name): + """ + Stop service + Similar to `service xyz stop` + """ + try: + Log.info(self, "Stop : {0:10}" .format(service_name), end='') + retcode = subprocess.getstatusoutput('service {0} stop' + .format(service_name)) + if retcode[0] == 0: + Log.info(self, "[" + Log.ENDC + "OK" + Log.OKBLUE + "]") + return True + else: + Log.debug(self, "{0}".format(retcode[1])) + Log.info(self, "[" + Log.FAIL + "Failed" + Log.OKBLUE+"]") + return False + except OSError as e: + Log.debug(self, "{0}".format(e)) + Log.error(self, "\nFailed to stop service : {0}" + .format(service_name)) + + def restart_service(self, service_name): + """ + Restart service + Similar to `service xyz restart` + """ + try: + if service_name in ['nginx', 'php5-fpm']: + service_cmd = ('{0} -t && service {0} restart' + .format(service_name)) + else: + service_cmd = ('service {0} restart'.format(service_name)) + + Log.info(self, "Restart : {0:10}".format(service_name), end='') + retcode = subprocess.getstatusoutput(service_cmd) + if retcode[0] == 0: + Log.info(self, "[" + Log.ENDC + "OK" + Log.OKBLUE + "]") + return True + else: + Log.debug(self, "{0}".format(retcode[1])) + Log.info(self, "[" + Log.FAIL + "Failed" + Log.OKBLUE+"]") + return False + except OSError as e: + Log.debug(self, "{0} {1}".format(e.errno, e.strerror)) + Log.error(self, "\nFailed to restart service : {0}" + .format(service_name)) + + def reload_service(self, service_name): + """ + Stop service + Similar to `service xyz stop` + """ + try: + if service_name in ['nginx', 'php5-fpm']: + service_cmd = ('{0} -t && service {0} reload' + .format(service_name)) + else: + service_cmd = ('service {0} reload'.format(service_name)) + + Log.info(self, "Reload : {0:10}".format(service_name), end='') + retcode = subprocess.getstatusoutput(service_cmd) + if retcode[0] == 0: + Log.info(self, "[" + Log.ENDC + "OK" + Log.OKBLUE + "]") + return True + else: + Log.debug(self, "{0}".format(retcode[1])) + Log.info(self, "[" + Log.FAIL + "Failed" + Log.OKBLUE+"]") + return False + except OSError as e: + Log.debug(self, "{0}".format(e)) + Log.error(self, "\nFailed to reload service {0}" + .format(service_name)) + + def get_service_status(self, service_name): + + + try: + is_exist = subprocess.getstatusoutput('which {0}' + .format(service_name)) + if is_exist[0] == 0 or service_name in ['php7.0-fpm', 'php5.6-fpm']: + retcode = subprocess.getstatusoutput('service {0} status' + .format(service_name)) + if retcode[0] == 0: + return True + else: + Log.debug(self, "{0}".format(retcode[1])) + return False + else: + return False + except OSError as e: + Log.debug(self, "{0}{1}".format(e.errno, e.strerror)) + Log.error(self, "Unable to get services status of {0}" + .format(service_name)) + return False diff --git a/wo/core/shellexec.py b/wo/core/shellexec.py new file mode 100644 index 0000000..c64d5bc --- /dev/null +++ b/wo/core/shellexec.py @@ -0,0 +1,83 @@ +"""WordOps Shell Functions""" +from wo.core.logging import Log +import os +import sys +import subprocess + + +class CommandExecutionError(Exception): + """custom Exception for command execution""" + pass + + +class WOShellExec(): + """Method to run shell commands""" + def __init__(): + pass + + def cmd_exec(self, command, errormsg='', log=True): + """Run shell command from Python""" + try: + log and Log.debug(self, "Running command: {0}".format(command)) + + with subprocess.Popen([command], stdout=subprocess.PIPE, + stderr=subprocess.PIPE, shell=True) as proc: + (cmd_stdout_bytes, cmd_stderr_bytes) = proc.communicate() + (cmd_stdout, cmd_stderr) = (cmd_stdout_bytes.decode('utf-8', + "replace"), + cmd_stderr_bytes.decode('utf-8', + "replace")) + + if proc.returncode == 0: + Log.debug(self, "Command Output: {0}, \nCommand Error: {1}" + .format(cmd_stdout, cmd_stderr)) + return True + else: + Log.debug(self, "Command Output: {0}, \nCommand Error: {1}" + .format(cmd_stdout, cmd_stderr)) + return False + except OSError as e: + Log.debug(self, str(e)) + raise CommandExecutionError + except Exception as e: + Log.debug(self, str(e)) + raise CommandExecutionError + + def invoke_editor(self, filepath, errormsg=''): + """ + Open files using sensible editor + """ + try: + subprocess.call(['sensible-editor', filepath]) + except OSError as e: + Log.debug(self, "{0}{1}".format(e.errno, e.strerror)) + raise CommandExecutionError + + + def cmd_exec_stdout(self, command, errormsg='', log=True): + """Run shell command from Python""" + try: + log and Log.debug(self, "Running command: {0}".format(command)) + + with subprocess.Popen([command], stdout=subprocess.PIPE, + stderr=subprocess.PIPE, shell=True) as proc: + (cmd_stdout_bytes, cmd_stderr_bytes) = proc.communicate() + (cmd_stdout, cmd_stderr) = (cmd_stdout_bytes.decode('utf-8', + "replace"), + cmd_stderr_bytes.decode('utf-8', + "replace")) + + if proc.returncode == 0: + Log.debug(self, "Command Output: {0}, \nCommand Error: {1}" + .format(cmd_stdout, cmd_stderr)) + return cmd_stdout + else: + Log.debug(self, "Command Output: {0}, \nCommand Error: {1}" + .format(cmd_stdout, cmd_stderr)) + return cmd_stdout + except OSError as e: + Log.debug(self, str(e)) + raise CommandExecutionError + except Exception as e: + Log.debug(self, str(e)) + raise CommandExecutionError \ No newline at end of file diff --git a/wo/core/sslutils.py b/wo/core/sslutils.py new file mode 100644 index 0000000..21412dd --- /dev/null +++ b/wo/core/sslutils.py @@ -0,0 +1,43 @@ +import os +from wo.core.shellexec import WOShellExec +from wo.core.logging import Log + + +class SSL: + + def getExpirationDays(self,domain,returnonerror=False): + # check if exist + if not os.path.isfile('/etc/letsencrypt/live/{0}/cert.pem' + .format(domain)): + Log.error(self,'File Not Found : /etc/letsencrypt/live/{0}/cert.pem' + .format(domain),False) + if returnonerror: + return -1 + Log.error(self, "Check the WordOps log for more details " + "`tail /var/log/wo/wordops.log` and please try again...") + + + current_date = WOShellExec.cmd_exec_stdout(self, "date -d \"now\" +%s") + expiration_date = WOShellExec.cmd_exec_stdout(self, "date -d \"`openssl x509 -in /etc/letsencrypt/live/{0}/cert.pem" + " -text -noout|grep \"Not After\"|cut -c 25-`\" +%s".format(domain)) + + days_left = int((int(expiration_date) - int(current_date))/ 86400) + if (days_left > 0): + return days_left + else: + # return "Certificate Already Expired ! Please Renew soon." + return -1 + + def getExpirationDate(self,domain): + # check if exist + if not os.path.isfile('/etc/letsencrypt/live/{0}/cert.pem' + .format(domain)): + Log.error(self,'File Not Found : /etc/letsencrypt/live/{0}/cert.pem' + .format(domain),False) + Log.error(self, "Check the WordOps log for more details " + "`tail /var/log/wo/wordops.log` and please try again...") + + expiration_date = WOShellExec.cmd_exec_stdout(self, "date -d \"`openssl x509 -in /etc/letsencrypt/live/{0}/cert.pem" + " -text -noout|grep \"Not After\"|cut -c 25-`\" ".format(domain)) + return expiration_date + diff --git a/wo/core/variables.py b/wo/core/variables.py new file mode 100644 index 0000000..4355659 --- /dev/null +++ b/wo/core/variables.py @@ -0,0 +1,209 @@ +"""WordOps core variable module""" +import platform +import socket +import configparser +import os +import sys +import psutil +import datetime + + +class WOVariables(): + """Intialization of core variables""" + + # WordOps version + wo_version = "3.9.0" + # WordOps packages versions + wo_wp_cli = "2.0.1" + wo_adminer = "4.6.3" + + # Get WPCLI path + wo_wpcli_path = os.popen('which wp | tr "\n" " "').read() + if wo_wpcli_path == '': + wo_wpcli_path = '/usr/bin/wp ' + + # Current date and time of System + wo_date = datetime.datetime.now().strftime('%d%b%Y%H%M%S') + + # WordOps core variables + wo_platform_distro = platform.linux_distribution()[0].lower() + wo_platform_version = platform.linux_distribution()[1] + wo_platform_codename = os.popen("lsb_release -sc | tr -d \'\\n\'").read() + + # Get timezone of system + if os.path.isfile('/etc/timezone'): + with open("/etc/timezone", "r") as tzfile: + wo_timezone = tzfile.read().replace('\n', '') + if wo_timezone == "Etc/UTC": + wo_timezone = "UTC" + else: + wo_timezone = "Europe/Amsterdam" + + # Get FQDN of system + wo_fqdn = socket.getfqdn() + + # WordOps default webroot path + wo_webroot = '/var/www/' + + # PHP5 user + wo_php_user = 'www-data' + + # Get git user name and EMail + config = configparser.ConfigParser() + config.read(os.path.expanduser("~")+'/.gitconfig') + try: + wo_user = config['user']['name'] + wo_email = config['user']['email'] + except Exception as e: + wo_user = input("Enter your name: ") + wo_email = input("Enter your email: ") + os.system("git config --global user.name {0}".format(wo_user)) + os.system("git config --global user.email {0}".format(wo_email)) + + # Get System RAM and SWAP details + wo_ram = psutil.virtual_memory().total / (1024 * 1024) + wo_swap = psutil.swap_memory().total / (1024 * 1024) + + # MySQL hostname + wo_mysql_host = "" + config = configparser.RawConfigParser() + if os.path.exists('/etc/mysql/conf.d/my.cnf'): + cnfpath = "/etc/mysql/conf.d/my.cnf" + else: + cnfpath = os.path.expanduser("~")+"/.my.cnf" + if [cnfpath] == config.read(cnfpath): + try: + wo_mysql_host = config.get('client', 'host') + except configparser.NoOptionError as e: + wo_mysql_host = "localhost" + else: + wo_mysql_host = "localhost" + + # WordOps stack installation variables + # Nginx repo and packages + if wo_platform_codename == 'precise': + wo_nginx_repo = ("deb http://download.opensuse.org/repositories/home:" + "/rtCamp:/EasyEngine/xUbuntu_12.04/ /") + elif wo_platform_codename == 'trusty': + wo_nginx_repo = ("deb http://download.opensuse.org/repositories/home:" + "/rtCamp:/EasyEngine/xUbuntu_14.04/ /") + elif wo_platform_codename == 'xenial': + wo_nginx_repo = ("deb http://download.opensuse.org/repositories/home:" + "/rtCamp:/EasyEngine/xUbuntu_16.04/ /") + elif wo_platform_codename == 'bionic': + wo_nginx_repo = ("deb http://download.opensuse.org/repositories/home:" + "/rtCamp:/EasyEngine/xUbuntu_18.04/ /") + elif wo_platform_codename == 'whwozy': + wo_nginx_repo = ("deb http://download.opensuse.org/repositories/home:" + "/rtCamp:/EasyEngine/Debian_7.0/ /") + elif wo_platform_codename == 'jessie': + wo_nginx_repo = ("deb http://download.opensuse.org/repositories/home:" + "/rtCamp:/EasyEngine/Debian_8.0/ /") + + + wo_nginx = ["nginx-custom", "nginx-ee"] + wo_nginx_key = '3050AC3CD2AE6F03' + + # PHP repo and packages + if wo_platform_distro == 'ubuntu': + if wo_platform_codename == 'precise': + wo_php_repo = "ppa:ondrej/php5-5.6" + wo_php = ["php5-fpm", "php5-curl", "php5-gd", "php5-imap", + "php5-mcrypt", "php5-common", "php5-readline", + "php5-mysql", "php5-cli", "php5-memcache", "php5-imagick", + "memcached", "graphviz", "php-pear"] + elif (wo_platform_codename == 'trusty' or wo_platform_codename == 'xenial' or wo_platform_codename == 'bionic'): + wo_php_repo = "ppa:ondrej/php" + # PHP5.6 for those who like to live on the dangerous side + wo_php5_6 = ["php5.6-fpm", "php5.6-curl", "php5.6-gd", "php5.6-imap", + "php5.6-mcrypt", "php5.6-readline", "php5.6-common", "php5.6-recode", + "php5.6-mysql", "php5.6-cli", "php5.6-curl", "php5.6-mbstring", + "php5.6-bcmath", "php5.6-mysql", "php5.6-opcache", "php5.6-zip", "php5.6-xml", "php5.6-soap"] + # PHP7.0 going EOL soon + wo_php7_0 = ["php7.0-fpm", "php7.0-curl", "php7.0-gd", "php7.0-imap", + "php7.0-mcrypt", "php7.0-readline", "php7.0-common", "php7.0-recode", + "php7.0-cli", "php7.0-mbstring", + "php7.0-bcmath", "php7.0-mysql", "php7.0-opcache", "php7.0-zip", "php7.0-xml", "php7.0-soap"] + wo_php7_2 = ["php7.2-fpm", "php7.2-curl", "php7.2-gd", "php7.2-imap", + "php7.2-readline", "php7.2-common", "php7.2-recode", + "php7.2-cli", "php7.2-mbstring", + "php7.2-bcmath", "php7.2-mysql", "php7.2-opcache", "php7.2-zip", "php7.2-xml", "php7.2-soap"] + wo_php_extra = ["php-memcached", "php-imagick", "php-memcache", "memcached", + "graphviz", "php-pear", "php-xdebug", "php-msgpack", "php-redis"] + elif wo_platform_distro == 'debian': + if wo_platform_codename == 'wheezy': + wo_php_repo = ("deb http://packages.dotdeb.org {codename}-php56 all" + .format(codename=wo_platform_codename)) + else : + wo_php_repo = ("deb http://packages.dotdeb.org {codename} all".format(codename=wo_platform_codename)) + + wo_php = ["php5-fpm", "php5-curl", "php5-gd", "php5-imap", + "php5-mcrypt", "php5-common", "php5-readline", + "php5-mysqlnd", "php5-cli", "php5-memcache", "php5-imagick", + "memcached", "graphviz", "php-pear"] + + wo_php7_0 = ["php7.0-fpm", "php7.0-curl", "php7.0-gd", "php7.0-imap", + "php7.0-mcrypt", "php7.0-common", "php7.0-readline", "php7.0-redis", + "php7.0-mysql", "php7.0-cli", "php7.0-memcache", "php7.0-imagick", + "php7.0-mbstring", "php7.0-recode", "php7.0-bcmath", "php7.0-opcache", "php7.0-zip", "php7.0-xml", + "php7.0-soap", "php7.0-msgpack", + "memcached", "graphviz", "php-pear", "php7.0-xdebug"] + wo_php_extra = [] + + if wo_platform_codename == 'wheezy': + wo_php = wo_php + ["php5-dev"] + + if wo_platform_codename == 'precise' or wo_platform_codename == 'jessie': + wo_php = wo_php + ["php5-xdebug"] + + # MySQL repo and packages + if wo_platform_distro == 'ubuntu': + wo_mysql_repo = ("deb [arch=amd64,i386,ppc64el] http://sfo1.mirrors.digitalocean.com/mariadb/repo/" + "10.1/ubuntu {codename} main" + .format(codename=wo_platform_codename)) + elif wo_platform_distro == 'debian': + wo_mysql_repo = ("deb [arch=amd64,i386,ppc64el] http://sfo1.mirrors.digitalocean.com/mariadb/repo/" + "10.1/debian {codename} main" + .format(codename=wo_platform_codename)) + + wo_mysql = ["mariadb-server", "percona-toolkit"] + + # HHVM repo details + # 12.04 requires boot repository + if wo_platform_distro == 'ubuntu': + if wo_platform_codename == "precise": + wo_boost_repo = ("ppa:mapnik/boost") + wo_hhvm_repo = ("deb http://dl.hhvm.com/ubuntu {codename} main" + .format(codename=wo_platform_codename)) + elif wo_platform_codename == "trusty": + wo_hhvm_repo = ("deb http://dl.hhvm.com/ubuntu {codename} main" + .format(codename=wo_platform_codename)) + else: + wo_hhvm_repo = ("deb http://dl.hhvm.com/debian {codename} main" + .format(codename=wo_platform_codename)) + + wo_hhvm = ["hhvm"] + + # Redis repo details + if wo_platform_distro == 'ubuntu': + wo_redis_repo = ("ppa:chris-lea/redis-server") + + else: + wo_redis_repo = ("deb http://packages.dotdeb.org {codename} all" + .format(codename=wo_platform_codename)) + + if (wo_platform_codename == 'trusty' or wo_platform_codename == 'xenial' or wo_platform_codename == 'bionic'): + wo_redis = ['redis-server', 'php-redis'] + else: + wo_redis = ['redis-server', 'php5-redis'] + + # Repo path + wo_repo_file = "wo-repo.list" + wo_repo_file_path = ("/etc/apt/sources.list.d/" + wo_repo_file) + + # Application dabase file path + basedir = os.path.abspath(os.path.dirname('/var/lib/wo/')) + wo_db_uri = 'sqlite:///' + os.path.join(basedir, 'dbase.db') + + def __init__(self): + pass diff --git a/wo/utils/__init__.py b/wo/utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/wo/utils/test.py b/wo/utils/test.py new file mode 100644 index 0000000..e543a1c --- /dev/null +++ b/wo/utils/test.py @@ -0,0 +1,15 @@ +"""Testing utilities for WordOps""" +from wo.cli.main import WOTestApp +from cement.utils.test import * + + +class WOTestCase(CementTestCase): + app_class = WOTestApp + + def setUp(self): + """Override setup actions (for every test).""" + super(WOTestCase, self).setUp() + + def tearDown(self): + """Override teardown actions (for every test).""" + super(WOTestCase, self).tearDown()