mirror of
https://github.com/Mailu/Mailu.git
synced 2025-03-03 14:52:36 +02:00
Merge branch 'master' into ratelimits
This commit is contained in:
commit
7277e0b4e4
@ -66,5 +66,12 @@ $('document').ready(function() {
|
|||||||
// init clipboard.js
|
// init clipboard.js
|
||||||
new ClipboardJS('.btn-clip');
|
new ClipboardJS('.btn-clip');
|
||||||
|
|
||||||
|
// disable login if not possible
|
||||||
|
var l = $('#login_needs_https');
|
||||||
|
if (l.length && window.location.protocol != 'https:') {
|
||||||
|
l.removeClass("d-none");
|
||||||
|
$('form :input').prop('disabled', true);
|
||||||
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -30,6 +30,7 @@ def create_app_from_config(config):
|
|||||||
|
|
||||||
app.device_cookie_key = hmac.new(bytearray(app.secret_key, 'utf-8'), bytearray('DEVICE_COOKIE_KEY', 'utf-8'), 'sha256').digest()
|
app.device_cookie_key = hmac.new(bytearray(app.secret_key, 'utf-8'), bytearray('DEVICE_COOKIE_KEY', 'utf-8'), 'sha256').digest()
|
||||||
app.temp_token_key = hmac.new(bytearray(app.secret_key, 'utf-8'), bytearray('WEBMAIL_TEMP_TOKEN_KEY', 'utf-8'), 'sha256').digest()
|
app.temp_token_key = hmac.new(bytearray(app.secret_key, 'utf-8'), bytearray('WEBMAIL_TEMP_TOKEN_KEY', 'utf-8'), 'sha256').digest()
|
||||||
|
app.srs_key = hmac.new(bytearray(app.secret_key, 'utf-8'), bytearray('SRS_KEY', 'utf-8'), 'sha256').digest()
|
||||||
|
|
||||||
# Initialize list of translations
|
# Initialize list of translations
|
||||||
config.translations = {
|
config.translations = {
|
||||||
|
@ -52,6 +52,7 @@ DEFAULT_CONFIG = {
|
|||||||
'DKIM_PATH': '/dkim/{domain}.{selector}.key',
|
'DKIM_PATH': '/dkim/{domain}.{selector}.key',
|
||||||
'DEFAULT_QUOTA': 1000000000,
|
'DEFAULT_QUOTA': 1000000000,
|
||||||
'MESSAGE_RATELIMIT': '200/day',
|
'MESSAGE_RATELIMIT': '200/day',
|
||||||
|
'RECIPIENT_DELIMITER': '',
|
||||||
# Web settings
|
# Web settings
|
||||||
'SITENAME': 'Mailu',
|
'SITENAME': 'Mailu',
|
||||||
'WEBSITE': 'https://mailu.io',
|
'WEBSITE': 'https://mailu.io',
|
||||||
|
@ -5,6 +5,7 @@ import re
|
|||||||
import urllib
|
import urllib
|
||||||
import ipaddress
|
import ipaddress
|
||||||
import socket
|
import socket
|
||||||
|
import sqlalchemy.exc
|
||||||
import tenacity
|
import tenacity
|
||||||
|
|
||||||
SUPPORTED_AUTH_METHODS = ["none", "plain"]
|
SUPPORTED_AUTH_METHODS = ["none", "plain"]
|
||||||
@ -77,7 +78,7 @@ def handle_authentication(headers):
|
|||||||
# Authenticated user
|
# Authenticated user
|
||||||
elif method == "plain":
|
elif method == "plain":
|
||||||
is_valid_user = False
|
is_valid_user = False
|
||||||
if 'Auth-Port' in headers and int(urllib.parse.unquote(headers["Auth-Port"])) == 25:
|
if 'Auth-Port' in headers and urllib.parse.unquote(headers["Auth-Port"]) == '25':
|
||||||
return {
|
return {
|
||||||
"Auth-Status": "AUTH not supported",
|
"Auth-Status": "AUTH not supported",
|
||||||
"Auth-Error-Code": "502 5.5.1",
|
"Auth-Error-Code": "502 5.5.1",
|
||||||
@ -92,21 +93,26 @@ def handle_authentication(headers):
|
|||||||
try:
|
try:
|
||||||
user_email = raw_user_email.encode("iso8859-1").decode("utf8")
|
user_email = raw_user_email.encode("iso8859-1").decode("utf8")
|
||||||
password = raw_password.encode("iso8859-1").decode("utf8")
|
password = raw_password.encode("iso8859-1").decode("utf8")
|
||||||
|
ip = urllib.parse.unquote(headers["Client-Ip"])
|
||||||
except:
|
except:
|
||||||
app.logger.warn(f'Received undecodable user/password from nginx: {raw_user_email!r}/{raw_password!r}')
|
app.logger.warn(f'Received undecodable user/password from nginx: {raw_user_email!r}/{raw_password!r}')
|
||||||
else:
|
else:
|
||||||
user = models.User.query.get(user_email)
|
try:
|
||||||
is_valid_user = True
|
user = models.User.query.get(user_email)
|
||||||
ip = urllib.parse.unquote(headers["Client-Ip"])
|
is_valid_user = True
|
||||||
if check_credentials(user, password, ip, protocol):
|
except sqlalchemy.exc.StatementError as exc:
|
||||||
server, port = get_server(headers["Auth-Protocol"], True)
|
exc = str(exc).split('\n', 1)[0]
|
||||||
return {
|
app.logger.warn(f'Invalid user {user_email!r}: {exc}')
|
||||||
"Auth-Status": "OK",
|
else:
|
||||||
"Auth-Server": server,
|
if check_credentials(user, password, ip, protocol):
|
||||||
"Auth-User": user_email,
|
server, port = get_server(headers["Auth-Protocol"], True)
|
||||||
"Auth-User-Exists": is_valid_user,
|
return {
|
||||||
"Auth-Port": port
|
"Auth-Status": "OK",
|
||||||
}
|
"Auth-Server": server,
|
||||||
|
"Auth-User": user_email,
|
||||||
|
"Auth-User-Exists": is_valid_user,
|
||||||
|
"Auth-Port": port
|
||||||
|
}
|
||||||
status, code = get_status(protocol, "authentication")
|
status, code = get_status(protocol, "authentication")
|
||||||
return {
|
return {
|
||||||
"Auth-Status": status,
|
"Auth-Status": status,
|
||||||
|
@ -108,7 +108,7 @@ def postfix_recipient_map(recipient):
|
|||||||
|
|
||||||
This is meant for bounces to go back to the original sender.
|
This is meant for bounces to go back to the original sender.
|
||||||
"""
|
"""
|
||||||
srs = srslib.SRS(flask.current_app.config["SECRET_KEY"])
|
srs = srslib.SRS(flask.current_app.srs_key)
|
||||||
if srslib.SRS.is_srs_address(recipient):
|
if srslib.SRS.is_srs_address(recipient):
|
||||||
try:
|
try:
|
||||||
return flask.jsonify(srs.reverse(recipient))
|
return flask.jsonify(srs.reverse(recipient))
|
||||||
@ -123,7 +123,7 @@ def postfix_sender_map(sender):
|
|||||||
|
|
||||||
This is for bounces to come back the reverse path properly.
|
This is for bounces to come back the reverse path properly.
|
||||||
"""
|
"""
|
||||||
srs = srslib.SRS(flask.current_app.config["SECRET_KEY"])
|
srs = srslib.SRS(flask.current_app.srs_key)
|
||||||
domain = flask.current_app.config["DOMAIN"]
|
domain = flask.current_app.config["DOMAIN"]
|
||||||
try:
|
try:
|
||||||
localpart, domain_name = models.Email.resolve_domain(sender)
|
localpart, domain_name = models.Email.resolve_domain(sender)
|
||||||
@ -140,6 +140,7 @@ def postfix_sender_login(sender):
|
|||||||
localpart, domain_name = models.Email.resolve_domain(sender)
|
localpart, domain_name = models.Email.resolve_domain(sender)
|
||||||
if localpart is None:
|
if localpart is None:
|
||||||
return flask.jsonify(",".join(wildcard_senders)) if wildcard_senders else flask.abort(404)
|
return flask.jsonify(",".join(wildcard_senders)) if wildcard_senders else flask.abort(404)
|
||||||
|
localpart = localpart[:next((i for i, ch in enumerate(localpart) if ch in flask.current_app.config.get('RECIPIENT_DELIMITER')), None)]
|
||||||
destination = models.Email.resolve_destination(localpart, domain_name, True)
|
destination = models.Email.resolve_destination(localpart, domain_name, True)
|
||||||
destination = [*destination, *wildcard_senders] if destination else [*wildcard_senders]
|
destination = [*destination, *wildcard_senders] if destination else [*wildcard_senders]
|
||||||
return flask.jsonify(",".join(destination)) if destination else flask.abort(404)
|
return flask.jsonify(",".join(destination)) if destination else flask.abort(404)
|
||||||
|
@ -57,6 +57,8 @@ class IdnaEmail(db.TypeDecorator):
|
|||||||
|
|
||||||
def process_bind_param(self, value, dialect):
|
def process_bind_param(self, value, dialect):
|
||||||
""" encode unicode domain part of email address to punycode """
|
""" encode unicode domain part of email address to punycode """
|
||||||
|
if not '@' in value:
|
||||||
|
raise ValueError('invalid email address (no "@")')
|
||||||
localpart, domain_name = value.lower().rsplit('@', 1)
|
localpart, domain_name = value.lower().rsplit('@', 1)
|
||||||
if '@' in localpart:
|
if '@' in localpart:
|
||||||
raise ValueError('email local part must not contain "@"')
|
raise ValueError('email local part must not contain "@"')
|
||||||
|
@ -7,3 +7,12 @@
|
|||||||
{%- block subtitle %}
|
{%- block subtitle %}
|
||||||
{% trans %}to access the administration tools{% endtrans %}
|
{% trans %}to access the administration tools{% endtrans %}
|
||||||
{%- endblock %}
|
{%- endblock %}
|
||||||
|
|
||||||
|
{%- block content %}
|
||||||
|
{% if config["SESSION_COOKIE_SECURE"] %}
|
||||||
|
<div id="login_needs_https" class="alert alert-danger d-none" role="alert">
|
||||||
|
{% trans %}The login form has been disabled as <b>SESSION_COOKIE_SECURE</b> is on but you are accessing Mailu over HTTP.{% endtrans %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{{ super() }}
|
||||||
|
{%- endblock %}
|
||||||
|
@ -60,7 +60,7 @@ def has_dane_record(domain, timeout=10):
|
|||||||
# we will receive this non-specific exception. The safe behaviour is to
|
# we will receive this non-specific exception. The safe behaviour is to
|
||||||
# accept to defer the email.
|
# accept to defer the email.
|
||||||
flask.current_app.logger.warn(f'Unable to lookup the TLSA record for {domain}. Is the DNSSEC zone okay on https://dnsviz.net/d/{domain}/dnssec/?')
|
flask.current_app.logger.warn(f'Unable to lookup the TLSA record for {domain}. Is the DNSSEC zone okay on https://dnsviz.net/d/{domain}/dnssec/?')
|
||||||
return app.config['DEFER_ON_TLS_ERROR']
|
return flask.current_app.config['DEFER_ON_TLS_ERROR']
|
||||||
except dns.exception.Timeout:
|
except dns.exception.Timeout:
|
||||||
flask.current_app.logger.warn(f'Timeout while resolving the TLSA record for {domain} ({timeout}s).')
|
flask.current_app.logger.warn(f'Timeout while resolving the TLSA record for {domain} ({timeout}s).')
|
||||||
except dns.resolver.NXDOMAIN:
|
except dns.resolver.NXDOMAIN:
|
||||||
|
@ -1,17 +1,8 @@
|
|||||||
# This configuration was copied from Mailinabox. The original version is available at:
|
# This configuration was copied from Mailinabox. The original version is available at:
|
||||||
# https://raw.githubusercontent.com/mail-in-a-box/mailinabox/master/conf/postfix_outgoing_mail_header_filters
|
# https://raw.githubusercontent.com/mail-in-a-box/mailinabox/master/conf/postfix_outgoing_mail_header_filters
|
||||||
|
|
||||||
# Remove the first line of the Received: header. Note that we cannot fully remove the Received: header
|
# Remove typically private information.
|
||||||
# because OpenDKIM requires that a header be present when signing outbound mail. The first line is
|
/^\s*(Received|User-Agent|X-(Enigmail|Mailer|Originating-IP|Pgp-Agent)):/ IGNORE
|
||||||
# where the user's home IP address would be.
|
|
||||||
/^\s*Received:[^\n]*(.*)/ REPLACE Received: from authenticated-user ({{OUTCLEAN}} [{{OUTCLEAN_ADDRESS}}])$1
|
|
||||||
|
|
||||||
# Remove other typically private information.
|
|
||||||
/^\s*User-Agent:/ IGNORE
|
|
||||||
/^\s*X-Enigmail:/ IGNORE
|
|
||||||
/^\s*X-Mailer:/ IGNORE
|
|
||||||
/^\s*X-Originating-IP:/ IGNORE
|
|
||||||
/^\s*X-Pgp-Agent:/ IGNORE
|
|
||||||
|
|
||||||
# The Mime-Version header can leak the user agent too, e.g. in Mime-Version: 1.0 (Mac OS X Mail 8.1 \(2010.6\)).
|
# The Mime-Version header can leak the user agent too, e.g. in Mime-Version: 1.0 (Mac OS X Mail 8.1 \(2010.6\)).
|
||||||
/^\s*(Mime-Version:\s*[0-9\.]+)\s.+/ REPLACE $1
|
/^\s*(Mime-Version:\s*[0-9\.]+)\s.+/ REPLACE $1
|
||||||
|
@ -46,15 +46,6 @@ os.environ["FRONT_ADDRESS"] = system.get_host_address_from_environment("FRONT",
|
|||||||
os.environ["ADMIN_ADDRESS"] = system.get_host_address_from_environment("ADMIN", "admin")
|
os.environ["ADMIN_ADDRESS"] = system.get_host_address_from_environment("ADMIN", "admin")
|
||||||
os.environ["ANTISPAM_MILTER_ADDRESS"] = system.get_host_address_from_environment("ANTISPAM_MILTER", "antispam:11332")
|
os.environ["ANTISPAM_MILTER_ADDRESS"] = system.get_host_address_from_environment("ANTISPAM_MILTER", "antispam:11332")
|
||||||
os.environ["LMTP_ADDRESS"] = system.get_host_address_from_environment("LMTP", "imap:2525")
|
os.environ["LMTP_ADDRESS"] = system.get_host_address_from_environment("LMTP", "imap:2525")
|
||||||
os.environ["OUTCLEAN"] = os.environ["HOSTNAMES"].split(",")[0]
|
|
||||||
try:
|
|
||||||
_to_lookup = os.environ["OUTCLEAN"]
|
|
||||||
# Ensure we lookup a FQDN: @see #1884
|
|
||||||
if not _to_lookup.endswith('.'):
|
|
||||||
_to_lookup += '.'
|
|
||||||
os.environ["OUTCLEAN_ADDRESS"] = system.resolve_hostname(_to_lookup)
|
|
||||||
except:
|
|
||||||
os.environ["OUTCLEAN_ADDRESS"] = "10.10.10.10"
|
|
||||||
|
|
||||||
for postfix_file in glob.glob("/conf/*.cf"):
|
for postfix_file in glob.glob("/conf/*.cf"):
|
||||||
conf.jinja(postfix_file, os.environ, os.path.join("/etc/postfix", os.path.basename(postfix_file)))
|
conf.jinja(postfix_file, os.environ, os.path.join("/etc/postfix", os.path.basename(postfix_file)))
|
||||||
|
@ -100,9 +100,10 @@ go and fetch new email if available. Do not use too short delays if you do not
|
|||||||
want to be blacklisted by external services, but not too long delays if you
|
want to be blacklisted by external services, but not too long delays if you
|
||||||
want to receive your email in time.
|
want to receive your email in time.
|
||||||
|
|
||||||
The ``RECIPIENT_DELIMITER`` is a character used to delimit localpart from a
|
The ``RECIPIENT_DELIMITER`` is a list of characters used to delimit localpart
|
||||||
custom address part. For instance, if set to ``+``, users can use addresses
|
from a custom address part. For instance, if set to ``+-``, users can use
|
||||||
like ``localpart+custom@domain.tld`` to deliver mail to ``localpart@domain.tld``.
|
addresses like ``localpart+custom@example.com`` or ``localpart-custom@example.com``
|
||||||
|
to deliver mail to ``localpart@example.com``.
|
||||||
This is useful to provide external parties with different email addresses and
|
This is useful to provide external parties with different email addresses and
|
||||||
later classify incoming mail based on the custom part.
|
later classify incoming mail based on the custom part.
|
||||||
|
|
||||||
|
@ -1,19 +1,20 @@
|
|||||||
server:
|
server:
|
||||||
verbosity: 1
|
verbosity: 1
|
||||||
interface: 0.0.0.0
|
interface: 0.0.0.0
|
||||||
interface: ::0
|
{{ 'interface: ::0' if SUBNET6 }}
|
||||||
logfile: ""
|
logfile: ""
|
||||||
do-ip4: yes
|
do-ip4: yes
|
||||||
do-ip6: yes
|
do-ip6: {{ 'yes' if SUBNET6 else 'no' }}
|
||||||
do-udp: yes
|
do-udp: yes
|
||||||
do-tcp: yes
|
do-tcp: yes
|
||||||
do-daemonize: no
|
do-daemonize: no
|
||||||
access-control: {{ SUBNET }} allow
|
access-control: {{ SUBNET }} allow
|
||||||
|
{{ 'access-control: {{ SUBNET6 }} allow' if SUBNET6 }}
|
||||||
directory: "/etc/unbound"
|
directory: "/etc/unbound"
|
||||||
username: unbound
|
username: unbound
|
||||||
auto-trust-anchor-file: trusted-key.key
|
auto-trust-anchor-file: trusted-key.key
|
||||||
root-hints: "/etc/unbound/root.hints"
|
root-hints: "/etc/unbound/root.hints"
|
||||||
hide-identity: yes
|
hide-identity: yes
|
||||||
hide-version: yes
|
hide-version: yes
|
||||||
max-udp-size: 4096
|
cache-min-ttl: 300
|
||||||
msg-buffer-size: 65552
|
|
||||||
|
1
towncrier/newsfragments/1990.bugfix
Normal file
1
towncrier/newsfragments/1990.bugfix
Normal file
@ -0,0 +1 @@
|
|||||||
|
Fixed roundcube sso login not working.
|
3
towncrier/newsfragments/1992.enhancement
Normal file
3
towncrier/newsfragments/1992.enhancement
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
Make unbound work with ipv6
|
||||||
|
Add a cache-min-ttl of 5minutes
|
||||||
|
Enable qname minimisation (privacy)
|
1
towncrier/newsfragments/1996.enhancement
Normal file
1
towncrier/newsfragments/1996.enhancement
Normal file
@ -0,0 +1 @@
|
|||||||
|
Disable the login page if SESSION_COOKIE_SECURE is incompatible with how Mailu is accessed as this seems to be a common misconfiguration.
|
1
towncrier/newsfragments/2002.enhancement
Normal file
1
towncrier/newsfragments/2002.enhancement
Normal file
@ -0,0 +1 @@
|
|||||||
|
Derive a new subkey (from SECRET_KEY) for SRS
|
1
towncrier/newsfragments/2007.enhancement
Normal file
1
towncrier/newsfragments/2007.enhancement
Normal file
@ -0,0 +1 @@
|
|||||||
|
allow sending emails as user+detail@domain.tld
|
1
towncrier/newsfragments/466.feature
Normal file
1
towncrier/newsfragments/466.feature
Normal file
@ -0,0 +1 @@
|
|||||||
|
Remove the Received header with PRIMARY_HOSTNAME [PUBLIC_IP]
|
@ -11,7 +11,8 @@ FROM build_${QEMU}
|
|||||||
RUN apt-get update && apt-get install -y \
|
RUN apt-get update && apt-get install -y \
|
||||||
python3 curl python3-pip git python3-multidict \
|
python3 curl python3-pip git python3-multidict \
|
||||||
&& rm -rf /var/lib/apt/lists \
|
&& rm -rf /var/lib/apt/lists \
|
||||||
&& echo "ServerSignature Off" >> /etc/apache2/apache2.conf
|
&& echo "ServerSignature Off\nServerName roundcube" >> /etc/apache2/apache2.conf \
|
||||||
|
&& sed -i 's,CustomLog.*combined$,\0 "'"expr=!(%{HTTP_USER_AGENT}=='health'\&\&(-R '127.0.0.1/8' || -R '::1'))"'",' /etc/apache2/sites-available/000-default.conf
|
||||||
|
|
||||||
# Shared layer between nginx, dovecot, postfix, postgresql, rspamd, unbound, rainloop, roundcube
|
# Shared layer between nginx, dovecot, postfix, postgresql, rspamd, unbound, rainloop, roundcube
|
||||||
RUN pip3 install socrate
|
RUN pip3 install socrate
|
||||||
@ -33,11 +34,15 @@ RUN apt-get update && apt-get install -y \
|
|||||||
&& mv roundcubemail-* html \
|
&& mv roundcubemail-* html \
|
||||||
&& mv carddav html/plugins/ \
|
&& mv carddav html/plugins/ \
|
||||||
&& cd html \
|
&& cd html \
|
||||||
&& rm -rf CHANGELOG INSTALL LICENSE README.md UPGRADING composer.json-dist installer \
|
&& rm -rf CHANGELOG INSTALL LICENSE README.md UPGRADING composer.json-dist installer composer.* \
|
||||||
&& sed -i 's,mod_php5.c,mod_php7.c,g' .htaccess \
|
&& sed -i 's,mod_php5.c,mod_php7.c,g' .htaccess \
|
||||||
&& sed -i 's,^php_value.*post_max_size,#&,g' .htaccess \
|
&& sed -i 's,^php_value.*post_max_size,#&,g' .htaccess \
|
||||||
&& sed -i 's,^php_value.*upload_max_filesize,#&,g' .htaccess \
|
&& sed -i 's,^php_value.*upload_max_filesize,#&,g' .htaccess \
|
||||||
&& chown -R www-data: logs temp \
|
&& ln -sf index.php /var/www/html/sso.php \
|
||||||
|
&& ln -sf /dev/stderr /var/www/html/logs/errors.log \
|
||||||
|
&& chown -R root:root . \
|
||||||
|
&& chown www-data:www-data logs temp \
|
||||||
|
&& chmod -R a+rX . \
|
||||||
&& rm -rf /var/lib/apt/lists \
|
&& rm -rf /var/lib/apt/lists \
|
||||||
&& a2enmod rewrite deflate expires headers
|
&& a2enmod rewrite deflate expires headers
|
||||||
|
|
||||||
@ -51,4 +56,4 @@ VOLUME ["/data"]
|
|||||||
|
|
||||||
CMD /start.py
|
CMD /start.py
|
||||||
|
|
||||||
HEALTHCHECK CMD curl -f -L http://localhost/ || exit 1
|
HEALTHCHECK CMD curl -f -L -H 'User-Agent: health' http://localhost/ || exit 1
|
||||||
|
@ -52,6 +52,12 @@ class mailu extends rcube_plugin
|
|||||||
}
|
}
|
||||||
function login_failed($args)
|
function login_failed($args)
|
||||||
{
|
{
|
||||||
|
$ua = $_SERVER['HTTP_USER_AGENT'];
|
||||||
|
$ra = $_SERVER['REMOTE_ADDR'];
|
||||||
|
if ($ua == 'health' and ($ra == '127.0.0.1' or $ra == '::1')) {
|
||||||
|
echo "OK";
|
||||||
|
exit;
|
||||||
|
}
|
||||||
header('Location: sso.php');
|
header('Location: sso.php');
|
||||||
exit();
|
exit();
|
||||||
}
|
}
|
||||||
|
@ -34,11 +34,7 @@ else:
|
|||||||
conf.jinja("/php.ini", os.environ, "/usr/local/etc/php/conf.d/roundcube.ini")
|
conf.jinja("/php.ini", os.environ, "/usr/local/etc/php/conf.d/roundcube.ini")
|
||||||
|
|
||||||
# Create dirs, setup permissions
|
# Create dirs, setup permissions
|
||||||
os.system("mkdir -p /data/gpg /var/www/html/logs")
|
os.system("mkdir -p /data/gpg")
|
||||||
os.system("touch /var/www/html/logs/errors.log")
|
|
||||||
os.system("chown -R www-data:www-data /var/www/html/logs")
|
|
||||||
os.system("chmod -R a+rX /var/www/html/")
|
|
||||||
os.system("ln -sf /var/www/html/index.php /var/www/html/sso.php")
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
print("Initializing database")
|
print("Initializing database")
|
||||||
@ -61,8 +57,5 @@ except subprocess.CalledProcessError as e:
|
|||||||
# Setup database permissions
|
# Setup database permissions
|
||||||
os.system("chown -R www-data:www-data /data")
|
os.system("chown -R www-data:www-data /data")
|
||||||
|
|
||||||
# Tail roundcube logs
|
|
||||||
subprocess.Popen(["tail", "-f", "-n", "0", "/var/www/html/logs/errors.log"])
|
|
||||||
|
|
||||||
# Run apache
|
# Run apache
|
||||||
os.execv("/usr/local/bin/apache2-foreground", ["apache2-foreground"])
|
os.execv("/usr/local/bin/apache2-foreground", ["apache2-foreground"])
|
||||||
|
Loading…
x
Reference in New Issue
Block a user