Continue refactoring webapps and websites
This commit is contained in:
parent
12910bf072
commit
2a8d20910f
7
TODO.md
7
TODO.md
|
@ -206,3 +206,10 @@ ssh-copy-id root@<server-address>
|
|||
|
||||
* symbolicLink webapp (link stuff from other places)
|
||||
|
||||
* logs on panle/logs/ ? mkdir ~webapps, backend post save signal?
|
||||
* transaction abortion on backend.generation, transaction fault tolerant on backend.execute()
|
||||
* <IfModule security2_module> and other IfModule on backend SecRule
|
||||
|
||||
|
||||
* webalizer backend on webapps and check webapps.websites.all()
|
||||
* monitor in batches doesnt work!!!
|
||||
|
|
|
@ -42,7 +42,7 @@ ACCOUNTS_CREATE_RELATED = getattr(settings, 'ACCOUNTS_CREATE_RELATED', (
|
|||
('domains.Domain',
|
||||
'name',
|
||||
{
|
||||
'name': '"%s.orchestra.lan" % account.username'
|
||||
'name': '"%s.orchestra.lan" % account.username.replace("_", "-")',
|
||||
},
|
||||
_("Designates whether to creates a related subdomain <username>.orchestra.lan or not."),
|
||||
),
|
||||
|
|
|
@ -106,8 +106,7 @@ class ContactInline(admin.StackedInline):
|
|||
|
||||
insertattr(AccountAdmin, 'inlines', ContactInline)
|
||||
search_fields = (
|
||||
'contacts__short_name', 'contacts__full_name', 'contacts__phone',
|
||||
'contacts__phone2', 'contacts__email'
|
||||
'contacts__short_name', 'contacts__full_name',
|
||||
)
|
||||
for field in search_fields:
|
||||
insertattr(AccountAdmin, 'search_fields', field)
|
||||
|
|
|
@ -33,7 +33,10 @@ class Bind9MasterDomainBackend(ServiceController):
|
|||
self.append(textwrap.dedent("""\
|
||||
echo -e '%(zone)s' > %(zone_path)s.tmp
|
||||
diff -N -I'^\s*;;' %(zone_path)s %(zone_path)s.tmp || UPDATED=1
|
||||
mv %(zone_path)s.tmp %(zone_path)s""" % context
|
||||
mv %(zone_path)s.tmp %(zone_path)s
|
||||
# Because bind realod will not display any fucking error
|
||||
named-checkzone -k fail -n fail %(name)s %(zone_path)s
|
||||
""" % context
|
||||
))
|
||||
self.update_conf(context)
|
||||
|
||||
|
@ -78,7 +81,7 @@ class Bind9MasterDomainBackend(ServiceController):
|
|||
def get_servers(self, domain, backend):
|
||||
""" Get related server IPs from registered backend routes """
|
||||
from orchestra.apps.orchestration.manager import router
|
||||
operation = Operation.create(backend_cls=backend, action=Operation.SAVE, instance=domain)
|
||||
operation = Operation.create(backend, peration.SAVE, domain)
|
||||
servers = []
|
||||
for server in router.get_servers(operation):
|
||||
servers.append(server.get_ip())
|
||||
|
|
|
@ -160,14 +160,14 @@ class Domain(models.Model):
|
|||
type=Record.SOA,
|
||||
value=' '.join(soa)
|
||||
))
|
||||
is_a = not types or Record.A in types or Record.AAAA in types
|
||||
if Record.MX not in types and is_a:
|
||||
is_host = self.is_top or not types or Record.A in types or Record.AAAA in types
|
||||
if Record.MX not in types and is_host:
|
||||
for mx in settings.DOMAINS_DEFAULT_MX:
|
||||
records.append(AttrDict(
|
||||
type=Record.MX,
|
||||
value=mx
|
||||
))
|
||||
if (Record.A not in types and Record.AAAA not in types) and is_a:
|
||||
if (Record.A not in types and Record.AAAA not in types) and is_host:
|
||||
records.append(AttrDict(
|
||||
type=Record.A,
|
||||
value=settings.DOMAINS_DEFAULT_A
|
||||
|
@ -250,4 +250,5 @@ class Record(models.Model):
|
|||
def get_ttl(self):
|
||||
return self.ttl or settings.DOMAINS_DEFAULT_TTL
|
||||
|
||||
|
||||
services.register(Domain)
|
||||
|
|
|
@ -34,7 +34,7 @@ DOMAINS_SLAVES_PATH = getattr(settings, 'DOMAINS_SLAVES_PATH', '/etc/bind/named.
|
|||
|
||||
|
||||
DOMAINS_CHECKZONE_BIN_PATH = getattr(settings, 'DOMAINS_CHECKZONE_BIN_PATH',
|
||||
'/usr/sbin/named-checkzone -i local')
|
||||
'/usr/sbin/named-checkzone -i local -k fail -n fail')
|
||||
|
||||
DOMAINS_CHECKZONE_PATH = getattr(settings, 'DOMAINS_CHECKZONE_PATH', '/dev/shm')
|
||||
|
||||
|
|
|
@ -52,7 +52,7 @@ def execute(operations, async=False):
|
|||
for server in operation.servers:
|
||||
key = (server, operation.backend)
|
||||
if key not in scripts:
|
||||
scripts[key] = (operation.backend, [operation])
|
||||
scripts[key] = (operation.backend(), [operation])
|
||||
scripts[key][0].prepare()
|
||||
else:
|
||||
scripts[key][1].append(operation)
|
||||
|
|
|
@ -119,17 +119,17 @@ class BackendOperation(models.Model):
|
|||
|
||||
def __hash__(self):
|
||||
""" set() """
|
||||
backend_cls = type(self.backend)
|
||||
return hash(backend_cls) + hash(self.instance) + hash(self.action)
|
||||
backend = getattr(self, 'backend', self.backend)
|
||||
return hash(backend) + hash(self.instance) + hash(self.action)
|
||||
|
||||
def __eq__(self, operation):
|
||||
""" set() """
|
||||
return hash(self) == hash(operation)
|
||||
|
||||
@classmethod
|
||||
def create(cls, backend_cls, instance, action, servers=None):
|
||||
op = cls(backend=backend_cls.get_name(), instance=instance, action=action)
|
||||
op.backend = backend_cls()
|
||||
def create(cls, backend, instance, action, servers=None):
|
||||
op = cls(backend=backend.get_name(), instance=instance, action=action)
|
||||
op.backend = backend
|
||||
# instance should maintain any dynamic attribute until backend execution
|
||||
# deep copy is prefered over copy otherwise objects will share same atributes (queryset cache)
|
||||
op.instance = copy.deepcopy(instance)
|
||||
|
@ -154,7 +154,7 @@ class BackendOperation(models.Model):
|
|||
"""
|
||||
if self.action == self.DELETE:
|
||||
if hasattr(self.backend, 'get_context'):
|
||||
self.backend.get_context(op.instance)
|
||||
self.backend.get_context(self.instance)
|
||||
|
||||
def backend_class(self):
|
||||
return ServiceBackend.get_backend(self.backend)
|
||||
|
@ -194,7 +194,7 @@ class Route(models.Model):
|
|||
def get_servers(cls, operation, **kwargs):
|
||||
cache = kwargs.get('cache', {})
|
||||
servers = []
|
||||
backend_cls = type(operation.backend)
|
||||
backend_cls = operation.backend
|
||||
key = (backend_cls.get_name(), operation.action)
|
||||
try:
|
||||
routes = cache[key]
|
||||
|
|
|
@ -42,7 +42,7 @@ class SystemUserAdmin(ChangePasswordAdminMixin, SelectAccountAdminMixin, Extende
|
|||
'fields': ('shell', ('home', 'directory'), 'groups'),
|
||||
}),
|
||||
)
|
||||
search_fields = ['username']
|
||||
search_fields = ('username', 'account__username')
|
||||
readonly_fields = ('account_link',)
|
||||
change_readonly_fields = ('username',)
|
||||
filter_horizontal = ('groups',)
|
||||
|
|
|
@ -26,7 +26,6 @@ class SystemUser(models.Model):
|
|||
|
||||
Username max_length determined by LINUX system user lentgh: 32
|
||||
"""
|
||||
# TODO max_length
|
||||
username = models.CharField(_("username"), max_length=32, unique=True,
|
||||
help_text=_("Required. 64 characters or fewer. Letters, digits and ./-/_ only."),
|
||||
validators=[validators.validate_username])
|
||||
|
|
|
@ -9,8 +9,9 @@ from orchestra.apps.accounts.admin import AccountAdminMixin
|
|||
from orchestra.forms.widgets import DynamicHelpTextSelect
|
||||
from orchestra.plugins.admin import SelectPluginAdminMixin
|
||||
|
||||
from . import settings, options
|
||||
from .applications import App
|
||||
from . import settings
|
||||
from .options import AppOption
|
||||
from .types import AppType
|
||||
from .models import WebApp, WebAppOption
|
||||
|
||||
|
||||
|
@ -19,7 +20,7 @@ class WebAppOptionInline(admin.TabularInline):
|
|||
extra = 1
|
||||
|
||||
OPTIONS_HELP_TEXT = {
|
||||
op.name: str(unicode(op.help_text)) for op in options.get_enabled().values()
|
||||
op.name: str(unicode(op.help_text)) for op in AppOption.get_plugins()
|
||||
}
|
||||
|
||||
class Media:
|
||||
|
@ -35,7 +36,7 @@ class WebAppOptionInline(admin.TabularInline):
|
|||
plugin = self.parent_object.type_class
|
||||
else:
|
||||
request = kwargs['request']
|
||||
plugin = App.get_plugin(request.GET['type'])
|
||||
plugin = AppType.get_plugin(request.GET['type'])
|
||||
kwargs['choices'] = plugin.get_options_choices()
|
||||
# Help text based on select widget
|
||||
kwargs['widget'] = DynamicHelpTextSelect(
|
||||
|
@ -52,8 +53,8 @@ class WebAppAdmin(SelectPluginAdminMixin, AccountAdminMixin, ExtendedModelAdmin)
|
|||
inlines = [WebAppOptionInline]
|
||||
readonly_fields = ('account_link',)
|
||||
change_readonly_fields = ('name', 'type')
|
||||
list_prefetch_related = ('content_set__website',)
|
||||
plugin = App
|
||||
list_prefetch_related = ('contents__website',)
|
||||
plugin = AppType
|
||||
plugin_field = 'type'
|
||||
plugin_title = _("Web application type")
|
||||
|
||||
|
@ -63,7 +64,7 @@ class WebAppAdmin(SelectPluginAdminMixin, AccountAdminMixin, ExtendedModelAdmin)
|
|||
|
||||
def display_websites(self, webapp):
|
||||
websites = []
|
||||
for content in webapp.content_set.all():
|
||||
for content in webapp.contents.all():
|
||||
website = content.website
|
||||
url = change_url(website)
|
||||
name = "%s on %s" % (website.name, content.path)
|
||||
|
|
|
@ -1,44 +1,15 @@
|
|||
import pkgutil
|
||||
import textwrap
|
||||
|
||||
from .. import settings
|
||||
|
||||
|
||||
class WebAppServiceMixin(object):
|
||||
model = 'webapps.WebApp'
|
||||
directive = None
|
||||
|
||||
def valid_directive(self, webapp):
|
||||
return settings.WEBAPPS_TYPES[webapp.type]['directive'][0] == self.directive
|
||||
|
||||
def create_webapp_dir(self, context):
|
||||
self.append("mkdir -p %(app_path)s" % context)
|
||||
self.append("chown %(user)s:%(group)s %(app_path)s" % context)
|
||||
|
||||
def get_php_init_vars(self, webapp, per_account=False):
|
||||
"""
|
||||
process php options for inclusion on php.ini
|
||||
per_account=True merges all (account, webapp.type) options
|
||||
"""
|
||||
init_vars = []
|
||||
options = webapp.options.all()
|
||||
if per_account:
|
||||
options = webapp.account.webapps.filter(webapp_type=webapp.type)
|
||||
for opt in options:
|
||||
name = opt.name.replace('PHP-', '')
|
||||
value = "%s" % opt.value
|
||||
init_vars.append((name, value))
|
||||
enabled_functions = []
|
||||
for value in options.filter(name='php-enabled_functions').values_list('value', flat=True):
|
||||
enabled_functions += enabled_functions.get().value.split(',')
|
||||
if enabled_functions:
|
||||
disabled_functions = []
|
||||
for function in settings.WEBAPPS_PHP_DISABLED_FUNCTIONS:
|
||||
if function not in enabled_functions:
|
||||
disabled_functions.append(function)
|
||||
init_vars.append(('dissabled_functions', ','.join(disabled_functions)))
|
||||
return init_vars
|
||||
|
||||
def delete_webapp_dir(self, context):
|
||||
self.append("rm -fr %(app_path)s" % context)
|
||||
|
||||
|
|
|
@ -13,12 +13,9 @@ from .. import settings
|
|||
class PHPFPMBackend(WebAppServiceMixin, ServiceController):
|
||||
""" Per-webapp php application """
|
||||
verbose_name = _("PHP-FPM")
|
||||
directive = 'fpm'
|
||||
default_route_match = "webapp.type.endswith('-fpm')"
|
||||
|
||||
def save(self, webapp):
|
||||
if not self.valid_directive(webapp):
|
||||
return
|
||||
context = self.get_context(webapp)
|
||||
self.create_webapp_dir(context)
|
||||
self.append(textwrap.dedent("""\
|
||||
|
@ -31,8 +28,6 @@ class PHPFPMBackend(WebAppServiceMixin, ServiceController):
|
|||
))
|
||||
|
||||
def delete(self, webapp):
|
||||
if not self.valid_directive(webapp):
|
||||
return
|
||||
context = self.get_context(webapp)
|
||||
self.append("rm '%(fpm_path)s'" % context)
|
||||
self.delete_webapp_dir(context)
|
||||
|
@ -43,8 +38,8 @@ class PHPFPMBackend(WebAppServiceMixin, ServiceController):
|
|||
super(PHPFPMBackend, self).commit()
|
||||
self.append(textwrap.dedent("""
|
||||
[[ $UPDATEDFPM == 1 ]] && {
|
||||
service php5-fpm start
|
||||
service php5-fpm reload
|
||||
service php5-fpm start
|
||||
}"""))
|
||||
|
||||
def get_context(self, webapp):
|
||||
|
|
|
@ -2,6 +2,8 @@ import re
|
|||
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.db import models
|
||||
from django.db.models.signals import pre_save, pre_delete
|
||||
from django.dispatch import receiver
|
||||
from django.utils.functional import cached_property
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from jsonfield import JSONField
|
||||
|
@ -9,7 +11,7 @@ from jsonfield import JSONField
|
|||
from orchestra.core import validators, services
|
||||
from orchestra.utils.functional import cached
|
||||
|
||||
from . import settings, options
|
||||
from . import settings
|
||||
from .types import AppType
|
||||
|
||||
|
||||
|
@ -53,17 +55,11 @@ class WebApp(models.Model):
|
|||
opt.name: opt.value for opt in self.options.all()
|
||||
}
|
||||
|
||||
@property
|
||||
def app_type(self):
|
||||
return settings.WEBAPPS_TYPES[self.type]
|
||||
|
||||
def get_fpm_port(self):
|
||||
return settings.WEBAPPS_FPM_START_PORT + self.account_id
|
||||
|
||||
def get_directive(self):
|
||||
directive = self.app_type['directive']
|
||||
args = directive[1:] if len(directive) > 1 else ()
|
||||
return directive[0], args
|
||||
return self.type_instance.get_directive(self)
|
||||
|
||||
def get_path(self):
|
||||
context = {
|
||||
|
@ -86,8 +82,7 @@ class WebApp(models.Model):
|
|||
class WebAppOption(models.Model):
|
||||
webapp = models.ForeignKey(WebApp, verbose_name=_("Web application"),
|
||||
related_name='options')
|
||||
name = models.CharField(_("name"), max_length=128,
|
||||
choices=((op.name, op.verbose_name) for op in options.get_enabled().values()))
|
||||
name = models.CharField(_("name"), max_length=128, choices=AppType.get_options_choices())
|
||||
value = models.CharField(_("value"), max_length=256)
|
||||
|
||||
class Meta:
|
||||
|
@ -98,18 +93,24 @@ class WebAppOption(models.Model):
|
|||
def __unicode__(self):
|
||||
return self.name
|
||||
|
||||
@cached_property
|
||||
def option_class(self):
|
||||
return SiteDirective.get_plugin(self.name)
|
||||
|
||||
@cached_property
|
||||
def option_instance(self):
|
||||
""" Per request lived option instance """
|
||||
return self.option_class()
|
||||
|
||||
def clean(self):
|
||||
option = options.get_enabled()[self.name]
|
||||
option.validate(self)
|
||||
self.option_instance.validate(self)
|
||||
|
||||
|
||||
services.register(WebApp)
|
||||
|
||||
|
||||
# Admin bulk deletion doesn't call model.delete(), we use signals instead of model method overriding
|
||||
|
||||
from django.db.models.signals import pre_save, pre_delete
|
||||
from django.dispatch import receiver
|
||||
# Admin bulk deletion doesn't call model.delete()
|
||||
# So, signals are used instead of model method overriding
|
||||
|
||||
@receiver(pre_save, sender=WebApp, dispatch_uid='webapps.type.save')
|
||||
def type_save(sender, *args, **kwargs):
|
||||
|
|
|
@ -1,300 +1,350 @@
|
|||
from django.core.exceptions import ValidationError
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from orchestra.plugins import Plugin
|
||||
from orchestra.utils.functional import cached
|
||||
from orchestra.utils.python import import_class
|
||||
|
||||
from . import settings
|
||||
|
||||
|
||||
class AppOption(object):
|
||||
def __init__(self, name, *args, **kwargs):
|
||||
self.name = name
|
||||
self.verbose_name = kwargs.pop('verbose_name', name)
|
||||
self.help_text = kwargs.pop('help_text', '')
|
||||
for k,v in kwargs.iteritems():
|
||||
setattr(self, k, v)
|
||||
class AppOption(Plugin):
|
||||
PHP = 'PHP'
|
||||
PROCESS = 'Process'
|
||||
FILESYSTEM = 'FileSystem'
|
||||
|
||||
def validate(self, webapp):
|
||||
if self.regex and not re.match(self.regex, webapp.value):
|
||||
help_text = ""
|
||||
group = None
|
||||
|
||||
@classmethod
|
||||
@cached
|
||||
def get_plugins(cls):
|
||||
plugins = []
|
||||
for cls in settings.WEBAPPS_ENABLED_OPTIONS:
|
||||
plugins.append(import_class(cls))
|
||||
return plugins
|
||||
|
||||
@classmethod
|
||||
@cached
|
||||
def get_option_groups(cls):
|
||||
groups = {}
|
||||
for opt in cls.get_plugins():
|
||||
try:
|
||||
groups[opt.group].append(opt)
|
||||
except KeyError:
|
||||
groups[opt.group] = [opt]
|
||||
return groups
|
||||
|
||||
def validate(self, option):
|
||||
if self.regex and not re.match(self.regex, option.value):
|
||||
raise ValidationError({
|
||||
'value': ValidationError(_("'%(value)s' does not match %(regex)s."),
|
||||
params={
|
||||
'value': webapp.value,
|
||||
'value': option.value,
|
||||
'regex': self.regex
|
||||
}),
|
||||
})
|
||||
|
||||
|
||||
public_root = AppOption('public-root',
|
||||
verbose_name=_("Public root"),
|
||||
help_text=_("Document root relative to webapps/<webapp>/"),
|
||||
regex=r'[^ ]+'
|
||||
)
|
||||
class PublicRoot(AppOption):
|
||||
name = 'public-root'
|
||||
verbose_name = _("Public root")
|
||||
help_text = _("Document root relative to webapps/<webapp>/")
|
||||
regex = r'[^ ]+'
|
||||
group = AppOption.FILESYSTEM
|
||||
|
||||
timeout = AppOption('timeout',
|
||||
|
||||
class DirectoryProtection(AppOption):
|
||||
name = 'directory-protection'
|
||||
verbose_name = _("Directory protection")
|
||||
help_text = _("Space separated ...")
|
||||
regex = r'^([\w/_]+)\s+(\".*\")\s+([\w/_\.]+)$'
|
||||
group = AppOption.FILESYSTEM
|
||||
|
||||
|
||||
|
||||
class Timeout(AppOption):
|
||||
name = 'timeout'
|
||||
# FCGID FcgidIOTimeout
|
||||
# FPM pm.request_terminate_timeout
|
||||
# PHP max_execution_time ini
|
||||
verbose_name=_("Process timeout"),
|
||||
help_text=_("Maximum time in seconds allowed for a request to complete (a number between 0 and 999)."),
|
||||
regex=r'^[0-9]{1,3}$',
|
||||
)
|
||||
verbose_name = _("Process timeout")
|
||||
help_text = _("Maximum time in seconds allowed for a request to complete (a number between 0 and 999).")
|
||||
regex = r'^[0-9]{1,3}$'
|
||||
group = AppOption.PROCESS
|
||||
|
||||
processes = AppOption('processes',
|
||||
|
||||
class Processes(AppOption):
|
||||
name = 'processes'
|
||||
# FCGID MaxProcesses
|
||||
# FPM pm.max_children
|
||||
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)."),
|
||||
regex=r'^[0-9]$',
|
||||
)
|
||||
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).")
|
||||
regex=r'^[0-9]$'
|
||||
group = AppOption.PROCESS
|
||||
|
||||
php_enabled_functions = AppOption('php-enabled_functions',
|
||||
verbose_name=_("Enabled functions"),
|
||||
help_text = ' '.join(settings.WEBAPPS_PHP_DISABLED_FUNCTIONS),
|
||||
|
||||
class PHPEnabledFunctions(AppOption):
|
||||
name = 'enabled_functions'
|
||||
verbose_name=_("Enabled functions")
|
||||
help_text = ' '.join(settings.WEBAPPS_PHP_DISABLED_FUNCTIONS)
|
||||
regex=r'^[\w\.,-]+$'
|
||||
)
|
||||
group = AppOption.PHP
|
||||
|
||||
php_allow_url_include = AppOption('PHP-allow_url_include',
|
||||
verbose_name=_("Allow URL include"),
|
||||
|
||||
class PHPAllowURLInclude(AppOption):
|
||||
name = 'allow_url_include'
|
||||
verbose_name=_("Allow URL include")
|
||||
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)$'
|
||||
)
|
||||
group = AppOption.PHP
|
||||
|
||||
php_allow_url_fopen = AppOption('PHP-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)."),
|
||||
|
||||
class PHPAllowURLFopen(AppOption):
|
||||
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).")
|
||||
regex=r'^(On|Off|on|off)$'
|
||||
)
|
||||
group = AppOption.PHP
|
||||
|
||||
php_auto_append_file = AppOption('PHP-auto_append_file',
|
||||
verbose_name=_("Auto append file"),
|
||||
help_text=_("Specifies the name of a file that is automatically parsed after the main file."),
|
||||
|
||||
class PHPAutoAppendFile(AppOption):
|
||||
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.")
|
||||
regex=r'^[\w\.,-/]+$'
|
||||
)
|
||||
group = AppOption.PHP
|
||||
|
||||
php_auto_prepend_file = AppOption('PHP-auto_prepend_file',
|
||||
verbose_name=_("Auto prepend file"),
|
||||
help_text=_("Specifies the name of a file that is automatically parsed before the main file."),
|
||||
|
||||
class PHPAutoPrependFile(AppOption):
|
||||
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.")
|
||||
regex=r'^[\w\.,-/]+$'
|
||||
)
|
||||
group = AppOption.PHP
|
||||
|
||||
php_date_timezone = AppOption('PHP-date.timezone',
|
||||
verbose_name=_("date.timezone"),
|
||||
help_text=_("Sets the default timezone used by all date/time functions (Timezone string 'Europe/London')."),
|
||||
|
||||
class PHPDateTimeZone(AppOption):
|
||||
name = 'date.timezone'
|
||||
verbose_name=_("date.timezone")
|
||||
help_text=_("Sets the default timezone used by all date/time functions (Timezone string 'Europe/London').")
|
||||
regex=r'^\w+/\w+$'
|
||||
)
|
||||
group = AppOption.PHP
|
||||
|
||||
php_default_socket_timeout = AppOption('PHP-default_socket_timeout',
|
||||
verbose_name=_("Default socket timeout"),
|
||||
help_text=_("Number between 0 and 999."),
|
||||
|
||||
class PHPDefaultSocketTimeout(AppOption):
|
||||
name = 'default_socket_timeout'
|
||||
verbose_name=_("Default socket timeout")
|
||||
help_text=_("Number between 0 and 999.")
|
||||
regex=r'^[0-9]{1,3}$'
|
||||
)
|
||||
group = AppOption.PHP
|
||||
|
||||
php_display_errors = AppOption('PHP-display_errors',
|
||||
verbose_name=_("Display errors"),
|
||||
|
||||
class PHPDisplayErrors(AppOption):
|
||||
name = 'display_errors'
|
||||
verbose_name=_("Display errors")
|
||||
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)$'
|
||||
)
|
||||
group = AppOption.PHP
|
||||
|
||||
php_extension = AppOption('PHP-extension',
|
||||
verbose_name=_("Extension"),
|
||||
|
||||
class PHPExtension(AppOption):
|
||||
name = 'extension'
|
||||
verbose_name=_("Extension")
|
||||
regex=r'^[^ ]+$'
|
||||
)
|
||||
group = AppOption.PHP
|
||||
|
||||
php_magic_quotes_gpc = AppOption('PHP-magic_quotes_gpc',
|
||||
verbose_name=_("Magic quotes GPC"),
|
||||
|
||||
class PHPMagicQuotesGPC(AppOption):
|
||||
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) "
|
||||
"<b>DEPRECATED as of PHP 5.3.0</b>."),
|
||||
regex=r'^(On|Off|on|off)$',
|
||||
"<b>DEPRECATED as of PHP 5.3.0</b>.")
|
||||
regex=r'^(On|Off|on|off)$'
|
||||
deprecated=5.3
|
||||
)
|
||||
group = AppOption.PHP
|
||||
|
||||
php_magic_quotes_runtime = AppOption('PHP-magic_quotes_runtime',
|
||||
verbose_name=_("Magic quotes runtime"),
|
||||
|
||||
class PHPMagicQuotesRuntime(AppOption):
|
||||
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 "
|
||||
"with a backslash (On or Off) <b>DEPRECATED as of PHP 5.3.0</b>."),
|
||||
regex=r'^(On|Off|on|off)$',
|
||||
deprecated=5.3
|
||||
)
|
||||
|
||||
php_magic_quotes_sybase = AppOption('PHP-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)."),
|
||||
"with a backslash (On or Off) <b>DEPRECATED as of PHP 5.3.0</b>.")
|
||||
regex=r'^(On|Off|on|off)$'
|
||||
)
|
||||
deprecated=5.3
|
||||
group = AppOption.PHP
|
||||
|
||||
php_max_execution_time = AppOption('PHP-max_execution_time',
|
||||
verbose_name=_("Max execution time"),
|
||||
|
||||
class PHPMaginQuotesSybase(AppOption):
|
||||
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).")
|
||||
regex=r'^(On|Off|on|off)$'
|
||||
group = AppOption.PHP
|
||||
|
||||
|
||||
class PHPMaxExecutonTime(AppOption):
|
||||
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 "
|
||||
"the parser (Integer between 0 and 999)."),
|
||||
"the parser (Integer between 0 and 999).")
|
||||
regex=r'^[0-9]{1,3}$'
|
||||
)
|
||||
group = AppOption.PHP
|
||||
|
||||
php_max_input_time = AppOption('PHP-max_input_time',
|
||||
verbose_name=_("Max input time"),
|
||||
|
||||
class PHPMaxInputTime(AppOption):
|
||||
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 "
|
||||
"(Integer between 0 and 999)."),
|
||||
"(Integer between 0 and 999).")
|
||||
regex=r'^[0-9]{1,3}$'
|
||||
)
|
||||
group = AppOption.PHP
|
||||
|
||||
php_max_input_vars = AppOption('PHP-max_input_vars',
|
||||
verbose_name=_("Max input vars"),
|
||||
|
||||
class PHPMaxInputVars(AppOption):
|
||||
name = 'max_input_vars'
|
||||
verbose_name=_("Max input vars")
|
||||
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}$'
|
||||
)
|
||||
group = AppOption.PHP
|
||||
|
||||
php_memory_limit = AppOption('PHP-memory_limit',
|
||||
verbose_name=_("Memory limit"),
|
||||
|
||||
class PHPMemoryLimit(AppOption):
|
||||
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 "
|
||||
"(Value between 0M and 999M)."),
|
||||
"(Value between 0M and 999M).")
|
||||
regex=r'^[0-9]{1,3}M$'
|
||||
)
|
||||
group = AppOption.PHP
|
||||
|
||||
php_mysql_connect_timeout = AppOption('PHP-mysql.connect_timeout',
|
||||
verbose_name=_("Mysql connect timeout"),
|
||||
help_text=_("Number between 0 and 999."),
|
||||
|
||||
class PHPMySQLConnectTimeout(AppOption):
|
||||
name = 'mysql.connect_timeout'
|
||||
verbose_name=_("Mysql connect timeout")
|
||||
help_text=_("Number between 0 and 999.")
|
||||
regex=r'^([0-9]){1,3}$'
|
||||
)
|
||||
group = AppOption.PHP
|
||||
|
||||
php_output_buffering = AppOption('PHP-output_buffering',
|
||||
verbose_name=_("Output buffering"),
|
||||
help_text=_("Turn on output buffering (On or Off)."),
|
||||
|
||||
class PHPOutputBuffering(AppOption):
|
||||
name = 'output_buffering'
|
||||
verbose_name=_("Output buffering")
|
||||
help_text=_("Turn on output buffering (On or Off).")
|
||||
regex=r'^(On|Off|on|off)$'
|
||||
)
|
||||
group = AppOption.PHP
|
||||
|
||||
php_register_globals = AppOption('PHP-register_globals',
|
||||
verbose_name=_("Register globals"),
|
||||
|
||||
class PHPRegisterGlobals(AppOption):
|
||||
name = 'register_globals'
|
||||
verbose_name=_("Register globals")
|
||||
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)$'
|
||||
)
|
||||
group = AppOption.PHP
|
||||
|
||||
php_post_max_size = AppOption('PHP-post_max_size',
|
||||
verbose_name=_("Post max size"),
|
||||
help_text=_("Sets max size of post data allowed (Value between 0M and 999M)."),
|
||||
|
||||
class PHPPostMaxSize(AppOption):
|
||||
name = 'post_max_size'
|
||||
verbose_name=_("Post max size")
|
||||
help_text=_("Sets max size of post data allowed (Value between 0M and 999M).")
|
||||
regex=r'^[0-9]{1,3}M$'
|
||||
)
|
||||
group = AppOption.PHP
|
||||
|
||||
php_sendmail_path = AppOption('PHP-sendmail_path',
|
||||
verbose_name=_("sendmail_path"),
|
||||
help_text=_("Where the sendmail program can be found."),
|
||||
|
||||
class PHPSendmailPath(AppOption):
|
||||
name = 'sendmail_path'
|
||||
verbose_name=_("sendmail_path")
|
||||
help_text=_("Where the sendmail program can be found.")
|
||||
regex=r'^[^ ]+$'
|
||||
)
|
||||
group = AppOption.PHP
|
||||
|
||||
php_session_bug_compat_warn = AppOption('PHP-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)."),
|
||||
|
||||
class PHPSessionBugCompatWarn(AppOption):
|
||||
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).")
|
||||
regex=r'^(On|Off|on|off)$'
|
||||
)
|
||||
group = AppOption.PHP
|
||||
|
||||
php_session_auto_start = AppOption('PHP-session.auto_start',
|
||||
verbose_name=_("session.auto_start"),
|
||||
|
||||
class PHPSessionAutoStart(AppOption):
|
||||
name = 'session.auto_start',
|
||||
verbose_name=_("session.auto_start")
|
||||
help_text=_("Specifies whether the session module starts a session automatically on request "
|
||||
"startup (On or Off)."),
|
||||
"startup (On or Off).")
|
||||
regex=r'^(On|Off|on|off)$'
|
||||
group = AppOption.PHP
|
||||
|
||||
|
||||
class PHPSafeMode(AppOption):
|
||||
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>")
|
||||
regex=r'^(On|Off|on|off)$'
|
||||
)
|
||||
php_safe_mode = AppOption('PHP-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>"),
|
||||
regex=r'^(On|Off|on|off)$',
|
||||
deprecated=5.3
|
||||
)
|
||||
php_suhosin_post_max_vars = AppOption('PHP-suhosin.post.max_vars',
|
||||
verbose_name=_("Suhosin POST max vars"),
|
||||
help_text=_("Number between 0 and 9999."),
|
||||
group = AppOption.PHP
|
||||
|
||||
|
||||
class PHPSuhosinPostMaxVars(AppOption):
|
||||
name = 'suhosin.post.max_vars',
|
||||
verbose_name=_("Suhosin POST max vars")
|
||||
help_text=_("Number between 0 and 9999.")
|
||||
regex=r'^[0-9]{1,4}$'
|
||||
)
|
||||
php_suhosin_get_max_vars = AppOption('PHP-suhosin.get.max_vars',
|
||||
verbose_name=_("Suhosin GET max vars"),
|
||||
help_text=_("Number between 0 and 9999."),
|
||||
group = AppOption.PHP
|
||||
|
||||
|
||||
class PHPSuhosinGetMaxVars(AppOption):
|
||||
name = 'suhosin.get.max_vars'
|
||||
verbose_name=_("Suhosin GET max vars")
|
||||
help_text=_("Number between 0 and 9999.")
|
||||
regex=r'^[0-9]{1,4}$'
|
||||
)
|
||||
php_suhosin_request_max_vars = AppOption('PHP-suhosin.request.max_vars',
|
||||
verbose_name=_("Suhosin request max vars"),
|
||||
help_text=_("Number between 0 and 9999."),
|
||||
group = AppOption.PHP
|
||||
|
||||
|
||||
class PHPSuhosinRequestMaxVars(AppOption):
|
||||
name = 'suhosin.request.max_vars'
|
||||
verbose_name=_("Suhosin request max vars")
|
||||
help_text=_("Number between 0 and 9999.")
|
||||
regex=r'^[0-9]{1,4}$'
|
||||
)
|
||||
php_suhosin_session_encrypt = AppOption('PHP-suhosin.session.encrypt',
|
||||
verbose_name=_("suhosin.session.encrypt"),
|
||||
help_text=_("On or Off"),
|
||||
group = AppOption.PHP
|
||||
|
||||
|
||||
class PHPSuhosinSessionEncrypt(AppOption):
|
||||
name = 'suhosin.session.encrypt'
|
||||
verbose_name=_("suhosin.session.encrypt")
|
||||
help_text=_("On or Off")
|
||||
regex=r'^(On|Off|on|off)$'
|
||||
)
|
||||
php_suhosin_simulation = AppOption('PHP-suhosin.simulation',
|
||||
verbose_name=_("Suhosin simulation"),
|
||||
help_text=_("On or Off"),
|
||||
group = AppOption.PHP
|
||||
|
||||
|
||||
class PHPSuhosinSimulation(AppOption):
|
||||
name = 'suhosin.simulation'
|
||||
verbose_name=_("Suhosin simulation")
|
||||
help_text=_("On or Off")
|
||||
regex=r'^(On|Off|on|off)$'
|
||||
)
|
||||
php_suhosin_executor_include_whitelist = AppOption('PHP-suhosin.executor.include.whitelist',
|
||||
verbose_name=_("suhosin.executor.include.whitelist"),
|
||||
group = AppOption.PHP
|
||||
|
||||
|
||||
class PHPSuhosinExecutorIncludeWhitelist(AppOption):
|
||||
name = 'suhosin.executor.include.whitelist'
|
||||
verbose_name=_("suhosin.executor.include.whitelist")
|
||||
regex=r'.*$'
|
||||
)
|
||||
php_upload_max_filesize = AppOption('PHP-upload_max_filesize',
|
||||
verbose_name=_("upload_max_filesize"),
|
||||
help_text=_("Value between 0M and 999M."),
|
||||
group = AppOption.PHP
|
||||
|
||||
|
||||
class PHPUploadMaxFileSize(AppOption):
|
||||
name = 'upload_max_filesize',
|
||||
verbose_name=_("upload_max_filesize")
|
||||
help_text=_("Value between 0M and 999M.")
|
||||
regex=r'^[0-9]{1,3}M$'
|
||||
)
|
||||
php_zend_extension = AppOption('PHP-post_max_size',
|
||||
verbose_name=_("zend_extension"),
|
||||
group = AppOption.PHP
|
||||
|
||||
|
||||
class PHPPostMaxSize(AppOption):
|
||||
name = 'post_max_size'
|
||||
verbose_name=_("zend_extension")
|
||||
regex=r'^[^ ]+$'
|
||||
)
|
||||
|
||||
|
||||
filesystem = [
|
||||
public_root,
|
||||
]
|
||||
|
||||
process = [
|
||||
timeout,
|
||||
processes,
|
||||
]
|
||||
|
||||
php = [
|
||||
php_enabled_functions,
|
||||
php_allow_url_include,
|
||||
php_allow_url_fopen,
|
||||
php_auto_append_file,
|
||||
php_auto_prepend_file,
|
||||
php_date_timezone,
|
||||
php_default_socket_timeout,
|
||||
php_display_errors,
|
||||
php_extension,
|
||||
php_magic_quotes_gpc,
|
||||
php_magic_quotes_runtime,
|
||||
php_magic_quotes_sybase,
|
||||
php_max_execution_time,
|
||||
php_max_input_time,
|
||||
php_max_input_vars,
|
||||
php_memory_limit,
|
||||
php_mysql_connect_timeout,
|
||||
php_output_buffering,
|
||||
php_register_globals,
|
||||
php_post_max_size,
|
||||
php_sendmail_path,
|
||||
php_session_bug_compat_warn,
|
||||
php_session_auto_start,
|
||||
php_safe_mode,
|
||||
php_suhosin_post_max_vars,
|
||||
php_suhosin_get_max_vars,
|
||||
php_suhosin_request_max_vars,
|
||||
php_suhosin_session_encrypt,
|
||||
php_suhosin_simulation,
|
||||
php_suhosin_executor_include_whitelist,
|
||||
php_upload_max_filesize,
|
||||
php_zend_extension,
|
||||
]
|
||||
|
||||
|
||||
_enabled = None
|
||||
|
||||
def get_enabled():
|
||||
global _enabled
|
||||
if _enabled is None:
|
||||
from . import settings
|
||||
_enabled = {}
|
||||
for op in settings.WEBAPPS_ENABLED_OPTIONS:
|
||||
op = import_class(op)
|
||||
_enabled[op.name] = op
|
||||
return _enabled
|
||||
group = AppOption.PHP
|
||||
|
|
|
@ -2,29 +2,30 @@ from django.conf import settings
|
|||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
|
||||
WEBAPPS_BASE_ROOT = getattr(settings, 'WEBAPPS_BASE_ROOT', '%(home)s/webapps/%(app_name)s/')
|
||||
WEBAPPS_BASE_ROOT = getattr(settings, 'WEBAPPS_BASE_ROOT', '{home}/webapps/{app_name}/')
|
||||
|
||||
|
||||
|
||||
WEBAPPS_FPM_LISTEN = getattr(settings, 'WEBAPPS_FPM_LISTEN',
|
||||
# '/var/run/%(user)s-%(app_name)s.sock')
|
||||
'127.0.0.1:%(fpm_port)s')
|
||||
|
||||
# '127.0.0.1:9{app_id:03d}
|
||||
'/opt/php/5.4/socks/{user}-{app_name}.sock'
|
||||
)
|
||||
|
||||
WEBAPPS_FPM_START_PORT = getattr(settings, 'WEBAPPS_FPM_START_PORT', 10000)
|
||||
|
||||
|
||||
WEBAPPS_PHPFPM_POOL_PATH = getattr(settings, 'WEBAPPS_PHPFPM_POOL_PATH',
|
||||
'/etc/php5/fpm/pool.d/%(user)s-%(app_name)s.conf')
|
||||
'/etc/php5/fpm/pool.d/{user}-{app_name}.conf')
|
||||
|
||||
|
||||
WEBAPPS_FCGID_PATH = getattr(settings, 'WEBAPPS_FCGID_PATH',
|
||||
'/home/httpd/fcgid/%(user)s/%(app_name)s-wrapper')
|
||||
'/home/httpd/fcgid/{user}/{app_name}-wrapper')
|
||||
|
||||
|
||||
WEBAPPS_TYPES = getattr(settings, 'WEBAPPS_TYPES', (
|
||||
'orchestra.apps.webapps.types.Php55App',
|
||||
'orchestra.apps.webapps.types.Php52App',
|
||||
'orchestra.apps.webapps.types.Php4App',
|
||||
'orchestra.apps.webapps.types.PHP54App',
|
||||
'orchestra.apps.webapps.types.PHP52App',
|
||||
'orchestra.apps.webapps.types.PHP4App',
|
||||
'orchestra.apps.webapps.types.StaticApp',
|
||||
'orchestra.apps.webapps.types.WebalizerApp',
|
||||
'orchestra.apps.webapps.types.WordPressMuApp',
|
||||
|
@ -35,6 +36,7 @@ WEBAPPS_TYPES = getattr(settings, 'WEBAPPS_TYPES', (
|
|||
))
|
||||
|
||||
|
||||
|
||||
#WEBAPPS_TYPES_OVERRIDE = getattr(settings, 'WEBAPPS_TYPES_OVERRIDE', {})
|
||||
#for webapp_type, value in WEBAPPS_TYPES_OVERRIDE.iteritems():
|
||||
# if value is None:
|
||||
|
@ -76,50 +78,55 @@ WEBAPPS_PHP_DISABLED_FUNCTIONS = getattr(settings, 'WEBAPPS_PHP_DISABLED_FUNCTIO
|
|||
|
||||
|
||||
WEBAPPS_ENABLED_OPTIONS = getattr(settings, 'WEBAPPS_ENABLED_OPTIONS', (
|
||||
'orchestra.apps.webapps.options.public_root',
|
||||
'orchestra.apps.webapps.options.timeout',
|
||||
'orchestra.apps.webapps.options.processes',
|
||||
'orchestra.apps.webapps.options.php_enabled_functions',
|
||||
'orchestra.apps.webapps.options.php_allow_url_include',
|
||||
'orchestra.apps.webapps.options.php_allow_url_fopen',
|
||||
'orchestra.apps.webapps.options.php_auto_append_file',
|
||||
'orchestra.apps.webapps.options.php_auto_prepend_file',
|
||||
'orchestra.apps.webapps.options.php_date_timezone',
|
||||
'orchestra.apps.webapps.options.php_default_socket_timeout',
|
||||
'orchestra.apps.webapps.options.php_display_errors',
|
||||
'orchestra.apps.webapps.options.php_extension',
|
||||
'orchestra.apps.webapps.options.php_magic_quotes_gpc',
|
||||
'orchestra.apps.webapps.options.php_magic_quotes_runtime',
|
||||
'orchestra.apps.webapps.options.php_magic_quotes_sybase',
|
||||
'orchestra.apps.webapps.options.php_max_execution_time',
|
||||
'orchestra.apps.webapps.options.php_max_input_time',
|
||||
'orchestra.apps.webapps.options.php_max_input_vars',
|
||||
'orchestra.apps.webapps.options.php_memory_limit',
|
||||
'orchestra.apps.webapps.options.php_mysql_connect_timeout',
|
||||
'orchestra.apps.webapps.options.php_output_buffering',
|
||||
'orchestra.apps.webapps.options.php_register_globals',
|
||||
'orchestra.apps.webapps.options.php_post_max_size',
|
||||
'orchestra.apps.webapps.options.php_sendmail_path',
|
||||
'orchestra.apps.webapps.options.php_session_bug_compat_warn',
|
||||
'orchestra.apps.webapps.options.php_session_auto_start',
|
||||
'orchestra.apps.webapps.options.php_safe_mode',
|
||||
'orchestra.apps.webapps.options.php_suhosin_post_max_vars',
|
||||
'orchestra.apps.webapps.options.php_suhosin_get_max_vars',
|
||||
'orchestra.apps.webapps.options.php_suhosin_request_max_vars',
|
||||
'orchestra.apps.webapps.options.php_suhosin_session_encrypt',
|
||||
'orchestra.apps.webapps.options.php_suhosin_simulation',
|
||||
'orchestra.apps.webapps.options.php_suhosin_executor_include_whitelist',
|
||||
'orchestra.apps.webapps.options.php_upload_max_filesize',
|
||||
'orchestra.apps.webapps.options.php_zend_extension',
|
||||
'orchestra.apps.webapps.options.PublicRoot',
|
||||
'orchestra.apps.webapps.options.DirectoryProtection',
|
||||
'orchestra.apps.webapps.options.Timeout',
|
||||
'orchestra.apps.webapps.options.Processes',
|
||||
'orchestra.apps.webapps.options.PHPEnabledFunctions',
|
||||
'orchestra.apps.webapps.options.PHPAllowURLInclude',
|
||||
'orchestra.apps.webapps.options.PHPAllowURLFopen',
|
||||
'orchestra.apps.webapps.options.PHPAutoAppendFile',
|
||||
'orchestra.apps.webapps.options.PHPAutoPrependFile',
|
||||
'orchestra.apps.webapps.options.PHPDateTimeZone',
|
||||
'orchestra.apps.webapps.options.PHPDefaultSocketTimeout',
|
||||
'orchestra.apps.webapps.options.PHPDisplayErrors',
|
||||
'orchestra.apps.webapps.options.PHPExtension',
|
||||
'orchestra.apps.webapps.options.PHPMagicQuotesGPC',
|
||||
'orchestra.apps.webapps.options.PHPMagicQuotesRuntime',
|
||||
'orchestra.apps.webapps.options.PHPMaginQuotesSybase',
|
||||
'orchestra.apps.webapps.options.PHPMaxExecutonTime',
|
||||
'orchestra.apps.webapps.options.PHPMaxInputTime',
|
||||
'orchestra.apps.webapps.options.PHPMaxInputVars',
|
||||
'orchestra.apps.webapps.options.PHPMemoryLimit',
|
||||
'orchestra.apps.webapps.options.PHPMySQLConnectTimeout',
|
||||
'orchestra.apps.webapps.options.PHPOutputBuffering',
|
||||
'orchestra.apps.webapps.options.PHPRegisterGlobals',
|
||||
'orchestra.apps.webapps.options.PHPPostMaxSize',
|
||||
'orchestra.apps.webapps.options.PHPSendmailPath',
|
||||
'orchestra.apps.webapps.options.PHPSessionBugCompatWarn',
|
||||
'orchestra.apps.webapps.options.PHPSessionAutoStart',
|
||||
'orchestra.apps.webapps.options.PHPSafeMode',
|
||||
'orchestra.apps.webapps.options.PHPSuhosinPostMaxVars',
|
||||
'orchestra.apps.webapps.options.PHPSuhosinGetMaxVars',
|
||||
'orchestra.apps.webapps.options.PHPSuhosinRequestMaxVars',
|
||||
'orchestra.apps.webapps.options.PHPSuhosinSessionEncrypt',
|
||||
'orchestra.apps.webapps.options.PHPSuhosinSimulation',
|
||||
'orchestra.apps.webapps.options.PHPSuhosinExecutorIncludeWhitelist',
|
||||
'orchestra.apps.webapps.options.PHPUploadMaxFileSize',
|
||||
'orchestra.apps.webapps.options.PHPPostMaxSize',
|
||||
))
|
||||
|
||||
|
||||
WEBAPPS_WORDPRESSMU_ADMIN_PASSWORD = getattr(settings, 'WEBAPPS_WORDPRESSMU_ADMIN_PASSWORD',
|
||||
'secret')
|
||||
|
||||
WEBAPPS_WORDPRESSMU_BASE_URL = getattr(settings, 'WEBAPPS_WORDPRESSMU_BASE_URL',
|
||||
WEBAPPS_WORDPRESSMU_BASE_URL = getattr(settings, 'WEBAPPS_WORDPRESSMU_BASE_URL',
|
||||
'http://blogs.orchestra.lan/')
|
||||
|
||||
WEBAPPS_WORDPRESSMU_LISTEN = getattr(settings, 'WEBAPPS_WORDPRESSMU_LISTEN',
|
||||
'/opt/php/5.4/socks/wordpress-mu.sock'
|
||||
)
|
||||
|
||||
|
||||
WEBAPPS_DOKUWIKIMU_TEMPLATE_PATH = getattr(settings, 'WEBAPPS_DOKUWIKIMU_TEMPLATE_PATH',
|
||||
'/home/httpd/htdocs/wikifarm/template.tar.gz')
|
||||
|
@ -127,9 +134,22 @@ WEBAPPS_DOKUWIKIMU_TEMPLATE_PATH = getattr(settings, 'WEBAPPS_DOKUWIKIMU_TEMPLAT
|
|||
WEBAPPS_DOKUWIKIMU_FARM_PATH = getattr(settings, 'WEBAPPS_DOKUWIKIMU_FARM_PATH',
|
||||
'/home/httpd/htdocs/wikifarm/farm')
|
||||
|
||||
WEBAPPS_DOKUWIKIMU_LISTEN = getattr(settings, 'WEBAPPS_DOKUWIKIMU_LISTEN',
|
||||
'/opt/php/5.4/socks/dokuwiki-mu.sock'
|
||||
)
|
||||
|
||||
WEBAPPS_DRUPAL_SITES_PATH = getattr(settings, 'WEBAPPS_DRUPAL_SITES_PATH',
|
||||
'/home/httpd/htdocs/drupal-mu/sites/%(site_name)s')
|
||||
|
||||
WEBAPPS_DRUPALMU_SITES_PATH = getattr(settings, 'WEBAPPS_DRUPALMU_SITES_PATH',
|
||||
'/home/httpd/htdocs/drupal-mu/sites/{site_name}')
|
||||
|
||||
WEBAPPS_DRUPALMU_LISTEN = getattr(settings, 'WEBAPPS_DRUPALMU_LISTEN',
|
||||
'/opt/php/5.4/socks/drupal-mu.sock'
|
||||
)
|
||||
|
||||
|
||||
WEBAPPS_MOODLEMU_LISTEN = getattr(settings, 'WEBAPPS_MOODLEMU_LISTEN',
|
||||
'/opt/php/5.4/socks/moodle-mu.sock'
|
||||
)
|
||||
|
||||
|
||||
WEBAPPS_DEFAULT_MYSQL_DATABASE_HOST = getattr(settings, 'WEBAPPS_DEFAULT_MYSQL_DATABASE_HOST',
|
||||
|
|
|
@ -11,6 +11,7 @@ from orchestra.utils.functional import cached
|
|||
from orchestra.utils.python import import_class
|
||||
|
||||
from . import options, settings
|
||||
from .options import AppOption
|
||||
|
||||
|
||||
class AppType(plugins.Plugin):
|
||||
|
@ -22,11 +23,7 @@ class AppType(plugins.Plugin):
|
|||
serializer = None
|
||||
icon = 'orchestra/icons/apps.png'
|
||||
unique_name = False
|
||||
options = (
|
||||
('Process', options.process),
|
||||
('PHP', options.php),
|
||||
('File system', options.filesystem),
|
||||
)
|
||||
option_groups = (AppOption.FILESYSTEM, AppOption.PROCESS, AppOption.PHP)
|
||||
|
||||
@classmethod
|
||||
@cached
|
||||
|
@ -36,16 +33,18 @@ class AppType(plugins.Plugin):
|
|||
plugins.append(import_class(cls))
|
||||
return plugins
|
||||
|
||||
@classmethod
|
||||
def clean_data(cls, webapp):
|
||||
def clean_data(self, webapp):
|
||||
""" model clean, uses cls.serizlier by default """
|
||||
if cls.serializer:
|
||||
serializer = cls.serializer(data=webapp.data)
|
||||
if self.serializer:
|
||||
serializer = self.serializer(data=webapp.data)
|
||||
if not serializer.is_valid():
|
||||
raise ValidationError(serializer.errors)
|
||||
return serializer.data
|
||||
return {}
|
||||
|
||||
def get_directive(self, webapp):
|
||||
return ('static', webapp.get_path())
|
||||
|
||||
def get_form(self):
|
||||
self.form.plugin = self
|
||||
self.form.plugin_field = 'type'
|
||||
|
@ -69,19 +68,40 @@ class AppType(plugins.Plugin):
|
|||
'name': _("A WordPress blog with this name already exists."),
|
||||
})
|
||||
|
||||
def get_options(self):
|
||||
pass
|
||||
@classmethod
|
||||
@cached
|
||||
def get_php_options(cls):
|
||||
php_version = getattr(cls, 'php_version', 1)
|
||||
php_options = AppOption.get_option_groups()[AppOption.PHP]
|
||||
return [op for op in php_options if getattr(cls, 'deprecated', 99) > php_version]
|
||||
|
||||
@classmethod
|
||||
@cached
|
||||
def get_options(cls):
|
||||
""" Get enabled options based on cls.option_groups """
|
||||
groups = AppOption.get_option_groups()
|
||||
options = []
|
||||
for group in cls.option_groups:
|
||||
group_options = groups[group]
|
||||
if group == AppOption.PHP:
|
||||
group_options = cls.get_php_options()
|
||||
if group is None:
|
||||
options.insert(0, (group, group_options))
|
||||
else:
|
||||
options.append((group, group_options))
|
||||
return options
|
||||
|
||||
@classmethod
|
||||
def get_options_choices(cls):
|
||||
enabled = options.get_enabled().values()
|
||||
""" Generates grouped choices ready to use in Field.choices """
|
||||
# generators can not be @cached
|
||||
yield (None, '-------')
|
||||
for option in cls.options:
|
||||
if hasattr(option, '__iter__'):
|
||||
yield (option[0], [(op.name, op.verbose_name) for op in option[1] if op in enabled])
|
||||
elif option in enabled:
|
||||
yield (option.name, option.verbose_name)
|
||||
|
||||
for group, options in cls.get_options():
|
||||
if group is None:
|
||||
for option in options:
|
||||
yield (option.name, option.verbose_name)
|
||||
else:
|
||||
yield (group, [(op.name, op.verbose_name) for op in options])
|
||||
|
||||
def save(self, instance):
|
||||
pass
|
||||
|
@ -91,36 +111,84 @@ class AppType(plugins.Plugin):
|
|||
|
||||
def get_related_objects(self, instance):
|
||||
pass
|
||||
|
||||
def get_directive_context(self, webapp):
|
||||
return {
|
||||
'app_id': webapp.id,
|
||||
'app_name': webapp.name,
|
||||
'user': webapp.account.username,
|
||||
}
|
||||
|
||||
|
||||
class Php55App(AppType):
|
||||
name = 'php5.5-fpm'
|
||||
verbose_name = "PHP 5.5 FPM"
|
||||
# 'fpm', ('unix:/var/run/%(user)s-%(app_name)s.sock|fcgi://127.0.0.1%(app_path)s',),
|
||||
directive = ('fpm', 'fcgi://{}%(app_path)s'.format(settings.WEBAPPS_FPM_LISTEN))
|
||||
class PHPAppType(AppType):
|
||||
php_version = 5.4
|
||||
fpm_listen = settings.WEBAPPS_FPM_LISTEN
|
||||
|
||||
def get_directive(self, webapp):
|
||||
context = self.get_directive_context(webapp)
|
||||
socket_type = 'unix'
|
||||
if ':' in self.fpm_listen:
|
||||
socket_type = 'tcp'
|
||||
socket = self.fpm_listen.format(context)
|
||||
return ('fpm', socket_type, socket, webapp.get_path())
|
||||
|
||||
def get_php_init_vars(self, webapp, per_account=False):
|
||||
"""
|
||||
process php options for inclusion on php.ini
|
||||
per_account=True merges all (account, webapp.type) options
|
||||
"""
|
||||
init_vars = []
|
||||
php_options = type(self).get_php_options()
|
||||
options = webapp.options.all()
|
||||
if per_account:
|
||||
options = webapp.account.webapps.filter(webapp_type=webapp.type)
|
||||
php_options = [option.name for option in php_options]
|
||||
for opt in options:
|
||||
if opt.option_class in php_options:
|
||||
init_vars.append(
|
||||
(opt.name, opt.value)
|
||||
)
|
||||
enabled_functions = []
|
||||
for value in options.filter(name='enabled_functions').values_list('value', flat=True):
|
||||
enabled_functions += enabled_functions.get().value.split(',')
|
||||
if enabled_functions:
|
||||
disabled_functions = []
|
||||
for function in settings.WEBAPPS_PHP_DISABLED_FUNCTIONS:
|
||||
if function not in enabled_functions:
|
||||
disabled_functions.append(function)
|
||||
init_vars.append(
|
||||
('dissabled_functions', ','.join(disabled_functions))
|
||||
)
|
||||
return init_vars
|
||||
|
||||
|
||||
class PHP54App(PHPAppType):
|
||||
name = 'php5.4-fpm'
|
||||
php_version = 5.4
|
||||
verbose_name = "PHP 5.4 FPM"
|
||||
help_text = _("This creates a PHP5.5 application under ~/webapps/<app_name><br>"
|
||||
"PHP-FPM will be used to execute PHP files.")
|
||||
options = (
|
||||
('Process', options.process),
|
||||
('PHP', [op for op in options.php if getattr(op, 'deprecated', 99) > 5.5]),
|
||||
('File system', options.filesystem),
|
||||
)
|
||||
icon = 'orchestra/icons/apps/PHPFPM.png'
|
||||
|
||||
|
||||
class Php52App(AppType):
|
||||
name = 'php5.2-fcgi'
|
||||
verbose_name = "PHP 5.2 FCGI"
|
||||
directive = ('fcgi', settings.WEBAPPS_FCGID_PATH)
|
||||
class PHP52App(PHPAppType):
|
||||
name = 'php5.2-fcgid'
|
||||
php_version = 5.2
|
||||
verbose_name = "PHP 5.2 FCGID"
|
||||
help_text = _("This creates a PHP5.2 application under ~/webapps/<app_name><br>"
|
||||
"Apache-mod-fcgid will be used to execute PHP files.")
|
||||
icon = 'orchestra/icons/apps/PHPFCGI.png'
|
||||
|
||||
def get_directive(self, webapp):
|
||||
context = self.get_directive_context(webapp)
|
||||
wrapper_path = settings.WEBAPPS_FCGID_PATH.format(context)
|
||||
return ('fcgi', webapp.get_path(), wrapper_path)
|
||||
|
||||
|
||||
class Php4App(AppType):
|
||||
name = 'php4-fcgi'
|
||||
verbose_name = "PHP 4 FCGI"
|
||||
directive = ('fcgi', settings.WEBAPPS_FCGID_PATH)
|
||||
class PHP4App(PHP52App):
|
||||
name = 'php4-fcgid'
|
||||
php_version = 4
|
||||
verbose_name = "PHP 4 FCGID"
|
||||
help_text = _("This creates a PHP4 application under ~/webapps/<app_name><br>"
|
||||
"Apache-mod-fcgid will be used to execute PHP files.")
|
||||
icon = 'orchestra/icons/apps/PHPFCGI.png'
|
||||
|
@ -129,13 +197,11 @@ class Php4App(AppType):
|
|||
class StaticApp(AppType):
|
||||
name = 'static'
|
||||
verbose_name = "Static"
|
||||
directive = ('static',)
|
||||
help_text = _("This creates a Static application under ~/webapps/<app_name><br>"
|
||||
"Apache2 will be used to serve static content and execute CGI files.")
|
||||
icon = 'orchestra/icons/apps/Static.png'
|
||||
options = (
|
||||
('File system', options.filesystem),
|
||||
)
|
||||
option_groups = (AppOption.FILESYSTEM,)
|
||||
|
||||
|
||||
class WebalizerApp(AppType):
|
||||
name = 'webalizer'
|
||||
|
@ -144,10 +210,13 @@ class WebalizerApp(AppType):
|
|||
help_text = _("This creates a Webalizer application under "
|
||||
"~/webapps/<app_name>-<site_name>")
|
||||
icon = 'orchestra/icons/apps/Stats.png'
|
||||
options = ()
|
||||
option_groups = ()
|
||||
|
||||
def get_directive(self, webapp):
|
||||
return ('static', webapp.get_path())
|
||||
|
||||
|
||||
class WordPressMuApp(AppType):
|
||||
class WordPressMuApp(PHPAppType):
|
||||
name = 'wordpress-mu'
|
||||
verbose_name = "WordPress (SaaS)"
|
||||
directive = ('fpm', 'fcgi://127.0.0.1:8990/home/httpd/wordpress-mu/')
|
||||
|
@ -155,10 +224,11 @@ class WordPressMuApp(AppType):
|
|||
"By default this blog is accessible via <app_name>.blogs.orchestra.lan")
|
||||
icon = 'orchestra/icons/apps/WordPressMu.png'
|
||||
unique_name = True
|
||||
options = ()
|
||||
option_groups = ()
|
||||
fpm_listen = settings.WEBAPPS_WORDPRESSMU_LISTEN
|
||||
|
||||
|
||||
class DokuWikiMuApp(AppType):
|
||||
class DokuWikiMuApp(PHPAppType):
|
||||
name = 'dokuwiki-mu'
|
||||
verbose_name = "DokuWiki (SaaS)"
|
||||
directive = ('alias', '/home/httpd/wikifarm/farm/')
|
||||
|
@ -166,10 +236,11 @@ class DokuWikiMuApp(AppType):
|
|||
"By default this wiki is accessible via <app_name>.wikis.orchestra.lan")
|
||||
icon = 'orchestra/icons/apps/DokuWikiMu.png'
|
||||
unique_name = True
|
||||
options = ()
|
||||
option_groups = ()
|
||||
fpm_listen = settings.WEBAPPS_DOKUWIKIMU_LISTEN
|
||||
|
||||
|
||||
class MoodleMuApp(AppType):
|
||||
class MoodleMuApp(PHPAppType):
|
||||
name = 'moodle-mu'
|
||||
verbose_name = "Moodle (SaaS)"
|
||||
directive = ('alias', '/home/httpd/wikifarm/farm/')
|
||||
|
@ -177,10 +248,11 @@ class MoodleMuApp(AppType):
|
|||
"By default this wiki is accessible via <app_name>.moodle.orchestra.lan")
|
||||
icon = 'orchestra/icons/apps/MoodleMu.png'
|
||||
unique_name = True
|
||||
options = ()
|
||||
option_groups = ()
|
||||
fpm_listen = settings.WEBAPPS_MOODLEMU_LISTEN
|
||||
|
||||
|
||||
class DrupalMuApp(AppType):
|
||||
class DrupalMuApp(PHPAppType):
|
||||
name = 'drupal-mu'
|
||||
verbose_name = "Drupdal (SaaS)"
|
||||
directive = ('fpm', 'fcgi://127.0.0.1:8991/home/httpd/drupal-mu/')
|
||||
|
@ -190,7 +262,8 @@ class DrupalMuApp(AppType):
|
|||
"By default this site will be accessible via <app_name>.drupal.orchestra.lan")
|
||||
icon = 'orchestra/icons/apps/DrupalMu.png'
|
||||
unique_name = True
|
||||
options = ()
|
||||
option_groups = ()
|
||||
fpm_listen = settings.WEBAPPS_DRUPALMU_LISTEN
|
||||
|
||||
|
||||
from rest_framework import serializers
|
||||
|
@ -204,7 +277,7 @@ class SymbolicLinkSerializer(serializers.Serializer):
|
|||
path = serializers.CharField(label=_("Path"))
|
||||
|
||||
|
||||
class SymbolicLinkApp(AppType):
|
||||
class SymbolicLinkApp(PHPAppType):
|
||||
name = 'symbolic-link'
|
||||
verbose_name = "Symbolic link"
|
||||
form = SymbolicLinkForm
|
||||
|
@ -231,7 +304,7 @@ from orchestra.apps.databases.models import Database, DatabaseUser
|
|||
from orchestra.utils.python import random_ascii
|
||||
|
||||
|
||||
class WordPressApp(AppType):
|
||||
class WordPressApp(PHPAppType):
|
||||
name = 'wordpress'
|
||||
verbose_name = "WordPress"
|
||||
icon = 'orchestra/icons/apps/WordPress.png'
|
||||
|
|
|
@ -10,17 +10,18 @@ from orchestra.admin.utils import admin_link, change_url
|
|||
from orchestra.apps.accounts.admin import AccountAdminMixin, SelectAccountAdminMixin
|
||||
from orchestra.forms.widgets import DynamicHelpTextSelect
|
||||
|
||||
from . import settings, options
|
||||
from . import settings
|
||||
from .directives import SiteDirective
|
||||
from .forms import WebsiteAdminForm
|
||||
from .models import Content, Website, WebsiteOption
|
||||
from .models import Content, Website, Directive
|
||||
|
||||
|
||||
class WebsiteOptionInline(admin.TabularInline):
|
||||
model = WebsiteOption
|
||||
class DirectiveInline(admin.TabularInline):
|
||||
model = Directive
|
||||
extra = 1
|
||||
|
||||
OPTIONS_HELP_TEXT = {
|
||||
op.name: str(unicode(op.help_text)) for op in options.get_enabled().values()
|
||||
DIRECTIVES_HELP_TEXT = {
|
||||
op.name: str(unicode(op.help_text)) for op in SiteDirective.get_plugins()
|
||||
}
|
||||
|
||||
# class Media:
|
||||
|
@ -34,9 +35,9 @@ class WebsiteOptionInline(admin.TabularInline):
|
|||
if db_field.name == 'name':
|
||||
# Help text based on select widget
|
||||
kwargs['widget'] = DynamicHelpTextSelect(
|
||||
'this.id.replace("name", "value")', self.OPTIONS_HELP_TEXT
|
||||
'this.id.replace("name", "value")', self.DIECTIVES_HELP_TEXT
|
||||
)
|
||||
return super(WebsiteOptionInline, self).formfield_for_dbfield(db_field, **kwargs)
|
||||
return super(DirectiveInline, self).formfield_for_dbfield(db_field, **kwargs)
|
||||
|
||||
|
||||
class ContentInline(AccountAdminMixin, admin.TabularInline):
|
||||
|
@ -60,7 +61,7 @@ class WebsiteAdmin(SelectAccountAdminMixin, ExtendedModelAdmin):
|
|||
list_display = ('name', 'display_domains', 'display_webapps', 'account_link')
|
||||
list_filter = ('port', 'is_active')
|
||||
change_readonly_fields = ('name',)
|
||||
inlines = [ContentInline, WebsiteOptionInline]
|
||||
inlines = [ContentInline, DirectiveInline]
|
||||
filter_horizontal = ['domains']
|
||||
fieldsets = (
|
||||
(None, {
|
||||
|
@ -70,7 +71,7 @@ class WebsiteAdmin(SelectAccountAdminMixin, ExtendedModelAdmin):
|
|||
)
|
||||
form = WebsiteAdminForm
|
||||
filter_by_account_fields = ['domains']
|
||||
list_prefetch_related = ('domains', 'content_set__webapp')
|
||||
list_prefetch_related = ('domains', 'contents__webapp')
|
||||
search_fields = ('name', 'account__username', 'domains__name')
|
||||
|
||||
def display_domains(self, website):
|
||||
|
@ -85,7 +86,7 @@ class WebsiteAdmin(SelectAccountAdminMixin, ExtendedModelAdmin):
|
|||
|
||||
def display_webapps(self, website):
|
||||
webapps = []
|
||||
for content in website.content_set.all():
|
||||
for content in website.contents.all():
|
||||
webapp = content.webapp
|
||||
url = change_url(webapp)
|
||||
name = "%s on %s" % (webapp.get_type_display(), content.path)
|
||||
|
|
|
@ -68,36 +68,54 @@ class Apache2Backend(ServiceController):
|
|||
|
||||
def get_content_directives(self, site):
|
||||
directives = ''
|
||||
for content in site.content_set.all().order_by('-path'):
|
||||
method, args = content.webapp.get_directive()
|
||||
for content in site.contents.all().order_by('-path'):
|
||||
directive = content.webapp.get_directive()
|
||||
method, agrs = directive[0], directive[1:]
|
||||
method = getattr(self, 'get_%s_directives' % method)
|
||||
directives += method(content, *args)
|
||||
return directives
|
||||
|
||||
def get_static_directives(self, content, *args):
|
||||
def get_static_directives(self, content, app_path):
|
||||
context = self.get_content_context(content)
|
||||
context['path'] = args[0] % context if args else content.webapp.get_path()
|
||||
context['app_path'] = app_path
|
||||
return "Alias %(location)s %(path)s\n" % context
|
||||
|
||||
def get_fpm_directives(self, content, *args):
|
||||
def get_fpm_directives(self, content, socket_type, socket, app_path):
|
||||
if socket_type == 'unix':
|
||||
target = 'unix:%(socket)s|fcgi://127.0.0.1%(app_path)s/'
|
||||
if content.path != '/':
|
||||
target = 'unix:%(socket)s|fcgi://127.0.0.1%(app_path)s/$1'
|
||||
elif socket_type == 'tcp':
|
||||
target = 'fcgi://%(socket)s%(app_path)s/$1'
|
||||
else:
|
||||
raise TypeError("%s socket not supported." % socket_type)
|
||||
context = self.get_content_context(content)
|
||||
context['fcgi_path'] = args[0] % context
|
||||
directive = "ProxyPassMatch ^%(location)s(.*\.php(/.*)?)$ %(fcgi_path)s$1\n"
|
||||
return directive % context
|
||||
context.update({
|
||||
'app_path': app_path,
|
||||
'socket': socket,
|
||||
})
|
||||
return textwrap.dedent("""\
|
||||
ProxyPassMatch ^%(location)s/(.*\.php(/.*)?)$ {target}
|
||||
Alias %(location)s/ %(app_path)s/
|
||||
""".format(target=target) % context
|
||||
)
|
||||
|
||||
def get_fcgi_directives(self, content, fcgid_path):
|
||||
def get_fcgi_directives(self, content, app_path, wrapper_path):
|
||||
context = self.get_content_context(content)
|
||||
context['fcgid_path'] = fcgid_path % context
|
||||
fcgid = self.get_static_directives(content)
|
||||
fcgid += textwrap.dedent("""\
|
||||
context.update({
|
||||
'app_path': app_path,
|
||||
'wrapper_path': wrapper_path,
|
||||
})
|
||||
fcgid = textwrap.dedent("""\
|
||||
Alias %(location)s %(app_path)s
|
||||
ProxyPass %(location)s !
|
||||
<Directory %(app_path)s>
|
||||
Options +ExecCGI
|
||||
AddHandler fcgid-script .php
|
||||
FcgidWrapper %(fcgid_path)s\
|
||||
FcgidWrapper %(wrapper_path)s\
|
||||
""") % context
|
||||
for option in content.webapp.options.filter(name__startswith='Fcgid'):
|
||||
fcgid += " %s %s\n" % (option.name, option.value)
|
||||
fcgid += " %s %s\n" % (option.name, option.value)
|
||||
fcgid += "</Directory>\n"
|
||||
return fcgid
|
||||
|
||||
|
|
|
@ -24,17 +24,21 @@ class WebalizerBackend(ServiceController):
|
|||
))
|
||||
|
||||
def delete(self, content):
|
||||
pass
|
||||
# TODO delete has to be done on webapp deleteion, not content deletion
|
||||
# context = self.get_context(content)
|
||||
# self.append("rm -fr %(webalizer_path)s" % context)
|
||||
# self.append("rm %(webalizer_conf_path)s" % context)
|
||||
context = self.get_context(content)
|
||||
delete_webapp = not content.webapp.pk
|
||||
# TODO remove when confirmed that it works, otherwise create a second WebalizerBackend for WebApps
|
||||
if delete_webapp:
|
||||
self.append("mv %(webapp_path)s %(webapp_path)s.deleted" % context)
|
||||
if delete_webapp or not content.webapp.contents.filter(website=content.website).exists():
|
||||
self.append("mv %(webalizer_path)s %(webalizer_path)s.deleted" % context)
|
||||
self.append("rm %(webalizer_conf_path)s" % context)
|
||||
|
||||
def get_context(self, content):
|
||||
conf_file = "%s.conf" % content.website.unique_name
|
||||
context = {
|
||||
'site_logs': content.website.get_www_access_log_path(),
|
||||
'site_name': content.website.name,
|
||||
'webapp_path': content.webapp.get_path(),
|
||||
'webalizer_path': os.path.join(content.webapp.get_path(), content.website.name),
|
||||
'webalizer_conf_path': os.path.join(settings.WEBSITES_WEBALIZER_PATH, conf_file),
|
||||
'user': content.webapp.account.username,
|
||||
|
|
151
orchestra/apps/websites/directives.py
Normal file
151
orchestra/apps/websites/directives.py
Normal file
|
@ -0,0 +1,151 @@
|
|||
from django.core.exceptions import ValidationError
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from orchestra.plugins import Plugin
|
||||
from orchestra.utils.functional import cached
|
||||
from orchestra.utils.python import import_class
|
||||
|
||||
from . import settings
|
||||
|
||||
|
||||
# TODO multiple and unique validation support in the formset
|
||||
class SiteDirective(Plugin):
|
||||
HTTPD = 'httpd'
|
||||
SEC = 'sec'
|
||||
SSL = 'ssl'
|
||||
|
||||
help_text = ""
|
||||
unique = True
|
||||
|
||||
@classmethod
|
||||
@cached
|
||||
def get_plugins(cls):
|
||||
plugins = []
|
||||
for cls in settings.WEBSITES_ENABLED_DIRECTIVES:
|
||||
plugins.append(import_class(cls))
|
||||
return plugins
|
||||
|
||||
@classmethod
|
||||
@cached
|
||||
def get_option_groups(cls):
|
||||
groups = {}
|
||||
for opt in cls.get_plugins():
|
||||
try:
|
||||
groups[opt.group].append(opt)
|
||||
except KeyError:
|
||||
groups[opt.group] = [opt]
|
||||
return groups
|
||||
|
||||
@classmethod
|
||||
def get_plugin_choices(cls):
|
||||
""" Generates grouped choices ready to use in Field.choices """
|
||||
# generators can not be @cached
|
||||
yield (None, '-------')
|
||||
options = cls.get_option_groups()
|
||||
for option in options.pop(None, ()):
|
||||
yield (option.name, option.verbose_name)
|
||||
for group, options in options.iteritems():
|
||||
yield (group, [(op.name, op.verbose_name) for op in options])
|
||||
|
||||
def validate(self, website):
|
||||
if self.regex and not re.match(self.regex, website.value):
|
||||
raise ValidationError({
|
||||
'value': ValidationError(_("'%(value)s' does not match %(regex)s."),
|
||||
params={
|
||||
'value': website.value,
|
||||
'regex': self.regex
|
||||
}),
|
||||
})
|
||||
|
||||
|
||||
class Redirect(SiteDirective):
|
||||
name = 'redirect'
|
||||
verbose_name=_("Redirection")
|
||||
help_text = _("<tt><website path> <destination URL></tt>")
|
||||
regex = r'^[^ ]+\s[^ ]+$'
|
||||
group = SiteDirective.HTTPD
|
||||
|
||||
|
||||
class Proxy(SiteDirective):
|
||||
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
|
||||
|
||||
|
||||
class UserGroup(SiteDirective):
|
||||
name = 'user_group'
|
||||
verbose_name=_("SuexecUserGroup")
|
||||
help_text = _("<tt>user [group]</tt>, username and optional groupname.")
|
||||
regex = r'^[\w/_]+(\s[\w/_]+)*$'
|
||||
group = SiteDirective.HTTPD
|
||||
|
||||
def validate(self, directive):
|
||||
super(UserGroupDirective, self).validate(directive)
|
||||
options = directive.split()
|
||||
syetmusers = [options[0]]
|
||||
if len(options) > 1:
|
||||
systemusers.append(options[1])
|
||||
# TODO not sure about this dependency maybe make it part of pangea only
|
||||
from orchestra.apps.users.models import SystemUser
|
||||
errors = []
|
||||
for user in systemusers:
|
||||
if not SystemUser.objects.filter(username=user).exists():
|
||||
erros.append("")
|
||||
if errors:
|
||||
raise ValidationError({
|
||||
'value': errors
|
||||
})
|
||||
|
||||
|
||||
class ErrorDocument(SiteDirective):
|
||||
name = 'error_document'
|
||||
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>"
|
||||
"<tt> 401 /subscription_info.html</tt><br>"
|
||||
"<tt> 403 \"Sorry can't allow you access today\"</tt>")
|
||||
regex = r'[45]0[0-9]\s.*'
|
||||
group = SiteDirective.HTTPD
|
||||
|
||||
|
||||
class SSLCA(SiteDirective):
|
||||
name = 'ssl_ca'
|
||||
verbose_name=_("SSL CA")
|
||||
help_text = _("Filesystem path of the CA certificate file.")
|
||||
regex = r'^[^ ]+$'
|
||||
group = SiteDirective.SSL
|
||||
|
||||
|
||||
class SSLCert(SiteDirective):
|
||||
name = 'ssl_cert'
|
||||
verbose_name=_("SSL cert")
|
||||
help_text = _("Filesystem path of the certificate file.")
|
||||
regex = r'^[^ ]+$'
|
||||
group = SiteDirective.SSL
|
||||
|
||||
|
||||
class SSLKey(SiteDirective):
|
||||
name = 'ssl_key'
|
||||
verbose_name=_("SSL key")
|
||||
help_text = _("Filesystem path of the key file.")
|
||||
regex = r'^[^ ]+$'
|
||||
group = SiteDirective.SSL
|
||||
|
||||
|
||||
class SecRuleRemove(SiteDirective):
|
||||
name = 'sec_rule_remove'
|
||||
verbose_name=_("SecRuleRemoveById")
|
||||
help_text = _("Space separated ModSecurity rule IDs.")
|
||||
regex = r'^[0-9\s]+$'
|
||||
group = SiteDirective.SEC
|
||||
|
||||
|
||||
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)$'
|
||||
group = SiteDirective.SEC
|
|
@ -2,12 +2,14 @@ import re
|
|||
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.db import models
|
||||
from django.utils.functional import cached_property
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from orchestra.core import validators, services
|
||||
from orchestra.utils.functional import cached
|
||||
|
||||
from . import settings, options
|
||||
from . import settings
|
||||
from .directives import SiteDirective
|
||||
|
||||
|
||||
class Website(models.Model):
|
||||
|
@ -16,6 +18,7 @@ class Website(models.Model):
|
|||
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)
|
||||
|
@ -49,9 +52,9 @@ class Website(models.Model):
|
|||
raise TypeError('No protocol for port "%s"' % self.port)
|
||||
|
||||
@cached
|
||||
def get_options(self):
|
||||
def get_directives(self):
|
||||
return {
|
||||
opt.name: opt.value for opt in self.options.all()
|
||||
opt.name: opt.value for opt in self.directives.all()
|
||||
}
|
||||
|
||||
def get_absolute_url(self):
|
||||
|
@ -78,29 +81,34 @@ class Website(models.Model):
|
|||
return path.replace('//', '/')
|
||||
|
||||
|
||||
class WebsiteOption(models.Model):
|
||||
class Directive(models.Model):
|
||||
website = models.ForeignKey(Website, verbose_name=_("web site"),
|
||||
related_name='options')
|
||||
related_name='directives')
|
||||
name = models.CharField(_("name"), max_length=128,
|
||||
choices=((op.name, op.verbose_name) for op in options.get_enabled().values()))
|
||||
choices=SiteDirective.get_plugin_choices())
|
||||
value = models.CharField(_("value"), max_length=256)
|
||||
|
||||
class Meta:
|
||||
# unique_together = ('website', 'name')
|
||||
verbose_name = _("option")
|
||||
verbose_name_plural = _("options")
|
||||
|
||||
def __unicode__(self):
|
||||
return self.name
|
||||
|
||||
@cached_property
|
||||
def directive_class(self):
|
||||
return SiteDirective.get_plugin(self.name)
|
||||
|
||||
@cached_property
|
||||
def directive_instance(self):
|
||||
""" Per request lived directive instance """
|
||||
return self.directive_class()
|
||||
|
||||
def clean(self):
|
||||
option = options.get_enabled()[self.name]
|
||||
option.validate(self)
|
||||
self.directive_instance.validate(self)
|
||||
|
||||
|
||||
class Content(models.Model):
|
||||
webapp = models.ForeignKey('webapps.WebApp', verbose_name=_("web application"))
|
||||
website = models.ForeignKey('websites.Website', verbose_name=_("web site"))
|
||||
webapp = models.ForeignKey('webapps.WebApp', verbose_name=_("web application"),
|
||||
related_name='contents')
|
||||
website = models.ForeignKey('websites.Website', verbose_name=_("web site"),
|
||||
related_name='contents')
|
||||
path = models.CharField(_("path"), max_length=256, blank=True,
|
||||
validators=[validators.validate_url_path])
|
||||
|
||||
|
|
|
@ -1,127 +0,0 @@
|
|||
from django.core.exceptions import ValidationError
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from orchestra.utils.python import import_class
|
||||
|
||||
from . import settings
|
||||
|
||||
|
||||
# TODO multiple and unique validation support in the formset
|
||||
class SiteOption(object):
|
||||
unique = True
|
||||
|
||||
def __init__(self, name, *args, **kwargs):
|
||||
self.name = name
|
||||
self.verbose_name = kwargs.pop('verbose_name', name)
|
||||
self.help_text = kwargs.pop('help_text', '')
|
||||
for k,v in kwargs.iteritems():
|
||||
setattr(self, k, v)
|
||||
|
||||
def validate(self, website):
|
||||
if self.regex and not re.match(self.regex, website.value):
|
||||
raise ValidationError({
|
||||
'value': ValidationError(_("'%(value)s' does not match %(regex)s."),
|
||||
params={
|
||||
'value': website.value,
|
||||
'regex': self.regex
|
||||
}),
|
||||
})
|
||||
|
||||
|
||||
directory_protection = SiteOption('directory_protection',
|
||||
verbose_name=_("Directory protection"),
|
||||
help_text=_("Space separated ..."),
|
||||
regex=r'^([\w/_]+)\s+(\".*\")\s+([\w/_\.]+)$',
|
||||
)
|
||||
|
||||
redirect = SiteOption('redirect',
|
||||
verbose_name=_("Redirection"),
|
||||
help_text=_("<tt><website path> <destination URL></tt>"),
|
||||
regex=r'^[^ ]+\s[^ ]+$',
|
||||
)
|
||||
|
||||
proxy = SiteOption('proxy',
|
||||
verbose_name=_("Proxy"),
|
||||
help_text=_("<tt><website path> <target URL></tt>"),
|
||||
regex=r'^[^ ]+\shttp[^ ]+(timeout=[0-9]{1,3}|retry=[0-9]|\s)*$',
|
||||
)
|
||||
|
||||
ssl_ca = SiteOption('ssl_ca',
|
||||
verbose_name=_("SSL CA"),
|
||||
help_text=_("Filesystem path of the CA certificate file."),
|
||||
regex=r'^[^ ]+$'
|
||||
)
|
||||
|
||||
ssl_cert = SiteOption('ssl_cert',
|
||||
verbose_name=_("SSL cert"),
|
||||
help_text=_("Filesystem path of the certificate file."),
|
||||
regex=r'^[^ ]+$',
|
||||
)
|
||||
|
||||
ssl_key = SiteOption('ssl_key',
|
||||
verbose_name=_("SSL key"),
|
||||
help_text=_("Filesystem path of the key file."),
|
||||
regex=r'^[^ ]+$',
|
||||
)
|
||||
|
||||
sec_rule_remove = SiteOption('sec_rule_remove',
|
||||
verbose_name=_("SecRuleRemoveById"),
|
||||
help_text=_("Space separated ModSecurity rule IDs."),
|
||||
regex=r'^[0-9\s]+$',
|
||||
)
|
||||
|
||||
sec_engine = SiteOption('sec_engine',
|
||||
verbose_name=_("Modsecurity engine"),
|
||||
help_text=_("<tt>On</tt> or <tt>Off</tt>, defaults to On"),
|
||||
regex=r'^(On|Off)$',
|
||||
)
|
||||
|
||||
user_group = SiteOption('user_group',
|
||||
verbose_name=_("SuexecUserGroup"),
|
||||
help_text=_("<tt>user [group]</tt>, username and optional groupname."),
|
||||
# TODO validate existing user/group
|
||||
regex=r'^[\w/_]+(\s[\w/_]+)*$',
|
||||
)
|
||||
|
||||
error_document = SiteOption('error_document',
|
||||
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>"
|
||||
"<tt> 401 /subscription_info.html</tt><br>"
|
||||
"<tt> 403 \"Sorry can't allow you access today\"</tt>"),
|
||||
regex=r'[45]0[0-9]\s.*',
|
||||
)
|
||||
|
||||
|
||||
ssl = [
|
||||
ssl_ca,
|
||||
ssl_cert,
|
||||
ssl_key,
|
||||
]
|
||||
|
||||
sec = [
|
||||
sec_rule_remove,
|
||||
sec_engine,
|
||||
]
|
||||
|
||||
httpd = [
|
||||
directory_protection,
|
||||
redirect,
|
||||
proxy,
|
||||
user_group,
|
||||
error_document,
|
||||
]
|
||||
|
||||
|
||||
_enabled = None
|
||||
|
||||
def get_enabled():
|
||||
global _enabled
|
||||
if _enabled is None:
|
||||
from . import settings
|
||||
_enabled = {}
|
||||
for op in settings.WEBSITES_ENABLED_OPTIONS:
|
||||
op = import_class(op)
|
||||
_enabled[op.name] = op
|
||||
return _enabled
|
|
@ -43,7 +43,7 @@ class ContentSerializer(serializers.HyperlinkedModelSerializer):
|
|||
class WebsiteSerializer(AccountSerializerMixin, HyperlinkedModelSerializer):
|
||||
domains = RelatedDomainSerializer(many=True, allow_add_remove=True, required=False)
|
||||
contents = ContentSerializer(required=False, many=True, allow_add_remove=True,
|
||||
source='content_set')
|
||||
source='contents')
|
||||
options = OptionField(required=False)
|
||||
|
||||
class Meta:
|
||||
|
|
|
@ -14,10 +14,10 @@ WEBSITES_PORT_CHOICES = getattr(settings, 'WEBSITES_PORT_CHOICES', (
|
|||
|
||||
|
||||
WEBSITES_PROTOCOL_CHOICES = getattr(settings, 'WEBSITES_PROTOCOL_CHOICES', (
|
||||
('http', 'HTTP'),
|
||||
('https', 'HTTPS'),
|
||||
('http-https', 'HTTP and HTTPS),
|
||||
('https-only', 'HTTPS only'),
|
||||
('http', "HTTP"),
|
||||
('https', "HTTPS"),
|
||||
('http-https', _("HTTP and HTTPS")),
|
||||
('https-only', _("HTTPS only")),
|
||||
))
|
||||
|
||||
WEBSITES_DEFAULT_PORT = getattr(settings, 'WEBSITES_DEFAULT_PORT', 80)
|
||||
|
@ -29,17 +29,16 @@ WEBSITES_DEFAULT_IP = getattr(settings, 'WEBSITES_DEFAULT_IP', '*')
|
|||
WEBSITES_DOMAIN_MODEL = getattr(settings, 'WEBSITES_DOMAIN_MODEL', 'domains.Domain')
|
||||
|
||||
|
||||
WEBSITES_ENABLED_OPTIONS = getattr(settings, 'WEBSITES_ENABLED_OPTIONS', (
|
||||
'orchestra.apps.websites.options.directory_protection',
|
||||
'orchestra.apps.websites.options.redirect',
|
||||
'orchestra.apps.websites.options.proxy',
|
||||
'orchestra.apps.websites.options.ssl_ca',
|
||||
'orchestra.apps.websites.options.ssl_cert',
|
||||
'orchestra.apps.websites.options.ssl_key',
|
||||
'orchestra.apps.websites.options.sec_rule_remove',
|
||||
'orchestra.apps.websites.options.sec_engine',
|
||||
'orchestra.apps.websites.options.user_group',
|
||||
'orchestra.apps.websites.options.error_document',
|
||||
WEBSITES_ENABLED_DIRECTIVES = getattr(settings, 'WEBSITES_ENABLED_DIRECTIVES', (
|
||||
'orchestra.apps.websites.directives.Redirect',
|
||||
'orchestra.apps.websites.directives.Proxy',
|
||||
'orchestra.apps.websites.directives.UserGroup',
|
||||
'orchestra.apps.websites.directives.ErrorDocument',
|
||||
'orchestra.apps.websites.directives.SSLCA',
|
||||
'orchestra.apps.websites.directives.SSLCert',
|
||||
'orchestra.apps.websites.directives.SSLKey',
|
||||
'orchestra.apps.websites.directives.SecRuleRemove',
|
||||
'orchestra.apps.websites.directives.SecEngine',
|
||||
))
|
||||
|
||||
|
||||
|
|
|
@ -17,12 +17,12 @@ class Plugin(object):
|
|||
return cls.plugins
|
||||
|
||||
@classmethod
|
||||
@cached
|
||||
def get_plugin(cls, name):
|
||||
for plugin in cls.get_plugins():
|
||||
if plugin.get_name() == name:
|
||||
return plugin
|
||||
raise KeyError('This plugin is not registered')
|
||||
if not hasattr(cls, '_registry'):
|
||||
cls._registry = {
|
||||
plugin.get_name(): plugin for plugin in cls.get_plugins()
|
||||
}
|
||||
return cls._registry[name]
|
||||
|
||||
@classmethod
|
||||
def get_verbose_name(cls):
|
||||
|
@ -38,7 +38,9 @@ class Plugin(object):
|
|||
choices = []
|
||||
for plugin in cls.get_plugins():
|
||||
verbose = plugin.get_verbose_name()
|
||||
choices.append((plugin.get_name(), verbose))
|
||||
choices.append(
|
||||
(plugin.get_name(), verbose)
|
||||
)
|
||||
return sorted(choices, key=lambda e: e[1])
|
||||
|
||||
@classmethod
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
def cached(func):
|
||||
""" caches func return value """
|
||||
def cached_func(self, *args, **kwargs):
|
||||
attr = '_cached_' + func.__name__
|
||||
# id(self) prevents sharing within subclasses
|
||||
attr = '_cached_%s_%i' % (func.__name__, id(self))
|
||||
key = (args, tuple(kwargs.items()))
|
||||
try:
|
||||
return getattr(self, attr)[key]
|
||||
|
@ -13,4 +14,3 @@ def cached(func):
|
|||
setattr(self, attr, {key: value})
|
||||
return value
|
||||
return cached_func
|
||||
|
||||
|
|
Loading…
Reference in a new issue