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() * webalizer backend on webapps and check webapps.websites.all()
* monitor in batches doesnt work!!! * 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("""\ self.append(textwrap.dedent("""\
mysql -e 'GRANT ALL PRIVILEGES ON `%(database)s`.* TO "%(username)s"@"%(host)s" %(grant)s;' \ mysql -e 'GRANT ALL PRIVILEGES ON `%(database)s`.* TO "%(username)s"@"%(host)s" %(grant)s;' \
""" % context """) % context
)) )
def delete(self, database): def delete(self, database):
if database.type != database.MYSQL: if database.type != database.MYSQL:
@ -62,12 +62,12 @@ class MySQLUserBackend(ServiceController):
context = self.get_context(user) context = self.get_context(user)
self.append(textwrap.dedent("""\ self.append(textwrap.dedent("""\
mysql -e 'CREATE USER "%(username)s"@"%(host)s";' || true \ mysql -e 'CREATE USER "%(username)s"@"%(host)s";' || true \
""" % context """) % context
)) )
self.append(textwrap.dedent("""\ self.append(textwrap.dedent("""\
mysql -e 'UPDATE mysql.user SET Password="%(password)s" WHERE User="%(username)s";' \ mysql -e 'UPDATE mysql.user SET Password="%(password)s" WHERE User="%(username)s";' \
""" % context """) % context
)) )
def delete(self, user): def delete(self, user):
if user.type != user.MYSQL: if user.type != user.MYSQL:
@ -75,8 +75,8 @@ class MySQLUserBackend(ServiceController):
context = self.get_context(user) context = self.get_context(user)
self.append(textwrap.dedent("""\ self.append(textwrap.dedent("""\
mysql -e 'DROP USER "%(username)s"@"%(host)s";' \ mysql -e 'DROP USER "%(username)s"@"%(host)s";' \
""" % context """) % context
)) )
def commit(self): def commit(self):
self.append("mysql -e 'FLUSH PRIVILEGES;'") self.append("mysql -e 'FLUSH PRIVILEGES;'")
@ -99,8 +99,8 @@ class MysqlDisk(ServiceMonitor):
context = self.get_context(db) context = self.get_context(db)
self.append(textwrap.dedent("""\ self.append(textwrap.dedent("""\
mysql -e 'UPDATE db SET Insert_priv="N", Create_priv="N" WHERE Db="%(db_name)s";'\ mysql -e 'UPDATE db SET Insert_priv="N", Create_priv="N" WHERE Db="%(db_name)s";'\
""" % context """) % context
)) )
def recovery(self, db): def recovery(self, db):
if db.type != db.MYSQL: if db.type != db.MYSQL:
@ -108,8 +108,8 @@ class MysqlDisk(ServiceMonitor):
context = self.get_context(db) context = self.get_context(db)
self.append(textwrap.dedent("""\ self.append(textwrap.dedent("""\
mysql -e 'UPDATE db SET Insert_priv="Y", Create_priv="Y" WHERE Db="%(db_name)s";'\ mysql -e 'UPDATE db SET Insert_priv="Y", Create_priv="Y" WHERE Db="%(db_name)s";'\
""" % context """) % context
)) )
def prepare(self): def prepare(self):
super(MysqlDisk, self).prepare() super(MysqlDisk, self).prepare()

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,15 +1,22 @@
import pkgutil import pkgutil
import textwrap import textwrap
from .. import settings
class WebAppServiceMixin(object): class WebAppServiceMixin(object):
model = 'webapps.WebApp' model = 'webapps.WebApp'
directive = None directive = None
def create_webapp_dir(self, context): 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("mkdir -p %(app_path)s" % context)
self.append("chown %(user)s:%(group)s %(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): def delete_webapp_dir(self, context):
self.append("rm -fr %(app_path)s" % context) self.append("rm -fr %(app_path)s" % context)
@ -21,6 +28,7 @@ class WebAppServiceMixin(object):
'type': webapp.type, 'type': webapp.type,
'app_path': webapp.get_path().rstrip('/'), 'app_path': webapp.get_path().rstrip('/'),
'banner': self.get_banner(), '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): class PHPFcgidBackend(WebAppServiceMixin, ServiceController):
""" Per-webapp fcgid application """ """ Per-webapp fcgid application """
verbose_name = _("PHP-Fcgid") verbose_name = _("PHP-Fcgid")
directive = 'fcgi' directive = 'fcgid'
default_route_match = "webapp.type.endswith('-fcgi')" default_route_match = "webapp.type.endswith('-fcgid')"
def save(self, webapp): def save(self, webapp):
if not self.valid_directive(webapp):
return
context = self.get_context(webapp) context = self.get_context(webapp)
self.create_webapp_dir(context) self.create_webapp_dir(context)
self.set_under_construction(context)
self.append("mkdir -p %(wrapper_dir)s" % context) self.append("mkdir -p %(wrapper_dir)s" % context)
self.append(textwrap.dedent("""\ 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 echo -e '%(wrapper)s' > %(wrapper_path)s; UPDATED_APACHE=1
}""" % context)) }""") % context
)
self.append("chmod +x %(wrapper_path)s" % context) self.append("chmod +x %(wrapper_path)s" % context)
self.append("chown -R %(user)s:%(group)s %(wrapper_dir)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): def delete(self, webapp):
if not self.valid_directive(webapp):
return
context = self.get_context(webapp) context = self.get_context(webapp)
self.append("rm '%(wrapper_path)s'" % context) self.append("rm '%(wrapper_path)s'" % context)
self.delete_webapp_dir(context) self.delete_webapp_dir(context)
def commit(self): def commit(self):
if not self.cmds: self.append('if [[ $UPDATED_APACHE == 1 ]]; then service apache2 reload; fi')
return
super(PHPFcgidBackend, self).commit() def get_fcgid_wrapper(self, webapp, context):
self.append("[[ $UPDATED_APACHE == 1 ]] && { service apache2 reload; }") 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): def get_context(self, webapp):
context = super(PHPFcgidBackend, self).get_context(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 wrapper_path = settings.WEBAPPS_FCGID_PATH % context
context.update({ context.update({
'wrapper_content': textwrap.dedent("""\ 'wrapper': self.get_fcgid_wrapper(webapp, context),
#!/bin/sh
# %(banner)s
export PHPRC=/etc/%(type)s/cgi/
exec /usr/bin/%(type)s-cgi %(init_vars)s""" % context),
'wrapper_path': wrapper_path, 'wrapper_path': wrapper_path,
'wrapper_dir': os.path.dirname(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 return context

View File

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

View File

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

View File

@ -30,8 +30,8 @@ class WordPressBackend(WebAppServiceMixin, ServiceController):
mkdir %(app_path)s/wp-content/uploads mkdir %(app_path)s/wp-content/uploads
chmod 750 %(app_path)s/wp-content/uploads chmod 750 %(app_path)s/wp-content/uploads
chown -R %(user)s:%(group)s %(app_path)s chown -R %(user)s:%(group)s %(app_path)s
fi""" % context fi""") % context
)) )
def delete(self, webapp): def delete(self, webapp):
context = self.get_context(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 orchestra.utils.functional import cached
from . import settings from . import settings
from .options import AppOption
from .types import AppType from .types import AppType
@ -55,9 +56,6 @@ class WebApp(models.Model):
opt.name: opt.value for opt in self.options.all() 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): def get_directive(self):
return self.type_instance.get_directive(self) return self.type_instance.get_directive(self)
@ -67,6 +65,9 @@ class WebApp(models.Model):
'app_name': self.name, 'app_name': self.name,
} }
path = settings.WEBAPPS_BASE_ROOT % context 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('//', '/') return path.replace('//', '/')
def get_user(self): def get_user(self):
@ -95,7 +96,7 @@ class WebAppOption(models.Model):
@cached_property @cached_property
def option_class(self): def option_class(self):
return SiteDirective.get_plugin(self.name) return AppOption.get_plugin(self.name)
@cached_property @cached_property
def option_instance(self): def option_instance(self):

View File

@ -1,3 +1,5 @@
import re
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
@ -54,15 +56,6 @@ class PublicRoot(AppOption):
group = AppOption.FILESYSTEM 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): class Timeout(AppOption):
name = 'timeout' name = 'timeout'
# FCGID FcgidIOTimeout # FCGID FcgidIOTimeout
@ -78,273 +71,273 @@ class Processes(AppOption):
name = 'processes' name = 'processes'
# FCGID MaxProcesses # FCGID MaxProcesses
# FPM pm.max_children # FPM pm.max_children
verbose_name=_("Number of processes") verbose_name = _("Number of processes")
help_text=_("Maximum number of children that can be alive at the same time (a number between 0 and 9).") help_text = _("Maximum number of children that can be alive at the same time (a number between 0 and 9).")
regex=r'^[0-9]$' regex = r'^[0-9]$'
group = AppOption.PROCESS group = AppOption.PROCESS
class PHPEnabledFunctions(AppOption): class PHPEnabledFunctions(AppOption):
name = 'enabled_functions' name = 'enabled_functions'
verbose_name=_("Enabled functions") verbose_name = _("Enabled functions")
help_text = ' '.join(settings.WEBAPPS_PHP_DISABLED_FUNCTIONS) help_text = ' '.join(settings.WEBAPPS_PHP_DISABLED_FUNCTIONS)
regex=r'^[\w\.,-]+$' regex = r'^[\w\.,-]+$'
group = AppOption.PHP group = AppOption.PHP
class PHPAllowURLInclude(AppOption): class PHPAllowURLInclude(AppOption):
name = 'allow_url_include' name = 'allow_url_include'
verbose_name=_("Allow URL include") verbose_name = _("Allow URL include")
help_text=_("Allows the use of URL-aware fopen wrappers with include, include_once, require, " help_text = _("Allows the use of URL-aware fopen wrappers with include, include_once, require, "
"require_once (On or Off).") "require_once (On or Off).")
regex=r'^(On|Off|on|off)$' regex = r'^(On|Off|on|off)$'
group = AppOption.PHP group = AppOption.PHP
class PHPAllowURLFopen(AppOption): class PHPAllowURLFopen(AppOption):
name = 'allow_url_fopen' name = 'allow_url_fopen'
verbose_name=_("Allow URL fopen") verbose_name = _("Allow URL fopen")
help_text=_("Enables the URL-aware fopen wrappers that enable accessing URL object like files (On or Off).") help_text = _("Enables the URL-aware fopen wrappers that enable accessing URL object like files (On or Off).")
regex=r'^(On|Off|on|off)$' regex = r'^(On|Off|on|off)$'
group = AppOption.PHP group = AppOption.PHP
class PHPAutoAppendFile(AppOption): class PHPAutoAppendFile(AppOption):
name = 'auto_append_file' name = 'auto_append_file'
verbose_name=_("Auto append file") verbose_name = _("Auto append file")
help_text=_("Specifies the name of a file that is automatically parsed after the main file.") help_text = _("Specifies the name of a file that is automatically parsed after the main file.")
regex=r'^[\w\.,-/]+$' regex = r'^[\w\.,-/]+$'
group = AppOption.PHP group = AppOption.PHP
class PHPAutoPrependFile(AppOption): class PHPAutoPrependFile(AppOption):
name = 'auto_prepend_file' name = 'auto_prepend_file'
verbose_name=_("Auto prepend file") verbose_name = _("Auto prepend file")
help_text=_("Specifies the name of a file that is automatically parsed before the main file.") help_text = _("Specifies the name of a file that is automatically parsed before the main file.")
regex=r'^[\w\.,-/]+$' regex = r'^[\w\.,-/]+$'
group = AppOption.PHP group = AppOption.PHP
class PHPDateTimeZone(AppOption): class PHPDateTimeZone(AppOption):
name = 'date.timezone' name = 'date.timezone'
verbose_name=_("date.timezone") verbose_name = _("date.timezone")
help_text=_("Sets the default timezone used by all date/time functions (Timezone string 'Europe/London').") help_text = _("Sets the default timezone used by all date/time functions (Timezone string 'Europe/London').")
regex=r'^\w+/\w+$' regex = r'^\w+/\w+$'
group = AppOption.PHP group = AppOption.PHP
class PHPDefaultSocketTimeout(AppOption): class PHPDefaultSocketTimeout(AppOption):
name = 'default_socket_timeout' name = 'default_socket_timeout'
verbose_name=_("Default socket timeout") verbose_name = _("Default socket timeout")
help_text=_("Number between 0 and 999.") help_text = _("Number between 0 and 999.")
regex=r'^[0-9]{1,3}$' regex = r'^[0-9]{1,3}$'
group = AppOption.PHP group = AppOption.PHP
class PHPDisplayErrors(AppOption): class PHPDisplayErrors(AppOption):
name = 'display_errors' name = 'display_errors'
verbose_name=_("Display errors") verbose_name = _("Display errors")
help_text=_("Determines whether errors should be printed to the screen as part of the output or " help_text = _("Determines whether errors should be printed to the screen as part of the output or "
"if they should be hidden from the user (On or Off).") "if they should be hidden from the user (On or Off).")
regex=r'^(On|Off|on|off)$' regex = r'^(On|Off|on|off)$'
group = AppOption.PHP group = AppOption.PHP
class PHPExtension(AppOption): class PHPExtension(AppOption):
name = 'extension' name = 'extension'
verbose_name=_("Extension") verbose_name = _("Extension")
regex=r'^[^ ]+$' regex = r'^[^ ]+$'
group = AppOption.PHP group = AppOption.PHP
class PHPMagicQuotesGPC(AppOption): class PHPMagicQuotesGPC(AppOption):
name = 'magic_quotes_gpc' name = 'magic_quotes_gpc'
verbose_name=_("Magic quotes GPC") verbose_name = _("Magic quotes GPC")
help_text=_("Sets the magic_quotes state for GPC (Get/Post/Cookie) operations (On or Off) " help_text = _("Sets the magic_quotes state for GPC (Get/Post/Cookie) operations (On or Off) "
"<b>DEPRECATED as of PHP 5.3.0</b>.") "<b>DEPRECATED as of PHP 5.3.0</b>.")
regex=r'^(On|Off|on|off)$' regex = r'^(On|Off|on|off)$'
deprecated=5.3 deprecated=5.3
group = AppOption.PHP group = AppOption.PHP
class PHPMagicQuotesRuntime(AppOption): class PHPMagicQuotesRuntime(AppOption):
name = 'magic_quotes_runtime' name = 'magic_quotes_runtime'
verbose_name=_("Magic quotes runtime") verbose_name = _("Magic quotes runtime")
help_text=_("Functions that return data from any sort of external source will have quotes escaped " help_text = _("Functions that return data from any sort of external source will have quotes escaped "
"with a backslash (On or Off) <b>DEPRECATED as of PHP 5.3.0</b>.") "with a backslash (On or Off) <b>DEPRECATED as of PHP 5.3.0</b>.")
regex=r'^(On|Off|on|off)$' regex = r'^(On|Off|on|off)$'
deprecated=5.3 deprecated=5.3
group = AppOption.PHP group = AppOption.PHP
class PHPMaginQuotesSybase(AppOption): class PHPMaginQuotesSybase(AppOption):
name = 'magic_quotes_sybase' name = 'magic_quotes_sybase'
verbose_name=_("Magic quotes sybase") verbose_name = _("Magic quotes sybase")
help_text=_("Single-quote is escaped with a single-quote instead of a backslash (On or Off).") help_text = _("Single-quote is escaped with a single-quote instead of a backslash (On or Off).")
regex=r'^(On|Off|on|off)$' regex = r'^(On|Off|on|off)$'
group = AppOption.PHP group = AppOption.PHP
class PHPMaxExecutonTime(AppOption): class PHPMaxExecutonTime(AppOption):
name = 'max_execution_time' name = 'max_execution_time'
verbose_name=_("Max execution time") verbose_name = _("Max execution time")
help_text=_("Maximum time in seconds a script is allowed to run before it is terminated by " help_text = _("Maximum time in seconds a script is allowed to run before it is terminated by "
"the parser (Integer between 0 and 999).") "the parser (Integer between 0 and 999).")
regex=r'^[0-9]{1,3}$' regex = r'^[0-9]{1,3}$'
group = AppOption.PHP group = AppOption.PHP
class PHPMaxInputTime(AppOption): class PHPMaxInputTime(AppOption):
name = 'max_input_time' name = 'max_input_time'
verbose_name=_("Max input time") verbose_name = _("Max input time")
help_text=_("Maximum time in seconds a script is allowed to parse input data, like POST and GET " help_text = _("Maximum time in seconds a script is allowed to parse input data, like POST and GET "
"(Integer between 0 and 999).") "(Integer between 0 and 999).")
regex=r'^[0-9]{1,3}$' regex = r'^[0-9]{1,3}$'
group = AppOption.PHP group = AppOption.PHP
class PHPMaxInputVars(AppOption): class PHPMaxInputVars(AppOption):
name = 'max_input_vars' name = 'max_input_vars'
verbose_name=_("Max input vars") verbose_name = _("Max input vars")
help_text=_("How many input variables may be accepted (limit is applied to $_GET, $_POST " help_text = _("How many input variables may be accepted (limit is applied to $_GET, $_POST "
"and $_COOKIE superglobal separately) (Integer between 0 and 9999).") "and $_COOKIE superglobal separately) (Integer between 0 and 9999).")
regex=r'^[0-9]{1,4}$' regex = r'^[0-9]{1,4}$'
group = AppOption.PHP group = AppOption.PHP
class PHPMemoryLimit(AppOption): class PHPMemoryLimit(AppOption):
name = 'memory_limit' name = 'memory_limit'
verbose_name=_("Memory limit") verbose_name = _("Memory limit")
help_text=_("This sets the maximum amount of memory in bytes that a script is allowed to allocate " help_text = _("This sets the maximum amount of memory in bytes that a script is allowed to allocate "
"(Value between 0M and 999M).") "(Value between 0M and 999M).")
regex=r'^[0-9]{1,3}M$' regex = r'^[0-9]{1,3}M$'
group = AppOption.PHP group = AppOption.PHP
class PHPMySQLConnectTimeout(AppOption): class PHPMySQLConnectTimeout(AppOption):
name = 'mysql.connect_timeout' name = 'mysql.connect_timeout'
verbose_name=_("Mysql connect timeout") verbose_name = _("Mysql connect timeout")
help_text=_("Number between 0 and 999.") help_text = _("Number between 0 and 999.")
regex=r'^([0-9]){1,3}$' regex = r'^([0-9]){1,3}$'
group = AppOption.PHP group = AppOption.PHP
class PHPOutputBuffering(AppOption): class PHPOutputBuffering(AppOption):
name = 'output_buffering' name = 'output_buffering'
verbose_name=_("Output buffering") verbose_name = _("Output buffering")
help_text=_("Turn on output buffering (On or Off).") help_text = _("Turn on output buffering (On or Off).")
regex=r'^(On|Off|on|off)$' regex = r'^(On|Off|on|off)$'
group = AppOption.PHP group = AppOption.PHP
class PHPRegisterGlobals(AppOption): class PHPRegisterGlobals(AppOption):
name = 'register_globals' name = 'register_globals'
verbose_name=_("Register globals") verbose_name = _("Register globals")
help_text=_("Whether or not to register the EGPCS (Environment, GET, POST, Cookie, Server) " help_text = _("Whether or not to register the EGPCS (Environment, GET, POST, Cookie, Server) "
"variables as global variables (On or Off).") "variables as global variables (On or Off).")
regex=r'^(On|Off|on|off)$' regex = r'^(On|Off|on|off)$'
group = AppOption.PHP group = AppOption.PHP
class PHPPostMaxSize(AppOption): class PHPPostMaxSize(AppOption):
name = 'post_max_size' name = 'post_max_size'
verbose_name=_("Post max size") verbose_name = _("Post max size")
help_text=_("Sets max size of post data allowed (Value between 0M and 999M).") help_text = _("Sets max size of post data allowed (Value between 0M and 999M).")
regex=r'^[0-9]{1,3}M$' regex = r'^[0-9]{1,3}M$'
group = AppOption.PHP group = AppOption.PHP
class PHPSendmailPath(AppOption): class PHPSendmailPath(AppOption):
name = 'sendmail_path' name = 'sendmail_path'
verbose_name=_("sendmail_path") verbose_name = _("sendmail_path")
help_text=_("Where the sendmail program can be found.") help_text = _("Where the sendmail program can be found.")
regex=r'^[^ ]+$' regex = r'^[^ ]+$'
group = AppOption.PHP group = AppOption.PHP
class PHPSessionBugCompatWarn(AppOption): class PHPSessionBugCompatWarn(AppOption):
name = 'session.bug_compat_warn' name = 'session.bug_compat_warn'
verbose_name=_("session.bug_compat_warn") verbose_name = _("session.bug_compat_warn")
help_text=_("Enables an PHP bug on session initialization for legacy behaviour (On or Off).") help_text = _("Enables an PHP bug on session initialization for legacy behaviour (On or Off).")
regex=r'^(On|Off|on|off)$' regex = r'^(On|Off|on|off)$'
group = AppOption.PHP group = AppOption.PHP
class PHPSessionAutoStart(AppOption): class PHPSessionAutoStart(AppOption):
name = 'session.auto_start', name = 'session.auto_start'
verbose_name=_("session.auto_start") verbose_name = _("session.auto_start")
help_text=_("Specifies whether the session module starts a session automatically on request " help_text = _("Specifies whether the session module starts a session automatically on request "
"startup (On or Off).") "startup (On or Off).")
regex=r'^(On|Off|on|off)$' regex = r'^(On|Off|on|off)$'
group = AppOption.PHP group = AppOption.PHP
class PHPSafeMode(AppOption): class PHPSafeMode(AppOption):
name = 'safe_mode' name = 'safe_mode'
verbose_name=_("Safe mode") verbose_name = _("Safe mode")
help_text=_("Whether to enable PHP's safe mode (On or Off) <b>DEPRECATED as of PHP 5.3.0</b>") help_text = _("Whether to enable PHP's safe mode (On or Off) <b>DEPRECATED as of PHP 5.3.0</b>")
regex=r'^(On|Off|on|off)$' regex = r'^(On|Off|on|off)$'
deprecated=5.3 deprecated=5.3
group = AppOption.PHP group = AppOption.PHP
class PHPSuhosinPostMaxVars(AppOption): class PHPSuhosinPostMaxVars(AppOption):
name = 'suhosin.post.max_vars', name = 'suhosin.post.max_vars'
verbose_name=_("Suhosin POST max vars") verbose_name = _("Suhosin POST max vars")
help_text=_("Number between 0 and 9999.") help_text = _("Number between 0 and 9999.")
regex=r'^[0-9]{1,4}$' regex = r'^[0-9]{1,4}$'
group = AppOption.PHP group = AppOption.PHP
class PHPSuhosinGetMaxVars(AppOption): class PHPSuhosinGetMaxVars(AppOption):
name = 'suhosin.get.max_vars' name = 'suhosin.get.max_vars'
verbose_name=_("Suhosin GET max vars") verbose_name = _("Suhosin GET max vars")
help_text=_("Number between 0 and 9999.") help_text = _("Number between 0 and 9999.")
regex=r'^[0-9]{1,4}$' regex = r'^[0-9]{1,4}$'
group = AppOption.PHP group = AppOption.PHP
class PHPSuhosinRequestMaxVars(AppOption): class PHPSuhosinRequestMaxVars(AppOption):
name = 'suhosin.request.max_vars' name = 'suhosin.request.max_vars'
verbose_name=_("Suhosin request max vars") verbose_name = _("Suhosin request max vars")
help_text=_("Number between 0 and 9999.") help_text = _("Number between 0 and 9999.")
regex=r'^[0-9]{1,4}$' regex = r'^[0-9]{1,4}$'
group = AppOption.PHP group = AppOption.PHP
class PHPSuhosinSessionEncrypt(AppOption): class PHPSuhosinSessionEncrypt(AppOption):
name = 'suhosin.session.encrypt' name = 'suhosin.session.encrypt'
verbose_name=_("suhosin.session.encrypt") verbose_name = _("suhosin.session.encrypt")
help_text=_("On or Off") help_text = _("On or Off")
regex=r'^(On|Off|on|off)$' regex = r'^(On|Off|on|off)$'
group = AppOption.PHP group = AppOption.PHP
class PHPSuhosinSimulation(AppOption): class PHPSuhosinSimulation(AppOption):
name = 'suhosin.simulation' name = 'suhosin.simulation'
verbose_name=_("Suhosin simulation") verbose_name = _("Suhosin simulation")
help_text=_("On or Off") help_text = _("On or Off")
regex=r'^(On|Off|on|off)$' regex = r'^(On|Off|on|off)$'
group = AppOption.PHP group = AppOption.PHP
class PHPSuhosinExecutorIncludeWhitelist(AppOption): class PHPSuhosinExecutorIncludeWhitelist(AppOption):
name = 'suhosin.executor.include.whitelist' name = 'suhosin.executor.include.whitelist'
verbose_name=_("suhosin.executor.include.whitelist") verbose_name = _("suhosin.executor.include.whitelist")
regex=r'.*$' regex = r'.*$'
group = AppOption.PHP group = AppOption.PHP
class PHPUploadMaxFileSize(AppOption): class PHPUploadMaxFileSize(AppOption):
name = 'upload_max_filesize', name = 'upload_max_filesize'
verbose_name=_("upload_max_filesize") verbose_name = _("upload_max_filesize")
help_text=_("Value between 0M and 999M.") help_text = _("Value between 0M and 999M.")
regex=r'^[0-9]{1,3}M$' regex = r'^[0-9]{1,3}M$'
group = AppOption.PHP group = AppOption.PHP
class PHPPostMaxSize(AppOption): class PHPPostMaxSize(AppOption):
name = 'post_max_size' name = 'post_max_size'
verbose_name=_("zend_extension") verbose_name = _("zend_extension")
regex=r'^[^ ]+$' regex = r'^[^ ]+$'
group = AppOption.PHP group = AppOption.PHP

View File

@ -2,28 +2,31 @@ from django.conf import settings
from django.utils.translation import ugettext_lazy as _ 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', WEBAPPS_FPM_LISTEN = getattr(settings, 'WEBAPPS_FPM_LISTEN',
# '127.0.0.1:9{app_id:03d} # '127.0.0.1:9%(app_id)03d
'/opt/php/5.4/socks/{user}-{app_name}.sock' '/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', 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', 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', ( WEBAPPS_TYPES = getattr(settings, 'WEBAPPS_TYPES', (
'orchestra.apps.webapps.types.PHP54App', 'orchestra.apps.webapps.types.PHP54App',
'orchestra.apps.webapps.types.PHP53App',
'orchestra.apps.webapps.types.PHP52App', 'orchestra.apps.webapps.types.PHP52App',
'orchestra.apps.webapps.types.PHP4App', 'orchestra.apps.webapps.types.PHP4App',
'orchestra.apps.webapps.types.StaticApp', 'orchestra.apps.webapps.types.StaticApp',
@ -35,7 +38,10 @@ WEBAPPS_TYPES = getattr(settings, 'WEBAPPS_TYPES', (
'orchestra.apps.webapps.types.WordPressApp', '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', {}) #WEBAPPS_TYPES_OVERRIDE = getattr(settings, 'WEBAPPS_TYPES_OVERRIDE', {})
#for webapp_type, value in WEBAPPS_TYPES_OVERRIDE.iteritems(): #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', ( WEBAPPS_ENABLED_OPTIONS = getattr(settings, 'WEBAPPS_ENABLED_OPTIONS', (
'orchestra.apps.webapps.options.PublicRoot', 'orchestra.apps.webapps.options.PublicRoot',
'orchestra.apps.webapps.options.DirectoryProtection',
'orchestra.apps.webapps.options.Timeout', 'orchestra.apps.webapps.options.Timeout',
'orchestra.apps.webapps.options.Processes', 'orchestra.apps.webapps.options.Processes',
'orchestra.apps.webapps.options.PHPEnabledFunctions', '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', 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', WEBAPPS_DRUPALMU_LISTEN = getattr(settings, 'WEBAPPS_DRUPALMU_LISTEN',
'/opt/php/5.4/socks/drupal-mu.sock' '/opt/php/5.4/socks/drupal-mu.sock'

View File

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

View File

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

View File

@ -44,7 +44,7 @@ class Apache2Backend(ServiceController):
</VirtualHost>""" </VirtualHost>"""
)) ))
apache_conf = apache_conf.render(Context(context)) apache_conf = apache_conf.render(Context(context))
apache_conf += self.get_protections(site) # apache_conf += self.get_protections(site)
context['apache_conf'] = apache_conf context['apache_conf'] = apache_conf
self.append(textwrap.dedent("""\ self.append(textwrap.dedent("""\
@ -64,21 +64,21 @@ class Apache2Backend(ServiceController):
def commit(self): def commit(self):
""" reload Apache2 if necessary """ """ 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): def get_content_directives(self, site):
directives = '' 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() directive = content.webapp.get_directive()
method, agrs = directive[0], directive[1:] method, args = directive[0], directive[1:]
method = getattr(self, 'get_%s_directives' % method) method = getattr(self, 'get_%s_directives' % method)
directives += method(content, *args) directives += method(content, *args)
return directives return directives
def get_static_directives(self, content, app_path): def get_static_directives(self, content, app_path):
context = self.get_content_context(content) context = self.get_content_context(content)
context['app_path'] = app_path context['app_path'] = app_path % context
return "Alias %(location)s %(path)s\n" % context return "Alias %(location)s %(app_path)s\n" % context
def get_fpm_directives(self, content, socket_type, socket, app_path): def get_fpm_directives(self, content, socket_type, socket, app_path):
if socket_type == 'unix': if socket_type == 'unix':
@ -95,29 +95,26 @@ class Apache2Backend(ServiceController):
'socket': socket, 'socket': socket,
}) })
return textwrap.dedent("""\ return textwrap.dedent("""\
ProxyPassMatch ^%(location)s/(.*\.php(/.*)?)$ {target} ProxyPassMatch ^%(location)s(.*\.php(/.*)?)$ {target}
Alias %(location)s/ %(app_path)s/ Alias %(location)s %(app_path)s/
""".format(target=target) % context """.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 = self.get_content_context(content)
context.update({ context.update({
'app_path': app_path, 'app_path': app_path,
'wrapper_path': wrapper_path, 'wrapper_path': wrapper_path,
}) })
fcgid = textwrap.dedent("""\ return textwrap.dedent("""\
Alias %(location)s %(app_path)s Alias %(location)s %(app_path)s
ProxyPass %(location)s ! ProxyPass %(location)s !
<Directory %(app_path)s> <Directory %(app_path)s>
Options +ExecCGI Options +ExecCGI
AddHandler fcgid-script .php AddHandler fcgid-script .php
FcgidWrapper %(wrapper_path)s\ FcgidWrapper %(wrapper_path)s
</Directory>
""") % context """) % 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): def get_ssl(self, site):
cert = settings.WEBSITES_DEFAULT_HTTPS_CERT cert = settings.WEBSITES_DEFAULT_HTTPS_CERT
@ -129,54 +126,53 @@ class Apache2Backend(ServiceController):
SSLEngine on SSLEngine on
SSLCertificateFile %s SSLCertificateFile %s
SSLCertificateKeyFile %s\ SSLCertificateKeyFile %s\
""" % cert """) % cert
)
return directives return directives
def get_security(self, site): def get_security(self, site):
directives = '' 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(): for rule in rules.value.split():
directives += "SecRuleRemoveById %i\n" % int(rule) 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("""\ directives += textwrap.dedent("""\
<LocationMatch %s> <LocationMatch %s>
SecRuleEngine Off SecRuleEngine Off
</LocationMatch>\ </LocationMatch>\
""" % modsecurity.value) """) % modsecurity.value
if directives: if directives:
directives = '<IfModule mod_security2.c>\n%s\n</IfModule>' % directives directives = '<IfModule mod_security2.c>\n%s\n</IfModule>' % directives
return directives return directives
def get_redirect(self, site): def get_redirect(self, site):
directives = '' directives = ''
for redirect in site.options.filter(name='redirect'): for redirect in site.directives.filter(name='redirect'):
if re.match(r'^.*[\^\*\$\?\)]+.*$', redirect.value): if re.match(r'^.*[\^\*\$\?\)]+.*$', redirect.value):
directives += "RedirectMatch %s" % redirect.value directives += "RedirectMatch %s" % redirect.value
else: else:
directives += "Redirect %s" % redirect.value directives += "Redirect %s" % redirect.value
return directives return directives
def get_protections(self, site): # def get_protections(self, site):
protections = '' # protections = ''
context = self.get_context(site) # context = self.get_context(site)
for protection in site.options.filter(name='directory_protection'): # for protection in site.directives.filter(name='directory_protection'):
path, name, passwd = protection.value.split() # path, name, passwd = protection.value.split()
path = os.path.join(context['root'], path) # path = os.path.join(context['root'], path)
passwd = os.path.join(self.USER_HOME % context, passwd) # passwd = os.path.join(self.USER_HOME % context, passwd)
protections += textwrap.dedent(""" # protections += textwrap.dedent("""
<Directory %s> # <Directory %s>
AllowOverride All # AllowOverride All
#AuthPAM_Enabled off # #AuthPAM_Enabled off
AuthType Basic # AuthType Basic
AuthName %s # AuthName %s
AuthUserFile %s # AuthUserFile %s
<Limit GET POST> # <Limit GET POST>
require valid-user # require valid-user
</Limit> # </Limit>
</Directory>""" % (path, name, passwd) # </Directory>""" % (path, name, passwd)
) # )
return protections # return protections
def enable_or_disable(self, site): def enable_or_disable(self, site):
context = self.get_context(site) context = self.get_context(site)
@ -184,27 +180,25 @@ class Apache2Backend(ServiceController):
self.append(textwrap.dedent("""\ self.append(textwrap.dedent("""\
if [[ ! -f %(sites_enabled)s ]]; then if [[ ! -f %(sites_enabled)s ]]; then
a2ensite %(site_unique_name)s.conf a2ensite %(site_unique_name)s.conf
else UPDATED=1
UPDATED=0 fi""") % context
fi""" % context )
))
else: else:
self.append(textwrap.dedent("""\ self.append(textwrap.dedent("""\
if [[ -f %(sites_enabled)s ]]; then if [[ -f %(sites_enabled)s ]]; then
a2dissite %(site_unique_name)s.conf; a2dissite %(site_unique_name)s.conf;
else UPDATED=1
UPDATED=0 fi""") % context
fi""" % context )
))
def get_username(self, site): def get_username(self, site):
option = site.options.filter(name='user_group').first() option = site.directives.filter(name='user_group').first()
if option: if option:
return option.value.split()[0] return option.value.split()[0]
return site.account.username return site.account.username
def get_groupname(self, site): 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: if option and ' ' in option.value:
user, group = option.value.split() user, group = option.value.split()
return group return group
@ -236,7 +230,6 @@ class Apache2Backend(ServiceController):
'location': content.path, 'location': content.path,
'app_name': content.webapp.name, 'app_name': content.webapp.name,
'app_path': content.webapp.get_path(), 'app_path': content.webapp.get_path(),
'fpm_port': content.webapp.get_fpm_port(),
}) })
return context return context
@ -292,7 +285,7 @@ class Apache2Traffic(ServiceMonitor):
print sum print sum
}' || [[ $? == 1 ]] && true }' || [[ $? == 1 ]] && true
} | xargs echo ${OBJECT_ID} } | xargs echo ${OBJECT_ID}
}""" % context)) }""") % context)
def monitor(self, site): def monitor(self, site):
context = self.get_context(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 echo 'Webstats are coming soon' > %(webalizer_path)s/index.html
fi fi
echo '%(webalizer_conf)s' > %(webalizer_conf_path)s 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): def delete(self, content):
context = self.get_context(content) context = self.get_context(content)
delete_webapp = not content.webapp.pk delete_webapp = type(content.webapp).objects.filter(pk=content.webapp.pk).exists()
# TODO remove when confirmed that it works, otherwise create a second WebalizerBackend for WebApps
if delete_webapp: if delete_webapp:
self.append("mv %(webapp_path)s %(webapp_path)s.deleted" % context) self.append("mv %(webapp_path)s %(webapp_path)s.deleted" % context)
if delete_webapp or not content.webapp.contents.filter(website=content.website).exists(): if delete_webapp or not content.webapp.content_set.filter(website=content.website).exists():
self.append("mv %(webalizer_path)s %(webalizer_path)s.deleted" % context) self.append("rm -fr %(webalizer_path)s" % context)
self.append("rm %(webalizer_conf_path)s" % context) self.append("rm -f %(webalizer_conf_path)s" % context)
def get_context(self, content): def get_context(self, content):
conf_file = "%s.conf" % content.website.unique_name conf_file = "%s.conf" % content.website.unique_name
@ -88,6 +87,5 @@ class WebalizerBackend(ServiceController):
SearchEngine mamma.com query= SearchEngine mamma.com query=
SearchEngine alltheweb.com query= SearchEngine alltheweb.com query=
DumpSites yes""" % context DumpSites yes""") % context
)
return context return context

View File

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

View File

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

View File

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

View File

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