diff --git a/docs/changelog.rst b/docs/changelog.rst index 6554c03..f8aaef4 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -1,4 +1,9 @@ Changelog ========= +* :release:`0.1.0 <2015-01-27>` +* :feature:`-` add tasks to setup and delete per user PHP5 FPM pool + configurations +* :feature:`-` add tasks to setup, enable, disable and delete nginx virtual + host configurations * :support:`-` initial project setup diff --git a/docs/conf.py b/docs/conf.py index ddd7ec4..e7955e7 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -60,9 +60,9 @@ copyright = u'2015, Jan Dittberner' # built documents. # # The short X.Y version. -version = '0.0' +version = '0.1' # The full version, including alpha/beta/rc tags. -release = '0.0.0' +release = '0.1.0' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/gvaweb/webtasks/tasks.py b/gvaweb/webtasks/tasks.py index 6d71aa8..38a112f 100644 --- a/gvaweb/webtasks/tasks.py +++ b/gvaweb/webtasks/tasks.py @@ -5,6 +5,10 @@ This module defines Celery_ tasks to manage website configurations. from __future__ import absolute_import import os +import subprocess +from tempfile import mkstemp + +from jinja2 import Environment, PackageLoader from celery import shared_task from celery.utils.log import get_task_logger @@ -20,6 +24,21 @@ LN_CMD = '/bin/ln' SERVICE_CMD = '/usr/sbin/service' INSTALL_CMD = '/usr/bin/install' +JINJAENV = Environment(loader=PackageLoader('webtasks', 'templates')) + + +def _jinja_parentdomain(domain): + return '.'.join(domain.split('.')[1:]) + + +JINJAENV.filters['parentdomain'] = _jinja_parentdomain + + +def log_and_raise(exception, message, *args): + logargs = list(args) + [exception.returncode, exception.output] + _LOGGER.error(message + "\nreturncode: %d\noutput:\n%s", *logargs) + raise Exception(message % args) + def _build_vhost_config_path(sitename): return os.path.join(settings.GVAWEB_NGINX_SITES_AVAILABLE, sitename) @@ -39,18 +58,47 @@ def _build_document_root_path(sitename, username): settings.GVAWEB_WWWUSER_MOUNT, username, sitename, 'html') +def _get_template(templatename): + return JINJAENV.get_template(templatename) + + @shared_task -def create_web_vhost_config(username, sitename): +def create_web_vhost_config(username, sitename, wildcard): """ This task creates a virtual host configuration on an nginx web server. :param str username: user who owns the site :param str sitename: site name + :param boolean wildcard: designates whether this is website has a wildcard + subdomain :return: :py:const:`True` if the creation finished successfully :rtype: boolean """ + conftmpl = _get_template('vhost.nginx') + confdata = conftmpl.render( + domain=sitename, user=username, + docroot=_build_document_root_path(sitename, username), + wildcard=wildcard) + try: + nginxtemp, filename = mkstemp() + conffile = os.fdopen(nginxtemp, 'w') + conffile.write(confdata.encode('utf8')) + finally: + if conffile: + conffile.close() + try: + subprocess.check_output([ + SUDO_CMD, INSTALL_CMD, '-o', 'root', '-g', 'root', '-m', '0640', + filename, _build_vhost_config_path(sitename)], + stderr=subprocess.STDOUT) + subprocess.check_output([ + SUDO_CMD, RM_CMD, filename], stderr=subprocess.STDOUT) + except subprocess.CalledProcessError as cpe: + log_and_raise( + cpe, 'could not setup site configuration for %s', sitename) + return True @shared_task @@ -63,6 +111,17 @@ def disable_web_vhost(sitename): :rtype: boolean """ + try: + subprocess.check_output([ + SUDO_CMD, RM_CMD, _build_enabled_vhost_path(sitename)], + stderr=subprocess.STDOUT) + subprocess.check_output([ + SUDO_CMD, SERVICE_CMD, 'nginx', 'reload'], + stderr=subprocess.STDOUT) + except subprocess.CalledProcessError as cpe: + log_and_raise( + cpe, 'could not disable site configuration for %s', sitename) + return True @shared_task @@ -76,6 +135,20 @@ def enable_web_vhost(sitename): :rtype: boolean """ + try: + subprocess.check_output([ + SUDO_CMD, LN_CMD, '-s', + _build_vhost_config_path(sitename), + _build_enabled_vhost_path(sitename)], + stderr=subprocess.STDOUT) + subprocess.check_output([ + SUDO_CMD, SERVICE_CMD, 'nginx', 'restart'], + stderr=subprocess.STDOUT) + except subprocess.CalledProcessError as cpe: + log_and_raise( + cpe, 'could not enable site configuration for %s', sitename) + return True + @shared_task def delete_web_vhost_config(sitename): @@ -87,6 +160,14 @@ def delete_web_vhost_config(sitename): :rtype: boolean """ + try: + subprocess.check_output([ + SUDO_CMD, RM_CMD, _build_vhost_config_path(sitename)], + stderr=subprocess.STDOUT) + except subprocess.CalledProcessError as cpe: + log_and_raise( + cpe, 'could not delete site configuration for %s', sitename) + return True @shared_task @@ -99,6 +180,29 @@ def create_web_php_fpm_pool_config(username): :rtype: boolean """ + conftmpl = _get_template('fpmpool.conf') + confdata = conftmpl.render(user=username) + try: + fpmtemp, filename = mkstemp() + conffile = os.fdopen(fpmtemp, 'w') + conffile.write(confdata.encode('utf8')) + finally: + if conffile: + conffile.close() + try: + subprocess.check_output([ + SUDO_CMD, INSTALL_CMD, '-o', 'root', '-g', 'root', '-m', '0644', + filename, _build_php_fpm_pool_file(username)], + stderr=subprocess.STDOUT) + subprocess.check_output([ + SUDO_CMD, RM_CMD, filename], stderr=subprocess.STDOUT) + subprocess.check_output([ + SUDO_CMD, SERVICE_CMD, 'php5-fpm', 'reload'], + stderr=subprocess.STDOUT) + except subprocess.CalledProcessError as cpe: + log_and_raise(cpe, 'could not configure PHP FPM for %s', username) + return True + @shared_task def delete_web_php_fpm_pool_config(username): @@ -110,3 +214,14 @@ def delete_web_php_fpm_pool_config(username): :rtype: boolean """ + try: + subprocess.check_output([ + SUDO_CMD, RM_CMD, _build_php_fpm_pool_file(username)], + stderr=subprocess.STDOUT) + subprocess.check_output([ + SUDO_CMD, SERVICE_CMD, 'php5-fpm', 'reload'], + stderr=subprocess.STDOUT) + except subprocess.CalledProcessError as cpe: + log_and_raise( + cpe, 'could not delete PHP FPM configuration for %s', username) + return True diff --git a/gvaweb/webtasks/templates/fpmpool.conf b/gvaweb/webtasks/templates/fpmpool.conf new file mode 100644 index 0000000..66bb4c4 --- /dev/null +++ b/gvaweb/webtasks/templates/fpmpool.conf @@ -0,0 +1,15 @@ +[{{ user }}] +user = {{ user }} +group = {{ user }} +listen = /var/run/php5-fpm-{{ user }}.sock +listen.owner = www-data +listen.group = www-data +pm = dynamic +pm.max_children = 5 +pm.start_servers = 2 +pm.min_spare_servers = 1 +pm.max_spare_servers = 3 +pm.max_requests = 1000 +chdir = / +request_slowlog_timeout = 10s +slowlog = /var/log/php5-fpm/{{ user }}.slow.log diff --git a/gvaweb/webtasks/templates/vhost.nginx b/gvaweb/webtasks/templates/vhost.nginx new file mode 100644 index 0000000..16fa179 --- /dev/null +++ b/gvaweb/webtasks/templates/vhost.nginx @@ -0,0 +1,29 @@ +server { + server_name {{ domain }}; + {%- if wildcard %} + server_name *.{{ domain|parentdomain }}; + {%- endif %} + + access_log /var/log/nginx/{{ domain }}.access.log; + error_log /var/log/nginx/{{ domain }}.error.log; + + client_max_body_size 20M; + gzip on; + gzip_types text/javascript application/x-javascript text/css; + + root {{ docroot }}; + + index index.php index.html index.htm; + + location ~ ^/(.+\.php)$ { + try_files $uri =404; + fastcgi_pass unix:/var/run/php5-fpm-{{ user }}.sock; + fastcgi_index index.php; + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + include fastcgi_params; + } + + location ~ /\.ht { + deny all; + } +} diff --git a/requirements/base.txt b/requirements/base.txt index 00c89eb..fa14272 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -5,3 +5,4 @@ billiard==3.3.0.19 celery==3.1.17 kombu==3.0.24 pytz==2014.10 +Jinja2==2.7.3