Fixes on website apache backend
This commit is contained in:
parent
44e8b29b43
commit
340a40262f
|
@ -7,7 +7,9 @@ from django.utils import timezone
|
|||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from orchestra.apps.orchestration import ServiceController
|
||||
from orchestra.apps.systemusers.backends import SystemUserBackend
|
||||
from orchestra.apps.resources import ServiceMonitor
|
||||
from orchestra.utils.humanize import unit_to_bytes
|
||||
|
||||
from . import settings
|
||||
from .models import Address
|
||||
|
@ -20,6 +22,59 @@ from .models import Address
|
|||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class MailSystemUserBackend(ServiceController):
|
||||
verbose_name = _("Mail system users")
|
||||
model = 'mailboxes.Mailbox'
|
||||
|
||||
def save(self, mailbox):
|
||||
context = self.get_context(mailbox)
|
||||
self.append(textwrap.dedent("""
|
||||
if [[ $( id %(user)s ) ]]; then
|
||||
usermod %(user)s --password '%(password)s' --shell %(initial_shell)s
|
||||
else
|
||||
useradd %(user)s --home %(home)s --password '%(password)s'
|
||||
fi
|
||||
mkdir -p %(home)s
|
||||
chmod 751 %(home)s
|
||||
chown %(user)s:%(group)s %(home)s""") % context
|
||||
)
|
||||
if hasattr(mailbox, 'resources') and hasattr(mailbox.resources, 'disk'):
|
||||
self.set_quota(mailbox, context)
|
||||
|
||||
def set_quota(self, mailbox, context):
|
||||
context['quota'] = mailbox.resources.disk.allocated * unit_to_bytes(mailbox.resources.disk.unit)
|
||||
self.append(textwrap.dedent("""
|
||||
mkdir -p %(home)s/Maildir
|
||||
chown %(user)s:%(group)s %(home)s/Maildir
|
||||
if [[ ! -f %(home)s/Maildir/maildirsize ]]; then
|
||||
echo "%(quota)iS" > %(home)s/Maildir/maildirsize
|
||||
chown %(user)s:%(group)s %(home)s/Maildir/maildirsize
|
||||
else
|
||||
sed -i '1s/.*/%(quota)iS/' %(home)s/Maildir/maildirsize
|
||||
fi""") % context
|
||||
)
|
||||
|
||||
def delete(self, mailbox):
|
||||
context = self.get_context(mailbox)
|
||||
self.append(textwrap.dedent("""
|
||||
{ sleep 2 && killall -u %(user)s -s KILL; } &
|
||||
killall -u %(user)s || true
|
||||
userdel %(user)s || true
|
||||
groupdel %(user)s || true""") % context
|
||||
)
|
||||
self.append('mv %(home)s %(home)s.deleted' % context)
|
||||
|
||||
def get_context(self, mailbox):
|
||||
context = {
|
||||
'user': mailbox.name,
|
||||
'group': mailbox.name,
|
||||
'password': mailbox.password if mailbox.active else '*%s' % mailbox.password,
|
||||
'home': mailbox.get_home(),
|
||||
'initial_shell': '/dev/null',
|
||||
}
|
||||
return context
|
||||
|
||||
|
||||
class PasswdVirtualUserBackend(ServiceController):
|
||||
verbose_name = _("Mail virtual user (passwd-file)")
|
||||
model = 'mailboxes.Mailbox'
|
||||
|
@ -29,8 +84,8 @@ class PasswdVirtualUserBackend(ServiceController):
|
|||
|
||||
def set_user(self, context):
|
||||
self.append(textwrap.dedent("""
|
||||
if [[ $( grep "^%(username)s:" %(passwd_path)s ) ]]; then
|
||||
sed -i 's#^%(username)s:.*#%(passwd)s#' %(passwd_path)s
|
||||
if [[ $( grep "^%(user)s:" %(passwd_path)s ) ]]; then
|
||||
sed -i 's#^%(user)s:.*#%(passwd)s#' %(passwd_path)s
|
||||
else
|
||||
echo '%(passwd)s' >> %(passwd_path)s
|
||||
fi""") % context
|
||||
|
@ -40,14 +95,14 @@ class PasswdVirtualUserBackend(ServiceController):
|
|||
|
||||
def set_mailbox(self, context):
|
||||
self.append(textwrap.dedent("""
|
||||
if [[ ! $(grep "^%(username)s@%(mailbox_domain)s\s" %(virtual_mailbox_maps)s) ]]; then
|
||||
echo "%(username)s@%(mailbox_domain)s\tOK" >> %(virtual_mailbox_maps)s
|
||||
if [[ ! $(grep "^%(user)s@%(mailbox_domain)s\s" %(virtual_mailbox_maps)s) ]]; then
|
||||
echo "%(user)s@%(mailbox_domain)s\tOK" >> %(virtual_mailbox_maps)s
|
||||
UPDATED_VIRTUAL_MAILBOX_MAPS=1
|
||||
fi""") % context
|
||||
)
|
||||
|
||||
def generate_filter(self, mailbox, context):
|
||||
self.append("doveadm mailbox create -u %(username)s Spam" % context)
|
||||
self.append("doveadm mailbox create -u %(user)s Spam" % context)
|
||||
context['filtering_path'] = settings.MAILBOXES_SIEVE_PATH % context
|
||||
filtering = mailbox.get_filtering()
|
||||
if filtering:
|
||||
|
@ -67,8 +122,8 @@ class PasswdVirtualUserBackend(ServiceController):
|
|||
context = self.get_context(mailbox)
|
||||
self.append("{ sleep 2 && killall -u %(uid)s -s KILL; } &" % context)
|
||||
self.append("killall -u %(uid)s || true" % context)
|
||||
self.append("sed -i '/^%(username)s:.*/d' %(passwd_path)s" % context)
|
||||
self.append("sed -i '/^%(username)s@%(mailbox_domain)s\s.*/d' %(virtual_mailbox_maps)s" % context)
|
||||
self.append("sed -i '/^%(user)s:.*/d' %(passwd_path)s" % context)
|
||||
self.append("sed -i '/^%(user)s@%(mailbox_domain)s\s.*/d' %(virtual_mailbox_maps)s" % context)
|
||||
self.append("UPDATED_VIRTUAL_MAILBOX_MAPS=1")
|
||||
# TODO delete
|
||||
context['deleted'] = context['home'].rstrip('/') + '.deleted'
|
||||
|
@ -99,7 +154,7 @@ class PasswdVirtualUserBackend(ServiceController):
|
|||
def get_context(self, mailbox):
|
||||
context = {
|
||||
'name': mailbox.name,
|
||||
'username': mailbox.name,
|
||||
'user': mailbox.name,
|
||||
'password': mailbox.password if mailbox.active else '*%s' % mailbox.password,
|
||||
'uid': 10000 + mailbox.pk,
|
||||
'gid': 10000 + mailbox.pk,
|
||||
|
@ -112,7 +167,7 @@ class PasswdVirtualUserBackend(ServiceController):
|
|||
'mailbox_domain': settings.MAILBOXES_VIRTUAL_MAILBOX_DEFAULT_DOMAIN,
|
||||
}
|
||||
context['extra_fields'] = self.get_extra_fields(mailbox, context)
|
||||
context['passwd'] = '{username}:{password}:{uid}:{gid}::{home}::{extra_fields}'.format(**context)
|
||||
context['passwd'] = '{user}:{password}:{uid}:{gid}::{home}::{extra_fields}'.format(**context)
|
||||
return context
|
||||
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@ from .backends import ServiceBackend
|
|||
from .models import Server, Route, BackendLog, BackendOperation
|
||||
from .widgets import RouteBackendSelect
|
||||
|
||||
|
||||
STATE_COLORS = {
|
||||
BackendLog.RECEIVED: 'darkorange',
|
||||
BackendLog.TIMEOUT: 'red',
|
||||
|
|
|
@ -140,7 +140,7 @@ class ServiceBackend(plugins.Plugin):
|
|||
return list(scripts.iteritems())
|
||||
|
||||
def get_banner(self):
|
||||
time = timezone.now().strftime("%h %d, %Y %I:%M:%S")
|
||||
time = timezone.now().strftime("%h %d, %Y %I:%M:%S %Z")
|
||||
return "Generated by Orchestra at %s" % time
|
||||
|
||||
def execute(self, server, async=False):
|
||||
|
|
|
@ -19,28 +19,28 @@ class SystemUserBackend(ServiceController):
|
|||
groups = ','.join(self.get_groups(user))
|
||||
context['groups_arg'] = '--groups %s' % groups if groups else ''
|
||||
self.append(textwrap.dedent("""
|
||||
if [[ $( id %(username)s ) ]]; then
|
||||
usermod %(username)s --password '%(password)s' --shell %(shell)s %(groups_arg)s
|
||||
if [[ $( id %(user)s ) ]]; then
|
||||
usermod %(user)s --password '%(password)s' --shell %(shell)s %(groups_arg)s
|
||||
else
|
||||
useradd %(username)s --home %(home)s --password '%(password)s' --shell %(shell)s %(groups_arg)s
|
||||
useradd %(user)s --home %(home)s --password '%(password)s' --shell %(shell)s %(groups_arg)s
|
||||
fi
|
||||
mkdir -p %(home)s
|
||||
chmod 750 %(home)s
|
||||
chown %(username)s:%(username)s %(home)s""") % context
|
||||
chown %(user)s:%(user)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)
|
||||
self.append('usermod -a -G %(user)s %(member)s' % context)
|
||||
if not user.is_main:
|
||||
self.append('usermod -a -G %(username)s %(mainusername)s' % context)
|
||||
self.append('usermod -a -G %(user)s %(mainuser)s' % context)
|
||||
|
||||
def delete(self, user):
|
||||
context = self.get_context(user)
|
||||
self.append(textwrap.dedent("""\
|
||||
{ sleep 2 && killall -u %(username)s -s KILL; } &
|
||||
killall -u %(username)s || true
|
||||
userdel %(username)s || true
|
||||
groupdel %(username)s || true""") % context
|
||||
{ sleep 2 && killall -u %(user)s -s KILL; } &
|
||||
killall -u %(user)s || true
|
||||
userdel %(user)s || true
|
||||
groupdel %(group)s || true""") % context
|
||||
)
|
||||
self.delete_home(context, user)
|
||||
|
||||
|
@ -51,8 +51,7 @@ class SystemUserBackend(ServiceController):
|
|||
def delete_home(self, context, user):
|
||||
if user.home.rstrip('/') == user.get_base_home().rstrip('/'):
|
||||
# TODO delete instead of this shit
|
||||
context['deleted'] = context['home'].rstrip('/') + '.deleted'
|
||||
self.append("mv %(home)s %(deleted)s" % context)
|
||||
self.append("mv %(home)s %(home)s.deleted" % context)
|
||||
|
||||
def get_groups(self, user):
|
||||
if user.is_main:
|
||||
|
@ -62,10 +61,11 @@ class SystemUserBackend(ServiceController):
|
|||
def get_context(self, user):
|
||||
context = {
|
||||
'object_id': user.pk,
|
||||
'username': user.username,
|
||||
'user': user.username,
|
||||
'group': user.username,
|
||||
'password': user.password if user.active else '*%s' % user.password,
|
||||
'shell': user.shell,
|
||||
'mainusername': user.username if user.is_main else user.account.username,
|
||||
'mainuser': user.username if user.is_main else user.account.username,
|
||||
'home': user.get_home()
|
||||
}
|
||||
return context
|
||||
|
|
|
@ -122,6 +122,7 @@ class SystemUser(models.Model):
|
|||
|
||||
def get_base_home(self):
|
||||
context = {
|
||||
'user': self.username,
|
||||
'username': self.username,
|
||||
}
|
||||
return os.path.normpath(settings.SYSTEMUSERS_HOME % context)
|
||||
|
|
|
@ -20,7 +20,7 @@ SYSTEMUSERS_DISABLED_SHELLS = getattr(settings, 'SYSTEMUSERS_DISABLED_SHELLS', (
|
|||
))
|
||||
|
||||
|
||||
SYSTEMUSERS_HOME = getattr(settings, 'SYSTEMUSERS_HOME', '/home/./%(username)s')
|
||||
SYSTEMUSERS_HOME = getattr(settings, 'SYSTEMUSERS_HOME', '/home/./%(user)s')
|
||||
|
||||
|
||||
SYSTEMUSERS_FTP_LOG_PATH = getattr(settings, 'SYSTEMUSERS_FTP_LOG_PATH', '/var/log/vsftpd.log')
|
||||
|
|
|
@ -40,7 +40,8 @@ class PHPFcgidBackend(WebAppServiceMixin, ServiceController):
|
|||
|
||||
def delete(self, webapp):
|
||||
context = self.get_context(webapp)
|
||||
self.append("rm '%(wrapper_path)s'" % context)
|
||||
self.append("rm -f '%(wrapper_path)s'" % context)
|
||||
self.append("rm -f '%(cmd_options_path)s'" % context)
|
||||
self.delete_webapp_dir(context)
|
||||
|
||||
def commit(self):
|
||||
|
@ -75,7 +76,8 @@ class PHPFcgidBackend(WebAppServiceMixin, ServiceController):
|
|||
if value:
|
||||
cmd_options.append("%s %s" % (directive, value))
|
||||
if cmd_options:
|
||||
cmd_options.insert(0, 'FcgidCmdOptions %(wrapper_path)s' % context)
|
||||
head = '# %(banner)s\nFcgidCmdOptions %(wrapper_path)s' % context
|
||||
cmd_options.insert(0, head)
|
||||
return ' \\\n '.join(cmd_options)
|
||||
|
||||
def get_context(self, webapp):
|
||||
|
|
19
orchestra/apps/webapps/backends/webalizer.py
Normal file
19
orchestra/apps/webapps/backends/webalizer.py
Normal file
|
@ -0,0 +1,19 @@
|
|||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from orchestra.apps.orchestration import ServiceController
|
||||
|
||||
from . import WebAppServiceMixin
|
||||
|
||||
|
||||
class WebalizerAppBackend(WebAppServiceMixin, ServiceController):
|
||||
""" Needed for cleaning up webalizer main folder when webapp deleteion withou related contents """
|
||||
verbose_name = _("Webalizer App")
|
||||
default_route_match = "webapp.type == 'webalizer'"
|
||||
|
||||
def save(self, webapp):
|
||||
context = self.get_context(webapp)
|
||||
self.create_webapp_dir(context)
|
||||
|
||||
def delete(self, webapp):
|
||||
context = self.get_context(webapp)
|
||||
self.delete_webapp_dir(context)
|
26
orchestra/apps/webapps/migrations/0003_auto_20150310_2103.py
Normal file
26
orchestra/apps/webapps/migrations/0003_auto_20150310_2103.py
Normal file
|
@ -0,0 +1,26 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models, migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('webapps', '0002_webapp_data'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='webapp',
|
||||
name='type',
|
||||
field=models.CharField(max_length=32, verbose_name='type', choices=[(b'dokuwiki-mu', b'DokuWiki (SaaS)'), (b'drupal-mu', b'Drupdal (SaaS)'), (b'php4-fcgid', b'PHP 4 FCGID'), (b'php5.2-fcgid', b'PHP 5.2 FCGID'), (b'php5.3-fcgid', b'PHP 5.3 FCGID'), (b'php5.4-fpm', b'PHP 5.4 FPM'), (b'static', b'Static'), (b'symbolic-link', b'Symbolic link'), (b'webalizer', b'Webalizer'), (b'wordpress', b'WordPress'), (b'wordpress-mu', b'WordPress (SaaS)')]),
|
||||
preserve_default=True,
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='webappoption',
|
||||
name='name',
|
||||
field=models.CharField(max_length=128, verbose_name='name', choices=[(None, b'-------'), (b'FileSystem', [(b'public-root', 'Public root')]), (b'Process', [(b'timeout', 'Process timeout'), (b'processes', 'Number of processes')]), (b'PHP', [(b'enabled_functions', 'Enabled functions'), (b'allow_url_include', 'Allow URL include'), (b'allow_url_fopen', 'Allow URL fopen'), (b'auto_append_file', 'Auto append file'), (b'auto_prepend_file', 'Auto prepend file'), (b'date.timezone', 'date.timezone'), (b'default_socket_timeout', 'Default socket timeout'), (b'display_errors', 'Display errors'), (b'extension', 'Extension'), (b'magic_quotes_gpc', 'Magic quotes GPC'), (b'magic_quotes_runtime', 'Magic quotes runtime'), (b'magic_quotes_sybase', 'Magic quotes sybase'), (b'max_execution_time', 'Max execution time'), (b'max_input_time', 'Max input time'), (b'max_input_vars', 'Max input vars'), (b'memory_limit', 'Memory limit'), (b'mysql.connect_timeout', 'Mysql connect timeout'), (b'output_buffering', 'Output buffering'), (b'register_globals', 'Register globals'), (b'post_max_size', 'zend_extension'), (b'sendmail_path', 'sendmail_path'), (b'session.bug_compat_warn', 'session.bug_compat_warn'), (b'session.auto_start', 'session.auto_start'), (b'safe_mode', 'Safe mode'), (b'suhosin.post.max_vars', 'Suhosin POST max vars'), (b'suhosin.get.max_vars', 'Suhosin GET max vars'), (b'suhosin.request.max_vars', 'Suhosin request max vars'), (b'suhosin.session.encrypt', 'suhosin.session.encrypt'), (b'suhosin.simulation', 'Suhosin simulation'), (b'suhosin.executor.include.whitelist', 'suhosin.executor.include.whitelist'), (b'upload_max_filesize', 'upload_max_filesize'), (b'post_max_size', 'zend_extension')])]),
|
||||
preserve_default=True,
|
||||
),
|
||||
]
|
|
@ -1,3 +1,4 @@
|
|||
import os
|
||||
import re
|
||||
|
||||
from django.core.exceptions import ValidationError
|
||||
|
@ -68,7 +69,7 @@ class WebApp(models.Model):
|
|||
public_root = self.options.filter(name='public-root').first()
|
||||
if public_root:
|
||||
path = os.path.join(path, public_root.value)
|
||||
return path.replace('//', '/')
|
||||
return os.path.normpath(path.replace('//', '/'))
|
||||
|
||||
def get_user(self):
|
||||
return self.account.main_systemuser
|
||||
|
|
|
@ -167,7 +167,8 @@ class PHPAppType(AppType):
|
|||
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
|
||||
error_log_path = os.path.normpath(settings.WEBAPPS_PHP_ERROR_LOG_PATH % context)
|
||||
init_vars['error_log'] = error_log_path
|
||||
return init_vars
|
||||
|
||||
|
||||
|
@ -192,7 +193,7 @@ class PHP53App(PHPAppType):
|
|||
|
||||
def get_directive(self, webapp):
|
||||
context = self.get_directive_context(webapp)
|
||||
wrapper_path = settings.WEBAPPS_FCGID_PATH % context
|
||||
wrapper_path = os.path.normpath(settings.WEBAPPS_FCGID_PATH % context)
|
||||
return ('fcgid', webapp.get_path(), wrapper_path)
|
||||
|
||||
|
||||
|
@ -236,7 +237,9 @@ class WebalizerApp(AppType):
|
|||
option_groups = ()
|
||||
|
||||
def get_directive(self, webapp):
|
||||
return ('static', os.path.join(webapp.get_path(), '%(site_name)s/'))
|
||||
webalizer_path = os.path.join(webapp.get_path(), '%(site_name)s')
|
||||
webalizer_path = os.path.normpath(webalizer_path)
|
||||
return ('static', webalizer_path)
|
||||
|
||||
|
||||
class WordPressMuApp(PHPAppType):
|
||||
|
|
|
@ -59,14 +59,14 @@ class ContentInline(AccountAdminMixin, admin.TabularInline):
|
|||
|
||||
class WebsiteAdmin(SelectAccountAdminMixin, ExtendedModelAdmin):
|
||||
list_display = ('name', 'display_domains', 'display_webapps', 'account_link')
|
||||
list_filter = ('port', 'is_active')
|
||||
list_filter = ('protocol', 'is_active',)
|
||||
change_readonly_fields = ('name',)
|
||||
inlines = [ContentInline, DirectiveInline]
|
||||
filter_horizontal = ['domains']
|
||||
fieldsets = (
|
||||
(None, {
|
||||
'classes': ('extrapretty',),
|
||||
'fields': ('account_link', 'name', 'port', 'domains', 'is_active'),
|
||||
'fields': ('account_link', 'name', 'protocol', 'domains', 'is_active'),
|
||||
}),
|
||||
)
|
||||
form = WebsiteAdminForm
|
||||
|
@ -77,7 +77,7 @@ class WebsiteAdmin(SelectAccountAdminMixin, ExtendedModelAdmin):
|
|||
def display_domains(self, website):
|
||||
domains = []
|
||||
for domain in website.domains.all():
|
||||
url = '%s://%s' % (website.protocol, domain)
|
||||
url = '%s://%s' % (website.get_protocol(), domain)
|
||||
domains.append('<a href="%s">%s</a>' % (url, url))
|
||||
return '<br>'.join(domains)
|
||||
display_domains.short_description = _("domains")
|
||||
|
@ -102,9 +102,12 @@ class WebsiteAdmin(SelectAccountAdminMixin, ExtendedModelAdmin):
|
|||
"""
|
||||
formfield = super(WebsiteAdmin, self).formfield_for_dbfield(db_field, **kwargs)
|
||||
if db_field.name == 'domains':
|
||||
qset = Q()
|
||||
for port, __ in settings.WEBSITES_PORT_CHOICES:
|
||||
qset = qset & Q(websites__port=port)
|
||||
qset = Q(
|
||||
Q(websites__protocol=Website.HTTPS_ONLY) |
|
||||
Q(websites__protocol=Website.HTTP_AND_HTTPS) | Q(
|
||||
Q(websites__protocol=Website.HTTP) & Q(websites__protocol=Website.HTTPS)
|
||||
)
|
||||
)
|
||||
args = resolve(kwargs['request'].path).args
|
||||
if args:
|
||||
object_id = args[0]
|
||||
|
|
|
@ -9,27 +9,31 @@ from orchestra.apps.orchestration import ServiceController
|
|||
from orchestra.apps.resources import ServiceMonitor
|
||||
|
||||
from .. import settings
|
||||
from ..utils import normurlpath
|
||||
|
||||
|
||||
class Apache2Backend(ServiceController):
|
||||
HTTP_PORT = 80
|
||||
HTTPS_PORT = 443
|
||||
|
||||
model = 'websites.Website'
|
||||
related_models = (
|
||||
('websites.Content', 'website'),
|
||||
)
|
||||
verbose_name = _("Apache 2")
|
||||
|
||||
def save(self, site):
|
||||
context = self.get_context(site)
|
||||
def render_virtual_host(self, site, context, ssl=False):
|
||||
context['port'] = self.HTTPS_PORT if ssl else self.HTTP_PORT
|
||||
extra_conf = self.get_content_directives(site)
|
||||
if site.protocol is 'https':
|
||||
extra_conf += self.get_ssl(site)
|
||||
extra_conf += self.get_security(site)
|
||||
extra_conf += self.get_redirect(site)
|
||||
directives = site.get_directives()
|
||||
if ssl:
|
||||
extra_conf += self.get_ssl(directives)
|
||||
extra_conf += self.get_security(directives)
|
||||
extra_conf += self.get_redirects(directives)
|
||||
extra_conf += self.get_proxies(directives)
|
||||
context['extra_conf'] = extra_conf
|
||||
|
||||
apache_conf = Template(textwrap.dedent("""\
|
||||
# {{ banner }}
|
||||
<VirtualHost {{ ip }}:{{ site.port }}>
|
||||
return Template(textwrap.dedent("""\
|
||||
<VirtualHost {{ ip }}:{{ port }}>
|
||||
ServerName {{ site.domains.all|first }}\
|
||||
{% if site.domains.all|slice:"1:" %}
|
||||
ServerAlias {{ site.domains.all|slice:"1:"|join:' ' }}{% endif %}\
|
||||
|
@ -41,12 +45,38 @@ class Apache2Backend(ServiceController):
|
|||
{% for line in extra_conf.splitlines %}
|
||||
{{ line | safe }}{% endfor %}
|
||||
#IncludeOptional /etc/apache2/extra-vhos[t]/{{ site_unique_name }}.con[f]
|
||||
</VirtualHost>"""
|
||||
))
|
||||
apache_conf = apache_conf.render(Context(context))
|
||||
# apache_conf += self.get_protections(site)
|
||||
</VirtualHost>
|
||||
""")
|
||||
).render(Context(context))
|
||||
|
||||
def render_redirect_https(self, context):
|
||||
context['port'] = self.HTTP_PORT
|
||||
return Template(textwrap.dedent("""
|
||||
<VirtualHost {{ ip }}:{{ port }}>
|
||||
ServerName {{ site.domains.all|first }}\
|
||||
{% if site.domains.all|slice:"1:" %}
|
||||
ServerAlias {{ site.domains.all|slice:"1:"|join:' ' }}{% endif %}\
|
||||
{% if access_log %}
|
||||
CustomLog {{ access_log }} common{% endif %}\
|
||||
{% if error_log %}
|
||||
ErrorLog {{ error_log }}{% endif %}
|
||||
RewriteEngine On
|
||||
RewriteCond %{HTTPS} off
|
||||
RewriteRule (.*) https://%{HTTP_HOST}%{REQUEST_URI}
|
||||
</VirtualHost>
|
||||
""")
|
||||
).render(Context(context))
|
||||
|
||||
def save(self, site):
|
||||
context = self.get_context(site)
|
||||
apache_conf = '# %(banner)s\n' % context
|
||||
if site.protocol in (site.HTTP, site.HTTP_AND_HTTPS):
|
||||
apache_conf += self.render_virtual_host(site, context, ssl=False)
|
||||
if site.protocol in (site.HTTP_AND_HTTPS, site.HTTPS_ONLY, site.HTTPS):
|
||||
apache_conf += self.render_virtual_host(site, context, ssl=True)
|
||||
if site.protocol == site.HTTPS_ONLY:
|
||||
apache_conf += self.render_redirect_https(context)
|
||||
context['apache_conf'] = apache_conf
|
||||
|
||||
self.append(textwrap.dedent("""\
|
||||
{
|
||||
echo -e '%(apache_conf)s' | diff -N -I'^\s*#' %(sites_available)s -
|
||||
|
@ -78,7 +108,7 @@ class Apache2Backend(ServiceController):
|
|||
def get_static_directives(self, content, app_path):
|
||||
context = self.get_content_context(content)
|
||||
context['app_path'] = app_path % context
|
||||
return "Alias %(location)s %(app_path)s\n" % 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,8 +125,8 @@ 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
|
||||
)
|
||||
|
||||
|
@ -107,52 +137,61 @@ class Apache2Backend(ServiceController):
|
|||
'wrapper_path': wrapper_path,
|
||||
})
|
||||
return textwrap.dedent("""\
|
||||
Alias %(location)s %(app_path)s
|
||||
ProxyPass %(location)s !
|
||||
<Directory %(app_path)s>
|
||||
Alias %(location)s/ %(app_path)s/
|
||||
ProxyPass %(location)s/ !
|
||||
<Directory %(app_path)s/>
|
||||
Options +ExecCGI
|
||||
AddHandler fcgid-script .php
|
||||
FcgidWrapper %(wrapper_path)s
|
||||
</Directory>
|
||||
""") % context
|
||||
|
||||
def get_ssl(self, site):
|
||||
cert = settings.WEBSITES_DEFAULT_HTTPS_CERT
|
||||
custom_cert = site.options.filter(name='ssl')
|
||||
if custom_cert:
|
||||
cert = tuple(custom_cert[0].value.split())
|
||||
# TODO separate directtives?
|
||||
directives = textwrap.dedent("""\
|
||||
SSLEngine on
|
||||
SSLCertificateFile %s
|
||||
SSLCertificateKeyFile %s\
|
||||
""") % cert
|
||||
return directives
|
||||
|
||||
def get_security(self, site):
|
||||
directives = ''
|
||||
for rules in site.directives.filter(name='sec_rule_remove'):
|
||||
def get_ssl(self, directives):
|
||||
config = []
|
||||
ca = directives.get('ssl_ca')
|
||||
if ca:
|
||||
config.append("SSLCACertificateFile %s" % ca[0])
|
||||
cert = directives.get('ssl_cert')
|
||||
if cert:
|
||||
config.append("SSLCertificateFile %" % cert[0])
|
||||
key = directives.get('ssl_key')
|
||||
if key:
|
||||
config.append("SSLCertificateKeyFile %s" % key[0])
|
||||
return '\n'.join(config)
|
||||
|
||||
def get_security(self, directives):
|
||||
config = []
|
||||
for rules in directives.get('sec_rule_remove', []):
|
||||
for rule in rules.value.split():
|
||||
directives += "SecRuleRemoveById %i\n" % int(rule)
|
||||
for modsecurity in site.directives.filter(name='sec_rule_off'):
|
||||
directives += textwrap.dedent("""\
|
||||
<LocationMatch %s>
|
||||
SecRuleEngine Off
|
||||
config.append("SecRuleRemoveById %i" % int(rule))
|
||||
for modsecurity in directives.get('sec_rule_off', []):
|
||||
config.append(textwrap.dedent("""\
|
||||
<Location %s>
|
||||
SecRuleEngine off
|
||||
</LocationMatch>\
|
||||
""") % modsecurity.value
|
||||
if directives:
|
||||
directives = '<IfModule mod_security2.c>\n%s\n</IfModule>' % directives
|
||||
return directives
|
||||
""") % modsecurity
|
||||
)
|
||||
return '\n'.join(config)
|
||||
|
||||
def get_redirect(self, site):
|
||||
directives = ''
|
||||
for redirect in site.directives.filter(name='redirect'):
|
||||
if re.match(r'^.*[\^\*\$\?\)]+.*$', redirect.value):
|
||||
directives += "RedirectMatch %s" % redirect.value
|
||||
def get_redirects(self, directives):
|
||||
config = []
|
||||
for redirect in directives.get('redirect', []):
|
||||
source, target = redirect.split()
|
||||
if re.match(r'^.*[\^\*\$\?\)]+.*$', redirect):
|
||||
config.append("RedirectMatch %s %s" % (source, target))
|
||||
else:
|
||||
directives += "Redirect %s" % redirect.value
|
||||
return directives
|
||||
config.append("Redirect %s %s" % (source, target))
|
||||
return '\n'.join(config)
|
||||
|
||||
def get_proxies(self, directives):
|
||||
config = []
|
||||
for proxy in directives.get('proxy', []):
|
||||
source, target = redirect.split()
|
||||
source = normurlpath(source)
|
||||
config.append('ProxyPass %s %s' % (source, target))
|
||||
config.append('ProxyPassReverse %s %s' % (source, target))
|
||||
return '\n'.join(directives)
|
||||
|
||||
# def get_protections(self, site):
|
||||
# protections = ''
|
||||
# context = self.get_context(site)
|
||||
|
@ -192,15 +231,15 @@ class Apache2Backend(ServiceController):
|
|||
)
|
||||
|
||||
def get_username(self, site):
|
||||
option = site.directives.filter(name='user_group').first()
|
||||
option = site.get_directives().get('user_group')
|
||||
if option:
|
||||
return option.value.split()[0]
|
||||
return option[0]
|
||||
return site.account.username
|
||||
|
||||
def get_groupname(self, site):
|
||||
option = site.directives.filter(name='user_group').first()
|
||||
if option and ' ' in option.value:
|
||||
user, group = option.value.split()
|
||||
option = site.get_directives().get('user_group')
|
||||
if option and ' ' in option:
|
||||
user, group = option.split()
|
||||
return group
|
||||
return site.account.username
|
||||
|
||||
|
@ -227,7 +266,7 @@ class Apache2Backend(ServiceController):
|
|||
context = self.get_context(content.website)
|
||||
context.update({
|
||||
'type': content.webapp.type,
|
||||
'location': content.path,
|
||||
'location': normurlpath(content.path),
|
||||
'app_name': content.webapp.name,
|
||||
'app_path': content.webapp.get_path(),
|
||||
})
|
||||
|
|
|
@ -9,7 +9,7 @@ from .. import settings
|
|||
|
||||
|
||||
class WebalizerBackend(ServiceController):
|
||||
verbose_name = _("Webalizer")
|
||||
verbose_name = _("Webalizer Content")
|
||||
model = 'websites.Content'
|
||||
|
||||
def save(self, content):
|
||||
|
@ -27,7 +27,7 @@ class WebalizerBackend(ServiceController):
|
|||
context = self.get_context(content)
|
||||
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)
|
||||
self.append("rm -f %(webapp_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)
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import re
|
||||
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
|
@ -60,7 +62,7 @@ class SiteDirective(Plugin):
|
|||
|
||||
class Redirect(SiteDirective):
|
||||
name = 'redirect'
|
||||
verbose_name=_("Redirection")
|
||||
verbose_name = _("Redirection")
|
||||
help_text = _("<tt><website path> <destination URL></tt>")
|
||||
regex = r'^[^ ]+\s[^ ]+$'
|
||||
group = SiteDirective.HTTPD
|
||||
|
@ -68,7 +70,7 @@ class Redirect(SiteDirective):
|
|||
|
||||
class Proxy(SiteDirective):
|
||||
name = 'proxy'
|
||||
verbose_name=_("Proxy")
|
||||
verbose_name = _("Proxy")
|
||||
help_text = _("<tt><website path> <target URL></tt>")
|
||||
regex = r'^[^ ]+\shttp[^ ]+(timeout=[0-9]{1,3}|retry=[0-9]|\s)*$'
|
||||
group = SiteDirective.HTTPD
|
||||
|
@ -76,7 +78,7 @@ class Proxy(SiteDirective):
|
|||
|
||||
class UserGroup(SiteDirective):
|
||||
name = 'user_group'
|
||||
verbose_name=_("SuexecUserGroup")
|
||||
verbose_name = _("SuexecUserGroup")
|
||||
help_text = _("<tt>user [group]</tt>, username and optional groupname.")
|
||||
regex = r'^[\w/_]+(\s[\w/_]+)*$'
|
||||
group = SiteDirective.HTTPD
|
||||
|
@ -101,7 +103,7 @@ class UserGroup(SiteDirective):
|
|||
|
||||
class ErrorDocument(SiteDirective):
|
||||
name = 'error_document'
|
||||
verbose_name=_("ErrorDocumentRoot")
|
||||
verbose_name = _("ErrorDocumentRoot")
|
||||
help_text = _("<error code> <URL/path/message><br>"
|
||||
"<tt> 500 http://foo.example.com/cgi-bin/tester</tt><br>"
|
||||
"<tt> 404 /cgi-bin/bad_urls.pl</tt><br>"
|
||||
|
@ -113,7 +115,7 @@ class ErrorDocument(SiteDirective):
|
|||
|
||||
class SSLCA(SiteDirective):
|
||||
name = 'ssl_ca'
|
||||
verbose_name=_("SSL CA")
|
||||
verbose_name = _("SSL CA")
|
||||
help_text = _("Filesystem path of the CA certificate file.")
|
||||
regex = r'^[^ ]+$'
|
||||
group = SiteDirective.SSL
|
||||
|
@ -121,7 +123,7 @@ class SSLCA(SiteDirective):
|
|||
|
||||
class SSLCert(SiteDirective):
|
||||
name = 'ssl_cert'
|
||||
verbose_name=_("SSL cert")
|
||||
verbose_name = _("SSL cert")
|
||||
help_text = _("Filesystem path of the certificate file.")
|
||||
regex = r'^[^ ]+$'
|
||||
group = SiteDirective.SSL
|
||||
|
@ -129,7 +131,7 @@ class SSLCert(SiteDirective):
|
|||
|
||||
class SSLKey(SiteDirective):
|
||||
name = 'ssl_key'
|
||||
verbose_name=_("SSL key")
|
||||
verbose_name = _("SSL key")
|
||||
help_text = _("Filesystem path of the key file.")
|
||||
regex = r'^[^ ]+$'
|
||||
group = SiteDirective.SSL
|
||||
|
@ -137,7 +139,7 @@ class SSLKey(SiteDirective):
|
|||
|
||||
class SecRuleRemove(SiteDirective):
|
||||
name = 'sec_rule_remove'
|
||||
verbose_name=_("SecRuleRemoveById")
|
||||
verbose_name = _("SecRuleRemoveById")
|
||||
help_text = _("Space separated ModSecurity rule IDs.")
|
||||
regex = r'^[0-9\s]+$'
|
||||
group = SiteDirective.SEC
|
||||
|
@ -145,7 +147,7 @@ class SecRuleRemove(SiteDirective):
|
|||
|
||||
class SecEngine(SiteDirective):
|
||||
name = 'sec_engine'
|
||||
verbose_name=_("Modsecurity engine")
|
||||
help_text = _("<tt>On</tt> or <tt>Off</tt>, defaults to On")
|
||||
regex = r'^(On|Off)$'
|
||||
verbose_name = _("Modsecurity engine")
|
||||
help_text = _("URL location for disabling modsecurity engine.")
|
||||
regex = r'^[^ ]+$'
|
||||
group = SiteDirective.SEC
|
||||
|
|
|
@ -1,20 +1,43 @@
|
|||
from django import forms
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.db.models import Q
|
||||
|
||||
from .models import Website
|
||||
|
||||
|
||||
class WebsiteAdminForm(forms.ModelForm):
|
||||
def clean(self):
|
||||
""" Prevent multiples domains on the same port """
|
||||
""" Prevent multiples domains on the same protocol """
|
||||
domains = self.cleaned_data.get('domains')
|
||||
port = self.cleaned_data.get('port')
|
||||
if not domains:
|
||||
return self.cleaned_data
|
||||
protocol = self.cleaned_data.get('protocol')
|
||||
existing = []
|
||||
for domain in domains.all():
|
||||
if domain.websites.filter(port=port).exclude(pk=self.instance.pk).exists():
|
||||
if protocol == Website.HTTP:
|
||||
qset = Q(
|
||||
Q(protocol=Website.HTTP) |
|
||||
Q(protocol=Website.HTTP_AND_HTTPS) |
|
||||
Q(protocol=Website.HTTPS_ONLY)
|
||||
)
|
||||
elif protocol == Website.HTTPS:
|
||||
qset = Q(
|
||||
Q(protocol=Website.HTTPS) |
|
||||
Q(protocol=Website.HTTP_AND_HTTPS) |
|
||||
Q(protocol=Website.HTTPS_ONLY)
|
||||
)
|
||||
elif protocol in (Website.HTTP_AND_HTTPS, Website.HTTPS_ONLY):
|
||||
qset = Q()
|
||||
else:
|
||||
raise ValidationError({
|
||||
'protocol': _("Unknown protocol %s") % protocol
|
||||
})
|
||||
if domain.websites.filter(qset).exclude(pk=self.instance.pk).exists():
|
||||
existing.append(domain.name)
|
||||
if existing:
|
||||
context = (', '.join(existing), port)
|
||||
context = (', '.join(existing), protocol)
|
||||
raise ValidationError({
|
||||
'domains': 'A website is already defined for "%s" on port %s' % context
|
||||
'domains': 'A website is already defined for "%s" on protocol %s' % context
|
||||
})
|
||||
return self.cleaned_data
|
||||
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import os
|
||||
import re
|
||||
|
||||
from django.core.exceptions import ValidationError
|
||||
|
@ -10,18 +11,26 @@ from orchestra.utils.functional import cached
|
|||
|
||||
from . import settings
|
||||
from .directives import SiteDirective
|
||||
from .utils import normurlpath
|
||||
|
||||
|
||||
class Website(models.Model):
|
||||
""" Models a web site, also known as virtual host """
|
||||
HTTP = 'http'
|
||||
HTTPS = 'https'
|
||||
HTTP_AND_HTTPS = 'http/https'
|
||||
HTTPS_ONLY = 'https-only'
|
||||
|
||||
name = models.CharField(_("name"), max_length=128,
|
||||
validators=[validators.validate_name])
|
||||
account = models.ForeignKey('accounts.Account', verbose_name=_("Account"),
|
||||
related_name='websites')
|
||||
# TODO protocol
|
||||
port = models.PositiveIntegerField(_("port"),
|
||||
choices=settings.WEBSITES_PORT_CHOICES,
|
||||
default=settings.WEBSITES_DEFAULT_PORT)
|
||||
protocol = models.CharField(_("protocol"), max_length=16,
|
||||
choices=settings.WEBSITES_PROTOCOL_CHOICES,
|
||||
default=settings.WEBSITES_DEFAULT_PROTOCOL)
|
||||
# port = models.PositiveIntegerField(_("port"),
|
||||
# choices=settings.WEBSITES_PORT_CHOICES,
|
||||
# default=settings.WEBSITES_DEFAULT_PORT)
|
||||
domains = models.ManyToManyField(settings.WEBSITES_DOMAIN_MODEL,
|
||||
related_name='websites', verbose_name=_("domains"))
|
||||
contents = models.ManyToManyField('webapps.WebApp', through='websites.Content')
|
||||
|
@ -39,28 +48,29 @@ class Website(models.Model):
|
|||
'id': self.id,
|
||||
'pk': self.pk,
|
||||
'account': self.account.username,
|
||||
'port': self.port,
|
||||
'protocol': self.protocol,
|
||||
'name': self.name,
|
||||
}
|
||||
|
||||
@property
|
||||
def protocol(self):
|
||||
if self.port == 80:
|
||||
return 'http'
|
||||
if self.port == 443:
|
||||
return 'https'
|
||||
raise TypeError('No protocol for port "%s"' % self.port)
|
||||
def get_protocol(self):
|
||||
if self.protocol in (self.HTTP, self.HTTP_AND_HTTPS):
|
||||
return self.HTTP
|
||||
return self.HTTPS
|
||||
|
||||
@cached
|
||||
def get_directives(self):
|
||||
return {
|
||||
opt.name: opt.value for opt in self.directives.all()
|
||||
}
|
||||
directives = {}
|
||||
for opt in self.directives.all():
|
||||
try:
|
||||
directives[opt.name].append(opt.value)
|
||||
except KeyError:
|
||||
directives[opt.name] = [opt.value]
|
||||
return directives
|
||||
|
||||
def get_absolute_url(self):
|
||||
domain = self.domains.first()
|
||||
if domain:
|
||||
return '%s://%s' % (self.protocol, domain)
|
||||
return '%s://%s' % (self.get_protocol(), domain)
|
||||
|
||||
def get_www_log_context(self):
|
||||
return {
|
||||
|
@ -74,12 +84,12 @@ class Website(models.Model):
|
|||
def get_www_access_log_path(self):
|
||||
context = self.get_www_log_context()
|
||||
path = settings.WEBSITES_WEBSITE_WWW_ACCESS_LOG_PATH % context
|
||||
return path.replace('//', '/')
|
||||
return os.path.normpath(path.replace('//', '/'))
|
||||
|
||||
def get_www_error_log_path(self):
|
||||
context = self.get_www_log_context()
|
||||
path = settings.WEBSITES_WEBSITE_WWW_ERROR_LOG_PATH % context
|
||||
return path.replace('//', '/')
|
||||
return os.path.normpath(path.replace('//', '/'))
|
||||
|
||||
|
||||
class Directive(models.Model):
|
||||
|
@ -122,15 +132,12 @@ class Content(models.Model):
|
|||
return self.path
|
||||
|
||||
def clean(self):
|
||||
if not self.path.startswith('/'):
|
||||
self.path = '/' + self.path
|
||||
if not self.path.endswith('/'):
|
||||
self.path = self.path + '/'
|
||||
self.path = normurlpath(self.path)
|
||||
|
||||
def get_absolute_url(self):
|
||||
domain = self.website.domains.first()
|
||||
if domain:
|
||||
return '%s://%s%s' % (self.website.protocol, domain, self.path)
|
||||
return '%s://%s%s' % (self.website.get_protocol(), domain, self.path)
|
||||
|
||||
|
||||
services.register(Website)
|
||||
|
|
|
@ -7,19 +7,21 @@ WEBSITES_UNIQUE_NAME_FORMAT = getattr(settings, 'WEBSITES_UNIQUE_NAME_FORMAT',
|
|||
|
||||
|
||||
# TODO 'http', 'https', 'https-only', 'http and https' and rename to PROTOCOL
|
||||
WEBSITES_PORT_CHOICES = getattr(settings, 'WEBSITES_PORT_CHOICES', (
|
||||
(80, 'HTTP'),
|
||||
(443, 'HTTPS'),
|
||||
))
|
||||
#WEBSITES_PORT_CHOICES = getattr(settings, 'WEBSITES_PORT_CHOICES', (
|
||||
# (80, 'HTTP'),
|
||||
# (443, 'HTTPS'),
|
||||
#))
|
||||
|
||||
|
||||
WEBSITES_PROTOCOL_CHOICES = getattr(settings, 'WEBSITES_PROTOCOL_CHOICES', (
|
||||
('http', "HTTP"),
|
||||
('https', "HTTPS"),
|
||||
('http-https', _("HTTP and HTTPS")),
|
||||
('http/https', _("HTTP and HTTPS")),
|
||||
('https-only', _("HTTPS only")),
|
||||
))
|
||||
|
||||
WEBSITES_DEFAULT_PROTOCOL = getattr(settings, 'WEBSITES_DEFAULT_PROTOCOL', 'http')
|
||||
|
||||
WEBSITES_DEFAULT_PORT = getattr(settings, 'WEBSITES_DEFAULT_PORT', 80)
|
||||
|
||||
|
||||
|
@ -60,3 +62,13 @@ WEBSITES_WEBSITE_WWW_ERROR_LOG_PATH = getattr(settings, 'WEBSITES_WEBSITE_WWW_ER
|
|||
|
||||
WEBSITES_TRAFFIC_IGNORE_HOSTS = getattr(settings, 'WEBSITES_TRAFFIC_IGNORE_HOSTS',
|
||||
('127.0.0.1',))
|
||||
|
||||
|
||||
#WEBSITES_DEFAULT_SSl_CA = getattr(settings, 'WEBSITES_DEFAULT_SSl_CA',
|
||||
# '')
|
||||
|
||||
#WEBSITES_DEFAULT_SSl_CERT = getattr(settings, 'WEBSITES_DEFAULT_SSl_CERT',
|
||||
# '')
|
||||
|
||||
#WEBSITES_DEFAULT_SSl_KEY = getattr(settings, 'WEBSITES_DEFAULT_SSl_KEY',
|
||||
# '')
|
||||
|
|
5
orchestra/apps/websites/utils.py
Normal file
5
orchestra/apps/websites/utils.py
Normal file
|
@ -0,0 +1,5 @@
|
|||
def normurlpath(path):
|
||||
if not path.startswith('/'):
|
||||
path = '/' + path
|
||||
path = path.rstrip('/')
|
||||
return path.replace('//', '/')
|
Loading…
Reference in a new issue