Fixes on webapps and websites backend execution

This commit is contained in:
Marc Aymerich 2015-03-10 16:57:23 +00:00
parent 2a8d20910f
commit 44e8b29b43
24 changed files with 412 additions and 301 deletions

View File

@ -213,3 +213,7 @@ ssh-copy-id root@<server-address>
* webalizer backend on webapps and check webapps.websites.all()
* monitor in batches doesnt work!!!
* mv: cannot move `/home/marcay/webapps/webalizer/' to a subdirectory of itself, `/home/marcay/webapps/webalizer/.deleted'
* Create utility for dealing with web paths '//', leading and ending '/'

View File

@ -31,8 +31,8 @@ class MySQLBackend(ServiceController):
})
self.append(textwrap.dedent("""\
mysql -e 'GRANT ALL PRIVILEGES ON `%(database)s`.* TO "%(username)s"@"%(host)s" %(grant)s;' \
""" % context
))
""") % context
)
def delete(self, database):
if database.type != database.MYSQL:
@ -62,12 +62,12 @@ class MySQLUserBackend(ServiceController):
context = self.get_context(user)
self.append(textwrap.dedent("""\
mysql -e 'CREATE USER "%(username)s"@"%(host)s";' || true \
""" % context
))
""") % context
)
self.append(textwrap.dedent("""\
mysql -e 'UPDATE mysql.user SET Password="%(password)s" WHERE User="%(username)s";' \
""" % context
))
""") % context
)
def delete(self, user):
if user.type != user.MYSQL:
@ -75,8 +75,8 @@ class MySQLUserBackend(ServiceController):
context = self.get_context(user)
self.append(textwrap.dedent("""\
mysql -e 'DROP USER "%(username)s"@"%(host)s";' \
""" % context
))
""") % context
)
def commit(self):
self.append("mysql -e 'FLUSH PRIVILEGES;'")
@ -99,8 +99,8 @@ class MysqlDisk(ServiceMonitor):
context = self.get_context(db)
self.append(textwrap.dedent("""\
mysql -e 'UPDATE db SET Insert_priv="N", Create_priv="N" WHERE Db="%(db_name)s";'\
""" % context
))
""") % context
)
def recovery(self, db):
if db.type != db.MYSQL:
@ -108,8 +108,8 @@ class MysqlDisk(ServiceMonitor):
context = self.get_context(db)
self.append(textwrap.dedent("""\
mysql -e 'UPDATE db SET Insert_priv="Y", Create_priv="Y" WHERE Db="%(db_name)s";'\
""" % context
))
""") % context
)
def prepare(self):
super(MysqlDisk, self).prepare()

View File

@ -36,8 +36,8 @@ class Bind9MasterDomainBackend(ServiceController):
mv %(zone_path)s.tmp %(zone_path)s
# Because bind realod will not display any fucking error
named-checkzone -k fail -n fail %(name)s %(zone_path)s
""" % context
))
""") % context
)
self.update_conf(context)
def update_conf(self, context):
@ -47,13 +47,13 @@ class Bind9MasterDomainBackend(ServiceController):
-e 'N; /^\s*\\n\s*$/d; P; D' %(conf_path)s
echo '%(conf)s' >> %(conf_path)s
UPDATED=1
}""" % context
))
}""") % context
)
# Delete ex-top-domains that are now subdomains
self.append(textwrap.dedent("""\
sed -i -e '/zone\s\s*".*\.%(name)s".*/,/^\s*};\s*$/d' \\
-e 'N; /^\s*\\n\s*$/d; P; D' %(conf_path)s""" % context
))
-e 'N; /^\s*\\n\s*$/d; P; D' %(conf_path)s""") % context
)
if 'zone_path' in context:
context['zone_subdomains_path'] = re.sub(r'^(.*/)', r'\1*.', context['zone_path'])
self.append('rm -f %(zone_subdomains_path)s' % context)
@ -69,19 +69,19 @@ class Bind9MasterDomainBackend(ServiceController):
return
self.append(textwrap.dedent("""\
sed -e '/zone\s\s*"%(name)s".*/,/^\s*};\s*$/d' \\
-e 'N; /^\s*\\n\s*$/d; P; D' %(conf_path)s > %(conf_path)s.tmp""" % context
))
-e 'N; /^\s*\\n\s*$/d; P; D' %(conf_path)s > %(conf_path)s.tmp""") % context
)
self.append('diff -B -I"^\s*//" %(conf_path)s.tmp %(conf_path)s || UPDATED=1' % context)
self.append('mv %(conf_path)s.tmp %(conf_path)s' % context)
def commit(self):
""" reload bind if needed """
self.append('[[ $UPDATED == 1 ]] && service bind9 reload')
self.append('if [[ $UPDATED == 1 ]]; then service bind9 reload; fi')
def get_servers(self, domain, backend):
""" Get related server IPs from registered backend routes """
from orchestra.apps.orchestration.manager import router
operation = Operation.create(backend, peration.SAVE, domain)
operation = Operation.create(backend, domain, Operation.SAVE)
servers = []
for server in router.get_servers(operation):
servers.append(server.get_ip())
@ -110,7 +110,7 @@ class Bind9MasterDomainBackend(ServiceController):
allow-transfer { %(slaves)s; };
also-notify { %(also_notify)s };
notify yes;
};""" % context)
};""") % context
})
return context
@ -131,7 +131,7 @@ class Bind9SlaveDomainBackend(Bind9MasterDomainBackend):
def commit(self):
""" ideally slave should be restarted after master """
self.append('[[ $UPDATED == 1 ]] && { sleep 1 && service bind9 reload; } &')
self.append('if [[ $UPDATED == 1 ]]; then { sleep 1 && service bind9 reload; } & fi')
def get_masters(self, domain):
return self.get_servers(domain, Bind9MasterDomainBackend)
@ -152,6 +152,6 @@ class Bind9SlaveDomainBackend(Bind9MasterDomainBackend):
file "%(name)s";
masters { %(masters)s; };
allow-notify { %(masters)s; };
};""" % context)
};""") % context
})
return context

View File

@ -33,8 +33,8 @@ class PasswdVirtualUserBackend(ServiceController):
sed -i 's#^%(username)s:.*#%(passwd)s#' %(passwd_path)s
else
echo '%(passwd)s' >> %(passwd_path)s
fi""" % context
))
fi""") % context
)
self.append("mkdir -p %(home)s" % context)
self.append("chown %(uid)s:%(gid)s %(home)s" % context)
@ -43,7 +43,8 @@ class PasswdVirtualUserBackend(ServiceController):
if [[ ! $(grep "^%(username)s@%(mailbox_domain)s\s" %(virtual_mailbox_maps)s) ]]; then
echo "%(username)s@%(mailbox_domain)s\tOK" >> %(virtual_mailbox_maps)s
UPDATED_VIRTUAL_MAILBOX_MAPS=1
fi""" % context))
fi""") % context
)
def generate_filter(self, mailbox, context):
self.append("doveadm mailbox create -u %(username)s Spam" % context)
@ -92,7 +93,8 @@ class PasswdVirtualUserBackend(ServiceController):
self.append(textwrap.dedent("""\
[[ $UPDATED_VIRTUAL_MAILBOX_MAPS == 1 ]] && {
postmap %(virtual_mailbox_maps)s
}""" % context))
}""") % context
)
def get_context(self, mailbox):
context = {

View File

@ -154,7 +154,7 @@ class BackendOperation(models.Model):
"""
if self.action == self.DELETE:
if hasattr(self.backend, 'get_context'):
self.backend.get_context(self.instance)
self.backend().get_context(self.instance)
def backend_class(self):
return ServiceBackend.get_backend(self.backend)

View File

@ -26,8 +26,8 @@ class SystemUserBackend(ServiceController):
fi
mkdir -p %(home)s
chmod 750 %(home)s
chown %(username)s:%(username)s %(home)s""" % context
))
chown %(username)s:%(username)s %(home)s""") % context
)
for member in settings.SYSTEMUSERS_DEFAULT_GROUP_MEMBERS:
context['member'] = member
self.append('usermod -a -G %(username)s %(member)s' % context)
@ -40,8 +40,8 @@ class SystemUserBackend(ServiceController):
{ sleep 2 && killall -u %(username)s -s KILL; } &
killall -u %(username)s || true
userdel %(username)s || true
groupdel %(username)s || true""" % context
))
groupdel %(username)s || true""") % context
)
self.delete_home(context, user)
def grant_permission(self, user):
@ -145,7 +145,7 @@ class FTPTraffic(ServiceMonitor):
print sum
}' || [[ $? == 1 ]] && true
} | xargs echo ${OBJECT_ID}
}""" % current_date))
}""") % current_date)
def monitor(self, user):
context = self.get_context(user)

View File

@ -53,7 +53,7 @@ class WebAppAdmin(SelectPluginAdminMixin, AccountAdminMixin, ExtendedModelAdmin)
inlines = [WebAppOptionInline]
readonly_fields = ('account_link',)
change_readonly_fields = ('name', 'type')
list_prefetch_related = ('contents__website',)
list_prefetch_related = ('content_set__website',)
plugin = AppType
plugin_field = 'type'
plugin_title = _("Web application type")
@ -64,7 +64,7 @@ class WebAppAdmin(SelectPluginAdminMixin, AccountAdminMixin, ExtendedModelAdmin)
def display_websites(self, webapp):
websites = []
for content in webapp.contents.all():
for content in webapp.content_set.all():
website = content.website
url = change_url(website)
name = "%s on %s" % (website.name, content.path)

View File

@ -1,15 +1,22 @@
import pkgutil
import textwrap
from .. import settings
class WebAppServiceMixin(object):
model = 'webapps.WebApp'
directive = None
def create_webapp_dir(self, context):
self.append("[[ ! -e %(app_path)s ]] && CREATED=true" % context)
self.append("mkdir -p %(app_path)s" % context)
self.append("chown %(user)s:%(group)s %(app_path)s" % context)
def set_under_construction(self, context):
if context['under_construction_path']:
self.append("[[ $CREATED ]] && cp -r %(under_construction_path)s %(app_path)s" % context)
def delete_webapp_dir(self, context):
self.append("rm -fr %(app_path)s" % context)
@ -21,6 +28,7 @@ class WebAppServiceMixin(object):
'type': webapp.type,
'app_path': webapp.get_path().rstrip('/'),
'banner': self.get_banner(),
'under_construction_path': settings.settings.WEBAPPS_UNDER_CONSTRUCTION_PATH
}

View File

@ -12,54 +12,82 @@ from .. import settings
class PHPFcgidBackend(WebAppServiceMixin, ServiceController):
""" Per-webapp fcgid application """
verbose_name = _("PHP-Fcgid")
directive = 'fcgi'
default_route_match = "webapp.type.endswith('-fcgi')"
directive = 'fcgid'
default_route_match = "webapp.type.endswith('-fcgid')"
def save(self, webapp):
if not self.valid_directive(webapp):
return
context = self.get_context(webapp)
self.create_webapp_dir(context)
self.set_under_construction(context)
self.append("mkdir -p %(wrapper_dir)s" % context)
self.append(textwrap.dedent("""\
{
echo -e '%(wrapper_content)s' | diff -N -I'^\s*#' %(wrapper_path)s -
echo -e '%(wrapper)s' | diff -N -I'^\s*#' %(wrapper_path)s -
} || {
echo -e '%(wrapper_content)s' > %(wrapper_path)s; UPDATED_APACHE=1
}""" % context))
echo -e '%(wrapper)s' > %(wrapper_path)s; UPDATED_APACHE=1
}""") % context
)
self.append("chmod +x %(wrapper_path)s" % context)
self.append("chown -R %(user)s:%(group)s %(wrapper_dir)s" % context)
if context['cmd_options']:
self.append(textwrap.dedent("""
{
echo -e '%(cmd_options)s' | diff -N -I'^\s*#' %(cmd_options_path)s -
} || {
echo -e '%(cmd_options)s' > %(cmd_options_path)s; UPDATED_APACHE=1
}""" ) % context
)
def delete(self, webapp):
if not self.valid_directive(webapp):
return
context = self.get_context(webapp)
self.append("rm '%(wrapper_path)s'" % context)
self.delete_webapp_dir(context)
def commit(self):
if not self.cmds:
return
super(PHPFcgidBackend, self).commit()
self.append("[[ $UPDATED_APACHE == 1 ]] && { service apache2 reload; }")
self.append('if [[ $UPDATED_APACHE == 1 ]]; then service apache2 reload; fi')
def get_fcgid_wrapper(self, webapp, context):
opt = webapp.type_instance
# Format PHP init vars
init_vars = opt.get_php_init_vars(webapp)
if init_vars:
init_vars = [ '-d %s="%s"' % (k,v) for k,v in init_vars.iteritems() ]
init_vars = ', '.join(init_vars)
context.update({
'php_binary': opt.php_binary,
'php_rc': opt.php_rc,
'php_init_vars': init_vars,
})
return textwrap.dedent("""\
#!/bin/sh
# %(banner)s
export PHPRC=%(php_rc)s
exec %(php_binary)s %(php_init_vars)s""") % context
def get_fcgid_cmd_options(self, webapp, context):
maps = {
'MaxProcesses': webapp.get_options().get('processes', None),
'IOTimeout': webapp.get_options().get('timeout', None),
}
cmd_options = []
for directive, value in maps.iteritems():
if value:
cmd_options.append("%s %s" % (directive, value))
if cmd_options:
cmd_options.insert(0, 'FcgidCmdOptions %(wrapper_path)s' % context)
return ' \\\n '.join(cmd_options)
def get_context(self, webapp):
context = super(PHPFcgidBackend, self).get_context(webapp)
init_vars = self.get_php_init_vars(webapp)
if init_vars:
init_vars = [ '%s="%s"' % (k,v) for k,v in init_vars ]
init_vars = ', -d '.join(init_vars)
context['init_vars'] = '-d %s' % init_vars
else:
context['init_vars'] = ''
wrapper_path = settings.WEBAPPS_FCGID_PATH % context
context.update({
'wrapper_content': textwrap.dedent("""\
#!/bin/sh
# %(banner)s
export PHPRC=/etc/%(type)s/cgi/
exec /usr/bin/%(type)s-cgi %(init_vars)s""" % context),
'wrapper': self.get_fcgid_wrapper(webapp, context),
'wrapper_path': wrapper_path,
'wrapper_dir': os.path.dirname(wrapper_path),
})
context.update({
'cmd_options': self.get_fcgid_cmd_options(webapp, context),
'cmd_options_path': settings.WEBAPPS_FCGID_CMD_OPTIONS_PATH % context,
})
return context

View File

@ -18,14 +18,15 @@ class PHPFPMBackend(WebAppServiceMixin, ServiceController):
def save(self, webapp):
context = self.get_context(webapp)
self.create_webapp_dir(context)
self.set_under_construction(context)
self.append(textwrap.dedent("""\
{
echo -e '%(fpm_config)s' | diff -N -I'^\s*;;' %(fpm_path)s -
} || {
echo -e '%(fpm_config)s' > %(fpm_path)s
UPDATEDFPM=1
}""" % context
))
}""") % context
)
def delete(self, webapp):
context = self.get_context(webapp)
@ -37,18 +38,17 @@ class PHPFPMBackend(WebAppServiceMixin, ServiceController):
return
super(PHPFPMBackend, self).commit()
self.append(textwrap.dedent("""
[[ $UPDATEDFPM == 1 ]] && {
if [[ $UPDATEDFPM == 1 ]]; then
service php5-fpm reload
service php5-fpm start
}"""))
fi"""))
def get_context(self, webapp):
if not self.valid_directive(webapp):
return
context = super(PHPFPMBackend, self).get_context(webapp)
def get_fpm_config(self, webapp, context):
context.update({
'init_vars': self.get_php_init_vars(webapp),
'init_vars': webapp.type_instance.get_php_init_vars(webapp),
'fpm_port': webapp.get_fpm_port(),
'max_children': webapp.get_options().get('processes', False),
'request_terminate_timeout': webapp.get_options().get('timeout', False),
})
context['fpm_listen'] = settings.WEBAPPS_FPM_LISTEN % context
fpm_config = Template(textwrap.dedent("""\
@ -61,12 +61,18 @@ class PHPFPMBackend(WebAppServiceMixin, ServiceController):
listen.owner = {{ user }}
listen.group = {{ group }}
pm = ondemand
pm.max_children = 4
{% for name, value in init_vars %}
php_admin_value[{{ name | safe }}] = {{ value | safe }}{% endfor %}"""
{% if max_children %}pm.max_children = {{ max_children }}{% endif %}
{% if request_terminate_timeout %}request_terminate_timeout = {{ request_terminate_timeout }}{% endif %}
{% for name, value in init_vars.iteritems %}
php_admin_value[{{ name | safe }}] = {{ value | safe }}{% endfor %}
"""
))
return fpm_config.render(Context(context))
def get_context(self, webapp):
context = super(PHPFPMBackend, self).get_context(webapp)
context.update({
'fpm_config': fpm_config.render(Context(context)),
'fpm_config': self.get_fpm_config(webapp, context),
'fpm_path': settings.WEBAPPS_PHPFPM_POOL_PATH % context,
})
return context

View File

@ -7,17 +7,13 @@ from . import WebAppServiceMixin
class StaticBackend(WebAppServiceMixin, ServiceController):
verbose_name = _("Static")
directive = 'static'
default_route_match = "webapp.type == 'static'"
def save(self, webapp):
if not self.valid_directive(webapp):
return
context = self.get_context(webapp)
self.create_webapp_dir(context)
self.set_under_construction(context)
def delete(self, webapp):
if not self.valid_directive(webapp):
return
context = self.get_context(webapp)
self.delete_webapp_dir(context)

View File

@ -30,8 +30,8 @@ class WordPressBackend(WebAppServiceMixin, ServiceController):
mkdir %(app_path)s/wp-content/uploads
chmod 750 %(app_path)s/wp-content/uploads
chown -R %(user)s:%(group)s %(app_path)s
fi""" % context
))
fi""") % context
)
def delete(self, webapp):
context = self.get_context(webapp)

View File

@ -12,6 +12,7 @@ from orchestra.core import validators, services
from orchestra.utils.functional import cached
from . import settings
from .options import AppOption
from .types import AppType
@ -55,9 +56,6 @@ class WebApp(models.Model):
opt.name: opt.value for opt in self.options.all()
}
def get_fpm_port(self):
return settings.WEBAPPS_FPM_START_PORT + self.account_id
def get_directive(self):
return self.type_instance.get_directive(self)
@ -67,6 +65,9 @@ class WebApp(models.Model):
'app_name': self.name,
}
path = settings.WEBAPPS_BASE_ROOT % context
public_root = self.options.filter(name='public-root').first()
if public_root:
path = os.path.join(path, public_root.value)
return path.replace('//', '/')
def get_user(self):
@ -95,7 +96,7 @@ class WebAppOption(models.Model):
@cached_property
def option_class(self):
return SiteDirective.get_plugin(self.name)
return AppOption.get_plugin(self.name)
@cached_property
def option_instance(self):

View File

@ -1,3 +1,5 @@
import re
from django.core.exceptions import ValidationError
from django.utils.translation import ugettext_lazy as _
@ -54,15 +56,6 @@ class PublicRoot(AppOption):
group = AppOption.FILESYSTEM
class DirectoryProtection(AppOption):
name = 'directory-protection'
verbose_name = _("Directory protection")
help_text = _("Space separated ...")
regex = r'^([\w/_]+)\s+(\".*\")\s+([\w/_\.]+)$'
group = AppOption.FILESYSTEM
class Timeout(AppOption):
name = 'timeout'
# FCGID FcgidIOTimeout
@ -271,7 +264,7 @@ class PHPSessionBugCompatWarn(AppOption):
class PHPSessionAutoStart(AppOption):
name = 'session.auto_start',
name = 'session.auto_start'
verbose_name = _("session.auto_start")
help_text = _("Specifies whether the session module starts a session automatically on request "
"startup (On or Off).")
@ -289,7 +282,7 @@ class PHPSafeMode(AppOption):
class PHPSuhosinPostMaxVars(AppOption):
name = 'suhosin.post.max_vars',
name = 'suhosin.post.max_vars'
verbose_name = _("Suhosin POST max vars")
help_text = _("Number between 0 and 9999.")
regex = r'^[0-9]{1,4}$'
@ -336,7 +329,7 @@ class PHPSuhosinExecutorIncludeWhitelist(AppOption):
class PHPUploadMaxFileSize(AppOption):
name = 'upload_max_filesize',
name = 'upload_max_filesize'
verbose_name = _("upload_max_filesize")
help_text = _("Value between 0M and 999M.")
regex = r'^[0-9]{1,3}M$'

View File

@ -2,28 +2,31 @@ from django.conf import settings
from django.utils.translation import ugettext_lazy as _
WEBAPPS_BASE_ROOT = getattr(settings, 'WEBAPPS_BASE_ROOT', '{home}/webapps/{app_name}/')
WEBAPPS_BASE_ROOT = getattr(settings, 'WEBAPPS_BASE_ROOT', '%(home)s/webapps/%(app_name)s/')
WEBAPPS_FPM_LISTEN = getattr(settings, 'WEBAPPS_FPM_LISTEN',
# '127.0.0.1:9{app_id:03d}
'/opt/php/5.4/socks/{user}-{app_name}.sock'
# '127.0.0.1:9%(app_id)03d
'/opt/php/5.4/socks/%(user)s-%(app_name)s.sock'
)
WEBAPPS_FPM_START_PORT = getattr(settings, 'WEBAPPS_FPM_START_PORT', 10000)
WEBAPPS_PHPFPM_POOL_PATH = getattr(settings, 'WEBAPPS_PHPFPM_POOL_PATH',
'/etc/php5/fpm/pool.d/{user}-{app_name}.conf')
'/etc/php5/fpm/pool.d/%(user)s-%(app_name)s.conf')
WEBAPPS_FCGID_PATH = getattr(settings, 'WEBAPPS_FCGID_PATH',
'/home/httpd/fcgid/{user}/{app_name}-wrapper')
'/home/httpd/fcgi-bin.d/%(user)s/%(app_name)s-wrapper')
WEBAPPS_FCGID_CMD_OPTIONS_PATH = getattr(settings, 'WEBAPPS_FCGID_CMD_OPTIONS_PATH',
'/etc/apache2/fcgid-conf/%(user)s-%(app_name)s.conf')
WEBAPPS_PHP_ERROR_LOG_PATH = getattr(settings, 'WEBAPPS_PHP_ERROR_LOG_PATH',
'')
WEBAPPS_TYPES = getattr(settings, 'WEBAPPS_TYPES', (
'orchestra.apps.webapps.types.PHP54App',
'orchestra.apps.webapps.types.PHP53App',
'orchestra.apps.webapps.types.PHP52App',
'orchestra.apps.webapps.types.PHP4App',
'orchestra.apps.webapps.types.StaticApp',
@ -35,7 +38,10 @@ WEBAPPS_TYPES = getattr(settings, 'WEBAPPS_TYPES', (
'orchestra.apps.webapps.types.WordPressApp',
))
WEBAPPS_UNDER_CONSTRUCTION_PATH = getattr(settings, 'WEBAPPS_UNDER_CONSTRUCTION_PATH',
# Server-side path where a under construction stock page is
# '/var/www/undercontruction/index.html',
'')
#WEBAPPS_TYPES_OVERRIDE = getattr(settings, 'WEBAPPS_TYPES_OVERRIDE', {})
#for webapp_type, value in WEBAPPS_TYPES_OVERRIDE.iteritems():
@ -79,7 +85,6 @@ WEBAPPS_PHP_DISABLED_FUNCTIONS = getattr(settings, 'WEBAPPS_PHP_DISABLED_FUNCTIO
WEBAPPS_ENABLED_OPTIONS = getattr(settings, 'WEBAPPS_ENABLED_OPTIONS', (
'orchestra.apps.webapps.options.PublicRoot',
'orchestra.apps.webapps.options.DirectoryProtection',
'orchestra.apps.webapps.options.Timeout',
'orchestra.apps.webapps.options.Processes',
'orchestra.apps.webapps.options.PHPEnabledFunctions',
@ -140,7 +145,7 @@ WEBAPPS_DOKUWIKIMU_LISTEN = getattr(settings, 'WEBAPPS_DOKUWIKIMU_LISTEN',
WEBAPPS_DRUPALMU_SITES_PATH = getattr(settings, 'WEBAPPS_DRUPALMU_SITES_PATH',
'/home/httpd/htdocs/drupal-mu/sites/{site_name}')
'/home/httpd/htdocs/drupal-mu/sites/%(site_name)s')
WEBAPPS_DRUPALMU_LISTEN = getattr(settings, 'WEBAPPS_DRUPALMU_LISTEN',
'/opt/php/5.4/socks/drupal-mu.sock'

View File

@ -1,3 +1,5 @@
import os
from django import forms
from django.core.exceptions import ValidationError
from django.utils.safestring import mark_safe
@ -129,25 +131,31 @@ class PHPAppType(AppType):
socket_type = 'unix'
if ':' in self.fpm_listen:
socket_type = 'tcp'
socket = self.fpm_listen.format(context)
socket = self.fpm_listen % context
return ('fpm', socket_type, socket, webapp.get_path())
def get_context(self, webapp):
""" context used to format settings """
return {
'home': webapp.account.main_systemuser.get_home(),
'account': webapp.account.username,
'user': webapp.account.username,
'app_name': webapp.name,
}
def get_php_init_vars(self, webapp, per_account=False):
"""
process php options for inclusion on php.ini
per_account=True merges all (account, webapp.type) options
"""
init_vars = []
php_options = type(self).get_php_options()
init_vars = {}
options = webapp.options.all()
if per_account:
options = webapp.account.webapps.filter(webapp_type=webapp.type)
php_options = [option.name for option in php_options]
php_options = [option.name for option in type(self).get_php_options()]
for opt in options:
if opt.option_class in php_options:
init_vars.append(
(opt.name, opt.value)
)
if opt.name in php_options:
init_vars[opt.name] = opt.value
enabled_functions = []
for value in options.filter(name='enabled_functions').values_list('value', flat=True):
enabled_functions += enabled_functions.get().value.split(',')
@ -156,9 +164,10 @@ class PHPAppType(AppType):
for function in settings.WEBAPPS_PHP_DISABLED_FUNCTIONS:
if function not in enabled_functions:
disabled_functions.append(function)
init_vars.append(
('dissabled_functions', ','.join(disabled_functions))
)
init_vars['dissabled_functions'] = ','.join(disabled_functions)
if settings.WEBAPPS_PHP_ERROR_LOG_PATH and 'error_log' not in init_vars:
context = self.get_context(webapp)
init_vars['error_log'] = settings.WEBAPPS_PHP_ERROR_LOG_PATH % context
return init_vars
@ -171,23 +180,37 @@ class PHP54App(PHPAppType):
icon = 'orchestra/icons/apps/PHPFPM.png'
class PHP52App(PHPAppType):
name = 'php5.2-fcgid'
php_version = 5.2
verbose_name = "PHP 5.2 FCGID"
help_text = _("This creates a PHP5.2 application under ~/webapps/&lt;app_name&gt;<br>"
class PHP53App(PHPAppType):
name = 'php5.3-fcgid'
php_version = 5.3
php_binary = '/usr/bin/php5-cgi'
php_rc = '/etc/php5/cgi/'
verbose_name = "PHP 5.3 FCGID"
help_text = _("This creates a PHP5.3 application under ~/webapps/&lt;app_name&gt;<br>"
"Apache-mod-fcgid will be used to execute PHP files.")
icon = 'orchestra/icons/apps/PHPFCGI.png'
def get_directive(self, webapp):
context = self.get_directive_context(webapp)
wrapper_path = settings.WEBAPPS_FCGID_PATH.format(context)
return ('fcgi', webapp.get_path(), wrapper_path)
wrapper_path = settings.WEBAPPS_FCGID_PATH % context
return ('fcgid', webapp.get_path(), wrapper_path)
class PHP4App(PHP52App):
class PHP52App(PHP53App):
name = 'php5.2-fcgid'
php_version = 5.2
php_binary = '/usr/bin/php5.2-cgi'
php_rc = '/etc/php5.2/cgi/'
verbose_name = "PHP 5.2 FCGID"
help_text = _("This creates a PHP5.2 application under ~/webapps/&lt;app_name&gt;<br>"
"Apache-mod-fcgid will be used to execute PHP files.")
icon = 'orchestra/icons/apps/PHPFCGI.png'
class PHP4App(PHP53App):
name = 'php4-fcgid'
php_version = 4
php_binary = '/usr/bin/php4-cgi'
verbose_name = "PHP 4 FCGID"
help_text = _("This creates a PHP4 application under ~/webapps/&lt;app_name&gt;<br>"
"Apache-mod-fcgid will be used to execute PHP files.")
@ -213,7 +236,7 @@ class WebalizerApp(AppType):
option_groups = ()
def get_directive(self, webapp):
return ('static', webapp.get_path())
return ('static', os.path.join(webapp.get_path(), '%(site_name)s/'))
class WordPressMuApp(PHPAppType):

View File

@ -35,7 +35,7 @@ class DirectiveInline(admin.TabularInline):
if db_field.name == 'name':
# Help text based on select widget
kwargs['widget'] = DynamicHelpTextSelect(
'this.id.replace("name", "value")', self.DIECTIVES_HELP_TEXT
'this.id.replace("name", "value")', self.DIRECTIVES_HELP_TEXT
)
return super(DirectiveInline, self).formfield_for_dbfield(db_field, **kwargs)
@ -71,7 +71,7 @@ class WebsiteAdmin(SelectAccountAdminMixin, ExtendedModelAdmin):
)
form = WebsiteAdminForm
filter_by_account_fields = ['domains']
list_prefetch_related = ('domains', 'contents__webapp')
list_prefetch_related = ('domains', 'content_set__webapp')
search_fields = ('name', 'account__username', 'domains__name')
def display_domains(self, website):
@ -86,7 +86,7 @@ class WebsiteAdmin(SelectAccountAdminMixin, ExtendedModelAdmin):
def display_webapps(self, website):
webapps = []
for content in website.contents.all():
for content in website.content_set.all():
webapp = content.webapp
url = change_url(webapp)
name = "%s on %s" % (webapp.get_type_display(), content.path)

View File

@ -44,7 +44,7 @@ class Apache2Backend(ServiceController):
</VirtualHost>"""
))
apache_conf = apache_conf.render(Context(context))
apache_conf += self.get_protections(site)
# apache_conf += self.get_protections(site)
context['apache_conf'] = apache_conf
self.append(textwrap.dedent("""\
@ -64,21 +64,21 @@ class Apache2Backend(ServiceController):
def commit(self):
""" reload Apache2 if necessary """
self.append('[[ $UPDATED == 1 ]] && service apache2 reload || true')
self.append('if [[ $UPDATED == 1 ]]; then service apache2 reload; fi')
def get_content_directives(self, site):
directives = ''
for content in site.contents.all().order_by('-path'):
for content in site.content_set.all().order_by('-path'):
directive = content.webapp.get_directive()
method, agrs = directive[0], directive[1:]
method, args = directive[0], directive[1:]
method = getattr(self, 'get_%s_directives' % method)
directives += method(content, *args)
return directives
def get_static_directives(self, content, app_path):
context = self.get_content_context(content)
context['app_path'] = app_path
return "Alias %(location)s %(path)s\n" % context
context['app_path'] = app_path % context
return "Alias %(location)s %(app_path)s\n" % context
def get_fpm_directives(self, content, socket_type, socket, app_path):
if socket_type == 'unix':
@ -95,29 +95,26 @@ class Apache2Backend(ServiceController):
'socket': socket,
})
return textwrap.dedent("""\
ProxyPassMatch ^%(location)s/(.*\.php(/.*)?)$ {target}
Alias %(location)s/ %(app_path)s/
ProxyPassMatch ^%(location)s(.*\.php(/.*)?)$ {target}
Alias %(location)s %(app_path)s/
""".format(target=target) % context
)
def get_fcgi_directives(self, content, app_path, wrapper_path):
def get_fcgid_directives(self, content, app_path, wrapper_path):
context = self.get_content_context(content)
context.update({
'app_path': app_path,
'wrapper_path': wrapper_path,
})
fcgid = textwrap.dedent("""\
return textwrap.dedent("""\
Alias %(location)s %(app_path)s
ProxyPass %(location)s !
<Directory %(app_path)s>
Options +ExecCGI
AddHandler fcgid-script .php
FcgidWrapper %(wrapper_path)s\
FcgidWrapper %(wrapper_path)s
</Directory>
""") % context
for option in content.webapp.options.filter(name__startswith='Fcgid'):
fcgid += " %s %s\n" % (option.name, option.value)
fcgid += "</Directory>\n"
return fcgid
def get_ssl(self, site):
cert = settings.WEBSITES_DEFAULT_HTTPS_CERT
@ -129,54 +126,53 @@ class Apache2Backend(ServiceController):
SSLEngine on
SSLCertificateFile %s
SSLCertificateKeyFile %s\
""" % cert
)
""") % cert
return directives
def get_security(self, site):
directives = ''
for rules in site.options.filter(name='sec_rule_remove'):
for rules in site.directives.filter(name='sec_rule_remove'):
for rule in rules.value.split():
directives += "SecRuleRemoveById %i\n" % int(rule)
for modsecurity in site.options.filter(name='sec_rule_off'):
for modsecurity in site.directives.filter(name='sec_rule_off'):
directives += textwrap.dedent("""\
<LocationMatch %s>
SecRuleEngine Off
</LocationMatch>\
""" % modsecurity.value)
""") % modsecurity.value
if directives:
directives = '<IfModule mod_security2.c>\n%s\n</IfModule>' % directives
return directives
def get_redirect(self, site):
directives = ''
for redirect in site.options.filter(name='redirect'):
for redirect in site.directives.filter(name='redirect'):
if re.match(r'^.*[\^\*\$\?\)]+.*$', redirect.value):
directives += "RedirectMatch %s" % redirect.value
else:
directives += "Redirect %s" % redirect.value
return directives
def get_protections(self, site):
protections = ''
context = self.get_context(site)
for protection in site.options.filter(name='directory_protection'):
path, name, passwd = protection.value.split()
path = os.path.join(context['root'], path)
passwd = os.path.join(self.USER_HOME % context, passwd)
protections += textwrap.dedent("""
<Directory %s>
AllowOverride All
#AuthPAM_Enabled off
AuthType Basic
AuthName %s
AuthUserFile %s
<Limit GET POST>
require valid-user
</Limit>
</Directory>""" % (path, name, passwd)
)
return protections
# def get_protections(self, site):
# protections = ''
# context = self.get_context(site)
# for protection in site.directives.filter(name='directory_protection'):
# path, name, passwd = protection.value.split()
# path = os.path.join(context['root'], path)
# passwd = os.path.join(self.USER_HOME % context, passwd)
# protections += textwrap.dedent("""
# <Directory %s>
# AllowOverride All
# #AuthPAM_Enabled off
# AuthType Basic
# AuthName %s
# AuthUserFile %s
# <Limit GET POST>
# require valid-user
# </Limit>
# </Directory>""" % (path, name, passwd)
# )
# return protections
def enable_or_disable(self, site):
context = self.get_context(site)
@ -184,27 +180,25 @@ class Apache2Backend(ServiceController):
self.append(textwrap.dedent("""\
if [[ ! -f %(sites_enabled)s ]]; then
a2ensite %(site_unique_name)s.conf
else
UPDATED=0
fi""" % context
))
UPDATED=1
fi""") % context
)
else:
self.append(textwrap.dedent("""\
if [[ -f %(sites_enabled)s ]]; then
a2dissite %(site_unique_name)s.conf;
else
UPDATED=0
fi""" % context
))
UPDATED=1
fi""") % context
)
def get_username(self, site):
option = site.options.filter(name='user_group').first()
option = site.directives.filter(name='user_group').first()
if option:
return option.value.split()[0]
return site.account.username
def get_groupname(self, site):
option = site.options.filter(name='user_group').first()
option = site.directives.filter(name='user_group').first()
if option and ' ' in option.value:
user, group = option.value.split()
return group
@ -236,7 +230,6 @@ class Apache2Backend(ServiceController):
'location': content.path,
'app_name': content.webapp.name,
'app_path': content.webapp.get_path(),
'fpm_port': content.webapp.get_fpm_port(),
})
return context
@ -292,7 +285,7 @@ class Apache2Traffic(ServiceMonitor):
print sum
}' || [[ $? == 1 ]] && true
} | xargs echo ${OBJECT_ID}
}""" % context))
}""") % context)
def monitor(self, site):
context = self.get_context(site)

View File

@ -20,18 +20,17 @@ class WebalizerBackend(ServiceController):
echo 'Webstats are coming soon' > %(webalizer_path)s/index.html
fi
echo '%(webalizer_conf)s' > %(webalizer_conf_path)s
chown %(user)s:www-data %(webalizer_path)s""" % context
))
chown %(user)s:www-data %(webalizer_path)s""") % context
)
def delete(self, content):
context = self.get_context(content)
delete_webapp = not content.webapp.pk
# TODO remove when confirmed that it works, otherwise create a second WebalizerBackend for WebApps
delete_webapp = type(content.webapp).objects.filter(pk=content.webapp.pk).exists()
if delete_webapp:
self.append("mv %(webapp_path)s %(webapp_path)s.deleted" % context)
if delete_webapp or not content.webapp.contents.filter(website=content.website).exists():
self.append("mv %(webalizer_path)s %(webalizer_path)s.deleted" % context)
self.append("rm %(webalizer_conf_path)s" % context)
if delete_webapp or not content.webapp.content_set.filter(website=content.website).exists():
self.append("rm -fr %(webalizer_path)s" % context)
self.append("rm -f %(webalizer_conf_path)s" % context)
def get_context(self, content):
conf_file = "%s.conf" % content.website.unique_name
@ -88,6 +87,5 @@ class WebalizerBackend(ServiceController):
SearchEngine mamma.com query=
SearchEngine alltheweb.com query=
DumpSites yes""" % context
)
DumpSites yes""") % context
return context

View File

@ -10,9 +10,9 @@ from . import settings
# TODO multiple and unique validation support in the formset
class SiteDirective(Plugin):
HTTPD = 'httpd'
SEC = 'sec'
SSL = 'ssl'
HTTPD = 'HTTPD'
SEC = 'ModSecurity'
SSL = 'SSL'
help_text = ""
unique = True

View File

@ -66,7 +66,8 @@ class Website(models.Model):
return {
'home': self.account.main_systemuser.get_home(),
'account': self.account.username,
'name': self.name,
'user': self.account.username,
'site_name': self.name,
'unique_name': self.unique_name
}
@ -105,10 +106,9 @@ class Directive(models.Model):
class Content(models.Model):
webapp = models.ForeignKey('webapps.WebApp', verbose_name=_("web application"),
related_name='contents')
website = models.ForeignKey('websites.Website', verbose_name=_("web site"),
related_name='contents')
# related_name is content_set to differentiate between website.content -> webapp
webapp = models.ForeignKey('webapps.WebApp', verbose_name=_("web application"))
website = models.ForeignKey('websites.Website', verbose_name=_("web site"))
path = models.CharField(_("path"), max_length=256, blank=True,
validators=[validators.validate_url_path])
@ -124,6 +124,8 @@ class Content(models.Model):
def clean(self):
if not self.path.startswith('/'):
self.path = '/' + self.path
if not self.path.endswith('/'):
self.path = self.path + '/'
def get_absolute_url(self):
domain = self.website.domains.first()

View File

@ -43,7 +43,7 @@ class ContentSerializer(serializers.HyperlinkedModelSerializer):
class WebsiteSerializer(AccountSerializerMixin, HyperlinkedModelSerializer):
domains = RelatedDomainSerializer(many=True, allow_add_remove=True, required=False)
contents = ContentSerializer(required=False, many=True, allow_add_remove=True,
source='contents')
source='content_set')
options = OptionField(required=False)
class Meta:

View File

@ -36,7 +36,9 @@ MEDIA_URL = '/media/'
ALLOWED_HOSTS = '*'
# Set this to True to wrap each HTTP request in a transaction on this database.
ATOMIC_REQUESTS = True
# ATOMIC REQUESTS do not wrap middlewares (orchestra.apps.orchestration.middlewares.OperationsMiddleware)
ATOMIC_REQUESTS = False
MIDDLEWARE_CLASSES = (
'django.middleware.gzip.GZipMiddleware',
@ -46,9 +48,12 @@ MIDDLEWARE_CLASSES = (
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'orchestra.core.caches.RequestCacheMiddleware',
# ATOMIC REQUESTS do not wrap middlewares
'orchestra.core.middlewares.TransactionMiddleware',
'orchestra.apps.orchestration.middlewares.OperationsMiddleware',
# Uncomment the next line for simple clickjacking protection:
# 'django.middleware.clickjacking.XFrameOptionsMiddleware',
)

View File

@ -0,0 +1,47 @@
from django.db import connection, transaction
class TransactionMiddleware(object):
"""
Transaction middleware. If this is enabled, each view function will be run
with commit_on_response activated - that way a save() doesn't do a direct
commit, the commit is done when a successful response is created. If an
exception happens, the database is rolled back.
"""
def process_request(self, request):
"""Enters transaction management"""
transaction.enter_transaction_management()
def process_exception(self, request, exception):
"""Rolls back the database and leaves transaction management"""
if transaction.is_dirty():
# This rollback might fail because of network failure for example.
# If rollback isn't possible it is impossible to clean the
# connection's state. So leave the connection in dirty state and
# let request_finished signal deal with cleaning the connection.
transaction.rollback()
transaction.leave_transaction_management()
def process_response(self, request, response):
"""Commits and leaves transaction management."""
if not transaction.get_autocommit():
if transaction.is_dirty():
# Note: it is possible that the commit fails. If the reason is
# closed connection or some similar reason, then there is
# little hope to proceed nicely. However, in some cases (
# deferred foreign key checks for exampl) it is still possible
# to rollback().
try:
transaction.commit()
except Exception:
# If the rollback fails, the transaction state will be
# messed up. It doesn't matter, the connection will be set
# to clean state after the request finishes. And, we can't
# clean the state here properly even if we wanted to, the
# connection is in transaction but we can't rollback...
transaction.rollback()
transaction.leave_transaction_management()
raise
transaction.leave_transaction_management()
return response